diff --git a/assets/data/search-index.json b/assets/data/search-index.json index 026a71cf..c90b471d 100644 --- a/assets/data/search-index.json +++ b/assets/data/search-index.json @@ -1 +1 @@ -{"v":2,"n":2245,"o":{"fields":["t","d","i","b"],"storeFields":["u","d","k","t","x","e"],"idField":"id"},"i":{"documentCount":2245,"nextId":2245,"documentIds":{"0":0,"1":1,"2":2,"3":3,"4":4,"5":5,"6":6,"7":7,"8":8,"9":9,"10":10,"11":11,"12":12,"13":13,"14":14,"15":15,"16":16,"17":17,"18":18,"19":19,"20":20,"21":21,"22":22,"23":23,"24":24,"25":25,"26":26,"27":27,"28":28,"29":29,"30":30,"31":31,"32":32,"33":33,"34":34,"35":35,"36":36,"37":37,"38":38,"39":39,"40":40,"41":41,"42":42,"43":43,"44":44,"45":45,"46":46,"47":47,"48":48,"49":49,"50":50,"51":51,"52":52,"53":53,"54":54,"55":55,"56":56,"57":57,"58":58,"59":59,"60":60,"61":61,"62":62,"63":63,"64":64,"65":65,"66":66,"67":67,"68":68,"69":69,"70":70,"71":71,"72":72,"73":73,"74":74,"75":75,"76":76,"77":77,"78":78,"79":79,"80":80,"81":81,"82":82,"83":83,"84":84,"85":85,"86":86,"87":87,"88":88,"89":89,"90":90,"91":91,"92":92,"93":93,"94":94,"95":95,"96":96,"97":97,"98":98,"99":99,"100":100,"101":101,"102":102,"103":103,"104":104,"105":105,"106":106,"107":107,"108":108,"109":109,"110":110,"111":111,"112":112,"113":113,"114":114,"115":115,"116":116,"117":117,"118":118,"119":119,"120":120,"121":121,"122":122,"123":123,"124":124,"125":125,"126":126,"127":127,"128":128,"129":129,"130":130,"131":131,"132":132,"133":133,"134":134,"135":135,"136":136,"137":137,"138":138,"139":139,"140":140,"141":141,"142":142,"143":143,"144":144,"145":145,"146":146,"147":147,"148":148,"149":149,"150":150,"151":151,"152":152,"153":153,"154":154,"155":155,"156":156,"157":157,"158":158,"159":159,"160":160,"161":161,"162":162,"163":163,"164":164,"165":165,"166":166,"167":167,"168":168,"169":169,"170":170,"171":171,"172":172,"173":173,"174":174,"175":175,"176":176,"177":177,"178":178,"179":179,"180":180,"181":181,"182":182,"183":183,"184":184,"185":185,"186":186,"187":187,"188":188,"189":189,"190":190,"191":191,"192":192,"193":193,"194":194,"195":195,"196":196,"197":197,"198":198,"199":199,"200":200,"201":201,"202":202,"203":203,"204":204,"205":205,"206":206,"207":207,"208":208,"209":209,"210":210,"211":211,"212":212,"213":213,"214":214,"215":215,"216":216,"217":217,"218":218,"219":219,"220":220,"221":221,"222":222,"223":223,"224":224,"225":225,"226":226,"227":227,"228":228,"229":229,"230":230,"231":231,"232":232,"233":233,"234":234,"235":235,"236":236,"237":237,"238":238,"239":239,"240":240,"241":241,"242":242,"243":243,"244":244,"245":245,"246":246,"247":247,"248":248,"249":249,"250":250,"251":251,"252":252,"253":253,"254":254,"255":255,"256":256,"257":257,"258":258,"259":259,"260":260,"261":261,"262":262,"263":263,"264":264,"265":265,"266":266,"267":267,"268":268,"269":269,"270":270,"271":271,"272":272,"273":273,"274":274,"275":275,"276":276,"277":277,"278":278,"279":279,"280":280,"281":281,"282":282,"283":283,"284":284,"285":285,"286":286,"287":287,"288":288,"289":289,"290":290,"291":291,"292":292,"293":293,"294":294,"295":295,"296":296,"297":297,"298":298,"299":299,"300":300,"301":301,"302":302,"303":303,"304":304,"305":305,"306":306,"307":307,"308":308,"309":309,"310":310,"311":311,"312":312,"313":313,"314":314,"315":315,"316":316,"317":317,"318":318,"319":319,"320":320,"321":321,"322":322,"323":323,"324":324,"325":325,"326":326,"327":327,"328":328,"329":329,"330":330,"331":331,"332":332,"333":333,"334":334,"335":335,"336":336,"337":337,"338":338,"339":339,"340":340,"341":341,"342":342,"343":343,"344":344,"345":345,"346":346,"347":347,"348":348,"349":349,"350":350,"351":351,"352":352,"353":353,"354":354,"355":355,"356":356,"357":357,"358":358,"359":359,"360":360,"361":361,"362":362,"363":363,"364":364,"365":365,"366":366,"367":367,"368":368,"369":369,"370":370,"371":371,"372":372,"373":373,"374":374,"375":375,"376":376,"377":377,"378":378,"379":379,"380":380,"381":381,"382":382,"383":383,"384":384,"385":385,"386":386,"387":387,"388":388,"389":389,"390":390,"391":391,"392":392,"393":393,"394":394,"395":395,"396":396,"397":397,"398":398,"399":399,"400":400,"401":401,"402":402,"403":403,"404":404,"405":405,"406":406,"407":407,"408":408,"409":409,"410":410,"411":411,"412":412,"413":413,"414":414,"415":415,"416":416,"417":417,"418":418,"419":419,"420":420,"421":421,"422":422,"423":423,"424":424,"425":425,"426":426,"427":427,"428":428,"429":429,"430":430,"431":431,"432":432,"433":433,"434":434,"435":435,"436":436,"437":437,"438":438,"439":439,"440":440,"441":441,"442":442,"443":443,"444":444,"445":445,"446":446,"447":447,"448":448,"449":449,"450":450,"451":451,"452":452,"453":453,"454":454,"455":455,"456":456,"457":457,"458":458,"459":459,"460":460,"461":461,"462":462,"463":463,"464":464,"465":465,"466":466,"467":467,"468":468,"469":469,"470":470,"471":471,"472":472,"473":473,"474":474,"475":475,"476":476,"477":477,"478":478,"479":479,"480":480,"481":481,"482":482,"483":483,"484":484,"485":485,"486":486,"487":487,"488":488,"489":489,"490":490,"491":491,"492":492,"493":493,"494":494,"495":495,"496":496,"497":497,"498":498,"499":499,"500":500,"501":501,"502":502,"503":503,"504":504,"505":505,"506":506,"507":507,"508":508,"509":509,"510":510,"511":511,"512":512,"513":513,"514":514,"515":515,"516":516,"517":517,"518":518,"519":519,"520":520,"521":521,"522":522,"523":523,"524":524,"525":525,"526":526,"527":527,"528":528,"529":529,"530":530,"531":531,"532":532,"533":533,"534":534,"535":535,"536":536,"537":537,"538":538,"539":539,"540":540,"541":541,"542":542,"543":543,"544":544,"545":545,"546":546,"547":547,"548":548,"549":549,"550":550,"551":551,"552":552,"553":553,"554":554,"555":555,"556":556,"557":557,"558":558,"559":559,"560":560,"561":561,"562":562,"563":563,"564":564,"565":565,"566":566,"567":567,"568":568,"569":569,"570":570,"571":571,"572":572,"573":573,"574":574,"575":575,"576":576,"577":577,"578":578,"579":579,"580":580,"581":581,"582":582,"583":583,"584":584,"585":585,"586":586,"587":587,"588":588,"589":589,"590":590,"591":591,"592":592,"593":593,"594":594,"595":595,"596":596,"597":597,"598":598,"599":599,"600":600,"601":601,"602":602,"603":603,"604":604,"605":605,"606":606,"607":607,"608":608,"609":609,"610":610,"611":611,"612":612,"613":613,"614":614,"615":615,"616":616,"617":617,"618":618,"619":619,"620":620,"621":621,"622":622,"623":623,"624":624,"625":625,"626":626,"627":627,"628":628,"629":629,"630":630,"631":631,"632":632,"633":633,"634":634,"635":635,"636":636,"637":637,"638":638,"639":639,"640":640,"641":641,"642":642,"643":643,"644":644,"645":645,"646":646,"647":647,"648":648,"649":649,"650":650,"651":651,"652":652,"653":653,"654":654,"655":655,"656":656,"657":657,"658":658,"659":659,"660":660,"661":661,"662":662,"663":663,"664":664,"665":665,"666":666,"667":667,"668":668,"669":669,"670":670,"671":671,"672":672,"673":673,"674":674,"675":675,"676":676,"677":677,"678":678,"679":679,"680":680,"681":681,"682":682,"683":683,"684":684,"685":685,"686":686,"687":687,"688":688,"689":689,"690":690,"691":691,"692":692,"693":693,"694":694,"695":695,"696":696,"697":697,"698":698,"699":699,"700":700,"701":701,"702":702,"703":703,"704":704,"705":705,"706":706,"707":707,"708":708,"709":709,"710":710,"711":711,"712":712,"713":713,"714":714,"715":715,"716":716,"717":717,"718":718,"719":719,"720":720,"721":721,"722":722,"723":723,"724":724,"725":725,"726":726,"727":727,"728":728,"729":729,"730":730,"731":731,"732":732,"733":733,"734":734,"735":735,"736":736,"737":737,"738":738,"739":739,"740":740,"741":741,"742":742,"743":743,"744":744,"745":745,"746":746,"747":747,"748":748,"749":749,"750":750,"751":751,"752":752,"753":753,"754":754,"755":755,"756":756,"757":757,"758":758,"759":759,"760":760,"761":761,"762":762,"763":763,"764":764,"765":765,"766":766,"767":767,"768":768,"769":769,"770":770,"771":771,"772":772,"773":773,"774":774,"775":775,"776":776,"777":777,"778":778,"779":779,"780":780,"781":781,"782":782,"783":783,"784":784,"785":785,"786":786,"787":787,"788":788,"789":789,"790":790,"791":791,"792":792,"793":793,"794":794,"795":795,"796":796,"797":797,"798":798,"799":799,"800":800,"801":801,"802":802,"803":803,"804":804,"805":805,"806":806,"807":807,"808":808,"809":809,"810":810,"811":811,"812":812,"813":813,"814":814,"815":815,"816":816,"817":817,"818":818,"819":819,"820":820,"821":821,"822":822,"823":823,"824":824,"825":825,"826":826,"827":827,"828":828,"829":829,"830":830,"831":831,"832":832,"833":833,"834":834,"835":835,"836":836,"837":837,"838":838,"839":839,"840":840,"841":841,"842":842,"843":843,"844":844,"845":845,"846":846,"847":847,"848":848,"849":849,"850":850,"851":851,"852":852,"853":853,"854":854,"855":855,"856":856,"857":857,"858":858,"859":859,"860":860,"861":861,"862":862,"863":863,"864":864,"865":865,"866":866,"867":867,"868":868,"869":869,"870":870,"871":871,"872":872,"873":873,"874":874,"875":875,"876":876,"877":877,"878":878,"879":879,"880":880,"881":881,"882":882,"883":883,"884":884,"885":885,"886":886,"887":887,"888":888,"889":889,"890":890,"891":891,"892":892,"893":893,"894":894,"895":895,"896":896,"897":897,"898":898,"899":899,"900":900,"901":901,"902":902,"903":903,"904":904,"905":905,"906":906,"907":907,"908":908,"909":909,"910":910,"911":911,"912":912,"913":913,"914":914,"915":915,"916":916,"917":917,"918":918,"919":919,"920":920,"921":921,"922":922,"923":923,"924":924,"925":925,"926":926,"927":927,"928":928,"929":929,"930":930,"931":931,"932":932,"933":933,"934":934,"935":935,"936":936,"937":937,"938":938,"939":939,"940":940,"941":941,"942":942,"943":943,"944":944,"945":945,"946":946,"947":947,"948":948,"949":949,"950":950,"951":951,"952":952,"953":953,"954":954,"955":955,"956":956,"957":957,"958":958,"959":959,"960":960,"961":961,"962":962,"963":963,"964":964,"965":965,"966":966,"967":967,"968":968,"969":969,"970":970,"971":971,"972":972,"973":973,"974":974,"975":975,"976":976,"977":977,"978":978,"979":979,"980":980,"981":981,"982":982,"983":983,"984":984,"985":985,"986":986,"987":987,"988":988,"989":989,"990":990,"991":991,"992":992,"993":993,"994":994,"995":995,"996":996,"997":997,"998":998,"999":999,"1000":1000,"1001":1001,"1002":1002,"1003":1003,"1004":1004,"1005":1005,"1006":1006,"1007":1007,"1008":1008,"1009":1009,"1010":1010,"1011":1011,"1012":1012,"1013":1013,"1014":1014,"1015":1015,"1016":1016,"1017":1017,"1018":1018,"1019":1019,"1020":1020,"1021":1021,"1022":1022,"1023":1023,"1024":1024,"1025":1025,"1026":1026,"1027":1027,"1028":1028,"1029":1029,"1030":1030,"1031":1031,"1032":1032,"1033":1033,"1034":1034,"1035":1035,"1036":1036,"1037":1037,"1038":1038,"1039":1039,"1040":1040,"1041":1041,"1042":1042,"1043":1043,"1044":1044,"1045":1045,"1046":1046,"1047":1047,"1048":1048,"1049":1049,"1050":1050,"1051":1051,"1052":1052,"1053":1053,"1054":1054,"1055":1055,"1056":1056,"1057":1057,"1058":1058,"1059":1059,"1060":1060,"1061":1061,"1062":1062,"1063":1063,"1064":1064,"1065":1065,"1066":1066,"1067":1067,"1068":1068,"1069":1069,"1070":1070,"1071":1071,"1072":1072,"1073":1073,"1074":1074,"1075":1075,"1076":1076,"1077":1077,"1078":1078,"1079":1079,"1080":1080,"1081":1081,"1082":1082,"1083":1083,"1084":1084,"1085":1085,"1086":1086,"1087":1087,"1088":1088,"1089":1089,"1090":1090,"1091":1091,"1092":1092,"1093":1093,"1094":1094,"1095":1095,"1096":1096,"1097":1097,"1098":1098,"1099":1099,"1100":1100,"1101":1101,"1102":1102,"1103":1103,"1104":1104,"1105":1105,"1106":1106,"1107":1107,"1108":1108,"1109":1109,"1110":1110,"1111":1111,"1112":1112,"1113":1113,"1114":1114,"1115":1115,"1116":1116,"1117":1117,"1118":1118,"1119":1119,"1120":1120,"1121":1121,"1122":1122,"1123":1123,"1124":1124,"1125":1125,"1126":1126,"1127":1127,"1128":1128,"1129":1129,"1130":1130,"1131":1131,"1132":1132,"1133":1133,"1134":1134,"1135":1135,"1136":1136,"1137":1137,"1138":1138,"1139":1139,"1140":1140,"1141":1141,"1142":1142,"1143":1143,"1144":1144,"1145":1145,"1146":1146,"1147":1147,"1148":1148,"1149":1149,"1150":1150,"1151":1151,"1152":1152,"1153":1153,"1154":1154,"1155":1155,"1156":1156,"1157":1157,"1158":1158,"1159":1159,"1160":1160,"1161":1161,"1162":1162,"1163":1163,"1164":1164,"1165":1165,"1166":1166,"1167":1167,"1168":1168,"1169":1169,"1170":1170,"1171":1171,"1172":1172,"1173":1173,"1174":1174,"1175":1175,"1176":1176,"1177":1177,"1178":1178,"1179":1179,"1180":1180,"1181":1181,"1182":1182,"1183":1183,"1184":1184,"1185":1185,"1186":1186,"1187":1187,"1188":1188,"1189":1189,"1190":1190,"1191":1191,"1192":1192,"1193":1193,"1194":1194,"1195":1195,"1196":1196,"1197":1197,"1198":1198,"1199":1199,"1200":1200,"1201":1201,"1202":1202,"1203":1203,"1204":1204,"1205":1205,"1206":1206,"1207":1207,"1208":1208,"1209":1209,"1210":1210,"1211":1211,"1212":1212,"1213":1213,"1214":1214,"1215":1215,"1216":1216,"1217":1217,"1218":1218,"1219":1219,"1220":1220,"1221":1221,"1222":1222,"1223":1223,"1224":1224,"1225":1225,"1226":1226,"1227":1227,"1228":1228,"1229":1229,"1230":1230,"1231":1231,"1232":1232,"1233":1233,"1234":1234,"1235":1235,"1236":1236,"1237":1237,"1238":1238,"1239":1239,"1240":1240,"1241":1241,"1242":1242,"1243":1243,"1244":1244,"1245":1245,"1246":1246,"1247":1247,"1248":1248,"1249":1249,"1250":1250,"1251":1251,"1252":1252,"1253":1253,"1254":1254,"1255":1255,"1256":1256,"1257":1257,"1258":1258,"1259":1259,"1260":1260,"1261":1261,"1262":1262,"1263":1263,"1264":1264,"1265":1265,"1266":1266,"1267":1267,"1268":1268,"1269":1269,"1270":1270,"1271":1271,"1272":1272,"1273":1273,"1274":1274,"1275":1275,"1276":1276,"1277":1277,"1278":1278,"1279":1279,"1280":1280,"1281":1281,"1282":1282,"1283":1283,"1284":1284,"1285":1285,"1286":1286,"1287":1287,"1288":1288,"1289":1289,"1290":1290,"1291":1291,"1292":1292,"1293":1293,"1294":1294,"1295":1295,"1296":1296,"1297":1297,"1298":1298,"1299":1299,"1300":1300,"1301":1301,"1302":1302,"1303":1303,"1304":1304,"1305":1305,"1306":1306,"1307":1307,"1308":1308,"1309":1309,"1310":1310,"1311":1311,"1312":1312,"1313":1313,"1314":1314,"1315":1315,"1316":1316,"1317":1317,"1318":1318,"1319":1319,"1320":1320,"1321":1321,"1322":1322,"1323":1323,"1324":1324,"1325":1325,"1326":1326,"1327":1327,"1328":1328,"1329":1329,"1330":1330,"1331":1331,"1332":1332,"1333":1333,"1334":1334,"1335":1335,"1336":1336,"1337":1337,"1338":1338,"1339":1339,"1340":1340,"1341":1341,"1342":1342,"1343":1343,"1344":1344,"1345":1345,"1346":1346,"1347":1347,"1348":1348,"1349":1349,"1350":1350,"1351":1351,"1352":1352,"1353":1353,"1354":1354,"1355":1355,"1356":1356,"1357":1357,"1358":1358,"1359":1359,"1360":1360,"1361":1361,"1362":1362,"1363":1363,"1364":1364,"1365":1365,"1366":1366,"1367":1367,"1368":1368,"1369":1369,"1370":1370,"1371":1371,"1372":1372,"1373":1373,"1374":1374,"1375":1375,"1376":1376,"1377":1377,"1378":1378,"1379":1379,"1380":1380,"1381":1381,"1382":1382,"1383":1383,"1384":1384,"1385":1385,"1386":1386,"1387":1387,"1388":1388,"1389":1389,"1390":1390,"1391":1391,"1392":1392,"1393":1393,"1394":1394,"1395":1395,"1396":1396,"1397":1397,"1398":1398,"1399":1399,"1400":1400,"1401":1401,"1402":1402,"1403":1403,"1404":1404,"1405":1405,"1406":1406,"1407":1407,"1408":1408,"1409":1409,"1410":1410,"1411":1411,"1412":1412,"1413":1413,"1414":1414,"1415":1415,"1416":1416,"1417":1417,"1418":1418,"1419":1419,"1420":1420,"1421":1421,"1422":1422,"1423":1423,"1424":1424,"1425":1425,"1426":1426,"1427":1427,"1428":1428,"1429":1429,"1430":1430,"1431":1431,"1432":1432,"1433":1433,"1434":1434,"1435":1435,"1436":1436,"1437":1437,"1438":1438,"1439":1439,"1440":1440,"1441":1441,"1442":1442,"1443":1443,"1444":1444,"1445":1445,"1446":1446,"1447":1447,"1448":1448,"1449":1449,"1450":1450,"1451":1451,"1452":1452,"1453":1453,"1454":1454,"1455":1455,"1456":1456,"1457":1457,"1458":1458,"1459":1459,"1460":1460,"1461":1461,"1462":1462,"1463":1463,"1464":1464,"1465":1465,"1466":1466,"1467":1467,"1468":1468,"1469":1469,"1470":1470,"1471":1471,"1472":1472,"1473":1473,"1474":1474,"1475":1475,"1476":1476,"1477":1477,"1478":1478,"1479":1479,"1480":1480,"1481":1481,"1482":1482,"1483":1483,"1484":1484,"1485":1485,"1486":1486,"1487":1487,"1488":1488,"1489":1489,"1490":1490,"1491":1491,"1492":1492,"1493":1493,"1494":1494,"1495":1495,"1496":1496,"1497":1497,"1498":1498,"1499":1499,"1500":1500,"1501":1501,"1502":1502,"1503":1503,"1504":1504,"1505":1505,"1506":1506,"1507":1507,"1508":1508,"1509":1509,"1510":1510,"1511":1511,"1512":1512,"1513":1513,"1514":1514,"1515":1515,"1516":1516,"1517":1517,"1518":1518,"1519":1519,"1520":1520,"1521":1521,"1522":1522,"1523":1523,"1524":1524,"1525":1525,"1526":1526,"1527":1527,"1528":1528,"1529":1529,"1530":1530,"1531":1531,"1532":1532,"1533":1533,"1534":1534,"1535":1535,"1536":1536,"1537":1537,"1538":1538,"1539":1539,"1540":1540,"1541":1541,"1542":1542,"1543":1543,"1544":1544,"1545":1545,"1546":1546,"1547":1547,"1548":1548,"1549":1549,"1550":1550,"1551":1551,"1552":1552,"1553":1553,"1554":1554,"1555":1555,"1556":1556,"1557":1557,"1558":1558,"1559":1559,"1560":1560,"1561":1561,"1562":1562,"1563":1563,"1564":1564,"1565":1565,"1566":1566,"1567":1567,"1568":1568,"1569":1569,"1570":1570,"1571":1571,"1572":1572,"1573":1573,"1574":1574,"1575":1575,"1576":1576,"1577":1577,"1578":1578,"1579":1579,"1580":1580,"1581":1581,"1582":1582,"1583":1583,"1584":1584,"1585":1585,"1586":1586,"1587":1587,"1588":1588,"1589":1589,"1590":1590,"1591":1591,"1592":1592,"1593":1593,"1594":1594,"1595":1595,"1596":1596,"1597":1597,"1598":1598,"1599":1599,"1600":1600,"1601":1601,"1602":1602,"1603":1603,"1604":1604,"1605":1605,"1606":1606,"1607":1607,"1608":1608,"1609":1609,"1610":1610,"1611":1611,"1612":1612,"1613":1613,"1614":1614,"1615":1615,"1616":1616,"1617":1617,"1618":1618,"1619":1619,"1620":1620,"1621":1621,"1622":1622,"1623":1623,"1624":1624,"1625":1625,"1626":1626,"1627":1627,"1628":1628,"1629":1629,"1630":1630,"1631":1631,"1632":1632,"1633":1633,"1634":1634,"1635":1635,"1636":1636,"1637":1637,"1638":1638,"1639":1639,"1640":1640,"1641":1641,"1642":1642,"1643":1643,"1644":1644,"1645":1645,"1646":1646,"1647":1647,"1648":1648,"1649":1649,"1650":1650,"1651":1651,"1652":1652,"1653":1653,"1654":1654,"1655":1655,"1656":1656,"1657":1657,"1658":1658,"1659":1659,"1660":1660,"1661":1661,"1662":1662,"1663":1663,"1664":1664,"1665":1665,"1666":1666,"1667":1667,"1668":1668,"1669":1669,"1670":1670,"1671":1671,"1672":1672,"1673":1673,"1674":1674,"1675":1675,"1676":1676,"1677":1677,"1678":1678,"1679":1679,"1680":1680,"1681":1681,"1682":1682,"1683":1683,"1684":1684,"1685":1685,"1686":1686,"1687":1687,"1688":1688,"1689":1689,"1690":1690,"1691":1691,"1692":1692,"1693":1693,"1694":1694,"1695":1695,"1696":1696,"1697":1697,"1698":1698,"1699":1699,"1700":1700,"1701":1701,"1702":1702,"1703":1703,"1704":1704,"1705":1705,"1706":1706,"1707":1707,"1708":1708,"1709":1709,"1710":1710,"1711":1711,"1712":1712,"1713":1713,"1714":1714,"1715":1715,"1716":1716,"1717":1717,"1718":1718,"1719":1719,"1720":1720,"1721":1721,"1722":1722,"1723":1723,"1724":1724,"1725":1725,"1726":1726,"1727":1727,"1728":1728,"1729":1729,"1730":1730,"1731":1731,"1732":1732,"1733":1733,"1734":1734,"1735":1735,"1736":1736,"1737":1737,"1738":1738,"1739":1739,"1740":1740,"1741":1741,"1742":1742,"1743":1743,"1744":1744,"1745":1745,"1746":1746,"1747":1747,"1748":1748,"1749":1749,"1750":1750,"1751":1751,"1752":1752,"1753":1753,"1754":1754,"1755":1755,"1756":1756,"1757":1757,"1758":1758,"1759":1759,"1760":1760,"1761":1761,"1762":1762,"1763":1763,"1764":1764,"1765":1765,"1766":1766,"1767":1767,"1768":1768,"1769":1769,"1770":1770,"1771":1771,"1772":1772,"1773":1773,"1774":1774,"1775":1775,"1776":1776,"1777":1777,"1778":1778,"1779":1779,"1780":1780,"1781":1781,"1782":1782,"1783":1783,"1784":1784,"1785":1785,"1786":1786,"1787":1787,"1788":1788,"1789":1789,"1790":1790,"1791":1791,"1792":1792,"1793":1793,"1794":1794,"1795":1795,"1796":1796,"1797":1797,"1798":1798,"1799":1799,"1800":1800,"1801":1801,"1802":1802,"1803":1803,"1804":1804,"1805":1805,"1806":1806,"1807":1807,"1808":1808,"1809":1809,"1810":1810,"1811":1811,"1812":1812,"1813":1813,"1814":1814,"1815":1815,"1816":1816,"1817":1817,"1818":1818,"1819":1819,"1820":1820,"1821":1821,"1822":1822,"1823":1823,"1824":1824,"1825":1825,"1826":1826,"1827":1827,"1828":1828,"1829":1829,"1830":1830,"1831":1831,"1832":1832,"1833":1833,"1834":1834,"1835":1835,"1836":1836,"1837":1837,"1838":1838,"1839":1839,"1840":1840,"1841":1841,"1842":1842,"1843":1843,"1844":1844,"1845":1845,"1846":1846,"1847":1847,"1848":1848,"1849":1849,"1850":1850,"1851":1851,"1852":1852,"1853":1853,"1854":1854,"1855":1855,"1856":1856,"1857":1857,"1858":1858,"1859":1859,"1860":1860,"1861":1861,"1862":1862,"1863":1863,"1864":1864,"1865":1865,"1866":1866,"1867":1867,"1868":1868,"1869":1869,"1870":1870,"1871":1871,"1872":1872,"1873":1873,"1874":1874,"1875":1875,"1876":1876,"1877":1877,"1878":1878,"1879":1879,"1880":1880,"1881":1881,"1882":1882,"1883":1883,"1884":1884,"1885":1885,"1886":1886,"1887":1887,"1888":1888,"1889":1889,"1890":1890,"1891":1891,"1892":1892,"1893":1893,"1894":1894,"1895":1895,"1896":1896,"1897":1897,"1898":1898,"1899":1899,"1900":1900,"1901":1901,"1902":1902,"1903":1903,"1904":1904,"1905":1905,"1906":1906,"1907":1907,"1908":1908,"1909":1909,"1910":1910,"1911":1911,"1912":1912,"1913":1913,"1914":1914,"1915":1915,"1916":1916,"1917":1917,"1918":1918,"1919":1919,"1920":1920,"1921":1921,"1922":1922,"1923":1923,"1924":1924,"1925":1925,"1926":1926,"1927":1927,"1928":1928,"1929":1929,"1930":1930,"1931":1931,"1932":1932,"1933":1933,"1934":1934,"1935":1935,"1936":1936,"1937":1937,"1938":1938,"1939":1939,"1940":1940,"1941":1941,"1942":1942,"1943":1943,"1944":1944,"1945":1945,"1946":1946,"1947":1947,"1948":1948,"1949":1949,"1950":1950,"1951":1951,"1952":1952,"1953":1953,"1954":1954,"1955":1955,"1956":1956,"1957":1957,"1958":1958,"1959":1959,"1960":1960,"1961":1961,"1962":1962,"1963":1963,"1964":1964,"1965":1965,"1966":1966,"1967":1967,"1968":1968,"1969":1969,"1970":1970,"1971":1971,"1972":1972,"1973":1973,"1974":1974,"1975":1975,"1976":1976,"1977":1977,"1978":1978,"1979":1979,"1980":1980,"1981":1981,"1982":1982,"1983":1983,"1984":1984,"1985":1985,"1986":1986,"1987":1987,"1988":1988,"1989":1989,"1990":1990,"1991":1991,"1992":1992,"1993":1993,"1994":1994,"1995":1995,"1996":1996,"1997":1997,"1998":1998,"1999":1999,"2000":2000,"2001":2001,"2002":2002,"2003":2003,"2004":2004,"2005":2005,"2006":2006,"2007":2007,"2008":2008,"2009":2009,"2010":2010,"2011":2011,"2012":2012,"2013":2013,"2014":2014,"2015":2015,"2016":2016,"2017":2017,"2018":2018,"2019":2019,"2020":2020,"2021":2021,"2022":2022,"2023":2023,"2024":2024,"2025":2025,"2026":2026,"2027":2027,"2028":2028,"2029":2029,"2030":2030,"2031":2031,"2032":2032,"2033":2033,"2034":2034,"2035":2035,"2036":2036,"2037":2037,"2038":2038,"2039":2039,"2040":2040,"2041":2041,"2042":2042,"2043":2043,"2044":2044,"2045":2045,"2046":2046,"2047":2047,"2048":2048,"2049":2049,"2050":2050,"2051":2051,"2052":2052,"2053":2053,"2054":2054,"2055":2055,"2056":2056,"2057":2057,"2058":2058,"2059":2059,"2060":2060,"2061":2061,"2062":2062,"2063":2063,"2064":2064,"2065":2065,"2066":2066,"2067":2067,"2068":2068,"2069":2069,"2070":2070,"2071":2071,"2072":2072,"2073":2073,"2074":2074,"2075":2075,"2076":2076,"2077":2077,"2078":2078,"2079":2079,"2080":2080,"2081":2081,"2082":2082,"2083":2083,"2084":2084,"2085":2085,"2086":2086,"2087":2087,"2088":2088,"2089":2089,"2090":2090,"2091":2091,"2092":2092,"2093":2093,"2094":2094,"2095":2095,"2096":2096,"2097":2097,"2098":2098,"2099":2099,"2100":2100,"2101":2101,"2102":2102,"2103":2103,"2104":2104,"2105":2105,"2106":2106,"2107":2107,"2108":2108,"2109":2109,"2110":2110,"2111":2111,"2112":2112,"2113":2113,"2114":2114,"2115":2115,"2116":2116,"2117":2117,"2118":2118,"2119":2119,"2120":2120,"2121":2121,"2122":2122,"2123":2123,"2124":2124,"2125":2125,"2126":2126,"2127":2127,"2128":2128,"2129":2129,"2130":2130,"2131":2131,"2132":2132,"2133":2133,"2134":2134,"2135":2135,"2136":2136,"2137":2137,"2138":2138,"2139":2139,"2140":2140,"2141":2141,"2142":2142,"2143":2143,"2144":2144,"2145":2145,"2146":2146,"2147":2147,"2148":2148,"2149":2149,"2150":2150,"2151":2151,"2152":2152,"2153":2153,"2154":2154,"2155":2155,"2156":2156,"2157":2157,"2158":2158,"2159":2159,"2160":2160,"2161":2161,"2162":2162,"2163":2163,"2164":2164,"2165":2165,"2166":2166,"2167":2167,"2168":2168,"2169":2169,"2170":2170,"2171":2171,"2172":2172,"2173":2173,"2174":2174,"2175":2175,"2176":2176,"2177":2177,"2178":2178,"2179":2179,"2180":2180,"2181":2181,"2182":2182,"2183":2183,"2184":2184,"2185":2185,"2186":2186,"2187":2187,"2188":2188,"2189":2189,"2190":2190,"2191":2191,"2192":2192,"2193":2193,"2194":2194,"2195":2195,"2196":2196,"2197":2197,"2198":2198,"2199":2199,"2200":2200,"2201":2201,"2202":2202,"2203":2203,"2204":2204,"2205":2205,"2206":2206,"2207":2207,"2208":2208,"2209":2209,"2210":2210,"2211":2211,"2212":2212,"2213":2213,"2214":2214,"2215":2215,"2216":2216,"2217":2217,"2218":2218,"2219":2219,"2220":2220,"2221":2221,"2222":2222,"2223":2223,"2224":2224,"2225":2225,"2226":2226,"2227":2227,"2228":2228,"2229":2229,"2230":2230,"2231":2231,"2232":2232,"2233":2233,"2234":2234,"2235":2235,"2236":2236,"2237":2237,"2238":2238,"2239":2239,"2240":2240,"2241":2241,"2242":2242,"2243":2243,"2244":2244},"fieldIds":{"t":0,"d":1,"i":2,"b":3},"fieldLength":{"0":[null,3,21,5392],"1":[4,3,null,152],"2":[null,7,null,7],"3":[1,7,2,61],"4":[1,7,4,27],"5":[1,7,12,95],"6":[1,7,3,80],"7":[2,7,2,61],"8":[null,7,null,7],"9":[1,7,null,1],"10":[1,7,8,91],"11":[1,7,12,67],"12":[1,7,1,57],"13":[2,7,1,39],"14":[null,7,null,7],"15":[1,7,11,237],"16":[1,7,null,41],"17":[1,7,9,181],"18":[1,7,7,139],"19":[2,7,7,265],"20":[5,7,16,230],"21":[5,7,8,171],"22":[5,7,1,157],"23":[5,7,null,125],"24":[5,7,18,649],"25":[5,7,2,138],"26":[5,7,14,319],"27":[5,7,12,486],"28":[null,10,null,10],"29":[1,10,3,28],"30":[1,10,null,87],"31":[1,10,20,356],"32":[1,10,null,120],"33":[2,10,null,98],"34":[5,10,3,48],"35":[1,10,1,36],"36":[null,8,null,8],"37":[1,8,6,54],"38":[1,8,5,119],"39":[1,8,19,438],"40":[1,8,1,78],"41":[2,8,4,185],"42":[5,8,5,142],"43":[5,8,12,111],"44":[null,5,null,5],"45":[1,5,2,63],"46":[1,5,6,88],"47":[1,5,13,173],"48":[1,5,1,52],"49":[2,5,null,53],"50":[null,9,null,9],"51":[1,9,2,46],"52":[1,9,3,61],"53":[1,9,16,217],"54":[1,9,5,118],"55":[2,9,4,73],"56":[null,13,null,13],"57":[1,13,3,149],"58":[1,13,4,123],"59":[1,13,14,339],"60":[1,13,1,133],"61":[2,13,8,124],"62":[1,13,10,229],"63":[5,13,8,124],"64":[1,13,null,29],"65":[null,6,null,6],"66":[1,6,3,96],"67":[1,6,6,98],"68":[1,6,10,216],"69":[1,6,null,76],"70":[2,6,10,122],"71":[5,6,9,275],"72":[5,6,19,282],"73":[5,6,12,196],"74":[5,6,4,172],"75":[null,8,null,8],"76":[1,8,5,109],"77":[1,8,5,96],"78":[1,8,12,168],"79":[1,8,1,110],"80":[2,8,17,219],"81":[8,8,14,289],"82":[5,8,7,95],"83":[null,9,null,9],"84":[1,9,null,28],"85":[1,9,3,50],"86":[1,9,1,94],"87":[1,9,null,41],"88":[2,9,null,40],"89":[null,14,null,14],"90":[1,14,null,36],"91":[9,14,15,226],"92":[14,14,18,216],"93":[22,14,15,274],"94":[18,14,7,213],"95":[20,14,24,351],"96":[20,14,18,234],"97":[1,14,6,173],"98":[1,14,15,258],"99":[19,14,19,245],"100":[17,14,16,300],"101":[1,14,4,109],"102":[2,14,7,123],"103":[5,14,7,154],"104":[5,14,8,157],"105":[1,14,null,38],"106":[null,9,null,9],"107":[1,9,9,109],"108":[1,9,null,77],"109":[1,9,17,968],"110":[1,9,8,231],"111":[2,9,14,279],"112":[5,9,9,67],"113":[1,9,2,73],"114":[null,8,null,8],"115":[1,8,9,202],"116":[1,8,null,60],"117":[1,8,16,297],"118":[1,8,null,61],"119":[2,8,1,56],"120":[5,8,7,127],"121":[5,8,18,296],"122":[5,8,13,493],"123":[5,8,5,118],"124":[1,8,7,94],"125":[5,8,5,212],"126":[5,8,22,323],"127":[5,8,8,102],"128":[5,8,11,207],"129":[null,8,null,8],"130":[1,8,11,234],"131":[1,8,1,84],"132":[1,8,14,189],"133":[1,8,1,83],"134":[2,8,2,77],"135":[5,8,25,855],"136":[8,8,16,421],"137":[15,8,13,299],"138":[15,8,19,265],"139":[16,8,17,366],"140":[12,8,7,240],"141":[14,8,19,215],"142":[14,8,21,411],"143":[1,8,1,72],"144":[null,10,null,10],"145":[1,10,21,244],"146":[1,10,10,107],"147":[1,10,23,312],"148":[1,10,3,116],"149":[2,10,9,134],"150":[3,10,18,377],"151":[2,10,5,102],"152":[5,10,20,134],"153":[1,10,null,39],"154":[null,9,null,9],"155":[1,9,10,126],"156":[1,9,1,93],"157":[1,9,15,422],"158":[1,9,1,99],"159":[2,9,10,228],"160":[5,9,17,391],"161":[5,9,15,188],"162":[1,9,2,98],"163":[null,11,null,11],"164":[1,11,10,192],"165":[1,11,3,121],"166":[1,11,12,415],"167":[1,11,null,87],"168":[2,11,4,188],"169":[1,11,1,48],"170":[10,11,12,262],"171":[5,11,7,146],"172":[null,11,null,11],"173":[1,11,2,161],"174":[1,11,null,124],"175":[1,11,15,426],"176":[1,11,null,101],"177":[2,11,1,208],"178":[5,11,14,362],"179":[5,11,13,251],"180":[5,11,13,216],"181":[5,11,4,103],"182":[1,11,5,95],"183":[null,8,null,8],"184":[1,8,11,141],"185":[1,8,3,133],"186":[1,8,22,471],"187":[1,8,1,94],"188":[2,8,7,145],"189":[5,8,15,217],"190":[5,8,12,87],"191":[1,8,1,36],"192":[null,9,null,9],"193":[1,9,6,201],"194":[1,9,null,101],"195":[1,9,14,375],"196":[1,9,3,134],"197":[2,9,3,115],"198":[5,9,2,108],"199":[1,9,2,81],"200":[5,9,17,305],"201":[5,9,16,435],"202":[5,9,14,303],"203":[5,9,13,149],"204":[null,9,null,9],"205":[1,9,null,2],"206":[1,9,3,96],"207":[1,9,16,170],"208":[1,9,1,120],"209":[2,9,2,116],"210":[1,9,null,41],"211":[null,11,null,11],"212":[1,11,4,110],"213":[1,11,null,106],"214":[1,11,15,280],"215":[1,11,1,139],"216":[2,11,15,198],"217":[5,11,11,210],"218":[5,11,4,121],"219":[5,11,13,338],"220":[5,11,15,191],"221":[1,11,null,36],"222":[null,13,null,13],"223":[1,13,2,95],"224":[1,13,null,103],"225":[1,13,16,375],"226":[1,13,3,111],"227":[2,13,9,136],"228":[5,13,1,130],"229":[1,13,10,116],"230":[5,13,12,372],"231":[5,13,10,144],"232":[null,12,null,12],"233":[1,12,5,124],"234":[1,12,10,185],"235":[1,12,12,544],"236":[1,12,1,177],"237":[2,12,9,287],"238":[5,12,3,71],"239":[1,12,2,60],"240":[null,15,1,15],"241":[1,15,14,229],"242":[1,15,2,124],"243":[1,15,18,673],"244":[1,15,3,140],"245":[2,15,14,399],"246":[5,15,15,198],"247":[1,15,15,121],"248":[5,15,13,203],"249":[5,15,5,138],"250":[5,15,7,120],"251":[5,15,8,155],"252":[5,15,12,186],"253":[5,15,15,150],"254":[5,15,16,254],"255":[5,15,16,459],"256":[5,15,15,305],"257":[5,15,12,252],"258":[5,15,19,326],"259":[5,15,17,392],"260":[5,15,12,196],"261":[5,15,16,273],"262":[null,8,null,8],"263":[1,8,null,52],"264":[1,8,3,120],"265":[1,8,23,923],"266":[1,8,4,141],"267":[2,8,5,147],"268":[5,8,11,138],"269":[1,8,5,69],"270":[null,6,null,6],"271":[1,6,null,10],"272":[1,6,7,95],"273":[1,6,18,272],"274":[1,6,1,81],"275":[2,6,4,110],"276":[5,6,8,58],"277":[1,6,1,33],"278":[null,9,null,9],"279":[1,9,7,160],"280":[1,9,2,153],"281":[1,9,16,156],"282":[1,9,1,132],"283":[2,9,7,208],"284":[5,9,11,184],"285":[5,9,10,136],"286":[2,9,7,125],"287":[1,9,3,59],"288":[null,13,null,13],"289":[1,13,null,5],"290":[1,13,4,146],"291":[1,13,13,212],"292":[1,13,2,73],"293":[2,13,4,131],"294":[1,13,null,36],"295":[5,13,14,296],"296":[5,13,7,139],"297":[null,10,null,10],"298":[1,10,7,95],"299":[1,10,1,80],"300":[1,10,19,177],"301":[1,10,3,111],"302":[2,10,3,124],"303":[5,10,16,212],"304":[1,10,4,57],"305":[5,10,13,253],"306":[5,10,7,106],"307":[null,12,null,12],"308":[1,12,null,58],"309":[1,12,null,120],"310":[1,12,20,310],"311":[1,12,1,122],"312":[2,12,2,176],"313":[1,12,1,78],"314":[5,12,12,192],"315":[null,11,null,11],"316":[1,11,1,31],"317":[1,11,2,140],"318":[1,11,17,617],"319":[1,11,7,140],"320":[2,11,7,186],"321":[1,11,4,71],"322":[5,11,8,96],"323":[5,11,7,71],"324":[5,11,16,226],"325":[5,11,13,207],"326":[5,11,13,188],"327":[null,10,null,10],"328":[1,10,9,122],"329":[1,10,null,115],"330":[1,10,13,366],"331":[1,10,11,141],"332":[2,10,7,152],"333":[5,10,15,220],"334":[1,10,5,56],"335":[5,10,17,231],"336":[5,10,2,94],"337":[5,10,13,149],"338":[null,8,null,8],"339":[1,8,null,33],"340":[1,8,2,178],"341":[1,8,18,530],"342":[1,8,6,235],"343":[2,8,13,286],"344":[5,8,12,165],"345":[5,8,8,139],"346":[1,8,6,96],"347":[null,13,null,13],"348":[1,13,null,109],"349":[1,13,2,161],"350":[1,13,16,589],"351":[1,13,5,170],"352":[2,13,15,230],"353":[5,13,15,306],"354":[5,13,16,93],"355":[1,13,3,70],"356":[null,13,null,13],"357":[1,13,null,29],"358":[1,13,null,180],"359":[1,13,18,498],"360":[1,13,3,274],"361":[2,13,4,318],"362":[1,13,null,71],"363":[5,13,2,96],"364":[5,13,7,89],"365":[5,13,8,317],"366":[5,13,1,81],"367":[null,14,null,14],"368":[1,14,1,76],"369":[1,14,5,159],"370":[1,14,22,329],"371":[1,14,6,158],"372":[2,14,5,138],"373":[5,14,16,325],"374":[11,14,13,148],"375":[5,14,8,170],"376":[1,14,null,72],"377":[null,12,null,12],"378":[1,12,3,51],"379":[1,12,null,108],"380":[1,12,13,180],"381":[1,12,1,87],"382":[2,12,1,100],"383":[5,12,9,166],"384":[5,12,7,195],"385":[null,8,null,8],"386":[1,8,10,190],"387":[1,8,6,220],"388":[1,8,15,255],"389":[1,8,1,80],"390":[2,8,12,407],"391":[5,8,9,253],"392":[5,8,11,180],"393":[5,8,12,117],"394":[null,6,null,6],"395":[1,6,17,305],"396":[1,6,1,139],"397":[1,6,26,552],"398":[1,6,3,163],"399":[2,6,2,171],"400":[5,6,16,223],"401":[5,6,18,344],"402":[6,6,16,299],"403":[5,6,2,226],"404":[5,6,11,175],"405":[2,6,3,115],"406":[1,6,4,89],"407":[7,6,19,286],"408":[9,6,14,172],"409":[5,6,18,255],"410":[null,9,null,9],"411":[1,9,3,66],"412":[1,9,12,134],"413":[1,9,15,464],"414":[1,9,2,96],"415":[2,9,12,234],"416":[5,9,null,53],"417":[null,12,null,12],"418":[1,12,23,308],"419":[1,12,6,133],"420":[1,12,14,258],"421":[1,12,null,56],"422":[2,12,4,184],"423":[5,12,16,309],"424":[5,12,12,168],"425":[5,12,10,175],"426":[5,12,19,293],"427":[5,12,3,151],"428":[5,12,16,278],"429":[5,12,12,155],"430":[5,12,9,91],"431":[null,10,null,10],"432":[1,10,1,51],"433":[1,10,2,106],"434":[1,10,16,299],"435":[1,10,6,192],"436":[5,10,15,150],"437":[null,8,null,8],"438":[1,8,null,87],"439":[1,8,null,83],"440":[1,8,12,210],"441":[1,8,2,118],"442":[5,8,6,115],"443":[5,8,14,222],"444":[5,8,5,178],"445":[null,6,null,6],"446":[1,6,8,141],"447":[1,6,5,122],"448":[1,6,15,423],"449":[1,6,5,137],"450":[2,6,12,158],"451":[11,6,11,155],"452":[12,6,19,138],"453":[5,6,11,117],"454":[12,6,16,212],"455":[1,6,7,79],"456":[null,10,null,10],"457":[1,10,3,72],"458":[1,10,6,148],"459":[1,10,19,570],"460":[1,10,7,143],"461":[2,10,7,259],"462":[1,10,1,86],"463":[5,10,12,285],"464":[5,10,14,282],"465":[10,10,11,287],"466":[5,10,13,191],"467":[null,11,null,11],"468":[1,11,14,183],"469":[1,11,12,143],"470":[1,11,14,281],"471":[1,11,14,120],"472":[2,11,7,160],"473":[1,11,5,80],"474":[5,11,5,134],"475":[5,11,5,116],"476":[5,11,13,147],"477":[5,11,11,196],"478":[5,11,7,98],"479":[null,9,null,9],"480":[1,9,null,47],"481":[1,9,12,117],"482":[1,9,14,128],"483":[1,9,10,153],"484":[2,9,3,116],"485":[1,9,null,37],"486":[5,9,14,211],"487":[5,9,14,193],"488":[5,9,14,185],"489":[5,9,14,214],"490":[5,9,17,406],"491":[5,9,17,354],"492":[5,9,17,368],"493":[5,9,19,367],"494":[5,9,13,218],"495":[5,9,14,195],"496":[null,14,null,14],"497":[1,14,12,306],"498":[1,14,2,120],"499":[1,14,17,432],"500":[1,14,1,171],"501":[2,14,6,213],"502":[5,14,9,152],"503":[1,14,3,54],"504":[null,10,null,10],"505":[1,10,2,51],"506":[1,10,1,148],"507":[1,10,14,451],"508":[1,10,3,151],"509":[2,10,14,185],"510":[1,10,3,52],"511":[5,10,17,251],"512":[5,10,3,106],"513":[5,10,8,193],"514":[5,10,14,150],"515":[null,11,null,11],"516":[1,11,1,89],"517":[1,11,3,196],"518":[1,11,14,281],"519":[1,11,2,94],"520":[2,11,2,138],"521":[1,11,null,47],"522":[5,11,14,229],"523":[5,11,14,238],"524":[5,11,12,222],"525":[null,12,null,12],"526":[1,12,7,166],"527":[1,12,11,180],"528":[1,12,19,320],"529":[1,12,1,164],"530":[2,12,6,315],"531":[5,12,2,133],"532":[1,12,null,71],"533":[null,9,null,9],"534":[1,9,14,231],"535":[1,9,3,154],"536":[1,9,16,712],"537":[1,9,6,236],"538":[2,9,10,332],"539":[15,9,12,402],"540":[5,9,14,250],"541":[1,9,2,120],"542":[null,10,null,10],"543":[1,10,12,278],"544":[1,10,3,180],"545":[1,10,18,643],"546":[1,10,9,179],"547":[2,10,10,245],"548":[1,10,2,106],"549":[5,10,16,770],"550":[5,10,12,298],"551":[5,10,13,329],"552":[5,10,15,212],"553":[null,10,null,10],"554":[1,10,9,111],"555":[1,10,4,172],"556":[1,10,14,618],"557":[1,10,4,196],"558":[2,10,16,265],"559":[5,10,14,118],"560":[1,10,1,85],"561":[null,11,null,11],"562":[1,11,5,92],"563":[1,11,10,170],"564":[1,11,19,435],"565":[1,11,1,146],"566":[2,11,12,238],"567":[5,11,5,104],"568":[1,11,null,68],"569":[null,9,null,9],"570":[1,9,null,61],"571":[1,9,null,138],"572":[1,9,16,610],"573":[1,9,2,193],"574":[2,9,4,224],"575":[5,9,2,127],"576":[13,9,15,156],"577":[14,9,11,179],"578":[5,9,8,157],"579":[1,9,3,87],"580":[null,10,null,10],"581":[1,10,null,8],"582":[1,10,1,124],"583":[1,10,13,612],"584":[1,10,8,219],"585":[2,10,13,419],"586":[5,10,7,135],"587":[1,10,2,90],"588":[null,11,null,11],"589":[1,11,4,71],"590":[1,11,1,173],"591":[1,11,18,480],"592":[1,11,4,286],"593":[2,11,6,250],"594":[5,11,4,95],"595":[1,11,null,67],"596":[null,12,null,12],"597":[1,12,6,84],"598":[1,12,2,160],"599":[1,12,19,629],"600":[1,12,null,189],"601":[2,12,16,271],"602":[5,12,4,200],"603":[5,12,10,134],"604":[5,12,19,125],"605":[1,12,null,85],"606":[null,13,null,13],"607":[1,13,11,296],"608":[1,13,null,206],"609":[1,13,21,771],"610":[1,13,null,213],"611":[2,13,6,300],"612":[5,13,7,206],"613":[5,13,4,75],"614":[1,13,null,76],"615":[null,15,null,15],"616":[1,15,8,123],"617":[1,15,3,174],"618":[1,15,17,525],"619":[1,15,4,206],"620":[2,15,6,206],"621":[5,15,8,102],"622":[1,15,null,74],"623":[null,9,null,9],"624":[1,9,6,205],"625":[1,9,2,224],"626":[1,9,22,648],"627":[1,9,null,273],"628":[2,9,1,240],"629":[5,9,10,212],"630":[1,9,null,46],"631":[null,9,7,178],"632":[1,9,17,214],"633":[1,9,22,617],"634":[1,9,10,157],"635":[2,9,9,263],"636":[5,9,18,145],"637":[null,9,null,9],"638":[1,9,13,199],"639":[1,9,2,100],"640":[1,9,16,701],"641":[1,9,2,176],"642":[2,9,16,326],"643":[5,9,null,121],"644":[5,9,13,172],"645":[1,9,2,52],"646":[null,9,null,9],"647":[1,9,10,77],"648":[1,9,6,133],"649":[1,9,19,490],"650":[1,9,9,245],"651":[2,9,13,226],"652":[5,9,10,83],"653":[1,9,2,49],"654":[null,8,null,8],"655":[1,8,12,101],"656":[1,8,3,159],"657":[1,8,18,640],"658":[1,8,15,227],"659":[2,8,14,208],"660":[5,8,16,183],"661":[1,8,2,56],"662":[null,10,null,10],"663":[1,10,2,37],"664":[1,10,2,184],"665":[1,10,20,491],"666":[1,10,null,165],"667":[2,10,6,187],"668":[5,10,11,190],"669":[5,10,4,152],"670":[1,10,1,63],"671":[null,6,null,6],"672":[1,6,7,125],"673":[1,6,1,149],"674":[1,6,14,687],"675":[1,6,12,204],"676":[2,6,14,328],"677":[5,6,12,156],"678":[1,6,null,52],"679":[null,11,null,11],"680":[1,11,3,59],"681":[1,11,9,172],"682":[1,11,18,368],"683":[1,11,9,209],"684":[2,11,14,246],"685":[5,11,3,98],"686":[1,11,5,57],"687":[null,11,null,11],"688":[1,11,2,124],"689":[1,11,null,203],"690":[1,11,21,674],"691":[1,11,3,362],"692":[2,11,14,400],"693":[5,11,9,98],"694":[1,11,2,87],"695":[null,14,null,14],"696":[1,14,12,222],"697":[1,14,13,174],"698":[1,14,17,687],"699":[1,14,5,198],"700":[2,14,13,248],"701":[1,14,7,97],"702":[5,14,7,249],"703":[5,14,15,171],"704":[null,15,null,15],"705":[1,15,2,40],"706":[1,15,2,157],"707":[1,15,17,553],"708":[1,15,5,196],"709":[2,15,1,252],"710":[5,15,9,85],"711":[1,15,3,110],"712":[null,11,null,11],"713":[1,11,5,108],"714":[1,11,4,185],"715":[1,11,17,424],"716":[1,11,6,230],"717":[2,11,4,205],"718":[5,11,5,149],"719":[12,11,16,155],"720":[15,11,15,177],"721":[1,11,5,91],"722":[null,10,null,10],"723":[1,10,8,99],"724":[1,10,4,223],"725":[1,10,14,559],"726":[1,10,6,214],"727":[2,10,12,269],"728":[5,10,7,104],"729":[1,10,6,96],"730":[null,12,1,12],"731":[1,12,13,146],"732":[1,12,12,174],"733":[1,12,21,598],"734":[1,12,4,178],"735":[2,12,8,224],"736":[5,12,15,146],"737":[1,12,2,115],"738":[null,11,null,11],"739":[1,11,1,73],"740":[1,11,15,280],"741":[1,11,18,633],"742":[1,11,2,215],"743":[2,11,12,476],"744":[5,11,13,172],"745":[1,11,3,98],"746":[null,12,null,12],"747":[1,12,11,116],"748":[1,12,5,153],"749":[1,12,13,603],"750":[1,12,2,167],"751":[2,12,13,343],"752":[5,12,4,70],"753":[1,12,null,60],"754":[null,9,null,9],"755":[1,9,2,84],"756":[1,9,5,204],"757":[1,9,14,525],"758":[1,9,null,237],"759":[2,9,8,302],"760":[5,9,5,136],"761":[5,9,7,158],"762":[1,9,null,74],"763":[null,10,null,10],"764":[1,10,7,175],"765":[1,10,2,216],"766":[1,10,24,497],"767":[1,10,9,224],"768":[2,10,15,268],"769":[5,10,8,116],"770":[1,10,2,62],"771":[null,16,null,16],"772":[1,16,null,5],"773":[1,16,null,150],"774":[1,16,16,346],"775":[1,16,6,140],"776":[2,16,14,237],"777":[5,16,4,79],"778":[1,16,null,88],"779":[null,12,null,12],"780":[1,12,13,107],"781":[1,12,9,127],"782":[1,12,12,474],"783":[1,12,4,138],"784":[2,12,9,228],"785":[2,12,6,97],"786":[1,12,1,61],"787":[null,13,null,13],"788":[1,13,null,40],"789":[1,13,6,219],"790":[1,13,18,649],"791":[1,13,2,185],"792":[2,13,9,336],"793":[5,13,9,256],"794":[5,13,15,206],"795":[1,13,null,76],"796":[null,13,null,13],"797":[1,13,3,125],"798":[1,13,14,298],"799":[1,13,12,421],"800":[1,13,5,188],"801":[2,13,4,185],"802":[1,13,null,102],"803":[5,13,13,241],"804":[5,13,13,210],"805":[5,13,11,179],"806":[5,13,4,104],"807":[null,13,null,13],"808":[1,13,null,81],"809":[1,13,11,284],"810":[1,13,10,279],"811":[1,13,1,143],"812":[2,13,1,164],"813":[17,13,13,219],"814":[5,13,7,113],"815":[1,13,null,119],"816":[null,12,null,12],"817":[1,12,null,60],"818":[1,12,5,216],"819":[1,12,17,600],"820":[1,12,2,215],"821":[2,12,10,307],"822":[5,12,11,91],"823":[1,12,7,117],"824":[null,11,null,11],"825":[1,11,6,271],"826":[1,11,9,354],"827":[1,11,17,1237],"828":[1,11,2,184],"829":[2,11,7,360],"830":[5,11,8,202],"831":[5,11,9,278],"832":[5,11,19,412],"833":[5,11,15,329],"834":[1,11,null,134],"835":[null,7,null,7],"836":[1,7,11,135],"837":[1,7,15,328],"838":[1,7,16,481],"839":[1,7,2,223],"840":[2,7,8,260],"841":[5,7,7,100],"842":[1,7,2,108],"843":[null,7,null,7],"844":[1,7,1,82],"845":[1,7,2,133],"846":[1,7,15,277],"847":[1,7,2,126],"848":[2,7,6,192],"849":[5,7,18,194],"850":[5,7,11,76],"851":[null,6,null,6],"852":[1,6,null,65],"853":[1,6,4,190],"854":[1,6,15,372],"855":[1,6,2,167],"856":[2,6,2,208],"857":[5,6,13,206],"858":[null,14,null,14],"859":[1,14,3,74],"860":[1,14,null,202],"861":[1,14,18,571],"862":[1,14,null,241],"863":[2,14,9,312],"864":[5,14,6,104],"865":[1,14,null,84],"866":[null,8,null,8],"867":[1,8,3,147],"868":[1,8,8,223],"869":[1,8,16,348],"870":[7,8,3,213],"871":[1,8,5,173],"872":[2,8,2,173],"873":[5,8,3,201],"874":[1,8,null,85],"875":[5,8,6,203],"876":[5,8,3,239],"877":[5,8,7,176],"878":[null,8,null,8],"879":[1,8,12,89],"880":[1,8,8,154],"881":[1,8,17,938],"882":[1,8,1,205],"883":[2,8,13,261],"884":[5,8,13,137],"885":[1,8,2,76],"886":[null,10,null,10],"887":[1,10,null,21],"888":[1,10,4,137],"889":[1,10,15,353],"890":[1,10,4,201],"891":[2,10,16,281],"892":[5,10,11,128],"893":[1,10,1,67],"894":[null,7,null,7],"895":[1,7,null,27],"896":[1,7,3,196],"897":[1,7,20,552],"898":[1,7,1,156],"899":[2,7,12,184],"900":[5,7,4,136],"901":[1,7,5,88],"902":[null,10,null,10],"903":[1,10,1,133],"904":[1,10,4,168],"905":[1,10,19,1139],"906":[1,10,8,406],"907":[2,10,12,450],"908":[5,10,7,124],"909":[5,10,13,173],"910":[1,10,7,189],"911":[null,10,null,10],"912":[1,10,7,115],"913":[1,10,11,254],"914":[1,10,21,551],"915":[1,10,4,177],"916":[2,10,17,263],"917":[5,10,13,144],"918":[1,10,1,113],"919":[null,16,null,16],"920":[1,16,4,156],"921":[1,16,5,194],"922":[1,16,13,394],"923":[1,16,12,198],"924":[2,16,12,238],"925":[1,16,8,74],"926":[11,16,13,705],"927":[5,16,7,143],"928":[null,13,null,13],"929":[1,13,5,75],"930":[1,13,9,170],"931":[1,13,14,282],"932":[1,13,3,175],"933":[2,13,6,162],"934":[5,13,2,49],"935":[1,13,1,76],"936":[null,10,2,10],"937":[1,10,null,58],"938":[1,10,12,146],"939":[1,10,17,225],"940":[1,10,null,134],"941":[2,10,4,147],"942":[1,10,6,74],"943":[null,6,null,6],"944":[1,6,2,59],"945":[1,6,2,166],"946":[1,6,16,445],"947":[1,6,null,156],"948":[2,6,4,209],"949":[5,6,9,66],"950":[1,6,1,91],"951":[null,10,null,10],"952":[1,10,3,57],"953":[1,10,7,189],"954":[1,10,17,490],"955":[1,10,6,180],"956":[2,10,5,197],"957":[1,10,6,83],"958":[5,10,2,100],"959":[5,10,9,125],"960":[5,10,17,201],"961":[null,14,null,14],"962":[1,14,2,63],"963":[1,14,15,293],"964":[1,14,15,501],"965":[1,14,8,184],"966":[2,14,16,267],"967":[5,14,8,102],"968":[1,14,8,95],"969":[null,11,null,11],"970":[1,11,null,21],"971":[1,11,5,240],"972":[1,11,13,420],"973":[1,11,4,236],"974":[2,11,8,241],"975":[5,11,9,136],"976":[1,11,1,97],"977":[null,11,null,11],"978":[1,11,null,9],"979":[1,11,16,215],"980":[1,11,18,553],"981":[1,11,11,188],"982":[2,11,14,297],"983":[5,11,16,135],"984":[1,11,11,88],"985":[null,8,null,8],"986":[1,8,4,88],"987":[1,8,12,184],"988":[1,8,13,626],"989":[1,8,2,169],"990":[2,8,3,215],"991":[5,8,16,147],"992":[1,8,2,97],"993":[null,8,null,8],"994":[1,8,1,94],"995":[1,8,10,211],"996":[1,8,13,606],"997":[1,8,5,159],"998":[2,8,7,270],"999":[5,8,11,157],"1000":[1,8,1,78],"1001":[null,10,null,10],"1002":[1,10,null,13],"1003":[1,10,4,163],"1004":[1,10,21,506],"1005":[1,10,1,150],"1006":[2,10,8,282],"1007":[5,10,null,96],"1008":[1,10,1,67],"1009":[null,9,null,9],"1010":[1,9,null,57],"1011":[1,9,null,188],"1012":[1,9,3,393],"1013":[1,9,null,113],"1014":[2,9,null,149],"1015":[null,6,null,6],"1016":[1,6,12,203],"1017":[1,6,5,309],"1018":[1,6,17,1026],"1019":[1,6,2,359],"1020":[2,6,5,408],"1021":[5,6,10,162],"1022":[1,6,2,104],"1023":[null,11,null,11],"1024":[1,11,1,75],"1025":[1,11,5,144],"1026":[1,11,18,457],"1027":[1,11,null,159],"1028":[2,11,5,208],"1029":[5,11,9,115],"1030":[1,11,3,80],"1031":[null,8,null,8],"1032":[1,8,1,40],"1033":[1,8,13,256],"1034":[1,8,19,556],"1035":[1,8,16,174],"1036":[2,8,17,198],"1037":[5,8,7,153],"1038":[1,8,4,61],"1039":[null,13,null,13],"1040":[1,13,1,66],"1041":[1,13,6,197],"1042":[1,13,14,491],"1043":[1,13,5,262],"1044":[2,13,10,257],"1045":[5,13,11,134],"1046":[1,13,null,51],"1047":[null,14,null,14],"1048":[1,14,5,95],"1049":[1,14,13,259],"1050":[1,14,17,658],"1051":[1,14,14,319],"1052":[2,14,16,329],"1053":[1,14,2,100],"1054":[5,14,10,180],"1055":[5,14,14,189],"1056":[null,13,null,13],"1057":[1,13,6,68],"1058":[1,13,14,249],"1059":[1,13,16,703],"1060":[1,13,7,185],"1061":[2,13,12,202],"1062":[5,13,13,110],"1063":[1,13,1,49],"1064":[null,9,null,9],"1065":[1,9,null,68],"1066":[1,9,19,306],"1067":[1,9,22,596],"1068":[1,9,8,205],"1069":[2,9,21,285],"1070":[5,9,5,108],"1071":[1,9,null,41],"1072":[null,13,null,13],"1073":[1,13,2,106],"1074":[1,13,22,411],"1075":[1,13,16,354],"1076":[1,13,3,153],"1077":[2,13,7,147],"1078":[5,13,11,122],"1079":[1,13,null,113],"1080":[null,13,null,13],"1081":[1,13,null,69],"1082":[1,13,8,255],"1083":[1,13,17,264],"1084":[1,13,1,141],"1085":[2,13,4,231],"1086":[5,13,3,104],"1087":[1,13,null,55],"1088":[null,10,null,10],"1089":[1,10,19,278],"1090":[1,10,10,309],"1091":[1,10,20,839],"1092":[1,10,5,288],"1093":[2,10,11,405],"1094":[1,10,12,182],"1095":[5,10,9,153],"1096":[1,10,3,92],"1097":[null,13,null,13],"1098":[1,13,null,8],"1099":[1,13,16,205],"1100":[1,13,15,439],"1101":[1,13,3,147],"1102":[2,13,4,162],"1103":[5,13,10,108],"1104":[1,13,null,69],"1105":[null,11,null,11],"1106":[1,11,null,5],"1107":[1,11,3,127],"1108":[1,11,16,292],"1109":[1,11,1,108],"1110":[2,11,2,83],"1111":[5,11,2,64],"1112":[1,11,null,59],"1113":[null,6,null,6],"1114":[1,6,1,63],"1115":[1,6,8,208],"1116":[1,6,20,772],"1117":[1,6,4,283],"1118":[2,6,3,236],"1119":[5,6,5,175],"1120":[1,6,null,65],"1121":[null,12,null,12],"1122":[1,12,1,68],"1123":[1,12,3,192],"1124":[1,12,18,595],"1125":[1,12,6,233],"1126":[2,12,1,241],"1127":[10,12,1,105],"1128":[9,12,9,129],"1129":[1,12,null,78],"1130":[null,10,null,10],"1131":[1,10,null,5],"1132":[1,10,12,202],"1133":[1,10,16,380],"1134":[1,10,1,191],"1135":[2,10,9,215],"1136":[5,10,5,73],"1137":[1,10,null,78],"1138":[null,7,null,7],"1139":[1,7,null,56],"1140":[1,7,12,223],"1141":[1,7,null,71],"1142":[1,7,1,148],"1143":[2,7,null,80],"1144":[2,7,null,117],"1145":[3,7,null,92],"1146":[5,7,1,36],"1147":[1,7,null,82],"1148":[null,8,null,8],"1149":[1,8,null,59],"1150":[1,8,8,268],"1151":[1,8,null,38],"1152":[1,8,null,115],"1153":[2,8,null,80],"1154":[2,8,2,133],"1155":[3,8,null,98],"1156":[5,8,3,75],"1157":[1,8,null,65],"1158":[null,15,null,15],"1159":[1,15,null,5],"1160":[1,15,6,158],"1161":[1,15,21,326],"1162":[1,15,5,165],"1163":[2,15,8,145],"1164":[1,15,null,40],"1165":[null,6,null,6],"1166":[1,6,null,5],"1167":[1,6,2,105],"1168":[1,6,14,453],"1169":[1,6,3,150],"1170":[2,6,5,156],"1171":[1,6,1,61],"1172":[null,15,null,15],"1173":[1,15,null,47],"1174":[1,15,7,160],"1175":[1,15,18,464],"1176":[1,15,7,185],"1177":[2,15,5,147],"1178":[2,15,6,164],"1179":[1,15,8,72],"1180":[1,15,2,54],"1181":[null,18,null,18],"1182":[1,18,1,84],"1183":[1,18,8,161],"1184":[1,18,15,556],"1185":[1,18,4,203],"1186":[2,18,4,191],"1187":[2,18,2,177],"1188":[1,18,9,103],"1189":[1,18,null,53],"1190":[null,6,4,139],"1191":[7,6,6,264],"1192":[1,6,12,751],"1193":[7,6,4,169],"1194":[3,6,4,121],"1195":[5,6,2,48],"1196":[null,5,7,171],"1197":[2,5,null,42],"1198":[6,5,null,127],"1199":[6,5,12,4876],"1200":[null,5,4,149],"1201":[9,5,15,205],"1202":[4,5,null,589],"1203":[2,5,20,3036],"1204":[null,4,1,50],"1205":[3,4,null,15],"1206":[6,4,null,105],"1207":[2,4,27,5655],"1208":[3,4,12,240],"1209":[8,4,null,126],"1210":[null,9,null,62],"1211":[4,9,14,216],"1212":[7,9,13,2561],"1213":[9,9,22,482],"1214":[7,9,22,331],"1215":[5,9,12,111],"1216":[7,9,2,269],"1217":[null,4,14,197],"1218":[8,4,3,130],"1219":[5,4,13,176],"1220":[5,4,14,279],"1221":[7,4,9,142],"1222":[6,4,16,187],"1223":[6,4,12,206],"1224":[7,4,12,123],"1225":[12,4,16,208],"1226":[8,4,6,124],"1227":[6,4,12,186],"1228":[10,4,13,294],"1229":[3,4,24,1938],"1230":[null,9,14,245],"1231":[7,9,13,484],"1232":[7,9,14,283],"1233":[7,9,14,366],"1234":[6,9,12,442],"1235":[9,9,14,270],"1236":[8,9,16,377],"1237":[4,9,24,3015],"1238":[null,8,12,234],"1239":[6,8,17,465],"1240":[7,8,12,287],"1241":[7,8,16,688],"1242":[6,8,16,452],"1243":[8,8,14,436],"1244":[6,8,13,484],"1245":[6,8,13,170],"1246":[9,8,12,221],"1247":[10,8,26,3304],"1248":[null,8,null,165],"1249":[5,8,13,360],"1250":[9,8,5,123],"1251":[8,8,12,226],"1252":[13,8,12,364],"1253":[7,8,12,296],"1254":[8,8,12,289],"1255":[6,8,10,222],"1256":[9,8,18,483],"1257":[10,8,12,319],"1258":[8,8,13,322],"1259":[7,8,25,3325],"1260":[null,7,12,312],"1261":[6,7,12,164],"1262":[11,7,14,393],"1263":[9,7,15,743],"1264":[11,7,18,401],"1265":[12,7,12,599],"1266":[7,7,13,215],"1267":[7,7,14,370],"1268":[3,7,18,212],"1269":[8,7,13,526],"1270":[8,7,24,3954],"1271":[null,2,24,1813],"1272":[null,4,12,188],"1273":[9,4,18,583],"1274":[5,4,12,446],"1275":[6,4,14,586],"1276":[8,4,12,481],"1277":[6,4,12,358],"1278":[7,4,14,702],"1279":[6,4,12,389],"1280":[8,4,13,475],"1281":[5,4,12,301],"1282":[10,4,12,336],"1283":[8,4,12,405],"1284":[6,4,13,324],"1285":[4,4,22,6836],"1286":[null,3,15,326],"1287":[7,3,12,378],"1288":[4,3,13,404],"1289":[6,3,12,528],"1290":[8,3,12,231],"1291":[5,3,13,284],"1292":[10,3,14,398],"1293":[8,3,14,399],"1294":[9,3,14,239],"1295":[4,3,13,228],"1296":[4,3,12,593],"1297":[5,3,12,315],"1298":[6,3,13,144],"1299":[5,3,29,5832],"1300":[null,2,25,2096],"1301":[null,9,13,330],"1302":[10,9,14,209],"1303":[6,9,16,509],"1304":[13,9,13,224],"1305":[3,9,14,280],"1306":[8,9,17,472],"1307":[10,9,13,281],"1308":[4,9,25,3204],"1309":[null,9,23,1317],"1310":[null,8,28,5703],"1311":[null,5,22,1572],"1312":[null,5,12,336],"1313":[8,5,12,178],"1314":[5,5,15,417],"1315":[10,5,12,607],"1316":[9,5,14,495],"1317":[9,5,14,565],"1318":[2,5,12,124],"1319":[5,5,12,702],"1320":[4,5,12,120],"1321":[10,5,14,475],"1322":[7,5,25,5428],"1323":[null,10,30,7086],"1324":[null,5,11,225],"1325":[6,5,24,572],"1326":[11,5,14,255],"1327":[11,5,19,524],"1328":[5,5,16,173],"1329":[7,5,14,266],"1330":[9,5,16,210],"1331":[8,5,15,587],"1332":[7,5,23,645],"1333":[6,5,14,394],"1334":[9,5,13,229],"1335":[11,5,15,467],"1336":[9,5,21,555],"1337":[11,5,12,537],"1338":[6,5,32,5009],"1339":[null,7,15,251],"1340":[5,7,16,205],"1341":[6,7,13,637],"1342":[5,7,17,483],"1343":[6,7,17,470],"1344":[6,7,12,230],"1345":[8,7,12,238],"1346":[8,7,14,634],"1347":[6,7,15,382],"1348":[9,7,14,395],"1349":[6,7,22,4685],"1350":[null,6,5,205],"1351":[8,6,14,733],"1352":[8,6,19,634],"1353":[8,6,14,349],"1354":[7,6,15,490],"1355":[9,6,14,238],"1356":[10,6,14,437],"1357":[6,6,12,422],"1358":[7,6,26,7010],"1359":[null,5,9,142],"1360":[10,5,13,140],"1361":[11,5,15,483],"1362":[10,5,15,197],"1363":[12,5,12,301],"1364":[3,5,14,192],"1365":[8,5,18,628],"1366":[14,5,12,232],"1367":[9,5,12,293],"1368":[6,5,30,2854],"1369":[null,8,15,160],"1370":[7,8,12,295],"1371":[9,8,14,285],"1372":[8,8,14,440],"1373":[7,8,12,215],"1374":[12,8,13,440],"1375":[7,8,13,278],"1376":[10,8,13,251],"1377":[9,8,16,365],"1378":[10,8,17,508],"1379":[7,8,21,3418],"1380":[null,4,10,194],"1381":[10,4,12,286],"1382":[7,4,12,375],"1383":[10,4,17,402],"1384":[7,4,12,275],"1385":[9,4,14,151],"1386":[13,4,12,193],"1387":[7,4,12,230],"1388":[9,4,14,534],"1389":[7,4,12,325],"1390":[9,4,13,433],"1391":[9,4,12,312],"1392":[3,4,12,252],"1393":[5,4,12,181],"1394":[7,4,19,5644],"1395":[null,7,21,6228],"1396":[null,12,5,208],"1397":[6,12,12,360],"1398":[10,12,16,488],"1399":[10,12,15,352],"1400":[8,12,12,463],"1401":[10,12,26,3686],"1402":[null,10,12,258],"1403":[4,10,15,248],"1404":[12,10,12,453],"1405":[8,10,16,608],"1406":[8,10,13,243],"1407":[7,10,14,285],"1408":[7,10,12,408],"1409":[6,10,13,485],"1410":[5,10,15,289],"1411":[6,10,12,238],"1412":[8,10,15,374],"1413":[3,10,16,411],"1414":[5,10,29,4948],"1415":[null,9,26,2400],"1416":[null,12,22,4124],"1417":[null,4,14,255],"1418":[6,4,13,208],"1419":[11,4,12,202],"1420":[7,4,12,365],"1421":[10,4,16,358],"1422":[11,4,12,370],"1423":[8,4,13,296],"1424":[8,4,24,366],"1425":[6,4,15,181],"1426":[8,4,27,2039],"1427":[null,4,15,382],"1428":[10,4,14,555],"1429":[7,4,19,535],"1430":[8,4,13,1023],"1431":[6,4,12,490],"1432":[12,4,12,749],"1433":[3,4,16,231],"1434":[9,4,17,563],"1435":[5,4,30,9083],"1436":[4,4,20,527],"1437":[4,4,18,3411],"1438":[null,4,7,124],"1439":[5,4,2,234],"1440":[6,4,24,1310],"1441":[12,4,20,706],"1442":[8,4,21,1303],"1443":[8,4,16,538],"1444":[3,4,24,657],"1445":[4,4,20,511],"1446":[5,4,23,945],"1447":[3,4,3,102],"1448":[5,4,16,455],"1449":[6,4,12,336],"1450":[4,4,11,191],"1451":[null,4,3,175],"1452":[4,4,25,883],"1453":[4,4,20,742],"1454":[4,4,34,2297],"1455":[4,4,26,871],"1456":[5,4,15,329],"1457":[5,4,26,388],"1458":[9,4,26,656],"1459":[3,4,21,601],"1460":[6,4,13,403],"1461":[null,6,4,153],"1462":[5,6,13,279],"1463":[6,6,16,154],"1464":[7,6,22,774],"1465":[8,6,19,312],"1466":[7,6,31,3449],"1467":[3,6,17,100],"1468":[4,6,16,315],"1469":[4,6,24,283],"1470":[null,3,7,203],"1471":[7,3,28,485],"1472":[11,3,14,415],"1473":[6,3,16,889],"1474":[9,3,26,754],"1475":[8,3,20,806],"1476":[8,3,20,352],"1477":[10,3,14,410],"1478":[6,3,8,251],"1479":[8,3,12,297],"1480":[6,3,22,466],"1481":[6,3,6,263],"1482":[3,3,4,194],"1483":[4,3,13,217],"1484":[null,4,1,152],"1485":[7,4,20,623],"1486":[4,4,22,792],"1487":[5,4,19,1811],"1488":[6,4,22,1448],"1489":[5,4,21,1001],"1490":[3,4,18,435],"1491":[9,4,22,1232],"1492":[8,4,null,177],"1493":[2,4,6,128],"1494":[null,4,4,48],"1495":[3,4,22,3712],"1496":[9,4,17,224],"1497":[4,4,13,279],"1498":[4,4,4,207],"1499":[7,4,16,343],"1500":[6,4,2,24],"1501":[null,4,null,56],"1502":[9,4,14,143],"1503":[7,4,11,104],"1504":[8,4,null,60],"1505":[9,4,1,42],"1506":[10,4,6,109],"1507":[13,4,10,142],"1508":[12,4,13,113],"1509":[13,4,7,55],"1510":[4,4,null,40],"1511":[12,4,7,96],"1512":[9,4,8,101],"1513":[8,4,null,53],"1514":[6,4,2,59],"1515":[6,4,1,24],"1516":[9,4,4,141],"1517":[null,2,null,38],"1518":[2,2,null,48],"1519":[3,2,null,13],"1520":[4,2,null,69],"1521":[null,4,3,69],"1522":[3,4,4,165],"1523":[3,4,13,431],"1524":[3,4,17,410],"1525":[1,4,24,2766],"1526":[2,4,4,151],"1527":[3,4,null,53],"1528":[null,5,6,101],"1529":[5,5,null,182],"1530":[2,5,17,804],"1531":[2,5,11,400],"1532":[2,5,1,99],"1533":[3,5,18,991],"1534":[1,5,18,724],"1535":[null,3,null,154],"1536":[5,3,null,260],"1537":[3,3,3,107],"1538":[3,3,null,116],"1539":[3,3,3,109],"1540":[4,3,4,145],"1541":[4,3,null,104],"1542":[5,3,null,114],"1543":[3,3,null,165],"1544":[3,3,1,202],"1545":[4,3,null,131],"1546":[4,3,null,222],"1547":[2,3,null,176],"1548":[3,3,2,112],"1549":[3,3,2,107],"1550":[4,3,null,122],"1551":[5,3,3,106],"1552":[5,3,null,229],"1553":[3,3,null,204],"1554":[5,3,4,142],"1555":[5,3,2,143],"1556":[6,3,null,126],"1557":[4,3,2,139],"1558":[6,3,null,104],"1559":[5,3,2,123],"1560":[5,3,null,126],"1561":[5,3,null,111],"1562":[4,3,2,117],"1563":[3,3,null,90],"1564":[5,3,null,148],"1565":[5,3,null,166],"1566":[5,3,null,130],"1567":[4,3,null,143],"1568":[5,3,null,140],"1569":[5,3,3,187],"1570":[4,3,2,183],"1571":[4,3,null,282],"1572":[null,4,4,72],"1573":[3,4,2,167],"1574":[3,4,12,372],"1575":[3,4,17,419],"1576":[1,4,20,2968],"1577":[2,4,3,177],"1578":[3,4,null,53],"1579":[null,5,null,76],"1580":[5,5,null,182],"1581":[2,5,15,769],"1582":[2,5,14,375],"1583":[2,5,null,132],"1584":[3,5,17,685],"1585":[1,5,14,722],"1586":[null,4,3,62],"1587":[3,4,4,163],"1588":[3,4,15,329],"1589":[3,4,2,188],"1590":[1,4,20,2212],"1591":[2,4,null,144],"1592":[3,4,null,29],"1593":[null,5,10,106],"1594":[5,5,4,205],"1595":[2,5,18,874],"1596":[2,5,17,445],"1597":[2,5,3,129],"1598":[3,5,20,533],"1599":[1,5,16,920],"1600":[null,2,null,42],"1601":[4,2,3,123],"1602":[5,2,null,12],"1603":[4,2,null,24],"1604":[null,8,6,120],"1605":[1,8,6,60],"1606":[6,8,10,147],"1607":[11,8,null,93],"1608":[2,8,null,32],"1609":[3,8,null,37],"1610":[null,8,18,430],"1611":[4,8,7,81],"1612":[9,8,14,77],"1613":[1,8,7,70],"1614":[2,8,15,57],"1615":[1,8,2,36],"1616":[6,8,13,94],"1617":[2,8,1,63],"1618":[2,8,14,65],"1619":[null,2,null,35],"1620":[1,2,8,108],"1621":[3,2,null,17],"1622":[5,2,null,19],"1623":[2,2,null,23],"1624":[2,2,1,59],"1625":[6,2,12,233],"1626":[2,2,17,538],"1627":[null,6,null,12],"1628":[3,6,null,255],"1629":[3,6,16,937],"1630":[3,6,17,1411],"1631":[5,6,14,975],"1632":[4,6,13,147],"1633":[4,6,13,204],"1634":[4,6,15,436],"1635":[6,6,12,175],"1636":[2,6,11,323],"1637":[3,6,14,756],"1638":[4,6,16,1103],"1639":[4,6,14,866],"1640":[4,6,16,1192],"1641":[null,4,3,63],"1642":[9,4,13,160],"1643":[4,4,6,119],"1644":[4,4,7,106],"1645":[null,6,7,213],"1646":[4,6,6,114],"1647":[5,6,17,209],"1648":[8,6,20,220],"1649":[7,6,12,192],"1650":[12,6,14,683],"1651":[6,6,13,179],"1652":[9,6,21,693],"1653":[8,6,15,412],"1654":[6,6,14,102],"1655":[12,6,18,352],"1656":[2,6,14,120],"1657":[4,6,6,132],"1658":[null,4,null,106],"1659":[2,4,14,318],"1660":[4,4,12,334],"1661":[2,4,16,516],"1662":[4,4,12,316],"1663":[3,4,12,487],"1664":[3,4,15,358],"1665":[3,4,12,517],"1666":[4,4,13,465],"1667":[4,4,12,447],"1668":[6,4,12,459],"1669":[4,4,12,489],"1670":[4,4,13,560],"1671":[3,4,13,219],"1672":[5,4,8,406],"1673":[2,4,7,178],"1674":[3,4,11,319],"1675":[null,6,null,73],"1676":[6,6,17,373],"1677":[7,6,null,133],"1678":[9,6,null,88],"1679":[9,6,null,33],"1680":[null,10,4,141],"1681":[3,10,11,105],"1682":[7,10,6,167],"1683":[5,10,19,371],"1684":[7,10,19,647],"1685":[9,10,18,798],"1686":[7,10,21,203],"1687":[4,10,5,70],"1688":[9,10,13,232],"1689":[2,10,8,85],"1690":[4,10,null,51],"1691":[null,9,1,183],"1692":[3,9,12,137],"1693":[5,9,null,21],"1694":[4,9,5,110],"1695":[8,9,1,71],"1696":[5,9,17,160],"1697":[3,9,12,168],"1698":[5,9,13,162],"1699":[4,9,14,192],"1700":[4,9,15,314],"1701":[5,9,18,296],"1702":[2,9,7,109],"1703":[2,9,12,107],"1704":[4,9,1,128],"1705":[null,6,null,118],"1706":[9,6,13,312],"1707":[9,6,5,251],"1708":[9,6,1,121],"1709":[5,6,null,85],"1710":[null,8,3,52],"1711":[1,8,5,79],"1712":[2,8,null,48],"1713":[2,8,null,85],"1714":[2,8,7,50],"1715":[2,8,1,92],"1716":[null,8,3,107],"1717":[4,8,6,160],"1718":[4,8,14,269],"1719":[4,8,8,256],"1720":[4,8,4,157],"1721":[4,8,14,138],"1722":[7,8,7,129],"1723":[5,8,4,205],"1724":[4,8,null,66],"1725":[null,5,6,162],"1726":[2,5,18,731],"1727":[2,5,20,353],"1728":[4,5,13,349],"1729":[4,5,13,189],"1730":[5,5,10,192],"1731":[null,4,6,118],"1732":[2,4,7,66],"1733":[4,4,1,81],"1734":[5,4,10,140],"1735":[2,4,null,59],"1736":[1,4,1,80],"1737":[2,4,4,45],"1738":[null,8,4,126],"1739":[1,8,6,63],"1740":[6,8,14,175],"1741":[11,8,null,164],"1742":[2,8,null,32],"1743":[3,8,null,37],"1744":[null,4,null,10],"1745":[3,4,8,195],"1746":[4,4,15,272],"1747":[4,4,15,549],"1748":[4,4,16,654],"1749":[3,4,6,350],"1750":[4,4,16,168],"1751":[3,4,6,67],"1752":[7,4,10,98],"1753":[5,4,3,50],"1754":[6,4,12,235],"1755":[null,2,2,43],"1756":[1,2,8,99],"1757":[3,2,4,86],"1758":[5,2,5,116],"1759":[2,2,12,160],"1760":[2,2,18,263],"1761":[null,4,null,10],"1762":[3,4,null,148],"1763":[4,4,11,574],"1764":[3,4,21,1079],"1765":[5,4,6,78],"1766":[3,4,15,571],"1767":[3,4,null,236],"1768":[6,4,19,424],"1769":[3,4,18,210],"1770":[3,4,13,168],"1771":[4,4,7,129],"1772":[3,4,11,268],"1773":[4,4,12,140],"1774":[2,4,10,134],"1775":[6,4,13,340],"1776":[7,4,1,105],"1777":[null,5,null,45],"1778":[5,5,null,480],"1779":[null,17,null,124],"1780":[3,17,14,218],"1781":[7,17,6,134],"1782":[7,17,3,143],"1783":[10,17,null,232],"1784":[8,17,null,82],"1785":[5,17,2,80],"1786":[7,17,7,87],"1787":[null,7,null,128],"1788":[3,7,null,119],"1789":[11,7,15,354],"1790":[7,7,null,32],"1791":[3,7,3,163],"1792":[5,7,9,205],"1793":[null,14,null,134],"1794":[6,14,null,89],"1795":[6,14,null,200],"1796":[12,14,null,160],"1797":[5,14,null,56],"1798":[9,14,null,233],"1799":[7,14,null,185],"1800":[3,14,2,170],"1801":[5,14,null,219],"1802":[null,11,5,127],"1803":[8,11,3,137],"1804":[9,11,16,518],"1805":[3,11,12,208],"1806":[5,11,14,219],"1807":[null,13,4,128],"1808":[3,13,5,98],"1809":[10,13,16,599],"1810":[3,13,6,152],"1811":[5,13,15,204],"1812":[null,8,4,120],"1813":[3,8,3,121],"1814":[9,8,16,660],"1815":[3,8,1,161],"1816":[5,8,15,304],"1817":[null,12,null,111],"1818":[3,12,null,96],"1819":[6,12,4,105],"1820":[10,12,16,346],"1821":[4,12,6,134],"1822":[11,12,7,167],"1823":[7,12,7,153],"1824":[9,12,5,329],"1825":[3,12,3,227],"1826":[5,12,13,218],"1827":[null,10,10,155],"1828":[6,10,15,202],"1829":[7,10,12,169],"1830":[13,10,13,180],"1831":[8,10,1,142],"1832":[3,10,13,161],"1833":[5,10,16,202],"1834":[null,11,null,111],"1835":[3,11,null,71],"1836":[8,11,1,99],"1837":[9,11,8,127],"1838":[9,11,13,242],"1839":[6,11,14,335],"1840":[4,11,9,237],"1841":[10,11,12,264],"1842":[7,11,15,248],"1843":[7,11,13,202],"1844":[3,11,10,217],"1845":[5,11,8,163],"1846":[null,9,1,114],"1847":[3,9,3,112],"1848":[9,9,12,332],"1849":[6,9,3,44],"1850":[8,9,13,383],"1851":[3,9,6,209],"1852":[5,9,11,241],"1853":[null,11,3,176],"1854":[4,11,null,86],"1855":[7,11,13,248],"1856":[6,11,13,95],"1857":[7,11,3,154],"1858":[6,11,16,188],"1859":[4,11,3,147],"1860":[3,11,8,205],"1861":[5,11,13,221],"1862":[null,12,2,137],"1863":[3,12,2,128],"1864":[10,12,16,243],"1865":[5,12,3,118],"1866":[3,12,7,133],"1867":[5,12,9,219],"1868":[null,16,5,155],"1869":[8,16,null,111],"1870":[14,16,13,199],"1871":[8,16,8,116],"1872":[8,16,4,101],"1873":[10,16,13,247],"1874":[7,16,12,163],"1875":[3,16,9,251],"1876":[5,16,14,260],"1877":[null,8,2,98],"1878":[9,8,1,100],"1879":[6,8,11,84],"1880":[7,8,12,257],"1881":[7,8,12,159],"1882":[9,8,12,201],"1883":[7,8,6,134],"1884":[3,8,4,143],"1885":[5,8,9,215],"1886":[null,8,2,132],"1887":[3,8,2,128],"1888":[13,8,8,276],"1889":[5,8,null,92],"1890":[3,8,8,230],"1891":[5,8,7,218],"1892":[null,10,null,118],"1893":[3,10,null,121],"1894":[11,10,15,159],"1895":[8,10,7,191],"1896":[9,10,8,153],"1897":[3,10,10,326],"1898":[5,10,8,249],"1899":[null,10,1,134],"1900":[3,10,null,88],"1901":[12,10,14,355],"1902":[9,10,2,173],"1903":[3,10,3,162],"1904":[5,10,9,250],"1905":[null,9,2,132],"1906":[3,9,1,91],"1907":[5,9,12,288],"1908":[6,9,13,392],"1909":[13,9,13,238],"1910":[3,9,5,264],"1911":[5,9,9,285],"1912":[null,12,1,116],"1913":[8,12,null,82],"1914":[3,12,13,211],"1915":[4,12,16,346],"1916":[8,12,5,134],"1917":[9,12,7,197],"1918":[7,12,18,184],"1919":[7,12,14,336],"1920":[6,12,4,132],"1921":[9,12,20,506],"1922":[3,12,13,533],"1923":[5,12,13,279],"1924":[null,9,2,121],"1925":[3,9,4,99],"1926":[10,9,13,326],"1927":[6,9,4,106],"1928":[3,9,9,278],"1929":[5,9,14,233],"1930":[null,14,3,109],"1931":[6,14,12,164],"1932":[6,14,14,406],"1933":[11,14,5,209],"1934":[11,14,15,194],"1935":[9,14,12,172],"1936":[12,14,12,166],"1937":[11,14,13,123],"1938":[6,14,9,79],"1939":[3,14,5,270],"1940":[5,14,14,291],"1941":[null,11,null,188],"1942":[6,11,2,102],"1943":[8,11,12,293],"1944":[9,11,12,198],"1945":[7,11,15,286],"1946":[10,11,9,250],"1947":[7,11,7,375],"1948":[3,11,5,207],"1949":[5,11,7,368],"1950":[null,12,8,180],"1951":[6,12,4,186],"1952":[4,12,14,192],"1953":[7,12,6,161],"1954":[10,12,3,170],"1955":[10,12,5,151],"1956":[3,12,5,98],"1957":[3,12,2,188],"1958":[5,12,11,237],"1959":[null,7,9,164],"1960":[3,7,null,126],"1961":[14,7,18,264],"1962":[3,7,12,172],"1963":[3,7,7,207],"1964":[5,7,14,253],"1965":[null,10,3,160],"1966":[3,10,1,108],"1967":[10,10,15,230],"1968":[8,10,2,80],"1969":[9,10,3,241],"1970":[3,10,2,197],"1971":[5,10,11,286],"1972":[null,14,1,214],"1973":[3,14,1,137],"1974":[10,14,17,294],"1975":[7,14,11,175],"1976":[6,14,14,310],"1977":[3,14,15,264],"1978":[5,14,13,304],"1979":[null,13,1,164],"1980":[3,13,null,117],"1981":[12,13,17,493],"1982":[10,13,7,129],"1983":[3,13,4,231],"1984":[5,13,7,259],"1985":[null,11,7,151],"1986":[3,11,1,147],"1987":[11,11,15,411],"1988":[10,11,12,185],"1989":[3,11,8,171],"1990":[5,11,14,286],"1991":[null,11,3,137],"1992":[3,11,null,129],"1993":[8,11,15,420],"1994":[5,11,17,462],"1995":[3,11,9,229],"1996":[5,11,14,298],"1997":[null,10,null,201],"1998":[3,10,null,173],"1999":[16,10,17,775],"2000":[3,10,12,508],"2001":[5,10,10,403],"2002":[null,9,2,146],"2003":[1,9,11,156],"2004":[9,9,null,122],"2005":[6,9,14,244],"2006":[9,9,6,181],"2007":[9,9,16,200],"2008":[10,9,13,147],"2009":[10,9,17,389],"2010":[9,9,12,164],"2011":[8,9,13,272],"2012":[5,9,15,241],"2013":[5,9,14,236],"2014":[null,7,null,112],"2015":[5,7,7,135],"2016":[6,7,9,97],"2017":[6,7,15,103],"2018":[7,7,12,144],"2019":[11,7,8,92],"2020":[9,7,3,70],"2021":[12,7,12,121],"2022":[7,7,null,76],"2023":[8,7,17,434],"2024":[12,7,15,227],"2025":[6,7,5,77],"2026":[3,7,1,181],"2027":[5,7,15,267],"2028":[null,15,null,167],"2029":[10,15,15,256],"2030":[5,15,4,83],"2031":[11,15,17,294],"2032":[8,15,1,192],"2033":[8,15,14,305],"2034":[7,15,5,115],"2035":[9,15,9,199],"2036":[3,15,2,213],"2037":[5,15,11,274],"2038":[null,12,1,110],"2039":[6,12,1,120],"2040":[6,12,5,188],"2041":[4,12,17,224],"2042":[5,12,17,468],"2043":[11,12,17,350],"2044":[10,12,8,203],"2045":[11,12,13,277],"2046":[8,12,5,346],"2047":[3,12,5,269],"2048":[5,12,12,255],"2049":[null,14,2,142],"2050":[7,14,null,98],"2051":[6,14,5,109],"2052":[9,14,4,156],"2053":[6,14,null,72],"2054":[10,14,15,480],"2055":[9,14,14,268],"2056":[6,14,16,280],"2057":[9,14,2,127],"2058":[3,14,6,191],"2059":[5,14,10,266],"2060":[null,11,5,169],"2061":[10,11,null,130],"2062":[11,11,15,344],"2063":[9,11,13,206],"2064":[6,11,2,118],"2065":[9,11,10,260],"2066":[11,11,7,263],"2067":[3,11,5,144],"2068":[5,11,3,268],"2069":[null,17,4,209],"2070":[10,17,4,93],"2071":[6,17,12,195],"2072":[8,17,5,207],"2073":[10,17,13,198],"2074":[10,17,13,333],"2075":[8,17,12,173],"2076":[11,17,16,209],"2077":[9,17,10,169],"2078":[3,17,12,297],"2079":[5,17,14,253],"2080":[null,15,null,196],"2081":[3,15,1,137],"2082":[8,15,19,447],"2083":[5,15,1,46],"2084":[3,15,16,329],"2085":[5,15,17,299],"2086":[null,13,8,203],"2087":[1,13,7,88],"2088":[5,13,12,227],"2089":[12,13,12,124],"2090":[8,13,5,109],"2091":[8,13,13,90],"2092":[6,13,3,74],"2093":[4,13,12,107],"2094":[8,13,6,129],"2095":[10,13,14,204],"2096":[6,13,16,287],"2097":[5,13,11,297],"2098":[null,6,4,168],"2099":[6,6,13,199],"2100":[1,6,10,171],"2101":[10,6,1,44],"2102":[9,6,9,78],"2103":[10,6,11,176],"2104":[9,6,9,136],"2105":[10,6,1,121],"2106":[5,6,7,254],"2107":[5,6,10,183],"2108":[null,11,null,122],"2109":[3,11,12,185],"2110":[4,11,16,225],"2111":[5,11,5,163],"2112":[8,11,5,183],"2113":[3,11,2,183],"2114":[4,11,6,156],"2115":[null,13,9,194],"2116":[10,13,12,178],"2117":[8,13,12,239],"2118":[8,13,14,173],"2119":[10,13,9,120],"2120":[7,13,15,216],"2121":[3,13,10,199],"2122":[5,13,11,247],"2123":[null,10,1,153],"2124":[3,10,null,117],"2125":[13,10,14,530],"2126":[3,10,13,276],"2127":[5,10,12,281],"2128":[null,7,2,154],"2129":[3,7,1,138],"2130":[8,7,17,395],"2131":[3,7,10,220],"2132":[5,7,10,262],"2133":[null,10,4,163],"2134":[3,10,null,144],"2135":[10,10,19,301],"2136":[8,10,13,130],"2137":[3,10,15,316],"2138":[5,10,11,235],"2139":[null,11,1,150],"2140":[3,11,null,155],"2141":[9,11,19,417],"2142":[6,11,4,213],"2143":[11,11,1,165],"2144":[3,11,3,208],"2145":[5,11,14,282],"2146":[null,10,1,182],"2147":[3,10,3,153],"2148":[8,10,20,359],"2149":[6,10,15,465],"2150":[6,10,13,159],"2151":[3,10,10,175],"2152":[5,10,10,269],"2153":[null,9,1,201],"2154":[3,9,null,156],"2155":[9,9,34,699],"2156":[10,9,14,314],"2157":[8,9,15,500],"2158":[3,9,23,241],"2159":[5,9,14,386],"2160":[null,10,3,202],"2161":[3,10,null,164],"2162":[12,10,13,255],"2163":[7,10,13,299],"2164":[8,10,12,317],"2165":[12,10,12,424],"2166":[8,10,16,570],"2167":[3,10,9,397],"2168":[5,10,13,411],"2169":[null,13,null,163],"2170":[3,13,null,184],"2171":[12,13,9,199],"2172":[6,13,12,138],"2173":[7,13,5,248],"2174":[8,13,12,211],"2175":[7,13,14,140],"2176":[6,13,22,230],"2177":[11,13,17,151],"2178":[3,13,3,300],"2179":[5,13,15,385],"2180":[null,14,4,175],"2181":[3,14,null,180],"2182":[4,14,5,211],"2183":[9,14,9,129],"2184":[8,14,10,198],"2185":[6,14,15,236],"2186":[8,14,2,125],"2187":[5,14,14,235],"2188":[10,14,4,154],"2189":[10,14,11,221],"2190":[2,14,22,176],"2191":[3,14,6,315],"2192":[5,14,15,395],"2193":[null,10,null,192],"2194":[3,10,2,152],"2195":[11,10,13,193],"2196":[12,10,11,190],"2197":[8,10,11,238],"2198":[9,10,10,222],"2199":[9,10,6,168],"2200":[9,10,13,132],"2201":[3,10,11,292],"2202":[5,10,10,365],"2203":[null,7,null,124],"2204":[2,7,null,47],"2205":[2,7,null,117],"2206":[3,7,null,83],"2207":[7,7,null,213],"2208":[4,7,null,91],"2209":[6,7,2,144],"2210":[4,7,6,119],"2211":[null,6,null,67],"2212":[5,6,null,130],"2213":[3,6,null,129],"2214":[5,6,null,89],"2215":[3,6,null,74],"2216":[null,1,null,36],"2217":[2,1,null,80],"2218":[2,1,null,114],"2219":[2,1,null,435],"2220":[4,1,null,168],"2221":[1,1,null,13],"2222":[1,1,null,4],"2223":[1,1,null,56],"2224":[2,1,null,45],"2225":[null,3,null,79],"2226":[2,3,null,214],"2227":[3,3,null,116],"2228":[6,3,null,42],"2229":[6,3,null,369],"2230":[5,3,null,60],"2231":[3,3,null,1754],"2232":[3,3,null,57],"2233":[6,3,null,103],"2234":[6,3,null,12],"2235":[null,1,null,75],"2236":[6,1,null,12],"2237":[null,2,null,61],"2238":[3,2,null,205],"2239":[4,2,null,95],"2240":[2,2,null,32],"2241":[3,2,null,49],"2242":[4,2,null,42],"2243":[null,1,null,65],"2244":[4,1,null,56]},"averageFieldLength":[3.9678600269731916,9.108685968819621,10.007055624044638,282.01158129175957],"storedFields":{"0":{"u":"/docs/adr/index.html","d":"Architecture Decision Records","k":"Architecture Decision Records","x":"Accepted ADRs explaining why the core cross-cutting patterns exist. Read these before changing a pattern they describe: they capture context and trade-offs that aren't obvious…"},"1":{"u":"/docs/adr/index.html#writing-a-new-adr","d":"Architecture Decision Records","k":"Architecture Decision Records","t":"Writing a new ADR","x":"Start from docs-src/adr/template.md in this repo (it is not rendered; the generator only picks up NNN-.md), which carries the structure every record uses: Status (Proposed /…"},"2":{"u":"/docs/adr/001-manual-dto-mapping.html","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records"},"3":{"u":"/docs/adr/001-manual-dto-mapping.html#status","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records","t":"Status","x":"Accepted. Mechanism clarified 2026-06-26: the per-entity mappers are Riok.Mapperly source-generated (compile-time), not hand-written line by line. The decision to avoid runtime…"},"4":{"u":"/docs/adr/001-manual-dto-mapping.html#context","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records","t":"Context","x":"Domain entities must be mapped to DTOs for API responses. The two common approaches are: 1. Manual mapping classes (IEntityDTOMapper ) 2. Convention-based reflection mapping…"},"5":{"u":"/docs/adr/001-manual-dto-mapping.html#decision","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records","t":"Decision","x":"Use explicit, per-entity DTO mappers (each a Riok.Mapperly [Mapper] partial class whose MapToDTO body is source-generated at compile time) registered via Scrutor assembly…"},"6":{"u":"/docs/adr/001-manual-dto-mapping.html#rationale","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records","t":"Rationale","x":"- Compile-time safety: Mapping errors surface at build time, not runtime. Property renames break the build rather than silently mapping null. - Testability: Each mapper is a…"},"7":{"u":"/docs/adr/001-manual-dto-mapping.html#trade-offs","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records","t":"Trade-offs","x":"- More files (35 DTO mappers across Store + ADC: 22 in ADC, 13 in Store, plus the parallel IEntityRequestMapper classes). The interface's default MapToDTOs implementation is…"},"8":{"u":"/docs/adr/002-navigation-populators.html","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records"},"9":{"u":"/docs/adr/002-navigation-populators.html#status","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records","t":"Status","x":"Accepted"},"10":{"u":"/docs/adr/002-navigation-populators.html#context","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records","t":"Context","x":"The application supports multiple database backends (SQL Server, Cosmos DB, SQLite). EF Core's .Include() works for SQL Server but fails for Cosmos DB cross-container…"},"11":{"u":"/docs/adr/002-navigation-populators.html#decision","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records","t":"Decision","x":"Each entity that has unsupported navigations gets a INavigationPopulator implementation. A DeclarativeNavigationPopulator base class (added in MMCA.Common) allows populators to…"},"12":{"u":"/docs/adr/002-navigation-populators.html#rationale","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records","t":"Rationale","x":"- Multi-DB support: The query pipeline automatically falls back from Include to NavigationPopulator when the data source reports navigations as unsupported. - Batch efficiency:…"},"13":{"u":"/docs/adr/002-navigation-populators.html#trade-offs","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records","t":"Trade-offs","x":"- Extra abstraction layer for SQL Server (where Include works fine). Mitigated: the populator is only called when the query pipeline's metadata says navigations are unsupported.…"},"14":{"u":"/docs/adr/003-outbox-dual-dispatch.html","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records"},"15":{"u":"/docs/adr/003-outbox-dual-dispatch.html#status","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-07-19 (integration-event routing via IMessageBus, lease-based claims for safe scale-out, dead-letter visibility, post-commit dispatch; see Revision below).…"},"16":{"u":"/docs/adr/003-outbox-dual-dispatch.html#context","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Context","x":"Domain events must be reliably published after aggregate changes are persisted. Two failure modes exist: 1. In-process dispatch fails (e.g., handler throws): the event is lost if…"},"17":{"u":"/docs/adr/003-outbox-dual-dispatch.html#decision","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Decision","x":"Use a dual-dispatch strategy: 1. Outbox persistence: Domain events are serialized into OutboxMessage rows within the same database transaction as the aggregate changes. This…"},"18":{"u":"/docs/adr/003-outbox-dual-dispatch.html#rationale","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Rationale","x":"- Guaranteed delivery: The outbox table is written atomically with the aggregate changes. Even if the process crashes after persistence, the background processor catches up. -…"},"19":{"u":"/docs/adr/003-outbox-dual-dispatch.html#trade-offs","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Trade-offs","x":"- Domain event handlers must be idempotent (this is a good practice regardless). - The outbox table grows until processed entries are cleaned up: OutboxCleanupService purges rows…"},"20":{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-07-19","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-07-19)","x":"Four changes from the 2026-07-19 full review: 1. Integration events route through the outbox to IMessageBus, never local dispatch. An IIntegrationEvent raised via AddDomainEvent…"},"21":{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-07-24","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-07-24)","x":"Three capture-side corrections found in a code review. None change the dual-dispatch decision; they close gaps between what it promised and what the interceptor did. 1. Capture…"},"22":{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-08-01","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-08-01)","x":"One retry-pacing correction. The dual-dispatch decision is unchanged; the Trade-offs above described a cadence the processor no longer has. 1. Retry backoff is explicit, and it…"},"23":{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-08-07","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"One retry-pacing refinement. The decision and the curve are unchanged; the waits are no longer identical across a batch. 1. The retry backoff carries random jitter. The…"},"24":{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-08-26","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-08-26)","x":"Five changes. The dual-dispatch decision is unchanged; what changes is what happens to a message that must not overtake its predecessor, to one that never made it out, and to the…"},"25":{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-09-07","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The dispatch model is unchanged. What changed is the name the outbox's downstream resources take by default, from the 2026-09-07 security review (SEC-Common-53).…"},"26":{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-09-11","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"The dispatch model is unchanged: same table, same claim lease, same dual dispatch, same retry and dead-letter policy. What changed is what a row carries, because the hop was…"},"27":{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-10-01","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The dual-dispatch decision is unchanged. One rule is added to it (Decision item 4), and several statements made by earlier revisions no longer describe the code. The…"},"28":{"u":"/docs/adr/004-authentication-dual-fetch.html","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records"},"29":{"u":"/docs/adr/004-authentication-dual-fetch.html#status","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Status","x":"Accepted. Updated 2026-09-03 (JwtSettings / JwtSigningAlgorithm cited at their real Auth/ paths, and the validator registration described with its current signature and…"},"30":{"u":"/docs/adr/004-authentication-dual-fetch.html#context","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Context","x":"When the modular monolith is extracted into per-module service hosts behind a gateway (ADR-008), every service must authenticate the same end-user JWT, but only one service…"},"31":{"u":"/docs/adr/004-authentication-dual-fetch.html#decision","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Decision","x":"Validate cross-service tokens with asymmetric (RS256) signatures plus JWKS / OIDC discovery, keeping the symmetric (HS256) shared-secret path as the in-process monolith option.…"},"32":{"u":"/docs/adr/004-authentication-dual-fetch.html#rationale","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Rationale","x":"- No shared signing key. Only Identity can mint tokens; every other service holds only the public key it fetched, so a compromised non-Identity service cannot forge tokens, and…"},"33":{"u":"/docs/adr/004-authentication-dual-fetch.html#trade-offs","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Trade-offs","x":"- More moving parts than a shared secret. RS256 needs key generation, distribution of the public half, a JWKS endpoint, and discovery wiring, versus one symmetric string. -…"},"34":{"u":"/docs/adr/004-authentication-dual-fetch.html#revision-2026-10-01","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Anchor-only refresh; no decision or rationale changed. The AddForwardedJwtBearer registration lives in the partial file…"},"35":{"u":"/docs/adr/004-authentication-dual-fetch.html#related","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Related","x":"ADR-007 (gRPC calls forward the validated JWT downstream via JwtForwardingClientInterceptor), ADR-008 (the extraction that split issuer and validator into separate processes),…"},"36":{"u":"/docs/adr/005-soft-delete-vs-erasure.html","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records"},"37":{"u":"/docs/adr/005-soft-delete-vs-erasure.html#status","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Status","x":"Accepted. Updated 2026-06-27 (documented the [Pii] → IAnonymizable build-time guard and PiiRedactor). Updated 2026-08-26 (DeletedOn/DeletedBy stamps, the DeleteChildren cascade…"},"38":{"u":"/docs/adr/005-soft-delete-vs-erasure.html#context","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Context","x":"The framework's default deletion model is soft-delete: AuditableBaseEntity.Delete() sets IsDeleted = true and EF Core global query filters exclude the row from normal queries.…"},"39":{"u":"/docs/adr/005-soft-delete-vs-erasure.html#decision","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Decision","x":"Separate the two concerns and provide an extension point for each, rather than overloading soft-delete: 1. Soft-delete stays the default for lifecycle/state management (hide +…"},"40":{"u":"/docs/adr/005-soft-delete-vs-erasure.html#rationale","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Rationale","x":"- Right tool per concern: soft-delete answers \"is this record active?\"; erasure answers \"has this person's data been removed?\". Conflating them (e.g. hard-deleting inside…"},"41":{"u":"/docs/adr/005-soft-delete-vs-erasure.html#trade-offs","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Trade-offs","x":"- Erasure is opt-in per entity, but the opt-in is guarded: an aggregate exposing a [Pii]-marked property that does not implement IAnonymizable fails the architecture fitness…"},"42":{"u":"/docs/adr/005-soft-delete-vs-erasure.html#revision-2026-09-07","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Anonymization keeps the row and its foreign keys: Anonymize() overwrites the personal fields and leaves the entity's relationships intact, because the aggregate still has to hang…"},"43":{"u":"/docs/adr/005-soft-delete-vs-erasure.html#revision-2026-10-01","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision changed. One description in Decision 1 was corrected: it said composing Result.Combine(DeleteChildren (lines), base.Delete()) aborts the cascade \"before the root is…"},"44":{"u":"/docs/adr/006-database-per-service.html","d":"ADR-006: Database per Service","k":"Architecture Decision Records"},"45":{"u":"/docs/adr/006-database-per-service.html#status","d":"ADR-006: Database per Service","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-07). Supersedes the earlier \"deliberately one shared database\" stance. Clarified 2026-06-27: the single context class became one sealed context class per engine…"},"46":{"u":"/docs/adr/006-database-per-service.html#context","d":"ADR-006: Database per Service","k":"Architecture Decision Records","t":"Context","x":"When the modules were first extracted into independently-deployable services, all services in an app still pointed at a single shared SQL database with a single OutboxMessages…"},"47":{"u":"/docs/adr/006-database-per-service.html#decision","d":"ADR-006: Database per Service","k":"Architecture Decision Records","t":"Decision","x":"Adopt database-per-service: each service owns its own physical database with its own OutboxMessages table. - One sealed concrete context class per engine, one instance per…"},"48":{"u":"/docs/adr/006-database-per-service.html#rationale","d":"ADR-006: Database per Service","k":"Architecture Decision Records","t":"Rationale","x":"- Removes the shared-outbox race (the sharpest cost of the shared DB) without an OriginService filter: physical isolation is simpler and stronger than a logical filter. - Real…"},"49":{"u":"/docs/adr/006-database-per-service.html#trade-offs","d":"ADR-006: Database per Service","k":"Architecture Decision Records","t":"Trade-offs","x":"- No cross-database FKs or transactions. Relationships that span services degrade to scalar IDs; consistency across services is eventual (outbox + broker), not transactional. -…"},"50":{"u":"/docs/adr/007-grpc-extraction.html","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records"},"51":{"u":"/docs/adr/007-grpc-extraction.html#status","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-08-23 (the [ServiceContract] marker now has a dedicated fitness rule behind it, ServiceContractPurityTestsBase, subclassed in all four repos; it is a…"},"52":{"u":"/docs/adr/007-grpc-extraction.html#context","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records","t":"Context","x":"Once modules became separate service processes, the in-process interface calls between them (e.g. Conference → Engagement's IBookmarkCountService, Engagement → Conference's…"},"53":{"u":"/docs/adr/007-grpc-extraction.html#decision","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records","t":"Decision","x":"Use gRPC, exposed through MMCA.Common.Grpc, with a contract-package convention: - .Contracts projects hold the .proto definitions plus a gRPC adapter that implements the same…"},"54":{"u":"/docs/adr/007-grpc-extraction.html#rationale","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records","t":"Rationale","x":"- No business-logic rewrite: the gRPC adapter implements the interface modules already depend on; swapping in-process for cross-process is a registration change. - Transport…"},"55":{"u":"/docs/adr/007-grpc-extraction.html#trade-offs","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records","t":"Trade-offs","x":"- Bidirectional pairs need care. Conference ↔ Engagement is a mutual gRPC pair; the AppHost deliberately omits a reciprocal WaitFor to avoid a startup deadlock: transient \"peer…"},"56":{"u":"/docs/adr/008-service-extraction-topology.html","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records"},"57":{"u":"/docs/adr/008-service-extraction-topology.html#status","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Status","x":"Accepted. Amended by ADR-089 (2026-08-18): the Gateway keeps the route-to-service map this record gave it, but stops expressing it as MapForwarder calls in code. YARP…"},"58":{"u":"/docs/adr/008-service-extraction-topology.html#context","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Context","x":"ADC began as a modular monolith: one MMCA.ADC.WebAPI host loaded every module (Identity, Conference, Engagement, Notification) in-process via the ModuleLoader, sharing one…"},"59":{"u":"/docs/adr/008-service-extraction-topology.html#decision","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Decision","x":"Extract one service host per module: MMCA.ADC.{Identity,Conference,Engagement,Notification}.Service and front them with a single YARP reverse-proxy Gateway (MMCA.ADC.Gateway,…"},"60":{"u":"/docs/adr/008-service-extraction-topology.html#rationale","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Rationale","x":"- No business-logic rewrite. Because a service is just the monolith with one module enabled, extraction was a hosting/wiring change, not a domain change, and the module-isolation…"},"61":{"u":"/docs/adr/008-service-extraction-topology.html#trade-offs","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Trade-offs","x":"- Operational complexity. Four deployables plus a Gateway, service discovery, a broker, and per-service databases, versus one process. Mitigated locally by Aspire orchestration…"},"62":{"u":"/docs/adr/008-service-extraction-topology.html#applicability","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Applicability","x":"This ADR is framed around ADC (the first repo extracted), but the same topology is now the framework's standard extraction shape, not an ADC-only choice. MMCA.Store followed it:…"},"63":{"u":"/docs/adr/008-service-extraction-topology.html#revision-2026-10-01","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Three statements are corrected against the code; the topology itself is unchanged. The extracted service hosts do not rely on Disabled stubs for their peers: each passes only its…"},"64":{"u":"/docs/adr/008-service-extraction-topology.html#related","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (outbox dual dispatch), ADR-004 (cross-service token validation via JWKS), ADR-006 (database per service), and ADR-007 (gRPC cross-service calls) are the facet decisions…"},"65":{"u":"/docs/adr/009-resilience-and-recovery-objectives.html","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records"},"66":{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#status","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-14). Amended by ADR-087 (2026-08-18): the resilience objective extends past outbound HTTP and gRPC clients for the first time, to the outbox's broker publish,…"},"67":{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#context","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Context","x":"The framework already supplies the mechanisms for surviving partial failure: a standard Polly resilience handler (timeout / retry / circuit breaker), the outbox for at-least-once…"},"68":{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#decision","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Decision","x":"1. Resilience is a framework invariant, not a per-call choice. Every outbound HttpClient and gRPC client registered through the framework's extension methods (AddTypedGrpcClient,…"},"69":{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#rationale","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Rationale","x":"- Invariant over discipline. A fitness function turns \"remember to add resilience\" into a build gate: the same approach the framework already uses for the layer rules and the…"},"70":{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#trade-offs","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Trade-offs","x":"- The named gate (ResilienceHandlerTests, MMCA.Common.Grpc.Tests) asserts every option the gRPC client path (AddTypedGrpcClient) configures…"},"71":{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#revision-2026-08-18","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"This record's first Decision point scoped resilience to \"every outbound HttpClient and gRPC client registered through the framework's extension methods\". That scope was accurate…"},"72":{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#revision-2026-09-11","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"Two amendments, both about parts of this posture the record could not previously see or reach. (a) The standard handler is now observable. Decision point 1 makes the resilience…"},"73":{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#revision-2026-09-19","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Revision (2026-09-19)","x":"That consumer-side companion is now in MMCA.Store's main. StripeClientFactory builds the SDK client over an explicitly bounded HTTP stack instead of Stripe.net's defaults, and…"},"74":{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#revision-2026-10-01","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changes; the current-state sections are brought back in line with the code. Decision point 1 no longer says the gRPC client matches the global HTTP…"},"75":{"u":"/docs/adr/010-integration-event-schema-versioning.html","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records"},"76":{"u":"/docs/adr/010-integration-event-schema-versioning.html#status","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-19). Updated 2026-06-27 (Helpdesk enforcement gap closed; all three consumers now gate the convention). Updated 2026-08-14 (ADC now gates seven events, and a…"},"77":{"u":"/docs/adr/010-integration-event-schema-versioning.html#context","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Context","x":"Integration events cross service boundaries (Identity → Conference, Conference ↔ Engagement, …) and are resolved by consumers solely by their type string: the outbox serializes…"},"78":{"u":"/docs/adr/010-integration-event-schema-versioning.html#decision","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Decision","x":"1. Every integration event carries an explicit SchemaVersion. BaseIntegrationEvent exposes public virtual int SchemaVersion = 1;. It is serialized with the payload…"},"79":{"u":"/docs/adr/010-integration-event-schema-versioning.html#rationale","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Rationale","x":"- A signal, enforced. A version field plus a build-gating convention test turns \"remember the contract\" into something the tooling checks: the same invariant-over-discipline…"},"80":{"u":"/docs/adr/010-integration-event-schema-versioning.html#trade-offs","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Trade-offs","x":"- SchemaVersion is a signal, not a mechanism: by itself it does not stop a consumer breaking on a real reshape. The load-bearing half is the discipline (new type + upcaster). At…"},"81":{"u":"/docs/adr/010-integration-event-schema-versioning.html#amendment-2026-09-11-integration-event-payload-purity","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Amendment (2026-09-11): integration-event payload purity","x":"Versioning answers \"how may this contract change\". It does not answer \"what may be in it in the first place\", and that gap is where the more expensive failure lives. An event…"},"82":{"u":"/docs/adr/010-integration-event-schema-versioning.html#revision-2026-10-01","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; two statements and one citation were corrected against source. The amendment claimed both purity rules are vacuous for a module-less map; only…"},"83":{"u":"/docs/adr/011-single-locale-i18n.html","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records"},"84":{"u":"/docs/adr/011-single-locale-i18n.html#status","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records","t":"Status","x":"Superseded by ADR-027 (2026-06-27). Originally Accepted (2026-06-19). The \"if multi-locale is ever required\" scope below is the blueprint ADR-027 implements; this record is…"},"85":{"u":"/docs/adr/011-single-locale-i18n.html#context","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records","t":"Context","x":"The MMCA applications (the ADC conference app, the Store) and the MMCA.Common.UI library currently ship a single locale (en-US). The architecture rubric scores…"},"86":{"u":"/docs/adr/011-single-locale-i18n.html#decision","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records","t":"Decision","x":"1. Single-locale (en-US) is an explicit non-goal for now. User-facing strings are inline in markup; dates/numbers use invariant or fixed formatting where appropriate. 2. The…"},"87":{"u":"/docs/adr/011-single-locale-i18n.html#rationale","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records","t":"Rationale","x":"- Recording the decision converts an implicit rubric-zero into a conscious, revisitable choice: the same posture as the single-region DR acceptance in ADR-009. - Premature i18n…"},"88":{"u":"/docs/adr/011-single-locale-i18n.html#trade-offs","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Adding a locale later touches every view plus the formatting paths: a real but bounded effort, accepted. - Hard-coded strings make a future extraction larger; mitigated by the…"},"89":{"u":"/docs/adr/012-grpc-host-transport.html","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records"},"90":{"u":"/docs/adr/012-grpc-host-transport.html#status","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Status","x":"Accepted (re-verified against source 2026-09-03). Revised 2026-09-07 (the rate-limit and HTTPS-redirect exemptions that used to key on a caller-set Content-Type: application/grpc…"},"91":{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-06-22-store-converged-to-profile-a","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-06-22): Store converged to Profile A","x":"Store originally chose Profile B, but its cross-service gRPC failed in Azure Container Apps. With Http1AndHttp2 Kestrel + transport: 'auto' ingress on a cleartext endpoint there…"},"92":{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-07-09-adc-notification-adds-a-mixed-endpoint-profile-per-endpoint-protocols","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-07-09): ADC Notification adds a mixed-endpoint profile (per-endpoint protocols)","x":"The live-channel push pipeline (ADR-039) gave ADC's Notification service an inbound cleartext gRPC server (LiveChannelPushService.PushToChannel, called best-effort by Engagement…"},"93":{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-07-25-probe-listeners-are-adcs-answer-not-tcp-probes-and-gateway-routed-jwks-is-a-local-only-rule","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-07-25): probe listeners are ADC's answer, not TCP probes; and gateway-routed JWKS is a local-only rule","x":"Two claims above were written from an earlier state of the code and no longer describe either app. 1. ADC probes never touch the traffic endpoint; TCP probes were then…"},"94":{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-07-28-the-probe-listener-is-the-single-pattern-in-both-apps-no-tcp-probes-anywhere","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-07-28): the probe listener is the single pattern in both apps (no TCP probes anywhere)","x":"Store PR 55 (commit 297064bb, merged 2026-07-27) ported ADC's dedicated probe listener to Store, so the Store-only tcpSocket exception recorded in the 2026-07-25 update above is…"},"95":{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-08-07-the-probe-listener-moved-into-mmcacommon-and-notifications-grpc-endpoint-carries-a-second-service","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-08-07): the probe listener moved into MMCA.Common, and Notification's gRPC endpoint carries a second service","x":"1. One shared framework method, not a per-service file. The KestrelConfiguration.cs copies the two updates above cite no longer exist in either app. The pattern was extracted…"},"96":{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-08-14-stores-sales-runs-the-mixed-endpoint-profile-too-so-no-pure-profile-b-host-remains","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-08-14): Store's Sales runs the mixed-endpoint profile too, so no pure Profile B host remains","x":"Sales gained an inbound gRPC edge of its own (IUserSalesExportService, the Identity-driven data-subject export), and it resolved that the same way ADC's Notification did: not by…"},"97":{"u":"/docs/adr/012-grpc-host-transport.html#context","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Context","x":"Once modules were extracted into separate service hosts (ADR-008) that call each other synchronously over gRPC (ADR-007), each service's Kestrel had to serve both REST traffic…"},"98":{"u":"/docs/adr/012-grpc-host-transport.html#decision","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Decision","x":"Pick one of two coherent transport profiles per app, and wire the gateway forwarder and JWKS discovery to match. Use when services must serve gRPC on cleartext (any bidirectional…"},"99":{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-08-29-aspire-local-health-gating-for-http2-only-endpoints-and-the-downstream-probe-negotiates-its-version","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-08-29): Aspire-local health gating for Http2-only endpoints, and the downstream probe negotiates its version","x":"Two v1.167.0 framework additions close the last two operational gaps this profile family carried. 1. Profile A resources are now health-gated locally. Aspire's stock…"},"100":{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-08-29-east-west-grpc-clients-state-their-own-circuit-breaker-v11680","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-08-29): east-west gRPC clients state their own circuit breaker (v1.168.0)","x":"The health checks above are the gateway's view of a downstream. An east-west call (AddTypedGrpcClient, MMCA.Common/Source/Presentation/MMCA.Common.Grpc/DependencyInjection.cs)…"},"101":{"u":"/docs/adr/012-grpc-host-transport.html#rationale","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Rationale","x":"- The Kestrel protocol choice is the root constraint; the gateway-forward mode and the JWKS authority are downstream consequences, not independent knobs. Documenting them as a…"},"102":{"u":"/docs/adr/012-grpc-host-transport.html#trade-offs","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Two profiles to keep straight. A service that gains an inbound gRPC edge must migrate from Profile B to Profile A and move its gateway cluster onto the HTTP/2 exact version…"},"103":{"u":"/docs/adr/012-grpc-host-transport.html#revision-2026-09-07","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The transport profiles are unchanged. What changed is how the rest of the pipeline recognises an h2c gRPC request, from the 2026-09-07 security review (SEC-Common-44). Two…"},"104":{"u":"/docs/adr/012-grpc-host-transport.html#revision-2026-10-01","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed: both apps still run Profile A on their gRPC-serving hosts and the mixed-endpoint profile on ADC Notification and Store Sales. This pass…"},"105":{"u":"/docs/adr/012-grpc-host-transport.html#related","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Related","x":"- ADR-004 (cross-service token validation via JWKS / OIDC discovery), ADR-007 (gRPC cross-service calls), ADR-008 (monolith → services + gateway topology), ADR-039 (live-channel…"},"106":{"u":"/docs/adr/013-result-pattern.html","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records"},"107":{"u":"/docs/adr/013-result-pattern.html#status","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-07-21 (exception-handler chain / ProblemDetails edge contract documented). Revised 2026-08-26 (ErrorType.Unexpected, severity-ranked status selection for…"},"108":{"u":"/docs/adr/013-result-pattern.html#context","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Context","x":"Operations at every layer fail in expected ways: input is invalid, a domain invariant is broken, a requested entity is missing, a uniqueness conflict occurs, the caller lacks…"},"109":{"u":"/docs/adr/013-result-pattern.html#decision","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Decision","x":"Model expected failures as values using Result / Result (MMCA.Common.Shared.Abstractions), not exceptions. - A Result is either success or failure; a failure carries one or more…"},"110":{"u":"/docs/adr/013-result-pattern.html#rationale","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Rationale","x":"- Failures are in the signature. A method that can fail returns Result , so the caller cannot silently ignore the failure path the way an uncaught exception allows. - Category,…"},"111":{"u":"/docs/adr/013-result-pattern.html#trade-offs","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Trade-offs","x":"- More ceremony at call sites than letting an exception bubble; the combinators absorb most of it. - Two error channels coexist (Result for expected, exceptions for exceptional).…"},"112":{"u":"/docs/adr/013-result-pattern.html#revision-2026-10-01","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Citations were re-anchored to the current source after line shifts: the gRPC status table, ToRpcException ranking and trailer writes in…"},"113":{"u":"/docs/adr/013-result-pattern.html#related","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Related","x":"ADR-007 (Result over the wire via gRPC, the second edge the shared severity ranking now serves), ADR-014 (the decorator pipeline returns Result.Failure to short-circuit a command…"},"114":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records"},"115":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#status","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-07-19 (Transactional semantics: rollback on business failure + post-commit event dispatch; see Revision below). Revised 2026-08-18 (the pipeline order…"},"116":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#context","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Context","x":"Commands and queries share cross-cutting concerns: validation, transactions, cache invalidation, logging / timing, and feature gating. Putting that logic inside each handler…"},"117":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#decision","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Decision","x":"Use single-responsibility handlers behind a Scrutor-composed decorator pipeline. - ICommandHandler and IQueryHandler (MMCA.Common.Application) are one handler per use case, each…"},"118":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#rationale","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Rationale","x":"- Thin, testable handlers. A handler has no transaction, logging, or caching plumbing, so it is unit-tested in isolation. - One place to read and change the pipeline. The order…"},"119":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#trade-offs","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Trade-offs","x":"- Registration order is the reverse of execution order (a Scrutor foot-gun), mitigated by the inline ordering comments in AddApplicationDecorators(). - Decorators must be…"},"120":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#revision-2026-07-19","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Revision (2026-07-19)","x":"Two Transactional-decorator semantics changed with the 2026-07-19 full review: - A returned business failure now rolls the transaction back. Previously a handler returning…"},"121":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#revision-2026-08-18","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Two decorators were added to both chains, so the order recorded in the Decision above is no longer the shipped one. The registration site is unchanged in kind:…"},"122":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#revision-2026-08-26","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Revision (2026-08-26)","x":"One decorator joins the query chain, and the registration sequence stops being a rule consumers have to remember. The query line in the Revision (2026-08-18) above is superseded;…"},"123":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#revision-2026-09-07","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"One decorator's key composition changed (SEC-Common-19 / SEC-Common-37). CachingQueryDecorator builds its entry key as TenantCacheKey.Scope(tenantContext,…"},"124":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#related","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Related","x":"ADR-013 (Result, the short-circuit currency of the pipeline, and the Failure error type the timeout decorator reuses because the taxonomy has no timeout member), ADR-003…"},"125":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#revision-2026-09-11","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"One decorator behaviour changed on both chains, and it is a logging decision rather than a pipeline one: the order, the markers, the sealing rule and the short-circuit currency…"},"126":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#revision-2026-09-19","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Revision (2026-09-19)","x":"One decorator pair changed inside, and the pipeline around it did not: the order is still FeatureGate - Authorization - Logging - Caching - Validating - Timeout - Transactional -…"},"127":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#revision-2026-09-25","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Revision (2026-09-25)","x":"The consumer fitness tests changed, not the pipeline. ADC's DecoratorPipelineOrderTests now builds its registration sequence through AddMmcaApplicationPipeline, the helper its…"},"128":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#revision-2026-10-01","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The Decision gains one bullet stating where each kind of validation lives, so the Validating decorator's role is read against its neighbours rather than in isolation. The chain…"},"129":{"u":"/docs/adr/015-architecture-fitness-functions.html","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records"},"130":{"u":"/docs/adr/015-architecture-fitness-functions.html#status","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-08-18 (two new rule families, namespace dependency cycles and trailing CancellationToken declarations, plus a third enforcement layer: a compile-time…"},"131":{"u":"/docs/adr/015-architecture-fitness-functions.html#context","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Context","x":"The codebase rests on invariants that are easy to state and easy to erode by accident: clean- architecture layer flow (Domain depends on nothing above it), module isolation (no…"},"132":{"u":"/docs/adr/015-architecture-fitness-functions.html#decision","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Decision","x":"Enforce architectural invariants as automated checks that gate the build, in two layers (a third joined them on 2026-08-18: see the Revision at the end). 1. Compile-time guard.…"},"133":{"u":"/docs/adr/015-architecture-fitness-functions.html#rationale","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Rationale","x":"- Invariant over discipline. Turning \"do not do X\" into a red build is the only enforcement that scales. It is the same lever used by the layer rules, the resilience gate…"},"134":{"u":"/docs/adr/015-architecture-fitness-functions.html#trade-offs","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Trade-offs","x":"- The tests assert structure / registration, not runtime behavior. ADR-009's test proves a client wires resilience, not that its policy values are correct; parameter tuning stays…"},"135":{"u":"/docs/adr/015-architecture-fitness-functions.html#revision-2026-08-18","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Two new rule families joined the shared library, and a third enforcement layer joined the two the Decision above describes. The counts in MMCA.Common/FACTS.md move with them: 102…"},"136":{"u":"/docs/adr/015-architecture-fitness-functions.html#revision-2026-08-18-section-b-rule-families","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-08-18): Section B rule families","x":"A second entry on the same date, kept separate rather than folded into the one above because it lands with a different wave and changes a number that revision states. Two rule…"},"137":{"u":"/docs/adr/015-architecture-fitness-functions.html#revision-2026-08-23-superseded-counts-a-re-anchored-citation-and-the-real-test-floor","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-08-23): superseded counts, a re-anchored citation, and the real test floor","x":"No rule family joined or left the library in this entry. It corrects three things the two 2026-08-18 revisions above state, and it is kept as its own entry rather than edited…"},"138":{"u":"/docs/adr/015-architecture-fitness-functions.html#revision-2026-09-01-the-public-api-gates-real-coverage-and-re-anchored-citations","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-09-01): the public API gate's real coverage, and re-anchored citations","x":"No rule family joined or left the library in this entry and no decision changed. It corrects the public-API figures the first 2026-08-18 revision recorded, which had gone stale…"},"139":{"u":"/docs/adr/015-architecture-fitness-functions.html#revision-2026-09-03-the-accepted-cycles-middle-node-the-feature-folder-move-re-anchored-citations","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-09-03): the accepted cycle's middle node, the feature-folder move, re-anchored citations","x":"No rule family joined or left the library in this entry and no decision changed. One accepted allowance changed shape, the files that hold the rules and the tests moved, and the…"},"140":{"u":"/docs/adr/015-architecture-fitness-functions.html#revision-2026-09-11-the-restated-counts-come-out-six-citations-move","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-09-11): the restated counts come out, six citations move","x":"No rule family joined or left the library in this entry and no decision changed. It removes the one kind of number this record keeps failing to keep current and re-anchors the…"},"141":{"u":"/docs/adr/015-architecture-fitness-functions.html#revision-2026-09-19-the-gate-covers-eighteen-projects-and-the-unshipped-side-nearly-doubled","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-09-19): the gate covers eighteen projects, and the unshipped side nearly doubled","x":"No rule family joined or left the library in this entry and no decision changed. It corrects the public-API gate's file and declaration figures, which had gone stale in the…"},"142":{"u":"/docs/adr/015-architecture-fitness-functions.html#revision-2026-10-01-rs0026--rs0027-become-errors-the-baselines-roll-over-citations-move","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-10-01): RS0026 / RS0027 become errors, the baselines roll over, citations move","x":"No rule family joined or left the library in this entry. One public API gate setting changed, the baseline figures moved with the v1.216.0 release (MMCA.Common/FACTS.md:14), and…"},"143":{"u":"/docs/adr/015-architecture-fitness-functions.html#related","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Related","x":"ADR-009 (resilience gate), ADR-010 (event-version gate), ADR-016 (MassTransit pin gate, and the lockstep release cadence the public API baseline is pinned to), ADR-006/007/008…"},"144":{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records"},"145":{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#status","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-15). Amended (2026-07-28): the fitness function now gates two commercial-license majors (MassTransit and SixLabors.ImageSharp), so the decision is restated as…"},"146":{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#context","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Context","x":"MMCA.Common publishes its MMCA.Common. NuGet package set (see FACTS.md for the authoritative list and count) consumed by three downstream repos: the two production apps (Store,…"},"147":{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#decision","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Decision","x":"1. Version the whole MMCA.Common. package set in lockstep. All packages share one version (MinVer, derived from a single vX.Y.Z git tag); a release tags every package (see…"},"148":{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#rationale","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Rationale","x":"- One version, one compatibility story. Lockstep removes the N-package matrix: \"everything on vX.Y.Z\" is the only supported combination, which is the right trade for a small…"},"149":{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#trade-offs","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Trade-offs","x":"- A consumer cannot adopt a single package in isolation: it takes the whole set at the new version. - Lockstep will bump a package whose code did not change (acceptable: the…"},"150":{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#transport-exit-options","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Transport exit options","x":"The pin has a horizon. MassTransit v8 is the free major and its community support ends at the end of 2026 (a maintainer statement about the package, not something this repository…"},"151":{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#alternatives-rejected","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Alternatives rejected","x":"- Opt-in flags and compatibility shims for a framework change, with consumers upgraded per page or per PR. Declined outright: no virtual opt-in properties, no parallel old and…"},"152":{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#revision-2026-10-01","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision, pin, gate or rationale changed: MassTransit stays below major 9 and ImageSharp below major 4, and every repo still sits on one MassTransit patch. The current-state…"},"153":{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#related","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (the fitness function that enforces the pins), ADR-003 / ADR-006 (MassTransit is the broker transport behind the outbox and database-per-service flows). ADR-118 revisits…"},"154":{"u":"/docs/adr/017-request-idempotency.html","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records"},"155":{"u":"/docs/adr/017-request-idempotency.html#status","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-08-01: the guard around execute-and-store is now an IDistributedLock resolved from DI (Redis-backed wherever a connection multiplexer is registered, which…"},"156":{"u":"/docs/adr/017-request-idempotency.html#context","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Context","x":"Write endpoints (POST / PUT / PATCH) are exposed to client retries and double-submits: a flaky network, an impatient user double-clicking, or a resilience pipeline re-issuing a…"},"157":{"u":"/docs/adr/017-request-idempotency.html#decision","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Decision","x":"Provide opt-in, client-driven request idempotency as an MVC action filter in MMCA.Common.API. - Opt-in per action. [Idempotent] (IdempotentAttribute, a ServiceFilterAttribute…"},"158":{"u":"/docs/adr/017-request-idempotency.html#rationale","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Rationale","x":"- Safety at the edge, not in every handler. Deduplication lives in one filter, so a handler stays a thin use case (ADR-014) and does not grow ad-hoc \"did I already do this?\"…"},"159":{"u":"/docs/adr/017-request-idempotency.html#trade-offs","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Trade-offs","x":"- Cross-instance mutual exclusion follows Redis, so it is a deployment property, not a guarantee. Every ADC and Store service host registers a Redis IConnectionMultiplexer when a…"},"160":{"u":"/docs/adr/017-request-idempotency.html#revision-2026-08-18","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"The last Trade-off above is the one this revision addresses, and it does so by changing what is required. Nothing here makes an endpoint idempotent. What it requires is that…"},"161":{"u":"/docs/adr/017-request-idempotency.html#revision-2026-10-01","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The Decision and Trade-offs are corrected to the filter as it runs today. Four statements had drifted. The cache-key subject is FindUserIdValue() (sub, then…"},"162":{"u":"/docs/adr/017-request-idempotency.html#related","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (handler idempotency for outbox/event consumers, a distinct concern), ADR-013 (Result is the response the filter caches/replays), ADR-014 (the filter keeps the handler…"},"163":{"u":"/docs/adr/018-polyglot-persistence.html","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records"},"164":{"u":"/docs/adr/018-polyglot-persistence.html#status","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Status","x":"Accepted. The framework plumbing is complete, covered by unit and integration tests (DataSourceResolverTests, CrossDataSourceDegradeConventionTests, EntityTypeConfigurationTests,…"},"165":{"u":"/docs/adr/018-polyglot-persistence.html#context","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Context","x":"ADR-006 (database-per-service) splits storage along the Name axis: several physically separate databases, all on the same engine (SQL Server), one per service. A second,…"},"166":{"u":"/docs/adr/018-polyglot-persistence.html#decision","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Decision","x":"Support four storage engines behind one entity model and one set of repository abstractions, selected per entity configuration. 1. DataSource engine enum: SQLServer (full…"},"167":{"u":"/docs/adr/018-polyglot-persistence.html#rationale","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Rationale","x":"- Right store per access pattern, as a configuration decision. The engine becomes an attribute on a configuration class, not a rewrite. The same domain entity, application…"},"168":{"u":"/docs/adr/018-polyglot-persistence.html#trade-offs","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Trade-offs","x":"- No cross-engine JOINs, FKs, or transactions. This is the ADR-006 cost made sharper: across engines it is a hard limit, not a deployment choice. A query spanning engines (for…"},"169":{"u":"/docs/adr/018-polyglot-persistence.html#related","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Related","x":"ADR-006 (database-per-service: the Name axis this ADR's Engine axis is orthogonal to; they share DataSourceKey), ADR-002 (navigation populators bridge the relationships the…"},"170":{"u":"/docs/adr/018-polyglot-persistence.html#revision-2026-08-29-engine-substitution-for-a-single-engine-host","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Revision (2026-08-29): engine substitution for a single-engine host","x":"Decision item 4 says engines never collapse into each other, and that held while every host in this workspace configured SQL Server. A host that configures only SQLite (or only…"},"171":{"u":"/docs/adr/018-polyglot-persistence.html#revision-2026-10-01","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision changed; this pass corrects statements that overreached the code and refreshes stale citations. Decision items 5 and 6, the Trade-offs and Related now say that the…"},"172":{"u":"/docs/adr/019-rate-limiting.html","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records"},"173":{"u":"/docs/adr/019-rate-limiting.html#status","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-08-01 (the auth-ip per-IP anonymous-authentication limiter, which the shared auth controller applies to login and register by default, is recorded as the…"},"174":{"u":"/docs/adr/019-rate-limiting.html#context","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Context","x":"Every service exposes read and write endpoints to the public internet through the gateway (ADR-008). Abusive or runaway clients (scrapers, credential stuffing, retry storms, a…"},"175":{"u":"/docs/adr/019-rate-limiting.html#decision","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Decision","x":"Rate limiting is layered, and the always-on global limiter is authenticated-only. 1. A global limiter that only caps authenticated callers. AddCommonRateLimiting…"},"176":{"u":"/docs/adr/019-rate-limiting.html#rationale","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Rationale","x":"- Limit the traffic that is both attributable and expensive. An authenticated request is tied to a principal and usually drives the database; capping per-principal stops a single…"},"177":{"u":"/docs/adr/019-rate-limiting.html#trade-offs","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Trade-offs","x":"- The global limiter only protects the authenticated surface. The anonymous surface is covered endpoint by endpoint instead: the anonymous credential endpoints (login, register,…"},"178":{"u":"/docs/adr/019-rate-limiting.html#revision-2026-08-18","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"The layering above is unchanged: the global limiter is still authenticated-only, infrastructure and anonymous traffic are still exempt, auth-ip still covers login and register by…"},"179":{"u":"/docs/adr/019-rate-limiting.html#revision-2026-09-07","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The layering is unchanged: the global limiter is still authenticated-only, login and register still carry auth-ip, and infrastructure paths are still exempt. Four things below it…"},"180":{"u":"/docs/adr/019-rate-limiting.html#revision-2026-09-10","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Revision (2026-09-10)","x":"Both gateways now scope auth-tight the same way, and the trusted-caller exemption's client half ships in the framework. Two items, both landed. 1. Store's gateway splits the…"},"181":{"u":"/docs/adr/019-rate-limiting.html#revision-2026-10-01","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Two current-state statements in the Decision were corrected. The ADC Conference output-cache list now reads as a sample of a larger…"},"182":{"u":"/docs/adr/019-rate-limiting.html#related","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the JWKS/discovery traffic the limiter exempts, and the authenticated principal it keys on), ADR-008 (the gateway edge this protects), ADR-017 (request idempotency, the…"},"183":{"u":"/docs/adr/020-permission-based-authorization.html","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records"},"184":{"u":"/docs/adr/020-permission-based-authorization.html#status","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-25, amended 2026-07-10 and 2026-08-23). Revised 2026-09-07 (a fallback policy requiring an authenticated caller is on by default, so anonymity has to be…"},"185":{"u":"/docs/adr/020-permission-based-authorization.html#context","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Context","x":"The default answer in ASP.NET Core is pure role-based access control (RBAC): an endpoint declares [Authorize(Policy = \"RequireOrganizer\")] against a named policy that calls…"},"186":{"u":"/docs/adr/020-permission-based-authorization.html#decision","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Decision","x":"Authorize on capabilities resolved from roles: a permission layer over RBAC, and the framework's one authorization model. Nothing is pre-registered per role name. - A central…"},"187":{"u":"/docs/adr/020-permission-based-authorization.html#rationale","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Rationale","x":"- Capabilities decouple endpoints from roles. A route says what it does (conference:sessions:manage), and who may do it is a registry decision, so adding ContentEditor with a…"},"188":{"u":"/docs/adr/020-permission-based-authorization.html#trade-offs","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Trade-offs","x":"- It is still RBAC, not ABAC. The model resolves role to permission; it does not evaluate resource or attribute conditions. Per-resource ownership (\"a customer may read only…"},"189":{"u":"/docs/adr/020-permission-based-authorization.html#revision-2026-09-07","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Permission-based authorization is unchanged. What changed is the default for an endpoint that declares nothing (SEC-Common-16 / SEC-ADC-03). 1. An undecorated endpoint now…"},"190":{"u":"/docs/adr/020-permission-based-authorization.html#revision-2026-10-01","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed: the framework still declares no role names and still gates its own endpoints and navigation on capabilities. The role-vocabulary paragraph now…"},"191":{"u":"/docs/adr/020-permission-based-authorization.html#related","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the authenticated principal and claims this keys on, including the optional permission claim), ADR-008 (each extracted service authorizes independently, so the registry…"},"192":{"u":"/docs/adr/021-consumer-inbox-idempotency.html","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records"},"193":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#status","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-09; adoption reviewed 2026-07-15, inventory refreshed 2026-10-01). Revised 2026-08-18 (the inbox stays opt-in, but being off is no longer silent: a…"},"194":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#context","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Context","x":"ADR-003 makes integration-event delivery at-least-once: the outbox guarantees a published event is not lost, and the MassTransit broker redelivers on consumer failure.…"},"195":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#decision","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Decision","x":"Add an opt-in inbox that records each successfully-processed integration event by its MessageId and skips redeliveries. - Every event carries a MessageId. BaseDomainEvent stamps…"},"196":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#rationale","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Rationale","x":"- Dedup once, not in every handler. A single consume-edge check turns \"every handler author must remember to be idempotent against redelivery\" into a framework guarantee for the…"},"197":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#trade-offs","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Trade-offs","x":"- Not exactly-once. The crash-after-handler-before-inbox window reprocesses once, so handlers must stay idempotent for it; the inbox narrows the duplicate window, it does not…"},"198":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#revision-2026-09-07","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Inbox idempotency is unchanged. The names the consumer endpoints take are not (SEC-Common-53). MessageBus:EndpointPrefix left unset used to mean \"no prefix\"; it now defaults to…"},"199":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#related","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox and at-least-once delivery whose consumer side this deduplicates; handler idempotency is still required for the crash window), ADR-006 (the inbox lives in the…"},"200":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#revision-2026-08-18","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"The decision is unchanged: the inbox is still opt-in and NoOpInboxStore is still the default. What changed is that the default is now loud. 1. A broker-connected host with no…"},"201":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#revision-2026-08-26","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Revision (2026-08-26)","x":"Two changes, and the first one reverses this record's central choice. The inbox is no longer opt-in where redelivery is possible, and its row is no longer a separate write. 1.…"},"202":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#revision-2026-09-11","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"Dedup semantics are unchanged: same MessageId key, same TryBeginAsync to handlers to CompleteAsync path, same staged row, same unique index as the race guard. What changed is…"},"203":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#revision-2026-10-01","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision changed. The consumer inventory did: ADC Conference now consumes UserDeleted beside UserRegistered…"},"204":{"u":"/docs/adr/022-browser-session-cookie-auth.html","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records"},"205":{"u":"/docs/adr/022-browser-session-cookie-auth.html#status","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Status","x":"Accepted."},"206":{"u":"/docs/adr/022-browser-session-cookie-auth.html#context","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Context","x":"The apps are Blazor Web Apps: a server-rendered (SSR) prerender pass runs on the first request, then an interactive phase (Blazor Server or WebAssembly) takes over.…"},"207":{"u":"/docs/adr/022-browser-session-cookie-auth.html#decision","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Decision","x":"Carry the session in HttpOnly cookies and add an authentication scheme that reads them during SSR prerender. The mechanism ships in MMCA.Common.API (SessionCookies/) with a…"},"208":{"u":"/docs/adr/022-browser-session-cookie-auth.html#rationale","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Rationale","x":"- Fixes the fresh-GET prerender gap. Without a server-readable session, every deep-link or F5 to an [Authorize] page would redirect to /login despite a valid session; the cookie…"},"209":{"u":"/docs/adr/022-browser-session-cookie-auth.html#trade-offs","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Trade-offs","x":"- A non-validating auth scheme exists. SessionCookieAuthenticationHandler trusts a cookie it does not cryptographically verify. This is sound only because (a) the cookie is…"},"210":{"u":"/docs/adr/022-browser-session-cookie-auth.html#related","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the JWT/JWKS validation the API performs on every call, which is why the SSR handler can skip signature validation), ADR-008 (the gateway and topology the UI talks to),…"},"211":{"u":"/docs/adr/023-security-response-headers.html","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records"},"212":{"u":"/docs/adr/023-security-response-headers.html#status","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-02). Revised 2026-09-01 (the static default is now a complete hardened baseline: it ships script-src 'self' 'wasm-unsafe-eval' and style-src 'self'…"},"213":{"u":"/docs/adr/023-security-response-headers.html#context","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Context","x":"Every client-facing host (the YARP Gateway and the Blazor UI web host in each app) must stamp the same hardened HTTP response headers: X-Content-Type-Options, X-Frame-Options,…"},"214":{"u":"/docs/adr/023-security-response-headers.html#decision","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Decision","x":"Ship one security-headers middleware in MMCA.Common.Aspire (MMCA.Common.Aspire.Security), registered with AddCommonSecurityHeaders(configuration?, configure?) and inserted early…"},"215":{"u":"/docs/adr/023-security-response-headers.html#rationale","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Rationale","x":"- One hardened default, defined once. Centralizing the header set removes per-host drift and makes a new edge host secure by default rather than by remembering to copy headers. -…"},"216":{"u":"/docs/adr/023-security-response-headers.html#trade-offs","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Trade-offs","x":"- The baseline is complete, not maximal. Shipping a policy that works for a Blazor/MudBlazor host means the default carries style-src 'unsafe-inline' (MudBlazor injects styles at…"},"217":{"u":"/docs/adr/023-security-response-headers.html#revision-2026-09-07","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Two changes from the 2026-09-07 security review. 1. Credential-carrying paths get a stricter referrer and cache posture. SecurityHeadersSettings.CredentialPathPrefixes…"},"218":{"u":"/docs/adr/023-security-response-headers.html#revision-2026-09-10","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Revision (2026-09-10)","x":"Both UI origins are now pinned by a test, not just Store's. ADC has MMCA.ADC/Tests/Hosts/MMCA.ADC.UI.Web.Tests/SecurityHeadersTests.cs:17-18, a one-line subclass of the…"},"219":{"u":"/docs/adr/023-security-response-headers.html#revision-2026-09-19","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Revision (2026-09-19)","x":"The Blazor policy is wider than the Decision above recorded, and the UI host's forwarded-headers options clear their allow-lists on purpose. The provider is no longer the copy…"},"220":{"u":"/docs/adr/023-security-response-headers.html#revision-2026-10-01","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; one Trade-offs statement was corrected and the current homes of the forwarded-headers posture are recorded. 1. The registration-order trade-off…"},"221":{"u":"/docs/adr/023-security-response-headers.html#related","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Related","x":"ADR-019 (rate limiting, the other always-on edge protection living in the same Aspire layer), ADR-022 (browser session-cookie auth, the other browser-edge security control),…"},"222":{"u":"/docs/adr/024-push-notifications.html","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records"},"223":{"u":"/docs/adr/024-push-notifications.html#status","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27, amended 2026-07-15). Revised 2026-08-07 (transactional email recorded as an app-level concern outside the channel model; see Revision below). Revised…"},"224":{"u":"/docs/adr/024-push-notifications.html#context","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Context","x":"The framework needs to deliver user-facing notifications (an organizer broadcasting a schedule change, a per-user alert). Two delivery models each fail on their own. A pure…"},"225":{"u":"/docs/adr/024-push-notifications.html#decision","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Decision","x":"Deliver notifications over two channels from one application use case, with the transport and the recipient policy both behind abstractions. - A durable per-user inbox plus a…"},"226":{"u":"/docs/adr/024-push-notifications.html#rationale","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Rationale","x":"- Each channel covers the other's failure mode. The inbox guarantees eventual delivery to offline users; the push gives connected users immediacy. Persisting the inbox before…"},"227":{"u":"/docs/adr/024-push-notifications.html#trade-offs","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Fan-out write amplification. One UserNotification row is written per recipient, so a broadcast to a large audience is a large insert. This is fine for the current per-event /…"},"228":{"u":"/docs/adr/024-push-notifications.html#revision-2026-09-07","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Two bounds were added under the delivery model, from the 2026-09-07 security review. 1. A connection cap per user. NotificationHub refuses a connection once the caller already…"},"229":{"u":"/docs/adr/024-push-notifications.html#related","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox dual-dispatch path, which is distinct: that carries service-to-service integration events, this carries user-facing notifications), ADR-004 (the /hubs…"},"230":{"u":"/docs/adr/024-push-notifications.html#revision-2026-08-07","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"Records transactional email, a delivery path the channel model above never mentions. The decision is unchanged: this closes a documentation gap so the asymmetry reads as…"},"231":{"u":"/docs/adr/024-push-notifications.html#revision-2026-10-01","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The opt-in deduplication key is now scoped to the sender. SendPushNotificationHandler no longer stores or looks up the client-supplied DedupKey as-is: it derives the persisted…"},"232":{"u":"/docs/adr/025-startup-warmup-readiness.html","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records"},"233":{"u":"/docs/adr/025-startup-warmup-readiness.html#status","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27). Revised 2026-07-28: /health/ready now excludes optional-tagged checks as well as live-tagged ones (see Decision), and the absence of a warm-up timeout was…"},"234":{"u":"/docs/adr/025-startup-warmup-readiness.html#context","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Context","x":"On the Azure Container Apps Consumption plan a replica that has been idle is CPU-throttled, and a scale-from-zero or scaled-out replica starts cold. The first authenticated…"},"235":{"u":"/docs/adr/025-startup-warmup-readiness.html#decision","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Decision","x":"Ship a small warm-up subsystem in MMCA.Common.Aspire, wired into AddServiceDefaults() so every host gets it. - A readiness gate that starts closed. WarmupReadinessGate…"},"236":{"u":"/docs/adr/025-startup-warmup-readiness.html#rationale","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Rationale","x":"- Keep cold replicas out of rotation, briefly. Gating readiness on warm-up means the platform does not send a user request to a replica that is still doing its first handshakes,…"},"237":{"u":"/docs/adr/025-startup-warmup-readiness.html#trade-offs","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Trade-offs","x":"- A replica can enter rotation not fully warm. The gate is opened in a finally once the Task.WhenAll over every registered task returns, that is, once each task has completed,…"},"238":{"u":"/docs/adr/025-startup-warmup-readiness.html#revision-2026-10-01","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Citations were refreshed: the /health/ready predicate, the MapCachedHealthChecks helper and the uncached /alive mapping now live in…"},"239":{"u":"/docs/adr/025-startup-warmup-readiness.html#related","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the OIDC discovery document the built-in task pre-fetches, and the auth-side view of the same cold-start), ADR-009 (the Polly resilience pipeline that absorbs the lazy…"},"240":{"u":"/docs/adr/026-caching-strategy.html","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records"},"241":{"u":"/docs/adr/026-caching-strategy.html#status","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27, amended 2026-07-10, 2026-07-23, 2026-07-25, 2026-08-14). Amended by ADR-077 (2026-08-13): Tier 1's substrate gains a third, opt-in implementation…"},"242":{"u":"/docs/adr/026-caching-strategy.html#context","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Context","x":"The framework needs caching in two distinct places. Inside the application pipeline, query results are memoized and invalidated on mutation (the Caching decorators of ADR-014,…"},"243":{"u":"/docs/adr/026-caching-strategy.html#decision","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Decision","x":"Cache in two tiers, each with its own substrate. - One abstraction. ICacheService (MMCA.Common.Application/Interfaces/ICacheService.cs) exposes GetAsync / SetAsync / RemoveAsync…"},"244":{"u":"/docs/adr/026-caching-strategy.html#rationale","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Rationale","x":"- One substrate, swapped by environment. Keeping ICacheService as the only thing application code sees lets the deployment decide memory vs distributed. The auto-swap (presence…"},"245":{"u":"/docs/adr/026-caching-strategy.html#trade-offs","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Trade-offs","x":"- Memory mode is per-replica. In the in-process store each replica caches independently; a scaled-out deployment that did not wire Redis would see cross-replica staleness bounded…"},"246":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-09-07","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Four changes from the 2026-09-07 security review. The tiers themselves are unchanged. 1. Tier-1 keys are caller-scoped where the result is caller-scoped (SEC-Common-19 /…"},"247":{"u":"/docs/adr/026-caching-strategy.html#related","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Related","x":"ADR-014 (the Caching decorators and IQueryCacheable / ICacheInvalidating markers that consume this substrate), ADR-019 (output caching as the anonymous-traffic lever, and…"},"248":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-07-24","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-07-24)","x":"Three substrate corrections from a code review. 1. An optional key namespace (Cache:KeyPrefix). Services sharing one cache instance also share one keyspace, and nothing stopped…"},"249":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-07-25","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-07-25)","x":"An audit against the code. No behavior changed; the ADR text did. 1. The IncrementAsync entry above was wrong. It described a Redis INCR override. There is no such override, and…"},"250":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-07-28","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-07-28)","x":"Line anchors only, re-verified against the current source. No decision, no behavior, and no substantive prose changed. 1. Tier 2. Store Catalog's…"},"251":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-01","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-01)","x":"Line anchors only, re-verified against the current source. No decision, no behavior, and no substantive prose changed. 1. AddCaching. Now at…"},"252":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-07","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"Line anchors only, re-verified against the current source. No decision, no behavior, and no substantive prose changed. 1. AddCaching. Now at…"},"253":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-13","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-13)","x":"Tier 1 is amended by ADR-077, which is where the decision and its trade-offs are recorded. The three points that change the reading of this record: 1. A third substrate, opted…"},"254":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-14","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-14)","x":"One substrate correction plus a line-anchor re-verification. No decision and no behavior changed. 1. The 30-second default now has a named home, CacheOptions.DefaultDuration.…"},"255":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-18","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Every previous revision moved Tier 1. This one moves Tier 2, and it is the first change to the output-cache edge since ADR-040. Tier 2 as decided here is per-process by…"},"256":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-23","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"One correction of substance plus a line-anchor re-verification. No decision and no behavior changed. 1. The counter trade-off now names the contradiction a reader will hit.…"},"257":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-31","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-31)","x":"Line anchors only, re-verified against the current source. No decision, no behavior, and no substantive prose changed. 1. AddCaching. Now at…"},"258":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-09-01","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-09-01)","x":"One amendment of substance (an optional third tier is recorded) plus line anchors for the files the 2026-08-31 entry did not re-read. No behavior changed, and neither server tier…"},"259":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-09-03","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-09-03)","x":"Two substrate corrections plus a line-anchor and file-path re-verification. No decision changed; what changed is where the wiring lives and which substrate the applications…"},"260":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-09-25","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-09-25)","x":"One wiring convergence, one count correction, and a line-anchor re-verification of the service call sites and the substrate registrations. No decision changed, and the ADC-only…"},"261":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-10-01","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"One wiring correction, one recorded client-tier detail, and a line-anchor re-verification. No decision and no rationale changed. 1. The hybrid opt-in is a framework helper now,…"},"262":{"u":"/docs/adr/027-multi-locale-i18n.html","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records"},"263":{"u":"/docs/adr/027-multi-locale-i18n.html#status","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27, amended 2026-07-02, 2026-07-03, 2026-07-09, 2026-07-29, and 2026-09-15: the MudBlazor localization interceptor is replaced so no dependency assigns the…"},"264":{"u":"/docs/adr/027-multi-locale-i18n.html#context","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Context","x":"ADR-011 recorded single-locale (en-US) as a deliberate, revisitable non-goal and sketched what re-introducing i18n would entail. That revisit has now happened: the framework adds…"},"265":{"u":"/docs/adr/027-multi-locale-i18n.html#decision","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Decision","x":"1. Supported cultures are an explicit allowlist: en-US (default) + es. Adding a locale is adding a .es.resx sibling set and one allowlist entry, not new infrastructure. 2.…"},"266":{"u":"/docs/adr/027-multi-locale-i18n.html#rationale","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Rationale","x":"- Keying error localization on the existing Error.Code is the cheapest correct extension point. The codes are already stable and already cross the wire; localizing at the edge…"},"267":{"u":"/docs/adr/027-multi-locale-i18n.html#trade-offs","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Every view and every user-facing message is touched: a large, mostly mechanical sweep, accepted as the cost ADR-011 always named. - WASM Spanish formatting needs ICU…"},"268":{"u":"/docs/adr/027-multi-locale-i18n.html#revision-2026-10-01","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; four statements were corrected to match the code and several citations were refreshed. Decision 2: the composite-key example now matches the…"},"269":{"u":"/docs/adr/027-multi-locale-i18n.html#related","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Related","x":"ADR-011 (superseded), ADR-013 (the Error.Code this localizes on), ADR-015 (the i18n gates now live here: the MA0076 culture-less formatting build gate and the…"},"270":{"u":"/docs/adr/028-dark-theme-mode.html","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records"},"271":{"u":"/docs/adr/028-dark-theme-mode.html#status","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27; revised 2026-07-15; revised 2026-08-31)."},"272":{"u":"/docs/adr/028-dark-theme-mode.html#context","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Context","x":"MMCATheme (MMCA.Common.UI/Theme/MMCATheme.cs) has always defined a complete, brand-tuned PaletteDark alongside PaletteLight, but MudThemeProvider was hard-wired to light: no…"},"273":{"u":"/docs/adr/028-dark-theme-mode.html#decision","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Decision","x":"1. Bind the existing theme. The shared MainLayout renders a single component (MMCA.Common.UI/Layout/MainLayout.razor:14), which owns the four Mud providers plus the Day/Dark…"},"274":{"u":"/docs/adr/028-dark-theme-mode.html#rationale","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Rationale","x":"- Reusing the i18n cookie/profile machinery means one persistence model for both user preferences, instead of two subtly different ones. Theme and locale are the same shape of…"},"275":{"u":"/docs/adr/028-dark-theme-mode.html#trade-offs","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Trade-offs","x":"- The same FOUC hazard as locale is not yet closed for theme. The SSR data-theme/inline-script read is unimplemented (Decision 3), so the first paint can briefly flash the wrong…"},"276":{"u":"/docs/adr/028-dark-theme-mode.html#revision-2026-10-01","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Anchor refresh only: no decision or rationale changed. The snackbar live-region wrapper above the @code block moved the MmcaThemeProviders members, so the Theme parameter, the…"},"277":{"u":"/docs/adr/028-dark-theme-mode.html#related","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Related","x":"ADR-027 (shares the cookie source-of-truth and the User preference migration, and is the model for the theme no-flash SSR bootstrap that is not yet wired), ADR-022 (the SSR…"},"278":{"u":"/docs/adr/029-authentication-brute-force-protection.html","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records"},"279":{"u":"/docs/adr/029-authentication-brute-force-protection.html#status","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27). Updated 2026-07-02 (the check/increment/reset call sequence was hoisted into AuthenticationServiceBase ; the adoption note and the \"convention the consumer…"},"280":{"u":"/docs/adr/029-authentication-brute-force-protection.html#context","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Context","x":"ADR-019's global rate limiter is principal-keyed: it caps requests per authenticated principal, and anonymous traffic is exempt with one metered exception, the configured…"},"281":{"u":"/docs/adr/029-authentication-brute-force-protection.html#decision","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Decision","x":"Provide a framework ILoginProtectionService (MMCA.Common.Application.Auth) with a single implementation LoginProtectionService (MMCA.Common.Infrastructure.Auth), registered…"},"282":{"u":"/docs/adr/029-authentication-brute-force-protection.html#rationale","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Rationale","x":"- Complements ADR-019 rather than duplicating it. ADR-019 carries two limiter layers and this is the third on top of them: its global limiter caps authenticated throughput per…"},"283":{"u":"/docs/adr/029-authentication-brute-force-protection.html#trade-offs","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Trade-offs","x":"- Cache-scoped state weakens under scale-out without Redis. In memory mode the counters are per-replica and evaporate on restart, so a multi-replica deployment that did not wire…"},"284":{"u":"/docs/adr/029-authentication-brute-force-protection.html#revision-2026-09-07","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The lockout model is unchanged. Three things about the order and cost of the login path changed, all from the 2026-09-07 security review. 1. The account-state gate runs after the…"},"285":{"u":"/docs/adr/029-authentication-brute-force-protection.html#revision-2026-10-01","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The login lockout and registration throttle are unchanged. Two current-state statements were corrected. First, ResetPasswordHandlerBase is no longer the only framework call site…"},"286":{"u":"/docs/adr/029-authentication-brute-force-protection.html#alternatives-rejected","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Alternatives rejected","x":"- Making the failed-attempt and registration counters atomic. The increment in DistributedCacheService.IncrementAsync is a read-modify-write through IDistributedCache and is…"},"287":{"u":"/docs/adr/029-authentication-brute-force-protection.html#related","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Related","x":"ADR-019 (the layered limiter: a principal-keyed global cap that exempts this anonymous surface, its one metered anonymous exception being the real-time hub paths, plus the per-IP…"},"288":{"u":"/docs/adr/030-startup-sole-migrator.html","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records"},"289":{"u":"/docs/adr/030-startup-sole-migrator.html#status","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27)."},"290":{"u":"/docs/adr/030-startup-sole-migrator.html#context","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Context","x":"Under database-per-service (ADR-006), each service owns its own database and its own migrations project, so something must apply pending migrations on every deploy. The…"},"291":{"u":"/docs/adr/030-startup-sole-migrator.html#decision","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Decision","x":"In Azure Container Apps, every service host runs ApplicationSettingsDatabaseInitStrategy = Migrate in production and is the sole migrator of its own database: it applies its…"},"292":{"u":"/docs/adr/030-startup-sole-migrator.html#rationale","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Rationale","x":"- One migrator, one mechanism. The code that owns the schema applies the schema; there is no second tool to keep in lockstep and no ordering race between a deploy step and…"},"293":{"u":"/docs/adr/030-startup-sole-migrator.html#trade-offs","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Auto-migrate-in-production is what \"None\" exists to prevent. An unintended or destructive migration would ship itself on the next deploy. The apps accept this; the build-time…"},"294":{"u":"/docs/adr/030-startup-sole-migrator.html#related","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Related","x":"ADR-006 (database-per-service: why each service owns and migrates its own database), ADR-025 (readiness gating keeps traffic off a still-migrating replica), ADR-009 (RTO/RPO +…"},"295":{"u":"/docs/adr/030-startup-sole-migrator.html#revision-2026-08-07","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"The sole-migrator decision extends to seed data: the same startup owner that applies the schema also runs the module seeders, in the same call, on the same boot. The Decision…"},"296":{"u":"/docs/adr/030-startup-sole-migrator.html#revision-2026-10-01","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed: every service host still sets DatabaseInitStrategy = 'Migrate' in production and remains the sole migrator of its database. The Context now…"},"297":{"u":"/docs/adr/031-feature-flag-management.html","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records"},"298":{"u":"/docs/adr/031-feature-flag-management.html#status","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27). Revised 2026-08-18 (a targeting-context accessor is now registered, so the built-in Targeting and Percentage filters give consistent per-user bucketing…"},"299":{"u":"/docs/adr/031-feature-flag-management.html#context","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Context","x":"The apps need to decouple release from deploy: ship code dark, flip a kill switch, or roll a feature out to a percentage of users without a redeploy. A flag has to be enforceable…"},"300":{"u":"/docs/adr/031-feature-flag-management.html#decision","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Decision","x":"Standardize on Microsoft.FeatureManagement, configured from the \"FeatureManagement\" configuration section and registered once in AddAPI (services.AddFeatureManagement() +…"},"301":{"u":"/docs/adr/031-feature-flag-management.html#rationale","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Rationale","x":"- Release decoupled from deploy. A kill switch or a percentage rollout becomes a configuration change, not a code change: the central reason feature management exists. - Two…"},"302":{"u":"/docs/adr/031-feature-flag-management.html#trade-offs","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Trade-offs","x":"- The two enforcement points must agree. A flag gated on the controller but not the handler (or vice versa) is a half-protected feature; no fitness rule asserts both are wired,…"},"303":{"u":"/docs/adr/031-feature-flag-management.html#revision-2026-08-18","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Progressive rollout is now usable, because the targeting context exists. The Decision above listed the Percentage / TimeWindow / Targeting filters as \"available\", and the last…"},"304":{"u":"/docs/adr/031-feature-flag-management.html#related","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Related","x":"ADR-014 (the decorator pipeline whose outermost slot FeatureGate fills, and the ordering that puts it first, now with Authorization registered directly inside it so a disabled…"},"305":{"u":"/docs/adr/031-feature-flag-management.html#revision-2026-09-11","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"The expiry gap the Trade-offs recorded is closed. \"The framework provides no expiry or staleness check\" was true from this record's acceptance until now. What it cost was never…"},"306":{"u":"/docs/adr/031-feature-flag-management.html#revision-2026-10-01","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The disabled response is not-found, but not anonymous. The Decision and Rationale said both surfaces emit the HandleFailure not-found shape, so a disabled feature is…"},"307":{"u":"/docs/adr/032-password-hashing.html","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records"},"308":{"u":"/docs/adr/032-password-hashing.html#status","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Status","x":"Superseded by ADR-102 (2026-08-31). Originally Accepted (2026-06-29, adoption note revised 2026-07-06, registration note revised 2026-08-01, call-site hoist recorded 2026-08-23).…"},"309":{"u":"/docs/adr/032-password-hashing.html#context","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Context","x":"Identity stores a credential as a (salt, hash) pair, never plaintext. The framework needs one canonical hasher that every consuming Identity flow shares, so the key-derivation…"},"310":{"u":"/docs/adr/032-password-hashing.html#decision","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Decision","x":"Provide a single IPasswordHasher (MMCA.Common.Application.Interfaces.Infrastructure, IPasswordHasher.cs:6) with one implementation PasswordHasher…"},"311":{"u":"/docs/adr/032-password-hashing.html#rationale","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Rationale","x":"- One framework-owned primitive, not per-app crypto. Putting the algorithm, work factor, salt size, and comparison in a single shared type means a future hardening (raising…"},"312":{"u":"/docs/adr/032-password-hashing.html#trade-offs","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Trade-offs","x":"- The legacy branch is a permanent correctness dependency that looks deletable. Its load-bearing role is invisible from the method body alone, so it is the single most…"},"313":{"u":"/docs/adr/032-password-hashing.html#related","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (cross-service JWT / JWKS authentication: the hasher gates credential verification that issues the tokens that ADR-004 then validates across services), ADR-005…"},"314":{"u":"/docs/adr/032-password-hashing.html#revision-2026-08-23","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"The decision is unchanged: one framework-owned IPasswordHasher, PBKDF2-HMAC-SHA512 for new passwords, salt-length dispatch on verification. What changed is where the last two…"},"315":{"u":"/docs/adr/033-resource-ownership-authorization.html","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records"},"316":{"u":"/docs/adr/033-resource-ownership-authorization.html#status","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-02, revised 2026-07-25, 2026-08-01, 2026-08-31). Revised 2026-10-01 (the fail-closed owner gate and the per-record ownership check are now framework code in…"},"317":{"u":"/docs/adr/033-resource-ownership-authorization.html#context","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Context","x":"ADR-020 added a permission (capability) layer over RBAC: it answers \"what may this role do\", resolving a role to a permission so an endpoint can require a capability instead of a…"},"318":{"u":"/docs/adr/033-resource-ownership-authorization.html#decision","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Decision","x":"Provide a row/resource-level ownership axis in MMCA.Common.API (the Authorization folder), with two enforcement points keyed on the caller's owner claim (customerid by default)…"},"319":{"u":"/docs/adr/033-resource-ownership-authorization.html#rationale","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Rationale","x":"- Reject-one and filter-many are genuinely two mechanisms. A single-resource route has an id to compare, so a short action filter that 403s on a mismatch is the cheapest correct…"},"320":{"u":"/docs/adr/033-resource-ownership-authorization.html#trade-offs","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Opt-in per controller/handler. Neither point is automatic: a controller that forgets the [ServiceFilter] or omits the ownership spec from a query leaks across customers, the…"},"321":{"u":"/docs/adr/033-resource-ownership-authorization.html#related","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Related","x":"ADR-020 (the role/permission RBAC layer this complements, and whose explicit 020-permission-based-authorization.md:159-160 scope-out this fills), ADR-034 (the generic entity…"},"322":{"u":"/docs/adr/033-resource-ownership-authorization.html#revision-2026-07-25","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Revision (2026-07-25)","x":"An audit against the code. No behavior changed; the ADR text did. 1. The per-mutation check's failure shape was described as one branch, and it is two. ValidateOwnershipAsync was…"},"323":{"u":"/docs/adr/033-resource-ownership-authorization.html#revision-2026-08-01","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Revision (2026-08-01)","x":"Anchor-only correction. No behavior changed; OrdersController was refactored (a constructor parameter added, GetOwnershipSpecification() and the IsAdmin property extracted,…"},"324":{"u":"/docs/adr/033-resource-ownership-authorization.html#revision-2026-08-31","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Revision (2026-08-31)","x":"Behavior changed on both adopters since the 2026-08-01 pass, so this is not an anchor refresh. 1. The ambiguous null specification is now gated.…"},"325":{"u":"/docs/adr/033-resource-ownership-authorization.html#revision-2026-09-10","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Revision (2026-09-10)","x":"ADC adopts both enforcement points, so the \"Engagement Bookmarks only\" framing above is no longer the whole inventory. The Adoption text describes ADC through the action-filter…"},"326":{"u":"/docs/adr/033-resource-ownership-authorization.html#revision-2026-10-01","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The fail-closed gate this record named as a candidate for extraction (Revision 2026-09-10, item 3) is now framework code. MMCA.Common v1.216.0 (MMCA.Common/CHANGELOG.md:26) adds…"},"327":{"u":"/docs/adr/034-generic-entity-query-layer.html","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records"},"328":{"u":"/docs/adr/034-generic-entity-query-layer.html#status","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-30). Amended (2026-07-23): the filter strategy registry now also covers long/long? via LongFilterStrategy. Amended (2026-07-25): the keyed by-id fast path is…"},"329":{"u":"/docs/adr/034-generic-entity-query-layer.html#context","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Context","x":"Every module exposes many entities, and most of them need the same read and write surface: list, page, look up for a dropdown, fetch by id, create, delete. Hand writing a…"},"330":{"u":"/docs/adr/034-generic-entity-query-layer.html#decision","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Decision","x":"Give every entity a generic REST resource surface and a bounded dynamic query contract, supplied by two controller bases over a shared query pipeline. 1. Generic read controller.…"},"331":{"u":"/docs/adr/034-generic-entity-query-layer.html#rationale","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Rationale","x":"- Write the resource surface once, inherit it everywhere. The verbs, routes, filter contract, pagination, and header are defined once on the two bases; a concrete controller…"},"332":{"u":"/docs/adr/034-generic-entity-query-layer.html#trade-offs","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- The wire contract tracks the entity model. Filterable, sortable, and projectable surface is the entity's property set. A model change is an API change unless mediated by the…"},"333":{"u":"/docs/adr/034-generic-entity-query-layer.html#revision-2026-09-07","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The query layer's shape is unchanged. What a caller is allowed to name is not, from the 2026-09-07 security review. 1. Client keys resolve against the DTO contract (SEC-Common-25…"},"334":{"u":"/docs/adr/034-generic-entity-query-layer.html#related","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-001 (manual DTO mapping: the generic controllers project through IEntityDTOMapper), ADR-002 (navigation populators: the unsupported-include path), ADR-013 (Result pattern at…"},"335":{"u":"/docs/adr/034-generic-entity-query-layer.html#revision-2026-07-24","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Revision (2026-07-24)","x":"Filter and pagination corrections from a code review. The contract shape is unchanged; these close cases where the engine widened a result set instead of narrowing it, or…"},"336":{"u":"/docs/adr/034-generic-entity-query-layer.html#revision-2026-07-25","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Revision (2026-07-25)","x":"Citation maintenance from an ADR audit. No decision or behavior changed; the source anchors above were rebased to their current declaration and call-site lines. 1. The fast-path…"},"337":{"u":"/docs/adr/034-generic-entity-query-layer.html#revision-2026-10-01","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; this records clarifications and refreshed citations from an ADR audit. 1. MaxPageSize is clamped to the pipeline ceiling. The resolved setting…"},"338":{"u":"/docs/adr/035-optimistic-concurrency.html","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records"},"339":{"u":"/docs/adr/035-optimistic-concurrency.html#status","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-02). Revised 2026-09-07 (a conditional write always stamps the aggregate root, so If-Match is enforced even when only child rows changed, and a…"},"340":{"u":"/docs/adr/035-optimistic-concurrency.html#context","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Context","x":"Every mutable aggregate in the framework is edited through a load-modify-save handler: the update use case fetches the tracked entity, applies the request, and calls…"},"341":{"u":"/docs/adr/035-optimistic-concurrency.html#decision","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Decision","x":"Give every auditable entity a database-managed RowVersion concurrency token, round-trip it through the client as an HTTP entity tag, and stamp the client's last-seen value as…"},"342":{"u":"/docs/adr/035-optimistic-concurrency.html#rationale","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Rationale","x":"- Database-managed token over a hand-maintained version field. A SQL Server rowversion auto-increments on the server on every write; no domain code sets or reads it (the setter…"},"343":{"u":"/docs/adr/035-optimistic-concurrency.html#trade-offs","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Trade-offs","x":"- The rewrite keys on the conflict outcome, not on its cause. The filter turns any 409 result the action returns under an If-Match request into a 412…"},"344":{"u":"/docs/adr/035-optimistic-concurrency.html#revision-2026-09-07","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Two additions from the 2026-09-07 security review. 1. If-Match is enforced even when the root row is otherwise unchanged (SEC-Common-77). IWriteRepository.TouchConcurrencyToken…"},"345":{"u":"/docs/adr/035-optimistic-concurrency.html#revision-2026-10-01","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision changed; three current-state statements were corrected and stale citations refreshed. (1) Only tables mapped from an auditable entity carry RowVersion; the…"},"346":{"u":"/docs/adr/035-optimistic-concurrency.html#related","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Related","x":"ADR-017 (HTTP request idempotency, which dedups retries of the same request, the mirror-image concern to two distinct edits racing here, and whose declared-intent gate sits at…"},"347":{"u":"/docs/adr/036-external-oauth-login.html","d":"ADR-036: External OAuth Login (Federated Google/GitHub/Apple) with Local-JWT Exchange","k":"Architecture Decision Records"},"348":{"u":"/docs/adr/036-external-oauth-login.html#status","d":"ADR-036: External OAuth Login (Federated Google/GitHub/Apple) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-02, migration attribution corrected 2026-07-06, native-callback redirect branch added 2026-07-17 per ADR-043, email-verified account-takeover guard before…"},"349":{"u":"/docs/adr/036-external-oauth-login.html#context","d":"ADR-036: External OAuth Login (Federated Google/GitHub/Apple) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Context","x":"The framework's Identity story so far is entirely first-party: a user registers with an email and password, the credentials are hashed (ADR-032), and Identity mints its own RS256…"},"350":{"u":"/docs/adr/036-external-oauth-login.html#decision","d":"ADR-036: External OAuth Login (Federated Google/GitHub/Apple) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Decision","x":"Add an opt-in external-login path that federates Google, GitHub and Apple sign-in at the edge and immediately exchanges the external identity for the app's own local JWT pair,…"},"351":{"u":"/docs/adr/036-external-oauth-login.html#rationale","d":"ADR-036: External OAuth Login (Federated Google/GitHub/Apple) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Rationale","x":"- Terminate federation at the edge, keep one internal identity. Exchanging the external principal for a local JWT the moment the callback returns means every downstream concern…"},"352":{"u":"/docs/adr/036-external-oauth-login.html#trade-offs","d":"ADR-036: External OAuth Login (Federated Google/GitHub/Apple) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Trade-offs","x":"- Opt-in per app, and easy to half-wire. The flow needs four cooperating pieces (scheme registration, the controller subclass, the service override, and the OAuthUIBaseUrl…"},"353":{"u":"/docs/adr/036-external-oauth-login.html#revision-2026-09-07","d":"ADR-036: External OAuth Login (Federated Google/GitHub/Apple) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Four changes from the 2026-09-07 security review. 1. The empty credential an external account carries is explicitly non-authenticating (SEC-Common-01). This record already said…"},"354":{"u":"/docs/adr/036-external-oauth-login.html#revision-2026-10-01","d":"ADR-036: External OAuth Login (Federated Google/GitHub/Apple) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Anchor refresh only: no decision or rationale changed. The Decision and Trade-offs citations into ADC's AuthenticationService.cs are re-anchored to the current file…"},"355":{"u":"/docs/adr/036-external-oauth-login.html#related","d":"ADR-036: External OAuth Login (Federated Google/GitHub/Apple) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the RS256/JWKS token this flow exchanges the external identity for, and validates everywhere after), ADR-022 (the browser cookies that carry the resulting session),…"},"356":{"u":"/docs/adr/037-field-level-encryption-at-rest.html","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records"},"357":{"u":"/docs/adr/037-field-level-encryption-at-rest.html#status","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-06; revised 2026-07-24, 2026-07-25, 2026-08-15, 2026-08-18). Revised 2026-08-18: the versioned-envelope converter is no longer unpublished, it is included in…"},"358":{"u":"/docs/adr/037-field-level-encryption-at-rest.html#context","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Context","x":"Transparent database encryption (TDE) protects the data files as a whole, but it decrypts transparently for anyone who can query the database, so a leaked backup restored on a…"},"359":{"u":"/docs/adr/037-field-level-encryption-at-rest.html#decision","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Decision","x":"Provide a single framework-owned EF Core value converter that transparently encrypts string columns at rest with authenticated encryption, applied per property in an entity…"},"360":{"u":"/docs/adr/037-field-level-encryption-at-rest.html#rationale","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Rationale","x":"- Authenticated, not merely confidential. AES-GCM binds a 128-bit tag to the ciphertext (EncryptedStringConverter.cs:81, :201), so a tampered or truncated value fails to decrypt…"},"361":{"u":"/docs/adr/037-field-level-encryption-at-rest.html#trade-offs","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Latent today, proven by tests rather than production. The plumbing is complete and unit-tested, but no entity configuration wires it, so the encrypt/decrypt round-trip, the…"},"362":{"u":"/docs/adr/037-field-level-encryption-at-rest.html#related","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Related","x":"ADR-005 (soft-delete vs erasure: the other sensitive-data control, which names this converter as the mechanism for erasure fields that must stay retrievable,…"},"363":{"u":"/docs/adr/037-field-level-encryption-at-rest.html#revision-2026-07-24","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Revision (2026-07-24)","x":"Documented a constraint the converter always had but did not state: the ciphertext is non-deterministic. Every write uses a fresh random nonce, which is the correct property for…"},"364":{"u":"/docs/adr/037-field-level-encryption-at-rest.html#revision-2026-07-25","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Revision (2026-07-25)","x":"Documentation-only correction, no behavior change. Item 1 of the Decision still illustrated the converter with builder.Property(e = e.Email), contradicting the 2026-07-24…"},"365":{"u":"/docs/adr/037-field-level-encryption-at-rest.html#revision-2026-08-15","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Revision (2026-08-15)","x":"Behavior change, not a documentation correction. The stored layout is now a versioned envelope: Base64 of [key version (1)] [nonce (12)] [ciphertext (N)] [tag (16)] rather than…"},"366":{"u":"/docs/adr/037-field-level-encryption-at-rest.html#revision-2026-10-01","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Anchor-only refresh; no decision, rationale or trade-off changed. The three citations of ADR-005 in Context, Trade-offs and Related now point at the record's canonical home and…"},"367":{"u":"/docs/adr/038-supply-chain-provenance.html","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records"},"368":{"u":"/docs/adr/038-supply-chain-provenance.html#status","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-06; revised 2026-07-21). Revised 2026-09-07 (MMCA.Common pins every action by commit SHA and every global tool by version, restores in locked mode, fails the…"},"369":{"u":"/docs/adr/038-supply-chain-provenance.html#context","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Context","x":"MMCA.Common is a published framework: on every v tag (release.yml:3-5) it packs its NuGet packages and pushes them to both GitHub Packages (release.yml:117-118) and nuget.org…"},"370":{"u":"/docs/adr/038-supply-chain-provenance.html#decision","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Decision","x":"Treat supply-chain integrity as a set of build-gating controls, the same invariant-over-discipline posture ADR-015 applies to architecture rules. Four controls, each a hard gate:…"},"371":{"u":"/docs/adr/038-supply-chain-provenance.html#rationale","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Rationale","x":"- Provenance is a gate, not a document. A hard-failing SBOM step means the bill of materials cannot silently go missing on a release: the artifact is produced or the release…"},"372":{"u":"/docs/adr/038-supply-chain-provenance.html#trade-offs","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The SBOM is generated and archived, not yet signed or attested (superseded 2026-09-22; see the Revision below). The gate proves a bill of materials exists for each release…"},"373":{"u":"/docs/adr/038-supply-chain-provenance.html#revision-2026-09-07","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The provenance chain gained the controls that make it reproducible under an attacker, from the 2026-09-07 security review. 1. Actions are pinned by commit SHA, not by a mutable…"},"374":{"u":"/docs/adr/038-supply-chain-provenance.html#revision-2026-09-22-attestations-and-one-implementation-of-each-gate","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Revision (2026-09-22): attestations, and one implementation of each gate","x":"Two of the trade-offs above have moved. First, every .nupkg a release packs now carries a signed SLSA build-provenance attestation: release.yml calls…"},"375":{"u":"/docs/adr/038-supply-chain-provenance.html#revision-2026-10-01","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; the current-state sections now describe the gates where they live today. Context names both registries a tag publishes to: GitHub Packages…"},"376":{"u":"/docs/adr/038-supply-chain-provenance.html#related","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Related","x":"ADR-016 (lockstep versioning + the MassTransit-v8 license pin; this record extends dependency governance from versioning and licensing into supply-chain provenance and…"},"377":{"u":"/docs/adr/039-live-channel-push.html","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records"},"378":{"u":"/docs/adr/039-live-channel-push.html#status","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-09). Revised 2026-09-07 (channel subscription can be gated by an app-supplied authorizer, connections are capped per user, the backplane channel is namespaced…"},"379":{"u":"/docs/adr/039-live-channel-push.html#context","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Context","x":"Conference-day features (live polls, session Q&A, live result counters) need sub-second fan-out of small events to whoever is looking at a page right now. The existing…"},"380":{"u":"/docs/adr/039-live-channel-push.html#decision","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Decision","x":"One realtime transport, two publisher boundaries: - NotificationHub stays the single hub and gains its first client-invokable methods: JoinChannel / LeaveChannel map the calling…"},"381":{"u":"/docs/adr/039-live-channel-push.html#rationale","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Rationale","x":"- One WebSocket per client keeps connection management, token refresh, reconnect, and backplane behavior in one place; channel membership is a property of the existing…"},"382":{"u":"/docs/adr/039-live-channel-push.html#trade-offs","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Ephemeral means lossy: a client that connects after an event was published never sees it. Features must treat channel events as cache-invalidation hints over fetchable state,…"},"383":{"u":"/docs/adr/039-live-channel-push.html#revision-2026-07-24","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Revision (2026-07-24)","x":"Two corrections from a code review; the best-effort, per-session-ordered decision is unchanged. 1. Broadcasts are enqueued after commit, not during the command. CastVoteHandler…"},"384":{"u":"/docs/adr/039-live-channel-push.html#revision-2026-09-07","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The channel model is unchanged: a client subscribes to a channel key and the server pushes to it. What a signed-in client is allowed to subscribe to, and how much it may hold…"},"385":{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records"},"386":{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#status","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-10). Amended (2026-07-10): explicit query-string variance parity with the built-in default policy (the initial release accidentally dropped it, collapsing every…"},"387":{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#context","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Context","x":"The framework's read-scaling design leans on ASP.NET Core output caching: anonymous-readable endpoints ([AllowAnonymous] GETs like event/session/speaker catalogs) carry named…"},"388":{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#decision","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Decision","x":"MMCA.Common.API ships PublicEndpointOutputCachePolicy, an IOutputCachePolicy that mirrors the built-in default policy with one deliberate difference: it does not disable cache…"},"389":{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#rationale","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Rationale","x":"- The response payload, not the request's auth state, is what determines cacheability. For a user-independent payload, Authorization is noise; refusing to cache on it turns the…"},"390":{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#trade-offs","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Trade-offs","x":"- Consumers must audit which named policies move to AddPublicEndpointPolicy. Policies on permission-gated endpoints (e.g. an organizer dashboard) must NOT move; if such an…"},"391":{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#revision-2026-09-10","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Revision (2026-09-10)","x":"Store now uses the cross-service eviction path too, and the first case is an Application-layer handler rather than a controller. The trade-off above says to check which process…"},"392":{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#revision-2026-09-25","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Revision (2026-09-25)","x":"Store's ProductsCache TTL is 60 seconds, because its payload moves on the clock. A product payload carries each variant's effective price, and a discount window opening or…"},"393":{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#revision-2026-10-01","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Citations in Context, Decision and Trade-offs are re-anchored to the current hosts: ADC Conference Program.cs (the adminBypassRoles array at…"},"394":{"u":"/docs/adr/041-observability-and-telemetry.html","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records"},"395":{"u":"/docs/adr/041-observability-and-telemetry.html#status","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-10). Amended (2026-07-23) to document the Telemetry:DisableHttpClientMetrics and Telemetry:DisableRuntimeMetrics cost knobs and to correct the…"},"396":{"u":"/docs/adr/041-observability-and-telemetry.html#context","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Context","x":"The framework is a modular monolith whose modules extract into standalone services (ADR-008), so the same telemetry has to make sense whether a request stays in one process or…"},"397":{"u":"/docs/adr/041-observability-and-telemetry.html#decision","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Decision","x":"Standardize telemetry in the shared Aspire service defaults, add framework-specific instrumentation for the CQRS and outbox paths, and expose cost knobs with fail-safe defaults.…"},"398":{"u":"/docs/adr/041-observability-and-telemetry.html#rationale","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Rationale","x":"- Instrument only where auto-instrumentation is blind. The CQRS RED histograms and the outbox dead-letter counter cover the two framework-owned hot paths; everything else (HTTP,…"},"399":{"u":"/docs/adr/041-observability-and-telemetry.html#trade-offs","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Trade-offs","x":"- Custom instrumentation carries a maintenance cost. The Aspire package has no reference to Application or Infrastructure by design, so the meter and activity-source names are…"},"400":{"u":"/docs/adr/041-observability-and-telemetry.html#revision-2026-08-18","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Two meters and one hop. Two new failure counters, each on its own meter. cache.eviction.failed, tagged cachetag, on MMCA.Common.OutputCache…"},"401":{"u":"/docs/adr/041-observability-and-telemetry.html#amended-2026-08-31","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Amended (2026-08-31)","x":"The log side of this record. Until now it named only the OpenTelemetry logging call inside ConfigureOpenTelemetry (Extensions.cs:132); what a host actually WRITES its application…"},"402":{"u":"/docs/adr/041-observability-and-telemetry.html#amended-2026-09-03-probe-telemetry","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Amended (2026-09-03): probe telemetry","x":"Health probes were the trace bill. Container Apps liveness and readiness probes, the gateway's downstream aggregate probes, YARP active health checks and the availability web…"},"403":{"u":"/docs/adr/041-observability-and-telemetry.html#revision-2026-09-07","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Telemetry gained the properties that make it usable as evidence, from the 2026-09-07 security review. 1. The daily ingestion cap is alerted on (SEC-ADC-47 / SEC-Store-55). A…"},"404":{"u":"/docs/adr/041-observability-and-telemetry.html#revision-2026-09-11","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"An eighth meter, a second trace source, a second poll loop kept off the bill, and one meter this pipeline does not carry. The durable internal-command queue exports through this…"},"405":{"u":"/docs/adr/041-observability-and-telemetry.html#alternatives-rejected","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Alternatives rejected","x":"- An ActivitySource span per command and query in the CQRS pipeline, tagged by module. Evaluated 2026-09-16 against a modular-monolith template that ships one, and declined. The…"},"406":{"u":"/docs/adr/041-observability-and-telemetry.html#related","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox whose dead-letter counter and poll-span filtering this defines), ADR-014 (the CQRS decorator pipeline that emits the RED histograms as a byproduct of its…"},"407":{"u":"/docs/adr/041-observability-and-telemetry.html#amended-2026-09-11-the-polly-meter","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Amended (2026-09-11): the Polly meter","x":"A ninth meter, one cost knob, and one log level. Polly's meter is subscribed. AddServiceDefaults puts the standard resilience handler on every HttpClient and every gRPC typed…"},"408":{"u":"/docs/adr/041-observability-and-telemetry.html#revision-2026-09-19-the-ai-meter-is-subscribed","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Revision (2026-09-19): the AI meter is subscribed","x":"A tenth meter, and a fourth trace source. MMCA.Common.AI is in the chain now. The subscription block ends with .AddMeter(AiTelemetryName)…"},"409":{"u":"/docs/adr/041-observability-and-telemetry.html#revision-2026-10-01","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"A third metrics cost knob, and the citations follow the telemetry code into its own file. ASP.NET Core metrics are no longer unconditional. ConfigureMetrics now starts by calling…"},"410":{"u":"/docs/adr/042-device-capability-abstraction.html","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records"},"411":{"u":"/docs/adr/042-device-capability-abstraction.html#status","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-10, amended 2026-07-17, 2026-07-23, 2026-08-14, 2026-08-29 and 2026-09-03). The 2026-08-29 amendment adds the UseMmcaMauiErrorHandling last-chance handlers and…"},"412":{"u":"/docs/adr/042-device-capability-abstraction.html#context","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Context","x":"The consumer apps ship the same Blazor component set through three heads: MAUI Blazor Hybrid (Android/iOS/MacCatalyst/Windows), Blazor Server SSR, and WebAssembly. Native device…"},"413":{"u":"/docs/adr/042-device-capability-abstraction.html#decision","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Decision","x":"Add a per-capability contract layer to MMCA.Common.UI and a dedicated package, MMCA.Common.UI.Maui, carrying the native implementations. - One small interface per capability, no…"},"414":{"u":"/docs/adr/042-device-capability-abstraction.html#rationale","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Rationale","x":"- A god IDeviceCapabilities interface would force every head to implement everything and turn each new capability into a breaking change; per-capability contracts are open/closed…"},"415":{"u":"/docs/adr/042-device-capability-abstraction.html#trade-offs","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Trade-offs","x":"- A second build runner raises release surface: ubuntu and windows must both succeed for a whole release. Accepted; the publish-maui job is gated by the same tag and SBOM…"},"416":{"u":"/docs/adr/042-device-capability-abstraction.html#revision-2026-10-01","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. The Decision bullet on safe defaults no longer points at a Fallbacks/ folder, which does not exist: each null fallback sits beside its contract…"},"417":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records"},"418":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#status","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-15). Revised 2026-07-28 (the Android https App Links leg is recorded as shipped, the outstanding Android item is restated as the served certificate fingerprint,…"},"419":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#context","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Context","x":"Three mobile flows all need a URL to leave the web world and land inside the MAUI app: 1. Shared links and QR codes. The share sheet and QR codes carry ordinary https web URLs.…"},"420":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#decision","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Decision","x":"- Custom-scheme returnUrl allowlist in the framework. CompleteAsync consults OAuth:AllowedReturnUrlSchemes (a config array, default empty). When the challenge's stashed returnUrl…"},"421":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#rationale","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Rationale","x":"- Reusing the single-use-code exchange keeps the token-never-in-URL invariant identical across web and native; the only new surface is WHERE the code lands. - A scheme allowlist…"},"422":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#trade-offs","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Trade-offs","x":"- The app-facing hostname is baked into store binaries (intent filters, entitlements). The apps currently ride the Azure Container Apps default domain, which changes if the…"},"423":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-07-28","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-07-28)","x":"Correction pass from an ADR audit. No decision or behavior changed; the Status section had the Android leg backwards and the Decision section attributed the token exchange to the…"},"424":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-08-01","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-08-01)","x":"Status pass from an ADR audit. No decision and no behavior changed; the one item the previous revision left open is closed, and the anchor that revision itself introduced had…"},"425":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-08-07","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"Anchor and precision pass from an ADR audit. No decision and no behavior changed. 1. The two Program.cs anchors moved one line. MMCA.ADC commit 886fa189 (PR 100, merged…"},"426":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-08-31","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-08-31)","x":"Anchor and count pass from an ADR audit. No decision and no behavior changed. 1. The ADC Program.cs association block moved again. MMCA.ADC commit 6323a7b9 (PR 155) shifted it…"},"427":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-09-07","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The callback routing is unchanged. The boundary where a platform-supplied URI enters the app is now validated (SEC-Common-88 / SEC-ADC-65). DeepLinkDispatcher.Publish…"},"428":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-09-11","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"Anchor and correction pass from an ADR audit. No decision and no behavior changed. One citation pointed at a file that does not exist. 1. There is no DeepLinkRouteGuard in ADC.…"},"429":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-09-25","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-09-25)","x":"Anchor pass over the MMCA.ADC citations. No decision and no behavior changed. 1. The ADC association block moved up six lines. In…"},"430":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-10-01","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Anchor pass from an ADR audit. No decision and no rationale changed. In the Decision's client-flow bullet the completion page now calls the service at…"},"431":{"u":"/docs/adr/044-native-push-delivery.html","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records"},"432":{"u":"/docs/adr/044-native-push-delivery.html#status","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-11). Amends ADR-024. The framework pipeline is implemented and inert by default; each consumer switches it on by provisioning a notification hub with platform…"},"433":{"u":"/docs/adr/044-native-push-delivery.html#context","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records","t":"Context","x":"ADR-024 established two notification channels: a durable per-user UserNotification inbox (the source of truth) and a transient SignalR push behind IPushNotificationSender. Both…"},"434":{"u":"/docs/adr/044-native-push-delivery.html#decision","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records","t":"Decision","x":"- Azure Notification Hubs as the delivery fan-out. One hub abstracts both platforms behind one API, holds the platform credentials outside our code, and its installation model…"},"435":{"u":"/docs/adr/044-native-push-delivery.html#consequences","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records","t":"Consequences","x":"- Sends fan out per 20-user chunk and per platform: an audience of N users costs ceil(N/20) 2 hub calls. Acceptable at conference scale; a template-based send can consolidate…"},"436":{"u":"/docs/adr/044-native-push-delivery.html#revision-2026-10-01","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The client half of the pipeline is no longer Null-only. MMCA.Common.UI.Maui ships credentialed token providers behind the opt-in AddMauiPushDeviceTokenProvider()…"},"437":{"u":"/docs/adr/045-managed-file-storage-and-avatars.html","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records"},"438":{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#status","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-11). Records the BR-116 amendment (ADC): avatar photos are IN scope, powered by two new framework extension points. The framework legs are implemented; each…"},"439":{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#context","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Context","x":"The MAUI capability program (ADR-042) brought MediaPicker/camera within reach, and ADC amended BR-116 to include user avatar photos. That needs binary blob storage (the databases…"},"440":{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#decision","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Decision","x":"- IFileStorageService (Application): upload-by-blob-name returning the public URI, plus idempotent delete. Default is an unconfigured Null implementation whose uploads fail with…"},"441":{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#consequences","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Consequences","x":"- The avatars container is public-read by design: avatar URLs render in tags on anonymous-visible surfaces without SAS plumbing. The random blob suffix prevents enumeration; the…"},"442":{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#revision-2026-09-07","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The 2 MB compressed upload cap bounds the bytes on the wire; it does not bound what those bytes decode to. A highly compressible image declares its dimensions in a header that…"},"443":{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#revision-2026-09-12","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Revision (2026-09-12)","x":"This record scoped managed uploads to avatars and said so: \"no other managed uploads exist\". That statement is superseded by ADR-123, which publishes speaker session materials…"},"444":{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#revision-2026-10-01","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Avatar blob deletion no longer happens as an inline storage call. Every path that retires an avatar schedules a DeleteAvatarBlobInternalCommand…"},"445":{"u":"/docs/adr/046-http-api-versioning.html","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records"},"446":{"u":"/docs/adr/046-http-api-versioning.html#status","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-15). Revised 2026-08-01 (anonymity is granted by each per-service subclass, not by ServiceInfoControllerBase; corrected the ADR-034 cross-reference, which puts…"},"447":{"u":"/docs/adr/046-http-api-versioning.html#context","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Context","x":"The framework's REST surface is served by controllers hosted in extracted service processes behind a YARP gateway. As those services evolve, a response shape has to be able to…"},"448":{"u":"/docs/adr/046-http-api-versioning.html#decision","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Decision","x":"Standardize one header-based API-versioning setup in MMCA.Common.API, adopt it in every service host through a single registration call, and keep it exercised by a shared fitness…"},"449":{"u":"/docs/adr/046-http-api-versioning.html#rationale","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Rationale","x":"- Header selection keeps URLs stable. Routing stays version-free, so gateway route maps, client URL builders, and OpenAPI paths do not fork per version; a caller opts into a…"},"450":{"u":"/docs/adr/046-http-api-versioning.html#trade-offs","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Trade-offs","x":"- The class-level version attributes are not inherited. Each per-service subclass must repeat the [ApiVersion(...)] and routing attributes (the same inheritance caveat ADR-036…"},"451":{"u":"/docs/adr/046-http-api-versioning.html#revision-2026-09-22-the-consumer-contract-is-a-committed-artifact","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Revision (2026-09-22): the consumer contract is a committed artifact","x":"The trade-off above described the OpenAPI document as a dev/CI artifact that nothing outside a running host could diff. MMCA.ADC now writes it to disk at build time.…"},"452":{"u":"/docs/adr/046-http-api-versioning.html#revision-2026-09-25-both-consumers-commit-and-gate-their-openapi-documents","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Revision (2026-09-25): both consumers commit and gate their OpenAPI documents","x":"MMCA.Store now carries the same gate, so the committed contract is a property of both consumers rather than of MMCA.ADC alone. Each repo's Directory.Build.props references…"},"453":{"u":"/docs/adr/046-http-api-versioning.html#revision-2026-10-01","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Re-anchored the citations in Decision, Trade-offs and Related, which had moved: AddCommonApiVersioning and its options…"},"454":{"u":"/docs/adr/046-http-api-versioning.html#revision-2026-10-01-v12170-the-host-registers-the-openapi-document","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Revision (2026-10-01, v1.217.0): the host registers the OpenAPI document","x":"What changed. AddCommonOpenApi used to register the documents itself through the versioning builder (AddApiVersioning().AddOpenApi(), one document per discovered API version),…"},"455":{"u":"/docs/adr/046-http-api-versioning.html#related","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Related","x":"ADR-010 (integration-event schema versioning: the asynchronous, SchemaVersion-carried, consumer-resolved axis this deliberately contrasts with; HTTP versioning here is…"},"456":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records"},"457":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#status","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-15). Revised 2026-08-07 (validator hoisted into a shared generic, the 30-second constant moved, the two apps revoke at different speeds). Revised 2026-08-23:…"},"458":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#context","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Context","x":"Soft-delete is the framework's default deletion model (ADR-005): AuditableBaseEntity.Delete() sets IsDeleted = true and EF global query filters hide the row, but the record…"},"459":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#decision","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Decision","x":"Add a shared-pipeline middleware, SoftDeletedUserMiddleware (Source/Presentation/MMCA.Common.API/Middleware/SoftDeletedUserMiddleware.cs:31, BR-133), that rejects an…"},"460":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#rationale","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Rationale","x":"- Bounds the stateless-JWT revocation gap cheaply. Stateless JWT (ADR-004) has no built-in revocation, so a deactivated account would otherwise stay usable for the full remaining…"},"461":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#trade-offs","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Trade-offs","x":"- Revocation is bounded, not immediate. A soft-deleted user whose status is cached as not-deleted keeps passing until that cache entry expires (up to 30 seconds), unless the…"},"462":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#related","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Related","x":"ADR-005 (soft-delete is the deletion model whose still-authenticated tokens this middleware revokes; deleting a user is a soft-delete, not a row removal), ADR-004 (the stateless…"},"463":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#revision-2026-08-07","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"Re-verified against current source. The decision is unchanged, but three things it described have moved: the validator implementation, the home of the 30-second constant, and the…"},"464":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#revision-2026-08-23","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"Re-verified against current source. The decision, the cache design, the fail-open policy and the per-app asymmetry are all unchanged; what moved is where the middleware's…"},"465":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#revision-2026-09-03-mmcacommon-11850","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Revision (2026-09-03, MMCA.Common 1.185.0)","x":"Re-verified against current source. The middleware, the cache design, the 30-second window and the fail-open policy are all unchanged. What changed is who writes the deleted…"},"466":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#revision-2026-10-01","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Re-verified against current source (MMCA.Common v1.216.0). The decision, the middleware, the 30-second window, the fail-open policy and the shared marker write are unchanged. One…"},"467":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records"},"468":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#status","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-15). Revised 2026-07-21 (corrected the empty-placeholder-folder inventory and the Directory.Build.props and ADC User source citations). Revised 2026-07-28…"},"469":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#context","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Context","x":"Every entity needs an identity type. The framework's base entity is generic over that type: BaseEntity constrains it to notnull and exposes a single required init Id…"},"470":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#decision","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Decision","x":"Model every identifier as a primitive named through a global-using alias, declared per module, not as a wrapper struct. - Identity is a primitive behind an alias. Each module…"},"471":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#rationale","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Rationale","x":"- Readable signatures at zero runtime cost. GetRepository () reads as intent while the CLR sees a plain int. There is no allocation, boxing, or wrapper indirection per…"},"472":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#trade-offs","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Trade-offs","x":"- No compile-time protection against swapping same-typed identifiers. An alias is a type synonym, not a distinct type. Because most aliases resolve to int, the compiler will not…"},"473":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#related","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Related","x":"ADR-001 (the per-entity DTO mappers are parameterized by this identifier type, IEntityDTOMapper ), ADR-034 (the generic entity controllers and query contract ride on the same…"},"474":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#revision-2026-08-18","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"No decision, no behavior and no citation in this record changed. What changed is the standing of the deferral it records. The last Trade-offs entry above (\"Revisiting the trade…"},"475":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#revision-2026-08-23","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"No decision and no rationale changed. Two counts did, both because Conference gained an alias. The workspace census below is superseded by the Revision (2026-09-11), and…"},"476":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#revision-2026-09-11","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"No decision and no rationale changed. The workspace alias count did, because Store Catalog gained the Reviews aggregate's two identifiers. The census in this revision is itself…"},"477":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#revision-2026-09-19","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Revision (2026-09-19)","x":"No decision and no rationale changed. Three facts stated above did, two of them counts and one of them a claim about what exists. Conference's alias file declares nineteen…"},"478":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#revision-2026-10-01","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision and no rationale changed. One Trade-offs statement and three Decision citations did. The Trade-offs entry on identifier swapping still treated SpeakerIdentifierType…"},"479":{"u":"/docs/adr/049-library-configureawait-policy.html","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records"},"480":{"u":"/docs/adr/049-library-configureawait-policy.html#status","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-20; measurements re-anchored 2026-08-07, 2026-08-14, 2026-08-18, 2026-08-23, 2026-08-31, 2026-09-01, 2026-09-03, 2026-09-11 and 2026-09-19). Revised 2026-09-19:…"},"481":{"u":"/docs/adr/049-library-configureawait-policy.html#context","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Context","x":"MMCA.Common ships as NuGet packages consumed by host applications, not as an application itself. Library code that awaits without ConfigureAwait(false) captures the caller's…"},"482":{"u":"/docs/adr/049-library-configureawait-policy.html#decision","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Decision","x":"Packaged non-UI framework code awaits with ConfigureAwait(false); UI component packages and application code do not. - Enforcement is a build gate, not a convention. The…"},"483":{"u":"/docs/adr/049-library-configureawait-policy.html#rationale","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Rationale","x":"- Correctness for the one consumer that already has a context. The MAUI head consumes Infrastructure/Application/API packages through DI; a sync-over-async call anywhere in that…"},"484":{"u":"/docs/adr/049-library-configureawait-policy.html#trade-offs","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Visual noise in framework source. Every await in Source/ (except UI packages) carries .ConfigureAwait(false) (324 sites at adoption; 1,115 gated sites as of the 2026-10-01…"},"485":{"u":"/docs/adr/049-library-configureawait-policy.html#related","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Related","x":"ADR-042 (the MAUI package whose synchronization context motivates the policy), ADR-027 (the same \"machine-boundary hygiene as a build gate\" posture applied to culture-explicit…"},"486":{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-08-07","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"An audit against the code. The policy did not change; three statements about it did. 1. The exemption covers three packages, not the two the Decision named. The glob is…"},"487":{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-08-14","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-08-14)","x":"A re-measurement only. The policy, the gate and the exemption are unchanged; the counts the document quotes were a week old and had moved by roughly 9%. 1. Framework site counts,…"},"488":{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-08-18","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"A re-measurement only, in the same terms as the 2026-08-14 pass. The policy, the gate and the exemption are unchanged; two of the three counted figures moved. 1. Framework site…"},"489":{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-08-23","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"A re-measurement only, in the same terms as the 2026-08-18 pass. The policy, the gate and the exemption are unchanged; both counted figures moved. 1. Framework site counts,…"},"490":{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-08-31","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-08-31)","x":"A re-measurement plus a correction to two file anchors. The policy, the gate and the exemption are unchanged. Every counted figure moved, two of the surrounding narratives did…"},"491":{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-09-01","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-09-01)","x":"A re-measurement only, in the same terms as the 2026-08-31 pass. The policy, the gate and the exemption are unchanged, the .editorconfig anchors that shifted last pass are still…"},"492":{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-09-03","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-09-03)","x":"A re-measurement plus one anchor correction, in the same terms as the 2026-09-01 pass. The policy, the gate and the exemption are unchanged, the .editorconfig anchors are still…"},"493":{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-09-11","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"A re-measurement plus one anchor correction, in the same terms as the 2026-09-03 pass. The policy, the gate and the exemption are unchanged and the .editorconfig anchors are…"},"494":{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-09-19","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-09-19)","x":"A re-measurement in the same terms as the 2026-09-11 pass, covering the two counted figures that move (framework sites and the consumer-scale upper bound) and the…"},"495":{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-10-01","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"A count and anchor refresh only. No decision or rationale changed: the policy, the gate (MMCA.Common/.editorconfig:833) and the UI exemption (:836) stand as written. The…"},"496":{"u":"/docs/adr/050-jwt-refresh-token-rotation.html","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records"},"497":{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#status","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Status","x":"Superseded by ADR-097 (2026-09-01). Originally Accepted (2026-07-21, storage model recorded as superseded 2026-08-26). The body below is retained as the historical record (its…"},"498":{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#context","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Context","x":"Identity issues two credentials on every successful sign-in: a short-lived, stateless JWT access token that every service validates by signature and expiry (ADR-004), and a…"},"499":{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#decision","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Decision","x":"Mint a stateless JWT access token plus a single, server-stored refresh token that rotates on every use, with a token mismatch triggering revocation. - Access token is stateless;…"},"500":{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#rationale","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Rationale","x":"- Short access token plus refresh keeps the hot path stateless. Every service validates the access token with no store lookup (ADR-004); the short exp bounds the revocation gap,…"},"501":{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#trade-offs","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Trade-offs","x":"- One refresh token per user means one live session. A new login overwrites the single stored token, so signing in on a second device invalidates the first device's refresh…"},"502":{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#revision-2026-10-01","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Three statements were corrected against current source. The removed UpdateRefreshToken and RevokeRefreshToken no longer appear in either Domain…"},"503":{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#related","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the stateless RS256/JWKS access token this refresh flow reissues, and the algorithm pinning GetPrincipalFromExpiredToken relies on), ADR-032 (the password hashing that…"},"504":{"u":"/docs/adr/051-client-auth-token-lifecycle.html","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records"},"505":{"u":"/docs/adr/051-client-auth-token-lifecycle.html#status","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-23). Revised 2026-08-14 (SetTokensAsync writes the refresh token and the access token under one shared guard, so a failed refresh-token write also drops both…"},"506":{"u":"/docs/adr/051-client-auth-token-lifecycle.html#context","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Context","x":"ADR-022 and ADR-050 describe the two server halves of authentication: the Blazor host's HttpOnly session cookie that survives SSR prerender (ADR-022), and the Identity service's…"},"507":{"u":"/docs/adr/051-client-auth-token-lifecycle.html#decision","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Decision","x":"Model the client token lifecycle as three small abstractions (ITokenStorageService, the freshness-checked persistence every caller consumes; ISecureTokenStore, raw persistence…"},"508":{"u":"/docs/adr/051-client-auth-token-lifecycle.html#rationale","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Rationale","x":"- One application surface, three storage stories. Pages, services, and the HTTP pipeline talk to ITokenStorageService and AuthenticationStateProvider only; the head-specific…"},"509":{"u":"/docs/adr/051-client-auth-token-lifecycle.html#trade-offs","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Trade-offs","x":"- The browser heads depend on the same-origin UI host. SameOriginProxyTokenRefresher only works where the UI host serves the /auth/session/ endpoints; a browser head deployed…"},"510":{"u":"/docs/adr/051-client-auth-token-lifecycle.html#related","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Related","x":"ADR-022 (the Blazor host's HttpOnly session cookie and the /auth/session/ endpoints the browser refresher proxies through), ADR-050 (the single rotating refresh token with reuse…"},"511":{"u":"/docs/adr/051-client-auth-token-lifecycle.html#revision-2026-08-07","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"The MAUI half of ITokenStorageService is no longer app-local. The original Decision left the SecureStorage-backed implementation in each app because it depends on the MAUI…"},"512":{"u":"/docs/adr/051-client-auth-token-lifecycle.html#revision-2026-08-14","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Revision (2026-08-14)","x":"SetTokensAsync closed a gap the original hoist left open. Point 3 above previously described the method as writing the refresh token first and dropping both tokens only when the…"},"513":{"u":"/docs/adr/051-client-auth-token-lifecycle.html#revision-2026-08-31","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Revision (2026-08-31)","x":"MAUI storage is two classes, and the freshness check the browser heads always had is now on every head. 1. A raw store beneath the storage service. ISecureTokenStore is the third…"},"514":{"u":"/docs/adr/051-client-auth-token-lifecycle.html#revision-2026-10-01","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Citations in Decision and Trade-offs are re-anchored to the current source (DirectApiTokenRefresher, JsFetchSessionCookieSync,…"},"515":{"u":"/docs/adr/052-background-job-execution.html","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records"},"516":{"u":"/docs/adr/052-background-job-execution.html#status","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Status","x":"Superseded by ADR-121 (2026-09-11). The expensive half of this decision (Wait full mode, in-queue dedup, a 409 refusal, the session-scoring queue and drain) moved to durable…"},"517":{"u":"/docs/adr/052-background-job-execution.html#context","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Context","x":"Some requests trigger work that cannot run inside the request: an AI scoring pass over an event's sessions takes minutes and issues one paid API call per session, and a…"},"518":{"u":"/docs/adr/052-background-job-execution.html#decision","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Decision","x":"In-process background work runs as a bounded queue plus a single-reader hosted drain. Nothing starts an untracked Task from a request. - A bounded Channel per job kind,…"},"519":{"u":"/docs/adr/052-background-job-execution.html#rationale","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Rationale","x":"- The host lifetime is the point. A BackgroundService is the only in-process shape the host can cancel and await. Everything else in the decision follows from wanting that. - The…"},"520":{"u":"/docs/adr/052-background-job-execution.html#trade-offs","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Trade-offs","x":"- In-process only. The queue does not survive a restart and does not span replicas. Accepted because every current job is either ephemeral (a lost broadcast is a missed UI…"},"521":{"u":"/docs/adr/052-background-job-execution.html#related","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox, for work that must be durable and cross-process, and the post-commit deferral this relies on), ADR-008 (the broker, for cross-service work), ADR-014 (the…"},"522":{"u":"/docs/adr/052-background-job-execution.html#revision-2026-08-23","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"The decision is unchanged: post-commit work is still enqueued only once the write is durable. What changed is the record of how. This ADR stated the domain-event handler as the…"},"523":{"u":"/docs/adr/052-background-job-execution.html#revision-2026-08-31","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Revision (2026-08-31)","x":"The decision and the safety property are unchanged: post-commit work is still enqueued only once the write is durable. What changed is who keeps the ordering for shape 2. Three…"},"524":{"u":"/docs/adr/052-background-job-execution.html#revision-2026-09-19","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Revision (2026-09-19)","x":"The decision and the safety property are unchanged: post-commit work is still enqueued only once the write is durable. What changed is where the save sits in the one remaining…"},"525":{"u":"/docs/adr/053-dual-registry-package-publishing.html","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records"},"526":{"u":"/docs/adr/053-dual-registry-package-publishing.html#status","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-25). Amended (2026-07-25) to put the pre-decision Context statements in the past tense, to record the MMCA. ID prefix reservation as then-pending, to scope the…"},"527":{"u":"/docs/adr/053-dual-registry-package-publishing.html#context","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Context","x":"The MMCA.Common. packages have shipped to GitHub Packages since the first release (the package list and its count are generated and CI-gated in MMCA.Common/FACTS.md:19-43, so…"},"528":{"u":"/docs/adr/053-dual-registry-package-publishing.html#decision","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Decision","x":"Every release publishes to both registries, from the same tag, in the same workflow run. - release.yml keeps its existing dotnet nuget push to…"},"529":{"u":"/docs/adr/053-dual-registry-package-publishing.html#rationale","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Rationale","x":"- The install line has to be true. Documentation that cannot be followed is worse than no documentation, because the reader concludes the project is broken rather than that the…"},"530":{"u":"/docs/adr/053-dual-registry-package-publishing.html#trade-offs","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Trade-offs","x":"- A published version can never be withdrawn. nuget.org allows unlisting, not deletion. A bad release is now permanent public history, which raises the stakes on the release…"},"531":{"u":"/docs/adr/053-dual-registry-package-publishing.html#revision-2026-10-01","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision changed; the record is re-pinned to release.yml as of MMCA.Common v1.216.0 and two statements are corrected. Re-pinned: the GitHub Packages pushes (release.yml:118,…"},"532":{"u":"/docs/adr/053-dual-registry-package-publishing.html#related","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Related","x":"ADR-016 (lockstep versioning: every package ships at one version, so both registries receive the same set of ids per release, enumerated in MMCA.Common/FACTS.md:19-43), ADR-038…"},"533":{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records"},"534":{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#status","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-25). Amended (2026-07-28): Store's reconciliation sweep now derives from PeriodicBackgroundService, so the shared-loop and adoption paragraphs are rewritten and…"},"535":{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#context","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Context","x":"Checkout spans a boundary no transaction covers. CheckOutHandler commits the order insert, the cart transition and the atomic conditional stock decrements in one local…"},"536":{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#decision","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Decision","x":"Multi-step workflows are choreographed sagas: each step raises a domain event, and the follow-up or compensating action lives in its own handler. A periodic reconciliation sweep…"},"537":{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#rationale","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Rationale","x":"- No two-phase commit is available, and none is wanted. Transactions are per data source and best-effort sequential (ADR-006), and an external payment provider cannot enlist in a…"},"538":{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#trade-offs","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Trade-offs","x":"- Inconsistency is bounded, not eliminated. Between the cancellation commit and the compensation commit, stock is held against a cancelled order. Between a dropped webhook and…"},"539":{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#revision-2026-09-11-the-unpaid-order-deadline-moves-onto-the-order-and-the-sweep-becomes-its-backstop","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Revision (2026-09-11): the unpaid-order deadline moves onto the order, and the sweep becomes its backstop","x":"Expiring an unpaid order is no longer something a scan notices. Every order now arms its own deadline, and the sweep's expiry pass stays exactly where it was, underneath.…"},"540":{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#revision-2026-10-01","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The idempotency guarantee is now one transaction rather than one SaveChanges. OrderCancelledSagaHandler runs the whole restoration as a single ExecuteInTransactionAsync delegate…"},"541":{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#related","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox delivery and retry this leans on for compensation redelivery; this record says what the redelivered handler must do), ADR-006 (which accepts \"no…"},"542":{"u":"/docs/adr/055-repository-and-specification-contract.html","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records"},"543":{"u":"/docs/adr/055-repository-and-specification-contract.html#status","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-25). Revised 2026-08-01 (qualified the \"referenced nowhere\" claim about IEntityReader / IEntityQuerier: an ADC doc comment now names IEntityQuerier, though no…"},"544":{"u":"/docs/adr/055-repository-and-specification-contract.html#context","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Context","x":"Every read an application handler performs has to come from somewhere, and the shape of that contract decides whether the module can still be lifted into its own service later…"},"545":{"u":"/docs/adr/055-repository-and-specification-contract.html#decision","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Decision","x":"Data access is repository plus specification: interface-segregated read interfaces for the operations, expression-tree specifications for the predicates, and a build-failing…"},"546":{"u":"/docs/adr/055-repository-and-specification-contract.html#rationale","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Rationale","x":"- A narrow interface is the enforcement, not a style preference. A handler that asks for IEntityReader cannot reach TableNoTracking, because the member is not on the interface.…"},"547":{"u":"/docs/adr/055-repository-and-specification-contract.html#trade-offs","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- The ISP split is guidance, not yet a wired dependency. (Superseded by the Revision (2026-08-21) below: the split has real dependents shipped in both MMCA.ADC and MMCA.Store,…"},"548":{"u":"/docs/adr/055-repository-and-specification-contract.html#related","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-007 and ADR-008 (the extraction promise the queryable ban exists to protect), ADR-015 (the fitness-function machinery that runs this rule and its per-repo maps), ADR-014 (the…"},"549":{"u":"/docs/adr/055-repository-and-specification-contract.html#revision-2026-08-18","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Five changes, four of them widening the contract and one of them fixing a correctness defect. The decision this record states is unchanged: data access is still repository plus…"},"550":{"u":"/docs/adr/055-repository-and-specification-contract.html#revision-2026-08-21","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Revision (2026-08-21)","x":"Nothing in the contract changed; its consumers did. This revision records the first real adoption of the two surfaces this record had honestly flagged as unconsumed: the narrow…"},"551":{"u":"/docs/adr/055-repository-and-specification-contract.html#revision-2026-08-31","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Revision (2026-08-31)","x":"Again nothing in the decision changed. Two things did: the querier grew a second time, and the reference application joined the two production apps as a consumer of the narrow…"},"552":{"u":"/docs/adr/055-repository-and-specification-contract.html#revision-2026-10-01","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; the consumers grew again and the anchors moved. MMCA.ADC now declares IEntityReader / IEntityQuerier at forty read-only sites across…"},"553":{"u":"/docs/adr/056-blazor-render-mode-strategy.html","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records"},"554":{"u":"/docs/adr/056-blazor-render-mode-strategy.html#status","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-28). Revised 2026-08-14: re-anchored the host, base-class and AppHost citations to their current lines; scoped the \"only @rendermode attributes\" enumeration to…"},"555":{"u":"/docs/adr/056-blazor-render-mode-strategy.html#context","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Context","x":"Both web applications are Blazor Web Apps: a static server-rendered (SSR) prerender pass produces the first HTML, then an interactive runtime takes over, either a Blazor Server…"},"556":{"u":"/docs/adr/056-blazor-render-mode-strategy.html#decision","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Decision","x":"Run one render mode for the entire routable component tree, chosen at the application root, default InteractiveAuto, with prerendering left on and the resulting double fetch…"},"557":{"u":"/docs/adr/056-blazor-render-mode-strategy.html#rationale","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Rationale","x":"- InteractiveAuto gets both halves without asking page authors to choose. The first visit gets the Server circuit's immediate interactivity while the WASM bundle downloads in the…"},"558":{"u":"/docs/adr/056-blazor-render-mode-strategy.html#trade-offs","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Trade-offs","x":"- Everything shared has to run in both runtimes. The WASM-compatibility layer rule (MMCA.Common.LayerEnforcement.targets:102-115) forbids the shared UI package from touching…"},"559":{"u":"/docs/adr/056-blazor-render-mode-strategy.html#revision-2026-10-01","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. The prerender-skip guard bullet now lists four stated reasons rather than three: ADCHome skips its prerender fetch because an un-timed call to a…"},"560":{"u":"/docs/adr/056-blazor-render-mode-strategy.html#related","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Related","x":"ADR-022 (reads the HttpOnly session cookie during the SSR prerender pass this decision keeps enabled), ADR-027 (flows one culture through the SSR to Server to WASM sequence this…"},"561":{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records"},"562":{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#status","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-28). Revised 2026-08-01: the diff now fails closed in both repos (the true is gone and MMCA.Store's build-and-test checkout sets fetch-depth: 0), so the…"},"563":{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#context","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Context","x":"ADR-030 decides who applies a migration: every service host runs DatabaseInitStrategy = Migrate and self-applies its pending EF Core migrations at startup as the sole migrator,…"},"564":{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#decision","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Decision","x":"Schema changes follow expand/contract, and a CI step enforces the contract half. - Expand now, contract later, as a written rule. Adding nullable columns, new tables and new…"},"565":{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#rationale","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Rationale","x":"- Rollback is one-way for schema, so the check belongs where the drop is still cheap. The only moment a destructive migration can be reconsidered for free is the PR that adds it;…"},"566":{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#trade-offs","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- Three operations, not a model of compatibility. AlterColumn narrowing a type or flipping a column to NOT NULL, DropForeignKey, DropPrimaryKey, DropSchema, RenameColumn and a…"},"567":{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#revision-2026-10-01","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Both deploy.yml files grew, so every workflow citation is re-anchored to the current lines: the startup-migrate note…"},"568":{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#related","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-030 (decides that each service self-applies its migrations at startup, which is precisely why a rolled-back revision meets the new schema; this ADR constrains what those…"},"569":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records"},"570":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#status","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-28; revised 2026-08-14, 2026-08-18, 2026-08-23, and 2026-09-03). Revised 2026-09-22 (the layer rules also ship as compile-time MSBuild targets inside…"},"571":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#context","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Context","x":"ADR-015 turned the architecture invariants into build-gating tests, and drew its own boundary explicitly: the fitness suite asserts \"structure / registration, not runtime…"},"572":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#decision","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Decision","x":"Ship the runtime conformance suites in the MMCA.Common.Testing package as abstract behavioral bases that each consuming host subclasses, and run every one of them against a host…"},"573":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#rationale","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Rationale","x":"- Runtime conformance is the half ADR-015 excluded. Structural rules answer \"is the code shaped correctly\"; these suites answer \"does the composed host behave correctly\". A host…"},"574":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#trade-offs","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Trade-offs","x":"- Opt-in per host, exactly like ADR-015. The framework ships the suites; a host gets the gate only once someone writes the subclass. That is the same audit-the-inventory caveat,…"},"575":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#revision-2026-09-10","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Revision (2026-09-10)","x":"The security-headers suite reaches both UI web hosts, so the adoption inventory above is no longer Gateway-only for it. ADC added MMCA.ADC/Tests/Hosts/MMCA.ADC.UI.Web.Tests,…"},"576":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#revision-2026-09-22-the-layer-rules-also-ship-as-compile-time-targets","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Revision (2026-09-22): the layer rules also ship as compile-time targets","x":"The runtime suites above judge compiled assemblies, which places the first failure in the architecture test tier. MMCA.Common.Shared now also packs…"},"577":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#revision-2026-09-25-committed-openapi-documents-and-page-test-bases-in-testingui","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Revision (2026-09-25): committed OpenAPI documents, and page-test bases in Testing.UI","x":"The live-document guard now sits beside a committed document in both consumers. MMCA.ADC and MMCA.Store each write Source/Services/ /openapi/ .json at build time, commit it, and…"},"578":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#revision-2026-10-01","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; the seven bases still ship in MMCA.Common.Testing and are subclassed as inventoried above. Three statements are corrected. The gateway probe…"},"579":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#related","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (the structural / registration fitness layer this complements; its stated non-goal, \"not runtime behavior\", is exactly this ADR's scope, and the two tiers ship as two…"},"580":{"u":"/docs/adr/059-module-contract-and-composition.html","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records"},"581":{"u":"/docs/adr/059-module-contract-and-composition.html#status","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-28; revised 2026-08-14)."},"582":{"u":"/docs/adr/059-module-contract-and-composition.html#context","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Context","x":"The framework's headline claim is that an application is built as a modular monolith and later extracted into services without rewriting business logic. ADR-008 states the…"},"583":{"u":"/docs/adr/059-module-contract-and-composition.html#decision","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Decision","x":"Make IModule the single composition contract, discover implementations by reflection, register them in topological dependency order, and represent a disabled module by stub…"},"584":{"u":"/docs/adr/059-module-contract-and-composition.html#rationale","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Rationale","x":"- Reflection discovery keeps hosts out of the module registration business. A host names the assemblies and gets whatever IModule types they contain: no per-module registration…"},"585":{"u":"/docs/adr/059-module-contract-and-composition.html#trade-offs","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Trade-offs","x":"- The composition root has to name every module assembly. Discovery scans exactly the list it is handed (ModuleLoader.cs:59-65), so adding a module to a host is a third edit…"},"586":{"u":"/docs/adr/059-module-contract-and-composition.html#revision-2026-10-01","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The decision is unchanged; four statements are corrected against the code and the citations are re-anchored. A GetTypes() failure is no longer a skip: a…"},"587":{"u":"/docs/adr/059-module-contract-and-composition.html#related","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Related","x":"ADR-008 (the extraction topology that consumes this model: \"a service is the monolith with one module enabled\" is a statement about ModuleLoader plus the Disabled stubs, cited…"},"588":{"u":"/docs/adr/060-performance-regression-gate.html","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records"},"589":{"u":"/docs/adr/060-performance-regression-gate.html#status","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-28). Revised 2026-08-01 (corrected the count in Trade-offs: the single ratio floor names two of the eight benchmarks, so six, not seven, are gated on…"},"590":{"u":"/docs/adr/060-performance-regression-gate.html#context","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Context","x":"Rubric section 12 asks for hot-path efficiency that is measured, not assumed (Website/docs-src/governance/ArchitectureEvaluationCriteria.md:386). MMCA.Common has a…"},"591":{"u":"/docs/adr/060-performance-regression-gate.html#decision","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Decision","x":"Measure the hot-path suite on every code PR and verify the results against a committed baseline that carries two rule kinds: absolute allocation ceilings where the measurement is…"},"592":{"u":"/docs/adr/060-performance-regression-gate.html#rationale","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Rationale","x":"- A ratio is a property of the code; an absolute nanosecond count is a property of the runner. Both benchmarks in a floor run in the same process, on the same machine, in the…"},"593":{"u":"/docs/adr/060-performance-regression-gate.html#trade-offs","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The Short job cannot see small latency regressions. Three warmup and three iterations (ci.yml:386) give wide confidence intervals: enough for a 1000x floor and for counting…"},"594":{"u":"/docs/adr/060-performance-regression-gate.html#revision-2026-10-01","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. The consumer load-freshness jobs no longer carry the recency query and the break-glass check inline: both MMCA.ADC and MMCA.Store now call the…"},"595":{"u":"/docs/adr/060-performance-regression-gate.html#related","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (structural fitness functions, which explicitly stop at structure and registration; this is their runtime-cost counterpart), ADR-038 (the other build-gating control set,…"},"596":{"u":"/docs/adr/061-runtime-secret-management.html","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records"},"597":{"u":"/docs/adr/061-runtime-secret-management.html#status","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-01; vault-backed configuration source recorded and citations re-anchored 2026-08-23; gateway synthetic-traffic secret recorded and citations re-anchored…"},"598":{"u":"/docs/adr/061-runtime-secret-management.html#context","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Context","x":"A Container App can hold a credential two ways: as a literal value in the app's own secrets collection, or as a reference to a Key Vault secret that the platform resolves at…"},"599":{"u":"/docs/adr/061-runtime-secret-management.html#decision","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Decision","x":"Every production secret lives in Azure Key Vault and reaches the app as a keyVaultUrl secret reference resolved by a user-assigned managed identity; the same identity also lets a…"},"600":{"u":"/docs/adr/061-runtime-secret-management.html#rationale","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Rationale","x":"- A reference has one home; a literal has as many homes as it has consumers. Three vault secrets in each repo are referenced by more than one app: Redis by all four ADC services…"},"601":{"u":"/docs/adr/061-runtime-secret-management.html#trade-offs","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Trade-offs","x":"- One identity means vault-wide read for every app that carries it. A Key Vault Secrets User grant is scoped to the vault, so any app running as the shared identity can read…"},"602":{"u":"/docs/adr/061-runtime-secret-management.html#revision-2026-09-07","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Two changes from the 2026-09-07 security review. 1. Per-service Service Bus SAS rules (SEC-ADC-26 / SEC-Store-38). One namespace-wide rule shared by every service means any one…"},"603":{"u":"/docs/adr/061-runtime-secret-management.html#revision-2026-09-10","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Revision (2026-09-10)","x":"ADC's Gateway now reads the vault like every other deployable, closing the one adoption difference above. MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/Program.cs:66 calls…"},"604":{"u":"/docs/adr/061-runtime-secret-management.html#revision-2026-10-01","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Every main.bicep, deploy.yml, Program.cs, sample and CI citation in the sections above is re-anchored, and the secret counts (nineteen in ADC,…"},"605":{"u":"/docs/adr/061-runtime-secret-management.html#related","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Related","x":"ADR-037 (037-field-level-encryption-at-rest.md:108-110 directs a consumer to keep the field-encryption key in Key Vault but decides no delivery mechanism, and nothing wires that…"},"606":{"u":"/docs/adr/062-slo-alerting-as-code.html","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records"},"607":{"u":"/docs/adr/062-slo-alerting-as-code.html#status","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-01). Revised 2026-08-18: Store's two operational extras (the outbox-dead-letter scheduled query rule and the outside-in Gateway availability web test with its…"},"608":{"u":"/docs/adr/062-slo-alerting-as-code.html#context","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Context","x":"ADR-041 standardized what the fleet emits: RED histograms off the CQRS pipeline, an outbox dead-letter counter, correlation ids, exporters, and the cost knobs that keep ingestion…"},"609":{"u":"/docs/adr/062-slo-alerting-as-code.html#decision","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Decision","x":"Declare each consumer's SLO alerts as data in its Bicep template, materialize them as Log Analytics scheduled query rules, and make the alert-to-runbook pairing a build gate…"},"610":{"u":"/docs/adr/062-slo-alerting-as-code.html#rationale","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Rationale","x":"- Alerts as data, not as portal state. One array is reviewable in a PR, diffable across environments, and re-deployable; the rules, the workbook, and the notification channel are…"},"611":{"u":"/docs/adr/062-slo-alerting-as-code.html#trade-offs","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- It is a text gate over IaC, not a check against deployed state. The base matches literal anchors and regexes in the template and headings in markdown. It proves the two files…"},"612":{"u":"/docs/adr/062-slo-alerting-as-code.html#revision-2026-09-07","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The alert-to-runbook model is unchanged. Store's rule set grew by three, in one block from the 2026-09-07 security review (MMCA.Store/infra/main.bicep:458-459). 1. A security…"},"613":{"u":"/docs/adr/062-slo-alerting-as-code.html#revision-2026-10-01","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Both templates grew by 19 lines above the alert block, so every main.bicep citation in the current-state sections is re-anchored: sloAlertSpecs…"},"614":{"u":"/docs/adr/062-slo-alerting-as-code.html#related","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-041 (the telemetry this alerts on top of: it defines emission, instrumentation and cost knobs and stops before thresholds, severities and runbooks), ADR-009 (recovery…"},"615":{"u":"/docs/adr/063-accessibility-conformance-gate.html","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records"},"616":{"u":"/docs/adr/063-accessibility-conformance-gate.html#status","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-01). Revised 2026-08-14: refreshed the E2ETestBase helper line anchors (explanatory comments were added above ScanGridAsync), the two consumer suite scan counts…"},"617":{"u":"/docs/adr/063-accessibility-conformance-gate.html#context","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Context","x":"Accessibility was documented before it was enforced. The narrative guide (common-ACCESSIBILITY.md, rubric section 21) named WCAG 2.1 AA as the target for the shared…"},"618":{"u":"/docs/adr/063-accessibility-conformance-gate.html#decision","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Decision","x":"Ship WCAG 2.1 AA as a named, versioned test contract in MMCA.Common.Testing.E2E, assert it from the package's own workflow bases, and wire it as a required merge check and a…"},"619":{"u":"/docs/adr/063-accessibility-conformance-gate.html#rationale","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Rationale","x":"- A named constant is the contract. Putting the rule set in a shipped, referenced symbol rather than in each repo's test setup means \"what WCAG 2.1 AA means here\" has exactly one…"},"620":{"u":"/docs/adr/063-accessibility-conformance-gate.html#trade-offs","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- Best-practice rules are out of scope, deliberately. Findings axe would classify as best practice (and anything WCAG AAA) are not measured at all, so the gate can be green on a…"},"621":{"u":"/docs/adr/063-accessibility-conformance-gate.html#revision-2026-10-01","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Citations are re-anchored to current source: the Identity base assertions (UserLoginTestsBase.cs:82, UserRegistrationTestsBase.cs:95,…"},"622":{"u":"/docs/adr/063-accessibility-conformance-gate.html#related","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (architecture fitness functions: the structural tier this parallels at the browser tier, and the same invariant-over-discipline posture), ADR-058 (runtime conformance…"},"623":{"u":"/docs/adr/064-deploy-recency-gates.html","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records"},"624":{"u":"/docs/adr/064-deploy-recency-gates.html#status","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-01). Revised 2026-08-07: the MMCA.Helpdesk workflow inventory below was corrected (it also carries release-templates.yml, and its ci.yml runs two jobs, not…"},"625":{"u":"/docs/adr/064-deploy-recency-gates.html#context","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Context","x":"A production rollout in both deployed apps waits on a list of jobs in deploy.needs (MMCA.ADC/.github/workflows/deploy.yml:1185, MMCA.Store/.github/workflows/deploy.yml:1136).…"},"626":{"u":"/docs/adr/064-deploy-recency-gates.html#decision","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Decision","x":"A production deploy is blocked not only on green tests but on proof of recency for out-of-band verification: four gates assert that a real drill, a real load run, a real broker…"},"627":{"u":"/docs/adr/064-deploy-recency-gates.html#rationale","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Rationale","x":"- A proof with no expiry date is documentation, not a control. ADR-009 already required the drill to be recorded, and recording it was the honest half of the problem; a record…"},"628":{"u":"/docs/adr/064-deploy-recency-gates.html#trade-offs","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Trade-offs","x":"- An unrelated stale proof blocks an unrelated deploy. A one-line hotfix does not ship when the monthly k6 cron did not fire, and the failure surfaces after merge: the gate job…"},"629":{"u":"/docs/adr/064-deploy-recency-gates.html#revision-2026-10-01","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The four gate bodies no longer live in each repo's deploy.yml. MMCA.Common v1.216.0 (463) added the composite action MMCA.Common/.github/actions/freshness-gate/action.yml, and…"},"630":{"u":"/docs/adr/064-deploy-recency-gates.html#related","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Related","x":"ADR-009 (states the recovery objectives and requires that a restore be drilled and recorded; this record decides that a deploy is blocked on how recently that drill, and the…"},"631":{"u":"/docs/adr/065-scaffolding-templates.html","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","x":"Status: Accepted (2026-08-02). Revised 2026-08-07: the staged analyzer delta relaxes three rules rather than one; mmca-module prints seven wire-ups rather than five, and a…"},"632":{"u":"/docs/adr/065-scaffolding-templates.html#context","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","t":"Context","x":"Build by hand is accurate and complete, and phases 1 through 6 of it are transcription work (common-BUILD-BY-HAND.md:98 through :1255). Its own instruction for the load-bearing…"},"633":{"u":"/docs/adr/065-scaffolding-templates.html#decision","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","t":"Decision","x":"Ship a dotnet new template pack, MMCA.Templates, containing four templates: The template content is the MMCA.Helpdesk reference application itself, staged at pack time.…"},"634":{"u":"/docs/adr/065-scaffolding-templates.html#rationale","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","t":"Rationale","x":"Deriving from the seed rather than maintaining a template tree is the whole design. A hand-maintained copy of a 12-project solution drifts within one release, and drift in a…"},"635":{"u":"/docs/adr/065-scaffolding-templates.html#trade-offs","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","t":"Trade-offs","x":"- One documented one-time fixup in every generated app, above, covering all three relaxed rules. The alternative to the SA1210 half of the delta was moving every app-local using…"},"636":{"u":"/docs/adr/065-scaffolding-templates.html#revision-2026-10-01","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. The frozen wire contract is now keyed by each event's [EventName] value rather than its CLR type name…"},"637":{"u":"/docs/adr/066-broker-transport-selection.html","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records"},"638":{"u":"/docs/adr/066-broker-transport-selection.html#status","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-07). Revised 2026-08-14 (source citations re-anchored; the ADC AppHost comment that used to say no WithBroker() was wired has been corrected in code, so the…"},"639":{"u":"/docs/adr/066-broker-transport-selection.html#context","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Context","x":"ADR-003 decides that integration events leave an aggregate through the outbox and are published by OutboxProcessor via IMessageBus, and it settles the dispatch question…"},"640":{"u":"/docs/adr/066-broker-transport-selection.html#decision","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Decision","x":"Keep one IMessageBus abstraction with a three-value transport selector, choose the value at the deployment edge (never in application code), configure both broker transports…"},"641":{"u":"/docs/adr/066-broker-transport-selection.html#rationale","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Rationale","x":"- The transport is a deployment fact, so it lives at the deployment edge. The only difference between a laptop and production is two environment variables set by the AppHost or…"},"642":{"u":"/docs/adr/066-broker-transport-selection.html#trade-offs","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Trade-offs","x":"- Two brokers means two behaviors to keep aligned. Configuration parity is enforced by one code path, but the products still differ (Service Bus supports delayed redelivery…"},"643":{"u":"/docs/adr/066-broker-transport-selection.html#revision-2026-09-07","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The transport selection is unchanged. The credential topology under it is (SEC-ADC-26 / SEC-Store-38). Both repos previously sourced their Service Bus connection strings from one…"},"644":{"u":"/docs/adr/066-broker-transport-selection.html#revision-2026-10-01","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The local Service Bus emulator path is no longer ADC-only. Store's AppHost selects its broker once on STOREBROKER=servicebus: set, it calls AddServiceBusEmulatorBroker over the…"},"645":{"u":"/docs/adr/066-broker-transport-selection.html#related","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox that feeds IMessageBus; this ADR picks the transport underneath it), ADR-016 (the MassTransit v8 pin the emulator tier must work within, which is why the…"},"646":{"u":"/docs/adr/067-ui-module-shell-composition.html","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records"},"647":{"u":"/docs/adr/067-ui-module-shell-composition.html#status","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-07). Revised 2026-08-29: records the component-vendor choice (MudBlazor) that the Context already scoped to this ADR, and the IToastService / IAppDialogService…"},"648":{"u":"/docs/adr/067-ui-module-shell-composition.html#context","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Context","x":"ADR-059 decided how a module plugs into the server: an IModule implementation is discovered by reflection, registered in topological order, and a host composes an application out…"},"649":{"u":"/docs/adr/067-ui-module-shell-composition.html#decision","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Decision","x":"Ship the application shell in the framework package and let each module plug into it by implementing IUIModule, resolved from DI as IEnumerable . - The contract is four members,…"},"650":{"u":"/docs/adr/067-ui-module-shell-composition.html#rationale","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Rationale","x":"- One composition model across both tiers. A module already declares its server-side surface through IModule (ADR-059); declaring its UI surface through IUIModule means \"add a…"},"651":{"u":"/docs/adr/067-ui-module-shell-composition.html#trade-offs","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Trade-offs","x":"- Assembly is required even when it carries no route. A host-only module that contributes only a layout component still has to return an assembly, which then joins…"},"652":{"u":"/docs/adr/067-ui-module-shell-composition.html#revision-2026-10-01","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Re-anchored the citations that moved: Routes.razor (inject :7, router :12-14, AuthorizeRouteView :27-50), NavMenu.razor (inject :9, filters…"},"653":{"u":"/docs/adr/067-ui-module-shell-composition.html#related","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Related","x":"ADR-059 (the server-side IModule contract this mirrors in the presentation layer), ADR-056 (the render-mode strategy for the web heads, which decides how these components render…"},"654":{"u":"/docs/adr/068-value-objects-as-validated-primitives.html","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records"},"655":{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#status","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-07). Revised 2026-08-31 (Money's non-validating construction paths named, Currency's converter counted among the serialization annotations, Address added to the…"},"656":{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#context","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Context","x":"A domain model has two kinds of small type: the identity of a thing, and a value the thing carries. ADR-048 recorded the identity half: identifiers stay primitives named through…"},"657":{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#decision","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Decision","x":"Model a domain value that carries an invariant as an immutable record value object with a Result-returning factory; keep identifiers primitive (ADR-048). - One abstract record…"},"658":{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#rationale","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Rationale","x":"- The invariant belongs to the type, not to every caller. A string email can be validated in one handler and not the next; an Email cannot exist unvalidated, because the only…"},"659":{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#trade-offs","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Trade-offs","x":"- The pattern is not uniformly applied. Only three of the seven types have a companion Invariants class; the rest inline their checks. Only two of the four multi-field values…"},"660":{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#revision-2026-10-01","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision changed; four statements were corrected and anchors refreshed. (1) The Money sugar paragraph claimed an external caller cannot assemble a Money whose currency was…"},"661":{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#related","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Related","x":"ADR-048 (the deliberate opposite call for identifiers: primitives behind aliases, wrapper structs rejected, because identifiers cross process boundaries constantly and carry no…"},"662":{"u":"/docs/adr/069-shared-data-protection-key-ring.html","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records"},"663":{"u":"/docs/adr/069-shared-data-protection-key-ring.html#status","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-07). Updated 2026-08-14: Store's adoption has landed and is live (its own dedicated storage account, gated on dataProtectionStorageReady), and the ADC call-site…"},"664":{"u":"/docs/adr/069-shared-data-protection-key-ring.html#context","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Context","x":"ASP.NET Core's DataProtection default keeps the key ring in memory, per process. That is correct for a single-process host and wrong for a scaled-out one: every replica generates…"},"665":{"u":"/docs/adr/069-shared-data-protection-key-ring.html#decision","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Decision","x":"Add one opt-in registration call, AddCommonDataProtection, that persists the key ring to a single Azure blob so every replica of a host shares one ring…"},"666":{"u":"/docs/adr/069-shared-data-protection-key-ring.html#rationale","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Rationale","x":"- The key ring is the smallest thing that has to be shared. Sticky sessions would paper over the symptom while making a replica restart a mass sign-out, and a shared cache would…"},"667":{"u":"/docs/adr/069-shared-data-protection-key-ring.html#trade-offs","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Trade-offs","x":"- The key ring is encrypted at rest only where gate 2 was switched on. Both templates ship the path and both default it off (2026-09-10 revision), so on default parameters the…"},"668":{"u":"/docs/adr/069-shared-data-protection-key-ring.html#revision-2026-09-10","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Revision (2026-09-10)","x":"Gate 2 ships in both templates, default off, so \"configured nowhere\" is retired. The framework side is unchanged: AddCommonDataProtection…"},"669":{"u":"/docs/adr/069-shared-data-protection-key-ring.html#revision-2026-10-01","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; this revision refreshes citations only. Re-anchored in Context, Decision and the Store adoption notes: the ADC replica caps, sticky UI ingress,…"},"670":{"u":"/docs/adr/069-shared-data-protection-key-ring.html#related","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Related","x":"ADR-022 (the browser session cookies whose decryption this makes replica-independent, together with the antiforgery tokens the SSR pages mint), ADR-008 (the multi-host topology…"},"671":{"u":"/docs/adr/070-fail-fast-configuration-contract.html","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records"},"672":{"u":"/docs/adr/070-fail-fast-configuration-contract.html#status","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-07). Revised 2026-08-14 (source citations re-anchored). Revised 2026-08-23 (inventory re-counted after the password-reset vertical and the opt-in feature waves:…"},"673":{"u":"/docs/adr/070-fail-fast-configuration-contract.html#context","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Context","x":"Every host in the workspace reads a dozen or more configuration sections: connection strings, SMTP, JWT key material, outbox tuning, message-bus provider, module enablement,…"},"674":{"u":"/docs/adr/070-fail-fast-configuration-contract.html#decision","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Decision","x":"Bind every settings section through a validating chain that runs at startup, and read the bound value through IOptions of the concrete settings class. - One binding shape, used…"},"675":{"u":"/docs/adr/070-fail-fast-configuration-contract.html#rationale","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Rationale","x":"- A boot failure is cheaper than a first-use failure. A host that will not start is caught by the deployment, by a local dotnet run, or by CI. A host that starts and fails on the…"},"676":{"u":"/docs/adr/070-fail-fast-configuration-contract.html#trade-offs","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- Nothing enforces it. There is no architecture fitness test asserting that a new AddOptions call carries ValidateDataAnnotations().ValidateOnStart(). The uniformity above is…"},"677":{"u":"/docs/adr/070-fail-fast-configuration-contract.html#revision-2026-10-01","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; the inventory and several descriptions were brought back in line with source. The framework now has thirty-one registrations on the chain rather…"},"678":{"u":"/docs/adr/070-fail-fast-configuration-contract.html#related","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-025 (startup warm-up and readiness gating: this contract decides what happens before a host reaches that machinery), ADR-031 (feature flags read from configuration, whose…"},"679":{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records"},"680":{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#status","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-12). Amended 2026-08-13: the composition-time string trade-off below was resolved in v1.147.0 by a deferred-resolution overload; see the updated trade-off…"},"681":{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#context","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Context","x":"ADC's badge check-in feature (ADR-072) needs two things that look like one thing: an attendee's device has to show a QR code, and an organizer's device has to read one. They are…"},"682":{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#decision","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Decision","x":"Split the feature by what it actually depends on: QR display ships as a shared component, barcode scanning ships as an ADR-042 capability whose native half is opt-in per head. -…"},"683":{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#rationale","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Rationale","x":"- Rendering a QR is not a device concern, so making it one would have been ceremony. As a capability it would have needed an interface, a null fallback and a native override for…"},"684":{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#trade-offs","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Trade-offs","x":"- The scan page's text is the caller's delegate, not a string (resolved in v1.147.0). cancelText and cameraDescription arrive as Func and are invoked once per scan, when the…"},"685":{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#revision-2026-10-01","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. The package pin citations are re-anchored to MMCA.Common/Directory.Packages.props:191 (QRCoder 1.8.0) and :225 (ZXing.Net.Maui.Controls 0.10.4).…"},"686":{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#related","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Related","x":"ADR-042 (the capability pattern this extends: contract in MMCA.Common.UI, native implementation in MMCA.Common.UI.Maui, override after AddUIShared), ADR-072 (the ADC feature that…"},"687":{"u":"/docs/adr/072-qr-badge-check-in-and-points.html","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records"},"688":{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#status","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13). Amended (2026-08-14): ADC shipped two attendee-self-recorded scan surfaces (sponsor booth visits and room self check-in), a third CheckInScope, a sixth…"},"689":{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#context","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Context","x":"ADC wanted two conference-day capabilities that turn out to be one mechanism. Organizers want to know who actually attended which session, which the schedule cannot tell them: a…"},"690":{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#decision","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Decision","x":"AttendeeBadge (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Domain/Badges/AttendeeBadge.cs:17-24) is one row per user holding a single Guid Credential, minted on first…"},"691":{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#rationale","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Rationale","x":"- An opaque credential makes the server the only interpreter. A JWT or HMAC badge would verify offline, but the scanning device is online by necessity (it has to write a check-in…"},"692":{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#trade-offs","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Trade-offs","x":"- The badge credential is a bearer value. Anyone who photographs an attendee's screen can be checked in as that attendee. The mitigations are that a badge scan is organizer-side,…"},"693":{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#revision-2026-10-01","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"One content fact changed and is corrected in place in the Decision: a fourth handler raises the feedback events. The event-side batch path,…"},"694":{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#related","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Related","x":"ADR-071 (the framework halves this consumes: the QR component on /my-badge and the scanner capability behind /check-in), ADR-003 (the outbox path AttendeeCheckedIn and the two…"},"695":{"u":"/docs/adr/073-multi-tenancy-model.html","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records"},"696":{"u":"/docs/adr/073-multi-tenancy-model.html#status","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13). The implementation lands in the MMCA.Common enterprise capability wave release, alongside the scheduler, audit trail, DSAR export, and CSV export work. It…"},"697":{"u":"/docs/adr/073-multi-tenancy-model.html#context","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Context","x":"MMCA.Common already partitions data along two axes and neither of them is a tenant. ADR-006 partitions by source name (every entity resolves to a DataSourceKey(Engine, Name),…"},"698":{"u":"/docs/adr/073-multi-tenancy-model.html#decision","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Decision","x":"Ship shared-schema tenancy as a second named query filter, with per-tenant database routing as a configuration override on the same source key, both opt-in and both inert until a…"},"699":{"u":"/docs/adr/073-multi-tenancy-model.html#rationale","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Rationale","x":"- A query filter is the only place the rule cannot be forgotten. Per-handler Where clauses are correct until the tenth handler, and the tenth handler is a data leak rather than a…"},"700":{"u":"/docs/adr/073-multi-tenancy-model.html#trade-offs","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Reads are on discipline where writes are on an invariant. A consumer calling EF's own parameterless IgnoreQueryFilters() on a raw Table surface drops the tenant filter along…"},"701":{"u":"/docs/adr/073-multi-tenancy-model.html#related","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Related","x":"ADR-006 (the source-name axis this composes with: an override re-points a DataSourceKey without changing it, and the per-source outbox this record drains once per tenant),…"},"702":{"u":"/docs/adr/073-multi-tenancy-model.html#revision-2026-09-11","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"The gap this closes was never written down here. Everything above is about the tenant inside a request: the middleware resolves it, the named filter composes on it, the write…"},"703":{"u":"/docs/adr/073-multi-tenancy-model.html#revision-2026-10-01","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The body now agrees with the column the 2026-09-11 Revision added. \"Background work drains and migrates per tenant\" still stated that there was deliberately no OutboxMessage…"},"704":{"u":"/docs/adr/074-recurring-job-scheduler.html","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records"},"705":{"u":"/docs/adr/074-recurring-job-scheduler.html#status","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13; revised 2026-08-14, 2026-08-18, 2026-08-31). The implementation lands in the MMCA.Common \"enterprise capability wave\" release and is opt-in: a host calls…"},"706":{"u":"/docs/adr/074-recurring-job-scheduler.html#context","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Context","x":"The framework had two kinds of background work and neither of them is a schedule. OutboxProcessor…"},"707":{"u":"/docs/adr/074-recurring-job-scheduler.html#decision","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Decision","x":"A persistent job store plus a single-runner claim lease, reusing the exact idiom the outbox proved. The outbox claims a batch with an ExecuteUpdateAsync that sets LockedUntil and…"},"708":{"u":"/docs/adr/074-recurring-job-scheduler.html#rationale","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Rationale","x":"- The lease is already proven under production load. Multi-replica correctness for recurring work is the hard part, and it was solved once for the outbox: an atomic claim update,…"},"709":{"u":"/docs/adr/074-recurring-job-scheduler.html#trade-offs","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Trade-offs","x":"- A polling loop is not a real-time scheduler. Worst-case start lag is one polling interval, 30 seconds at the default, so sub-minute precision is not on offer. A job that must…"},"710":{"u":"/docs/adr/074-recurring-job-scheduler.html#revision-2026-10-01","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. The job-store field list now names LastError, a nullable string the entry already carries…"},"711":{"u":"/docs/adr/074-recurring-job-scheduler.html#related","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox whose claim-lease idiom and smart wait this reuses verbatim, and whose at-least-once posture it inherits along with the idempotency obligation on job bodies),…"},"712":{"u":"/docs/adr/075-audit-trail.html","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records"},"713":{"u":"/docs/adr/075-audit-trail.html#status","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13; corrected 2026-08-14: the adoption sweep and the ApplicationDbContext line citations). The implementation lands in the MMCA.Common \"enterprise capability…"},"714":{"u":"/docs/adr/075-audit-trail.html#context","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Context","x":"The framework already answers \"who touched this row last\". Every AuditableBaseEntity carries CreatedOn/By and LastModifiedOn/By, stamped by AuditSaveChangesInterceptor on the way…"},"715":{"u":"/docs/adr/075-audit-trail.html#decision","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Decision","x":"AuditTrailSaveChangesInterceptor (Infrastructure Persistence/AuditTrail/) joins the interceptors ApplicationDbContext.OnConfiguring already passes to…"},"716":{"u":"/docs/adr/075-audit-trail.html#rationale","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Rationale","x":"- IAuditableEntity is a statement about a business row, and an audit row is not one. The interface means \"this row stamps who created and last modified it and participates in…"},"717":{"u":"/docs/adr/075-audit-trail.html#trade-offs","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Write amplification is real and it is on the caller's latency path. An entity with twenty changed properties writes twenty rows inside the caller's transaction, so an audited…"},"718":{"u":"/docs/adr/075-audit-trail.html#revision-2026-09-07","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Two additions from the 2026-09-07 security review. 1. The [Pii] convention is now checked in both directions (SEC-Store-19 / SEC-Store-24). The shared base already obliged a…"},"719":{"u":"/docs/adr/075-audit-trail.html#revision-2026-09-25-both-apps-record-trail-dml-by-different-means","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Revision (2026-09-25): both apps record trail DML, by different means","x":"Revision item 2 above previously held for Store only: ADC's server-level audit carried no statement auditing, so a rewrite of dbo.AuditTrailEntries by the shared admin login left…"},"720":{"u":"/docs/adr/075-audit-trail.html#revision-2026-10-01-retention-has-no-fallback-and-the-current-state-text-matches-the-code","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Revision (2026-10-01): retention has no fallback, and the current-state text matches the code","x":"- Retention runs on the scheduler or not at all. The Decision previously named a PeriodicBackgroundService subclass as the fallback for a host without the scheduler. No such…"},"721":{"u":"/docs/adr/075-audit-trail.html#related","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the same-transaction write this copies wholesale, including the retry-discard and the Add-only mutation rule), ADR-005 (soft-delete, [Pii] and erasure: why the trail…"},"722":{"u":"/docs/adr/076-data-subject-export.html","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records"},"723":{"u":"/docs/adr/076-data-subject-export.html#status","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13). Revised 2026-08-14 (the API-surface section corrected to the shipped mechanism, an abstract DataExportControllerBase a subclass mounts, not an…"},"724":{"u":"/docs/adr/076-data-subject-export.html#context","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Context","x":"A data-subject access request is a legal obligation with a clock on it: the person asks for a copy of the personal data held about them, and the operator has a deadline to hand…"},"725":{"u":"/docs/adr/076-data-subject-export.html#decision","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Decision","x":"The framework takes the part that is the same in both apps; the app keeps the part that is not. A consumer's export handler becomes a subclass that supplies a role test and a set…"},"726":{"u":"/docs/adr/076-data-subject-export.html#rationale","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Rationale","x":"- The two halves of a handler have different owners. The ownership gate, the aggregate load, the fan-out, the per-section catch and the envelope are the same decisions in both…"},"727":{"u":"/docs/adr/076-data-subject-export.html#trade-offs","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- Best-effort degradation can return a quietly incomplete package. Available = false is the only signal, and nothing forces a caller, a UI, or the subject to read it. A section…"},"728":{"u":"/docs/adr/076-data-subject-export.html#revision-2026-10-01","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Store now registers a second export section: alongside Sales it contributes CatalogUserDataExportSection (SectionName \"Catalog\",…"},"729":{"u":"/docs/adr/076-data-subject-export.html#related","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-005 (the erasure half of the same privacy obligation, whose IAnonymizable opt-in and [Pii] guard are this contract's mirror: one erases what the other copies), ADR-033 (the…"},"730":{"u":"/docs/adr/077-hybridcache-substrate.html","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records"},"731":{"u":"/docs/adr/077-hybridcache-substrate.html#status","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13). Amends ADR-026: Tier 1's substrate gains a third implementation beside MemoryCacheService and DistributedCacheService. It is opt-in through…"},"732":{"u":"/docs/adr/077-hybridcache-substrate.html#context","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Context","x":"ADR-026 settled Tier 1 as one abstraction (ICacheService) over two implementations chosen at startup: in-process memory when no real IDistributedCache is present, Redis…"},"733":{"u":"/docs/adr/077-hybridcache-substrate.html#decision","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Decision","x":"Ship HybridCacheService as a third ICacheService implementation, opt-in per host, under a disjoint keyspace. This is the structural rule the design is built around, and…"},"734":{"u":"/docs/adr/077-hybridcache-substrate.html#rationale","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Rationale","x":"- The disjoint keyspace is the decision; everything else is implementation. Rather than trusting a second implementation to write a shape compatible with the first, this record…"},"735":{"u":"/docs/adr/077-hybridcache-substrate.html#trade-offs","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Invalidation does not reach other replicas' L1 immediately. A remove evicts the L2 entry and the calling replica's L1; every other replica keeps its copy for up to…"},"736":{"u":"/docs/adr/077-hybridcache-substrate.html#revision-2026-10-01","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Two decisions joined the record. First, ICacheService gained GetFromSharedStoreAsync (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:62), which…"},"737":{"u":"/docs/adr/077-hybridcache-substrate.html#related","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Related","x":"ADR-026 (amended by this record: its Tier 1 substrate gains a third implementation, its 30-second default TTL becomes the local-cache bound as well, its prefix-invalidation model…"},"738":{"u":"/docs/adr/078-csv-export-endpoint.html","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records"},"739":{"u":"/docs/adr/078-csv-export-endpoint.html#status","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13; revised 2026-08-18, 2026-08-31, 2026-09-19). The endpoint ships in the MMCA.Common \"enterprise capability wave\" release (v1.150.0), its scoping hook in…"},"740":{"u":"/docs/adr/078-csv-export-endpoint.html#context","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Context","x":"The request is \"export what you filtered\". The generic entity surface of ADR-034 already accepts a full query vocabulary on the paged route…"},"741":{"u":"/docs/adr/078-csv-export-endpoint.html#decision","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Decision","x":"EntityControllerBase gains a virtual [HttpGet(\"export\")] ExportAsync(...) action (Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:248, method at…"},"742":{"u":"/docs/adr/078-csv-export-endpoint.html#rationale","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Rationale","x":"- A route is an unambiguous request; an Accept header is a preference. Given a cache policy that ignores Accept and a pipeline configured to never return 406, a client that…"},"743":{"u":"/docs/adr/078-csv-export-endpoint.html#trade-offs","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Trade-offs","x":"- Every derivative gains a bulk read whether its owner wanted one or not. The only gate is the controller's existing authorization posture. A resource that was safe to page 20…"},"744":{"u":"/docs/adr/078-csv-export-endpoint.html#revision-2026-10-01","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. The CSV machinery now lives outside the controller, in the internal static EntityCsvExporter…"},"745":{"u":"/docs/adr/078-csv-export-endpoint.html#related","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Related","x":"ADR-034 (the generic entity surface and query contract this extends, and the MaxUnboundedResultLimit ceiling that forced the page loop), ADR-040 (the output-cache policy whose…"},"746":{"u":"/docs/adr/079-shared-http-middleware-pipeline.html","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records"},"747":{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#status","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14). Revised 2026-08-19 (refreshed the WebApplicationBuilderExtensions.cs cross-reference anchor, which moved to :555). Revised 2026-08-21: the order became…"},"748":{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#context","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Context","x":"In ASP.NET Core, middleware order is behavior, not style: a rate limiter placed before authentication partitions every request as anonymous, an HTTPS redirect placed in front of…"},"749":{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#decision","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Decision","x":"Ship the edge as one ordered pipeline in the framework, UseCommonMiddlewarePipeline (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationExtensions.cs:48), and…"},"750":{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#rationale","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Rationale","x":"- Order is behavior, so it belongs to the framework, not to each host. Four of the adjacencies above fail silently when reversed: the limiter stops limiting, the tenant resolver…"},"751":{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#trade-offs","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Trade-offs","x":"- Two of this record's original costs are retired (2026-08-21). As accepted, nothing froze the order (no test referenced the method; the adjacencies were protected by comments…"},"752":{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#revision-2026-10-01","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Anchor refresh only; no decision or rationale changed. Refreshed citations: the private ApplyPipeline helper (WebApplicationExtensions.cs:163-179), the lazy validator resolution…"},"753":{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#related","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Related","x":"ADR-014 (the in-process sibling: one fixed decorator order for commands and queries), ADR-019 (depends on forwarded headers before the limiter and on the limiter after…"},"754":{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records"},"755":{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#status","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14). Revised 2026-09-03 (the smoke gate is now two tiers: a revision activation gate runs before the HTTP probes, and the rollback selector filters on…"},"756":{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#context","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Context","x":"Both production apps deploy to Azure Container Apps from a single deploy.yml job on push to main, and every gate runs before anything rolls out. The deploy job waits on eleven…"},"757":{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#decision","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Decision","x":"Roll out one revision at a time, verify it from outside, and auto-revert the image only when the verification fails. - Single-revision rollout. Every container app runs…"},"758":{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#rationale","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Rationale","x":"- ARM success is the wrong success signal. The smoke gate converts \"the control plane accepted the template\" into \"the fleet is serving the revision this run built\", which is the…"},"759":{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#trade-offs","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Trade-offs","x":"- Schema is never rolled back, so a bad migration is fix-forward only. The image reverts and the database does not, so the previous release resumes against the new schema. This…"},"760":{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#revision-2026-09-10","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Revision (2026-09-10)","x":"ADC's activation tier is now one shared budget over the whole fleet, matching Store. The attempt loop is the outer loop and the apps are iterated inside it: for i in $(seq 1 30)…"},"761":{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#revision-2026-10-01","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Three current-state statements are corrected; the rollout and revision-only rollback model is unchanged. First, the Gateway is not readiness-gated on /health/ready: in both repos…"},"762":{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#related","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Related","x":"ADR-057 (built on this model: revision-only rollback is why every migration must be backward compatible one release back), ADR-030 (startup migration as sole migrator, the reason…"},"763":{"u":"/docs/adr/081-cost-baseline-deploy-gate.html","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records"},"764":{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#status","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14). Revised 2026-08-31 (the read-only claim is qualified: one az config set on the runner's own CLI precedes the queries; citations re-anchored). Revised…"},"765":{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#context","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Context","x":"Both deployed apps run a deliberately small production footprint: every Container App is declared with maxReplicas: 2 and every SQL database with the Basic tier…"},"766":{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#decision","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Decision","x":"The cost baseline is asserted by a read-only reusable workflow that both runs weekly and sits in deploy.needs, so an un-reverted scale-up blocks the next production deploy. - One…"},"767":{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#rationale","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Rationale","x":"- Configuration drift is the leading indicator; spend is the lagging one. The budget notification fires at 80% of actual spend, after the money is gone, and names a number rather…"},"768":{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#trade-offs","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- A legitimate scale-up blocks deploys until the baseline is edited. Standing up extra capacity for a real event and then shipping a fix during it requires a pull request against…"},"769":{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#revision-2026-10-01","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Store's SQL check now matches its own stated window. It reads [sku.tier, sku.name] (MMCA.Store/.github/workflows/cost-guard.yml:96-97) and passes a database only through…"},"770":{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#related","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-064 (the sibling deploy-precondition record, which decides the four proof-of-recency gates and enumerates this one only in passing; its break-glass input does not apply…"},"771":{"u":"/docs/adr/082-two-tier-cors-posture.html","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records"},"772":{"u":"/docs/adr/082-two-tier-cors-posture.html#status","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14)."},"773":{"u":"/docs/adr/082-two-tier-cors-posture.html#context","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Context","x":"Both deployed applications put a YARP gateway in front of per-module service hosts (ADR-008), and the browser and MAUI clients talk to the gateway origin while the services…"},"774":{"u":"/docs/adr/082-two-tier-cors-posture.html#decision","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Decision","x":"Ship two cross-origin policies from the framework: an allow-listed one for service hosts and a deliberately broader one for gateways. - Service hosts register two named policies…"},"775":{"u":"/docs/adr/082-two-tier-cors-posture.html#rationale","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Rationale","x":"- A proxy cannot allow-list what it does not own. The gateway has no controllers and no knowledge of which headers the fronted services accept, so a header allow-list there would…"},"776":{"u":"/docs/adr/082-two-tier-cors-posture.html#trade-offs","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Trade-offs","x":"- The gateway policy is broad on two of three axes. Any header and any method are accepted for an allow-listed origin. The origin list is the only lever there, so a mistake in…"},"777":{"u":"/docs/adr/082-two-tier-cors-posture.html#revision-2026-10-01","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision changed. Citations were re-anchored to the current source: AddCommonCors and its policy lines (WebApplicationBuilderExtensions.cs:124-141), the pipeline Cors step…"},"778":{"u":"/docs/adr/082-two-tier-cors-posture.html#related","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Related","x":"ADR-079 (the shared middleware pipeline whose fixed order places the environment-selected CORS policy between routing and authentication), ADR-008 (the gateway plus per-module…"},"779":{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records"},"780":{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#status","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14). Revised 2026-08-23: the adopter counts were refreshed (ADC Conference's ActivityChanged joined the base-derived set) and three source citations were…"},"781":{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#context","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Context","x":"ADR-003 decides how a domain event moves: captured into the outbox inside SaveChangesAsync, dispatched in-process after commit, or published to the broker when it is an…"},"782":{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#decision","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Decision","x":"Every generic CRUD lifecycle transition of an entity raises one event type for that entity, carrying a DomainEntityState discriminator; handlers filter on State. - One base…"},"783":{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#rationale","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Rationale","x":"- One type per entity is one subscription surface. A subscriber declares interest in the entity, then decides which transitions matter, instead of the container deciding for it…"},"784":{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#trade-offs","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Trade-offs","x":"- Every selective handler pays a filter. A handler that cares about one transition has to open with a State guard and return (SpeakerDeletedHandler.cs:29-30 is the shape to…"},"785":{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#alternatives-rejected","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Alternatives rejected","x":"- Splitting each XChanged event into per-fact past-tense events (SpeakerRenamed, SessionCancelled, and so on) with every handler and bridge updated to match. Proposed 2026-08-26,…"},"786":{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#related","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (how these events are captured and dispatched; this ADR decides only their shape), ADR-010 (schema versioning for the discriminator once it crosses a service boundary),…"},"787":{"u":"/docs/adr/084-stripe-webhook-ingress.html","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records"},"788":{"u":"/docs/adr/084-stripe-webhook-ingress.html#status","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14). Revised 2026-09-03. Revised 2026-09-07 (the webhook action carries its own request size limit, the auto-minted signing secret is shared through the…"},"789":{"u":"/docs/adr/084-stripe-webhook-ingress.html#context","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Context","x":"Four ADRs already cover how a message crosses a boundary in this workspace. ADR-003 decides how an event leaves a service (outbox, at-least-once). ADR-021 decides how a…"},"790":{"u":"/docs/adr/084-stripe-webhook-ingress.html#decision","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Decision","x":"Treat third-party webhook ingress as its own contract with two halves: an acceptance-coded endpoint and a self-registering, self-provisioning endpoint registration at startup. -…"},"791":{"u":"/docs/adr/084-stripe-webhook-ingress.html#rationale","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Rationale","x":"- The caller's protocol decides the response vocabulary. Stripe reads a status code as \"keep retrying\" or \"stop\", not as \"this succeeded\" or \"this failed\". Mapping every…"},"792":{"u":"/docs/adr/084-stripe-webhook-ingress.html#trade-offs","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Trade-offs","x":"- A startup service that writes to a live third-party account. Booting a Sales replica creates and deletes webhook endpoints in the real Stripe account…"},"793":{"u":"/docs/adr/084-stripe-webhook-ingress.html#revision-2026-09-07","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Four changes from the 2026-09-07 security review. The ingress contract (raw body, signature verified before parsing, anonymous endpoint) is unchanged. 1. The webhook action…"},"794":{"u":"/docs/adr/084-stripe-webhook-ingress.html#revision-2026-10-01","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The ingress contract (acceptance-coded status codes, anonymous raw-body endpoint, startup self-registration with marker-scoped deletion) is unchanged. This revision brings the…"},"795":{"u":"/docs/adr/084-stripe-webhook-ingress.html#related","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (outbound at-least-once delivery, the other end of the same family), ADR-021 (broker-side inbound dedup, which never sees a webhook), ADR-017 (client-supplied idempotency…"},"796":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records"},"797":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#status","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-18). Revised 2026-08-23 (the alias count and the migration-surface census were recounted, the census gained a stated methodology, and the CheckIn and…"},"798":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#context","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Context","x":"ADR-048 decided that every entity identity is a primitive named through a per-module global using {Entity}IdentifierType = ... alias, and recorded the cost in one line of…"},"799":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#decision","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Decision","x":"Keep the aliases. The wrapper-struct alternative is evaluated in this record, priced, and deferred again, this time against explicit triggers. Inside a module an identifier is…"},"800":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#rationale","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Rationale","x":"- The cost is paid once and the benefit accrues per defect avoided, and the defect count is currently zero. No production incident in any of the four repos has been traced to a…"},"801":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#trade-offs","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The exposure is unmitigated, not reduced. This record buys no safety whatsoever. Every transposition ADR-048 could not catch is still uncatchable today, and the CheckIn…"},"802":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#related","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Related","x":"ADR-048 (the decision this record revisits and upholds; its Status now points here), ADR-068 (the deliberate opposite case: domain values carry invariants and therefore do get…"},"803":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#revision-2026-08-23","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"The decision, the priced alternative, the three triggers and the trade-offs are unchanged. What changed is arithmetic and three citations. The alias count is 44 across 10 files,…"},"804":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#revision-2026-09-11","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"The decision, the priced alternative, the three triggers and the trade-offs are unchanged. What changed is arithmetic and one Context fact. The alias count is 46 across the same…"},"805":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#revision-2026-09-19","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Revision (2026-09-19)","x":"The decision, the priced alternative, the three triggers and the trade-offs are unchanged. What changed is arithmetic and the non-int bullet. The alias count is 48 across the…"},"806":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#revision-2026-10-01","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision, trigger, rationale or trade-off changed. The migration-surface census is re-measured on the same rule: 3,869 lines across 1,209 files (Common 296/117, ADC 2,282/694,…"},"807":{"u":"/docs/adr/086-process-manager-deferred.html","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records"},"808":{"u":"/docs/adr/086-process-manager-deferred.html#status","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-18) as a documented deferral. Nothing ships with this record: no state machine, no correlation store, no new package. What ships is the shape the coordinator…"},"809":{"u":"/docs/adr/086-process-manager-deferred.html#context","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Context","x":"ADR-054 decided how this workspace achieves cross-boundary consistency without two-phase commit: choreography. Each step of a workflow raises a domain event, each compensating…"},"810":{"u":"/docs/adr/086-process-manager-deferred.html#decision","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Decision","x":"Defer the process manager, and record its shape so the deferral is a design decision rather than an omission. A durable multi-step workflow coordinator in this workspace is a…"},"811":{"u":"/docs/adr/086-process-manager-deferred.html#rationale","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Rationale","x":"- Choreography is genuinely correct for the workflow that exists. This is not a case of the simpler option being tolerated. Checkout's saga state is two fields on Order, and an…"},"812":{"u":"/docs/adr/086-process-manager-deferred.html#trade-offs","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The first workflow to hit the trigger pays the full cost at once, under whatever deadline made it appear. Deferral moves the work onto the critical path of the feature that…"},"813":{"u":"/docs/adr/086-process-manager-deferred.html#revision-2026-09-11-the-first-per-instance-deadline-exists-and-it-is-not-a-state-machine","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Revision (2026-09-11): the first per-instance deadline exists, and it is not a state machine","x":"One of the three properties this record listed as absent has arrived, from an unexpected direction. MMCA.Store now gives every unpaid order its own scheduled deadline:…"},"814":{"u":"/docs/adr/086-process-manager-deferred.html#revision-2026-10-01","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Two current-state statements are refreshed. The MassTransit pin is 8.5.11 for all three packages, still v8…"},"815":{"u":"/docs/adr/086-process-manager-deferred.html#related","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Related","x":"ADR-054 (the accepted mechanism this record defers an alternative to: choreographed compensation, the persisted aggregate marker, and the reconciliation sweep that would remain…"},"816":{"u":"/docs/adr/087-broker-poison-message-handling.html","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records"},"817":{"u":"/docs/adr/087-broker-poison-message-handling.html#status","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-18). Amends ADR-009: the outbox's broker publish gains a circuit breaker, which is the first resilience policy this workspace applies to something other than an…"},"818":{"u":"/docs/adr/087-broker-poison-message-handling.html#context","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Context","x":"Delivery in this workspace has always been at-least-once with retries on both legs: the outbox retries a failed publish with jittered exponential backoff and eventually…"},"819":{"u":"/docs/adr/087-broker-poison-message-handling.html#decision","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Decision","x":"Three changes, each scoped to one failure: second-level redelivery configured per transport, a fault consumer with its own meter, and a circuit breaker around the outbox's broker…"},"820":{"u":"/docs/adr/087-broker-poison-message-handling.html#rationale","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Rationale","x":"- The transport asymmetry follows a real capability difference, not a preference. RabbitMQ needs a plugin the dev container lacks; Service Bus does not. A single default would be…"},"821":{"u":"/docs/adr/087-broker-poison-message-handling.html#trade-offs","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Trade-offs","x":"- Delayed redelivery is off where the plugin problem lives. RabbitMQ is the local transport and also a plausible self-hosted production transport; both get default-off, so the…"},"822":{"u":"/docs/adr/087-broker-poison-message-handling.html#revision-2026-10-01","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. The broker transport wiring now lives in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs…"},"823":{"u":"/docs/adr/087-broker-poison-message-handling.html#related","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox publish leg this breaker wraps, and the retry, jittered backoff and dead-lettering that BrokenCircuitException reuses unchanged), ADR-066 (the transport…"},"824":{"u":"/docs/adr/088-gateway-edge-responsibilities.html","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records"},"825":{"u":"/docs/adr/088-gateway-edge-responsibilities.html#status","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-18). Extends ADR-019 with a fourth, edge-tier layer whose posture is the deliberate opposite of the service tier's authenticated-only global limiter; nothing in…"},"826":{"u":"/docs/adr/088-gateway-edge-responsibilities.html#context","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Context","x":"ADR-008 made the Gateway the only client entry point and gave it three jobs: the route-to-service map, CORS, and forwarding the caller's Authorization header. Nothing was added…"},"827":{"u":"/docs/adr/088-gateway-edge-responsibilities.html#decision","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Decision","x":"Ship a gateway edge kit in a Gateway namespace inside MMCA.Common.Aspire, owning exactly three responsibilities, and record three more as deliberately declined. A fourth section,…"},"828":{"u":"/docs/adr/088-gateway-edge-responsibilities.html#rationale","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Rationale","x":"- The edge is the only place that sees a request exactly once. That is what makes ensure-at-the-edge correct and mint-per-service wrong: not that the service version is broken,…"},"829":{"u":"/docs/adr/088-gateway-edge-responsibilities.html#trade-offs","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The limiter closes over an eagerly-bound copy of the settings (GatewayRateLimitingExtensions.cs:278-279, consumed at :308 and :310), so an IOptionsMonitor reload never reaches…"},"830":{"u":"/docs/adr/088-gateway-edge-responsibilities.html#revision-2026-09-07","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Two changes from the 2026-09-07 security review. 1. Health endpoints are cached server-side, and the bypass stays (SEC-Common-71 / SEC-ADC-17 / SEC-ADC-56). /health and…"},"831":{"u":"/docs/adr/088-gateway-edge-responsibilities.html#revision-2026-09-10","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Revision (2026-09-10)","x":"Both public UI hosts now carry the own-host hardening, so item 2 above is no longer a Store-only remediation. ADC's conference UI is externally reachable on its own FQDN, which…"},"832":{"u":"/docs/adr/088-gateway-edge-responsibilities.html#revision-2026-09-20","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Revision (2026-09-20)","x":"The decision the last revision declined to take is taken here: the own-host UI hardening is framework code. Two near-identical copies in two consumers is the shape that says a…"},"833":{"u":"/docs/adr/088-gateway-edge-responsibilities.html#revision-2026-10-01","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Both consumers now turn active destination probing off. The framework half of the delegation is unchanged: active checks stay opt-in…"},"834":{"u":"/docs/adr/088-gateway-edge-responsibilities.html#related","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Related","x":"ADR-008 (the record that made the Gateway the only entry point and gave it routing, CORS and auth forwarding; this is the first record to add cross-cutting behavior to it),…"},"835":{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records"},"836":{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#status","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-18; revised 2026-08-23: ADC's table gained a 27th route, /Activities, on 2026-08-19; revised 2026-08-31: section 5 now states the activity timeout where it…"},"837":{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#context","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Context","x":"Before this record, both gateways built their route table by hand, in code. ADC made 26 MapForwarder calls and Store made 10. Neither host called AddReverseProxy or…"},"838":{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#decision","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Decision","x":"Make configuration the single source of the gateway route table, and pin it with a test. Each gateway calls…"},"839":{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#rationale","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Rationale","x":"- The drift already happened, in the repository that has the most gateway tests. ADC is the careful consumer, and it still carried three unpinned routes, an off-by-one comment…"},"840":{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#trade-offs","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Trade-offs","x":"- The compiler stopped helping. A misspelled cluster reference, a malformed path pattern or a route that shadows another is a runtime failure, discovered as a 404 or a 502, where…"},"841":{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#revision-2026-10-01","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision changed. Two current-state statements were corrected. The Rationale example of an ungated comment drifting no longer holds for ADC, whose conference label now reads…"},"842":{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#related","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Related","x":"ADR-008 (amended: the Gateway keeps the route-to-service map it was given, now expressed as configuration rather than as forwarder registrations), ADR-088 (the other half of this…"},"843":{"u":"/docs/adr/090-event-upcaster-registration.html","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records"},"844":{"u":"/docs/adr/090-event-upcaster-registration.html#status","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-21). Completes the follow-up named in ADR-010: that record established the versioning policy (a SchemaVersion signal plus a new-type-and-upcaster discipline for…"},"845":{"u":"/docs/adr/090-event-upcaster-registration.html#context","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Context","x":"ADR-010 splits event evolution into a signal and a discipline. The signal (SchemaVersion, a fitness-function-gated property on every integration event) shipped with ADR-010…"},"846":{"u":"/docs/adr/090-event-upcaster-registration.html#decision","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Decision","x":"1. A typed upcaster abstraction, in the Application layer. IEventUpcaster (Source/Core/MMCA.Common.Application/Interfaces/Events/IEventUpcaster.cs) is a pure payload mapping from…"},"847":{"u":"/docs/adr/090-event-upcaster-registration.html#rationale","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Rationale","x":"- The discipline becomes a mechanism. ADR-010's own framing (a thing that matters is a check, not a comment) now applies to the upcaster half: the transform has a first-class…"},"848":{"u":"/docs/adr/090-event-upcaster-registration.html#trade-offs","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Trade-offs","x":"- The upcast walk advances by declared target type, not runtime type. A misbehaving upcaster that returns an instance of some other type cannot send the walk into an unvalidated…"},"849":{"u":"/docs/adr/090-event-upcaster-registration.html#revision-2026-09-03","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Revision (2026-09-03)","x":"The decision, the mechanism, and both delivery paths are unchanged. What changed is one trade-off's premise and three file locations. 1. Outbox type resolution is no longer…"},"850":{"u":"/docs/adr/090-event-upcaster-registration.html#revision-2026-10-01","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision, mechanism, or rationale changed; this revision refreshes citations only. AddEventUpcaster is defined in…"},"851":{"u":"/docs/adr/091-cache-backed-password-reset.html","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records"},"852":{"u":"/docs/adr/091-cache-backed-password-reset.html#status","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-22). Extends ADR-029 (the cache-backed login-protection idiom this record reuses) and ADR-032 (which decided how a password is stored, never how a user who has…"},"853":{"u":"/docs/adr/091-cache-backed-password-reset.html#context","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Context","x":"Both consumer apps shipped authenticated password change (PUT /Auth/password) and nothing for a user who cannot sign in at all. The recorded fallback in MMCA.ADC's specification…"},"854":{"u":"/docs/adr/091-cache-backed-password-reset.html#decision","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Decision","x":"1. The reset token is a cache record, not a schema change. IPasswordResetTokenService (Source/Core/MMCA.Common.Application/Auth/IPasswordResetTokenService.cs) is two methods,…"},"855":{"u":"/docs/adr/091-cache-backed-password-reset.html#rationale","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Rationale","x":"- No migration is the whole point. A reset credential is short-lived by nature, and the expiry semantics a reset needs (a TTL, a single use, an attempt cap) are native to a cache…"},"856":{"u":"/docs/adr/091-cache-backed-password-reset.html#trade-offs","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Trade-offs","x":"- Cache eviction invalidates outstanding tokens. A Redis restart, an eviction under memory pressure, or a fall back to the in-memory store on a different replica all silently…"},"857":{"u":"/docs/adr/091-cache-backed-password-reset.html#revision-2026-10-01","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Three behaviors changed after acceptance, and four statements are corrected: one was omitted at acceptance, two were wrong as written, and one had drifted from a later revision…"},"858":{"u":"/docs/adr/092-web-vitals-budget-gate.html","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records"},"859":{"u":"/docs/adr/092-web-vitals-budget-gate.html#status","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-23). Revised 2026-09-01: the measurement flow itself now ships as an IPage extension that every suite routes through, and the framework gallery measures three…"},"860":{"u":"/docs/adr/092-web-vitals-budget-gate.html#context","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Context","x":"Rubric section 23 asks for client-side performance that is measured rather than assumed, naming Core Web Vitals (LCP, INP, CLS) or an equivalent as the evidence…"},"861":{"u":"/docs/adr/092-web-vitals-budget-gate.html#decision","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Decision","x":"Ship the measurement infrastructure, the measurement flow and the assert mechanics in MMCA.Common.Testing.E2E, default the budget to the Core Web Vitals good band, and let the…"},"862":{"u":"/docs/adr/092-web-vitals-budget-gate.html#rationale","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Rationale","x":"- The good band is an external contract, which is what makes an absolute ceiling defensible here. ADR-060 refused absolute latency because a nanosecond count is a property of the…"},"863":{"u":"/docs/adr/092-web-vitals-budget-gate.html#trade-offs","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- The gate is ui-scoped and may legitimately skip. Both apps gate e2e-gate on a ui change filter (ADC deploy.yml:838, Store :810) and deploy accepts skipped for it (ADC :1230,…"},"864":{"u":"/docs/adr/092-web-vitals-budget-gate.html#revision-2026-10-01","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. One sentence is corrected: the gallery's components test was described as the only case in the whole adoption set that drives an interaction,…"},"865":{"u":"/docs/adr/092-web-vitals-budget-gate.html#related","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-063 (the structural sibling: the same package, the same Playwright suite and the same deploy gate, applied to WCAG 2.1 AA instead of load performance), ADR-060 (the backend…"},"866":{"u":"/docs/adr/093-container-image-posture.html","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records"},"867":{"u":"/docs/adr/093-container-image-posture.html#status","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-23) for the three build decisions below. The two runtime postures below were recorded as undecided at that date: they described what the images did and the…"},"868":{"u":"/docs/adr/093-container-image-posture.html#context","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Context","x":"Eleven Dockerfiles produce every deployable container in the two Azure-hosted applications: six in MMCA.ADC (four services, the Gateway, the Blazor web host) and five in…"},"869":{"u":"/docs/adr/093-container-image-posture.html#decision","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Decision","x":"1. The GitHub Packages credential is a BuildKit secret, never an ARG or ENV. Both applications' nuget.config source-maps MMCA. to GitHub Packages, so every restore inside an…"},"870":{"u":"/docs/adr/093-container-image-posture.html#runtime-postures-closed-2026-09-07","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Runtime postures (closed 2026-09-07)","x":"The base image is pinned by digest. All eleven images name the runtime and the SDK image by sha256, with the 10.0 tag left in place as a readable suffix (ADC…"},"871":{"u":"/docs/adr/093-container-image-posture.html#rationale","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Rationale","x":"- A secret that is never a layer cannot leak from a layer. BuildKit secret mounts are the only mechanism that keeps the credential out of the image, the build cache and docker…"},"872":{"u":"/docs/adr/093-container-image-posture.html#trade-offs","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Trade-offs","x":"- Eleven copies drift independently. There is no shared base Dockerfile and no test that compares them, so a fix applied to one image is applied to one image. The ReadyToRun…"},"873":{"u":"/docs/adr/093-container-image-posture.html#revision-2026-09-07","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The open postures this record listed are closed in both consumers, from the 2026-09-07 security review. 1. Base images are pinned by digest (SEC-ADC-32 / SEC-ADC-54 /…"},"874":{"u":"/docs/adr/093-container-image-posture.html#related","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Related","x":"ADR-038 (supply-chain provenance: it gates the package graph with lock files, a vulnerability audit and an SBOM, and stops at the repository boundary, so the image layers this…"},"875":{"u":"/docs/adr/093-container-image-posture.html#revision-2026-09-03","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Revision (2026-09-03)","x":"Container sizing is uniform, so the ReadyToRun argument applies evenly. Decision 3 justified ReadyToRun by the CPU the replicas land on and cited a split: four ADC apps at 0.25…"},"876":{"u":"/docs/adr/093-container-image-posture.html#revision-2026-09-10","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Revision (2026-09-10)","x":"Locked mode is back on Store's four non-UI images, and the inventory is stated per image rather than by one example. The anchor in Revision (2026-09-07) item 3 pointed at a line…"},"877":{"u":"/docs/adr/093-container-image-posture.html#revision-2026-10-01","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Three statements in Context and decision 4 no longer matched the files and are corrected in place. The digest pair is shared, not per…"},"878":{"u":"/docs/adr/094-client-entity-data-access.html","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records"},"879":{"u":"/docs/adr/094-client-entity-data-access.html#status","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-23). Revised 2026-08-31: GetByIdAsync is recorded with its real signature (id, includeChildren, CancellationToken; a missing entity is a NotFound failure, there…"},"880":{"u":"/docs/adr/094-client-entity-data-access.html#context","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Context","x":"ADR-034 decided the server half of entity data access: a generic controller base with a dynamic query contract, where filters arrive as filters[Property].operator /…"},"881":{"u":"/docs/adr/094-client-entity-data-access.html#decision","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Decision","x":"Client-side entity data access goes through one hand-written base hierarchy in MMCA.Common.UI. - One HTTP root: AuthenticatedServiceBase…"},"882":{"u":"/docs/adr/094-client-entity-data-access.html#rationale","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Rationale","x":"- A hand-written typed base beats a generated client here because the surface is already generic. ADR-034 collapsed N entity endpoints into one shape, so there is exactly one…"},"883":{"u":"/docs/adr/094-client-entity-data-access.html#trade-offs","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- No generated client means drift is caught at runtime, not at build time. A server-side rename of a query parameter or a DTO property does not fail the UI build; it fails the…"},"884":{"u":"/docs/adr/094-client-entity-data-access.html#revision-2026-10-01","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The Blazor Server per-circuit state rule is recorded as a Decision bullet. It was already enforced but not written down: UI assemblies carry no mutable static members outside a…"},"885":{"u":"/docs/adr/094-client-entity-data-access.html#related","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-034 (the server surface this contract calls, and the filter grammar the client constructs), ADR-017 (the server-side filter whose client half is specified here: who mints the…"},"886":{"u":"/docs/adr/095-soft-delete-unique-indexes.html","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records"},"887":{"u":"/docs/adr/095-soft-delete-unique-indexes.html#status","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-23). Revised 2026-08-26 (the convention appends its clause to a hand-authored filter instead of skipping the index)."},"888":{"u":"/docs/adr/095-soft-delete-unique-indexes.html#context","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Context","x":"ADR-005 makes deletion soft: an IAuditableEntity sets IsDeleted = true (MMCA.Common/Source/Core/MMCA.Common.Domain/Interfaces/IAuditableEntity.cs:11) and a named global query…"},"889":{"u":"/docs/adr/095-soft-delete-unique-indexes.html#decision","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Decision","x":"Make the filter a convention: every unique index on a soft-deletable entity excludes deleted rows, automatically, in every context of every consumer. - A model-finalizing…"},"890":{"u":"/docs/adr/095-soft-delete-unique-indexes.html#rationale","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Rationale","x":"- The database should agree with what the application shows. The query filter already says a soft-deleted row does not exist; a unique index that disagrees is the one place the…"},"891":{"u":"/docs/adr/095-soft-delete-unique-indexes.html#trade-offs","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Trade-offs","x":"- It moves schema in consumers, invisibly from the entity configuration. Adopting the convention is a database-contract change: nothing in an entity configuration changed, but…"},"892":{"u":"/docs/adr/095-soft-delete-unique-indexes.html#revision-2026-10-01","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Engine coverage now includes PostgreSQL: SoftDeleteFilterSql.Build emits \"IsDeleted\" = false there, because the flag is a real boolean column,…"},"893":{"u":"/docs/adr/095-soft-delete-unique-indexes.html#related","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Related","x":"ADR-005 (decides soft-delete over erasure and owns the query filter that hides the row, but says nothing about uniqueness: this ADR closes that gap), ADR-057 (the expand/contract…"},"894":{"u":"/docs/adr/096-best-effort-side-effects.html","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records"},"895":{"u":"/docs/adr/096-best-effort-side-effects.html#status","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-23). Revised 2026-08-31. Revised 2026-09-19. Revised 2026-09-25 (the ADR-054 cross-reference is re-anchored onto that record's current best-effort trade-off)."},"896":{"u":"/docs/adr/096-best-effort-side-effects.html#context","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Context","x":"A command that has already committed often has follow-up work attached to it: evict the output-cache entries the write invalidated, broadcast the new state to a live channel,…"},"897":{"u":"/docs/adr/096-best-effort-side-effects.html#decision","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Decision","x":"One framework helper defines the contract, and a swallow that does not go through it is a deliberate, documented exception. - BestEffort.ExecuteAsync(operation, logger, action,…"},"898":{"u":"/docs/adr/096-best-effort-side-effects.html#rationale","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Rationale","x":"- One policy beats five local leniencies. Each feature record is still right about its own degradation; what they could not each decide is the shape of the swallow. A single…"},"899":{"u":"/docs/adr/096-best-effort-side-effects.html#trade-offs","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- Nothing gates use of the helper. There is no fitness rule, analyzer or architecture test that fails a build for a hand-rolled catch (Exception) that should have been a…"},"900":{"u":"/docs/adr/096-best-effort-side-effects.html#revision-2026-10-01","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; the adoption inventory and several statements about it are corrected. Adoption is twelve call sites, not eleven: Store's inventory set path is a…"},"901":{"u":"/docs/adr/096-best-effort-side-effects.html#related","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-024 (push delivery failure is non-fatal and recorded rather than raised, one of the local leniencies this policy generalizes), ADR-026 (eviction is best-effort, and its…"},"902":{"u":"/docs/adr/097-multi-device-refresh-sessions.html","d":"ADR-097: Multi-Device Refresh Sessions (Hashed, Rotating, Per Device)","k":"Architecture Decision Records"},"903":{"u":"/docs/adr/097-multi-device-refresh-sessions.html#status","d":"ADR-097: Multi-Device Refresh Sessions (Hashed, Rotating, Per Device)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-26). Supersedes the storage model of ADR-050 (one plaintext refresh-token column on the user row); the rotation and reuse-detection policy that record decided…"},"904":{"u":"/docs/adr/097-multi-device-refresh-sessions.html#context","d":"ADR-097: Multi-Device Refresh Sessions (Hashed, Rotating, Per Device)","k":"Architecture Decision Records","t":"Context","x":"ADR-050 stores a user's refresh token as a single nullable RefreshToken string plus its RefreshTokenExpiry on the app's User aggregate. That model settles rotation and reuse…"},"905":{"u":"/docs/adr/097-multi-device-refresh-sessions.html#decision","d":"ADR-097: Multi-Device Refresh Sessions (Hashed, Rotating, Per Device)","k":"Architecture Decision Records","t":"Decision","x":"Refresh tokens become rows in their own table: one row per signed-in device, hashed at rest, chained on rotation. - RefreshSession is a flat framework record, not an aggregate.…"},"906":{"u":"/docs/adr/097-multi-device-refresh-sessions.html#rationale","d":"ADR-097: Multi-Device Refresh Sessions (Hashed, Rotating, Per Device)","k":"Architecture Decision Records","t":"Rationale","x":"- A credential at rest is a credential. Hashing is what turns a database read from \"mint tokens for every signed-in user\" into \"hold a list of digests\". The unsalted digest is…"},"907":{"u":"/docs/adr/097-multi-device-refresh-sessions.html#trade-offs","d":"ADR-097: Multi-Device Refresh Sessions (Hashed, Rotating, Per Device)","k":"Architecture Decision Records","t":"Trade-offs","x":"- This is a breaking change with a data migration attached. IAuthUser loses RefreshToken, RefreshTokenExpiry, UpdateRefreshToken and RevokeRefreshToken (IAuthUser.cs:9-14, the…"},"908":{"u":"/docs/adr/097-multi-device-refresh-sessions.html#revision-2026-09-07","d":"ADR-097: Multi-Device Refresh Sessions (Hashed, Rotating, Per Device)","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Credential rotation now ends the session family (SEC-Common-02). A stolen refresh chain otherwise survived the exact remediation a user performs on discovering it: the rotation…"},"909":{"u":"/docs/adr/097-multi-device-refresh-sessions.html#revision-2026-10-01","d":"ADR-097: Multi-Device Refresh Sessions (Hashed, Rotating, Per Device)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; the current-state sections now match the code. Content corrected in place: the session cap is read through a protected virtual…"},"910":{"u":"/docs/adr/097-multi-device-refresh-sessions.html#related","d":"ADR-097: Multi-Device Refresh Sessions (Hashed, Rotating, Per Device)","k":"Architecture Decision Records","t":"Related","x":"ADR-050 (the single-column model this record replaces, and the source of the rotation and reuse-detection policy it keeps), ADR-004 (the stateless RS256 access token this flow…"},"911":{"u":"/docs/adr/098-aspire-orchestration-not-testing-or-dashboards.html","d":"ADR-098: Aspire for Orchestration, Not for Testing or Production Dashboards","k":"Architecture Decision Records"},"912":{"u":"/docs/adr/098-aspire-orchestration-not-testing-or-dashboards.html#status","d":"ADR-098: Aspire for Orchestration, Not for Testing or Production Dashboards","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-28). Revised 2026-08-31: the sanctioned nightly AppHost smoke test is implemented, and every citation below is re-verified against current source. Revised…"},"913":{"u":"/docs/adr/098-aspire-orchestration-not-testing-or-dashboards.html#context","d":"ADR-098: Aspire for Orchestration, Not for Testing or Production Dashboards","k":"Architecture Decision Records","t":"Context","x":"Aspire is used on exactly two surfaces here. Orchestration. Each app has an AppHost that composes the local stack from one file: ADC's provisions a persistent SQL Server…"},"914":{"u":"/docs/adr/098-aspire-orchestration-not-testing-or-dashboards.html#decision","d":"ADR-098: Aspire for Orchestration, Not for Testing or Production Dashboards","k":"Architecture Decision Records","t":"Decision","x":"The tiers that exist keep their shape, and DistributedApplicationTestingBuilder stays out of them. - Per-service tier: one in-process host, real SQL, mocked cross-service edges.…"},"915":{"u":"/docs/adr/098-aspire-orchestration-not-testing-or-dashboards.html#rationale","d":"ADR-098: Aspire for Orchestration, Not for Testing or Production Dashboards","k":"Architecture Decision Records","t":"Rationale","x":"- Test at the boundary that ships. A service is deployed as its own container app (MMCA.ADC/infra/main.bicep:1624, :1851, :2002, :2135), configured entirely through environment…"},"916":{"u":"/docs/adr/098-aspire-orchestration-not-testing-or-dashboards.html#trade-offs","d":"ADR-098: Aspire for Orchestration, Not for Testing or Production Dashboards","k":"Architecture Decision Records","t":"Trade-offs","x":"- Nothing below the E2E lane tests the AppHost's own wiring. A bad reference or a missing environment injection in Program.cs is caught by the E2E gate or by a developer, and the…"},"917":{"u":"/docs/adr/098-aspire-orchestration-not-testing-or-dashboards.html#revision-2026-10-01","d":"ADR-098: Aspire for Orchestration, Not for Testing or Production Dashboards","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Two statements were incomplete and are corrected in place. First, the framework's AppHost testing package is also exercised per PR in…"},"918":{"u":"/docs/adr/098-aspire-orchestration-not-testing-or-dashboards.html#related","d":"ADR-098: Aspire for Orchestration, Not for Testing or Production Dashboards","k":"Architecture Decision Records","t":"Related","x":"ADR-041 (the shared Aspire OpenTelemetry baseline and the sampling / metric-toggle knobs this record's production half configures), ADR-062 (the alert rules and the workbook that…"},"919":{"u":"/docs/adr/099-generic-write-side-entity-commands.html","d":"ADR-099: Generic Write-Side Entity Commands (Create, Update, Delete Without a Handler per Aggregate)","k":"Architecture Decision Records"},"920":{"u":"/docs/adr/099-generic-write-side-entity-commands.html#status","d":"ADR-099: Generic Write-Side Entity Commands (Create, Update, Delete Without a Handler per Aggregate)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-29, framework v1.170.0). Extends ADR-034, which gave every entity a generic read surface plus create and delete, by completing the write half: a generic update…"},"921":{"u":"/docs/adr/099-generic-write-side-entity-commands.html#context","d":"ADR-099: Generic Write-Side Entity Commands (Create, Update, Delete Without a Handler per Aggregate)","k":"Architecture Decision Records","t":"Context","x":"ADR-034 records the read side of the generic resource layer and stops one verb short. Its AggregateRootEntityControllerBase ships [HttpPost] create and [HttpDelete(\"{id}\")]…"},"922":{"u":"/docs/adr/099-generic-write-side-entity-commands.html#decision","d":"ADR-099: Generic Write-Side Entity Commands (Create, Update, Delete Without a Handler per Aggregate)","k":"Architecture Decision Records","t":"Decision","x":"Ship the generic write side as four additive pieces plus a registration helper. 1. The aggregate keeps the mutation, behind one interface. IEntityUpdateApplier…"},"923":{"u":"/docs/adr/099-generic-write-side-entity-commands.html#rationale","d":"ADR-099: Generic Write-Side Entity Commands (Create, Update, Delete Without a Handler per Aggregate)","k":"Architecture Decision Records","t":"Rationale","x":"- The one thing that varies per aggregate is the one thing the module writes. Load, concurrency stamping, NotFound, save, DTO projection, cache invalidation and validation are…"},"924":{"u":"/docs/adr/099-generic-write-side-entity-commands.html#trade-offs","d":"ADR-099: Generic Write-Side Entity Commands (Create, Update, Delete Without a Handler per Aggregate)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The update request is the write contract, so it tracks the aggregate. Exactly the coupling ADR-034 records for the read side, on the other axis: what a caller may change is…"},"925":{"u":"/docs/adr/099-generic-write-side-entity-commands.html#related","d":"ADR-099: Generic Write-Side Entity Commands (Create, Update, Delete Without a Handler per Aggregate)","k":"Architecture Decision Records","t":"Related","x":"ADR-034 (the read side plus create and delete this record completes; its AggregateRootEntityControllerBase is what CrudEntityControllerBase derives from), ADR-083 (the events the…"},"926":{"u":"/docs/adr/099-generic-write-side-entity-commands.html#revision-2026-08-30-the-five-surfaces-that-complete-the-write-side","d":"ADR-099: Generic Write-Side Entity Commands (Create, Update, Delete Without a Handler per Aggregate)","k":"Architecture Decision Records","t":"Revision (2026-08-30): the five surfaces that complete the write side","x":"The decision above shipped the generic update and left the bases where it found them. Reading real write handlers against those bases surfaced five shapes that still forced a…"},"927":{"u":"/docs/adr/099-generic-write-side-entity-commands.html#revision-2026-10-01","d":"ADR-099: Generic Write-Side Entity Commands (Create, Update, Delete Without a Handler per Aggregate)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision changed; two statements in the current-state sections were corrected and stale citations re-anchored. The Rationale claimed the generic update inherits every ADR-014…"},"928":{"u":"/docs/adr/100-outbox-opt-in-resolved-from-messaging-mode.html","d":"ADR-100: The Outbox Is Resolved from the Messaging Mode, Not Always On","k":"Architecture Decision Records"},"929":{"u":"/docs/adr/100-outbox-opt-in-resolved-from-messaging-mode.html#status","d":"ADR-100: The Outbox Is Resolved from the Messaging Mode, Not Always On","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-29, framework v1.170.0). Amends ADR-003 (the outbox runs when the transport needs it, rather than in every host unconditionally) and follows the three-valued…"},"930":{"u":"/docs/adr/100-outbox-opt-in-resolved-from-messaging-mode.html#context","d":"ADR-100: The Outbox Is Resolved from the Messaging Mode, Not Always On","k":"Architecture Decision Records","t":"Context","x":"ADR-003 gives every host a transactional outbox: domain and integration events are written to OutboxMessages in the same transaction as the aggregate changes, OutboxProcessor…"},"931":{"u":"/docs/adr/100-outbox-opt-in-resolved-from-messaging-mode.html#decision","d":"ADR-100: The Outbox Is Resolved from the Messaging Mode, Not Always On","k":"Architecture Decision Records","t":"Decision","x":"Make the outbox a three-valued setting resolved from the transport, gate the components that cost something, keep the schema, and refuse the one combination that cannot work. 1.…"},"932":{"u":"/docs/adr/100-outbox-opt-in-resolved-from-messaging-mode.html#rationale","d":"ADR-100: The Outbox Is Resolved from the Messaging Mode, Not Always On","k":"Architecture Decision Records","t":"Rationale","x":"- The outbox is a transport decision, not a persistence one. Whether an event must survive a process boundary is exactly the question MessageBus:Provider already answers, so…"},"933":{"u":"/docs/adr/100-outbox-opt-in-resolved-from-messaging-mode.html#trade-offs","d":"ADR-100: The Outbox Is Resolved from the Messaging Mode, Not Always On","k":"Architecture Decision Records","t":"Trade-offs","x":"- The default for an in-process host changed. A host on InProcess messaging (or with no MessageBus section at all, which resolves to the same provider) no longer runs the durable…"},"934":{"u":"/docs/adr/100-outbox-opt-in-resolved-from-messaging-mode.html#revision-2026-10-01","d":"ADR-100: The Outbox Is Resolved from the Messaging Mode, Not Always On","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Re-anchored the startup-guard citations in Decision 4 (DependencyInjection.Messaging.cs:196, guard :198, throw :200-201) and the…"},"935":{"u":"/docs/adr/100-outbox-opt-in-resolved-from-messaging-mode.html#related","d":"ADR-100: The Outbox Is Resolved from the Messaging Mode, Not Always On","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox itself: the dual-dispatch contract, the processor, the retry and dead-letter policy, all unchanged for a host that runs it), ADR-021 (the consumer-side…"},"936":{"u":"/docs/adr/101-common-metapackage.html","d":"ADR-101: An MMCA.Common Metapackage for the Core Six","k":"Architecture Decision Records"},"937":{"u":"/docs/adr/101-common-metapackage.html#status","d":"ADR-101: An MMCA.Common Metapackage for the Core Six","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-29, framework v1.170.0). Extends ADR-053 (the metapackage ships to both registries from the same tag, through the same workflow) and inherits ADR-016 unchanged…"},"938":{"u":"/docs/adr/101-common-metapackage.html#context","d":"ADR-101: An MMCA.Common Metapackage for the Core Six","k":"Architecture Decision Records","t":"Context","x":"ADR-053 made the install path credential-free: dotnet add package MMCA.Common.API works for anyone, from nuget.org, with no token and no hand-written nuget.config. What it did…"},"939":{"u":"/docs/adr/101-common-metapackage.html#decision","d":"ADR-101: An MMCA.Common Metapackage for the Core Six","k":"Architecture Decision Records","t":"Decision","x":"Publish a metapackage named MMCA.Common that carries dependencies and no code. 1. It bundles the Core 6, in layer order. Source/MMCA.Common/MMCA.Common.csproj:26-31 references…"},"940":{"u":"/docs/adr/101-common-metapackage.html#rationale","d":"ADR-101: An MMCA.Common Metapackage for the Core Six","k":"Architecture Decision Records","t":"Rationale","x":"- The common case should cost one line. Six references is not hard, it is repetitive and order-independent noise that a reader has to verify rather than read, and getting it…"},"941":{"u":"/docs/adr/101-common-metapackage.html#trade-offs","d":"ADR-101: An MMCA.Common Metapackage for the Core Six","k":"Architecture Decision Records","t":"Trade-offs","x":"- A consumer mixing both forms has more pins to keep in step, not fewer. An app taking MMCA.Common plus MMCA.Common.UI and the Testing. set still has to sweep every entry to the…"},"942":{"u":"/docs/adr/101-common-metapackage.html#related","d":"ADR-101: An MMCA.Common Metapackage for the Core Six","k":"Architecture Decision Records","t":"Related","x":"ADR-053 (dual-registry publishing, which the metapackage joins with no workflow change), ADR-016 (one version for every package, one sweep per consumer: the property that makes…"},"943":{"u":"/docs/adr/102-pbkdf2-only-password-hashing.html","d":"ADR-102: PBKDF2-Only Password Hashing","k":"Architecture Decision Records"},"944":{"u":"/docs/adr/102-pbkdf2-only-password-hashing.html#status","d":"ADR-102: PBKDF2-Only Password Hashing","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-31). Supersedes ADR-032. Revised 2026-09-11: verification now carries a canonical-material length guard (it rejects any stored hash or salt that is not exactly…"},"945":{"u":"/docs/adr/102-pbkdf2-only-password-hashing.html#context","d":"ADR-102: PBKDF2-Only Password Hashing","k":"Architecture Decision Records","t":"Context","x":"ADR-032 recorded a hasher with two verification paths: PBKDF2-HMAC-SHA512 for new credentials, and an HMAC-SHA512 recompute for rows written under an older scheme, selected at…"},"946":{"u":"/docs/adr/102-pbkdf2-only-password-hashing.html#decision","d":"ADR-102: PBKDF2-Only Password Hashing","k":"Architecture Decision Records","t":"Decision","x":"Hashing and verification are PBKDF2-only. There is one framework IPasswordHasher and one code path through it, in both directions. - One interface, one implementation, one…"},"947":{"u":"/docs/adr/102-pbkdf2-only-password-hashing.html#rationale","d":"ADR-102: PBKDF2-Only Password Hashing","k":"Architecture Decision Records","t":"Rationale","x":"- A verification path that serves no rows is pure risk. With the production stores confirmed free of legacy-format credentials, the branch could only ever be reached by a…"},"948":{"u":"/docs/adr/102-pbkdf2-only-password-hashing.html#trade-offs","d":"ADR-102: PBKDF2-Only Password Hashing","k":"Architecture Decision Records","t":"Trade-offs","x":"- A legacy-format row that reaches production now fails verification. Restoring an old backup, importing a credential from an external system in the legacy shape, or missing a…"},"949":{"u":"/docs/adr/102-pbkdf2-only-password-hashing.html#revision-2026-10-01","d":"ADR-102: PBKDF2-Only Password Hashing","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Anchor refresh only; no decision or rationale changed. The Decision's citations are re-anchored to current source: the TryAddSingleton registration (DependencyInjection.cs:304),…"},"950":{"u":"/docs/adr/102-pbkdf2-only-password-hashing.html#related","d":"ADR-102: PBKDF2-Only Password Hashing","k":"Architecture Decision Records","t":"Related","x":"ADR-032 (the superseded record: the same hasher, its parameters and its call-site hoisting, plus the legacy-compatibility design this reverses), ADR-004 (cross-service JWT / JWKS…"},"951":{"u":"/docs/adr/103-bunit-component-test-tier.html","d":"ADR-103: bUnit Component-Test Tier as a Shipped Package","k":"Architecture Decision Records"},"952":{"u":"/docs/adr/103-bunit-component-test-tier.html#status","d":"ADR-103: bUnit Component-Test Tier as a Shipped Package","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-31). Revised 2026-09-03: one trade-off overstated how far the AngleSharp advisory pin travels. See Revision (2026-09-03) at the end. Revised 2026-09-19: four…"},"953":{"u":"/docs/adr/103-bunit-component-test-tier.html#context","d":"ADR-103: bUnit Component-Test Tier as a Shipped Package","k":"Architecture Decision Records","t":"Context","x":"Three test tiers in this workspace are decided in writing and one is not. ADR-015 gates structure with NetArchTest. ADR-058 ships the runtime conformance suites as abstract bases…"},"954":{"u":"/docs/adr/103-bunit-component-test-tier.html#decision","d":"ADR-103: bUnit Component-Test Tier as a Shipped Package","k":"Architecture Decision Records","t":"Decision","x":"Ship the component-test tier as a package. MMCA.Common.Testing.UI (MMCA.Common/Source/Hosting/MMCA.Common.Testing.UI/MMCA.Common.Testing.UI.csproj:3-4) is one of the MMCA.Common.…"},"955":{"u":"/docs/adr/103-bunit-component-test-tier.html#rationale","d":"ADR-103: bUnit Component-Test Tier as a Shipped Package","k":"Architecture Decision Records","t":"Rationale","x":"- The setup is what a component test gets wrong, so the setup is what the framework should own. Every item in the Decision is a choice with one correct answer per repo and a…"},"956":{"u":"/docs/adr/103-bunit-component-test-tier.html#trade-offs","d":"ADR-103: bUnit Component-Test Tier as a Shipped Package","k":"Architecture Decision Records","t":"Trade-offs","x":"- Loose JSInterop proves nothing about JS. A component test can render a component that calls a JS module which does not exist, because the loose mode answers with defaults…"},"957":{"u":"/docs/adr/103-bunit-component-test-tier.html#related","d":"ADR-103: bUnit Component-Test Tier as a Shipped Package","k":"Architecture Decision Records","t":"Related","x":"ADR-058 (the runtime conformance tier this sits below: same delivery shape, different question, and its bases need a booted host where these need only a renderer), ADR-063 and…"},"958":{"u":"/docs/adr/103-bunit-component-test-tier.html#revision-2026-09-03","d":"ADR-103: bUnit Component-Test Tier as a Shipped Package","k":"Architecture Decision Records","t":"Revision (2026-09-03)","x":"The decision and the mechanism are unchanged. One trade-off's premise was wrong. \"The bUnit version is pinned per repo\" said the AngleSharp advisory pin has to be repeated across…"},"959":{"u":"/docs/adr/103-bunit-component-test-tier.html#revision-2026-09-19","d":"ADR-103: bUnit Component-Test Tier as a Shipped Package","k":"Architecture Decision Records","t":"Revision (2026-09-19)","x":"The decision and the mechanism are unchanged. Four facts had drifted since the record was written. The bunit line is now pinned at 2.11.3, not 2.9.0, and all three central…"},"960":{"u":"/docs/adr/103-bunit-component-test-tier.html#revision-2026-10-01","d":"ADR-103: bUnit Component-Test Tier as a Shipped Package","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The authorization mechanism changed; the rest of the decision is unchanged. The base no longer registers a permissive IsAuthenticatedAuthorizationService. After…"},"961":{"u":"/docs/adr/104-smart-enums-as-opt-in-capability.html","d":"ADR-104: Plain Enums by Default, Enumeration as the Opt-In Smart-Enum Base","k":"Architecture Decision Records"},"962":{"u":"/docs/adr/104-smart-enums-as-opt-in-capability.html#status","d":"ADR-104: Plain Enums by Default, Enumeration as the Opt-In Smart-Enum Base","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-31). Revised 2026-09-09: ADR-115 copies this record's posture verbatim for strongly typed identifiers: shipped, tested, wired into JSON, EF Core, MVC binding…"},"963":{"u":"/docs/adr/104-smart-enums-as-opt-in-capability.html#context","d":"ADR-104: Plain Enums by Default, Enumeration as the Opt-In Smart-Enum Base","k":"Architecture Decision Records","t":"Context","x":"A bounded set of named values shows up everywhere in this workspace: the state that triggered a domain event, an error category, a rate-limiting algorithm, a message-bus…"},"964":{"u":"/docs/adr/104-smart-enums-as-opt-in-capability.html#decision","d":"ADR-104: Plain Enums by Default, Enumeration as the Opt-In Smart-Enum Base","k":"Architecture Decision Records","t":"Decision","x":"A plain C enum is the default for a bounded set. Enumeration is the opt-in base for a set whose members need behavior or data, and nothing in the framework pushes a consumer…"},"965":{"u":"/docs/adr/104-smart-enums-as-opt-in-capability.html#rationale","d":"ADR-104: Plain Enums by Default, Enumeration as the Opt-In Smart-Enum Base","k":"Architecture Decision Records","t":"Rationale","x":"- The default should be the cheap type. A CLR enum costs nothing at a call boundary, needs no serializer or EF registration, and is what a .NET reader expects for a set of names.…"},"966":{"u":"/docs/adr/104-smart-enums-as-opt-in-capability.html#trade-offs","d":"ADR-104: Plain Enums by Default, Enumeration as the Opt-In Smart-Enum Base","k":"Architecture Decision Records","t":"Trade-offs","x":"- A capability nobody uses is a capability nobody has stress-tested. The contract is pinned by 33 test methods against private fixtures, not by a production set with real…"},"967":{"u":"/docs/adr/104-smart-enums-as-opt-in-capability.html#revision-2026-10-01","d":"ADR-104: Plain Enums by Default, Enumeration as the Opt-In Smart-Enum Base","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. The remark on Enumeration now states the same three assertions the Context lists and says the fitness rule names no record and inspects no…"},"968":{"u":"/docs/adr/104-smart-enums-as-opt-in-capability.html#related","d":"ADR-104: Plain Enums by Default, Enumeration as the Opt-In Smart-Enum Base","k":"Architecture Decision Records","t":"Related","x":"ADR-037 (the precedent for recording a shipped, tested and unadopted framework capability as a decision rather than leaving it to read as unfinished adoption), ADR-068 (the seven…"},"969":{"u":"/docs/adr/105-data-residency-build-gate.html","d":"ADR-105: The Published Data-Residency Claim as a Build Gate","k":"Architecture Decision Records"},"970":{"u":"/docs/adr/105-data-residency-build-gate.html#status","d":"ADR-105: The Published Data-Residency Claim as a Build Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-01). Revised 2026-10-01 (region matching is a whole-token comparison rather than plain containment; see Revision below)."},"971":{"u":"/docs/adr/105-data-residency-build-gate.html#context","d":"ADR-105: The Published Data-Residency Claim as a Build Gate","k":"Architecture Decision Records","t":"Context","x":"Both deployed apps publish a privacy policy at their repo root, and each has a section that tells a user where their personal data is stored (MMCA.ADC/PRIVACY.md:61,…"},"972":{"u":"/docs/adr/105-data-residency-build-gate.html#decision","d":"ADR-105: The Published Data-Residency Claim as a Build Gate","k":"Architecture Decision Records","t":"Decision","x":"The published data-residency claim is a build-gated assertion. A shared fitness base parses the region where a repo actually provisions its PII-bearing storage from that repo's…"},"973":{"u":"/docs/adr/105-data-residency-build-gate.html#rationale","d":"ADR-105: The Published Data-Residency Claim as a Build Gate","k":"Architecture Decision Records","t":"Rationale","x":"- The statement with the most legal weight had the least enforcement. Every other compliance claim in these repos is backed by code a rule can inspect: erasure by IAnonymizable,…"},"974":{"u":"/docs/adr/105-data-residency-build-gate.html#trade-offs","d":"ADR-105: The Published Data-Residency Claim as a Build Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- It proves the policy agrees with a file, not with Azure. Both extractors read committed text. A database provisioned by hand into another region, a restore into a different…"},"975":{"u":"/docs/adr/105-data-residency-build-gate.html#revision-2026-10-01","d":"ADR-105: The Published Data-Residency Claim as a Build Gate","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Region matching moved from plain containment to a whole-token comparison. The protected static ContainsRegionClaim…"},"976":{"u":"/docs/adr/105-data-residency-build-gate.html#related","d":"ADR-105: The Published Data-Residency Claim as a Build Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-009 (the single-region acceptance a consumer must declare: this record is what keeps the published claim honest about which region that is), ADR-005 (the erasure path guarded…"},"977":{"u":"/docs/adr/106-extension-members-as-public-di-surface.html","d":"ADR-106: C# Extension Members as the Public DI Registration Surface","k":"Architecture Decision Records"},"978":{"u":"/docs/adr/106-extension-members-as-public-di-surface.html#status","d":"ADR-106: C# Extension Members as the Public DI Registration Surface","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-01; counts re-measured 2026-10-01)."},"979":{"u":"/docs/adr/106-extension-members-as-public-di-surface.html#context","d":"ADR-106: C# Extension Members as the Public DI Registration Surface","k":"Architecture Decision Records","t":"Context","x":"Every host in this workspace boots the same way: a Program.cs calls a short list of Add methods on IServiceCollection, one per layer, and all of the framework's wiring sits…"},"980":{"u":"/docs/adr/106-extension-members-as-public-di-surface.html#decision","d":"ADR-106: C# Extension Members as the Public DI Registration Surface","k":"Architecture Decision Records","t":"Decision","x":"The framework's public dependency-injection surface is written as C extension members: extension(T) blocks inside public static class types, compiled under LangVersion preview in…"},"981":{"u":"/docs/adr/106-extension-members-as-public-di-surface.html#rationale","d":"ADR-106: C# Extension Members as the Public DI Registration Surface","k":"Architecture Decision Records","t":"Rationale","x":"- One Add name per layer is the point of the surface. A host reads as a list of layers (Program.cs:78, :79, :101, :132), and grouping the registrations by receiver in a single…"},"982":{"u":"/docs/adr/106-extension-members-as-public-di-surface.html#trade-offs","d":"ADR-106: C# Extension Members as the Public DI Registration Surface","k":"Architecture Decision Records","t":"Trade-offs","x":"- A preview language feature under a floating SDK is a moving target. No global.json pins an SDK version and CI installs dotnet-version: '10.0.x'…"},"983":{"u":"/docs/adr/106-extension-members-as-public-di-surface.html#revision-2026-10-01","d":"ADR-106: C# Extension Members as the Public DI Registration Surface","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed: the framework's DI surface is still written as extension(T) blocks compiled under LangVersion preview, and both emitted shapes are still…"},"984":{"u":"/docs/adr/106-extension-members-as-public-di-surface.html#related","d":"ADR-106: C# Extension Members as the Public DI Registration Surface","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (the RS0016/RS0017 baseline that freezes both emitted shapes of every extension member, and the fitness-rule tier that had to learn about ExtensionMarkerAttribute),…"},"985":{"u":"/docs/adr/107-transaction-execution-contract.html","d":"ADR-107: Transaction Execution and Commit-Ambiguity Contract","k":"Architecture Decision Records"},"986":{"u":"/docs/adr/107-transaction-execution-contract.html#status","d":"ADR-107: Transaction Execution and Commit-Ambiguity Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-03; adoption counts and consumer state re-measured 2026-09-19). Revised 2026-09-25 (MMCA.ADC's ITransactional count corrected to six, adding…"},"987":{"u":"/docs/adr/107-transaction-execution-contract.html#context","d":"ADR-107: Transaction Execution and Commit-Ambiguity Contract","k":"Architecture Decision Records","t":"Context","x":"Every transactional write in this workspace funnels through one method. IUnitOfWork.ExecuteInTransactionAsync…"},"988":{"u":"/docs/adr/107-transaction-execution-contract.html#decision","d":"ADR-107: Transaction Execution and Commit-Ambiguity Contract","k":"Architecture Decision Records","t":"Decision","x":"ExecuteInTransactionAsync is a re-entrant, retriable, commit-once unit: the whole delegate is the retriable work, the commit phase is deliberately outside the retry, and a commit…"},"989":{"u":"/docs/adr/107-transaction-execution-contract.html#rationale","d":"ADR-107: Transaction Execution and Commit-Ambiguity Contract","k":"Architecture Decision Records","t":"Rationale","x":"- Retrying the operation is safe; retrying the commit is not. A transient failure before the commit leaves nothing durable, so re-running the delegate against a cleared change…"},"990":{"u":"/docs/adr/107-transaction-execution-contract.html#trade-offs","d":"ADR-107: Transaction Execution and Commit-Ambiguity Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- The ambiguity is a hard failure the caller must handle, and today nobody catches it. A host with no [Idempotent] coverage on the affected endpoint surfaces it as an unhandled…"},"991":{"u":"/docs/adr/107-transaction-execution-contract.html#revision-2026-10-01","d":"ADR-107: Transaction Execution and Commit-Ambiguity Contract","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The commit phase gained a pre-commit step. Since v1.215.0 (MMCA.Common/CHANGELOG.md:53), a successful attempt runs FlushEnrolledCommandsBeforeCommitAsync before TryCommit…"},"992":{"u":"/docs/adr/107-transaction-execution-contract.html#related","d":"ADR-107: Transaction Execution and Commit-Ambiguity Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-014 (the Transactional decorator that is the main caller, and whose 2026-07-19 revision decided the failed-Result rollback this record implements), ADR-006…"},"993":{"u":"/docs/adr/108-distributed-lock-primitive.html","d":"ADR-108: Cross-Replica Mutual Exclusion via IDistributedLock","k":"Architecture Decision Records"},"994":{"u":"/docs/adr/108-distributed-lock-primitive.html#status","d":"ADR-108: Cross-Replica Mutual Exclusion via IDistributedLock","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-03). Revised 2026-09-19 (the second consumer moved: ADC's AI scoring pass now runs as the internal command ScoreEventSessionsInternalCommandHandler under…"},"995":{"u":"/docs/adr/108-distributed-lock-primitive.html#context","d":"ADR-108: Cross-Replica Mutual Exclusion via IDistributedLock","k":"Architecture Decision Records","t":"Context","x":"Both deployed apps run more than one replica of every service. ADC's Conference container app is declared with minReplicas: 1, maxReplicas: 2 (MMCA.ADC/infra/main.bicep:1851,…"},"996":{"u":"/docs/adr/108-distributed-lock-primitive.html#decision","d":"ADR-108: Cross-Replica Mutual Exclusion via IDistributedLock","k":"Architecture Decision Records","t":"Decision","x":"The framework ships one cross-replica mutual-exclusion primitive, IDistributedLock: a non-reentrant, TTL-bounded, explicitly best-effort lock with an owner-scoped idempotent…"},"997":{"u":"/docs/adr/108-distributed-lock-primitive.html#rationale","d":"ADR-108: Cross-Replica Mutual Exclusion via IDistributedLock","k":"Architecture Decision Records","t":"Rationale","x":"- The degraded mode had to be visible, not silent. Registering nothing when Redis is absent would push a null check onto every caller, and registering a no-op lock would make a…"},"998":{"u":"/docs/adr/108-distributed-lock-primitive.html#trade-offs","d":"ADR-108: Cross-Replica Mutual Exclusion via IDistributedLock","k":"Architecture Decision Records","t":"Trade-offs","x":"- Failover can hand the same key to two holders. The lock inherits Redis's failover behavior (RedisDistributedLock.cs:19-23), so a primary loss between acquire and replicate is a…"},"999":{"u":"/docs/adr/108-distributed-lock-primitive.html#revision-2026-10-01","d":"ADR-108: Cross-Replica Mutual Exclusion via IDistributedLock","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Two statements no longer matched the code. First, adoption grew to four call sites: the framework's own PasswordResetTokenService takes IDistributedLock as a constructor…"},"1000":{"u":"/docs/adr/108-distributed-lock-primitive.html#related","d":"ADR-108: Cross-Replica Mutual Exclusion via IDistributedLock","k":"Architecture Decision Records","t":"Related","x":"ADR-017 (the HTTP idempotency filter, the first consumer, whose 409-on-lock-miss this record explains), ADR-026 (the cache registration this lock is registered beside, and the…"},"1001":{"u":"/docs/adr/109-feature-by-folder-convention.html","d":"ADR-109: Feature-by-Folder Layout as an Enforced Convention","k":"Architecture Decision Records"},"1002":{"u":"/docs/adr/109-feature-by-folder-convention.html#status","d":"ADR-109: Feature-by-Folder Layout as an Enforced Convention","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-03; folder inventory and IDE0130 exception list refreshed 2026-09-19)."},"1003":{"u":"/docs/adr/109-feature-by-folder-convention.html#context","d":"ADR-109: Feature-by-Folder Layout as an Enforced Convention","k":"Architecture Decision Records","t":"Context","x":"The rubric's section 5 asks that \"code is organized by feature/capability, so a change touches one cohesive slice\", with features \"grouped by use case ..., not by horizontal…"},"1004":{"u":"/docs/adr/109-feature-by-folder-convention.html#decision","d":"ADR-109: Feature-by-Folder Layout as an Enforced Convention","k":"Architecture Decision Records","t":"Decision","x":"Folder structure is a governed architectural property, not a matter of taste: an aggregate or feature names a folder, a folder holds at most twelve direct code files, namespaces…"},"1005":{"u":"/docs/adr/109-feature-by-folder-convention.html#rationale","d":"ADR-109: Feature-by-Folder Layout as an Enforced Convention","k":"Architecture Decision Records","t":"Rationale","x":"- A wide folder has stopped naming a feature. The rule's own statement of intent is that a folder \"that accumulates dozens of direct code files has stopped naming a feature and…"},"1006":{"u":"/docs/adr/109-feature-by-folder-convention.html#trade-offs","d":"ADR-109: Feature-by-Folder Layout as an Enforced Convention","k":"Architecture Decision Records","t":"Trade-offs","x":"- Layout changes are public API breaks on a lockstep package family. Because the namespace is the folder, a reorganization is a breaking release for every consumer (ADR-016),…"},"1007":{"u":"/docs/adr/109-feature-by-folder-convention.html#revision-2026-10-01","d":"ADR-109: Feature-by-Folder Layout as an Enforced Convention","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Citations were re-anchored: the rubric section 5 text (ArchitectureEvaluationCriteria.md:211, :214), the v1.183.0 and v1.184.0 changelog entries…"},"1008":{"u":"/docs/adr/109-feature-by-folder-convention.html#related","d":"ADR-109: Feature-by-Folder Layout as an Enforced Convention","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (the fitness-function tier this rule joins, and the public-API baseline that records FolderWidthTestsBase), ADR-016 (lockstep versioning: a layout change lands in every…"},"1009":{"u":"/docs/adr/110-rubric-v2-category-realignment.html","d":"ADR-110: Rubric Version 2, Category Realignment at 34","k":"Architecture Decision Records"},"1010":{"u":"/docs/adr/110-rubric-v2-category-realignment.html#status","d":"ADR-110: Rubric Version 2, Category Realignment at 34","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-04; section 16 scope corrected 2026-09-04: MMCA.ADC scores the category, recorded in ADR-111; content refreshed 2026-09-19: MMCA.Common now scores section 16,…"},"1011":{"u":"/docs/adr/110-rubric-v2-category-realignment.html#context","d":"ADR-110: Rubric Version 2, Category Realignment at 34","k":"Architecture Decision Records","t":"Context","x":"The 34-category Architecture Evaluation Criteria (Website/docs-src/governance/ArchitectureEvaluationCriteria.md) is the only basis on which the three scorecards are scored, and…"},"1012":{"u":"/docs/adr/110-rubric-v2-category-realignment.html#decision","d":"ADR-110: Rubric Version 2, Category Realignment at 34","k":"Architecture Decision Records","t":"Decision","x":"The rubric is versioned, and version 2 keeps 34 categories with stable numbering by replacing the two overlap-heavy categories in place and adding criteria to eleven others. 1.…"},"1013":{"u":"/docs/adr/110-rubric-v2-category-realignment.html#rationale","d":"ADR-110: Rubric Version 2, Category Realignment at 34","k":"Architecture Decision Records","t":"Rationale","x":"Replacing in place preserves every NN citation in the backlogs, every §NN in the ADRs and the scorecard history, the command document's Part A/B/C dispatch split, and the literal…"},"1014":{"u":"/docs/adr/110-rubric-v2-category-realignment.html#trade-offs","d":"ADR-110: Rubric Version 2, Category Realignment at 34","k":"Architecture Decision Records","t":"Trade-offs","x":"- The index trend line carries a denominator change. The scorecard headers and the backlog headers record both the old and the new fractions for the cycle where it happened. -…"},"1015":{"u":"/docs/adr/111-ai-session-scoring-governance.html","d":"ADR-111: AI Session Scoring Governance","k":"Architecture Decision Records"},"1016":{"u":"/docs/adr/111-ai-session-scoring-governance.html#status","d":"ADR-111: AI Session Scoring Governance","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-04). Extended by ADR-120 (2026-09-11): the rules here that belong to calling a model rather than to scoring a session (a bounded call, a versioned and hashed…"},"1017":{"u":"/docs/adr/111-ai-session-scoring-governance.html#context","d":"ADR-111: AI Session Scoring Governance","k":"Architecture Decision Records","t":"Context","x":"MMCA.ADC ships one product feature that calls a language model. An organizer, looking at the session selection dashboard for an event, can trigger an AI scoring pass that rates…"},"1018":{"u":"/docs/adr/111-ai-session-scoring-governance.html#decision","d":"ADR-111: AI Session Scoring Governance","k":"Architecture Decision Records","t":"Decision","x":"A product feature that calls a model is governed like any other production dependency: the call sits behind a port, the model and the prompt are versioned and persisted with…"},"1019":{"u":"/docs/adr/111-ai-session-scoring-governance.html#rationale","d":"ADR-111: AI Session Scoring Governance","k":"Architecture Decision Records","t":"Rationale","x":"- A version you do not persist is not a version. The prompt version is worth something only because it is on the row next to the score. An organizer looking at a number two…"},"1020":{"u":"/docs/adr/111-ai-session-scoring-governance.html#trade-offs","d":"ADR-111: AI Session Scoring Governance","k":"Architecture Decision Records","t":"Trade-offs","x":"- Recorded-response replay cannot see model drift. Every free-tier case answers from a response captured at a point in time, so the tier that runs on every deploy is blind to…"},"1021":{"u":"/docs/adr/111-ai-session-scoring-governance.html#revision-2026-10-01","d":"ADR-111: AI Session Scoring Governance","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The guardrails are now one module-owned composition rather than a single framework registration. AddConferenceAiGuardrails registers the framework's PiiRedactionGuardrail and…"},"1022":{"u":"/docs/adr/111-ai-session-scoring-governance.html#related","d":"ADR-111: AI Session Scoring Governance","k":"Architecture Decision Records","t":"Related","x":"ADR-110 (the rubric category this record answers: section 16, AI-Native Application Architecture, scored for MMCA.ADC because of this feature), ADR-061 (the Key Vault reference…"},"1023":{"u":"/docs/adr/112-catalog-owned-effective-pricing.html","d":"ADR-112: Catalog Owns Effective Pricing; Sales Snapshots It at Checkout","k":"Architecture Decision Records"},"1024":{"u":"/docs/adr/112-catalog-owned-effective-pricing.html#status","d":"ADR-112: Catalog Owns Effective Pricing; Sales Snapshots It at Checkout","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-07). Revised 2026-09-07: an order line freezes the list price and the promotion label beside the charged unit price, so an order answers what it saved without…"},"1025":{"u":"/docs/adr/112-catalog-owned-effective-pricing.html#context","d":"ADR-112: Catalog Owns Effective Pricing; Sales Snapshots It at Checkout","k":"Architecture Decision Records","t":"Context","x":"MMCA.Store sells product variants, and a variant's price is one Money on the variant row…"},"1026":{"u":"/docs/adr/112-catalog-owned-effective-pricing.html#decision","d":"ADR-112: Catalog Owns Effective Pricing; Sales Snapshots It at Checkout","k":"Architecture Decision Records","t":"Decision","x":"Promotional pricing is a variant-level owned value object, not an aggregate. VariantDiscount…"},"1027":{"u":"/docs/adr/112-catalog-owned-effective-pricing.html#rationale","d":"ADR-112: Catalog Owns Effective Pricing; Sales Snapshots It at Checkout","k":"Architecture Decision Records","t":"Rationale","x":"A discount that belongs to exactly one variant, is replaced rather than edited, and has no life of its own once the variant is gone is a value object by every test we apply…"},"1028":{"u":"/docs/adr/112-catalog-owned-effective-pricing.html#trade-offs","d":"ADR-112: Catalog Owns Effective Pricing; Sales Snapshots It at Checkout","k":"Architecture Decision Records","t":"Trade-offs","x":"- Cached reads lag a window boundary by up to 60 seconds. The three write commands implement ICacheInvalidating and their controllers evict the catalog:products output-cache tag,…"},"1029":{"u":"/docs/adr/112-catalog-owned-effective-pricing.html#revision-2026-10-01","d":"ADR-112: Catalog Owns Effective Pricing; Sales Snapshots It at Checkout","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Two statements no longer matched the code and are corrected in place; the decision itself is unchanged. The products output-cache policy now expires after 60 seconds rather than…"},"1030":{"u":"/docs/adr/112-catalog-owned-effective-pricing.html#related","d":"ADR-112: Catalog Owns Effective Pricing; Sales Snapshots It at Checkout","k":"Architecture Decision Records","t":"Related","x":"- ADR-005, ADR-006: each service owns its database, and cross-service copies are denormalized rather than joined. - ADR-007: [ServiceContract] purity, the rule that keeps the…"},"1031":{"u":"/docs/adr/113-postgresql-as-a-first-class-engine.html","d":"ADR-113: PostgreSQL as a First-Class Engine","k":"Architecture Decision Records"},"1032":{"u":"/docs/adr/113-postgresql-as-a-first-class-engine.html#status","d":"ADR-113: PostgreSQL as a First-Class Engine","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-09). Extends ADR-006 (one sealed context class per engine) and ADR-018 (engine as a routing decision) with a fourth engine. Revised 2026-10-01 (the Helpdesk…"},"1033":{"u":"/docs/adr/113-postgresql-as-a-first-class-engine.html#context","d":"ADR-113: PostgreSQL as a First-Class Engine","k":"Architecture Decision Records","t":"Context","x":"The framework has shipped three database engines since its first release: SQL Server, Azure Cosmos DB and SQLite. Every one of them is reached the same way, because ADR-006 fixed…"},"1034":{"u":"/docs/adr/113-postgresql-as-a-first-class-engine.html#decision","d":"ADR-113: PostgreSQL as a First-Class Engine","k":"Architecture Decision Records","t":"Decision","x":"PostgreSQL is added as a fourth first-class engine, mirroring the SQL Server footprint one for one, and differing from it only where the server forces a difference. 1. The engine…"},"1035":{"u":"/docs/adr/113-postgresql-as-a-first-class-engine.html#rationale","d":"ADR-113: PostgreSQL as a First-Class Engine","k":"Architecture Decision Records","t":"Rationale","x":"The shape follows from the alternatives that were weighed and declined: - A snakecase naming convention by default. Rejected. It is the PostgreSQL community norm, but it would…"},"1036":{"u":"/docs/adr/113-postgresql-as-a-first-class-engine.html#trade-offs","d":"ADR-113: PostgreSQL as a First-Class Engine","k":"Architecture Decision Records","t":"Trade-offs","x":"- Every consumer's package graph gains Npgsql.EntityFrameworkCore.PostgreSQL. It is referenced unconditionally from MMCA.Common.Infrastructure, exactly as the Cosmos, SQLite and…"},"1037":{"u":"/docs/adr/113-postgresql-as-a-first-class-engine.html#revision-2026-10-01","d":"ADR-113: PostgreSQL as a First-Class Engine","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The two rollout follow-ups are no longer plans. The Helpdesk canary ships as the advisory postgresql-canary job (MMCA.Helpdesk/.github/workflows/ci.yml:146-155,…"},"1038":{"u":"/docs/adr/113-postgresql-as-a-first-class-engine.html#related","d":"ADR-113: PostgreSQL as a First-Class Engine","k":"Architecture Decision Records","t":"Related","x":"ADR-006 (one sealed context per engine, one instance per database), ADR-018 (the engine axis this record adds a member to), ADR-035 (the application-managed RowVersion token…"},"1039":{"u":"/docs/adr/114-internal-commands-durable-job-queue.html","d":"ADR-114: Internal Commands, a Job Queue Built on the Outbox's Machinery","k":"Architecture Decision Records"},"1040":{"u":"/docs/adr/114-internal-commands-durable-job-queue.html#status","d":"ADR-114: Internal Commands, a Job Queue Built on the Outbox's Machinery","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-09). Rides the outbox machinery of ADR-003 without extending it. Revised 2026-09-19 (three corrections to match the code: a schedule outside a transaction…"},"1041":{"u":"/docs/adr/114-internal-commands-durable-job-queue.html#context","d":"ADR-114: Internal Commands, a Job Queue Built on the Outbox's Machinery","k":"Architecture Decision Records","t":"Context","x":"The framework has had two ways to move work off the request thread and neither of them is a job queue. The transactional outbox (ADR-003) carries events. OutboxMessage…"},"1042":{"u":"/docs/adr/114-internal-commands-durable-job-queue.html#decision","d":"ADR-114: Internal Commands, a Job Queue Built on the Outbox's Machinery","k":"Architecture Decision Records","t":"Decision","x":"Deferred work is an ordinary CQRS command, scheduled into a per-source InternalCommands table and executed later through the normal decorator pipeline. Five parts carry that. 1.…"},"1043":{"u":"/docs/adr/114-internal-commands-durable-job-queue.html#rationale","d":"ADR-114: Internal Commands, a Job Queue Built on the Outbox's Machinery","k":"Architecture Decision Records","t":"Rationale","x":"Four alternatives were weighed, and each one gave up the atomicity guarantee or duplicated a vocabulary the framework already owns: Hangfire. It is the obvious answer and it…"},"1044":{"u":"/docs/adr/114-internal-commands-durable-job-queue.html#trade-offs","d":"ADR-114: Internal Commands, a Job Queue Built on the Outbox's Machinery","k":"Architecture Decision Records","t":"Trade-offs","x":"Consumers take one migration per relational data source. The table is in the model of every relational source (SQL Server, PostgreSQL and SQLite after ADR-113), so a consumer…"},"1045":{"u":"/docs/adr/114-internal-commands-durable-job-queue.html#revision-2026-10-01","d":"ADR-114: Internal Commands, a Job Queue Built on the Outbox's Machinery","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Two statements no longer matched the code. First, a schedule made outside a transaction now saves…"},"1046":{"u":"/docs/adr/114-internal-commands-durable-job-queue.html#related","d":"ADR-114: Internal Commands, a Job Queue Built on the Outbox's Machinery","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the claim-lease, backoff and dead-letter machinery this record borrows as an idiom), ADR-014 (the decorator chain a deferred command runs through unchanged), ADR-074…"},"1047":{"u":"/docs/adr/115-strongly-typed-identifiers-opt-in.html","d":"ADR-115: Strongly Typed Identifiers as an Opt-In Capability, Aliases Still the Default","k":"Architecture Decision Records"},"1048":{"u":"/docs/adr/115-strongly-typed-identifiers-opt-in.html#status","d":"ADR-115: Strongly Typed Identifiers as an Opt-In Capability, Aliases Still the Default","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-09). Revised 2026-09-19 (the alias count and the migration-surface census this record quotes were re-measured, the CheckIn parameter run was corrected from…"},"1049":{"u":"/docs/adr/115-strongly-typed-identifiers-opt-in.html#context","d":"ADR-115: Strongly Typed Identifiers as an Opt-In Capability, Aliases Still the Default","k":"Architecture Decision Records","t":"Context","x":"ADR-048 chose primitive identifier aliases over wrapper structs. ADR-085 re-opened that choice on 2026-08-18, priced the migration in numbers (44 aliases, 43 of them int, 3,192…"},"1050":{"u":"/docs/adr/115-strongly-typed-identifiers-opt-in.html#decision","d":"ADR-115: Strongly Typed Identifiers as an Opt-In Capability, Aliases Still the Default","k":"Architecture Decision Records","t":"Decision","x":"The framework ships strongly typed identifiers as an opt-in capability. The primitive aliases remain the default identifier model, no existing entity, consumer or test migrates,…"},"1051":{"u":"/docs/adr/115-strongly-typed-identifiers-opt-in.html#rationale","d":"ADR-115: Strongly Typed Identifiers as an Opt-In Capability, Aliases Still the Default","k":"Architecture Decision Records","t":"Rationale","x":"- The gap was the plumbing, not the type. A readonly record struct wrapping an int is four lines any team can write. What stops teams is the six boundaries around it: EF mapping…"},"1052":{"u":"/docs/adr/115-strongly-typed-identifiers-opt-in.html#trade-offs","d":"ADR-115: Strongly Typed Identifiers as an Opt-In Capability, Aliases Still the Default","k":"Architecture Decision Records","t":"Trade-offs","x":"- Two identifier styles can now coexist in one codebase, and nothing prevents that. A repo can wrap some identifiers and leave others primitive, and the reader cannot tell from…"},"1053":{"u":"/docs/adr/115-strongly-typed-identifiers-opt-in.html#related","d":"ADR-115: Strongly Typed Identifiers as an Opt-In Capability, Aliases Still the Default","k":"Architecture Decision Records","t":"Related","x":"ADR-048 (the primitive alias decision this record leaves in force), ADR-085 (the priced deferral and its three revisit triggers, none of which is the trigger here), ADR-104 (the…"},"1054":{"u":"/docs/adr/115-strongly-typed-identifiers-opt-in.html#revision-2026-09-19","d":"ADR-115: Strongly Typed Identifiers as an Opt-In Capability, Aliases Still the Default","k":"Architecture Decision Records","t":"Revision (2026-09-19)","x":"The decision is unchanged: the framework still ships the capability, the aliases are still the default, and adoption is still zero. Four facts this record borrowed were…"},"1055":{"u":"/docs/adr/115-strongly-typed-identifiers-opt-in.html#revision-2026-10-01","d":"ADR-115: Strongly Typed Identifiers as an Opt-In Capability, Aliases Still the Default","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The decision is unchanged: the framework still ships the capability, the aliases are still the default, and adoption is still zero. Three statements were corrected against source…"},"1056":{"u":"/docs/adr/116-identity-completions-opt-in.html","d":"ADR-116: Identity Completions Ship as Opt-In Base Classes, Not as Framework Features","k":"Architecture Decision Records"},"1057":{"u":"/docs/adr/116-identity-completions-opt-in.html#status","d":"ADR-116: Identity Completions Ship as Opt-In Base Classes, Not as Framework Features","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-09; revised 2026-09-19: item 10 now scopes the \"no @page directive\" claim to what this decision ships, because MMCA.Common.UI itself ships 13 of them in the…"},"1058":{"u":"/docs/adr/116-identity-completions-opt-in.html#context","d":"ADR-116: Identity Completions Ship as Opt-In Base Classes, Not as Framework Features","k":"Architecture Decision Records","t":"Context","x":"The framework already owns the hard, uniform parts of identity. AuthenticationServiceBase carries login, registration, refresh-token rotation with reuse detection and revocation…"},"1059":{"u":"/docs/adr/116-identity-completions-opt-in.html#decision","d":"ADR-116: Identity Completions Ship as Opt-In Base Classes, Not as Framework Features","k":"Architecture Decision Records","t":"Decision","x":"The four identity completions ship as opt-in extension points: contracts and abstract bases in Application, implementations in Infrastructure behind their own Add calls, and…"},"1060":{"u":"/docs/adr/116-identity-completions-opt-in.html#rationale","d":"ADR-116: Identity Completions Ship as Opt-In Base Classes, Not as Framework Features","k":"Architecture Decision Records","t":"Rationale","x":"Six shapes were considered for these four capabilities, and each rejection is what produced the opt-in posture above: - Ship a framework User entity with two-factor and…"},"1061":{"u":"/docs/adr/116-identity-completions-opt-in.html#trade-offs","d":"ADR-116: Identity Completions Ship as Opt-In Base Classes, Not as Framework Features","k":"Architecture Decision Records","t":"Trade-offs","x":"- A consumer that adopts nothing sees no behaviour change and no new configuration. The only edit that reaches an unadopted consumer is the widened AuthenticationServiceBase…"},"1062":{"u":"/docs/adr/116-identity-completions-opt-in.html#revision-2026-10-01","d":"ADR-116: Identity Completions Ship as Opt-In Base Classes, Not as Framework Features","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Item 10 said no confirmation page ships in MMCA.Common.UI. One now does: ConfirmEmail.razor carries @page \"/confirm-email\" and [AllowAnonymous]…"},"1063":{"u":"/docs/adr/116-identity-completions-opt-in.html#related","d":"ADR-116: Identity Completions Ship as Opt-In Base Classes, Not as Framework Features","k":"Architecture Decision Records","t":"Related","x":"ADR-020 (the compiled role-to-permission registry stored grants layer over), ADR-091 (the token-service design email confirmation mirrors), ADR-032 (the narrow-contract precedent…"},"1064":{"u":"/docs/adr/117-apphost-integration-test-base.html","d":"ADR-117: AppHost Integration Testing as a Shipped Package","k":"Architecture Decision Records"},"1065":{"u":"/docs/adr/117-apphost-integration-test-base.html#status","d":"ADR-117: AppHost Integration Testing as a Shipped Package","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-09). Adds the AppHost test tier ADR-098 left out, as a package rather than a per-repo copy. Revised 2026-09-11: both consumer tiers now subclass the package…"},"1066":{"u":"/docs/adr/117-apphost-integration-test-base.html#context","d":"ADR-117: AppHost Integration Testing as a Shipped Package","k":"Architecture Decision Records","t":"Context","x":"The AppHost is the only file that states how a whole stack fits together: which project resources exist, which database each one owns, which broker they share, where JWKS…"},"1067":{"u":"/docs/adr/117-apphost-integration-test-base.html#decision","d":"ADR-117: AppHost Integration Testing as a Shipped Package","k":"Architecture Decision Records","t":"Decision","x":"Ship the AppHost test tier as a package, MMCA.Common.Testing.Aspire, so a consumer's smoke tier is a subclass and a set of assertions rather than a copied fixture. Make the…"},"1068":{"u":"/docs/adr/117-apphost-integration-test-base.html#rationale","d":"ADR-117: AppHost Integration Testing as a Shipped Package","k":"Architecture Decision Records","t":"Rationale","x":"Six shapes were weighed, and each rejection is a property the package keeps: - Leave it as a per-repo copy. Rejected: that is what produced roughly a hundred lines of budget and…"},"1069":{"u":"/docs/adr/117-apphost-integration-test-base.html#trade-offs","d":"ADR-117: AppHost Integration Testing as a Shipped Package","k":"Architecture Decision Records","t":"Trade-offs","x":"- A consumer's smoke tier is a subclass. MMCA.ADC.AppHost.SmokeTests is two files. The fixture is AdcAppHostFixture : AppHostFixtureBase…"},"1070":{"u":"/docs/adr/117-apphost-integration-test-base.html#revision-2026-10-01","d":"ADR-117: AppHost Integration Testing as a Shipped Package","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Decision 9 is corrected: it showed Assert.SkipWhen(!Fixture.IsAvailable, Fixture.SkipReason!) as the consumer's skip, a form that throws…"},"1071":{"u":"/docs/adr/117-apphost-integration-test-base.html#related","d":"ADR-117: AppHost Integration Testing as a Shipped Package","k":"Architecture Decision Records","t":"Related","x":"ADR-098 (the orchestration posture this tier tests without changing), ADR-058 (the shipped-test-tier-as-a-package pattern this record follows), ADR-016 (the lockstep release this…"},"1072":{"u":"/docs/adr/118-message-transport-library-policy.html","d":"ADR-118: Message Transport Library Policy (MassTransit v8 Pin and Exit Strategy)","k":"Architecture Decision Records"},"1073":{"u":"/docs/adr/118-message-transport-library-policy.html#status","d":"ADR-118: Message Transport Library Policy (MassTransit v8 Pin and Exit Strategy)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-11). Promotes the MassTransit clause inside ADR-016 into a decision of its own: ADR-016 owns the release policy that the pin is enforced under, this record owns…"},"1074":{"u":"/docs/adr/118-message-transport-library-policy.html#context","d":"ADR-118: Message Transport Library Policy (MassTransit v8 Pin and Exit Strategy)","k":"Architecture Decision Records","t":"Context","x":"MassTransit is the only message-broker library in this workspace, and it is pinned to 8.5.11 across all three of its packages (MMCA.Common/Directory.Packages.props:128-130, the…"},"1075":{"u":"/docs/adr/118-message-transport-library-policy.html#decision","d":"ADR-118: Message Transport Library Policy (MassTransit v8 Pin and Exit Strategy)","k":"Architecture Decision Records","t":"Decision","x":"Stay on MassTransit v8 behind IMessageBus, and keep the outbox and inbox custom precisely because that is what keeps a transport swap cheap. Record the exit triggers now, and the…"},"1076":{"u":"/docs/adr/118-message-transport-library-policy.html#rationale","d":"ADR-118: Message Transport Library Policy (MassTransit v8 Pin and Exit Strategy)","k":"Architecture Decision Records","t":"Rationale","x":"- The pin is enforceable, so it is honest. A comment beside a version number is a hope; a fitness function that parses Directory.Packages.props is the reason the v9 crash was…"},"1077":{"u":"/docs/adr/118-message-transport-library-policy.html#trade-offs","d":"ADR-118: Message Transport Library Policy (MassTransit v8 Pin and Exit Strategy)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The workspace sits on a library version with a finite support horizon. Nothing forces a move today, and the exit triggers above are what convert \"we should look at this…"},"1078":{"u":"/docs/adr/118-message-transport-library-policy.html#revision-2026-10-01","d":"ADR-118: Message Transport Library Policy (MassTransit v8 Pin and Exit Strategy)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision, trigger, replacement order or rationale changed. Facts restated from source: all five pinned MassTransit entries are at the v8 patch 8.5.11…"},"1079":{"u":"/docs/adr/118-message-transport-library-policy.html#related","d":"ADR-118: Message Transport Library Policy (MassTransit v8 Pin and Exit Strategy)","k":"Architecture Decision Records","t":"Related","x":"ADR-016 (the lockstep release policy this pin is enforced under; its 2026-08-28 amendment first sketched exit options, and this record takes ownership of that list. ADR-016's…"},"1080":{"u":"/docs/adr/119-restrict-delete-by-default.html","d":"ADR-119: Delete Behavior Is an Explicit Business Rule (Restrict by Default)","k":"Architecture Decision Records"},"1081":{"u":"/docs/adr/119-restrict-delete-by-default.html#status","d":"ADR-119: Delete Behavior Is an Explicit Business Rule (Restrict by Default)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-11). Inverts EF Core's cascading default for every relationship in every repo built on this framework, and stamps each foreign key with where its delete…"},"1082":{"u":"/docs/adr/119-restrict-delete-by-default.html#context","d":"ADR-119: Delete Behavior Is an Explicit Business Rule (Restrict by Default)","k":"Architecture Decision Records","t":"Context","x":"EF Core picks a delete behavior for you. A required relationship gets Cascade, an optional one gets ClientSetNull, and neither choice appears anywhere a reviewer reads. The…"},"1083":{"u":"/docs/adr/119-restrict-delete-by-default.html#decision","d":"ADR-119: Delete Behavior Is an Explicit Business Rule (Restrict by Default)","k":"Architecture Decision Records","t":"Decision","x":"Restrict is the default delete behavior for every relationship nobody configured, a cascade is an opt-in with a stated reason, and the finished model records which of the two…"},"1084":{"u":"/docs/adr/119-restrict-delete-by-default.html#rationale","d":"ADR-119: Delete Behavior Is an Explicit Business Rule (Restrict by Default)","k":"Architecture Decision Records","t":"Rationale","x":"- The safe default is the one that fails loudly. Restrict turns an unintended parent delete into an error at the point of the delete, where it can be read and fixed. Cascade…"},"1085":{"u":"/docs/adr/119-restrict-delete-by-default.html#trade-offs","d":"ADR-119: Delete Behavior Is an Explicit Business Rule (Restrict by Default)","k":"Architecture Decision Records","t":"Trade-offs","x":"- One migration per service database, authored and merged on 2026-09-11. The FK constraints change, so each database needed a migration: four in ADC (Conference, Identity,…"},"1086":{"u":"/docs/adr/119-restrict-delete-by-default.html#revision-2026-10-01","d":"ADR-119: Delete Behavior Is an Explicit Business Rule (Restrict by Default)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. The Context list of genuine hard deletes is completed: it now also names the framework's outbox cleanup…"},"1087":{"u":"/docs/adr/119-restrict-delete-by-default.html#related","d":"ADR-119: Delete Behavior Is an Explicit Business Rule (Restrict by Default)","k":"Architecture Decision Records","t":"Related","x":"ADR-006 (one context class per engine over one abstract base, which is what makes this a single registration), ADR-005 (the soft-delete posture that keeps cascades dormant at…"},"1088":{"u":"/docs/adr/120-governed-chat-client-boundary.html","d":"ADR-120: Governed Language-Model Boundary (MMCA.Common.AI)","k":"Architecture Decision Records"},"1089":{"u":"/docs/adr/120-governed-chat-client-boundary.html#status","d":"ADR-120: Governed Language-Model Boundary (MMCA.Common.AI)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-11). Extends ADR-111, which keeps every scoring-specific rule it states: this record moves the parts that are not about session scoring into the framework, as…"},"1090":{"u":"/docs/adr/120-governed-chat-client-boundary.html#context","d":"ADR-120: Governed Language-Model Boundary (MMCA.Common.AI)","k":"Architecture Decision Records","t":"Context","x":"Rubric section 16, AI-Native Application Architecture, asks one question of a product feature that calls a language model: is that dependency governed like any other external…"},"1091":{"u":"/docs/adr/120-governed-chat-client-boundary.html#decision","d":"ADR-120: Governed Language-Model Boundary (MMCA.Common.AI)","k":"Architecture Decision Records","t":"Decision","x":"The framework owns the language-model boundary as one optional package, MMCA.Common.AI, that names no vendor. A provider arrives as an adapter package contributing one factory,…"},"1092":{"u":"/docs/adr/120-governed-chat-client-boundary.html#rationale","d":"ADR-120: Governed Language-Model Boundary (MMCA.Common.AI)","k":"Architecture Decision Records","t":"Rationale","x":"- These rules belong to the call, not to the feature. An output ceiling, a timeout, a tool gate and a token counter are true of every model call anybody will ever write here.…"},"1093":{"u":"/docs/adr/120-governed-chat-client-boundary.html#trade-offs","d":"ADR-120: Governed Language-Model Boundary (MMCA.Common.AI)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Off by absence puts a null on the consumer. Because nothing is registered when Ai:Enabled is false, a feature resolves the client with GetService and holds a nullable…"},"1094":{"u":"/docs/adr/120-governed-chat-client-boundary.html#consequences","d":"ADR-120: Governed Language-Model Boundary (MMCA.Common.AI)","k":"Architecture Decision Records","t":"Consequences","x":"- ADC's SessionScoringService runs on IChatClient (SessionScoringService.cs:46-49). The port IAiScoringService stays, PromptVersion stays and keeps being persisted with every…"},"1095":{"u":"/docs/adr/120-governed-chat-client-boundary.html#revision-2026-10-01","d":"ADR-120: Governed Language-Model Boundary (MMCA.Common.AI)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; the current-state sections now match the code. ADC adopted the package fully: the host calls AddAnthropicAiProvider()…"},"1096":{"u":"/docs/adr/120-governed-chat-client-boundary.html#related","d":"ADR-120: Governed Language-Model Boundary (MMCA.Common.AI)","k":"Architecture Decision Records","t":"Related","x":"ADR-111 (the record this one extends: every scoring-specific rule, the prompt-change protocol, the golden and live evaluation tiers, the input and output guardrails and the…"},"1097":{"u":"/docs/adr/121-ephemeral-in-process-work-queue.html","d":"ADR-121: Ephemeral In-Process Work Queue (Bounded Channel plus Hosted Drain)","k":"Architecture Decision Records"},"1098":{"u":"/docs/adr/121-ephemeral-in-process-work-queue.html#status","d":"ADR-121: Ephemeral In-Process Work Queue (Bounded Channel plus Hosted Drain)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-11). Supersedes ADR-052."},"1099":{"u":"/docs/adr/121-ephemeral-in-process-work-queue.html#context","d":"ADR-121: Ephemeral In-Process Work Queue (Bounded Channel plus Hosted Drain)","k":"Architecture Decision Records","t":"Context","x":"ADR-052 ran two different kinds of work through one mechanism: ephemeral broadcasts that must not put a gRPC round trip on the hot path of a vote, and an AI scoring pass that…"},"1100":{"u":"/docs/adr/121-ephemeral-in-process-work-queue.html#decision","d":"ADR-121: Ephemeral In-Process Work Queue (Bounded Channel plus Hosted Drain)","k":"Architecture Decision Records","t":"Decision","x":"Ephemeral in-process work runs as a bounded channel plus a single-reader hosted drain. Nothing starts an untracked Task from a request, and nothing that must run lands here. - A…"},"1101":{"u":"/docs/adr/121-ephemeral-in-process-work-queue.html#rationale","d":"ADR-121: Ephemeral In-Process Work Queue (Bounded Channel plus Hosted Drain)","k":"Architecture Decision Records","t":"Rationale","x":"- The host lifetime is the point. A BackgroundService is the only in-process shape the host can cancel and await. Everything else in the decision follows from wanting that, and a…"},"1102":{"u":"/docs/adr/121-ephemeral-in-process-work-queue.html#trade-offs","d":"ADR-121: Ephemeral In-Process Work Queue (Bounded Channel plus Hosted Drain)","k":"Architecture Decision Records","t":"Trade-offs","x":"- In-process only. The queue does not survive a restart and does not span replicas. Accepted because every job left here is ephemeral by definition. Work that must survive a…"},"1103":{"u":"/docs/adr/121-ephemeral-in-process-work-queue.html#revision-2026-10-01","d":"ADR-121: Ephemeral In-Process Work Queue (Bounded Channel plus Hosted Drain)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Two Context and Related statements are corrected: the scoring trigger no longer answers 202 Accepted unconditionally, because a…"},"1104":{"u":"/docs/adr/121-ephemeral-in-process-work-queue.html#related","d":"ADR-121: Ephemeral In-Process Work Queue (Bounded Channel plus Hosted Drain)","k":"Architecture Decision Records","t":"Related","x":"ADR-114 (the durable, leased, retrying queue that owns expensive and must-run work), ADR-108 (the cross-replica claim that replaced this record's per-replica dedup), ADR-039…"},"1105":{"u":"/docs/adr/122-dev-only-relaxations-fail-closed.html","d":"ADR-122: Dev-Only Relaxations Are Environment-Gated and Fail Closed","k":"Architecture Decision Records"},"1106":{"u":"/docs/adr/122-dev-only-relaxations-fail-closed.html#status","d":"ADR-122: Dev-Only Relaxations Are Environment-Gated and Fail Closed","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-11)."},"1107":{"u":"/docs/adr/122-dev-only-relaxations-fail-closed.html#context","d":"ADR-122: Dev-Only Relaxations Are Environment-Gated and Fail Closed","k":"Architecture Decision Records","t":"Context","x":"Several capabilities are useful locally and dangerous in a deployed environment: EF Core rendering parameter values into logs and exception messages, an SMTP session that skips…"},"1108":{"u":"/docs/adr/122-dev-only-relaxations-fail-closed.html#decision","d":"ADR-122: Dev-Only Relaxations Are Environment-Gated and Fail Closed","k":"Architecture Decision Records","t":"Decision","x":"A relaxation applies only when the environment is positively identified as Development. An unknown, absent, or unparseable environment yields the production posture. Three…"},"1109":{"u":"/docs/adr/122-dev-only-relaxations-fail-closed.html#rationale","d":"ADR-122: Dev-Only Relaxations Are Environment-Gated and Fail Closed","k":"Architecture Decision Records","t":"Rationale","x":"- Unknown is not safe. The failure this prevents is not a developer flipping a flag on purpose, it is a flag arriving somewhere nobody looked. Treating an absent environment as…"},"1110":{"u":"/docs/adr/122-dev-only-relaxations-fail-closed.html#trade-offs","d":"ADR-122: Dev-Only Relaxations Are Environment-Gated and Fail Closed","k":"Architecture Decision Records","t":"Trade-offs","x":"- ASPNETCOREENVIRONMENT is the trust root. A host that misreports itself as Development gets every relaxation at once. The rule concentrates the risk on one value instead of…"},"1111":{"u":"/docs/adr/122-dev-only-relaxations-fail-closed.html#revision-2026-10-01","d":"ADR-122: Dev-Only Relaxations Are Environment-Gated and Fail Closed","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Citations were re-anchored to the current source: the AI gate (DependencyInjection.cs:262-266, remarks :257-260), the RequireHttpsMetadata…"},"1112":{"u":"/docs/adr/122-dev-only-relaxations-fail-closed.html#related","d":"ADR-122: Dev-Only Relaxations Are Environment-Gated and Fail Closed","k":"Architecture Decision Records","t":"Related","x":"ADR-070 (binds and validates settings at startup; this record governs when a validated setting may be honored), ADR-023 (the HSTS and CSP posture relaxed here), ADR-082 (the two…"},"1113":{"u":"/docs/adr/123-speaker-session-assets.html","d":"ADR-123: Speaker-Published Session Materials","k":"Architecture Decision Records"},"1114":{"u":"/docs/adr/123-speaker-session-assets.html#status","d":"ADR-123: Speaker-Published Session Materials","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-12). Extends ADR-045 from images to documents: the framework gains a document content sniffer, a blob-name sanitizer and stored response headers, and ADC gains…"},"1115":{"u":"/docs/adr/123-speaker-session-assets.html#context","d":"ADR-123: Speaker-Published Session Materials","k":"Architecture Decision Records","t":"Context","x":"A speaker finishes a talk and forty people want the deck. Until now ADC's only answer was Session.ResourceLinks, a free-text field imported from Sessionize: the speaker types…"},"1116":{"u":"/docs/adr/123-speaker-session-assets.html#decision","d":"ADR-123: Speaker-Published Session Materials","k":"Architecture Decision Records","t":"Decision","x":"Session materials are a separate Conference aggregate, written by the session's own speakers or by an organizer, stored in a public-read container under an unguessable blob name,…"},"1117":{"u":"/docs/adr/123-speaker-session-assets.html#rationale","d":"ADR-123: Speaker-Published Session Materials","k":"Architecture Decision Records","t":"Rationale","x":"Four alternatives were weighed. A child collection on Session. The obvious DDD answer, and wrong for this entity. Session is imported from Sessionize and re-imported on every…"},"1118":{"u":"/docs/adr/123-speaker-session-assets.html#trade-offs","d":"ADR-123: Speaker-Published Session Materials","k":"Architecture Decision Records","t":"Trade-offs","x":"- The container is public-read, so a URL is a credential. Anyone holding a blob URL can fetch it, forever, with no session and no referrer check. That is accepted for the same…"},"1119":{"u":"/docs/adr/123-speaker-session-assets.html#revision-2026-10-01","d":"ADR-123: Speaker-Published Session Materials","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Two parts of the decision changed and the rest of the record was re-anchored. The options-carrying IFileStorageService.UploadAsync overload is no longer a default interface…"},"1120":{"u":"/docs/adr/123-speaker-session-assets.html#related","d":"ADR-123: Speaker-Published Session Materials","k":"Architecture Decision Records","t":"Related","x":"ADR-045 (the storage abstraction, the image path this record is the document sibling of, and the avatar-only scope statement this supersedes), ADR-114 (the durable queue the blob…"},"1121":{"u":"/docs/adr/124-blazor-circuit-ceiling-ui-edge.html","d":"ADR-124: The Server-Rendered UI Host Is Its Own Hardened Edge","k":"Architecture Decision Records"},"1122":{"u":"/docs/adr/124-blazor-circuit-ceiling-ui-edge.html#status","d":"ADR-124: The Server-Rendered UI Host Is Its Own Hardened Edge","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-19). Revised 2026-09-25 (the hardening kit is one framework copy in MMCA.Common.UI.Web/Hardening/, first shipped in v1.206.0, which both UI hosts consume; the…"},"1123":{"u":"/docs/adr/124-blazor-circuit-ceiling-ui-edge.html#context","d":"ADR-124: The Server-Rendered UI Host Is Its Own Hardened Edge","k":"Architecture Decision Records","t":"Context","x":"ADR-019 layers rate limiting, and ADR-088 makes the Gateway the layer that carries the outermost one. Both records describe traffic that arrives through the Gateway. The…"},"1124":{"u":"/docs/adr/124-blazor-circuit-ceiling-ui-edge.html#decision","d":"ADR-124: The Server-Rendered UI Host Is Its Own Hardened Edge","k":"Architecture Decision Records","t":"Decision","x":"The server-rendered UI host defends itself, in three layers bound from two configuration sections the host owns, in both ADC and Store, using one framework kit. - A ceiling on…"},"1125":{"u":"/docs/adr/124-blazor-circuit-ceiling-ui-edge.html#rationale","d":"ADR-124: The Server-Rendered UI Host Is Its Own Hardened Edge","k":"Architecture Decision Records","t":"Rationale","x":"- A separate origin is a separate edge. The Gateway cannot defend a hostname it never receives a request for, so the choice was between routing the UI through the Gateway and…"},"1126":{"u":"/docs/adr/124-blazor-circuit-ceiling-ui-edge.html#trade-offs","d":"ADR-124: The Server-Rendered UI Host Is Its Own Hardened Edge","k":"Architecture Decision Records","t":"Trade-offs","x":"- Both ceilings are per replica, in memory. The effective allowance is the configured number multiplied by the replica count (two at most for ADC's UI today), so two replicas…"},"1127":{"u":"/docs/adr/124-blazor-circuit-ceiling-ui-edge.html#revision-2026-09-25-one-framework-kit-one-forwarded-header-posture","d":"ADR-124: The Server-Rendered UI Host Is Its Own Hardened Edge","k":"Architecture Decision Records","t":"Revision (2026-09-25): one framework kit, one forwarded-header posture","x":"The hardening kit this record decides now lives once, in MMCA.Common.UI.Web/Hardening/, first shipped in v1.206.0 (MMCA.Common/CHANGELOG.md:270-282). ADC and Store each import it…"},"1128":{"u":"/docs/adr/124-blazor-circuit-ceiling-ui-edge.html#revision-2026-10-01-the-circuit-transport-and-the-circuit-count","d":"ADR-124: The Server-Rendered UI Host Is Its Own Hardened Edge","k":"Architecture Decision Records","t":"Revision (2026-10-01): the circuit transport and the circuit count","x":"Two behaviours of the kit changed in v1.213.0 (MMCA.Common/CHANGELOG.md:82,145,148), and the Decision above now states both. First, the chained limiter no longer applies the…"},"1129":{"u":"/docs/adr/124-blazor-circuit-ceiling-ui-edge.html#related","d":"ADR-124: The Server-Rendered UI Host Is Its Own Hardened Edge","k":"Architecture Decision Records","t":"Related","x":"ADR-019 (the layered rate-limiting posture this adds a layer to), ADR-088 (the Gateway edge whose limiter this host is outside of, and which the registration comments name…"},"1130":{"u":"/docs/adr/125-parameterized-sql-only.html","d":"ADR-125: Parameterized SQL Only, Enforced by a Fitness Gate","k":"Architecture Decision Records"},"1131":{"u":"/docs/adr/125-parameterized-sql-only.html#status","d":"ADR-125: Parameterized SQL Only, Enforced by a Fitness Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-19)."},"1132":{"u":"/docs/adr/125-parameterized-sql-only.html#context","d":"ADR-125: Parameterized SQL Only, Enforced by a Fitness Gate","k":"Architecture Decision Records","t":"Context","x":"The supported way to read data is the repository plus specification contract (ADR-055), and LINQ answers almost everything. It does not answer a window function, a recursive CTE…"},"1133":{"u":"/docs/adr/125-parameterized-sql-only.html#decision","d":"ADR-125: Parameterized SQL Only, Enforced by a Fitness Gate","k":"Architecture Decision Records","t":"Decision","x":"Give raw SQL exactly one door whose signature makes the unsafe call uncompilable, and ban the four raw EF members in module code with a fitness test rather than a guideline. -…"},"1134":{"u":"/docs/adr/125-parameterized-sql-only.html#rationale","d":"ADR-125: Parameterized SQL Only, Enforced by a Fitness Gate","k":"Architecture Decision Records","t":"Rationale","x":"- A signature outranks a guideline. Both halves of this decision aim at the same thing from different directions: the contract removes the unsafe call from the type system, and…"},"1135":{"u":"/docs/adr/125-parameterized-sql-only.html#trade-offs","d":"ADR-125: Parameterized SQL Only, Enforced by a Fitness Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- FormattableString blocks the accident, not the intent. FormattableStringFactory.Create builds a FormattableString out of an already-concatenated string with no interpolation…"},"1136":{"u":"/docs/adr/125-parameterized-sql-only.html#revision-2026-10-01","d":"ADR-125: Parameterized SQL Only, Enforced by a Fitness Gate","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Citations refreshed: the CHANGELOG anchors (MMCA.Common/CHANGELOG.md:1038, :1108-1117, :1118-1121, :1153-1159), the executor registration…"},"1137":{"u":"/docs/adr/125-parameterized-sql-only.html#related","d":"ADR-125: Parameterized SQL Only, Enforced by a Fitness Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-055 (the repository plus specification path this escape hatch sits beside, and the reason the hatch stays narrow), ADR-015 (the fitness-function style this rule is written…"},"1138":{"u":"/docs/adr/126-event-sourcing-not-adopted.html","d":"ADR-126: Event Sourcing Is Not Adopted","k":"Architecture Decision Records"},"1139":{"u":"/docs/adr/126-event-sourcing-not-adopted.html#status","d":"ADR-126: Event Sourcing Is Not Adopted","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-22) as a documented rejection. Nothing ships with this record: no event store, no stream table, no projection host, no new package. What ships is the reason…"},"1140":{"u":"/docs/adr/126-event-sourcing-not-adopted.html#context","d":"ADR-126: Event Sourcing Is Not Adopted","k":"Architecture Decision Records","t":"Context","x":"Event sourcing keeps an append-only log of the facts that happened to an aggregate and treats that log as the system of record. Current state becomes a fold over the log, a…"},"1141":{"u":"/docs/adr/126-event-sourcing-not-adopted.html#decision","d":"ADR-126: Event Sourcing Is Not Adopted","k":"Architecture Decision Records","t":"Decision","x":"Do not adopt event sourcing. Aggregates stay current-state EF rows that raise domain events for notification, carry audit fields, and soft-delete. No repository gains an event…"},"1142":{"u":"/docs/adr/126-event-sourcing-not-adopted.html#rationale","d":"ADR-126: Event Sourcing Is Not Adopted","k":"Architecture Decision Records","t":"Rationale","x":"- No workload asks the question event sourcing answers. Neither ADC nor MMCA.Store has an aggregate that needs replayable per-aggregate history or a \"state as of\" query.…"},"1143":{"u":"/docs/adr/126-event-sourcing-not-adopted.html#trade-offs","d":"ADR-126: Event Sourcing Is Not Adopted","k":"Architecture Decision Records","t":"Trade-offs","x":"- A late adoption costs more than an early one. The module that eventually needs a stream converts an aggregate that already has production data, deployed migrations and reads…"},"1144":{"u":"/docs/adr/126-event-sourcing-not-adopted.html#alternatives-rejected","d":"ADR-126: Event Sourcing Is Not Adopted","k":"Architecture Decision Records","t":"Alternatives rejected","x":"- Marten on PostgreSQL, weighed 2026-09-22 and rejected. Marten is the credible .NET event store and the specific option this record weighed. It was dropped on two grounds rather…"},"1145":{"u":"/docs/adr/126-event-sourcing-not-adopted.html#when-to-revisit","d":"ADR-126: Event Sourcing Is Not Adopted","k":"Architecture Decision Records","t":"When to revisit","x":"Revisit when an aggregate acquires a business requirement for full replayable history or temporal queries: a financial ledger whose every movement must be reconstructible, or a…"},"1146":{"u":"/docs/adr/126-event-sourcing-not-adopted.html#revision-2026-10-01","d":"ADR-126: Event Sourcing Is Not Adopted","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Two citations were re-anchored: the soft-delete global query filter now points at ApplySoftDeleteFilters…"},"1147":{"u":"/docs/adr/126-event-sourcing-not-adopted.html#related","d":"ADR-126: Event Sourcing Is Not Adopted","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (at-least-once delivery of domain events, the mechanism a per-aggregate event log would duplicate rather than replace), ADR-021 (the consume-edge inbox that makes…"},"1148":{"u":"/docs/adr/127-actor-model-not-adopted.html","d":"ADR-127: The Actor Model Is Not Adopted","k":"Architecture Decision Records"},"1149":{"u":"/docs/adr/127-actor-model-not-adopted.html#status","d":"ADR-127: The Actor Model Is Not Adopted","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-22) as a documented rejection. Nothing ships with this record: no grain interface, no silo, no placement configuration, no new package. What ships is the reason…"},"1150":{"u":"/docs/adr/127-actor-model-not-adopted.html#context","d":"ADR-127: The Actor Model Is Not Adopted","k":"Architecture Decision Records","t":"Context","x":"The actor model gives every logical entity its own single-threaded unit of execution holding its state in memory and serializes all messages to that entity through it; a…"},"1151":{"u":"/docs/adr/127-actor-model-not-adopted.html#decision","d":"ADR-127: The Actor Model Is Not Adopted","k":"Architecture Decision Records","t":"Decision","x":"Do not adopt the actor model, and do not introduce an actor runtime as a hosting layer. Per-entity state stays in the owning module's database behind optimistic concurrency, read…"},"1152":{"u":"/docs/adr/127-actor-model-not-adopted.html#rationale","d":"ADR-127: The Actor Model Is Not Adopted","k":"Architecture Decision Records","t":"Rationale","x":"- No workload has the shape. Actors earn their cost on high-cardinality per-entity in-memory state with single-writer semantics, and nothing here has all three: entity counts are…"},"1153":{"u":"/docs/adr/127-actor-model-not-adopted.html#trade-offs","d":"ADR-127: The Actor Model Is Not Adopted","k":"Architecture Decision Records","t":"Trade-offs","x":"- A genuinely actor-shaped workload will arrive without groundwork, and the first adoption pays the learning cost under whatever deadline produced the requirement. - Some…"},"1154":{"u":"/docs/adr/127-actor-model-not-adopted.html#alternatives-rejected","d":"ADR-127: The Actor Model Is Not Adopted","k":"Architecture Decision Records","t":"Alternatives rejected","x":"- Microsoft Orleans hosted under Aspire, weighed 2026-09-22 and rejected. Orleans is the .NET virtual-actor runtime and the option this record weighed. Aspire already composes…"},"1155":{"u":"/docs/adr/127-actor-model-not-adopted.html#when-to-revisit","d":"ADR-127: The Actor Model Is Not Adopted","k":"Architecture Decision Records","t":"When to revisit","x":"Revisit when a real-time per-entity state workload appears that optimistic concurrency plus Redis cannot carry: an entity mutated by enough concurrent callers that the…"},"1156":{"u":"/docs/adr/127-actor-model-not-adopted.html#revision-2026-10-01","d":"ADR-127: The Actor Model Is Not Adopted","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Citations refreshed: the vote handler's class declaration (CastVoteHandler.cs:20) and its poll repository call (:33), OnModelCreating…"},"1157":{"u":"/docs/adr/127-actor-model-not-adopted.html#related","d":"ADR-127: The Actor Model Is Not Adopted","k":"Architecture Decision Records","t":"Related","x":"ADR-007, ADR-008 and ADR-059 (the boundary a future silo would live behind), ADR-026 and ADR-077 (the caching tiers that absorb the read pressure, and the in-process residency…"},"1158":{"u":"/docs/adr/128-time-as-an-input.html","d":"ADR-128: Time as an Input (No Ambient Clock Reads in Domain and Application)","k":"Architecture Decision Records"},"1159":{"u":"/docs/adr/128-time-as-an-input.html#status","d":"ADR-128: Time as an Input (No Ambient Clock Reads in Domain and Application)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-10-01)."},"1160":{"u":"/docs/adr/128-time-as-an-input.html#context","d":"ADR-128: Time as an Input (No Ambient Clock Reads in Domain and Application)","k":"Architecture Decision Records","t":"Context","x":"Expiry windows, payment deadlines, discount windows, overdue checks and cutoffs are business rules, and every one of them branches on \"now\". When the rule reads DateTime.UtcNow…"},"1161":{"u":"/docs/adr/128-time-as-an-input.html#decision","d":"ADR-128: Time as an Input (No Ambient Clock Reads in Domain and Application)","k":"Architecture Decision Records","t":"Decision","x":"Domain and Application code takes time as an input and never reads the ambient clock. A handler injects TimeProvider and passes the instant into the domain method; an aggregate…"},"1162":{"u":"/docs/adr/128-time-as-an-input.html#rationale","d":"ADR-128: Time as an Input (No Ambient Clock Reads in Domain and Application)","k":"Architecture Decision Records","t":"Rationale","x":"- A test can only drive what it can supply. Passing the instant in makes every time-dependent branch a plain input, so an expiry, a cutoff or an overdue state is one argument…"},"1163":{"u":"/docs/adr/128-time-as-an-input.html#trade-offs","d":"ADR-128: Time as an Input (No Ambient Clock Reads in Domain and Application)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The rule bans five getters, not the idea of a clock. A read through any other path (a static TimeProvider.System.GetUtcNow(), Environment.TickCount, Stopwatch) is not matched…"},"1164":{"u":"/docs/adr/128-time-as-an-input.html#related","d":"ADR-128: Time as an Input (No Ambient Clock Reads in Domain and Application)","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (the fitness-function style and the shared TestsBase package this rule is shipped in), ADR-125 (the precedent for a fitness-gated convention with its own record and an…"},"1165":{"u":"/docs/adr/129-tactical-aggregate-contract.html","d":"ADR-129: The Tactical Aggregate Contract","k":"Architecture Decision Records"},"1166":{"u":"/docs/adr/129-tactical-aggregate-contract.html#status","d":"ADR-129: The Tactical Aggregate Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-10-01)."},"1167":{"u":"/docs/adr/129-tactical-aggregate-contract.html#context","d":"ADR-129: The Tactical Aggregate Contract","k":"Architecture Decision Records","t":"Context","x":"Every entity in the four repos is written the same way: a sealed class over a framework base, a private constructor, a static Create(...) returning Result , private setters,…"},"1168":{"u":"/docs/adr/129-tactical-aggregate-contract.html#decision","d":"ADR-129: The Tactical Aggregate Contract","k":"Architecture Decision Records","t":"Decision","x":"Entities follow one tactical contract. The construction, encapsulation and placement parts are fitness-gated by EntityConventionTestsBase; the child-access part and the use of…"},"1169":{"u":"/docs/adr/129-tactical-aggregate-contract.html#rationale","d":"ADR-129: The Tactical Aggregate Contract","k":"Architecture Decision Records","t":"Rationale","x":"- One contract, one record. The pieces were each correct in their own ADR, but an agent writing a new entity needs the whole shape at once, and a reviewer needs to know which…"},"1170":{"u":"/docs/adr/129-tactical-aggregate-contract.html#trade-offs","d":"ADR-129: The Tactical Aggregate Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- Part of the contract is not enforced. Child access through GetChildOrNotFound, SetItems and the remove, restore and cascade-delete helpers, the existence of an Invariants class…"},"1171":{"u":"/docs/adr/129-tactical-aggregate-contract.html#related","d":"ADR-129: The Tactical Aggregate Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-013 (the Result factories this contract requires), ADR-068 (value-object factories and the static Invariants class this contract reuses for entities), ADR-126 (an aggregate…"},"1172":{"u":"/docs/adr/130-per-engine-data-source-strategy.html","d":"ADR-130: Per-Engine Data Source Strategy (Engine Facts Instead of Branching on the Enum)","k":"Architecture Decision Records"},"1173":{"u":"/docs/adr/130-per-engine-data-source-strategy.html#status","d":"ADR-130: Per-Engine Data Source Strategy (Engine Facts Instead of Branching on the Enum)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-10-01). Targeted at MMCA.Common v1.218.0, which is unreleased at the time of writing (the CHANGELOG carries no entry for it yet). Refines ADR-018 (engine as a…"},"1174":{"u":"/docs/adr/130-per-engine-data-source-strategy.html#context","d":"ADR-130: Per-Engine Data Source Strategy (Engine Facts Instead of Branching on the Enum)","k":"Architecture Decision Records","t":"Context","x":"MMCA.Common persists through four engines named by one shipped public enum, DataSource…"},"1175":{"u":"/docs/adr/130-per-engine-data-source-strategy.html#decision","d":"ADR-130: Per-Engine Data Source Strategy (Engine Facts Instead of Branching on the Enum)","k":"Architecture Decision Records","t":"Decision","x":"One internal strategy object per engine answers every engine question, looked up through a static registry keyed by the shipped enum. Call sites read engine facts; they no longer…"},"1176":{"u":"/docs/adr/130-per-engine-data-source-strategy.html#rationale","d":"ADR-130: Per-Engine Data Source Strategy (Engine Facts Instead of Branching on the Enum)","k":"Architecture Decision Records","t":"Rationale","x":"- One place per engine. An engine's behavior is now one class, and a fifth engine is one class plus one registry line (DataSourceEngines.cs:15-16); a missed branch can no longer…"},"1177":{"u":"/docs/adr/130-per-engine-data-source-strategy.html#trade-offs","d":"ADR-130: Per-Engine Data Source Strategy (Engine Facts Instead of Branching on the Enum)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Breaking for consumers. The RequestIdentityInsert rename has no alias (IUnitOfWork.cs:45); a Cosmos-default host that injects IRawSqlQueryExecutor now fails at startup…"},"1178":{"u":"/docs/adr/130-per-engine-data-source-strategy.html#alternatives-rejected","d":"ADR-130: Per-Engine Data Source Strategy (Engine Facts Instead of Branching on the Enum)","k":"Architecture Decision Records","t":"Alternatives rejected","x":"- Engines registered in DI (weighed 2026-10-01). Engines as IDataSourceEngine services resolved from the container. Rejected because the conventions, EntityTypeConfiguration and…"},"1179":{"u":"/docs/adr/130-per-engine-data-source-strategy.html#consequences","d":"ADR-130: Per-Engine Data Source Strategy (Engine Facts Instead of Branching on the Enum)","k":"Architecture Decision Records","t":"Consequences","x":"- Consumer upgrade. Rename RequestIdentityInsert calls to RequestExplicitKeyInsert; on a Cosmos-default host, remove any IRawSqlQueryExecutor dependency; on SQLite with…"},"1180":{"u":"/docs/adr/130-per-engine-data-source-strategy.html#related","d":"ADR-130: Per-Engine Data Source Strategy (Engine Facts Instead of Branching on the Enum)","k":"Architecture Decision Records","t":"Related","x":"ADR-006 (one sealed context per engine), ADR-018 (engines behind one model and the resolver), ADR-113 (the fourth engine), ADR-030 (the startup migrator that reads Migrations),…"},"1181":{"u":"/docs/adr/131-same-origin-api-proxy.html","d":"ADR-131: Same-Origin API Proxy (a Backend-for-Frontend That Keeps Tokens Out of Browser Script)","k":"Architecture Decision Records"},"1182":{"u":"/docs/adr/131-same-origin-api-proxy.html#status","d":"ADR-131: Same-Origin API Proxy (a Backend-for-Frontend That Keeps Tokens Out of Browser Script)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-10-01). Targeted at MMCA.Common v1.218.0, which is unreleased at the time of writing. Records the owner's TD-08 decision for Option A of the token-storage design…"},"1183":{"u":"/docs/adr/131-same-origin-api-proxy.html#context","d":"ADR-131: Same-Origin API Proxy (a Backend-for-Frontend That Keeps Tokens Out of Browser Script)","k":"Architecture Decision Records","t":"Context","x":"A Blazor WebAssembly client calls the API through the gateway, which is a different origin from the UI host, so the HttpOnly session cookies the UI host writes (ADR-022) never…"},"1184":{"u":"/docs/adr/131-same-origin-api-proxy.html#decision","d":"ADR-131: Same-Origin API Proxy (a Backend-for-Frontend That Keeps Tokens Out of Browser Script)","k":"Architecture Decision Records","t":"Decision","x":"The UI host serves the API on its own origin and forwards to the gateway server-side, attaching the bearer it reads from the HttpOnly cookie. The browser never holds a token that…"},"1185":{"u":"/docs/adr/131-same-origin-api-proxy.html#rationale","d":"ADR-131: Same-Origin API Proxy (a Backend-for-Frontend That Keeps Tokens Out of Browser Script)","k":"Architecture Decision Records","t":"Rationale","x":"- Tokens out of script, with no infrastructure prerequisite. An injected script can still call the API as the user while the page is open, but it cannot carry a token away: the…"},"1186":{"u":"/docs/adr/131-same-origin-api-proxy.html#trade-offs","d":"ADR-131: Same-Origin API Proxy (a Backend-for-Frontend That Keeps Tokens Out of Browser Script)","k":"Architecture Decision Records","t":"Trade-offs","x":"- One extra hop. Every browser API call and every hub frame passes through the UI host before the gateway (SameOriginApiProxyEndpoint.cs:315-319). - The UI host becomes a…"},"1187":{"u":"/docs/adr/131-same-origin-api-proxy.html#alternatives-rejected","d":"ADR-131: Same-Origin API Proxy (a Backend-for-Frontend That Keeps Tokens Out of Browser Script)","k":"Architecture Decision Records","t":"Alternatives rejected","x":"- Option B: a same-site cookie sent straight to the API (weighed 2026-10-01). Rejected because it needs UI and gateway under one registrable domain in every environment (DNS,…"},"1188":{"u":"/docs/adr/131-same-origin-api-proxy.html#consequences","d":"ADR-131: Same-Origin API Proxy (a Backend-for-Frontend That Keeps Tokens Out of Browser Script)","k":"Architecture Decision Records","t":"Consequences","x":"- Adoption, per Blazor Web host. Register after the session-cookie and token registrations, map next to the session-cookie endpoints after UseAuthorization, configure the section…"},"1189":{"u":"/docs/adr/131-same-origin-api-proxy.html#related","d":"ADR-131: Same-Origin API Proxy (a Backend-for-Frontend That Keeps Tokens Out of Browser Script)","k":"Architecture Decision Records","t":"Related","x":"ADR-022 (the HttpOnly session cookie the proxy reads), ADR-051 (client token lifecycle across render modes), ADR-023 (security headers and the CSP), ADR-082 (the gateway CORS…"},"1190":{"u":"/docs/onboarding/index.html","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","x":"A teaching guide for an experienced .NET engineer who is new to this codebase. It walks every first-party type, explaining not just what each type is but how it works and why it…"},"1191":{"u":"/docs/onboarding/index.html#how-the-guide-is-organized-two-axes","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","t":"How the guide is organized, two axes","x":"The guide has two organizing axes that work together. 1. Primary axis, functional grouping. Every type lives in exactly one functional group: the capability or cross-cutting…"},"1192":{"u":"/docs/onboarding/index.html#chapters","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","t":"Chapters","x":"---"},"1193":{"u":"/docs/onboarding/index.html#legend-how-to-read-a-type-section","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","t":"Legend, how to read a type section","x":"Every type gets one section using this template: {TypeName} {Assembly} · {namespace} · {file:line} · Level {n} · {kind} - What it is: one or two plain-language sentences. -…"},"1194":{"u":"/docs/onboarding/index.html#suggested-reading-paths","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","t":"Suggested reading paths","x":"- Framework-first (recommended). Primer → group-01 → upward. You meet the MMCA.Common foundations before the MMCA.ADC features that build on them; this matches dependency order…"},"1195":{"u":"/docs/onboarding/index.html#the-companion-projects-context","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","t":"The companion projects (context)","x":"This guide covers MMCA.Common (the framework) and MMCA.ADC (one consumer). MMCA.Store is out of scope. The dependency arrow is why the Common framework groups (1–16) come before…"},"1196":{"u":"/docs/onboarding/00-dependency-manifest.html","d":"Phase 1: Dependency Manifest & Leveling","k":"Onboarding Guide","x":"Each distinct type node is assigned a Level by longest-path layering over its first-party dependencies (base/interface, generic constraints, field/property/param/return types,…"},"1197":{"u":"/docs/onboarding/00-dependency-manifest.html#level-distribution","d":"Phase 1: Dependency Manifest & Leveling","k":"Onboarding Guide","t":"Level distribution"},"1198":{"u":"/docs/onboarding/00-dependency-manifest.html#cycles-scc-size--1-46","d":"Phase 1: Dependency Manifest & Leveling","k":"Onboarding Guide","t":"Cycles (SCC size > 1): 46"},"1199":{"u":"/docs/onboarding/00-dependency-manifest.html#manifest-by-level-then-assembly","d":"Phase 1: Dependency Manifest & Leveling","k":"Onboarding Guide","t":"Manifest (by level, then assembly)"},"1200":{"u":"/docs/onboarding/00-group-taxonomy.html","d":"Phase 1b - Functional Group Taxonomy","k":"Onboarding Guide","x":"This is the primary axis of the guide. Every one of the 5,034 distinct first-party type nodes from 00-inventory.md is assigned to exactly one functional group - its primary home:…"},"1201":{"u":"/docs/onboarding/00-group-taxonomy.html#design-notes-boundary-decisions-worth-stating-up-front","d":"Phase 1b - Functional Group Taxonomy","k":"Onboarding Guide","t":"Design notes (boundary decisions worth stating up front)","x":"- Cycles are kept whole. The 13 dependency cycles (SCCs) from the manifest are never split across groups. Notably the ApplicationDbContext AuditSaveChangesInterceptor…"},"1202":{"u":"/docs/onboarding/00-group-taxonomy.html#the-groups-ordered","d":"Phase 1b - Functional Group Taxonomy","k":"Onboarding Guide","t":"The groups (ordered)","x":"Reconciliation: 2263 production types across 27 groups + 2771 test/testing types in G25 = 5034 (matches the inventory's distinct-node count). No type appears twice; none dropped.…"},"1203":{"u":"/docs/onboarding/00-group-taxonomy.html#group-membership","d":"Phase 1b - Functional Group Taxonomy","k":"Onboarding Guide","t":"Group membership","x":"group-01-result-error-handling.md 16 types The Result/Error railway that every operation returns instead of throwing; pagination result shapes. group-02-domain-building-blocks.md…"},"1204":{"u":"/docs/onboarding/00-inventory.html","d":"Phase 0: Type Inventory","k":"Onboarding Guide","x":"Generated mechanically by a Roslyn syntactic parse of every in-scope .cs file under MMCA.Common/Source, MMCA.Common/Tests, MMCA.ADC/Source, MMCA.ADC/Tests. - Files scanned: 3860…"},"1205":{"u":"/docs/onboarding/00-inventory.html#counts-by-kind","d":"Phase 0: Type Inventory","k":"Onboarding Guide","t":"Counts by kind"},"1206":{"u":"/docs/onboarding/00-inventory.html#counts-by-assembly-distinct-nodes","d":"Phase 0: Type Inventory","k":"Onboarding Guide","t":"Counts by assembly (distinct nodes)"},"1207":{"u":"/docs/onboarding/00-inventory.html#full-inventory","d":"Phase 0: Type Inventory","k":"Onboarding Guide","t":"Full inventory"},"1208":{"u":"/docs/onboarding/00-inventory.html#extensiont-blocks","d":"Phase 0: Type Inventory","k":"Onboarding Guide","t":"extension(T) blocks"},"1209":{"u":"/docs/onboarding/00-inventory.html#generated--excluded-artifacts-no-type-sections-written","d":"Phase 0: Type Inventory","k":"Onboarding Guide","t":"Generated / excluded artifacts (no type sections written)","x":"136 files excluded as generated (EF migrations, snapshots, .g.cs, AssemblyInfo)."},"1210":{"u":"/docs/onboarding/00-primer.html","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","x":"This chapter teaches the cross-cutting things once, so the per-type chapters can stay focused. Read it before the group chapters (start with group-01). Everything here is either…"},"1211":{"u":"/docs/onboarding/00-primer.html#1-the-big-picture","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"1. The big picture","x":"Two codebases are in scope: - MMCA.Common: a framework, published as twenty-two NuGet packages (the count and list are owned by MMCA.Common/FACTS.md) to nuget.org (the documented…"},"1212":{"u":"/docs/onboarding/00-primer.html#2-architectural-styles-this-codebase-commits-to","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"2. Architectural styles this codebase commits to","x":"These are the recurring ideas. Each is taught fully at its first concrete appearance in a group chapter; here is the orientation so the vocabulary is familiar. - Domain-Driven…"},"1213":{"u":"/docs/onboarding/00-primer.html#3-the-external-stack-bcl--nuget-external-level-0","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"3. The external stack (BCL / NuGet, \"external Level 0\")","x":"These are not first-party and get no per-type sections. Versions are from MMCA.Common/Directory.Packages.props and MMCA.ADC/Directory.Packages.props (Central Package Management,…"},"1214":{"u":"/docs/onboarding/00-primer.html#4-c-build-and-code-style-conventions","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"4. C#, build, and code-style conventions","x":"- .NET 10.0, LangVersion: preview: required because the codebase uses C extension types (extension(T) syntax, see below). - Central Package Management (CPM). All NuGet versions…"},"1215":{"u":"/docs/onboarding/00-primer.html#5-the-solution--test-layout","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"5. The solution / test layout","x":"- .slnx: the human solution (XML format). .slnf, a solution filter used in CI to build a subset fast (MMCA.Store.CI.slnf, MMCA.ADC.CI.slnf). - Microsoft Testing Platform, not…"},"1216":{"u":"/docs/onboarding/00-primer.html#6-the-34-category-architecture-evaluation-lens","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"6. The 34-category architecture-evaluation lens","x":"This codebase is also scored against a 34-category rubric (Website/docs-src/governance/ArchitectureEvaluationCriteria.md, published at ). This guide weaves the rubric in so you…"},"1217":{"u":"/docs/onboarding/group-01-result-error-handling.html","d":"1. Result & Error Handling","k":"Onboarding Guide","x":"What this group covers. This is the first capability chapter, and it is deliberately first because the pattern it teaches underpins almost every other one in the guide. Before…"},"1218":{"u":"/docs/onboarding/group-01-result-error-handling.html#why-a-return-value-instead-of-an-exception","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"Why a return value instead of an exception","x":"Exceptions are expensive (stack capture and unwinding), they are invisible in a method's signature, and they conflate programmer errors with business outcomes. \"This order ID…"},"1219":{"u":"/docs/onboarding/group-01-result-error-handling.html#three-pieces-classification-carrier-envelope","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"Three pieces: classification, carrier, envelope","x":"ErrorType (MMCA.Common/Source/Core/MMCA.Common.Shared/Abstractions/ErrorType.cs:10) is the classification axis, a nine-member enum whose per-member doc comments name the HTTP…"},"1220":{"u":"/docs/onboarding/group-01-result-error-handling.html#the-railway-in-one-picture","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"The railway, in one picture","x":"\"Railway-oriented programming\" is the mental model: imagine two parallel tracks, a success track and a failure track. An operation that takes the current result as input is…"},"1221":{"u":"/docs/onboarding/group-01-result-error-handling.html#construction-discipline-and-the-invariant-it-buys","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"Construction discipline, and the invariant it buys","x":"You cannot new a Result directly: its constructors are internal (Result.cs:211, Result.cs:217), so the only way to produce one is through the static factory methods on the base…"},"1222":{"u":"/docs/onboarding/group-01-result-error-handling.html#severity-not-position-classifies-a-failure","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"Severity, not position, classifies a failure","x":"Result.Combine aggregates errors in evaluation order, so if a transport picked the first error to classify the whole failure, an incidental validation failure evaluated early…"},"1223":{"u":"/docs/onboarding/group-01-result-error-handling.html#making-a-result-survive-a-round-trip","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"Making a result survive a round trip","x":"The construction discipline above creates a problem the moment a result must be serialized: because Result 's constructors are internal and its properties are get-only,…"},"1224":{"u":"/docs/onboarding/group-01-result-error-handling.html#how-a-failure-becomes-an-http-response","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"How a failure becomes an HTTP response","x":"Follow a typical write through the layers. A domain factory or aggregate method validates its inputs and returns Result.Combine(...); a command handler in the application layer…"},"1225":{"u":"/docs/onboarding/group-01-result-error-handling.html#and-how-the-same-failure-crosses-a-service-boundary-or-reaches-the-browser","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"And how the same failure crosses a service boundary, or reaches the browser","x":"On the gRPC side, ResultGrpcExtensions carries a mirror of that table, ErrorType to Grpc.Core.StatusCode, in its own FrozenDictionary…"},"1226":{"u":"/docs/onboarding/group-01-result-error-handling.html#the-two-exceptions-and-why-they-exist-anyway","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"The two exceptions, and why they exist anyway","x":"A return-value pattern still needs a fallback for the places where you genuinely cannot return one. DomainException…"},"1227":{"u":"/docs/onboarding/group-01-result-error-handling.html#offset-paging-the-read-side-envelopes","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"Offset paging: the read-side envelopes","x":"The read side needs shapes of its own. A query that returns a list wraps it in CollectionResult , a thin [DataContract] record…"},"1228":{"u":"/docs/onboarding/group-01-result-error-handling.html#keyset-paging-the-same-envelope-family-a-different-cost-model","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"Keyset paging: the same envelope family, a different cost model","x":"Offset paging answers \"give me page 37\" and costs the database a scan of the 36 pages before it. KeysetPageRequest…"},"1229":{"u":"/docs/onboarding/group-01-result-error-handling.html#where-this-leads","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"Where this leads","x":"With these sixteen types you have the full error-and-result vocabulary the rest of the guide assumes. You will see Result returned by the domain building blocks of Chapter 2…"},"1230":{"u":"/docs/onboarding/group-02-domain-building-blocks.html","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","x":"What this group covers. This is the DDD heart of the framework, the small, dependency-light primitives every business model in MMCA.Common and MMCA.ADC is built from. There are…"},"1231":{"u":"/docs/onboarding/group-02-domain-building-blocks.html#the-entity-chain-one-capability-per-rung","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"The entity chain, one capability per rung","x":"Read the chain bottom-up. BaseEntity (MMCA.Common/Source/Core/MMCA.Common.Domain/Entities/BaseEntity.cs:34) carries a single required init identifier of the per-entity alias…"},"1232":{"u":"/docs/onboarding/group-02-domain-building-blocks.html#three-opt-in-markers-beside-the-chain","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"Three opt-in markers beside the chain","x":"Not every cross-cutting capability belongs on the inheritance chain, because not every entity should pay for it. ITenantEntity…"},"1233":{"u":"/docs/onboarding/group-02-domain-building-blocks.html#how-a-domain-event-leaves-an-aggregate","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"How a domain event leaves an aggregate","x":"The runtime flow ties this group to the events/outbox group. A command handler loads an aggregate, calls a business method, and that method calls AddDomainEvent(...); the event…"},"1234":{"u":"/docs/onboarding/group-02-domain-building-blocks.html#value-objects-invalid-instances-cannot-exist","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"Value objects, invalid instances cannot exist","x":"The second family models concepts with no identity: two Money(10, USD) are equal because their values match, not because they are the same row. ValueObject is the cheapest…"},"1235":{"u":"/docs/onboarding/group-02-domain-building-blocks.html#smart-enumerations-a-closed-set-that-can-carry-behavior","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"Smart enumerations, a closed set that can carry behavior","x":"Enumeration (MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:76) is the answer to a recurring shape a CLR enum handles badly: a closed set of named members…"},"1236":{"u":"/docs/onboarding/group-02-domain-building-blocks.html#governance-markers-metadata-that-other-layers-act-on","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"Governance markers, metadata that other layers act on","x":"The last family is tiny attributes and helpers that carry intent the rest of the stack reads reflectively. PiiAttribute…"},"1237":{"u":"/docs/onboarding/group-02-domain-building-blocks.html#where-this-group-sits","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"Where this group sits","x":"Everything above is consumed by the layers that follow: every module entity (for example the Conference domain, Engagement, and Identity modules) derives from one of the three…"},"1238":{"u":"/docs/onboarding/group-03-querying-specifications.html","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","x":"What this group covers. Every read in MMCA.Common and ADC (\"list the published events\", \"get session 42\", \"the speakers in Atlanta, page 3, sorted by name, with only the name and…"},"1239":{"u":"/docs/onboarding/group-03-querying-specifications.html#the-specification-pattern-the-trusted-predicate","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"The Specification pattern, the trusted predicate","x":"ISpecification (MMCA.Common/Source/Core/MMCA.Common.Domain/Interfaces/ISpecification.cs:12) exposes two faces of one rule: a Criteria expression tree that EF Core translates to…"},"1240":{"u":"/docs/onboarding/group-03-querying-specifications.html#queryspecification-a-whole-read-in-one-object","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"QuerySpecification, a whole read in one object","x":"A plain specification is only a predicate, which leaves includes, ordering, paging, and tracking to be threaded through every layer as loose arguments. QuerySpecification…"},"1241":{"u":"/docs/onboarding/group-03-querying-specifications.html#dynamic-filtering-one-strategy-per-clr-type","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"Dynamic filtering, one Strategy per CLR type","x":"User filters arrive as a Dictionary , property name to operator key plus raw string value, parsed from the query string by QueryFilterModelBinder at the API edge, which caps a…"},"1242":{"u":"/docs/onboarding/group-03-querying-specifications.html#sorting-sparse-fieldsets-and-paging-arithmetic","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"Sorting, sparse fieldsets, and paging arithmetic","x":"QueryFieldService (MMCA.Common/Source/Core/MMCA.Common.Application/Services/QueryFieldService.cs:16) owns the rest of read shaping. ApplySorting (QueryFieldService.cs:183, with a…"},"1243":{"u":"/docs/onboarding/group-03-querying-specifications.html#the-pipeline-two-entity-paths-plus-projection-pushdown","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"The pipeline: two entity paths plus projection pushdown","x":"IEntityQueryPipeline (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/IEntityQueryPipeline.cs:10) is the execution contract, implemented by the sealed…"},"1244":{"u":"/docs/onboarding/group-03-querying-specifications.html#the-query-service-the-public-face","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"The query service, the public face","x":"IEntityQueryService (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Mapping/IEntityQueryService.cs:19) and its concrete EntityQueryService…"},"1245":{"u":"/docs/onboarding/group-03-querying-specifications.html#end-to-end-one-list-request","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"End to end, one list request","x":"The request reaches a read controller, EntityControllerBase (Group 12), which resolves MaxPageSize per request from ApplicationSettings, falling back to 500 when unset…"},"1246":{"u":"/docs/onboarding/group-03-querying-specifications.html#query-tags-naming-the-use-case-behind-a-statement","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"Query tags, naming the use case behind a statement","x":"Two types in the Services/Query folder serve the operator reading a query store rather than the read path itself. QueryTagScope…"},"1247":{"u":"/docs/onboarding/group-03-querying-specifications.html#also-filed-here-best-effort-dispatch-and-the-upcaster-registry","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"Also filed here: best-effort dispatch and the upcaster registry","x":"Four types in this group are not part of the read path at all; they are co-located in MMCA.Common.Application/Services and are grouped by that folder. BestEffort…"},"1248":{"u":"/docs/onboarding/group-04-events-outbox.html","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","x":"What this chapter covers. This group is the codebase's event spine: how an aggregate says \"something happened\", how that fact is persisted so it cannot be lost, and how it…"},"1249":{"u":"/docs/onboarding/group-04-events-outbox.html#the-two-kinds-of-event","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"The two kinds of event","x":"Everything starts with two marker interfaces in the Domain layer. IDomainEvent is the base contract: a DateOccurred timestamp (when the business action happened, not when it was…"},"1250":{"u":"/docs/onboarding/group-04-events-outbox.html#stored-identity-what-a-row-remembers-an-event-as","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Stored identity: what a row remembers an event as","x":"An event in memory is a CLR type; an event in a table is a string. Which string it is decides whether a row survives a refactoring, and EventNameResolver is the single cached…"},"1251":{"u":"/docs/onboarding/group-04-events-outbox.html#raising-and-capturing-where-the-outbox-is-written","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Raising and capturing: where the outbox is written","x":"Aggregates raise events by calling AddDomainEvent() (see AuditableAggregateRootEntity in G02), which simply buffers them on the entity. Nothing is dispatched yet; the events ride…"},"1252":{"u":"/docs/onboarding/group-04-events-outbox.html#the-routing-split-local-events-dispatch-in-process-integration-events-wait-for-the-bus","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"The routing split: local events dispatch in-process, integration events wait for the bus","x":"Here is the detail that most people get wrong, and it is the heart of the design. After the transaction commits (SavedChanges), the interceptor does not treat all captured events…"},"1253":{"u":"/docs/onboarding/group-04-events-outbox.html#who-the-delivery-runs-as-the-captured-origin","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Who the delivery runs as: the captured origin","x":"A delivered event runs a poll cycle later, on another thread and possibly in another process, so without help it would run as an anonymous, tenant-less system call: a handler…"},"1254":{"u":"/docs/onboarding/group-04-events-outbox.html#the-safety-net-how-the-processor-schedules-itself","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"The safety net: how the processor schedules itself","x":"The OutboxProcessor is a BackgroundService and the most intricate type in the group; most of its complexity is about not wasting work. It exists because the steps between commit…"},"1255":{"u":"/docs/onboarding/group-04-events-outbox.html#claiming-scale-out-and-ordered-delivery","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Claiming, scale-out, and ordered delivery","x":"Because a deployment may run more than one replica, each cycle claims its eligible prefix with a lease (LockedUntil plus LockToken,…"},"1256":{"u":"/docs/onboarding/group-04-events-outbox.html#failures-dead-letters-and-keeping-the-table-and-telemetry-bounded","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Failures, dead-letters, and keeping the table (and telemetry) bounded","x":"Delivery failures split into outcomes worth keeping straight. A transient failure (a handler or broker publish throwing) increments the row's RetryCount, records LastError, and…"},"1257":{"u":"/docs/onboarding/group-04-events-outbox.html#the-pluggable-transport-and-the-posture-flags-that-decide-it","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"The pluggable transport, and the posture flags that decide it","x":"Here is the boundary that makes a module extractable without rewriting its handlers. Application code that wants to publish an integration event depends on IEventBus (or on the…"},"1258":{"u":"/docs/onboarding/group-04-events-outbox.html#consuming-from-the-broker-the-inbox-and-the-generic-consumer","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Consuming from the broker: the inbox and the generic consumer","x":"On the receiving side of a broker hop, application code keeps writing plain IIntegrationEventHandler implementations; there is no MassTransit-specific consumer class to author…"},"1259":{"u":"/docs/onboarding/group-04-events-outbox.html#putting-it-together-one-events-life","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Putting it together, one event's life","x":"To see the whole spine at once, follow a single integration event from a producer service to a consumer service in broker mode. (1) A command mutates an aggregate, which raises…"},"1260":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","x":"What this group covers. Every write and every read in an MMCA application is a use case: a small, single-purpose object (a command or a query) handed to a handler that does…"},"1261":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#the-shape-thin-handlers-fat-pipeline","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"The shape: thin handlers, fat pipeline","x":"A handler is deliberately tiny. ICommandHandler (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Contracts/ICommandHandler.cs:9) and IQueryHandler…"},"1262":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#how-the-pipeline-is-assembled-scrutor-registration-versus-execution-order","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"How the pipeline is assembled (Scrutor, registration versus execution order)","x":"The wiring lives in the partial DependencyInjection class (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:24, with the module scan in…"},"1263":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#why-this-exact-order-and-what-each-layer-guards","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Why this exact order, and what each layer guards","x":"The nesting order is a deliberate cost-and-correctness argument, spelled out in the registration XML-doc (DependencyInjection.cs:87-109): - Feature-gating is outermost so a…"},"1264":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#opt-in-by-marker-interface-pay-only-for-what-you-use","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Opt-in by marker interface, pay only for what you use","x":"The pipeline is registered for every handler, but most decorators are dormant unless the use case asks for them. The switch is a set of tiny marker / role interfaces in…"},"1265":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#the-generic-write-side-three-verbs-a-module-does-not-have-to-write","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"The generic write side: three verbs a module does not have to write","x":"Below the pipeline sits a second body of shared code: the workflows every aggregate write repeats. They are ordinary handlers, so the decorators wrap them exactly as they wrap a…"},"1266":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#declaring-the-contract-the-opt-in-request-markers","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Declaring the contract: the opt-in request markers","x":"ICommand (.../UseCases/ICommand.cs:31) and IQuery (.../UseCases/IQuery.cs:26) let a request DTO declare the result type its handler produces. Both are body-less interfaces whose…"},"1267":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#tenant-scoping-and-the-two-lock-tables","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Tenant scoping and the two lock tables","x":"Both caching decorators are multi-tenant aware, and the reason is a nice illustration of where a cross-cutting concern has to live. ICacheService is a singleton and therefore…"},"1268":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#two-supporting-pieces","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Two supporting pieces","x":"Two small helpers make the short-circuit decorators possible. ResultFailureFactory…"},"1269":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#the-other-application-layer-contracts-in-this-group","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"The other Application-layer contracts in this group","x":"Several contracts sit beside the pipeline rather than inside it. They are declared here, in the Application layer, and implemented in Infrastructure or the composition root,…"},"1270":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#where-this-fits-and-the-failure-mode-contract","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Where this fits, and the failure-mode contract","x":"These contracts sit in the Application layer of Clean Architecture (primer §1), above Domain and below Infrastructure and the API. The API layer (G12) resolves a closed handler…"},"1271":{"u":"/docs/onboarding/group-06-validation.html","d":"6. Validation","k":"Onboarding Guide","x":"What this group covers. This is the framework-level validation kit that MMCA.Common.Application ships so every module validates input the same way. It has four parts: (1) a set…"},"1272":{"u":"/docs/onboarding/group-07-persistence-ef-core.html","d":"7. Persistence & EF Core","k":"Onboarding Guide","x":"What this group covers. This is the framework's data-access engine: everything between a domain aggregate and a row in a database. It is the single largest group in the guide…"},"1273":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#one-base-context-one-class-per-engine-one-instance-per-database","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"One base context, one class per engine, one instance per database","x":"ApplicationDbContext (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:46) is an abstract primary-constructor class over EF's…"},"1274":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#savechanges-as-an-interceptor-pipeline","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"SaveChanges as an interceptor pipeline","x":"The base context resolves its interceptors from DI in OnConfiguring (ApplicationDbContext.cs:285-317), and registration order is execution order. The audit interceptor runs…"},"1275":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#deferred-work-the-internal-command-queue","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Deferred work, the internal-command queue","x":"The same \"write it in the transaction, run it afterwards\" reasoning the outbox applies to events is applied to work by the internal-command queue (ADR-114).…"},"1276":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#the-tenant-boundary-read-filter-plus-write-guard","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"The tenant boundary, read filter plus write guard","x":"Multi-tenancy (ADR-073) is two independent halves that meet in this group. The read half is the named Tenant query filter the base context applies to every non-owned…"},"1277":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#recording-what-changed-the-audit-trail","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Recording what changed, the audit trail","x":"AuditTrailSaveChangesInterceptor (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:63) is the fourth interceptor…"},"1278":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#repositories-specifications-and-the-unit-of-work","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Repositories, specifications, and the unit of work","x":"Handlers do not touch a DbContext directly. They ask a UnitOfWork (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:13) for a repository. The…"},"1279":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#routing-an-entity-to-its-database","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Routing an entity to its database","x":"The heart of ADR-006 is that every entity resolves to a DataSourceKey (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/DataSourceKey.cs:15),…"},"1280":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#three-model-finalizing-conventions-and-the-identifier-mapping","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Three model-finalizing conventions and the identifier mapping","x":"The base context adds all three of its conventions in ConfigureConventions (ApplicationDbContext.cs:373-394), and each exists because a cross-cutting policy above would otherwise…"},"1281":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#entity-configuration-and-engine-portability","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Entity configuration and engine portability","x":"Concrete entity configurations derive from the engine-aware EntityTypeConfiguration…"},"1282":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#two-tables-one-database-owns-refresh-sessions-and-permission-grants","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Two tables one database owns, refresh sessions and permission grants","x":"Most of the framework's own tables are infrastructure every relational source needs. Two are not: both are Identity-module data that exactly one database owns, so both are mapped…"},"1283":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#encryption-seeding-design-time-and-the-shared-helpers","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Encryption, seeding, design time, and the shared helpers","x":"A handful of supporting pieces round out the EF side. EncryptedStringConverter…"},"1284":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#the-storage-image-and-push-contracts","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"The storage, image, and push contracts","x":"The group also carries the Application-layer contracts for the storage-adjacent services that are not EF at all. Each has a null default in the container so a host that has not…"},"1285":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#where-this-group-sits","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Where this group sits","x":"Persistence is the concrete floor the abstract domain stands on. The entity bases and audit contracts from Group 02 are what the interceptors stamp and the query filters hide;…"},"1286":{"u":"/docs/onboarding/group-08-auth.html","d":"8. Authentication & Authorization","k":"Onboarding Guide","x":"What this group covers. This is the security spine of the framework: how a caller proves who they are (authentication), how the system decides what they may do (authorization),…"},"1287":{"u":"/docs/onboarding/group-08-auth.html#tokens-one-signing-switch-two-validation-worlds","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Tokens: one signing switch, two validation worlds","x":"The framework mints two credentials on every successful sign-in: a short-lived access token (a JWT, 15 minutes by default,…"},"1288":{"u":"/docs/onboarding/group-08-auth.html#the-shared-authentication-workflow","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"The shared authentication workflow","x":"Login, registration, refresh, revocation, and device listing are not re-implemented per app. They live once in AuthenticationServiceBase…"},"1289":{"u":"/docs/onboarding/group-08-auth.html#refresh-sessions-one-row-per-device","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Refresh sessions: one row per device","x":"A refresh token is no longer a column on the user row. Every issue opens its own RefreshSession (MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:31), so signing…"},"1290":{"u":"/docs/onboarding/group-08-auth.html#what-the-apps-user-aggregate-must-expose","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"What the app's User aggregate must expose","x":"The shared workflows never see an app's User class. They see four small Domain-layer contracts, each sized to one workflow, which is the [Rubric §1, SOLID] interface-segregation…"},"1291":{"u":"/docs/onboarding/group-08-auth.html#passwords-and-brute-force-protection","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Passwords and brute-force protection","x":"Password material is handled by PasswordHasher (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordHasher.cs:12), which hashes with PBKDF2-HMAC-SHA512 at 600,000…"},"1292":{"u":"/docs/onboarding/group-08-auth.html#the-second-factor-one-optional-step-in-the-same-sign-in","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"The second factor: one optional step in the same sign-in","x":"Two-factor authentication ships as contracts plus a stateless implementation, never as a feature that turns itself on: the account row belongs to the app, and making a second…"},"1293":{"u":"/docs/onboarding/group-08-auth.html#forgot-password-a-cache-backed-single-use-token","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Forgot password: a cache-backed single-use token","x":"A user who has lost the password cannot present one, so this flow is anonymous by necessity, which makes every one of its responses a potential account-enumeration oracle. It is…"},"1294":{"u":"/docs/onboarding/group-08-auth.html#email-confirmation-the-same-token-shape-a-different-prefix","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Email confirmation: the same token shape, a different prefix","x":"Confirming an address is the forgot-password flow's twin, and it is built that way on purpose: member for member, IEmailConfirmationTokenService…"},"1295":{"u":"/docs/onboarding/group-08-auth.html#reading-identity-from-claims","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Reading identity from claims","x":"Once a request is authenticated, downstream code needs the caller's identity without re-parsing the JWT, and it needs one answer no matter which pipeline produced the principal.…"},"1296":{"u":"/docs/onboarding/group-08-auth.html#authorization-permissions-and-ownership","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Authorization: permissions and ownership","x":"There is one authorization model here, and it is capabilities, not role names. The single AddAuthorizationPolicies() extension in AuthorizationExtensions…"},"1297":{"u":"/docs/onboarding/group-08-auth.html#session-cookies-keeping-ssr-authenticated","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Session cookies: keeping SSR authenticated","x":"The final cluster solves a Blazor-specific problem: an interactive Blazor app keeps its access token in browser memory, but a cold server-side render (a new tab, an F5, an…"},"1298":{"u":"/docs/onboarding/group-08-auth.html#privacy-the-data-subject-export-package","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Privacy: the data-subject export package","x":"Three members of this group belong to the privacy surface that sits beside erasure. UserDataExportDTO (MMCA.Common/Source/Core/MMCA.Common.Shared/Privacy/UserDataExportDTO.cs:15)…"},"1299":{"u":"/docs/onboarding/group-08-auth.html#shared-primitives-and-adjacent-members","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Shared primitives and adjacent members","x":"Several group members are general-purpose primitives that landed in this chapter because of how the dependency grouping fell, though one of them is now load-bearing for auth.…"},"1300":{"u":"/docs/onboarding/group-09-caching.html","d":"9. Caching","k":"Onboarding Guide","x":"What this group covers. Caching in this codebase is small, deliberate, and woven into the CQRS pipeline rather than scattered across handlers. The group is nine types: one port…"},"1301":{"u":"/docs/onboarding/group-10-notifications.html","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","x":"What this group covers. This is the notification subsystem, the machinery that turns \"an organizer wants to tell every attendee something\" into messages that actually reach…"},"1302":{"u":"/docs/onboarding/group-10-notifications.html#the-layering-and-why-the-pieces-sit-where-they-do","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"The layering, and why the pieces sit where they do","x":"The dependency flow of the group mirrors the framework's Clean Architecture story ([Rubric §3, Clean Architecture]). The Domain layer holds the two aggregates, PushNotification…"},"1303":{"u":"/docs/onboarding/group-10-notifications.html#the-broadcast-send-flow-end-to-end","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"The broadcast send flow, end to end","x":"Sending a notification is a command-side vertical slice ([Rubric §5, Vertical Slice], [Rubric §6, CQRS & Event-Driven]). An organizer POSTs to NotificationsController, which is…"},"1304":{"u":"/docs/onboarding/group-10-notifications.html#scope-keys-one-string-that-narrows-a-broadcast-an-inbox-and-a-live-channel","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"Scope keys: one string that narrows a broadcast, an inbox, and a live channel","x":"NotificationScopeKey is the small type that ties three otherwise unrelated paths together. It owns the format (event:{id} via ForEvent, session:{id} via ForSession, both rendered…"},"1305":{"u":"/docs/onboarding/group-10-notifications.html#the-inbox-side","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"The inbox side","x":"Reading and acknowledging notifications is the query/command counterpart, served by InboxController under the same feature gate and a plain [Authorize], so any authenticated user…"},"1306":{"u":"/docs/onboarding/group-10-notifications.html#the-signalr-transport-and-how-it-survives-extraction","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"The SignalR transport, and how it survives extraction","x":"NotificationHub is intentionally thin (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Notifications/NotificationHub.cs:24-27): it is [Authorize]d, and beyond ASP.NET's…"},"1307":{"u":"/docs/onboarding/group-10-notifications.html#the-module-host-native-device-registration-and-the-privacy-export","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"The module host, native-device registration, and the privacy export","x":"On the ADC side the whole capability is packaged by NotificationModule (MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.API/NotificationModule.cs:15), an IModule that…"},"1308":{"u":"/docs/onboarding/group-10-notifications.html#where-this-group-sits","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"Where this group sits","x":"Upstream, this group depends on the domain building blocks of Group 02 (both aggregates derive from AuditableAggregateRootEntity ), the Result pattern of Group 01, the CQRS…"},"1309":{"u":"/docs/onboarding/group-11-navigation-populators.html","d":"11. Navigation Metadata & Populators (EF-decoupled eager loading)","k":"Onboarding Guide","x":"EF Core gives you .Include() for eager loading, and for a single SQL Server database that is the right tool. But this codebase is a database-per-service modular monolith…"},"1310":{"u":"/docs/onboarding/group-12-api-hosting-mapping.html","d":"12. API Hosting, Middleware, Idempotency & DTO/Contract Mapping","k":"Onboarding Guide","x":"What this group covers. This is the ASP.NET Core edge of the framework: the layer that turns an HTTP request into a domain call and turns a Result back into an HTTP response.…"},"1311":{"u":"/docs/onboarding/group-13-grpc-contracts.html","d":"13. gRPC & Inter-Service Contracts","k":"Onboarding Guide","x":"What this chapter is about. Once the ADC modules stopped sharing a process and became four separate service hosts (Identity, Conference, Engagement, Notification), the in-process…"},"1312":{"u":"/docs/onboarding/group-14-module-system-composition.html","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","x":"What this chapter covers. This is the wiring layer, the code that turns a pile of layered assemblies into a running host. It answers three questions a new host author asks: how…"},"1313":{"u":"/docs/onboarding/group-14-module-system-composition.html#the-module-contract-and-the-boundary-it-creates","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"The module contract and the boundary it creates","x":"A module is the unit of cohesion above a feature slice: Conference, Engagement, Identity, Notification. Each one implements IModule…"},"1314":{"u":"/docs/onboarding/group-14-module-system-composition.html#discovery-and-kahn-ordered-registration","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Discovery and Kahn-ordered registration","x":"ModuleLoader (MMCA.Common/Source/Core/MMCA.Common.Application/Modules/ModuleLoader.cs:15) is the engine. Its one DiscoverAndRegister overload (ModuleLoader.cs:58-64) takes the…"},"1315":{"u":"/docs/onboarding/group-14-module-system-composition.html#the-two-composition-roots-and-the-pipeline-that-seals-them","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"The two composition roots, and the pipeline that seals them","x":"Service registration itself lives in two static DependencyInjection classes, each using a C extension(IServiceCollection services) block (see primer §4 for the extension(T)…"},"1316":{"u":"/docs/onboarding/group-14-module-system-composition.html#opt-in-platform-features-are-composed-the-same-way","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Opt-in platform features are composed the same way","x":"Four capabilities are registered beside the roots rather than inside them, and they share one discipline: registering a feature is not the same as turning it on.…"},"1317":{"u":"/docs/onboarding/group-14-module-system-composition.html#deferred-work-and-the-context-that-travels-with-it","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Deferred work, and the context that travels with it","x":"The last two things AddInfrastructure composes are a queue for work a request wants done later, and the machinery that lets whatever drains a queue act as the identity that…"},"1318":{"u":"/docs/onboarding/group-14-module-system-composition.html#assembly-anchors","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Assembly anchors","x":"Several pieces of machinery need a Type whose Assembly identifies a layer: Scrutor's FromAssemblyOf () scans, FluentValidation's AddValidatorsFromAssemblyContaining (), and…"},"1319":{"u":"/docs/onboarding/group-14-module-system-composition.html#configuration-binding-the-settings-family","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Configuration binding, the Settings family","x":"Everything a host operator tunes arrives as a strongly-typed settings object bound from an appsettings.json section, each carrying a static readonly string SectionName so the…"},"1320":{"u":"/docs/onboarding/group-14-module-system-composition.html#the-two-routing-attributes","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"The two routing attributes","x":"Two attributes, both in MMCA.Common.Infrastructure, both Inherited = true so they ride down a configuration class hierarchy, encode where an entity is stored declaratively: the…"},"1321":{"u":"/docs/onboarding/group-14-module-system-composition.html#shared-user-use-case-bases-composition-in-the-other-direction","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Shared user use-case bases: composition in the other direction","x":"The chapter's last family is composition at the handler level rather than the container level. ADC and Store each own an Identity module, and thirteen of their account use cases…"},"1322":{"u":"/docs/onboarding/group-14-module-system-composition.html#end-to-end-one-hosts-boot","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"End-to-end: one host's boot","x":"Reading MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs top to bottom shows the whole chapter cooperating. The host calls AddInfrastructure(builder.Configuration)…"},"1323":{"u":"/docs/onboarding/group-15-common-ui-framework.html","d":"15. Common UI Framework (MudBlazor components, theme, base pages)","k":"Onboarding Guide","x":"What this chapter covers. MMCA.Common.UI is the Blazor presentation package, and it is one of the two layers (with Grpc) allowed to reference Shared only: its single…"},"1324":{"u":"/docs/onboarding/group-16-aspire-orchestration.html","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","x":"This chapter covers the hosting boundary: how the distributed MMCA system is composed and run, locally as a single dotnet run, and in Azure Container Apps (ACA) as a set of…"},"1325":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#the-orchestrator-declaring-the-resource-graph","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"The orchestrator: declaring the resource graph","x":"When you run dotnet run --project Source/Hosting/MMCA.ADC.AppHost, Aspire executes Program.cs top to bottom, building a resource model rather than starting anything immediately.…"},"1326":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#broker-parity-rabbitmq-by-default-the-service-bus-emulator-on-demand","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Broker parity: RabbitMQ by default, the Service Bus emulator on demand","x":"RabbitMQ is a different broker from the one production runs, so anything that only misbehaves on Azure Service Bus (entity-name limits, topic and subscription provisioning,…"},"1327":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#startup-ordering-health-gated-waits-and-the-grpc-deadlock-avoidance-trick","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Startup ordering, health-gated waits, and the gRPC deadlock-avoidance trick","x":"Aspire's WaitFor builds a startup dependency graph from the resource model: a service does not start until the resources it waits on report healthy. What \"healthy\" means for a…"},"1328":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#the-service-baseline-addservicedefaults","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"The service baseline: AddServiceDefaults()","x":"Every running host calls one method early in Program.cs: AddServiceDefaults() from the single framework-grade Extensions in MMCA.Common.Aspire (Level 11,…"},"1329":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#one-source-of-truth-for-resilience-numbers","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"One source of truth for resilience numbers","x":"The numbers that pipeline uses are not literals in the Aspire package. They live in HttpResilienceDefaults (Level 0,…"},"1330":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#listeners-and-probes-one-kestrel-profile-per-host-shape","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Listeners and probes: one Kestrel profile per host shape","x":"Before a service can answer a health probe it has to be listening on a protocol the prober speaks, and that is not free when the same port serves inbound gRPC.…"},"1331":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#health-checks-liveness-readiness-and-the-optional-tag","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Health checks: liveness, readiness, and the \"optional\" tag","x":"MapDefaultEndpoints() (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.Health.cs:123) exposes the three-probe surface the platform reads: /health (every check, for…"},"1332":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#telemetry-what-gets-exported-and-what-it-costs","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Telemetry: what gets exported, and what it costs","x":"ConfigureOpenTelemetry() (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.Telemetry.cs:71) wires logging with formatted messages and scopes (:73-77), metrics, and…"},"1333":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#warm-up-defeating-aca-cold-start","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Warm-up: defeating ACA cold-start","x":"The warm-up subsystem exists for one concrete failure mode: the \"first request fails, second succeeds\" pattern on a CPU-throttled idle ACA replica, where lazy initialization…"},"1334":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#configuration-secrets-the-vault-as-one-more-configuration-source","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Configuration secrets: the vault as one more configuration source","x":"Connection strings, RSA signing keys and OAuth client secrets have to reach the process somehow, and KeyVaultConfigurationExtensions (Level 0,…"},"1335":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#security-headers-cors-and-the-shared-key-ring-at-the-host-edge","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Security headers, CORS, and the shared key ring at the host edge","x":"The next boundary in this group hardens the request and response edge. The shared response-header middleware is defined entirely in…"},"1336":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#the-gateway-edge-kit-correlation-rate-limiting-downstream-readiness","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"The gateway edge kit: correlation, rate limiting, downstream readiness","x":"A YARP gateway is the one process every client request passes through, and it is also the one host that has no application container: no DbContext, no module loader, no…"},"1337":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#the-yarp-building-blocks-cluster-profiles-ejection-trace-headers-route-policies","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"The YARP building blocks: cluster profiles, ejection, trace headers, route policies","x":"The route table itself is configuration, not code (ADR-089), and MMCA.Common.Gateway deliberately does not load it: LoadFromConfig (or any other IProxyConfigProvider) stays the…"},"1338":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#how-it-all-fits-at-runtime","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"How it all fits at runtime","x":"Putting the pieces in sequence: the AppHost declares the graph, picks a broker once, and injects per-service env vars (WithSQLServerDataSource, WithSelectedBroker,…"},"1339":{"u":"/docs/onboarding/group-17-conference-domain.html","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","x":"What this chapter covers. This is the heart of the Atlanta Developers Conference application, the Conference bounded context, the largest and richest domain in MMCA.ADC. It…"},"1340":{"u":"/docs/onboarding/group-17-conference-domain.html#two-packages-one-bounded-context","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Two packages, one bounded context","x":"The Conference context spans two of the module's projects, and the split is deliberate Clean Architecture ([Rubric §3, Clean Architecture]). MMCA.ADC.Conference.Domain holds the…"},"1341":{"u":"/docs/onboarding/group-17-conference-domain.html#ten-aggregates-and-their-ownership-boundaries","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Ten aggregates and their ownership boundaries","x":"An aggregate is a root entity plus the children it exclusively owns; invariants are enforced inside the boundary, and references across aggregates are by ID, never by object…"},"1342":{"u":"/docs/onboarding/group-17-conference-domain.html#the-aggregate-shape-taught-once","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"The aggregate shape, taught once","x":"Open any of the roots and you will see the same skeleton; this repetition is the point, and it is what makes the per-type sections that follow read quickly. The shape, using…"},"1343":{"u":"/docs/onboarding/group-17-conference-domain.html#invariants-business-rules-as-testable-units","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Invariants, business rules as testable units","x":"Each aggregate has a co-located static invariant class, EventInvariants (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:14),…"},"1344":{"u":"/docs/onboarding/group-17-conference-domain.html#domain-events-and-the-outbox-spine","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Domain events and the outbox spine","x":"Every state-changing method raises a domain event through the inherited AddDomainEvent(...), and the eighteen events come in two shapes with two different base types. The nine…"},"1345":{"u":"/docs/onboarding/group-17-conference-domain.html#the-cross-aggregate-cascade-a-pure-domain-service","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"The cross-aggregate cascade: a pure domain service","x":"One business rule cannot live inside a single aggregate: deleting an Event must also delete every Session belonging to it (BR-127), every Sponsor sold against it, every Partner…"},"1346":{"u":"/docs/onboarding/group-17-conference-domain.html#read-models-and-the-ai-decision-support-feature","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Read models and the AI decision-support feature","x":"The largest cluster in Conference.Shared is the DTO layer, the wire contracts that decouple the API from the domain entities ([Rubric §9, API & Contract Design]; ADR-001 chose…"},"1347":{"u":"/docs/onboarding/group-17-conference-domain.html#authorization-vocabulary-and-current-event-selection","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Authorization vocabulary and current-event selection","x":"Two more Shared helpers deserve a mention because they encode policy the whole module relies on. ConferencePermissions…"},"1348":{"u":"/docs/onboarding/group-17-conference-domain.html#crossing-the-module-boundary-contracts-stubs-and-integration-events","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Crossing the module boundary: contracts, stubs, and integration events","x":"Conference does not live alone. Three kinds of connection point join it to other modules, and all live in Conference.Shared so neither side reaches into the other's domain…"},"1349":{"u":"/docs/onboarding/group-17-conference-domain.html#end-to-end-one-organizer-action","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"End-to-end: one organizer action","x":"To see the chapter cooperate, follow an organizer renaming a room on an event. The application handler loads the Event aggregate (with its Rooms hydrated by the navigation…"},"1350":{"u":"/docs/onboarding/group-18-conference-application.html","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","x":"What this chapter covers. This is the application layer of the Conference module, the largest single application assembly in the codebase (this group covers 358 entries in the…"},"1351":{"u":"/docs/onboarding/group-18-conference-application.html#the-vertical-slice-anatomy-of-a-use-case","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"The vertical-slice anatomy of a use case","x":"Open any feature folder under Sessions/UseCases/, Events/UseCases/, Speakers/UseCases/, Sponsors/UseCases/, Partners/UseCases/, Activities/UseCases/, Categories/UseCases/, or…"},"1352":{"u":"/docs/onboarding/group-18-conference-application.html#manual-mapping-validation-rule-fragments-and-authorization-specifications","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Manual mapping, validation rule fragments, and authorization specifications","x":"Three sibling families recur across every aggregate. DTO mappers (SessionDTOMapper, EventDTOMapper, SpeakerDTOMapper, SponsorDTOMapper, PartnerDTOMapper…"},"1353":{"u":"/docs/onboarding/group-18-conference-application.html#query-services-navigation-populators-and-the-composition-root","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Query services, navigation populators, and the composition root","x":"Read paths do not get bespoke handlers for the common cases; they go through the framework's generic IEntityQueryService , which supplies filtering, sorting, paging, and field…"},"1354":{"u":"/docs/onboarding/group-18-conference-application.html#event-driven-reactions-domain-and-integration-handlers","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Event-driven reactions: domain and integration handlers","x":"The application layer is also where the module reacts to events. The module keeps exactly one domain event handler, and it is the one with a real side effect.…"},"1355":{"u":"/docs/onboarding/group-18-conference-application.html#attendee-facing-read-models-calendar-export-and-nownext","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Attendee-facing read models: calendar export and Now/Next","x":"A small cluster of queries serves the public schedule surfaces without going through the generic query service, because their output is not a DTO list. ExportEventCalendarHandler…"},"1356":{"u":"/docs/onboarding/group-18-conference-application.html#session-materials-authorization-as-data-blob-cleanup-as-a-durable-command","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Session materials: authorization as data, blob cleanup as a durable command","x":"Speakers publish their slides and links against their own sessions, which makes the SessionAssets slice the one write surface in this module where the right to act is data rather…"},"1357":{"u":"/docs/onboarding/group-18-conference-application.html#the-sessionize-import-strategy-pattern-orchestration","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"The Sessionize import: Strategy-pattern orchestration","x":"The single most involved use case is importing a conference agenda from Sessionize.com. Sessionize returns one JSON payload covering five interdependent entity families…"},"1358":{"u":"/docs/onboarding/group-18-conference-application.html#decision-support-ai-scoring-and-content-analytics","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Decision support: AI scoring and content analytics","x":"The last cluster is session-selection decision support, analytics that help organizers triage proposals. GetSessionSelectionDashboardHandler is a composite query: it validates…"},"1359":{"u":"/docs/onboarding/group-19-conference-infrastructure.html","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","x":"What this chapter covers. This is the adapter layer of the Conference module, the place where the engine-agnostic domain meets concrete technology. Three concerns live here: (1)…"},"1360":{"u":"/docs/onboarding/group-19-conference-infrastructure.html#engine-agnostic-entities-engine-chosen-by-the-config-base-class","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"Engine-agnostic entities, engine chosen by the config base class","x":"The most important idea in this chapter is one the entities themselves never express: what storage engine each entity uses is decided here, not in the domain. A Conference domain…"},"1361":{"u":"/docs/onboarding/group-19-conference-infrastructure.html#each-config-inherits-the-cross-cutting-behavior-then-adds-entity-specifics","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"Each config inherits the cross-cutting behavior, then adds entity specifics","x":"Every configuration's Configure method begins with base.Configure(builder) (for example SessionConfiguration.cs:18, ActivityConfiguration.cs:17) and then adds its own mappings.…"},"1362":{"u":"/docs/onboarding/group-19-conference-infrastructure.html#dbsets-the-context-shape-and-how-the-configurations-are-actually-found","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"DbSets, the context shape, and how the configurations are actually found","x":"ModuleApplicationDbContext (MMCA.ADC.Conference.Infrastructure/Persistence/DbContexts/ModuleApplicationDbContext.cs:22) is the Conference module's abstract DbContext. It does one…"},"1363":{"u":"/docs/onboarding/group-19-conference-infrastructure.html#seeding-two-real-events-always-sample-data-only-in-dev-and-ci","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"Seeding: two real events always, sample data only in dev and CI","x":"ConferenceModuleDbSeeder (MMCA.ADC.Conference.Infrastructure/Persistence/DbContexts/Seeding/ConferenceModuleDbSeeder.cs:27) derives from the framework's DbSeeder and runs after…"},"1364":{"u":"/docs/onboarding/group-19-conference-infrastructure.html#the-sessionize-adapter","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"The Sessionize adapter","x":"SessionizeService (MMCA.ADC.Conference.Infrastructure/Events/Sessionize/SessionizeService.cs:12) is a deliberately thin HTTP client: the whole class is one method…"},"1365":{"u":"/docs/onboarding/group-19-conference-infrastructure.html#the-ai-scoring-adapter-and-its-response-guardrail","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"The AI scoring adapter and its response guardrail","x":"SessionScoringService (MMCA.ADC.Conference.Infrastructure/Sessions/Scoring/SessionScoringService.cs:46) is the richer of the two adapters: it scores one session proposal against…"},"1366":{"u":"/docs/onboarding/group-19-conference-infrastructure.html#the-scoring-run-is-durable-now-and-this-layer-keeps-one-adapter-inside-it","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"The scoring run is durable now, and this layer keeps one adapter inside it","x":"A multi-minute paid AI pass triggered from an HTTP POST used to be this chapter's biggest piece of machinery: a hosted BackgroundService draining an in-memory queue, plus a…"},"1367":{"u":"/docs/onboarding/group-19-conference-infrastructure.html#di-wiring-and-what-it-deliberately-no-longer-registers","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"DI wiring, and what it deliberately no longer registers","x":"DependencyInjection (MMCA.ADC.Conference.Infrastructure/DependencyInjection.cs:18) is a single extension(IServiceCollection) block (the codebase's standard DI-registration idiom,…"},"1368":{"u":"/docs/onboarding/group-19-conference-infrastructure.html#how-it-fits-together-at-runtime","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"How it fits together at runtime","x":"Three flows tie the chapter together. Persistence flow: a Conference command handler mutates an aggregate and the unit of work saves; that resolves the concrete…"},"1369":{"u":"/docs/onboarding/group-20-conference-api-grpc.html","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","x":"What this chapter covers. This is the edge of the Conference bounded context: the layer that turns the Conference domain (G17) and its CQRS slices (G18) into a running HTTP +…"},"1370":{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-controller-hierarchy-almost-everything-is-inherited","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The controller hierarchy, almost everything is inherited","x":"The Conference API exposes twenty-three controllers, and the striking thing about them is how little code each carries. They split into three structural families, all built on…"},"1371":{"u":"/docs/onboarding/group-20-conference-api-grpc.html#one-read-hook-per-controller-not-one-filter-per-action","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"One read hook per controller, not one filter per action","x":"The single most important thing to learn before editing any file here is where row scoping lives. It is not threaded through each action: the framework base declares one hook,…"},"1372":{"u":"/docs/onboarding/group-20-conference-api-grpc.html#authorization-at-the-edge-three-shapes-not-one","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"Authorization at the edge, three shapes not one","x":"Authorization is capability-based by default but not uniform, and the differences are the interesting part. Most write-bearing controllers carry a class-level…"},"1373":{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-request-records-the-inbound-write-shapes","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The request records, the inbound write shapes","x":"Several controllers declare small record class request types alongside themselves, co-located in the same file: AddRoomRequest and UpdateRoomRequest…"},"1374":{"u":"/docs/onboarding/group-20-conference-api-grpc.html#where-the-generic-shape-gives-way-filters-warnings-calendars-and-conditional-writes","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"Where the generic shape gives way: filters, warnings, calendars and conditional writes","x":"SessionsController shows best how a controller earns its overrides. Every read action is [AllowAnonymous] and [OutputCache(PolicyName = \"SessionsCache\")]…"},"1375":{"u":"/docs/onboarding/group-20-conference-api-grpc.html#two-more-deviations-versioning-and-decision-support","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"Two more deviations, versioning and decision support","x":"ServiceInfoController exists to prove the API-versioning machinery works beyond a single version ([Rubric §9, API & Contract Design]). It is a one-member shell over Common's…"},"1376":{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-module-entry-point-and-seeder-how-conference-plugs-in","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The module entry point and seeder, how Conference plugs in","x":"ConferenceModule is the Conference implementation of IModule. It is tiny by design: Register(...) calls the DependencyInjection extension's AddConferenceModule(...)…"},"1377":{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-grpc-edge-conference-as-both-server-and-client","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The gRPC edge, Conference as both server and client","x":"When Conference runs in its own process, two of its in-process collaborations must cross a network boundary, and both are handled by the G13 transport boundary (Result over the…"},"1378":{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-service-host-kestrel-first-then-caching-and-warm-up","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The service host: Kestrel first, then caching and warm-up","x":"The MMCA.ADC.Conference.Service Program.cs boots only the Conference module. Kestrel is configured before anything else, and the whole of it is one line:…"},"1379":{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-runtime-picture-one-host-two-transports","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The runtime picture, one host, two transports","x":"After module discovery (Program.cs:367-371) the host wires the Engagement gRPC client (:349), the broker (AddBrokerMessaging registering the UserRegistered integration-event…"},"1380":{"u":"/docs/onboarding/group-21-conference-ui.html","d":"21. ADC Conference - UI","k":"Onboarding Guide","x":"What this chapter covers. This is the consumer half of the \"write-once UI, render everywhere\" story (primer §2): the Blazor pages, page-local collaborators, and per-entity HTTP…"},"1381":{"u":"/docs/onboarding/group-21-conference-ui.html#the-layering-inside-the-ui-a-page-never-touches-httpclient","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"The layering inside the UI: a page never touches HttpClient","x":"Each page is a .razor + .razor.cs code-behind pair that depends on a UI service interface, never on HttpClient and never on the API's internals. The ten CRUD-shaped entities…"},"1382":{"u":"/docs/onboarding/group-21-conference-ui.html#the-list-pages-two-bases-one-recipe","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"The list pages: two bases, one recipe","x":"Twelve list screens hang off DataGridListPageBase , which supplies server-side paging against MudDataGrid , cancellation lifecycle, loading and load-failed state, filter and sort…"},"1383":{"u":"/docs/onboarding/group-21-conference-ui.html#detail-pages-form-models-and-one-declaration-of-the-rules","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Detail pages, form models, and one declaration of the rules","x":"The organizer detail pages have no ADC-local base: all nine inherit Common's DetailPageBase (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Common/DetailPageBase.cs:26)…"},"1384":{"u":"/docs/onboarding/group-21-conference-ui.html#container-and-presentational-split-and-page-local-collaborators","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Container and presentational split, and page-local collaborators","x":"The behaviour-heavy screens do not keep everything in one code-behind: the page stays the container (data fetching, filter and paging state, service calls) and hands rendering to…"},"1385":{"u":"/docs/onboarding/group-21-conference-ui.html#child-and-join-entities-a-thin-postdelete-base","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Child-and-join entities: a thin POST/DELETE base","x":"Sessions, speakers, and events own join relationships (a speaker added to a session, a category item to a speaker) that the generic CRUD base cannot model, because the write…"},"1386":{"u":"/docs/onboarding/group-21-conference-ui.html#display-enrichment-lookups-the-getall-vs-getbyid-populator-gap-worked-around-in-the-ui","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Display-enrichment lookups: the GetAll-vs-GetById populator gap, worked around in the UI","x":"Because the API's list endpoints do not always populate every cross-entity navigation, several pages need a cheap id-to-name map to render speaker names beside a session or an…"},"1387":{"u":"/docs/onboarding/group-21-conference-ui.html#three-feature-areas-that-go-beyond-crud","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Three feature areas that go beyond CRUD","x":"First, the speaker self-service dashboard. SpeakerDashboard sits behind a plain [Authorize] attribute and is gated on the speakerid JWT claim, showing the linked speaker's…"},"1388":{"u":"/docs/onboarding/group-21-conference-ui.html#session-materials-a-resource-deliberately-not-shaped-as-crud","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Session materials, a resource deliberately not shaped as CRUD","x":"Speakers publish the decks, handouts, and links that go with their own sessions, and anyone downloads them from the public session page (ADR-123, speaker session assets). This is…"},"1389":{"u":"/docs/onboarding/group-21-conference-ui.html#session-selection-decision-support-the-asynchronous-edge","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Session-selection decision support, the asynchronous edge","x":"The most behaviour-rich page is the organizer-only SessionSelectionDashboard (Pages/Sessions/Selection/SessionSelectionDashboard.razor.cs:16), which renders category…"},"1390":{"u":"/docs/onboarding/group-21-conference-ui.html#public-versus-authenticated-rendering-and-the-offline-first-path","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Public versus authenticated rendering, and the offline-first path","x":"A recurring [Rubric §11, Security] pattern: the same conference entity is exposed through two page families. No page under Pages/Public/ carries an @attribute [Authorize] at all,…"},"1391":{"u":"/docs/onboarding/group-21-conference-ui.html#sponsors-partners-and-activities-three-feature-areas-in-miniature","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Sponsors, partners, and activities, three feature areas in miniature","x":"The sponsor surface is a compact tour of every pattern above. Organizers manage the roster through SponsorList / SponsorCreate / SponsorDetail: the list is an…"},"1392":{"u":"/docs/onboarding/group-21-conference-ui.html#the-landing-page","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"The landing page","x":"ADCHome is the conference front door, shared by the web and MAUI heads; both serve the editorial images from their own site root today, so neither overrides the ImageBasePath…"},"1393":{"u":"/docs/onboarding/group-21-conference-ui.html#routes-navigation-and-localized-strings","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Routes, navigation, and localized strings","x":"All paths are centralized in ConferenceRoutePaths, a static catalogue of literal routes and id-parameterized builder methods (EventDetails(id), PublicSessionDetails(id),…"},"1394":{"u":"/docs/onboarding/group-21-conference-ui.html#how-it-all-plugs-into-the-shell","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"How it all plugs into the shell","x":"Two registration types wire the area in. ConferenceUIModule implements Common's IUIModule (the front-end counterpart of the IModule back-end contract): it declares the module's…"},"1395":{"u":"/docs/onboarding/group-22-engagement-module.html","d":"22. ADC Engagement Module (Session Bookmarks)","k":"Onboarding Guide","x":"What this group covers. Engagement is the ADC bounded context that holds everything an attendee does at the conference beyond browsing the catalog. Four capability families live…"},"1396":{"u":"/docs/onboarding/group-23-engagement-live-layer.html","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","x":"What this chapter covers. This is the conference-day layer of the Engagement bounded context: the features that only matter while an event is actually happening in the room.…"},"1397":{"u":"/docs/onboarding/group-23-engagement-live-layer.html#the-two-aggregates-and-their-invariants","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","t":"The two aggregates and their invariants","x":"Both aggregates are sealed AuditableAggregateRootEntity subclasses that follow the framework's factory-plus-Result discipline (primer §2). LivePoll holds an EventId, an optional…"},"1398":{"u":"/docs/onboarding/group-23-engagement-live-layer.html#the-write-path-and-where-the-realtime-broadcast-actually-happens","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","t":"The write path, and where the realtime broadcast actually happens","x":"Each operation is a vertical slice under Application/{LivePollsSessionQuestions}/UseCases/{Op}/. The three lifecycle commands do not hand-roll their load-mutate-save sequence at…"},"1399":{"u":"/docs/onboarding/group-23-engagement-live-layer.html#one-websocket-one-publisher-port-and-a-cross-service-ingress","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","t":"One WebSocket, one publisher port, and a cross-service ingress","x":"The transport itself is framework-owned (ADR-039, Group 10). The single NotificationHub carries both durable notifications and channel events on one connection, and the…"},"1400":{"u":"/docs/onboarding/group-23-engagement-live-layer.html#the-read-path-and-how-the-ui-reacts","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","t":"The read path and how the UI reacts","x":"Reads do not go through the generic entity-query machinery; the live views need shaped projections. LivePollResultsBuilder…"},"1401":{"u":"/docs/onboarding/group-23-engagement-live-layer.html#authorization-feature-gating-and-the-cross-service-dependency-on-conference","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","t":"Authorization, feature gating, and the cross-service dependency on Conference","x":"Both controllers, LivePollsController (MMCA.ADC.Engagement.API/Controllers/LivePollsController.cs:42) and SessionQuestionsController…"},"1402":{"u":"/docs/onboarding/group-24-identity-module.html","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","x":"What this chapter covers. This is the Identity bounded context of MMCA.ADC, the module that owns who a person is across every ADC surface: web, WebAssembly, and MAUI. It is a…"},"1403":{"u":"/docs/onboarding/group-24-identity-module.html#projects-one-bounded-context","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Projects, one bounded context","x":"The module is split along the standard Clean Architecture layering ([Rubric §3, Clean Architecture]), each project pinned by a trivial AssemblyReference / ClassReference anchor…"},"1404":{"u":"/docs/onboarding/group-24-identity-module.html#the-user-aggregate-credentials-profile-and-cross-context-links-in-one-root","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"The User aggregate: credentials, profile, and cross-context links in one root","x":"User (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:34) is the only aggregate root in the module, and it carries more responsibility than most: it is…"},"1405":{"u":"/docs/onboarding/group-24-identity-module.html#authentication-a-thin-subclass-over-the-shared-engine","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Authentication: a thin subclass over the shared engine","x":"The login / registration / refresh / revocation workflow is not re-implemented here. It lives in AuthenticationServiceBase (G08), which owns the validate-first flow, the lockout…"},"1406":{"u":"/docs/onboarding/group-24-identity-module.html#password-recovery-the-anonymous-half-of-the-credential-lifecycle","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Password recovery: the anonymous half of the credential lifecycle","x":"PUT /Auth/password only serves a user who can already sign in. The recovery pair that serves one who cannot is a second, anonymous vertical, and it is assembled the same way: two…"},"1407":{"u":"/docs/onboarding/group-24-identity-module.html#email-confirmation-proving-the-address-is-reachable","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Email confirmation: proving the address is reachable","x":"ADC asks a new account to prove its address, and deliberately does not gate sign-in on the answer: Authentication:EmailConfirmation:RequireConfirmedEmail is false…"},"1408":{"u":"/docs/onboarding/group-24-identity-module.html#administration-accounts-roles-and-the-capability-map","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Administration: accounts, roles, and the capability map","x":"The organizer-facing administration surface is a framework surface with ADC vocabulary plugged into it. UsersAdminController (UsersAdminController.cs:25) subclasses the framework…"},"1409":{"u":"/docs/onboarding/group-24-identity-module.html#the-privacy-pair-export-and-erasure","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"The privacy pair: export and erasure","x":"Two use cases make this module the codebase's clearest [Rubric §30, Compliance / Privacy / Data Governance] story, and both are thin ADC specializations of a G14 base. The…"},"1410":{"u":"/docs/onboarding/group-24-identity-module.html#avatars-the-third-mutating-slice","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Avatars: the third mutating slice","x":"The avatar trio is a small but complete example of a file-handling slice ([Rubric §11, Security] at the content boundary, ADR-045). UsersController caps the upload in two places:…"},"1411":{"u":"/docs/onboarding/group-24-identity-module.html#persistence-seeding-and-the-disabled-stub","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Persistence, seeding, and the disabled stub","x":"ModuleApplicationDbContext (ModuleApplicationDbContext.cs:15) is the abstract, engine-agnostic context declaring the single Users set (:22); the concrete per-engine class…"},"1412":{"u":"/docs/onboarding/group-24-identity-module.html#crossing-the-service-boundary-grpc-and-integration-events","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Crossing the service boundary: gRPC and integration events","x":"Identity talks to its peers two ways, and both live in Shared and Contracts so neither side reaches into the other's domain ([Rubric §7, Microservices Readiness]). Synchronously,…"},"1413":{"u":"/docs/onboarding/group-24-identity-module.html#the-ui-edge","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"The UI edge","x":"The Blazor surface is registered as an IdentityUIModule (MMCA.ADC.Identity.UI/IdentityUIModule.cs:15), an IUIModule descriptor that contributes three NavItems as resource keys,…"},"1414":{"u":"/docs/onboarding/group-24-identity-module.html#end-to-end-one-registration","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"End-to-end: one registration","x":"To see the chapter cooperate, follow a new attendee signing up. AuthController receives the register POST, captures the client IP for BR-213 rate limiting and the user-agent for…"},"1415":{"u":"/docs/onboarding/group-25-adc-host-composition.html","d":"25. ADC Application Host, UI Shell & Cross-Module Composition","k":"Onboarding Guide","x":"What this chapter covers. Every ADC module described so far (Conference, Engagement, Identity, Notification) is consumed somewhere. This chapter is that somewhere: the client…"},"1416":{"u":"/docs/onboarding/group-26-device-capability-layer.html","d":"26. Device Capability Abstraction Layer (Native Contracts, MAUI, Browser & Fallback Adapters)","k":"Onboarding Guide","x":"What this chapter covers. One Blazor component library in MMCA.Common.UI renders on three very different heads: Blazor Server (server-side prerender plus interactive Server…"},"1417":{"u":"/docs/onboarding/group-27-common-ai-integration.html","d":"27. Common AI Integration","k":"Onboarding Guide","x":"What this chapter covers. This group is the framework's language-model boundary: the optional MMCA.Common.AI package that turns a call to an LLM provider into an ordinary,…"},"1418":{"u":"/docs/onboarding/group-27-common-ai-integration.html#the-configuration-surface-is-the-contract","d":"27. Common AI Integration","k":"Onboarding Guide","t":"The configuration surface is the contract","x":"AiSettings (MMCA.Common/Source/Core/MMCA.Common.AI/AiSettings.cs:22) binds from the Ai section (AiSettings.cs:25) and holds every knob the package has: Enabled (:39), Provider…"},"1419":{"u":"/docs/onboarding/group-27-common-ai-integration.html#the-provider-is-a-registered-factory-not-a-type-the-package-names","d":"27. Common AI Integration","k":"Onboarding Guide","t":"The provider is a registered factory, not a type the package names","x":"IAiProviderFactory (MMCA.Common/Source/Core/MMCA.Common.AI/Providers/IAiProviderFactory.cs:20) is the whole provider boundary: a Name a configuration file selects it by (:26) and…"},"1420":{"u":"/docs/onboarding/group-27-common-ai-integration.html#one-entry-point-one-pipeline-outermost-first","d":"27. Common AI Integration","k":"Onboarding Guide","t":"One entry point, one pipeline, outermost first","x":"AiServiceCollectionExtensions (MMCA.Common/Source/Core/MMCA.Common.AI/DependencyInjection.cs:77) is the only composition surface. It exposes two AddMmcaChatClient overloads…"},"1421":{"u":"/docs/onboarding/group-27-common-ai-integration.html#the-outer-layer-what-a-call-is-allowed-to-do","d":"27. Common AI Integration","k":"Onboarding Guide","t":"The outer layer: what a call is allowed to do","x":"BoundedChatClient (MMCA.Common/Source/Core/MMCA.Common.AI/Chat/BoundedChatClient.cs:65) is a DelegatingChatClient that applies five bounds (output tokens, wall clock, tool use,…"},"1422":{"u":"/docs/onboarding/group-27-common-ai-integration.html#the-middle-layer-what-the-call-may-say-and-what-it-may-answer","d":"27. Common AI Integration","k":"Onboarding Guide","t":"The middle layer: what the call may say, and what it may answer","x":"Bounds are configuration, so the framework can decide them. Content is not, so it does not. IChatGuardrail (MMCA.Common/Source/Core/MMCA.Common.AI/Chat/IChatGuardrail.cs:20) is…"},"1423":{"u":"/docs/onboarding/group-27-common-ai-integration.html#the-two-content-policies-the-framework-does-ship","d":"27. Common AI Integration","k":"Onboarding Guide","t":"The two content policies the framework does ship","x":"PiiRedactionGuardrail (MMCA.Common/Source/Core/MMCA.Common.AI/Guardrails/PiiRedactionGuardrail.cs:39) is the stated exception to \"extension points and no policy\": an email…"},"1424":{"u":"/docs/onboarding/group-27-common-ai-integration.html#the-inner-layer-what-the-call-actually-cost","d":"27. Common AI Integration","k":"Onboarding Guide","t":"The inner layer: what the call actually cost","x":"UsageRecordingChatClient (MMCA.Common/Source/Core/MMCA.Common.AI/Chat/UsageRecordingChatClient.cs:38) sits inside the guardrails and reports the provider's own numbers. On the…"},"1425":{"u":"/docs/onboarding/group-27-common-ai-integration.html#the-prompt-is-a-versioned-artifact","d":"27. Common AI Integration","k":"Onboarding Guide","t":"The prompt is a versioned artifact","x":"PromptContract (MMCA.Common/Source/Core/MMCA.Common.AI/PromptContract.cs:23) is a sealed record of the four things that decide what a model will answer: Name, Version, Model and…"},"1426":{"u":"/docs/onboarding/group-27-common-ai-integration.html#where-it-runs-today-and-how-it-is-tested","d":"27. Common AI Integration","k":"Onboarding Guide","t":"Where it runs today, and how it is tested","x":"Exactly one feature in the workspace calls a model: ADC's organizer-facing session scoring. The Conference service host reads one secret, Ai:ApiKey, and derives Ai:Enabled from…"},"1427":{"u":"/docs/onboarding/group-28-testing-infrastructure.html","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","x":"What this group covers. Everything the codebase uses to prove itself: the five reusable test-support packages that ship out of MMCA.Common/Source/Hosting (MMCA.Common.Testing,…"},"1428":{"u":"/docs/onboarding/group-28-testing-infrastructure.html#integration-tests-a-real-host-a-throwaway-database-a-per-test-reset","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Integration tests: a real host, a throwaway database, a per-test reset","x":"The integration tier boots the actual application, not a mock of it. The abstraction at its center is IIntegrationTestFixture…"},"1429":{"u":"/docs/onboarding/group-28-testing-infrastructure.html#orchestration-smoke-tests-booting-a-real-apphost","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Orchestration smoke tests: booting a real AppHost","x":"Every tier above boots hosts directly, which means none of them ever runs the file that states how a whole stack fits together. The AppHost declares the project resources, the…"},"1430":{"u":"/docs/onboarding/group-28-testing-infrastructure.html#architecture-fitness-functions-rules-that-gate-the-build","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Architecture fitness functions: rules that gate the build","x":"The layering and DDD conventions this codebase commits to are not left to code review, they are executed as tests. The reusable rule library lives in…"},"1431":{"u":"/docs/onboarding/group-28-testing-infrastructure.html#component-tests-real-mudblazor-faked-edges","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Component tests: real MudBlazor, faked edges","x":"The bUnit tier renders a single Blazor component in-process with its real dependencies but stubbed network and auth. BunitComponentTestBase…"},"1432":{"u":"/docs/onboarding/group-28-testing-infrastructure.html#end-to-end-tests-a-real-browser-accessibility-and-performance-as-gates","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"End-to-end tests: a real browser, accessibility and performance as gates","x":"The E2E tier drives a real browser through Playwright. PlaywrightFixture (MMCA.Common.Testing.E2E/Infrastructure/PlaywrightFixture.cs:6) is an xUnit collection fixture (its…"},"1433":{"u":"/docs/onboarding/group-28-testing-infrastructure.html#the-gallery-harness","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"The Gallery harness","x":"Component and E2E coverage of MMCA.Common's own UI needs a page to render, but the framework is not a runnable app. MMCA.Common.UI.Gallery is a deliberately backend-less Blazor…"},"1434":{"u":"/docs/onboarding/group-28-testing-infrastructure.html#evaluating-a-nondeterministic-dependency-the-ai-scoring-golden-corpus","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Evaluating a nondeterministic dependency: the AI-scoring golden corpus","x":"Every tier above assumes a deterministic system under test. ADC's session-scoring judge is not one: it asks a hosted model for a structured score, and the same prompt can return…"},"1435":{"u":"/docs/onboarding/group-28-testing-infrastructure.html#contract-pipeline-and-benchmark-bases","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Contract, pipeline, and benchmark bases","x":"The last family pins guarantees that live in the composition of the stack rather than in any one type, and it is the subject of ADR-058: these suites ship in MMCA.Common.Testing…"},"1436":{"u":"/docs/onboarding/group-28-testing-infrastructure.html#per-project-test-rollup","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Per-project test rollup","x":"The remaining test types in G25 are logged here by project rather than as individual sections (per the TESTS note): each project below is a Microsoft Testing Platform / xUnit v3…"},"1437":{"u":"/docs/onboarding/group-28-testing-infrastructure.html#test-suites-by-module","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Test suites by module","x":"--- ⬅ Common AI Integration • Index • Coverage audit ➡"},"1438":{"u":"/docs/onboarding/devops-aspire.html","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","x":"This chapter teaches how the MMCA.ADC system goes from a single dotnet run on your workstation to a running stack of six .NET processes plus four containers: databases, a broker,…"},"1439":{"u":"/docs/onboarding/devops-aspire.html#the-one-command-local-run","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"The one-command local run","x":"That command brings up everything the application needs locally: four SQL Server databases, Redis, RabbitMQ with management UI, a MailDev SMTP interceptor, four extracted…"},"1440":{"u":"/docs/onboarding/devops-aspire.html#mmcaadcapphost-the-orchestration-project","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"MMCA.ADC.AppHost, the orchestration project","x":"Source file: MMCA.ADC/Source/Hosting/MMCA.ADC.AppHost/Program.cs AppHost-local helper: MMCA.ADC/Source/Hosting/MMCA.ADC.AppHost/BrokerSelection.cs Framework extension helpers:…"},"1441":{"u":"/docs/onboarding/devops-aspire.html#where-service-defaults-come-from-mmcacommonaspire-not-a-local-project","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"Where service defaults come from, MMCA.Common.Aspire, not a local project","x":"There is no MMCA.ADC.ServiceDefaults project. Source/Hosting/ contains the AppHost and the four per-service migrations assemblies, nothing else. The conventional Aspire…"},"1442":{"u":"/docs/onboarding/devops-aspire.html#mmcacommonaspire-the-framework-service-defaults-package","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"MMCA.Common.Aspire, the framework service-defaults package","x":"Source: MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs Tags: .../HealthCheckTags.cs Telemetry: .../Telemetry/OutboxPollFilterProcessor.cs Security:…"},"1443":{"u":"/docs/onboarding/devops-aspire.html#mmcacommonaspirehosting-the-apphost-extensions-package","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"MMCA.Common.Aspire.Hosting, the AppHost extensions package","x":"Source: MMCA.Common/Source/Hosting/MMCA.Common.Aspire.Hosting/ Files: Extensions.cs, H2cHealthCheckExtensions.cs, H2cEndpointHealthCheck.cs, ServiceBusEmulatorResource.cs This…"},"1444":{"u":"/docs/onboarding/devops-aspire.html#the-six-dockerfiles","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"The six Dockerfiles","x":"All six Dockerfiles share the same multi-stage structure (base, build, publish, final) and the same base images. None build the AppHost: it is a local-only orchestration…"},"1445":{"u":"/docs/onboarding/devops-aspire.html#local-to-cloud-parity","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"Local-to-cloud parity","x":"The AppHost topology maps directly to the Azure infrastructure provisioned by infra/main.bicep. The table below cross-references the local resource with its Azure equivalent: The…"},"1446":{"u":"/docs/onboarding/devops-aspire.html#the-yarp-gateways-role","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"The YARP Gateway's role","x":"The gateway (Source/Hosts/MMCA.ADC.Gateway) is a pure YARP reverse proxy. It has no DbContext, no ModuleLoader, no REST controllers, and no broker connection. Its Program.cs is…"},"1447":{"u":"/docs/onboarding/devops-aspire.html#startup-ordering-summary","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"Startup ordering summary","x":"The health-based WaitFor chain imposes this ordering. Two things shape it that are not visible at the call site: three of the four services wait on Identity without any explicit…"},"1448":{"u":"/docs/onboarding/devops-aspire.html#the-apphost-composition-smoke-test","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"The AppHost composition smoke test","x":"MMCA.ADC/Tests/Integration/MMCA.ADC.AppHost.SmokeTests/AdcAppHostFixture.cs, MMCA.ADC/Tests/Integration/MMCA.ADC.AppHost.SmokeTests/AdcAppHostSmokeTests.cs Everything above is…"},"1449":{"u":"/docs/onboarding/devops-aspire.html#rubric-category-index-for-this-chapter","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"Rubric category index for this chapter","x":"---"},"1450":{"u":"/docs/onboarding/devops-aspire.html#not-determinable-from-source","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"Not determinable from source","x":"- The specific integration events that flow over the broker (UserRegistered, SpeakerLinkedToUser, SpeakerUnlinkedFromUser) are cited from AppHost inline comments…"},"1451":{"u":"/docs/onboarding/devops-cicd.html","d":"CI/CD and Operations","k":"Onboarding Guide","x":"This chapter walks the GitHub Actions workflows that govern MMCA, from the framework's continuous integration and lockstep NuGet release in MMCA.Common, through the ADC…"},"1452":{"u":"/docs/onboarding/devops-cicd.html#mmcacommon-ciyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.Common, ci.yml","x":"File: MMCA.Common/.github/workflows/ci.yml The continuous-integration workflow for the MMCA.Common framework. Because the published packages (the authoritative id list and count…"},"1453":{"u":"/docs/onboarding/devops-cicd.html#mmcacommon-releaseyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.Common, release.yml","x":"File: MMCA.Common/.github/workflows/release.yml The lockstep NuGet release workflow. When a maintainer pushes a vX.Y.Z git tag, this workflow deterministically derives the…"},"1454":{"u":"/docs/onboarding/devops-cicd.html#mmcaadc-deployyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.ADC, deploy.yml","x":"File: MMCA.ADC/.github/workflows/deploy.yml The primary CI/CD pipeline for the Atlanta Developers Conference application. It runs on every push to main, on every pull request…"},"1455":{"u":"/docs/onboarding/devops-cicd.html#mmcaadc-e2eyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.ADC, e2e.yml","x":"File: MMCA.ADC/.github/workflows/e2e.yml The full-stack Playwright E2E test workflow. It brings up the complete Aspire stack (SQL Server + Redis + RabbitMQ + four services +…"},"1456":{"u":"/docs/onboarding/devops-cicd.html#mmcaadc-cost-guardyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.ADC, cost-guard.yml","x":"File: MMCA.ADC/.github/workflows/cost-guard.yml A read-only FinOps check that confirms the production Azure footprint is at its cost baseline. It detects a specific operational…"},"1457":{"u":"/docs/onboarding/devops-cicd.html#mmcaadc-load-testyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.ADC, load-test.yml","x":"File: MMCA.ADC/.github/workflows/load-test.yml A k6 load test targeting the output-cached Conference read endpoints through the production Gateway. It establishes a repeatable…"},"1458":{"u":"/docs/onboarding/devops-cicd.html#repository-automation-the-claude-review-pair-and-the-maui-audit","d":"CI/CD and Operations","k":"Onboarding Guide","t":"Repository automation: the Claude review pair and the MAUI audit","x":"Files: MMCA.Common/.github/workflows/claude.yml, MMCA.Common/.github/workflows/claude-code-review.yml, MMCA.ADC/.github/workflows/claude.yml,…"},"1459":{"u":"/docs/onboarding/devops-cicd.html#cross-workflow-summary","d":"CI/CD and Operations","k":"Onboarding Guide","t":"Cross-workflow summary","x":"(dr-drill.yml is the ADR-009 §29 restore drill: it PITR-restores a copy of a chosen database, times the restore for the RTO record, verifies it comes back Online, then deletes…"},"1460":{"u":"/docs/onboarding/devops-cicd.html#rubric-category-index-for-this-chapter","d":"CI/CD and Operations","k":"Onboarding Guide","t":"Rubric category index for this chapter"},"1461":{"u":"/docs/onboarding/devops-iac.html","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","x":"This chapter teaches the Azure Infrastructure-as-Code layer for the MMCA.ADC application: what resources are provisioned, why they are shaped the way they are, how secrets reach…"},"1462":{"u":"/docs/onboarding/devops-iac.html#how-the-pieces-fit-together","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"How the pieces fit together","x":"Before diving into individual files, here is the end-to-end picture: Phases 1 and 2 are their own jobs (deploy.yml:1089, deploy.yml:1143) rather than steps inside deploy, so they…"},"1463":{"u":"/docs/onboarding/devops-iac.html#azureyaml-the-azd-project-definition","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"azure.yaml, the azd project definition","x":"File: MMCA.ADC/azure.yaml azure.yaml is the Azure Developer CLI (azd) manifest for the project. It declares six deployable services and points azd at the Bicep infrastructure…"},"1464":{"u":"/docs/onboarding/devops-iac.html#infrafoundationbicep-long-lived-shared-infrastructure","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"infra/foundation.bicep, long-lived shared infrastructure","x":"File: MMCA.ADC/infra/foundation.bicep Foundation is deployed first (CI/CD chapter: deploy.yml:1115-1121) on every run. It provisions three resources: the Azure Container…"},"1465":{"u":"/docs/onboarding/devops-iac.html#deployment-parameters-assembled-at-deploy-time-not-committed","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"Deployment parameters, assembled at deploy time, not committed","x":"There is no infra/main.parameters.json file in the repository, the tracked infra/ directory holds only foundation.bicep, main.bicep, DISASTER-RECOVERY.md, OPERATIONS.md,…"},"1466":{"u":"/docs/onboarding/devops-iac.html#inframainbicep-the-full-application-infrastructure","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"infra/main.bicep, the full application infrastructure","x":"File: MMCA.ADC/infra/main.bicep main.bicep declares every application-layer Azure resource: Application Insights, five SLO scheduled query rules (one of them the AI-scoring token…"},"1467":{"u":"/docs/onboarding/devops-iac.html#deployment-model-summary","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"Deployment model summary","x":"The complete credential chain: No static credential exists at any link in this chain. The GitHub secrets AZURECLIENTID, AZURETENANTID, AZURESUBSCRIPTIONID are the OIDC…"},"1468":{"u":"/docs/onboarding/devops-iac.html#rubric-category-cross-reference","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"Rubric category cross-reference","x":"---"},"1469":{"u":"/docs/onboarding/devops-iac.html#not-determinable-from-source","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"Not determinable from source","x":"- The exact AcrPull and Key Vault Secrets User role-assignment commands used in the out-of- band bootstrap are referenced in comments (main.bicep:1045-1049, main.bicep:1063-1066)…"},"1470":{"u":"/docs/onboarding/devops-runbooks.html","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","x":"This chapter covers every operational script and runbook in MMCA.ADC: the one-time Azure bootstrap, the database-per-service cutover story (how the legacy AtlDevCon monolith DB…"},"1471":{"u":"/docs/onboarding/devops-runbooks.html#azure-setupsh-one-time-azure-bootstrap","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"azure-setup.sh, One-time Azure bootstrap","x":"File: MMCA.ADC/scripts/azure-setup.sh (175 lines) What it is. A bash script that creates every Azure identity and OIDC credential the GitHub Actions deploy pipeline needs. It is…"},"1472":{"u":"/docs/onboarding/devops-runbooks.html#the-database-per-service-cutover-and-why-its-scripts-are-gone","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"The database-per-service cutover, and why its scripts are gone","x":"Before the surviving artifacts make sense, the story behind them does. Before ADR-006. All four modules (Identity, Conference, Engagement, Notification) pointed at a single…"},"1473":{"u":"/docs/onboarding/devops-runbooks.html#infradisaster-recoverymd-dr-runbook","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"infra/DISASTER-RECOVERY.md, DR runbook","x":"File: MMCA.ADC/infra/DISASTER-RECOVERY.md (234 lines; not the Store file of the same name) What it is. The authoritative disaster-recovery runbook for the ADC production…"},"1474":{"u":"/docs/onboarding/devops-runbooks.html#dr-drillyml-and-dr-restore-drillps1-the-adr-009-restore-drill","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"dr-drill.yml and dr-restore-drill.ps1, the ADR-009 restore drill","x":"Files: MMCA.ADC/.github/workflows/dr-drill.yml (112 lines), MMCA.ADC/scripts/dr-restore-drill.ps1 (101 lines) What it is. The automation behind the drill requirement above: the…"},"1475":{"u":"/docs/onboarding/devops-runbooks.html#infraoperationsmd-day-2-alert-triage-runbook","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"infra/OPERATIONS.md, day-2 alert triage runbook","x":"File: MMCA.ADC/infra/OPERATIONS.md (207 lines) What it is. The alert-to-action companion to the provisioned observability: what to do when each alert fires, how to read the SLO…"},"1476":{"u":"/docs/onboarding/devops-runbooks.html#infrasql-managed-identitymd-staged-passwordless-sql-runbook","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"infra/SQL-MANAGED-IDENTITY.md, staged passwordless-SQL runbook","x":"File: MMCA.ADC/infra/SQL-MANAGED-IDENTITY.md (100 lines) What it is. The runbook for moving the four service apps from SQL-login (password) auth to Entra managed-identity auth…"},"1477":{"u":"/docs/onboarding/devops-runbooks.html#infrapost-cutover-atldevcon-downgrademd-archive-downgrade-and-drop-runbook","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"infra/POST-CUTOVER-atldevcon-downgrade.md, archive downgrade and drop runbook","x":"File: MMCA.ADC/infra/POST-CUTOVER-atldevcon-downgrade.md (128 lines) What it is. The record of what happened to the legacy AtlDevCon database after the database-per-service…"},"1478":{"u":"/docs/onboarding/devops-runbooks.html#play-store-captureps1-android-screenshot-capture","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"play-store-capture.ps1, Android screenshot capture","x":"File: MMCA.ADC/scripts/play-store-capture.ps1 (147 lines) What it is. A PowerShell 7 script (play-store-capture.ps1:1) that captures a screenshot from an attached Android device…"},"1479":{"u":"/docs/onboarding/devops-runbooks.html#play-store-composeps1-play-store-screenshot-compositor","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"play-store-compose.ps1, Play Store screenshot compositor","x":"File: MMCA.ADC/scripts/play-store-compose.ps1 (187 lines) What it is. A PowerShell 7 script that reads raw captures from store-assets/play-store/raw/, wraps each into a 1080x1920…"},"1480":{"u":"/docs/onboarding/devops-runbooks.html#docsmobilereleaserunbookmd-store-submission-runbook","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"Docs/MobileReleaseRunbook.md, store-submission runbook","x":"File: MMCA.ADC/Docs/MobileReleaseRunbook.md (205 lines) What it is. The manual, credential-holding steps around a store submission that code and CI cannot perform, each tagged…"},"1481":{"u":"/docs/onboarding/devops-runbooks.html#the-operational-artifacts-in-full-context","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"The operational artifacts in full context","x":"The surviving artifacts form one story that starts at bootstrap and ends at day-2 triage: The AtlDevCon database is the thread that runs through the database half of that table,…"},"1482":{"u":"/docs/onboarding/devops-runbooks.html#rubric-tag-summary","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"Rubric tag summary","x":"---"},"1483":{"u":"/docs/onboarding/devops-runbooks.html#not-determinable-from-source","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"Not determinable from source","x":"- Whether the newest weekly drill is green: the drill ledger is maintained through 2026-08-10 (DISASTER-RECOVERY.md:210-217), but dr-drill.yml runs every Monday and a run reaches…"},"1484":{"u":"/docs/onboarding/devops-testing.html","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","x":"Chapter scope note. The tier chapters (tier-00 through the sweep) document every type in the production codebase one by one. Test types are the logged exception: this chapter…"},"1485":{"u":"/docs/onboarding/devops-testing.html#1-solution-composition-and-the-test-runner","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"1. Solution composition and the test runner","x":"The two deployed apps use the same two-file pattern; MMCA.Common and MMCA.Helpdesk ship a .slnx only, because their solutions are already fast enough not to need a CI subset:…"},"1486":{"u":"/docs/onboarding/devops-testing.html#2-test-project-layout","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"2. Test project layout","x":"The inventory below is drawn from 00-inventory.md:25-135 (test-assembly counts) and the solution files above. Counts are distinct types per project as reported by the Roslyn…"},"1487":{"u":"/docs/onboarding/devops-testing.html#3-shipped-testing-infrastructure-packages","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"3. Shipped testing-infrastructure packages","x":"MMCA.Common ships five of its twenty-two packages as general testing infrastructure that downstream apps consume as NuGet references rather than writing their own harness…"},"1488":{"u":"/docs/onboarding/devops-testing.html#4-architecture-fitness-tests-executable-governance","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"4. Architecture fitness tests, executable governance","x":"[Rubric §34, Architecture Governance & Documentation]: §34 assesses whether architectural decisions are documented, enforced, and kept honest over time; fitness functions are the…"},"1489":{"u":"/docs/onboarding/devops-testing.html#5-integration-and-e2e-strategy","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"5. Integration and E2E strategy","x":"The four integration test projects (Identity, Conference, Engagement, Notification) each boot their service in-process with WebApplicationFactory . The lifecycle is not written…"},"1490":{"u":"/docs/onboarding/devops-testing.html#6-worked-examples","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"6. Worked examples","x":"Three examples tie the infrastructure above to real test code. The per-repo class is a bare subclass; the facts, the package lists and the parsing live once in the shared base:…"},"1491":{"u":"/docs/onboarding/devops-testing.html#7-the-tiers-and-the-gates-that-run-them","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"7. The tiers and the gates that run them","x":"A test tier only means something once you know what it blocks. This is the map. MMCA.Common's ui-e2e job (MMCA.Common/.github/workflows/ci.yml:270) builds the out-of-slnx gallery…"},"1492":{"u":"/docs/onboarding/devops-testing.html#quick-reference-rubric-categories-touched-in-this-chapter","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"Quick reference: rubric categories touched in this chapter","x":"---"},"1493":{"u":"/docs/onboarding/devops-testing.html#cross-links","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"Cross-links","x":"- Primer: 00-primer.md5-the-solution--test-layout , solution files, MTP runner, slnx-excluded UI projects - Primer:…"},"1494":{"u":"/docs/onboarding/99-coverage-audit.html","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","x":"This audit reconciles the written guide against the mechanically-extracted inventory, logs every deliberate exception, verifies the grouping/ordering rules, proves all 34 rubric…"},"1495":{"u":"/docs/onboarding/99-coverage-audit.html#1-coverage-reconciliation","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"1. Coverage reconciliation","x":"Cross-check result: verify.ps1 confirms 0 of the 2,624 individually-sectioned types are missing from their group chapter, every one appears as a heading or in a sibling-family…"},"1496":{"u":"/docs/onboarding/99-coverage-audit.html#2-exceptions-log-every-deliberate-omission-with-reason","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"2. Exceptions log (every deliberate omission, with reason)","x":"EF Core migrations (/Migrations/, .Migrations.SqlServer), ModelSnapshot, .Designer.cs, .g.cs, GlobalUsings.g.cs, and AssemblyInfo.cs are excluded by rule (Tools/invtool…"},"1497":{"u":"/docs/onboarding/99-coverage-audit.html#3-grouping--ordering-verification","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"3. Grouping & ordering verification","x":"- Every type in exactly one group. classify.ps1 assigns all 3,668 nodes via name-level overrides (for the grab-bag MMCA.Common.Interfaces/Services namespaces) + ordered…"},"1498":{"u":"/docs/onboarding/99-coverage-audit.html#4-rubric-coverage-matrix","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"4. Rubric coverage matrix","x":"Every one of the 34 categories is explained at least once against real code. \"First explained in\" is the earliest group chapter (by order) that tags it; many recur and several…"},"1499":{"u":"/docs/onboarding/99-coverage-audit.html#5-open-questions--not-determinable-from-source","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"5. Open questions / not determinable from source","x":"1. IDbSeeder host invocation (group-07). The seeding contract and implementations are in MMCA.Common, but the IHostedService/startup invoker that actually runs seeding at boot…"},"1500":{"u":"/docs/onboarding/99-coverage-audit.html#6-how-to-regenerate-this-audit","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"6. How to regenerate this audit","x":"Then copy the refreshed out/00-inventory.md and out/00-dependency-manifest.md into Docs/Onboarding/ (the 00-group-taxonomy.md is written there directly by classify.ps1)."},"1501":{"u":"/docs/onboarding/CONCEPT-MAPS.html","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","x":"Mermaid diagrams distilled from the Onboarding guide (primer, group taxonomy, dependency manifest, and the 28 group chapters). Each diagram captures a relationship between the…"},"1502":{"u":"/docs/onboarding/CONCEPT-MAPS.html#1-system-context-two-codebases--the-22-packages","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"1. System context, two codebases + the 22 packages","x":"MMCA.Common is a framework published as twenty-two NuGet packages in lockstep, to nuget.org and GitHub Packages from one tag (ADR-053); MMCA.ADC and MMCA.Store consume them.…"},"1503":{"u":"/docs/onboarding/CONCEPT-MAPS.html#2-clean-architecture-the-layered-dependency-rule","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"2. Clean Architecture, the layered dependency rule","x":"Source dependencies point inward toward the Domain; each layer references only layers below it. Deliberate exceptions: UI and Grpc depend on Shared only (UI for Blazor WASM…"},"1504":{"u":"/docs/onboarding/CONCEPT-MAPS.html#3-the-28-functional-groups-dependency--build-order","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"3. The 28 functional groups, dependency / build order","x":"The primary axis of the guide: every type lives in exactly one of 28 chapter groups, ordered roughly topologically. Foundational, widely-depended-on concerns first (Result →…"},"1505":{"u":"/docs/onboarding/CONCEPT-MAPS.html#4-core-framework-patterns-how-the-building-blocks-compose","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"4. Core framework patterns, how the building blocks compose","x":"The pattern-level view of the same backbone: the ideas the primer commits to and how they feed each other. Result is the pervasive currency; DDD blocks produce domain events;…"},"1506":{"u":"/docs/onboarding/CONCEPT-MAPS.html#5-request-lifecycle-the-cqrs-decorator-pipeline-adr-014","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"5. Request lifecycle, the CQRS decorator pipeline (ADR-014)","x":"Handlers are thin (one method); every cross-cutting concern is a decorator wrapping the next. Scrutor TryDecorate composes them in reverse registration order (last registered =…"},"1507":{"u":"/docs/onboarding/CONCEPT-MAPS.html#6-event-driven-integration-outbox-dual-dispatch-adr-003--010--021--100","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"6. Event-driven integration, outbox dual-dispatch (ADR-003 / 010 / 021 / 100)","x":"Domain events are captured into an OutboxMessage row in the same transaction as the data (no dual-write bug). The two event kinds then part ways: local domain events are…"},"1508":{"u":"/docs/onboarding/CONCEPT-MAPS.html#7-modular-monolith--extractable-services-adr-006--007--008--012","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"7. Modular monolith → extractable services (ADR-006 / 007 / 008 / 012)","x":"Modules implement IModule and are discovered + Kahn-ordered by ModuleLoader (ADR-059). The same module code runs as a single monolith host or as N service processes behind a YARP…"},"1509":{"u":"/docs/onboarding/CONCEPT-MAPS.html#8-persistence-database-per-service--polyglot-engines-adr-006--018--030","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"8. Persistence, database-per-service + polyglot engines (ADR-006 / 018 / 030)","x":"One sealed context class per engine (SQLServerDbContext, PostgreSQLDbContext, SqliteDbContext, CosmosDbContext) over the abstract ApplicationDbContext, one instance per database.…"},"1510":{"u":"/docs/onboarding/CONCEPT-MAPS.html#9-authentication--authorization-stack","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"9. Authentication & Authorization stack","x":"The auth concern (G08) spans token validation, session cookies, federated sign-in, password hashing, brute-force protection, per-device refresh-session rotation and revocation, a…"},"1511":{"u":"/docs/onboarding/CONCEPT-MAPS.html#10-notifications-three-channels-behind-one-send-pipeline-adr-024--044","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"10. Notifications, three channels behind one send pipeline (ADR-024 / 044)","x":"One use case (SendPushNotificationHandler) writes a durable per-user inbox (optionally scoped to an event via ScopeKey), fires a transient SignalR push, and then an OS-level…"},"1512":{"u":"/docs/onboarding/CONCEPT-MAPS.html#11-ui-write-once-render-everywhere--i18n--theming","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"11. UI, write-once render everywhere + i18n + theming","x":"A page is authored once as a Razor component in a per-module UI library; both the Blazor web host (Server + WASM) and the .NET MAUI host reference the same libraries, so it…"},"1513":{"u":"/docs/onboarding/CONCEPT-MAPS.html#12-adc-business-modules-bounded-contexts-end-to-end","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"12. ADC business modules, bounded contexts end-to-end","x":"Each ADC module is a vertical slice through all layers. Conference is large enough to split across five chapters (G17-G21); Engagement takes two (G22 the bookmark, QR badge…"},"1514":{"u":"/docs/onboarding/CONCEPT-MAPS.html#13-the-adrs-grouped-by-theme","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"13. The ADRs, grouped by theme","x":"Every accepted ADR in Website/docs-src/adr/, clustered by the concern it governs. That directory's README.md is the canonical index and owns the count and range; this map only…"},"1515":{"u":"/docs/onboarding/CONCEPT-MAPS.html#14-the-34-category-evaluation-rubric","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"14. The 34-category evaluation rubric","x":"The lens the guide tags code against ([Rubric §N]). Scored on two axes: Maturity (0-4, process) and Implementation (0-10, substance). Three parts. ---"},"1516":{"u":"/docs/onboarding/CONCEPT-MAPS.html#15-how-the-axes-fit-together-reading-map","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"15. How the axes fit together (reading map)","x":"The guide is organized on two axes at once. This ties the diagrams above back to the guide's navigation. --- - Group-to-group arrows in §3 show the dominant \"builds on\" direction…"},"1517":{"u":"/docs/governance/index.html","d":"Architecture Governance","k":"Architecture Governance","x":"The governance artifacts behind the MMCA platform: the shared 34-category evaluation rubric, and each repo's evidence-based scorecard plus its remediation backlog. Every score…"},"1518":{"u":"/docs/governance/index.html#the-rubric","d":"Architecture Governance","k":"Architecture Governance","t":"The rubric","x":"- Architecture Evaluation Criteria: the 34-category rubric (Maturity 0-4 and Implementation 0-10 per category) that all three application repos are scored against. - Rubric…"},"1519":{"u":"/docs/governance/index.html#scorecards-and-backlogs","d":"Architecture Governance","k":"Architecture Governance","t":"Scorecards and backlogs"},"1520":{"u":"/docs/governance/index.html#how-these-are-maintained","d":"Architecture Governance","k":"Architecture Governance","t":"How these are maintained","x":"Scores are re-verified from source on a cadence: each category is scored by reading the current code, config, and CI (never rolled forward), and any change lands with the…"},"1521":{"u":"/docs/governance/adc-ArchitectureScorecard.html","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","x":"Canonical, version-controlled scorecard for this repo: the single source of truth for MMCA.ADC's architecture scores, scored against the rubric at…"},"1522":{"u":"/docs/governance/adc-ArchitectureScorecard.html#at-a-glance","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"At a glance","x":"- Maturity index = Σ(maturity×weight) ÷ Σ(weight×4) = 314 ÷ 324 = 96.9% - Implementation index = Σ(impl×weight) ÷ Σ(weight×10) = 697 ÷ 810 = 86.0% - Weaker axis: Implementation,…"},"1523":{"u":"/docs/governance/adc-ArchitectureScorecard.html#top-5-risks","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 risks","x":"1. Responsive behavior holds the lowest implementation score and sits in both bands: §22 (mat 3 / impl 7, weight 2; the top row of the implementation band at implPriority 4, and…"},"1524":{"u":"/docs/governance/adc-ArchitectureScorecard.html#top-5-strengths","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 strengths","x":"1. Architecture intent is executable, not prose (§34 Governance, mat 4 / impl 9, and §3 Clean Architecture, mat 4 / impl 9): 54 architecture-test classes in…"},"1525":{"u":"/docs/governance/adc-ArchitectureScorecard.html#scorecard","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Scorecard","x":"Weighted column = Maturity·weight / Implementation·weight per row. The maturity gap is four categories at maturity 3 (§10, §12, §21, §22); every other category is at maturity 4,…"},"1526":{"u":"/docs/governance/adc-ArchitectureScorecard.html#scoring-notes","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Scoring notes","x":"- N/A (excluded from denominators): none. §16 AI-Native Application Architecture is scored because the AI session-scoring feature calls a model in production (the rubric's…"},"1527":{"u":"/docs/governance/adc-ArchitectureScorecard.html#cross-repo-comparison","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Cross-repo comparison","x":"How this repo's quality relates to the other consumers (where the framework's quality propagates, where a repo diverges or under-uses it, and where a consumer does better than…"},"1528":{"u":"/docs/governance/adc-RemediationBacklog.html","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","x":"Derived from ArchitectureScorecard.md (evidence as of 2026-10-01 at MMCA.Common. v1.217.0): Maturity 96.9% (314/324) / Implementation 86.0% (697/810), weight total 81, no N/A…"},"1529":{"u":"/docs/governance/adc-RemediationBacklog.html#open-work-at-a-glance","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"Open work at a glance"},"1530":{"u":"/docs/governance/adc-RemediationBacklog.html#open-items","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"Open items","x":"Most criteria are CI-enforced (a frozen contract snapshot that spells out generic arguments and is keyed by the [EventName] wire identity, Common…"},"1531":{"u":"/docs/governance/adc-RemediationBacklog.html#implementation-band","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"Implementation band","x":"Categories scoring implementation <= 8, ranked by implPriority (target 9). §10, §21 and §22 also hold a maturity-band row; their items are under Open items. Σ implPriority = 34…"},"1532":{"u":"/docs/governance/adc-RemediationBacklog.html#maturity-band","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"Maturity band","x":"Σ maturity priority = 10 across the 4 rows (3+3+2+2)."},"1533":{"u":"/docs/governance/adc-RemediationBacklog.html#deliberate-and-accepted","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"Deliberate and accepted","x":"Recorded decisions, not scheduled work. - Single-region deployment (no multi-region failover): accepted in infra/DISASTER-RECOVERY.md, with the single SQL server and single…"},"1534":{"u":"/docs/governance/adc-RemediationBacklog.html#resolved","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"Resolved"},"1535":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html","d":"Architecture Evaluation Criteria","k":"Architecture Governance","x":"A structured rubric for evaluating the architecture of an enterprise application. Each category defines what is being assessed, concrete criteria to check, red flags that signal…"},"1536":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#how-to-use-this-rubric","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"How to Use This Rubric","x":"Score each category 0–4. Use the same scale everywhere so totals are comparable. Alongside the maturity level, rate how well each category is actually implemented on a finer 0–10…"},"1537":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#1-solid-principles","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"1. SOLID Principles","x":"Intent: Object/module-level design discipline that keeps code flexible and decoupled. Criteria - SRP: each class/handler has one reason to change; no \"god\" services orchestrating…"},"1538":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#2-design-patterns","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"2. Design Patterns","x":"Intent: Appropriate, idiomatic use of patterns, solving real problems, not pattern theater. Criteria - Creational (Factory methods on entities, Builder, Options) used where…"},"1539":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#3-clean-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"3. Clean Architecture","x":"Intent: Dependencies point inward; business rules are independent of frameworks, UI, and data stores. Criteria - Dependency rule enforced: Domain → (nothing); Application →…"},"1540":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#4-domain-driven-design","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"4. Domain-Driven Design","x":"Intent: The model reflects the business; boundaries follow capability boundaries, not technical layers. Criteria - Ubiquitous language: type/method names match business terms…"},"1541":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#5-vertical-slice-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"5. Vertical Slice Architecture","x":"Intent: Code is organized by feature/capability, so a change touches one cohesive slice. Criteria - Features grouped by use case (command/query + handler + validator + DTO…"},"1542":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#6-cqrs--event-driven-design","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"6. CQRS & Event-Driven Design","x":"Intent: Reads and writes are separated where it pays off; integration via events is reliable. Criteria - Commands (mutate, return Result) and queries (read, side-effect-free) are…"},"1543":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#7-microservices-readiness","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"7. Microservices Readiness","x":"Intent: Whether services (or future-extractable modules) are independently deployable and own their data. Criteria - Service boundaries align with bounded contexts; one team can…"},"1544":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#8-data-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"8. Data Architecture","x":"Intent: Persistence, consistency, and migrations are deliberate and safe. Criteria - Transaction boundaries match aggregate boundaries; unit-of-work scope is clear. - Migrations…"},"1545":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#9-api--contract-design","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"9. API & Contract Design","x":"Intent: External and inter-service contracts are clear, stable, and evolvable. Criteria - Consistent resource/endpoint design (REST/minimal APIs/gRPC) with predictable shapes. -…"},"1546":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#10-messaging--integration-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"10. Messaging & Integration Architecture","x":"Intent: Integration over a broker or an edge gateway is reliable, observable and evolvable: the transport, the failure paths and the long-running processes are deliberate. (§6…"},"1547":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#11-security","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"11. Security","x":"Intent: AuthN/AuthZ, secrets, and data protection are correct by construction. Criteria - Authentication centralized; tokens validated; identity flows documented (e.g.,…"},"1548":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#12-performance--scalability","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"12. Performance & Scalability","x":"Intent: The system meets latency/throughput goals and scales horizontally. Criteria - Async I/O throughout; no sync-over-async; no blocking the request thread. - Hot-path query…"},"1549":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#13-observability--operability","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"13. Observability & Operability","x":"Intent: You can understand and operate the system in production. Criteria - Structured logging with correlation/trace IDs flowing across module/service boundaries. - Distributed…"},"1550":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#14-testability--test-strategy","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"14. Testability & Test Strategy","x":"Intent: The design supports fast, reliable, meaningful tests at the right levels. Criteria - Healthy test pyramid: many fast unit tests on domain/application, fewer integration,…"},"1551":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#15-best-practices--code-quality","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"15. Best Practices & Code Quality","x":"Intent: Day-to-day craftsmanship that keeps the codebase healthy. Criteria - Analyzers at error severity (style, security, threading, maintainability) enforced in CI;…"},"1552":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#16-ai-native-application-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"16. AI-Native Application Architecture","x":"Intent: Where a product feature calls a language model or an agent, that dependency is governed like any other external system: isolated, versioned, evaluated, observed and…"},"1553":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#17-devops--deployment","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"17. DevOps & Deployment","x":"Intent: Building, releasing, and provisioning are automated, repeatable, and safe. (The local developer experience / inner loop behind this (local orchestration, cross-repo dev,…"},"1554":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#18-ui-architecture--component-design","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"18. UI Architecture & Component Design","x":"Intent: Components are cohesive, reusable, and composed cleanly, the UI has a deliberate structure, not page-sized blobs. Criteria - Container/presentational split: smart…"},"1555":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#19-state-management--data-flow","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"19. State Management & Data Flow","x":"Intent: Client state has a clear owner and predictable flow; server state is cached and invalidated deliberately. Criteria - Single source of truth per piece of state; ownership…"},"1556":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#20-design-system-theming--ui-consistency","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"20. Design System, Theming & UI Consistency","x":"Intent: A coherent visual language enforced by a component library, not re-implemented per screen. Criteria - Component library used consistently (e.g., MudBlazor): teams build…"},"1557":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#21-accessibility-a11y","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"21. Accessibility (a11y)","x":"Intent: The UI is usable by everyone, including assistive-technology users, and ideally enforced, not aspirational. Criteria - Semantic structure: correct…"},"1558":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#22-responsive-design--cross-browserdevice","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"22. Responsive Design & Cross-Browser/Device","x":"Intent: The UI works across viewport sizes, input modes, and supported browsers. Criteria - Fluid/responsive layouts via the design system's grid/breakpoints; no fixed-width…"},"1559":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#23-front-end-performance--rendering","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"23. Front-End Performance & Rendering","x":"Intent: The UI loads and responds fast; rendering work is bounded. (Complements §12: this is the client side.) Criteria - Initial load: bundle/payload size controlled;…"},"1560":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#24-forms-validation--ux-safety","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"24. Forms, Validation & UX Safety","x":"Intent: Data entry is safe, forgiving, and consistent, users don't lose work or get confused by errors. Criteria - Validation parity: client-side validation for fast feedback…"},"1561":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#25-navigation-routing--information-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"25. Navigation, Routing & Information Architecture","x":"Intent: Users can find their way; routes are meaningful, guarded, and role-aware. Criteria - Route design: clean, bookmarkable, deep-linkable URLs; parameters typed and…"},"1562":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#26-front-end-security","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"26. Front-End Security","x":"Intent: The client doesn't become the weak link, XSS, token handling, and trust boundaries are correct. (Complements §11.) Criteria - Output encoding / XSS: no unsanitized HTML…"},"1563":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#27-internationalization--localization","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"27. Internationalization & Localization","x":"Intent: The UI can be translated and respects culture, if in scope. (Score weight 0–1 if single-locale by design.) Criteria - Externalized strings: UI text in resource files, not…"},"1564":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#28-front-end-testing--quality","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"28. Front-End Testing & Quality","x":"Intent: The UI is verified at the right levels with stable, meaningful tests. (Complements §14.) Criteria - Component tests (e.g., bUnit) for rendering logic, parameters, events,…"},"1565":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#29-resilience-reliability--business-continuity","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"29. Resilience, Reliability & Business Continuity","x":"Intent: The system survives partial failure and recovers from disaster within defined objectives. (Extends the resilience facets of §7/§12 into a first-class recovery story.)…"},"1566":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#30-compliance-privacy--data-governance","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"30. Compliance, Privacy & Data Governance","x":"Intent: Personal and regulated data is classified, governed, and handled lawfully across its lifecycle. (§11 defends against attackers; this answers to regulators.) Criteria -…"},"1567":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#31-cost-efficiency--finops","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"31. Cost Efficiency / FinOps","x":"Intent: Cloud spend is proportional to value and driven by data, not guesswork. (§17 mentions cost; this makes it a first-class axis.) Criteria - Right-sizing: compute/database…"},"1568":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#32-dependency--supply-chain-management","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"32. Dependency & Supply-Chain Management","x":"Intent: Third-party and inter-package dependencies are controlled, auditable, and evolve safely, especially critical for a framework that publishes packages. (Elevates §15's…"},"1569":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#33-developer-experience--inner-loop","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"33. Developer Experience & Inner Loop","x":"Intent: Developers build, run, test, and iterate locally with fast, low-friction feedback. (Promoted out of §17: that scores release/ops automation; this scores the inner loop.)…"},"1570":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#34-architecture-governance--documentation","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"34. Architecture Governance & Documentation","x":"Intent: Decisions are recorded, conformance is enforced, and the system is documented so it stays coherent as it evolves. (Absorbs the former §16 Maintainability & Evolvability…"},"1571":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#appendix-quick-scan-checklist","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"Appendix: Quick-Scan Checklist","x":"A 2-minute triage before the full evaluation: any \"no\" warrants a deeper look. - [ ] Can you draw the dependency graph and is it acyclic and inward-pointing? - [ ] Is the domain…"},"1572":{"u":"/docs/governance/common-ArchitectureScorecard.html","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","x":"Canonical, version-controlled scorecard for this repo: the single source of truth for MMCA.Common's architecture scores. Scored against the rubric at…"},"1573":{"u":"/docs/governance/common-ArchitectureScorecard.html#at-a-glance","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"At a glance","x":"- Maturity index = Σ(maturity×weight) ÷ Σ(weight×4) = 317 ÷ 328 = 96.6% - Implementation index = Σ(impl×weight) ÷ Σ(weight×10) = 705 ÷ 820 = 86.0% - Weaker axis: Implementation…"},"1574":{"u":"/docs/governance/common-ArchitectureScorecard.html#top-5-risks","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 risks","x":"1. FinOps execution and governance live downstream, §31 (impl 8, maturity 2, the lowest maturity on the board; a documented accepted cap per the backlog's Deliberate and accepted…"},"1575":{"u":"/docs/governance/common-ArchitectureScorecard.html#top-5-strengths","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 strengths","x":"1. Dual-enforced Clean Architecture dependency rule (compile-time + fitness functions), §3 (impl 9): Source/Build/MMCA.Common.LayerEnforcement.targets:1-90 fails the build on…"},"1576":{"u":"/docs/governance/common-ArchitectureScorecard.html#scorecard","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Scorecard","x":"Weighted column = Maturity·weight / Implementation·weight per row."},"1577":{"u":"/docs/governance/common-ArchitectureScorecard.html#scoring-notes","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Scoring notes","x":"- N/A (excluded from denominators): none. All 34 rows carry numeric Maturity and Implementation, so Σweight is 82. §16 AI-Native Application Architecture is scored because…"},"1578":{"u":"/docs/governance/common-ArchitectureScorecard.html#cross-repo-comparison","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Cross-repo comparison","x":"How this repo's quality relates to the other consumers (where the framework's quality propagates, where a repo diverges or under-uses it, and where a consumer does better than…"},"1579":{"u":"/docs/governance/common-RemediationBacklog.html","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","x":"Derived from the scorecard: Maturity 96.6% (317/328) and Implementation 86.0% (705/820), framework v1.218.0, evidence as of 2026-10-01. Tasks are ranked on both scorecard axes,…"},"1580":{"u":"/docs/governance/common-RemediationBacklog.html#open-work-at-a-glance","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Open work at a glance"},"1581":{"u":"/docs/governance/common-RemediationBacklog.html#open-items","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Open items","x":"- Gap (maturity): SRP and DIP are enforced automatically: a ratcheted constructor-dependency ceiling is subclassed in Common…"},"1582":{"u":"/docs/governance/common-RemediationBacklog.html#implementation-band","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Implementation band","x":"Every category at implementation <= 8, ranked by implPriority = max(0, 9 − implementation) × weight. Four of these categories (1, 17, 30, 31) also sit in the maturity band; this…"},"1583":{"u":"/docs/governance/common-RemediationBacklog.html#maturity-band","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Maturity band","x":"Every category at maturity < 4, ranked by priority = (4 − maturity) × weight. Σ priority = 11 across the 4 maturity-band rows (4 + 3 + 2 + 2)."},"1584":{"u":"/docs/governance/common-RemediationBacklog.html#deliberate-and-accepted","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Deliberate and accepted","x":"Recorded decisions and deferred findings: each is real, none is scheduled work. The computed maturity priority = (4 − 2) × 2 = 4 is the highest weighted gap of any open category,…"},"1585":{"u":"/docs/governance/common-RemediationBacklog.html#resolved","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Resolved","x":"Categories at maturity 4 AND implementation = 9 (protect them, do not regress), then the closed items inside categories that are still open."},"1586":{"u":"/docs/governance/store-ArchitectureScorecard.html","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","x":"Canonical, version-controlled scorecard for this repo: the single source of truth for MMCA.Store's architecture scores. Scored against the rubric at…"},"1587":{"u":"/docs/governance/store-ArchitectureScorecard.html#at-a-glance","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"At a glance","x":"- Maturity index = Σ(maturity×weight) ÷ Σ(weight×4) = 308 ÷ 316 = 97.5% - Implementation index = Σ(impl×weight) ÷ Σ(weight×10) = 659 ÷ 790 = 83.4% - Weaker axis: Implementation…"},"1588":{"u":"/docs/governance/store-ArchitectureScorecard.html#top-5-risks","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 risks","x":"1. An internal gRPC edge returns customer PII to any caller: §11 (impl 7, weight 3): Identity maps CustomersGrpcService with .AllowAnonymous(), and it returns email and full name…"},"1589":{"u":"/docs/governance/store-ArchitectureScorecard.html#top-5-strengths","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 strengths","x":"1. Clean Architecture + DDD + CQRS depth, fitness-enforced: §3/§4 (impl 9): inherited framework substance, guarded by the shared NetArchTest suite (53 test classes,…"},"1590":{"u":"/docs/governance/store-ArchitectureScorecard.html#scorecard","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Scorecard","x":"Weighted = Maturity·weight / Implementation·weight."},"1591":{"u":"/docs/governance/store-ArchitectureScorecard.html#scoring-notes","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Scoring notes","x":"- Two axes per category: Maturity (0-4, process/governance) and Implementation (0-10, substance/execution). The indices are computed deterministically from the table: every…"},"1592":{"u":"/docs/governance/store-ArchitectureScorecard.html#cross-repo-comparison","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Cross-repo comparison","x":"How Store relates to MMCA.Common (the framework) and MMCA.ADC (the sibling consumer) is maintained once, for all three repos, in the workspace-internal…"},"1593":{"u":"/docs/governance/store-RemediationBacklog.html","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","x":"Derived from ArchitectureScorecard.md: Maturity 97.5% (308/316) / Implementation 83.4% (659/790), evidence as of 2026-10-01 at MMCA.Common. v1.217.0 (all 17 packages lockstep,…"},"1594":{"u":"/docs/governance/store-RemediationBacklog.html#open-work-at-a-glance","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"Open work at a glance"},"1595":{"u":"/docs/governance/store-RemediationBacklog.html#open-items","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"Open items","x":"- [ ] 11 Security, implementation 7: internal trust and secret handling. Four red flags hold the row at 7. The Identity customer gRPC edge is anonymous and returns email and full…"},"1596":{"u":"/docs/governance/store-RemediationBacklog.html#implementation-band","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"Implementation band","x":"21 categories, 52 gap points, the largest of the three repos. Levers are cited only where this ledger or the scorecard records one; an unnamed lever is named at the next…"},"1597":{"u":"/docs/governance/store-RemediationBacklog.html#maturity-band","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"Maturity band","x":"3 categories, 8 gap points. Ties break by priority desc, then weight desc, then category asc."},"1598":{"u":"/docs/governance/store-RemediationBacklog.html#deliberate-and-accepted","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"Deliberate and accepted","x":"- §16 AI-Native Application Architecture is N/A (rubric v2, ADR-110): no product feature calls a language model, so its weight 2 leaves both denominators until one does. - 5…"},"1599":{"u":"/docs/governance/store-RemediationBacklog.html#resolved","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"Resolved"},"1600":{"u":"/docs/guides/index.html","d":"Guides & Specifications","k":"Guides & Specifications","x":"As of: 2026-09-06. The narrative documentation for the MMCA platform: adoption guides, business specifications, workflow analyses, and per-concern reference notes. Files are…"},"1601":{"u":"/docs/guides/index.html#framework-mmcacommon","d":"Guides & Specifications","k":"Guides & Specifications","t":"Framework (MMCA.Common)","x":"- Capability Catalog: everything an adopting team gets, section by section (packages, scaffolding, the architectural core, the extraction path, data, messaging, API, identity,…"},"1602":{"u":"/docs/guides/index.html#mmcastore-e-commerce","d":"Guides & Specifications","k":"Guides & Specifications","t":"MMCA.Store (e-commerce)","x":"- Business Specification - Business Workflow Analysis - Navigation Flow - Manual Screen-Reader Pass Runbook"},"1603":{"u":"/docs/guides/index.html#mmcaadc-conference","d":"Guides & Specifications","k":"Guides & Specifications","t":"MMCA.ADC (conference)","x":"- Business Specifications - Navigation Flow - Manual Screen-Reader Pass Runbook - Integration-Test Tier Rework Plan Related reading: the Architecture Decision Records and the…"},"1604":{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","x":"As of: 2026-07-20. The automated layer (axe-core WCAG 2.1 AA scans in Tests/E2E/MMCA.ADC.E2E.Tests/AccessibilityTests.cs plus the shared Login/Register/Profile bases in…"},"1605":{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html#tools","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"Tools","x":"Run against the app started via Aspire (dotnet run --project Source/Hosting/MMCA.ADC.AppHost), reaching the UI through the Gateway. Test with the keyboard only (no mouse) for the…"},"1606":{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html#what-to-verify-on-every-flow","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"What to verify on every flow","x":"1. Landmarks and headings. The SR's landmark/heading list (NVDA Insert+F7) exposes a logical banner / navigation / main structure and a sensible h1...hN outline (one h1 per…"},"1607":{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html#flows-to-cover-mirror-the-automated-accessibilitytests-plus-the-role-journeys","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"Flows to cover (mirror the automated AccessibilityTests plus the role journeys)"},"1608":{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html#results-log","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"Results log","x":"Record one row per pass. A flow with any unresolved blocker is a FAIL (open a backlog item for it)."},"1609":{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html#after-a-pass","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"After a pass","x":"- File any defect as a remediation item; cross-link it from the §21 row's evidence. - Update the dated row above so the scorecard can cite a real, current manual pass (this is…"},"1610":{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","x":"As of: 2026-08-30. Status: complete (Phase 4 broker-transport tier landed 2026-07-06; Phase 5 residual = coverlet only). - Phase 0 ✅: Tests/WebAPI revived as MMCA.Common.API…"},"1611":{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#recommended-strategy-two-tiers","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Recommended strategy: two tiers","x":"1. Primary: per-service WebApplicationFactory : one in-process host per service (Identity / Conference / Engagement), cross-service edges mocked. AddBrokerMessaging…"},"1612":{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#three-code-facts-that-shape-the-rework-verified","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Three code facts that shape the rework (verified)","x":"- Only Conference.Service is WAF-incompatible: it ends with StartAsync() + self-HTTP/2 WarmupViaHttpAsync + WaitForShutdownAsync(). Identity/Engagement/Notification use…"},"1613":{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#databases","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Databases","x":"- SQLite in-memory for the fast bulk tier (no Docker, CI-friendly). A SQLite source with no migrations assembly has its schema created at startup by InitializeDatabasesAsync,…"},"1614":{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#project-structure","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Project structure","x":"- One WAF test project per service (MMCA.ADC.{Identity,Conference,Engagement}.IntegrationTests): can't reference two Program-bearing hosts in one project. - One…"},"1615":{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#ci","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"CI","x":"- Add the SQLite per-service tier to CI.slnf (seconds, no Docker) → restores the authz/CRUD merge gate (11) with no workflow change. - Keep the container-based MsSql + RabbitMQ…"},"1616":{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#phased-sequencing-fastest-win-first","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Phased sequencing (fastest win first)","x":"- Phase 0: re-home WebAPI.Tests middleware unit tests; drop the dead WebAPI reference; re-add to slnx+CI.slnf. ~16 tests green; removes a non-building project (16). - Phase 1:…"},"1617":{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#key-risks","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Key risks","x":"- The non-Identity JwtBearerOptions in-process override is the trickiest piece: prove it on one Conference auth test before fanning out. - SQLite vs SQL-Server fidelity (owned…"},"1618":{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#critical-files","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Critical files","x":"- Tests/Integration/MMCA.ADC.IntegrationTests/Infrastructure/TestWebApplicationFactory.cs (combined-host factory → split into per-service fixtures; its JWT config block is the…"},"1619":{"u":"/docs/guides/adc-NavigationFlow.html","d":"Navigation Flow","k":"Guides & Specifications","x":"As of: 2026-09-17. This document maps the site navigation flow for each actor in the MMCA.ADC application. Each mermaid diagram shows the pages accessible to that actor and the…"},"1620":{"u":"/docs/guides/adc-NavigationFlow.html#actors","d":"Navigation Flow","k":"Guides & Specifications","t":"Actors","x":"Roles & menu: the role vocabulary is Organizer, ContentEditor and Attendee (the default, RoleNames). Organizer holds every capability; ContentEditor holds the catalog-curation…"},"1621":{"u":"/docs/guides/adc-NavigationFlow.html#1-anonymous-user","d":"Navigation Flow","k":"Guides & Specifications","t":"1. Anonymous User","x":"Pages accessible without authentication: home, login, register, the two password-reset pages, and all public conference pages. ---"},"1622":{"u":"/docs/guides/adc-NavigationFlow.html#2-attendee-authenticated-user","d":"Navigation Flow","k":"Guides & Specifications","t":"2. Attendee (Authenticated User)","x":"Inherits all anonymous pages. Gains access to profile, feedback submission, and session bookmarking. Unauthenticated visitors are redirected to login. ---"},"1623":{"u":"/docs/guides/adc-NavigationFlow.html#3-speaker","d":"Navigation Flow","k":"Guides & Specifications","t":"3. Speaker","x":"Inherits all attendee pages. Gains access to the speaker dashboard for managing their own profile, viewing assigned sessions, and reviewing feedback ratings. ---"},"1624":{"u":"/docs/guides/adc-NavigationFlow.html#4-organizer","d":"Navigation Flow","k":"Guides & Specifications","t":"4. Organizer","x":"Authenticated users with the Organizer role. Inherits all attendee and public pages. Adds CRUD management for every conference entity (events, sessions, speakers, categories,…"},"1625":{"u":"/docs/guides/adc-NavigationFlow.html#5-functionality-flows-attendee--speaker","d":"Navigation Flow","k":"Guides & Specifications","t":"5. Functionality Flows (Attendee & Speaker)","x":"The diagrams in sections 1-4 map which pages each actor can reach. The diagrams below map how attendees and speakers accomplish each functionality, including inline actions…"},"1626":{"u":"/docs/guides/adc-NavigationFlow.html#navigation-patterns","d":"Navigation Flow","k":"Guides & Specifications","t":"Navigation Patterns","x":"- Unauthenticated users accessing protected pages are redirected to /login via the RedirectToLogin component. - Successful login/register redirects to Home (/) with a full page…"},"1627":{"u":"/docs/guides/adc-specifications.html","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","x":"As of: 2026-09-17. ---"},"1628":{"u":"/docs/guides/adc-specifications.html#1-system-overview","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"1. System Overview","x":"ADC is a conference management system for the Atlanta Developers Conference. It provides backend services to manage multi-day conference events, sessions, speakers, rooms,…"},"1629":{"u":"/docs/guides/adc-specifications.html#2-domain-model","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"2. Domain Model","x":"Relationships: - Owns many Rooms (child entities) - Owns many EventSpeakers (child join entities linking Event ↔ Speaker) - Owns many EventQuestionAnswers (child feedback…"},"1630":{"u":"/docs/guides/adc-specifications.html#3-business-rules","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"3. Business Rules","x":"Reading guide: Some rules reference other rules defined later in the document (e.g., BR-63, BR-80 are defined in Section 10). Forward references use the BR- numbering…"},"1631":{"u":"/docs/guides/adc-specifications.html#4-use-cases--business-processes","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"4. Use Cases / Business Processes","x":"See UC-30 (Registration) and UC-31 (Login) in Section 12.2 for the current email + password authentication flows. UC-34 (Request password reset) and UC-35 (Reset password) in the…"},"1632":{"u":"/docs/guides/adc-specifications.html#5-workflows--state-transitions","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"5. Workflows & State Transitions","x":"The Session.Status field is a free-text string imported from Sessionize. Default: null (for manually created sessions). Known Sessionize values: Accepted, Waitlisted, Accept…"},"1633":{"u":"/docs/guides/adc-specifications.html#6-events--side-effects","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"6. Events & Side Effects","x":"Domain events are raised for entity mutations. Not all events have registered handlers: events without handlers serve as extension points for future requirements. Note: Only…"},"1634":{"u":"/docs/guides/adc-specifications.html#7-business-constraints--invariants","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"7. Business Constraints & Invariants","x":"---"},"1635":{"u":"/docs/guides/adc-specifications.html#8-external-integrations-business-perspective","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"8. External Integrations (Business Perspective)","x":"---"},"1636":{"u":"/docs/guides/adc-specifications.html#9-glossary","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"9. Glossary","x":"---"},"1637":{"u":"/docs/guides/adc-specifications.html#ddd-structural-summary","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"DDD Structural Summary","x":"Why three bounded contexts instead of two: The original Events + Identity split grouped all conference-related entities together regardless of write profile. Separating…"},"1638":{"u":"/docs/guides/adc-specifications.html#10-specification-clarifications--addenda","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"10. Specification Clarifications & Addenda","x":"This section addresses gaps, ambiguities, and implicit design decisions identified during implementation review. New business rules are numbered BR-61+. API contract…"},"1639":{"u":"/docs/guides/adc-specifications.html#11-api-contract-specifications","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"11. API Contract Specifications","x":"This section documents API design decisions that apply across all endpoints. --- All error responses use the RFC 9457 ProblemDetails format (the successor to RFC 7807, same…"},"1640":{"u":"/docs/guides/adc-specifications.html#12-authentication--identity-architecture","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"12. Authentication & Identity Architecture","x":"This section defines the authentication mechanism for both the Web UI (Blazor) and MAUI (mobile) clients, which share a common Razor class library. It replaces the device-based…"},"1641":{"u":"/docs/guides/common-ACCESSIBILITY.html","d":"Accessibility (rubric §21)","k":"Guides & Specifications","x":"As of: 2026-07-20. The shared MMCA.Common.UI surface targets WCAG 2.1 AA. Accessibility is enforced two ways: an automated axe-core gate in CI (the bulk of coverage) and a…"},"1642":{"u":"/docs/guides/common-ACCESSIBILITY.html#automated-coverage-axe-core-wcag-21-aa","d":"Accessibility (rubric §21)","k":"Guides & Specifications","t":"Automated coverage (axe-core, WCAG 2.1 AA)","x":"The ui-e2e CI job runs Playwright + axe-core against the backend-less gallery; chromium is the blocking merge gate (firefox/webkit advisory). Scanned states: Component render is…"},"1643":{"u":"/docs/guides/common-ACCESSIBILITY.html#manual-screen-reader-pass","d":"Accessibility (rubric §21)","k":"Guides & Specifications","t":"Manual screen-reader pass","x":"Automation cannot judge reading order, focus management, or announcement quality, so the shared surface is walked manually. Checklist (re-run on any change to MainLayout, the…"},"1644":{"u":"/docs/guides/common-ACCESSIBILITY.html#known-limitations-tracked","d":"Accessibility (rubric §21)","k":"Guides & Specifications","t":"Known limitations (tracked)","x":"- ~~Dark-mode contrast (§20, not §21).~~ RESOLVED (2026-07-11). The two dark-palette WCAG AA contrast failures the prototype scan flagged (filled-primary button label ~2.65:1 on…"},"1645":{"u":"/docs/guides/common-BUILD-BY-HAND.html","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","x":"As of: 2026-09-13. This is the long-form walkthrough: every project, every file, and every load-bearing line that goes into an application on the MMCA.Common framework, in the…"},"1646":{"u":"/docs/guides/common-BUILD-BY-HAND.html#what-you-will-build","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"What you will build","x":"A modular monolith with one business module and two hosts: - Orders (your business module): an Order aggregate with OrderComment children, opened through a Result-returning…"},"1647":{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-0-prerequisites-and-decisions","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 0: Prerequisites and decisions","x":"Install: - .NET 10 SDK (the framework targets net10.0 with LangVersion: preview for C extension types). - SQL Server reachable locally (LocalDB, a container, or the one Aspire…"},"1648":{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-1-create-the-solution-and-the-build-plumbing","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 1: Create the solution and the build plumbing","x":"Scaffolded. dotnet new mmca-app writes every file in this phase. Read it to know what each one does; you do not need to type any of it. The plumbing files are the load-bearing,…"},"1649":{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-2-scaffold-the-module-project-set","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 2: Scaffold the module project set","x":"Scaffolded. dotnet new mmca-app creates this project set for your first module, and pwsh build/add-module.ps1 adds another one later: it drives dotnet new mmca-module and then…"},"1650":{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-3-the-vertical-slice-end-to-end-the-heart-of-it","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 3: The vertical slice end-to-end (the heart of it)","x":"Scaffolded. The generated module already contains this slice and six more, worked end to end. dotnet new mmca-command and dotnet new mmca-query add another one. This phase is the…"},"1651":{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-4-dbcontext-model-and-migrations","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 4: DbContext model and migrations","x":"Partly scaffolded. The migrations project and its design-time factory are generated. Running dotnet ef migrations add InitialCreate is still yours, and for a module added later…"},"1652":{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-5-compose-the-monolith-host-and-run-it","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 5: Compose the monolith host and run it","x":"Scaffolded. Both hosts, the AppHost, and the .resx pairs are generated. Read this phase before you touch any of them: the DI sequence, WaitFor(sql) rather than the database…"},"1653":{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-6-tests-and-the-architecture-fitness-map","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 6: Tests and the architecture-fitness map","x":"Scaffolded, with one deliberate gap. All three test projects and the map are generated. The IntegrationEventContractTests subclass is NOT: its frozen literal lists members…"},"1654":{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-7-upgrading-the-framework-version","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 7: Upgrading the framework version","x":"Not scaffolded. dotnet new mmca-app --framework-version picks the version you START on; moving to a later one is this phase. When a new MMCA.Common release ships, upgrade in one…"},"1655":{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-8-extract-a-module-into-its-own-service-the-payoff","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 8: Extract a module into its own service (the payoff)","x":"Not scaffolded. The generated solution carries the plumbing (the .Contracts proto convention and the .Service OpenAPI block in Directory.Build.props), but the extraction itself…"},"1656":{"u":"/docs/guides/common-BUILD-BY-HAND.html#verification-checklist","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Verification checklist","x":"1. Build green: dotnet build Contoso.Support.slnx with no warnings (TreatWarningsAsErrors + five analyzers). This is the primary automatable gate. 2. Unit + architecture tests…"},"1657":{"u":"/docs/guides/common-BUILD-BY-HAND.html#where-to-look-next","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Where to look next","x":"- Getting Started: the one-command path that writes phases 1 through 6 for you. If you are starting a new solution rather than adding the framework to an existing one, that is…"},"1658":{"u":"/docs/guides/common-CAPABILITIES.html","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","x":"As of: 2026-09-10 (framework v1.187.0). Everything an adopting team gets from the MMCA.Common framework: a .NET 10 package set for building a modular monolith on DDD, Clean…"},"1659":{"u":"/docs/guides/common-CAPABILITIES.html#package-map","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Package map","x":"Seventeen packages, one version number. The layer rule is enforced at compile time inside the framework and at test time in every consumer: API/Grpc depend on Infrastructure,…"},"1660":{"u":"/docs/guides/common-CAPABILITIES.html#getting-started-and-scaffolding","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Getting started and scaffolding","x":"From zero to a green, warning-free, tested solution before writing any business logic. - One-command solution scaffold. dotnet new install MMCA.Templates then dotnet new mmca-app…"},"1661":{"u":"/docs/guides/common-CAPABILITIES.html#architectural-core","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Architectural core","x":"The opinionated backbone: errors as values, one sealed pipeline, DDD building blocks, and module composition. - Result pattern end to end. Result / Result with nine ErrorType…"},"1662":{"u":"/docs/guides/common-CAPABILITIES.html#monolith-now-services-later","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Monolith now, services later","x":"The framework's central promise, and it is a tested property, not a slogan. - Host-only extraction. Turning a module into its own service changes hosting code only; Domain,…"},"1663":{"u":"/docs/guides/common-CAPABILITIES.html#data-and-persistence","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Data and persistence","x":"EF Core with the cross-cutting concerns every production system eventually rebuilds, already built. - Multi-database routing. Every entity resolves to a DataSourceKey (engine,…"},"1664":{"u":"/docs/guides/common-CAPABILITIES.html#messaging-and-eventing","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Messaging and eventing","x":"Reliable events with two deliberately distinct paths: in-process domain events and durable integration events. - Transactional outbox. The save interceptor writes every captured…"},"1665":{"u":"/docs/guides/common-CAPABILITIES.html#http-api-surface","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"HTTP API surface","x":"Controllers and middleware that erase most CRUD endpoint code and keep the pipeline order under test. - Five generic read endpoints for free. EntityControllerBase gives list,…"},"1666":{"u":"/docs/guides/common-CAPABILITIES.html#identity-auth-and-authorization","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Identity, auth, and authorization","x":"A full identity building-block set you compose when you want it; a fresh app runs issuer-less until then. - RS256 + JWKS + OIDC discovery across services. TokenService signs with…"},"1667":{"u":"/docs/guides/common-CAPABILITIES.html#caching-resilience-and-performance","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Caching, resilience, and performance","x":"- Declarative handler caching. A query implements IQueryCacheable, its invalidating commands implement ICacheInvalidating, and the pipeline does the rest; tenant-scoped keys are…"},"1668":{"u":"/docs/guides/common-CAPABILITIES.html#ui-blazor-web-desktop-and-mobile","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"UI: Blazor web, desktop, and mobile","x":"One shared component layer serving Blazor Server, WASM, and MAUI hybrid heads on Android, iOS, Mac Catalyst, and Windows. - Module-composed application shell. AddUIModule() scans…"},"1669":{"u":"/docs/guides/common-CAPABILITIES.html#hosting-orchestration-and-operations","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Hosting, orchestration, and operations","x":"Aspire-first local orchestration and one-line production plumbing. - AppHost DSL. Thirteen extensions: WithSQLServerDataSource, WithCosmosDataSource, WithSqliteDataSource…"},"1670":{"u":"/docs/guides/common-CAPABILITIES.html#testing-and-quality-gates","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Testing and quality gates","x":"The framework ships its own enforcement: adopters subclass, and drift fails the build. - 124 test methods across 47 abstract bases. Thirty-seven are parameterized by your…"},"1671":{"u":"/docs/guides/common-CAPABILITIES.html#compliance-and-privacy","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Compliance and privacy","x":"- PII handling. [Pii] marks personal data; the audit trail redacts marked values at capture so it never becomes a second copy erasure has to chase; PiiRedactor (Redact,…"},"1672":{"u":"/docs/guides/common-CAPABILITIES.html#governance-versioning-and-supply-chain","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Governance, versioning, and supply chain","x":"- 117 ADRs. Every framework pattern is documented as an architecture decision record with context and consequences, published publicly and indexed in one place; adopters inherit…"},"1673":{"u":"/docs/guides/common-CAPABILITIES.html#documentation-library","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Documentation library","x":"This library holds ten framework guides, the ADR index, the governance rubric with scorecards, and a chapter-per-subsystem onboarding walkthrough. - Getting Started. Nothing to a…"},"1674":{"u":"/docs/guides/common-CAPABILITIES.html#constraints-worth-knowing","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Constraints worth knowing","x":"Design choices an adopter should read before sizing a plan around them. None is a defect; each is documented in source or an ADR. - The outbox is off in in-process mode. With…"},"1675":{"u":"/docs/guides/common-COST.html","d":"Cost & FinOps Notes (rubric §31)","k":"Guides & Specifications","x":"As of: 2026-09-02. MMCA.Common is a library, so it cannot provision anything: right-sizing, scale rules, budgets, and per-service cost attribution live in the consumer apps' IaC…"},"1676":{"u":"/docs/guides/common-COST.html#what-the-framework-does-for-cost","d":"Cost & FinOps Notes (rubric §31)","k":"Guides & Specifications","t":"What the framework does for cost","x":"- Telemetry ingestion is the real line item, so high-volume / low-value spans are dropped. OutboxPollFilterProcessor (MMCA.Common.Aspire) suppresses the recurring OutboxPoll…"},"1677":{"u":"/docs/guides/common-COST.html#recommended-consumer-defaults-set-these-downstream","d":"Cost & FinOps Notes (rubric §31)","k":"Guides & Specifications","t":"Recommended consumer defaults (set these downstream)","x":"- Telemetry retention & sampling. Tune Log Analytics retention to the minimum the consumer's compliance window allows, and set Telemetry:TracesSampleRatio (the built-in…"},"1678":{"u":"/docs/guides/common-COST.html#cost-attribution--guardrail-samples-distilled-from-mmcaadc","d":"Cost & FinOps Notes (rubric §31)","k":"Guides & Specifications","t":"Cost-attribution & guardrail samples (distilled from MMCA.ADC)","x":"These belong in the consumer's IaC, not the library, but the framework documents the shape so every consumer attributes spend and guards surges the same way. The worked, deployed…"},"1679":{"u":"/docs/guides/common-COST.html#out-of-scope-for-the-framework-by-design","d":"Cost & FinOps Notes (rubric §31)","k":"Guides & Specifications","t":"Out of scope for the framework (by design)","x":"Provisioning, scale rules, budgets, per-service cost attribution, and surge/revert automation are consumer/IaC concerns and are not added to the library: see also ADR-009…"},"1680":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","x":"As of: 2026-08-06. MMCA.ECommerce is the simplest e-commerce application on the MMCA.Common framework: a Products catalog module and an Orders module with line items, behind a…"},"1681":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#before-you-start","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"Before you start","x":"- .NET 10 SDK (the framework targets net10.0 with LangVersion: preview). - Docker Desktop (Aspire provisions SQL Server as a container). - EF Core tools: dotnet tool install…"},"1682":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#2-generate-the-solution-with-the-products-module","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"2. Generate the solution with the Products module","x":"Five options do most of this guide's old work. Three remove an axis a catalog product does not have, and the code for an axis you turn off is never generated: --flat drops the…"},"1683":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#3-add-the-orders-module","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"3. Add the Orders module","x":"build/add-module.ps1 ships inside the solution you just generated. It runs dotnet new mmca-module with the shape options passed through, then performs every wire-up the template…"},"1684":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#4-reshape-products-into-a-catalog-product","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"4. Reshape Products into a catalog product","x":"The scaffolded module arrives as the template's worked example in your namespaces, already shaped by the flags in step 2: no children, no status, no requester, Name instead of…"},"1685":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#5-reshape-orders-into-an-order-with-line-items","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"5. Reshape Orders into an order with line items","x":"Orders keeps the child-collection pattern the template scaffolded, retargeted. -Child Item already did the naming (the entity is OrderItem, the slices are AddItem / EditItem /…"},"1686":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#6-point-the-ui-at-the-new-domain","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"6. Point the UI at the new domain","x":"The scaffolded Blazor host already has the load-bearing parts: the typed ECommerceApiClient calling the API server-side through Aspire service discovery (no CORS, no token), the…"},"1687":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#7-create-the-migrations","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"7. Create the migrations","x":"Neither module has a migration yet: any shape flag makes mmca-app drop the template's sample one (it described the sample shape), and -SkipMigration deferred the Orders one to…"},"1688":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#8-the-two-one-time-fixups-then-run-it","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"8. The two one-time fixups, then run it","x":"Apply the two fixups the scaffold deliberately leaves to you (they are name-dependent, so no generated value could be right). First, sort the using directives and the identifier…"},"1689":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#verification-checklist","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"Verification checklist","x":"1. Baseline green immediately after mmca-app, before any edit: 81 tests. 2. After build/add-module.ps1: still green at 99 tests, both modules' scaffolded suites running. 3. After…"},"1690":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#where-to-look-next","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"Where to look next","x":"- MMCA.ECommerce: the finished result of this guide, build- and test-verified. - Getting started: the single-module path, the vertical-slice templates (mmca-command /…"},"1691":{"u":"/docs/guides/common-GETTING-STARTED.html","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","x":"As of: 2026-10-01 (MMCA.Templates 1.12.0, framework v1.218.0). MMCA.Common is a .NET 10 framework for DDD, Clean Architecture, and CQRS, shipped as a set of lockstep-versioned…"},"1692":{"u":"/docs/guides/common-GETTING-STARTED.html#before-you-start","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Before you start","x":"- .NET 10 SDK. The framework targets net10.0 with LangVersion: preview for C extension types. - Docker Desktop. Aspire provisions SQL Server as a container, so you do not install…"},"1693":{"u":"/docs/guides/common-GETTING-STARTED.html#1-install-the-template-pack","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"1. Install the template pack","x":"Four templates arrive: mmca-app (a whole solution), mmca-module (a business module across all five layers), and mmca-command / mmca-query (a single vertical slice)."},"1694":{"u":"/docs/guides/common-GETTING-STARTED.html#2-generate-the-solution","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"2. Generate the solution","x":"Three names, and they are independent: the solution (also your root namespace), the first module in plural PascalCase, and that module's aggregate root in singular PascalCase.…"},"1695":{"u":"/docs/guides/common-GETTING-STARTED.html#3-build-and-test-before-you-change-anything","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"3. Build and test before you change anything","x":"That is a warning-free build with TreatWarningsAsErrors and all five analyzers at error severity, and a passing test run including the architecture-fitness rules, with no…"},"1696":{"u":"/docs/guides/common-GETTING-STARTED.html#4-check-the-first-migration","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"4. Check the first migration","x":"The scaffold ships the migrations project, its design-time factory, and (in the default shape) the sample's migrations already renamed for your aggregate. Look inside…"},"1697":{"u":"/docs/guides/common-GETTING-STARTED.html#5-run-it","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"5. Run it","x":"Run this from a real, interactive terminal. Launched from a headless or background shell the Aspire AppHost stalls at control-plane init and no dashboard appears. The dashboard…"},"1698":{"u":"/docs/guides/common-GETTING-STARTED.html#6-the-one-time-fixup","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"6. The one-time fixup","x":"The scaffold deliberately does not hand this one over, because renaming invalidates it and no fixed value is right for every name you could pick. It is covered in full in the…"},"1699":{"u":"/docs/guides/common-GETTING-STARTED.html#what-you-were-handed","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"What you were handed","x":"The Order aggregate arrives fully worked: a Result-returning factory, invariants, guarded mutations raising domain events, a child entity, soft-delete cascade, the caching pair,…"},"1700":{"u":"/docs/guides/common-GETTING-STARTED.html#add-your-next-feature","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Add your next feature","x":"A vertical slice (the path every feature follows) is one command, run from the module's UseCases folder: Handlers, validators, and mappers are convention-scanned, so there is no…"},"1701":{"u":"/docs/guides/common-GETTING-STARTED.html#surface-the-slice-at-the-edge","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Surface the slice at the edge","x":"The scaffold stops at the handler, and the template's closing instructions tell you to map the command in your module's controller. Every write in the generated app follows the…"},"1702":{"u":"/docs/guides/common-GETTING-STARTED.html#then-what","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Then what","x":"- Upgrade the framework. Bump every MMCA.Common. entry in Directory.Packages.props together, in one pass. See Phase 7 and the versioning policy. - Add real authentication. Copy…"},"1703":{"u":"/docs/guides/common-GETTING-STARTED.html#verification-checklist","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Verification checklist","x":"1. dotnet new mmca-app -n produced a solution that builds and tests green before you changed anything. 2. dotnet build .slnx is warning-free (TreatWarningsAsErrors + five…"},"1704":{"u":"/docs/guides/common-GETTING-STARTED.html#where-to-look-next","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Where to look next","x":"- Templates: every parameter of all four templates, dropping the Blazor UI host, and how the pack is built. ADR-065 explains why it is derived from the reference app rather than…"},"1705":{"u":"/docs/guides/common-RESILIENCE.html","d":"Resilience & Business Continuity (rubric §29)","k":"Guides & Specifications","x":"As of: 2026-09-15. MMCA.Common is a library, so it cannot operate a deployment: restores, RTO/RPO, and SLO alerting are executed in the consumer apps' IaC (e.g. MMCA.ADC's…"},"1706":{"u":"/docs/guides/common-RESILIENCE.html#what-the-framework-provides-and-verifies-in-repo","d":"Resilience & Business Continuity (rubric §29)","k":"Guides & Specifications","t":"What the framework provides (and verifies in-repo)","x":"Failure isolation, graceful degradation, graceful startup, and the restore procedure itself are therefore demonstrated and tested centrally: the framework drills backup→restore…"},"1707":{"u":"/docs/guides/common-RESILIENCE.html#caching-is-fail-open-configurable-never-load-bearing","d":"Resilience & Business Continuity (rubric §29)","k":"Guides & Specifications","t":"Caching is fail-open (configurable, never load-bearing)","x":"The cache is an optimization, never the system of record, so nothing about it can turn a cache problem into a failed request. A miss, an unreachable cache, or a populate that…"},"1708":{"u":"/docs/guides/common-RESILIENCE.html#baseline-slo--error-budget-template-consumers-fill-in","d":"Resilience & Business Continuity (rubric §29)","k":"Guides & Specifications","t":"Baseline SLO / error-budget template (consumers fill in)","x":"Adopt and tune per app; ADC's filled-in version lives in infra/DISASTER-RECOVERY.md + the SLO metric-alerts in infra/main.bicep. Define RTO/RPO per service (ADC's worked…"},"1709":{"u":"/docs/guides/common-RESILIENCE.html#restore-drill-runbook-reference","d":"Resilience & Business Continuity (rubric §29)","k":"Guides & Specifications","t":"Restore-drill runbook (reference)","x":"The only evidence backups actually restore is a periodic drill: restore a throwaway copy, confirm it comes back Online, record the measured restore time, then delete the copy.…"},"1710":{"u":"/docs/guides/common-RESPONSIVE.html","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","x":"As of: 2026-07-20. This document is the supported-device and browser matrix for the shared MMCA.Common.UI component library. It makes the responsive contract explicit (the rubric…"},"1711":{"u":"/docs/guides/common-RESPONSIVE.html#breakpoints","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","t":"Breakpoints","x":"The framework keeps C viewport detection and CSS media queries aligned around one mobile threshold. The C 960px mobile cutoff and the CSS 1023.98px cutoff intentionally differ:…"},"1712":{"u":"/docs/guides/common-RESPONSIVE.html#supported-devices","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","t":"Supported devices"},"1713":{"u":"/docs/guides/common-RESPONSIVE.html#touch-targets","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","t":"Touch targets","x":"Interactive controls on mobile surfaces meet a 48px minimum hit area (Material Design), exceeding both WCAG 2.5.8 Target Size (Minimum, AA, 24px) and WCAG 2.5.5 Target Size…"},"1714":{"u":"/docs/guides/common-RESPONSIVE.html#grid-density","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","t":"Grid density","x":"DataGridListPageBase exposes a DenseGrid property and a ToggleDensity() method. Derived list pages bind Dense=\"@DenseGrid\" on their MudDataGrid and surface a toggle. The chosen…"},"1715":{"u":"/docs/guides/common-RESPONSIVE.html#browser-matrix","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","t":"Browser matrix","x":"The shared UI is tested against three Playwright engines in CI (.github/workflows/ci.yml, ui-e2e job): a real-browser axe (WCAG 2.1 AA) + render smoke against the backend-less…"},"1716":{"u":"/docs/guides/common-SMALL-APPS.html","d":"Small Apps: the lowest floor MMCA.Common has","k":"Guides & Specifications","x":"As of: 2026-08-30. Two mmca-app options decide the shape of the solution rather than the shape of the sample module, and together they are the smallest thing the framework…"},"1717":{"u":"/docs/guides/common-SMALL-APPS.html#why-this-shape-exists","d":"Small Apps: the lowest floor MMCA.Common has","k":"Guides & Specifications","t":"Why this shape exists","x":"The default mmca-app output is a twelve-project solution: five module layers, a REST API host, a Blazor UI host, an Aspire AppHost, a migrations project, and three test projects.…"},"1718":{"u":"/docs/guides/common-SMALL-APPS.html#the-five-minute-path","d":"Small Apps: the lowest floor MMCA.Common has","k":"Guides & Specifications","t":"The five-minute path","x":"Add --flat if the aggregate owns no child collection; the four module-shape flags all still apply (see the templates guide). Warning-free under all five analyzers with…"},"1719":{"u":"/docs/guides/common-SMALL-APPS.html#what-you-get-anyway","d":"Small Apps: the lowest floor MMCA.Common has","k":"Guides & Specifications","t":"What you get anyway","x":"Nothing in the framework is disabled by this shape. The application code is identical to the SQL-Server-plus-Aspire output: the engine is a configuration-base choice plus a…"},"1720":{"u":"/docs/guides/common-SMALL-APPS.html#what-is-deliberately-off","d":"Small Apps: the lowest floor MMCA.Common has","k":"Guides & Specifications","t":"What is deliberately off","x":"- The transactional outbox. With in-process messaging (the default, since no MessageBus:Provider is configured) the outbox resolves off: events are dispatched inside the process…"},"1721":{"u":"/docs/guides/common-SMALL-APPS.html#adding-a-second-module","d":"Small Apps: the lowest floor MMCA.Common has","k":"Guides & Specifications","t":"Adding a second module","x":"mmca-module takes --database sqlserversqlite, and a solution generated by mmca-app adds its modules through build/add-module.ps1, which reads the engine off the API host's own…"},"1722":{"u":"/docs/guides/common-SMALL-APPS.html#two-limitations-worth-knowing-before-you-start","d":"Small Apps: the lowest floor MMCA.Common has","k":"Guides & Specifications","t":"Two limitations worth knowing before you start","x":"1. Writes need the X-Tenant-Id header. The generated app enables tenancy with RequireTenant: false, so an unresolved caller reads across tenants, but a write with no tenant…"},"1723":{"u":"/docs/guides/common-SMALL-APPS.html#when-to-flip-each-switch","d":"Small Apps: the lowest floor MMCA.Common has","k":"Guides & Specifications","t":"When to flip each switch","x":"Each of these is additive: the code you have written does not change, and the change is confined to configuration or to a host project. ---"},"1724":{"u":"/docs/guides/common-SMALL-APPS.html#where-to-look-next","d":"Small Apps: the lowest floor MMCA.Common has","k":"Guides & Specifications","t":"Where to look next","x":"- Getting started: the full-shape path, in six steps, with Aspire and SQL Server. - Templates: every parameter of all four templates, including the two solution-shape options…"},"1725":{"u":"/docs/guides/common-TEMPLATES.html","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","x":"As of: 2026-09-20 (MMCA.Templates 1.11.0, framework v1.206.0). MMCA.Templates is a dotnet new pack that scaffolds solutions, modules, and vertical slices on the MMCA.Common…"},"1726":{"u":"/docs/guides/common-TEMPLATES.html#mmca-app","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","t":"mmca-app","x":"The module and aggregate names are independent, so --module Billing --aggregate Invoice is fine. Everything derived from them follows: routes, the Aspire database resource, the…"},"1727":{"u":"/docs/guides/common-TEMPLATES.html#mmca-module","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","t":"mmca-module","x":"The six shape options behave exactly as they do for mmca-app, and they are per module: a solution can hold a flat, status-less catalog module beside one whose aggregate owns a…"},"1728":{"u":"/docs/guides/common-TEMPLATES.html#buildadd-moduleps1","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","t":"build/add-module.ps1","x":"Since 1.4.0 every solution mmca-app generates ships this script, and it is the supported way to add a second module. It runs mmca-module with your shape options passed through,…"},"1729":{"u":"/docs/guides/common-TEMPLATES.html#mmca-command-and-mmca-query","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","t":"mmca-command and mmca-query","x":"Run these from the module's UseCases folder. Each creates a folder named after the slice holding its two files. --child-collection exists because both handlers load through…"},"1730":{"u":"/docs/guides/common-TEMPLATES.html#how-the-pack-is-built","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","t":"How the pack is built","x":"The template content is the MMCA.Helpdesk reference application itself, staged at pack time. There is no second copy of the solution, so the template cannot drift from the app…"},"1731":{"u":"/docs/guides/common-VERSIONING.html","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","x":"As of: 2026-09-03. MMCA.Common publishes its NuGet packages (the authoritative list and count live in FACTS.md) versioned and released together as a single unit. They share one…"},"1732":{"u":"/docs/guides/common-VERSIONING.html#semantic-versioning","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"Semantic Versioning","x":"Versions follow SemVer 2.0: MAJOR.MINOR.PATCH: - MAJOR: reserved (see \"Breaking changes within 1.x\" below). - MINOR: new capability, and the channel breaking changes currently…"},"1733":{"u":"/docs/guides/common-VERSIONING.html#what-counts-as-breaking","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"What counts as breaking","x":"A change is breaking if it is any of: - Removing or renaming a public type/member, or changing a signature. - Changing the meaning of an existing configuration key, or changing a…"},"1734":{"u":"/docs/guides/common-VERSIONING.html#breaking-changes-within-1x","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"Breaking changes within 1.x","x":"Breaking changes ship as MINOR bumps, not MAJOR ones, and the version number is therefore not a reliable breakage signal on its own. This is deliberate and follows from the…"},"1735":{"u":"/docs/guides/common-VERSIONING.html#consumer-rollout","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"Consumer rollout","x":"Per project convention, framework upgrades are swept across all consumers in one pass: there are no opt-in flags or phased rollouts for a MMCA.Common change. When a release…"},"1736":{"u":"/docs/guides/common-VERSIONING.html#deprecation","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"Deprecation","x":"There is no [Obsolete] grace period today. Because the lockstep sweep updates every first-party caller in the same change set, a superseded API is removed in the release that…"},"1737":{"u":"/docs/guides/common-VERSIONING.html#supply-chain","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"Supply chain","x":"- All package versions are centrally pinned (Directory.Packages.props). - NuGet lock files are committed for reproducible restores. - MassTransit is pinned to v8 by policy (v9…"},"1738":{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","x":"As of: 2026-09-07. The automated layer (axe-core WCAG 2.1 AA scans in Tests/E2E/MMCA.Store.E2E.Tests/Workflows/AccessibilityTests.cs plus the shared Login/Register/Profile bases…"},"1739":{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html#tools","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"Tools","x":"Run against the app started via Aspire (dotnet run --project Source/Hosting/MMCA.Store.AppHost), reaching the Web UI at https://localhost:6002. Test with the keyboard only (no…"},"1740":{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html#what-to-verify-on-every-flow","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"What to verify on every flow","x":"1. Landmarks and headings. The SR's landmark/heading list (NVDA Insert+F7) exposes a logical banner / navigation / main structure and a sensible h1...hN outline (one h1 per…"},"1741":{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html#flows-to-cover-mirror-the-automated-accessibilitytests-plus-the-role-journeys","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"Flows to cover (mirror the automated AccessibilityTests plus the role journeys)"},"1742":{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html#results-log","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"Results log","x":"Record one row per pass. A flow with any unresolved blocker is a FAIL (open a backlog item for it)."},"1743":{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html#after-a-pass","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"After a pass","x":"- File any defect as a remediation item; cross-link it from the §21 row's evidence. - Update the dated row above so the scorecard can cite a real, current manual pass (this is…"},"1744":{"u":"/docs/guides/store-BusinessWorkflows.html","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","x":"As of: 2026-09-07."},"1745":{"u":"/docs/guides/store-BusinessWorkflows.html#workflow-list-summary","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"Workflow List Summary","x":"---"},"1746":{"u":"/docs/guides/store-BusinessWorkflows.html#1-identity-module-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"1. Identity Module Workflows","x":"Entry Point: POST /auth/register, AuthController.RegisterAsync(), AllowAnonymous Execution Path: Business Steps: 1. Validate registration input (email, password, first name, last…"},"1747":{"u":"/docs/guides/store-BusinessWorkflows.html#2-catalog-module-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"2. Catalog Module Workflows","x":"Entry Point: POST /categories, Admin only, [Idempotent] Response: 201 Created with CategoryDTO Entry Point: PUT /categories/{id}/name, Admin only Entry Point: PUT…"},"1748":{"u":"/docs/guides/store-BusinessWorkflows.html#3-sales-module-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"3. Sales Module Workflows","x":"Entry Point: POST /shoppingcarts/{customerId}/shoppingcartitems, Authenticated (owner or admin via OwnerOrAdminFilter) Decision Points: - Product variant doesn't exist - NotFound…"},"1749":{"u":"/docs/guides/store-BusinessWorkflows.html#4-ui-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"4. UI Workflows","x":"The UI provides a complete shopping experience through the CartDrawer component and Blazor pages. The CartDrawer is the only cart UI: there is no dedicated cart page. It is a…"},"1750":{"u":"/docs/guides/store-BusinessWorkflows.html#5-cross-module-interactions","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"5. Cross-Module Interactions","x":"Both export edges are best-effort: an unreachable peer degrades that one section rather than failing the export, so neither carries a startup wait. Asynchronously, four…"},"1751":{"u":"/docs/guides/store-BusinessWorkflows.html#6-external-interactions","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"6. External Interactions","x":"---"},"1752":{"u":"/docs/guides/store-BusinessWorkflows.html#7-cross-cutting-concerns-participating-in-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"7. Cross-Cutting Concerns Participating in Workflows","x":"---"},"1753":{"u":"/docs/guides/store-BusinessWorkflows.html#8-end-to-end-customer-journey","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"8. End-to-End Customer Journey","x":"Alternative Flows: - Payment fails - Order status PaymentFailed - customer can retry (create new Stripe session) - Cancel order - Status Cancelled (from PendingPayment,…"},"1754":{"u":"/docs/guides/store-BusinessWorkflows.html#9-potentially-missing-or-incomplete-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"9. Potentially Missing or Incomplete Workflows","x":"--- This document is derived from source code analysis. All workflows, decisions, and behaviors described above are confirmed implementations traceable to the referenced source…"},"1755":{"u":"/docs/guides/store-NavigationFlow.html","d":"Navigation Flow","k":"Guides & Specifications","x":"As of: 2026-09-25. This document maps the site navigation flow for each actor in the MMCA.Store application. Each mermaid diagram shows the pages accessible to that actor and the…"},"1756":{"u":"/docs/guides/store-NavigationFlow.html#actors","d":"Navigation Flow","k":"Guides & Specifications","t":"Actors","x":"Roles and enforcement: Admin is the only elevated role (registration creates a Customer). The 19 admin pages carry page-level [Authorize(Roles = \"Admin\")]. The three per-module…"},"1757":{"u":"/docs/guides/store-NavigationFlow.html#1-anonymous-user","d":"Navigation Flow","k":"Guides & Specifications","t":"1. Anonymous User","x":"Pages accessible without authentication: home, login, register, the two password-reset pages, and the public catalog. Add-to-cart on the product detail page sits inside an…"},"1758":{"u":"/docs/guides/store-NavigationFlow.html#2-customer-authenticated-user","d":"Navigation Flow","k":"Guides & Specifications","t":"2. Customer (Authenticated User)","x":"Inherits all anonymous pages. Gains the profile page, the cart drawer (a layout component, not a route), checkout, and their own orders. Unauthenticated visitors deep-linking to…"},"1759":{"u":"/docs/guides/store-NavigationFlow.html#3-admin","d":"Navigation Flow","k":"Guides & Specifications","t":"3. Admin","x":"Inherits all customer pages, plus the admin CRUD surfaces for all three modules and the user and role administration pages. Every page below except the shared /orders pair (see…"},"1760":{"u":"/docs/guides/store-NavigationFlow.html#authorization-model","d":"Navigation Flow","k":"Guides & Specifications","t":"Authorization Model","x":"Three cooperating layers; the API is always the boundary: 1. Page-level route guards. The 19 admin pages carry [Authorize(Roles = \"Admin\")] and /profile / /orders carry…"},"1761":{"u":"/docs/guides/store-Specification.html","d":"MMCA Business Specification Document","k":"Guides & Specifications","x":"As of: 2026-09-07."},"1762":{"u":"/docs/guides/store-Specification.html#1-system-overview","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"1. System Overview","x":"MMCA is an e-commerce platform built with .NET 10.0 using DDD and Clean Architecture. The business logic is organized as modules (Catalog, Sales, Identity) that have been…"},"1763":{"u":"/docs/guides/store-Specification.html#2-core-business-entities","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"2. Core Business Entities","x":"Description: A classification grouping for products. Supports hierarchical (parent-child) structures for nested categorization (e.g., \"Jewelry\" \"Rings\"). Key Properties:…"},"1764":{"u":"/docs/guides/store-Specification.html#3-business-workflows","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"3. Business Workflows","x":"Trigger: A new user submits registration with first name, last name, email, and password. Steps: 1. Validate registration request (email format, password requirements) 2. Verify…"},"1765":{"u":"/docs/guides/store-Specification.html#4-order-status-state-machine","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"4. Order Status State Machine","x":"Cancellable States: PendingPayment, PaymentInitiated, PaymentFailed. Shipped is deliberately NOT cancellable, for the same reason Paid is not: the goods and the money have both…"},"1766":{"u":"/docs/guides/store-Specification.html#5-business-rules","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"5. Business Rules","x":"---"},"1767":{"u":"/docs/guides/store-Specification.html#6-use-cases","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"6. Use Cases","x":"---"},"1768":{"u":"/docs/guides/store-Specification.html#7-domain-events-and-state-changes","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"7. Domain Events and State Changes","x":"Most mutations raise a single {Entity}Changed lifecycle event carrying a DomainEntityState discriminator (Added, Updated, Deleted) rather than one event per verb (ADR-083). Those…"},"1769":{"u":"/docs/guides/store-Specification.html#8-external-integrations","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"8. External Integrations","x":"Purpose: Processes online customer payments for orders. Business Impact: Enables the system to collect payments from customers and confirm payment success or failure…"},"1770":{"u":"/docs/guides/store-Specification.html#9-authorization-model","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"9. Authorization Model","x":"Permission-based endpoints: an endpoint states the capability it needs with [HasPermission(...)] over a constant from the owning module's {Module}Permissions, and each module…"},"1771":{"u":"/docs/guides/store-Specification.html#10-cross-module-communication","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"10. Cross-Module Communication","x":"The system enforces strict module boundaries. Modules communicate only through shared interface contracts: Across process boundaries these interfaces are satisfied by gRPC…"},"1772":{"u":"/docs/guides/store-Specification.html#11-user-interface","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"11. User Interface","x":"The UI is a Blazor Server + WebAssembly hybrid (InteractiveAuto render mode) using MudBlazor component library. It supports multiple hosting targets: - Web (Server + WASM):…"},"1773":{"u":"/docs/guides/store-Specification.html#12-cross-cutting-infrastructure","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"12. Cross-Cutting Infrastructure","x":"The IdempotencyFilter (applied via [Idempotent] attribute on Create endpoints) caches the first response for a given Idempotency-Key header value for 24 hours. Duplicate requests…"},"1774":{"u":"/docs/guides/store-Specification.html#13-testing","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"13. Testing","x":"- Full customer journey: Register - Browse - Add to Cart - Checkout - Admin Pay - Deliver - Order lifecycle: all state transitions including cancellation with inventory…"},"1775":{"u":"/docs/guides/store-Specification.html#14-missing-or-unclear-business-logic","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"14. Missing or Unclear Business Logic","x":"Observation: Four handlers send email today: the password-reset request (Section 3.12), OrderPaidHandler (payment receipt, which prints a struck \"Was\" amount and the promotion…"},"1776":{"u":"/docs/guides/store-Specification.html#15-seed-data-initial-system-state","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"15. Seed Data (Initial System State)","x":"The system seeds the following data at startup: Users: - Admin: one seeded administrator account (Admin role, no Customer record; credentials are environment-specific and not…"},"1777":{"u":"/articles/index.html","d":"The MMCA.Common article series","k":"The MMCA.Common series","x":"A long-form series that turns the MMCA.Common framework's architecture decisions into teachable patterns: the Result railway, the transactional outbox, database-per-service, JWKS…"},"1778":{"u":"/articles/index.html#the-series-53-articles","d":"The MMCA.Common article series","k":"The MMCA.Common series","t":"The series (53 articles)","x":"★ = cornerstone pillar (publish first; everything links up to these). The numbering is the reading order. Each article builds on the ones before it, so reading 1 through 53 in…"},"1779":{"u":"/articles/open-sourced-enterprise-dotnet-34-categories.html","d":"I open-sourced the enterprise .NET plumbing I never want to rewrite, and graded it against 34 categories","k":"The MMCA.Common series","x":"Subtitle: A .NET 10 framework for DDD, Clean Architecture, and CQRS, built as a modular monolith that extracts to microservices without a rewrite, and scored in the open against…"},"1780":{"u":"/articles/open-sourced-enterprise-dotnet-34-categories.html#what-it-is","d":"I open-sourced the enterprise .NET plumbing I never want to rewrite, and graded it against 34 categories","k":"The MMCA.Common series","t":"What it is","x":"MMCA.Common is a set of nineteen NuGet packages that give you a DDD + Clean Architecture + CQRS foundation as a modular monolith, plus the extraction boundaries to pull a module…"},"1781":{"u":"/articles/open-sourced-enterprise-dotnet-34-categories.html#the-one-thing-that-makes-it-different","d":"I open-sourced the enterprise .NET plumbing I never want to rewrite, and graded it against 34 categories","k":"The MMCA.Common series","t":"The one thing that makes it different","x":"Plenty of repositories will sell you Clean Architecture. The difference here is that the rules are not prose in a README. They are executable, and they fail the build. The…"},"1782":{"u":"/articles/open-sourced-enterprise-dotnet-34-categories.html#modular-monolith-now-microservices-later-no-rewrite","d":"I open-sourced the enterprise .NET plumbing I never want to rewrite, and graded it against 34 categories","k":"The MMCA.Common series","t":"Modular monolith now, microservices later, no rewrite","x":"The other thesis baked into the framework is that \"monolith vs microservices\" is a false binary. You build the extraction point now and cut the service when (if) you actually…"},"1783":{"u":"/articles/open-sourced-enterprise-dotnet-34-categories.html#i-scored-it-on-two-axes-then-published-every-gap","d":"I open-sourced the enterprise .NET plumbing I never want to rewrite, and graded it against 34 categories","k":"The MMCA.Common series","t":"I scored it on two axes, then published every gap","x":"Here is the part that usually gets left out of \"look at my framework\" posts. I graded MMCA.Common against a 34-category architecture rubric (backend, front-end, and…"},"1784":{"u":"/articles/open-sourced-enterprise-dotnet-34-categories.html#it-runs-in-production-on-two-real-apps","d":"I open-sourced the enterprise .NET plumbing I never want to rewrite, and graded it against 34 categories","k":"The MMCA.Common series","t":"It runs in production, on two real apps","x":"MMCA.Common is not a toy. Two deployed apps consume the same nineteen packages: - MMCA.ADC, the Atlanta Developers Conference app, deployed to Azure and used to run a live event.…"},"1785":{"u":"/articles/open-sourced-enterprise-dotnet-34-categories.html#what-this-series-will-cover","d":"I open-sourced the enterprise .NET plumbing I never want to rewrite, and graded it against 34 categories","k":"The MMCA.Common series","t":"What this series will cover","x":"Over the coming weeks I will take one pattern per article and go deep: - The Result railway that retired exceptions-as-control-flow. - The transactional outbox, in full. -…"},"1786":{"u":"/articles/open-sourced-enterprise-dotnet-34-categories.html#try-it-or-come-argue-with-me","d":"I open-sourced the enterprise .NET plumbing I never want to rewrite, and graded it against 34 categories","k":"The MMCA.Common series","t":"Try it, or come argue with me","x":"MMCA.Common is Apache-2.0 licensed and open source. The fastest way to form an opinion is to read the scorecard (the gaps are the honest part) and skim the ADRs (the \"why\" behind…"},"1787":{"u":"/articles/modular-monolith-to-microservices.html","d":"Modular monolith to microservices, without the rewrite","k":"The MMCA.Common series","x":"Subtitle: \"Monolith or microservices\" is the wrong question. Build the extraction point now, keep it tested, and cut the service later, on a boundary you have already proven. ---…"},"1788":{"u":"/articles/modular-monolith-to-microservices.html#why-it-matters","d":"Modular monolith to microservices, without the rewrite","k":"The MMCA.Common series","t":"Why it matters","x":"The reason the \"we will split it later\" plan usually fails is that the monolith quietly grows couplings that the eventual split has to unwind. Two modules share a database, so a…"},"1789":{"u":"/articles/modular-monolith-to-microservices.html#the-mmca-answer-four-extraction-points-all-enforced-in-the-monolith","d":"Modular monolith to microservices, without the rewrite","k":"The MMCA.Common series","t":"The MMCA answer: four extraction points, all enforced in the monolith","x":"There are four places a \"split it later\" plan leaks, and the framework closes each one with a real mechanism, not a guideline. The first leak is the shared database. In…"},"1790":{"u":"/articles/modular-monolith-to-microservices.html#what-the-boundary-looks-like-in-code","d":"Modular monolith to microservices, without the rewrite","k":"The MMCA.Common series","t":"What the boundary looks like in code","x":"The whole thesis fits in one DI swap. The same handler depends on the same interface; only the registration line differs between the two topologies. The transport choice is one…"},"1791":{"u":"/articles/modular-monolith-to-microservices.html#trade-offs-honestly","d":"Modular monolith to microservices, without the rewrite","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"Reversibility is not free, and the ADRs are candid about the bill. - Distributed-systems semantics arrive the moment you split. Cross-service consistency is eventual (outbox plus…"},"1792":{"u":"/articles/modular-monolith-to-microservices.html#apply-this-even-without-mmca","d":"Modular monolith to microservices, without the rewrite","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"You do not need this framework to keep the extraction point open. You need three habits. 1. Give each module its own logical data ownership now, even in one database. No…"},"1793":{"u":"/articles/34-category-architecture-rubric.html","d":"What good architecture actually means: a 34-category rubric you can score yourself against","k":"The MMCA.Common series","x":"Subtitle: \"Clean architecture\" is not a vibe. Here is a 34-category rubric that scores any system on two axes, demands evidence for every score, and exposes the one line that…"},"1794":{"u":"/articles/34-category-architecture-rubric.html#why-good-architecture-needs-a-rubric","d":"What good architecture actually means: a 34-category rubric you can score yourself against","k":"The MMCA.Common series","t":"Why \"good architecture\" needs a rubric","x":"The problem with architectural quality is that it is multi-dimensional and most of the dimensions are invisible until they bite you. SOLID discipline is invisible until a feature…"},"1795":{"u":"/articles/34-category-architecture-rubric.html#the-structure-three-parts-34-categories","d":"What good architecture actually means: a 34-category rubric you can score yourself against","k":"The MMCA.Common series","t":"The structure: three parts, 34 categories","x":"The rubric (Architecture Evaluation Criteria) is organized into three parts that match how a real system is actually built and operated. It stands at version 2 (ADR-110), which…"},"1796":{"u":"/articles/34-category-architecture-rubric.html#the-two-axes-maturity-and-implementation-evidence-or-it-does-not-count","d":"What good architecture actually means: a 34-category rubric you can score yourself against","k":"The MMCA.Common series","t":"The two axes: maturity and implementation, evidence or it does not count","x":"Here is the part that makes the rubric honest. Each category gets two scores, not one. - Maturity (0 to 4) measures process: how consistently and how well-governed the pattern…"},"1797":{"u":"/articles/34-category-architecture-rubric.html#how-the-index-is-computed","d":"What good architecture actually means: a 34-category rubric you can score yourself against","k":"The MMCA.Common series","t":"How the index is computed","x":"The scores roll up into two indices using per-category weights (defaults provided, adjustable per engagement). The maturity index is the sum of (category maturity × weight)…"},"1798":{"u":"/articles/34-category-architecture-rubric.html#the-worked-example-mmcacommon-scored-on-both-axes","d":"What good architecture actually means: a 34-category rubric you can score yourself against","k":"The MMCA.Common series","t":"The worked example: MMCA.Common, scored on both axes","x":"When I ran MMCA.Common through the rubric (the canonical, version-controlled scorecard lives in MMCA.Common: Architecture Scorecard), it landed at a maturity index of 97.0% and…"},"1799":{"u":"/articles/34-category-architecture-rubric.html#the-one-insight-worth-the-whole-exercise","d":"What good architecture actually means: a 34-category rubric you can score yourself against","k":"The MMCA.Common series","t":"The one insight worth the whole exercise","x":"When I lined the scores up, a single pattern explained almost all of the variance between the top tier and the middle tier, and it is the pattern that has driven every re-score…"},"1800":{"u":"/articles/34-category-architecture-rubric.html#trade-offs-honestly","d":"What good architecture actually means: a 34-category rubric you can score yourself against","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"A rubric is a tool, not an oracle, and it has sharp edges worth naming. - It can be gamed. If you score yourself, you can rationalize a 3 into a 4. The evidence requirement is…"},"1801":{"u":"/articles/34-category-architecture-rubric.html#apply-this-even-without-mmca","d":"What good architecture actually means: a 34-category rubric you can score yourself against","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"You do not need this framework, or even this exact rubric, to get the benefit: 1. Pick a fixed set of categories and score every system against the same set. Comparability is…"},"1802":{"u":"/articles/result-railway-csharp.html","d":"Stop throwing exceptions for control flow: the Result railway in C","k":"The MMCA.Common series","x":"Subtitle: \"This order ID does not exist\" is not an exceptional event, it is a routine branch of normal control flow. Here is the Result railway that models it as a value, and why…"},"1803":{"u":"/articles/result-railway-csharp.html#why-exceptions-are-not-a-control-flow-mechanism","d":"Stop throwing exceptions for control flow: the Result railway in C","k":"The MMCA.Common series","t":"Why exceptions are not a control-flow mechanism","x":"Exceptions are excellent at one job: aborting a computation that has gone genuinely wrong and unwinding the stack to someone who can recover. They are a poor fit for expected…"},"1804":{"u":"/articles/result-railway-csharp.html#the-mmca-answer-three-small-types-and-a-railway","d":"Stop throwing exceptions for control flow: the Result railway in C","k":"The MMCA.Common series","t":"The MMCA answer: three small types and a railway","x":"The pattern is factored into three dependency-free pieces, all living in MMCA.Common.Shared, the innermost layer (the Blazor WebAssembly UI can reference it without dragging in…"},"1805":{"u":"/articles/result-railway-csharp.html#trade-offs-honestly","d":"Stop throwing exceptions for control flow: the Result railway in C","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The Result pattern is not free, and ADR-013 names the rough edges rather than hiding them: - More ceremony at the call site than letting an exception bubble. Combine absorbs most…"},"1806":{"u":"/articles/result-railway-csharp.html#apply-this-even-without-mmca","d":"Stop throwing exceptions for control flow: the Result railway in C","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"You do not need this framework to adopt the railway. The ideas port to any stack: 1. Make failure visible in the signature. Return a result type (Result , OneOf, ErrorOr, your…"},"1807":{"u":"/articles/kill-anemic-domain-model.html","d":"Kill the anemic domain model: rich aggregates with factory methods that return Result","k":"The MMCA.Common series","x":"Subtitle: Public setters plus logic-in-services is not a domain model, it is a database row with extra steps. Here is the three-rung entity hierarchy in MMCA.Common that makes…"},"1808":{"u":"/articles/kill-anemic-domain-model.html#why-it-matters","d":"Kill the anemic domain model: rich aggregates with factory methods that return Result","k":"The MMCA.Common series","t":"Why it matters","x":"The cost is not theoretical. When the rules live outside the object: - Invalid state is representable. You can construct an Order with a negative total and no lines, because the…"},"1809":{"u":"/articles/kill-anemic-domain-model.html#the-mmca-answer-a-three-rung-hierarchy-one-capability-per-rung","d":"Kill the anemic domain model: rich aggregates with factory methods that return Result","k":"The MMCA.Common series","t":"The MMCA answer: a three-rung hierarchy, one capability per rung","x":"MMCA.Common builds every entity on a three-class inheritance chain. Read it bottom-up; each rung adds exactly one capability. BaseEntity is almost nothing: a single required init…"},"1810":{"u":"/articles/kill-anemic-domain-model.html#trade-offs-honestly","d":"Kill the anemic domain model: rich aggregates with factory methods that return Result","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"Rich aggregates are the right default, but they are not free, and the scorecard's §4 review names the gaps: - More boilerplate per entity. A private constructor, a static…"},"1811":{"u":"/articles/kill-anemic-domain-model.html#apply-this-even-without-mmca","d":"Kill the anemic domain model: rich aggregates with factory methods that return Result","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"You do not need this framework to drop the anemic model. The moves are portable: 1. Make constructors private and add a static factory that validates and returns a result type.…"},"1812":{"u":"/articles/specification-pattern.html","d":"Specifications over LINQ spaghetti: composable, reusable query intent","k":"The MMCA.Common series","x":"Subtitle: A .Where(s = s.SpeakerId == id && !s.IsDeleted && s.IsPublished) copied into nine controllers is nine places to forget the authorization clause. Here is the…"},"1813":{"u":"/articles/specification-pattern.html#why-it-matters","d":"Specifications over LINQ spaghetti: composable, reusable query intent","k":"The MMCA.Common series","t":"Why it matters","x":"Query predicates are not throwaway plumbing. Some of them encode business rules (\"a published event is one whose status is Published and whose start date has not passed\") and…"},"1814":{"u":"/articles/specification-pattern.html#the-mmca-answer-trusted-specifications-untrusted-filters-one-pipeline","d":"Specifications over LINQ spaghetti: composable, reusable query intent","k":"The MMCA.Common series","t":"The MMCA answer: trusted specifications, untrusted filters, one pipeline","x":"MMCA.Common splits the read side into three cooperating sub-systems, and the split is the point. ISpecification exposes two faces of the same rule: - a Criteria expression tree…"},"1815":{"u":"/articles/specification-pattern.html#trade-offs-honestly","d":"Specifications over LINQ spaghetti: composable, reusable query intent","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The pattern earns its keep, but a few real, specific gaps are worth stating plainly rather than glossing: - Two query vocabularies coexist. Specifications (typed,…"},"1816":{"u":"/articles/specification-pattern.html#apply-this-even-without-mmca","d":"Specifications over LINQ spaghetti: composable, reusable query intent","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The core ideas port to any stack and any ORM: 1. Give predicates a type and a name. A PublishedEvent specification object beats a copied .Where() lambda. Named intent is…"},"1817":{"u":"/articles/cqrs-decorator-pipeline.html","d":"The CQRS decorator pipeline: logging, caching, and transactions without touching a handler","k":"The MMCA.Common series","x":"Subtitle: Cross-cutting concerns belong around a handler, not inside it. Here is a Scrutor-composed decorator pipeline where logging, caching, and transactions wrap every command…"},"1818":{"u":"/articles/cqrs-decorator-pipeline.html#why-it-matters","d":"The CQRS decorator pipeline: logging, caching, and transactions without touching a handler","k":"The MMCA.Common series","t":"Why it matters","x":"The scattered approach fails in two specific ways. First, drift. When the transaction boilerplate lives in every handler, one handler eventually forgets it, or wraps the wrong…"},"1819":{"u":"/articles/cqrs-decorator-pipeline.html#the-shape-thin-handlers-fat-pipeline","d":"The CQRS decorator pipeline: logging, caching, and transactions without touching a handler","k":"The MMCA.Common series","t":"The shape: thin handlers, fat pipeline","x":"Every write and every read in MMCA.Common is a use case behind one of two interfaces: One method each. TResult is almost always a Result or Result (the railway error type from…"},"1820":{"u":"/articles/cqrs-decorator-pipeline.html#how-the-pipeline-is-assembled-and-why-order-is-the-whole-game","d":"The CQRS decorator pipeline: logging, caching, and transactions without touching a handler","k":"The MMCA.Common series","t":"How the pipeline is assembled, and why order is the whole game","x":"The wiring uses Scrutor's TryDecorate, and there is one rule you have to internalize: TryDecorate applies decorators in reverse registration order. The last one registered…"},"1821":{"u":"/articles/cqrs-decorator-pipeline.html#transactional-behavior-in-detail","d":"The CQRS decorator pipeline: logging, caching, and transactions without touching a handler","k":"The MMCA.Common series","t":"Transactional behavior, in detail","x":"The transactional decorator wraps the handler in IUnitOfWork.ExecuteInTransactionAsync only when the command implements ITransactional. Three outcomes, three behaviors: - An…"},"1822":{"u":"/articles/cqrs-decorator-pipeline.html#opt-in-by-marker-interface-pay-only-for-what-you-use","d":"The CQRS decorator pipeline: logging, caching, and transactions without touching a handler","k":"The MMCA.Common series","t":"Opt in by marker interface: pay only for what you use","x":"The pipeline is registered for every handler, but most decorators are dormant unless the use case asks for them, through a set of tiny marker interfaces: Each decorator does an…"},"1823":{"u":"/articles/cqrs-decorator-pipeline.html#the-di-sequence-that-makes-it-work","d":"The CQRS decorator pipeline: logging, caching, and transactions without touching a handler","k":"The MMCA.Common series","t":"The DI sequence that makes it work","x":"Because TryDecorate can only wrap registrations that already exist, the decorators must be registered after every module's concrete handlers. One step of this sequence is…"},"1824":{"u":"/articles/cqrs-decorator-pipeline.html#the-same-argument-one-layer-out-the-http-pipeline","d":"The CQRS decorator pipeline: logging, caching, and transactions without touching a handler","k":"The MMCA.Common series","t":"The same argument, one layer out: the HTTP pipeline","x":"The decorator chain covers everything that happens once a command or query reaches a handler. But the request had to cross an edge to get there, and that edge is an ordered chain…"},"1825":{"u":"/articles/cqrs-decorator-pipeline.html#trade-offs-honestly","d":"The CQRS decorator pipeline: logging, caching, and transactions without touching a handler","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The pipeline is not free, and ADR-014 names the rough edges: - Registration order is the reverse of execution order. This is a genuine Scrutor foot-gun. The framework mitigates…"},"1826":{"u":"/articles/cqrs-decorator-pipeline.html#apply-this-even-without-mmca","d":"The CQRS decorator pipeline: logging, caching, and transactions without touching a handler","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The idea ports to any DI container that supports decoration (Scrutor for Microsoft.Extensions.DI, or the equivalent in your stack): 1. Define a single handler interface per…"},"1827":{"u":"/articles/validation-kit.html","d":"Compose validators, don't copy them: a reusable FluentValidation kit","k":"The MMCA.Common series","x":"Subtitle: \"Email must be valid, password must be strong, name is required.\" Every command re-declares those rules, and then they drift. Here is a validation kit where the rules…"},"1828":{"u":"/articles/validation-kit.html#reusable-rule-fragments-composed-with-include","d":"Compose validators, don't copy them: a reusable FluentValidation kit","k":"The MMCA.Common series","t":"Reusable rule fragments, composed with Include","x":"The core idea is a rule fragment: a tiny AbstractValidator that owns exactly one field's contract, generic over the parent type T and configured with a selector expression.…"},"1829":{"u":"/articles/validation-kit.html#one-validator-for-the-request-free-for-the-command","d":"Compose validators, don't copy them: a reusable FluentValidation kit","k":"The MMCA.Common series","t":"One validator for the request, free for the command","x":"Most commands in this codebase are thin wrappers that carry a request DTO, for example CreateSessionCommand(CreateSessionRequest Request), and implement ICommandWithRequest (its…"},"1830":{"u":"/articles/validation-kit.html#a-validation-failure-is-a-result-and-the-edge-maps-it-to-400","d":"Compose validators, don't copy them: a reusable FluentValidation kit","k":"The MMCA.Common series","t":"A validation failure is a Result, and the edge maps it to 400","x":"A validator that runs is useless unless its output flows into the rest of the system cleanly. FluentValidation speaks in ValidationResult and ValidationFailure. The rest of the…"},"1831":{"u":"/articles/validation-kit.html#the-two-altitude-rule-validator-versus-domain-factory","d":"Compose validators, don't copy them: a reusable FluentValidation kit","k":"The MMCA.Common series","t":"The two-altitude rule: validator versus domain factory","x":"A reasonable objection: if the validator checks the input, why does the domain factory also return a Result and re-check things? Because they check at two different altitudes,…"},"1832":{"u":"/articles/validation-kit.html#trade-offs-honestly","d":"Compose validators, don't copy them: a reusable FluentValidation kit","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- It is a FluentValidation dependency. The kit is a thin composition layer over FluentValidation 12, not a hand-rolled engine. That is a deliberate trade (a mature, well-known…"},"1833":{"u":"/articles/validation-kit.html#apply-this-even-without-mmca","d":"Compose validators, don't copy them: a reusable FluentValidation kit","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any stack with a validation library that supports composition: 1. Write each field contract once as a fragment: a small validator generic over the parent…"},"1834":{"u":"/articles/transactional-outbox-dotnet.html","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","x":"Subtitle: \"Save to the database, then publish to the broker\" is a dual write with no atomicity. Here is the at-least-once pattern that fixes it, in one SaveChanges call. ---…"},"1835":{"u":"/articles/transactional-outbox-dotnet.html#why-it-matters","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"Why it matters","x":"The failure is rare per request and catastrophic in aggregate. It does not show up in tests, because tests do not kill the broker between two awaits. It shows up in production as…"},"1836":{"u":"/articles/transactional-outbox-dotnet.html#the-pattern-one-transaction-then-a-separate-delivery","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"The pattern: one transaction, then a separate delivery","x":"The transactional outbox splits the problem into two phases that are each individually safe: 1. Record intent atomically. When you save the order, you also write the event to an…"},"1837":{"u":"/articles/transactional-outbox-dotnet.html#how-mmcacommon-does-it-you-just-record-intent","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"How MMCA.Common does it: you just record intent","x":"The reason this pattern is often skipped is that wiring it by hand is tedious. In MMCA.Common it is automatic. Your aggregate records a domain event, and the framework does the…"},"1838":{"u":"/articles/transactional-outbox-dotnet.html#the-third-participant-in-that-sequence-the-change-trail","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"The third participant in that sequence: the change trail","x":"The first step of that sequence, \"stamp audit fields\", answers who touched a row last. It cannot answer what changed, because the stamp is a single overwrite: a row edited nine…"},"1839":{"u":"/articles/transactional-outbox-dotnet.html#when-the-commit-itself-is-ambiguous","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"When the commit itself is ambiguous","x":"Everything above rests on one commit, which makes it fair to ask what happens when the commit is the thing that fails. A commit can fail after the database has applied it but…"},"1840":{"u":"/articles/transactional-outbox-dotnet.html#the-delivery-side-outboxprocessor","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"The delivery side: OutboxProcessor","x":"A background service, OutboxProcessor, drains the outbox. The details that make it production-grade: - It only touches its own outbox. Under database-per-service, every…"},"1841":{"u":"/articles/transactional-outbox-dotnet.html#the-same-code-dispatches-in-process-and-over-a-broker","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"The same code dispatches in-process and over a broker","x":"Here is where the outbox earns its place in a framework whose whole thesis is \"monolith now, microservices later.\" The event you raised does not know how it will be delivered.…"},"1842":{"u":"/articles/transactional-outbox-dotnet.html#when-the-consumer-runs-out-of-retries","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"When the consumer runs out of retries","x":"The outbox makes the publish durable. It says nothing about what happens after the broker accepts the message, and that is the other half of delivery. MassTransit applies its own…"},"1843":{"u":"/articles/transactional-outbox-dotnet.html#the-write-that-skips-the-whole-pipeline","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"The write that skips the whole pipeline","x":"Everything above depends on one assumption: that writes go through SaveChangesAsync. There is a write that does not, and it is worth knowing before you reach for it, because the…"},"1844":{"u":"/articles/transactional-outbox-dotnet.html#trade-offs-honestly","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The outbox is not free, and the rubric review of this framework names the rough edges: - At-least-once means duplicates are possible. A consumer can receive the same event twice…"},"1845":{"u":"/articles/transactional-outbox-dotnet.html#apply-this-even-without-mmca","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The idea ports to any stack: 1. Write the event row in the same transaction as the state change. If your ORM cannot do that, you do not have an outbox, you have a hopeful second…"},"1846":{"u":"/articles/database-per-service.html","d":"Database-per-service inside a monolith (and why)","k":"The MMCA.Common series","x":"Subtitle: One DbContext class. Many databases. A host with no extra configuration behaves exactly like a single-database monolith, and the same code runs against per-service…"},"1847":{"u":"/articles/database-per-service.html#why-it-matters","d":"Database-per-service inside a monolith (and why)","k":"The MMCA.Common series","t":"Why it matters","x":"Two failures hide in the shared-database monolith, and both surface at the worst possible time. The first is the coupling itself. Once a foreign key spans two modules, those…"},"1848":{"u":"/articles/database-per-service.html#the-mmca-answer-one-context-class-one-instance-per-database","d":"Database-per-service inside a monolith (and why)","k":"The MMCA.Common series","t":"The MMCA answer: one context class, one instance per database","x":"The single most counter-intuitive fact, and the one every other choice flows from: there is exactly one concrete context class per engine (SQLServerDbContext,…"},"1849":{"u":"/articles/database-per-service.html#what-it-looks-like-in-configuration","d":"Database-per-service inside a monolith (and why)","k":"The MMCA.Common series","t":"What it looks like in configuration","x":"You do not write routing code. You write configuration, and the same compiled binary becomes a monolith or a set of isolated services depending on what is in appsettings.json.…"},"1850":{"u":"/articles/database-per-service.html#the-third-axis-which-customer-owns-the-row","d":"Database-per-service inside a monolith (and why)","k":"The MMCA.Common series","t":"The third axis: which customer owns the row","x":"Source name is one axis of partitioning, and the engine is another (that is the next article). Neither of them is a tenant. ADR-073 adds the third axis, \"which customer does this…"},"1851":{"u":"/articles/database-per-service.html#trade-offs-honestly","d":"Database-per-service inside a monolith (and why)","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"ADR-006 lists the bill in plain terms, and it is a real bill. - No distributed transactions. When a save spans sources, DbContextFactory.ExecuteInTransactionAsync opens a…"},"1852":{"u":"/articles/database-per-service.html#apply-this-even-without-mmca","d":"Database-per-service inside a monolith (and why)","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The mechanism is EF-specific, but the discipline ports to any stack. 1. Decide an entity's home logically, not physically. Route by module or bounded context, with a default that…"},"1853":{"u":"/articles/polyglot-persistence.html","d":"One entity model, four databases: polyglot persistence behind a single attribute","k":"The MMCA.Common series","x":"Subtitle: A domain entity should not know which database engine stores it. Here is a design where the same entity moves between SQL Server, PostgreSQL, Cosmos DB, and SQLite by…"},"1854":{"u":"/articles/polyglot-persistence.html#two-axes-not-one","d":"One entity model, four databases: polyglot persistence behind a single attribute","k":"The MMCA.Common series","t":"Two axes, not one","x":"This is the companion to the database-per-service article (Article 10), and the two are deliberately orthogonal. It helps to name the axes. Database-per-service (ADR-006) is the…"},"1855":{"u":"/articles/polyglot-persistence.html#the-configuration-hierarchy-where-the-engine-lives","d":"One entity model, four databases: polyglot persistence behind a single attribute","k":"The MMCA.Common series","t":"The configuration hierarchy: where the engine lives","x":"Every entity in the framework gets an EntityConfiguration class. The relevant inheritance chain is short and worth holding in your head. At the bottom is…"},"1856":{"u":"/articles/polyglot-persistence.html#the-entity-move-is-one-token","d":"One entity model, four databases: polyglot persistence behind a single attribute","k":"The MMCA.Common series","t":"The entity move is one token","x":"So what does it take to move Session from SQL Server to Cosmos? You change its configuration's base class from EntityTypeConfigurationSQLServer to EntityTypeConfigurationCosmos .…"},"1857":{"u":"/articles/polyglot-persistence.html#when-a-relationship-crosses-an-engine-boundary","d":"One entity model, four databases: polyglot persistence behind a single attribute","k":"The MMCA.Common series","t":"When a relationship crosses an engine boundary","x":"A document store cannot enforce a foreign key into a SQL table. So the moment Session moves to Cosmos while the Event it references stays in SQL Server, the relationship between…"},"1858":{"u":"/articles/polyglot-persistence.html#keeping-a-cross-source-predicate-translatable","d":"One entity model, four databases: polyglot persistence behind a single attribute","k":"The MMCA.Common series","t":"Keeping a cross-source predicate translatable","x":"Degrading the relationship solves the schema. It does not solve the query. Suppose you want all sessions whose event is published. Written the obvious way, that is a navigation:…"},"1859":{"u":"/articles/polyglot-persistence.html#the-honest-adoption-note","d":"One entity model, four databases: polyglot persistence behind a single attribute","k":"The MMCA.Common series","t":"The honest adoption note","x":"Here is the part most articles would quietly omit. All of the above is shipped and tested. None of it is running in production yet. Today, every current production entity…"},"1860":{"u":"/articles/polyglot-persistence.html#trade-offs-honestly","d":"One entity model, four databases: polyglot persistence behind a single attribute","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Cosmos has no JOINs, and the framework does not pretend otherwise. The whole resolve-then-filter-by-FK machinery exists because you cannot join across containers or across…"},"1861":{"u":"/articles/polyglot-persistence.html#apply-this-even-without-mmca","d":"One entity model, four databases: polyglot persistence behind a single attribute","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any stack with EF Core or a comparable configuration-driven ORM: 1. Keep the entity engine-free. The domain class is a plain object. The persistence engine…"},"1862":{"u":"/articles/navigation-populators.html","d":"EF Core Include chains are a trap: navigation populators decouple eager loading","k":"The MMCA.Common series","x":"Subtitle: Include(x).Include(y).ThenInclude(z) couples your read model to one physical database. Here is the eager-loading boundary that batch-loads related data and survives…"},"1863":{"u":"/articles/navigation-populators.html#why-it-matters","d":"EF Core Include chains are a trap: navigation populators decouple eager loading","k":"The MMCA.Common series","t":"Why it matters","x":"The cartesian explosion is the famous failure, and EF Core has a fix for it (AsSplitQuery), but split query is a band-aid on the deeper problem: Include is a JOIN, and a JOIN…"},"1864":{"u":"/articles/navigation-populators.html#the-mmca-answer-classify-then-batch-load-through-a-populator","d":"EF Core Include chains are a trap: navigation populators decouple eager loading","k":"The MMCA.Common series","t":"The MMCA answer: classify, then batch-load through a populator","x":"MMCA.Common splits the question into three responsibilities that live in three different layers, and no EF knowledge leaks upward. The Domain layer declares only what…"},"1865":{"u":"/articles/navigation-populators.html#the-boundary-that-survives-extraction","d":"EF Core Include chains are a trap: navigation populators decouple eager loading","k":"The MMCA.Common series","t":"The boundary that survives extraction","x":"Here is why this is not over-engineering. When you split a module out and a relationship's two ends move to different physical sources, the EF model's…"},"1866":{"u":"/articles/navigation-populators.html#trade-offs-honestly","d":"EF Core Include chains are a trap: navigation populators decouple eager loading","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"ADR-002 names the rough edges rather than hiding them. - Extra abstraction for the single-database case. For a pure single-SQL-Server host where Include always works, the…"},"1867":{"u":"/articles/navigation-populators.html#apply-this-even-without-mmca","d":"EF Core Include chains are a trap: navigation populators decouple eager loading","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any stack that might outgrow one database: 1. Stop assuming a relationship is a JOIN. Treat \"can these two be loaded together in one query?\" as a runtime…"},"1868":{"u":"/articles/optimistic-concurrency-rowversion.html","d":"Optimistic concurrency you cannot opt out of: RowVersion from the database to a required If-Match","k":"The MMCA.Common series","x":"Subtitle: Two admins open the same session, both edit it, both hit Save. Without a precondition the second write silently wins and the first admin's change is gone, with no…"},"1869":{"u":"/articles/optimistic-concurrency-rowversion.html#why-it-matters-and-why-it-is-not-idempotency","d":"Optimistic concurrency you cannot opt out of: RowVersion from the database to a required If-Match","k":"The MMCA.Common series","t":"Why it matters, and why it is not idempotency","x":"The lost update is a data-integrity bug that never announces itself. It does not throw. It does not log. It leaves a perfectly valid-looking row. You find out when the organizer…"},"1870":{"u":"/articles/optimistic-concurrency-rowversion.html#the-mmca-answer-a-database-token-handed-out-as-an-etag-demanded-back-as-a-precondition","d":"Optimistic concurrency you cannot opt out of: RowVersion from the database to a required If-Match","k":"The MMCA.Common series","t":"The MMCA answer: a database token, handed out as an ETag, demanded back as a precondition","x":"The mechanism is a concurrency token that the database manages, the read model exposes, the client states back in the If-Match header, and the persistence layer plants as EF's…"},"1871":{"u":"/articles/optimistic-concurrency-rowversion.html#the-detail-that-actually-makes-it-a-conflict","d":"Optimistic concurrency you cannot opt out of: RowVersion from the database to a required If-Match","k":"The MMCA.Common series","t":"The detail that actually makes it a conflict","x":"The interesting part is not \"store a version number.\" It is where the comparison happens. A hand-rolled \"read the current token, compare it to the client's, then save if they…"},"1872":{"u":"/articles/optimistic-concurrency-rowversion.html#from-exception-to-a-status-at-the-edge","d":"Optimistic concurrency you cannot opt out of: RowVersion from the database to a required If-Match","k":"The MMCA.Common series","t":"From exception to a status at the edge","x":"DbUpdateConcurrencyException is a DbUpdateException, and the framework already has one handler for that whole family. DbUpdateExceptionHandler translates any DbUpdateException…"},"1873":{"u":"/articles/optimistic-concurrency-rowversion.html#the-transport-a-weak-etag-out-a-required-if-match-back","d":"Optimistic concurrency you cannot opt out of: RowVersion from the database to a required If-Match","k":"The MMCA.Common series","t":"The transport: a weak ETag out, a required If-Match back","x":"The same RowVersion, the same SetOriginalRowVersion extension point, the same comparison inside the UPDATE. The header is where the token lives on the wire, which means anything…"},"1874":{"u":"/articles/optimistic-concurrency-rowversion.html#the-invariant-one-source-for-the-precondition","d":"Optimistic concurrency you cannot opt out of: RowVersion from the database to a required If-Match","k":"The MMCA.Common series","t":"The invariant: one source for the precondition","x":"A convention that lives only in a code-review checklist rots. So the one-transport rule is enforced mechanically. ArchitectureRules.UpdateRequestsAreNotConcurrencyAware scans…"},"1875":{"u":"/articles/optimistic-concurrency-rowversion.html#trade-offs-honestly","d":"Optimistic concurrency you cannot opt out of: RowVersion from the database to a required If-Match","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- The precondition is mandatory, and that is a real constraint. A caller that has not read the resource cannot write it. There is no null token, no wildcard escape, no…"},"1876":{"u":"/articles/optimistic-concurrency-rowversion.html#apply-this-even-without-mmca","d":"Optimistic concurrency you cannot opt out of: RowVersion from the database to a required If-Match","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any load-modify-save stack: 1. Put a database-managed concurrency token on your rows (SQL Server rowversion, a Postgres xmin, or an application-managed…"},"1877":{"u":"/articles/self-ordering-modules.html","d":"Self-ordering modules: discovered, Kahn-ordered, and extractable","k":"The MMCA.Common series","x":"Subtitle: A module declares its name and its dependencies. The framework discovers every module, sorts them with Kahn's algorithm, and registers them in an order where a…"},"1878":{"u":"/articles/self-ordering-modules.html#why-ordering-is-a-correctness-concern-not-a-style-one","d":"Self-ordering modules: discovered, Kahn-ordered, and extractable","k":"The MMCA.Common series","t":"Why ordering is a correctness concern, not a style one","x":"In MMCA.Common the registration order is not cosmetic. The CQRS pipeline wraps command and query handlers with decorators (logging, caching, validation, transactions) using…"},"1879":{"u":"/articles/self-ordering-modules.html#a-module-declares-intent-not-position","d":"Self-ordering modules: discovered, Kahn-ordered, and extractable","k":"The MMCA.Common series","t":"A module declares intent, not position","x":"A module in MMCA.Common is an IModule. The contract is deliberately small: Three of the five members ship with default implementations, so a minimal module implements only Name…"},"1880":{"u":"/articles/self-ordering-modules.html#the-engine-moduleloader-and-kahns-algorithm","d":"Self-ordering modules: discovered, Kahn-ordered, and extractable","k":"The MMCA.Common series","t":"The engine: ModuleLoader and Kahn's algorithm","x":"ModuleLoader is the piece that turns those declarations into an order. Its DiscoverAndRegister method scans the assemblies the host names in a required moduleAssemblies…"},"1881":{"u":"/articles/self-ordering-modules.html#disabling-a-module-without-breaking-its-consumers","d":"Self-ordering modules: discovered, Kahn-ordered, and extractable","k":"The MMCA.Common series","t":"Disabling a module without breaking its consumers","x":"Here is the part that turns this from a tidy registration trick into an extraction mechanism. A host can disable a module through configuration. ModulesSettings binds the…"},"1882":{"u":"/articles/self-ordering-modules.html#convention-scanning-what-a-modules-register-actually-wires","d":"Self-ordering modules: discovered, Kahn-ordered, and extractable","k":"The MMCA.Common series","t":"Convention scanning: what a module's Register actually wires","x":"Modules do not hand-register every handler. ScanModuleApplicationServices () is the Scrutor-based convention scan that a module's Add{X}Module calls to register, by assembly,…"},"1883":{"u":"/articles/self-ordering-modules.html#each-module-is-the-unit-of-extraction","d":"Self-ordering modules: discovered, Kahn-ordered, and extractable","k":"The MMCA.Common series","t":"Each module is the unit of extraction","x":"The reason all of this is worth the machinery is that the module is also the deploy and scale boundary, and the same self-ordering ModuleLoader registration runs at both ends of…"},"1884":{"u":"/articles/self-ordering-modules.html#trade-offs-honestly","d":"Self-ordering modules: discovered, Kahn-ordered, and extractable","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Reflection at startup. Discovery calls GetTypes() on every assembly the host named. This is a startup cost, paid once, and the per-GetTypes() guard means a load-time exception…"},"1885":{"u":"/articles/self-ordering-modules.html#apply-this-even-without-mmca","d":"Self-ordering modules: discovered, Kahn-ordered, and extractable","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports cleanly to any DI container: 1. Give each module a name and a declared dependency list, not a hard-coded registration position. 2. Topologically sort before you…"},"1886":{"u":"/articles/event-schema-versioning.html","d":"Event-schema versioning: never silently reshape an event","k":"The MMCA.Common series","x":"Subtitle: An integration event is a wire contract the moment it crosses a service boundary. Reshape it in place and you break consumers that are already deployed. Here is the…"},"1887":{"u":"/articles/event-schema-versioning.html#why-it-matters","d":"Event-schema versioning: never silently reshape an event","k":"The MMCA.Common series","t":"Why it matters","x":"In a monolith, an event handler and the code that raises the event compile together. Reshape the event and the consumer fails to compile; you fix it in the same commit. The type…"},"1888":{"u":"/articles/event-schema-versioning.html#the-mmca-answer-a-version-on-every-event-and-a-rule-for-changing-it","d":"Event-schema versioning: never silently reshape an event","k":"The MMCA.Common series","t":"The MMCA answer: a version on every event, and a rule for changing it","x":"The decision has two halves: a signal that is enforced, and a discipline for breaking changes. Half one: every integration event carries an explicit SchemaVersion. The base…"},"1889":{"u":"/articles/event-schema-versioning.html#how-it-complements-the-outbox","d":"Event-schema versioning: never silently reshape an event","k":"The MMCA.Common series","t":"How it complements the outbox","x":"This sits directly on top of the transactional outbox (ADR-003). The outbox guarantees an event is delivered at least once: persisted atomically with the state change, then…"},"1890":{"u":"/articles/event-schema-versioning.html#trade-offs-honestly","d":"Event-schema versioning: never silently reshape an event","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"ADR-010 is candid that the field is a signal, not a complete mechanism. - SchemaVersion by itself does not stop a consumer breaking on a real reshape. It is a value on the wire,…"},"1891":{"u":"/articles/event-schema-versioning.html#apply-this-even-without-mmca","d":"Event-schema versioning: never silently reshape an event","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The policy ports to any event-driven system with independently deployed consumers: 1. Put an explicit version on every event, and make it un-skippable. A test that fails the…"},"1892":{"u":"/articles/jwks-cross-service-auth.html","d":"Cross-service auth without a shared secret: JWKS dual-fetch","k":"The MMCA.Common series","x":"Subtitle: When you split a monolith into services, the easy answer is to copy the JWT signing secret into every validator. That is also the answer that turns one leaked key into…"},"1893":{"u":"/articles/jwks-cross-service-auth.html#why-it-matters","d":"Cross-service auth without a shared secret: JWKS dual-fetch","k":"The MMCA.Common series","t":"Why it matters","x":"Symmetric signing couples trust to secret distribution. The more services you have, the more copies of the forge-anything key exist, and the more places it can leak: a…"},"1894":{"u":"/articles/jwks-cross-service-auth.html#the-mmca-answer-publish-the-public-half-as-a-jwks-document","d":"Cross-service auth without a shared secret: JWKS dual-fetch","k":"The MMCA.Common series","t":"The MMCA answer: publish the public half as a JWKS document","x":"The framework's token service supports two signing algorithms, selected by the concrete JwtSettings.SigningAlgorithm, which defaults to RS256: - Monolith mode: HS256 (symmetric…"},"1895":{"u":"/articles/jwks-cross-service-auth.html#the-dual-fetch-and-the-graceful-empty-set","d":"Cross-service auth without a shared secret: JWKS dual-fetch","k":"The MMCA.Common series","t":"The dual-fetch, and the graceful empty set","x":"The dual-fetch ADR-004 describes is in the key discovery itself: a validator discovers the issuer's public key from the JWKS endpoint, and the framework falls back gracefully…"},"1896":{"u":"/articles/jwks-cross-service-auth.html#jwt-algorithm-pinning-the-attack-you-would-otherwise-ship","d":"Cross-service auth without a shared secret: JWKS dual-fetch","k":"The MMCA.Common series","t":"JWT algorithm pinning: the attack you would otherwise ship","x":"Switching to asymmetric keys opens a specific, classic attack, and the framework closes it explicitly. Once the RSA public key is published (which is the entire point of JWKS),…"},"1897":{"u":"/articles/jwks-cross-service-auth.html#trade-offs-honestly","d":"Cross-service auth without a shared secret: JWKS dual-fetch","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The JWKS layer is the right model, but it is not free, and the §11 review of the framework names the rough edges. - Two round trips on successful login. The Identity service's…"},"1898":{"u":"/articles/jwks-cross-service-auth.html#apply-this-even-without-mmca","d":"Cross-service auth without a shared secret: JWKS dual-fetch","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any stack with a JWT story: 1. In a single process, symmetric (HS256) is fine. Do not add asymmetric complexity you do not need. The signer and validator are…"},"1899":{"u":"/articles/password-hashing.html","d":"Password hashing done right: PBKDF2-SHA512, 600k iterations, timing-safe","k":"The MMCA.Common series","x":"Subtitle: Storing a password is a solved problem, and almost every breach headline is someone who solved it the wrong way. Here are the four decisions that separate a hash you…"},"1900":{"u":"/articles/password-hashing.html#why-it-matters","d":"Password hashing done right: PBKDF2-SHA512, 600k iterations, timing-safe","k":"The MMCA.Common series","t":"Why it matters","x":"A leaked password table is not just a breach of your app. People reuse passwords. The credentials you fail to protect become the keys an attacker tries against their bank, their…"},"1901":{"u":"/articles/password-hashing.html#the-mmca-answer-a-deliberately-slow-kdf-salted-compared-in-constant-time","d":"Password hashing done right: PBKDF2-SHA512, 600k iterations, timing-safe","k":"The MMCA.Common series","t":"The MMCA answer: a deliberately slow KDF, salted, compared in constant time","x":"MMCA.Common ships one production password hasher, split the Clean-Architecture way: the IPasswordHasher port sits in MMCA.Common.Application and its single PasswordHasher…"},"1902":{"u":"/articles/password-hashing.html#defense-in-depth-pii-at-rest-not-just-passwords","d":"Password hashing done right: PBKDF2-SHA512, 600k iterations, timing-safe","k":"The MMCA.Common series","t":"Defense in depth: PII at rest, not just passwords","x":"Passwords are the famous case, but they are not the only personal data sitting in your database. MMCA.Common ships field-level PII encryption as a separate control, now recorded…"},"1903":{"u":"/articles/password-hashing.html#trade-offs-honestly","d":"Password hashing done right: PBKDF2-SHA512, 600k iterations, timing-safe","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"A credible security post owns its rough edges, and a few sit around this hash rather than in it. - Plain-HTTP metadata discovery is an opt-out, not a default.…"},"1904":{"u":"/articles/password-hashing.html#apply-this-even-without-mmca","d":"Password hashing done right: PBKDF2-SHA512, 600k iterations, timing-safe","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The rules port to any stack, and they are short enough to memorize: 1. Never store plaintext, and never use a fast hash (MD5, SHA-1, plain SHA-256/512) for passwords. Use a…"},"1905":{"u":"/articles/idempotency-attribute.html","d":"Idempotency in one attribute: safe retries for HTTP APIs","k":"The MMCA.Common series","x":"Subtitle: Clients retry. Resilient HTTP handlers retry harder. A POST that creates a resource must survive being sent twice. Here is the whole thing in one [Idempotent]…"},"1906":{"u":"/articles/idempotency-attribute.html#why-it-matters","d":"Idempotency in one attribute: safe retries for HTTP APIs","k":"The MMCA.Common series","t":"Why it matters","x":"The non-idempotent POST is a quiet, expensive bug. It does not throw. It does not log an error. It produces a second perfectly valid-looking row. You find out when finance…"},"1907":{"u":"/articles/idempotency-attribute.html#the-mechanism-one-attribute-one-header","d":"Idempotency in one attribute: safe retries for HTTP APIs","k":"The MMCA.Common series","t":"The mechanism: one attribute, one header","x":"You mark an action [Idempotent]: The generic create action in the framework's controller bases carries [Idempotent] by default, so most write endpoints get it for free. The…"},"1908":{"u":"/articles/idempotency-attribute.html#the-concurrency-detail-that-actually-matters","d":"Idempotency in one attribute: safe retries for HTTP APIs","k":"The MMCA.Common series","t":"The concurrency detail that actually matters","x":"The interesting part is not \"cache the response.\" It is what happens when two duplicates arrive at the same time, before the first has finished. A naive cache check would let…"},"1909":{"u":"/articles/idempotency-attribute.html#the-same-problem-shows-up-on-the-broker-and-the-inbox-is-the-matching-mechanism","d":"Idempotency in one attribute: safe retries for HTTP APIs","k":"The MMCA.Common series","t":"The same problem shows up on the broker, and the inbox is the matching mechanism","x":"HTTP retries are one source of duplicates. The other is the message broker. MMCA.Common's outbox gives at-least-once delivery: an event may be published twice (the publish…"},"1910":{"u":"/articles/idempotency-attribute.html#trade-offs-honestly","d":"Idempotency in one attribute: safe retries for HTTP APIs","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Clients must send a stable key, from a stable identity. Idempotency is a contract, not magic. If a client generates a fresh key per retry, every retry is a \"new\" request and…"},"1911":{"u":"/articles/idempotency-attribute.html#apply-this-even-without-mmca","d":"Idempotency in one attribute: safe retries for HTTP APIs","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any HTTP stack: 1. Accept an Idempotency-Key header on write endpoints and require clients to keep it stable across retries. 2. Cache the first response…"},"1912":{"u":"/articles/self-invalidating-cache.html","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","x":"Subtitle: A read-through cache is easy. Keeping it correct is the hard part. Here is a cache where a write invalidates the reads it staled automatically, because invalidation…"},"1913":{"u":"/articles/self-invalidating-cache.html#why-invalidation-belongs-in-the-pipeline-not-the-handler","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"Why invalidation belongs in the pipeline, not the handler","x":"A handler's job is one use case. The fact that succeeding at that use case happens to stale some cached reads is a cross-cutting concern, not part of the use case. Tie the two…"},"1914":{"u":"/articles/self-invalidating-cache.html#the-cache-abstraction","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"The cache abstraction","x":"The whole subsystem is small and deliberate. The Application layer depends on one port: Four cache operations, a counter primitive, and one composed read-through helper.…"},"1915":{"u":"/articles/self-invalidating-cache.html#one-contract-three-backends","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"One contract, three backends","x":"AddCaching() picks a backend by probing the container. If a real distributed cache is registered (an IDistributedCache that is not the default MemoryDistributedCache, typically…"},"1916":{"u":"/articles/self-invalidating-cache.html#the-read-path-opt-in-by-marker-interface","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"The read path: opt in by marker interface","x":"A query caches by implementing IQueryCacheable (it supplies the cache key and the retention duration). The CachingQueryDecorator does the read-through: On a hit, the decorator…"},"1917":{"u":"/articles/self-invalidating-cache.html#the-write-path-invalidate-on-success-outside-the-transaction","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"The write path: invalidate on success, outside the transaction","x":"A command invalidates by implementing ICacheInvalidating, which exposes a CachePrefix. The CachingCommandDecorator runs the inner handler first, then evicts: The decorator calls…"},"1918":{"u":"/articles/self-invalidating-cache.html#swallowing-a-failure-is-a-discipline-not-a-catch","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"Swallowing a failure is a discipline, not a catch","x":"That swallow is the right call here and a dangerous habit everywhere else, so the framework gives it a name: BestEffort.ExecuteAsync(operation, logger, action, ct), for a side…"},"1919":{"u":"/articles/self-invalidating-cache.html#the-two-key-transformations-the-pipeline-applies","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"The two key transformations the pipeline applies","x":"Everything above treats the query's CacheKey and the command's CachePrefix as literal strings. At the decorator layer there are two exceptions, and they are the parts of key…"},"1920":{"u":"/articles/self-invalidating-cache.html#what-the-cache-is-and-is-not","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"What the cache is, and is not","x":"This is a request-result read-through cache for query handlers. It is not a session store and not a write-behind buffer. The cross-source consistency mechanism in this framework…"},"1921":{"u":"/articles/self-invalidating-cache.html#the-edge-tier-caching-authenticated-reads-without-leaking-identity","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"The edge tier: caching authenticated reads without leaking identity","x":"Tier 2 sits in front of the handler instead of inside the pipeline. MMCA.Common.API always calls app.UseOutputCache() but ships no policies: each host opts in. A read-heavy…"},"1922":{"u":"/articles/self-invalidating-cache.html#trade-offs-honestly","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Cache key correctness is the consumer's responsibility. The framework gives you read-through caching and prefix invalidation. It cannot know that GetProductQuery's key and…"},"1923":{"u":"/articles/self-invalidating-cache.html#apply-this-even-without-mmca","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any stack with a decoratable handler pipeline: 1. Put the cache behind a port the business layer depends on (Get, Set, Remove, RemoveByPrefix), and choose…"},"1924":{"u":"/articles/problem-details-http-grpc.html","d":"Problem Details across HTTP and gRPC (RFC 9457)","k":"The MMCA.Common series","x":"Subtitle: One error model, two transports. How a Result failure becomes the same RFC 9457 shape whether the caller reached you over HTTP or gRPC, and the honest gaps that still…"},"1925":{"u":"/articles/problem-details-http-grpc.html#why-it-matters","d":"Problem Details across HTTP and gRPC (RFC 9457)","k":"The MMCA.Common series","t":"Why it matters","x":"A consistent error contract is not cosmetic. It is the difference between a client that can branch on \"was this a validation problem, a not-found, or a conflict?\" mechanically,…"},"1926":{"u":"/articles/problem-details-http-grpc.html#the-mmca-answer-one-mapping-table-reused-on-both-transports","d":"Problem Details across HTTP and gRPC (RFC 9457)","k":"The MMCA.Common series","t":"The MMCA answer: one mapping table, reused on both transports","x":"MMCA.Common's whole error story starts from one decision recorded across the codebase: expected failures are values, not exceptions. Handlers return Result carrying an Error…"},"1927":{"u":"/articles/problem-details-http-grpc.html#the-rest-of-the-9-contract","d":"Problem Details across HTTP and gRPC (RFC 9457)","k":"The MMCA.Common series","t":"The rest of the §9 contract","x":"The same edge standardizes the things that drift if left to each endpoint: - Pagination and filtering. The generic read controllers expose GET /paged with filter, sort, and page…"},"1928":{"u":"/articles/problem-details-http-grpc.html#trade-offs-honestly","d":"Problem Details across HTTP and gRPC (RFC 9457)","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The scorecard scores §9 (API and Contract Design) at Maturity 4 of 4 and Implementation 9 of 10 (weighted 8/18). The category sits at the maturity ceiling: the contract is…"},"1929":{"u":"/articles/problem-details-http-grpc.html#apply-this-even-without-mmca","d":"Problem Details across HTTP and gRPC (RFC 9457)","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any stack with more than one client or more than one transport: 1. Model expected failures as typed values, not exceptions or ad-hoc status codes. A small…"},"1930":{"u":"/articles/notifications-vertical-slice.html","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","x":"Subtitle: A framework is mostly horizontal layers and abstract base classes. Notifications is the one concrete bounded context MMCA.Common ships, and it is built as a textbook…"},"1931":{"u":"/articles/notifications-vertical-slice.html#horizontal-layer-cut-vs-vertical-slice","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","t":"Horizontal layer cut vs. vertical slice","x":"The horizontal instinct is to organize by technical role. All the controllers in one folder, all the handlers in another, all the EF configurations in a third. The cost shows up…"},"1932":{"u":"/articles/notifications-vertical-slice.html#sending-a-push-notification-end-to-end","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","t":"Sending a push notification, end to end","x":"The send flow is the most instructive because it touches distinct consistency concerns: a deduplication check, a durable audit record, a per-user inbox, and two best-effort…"},"1933":{"u":"/articles/notifications-vertical-slice.html#retry-safety-on-two-levels-the-idempotency-filter-and-the-dedup-key","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","t":"Retry safety on two levels: the idempotency filter and the dedup key","x":"A broadcast is exactly the operation you never want to run twice, and the two ways it gets run twice are different problems. The client retries the HTTP call; or two retries…"},"1934":{"u":"/articles/notifications-vertical-slice.html#the-push-channel-an-adapter-chosen-at-composition-not-branched-at-runtime","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","t":"The push channel: an adapter chosen at composition, not branched at runtime","x":"The real-time channel is two cooperating types. NotificationHub is an [Authorize]d SignalR Hub. For this durable-push slice it contributes a ReceiveNotification method-name…"},"1935":{"u":"/articles/notifications-vertical-slice.html#the-native-push-channel-reaching-devices-the-hub-cannot","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","t":"The native push channel: reaching devices the hub cannot","x":"The SignalR leg stops at the edge of a connected client. A phone with the app backgrounded, killed, or offline hears nothing until the next launch, and conference announcements…"},"1936":{"u":"/articles/notifications-vertical-slice.html#the-in-app-inbox-pure-cqrs-readwrite-scoped-to-the-caller","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","t":"The in-app inbox: pure CQRS read/write, scoped to the caller","x":"The inbox is the durable channel, exposed by InboxController to any authenticated user (unlike the organizer-only send and history endpoints). Four slices back it: -…"},"1937":{"u":"/articles/notifications-vertical-slice.html#the-email-channel-and-the-feature-gate-over-all-of-it","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","t":"The email channel, and the feature gate over all of it","x":"Email is the smallest channel: one port IEmailSender and one SMTP adapter SmtpEmailSender, which constructs and disposes a fresh SmtpClient per send. It is independent of the…"},"1938":{"u":"/articles/notifications-vertical-slice.html#identifier-aliases-keep-the-ids-honest","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","t":"Identifier aliases keep the IDs honest","x":"A small but consistent detail: the notification entities do not type their keys as bare int. The slice uses solution-wide identifier aliases declared once in…"},"1939":{"u":"/articles/notifications-vertical-slice.html#trade-offs-honestly","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Best-effort real-time, durable record. The deliberate swallow of delivery exceptions means a push can fail silently as far as the WebSocket is concerned. That is the right call…"},"1940":{"u":"/articles/notifications-vertical-slice.html#apply-this-even-without-mmca","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The vertical-slice discipline ports to any stack: 1. Organize by feature, not by technical role. Keep a feature's domain, use cases, adapters, and endpoints together so the…"},"1941":{"u":"/articles/live-channel-push.html","d":"Ephemeral by design: sub-second live channels over one SignalR hub","k":"The MMCA.Common series","x":"Subtitle: Conference-day polls and Q&A need small events fanned out to whoever is watching the page right now, and persisting them is wrong. Here is how MMCA.Common adds…"},"1942":{"u":"/articles/live-channel-push.html#two-message-shapes-not-two-transports","d":"Ephemeral by design: sub-second live channels over one SignalR hub","k":"The MMCA.Common series","t":"Two message shapes, not two transports","x":"The mistake in both approaches is treating \"durable notification\" and \"live update\" as if the difference lives in the transport. It does not. Both are small messages pushed over…"},"1943":{"u":"/articles/live-channel-push.html#the-mmca-answer-one-hub-two-publisher-paths","d":"Ephemeral by design: sub-second live channels over one SignalR hub","k":"The MMCA.Common series","t":"The MMCA answer: one hub, two publisher paths","x":"The single NotificationHub gains channel semantics. A channel is just a SignalR group named by a key like event:1 or session:123, and the hub gets its first client-invokable…"},"1944":{"u":"/articles/live-channel-push.html#joining-a-channel-and-why-the-key-is-validated","d":"Ephemeral by design: sub-second live channels over one SignalR hub","k":"The MMCA.Common series","t":"Joining a channel, and why the key is validated","x":"Channel membership is a property of the one existing connection, which is the point of not adding a second hub. On the browser side, NotificationHubService carries both traffic…"},"1945":{"u":"/articles/live-channel-push.html#crossing-a-service-boundary-the-grpc-ingress","d":"Ephemeral by design: sub-second live channels over one SignalR hub","k":"The MMCA.Common series","t":"Crossing a service boundary: the gRPC ingress","x":"Here is where the publisher abstraction earns its keep. In ADC the module that raises live events (Engagement, running the poll and Q&A handlers) is not the host that maps the…"},"1946":{"u":"/articles/live-channel-push.html#the-queue-is-where-you-say-what-the-work-is-worth","d":"Ephemeral by design: sub-second live channels over one SignalR hub","k":"The MMCA.Common series","t":"The queue is where you say what the work is worth","x":"The publish queue in this article is one instance of a general contract (ADR-052): work that outlives a request goes on a bounded Channel singleton, drained by a SingleReader…"},"1947":{"u":"/articles/live-channel-push.html#the-sibling-contract-work-a-clock-owns","d":"Ephemeral by design: sub-second live channels over one SignalR hub","k":"The MMCA.Common series","t":"The sibling contract: work a clock owns","x":"The queue above and the durable row beside it answer the same question: a request started work and must not wait for it. Neither can answer the other one. Nothing starts a…"},"1948":{"u":"/articles/live-channel-push.html#trade-offs-honestly","d":"Ephemeral by design: sub-second live channels over one SignalR hub","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The ADR names the sharp edges, and they are all consequences of the ephemeral contract, not accidents: - Ephemeral means lossy. A client that connects after an event was…"},"1949":{"u":"/articles/live-channel-push.html#apply-this-even-without-mmca","d":"Ephemeral by design: sub-second live channels over one SignalR hub","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The idea ports to any real-time stack: 1. Split on the delivery contract, not the transport. \"Must survive an offline user\" and \"only matters on screen\" are different guarantees.…"},"1950":{"u":"/articles/manual-dto-mapping.html","d":"Delete AutoMapper: explicit, compile-time DTO mapping that you can actually test","k":"The MMCA.Common series","x":"Subtitle: Reflection-based mappers feel like they save you work until the day a renamed property silently maps to null in production. Here is the alternative: named mapper…"},"1951":{"u":"/articles/manual-dto-mapping.html#the-case-against-convention-based-mapping","d":"Delete AutoMapper: explicit, compile-time DTO mapping that you can actually test","k":"The MMCA.Common series","t":"The case against convention-based mapping","x":"Be precise about what goes wrong with a reflection mapper, because \"I prefer explicit code\" is not an argument. Here are four concrete failure modes, all things a type system is…"},"1952":{"u":"/articles/manual-dto-mapping.html#two-roles-two-interfaces","d":"Delete AutoMapper: explicit, compile-time DTO mapping that you can actually test","k":"The MMCA.Common series","t":"Two roles, two interfaces","x":"The first design move is to notice that \"mapping\" is actually two different jobs that deserve two different contracts. Reading data out and accepting data in are not symmetric,…"},"1953":{"u":"/articles/manual-dto-mapping.html#why-the-request-mapper-returns-a-result","d":"Delete AutoMapper: explicit, compile-time DTO mapping that you can actually test","k":"The MMCA.Common series","t":"Why the request mapper returns a Result","x":"When you read an entity out, mapping cannot really fail. The data already passed validation when it was written; you are just reshaping a valid object. So the read mapper returns…"},"1954":{"u":"/articles/manual-dto-mapping.html#mapperly-explicit-and-fast-because-a-generator-writes-the-body","d":"Delete AutoMapper: explicit, compile-time DTO mapping that you can actually test","k":"The MMCA.Common series","t":"Mapperly: explicit and fast, because a generator writes the body","x":"The AutoMapper crowd will raise one objection immediately: if you write every mapper by hand, you are back to typing dto.Name = entity.Name; forty times per entity, and that is…"},"1955":{"u":"/articles/manual-dto-mapping.html#auto-discovery-you-write-the-mapper-you-do-not-wire-it","d":"Delete AutoMapper: explicit, compile-time DTO mapping that you can actually test","k":"The MMCA.Common series","t":"Auto-discovery: you write the mapper, you do not wire it","x":"The last piece is registration, and the framework keeps it convention-driven without making it reflection-at-runtime. Mappers are plain classes that implement one of the two…"},"1956":{"u":"/articles/manual-dto-mapping.html#a-concrete-pair","d":"Delete AutoMapper: explicit, compile-time DTO mapping that you can actually test","k":"The MMCA.Common series","t":"A concrete pair","x":"Putting it together, a module holds both directions. The pair below is trimmed from the Store Catalog module's real Product mappers (the delegating sub-mappers, the two…"},"1957":{"u":"/articles/manual-dto-mapping.html#trade-offs-honestly","d":"Delete AutoMapper: explicit, compile-time DTO mapping that you can actually test","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"This is not a free win, and pretending otherwise would be exactly the kind of hand-waving the framework is built to avoid. - It is more boilerplate than AutoMapper's zero-config…"},"1958":{"u":"/articles/manual-dto-mapping.html#apply-this-even-without-mmca","d":"Delete AutoMapper: explicit, compile-time DTO mapping that you can actually test","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"You do not need this framework to adopt the pattern. The ideas port to any stack: 1. Split read mapping from write mapping. They are not symmetric. Reading reshapes valid data;…"},"1959":{"u":"/articles/permission-based-authorization.html","d":"Permission-based authorization: capabilities over role checks","k":"The MMCA.Common series","x":"Subtitle: [Authorize(Roles = \"Organizer\")] couples every endpoint to a role name. The day you need a role that can do most of what an organizer does but not all of it, you are…"},"1960":{"u":"/articles/permission-based-authorization.html#why-it-matters","d":"Permission-based authorization: capabilities over role checks","k":"The MMCA.Common series","t":"Why it matters","x":"Authorization that is coupled to role names has a fixed failure mode: reshaping who-can-do-what means touching endpoints. Every time the org chart of your application changes (a…"},"1961":{"u":"/articles/permission-based-authorization.html#the-mmca-answer-a-registry-a-builder-an-attribute-and-the-gates-that-read-them","d":"Permission-based authorization: capabilities over role checks","k":"The MMCA.Common series","t":"The MMCA answer: a registry, a builder, an attribute, and the gates that read them","x":"The capability layer is a small set of parts, and the design goal running through all of them is that the mechanism grants nothing until a host opts in. The registry is the…"},"1962":{"u":"/articles/permission-based-authorization.html#inert-until-adopted","d":"Permission-based authorization: capabilities over role checks","k":"The MMCA.Common series","t":"Inert until adopted","x":"The piece that makes this safe to add to a framework everyone consumes is that wiring it confers nothing until a host grants something. AddAuthorizationPolicies() always…"},"1963":{"u":"/articles/permission-based-authorization.html#trade-offs-honestly","d":"Permission-based authorization: capabilities over role checks","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The capability indirection earns its keep on exactly the content-editor case, but it is still RBAC, and the §11 review is explicit about the edges. - It is RBAC, not ABAC. The…"},"1964":{"u":"/articles/permission-based-authorization.html#apply-this-even-without-mmca","d":"Permission-based authorization: capabilities over role checks","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern is portable to any policy-based authorization stack: 1. Name capabilities, not roles, on the endpoint. sessions:manage says what the route does; Roles = \"Organizer\"…"},"1965":{"u":"/articles/session-cookie-auth-blazor-ssr.html","d":"Browser session-cookie auth for Blazor SSR: surviving the F5","k":"The MMCA.Common series","x":"Subtitle: Your Blazor app authenticates with a bearer token. Then a user presses F5 on an [Authorize] page and gets bounced to the login screen even though they are signed in.…"},"1966":{"u":"/articles/session-cookie-auth-blazor-ssr.html#why-it-matters","d":"Browser session-cookie auth for Blazor SSR: surviving the F5","k":"The MMCA.Common series","t":"Why it matters","x":"This is the gap between \"the user has a valid session\" and \"the server rendering the first paint can see it.\" A pure bearer-header design has nowhere to put the session that the…"},"1967":{"u":"/articles/session-cookie-auth-blazor-ssr.html#the-mmca-answer-httponly-cookies-plus-an-ssr-time-reader","d":"Browser session-cookie auth for Blazor SSR: surviving the F5","k":"The MMCA.Common series","t":"The MMCA answer: HttpOnly cookies plus an SSR-time reader","x":"The mechanism ships in MMCA.Common.API (a SessionCookies/ folder) with a MMCA.Common.UI companion, and both apps' Web UI hosts wire it. It has four moving parts. Two HttpOnly…"},"1968":{"u":"/articles/session-cookie-auth-blazor-ssr.html#the-csrf-tax-and-how-it-is-paid","d":"Browser session-cookie auth for Blazor SSR: surviving the F5","k":"The MMCA.Common series","t":"The CSRF tax, and how it is paid","x":"Cookie-based auth reintroduces a concern that a pure bearer-header design does not have: a cross-site page can cause the browser to send your cookies. The framework pays that tax…"},"1969":{"u":"/articles/session-cookie-auth-blazor-ssr.html#scaling-out-the-key-ring-has-to-be-shared","d":"Browser session-cookie auth for Blazor SSR: surviving the F5","k":"The MMCA.Common series","t":"Scaling out: the key ring has to be shared","x":"One replica is a special case. Add a second and a failure appears that has nothing to do with the design above: ASP.NET Core's DataProtection key ring lives in memory, per…"},"1970":{"u":"/articles/session-cookie-auth-blazor-ssr.html#trade-offs-honestly","d":"Browser session-cookie auth for Blazor SSR: surviving the F5","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The pattern fixes a real gap, and the §26 review names what it costs. - A non-validating auth scheme exists in your codebase. SessionCookieAuthenticationHandler trusts a cookie…"},"1971":{"u":"/articles/session-cookie-auth-blazor-ssr.html#apply-this-even-without-mmca","d":"Browser session-cookie auth for Blazor SSR: surviving the F5","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The shape ports to any server-prerendered SPA with token auth: 1. Put the session in an HttpOnly cookie, not localStorage. The server can read a cookie during render; it cannot…"},"1972":{"u":"/articles/external-oauth-login.html","d":"Google, GitHub and Apple login without leaking tokens: external OAuth behind your own JWTs","k":"The MMCA.Common series","x":"Subtitle: You want \"Sign in with Google,\" \"Sign in with GitHub,\" and the \"Sign in with Apple\" button the store guidelines ask for. So you add the provider packages, wire the…"},"1973":{"u":"/articles/external-oauth-login.html#why-it-matters","d":"Google, GitHub and Apple login without leaking tokens: external OAuth behind your own JWTs","k":"The MMCA.Common series","t":"Why it matters","x":"The invariant the rest of the system leans on is small and load-bearing: inside the app, a user is always a local User carrying the app's own JWT. Every other auth concern is…"},"1974":{"u":"/articles/external-oauth-login.html#the-mmca-answer-external-oauth-behind-a-local-jwt-exchange","d":"Google, GitHub and Apple login without leaking tokens: external OAuth behind your own JWTs","k":"The MMCA.Common series","t":"The MMCA answer: external OAuth behind a local-JWT exchange","x":"The mechanism ships in MMCA.Common.API, and adoption is one app deep (more on that below). It has three moving parts and one rule: the tokens never touch the URL. Scheme…"},"1975":{"u":"/articles/external-oauth-login.html#the-same-handshake-now-for-native-heads","d":"Google, GitHub and Apple login without leaking tokens: external OAuth behind your own JWTs","k":"The MMCA.Common series","t":"The same handshake, now for native heads","x":"A mobile head cannot intercept a redirect to a web URL, and the providers reject OAuth inside an embedded WebView, so a MAUI app has to run the provider flow in the system…"},"1976":{"u":"/articles/external-oauth-login.html#finding-the-local-user-three-ways","d":"Google, GitHub and Apple login without leaking tokens: external OAuth behind your own JWTs","k":"The MMCA.Common series","t":"Finding the local user, three ways","x":"ExternalLoginAsync is where the external identity becomes one of your users, and it resolves in a deliberate order inside a single transaction. First it looks for a user already…"},"1977":{"u":"/articles/external-oauth-login.html#trade-offs-honestly","d":"Google, GitHub and Apple login without leaking tokens: external OAuth behind your own JWTs","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The pattern buys real safety, and ADR-036 is candid about what it costs. - Opt-in per app, and easy to half-wire. The flow needs four cooperating pieces: scheme registration, the…"},"1978":{"u":"/articles/external-oauth-login.html#apply-this-even-without-mmca","d":"Google, GitHub and Apple login without leaking tokens: external OAuth behind your own JWTs","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The shape ports to any app that wants social login without a second identity system: 1. Terminate federation at the edge. Exchange the external principal for your own token the…"},"1979":{"u":"/articles/jwt-refresh-token-rotation.html","d":"Refresh tokens that rotate per device, and reuse detection that makes theft self-limiting","k":"The MMCA.Common series","x":"Subtitle: A stateless JWT you cannot revoke has to be short-lived, which forces a refresh token, which is a long-lived bearer credential that can be stolen and replayed. Here is…"},"1980":{"u":"/articles/jwt-refresh-token-rotation.html#why-it-matters","d":"Refresh tokens that rotate per device, and reuse detection that makes theft self-limiting","k":"The MMCA.Common series","t":"Why it matters","x":"The two credentials pull in opposite directions, and you cannot collapse them. The access token wants to be stateless (no lookup on the hot path) which means it cannot be revoked…"},"1981":{"u":"/articles/jwt-refresh-token-rotation.html#the-mmca-answer-one-hashed-session-per-device-rotated-on-every-use","d":"Refresh tokens that rotate per device, and reuse detection that makes theft self-limiting","k":"The MMCA.Common series","t":"The MMCA answer: one hashed session per device, rotated on every use","x":"The whole workflow lives in one place: AuthenticationServiceBase (AuthenticationServiceBase.cs:74). Both apps' Identity modules subclass it and change nothing about the token…"},"1982":{"u":"/articles/jwt-refresh-token-rotation.html#both-apps-inherit-it-the-oauth-path-opens-the-same-session","d":"Refresh tokens that rotate per device, and reuse detection that makes theft self-limiting","k":"The MMCA.Common series","t":"Both apps inherit it; the OAuth path opens the same session","x":"The rotation, reuse detection, session cap, and lifetime logic are identical in ADC and Store because neither one owns a copy. ADC's AuthenticationService…"},"1983":{"u":"/articles/jwt-refresh-token-rotation.html#trade-offs-honestly","d":"Refresh tokens that rotate per device, and reuse detection that makes theft self-limiting","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"This model is a deliberate set of choices, and ADR-097 names the edges rather than hiding them. - Every device is its own row, so sign-ins grow a table. A session is a device…"},"1984":{"u":"/articles/jwt-refresh-token-rotation.html#apply-this-even-without-mmca","d":"Refresh tokens that rotate per device, and reuse detection that makes theft self-limiting","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any stack with a JWT story: 1. Keep the access token short and stateless. Validate it by signature and expiry with no lookup, and set a lifetime measured in…"},"1985":{"u":"/articles/generic-entity-controllers.html","d":"Generic entity controllers and the dynamic query contract (ADR-034)","k":"The MMCA.Common series","x":"Subtitle: The write-once REST surface every entity inherits. How list, page, lookup, by-id, create, and delete come from two base classes, plus a bounded OData-lite query…"},"1986":{"u":"/articles/generic-entity-controllers.html#why-it-matters","d":"Generic entity controllers and the dynamic query contract (ADR-034)","k":"The MMCA.Common series","t":"Why it matters","x":"Most entities need the same six things: list them, page through them, fetch a lightweight id/name list for a dropdown, get one by id, create one, delete one. That surface is so…"},"1987":{"u":"/articles/generic-entity-controllers.html#the-mmca-answer-two-base-classes-and-an-odata-lite-contract","d":"Generic entity controllers and the dynamic query contract (ADR-034)","k":"The MMCA.Common series","t":"The MMCA answer: two base classes and an OData-lite contract","x":"The read surface is EntityControllerBase . It is an [ApiController] with [Route(\"[controller]\")] and [ApiVersion(\"1.0\")], and it exposes five GET routes that any entity inherits…"},"1988":{"u":"/articles/generic-entity-controllers.html#the-line-where-the-users-value-stops-being-code","d":"Generic entity controllers and the dynamic query contract (ADR-034)","k":"The MMCA.Common series","t":"The line where the user's value stops being code","x":"There is one more constraint under all of this, and it is the one that decides whether a user-supplied filter DSL is a good idea or a liability. When a strategy builds its…"},"1989":{"u":"/articles/generic-entity-controllers.html#trade-offs-honestly","d":"Generic entity controllers and the dynamic query contract (ADR-034)","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"A generic, dynamically queryable contract coupled to the entity model is a real trade against narrow bespoke endpoints. ADR-034 names the costs rather than hiding them. - The…"},"1990":{"u":"/articles/generic-entity-controllers.html#apply-this-even-without-mmca","d":"Generic entity controllers and the dynamic query contract (ADR-034)","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any stack where you expose more than a handful of CRUD resources: 1. Put the CRUD shape on a generic base and close the type parameters per entity. The…"},"1991":{"u":"/articles/resource-ownership-authorization.html","d":"Resource-ownership authorization: which rows you may touch, not just which actions","k":"The MMCA.Common series","x":"Subtitle: Article 24 covered what a role or permission lets you do. It said, explicitly, that \"a customer may read only their own order\" is a different concern. This is that…"},"1992":{"u":"/articles/resource-ownership-authorization.html#why-it-matters","d":"Resource-ownership authorization: which rows you may touch, not just which actions","k":"The MMCA.Common series","t":"Why it matters","x":"The failure mode here has a name, and it is not obscure. The OWASP API Security Top 10 lists Broken Object Level Authorization (the modern label for the classic Insecure Direct…"},"1993":{"u":"/articles/resource-ownership-authorization.html#the-mmca-answer-two-enforcement-points-one-bypass","d":"Resource-ownership authorization: which rows you may touch, not just which actions","k":"The MMCA.Common series","t":"The MMCA answer: two enforcement points, one bypass","x":"The first instinct is to look for a single ownership abstraction. There is not one, and ADR-033 is explicit about why: there are two genuinely different problems wearing one…"},"1994":{"u":"/articles/resource-ownership-authorization.html#how-mmcastore-wires-it","d":"Resource-ownership authorization: which rows you may touch, not just which actions","k":"The MMCA.Common series","t":"How MMCA.Store wires it","x":"This is not framework-only theory. MMCA.Store turns both points on in production. The filter guards the shopping-cart and customer-profile controllers as a class-level…"},"1995":{"u":"/articles/resource-ownership-authorization.html#trade-offs-honestly","d":"Resource-ownership authorization: which rows you may touch, not just which actions","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The §11 review is blunt about the edges of this, and they are real. - It is claim-trusting, not ABAC. Both points key entirely on the configured owner claim (customerid by…"},"1996":{"u":"/articles/resource-ownership-authorization.html#apply-this-even-without-mmca","d":"Resource-ownership authorization: which rows you may touch, not just which actions","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern carries to any web API, framework or not: 1. Treat ownership as a second axis, not a corollary of the role check. \"May read orders\" and \"may read this order\" are…"},"1997":{"u":"/articles/rate-limiting-brute-force-protection.html","d":"Defending the API edge: three controls that cover the whole surface","k":"The MMCA.Common series","x":"Subtitle: A global \"N requests per minute\" limiter feels like edge protection until you notice what it cannot do: it cannot key an anonymous login attempt to a principal, because…"},"1998":{"u":"/articles/rate-limiting-brute-force-protection.html#why-it-matters","d":"Defending the API edge: three controls that cover the whole surface","k":"The MMCA.Common series","t":"Why it matters","x":"\"Add rate limiting\" is not a decision. The load-bearing questions are who gets limited, by what partition key, and what is exempt. A naive global IP bucket gets all three wrong…"},"1999":{"u":"/articles/rate-limiting-brute-force-protection.html#the-mmca-answer-an-authenticated-only-global-limiter-plus-two-controls-for-the-surface-it-exempts","d":"Defending the API edge: three controls that cover the whole surface","k":"The MMCA.Common series","t":"The MMCA answer: an authenticated-only global limiter, plus two controls for the surface it exempts","x":"AddCommonRateLimiting (in MMCA.Common.API, Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.cs:386) installs a GlobalLimiter (:430-431) that runs on…"},"2000":{"u":"/articles/rate-limiting-brute-force-protection.html#trade-offs-honestly","d":"Defending the API edge: three controls that cover the whole surface","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"None of the three is a complete edge defence on its own, and the §11 review is explicit about the boundaries. - Per-principal keying needs an authenticated identity, full stop.…"},"2001":{"u":"/articles/rate-limiting-brute-force-protection.html#apply-this-even-without-mmca","d":"Defending the API edge: three controls that cover the whole surface","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The shape ports to any ASP.NET Core service, with or without this framework: 1. Pick the partition key before you pick the limit. \"300 a minute\" is meaningless until you answer…"},"2002":{"u":"/articles/aspire-one-command.html","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","x":"Subtitle: Four service processes, four databases, a broker, a cache, a mail interceptor, and a live telemetry dashboard, all from a single dotnet run. Here is how MMCA.Common…"},"2003":{"u":"/articles/aspire-one-command.html#prerequisites","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Prerequisites","x":"You need a few things in place before the first run: - .NET 10 SDK (the framework targets .NET 10.0 with C preview language features). - Docker (or a compatible container…"},"2004":{"u":"/articles/aspire-one-command.html#step-1-run-the-whole-stack-with-one-command","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Step 1: run the whole stack with one command","x":"From the consumer repo, the entire local topology comes up with: That single command brings up everything the application needs locally: four SQL Server databases, Redis,…"},"2005":{"u":"/articles/aspire-one-command.html#step-2-understand-what-addservicedefaults-configured","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Step 2: understand what AddServiceDefaults() configured","x":"Each service host calls one method first in its Program.cs, before anything app-specific: AddServiceDefaults() is the service-side baseline, and it is identical across both…"},"2006":{"u":"/articles/aspire-one-command.html#step-3-read-the-health-endpoints-from-mapdefaultendpoints","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Step 3: read the health endpoints from MapDefaultEndpoints()","x":"MapDefaultEndpoints() exposes the probe surface your platform reads: - /health runs all registered checks. This is the one for humans and dashboards. It is served through…"},"2007":{"u":"/articles/aspire-one-command.html#step-4-see-how-the-apphost-wires-the-distributed-app","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Step 4: see how the AppHost wires the distributed app","x":"The AppHost Program.cs does not start anything immediately. It builds a resource model: a graph of containers, databases, services, the gateway, and the UI, with their dependency…"},"2008":{"u":"/articles/aspire-one-command.html#step-5-wire-the-extraction-points-grpc-and-jwks","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Step 5: wire the extraction points (gRPC and JWKS)","x":"The same AppHost wires the extraction boundaries that make the modular monolith behave like real microservices: typed gRPC clients and cross-service auth. gRPC references are…"},"2009":{"u":"/articles/aspire-one-command.html#step-6-make-the-telemetry-coherent-and-keep-the-bill-down","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Step 6: make the telemetry coherent, and keep the bill down","x":"There are a few more pieces worth knowing, because they shape what you see in the dashboard, and what you pay for it. Start with the noise. The OutboxProcessor background service…"},"2010":{"u":"/articles/aspire-one-command.html#step-7-harden-the-response-headers-and-reuse-connections","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Step 7: harden the response headers, and reuse connections","x":"Two smaller pieces round out the baseline, and both are the kind of thing each host would otherwise hand-roll and drift on. First, security headers at the host edge. Instead of…"},"2011":{"u":"/articles/aspire-one-command.html#step-8-refuse-to-boot-on-bad-configuration","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Step 8: refuse to boot on bad configuration","x":"One command bringing the stack up is only useful if a misconfigured process says so immediately. A bad settings value can surface in two places. At first use, where a missing…"},"2012":{"u":"/articles/aspire-one-command.html#trade-offs-and-gotchas-honestly","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Trade-offs and gotchas, honestly","x":"A single-command stack is a force multiplier, but it has edges worth naming: - It wants Docker and an interactive console. No container runtime means no SQL, Redis, or broker.…"},"2013":{"u":"/articles/aspire-one-command.html#apply-this-even-without-mmca","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports cleanly to any Aspire app: 1. Put service defaults (telemetry, discovery, resilience) in one shared method every host calls, not copy-pasted per service. Drift…"},"2014":{"u":"/articles/extract-module-to-grpc-service.html","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","x":"Subtitle: A step-by-step walkthrough of lifting a module out of the monolith into its own gRPC service without rewriting application code. Define a contract, wire a typed client,…"},"2015":{"u":"/articles/extract-module-to-grpc-service.html#step-0-the-starting-invariant","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Step 0: the starting invariant","x":"The precondition that makes everything else possible: application and domain code already talk to abstractions, and transport choices live at the edges. Concretely, that means…"},"2016":{"u":"/articles/extract-module-to-grpc-service.html#step-1-define-the-contracts-project","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Step 1: define the .Contracts project","x":"The wire surface lives in its own project whose name ends in .Contracts. That suffix is load-bearing. Directory.Build.props gives any .Contracts project special treatment: it…"},"2017":{"u":"/articles/extract-module-to-grpc-service.html#step-2-server-side-addgrpcservicedefaults","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Step 2: server side, AddGrpcServiceDefaults()","x":"In the extracted Engagement service's host, register the gRPC server defaults and map the service: AddGrpcServiceDefaults() registers GrpcResultExceptionInterceptor plus gRPC…"},"2018":{"u":"/articles/extract-module-to-grpc-service.html#step-3-client-side-addtypedgrpcclienttclientservicename","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Step 3: client side, AddTypedGrpcClient(serviceName)","x":"On the calling side (Conference's host), wire the typed gRPC client and replace the local interface registration with the adapter: AddTypedGrpcClient (serviceName) wires the…"},"2019":{"u":"/articles/extract-module-to-grpc-service.html#step-4-flip-the-message-bus-from-in-process-to-broker","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Step 4: flip the message bus from in-process to broker","x":"Synchronous calls now cross the wire. The asynchronous, fire-and-forget flows (domain and integration events) need the same treatment, and the framework makes it a configuration…"},"2020":{"u":"/articles/extract-module-to-grpc-service.html#step-5-jwks-so-the-new-service-validates-tokens","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Step 5: JWKS so the new service validates tokens","x":"The extracted Engagement service now receives forwarded JWTs from Conference and must validate them. It does so against the issuer's JWKS, the public signing keys, not a shared…"},"2021":{"u":"/articles/extract-module-to-grpc-service.html#step-6-wire-it-in-the-apphost-with-mmcacommonaspirehosting","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Step 6: wire it in the AppHost with MMCA.Common.Aspire.Hosting","x":"Local orchestration is where the topology becomes runnable. MMCA.Common.Aspire.Hosting provides AppHost extension methods for the cross-cutting infrastructure of an extracted…"},"2022":{"u":"/articles/extract-module-to-grpc-service.html#step-7-route-through-the-yarp-gateway","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Step 7: route through the YARP gateway","x":"Clients never address a service directly. A single YARP reverse-proxy gateway owns the route-to-service map and is the only entry point. It has no DbContext and no controllers;…"},"2023":{"u":"/articles/extract-module-to-grpc-service.html#step-8-what-the-edge-process-itself-owns","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Step 8: what the edge process itself owns","x":"Step 7 gets a request to the right service. The edge process also has jobs of its own, and they are jobs no service behind it can do, because only the gateway sees every request…"},"2024":{"u":"/articles/extract-module-to-grpc-service.html#a-note-you-cannot-skip-adr-012s-two-kestrel-transport-profiles","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"A note you cannot skip: ADR-012's two Kestrel transport profiles","x":"On a cleartext endpoint there is no TLS, so there is no ALPN to negotiate the protocol. Kestrel must be told up front which protocols the cleartext port speaks, and the choice…"},"2025":{"u":"/articles/extract-module-to-grpc-service.html#the-whole-point-it-stays-reversible","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"The whole point: it stays reversible","x":"Walk the steps back and you see why this is not a one-way door. Because transport lives at the edge and the core talks to abstractions, a service can be re-collapsed into a…"},"2026":{"u":"/articles/extract-module-to-grpc-service.html#trade-offs-honestly","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Operational complexity multiplies. You now run multiple deployables plus a gateway, service discovery, a broker, and per-service databases instead of one process. Aspire hides…"},"2027":{"u":"/articles/extract-module-to-grpc-service.html#apply-this-even-without-mmca","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The recipe ports to any stack: 1. Make the core talk to abstractions and put a fitness test on it. A message-bus interface and plain service interfaces, with an architecture test…"},"2028":{"u":"/articles/resilience-recovery-objectives.html","d":"Retries are not a recovery plan: resilience handlers, RTO/RPO, and a restore you actually drilled","k":"The MMCA.Common series","x":"Subtitle: The last article promised the resilience handler that makes retries happen, and the recovery objectives behind it. Here is both halves: the Polly pipeline on every…"},"2029":{"u":"/articles/resilience-recovery-objectives.html#half-one-the-standard-resilience-handler-on-every-outbound-client","d":"Retries are not a recovery plan: resilience handlers, RTO/RPO, and a restore you actually drilled","k":"The MMCA.Common series","t":"Half one: the standard resilience handler on every outbound client","x":"The runtime half is the part already shown elsewhere in this series, so I will summarize and attribute rather than re-derive it. Every outbound HTTP and gRPC client in the system…"},"2030":{"u":"/articles/resilience-recovery-objectives.html#fitness-enforced-not-just-present","d":"Retries are not a recovery plan: resilience handlers, RTO/RPO, and a restore you actually drilled","k":"The MMCA.Common series","t":"Fitness-enforced, not just present","x":"A convention that is \"applied everywhere by everyone remembering to\" is the same failure mode the idempotency and caching articles kept warning about. The framing across this…"},"2031":{"u":"/articles/resilience-recovery-objectives.html#the-one-place-resilience-left-the-outbound-client-the-outbox-broker-publish","d":"Retries are not a recovery plan: resilience handlers, RTO/RPO, and a restore you actually drilled","k":"The MMCA.Common series","t":"The one place resilience left the outbound client: the outbox broker publish","x":"For most of this series, \"resilience policy\" has meant \"outbound HTTP or gRPC client.\" ADR-087 (accepted 2026-08-18) amends ADR-009 with the first exception, and it is a narrow…"},"2032":{"u":"/articles/resilience-recovery-objectives.html#half-two-declared-rto-and-rpo-per-scenario","d":"Retries are not a recovery plan: resilience handlers, RTO/RPO, and a restore you actually drilled","k":"The MMCA.Common series","t":"Half two: declared RTO and RPO per scenario","x":"Now the part that is actually missing from most \"resilience\" treatments, and the part this article exists to document. ADR-009 requires each consuming app to declare, in writing,…"},"2033":{"u":"/articles/resilience-recovery-objectives.html#the-drilled-restore-backups-you-tested-not-backups-you-assumed","d":"Retries are not a recovery plan: resilience handlers, RTO/RPO, and a restore you actually drilled","k":"The MMCA.Common series","t":"The drilled restore: backups you tested, not backups you assumed","x":"The second word in \"declared RTO/RPO and a drilled restore\" is the one that separates a posture from a wish. The backup posture in ADC's runbook is two tiers. Point-in-time…"},"2034":{"u":"/articles/resilience-recovery-objectives.html#database-per-service-multiplies-the-restore-surface","d":"Retries are not a recovery plan: resilience handlers, RTO/RPO, and a restore you actually drilled","k":"The MMCA.Common series","t":"Database-per-service multiplies the restore surface","x":"There is a structural reason recovery is harder here than in a monolith, and it is the direct cost of a decision celebrated elsewhere in this series. ADR-006…"},"2035":{"u":"/articles/resilience-recovery-objectives.html#the-dr-runbook-and-the-deploy-time-auto-revert","d":"Retries are not a recovery plan: resilience handlers, RTO/RPO, and a restore you actually drilled","k":"The MMCA.Common series","t":"The DR runbook and the deploy-time auto-revert","x":"The runbook governs four per-service databases, ADCIdentity, ADCConference, ADCEngagement and ADCNotification, and those four are the entire application data estate: one Basic…"},"2036":{"u":"/articles/resilience-recovery-objectives.html#trade-offs-honestly","d":"Retries are not a recovery plan: resilience handlers, RTO/RPO, and a restore you actually drilled","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- RTO/RPO are only as real as the last drill. ADC's objectives are declared with specific numbers, and the restore is drilled repeatedly: the drill table records six passing…"},"2037":{"u":"/articles/resilience-recovery-objectives.html#apply-this-even-without-mmca","d":"Retries are not a recovery plan: resilience handlers, RTO/RPO, and a restore you actually drilled","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The posture ports to any stack, and most of it is discipline rather than code: 1. Put the resilience handler in one shared place every client inherits (the HttpClient/gRPC…"},"2038":{"u":"/articles/architecture-fitness-functions.html","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","x":"Subtitle: The inward-dependency rule everyone agrees on is the one everyone eventually breaks. Here is how MMCA.Common enforces it twice, defines the rules once, and makes them…"},"2039":{"u":"/articles/architecture-fitness-functions.html#why-a-diagram-is-not-enforcement","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","t":"Why a diagram is not enforcement","x":"The inward-dependency rule is the load-bearing wall of Clean Architecture. The whole value proposition, business logic that is independent of frameworks, UI, and data stores,…"},"2040":{"u":"/articles/architecture-fitness-functions.html#enforced-twice-compile-time-and-runtime","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","t":"Enforced twice: compile-time and runtime","x":"MMCA.Common enforces the dependency rules through two independent gates, and the redundancy is deliberate (MMCA.Common/CLAUDE.md, Architecture Enforcement section). 1.…"},"2041":{"u":"/articles/architecture-fitness-functions.html#defined-once-identical-everywhere","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","t":"Defined once, identical everywhere","x":"Here is the part that makes the fitness functions scale across more than one repo without rotting into three slightly-different copies. The rule bodies do not live in the test…"},"2042":{"u":"/articles/architecture-fitness-functions.html#the-rules-that-matter-most","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","t":"The rules that matter most","x":"Three example rule families show what fitness functions actually assert. LayerDependencyTests. The inward-dependency rule: API reaches down to Infrastructure, Infrastructure to…"},"2043":{"u":"/articles/architecture-fitness-functions.html#freezing-the-wire-the-contract-no-single-repos-build-can-see","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","t":"Freezing the wire: the contract no single repo's build can see","x":"A .proto file is not source in the ordinary sense. It is a published contract, and what makes it dangerous is that breaking it looks exactly like editing a local file. Renumber a…"},"2044":{"u":"/articles/architecture-fitness-functions.html#the-other-kind-of-fitness-test-documentation-as-a-contract","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","t":"The other kind of fitness test: documentation as a contract","x":"Everything above tests code against code. There is a second family that tests documentation against code, and it is the one that stops a doc from rotting the moment someone ships…"},"2045":{"u":"/articles/architecture-fitness-functions.html#the-same-family-aimed-at-operations-every-alert-needs-a-runbook","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","t":"The same family, aimed at operations: every alert needs a runbook","x":"The newest member of that family points the idea at your on-call rotation. An alert with no triage steps is a page with no next step, and a runbook section for an alert that no…"},"2046":{"u":"/articles/architecture-fitness-functions.html#the-third-kind-a-fitness-function-for-cost","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","t":"The third kind: a fitness function for cost","x":"Structural rules assert shape. Documentation rules assert honesty. There is a third thing worth failing a build over, and it is the one most teams leave to a dashboard: cost.…"},"2047":{"u":"/articles/architecture-fitness-functions.html#trade-offs-honestly","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"Fitness functions are not free, and the scorecard does not pretend otherwise. - They only cover what you write a rule for. Read the scorecard category by category and the pattern…"},"2048":{"u":"/articles/architecture-fitness-functions.html#apply-this-even-without-mmca","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any stack and most of it is one afternoon of work: 1. Pick the one rule you would be most upset to see violated silently. For most teams that is the…"},"2049":{"u":"/articles/test-pyramid.html","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","x":"Subtitle: A suite you run constantly is worth more than a suite you run nervously. Here is how MMCA.Common keeps 2,254 tests fast enough to live in the inner loop, with…"},"2050":{"u":"/articles/test-pyramid.html#why-the-shape-matters-more-than-the-count","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","t":"Why the shape matters more than the count","x":"The test-count number is not the point. The shape is. The rubric (Architecture Evaluation Criteria, category 14) describes a healthy pyramid as \"many fast unit tests on domain…"},"2051":{"u":"/articles/test-pyramid.html#the-base-fast-pure-no-infrastructure","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","t":"The base: fast, pure, no infrastructure","x":"The wide base of the MMCA.Common pyramid is pure unit tests with no I/O (28. Testing & Quality Infrastructure): domain entity factories and invariants, value objects, the…"},"2052":{"u":"/articles/test-pyramid.html#the-middle-real-sqlite-two-real-databases-still-no-docker","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","t":"The middle: real SQLite, two real databases, still no Docker","x":"Above the base sit the persistence and EF-configuration tests, and this is where the \"no Docker\" claim gets interesting. Rather than mock the ORM (which proves nothing about…"},"2053":{"u":"/articles/test-pyramid.html#the-cap-and-an-orthogonal-tier","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","t":"The cap, and an orthogonal tier","x":"At the top of the pyramid are the component tests (bUnit, rendering Blazor components in isolation) and the end-to-end tests (Playwright, driving a real browser). These are…"},"2054":{"u":"/articles/test-pyramid.html#shipped-test-packages-consumers-do-not-re-invent-the-harness","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","t":"Shipped test packages: consumers do not re-invent the harness","x":"Five of the framework's nineteen published NuGet packages are test infrastructure, not test code, and this is how the pyramid stays consistent across three codebases instead of…"},"2055":{"u":"/articles/test-pyramid.html#the-apphost-tier-a-compiled-claim-about-the-composition","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","t":"The AppHost tier: a compiled claim about the composition","x":"There is one file the tiers above never execute: the AppHost. It is the only place that states how a whole stack fits together, which project resources exist, which database each…"},"2056":{"u":"/articles/test-pyramid.html#accessibility-as-a-shipped-test-contract","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","t":"Accessibility as a shipped test contract","x":"The E2E package carries one more contract, and it is the tier where a published standard turns into a build failure instead of a review comment. ADR-063 records it: WCAG 2.1 AA…"},"2057":{"u":"/articles/test-pyramid.html#runner-and-gate-xunit-v3-mtp-and-a-coverage-floor","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","t":"Runner and gate: xUnit v3, MTP, and a coverage floor","x":"A detail that trips up people coming from older .NET projects: the suite runs on the Microsoft Testing Platform (MTP), not VSTest, configured in global.json, with xUnit v3,…"},"2058":{"u":"/articles/test-pyramid.html#trade-offs-honestly","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The shape is right, but the scorecard names real gaps and one is squarely in the testing story. - The UI tier's visual check is markup-deep, not pixel-deep. The framework ships…"},"2059":{"u":"/articles/test-pyramid.html#apply-this-even-without-mmca","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The shape ports to any stack: 1. Make the base fast enough to run on every save. No Docker, no network, no shared database in the layer you run constantly. If your unit tests…"},"2060":{"u":"/articles/soft-delete-vs-erasure.html","d":"Soft-delete vs the right to erasure: the GDPR conflict and the erasure pathway","k":"The MMCA.Common series","x":"Subtitle: Soft-delete is the right default for lifecycle and audit. It is also, by itself, illegal for personal data under GDPR. This is the story of scoring that conflict 1 out…"},"2061":{"u":"/articles/soft-delete-vs-erasure.html#why-it-matters-and-why-i-am-telling-on-myself","d":"Soft-delete vs the right to erasure: the GDPR conflict and the erasure pathway","k":"The MMCA.Common series","t":"Why it matters, and why I am telling on myself","x":"When I first graded MMCA.Common against an architecture rubric and committed the scorecard to the repo, the original single-axis snapshot scored Compliance, Privacy & Data…"},"2062":{"u":"/articles/soft-delete-vs-erasure.html#the-mmca-answer-separate-the-two-concerns-give-each-a-mechanism","d":"Soft-delete vs the right to erasure: the GDPR conflict and the erasure pathway","k":"The MMCA.Common series","t":"The MMCA answer: separate the two concerns, give each a mechanism","x":"The wrong fix is to overload Delete() so it hard-deletes personal data. That would break audit, undelete, and referential integrity all at once, trading one defect for three.…"},"2063":{"u":"/articles/soft-delete-vs-erasure.html#the-delete-that-really-removes-rows-restrict-by-default","d":"Soft-delete vs the right to erasure: the GDPR conflict and the erasure pathway","k":"The MMCA.Common series","t":"The delete that really removes rows: restrict by default","x":"Soft-delete keeps the row. Anonymization keeps the row and destroys the person inside it. Physical erasure, a real DELETE that takes rows out of the table, is a third thing, and…"},"2064":{"u":"/articles/soft-delete-vs-erasure.html#the-second-hiding-place-the-outbox","d":"Soft-delete vs the right to erasure: the GDPR conflict and the erasure pathway","k":"The MMCA.Common series","t":"The second hiding place: the outbox","x":"There was a second source of retained personal data, and it is the kind of thing you only find when you go looking honestly. The transactional outbox (ADR-003) writes an event…"},"2065":{"u":"/articles/soft-delete-vs-erasure.html#the-live-session-cutting-off-a-token-mid-flight","d":"Soft-delete vs the right to erasure: the GDPR conflict and the erasure pathway","k":"The MMCA.Common series","t":"The live session: cutting off a token mid-flight","x":"Anonymizing the aggregate and committing the soft-delete shuts two doors: no future logins, and no silent re-mint of a fresh access token, because the refresh flow re-fetches the…"},"2066":{"u":"/articles/soft-delete-vs-erasure.html#the-other-right-portability-and-why-it-degrades-instead-of-failing","d":"Soft-delete vs the right to erasure: the GDPR conflict and the erasure pathway","k":"The MMCA.Common series","t":"The other right: portability, and why it degrades instead of failing","x":"Erasure has a sibling that lands in the same regulation and is usually built badly. The right to data portability means a user can ask for everything you hold about them, and…"},"2067":{"u":"/articles/soft-delete-vs-erasure.html#trade-offs-honestly","d":"Soft-delete vs the right to erasure: the GDPR conflict and the erasure pathway","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The framework provides the mechanisms (IAnonymizable, OutboxCleanupService); the consumer owns the policy. That division is the most important caveat, and ADR-005 states it…"},"2068":{"u":"/articles/soft-delete-vs-erasure.html#apply-this-even-without-mmca","d":"Soft-delete vs the right to erasure: the GDPR conflict and the erasure pathway","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern is independent of the framework: 1. Keep soft-delete for lifecycle. It is the right tool for \"is this active?\", and audit / undelete / referential integrity all…"},"2069":{"u":"/articles/reusable-blazor-ui-framework.html","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","x":"Subtitle: This series has been backend all the way down. But the front end is where DRY usually goes to die: every list screen re-implements paging, sort, filter, loading, and…"},"2070":{"u":"/articles/reusable-blazor-ui-framework.html#the-package-that-is-allowed-to-depend-on-almost-nothing","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","t":"The package that is allowed to depend on almost nothing","x":"One structural fact sets the tone. MMCA.Common.UI is allowed to reference MMCA.Common.Shared and nothing else. Not Application, not Domain, not Infrastructure. That is a…"},"2071":{"u":"/articles/reusable-blazor-ui-framework.html#datagridlistpagebase-the-screen-you-stop-rewriting","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","t":"DataGridListPageBase: the screen you stop rewriting","x":"The centerpiece is an abstract Blazor component, DataGridListPageBase . Every server-paged list screen in every consumer app derives from it. It folds into one reusable place the…"},"2072":{"u":"/articles/reusable-blazor-ui-framework.html#the-same-page-on-a-phone-without-a-second-page","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","t":"The same page on a phone, without a second page","x":"The desktop path above is only half of what DataGridListPageBase does, because a data grid is the wrong control on a 390px screen and no amount of CSS fixes that. The base…"},"2073":{"u":"/articles/reusable-blazor-ui-framework.html#the-data-access-boundary-bind-to-an-interface-never-to-httpclient","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","t":"The data-access boundary: bind to an interface, never to HttpClient","x":"A page in this framework never injects a raw HttpClient. It injects IEntityService , a generic CRUD contract: GetAllAsync, GetPagedAsync, GetByIdAsync, AddAsync, UpdateAsync,…"},"2074":{"u":"/articles/reusable-blazor-ui-framework.html#theming-one-set-of-tokens-guarded-by-a-fitness-test","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","t":"Theming: one set of tokens, guarded by a fitness test","x":"Visual consistency lives in one static MudTheme, exposed as MMCATheme.Instance and handed to MudThemeProvider by a shared component, MmcaThemeProviders, that a root layout drops…"},"2075":{"u":"/articles/reusable-blazor-ui-framework.html#render-mode-state-persistence-no-flash-no-double-fetch","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","t":"Render-mode state persistence: no flash, no double fetch","x":"Modern Blazor's InteractiveAuto renders a page up to three times: static SSR, then interactive Server, then WASM. Naively, each transition re-runs the data fetch, and the user…"},"2076":{"u":"/articles/reusable-blazor-ui-framework.html#the-shell-ships-in-the-package-too-modules-plug-in-with-iuimodule","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","t":"The shell ships in the package too: modules plug in with IUIModule","x":"A base class removes the duplication inside a page. The next duplication up is the application shell itself: the router, the layout, the nav menu, and the handful of pages every…"},"2077":{"u":"/articles/reusable-blazor-ui-framework.html#host-polymorphic-token-refresh-one-component-set-three-platforms","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","t":"Host-polymorphic token refresh: one component set, three platforms","x":"Here is where the front-end ports pay off the most. The same Razor component set runs on Blazor Server, Blazor WebAssembly, and a .NET MAUI hybrid host (Android, iOS, macOS,…"},"2078":{"u":"/articles/reusable-blazor-ui-framework.html#trade-offs-honestly","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- This is opinionated UI infrastructure coupled to MudBlazor. DataGridListPageBase is built around MudDataGrid , GridState , and MudBlazor's viewport observer. The base is…"},"2079":{"u":"/articles/reusable-blazor-ui-framework.html#apply-this-even-without-mmca","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The moves port to any component framework, not just Blazor: 1. Put a list-page base component in charge of paging, sort, filter, loading, cancellation, and teardown. A concrete…"},"2080":{"u":"/articles/i18n-and-theming.html","d":"One preference, two switches: shipping i18n and dark mode on a single cookie-and-profile pipeline","k":"The MMCA.Common series","x":"Subtitle: A user picking Spanish and a user picking dark mode are doing the same thing: stating a per-user preference that has to survive a page refresh, three render modes, and…"},"2081":{"u":"/articles/i18n-and-theming.html#why-it-matters","d":"One preference, two switches: shipping i18n and dark mode on a single cookie-and-profile pipeline","k":"The MMCA.Common series","t":"Why it matters","x":"Count the places a preference has to be true at once. The static prerender pass renders HTML on the server before any JavaScript runs. The interactive Server circuit takes over.…"},"2082":{"u":"/articles/i18n-and-theming.html#the-mmca-answer-one-preference-pipeline-used-twice","d":"One preference, two switches: shipping i18n and dark mode on a single cookie-and-profile pipeline","k":"The MMCA.Common series","t":"The MMCA answer: one preference pipeline, used twice","x":"Supported cultures are an explicit allowlist, not an open-ended capability. SupportedCultures (MMCA.Common.Shared/Globalization/SupportedCultures.cs:9) holds Default = \"en-US\"…"},"2083":{"u":"/articles/i18n-and-theming.html#the-pipeline-in-one-sketch","d":"One preference, two switches: shipping i18n and dark mode on a single cookie-and-profile pipeline","k":"The MMCA.Common series","t":"The pipeline, in one sketch","x":"This is illustrative-of-shape, condensed from the files above to put the shared pipeline in one view, not a verbatim paste: A culture choice and a theme choice enter the same way…"},"2084":{"u":"/articles/i18n-and-theming.html#trade-offs-honestly","d":"One preference, two switches: shipping i18n and dark mode on a single cookie-and-profile pipeline","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Coverage is two locales, en-US and Spanish, full stop. Spanish (es) is the one added locale (SupportedCultures.cs:18). The mechanism is built to scale (adding a locale is one…"},"2085":{"u":"/articles/i18n-and-theming.html#apply-this-even-without-mmca","d":"One preference, two switches: shipping i18n and dark mode on a single cookie-and-profile pipeline","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The moves port to any component framework with a server-prerender-then-client lifecycle: 1. Treat locale and theme as the same kind of thing. Both are small per-user preferences.…"},"2086":{"u":"/articles/build-your-first-module.html","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","x":"Subtitle: One command writes the solution. The seven steps after it are the ones worth understanding, because they are what makes the generated code safe to change. --- Most \"add…"},"2087":{"u":"/articles/build-your-first-module.html#prerequisites","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"Prerequisites","x":"- .NET 10 SDK. The framework targets net10.0 with LangVersion: preview for C extension types. - Familiarity with the layer flow: API/Grpc - Infrastructure - Application - Domain…"},"2088":{"u":"/articles/build-your-first-module.html#step-0-scaffold-the-solution","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"Step 0: scaffold the solution","x":"Three names, all independent: the solution (also your root namespace), the first module in plural PascalCase, and its aggregate root in singular PascalCase. Everything derived…"},"2089":{"u":"/articles/build-your-first-module.html#step-1-define-the-aggregate-with-a-private-constructor-and-a-result-factory","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"Step 1: define the aggregate with a private constructor and a Result factory","x":"Open the Domain layer of the module you just generated (Source/Modules/Orders/...Orders.Domain/), or start a fresh one there. An aggregate root inherits from…"},"2090":{"u":"/articles/build-your-first-module.html#step-2-raise-a-domain-event-with-adddomainevent","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"Step 2: raise a domain event with AddDomainEvent","x":"State changes that other parts of the system care about are announced as domain events. You do not publish them directly; you record them on the aggregate, and the framework…"},"2091":{"u":"/articles/build-your-first-module.html#step-3-write-the-command-and-its-handler","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"Step 3: write the command and its handler","x":"A write is a use case: a small command object handed to a handler that does exactly one thing. The handler implements ICommandHandler , which is a single method, Task…"},"2092":{"u":"/articles/build-your-first-module.html#step-4-add-a-fluentvalidation-validator","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"Step 4: add a FluentValidation validator","x":"Input validation does not belong in the handler. Write a FluentValidation validator for the command, and the pipeline runs it automatically before the handler ever executes: This…"},"2093":{"u":"/articles/build-your-first-module.html#step-5-implement-imodule","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"Step 5: implement IModule","x":"A module is the unit of cohesion above a feature slice. It implements IModule: a display Name, an optional Dependencies list of other module names, a RequiresDependencies flag,…"},"2094":{"u":"/articles/build-your-first-module.html#step-6-register-in-the-exact-di-sequence","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"Step 6: register in the exact DI sequence","x":"This is the step the most experienced developers still get wrong, because one ordering rule is load-bearing. AddApplicationDecorators() must run after every module's handler…"},"2095":{"u":"/articles/build-your-first-module.html#step-7-watch-the-decorator-pipeline-kick-in-for-free","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"Step 7: watch the decorator pipeline kick in, for free","x":"With the sequence correct, every command and query handler is now wrapped, in this execution order: You wrote a thin handler. The pipeline added the rest, driven by the marker…"},"2096":{"u":"/articles/build-your-first-module.html#the-next-slice-and-the-next-module","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"The next slice, and the next module","x":"You will repeat that shape for every feature you add, so the pack scaffolds it too, which keeps it from drifting one hand-typed slice at a time. A single vertical slice is one…"},"2097":{"u":"/articles/build-your-first-module.html#trade-offs-and-gotchas-honestly","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"Trade-offs and gotchas, honestly","x":"The shape buys consistency, and it asks for discipline in return: - The DI order is unforgiving. Call AddApplicationDecorators() too early and handlers run undecorated with no…"},"2098":{"u":"/articles/write-your-first-fitness-test.html","d":"Write your first architecture fitness test","k":"The MMCA.Common series","x":"Subtitle: Your Clean Architecture diagram is a hope, not a guarantee, until something fails the build when it is violated. Here is how to add NetArchTest fitness functions to…"},"2099":{"u":"/articles/write-your-first-fitness-test.html#why-rules-as-code-beats-review","d":"Write your first architecture fitness test","k":"The MMCA.Common series","t":"Why rules-as-code beats review","x":"Before the steps, the thesis, because it shapes everything below. An architecture invariant is easy to state (\"Domain depends on nothing above it\") and easy to erode by accident.…"},"2100":{"u":"/articles/write-your-first-fitness-test.html#prerequisites","d":"Write your first architecture fitness test","k":"The MMCA.Common series","t":"Prerequisites","x":"- A .NET solution with a layered shape you want to protect. The MMCA layers are API/Grpc - Infrastructure - Application - Domain - Shared, but the rule library generalizes. -…"},"2101":{"u":"/articles/write-your-first-fitness-test.html#step-1-install-the-package-and-create-a-test-project","d":"Write your first architecture fitness test","k":"The MMCA.Common series","t":"Step 1: install the package and create a test project","x":"Add a dedicated architecture-test project (mirroring how the framework keeps Tests/Architecture/.Architecture.Tests), and reference the package: The package brings the…"},"2102":{"u":"/articles/write-your-first-fitness-test.html#step-2-implement-iarchitecturemap-one-anchor-type-per-layer","d":"Write your first architecture fitness test","k":"The MMCA.Common series","t":"Step 2: implement IArchitectureMap, one anchor type per layer","x":"This is the only real work, and it is small. IArchitectureMap is the single extension point every rule is parameterized by. You declare your layers (and modules, if you have…"},"2103":{"u":"/articles/write-your-first-fitness-test.html#step-3-subclass-the-test-bases-to-inherit-the-rule-library","d":"Write your first architecture fitness test","k":"The MMCA.Common series","t":"Step 3: subclass the test bases to inherit the rule library","x":"Now your test classes shrink to thin sealed subclasses that supply the map and inherit the [Fact]s. Two of the most valuable bases to start with: That is genuinely the whole test…"},"2104":{"u":"/articles/write-your-first-fitness-test.html#step-4-run-the-suite-under-microsoft-testing-platform","d":"Write your first architecture fitness test","k":"The MMCA.Common series","t":"Step 4: run the suite under Microsoft Testing Platform","x":"The fitness tests are ordinary tests in your test tier, so they run with the same command as everything else: Because they run in the normal tier, a violated invariant fails CI…"},"2105":{"u":"/articles/write-your-first-fitness-test.html#step-5-optional-add-the-compile-time-layer-guard-too","d":"Write your first architecture fitness test","k":"The MMCA.Common series","t":"Step 5 (optional): add the compile-time layer guard too","x":"The runtime suite is the broad net. For the single most common mistake, a bad project reference, you can get even faster feedback by also adding the compile-time guard, so the…"},"2106":{"u":"/articles/write-your-first-fitness-test.html#trade-offs-and-gotchas-honestly","d":"Write your first architecture fitness test","k":"The MMCA.Common series","t":"Trade-offs and gotchas, honestly","x":"ADR-015 names its own rough edges, and you should know them before you trust the green: - The tests assert structure and registration, not runtime behavior. A fitness test can…"},"2107":{"u":"/articles/write-your-first-fitness-test.html#apply-this-even-without-mmca","d":"Write your first architecture fitness test","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"If you are not on MMCA.Common, the pattern still ports to any .NET stack with NetArchTest: 1. Write each invariant as a named test, one per forbidden edge, so a failure points at…"},"2108":{"u":"/articles/two-real-apps-case-study.html","d":"Two real apps on one framework: a conference platform and a store","k":"The MMCA.Common series","x":"Subtitle: A framework's README can claim anything. The real test is whether two unrelated applications, with different domains and different deployments, can both run on it…"},"2109":{"u":"/articles/two-real-apps-case-study.html#the-two-apps","d":"Two real apps on one framework: a conference platform and a store","k":"The MMCA.Common series","t":"The two apps","x":"MMCA.ADC is the Atlanta Developers Conference application. Its domain is events: conferences, sessions, speakers, rooms, categories, and the questions attendees ask. It carries…"},"2110":{"u":"/articles/two-real-apps-case-study.html#what-they-actually-share","d":"Two real apps on one framework: a conference platform and a store","k":"The MMCA.Common series","t":"What they actually share","x":"Both apps sit at the same architecture stage, and that is deliberate. Each one has its modules extracted into separate service hosts (one process per module) behind a YARP…"},"2111":{"u":"/articles/two-real-apps-case-study.html#how-a-framework-change-ships","d":"Two real apps on one framework: a conference platform and a store","k":"The MMCA.Common series","t":"How a framework change ships","x":"The interesting operational property is not that the apps share code. It is how a change to the shared code reaches them. There is no phased rollout, no opt-in flag, no…"},"2112":{"u":"/articles/two-real-apps-case-study.html#what-each-app-proves-that-the-framework-cannot","d":"Two real apps on one framework: a conference platform and a store","k":"The MMCA.Common series","t":"What each app proves that the framework cannot","x":"A framework can demonstrate a pattern. It cannot, by itself, demonstrate that the pattern survives a real domain and a real deployment. That is the division of labor between…"},"2113":{"u":"/articles/two-real-apps-case-study.html#the-honest-note","d":"Two real apps on one framework: a conference platform and a store","k":"The MMCA.Common series","t":"The honest note","x":"Here is the caveat the framework writes about itself. It is not a preamble or a methodology section: it is written into the category rows. Because the framework is a library and…"},"2114":{"u":"/articles/two-real-apps-case-study.html#what-we-covered--next","d":"Two real apps on one framework: a conference platform and a store","k":"The MMCA.Common series","t":"What we covered / Next","x":"What we covered: two unrelated applications, a conference platform and an e-commerce store, both running on the same MMCA.Common packages at the same lockstep version and the…"},"2115":{"u":"/articles/device-capability-abstraction.html","d":"One Blazor UI, two hosts: a device-capability layer that stays resolvable everywhere","k":"The MMCA.Common series","x":"Subtitle: The same Blazor component set runs in a browser and inside a MAUI Blazor Hybrid app. But a share sheet, a fingerprint prompt, and a haptic tap only exist on the phone.…"},"2116":{"u":"/articles/device-capability-abstraction.html#the-mmca-answer-one-small-contract-per-capability-chosen-per-host","d":"One Blazor UI, two hosts: a device-capability layer that stays resolvable everywhere","k":"The MMCA.Common series","t":"The MMCA answer: one small contract per capability, chosen per host","x":"There is no god IDeviceCapabilities interface. That was a deliberate rejection: an aggregate device service forces every head to implement everything, and every new capability…"},"2117":{"u":"/articles/device-capability-abstraction.html#three-flavors-of-unavailable-all-registered-by-default","d":"One Blazor UI, two hosts: a device-capability layer that stays resolvable everywhere","k":"The MMCA.Common series","t":"Three flavors of \"unavailable,\" all registered by default","x":"Small contracts only help if every one of them always resolves. A component that injects IShareService must get something on every head, or the browser build throws at runtime…"},"2118":{"u":"/articles/device-capability-abstraction.html#heads-override-after-the-defaults-last-registration-wins","d":"One Blazor UI, two hosts: a device-capability layer that stays resolvable everywhere","k":"The MMCA.Common series","t":"Heads override after the defaults, last registration wins","x":"A default that is always inert would be useless on the platforms that can do the real thing. So heads override, and the mechanism is deliberately boring: plain Add registrations,…"},"2119":{"u":"/articles/device-capability-abstraction.html#the-ninth-package-and-why-it-lives-outside-the-solution","d":"One Blazor UI, two hosts: a device-capability layer that stays resolvable everywhere","k":"The MMCA.Common series","t":"The ninth package, and why it lives outside the solution","x":"The native adapters cannot ship in MMCA.Common.UI, because that package must stay WebAssembly-compatible. They ship in MMCA.Common.UI.Maui, the ninth of the framework's nineteen…"},"2120":{"u":"/articles/device-capability-abstraction.html#deep-links-one-funnel-no-translation-table","d":"One Blazor UI, two hosts: a device-capability layer that stays resolvable everywhere","k":"The MMCA.Common series","t":"Deep links: one funnel, no translation table","x":"The most elegant part is navigation. A notification tap, a home-screen app action, an app link, a scanned QR code: all four are native events that need to land on a Blazor route.…"},"2121":{"u":"/articles/device-capability-abstraction.html#trade-offs-honestly","d":"One Blazor UI, two hosts: a device-capability layer that stays resolvable everywhere","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Null-object defaults can mask a missing registration. A MAUI head that forgets UseMauiDeviceCapabilities does not fail fast: it silently resolves the null defaults and loses…"},"2122":{"u":"/articles/device-capability-abstraction.html#apply-this-even-without-mmca","d":"One Blazor UI, two hosts: a device-capability layer that stays resolvable everywhere","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any UI that runs on more than one host: 1. Define one small interface per capability, not an aggregate device service. Each new capability is then additive,…"},"2123":{"u":"/articles/managed-file-storage-avatars.html","d":"Managed file storage: uploads you don't have to trust","k":"The MMCA.Common series","x":"Subtitle: A user uploads a profile photo. It is attacker-controlled bytes with GPS coordinates baked into the metadata and, if you are unlucky, a payload hiding behind a valid…"},"2124":{"u":"/articles/managed-file-storage-avatars.html#why-it-matters","d":"Managed file storage: uploads you don't have to trust","k":"The MMCA.Common series","t":"Why it matters","x":"An avatar is user-generated content on an anonymous-visible surface. That combination is exactly where file-upload bugs turn into incidents. The two that bite hardest are not…"},"2125":{"u":"/articles/managed-file-storage-avatars.html#the-mmca-answer-a-storage-boundary-and-a-re-encode-that-keeps-only-pixels","d":"Managed file storage: uploads you don't have to trust","k":"The MMCA.Common series","t":"The MMCA answer: a storage boundary, and a re-encode that keeps only pixels","x":"The framework had no blob abstraction before this. Anything file-shaped would have been written directly against the Azure SDK inside a module, unusable by the next consumer and…"},"2126":{"u":"/articles/managed-file-storage-avatars.html#trade-offs-honestly","d":"Managed file storage: uploads you don't have to trust","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"ADR-045 owns its rough edges in its Consequences section, and they are real design choices rather than oversights. - The avatars container is public-read by design. Avatar URLs…"},"2127":{"u":"/articles/managed-file-storage-avatars.html#apply-this-even-without-mmca","d":"Managed file storage: uploads you don't have to trust","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any stack and any blob store. The rules are short: 1. Never trust the declared content type or the file extension. Sniff the leading magic bytes and accept…"},"2128":{"u":"/articles/http-api-versioning.html","d":"HTTP API versioning, proven not just claimed","k":"The MMCA.Common series","x":"Subtitle: Most APIs \"support versioning\" by shipping v1.0 and never a second version. Here is a header-based setup that introduces versioning without breaking a single caller,…"},"2129":{"u":"/articles/http-api-versioning.html#why-it-matters","d":"HTTP API versioning, proven not just claimed","k":"The MMCA.Common series","t":"Why it matters","x":"HTTP APIs need a versioning axis of their own: one the caller selects per request, that the service can advertise, and that it can deprecate over time. In a framework whose…"},"2130":{"u":"/articles/http-api-versioning.html#the-mmca-answer-one-registration-one-exemplar-one-fitness-contract","d":"HTTP API versioning, proven not just claimed","k":"The MMCA.Common series","t":"The MMCA answer: one registration, one exemplar, one fitness contract","x":"MMCA.Common standardizes a single header-based setup in MMCA.Common.API, adopts it in every service host through one call, and keeps it exercised by a shared contract test that…"},"2131":{"u":"/articles/http-api-versioning.html#trade-offs-honestly","d":"HTTP API versioning, proven not just claimed","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"ADR-046 is refreshingly candid about what is real today versus what the shape leaves room for. Four things are worth naming, and none of them is a bug. - The class-level version…"},"2132":{"u":"/articles/http-api-versioning.html#apply-this-even-without-mmca","d":"HTTP API versioning, proven not just claimed","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any HTTP stack that will have to evolve a response shape without a flag day: 1. Pick one version reader and put it behind one registration. Header, query, or…"},"2133":{"u":"/articles/feature-flags-cqrs-pipeline.html","d":"Feature Flags in the CQRS Pipeline: Gate Commands, Not Code","k":"The MMCA.Common series","x":"Subtitle: Decoupling release from deploy means shipping code dark and flipping a switch. Here is how MMCA.Common gates commands and queries with feature flags at the outermost…"},"2134":{"u":"/articles/feature-flags-cqrs-pipeline.html#why-it-matters","d":"Feature Flags in the CQRS Pipeline: Gate Commands, Not Code","k":"The MMCA.Common series","t":"Why it matters","x":"A feature flag is a branch in production behavior, and branches that are copy-pasted drift. If the gate lives inside the handler, then whether a feature is on is decided in as…"},"2135":{"u":"/articles/feature-flags-cqrs-pipeline.html#the-mmca-answer-an-outermost-decorator-plus-a-marker-interface","d":"Feature Flags in the CQRS Pipeline: Gate Commands, Not Code","k":"The MMCA.Common series","t":"The MMCA answer: an outermost decorator plus a marker interface","x":"The whole mechanism is two small types plus a registration. The opt-in signal is IFeatureGated (UseCases/Markers/IFeatureGated.cs:10), an interface with exactly one member,…"},"2136":{"u":"/articles/feature-flags-cqrs-pipeline.html#the-second-surface-and-why-disabled-means-404","d":"Feature Flags in the CQRS Pipeline: Gate Commands, Not Code","k":"The MMCA.Common series","t":"The second surface, and why disabled means 404","x":"A feature can be reachable from the CQRS pipeline, but it can also be reachable straight from an MVC action that never dispatches a command. So the same flag name is enforced at…"},"2137":{"u":"/articles/feature-flags-cqrs-pipeline.html#trade-offs-honestly","d":"Feature Flags in the CQRS Pipeline: Gate Commands, Not Code","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- The two surfaces must be kept in agreement by hand. Gating the controller but not the command (or the reverse) leaves a half-protected feature reachable through the entry you…"},"2138":{"u":"/articles/feature-flags-cqrs-pipeline.html#apply-this-even-without-mmca","d":"Feature Flags in the CQRS Pipeline: Gate Commands, Not Code","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any pipeline that already wraps its handlers (MediatR behaviors, an interceptor, your own decorator chain): 1. Make feature gating a marker interface with…"},"2139":{"u":"/articles/field-level-encryption-ef-core.html","d":"Field-Level Encryption in EF Core: AES-GCM for PII Columns","k":"The MMCA.Common series","x":"Subtitle: Transparent database encryption protects the file, then decrypts for anyone who can query it. When a column holds data sensitive enough that a database reader should…"},"2140":{"u":"/articles/field-level-encryption-ef-core.html#why-it-matters","d":"Field-Level Encryption in EF Core: AES-GCM for PII Columns","k":"The MMCA.Common series","t":"Why it matters","x":"The gap between \"the database is encrypted\" and \"this column is protected\" is where compliance findings live. A PII inventory (rubric §30 asks for exactly this: what is…"},"2141":{"u":"/articles/field-level-encryption-ef-core.html#the-mmca-answer-one-value-converter-applied-per-property","d":"Field-Level Encryption in EF Core: AES-GCM for PII Columns","k":"The MMCA.Common series","t":"The MMCA answer: one value converter, applied per property","x":"The mechanism is EncryptedStringConverter, a sealed ValueConverter in the MMCA.Common.Infrastructure.Persistence.Encryption namespace. You attach it to a single property in an EF…"},"2142":{"u":"/articles/field-level-encryption-ef-core.html#the-honest-part-shipped-tested-latent","d":"Field-Level Encryption in EF Core: AES-GCM for PII Columns","k":"The MMCA.Common series","t":"The honest part: shipped, tested, latent","x":"Here is what separates this from a \"look what we built\" post. This converter is not in production. Zero columns across the four repositories are encrypted with it today. The…"},"2143":{"u":"/articles/field-level-encryption-ef-core.html#column-level-crypto-vs-tde-the-trade-you-are-actually-making","d":"Field-Level Encryption in EF Core: AES-GCM for PII Columns","k":"The MMCA.Common series","t":"Column-level crypto vs TDE: the trade you are actually making","x":"TDE and field-level encryption are not competitors; they are different threat models, and the honest framing is when each earns its cost. TDE is cheap, global, and transparent to…"},"2144":{"u":"/articles/field-level-encryption-ef-core.html#trade-offs-honestly","d":"Field-Level Encryption in EF Core: AES-GCM for PII Columns","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Encrypted columns are not queryable. The random nonce makes ciphertext non-deterministic, so there is no equality filter, index seek, sort, or join on an encrypted column.…"},"2145":{"u":"/articles/field-level-encryption-ef-core.html#apply-this-even-without-mmca","d":"Field-Level Encryption in EF Core: AES-GCM for PII Columns","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any EF Core codebase, and to most ORMs with a value-conversion or type-handler hook: 1. Use an EF Core ValueConverter (or your ORM's equivalent) so…"},"2146":{"u":"/articles/security-headers-csp-blazor.html","d":"Security Headers and CSP for Blazor: One Middleware, Every Host","k":"The MMCA.Common series","x":"Subtitle: Every client-facing host must stamp the same hardened response headers, but a Blazor host needs a Content-Security-Policy no static string can express. Here is one…"},"2147":{"u":"/articles/security-headers-csp-blazor.html#why-it-matters","d":"Security Headers and CSP for Blazor: One Middleware, Every Host","k":"The MMCA.Common series","t":"Why it matters","x":"Security response headers are cheap defence-in-depth: X-Content-Type-Options: nosniff stops MIME sniffing, X-Frame-Options and frame-ancestors stop clickjacking, HSTS forces…"},"2148":{"u":"/articles/security-headers-csp-blazor.html#the-mmca-answer-one-middleware-a-pluggable-csp","d":"Security Headers and CSP for Blazor: One Middleware, Every Host","k":"The MMCA.Common series","t":"The MMCA answer: one middleware, a pluggable CSP","x":"MMCA.Common ships a single SecurityHeadersMiddleware (MMCA.Common.Aspire/Security/SecurityHeaders.cs:145) in the MMCA.Common.Aspire.Security layer, registered with…"},"2149":{"u":"/articles/security-headers-csp-blazor.html#the-blazor-policy-built-at-runtime","d":"Security Headers and CSP for Blazor: One Middleware, Every Host","k":"The MMCA.Common series","t":"The Blazor policy, built at runtime","x":"An HTML host opts into the origin-pinned policy by registering its own provider before calling AddCommonSecurityHeaders. Both apps register one shared BlazorCspPolicyProvider…"},"2150":{"u":"/articles/security-headers-csp-blazor.html#adopted-at-both-edges-of-both-apps","d":"Security Headers and CSP for Blazor: One Middleware, Every Host","k":"The MMCA.Common series","t":"Adopted at both edges of both apps","x":"This is not a library that exists and waits to be used. Store and ADC each wire it at both client-facing edges. The Gateway hosts call…"},"2151":{"u":"/articles/security-headers-csp-blazor.html#trade-offs-honestly","d":"Security Headers and CSP for Blazor: One Middleware, Every Host","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- The baseline is complete, not maximal. A default that works for a Blazor and MudBlazor host is a default that carries style-src 'unsafe-inline' (SecurityHeaders.cs:65-67), so…"},"2152":{"u":"/articles/security-headers-csp-blazor.html#apply-this-even-without-mmca","d":"Security Headers and CSP for Blazor: One Middleware, Every Host","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The shape ports to any ASP.NET Core stack, and the idea ports to any web framework: 1. Put the hardened header set in one middleware with strongly-typed, overridable settings,…"},"2153":{"u":"/articles/observability-opentelemetry.html","d":"Observability by Default: OpenTelemetry and Azure Monitor in MMCA","k":"The MMCA.Common series","x":"Subtitle: Auto-instrumentation gives you HTTP and runtime signals for free, but it is blind to the two paths that carry almost all of your framework's own work: the CQRS pipeline…"},"2154":{"u":"/articles/observability-opentelemetry.html#why-it-matters","d":"Observability by Default: OpenTelemetry and Azure Monitor in MMCA","k":"The MMCA.Common series","t":"Why it matters","x":"Observability is the category you do not miss until production, and by then retrofitting it is expensive. The rubric names the bar directly: structured logging with correlation…"},"2155":{"u":"/articles/observability-opentelemetry.html#the-mmca-answer-one-call-one-baseline-on-every-host","d":"Observability by Default: OpenTelemetry and Azure Monitor in MMCA","k":"The MMCA.Common series","t":"The MMCA answer: one call, one baseline, on every host","x":"Every framework-consuming host calls AddServiceDefaults() first in Program.cs, and that method calls ConfigureOpenTelemetry() (Extensions.cs:87, the call at :89) before anything…"},"2156":{"u":"/articles/observability-opentelemetry.html#the-one-that-pays-for-itself-filtering-idle-poll-spans","d":"Observability by Default: OpenTelemetry and Azure Monitor in MMCA","k":"The MMCA.Common series","t":"The one that pays for itself: filtering idle poll spans","x":"The OutboxProcessor polls every relational outbox table on a recurring cycle (both deployment templates push the interval to 300 seconds precisely so idle polls cost less:…"},"2157":{"u":"/articles/observability-opentelemetry.html#the-other-half-which-signal-wakes-a-human","d":"Observability by Default: OpenTelemetry and Azure Monitor in MMCA","k":"The MMCA.Common series","t":"The other half: which signal wakes a human","x":"Everything above is emission. Which of those signals pages somebody, at what threshold, at what severity, is a separate decision, and for a long time it lived in the deployment…"},"2158":{"u":"/articles/observability-opentelemetry.html#trade-offs-honestly","d":"Observability by Default: OpenTelemetry and Azure Monitor in MMCA","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- The literal names can drift. The meter, activity-source, and poll-span names are duplicated as string literals across the Aspire package (OutboxPollFilterProcessor.cs:26-29 and…"},"2159":{"u":"/articles/observability-opentelemetry.html#apply-this-even-without-mmca","d":"Observability by Default: OpenTelemetry and Azure Monitor in MMCA","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any OpenTelemetry stack, with or without Aspire: 1. Put the telemetry baseline in one shared bootstrap every host calls, not in each app. One place to change…"},"2160":{"u":"/articles/saga-compensation-reconciliation.html","d":"Undo Is a Feature: Saga Compensation and the Reconciliation Backstop","k":"The MMCA.Common series","x":"Subtitle: A cancelled order has to give back stock that a transaction closed minutes ago already took, and a payment confirmation that never arrives strands the order forever.…"},"2161":{"u":"/articles/saga-compensation-reconciliation.html#why-it-matters","d":"Undo Is a Feature: Saga Compensation and the Reconciliation Backstop","k":"The MMCA.Common series","t":"Why it matters","x":"The rubric has two categories aimed straight at this. §6 (CQRS and Event-Driven Design, heading at ArchitectureEvaluationCriteria.md:229) asks for idempotent consumers (:238) and…"},"2162":{"u":"/articles/saga-compensation-reconciliation.html#the-mmca-answer-compensation-is-a-handler-not-a-branch-in-the-command","d":"Undo Is a Feature: Saga Compensation and the Reconciliation Backstop","k":"The MMCA.Common series","t":"The MMCA answer: compensation is a handler, not a branch in the command","x":"The command handler does one thing, and it does not even own the plumbing for that. CancelOrderHandler derives from MutateEntityHandlerBase (CancelOrderHandler.cs:27-31), so the…"},"2163":{"u":"/articles/saga-compensation-reconciliation.html#the-marker-idempotency-as-a-database-invariant","d":"Undo Is a Feature: Saga Compensation and the Reconciliation Backstop","k":"The MMCA.Common series","t":"The marker: idempotency as a database invariant","x":"Here is the part worth stealing even if you never touch this framework. At-least-once delivery means the compensating handler will eventually run twice. If restoring stock is not…"},"2164":{"u":"/articles/saga-compensation-reconciliation.html#redelivery-is-the-retry-mechanism-so-throwing-is-correct","d":"Undo Is a Feature: Saga Compensation and the Reconciliation Backstop","k":"The MMCA.Common series","t":"Redelivery is the retry mechanism, so throwing is correct","x":"A compensating handler needs no retry loop of its own, because it already sits on one. When in-process dispatch fails, the domain-event interceptor logs the failure and signals…"},"2165":{"u":"/articles/saga-compensation-reconciliation.html#the-inbound-edge-a-webhook-is-a-delivery-contract-not-an-api-call","d":"Undo Is a Feature: Saga Compensation and the Reconciliation Backstop","k":"The MMCA.Common series","t":"The inbound edge: a webhook is a delivery contract, not an API call","x":"Everything so far is about a message leaving the process (ADR-003, at-least-once out) or a broker redelivery arriving at a consumer (ADR-021, dedup on the way in). The Stripe…"},"2166":{"u":"/articles/saga-compensation-reconciliation.html#the-backstop-a-periodic-sweep-against-the-provider","d":"Undo Is a Feature: Saga Compensation and the Reconciliation Backstop","k":"The MMCA.Common series","t":"The backstop: a periodic sweep against the provider","x":"Compensation answers \"step two failed.\" It does not answer \"step three never reported back.\" A webhook that is never delivered, or that arrives and is rejected because the…"},"2167":{"u":"/articles/saga-compensation-reconciliation.html#trade-offs-honestly","d":"Undo Is a Feature: Saga Compensation and the Reconciliation Backstop","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Adoption is one module, and the record says so. This pattern lives in MMCA.Store's Sales module only: the two saga handlers and the one reconciliation sweep above. MMCA.ADC and…"},"2168":{"u":"/articles/saga-compensation-reconciliation.html#apply-this-even-without-mmca","d":"Undo Is a Feature: Saga Compensation and the Reconciliation Backstop","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The mechanism ports to any stack that has events, a database, and an external dependency: 1. Put the compensating action in its own handler, triggered by the event the first step…"},"2169":{"u":"/articles/governed-llm-boundary.html","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","x":"Subtitle: A chat completion is an unbounded, metered, non-deterministic call to somebody else's server, and most codebases make it by newing up a vendor client in the middle of a…"},"2170":{"u":"/articles/governed-llm-boundary.html#why-it-matters","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","t":"Why it matters","x":"Rubric §16, AI-Native Application Architecture, asks exactly one question of a product feature that calls a model: is that dependency governed like any other external system,…"},"2171":{"u":"/articles/governed-llm-boundary.html#the-mmca-answer-one-optional-package-and-the-call-is-a-pipeline","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","t":"The MMCA answer: one optional package, and the call is a pipeline","x":"MMCA.Common.AI is one of the nineteen published framework packages (MMCA.Common/FACTS.md:19, listed at :22, framework v1.205.0 at :14) and it is optional: nothing in the…"},"2172":{"u":"/articles/governed-llm-boundary.html#off-by-absence-not-off-by-flag","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","t":"Off by absence, not off by flag","x":"AiSettings (AiSettings.cs:31) binds one configuration section, Ai (:34), and carries the whole surface: Enabled (:48), Provider (:51), Model (:59), ApiKey (:70), MaxOutputTokens…"},"2173":{"u":"/articles/governed-llm-boundary.html#bounds-four-of-them-on-both-paths","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","t":"Bounds: four of them, on both paths","x":"BoundedChatClient (BoundedChatClient.cs:51) is a DelegatingChatClient and enforces four bounds, each applied to the buffered and the streaming path (the list is documented on the…"},"2174":{"u":"/articles/governed-llm-boundary.html#guardrails-the-framework-ships-the-extension-point-not-the-policy","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","t":"Guardrails: the framework ships the extension point, not the policy","x":"IChatGuardrail (IChatGuardrail.cs:20) has two methods: InspectRequestAsync (:27-30) and InspectResponseAsync (:37-40), each answering with a GuardrailVerdict. An application…"},"2175":{"u":"/articles/governed-llm-boundary.html#prompts-a-versioned-contract-the-framework-hashes","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","t":"Prompts: a versioned contract the framework hashes","x":"PromptContract (PromptContract.cs:23) is a record of four things: name, version, model and system prompt. Its Hash (:46) is the lowercase hex SHA-256 of those four joined with a…"},"2176":{"u":"/articles/governed-llm-boundary.html#metering-two-counters-one-histogram-one-name","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","t":"Metering: two counters, one histogram, one name","x":"AiUsageMeter (AiUsageMeter.cs:20) publishes mmca.ai.inputtokens (:29) and mmca.ai.outputtokens (:32) on the meter MMCA.Common.AI (:26), plus a mmca.ai.call.duration histogram in…"},"2177":{"u":"/articles/governed-llm-boundary.html#the-layering-rule-that-keeps-all-of-this-at-one-place","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","t":"The layering rule that keeps all of this at one place","x":"A governed boundary that leaks is not a boundary. There are exactly two ways this one leaks, and AiDependencyIsolationTestsBase (AiDependencyIsolationTestsBase.cs:19) is the…"},"2178":{"u":"/articles/governed-llm-boundary.html#trade-offs-honestly","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Two of the seven §16 criteria are absent from the framework on purpose. An evaluation gate in CI and retrieval as data architecture are feature-side, and ADR-120 records both…"},"2179":{"u":"/articles/governed-llm-boundary.html#apply-this-even-without-mmca","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The shape ports to any stack with a DI container and a vendor SDK. Microsoft.Extensions.AI makes it cheap in .NET because IChatClient and DelegatingChatClient already exist, but…"},"2180":{"u":"/articles/durable-internal-commands.html","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","x":"Subtitle: A handler that wants something to happen after its transaction commits has four mechanisms to choose from, and picking the wrong one loses the work or pays for…"},"2181":{"u":"/articles/durable-internal-commands.html#why-it-matters","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"Why it matters","x":"The rubric asks for this twice, from two directions. §6 (CQRS and Event-Driven Design) wants reads and writes separated through a dispatcher with a real decorator pipeline, an…"},"2182":{"u":"/articles/durable-internal-commands.html#four-mechanisms-one-question","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"Four mechanisms, one question","x":"The framework has four ways to do work later, and they are not interchangeable. The bounded channel (ADR-121) is a Channel singleton with a SingleReader BackgroundService drain,…"},"2183":{"u":"/articles/durable-internal-commands.html#the-mmca-answer-a-deferred-execution-is-the-inline-execution","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"The MMCA answer: a deferred execution IS the inline execution","x":"Here is the decision that makes the rest of it cheap. IInternalCommand is a marker interface deriving from ICommand and nothing else (IInternalCommand.cs:38). A command that opts…"},"2184":{"u":"/articles/durable-internal-commands.html#scheduling-rides-the-callers-unit-of-work","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"Scheduling rides the caller's unit of work","x":"IInternalCommandScheduler exposes ScheduleAsync(command, runAt) and a TimeSpan delay overload, both returning Result (IInternalCommandScheduler.cs:41-57). runAt is \"the earliest…"},"2185":{"u":"/articles/durable-internal-commands.html#the-row-and-what-it-remembers","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"The row, and what it remembers","x":"InternalCommandMessage (InternalCommandMessage.cs:21) is deliberately not an auditable entity, for the same reason OutboxMessage is not: it is framework bookkeeping, not domain…"},"2186":{"u":"/articles/durable-internal-commands.html#a-rename-that-does-not-orphan-the-queue","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"A rename that does not orphan the queue","x":"The row stores the command's assembly-qualified CLR name by default, which means renaming the class, moving its namespace, or moving its assembly orphans every row already…"},"2187":{"u":"/articles/durable-internal-commands.html#the-drain-claim-execute-stamp","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"The drain: claim, execute, stamp","x":"InternalCommandProcessor is a BackgroundService (InternalCommandProcessor.cs:47). Its cycle is the outbox's idiom, borrowed rather than shared. It waits a five-second startup…"},"2188":{"u":"/articles/durable-internal-commands.html#the-worked-example-an-email-that-must-survive-a-deploy","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"The worked example: an email that must survive a deploy","x":"MMCA.Store's Sales module schedules a payment-failure notification. The command is fifteen lines and most of them are documentation: One identifier as the payload, a stable name,…"},"2189":{"u":"/articles/durable-internal-commands.html#the-expensive-case-permission-timeout-and-a-cross-replica-claim","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"The expensive case: permission, timeout and a cross-replica claim","x":"MMCA.ADC's AI session scoring is the other end of the range, and it is the case ADR-121 moved out of the in-process channel. One pass issues one paid Anthropic call per session,…"},"2190":{"u":"/articles/durable-internal-commands.html#operating-it","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"Operating it","x":"A queue with no operator surface is a queue that gets hand-edited in production SQL. IInternalCommandAdministration (IInternalCommandAdministration.cs:17) mirrors…"},"2191":{"u":"/articles/durable-internal-commands.html#trade-offs-honestly","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- One migration per relational data source. The table is in the model of every relational source (SQL Server, PostgreSQL and SQLite after ADR-113), so adopting the queue means…"},"2192":{"u":"/articles/durable-internal-commands.html#apply-this-even-without-mmca","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The mechanism ports to anything with a relational database and a background worker. Six steps, in the order that matters: 1. Write the job to a table inside the caller's…"},"2193":{"u":"/articles/identity-completions.html","d":"Finishing Identity: Second Factor, Email Confirmation and Stored Permission Grants","k":"The MMCA.Common series","x":"Subtitle: A framework that owns sign-in for two deployed apps cannot add a second factor by adding a step. Here is how a TOTP challenge, an email-confirmation gate and…"},"2194":{"u":"/articles/identity-completions.html#why-it-matters","d":"Finishing Identity: Second Factor, Email Confirmation and Stored Permission Grants","k":"The MMCA.Common series","t":"Why it matters","x":"Rubric §11 (Security) is a weight-3 category asking that authentication be centralized and identity flows documented, that authorization be enforced at the right layer rather…"},"2195":{"u":"/articles/identity-completions.html#the-mmca-answer-the-unadopted-path-is-unreachable-not-merely-disabled","d":"Finishing Identity: Second Factor, Email Confirmation and Stored Permission Grants","k":"The MMCA.Common series","t":"The MMCA answer: the unadopted path is unreachable, not merely disabled","x":"AuthenticationServiceBase gained two constructor parameters, both optional and both defaulted to null: ITwoFactorAuthenticator? twoFactor = null and IOptions ?…"},"2196":{"u":"/articles/identity-completions.html#the-step-up-assertion-is-a-claim-and-presence-is-the-whole-test","d":"Finishing Identity: Second Factor, Email Confirmation and Stored Permission Grants","k":"The MMCA.Common series","t":"The step-up assertion is a claim, and presence is the whole test","x":"A challenge that is satisfied has to be recorded somewhere the rest of the system can read. The framework stamps an amr-style claim: AuthClaimTypes.MultiFactor is the literal mfa…"},"2197":{"u":"/articles/identity-completions.html#the-framework-ships-no-user-table-on-purpose","d":"Finishing Identity: Second Factor, Email Confirmation and Stored Permission Grants","k":"The MMCA.Common series","t":"The framework ships no user table, on purpose","x":"ITwoFactorService is stateless cryptography: secrets, provisioning URIs, verification inside a skew window, recovery codes. ITwoFactorStore is the persistence, and the consumer…"},"2198":{"u":"/articles/identity-completions.html#stored-grants-operator-editable-and-structurally-unable-to-deny","d":"Finishing Identity: Second Factor, Email Confirmation and Stored Permission Grants","k":"The MMCA.Common series","t":"Stored grants: operator-editable, and structurally unable to deny","x":"Article 24 covered the compiled permission registry: a frozen role-to-permission map, consulted by the CQRS authorization decorators and the [HasPermission] policy. It has one…"},"2199":{"u":"/articles/identity-completions.html#two-refusals-that-are-worth-more-than-the-feature","d":"Finishing Identity: Second Factor, Email Confirmation and Stored Permission Grants","k":"The MMCA.Common series","t":"Two refusals that are worth more than the feature","x":"StoredPermissionRoleAdministrationService (:45) is the shipped IRoleAdministrationService: it reports each role's compiled and stored permissions and replaces the stored half.…"},"2200":{"u":"/articles/identity-completions.html#the-administration-api-is-two-bases-gated-on-capabilities","d":"Finishing Identity: Second Factor, Email Confirmation and Stored Permission Grants","k":"The MMCA.Common series","t":"The administration API is two bases, gated on capabilities","x":"RolesAdminControllerBase (:61) needs no app-supplied service, because AddStoredPermissionGrants() registers a complete one; an app implements the interface only to change that…"},"2201":{"u":"/articles/identity-completions.html#trade-offs-honestly","d":"Finishing Identity: Second Factor, Email Confirmation and Stored Permission Grants","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Two-factor ships unadopted, and the article will not pretend otherwise. A search across both consumers' Source/ trees this run finds no call to AddTwoFactorAuthentication and…"},"2202":{"u":"/articles/identity-completions.html#apply-this-even-without-mmca","d":"Finishing Identity: Second Factor, Email Confirmation and Stored Permission Grants","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The identity mechanics here are standard. What is worth copying is the shape of the extension. 1. Make the unadopted path unreachable rather than disabled. An optional…"},"2203":{"u":"/articles/series-index.html","d":"The MMCA series: every pattern, one place","k":"The MMCA.Common series","x":"Subtitle: A single map of the whole series. Start at the top if MMCA.Common is new to you, or jump straight to the pattern you came for. --- MMCA.Common is an Apache-2.0 licensed…"},"2204":{"u":"/articles/series-index.html#start-here","d":"The MMCA series: every pattern, one place","k":"The MMCA.Common series","t":"Start here","x":"The thesis and the framing. Read these first if you have not met the framework. - 01 · I open-sourced the enterprise .NET plumbing I never want to rewrite, and graded it against…"},"2205":{"u":"/articles/series-index.html#core-patterns","d":"The MMCA series: every pattern, one place","k":"The MMCA.Common series","t":"Core patterns","x":"The day-one building blocks. Error handling, the domain model, query intent, the handler pipeline, the reliability backbone that ties them together, the compensating handlers…"},"2206":{"u":"/articles/series-index.html#data-and-persistence","d":"The MMCA series: every pattern, one place","k":"The MMCA.Common series","t":"Data and persistence","x":"How the data layer is built so a module owns its own database, and even its own storage engine, and can become its own service later without a rewrite. - 10 ·…"},"2207":{"u":"/articles/series-index.html#auth-the-api-edge-and-cross-cutting","d":"The MMCA series: every pattern, one place","k":"The MMCA.Common series","t":"Auth, the API edge, and cross-cutting","x":"The concerns that sit across every module: authentication between services, authorization (by role, by permission, and by row ownership), password storage, safe retries, caching,…"},"2208":{"u":"/articles/series-index.html#run-extract-and-harden","d":"The MMCA series: every pattern, one place","k":"The MMCA.Common series","t":"Run, extract, and harden","x":"Bring the whole stack up with one command, then cut a module out into its own service, make the running system survive failure, see what it is doing in production, and put…"},"2209":{"u":"/articles/series-index.html#proof-front-end-and-getting-started","d":"The MMCA series: every pattern, one place","k":"The MMCA.Common series","t":"Proof, front end, and getting started","x":"The evidence that the patterns hold (fitness functions, the test pyramid, the erasure pathway), the reusable Blazor UI framework and the internationalization and theming that…"},"2210":{"u":"/articles/series-index.html#where-to-go-next","d":"The MMCA series: every pattern, one place","k":"The MMCA.Common series","t":"Where to go next","x":"If you are evaluating the framework, read 01 then 03 then 41: the thesis, the rubric, and the proof. If you are here to solve a specific problem, the core-patterns and…"},"2211":{"u":"/","d":"Ivan Ball-llovera · Senior Software Architect","k":"Site","x":"Senior Software Architect Ivan Ball-llovera Cloud-native enterprise architecture on the Microsoft stack I design and ship production-grade .NET platforms: modular monoliths that…"},"2212":{"u":"/","d":"Ivan Ball-llovera · Senior Software Architect","k":"Site","t":"Architecture that earns its keep","x":"I am a Senior Software Architect with more than 25 years designing and delivering scalable, cloud-native systems on the Microsoft stack. My focus is Domain-Driven Design, Clean…"},"2213":{"u":"/","d":"Ivan Ball-llovera · Senior Software Architect","k":"Site","t":"The MMCA platform","x":"A production-grade .NET 10 framework and a set of reference apps that demonstrate modern enterprise architecture end to end. It is built as a modular monolith that extracts…"},"2214":{"u":"/","d":"Ivan Ball-llovera · Senior Software Architect","k":"Site","t":"Deep dives on enterprise .NET","x":"A long-form series turning the framework's decisions into teachable patterns, every claim grounded in real source. A few recent pieces: Proof & getting started · No. 53 The…"},"2215":{"u":"/","d":"Ivan Ball-llovera · Senior Software Architect","k":"Site","t":"Speaking & giving back","x":"I help run two community-driven Atlanta technology conferences and keep production-grade patterns free and in the open. See talks & community work Organizer & speaker Two Atlanta…"},"2216":{"u":"/resume.html","d":"Résumé","k":"Site","x":"Résumé Ivan Ball-llovera Senior Software Architect 25+ years designing and delivering scalable, cloud-native systems on the Microsoft stack: Domain-Driven Design, Clean…"},"2217":{"u":"/resume.html","d":"Résumé","k":"Site","t":"Professional summary","x":"Senior Software Architect with 25+ years designing and delivering scalable, cloud-native systems on the Microsoft stack. Deep expertise in Domain-Driven Design, Clean…"},"2218":{"u":"/resume.html","d":"Résumé","k":"Site","t":"Core competencies","x":"Architecture & design Domain-Driven Design, Clean Architecture, CQRS, Modular Monolith → Microservices, Event-Driven Architecture, Outbox Pattern, gRPC, API Gateway (YARP),…"},"2219":{"u":"/resume.html","d":"Résumé","k":"Site","t":"Professional experience","x":"Senior Software Engineer · Assurant June 2025 – Present · Architect-level scope: platform, security, and cross-team technical decisions Re-architected the AR.com renters quote…"},"2220":{"u":"/resume.html","d":"Résumé","k":"Site","t":"Featured project · MMCA platform","x":"Personal / open source · github.com/ivanball/MMCA.Common A production-grade .NET 10 reference platform demonstrating modern enterprise architecture end-to-end. The conference…"},"2221":{"u":"/resume.html","d":"Résumé","k":"Site","t":"Education","x":"B.S., Computer Science University of Havana (Faculty of Mathematics), Havana, Cuba (1994 – 1999)"},"2222":{"u":"/resume.html","d":"Résumé","k":"Site","t":"Languages","x":"English · Spanish (bilingual)"},"2223":{"u":"/resume.html","d":"Résumé","k":"Site","t":"Certifications","x":"✓ Azure Administrator Associate (AZ-104, 2025) ✓ Azure AI Fundamentals (AI-900, 2024) ✓ Azure Data Fundamentals (DP-900, 2021) ✓ Azure Fundamentals (AZ-900, 2021) → In progress…"},"2224":{"u":"/resume.html","d":"Résumé","k":"Site","t":"Professional development","x":"Continuously prototypes emerging technologies, with a current focus on Clean Architecture using .NET 10, Blazor, .NET MAUI, and ASP.NET Core Web API, and on AI-assisted…"},"2225":{"u":"/platform.html","d":"The MMCA Platform","k":"Site","x":"Featured work · Open source The MMCA platform A production-grade .NET 10 platform that demonstrates modern enterprise architecture end to end: an open-source framework of…"},"2226":{"u":"/platform.html","d":"The MMCA Platform","k":"Site","t":"MMCA.Common","x":"A NuGet package framework for building modular-monolith applications with DDD, Clean Architecture, and CQRS, and the extension points to extract a module into its own…"},"2227":{"u":"/platform.html","d":"The MMCA Platform","k":"Site","t":"Three reference applications","x":"The framework is consumed by real apps. Each one exercises the patterns differently, and the conference app ran a live event on Azure. Conference MMCA.ADC A production-deployed…"},"2228":{"u":"/platform.html","d":"The MMCA Platform","k":"Site","t":"From one graph, laptop to cloud","x":"Orchestrated locally with .NET Aspire, then deployed to Azure Container Apps from the same declarative model. The .NET Aspire dashboard: services, databases, and the broker as…"},"2229":{"u":"/platform.html","d":"The MMCA Platform","k":"Site","t":"Architectural styles the codebase commits to","x":"The recurring ideas every repo is built on, summarized from the onboarding primer's orientation chapter. Each is taught in full at its first concrete appearance in the reference…"},"2230":{"u":"/platform.html","d":"The MMCA Platform","k":"Site","t":"A two-axis architecture scorecard","x":"Every repo is scored against a 34-category rubric on two axes, Maturity (how complete the decision is) and Implementation (how well it is realized in code), with evidence and…"},"2231":{"u":"/platform.html","d":"The MMCA Platform","k":"Site","t":"Architecture Decision Records","x":"131 ADRs capture the context and trade-offs behind each cross-cutting pattern, so the design is teachable, not tribal knowledge. Every entry below links to the full record. 001…"},"2232":{"u":"/platform.html","d":"The MMCA Platform","k":"Site","t":"The reference library","x":"The full architecture documentation, maintained in this site's repository as its canonical home and rendered as browsable pages: every Architecture Decision Record, the…"},"2233":{"u":"/platform.html","d":"The MMCA Platform","k":"Site","t":"Use it, read it, or follow along","x":"The framework is Apache 2.0 and published to nuget.org. The fastest way in is the reference app: one module wired end to end through all five layers, with the extraction path…"},"2234":{"u":"/platform.html","d":"The MMCA Platform","k":"Site","t":"Get each deep dive by email","x":"One message per article, no digests and no other mail. Email address Subscribe"},"2235":{"u":"/writing.html","d":"Writing","k":"Site","x":"Writing Deep dives on enterprise .NET A long-form series that turns the MMCA framework's architecture decisions into teachable patterns, every claim grounded in real source. Read…"},"2236":{"u":"/writing.html","d":"Writing","k":"Site","t":"Get each deep dive by email","x":"One message per article, no digests and no other mail. Email address Subscribe"},"2237":{"u":"/speaking.html","d":"Speaking & Community","k":"Site","x":"Speaking & community Talks and giving back For more than 20 years I have been an active contributor to the Microsoft developer communities in Atlanta and South Florida: teaching,…"},"2238":{"u":"/speaking.html","d":"Speaking & Community","k":"Site","t":"Sessions and workshops","x":"Upcoming · Atlanta Developers Conference precon · October 16, 2026 Build an AI-Ready .NET App with an AI Pair: Clean Architecture, MCP, and Agentic Workflows Full-day, hands-on…"},"2239":{"u":"/speaking.html","d":"Speaking & Community","k":"Site","t":"Organizing two Atlanta conferences","x":"I help convene developers in person, giving the local community direct, no-cost access to expert content on the Microsoft platform. Lead organizer Atlanta Cloud + AI Conference…"},"2240":{"u":"/speaking.html","d":"Speaking & Community","k":"Site","t":"User groups","x":"An active participant in Atlanta's Microsoft technology user-group ecosystem, the same community network from which the conferences draw their speakers and attendees. • Atlanta…"},"2241":{"u":"/speaking.html","d":"Speaking & Community","k":"Site","t":"Open source & mentorship","x":"My MMCA framework is Apache-2.0 licensed and documented with architecture decision records, so the patterns are not just usable but teachable. I mentor developers one on one,…"},"2242":{"u":"/speaking.html","d":"Speaking & Community","k":"Site","t":"What I speak on","x":"Sessions and workshops for conferences, user groups, and teams. Clean Architecture & DDD on .NET Modular monolith → microservices The transactional outbox Database-per-service…"},"2243":{"u":"/contact.html","d":"Contact","k":"Site","x":"Contact Let's connect Happy to talk architecture, the MMCA platform, speaking at your conference or user group, or comparing notes on .NET and Azure. The fastest ways to reach…"},"2244":{"u":"/contact.html","d":"Contact","k":"Site","t":"Three places to start","x":"Open source The MMCA platform A .NET 10 framework and three reference apps, graded in the open against a 34-category rubric. See the architecture → Writing Deep dives on…"}},"dirtCount":0,"index":[["✉",{"3":{"2243":1}}],["✓",{"3":{"2223":4}}],["▸",{"3":{"2213":4}}],["↗",{"3":{"2213":1,"2225":2,"2231":1,"2233":5,"2235":1,"2241":1}}],["📚",{"3":{"1876":1,"1885":1,"1940":1,"1949":1,"1978":1,"2027":1}}],["📄",{"3":{"1792":1,"1801":1,"1845":1,"1852":1,"1876":1,"1885":1,"1898":1,"1904":1,"1949":1,"1964":1,"1971":1,"1978":1,"1984":1,"1990":1,"1996":1,"2001":1,"2027":1,"2048":1,"2059":1,"2068":1,"2085":1,"2127":1}}],["⭐",{"3":{"1786":1,"1792":1,"1801":1,"1845":1,"1852":1,"1876":1,"1885":1,"1898":1,"1904":1,"1940":1,"1949":1,"1964":1,"1971":1,"1978":1,"1984":1,"1990":1,"1996":1,"2001":1,"2027":1,"2048":1,"2059":1,"2068":1,"2085":1,"2127":1}}],["★",{"3":{"1778":8}}],["✅",{"3":{"1610":6}}],["−",{"3":{"1582":1,"1583":1,"1584":1}}],["≈",{"3":{"1536":1}}],["÷",{"3":{"1522":4,"1536":2,"1573":4,"1587":4}}],["σ",{"3":{"1522":4,"1531":1,"1532":1,"1536":4,"1573":4,"1582":1,"1583":1,"1587":4}}],["σweight",{"3":{"0":1,"1526":1,"1577":1,"1591":1}}],["≥",{"3":{"1466":1,"1708":1}}],["×4",{"3":{"1577":1,"1591":1}}],["×10",{"3":{"1526":1,"1577":1,"1591":1}}],["×",{"3":{"1455":1,"1536":5,"1582":1,"1583":1,"1584":1,"1593":2,"1677":1,"1797":2}}],["➡",{"3":{"1229":1,"1237":1,"1247":1,"1259":1,"1270":1,"1271":1,"1285":1,"1299":1,"1300":1,"1308":1,"1309":1,"1310":1,"1311":1,"1322":1,"1323":1,"1338":1,"1349":1,"1358":1,"1368":1,"1379":1,"1394":1,"1395":1,"1401":1,"1414":1,"1415":1,"1416":1,"1426":1,"1437":1}}],["⬅",{"3":{"1229":1,"1237":1,"1247":1,"1259":1,"1270":1,"1271":1,"1285":1,"1299":1,"1300":1,"1308":1,"1309":1,"1310":1,"1311":1,"1322":1,"1323":1,"1338":1,"1349":1,"1358":1,"1368":1,"1379":1,"1394":1,"1395":1,"1401":1,"1414":1,"1415":1,"1416":1,"1426":1,"1437":1}}],[">",{"0":{"1198":1},"3":{"2243":1}}],["<=",{"3":{"1531":1,"1582":1,"1587":1,"1634":1}}],["",{"1":{"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1},"3":{"2231":1}}],["enumerationconverter<",{"3":{"1237":1}}],["enumerationconverter",{"2":{"1230":1,"1235":1},"3":{"1198":1,"1199":2,"1203":1,"1207":1,"1230":1,"1235":1,"1237":3}}],["enumerationtests",{"3":{"964":3,"1191":1,"1199":1,"1207":4,"1237":1,"1437":1,"1495":1}}],["enumerationserializationtests",{"2":{"964":1},"3":{"964":3,"966":1,"1199":1,"1207":5,"1237":1}}],["enumerations",{"0":{"1235":1},"3":{"0":1,"964":1,"1049":1,"1051":1,"1230":1,"1285":4,"1310":2,"1435":1,"1437":1}}],["enumerationvalueconvertertests",{"2":{"964":1},"3":{"964":3,"1191":1,"1199":1,"1207":3,"1237":1,"1285":6,"1495":1}}],["enumerationvalueconverter",{"2":{"964":1},"3":{"0":1,"964":2,"965":1,"966":1,"1199":2,"1203":1,"1207":3,"1237":3,"1281":2,"1285":23}}],["enumerationjsonconverterfactory",{"2":{"964":1,"1230":1,"1235":1,"1237":1},"3":{"0":1,"964":1,"1050":1,"1198":1,"1199":5,"1203":1,"1207":1,"1230":1,"1235":1,"1237":6,"1299":1,"1310":2}}],["enumeration",{"2":{"963":1,"964":2,"966":1,"967":2,"968":1,"1049":1,"1051":1,"1052":1,"1235":2},"3":{"0":5,"166":1,"243":1,"441":1,"554":1,"626":1,"649":2,"651":1,"838":1,"853":1,"854":1,"855":1,"961":1,"963":8,"964":14,"965":5,"966":8,"967":4,"968":1,"990":1,"1049":1,"1050":1,"1051":1,"1052":1,"1198":1,"1199":10,"1203":1,"1207":4,"1212":2,"1230":2,"1231":1,"1234":1,"1235":17,"1237":71,"1259":1,"1271":1,"1281":2,"1285":22,"1293":2,"1299":16,"1300":2,"1310":8,"1322":6,"1323":9,"1358":3,"1395":1,"1414":4,"1432":1,"1435":16,"1437":4,"1495":1,"1497":1,"1525":1,"1631":1,"1640":2,"1661":3,"1766":1,"1769":1,"1914":1,"2076":1,"2126":1}}],["enumerator",{"3":{"1222":1,"1229":1,"1424":1,"2176":1}}],["enumerate",{"3":{"166":1,"248":1,"698":1,"773":1,"775":1,"1059":1,"1285":3,"1299":4,"1300":2,"1310":1,"1323":4,"1348":1,"1368":1,"1379":1,"1395":2,"1406":1,"1407":2,"1414":2,"1416":1,"1426":3,"1428":1,"1435":5,"1533":1,"1588":1,"1590":1,"1595":1,"1915":1}}],["enumerated",{"3":{"0":1,"171":1,"184":1,"186":1,"200":1,"526":1,"532":1,"618":1,"837":1,"899":1,"1018":1,"1033":1,"1036":1,"1212":1,"1237":1,"1270":1,"1299":1,"1300":1,"1308":1,"1310":1,"1322":1,"1323":3,"1338":2,"1343":1,"1358":2,"1395":1,"1435":3,"1453":1,"1491":1}}],["enumeratesourcefiles",{"3":{"1435":1}}],["enumerates",{"3":{"0":3,"184":1,"189":1,"243":2,"526":1,"585":2,"626":1,"640":1,"699":1,"765":1,"766":1,"770":1,"990":1,"1259":1,"1271":1,"1285":2,"1299":2,"1300":1,"1310":1,"1323":6,"1338":1,"1395":5,"1412":1,"1414":2,"1416":1,"1426":1,"1435":11,"1446":1,"1454":1,"1685":1,"1850":1,"1961":1,"2199":1}}],["enums",{"1":{"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1},"3":{"0":2,"782":1,"961":1,"963":2,"964":1,"1203":1,"1207":2,"1212":1,"1230":1,"1233":1,"1235":1,"1236":1,"1237":2,"1285":1,"1323":2,"1341":1,"1349":2,"1350":1,"1358":4,"1368":1,"1379":1,"1394":1,"1395":6,"1401":1,"1435":3,"1437":1,"1488":1,"1495":1,"1661":3,"2043":1,"2231":1}}],["enum",{"1":{"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1,"1172":1,"1173":1,"1174":1,"1175":1,"1176":1,"1177":1,"1178":1,"1179":1,"1180":1},"3":{"0":6,"110":1,"136":1,"142":1,"150":1,"164":1,"166":1,"454":2,"682":1,"691":2,"692":1,"717":1,"782":2,"783":2,"784":2,"961":1,"963":4,"964":6,"965":3,"966":1,"968":1,"1034":1,"1049":1,"1051":1,"1089":1,"1091":1,"1094":1,"1172":1,"1174":2,"1175":3,"1176":1,"1177":1,"1178":1,"1203":38,"1205":1,"1207":43,"1212":3,"1217":1,"1219":1,"1225":1,"1229":7,"1230":1,"1233":1,"1235":1,"1236":1,"1237":11,"1247":2,"1259":1,"1270":6,"1281":1,"1284":1,"1285":26,"1299":14,"1308":12,"1309":3,"1310":6,"1311":1,"1319":2,"1322":8,"1323":28,"1338":10,"1340":2,"1341":2,"1343":2,"1348":1,"1349":23,"1352":1,"1358":34,"1368":13,"1379":5,"1383":1,"1389":1,"1392":2,"1393":1,"1394":20,"1395":58,"1401":18,"1409":1,"1414":7,"1416":1,"1421":1,"1426":6,"1429":1,"1430":1,"1435":36,"1437":2,"1495":1,"1537":1,"1630":1,"1649":1,"1685":3,"1726":1,"1748":1,"1765":1,"1804":3,"1806":1,"1929":2,"1931":1,"2043":1,"2196":2,"2231":2}}],["entangled",{"3":{"1435":1}}],["entail",{"3":{"85":1,"264":1}}],["enterprise",{"0":{"2214":1},"1":{"1779":1,"1780":1,"1781":1,"1782":1,"1783":1,"1784":1,"1785":1,"1786":1},"3":{"696":1,"705":1,"713":1,"723":1,"739":1,"1011":1,"1213":1,"1331":1,"1338":3,"1437":2,"1442":1,"1495":1,"1535":2,"1779":2,"2204":1,"2211":1,"2213":1,"2215":1,"2219":2,"2220":1,"2225":1,"2235":1,"2238":1,"2244":1}}],["entering",{"3":{"498":1,"501":1,"1316":1,"1322":1,"1338":1,"1394":4,"1395":1,"1435":2}}],["enter",{"3":{"237":1,"507":1,"513":1,"725":1,"1247":1,"1271":4,"1285":1,"1323":6,"1338":1,"1358":11,"1379":1,"1394":1,"1416":1,"1437":1,"1452":1,"1459":1,"1466":1,"1668":1,"1979":1,"2035":1,"2083":1}}],["entered",{"3":{"139":1,"265":1,"333":1,"512":1,"1271":1,"1319":1,"1322":1,"1349":2,"1352":1,"1358":4,"1394":8,"1416":4,"1443":1,"1630":1,"1640":1,"1764":1}}],["enters",{"3":{"27":1,"235":1,"292":1,"427":1,"545":1,"564":1,"758":1,"1012":1,"1237":1,"1285":3,"1322":1,"1323":2,"1349":3,"1358":5,"1379":1,"1395":2,"1416":1,"1435":3,"1454":2,"2166":1,"2172":1}}],["entropy",{"3":{"907":1,"1299":1,"1310":1}}],["entrancy",{"3":{"1285":2,"1323":3,"1416":1}}],["entrance",{"3":{"657":2,"658":1}}],["entrant",{"3":{"0":1,"988":2,"989":1,"991":1,"1212":1,"1263":1,"1270":1,"1278":1,"1323":1,"1663":1,"1839":1}}],["entra",{"3":{"0":1,"599":2,"600":2,"1454":1,"1466":7,"1476":4}}],["entries",{"2":{"1231":1,"1233":1},"3":{"0":1,"17":4,"19":1,"122":1,"135":1,"137":1,"139":1,"142":3,"145":2,"147":1,"150":1,"166":2,"186":1,"219":1,"245":1,"330":2,"337":1,"372":1,"388":1,"390":1,"395":2,"407":1,"418":1,"423":2,"424":1,"429":1,"497":1,"511":1,"543":2,"585":1,"599":1,"611":1,"624":2,"626":2,"633":4,"669":3,"690":2,"692":2,"698":1,"715":1,"725":1,"733":2,"734":1,"735":1,"737":1,"740":1,"777":1,"819":1,"827":1,"829":1,"839":1,"841":1,"881":1,"896":1,"972":1,"996":1,"998":1,"1006":1,"1007":2,"1018":1,"1073":1,"1074":3,"1075":1,"1078":1,"1116":1,"1161":1,"1225":1,"1231":1,"1233":1,"1237":5,"1241":4,"1242":1,"1247":4,"1259":6,"1267":1,"1270":6,"1271":1,"1273":1,"1274":1,"1276":1,"1278":2,"1284":1,"1285":32,"1299":8,"1300":13,"1309":1,"1310":6,"1311":1,"1319":1,"1322":5,"1323":21,"1338":7,"1349":1,"1350":1,"1358":5,"1368":1,"1379":4,"1393":1,"1394":7,"1395":33,"1414":8,"1416":7,"1426":1,"1435":50,"1437":1,"1442":1,"1446":1,"1455":1,"1462":1,"1466":9,"1489":1,"1491":1,"1495":1,"1598":1,"1630":2,"1649":1,"1650":2,"1651":1,"1660":1,"1700":2,"1707":1,"1726":2,"1730":1,"1773":1,"1850":1,"1897":1,"1911":1,"1915":1,"1917":1,"1919":2,"1921":1,"1923":1,"1926":2,"1987":1,"2000":1,"2084":1,"2096":1,"2102":2,"2110":1,"2111":1,"2149":1,"2164":1}}],["entrypagesize",{"3":{"1395":1}}],["entrypoint",{"2":{"869":1},"3":{"0":1,"869":1,"870":1,"873":1,"1444":5}}],["entry",{"0":{"1376":1,"1420":1},"3":{"0":12,"27":1,"41":1,"59":2,"60":1,"71":1,"73":1,"74":1,"96":1,"121":1,"122":3,"123":1,"130":1,"135":4,"136":3,"137":3,"138":3,"139":5,"140":8,"141":4,"142":6,"145":5,"147":3,"149":1,"157":2,"166":7,"170":3,"171":1,"177":1,"186":2,"200":2,"201":5,"219":2,"243":2,"245":1,"248":4,"249":2,"251":1,"252":3,"254":5,"256":5,"257":4,"258":7,"259":4,"261":1,"265":2,"286":2,"298":1,"301":1,"303":2,"333":1,"335":2,"341":1,"349":1,"359":3,"362":1,"365":1,"366":1,"370":1,"372":1,"386":1,"395":2,"404":1,"408":1,"418":1,"423":4,"424":1,"425":5,"426":4,"427":1,"428":6,"436":1,"443":3,"461":1,"474":1,"478":2,"483":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"499":1,"507":1,"511":1,"543":2,"549":4,"551":2,"558":1,"572":1,"577":1,"585":1,"599":6,"600":1,"607":2,"609":1,"633":3,"636":1,"649":2,"657":1,"668":1,"676":1,"680":1,"698":4,"709":2,"710":1,"720":1,"725":1,"731":1,"732":1,"733":2,"735":1,"740":2,"742":1,"774":1,"776":2,"782":1,"790":1,"792":2,"794":1,"798":1,"821":1,"825":1,"826":1,"830":1,"832":1,"834":1,"838":2,"840":2,"861":2,"881":2,"899":1,"905":2,"909":1,"914":1,"937":1,"941":2,"972":2,"980":1,"988":1,"995":1,"996":2,"997":1,"1018":6,"1019":1,"1034":2,"1036":1,"1059":1,"1065":1,"1067":1,"1069":1,"1070":1,"1073":1,"1074":1,"1082":1,"1116":4,"1133":2,"1135":1,"1136":1,"1160":2,"1161":4,"1162":2,"1163":4,"1173":1,"1175":2,"1179":1,"1192":1,"1199":9,"1203":1,"1207":1,"1212":2,"1223":1,"1229":2,"1231":1,"1237":14,"1241":2,"1242":2,"1247":22,"1259":7,"1262":2,"1263":3,"1264":1,"1265":1,"1267":3,"1270":24,"1271":1,"1273":1,"1274":1,"1276":2,"1279":3,"1283":1,"1284":1,"1285":100,"1297":1,"1299":34,"1300":35,"1303":1,"1306":1,"1308":6,"1309":1,"1310":20,"1311":2,"1315":1,"1319":2,"1322":24,"1323":74,"1325":1,"1331":2,"1337":2,"1338":24,"1346":1,"1349":9,"1355":1,"1356":1,"1358":42,"1368":1,"1369":2,"1372":1,"1374":1,"1378":2,"1379":17,"1388":1,"1391":1,"1394":24,"1395":62,"1401":1,"1407":1,"1413":2,"1414":20,"1415":18,"1416":33,"1417":1,"1421":1,"1424":1,"1426":6,"1428":1,"1431":1,"1433":1,"1435":128,"1436":1,"1437":11,"1440":3,"1442":2,"1446":2,"1453":1,"1454":1,"1455":1,"1459":2,"1463":1,"1464":1,"1466":15,"1473":2,"1479":1,"1487":2,"1488":2,"1489":4,"1506":2,"1525":3,"1560":1,"1576":1,"1584":1,"1588":1,"1590":1,"1595":1,"1598":2,"1626":1,"1630":5,"1636":1,"1639":1,"1640":2,"1647":1,"1650":3,"1651":1,"1652":3,"1654":2,"1661":1,"1672":1,"1674":1,"1683":2,"1684":4,"1685":3,"1686":1,"1692":1,"1701":2,"1702":1,"1707":4,"1718":1,"1721":1,"1726":2,"1727":1,"1728":1,"1736":1,"1745":1,"1746":5,"1747":17,"1748":13,"1777":1,"1789":2,"1820":2,"1848":1,"1850":2,"1870":2,"1897":2,"1903":1,"1904":2,"1907":1,"1908":3,"1911":1,"1914":1,"1915":2,"1916":1,"1917":2,"1919":2,"1921":4,"1922":1,"1929":1,"1932":1,"1933":1,"1961":1,"1973":1,"1988":1,"2007":1,"2022":2,"2042":1,"2068":2,"2082":1,"2084":1,"2095":1,"2111":1,"2120":2,"2137":1,"2141":2,"2149":1,"2171":1,"2227":1,"2231":2}}],["entire",{"3":{"0":1,"47":1,"556":1,"557":1,"564":1,"640":1,"756":1,"759":1,"798":1,"906":1,"926":1,"979":1,"990":1,"1212":1,"1247":1,"1259":2,"1263":1,"1270":7,"1271":1,"1285":3,"1299":4,"1300":2,"1308":4,"1309":2,"1310":6,"1322":6,"1323":8,"1338":4,"1347":1,"1349":6,"1358":26,"1379":2,"1381":1,"1394":9,"1395":6,"1401":2,"1414":5,"1415":5,"1416":10,"1426":2,"1435":14,"1440":1,"1453":1,"1466":3,"1471":1,"1481":1,"1559":1,"1637":1,"1682":1,"1766":1,"1798":1,"1809":1,"1814":1,"1828":2,"1830":1,"1855":1,"1892":1,"1896":1,"1908":1,"1917":1,"1921":1,"1937":1,"1941":1,"2004":1,"2033":1,"2035":1,"2071":1,"2130":1,"2134":1,"2188":1}}],["entirely",{"3":{"0":3,"27":1,"32":1,"70":1,"109":1,"121":1,"126":1,"136":1,"137":1,"243":1,"244":1,"259":1,"295":1,"320":1,"349":1,"361":1,"388":1,"391":1,"427":1,"443":1,"464":1,"508":1,"527":1,"536":1,"556":1,"560":1,"564":1,"607":1,"657":1,"684":1,"724":1,"757":1,"789":1,"790":1,"799":1,"915":1,"926":1,"935":1,"988":1,"1118":1,"1170":1,"1229":2,"1237":1,"1241":1,"1245":1,"1247":4,"1256":1,"1259":4,"1266":1,"1270":7,"1271":2,"1274":1,"1279":1,"1280":1,"1285":14,"1299":13,"1300":3,"1308":3,"1310":6,"1311":1,"1313":1,"1319":2,"1322":4,"1323":12,"1330":1,"1335":1,"1338":8,"1339":1,"1341":2,"1349":2,"1358":21,"1367":1,"1368":2,"1378":1,"1379":2,"1382":1,"1391":1,"1392":1,"1394":11,"1395":11,"1399":1,"1400":1,"1401":7,"1414":9,"1416":12,"1426":2,"1429":1,"1435":23,"1437":3,"1445":1,"1446":1,"1448":1,"1453":1,"1454":3,"1455":1,"1458":1,"1464":2,"1466":3,"1477":1,"1487":1,"1489":1,"1495":2,"1503":1,"1561":1,"1576":1,"1630":1,"1650":1,"1682":1,"1719":1,"1726":1,"1764":1,"1776":1,"1780":1,"1848":1,"1857":1,"1858":1,"1871":1,"1883":1,"1915":1,"1920":1,"1921":1,"1985":1,"1987":1,"1995":1,"1999":1,"2012":1,"2028":1,"2029":1,"2030":1,"2043":1,"2046":1,"2053":1,"2075":1,"2117":1,"2120":1,"2144":1,"2160":1,"2165":1}}],["entitling",{"3":{"1763":1,"1764":1}}],["entitlements",{"3":{"418":1,"422":2,"425":1,"426":1,"1480":1,"1747":1,"1748":1,"1764":2,"1768":1}}],["entitlement",{"3":{"418":1,"420":1,"422":1,"1285":1,"1306":2,"1308":1,"1415":2,"1416":2,"1747":1,"1763":3,"1764":6,"1766":2,"1944":1}}],["entitled",{"3":{"0":1,"1247":1,"1308":2,"1322":1,"1358":7,"1395":3,"1758":1,"1763":1,"1944":1}}],["entitieswithpiiproperties",{"3":{"1435":2,"1488":1}}],["entitieswithpiiimplementanonymizable",{"2":{"725":1},"3":{"39":1,"725":1,"1237":1,"1435":1,"1495":1}}],["entities",{"0":{"1360":1,"1385":1,"1763":1},"1":{"1230":1,"1231":1,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":1},"3":{"0":1,"4":1,"5":1,"11":1,"12":1,"13":1,"21":1,"39":2,"43":1,"47":1,"132":1,"164":1,"166":1,"329":1,"331":2,"341":2,"360":1,"439":1,"469":1,"470":2,"536":1,"549":1,"642":1,"657":2,"698":1,"715":1,"799":1,"881":1,"988":1,"1018":1,"1049":1,"1082":1,"1085":1,"1117":1,"1132":1,"1140":1,"1150":2,"1168":17,"1169":1,"1170":3,"1171":1,"1202":1,"1203":4,"1207":43,"1212":2,"1229":1,"1230":2,"1231":3,"1232":1,"1236":1,"1237":28,"1244":1,"1247":7,"1249":1,"1259":3,"1269":1,"1270":1,"1277":1,"1278":3,"1279":1,"1281":2,"1285":57,"1299":1,"1308":6,"1309":12,"1310":9,"1316":1,"1322":2,"1323":2,"1339":1,"1341":2,"1342":1,"1346":1,"1349":15,"1352":1,"1358":44,"1359":1,"1360":2,"1362":1,"1368":5,"1373":1,"1379":3,"1381":1,"1394":4,"1395":6,"1401":3,"1430":3,"1435":23,"1436":4,"1440":1,"1445":1,"1466":3,"1488":3,"1495":1,"1525":2,"1538":1,"1539":1,"1540":3,"1545":2,"1576":1,"1626":1,"1628":1,"1629":12,"1630":8,"1631":11,"1632":1,"1634":2,"1635":2,"1636":1,"1637":11,"1638":17,"1639":8,"1649":1,"1650":1,"1651":1,"1685":1,"1727":1,"1728":1,"1771":1,"1809":1,"1811":1,"1839":2,"1843":1,"1848":1,"1852":1,"1855":1,"1862":1,"1863":1,"1864":1,"1866":1,"1938":1,"1986":3,"1987":1,"1990":1,"2060":1,"2103":1,"2226":2,"2229":2}}],["entitymodel",{"3":{"1576":2,"1581":2}}],["entitypropertysettersarenonpublic",{"3":{"1435":1}}],["entitypath",{"3":{"1247":1}}],["entityquotasapplied",{"3":{"1322":2}}],["entityqueryparameterstests",{"3":{"1199":1,"1207":3}}],["entityqueryparameters",{"2":{"333":1,"1243":1,"1244":1,"1245":1,"1814":1},"3":{"333":1,"1199":12,"1203":1,"1207":2,"1243":4,"1244":1,"1245":1,"1247":20,"1358":1,"1495":2,"1814":1}}],["entityquerypipelinetests",{"3":{"1199":1,"1207":3}}],["entityquerypipelineorderingtests",{"3":{"1199":1,"1207":3,"1435":1,"1437":2}}],["entityquerypipeline",{"2":{"330":2,"333":1,"337":1,"740":1,"1245":1,"1309":1,"1816":1,"1987":1,"1990":1},"3":{"330":9,"331":1,"332":1,"333":2,"337":3,"549":3,"552":2,"740":2,"741":1,"744":1,"1199":9,"1203":1,"1207":2,"1229":2,"1243":42,"1245":1,"1247":19,"1262":1,"1278":2,"1285":8,"1309":17,"1495":2,"1576":1,"1814":1,"1816":1,"1987":2,"1990":1}}],["entityqueryservicetests",{"3":{"1199":1,"1207":7,"1247":1,"1435":1,"1585":1}}],["entityqueryservicecontracttests",{"3":{"1199":1,"1207":5}}],["entityqueryserviceresolutiontests",{"3":{"1199":1,"1207":6,"1247":1,"1437":1}}],["entityqueryserviceprojectiontests",{"3":{"1199":1,"1207":6,"1247":1,"1437":1}}],["entityqueryservice",{"2":{"333":2,"337":1,"543":1,"552":1,"1269":1,"1497":1,"1816":1,"1990":1},"3":{"331":5,"332":1,"333":3,"336":3,"337":1,"543":1,"545":4,"549":2,"552":1,"1199":15,"1203":1,"1207":2,"1229":5,"1244":49,"1247":24,"1269":1,"1270":5,"1285":4,"1308":11,"1309":8,"1310":2,"1353":1,"1358":37,"1395":4,"1435":3,"1437":1,"1495":2,"1497":1,"1585":1,"1814":2,"1816":1,"1987":1,"1990":1}}],["entityname",{"3":{"1285":3,"1310":2,"1323":2,"1394":4}}],["entitytenantid",{"3":{"1285":3}}],["entitytypeof",{"3":{"1435":2}}],["entitytypename",{"3":{"1285":1}}],["entitytypeextensionstests",{"3":{"1199":1,"1207":4,"1237":2}}],["entitytypeextensions",{"2":{"980":1,"1214":1,"1230":1,"1236":1,"1237":1},"3":{"980":3,"1199":1,"1203":1,"1207":2,"1208":1,"1214":1,"1230":1,"1236":2,"1237":9,"1281":1,"1285":3,"1349":6}}],["entitytype",{"3":{"715":3,"980":1,"1208":1,"1237":2,"1270":2,"1285":14,"1322":1,"1435":1}}],["entitytypebuilder",{"2":{"658":1,"660":1,"1036":1},"3":{"658":1,"660":1,"1036":1,"1285":12,"1368":3,"1395":10,"1414":1}}],["entitytypebuilderextensions",{"2":{"655":1},"3":{"655":1,"657":2,"658":1,"1199":1,"1203":1,"1207":2,"1237":2,"1281":2,"1285":12,"1495":1}}],["entitytypeconfigurationbasetests",{"3":{"1199":1,"1207":8,"1285":1}}],["entitytypeconfigurationbase",{"2":{"1855":1},"3":{"1199":4,"1203":1,"1207":2,"1212":1,"1281":2,"1285":15,"1368":2,"1855":1}}],["entitytypeconfigurationsqlite",{"2":{"166":1,"1281":1,"1721":1,"1726":1,"1855":1},"3":{"166":1,"1199":11,"1203":1,"1207":2,"1281":2,"1285":6,"1721":1,"1726":1,"1855":1}}],["entitytypeconfigurationsqlserver",{"2":{"166":1,"470":1,"1212":1,"1281":1,"1360":1,"1411":1,"1650":1,"1848":1,"1855":1,"1856":1},"3":{"166":1,"470":1,"1199":38,"1203":1,"1207":2,"1212":1,"1281":2,"1285":12,"1322":1,"1358":1,"1360":2,"1368":25,"1395":12,"1411":1,"1414":2,"1650":1,"1848":1,"1855":1,"1856":1}}],["entitytypeconfigurationcosmos",{"2":{"166":1,"1281":1,"1855":1,"1856":1},"3":{"166":1,"1199":1,"1203":1,"1207":2,"1281":2,"1285":10,"1855":1,"1856":1}}],["entitytypeconfigurationtests",{"2":{"164":1},"3":{"164":1,"1199":1,"1207":5,"1285":2}}],["entitytypeconfiguration",{"2":{"1176":1,"1178":1,"1285":1,"1360":1,"1499":1,"1855":1,"1861":1},"3":{"135":1,"164":2,"166":1,"171":1,"231":1,"889":1,"1004":2,"1007":1,"1034":1,"1175":2,"1176":1,"1178":1,"1199":6,"1203":14,"1207":28,"1212":1,"1237":2,"1281":7,"1285":73,"1299":4,"1303":1,"1308":2,"1322":1,"1360":2,"1368":3,"1435":1,"1495":1,"1499":2,"1576":3,"1855":1,"1861":1}}],["entitytypeconfigurationpostgresql",{"2":{"166":1,"1034":1,"1036":1,"1212":1,"1281":1,"1855":1},"3":{"0":1,"166":1,"1034":3,"1036":1,"1199":4,"1203":1,"1207":2,"1212":1,"1281":2,"1285":5,"1495":1,"1855":1}}],["entityfilter",{"3":{"1285":2}}],["entityframeworkqueryableextensions",{"3":{"1285":2}}],["entityframeworkcore",{"2":{"401":1,"633":1,"1034":1,"1035":1,"1036":1,"1213":1,"1310":1,"1368":2,"1435":2,"1684":1,"1685":1,"1721":1,"1726":1,"1727":1,"1814":1,"1864":1,"2042":1,"2104":1},"3":{"0":1,"401":1,"633":1,"1034":1,"1035":1,"1036":1,"1213":1,"1285":15,"1309":2,"1310":2,"1322":1,"1338":1,"1368":20,"1435":9,"1488":2,"1684":1,"1685":2,"1721":1,"1726":1,"1727":1,"1814":1,"1864":2,"2042":1,"2104":1}}],["entityfullname",{"3":{"1285":3}}],["entityentry",{"3":{"1259":2,"1285":7}}],["entityexportauthorizationtests",{"3":{"1199":1,"1207":2,"1379":1,"1436":1,"1437":2,"1486":1}}],["entityhandlerbasetests",{"3":{"1207":18}}],["entitywithoutgeneratedid",{"3":{"1199":2,"1207":1}}],["entitywithgeneratedid",{"3":{"1199":2,"1207":1}}],["entitywithnavigation",{"3":{"1199":2,"1207":1}}],["entitybuilderbase",{"3":{"1199":9,"1207":2,"1427":1,"1428":2,"1435":8,"1487":2,"1670":1}}],["entitysettypenames",{"3":{"1435":1}}],["entityservicebasecachingtests",{"3":{"1199":1,"1207":4,"1323":1}}],["entityservicebasetests",{"2":{"883":1},"3":{"883":1,"1199":1,"1207":5,"1323":1}}],["entityservicebaseidempotencyretrytests",{"3":{"881":1,"1199":1,"1207":5,"1323":1,"1437":2}}],["entityservicebase",{"2":{"258":1,"259":1,"341":1,"833":1,"879":1,"881":1,"884":1,"1227":1,"1381":1,"1668":1,"2073":1,"2079":1},"3":{"0":2,"243":4,"245":1,"258":3,"259":2,"261":2,"341":2,"345":1,"827":2,"833":1,"879":1,"881":10,"884":2,"1199":13,"1203":1,"1207":2,"1212":1,"1227":2,"1229":1,"1299":7,"1310":4,"1323":39,"1349":1,"1379":1,"1381":1,"1394":47,"1395":1,"1437":1,"1525":1,"1576":1,"1590":1,"1652":1,"1667":1,"1668":1,"2073":2,"2079":1}}],["entitystate",{"2":{"1082":1},"3":{"1082":1,"1259":1}}],["entityid",{"3":{"782":1,"1249":1,"1259":2,"1265":1,"1270":5,"1349":5,"1358":24,"1398":1,"1401":4,"1414":2,"1630":1,"1637":1,"1639":1}}],["entitykey",{"3":{"715":3,"1270":2,"1285":7,"1322":1}}],["entitydatasourceregistrytests",{"3":{"1199":1,"1207":14,"1285":10}}],["entitydatasourceregistry",{"2":{"46":1,"166":1,"171":1,"1033":1,"1279":1,"1283":1,"1320":1,"1360":1,"1789":1,"1848":1,"1852":1,"1856":1},"3":{"46":1,"166":2,"171":2,"1004":1,"1033":1,"1199":8,"1203":1,"1207":3,"1212":1,"1259":1,"1279":3,"1283":1,"1285":64,"1299":2,"1320":1,"1322":4,"1325":1,"1338":1,"1360":1,"1437":1,"1789":1,"1848":1,"1852":1,"1856":1}}],["entitycollection",{"3":{"1308":1,"1310":2,"1358":6,"1395":2,"1414":1}}],["entityconventiontests",{"3":{"1168":2,"1199":1,"1207":2,"1435":4,"1488":1,"1525":1,"1534":1,"1576":1,"1581":1}}],["entityconventiontestsbase",{"2":{"1168":1},"3":{"0":1,"1168":4,"1199":2,"1207":2,"1435":6,"1488":1,"1525":1,"1581":1,"1653":1}}],["entityconfigurationoptionstests",{"3":{"1199":1,"1207":2}}],["entityconfigurationoptions",{"3":{"1199":7,"1203":1,"1207":2,"1281":2,"1285":8}}],["entityconfiguration",{"3":{"470":1,"536":1,"657":3,"658":1,"690":3,"693":2,"889":1,"1004":2,"1026":1,"1082":2,"1116":1,"1203":30,"1207":60,"1212":1,"1237":3,"1285":16,"1349":9,"1358":13,"1360":2,"1368":44,"1395":31,"1397":2,"1400":1,"1401":1,"1411":1,"1414":3,"1435":1,"1684":1,"1685":1,"1853":1,"1855":1}}],["entitycontrollerbasetests",{"3":{"1199":1,"1207":5,"1310":1}}],["entitycontrollerbasereadspecificationtests",{"2":{"1437":1},"3":{"1199":1,"1207":9,"1310":2,"1437":2}}],["entitycontrollerbaseexporttests",{"3":{"1199":1,"1207":11,"1310":1,"1437":2}}],["entitycontrollerbaseexportcolumntests",{"3":{"1199":1,"1207":1}}],["entitycontrollerbaseetagtests",{"3":{"1199":1,"1207":8,"1310":1,"1437":2}}],["entitycontrollerbase",{"2":{"325":1,"337":1,"341":1,"345":1,"739":1,"744":1,"1049":1,"1245":1,"1701":1,"1729":1,"1873":1,"1987":1,"1990":1,"1994":1},"3":{"0":2,"318":1,"325":1,"330":15,"331":1,"332":1,"334":1,"337":2,"341":2,"345":1,"453":1,"455":1,"674":2,"739":1,"740":3,"741":10,"743":2,"744":2,"1049":2,"1055":1,"1199":30,"1203":1,"1207":2,"1212":2,"1229":5,"1245":7,"1247":1,"1299":4,"1310":44,"1322":3,"1358":8,"1370":1,"1371":3,"1379":24,"1435":1,"1437":1,"1495":1,"1574":1,"1576":3,"1581":1,"1585":1,"1650":1,"1665":1,"1701":1,"1729":1,"1873":1,"1987":1,"1990":1,"1994":6}}],["entitycsvexporter",{"2":{"744":1},"3":{"741":13,"743":3,"744":4,"1199":2,"1203":1,"1207":2,"1229":1,"1310":25,"1495":1}}],["entitychangedeventtests",{"3":{"782":1,"1199":1,"1207":4}}],["entitychangedevent",{"2":{"780":1,"784":1,"1249":1,"1339":1,"1344":1,"1664":1},"3":{"0":1,"780":1,"782":6,"784":1,"964":1,"1199":12,"1203":1,"1207":2,"1212":1,"1237":5,"1249":3,"1259":10,"1339":1,"1344":1,"1349":31,"1650":1,"1664":1,"1768":1}}],["entity",{"0":{"1231":1,"1243":1,"1279":1,"1281":1,"1361":1,"1856":1},"1":{"327":1,"328":1,"329":1,"330":1,"331":1,"332":1,"333":1,"334":1,"335":1,"336":1,"337":1,"779":1,"780":1,"781":1,"782":1,"783":1,"784":1,"785":1,"786":1,"878":1,"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"919":1,"920":1,"921":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"1238":1,"1239":1,"1240":1,"1241":1,"1242":1,"1243":1,"1244":1,"1245":1,"1246":1,"1247":1,"1853":1,"1854":1,"1855":1,"1856":1,"1857":1,"1858":1,"1859":1,"1860":1,"1861":1,"1985":1,"1986":1,"1987":1,"1988":1,"1989":1,"1990":1},"2":{"697":1,"715":1,"781":1,"923":1,"1048":1,"1232":1,"1273":1,"1866":1,"1954":1,"2162":1},"3":{"0":26,"3":1,"5":2,"7":1,"11":1,"12":1,"39":4,"41":2,"42":1,"81":2,"108":1,"109":1,"117":1,"122":1,"128":1,"145":1,"150":1,"164":1,"165":1,"166":6,"167":1,"168":2,"170":1,"193":1,"265":5,"268":1,"321":1,"327":1,"328":1,"329":5,"330":3,"331":2,"332":3,"333":3,"335":2,"340":1,"341":8,"343":1,"345":1,"346":1,"358":1,"359":2,"361":2,"365":2,"440":1,"455":1,"460":1,"469":4,"470":5,"471":2,"472":1,"473":2,"477":1,"536":1,"544":1,"545":5,"551":1,"642":1,"649":2,"658":1,"697":3,"698":4,"699":1,"700":2,"707":1,"713":1,"714":1,"715":5,"716":2,"717":3,"718":2,"720":1,"725":1,"740":1,"741":1,"745":1,"779":1,"781":4,"782":7,"783":3,"784":3,"798":2,"799":1,"853":1,"878":1,"879":1,"880":2,"881":5,"882":1,"883":1,"889":4,"890":1,"891":4,"899":1,"905":7,"906":1,"919":1,"920":1,"922":2,"923":1,"926":3,"932":1,"935":1,"971":1,"988":1,"1026":1,"1033":1,"1034":4,"1035":2,"1042":1,"1048":2,"1049":1,"1050":2,"1052":1,"1060":1,"1074":1,"1083":1,"1115":1,"1117":1,"1134":1,"1140":1,"1150":4,"1151":1,"1152":3,"1153":1,"1154":1,"1155":2,"1167":4,"1168":4,"1169":1,"1170":2,"1175":1,"1191":1,"1192":3,"1193":1,"1200":1,"1201":1,"1202":4,"1203":4,"1212":24,"1213":1,"1218":1,"1229":3,"1230":3,"1231":8,"1232":6,"1233":1,"1234":2,"1236":3,"1237":76,"1238":5,"1239":3,"1241":4,"1242":5,"1243":8,"1244":5,"1245":2,"1247":78,"1251":1,"1259":13,"1260":1,"1265":2,"1269":4,"1270":40,"1271":3,"1272":2,"1273":3,"1274":2,"1276":2,"1277":3,"1278":1,"1279":5,"1280":6,"1281":5,"1282":1,"1283":1,"1285":265,"1290":1,"1299":13,"1308":21,"1309":35,"1310":45,"1315":2,"1316":1,"1318":1,"1320":5,"1322":30,"1323":38,"1326":1,"1338":4,"1339":1,"1341":1,"1346":2,"1348":2,"1349":106,"1353":3,"1354":1,"1355":1,"1357":6,"1358":321,"1359":1,"1360":5,"1361":3,"1362":3,"1368":44,"1370":2,"1373":1,"1379":15,"1380":1,"1381":1,"1382":1,"1383":1,"1386":1,"1390":1,"1394":62,"1395":59,"1400":2,"1401":22,"1402":1,"1408":1,"1414":20,"1428":2,"1430":1,"1435":91,"1436":1,"1437":6,"1439":1,"1454":3,"1466":3,"1486":2,"1487":2,"1488":5,"1495":4,"1496":1,"1499":1,"1505":1,"1509":1,"1525":3,"1534":1,"1576":9,"1590":2,"1624":1,"1626":2,"1629":8,"1630":15,"1631":5,"1632":5,"1633":8,"1634":3,"1636":4,"1637":10,"1638":30,"1639":11,"1640":15,"1648":2,"1649":1,"1650":3,"1651":1,"1652":1,"1653":1,"1659":3,"1661":4,"1663":3,"1667":1,"1670":1,"1683":1,"1685":4,"1699":1,"1703":1,"1719":2,"1722":1,"1723":1,"1726":6,"1727":2,"1728":1,"1733":1,"1746":2,"1763":1,"1764":2,"1766":1,"1768":1,"1773":2,"1774":1,"1778":1,"1782":1,"1789":2,"1802":1,"1807":1,"1809":6,"1810":2,"1814":7,"1815":1,"1816":4,"1823":1,"1831":1,"1832":1,"1838":3,"1846":1,"1848":12,"1849":1,"1850":5,"1852":2,"1853":8,"1854":3,"1855":6,"1856":1,"1857":3,"1858":1,"1859":3,"1860":4,"1861":5,"1864":6,"1865":1,"1866":3,"1867":2,"1868":1,"1870":4,"1871":1,"1876":2,"1882":1,"1902":1,"1907":1,"1947":1,"1950":1,"1952":6,"1953":4,"1954":5,"1955":1,"1956":1,"1957":1,"1958":2,"1984":2,"1985":4,"1986":2,"1987":4,"1989":5,"1990":6,"2038":1,"2051":1,"2052":2,"2058":1,"2062":3,"2063":1,"2067":1,"2089":1,"2098":1,"2103":1,"2109":1,"2125":1,"2141":2,"2144":1,"2162":1,"2163":1,"2185":1,"2206":1,"2207":1,"2219":1,"2229":5,"2231":12}}],["endian",{"3":{"1478":1}}],["endipaddress",{"3":{"1466":1}}],["endings",{"3":{"1091":1,"1299":1,"1323":1,"1425":1,"1426":1,"1435":1,"1437":1,"2175":1,"2179":1}}],["ending",{"3":{"0":4,"53":1,"905":2,"1168":1,"1299":2,"1323":1,"1332":1,"1358":2,"1368":1,"1424":1,"1435":2,"1437":1,"1630":1,"1988":1,"2009":1,"2156":1}}],["endless",{"3":{"1394":1}}],["endlocal",{"3":{"1349":1}}],["endexclusiveutc",{"3":{"1349":1}}],["endedit",{"3":{"1323":2,"1383":1,"1394":22}}],["endedly",{"3":{"0":1,"468":1}}],["ended",{"3":{"0":1,"499":1,"797":1,"1012":1,"1049":1,"1075":1,"1212":1,"1247":1,"1299":1,"1310":1,"1347":1,"1349":2,"1392":2,"1394":4,"1395":1,"1401":1,"1416":3,"1491":1,"1749":1,"2028":1,"2082":1}}],["endutc",{"3":{"1349":2,"1394":1,"1395":1}}],["enddateselector",{"3":{"1358":1}}],["enddate",{"2":{"1629":1},"3":{"1347":1,"1349":8,"1352":1,"1358":13,"1368":1,"1374":1,"1394":7,"1395":2,"1629":3,"1630":5,"1634":2,"1639":1}}],["endtimeselector",{"3":{"1358":2}}],["endtime",{"3":{"1341":1,"1349":3,"1358":11,"1368":2,"1394":3}}],["endcaptureexclusion",{"2":{"1274":1},"3":{"1274":1,"1285":2}}],["endobject",{"3":{"1229":2}}],["endsattime",{"3":{"1394":1}}],["endsatdate",{"3":{"1394":1}}],["endsatlocal",{"3":{"1349":1,"1395":1}}],["endsat",{"3":{"1349":5,"1358":14,"1361":1,"1368":5,"1394":7,"1395":3,"1525":1,"1629":2,"1630":9,"1634":2,"1638":1,"1639":2}}],["endsatutc",{"3":{"1299":2,"1349":1}}],["endswith",{"2":{"1006":1},"3":{"1006":1,"1247":1,"1358":1}}],["ends",{"3":{"0":2,"10":2,"109":1,"110":1,"113":1,"117":1,"150":1,"166":1,"295":1,"341":2,"408":1,"413":1,"500":1,"538":1,"555":1,"675":1,"744":1,"757":1,"908":1,"1018":1,"1027":1,"1117":1,"1126":1,"1234":1,"1237":2,"1241":1,"1243":1,"1246":2,"1247":2,"1259":1,"1262":2,"1270":3,"1271":2,"1280":2,"1285":4,"1299":12,"1300":1,"1308":1,"1309":8,"1310":6,"1311":2,"1315":1,"1322":4,"1323":12,"1326":1,"1338":4,"1349":4,"1358":16,"1368":4,"1372":1,"1374":1,"1379":6,"1394":5,"1395":6,"1401":7,"1414":3,"1415":1,"1416":11,"1422":1,"1426":4,"1435":14,"1437":3,"1440":1,"1444":1,"1452":1,"1454":1,"1459":1,"1466":2,"1474":2,"1481":2,"1491":1,"1612":1,"1637":1,"1638":1,"1700":1,"1789":1,"1848":1,"1857":1,"1864":3,"1865":1,"1874":1,"1883":1,"1901":1,"1932":1,"1979":1,"1982":1,"1983":1,"1984":1,"1986":1,"1987":1,"2016":1,"2023":1,"2120":1,"2124":1,"2231":1}}],["endpointproperty",{"3":{"1338":1}}],["endpointprefix",{"2":{"203":1},"3":{"0":1,"15":1,"25":1,"198":2,"203":1,"1322":6,"1664":1}}],["endpointreference",{"3":{"1338":5}}],["endpointkey",{"3":{"1338":5}}],["endpointname",{"3":{"1327":1,"1338":6}}],["endpoint=sb",{"3":{"1338":1}}],["endpoint=",{"3":{"1322":1}}],["endpointmetadata",{"2":{"1299":1},"3":{"1299":1}}],["endpointfeaturestub",{"3":{"1199":2,"1207":1}}],["endpointcultureappliertests",{"3":{"1199":1,"1207":2,"1437":1}}],["endpointcultureapplier",{"3":{"265":1,"1199":4,"1203":1,"1207":2,"1323":17,"1416":1,"1437":1,"1495":1}}],["endpointdatasource",{"3":{"175":1}}],["endpointdefaults",{"3":{"91":1,"98":1,"1311":2}}],["endpointswithoutauthorizationattribute",{"3":{"1435":5}}],["endpoints",{"0":{"99":1,"2006":1},"2":{"189":1},"3":{"0":11,"31":1,"33":1,"92":2,"93":1,"95":2,"102":1,"156":1,"157":1,"162":1,"174":2,"175":1,"176":2,"177":1,"178":1,"182":1,"184":1,"185":2,"186":5,"187":1,"189":4,"190":1,"198":1,"207":1,"208":1,"235":2,"239":1,"242":1,"243":1,"279":1,"280":2,"283":1,"287":2,"318":2,"324":1,"329":2,"332":1,"350":1,"386":1,"387":3,"388":1,"390":1,"393":1,"401":1,"402":1,"406":1,"415":1,"418":3,"420":1,"423":1,"424":1,"438":1,"448":1,"508":1,"509":1,"510":1,"583":1,"585":1,"591":1,"625":1,"690":1,"725":1,"743":1,"749":2,"759":1,"790":1,"791":1,"792":1,"825":1,"826":2,"827":1,"830":1,"840":1,"854":1,"860":1,"881":1,"882":1,"903":1,"905":1,"906":1,"910":1,"917":1,"980":1,"1026":1,"1030":1,"1058":1,"1059":2,"1069":1,"1123":1,"1188":1,"1192":1,"1203":5,"1207":16,"1208":9,"1212":1,"1213":2,"1229":2,"1237":1,"1247":1,"1271":1,"1285":2,"1287":2,"1293":1,"1296":2,"1299":13,"1303":1,"1306":2,"1308":6,"1310":58,"1311":1,"1322":7,"1323":17,"1330":1,"1331":1,"1338":11,"1346":1,"1347":2,"1349":6,"1351":1,"1358":18,"1376":1,"1378":1,"1379":13,"1386":1,"1391":1,"1394":12,"1395":38,"1401":5,"1405":1,"1407":1,"1413":3,"1414":21,"1416":2,"1435":22,"1437":5,"1440":1,"1441":2,"1442":2,"1446":1,"1450":1,"1454":2,"1455":1,"1457":6,"1466":4,"1468":1,"1489":1,"1491":1,"1495":1,"1523":1,"1525":3,"1533":1,"1539":1,"1547":1,"1549":1,"1576":2,"1599":1,"1625":1,"1629":2,"1630":8,"1631":10,"1632":1,"1634":3,"1636":1,"1637":3,"1638":9,"1639":13,"1640":4,"1650":3,"1652":1,"1659":1,"1665":2,"1666":2,"1676":1,"1699":1,"1702":1,"1716":1,"1726":1,"1746":1,"1747":3,"1748":3,"1749":1,"1752":4,"1759":1,"1764":4,"1770":3,"1773":1,"1775":3,"1873":1,"1895":1,"1897":2,"1907":2,"1911":1,"1921":3,"1922":1,"1923":1,"1928":1,"1936":1,"1940":1,"1959":1,"1960":2,"1961":2,"1962":1,"1963":1,"1964":2,"1968":1,"1970":1,"1971":1,"1974":1,"1989":2,"1991":1,"1994":4,"1997":2,"1998":1,"1999":1,"2000":1,"2001":2,"2024":1,"2046":1,"2112":1,"2125":1,"2126":1,"2131":2,"2168":1,"2194":1,"2231":1}}],["endpoint",{"0":{"92":2,"95":1,"96":1},"1":{"738":1,"739":1,"740":1,"741":1,"742":1,"743":1,"744":1,"745":1},"2":{"397":1,"1332":1,"1418":1,"2009":1,"2155":1,"2158":1},"3":{"0":24,"24":1,"25":1,"27":2,"31":2,"33":2,"39":2,"42":1,"53":1,"55":2,"59":2,"62":3,"90":1,"91":9,"92":11,"93":5,"94":3,"95":12,"96":9,"97":3,"98":16,"99":6,"100":6,"101":4,"102":3,"103":3,"104":1,"105":1,"111":1,"126":1,"157":1,"159":2,"160":2,"173":2,"175":4,"177":4,"179":1,"185":2,"187":1,"188":1,"189":4,"193":1,"198":5,"203":2,"207":2,"208":1,"209":2,"216":1,"223":1,"225":2,"229":3,"235":3,"243":3,"245":1,"265":3,"281":1,"317":2,"318":2,"321":1,"324":1,"332":2,"333":1,"335":1,"342":1,"350":1,"378":1,"389":1,"390":2,"391":1,"397":1,"415":3,"418":1,"435":1,"448":1,"453":1,"459":1,"507":1,"510":1,"517":2,"520":1,"535":1,"549":1,"599":1,"640":1,"649":2,"651":1,"690":1,"709":1,"717":1,"724":1,"725":4,"726":2,"727":2,"728":1,"732":1,"735":1,"738":1,"739":2,"740":2,"741":2,"742":1,"743":2,"749":3,"751":2,"773":1,"775":1,"789":6,"790":17,"791":4,"792":7,"793":4,"794":3,"795":1,"800":1,"826":2,"827":1,"832":1,"837":1,"838":1,"853":2,"869":1,"881":4,"883":1,"907":1,"914":1,"924":1,"965":1,"990":1,"1016":1,"1018":3,"1019":2,"1020":1,"1021":2,"1051":1,"1059":2,"1060":1,"1066":1,"1067":3,"1068":2,"1069":2,"1091":3,"1099":1,"1116":1,"1124":1,"1184":1,"1185":1,"1188":1,"1192":1,"1212":4,"1218":1,"1225":1,"1229":4,"1237":1,"1241":1,"1247":4,"1259":3,"1263":2,"1264":1,"1269":1,"1270":9,"1271":2,"1278":1,"1285":5,"1287":3,"1296":5,"1297":2,"1299":63,"1300":3,"1301":1,"1303":1,"1306":3,"1308":21,"1310":70,"1311":6,"1319":2,"1321":1,"1322":26,"1323":114,"1325":4,"1327":10,"1330":2,"1331":2,"1332":1,"1333":1,"1336":3,"1338":90,"1346":3,"1349":17,"1352":1,"1358":91,"1365":1,"1368":2,"1371":1,"1372":1,"1374":1,"1378":4,"1379":20,"1381":1,"1385":1,"1387":1,"1388":3,"1389":2,"1394":58,"1395":104,"1399":3,"1401":26,"1405":1,"1407":1,"1408":2,"1409":1,"1412":1,"1414":58,"1415":6,"1416":12,"1418":4,"1419":2,"1426":6,"1429":3,"1433":1,"1435":71,"1436":1,"1437":19,"1439":1,"1440":12,"1441":2,"1442":4,"1443":2,"1446":3,"1448":3,"1454":2,"1455":2,"1457":1,"1459":1,"1460":1,"1466":12,"1475":1,"1476":1,"1486":3,"1487":1,"1488":2,"1495":4,"1523":1,"1525":4,"1533":4,"1545":1,"1576":1,"1581":2,"1590":1,"1611":1,"1630":3,"1631":7,"1634":1,"1637":1,"1638":7,"1639":10,"1640":17,"1652":3,"1655":2,"1663":1,"1665":5,"1669":4,"1670":2,"1672":1,"1685":1,"1697":4,"1701":1,"1702":2,"1706":1,"1719":1,"1726":1,"1746":4,"1747":5,"1748":4,"1754":2,"1757":1,"1759":1,"1764":1,"1770":1,"1774":1,"1775":3,"1804":1,"1814":2,"1822":1,"1824":2,"1875":1,"1876":1,"1895":2,"1897":3,"1908":1,"1909":1,"1911":1,"1920":1,"1921":1,"1922":2,"1924":3,"1927":2,"1929":2,"1933":1,"1945":3,"1949":1,"1959":3,"1960":3,"1962":1,"1963":1,"1964":3,"1967":1,"1968":1,"1971":2,"1977":1,"1986":1,"1989":2,"1992":2,"1993":1,"1994":1,"1997":1,"1999":4,"2000":4,"2001":2,"2004":1,"2007":1,"2008":2,"2009":1,"2024":12,"2035":1,"2055":1,"2065":2,"2067":1,"2073":1,"2125":1,"2131":4,"2132":2,"2133":1,"2134":1,"2149":1,"2151":1,"2155":1,"2158":1,"2159":1,"2160":1,"2165":11,"2166":1,"2167":2,"2168":1,"2189":1,"2198":1,"2199":1,"2200":1,"2202":2,"2231":4,"2238":1}}],["end",{"0":{"1245":2,"1303":2,"1322":2,"1349":2,"1414":2,"1432":2,"1513":2,"1559":1,"1562":1,"1564":1,"1650":2,"1753":2,"1932":2,"2209":1},"3":{"0":10,"15":1,"30":1,"59":1,"62":2,"66":2,"76":1,"92":2,"96":1,"107":2,"109":2,"115":4,"130":6,"132":1,"150":1,"155":1,"164":3,"173":1,"179":1,"180":1,"193":3,"241":4,"272":1,"298":2,"386":1,"395":6,"398":2,"402":1,"434":2,"468":4,"501":1,"516":1,"543":3,"574":1,"625":4,"626":1,"696":1,"706":1,"795":1,"797":3,"812":1,"827":1,"844":1,"846":2,"857":2,"861":1,"862":1,"867":2,"881":2,"883":2,"905":1,"920":1,"946":2,"952":2,"972":1,"1025":1,"1026":1,"1048":1,"1050":2,"1067":2,"1073":1,"1075":2,"1081":2,"1185":1,"1192":4,"1202":4,"1203":4,"1212":6,"1216":4,"1220":2,"1225":2,"1229":2,"1233":2,"1234":2,"1237":13,"1247":1,"1256":2,"1259":4,"1270":1,"1271":4,"1274":2,"1275":1,"1280":1,"1285":13,"1289":3,"1296":1,"1299":16,"1308":3,"1309":3,"1310":3,"1311":2,"1322":7,"1323":73,"1325":2,"1332":1,"1335":1,"1338":27,"1343":1,"1347":1,"1349":21,"1351":2,"1357":1,"1358":46,"1361":1,"1368":3,"1374":2,"1375":2,"1379":6,"1382":1,"1384":1,"1389":1,"1390":1,"1392":1,"1394":74,"1395":26,"1397":2,"1401":18,"1402":2,"1412":1,"1414":10,"1415":4,"1416":40,"1426":7,"1427":5,"1430":2,"1431":1,"1432":1,"1435":79,"1436":6,"1437":15,"1438":1,"1440":2,"1441":1,"1442":2,"1451":3,"1454":4,"1455":2,"1460":1,"1461":1,"1462":2,"1464":2,"1466":16,"1474":1,"1480":2,"1485":1,"1487":1,"1488":2,"1490":1,"1491":2,"1492":1,"1495":10,"1498":3,"1524":1,"1525":5,"1529":3,"1530":3,"1531":1,"1534":3,"1535":2,"1553":1,"1562":1,"1564":3,"1571":1,"1573":1,"1575":2,"1576":4,"1580":2,"1581":2,"1582":1,"1584":1,"1585":2,"1587":2,"1589":1,"1590":3,"1596":2,"1598":2,"1599":3,"1601":2,"1620":1,"1629":6,"1630":2,"1631":1,"1634":2,"1646":3,"1650":2,"1652":4,"1653":2,"1660":2,"1661":2,"1668":2,"1673":2,"1676":1,"1680":2,"1684":1,"1688":1,"1701":2,"1719":2,"1721":2,"1756":1,"1763":1,"1766":2,"1767":1,"1778":1,"1783":1,"1795":4,"1804":2,"1814":1,"1817":1,"1859":2,"1874":2,"1883":2,"1908":1,"1909":1,"1922":1,"1935":2,"1940":2,"1946":2,"1980":1,"1981":1,"2003":1,"2008":2,"2023":1,"2024":2,"2049":2,"2050":2,"2053":2,"2056":1,"2058":1,"2062":2,"2068":1,"2069":2,"2071":2,"2077":1,"2078":1,"2079":2,"2080":1,"2085":2,"2126":2,"2129":1,"2146":1,"2154":1,"2155":2,"2174":1,"2189":1,"2198":1,"2213":2,"2219":3,"2220":2,"2225":2,"2231":1,"2233":2}}],["exotic",{"3":{"1259":1,"1905":1,"1951":1,"2124":1}}],["exhibiting",{"3":{"1394":1}}],["exhibits",{"3":{"1394":1}}],["exhibitors",{"3":{"1358":1,"1363":1,"1368":1,"1379":1,"1437":1,"1628":1,"1630":1}}],["exhibitor",{"3":{"1349":1,"1358":3,"1368":1,"1394":3,"1437":1,"1630":2}}],["exhibit",{"3":{"725":1}}],["exhausting",{"3":{"1268":1,"1338":1,"2155":1}}],["exhaustively",{"3":{"1343":1,"1404":1}}],["exhaustiveness",{"3":{"257":1}}],["exhaustive",{"3":{"254":1,"260":1,"626":1,"1323":2,"1358":1,"1394":1,"1395":2,"1497":2}}],["exhaustion",{"3":{"0":1,"20":2,"1256":1,"1358":2,"1394":2,"1435":1}}],["exhaust",{"3":{"174":1,"1322":2,"1323":2,"1338":1}}],["exhausts",{"3":{"24":1,"818":1,"1237":1,"1255":1,"1256":1,"1258":1,"1259":3,"1267":1,"1316":1,"1322":2,"1331":1,"1435":2,"1437":1,"1441":1,"1446":1,"1588":1,"1590":1,"1595":1,"1916":1}}],["exhausted",{"3":{"0":1,"20":1,"395":1,"397":1,"819":1,"820":1,"1212":1,"1256":1,"1259":7,"1285":3,"1308":1,"1317":1,"1322":4,"1323":1,"1349":1,"1358":2,"1388":1,"1394":3,"1416":1,"1426":1,"1435":1,"1466":2,"1576":1,"1638":1,"1664":1,"1842":1,"1844":1,"2155":3}}],["exfiltrate",{"3":{"1299":1,"1965":1}}],["exfiltrated",{"3":{"530":1}}],["exfiltration",{"3":{"206":1,"208":1,"219":1,"1323":1,"2149":1}}],["ex",{"3":{"265":1,"819":1,"1247":3,"1259":7,"1270":4,"1299":1,"1300":1,"1308":2,"1310":1,"1311":4,"1322":1,"1323":5,"1338":1,"1358":4,"1365":1,"1379":6,"1395":2,"1414":2,"1525":2,"1590":1,"1926":1,"2031":1}}],["examples",{"0":{"1490":1},"3":{"493":1,"495":2,"1270":1,"1285":1,"1308":1,"1309":1,"1323":1,"1358":1,"1435":1,"1484":2,"1490":1,"1734":1,"2108":1}}],["example",{"0":{"1798":1,"2188":1},"3":{"31":1,"53":2,"72":1,"82":1,"111":1,"119":1,"128":2,"165":1,"168":1,"185":1,"188":1,"268":2,"283":1,"318":1,"359":2,"363":1,"364":2,"370":1,"380":1,"401":1,"413":1,"415":1,"416":1,"420":1,"427":1,"448":1,"459":1,"474":1,"486":1,"490":1,"491":1,"492":1,"493":1,"543":1,"545":4,"550":1,"556":1,"566":1,"599":1,"620":1,"733":2,"780":1,"782":1,"799":1,"801":1,"837":1,"841":2,"861":1,"873":1,"876":1,"881":2,"963":1,"980":1,"1103":1,"1133":1,"1168":2,"1175":1,"1177":1,"1212":1,"1219":1,"1229":7,"1233":1,"1237":10,"1243":1,"1247":7,"1249":1,"1259":9,"1262":1,"1265":1,"1268":1,"1270":10,"1271":9,"1285":36,"1287":1,"1299":16,"1300":4,"1301":1,"1308":3,"1309":13,"1310":19,"1311":9,"1315":1,"1319":3,"1322":16,"1323":17,"1338":12,"1342":1,"1344":1,"1346":1,"1347":1,"1349":21,"1351":1,"1358":26,"1360":1,"1361":1,"1368":2,"1370":1,"1371":1,"1372":2,"1373":1,"1375":1,"1379":4,"1383":1,"1384":1,"1388":1,"1394":7,"1395":11,"1401":1,"1410":1,"1414":8,"1415":4,"1416":12,"1426":1,"1430":3,"1432":1,"1435":37,"1436":1,"1437":2,"1441":2,"1442":2,"1453":1,"1454":1,"1466":1,"1473":1,"1478":1,"1479":1,"1485":1,"1487":3,"1490":3,"1491":1,"1492":2,"1499":1,"1524":1,"1525":1,"1576":1,"1584":3,"1628":1,"1630":1,"1638":1,"1652":1,"1655":1,"1674":1,"1684":1,"1685":2,"1700":1,"1701":1,"1708":1,"1709":1,"1726":1,"1727":2,"1801":1,"1809":1,"1828":2,"1829":1,"1840":1,"1859":1,"1880":1,"1921":1,"1930":1,"1932":1,"1939":1,"1940":1,"1953":1,"1954":1,"1975":1,"2003":1,"2007":1,"2009":2,"2015":1,"2018":1,"2031":1,"2032":1,"2041":1,"2042":2,"2047":1,"2077":1,"2082":1,"2086":1,"2087":1,"2143":1,"2155":1}}],["exacting",{"3":{"1379":1}}],["exactness",{"3":{"1338":1}}],["exact",{"0":{"1263":1,"2094":1},"3":{"0":3,"1":1,"22":1,"24":1,"98":1,"99":2,"100":1,"102":1,"109":1,"167":1,"283":1,"341":1,"342":1,"371":1,"420":1,"440":1,"459":1,"472":1,"499":1,"530":1,"572":1,"574":1,"626":1,"627":1,"691":1,"707":1,"716":1,"725":1,"740":1,"757":1,"758":1,"765":1,"766":1,"813":1,"827":1,"838":1,"861":1,"863":1,"871":1,"890":1,"905":1,"908":1,"916":1,"980":1,"996":2,"1004":1,"1005":1,"1018":1,"1036":1,"1067":2,"1071":1,"1092":1,"1163":1,"1191":1,"1217":1,"1237":3,"1249":1,"1259":3,"1270":3,"1276":1,"1285":17,"1299":11,"1300":1,"1308":2,"1310":15,"1311":2,"1314":1,"1322":6,"1323":14,"1338":7,"1347":1,"1349":3,"1356":2,"1358":18,"1365":1,"1368":8,"1379":7,"1394":6,"1395":4,"1401":3,"1404":1,"1408":1,"1414":3,"1415":1,"1416":9,"1417":1,"1426":2,"1428":1,"1434":1,"1435":42,"1437":3,"1443":1,"1444":1,"1446":1,"1449":1,"1452":1,"1453":1,"1454":10,"1455":1,"1462":1,"1466":1,"1469":1,"1473":1,"1485":2,"1491":1,"1495":6,"1499":2,"1524":1,"1525":4,"1530":1,"1590":3,"1595":1,"1598":1,"1639":1,"1645":1,"1696":1,"1728":1,"1734":1,"1783":1,"1798":1,"1801":1,"1830":1,"1850":1,"1871":1,"1873":1,"1908":1,"1921":1,"1926":1,"1928":1,"1950":1,"1953":1,"1973":1,"1981":1,"2035":1,"2061":1,"2082":1,"2085":1,"2088":1,"2097":1,"2107":1,"2120":1,"2123":1,"2125":1,"2135":1,"2166":3,"2175":1}}],["exactlyone",{"3":{"1414":1}}],["exactly",{"3":{"0":24,"15":1,"21":1,"23":2,"24":1,"26":2,"38":1,"39":1,"71":2,"93":1,"96":1,"99":1,"109":3,"135":6,"136":1,"139":2,"140":1,"148":2,"159":1,"160":1,"166":2,"170":2,"178":2,"180":1,"186":1,"187":1,"195":3,"197":1,"199":1,"201":1,"202":2,"214":1,"217":1,"218":1,"235":2,"243":1,"253":1,"254":1,"255":1,"257":1,"260":1,"266":1,"292":1,"303":2,"305":1,"310":1,"318":1,"335":2,"342":1,"343":1,"350":2,"351":1,"352":1,"353":1,"359":2,"361":1,"363":1,"369":1,"373":1,"374":1,"380":1,"387":1,"388":1,"390":2,"391":1,"395":1,"400":1,"401":1,"402":1,"407":1,"414":1,"415":1,"420":2,"426":1,"433":1,"443":1,"448":1,"458":2,"459":1,"464":1,"483":1,"490":2,"491":2,"492":1,"493":2,"494":1,"499":1,"507":1,"527":1,"536":2,"539":1,"545":1,"549":2,"551":1,"556":1,"563":1,"564":1,"571":1,"572":1,"574":1,"577":1,"579":1,"583":2,"585":2,"592":2,"593":1,"600":1,"608":1,"609":2,"610":1,"618":2,"619":1,"625":1,"626":1,"627":1,"633":1,"634":1,"635":1,"639":1,"640":2,"649":1,"651":1,"656":1,"657":1,"658":1,"665":1,"667":1,"674":5,"682":2,"684":1,"690":4,"692":1,"693":1,"698":3,"699":1,"702":1,"707":2,"708":1,"709":1,"714":1,"715":1,"716":1,"725":1,"727":1,"732":1,"733":4,"734":1,"735":2,"736":1,"740":1,"741":2,"742":1,"749":1,"757":3,"758":2,"761":1,"766":2,"768":1,"773":1,"775":1,"782":1,"790":2,"791":1,"799":1,"801":1,"803":1,"813":3,"814":1,"815":1,"819":2,"820":1,"826":1,"827":7,"828":1,"829":3,"832":1,"838":3,"839":1,"840":1,"846":1,"847":1,"849":1,"853":1,"861":1,"862":1,"876":1,"881":2,"882":1,"889":2,"891":1,"897":2,"898":1,"899":1,"903":1,"904":1,"905":6,"907":1,"913":2,"920":1,"923":2,"924":2,"926":3,"931":1,"932":1,"938":1,"939":1,"940":2,"944":1,"946":2,"948":2,"955":1,"956":1,"962":1,"966":1,"968":1,"971":1,"972":1,"980":1,"988":1,"990":1,"995":1,"996":1,"1005":1,"1006":1,"1013":1,"1018":4,"1020":1,"1027":2,"1034":1,"1036":1,"1041":1,"1042":2,"1043":1,"1049":1,"1050":2,"1051":2,"1053":1,"1059":1,"1061":1,"1067":1,"1069":1,"1076":1,"1082":1,"1083":1,"1084":2,"1085":1,"1090":1,"1091":2,"1092":1,"1093":2,"1107":1,"1108":1,"1116":2,"1118":1,"1124":1,"1132":1,"1133":2,"1135":1,"1140":1,"1161":1,"1175":1,"1178":1,"1184":2,"1191":2,"1200":1,"1212":4,"1220":1,"1223":1,"1228":1,"1229":14,"1230":1,"1231":2,"1232":1,"1233":1,"1234":1,"1235":1,"1237":22,"1239":1,"1241":2,"1243":2,"1244":1,"1247":26,"1250":1,"1251":2,"1253":1,"1255":1,"1259":28,"1260":1,"1262":1,"1263":2,"1264":1,"1265":1,"1266":1,"1267":1,"1269":1,"1270":48,"1271":7,"1274":3,"1275":3,"1276":2,"1277":1,"1278":2,"1279":3,"1280":3,"1282":3,"1283":1,"1285":107,"1289":2,"1291":1,"1293":2,"1294":1,"1296":2,"1299":54,"1300":15,"1306":1,"1308":23,"1309":8,"1310":58,"1311":3,"1314":1,"1315":3,"1316":2,"1317":4,"1319":2,"1321":1,"1322":67,"1323":90,"1324":1,"1327":1,"1329":1,"1330":1,"1331":1,"1333":3,"1334":1,"1335":2,"1336":1,"1338":54,"1341":6,"1344":1,"1346":1,"1347":1,"1348":1,"1349":36,"1351":2,"1352":2,"1353":1,"1354":1,"1356":2,"1357":1,"1358":141,"1361":1,"1367":2,"1368":12,"1370":1,"1372":1,"1373":1,"1377":1,"1379":21,"1383":1,"1388":2,"1390":2,"1394":81,"1395":87,"1398":1,"1399":1,"1401":30,"1404":1,"1406":1,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1414":54,"1415":12,"1416":37,"1419":1,"1420":1,"1422":1,"1423":1,"1425":1,"1426":9,"1427":2,"1432":1,"1434":2,"1435":165,"1437":15,"1440":3,"1441":2,"1442":2,"1446":1,"1448":1,"1452":1,"1453":1,"1454":15,"1455":3,"1458":1,"1461":1,"1462":1,"1465":1,"1466":18,"1471":1,"1472":2,"1473":1,"1475":7,"1479":1,"1481":1,"1482":1,"1483":1,"1485":3,"1487":3,"1488":2,"1489":2,"1490":2,"1491":3,"1495":5,"1497":1,"1504":1,"1525":5,"1530":1,"1576":1,"1590":1,"1630":3,"1631":1,"1634":2,"1638":1,"1640":1,"1650":1,"1651":1,"1655":1,"1663":2,"1667":2,"1682":2,"1683":1,"1684":4,"1685":5,"1688":1,"1692":1,"1696":2,"1718":1,"1727":1,"1748":3,"1763":5,"1764":2,"1766":1,"1782":1,"1788":1,"1789":1,"1795":1,"1798":1,"1799":1,"1800":1,"1804":1,"1809":1,"1813":1,"1814":3,"1815":1,"1820":1,"1821":1,"1824":2,"1828":2,"1831":2,"1838":2,"1839":1,"1841":2,"1842":1,"1846":1,"1848":3,"1851":2,"1855":1,"1863":1,"1868":1,"1870":1,"1872":1,"1873":1,"1875":1,"1880":1,"1887":1,"1890":1,"1891":1,"1899":1,"1901":1,"1903":1,"1907":1,"1908":2,"1917":1,"1918":2,"1919":1,"1921":2,"1922":3,"1926":2,"1928":1,"1930":1,"1932":1,"1933":1,"1935":1,"1936":1,"1939":1,"1941":1,"1943":1,"1945":1,"1946":1,"1947":1,"1952":1,"1957":1,"1959":1,"1960":1,"1963":1,"1964":1,"1967":1,"1969":1,"1970":1,"1973":1,"1974":1,"1975":2,"1976":1,"1977":2,"1978":3,"1979":2,"1980":1,"1981":2,"1982":1,"1992":1,"1995":1,"1997":1,"1999":2,"2000":3,"2001":1,"2002":1,"2007":1,"2009":1,"2011":1,"2023":1,"2026":1,"2028":1,"2031":1,"2035":1,"2041":2,"2042":2,"2043":2,"2046":2,"2047":1,"2056":2,"2060":1,"2063":2,"2065":1,"2069":1,"2073":1,"2074":1,"2075":1,"2080":1,"2084":1,"2087":1,"2091":1,"2097":1,"2100":1,"2104":1,"2106":2,"2108":1,"2110":1,"2113":1,"2118":1,"2124":1,"2128":1,"2130":1,"2133":1,"2135":1,"2137":1,"2139":1,"2140":1,"2141":3,"2142":1,"2143":1,"2147":2,"2148":1,"2149":1,"2150":1,"2156":1,"2157":1,"2164":1,"2165":1,"2166":3,"2170":1,"2173":1,"2175":1,"2177":1,"2178":1,"2184":1,"2185":1,"2189":1,"2191":1,"2194":1,"2198":2,"2202":1,"2229":1}}],["exif",{"3":{"439":1,"440":1,"1115":1,"1284":1,"1285":2,"1322":3,"1414":2,"1630":1,"2123":1,"2124":1,"2125":2,"2127":3}}],["exiting",{"3":{"626":1,"1459":1}}],["exits",{"3":{"0":2,"397":1,"566":2,"591":1,"626":5,"627":1,"757":1,"766":1,"1215":2,"1299":1,"1309":1,"1310":1,"1323":1,"1349":1,"1394":4,"1395":1,"1415":1,"1416":2,"1435":4,"1454":6,"1457":1,"1474":2,"1485":2,"1491":1,"1525":3,"1590":1,"1684":1,"2057":1}}],["exit",{"0":{"150":1},"1":{"1072":1,"1073":1,"1074":1,"1075":1,"1076":1,"1077":1,"1078":1,"1079":1},"3":{"0":4,"145":4,"153":1,"370":2,"373":1,"452":1,"633":1,"757":1,"766":1,"873":2,"876":1,"877":2,"980":1,"1072":1,"1073":1,"1075":2,"1077":2,"1078":1,"1079":2,"1212":1,"1270":2,"1285":2,"1292":1,"1299":1,"1310":1,"1322":1,"1323":5,"1358":1,"1379":1,"1395":3,"1414":1,"1416":9,"1444":1,"1452":2,"1454":3,"1458":1,"1474":1,"1485":1,"1525":3,"1576":1,"1590":1,"1672":1,"1929":1,"2057":1,"2231":1}}],["existent",{"3":{"1358":1,"1414":1}}],["existence",{"3":{"0":1,"230":1,"295":1,"318":5,"435":1,"459":1,"463":1,"468":1,"545":2,"550":1,"583":2,"856":1,"1003":1,"1058":1,"1170":1,"1244":1,"1247":2,"1270":3,"1276":1,"1285":15,"1299":1,"1307":1,"1308":1,"1310":2,"1321":1,"1322":3,"1323":1,"1351":1,"1358":15,"1363":1,"1368":4,"1379":2,"1394":1,"1395":3,"1401":1,"1411":1,"1414":2,"1435":3,"1437":2,"1590":1,"1599":1,"1630":1,"1665":1,"1747":1,"1758":1,"1760":1,"1764":1,"1771":1,"1994":1,"2033":1,"2065":1,"2089":1,"2136":1}}],["existed",{"3":{"22":1,"243":1,"274":1,"459":1,"477":1,"538":1,"539":1,"600":1,"698":1,"708":1,"837":1,"921":1,"945":1,"1017":1,"1018":1,"1020":1,"1066":1,"1090":1,"1118":1,"1160":1,"1183":1,"1247":3,"1249":1,"1250":1,"1253":1,"1259":4,"1270":1,"1273":1,"1285":5,"1299":1,"1300":2,"1317":1,"1319":1,"1322":4,"1323":6,"1329":1,"1349":4,"1354":1,"1358":8,"1364":1,"1368":2,"1394":2,"1395":3,"1407":1,"1414":2,"1416":1,"1426":1,"1435":10,"1464":1,"1466":1,"1495":3,"1630":1,"1640":1,"1726":1,"1747":1,"1764":1,"2047":1,"2060":1,"2166":1,"2167":1,"2185":1}}],["existinguser",{"3":{"1414":2}}],["existingdeletedbookmark",{"3":{"1395":3}}],["existinganswerids",{"3":{"1395":3}}],["existinganswer",{"3":{"1358":1}}],["existingspeaker",{"3":{"1358":1}}],["existingquestion",{"3":{"1358":1}}],["existingfilter",{"3":{"1285":3}}],["existing",{"2":{"1483":1,"1696":1},"3":{"0":21,"20":1,"24":1,"48":1,"52":1,"78":1,"79":1,"81":1,"99":1,"121":3,"126":1,"131":1,"142":1,"160":2,"200":1,"218":1,"241":1,"255":1,"265":1,"266":1,"273":1,"295":1,"309":1,"310":1,"318":1,"319":1,"350":1,"351":2,"352":2,"379":1,"380":1,"381":1,"390":1,"396":1,"401":1,"420":1,"434":1,"438":1,"443":1,"448":1,"449":2,"493":1,"518":1,"528":1,"534":1,"537":1,"545":1,"546":1,"549":2,"563":1,"575":1,"582":1,"587":1,"590":1,"599":2,"635":1,"640":1,"644":1,"648":1,"649":1,"664":1,"665":1,"690":1,"725":1,"733":3,"734":1,"736":1,"741":1,"742":1,"743":1,"756":1,"765":1,"789":1,"790":2,"793":1,"794":1,"799":2,"827":1,"846":1,"852":1,"854":2,"861":1,"889":1,"890":1,"905":1,"914":2,"920":2,"922":1,"923":1,"926":1,"938":1,"945":1,"948":1,"964":1,"1006":1,"1011":1,"1018":2,"1019":1,"1026":1,"1027":1,"1034":2,"1050":1,"1058":2,"1059":3,"1068":1,"1075":1,"1100":1,"1116":2,"1134":1,"1144":2,"1149":1,"1155":1,"1162":1,"1178":1,"1179":1,"1184":1,"1187":1,"1190":2,"1212":2,"1220":1,"1232":1,"1237":2,"1247":8,"1249":1,"1259":6,"1269":1,"1270":11,"1271":2,"1276":1,"1280":2,"1285":18,"1289":1,"1292":2,"1299":11,"1300":2,"1303":1,"1304":1,"1308":13,"1309":1,"1310":7,"1311":1,"1315":1,"1322":5,"1323":11,"1326":1,"1338":9,"1349":4,"1352":1,"1358":36,"1361":1,"1365":1,"1368":4,"1379":2,"1394":3,"1395":25,"1401":3,"1405":1,"1406":1,"1407":1,"1414":9,"1415":2,"1416":6,"1426":1,"1435":16,"1437":1,"1440":3,"1462":1,"1466":7,"1469":1,"1471":2,"1473":1,"1480":2,"1483":1,"1487":1,"1488":1,"1495":12,"1525":1,"1576":3,"1601":1,"1611":1,"1626":1,"1630":4,"1631":7,"1634":5,"1635":1,"1637":1,"1638":13,"1639":3,"1640":4,"1644":1,"1657":1,"1660":3,"1663":1,"1673":1,"1680":1,"1683":1,"1696":1,"1704":1,"1721":1,"1723":1,"1727":2,"1728":1,"1733":1,"1747":1,"1764":1,"1766":1,"1776":1,"1799":1,"1809":1,"1814":1,"1825":1,"1858":1,"1885":1,"1888":1,"1914":1,"1932":1,"1943":1,"1944":2,"1976":1,"1977":2,"1978":2,"1993":1,"2074":1,"2096":2,"2100":1,"2130":1,"2132":1,"2141":1,"2149":1,"2195":1,"2197":2,"2231":2}}],["existsasync",{"2":{"91":1,"295":1,"1363":1,"1776":1},"3":{"91":1,"295":1,"459":1,"545":2,"550":1,"1244":1,"1247":4,"1285":6,"1322":1,"1358":5,"1363":1,"1368":1,"1395":2,"1414":1,"1638":1,"1750":1,"1776":1,"1814":1}}],["exists",{"0":{"813":1,"1717":1},"2":{"1255":1},"3":{"0":6,"19":1,"24":1,"33":1,"45":1,"93":1,"103":1,"122":1,"135":3,"136":1,"143":1,"150":2,"157":1,"160":1,"161":1,"175":1,"179":1,"200":1,"209":1,"225":1,"243":2,"245":1,"254":1,"256":1,"265":3,"268":1,"273":1,"293":1,"301":1,"303":1,"305":1,"309":1,"318":2,"324":1,"325":1,"326":1,"336":1,"341":1,"343":2,"350":1,"361":1,"372":1,"383":1,"388":1,"390":1,"391":1,"398":1,"401":1,"413":2,"415":1,"418":2,"420":1,"423":1,"428":1,"442":1,"443":1,"448":1,"463":1,"477":1,"490":1,"491":1,"492":1,"493":2,"507":1,"517":1,"528":1,"536":1,"539":1,"543":1,"546":1,"548":1,"549":1,"551":1,"556":2,"557":1,"563":1,"572":1,"583":2,"585":1,"591":2,"592":1,"593":1,"599":3,"605":1,"607":1,"608":1,"609":3,"611":2,"618":2,"626":1,"633":2,"640":1,"645":1,"656":1,"657":1,"658":2,"665":3,"674":1,"676":4,"681":1,"689":1,"690":2,"698":2,"703":2,"707":2,"714":1,"716":2,"718":1,"720":1,"724":1,"725":1,"727":1,"736":1,"741":2,"749":1,"753":1,"759":1,"760":1,"762":1,"773":1,"790":1,"792":2,"797":1,"798":1,"804":1,"808":1,"809":2,"810":1,"811":2,"812":2,"818":1,"819":1,"820":2,"821":1,"827":2,"832":1,"834":1,"837":2,"838":1,"840":1,"862":1,"871":1,"882":1,"884":1,"890":1,"891":1,"913":2,"914":1,"916":1,"924":1,"926":2,"932":1,"945":1,"946":1,"947":1,"953":1,"956":1,"963":1,"965":1,"973":1,"974":2,"980":1,"988":1,"996":1,"997":1,"1005":1,"1014":1,"1018":1,"1019":1,"1020":1,"1029":1,"1042":2,"1043":1,"1050":2,"1051":1,"1052":1,"1067":2,"1071":1,"1077":1,"1082":1,"1092":1,"1100":1,"1118":2,"1132":1,"1134":2,"1142":1,"1168":1,"1190":1,"1191":1,"1196":1,"1200":1,"1212":1,"1229":8,"1231":1,"1232":1,"1235":1,"1237":12,"1239":2,"1240":1,"1241":2,"1242":1,"1247":11,"1251":1,"1254":1,"1255":1,"1257":1,"1259":8,"1270":15,"1271":4,"1276":1,"1277":2,"1280":1,"1285":61,"1287":1,"1288":2,"1289":1,"1292":1,"1296":2,"1299":37,"1300":7,"1303":1,"1306":1,"1308":5,"1309":2,"1310":31,"1311":1,"1312":1,"1314":1,"1319":2,"1321":1,"1322":37,"1323":55,"1331":1,"1332":2,"1333":1,"1336":2,"1338":21,"1344":1,"1347":1,"1348":1,"1349":28,"1351":3,"1356":2,"1358":99,"1367":1,"1368":8,"1370":1,"1371":2,"1374":1,"1375":1,"1378":1,"1379":10,"1382":1,"1388":1,"1394":31,"1395":46,"1401":12,"1404":1,"1405":1,"1409":1,"1410":1,"1413":2,"1414":40,"1415":8,"1416":37,"1417":1,"1426":5,"1430":1,"1435":100,"1437":5,"1440":1,"1442":2,"1444":3,"1446":1,"1447":1,"1448":2,"1451":1,"1454":10,"1456":2,"1457":1,"1458":1,"1464":1,"1465":1,"1466":7,"1467":1,"1469":1,"1470":1,"1471":3,"1473":2,"1474":1,"1475":1,"1477":1,"1478":1,"1481":1,"1483":2,"1485":2,"1487":3,"1488":4,"1489":1,"1490":2,"1491":2,"1495":2,"1523":2,"1525":6,"1530":5,"1531":1,"1533":3,"1538":1,"1544":1,"1546":1,"1547":1,"1576":2,"1584":1,"1590":1,"1595":3,"1625":1,"1626":1,"1630":5,"1631":9,"1632":1,"1634":1,"1637":3,"1638":6,"1640":6,"1645":1,"1650":2,"1652":1,"1653":1,"1665":1,"1667":1,"1674":1,"1682":1,"1683":1,"1685":2,"1691":1,"1700":1,"1719":2,"1723":1,"1725":1,"1729":1,"1731":1,"1736":1,"1746":1,"1747":1,"1748":6,"1754":2,"1759":1,"1764":3,"1775":3,"1789":1,"1801":1,"1809":1,"1810":1,"1814":2,"1824":1,"1830":1,"1834":1,"1836":1,"1848":1,"1850":3,"1857":1,"1860":1,"1863":2,"1873":1,"1877":1,"1880":2,"1888":1,"1901":1,"1907":1,"1908":3,"1922":1,"1925":1,"1933":1,"1944":1,"1946":1,"1947":1,"1951":1,"1952":1,"1953":3,"1954":1,"1955":1,"1969":1,"1970":1,"1974":1,"1988":1,"1993":1,"1994":4,"1999":1,"2008":1,"2013":1,"2032":1,"2036":1,"2039":1,"2040":1,"2045":1,"2056":1,"2062":1,"2073":1,"2074":2,"2077":1,"2106":1,"2120":1,"2126":1,"2130":1,"2131":1,"2134":1,"2137":1,"2150":1,"2155":1,"2164":1,"2165":3,"2166":1,"2167":3,"2173":1,"2175":1,"2182":2,"2184":1,"2187":1,"2203":1}}],["exist",{"0":{"1226":1,"1234":1},"3":{"0":11,"11":1,"16":1,"19":1,"61":1,"62":1,"68":1,"95":1,"97":1,"117":1,"122":2,"141":2,"142":1,"160":1,"219":1,"255":1,"341":1,"343":1,"395":1,"412":1,"416":1,"418":1,"422":2,"428":1,"433":1,"443":1,"468":1,"477":1,"518":1,"536":1,"543":1,"549":1,"551":1,"556":1,"560":1,"583":1,"591":1,"592":1,"601":1,"611":1,"619":1,"622":1,"625":1,"629":1,"638":1,"658":1,"666":1,"677":1,"681":1,"691":1,"725":1,"735":1,"780":1,"803":1,"819":1,"827":1,"829":2,"831":1,"846":1,"847":1,"856":1,"861":1,"876":1,"890":1,"899":1,"905":2,"914":1,"916":1,"918":1,"923":1,"924":1,"926":2,"956":1,"989":2,"1021":1,"1034":1,"1066":1,"1092":1,"1114":1,"1117":1,"1125":1,"1153":1,"1167":1,"1191":1,"1218":1,"1223":1,"1229":2,"1237":3,"1241":1,"1247":3,"1251":1,"1259":2,"1262":1,"1263":2,"1270":9,"1271":2,"1278":1,"1285":20,"1296":1,"1299":10,"1300":1,"1307":1,"1308":3,"1310":6,"1311":1,"1314":1,"1319":1,"1322":11,"1323":19,"1325":1,"1327":1,"1331":1,"1338":11,"1347":2,"1348":1,"1349":11,"1352":1,"1358":28,"1363":1,"1367":1,"1368":4,"1370":1,"1372":1,"1379":1,"1394":5,"1395":21,"1401":2,"1403":1,"1408":1,"1414":11,"1415":4,"1416":11,"1432":1,"1435":31,"1437":1,"1452":2,"1453":1,"1454":6,"1455":1,"1458":1,"1464":1,"1466":5,"1469":2,"1470":1,"1472":1,"1473":1,"1475":1,"1476":1,"1488":2,"1491":3,"1495":2,"1506":1,"1525":1,"1544":1,"1549":1,"1552":1,"1556":1,"1566":1,"1570":1,"1576":4,"1595":1,"1596":1,"1625":1,"1630":5,"1631":9,"1633":1,"1634":1,"1637":1,"1638":1,"1639":2,"1648":1,"1652":1,"1668":1,"1699":1,"1700":1,"1727":1,"1748":3,"1754":2,"1764":2,"1766":3,"1775":2,"1800":1,"1802":2,"1803":1,"1809":1,"1811":1,"1815":1,"1820":1,"1823":1,"1824":1,"1839":1,"1842":1,"1855":1,"1866":1,"1875":1,"1878":1,"1880":1,"1893":1,"1927":1,"1940":1,"1960":1,"1970":1,"2000":1,"2032":1,"2036":1,"2039":1,"2046":2,"2055":1,"2079":1,"2096":1,"2097":1,"2113":1,"2115":2,"2121":1,"2158":1,"2163":1,"2168":1,"2178":1,"2179":2,"2180":1,"2181":1}}],["exchanging",{"3":{"351":1,"1310":1,"1435":1,"1576":1}}],["exchangecode",{"3":{"1310":1}}],["exchanged",{"3":{"1299":1,"1323":3,"1415":1,"1581":1,"1971":1}}],["exchangeresponse",{"3":{"1199":2,"1207":1}}],["exchangeoauthcodeasync",{"2":{"420":2,"423":1,"426":1,"428":1},"3":{"420":2,"423":1,"426":1,"428":1,"1323":2,"1435":1}}],["exchangeasync",{"2":{"1534":1,"1974":1,"1975":1},"3":{"350":1,"1299":1,"1310":4,"1534":1,"1974":1,"1975":1}}],["exchanges",{"3":{"0":1,"350":1,"355":1,"419":1,"499":1,"503":1,"528":1,"1184":1,"1297":1,"1299":4,"1323":1,"1453":1,"1454":1,"1584":1,"1726":1}}],["exchange",{"0":{"1974":1},"1":{"347":1,"348":1,"349":1,"350":1,"351":1,"352":1,"353":1,"354":1,"355":1},"2":{"178":1,"818":1,"1333":1,"1842":1},"3":{"0":5,"159":1,"160":1,"161":1,"178":1,"340":1,"341":1,"342":1,"347":1,"350":6,"351":1,"352":1,"418":1,"420":1,"421":1,"422":1,"423":3,"426":3,"428":1,"528":3,"529":1,"530":2,"531":1,"640":1,"733":1,"735":1,"818":1,"872":1,"923":1,"1184":1,"1259":1,"1299":8,"1300":3,"1310":9,"1322":6,"1323":10,"1333":1,"1338":2,"1358":1,"1394":1,"1405":1,"1414":2,"1416":4,"1435":7,"1437":2,"1453":3,"1466":1,"1534":1,"1584":1,"1666":1,"1775":1,"1842":1,"1897":2,"1910":1,"1973":1,"1974":3,"1976":2,"1977":2,"1978":3,"2012":1,"2077":1,"2078":1,"2231":1}}],["excuse",{"3":{"1285":1}}],["excuses",{"3":{"318":1,"565":1,"1299":1,"1993":1}}],["excused",{"3":{"135":1,"139":1,"1435":2}}],["excluding",{"3":{"152":1,"619":1,"690":1,"799":1,"940":1,"1196":1,"1281":1,"1285":1,"1310":1,"1331":2,"1338":1,"1349":2,"1358":4,"1401":1,"1414":1,"1435":5,"1454":1,"1466":4,"1630":1,"1747":1,"1800":1,"2066":1}}],["excludereservedids",{"3":{"1358":1}}],["excludeitemid",{"3":{"1349":1}}],["excludefromdescription",{"3":{"1299":1,"1310":1,"1323":1,"1338":1}}],["excludessoftdeleteditems",{"2":{"1685":1},"3":{"1685":1}}],["excludesframeworkevents",{"3":{"1435":1}}],["excludesessionid",{"3":{"1358":7}}],["excludescoredsinceasync",{"3":{"1358":3}}],["excludesdomainevents",{"3":{"1285":1}}],["excludes",{"3":{"233":1,"607":1,"609":1,"819":1,"889":2,"1212":1,"1259":1,"1281":1,"1285":6,"1322":3,"1323":1,"1331":1,"1338":2,"1349":2,"1358":6,"1371":1,"1379":2,"1395":2,"1400":1,"1403":1,"1414":1,"1432":1,"1435":6,"1454":2,"1466":1,"1485":1,"1491":1,"1533":1,"1665":1,"2006":1,"2060":1}}],["exclude",{"3":{"0":1,"38":1,"141":1,"403":1,"609":1,"612":1,"1082":1,"1212":1,"1247":1,"1285":1,"1349":1,"1351":1,"1358":2,"1401":1,"1435":1,"1630":1,"1634":1,"1638":2,"1800":1,"2060":1,"2157":3,"2231":1}}],["excludedpathfragments",{"3":{"1435":3}}],["excludedpathprefixes",{"3":{"698":1,"1285":1,"1319":1}}],["excluded",{"0":{"1209":1},"3":{"0":5,"24":1,"135":2,"149":1,"474":1,"482":1,"490":1,"536":1,"573":1,"698":1,"700":1,"734":1,"799":1,"803":1,"805":1,"867":1,"871":1,"980":1,"982":1,"988":1,"996":1,"1026":1,"1074":1,"1075":1,"1078":1,"1184":1,"1196":1,"1204":1,"1209":1,"1212":2,"1215":1,"1227":1,"1231":1,"1259":4,"1270":2,"1277":1,"1285":7,"1299":2,"1310":2,"1322":2,"1323":4,"1338":3,"1349":4,"1358":13,"1368":1,"1394":1,"1395":5,"1400":1,"1401":7,"1409":1,"1414":8,"1435":12,"1437":1,"1458":1,"1466":1,"1473":2,"1485":1,"1486":1,"1493":1,"1495":3,"1496":1,"1525":1,"1526":1,"1552":1,"1576":1,"1577":1,"1581":1,"1590":1,"1591":1,"1610":1,"1616":1,"1629":1,"1630":5,"1631":1,"1632":2,"1634":5,"1636":1,"1637":2,"1638":2,"1639":4,"1663":1,"1783":1,"1850":2,"1922":1,"2066":1,"2099":1,"2157":1}}],["exclusivity",{"3":{"709":1,"1910":1}}],["exclusivemajorceiling",{"3":{"1435":1}}],["exclusively",{"3":{"193":1,"930":1,"1247":1,"1259":1,"1270":2,"1285":3,"1299":2,"1310":1,"1323":1,"1338":1,"1341":1,"1349":1,"1358":1,"1416":1,"1426":1,"1464":1,"1772":1}}],["exclusive",{"3":{"0":2,"159":1,"996":1,"997":1,"1026":1,"1074":1,"1212":1,"1269":1,"1299":1,"1315":1,"1322":3,"1347":1,"1349":4,"1394":1,"1395":2,"1401":1,"1426":1,"1435":2,"1488":1,"1629":2,"1748":1,"1763":1,"1764":1,"1766":1,"1908":1}}],["exclusionid",{"3":{"1358":1}}],["exclusiontestdbcontext",{"3":{"1199":2,"1207":1}}],["exclusionaggregate",{"3":{"1199":3,"1207":1}}],["exclusionevent",{"3":{"1199":2,"1207":1}}],["exclusions",{"3":{"876":1,"1006":1,"1352":1,"1414":1,"1454":1,"1485":1,"1495":2,"2042":1,"2157":1}}],["exclusion",{"1":{"993":1,"994":1,"995":1,"996":1,"997":1,"998":1,"999":1,"1000":1},"3":{"0":1,"135":1,"142":1,"159":2,"161":1,"235":1,"484":1,"486":1,"490":1,"524":1,"607":1,"696":3,"984":1,"993":1,"995":2,"996":3,"997":1,"998":2,"1000":1,"1212":1,"1237":2,"1260":1,"1269":1,"1270":4,"1274":2,"1285":14,"1299":3,"1300":2,"1310":6,"1322":1,"1338":1,"1343":2,"1349":2,"1351":1,"1358":5,"1366":1,"1394":1,"1395":1,"1401":1,"1414":1,"1416":1,"1435":4,"1437":2,"1442":1,"1473":1,"1495":1,"1533":2,"1591":1,"1634":1,"2056":1,"2119":1,"2231":1}}],["excerpt",{"3":{"1247":1,"1323":1}}],["excellent",{"3":{"1536":1,"1796":1,"1803":1}}],["excel",{"3":{"741":1,"1310":2}}],["exceeding",{"3":{"1426":1,"1713":1}}],["exceed",{"3":{"709":1,"828":1,"829":1,"1285":2,"1291":1,"1349":4,"1358":1,"1394":1,"1398":1,"1435":6,"1684":2,"1685":2,"1831":1,"2165":1}}],["exceeded",{"3":{"556":1,"861":1,"1299":3,"1311":2,"1323":1,"1377":1,"1379":1,"1395":1,"1466":1,"1475":1,"1639":2,"1640":2,"1926":1}}],["exceeds",{"3":{"0":1,"137":1,"369":1,"576":1,"591":1,"626":1,"861":1,"1091":1,"1237":1,"1247":1,"1299":3,"1308":1,"1394":1,"1416":1,"1421":1,"1426":1,"1435":2,"1455":1,"1466":1,"1637":1}}],["excessive",{"3":{"1638":2}}],["excessattempts",{"3":{"1299":1}}],["excess",{"3":{"282":1,"1323":2,"1338":1,"1435":1,"1639":1,"1640":1}}],["exceptargumentguards",{"3":{"1435":2}}],["excepted",{"3":{"619":1,"1487":1}}],["except",{"3":{"0":5,"31":2,"109":1,"130":2,"135":2,"138":2,"141":1,"142":1,"359":1,"388":1,"401":1,"484":1,"766":1,"881":1,"905":1,"926":1,"1034":1,"1092":1,"1247":1,"1259":1,"1270":1,"1271":1,"1285":4,"1299":4,"1308":1,"1310":7,"1317":1,"1322":1,"1323":1,"1332":1,"1338":3,"1349":1,"1358":10,"1368":2,"1391":1,"1394":7,"1395":4,"1414":2,"1426":1,"1435":10,"1437":1,"1442":1,"1454":1,"1458":1,"1489":1,"1610":1,"1626":1,"1634":1,"1759":1,"1760":1,"1820":1,"1822":1,"1855":1,"1877":1,"1897":1,"1903":1,"1918":1,"1921":2,"1947":1,"1953":1,"1954":1,"2099":1}}],["exceptionobject",{"3":{"1416":1}}],["exceptionhandler",{"3":{"1310":2,"1435":1,"1665":1}}],["exceptionhandlertests",{"3":{"1199":1,"1207":3,"1229":1}}],["exceptionhandled",{"3":{"1310":2}}],["exception",{"0":{"1218":1,"1872":1},"2":{"896":1,"899":1,"1820":1,"1918":1,"2031":1},"3":{"0":15,"17":1,"20":1,"24":1,"39":1,"53":1,"94":1,"95":1,"98":1,"107":2,"108":1,"109":12,"110":3,"111":6,"115":3,"122":1,"125":6,"135":1,"141":1,"142":1,"164":1,"175":1,"200":1,"230":1,"235":1,"265":3,"279":1,"280":2,"285":1,"287":1,"303":1,"310":1,"342":1,"370":1,"386":1,"388":2,"395":2,"397":1,"407":4,"414":1,"415":1,"420":1,"427":1,"440":1,"459":1,"465":1,"536":4,"539":1,"549":1,"583":1,"586":1,"618":3,"619":1,"620":2,"651":1,"657":2,"672":1,"674":2,"683":1,"688":1,"690":1,"691":2,"692":1,"698":1,"749":1,"757":1,"819":1,"827":1,"840":1,"881":1,"882":2,"896":2,"897":3,"899":2,"912":2,"913":1,"914":2,"917":1,"918":1,"965":1,"980":1,"988":6,"989":1,"990":1,"1002":1,"1004":1,"1005":1,"1018":2,"1022":1,"1042":1,"1054":1,"1058":1,"1107":1,"1124":2,"1126":1,"1134":1,"1201":1,"1202":1,"1203":2,"1208":1,"1212":2,"1217":1,"1226":1,"1228":1,"1229":18,"1237":3,"1241":1,"1247":12,"1252":2,"1259":19,"1263":6,"1269":1,"1270":34,"1271":1,"1273":1,"1275":1,"1278":3,"1283":1,"1285":74,"1287":1,"1292":1,"1298":1,"1299":17,"1300":1,"1303":2,"1308":8,"1310":40,"1311":21,"1317":1,"1322":40,"1323":50,"1327":2,"1338":19,"1349":5,"1351":1,"1354":2,"1358":28,"1365":1,"1368":4,"1378":1,"1379":2,"1389":1,"1394":23,"1395":38,"1401":1,"1412":1,"1414":14,"1415":7,"1416":30,"1422":2,"1423":1,"1426":12,"1435":69,"1437":7,"1444":1,"1449":1,"1454":2,"1484":1,"1486":1,"1487":6,"1488":2,"1494":1,"1495":11,"1497":1,"1525":1,"1590":1,"1614":1,"1630":1,"1633":1,"1637":1,"1639":2,"1650":1,"1659":1,"1665":1,"1668":1,"1684":1,"1701":1,"1728":1,"1740":1,"1752":1,"1773":2,"1803":1,"1805":6,"1806":1,"1814":1,"1820":2,"1821":4,"1824":1,"1825":1,"1826":1,"1839":5,"1872":1,"1884":1,"1885":1,"1911":1,"1917":1,"1918":1,"1921":1,"1926":1,"1932":2,"1933":2,"1950":1,"1953":3,"1958":1,"1993":1,"1997":1,"1999":2,"2031":2,"2056":3,"2059":1,"2071":1,"2089":1,"2091":1,"2095":2,"2116":1,"2119":1,"2122":1,"2125":1,"2131":1,"2158":1,"2164":3,"2168":2,"2174":2,"2187":1,"2189":1,"2190":1,"2201":1,"2231":1}}],["exceptional",{"3":{"0":2,"109":1,"111":1,"1217":1,"1218":1,"1226":2,"1229":1,"1323":1,"1414":1,"1435":3,"1802":1,"1804":1,"1805":1,"1806":1,"1958":1}}],["exceptions",{"0":{"1226":1,"1496":1,"1803":1},"1":{"106":1,"107":1,"108":1,"109":1,"110":1,"111":1,"112":1,"113":1,"1802":1,"1803":1,"1804":1,"1805":1,"1806":1},"2":{"1229":1},"3":{"0":5,"39":1,"106":1,"108":1,"109":2,"111":1,"120":3,"139":1,"265":1,"314":1,"318":1,"344":1,"549":1,"619":1,"674":2,"677":1,"683":1,"690":1,"798":1,"964":1,"988":1,"992":1,"1004":1,"1049":1,"1192":1,"1203":3,"1207":10,"1211":1,"1212":1,"1217":1,"1218":1,"1226":4,"1229":13,"1247":1,"1256":1,"1259":1,"1270":3,"1271":1,"1278":1,"1285":5,"1286":1,"1299":2,"1310":5,"1311":5,"1322":2,"1323":7,"1338":1,"1349":1,"1358":1,"1372":1,"1395":3,"1414":3,"1416":3,"1435":5,"1466":1,"1488":1,"1495":1,"1503":1,"1538":1,"1551":1,"1576":2,"1639":1,"1652":1,"1661":1,"1699":1,"1785":1,"1801":1,"1802":2,"1803":1,"1804":2,"1805":2,"1806":5,"1839":1,"1919":1,"1922":1,"1926":2,"1929":2,"1932":1,"1939":1,"1953":1,"1958":1,"1993":1,"2011":1,"2042":1,"2205":1,"2229":1,"2231":1}}],["exec",{"3":{"1478":2}}],["executably",{"3":{"1237":1}}],["executable",{"0":{"1488":1},"3":{"0":1,"1116":1,"1134":1,"1162":1,"1169":1,"1214":1,"1229":1,"1237":3,"1247":1,"1271":2,"1284":1,"1285":1,"1310":1,"1311":2,"1346":1,"1349":1,"1352":1,"1358":9,"1435":17,"1437":3,"1444":1,"1456":1,"1460":1,"1484":1,"1488":2,"1490":1,"1522":1,"1524":1,"1525":1,"1570":1,"1573":1,"1575":1,"1590":1,"1669":1,"1781":1,"1792":1,"1810":1,"1897":1,"2037":1,"2039":1,"2042":1,"2062":2,"2074":1}}],["executor",{"3":{"0":1,"881":1,"1133":1,"1135":2,"1136":1,"1137":1,"1175":1,"1176":1,"1180":1,"1308":5,"1309":1,"1322":1,"1323":3,"1394":4,"1395":5,"1414":2,"1416":1,"1435":2,"1652":1}}],["executing",{"3":{"0":1,"157":2,"698":1,"709":1,"995":1,"1044":1,"1212":1,"1246":1,"1247":1,"1270":1,"1276":1,"1285":3,"1299":1,"1310":2,"1322":2,"1331":1,"1358":1,"1415":1,"1435":1,"1437":1,"1576":1,"1850":1,"1908":1,"1911":1,"2191":1,"2231":1}}],["executionandpublication",{"2":{"1287":1},"3":{"1287":1,"1299":1}}],["executions",{"3":{"1263":1,"1270":1,"1322":1,"1707":1,"1820":1,"1908":1}}],["executionlog",{"3":{"1199":4,"1207":1}}],["executionconfigurationbuilder",{"2":{"1067":1},"3":{"1067":1}}],["executioncontext",{"2":{"1246":1},"3":{"265":1,"1246":1,"1323":1,"1416":1}}],["execution",{"0":{"1262":1,"2183":2},"1":{"515":1,"516":1,"517":1,"518":1,"519":1,"520":1,"521":1,"522":1,"523":1,"524":1,"985":1,"986":1,"987":1,"988":1,"989":1,"990":1,"991":1,"992":1},"2":{"2190":1},"3":{"0":9,"21":1,"71":2,"117":1,"119":1,"120":1,"121":1,"122":3,"157":1,"407":1,"435":1,"515":1,"518":1,"549":1,"579":1,"707":3,"708":1,"711":1,"715":1,"819":2,"985":1,"987":2,"988":1,"991":1,"992":1,"996":1,"997":1,"1042":5,"1044":2,"1046":1,"1135":1,"1150":1,"1152":1,"1212":4,"1243":1,"1245":1,"1247":6,"1251":1,"1261":1,"1262":1,"1263":1,"1267":2,"1269":1,"1270":21,"1271":1,"1274":3,"1275":5,"1278":2,"1285":18,"1299":2,"1300":1,"1308":1,"1310":2,"1311":1,"1315":2,"1316":1,"1317":4,"1322":11,"1323":1,"1356":1,"1358":4,"1414":2,"1416":1,"1435":7,"1437":2,"1454":3,"1460":1,"1462":1,"1466":1,"1475":1,"1485":1,"1487":1,"1491":1,"1506":1,"1523":1,"1524":1,"1525":1,"1530":2,"1533":1,"1536":2,"1573":2,"1574":1,"1576":4,"1577":1,"1583":1,"1584":1,"1587":1,"1591":1,"1610":1,"1663":2,"1746":4,"1773":2,"1783":1,"1820":1,"1825":1,"1839":3,"1864":1,"1908":1,"1916":1,"1947":1,"2031":2,"2037":1,"2095":2,"2155":2,"2183":1,"2187":1,"2190":2,"2191":3,"2192":3,"2219":1,"2231":2}}],["executeandstoreasync",{"3":{"1310":1}}],["executeasync",{"2":{"255":1,"391":1,"522":1,"523":1,"524":1,"881":1,"897":1,"988":1,"1100":1,"1309":1,"1398":1,"1413":1,"1652":1,"1664":1,"1918":1,"1921":1,"1923":1,"1945":1,"2183":1},"3":{"0":1,"255":1,"391":1,"522":2,"523":2,"524":1,"536":1,"707":1,"881":1,"897":1,"988":1,"1100":4,"1212":1,"1243":2,"1244":1,"1247":12,"1259":3,"1269":1,"1270":3,"1273":1,"1285":10,"1309":1,"1310":1,"1322":5,"1323":6,"1333":1,"1338":7,"1394":9,"1395":26,"1398":2,"1401":6,"1413":1,"1414":1,"1441":1,"1652":1,"1664":1,"1918":2,"1921":1,"1923":1,"1945":1,"2166":1,"2183":1}}],["executeunderdistributedlockasync",{"3":{"1310":1}}],["executeunderprocesslockasync",{"3":{"1310":1}}],["executeupdateasync",{"2":{"20":1,"24":1,"995":1,"1843":1,"1947":1},"3":{"20":1,"24":1,"698":1,"707":2,"995":1,"1259":4,"1278":1,"1285":17,"1289":1,"1299":1,"1316":1,"1322":3,"1495":1,"1748":1,"1764":1,"1843":1,"1947":1}}],["executeupdate",{"2":{"1255":1,"1843":1},"3":{"0":1,"536":1,"540":1,"1252":1,"1255":1,"1259":3,"1278":1,"1285":5,"1299":1,"1308":1,"1590":1,"1663":1,"1843":2}}],["executerawlookupmethod",{"3":{"1285":1}}],["executerawlookupasync",{"3":{"1285":5}}],["executeclaimedjobasync",{"3":{"1322":2}}],["executeclaimedasync",{"3":{"1285":1}}],["executecycleasync",{"3":{"536":1,"1285":1,"1316":1,"1322":2,"2166":1}}],["executewithserversideincludesasync",{"3":{"1247":2}}],["executewithmanualnavigationasync",{"3":{"1247":2,"1309":1}}],["execute",{"0":{"2187":1},"3":{"0":1,"155":1,"156":1,"157":2,"159":1,"590":1,"707":1,"733":1,"990":1,"995":1,"996":1,"1202":1,"1203":1,"1244":1,"1247":2,"1259":1,"1269":1,"1270":2,"1283":1,"1285":4,"1300":1,"1308":1,"1310":7,"1322":3,"1323":1,"1338":1,"1358":2,"1394":2,"1416":1,"1435":4,"1444":1,"1466":1,"1489":1,"1590":1,"1665":1,"1667":1,"1764":1,"1814":1,"1908":6,"1910":2,"1911":2,"1914":1,"2055":1,"2192":2}}],["executeintransactionasync",{"2":{"21":1,"120":1,"535":1,"536":1,"539":1,"540":1,"987":2,"988":1,"1278":1,"1405":1,"1663":1,"1821":1,"1839":2,"1851":1,"2091":1,"2095":1},"3":{"0":1,"21":1,"120":1,"535":1,"536":1,"539":1,"540":2,"987":2,"988":3,"1212":1,"1263":1,"1270":3,"1278":1,"1285":10,"1405":1,"1414":2,"1590":1,"1663":1,"1821":1,"1839":3,"1851":1,"2091":1,"2095":1}}],["executeprojectedasync",{"2":{"549":1,"1243":1,"1244":1,"1247":1},"3":{"0":1,"549":1,"1243":1,"1244":1,"1247":4}}],["executedeletingfixture",{"3":{"1199":2,"1207":1,"1435":7}}],["executedelete",{"3":{"39":1,"1277":1,"1285":1,"1358":1,"1435":3}}],["executedeleteasync",{"2":{"39":1},"3":{"0":1,"39":1,"905":1,"1278":1,"1285":13,"1358":1,"1435":10,"1525":1}}],["executed",{"3":{"0":5,"130":2,"136":2,"137":4,"140":2,"142":1,"218":1,"626":1,"628":1,"641":1,"827":1,"1042":1,"1050":2,"1055":1,"1212":1,"1246":1,"1247":1,"1270":4,"1285":5,"1310":1,"1311":1,"1317":1,"1323":1,"1338":2,"1358":12,"1379":2,"1394":2,"1395":1,"1401":2,"1404":1,"1410":1,"1415":1,"1430":1,"1435":2,"1437":1,"1440":1,"1454":2,"1488":1,"1495":1,"1525":1,"1590":2,"1705":1,"1783":1,"1823":1,"2033":1}}],["executesql",{"2":{"1132":1},"3":{"1132":1,"1435":1}}],["executesqlinterpolated",{"2":{"1132":1},"3":{"1132":1}}],["executesqlrawasync",{"2":{"1132":1,"1133":1},"3":{"0":1,"1132":1,"1133":1,"1430":1,"1435":3}}],["executesqlraw",{"2":{"1132":1},"3":{"0":1,"1132":1,"1133":1,"1430":1,"1435":3}}],["executes",{"3":{"0":2,"135":1,"136":1,"137":2,"159":2,"265":1,"707":1,"972":1,"1042":1,"1043":1,"1100":1,"1238":1,"1247":1,"1259":2,"1270":4,"1271":2,"1275":1,"1285":4,"1299":1,"1309":2,"1310":2,"1316":1,"1322":1,"1323":2,"1325":1,"1358":2,"1368":1,"1394":1,"1414":1,"1416":3,"1429":1,"1430":1,"1435":2,"1452":1,"1488":1,"1523":1,"1525":1,"1530":1,"1576":1,"1814":1,"1910":2,"2092":1,"2100":1,"2177":1,"2183":1,"2187":1,"2193":1,"2202":1}}],["exe",{"3":{"1285":1,"1415":1,"1478":1,"1485":2}}],["exercisable",{"3":{"1358":1}}],["exercising",{"3":{"914":1,"1271":1,"1426":1,"1435":6,"1436":4,"1455":1,"1459":1,"1489":1,"2066":1}}],["exercised",{"3":{"136":1,"150":1,"267":1,"359":1,"361":1,"448":2,"450":1,"455":1,"572":1,"583":1,"639":1,"659":1,"667":1,"707":1,"733":1,"812":1,"917":1,"998":1,"1075":1,"1212":1,"1237":4,"1247":5,"1259":1,"1264":1,"1270":12,"1271":1,"1285":10,"1299":4,"1300":3,"1308":2,"1310":1,"1322":13,"1323":9,"1338":1,"1349":3,"1358":17,"1363":1,"1368":4,"1379":2,"1394":4,"1395":6,"1401":1,"1414":4,"1416":3,"1426":6,"1428":1,"1431":1,"1435":27,"1437":2,"1445":1,"1487":1,"1489":1,"1495":1,"1499":1,"1576":3,"1670":2,"1672":1,"1841":1,"1859":2,"1883":1,"2054":1,"2062":1,"2128":1,"2130":2,"2142":1,"2227":1}}],["exercises",{"3":{"0":3,"136":1,"170":1,"185":1,"558":1,"573":1,"635":1,"638":1,"641":1,"698":1,"716":1,"758":1,"848":1,"941":1,"954":1,"1020":1,"1270":1,"1271":1,"1285":3,"1299":1,"1308":1,"1310":1,"1322":1,"1323":1,"1338":1,"1358":6,"1368":1,"1394":1,"1395":3,"1401":1,"1426":2,"1435":25,"1437":2,"1445":1,"1455":1,"1486":1,"1489":2,"1525":1,"1534":1,"1584":1,"1599":1,"1610":1,"1662":1,"1672":1,"1701":1,"1705":1,"1928":1,"1951":1,"1959":1,"2052":1,"2072":1,"2096":1,"2112":1,"2227":1,"2231":1}}],["exercise",{"0":{"1799":1},"3":{"0":2,"62":1,"390":1,"650":1,"725":1,"776":1,"838":1,"1154":1,"1259":1,"1270":2,"1285":4,"1310":2,"1323":4,"1338":4,"1358":2,"1379":1,"1394":2,"1395":1,"1401":1,"1416":1,"1426":2,"1428":1,"1429":1,"1434":1,"1435":8,"1454":2,"1487":2,"1490":1,"1493":1,"1525":1,"1652":1,"1697":1,"1890":1,"1922":1,"2054":1,"2128":1}}],["exemplary",{"3":{"1525":3,"1536":1,"1575":1,"1576":2,"1589":1,"1590":2,"1796":1,"1798":1,"2058":1}}],["exemplar",{"0":{"2130":1},"3":{"0":1,"448":1,"449":2,"1212":1,"1322":1,"1342":1,"2130":2,"2131":1}}],["exemptprefixes",{"3":{"1323":1}}],["exemptpartitionkey",{"3":{"1323":1}}],["exemptpathprefixes",{"2":{"189":1},"3":{"189":1,"1299":3}}],["exemptablefixtureservice",{"3":{"1199":2,"1207":1,"1435":3}}],["exempting",{"3":{"1135":1,"1299":1,"1323":1,"1338":1,"1435":7,"2001":1}}],["exemption",{"3":{"0":5,"103":2,"135":6,"136":1,"139":1,"142":1,"173":4,"175":1,"179":2,"180":1,"181":1,"279":1,"480":1,"482":1,"486":1,"487":2,"488":3,"489":3,"490":3,"491":3,"492":3,"493":3,"494":1,"495":1,"507":1,"545":1,"747":1,"749":1,"753":1,"789":1,"792":1,"825":1,"826":1,"827":1,"828":1,"830":1,"832":4,"834":1,"1004":1,"1006":4,"1122":1,"1124":1,"1126":1,"1127":1,"1133":2,"1134":1,"1135":1,"1161":3,"1162":1,"1212":1,"1299":2,"1308":1,"1310":7,"1323":5,"1336":1,"1338":3,"1415":2,"1435":41,"1437":1,"1446":3,"1466":1,"1488":1,"1495":1,"1798":1,"1824":1,"1995":1,"1996":1,"1997":1,"2000":1,"2001":4,"2042":1}}],["exemptions",{"3":{"0":2,"90":1,"103":1,"135":1,"142":1,"545":1,"547":1,"1005":1,"1124":1,"1161":1,"1310":1,"1323":2,"1338":1,"1415":1,"1435":9,"1525":1}}],["exemptfoldersuffixes",{"2":{"1004":1,"2042":1},"3":{"1004":2,"1430":1,"1435":4,"2042":1}}],["exemptmethods",{"3":{"135":1,"139":1,"1435":2}}],["exempt",{"3":{"0":3,"174":1,"178":1,"179":3,"180":1,"189":1,"227":1,"244":1,"280":1,"484":1,"486":1,"487":2,"488":4,"489":5,"490":8,"491":7,"492":8,"493":3,"494":3,"495":2,"552":1,"575":1,"749":1,"792":1,"827":1,"830":1,"831":1,"832":1,"881":1,"1006":1,"1124":3,"1126":1,"1186":1,"1296":2,"1299":6,"1310":1,"1323":3,"1336":1,"1338":4,"1343":1,"1415":2,"1430":1,"1435":17,"1446":2,"1464":1,"1466":1,"1576":1,"1581":1,"1582":1,"1599":1,"1636":1,"1665":1,"1669":1,"1697":1,"1998":2,"2001":3,"2113":1,"2167":1}}],["exemptedoffenderfixture",{"3":{"1199":2,"1207":1,"1435":4}}],["exempted",{"3":{"0":1,"135":1,"136":1,"139":1,"179":1,"265":1,"635":1,"749":1,"1168":1,"1212":1,"1285":1,"1310":1,"1323":1,"1430":1,"1435":10,"1446":1,"1466":1,"1770":1,"2041":1}}],["exemptseverytypeunderit",{"3":{"1435":1}}],["exemptsonlythatmember",{"3":{"1435":2}}],["exempts",{"0":{"1999":1},"3":{"0":4,"175":2,"182":1,"239":1,"243":1,"287":1,"484":1,"545":2,"566":1,"792":1,"826":1,"827":1,"829":1,"832":1,"833":2,"881":1,"884":1,"1135":1,"1161":3,"1163":2,"1299":1,"1310":2,"1336":1,"1338":4,"1435":9,"1454":1,"1464":1,"1590":1,"1997":1,"1999":2,"2001":1,"2023":2,"2231":2}}],["extreme",{"3":{"1358":1,"1416":1,"1435":1}}],["extraforbiddendomaindependencies",{"2":{"2103":1},"3":{"1435":2,"1488":1,"2103":1}}],["extrastatefixtureid",{"3":{"1199":2,"1207":1,"1435":9}}],["extras",{"3":{"598":1,"607":2,"752":1,"1034":1,"1308":1,"1323":9,"1358":1,"1394":1,"1435":2,"1440":1,"1665":1}}],["extra",{"3":{"0":1,"13":1,"24":1,"92":1,"94":1,"95":2,"102":1,"265":1,"275":1,"330":1,"351":1,"383":1,"401":1,"405":1,"426":1,"435":1,"454":2,"471":1,"633":1,"640":1,"649":1,"665":1,"683":1,"690":1,"749":1,"768":1,"782":1,"784":1,"827":2,"871":1,"924":1,"996":1,"1027":1,"1050":1,"1186":1,"1227":1,"1228":1,"1229":3,"1247":7,"1252":1,"1254":1,"1259":8,"1265":1,"1270":9,"1276":1,"1283":1,"1285":14,"1299":7,"1308":1,"1309":1,"1310":8,"1319":1,"1322":5,"1323":7,"1325":1,"1336":1,"1337":1,"1338":6,"1344":1,"1349":3,"1358":13,"1368":1,"1373":1,"1379":1,"1384":1,"1394":12,"1395":3,"1401":2,"1414":4,"1416":3,"1435":23,"1437":1,"1440":2,"1466":1,"1474":1,"1475":1,"1495":1,"1626":1,"1647":1,"1681":1,"1692":1,"1701":1,"1726":1,"1754":1,"1782":1,"1807":1,"1825":1,"1838":1,"1846":1,"1850":1,"1866":1,"1897":1,"1900":1,"1907":1,"1925":1,"1948":1,"1969":1,"2106":1,"2129":1,"2149":1,"2168":1,"2192":1}}],["extracttwitterhandle",{"3":{"1358":2}}],["extractability",{"3":{"1311":1,"1435":1}}],["extractable",{"0":{"1508":1},"1":{"1877":1,"1878":1,"1879":1,"1880":1,"1881":1,"1882":1,"1883":1,"1884":1,"1885":1},"3":{"135":1,"692":1,"1192":3,"1202":1,"1203":1,"1212":1,"1257":1,"1269":1,"1349":1,"1358":3,"1368":1,"1395":2,"1414":2,"1415":1,"1435":2,"1495":2,"1525":1,"1543":2,"1575":1,"1576":1,"1589":1,"1590":1,"1661":1,"1685":1,"1877":1,"2206":1,"2213":1,"2229":1}}],["extractname",{"3":{"1310":1}}],["extractclaims",{"3":{"1310":2}}],["extractors",{"3":{"974":1}}],["extractor",{"3":{"973":1,"1495":5,"1497":1}}],["extractgridfilters",{"3":{"881":1,"1323":1}}],["extractsociallinks",{"3":{"1358":1}}],["extractsortparameters",{"3":{"881":1,"1323":1}}],["extracts",{"3":{"350":1,"1288":1,"1310":2,"1323":2,"1338":1,"1395":1,"1435":1,"1452":1,"1671":1,"1779":1,"2071":1,"2154":1,"2203":1,"2213":1,"2220":1}}],["extracting",{"1":{"2014":1,"2015":1,"2016":1,"2017":1,"2018":1,"2019":1,"2020":1,"2021":1,"2022":1,"2023":1,"2024":1,"2025":1,"2026":1,"2027":1},"3":{"59":1,"734":1,"1109":1,"1247":1,"1259":1,"1270":3,"1285":3,"1299":1,"1310":1,"1323":1,"1338":3,"1349":1,"1358":6,"1394":7,"1395":1,"1415":1,"1435":1,"1543":1,"1645":1,"1778":1,"2013":1,"2014":2,"2071":1,"2208":1}}],["extractions",{"3":{"1495":1}}],["extraction",{"0":{"1306":1,"1789":1,"1865":1,"1883":1,"2008":1},"1":{"56":1,"57":1,"58":1,"59":1,"60":1,"61":1,"62":1,"63":1,"64":1},"3":{"0":5,"31":1,"35":1,"56":1,"57":1,"59":1,"60":1,"62":3,"64":1,"86":1,"88":2,"126":1,"229":1,"325":1,"326":1,"463":1,"545":1,"548":1,"567":1,"582":3,"584":2,"587":1,"640":1,"645":1,"690":1,"707":1,"733":1,"750":1,"753":1,"773":1,"783":1,"786":1,"827":1,"831":1,"846":1,"882":2,"883":1,"935":1,"954":1,"1025":1,"1034":1,"1035":1,"1212":4,"1213":1,"1214":1,"1259":1,"1270":1,"1285":6,"1286":1,"1287":1,"1299":4,"1300":2,"1302":1,"1308":5,"1310":5,"1311":7,"1312":1,"1313":1,"1319":1,"1322":9,"1323":3,"1348":1,"1349":5,"1358":3,"1379":11,"1382":1,"1394":3,"1395":9,"1414":6,"1415":2,"1435":23,"1445":1,"1446":3,"1449":1,"1452":1,"1488":1,"1495":6,"1525":2,"1529":1,"1530":2,"1531":1,"1534":1,"1575":4,"1576":5,"1585":1,"1601":1,"1637":2,"1645":1,"1647":1,"1651":2,"1653":1,"1655":1,"1656":1,"1662":4,"1683":1,"1690":1,"1696":1,"1716":1,"1723":1,"1780":1,"1782":2,"1786":1,"1787":3,"1789":1,"1792":4,"1814":1,"1841":1,"1847":1,"1852":1,"1862":1,"1865":2,"1881":1,"1885":3,"1891":1,"1894":1,"1898":1,"1925":1,"2008":1,"2015":1,"2019":1,"2027":3,"2028":1,"2029":1,"2042":1,"2053":1,"2073":2,"2079":1,"2093":1,"2110":1,"2112":1,"2113":1,"2208":1,"2226":1,"2233":1}}],["extractdeployedregion",{"2":{"972":1,"973":1,"2042":1},"3":{"0":1,"972":2,"973":1,"1435":2,"1488":1,"2042":1}}],["extract",{"0":{"1655":1,"2208":1},"3":{"0":2,"58":1,"59":1,"62":2,"243":1,"396":1,"743":1,"745":1,"1211":1,"1285":1,"1299":2,"1308":1,"1310":3,"1379":1,"1395":1,"1533":1,"1645":2,"1646":2,"1649":1,"1655":1,"1658":1,"1662":1,"1691":1,"1702":1,"1717":1,"1723":1,"1778":1,"1841":1,"1863":1,"1883":1,"1892":1,"1929":1,"2015":1,"2168":1,"2179":1,"2192":1,"2202":1,"2211":1,"2214":1,"2225":1,"2226":1}}],["extracted",{"3":{"0":6,"20":1,"30":2,"31":2,"32":1,"46":1,"57":1,"59":2,"62":2,"63":1,"95":1,"97":1,"191":1,"323":1,"349":1,"398":1,"447":1,"448":2,"458":1,"459":1,"461":1,"564":1,"572":1,"574":1,"578":1,"582":3,"583":1,"584":2,"586":1,"639":1,"640":2,"649":1,"707":1,"708":1,"725":1,"726":1,"733":1,"749":2,"750":1,"882":1,"918":1,"933":1,"972":2,"998":1,"1192":2,"1213":2,"1229":1,"1248":1,"1252":1,"1259":7,"1270":2,"1273":1,"1285":10,"1286":1,"1296":1,"1299":10,"1300":1,"1301":1,"1306":2,"1308":7,"1309":2,"1310":16,"1311":7,"1319":1,"1322":10,"1323":2,"1338":2,"1349":3,"1358":7,"1369":2,"1379":3,"1384":1,"1390":2,"1394":9,"1395":10,"1401":1,"1403":2,"1408":1,"1412":1,"1414":10,"1430":1,"1435":18,"1437":1,"1439":1,"1443":1,"1454":1,"1488":2,"1494":1,"1495":6,"1507":1,"1575":1,"1576":1,"1599":1,"1650":1,"1655":1,"1662":2,"1762":1,"1779":1,"1780":1,"1782":1,"1789":4,"1804":1,"1837":1,"1847":1,"1867":1,"1881":1,"1883":1,"1887":1,"1891":1,"1895":1,"1896":1,"1898":2,"1915":1,"1924":1,"1926":1,"1928":1,"1947":1,"2004":1,"2017":1,"2018":1,"2019":1,"2020":2,"2021":1,"2054":1,"2065":1,"2110":1,"2130":1,"2134":1,"2154":1,"2229":1,"2231":1}}],["extern",{"3":{"1610":1}}],["externalauthbroker",{"3":{"1416":1}}],["externalauthextensionstests",{"3":{"1199":1,"1207":2}}],["externalauthextensions",{"2":{"1414":1},"3":{"350":2,"980":1,"1199":5,"1203":1,"1207":2,"1208":1,"1299":1,"1310":10,"1323":1,"1414":3,"1495":1}}],["externalizing",{"3":{"1299":1}}],["externalized",{"3":{"265":1,"1310":2,"1323":2,"1393":1,"1394":4,"1395":1,"1415":1,"1548":1,"1553":1,"1563":1,"1652":1,"2082":1}}],["externalize",{"3":{"86":1,"1652":1}}],["externalcontractfixtureservice",{"3":{"1199":1,"1207":1,"1435":2}}],["externalsessionid",{"3":{"1285":1}}],["externals",{"3":{"1193":1,"1229":7,"1237":2,"1259":3,"1285":6,"1299":3,"1300":2,"1308":13,"1310":3,"1322":20,"1323":44,"1338":7,"1358":3,"1379":9,"1394":39,"1395":103,"1401":5,"1414":26,"1415":1,"1416":12,"1435":20}}],["externalprovideralreadylinked",{"2":{"350":1,"352":1,"1976":1},"3":{"350":1,"352":1,"1976":1}}],["externalemailnotverified",{"2":{"350":1,"352":1,"353":1,"354":1,"1405":1,"1976":1},"3":{"0":1,"350":1,"352":2,"353":1,"354":1,"1405":1,"1414":2,"1976":1}}],["externalemailinvalid",{"2":{"350":1,"354":1,"1976":1},"3":{"0":1,"350":1,"354":1,"1414":1,"1976":1}}],["externallinkservice",{"3":{"1416":2}}],["externallinktests",{"3":{"1199":1,"1207":3}}],["externallink",{"2":{"2120":1},"3":{"0":1,"413":1,"1323":1,"1416":5,"1598":2,"2120":1}}],["externallinkrequireslocalsignin",{"2":{"350":1,"353":1,"354":1,"1405":1,"1976":1},"3":{"0":1,"350":1,"353":1,"354":1,"1405":1,"1976":1}}],["externalloginemailverifier",{"3":{"1414":1}}],["externalloginscheme",{"2":{"1414":1},"3":{"1310":2,"1414":1}}],["externallogincoreasync",{"2":{"354":1},"3":{"350":1,"354":1,"1414":1}}],["externalloginnotsupported",{"2":{"350":1,"352":1,"1977":1},"3":{"350":1,"352":1,"1299":1,"1977":1}}],["externalloginasync",{"2":{"350":1,"351":1,"353":1,"354":1,"1974":1,"1976":1,"1977":1,"1978":1},"3":{"350":5,"351":1,"353":1,"354":1,"1288":1,"1299":5,"1310":3,"1405":2,"1414":1,"1974":1,"1976":2,"1977":2,"1978":1}}],["externallogin",{"2":{"352":1,"1974":1,"1978":1},"3":{"0":1,"350":2,"352":1,"1310":2,"1405":1,"1414":4,"1666":1,"1974":2,"1978":1}}],["externallyfixedmethods",{"3":{"1435":2}}],["externally",{"3":{"0":4,"350":1,"831":1,"860":1,"1123":1,"1259":7,"1270":1,"1285":1,"1310":1,"1322":2,"1323":5,"1343":1,"1349":7,"1358":3,"1379":1,"1394":3,"1414":2,"1415":1,"1426":2,"1435":2,"1440":1,"1466":2,"1630":1,"1635":2,"1639":1,"1754":1,"1999":1,"2168":1}}],["external",{"0":{"1213":2,"1635":1,"1751":1,"1769":1,"1974":1},"1":{"347":1,"348":1,"349":1,"350":1,"351":1,"352":1,"353":1,"354":1,"355":1,"1972":1,"1973":1,"1974":1,"1975":1,"1976":1,"1977":1,"1978":1},"2":{"601":1,"1476":1,"1974":1},"3":{"0":3,"150":1,"219":1,"284":1,"291":1,"347":1,"348":2,"349":1,"350":15,"351":3,"352":1,"353":4,"355":2,"415":1,"419":1,"440":1,"459":1,"497":1,"499":2,"502":2,"503":1,"519":1,"535":1,"536":1,"537":1,"538":1,"599":1,"601":1,"657":2,"660":1,"706":1,"792":1,"809":1,"810":2,"812":1,"840":1,"862":1,"926":1,"946":1,"948":2,"950":1,"988":1,"996":1,"1004":2,"1073":1,"1074":1,"1090":2,"1188":1,"1191":2,"1192":3,"1202":1,"1203":1,"1210":2,"1212":1,"1237":3,"1258":1,"1259":5,"1270":1,"1271":3,"1285":4,"1297":1,"1299":6,"1300":1,"1308":2,"1310":9,"1322":6,"1323":9,"1338":2,"1339":1,"1341":1,"1343":1,"1346":2,"1349":7,"1354":1,"1357":1,"1358":47,"1368":30,"1379":2,"1390":2,"1394":9,"1395":2,"1401":1,"1404":1,"1405":2,"1411":1,"1414":29,"1415":2,"1416":21,"1417":1,"1426":5,"1435":2,"1437":4,"1453":1,"1454":2,"1466":9,"1476":2,"1486":1,"1495":3,"1533":1,"1545":1,"1552":1,"1598":2,"1628":1,"1632":1,"1638":1,"1639":1,"1640":2,"1666":3,"1668":3,"1736":1,"1748":1,"1778":1,"1901":1,"1971":1,"1972":2,"1973":2,"1974":5,"1976":6,"1977":1,"1978":5,"1982":2,"1999":1,"2066":1,"2120":1,"2121":1,"2125":1,"2160":1,"2167":2,"2168":1,"2170":1,"2207":1,"2231":1}}],["extexp0001",{"3":{"1590":1}}],["extensibility",{"2":{"960":1,"1067":1},"3":{"846":1,"850":1,"960":1,"980":1,"1067":1,"1176":1,"1178":1,"1207":1,"1208":1,"1247":6,"1270":1,"1315":1,"1316":1,"1322":7,"1338":1,"1414":2}}],["extensible",{"3":{"235":1,"920":1,"1270":2,"1310":1,"1322":1,"1349":1,"1358":1,"1368":1,"1394":1,"1665":1}}],["extensiont",{"3":{"1426":1}}],["extensionof",{"3":{"1394":2}}],["extensionmarkerattribute",{"2":{"984":1},"3":{"980":1,"984":1}}],["extensions",{"0":{"1443":1},"2":{"63":1,"171":2,"253":1,"395":1,"409":1,"451":1,"452":1,"665":1,"676":1,"732":1,"733":1,"833":1,"917":1,"1090":1,"1091":1,"1213":2,"1247":1,"1259":1,"1270":1,"1300":2,"1310":2,"1311":3,"1322":2,"1323":1,"1328":2,"1338":4,"1368":1,"1416":1,"1426":6,"1610":1,"1914":1,"1915":1,"2011":1,"2176":1,"2177":1,"2179":1},"3":{"0":3,"61":1,"63":1,"72":2,"166":2,"171":3,"235":3,"237":1,"238":2,"253":1,"255":1,"256":1,"258":2,"259":1,"261":1,"395":1,"397":25,"399":3,"400":3,"401":3,"402":11,"404":4,"407":6,"408":5,"409":3,"451":1,"452":1,"640":3,"665":2,"673":1,"674":1,"676":1,"732":1,"733":1,"819":2,"821":1,"822":1,"827":1,"833":2,"883":2,"884":1,"897":1,"899":1,"900":1,"913":2,"917":1,"920":1,"926":1,"980":6,"982":1,"1006":1,"1018":1,"1034":3,"1037":1,"1066":1,"1090":1,"1091":4,"1103":1,"1199":13,"1202":1,"1203":10,"1207":42,"1208":12,"1213":4,"1229":1,"1236":2,"1237":7,"1247":4,"1259":4,"1270":16,"1271":8,"1278":1,"1285":17,"1295":2,"1299":7,"1300":6,"1308":12,"1310":11,"1311":3,"1322":26,"1323":37,"1324":1,"1325":7,"1326":5,"1328":5,"1331":11,"1332":8,"1333":4,"1336":1,"1338":104,"1349":3,"1358":22,"1368":5,"1379":3,"1381":1,"1394":6,"1395":15,"1414":15,"1415":1,"1416":5,"1421":1,"1424":2,"1426":26,"1435":17,"1436":2,"1437":4,"1440":25,"1441":1,"1442":46,"1443":27,"1447":1,"1449":3,"1486":1,"1495":11,"1534":1,"1574":1,"1576":7,"1582":1,"1585":1,"1590":1,"1610":1,"1655":1,"1661":1,"1665":1,"1669":1,"1670":1,"1706":1,"1782":1,"1826":1,"1914":1,"1915":1,"2011":1,"2155":17,"2156":1,"2158":3,"2176":1,"2177":1,"2179":1}}],["extension",{"0":{"1208":1,"2174":1},"1":{"843":1,"844":1,"845":1,"846":1,"847":1,"848":1,"849":1,"850":1,"977":1,"978":1,"979":1,"980":1,"981":1,"982":1,"983":1,"984":1},"2":{"180":1,"388":1,"980":1,"982":1,"983":1,"1204":1,"1326":1,"1353":1,"1417":1,"1830":1},"3":{"0":20,"39":2,"41":1,"48":1,"68":1,"71":1,"76":1,"80":1,"95":1,"99":1,"109":3,"110":1,"133":1,"164":2,"165":1,"167":1,"180":1,"200":1,"214":1,"215":1,"243":1,"258":3,"265":1,"266":1,"295":1,"300":1,"301":1,"306":1,"341":2,"342":1,"358":1,"388":1,"411":1,"413":1,"415":1,"434":2,"438":2,"443":2,"511":1,"549":3,"574":1,"614":1,"649":2,"724":2,"725":1,"728":1,"749":1,"751":2,"766":1,"826":1,"827":2,"833":1,"843":1,"844":1,"845":1,"847":1,"859":2,"861":2,"889":1,"914":1,"922":1,"926":1,"972":1,"976":1,"977":1,"979":4,"980":20,"981":3,"982":1,"983":3,"984":2,"1004":1,"1033":1,"1034":1,"1059":2,"1067":1,"1089":3,"1116":3,"1117":1,"1119":1,"1124":2,"1125":1,"1126":2,"1204":1,"1211":1,"1212":10,"1214":7,"1224":1,"1225":1,"1229":7,"1236":2,"1237":12,"1239":1,"1243":1,"1244":1,"1245":1,"1247":10,"1258":1,"1259":7,"1262":2,"1265":2,"1270":19,"1271":15,"1280":2,"1281":1,"1284":1,"1285":63,"1296":1,"1299":20,"1300":3,"1303":1,"1308":15,"1309":1,"1310":52,"1311":12,"1312":1,"1313":1,"1315":2,"1322":20,"1323":54,"1325":1,"1326":1,"1333":1,"1335":1,"1338":62,"1339":1,"1346":3,"1349":6,"1353":1,"1358":18,"1367":1,"1368":4,"1376":1,"1377":1,"1378":3,"1379":9,"1381":1,"1388":2,"1391":2,"1394":27,"1395":42,"1397":1,"1399":2,"1401":5,"1414":15,"1415":3,"1416":20,"1417":1,"1419":1,"1420":2,"1422":4,"1423":1,"1426":15,"1428":1,"1429":2,"1430":1,"1431":1,"1432":2,"1434":2,"1435":57,"1437":3,"1440":7,"1441":2,"1443":1,"1455":2,"1486":1,"1487":9,"1495":3,"1499":1,"1525":1,"1574":2,"1576":5,"1581":2,"1584":3,"1585":1,"1598":1,"1618":1,"1630":2,"1633":2,"1639":1,"1647":1,"1650":2,"1657":1,"1665":1,"1692":1,"1734":1,"1768":1,"1783":1,"1798":1,"1800":1,"1809":1,"1814":1,"1822":2,"1824":2,"1830":1,"1850":1,"1859":3,"1860":1,"1867":1,"1870":1,"1873":1,"1879":1,"1890":1,"1891":2,"1902":1,"1918":1,"1925":1,"1994":1,"2021":1,"2041":1,"2062":1,"2074":1,"2076":1,"2087":1,"2102":1,"2112":1,"2121":1,"2124":1,"2125":1,"2127":1,"2147":1,"2148":1,"2151":1,"2170":1,"2171":1,"2179":2,"2202":1,"2219":1,"2226":1,"2231":3}}],["extending",{"3":{"890":1,"1040":1,"1043":1,"1285":1,"1323":1,"1338":1,"1347":1,"1348":1,"1349":1,"1394":3,"1401":3,"1466":1,"2191":1,"2231":1}}],["extend",{"3":{"0":3,"175":1,"189":1,"237":1,"572":1,"658":1,"694":1,"706":1,"749":1,"854":1,"926":1,"1229":1,"1247":2,"1259":1,"1285":1,"1293":1,"1299":4,"1309":1,"1310":5,"1322":2,"1323":1,"1358":2,"1394":4,"1395":3,"1401":1,"1412":1,"1435":3,"1454":1,"1487":2,"1570":1,"1594":1,"1595":1,"1596":1,"1599":1,"1640":1,"1684":1,"2042":1,"2193":1,"2231":1}}],["extends",{"3":{"0":5,"66":1,"295":1,"376":1,"532":1,"549":2,"600":1,"660":1,"686":1,"745":1,"823":1,"825":2,"852":1,"914":1,"920":1,"937":1,"976":1,"981":1,"998":1,"1032":1,"1057":1,"1089":1,"1096":1,"1114":1,"1176":1,"1182":1,"1192":1,"1212":3,"1227":1,"1237":3,"1249":1,"1259":3,"1270":1,"1271":2,"1280":1,"1285":11,"1290":2,"1299":3,"1310":7,"1321":1,"1323":3,"1338":6,"1349":1,"1358":4,"1379":2,"1394":25,"1395":3,"1405":1,"1409":1,"1411":1,"1414":4,"1416":1,"1417":1,"1430":1,"1435":3,"1442":1,"1466":2,"1487":3,"1488":2,"1565":1,"1575":1,"1590":1,"1764":1,"1804":1,"1852":1,"1904":1,"2037":1,"2048":1,"2231":7}}],["extended",{"3":{"0":3,"15":1,"328":1,"674":1,"690":1,"701":1,"834":1,"889":1,"920":1,"1016":1,"1160":1,"1169":1,"1217":1,"1229":1,"1271":2,"1285":4,"1299":1,"1310":4,"1322":1,"1323":2,"1338":2,"1358":2,"1395":1,"1414":1,"1416":4,"1435":5,"1495":2,"1537":1,"1854":1,"1939":1,"2011":1,"2231":1}}],["expr",{"3":{"1247":1}}],["expressiveness",{"3":{"1358":3}}],["expressible",{"3":{"699":1,"823":1,"1229":1,"1270":1,"1285":1,"1308":2,"1310":1,"1358":5,"1401":1,"1414":2,"1415":2,"1416":1}}],["expressing",{"3":{"57":1,"691":1,"926":1,"1041":1,"1275":1,"1310":1,"1322":1,"1323":3,"1349":2,"1358":4,"1394":1,"1401":1,"1414":1,"1843":1,"2182":1}}],["expressionboundliteraltrue",{"3":{"1435":1}}],["expression<",{"3":{"1358":5}}],["expressionsortabletemplatecolumngrid",{"3":{"1435":2}}],["expressions",{"3":{"359":1,"365":1,"434":1,"545":1,"1020":1,"1091":1,"1227":1,"1229":1,"1237":1,"1242":1,"1247":15,"1259":1,"1270":1,"1271":3,"1285":10,"1309":3,"1322":5,"1323":3,"1338":3,"1352":1,"1358":21,"1394":1,"1395":6,"1414":1,"1435":3,"2141":1}}],["expressionvisitor",{"2":{"1816":1,"1858":1},"3":{"0":1,"545":1,"549":1,"1239":1,"1247":2,"1430":1,"1435":2,"1814":1,"1816":1,"1858":1}}],["expression",{"2":{"547":1,"1238":1,"1242":1,"1816":1,"1858":2},"3":{"0":3,"6":1,"318":2,"319":1,"330":1,"332":1,"523":1,"543":2,"545":7,"546":2,"547":3,"548":1,"549":5,"556":1,"633":1,"651":1,"655":1,"657":1,"660":1,"676":1,"697":1,"698":2,"699":1,"707":4,"709":1,"726":1,"1050":1,"1091":1,"1213":1,"1214":1,"1219":1,"1229":3,"1231":1,"1237":7,"1238":1,"1239":9,"1242":3,"1247":75,"1252":1,"1259":6,"1268":1,"1269":2,"1270":9,"1271":21,"1273":1,"1285":51,"1299":15,"1300":3,"1304":1,"1308":12,"1309":17,"1310":8,"1311":1,"1322":26,"1323":21,"1338":12,"1349":14,"1352":2,"1358":139,"1368":3,"1379":6,"1394":13,"1395":25,"1401":10,"1414":17,"1415":3,"1416":6,"1425":1,"1426":1,"1430":1,"1435":33,"1437":1,"1452":1,"1457":1,"1464":1,"1466":1,"1474":2,"1489":1,"1576":1,"1590":1,"1598":1,"1686":1,"1688":1,"1726":2,"1814":8,"1816":4,"1828":4,"1858":3,"1864":2,"1919":1,"1947":2,"1951":1,"1952":1,"1954":1,"1987":3,"1988":1,"1989":1,"1990":1,"1993":1,"2088":1,"2175":1,"2195":1}}],["expressed",{"3":{"0":1,"59":1,"136":1,"243":1,"319":1,"464":1,"509":1,"530":1,"729":1,"781":1,"784":1,"836":1,"837":1,"838":2,"842":1,"1091":1,"1237":3,"1247":1,"1255":1,"1259":1,"1263":1,"1270":3,"1271":2,"1273":1,"1278":1,"1282":1,"1285":6,"1299":6,"1308":2,"1310":2,"1322":3,"1323":3,"1338":7,"1342":1,"1349":8,"1352":1,"1358":28,"1368":1,"1379":1,"1394":6,"1395":13,"1401":2,"1414":6,"1416":2,"1435":6,"1440":1,"1454":1,"1459":1,"1464":1,"1466":1,"1490":1,"1525":1,"1655":1,"1726":1,"1832":1}}],["expresses",{"3":{"0":1,"691":1,"708":2,"783":1,"809":1,"810":1,"954":1,"1231":1,"1237":2,"1247":1,"1271":1,"1285":2,"1299":2,"1300":1,"1308":1,"1337":1,"1358":7,"1379":3,"1394":2,"1395":1,"1416":1,"1435":1,"1454":1,"1539":1,"2041":1}}],["express",{"3":{"0":2,"187":1,"189":1,"203":1,"317":1,"523":1,"547":1,"608":1,"610":1,"675":1,"733":1,"743":1,"810":1,"819":2,"820":1,"855":1,"861":1,"862":1,"889":1,"926":1,"1102":1,"1115":1,"1116":1,"1134":1,"1229":1,"1237":1,"1247":2,"1259":1,"1270":3,"1271":1,"1276":1,"1285":7,"1299":1,"1300":1,"1310":1,"1322":6,"1323":3,"1338":2,"1341":1,"1347":1,"1349":3,"1351":1,"1352":1,"1356":1,"1358":17,"1360":1,"1361":1,"1368":1,"1372":2,"1379":3,"1394":14,"1395":3,"1401":2,"1415":1,"1430":1,"1435":6,"1440":1,"1452":2,"1466":1,"1473":1,"1489":1,"1536":1,"1815":1,"1855":1,"1897":1,"1959":1,"1964":1,"1991":1,"1996":1,"2146":1}}],["exp",{"2":{"500":1,"1979":1},"3":{"499":1,"500":1,"1299":4,"1576":1,"1979":1,"1981":1}}],["expansion",{"3":{"86":1,"265":1,"1259":2,"1285":7,"1323":1,"1435":6,"1437":2,"1453":1,"1525":3,"1533":1,"1563":1,"1576":1,"1638":1}}],["expanding",{"3":{"1277":1,"1285":1,"1474":1}}],["expandoobject",{"3":{"1242":1,"1244":1,"1247":6}}],["expandrelational",{"2":{"698":1,"703":1},"3":{"698":1,"703":1,"1259":2,"1285":10}}],["expands",{"3":{"442":1,"674":1,"869":1,"1259":1,"1285":2,"1299":1,"1310":1,"1435":3,"1453":1,"1846":1}}],["expandedsessions",{"3":{"1394":1}}],["expanded",{"3":{"27":1,"135":1,"468":2,"476":1,"1243":1,"1247":1,"1256":1,"1285":1,"1309":1,"1338":1,"1394":1,"1435":4,"1576":1,"1987":1}}],["expand",{"1":{"561":1,"562":1,"563":1,"564":1,"565":1,"566":1,"567":1,"568":1},"3":{"0":6,"15":1,"26":2,"41":1,"293":2,"296":1,"561":1,"564":6,"566":2,"756":2,"759":1,"761":1,"891":1,"892":1,"893":2,"907":2,"932":1,"1012":1,"1018":1,"1212":2,"1275":1,"1276":2,"1285":10,"1310":3,"1394":3,"1435":1,"1454":6,"1458":1,"1460":2,"1525":2,"1544":2,"1576":2,"1590":2,"1599":1,"1663":1,"1727":1,"2231":2}}],["expert",{"3":{"2223":1,"2239":1}}],["expertise",{"3":{"1368":1,"2217":1}}],["experiment",{"3":{"1299":2}}],["experiences",{"3":{"860":1,"1416":1}}],["experience",{"0":{"1569":1,"2219":1},"2":{"1326":1},"3":{"820":1,"1051":1,"1216":1,"1259":1,"1271":1,"1285":2,"1310":1,"1322":5,"1323":2,"1326":1,"1338":4,"1349":1,"1353":1,"1358":1,"1395":4,"1401":1,"1414":1,"1415":2,"1416":5,"1435":12,"1439":1,"1449":1,"1455":1,"1460":1,"1463":2,"1485":1,"1487":1,"1491":1,"1492":1,"1498":1,"1525":1,"1534":1,"1553":1,"1576":1,"1580":1,"1581":1,"1582":1,"1590":1,"1596":1,"1604":1,"1738":1,"1749":2,"1795":1,"1955":1,"2130":1,"2219":1,"2239":1}}],["experienced",{"3":{"513":1,"1190":1,"2094":1}}],["expecting",{"3":{"1270":1,"1414":1,"1437":1}}],["expects",{"3":{"185":1,"564":1,"634":1,"743":1,"827":1,"831":1,"965":1,"1259":1,"1270":2,"1271":1,"1285":3,"1299":1,"1308":1,"1310":2,"1322":2,"1323":6,"1349":1,"1358":1,"1394":2,"1395":1,"1416":1,"1435":6,"1466":1,"1638":1,"1886":1,"1887":1,"1891":1,"2166":1}}],["expect",{"3":{"77":1,"139":1,"812":1,"1036":1,"1214":1,"1237":1,"1270":1,"1285":1,"1308":1,"1310":1,"1358":1,"1394":1,"1576":1,"1581":1,"1684":1,"1685":1,"1718":1,"1851":1,"1886":1,"2066":1,"2105":1,"2159":1}}],["expectations",{"3":{"689":1,"1270":1,"1299":1,"1300":1,"1435":5,"1454":1,"1546":1,"2032":1}}],["expectation",{"3":{"23":1,"572":1,"585":1,"838":2,"1090":1,"1285":1,"1299":1,"1308":1,"1310":1,"1322":1,"1358":2,"1394":1,"1435":6,"1437":1,"1491":1,"1590":1,"1595":1,"1898":1}}],["expecteddisabledstubs",{"3":{"1435":1}}],["expecteddependencies",{"3":{"1435":5,"1437":1}}],["expectedrequiresdependencies",{"3":{"1435":5,"1437":1}}],["expectedname",{"3":{"1435":10,"1437":1}}],["expectedsecret",{"3":{"1338":1}}],["expectedstepnames",{"3":{"459":1,"464":1,"1310":1,"1435":3,"1487":1}}],["expectedcontract",{"2":{"635":1,"1728":1,"2106":1},"3":{"633":1,"635":1,"1435":6,"1653":1,"1726":1,"1728":1,"2106":1}}],["expectedcommanddecorators",{"2":{"121":1},"3":{"1435":1}}],["expectedquerydecorators",{"2":{"121":1},"3":{"1435":1}}],["expected",{"3":{"0":3,"68":1,"108":2,"109":2,"110":1,"111":1,"126":1,"127":1,"130":1,"132":1,"137":1,"140":1,"141":1,"142":1,"243":1,"386":1,"472":1,"530":1,"565":1,"635":1,"757":1,"765":1,"790":1,"972":1,"973":1,"996":1,"1018":1,"1020":1,"1049":1,"1163":1,"1212":3,"1215":1,"1217":2,"1218":1,"1229":4,"1259":1,"1261":1,"1262":1,"1268":1,"1270":5,"1273":1,"1275":1,"1285":4,"1286":1,"1287":1,"1299":3,"1310":4,"1311":1,"1317":1,"1322":10,"1323":7,"1338":1,"1349":3,"1358":3,"1379":1,"1394":13,"1395":8,"1401":3,"1412":1,"1414":3,"1416":3,"1435":21,"1448":1,"1453":1,"1454":8,"1458":1,"1459":1,"1460":1,"1464":2,"1485":2,"1487":3,"1488":1,"1489":1,"1491":3,"1523":5,"1524":5,"1525":1,"1574":5,"1575":5,"1576":2,"1585":1,"1588":5,"1637":1,"1719":1,"1803":2,"1805":1,"1806":3,"1819":1,"1825":1,"1876":1,"1886":1,"1896":2,"1922":1,"1926":1,"1929":1,"1944":1,"1953":2,"1958":1,"2057":1,"2103":1,"2104":1,"2106":1,"2128":1,"2155":1,"2190":1,"2226":1,"2229":1,"2231":2}}],["expensive",{"0":{"2189":1},"3":{"0":4,"81":1,"108":1,"111":1,"176":1,"234":1,"311":1,"474":1,"516":1,"518":3,"519":1,"520":1,"619":1,"627":1,"683":1,"735":1,"776":1,"800":1,"811":2,"830":1,"921":1,"953":2,"965":1,"1049":1,"1051":2,"1066":1,"1067":1,"1099":3,"1100":1,"1104":1,"1123":1,"1218":1,"1229":2,"1237":1,"1247":2,"1270":4,"1285":1,"1309":1,"1323":1,"1327":1,"1338":2,"1358":5,"1394":3,"1395":2,"1416":1,"1435":5,"1440":1,"1454":2,"1474":1,"1489":1,"1554":1,"1683":1,"1803":1,"1811":1,"1839":1,"1853":1,"1861":1,"1900":1,"1901":1,"1904":1,"1906":1,"1957":1,"1997":1,"1998":1,"1999":1,"2001":1,"2154":2,"2157":1,"2159":2,"2173":1,"2189":1}}],["expiries",{"3":{"1270":1}}],["expiring",{"3":{"534":1,"539":1,"1291":1,"1323":1,"1395":2,"1435":1}}],["expiryrule",{"3":{"1435":6}}],["expiryskew",{"3":{"1323":7,"1416":2}}],["expiryinpast",{"3":{"1299":1}}],["expiry",{"3":{"0":7,"150":1,"159":1,"201":1,"207":2,"209":1,"243":1,"255":1,"302":1,"305":2,"372":1,"458":1,"474":1,"497":2,"498":1,"499":6,"500":1,"505":1,"506":1,"507":2,"509":1,"513":1,"514":1,"529":1,"534":2,"536":3,"537":2,"538":1,"539":3,"627":1,"628":1,"708":1,"853":1,"854":1,"855":1,"905":2,"906":1,"998":1,"1160":1,"1162":1,"1212":3,"1263":1,"1267":1,"1270":1,"1287":1,"1297":2,"1299":23,"1300":4,"1310":1,"1322":5,"1323":10,"1331":1,"1395":5,"1414":3,"1416":2,"1435":12,"1437":1,"1629":1,"1634":1,"1638":2,"1640":4,"1763":1,"1764":2,"1766":3,"1921":1,"1967":3,"1970":1,"1979":1,"1980":1,"1981":3,"1983":1,"1984":1,"2001":1,"2065":2,"2068":1,"2095":1,"2137":2,"2166":3,"2192":1}}],["expiration",{"3":{"0":1,"243":3,"259":1,"388":3,"389":1,"733":4,"1299":1,"1300":19,"1310":7,"1667":1,"1707":1,"1921":2,"1922":1}}],["expireunpaidorderinternalcommand",{"2":{"539":1,"813":1,"814":1,"2188":1},"3":{"539":2,"813":1,"814":1,"2188":1}}],["expireunpaidorderinternalcommandhandler",{"2":{"539":1,"674":1},"3":{"539":2,"674":2,"813":1}}],["expireunpaidorder",{"2":{"534":1,"794":1,"2166":1},"3":{"0":1,"534":1,"794":1,"2166":1}}],["expirestrandedunpaidordersasync",{"2":{"2166":1},"3":{"2166":1}}],["expiresin",{"3":{"1640":2}}],["expiresatunixseconds",{"3":{"1299":5}}],["expiresat",{"3":{"905":3,"1299":3,"1338":2,"1435":1}}],["expires",{"3":{"0":2,"19":1,"20":1,"27":1,"392":1,"458":2,"459":2,"460":1,"461":1,"498":1,"536":1,"707":1,"788":1,"793":2,"794":1,"881":1,"905":1,"907":1,"1028":1,"1029":1,"1044":1,"1074":1,"1183":1,"1255":1,"1259":6,"1270":3,"1275":1,"1293":1,"1299":7,"1300":2,"1310":6,"1317":1,"1322":5,"1323":5,"1338":1,"1358":1,"1394":2,"1395":4,"1416":2,"1428":1,"1435":1,"1437":1,"1466":1,"1638":1,"1640":5,"1840":1,"1908":1,"1910":1,"1916":1,"1917":1,"1921":1,"1946":1,"1980":1,"2065":1,"2095":1,"2166":1,"2168":1,"2188":1,"2191":1}}],["expiredflag",{"3":{"1435":2}}],["expired",{"2":{"790":1,"1769":1},"3":{"0":2,"350":1,"499":2,"500":1,"513":1,"536":3,"608":1,"612":1,"768":1,"790":1,"813":1,"853":2,"854":1,"855":1,"856":1,"904":1,"905":5,"996":1,"997":1,"1026":1,"1042":1,"1212":1,"1255":1,"1263":1,"1270":5,"1285":2,"1286":1,"1287":1,"1288":1,"1289":3,"1293":1,"1297":1,"1299":41,"1300":2,"1310":1,"1322":7,"1323":13,"1357":1,"1358":3,"1379":1,"1395":1,"1407":1,"1414":3,"1430":1,"1435":4,"1466":1,"1640":5,"1707":2,"1746":1,"1764":3,"1769":1,"1896":1,"1915":1,"1933":1,"1947":1,"1967":2,"1971":1,"1974":1,"1981":5,"1984":4,"2157":2,"2162":1,"2166":2,"2187":1}}],["expire",{"3":{"0":1,"536":1,"540":1,"793":1,"1270":1,"1282":1,"1299":2,"1300":1,"1310":2,"1323":3,"1394":1,"1414":1,"1435":1,"1437":1,"1630":1,"1634":1,"1766":3,"2196":1}}],["expo",{"3":{"1339":1,"1341":1,"1349":3,"1358":3,"1394":1}}],["exponent",{"3":{"279":1,"1259":1,"1275":1,"1285":1,"1291":1}}],["exponentially",{"3":{"1840":1}}],["exponential",{"3":{"0":4,"15":1,"19":1,"23":1,"72":1,"73":1,"150":1,"175":1,"281":1,"282":1,"285":1,"640":1,"818":1,"827":1,"881":1,"1041":1,"1212":1,"1256":1,"1259":2,"1275":1,"1291":1,"1299":2,"1322":3,"1323":3,"1329":1,"1338":2,"1395":1,"1525":1,"1575":1,"1576":1,"1640":1,"1664":2,"1666":1,"1706":1,"1841":1,"1902":1,"1909":1,"1999":1,"2000":1,"2001":2,"2073":1,"2182":1,"2231":1}}],["exposing",{"3":{"41":1,"60":1,"188":1,"206":1,"225":1,"300":1,"413":1,"939":1,"1237":2,"1239":2,"1270":4,"1285":5,"1308":3,"1310":5,"1311":1,"1322":3,"1323":6,"1338":1,"1349":6,"1358":7,"1367":1,"1368":2,"1379":1,"1381":1,"1394":3,"1395":3,"1401":1,"1414":4,"1416":3,"1430":1,"1435":6,"1487":2,"1650":1,"1814":1,"1830":1,"1961":1,"1963":1,"2022":1}}],["exposure",{"3":{"0":1,"177":1,"459":2,"461":1,"465":1,"481":1,"498":1,"501":2,"520":1,"691":1,"735":1,"792":1,"799":2,"801":1,"802":1,"981":1,"1019":1,"1135":1,"1237":1,"1247":2,"1270":2,"1285":1,"1299":3,"1306":1,"1310":2,"1322":2,"1323":2,"1358":7,"1395":3,"1423":1,"1435":2,"1476":1,"1491":1,"1523":1,"1531":1,"1533":1,"1640":1,"1922":1,"1979":1}}],["expose",{"0":{"1290":1},"3":{"76":1,"141":1,"397":1,"507":1,"545":1,"940":1,"1168":2,"1237":1,"1247":2,"1270":1,"1299":3,"1309":1,"1310":8,"1311":1,"1322":2,"1323":2,"1332":1,"1338":3,"1349":1,"1358":2,"1371":1,"1379":1,"1394":3,"1395":4,"1435":8,"1441":1,"1444":2,"1487":1,"1599":1,"1666":1,"1876":1,"1927":1,"1990":1,"2192":1,"2238":1}}],["exposes",{"3":{"0":4,"24":1,"31":1,"68":1,"78":1,"81":1,"102":1,"160":1,"174":1,"243":1,"329":1,"330":1,"341":1,"459":1,"469":1,"507":1,"545":1,"547":2,"549":1,"578":1,"599":1,"618":1,"649":1,"657":1,"698":1,"725":1,"727":1,"840":1,"854":1,"869":1,"905":1,"924":1,"959":1,"988":1,"996":1,"1018":1,"1042":1,"1059":1,"1060":1,"1067":1,"1083":1,"1123":1,"1161":1,"1168":1,"1175":2,"1212":1,"1237":4,"1239":1,"1241":2,"1244":1,"1247":6,"1257":1,"1259":1,"1264":5,"1267":1,"1270":5,"1274":1,"1284":1,"1285":9,"1286":1,"1292":1,"1295":1,"1299":15,"1300":5,"1302":1,"1305":1,"1308":2,"1309":3,"1310":10,"1315":2,"1317":1,"1319":1,"1322":3,"1323":10,"1331":1,"1335":1,"1336":1,"1338":2,"1340":1,"1341":1,"1349":8,"1358":11,"1365":1,"1368":5,"1370":2,"1377":2,"1379":5,"1382":1,"1384":1,"1394":14,"1395":17,"1399":1,"1401":2,"1407":1,"1414":11,"1415":3,"1416":8,"1419":2,"1420":1,"1426":2,"1428":2,"1430":2,"1431":1,"1434":1,"1435":16,"1440":1,"1443":1,"1452":1,"1454":1,"1464":1,"1466":1,"1487":2,"1525":1,"1576":2,"1606":1,"1629":3,"1650":1,"1661":1,"1663":2,"1666":1,"1714":1,"1740":1,"1754":1,"1775":2,"1781":1,"1789":1,"1793":1,"1809":1,"1810":1,"1814":2,"1815":1,"1870":1,"1874":1,"1888":1,"1917":1,"1931":1,"1981":1,"1987":1,"1999":1,"2006":1,"2013":1,"2020":1,"2056":1,"2091":1,"2103":1,"2121":1,"2125":1,"2155":1,"2184":1}}],["exposed",{"3":{"0":2,"53":1,"92":1,"93":1,"94":1,"100":1,"130":1,"156":1,"160":1,"207":1,"243":1,"292":1,"305":1,"599":1,"743":1,"751":1,"881":1,"883":1,"924":1,"1050":1,"1100":2,"1228":1,"1237":3,"1239":1,"1240":1,"1247":2,"1259":1,"1262":1,"1270":4,"1285":2,"1289":1,"1296":1,"1299":8,"1309":1,"1310":4,"1311":4,"1322":6,"1323":5,"1329":1,"1338":4,"1342":1,"1346":1,"1349":11,"1353":1,"1358":12,"1368":2,"1379":2,"1390":1,"1394":2,"1395":4,"1397":1,"1401":3,"1406":2,"1414":1,"1415":2,"1431":1,"1435":11,"1440":1,"1466":1,"1474":1,"1547":1,"1549":1,"1590":1,"1629":2,"1631":2,"1638":3,"1640":1,"1646":1,"1669":1,"1754":1,"1764":1,"1775":1,"1789":1,"1936":1,"2074":1,"2137":1}}],["exportforbidden",{"3":{"1322":2,"1414":1}}],["exportfilenameprefix",{"3":{"741":1}}],["exportparameters",{"3":{"1299":1}}],["exporting",{"3":{"1264":1,"1270":1,"1299":1,"1308":1,"1358":1,"1414":3,"1415":1,"1435":1,"1454":1,"1466":2}}],["exportcalendarasync",{"3":{"1379":2}}],["exportcalendar",{"3":{"1203":5,"1207":16,"1299":6,"1349":1,"1355":3,"1358":15,"1437":1,"1495":1}}],["exportcontenttype",{"3":{"725":1,"1310":2}}],["exporttestcontroller",{"3":{"1199":2,"1207":1}}],["exporttestentity",{"3":{"1199":7,"1207":1}}],["exporttestdto",{"3":{"1199":5,"1207":1}}],["exportmoney",{"3":{"1199":3,"1207":1}}],["exportrowlimitheadername",{"2":{"741":1},"3":{"741":1,"1310":1}}],["exportability",{"3":{"744":1}}],["exportable",{"3":{"725":1,"743":1,"1310":1,"1355":1,"1358":5,"1486":1}}],["exportasync",{"2":{"318":1,"324":2,"743":1},"3":{"318":2,"324":2,"725":1,"741":3,"743":4,"1310":12,"1322":2,"1358":1,"1371":1,"1379":18,"1414":5,"1437":1,"1994":1}}],["exportuserdataregistrationtests",{"3":{"1199":1,"1207":2,"1414":2,"1437":2}}],["exportuserdatahandler",{"3":{"724":4,"1199":3,"1203":1,"1207":2,"1299":2,"1308":1,"1322":3,"1395":5,"1409":2,"1414":22,"1534":1,"1599":1}}],["exportuserdatahandlertests",{"2":{"724":1},"3":{"724":1,"1199":1,"1207":3,"1414":2,"1437":2}}],["exportuserdatahandlerbasetests",{"3":{"1199":1,"1207":7,"1322":4,"1437":2}}],["exportuserdatahandlerbase",{"2":{"723":1,"725":1,"1298":1,"1402":1,"1671":1,"2066":1},"3":{"0":1,"723":1,"725":3,"727":1,"728":1,"1199":4,"1203":1,"1207":2,"1212":1,"1270":2,"1298":1,"1299":11,"1321":5,"1322":18,"1402":1,"1409":1,"1414":11,"1437":1,"1495":2,"1671":1,"2066":1}}],["exportuserdataquery",{"2":{"723":1,"726":1},"3":{"726":1,"1199":6,"1203":1,"1207":2,"1310":1,"1322":3,"1409":2,"1414":8}}],["exportuserdata",{"2":{"1499":1},"3":{"585":1,"724":2,"725":4,"727":1,"728":1,"1203":8,"1207":26,"1299":4,"1308":1,"1321":2,"1322":17,"1395":2,"1409":3,"1414":21,"1437":1,"1499":1}}],["exporteventcalendarhandlertests",{"3":{"1199":1,"1207":2,"1358":2,"1437":1}}],["exporteventcalendarhandler",{"3":{"1199":2,"1203":1,"1207":2,"1299":5,"1355":6,"1358":6,"1437":1}}],["exporteventcalendarquery",{"3":{"1199":5,"1203":1,"1207":2,"1358":3,"1379":1,"1495":1}}],["exporter",{"2":{"397":1,"1332":1,"2009":1,"2155":1,"2158":1},"3":{"395":1,"397":2,"399":1,"741":4,"744":1,"1213":1,"1310":6,"1332":2,"1338":6,"1442":3,"1466":1,"2009":2,"2155":1,"2158":1,"2159":2}}],["exporters",{"3":{"0":1,"397":2,"399":1,"402":1,"409":1,"591":1,"608":1,"1332":4,"1338":6,"1442":5,"1491":1,"1576":3,"1922":1,"2009":2,"2013":1,"2046":1,"2156":4,"2158":1,"2159":1}}],["exported",{"0":{"1332":1},"3":{"0":3,"47":1,"396":1,"407":1,"428":1,"440":1,"556":1,"591":2,"764":1,"766":1,"899":1,"1089":1,"1213":1,"1310":3,"1322":1,"1323":1,"1338":5,"1349":2,"1358":13,"1395":6,"1401":1,"1403":1,"1409":1,"1414":6,"1415":3,"1416":2,"1434":1,"1435":3,"1440":1,"1455":1,"1466":1,"1472":1,"1474":1,"1491":2,"1630":1,"1669":1,"1764":1,"2046":1,"2214":1}}],["exportsectionunavailable",{"3":{"1322":6}}],["exportsessioncalendarhandlertests",{"3":{"1199":1,"1207":2,"1358":2}}],["exportsessioncalendarhandler",{"3":{"1199":2,"1203":1,"1207":2,"1299":5,"1355":4,"1358":4}}],["exportsessioncalendarquery",{"2":{"1374":1},"3":{"1199":4,"1203":1,"1207":2,"1358":3,"1374":1,"1379":3,"1495":1}}],["exportshapetestcontroller",{"3":{"1199":2,"1207":1}}],["exportshapetestdto",{"3":{"1199":3,"1207":1}}],["exports",{"3":{"0":2,"81":1,"324":2,"397":1,"400":1,"404":2,"408":1,"557":1,"583":1,"585":3,"591":1,"725":2,"728":1,"741":1,"743":1,"821":1,"1203":8,"1207":24,"1247":1,"1259":1,"1270":1,"1299":5,"1310":3,"1311":2,"1322":1,"1338":1,"1349":2,"1358":6,"1379":1,"1395":24,"1401":2,"1416":3,"1435":13,"1437":2,"1441":2,"1442":2,"1443":1,"1454":1,"1455":1,"1459":1,"1464":1,"1487":1,"1489":1,"1534":1,"1544":1,"1630":2,"1763":1,"1764":2,"1894":1,"2003":1}}],["export",{"0":{"1298":1,"1307":1,"1355":1,"1409":1},"1":{"722":1,"723":1,"724":1,"725":1,"726":1,"727":1,"728":1,"729":1,"738":1,"739":1,"740":1,"741":1,"742":1,"743":1,"744":1,"745":1,"1402":1,"1403":1,"1404":1,"1405":1,"1406":1,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1413":1,"1414":1},"2":{"690":1,"916":1,"1477":1,"1765":1},"3":{"0":14,"18":1,"39":1,"41":1,"95":3,"96":2,"104":2,"318":3,"321":2,"324":2,"325":1,"330":1,"397":1,"399":1,"408":1,"440":1,"545":1,"583":1,"585":3,"586":3,"650":2,"690":2,"692":4,"694":1,"696":2,"709":1,"722":1,"723":2,"724":6,"725":10,"727":12,"728":2,"729":1,"738":1,"740":3,"741":18,"742":4,"743":13,"744":2,"745":1,"896":2,"914":2,"916":2,"973":1,"976":1,"1018":1,"1020":2,"1026":2,"1028":1,"1044":1,"1192":4,"1201":1,"1202":2,"1203":4,"1207":6,"1212":6,"1229":1,"1256":1,"1259":1,"1270":1,"1285":1,"1298":2,"1299":20,"1306":1,"1307":1,"1308":49,"1310":47,"1311":1,"1316":2,"1321":2,"1322":32,"1323":4,"1327":1,"1332":1,"1338":8,"1349":3,"1350":1,"1355":3,"1358":18,"1370":2,"1371":5,"1372":1,"1379":42,"1395":58,"1401":2,"1402":3,"1403":2,"1404":1,"1409":2,"1414":67,"1415":2,"1416":6,"1435":9,"1436":1,"1437":15,"1440":2,"1442":7,"1445":1,"1447":1,"1449":2,"1454":2,"1466":7,"1468":1,"1469":1,"1475":1,"1477":4,"1481":2,"1483":1,"1486":4,"1495":4,"1525":3,"1533":2,"1534":1,"1544":2,"1566":1,"1574":1,"1576":3,"1581":3,"1585":1,"1589":1,"1590":3,"1630":5,"1638":1,"1659":1,"1661":1,"1665":7,"1666":1,"1667":1,"1669":2,"1671":1,"1676":1,"1719":1,"1745":2,"1746":2,"1748":1,"1750":4,"1763":1,"1764":4,"1765":1,"1771":4,"1840":1,"1897":1,"1987":5,"1990":1,"1994":10,"1996":1,"2013":1,"2066":5,"2067":2,"2125":1,"2127":1,"2155":1,"2158":1,"2159":2,"2170":1,"2191":1,"2231":4}}],["exploiting",{"3":{"2118":1}}],["exploits",{"3":{"1322":1,"1323":1,"1435":1,"1446":1}}],["explodes",{"3":{"1435":1}}],["exploding",{"3":{"1299":1}}],["explosion",{"3":{"1247":1,"1285":2,"1863":1}}],["exploring",{"3":{"2227":1}}],["explore",{"3":{"2211":1,"2243":1}}],["explorer",{"3":{"446":1,"448":4,"1050":1,"1310":4,"2130":1,"2131":1}}],["exploration",{"3":{"740":1}}],["explainable",{"3":{"1349":1,"1358":2,"1395":2}}],["explainswhatismissinganddoesnotcalltheservice",{"3":{"1435":1}}],["explains",{"3":{"1000":1,"1193":1,"1216":1,"1229":1,"1237":3,"1247":8,"1259":8,"1270":4,"1271":1,"1285":23,"1299":20,"1300":2,"1308":6,"1310":8,"1322":9,"1323":16,"1327":1,"1338":17,"1343":1,"1349":10,"1358":16,"1368":5,"1394":20,"1395":24,"1396":1,"1401":6,"1404":1,"1414":15,"1415":3,"1416":7,"1426":1,"1435":21,"1442":1,"1444":1,"1446":1,"1452":1,"1455":1,"1458":1,"1464":1,"1466":4,"1475":2,"1488":1,"1496":1,"1657":1,"1684":1,"1704":1,"1801":1,"2002":1,"2088":1,"2166":1,"2175":1,"2177":1,"2210":1}}],["explain",{"3":{"982":1,"990":1,"1237":1,"1247":5,"1259":1,"1270":2,"1271":1,"1285":5,"1299":7,"1300":1,"1310":2,"1311":2,"1312":1,"1322":4,"1323":1,"1338":1,"1349":5,"1358":4,"1379":1,"1394":2,"1395":1,"1401":1,"1412":1,"1414":1,"1435":2,"1438":1,"1442":1,"1448":1,"1454":1,"1501":1,"1514":1,"1551":1,"1638":1,"2165":1}}],["explained",{"3":{"905":1,"972":2,"1191":1,"1194":1,"1216":2,"1237":1,"1299":1,"1308":1,"1310":1,"1323":1,"1338":1,"1358":4,"1368":1,"1379":1,"1394":2,"1415":1,"1416":1,"1435":1,"1440":1,"1492":1,"1493":1,"1494":1,"1495":1,"1498":4,"1799":1}}],["explaining",{"3":{"0":1,"365":1,"373":1,"424":1,"484":1,"626":1,"700":1,"750":1,"820":1,"1190":1,"1222":1,"1237":3,"1259":1,"1271":1,"1285":2,"1299":4,"1310":1,"1322":1,"1323":2,"1338":2,"1358":2,"1361":1,"1368":1,"1394":2,"1395":3,"1435":5,"1488":1,"2142":1,"2196":1}}],["explanation",{"3":{"642":1,"1133":1,"1161":1,"1232":1,"1299":1,"1338":1,"1368":1,"1394":1,"1395":4,"1416":1,"1426":1,"1435":1,"1466":1,"1802":1}}],["explanatory",{"3":{"93":1,"616":1,"856":1,"1052":1,"1259":1,"1270":1,"1285":2,"1322":1,"1358":2,"1416":2,"1626":1}}],["explicitcapture",{"3":{"1308":2,"1426":1}}],["explicitsource",{"3":{"1285":2}}],["explicitorderupdatecommandvalidator",{"3":{"1199":2,"1207":1}}],["explicitassemblyprovider",{"2":{"1283":1},"3":{"1199":2,"1203":1,"1207":1,"1283":1,"1285":4}}],["explicitkeyinsertgroup",{"3":{"1175":2}}],["explicitly",{"2":{"1843":1},"3":{"0":8,"15":1,"24":1,"25":1,"31":1,"39":1,"54":1,"70":1,"71":1,"73":1,"93":1,"100":1,"109":1,"135":1,"175":1,"184":1,"186":1,"193":1,"195":1,"196":1,"243":1,"317":1,"348":1,"353":1,"415":1,"556":1,"564":1,"571":1,"590":1,"595":1,"625":1,"627":1,"630":1,"657":1,"690":1,"698":1,"733":1,"741":1,"758":1,"775":1,"782":1,"819":1,"823":1,"827":1,"881":1,"897":1,"899":1,"905":1,"922":1,"931":2,"953":1,"954":1,"955":1,"963":1,"973":1,"996":1,"1012":1,"1014":1,"1051":1,"1059":1,"1118":1,"1124":1,"1133":1,"1190":1,"1193":1,"1212":3,"1229":4,"1237":4,"1241":1,"1247":7,"1257":1,"1259":10,"1270":8,"1271":4,"1275":1,"1276":2,"1285":32,"1287":1,"1296":2,"1298":1,"1299":28,"1300":6,"1301":1,"1303":1,"1308":8,"1309":2,"1310":17,"1311":3,"1322":19,"1323":20,"1329":1,"1332":1,"1336":1,"1338":8,"1342":1,"1346":1,"1348":1,"1349":8,"1351":1,"1353":1,"1358":28,"1361":3,"1368":2,"1378":1,"1379":6,"1384":1,"1385":1,"1387":1,"1394":32,"1395":25,"1401":6,"1403":1,"1408":1,"1414":25,"1415":1,"1416":11,"1421":1,"1426":1,"1430":1,"1435":18,"1437":2,"1440":2,"1442":2,"1443":1,"1446":1,"1452":1,"1454":4,"1457":1,"1458":1,"1459":1,"1461":1,"1466":4,"1469":1,"1473":1,"1474":1,"1475":1,"1487":4,"1488":1,"1493":1,"1495":2,"1507":1,"1553":1,"1571":1,"1576":1,"1590":2,"1630":2,"1638":1,"1666":1,"1684":1,"1685":1,"1708":1,"1723":1,"1798":1,"1826":1,"1841":1,"1843":1,"1852":1,"1860":1,"1875":1,"1880":1,"1896":1,"1897":1,"1898":1,"1915":1,"1957":1,"1967":1,"1991":1,"1996":2,"2001":1,"2031":1,"2032":1,"2040":1,"2042":1,"2043":1,"2054":1,"2062":1,"2066":1,"2085":1,"2096":1,"2130":1,"2166":1,"2168":1,"2231":1}}],["explicit",{"0":{"1954":1},"1":{"1080":1,"1081":1,"1082":1,"1083":1,"1084":1,"1085":1,"1086":1,"1087":1,"1950":1,"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1956":1,"1957":1,"1958":1},"2":{"1083":1,"1084":1,"1582":1,"2063":1},"3":{"0":14,"5":1,"7":1,"15":1,"19":1,"22":2,"24":1,"31":1,"39":1,"68":1,"72":2,"73":1,"74":1,"78":1,"86":1,"95":3,"99":1,"108":1,"115":1,"118":1,"128":1,"173":1,"186":2,"189":1,"193":1,"195":1,"198":1,"200":1,"201":1,"215":1,"219":1,"243":1,"255":1,"265":4,"266":1,"274":1,"291":1,"312":1,"318":1,"320":1,"321":1,"353":1,"358":1,"368":1,"370":1,"373":1,"386":1,"390":2,"393":1,"397":1,"398":1,"402":1,"418":1,"420":1,"427":1,"428":1,"485":1,"499":1,"518":1,"537":1,"549":3,"556":1,"557":1,"558":1,"564":1,"583":1,"584":1,"618":1,"626":1,"628":1,"633":1,"640":1,"674":1,"709":1,"715":1,"742":1,"766":1,"774":1,"799":1,"817":1,"840":1,"847":2,"881":1,"905":2,"914":2,"926":2,"931":2,"932":1,"933":1,"939":1,"941":1,"942":1,"947":1,"965":1,"971":1,"980":1,"982":1,"988":1,"996":1,"998":1,"999":1,"1012":1,"1013":1,"1035":1,"1050":3,"1052":2,"1060":1,"1066":1,"1067":1,"1080":1,"1083":3,"1084":2,"1092":1,"1108":1,"1117":1,"1133":1,"1175":3,"1212":2,"1213":1,"1219":1,"1221":1,"1229":8,"1233":2,"1236":1,"1237":9,"1239":1,"1241":1,"1247":7,"1249":2,"1253":1,"1256":1,"1257":1,"1259":14,"1263":2,"1264":1,"1269":1,"1270":12,"1271":3,"1275":1,"1278":2,"1280":2,"1285":48,"1287":1,"1299":33,"1300":8,"1308":20,"1309":2,"1310":20,"1311":10,"1315":1,"1316":1,"1317":1,"1319":2,"1321":1,"1322":25,"1323":41,"1330":3,"1332":2,"1337":2,"1338":25,"1341":1,"1342":1,"1344":1,"1349":30,"1352":1,"1353":1,"1357":1,"1358":65,"1363":1,"1368":15,"1372":1,"1374":1,"1375":1,"1379":12,"1382":1,"1389":1,"1391":1,"1392":1,"1393":1,"1394":30,"1395":42,"1398":1,"1400":1,"1401":23,"1410":1,"1414":36,"1415":12,"1416":19,"1426":2,"1435":46,"1437":1,"1439":1,"1440":1,"1441":1,"1442":2,"1443":1,"1444":1,"1447":1,"1450":1,"1452":1,"1453":3,"1454":2,"1457":1,"1466":4,"1468":1,"1473":1,"1474":2,"1475":1,"1485":1,"1486":1,"1488":1,"1495":1,"1525":4,"1533":1,"1540":1,"1542":1,"1543":1,"1544":1,"1546":1,"1552":3,"1555":1,"1565":1,"1575":1,"1576":12,"1581":1,"1582":1,"1590":1,"1630":2,"1631":1,"1638":1,"1640":1,"1650":1,"1666":1,"1672":1,"1676":1,"1685":1,"1692":1,"1710":1,"1748":1,"1764":1,"1794":1,"1829":1,"1840":1,"1841":1,"1857":1,"1888":2,"1889":1,"1891":1,"1897":3,"1902":1,"1903":1,"1909":2,"1910":1,"1947":1,"1949":1,"1950":2,"1951":2,"1954":2,"1956":1,"1957":3,"1958":3,"1961":1,"1962":1,"1963":1,"1964":2,"1981":1,"1993":1,"1994":1,"1995":1,"1996":1,"2000":1,"2018":1,"2029":3,"2032":1,"2062":1,"2063":2,"2066":1,"2082":1,"2084":1,"2085":1,"2126":1,"2137":1,"2141":1,"2151":1,"2156":1,"2159":1,"2160":1,"2161":1,"2164":1,"2168":2,"2174":1,"2178":1,"2184":1,"2185":1,"2197":1,"2207":1,"2231":3}}],["sf1nopko",{"2":{"1363":1},"3":{"1363":1,"1368":1}}],["svg",{"3":{"1323":1,"2124":1}}],["svc",{"3":{"248":1,"1300":3}}],["sdlc",{"3":{"1394":1,"2238":1}}],["sd",{"3":{"1323":3,"2075":1}}],["sdks",{"3":{"74":1,"1076":1,"1213":1,"1416":2,"1435":1,"1576":1}}],["sdk",{"2":{"877":1,"1459":1},"3":{"0":4,"72":5,"73":6,"74":1,"374":1,"439":1,"868":2,"869":1,"870":1,"873":1,"877":1,"980":3,"982":3,"1016":1,"1018":1,"1067":3,"1075":1,"1076":1,"1091":5,"1092":1,"1095":1,"1212":1,"1270":1,"1285":1,"1322":3,"1338":3,"1350":1,"1358":1,"1415":9,"1416":3,"1419":2,"1426":10,"1429":1,"1430":1,"1435":13,"1437":1,"1440":1,"1442":1,"1444":3,"1449":1,"1454":4,"1459":1,"1466":2,"1478":2,"1487":1,"1489":1,"1525":2,"1552":2,"1576":1,"1581":1,"1590":1,"1647":1,"1681":1,"1692":1,"1717":2,"1718":1,"2003":1,"2042":1,"2087":1,"2103":2,"2123":1,"2125":1,"2169":2,"2170":1,"2171":1,"2177":1,"2179":4}}],["sb",{"3":{"1322":1,"1435":3}}],["sbom",{"1":{"367":1,"368":1,"369":1,"370":1,"371":1,"372":1,"373":1,"374":1,"375":1,"376":1},"3":{"0":2,"367":1,"368":1,"369":2,"370":5,"371":4,"372":2,"373":3,"374":3,"375":4,"413":1,"415":1,"530":1,"531":1,"532":1,"590":1,"861":1,"870":1,"874":1,"1212":1,"1444":1,"1453":11,"1460":2,"1525":5,"1534":1,"1568":2,"1575":2,"1576":6,"1584":1,"1585":1,"1590":2,"1599":1,"1672":1,"1737":1,"1800":1,"2119":1,"2121":1,"2231":1}}],["s6672",{"3":{"1310":2}}],["s6932",{"3":{"790":1,"1308":1}}],["s5693",{"3":{"1379":1,"1394":1}}],["s5332",{"3":{"1308":1,"1311":1,"1322":1,"1358":1}}],["s5122",{"3":{"0":1,"774":2,"776":1,"777":1,"1310":1,"1338":1}}],["s4",{"3":{"1456":2}}],["s4275",{"3":{"1394":1}}],["s4830",{"3":{"1285":1,"1415":1}}],["s4035",{"3":{"0":1,"964":1,"1231":1,"1235":1,"1237":2,"1299":2}}],["sr",{"3":{"1358":3,"1596":1,"1606":1,"1608":1,"1740":1,"1742":1}}],["srp",{"3":{"1270":1,"1271":1,"1430":1,"1435":1,"1525":1,"1537":2,"1576":2,"1581":1,"1583":1}}],["srcset",{"3":{"1598":1}}],["src",{"3":{"0":2,"1":1,"147":2,"212":7,"213":3,"214":15,"215":3,"216":3,"219":23,"220":2,"324":1,"358":1,"361":1,"362":1,"366":1,"490":1,"491":1,"492":1,"493":1,"495":1,"582":1,"590":2,"756":2,"765":1,"773":2,"860":1,"914":1,"1003":1,"1011":1,"1012":3,"1017":1,"1090":2,"1094":1,"1095":1,"1212":4,"1216":2,"1237":2,"1271":1,"1299":8,"1310":1,"1323":53,"1335":2,"1338":16,"1349":1,"1358":11,"1379":7,"1390":1,"1394":11,"1416":4,"1427":1,"1429":2,"1435":3,"1437":1,"1440":1,"1442":2,"1444":1,"1495":1,"1501":1,"1514":1,"1524":4,"1525":9,"1530":1,"1574":1,"1576":14,"1581":2,"1585":1,"1590":5,"1595":1,"1599":3,"1792":1,"1999":2,"2000":2,"2001":1,"2074":1,"2085":1,"2116":1,"2117":1,"2119":1,"2146":3,"2148":6,"2149":17,"2150":1,"2151":2,"2152":4,"2157":1,"2171":1}}],["s2696",{"3":{"1416":1}}],["s2092",{"3":{"1299":1,"1310":1}}],["s2077",{"3":{"0":1,"1133":1,"1285":1,"1435":1}}],["s2326",{"3":{"1270":2}}],["s2245",{"3":{"1259":1,"1323":1}}],["s2221",{"3":{"1247":1,"1310":1,"1426":1}}],["s8970",{"3":{"1576":2,"1585":1,"1590":1}}],["s8",{"3":{"1201":1}}],["s1481",{"3":{"1726":1}}],["s107",{"3":{"1576":1,"1580":1,"1581":1,"1583":1}}],["s1075",{"3":{"1368":1,"1394":3,"1415":1,"1416":3}}],["s1144",{"3":{"1237":1}}],["s1200",{"3":{"1576":1,"1580":1,"1581":1,"1583":1}}],["s12",{"3":{"766":1,"769":1}}],["s1",{"3":{"766":1,"767":1,"769":1,"1590":1}}],["s+",{"3":{"609":1,"1475":1}}],["ssas",{"3":{"2218":1,"2219":1}}],["ssis",{"3":{"2218":1,"2219":2}}],["ssh",{"3":{"1525":1,"1534":1}}],["ssl",{"3":{"1415":2,"1769":1}}],["ss",{"3":{"1358":13}}],["ss9",{"3":{"448":1}}],["ssrs",{"3":{"2218":1,"2219":1}}],["ssrf",{"3":{"1357":1,"1358":1,"1368":1}}],["ssr",{"0":{"1297":1,"1967":1},"1":{"204":1,"205":1,"206":1,"207":1,"208":1,"209":1,"210":1,"1965":1,"1966":1,"1967":1,"1968":1,"1969":1,"1970":1,"1971":1},"3":{"0":6,"204":1,"206":3,"207":3,"208":2,"209":3,"210":1,"264":1,"265":3,"266":1,"269":1,"272":2,"273":2,"274":1,"275":1,"277":2,"412":1,"458":1,"462":1,"486":1,"506":2,"507":2,"555":3,"556":3,"557":1,"560":2,"649":1,"664":1,"670":1,"682":1,"881":2,"1212":2,"1297":2,"1299":9,"1310":3,"1323":36,"1382":1,"1390":1,"1392":1,"1394":6,"1395":1,"1401":3,"1415":4,"1416":5,"1435":6,"1437":2,"1446":2,"1466":1,"1487":1,"1525":2,"1533":1,"1559":1,"1576":5,"1581":1,"1590":1,"1666":1,"1758":1,"1760":1,"1778":1,"1964":1,"1965":3,"1966":3,"1967":4,"1970":4,"1971":3,"2075":3,"2084":2,"2085":2,"2118":1,"2207":1,"2231":2}}],["snyk",{"3":{"2218":1,"2219":2}}],["snippet",{"3":{"1435":2,"2021":1}}],["snippets",{"3":{"955":1}}],["sniff",{"3":{"1285":1,"1368":1,"1414":4,"2127":1}}],["sniffed",{"3":{"1174":1,"1414":1}}],["sniffer",{"3":{"1114":1,"1116":2,"1117":3,"1118":2,"1284":1,"1285":2,"1346":1,"1358":2,"1437":1,"2231":1}}],["sniffs",{"3":{"1116":1,"1410":1,"1414":1}}],["sniffing",{"3":{"201":1,"1259":1,"1285":2,"1414":3,"1416":1,"1537":1,"1630":1,"1639":1,"2147":1}}],["snap",{"3":{"2081":1}}],["snappy",{"3":{"1323":1}}],["snapshotname",{"3":{"1435":2}}],["snapshotted",{"3":{"692":1,"1285":3,"1299":5,"1323":1,"1338":1,"1368":2,"1389":1,"1395":1,"1397":1,"1401":7,"1428":1,"1435":1,"1437":1,"1525":1,"1629":2,"1630":1,"1929":1}}],["snapshotting",{"3":{"691":1,"1233":1,"1285":1,"1299":1,"1310":2,"1349":1,"1401":5,"1435":1,"1629":1,"1929":1}}],["snapshots=1",{"3":{"1431":1,"1435":3,"1437":1}}],["snapshots",{"1":{"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1},"3":{"21":1,"583":1,"739":1,"741":3,"743":1,"745":1,"988":1,"1023":1,"1144":1,"1178":1,"1209":1,"1212":1,"1251":2,"1259":1,"1274":1,"1285":4,"1310":2,"1322":4,"1323":6,"1349":2,"1394":4,"1395":1,"1401":3,"1416":5,"1431":1,"1435":5,"1437":3,"1576":1,"1631":1,"1659":1,"1670":1,"1685":1,"1686":1,"2054":1,"2059":1,"2198":1,"2231":1}}],["snapshot",{"2":{"633":1,"636":1},"3":{"0":3,"184":1,"186":1,"387":1,"484":1,"486":1,"487":1,"488":1,"489":1,"490":2,"491":2,"492":2,"493":2,"494":2,"536":1,"540":1,"556":1,"564":1,"572":2,"573":2,"585":1,"633":1,"636":1,"676":1,"690":1,"725":2,"727":1,"744":1,"782":2,"783":2,"801":1,"825":1,"891":1,"914":1,"990":2,"1034":1,"1050":1,"1059":1,"1060":1,"1142":1,"1199":3,"1203":1,"1207":1,"1247":1,"1273":2,"1279":1,"1282":3,"1285":32,"1296":1,"1298":1,"1299":13,"1300":3,"1310":2,"1321":1,"1322":7,"1323":17,"1338":3,"1346":1,"1348":1,"1349":12,"1354":1,"1355":1,"1358":9,"1374":1,"1379":4,"1390":1,"1394":22,"1395":42,"1397":1,"1401":8,"1409":2,"1414":5,"1415":3,"1416":6,"1430":1,"1431":1,"1435":43,"1437":8,"1454":2,"1487":2,"1495":2,"1496":1,"1499":1,"1523":1,"1524":3,"1525":4,"1530":3,"1534":2,"1576":4,"1585":1,"1630":1,"1639":1,"1642":1,"1662":1,"1670":1,"1674":1,"1685":8,"1696":1,"1726":1,"1748":4,"1764":4,"1766":1,"1768":2,"1800":1,"1928":3,"1929":1,"1932":1,"2043":3,"2048":1,"2054":1,"2058":2,"2061":1,"2068":1,"2078":2,"2198":1,"2199":1}}],["snackbars",{"3":{"1323":1,"1525":3,"1590":1,"1606":1,"1643":1,"1668":1,"1740":1,"1741":1}}],["snackbaroptions",{"3":{"1323":2}}],["snackbar",{"2":{"1686":1},"3":{"109":1,"265":3,"276":1,"881":1,"905":1,"954":1,"1323":17,"1393":1,"1394":30,"1395":3,"1400":1,"1401":1,"1416":2,"1431":1,"1435":10,"1487":2,"1525":4,"1576":1,"1590":1,"1652":3,"1668":1,"1670":1,"1685":1,"1686":1,"2071":1,"2078":1}}],["snake",{"2":{"1035":1},"3":{"0":2,"1034":1,"1035":1,"1273":1,"1285":2,"1310":2,"1855":1}}],["s3330",{"3":{"1310":1}}],["s3875",{"3":{"1237":1}}],["s3011",{"3":{"1285":1}}],["s30",{"2":{"1201":1},"3":{"1201":1}}],["s3",{"3":{"0":1,"764":1,"769":1,"1590":1}}],["s0",{"3":{"0":2,"764":4,"766":5,"767":3,"769":3,"1477":4,"1482":1,"1590":1,"1599":1}}],["skills",{"3":{"2238":2}}],["skim",{"3":{"1194":1,"1248":1,"1417":1,"1440":1,"1786":1,"1811":1,"2114":1}}],["skims",{"3":{"1132":1,"1435":1}}],["skipmigration",{"3":{"1683":1,"1687":1,"1721":1,"1728":2}}],["skiperror",{"3":{"1285":1}}],["skipwhenunavailable",{"2":{"1327":1,"1448":1},"3":{"1327":1,"1338":3,"1448":1}}],["skipwhen",{"2":{"1070":2},"3":{"1067":2,"1070":2,"1327":2,"1338":2,"1429":1,"1448":2}}],["skipreason",{"2":{"1070":1,"1327":1},"3":{"1067":4,"1070":2,"1327":2,"1338":3,"1429":2,"1448":2,"2055":1}}],["skipbearer",{"2":{"514":1},"3":{"507":1,"514":1}}],["skippable",{"3":{"764":3,"1454":1,"1595":1,"1598":1,"1891":1}}],["skipping",{"3":{"0":1,"208":1,"397":1,"557":1,"733":1,"759":1,"887":1,"889":1,"890":2,"1067":1,"1068":1,"1117":1,"1133":1,"1168":1,"1223":1,"1231":1,"1237":2,"1241":1,"1247":1,"1271":1,"1285":11,"1299":1,"1300":2,"1310":4,"1322":1,"1323":1,"1332":1,"1338":2,"1348":1,"1349":3,"1358":8,"1394":3,"1395":1,"1401":1,"1414":3,"1416":5,"1432":1,"1435":10,"1440":1,"1442":1,"1454":1,"1466":2,"1469":1,"1488":1,"1920":1,"1984":1,"2009":1,"2075":1,"2155":1,"2159":1,"2167":1,"2231":1}}],["skippedsoftdeleted",{"2":{"1631":1},"3":{"1349":2,"1358":4,"1631":1,"1638":1}}],["skipped",{"2":{"620":1,"624":1,"863":1},"3":{"0":6,"39":1,"120":1,"135":1,"157":1,"168":1,"201":2,"214":1,"292":1,"318":1,"335":1,"341":1,"370":1,"397":1,"534":1,"536":1,"564":1,"583":1,"593":1,"620":1,"624":1,"626":5,"627":1,"628":1,"649":1,"707":1,"709":1,"717":1,"749":1,"758":1,"766":4,"849":1,"861":4,"863":2,"889":1,"891":1,"990":1,"997":1,"1004":1,"1006":1,"1092":1,"1220":1,"1229":1,"1241":2,"1242":1,"1247":3,"1259":8,"1266":1,"1269":1,"1270":6,"1276":1,"1278":2,"1279":1,"1285":9,"1287":1,"1288":1,"1299":1,"1300":3,"1310":3,"1314":1,"1322":11,"1323":4,"1333":1,"1338":1,"1346":1,"1349":3,"1358":19,"1368":1,"1371":1,"1379":2,"1394":1,"1395":6,"1401":2,"1414":4,"1424":1,"1430":2,"1435":21,"1437":1,"1442":1,"1444":3,"1448":1,"1452":3,"1454":11,"1455":2,"1458":1,"1472":1,"1474":1,"1487":1,"1488":1,"1489":2,"1491":8,"1525":5,"1530":1,"1533":1,"1550":1,"1576":2,"1584":1,"1590":6,"1595":2,"1598":1,"1630":1,"1631":2,"1635":1,"1638":2,"1640":1,"1653":1,"1728":1,"1747":1,"1764":1,"1804":1,"1821":1,"1837":1,"1857":1,"1908":1,"1909":1,"1994":1,"2042":1,"2155":1,"2166":1,"2175":1,"2179":1}}],["skipsabstractcontrollers",{"3":{"1435":1}}],["skipsave",{"3":{"0":1,"926":3,"1265":1,"1270":7,"1358":4,"1414":2,"1665":1}}],["skipscoredsinceutc",{"3":{"1358":4}}],["skipsspeakersmissingfromrepository",{"3":{"1358":1}}],["skips",{"0":{"1843":1},"3":{"0":4,"22":1,"27":1,"117":1,"135":1,"166":1,"194":1,"195":2,"243":1,"244":1,"248":1,"255":1,"259":1,"296":1,"330":1,"337":1,"451":1,"452":1,"499":1,"528":1,"536":1,"538":3,"547":1,"556":1,"557":1,"559":1,"564":1,"573":1,"583":1,"599":1,"626":1,"628":1,"657":2,"715":1,"757":1,"790":2,"795":1,"889":1,"907":1,"914":1,"931":1,"980":1,"988":1,"1018":1,"1020":1,"1107":1,"1133":1,"1241":2,"1242":1,"1245":1,"1247":8,"1252":1,"1255":1,"1259":4,"1270":2,"1271":3,"1280":2,"1285":15,"1299":2,"1300":1,"1308":1,"1310":5,"1319":1,"1322":3,"1323":3,"1327":1,"1336":1,"1338":2,"1349":4,"1357":1,"1358":18,"1368":3,"1378":1,"1379":2,"1390":1,"1392":1,"1394":5,"1395":8,"1401":5,"1413":1,"1414":4,"1416":5,"1434":1,"1435":27,"1437":5,"1444":1,"1448":1,"1453":1,"1454":5,"1455":1,"1457":1,"1458":1,"1459":1,"1466":2,"1471":1,"1485":2,"1487":1,"1489":2,"1491":1,"1533":1,"1595":1,"1598":2,"1631":1,"1638":1,"1650":1,"1667":1,"1676":1,"1728":1,"1764":1,"1804":1,"1815":1,"1896":1,"1917":1,"1919":1,"1921":1,"1922":1,"1946":1,"1981":1,"1987":1,"2011":1,"2056":1,"2095":2,"2104":1,"2163":1,"2164":1,"2165":2,"2167":1}}],["skip",{"0":{"2024":1},"2":{"626":2,"642":1,"768":2,"1474":2,"1533":1,"1598":1},"3":{"0":8,"20":1,"24":1,"32":1,"103":2,"179":1,"195":1,"200":1,"201":2,"210":1,"276":1,"318":1,"335":1,"341":1,"355":1,"388":1,"453":1,"459":1,"528":1,"529":1,"538":1,"549":3,"554":1,"556":1,"559":1,"567":1,"586":1,"591":3,"620":1,"626":8,"627":2,"642":1,"708":1,"741":1,"743":1,"768":2,"863":1,"869":1,"916":1,"918":1,"924":1,"1005":1,"1006":1,"1020":2,"1067":6,"1070":1,"1084":1,"1184":1,"1212":2,"1229":2,"1240":1,"1242":1,"1243":2,"1247":19,"1259":6,"1270":3,"1271":1,"1285":21,"1290":1,"1299":6,"1300":1,"1308":5,"1309":2,"1322":5,"1323":8,"1332":1,"1338":17,"1349":2,"1352":1,"1358":14,"1390":1,"1394":7,"1395":12,"1399":1,"1400":1,"1401":3,"1414":9,"1416":3,"1425":1,"1429":2,"1435":11,"1437":3,"1442":2,"1444":1,"1448":1,"1452":1,"1453":1,"1454":13,"1455":2,"1474":3,"1485":1,"1489":1,"1491":4,"1495":1,"1499":1,"1524":1,"1525":4,"1533":1,"1576":1,"1588":1,"1590":2,"1595":1,"1597":1,"1598":2,"1599":1,"1606":2,"1610":2,"1612":1,"1638":1,"1643":2,"1650":1,"1663":1,"1683":1,"1685":2,"1719":1,"1726":1,"1740":2,"1807":1,"1814":1,"1840":1,"1844":1,"1857":1,"1859":1,"1864":1,"1870":1,"1881":1,"1890":1,"1910":1,"1917":1,"1920":1,"1921":1,"1949":1,"1983":1,"2009":1,"2028":1,"2055":1,"2067":1,"2086":1,"2100":1,"2106":1,"2110":1,"2120":1,"2125":1,"2130":1,"2144":1,"2154":1,"2156":1,"2170":1,"2179":1,"2189":1}}],["skuexistsasync",{"3":{"1750":1}}],["skus",{"3":{"1749":1,"1766":2,"1772":1}}],["skuid",{"3":{"1198":2,"1199":10,"1207":2}}],["sku",{"2":{"767":1,"769":3,"1464":1},"3":{"583":1,"766":5,"767":1,"769":3,"889":1,"897":1,"1435":5,"1464":2,"1466":1,"1472":1,"1477":6,"1598":1,"1745":2,"1747":4,"1749":2,"1750":2,"1763":2,"1766":2,"1767":1,"1768":3,"1771":2,"1772":1}}],["sketch",{"0":{"2083":1}}],["sketched",{"3":{"264":1,"963":1,"1079":1}}],["skeletons",{"3":{"1559":1}}],["skeleton",{"3":{"980":1,"1342":1,"1358":1,"1394":2,"1395":1,"1401":1,"1435":2}}],["skew",{"3":{"0":3,"146":1,"148":1,"178":1,"507":1,"513":1,"1059":1,"1256":1,"1259":1,"1292":2,"1297":1,"1299":2,"1310":1,"1323":9,"1416":1,"1435":3,"1466":1,"1647":1,"1654":1,"1726":1,"2197":2}}],["squash",{"3":{"1452":1}}],["squatting",{"3":{"1285":1}}],["squarely",{"3":{"1284":1,"1358":1,"2058":1}}],["square",{"3":{"0":1,"440":1,"1285":2,"1322":3,"1394":1,"1495":1,"2125":1}}],["sqs",{"3":{"0":1,"1075":1}}],["sqldatabase",{"2":{"1472":1,"1477":1},"3":{"1472":1,"1477":1}}],["sqllocation",{"3":{"1465":2,"1466":2,"1473":2}}],["sqlaadadminobjectid",{"3":{"1466":2}}],["sqlaadadminlogin",{"3":{"1466":1}}],["sqladminpassword",{"2":{"1465":1},"3":{"1465":2,"1466":1,"1525":1,"1533":1}}],["sqlauditingsettings",{"2":{"719":1},"3":{"719":1}}],["sqlauditconventiontests",{"2":{"719":1},"3":{"0":1,"719":2,"1199":1,"1207":2,"1435":4,"1466":2,"1590":1}}],["sql=true",{"3":{"1454":1,"1466":1,"1476":1,"1590":1,"1595":1,"1599":1}}],["sqlbaseenvironmentvariable",{"2":{"1428":1},"3":{"1428":1,"1435":2,"1487":1,"1489":1}}],["sqlqueryable",{"3":{"1285":2}}],["sqlquery",{"2":{"1132":1,"1133":1},"3":{"1132":1,"1133":1,"1285":2,"1435":1}}],["sqlqueryraw",{"2":{"1132":1},"3":{"0":1,"1132":1,"1133":1,"1430":1,"1435":3}}],["sqlsecurityauditevents",{"2":{"719":1},"3":{"719":2,"1466":2}}],["sqlserverfqdn",{"3":{"1466":1}}],["sqlserverbaseconnectionstring",{"3":{"1435":1}}],["sqlservermodel",{"3":{"1435":2}}],["sqlservermigrationsassembly",{"2":{"166":1,"633":1,"1445":1,"1683":1,"2096":1},"3":{"166":1,"633":1,"1285":4,"1435":1,"1445":1,"1683":3,"2096":1}}],["sqlserverretryingexecutionstrategy",{"3":{"1285":1}}],["sqlserverthingconfiguration",{"3":{"1199":1,"1207":1}}],["sqlserverthing",{"3":{"1199":3,"1207":1}}],["sqlserverindexedentity",{"3":{"1199":3,"1207":1}}],["sqlserverintegrationtestfixturebase",{"2":{"572":1,"914":1,"1212":1,"1427":1,"1428":1,"1487":1,"1489":1,"1599":1,"1670":1,"2054":1},"3":{"0":1,"572":6,"914":3,"916":1,"1199":5,"1207":2,"1212":1,"1427":1,"1428":3,"1435":8,"1487":2,"1489":2,"1495":1,"1599":1,"1670":1,"2054":1}}],["sqlserverdatasourceengine",{"3":{"1175":3}}],["sqlserverdbcontexttests",{"3":{"1199":1,"1207":3}}],["sqlserverdbcontextmodelsnapshot",{"3":{"1140":1,"1209":4}}],["sqlserverdbcontext",{"2":{"47":1,"200":1,"812":1,"1192":1,"1362":1,"1411":1,"1509":1,"1613":1,"1651":1,"1696":1,"1848":1,"1852":1,"1856":1},"3":{"47":2,"71":1,"166":1,"200":1,"674":2,"812":1,"819":3,"987":1,"1034":1,"1192":1,"1199":5,"1202":1,"1203":2,"1207":2,"1212":2,"1272":1,"1273":5,"1283":1,"1285":22,"1362":1,"1368":7,"1395":10,"1411":1,"1414":1,"1435":5,"1440":1,"1509":1,"1613":1,"1650":2,"1651":1,"1663":1,"1696":1,"1726":1,"1848":2,"1852":1,"1856":1,"2031":1}}],["sqlserverauditing",{"2":{"719":1},"3":{"719":1,"1466":1}}],["sqlserveruniqueconstraintviolationdetectortests",{"2":{"1199":1,"1437":1},"3":{"1199":1,"1207":2,"1285":1,"1437":2}}],["sqlserveruniqueconstraintviolationdetector",{"2":{"1203":1,"1283":1,"1322":1},"3":{"692":1,"1199":3,"1203":1,"1207":2,"1283":2,"1285":8,"1322":1,"1395":1}}],["sqlserverorsqlite",{"2":{"636":1},"3":{"633":1,"636":1}}],["sqlserver",{"2":{"170":1,"564":2,"631":1,"1174":1,"1326":1,"1360":1,"1368":1,"1496":1,"1723":1,"1727":1,"1728":1},"3":{"166":2,"170":2,"195":3,"341":2,"345":1,"350":1,"403":1,"564":8,"631":2,"633":1,"891":4,"1018":1,"1026":1,"1034":2,"1085":1,"1140":1,"1174":1,"1175":1,"1209":136,"1212":1,"1213":3,"1259":1,"1281":1,"1285":25,"1322":1,"1326":1,"1338":6,"1358":1,"1360":1,"1368":8,"1395":7,"1414":3,"1435":8,"1440":3,"1442":1,"1443":1,"1454":2,"1472":1,"1495":2,"1496":1,"1499":1,"1576":1,"1590":1,"1651":1,"1660":1,"1696":1,"1721":2,"1723":1,"1726":4,"1727":4,"1728":1,"1859":1,"1861":1}}],["sqlserverconnectionstring",{"2":{"166":1,"170":1,"1325":1,"1440":2,"1472":1,"1489":1,"1721":1,"1728":1},"3":{"166":1,"170":1,"1285":6,"1325":1,"1338":3,"1435":1,"1440":3,"1472":1,"1489":1,"1721":1,"1728":1,"2007":1}}],["sqlexception",{"3":{"692":1,"1285":6,"1358":1,"1395":1,"1437":1}}],["sqlconnection",{"3":{"1487":1,"1489":1}}],["sqlcommand",{"3":{"1435":1}}],["sqlcliententraauthenticationtests",{"3":{"1199":1,"1207":2,"1495":1}}],["sqlclient",{"2":{"1476":1},"3":{"692":1,"1207":2,"1259":1,"1285":1,"1338":3,"1435":2,"1442":1,"1476":1,"1533":1,"1676":1,"2009":1,"2153":1,"2156":1}}],["sqlcmd",{"2":{"291":1,"563":1},"3":{"0":1,"291":4,"563":1,"757":1,"1212":1,"1454":4,"1466":2,"1473":1}}],["sqliteconfig",{"3":{"1285":2}}],["sqliteconnectionstring",{"2":{"633":1,"1721":1,"1728":1},"3":{"166":1,"633":1,"1285":5,"1338":2,"1440":1,"1721":1,"1728":1}}],["sqlitetestdbcontext",{"3":{"1199":2,"1207":1}}],["sqlitetestentityconfig",{"3":{"1199":2,"1207":1}}],["sqlitetestentity",{"3":{"1199":4,"1207":1}}],["sqliteindexedentity",{"3":{"1199":3,"1207":1}}],["sqlitedatasourceengine",{"2":{"1175":1},"3":{"1175":3,"1176":1}}],["sqlitedbcontext",{"2":{"47":1,"1509":1,"1696":1,"1718":1,"1727":1,"1730":1,"1856":1},"3":{"47":1,"166":1,"200":1,"1199":4,"1203":1,"1207":2,"1212":2,"1272":1,"1273":2,"1285":13,"1322":2,"1435":3,"1509":1,"1663":1,"1696":1,"1718":1,"1726":2,"1727":2,"1730":1,"1848":1,"1856":1}}],["sqlite3",{"2":{"370":1},"3":{"370":1}}],["sqlitepclraw",{"2":{"370":1},"3":{"370":3}}],["sqlitemigrationsassembly",{"2":{"171":1,"633":1,"1279":1,"1322":1,"1718":1,"1721":1,"1726":1,"1727":1,"1730":1},"3":{"166":1,"171":1,"633":1,"1279":1,"1285":3,"1310":1,"1322":1,"1718":1,"1721":1,"1726":1,"1727":1,"1730":1}}],["sqlite",{"0":{"2052":1},"2":{"170":1,"631":1,"932":1,"1601":1,"1660":1,"1673":1,"1691":1,"1696":1,"1704":1,"1717":1,"1721":2,"1725":1,"1726":2,"1727":3,"1730":2},"3":{"0":11,"10":2,"45":1,"164":1,"166":6,"170":4,"201":1,"290":1,"296":1,"343":2,"345":1,"370":1,"371":1,"564":1,"631":2,"633":8,"676":1,"698":1,"889":2,"890":1,"892":1,"906":1,"932":1,"1033":3,"1034":6,"1035":1,"1036":1,"1044":1,"1050":3,"1067":2,"1174":2,"1175":3,"1176":1,"1177":1,"1178":3,"1179":1,"1180":1,"1212":7,"1213":4,"1247":1,"1259":2,"1279":5,"1280":2,"1281":3,"1283":1,"1285":83,"1308":1,"1310":9,"1318":1,"1319":1,"1320":1,"1322":8,"1325":1,"1331":1,"1338":5,"1360":1,"1368":2,"1395":2,"1435":10,"1437":8,"1440":3,"1442":2,"1488":1,"1489":4,"1495":1,"1499":3,"1509":1,"1525":2,"1576":3,"1581":1,"1582":1,"1584":2,"1601":1,"1613":2,"1615":1,"1617":1,"1660":2,"1663":2,"1673":1,"1691":1,"1696":2,"1704":1,"1706":3,"1717":3,"1718":4,"1719":1,"1720":1,"1721":6,"1722":1,"1725":1,"1726":11,"1727":7,"1728":3,"1730":5,"1751":1,"1762":1,"1773":1,"1848":1,"1852":1,"1853":2,"1854":1,"1855":4,"1859":4,"1860":2,"1861":1,"1875":1,"2052":2,"2058":2,"2059":2,"2191":1,"2229":1,"2231":1}}],["sql",{"0":{"1476":2},"1":{"1130":1,"1131":1,"1132":1,"1133":1,"1134":1,"1135":1,"1136":1,"1137":1},"2":{"566":1,"599":1,"769":1,"974":1,"1454":1,"1465":2,"1467":1,"1469":4,"1471":1,"1476":4,"1481":1,"1483":1,"1646":1,"1774":1,"2035":1},"3":{"0":39,"10":2,"13":1,"21":1,"24":1,"41":1,"45":1,"46":1,"47":3,"164":3,"165":2,"166":3,"168":1,"170":8,"171":1,"201":1,"235":1,"318":1,"330":1,"331":1,"341":2,"342":1,"343":1,"387":1,"395":1,"402":1,"403":2,"405":2,"471":1,"536":1,"543":1,"544":1,"546":1,"547":1,"549":1,"566":1,"572":1,"574":1,"598":1,"599":9,"600":1,"601":2,"605":1,"609":5,"611":1,"632":1,"633":3,"640":1,"644":1,"692":1,"698":1,"699":1,"713":2,"715":1,"718":1,"764":4,"765":3,"766":9,"769":2,"800":1,"860":1,"888":1,"889":4,"890":2,"892":1,"905":1,"907":1,"913":2,"914":2,"971":2,"972":2,"973":1,"974":2,"987":1,"988":1,"1004":1,"1033":4,"1034":15,"1035":2,"1036":2,"1044":1,"1050":3,"1067":1,"1069":1,"1085":1,"1130":1,"1132":4,"1133":4,"1134":1,"1135":1,"1144":1,"1162":1,"1167":1,"1174":2,"1175":5,"1176":2,"1178":2,"1180":1,"1192":1,"1212":12,"1217":1,"1236":1,"1237":4,"1239":1,"1241":5,"1242":1,"1246":1,"1247":20,"1259":7,"1263":1,"1270":3,"1272":1,"1273":2,"1276":1,"1277":1,"1279":7,"1280":2,"1281":2,"1283":2,"1285":160,"1289":1,"1299":5,"1300":2,"1308":8,"1309":8,"1310":6,"1316":1,"1317":1,"1319":1,"1320":1,"1322":2,"1325":2,"1326":2,"1331":4,"1338":13,"1341":2,"1349":6,"1351":3,"1352":1,"1357":1,"1358":28,"1359":1,"1360":1,"1368":12,"1379":5,"1395":25,"1400":1,"1401":7,"1405":1,"1409":1,"1414":9,"1427":1,"1428":5,"1429":1,"1435":51,"1436":5,"1437":18,"1439":1,"1440":11,"1442":4,"1445":2,"1450":1,"1452":2,"1454":16,"1455":2,"1456":7,"1459":3,"1462":1,"1464":2,"1465":5,"1466":40,"1467":1,"1468":1,"1469":5,"1470":2,"1471":2,"1472":9,"1473":10,"1474":6,"1475":3,"1476":24,"1477":8,"1481":3,"1482":2,"1483":2,"1485":6,"1486":2,"1487":4,"1489":10,"1491":3,"1495":3,"1509":1,"1523":4,"1524":2,"1525":9,"1531":1,"1533":6,"1534":1,"1574":1,"1576":6,"1584":1,"1588":1,"1589":1,"1590":16,"1594":1,"1595":2,"1596":1,"1598":2,"1599":4,"1601":1,"1610":6,"1613":1,"1616":1,"1617":1,"1640":1,"1646":1,"1647":2,"1652":4,"1653":3,"1656":1,"1663":2,"1664":1,"1669":2,"1670":1,"1672":1,"1676":1,"1677":1,"1681":1,"1683":2,"1684":1,"1688":3,"1692":1,"1696":1,"1697":1,"1699":1,"1703":1,"1716":2,"1717":3,"1718":1,"1719":1,"1720":1,"1722":1,"1723":1,"1724":1,"1726":2,"1730":2,"1751":1,"1762":2,"1773":1,"1774":2,"1814":3,"1839":2,"1843":1,"1847":1,"1848":1,"1850":1,"1852":1,"1853":4,"1854":1,"1855":3,"1856":1,"1857":2,"1858":2,"1859":3,"1860":2,"1862":1,"1863":1,"1864":1,"1866":1,"1870":1,"1871":1,"1875":1,"1876":1,"1921":1,"1985":1,"1987":1,"1988":2,"1993":1,"2002":1,"2003":2,"2004":1,"2006":1,"2012":2,"2032":1,"2033":5,"2035":4,"2036":1,"2042":3,"2052":1,"2054":1,"2058":4,"2110":1,"2157":1,"2166":1,"2190":1,"2191":1,"2192":1,"2212":1,"2218":2,"2219":4,"2223":1,"2229":1,"2231":4}}],["slept",{"3":{"1435":1}}],["sleeps",{"3":{"1259":1,"1322":1,"1323":1,"1435":1,"1670":1,"1840":2,"1947":1}}],["sleep",{"2":{"760":1},"3":{"757":1,"759":1,"760":2,"998":1,"1285":1,"1322":2,"1323":1,"1416":1,"1428":1,"1435":1,"1466":1,"1525":1,"1564":1,"1576":1}}],["sleeping",{"3":{"17":1,"881":1,"1160":1,"1259":1,"1275":1,"1323":1,"1435":1}}],["slugged",{"3":{"1495":1}}],["slugs",{"3":{"1479":1,"1495":1}}],["slug",{"2":{"1478":1,"1479":1},"3":{"1414":2,"1478":2,"1479":3}}],["slate",{"3":{"1323":1}}],["slashes",{"3":{"1358":1,"1435":1}}],["slash",{"3":{"420":1,"427":1,"1310":1,"1323":3,"1338":2,"1383":1,"1416":4,"1455":1}}],["slack",{"3":{"178":1,"593":1,"626":1,"627":1,"1285":1,"1323":1,"1414":1,"1454":1,"1491":1,"2046":1,"2219":1}}],["slsa",{"3":{"374":1,"1453":1,"1576":2}}],["sldexp",{"3":{"245":1,"1300":1,"1437":1}}],["sln",{"3":{"1727":1}}],["slnf",{"2":{"1215":2,"1454":1,"1458":1,"1485":2,"1493":1,"1530":1,"1610":1,"1615":1,"1616":1,"1618":1,"1774":1},"3":{"218":2,"374":1,"575":2,"609":2,"914":1,"954":2,"960":1,"1066":1,"1192":1,"1215":3,"1338":2,"1428":1,"1435":4,"1437":3,"1454":6,"1458":1,"1459":2,"1485":19,"1486":6,"1489":1,"1491":5,"1493":1,"1523":1,"1525":34,"1530":6,"1531":1,"1533":2,"1534":2,"1590":24,"1595":3,"1596":1,"1599":2,"1610":5,"1615":1,"1616":2,"1618":1,"1756":1,"1774":1}}],["slnx",{"2":{"139":1,"140":1,"373":1,"509":1,"511":1,"514":1,"528":1,"591":1,"868":1,"906":1,"974":1,"1066":1,"1215":1,"1433":1,"1453":1,"1491":1,"1502":1,"1616":1,"1618":1,"1648":1,"1654":1,"1656":1,"1683":1,"1689":1,"1703":1,"2119":1},"3":{"0":2,"135":3,"137":1,"139":1,"140":1,"142":1,"373":2,"413":1,"509":2,"511":1,"514":3,"528":2,"531":1,"591":1,"618":1,"633":1,"733":1,"737":1,"868":1,"906":1,"914":1,"972":1,"974":1,"980":1,"1034":1,"1066":2,"1067":1,"1192":1,"1215":2,"1322":2,"1338":2,"1416":2,"1433":2,"1435":17,"1437":3,"1452":2,"1453":3,"1454":1,"1455":1,"1459":2,"1485":12,"1486":8,"1489":4,"1491":3,"1493":1,"1502":1,"1576":14,"1610":1,"1616":2,"1618":1,"1648":1,"1653":2,"1656":2,"1683":1,"1685":1,"1689":2,"1703":2,"1726":1,"1727":1,"1728":1,"1922":1,"2096":1,"2119":1}}],["sli",{"3":{"1706":1,"1708":1}}],["slight",{"3":{"1323":1}}],["slightly",{"3":{"178":1,"283":1,"592":1,"804":1,"933":1,"1117":1,"1237":1,"1247":1,"1285":1,"1299":1,"1310":3,"1323":2,"1342":1,"1358":1,"1379":1,"1395":1,"1491":1,"1630":1,"1959":1,"2041":1,"2046":1}}],["slid",{"3":{"1323":1}}],["slideware",{"3":{"1782":1,"2226":1}}],["slides",{"3":{"1350":1,"1356":1,"1368":1,"1394":1,"1629":1,"1981":1,"2231":1}}],["slide",{"3":{"1115":1,"1349":1,"1379":1,"1394":1,"1466":1,"2000":1,"2238":1}}],["sliding",{"3":{"0":2,"173":1,"178":1,"280":1,"497":2,"499":1,"500":1,"1212":2,"1300":1,"1310":3,"1338":1,"1665":1,"1999":1,"2001":1}}],["slidingwindowratelimiteroptions",{"3":{"1310":1}}],["slidingwindow",{"2":{"1999":1},"3":{"0":1,"178":1,"1310":4}}],["slicing",{"3":{"1005":1,"1310":1,"1414":1,"1435":2,"2113":1}}],["sliceable",{"3":{"1322":1}}],["sliced",{"3":{"1310":1,"1484":1,"2113":1}}],["slicefixtures",{"3":{"1207":11,"1430":5,"1435":25,"1495":1}}],["slicecohesion",{"3":{"1575":1}}],["slicecohesiontests",{"3":{"1199":2,"1207":4,"1435":16,"1488":2,"1495":1,"1525":1,"1533":1,"1534":1,"1576":1,"1590":2}}],["slicecohesiontestsbase",{"2":{"1003":1,"2047":1},"3":{"1003":2,"1199":3,"1207":2,"1430":3,"1435":13,"1495":1,"1525":1,"1576":2,"1590":1,"1653":1,"2047":1}}],["slicecohesionfitnesstests",{"3":{"1199":1,"1207":3,"1430":4,"1435":13,"1495":1,"1576":1,"1585":1}}],["slices",{"3":{"132":1,"1006":1,"1207":1,"1299":2,"1350":1,"1358":7,"1369":1,"1398":1,"1401":2,"1430":6,"1435":13,"1488":1,"1513":1,"1525":3,"1541":2,"1576":6,"1581":2,"1582":2,"1585":1,"1590":1,"1683":1,"1685":3,"1700":1,"1725":1,"1726":2,"1727":1,"1729":1,"1730":3,"1931":1,"1936":2,"2141":1}}],["slice",{"0":{"1351":1,"1410":1,"1541":1,"1650":1,"1701":1,"1931":1,"2096":1},"1":{"1930":1,"1931":1,"1932":1,"1933":1,"1934":1,"1935":1,"1936":1,"1937":1,"1938":1,"1939":1,"1940":1},"3":{"122":1,"359":1,"387":1,"632":1,"633":2,"1003":2,"1119":1,"1192":4,"1201":1,"1212":3,"1216":1,"1224":1,"1247":3,"1259":1,"1265":3,"1270":8,"1285":3,"1299":1,"1303":2,"1308":2,"1309":2,"1310":4,"1313":1,"1322":5,"1323":4,"1347":1,"1349":3,"1350":2,"1351":5,"1356":1,"1358":68,"1361":1,"1368":1,"1369":1,"1379":1,"1395":16,"1398":1,"1401":7,"1402":1,"1410":1,"1414":11,"1430":6,"1435":31,"1487":2,"1488":1,"1495":8,"1496":1,"1498":1,"1513":1,"1524":2,"1525":7,"1529":1,"1530":1,"1531":1,"1533":2,"1534":2,"1541":6,"1571":1,"1576":6,"1580":1,"1581":3,"1582":2,"1585":1,"1590":2,"1596":1,"1650":1,"1660":2,"1670":1,"1683":1,"1685":2,"1690":1,"1693":1,"1700":4,"1725":2,"1726":3,"1729":4,"1778":1,"1795":1,"1803":1,"1864":1,"1882":1,"1921":1,"1929":1,"1930":3,"1931":1,"1933":1,"1934":2,"1936":1,"1938":1,"1939":2,"1940":6,"1959":1,"2047":1,"2086":1,"2093":1,"2096":3,"2113":2,"2207":1,"2229":3}}],["slimmer",{"3":{"784":1}}],["slipping",{"3":{"1995":1}}],["slipped",{"3":{"1285":1}}],["slips",{"3":{"1322":1,"1495":1,"1908":1}}],["slip",{"3":{"157":1,"856":1,"1310":1,"1322":1,"1338":1,"1395":1,"1401":1,"1414":1,"1431":1,"1435":5,"1871":1,"2047":1}}],["slogan",{"3":{"1662":1,"2056":1}}],["sloalerts",{"2":{"612":1,"613":1,"2045":1},"3":{"609":2,"612":1,"613":1,"1435":2,"1466":1,"1475":1,"1525":1,"2045":1}}],["sloalertspecs",{"2":{"607":1,"609":1,"611":1,"612":1,"613":1,"1016":1,"1019":1,"1473":1,"2045":1},"3":{"0":1,"607":3,"609":3,"611":1,"612":1,"613":1,"1016":1,"1018":1,"1019":1,"1212":1,"1435":6,"1466":3,"1473":1,"1475":2,"1525":1,"1576":1,"1590":2,"1599":1,"2045":1,"2157":1}}],["slos",{"3":{"609":3,"914":1,"1018":1,"1435":2,"1466":3,"1565":1,"1573":1,"1574":1,"1576":1,"1577":1,"1580":1,"1581":1,"1582":1,"1706":1,"2157":1}}],["slowmo",{"3":{"1435":2,"1487":1}}],["slowing",{"3":{"1259":1,"1299":1,"1485":1}}],["slowly",{"3":{"1123":1,"1299":1,"1323":2,"1338":1,"1339":1,"1379":1,"1395":1,"1401":1,"1415":1,"1999":1,"2010":1}}],["sloworkbook",{"3":{"609":1,"1473":1,"1525":1,"1576":1}}],["slowdown",{"3":{"593":1,"1379":1,"1435":1,"2047":1}}],["slows",{"3":{"592":2,"1435":1,"1455":1,"2046":2}}],["slowbenchmark",{"2":{"591":1},"3":{"591":1}}],["slower",{"3":{"509":1,"511":1,"539":1,"717":1,"996":1,"1247":1,"1270":1,"1285":1,"1310":1,"1338":2,"1358":1,"1394":1,"1414":1,"1435":1,"1440":1,"1533":1,"1988":1,"2048":1}}],["slowest",{"3":{"0":1,"727":1,"1069":1,"1270":1,"1338":3,"1432":1,"1435":1,"1448":1,"1452":1,"1454":1,"1473":2,"1487":1,"1489":1,"2033":1}}],["slow",{"0":{"1901":1},"3":{"235":1,"236":1,"309":1,"311":1,"383":1,"459":1,"520":2,"539":1,"591":1,"709":1,"759":1,"765":1,"818":1,"821":1,"829":1,"833":1,"860":1,"996":1,"1020":1,"1042":1,"1102":1,"1259":1,"1270":1,"1285":2,"1299":1,"1300":3,"1306":1,"1308":1,"1310":2,"1322":2,"1323":5,"1331":1,"1336":1,"1338":4,"1356":1,"1358":2,"1379":1,"1389":1,"1394":5,"1395":6,"1401":1,"1414":1,"1415":1,"1416":3,"1432":1,"1435":6,"1437":1,"1440":1,"1442":1,"1445":1,"1448":1,"1454":1,"1458":2,"1459":1,"1474":1,"1487":1,"1490":1,"1548":1,"1550":1,"1569":1,"1667":1,"1670":1,"1707":1,"1888":1,"1901":1,"1921":1,"1943":1,"1945":1,"1988":1,"2006":1,"2013":1,"2031":1,"2046":1,"2049":1,"2050":1,"2052":1,"2059":1,"2187":1}}],["sloppy",{"3":{"135":1,"2066":1}}],["slots",{"3":{"319":1,"321":1,"471":1,"1012":1,"1013":1,"1247":1,"1300":3,"1310":1,"1323":1,"1358":5,"1379":1,"1401":2,"1414":1,"1452":1,"1478":2,"1479":2,"1553":1,"1554":1,"1571":1,"1576":1,"1630":1,"1993":1}}],["slot",{"3":{"0":1,"235":1,"265":1,"273":1,"299":1,"304":1,"499":1,"753":2,"774":1,"888":1,"891":1,"904":1,"1012":1,"1013":2,"1033":1,"1034":1,"1124":1,"1212":1,"1270":3,"1271":1,"1285":3,"1299":1,"1300":3,"1303":2,"1306":1,"1308":2,"1310":4,"1322":4,"1323":26,"1331":2,"1338":3,"1349":5,"1351":1,"1358":18,"1368":5,"1379":2,"1395":5,"1401":2,"1414":7,"1415":3,"1416":5,"1435":3,"1457":1,"1458":1,"1478":1,"1479":2,"1488":1,"1495":5,"1530":1,"1553":2,"1630":1,"1636":1,"1778":1,"1875":1,"1944":1,"1956":1,"1999":1,"2082":1,"2083":1,"2117":1,"2122":1,"2133":1}}],["slo",{"0":{"1708":1},"1":{"606":1,"607":1,"608":1,"609":1,"610":1,"611":1,"612":1,"613":1,"614":1},"3":{"0":4,"177":1,"403":1,"606":1,"607":3,"609":11,"612":1,"759":1,"765":1,"914":1,"971":1,"1212":1,"1430":1,"1435":6,"1454":2,"1465":1,"1466":16,"1468":3,"1473":6,"1475":6,"1481":2,"1488":2,"1525":3,"1534":1,"1549":1,"1574":2,"1576":6,"1581":1,"1582":2,"1585":2,"1590":3,"1669":1,"1670":1,"1705":3,"1708":2,"2000":1,"2045":1,"2048":1,"2157":2,"2159":1,"2231":2}}],["spun",{"3":{"2134":1}}],["spurious",{"3":{"897":1,"898":1,"996":1,"1299":1,"1308":1,"1322":1,"1323":2,"1395":2,"1414":1,"1471":1,"1533":1,"1650":1,"1918":1}}],["splats",{"3":{"1394":1,"1435":2}}],["splice",{"3":{"219":1,"1323":1,"2149":1}}],["spliced",{"3":{"219":1,"1285":1,"1323":2,"1466":2,"2148":1,"2149":1}}],["splitting",{"3":{"135":1,"185":1,"507":1,"530":1,"592":1,"642":1,"691":1,"757":2,"758":1,"785":1,"989":1,"1237":3,"1247":2,"1261":1,"1270":2,"1271":1,"1285":5,"1299":3,"1300":1,"1308":2,"1310":3,"1322":2,"1323":8,"1338":1,"1349":2,"1358":10,"1368":1,"1379":1,"1384":1,"1394":5,"1395":3,"1401":9,"1414":3,"1416":5,"1435":7,"1454":1,"1497":1,"1559":1,"1596":1,"1748":1,"1819":1}}],["splits",{"3":{"0":1,"165":1,"180":1,"505":1,"528":1,"574":1,"649":1,"759":1,"778":1,"781":1,"845":1,"905":1,"1175":1,"1178":1,"1241":1,"1247":2,"1259":1,"1285":8,"1299":4,"1308":2,"1309":1,"1310":2,"1311":1,"1323":1,"1337":1,"1342":1,"1349":1,"1358":4,"1368":1,"1382":1,"1394":4,"1395":1,"1401":2,"1415":1,"1429":1,"1445":2,"1459":1,"1466":2,"1491":1,"1495":2,"1525":1,"1534":1,"1787":1,"1814":2,"1836":1,"1864":1,"1937":1,"1941":1,"1949":1,"1958":1,"1994":1,"1996":1,"2184":1,"2192":1,"2231":1}}],["split",{"0":{"1252":1,"1384":1},"3":{"0":8,"35":1,"47":2,"58":1,"59":1,"60":1,"62":1,"91":1,"92":1,"96":1,"97":1,"139":1,"145":1,"160":1,"161":1,"168":2,"226":1,"266":1,"320":1,"379":1,"380":1,"440":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":2,"495":1,"507":1,"509":1,"513":1,"514":1,"522":1,"526":1,"529":1,"530":1,"543":2,"545":3,"547":3,"549":2,"550":1,"554":1,"560":1,"582":1,"587":1,"616":1,"631":1,"633":2,"638":1,"652":1,"657":1,"664":1,"677":1,"682":1,"698":1,"707":1,"726":2,"731":1,"733":2,"741":1,"757":1,"758":1,"804":1,"805":1,"826":1,"827":1,"828":1,"844":1,"849":1,"862":1,"867":1,"872":1,"875":2,"923":1,"926":1,"927":1,"938":1,"941":1,"983":1,"1004":3,"1010":1,"1011":1,"1013":1,"1018":1,"1019":2,"1069":1,"1101":1,"1177":1,"1201":2,"1212":4,"1229":2,"1234":1,"1237":2,"1238":2,"1240":1,"1243":2,"1244":1,"1247":9,"1249":1,"1254":1,"1256":1,"1259":6,"1265":1,"1270":4,"1273":1,"1274":2,"1278":3,"1280":1,"1285":20,"1292":1,"1299":9,"1300":3,"1308":4,"1309":10,"1310":12,"1315":1,"1322":8,"1323":23,"1327":1,"1338":7,"1340":1,"1348":1,"1349":11,"1351":1,"1358":35,"1361":1,"1362":1,"1365":2,"1368":5,"1370":2,"1374":2,"1379":16,"1384":1,"1388":2,"1394":27,"1395":27,"1401":13,"1402":1,"1403":1,"1412":1,"1413":1,"1414":15,"1415":4,"1416":12,"1422":1,"1423":1,"1424":1,"1426":1,"1430":1,"1435":29,"1437":3,"1440":2,"1446":5,"1449":1,"1453":4,"1454":7,"1455":1,"1460":1,"1461":1,"1462":1,"1466":2,"1468":2,"1469":1,"1470":2,"1473":1,"1485":2,"1486":1,"1488":1,"1491":2,"1495":10,"1497":1,"1513":1,"1525":2,"1529":1,"1530":1,"1531":1,"1534":1,"1554":1,"1576":4,"1584":2,"1588":1,"1590":2,"1594":1,"1595":1,"1596":1,"1598":1,"1599":2,"1610":1,"1613":1,"1618":1,"1637":1,"1650":1,"1673":1,"1674":1,"1701":1,"1754":1,"1775":2,"1782":2,"1787":1,"1788":2,"1789":1,"1791":4,"1792":1,"1795":1,"1806":1,"1814":1,"1816":1,"1846":2,"1848":1,"1851":3,"1852":3,"1863":2,"1864":2,"1865":1,"1866":1,"1883":1,"1892":1,"1901":1,"1914":1,"1931":1,"1941":1,"1947":1,"1949":1,"1950":1,"1958":1,"1976":1,"1978":1,"1984":1,"1987":1,"1995":1,"1996":1,"2006":1,"2013":1,"2014":3,"2024":1,"2025":1,"2026":1,"2027":2,"2037":1,"2062":1,"2110":1,"2125":1,"2126":1,"2127":1,"2148":1,"2153":1,"2178":1,"2184":1,"2197":1,"2231":2}}],["spymapper",{"3":{"1199":2,"1207":1}}],["sp",{"2":{"518":1},"3":{"518":2,"1100":2,"1322":1,"1435":1,"1533":1}}],["sprint",{"3":{"1985":1,"2042":1}}],["sprinkling",{"3":{"1912":1,"2159":1}}],["sprinkled",{"3":{"1291":1,"1308":1}}],["spring",{"3":{"1347":1,"1349":2,"1354":1,"1358":4,"1392":1,"1395":1}}],["sprawling",{"3":{"1299":1}}],["sprawl",{"3":{"1285":1,"1525":1,"1541":1}}],["sprayed",{"3":{"1368":1,"1997":1}}],["spraying",{"3":{"280":1,"1310":1,"1405":1,"1414":1}}],["spray",{"3":{"175":1,"177":1,"1310":5,"1338":1,"1405":1,"1437":1,"1598":1,"1599":1,"1998":2,"2000":2,"2001":1}}],["spreading",{"3":{"280":1,"1395":1,"1435":2}}],["spreadsheet",{"3":{"743":2,"1285":2,"1310":3}}],["spreadsheets",{"3":{"743":1}}],["spreads",{"3":{"22":1,"23":1,"603":1,"651":1,"1285":1,"1322":1,"1349":1,"1379":2,"1416":1,"1435":2,"1525":1,"1541":1}}],["spread",{"3":{"0":2,"80":1,"408":1,"956":1,"980":1,"1174":1,"1285":1,"1300":1,"1310":1,"1322":1,"1323":1,"1346":1,"1358":10,"1379":3,"1395":1,"1414":1,"1416":1,"1435":4,"1473":1,"1637":1,"2177":1}}],["spills",{"3":{"1323":1}}],["spirit",{"3":{"491":1,"1323":1}}],["spinners",{"3":{"1606":1,"1740":1}}],["spinner",{"3":{"1323":2,"1389":1,"1394":10,"1395":1,"1560":1,"1576":1,"1642":2,"1643":1,"2069":1}}],["spinning",{"3":{"1259":2,"1322":1,"1676":1,"2002":1}}],["spine",{"0":{"1344":1},"3":{"1248":1,"1259":1,"1286":1,"1287":1,"1339":1,"1344":1,"1402":1,"1499":1}}],["spins",{"3":{"1213":1,"1569":1}}],["spin",{"3":{"19":1,"237":1,"1947":1,"2052":1}}],["spikes",{"3":{"875":1,"1188":1,"1487":1}}],["spike",{"3":{"0":3,"58":1,"390":1,"609":2,"611":1,"863":1,"898":1,"1077":3,"1124":1,"1338":2,"1349":1,"1435":1,"1455":1,"1464":1,"1466":2,"1669":1}}],["spof",{"3":{"2037":1}}],["spofs",{"3":{"1475":1,"2032":1}}],["spooky",{"3":{"1555":1}}],["spoof",{"3":{"1308":1}}],["spoofed",{"3":{"1299":1,"1401":2}}],["spoofable",{"3":{"177":1,"751":1,"1285":2,"1310":1}}],["spoofing",{"3":{"0":1}}],["spoken",{"3":{"1416":3}}],["sportsesports",{"3":{"1394":1}}],["sporadic",{"3":{"484":1}}],["spotting",{"3":{"2047":1}}],["spotty",{"3":{"1640":1}}],["spots",{"3":{"0":1,"422":1,"759":1,"1270":1,"1395":1,"1415":1,"1435":1,"1473":1,"1536":2,"1796":1}}],["spot",{"3":{"0":2,"135":1,"422":1,"425":1,"429":1,"759":1,"761":1,"800":1,"988":1,"1259":1,"1338":1,"1395":1,"1404":1,"1435":7,"1437":1,"1448":2,"1491":1,"1495":19,"1499":1,"1999":1,"2047":1}}],["sponsorvalidationrules",{"3":{"1207":10,"1271":4,"1349":3,"1358":37}}],["sponsorvisitlink",{"2":{"1393":1},"3":{"1393":1,"1394":2,"1395":1}}],["sponsorvisitroundtriptests",{"3":{"1199":1,"1207":3}}],["sponsorvisitresultdto",{"3":{"1199":8,"1203":1,"1207":2,"1395":15}}],["sponsorvisitrequestvalidatortests",{"3":{"1199":1,"1207":2}}],["sponsorvisitrequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1395":5}}],["sponsorvisitrequest",{"3":{"1199":7,"1203":1,"1207":2,"1395":9}}],["sponsorvisittests",{"3":{"1199":1,"1207":2,"1395":3,"1437":2}}],["sponsorvisitpage",{"3":{"1199":2,"1207":2}}],["sponsorvisits",{"2":{"690":2,"1395":1},"3":{"690":2,"1203":2,"1207":4,"1395":14}}],["sponsorvisit",{"2":{"690":1},"3":{"556":1,"690":1,"1199":5,"1203":1,"1207":3,"1394":1,"1395":38}}],["sponsorname",{"3":{"1349":2,"1379":2,"1395":2}}],["sponsornamerules",{"3":{"1199":2,"1203":1,"1207":1,"1352":1,"1358":6}}],["sponsornavigationpopulatortests",{"3":{"1199":1,"1207":2,"1358":2}}],["sponsornavigationpopulator",{"3":{"1199":2,"1203":1,"1207":2,"1309":5,"1349":1,"1353":1,"1358":7}}],["sponsorbuilder",{"3":{"1199":3,"1207":2,"1435":1}}],["sponsorboothnumberrules",{"3":{"1199":2,"1203":1,"1207":1,"1358":7}}],["sponsorupdateappliertests",{"3":{"1199":1,"1207":2}}],["sponsorupdateapplier",{"3":{"1199":2,"1203":1,"1207":2,"1270":1,"1349":1,"1353":1,"1358":8}}],["sponsorupdaterequestvalidatortests",{"3":{"1199":1,"1207":2}}],["sponsorupdaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1358":13}}],["sponsorupdaterequest",{"3":{"1199":7,"1203":1,"1207":2,"1349":1,"1358":16,"1379":1}}],["sponsorformfields",{"3":{"1349":1,"1394":5}}],["sponsorformmodel",{"3":{"1199":3,"1203":1,"1207":2,"1349":1,"1383":2,"1394":9}}],["sponsorfieldrules",{"3":{"1199":3,"1203":1,"1207":1,"1352":1,"1358":20}}],["sponsorwebsiteurlrules",{"3":{"1199":2,"1203":1,"1207":1,"1271":1,"1358":4}}],["sponsortests",{"3":{"1199":1,"1207":2,"1349":1,"1437":2}}],["sponsortwitterhandlerules",{"3":{"1199":2,"1203":1,"1207":1,"1358":4}}],["sponsortierinfo",{"3":{"1495":2}}],["sponsortiers",{"3":{"1394":2}}],["sponsortier",{"3":{"1199":25,"1203":1,"1207":2,"1341":1,"1349":10,"1358":3,"1368":5,"1391":1,"1394":11}}],["sponsorlogourlrules",{"3":{"1199":2,"1203":1,"1207":1,"1271":1,"1358":4}}],["sponsorlist",{"3":{"1199":3,"1203":1,"1207":2,"1323":2,"1349":2,"1382":2,"1391":2,"1394":20}}],["sponsorlistpage",{"3":{"1199":2,"1207":2}}],["sponsorlinkedinurlrules",{"3":{"1199":2,"1203":1,"1207":1,"1358":6}}],["sponsorliveinfo",{"3":{"1199":7,"1203":1,"1207":2,"1348":1,"1349":7,"1358":1,"1377":1,"1379":3,"1395":2}}],["sponsorinfo",{"3":{"1495":2}}],["sponsoring",{"3":{"1394":1}}],["sponsorinvariantstests",{"3":{"1199":1,"1207":2,"1349":1}}],["sponsorinvariants",{"2":{"1343":1,"1361":1},"3":{"1199":14,"1203":1,"1207":2,"1343":2,"1349":8,"1358":34,"1361":1,"1368":7}}],["sponsorid",{"2":{"803":1,"1052":1,"1055":1},"3":{"690":3,"799":2,"803":1,"1052":1,"1055":1,"1349":2,"1358":2,"1379":1,"1395":34,"1414":2,"1435":1,"1525":1,"1626":2}}],["sponsoridentifiertype",{"2":{"468":1,"799":1,"1055":1},"3":{"468":1,"799":2,"1055":1,"1349":4,"1358":5,"1394":4,"1395":11}}],["sponsoreditmodel",{"3":{"1199":2,"1203":1,"1207":2,"1394":8}}],["sponsoreventidrules",{"3":{"1199":2,"1203":1,"1207":1,"1271":1,"1358":8}}],["sponsordetails",{"2":{"1393":1},"3":{"1393":1,"1394":1}}],["sponsordetailtests",{"3":{"1199":1,"1207":2}}],["sponsordetail",{"3":{"1199":2,"1203":1,"1207":2,"1349":1,"1383":2,"1387":1,"1391":2,"1394":33,"1395":1}}],["sponsordetailpage",{"3":{"1199":3,"1207":2}}],["sponsordescriptionrules",{"3":{"1199":2,"1203":1,"1207":1,"1358":4}}],["sponsordtomappertests",{"3":{"1199":1,"1207":2,"1358":2}}],["sponsordtomapper",{"3":{"1199":5,"1203":1,"1207":2,"1310":1,"1349":4,"1352":1,"1358":7}}],["sponsordto",{"3":{"1199":20,"1203":1,"1207":2,"1346":2,"1349":13,"1358":22,"1379":1,"1381":1,"1394":27,"1495":1}}],["sponsorconfiguration",{"2":{"1360":1},"3":{"1199":1,"1203":1,"1207":2,"1349":8,"1358":5,"1360":1,"1361":3,"1368":10}}],["sponsorcreatetests",{"3":{"1199":1,"1207":2}}],["sponsorcreate",{"3":{"1199":2,"1203":1,"1207":2,"1349":3,"1391":1,"1394":25}}],["sponsorcreaterequestvalidatortests",{"3":{"1199":1,"1207":2,"1358":2}}],["sponsorcreaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1358":11}}],["sponsorcreaterequestmapper",{"3":{"1199":1,"1203":1,"1207":2,"1310":2,"1358":5}}],["sponsorcreaterequest",{"3":{"1199":8,"1203":1,"1207":2,"1270":1,"1349":1,"1358":13,"1379":1}}],["sponsorcreatemodel",{"3":{"1199":3,"1203":1,"1207":2,"1394":7}}],["sponsorcreatepage",{"3":{"1199":3,"1207":2}}],["sponsorchanged",{"3":{"782":1,"1199":3,"1203":1,"1207":2,"1344":1,"1349":10,"1358":2,"1437":1}}],["sponsorsmanage",{"3":{"1349":2,"1358":1,"1372":1,"1379":2}}],["sponsorships",{"3":{"1379":1,"1394":1}}],["sponsorshippacketurl",{"2":{"1386":1},"3":{"1349":2,"1358":6,"1368":2,"1386":1,"1394":4,"1525":1}}],["sponsorshippacketurlmaxlength",{"3":{"1349":2,"1358":2}}],["sponsorship",{"3":{"1271":1,"1339":1,"1341":3,"1349":5,"1358":10,"1363":1,"1368":4,"1379":1,"1383":1,"1391":2,"1394":11,"1435":1,"1437":1,"1454":1,"1462":1,"1466":1,"1473":1,"1628":1,"1630":2}}],["sponsorsortrules",{"3":{"1199":2,"1203":1,"1207":1,"1358":4}}],["sponsorservice",{"3":{"881":1,"1199":6,"1203":1,"1207":2,"1368":1,"1381":3,"1394":9}}],["sponsorscontrollertests",{"3":{"1199":1,"1207":2,"1437":2,"1486":1}}],["sponsorscontroller",{"3":{"743":1,"1199":4,"1203":1,"1207":2,"1349":5,"1358":20,"1370":2,"1371":3,"1372":3,"1374":3,"1379":10,"1435":1}}],["sponsorscache",{"2":{"386":1},"3":{"386":1,"1349":1,"1378":1,"1379":2}}],["sponsors",{"0":{"1391":1},"2":{"1349":1,"1394":1,"1395":1},"3":{"0":1,"186":1,"556":1,"743":1,"837":1,"838":1,"881":1,"1004":1,"1203":40,"1207":112,"1270":1,"1271":4,"1309":4,"1310":2,"1341":3,"1345":2,"1346":1,"1347":2,"1348":1,"1349":37,"1350":2,"1351":1,"1352":1,"1358":127,"1362":1,"1363":1,"1368":18,"1374":1,"1379":9,"1380":1,"1381":3,"1382":4,"1383":3,"1386":1,"1387":1,"1391":6,"1394":59,"1395":13,"1414":1,"1432":1,"1435":1,"1436":1,"1437":6,"1446":1,"1486":1,"1495":1,"1525":1,"1620":1,"1624":1,"1626":6,"1628":1,"1630":7,"2239":1}}],["sponsor",{"2":{"1345":1},"3":{"0":2,"556":1,"583":1,"688":2,"690":7,"691":3,"924":1,"1199":28,"1203":1,"1207":2,"1212":1,"1270":1,"1271":5,"1309":4,"1310":1,"1339":1,"1341":12,"1342":2,"1345":3,"1348":1,"1349":76,"1350":1,"1351":3,"1353":1,"1354":1,"1358":141,"1360":1,"1361":2,"1368":22,"1374":1,"1379":11,"1380":1,"1382":1,"1386":2,"1387":1,"1391":3,"1392":1,"1394":89,"1395":103,"1414":1,"1435":2,"1437":11,"1465":1,"1466":1,"1486":5,"1495":4,"1525":4,"1626":2,"1628":1,"1630":10,"1637":1}}],["speeding",{"3":{"1300":1}}],["speed",{"3":{"459":1,"463":1,"1300":1,"1435":3,"1487":1,"1553":1,"1559":1,"1899":1,"1922":1,"2050":1}}],["speeds",{"3":{"133":1,"457":1,"465":1,"2099":1,"2105":3}}],["speechoptions",{"3":{"1416":2}}],["speechtotextoptions",{"3":{"1416":1}}],["speechtotext",{"3":{"1401":3,"1416":2}}],["speech",{"3":{"412":1,"415":1,"1192":1,"1202":1,"1203":1,"1394":1,"1415":2,"1416":16,"1495":1,"1668":2}}],["spell",{"3":{"526":1,"607":1,"611":1,"650":1,"727":1,"1247":3,"1285":2,"1299":2,"1300":2,"1322":1,"1323":1,"1325":1,"1338":1,"1351":1,"1358":2,"1368":1,"1395":2,"1404":1,"1405":1,"1414":5,"1435":2,"1443":1,"1595":1,"1813":1}}],["spells",{"3":{"390":1,"674":1,"1034":1,"1237":1,"1247":3,"1259":4,"1270":1,"1285":5,"1299":5,"1308":1,"1310":3,"1314":1,"1322":7,"1323":5,"1325":1,"1338":1,"1342":1,"1349":2,"1358":4,"1368":2,"1379":2,"1394":3,"1395":4,"1401":2,"1411":1,"1414":4,"1416":2,"1435":8,"1454":1,"1466":1,"1485":1,"1523":1,"1525":1,"1530":1,"1576":1,"2003":1,"2143":1}}],["spellings",{"3":{"892":1,"1247":2,"1285":3,"1295":1,"1296":1,"1323":1,"1338":1,"1435":1,"1595":1}}],["spelling",{"3":{"283":2,"633":4,"889":1,"974":1,"1068":1,"1247":1,"1270":1,"1285":5,"1299":1,"1322":1,"1342":1,"1368":1,"1394":1,"1395":1,"1401":1,"1414":2,"1416":1,"1435":6,"1595":1,"1721":1,"1728":1}}],["spelled",{"3":{"122":1,"793":1,"803":1,"938":1,"980":1,"1020":1,"1230":1,"1237":1,"1259":1,"1263":1,"1270":2,"1271":1,"1285":3,"1299":5,"1300":1,"1308":1,"1310":3,"1311":1,"1322":2,"1323":3,"1325":2,"1338":2,"1358":4,"1368":1,"1378":1,"1379":1,"1394":1,"1395":5,"1401":1,"1408":1,"1414":2,"1416":4,"1435":2,"1437":2,"1491":1,"1534":1,"2029":1,"2165":1}}],["spent",{"3":{"73":1,"818":2,"819":1,"872":1,"1259":1,"1282":1,"1285":1,"1292":1,"1299":2,"1310":1,"1323":1,"1338":3,"1407":1,"1417":1,"1472":1,"1482":1,"1666":1,"1842":1,"1979":1,"1981":1,"1984":1,"2169":1}}],["spending",{"3":{"151":1,"1292":1,"1435":1,"1437":1}}],["spends",{"3":{"19":1,"122":1,"592":1,"818":1,"1263":2,"1270":2,"1310":1,"1323":2,"1338":1,"1394":2,"1395":1,"1400":1,"1401":2,"1414":1,"1435":2,"1454":2,"1491":2,"1820":1,"1907":1,"2002":1,"2046":1}}],["spend",{"3":{"0":3,"178":1,"408":1,"517":1,"538":1,"650":1,"759":1,"760":1,"765":3,"767":2,"768":1,"831":1,"1017":1,"1018":3,"1019":3,"1020":3,"1041":1,"1090":1,"1091":2,"1092":1,"1093":3,"1212":1,"1229":1,"1299":1,"1310":2,"1323":2,"1329":1,"1338":2,"1349":2,"1358":3,"1368":2,"1379":1,"1394":1,"1395":1,"1401":1,"1421":1,"1424":1,"1425":1,"1426":2,"1434":1,"1435":3,"1437":1,"1441":1,"1455":2,"1456":2,"1459":1,"1466":7,"1475":1,"1489":1,"1495":1,"1525":3,"1552":1,"1567":2,"1576":2,"1590":1,"1677":1,"1678":1,"1682":1,"1787":1,"2005":1,"2155":1,"2159":1,"2167":1,"2169":1,"2176":3,"2178":1,"2179":2,"2182":1}}],["speakasync",{"3":{"1394":1,"1416":4}}],["speakable",{"3":{"882":1}}],["speak",{"0":{"2242":1},"3":{"243":1,"286":1,"346":1,"536":1,"800":1,"1300":1,"1310":1,"1311":1,"1330":1,"1337":1,"1338":6,"1349":1,"1416":2,"1445":1,"1446":1,"1655":1,"1669":1,"1927":1,"2024":1,"2115":1,"2215":1,"2242":1}}],["speaking",{"0":{"2215":1},"1":{"2237":1,"2238":1,"2239":1,"2240":1,"2241":1,"2242":1},"3":{"103":1,"856":1,"1050":1,"1053":1,"1416":3,"2237":2,"2243":1}}],["speaks",{"3":{"31":1,"97":2,"98":1,"99":1,"265":1,"331":2,"341":1,"433":1,"692":1,"905":1,"1067":1,"1134":1,"1217":1,"1241":1,"1270":1,"1271":3,"1299":2,"1308":1,"1310":1,"1325":1,"1330":1,"1336":1,"1338":4,"1349":2,"1358":1,"1394":2,"1395":2,"1416":5,"1429":1,"1435":1,"1439":2,"1443":1,"1446":1,"1684":1,"1830":2,"1873":1,"1887":1,"1927":1,"1987":2,"2008":2,"2024":1,"2082":1,"2140":1}}],["speakerprofiletests",{"3":{"1495":1}}],["speakeroverlap",{"3":{"1349":2,"1394":1}}],["speakername",{"3":{"1349":1,"1394":1}}],["speakernavigationpopulatortests",{"3":{"1199":1,"1207":2,"1358":2}}],["speakernavigationpopulator",{"3":{"1199":2,"1203":1,"1207":2,"1309":5,"1349":2,"1353":1,"1358":11}}],["speakerresult",{"3":{"1247":1}}],["speakerrenamed",{"2":{"785":1},"3":{"785":1}}],["speakervalidationrules",{"3":{"1207":7,"1271":3,"1349":3,"1358":23}}],["speakervalidationrulestests",{"3":{"1199":1,"1207":4,"1358":6}}],["speakermanagementtests",{"3":{"1199":1,"1207":2}}],["speakertests",{"3":{"1199":1,"1207":2,"1349":1}}],["speakerbuilder",{"3":{"1199":11,"1207":2,"1435":1}}],["speakerwebsiteurlrules",{"3":{"1199":2,"1203":1,"1207":1,"1358":2}}],["speakergithuburlrules",{"3":{"1199":2,"1203":1,"1207":1,"1271":1,"1358":5}}],["speakerformfields",{"3":{"1394":2}}],["speakerformmodel",{"3":{"1199":3,"1203":1,"1207":2,"1383":2,"1394":8}}],["speakerfeedbackauthtests",{"3":{"1199":1,"1207":2,"1437":2}}],["speakerfieldrules",{"3":{"1199":3,"1203":1,"1207":1,"1352":1,"1358":20}}],["speakerfirstnamerules",{"3":{"1199":3,"1203":1,"1207":1,"1271":1,"1352":1,"1358":5}}],["speakerentityqueryservicetests",{"3":{"1199":1,"1207":2,"1358":2}}],["speakerentityqueryservice",{"2":{"1244":1},"3":{"1199":2,"1203":1,"1207":2,"1242":2,"1244":3,"1247":2,"1353":2,"1358":9}}],["speakeremailrules",{"3":{"1199":2,"1203":1,"1207":1,"1358":7}}],["speakereditmodel",{"3":{"1199":3,"1203":1,"1207":2,"1394":5}}],["speakerqrtests",{"3":{"1199":1,"1207":3,"1437":2}}],["speakerqr",{"3":{"1199":2,"1203":1,"1207":2,"1323":5,"1387":3,"1394":11,"1525":1}}],["speakerqrpage",{"3":{"1199":2,"1207":2}}],["speakerquestionanswerid",{"3":{"1349":1}}],["speakerquestionansweridentifiertype",{"3":{"1349":3,"1358":1}}],["speakerquestionanswerscontroller",{"2":{"1631":1,"1638":1},"3":{"1631":1,"1638":2}}],["speakerquestionanswers",{"3":{"1309":1,"1349":3,"1358":4,"1368":1,"1435":1,"1629":2,"1631":2,"1638":1}}],["speakerquestionanswernavigationpopulatortests",{"3":{"1199":1,"1207":2,"1358":2}}],["speakerquestionanswernavigationpopulator",{"3":{"1199":2,"1203":1,"1207":2,"1358":5}}],["speakerquestionanswerconfiguration",{"2":{"1361":1},"3":{"1199":1,"1203":1,"1207":2,"1349":3,"1361":2,"1368":4}}],["speakerquestionanswerchanged",{"2":{"1344":1,"1630":1,"1631":1,"1633":1},"3":{"1199":3,"1203":1,"1207":2,"1344":1,"1349":6,"1630":1,"1631":3,"1633":2}}],["speakerquestionanswertests",{"3":{"1199":1,"1207":2,"1349":1}}],["speakerquestionanswerdtomappertests",{"3":{"1199":1,"1207":2,"1358":2}}],["speakerquestionanswerdtomapper",{"3":{"1199":7,"1203":1,"1207":2,"1349":3,"1358":7}}],["speakerquestionanswerdto",{"2":{"1346":1},"3":{"1199":3,"1203":1,"1207":2,"1346":1,"1349":5,"1358":1}}],["speakerquestionanswer",{"2":{"1341":1,"1362":1,"1637":1,"1639":1},"3":{"1198":1,"1199":12,"1203":1,"1207":2,"1341":2,"1349":12,"1358":13,"1362":1,"1368":4,"1629":1,"1630":3,"1631":1,"1633":2,"1634":3,"1636":1,"1637":5,"1638":7,"1639":5}}],["speakerlastnamerules",{"3":{"1199":3,"1203":1,"1207":1,"1358":3}}],["speakerlocalities",{"3":{"1349":3,"1394":1}}],["speakerlocality",{"3":{"1349":2,"1394":1}}],["speakerlocalityhelpertests",{"3":{"1199":1,"1207":2,"1358":5}}],["speakerlocalityhelper",{"3":{"1199":3,"1203":1,"1207":3,"1346":1,"1349":6,"1358":18,"1495":2}}],["speakerlocalitysummary",{"3":{"1199":4,"1203":1,"1207":1,"1346":1,"1349":3,"1358":2}}],["speakerlookupservice",{"2":{"1386":1},"3":{"1199":1,"1203":1,"1207":2,"1379":1,"1381":1,"1386":2,"1394":11}}],["speakerlist",{"3":{"1199":1,"1203":1,"1207":2,"1323":2,"1349":1,"1358":1,"1382":2,"1394":13}}],["speakerlistpage",{"3":{"1199":2,"1207":2}}],["speakerlinkbrokerflowtests",{"3":{"1199":1,"1207":2}}],["speakerlinkscontrollertests",{"3":{"1199":1,"1207":2,"1486":1}}],["speakerlinkscontroller",{"3":{"1199":2,"1203":1,"1207":2,"1349":5,"1358":14,"1370":2,"1372":2,"1379":8,"1394":4,"1435":1,"1495":1,"1534":1}}],["speakerlinked",{"3":{"1437":1,"1489":1,"1610":1}}],["speakerlinkedinurlrules",{"3":{"1199":2,"1203":1,"1207":1,"1271":1,"1358":2}}],["speakerlinkedtouserhandlertests",{"3":{"1199":1,"1207":3}}],["speakerlinkedtouserhandler",{"2":{"1412":1,"1610":1},"3":{"1199":2,"1203":1,"1207":2,"1259":2,"1349":1,"1412":2,"1414":9,"1523":1,"1525":1,"1533":1,"1610":1}}],["speakerlinkedtouser",{"2":{"195":1,"1340":1,"1354":1,"1450":1,"1611":1},"3":{"195":1,"1199":10,"1203":1,"1207":2,"1237":2,"1259":1,"1340":1,"1348":4,"1349":16,"1354":2,"1358":5,"1394":1,"1412":1,"1414":6,"1435":2,"1437":1,"1450":1,"1466":1,"1533":1,"1611":1}}],["speakerinvariantstests",{"3":{"1199":1,"1207":2,"1349":1}}],["speakerinvariants",{"2":{"1343":1,"1809":1},"3":{"1199":16,"1203":1,"1207":2,"1237":1,"1285":1,"1343":3,"1349":14,"1358":39,"1368":5,"1809":1}}],["speakerinfo",{"3":{"1199":25,"1203":2,"1207":2,"1358":9,"1368":1,"1386":1,"1394":15}}],["speakerids",{"3":{"1349":1,"1358":4,"1379":3,"1401":5}}],["speakerid",{"2":{"1385":2,"1812":2},"3":{"1198":2,"1199":9,"1207":2,"1247":1,"1349":24,"1358":51,"1361":1,"1368":14,"1374":1,"1379":16,"1385":2,"1387":1,"1394":46,"1414":1,"1437":1,"1487":1,"1630":3,"1631":4,"1633":2,"1634":4,"1639":1,"1640":7,"1812":2}}],["speakeridentifiertype",{"2":{"470":1,"472":1,"475":1,"476":1,"477":1,"478":1,"798":1,"803":1,"804":1,"805":1,"1049":1,"1054":1},"3":{"470":2,"472":1,"475":2,"476":2,"477":1,"478":2,"798":1,"803":1,"804":1,"805":1,"1049":1,"1054":1,"1212":1,"1285":1,"1349":22,"1358":46,"1379":4,"1394":20,"1401":7,"1403":1,"1414":2}}],["speakerunlinked",{"3":{"1437":1}}],["speakerunlinkedfromuserhandlertests",{"3":{"1199":1,"1207":3}}],["speakerunlinkedfromuserhandler",{"2":{"1412":1,"1610":1},"3":{"1199":2,"1203":1,"1207":2,"1259":2,"1349":1,"1358":1,"1412":1,"1414":4,"1525":1,"1533":1,"1610":1}}],["speakerunlinkedfromuser",{"2":{"195":1,"1340":1,"1348":1,"1354":1,"1412":1,"1450":1,"1611":1},"3":{"195":1,"1199":8,"1203":1,"1207":2,"1237":2,"1259":1,"1340":1,"1348":2,"1349":14,"1354":1,"1358":9,"1394":1,"1412":1,"1414":2,"1435":1,"1437":1,"1450":1,"1466":1,"1611":1}}],["speakerusersearch",{"3":{"1199":2,"1203":1,"1207":2,"1384":2,"1394":5,"1414":2}}],["speakerupdateauthtests",{"3":{"1199":1,"1207":2}}],["speakerupdateapplier",{"3":{"926":1,"1199":3,"1203":1,"1207":2,"1270":4,"1353":2,"1358":11}}],["speakerupdaterequestvalidatortests",{"3":{"1199":1,"1207":2}}],["speakerupdaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1358":9}}],["speakerupdaterequest",{"3":{"1199":8,"1203":1,"1207":2,"1358":15,"1379":1}}],["speakerclaims",{"3":{"1405":1,"1414":1}}],["speakercount",{"3":{"1349":1}}],["speakercommandvalidatortests",{"3":{"1207":1,"1358":2}}],["speakerconfiguration",{"2":{"1360":1},"3":{"657":1,"1199":1,"1203":1,"1207":2,"1285":3,"1349":7,"1358":1,"1360":1,"1361":3,"1368":4}}],["speakercreated",{"3":{"1630":1,"1633":1}}],["speakercreaterequestvalidatortests",{"3":{"1199":1,"1207":2,"1358":2}}],["speakercreaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1358":11}}],["speakercreaterequestmapper",{"3":{"1199":1,"1203":1,"1207":2,"1358":13}}],["speakercreaterequest",{"3":{"1199":8,"1203":1,"1207":2,"1270":1,"1358":34,"1379":1}}],["speakercreate",{"3":{"1199":1,"1203":1,"1207":2,"1394":20}}],["speakercreatemodel",{"3":{"1199":3,"1203":1,"1207":2,"1394":6}}],["speakercreatepage",{"3":{"1199":5,"1207":2}}],["speakercategoryitemid",{"3":{"1349":1,"1358":5}}],["speakercategoryitemidentifiertype",{"3":{"1349":4,"1358":4,"1394":4}}],["speakercategoryitemnavigationpopulatortests",{"3":{"1199":1,"1207":2,"1358":2}}],["speakercategoryitemnavigationpopulator",{"3":{"1199":2,"1203":1,"1207":2,"1309":1,"1358":10}}],["speakercategoryitemconfiguration",{"2":{"1361":1},"3":{"1199":1,"1203":1,"1207":2,"1349":1,"1358":1,"1361":2,"1368":6}}],["speakercategoryitemchanged",{"2":{"1344":1,"1630":1,"1633":1},"3":{"1199":4,"1203":1,"1207":2,"1344":1,"1349":6,"1358":2,"1630":1,"1633":1}}],["speakercategoryitemtests",{"3":{"1199":1,"1207":2,"1349":1}}],["speakercategoryitems",{"2":{"1362":1},"3":{"1309":3,"1323":1,"1349":4,"1358":12,"1362":1,"1368":2,"1379":2,"1394":1,"1435":1,"1446":1,"1629":2,"1631":3,"1634":1,"1638":2}}],["speakercategoryitemscontrollertests",{"3":{"1199":1,"1207":2}}],["speakercategoryitemscontroller",{"2":{"1370":1,"1371":1},"3":{"1199":3,"1203":1,"1207":3,"1310":2,"1349":1,"1358":23,"1370":2,"1371":2,"1372":2,"1373":1,"1379":16}}],["speakercategoryitemspaneltests",{"3":{"1199":1,"1207":3}}],["speakercategoryitemspanel",{"2":{"1384":1},"3":{"1199":2,"1203":1,"1207":2,"1384":2,"1394":12}}],["speakercategoryitemservice",{"2":{"1385":1},"3":{"1199":2,"1203":1,"1207":1,"1323":1,"1385":1,"1394":4}}],["speakercategoryitemdtomappertests",{"3":{"1199":1,"1207":2,"1358":2}}],["speakercategoryitemdtomapper",{"3":{"1199":9,"1203":1,"1207":2,"1349":3,"1358":16}}],["speakercategoryitemdto",{"3":{"1199":10,"1203":1,"1207":2,"1346":1,"1349":7,"1358":6,"1379":1,"1394":4}}],["speakercategoryitem",{"2":{"1341":1},"3":{"1198":1,"1199":16,"1203":1,"1207":2,"1237":2,"1309":1,"1341":3,"1349":8,"1358":24,"1368":3,"1379":1,"1394":1,"1629":1,"1630":1,"1631":1,"1633":2,"1634":2,"1636":1,"1637":3,"1638":5}}],["speakerchanged",{"2":{"1354":1,"1637":1},"3":{"782":2,"1199":5,"1203":1,"1207":2,"1259":1,"1344":3,"1349":13,"1354":1,"1358":4,"1630":1,"1633":3,"1637":3,"1638":1}}],["speakerdeleted",{"3":{"1358":1}}],["speakerdeletedhandlertests",{"3":{"1199":1,"1207":3}}],["speakerdeletedhandler",{"2":{"780":1,"782":1,"785":1},"3":{"780":1,"782":2,"784":1,"785":1,"1199":3,"1203":1,"1207":2,"1237":1,"1349":7,"1354":4,"1358":3}}],["speakerdetails",{"3":{"1394":2}}],["speakerdetailtests",{"3":{"1199":1,"1207":2,"1394":2}}],["speakerdetail",{"3":{"1199":2,"1203":1,"1207":2,"1237":1,"1349":1,"1383":2,"1394":47,"1414":2,"1495":1,"1525":1,"1530":1,"1534":1,"1626":1}}],["speakerdetailpage",{"3":{"1199":4,"1207":2}}],["speakerdetaillookupservice",{"2":{"1386":1},"3":{"1199":1,"1203":1,"1207":2,"1386":2,"1394":18}}],["speakerdetaillookups",{"2":{"1386":1},"3":{"1199":5,"1203":1,"1207":1,"1386":1,"1394":8}}],["speakerdtotests",{"3":{"1199":1,"1207":2}}],["speakerdto",{"3":{"1199":28,"1203":1,"1207":3,"1346":2,"1349":17,"1358":22,"1379":1,"1381":1,"1394":20}}],["speakerdtomappertests",{"3":{"1199":1,"1207":2,"1358":2}}],["speakerdtomapper",{"2":{"5":1,"6":1},"3":{"5":1,"6":1,"657":1,"1199":8,"1203":1,"1207":2,"1349":9,"1352":3,"1358":27}}],["speakerdashboardtests",{"3":{"1199":2,"1207":4}}],["speakerdashboardpage",{"3":{"1199":3,"1207":2}}],["speakerdashboardservicetests",{"3":{"1199":1,"1207":2,"1394":1}}],["speakerdashboardservice",{"2":{"1387":1},"3":{"881":1,"1199":2,"1203":1,"1207":2,"1299":2,"1349":3,"1387":3,"1394":23}}],["speakerdashboard",{"2":{"1387":1},"3":{"556":1,"1119":1,"1199":2,"1203":1,"1207":2,"1349":3,"1387":2,"1394":20,"1530":1}}],["speakerscache",{"3":{"1349":1,"1358":1,"1378":1,"1379":2}}],["speakerscontrollertests",{"3":{"1199":1,"1207":2}}],["speakerscontroller",{"3":{"550":1,"552":1,"743":1,"1199":4,"1203":1,"1207":2,"1247":4,"1310":4,"1349":7,"1358":43,"1370":2,"1371":3,"1372":7,"1374":5,"1379":19,"1394":1,"1395":2,"1435":2,"1495":1,"1525":1}}],["speakerssynced",{"3":{"1349":1,"1358":1}}],["speakersmanage",{"2":{"1372":1},"3":{"1349":3,"1358":7,"1372":3,"1379":7,"1394":1}}],["speakersyncstrategytests",{"3":{"1199":1,"1207":2,"1358":1}}],["speakersyncstrategy",{"3":{"1199":3,"1203":1,"1207":2,"1349":6,"1357":1,"1358":16,"1525":1}}],["speakersexcept",{"3":{"1394":1}}],["speakerselfservicetests",{"3":{"1199":1,"1207":2,"1525":2}}],["speakersessionassettests",{"3":{"1199":1,"1207":2}}],["speakersessionscontrollertests",{"3":{"1199":1,"1207":2,"1486":1}}],["speakersessionscontroller",{"2":{"1370":1,"1374":1},"3":{"1199":2,"1203":1,"1207":2,"1349":4,"1370":2,"1374":3,"1379":6,"1395":2,"1435":1,"1495":1}}],["speakersessionsummary",{"3":{"1199":7,"1203":1,"1207":1,"1346":1,"1349":3,"1358":3,"1394":1}}],["speakersessionoverlapdto",{"2":{"1346":1},"3":{"1199":8,"1203":1,"1207":4,"1346":1,"1349":9,"1358":3,"1379":1}}],["speakerservice",{"3":{"881":1,"1199":7,"1203":1,"1207":2,"1381":4,"1394":8}}],["speakers",{"2":{"1349":1,"1379":1,"1394":1},"3":{"0":2,"186":1,"353":2,"470":1,"550":1,"554":1,"556":2,"657":3,"743":1,"782":1,"881":2,"926":1,"1004":6,"1018":1,"1116":4,"1118":1,"1168":1,"1202":1,"1203":95,"1207":296,"1212":1,"1237":6,"1238":1,"1242":2,"1247":3,"1259":2,"1270":8,"1271":3,"1285":1,"1299":1,"1309":7,"1310":4,"1323":4,"1339":1,"1341":3,"1342":1,"1344":1,"1345":1,"1346":5,"1347":1,"1348":3,"1349":87,"1350":4,"1351":3,"1352":4,"1353":2,"1354":5,"1356":2,"1357":4,"1358":294,"1362":1,"1363":3,"1364":1,"1366":1,"1368":19,"1370":3,"1372":1,"1379":36,"1380":1,"1381":4,"1382":5,"1383":3,"1384":2,"1385":1,"1386":4,"1387":7,"1388":1,"1391":1,"1394":112,"1395":3,"1401":11,"1412":1,"1414":7,"1415":2,"1432":1,"1434":2,"1435":2,"1436":1,"1437":6,"1440":1,"1446":1,"1457":1,"1491":1,"1495":1,"1525":1,"1530":1,"1533":1,"1534":1,"1607":1,"1620":1,"1624":1,"1625":1,"1626":4,"1628":8,"1629":7,"1630":6,"1631":16,"1634":3,"1635":5,"1636":5,"1637":4,"1638":10,"1639":4,"1640":14,"1814":1,"1862":1,"1865":1,"2069":1,"2109":2,"2215":1,"2237":1,"2239":2,"2240":1}}],["speaker",{"0":{"1623":1,"1625":1},"1":{"1113":1,"1114":1,"1115":1,"1116":1,"1117":1,"1118":1,"1119":1,"1120":1},"2":{"1354":1,"1620":1,"1625":1,"1634":1,"1635":1,"1639":1,"1640":1,"1982":1},"3":{"0":3,"340":1,"348":1,"350":2,"351":1,"353":5,"387":2,"443":1,"470":1,"545":1,"655":1,"657":4,"888":2,"926":1,"1017":1,"1018":3,"1113":1,"1115":4,"1116":7,"1117":1,"1118":2,"1168":2,"1192":1,"1198":2,"1199":63,"1201":1,"1202":1,"1203":2,"1207":19,"1212":1,"1237":3,"1239":1,"1247":2,"1270":3,"1271":2,"1280":1,"1285":3,"1287":1,"1299":4,"1309":4,"1310":1,"1311":1,"1322":1,"1323":4,"1339":3,"1341":13,"1342":8,"1344":2,"1346":5,"1347":2,"1348":2,"1349":182,"1350":2,"1351":2,"1352":4,"1353":1,"1354":9,"1356":7,"1358":493,"1360":1,"1361":4,"1362":1,"1363":1,"1365":3,"1368":33,"1370":2,"1372":5,"1374":3,"1375":1,"1376":1,"1377":1,"1378":1,"1379":67,"1380":1,"1381":1,"1382":2,"1384":1,"1385":2,"1386":1,"1387":8,"1388":7,"1389":1,"1391":1,"1394":197,"1395":16,"1396":2,"1401":38,"1402":1,"1404":1,"1405":2,"1411":1,"1412":4,"1414":28,"1415":2,"1423":1,"1435":11,"1436":1,"1437":22,"1466":5,"1486":4,"1488":2,"1489":1,"1491":1,"1495":3,"1497":1,"1523":1,"1525":4,"1530":1,"1533":2,"1534":2,"1607":1,"1610":4,"1616":1,"1620":8,"1623":1,"1625":2,"1626":15,"1628":4,"1629":37,"1630":14,"1631":58,"1632":2,"1633":7,"1634":11,"1635":4,"1636":12,"1637":16,"1638":38,"1639":12,"1640":77,"1804":1,"1812":1,"1814":4,"1868":2,"1921":2,"1954":1,"1976":1,"1982":1,"2109":1,"2112":1,"2125":1,"2126":1,"2215":1,"2231":1}}],["specparser",{"3":{"2219":1}}],["speculatively",{"3":{"1454":1}}],["spectrum",{"3":{"1435":1,"1491":1,"2003":1}}],["spectestentity",{"3":{"1199":27,"1207":1}}],["spectestchild",{"3":{"1199":6,"1207":1}}],["spectestmap",{"3":{"1198":1,"1199":2,"1207":1,"1435":8}}],["specfactory",{"3":{"1299":1}}],["spec2",{"3":{"1247":3}}],["spec1",{"3":{"1247":3}}],["specs",{"3":{"319":1,"607":2,"609":3,"976":1,"1192":1,"1247":1,"1338":1,"1430":1,"1435":4,"1466":3,"1475":3,"1495":1,"1545":1,"2045":3}}],["spec",{"3":{"0":3,"318":2,"320":3,"549":1,"607":4,"609":15,"610":1,"611":1,"1212":1,"1247":6,"1285":2,"1310":1,"1335":1,"1338":2,"1358":3,"1414":1,"1430":1,"1435":3,"1466":3,"1475":1,"1495":1,"1499":1,"1533":1,"1576":1,"1590":1,"1637":1,"1638":10,"1814":2,"2157":2,"2238":1}}],["specimen",{"3":{"1322":1,"1339":1}}],["specifying",{"3":{"1638":1}}],["specifykind",{"3":{"1285":2,"1308":1,"1395":1,"1414":1}}],["specify",{"3":{"1237":1,"1271":1,"1358":6,"1638":2}}],["specified",{"3":{"861":1,"885":1,"1358":1,"1630":1,"1638":3,"1639":1}}],["specifies",{"3":{"859":1,"880":1,"1285":1,"1416":1,"1428":1,"1639":1}}],["specificity",{"3":{"1310":1,"1576":1}}],["specifically",{"3":{"665":1,"702":1,"1229":1,"1247":1,"1285":1,"1310":3,"1311":1,"1322":2,"1323":4,"1338":2,"1349":1,"1358":2,"1394":2,"1395":3,"1414":1,"1416":1,"1426":1,"1435":8,"1454":1,"1466":1,"1489":1,"1726":1,"1839":1,"1959":1,"2174":1}}],["specificationprefix",{"3":{"1285":3}}],["specificationtestcontext",{"3":{"1207":3}}],["specificationtestdbcontext",{"3":{"1199":8,"1207":1}}],["specificationtests",{"3":{"1199":1,"1207":5,"1247":5}}],["specificationadditionaltests",{"3":{"1199":1,"1207":5}}],["specificationhonoringqueryservice",{"3":{"1199":2,"1207":1}}],["specificationconventiontests",{"3":{"1199":1,"1207":2,"1247":1,"1435":5,"1488":2,"1525":3}}],["specificationconventiontestsbase",{"2":{"1653":1},"3":{"1199":2,"1207":2,"1239":3,"1247":1,"1435":7,"1495":1,"1653":1}}],["specificationcompositiontests",{"3":{"1199":1,"1207":7,"1247":6,"1437":2}}],["specificationcomposer",{"2":{"1239":1},"3":{"545":1,"549":1,"1199":4,"1203":1,"1207":1,"1239":1,"1247":6,"1495":1}}],["specificationentitytype",{"3":{"1435":1}}],["specificationevaluatortests",{"3":{"1199":1,"1207":8,"1247":1,"1285":1,"1437":1}}],["specificationevaluator",{"2":{"1272":1,"1278":1},"3":{"1199":4,"1203":1,"1207":2,"1240":8,"1244":1,"1246":1,"1247":8,"1272":1,"1278":2,"1285":25,"1437":1,"1495":1}}],["specificationextensions",{"2":{"549":1,"550":1,"1239":1},"3":{"319":1,"326":1,"549":2,"550":1,"1199":1,"1203":1,"1207":2,"1239":6,"1247":5,"1358":1,"1379":3,"1495":1}}],["specificationbenchmarks",{"3":{"591":2,"592":1,"1199":1,"1207":5,"1247":1,"1435":2,"1437":2,"1495":1,"1576":1}}],["specificationfitnesstests",{"2":{"137":1,"139":1},"3":{"137":1,"139":1,"1198":1,"1199":2,"1207":9,"1435":28,"1488":2,"1495":1,"1576":1}}],["specificationsdonotnavigatetootherentities",{"2":{"168":1,"171":1,"548":1,"1247":1,"1437":1,"1488":1,"1495":1,"1576":1,"1858":1},"3":{"168":1,"171":1,"548":1,"1239":1,"1247":2,"1358":1,"1435":5,"1437":1,"1488":1,"1495":1,"1576":1,"1858":1}}],["specifications",{"0":{"1278":1,"1352":1,"1639":1,"1814":1},"1":{"1238":1,"1239":1,"1240":1,"1241":1,"1242":1,"1243":1,"1244":1,"1245":1,"1246":1,"1247":1,"1600":1,"1601":1,"1602":1,"1603":1,"1627":1,"1628":1,"1629":1,"1630":1,"1631":1,"1632":1,"1633":1,"1634":1,"1635":1,"1636":1,"1637":1,"1638":1,"1639":1,"1640":1,"1812":1,"1813":1,"1814":1,"1815":1,"1816":1},"2":{"168":1},"3":{"132":1,"168":3,"171":2,"318":3,"322":1,"543":1,"545":8,"547":1,"549":3,"592":1,"743":1,"1192":1,"1202":4,"1203":18,"1207":112,"1237":1,"1238":2,"1239":11,"1240":1,"1241":1,"1247":46,"1259":1,"1285":4,"1349":3,"1350":1,"1352":3,"1358":16,"1379":1,"1395":2,"1430":5,"1435":23,"1436":1,"1437":6,"1486":1,"1488":2,"1495":2,"1525":2,"1576":3,"1585":1,"1590":2,"1600":2,"1603":1,"1627":1,"1638":1,"1659":1,"1661":1,"1778":1,"1811":1,"1812":1,"1814":2,"1815":2,"1816":2,"1993":2,"2205":1,"2219":1,"2226":2,"2232":2}}],["specification",{"0":{"1239":1,"1638":1},"1":{"542":1,"543":1,"544":1,"545":1,"546":1,"547":1,"548":1,"549":1,"550":1,"551":1,"552":1,"1761":1,"1762":1,"1763":1,"1764":1,"1765":1,"1766":1,"1767":1,"1768":1,"1769":1,"1770":1,"1771":1,"1772":1,"1773":1,"1774":1,"1775":1,"1776":1},"2":{"319":1,"321":1,"547":1,"550":2,"592":1,"1240":2,"1246":1,"1758":1,"1996":1},"3":{"0":7,"168":1,"318":9,"319":5,"321":2,"324":3,"325":1,"542":1,"543":5,"544":1,"545":12,"546":1,"547":4,"548":3,"549":18,"550":6,"551":4,"552":1,"591":1,"592":3,"595":1,"743":1,"798":1,"853":1,"1132":1,"1137":1,"1192":1,"1199":69,"1202":1,"1203":2,"1207":7,"1212":1,"1238":2,"1239":25,"1240":7,"1243":2,"1244":1,"1245":1,"1246":2,"1247":92,"1272":1,"1278":1,"1283":1,"1285":57,"1296":1,"1299":11,"1309":1,"1310":14,"1322":2,"1338":2,"1349":1,"1352":4,"1358":94,"1371":2,"1372":1,"1374":2,"1379":33,"1394":2,"1430":1,"1435":43,"1437":4,"1488":2,"1495":3,"1524":1,"1525":2,"1538":2,"1575":1,"1576":5,"1585":2,"1590":1,"1599":1,"1602":1,"1630":3,"1631":1,"1636":1,"1661":2,"1667":1,"1670":1,"1748":2,"1758":1,"1761":1,"1776":1,"1812":2,"1813":1,"1814":17,"1816":2,"1858":2,"1859":1,"1922":1,"1993":8,"1994":4,"1995":3,"1996":2,"2046":1,"2182":1,"2231":2}}],["specifics",{"0":{"1361":1},"3":{"164":1,"166":1,"987":1,"1349":1,"1358":1,"1368":1,"1416":1,"1466":5}}],["specific",{"3":{"0":4,"6":2,"93":1,"94":1,"109":1,"111":2,"137":1,"166":1,"175":1,"201":1,"224":1,"236":1,"265":1,"319":1,"332":1,"351":1,"381":1,"397":1,"399":1,"498":1,"499":1,"500":1,"506":1,"508":1,"530":1,"558":1,"572":1,"618":1,"627":1,"633":2,"642":1,"649":1,"650":1,"666":1,"681":1,"683":1,"697":1,"724":4,"725":1,"727":1,"728":1,"737":1,"743":1,"766":1,"782":1,"815":1,"828":1,"854":1,"863":1,"876":1,"882":1,"888":1,"890":1,"916":1,"939":1,"954":2,"959":1,"963":1,"987":1,"1016":1,"1020":1,"1041":1,"1066":1,"1067":1,"1068":1,"1089":1,"1093":1,"1096":1,"1132":1,"1144":1,"1174":1,"1175":1,"1179":1,"1190":1,"1201":1,"1211":1,"1212":1,"1214":1,"1229":4,"1234":1,"1237":1,"1246":1,"1247":3,"1258":1,"1259":2,"1265":1,"1270":11,"1271":2,"1273":1,"1283":1,"1285":20,"1288":1,"1290":2,"1295":1,"1297":1,"1299":18,"1300":1,"1301":1,"1308":11,"1310":10,"1320":1,"1321":1,"1322":18,"1323":28,"1327":1,"1337":1,"1338":9,"1343":2,"1349":7,"1351":1,"1358":30,"1365":1,"1368":2,"1370":1,"1379":12,"1386":1,"1394":21,"1395":21,"1401":6,"1405":1,"1409":1,"1414":23,"1415":8,"1416":11,"1423":1,"1426":1,"1428":2,"1431":1,"1432":1,"1435":31,"1437":6,"1439":1,"1440":1,"1442":1,"1444":1,"1446":1,"1448":1,"1450":1,"1452":1,"1454":2,"1455":1,"1456":1,"1459":1,"1466":1,"1473":1,"1474":2,"1487":4,"1489":3,"1495":2,"1524":1,"1537":1,"1553":1,"1576":1,"1590":2,"1595":1,"1629":3,"1630":1,"1631":3,"1635":1,"1636":5,"1638":5,"1639":1,"1640":6,"1644":1,"1655":1,"1657":1,"1665":1,"1666":1,"1669":1,"1684":1,"1685":1,"1726":2,"1748":1,"1763":3,"1768":1,"1770":1,"1776":1,"1815":1,"1818":1,"1851":1,"1852":1,"1881":1,"1895":1,"1896":1,"1926":1,"1928":1,"1976":1,"1982":1,"1989":1,"1991":1,"2005":2,"2029":1,"2032":1,"2036":1,"2058":2,"2066":1,"2072":1,"2087":1,"2106":1,"2122":1,"2136":1,"2144":1,"2153":1,"2155":1,"2158":1,"2167":1,"2182":1,"2188":1,"2210":1,"2231":4}}],["specially",{"3":{"1311":1}}],["specialization",{"3":{"1358":8,"1394":2,"1415":1}}],["specializations",{"3":{"1353":1,"1407":1,"1409":1}}],["specialize",{"3":{"1358":1}}],["specializes",{"3":{"1338":1,"1636":1}}],["specialized",{"3":{"1285":2,"1349":2,"1358":3,"1394":2,"1395":3,"1415":1}}],["specializing",{"3":{"1229":1,"1358":1}}],["specialised",{"3":{"940":1,"1271":1,"1692":1}}],["specialist",{"3":{"620":1}}],["specialprice",{"2":{"658":1,"660":1},"3":{"658":1,"660":1}}],["special",{"3":{"0":2,"109":1,"135":2,"227":1,"389":1,"741":1,"820":1,"982":1,"1025":1,"1026":1,"1028":1,"1212":1,"1237":1,"1259":1,"1271":1,"1285":1,"1310":3,"1311":1,"1323":1,"1338":1,"1341":2,"1349":8,"1358":6,"1363":1,"1368":3,"1379":1,"1394":3,"1395":1,"1414":3,"1415":1,"1416":1,"1435":2,"1454":1,"1466":1,"1628":1,"1630":3,"1637":1,"1640":1,"1747":1,"1763":1,"1766":6,"1767":1,"1828":1,"1969":1,"1999":1,"2016":1}}],["spaghetti",{"1":{"1812":1,"1813":1,"1814":1,"1815":1,"1816":1},"3":{"1778":1,"1811":1,"1812":2,"2098":1,"2205":1,"2226":1}}],["spawning",{"3":{"1442":1}}],["spacing",{"3":{"890":1,"1323":1,"1556":2,"1576":1}}],["spaces",{"3":{"23":1,"972":1,"1285":1,"1323":1,"1435":3}}],["space",{"3":{"0":1,"157":1,"607":2,"609":2,"725":1,"1060":1,"1247":1,"1285":2,"1299":2,"1310":2,"1323":4,"1338":1,"1358":5,"1368":1,"1394":2,"1414":1,"1416":1,"1454":1,"1458":1,"1464":1,"1636":1,"1907":1,"1926":1}}],["sparingly",{"3":{"1270":2}}],["spared",{"3":{"1875":1}}],["spares",{"3":{"1270":2,"1379":1}}],["spare",{"3":{"790":1,"862":1,"1338":1,"1435":1}}],["sparse",{"0":{"1242":1},"3":{"0":1,"330":1,"332":1,"337":1,"591":1,"740":1,"1238":2,"1247":4,"1285":1,"1361":1,"1414":1,"1437":1,"1665":1,"1719":1,"1814":1,"1816":1,"1985":1,"1987":1,"1990":1,"2231":1}}],["spam",{"3":{"280":1,"1338":1,"1398":1,"1401":3,"1416":1,"1998":1,"2156":1}}],["spa",{"3":{"174":1,"1299":2,"1487":1,"1971":1,"1972":1,"1997":1,"2152":1}}],["spanid",{"3":{"1074":1,"1253":1,"1259":3,"1285":1,"2185":1}}],["spanish",{"3":{"0":1,"264":1,"265":2,"267":1,"856":1,"1310":3,"1323":5,"1379":1,"1388":1,"1394":2,"1395":1,"1435":8,"1437":1,"1576":3,"1581":1,"1582":1,"1590":1,"1594":1,"1595":2,"1652":1,"1668":1,"1684":2,"1783":1,"2079":1,"2080":2,"2084":4,"2085":1,"2215":1,"2222":1,"2231":1,"2241":1,"2244":1}}],["spanning",{"3":{"168":1,"760":1,"810":1,"1201":1,"1349":1,"1358":1,"1368":2,"1394":1,"1423":1,"1437":1,"1495":3,"1540":1,"1546":1,"1576":1,"1636":1,"2217":1,"2219":1}}],["span",{"2":{"1557":1},"3":{"47":1,"49":1,"115":1,"295":1,"395":6,"397":9,"398":1,"399":3,"400":1,"402":4,"404":1,"405":2,"406":3,"409":1,"520":1,"635":1,"821":1,"1042":1,"1091":1,"1093":1,"1102":1,"1212":1,"1229":2,"1247":2,"1251":1,"1259":3,"1273":1,"1285":9,"1300":2,"1308":1,"1310":5,"1323":4,"1332":4,"1338":39,"1349":1,"1358":7,"1414":4,"1420":1,"1424":2,"1426":1,"1435":1,"1442":7,"1449":1,"1495":3,"1525":1,"1557":1,"1564":1,"1571":1,"1574":1,"1576":4,"1582":1,"1590":2,"1630":1,"1663":1,"1818":1,"1842":1,"1908":1,"2009":3,"2153":1,"2156":10,"2158":3,"2159":1,"2178":1,"2182":1,"2191":1,"2212":1,"2214":1,"2231":1}}],["spans",{"0":{"2156":1},"3":{"0":1,"139":1,"157":1,"161":1,"230":1,"359":1,"396":1,"397":2,"398":1,"399":2,"402":2,"405":2,"466":1,"535":1,"609":1,"674":2,"676":1,"761":1,"803":1,"926":1,"946":1,"1044":1,"1089":1,"1124":1,"1201":1,"1212":1,"1270":2,"1285":6,"1299":1,"1301":1,"1319":1,"1322":1,"1338":14,"1340":1,"1345":1,"1349":2,"1358":8,"1379":1,"1394":1,"1395":1,"1416":1,"1426":1,"1430":1,"1435":2,"1437":2,"1442":6,"1453":1,"1466":3,"1495":1,"1510":1,"1549":1,"1576":1,"1667":1,"1669":1,"1676":2,"1788":1,"1840":1,"1847":1,"1851":1,"1852":1,"1857":1,"1887":1,"1891":1,"1901":1,"1908":1,"1911":1,"1999":1,"2009":3,"2011":1,"2013":2,"2141":1,"2153":2,"2155":2,"2156":4,"2158":1,"2159":2,"2160":1,"2191":1,"2219":1,"2226":1}}],["sms",{"3":{"1416":1,"2219":1}}],["sm",{"3":{"1323":1,"1590":1,"1711":1,"2072":1}}],["smuggling",{"3":{"1311":1,"1323":1}}],["smuggled",{"3":{"926":1,"1265":1,"1270":1,"1322":1,"1394":1}}],["smuggle",{"3":{"0":1,"1247":1,"1299":1,"1322":1,"1323":1,"1358":2,"1401":2,"1435":1}}],["smearing",{"3":{"1831":1,"1833":1}}],["smear",{"3":{"1278":1}}],["smeared",{"3":{"1247":1,"1323":1,"1358":1,"1395":1,"1401":1,"1930":1}}],["smell",{"3":{"70":1,"785":1,"1247":2,"1358":1,"1435":2,"1537":1,"1827":1,"1833":1}}],["smooth",{"3":{"1323":1}}],["smooths",{"3":{"1310":1}}],["smoothing",{"3":{"178":1,"1310":1}}],["smokes",{"3":{"1445":1,"1489":1}}],["smoketests",{"2":{"1069":1},"3":{"913":4,"1066":1,"1067":2,"1069":3,"1070":1,"1199":3,"1203":3,"1206":1,"1207":9,"1327":2,"1338":9,"1448":2,"1486":1}}],["smoke",{"0":{"1429":1,"1448":1},"3":{"0":8,"150":2,"373":1,"563":1,"564":1,"589":1,"591":1,"594":1,"620":1,"624":4,"626":4,"629":1,"631":2,"633":8,"635":3,"640":2,"642":1,"699":1,"755":2,"757":4,"758":2,"759":2,"762":1,"912":2,"913":2,"914":2,"916":1,"917":2,"1067":1,"1069":2,"1073":1,"1075":1,"1192":1,"1212":4,"1213":1,"1338":8,"1427":1,"1433":1,"1435":7,"1436":2,"1437":10,"1448":1,"1452":2,"1454":12,"1459":2,"1460":3,"1464":1,"1466":3,"1468":2,"1473":1,"1481":1,"1485":3,"1486":3,"1487":1,"1489":5,"1491":10,"1492":1,"1493":1,"1495":3,"1496":2,"1523":1,"1525":6,"1530":3,"1534":1,"1574":1,"1576":3,"1580":1,"1581":1,"1582":1,"1584":1,"1587":1,"1588":1,"1589":2,"1590":6,"1599":2,"1610":1,"1656":1,"1660":1,"1695":1,"1705":1,"1715":1,"1726":1,"1730":1,"1922":2,"2037":1,"2055":1,"2058":1,"2078":1,"2088":1,"2231":2}}],["smallness",{"3":{"2171":1}}],["smallestscreensize",{"3":{"1415":1}}],["smallest",{"3":{"414":1,"666":1,"699":1,"979":1,"1232":1,"1237":1,"1247":1,"1269":1,"1270":1,"1285":3,"1322":1,"1323":5,"1338":1,"1349":1,"1351":1,"1358":15,"1368":2,"1394":9,"1395":3,"1401":3,"1414":2,"1416":4,"1435":8,"1437":1,"1466":1,"1716":1,"1726":1,"1764":1,"1769":1,"1850":1,"1937":1,"2073":1}}],["smaller",{"3":{"409":1,"545":1,"547":1,"549":1,"593":1,"775":1,"827":1,"840":1,"875":1,"981":1,"1091":1,"1115":1,"1168":1,"1170":1,"1201":1,"1280":1,"1282":1,"1299":1,"1310":2,"1322":2,"1323":3,"1343":1,"1349":2,"1358":2,"1394":2,"1421":1,"1426":2,"1428":2,"1435":4,"1466":2,"1478":1,"1479":1,"1491":1,"1922":1,"1945":1,"2010":1,"2078":1,"2157":1,"2163":1,"2171":1,"2173":1}}],["small",{"0":{"1804":1,"2116":1},"1":{"1716":1,"1717":1,"1718":1,"1719":1,"1720":1,"1721":1,"1722":1,"1723":1,"1724":1},"3":{"6":1,"60":1,"122":1,"148":1,"165":1,"201":1,"235":1,"255":1,"273":1,"280":1,"292":1,"379":1,"398":1,"413":1,"440":1,"442":1,"507":1,"520":1,"572":1,"583":1,"593":1,"633":1,"650":1,"656":1,"681":1,"684":1,"692":1,"732":1,"742":1,"765":1,"897":1,"921":1,"930":1,"932":1,"1093":1,"1134":1,"1150":3,"1152":1,"1191":1,"1222":1,"1223":1,"1229":4,"1230":2,"1233":1,"1237":1,"1241":1,"1242":1,"1247":6,"1248":1,"1252":1,"1256":1,"1257":1,"1259":6,"1260":1,"1268":1,"1270":3,"1272":1,"1283":1,"1285":5,"1289":1,"1290":1,"1291":2,"1299":13,"1300":3,"1304":1,"1308":1,"1309":3,"1310":5,"1311":1,"1312":1,"1317":1,"1319":1,"1321":3,"1322":5,"1323":28,"1324":1,"1327":1,"1336":1,"1338":6,"1346":1,"1349":13,"1351":1,"1352":1,"1353":1,"1355":1,"1358":16,"1359":1,"1368":3,"1373":1,"1379":1,"1381":1,"1384":1,"1388":1,"1394":21,"1395":22,"1397":1,"1398":1,"1401":12,"1409":1,"1410":1,"1414":10,"1415":2,"1416":8,"1426":1,"1435":13,"1437":1,"1448":1,"1454":2,"1458":1,"1459":1,"1466":2,"1477":1,"1488":1,"1491":1,"1508":1,"1525":1,"1530":1,"1533":1,"1576":1,"1581":1,"1601":1,"1610":1,"1652":1,"1660":1,"1673":1,"1674":1,"1691":1,"1704":1,"1711":1,"1716":1,"1717":1,"1725":1,"1726":2,"1791":1,"1802":1,"1804":1,"1806":1,"1825":1,"1832":1,"1833":1,"1843":1,"1879":1,"1897":1,"1914":1,"1929":1,"1931":1,"1938":1,"1941":1,"1942":1,"1946":1,"1949":1,"1957":1,"1961":1,"1964":1,"1967":1,"1971":1,"1973":1,"1984":1,"2011":1,"2025":1,"2031":1,"2041":1,"2046":1,"2054":1,"2080":1,"2085":1,"2091":1,"2102":1,"2114":1,"2115":1,"2116":1,"2117":1,"2122":3,"2126":1,"2135":1,"2173":1,"2196":1}}],["smarttoy",{"3":{"1394":1}}],["smarter",{"3":{"1310":1}}],["smart",{"0":{"1235":1},"1":{"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1},"3":{"0":4,"15":1,"17":1,"18":1,"707":2,"711":1,"961":1,"963":2,"964":2,"965":3,"966":1,"1042":1,"1043":1,"1045":1,"1049":1,"1051":2,"1212":2,"1230":1,"1237":2,"1254":2,"1259":10,"1275":1,"1281":1,"1285":15,"1316":2,"1322":3,"1323":1,"1394":3,"1435":1,"1437":1,"1466":2,"1525":1,"1554":1,"1576":1,"1585":1,"1661":1,"1664":1,"1676":1,"1840":1,"2231":1}}],["smtppassword",{"3":{"1466":1}}],["smtpemailsendertests",{"3":{"1199":1,"1207":2}}],["smtpemailsender",{"2":{"72":1,"1511":1,"1751":1,"1937":1},"3":{"72":2,"230":4,"674":2,"1199":2,"1203":1,"1207":2,"1301":5,"1308":24,"1319":2,"1322":5,"1495":2,"1511":1,"1669":1,"1751":1,"1769":1,"1931":1,"1937":1}}],["smtpclient",{"2":{"72":1,"1937":1},"3":{"72":1,"230":1,"1301":2,"1308":4,"1322":2,"1937":1}}],["smtpsettingstests",{"3":{"1199":1,"1207":1}}],["smtpsettings",{"2":{"72":1,"1301":1},"3":{"72":2,"674":2,"1199":7,"1203":1,"1207":2,"1301":1,"1308":11,"1312":1,"1319":1,"1322":12}}],["smtptransportsecuritytests",{"3":{"1108":1,"1199":1,"1207":2,"1322":1}}],["smtptransportsecurity",{"2":{"1108":1,"1301":1},"3":{"0":1,"1108":2,"1199":3,"1203":1,"1207":2,"1212":1,"1301":1,"1308":4,"1312":1,"1319":5,"1322":5,"1495":2}}],["smtp",{"3":{"0":2,"66":2,"72":2,"74":1,"230":2,"598":1,"599":1,"600":1,"673":2,"827":1,"853":1,"856":1,"1107":1,"1108":4,"1109":2,"1112":1,"1301":2,"1302":1,"1308":9,"1319":1,"1322":9,"1325":4,"1338":3,"1407":1,"1414":2,"1439":1,"1440":3,"1445":5,"1454":1,"1465":1,"1466":9,"1469":2,"1471":5,"1473":1,"1669":1,"1751":1,"1769":1,"1937":2,"2004":1,"2011":2,"2012":1,"2164":1,"2188":2}}],["shy",{"3":{"1525":1}}],["sh",{"0":{"1471":1},"3":{"1470":1,"1471":20,"1474":1,"1481":1,"1482":2}}],["shuffling",{"3":{"1395":1}}],["shuts",{"3":{"1416":1,"2065":1}}],["shut",{"3":{"1299":1,"2006":1}}],["shutting",{"3":{"1270":1}}],["shutdowntimeoutseconds",{"3":{"1435":2,"1487":1}}],["shutdownsavetimeout",{"3":{"1259":1}}],["shutdown",{"3":{"0":5,"21":2,"27":2,"235":1,"237":1,"517":1,"518":2,"536":1,"572":4,"573":1,"574":2,"819":1,"837":1,"897":1,"898":3,"1100":2,"1192":1,"1212":2,"1247":2,"1252":1,"1255":1,"1259":7,"1270":2,"1275":1,"1285":2,"1308":1,"1310":4,"1322":5,"1323":1,"1333":1,"1336":1,"1338":9,"1358":1,"1365":1,"1395":1,"1399":1,"1401":1,"1414":1,"1435":5,"1436":1,"1437":1,"1487":2,"1524":1,"1525":1,"1549":1,"1565":1,"1590":2,"1599":1,"1670":1,"1918":1,"2054":2,"2164":1,"2231":1}}],["shrug",{"3":{"1415":1,"1435":1}}],["shrugs",{"3":{"1308":1}}],["shrunken",{"3":{"2104":1}}],["shrunk",{"3":{"1394":1}}],["shrinks",{"3":{"546":1,"783":1,"1237":1,"1349":1,"1368":1,"1394":1,"1435":1,"1479":1,"1577":1,"1814":1,"2147":1}}],["shrink",{"3":{"491":1,"651":1,"1183":1,"1299":1,"1310":1,"1352":1,"1358":1,"1389":1,"1394":2,"1401":2,"1435":4,"2041":1,"2103":1}}],["shrinking",{"3":{"461":1,"1018":1,"1020":1,"1299":1,"1379":1,"1401":1}}],["shrank",{"3":{"490":1,"1414":1,"1491":1,"1495":1}}],["shredded",{"3":{"2009":1}}],["shred",{"3":{"398":1}}],["shed",{"3":{"1126":1,"1346":1,"1349":1,"1395":2,"1435":1}}],["sheds",{"3":{"819":1,"1124":1,"1323":1,"1338":1,"1435":1,"2031":1}}],["shedding",{"3":{"575":1,"831":1,"1270":1,"1401":1,"1415":2}}],["shelf",{"3":{"627":1,"1041":1,"1101":1,"1300":1,"1914":1}}],["shelling",{"3":{"1429":1}}],["shells",{"3":{"1192":1,"1202":1,"1203":1,"1212":1,"1323":1,"1369":1,"1413":1,"1415":1,"1431":1,"1435":1,"1495":1}}],["shellpagese2etests",{"2":{"616":1},"3":{"616":1,"618":1,"1199":1,"1207":2}}],["shell",{"0":{"1394":1,"2076":1},"1":{"646":1,"647":1,"648":1,"649":1,"650":1,"651":1,"652":1,"653":1,"1415":1},"2":{"1478":1},"3":{"0":6,"415":1,"616":1,"618":3,"646":1,"648":3,"649":7,"650":3,"651":3,"869":2,"914":1,"954":1,"957":1,"974":1,"1059":1,"1192":1,"1202":1,"1203":1,"1212":3,"1323":23,"1358":1,"1375":1,"1394":5,"1395":3,"1413":1,"1414":6,"1415":13,"1416":5,"1435":3,"1437":2,"1439":1,"1451":3,"1453":1,"1460":1,"1474":3,"1478":2,"1480":1,"1489":1,"1512":1,"1525":1,"1561":1,"1590":1,"1596":1,"1652":1,"1659":1,"1668":1,"1681":1,"1697":1,"1760":2,"2076":6,"2079":1,"2231":3}}],["sheets",{"3":{"1323":2}}],["sheet",{"3":{"412":1,"419":1,"743":1,"1323":6,"1401":1,"1416":13,"1525":1,"2115":2}}],["shirt",{"3":{"1349":3}}],["shims",{"3":{"135":1,"151":1,"1281":1,"1285":3,"1435":1,"1495":2,"1855":2,"1861":2}}],["shim",{"3":{"0":1,"166":1,"470":1,"1004":1,"1034":1,"1175":1,"1212":1,"1285":11,"1360":1,"1368":1,"1395":4,"1576":1,"1855":1,"1967":1,"2111":1}}],["shifting",{"3":{"861":1,"1285":1,"1395":1,"1435":1,"1495":1}}],["shifted",{"3":{"251":1,"323":1,"424":1,"425":1,"426":1,"490":1,"491":1,"493":1,"543":1,"589":1,"1358":1,"1495":12}}],["shifts",{"3":{"41":1,"112":1,"693":1,"860":1,"1004":1,"1269":1,"1270":1,"1347":1,"1349":1,"1358":1,"1379":1,"1395":2,"1435":2,"1495":3}}],["shift",{"2":{"1606":1,"1740":1},"3":{"0":1,"279":1,"490":1,"709":1,"1019":1,"1074":1,"1291":1,"1299":1,"1347":1,"1358":3,"1379":1,"1395":1,"1434":1,"1442":1,"1606":1,"1740":1}}],["shipall",{"2":{"1808":1},"3":{"1808":1}}],["shiporderhandler",{"2":{"1874":1},"3":{"1764":1,"1874":1}}],["shiporderdialog",{"2":{"1749":1,"1759":1},"3":{"1749":1,"1759":1}}],["shipmentinvariants",{"3":{"1766":3}}],["shipments",{"3":{"1754":1,"1775":2}}],["shipmenttrackingnumber",{"2":{"1748":1},"3":{"1748":1}}],["shipment",{"2":{"1754":1},"3":{"1745":2,"1748":4,"1754":4,"1759":1,"1762":1,"1763":3,"1764":4,"1765":1,"1766":5,"1767":1,"1768":1,"1769":1,"1770":2,"1775":4,"1776":1,"2109":1}}],["shipmentline",{"3":{"1726":1}}],["shipmentlineidentifiertype",{"2":{"1726":1},"3":{"1726":1}}],["shippable",{"3":{"1270":1,"1765":1}}],["shippingaddresscity",{"3":{"1285":1}}],["shippingaddressline1",{"3":{"1285":1}}],["shippingaddress",{"3":{"1285":1}}],["shipping",{"1":{"2080":1,"2081":1,"2082":1,"2083":1,"2084":1,"2085":1},"3":{"0":2,"67":1,"216":1,"360":1,"449":1,"591":1,"619":1,"627":1,"635":2,"650":1,"657":1,"682":1,"706":1,"715":1,"726":1,"768":1,"832":1,"854":1,"932":1,"955":1,"973":1,"1051":1,"1052":1,"1092":1,"1133":1,"1212":1,"1270":2,"1285":8,"1299":2,"1308":2,"1322":2,"1323":2,"1338":1,"1349":5,"1379":1,"1401":1,"1414":2,"1415":2,"1416":1,"1426":1,"1434":1,"1435":10,"1444":1,"1454":1,"1487":1,"1749":1,"1764":1,"1766":3,"1770":1,"1775":1,"1807":1,"1808":1,"1809":1,"1814":1,"1901":1,"1930":1,"2009":1,"2054":1,"2080":1,"2128":1,"2133":1,"2209":1}}],["shippedstate",{"3":{"1766":3}}],["shippedon",{"3":{"1763":1}}],["shipped",{"0":{"1487":1,"2054":1,"2056":1,"2142":1},"1":{"569":1,"570":1,"571":1,"572":1,"573":1,"574":1,"575":1,"576":1,"577":1,"578":1,"579":1,"615":1,"616":1,"617":1,"618":1,"619":1,"620":1,"621":1,"622":1,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1,"951":1,"952":1,"953":1,"954":1,"955":1,"956":1,"957":1,"958":1,"959":1,"960":1,"1064":1,"1065":1,"1066":1,"1067":1,"1068":1,"1069":1,"1070":1,"1071":1},"2":{"135":1,"136":1,"138":1,"141":1,"142":1,"967":1,"980":1,"981":1,"983":1,"991":1,"1052":1,"1055":1,"1672":1,"1754":1,"1765":1,"1807":1,"1811":1,"2099":1},"3":{"0":28,"19":1,"22":1,"23":1,"80":1,"81":1,"91":1,"107":1,"109":1,"115":2,"121":1,"122":1,"130":1,"135":10,"136":1,"137":1,"138":2,"139":3,"140":1,"141":4,"142":7,"164":1,"166":1,"180":1,"186":1,"233":1,"235":2,"238":1,"241":1,"243":1,"245":1,"255":1,"256":1,"258":1,"265":2,"268":1,"317":1,"350":1,"358":1,"361":1,"362":1,"364":2,"370":1,"403":1,"418":2,"423":2,"443":1,"448":1,"468":1,"469":1,"482":1,"487":1,"497":3,"502":1,"527":1,"538":1,"539":1,"543":2,"545":1,"547":1,"550":1,"563":1,"569":1,"575":1,"576":1,"583":1,"599":2,"607":1,"609":1,"614":1,"615":1,"618":1,"619":1,"622":1,"627":1,"638":1,"640":1,"647":1,"649":2,"655":1,"657":5,"659":1,"660":2,"661":1,"665":1,"668":1,"688":1,"707":1,"714":1,"717":1,"723":4,"725":2,"729":1,"731":1,"732":1,"733":1,"734":1,"743":2,"745":1,"758":1,"797":1,"804":1,"809":1,"825":1,"827":1,"831":2,"845":1,"853":1,"858":1,"861":4,"873":1,"891":1,"905":2,"910":1,"921":1,"922":1,"923":1,"926":1,"951":1,"954":2,"955":1,"960":2,"962":1,"963":1,"964":5,"966":3,"967":1,"968":2,"972":2,"975":2,"980":8,"981":2,"982":3,"983":3,"988":1,"990":1,"991":1,"996":2,"1003":1,"1004":1,"1007":1,"1012":1,"1013":1,"1016":2,"1017":1,"1032":1,"1033":1,"1034":1,"1036":1,"1037":2,"1049":1,"1052":2,"1055":2,"1064":1,"1071":1,"1074":1,"1075":1,"1081":1,"1089":1,"1092":1,"1093":1,"1094":3,"1122":1,"1124":1,"1127":1,"1132":1,"1134":1,"1150":1,"1160":1,"1161":1,"1164":1,"1174":1,"1175":1,"1192":2,"1212":8,"1214":1,"1231":1,"1234":2,"1237":5,"1259":2,"1270":8,"1271":1,"1273":1,"1281":1,"1283":1,"1285":19,"1286":1,"1289":1,"1292":1,"1296":1,"1299":12,"1300":2,"1310":13,"1322":8,"1323":25,"1326":1,"1338":2,"1346":1,"1349":2,"1358":1,"1368":6,"1394":2,"1395":4,"1414":6,"1415":1,"1416":4,"1426":1,"1427":2,"1429":1,"1431":1,"1432":1,"1434":2,"1435":67,"1436":1,"1437":4,"1443":1,"1454":4,"1464":1,"1466":2,"1484":1,"1485":1,"1486":6,"1487":6,"1488":1,"1489":2,"1490":1,"1491":1,"1493":1,"1495":4,"1506":1,"1520":1,"1525":1,"1545":1,"1552":1,"1562":1,"1573":2,"1574":2,"1575":4,"1576":17,"1580":2,"1581":3,"1582":1,"1585":1,"1630":1,"1638":1,"1645":1,"1653":1,"1658":1,"1663":1,"1664":1,"1665":2,"1666":1,"1668":1,"1670":1,"1672":2,"1681":1,"1684":1,"1685":2,"1688":2,"1689":1,"1691":1,"1703":1,"1726":1,"1748":8,"1749":1,"1750":1,"1754":1,"1757":1,"1759":1,"1764":5,"1765":4,"1766":6,"1767":2,"1775":1,"1781":1,"1807":1,"1809":2,"1811":1,"1853":1,"1859":1,"1860":1,"1861":1,"1886":1,"1890":1,"1898":1,"1902":1,"2040":1,"2041":2,"2045":1,"2059":2,"2061":2,"2074":1,"2084":2,"2099":2,"2115":1,"2126":1,"2128":1,"2130":1,"2143":1,"2145":1,"2167":1,"2199":1,"2219":1,"2229":1,"2231":8,"2238":2}}],["ships",{"0":{"2076":1,"2111":1,"2174":1,"2197":1},"3":{"0":34,"72":1,"76":3,"78":1,"80":1,"99":1,"109":1,"134":1,"135":1,"136":3,"140":1,"145":1,"147":1,"150":1,"168":1,"174":1,"180":1,"185":1,"207":1,"212":1,"214":1,"225":1,"243":1,"255":1,"256":1,"273":1,"295":1,"302":1,"317":1,"318":1,"328":1,"358":1,"369":1,"370":1,"371":1,"380":1,"388":1,"397":2,"405":1,"413":2,"432":1,"433":1,"434":2,"436":1,"440":1,"447":1,"448":1,"454":1,"477":1,"481":1,"483":1,"497":1,"528":1,"532":1,"536":1,"543":1,"545":2,"550":1,"551":2,"564":1,"565":1,"571":1,"572":1,"574":1,"579":1,"583":1,"609":1,"610":2,"617":1,"620":1,"631":1,"633":1,"634":1,"635":1,"640":1,"648":1,"649":1,"656":1,"659":1,"667":1,"668":1,"682":2,"683":1,"684":1,"691":1,"707":1,"711":1,"715":1,"725":4,"739":1,"748":1,"774":1,"776":1,"782":2,"786":1,"808":2,"825":1,"827":1,"831":1,"832":1,"840":1,"844":1,"848":1,"855":1,"859":2,"863":1,"880":1,"905":3,"907":1,"914":1,"915":1,"921":1,"922":1,"933":1,"937":1,"939":2,"940":1,"953":1,"954":1,"960":1,"963":1,"974":1,"984":1,"996":1,"1004":2,"1017":1,"1034":1,"1037":1,"1044":2,"1049":1,"1050":1,"1053":1,"1054":1,"1055":1,"1057":3,"1058":2,"1059":7,"1061":1,"1062":2,"1066":1,"1075":1,"1077":1,"1089":2,"1091":5,"1093":2,"1096":1,"1124":2,"1139":2,"1149":2,"1160":1,"1162":1,"1178":1,"1211":1,"1212":4,"1213":1,"1237":4,"1239":1,"1248":1,"1259":1,"1269":1,"1270":4,"1271":2,"1285":12,"1288":1,"1292":2,"1296":2,"1299":9,"1300":1,"1308":3,"1310":11,"1311":3,"1312":1,"1317":1,"1319":1,"1322":8,"1323":10,"1335":1,"1338":8,"1340":1,"1349":6,"1358":1,"1368":1,"1379":3,"1394":1,"1395":3,"1407":1,"1414":9,"1415":2,"1416":20,"1419":1,"1422":1,"1426":5,"1427":1,"1430":4,"1434":1,"1435":44,"1437":2,"1441":2,"1442":4,"1444":1,"1446":2,"1452":1,"1453":1,"1454":4,"1458":2,"1460":1,"1466":5,"1470":1,"1486":2,"1487":2,"1488":6,"1490":1,"1491":1,"1495":1,"1502":2,"1523":1,"1525":5,"1530":2,"1544":1,"1552":1,"1574":2,"1576":16,"1577":2,"1581":3,"1582":1,"1584":1,"1590":3,"1594":1,"1595":1,"1625":1,"1630":1,"1640":1,"1650":1,"1651":1,"1652":2,"1653":1,"1654":1,"1660":1,"1661":1,"1662":1,"1663":1,"1665":1,"1666":1,"1667":1,"1669":1,"1670":1,"1673":1,"1674":1,"1680":1,"1682":1,"1683":1,"1685":1,"1688":1,"1696":1,"1698":1,"1700":1,"1718":1,"1726":2,"1727":1,"1728":2,"1749":1,"1763":1,"1764":1,"1766":1,"1767":1,"1780":2,"1783":2,"1789":1,"1798":1,"1799":1,"1809":1,"1814":1,"1827":1,"1828":1,"1829":1,"1839":1,"1844":2,"1882":2,"1888":1,"1890":3,"1897":1,"1901":1,"1902":1,"1920":1,"1921":2,"1929":2,"1930":1,"1932":1,"1940":1,"1947":1,"1960":1,"1967":1,"1969":1,"1974":1,"1976":1,"1993":1,"1996":1,"1997":1,"1998":1,"2000":1,"2007":1,"2009":2,"2010":2,"2016":1,"2023":1,"2033":1,"2043":1,"2044":1,"2045":1,"2054":2,"2056":1,"2058":1,"2066":1,"2067":1,"2074":1,"2076":1,"2079":1,"2080":1,"2082":2,"2086":1,"2093":1,"2096":1,"2100":1,"2106":1,"2113":2,"2114":1,"2123":1,"2126":1,"2130":2,"2132":2,"2146":1,"2147":1,"2148":2,"2149":1,"2152":1,"2153":2,"2155":2,"2167":1,"2169":1,"2174":1,"2177":2,"2179":1,"2191":2,"2201":2,"2202":1,"2231":11}}],["ship",{"0":{"576":1,"1423":1,"1896":1},"1":{"1056":1,"1057":1,"1058":1,"1059":1,"1060":1,"1061":1,"1062":1,"1063":1},"2":{"1808":1,"1811":1},"3":{"0":7,"62":1,"76":1,"78":1,"85":1,"109":1,"135":2,"147":1,"186":1,"213":1,"214":1,"235":1,"265":1,"269":1,"293":1,"299":1,"300":1,"302":1,"412":1,"415":1,"422":1,"425":1,"426":2,"448":1,"470":1,"528":1,"570":1,"572":2,"574":1,"577":1,"578":1,"579":1,"618":1,"628":2,"633":2,"634":1,"638":1,"640":1,"649":1,"658":1,"660":1,"665":1,"667":1,"672":1,"682":1,"691":1,"698":1,"708":1,"715":1,"717":1,"723":1,"733":1,"749":1,"758":1,"774":1,"798":1,"818":1,"827":1,"832":1,"836":1,"854":1,"861":1,"862":1,"914":1,"922":1,"953":1,"954":1,"955":1,"1016":1,"1049":1,"1056":1,"1059":2,"1060":2,"1067":1,"1091":1,"1096":1,"1212":2,"1237":2,"1259":1,"1270":1,"1271":3,"1281":1,"1285":4,"1299":5,"1300":1,"1307":1,"1308":1,"1310":4,"1314":1,"1322":4,"1323":4,"1333":1,"1338":3,"1358":1,"1394":1,"1395":2,"1401":2,"1414":1,"1415":2,"1416":7,"1417":2,"1418":1,"1426":4,"1427":1,"1430":1,"1431":1,"1432":2,"1435":7,"1437":2,"1453":1,"1458":1,"1464":1,"1466":1,"1485":1,"1488":2,"1491":1,"1525":1,"1530":1,"1533":1,"1574":1,"1576":1,"1582":1,"1584":1,"1590":1,"1595":1,"1626":1,"1647":1,"1653":1,"1660":1,"1661":1,"1665":1,"1666":1,"1668":1,"1672":2,"1674":1,"1698":1,"1705":1,"1726":1,"1732":1,"1734":1,"1745":2,"1748":6,"1749":1,"1754":1,"1759":1,"1760":1,"1763":1,"1764":7,"1766":1,"1767":1,"1768":2,"1770":1,"1787":1,"1808":1,"1809":2,"1811":1,"1814":2,"1842":1,"1852":1,"1879":1,"1881":1,"1888":1,"1904":1,"1926":1,"1940":1,"1958":1,"1964":1,"1969":1,"1977":1,"1998":1,"2023":1,"2027":1,"2028":1,"2037":2,"2043":1,"2048":1,"2052":1,"2059":1,"2072":1,"2084":1,"2088":1,"2100":1,"2106":1,"2115":1,"2119":2,"2121":2,"2130":2,"2132":1,"2133":1,"2137":1,"2159":1,"2172":1,"2179":1,"2194":1,"2195":1,"2199":1,"2201":1,"2202":1,"2211":1,"2212":1,"2217":1,"2219":1,"2225":1,"2231":1,"2238":1}}],["shop",{"3":{"1749":1,"1772":1}}],["shopping",{"3":{"1741":1,"1748":2,"1749":6,"1762":2,"1763":11,"1764":4,"1766":2,"1767":6,"1770":2,"1772":4,"1994":1}}],["shoppingcartinvariants",{"3":{"1766":3}}],["shoppingcartitems",{"3":{"1745":3,"1748":3,"1763":1}}],["shoppingcartitemcreaterequest",{"3":{"1270":1}}],["shoppingcartitemchanged",{"2":{"782":1,"1748":1},"3":{"782":2,"1748":2,"1768":1}}],["shoppingcartitem",{"3":{"1237":1,"1763":1}}],["shoppingcartlisttests",{"3":{"1435":2}}],["shoppingcartlist",{"3":{"1323":1,"1590":3,"1595":3,"1596":1}}],["shoppingcartcreaterequest",{"3":{"1270":1}}],["shoppingcartchanged",{"3":{"782":1,"1748":1,"1768":1}}],["shoppingcartcheckedout",{"2":{"782":1,"1748":1},"3":{"0":1,"782":3,"1259":1,"1748":1,"1768":1}}],["shoppingcart",{"2":{"1748":1,"1768":1,"2109":1},"3":{"782":1,"1323":2,"1435":2,"1590":1,"1748":1,"1763":1,"1764":1,"1766":1,"1768":4,"2109":1}}],["shoppingcartdetail",{"2":{"1590":1},"3":{"556":1,"1323":1,"1590":2}}],["shoppingcartbycustomerspecification",{"2":{"318":1,"1994":1},"3":{"318":4,"1994":2}}],["shoppingcartservice",{"3":{"881":1,"1323":1,"1590":1,"1596":1}}],["shoppingcartsmanage",{"2":{"318":1,"324":1,"1994":1},"3":{"318":1,"324":1,"1994":1}}],["shoppingcartscontroller",{"2":{"318":3,"324":1,"743":1},"3":{"318":9,"324":3,"325":1,"326":1,"743":1,"1299":8,"1310":2,"1590":1,"1598":1,"1994":11}}],["shoppingcarts",{"2":{"554":1},"3":{"317":1,"318":1,"535":1,"539":1,"554":1,"556":1,"674":1,"782":3,"813":1,"814":1,"881":2,"897":1,"900":1,"988":2,"1025":2,"1741":1,"1745":5,"1748":9,"1749":3,"1763":2,"1764":3,"1993":1}}],["shopper",{"3":{"831":1,"1028":1,"1590":1,"1595":2,"1741":2,"1763":1,"1768":1}}],["shouting",{"3":{"1323":1}}],["shouldgotoregisterpage",{"3":{"1435":1}}],["shouldnavigatetohomepage",{"3":{"1435":2}}],["shouldnotexceed",{"3":{"1435":1}}],["shouldnotexpose",{"3":{"81":1,"1435":2}}],["shouldnotinject",{"3":{"1435":2}}],["shouldnotimplement",{"3":{"1310":1,"1435":2,"1874":1}}],["shouldnotresidein",{"3":{"1435":1}}],["shouldnotreach",{"3":{"1435":6}}],["shouldnotreadtheambientclock",{"2":{"1161":1,"1435":1},"3":{"1161":1,"1435":1}}],["shouldnothave",{"3":{"1435":2}}],["shouldnot",{"3":{"1435":3,"1488":1}}],["shouldnotnavigate",{"3":{"1435":2}}],["shouldnotthrow",{"3":{"1435":2}}],["shouldnotbe",{"3":{"1435":10}}],["shouldnotdependon",{"2":{"1435":2,"1488":2},"3":{"54":1,"1435":15,"1488":6}}],["shouldupdateemail",{"3":{"1435":1}}],["shouldupdateprofileaddress",{"3":{"1435":1}}],["shouldupdateprofilename",{"3":{"1435":1}}],["shouldusereadrepositories",{"3":{"1435":4}}],["shouldpreventaccesstoprotectedpages",{"3":{"1435":1}}],["shouldexpose",{"3":{"1435":2,"1488":1}}],["shouldrender",{"2":{"1554":1,"1559":1},"3":{"1554":1,"1559":1}}],["shouldredirecttologinpage",{"3":{"1435":1}}],["shouldredirecttologin",{"3":{"1435":1}}],["shouldresidein",{"3":{"1435":4}}],["shouldreside",{"3":{"1435":1}}],["shouldreturn",{"3":{"1435":2,"1488":1}}],["shouldregister",{"3":{"1435":1}}],["shouldreportpartialresults",{"3":{"1416":1}}],["shouldonlyoccurin",{"3":{"1435":2}}],["shouldinherit",{"3":{"1435":2}}],["shouldincrease",{"3":{"1435":2}}],["shouldimplement",{"3":{"1435":2,"1488":1}}],["shouldcarry",{"3":{"1435":2}}],["shouldcollectrequest",{"2":{"402":1},"3":{"402":1,"1332":1,"1338":5,"1442":2}}],["shouldcollectoutgoing",{"2":{"402":1},"3":{"402":1,"1332":1,"1338":5,"1442":1}}],["shouldhavenoaccessibilityviolations",{"3":{"1435":7}}],["shouldhave",{"2":{"1488":1},"3":{"1435":14,"1488":2}}],["shouldhandle",{"3":{"819":1}}],["shouldmatch",{"3":{"1435":5}}],["shouldlocalizeandpersist",{"3":{"1435":1}}],["shouldlockasync",{"3":{"1323":2}}],["shouldloadwithuserdata",{"3":{"1435":1}}],["shouldload",{"3":{"1309":2}}],["shouldapplyandpersist",{"3":{"1435":1}}],["shouldattempt",{"3":{"1299":2}}],["shouldaudit",{"3":{"1237":1,"1285":3}}],["shouldsucceed",{"3":{"1435":1}}],["shouldshowerror",{"3":{"1435":4}}],["shouldshipfrom",{"2":{"81":1,"1435":1},"3":{"81":1,"1435":2}}],["shouldstayat",{"2":{"2177":1},"3":{"1435":2,"2177":1}}],["shouldstaybehind",{"2":{"1435":1,"2177":1},"3":{"1435":2,"2177":1}}],["shouldseed",{"2":{"1595":1},"3":{"1283":1,"1285":3,"1411":1,"1414":3,"1595":1}}],["shouldbereachable",{"3":{"1435":1}}],["shouldbeforbidden",{"2":{"1626":1},"3":{"1435":1,"1626":1}}],["shouldbeaccessible",{"3":{"1435":2}}],["shouldbesealed",{"3":{"1435":4}}],["shouldbecolocatedwith",{"3":{"1435":6}}],["shouldbe",{"2":{"1168":1},"3":{"1168":1,"1237":1,"1435":20}}],["shouldwritefilesink",{"2":{"401":1},"3":{"401":1,"1332":1,"1338":2}}],["shoulddeclare",{"2":{"160":1},"3":{"160":1,"1435":12}}],["should",{"3":{"0":1,"32":1,"33":1,"59":1,"62":1,"71":1,"109":1,"139":1,"155":1,"159":2,"160":2,"174":1,"175":1,"185":3,"188":1,"224":1,"230":1,"349":1,"358":1,"360":2,"361":1,"517":1,"519":1,"520":1,"537":1,"545":1,"550":1,"573":1,"625":1,"667":1,"674":1,"682":1,"709":1,"725":2,"758":1,"767":2,"801":1,"819":1,"832":2,"837":1,"861":1,"890":1,"899":1,"916":1,"940":2,"941":1,"955":1,"965":1,"980":1,"989":1,"1000":1,"1017":1,"1049":1,"1077":1,"1085":1,"1124":1,"1140":1,"1229":1,"1232":1,"1247":2,"1259":1,"1270":3,"1285":9,"1299":10,"1300":3,"1303":1,"1308":4,"1309":1,"1310":13,"1311":3,"1322":3,"1323":8,"1338":6,"1341":1,"1349":6,"1358":10,"1368":3,"1379":2,"1394":7,"1395":10,"1414":2,"1415":1,"1416":8,"1419":1,"1426":1,"1434":1,"1435":32,"1437":1,"1438":1,"1439":1,"1453":1,"1454":1,"1455":1,"1466":1,"1488":1,"1489":3,"1491":2,"1499":1,"1530":1,"1536":1,"1629":2,"1630":3,"1637":2,"1638":7,"1639":5,"1640":2,"1673":1,"1674":1,"1675":1,"1676":1,"1677":1,"1764":1,"1775":3,"1781":2,"1800":1,"1801":1,"1805":1,"1811":2,"1816":1,"1818":3,"1826":1,"1853":1,"1860":1,"1861":1,"1862":1,"1867":2,"1887":1,"1889":1,"1897":1,"1898":2,"1900":1,"1910":1,"1923":1,"1929":1,"1959":2,"1964":2,"1978":1,"1980":1,"1986":1,"1990":1,"1998":1,"1999":1,"2014":1,"2038":1,"2076":1,"2079":2,"2080":1,"2084":1,"2085":1,"2098":1,"2106":1,"2113":1,"2127":1,"2134":1,"2139":3,"2140":1,"2144":1,"2145":1,"2164":1,"2167":1,"2168":1,"2173":1,"2188":1,"2191":1}}],["showtargetpickers",{"3":{"1394":1}}],["showeventpicker",{"3":{"1394":1}}],["showeventpicker=",{"3":{"1394":2}}],["showempty",{"3":{"1394":1}}],["showed",{"3":{"147":1,"1070":1,"1338":1}}],["showgrid",{"3":{"1394":1}}],["showmyscheduleonlychanged",{"3":{"1394":1}}],["showmyscheduleonly",{"3":{"1394":1}}],["showmap",{"3":{"1394":3}}],["showmessageboxasync",{"3":{"1323":3}}],["showasync",{"3":{"1394":5}}],["showactionerror",{"3":{"1401":5}}],["showaction",{"3":{"1323":6,"1395":1}}],["showcancelsnackbar",{"3":{"1323":3,"1394":1}}],["showcase",{"3":{"618":1,"1433":1,"1435":1,"1437":1,"1485":1,"1486":1,"1642":2}}],["showpersistent",{"3":{"1323":8}}],["show",{"2":{"1483":1,"2033":1},"3":{"450":1,"537":1,"644":1,"681":1,"689":1,"741":1,"766":1,"1020":1,"1026":1,"1027":1,"1028":1,"1239":1,"1259":2,"1299":5,"1308":3,"1323":15,"1349":6,"1352":1,"1358":6,"1381":1,"1394":10,"1395":9,"1398":1,"1401":2,"1414":5,"1415":1,"1416":7,"1430":1,"1435":5,"1437":2,"1441":1,"1473":1,"1474":1,"1475":1,"1477":1,"1483":1,"1504":1,"1516":1,"1626":1,"1652":1,"1680":1,"1719":1,"1727":1,"1748":1,"1757":1,"1814":1,"1835":1,"1872":1,"1957":1,"2009":1,"2026":1,"2033":1,"2042":1,"2069":1,"2086":1,"2118":1,"2131":1,"2164":1,"2201":1}}],["shown",{"3":{"265":1,"1247":1,"1299":3,"1323":5,"1349":4,"1358":1,"1368":2,"1394":6,"1395":3,"1414":2,"1435":5,"1437":1,"1443":1,"1466":1,"1590":1,"1625":1,"1626":1,"1628":1,"1636":1,"1646":1,"1653":1,"1681":1,"1726":1,"1760":1,"2029":1,"2135":1}}],["showingcacheddata",{"3":{"1394":1}}],["showing",{"3":{"109":1,"265":1,"635":1,"881":1,"1299":1,"1310":2,"1323":3,"1349":1,"1358":4,"1387":1,"1390":1,"1394":8,"1395":5,"1414":2,"1415":1,"1416":2,"1435":5,"1478":1,"1749":1,"1940":1,"2002":1}}],["showstheapismessage",{"3":{"1435":1}}],["showsthesameconfirmation",{"3":{"1435":1}}],["showsclientvalidationerrors",{"3":{"1435":1}}],["showsearch",{"3":{"1323":1}}],["showsessionslink",{"3":{"1323":1}}],["shows",{"0":{"1909":1},"3":{"82":1,"109":1,"214":1,"227":1,"279":1,"536":1,"557":1,"585":1,"674":1,"684":1,"776":1,"782":1,"875":1,"890":1,"897":1,"905":2,"963":1,"981":1,"1004":1,"1018":1,"1020":1,"1029":1,"1102":2,"1229":1,"1237":2,"1259":1,"1270":1,"1285":7,"1288":1,"1299":4,"1300":1,"1306":1,"1308":7,"1310":3,"1312":1,"1322":6,"1323":39,"1338":12,"1339":1,"1343":1,"1349":7,"1350":1,"1358":11,"1361":1,"1368":1,"1374":1,"1379":3,"1383":1,"1384":1,"1386":1,"1387":1,"1390":3,"1394":37,"1395":26,"1396":1,"1401":13,"1402":1,"1413":1,"1414":2,"1415":2,"1416":7,"1426":2,"1430":1,"1432":1,"1435":7,"1437":1,"1439":1,"1455":1,"1466":1,"1469":1,"1473":1,"1483":2,"1491":1,"1576":1,"1619":1,"1626":1,"1630":2,"1645":1,"1656":2,"1660":2,"1669":1,"1672":1,"1683":1,"1684":1,"1688":1,"1701":1,"1703":1,"1728":1,"1729":1,"1747":1,"1749":2,"1755":1,"1767":2,"1772":2,"1835":1,"1912":1,"1918":1,"1922":1,"1931":1,"1942":1,"1948":1,"1949":1,"1970":1,"2004":1,"2009":1,"2021":1,"2080":1,"2085":1,"2098":1,"2115":1,"2116":1,"2134":1}}],["shotgun",{"3":{"1011":1,"1012":1,"1495":1,"1570":1}}],["shot",{"3":{"0":1,"422":1,"1161":1,"1259":1,"1285":3,"1296":1,"1308":1,"1323":4,"1333":1,"1338":1,"1358":1,"1388":1,"1392":1,"1394":6,"1413":1,"1414":1,"1416":2,"1437":1,"1764":1}}],["shortfall",{"3":{"1349":1,"1416":1}}],["shortcuts",{"3":{"1415":7}}],["shortcut",{"3":{"517":1,"923":1,"940":1,"1247":1,"1285":1,"1310":1,"1395":2,"1415":7,"1435":2,"1440":2,"1487":2,"1490":1,"1758":1,"2039":1}}],["shorthand",{"3":{"255":1,"674":1,"952":1,"954":1,"959":1,"1258":1,"1259":2,"1285":1,"1435":1,"1495":1}}],["shorter",{"3":{"139":1,"142":1,"359":1,"386":1,"387":1,"500":1,"733":1,"741":1,"827":2,"907":1,"975":1,"1244":1,"1247":1,"1270":2,"1285":2,"1287":1,"1299":1,"1300":2,"1308":1,"1322":2,"1323":1,"1349":1,"1358":1,"1414":1,"1435":3,"1464":1,"1820":1,"1823":1,"1899":1}}],["shortest",{"3":{"59":1,"135":1,"1265":1,"1358":2,"1435":6,"1758":1}}],["shorten",{"3":{"938":1,"1259":1,"1322":1,"1395":1}}],["shortened",{"3":{"461":1,"1187":1,"1395":1}}],["shortening",{"3":{"22":1,"459":1}}],["shortens",{"3":{"19":1,"526":1,"675":1,"1338":1,"1414":1}}],["short",{"3":{"0":3,"1":1,"39":1,"70":1,"71":1,"109":2,"110":1,"113":1,"117":1,"121":1,"122":1,"124":1,"125":1,"126":1,"223":1,"233":1,"235":1,"243":2,"244":1,"283":1,"284":1,"300":1,"318":1,"319":1,"324":1,"350":3,"351":1,"352":1,"359":2,"360":1,"361":2,"386":1,"390":2,"392":1,"459":2,"498":2,"500":2,"501":1,"508":1,"528":1,"549":1,"591":3,"593":1,"633":1,"640":1,"684":1,"690":1,"691":1,"734":1,"741":1,"743":1,"744":1,"790":2,"819":1,"820":2,"853":1,"855":1,"920":1,"921":1,"946":1,"948":1,"964":1,"973":1,"979":1,"1006":1,"1042":1,"1061":1,"1168":1,"1183":1,"1212":1,"1220":2,"1224":1,"1229":5,"1231":1,"1237":6,"1239":1,"1245":1,"1247":3,"1250":1,"1255":1,"1256":1,"1259":10,"1263":2,"1265":1,"1268":5,"1270":32,"1271":3,"1285":20,"1287":1,"1299":25,"1300":5,"1303":1,"1308":9,"1309":1,"1310":18,"1319":1,"1322":8,"1323":12,"1327":1,"1336":1,"1338":19,"1345":1,"1346":1,"1349":9,"1358":51,"1370":1,"1374":2,"1379":6,"1386":1,"1388":1,"1390":1,"1394":22,"1395":20,"1401":14,"1405":1,"1409":1,"1414":13,"1415":5,"1416":5,"1426":2,"1429":1,"1435":11,"1437":2,"1440":1,"1442":1,"1446":1,"1452":1,"1453":3,"1454":2,"1459":1,"1460":1,"1461":1,"1466":1,"1488":2,"1491":3,"1522":1,"1525":1,"1553":1,"1576":9,"1584":2,"1591":2,"1611":1,"1629":1,"1639":2,"1640":1,"1665":1,"1700":1,"1706":1,"1725":1,"1731":1,"1799":1,"1804":2,"1820":1,"1827":1,"1830":1,"1833":1,"1855":1,"1873":1,"1904":1,"1915":1,"1918":1,"1920":1,"1921":1,"1922":1,"1932":2,"1961":1,"1971":1,"1974":1,"1978":1,"1979":3,"1980":1,"1983":1,"1984":1,"1993":1,"1999":1,"2000":1,"2006":1,"2007":1,"2029":1,"2046":2,"2047":1,"2095":2,"2127":1,"2135":1,"2136":1,"2141":2,"2142":1,"2155":1,"2159":1,"2187":1,"2202":1,"2231":2}}],["shas",{"3":{"1482":1}}],["shadowed",{"3":{"1323":1}}],["shadow",{"3":{"1035":1,"1059":1,"1237":1,"1276":1,"1285":5,"1300":3,"1349":2,"1368":1,"1394":6,"1435":1,"1466":1,"1479":2,"2200":1}}],["shadowing",{"3":{"964":1,"1237":1}}],["shadows",{"3":{"840":1,"1310":1,"1442":1}}],["sha2",{"3":{"905":1,"1299":1}}],["sha256",{"2":{"418":2,"423":1,"424":1,"868":1,"870":1,"873":1,"1480":1},"3":{"30":1,"418":2,"423":2,"424":1,"868":1,"870":1,"873":1,"1299":8,"1310":2,"1426":1,"1444":1,"1480":1,"1894":2}}],["shaky",{"3":{"682":1,"685":1,"1416":2}}],["shaping",{"3":{"346":1,"572":1,"591":1,"595":1,"744":1,"827":2,"1238":1,"1242":3,"1244":2,"1247":9,"1270":3,"1272":1,"1308":4,"1310":2,"1338":2,"1349":4,"1355":1,"1358":7,"1379":1,"1394":1,"1435":3,"1437":1,"1487":1,"1726":2,"1814":1,"2054":1}}],["shapecollectiondata",{"2":{"1242":1},"3":{"741":1,"1242":1,"1247":4}}],["shaperow",{"2":{"744":1},"3":{"741":1,"744":1,"1310":1}}],["shaper",{"3":{"741":2,"742":1,"1435":1,"1437":1}}],["shapedaccessorcache",{"3":{"1247":3}}],["shapedata",{"2":{"741":1},"3":{"741":1,"1242":1,"1247":3,"1310":1}}],["shaped",{"0":{"1388":1},"3":{"175":1,"249":1,"253":1,"337":1,"341":1,"388":3,"439":1,"443":2,"573":1,"585":1,"608":1,"626":1,"633":1,"719":1,"741":3,"743":1,"785":1,"789":1,"825":1,"830":1,"880":1,"890":1,"921":1,"946":1,"948":1,"964":1,"1004":1,"1018":1,"1117":1,"1124":1,"1150":1,"1152":1,"1153":1,"1177":1,"1217":1,"1229":4,"1247":5,"1259":1,"1270":1,"1271":2,"1284":1,"1285":7,"1299":8,"1310":10,"1319":1,"1322":2,"1323":9,"1325":1,"1338":1,"1346":1,"1349":8,"1351":1,"1352":1,"1358":12,"1368":4,"1379":1,"1381":1,"1394":7,"1395":8,"1400":1,"1401":1,"1409":1,"1414":5,"1415":1,"1416":7,"1426":1,"1435":7,"1437":1,"1461":1,"1475":1,"1495":1,"1525":1,"1542":1,"1575":1,"1576":3,"1584":1,"1668":1,"1684":1,"1701":2,"1721":1,"1729":1,"1730":1,"1873":1,"1882":1,"1910":1,"1915":1,"1922":1,"1939":1,"1957":1,"2000":1,"2001":1,"2073":1,"2116":1,"2125":1,"2149":1}}],["shapes",{"0":{"1372":1,"1373":1,"1942":1},"3":{"0":2,"109":2,"157":1,"166":1,"170":1,"301":1,"317":1,"318":1,"395":1,"518":1,"522":1,"535":1,"549":1,"557":1,"566":1,"583":1,"591":1,"633":3,"640":1,"657":1,"733":1,"734":1,"743":1,"781":1,"782":1,"789":1,"810":1,"812":1,"826":1,"834":1,"845":1,"853":1,"920":1,"926":2,"980":2,"983":1,"984":1,"998":1,"1050":2,"1060":1,"1068":1,"1100":1,"1110":1,"1118":1,"1171":1,"1202":1,"1203":1,"1212":1,"1227":2,"1228":1,"1229":1,"1237":2,"1242":1,"1245":1,"1247":1,"1249":1,"1260":1,"1263":1,"1265":1,"1270":6,"1271":2,"1273":1,"1279":1,"1285":11,"1292":1,"1296":1,"1299":13,"1300":1,"1308":4,"1310":14,"1311":8,"1316":1,"1319":1,"1322":7,"1323":7,"1327":1,"1333":1,"1338":5,"1339":1,"1341":1,"1344":1,"1346":1,"1349":1,"1358":20,"1365":1,"1368":1,"1372":2,"1373":1,"1379":1,"1386":1,"1392":1,"1394":7,"1395":19,"1398":1,"1401":4,"1409":1,"1414":6,"1415":1,"1430":1,"1431":1,"1435":42,"1437":2,"1443":1,"1495":1,"1525":1,"1534":1,"1545":2,"1599":1,"1651":1,"1664":1,"1665":1,"1719":1,"1730":1,"1926":1,"1948":1,"1951":1,"1954":2,"1957":1,"1990":1,"1993":1,"1994":1,"2017":1,"2099":1,"2130":1}}],["shape",{"0":{"1261":1,"1294":1,"1330":1,"1342":1,"1362":1,"1374":1,"1612":1,"1717":1,"1819":1,"2050":1},"2":{"1703":1},"3":{"0":21,"24":1,"31":1,"42":1,"53":1,"59":1,"62":2,"68":1,"71":1,"72":1,"76":1,"77":2,"78":1,"80":1,"109":4,"110":1,"111":1,"122":1,"125":1,"126":1,"127":1,"130":1,"134":1,"136":2,"138":1,"139":2,"159":1,"164":1,"166":3,"180":1,"184":1,"186":1,"195":2,"201":1,"218":1,"226":1,"227":1,"230":1,"234":1,"243":1,"245":1,"247":1,"248":2,"255":2,"256":1,"258":1,"274":1,"280":1,"282":1,"300":1,"306":1,"309":1,"322":1,"325":2,"329":1,"331":1,"332":1,"333":1,"335":1,"341":1,"350":2,"352":1,"353":1,"372":1,"379":1,"380":1,"384":1,"400":1,"401":1,"404":1,"407":1,"412":1,"418":2,"423":1,"427":2,"428":1,"429":1,"430":1,"447":2,"448":5,"449":1,"451":1,"455":1,"459":1,"497":1,"498":1,"502":1,"509":1,"517":2,"519":1,"522":1,"523":1,"536":1,"539":1,"541":1,"544":1,"545":2,"549":6,"556":1,"563":2,"564":4,"572":2,"574":1,"582":2,"609":1,"625":1,"626":1,"630":1,"633":5,"635":2,"638":1,"655":1,"657":1,"661":1,"668":1,"674":2,"675":1,"676":1,"681":1,"689":1,"691":1,"698":2,"699":1,"700":1,"707":2,"714":1,"725":2,"726":1,"727":1,"731":1,"732":2,"733":2,"734":1,"740":1,"743":1,"749":3,"750":1,"760":2,"774":1,"776":1,"780":1,"782":5,"783":2,"784":3,"786":1,"790":3,"797":1,"798":1,"799":1,"801":2,"804":1,"808":1,"810":2,"812":1,"818":1,"821":1,"827":2,"829":1,"831":1,"832":1,"837":2,"838":1,"840":3,"845":3,"846":2,"847":1,"848":2,"854":1,"862":1,"868":1,"881":1,"882":2,"889":1,"891":1,"897":1,"898":1,"899":1,"901":1,"905":2,"907":1,"909":1,"913":1,"914":1,"921":1,"924":1,"926":5,"930":1,"935":1,"939":1,"941":1,"945":1,"946":4,"947":1,"948":2,"950":1,"957":1,"963":1,"964":1,"965":1,"966":1,"968":1,"971":2,"976":1,"979":2,"980":5,"981":3,"982":1,"1003":1,"1004":3,"1005":1,"1006":1,"1008":1,"1018":4,"1020":1,"1025":1,"1033":1,"1034":1,"1035":2,"1037":1,"1042":1,"1043":1,"1048":1,"1049":1,"1050":2,"1058":1,"1059":3,"1066":1,"1069":1,"1082":1,"1090":1,"1091":1,"1092":1,"1095":1,"1101":1,"1102":1,"1104":1,"1108":1,"1115":1,"1124":1,"1126":2,"1132":2,"1143":1,"1152":1,"1162":1,"1167":1,"1168":1,"1169":1,"1174":1,"1184":1,"1193":1,"1212":2,"1218":1,"1223":1,"1226":1,"1229":22,"1233":1,"1234":3,"1235":2,"1237":16,"1238":2,"1239":2,"1240":1,"1242":1,"1244":3,"1247":30,"1258":1,"1259":9,"1260":1,"1262":1,"1263":3,"1269":2,"1270":38,"1271":10,"1273":1,"1276":1,"1278":1,"1285":73,"1287":1,"1288":2,"1291":2,"1293":1,"1295":1,"1298":1,"1299":106,"1300":11,"1306":1,"1308":31,"1309":5,"1310":43,"1311":8,"1312":1,"1316":1,"1319":3,"1322":58,"1323":108,"1329":1,"1332":1,"1333":1,"1336":1,"1338":40,"1342":2,"1347":1,"1349":78,"1351":3,"1352":4,"1357":1,"1358":223,"1361":1,"1365":1,"1367":1,"1368":30,"1369":1,"1371":1,"1372":3,"1375":3,"1377":1,"1379":40,"1381":1,"1382":1,"1388":1,"1391":2,"1394":84,"1395":133,"1396":1,"1398":1,"1399":1,"1400":1,"1401":44,"1403":1,"1404":2,"1408":1,"1414":54,"1415":12,"1416":48,"1417":2,"1420":1,"1426":5,"1428":1,"1430":5,"1432":1,"1434":1,"1435":240,"1437":9,"1439":1,"1440":3,"1442":2,"1443":1,"1446":1,"1447":1,"1454":4,"1455":1,"1458":3,"1464":1,"1465":1,"1466":5,"1469":1,"1473":1,"1475":1,"1477":1,"1485":1,"1487":1,"1488":3,"1490":1,"1491":4,"1495":2,"1496":1,"1514":1,"1525":1,"1533":1,"1544":1,"1546":1,"1576":2,"1584":1,"1590":3,"1595":2,"1610":1,"1626":1,"1630":3,"1640":7,"1645":1,"1650":1,"1653":2,"1660":2,"1661":1,"1664":1,"1667":1,"1672":1,"1678":1,"1683":2,"1684":2,"1685":3,"1687":2,"1691":2,"1696":3,"1698":1,"1700":3,"1703":2,"1704":1,"1716":5,"1717":3,"1718":3,"1719":2,"1722":2,"1723":1,"1724":2,"1726":14,"1727":2,"1728":2,"1729":1,"1730":1,"1764":1,"1804":6,"1805":1,"1809":2,"1814":4,"1815":1,"1816":1,"1824":1,"1831":1,"1832":1,"1842":1,"1860":2,"1862":1,"1866":1,"1868":1,"1874":1,"1886":3,"1887":2,"1888":3,"1889":1,"1891":1,"1900":1,"1901":2,"1904":1,"1908":2,"1914":2,"1918":2,"1919":2,"1921":1,"1922":1,"1923":1,"1924":1,"1925":1,"1926":2,"1927":1,"1928":1,"1929":1,"1932":1,"1933":1,"1941":1,"1944":1,"1946":1,"1949":1,"1950":1,"1952":1,"1954":1,"1956":1,"1969":1,"1971":1,"1976":1,"1977":2,"1978":2,"1985":1,"1986":2,"1987":1,"1988":1,"1989":1,"1990":2,"1992":1,"2000":3,"2001":1,"2007":2,"2009":1,"2012":1,"2023":1,"2042":1,"2043":1,"2045":1,"2046":1,"2049":1,"2050":1,"2054":1,"2056":1,"2058":2,"2059":2,"2066":1,"2073":1,"2081":1,"2083":1,"2087":1,"2088":4,"2092":1,"2095":1,"2096":1,"2097":2,"2100":1,"2105":1,"2106":3,"2109":1,"2112":1,"2118":1,"2122":1,"2124":1,"2128":1,"2129":2,"2130":4,"2131":3,"2132":2,"2133":1,"2135":1,"2141":1,"2149":1,"2152":1,"2155":2,"2162":1,"2165":1,"2166":2,"2179":1,"2185":1,"2197":1,"2202":1,"2229":1,"2231":1}}],["shallowly",{"3":{"1458":1}}],["shallow",{"3":{"237":1,"566":1,"1237":1,"1358":1,"1435":1,"1437":1,"1452":1,"1576":1,"1956":1}}],["shards",{"3":{"2219":1}}],["sharply",{"3":{"1310":1,"1416":1}}],["sharpened",{"3":{"804":1}}],["sharpens",{"3":{"803":1,"1995":1}}],["sharper",{"3":{"168":1,"549":1,"1267":1,"1280":1,"1285":1,"1323":3,"1435":7,"2024":1}}],["sharpest",{"3":{"48":1,"160":1,"585":1,"1300":1,"1322":1,"1323":1,"1327":1,"1358":1,"1394":1,"1395":1,"1414":2,"1435":6,"1440":1,"1454":1,"2155":1}}],["sharp",{"3":{"102":1,"549":1,"735":1,"1311":1,"1395":1,"1435":2,"1786":1,"1791":1,"1792":1,"1800":1,"1806":1,"1811":1,"1816":1,"1826":1,"1833":1,"1845":1,"1852":1,"1861":1,"1867":1,"1876":1,"1885":1,"1891":1,"1898":1,"1904":1,"1911":1,"1920":1,"1921":3,"1923":1,"1929":1,"1940":1,"1948":1,"1949":1,"1958":1,"1964":1,"1971":1,"1978":1,"1984":1,"1990":1,"1996":1,"2001":1,"2013":1,"2021":1,"2027":1,"2029":1,"2047":1,"2048":1,"2057":1,"2059":1,"2068":1,"2079":1,"2085":1,"2097":1,"2107":1,"2122":1,"2127":1,"2132":1,"2138":1,"2145":1,"2152":1,"2159":1,"2168":1,"2179":1,"2192":1,"2202":1,"2210":1}}],["sharing",{"3":{"24":1,"25":1,"30":1,"58":1,"175":1,"228":1,"246":1,"248":1,"503":1,"597":1,"629":1,"665":1,"682":2,"715":1,"827":1,"846":1,"897":1,"964":1,"996":1,"1052":1,"1055":1,"1203":1,"1207":8,"1229":2,"1237":3,"1242":1,"1247":1,"1259":2,"1267":1,"1270":5,"1279":1,"1285":6,"1297":1,"1299":7,"1300":6,"1308":1,"1309":1,"1310":2,"1311":1,"1322":5,"1323":9,"1338":2,"1341":1,"1349":1,"1355":1,"1358":8,"1368":2,"1379":1,"1394":3,"1395":6,"1401":3,"1405":2,"1414":4,"1416":7,"1435":4,"1436":1,"1437":3,"1542":1,"1721":1,"1764":1,"1848":1,"1919":2,"1993":1,"1996":1,"2052":1,"2123":1}}],["sharefile",{"3":{"1416":2}}],["sharefilerequest",{"3":{"1416":2}}],["sharefileasync",{"3":{"1323":1,"1416":6,"2116":1}}],["sharetextrequest",{"3":{"1416":2}}],["sharetitle",{"3":{"1323":1}}],["sharelinkasync",{"3":{"1416":5,"2116":2}}],["sharelink",{"3":{"1416":4}}],["sharepagebutton",{"3":{"1323":7}}],["sharepagebuttontests",{"3":{"1199":2,"1207":4}}],["shareable",{"3":{"691":1,"1323":4,"1394":1,"1395":3,"1714":1}}],["sharescheduleasync",{"3":{"1394":1}}],["sharesthegatewayliftstrigger",{"3":{"1338":1}}],["shares",{"3":{"0":1,"74":1,"174":1,"181":2,"243":1,"265":1,"269":1,"277":1,"309":1,"665":1,"881":1,"998":1,"1000":1,"1133":1,"1229":2,"1239":1,"1255":1,"1259":2,"1267":1,"1270":1,"1271":2,"1273":1,"1277":1,"1278":1,"1285":10,"1289":1,"1299":2,"1308":1,"1309":2,"1310":10,"1322":3,"1323":9,"1338":5,"1347":1,"1349":3,"1358":8,"1379":4,"1390":1,"1394":1,"1395":3,"1401":1,"1414":1,"1415":1,"1416":3,"1426":1,"1435":9,"1437":1,"1441":1,"1487":1,"1538":1,"1590":1,"1697":1,"1848":1,"1850":1,"1893":1,"1997":1,"1999":1,"2070":1,"2176":1,"2231":1}}],["share",{"0":{"2110":1},"2":{"412":1,"2115":1,"2118":1},"3":{"0":8,"10":1,"15":1,"25":1,"27":1,"32":1,"46":1,"111":1,"116":1,"147":1,"157":1,"166":2,"169":1,"235":1,"248":1,"253":1,"335":1,"395":2,"412":2,"419":1,"420":1,"507":1,"633":2,"665":2,"667":1,"714":1,"733":1,"778":1,"799":1,"803":1,"827":2,"854":1,"881":1,"883":1,"891":1,"905":2,"909":1,"922":1,"926":1,"964":1,"998":1,"1018":1,"1036":1,"1040":1,"1043":1,"1044":1,"1045":1,"1066":1,"1096":1,"1116":1,"1185":1,"1192":1,"1193":1,"1196":1,"1202":1,"1203":1,"1239":1,"1241":1,"1242":1,"1243":3,"1247":13,"1270":7,"1271":3,"1283":1,"1285":27,"1296":1,"1299":9,"1300":9,"1308":6,"1309":3,"1310":6,"1316":1,"1319":2,"1321":1,"1322":9,"1323":28,"1324":1,"1331":1,"1336":1,"1338":11,"1345":1,"1349":5,"1350":1,"1352":1,"1357":1,"1358":38,"1365":1,"1368":3,"1379":6,"1384":2,"1394":27,"1395":17,"1396":1,"1401":3,"1414":2,"1415":2,"1416":51,"1424":1,"1426":2,"1434":1,"1435":17,"1437":10,"1444":1,"1452":1,"1453":1,"1466":6,"1485":1,"1488":1,"1495":4,"1590":2,"1596":1,"1629":2,"1630":2,"1638":5,"1639":1,"1640":3,"1663":1,"1668":2,"1730":1,"1731":1,"1788":1,"1804":1,"1846":1,"1858":1,"1861":1,"1864":1,"1867":1,"1893":1,"1897":1,"1899":1,"1907":2,"1908":1,"1915":1,"1931":1,"1971":1,"2020":1,"2023":1,"2055":1,"2080":1,"2085":1,"2111":1,"2114":1,"2115":4,"2116":1,"2117":2,"2118":2,"2120":1,"2121":2,"2191":1,"2231":1}}],["shared→masstransit",{"3":{"1575":1}}],["shared→domain→application→infra→api",{"3":{"1575":1}}],["shareddtos",{"3":{"1435":1}}],["sharedintegrationeventsnamespace",{"3":{"1435":1}}],["sharedadchome",{"3":{"1415":1}}],["sharedaccesskey=sas",{"3":{"1338":1}}],["sharedaccesskeyname=rootmanagesharedaccesskey",{"3":{"1338":1}}],["sharedassemblies",{"2":{"81":1,"1435":1},"3":{"80":1,"81":1,"1435":2}}],["sharedownloadedfileasync",{"3":{"1323":1}}],["sharedkeywords",{"3":{"1349":2}}],["sharedkey",{"3":{"1299":1}}],["sharedlayertests",{"3":{"1199":1,"1207":2,"1435":4,"1488":1}}],["sharedlayertestsbase",{"3":{"1199":2,"1207":2,"1435":5,"1653":1}}],["sharedhttptestdoublestests",{"3":{"1199":1,"1207":2,"1435":3}}],["sharedcode",{"3":{"1435":2}}],["sharedcodes",{"3":{"1435":2}}],["sharedcodeerrors",{"3":{"1199":2,"1207":1,"1435":5}}],["sharedcategoryitems",{"3":{"1349":2}}],["sharedcachestripewebhooksecretstore",{"2":{"793":1},"3":{"792":1,"793":2,"794":1,"1588":1,"1590":1,"1595":1,"1596":1}}],["sharedstorereadoptions",{"2":{"733":1,"736":1},"3":{"733":1,"736":1}}],["sharedresource",{"3":{"265":1,"268":1,"905":1,"1199":14,"1203":1,"1207":2,"1323":27,"1435":4,"1495":1,"1576":2}}],["shared",{"0":{"1283":1,"1288":1,"1299":1,"1321":1,"1335":1,"1405":1,"1464":1,"1969":1},"1":{"646":1,"647":1,"648":1,"649":1,"650":1,"651":1,"652":1,"653":1,"662":1,"663":1,"664":1,"665":1,"666":1,"667":1,"668":1,"669":1,"670":1,"695":1,"696":1,"697":1,"698":1,"699":1,"700":1,"701":1,"702":1,"703":1,"746":1,"747":1,"748":1,"749":1,"750":1,"751":1,"752":1,"753":1,"1892":1,"1893":1,"1894":1,"1895":1,"1896":1,"1897":1,"1898":1},"2":{"107":1,"109":1,"186":1,"190":1,"471":1,"472":1,"473":1,"576":1,"578":1,"938":1,"963":1,"966":1,"1051":1,"1217":1,"1222":1,"1229":3,"1230":1,"1237":2,"1271":1,"1286":2,"1308":2,"1324":1,"1339":1,"1340":1,"1343":1,"1358":1,"1395":3,"1401":1,"1403":1,"1414":1,"1503":1,"1649":2,"1652":1,"1659":1,"1686":1,"1698":2,"1701":2,"1726":2,"1780":1,"1804":1,"1828":1,"1961":1,"2070":1,"2077":1,"2156":1},"3":{"0":62,"15":1,"23":1,"26":2,"27":5,"31":2,"32":1,"33":1,"39":1,"42":1,"43":1,"45":1,"46":1,"47":1,"48":2,"53":1,"54":2,"58":1,"59":2,"61":1,"62":1,"68":1,"71":1,"74":1,"76":1,"78":2,"80":10,"81":4,"93":1,"95":2,"96":1,"98":2,"100":1,"107":1,"109":14,"110":2,"111":1,"113":1,"115":1,"121":1,"126":2,"130":2,"131":1,"132":1,"134":1,"135":1,"136":1,"139":1,"140":1,"150":1,"152":1,"155":1,"157":2,"160":2,"161":3,"166":1,"171":1,"173":2,"175":4,"176":1,"179":2,"182":1,"184":1,"186":13,"190":2,"196":1,"200":1,"202":2,"208":1,"214":2,"215":1,"216":2,"217":1,"220":1,"235":1,"237":1,"238":1,"241":1,"242":1,"243":3,"245":1,"246":2,"255":1,"256":1,"265":7,"266":2,"273":4,"283":2,"303":2,"305":5,"310":2,"311":1,"314":1,"318":2,"320":1,"325":1,"330":1,"340":1,"341":4,"346":1,"348":1,"350":1,"353":1,"360":1,"361":1,"368":1,"370":2,"382":2,"386":2,"387":1,"388":3,"391":1,"392":1,"397":2,"411":1,"412":2,"413":5,"418":3,"419":1,"420":5,"426":2,"427":1,"428":2,"430":1,"447":1,"448":2,"450":2,"457":1,"459":5,"461":2,"462":1,"463":1,"464":1,"466":1,"469":2,"470":9,"471":1,"472":2,"473":1,"475":10,"476":11,"477":13,"478":1,"482":1,"484":1,"490":1,"491":1,"492":1,"493":1,"499":2,"500":1,"505":1,"507":5,"508":1,"509":1,"510":1,"511":2,"516":1,"518":1,"523":2,"528":1,"534":1,"536":3,"538":1,"540":1,"549":2,"556":8,"558":2,"564":1,"570":1,"571":1,"572":3,"574":1,"576":6,"577":1,"578":1,"579":2,"583":6,"585":5,"587":1,"590":1,"591":2,"593":1,"594":1,"599":4,"601":4,"602":3,"603":1,"604":1,"605":1,"609":1,"610":1,"614":1,"617":2,"618":2,"624":1,"626":1,"633":1,"638":2,"640":2,"642":1,"643":4,"644":1,"646":1,"647":1,"649":2,"656":1,"657":5,"662":1,"665":1,"666":3,"667":3,"672":1,"674":4,"682":1,"690":10,"693":1,"695":1,"696":3,"698":4,"700":1,"702":2,"703":1,"707":1,"709":1,"718":1,"719":1,"725":4,"728":1,"729":1,"733":1,"741":1,"743":1,"746":1,"750":1,"753":1,"756":1,"757":1,"759":1,"760":2,"774":1,"776":1,"778":1,"782":4,"788":1,"790":5,"792":3,"793":1,"794":4,"798":3,"799":1,"803":1,"804":2,"805":1,"806":1,"810":1,"819":3,"821":1,"826":1,"827":5,"829":1,"831":2,"832":1,"833":1,"834":1,"836":1,"837":1,"838":4,"840":1,"841":1,"848":1,"860":1,"861":2,"868":1,"869":1,"872":1,"877":1,"881":6,"883":2,"884":1,"890":1,"891":1,"893":1,"903":1,"905":6,"906":1,"909":1,"910":1,"912":1,"914":1,"918":2,"920":1,"921":1,"922":1,"926":1,"938":1,"939":1,"946":1,"954":1,"956":1,"960":1,"962":1,"963":5,"964":3,"966":2,"967":1,"972":2,"973":2,"980":1,"982":1,"995":1,"996":1,"999":2,"1000":1,"1004":8,"1006":1,"1025":1,"1026":4,"1035":2,"1040":1,"1042":2,"1049":2,"1050":10,"1051":1,"1052":4,"1054":2,"1055":5,"1057":1,"1058":2,"1059":14,"1066":1,"1067":2,"1074":2,"1078":1,"1087":1,"1094":1,"1096":1,"1100":1,"1116":6,"1119":1,"1124":3,"1126":1,"1137":1,"1147":1,"1150":1,"1164":1,"1168":3,"1171":1,"1183":1,"1191":1,"1192":9,"1193":1,"1198":12,"1199":325,"1201":1,"1202":2,"1203":256,"1206":9,"1207":981,"1208":2,"1211":4,"1212":19,"1214":1,"1217":1,"1219":3,"1220":1,"1222":2,"1223":2,"1224":1,"1225":1,"1226":2,"1227":2,"1228":1,"1229":68,"1230":2,"1234":12,"1235":2,"1236":2,"1237":74,"1239":1,"1240":1,"1241":4,"1242":1,"1243":1,"1246":2,"1247":25,"1251":1,"1253":2,"1254":3,"1257":1,"1259":17,"1260":1,"1263":1,"1264":1,"1265":2,"1267":1,"1270":36,"1271":27,"1275":1,"1276":4,"1281":1,"1283":1,"1285":103,"1286":4,"1288":4,"1289":1,"1290":6,"1292":2,"1294":1,"1295":3,"1296":9,"1297":1,"1298":2,"1299":386,"1300":22,"1302":4,"1303":3,"1304":2,"1307":2,"1308":57,"1309":1,"1310":103,"1311":36,"1312":2,"1317":1,"1319":2,"1321":2,"1322":59,"1323":159,"1324":2,"1325":1,"1327":3,"1329":6,"1332":1,"1333":1,"1335":5,"1336":3,"1337":6,"1338":73,"1339":1,"1340":10,"1341":2,"1343":4,"1346":13,"1347":8,"1348":9,"1349":272,"1350":1,"1351":1,"1352":4,"1354":1,"1357":2,"1358":368,"1363":1,"1367":1,"1368":18,"1370":1,"1371":1,"1372":3,"1374":1,"1375":1,"1376":2,"1377":1,"1378":5,"1379":32,"1382":1,"1383":3,"1384":1,"1385":1,"1387":2,"1388":3,"1390":1,"1392":1,"1394":190,"1395":290,"1397":2,"1398":3,"1400":3,"1401":145,"1402":1,"1403":6,"1404":4,"1405":1,"1408":1,"1409":3,"1410":1,"1411":1,"1412":3,"1413":5,"1414":188,"1415":63,"1416":114,"1422":1,"1426":3,"1429":1,"1430":5,"1431":1,"1432":3,"1433":2,"1435":343,"1436":16,"1437":34,"1440":2,"1441":2,"1442":3,"1444":2,"1446":9,"1448":2,"1449":3,"1452":1,"1453":3,"1454":5,"1455":1,"1458":1,"1461":1,"1462":1,"1463":1,"1464":1,"1466":23,"1468":1,"1471":2,"1472":3,"1475":1,"1476":1,"1477":2,"1480":1,"1485":2,"1486":9,"1487":9,"1488":18,"1489":2,"1490":6,"1491":1,"1495":48,"1496":5,"1499":1,"1502":1,"1503":4,"1512":1,"1517":1,"1522":1,"1523":1,"1524":1,"1525":37,"1527":1,"1530":1,"1531":1,"1533":2,"1534":4,"1539":1,"1541":3,"1543":2,"1550":1,"1554":1,"1555":2,"1556":2,"1564":2,"1565":1,"1567":1,"1569":1,"1571":1,"1575":1,"1576":27,"1578":1,"1581":1,"1584":1,"1585":2,"1588":2,"1589":1,"1590":18,"1594":1,"1595":4,"1596":1,"1598":3,"1599":3,"1604":1,"1626":2,"1629":1,"1630":2,"1638":1,"1639":1,"1640":3,"1641":1,"1642":1,"1643":2,"1647":1,"1648":1,"1649":3,"1650":2,"1652":4,"1655":2,"1659":4,"1661":3,"1662":3,"1663":1,"1664":1,"1666":1,"1667":2,"1668":8,"1669":1,"1670":3,"1671":1,"1672":1,"1674":1,"1676":1,"1684":4,"1685":4,"1686":1,"1692":1,"1698":2,"1700":1,"1701":5,"1706":1,"1710":1,"1713":2,"1715":1,"1720":1,"1723":1,"1726":2,"1730":1,"1738":1,"1740":1,"1747":1,"1759":2,"1760":1,"1763":6,"1764":1,"1768":1,"1771":1,"1780":3,"1782":1,"1785":1,"1789":4,"1804":3,"1805":1,"1806":1,"1814":1,"1820":1,"1826":4,"1828":1,"1832":1,"1834":1,"1840":1,"1841":1,"1847":3,"1848":1,"1850":3,"1851":1,"1852":2,"1855":1,"1868":1,"1874":1,"1883":1,"1890":1,"1891":2,"1892":3,"1894":3,"1895":2,"1897":2,"1898":1,"1915":2,"1916":2,"1919":1,"1921":3,"1922":3,"1928":1,"1938":1,"1945":1,"1948":3,"1961":2,"1962":1,"1970":1,"1971":2,"1975":1,"1976":1,"1982":1,"1988":1,"1990":1,"1995":1,"1996":1,"1997":1,"1998":1,"1999":2,"2000":5,"2001":4,"2008":1,"2010":1,"2013":2,"2014":1,"2015":1,"2016":1,"2020":1,"2023":2,"2027":2,"2033":1,"2035":1,"2037":1,"2041":1,"2042":4,"2046":1,"2048":1,"2051":1,"2054":2,"2059":2,"2063":1,"2065":1,"2066":1,"2067":1,"2070":1,"2074":1,"2077":2,"2078":2,"2079":1,"2082":2,"2083":1,"2084":1,"2087":1,"2100":1,"2103":3,"2106":2,"2110":3,"2111":1,"2113":1,"2115":2,"2117":2,"2118":2,"2119":1,"2120":3,"2121":1,"2122":3,"2127":1,"2129":1,"2130":3,"2131":2,"2142":1,"2149":3,"2150":1,"2151":2,"2153":1,"2154":1,"2156":1,"2159":2,"2165":1,"2166":1,"2167":1,"2168":1,"2174":1,"2177":2,"2185":1,"2187":1,"2193":1,"2194":1,"2197":1,"2202":2,"2207":1,"2213":1,"2214":1,"2220":1,"2226":5,"2229":3,"2231":13}}],["sha",{"2":{"1588":1,"1596":1},"3":{"0":5,"157":2,"161":1,"225":1,"231":1,"368":1,"373":3,"374":2,"418":1,"424":1,"428":1,"429":1,"629":1,"854":1,"868":1,"870":1,"873":1,"905":1,"1018":3,"1091":1,"1212":2,"1285":1,"1289":1,"1293":1,"1294":1,"1299":7,"1310":4,"1417":1,"1425":1,"1426":1,"1435":1,"1437":1,"1444":4,"1451":2,"1454":8,"1455":2,"1458":4,"1460":3,"1464":1,"1465":1,"1466":1,"1468":1,"1474":2,"1525":1,"1588":1,"1590":1,"1595":1,"1596":1,"1640":2,"1665":1,"1666":1,"1764":1,"1766":1,"1899":1,"1901":2,"1904":2,"1907":2,"1981":1,"2175":1,"2179":1}}],["sha512",{"1":{"307":1,"308":1,"309":1,"310":1,"311":1,"312":1,"313":1,"314":1,"1899":1,"1900":1,"1901":1,"1902":1,"1903":1,"1904":1},"2":{"310":1,"946":1},"3":{"0":3,"307":1,"308":1,"309":1,"310":3,"311":1,"314":1,"945":2,"946":4,"1212":2,"1291":1,"1299":4,"1323":1,"1435":1,"1575":1,"1576":1,"1666":1,"1746":1,"1762":1,"1763":1,"1764":4,"1898":1,"1899":1,"1901":3,"1903":1,"1904":3,"2207":1}}],["symptoms",{"3":{"770":1,"2080":1}}],["symptom",{"3":{"234":1,"310":1,"664":1,"666":1,"821":1,"956":1,"963":1,"1338":5,"1395":2,"1429":1,"1435":3,"1650":1,"1969":1,"1988":1}}],["symmetricsecuritykey",{"3":{"1299":1,"1310":1}}],["symmetrically",{"3":{"1259":1,"1285":1,"1299":1,"1322":1,"1323":1,"1435":1}}],["symmetrical",{"3":{"930":1}}],["symmetric",{"3":{"24":1,"30":2,"31":2,"32":1,"33":1,"698":1,"764":2,"766":1,"819":1,"1212":1,"1237":1,"1267":1,"1270":1,"1285":1,"1287":1,"1299":3,"1311":3,"1323":3,"1349":1,"1414":1,"1440":1,"1449":1,"1842":1,"1892":2,"1893":1,"1894":1,"1897":1,"1898":2,"1919":1,"1952":1,"1958":1,"2008":1,"2020":1,"2027":1,"2031":1,"2229":1}}],["symmetry",{"3":{"0":1,"870":1,"1035":1,"1270":3,"1285":1,"1310":2,"1311":1,"1338":1,"1358":1,"1379":1,"1394":2,"1395":2,"1414":3,"1926":1,"2182":1}}],["symbolic",{"3":{"1466":1}}],["symbolically",{"3":{"1310":1}}],["symbols",{"3":{"636":1,"954":2,"1034":1,"1037":1,"1323":1,"1416":1,"1435":2,"1672":1}}],["symbol",{"3":{"0":1,"336":1,"619":1,"633":4,"634":1,"1310":2,"1323":9,"1395":1,"1416":1,"1431":1,"1487":1,"1491":1,"1590":1,"1599":1}}],["synonym",{"3":{"472":1}}],["synonyms",{"3":{"0":1,"798":1}}],["syncquestionanswers",{"3":{"1349":1,"1358":1}}],["synccategoryitems",{"3":{"1349":1,"1358":2}}],["synctrackedkey",{"3":{"1285":1}}],["syncing",{"3":{"1259":1,"1349":1,"1416":2}}],["syncstrategy",{"3":{"1525":1,"1534":1}}],["syncsessionquestionanswers",{"3":{"1358":1}}],["syncsessionchildren",{"3":{"1358":1}}],["syncscopedreadcontroller",{"3":{"1199":2,"1207":1}}],["syncs",{"3":{"988":1,"1252":1,"1259":1,"1358":1,"1416":1,"1631":2,"2219":1}}],["syncregistrationsasync",{"2":{"707":1,"710":1},"3":{"707":1,"710":1,"1322":1}}],["syncfusion",{"3":{"650":1}}],["syncasync",{"2":{"507":1},"3":{"507":1,"1323":4,"1358":3}}],["synchronizes",{"3":{"1631":1}}],["synchronize",{"2":{"1459":1},"3":{"1358":1,"1458":2,"1459":1}}],["synchronized",{"3":{"23":1,"643":1,"1323":1}}],["synchronization",{"3":{"481":2,"482":1,"485":1,"1229":1,"1285":1,"1309":1,"1311":1,"1323":3,"1379":1,"1394":2,"1435":1,"2231":1}}],["synchronizationcontext",{"2":{"481":1},"3":{"481":1,"1358":1}}],["synchronously",{"3":{"97":1,"601":1,"1259":4,"1322":1,"1323":2,"1334":1,"1338":2,"1349":1,"1394":5,"1401":1,"1412":1,"1426":1,"1466":1,"1791":1,"2015":1,"2110":1}}],["synchronous",{"1":{"50":1,"51":1,"52":1,"53":1,"54":1,"55":1},"3":{"0":3,"50":1,"52":1,"59":1,"68":1,"109":1,"184":1,"444":1,"603":1,"727":1,"743":3,"861":1,"933":1,"1059":2,"1060":1,"1202":1,"1203":1,"1212":1,"1213":1,"1220":1,"1229":3,"1247":2,"1259":10,"1270":1,"1274":1,"1282":1,"1285":15,"1296":1,"1299":5,"1300":1,"1308":1,"1310":8,"1311":4,"1317":1,"1322":2,"1323":10,"1338":1,"1348":2,"1349":2,"1358":10,"1371":2,"1379":4,"1394":6,"1395":3,"1401":4,"1416":13,"1430":1,"1435":10,"1495":1,"1525":1,"1542":1,"1543":1,"1548":1,"1655":2,"1788":1,"1789":2,"1792":1,"1804":1,"2015":1,"2019":1,"2029":1,"2043":1,"2189":1,"2198":1,"2202":1,"2231":1}}],["synced",{"3":{"0":1,"1212":1,"1323":1,"1346":1,"1358":6,"1368":1,"1631":2,"1635":1}}],["sync",{"3":{"0":1,"209":1,"243":1,"273":1,"274":1,"399":1,"434":1,"483":1,"507":1,"509":1,"649":1,"650":1,"1117":1,"1212":1,"1220":1,"1229":1,"1237":1,"1270":2,"1285":6,"1299":1,"1310":2,"1322":1,"1323":5,"1338":4,"1341":1,"1346":2,"1349":15,"1357":1,"1358":17,"1361":1,"1368":4,"1394":2,"1395":4,"1401":1,"1415":1,"1416":1,"1435":2,"1437":2,"1479":1,"1495":2,"1525":2,"1534":1,"1543":1,"1548":1,"1559":1,"1576":2,"1628":2,"1629":1,"1630":1,"1634":1,"1635":1,"1637":1,"1638":1,"1659":1,"1668":1,"1672":1,"1832":1,"1970":2,"2082":1,"2135":1,"2156":1,"2158":1,"2219":1}}],["syntactically",{"3":{"1322":1,"1323":1,"1394":1}}],["syntactic",{"3":{"1191":1,"1204":1,"1499":1}}],["syntax",{"2":{"2043":1},"3":{"0":1,"136":2,"332":1,"981":1,"1184":1,"1214":2,"1237":1,"1247":3,"1259":1,"1262":1,"1264":1,"1270":1,"1271":1,"1285":4,"1299":3,"1308":5,"1310":2,"1315":1,"1322":3,"1323":6,"1338":1,"1349":1,"1394":1,"1416":4,"1435":5,"1437":1,"1454":1,"1485":1,"1492":1,"1639":1,"1927":1,"1989":1,"2043":1}}],["synthesis",{"3":{"1416":2}}],["synthesize",{"3":{"1416":1}}],["synthesizer",{"3":{"1416":2}}],["synthesizes",{"3":{"1299":1,"1395":1,"1487":1}}],["synthesized",{"3":{"1299":2,"1310":1,"1311":1,"1323":1,"1435":1}}],["synthesizing",{"3":{"109":1,"1299":1,"1311":2,"1437":1}}],["synthetictrafficheadername",{"3":{"827":1,"1338":3}}],["synthetictrafficsecret",{"2":{"827":1,"1466":1,"2023":1},"3":{"827":2,"1338":6,"1457":1,"1466":2,"2023":1}}],["synthetic",{"2":{"1457":2,"1467":1,"1469":1},"3":{"0":1,"173":1,"179":1,"572":1,"597":1,"598":1,"599":2,"825":2,"827":6,"1244":1,"1247":1,"1336":2,"1338":12,"1349":1,"1368":1,"1394":2,"1435":1,"1454":3,"1457":5,"1460":1,"1465":1,"1466":5,"1467":1,"1469":1,"1473":1,"1475":1,"1487":1,"1534":1,"1590":1,"1669":1}}],["systematically",{"3":{"1525":1,"1530":1}}],["systematic",{"3":{"1435":1,"1495":1,"1529":1,"1531":1}}],["systempreferred",{"3":{"1416":1}}],["systemprefersdark",{"2":{"273":1},"3":{"273":1,"1323":1,"2082":1}}],["systemprompt",{"2":{"1425":1},"3":{"1018":1,"1368":2,"1425":1,"1426":2}}],["systemnethttpclient",{"2":{"73":1},"3":{"73":1}}],["systems",{"3":{"0":1,"61":1,"689":1,"812":1,"1011":1,"1116":1,"1238":1,"1358":2,"1379":1,"1394":1,"1425":1,"1775":1,"1788":1,"1791":1,"1801":1,"1814":1,"1834":1,"1845":1,"1846":1,"1852":1,"1911":1,"2026":1,"2107":1,"2159":1,"2161":1,"2168":1,"2170":1,"2192":1,"2212":1,"2216":1,"2217":1,"2219":6,"2237":1}}],["system",{"0":{"1502":1,"1556":1,"1628":1,"1762":1,"1776":1},"1":{"1312":1,"1313":1,"1314":1,"1315":1,"1316":1,"1317":1,"1318":1,"1319":1,"1320":1,"1321":1,"1322":1},"2":{"72":1,"397":1,"471":1,"475":1,"476":1,"477":1,"591":1,"665":1,"681":1,"798":1,"800":1,"803":1,"804":1,"805":1,"960":1,"966":1,"1049":1,"1054":1,"1163":1,"1196":1,"1229":5,"1270":1,"1310":2,"1323":3,"1331":1,"1343":1,"1368":1,"1399":1,"1414":1,"1446":1,"1479":2,"1686":1,"2155":1,"2185":1},"3":{"0":4,"58":1,"72":1,"78":2,"79":1,"81":1,"125":1,"244":1,"259":1,"266":1,"274":1,"340":1,"349":2,"350":2,"351":1,"361":1,"397":3,"400":1,"413":1,"419":1,"427":1,"470":2,"471":1,"475":1,"476":1,"477":1,"517":1,"520":1,"536":1,"539":1,"591":1,"593":1,"607":2,"611":1,"612":1,"665":1,"681":1,"691":2,"696":1,"698":4,"699":1,"700":2,"702":3,"706":1,"717":1,"741":1,"765":1,"792":1,"798":1,"799":2,"800":1,"801":1,"803":1,"804":1,"805":3,"809":1,"810":1,"853":1,"899":1,"947":1,"948":1,"954":2,"960":1,"963":1,"964":1,"966":1,"980":3,"1013":1,"1018":5,"1035":1,"1049":1,"1050":1,"1054":1,"1058":1,"1082":1,"1090":1,"1091":4,"1099":1,"1115":1,"1134":1,"1140":1,"1142":1,"1145":1,"1154":1,"1155":1,"1157":1,"1163":1,"1185":1,"1192":2,"1196":1,"1202":2,"1203":2,"1212":3,"1213":3,"1216":2,"1221":1,"1223":1,"1227":1,"1229":17,"1231":1,"1235":1,"1237":34,"1241":1,"1247":59,"1249":1,"1253":2,"1259":19,"1264":1,"1270":25,"1271":5,"1274":1,"1275":2,"1285":66,"1286":1,"1292":1,"1293":1,"1299":78,"1300":11,"1301":1,"1308":26,"1309":7,"1310":48,"1311":6,"1312":2,"1317":2,"1322":39,"1323":88,"1324":1,"1331":1,"1332":2,"1338":24,"1339":1,"1341":1,"1343":2,"1349":14,"1358":71,"1365":3,"1368":13,"1369":1,"1379":6,"1380":1,"1394":38,"1395":60,"1399":1,"1401":5,"1402":1,"1413":1,"1414":29,"1415":8,"1416":67,"1421":1,"1423":1,"1425":1,"1426":19,"1430":1,"1434":3,"1435":111,"1437":3,"1438":1,"1440":3,"1442":6,"1446":1,"1448":1,"1452":1,"1454":3,"1456":1,"1457":3,"1464":2,"1466":6,"1473":1,"1475":1,"1479":3,"1487":2,"1491":2,"1495":5,"1498":1,"1522":1,"1525":1,"1533":1,"1534":1,"1536":1,"1548":1,"1549":1,"1552":1,"1558":1,"1564":1,"1565":1,"1569":1,"1570":1,"1571":2,"1573":1,"1576":2,"1580":1,"1581":1,"1582":1,"1587":1,"1590":2,"1594":1,"1595":1,"1596":1,"1628":1,"1629":4,"1630":2,"1631":31,"1632":1,"1635":1,"1637":3,"1638":3,"1640":31,"1659":1,"1661":1,"1663":1,"1665":1,"1668":1,"1669":1,"1686":1,"1707":1,"1747":1,"1751":1,"1762":1,"1764":3,"1768":1,"1769":2,"1771":1,"1775":1,"1776":1,"1787":1,"1793":1,"1794":1,"1795":3,"1798":1,"1800":1,"1801":1,"1811":1,"1812":1,"1830":1,"1834":1,"1835":2,"1843":1,"1850":1,"1868":2,"1887":1,"1888":3,"1891":1,"1892":2,"1898":1,"1906":2,"1915":1,"1920":1,"1921":1,"1931":1,"1950":1,"1951":1,"1973":3,"1975":2,"1976":1,"1978":2,"1988":1,"2002":1,"2006":1,"2023":1,"2029":1,"2036":1,"2037":1,"2063":1,"2066":2,"2078":3,"2090":1,"2112":1,"2120":1,"2154":2,"2155":4,"2167":1,"2170":1,"2175":1,"2185":2,"2192":1,"2194":1,"2195":1,"2196":1,"2202":1,"2208":1,"2219":2,"2226":1}}],["swipe",{"3":{"1323":1,"1415":1,"1416":2,"1668":1}}],["switchboard",{"3":{"2113":1}}],["switchboards",{"3":{"1541":1}}],["switchgeneration",{"3":{"1395":2}}],["switchable",{"3":{"572":1,"578":1,"1416":1,"1647":1}}],["switching",{"3":{"265":1,"267":1,"415":1,"454":1,"733":1,"799":1,"882":1,"1247":1,"1276":1,"1285":2,"1299":2,"1310":2,"1322":1,"1323":1,"1368":1,"1389":1,"1394":2,"1415":1,"1416":2,"1426":1,"1435":2,"1661":1,"1853":1,"1896":1}}],["switched",{"3":{"0":2,"235":1,"556":1,"667":1,"914":1,"930":1,"941":1,"1232":1,"1259":1,"1270":1,"1285":3,"1308":1,"1310":4,"1311":1,"1312":1,"1322":2,"1323":3,"1331":1,"1338":2,"1346":1,"1349":1,"1358":3,"1379":1,"1394":5,"1395":12,"1414":2,"1416":2,"1418":1,"1435":3,"1437":2,"1466":1,"1571":1,"1630":1,"1667":1,"1719":1,"1844":1,"1937":1,"1969":1}}],["switchers",{"3":{"1414":1}}],["switcher",{"3":{"0":1,"265":5,"266":1,"273":1,"1299":1,"1310":5,"1322":1,"1323":5,"1414":1,"1416":4,"1435":1,"1437":1,"1576":1,"1643":1,"1652":4,"1668":1,"2082":2,"2085":1}}],["switches",{"1":{"2080":1,"2081":1,"2082":1,"2083":1,"2084":1,"2085":1},"3":{"0":2,"109":1,"171":1,"179":1,"265":1,"432":1,"452":2,"609":1,"674":1,"684":1,"939":1,"1175":1,"1184":1,"1270":2,"1277":1,"1278":1,"1285":4,"1299":1,"1300":1,"1305":2,"1308":3,"1309":2,"1310":1,"1319":2,"1322":3,"1323":5,"1325":1,"1332":1,"1338":3,"1349":1,"1354":1,"1358":3,"1394":4,"1395":6,"1401":1,"1415":2,"1426":1,"1435":4,"1440":2,"1455":1,"1466":4,"1480":1,"1495":1,"1674":1,"1700":1,"1920":1,"2080":1,"2209":1}}],["switch",{"0":{"1287":1,"1723":1},"2":{"966":1,"1537":1,"1861":1},"3":{"0":8,"31":1,"32":1,"59":1,"142":1,"150":2,"164":1,"166":2,"171":1,"195":1,"243":2,"265":4,"267":1,"290":1,"295":3,"296":1,"299":1,"301":1,"302":1,"361":1,"440":1,"572":1,"576":1,"600":1,"602":1,"618":1,"644":1,"666":1,"674":1,"691":1,"819":1,"838":1,"879":1,"916":1,"963":1,"964":1,"965":1,"966":1,"1034":4,"1035":1,"1036":1,"1051":1,"1075":2,"1091":1,"1107":2,"1108":1,"1212":1,"1220":1,"1229":1,"1235":1,"1237":2,"1241":1,"1243":1,"1247":4,"1259":2,"1260":1,"1264":1,"1270":4,"1273":2,"1285":22,"1287":1,"1296":2,"1299":5,"1302":1,"1310":12,"1311":1,"1312":1,"1322":6,"1323":23,"1335":1,"1338":7,"1346":1,"1349":6,"1358":2,"1362":1,"1379":1,"1394":24,"1395":39,"1398":1,"1401":6,"1414":3,"1415":3,"1416":22,"1420":2,"1426":2,"1430":1,"1435":12,"1437":4,"1439":1,"1442":1,"1443":1,"1445":1,"1446":1,"1466":6,"1474":1,"1478":1,"1487":2,"1495":2,"1506":1,"1525":1,"1534":1,"1537":2,"1590":2,"1601":1,"1606":1,"1607":1,"1630":1,"1652":1,"1660":2,"1673":1,"1685":1,"1691":1,"1711":1,"1716":1,"1723":2,"1740":1,"1741":1,"1794":1,"1804":1,"1841":1,"1855":1,"1861":1,"1893":1,"1969":1,"1971":1,"2010":1,"2012":1,"2019":2,"2072":1,"2082":1,"2085":2,"2110":1,"2125":1,"2133":1,"2134":1,"2137":1,"2138":1,"2144":1,"2178":1,"2179":1,"2196":1}}],["swagger",{"3":{"1533":1}}],["swamping",{"3":{"714":1}}],["swallowing",{"0":{"1918":1},"3":{"230":1,"537":1,"897":1,"898":2,"901":1,"989":1,"1237":1,"1252":1,"1259":2,"1310":2,"1322":1,"1323":4,"1338":1,"1354":1,"1358":1,"1395":2,"1412":1,"1414":1,"1416":4,"1435":1,"1454":1,"1458":1,"1495":1,"1945":1,"2164":1}}],["swallow",{"3":{"0":1,"111":1,"255":1,"258":1,"517":1,"534":2,"536":1,"538":1,"896":1,"897":5,"898":2,"899":2,"901":1,"1247":6,"1259":5,"1270":1,"1300":1,"1308":1,"1310":2,"1322":1,"1323":2,"1338":1,"1358":2,"1388":1,"1394":3,"1395":6,"1401":4,"1414":2,"1416":5,"1437":2,"1664":1,"1918":2,"1932":1,"1933":1,"1939":1,"1945":1,"1948":1,"1949":1}}],["swallowed",{"3":{"0":1,"157":1,"400":1,"461":1,"854":1,"896":2,"897":3,"898":1,"901":1,"988":1,"1212":1,"1247":8,"1285":1,"1299":1,"1300":2,"1306":1,"1308":1,"1310":4,"1322":2,"1323":9,"1354":1,"1357":1,"1358":1,"1368":1,"1382":2,"1394":11,"1395":7,"1398":1,"1401":5,"1407":1,"1416":7,"1432":1,"1435":1,"1437":3,"1442":1,"1454":1,"1664":1,"1908":2,"1917":1,"1918":1,"2005":1,"2116":1,"2155":1}}],["swallows",{"3":{"0":2,"125":1,"195":1,"230":1,"759":1,"897":1,"988":1,"1259":1,"1263":1,"1270":1,"1275":1,"1285":2,"1299":3,"1308":1,"1310":2,"1323":8,"1358":1,"1394":1,"1395":3,"1401":2,"1414":1,"1415":1,"1416":6,"1430":1,"1435":2,"1664":1,"1932":1,"1948":2}}],["swapping",{"3":{"0":2,"54":1,"472":1,"478":1,"798":1,"799":1,"1212":1,"1259":1,"1285":2,"1299":1,"1300":1,"1311":1,"1322":1,"1323":2,"1358":2,"1360":1,"1368":1,"1377":1,"1395":3,"1414":1,"1419":1,"1435":2,"1792":1,"1824":1,"1901":1,"2016":1,"2072":1,"2078":1,"2149":1}}],["swappable",{"1":{"240":1,"241":1,"242":1,"243":1,"244":1,"245":1,"246":1,"247":1,"248":1,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"260":1,"261":1},"3":{"0":1,"240":1,"1212":1,"1248":1,"1259":1,"1270":1,"1285":4,"1308":1,"1323":3,"1358":3,"1395":3,"1416":4,"1426":1,"1435":2,"1667":1,"2231":1}}],["swapped",{"3":{"0":3,"244":1,"380":1,"499":1,"636":1,"800":1,"803":1,"1259":3,"1299":1,"1310":1,"1349":1,"1358":2,"1395":1,"1401":1,"1435":3,"1495":1,"1683":1,"1735":1,"1901":2,"1981":1,"2025":1,"2177":1}}],["swaps",{"3":{"0":4,"178":1,"225":1,"434":1,"440":1,"633":1,"832":2,"881":1,"1018":1,"1212":2,"1239":1,"1285":1,"1299":1,"1306":1,"1310":3,"1315":1,"1322":1,"1323":2,"1338":1,"1358":2,"1377":1,"1379":1,"1382":1,"1394":4,"1395":5,"1412":1,"1414":2,"1416":2,"1430":1,"1435":1,"1437":1,"1439":1,"1466":1,"1476":1,"1487":1,"1491":1,"1525":1,"1935":1,"1947":1,"1971":1,"1974":1,"1978":1,"2012":1,"2074":1,"2118":1,"2122":1,"2125":1}}],["swap",{"3":{"0":3,"31":1,"68":1,"150":1,"241":1,"243":2,"244":1,"247":1,"253":1,"257":1,"259":2,"261":1,"265":1,"350":1,"454":1,"632":1,"651":1,"664":1,"732":1,"801":1,"1018":2,"1019":1,"1075":1,"1076":1,"1077":1,"1079":1,"1090":1,"1091":1,"1092":2,"1142":1,"1212":2,"1247":1,"1257":1,"1259":1,"1285":2,"1287":1,"1300":1,"1308":1,"1310":1,"1311":1,"1322":5,"1323":2,"1338":1,"1368":1,"1379":1,"1395":7,"1412":1,"1414":1,"1416":1,"1426":3,"1434":1,"1435":5,"1437":1,"1441":1,"1473":1,"1491":1,"1533":1,"1552":1,"1570":1,"1576":4,"1585":1,"1723":1,"1726":1,"1764":1,"1789":1,"1790":1,"1802":1,"1846":1,"1848":1,"1852":1,"1915":2,"1921":1,"1932":1,"1978":1,"2027":1,"2032":1,"2059":1,"2074":1,"2077":1,"2110":1,"2127":1,"2149":2,"2176":1,"2231":1}}],["swelling",{"3":{"1394":1}}],["sweepdeadlettersasync",{"3":{"1435":1}}],["sweepharness",{"3":{"1199":2,"1207":1}}],["sweeper",{"3":{"853":1,"854":1,"1259":2,"1292":1,"1293":1,"1299":3,"1323":1,"1414":2,"1640":1,"1666":1,"1764":1}}],["sweeping",{"3":{"148":1,"284":1,"1299":1,"1323":1,"1358":1,"1405":1,"1414":1,"1960":1,"2195":1}}],["sweeps",{"3":{"0":2,"24":1,"145":1,"635":1,"788":1,"793":1,"839":1,"937":1,"1212":2,"1259":1,"1276":1,"1278":1,"1283":1,"1285":4,"1299":1,"1312":1,"1316":1,"1322":1,"1358":1,"1437":3,"1489":1,"1491":1,"1525":1,"1584":1,"1660":1,"1663":2,"1730":2,"1763":1,"1844":1}}],["sweep",{"0":{"539":1,"2166":1},"3":{"0":9,"24":1,"27":1,"123":1,"135":1,"146":1,"147":3,"148":1,"149":1,"151":1,"187":2,"245":2,"246":1,"254":1,"257":1,"258":1,"260":1,"267":1,"369":1,"465":1,"497":1,"534":6,"536":7,"537":2,"538":3,"539":9,"540":2,"541":4,"574":1,"579":1,"634":1,"696":1,"707":2,"713":1,"715":2,"720":1,"725":1,"733":1,"737":1,"739":1,"741":2,"743":2,"744":1,"760":1,"780":1,"782":1,"789":1,"792":1,"794":1,"798":1,"799":1,"802":1,"809":6,"810":5,"812":2,"813":3,"814":1,"815":2,"891":1,"903":2,"905":9,"906":1,"907":2,"909":1,"941":2,"942":1,"955":1,"1003":1,"1004":1,"1016":1,"1018":1,"1022":1,"1036":1,"1041":1,"1042":1,"1044":1,"1074":1,"1075":1,"1084":1,"1118":1,"1212":1,"1256":2,"1259":7,"1264":1,"1270":2,"1273":1,"1275":2,"1276":1,"1285":31,"1289":4,"1299":9,"1310":2,"1315":1,"1316":1,"1317":1,"1322":8,"1323":2,"1358":3,"1366":2,"1367":1,"1368":1,"1395":4,"1435":7,"1437":5,"1458":1,"1459":2,"1460":1,"1466":1,"1474":4,"1482":1,"1484":1,"1488":1,"1489":1,"1491":1,"1495":32,"1499":1,"1525":1,"1575":2,"1576":3,"1584":1,"1590":2,"1599":3,"1647":1,"1653":1,"1663":1,"1671":1,"1692":1,"1736":1,"1764":3,"1843":2,"1844":1,"1947":1,"1958":1,"1959":1,"1964":2,"1981":1,"1983":2,"2054":1,"2111":1,"2114":1,"2160":1,"2165":2,"2166":9,"2167":8,"2168":2,"2182":1,"2189":1,"2214":1,"2231":1}}],["swept",{"3":{"0":2,"78":1,"249":1,"633":1,"905":1,"1004":1,"1077":1,"1256":1,"1277":1,"1285":2,"1289":1,"1299":2,"1332":1,"1358":2,"1435":1,"1440":1,"1454":1,"1464":1,"1530":1,"1568":1,"1734":1,"1735":1,"1784":1,"2111":1}}],["se",{"3":{"1685":1}}],["sed",{"3":{"1285":1}}],["seq",{"2":{"760":1},"3":{"760":1}}],["sequencing",{"0":{"1616":1},"3":{"127":1,"151":1,"383":1,"518":1,"1100":1,"1358":1,"1394":1,"1466":1,"1943":1}}],["sequenceequal",{"2":{"1901":1},"3":{"1285":1,"1299":1,"1435":1,"1901":1}}],["sequenced",{"3":{"1191":1,"1322":1,"1435":2}}],["sequences",{"3":{"0":1,"126":1,"127":1,"1262":1,"1322":1,"1395":1,"1422":1,"1435":2,"1825":2}}],["sequence",{"0":{"1823":1,"1838":1,"2094":1},"3":{"0":3,"57":1,"115":1,"117":2,"119":1,"121":2,"122":1,"127":1,"256":1,"279":1,"281":1,"283":1,"459":1,"523":1,"560":1,"572":3,"633":1,"651":1,"698":1,"715":1,"733":1,"749":1,"750":1,"751":1,"838":1,"1069":1,"1100":2,"1145":1,"1212":1,"1232":1,"1237":5,"1259":4,"1262":4,"1270":7,"1285":5,"1288":1,"1299":4,"1300":1,"1308":2,"1310":7,"1315":2,"1322":8,"1323":4,"1338":2,"1342":1,"1349":1,"1358":22,"1394":8,"1395":2,"1398":1,"1401":3,"1414":3,"1415":4,"1416":1,"1426":1,"1435":16,"1437":2,"1440":1,"1451":1,"1454":1,"1462":1,"1466":1,"1472":1,"1473":1,"1477":1,"1487":1,"1488":1,"1576":1,"1590":1,"1630":1,"1652":2,"1655":1,"1657":1,"1684":1,"1726":1,"1778":1,"1823":3,"1824":1,"1825":3,"1837":1,"1838":1,"1877":1,"1880":1,"1882":1,"1885":1,"1904":1,"1914":1,"1940":1,"2090":1,"2094":1,"2095":1,"2097":2,"2106":1,"2195":1}}],["sequentially",{"3":{"1":1,"122":1,"538":1,"727":1,"759":1,"914":1,"916":1,"1263":1,"1271":1,"1278":1,"1309":1,"1321":1,"1338":1,"1358":3,"1414":2,"1428":1,"1435":1,"1487":1,"1851":1,"2167":1}}],["sequential",{"3":{"0":1,"122":1,"286":1,"470":1,"477":1,"537":1,"690":1,"798":1,"987":1,"988":1,"992":1,"1116":1,"1233":1,"1237":2,"1263":1,"1270":2,"1271":1,"1285":4,"1291":1,"1299":2,"1321":1,"1341":1,"1349":1,"1358":8,"1368":1,"1394":2,"1395":5,"1401":1,"1414":4,"1435":2,"1437":1,"1454":2,"1576":1,"1630":2,"1663":1,"1839":1,"1844":1,"2000":1,"2163":1,"2168":1}}],["segoe",{"3":{"1323":2,"2074":1}}],["segregation",{"3":{"1212":1,"1237":2,"1259":1,"1260":1,"1270":3,"1278":1,"1285":3,"1290":1,"1299":7,"1308":2,"1310":3,"1322":1,"1323":2,"1358":5,"1379":1,"1394":4,"1395":1,"1414":1,"1416":1,"1426":1}}],["segregated",{"3":{"545":1,"1272":1,"1278":1,"1310":1,"1358":1}}],["segmentversionedprobecontroller",{"3":{"1199":3,"1207":1}}],["segmented",{"3":{"178":1}}],["segment",{"3":{"0":1,"123":1,"135":1,"139":1,"178":1,"246":1,"333":1,"446":1,"447":1,"450":1,"470":1,"477":1,"549":1,"607":1,"635":1,"741":1,"798":1,"827":1,"1004":3,"1006":2,"1050":2,"1115":1,"1116":2,"1118":1,"1124":1,"1126":2,"1228":1,"1229":4,"1241":3,"1247":10,"1267":1,"1270":3,"1284":1,"1285":14,"1299":5,"1300":1,"1310":9,"1320":1,"1322":2,"1323":10,"1338":6,"1341":1,"1349":1,"1358":2,"1364":1,"1368":3,"1395":1,"1414":5,"1435":14,"1466":1,"1476":1,"1665":1,"1729":1,"1809":1,"1855":1,"1919":2,"2042":3,"2043":1,"2128":1,"2129":1,"2131":1,"2132":1,"2148":1}}],["segments",{"3":{"0":1,"333":1,"549":1,"599":1,"1228":1,"1229":3,"1241":2,"1247":4,"1270":2,"1285":1,"1299":1,"1310":4,"1323":4,"1336":1,"1338":2,"1435":2,"1466":2}}],["segmentsperwindow",{"2":{"178":1},"3":{"0":1,"178":1,"1310":5}}],["sesion",{"3":{"265":1}}],["session→400",{"3":{"1610":1}}],["sessionpresent",{"3":{"1395":1}}],["sessionprefix",{"3":{"1308":3}}],["sessionvalidationservice",{"3":{"1395":1}}],["sessionvalidationrules",{"3":{"1207":9,"1271":2,"1358":28,"1495":1}}],["sessionvalidationrulestests",{"3":{"1199":1,"1207":4,"1358":4}}],["sessionqa",{"2":{"1629":1,"1631":1},"3":{"1395":3,"1401":2,"1629":1,"1631":1}}],["sessionquestionid",{"3":{"1395":3,"1401":5,"1629":1}}],["sessionquestionidentifiertype",{"3":{"1395":4,"1401":16,"1414":1}}],["sessionquestioninvariants",{"2":{"1397":1},"3":{"1199":9,"1203":1,"1207":2,"1395":3,"1397":2,"1398":2,"1401":17}}],["sessionquestionlifecycletests",{"3":{"1199":1,"1207":2}}],["sessionquestionconfiguration",{"3":{"1199":2,"1203":1,"1207":2,"1395":5,"1401":3}}],["sessionquestionchannel",{"2":{"1401":1},"3":{"1199":9,"1203":1,"1207":2,"1398":2,"1401":29}}],["sessionquestionchanged",{"2":{"782":1,"1397":1},"3":{"690":1,"782":1,"1199":5,"1203":1,"1207":2,"1395":4,"1397":1,"1401":10}}],["sessionquestiontests",{"3":{"1199":1,"1207":2}}],["sessionquestionviewbuilder",{"2":{"1400":1,"1533":1},"3":{"1199":6,"1203":1,"1207":2,"1285":1,"1395":2,"1400":2,"1401":11,"1435":1,"1533":1}}],["sessionquestion",{"2":{"1396":1,"1397":1},"3":{"1199":22,"1203":1,"1207":2,"1349":1,"1395":11,"1396":2,"1397":6,"1401":43,"1414":1,"1435":2,"1437":1,"1486":1,"1495":2,"1629":1}}],["sessionquestiondto",{"2":{"1397":1},"3":{"1199":20,"1203":1,"1207":2,"1395":4,"1397":2,"1401":39}}],["sessionquestiondismissedpayload",{"3":{"1199":2,"1203":1,"1207":2,"1401":5}}],["sessionquestionpendingcountchangedpayload",{"2":{"1401":1},"3":{"1199":4,"1203":1,"1207":2,"1401":9}}],["sessionquestionapprovedpayload",{"2":{"1398":1},"3":{"1199":4,"1203":1,"1207":2,"1398":1,"1401":10}}],["sessionquestionanswerid",{"3":{"1349":1,"1358":8}}],["sessionquestionansweridentifiertype",{"3":{"1349":3,"1358":4,"1394":3,"1395":1}}],["sessionquestionanswertests",{"3":{"1199":1,"1207":2}}],["sessionquestionanswernavigationpopulatortests",{"3":{"1199":1,"1207":2,"1358":2}}],["sessionquestionanswernavigationpopulator",{"3":{"1199":2,"1203":1,"1207":2,"1358":5}}],["sessionquestionanswerconfiguration",{"2":{"1361":1},"3":{"1199":1,"1203":1,"1207":2,"1358":1,"1361":4,"1368":5,"1395":1}}],["sessionquestionanswerchanged",{"2":{"1344":1,"1630":1,"1633":1},"3":{"1199":3,"1203":1,"1207":2,"1344":1,"1349":5,"1358":2,"1630":1,"1633":1}}],["sessionquestionanswerrules",{"3":{"1199":3,"1203":1,"1207":2,"1349":4,"1351":1,"1358":28}}],["sessionquestionanswerdtomappertests",{"3":{"1199":1,"1207":2,"1358":2}}],["sessionquestionanswerdtomapper",{"3":{"1199":11,"1203":1,"1207":2,"1358":8}}],["sessionquestionanswerdto",{"2":{"1346":1},"3":{"1199":16,"1203":1,"1207":2,"1346":1,"1349":9,"1358":3,"1379":1,"1394":4,"1395":5}}],["sessionquestionansweredpayload",{"3":{"1199":2,"1203":1,"1207":2,"1401":7}}],["sessionquestionanswer",{"2":{"1341":1,"1637":1,"1638":1,"1639":1},"3":{"1198":1,"1199":19,"1203":1,"1207":2,"1341":2,"1349":11,"1358":19,"1368":3,"1379":1,"1629":1,"1630":5,"1631":1,"1633":2,"1634":3,"1636":1,"1637":7,"1638":5,"1639":6}}],["sessionquestionanswers",{"2":{"1115":1,"1342":1,"1362":1},"3":{"1115":1,"1342":1,"1349":5,"1358":17,"1362":1,"1368":2,"1379":3,"1394":3,"1395":2,"1446":1,"1629":2,"1630":1,"1631":1,"1634":1,"1637":1,"1638":4}}],["sessionquestionanswerscontrollertests",{"3":{"1199":1,"1207":2}}],["sessionquestionanswerscontroller",{"2":{"325":1,"1370":1,"1372":1,"1638":1},"3":{"325":3,"326":1,"743":1,"1199":3,"1203":1,"1207":6,"1239":1,"1247":3,"1310":2,"1349":1,"1358":32,"1370":2,"1371":3,"1372":2,"1373":3,"1379":30,"1395":2,"1495":1,"1638":1,"1994":1}}],["sessionquestionupvoteidentifiertype",{"3":{"1401":1}}],["sessionquestionupvoteinvariants",{"3":{"1199":2,"1203":1,"1207":2,"1401":4}}],["sessionquestionupvotes",{"3":{"1395":1,"1629":1}}],["sessionquestionupvoteconfiguration",{"3":{"1199":2,"1203":1,"1207":2,"1285":1,"1395":4,"1401":1}}],["sessionquestionupvotechangedpayload",{"3":{"1199":4,"1203":1,"1207":2,"1395":2,"1401":7}}],["sessionquestionupvotechangedhandlertests",{"3":{"1199":1,"1207":3,"1437":1}}],["sessionquestionupvotechangedhandler",{"2":{"522":1,"1100":1,"1398":1},"3":{"522":2,"897":2,"900":1,"1100":2,"1199":2,"1203":1,"1207":2,"1247":1,"1395":3,"1398":7,"1401":11,"1495":2}}],["sessionquestionupvotechanged",{"2":{"383":1,"522":1,"1100":1,"1398":1},"3":{"383":1,"1199":5,"1203":1,"1207":2,"1398":1,"1401":9}}],["sessionquestionupvotetests",{"3":{"1199":1,"1207":2}}],["sessionquestionupvote",{"2":{"1397":1,"1401":1},"3":{"1199":15,"1203":1,"1207":2,"1395":6,"1397":1,"1401":23,"1495":1,"1629":1}}],["sessionquestionuiservicetests",{"3":{"1199":1,"1207":2}}],["sessionquestionuiservice",{"2":{"1192":1},"3":{"881":1,"1192":1,"1199":2,"1203":1,"1207":2,"1299":6,"1395":7,"1401":1}}],["sessionquestionscontrollertests",{"3":{"1199":1,"1207":3}}],["sessionquestionscontroller",{"2":{"1873":1},"3":{"1199":3,"1203":1,"1207":2,"1310":2,"1379":1,"1395":3,"1401":30,"1495":1,"1525":1,"1873":1}}],["sessionquestionsubmittedpointshandlertests",{"3":{"782":1,"1199":1,"1207":3,"1395":1}}],["sessionquestionsubmittedpointshandler",{"3":{"690":1,"692":1,"782":1,"1199":2,"1203":1,"1207":2,"1237":1,"1395":10,"1401":2}}],["sessionquestions",{"2":{"1401":2,"2109":1},"3":{"493":1,"494":1,"495":1,"522":3,"523":2,"524":1,"897":1,"996":1,"1100":3,"1203":33,"1207":98,"1247":4,"1270":1,"1285":1,"1308":1,"1358":1,"1395":19,"1396":1,"1397":3,"1398":3,"1400":3,"1401":88,"1435":3,"1437":1,"1446":1,"1495":1,"1631":3,"2109":1}}],["sessionmatchesscoretier",{"3":{"1394":1}}],["sessionmatchesstatus",{"3":{"1394":2}}],["sessionmatchesfilters",{"3":{"1394":1}}],["sessionmappingonlydbcontext",{"3":{"1199":1,"1207":1}}],["sessionget",{"3":{"1323":1}}],["sessionnotinevent",{"3":{"1401":1}}],["sessionnotfound",{"3":{"905":1,"1358":1,"1394":1}}],["sessionnavigationpopulatortests",{"3":{"1199":1,"1207":2,"1358":2}}],["sessionnavigationpopulator",{"3":{"1199":2,"1203":1,"1207":2,"1309":2,"1353":1,"1358":11}}],["sessionbstatus",{"3":{"1349":1}}],["sessionbtitle",{"3":{"1349":1}}],["sessionbid",{"3":{"1349":1}}],["sessionbuilder",{"3":{"1199":28,"1207":2,"1435":1}}],["sessionbookmarkcount",{"3":{"1395":1}}],["sessionbookmarks",{"3":{"1395":5}}],["sessionbookmarksgrpcservice",{"2":{"1377":1},"3":{"1199":1,"1203":1,"1207":2,"1311":6,"1349":1,"1358":2,"1377":4,"1379":6,"1395":1}}],["sessionbookmarkvalidationserviceclient",{"3":{"1379":1}}],["sessionbookmarkvalidationservicebase",{"3":{"1379":1}}],["sessionbookmarkvalidationservicetests",{"3":{"1199":1,"1207":2,"1358":2}}],["sessionbookmarkvalidationservice",{"3":{"1199":5,"1203":1,"1207":2,"1349":2,"1354":4,"1358":9,"1379":3}}],["sessionbookmarkvalidationservicegrpcadapter",{"2":{"53":1,"1203":1,"1311":1,"1377":1,"1926":1},"3":{"53":1,"1199":2,"1203":1,"1207":2,"1311":4,"1349":1,"1377":4,"1379":8,"1926":1}}],["sessionbookmarkuiservicetests",{"3":{"1199":1,"1207":2,"1395":2,"1416":1}}],["sessionbookmarkuiservice",{"3":{"1199":2,"1203":1,"1207":2,"1395":14,"1416":1}}],["sessionbookmarkbuttontests",{"3":{"1199":1,"1207":2}}],["sessionbookmarkbutton",{"2":{"1390":1},"3":{"1199":2,"1203":1,"1207":2,"1390":3,"1394":6,"1395":4,"1495":1}}],["sessiontitlerequiredkey",{"3":{"1394":1}}],["sessiontitlerules",{"3":{"1199":3,"1203":1,"1207":1,"1271":1,"1352":1,"1358":5}}],["sessiontitle",{"3":{"1349":5,"1379":1,"1394":3,"1395":3}}],["sessiontests",{"3":{"1199":1,"1207":2}}],["sessiontokenresult",{"2":{"1297":1},"3":{"1199":7,"1203":1,"1207":1,"1297":1,"1299":7}}],["sessiontokenresponse",{"2":{"1297":1,"1967":1},"3":{"1199":3,"1203":1,"1207":1,"1297":1,"1299":6,"1967":1}}],["sessionevent",{"3":{"1368":1}}],["sessioneventidrules",{"3":{"1199":3,"1203":1,"1207":1,"1271":3,"1358":8,"1495":1}}],["sessionentityqueryservicetests",{"3":{"1199":1,"1207":2,"1358":2}}],["sessionentityqueryservice",{"3":{"1199":2,"1203":1,"1207":2,"1353":2,"1358":8}}],["sessioneditmodel",{"3":{"1199":1,"1203":1,"1207":2,"1394":6}}],["sessionformfields",{"3":{"1394":2}}],["sessionformmodel",{"2":{"1525":1},"3":{"1199":3,"1203":1,"1207":2,"1349":2,"1383":2,"1394":8,"1525":2,"1530":1,"1531":1,"1534":1}}],["sessionfieldrules",{"3":{"1199":3,"1203":1,"1207":1,"1352":1,"1358":21}}],["sessionfeedbackorganizer",{"3":{"1394":1}}],["sessionfeedbacktests",{"3":{"1199":1,"1207":2,"1395":1,"1437":1}}],["sessionfeedbackservicetests",{"3":{"1199":1,"1207":2,"1395":1}}],["sessionfeedbackservice",{"3":{"1199":2,"1203":1,"1207":2,"1395":6}}],["sessionfeedbacksubmittedpointshandlertests",{"3":{"1199":1,"1207":2,"1395":1}}],["sessionfeedbacksubmittedpointshandler",{"3":{"1199":2,"1203":1,"1207":2,"1259":1,"1349":2,"1358":2,"1395":8}}],["sessionfeedbacksubmitted",{"2":{"195":1,"1340":1,"1348":1,"1638":1},"3":{"195":1,"690":1,"1199":6,"1203":1,"1207":2,"1237":2,"1340":1,"1348":5,"1349":6,"1358":4,"1395":3,"1435":1,"1466":1,"1630":1,"1638":1}}],["sessionfeedbackdto",{"3":{"1199":10,"1203":1,"1207":4,"1346":2,"1349":12,"1358":3,"1379":1,"1394":3}}],["sessionfeedbackpage",{"3":{"1199":2,"1207":2}}],["sessionfeedback",{"2":{"692":1},"3":{"690":1,"692":1,"1199":5,"1203":1,"1207":2,"1343":1,"1349":1,"1394":2,"1395":36,"1525":1,"1530":1,"1533":1}}],["sessiondurationsorttests",{"3":{"1199":1,"1207":2}}],["sessiondtomappertests",{"3":{"1199":1,"1207":2,"1358":2}}],["sessiondtomapper",{"3":{"1199":8,"1203":1,"1207":2,"1349":4,"1352":2,"1358":21}}],["sessiondtotests",{"3":{"1199":1,"1207":2}}],["sessiondto",{"3":{"1199":43,"1203":1,"1207":2,"1310":2,"1346":2,"1349":40,"1358":23,"1379":2,"1381":1,"1394":21,"1395":7,"1495":2,"1874":1}}],["sessiondescriptionrules",{"3":{"1199":2,"1203":1,"1207":1,"1358":3}}],["sessiondetails",{"3":{"1394":2,"1395":2}}],["sessiondetailstaleloadtests",{"3":{"1199":1,"1207":2}}],["sessiondetailroomcachetests",{"3":{"1199":1,"1207":2,"1437":2}}],["sessiondetailcategorychiptests",{"3":{"1199":1,"1207":2}}],["sessiondetailpage",{"3":{"1199":10,"1207":2}}],["sessiondetail",{"3":{"1119":1,"1199":4,"1203":1,"1207":2,"1237":1,"1383":2,"1394":49,"1525":3,"1530":1,"1534":1}}],["sessiondeleted",{"2":{"781":1,"783":1},"3":{"781":1,"783":1}}],["sessionupdaterequestvalidatortests",{"3":{"1199":1,"1207":2,"1358":2}}],["sessionupdaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1358":13}}],["sessionupdaterequest",{"2":{"1874":1},"3":{"1199":5,"1203":1,"1207":2,"1358":10,"1379":1,"1874":1}}],["sessiononlycontextbase",{"3":{"1199":3,"1207":1}}],["sessionlookup",{"3":{"1395":1,"1401":1}}],["sessionlookups",{"2":{"1386":1},"3":{"1199":1,"1203":1,"1207":2,"1384":2,"1386":1,"1394":22}}],["sessionlookupservicetests",{"3":{"1199":1,"1207":2,"1395":2}}],["sessionlookupservice",{"2":{"1192":1},"3":{"1192":1,"1199":2,"1203":1,"1207":2,"1395":14,"1495":1}}],["sessionlevel",{"3":{"1349":2,"1358":1,"1394":1}}],["sessionlisturi",{"3":{"1379":2}}],["sessionlisteventfiltertests",{"3":{"1199":1,"1207":2,"1435":1}}],["sessionlist",{"3":{"1199":2,"1203":1,"1207":2,"1323":4,"1349":2,"1382":2,"1384":1,"1394":21}}],["sessionlistpage",{"3":{"1199":3,"1207":2,"1435":1}}],["sessionlivemoderationpaneltests",{"3":{"1199":1,"1207":2}}],["sessionlivemoderationpanel",{"2":{"1400":1},"3":{"1199":3,"1203":1,"1207":3,"1395":4,"1400":1,"1401":16,"1530":1}}],["sessionliveurlrules",{"3":{"1199":2,"1203":1,"1207":1,"1358":5}}],["sessionliveuiservice",{"2":{"1192":1,"1400":1},"3":{"1192":1,"1199":1,"1203":1,"1207":2,"1395":6,"1400":2,"1401":2}}],["sessionlivequestionpanel",{"2":{"1400":1},"3":{"1199":3,"1203":1,"1207":2,"1395":2,"1400":1,"1401":10}}],["sessionlivequestionpaneltests",{"3":{"954":1,"960":1,"1199":1,"1207":2}}],["sessionlivepollpaneltests",{"3":{"1199":1,"1207":2}}],["sessionlivepollpanel",{"2":{"1400":1},"3":{"1199":3,"1203":1,"1207":2,"1394":1,"1395":1,"1400":1,"1401":7}}],["sessionliveinfo",{"3":{"1199":20,"1203":1,"1207":2,"1348":1,"1349":9,"1358":1,"1377":1,"1379":3,"1401":8,"1495":1}}],["sessionlive",{"2":{"1396":1,"1401":1},"3":{"556":3,"881":1,"954":1,"960":1,"1192":1,"1199":2,"1203":17,"1207":46,"1299":5,"1394":1,"1395":44,"1396":4,"1399":5,"1400":9,"1401":36,"1495":3,"1525":1,"1530":1,"1533":1}}],["sessionhandoffprotector",{"3":{"1184":1}}],["sessionroomfiltertests",{"3":{"1199":1,"1207":2,"1437":2}}],["sessionroomschedulingtests",{"3":{"1199":1,"1207":2,"1358":2}}],["sessionroomscheduling",{"2":{"1358":1,"1533":1},"3":{"545":1,"551":1,"1199":4,"1203":1,"1207":2,"1351":7,"1358":15,"1495":2,"1533":1}}],["sessionretrievalfailed",{"2":{"1599":1},"3":{"1599":1}}],["sessionresult",{"3":{"1394":1,"1395":1}}],["sessionresourcelinksrules",{"3":{"1199":2,"1203":1,"1207":1,"1358":3}}],["sessionrecordingurlrules",{"3":{"1199":2,"1203":1,"1207":1,"1358":3}}],["sessionremindercoordinatortests",{"3":{"1199":1,"1207":2,"1395":1,"1416":1}}],["sessionremindercoordinator",{"3":{"1199":4,"1203":1,"1207":2,"1395":12,"1416":3}}],["sessionreminderplannertests",{"3":{"1199":1,"1207":2,"1395":1}}],["sessionreminderplanner",{"3":{"1199":4,"1203":1,"1207":3,"1395":16}}],["sessionreminder",{"3":{"1199":2,"1203":1,"1207":1,"1395":6}}],["sessionrefreshoutcome",{"3":{"1184":1,"1185":1}}],["sessionrefreshstatus",{"3":{"1184":1}}],["sessioncreationfailed",{"2":{"1599":1},"3":{"1599":1,"1769":1}}],["sessioncreaterequestvalidatortests",{"3":{"1199":1,"1207":2,"1358":4}}],["sessioncreaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1358":13}}],["sessioncreaterequestmappertests",{"3":{"1199":1,"1207":2,"1358":2}}],["sessioncreaterequestmapper",{"3":{"1199":2,"1203":1,"1207":2,"1358":9}}],["sessioncreaterequest",{"3":{"1199":9,"1203":1,"1207":2,"1270":1,"1358":12,"1373":1,"1379":1}}],["sessioncreatemodel",{"3":{"1199":2,"1203":1,"1207":2,"1394":6}}],["sessioncreatepage",{"3":{"1199":12,"1207":2}}],["sessioncreated",{"2":{"781":1,"783":1},"3":{"781":1,"783":1,"1259":1,"1630":1,"1631":2,"1633":3}}],["sessioncreatedhandler",{"2":{"780":1},"3":{"780":1}}],["sessioncreate",{"3":{"128":1,"1199":1,"1203":1,"1207":2,"1394":23,"1525":2,"1531":1,"1534":2}}],["sessioncleanuptestcontext",{"3":{"1199":2,"1207":1}}],["sessionclaimstoken",{"3":{"1184":2,"1185":1}}],["sessioncount",{"3":{"1349":1}}],["sessioncommandvalidatortests",{"3":{"1207":4,"1358":4}}],["sessionconfiguration",{"2":{"1360":1},"3":{"1082":1,"1199":1,"1203":1,"1207":2,"1349":1,"1358":2,"1360":2,"1361":7,"1368":6,"1495":1}}],["sessioncookieauthenticationextensions",{"2":{"1297":1},"3":{"1199":1,"1203":1,"1207":1,"1297":1,"1299":2}}],["sessioncookieauthenticationhandlertests",{"3":{"1199":1,"1207":2,"1299":1}}],["sessioncookieauthenticationhandler",{"2":{"207":1,"209":1,"1286":1,"1297":1,"1299":1,"1415":1,"1967":1,"1970":1,"1971":1},"3":{"0":1,"207":1,"209":1,"1199":3,"1203":1,"1207":3,"1208":1,"1286":1,"1297":7,"1299":14,"1415":1,"1967":1,"1970":1,"1971":1}}],["sessioncookierequest",{"2":{"1297":1},"3":{"1199":3,"1203":1,"1207":1,"1297":1,"1299":4}}],["sessioncookieendpointstests",{"3":{"1199":1,"1207":3,"1299":1}}],["sessioncookieendpoints",{"2":{"1297":1,"1967":1},"3":{"980":1,"1198":1,"1199":10,"1203":1,"1207":3,"1208":1,"1286":1,"1297":8,"1299":17,"1323":2,"1967":1}}],["sessioncookie",{"3":{"207":1,"1299":2,"1967":1}}],["sessioncookiesync",{"3":{"1323":2}}],["sessioncookiesamesite",{"3":{"1184":2}}],["sessioncookiesettings",{"2":{"1184":1},"3":{"1184":3}}],["sessioncookies",{"3":{"207":1,"980":1,"1108":1,"1183":2,"1184":3,"1203":12,"1207":46,"1208":3,"1297":6,"1299":48,"1323":1,"1495":1,"1581":1,"1967":1}}],["sessioncookiejartests",{"3":{"1199":1,"1207":2,"1299":1}}],["sessioncookiejar",{"2":{"207":1,"1967":1},"3":{"207":1,"1108":1,"1198":1,"1199":4,"1203":1,"1207":2,"1297":4,"1299":9,"1533":1,"1967":1}}],["sessioncategories",{"3":{"1349":2,"1394":1}}],["sessioncategoryitemid",{"3":{"1349":1,"1358":5}}],["sessioncategoryitemidentifiertype",{"3":{"1349":3,"1358":4,"1394":3}}],["sessioncategoryitemtests",{"3":{"1199":1,"1207":2}}],["sessioncategoryitemnavigationpopulatortests",{"3":{"1199":1,"1207":2,"1358":2}}],["sessioncategoryitemnavigationpopulator",{"3":{"1199":2,"1203":1,"1207":2,"1358":10}}],["sessioncategoryitemconfiguration",{"2":{"1361":1},"3":{"1199":1,"1203":1,"1207":2,"1361":2,"1368":6}}],["sessioncategoryitemchanged",{"2":{"1344":1,"1630":1,"1633":1},"3":{"1199":4,"1203":1,"1207":2,"1344":1,"1349":6,"1358":1,"1630":1,"1633":1}}],["sessioncategoryitemdtomappertests",{"3":{"1199":1,"1207":2,"1358":2}}],["sessioncategoryitemdtomapper",{"3":{"1199":9,"1203":1,"1207":2,"1358":9}}],["sessioncategoryitemdto",{"3":{"1199":10,"1203":1,"1207":2,"1346":1,"1349":10,"1358":2,"1379":1,"1394":4}}],["sessioncategoryitem",{"2":{"1341":1},"3":{"1198":1,"1199":17,"1203":1,"1207":2,"1237":2,"1341":3,"1349":12,"1358":16,"1368":3,"1379":1,"1394":2,"1629":1,"1630":1,"1631":2,"1633":2,"1634":3,"1636":1,"1637":3,"1638":3}}],["sessioncategoryitemscontrollertests",{"3":{"1199":1,"1207":2}}],["sessioncategoryitemscontroller",{"2":{"1370":1,"1371":1},"3":{"1199":3,"1203":1,"1207":3,"1349":2,"1358":22,"1370":2,"1371":2,"1372":2,"1373":1,"1379":17}}],["sessioncategoryitemservice",{"2":{"1385":1},"3":{"1199":2,"1203":1,"1207":1,"1323":1,"1385":1,"1394":4}}],["sessioncategoryitems",{"2":{"1115":1,"1362":1},"3":{"1115":1,"1323":1,"1349":3,"1358":14,"1362":1,"1368":2,"1379":2,"1394":1,"1446":1,"1629":2,"1630":1,"1631":2,"1634":2,"1637":1,"1638":2}}],["sessioncalendarcontroller",{"2":{"1370":1,"1372":1,"1374":1},"3":{"1199":1,"1203":1,"1207":2,"1370":2,"1372":2,"1374":3,"1379":6,"1435":1,"1495":1,"1534":1}}],["sessioncapexceeded",{"3":{"905":2,"1289":1,"1981":1}}],["sessioncancelled",{"2":{"785":1},"3":{"785":1}}],["sessionchanged",{"2":{"781":1,"783":1},"3":{"781":1,"782":3,"783":1,"1199":3,"1203":1,"1207":2,"1259":1,"1344":3,"1349":12,"1358":1,"1630":1,"1633":1}}],["sessioncheckin",{"2":{"691":1,"692":1},"3":{"690":1,"691":1,"692":1,"1395":5,"1414":1}}],["sessionincludechildrenregressiontests",{"3":{"1199":1,"1207":2,"1525":3}}],["sessioninvariantstests",{"3":{"1199":1,"1207":2}}],["sessioninvariants",{"2":{"1343":1,"1361":1,"1363":1},"3":{"1199":24,"1203":1,"1207":2,"1237":1,"1343":10,"1349":28,"1351":1,"1354":1,"1358":54,"1361":1,"1363":1,"1368":8,"1395":2}}],["sessioninfo",{"3":{"1199":14,"1203":1,"1207":1,"1395":12,"1401":10}}],["sessionizeid",{"3":{"1638":1}}],["sessionizeintegration",{"2":{"1351":1,"2135":1},"3":{"1346":1,"1349":4,"1351":1,"1358":2,"2135":2}}],["sessionizemodels",{"3":{"1207":9,"1357":1,"1358":22}}],["sessionizerefreshtests",{"3":{"1199":1,"1207":2,"1358":1}}],["sessionizerefreshoutcome",{"2":{"1381":1,"1386":1},"3":{"1199":3,"1203":1,"1207":2,"1381":2,"1386":1,"1394":10,"1525":1}}],["sessionizeresponse",{"2":{"1364":1},"3":{"1199":13,"1203":1,"1207":1,"1357":1,"1358":10,"1364":1,"1368":3}}],["sessionizeroom",{"3":{"1199":5,"1203":1,"1207":1,"1357":1,"1358":3}}],["sessionizeservicetests",{"3":{"1199":1,"1207":2,"1437":1}}],["sessionizeservice",{"2":{"1364":1},"3":{"1199":3,"1203":1,"1207":2,"1349":1,"1358":4,"1364":8,"1368":5,"1495":1}}],["sessionizesession",{"3":{"1199":5,"1203":1,"1207":1,"1357":1,"1358":4}}],["sessionizesynccontext",{"3":{"1199":13,"1203":1,"1207":2,"1357":1,"1358":14}}],["sessionizesyncwarnings",{"3":{"1199":5,"1203":1,"1207":2,"1357":1,"1358":10,"1495":1}}],["sessionizesyncresult",{"3":{"1199":8,"1203":1,"1207":1,"1349":1,"1357":1,"1358":5}}],["sessionizespeaker",{"3":{"1199":5,"1203":1,"1207":1,"1357":1,"1358":5}}],["sessionizecodemaxlength",{"3":{"1349":2,"1358":1}}],["sessionizecode",{"2":{"1635":1},"3":{"1349":5,"1352":1,"1358":14,"1361":1,"1368":8,"1394":8,"1629":2,"1630":1,"1631":5,"1635":1}}],["sessionizecodeattribute",{"2":{"1383":1},"3":{"1199":1,"1203":1,"1207":2,"1383":3,"1394":4,"1525":1}}],["sessionizecodeformattests",{"3":{"1199":1,"1207":2,"1349":1,"1436":1}}],["sessionizecodeformat",{"2":{"1352":2,"1364":1,"1630":1},"3":{"1199":8,"1203":1,"1207":2,"1346":7,"1349":10,"1352":2,"1358":12,"1364":1,"1368":2,"1383":1,"1394":3,"1495":2,"1525":1,"1630":1}}],["sessionizecategory",{"3":{"1199":5,"1203":1,"1207":1,"1357":1,"1358":4}}],["sessionizecategoryitem",{"3":{"1199":4,"1203":1,"1207":1,"1357":1,"1358":13}}],["sessionizequestionanswer",{"3":{"1199":6,"1203":1,"1207":1,"1357":1,"1358":6}}],["sessionizequestion",{"3":{"1199":4,"1203":1,"1207":1,"1357":1,"1358":3}}],["sessionizelink",{"3":{"1199":3,"1203":1,"1207":1,"1357":1,"1358":5}}],["sessionize",{"0":{"1357":1,"1364":1},"2":{"1207":1,"1358":1},"3":{"0":1,"344":1,"353":1,"470":1,"477":1,"798":1,"988":1,"1004":1,"1115":2,"1116":2,"1117":1,"1192":1,"1202":1,"1203":12,"1207":22,"1212":1,"1285":4,"1341":9,"1342":3,"1343":3,"1346":6,"1349":71,"1350":1,"1351":3,"1352":2,"1354":1,"1357":7,"1358":147,"1359":1,"1361":1,"1363":4,"1364":3,"1367":1,"1368":25,"1370":1,"1374":2,"1379":10,"1381":1,"1394":27,"1435":3,"1436":2,"1437":2,"1486":2,"1488":1,"1495":1,"1533":1,"1628":4,"1629":27,"1630":30,"1631":18,"1632":3,"1634":1,"1635":6,"1636":3,"1637":5,"1638":48,"1639":3,"1640":14,"2135":1}}],["sessionid=",{"3":{"1394":3,"1395":4,"1401":2,"1630":1,"1639":1}}],["sessionids=",{"3":{"1394":1}}],["sessionids",{"3":{"1358":6,"1379":2,"1394":3,"1395":6}}],["sessionidentifiertype",{"2":{"472":1,"799":1,"1055":1},"3":{"472":1,"799":2,"1055":1,"1259":1,"1349":23,"1358":41,"1379":6,"1394":19,"1395":36,"1401":26,"1414":2}}],["sessionid",{"2":{"1052":1,"1055":1,"1356":1},"3":{"0":2,"690":3,"799":1,"905":3,"996":1,"1052":1,"1055":1,"1116":2,"1212":1,"1259":1,"1299":13,"1310":2,"1323":3,"1341":2,"1344":1,"1349":28,"1356":1,"1358":80,"1361":4,"1368":19,"1379":30,"1394":52,"1395":117,"1397":2,"1400":1,"1401":53,"1414":4,"1435":1,"1437":1,"1466":1,"1525":1,"1534":1,"1626":1,"1629":2,"1630":6,"1631":7,"1633":3,"1634":4,"1637":1,"1638":1,"1639":2,"1640":2}}],["sessionastatus",{"3":{"1349":1}}],["sessionassetmalwarescanning",{"3":{"1466":1}}],["sessionassetrequests",{"3":{"1207":2,"1388":2,"1394":6}}],["sessionassetvalidationrules",{"3":{"1207":6,"1285":2,"1349":3,"1352":1,"1358":7}}],["sessionassettests",{"3":{"1199":1,"1207":2,"1349":2}}],["sessionassettitlerules",{"3":{"1199":2,"1203":1,"1207":1,"1358":5}}],["sessionassetbuilder",{"3":{"1199":2,"1207":2}}],["sessionassetfixtures",{"3":{"1199":6,"1207":2}}],["sessionassetfieldrules",{"3":{"1199":4,"1203":1,"1207":1,"1352":1,"1358":11}}],["sessionassetfilenamerules",{"3":{"1199":2,"1203":1,"1207":1,"1358":3}}],["sessionasseturlrules",{"3":{"1199":2,"1203":1,"1207":1,"1358":5}}],["sessionassetupdaterequestvalidatortests",{"3":{"1199":1,"1207":2}}],["sessionassetupdaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1349":1,"1358":6}}],["sessionassetupdaterequest",{"2":{"1388":1},"3":{"1199":11,"1203":2,"1207":3,"1358":10,"1379":1,"1388":1,"1394":4}}],["sessionassetdisplay",{"3":{"1199":3,"1203":1,"1207":2,"1388":3,"1394":6,"1495":1}}],["sessionassetdto",{"3":{"1199":18,"1203":1,"1207":2,"1346":3,"1349":23,"1358":5,"1379":1,"1394":7}}],["sessionassetdtomappertests",{"3":{"1199":1,"1207":2,"1358":2}}],["sessionassetdtomapper",{"2":{"3":1},"3":{"3":1,"1199":9,"1203":1,"1207":2,"1349":1,"1356":1,"1358":14}}],["sessionassetlinkrequestvalidatortests",{"3":{"1199":1,"1207":2}}],["sessionassetlinkrequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1358":7,"1394":2}}],["sessionassetlinkrequest",{"2":{"1388":1},"3":{"1199":13,"1203":2,"1207":3,"1358":12,"1379":1,"1388":1,"1394":8}}],["sessionassetlimits",{"2":{"1118":1,"1356":1,"1372":1,"1388":1},"3":{"1116":3,"1118":1,"1199":14,"1203":1,"1207":2,"1285":2,"1346":8,"1349":13,"1356":1,"1358":9,"1372":1,"1379":4,"1388":3,"1394":7}}],["sessionassetid",{"3":{"1349":2}}],["sessionassetidentifiertype",{"2":{"468":1,"470":1,"472":1,"477":1,"478":1,"798":1,"805":1,"1049":1,"1054":1},"3":{"468":1,"470":1,"472":1,"477":3,"478":1,"798":1,"805":1,"1049":1,"1054":1,"1212":1,"1349":3,"1358":4}}],["sessionassetinvariantstests",{"3":{"1199":1,"1207":2,"1349":2}}],["sessionassetinvariants",{"2":{"1343":1,"1368":1},"3":{"1116":2,"1199":11,"1203":1,"1207":2,"1343":7,"1349":6,"1358":9,"1368":5,"1495":1}}],["sessionassetkind",{"2":{"1341":1},"3":{"1116":1,"1199":14,"1203":1,"1207":2,"1341":3,"1349":12,"1394":3}}],["sessionassetcomposer",{"3":{"1199":2,"1203":1,"1207":2,"1388":2,"1394":5,"1495":1}}],["sessionassetconfiguration",{"2":{"1360":1},"3":{"1116":1,"1199":1,"1203":1,"1207":2,"1349":2,"1360":1,"1361":6,"1368":5,"1495":2}}],["sessionassetchanged",{"2":{"780":1,"1344":1},"3":{"780":1,"782":1,"1199":3,"1203":1,"1207":2,"1344":3,"1349":7,"1495":1}}],["sessionassetstests",{"3":{"1199":1,"1207":2}}],["sessionassetsortorderrules",{"3":{"1199":2,"1203":1,"1207":1,"1358":7}}],["sessionassetsessionidrules",{"3":{"1199":3,"1203":1,"1207":1,"1358":6}}],["sessionassetservice",{"3":{"881":1,"1199":3,"1203":1,"1207":2,"1349":2,"1388":2,"1394":9,"1495":2}}],["sessionassetspaneltests",{"3":{"1199":1,"1207":2}}],["sessionassetspanelpage",{"3":{"1199":2,"1207":2}}],["sessionassetspanel",{"3":{"1116":1,"1199":2,"1203":1,"1207":2,"1349":4,"1388":4,"1394":16,"1495":2,"1525":1,"1530":1}}],["sessionassetscontrollertests",{"3":{"1199":1,"1207":2,"1379":1,"1486":1}}],["sessionassetscontroller",{"2":{"1372":1},"3":{"1116":4,"1199":3,"1203":1,"1207":2,"1349":2,"1358":13,"1370":2,"1372":4,"1379":3,"1435":2,"1495":2}}],["sessionassetsdownloadlisttests",{"3":{"1199":1,"1207":2}}],["sessionassetsdownloadlist",{"2":{"1116":1,"1388":1},"3":{"1116":1,"1199":2,"1203":1,"1207":2,"1349":1,"1388":3,"1394":9,"1495":1}}],["sessionassetsmanage",{"2":{"184":1},"3":{"184":1,"1349":5,"1372":2,"1379":2}}],["sessionassets",{"2":{"1004":1,"1207":1,"1356":1,"1379":1,"1394":2},"3":{"0":1,"536":2,"881":1,"1004":1,"1116":12,"1203":45,"1207":126,"1264":1,"1285":6,"1317":1,"1322":2,"1341":2,"1343":1,"1344":1,"1346":2,"1349":17,"1352":1,"1356":4,"1358":73,"1362":1,"1368":6,"1370":1,"1372":1,"1379":5,"1388":8,"1394":31,"1446":2,"1486":1,"1629":1,"1630":5,"1639":5}}],["sessionassetaccessservicetests",{"3":{"1199":1,"1207":2,"1358":2}}],["sessionassetaccessservice",{"2":{"552":1,"1116":1},"3":{"0":1,"552":1,"1116":4,"1199":2,"1203":1,"1207":2,"1349":3,"1356":3,"1358":9,"1495":1}}],["sessionasset",{"2":{"1114":1,"1116":1,"1345":1,"1346":1,"1351":1,"1356":1,"1388":4},"3":{"0":1,"1114":1,"1116":4,"1199":23,"1203":1,"1207":2,"1212":1,"1339":1,"1341":9,"1342":7,"1343":3,"1344":1,"1345":2,"1346":1,"1349":23,"1351":1,"1356":1,"1358":45,"1368":4,"1388":4,"1394":2,"1435":1,"1495":2}}],["sessionatitle",{"3":{"1349":1}}],["sessionattendance",{"3":{"1395":3}}],["sessionattendancerow",{"3":{"1199":2,"1203":1,"1207":1,"1395":4}}],["sessionattendancedto",{"3":{"1199":6,"1203":1,"1207":2,"1395":10}}],["sessionaid",{"3":{"1349":1}}],["sessionaiscoreconfiguration",{"3":{"1199":1,"1203":1,"1207":2,"1349":1,"1358":1,"1361":4,"1368":4,"1495":1}}],["sessionaiscoretests",{"3":{"1199":1,"1207":2}}],["sessionaiscoredto",{"3":{"1199":9,"1203":1,"1207":3,"1346":2,"1349":8,"1358":1,"1394":3,"1495":2}}],["sessionaiscoreidentifiertype",{"2":{"468":1,"477":1},"3":{"468":1,"477":1,"1349":1}}],["sessionaiscore",{"2":{"1018":2},"3":{"0":1,"1018":3,"1199":7,"1203":1,"1207":2,"1341":8,"1346":1,"1349":10,"1358":11,"1362":1,"1368":9,"1495":2}}],["sessionawareauthenticationservice",{"3":{"1199":2,"1207":1}}],["sessionaccessibilityinforules",{"3":{"1199":2,"1203":1,"1207":1,"1358":7}}],["sessionsnext",{"3":{"1395":1}}],["sessionsnow",{"3":{"1395":1}}],["sessionsnavrequiredrole",{"2":{"186":1,"190":1},"3":{"186":1,"190":1,"905":1,"909":1,"1323":1,"1626":1,"1760":1}}],["sessionsfailed",{"3":{"1349":1}}],["sessionsscored",{"3":{"1349":1,"1389":1,"1394":5}}],["sessionssynced",{"3":{"1349":1,"1358":1}}],["sessionsmanage",{"3":{"1349":2,"1358":8,"1372":1,"1379":4}}],["sessionsyncstrategytests",{"3":{"1199":1,"1207":2,"1358":1}}],["sessionsyncstrategy",{"3":{"1199":3,"1203":1,"1207":2,"1357":5,"1358":9,"1495":1}}],["sessionscachetag",{"3":{"1368":2,"1395":2}}],["sessionscache",{"3":{"1349":1,"1358":1,"1372":2,"1374":1,"1378":1,"1379":6}}],["sessionschedulepagerequest",{"2":{"1390":1},"3":{"1199":4,"1203":1,"1207":1,"1390":1,"1394":7}}],["sessionscorestamp",{"3":{"1495":2}}],["sessionscored",{"3":{"1259":1}}],["sessionscoreresponseguardrailtests",{"2":{"1434":1},"3":{"1199":1,"1207":3,"1368":2,"1434":2,"1436":1,"1437":1,"1486":1}}],["sessionscoreresponseguardrail",{"2":{"1018":1,"1021":1,"1094":1,"1365":1},"3":{"1018":3,"1021":3,"1094":1,"1199":5,"1203":1,"1207":2,"1365":4,"1368":11,"1426":2,"1495":1,"1525":2,"1534":1}}],["sessionscoringcandidate",{"3":{"1495":2}}],["sessionscoringcontract",{"2":{"1365":1,"1367":1,"1368":1,"1491":1},"3":{"1365":1,"1367":1,"1368":3,"1491":1}}],["sessionscoringworkitem",{"3":{"1495":2}}],["sessionscoringenqueueresult",{"3":{"1495":2}}],["sessionscoringsweepjob",{"3":{"1270":1,"1495":2}}],["sessionscoringservicetests",{"2":{"1434":1},"3":{"1199":1,"1207":3,"1368":1,"1434":2,"1436":1,"1437":1}}],["sessionscoringservice",{"2":{"1016":1,"1018":1,"1021":1,"1094":1,"1095":1,"1365":2,"1368":1,"1491":1},"3":{"1016":1,"1017":1,"1018":11,"1021":2,"1090":1,"1093":1,"1094":2,"1095":2,"1199":9,"1203":1,"1207":2,"1346":1,"1349":1,"1358":9,"1365":11,"1368":19,"1426":3,"1434":1,"1437":2,"1491":1,"1495":1,"1525":8}}],["sessionscoringinput",{"3":{"1199":10,"1203":1,"1207":1,"1358":7,"1368":1}}],["sessionscoringresult",{"3":{"1199":6,"1203":1,"1207":1,"1358":5,"1365":1,"1368":4}}],["sessionscoringrunnertests",{"3":{"1199":1,"1207":2,"1358":2,"1437":2}}],["sessionscoringrunner",{"3":{"1082":1,"1086":1,"1199":2,"1203":1,"1207":2,"1285":1,"1358":22,"1435":1,"1437":1,"1495":3,"1525":1}}],["sessionscoring",{"2":{"1018":1,"1021":1,"1099":1,"1103":1},"3":{"1018":1,"1021":1,"1099":1,"1103":1,"1346":1,"1349":4,"1358":2,"1379":2,"1495":1}}],["sessionscoringpromptcontractpintests",{"2":{"1018":1,"1425":1,"1427":1,"1434":1},"3":{"1018":1,"1199":1,"1207":1,"1425":1,"1427":1,"1434":1}}],["sessionscoringprocessortests",{"3":{"1495":1}}],["sessionscoringprocessor",{"2":{"491":1,"492":1,"493":1,"518":1},"3":{"491":2,"492":3,"493":2,"518":1,"1441":2,"1495":4}}],["sessionscoringqueue",{"2":{"518":1},"3":{"0":2,"518":1,"1495":4}}],["sessionscontrollertests",{"3":{"1199":1,"1207":2}}],["sessionscontroller",{"2":{"543":1,"552":1},"3":{"543":2,"545":2,"546":1,"550":1,"552":1,"743":2,"1199":3,"1203":1,"1207":2,"1247":7,"1310":6,"1349":2,"1358":45,"1370":5,"1371":4,"1372":4,"1373":1,"1374":9,"1379":10,"1394":2,"1435":4,"1495":1,"1525":1,"1533":1}}],["sessionsimilaritycalculatortests",{"3":{"1199":1,"1207":2,"1358":2}}],["sessionsimilaritycalculator",{"3":{"1199":3,"1203":1,"1207":2,"1358":7}}],["sessionspeakerids",{"3":{"1358":1}}],["sessionspeakerid",{"3":{"1349":1,"1358":5}}],["sessionspeakeridentifiertype",{"3":{"1349":3,"1358":4,"1394":3}}],["sessionspeakertests",{"3":{"1199":1,"1207":2}}],["sessionspeakernavigationpopulatortests",{"3":{"1199":1,"1207":2,"1358":2}}],["sessionspeakernavigationpopulator",{"3":{"1199":2,"1203":1,"1207":2,"1358":5}}],["sessionspeakerconfiguration",{"3":{"1199":1,"1203":1,"1207":2,"1361":2,"1368":8}}],["sessionspeakerchanged",{"2":{"1344":1,"1633":1},"3":{"1199":4,"1203":1,"1207":2,"1344":1,"1349":6,"1358":2,"1630":1,"1633":1}}],["sessionspeakerdtomappertests",{"3":{"1199":1,"1207":2,"1358":2}}],["sessionspeakerdtomapper",{"3":{"1199":9,"1203":1,"1207":2,"1358":8}}],["sessionspeakerdto",{"3":{"1199":9,"1203":1,"1207":2,"1346":1,"1349":9,"1358":2,"1379":1,"1394":3}}],["sessionspeaker",{"3":{"1198":1,"1199":24,"1203":1,"1207":2,"1237":4,"1323":1,"1341":3,"1349":8,"1352":1,"1358":38,"1363":2,"1368":5,"1379":1,"1394":5,"1437":1,"1629":1,"1630":2,"1631":2,"1633":2,"1634":3,"1636":1,"1637":3,"1638":4,"1639":1}}],["sessionspeakerscontrollertests",{"3":{"1199":1,"1207":2}}],["sessionspeakerscontroller",{"2":{"1370":1,"1371":1},"3":{"1199":3,"1203":1,"1207":3,"1310":2,"1349":2,"1358":23,"1370":2,"1371":2,"1372":2,"1373":1,"1379":17}}],["sessionspeakerservice",{"2":{"1385":1},"3":{"1199":1,"1203":1,"1207":1,"1323":1,"1385":1,"1394":3}}],["sessionspeakers",{"2":{"1115":1,"1356":1,"1362":1},"3":{"1115":1,"1116":1,"1323":1,"1349":4,"1356":1,"1358":21,"1362":1,"1368":4,"1379":2,"1394":6,"1446":1,"1525":1,"1629":2,"1630":1,"1631":2,"1634":3,"1637":1,"1638":3}}],["sessionspagee2etests",{"3":{"618":2,"906":1,"909":1,"1199":1,"1207":2,"1323":1,"1435":5,"1437":1}}],["sessionstatusrules",{"3":{"1199":2,"1203":1,"1207":1,"1358":3}}],["sessionstatusdisplay",{"3":{"1199":1,"1203":1,"1207":2,"1384":2,"1394":6,"1495":1}}],["sessionstatuses",{"3":{"1199":23,"1203":1,"1207":2,"1343":7,"1349":10,"1352":1,"1355":1,"1358":40,"1394":6,"1395":2,"1495":2,"1534":1}}],["sessionstampingtokenservice",{"2":{"1286":1,"1289":1},"3":{"905":1,"1199":1,"1203":1,"1207":1,"1286":1,"1289":1,"1299":5,"1495":1}}],["sessionstests",{"3":{"906":1,"1199":1,"1207":2,"1323":1,"1435":1,"1437":2}}],["sessionstorage",{"2":{"2075":1},"3":{"881":1,"1323":7,"1576":1,"1714":1,"2075":1}}],["sessionset",{"3":{"1323":1}}],["sessionservice",{"3":{"881":1,"1199":8,"1203":1,"1207":2,"1323":1,"1381":3,"1394":7,"1395":1}}],["sessionselectiontests",{"3":{"1199":1,"1207":2}}],["sessionselectionstaleresponsetests",{"3":{"1199":1,"1207":2,"1394":2,"1437":2}}],["sessionselectionspeakeroverlaptests",{"3":{"1199":1,"1207":2,"1394":1}}],["sessionselectionspeakeroverlap",{"2":{"1384":1},"3":{"1199":2,"1203":1,"1207":2,"1384":3,"1394":4}}],["sessionselectionservicetests",{"3":{"1199":1,"1207":2}}],["sessionselectionservice",{"2":{"1389":1},"3":{"881":1,"1199":2,"1203":1,"1207":2,"1358":4,"1381":1,"1389":4,"1394":9}}],["sessionselectionfilters",{"2":{"1384":1},"3":{"1199":2,"1203":1,"1207":2,"1384":2,"1394":7}}],["sessionselectionfilteroptions",{"2":{"1384":1},"3":{"1199":2,"1203":1,"1207":2,"1384":2,"1394":4,"1495":1}}],["sessionselectionaiscorestests",{"3":{"1199":1,"1207":2,"1394":2,"1437":1}}],["sessionselectionaiscores",{"2":{"1384":1},"3":{"1199":2,"1203":1,"1207":2,"1384":3,"1394":6}}],["sessionselectiondashboard",{"2":{"1389":1,"1534":1},"3":{"1199":3,"1203":1,"1207":2,"1349":2,"1358":1,"1389":2,"1394":29,"1534":1}}],["sessionselectiondashboardtests",{"3":{"1199":2,"1207":4,"1394":2}}],["sessionselectiondashboarddto",{"2":{"1346":1,"1389":1},"3":{"1199":16,"1203":1,"1207":3,"1346":5,"1349":8,"1358":2,"1379":1,"1389":1,"1394":10}}],["sessionselectiondisplay",{"2":{"1384":1},"3":{"1199":3,"1203":1,"1207":2,"1384":2,"1394":15}}],["sessionselectioncontrollertests",{"3":{"1199":1,"1207":3}}],["sessionselectioncontroller",{"2":{"1370":1,"1375":1,"1378":1,"1389":1},"3":{"1018":1,"1021":1,"1099":1,"1103":1,"1199":2,"1203":1,"1207":2,"1310":1,"1346":3,"1349":6,"1358":19,"1366":1,"1368":1,"1370":2,"1372":1,"1375":2,"1378":1,"1379":10,"1389":2,"1394":3,"1495":2,"1525":1,"2189":1}}],["sessionselectionmanage",{"2":{"1018":1,"1264":1,"1375":1,"1379":1},"3":{"1018":1,"1264":1,"1349":1,"1358":4,"1372":1,"1375":1,"1379":2,"1525":1}}],["sessionselection",{"3":{"837":1,"839":1,"841":1,"1018":1,"1358":9,"1394":8,"1437":1,"1446":1,"1495":1,"1631":2,"1639":5}}],["sessions",{"0":{"1282":1,"1289":1,"2238":1},"1":{"456":1,"457":1,"458":1,"459":1,"460":1,"461":1,"462":1,"463":1,"464":1,"465":1,"466":1,"902":1,"903":1,"904":1,"905":1,"906":1,"907":1,"908":1,"909":1,"910":1},"2":{"1349":1,"1379":1,"1394":2},"3":{"0":9,"128":1,"186":2,"187":1,"325":1,"353":1,"456":1,"492":1,"493":2,"494":1,"495":1,"497":4,"501":1,"517":1,"543":2,"545":3,"550":1,"554":1,"556":1,"583":1,"616":2,"618":3,"666":1,"690":1,"743":3,"782":2,"827":1,"852":1,"857":1,"862":1,"881":2,"902":1,"903":1,"905":12,"906":1,"907":4,"908":1,"909":2,"910":4,"926":1,"995":1,"1004":2,"1017":1,"1018":6,"1082":3,"1085":1,"1090":1,"1099":2,"1103":1,"1116":3,"1192":1,"1199":14,"1202":2,"1203":175,"1207":526,"1212":1,"1237":5,"1239":2,"1247":9,"1264":2,"1270":7,"1271":2,"1282":2,"1285":16,"1286":2,"1289":5,"1292":1,"1296":2,"1299":37,"1300":1,"1309":1,"1310":6,"1311":1,"1322":4,"1323":54,"1339":1,"1341":3,"1343":3,"1344":1,"1345":1,"1346":10,"1347":2,"1348":5,"1349":137,"1350":2,"1351":9,"1352":4,"1353":1,"1354":2,"1355":4,"1356":1,"1357":2,"1358":577,"1360":1,"1361":2,"1362":1,"1363":2,"1364":1,"1365":3,"1366":4,"1368":42,"1370":3,"1372":2,"1374":5,"1379":69,"1380":2,"1381":3,"1382":2,"1383":3,"1384":13,"1385":1,"1387":3,"1388":2,"1389":12,"1390":11,"1394":149,"1395":42,"1401":6,"1404":1,"1408":2,"1414":20,"1415":2,"1416":1,"1426":3,"1432":1,"1435":28,"1436":1,"1437":17,"1440":1,"1441":2,"1446":1,"1457":1,"1466":3,"1478":1,"1488":1,"1495":6,"1525":8,"1530":2,"1534":2,"1576":1,"1581":1,"1582":1,"1585":1,"1590":1,"1599":1,"1607":2,"1620":1,"1623":1,"1624":1,"1626":11,"1628":9,"1629":11,"1630":16,"1631":29,"1632":3,"1634":10,"1635":2,"1636":8,"1637":10,"1638":12,"1639":9,"1640":6,"1666":6,"1668":1,"1671":1,"1751":1,"1760":3,"1762":1,"1812":1,"1814":3,"1858":1,"1921":1,"1946":1,"1960":1,"1961":1,"1964":1,"1981":1,"1984":2,"1994":1,"2011":1,"2062":1,"2069":1,"2109":2,"2189":1,"2215":1,"2231":1,"2237":1,"2239":1,"2242":1}}],["session",{"0":{"1297":1,"1356":1,"1388":1,"1389":1,"1981":1,"1982":1,"2065":1},"1":{"204":1,"205":1,"206":1,"207":1,"208":1,"209":1,"210":1,"1015":1,"1016":1,"1017":1,"1018":1,"1019":1,"1020":1,"1021":1,"1022":1,"1113":1,"1114":1,"1115":1,"1116":1,"1117":1,"1118":1,"1119":1,"1120":1,"1395":1,"1396":1,"1397":1,"1398":1,"1399":1,"1400":1,"1401":1,"1965":1,"1966":1,"1967":1,"1968":1,"1969":1,"1970":1,"1971":1},"2":{"164":1,"780":1,"781":1,"790":2,"1115":2,"1116":1,"1117":1,"1188":1,"1341":1,"1342":1,"1345":1,"1629":1,"1630":1,"1632":1,"1634":1,"1637":1,"1639":1,"1764":1,"1769":2,"1853":1,"1856":1,"1857":1,"1858":1,"1859":1},"3":{"0":42,"57":1,"59":1,"160":1,"164":1,"165":1,"168":1,"170":1,"185":2,"186":1,"204":1,"207":6,"208":2,"209":2,"210":1,"221":1,"243":2,"261":1,"281":1,"285":1,"287":1,"340":1,"348":1,"350":2,"351":1,"355":1,"379":1,"380":2,"383":1,"384":1,"387":2,"390":1,"443":1,"458":1,"462":1,"497":2,"498":1,"499":4,"500":2,"501":4,"503":1,"506":1,"507":3,"508":2,"509":1,"510":2,"514":1,"516":1,"517":2,"519":1,"524":1,"534":3,"536":11,"540":3,"545":2,"551":1,"555":1,"560":1,"583":3,"664":2,"665":1,"670":1,"689":4,"690":11,"691":1,"692":1,"693":1,"748":1,"756":1,"780":1,"781":2,"782":2,"788":1,"790":4,"793":5,"794":1,"854":1,"857":1,"861":1,"864":1,"897":4,"903":4,"904":2,"905":18,"906":5,"907":1,"908":2,"909":1,"910":1,"926":2,"954":2,"995":1,"996":2,"1012":1,"1015":1,"1016":2,"1017":4,"1018":2,"1020":1,"1059":1,"1082":2,"1089":2,"1090":3,"1091":1,"1099":2,"1100":1,"1101":1,"1107":1,"1108":1,"1113":1,"1115":4,"1116":21,"1117":7,"1118":4,"1124":1,"1125":1,"1129":1,"1150":2,"1182":1,"1183":1,"1184":9,"1185":3,"1187":3,"1188":3,"1189":1,"1192":6,"1198":1,"1199":127,"1201":1,"1202":6,"1203":7,"1207":2,"1212":8,"1237":6,"1238":1,"1239":6,"1247":10,"1270":5,"1271":2,"1273":1,"1282":4,"1283":2,"1285":25,"1286":3,"1288":2,"1289":9,"1297":1,"1299":114,"1300":1,"1301":1,"1302":1,"1304":2,"1306":2,"1308":10,"1310":11,"1311":2,"1315":1,"1317":1,"1321":1,"1322":11,"1323":73,"1338":2,"1339":4,"1341":30,"1342":7,"1343":6,"1345":7,"1346":4,"1347":6,"1348":9,"1349":250,"1350":4,"1351":8,"1352":3,"1353":1,"1354":7,"1355":1,"1356":14,"1358":741,"1359":2,"1360":2,"1361":5,"1362":1,"1363":4,"1365":7,"1366":1,"1368":70,"1370":2,"1372":9,"1373":2,"1374":5,"1375":2,"1376":2,"1377":3,"1379":70,"1380":2,"1384":4,"1385":1,"1386":2,"1387":4,"1388":12,"1389":6,"1390":3,"1394":249,"1395":353,"1396":5,"1398":7,"1399":2,"1400":6,"1401":167,"1404":3,"1405":4,"1407":1,"1409":1,"1414":38,"1415":6,"1416":11,"1426":2,"1427":1,"1432":1,"1434":3,"1435":44,"1436":6,"1437":42,"1440":1,"1441":1,"1446":4,"1454":2,"1460":1,"1462":1,"1466":12,"1474":1,"1478":1,"1484":1,"1486":10,"1487":3,"1488":1,"1491":1,"1495":19,"1497":1,"1499":1,"1510":2,"1523":1,"1525":7,"1526":1,"1530":2,"1533":7,"1534":1,"1548":1,"1555":1,"1562":1,"1581":1,"1584":2,"1585":1,"1590":1,"1607":1,"1610":2,"1620":2,"1622":1,"1624":1,"1625":1,"1626":7,"1628":7,"1629":35,"1630":63,"1631":55,"1632":4,"1633":8,"1634":20,"1635":1,"1636":10,"1637":36,"1638":46,"1639":31,"1640":7,"1659":1,"1663":1,"1666":6,"1668":2,"1671":1,"1745":1,"1748":4,"1749":1,"1753":1,"1758":1,"1760":1,"1763":2,"1764":6,"1767":4,"1768":1,"1769":3,"1778":1,"1789":1,"1803":1,"1812":1,"1814":3,"1831":1,"1853":1,"1856":2,"1857":1,"1858":3,"1859":1,"1868":3,"1869":1,"1920":1,"1921":1,"1934":1,"1940":1,"1943":1,"1944":1,"1959":2,"1964":1,"1965":1,"1966":2,"1967":4,"1970":3,"1971":4,"1972":3,"1973":1,"1974":1,"1976":1,"1978":1,"1979":1,"1980":2,"1981":11,"1982":3,"1983":4,"1984":5,"1994":1,"2001":1,"2075":1,"2077":1,"2078":1,"2109":4,"2125":1,"2126":1,"2157":1,"2162":3,"2166":4,"2178":1,"2179":1,"2189":4,"2207":2,"2231":6,"2238":1}}],["semicolons",{"3":{"1285":1}}],["semicolon",{"3":{"219":1,"1270":4,"1285":2,"1299":1,"1310":1,"1323":4,"1338":1,"1414":2,"1435":4,"2149":1}}],["semanticproperties",{"3":{"1416":2}}],["semantically",{"3":{"1285":2,"1358":1,"1379":1,"1435":1,"1638":1}}],["semantic",{"0":{"1732":1},"3":{"684":1,"1196":1,"1237":1,"1249":1,"1259":1,"1270":1,"1271":1,"1299":1,"1323":1,"1358":1,"1395":1,"1416":2,"1424":1,"1499":1,"1525":2,"1533":1,"1557":1,"1568":1,"1576":2,"1584":1,"1590":1,"2176":1}}],["semanticscreenreader",{"3":{"1416":5}}],["semantics",{"3":{"0":5,"24":1,"40":1,"61":1,"115":1,"119":1,"120":1,"121":1,"202":1,"242":1,"247":1,"265":1,"310":1,"389":1,"434":1,"513":1,"618":1,"641":1,"658":1,"698":1,"733":1,"734":1,"735":1,"840":1,"842":1,"855":1,"906":1,"923":1,"946":1,"996":1,"1012":1,"1020":1,"1042":1,"1043":1,"1050":1,"1076":1,"1150":1,"1152":2,"1212":1,"1229":1,"1237":4,"1247":3,"1259":5,"1260":2,"1263":1,"1270":1,"1285":6,"1299":7,"1308":4,"1310":5,"1322":2,"1323":16,"1338":2,"1349":2,"1358":13,"1368":1,"1378":1,"1379":3,"1394":5,"1395":14,"1401":2,"1414":7,"1416":6,"1435":11,"1437":2,"1445":1,"1454":2,"1466":1,"1487":1,"1491":1,"1546":1,"1557":1,"1576":2,"1585":1,"1590":1,"1630":3,"1637":2,"1639":1,"1663":1,"1758":1,"1788":1,"1791":1,"1820":1,"1824":1,"1825":2,"1846":1,"1943":1,"1990":1,"2026":1,"2058":1,"2064":1,"2181":1,"2187":1,"2191":1,"2202":1}}],["semaphores",{"3":{"1270":2,"1299":2}}],["semaphoreslim",{"2":{"436":1,"995":1,"1254":1,"1773":1,"1908":1},"3":{"157":1,"434":1,"436":1,"995":1,"1254":1,"1259":6,"1270":3,"1275":1,"1285":5,"1299":8,"1323":3,"1331":1,"1338":3,"1773":1,"1908":1}}],["semaphorefullexception",{"3":{"1259":1,"1275":1,"1285":1}}],["semaphore",{"3":{"155":1,"157":3,"159":1,"830":1,"996":1,"1259":4,"1267":1,"1270":6,"1275":1,"1285":9,"1297":1,"1299":8,"1300":1,"1310":3,"1322":2,"1323":1,"1331":1,"1338":4,"1576":1,"1584":1,"1908":2}}],["semver",{"3":{"147":1,"1575":1,"1576":3,"1672":1,"1673":1,"1732":1}}],["separable",{"3":{"126":1,"1395":1,"1435":1}}],["separators",{"3":{"741":1,"1116":1,"1285":2,"1299":3,"1322":1,"1323":1,"1395":1}}],["separator",{"3":{"599":1,"741":2,"743":1,"1228":1,"1229":1,"1247":3,"1285":1,"1295":1,"1300":1,"1308":1,"1323":4,"1334":1,"1338":1,"1394":4,"1441":1,"1466":1,"1485":1,"1684":1}}],["separation",{"3":{"0":1,"642":1,"868":1,"999":2,"1050":1,"1230":1,"1270":1,"1285":2,"1299":3,"1308":1,"1322":2,"1323":2,"1358":1,"1380":1,"1394":2,"1395":2,"1401":2,"1416":2,"1435":1,"1466":1,"1525":1,"1544":1,"1554":1,"1576":1,"1637":1,"1638":1,"1814":1,"1816":1}}],["separating",{"3":{"0":1,"1185":1,"1229":1,"1259":1,"1285":1,"1299":4,"1301":1,"1308":1,"1310":2,"1323":4,"1349":1,"1358":4,"1368":1,"1379":2,"1394":3,"1395":1,"1401":3,"1416":1,"1426":1,"1435":1,"1477":1,"1488":1,"1576":1,"1637":2,"1952":1,"2006":1,"2068":1}}],["separates",{"3":{"395":1,"418":1,"426":1,"862":1,"905":1,"998":2,"1237":1,"1247":1,"1263":1,"1270":1,"1285":1,"1299":1,"1310":1,"1322":1,"1323":2,"1358":1,"1395":1,"1401":1,"1414":1,"1415":1,"1435":2,"1454":1,"1461":1,"1793":1,"1981":1,"2033":1,"2062":1,"2142":1}}],["separately",{"3":{"224":1,"235":1,"265":1,"275":1,"296":1,"322":1,"518":2,"549":1,"626":1,"665":1,"676":1,"684":1,"707":1,"715":1,"724":1,"785":1,"789":1,"838":2,"881":1,"883":1,"1049":1,"1061":1,"1100":1,"1169":1,"1231":1,"1237":2,"1243":1,"1247":1,"1259":4,"1270":1,"1271":3,"1285":3,"1299":2,"1304":1,"1308":1,"1309":1,"1310":1,"1315":1,"1323":5,"1333":1,"1338":4,"1349":5,"1358":15,"1379":1,"1394":1,"1395":4,"1401":2,"1414":4,"1416":3,"1426":1,"1428":1,"1435":13,"1437":1,"1440":1,"1441":1,"1442":1,"1466":4,"1475":2,"1486":1,"1488":1,"1553":1,"1638":1,"1639":1,"1674":1,"1814":1,"1836":1,"1886":1,"1937":1,"2000":1,"2001":1,"2062":1,"2160":1,"2192":1}}],["separated",{"3":{"26":1,"330":1,"633":2,"757":1,"974":1,"1012":1,"1212":1,"1247":4,"1253":1,"1259":1,"1260":1,"1270":1,"1271":1,"1285":2,"1299":4,"1309":1,"1310":3,"1317":1,"1322":1,"1323":3,"1338":2,"1395":2,"1401":1,"1416":2,"1435":1,"1454":1,"1525":2,"1542":1,"1544":1,"1629":1,"1726":1,"1987":1,"2181":1}}],["separate",{"0":{"1836":1,"2062":1},"3":{"0":9,"24":1,"30":1,"35":1,"39":1,"52":1,"70":1,"97":1,"109":1,"117":1,"136":1,"165":2,"168":3,"186":1,"188":1,"201":1,"237":2,"255":1,"291":1,"317":1,"318":1,"319":1,"320":1,"361":1,"363":1,"390":1,"391":1,"403":1,"509":1,"511":1,"532":1,"551":1,"558":1,"572":2,"579":1,"602":1,"634":1,"640":1,"651":1,"665":1,"676":1,"690":1,"698":1,"707":1,"731":1,"742":1,"751":1,"782":1,"784":1,"790":1,"829":2,"838":1,"862":1,"869":1,"881":1,"882":1,"905":1,"971":1,"980":1,"1006":1,"1018":1,"1019":2,"1025":2,"1042":1,"1043":2,"1045":1,"1049":1,"1050":1,"1059":2,"1067":1,"1068":1,"1116":3,"1123":1,"1124":1,"1125":2,"1170":1,"1174":1,"1178":1,"1193":1,"1212":4,"1237":5,"1247":7,"1248":1,"1259":2,"1262":1,"1267":1,"1270":9,"1271":4,"1273":1,"1279":1,"1285":16,"1292":1,"1299":13,"1300":4,"1301":1,"1308":10,"1309":1,"1310":7,"1311":1,"1312":1,"1314":1,"1322":13,"1323":26,"1324":1,"1338":11,"1341":1,"1345":1,"1348":1,"1349":19,"1351":2,"1354":1,"1358":39,"1368":3,"1373":1,"1379":1,"1383":1,"1392":1,"1394":20,"1395":31,"1397":1,"1400":2,"1401":17,"1414":18,"1415":3,"1416":10,"1426":5,"1432":1,"1435":28,"1436":1,"1437":1,"1440":2,"1441":1,"1443":1,"1446":1,"1448":1,"1454":1,"1455":1,"1456":1,"1459":1,"1464":1,"1466":6,"1472":1,"1477":1,"1489":2,"1491":3,"1511":1,"1516":1,"1544":1,"1598":1,"1604":1,"1615":1,"1616":1,"1625":1,"1626":1,"1629":3,"1630":2,"1631":1,"1633":1,"1637":1,"1638":1,"1639":1,"1651":1,"1661":2,"1664":1,"1692":1,"1695":1,"1730":3,"1738":1,"1747":1,"1748":1,"1754":1,"1764":1,"1766":1,"1767":1,"1789":1,"1795":1,"1801":1,"1804":1,"1809":1,"1820":1,"1841":1,"1845":1,"1846":1,"1847":1,"1848":2,"1850":1,"1854":1,"1882":1,"1899":1,"1902":1,"1918":1,"1963":1,"1971":1,"1994":1,"1996":1,"1999":1,"2003":1,"2033":1,"2040":1,"2062":2,"2066":1,"2067":1,"2068":1,"2072":1,"2073":1,"2087":1,"2106":1,"2108":1,"2110":1,"2121":1,"2122":1,"2132":1,"2143":1,"2150":1,"2155":1,"2157":1,"2162":1,"2184":1,"2192":1,"2201":1,"2229":1}}],["seat",{"3":{"1630":1,"1631":1}}],["seating",{"3":{"1629":2}}],["seats",{"3":{"1394":1}}],["season",{"3":{"1323":1}}],["seam",{"3":{"1322":1,"1323":1,"1358":1}}],["seamless",{"3":{"1299":1}}],["searchandwaitforrowasync",{"2":{"1432":1},"3":{"1432":1,"1435":1,"1487":1}}],["searchasync",{"3":{"1395":8}}],["searchability",{"3":{"1285":1}}],["searchable",{"3":{"363":1,"611":1,"1285":1,"1349":1}}],["searchfunc",{"3":{"1394":1}}],["searchfilterkey",{"3":{"1323":3,"1414":1}}],["searchusersasync",{"3":{"1394":2}}],["searchstringchanged",{"3":{"1394":1}}],["searchstring",{"3":{"1323":2,"1358":1,"1394":2}}],["searchvalues",{"3":{"1310":4,"1323":2}}],["searchtermfrom",{"3":{"1323":1}}],["searchterm",{"3":{"1299":1,"1310":1,"1323":2}}],["searching",{"3":{"1285":1,"1394":1}}],["searchedlocation",{"3":{"1323":1}}],["searched",{"3":{"488":1,"490":1,"1161":1,"1435":1}}],["searches",{"3":{"350":1,"1150":1,"1259":1,"1358":2,"1435":2,"1455":1,"1748":1}}],["search",{"3":{"0":1,"135":1,"243":1,"255":1,"259":1,"491":1,"492":1,"493":1,"511":1,"527":1,"528":1,"610":1,"684":1,"685":1,"690":2,"751":1,"819":1,"822":1,"837":1,"861":1,"863":1,"899":1,"946":1,"956":1,"963":1,"980":1,"1093":1,"1229":2,"1270":16,"1271":1,"1285":3,"1299":3,"1300":2,"1310":3,"1323":14,"1349":1,"1358":2,"1368":2,"1382":1,"1384":1,"1390":2,"1394":50,"1395":17,"1413":1,"1414":8,"1416":4,"1435":9,"1437":3,"1473":1,"1489":1,"1525":1,"1544":1,"1552":1,"1576":1,"1590":1,"1598":1,"1599":1,"1626":2,"1630":1,"1631":1,"1680":1,"1684":1,"1749":4,"1754":4,"1772":1,"1785":1,"1880":1,"1901":1,"1921":1,"1988":1,"2047":1,"2144":1,"2157":1,"2201":1,"2203":1,"2219":2}}],["seal",{"3":{"1322":5,"1437":1,"1823":1,"1825":1}}],["sealpipeline",{"3":{"1262":1,"1315":1,"1322":2}}],["sealing",{"3":{"0":1,"125":1,"126":1,"1168":1,"1169":1,"1170":1,"1259":1,"1270":1,"1311":1,"1322":1,"1414":1,"1435":4,"1670":1,"1826":1,"1882":1}}],["seals",{"0":{"1315":1},"3":{"0":1,"117":1,"122":1,"1288":1,"1310":1,"1322":1,"1382":1,"1395":1,"1414":1,"1435":1,"1661":1,"1823":1,"1882":1}}],["sealed",{"3":{"0":10,"45":1,"47":2,"80":1,"115":2,"166":1,"214":1,"255":1,"281":1,"310":2,"314":1,"318":1,"341":1,"350":1,"359":1,"448":2,"450":1,"455":1,"492":1,"498":1,"499":1,"511":1,"577":1,"631":2,"635":1,"657":2,"725":1,"733":1,"782":1,"854":1,"910":1,"922":1,"925":1,"926":1,"962":1,"963":3,"964":2,"967":1,"968":1,"988":1,"996":1,"1026":1,"1032":1,"1033":2,"1034":1,"1038":1,"1051":1,"1117":1,"1119":1,"1167":1,"1168":4,"1170":1,"1173":1,"1175":1,"1180":1,"1212":4,"1219":1,"1229":11,"1235":1,"1237":23,"1241":1,"1243":1,"1247":35,"1259":20,"1260":1,"1262":1,"1265":2,"1270":32,"1271":8,"1272":1,"1273":1,"1282":1,"1285":109,"1288":1,"1299":72,"1300":10,"1308":43,"1309":10,"1310":54,"1311":6,"1317":1,"1322":50,"1323":102,"1338":56,"1349":84,"1358":352,"1365":1,"1368":17,"1376":1,"1379":35,"1383":1,"1394":65,"1395":141,"1397":1,"1401":55,"1414":96,"1415":7,"1416":86,"1422":1,"1425":1,"1426":4,"1430":2,"1431":1,"1435":238,"1488":1,"1489":1,"1509":1,"1524":1,"1525":8,"1576":2,"1590":6,"1650":1,"1653":1,"1661":2,"1663":1,"1674":1,"1719":1,"1806":1,"1848":1,"1856":1,"1873":1,"1890":1,"1895":1,"1977":1,"1993":1,"1999":1,"2041":1,"2100":1,"2103":2,"2130":3,"2131":1,"2141":1,"2198":1,"2231":1}}],["senior",{"1":{"2211":1,"2212":1,"2213":1,"2214":1,"2215":1},"3":{"1793":1,"2098":1,"2108":1,"2211":1,"2212":1,"2216":1,"2217":1,"2219":2}}],["sensibly",{"3":{"1416":1,"1561":1}}],["sensible",{"3":{"709":1,"1338":1,"1394":1,"1435":2,"1487":1,"1554":1,"1560":1,"1606":1,"1641":1,"1677":1,"1740":1,"1926":1}}],["sensitivity",{"3":{"610":1,"1196":1,"1430":1,"1435":3,"1466":1,"1637":1,"2157":1}}],["sensitivedatadiscovery",{"3":{"1466":1}}],["sensitivedatalogginggatetests",{"3":{"1108":1,"1199":1,"1207":3,"1285":1}}],["sensitivedatalogginggate",{"2":{"1108":1},"3":{"0":1,"1108":2,"1199":2,"1203":1,"1207":2,"1212":1,"1273":3,"1285":6,"1322":1,"1495":2}}],["sensitively",{"3":{"1358":1}}],["sensitive",{"3":{"0":1,"122":1,"209":1,"265":1,"295":1,"313":1,"358":2,"360":1,"362":1,"585":1,"916":1,"950":1,"1108":1,"1110":1,"1247":1,"1283":1,"1285":1,"1299":3,"1309":1,"1311":1,"1322":1,"1323":3,"1338":1,"1349":1,"1358":6,"1379":1,"1394":1,"1395":3,"1414":3,"1415":1,"1416":6,"1422":1,"1435":1,"1452":2,"1466":1,"1562":1,"1566":1,"1630":1,"1970":1,"1972":1,"2138":1,"2139":2,"2140":1,"2143":1,"2144":1,"2148":1}}],["sense",{"3":{"396":1,"988":1,"1247":1,"1259":1,"1285":1,"1300":1,"1358":4,"1368":1,"1395":1,"1416":1,"1466":1,"1472":1,"2043":1,"2154":1}}],["sentto",{"3":{"1323":1}}],["senton",{"3":{"1308":5,"1414":2,"1435":2}}],["sentrequest",{"3":{"1199":3,"1207":1}}],["sentbyuserid",{"2":{"231":1},"3":{"225":1,"231":1,"1285":1,"1308":5,"1435":1,"1932":1}}],["sent",{"3":{"225":2,"227":1,"231":1,"434":1,"741":1,"759":1,"791":1,"837":1,"857":1,"1018":1,"1091":1,"1183":1,"1184":1,"1185":1,"1187":2,"1270":1,"1278":1,"1285":3,"1299":2,"1302":1,"1307":2,"1308":17,"1310":2,"1322":7,"1323":15,"1336":1,"1338":4,"1358":4,"1368":1,"1379":1,"1394":9,"1395":7,"1414":4,"1422":1,"1426":1,"1434":1,"1435":10,"1437":1,"1639":1,"1640":2,"1769":1,"1775":1,"1905":1,"1932":1,"1968":1,"2023":1,"2128":1,"2188":1}}],["sentences",{"3":{"265":1,"534":1,"543":1,"626":1,"1193":1,"1237":1,"1285":1,"1299":1,"1394":2,"1395":1,"1434":1,"1495":2,"1563":1,"1652":1,"1736":1}}],["sentence",{"3":{"0":1,"138":1,"139":1,"142":1,"245":1,"265":2,"373":1,"522":1,"523":1,"582":2,"611":1,"624":1,"626":2,"628":1,"798":1,"822":1,"864":1,"971":2,"972":2,"973":4,"974":2,"1007":1,"1026":1,"1067":2,"1068":1,"1119":1,"1237":1,"1270":1,"1285":3,"1310":2,"1322":3,"1323":9,"1338":2,"1358":4,"1368":1,"1388":1,"1394":12,"1395":9,"1414":2,"1426":1,"1429":1,"1435":2,"1477":1,"1495":3,"1525":2,"1576":2,"1585":1,"1590":2,"1652":1,"1749":1,"1899":1,"2032":1,"2038":2,"2042":2,"2055":1,"2170":1,"2177":1,"2225":1}}],["sentinels",{"3":{"1323":1}}],["sentinelref",{"3":{"1323":2}}],["sentinel",{"3":{"0":1,"26":1,"265":4,"268":1,"657":5,"658":3,"1234":1,"1237":4,"1244":1,"1247":1,"1253":1,"1259":1,"1274":1,"1275":1,"1281":1,"1285":5,"1299":4,"1308":2,"1310":1,"1323":10,"1349":2,"1351":1,"1358":3,"1368":1,"1382":1,"1389":3,"1394":19,"1395":3,"1435":6,"1437":4,"1576":1,"1590":1,"1610":1,"1652":1,"1668":1}}],["sendnotfoundprobeasync",{"3":{"1435":1}}],["sendnotificationasync",{"3":{"1323":1}}],["sendvalidationerrorprobeasync",{"3":{"1435":1}}],["sendforwardedasync",{"3":{"1435":1}}],["sendgetrequestasync",{"3":{"1394":3}}],["sendgrid",{"3":{"1308":1}}],["sendprobeasync",{"3":{"1338":2}}],["sendpushnotificationrequestvalidatortests",{"3":{"1199":1,"1207":2}}],["sendpushnotificationrequestvalidator",{"2":{"1304":1},"3":{"1199":2,"1203":1,"1207":2,"1304":2,"1308":13}}],["sendpushnotificationrequest",{"2":{"1302":1,"1304":1},"3":{"1199":14,"1203":1,"1207":2,"1302":1,"1304":2,"1308":13,"1323":11,"1435":1}}],["sendpushnotificationcommand",{"2":{"435":1,"436":1,"1303":1,"1932":1},"3":{"435":1,"436":1,"1199":5,"1203":1,"1207":2,"1270":2,"1303":3,"1308":12,"1932":1}}],["sendpushnotificationhandlertests",{"3":{"1199":1,"1207":3}}],["sendpushnotificationhandler",{"2":{"225":1,"229":1,"230":1,"231":1,"434":1,"435":1,"1303":1,"1511":1,"1932":1},"3":{"0":1,"225":7,"229":1,"230":1,"231":2,"434":1,"435":3,"436":1,"1199":2,"1203":1,"1207":2,"1285":2,"1301":1,"1303":3,"1308":51,"1322":2,"1511":1,"1932":1}}],["sendresult",{"3":{"1323":3}}],["sendreadwithauthrefreshasync",{"3":{"1323":3}}],["sendrequestasync",{"2":{"2073":1},"3":{"0":1,"881":2,"1323":7,"1394":12,"2073":1}}],["sendtouserasync",{"3":{"1308":3}}],["sendtousersasync",{"2":{"434":1,"1932":2},"3":{"434":1,"1285":1,"1308":5,"1322":2,"1932":2}}],["sendmailasync",{"3":{"230":1,"1308":1}}],["sendasync",{"3":{"230":1,"1308":8,"1310":3,"1322":1,"1323":12,"1338":1,"1435":6,"1487":1}}],["sendemailasync",{"2":{"2180":1},"3":{"2180":1}}],["sendemailconfirmationasync",{"3":{"1414":2,"1435":1}}],["sendemailconfirmationcommand",{"2":{"1407":1},"3":{"1199":7,"1203":1,"1207":2,"1299":1,"1322":2,"1407":2,"1414":12,"1435":1}}],["sendemailconfirmationrequestvalidator",{"2":{"1294":1},"3":{"1199":1,"1203":1,"1207":1,"1294":1,"1299":3}}],["sendemailconfirmationrequest",{"2":{"1294":1},"3":{"1199":11,"1203":1,"1207":1,"1294":1,"1299":3,"1322":1,"1323":1,"1414":3}}],["sendemailconfirmation",{"2":{"1407":1},"3":{"674":1,"1203":2,"1207":6,"1322":2,"1407":2,"1414":4}}],["sendemailconfirmationhandlertests",{"3":{"1199":1,"1207":2}}],["sendemailconfirmationhandler",{"2":{"674":1,"1407":1},"3":{"674":4,"1199":2,"1203":1,"1207":2,"1299":2,"1322":1,"1407":2,"1414":5,"1495":1}}],["sendemailconfirmationhandlerbase",{"2":{"674":1,"1321":1,"1407":1,"2197":1},"3":{"230":1,"674":2,"1199":3,"1203":1,"1207":2,"1299":3,"1321":3,"1322":6,"1407":1,"1414":2,"1495":1,"1576":1,"1581":1,"2197":1}}],["senders",{"3":{"1308":1,"1322":1}}],["sender",{"3":{"0":1,"223":1,"225":4,"230":3,"231":3,"380":1,"433":1,"436":1,"1192":1,"1285":1,"1299":2,"1301":1,"1302":3,"1306":1,"1308":21,"1319":1,"1322":8,"1435":1,"1466":1,"1495":1,"1511":1,"1934":1,"1937":1,"1939":1,"1940":1,"1943":1}}],["sendordershippedemailinternalcommand",{"2":{"1264":1},"3":{"1264":2}}],["sendordershippedemailinternalcommandhandler",{"2":{"230":1},"3":{"230":1}}],["sendorderpaymentfailedemail",{"3":{"2186":1}}],["sendorderpaymentfailedemailinternalcommand",{"2":{"2164":1},"3":{"2164":1,"2188":2}}],["sendorderpaymentfailedemailinternalcommandhandler",{"2":{"230":1},"3":{"230":1,"2188":1}}],["sendorderpaidemailinternalcommand",{"2":{"1026":1},"3":{"1026":1}}],["sendorderpaidemailinternalcommandhandler",{"2":{"230":1,"1026":1},"3":{"230":2,"1026":3}}],["sending",{"0":{"1932":1},"3":{"180":1,"792":1,"1019":1,"1186":1,"1202":1,"1203":1,"1271":1,"1285":2,"1303":2,"1308":8,"1322":4,"1323":2,"1338":1,"1358":3,"1383":1,"1395":1,"1401":3,"1414":3,"1423":1,"1426":1,"1507":1,"1768":1,"1769":1,"1932":2}}],["sendshttp2withpriorknowledge",{"3":{"1338":1}}],["sendshttp11",{"3":{"1338":1}}],["sends",{"3":{"0":2,"97":1,"111":1,"157":2,"160":1,"180":1,"206":1,"227":1,"230":1,"382":1,"434":2,"435":2,"448":2,"449":1,"507":1,"649":1,"773":1,"826":1,"827":1,"842":1,"881":2,"1050":1,"1091":1,"1237":1,"1241":1,"1247":1,"1285":3,"1299":4,"1303":1,"1308":11,"1310":2,"1322":5,"1323":17,"1327":1,"1338":4,"1346":1,"1349":3,"1358":13,"1368":1,"1379":2,"1388":1,"1394":16,"1395":8,"1399":1,"1401":1,"1414":4,"1415":1,"1421":1,"1435":6,"1437":1,"1440":1,"1454":1,"1457":2,"1466":3,"1480":1,"1599":1,"1626":1,"1640":2,"1665":1,"1718":1,"1764":1,"1766":1,"1769":1,"1834":1,"1907":1,"1932":1,"1935":1,"1943":1,"1948":1,"2002":1,"2009":1,"2130":2,"2132":2,"2146":2,"2180":1,"2188":1}}],["send",{"0":{"1303":1,"1511":1},"2":{"641":1,"642":1},"3":{"0":6,"31":1,"72":2,"109":1,"175":1,"202":1,"225":6,"227":2,"230":10,"231":2,"236":1,"336":1,"380":1,"435":1,"436":1,"640":1,"641":1,"642":1,"649":1,"675":1,"740":1,"789":1,"795":1,"848":1,"854":2,"856":2,"881":2,"883":1,"896":1,"926":1,"1018":1,"1041":1,"1203":3,"1207":6,"1247":1,"1249":1,"1270":1,"1285":8,"1293":1,"1299":10,"1301":5,"1302":1,"1303":3,"1304":2,"1306":1,"1308":50,"1310":8,"1322":18,"1323":47,"1338":4,"1349":1,"1358":4,"1368":1,"1395":5,"1399":1,"1401":1,"1407":3,"1414":9,"1426":1,"1433":1,"1435":9,"1437":5,"1466":3,"1511":2,"1533":1,"1576":1,"1590":1,"1594":1,"1595":2,"1640":4,"1665":1,"1666":1,"1668":1,"1718":1,"1722":1,"1748":1,"1760":1,"1764":1,"1766":1,"1769":2,"1775":1,"1910":1,"1932":7,"1933":5,"1934":2,"1935":2,"1936":1,"1937":3,"1939":4,"1940":1,"1949":1,"1968":1,"2147":1,"2165":1,"2195":1}}],["sev",{"2":{"2045":1},"3":{"0":1,"609":15,"1435":2,"1466":3,"1468":1,"1473":4,"1475":8,"2045":1}}],["seventy",{"3":{"2043":1}}],["seventeenth",{"3":{"1485":1}}],["seventeen",{"3":{"135":1,"141":2,"468":1,"475":2,"616":1,"618":2,"782":1,"803":1,"1212":1,"1349":1,"1358":1,"1368":13,"1394":3,"1416":2,"1466":1,"1485":1,"1659":1,"1665":1,"2110":1,"2111":1}}],["seventh",{"3":{"0":1,"1055":1,"1358":1,"1435":5,"1525":1,"1727":1}}],["seven",{"3":{"0":14,"76":1,"80":1,"115":2,"117":1,"121":2,"122":1,"126":1,"135":1,"150":1,"184":1,"195":1,"203":1,"241":3,"243":1,"245":3,"250":1,"251":2,"252":1,"254":5,"256":2,"257":2,"258":1,"259":3,"260":3,"261":1,"400":2,"401":2,"404":1,"418":1,"423":1,"426":1,"454":1,"501":3,"502":1,"554":1,"556":1,"572":2,"574":1,"577":1,"578":3,"579":1,"589":1,"599":1,"604":1,"616":1,"626":1,"631":1,"632":2,"633":3,"635":2,"638":1,"640":1,"657":1,"659":3,"672":3,"674":3,"677":1,"690":1,"715":1,"749":1,"750":1,"767":1,"774":1,"782":2,"801":1,"881":3,"897":1,"900":1,"914":1,"968":1,"1018":1,"1020":1,"1050":1,"1076":1,"1090":1,"1176":1,"1212":4,"1220":1,"1237":1,"1241":1,"1247":7,"1260":1,"1262":1,"1270":7,"1273":1,"1285":2,"1299":3,"1300":2,"1308":1,"1310":1,"1311":1,"1322":7,"1323":7,"1338":7,"1346":1,"1349":17,"1358":33,"1368":4,"1370":1,"1379":2,"1381":1,"1394":6,"1395":9,"1401":3,"1414":10,"1416":1,"1427":2,"1430":1,"1435":28,"1437":1,"1442":1,"1449":1,"1454":1,"1458":1,"1475":1,"1485":1,"1491":1,"1495":3,"1523":1,"1525":6,"1530":1,"1531":2,"1576":3,"1590":3,"1599":2,"1626":1,"1649":1,"1650":1,"1661":1,"1666":1,"1669":1,"1670":2,"1685":1,"1727":1,"1728":1,"1804":1,"1820":1,"1825":1,"1838":1,"1873":1,"1915":1,"1922":1,"1962":1,"1979":1,"1981":1,"1983":2,"2043":1,"2054":1,"2059":1,"2064":1,"2086":1,"2096":2,"2097":2,"2130":1,"2166":2,"2170":2,"2178":1}}],["sever",{"3":{"1435":2}}],["severities",{"3":{"610":1,"614":1,"649":1,"1323":3,"1466":1,"1473":1,"1475":1,"1482":1,"2078":1,"2157":1}}],["severityfor",{"3":{"1395":2}}],["severity",{"0":{"1222":1},"2":{"135":1,"490":1,"491":1,"492":1,"493":1,"1525":1},"3":{"0":6,"107":2,"110":1,"111":1,"113":1,"135":2,"265":1,"407":1,"486":1,"490":2,"491":2,"492":2,"493":2,"607":2,"608":1,"609":23,"610":1,"611":6,"612":2,"633":2,"896":2,"898":2,"980":1,"981":2,"1018":1,"1198":2,"1199":9,"1207":2,"1212":1,"1213":1,"1214":2,"1225":1,"1229":2,"1237":1,"1271":1,"1310":2,"1323":24,"1338":1,"1358":1,"1394":3,"1395":5,"1430":1,"1435":6,"1437":1,"1452":2,"1466":12,"1475":2,"1488":1,"1525":1,"1551":1,"1576":1,"1580":1,"1581":1,"1583":1,"1590":1,"1648":1,"1653":1,"1660":1,"1661":1,"1672":1,"1682":1,"1684":1,"1695":1,"1726":2,"1902":1,"1904":1,"2045":2,"2048":1,"2084":1,"2085":1,"2157":12,"2219":1,"2231":1}}],["several",{"3":{"0":1,"27":1,"109":3,"131":1,"165":1,"166":1,"185":1,"228":1,"268":1,"396":1,"471":1,"508":1,"509":1,"544":1,"549":1,"551":1,"600":2,"601":1,"632":1,"677":1,"714":1,"765":1,"809":1,"810":1,"822":1,"839":1,"900":1,"974":1,"988":1,"1055":1,"1067":1,"1103":1,"1104":1,"1107":1,"1220":1,"1229":4,"1239":1,"1246":1,"1247":1,"1259":1,"1263":1,"1269":2,"1270":9,"1271":1,"1285":14,"1288":1,"1299":3,"1300":3,"1301":1,"1308":1,"1309":1,"1310":8,"1318":1,"1319":1,"1322":5,"1323":10,"1331":1,"1332":1,"1338":3,"1349":9,"1350":1,"1351":1,"1352":1,"1358":10,"1361":1,"1373":1,"1379":2,"1386":1,"1394":4,"1395":9,"1404":1,"1408":1,"1415":1,"1416":6,"1419":1,"1428":1,"1435":16,"1441":1,"1487":2,"1488":1,"1489":1,"1495":3,"1498":1,"1499":2,"1536":1,"1576":1,"1581":1,"1670":1,"1674":1,"1691":1,"1696":1,"1698":1,"1707":1,"1725":1,"1726":1,"1766":1,"1804":1,"1862":1,"1941":1,"2040":1,"2086":1}}],["severe",{"3":{"0":1,"109":2,"1224":1,"1229":2,"1310":5,"1311":2,"1323":2,"1701":1,"1804":1,"1805":1,"1926":2}}],["ser",{"3":{"1684":1,"1685":2}}],["serious",{"3":{"2060":1}}],["seriously",{"3":{"1395":1}}],["serif",{"3":{"1323":1,"2074":1}}],["serially",{"3":{"1454":1}}],["serial",{"3":{"1299":1,"1435":1}}],["serializable",{"3":{"1299":1,"1323":1,"1349":1,"1358":2,"1416":1,"1533":1}}],["serialization",{"2":{"1229":2,"1368":1},"3":{"0":1,"245":1,"253":1,"291":1,"380":1,"520":1,"593":1,"655":1,"657":2,"658":1,"659":1,"733":1,"964":1,"1043":1,"1102":1,"1150":1,"1154":1,"1203":3,"1207":24,"1212":2,"1223":1,"1229":8,"1233":1,"1234":1,"1235":1,"1237":6,"1259":3,"1270":1,"1299":3,"1300":3,"1310":3,"1322":3,"1323":2,"1340":1,"1349":1,"1358":9,"1368":1,"1379":2,"1401":2,"1414":3,"1416":2,"1435":3,"1437":1,"1495":1,"1539":1,"1915":1,"1919":1,"1943":1,"2039":1,"2231":1}}],["serializing",{"3":{"434":1,"1251":1,"1270":1,"1300":1,"1310":3,"1322":1,"1358":1,"1395":2,"1401":1,"1416":1,"1915":1}}],["serializetoutf8bytes",{"3":{"1300":1,"1310":1,"1414":1}}],["serialize",{"3":{"1229":1,"1244":1,"1247":2,"1270":2,"1285":1,"1299":5,"1300":2,"1310":3,"1323":1,"1349":2,"1358":1,"1394":1,"1398":1,"1401":2,"1414":3,"1416":3,"1426":2,"1837":1,"1908":2,"1911":1,"1949":1}}],["serializes",{"3":{"24":2,"77":1,"157":1,"195":1,"243":1,"301":1,"306":1,"341":1,"423":1,"424":1,"436":1,"519":1,"541":1,"657":1,"660":1,"725":1,"964":1,"995":1,"996":2,"999":1,"1018":1,"1050":2,"1142":1,"1150":1,"1229":3,"1233":3,"1234":1,"1237":5,"1259":2,"1270":1,"1285":5,"1299":6,"1300":2,"1310":12,"1322":3,"1323":3,"1344":1,"1349":1,"1358":1,"1395":2,"1398":1,"1401":4,"1414":4,"1416":3,"1435":2,"1454":1,"1637":1,"1805":1,"1873":1,"1886":2,"1908":1,"1911":1,"1926":1,"2075":1,"2090":1,"2168":1}}],["serialized",{"3":{"0":3,"17":1,"38":1,"78":1,"157":2,"159":3,"330":1,"380":1,"447":1,"472":1,"536":1,"558":1,"781":1,"783":1,"852":1,"857":1,"965":1,"966":1,"1034":1,"1043":1,"1074":1,"1140":1,"1175":1,"1212":1,"1223":1,"1229":4,"1237":3,"1245":1,"1259":6,"1274":1,"1275":1,"1285":2,"1288":1,"1297":1,"1299":5,"1308":3,"1310":4,"1311":1,"1322":2,"1323":4,"1349":4,"1358":1,"1379":1,"1395":9,"1399":1,"1401":5,"1405":1,"1414":4,"1416":1,"1435":6,"1545":1,"1629":1,"1630":1,"1638":1,"1639":1,"1804":1,"1844":1,"1888":1,"1907":2,"1943":1,"1945":1,"1949":1,"1967":1,"1971":1,"1987":1,"2064":1,"2129":1,"2183":1}}],["serializeroptions",{"3":{"1259":1,"1285":1}}],["serializer",{"3":{"0":1,"79":1,"82":1,"381":1,"470":1,"965":1,"1212":1,"1285":1,"1299":2,"1310":2,"1323":1,"1358":1,"1395":1,"1399":1,"1426":1,"1437":1,"1943":1,"2231":1}}],["series",{"0":{"1778":1,"1785":1},"1":{"1777":1,"1778":1,"2203":1,"2204":1,"2205":1,"2206":1,"2207":1,"2208":1,"2209":1,"2210":1},"3":{"0":1,"24":1,"126":1,"527":1,"863":1,"1247":1,"1259":1,"1270":1,"1285":1,"1310":1,"1358":1,"1394":2,"1424":3,"1426":4,"1777":2,"1778":2,"1779":1,"1782":1,"1789":1,"1792":1,"1801":1,"1806":1,"1811":1,"1816":1,"1820":1,"1826":1,"1833":1,"1839":1,"1845":1,"1852":1,"1861":1,"1867":1,"1876":2,"1885":2,"1891":1,"1898":1,"1904":1,"1911":1,"1923":1,"1929":1,"1934":1,"1940":2,"1941":1,"1946":1,"1949":2,"1956":1,"1958":1,"1964":1,"1971":1,"1978":2,"1984":1,"1990":1,"1996":1,"2001":1,"2013":1,"2027":2,"2029":1,"2030":1,"2031":1,"2034":1,"2037":2,"2048":1,"2059":1,"2068":1,"2069":2,"2076":1,"2079":1,"2085":1,"2097":1,"2107":2,"2114":1,"2122":2,"2127":1,"2132":1,"2138":2,"2145":2,"2152":1,"2159":1,"2168":1,"2176":1,"2179":1,"2182":1,"2192":1,"2193":1,"2202":4,"2203":3,"2210":1,"2214":3,"2231":1,"2235":2,"2244":2}}],["serilogbootstraptests",{"3":{"1199":1,"1207":2,"1436":1,"1486":1}}],["seriloghostextensionstests",{"3":{"401":1,"1199":1,"1207":3,"1338":2,"1437":1}}],["seriloghostextensions",{"3":{"401":3,"980":1,"1199":2,"1203":1,"1207":2,"1208":1,"1310":1,"1332":2,"1338":3,"1441":2}}],["serilogloggerprovider",{"2":{"401":1},"3":{"401":1,"1338":1}}],["serilog",{"3":{"0":3,"395":2,"401":4,"914":1,"1213":1,"1310":1,"1322":1,"1324":1,"1332":1,"1338":6,"1378":1,"1436":1,"1437":1,"1441":2,"1444":1,"1455":1,"1466":2,"1488":1,"1489":1,"1576":1,"1659":1,"1669":2,"2042":1,"2062":1}}],["serving",{"3":{"0":2,"31":1,"62":1,"91":2,"94":1,"99":1,"104":1,"234":1,"235":2,"390":1,"421":1,"563":1,"609":1,"699":1,"733":1,"756":1,"757":4,"758":2,"765":1,"875":1,"971":1,"1124":1,"1270":1,"1278":1,"1285":2,"1300":1,"1306":1,"1310":8,"1311":1,"1323":3,"1334":1,"1338":4,"1349":1,"1358":6,"1368":1,"1371":1,"1378":1,"1379":1,"1394":1,"1395":8,"1414":2,"1435":5,"1454":6,"1460":1,"1466":7,"1473":1,"1475":1,"1477":1,"1576":1,"1668":1,"1707":1,"1919":1,"1922":1,"2000":1,"2035":2,"2042":1,"2123":1,"2124":1,"2146":1,"2150":1,"2155":1}}],["servicetestfixture",{"2":{"1616":1},"3":{"1616":1}}],["servicetype",{"3":{"1322":2,"1338":1,"1435":1}}],["servicelifetime",{"3":{"1435":1}}],["servicehost",{"3":{"1430":1,"1435":3}}],["serviceprovider",{"3":{"1259":6,"1285":4,"1300":2,"1314":2,"1322":2,"1323":2,"1368":3,"1379":1,"1394":2,"1395":2,"1401":1,"1426":4,"1435":2}}],["serviced",{"3":{"1570":1}}],["servicedatabaseltr",{"2":{"1473":1,"2033":1},"3":{"1473":1,"2033":1}}],["servicedatabasenames",{"2":{"1472":1},"3":{"1466":1,"1472":1}}],["servicedatabases",{"3":{"1435":1}}],["servicedatasources",{"3":{"1327":1,"1338":2}}],["servicediscovery",{"2":{"1213":1},"3":{"1213":2}}],["servicedescriptor",{"2":{"122":1},"3":{"122":1,"1285":2,"1308":1,"1310":2,"1311":1,"1322":7,"1358":1,"1368":1,"1379":2,"1395":2,"1414":2,"1426":1,"1435":2}}],["servicedefaults",{"2":{"61":1,"1438":1,"1441":1},"3":{"0":1,"61":1,"1192":2,"1202":1,"1203":1,"1324":1,"1338":3,"1435":1,"1438":1,"1441":3,"1495":2,"1590":1,"2155":1}}],["servicemodel",{"3":{"1414":1}}],["servicemodels",{"3":{"1199":2,"1207":1,"1435":6,"1486":1,"1488":1}}],["servicemocks",{"3":{"1199":4,"1207":3}}],["servicecollectiondescriptorextensions",{"2":{"1308":1,"1349":1,"1379":1,"1414":1},"3":{"1308":1,"1310":1,"1349":1,"1379":1,"1395":1,"1414":1}}],["servicecollection",{"3":{"572":1,"1262":1,"1435":9}}],["servicecontractinterfaceof",{"3":{"1435":1}}],["servicecontractimplementations",{"3":{"1435":2}}],["servicecontractimplementationsarenotpublic",{"2":{"1311":1},"3":{"1311":1,"1435":1}}],["servicecontractpuritytests",{"3":{"1199":2,"1207":4,"1311":1,"1435":16,"1488":3,"1576":1,"1590":1}}],["servicecontractpuritytestsbase",{"2":{"51":1,"1584":1,"1662":1},"3":{"51":1,"54":1,"1199":3,"1207":2,"1311":5,"1349":1,"1414":1,"1435":13,"1576":1,"1584":1,"1662":1}}],["servicecontractattributefullname",{"2":{"1311":1},"3":{"1311":1,"1435":1}}],["servicecontractattribute",{"2":{"1348":1,"1800":1},"3":{"1199":1,"1203":1,"1207":2,"1311":9,"1348":1,"1349":3,"1395":4,"1414":3,"1435":6,"1576":1,"1800":1}}],["servicecontractsdonotdependonserviceinternals",{"2":{"1928":1},"3":{"54":1,"1311":1,"1435":4,"1928":1}}],["servicecontracts",{"3":{"54":1,"1435":3}}],["servicecontract",{"2":{"51":1,"54":1,"1025":1,"1030":1,"1662":1,"1928":1,"1929":1},"3":{"51":1,"54":1,"1025":1,"1030":1,"1202":1,"1203":1,"1308":2,"1311":5,"1348":1,"1349":2,"1358":1,"1395":3,"1403":1,"1414":2,"1435":11,"1576":1,"1584":1,"1585":1,"1661":1,"1662":1,"1928":2,"1929":1}}],["servicefilter",{"2":{"320":1,"1995":1},"3":{"318":2,"320":1,"324":1,"1299":1,"1395":3,"1993":1,"1994":2,"1995":1}}],["servicefilterattribute",{"2":{"157":1,"343":1,"1907":1},"3":{"157":1,"341":1,"343":1,"1310":4,"1907":1}}],["serviceinternalnamespaces",{"3":{"1435":1}}],["serviceinternals",{"3":{"54":1,"1435":3}}],["serviceinforesponse",{"3":{"448":1,"1199":2,"1203":1,"1207":1,"1310":6,"1379":1,"1495":1,"2130":1}}],["serviceinfo",{"2":{"449":1,"450":1,"2131":1},"3":{"448":2,"449":1,"450":1,"572":1,"1212":1,"1310":2,"1375":1,"1379":3,"1435":5,"1487":2,"1495":1,"1496":1,"1590":1,"2054":1,"2130":1,"2131":2}}],["serviceinfov2response",{"2":{"448":1,"2130":1},"3":{"448":1,"1199":2,"1203":1,"1207":1,"1310":6,"1379":1,"1495":1,"2130":1}}],["serviceinfoversioningcontracttestsbase",{"2":{"448":1,"455":1,"572":1,"1427":1,"1487":1,"2054":1,"2130":1,"2132":1},"3":{"0":1,"448":4,"455":1,"572":3,"1199":2,"1207":2,"1310":2,"1427":1,"1435":7,"1487":2,"2054":1,"2130":4,"2132":1}}],["serviceinfocontroller",{"2":{"448":1,"1375":1,"2130":1},"3":{"448":8,"1199":1,"1203":1,"1207":2,"1310":3,"1370":2,"1375":4,"1379":6,"1435":3,"1488":1,"1495":1,"1525":1,"1534":1,"1590":2,"1599":2,"2130":5}}],["serviceinfocontrollerbase",{"2":{"446":1,"448":1,"449":1,"1370":1,"1375":1,"2130":1,"2132":1},"3":{"0":1,"446":1,"448":8,"449":1,"1199":2,"1203":1,"1207":4,"1212":1,"1310":24,"1370":1,"1375":1,"1379":8,"1435":1,"1495":1,"2130":7,"2132":1}}],["servicenames",{"3":{"827":1,"1338":3}}],["servicename",{"0":{"2018":1},"2":{"53":1,"2008":1,"2018":1,"2027":1,"2029":1},"3":{"53":1,"1308":5,"1310":2,"1311":7,"1315":1,"1322":1,"1338":5,"1375":1,"1377":2,"1379":7,"1395":4,"1414":5,"1440":4,"1466":1,"1655":1,"2008":1,"2018":2,"2027":1,"2029":1}}],["serviceuri",{"2":{"440":1},"3":{"0":1,"440":1,"1322":7,"1466":2,"2125":2}}],["servicebusadministrationclient",{"3":{"1322":2}}],["servicebusclient",{"3":{"1322":2}}],["servicebusroundtripsmoketests",{"3":{"1199":1,"1207":2,"1435":2,"1437":1}}],["servicebusemulatorimagetag",{"3":{"1338":1}}],["servicebusemulatorimage",{"3":{"1338":1}}],["servicebusemulatorimageregistry",{"3":{"1338":1}}],["servicebusemulatorbrokertests",{"3":{"1199":1,"1207":2,"1338":3,"1437":2}}],["servicebusemulatorcollection",{"3":{"1199":2,"1207":1}}],["servicebusemulator",{"2":{"1203":1,"1436":1,"1437":1,"1485":1,"1486":1,"1489":2},"3":{"1199":3,"1203":1,"1206":1,"1207":9,"1435":4,"1436":1,"1437":2,"1445":1,"1485":1,"1486":1,"1489":2,"1495":1,"1598":1}}],["servicebusemulatorfixture",{"3":{"1199":3,"1207":3,"1435":2,"1437":2}}],["servicebusemulatorfixturebasetests",{"3":{"1199":1,"1207":4,"1435":1,"1437":1,"1486":1}}],["servicebusemulatorfixturebase",{"2":{"145":1,"638":1,"640":1,"1077":1,"1670":1},"3":{"145":1,"150":3,"638":1,"640":2,"642":1,"1074":1,"1077":1,"1199":4,"1207":2,"1322":2,"1435":4,"1437":2,"1487":1,"1495":1,"1670":1}}],["servicebusemulatorresource",{"2":{"640":1,"1443":2},"3":{"640":1,"1199":3,"1203":1,"1207":2,"1326":5,"1338":6,"1440":2,"1443":4,"1450":1,"1495":1}}],["servicebusemulatorsupporttests",{"3":{"1199":1,"1207":2,"1322":2,"1437":2}}],["servicebusemulatorsupport",{"2":{"638":1,"640":1,"1319":1},"3":{"150":3,"638":1,"640":2,"1074":1,"1199":2,"1203":1,"1207":2,"1319":3,"1322":10,"1495":1}}],["servicebus",{"2":{"145":1,"147":1,"149":1,"150":1,"152":1,"810":1,"1074":1,"1075":2,"1077":1,"1078":1},"3":{"0":2,"145":4,"147":2,"149":1,"150":2,"152":1,"624":2,"626":1,"640":4,"810":1,"1074":1,"1075":2,"1077":1,"1078":1,"1213":1,"1322":2,"1338":1,"1435":5,"1437":1,"1440":1,"1443":1,"1454":1,"1459":1,"1488":1,"1489":1,"1525":2,"1599":1}}],["serviceexceptionhelper",{"3":{"0":1,"109":1,"265":1}}],["services",{"0":{"1353":1,"1508":1,"1662":1},"1":{"56":1,"57":1,"58":1,"59":1,"60":1,"61":1,"62":1,"63":1,"64":1},"2":{"92":1,"95":1,"96":1,"104":1,"121":1,"180":1,"265":1,"300":1,"412":1,"454":2,"846":1,"954":3,"979":8,"980":1,"981":1,"1004":1,"1207":2,"1247":2,"1249":1,"1269":1,"1306":1,"1308":1,"1309":1,"1368":1,"1376":1,"1394":3,"1395":4,"1415":1,"1416":6,"1440":1,"1446":1,"1454":1,"1485":1,"1490":1,"1652":2,"1683":1,"1701":1,"1727":1,"1809":1,"1881":1,"1934":1,"2008":1,"2018":1,"2021":1,"2025":1,"2093":1,"2096":1},"3":{"0":14,"20":1,"24":1,"30":1,"31":4,"46":4,"49":3,"53":2,"55":2,"56":1,"59":1,"60":2,"61":1,"62":1,"63":2,"73":1,"92":1,"93":3,"94":2,"95":8,"96":5,"97":1,"98":5,"99":1,"102":1,"103":1,"104":1,"105":1,"107":1,"109":1,"117":2,"121":1,"142":1,"157":2,"175":2,"180":1,"181":1,"186":2,"195":4,"203":1,"235":6,"241":2,"242":1,"243":14,"245":9,"247":1,"248":1,"254":2,"258":2,"259":5,"260":4,"261":2,"264":1,"265":4,"281":1,"283":2,"295":7,"300":2,"303":1,"310":1,"312":1,"313":1,"318":1,"330":5,"331":1,"333":4,"336":1,"337":3,"341":1,"349":2,"353":1,"387":2,"388":1,"390":5,"391":2,"392":3,"396":1,"400":1,"401":2,"412":1,"413":4,"416":2,"418":1,"420":1,"423":1,"426":2,"427":1,"428":5,"429":1,"430":2,"434":1,"436":1,"447":1,"448":5,"449":1,"451":1,"452":2,"454":4,"459":3,"461":1,"482":1,"486":2,"487":1,"489":1,"490":1,"491":2,"492":2,"493":1,"505":1,"507":12,"508":1,"509":2,"511":1,"512":1,"513":3,"514":3,"518":1,"536":1,"538":2,"539":1,"541":1,"545":1,"549":1,"552":1,"556":1,"558":1,"572":4,"574":1,"577":1,"578":1,"582":1,"583":9,"584":1,"585":7,"586":1,"600":2,"602":1,"640":2,"642":1,"644":1,"649":2,"657":2,"665":2,"668":1,"669":1,"674":7,"682":3,"690":2,"698":1,"707":1,"715":2,"723":1,"725":2,"727":1,"733":3,"736":1,"740":1,"741":1,"744":1,"749":6,"751":1,"773":1,"774":3,"775":1,"790":1,"810":1,"826":2,"827":5,"831":1,"833":1,"837":1,"838":2,"846":2,"854":2,"868":3,"869":2,"870":2,"873":3,"876":8,"877":3,"881":27,"897":2,"905":1,"913":2,"914":1,"916":1,"930":2,"931":1,"950":1,"953":1,"954":8,"979":10,"980":5,"981":1,"983":1,"998":4,"999":2,"1004":4,"1006":1,"1025":1,"1026":2,"1028":1,"1029":1,"1050":4,"1059":5,"1069":2,"1082":1,"1086":1,"1090":1,"1092":1,"1095":1,"1100":1,"1103":1,"1104":1,"1116":1,"1140":1,"1161":1,"1178":1,"1183":2,"1184":3,"1192":7,"1199":7,"1201":1,"1202":4,"1203":289,"1206":1,"1207":991,"1208":60,"1212":5,"1213":2,"1227":1,"1229":3,"1237":2,"1241":4,"1242":2,"1243":5,"1244":1,"1246":2,"1247":92,"1248":1,"1249":1,"1252":1,"1253":1,"1256":3,"1257":1,"1259":10,"1262":3,"1269":1,"1270":12,"1271":1,"1275":2,"1278":1,"1280":1,"1284":1,"1285":29,"1286":1,"1299":51,"1300":21,"1303":1,"1304":1,"1306":9,"1308":55,"1309":32,"1310":69,"1311":27,"1313":3,"1315":2,"1319":1,"1322":44,"1323":313,"1324":6,"1325":2,"1327":3,"1330":6,"1331":11,"1332":1,"1333":4,"1334":5,"1335":1,"1336":1,"1337":1,"1338":77,"1339":1,"1340":1,"1344":1,"1348":1,"1349":18,"1350":1,"1351":1,"1352":1,"1353":1,"1354":2,"1358":96,"1359":1,"1367":1,"1368":9,"1376":1,"1377":7,"1378":3,"1379":48,"1380":3,"1381":25,"1384":1,"1385":7,"1386":10,"1387":8,"1388":3,"1389":3,"1390":3,"1391":2,"1392":2,"1394":200,"1395":230,"1400":7,"1401":28,"1403":2,"1405":2,"1408":1,"1412":1,"1413":2,"1414":79,"1415":34,"1416":360,"1420":1,"1426":6,"1427":1,"1428":1,"1430":1,"1431":4,"1433":1,"1434":3,"1435":70,"1436":7,"1437":38,"1439":1,"1440":12,"1441":2,"1442":5,"1443":2,"1444":3,"1445":3,"1446":7,"1447":1,"1448":2,"1454":5,"1455":4,"1459":1,"1460":1,"1463":10,"1466":33,"1468":1,"1472":1,"1473":1,"1476":1,"1485":1,"1486":6,"1487":5,"1488":2,"1489":4,"1490":2,"1495":27,"1497":2,"1507":1,"1508":2,"1522":1,"1525":9,"1530":2,"1533":2,"1534":3,"1537":1,"1540":1,"1541":2,"1543":4,"1546":1,"1547":1,"1548":1,"1549":1,"1554":1,"1555":2,"1569":1,"1576":9,"1581":3,"1585":2,"1587":1,"1588":2,"1590":13,"1595":5,"1598":1,"1599":1,"1610":2,"1612":1,"1618":1,"1628":1,"1637":1,"1640":1,"1650":1,"1651":1,"1652":5,"1655":5,"1662":1,"1666":1,"1668":2,"1676":1,"1677":1,"1683":1,"1701":2,"1707":1,"1723":1,"1727":1,"1760":1,"1764":3,"1769":2,"1789":2,"1791":2,"1807":2,"1809":1,"1823":1,"1834":1,"1847":1,"1849":1,"1851":2,"1863":1,"1877":1,"1881":1,"1882":3,"1886":1,"1892":1,"1893":2,"1894":2,"1896":1,"1897":1,"1898":1,"1907":3,"1915":1,"1919":1,"1922":1,"1925":1,"1934":1,"1947":2,"1969":1,"2003":1,"2007":1,"2008":2,"2009":1,"2014":1,"2018":1,"2021":1,"2022":1,"2023":2,"2024":1,"2025":1,"2027":1,"2054":3,"2066":1,"2081":2,"2082":3,"2085":1,"2093":2,"2096":1,"2109":1,"2110":1,"2112":1,"2116":1,"2117":2,"2118":1,"2120":3,"2122":1,"2126":1,"2129":2,"2130":2,"2135":1,"2153":1,"2154":1,"2155":3,"2163":1,"2167":1,"2176":1,"2201":1,"2207":1,"2228":1,"2229":1,"2231":2}}],["service",{"0":{"95":1,"1225":1,"1244":1,"1326":1,"1328":1,"1345":1,"1378":1,"1399":1,"1401":1,"1412":1,"1441":1,"1442":1,"1472":1,"1509":1,"1655":1,"1945":1,"2020":1,"2034":1},"1":{"28":1,"29":1,"30":1,"31":1,"32":1,"33":1,"34":1,"35":1,"44":1,"45":1,"46":1,"47":1,"48":1,"49":1,"50":1,"51":1,"52":1,"53":1,"54":1,"55":1,"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"294":1,"295":1,"296":1,"746":1,"747":1,"748":1,"749":1,"750":1,"751":1,"752":1,"753":1,"771":1,"772":1,"773":1,"774":1,"775":1,"776":1,"777":1,"778":1,"1238":1,"1239":1,"1240":1,"1241":1,"1242":1,"1243":1,"1244":1,"1245":1,"1246":1,"1247":1,"1311":1,"1324":1,"1325":1,"1326":1,"1327":1,"1328":1,"1329":1,"1330":1,"1331":1,"1332":1,"1333":1,"1334":1,"1335":1,"1336":1,"1337":1,"1338":1,"1369":1,"1370":1,"1371":1,"1372":1,"1373":1,"1374":1,"1375":1,"1376":1,"1377":1,"1378":1,"1379":1,"1846":1,"1847":1,"1848":1,"1849":1,"1850":1,"1851":1,"1852":1,"1892":1,"1893":1,"1894":1,"1895":1,"1896":1,"1897":1,"1898":1,"2014":1,"2015":1,"2016":1,"2017":1,"2018":1,"2019":1,"2020":1,"2021":1,"2022":1,"2023":1,"2024":1,"2025":1,"2026":1,"2027":1},"2":{"62":1,"229":1,"493":3,"1362":1,"1369":1,"1378":1,"1403":1,"1444":4,"1468":1,"1524":1,"1612":1,"1702":1},"3":{"0":82,"10":1,"15":1,"28":1,"30":7,"31":6,"32":3,"40":1,"44":1,"46":1,"47":5,"48":1,"49":1,"50":1,"52":1,"53":3,"54":1,"55":2,"57":2,"59":14,"60":4,"61":5,"62":6,"63":3,"64":3,"67":1,"68":2,"77":2,"81":1,"91":4,"92":6,"93":6,"94":3,"95":8,"96":5,"97":3,"98":5,"99":2,"100":5,"102":2,"103":3,"105":2,"107":1,"109":3,"110":1,"111":1,"115":1,"117":9,"122":2,"126":2,"127":1,"136":2,"145":2,"146":1,"147":1,"150":7,"152":2,"153":1,"155":1,"158":2,"159":2,"165":2,"167":1,"169":1,"174":2,"175":3,"176":1,"177":3,"181":1,"186":1,"191":2,"195":23,"196":2,"197":2,"200":5,"201":4,"202":1,"203":1,"216":1,"217":1,"219":1,"220":1,"224":1,"229":4,"234":1,"235":6,"236":1,"237":1,"241":4,"243":14,"245":13,"249":1,"255":7,"257":1,"258":1,"259":3,"260":5,"279":1,"280":1,"281":1,"283":3,"285":1,"288":1,"290":2,"291":7,"292":3,"293":2,"294":2,"295":8,"296":1,"300":1,"302":2,"313":1,"329":1,"330":1,"343":1,"349":2,"350":3,"352":1,"361":1,"380":1,"381":1,"382":1,"386":3,"387":4,"388":1,"389":2,"390":10,"391":3,"392":3,"395":3,"396":1,"397":2,"398":2,"399":1,"400":3,"401":6,"403":1,"407":1,"412":1,"413":2,"415":1,"418":2,"420":1,"426":3,"428":3,"429":2,"430":1,"433":1,"435":1,"446":2,"447":2,"448":6,"449":1,"450":1,"451":1,"452":8,"453":1,"454":1,"458":3,"459":3,"461":3,"471":1,"473":1,"493":4,"497":1,"498":1,"499":2,"500":1,"506":1,"513":3,"517":1,"518":1,"521":2,"524":1,"535":1,"536":3,"539":1,"541":1,"544":1,"545":1,"547":1,"548":1,"556":1,"558":2,"563":1,"564":1,"568":3,"572":5,"573":1,"574":2,"582":3,"583":18,"584":3,"585":5,"586":1,"587":1,"597":2,"598":3,"599":8,"600":1,"601":1,"602":8,"605":2,"609":2,"612":1,"624":1,"625":2,"626":9,"628":1,"633":1,"638":7,"639":2,"640":20,"641":4,"642":8,"643":10,"644":3,"645":1,"657":1,"659":1,"664":3,"665":5,"667":1,"668":2,"669":1,"674":5,"675":1,"690":3,"691":1,"692":4,"693":1,"696":1,"697":1,"699":1,"702":2,"706":3,"707":1,"708":1,"709":1,"711":1,"715":2,"725":4,"726":1,"727":1,"731":2,"732":1,"733":6,"741":1,"744":2,"746":1,"749":11,"750":3,"751":3,"756":1,"757":3,"758":4,"759":1,"762":2,"764":1,"766":2,"768":3,"771":1,"773":6,"774":21,"775":2,"776":5,"777":1,"778":3,"783":1,"786":1,"789":1,"790":1,"791":1,"792":1,"793":3,"794":1,"799":1,"809":1,"810":3,"815":2,"818":1,"819":4,"820":1,"821":2,"822":1,"823":1,"825":2,"826":10,"827":8,"828":2,"829":2,"834":2,"836":1,"837":2,"838":3,"839":1,"842":1,"846":1,"848":1,"854":4,"857":1,"860":1,"868":3,"869":12,"870":3,"872":2,"873":3,"875":1,"876":8,"877":2,"881":1,"883":2,"896":1,"897":2,"905":1,"906":1,"907":1,"913":4,"914":10,"915":3,"918":1,"922":1,"930":1,"931":2,"933":1,"935":1,"939":1,"950":1,"952":1,"954":4,"956":1,"960":1,"980":2,"987":3,"990":1,"992":1,"994":1,"995":1,"996":1,"998":3,"999":3,"1000":1,"1004":1,"1011":2,"1012":2,"1013":1,"1016":2,"1017":3,"1018":8,"1021":1,"1024":1,"1026":1,"1027":2,"1028":2,"1029":1,"1030":2,"1034":2,"1043":1,"1058":1,"1059":5,"1061":1,"1063":1,"1066":2,"1067":7,"1068":1,"1069":1,"1070":1,"1073":1,"1074":3,"1075":8,"1076":1,"1083":1,"1085":1,"1090":1,"1093":3,"1094":3,"1095":1,"1100":1,"1107":1,"1108":1,"1116":4,"1117":1,"1118":1,"1119":1,"1124":1,"1127":1,"1135":1,"1140":1,"1150":1,"1155":2,"1175":2,"1176":1,"1185":1,"1187":2,"1191":2,"1192":15,"1198":2,"1199":12,"1202":10,"1203":22,"1206":4,"1207":36,"1208":4,"1212":28,"1213":6,"1218":1,"1225":2,"1229":1,"1237":8,"1238":2,"1239":1,"1241":1,"1242":1,"1243":1,"1244":2,"1245":3,"1246":1,"1247":16,"1248":2,"1249":1,"1251":1,"1252":2,"1253":1,"1254":1,"1257":2,"1258":2,"1259":55,"1262":3,"1263":1,"1265":2,"1269":2,"1270":23,"1271":2,"1272":1,"1273":1,"1275":3,"1276":2,"1277":2,"1285":73,"1286":1,"1287":6,"1289":5,"1291":1,"1293":1,"1296":1,"1299":64,"1300":33,"1301":1,"1302":2,"1303":2,"1305":1,"1306":9,"1307":1,"1308":77,"1309":9,"1310":144,"1311":54,"1312":3,"1313":3,"1314":2,"1315":2,"1317":3,"1319":3,"1320":1,"1322":96,"1323":94,"1324":12,"1325":10,"1326":4,"1327":8,"1328":2,"1329":1,"1330":6,"1331":13,"1332":7,"1333":3,"1334":5,"1335":2,"1336":5,"1337":2,"1338":190,"1339":2,"1340":4,"1343":1,"1345":3,"1346":1,"1347":2,"1348":3,"1349":52,"1353":4,"1354":1,"1355":1,"1358":96,"1360":1,"1362":2,"1365":2,"1367":2,"1368":13,"1369":5,"1372":1,"1376":1,"1377":7,"1378":5,"1379":106,"1380":1,"1381":2,"1382":1,"1384":1,"1385":1,"1387":2,"1388":2,"1389":2,"1390":3,"1391":1,"1394":158,"1395":226,"1397":1,"1399":4,"1400":2,"1401":44,"1402":2,"1403":2,"1405":3,"1408":6,"1409":1,"1411":2,"1412":6,"1413":1,"1414":122,"1415":9,"1416":44,"1424":1,"1426":8,"1427":1,"1428":3,"1429":3,"1430":4,"1431":1,"1434":1,"1435":172,"1436":7,"1437":47,"1438":4,"1439":2,"1440":43,"1441":14,"1442":6,"1443":8,"1444":12,"1445":12,"1446":13,"1448":6,"1449":1,"1450":1,"1451":1,"1454":32,"1455":11,"1458":1,"1459":15,"1460":1,"1461":1,"1462":1,"1463":10,"1464":1,"1466":89,"1468":9,"1470":3,"1472":16,"1473":6,"1474":2,"1475":7,"1476":4,"1477":2,"1480":1,"1481":4,"1482":1,"1483":1,"1485":7,"1486":13,"1487":10,"1488":12,"1489":24,"1490":2,"1491":11,"1492":1,"1495":25,"1496":1,"1497":3,"1499":1,"1508":3,"1509":1,"1522":1,"1523":2,"1524":3,"1525":46,"1530":6,"1531":8,"1533":14,"1534":11,"1543":5,"1544":2,"1545":1,"1546":2,"1547":2,"1549":1,"1552":1,"1555":1,"1565":2,"1567":2,"1571":4,"1574":3,"1575":3,"1576":8,"1580":1,"1581":1,"1582":1,"1584":2,"1585":1,"1587":1,"1588":4,"1589":4,"1590":46,"1594":4,"1595":13,"1596":6,"1598":5,"1599":6,"1610":11,"1611":3,"1612":2,"1613":1,"1614":1,"1615":1,"1616":1,"1617":1,"1618":2,"1625":2,"1629":3,"1630":6,"1631":6,"1634":2,"1635":1,"1636":2,"1637":4,"1638":1,"1639":4,"1640":2,"1645":2,"1646":2,"1649":1,"1650":2,"1651":1,"1652":2,"1653":3,"1655":12,"1659":5,"1661":2,"1662":9,"1663":1,"1664":2,"1665":3,"1666":1,"1667":1,"1669":8,"1670":4,"1674":2,"1675":1,"1676":2,"1677":1,"1679":1,"1685":1,"1686":1,"1696":1,"1698":1,"1701":1,"1702":2,"1706":2,"1708":2,"1714":1,"1717":2,"1720":1,"1723":3,"1726":1,"1748":1,"1749":1,"1751":3,"1760":2,"1762":2,"1764":3,"1768":2,"1769":1,"1771":1,"1774":2,"1777":1,"1778":3,"1780":1,"1782":5,"1785":2,"1787":4,"1789":10,"1791":4,"1792":4,"1798":1,"1802":1,"1804":1,"1807":2,"1809":1,"1816":1,"1817":1,"1822":1,"1823":2,"1824":1,"1825":2,"1826":1,"1838":3,"1840":4,"1841":3,"1842":2,"1844":2,"1845":1,"1846":3,"1847":3,"1851":3,"1852":4,"1854":2,"1862":1,"1863":1,"1864":1,"1874":2,"1881":1,"1882":1,"1883":3,"1885":3,"1886":2,"1887":1,"1891":3,"1892":7,"1893":2,"1894":3,"1895":3,"1896":2,"1897":1,"1898":3,"1902":1,"1915":2,"1918":1,"1919":1,"1920":1,"1921":7,"1922":5,"1923":3,"1926":3,"1927":1,"1928":2,"1939":1,"1943":1,"1945":4,"1947":4,"1948":1,"1949":1,"1950":1,"1967":1,"1969":2,"1973":1,"1974":1,"1977":3,"1979":1,"1987":2,"1993":1,"1998":1,"1999":4,"2000":3,"2001":2,"2002":2,"2003":4,"2005":5,"2007":7,"2008":4,"2009":2,"2011":1,"2012":3,"2013":6,"2014":2,"2017":4,"2018":1,"2019":2,"2020":3,"2021":3,"2022":4,"2023":11,"2024":4,"2025":1,"2026":4,"2027":4,"2028":1,"2029":4,"2032":2,"2033":3,"2034":6,"2035":5,"2036":4,"2037":3,"2042":1,"2048":1,"2052":2,"2054":4,"2059":1,"2062":2,"2065":2,"2066":3,"2073":1,"2075":1,"2079":1,"2081":2,"2084":1,"2086":1,"2106":1,"2110":5,"2112":4,"2114":1,"2116":1,"2117":1,"2118":1,"2122":1,"2126":4,"2127":1,"2129":3,"2130":7,"2131":1,"2134":1,"2137":2,"2144":1,"2150":1,"2153":1,"2154":1,"2155":5,"2159":1,"2161":1,"2163":3,"2165":2,"2166":2,"2167":1,"2174":2,"2178":3,"2179":1,"2192":1,"2193":1,"2200":1,"2201":3,"2206":2,"2207":1,"2208":2,"2212":1,"2213":2,"2218":2,"2220":2,"2225":1,"2226":3,"2227":1,"2229":8,"2231":11,"2242":2}}],["servefromcacheasync",{"3":{"1310":1}}],["serve",{"3":{"0":2,"53":1,"55":1,"91":2,"92":2,"97":2,"98":2,"100":1,"101":1,"213":1,"234":1,"235":1,"236":1,"237":1,"243":1,"264":1,"265":1,"266":1,"388":1,"441":1,"451":1,"506":1,"556":1,"571":1,"733":1,"741":1,"751":1,"774":1,"819":1,"825":1,"829":1,"945":1,"1043":1,"1191":1,"1200":1,"1237":1,"1239":1,"1246":1,"1247":4,"1259":1,"1267":1,"1270":4,"1276":1,"1278":1,"1285":16,"1299":3,"1300":2,"1308":1,"1309":2,"1310":6,"1311":1,"1316":1,"1322":2,"1323":8,"1331":2,"1336":1,"1338":8,"1349":1,"1356":1,"1358":12,"1361":1,"1368":3,"1379":7,"1392":1,"1394":6,"1395":13,"1401":1,"1414":2,"1415":2,"1416":3,"1435":3,"1440":1,"1442":3,"1466":2,"1474":1,"1475":3,"1487":1,"1533":1,"1630":1,"1633":1,"1666":1,"1707":1,"1820":1,"1848":1,"1898":1,"1919":1,"1921":1,"1945":1,"2013":1,"2024":3,"2126":1,"2127":1,"2146":1,"2152":1}}],["served",{"3":{"0":7,"37":1,"42":1,"164":1,"170":2,"175":1,"176":1,"177":1,"233":1,"235":2,"242":1,"243":2,"341":2,"388":1,"391":1,"418":4,"420":1,"423":2,"424":2,"426":1,"428":1,"429":1,"433":1,"447":1,"450":1,"461":1,"573":1,"577":1,"707":1,"733":1,"735":1,"740":1,"749":1,"761":1,"826":2,"827":1,"905":1,"947":1,"1099":1,"1116":1,"1124":1,"1185":1,"1212":1,"1221":1,"1246":1,"1247":1,"1263":1,"1264":1,"1267":2,"1270":5,"1285":5,"1287":1,"1296":1,"1299":4,"1300":1,"1305":2,"1308":6,"1310":9,"1322":3,"1323":14,"1338":1,"1346":1,"1349":6,"1357":1,"1358":5,"1378":3,"1379":4,"1394":4,"1395":8,"1399":1,"1401":1,"1405":1,"1414":5,"1415":2,"1426":1,"1435":3,"1437":2,"1445":2,"1446":2,"1452":2,"1453":2,"1454":19,"1455":2,"1456":1,"1457":2,"1458":3,"1465":1,"1466":2,"1472":1,"1480":2,"1487":1,"1525":2,"1590":3,"1630":1,"1639":1,"1640":1,"1650":1,"1707":1,"1747":1,"1764":1,"1820":1,"1824":1,"1848":1,"1916":1,"1919":1,"1921":3,"1925":1,"1935":1,"2006":1,"2023":2,"2054":1,"2074":1,"2124":1,"2129":1,"2132":1,"2139":1,"2149":1,"2201":1,"2219":1,"2231":1}}],["serves",{"3":{"0":4,"63":1,"91":2,"93":1,"95":1,"98":3,"102":2,"110":1,"113":2,"170":1,"213":1,"216":2,"229":1,"265":1,"291":1,"391":1,"418":1,"448":2,"509":1,"536":1,"546":1,"571":1,"574":1,"577":1,"584":1,"633":1,"650":1,"665":1,"676":1,"697":1,"698":2,"725":1,"756":1,"758":1,"767":1,"832":1,"869":1,"877":1,"881":1,"882":1,"889":1,"926":2,"947":1,"954":1,"972":1,"1034":1,"1059":3,"1067":2,"1118":1,"1150":1,"1184":1,"1212":1,"1237":1,"1247":4,"1249":1,"1259":3,"1263":1,"1270":5,"1277":1,"1280":1,"1285":15,"1286":1,"1299":4,"1308":6,"1310":16,"1311":1,"1322":2,"1323":15,"1324":1,"1330":1,"1331":3,"1338":9,"1349":2,"1355":1,"1358":8,"1361":2,"1365":1,"1368":4,"1370":1,"1375":1,"1377":1,"1379":3,"1390":1,"1394":9,"1395":14,"1399":1,"1401":2,"1406":2,"1407":1,"1408":2,"1412":1,"1414":6,"1415":2,"1416":1,"1430":2,"1432":1,"1435":20,"1437":1,"1440":2,"1443":1,"1446":3,"1447":1,"1448":1,"1452":1,"1454":1,"1455":1,"1456":1,"1466":2,"1474":1,"1480":1,"1490":1,"1590":1,"1595":1,"1626":2,"1648":1,"1665":1,"1667":1,"1719":1,"1749":1,"1759":1,"1760":1,"1789":1,"1850":2,"1894":1,"1921":1,"1922":1,"1928":1,"1988":1,"1998":1,"2016":1,"2020":1,"2024":4,"2072":1,"2076":1,"2120":1,"2125":1,"2130":1,"2148":1,"2149":1,"2151":2,"2162":1,"2200":1,"2231":2}}],["serverless",{"3":{"1567":1,"1677":1}}],["server=true",{"3":{"1455":1,"1533":1}}],["serveresponseasync",{"3":{"1310":1}}],["servercallcontext",{"3":{"1308":2,"1379":2,"1395":2,"1414":1,"1437":1}}],["servertokenstorageservicetests",{"3":{"1199":1,"1207":3,"1437":1}}],["servertokenstorageservice",{"2":{"486":1,"487":1,"489":1,"490":1,"491":1,"492":1,"507":1},"3":{"486":2,"487":2,"489":2,"490":2,"491":2,"492":3,"507":8,"514":1,"1199":4,"1203":1,"1207":2,"1299":2,"1323":16,"1415":1,"1416":1,"1437":1,"1486":1,"1495":2,"1589":1,"1590":2,"1599":1}}],["serverstreamingserverhandler",{"3":{"1311":1}}],["servers",{"2":{"1928":1},"3":{"91":1,"1192":1,"1202":1,"1203":1,"1300":3,"1358":1,"1369":1,"1435":1,"1473":1,"1928":1,"2219":1}}],["serverdata=",{"3":{"1394":1}}],["serverdata",{"2":{"2071":1,"2075":1},"3":{"0":1,"556":1,"881":1,"1323":5,"1382":1,"1394":6,"1525":1,"2071":1,"2073":1,"2075":1}}],["server",{"0":{"1377":1,"2017":1},"1":{"1121":1,"1122":1,"1123":1,"1124":1,"1125":1,"1126":1,"1127":1,"1128":1,"1129":1},"2":{"402":1,"409":1,"451":1,"452":1,"1697":1,"2158":1},"3":{"0":49,"10":2,"13":1,"45":1,"47":1,"53":2,"86":1,"91":2,"92":2,"97":2,"100":1,"103":1,"109":5,"117":1,"128":1,"158":1,"164":3,"165":2,"166":6,"170":9,"171":1,"174":2,"175":3,"176":1,"179":3,"180":1,"201":1,"206":3,"207":2,"208":2,"217":1,"228":1,"237":1,"241":1,"243":1,"258":1,"264":1,"265":7,"273":2,"330":2,"333":1,"337":1,"341":4,"342":4,"343":2,"350":1,"351":1,"363":1,"384":2,"387":1,"389":1,"397":2,"402":2,"409":1,"412":1,"413":1,"415":3,"419":1,"434":1,"436":1,"440":1,"443":1,"451":1,"452":1,"470":1,"499":1,"501":1,"506":3,"507":5,"508":2,"547":1,"549":1,"555":2,"556":5,"557":2,"558":3,"560":1,"572":1,"574":1,"609":1,"632":1,"633":5,"640":1,"644":1,"648":2,"650":1,"653":1,"664":1,"682":1,"684":1,"689":1,"690":3,"691":1,"692":1,"708":1,"719":4,"735":1,"741":1,"749":1,"766":1,"826":1,"827":3,"828":1,"830":2,"833":2,"855":2,"857":1,"860":2,"863":1,"865":2,"879":1,"880":2,"881":6,"882":3,"883":1,"884":1,"885":4,"889":2,"890":1,"892":1,"905":3,"909":1,"913":1,"914":1,"926":1,"971":1,"972":1,"974":1,"987":1,"988":1,"1004":1,"1028":1,"1033":4,"1034":18,"1035":2,"1036":3,"1044":1,"1050":2,"1052":1,"1067":1,"1069":1,"1083":1,"1085":1,"1091":1,"1116":2,"1117":1,"1121":1,"1123":3,"1124":1,"1129":1,"1132":1,"1133":1,"1134":1,"1135":1,"1144":1,"1174":2,"1175":2,"1176":1,"1182":1,"1184":9,"1185":2,"1187":2,"1192":1,"1212":11,"1213":3,"1217":1,"1219":2,"1225":1,"1227":1,"1229":7,"1236":1,"1237":3,"1239":1,"1241":5,"1242":6,"1243":2,"1245":2,"1247":21,"1259":2,"1265":2,"1270":6,"1271":2,"1273":2,"1277":1,"1278":1,"1279":7,"1280":2,"1281":3,"1283":3,"1285":114,"1286":1,"1289":1,"1297":5,"1299":37,"1300":17,"1305":1,"1308":29,"1309":4,"1310":37,"1311":24,"1316":1,"1319":1,"1320":1,"1322":8,"1323":210,"1325":2,"1326":2,"1331":2,"1332":3,"1333":2,"1338":36,"1341":3,"1346":1,"1349":21,"1351":1,"1354":1,"1356":1,"1357":1,"1358":54,"1359":1,"1360":1,"1368":7,"1377":2,"1378":1,"1379":14,"1380":1,"1381":1,"1382":2,"1383":3,"1384":1,"1387":2,"1388":1,"1389":1,"1390":2,"1391":3,"1392":1,"1394":170,"1395":169,"1398":1,"1400":1,"1401":22,"1407":1,"1409":1,"1413":3,"1414":31,"1415":21,"1416":87,"1418":1,"1426":1,"1427":1,"1428":3,"1430":1,"1431":1,"1432":1,"1435":62,"1436":6,"1437":19,"1439":1,"1440":12,"1442":8,"1445":3,"1446":2,"1450":2,"1452":4,"1454":3,"1455":2,"1456":2,"1459":2,"1466":38,"1472":3,"1473":3,"1474":4,"1475":2,"1476":1,"1477":2,"1485":1,"1486":2,"1487":7,"1488":2,"1489":12,"1491":3,"1495":4,"1509":1,"1512":1,"1525":16,"1530":1,"1531":2,"1533":3,"1534":2,"1553":1,"1555":4,"1559":2,"1560":3,"1562":1,"1563":1,"1571":3,"1576":9,"1581":3,"1582":1,"1584":1,"1585":1,"1590":9,"1594":1,"1595":2,"1596":1,"1598":8,"1599":1,"1601":1,"1610":2,"1617":1,"1625":1,"1626":2,"1629":2,"1630":5,"1631":2,"1634":1,"1637":1,"1638":3,"1639":5,"1640":5,"1646":2,"1647":2,"1652":7,"1653":1,"1655":1,"1659":2,"1663":2,"1665":1,"1666":2,"1667":2,"1668":4,"1670":2,"1672":2,"1674":1,"1676":3,"1680":1,"1681":1,"1684":1,"1686":1,"1691":1,"1692":1,"1696":1,"1697":1,"1701":3,"1708":1,"1716":2,"1717":3,"1718":1,"1719":2,"1720":2,"1722":1,"1723":2,"1724":1,"1726":2,"1730":2,"1747":1,"1748":1,"1749":1,"1751":1,"1756":1,"1758":2,"1762":3,"1764":1,"1766":1,"1772":2,"1773":1,"1774":1,"1789":2,"1804":2,"1813":1,"1814":2,"1816":2,"1839":2,"1850":1,"1852":1,"1853":3,"1854":1,"1855":4,"1856":1,"1857":1,"1858":2,"1859":2,"1860":4,"1862":1,"1866":1,"1870":2,"1871":2,"1873":1,"1875":2,"1876":1,"1882":1,"1902":1,"1905":4,"1915":1,"1920":1,"1926":1,"1927":1,"1935":1,"1944":1,"1955":1,"1965":4,"1966":2,"1967":3,"1971":7,"1974":3,"1978":2,"1979":2,"1980":1,"1983":1,"1984":2,"1987":2,"1988":1,"1997":1,"1998":1,"1999":5,"2000":1,"2001":1,"2002":1,"2003":2,"2004":1,"2006":1,"2016":1,"2017":2,"2023":1,"2024":1,"2027":2,"2032":1,"2033":1,"2035":2,"2036":1,"2042":2,"2052":1,"2054":1,"2058":3,"2068":1,"2069":3,"2070":1,"2071":3,"2073":5,"2075":1,"2076":1,"2077":2,"2079":3,"2080":2,"2081":3,"2082":2,"2084":3,"2085":4,"2088":1,"2117":2,"2118":1,"2125":1,"2157":1,"2158":1,"2166":1,"2169":1,"2191":1,"2218":1,"2219":2,"2223":1,"2229":1,"2231":5}}],["selenium",{"3":{"2218":1,"2219":1}}],["selecteventasync",{"3":{"1358":2}}],["selectedrole",{"3":{"1414":1}}],["selectedroomidchanged",{"3":{"1394":1}}],["selectedroomid",{"3":{"1394":4}}],["selectedvalue",{"3":{"1395":1}}],["selectedsessionid",{"3":{"1395":1}}],["selectedstatus",{"3":{"1394":1}}],["selectedeventname",{"3":{"1394":1}}],["selectedeventidchanged",{"3":{"1394":1}}],["selectedeventid",{"3":{"1394":4}}],["selected",{"3":{"0":3,"31":1,"166":1,"170":1,"243":1,"253":1,"447":1,"448":1,"455":1,"674":1,"689":1,"774":1,"775":1,"778":1,"827":1,"840":1,"905":1,"945":1,"1016":1,"1091":1,"1133":1,"1192":1,"1202":1,"1203":1,"1212":1,"1257":1,"1285":6,"1290":1,"1299":2,"1300":2,"1309":1,"1310":5,"1322":2,"1323":2,"1338":3,"1349":4,"1355":1,"1358":3,"1375":1,"1379":1,"1388":1,"1394":14,"1395":3,"1401":1,"1414":2,"1415":1,"1416":6,"1417":1,"1426":2,"1432":2,"1435":4,"1466":1,"1495":1,"1576":1,"1625":1,"1626":1,"1629":1,"1634":1,"1652":1,"1655":1,"1662":1,"1670":1,"1841":1,"1894":1,"1897":1,"2011":1,"2046":1,"2129":1,"2130":1,"2132":1,"2231":2}}],["selectcurrentornext",{"3":{"1349":7,"1358":1,"1394":10,"1395":1}}],["selectcleartexturl",{"3":{"1338":2}}],["selecttargets",{"3":{"1285":1}}],["selectmany",{"3":{"1285":1,"1310":1,"1323":1,"1435":1}}],["selectable",{"3":{"178":1,"1341":1,"1349":3,"1474":1,"1590":1,"1666":1,"2001":1}}],["selective",{"3":{"780":1,"784":1,"1349":2}}],["selecting",{"3":{"0":1,"359":1,"846":1,"1247":2,"1283":1,"1285":3,"1299":1,"1310":1,"1349":1,"1358":2,"1394":1,"1395":1,"1435":2,"1491":1,"1686":1,"1747":1}}],["selections",{"3":{"1394":1}}],["selectionservice",{"3":{"1394":1}}],["selection",{"0":{"1347":1,"1389":1},"1":{"637":1,"638":1,"639":1,"640":1,"641":1,"642":1,"643":1,"644":1,"645":1},"2":{"1394":1},"3":{"0":1,"27":1,"86":2,"107":1,"185":1,"199":1,"225":1,"305":1,"312":1,"359":1,"365":1,"412":1,"449":1,"591":1,"637":1,"642":2,"643":1,"774":2,"823":1,"881":1,"913":1,"917":1,"945":1,"947":2,"1017":1,"1111":1,"1202":2,"1203":14,"1207":34,"1212":2,"1229":1,"1238":1,"1243":2,"1247":2,"1257":1,"1278":1,"1285":6,"1291":1,"1299":1,"1300":1,"1310":4,"1322":2,"1323":3,"1325":1,"1338":6,"1339":1,"1346":1,"1347":1,"1349":9,"1358":9,"1368":2,"1376":1,"1379":2,"1380":1,"1381":1,"1384":9,"1389":9,"1392":1,"1394":60,"1395":4,"1404":1,"1414":2,"1416":7,"1426":3,"1428":1,"1435":2,"1436":2,"1437":8,"1445":2,"1454":1,"1459":1,"1466":1,"1468":1,"1491":1,"1492":1,"1495":1,"1523":2,"1525":1,"1530":1,"1531":1,"1563":1,"1576":1,"1620":2,"1624":1,"1626":2,"1631":2,"1639":1,"1814":1,"1959":1,"2046":1,"2141":1,"2231":1}}],["selectors",{"3":{"1278":1,"1285":1,"1309":3,"1358":18,"1415":2,"1435":4,"1525":1,"1530":1,"1564":2,"1576":1,"1639":1,"1864":1,"1867":1}}],["selectorderedcandidates",{"2":{"1255":1},"3":{"1237":1,"1255":1,"1259":1}}],["selector",{"2":{"1832":1},"3":{"0":4,"328":1,"333":2,"640":2,"692":1,"755":1,"1192":1,"1229":1,"1239":1,"1247":6,"1271":25,"1285":11,"1299":1,"1309":7,"1310":3,"1322":1,"1323":2,"1338":1,"1347":1,"1352":3,"1358":129,"1394":5,"1395":4,"1414":1,"1415":1,"1435":15,"1486":1,"1525":1,"1533":1,"1576":1,"1749":1,"1772":1,"1828":2,"1829":1,"1832":3,"1833":1,"1858":1,"2231":1}}],["select",{"2":{"1238":1,"2076":1,"2139":1,"2192":1},"3":{"0":2,"235":1,"311":1,"330":1,"402":1,"536":1,"549":2,"550":1,"618":2,"620":1,"640":1,"649":1,"1238":1,"1242":1,"1243":1,"1247":4,"1269":1,"1270":2,"1271":2,"1285":14,"1286":1,"1308":4,"1310":3,"1322":1,"1323":2,"1325":1,"1331":1,"1332":1,"1338":4,"1349":4,"1358":14,"1361":1,"1368":1,"1379":5,"1394":2,"1395":12,"1401":4,"1414":10,"1416":2,"1432":1,"1435":5,"1437":1,"1440":1,"1442":1,"1443":1,"1445":1,"1466":1,"1487":2,"1576":1,"1676":1,"1749":1,"1814":1,"1987":1,"2007":1,"2009":1,"2021":1,"2056":1,"2076":1,"2139":1,"2141":1,"2192":1}}],["selectsortkey",{"3":{"1414":3}}],["selects",{"3":{"0":1,"98":1,"201":1,"280":1,"330":1,"332":1,"447":1,"448":1,"499":1,"539":1,"592":1,"599":1,"638":1,"644":1,"757":1,"905":1,"946":1,"1091":1,"1108":1,"1112":1,"1222":1,"1229":1,"1237":1,"1240":1,"1243":2,"1247":3,"1256":1,"1259":2,"1269":2,"1283":1,"1285":5,"1287":1,"1299":2,"1310":1,"1311":2,"1318":1,"1322":2,"1326":1,"1335":1,"1338":4,"1358":5,"1391":1,"1394":1,"1395":4,"1401":2,"1415":1,"1419":1,"1426":1,"1435":10,"1437":2,"1440":1,"1454":3,"1455":1,"1466":3,"1485":1,"1525":1,"1652":1,"1668":1,"1764":1,"1789":1,"1841":1,"1901":1,"1915":1,"1923":1,"1987":1,"2019":1,"2129":1}}],["selling",{"3":{"1394":1,"2109":1}}],["sell",{"3":{"1394":1,"1781":1}}],["sells",{"3":{"689":1,"698":1,"1025":1,"1035":1,"1394":2}}],["selfreference",{"3":{"1416":3}}],["selfhealasync",{"3":{"1300":2}}],["selfhttpwarmup",{"3":{"1395":1,"1414":1}}],["selfhttpwarmuptask",{"2":{"1191":1,"1497":1},"3":{"235":2,"1191":3,"1198":2,"1199":4,"1203":2,"1207":4,"1333":1,"1338":4,"1395":9,"1412":2,"1414":14,"1495":5,"1497":3}}],["selfhttpwarmuptaskbasetests",{"3":{"1199":1,"1207":8,"1338":3,"1437":1}}],["selfhttpwarmuptaskbase",{"2":{"233":1,"235":1,"238":1,"1412":1,"1667":1},"3":{"0":1,"233":1,"235":3,"238":1,"1199":6,"1203":1,"1207":2,"1333":2,"1338":5,"1378":1,"1379":3,"1395":2,"1412":1,"1414":1,"1437":1,"1442":7,"1495":1,"1667":1}}],["selfhttpoutputcache",{"3":{"1379":1}}],["selfhttpoutputcachewarmuptask",{"2":{"1378":1},"3":{"235":4,"1199":1,"1203":1,"1207":2,"1333":1,"1338":4,"1378":3,"1379":8,"1394":1}}],["selfonlydeletefixture",{"3":{"1199":2,"1207":1,"1435":4}}],["selfmappingupcaster",{"3":{"1199":2,"1207":1}}],["selfcheckinoutcome",{"3":{"1199":5,"1203":1,"1207":2,"1395":20}}],["self",{"1":{"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"294":1,"295":1,"296":1,"787":1,"788":1,"789":1,"790":1,"791":1,"792":1,"793":1,"794":1,"795":1,"1877":1,"1878":1,"1879":1,"1880":1,"1881":1,"1882":1,"1883":1,"1884":1,"1885":1,"1912":1,"1913":1,"1914":1,"1915":1,"1916":1,"1917":1,"1918":1,"1919":1,"1920":1,"1921":1,"1922":1,"1923":1,"1979":1,"1980":1,"1981":1,"1982":1,"1983":1,"1984":1},"3":{"0":8,"55":1,"109":1,"165":1,"195":1,"196":1,"212":3,"214":11,"215":1,"216":1,"219":10,"235":3,"236":1,"243":1,"245":1,"282":1,"288":1,"332":1,"333":1,"341":1,"359":1,"360":1,"389":1,"484":1,"486":1,"500":2,"563":1,"568":1,"684":1,"688":2,"689":1,"690":2,"691":3,"692":2,"707":1,"733":1,"757":1,"758":1,"787":1,"790":2,"791":1,"794":1,"821":1,"828":1,"846":1,"853":1,"856":1,"910":1,"964":2,"1020":1,"1050":1,"1052":1,"1100":1,"1161":1,"1192":1,"1212":2,"1235":1,"1237":3,"1247":3,"1270":4,"1271":1,"1285":5,"1287":1,"1291":1,"1299":9,"1308":5,"1309":2,"1310":10,"1311":1,"1316":1,"1322":9,"1323":25,"1327":1,"1331":3,"1335":2,"1338":26,"1349":13,"1358":13,"1372":2,"1374":2,"1377":1,"1379":10,"1387":2,"1394":9,"1395":52,"1401":8,"1405":1,"1406":1,"1410":1,"1414":14,"1415":2,"1416":1,"1430":1,"1433":1,"1435":33,"1437":3,"1440":1,"1442":3,"1454":3,"1458":1,"1459":1,"1466":2,"1468":1,"1485":2,"1492":1,"1495":3,"1509":1,"1525":1,"1533":1,"1551":1,"1574":2,"1576":11,"1581":1,"1585":1,"1590":2,"1599":1,"1610":1,"1612":1,"1629":2,"1631":3,"1640":2,"1664":1,"1748":1,"1763":1,"1778":3,"1791":1,"1809":1,"1844":1,"1876":1,"1877":1,"1883":2,"1911":1,"1912":1,"1922":1,"1923":1,"1978":1,"1979":1,"1989":1,"2000":1,"2001":1,"2006":1,"2008":1,"2021":1,"2028":1,"2029":1,"2074":2,"2078":1,"2126":1,"2141":1,"2145":3,"2148":5,"2149":12,"2151":1,"2168":1,"2206":1,"2207":2,"2231":1}}],["seeing",{"3":{"1247":1,"1300":1,"1310":1,"1349":1,"1358":3,"1368":1,"1380":1,"1394":1,"1395":3,"1414":2,"1435":2}}],["seekable",{"3":{"1285":1,"1322":1}}],["seeks",{"3":{"1278":1,"1285":2,"1401":1}}],["seeking",{"3":{"1228":1,"1229":1}}],["seeked",{"3":{"359":1}}],["seek",{"3":{"200":1,"361":1,"1228":1,"1229":4,"1259":3,"1278":1,"1285":10,"1310":1,"1395":6,"1437":1,"1576":1,"2143":1,"2144":1}}],["seen",{"3":{"100":1,"188":1,"341":2,"344":1,"527":1,"1093":1,"1229":2,"1259":2,"1270":1,"1285":1,"1299":2,"1308":2,"1310":1,"1311":1,"1322":2,"1323":3,"1349":3,"1358":9,"1379":1,"1395":3,"1401":1,"1415":1,"1422":1,"1426":2,"1435":2,"1446":1,"1466":1,"1684":1,"1707":1,"1807":1,"1850":1,"1926":1,"1963":1,"2162":1}}],["sees",{"3":{"0":5,"17":1,"39":1,"41":1,"47":1,"53":1,"78":1,"109":1,"125":2,"126":1,"160":1,"175":1,"177":1,"217":1,"224":1,"244":1,"255":1,"265":1,"318":1,"351":1,"371":1,"382":1,"384":1,"390":1,"471":1,"544":1,"564":1,"583":1,"584":1,"592":1,"609":1,"611":1,"620":1,"639":1,"675":1,"692":1,"698":1,"699":1,"700":1,"714":3,"715":1,"717":2,"725":1,"789":1,"795":1,"798":1,"821":1,"825":1,"826":1,"827":1,"828":1,"829":1,"831":1,"881":1,"926":2,"933":1,"966":1,"982":1,"988":1,"989":1,"1018":2,"1019":1,"1020":1,"1033":1,"1050":2,"1059":1,"1061":1,"1066":1,"1092":1,"1116":2,"1117":1,"1123":1,"1125":2,"1141":1,"1162":1,"1169":1,"1170":1,"1177":1,"1184":1,"1186":1,"1188":1,"1212":2,"1232":1,"1237":4,"1247":7,"1257":1,"1259":6,"1266":1,"1270":6,"1271":5,"1276":1,"1285":20,"1299":4,"1300":3,"1301":1,"1304":2,"1308":9,"1310":9,"1311":1,"1314":1,"1322":7,"1323":16,"1331":1,"1332":1,"1335":1,"1337":2,"1338":6,"1349":8,"1358":27,"1365":1,"1368":1,"1370":1,"1374":1,"1378":1,"1379":6,"1394":19,"1395":12,"1401":8,"1407":1,"1409":1,"1414":8,"1415":1,"1416":4,"1419":1,"1423":1,"1426":3,"1435":25,"1437":2,"1440":1,"1441":1,"1442":1,"1454":1,"1466":2,"1472":1,"1474":1,"1484":1,"1488":2,"1595":1,"1640":1,"1671":1,"1676":1,"1707":2,"1748":2,"1758":1,"1764":1,"1830":2,"1839":1,"1840":1,"1850":1,"1870":1,"1875":1,"1880":1,"1888":1,"1905":1,"1926":1,"1929":1,"1932":1,"1939":1,"1948":1,"1965":1,"1967":1,"1973":1,"1993":1,"1998":1,"1999":1,"2000":1,"2023":2,"2040":1,"2046":1,"2047":1,"2055":1,"2075":1,"2115":1,"2122":1,"2140":1,"2143":1,"2164":1,"2166":1,"2178":1}}],["seeddarkthemeasync",{"3":{"1435":2}}],["seeddevelopersconferenceeventasync",{"3":{"1368":1}}],["seedlive",{"3":{"1435":1}}],["seedpartnersasync",{"3":{"1368":3}}],["seedquestionsasync",{"3":{"1368":1}}],["seedcloudaiconferenceeventasync",{"3":{"1368":1}}],["seedable",{"3":{"1435":1}}],["seedactivitiesasync",{"3":{"1368":1}}],["seedaccountasync",{"3":{"1285":1}}],["seedaccount",{"3":{"1199":5,"1203":1,"1207":2,"1283":2,"1285":6,"1414":1,"1495":1}}],["seedasync",{"2":{"1314":1,"1880":1},"3":{"1285":6,"1314":2,"1322":3,"1368":3,"1379":2,"1414":4,"1880":1}}],["seedallasync",{"2":{"295":2},"3":{"295":2,"1285":1,"1314":1,"1322":2,"1368":1,"1880":1}}],["seededids",{"3":{"1199":2,"1207":1}}],["seeded",{"3":{"207":1,"295":2,"310":1,"499":1,"618":1,"657":1,"747":1,"749":1,"774":1,"1281":1,"1285":2,"1296":1,"1297":1,"1299":2,"1310":3,"1341":1,"1349":3,"1363":3,"1368":7,"1379":1,"1394":8,"1395":1,"1401":2,"1414":4,"1428":1,"1432":1,"1435":11,"1437":2,"1487":3,"1576":1,"1594":1,"1595":2,"1629":1,"1642":1,"1776":2,"1824":1,"1967":1,"2056":1}}],["seedermocks",{"3":{"1199":6,"1207":3}}],["seeders",{"3":{"295":2,"314":1,"583":2,"1272":1,"1276":1,"1283":1,"1285":4,"1299":1,"1310":2,"1322":4,"1776":1,"1850":1}}],["seeder",{"0":{"1376":1},"3":{"0":2,"295":7,"585":1,"698":1,"1237":2,"1246":1,"1247":1,"1253":1,"1270":1,"1285":20,"1299":1,"1310":2,"1314":1,"1322":9,"1349":2,"1358":2,"1359":1,"1363":1,"1368":9,"1379":6,"1411":1,"1414":12,"1436":1,"1437":2}}],["seeding",{"0":{"1283":1,"1363":1,"1411":1},"3":{"0":1,"295":6,"296":1,"310":4,"314":2,"470":1,"583":1,"698":1,"700":1,"946":2,"1192":1,"1202":1,"1203":7,"1207":32,"1283":6,"1285":28,"1299":1,"1310":2,"1314":1,"1322":5,"1349":1,"1358":2,"1363":2,"1368":12,"1376":2,"1379":5,"1411":3,"1414":13,"1435":2,"1437":1,"1440":2,"1443":1,"1486":1,"1499":3,"1594":1,"1595":2,"1599":1,"1617":1,"1630":1,"1637":1,"1640":1,"1661":1,"1663":2,"1776":1,"1880":1,"2012":1}}],["seed",{"0":{"1776":1},"3":{"0":2,"80":1,"146":1,"207":1,"295":6,"314":1,"369":1,"545":1,"551":1,"556":1,"562":1,"564":1,"566":1,"572":3,"583":1,"585":1,"616":1,"618":2,"620":1,"631":11,"632":1,"633":6,"634":2,"635":7,"636":2,"649":1,"651":1,"657":1,"658":1,"665":1,"666":1,"674":1,"698":1,"700":1,"782":2,"861":1,"980":1,"1004":1,"1034":1,"1036":1,"1037":1,"1190":1,"1212":1,"1229":1,"1234":1,"1237":1,"1270":1,"1283":3,"1285":17,"1299":5,"1300":1,"1310":1,"1314":1,"1323":3,"1334":1,"1335":1,"1338":5,"1363":1,"1368":5,"1379":3,"1394":1,"1395":3,"1414":3,"1435":8,"1485":1,"1487":1,"1488":2,"1491":6,"1576":1,"1595":2,"1596":1,"1599":1,"1617":1,"1618":1,"1652":1,"1660":1,"1663":1,"1706":1,"1730":3,"1750":1,"1880":2,"1969":1,"2056":1,"2076":2,"2086":1,"2100":1,"2227":2,"2231":1,"2233":1,"2238":2}}],["seedsignedincookieasync",{"3":{"1435":1}}],["seedsponsorsasync",{"3":{"1368":1}}],["seedspeakersasync",{"3":{"1368":1}}],["seedsampleeventlinksasync",{"3":{"1368":1}}],["seedsessionsasync",{"3":{"1368":2}}],["seeds",{"3":{"0":1,"207":1,"210":1,"295":1,"296":1,"459":1,"464":1,"499":1,"507":1,"583":1,"698":1,"749":2,"1229":1,"1237":1,"1241":1,"1247":1,"1278":1,"1299":2,"1300":3,"1308":2,"1310":6,"1322":2,"1323":6,"1338":1,"1346":1,"1349":6,"1358":1,"1363":2,"1368":1,"1379":1,"1394":11,"1395":1,"1401":3,"1411":1,"1414":3,"1435":7,"1440":3,"1448":1,"1651":1,"1663":1,"1706":1,"1776":1,"2117":1,"2122":2}}],["see",{"0":{"2007":1,"2043":1},"3":{"0":9,"15":6,"17":2,"19":2,"21":1,"22":1,"24":1,"39":1,"47":1,"57":2,"62":1,"66":3,"72":1,"76":2,"91":4,"92":2,"93":2,"95":1,"97":1,"98":1,"101":1,"110":1,"115":4,"117":1,"122":2,"130":8,"132":1,"133":1,"135":3,"136":1,"139":1,"145":1,"146":1,"147":1,"155":3,"164":2,"166":1,"173":1,"175":2,"189":1,"193":4,"194":1,"202":1,"223":2,"228":1,"233":6,"241":5,"245":1,"248":2,"259":1,"272":1,"279":1,"298":2,"301":1,"302":1,"303":1,"316":1,"346":1,"359":1,"368":1,"372":1,"376":1,"386":4,"388":1,"391":1,"395":7,"397":1,"400":2,"413":1,"418":4,"423":2,"424":1,"425":2,"428":2,"432":1,"438":1,"443":1,"446":3,"448":1,"449":1,"451":1,"463":1,"468":4,"472":1,"497":2,"499":1,"505":1,"516":1,"526":2,"528":1,"530":2,"534":2,"537":1,"543":3,"546":1,"547":1,"564":1,"570":2,"593":1,"599":1,"612":1,"617":1,"624":1,"633":1,"638":1,"655":1,"665":1,"676":1,"680":1,"696":2,"698":1,"699":1,"713":1,"718":1,"725":1,"731":1,"733":1,"734":1,"759":1,"764":1,"790":1,"791":1,"797":3,"798":1,"808":1,"809":1,"821":1,"825":1,"826":1,"827":1,"829":2,"836":1,"840":1,"844":1,"852":1,"867":2,"868":1,"869":1,"870":3,"873":1,"875":1,"879":1,"891":1,"899":1,"905":1,"920":1,"922":2,"926":3,"952":3,"957":1,"970":1,"971":1,"974":1,"986":1,"994":1,"995":1,"1016":1,"1018":1,"1019":1,"1020":1,"1028":1,"1032":1,"1033":1,"1040":1,"1048":1,"1050":1,"1057":1,"1067":1,"1068":1,"1074":1,"1101":1,"1108":2,"1114":1,"1116":3,"1122":2,"1135":1,"1140":1,"1162":1,"1191":1,"1193":1,"1212":2,"1213":4,"1214":2,"1218":1,"1223":1,"1225":1,"1228":1,"1229":7,"1230":1,"1231":1,"1232":1,"1236":1,"1237":12,"1246":1,"1247":11,"1251":2,"1253":1,"1259":16,"1263":1,"1265":3,"1267":1,"1269":1,"1270":30,"1271":7,"1280":1,"1282":1,"1285":46,"1288":1,"1290":2,"1299":39,"1300":10,"1301":1,"1305":1,"1308":26,"1309":3,"1310":46,"1311":12,"1315":1,"1318":1,"1320":1,"1322":27,"1323":46,"1338":23,"1340":1,"1341":1,"1342":1,"1347":1,"1349":28,"1356":1,"1358":100,"1366":1,"1368":12,"1372":1,"1379":31,"1388":1,"1391":1,"1394":46,"1395":66,"1398":2,"1401":33,"1408":1,"1414":21,"1415":2,"1416":12,"1422":1,"1426":26,"1427":1,"1428":1,"1430":2,"1432":1,"1435":118,"1437":3,"1440":1,"1441":1,"1442":3,"1443":2,"1452":2,"1453":1,"1454":6,"1458":2,"1459":1,"1462":2,"1464":2,"1466":10,"1470":1,"1473":2,"1475":2,"1485":1,"1486":3,"1487":3,"1488":1,"1491":2,"1495":8,"1496":1,"1497":1,"1499":2,"1501":1,"1525":1,"1526":1,"1529":1,"1530":1,"1544":1,"1546":1,"1550":1,"1560":1,"1561":1,"1573":2,"1575":1,"1576":1,"1580":1,"1582":2,"1583":1,"1587":2,"1590":1,"1595":1,"1596":1,"1620":1,"1626":1,"1629":7,"1630":13,"1631":8,"1633":2,"1634":3,"1636":1,"1637":4,"1638":8,"1639":6,"1640":1,"1645":1,"1647":3,"1648":2,"1649":2,"1650":7,"1651":1,"1652":3,"1653":1,"1654":1,"1655":5,"1669":1,"1676":1,"1679":1,"1688":1,"1692":1,"1700":1,"1702":3,"1706":1,"1717":1,"1718":1,"1725":1,"1726":1,"1732":1,"1748":2,"1754":1,"1756":1,"1759":1,"1763":1,"1764":2,"1767":4,"1802":1,"1803":1,"1812":1,"1815":1,"1816":1,"1818":1,"1824":1,"1831":1,"1838":1,"1839":1,"1840":1,"1851":1,"1860":1,"1876":1,"1911":2,"1914":1,"1921":2,"1923":1,"1936":1,"1956":1,"1966":1,"1978":1,"1993":1,"1996":2,"2001":2,"2009":1,"2025":1,"2027":1,"2039":1,"2048":1,"2099":1,"2105":1,"2118":1,"2128":1,"2130":1,"2132":1,"2135":1,"2139":1,"2141":1,"2145":1,"2156":1,"2159":1,"2170":1,"2171":1,"2173":1,"2208":1,"2213":1,"2215":1,"2230":1,"2231":2,"2241":1,"2243":1,"2244":2}}],["setviewportsize",{"3":{"1590":1,"1595":1}}],["setvalues",{"3":{"1285":1}}],["setvalidator",{"2":{"1829":1,"1832":1},"3":{"1271":7,"1401":4,"1414":3,"1829":1,"1832":1}}],["setvariantdiscount",{"3":{"674":1}}],["setvariantdiscounthandler",{"3":{"674":2}}],["sethostconnectionstring",{"3":{"1435":1,"1487":1}}],["setnameddatasource",{"3":{"1435":2}}],["setbearertoken",{"3":{"1428":1,"1435":2,"1487":2,"1490":1}}],["setb",{"3":{"1358":1}}],["setbrowsercultureasync",{"2":{"265":1,"1525":1,"1652":1,"2082":1},"3":{"265":1,"1310":1,"1323":4,"1525":1,"1652":1,"2082":1}}],["setenvironmentvariable",{"3":{"1435":2,"1487":1}}],["seteventquestionanswers",{"3":{"1349":1,"1358":2}}],["seteventspeakers",{"3":{"1349":1,"1358":2}}],["setevent",{"3":{"1349":7,"1358":8}}],["seterror",{"3":{"1323":1}}],["setcategory",{"3":{"1349":1,"1358":1}}],["setcategoryitems",{"3":{"1349":2,"1358":1}}],["setcorrelationid",{"3":{"1310":4}}],["setconcurrencyetag",{"3":{"341":1,"922":1,"1299":1,"1310":6,"1665":1,"1873":1}}],["setx",{"3":{"1342":1}}],["setxxx",{"3":{"1168":2}}],["setdiscount",{"3":{"1766":1}}],["setdiscountonallvariants",{"2":{"1026":1,"1766":1},"3":{"1026":1,"1766":2}}],["setdescription",{"3":{"1416":1}}],["setdarkmodeasync",{"3":{"1323":4,"2082":1}}],["setinventory",{"2":{"1768":1},"3":{"1745":2,"1748":3,"1768":1}}],["setinventoryitemcommand",{"3":{"1270":1}}],["setitems",{"2":{"1170":1},"3":{"1168":2,"1170":1,"1231":1,"1237":2,"1342":1,"1349":4,"1358":2,"1401":2,"1437":1,"1661":1,"1809":1}}],["setobserved",{"3":{"1416":2}}],["setoptions",{"3":{"1401":4}}],["setoldestdueage",{"3":{"1285":1}}],["setoldestpendingage",{"3":{"1259":2,"2155":1}}],["setoriginalrowversion",{"2":{"341":1,"342":1,"1870":1,"1871":1,"1873":1,"1875":1,"1876":1},"3":{"0":1,"341":3,"342":1,"926":1,"1237":2,"1270":2,"1285":8,"1310":2,"1358":4,"1870":1,"1871":1,"1873":1,"1875":1,"1876":1}}],["setprincipal",{"3":{"1435":2}}],["setproperty",{"3":{"1285":2}}],["setpropertycalls",{"3":{"1285":1}}],["setproperties",{"3":{"1285":1}}],["setproductdiscount",{"3":{"674":1}}],["setproductdiscounthandler",{"3":{"674":2}}],["setpublishedasync",{"3":{"1394":1}}],["setpermissionasync",{"3":{"1435":3}}],["setpermissionsasync",{"3":{"1310":2}}],["setpermissions",{"3":{"1299":1}}],["setpendingdepth",{"3":{"1259":3,"1285":2}}],["setresponse",{"3":{"1431":1,"1435":3}}],["setrendererinfo",{"2":{"955":1,"956":1},"3":{"0":1,"954":3,"955":1,"956":1,"1431":2,"1435":3}}],["setrooms",{"3":{"1349":1,"1358":2}}],["setroom",{"3":{"1349":1,"1358":1}}],["setrowsperpageasync",{"3":{"1323":1}}],["setroleasync",{"3":{"1323":7,"1414":1}}],["setroles",{"3":{"1299":1}}],["setrolesasync",{"3":{"1299":1,"1310":2,"1323":7,"1414":2}}],["setrolepermissionsrequestvalidator",{"2":{"1296":1},"3":{"1199":1,"1203":1,"1207":1,"1296":1,"1299":5}}],["setrolepermissionsrequest",{"3":{"1199":6,"1203":1,"1207":1,"1296":1,"1299":3,"1323":2}}],["setlivepoll",{"3":{"1401":4}}],["setlockedasync",{"3":{"1299":3,"1310":1,"1414":1}}],["setloadfailed",{"2":{"111":1,"112":1},"3":{"109":1,"111":1,"112":1,"1323":1}}],["setleaderboardparticipationasync",{"3":{"1395":7}}],["setleaderboardparticipation",{"3":{"1203":1,"1207":2,"1271":1,"1285":1,"1395":4}}],["setleaderboardparticipationrequest",{"3":{"1199":5,"1203":1,"1207":2,"1395":10,"1435":1}}],["setleaderboardparticipationhandlertests",{"3":{"1199":1,"1207":3,"1395":1}}],["setleaderboardparticipationhandler",{"2":{"692":1,"693":1},"3":{"690":1,"692":1,"693":1,"1199":2,"1203":1,"1207":2,"1271":2,"1285":3,"1395":15}}],["setauthenticationstate",{"3":{"1435":3}}],["setactionsasync",{"3":{"1415":2}}],["setavatarasync",{"3":{"1414":2}}],["setavatarurl",{"3":{"1414":3}}],["seta",{"3":{"1358":1}}],["setapplicationname",{"3":{"665":1,"1338":1}}],["setasync",{"3":{"243":1,"733":2,"1270":2,"1299":1,"1300":15,"1310":1,"1323":2,"1415":1,"1416":12}}],["setunreadcount",{"3":{"1323":5}}],["setuserrolesrequestvalidator",{"2":{"1296":1},"3":{"1199":1,"1203":1,"1207":1,"1296":1,"1299":4}}],["setuserrolesrequest",{"3":{"1199":6,"1203":1,"1207":1,"1296":1,"1299":2,"1310":1,"1323":3,"1495":1}}],["setuser",{"3":{"954":1,"1431":1,"1435":3,"1487":1}}],["setuseravatarcommandmarkertests",{"3":{"1199":1,"1207":2}}],["setuseravatarcommandvalidator",{"3":{"1199":1,"1203":1,"1207":2,"1414":7}}],["setuseravatarcommand",{"2":{"1264":1,"1410":1},"3":{"1199":7,"1203":1,"1207":2,"1264":2,"1410":2,"1414":9}}],["setuseravatarhandlertests",{"3":{"1199":1,"1207":2,"1414":1}}],["setuseravatarhandler",{"2":{"1525":1,"2126":1},"3":{"444":2,"926":1,"1199":3,"1203":1,"1207":2,"1270":3,"1285":8,"1322":4,"1410":2,"1414":19,"1495":1,"1525":1,"2126":2}}],["setuseravatar",{"3":{"444":2,"926":1,"1203":3,"1207":10,"1264":1,"1270":1,"1285":1,"1410":1,"1414":7,"1435":1,"1495":2}}],["setupdefaultroles",{"3":{"1435":1}}],["setupdefaultclient",{"3":{"1435":1}}],["setuproles",{"3":{"1435":1}}],["setups",{"3":{"1368":1}}],["setuppublicroomfilter",{"3":{"1358":1}}],["setup",{"0":{"1471":1},"3":{"0":1,"61":1,"140":1,"373":1,"402":1,"448":1,"619":1,"749":1,"953":1,"954":1,"955":2,"1247":2,"1285":1,"1299":2,"1308":1,"1310":3,"1323":2,"1395":1,"1414":1,"1416":3,"1426":1,"1428":1,"1430":1,"1431":1,"1435":14,"1452":3,"1453":1,"1454":3,"1455":1,"1470":1,"1471":20,"1474":1,"1481":1,"1482":2,"1487":2,"1564":1,"1569":1,"1576":1,"1685":1,"1750":1,"2128":1,"2130":1,"2138":1,"2141":1,"2153":1}}],["settag",{"3":{"1426":1}}],["settable",{"3":{"382":1,"881":1,"954":1,"1259":1,"1270":1,"1285":2,"1299":1,"1310":1,"1322":1,"1323":3,"1338":1,"1358":3,"1379":1,"1395":1,"1401":2,"1435":8,"1890":1,"1948":1}}],["settling",{"3":{"831":1}}],["settleasync",{"3":{"1435":1}}],["settleselector",{"3":{"1435":2}}],["settles",{"3":{"350":1,"639":1,"904":1,"1322":4,"1338":1,"1354":1,"1358":1,"1394":1,"1414":2,"1432":1,"1435":4,"1487":2,"1685":1}}],["settled",{"3":{"175":1,"325":1,"732":1,"906":1,"1299":2,"1310":1,"1336":1,"1338":4,"1358":1,"1394":1,"1401":2,"1426":1,"1432":1,"1435":1,"1452":1,"1454":1,"1487":1,"1827":1,"1925":1,"2142":1}}],["settle",{"3":{"160":1,"716":1,"1004":1,"1190":1,"1247":1,"1435":6,"1483":1,"1685":1,"1923":1}}],["settextasync",{"3":{"1416":5}}],["settenant",{"2":{"698":1},"3":{"698":4,"1259":1,"1270":3,"1285":3,"1310":2,"1316":1,"1322":3}}],["setter",{"3":{"265":1,"341":1,"342":1,"674":1,"675":1,"698":1,"883":1,"963":1,"1168":4,"1169":2,"1170":3,"1237":1,"1247":1,"1270":1,"1274":1,"1285":2,"1310":2,"1322":4,"1323":8,"1341":4,"1342":2,"1349":14,"1358":4,"1394":1,"1395":4,"1401":3,"1404":1,"1414":3,"1416":1,"1430":1,"1435":11,"1808":1,"1843":1,"1870":1,"2071":1,"2086":1,"2163":1}}],["setters",{"3":{"0":2,"265":1,"962":1,"1167":1,"1168":3,"1229":1,"1231":1,"1237":4,"1242":1,"1274":1,"1278":1,"1285":2,"1308":1,"1323":2,"1349":5,"1358":3,"1395":2,"1401":3,"1435":8,"1487":1,"1488":1,"1525":2,"1534":1,"1540":1,"1590":1,"1807":2,"1809":2,"1810":1,"1811":1,"1987":1,"2089":1}}],["settokensasync",{"2":{"423":1,"426":1,"428":1,"430":1,"505":1,"512":1,"514":1},"3":{"423":1,"426":1,"428":1,"430":1,"505":1,"507":1,"511":1,"512":3,"514":1,"1323":7,"1416":2,"1435":2}}],["setting",{"3":{"0":1,"20":1,"24":2,"32":1,"73":1,"100":1,"142":1,"145":1,"178":2,"186":1,"193":1,"195":2,"200":1,"201":2,"274":1,"290":1,"291":1,"296":1,"337":1,"403":1,"418":1,"422":1,"423":1,"499":1,"599":1,"640":1,"673":1,"719":2,"740":1,"741":3,"750":1,"776":1,"819":2,"820":1,"821":1,"827":1,"828":1,"831":1,"832":1,"837":1,"838":2,"882":1,"905":1,"907":1,"922":1,"931":3,"933":1,"935":1,"1107":1,"1108":2,"1112":1,"1118":1,"1175":1,"1178":1,"1229":1,"1232":1,"1237":2,"1256":2,"1259":5,"1267":1,"1270":2,"1273":2,"1285":19,"1291":1,"1299":5,"1300":6,"1308":3,"1310":4,"1311":2,"1322":8,"1323":8,"1325":1,"1334":1,"1338":10,"1349":2,"1358":1,"1394":3,"1395":9,"1401":1,"1415":1,"1416":7,"1421":1,"1426":2,"1435":7,"1437":1,"1439":1,"1440":2,"1441":1,"1443":1,"1454":2,"1464":4,"1465":1,"1466":12,"1469":1,"1525":1,"1590":1,"1626":1,"1700":1,"1720":1,"1766":1,"1768":1,"1842":2,"1844":1,"1870":1,"1903":1,"1928":1,"1999":1,"2000":1,"2010":1,"2019":1,"2056":1,"2073":1,"2130":1,"2148":2,"2202":1,"2231":1}}],["settingstests",{"3":{"1207":6,"1322":2}}],["settings",{"0":{"1319":1},"2":{"130":1,"201":1,"819":1,"1993":2},"3":{"0":11,"22":1,"24":2,"59":1,"73":1,"74":1,"130":1,"135":3,"139":3,"178":1,"186":2,"190":1,"193":1,"200":1,"201":1,"217":1,"243":1,"281":1,"318":2,"330":1,"337":1,"403":1,"530":1,"539":1,"583":2,"586":1,"633":1,"640":1,"641":1,"649":2,"650":1,"672":2,"673":1,"674":5,"675":2,"676":2,"696":1,"701":1,"707":3,"710":1,"713":1,"715":3,"727":1,"733":1,"741":2,"742":1,"743":1,"819":2,"827":6,"829":2,"831":1,"832":2,"834":1,"838":1,"840":1,"905":2,"907":1,"909":1,"914":1,"932":1,"946":1,"954":1,"980":1,"1004":1,"1034":1,"1042":1,"1045":1,"1107":1,"1112":1,"1116":1,"1119":1,"1127":1,"1175":3,"1177":3,"1178":2,"1179":1,"1184":4,"1189":1,"1192":5,"1202":2,"1203":13,"1207":48,"1212":2,"1259":6,"1267":1,"1270":4,"1272":1,"1273":1,"1276":1,"1279":1,"1283":1,"1285":53,"1286":1,"1287":3,"1289":1,"1293":1,"1294":1,"1299":26,"1300":12,"1301":1,"1308":6,"1310":36,"1312":2,"1315":1,"1319":9,"1322":50,"1323":38,"1325":1,"1326":1,"1334":1,"1336":1,"1337":2,"1338":59,"1358":11,"1365":1,"1378":1,"1379":1,"1395":15,"1414":2,"1415":7,"1416":19,"1417":1,"1418":1,"1426":17,"1428":3,"1435":13,"1436":1,"1437":7,"1441":1,"1442":2,"1443":1,"1454":1,"1455":1,"1465":1,"1466":2,"1473":1,"1475":1,"1483":1,"1486":2,"1487":2,"1495":10,"1525":1,"1576":2,"1581":1,"1625":2,"1626":1,"1660":1,"1665":1,"1669":2,"1674":1,"1707":1,"1733":1,"1747":1,"1764":1,"1824":1,"1841":1,"1880":1,"1982":1,"1993":2,"1999":1,"2001":1,"2011":5,"2013":1,"2023":2,"2026":1,"2027":1,"2152":1,"2166":1,"2172":1,"2179":1,"2187":1,"2195":1,"2197":1,"2201":1,"2202":1,"2231":1}}],["setsharedenvironment",{"3":{"1435":1}}],["setsessioncategoryitems",{"3":{"1358":1}}],["setsessionquestionanswers",{"3":{"1358":1}}],["setsessionspeakers",{"3":{"1358":1}}],["setsession",{"3":{"1349":4,"1358":4}}],["setscrollposition",{"3":{"1323":1}}],["setspeaker",{"3":{"1349":3,"1358":1}}],["setspeakerquestionanswers",{"3":{"1349":1,"1358":2}}],["setspeakercategoryitems",{"3":{"1309":1,"1349":1,"1358":2}}],["setsproperty",{"2":{"1843":1},"3":{"1285":4,"1843":1}}],["setsvaluegeneratednever",{"3":{"1285":1}}],["setstatus",{"3":{"1394":1,"1401":1}}],["setstablenameandkey",{"3":{"1285":1}}],["setstoredpermissionsasync",{"2":{"2199":1,"2202":1},"3":{"1059":1,"1299":6,"1310":1,"1323":4,"2199":1,"2202":1}}],["setsrequiredmembers",{"3":{"1229":3}}],["sets",{"3":{"0":4,"17":1,"24":1,"31":2,"38":1,"43":1,"59":1,"72":1,"74":1,"94":1,"178":1,"189":1,"202":1,"243":1,"263":1,"265":3,"296":1,"305":1,"311":1,"341":1,"342":1,"370":1,"397":4,"399":1,"446":1,"448":2,"458":1,"499":1,"507":2,"528":1,"557":1,"562":1,"564":1,"578":1,"601":1,"611":1,"617":1,"626":2,"640":4,"650":1,"657":1,"660":1,"698":1,"702":1,"705":1,"707":1,"715":1,"733":1,"736":1,"740":2,"741":1,"755":1,"757":1,"764":2,"767":1,"810":1,"819":1,"826":1,"827":2,"854":1,"863":1,"881":2,"888":1,"905":1,"907":1,"916":1,"926":1,"931":1,"933":2,"954":1,"963":1,"979":1,"1004":2,"1018":2,"1042":1,"1083":1,"1124":1,"1134":1,"1140":1,"1160":1,"1162":1,"1184":2,"1212":2,"1213":1,"1229":2,"1231":1,"1237":2,"1239":1,"1242":1,"1247":4,"1257":1,"1259":6,"1271":6,"1274":1,"1275":1,"1285":21,"1287":1,"1289":1,"1294":1,"1299":13,"1300":6,"1308":5,"1310":34,"1311":2,"1322":15,"1323":42,"1325":1,"1326":1,"1335":1,"1338":21,"1348":1,"1349":10,"1351":1,"1358":55,"1363":1,"1368":8,"1378":1,"1379":2,"1394":19,"1395":23,"1401":8,"1411":1,"1414":15,"1415":6,"1416":10,"1426":1,"1428":1,"1429":1,"1430":1,"1431":1,"1435":31,"1440":6,"1442":5,"1443":1,"1444":1,"1446":1,"1448":1,"1452":2,"1453":2,"1454":3,"1458":1,"1463":2,"1466":3,"1474":1,"1475":2,"1483":1,"1487":3,"1488":1,"1489":1,"1490":1,"1491":1,"1495":2,"1523":1,"1525":2,"1530":1,"1534":1,"1548":1,"1559":1,"1576":1,"1590":1,"1595":2,"1610":1,"1626":1,"1629":1,"1639":1,"1640":5,"1648":1,"1667":1,"1677":1,"1707":1,"1747":1,"1748":2,"1753":1,"1764":2,"1809":1,"1810":1,"1829":1,"1860":1,"1870":1,"1897":1,"1909":1,"1932":1,"1969":1,"1992":1,"2000":1,"2007":2,"2008":1,"2009":2,"2070":1,"2076":1,"2082":2,"2125":1,"2130":2,"2137":1,"2155":1,"2158":1,"2163":1,"2166":1,"2185":1,"2192":1}}],["set",{"0":{"1235":1,"1649":1,"1677":1,"1895":1,"2074":1,"2077":1},"2":{"764":1,"1135":1,"1136":1,"1911":1,"1923":1},"3":{"0":24,"17":1,"19":2,"24":2,"25":1,"26":2,"31":2,"32":1,"33":3,"39":3,"41":1,"45":1,"68":2,"79":1,"81":1,"82":1,"90":1,"94":1,"95":2,"98":3,"109":1,"121":1,"122":1,"135":1,"139":1,"142":2,"145":5,"146":1,"147":2,"149":2,"157":1,"164":1,"166":1,"167":1,"173":1,"175":1,"177":1,"178":1,"180":1,"181":2,"186":4,"189":1,"195":2,"200":1,"202":1,"215":1,"243":2,"245":1,"248":2,"259":1,"261":1,"265":8,"273":1,"291":2,"317":1,"318":1,"330":2,"332":1,"335":4,"337":2,"349":1,"350":1,"351":1,"352":1,"370":2,"386":3,"388":1,"392":1,"397":2,"402":1,"411":1,"412":1,"415":1,"424":1,"441":1,"448":2,"452":2,"461":1,"493":1,"494":1,"498":1,"499":3,"500":1,"507":2,"509":1,"514":1,"526":2,"528":2,"532":1,"549":2,"551":1,"554":2,"556":4,"564":1,"566":1,"567":1,"571":1,"572":1,"574":1,"576":1,"578":1,"584":2,"585":2,"592":1,"595":1,"598":1,"599":4,"601":1,"602":1,"612":1,"616":1,"618":2,"619":1,"632":1,"633":2,"640":1,"641":1,"642":1,"644":1,"650":1,"652":1,"655":1,"656":1,"657":1,"659":1,"668":2,"674":2,"689":1,"690":1,"697":1,"698":2,"702":1,"703":1,"707":2,"711":1,"715":1,"725":1,"727":1,"733":4,"749":3,"757":1,"764":2,"766":3,"780":2,"790":2,"809":1,"827":2,"832":1,"833":2,"837":3,"857":1,"859":1,"861":3,"863":1,"864":1,"869":1,"873":1,"875":2,"876":1,"881":4,"884":2,"897":3,"900":2,"903":1,"905":1,"907":2,"914":1,"924":1,"926":3,"929":1,"933":1,"937":1,"938":1,"939":2,"940":3,"941":1,"942":1,"948":1,"953":3,"963":4,"964":5,"965":4,"966":3,"973":1,"974":1,"980":1,"981":1,"988":1,"996":1,"998":2,"999":1,"1006":1,"1020":1,"1033":1,"1034":1,"1036":1,"1042":2,"1043":2,"1050":1,"1058":1,"1059":4,"1060":1,"1067":3,"1079":1,"1082":1,"1090":2,"1095":1,"1107":1,"1108":1,"1116":2,"1124":3,"1133":2,"1135":1,"1136":1,"1150":1,"1163":1,"1168":1,"1175":1,"1184":1,"1196":1,"1212":6,"1214":1,"1229":7,"1234":1,"1235":1,"1237":17,"1241":7,"1242":5,"1243":3,"1247":35,"1252":1,"1254":1,"1256":1,"1259":22,"1261":1,"1264":2,"1265":2,"1266":1,"1270":21,"1271":8,"1273":3,"1274":1,"1275":2,"1276":1,"1278":3,"1279":1,"1282":2,"1285":102,"1286":1,"1287":1,"1289":1,"1292":2,"1295":1,"1296":2,"1297":1,"1299":86,"1300":12,"1301":1,"1303":1,"1308":16,"1309":6,"1310":57,"1311":6,"1315":1,"1316":1,"1317":2,"1319":2,"1321":2,"1322":49,"1323":87,"1324":1,"1325":2,"1327":1,"1337":1,"1338":34,"1341":1,"1342":1,"1347":1,"1348":1,"1349":37,"1352":5,"1356":1,"1357":1,"1358":170,"1361":2,"1363":2,"1368":10,"1376":1,"1379":7,"1383":2,"1384":1,"1389":1,"1390":1,"1394":59,"1395":68,"1400":3,"1401":39,"1404":2,"1407":2,"1408":2,"1411":2,"1412":2,"1413":1,"1414":51,"1415":4,"1416":19,"1420":1,"1421":1,"1426":9,"1429":1,"1430":2,"1431":1,"1432":1,"1435":135,"1436":1,"1437":9,"1439":1,"1440":3,"1441":1,"1442":1,"1443":1,"1444":1,"1445":1,"1449":1,"1452":1,"1453":1,"1454":8,"1455":2,"1457":1,"1458":6,"1459":3,"1460":1,"1464":2,"1466":18,"1469":3,"1471":1,"1474":2,"1476":5,"1478":1,"1479":1,"1480":1,"1487":3,"1488":2,"1489":1,"1495":7,"1503":1,"1508":1,"1524":1,"1525":8,"1533":5,"1534":1,"1559":1,"1570":1,"1574":1,"1576":8,"1590":4,"1595":4,"1598":1,"1610":1,"1612":1,"1629":8,"1630":12,"1631":3,"1632":1,"1634":1,"1636":1,"1637":3,"1638":1,"1639":2,"1640":5,"1645":1,"1647":2,"1649":3,"1650":1,"1652":1,"1654":1,"1658":1,"1661":1,"1663":1,"1665":1,"1666":3,"1667":3,"1669":1,"1670":1,"1672":1,"1673":1,"1675":1,"1676":5,"1677":1,"1678":1,"1683":1,"1685":2,"1691":1,"1692":1,"1694":2,"1698":1,"1702":1,"1723":1,"1726":2,"1734":1,"1735":1,"1736":1,"1741":2,"1745":3,"1747":3,"1748":3,"1764":3,"1766":1,"1767":3,"1779":1,"1780":1,"1801":2,"1802":1,"1807":2,"1809":2,"1819":1,"1822":1,"1827":1,"1840":1,"1843":4,"1844":1,"1849":1,"1860":1,"1862":1,"1876":1,"1894":4,"1895":2,"1897":1,"1898":3,"1908":1,"1911":2,"1915":1,"1919":1,"1921":1,"1923":1,"1931":1,"1932":1,"1954":1,"1961":3,"1962":1,"1974":4,"1976":1,"1978":1,"1982":1,"1983":1,"1984":1,"1987":3,"1989":1,"1993":2,"2001":1,"2004":1,"2007":1,"2009":2,"2011":1,"2026":2,"2028":1,"2041":1,"2044":1,"2056":1,"2064":2,"2072":1,"2077":1,"2078":2,"2079":1,"2082":3,"2084":2,"2089":1,"2097":1,"2105":1,"2106":3,"2115":1,"2118":1,"2122":1,"2126":1,"2128":1,"2146":1,"2148":2,"2152":2,"2155":5,"2156":1,"2159":2,"2163":1,"2165":2,"2173":1,"2179":1,"2185":1,"2187":1,"2195":1,"2196":1,"2198":2,"2199":1,"2213":1,"2226":1,"2231":1}}],["securely",{"3":{"1763":1}}],["secure",{"2":{"601":1,"1107":1,"1108":1,"1111":1,"1467":1},"3":{"0":1,"29":1,"215":1,"506":1,"507":1,"508":1,"601":1,"1107":1,"1108":2,"1109":1,"1111":1,"1183":1,"1297":1,"1299":3,"1310":4,"1319":2,"1322":2,"1323":1,"1338":1,"1415":2,"1416":7,"1437":1,"1454":1,"1465":1,"1466":4,"1467":1,"1468":1,"1585":1,"1640":3,"1659":1,"1668":1,"1899":1}}],["securestorage",{"2":{"509":1,"511":1,"513":1},"3":{"0":2,"507":3,"508":1,"509":2,"510":1,"511":3,"513":1,"1184":1,"1187":1,"1212":1,"1323":7,"1415":2,"1416":5,"1638":1,"1640":6,"2231":1}}],["securitykey",{"3":{"1299":2}}],["securityalgorithms",{"3":{"1299":3,"1435":1}}],["securityheadersextensions",{"3":{"1199":1,"1203":1,"1207":1,"1335":1,"1338":4,"1442":1}}],["securityheaderstestsbase",{"2":{"218":1},"3":{"218":2,"572":5,"1199":3,"1207":2,"1338":1,"1427":1,"1435":10,"1437":1,"1487":3,"1492":1,"1495":1,"2054":1}}],["securityheaderstests",{"2":{"2150":1},"3":{"217":1,"218":2,"572":4,"575":1,"1199":2,"1207":4,"1435":4,"1436":1,"1437":2,"1486":2,"1487":1,"1534":1,"2150":1}}],["securityheaders",{"3":{"214":2,"216":3,"217":1,"219":1,"220":1,"674":1,"980":1,"1108":1,"1207":7,"1208":2,"1335":2,"1338":28,"1442":6,"1449":1,"1495":1,"1525":1,"1576":1,"1584":1,"1585":1,"2010":1,"2148":8,"2151":3}}],["securityheadersmiddleware",{"2":{"214":1,"217":1,"2010":1,"2148":1,"2152":1},"3":{"214":1,"217":1,"1199":3,"1203":1,"1207":1,"1323":3,"1335":1,"1338":12,"1415":1,"1435":1,"1437":1,"1442":1,"1495":2,"2010":1,"2148":1,"2152":1}}],["securityheadersmiddlewaretests",{"2":{"214":1,"2150":1},"3":{"214":1,"1199":1,"1207":4,"1338":5,"1576":1,"1585":1,"2150":1}}],["securityheaderssettings",{"2":{"0":1,"214":2,"216":1,"217":1,"1338":1,"1576":1,"2148":1},"3":{"0":1,"214":2,"216":1,"217":1,"674":1,"1199":5,"1203":1,"1207":1,"1335":1,"1338":7,"1576":1,"2148":2}}],["security",{"0":{"1335":1,"1547":1,"1562":1},"1":{"211":1,"212":1,"213":1,"214":1,"215":1,"216":1,"217":1,"218":1,"219":1,"220":1,"221":1,"2146":1,"2147":1,"2148":1,"2149":1,"2150":1,"2151":1,"2152":1},"2":{"214":1,"665":1,"1334":1,"1422":1,"1673":1,"2148":1},"3":{"0":4,"23":1,"25":1,"59":1,"103":1,"177":1,"179":1,"207":2,"211":1,"213":2,"214":4,"216":2,"217":3,"218":1,"219":7,"220":1,"221":1,"228":1,"246":1,"283":1,"284":1,"311":1,"312":1,"313":2,"333":1,"344":1,"353":1,"358":2,"362":2,"369":2,"373":1,"375":1,"376":3,"395":1,"403":1,"419":1,"440":1,"509":1,"563":1,"572":4,"573":1,"574":2,"575":1,"602":1,"607":1,"612":2,"617":1,"665":1,"674":1,"676":1,"684":1,"718":1,"751":2,"752":1,"773":1,"778":1,"793":1,"810":1,"825":1,"826":2,"827":2,"830":1,"837":1,"854":1,"873":1,"946":2,"948":1,"950":2,"980":1,"1011":1,"1075":1,"1077":1,"1108":3,"1115":1,"1117":3,"1186":1,"1189":1,"1192":2,"1202":2,"1203":13,"1207":44,"1208":2,"1212":2,"1216":2,"1228":1,"1229":2,"1236":1,"1237":5,"1239":1,"1241":3,"1247":14,"1253":1,"1259":1,"1263":1,"1270":13,"1271":6,"1273":1,"1276":1,"1282":1,"1283":1,"1284":1,"1285":36,"1286":2,"1289":2,"1291":1,"1296":1,"1299":112,"1300":5,"1303":1,"1304":1,"1305":1,"1306":1,"1307":1,"1308":22,"1310":38,"1311":3,"1317":1,"1319":2,"1321":1,"1322":41,"1323":80,"1324":1,"1334":1,"1335":5,"1336":1,"1337":2,"1338":69,"1346":2,"1347":1,"1349":12,"1352":2,"1354":2,"1356":1,"1358":81,"1364":1,"1365":1,"1368":3,"1371":1,"1372":1,"1379":20,"1383":1,"1387":1,"1388":1,"1390":2,"1394":46,"1395":65,"1401":37,"1402":1,"1405":1,"1406":1,"1408":1,"1410":1,"1414":58,"1415":10,"1416":50,"1418":1,"1420":1,"1421":1,"1422":1,"1426":9,"1428":1,"1435":58,"1436":4,"1437":4,"1440":1,"1441":1,"1442":5,"1449":1,"1453":2,"1454":3,"1455":1,"1458":1,"1460":1,"1464":6,"1465":1,"1466":8,"1468":1,"1471":2,"1473":1,"1474":1,"1476":1,"1482":1,"1486":2,"1487":5,"1491":2,"1492":1,"1495":5,"1498":2,"1524":1,"1525":7,"1529":2,"1530":1,"1531":1,"1533":1,"1534":2,"1547":1,"1551":1,"1553":2,"1561":1,"1562":2,"1574":3,"1575":1,"1576":9,"1580":2,"1581":2,"1582":1,"1584":3,"1585":6,"1587":2,"1589":1,"1590":3,"1594":3,"1595":3,"1596":1,"1599":1,"1626":1,"1638":1,"1640":1,"1659":1,"1666":1,"1669":2,"1670":1,"1672":1,"1673":1,"1674":1,"1768":1,"1778":2,"1795":2,"1800":1,"1813":1,"1814":1,"1816":1,"1828":1,"1898":1,"1902":2,"1903":1,"1904":2,"1907":1,"1932":1,"1936":1,"1939":1,"1964":2,"1965":1,"1967":1,"1971":1,"1978":1,"1982":1,"1984":1,"1992":1,"1996":1,"1999":1,"2000":2,"2001":2,"2010":4,"2044":1,"2054":2,"2074":1,"2077":1,"2125":3,"2127":2,"2142":1,"2145":2,"2146":3,"2147":3,"2148":4,"2149":3,"2152":5,"2159":1,"2194":1,"2198":1,"2202":1,"2207":1,"2218":2,"2219":3,"2231":2}}],["sec",{"3":{"0":2,"25":1,"42":2,"103":1,"123":2,"179":2,"189":2,"198":1,"207":1,"209":1,"217":2,"246":3,"284":3,"333":5,"344":1,"353":2,"373":7,"384":3,"403":6,"427":2,"442":1,"602":4,"612":2,"643":3,"718":3,"747":1,"749":1,"793":4,"830":4,"831":1,"873":7,"876":1,"908":1,"920":1,"921":1,"1184":1,"1247":19,"1270":3,"1285":3,"1297":1,"1299":4,"1300":3,"1308":2,"1310":5,"1322":11,"1323":1,"1338":6,"1354":1,"1358":2,"1401":1,"1414":2,"1415":3,"1416":6,"1435":5,"1446":1,"1452":1,"1466":4,"1533":1,"1968":2,"1970":1,"1971":1,"1999":2}}],["sectionheading",{"2":{"1686":1}}],["sectioned",{"3":{"1201":1,"1203":2,"1495":39,"1496":2,"1499":1}}],["sectionname",{"2":{"728":1,"1446":1,"1999":1},"3":{"674":2,"725":1,"728":1,"1259":3,"1285":7,"1299":8,"1300":4,"1308":1,"1310":4,"1319":2,"1322":17,"1323":10,"1338":5,"1395":2,"1414":7,"1426":3,"1446":1}}],["sections",{"0":{"1209":1},"3":{"0":3,"27":3,"74":2,"104":1,"128":1,"203":1,"231":1,"256":1,"259":1,"337":1,"375":2,"393":1,"409":1,"418":1,"429":1,"463":1,"540":1,"552":2,"604":1,"607":1,"609":3,"611":2,"612":1,"613":1,"649":1,"672":2,"673":1,"674":8,"675":1,"676":2,"677":1,"725":8,"726":1,"727":3,"728":1,"731":1,"794":1,"832":1,"833":2,"877":1,"909":1,"927":1,"960":1,"976":1,"980":1,"995":1,"1021":1,"1068":1,"1095":1,"1124":1,"1125":1,"1128":1,"1191":1,"1194":1,"1210":1,"1211":1,"1212":1,"1213":1,"1229":1,"1279":1,"1285":4,"1299":3,"1309":1,"1310":4,"1312":4,"1315":1,"1319":3,"1321":1,"1322":8,"1323":11,"1338":2,"1339":1,"1342":2,"1358":1,"1394":7,"1400":1,"1401":2,"1402":1,"1403":1,"1414":8,"1426":1,"1435":4,"1436":1,"1437":2,"1450":1,"1451":1,"1466":1,"1475":4,"1480":1,"1486":1,"1488":1,"1495":74,"1496":4,"1497":2,"1499":1,"1522":1,"1525":1,"1576":1,"1625":1,"2011":4,"2045":1,"2059":1,"2066":1,"2231":1}}],["section",{"0":{"136":1,"1193":1},"2":{"1417":1,"1422":1,"1425":1},"3":{"0":21,"59":1,"72":1,"76":1,"94":1,"95":1,"96":1,"97":1,"109":1,"130":3,"135":1,"137":2,"139":2,"145":1,"159":1,"161":1,"166":3,"173":1,"178":3,"180":3,"214":2,"215":1,"216":1,"219":1,"225":2,"243":1,"255":1,"281":1,"286":1,"298":1,"300":1,"350":1,"375":1,"395":4,"397":1,"400":1,"407":1,"408":1,"409":1,"418":1,"423":3,"426":1,"428":2,"429":2,"432":1,"434":2,"440":1,"475":1,"476":1,"477":1,"482":1,"484":1,"490":1,"511":1,"526":1,"543":1,"583":1,"584":1,"585":4,"586":1,"590":1,"607":6,"608":1,"609":6,"610":1,"611":2,"617":1,"633":1,"640":1,"651":1,"652":1,"673":1,"674":13,"675":1,"676":5,"678":1,"690":2,"696":1,"698":1,"702":1,"707":3,"715":2,"723":1,"725":8,"726":2,"727":5,"728":2,"729":1,"731":1,"733":2,"798":1,"800":1,"819":1,"821":1,"827":5,"829":1,"832":3,"836":1,"837":1,"838":2,"839":1,"841":1,"845":1,"849":1,"854":1,"860":2,"865":1,"896":1,"901":1,"915":3,"931":1,"933":1,"971":1,"972":1,"976":1,"996":5,"997":1,"998":2,"1003":1,"1006":1,"1007":2,"1010":5,"1012":2,"1014":2,"1017":1,"1018":1,"1022":1,"1034":2,"1058":1,"1059":1,"1062":1,"1073":1,"1078":1,"1079":2,"1089":1,"1090":2,"1091":2,"1094":2,"1095":1,"1096":1,"1124":3,"1184":1,"1188":1,"1193":3,"1194":1,"1212":8,"1214":1,"1216":1,"1229":1,"1237":2,"1247":6,"1259":39,"1260":1,"1264":1,"1270":6,"1271":1,"1273":2,"1275":1,"1276":1,"1277":1,"1278":1,"1279":5,"1285":41,"1293":1,"1296":1,"1298":3,"1299":26,"1300":17,"1306":1,"1308":16,"1309":3,"1310":20,"1311":1,"1315":1,"1319":12,"1321":1,"1322":88,"1323":58,"1325":2,"1331":2,"1335":2,"1336":1,"1337":4,"1338":38,"1349":6,"1358":4,"1365":1,"1367":1,"1368":11,"1388":1,"1391":1,"1394":30,"1395":35,"1401":8,"1409":2,"1413":3,"1414":31,"1415":3,"1416":2,"1417":4,"1418":4,"1419":1,"1420":3,"1421":4,"1422":2,"1423":1,"1424":1,"1425":1,"1426":13,"1427":1,"1430":2,"1434":1,"1435":30,"1437":10,"1439":1,"1440":1,"1441":2,"1442":2,"1443":3,"1446":2,"1451":1,"1459":2,"1462":1,"1464":1,"1466":13,"1469":1,"1470":1,"1471":1,"1472":1,"1473":2,"1475":5,"1477":1,"1480":4,"1484":1,"1485":2,"1486":6,"1487":8,"1488":2,"1489":1,"1493":2,"1495":51,"1496":3,"1497":1,"1499":2,"1525":3,"1531":1,"1574":2,"1576":2,"1588":1,"1590":3,"1595":2,"1599":1,"1601":2,"1624":1,"1626":2,"1629":4,"1630":9,"1631":7,"1634":1,"1637":3,"1638":4,"1639":1,"1640":4,"1650":1,"1651":1,"1655":4,"1665":1,"1666":1,"1667":1,"1668":1,"1669":2,"1671":3,"1672":1,"1674":1,"1682":1,"1683":2,"1707":3,"1721":1,"1725":1,"1726":1,"1728":1,"1748":1,"1749":1,"1750":4,"1757":2,"1758":1,"1759":1,"1764":6,"1769":1,"1771":3,"1772":1,"1775":2,"1848":1,"1881":1,"1894":1,"1901":1,"1907":1,"1908":1,"1915":1,"1918":1,"1920":1,"1932":1,"1935":1,"1939":1,"1974":1,"1991":1,"1999":1,"2000":2,"2001":1,"2010":2,"2011":4,"2012":1,"2013":2,"2023":1,"2026":1,"2040":1,"2045":3,"2048":3,"2054":1,"2059":1,"2062":1,"2066":7,"2093":1,"2113":1,"2125":2,"2126":1,"2135":1,"2137":1,"2148":1,"2149":1,"2151":1,"2157":2,"2166":1,"2172":2,"2179":1,"2185":1,"2194":1,"2231":6}}],["secretbytelength",{"3":{"1299":2}}],["secretkey",{"2":{"792":1,"2165":1},"3":{"790":1,"792":1,"1769":1,"2165":1}}],["secretmissing",{"3":{"790":1}}],["secretforkey",{"3":{"31":1,"674":1,"1299":3}}],["secretref",{"3":{"0":1,"599":2,"640":1,"1212":1,"1466":11,"1495":1,"1576":1}}],["secrets",{"0":{"1334":1},"2":{"598":1,"601":1,"869":1,"875":1,"1338":1,"1454":1},"3":{"0":5,"349":1,"351":1,"359":1,"598":2,"599":6,"600":2,"601":4,"603":1,"758":1,"766":1,"869":2,"875":1,"1012":1,"1059":1,"1066":2,"1212":2,"1285":3,"1299":4,"1310":3,"1322":2,"1323":2,"1325":1,"1334":2,"1338":12,"1414":6,"1416":7,"1418":1,"1426":1,"1435":1,"1440":1,"1441":1,"1444":1,"1453":1,"1454":1,"1455":1,"1458":1,"1459":1,"1461":2,"1465":2,"1466":23,"1467":5,"1468":2,"1469":1,"1471":3,"1473":5,"1474":1,"1476":1,"1483":1,"1525":2,"1534":1,"1547":2,"1553":3,"1562":3,"1569":1,"1571":2,"1576":3,"1585":1,"1590":2,"1662":1,"1669":1,"1769":1,"1944":1,"1973":1,"1974":1,"1978":1,"2067":1,"2127":1,"2141":2,"2145":1,"2197":1,"2229":1}}],["secret",{"1":{"596":1,"597":1,"598":1,"599":1,"600":1,"601":1,"602":1,"603":1,"604":1,"605":1,"1892":1,"1893":1,"1894":1,"1895":1,"1896":1,"1897":1,"1898":1},"2":{"1457":1,"1467":1,"1469":1,"1471":1},"3":{"0":10,"30":4,"31":3,"33":1,"53":1,"72":1,"179":1,"180":1,"217":1,"350":1,"441":1,"500":1,"529":2,"530":2,"535":1,"596":1,"597":2,"598":3,"599":12,"601":5,"604":1,"605":3,"640":2,"644":1,"664":2,"670":1,"673":1,"678":1,"788":1,"789":2,"790":11,"791":3,"792":9,"793":4,"794":6,"827":10,"869":3,"871":2,"872":1,"1017":4,"1020":2,"1212":2,"1287":2,"1292":9,"1299":32,"1310":8,"1311":2,"1321":3,"1322":18,"1323":11,"1334":2,"1336":3,"1338":30,"1395":1,"1405":1,"1415":2,"1416":1,"1418":1,"1422":1,"1426":3,"1440":1,"1441":2,"1444":4,"1446":2,"1449":2,"1453":3,"1454":13,"1457":4,"1460":1,"1465":4,"1466":36,"1467":3,"1468":2,"1469":4,"1471":3,"1472":1,"1480":2,"1485":2,"1491":1,"1525":1,"1553":1,"1584":1,"1585":1,"1588":2,"1590":2,"1594":1,"1595":3,"1596":1,"1666":2,"1669":4,"1769":1,"1785":1,"1789":1,"1891":1,"1892":7,"1893":1,"1894":3,"1896":1,"1897":1,"1898":2,"1900":1,"1901":2,"1903":1,"1904":3,"1972":1,"1974":2,"1979":2,"1980":1,"1984":1,"2008":1,"2011":1,"2020":2,"2023":3,"2027":1,"2110":1,"2124":1,"2126":1,"2141":1,"2148":1,"2160":1,"2165":7,"2166":1,"2168":1,"2174":1,"2197":2,"2207":1,"2229":1,"2231":3}}],["secondfactor",{"3":{"1299":1}}],["secondentityfullname",{"3":{"1285":2}}],["secondjob",{"3":{"1199":2,"1207":1}}],["secondtestrequestvalidator",{"3":{"1199":2,"1207":1}}],["secondarysynced",{"3":{"1358":1}}],["secondarycontrasttext",{"3":{"1323":1}}],["secondarylight",{"3":{"1323":1}}],["secondarydark",{"3":{"1323":1}}],["secondary",{"2":{"2074":1},"3":{"618":1,"1191":1,"1285":1,"1299":1,"1323":11,"1349":1,"1357":1,"1358":1,"1379":1,"1394":1,"1491":1,"1525":5,"1534":1,"1576":1,"1590":1,"1611":1,"1637":1,"2074":2}}],["seconds",{"3":{"0":1,"19":2,"22":2,"23":1,"24":1,"72":2,"73":2,"121":1,"178":1,"233":1,"235":1,"243":3,"245":1,"254":1,"257":1,"379":2,"387":1,"390":3,"392":2,"460":2,"461":2,"463":1,"565":1,"707":1,"709":1,"733":1,"735":1,"818":1,"819":2,"821":2,"827":1,"830":1,"831":1,"833":1,"837":2,"838":2,"869":1,"914":1,"1028":2,"1029":1,"1044":1,"1061":1,"1089":1,"1091":1,"1101":1,"1124":2,"1184":1,"1256":1,"1259":6,"1263":1,"1264":3,"1270":4,"1273":1,"1285":4,"1292":1,"1299":7,"1300":7,"1308":4,"1310":6,"1311":4,"1316":1,"1319":1,"1322":7,"1323":17,"1327":1,"1336":1,"1338":9,"1358":2,"1377":1,"1379":4,"1389":1,"1394":1,"1395":4,"1401":5,"1414":1,"1415":2,"1416":5,"1418":1,"1424":1,"1426":2,"1429":1,"1432":1,"1435":7,"1440":3,"1442":1,"1446":2,"1454":2,"1455":1,"1466":7,"1489":1,"1615":1,"1638":1,"1667":1,"1747":1,"1764":1,"1840":1,"1842":1,"1905":1,"1915":1,"1917":1,"1921":1,"1922":1,"1923":1,"1947":1,"2009":1,"2023":1,"2050":1,"2065":1,"2133":1,"2155":4,"2156":1,"2157":1,"2172":1,"2176":1,"2191":1,"2197":1,"2201":1}}],["second",{"0":{"95":1,"1292":1,"1721":1,"2064":1,"2072":1,"2136":1},"1":{"816":1,"817":1,"818":1,"819":1,"820":1,"821":1,"822":1,"823":1,"1941":1,"1942":1,"1943":1,"1944":1,"1945":1,"1946":1,"1947":1,"1948":1,"1949":1,"2193":1,"2194":1,"2195":1,"2196":1,"2197":1,"2198":1,"2199":1,"2200":1,"2201":1,"2202":1},"3":{"0":34,"15":1,"24":2,"27":1,"31":1,"38":1,"55":1,"70":3,"71":4,"74":1,"91":1,"92":1,"95":1,"100":2,"113":1,"115":1,"122":1,"126":3,"130":1,"136":1,"137":1,"139":2,"150":1,"152":1,"156":1,"157":1,"165":1,"173":1,"178":3,"189":1,"197":1,"199":1,"200":1,"201":2,"214":1,"219":1,"231":1,"233":1,"234":1,"235":4,"237":2,"243":3,"252":1,"254":2,"258":1,"265":2,"279":1,"280":1,"285":1,"292":1,"296":1,"305":1,"324":1,"325":1,"340":1,"341":3,"342":1,"350":2,"351":1,"352":2,"358":1,"374":1,"379":2,"386":1,"387":2,"388":1,"390":1,"391":1,"395":3,"397":1,"404":3,"409":1,"411":1,"412":1,"415":1,"428":1,"436":1,"448":2,"450":2,"457":1,"459":7,"460":1,"461":2,"463":2,"465":5,"466":1,"477":1,"501":2,"505":1,"507":1,"508":1,"513":1,"517":1,"519":1,"524":1,"528":1,"530":1,"531":1,"534":1,"536":1,"538":2,"539":1,"540":1,"543":1,"546":1,"549":1,"551":1,"556":3,"558":1,"564":2,"599":1,"600":1,"609":1,"617":1,"619":1,"624":1,"628":2,"629":1,"631":1,"633":3,"635":2,"638":1,"640":4,"642":1,"649":1,"655":1,"656":1,"657":1,"660":1,"665":2,"673":1,"674":1,"675":2,"676":2,"681":1,"689":1,"690":2,"691":4,"692":1,"697":1,"698":2,"699":1,"700":1,"702":1,"707":3,"709":1,"715":1,"720":1,"728":1,"732":1,"733":4,"734":2,"735":2,"736":2,"737":2,"740":1,"741":3,"742":1,"749":2,"757":3,"758":1,"759":2,"761":1,"784":1,"789":1,"790":1,"792":1,"794":1,"810":1,"811":3,"813":1,"816":1,"818":1,"819":4,"820":1,"821":2,"825":1,"827":8,"828":1,"829":5,"831":1,"832":2,"834":1,"837":2,"849":1,"853":1,"854":2,"855":1,"859":1,"861":1,"881":2,"882":2,"889":1,"891":1,"897":1,"904":1,"905":1,"906":1,"907":1,"914":1,"916":1,"917":1,"926":2,"931":1,"934":1,"946":1,"947":2,"974":1,"988":3,"990":1,"994":1,"995":2,"996":6,"999":1,"1003":1,"1018":1,"1019":1,"1020":2,"1025":1,"1028":1,"1042":2,"1043":2,"1045":1,"1050":1,"1051":2,"1058":1,"1059":1,"1060":1,"1074":1,"1075":1,"1079":1,"1089":2,"1090":2,"1091":1,"1092":1,"1093":1,"1107":1,"1116":3,"1128":1,"1134":1,"1142":1,"1154":2,"1178":1,"1184":1,"1185":1,"1212":5,"1221":1,"1229":4,"1231":1,"1234":1,"1236":2,"1237":17,"1243":1,"1244":2,"1247":11,"1248":1,"1249":2,"1251":1,"1252":1,"1254":2,"1255":1,"1256":1,"1257":1,"1259":15,"1262":1,"1263":3,"1264":3,"1265":3,"1267":1,"1269":1,"1270":36,"1271":4,"1273":3,"1274":3,"1275":5,"1277":1,"1278":3,"1282":1,"1285":73,"1286":2,"1288":1,"1292":5,"1296":1,"1297":2,"1299":61,"1300":13,"1301":1,"1304":1,"1306":4,"1308":25,"1309":4,"1310":29,"1311":10,"1314":1,"1316":2,"1317":1,"1321":1,"1322":39,"1323":74,"1325":1,"1326":2,"1327":1,"1329":9,"1330":1,"1331":1,"1332":2,"1333":2,"1334":1,"1336":1,"1337":7,"1338":56,"1346":1,"1349":22,"1351":1,"1354":1,"1355":1,"1357":1,"1358":97,"1364":1,"1365":1,"1367":1,"1368":11,"1371":2,"1374":2,"1375":1,"1377":2,"1378":2,"1379":18,"1384":1,"1387":1,"1388":2,"1389":3,"1391":2,"1392":2,"1394":42,"1395":77,"1396":1,"1397":1,"1398":1,"1401":13,"1405":1,"1406":1,"1412":1,"1414":39,"1415":13,"1416":17,"1423":1,"1426":4,"1428":1,"1429":3,"1430":1,"1432":2,"1435":92,"1437":10,"1439":1,"1440":5,"1441":5,"1442":2,"1444":1,"1445":2,"1446":5,"1452":2,"1453":1,"1454":10,"1455":1,"1457":1,"1458":2,"1459":1,"1464":5,"1465":1,"1466":16,"1468":1,"1472":1,"1473":1,"1475":3,"1477":2,"1480":1,"1487":3,"1488":2,"1489":1,"1491":3,"1495":7,"1508":1,"1525":3,"1533":2,"1544":1,"1546":1,"1576":4,"1584":1,"1590":3,"1629":2,"1630":3,"1631":1,"1638":2,"1650":1,"1653":1,"1665":1,"1666":1,"1667":2,"1671":1,"1683":2,"1685":2,"1688":1,"1700":2,"1718":1,"1720":2,"1721":2,"1723":1,"1726":4,"1728":2,"1730":4,"1747":1,"1754":1,"1764":2,"1766":1,"1775":1,"1778":2,"1780":1,"1789":1,"1812":1,"1818":1,"1820":3,"1824":1,"1828":1,"1838":1,"1839":4,"1840":1,"1842":2,"1845":1,"1847":1,"1850":1,"1851":1,"1852":1,"1862":1,"1868":1,"1870":1,"1874":1,"1875":2,"1876":2,"1897":3,"1898":1,"1899":2,"1901":1,"1906":1,"1908":3,"1910":1,"1914":1,"1915":2,"1917":2,"1919":2,"1920":1,"1921":2,"1922":2,"1924":1,"1932":3,"1933":1,"1936":1,"1941":6,"1942":1,"1943":1,"1944":2,"1945":1,"1946":1,"1947":2,"1949":3,"1958":1,"1959":1,"1961":1,"1963":1,"1966":3,"1969":3,"1972":1,"1973":1,"1976":1,"1977":1,"1978":3,"1979":1,"1985":1,"1994":1,"1996":1,"1999":2,"2005":3,"2006":1,"2010":1,"2011":2,"2012":1,"2023":4,"2028":1,"2029":4,"2031":4,"2033":1,"2036":1,"2044":2,"2047":1,"2048":1,"2051":1,"2056":1,"2062":1,"2064":1,"2068":2,"2069":1,"2071":1,"2072":1,"2080":1,"2081":1,"2088":1,"2096":1,"2099":1,"2112":1,"2113":1,"2120":1,"2125":1,"2128":4,"2130":1,"2131":2,"2132":1,"2133":1,"2135":1,"2136":1,"2139":2,"2145":1,"2146":1,"2155":2,"2156":3,"2157":1,"2160":1,"2163":2,"2165":1,"2166":2,"2167":2,"2168":1,"2171":1,"2173":1,"2177":1,"2178":1,"2179":1,"2180":2,"2183":1,"2186":1,"2187":1,"2191":1,"2192":2,"2193":4,"2194":1,"2195":2,"2196":3,"2197":1,"2202":2,"2207":3,"2210":1,"2231":5,"2238":1}}],["school",{"3":{"1394":1}}],["schedulable",{"3":{"0":1,"1044":1,"1322":1,"1533":1,"1574":1,"1581":1,"1584":1,"2185":1,"2191":1}}],["scheduling",{"0":{"2184":1},"3":{"0":1,"230":1,"255":1,"444":1,"517":1,"529":1,"536":1,"539":3,"706":1,"707":6,"708":1,"709":2,"710":1,"995":1,"1036":1,"1041":1,"1042":2,"1044":2,"1191":1,"1203":6,"1207":44,"1259":1,"1264":1,"1275":1,"1283":1,"1285":11,"1316":3,"1317":1,"1319":1,"1322":26,"1349":1,"1358":14,"1379":3,"1395":2,"1409":1,"1410":1,"1414":10,"1416":11,"1435":1,"1437":6,"1441":1,"1486":1,"1495":1,"1526":1,"1531":1,"1573":1,"1577":1,"1593":1,"1629":2,"1636":1,"1637":1,"1922":1,"1949":2,"2164":2,"2188":1,"2191":2}}],["scheduleavatarblobdeletionasync",{"3":{"1414":5}}],["scheduleasync",{"2":{"1099":1,"1103":1,"2184":1,"2192":1},"3":{"1042":1,"1099":1,"1103":1,"1285":4,"1322":5,"1358":2,"1375":1,"1379":1,"1414":5,"1416":5,"2184":1,"2188":1,"2192":1}}],["scheduleerror",{"3":{"1394":2}}],["schedulecachekey",{"3":{"1394":1}}],["scheduleblobcleanupasync",{"3":{"1358":1}}],["scheduleorphancleanupasync",{"3":{"1358":2}}],["schedule",{"2":{"1455":1},"3":{"0":1,"19":1,"23":1,"224":1,"230":1,"404":1,"444":2,"500":1,"511":1,"536":1,"539":2,"591":1,"617":1,"625":1,"627":1,"628":1,"641":1,"689":1,"706":1,"707":7,"708":3,"709":3,"727":1,"860":1,"900":1,"954":2,"1018":2,"1019":1,"1040":2,"1045":1,"1099":1,"1100":1,"1103":1,"1116":2,"1118":1,"1259":2,"1260":1,"1269":1,"1270":5,"1285":2,"1316":1,"1322":24,"1323":1,"1339":1,"1342":1,"1348":1,"1349":12,"1350":1,"1351":1,"1355":1,"1358":31,"1368":6,"1375":2,"1379":11,"1384":1,"1390":5,"1394":49,"1395":19,"1410":1,"1414":2,"1415":2,"1416":12,"1426":1,"1435":1,"1437":6,"1442":1,"1454":1,"1455":7,"1457":1,"1458":1,"1474":1,"1489":3,"1523":1,"1525":1,"1533":2,"1598":3,"1625":3,"1628":4,"1629":2,"1630":4,"1631":6,"1635":1,"1636":4,"1637":1,"1638":1,"1639":4,"1840":1,"1894":1,"1947":6,"2005":1,"2046":1,"2126":1,"2155":1,"2185":2,"2189":1,"2191":1,"2231":2}}],["schedules",{"0":{"1254":1},"3":{"0":2,"440":1,"444":3,"459":1,"466":1,"536":1,"640":1,"644":1,"707":1,"810":1,"814":1,"819":1,"849":1,"897":1,"901":1,"907":1,"1016":1,"1018":1,"1020":1,"1042":1,"1044":1,"1116":1,"1118":1,"1212":1,"1285":1,"1299":1,"1300":1,"1322":6,"1338":1,"1346":1,"1356":1,"1358":9,"1366":1,"1368":2,"1375":1,"1379":4,"1389":1,"1394":4,"1395":5,"1407":1,"1409":1,"1410":1,"1414":13,"1416":5,"1435":2,"1630":1,"1638":1,"1639":1,"1842":1,"1917":1,"2164":1,"2184":1,"2188":2,"2189":1,"2191":1,"2192":2}}],["scheduledon",{"2":{"1045":1},"3":{"1042":1,"1045":1,"1285":5,"1322":2,"2185":1}}],["scheduledjobs",{"3":{"1273":1,"1285":3,"1316":1,"1322":4,"1435":1,"1437":1}}],["scheduledjobrunnertests",{"3":{"1199":1,"1207":2,"1322":2,"1437":1}}],["scheduledjobrunner",{"2":{"995":1,"1043":1},"3":{"707":4,"710":1,"995":2,"996":1,"1043":1,"1199":5,"1203":1,"1207":3,"1269":1,"1270":2,"1285":4,"1312":1,"1316":10,"1322":42,"1437":1,"1441":2,"1495":2}}],["scheduledjoboverridesettings",{"2":{"707":1,"1319":1},"3":{"707":1,"1199":3,"1203":1,"1207":1,"1270":1,"1319":1,"1322":2}}],["scheduledjobentry",{"2":{"995":1,"1036":1,"1041":1},"3":{"0":1,"707":3,"710":1,"995":1,"1041":2,"1199":7,"1203":1,"1207":2,"1212":1,"1270":1,"1273":1,"1285":4,"1316":3,"1322":8}}],["scheduledqueryalertspecs",{"2":{"609":1,"1475":1,"2157":1},"3":{"609":1,"1466":1,"1475":2,"2157":1}}],["scheduledqueryrules",{"2":{"1473":1,"2157":1},"3":{"0":1,"609":1,"1466":1,"1473":1,"1576":1,"2157":1}}],["scheduled",{"3":{"0":4,"24":1,"71":1,"166":1,"229":1,"403":1,"405":1,"440":1,"444":1,"470":1,"534":1,"536":1,"538":1,"539":1,"541":1,"607":1,"609":4,"625":1,"626":1,"709":1,"715":1,"720":1,"767":1,"808":1,"810":1,"813":2,"818":2,"827":1,"897":1,"898":1,"988":1,"1018":2,"1019":1,"1020":1,"1026":1,"1042":1,"1043":1,"1044":3,"1099":1,"1100":1,"1116":2,"1212":4,"1259":2,"1263":1,"1270":5,"1275":1,"1277":1,"1285":11,"1317":1,"1322":15,"1326":1,"1336":1,"1338":5,"1345":1,"1349":3,"1351":1,"1356":2,"1358":14,"1368":1,"1379":7,"1394":1,"1395":13,"1414":7,"1415":2,"1416":5,"1435":3,"1437":1,"1439":1,"1454":4,"1455":2,"1456":1,"1457":1,"1459":5,"1464":5,"1466":9,"1468":1,"1473":3,"1474":5,"1475":1,"1524":2,"1525":5,"1529":1,"1530":3,"1531":2,"1533":6,"1567":1,"1574":3,"1576":1,"1577":1,"1580":2,"1581":1,"1582":2,"1583":1,"1584":7,"1590":3,"1598":2,"1629":3,"1664":1,"1669":1,"1677":1,"1678":1,"1709":1,"1719":1,"1822":1,"1831":1,"1838":1,"2033":1,"2157":2,"2160":1,"2164":1,"2166":2,"2167":2,"2186":1,"2190":1,"2191":4,"2192":1,"2231":2}}],["schedulertableenabled",{"3":{"1285":2}}],["schedulertestharness",{"3":{"1199":3,"1207":5,"1322":2}}],["schedulertestcontext",{"3":{"1199":2,"1207":1}}],["schedulermodelgatetests",{"3":{"1191":1,"1199":1,"1207":4,"1285":2,"1322":2,"1437":2,"1495":1}}],["schedulermetricstests",{"3":{"1199":1,"1207":2,"1322":2,"1437":1}}],["schedulermetrics",{"2":{"711":1,"2158":1},"3":{"707":2,"711":1,"1199":3,"1203":1,"1207":2,"1316":5,"1322":12,"1437":1,"1495":1,"2155":1,"2158":1}}],["schedulersettings",{"2":{"707":1,"711":1},"3":{"674":1,"707":4,"711":1,"1199":7,"1203":1,"1207":3,"1270":1,"1285":3,"1312":1,"1316":2,"1319":5,"1322":17}}],["schedulers",{"3":{"0":1,"827":1,"1043":1}}],["scheduler",{"1":{"704":1,"705":1,"706":1,"707":1,"708":1,"709":1,"710":1,"711":1},"2":{"400":1,"408":1,"1316":1,"2005":1,"2012":1,"2159":1},"3":{"0":9,"170":2,"258":1,"400":2,"408":1,"536":2,"539":1,"540":1,"696":1,"704":1,"705":1,"707":9,"708":1,"709":1,"710":1,"711":1,"713":1,"715":2,"716":2,"720":3,"721":1,"897":1,"905":1,"910":2,"994":1,"996":1,"999":1,"1000":1,"1034":1,"1041":1,"1042":1,"1043":1,"1046":1,"1085":1,"1212":1,"1213":1,"1237":1,"1269":1,"1270":8,"1273":1,"1277":2,"1283":1,"1285":20,"1299":1,"1316":5,"1317":2,"1319":1,"1322":37,"1332":3,"1338":1,"1358":1,"1378":1,"1379":1,"1395":2,"1410":1,"1414":1,"1435":1,"1437":3,"1441":2,"1442":2,"1445":1,"1466":6,"1468":1,"1495":2,"1576":1,"1584":1,"1659":1,"1663":4,"1669":1,"1674":1,"1947":1,"1949":1,"2005":3,"2011":1,"2012":1,"2155":7,"2159":1,"2182":1,"2191":1,"2226":1,"2231":1}}],["schemas",{"3":{"0":1,"109":1,"656":1,"1034":1,"1035":1,"1082":1,"1085":1,"1093":1,"1285":1,"1299":2,"1421":1,"1542":1,"2173":1}}],["schemaversionrule",{"3":{"1435":1}}],["schemaversion",{"2":{"78":1,"81":1,"131":1,"256":1,"447":1,"455":1,"844":1,"845":1,"847":1,"1507":1,"1575":1,"1576":1,"1888":1,"1890":1,"1891":1,"2129":1},"3":{"0":2,"78":5,"79":1,"80":2,"81":1,"82":1,"131":1,"255":1,"256":1,"447":1,"455":1,"782":1,"844":1,"845":1,"846":1,"847":1,"1212":1,"1249":3,"1259":7,"1270":2,"1349":1,"1435":15,"1488":1,"1507":1,"1525":3,"1575":1,"1576":2,"1664":1,"1888":5,"1890":3,"1891":1,"2129":1,"2231":2}}],["schema",{"1":{"75":1,"76":1,"77":1,"78":1,"79":1,"80":1,"81":1,"82":1,"561":1,"562":1,"563":1,"564":1,"565":1,"566":1,"567":1,"568":1,"695":1,"696":1,"697":1,"698":1,"699":1,"700":1,"701":1,"702":1,"703":1,"1886":1,"1887":1,"1888":1,"1889":1,"1890":1,"1891":1},"3":{"0":15,"26":1,"46":1,"48":1,"75":1,"109":1,"166":1,"200":1,"282":1,"292":2,"295":1,"309":1,"342":1,"343":1,"376":1,"452":1,"453":1,"455":1,"470":1,"471":2,"561":1,"563":1,"564":2,"565":2,"566":1,"568":3,"572":1,"573":1,"574":1,"633":1,"657":1,"658":1,"695":1,"698":1,"700":1,"702":1,"703":1,"707":1,"708":1,"719":1,"756":2,"757":2,"759":2,"761":1,"762":1,"786":1,"799":1,"854":1,"855":1,"891":3,"892":1,"893":1,"931":1,"932":1,"933":1,"935":1,"964":1,"965":1,"1004":1,"1012":1,"1018":5,"1026":1,"1034":4,"1042":1,"1043":1,"1050":2,"1051":1,"1082":1,"1083":1,"1085":2,"1090":2,"1093":1,"1133":2,"1144":1,"1154":1,"1175":1,"1212":8,"1213":1,"1237":3,"1259":2,"1270":1,"1273":1,"1276":1,"1278":1,"1280":1,"1281":3,"1285":54,"1293":1,"1299":7,"1308":2,"1310":27,"1311":2,"1322":10,"1326":1,"1338":1,"1349":5,"1358":7,"1361":2,"1363":1,"1365":4,"1368":20,"1379":4,"1394":1,"1395":12,"1401":1,"1404":1,"1411":1,"1414":5,"1416":1,"1426":1,"1435":4,"1437":4,"1440":1,"1454":8,"1460":2,"1466":5,"1472":2,"1489":2,"1491":1,"1495":1,"1525":3,"1543":1,"1544":4,"1545":1,"1546":1,"1550":1,"1576":5,"1589":1,"1590":1,"1613":1,"1640":1,"1663":4,"1664":1,"1666":1,"1683":1,"1687":1,"1696":1,"1718":1,"1720":1,"1764":1,"1778":1,"1809":1,"1847":1,"1850":3,"1853":1,"1855":6,"1858":1,"1872":2,"1875":1,"1885":1,"1886":2,"1887":1,"1889":1,"1891":2,"1926":1,"1947":1,"1990":1,"2012":1,"2042":1,"2054":1,"2111":1,"2132":1,"2178":1,"2191":1,"2206":1,"2231":5}}],["schemename",{"2":{"1299":1},"3":{"1299":4,"1435":3}}],["schemes",{"3":{"350":2,"352":1,"420":1,"422":1,"1237":1,"1271":1,"1299":1,"1310":4,"1323":3,"1349":1,"1358":2,"1416":2,"1974":1,"1975":1,"1977":2}}],["scheme",{"3":{"0":3,"59":1,"92":2,"96":1,"207":3,"208":2,"209":2,"265":1,"273":2,"309":1,"310":1,"311":1,"350":3,"352":1,"355":1,"360":1,"361":2,"418":4,"420":5,"421":2,"422":3,"427":2,"556":1,"557":1,"618":1,"665":1,"668":1,"749":2,"751":1,"827":2,"907":1,"945":1,"948":1,"1124":1,"1184":1,"1186":1,"1212":2,"1237":3,"1271":7,"1285":4,"1286":1,"1297":1,"1299":13,"1310":27,"1311":1,"1322":2,"1323":16,"1338":2,"1346":1,"1349":2,"1352":1,"1358":38,"1394":4,"1405":1,"1414":4,"1415":10,"1416":14,"1433":1,"1435":8,"1440":1,"1489":1,"1533":1,"1576":1,"1598":1,"1610":1,"1646":1,"1649":1,"1697":1,"1720":1,"1824":3,"1899":1,"1902":1,"1903":1,"1964":1,"1967":3,"1970":1,"1974":5,"1975":3,"1977":1,"2024":1,"2082":1,"2085":1,"2121":1,"2141":1,"2143":1,"2231":2}}],["sc",{"3":{"1358":2}}],["science",{"3":{"1912":1,"2221":1}}],["sci",{"3":{"1349":4,"1358":9}}],["scuffed",{"3":{"1323":1}}],["sccs",{"3":{"1201":1,"1495":3,"1497":1,"1516":1}}],["scc",{"0":{"1198":1},"3":{"1196":1,"1237":1,"1435":1,"1495":5,"1497":1}}],["scenarios",{"3":{"1285":1,"1358":1,"1473":1,"1638":1,"1774":1,"2032":1,"2036":1}}],["scenario",{"0":{"2032":1},"3":{"68":2,"627":1,"1229":1,"1271":1,"1285":1,"1322":2,"1323":1,"1349":1,"1394":2,"1466":1,"1471":1,"1473":2,"1533":1,"1590":1,"1708":1,"2028":1,"2032":3,"2033":1,"2036":1,"2037":2}}],["scorable",{"3":{"1349":1,"2189":1}}],["scoretier",{"3":{"1394":1}}],["scoretieroptions",{"3":{"1394":2}}],["scorematchesfilter",{"3":{"1394":3}}],["scorepollsession",{"2":{"1389":1},"3":{"1199":2,"1203":1,"1207":3,"1389":5,"1394":21}}],["scorepollsignal",{"2":{"1389":1},"3":{"1199":4,"1203":1,"1207":1,"1389":1,"1394":6,"1495":1}}],["scorepollhost",{"3":{"1199":3,"1203":1,"1207":1,"1389":1,"1394":5,"1495":1}}],["scorepolltrackertests",{"3":{"1199":1,"1207":2}}],["scorepolltracker",{"2":{"1389":1},"3":{"1199":3,"1203":1,"1207":3,"1389":2,"1394":17,"1495":2}}],["scorerequested",{"3":{"1394":2}}],["scorer",{"3":{"756":1,"1016":1,"1018":4,"1021":2,"1095":2,"1358":4,"1359":1,"1365":5,"1367":1,"1368":9,"1434":1,"1436":1,"1454":2,"1460":1,"1462":1,"1484":1,"1486":1,"1491":2,"2238":1}}],["scoreeventsessionscommand",{"3":{"1495":1}}],["scoreeventsessionshandler",{"3":{"1285":1,"1349":3,"1495":3}}],["scoreeventsessionsresultdto",{"2":{"1346":1,"1389":1},"3":{"1199":9,"1203":1,"1207":1,"1346":1,"1349":3,"1358":2,"1389":1,"1394":3}}],["scoreeventsessionsinternalcommandmarkertests",{"2":{"1437":1},"3":{"1199":1,"1207":2,"1437":2}}],["scoreeventsessionsinternalcommand",{"2":{"1018":1,"1264":1,"1346":1,"1366":1,"1375":1,"1389":1},"3":{"1018":1,"1199":6,"1203":1,"1207":2,"1264":2,"1285":1,"1346":2,"1349":3,"1358":8,"1366":1,"1368":1,"1375":1,"1379":3,"1389":2,"1437":1,"1495":2,"2189":2}}],["scoreeventsessionsinternalcommandhandlertests",{"2":{"1437":1},"3":{"1199":1,"1207":2,"1437":2}}],["scoreeventsessionsinternalcommandhandler",{"2":{"994":1,"996":1,"1018":1,"1270":1,"1366":1,"1368":1,"1495":1,"2189":1},"3":{"493":1,"494":1,"495":1,"994":1,"995":1,"996":2,"998":1,"1018":2,"1099":1,"1103":1,"1199":2,"1203":1,"1207":2,"1270":3,"1358":13,"1366":2,"1368":3,"1437":2,"1495":2,"2189":1}}],["scoreeventsessions",{"3":{"493":1,"494":1,"495":1,"995":1,"1018":2,"1082":1,"1099":1,"1203":9,"1207":18,"1264":1,"1270":3,"1285":1,"1346":1,"1349":1,"1358":24,"1366":1,"1368":1,"1389":1,"1437":1,"1466":1,"1495":3,"1525":1}}],["score",{"1":{"1793":1,"1794":1,"1795":1,"1796":1,"1797":1,"1798":1,"1799":1,"1800":1,"1801":1},"3":{"0":3,"520":1,"1010":2,"1012":6,"1014":2,"1017":2,"1018":9,"1019":2,"1020":2,"1082":1,"1094":1,"1193":1,"1212":1,"1216":1,"1341":3,"1346":1,"1349":15,"1358":39,"1361":2,"1365":3,"1368":15,"1375":1,"1384":2,"1394":34,"1395":1,"1434":5,"1435":1,"1437":3,"1454":1,"1486":1,"1495":1,"1517":1,"1523":1,"1524":1,"1525":4,"1526":2,"1529":3,"1530":3,"1533":1,"1534":1,"1536":8,"1552":1,"1563":1,"1574":1,"1577":2,"1579":1,"1581":2,"1584":3,"1590":1,"1596":1,"1598":1,"1629":1,"1631":2,"1639":1,"1783":1,"1793":3,"1796":1,"1798":4,"1799":1,"1800":3,"1801":6,"2049":1,"2059":1,"2113":1,"2170":1,"2189":3,"2204":1,"2238":2}}],["scorecards",{"0":{"1519":1},"3":{"1011":1,"1012":1,"1216":1,"1520":1,"1603":1,"1673":2,"2230":1,"2232":1}}],["scorecard",{"0":{"1525":1,"1576":1,"1590":1,"2230":1},"1":{"1521":1,"1522":1,"1523":1,"1524":1,"1525":1,"1526":1,"1527":1,"1572":1,"1573":1,"1574":1,"1575":1,"1576":1,"1577":1,"1578":1,"1586":1,"1587":1,"1588":1,"1589":1,"1590":1,"1591":1,"1592":1},"3":{"0":2,"1006":2,"1007":1,"1010":1,"1011":4,"1012":3,"1013":2,"1014":2,"1021":1,"1089":1,"1339":1,"1341":2,"1476":1,"1495":1,"1517":1,"1519":4,"1521":2,"1525":1,"1527":1,"1528":1,"1535":2,"1536":1,"1552":1,"1570":1,"1572":2,"1576":1,"1578":1,"1579":3,"1586":2,"1590":1,"1593":2,"1596":1,"1604":1,"1609":1,"1672":1,"1738":1,"1743":1,"1777":1,"1786":2,"1793":1,"1795":1,"1798":3,"1800":2,"1801":4,"1804":1,"1810":2,"1887":1,"1904":2,"1909":2,"1915":1,"1928":1,"1929":3,"2033":1,"2042":2,"2047":2,"2058":2,"2059":1,"2061":3,"2068":2,"2078":1,"2084":1,"2108":1,"2114":3,"2132":2,"2152":2,"2159":2,"2168":2,"2179":2,"2192":2,"2202":2,"2210":2}}],["scoredthisrun",{"3":{"1394":4}}],["scoredon",{"3":{"1349":2,"1358":1,"1389":1,"1394":3}}],["scored",{"0":{"1783":1,"1798":1},"3":{"0":1,"1011":5,"1012":5,"1013":2,"1014":2,"1017":1,"1018":1,"1022":1,"1090":2,"1216":1,"1346":2,"1349":4,"1358":17,"1375":1,"1379":3,"1389":2,"1394":5,"1425":1,"1427":1,"1437":2,"1466":1,"1515":1,"1518":1,"1520":1,"1521":1,"1525":3,"1526":2,"1533":1,"1535":2,"1546":1,"1552":1,"1553":1,"1570":1,"1572":1,"1576":3,"1577":2,"1584":3,"1586":1,"1590":1,"1591":1,"1598":1,"1644":1,"1779":1,"1793":2,"1798":2,"1800":3,"1801":1,"1902":1,"1915":1,"2039":1,"2061":2,"2068":2,"2084":1,"2170":1,"2189":1,"2213":1,"2230":1}}],["scoresessionsasync",{"2":{"1389":1},"3":{"1379":4,"1389":1,"1394":7}}],["scoresessionasync",{"3":{"1018":1,"1358":5,"1368":1}}],["scoreschema",{"3":{"1365":2,"1368":1}}],["scores",{"3":{"0":2,"85":1,"768":1,"1010":3,"1012":5,"1014":1,"1017":1,"1018":7,"1019":2,"1020":1,"1090":1,"1094":1,"1259":28,"1285":1,"1322":7,"1341":2,"1346":2,"1349":10,"1358":21,"1361":1,"1365":4,"1366":1,"1368":9,"1379":1,"1389":2,"1394":8,"1395":1,"1434":2,"1435":1,"1436":1,"1437":2,"1441":1,"1486":1,"1491":1,"1520":1,"1521":1,"1525":1,"1535":1,"1536":1,"1546":2,"1569":2,"1572":1,"1586":1,"1590":1,"1591":3,"1593":1,"1631":1,"1783":4,"1793":1,"1796":1,"1797":1,"1798":4,"1799":1,"1909":1,"1928":1,"2052":1,"2058":1,"2061":3,"2113":1,"2170":1,"2181":1,"2231":1}}],["scoringinstruments",{"3":{"1495":2}}],["scoring=true",{"3":{"1491":1}}],["scoringnoscores",{"3":{"1394":1}}],["scoringcomplete",{"3":{"1394":2}}],["scoringpartial",{"3":{"1394":2}}],["scoringpollfailed",{"3":{"1394":2}}],["scoringtimedout",{"3":{"1394":2}}],["scoring",{"0":{"1358":1,"1365":1,"1366":1,"1434":1,"1526":1,"1577":1,"1591":1},"1":{"1015":1,"1016":1,"1017":1,"1018":1,"1019":1,"1020":1,"1021":1,"1022":1},"2":{"1203":1,"1368":1,"1378":1,"1426":1,"1434":1,"1436":1,"1437":1,"1441":1,"1454":1,"1485":1,"1486":1,"1491":1,"1495":1},"3":{"0":8,"492":1,"493":1,"516":1,"517":2,"520":2,"607":2,"609":2,"994":1,"995":1,"996":2,"998":1,"999":1,"1004":1,"1012":1,"1015":1,"1016":3,"1017":3,"1018":11,"1019":1,"1020":3,"1022":3,"1089":4,"1090":4,"1093":1,"1096":1,"1099":2,"1103":2,"1192":1,"1193":1,"1199":10,"1203":5,"1206":1,"1207":38,"1212":1,"1270":2,"1341":1,"1346":4,"1349":14,"1353":1,"1358":47,"1359":2,"1365":10,"1366":3,"1367":1,"1368":35,"1375":1,"1378":1,"1379":5,"1389":7,"1394":27,"1395":1,"1425":1,"1426":12,"1427":1,"1434":10,"1435":5,"1436":8,"1437":12,"1441":7,"1454":8,"1462":1,"1466":9,"1468":1,"1469":2,"1485":2,"1486":3,"1491":8,"1492":1,"1495":18,"1525":5,"1526":1,"1528":1,"1531":1,"1533":1,"1534":2,"1536":1,"1552":1,"1593":1,"1631":2,"1639":1,"1783":2,"1793":1,"1794":1,"1801":1,"1946":1,"2059":1,"2060":1,"2061":1,"2178":1,"2179":1,"2181":1,"2189":1,"2231":4}}],["scoping",{"0":{"1267":1},"3":{"0":1,"159":1,"318":3,"320":1,"321":2,"324":1,"335":1,"544":1,"545":1,"619":1,"739":1,"743":3,"826":1,"906":1,"964":1,"1239":1,"1247":3,"1270":11,"1285":2,"1296":1,"1299":7,"1308":1,"1310":11,"1322":2,"1323":6,"1349":1,"1352":2,"1358":5,"1371":2,"1379":27,"1387":1,"1394":13,"1395":9,"1401":3,"1414":1,"1415":1,"1416":1,"1435":9,"1437":3,"1454":1,"1466":1,"1495":2,"1525":2,"1590":1,"1594":1,"1595":1,"1598":1,"1630":2,"1638":4,"1665":1,"1748":1,"1758":1,"1814":2,"1919":6,"1994":2,"1995":1}}],["scoper",{"3":{"1996":1}}],["scopers",{"3":{"1260":1}}],["scopeunresolvedforpublicreader",{"3":{"1394":2}}],["scopecaption",{"3":{"1323":1}}],["scopecapturinglogger",{"3":{"1199":3,"1207":2}}],["scope=",{"3":{"1323":1}}],["scopequeryasync",{"3":{"1323":1}}],["scopeprovider",{"3":{"1323":4}}],["scopeable",{"3":{"1323":1,"1464":1}}],["scopefactory",{"3":{"1259":4,"1285":1,"1358":3,"1401":1}}],["scopekeyregex",{"3":{"1308":1}}],["scopekeymaxlength",{"2":{"1305":1},"3":{"1285":2,"1305":1,"1308":7}}],["scopekey",{"2":{"231":1,"1511":1,"1940":1},"3":{"225":1,"231":1,"1285":1,"1308":27,"1323":4,"1395":1,"1414":4,"1511":1,"1932":2,"1940":1}}],["scopedeventids",{"3":{"1358":1}}],["scopedexporttestcontroller",{"3":{"1199":2,"1207":1}}],["scopedkey",{"3":{"1323":4}}],["scopedintegrationeventhandlerbasetests",{"3":{"1199":1,"1207":6,"1259":1,"1437":2}}],["scopedintegrationeventhandlerbase",{"2":{"1252":1,"1412":1,"1664":1},"3":{"1199":10,"1203":1,"1207":2,"1252":3,"1259":24,"1354":1,"1358":8,"1395":9,"1412":1,"1414":7,"1495":1,"1664":1}}],["scopedprobe",{"3":{"1199":3,"1207":1}}],["scopeduseroverride",{"2":{"1045":1,"2185":1},"3":{"0":1,"1042":2,"1045":1,"1199":5,"1203":1,"1207":2,"1312":1,"1317":6,"1322":12,"1495":2,"2185":1}}],["scoped",{"0":{"1936":1},"1":{"479":1,"480":1,"481":1,"482":1,"483":1,"484":1,"485":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"495":1},"3":{"0":19,"24":1,"26":1,"39":1,"71":1,"85":1,"86":1,"109":1,"115":1,"122":1,"149":1,"157":1,"173":1,"195":1,"200":1,"201":2,"214":1,"219":1,"223":1,"225":1,"231":1,"241":1,"243":1,"246":2,"283":1,"292":1,"303":1,"317":3,"318":2,"320":1,"324":2,"325":2,"326":1,"397":1,"415":1,"434":1,"443":1,"479":1,"482":1,"483":1,"490":1,"491":1,"492":1,"493":1,"494":1,"511":1,"513":1,"518":1,"524":1,"530":2,"545":1,"549":1,"554":1,"564":1,"601":1,"602":1,"609":2,"620":1,"633":1,"638":1,"647":1,"651":1,"665":2,"666":2,"669":1,"670":1,"698":4,"699":1,"700":2,"702":1,"703":1,"706":1,"707":3,"708":2,"719":1,"724":1,"743":1,"747":1,"749":1,"751":1,"758":1,"766":1,"791":1,"794":1,"819":1,"827":2,"832":1,"863":1,"881":1,"905":2,"910":1,"916":1,"924":1,"926":1,"941":1,"988":1,"996":3,"1012":3,"1019":1,"1020":1,"1035":1,"1042":1,"1052":1,"1073":1,"1099":1,"1100":1,"1116":2,"1124":1,"1133":2,"1147":1,"1153":1,"1168":2,"1183":1,"1212":4,"1214":2,"1237":6,"1247":7,"1252":1,"1253":3,"1256":1,"1258":1,"1259":16,"1262":2,"1263":2,"1264":1,"1265":1,"1267":3,"1269":3,"1270":41,"1271":2,"1273":1,"1275":1,"1276":1,"1277":2,"1278":1,"1285":48,"1289":1,"1293":1,"1295":1,"1299":29,"1300":3,"1305":2,"1306":1,"1307":1,"1308":24,"1309":1,"1310":43,"1311":1,"1312":2,"1315":3,"1316":3,"1317":1,"1321":1,"1322":44,"1323":67,"1336":1,"1338":16,"1345":1,"1349":5,"1352":3,"1354":2,"1358":41,"1365":1,"1367":2,"1368":10,"1371":1,"1377":1,"1379":30,"1382":1,"1383":1,"1384":1,"1394":58,"1395":84,"1399":1,"1400":1,"1401":24,"1412":1,"1414":19,"1415":4,"1416":39,"1426":1,"1430":1,"1435":44,"1437":6,"1445":1,"1452":1,"1453":1,"1454":9,"1457":1,"1459":1,"1460":2,"1466":14,"1468":1,"1473":2,"1480":1,"1485":1,"1486":1,"1488":1,"1491":3,"1495":2,"1511":1,"1524":2,"1525":8,"1530":1,"1533":3,"1547":1,"1551":1,"1554":1,"1555":3,"1576":7,"1581":1,"1590":3,"1595":2,"1598":1,"1599":1,"1628":2,"1629":4,"1630":5,"1631":4,"1634":2,"1635":1,"1637":1,"1638":2,"1639":1,"1663":1,"1665":1,"1667":2,"1668":1,"1672":1,"1713":1,"1748":1,"1756":1,"1814":3,"1824":1,"1848":1,"1850":2,"1882":3,"1907":1,"1919":3,"1920":1,"1923":2,"1936":1,"1939":3,"1940":1,"1947":2,"1955":2,"1958":1,"1993":2,"1994":4,"1996":2,"1999":1,"2000":1,"2009":1,"2023":1,"2032":1,"2056":2,"2073":1,"2094":2,"2117":1,"2121":1,"2125":1,"2149":1,"2185":1,"2231":1}}],["scopeservices",{"3":{"1259":1}}],["scopes",{"3":{"0":6,"143":1,"179":1,"200":1,"201":1,"225":1,"318":1,"325":2,"378":1,"384":1,"397":1,"544":2,"545":1,"548":1,"607":1,"743":1,"782":1,"826":1,"905":2,"953":1,"1057":1,"1212":1,"1239":1,"1259":1,"1267":1,"1270":3,"1285":3,"1299":1,"1300":1,"1308":1,"1310":4,"1322":2,"1323":2,"1332":1,"1338":1,"1349":4,"1358":6,"1379":3,"1394":4,"1395":17,"1401":3,"1414":4,"1416":1,"1435":15,"1437":1,"1456":1,"1482":1,"1576":2,"1760":1,"1812":1,"1814":1,"1816":2,"1932":1,"1974":1,"1994":2,"2005":1,"2162":1,"2231":2}}],["scope",{"0":{"1304":1,"1679":1},"2":{"1054":1,"1919":2},"3":{"0":15,"15":1,"24":2,"26":6,"27":7,"71":1,"81":1,"84":1,"123":4,"125":1,"135":3,"136":2,"141":1,"160":2,"178":3,"180":1,"193":1,"201":1,"202":6,"223":1,"225":1,"230":1,"243":1,"246":2,"264":1,"312":1,"319":2,"320":1,"321":1,"343":1,"350":2,"359":2,"361":1,"365":2,"368":1,"369":1,"384":1,"388":1,"397":1,"405":2,"438":2,"459":1,"461":2,"501":1,"507":1,"522":1,"526":1,"528":1,"529":1,"531":1,"534":1,"536":4,"545":1,"546":1,"550":1,"556":1,"579":1,"620":1,"674":1,"688":1,"690":10,"691":3,"692":1,"696":3,"698":12,"699":1,"700":2,"702":5,"703":1,"707":3,"719":1,"731":2,"743":2,"744":1,"749":1,"799":1,"803":1,"820":1,"827":1,"848":4,"889":1,"898":1,"899":1,"905":1,"910":1,"914":1,"920":1,"926":3,"966":1,"988":5,"990":1,"994":1,"996":1,"1010":1,"1012":2,"1013":1,"1018":2,"1042":3,"1051":1,"1054":1,"1067":1,"1074":1,"1093":1,"1100":1,"1108":1,"1114":1,"1120":1,"1124":1,"1133":1,"1157":1,"1161":1,"1168":3,"1177":1,"1192":1,"1195":1,"1204":2,"1211":1,"1212":2,"1214":1,"1236":1,"1237":1,"1238":1,"1239":1,"1240":1,"1245":1,"1246":7,"1247":14,"1252":1,"1253":3,"1254":1,"1256":1,"1258":1,"1259":32,"1260":1,"1263":3,"1265":1,"1267":1,"1269":1,"1270":43,"1275":3,"1276":6,"1277":1,"1278":3,"1285":88,"1299":10,"1302":1,"1303":1,"1304":3,"1305":2,"1307":2,"1308":56,"1310":25,"1316":1,"1317":3,"1322":40,"1323":50,"1338":4,"1341":1,"1349":5,"1351":1,"1352":3,"1354":2,"1358":52,"1368":1,"1371":1,"1375":1,"1379":7,"1382":2,"1390":1,"1394":37,"1395":158,"1398":1,"1400":1,"1401":28,"1406":1,"1412":1,"1414":21,"1415":1,"1416":15,"1427":1,"1430":1,"1432":1,"1435":54,"1437":13,"1453":1,"1454":1,"1458":2,"1462":1,"1469":1,"1471":1,"1474":1,"1484":1,"1486":1,"1487":1,"1488":1,"1495":7,"1497":1,"1499":2,"1533":1,"1544":1,"1547":1,"1552":1,"1563":1,"1584":1,"1595":1,"1599":1,"1604":1,"1629":1,"1630":9,"1631":3,"1635":1,"1638":6,"1640":2,"1653":1,"1664":1,"1666":1,"1668":1,"1684":1,"1706":1,"1738":1,"1747":1,"1764":1,"1775":1,"1814":2,"1817":1,"1820":2,"1823":1,"1848":1,"1850":3,"1890":1,"1909":2,"1919":4,"1923":2,"1932":3,"1934":1,"1936":6,"1939":2,"1940":1,"1947":1,"1952":1,"1974":2,"1993":1,"1995":1,"1996":3,"1999":2,"2009":1,"2031":1,"2035":1,"2046":1,"2047":1,"2065":1,"2073":2,"2084":3,"2106":1,"2125":1,"2134":1,"2135":1,"2141":2,"2155":1,"2162":3,"2163":1,"2164":1,"2166":2,"2168":3,"2178":1,"2183":1,"2184":1,"2187":1,"2188":1,"2219":1,"2231":2}}],["scrypt",{"3":{"1904":1}}],["scroller",{"3":{"1525":1}}],["scrolled",{"3":{"1323":1,"2072":1}}],["scrolledid",{"3":{"1323":3}}],["scrollports",{"3":{"1437":1}}],["scrollposition",{"3":{"1323":1}}],["scrollwidth",{"3":{"1435":1}}],["scrollbar",{"3":{"1435":1}}],["scrollbehavior",{"3":{"1323":2}}],["scrollintoviewasync",{"3":{"1323":1}}],["scrollingelement",{"3":{"1323":1}}],["scrolling",{"3":{"1228":1,"1229":1,"1323":1,"1435":1,"1558":1}}],["scrollmanager",{"3":{"1323":2}}],["scrolltop",{"3":{"1323":1}}],["scrolltrackerid",{"3":{"1323":1}}],["scrollcontainerselector",{"3":{"1323":1}}],["scrolls",{"3":{"1247":1,"1323":5,"1435":1,"1437":1}}],["scroll",{"3":{"881":2,"883":1,"1285":1,"1323":33,"1382":1,"1394":12,"1395":2,"1437":3,"1525":1,"1642":1,"1668":3,"2072":2,"2075":1,"2078":2}}],["scratch",{"3":{"799":1,"1395":1,"1401":1,"1414":1,"1435":2,"1454":1,"1465":1,"1779":1}}],["scraper",{"3":{"1997":1}}],["scrapers",{"3":{"174":1}}],["scraped",{"3":{"1458":1}}],["scrape",{"3":{"1414":1,"1892":1}}],["scraping",{"3":{"725":1,"1316":1}}],["scramble",{"3":{"150":1}}],["screenreader",{"3":{"1523":1,"1525":2,"1530":1,"1532":1,"1588":2,"1590":2,"1595":1,"1597":1}}],["screencap",{"2":{"1478":1},"3":{"1478":3}}],["screenlayout",{"3":{"1415":1}}],["screensize",{"3":{"1415":1}}],["screens",{"3":{"1059":1,"1299":1,"1323":4,"1349":2,"1379":2,"1380":1,"1382":1,"1384":1,"1394":3,"1395":2,"1408":1,"1414":3,"1416":3,"1435":1,"1437":1,"1558":1,"2069":1,"2078":1}}],["screenshots",{"3":{"1416":1,"1435":2,"1478":4,"1479":5,"1668":1}}],["screenshotformat",{"3":{"1416":1}}],["screenshotted",{"3":{"690":1,"1395":5,"1630":1}}],["screenshot",{"0":{"1478":1,"1479":1},"3":{"0":1,"691":1,"1202":1,"1203":1,"1390":1,"1395":2,"1416":7,"1435":1,"1478":1,"1479":1,"1495":1}}],["screened",{"3":{"676":1,"1414":1}}],["screen",{"0":{"1643":1,"2071":1},"1":{"1604":1,"1605":1,"1606":1,"1607":1,"1608":1,"1609":1,"1738":1,"1739":1,"1740":1,"1741":1,"1742":1,"1743":1},"3":{"24":1,"109":1,"380":1,"620":1,"622":1,"683":1,"691":1,"692":1,"906":1,"1059":2,"1259":2,"1270":2,"1285":3,"1299":8,"1310":1,"1317":1,"1322":1,"1323":21,"1338":1,"1346":1,"1349":2,"1358":8,"1374":1,"1379":3,"1387":1,"1390":1,"1394":24,"1395":23,"1396":1,"1401":2,"1408":1,"1413":3,"1414":6,"1415":10,"1416":27,"1435":13,"1437":1,"1478":1,"1522":1,"1523":3,"1525":1,"1529":1,"1530":2,"1531":1,"1532":1,"1556":1,"1557":1,"1576":1,"1588":1,"1590":1,"1594":1,"1595":2,"1602":1,"1603":1,"1604":2,"1605":1,"1609":1,"1625":1,"1639":1,"1641":1,"1667":1,"1668":2,"1673":1,"1684":1,"1711":1,"1738":2,"1739":1,"1743":1,"1759":1,"1772":1,"1941":2,"1942":1,"1949":1,"1961":1,"1965":1,"2044":1,"2069":4,"2070":1,"2071":2,"2072":1,"2079":4,"2115":1,"2120":1,"2190":1,"2199":1,"2201":1,"2202":1}}],["scrum",{"3":{"2218":1,"2219":1}}],["scrutiny",{"3":{"1435":1}}],["scrutor",{"0":{"1262":1},"3":{"0":2,"5":1,"117":2,"119":1,"121":2,"122":2,"572":1,"649":1,"781":1,"922":1,"1042":1,"1212":1,"1213":1,"1259":2,"1262":2,"1265":1,"1268":1,"1270":12,"1275":1,"1308":1,"1310":3,"1312":1,"1315":3,"1317":1,"1318":1,"1322":15,"1323":8,"1353":1,"1358":10,"1379":2,"1394":9,"1403":1,"1414":9,"1435":5,"1437":2,"1488":1,"1506":1,"1576":3,"1650":1,"1729":1,"1817":1,"1820":1,"1825":1,"1826":1,"1878":1,"1882":1,"1955":1,"2042":1,"2094":2,"2183":1,"2226":1,"2231":1}}],["scrubemailaddressesfromproductreviewernames",{"3":{"403":1}}],["scrubs",{"3":{"403":1,"1322":1,"1323":1,"1626":1,"1640":1,"2062":1}}],["scrubbed",{"3":{"0":1,"395":1}}],["scripting",{"3":{"1466":1}}],["scriptedhandler",{"3":{"1199":4,"1207":2}}],["scripted",{"3":{"861":2,"862":1,"1003":1,"1004":1,"1126":1,"1435":2,"1726":1,"2004":1}}],["scripts",{"0":{"1472":1},"1":{"1470":1,"1471":1,"1472":1,"1473":1,"1474":1,"1475":1,"1476":1,"1477":1,"1478":1,"1479":1,"1480":1,"1481":1,"1482":1,"1483":1},"3":{"0":1,"208":1,"387":1,"390":1,"484":1,"490":1,"491":1,"492":1,"493":1,"633":1,"1004":3,"1093":1,"1192":1,"1323":2,"1338":1,"1421":1,"1435":2,"1470":6,"1471":1,"1472":6,"1473":1,"1474":1,"1478":2,"1479":1,"1481":3,"1562":1,"1569":1,"1705":1,"1709":1,"1726":1,"1875":1,"1921":1,"2173":1}}],["script",{"1":{"1181":1,"1182":1,"1183":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1},"2":{"483":1},"3":{"0":3,"157":1,"208":1,"212":2,"213":1,"214":5,"215":1,"216":1,"219":6,"273":1,"275":1,"286":1,"461":1,"483":1,"484":1,"543":1,"545":1,"633":2,"634":1,"659":1,"861":2,"869":1,"966":1,"996":2,"998":1,"1004":1,"1082":1,"1084":1,"1181":1,"1183":4,"1184":1,"1185":3,"1186":2,"1187":3,"1212":2,"1271":1,"1285":2,"1299":3,"1310":2,"1315":1,"1322":5,"1323":6,"1335":2,"1338":9,"1358":1,"1414":1,"1416":1,"1432":2,"1435":6,"1442":1,"1451":1,"1454":1,"1457":5,"1459":1,"1470":1,"1471":5,"1472":1,"1474":13,"1478":3,"1479":4,"1491":1,"1495":1,"1524":1,"1525":3,"1530":1,"1576":1,"1590":2,"1599":1,"1660":2,"1664":1,"1672":2,"1673":1,"1680":1,"1681":1,"1683":5,"1684":1,"1700":2,"1709":1,"1721":2,"1726":1,"1727":1,"1728":5,"1730":1,"1965":1,"1967":1,"1971":1,"2033":2,"2084":1,"2096":2,"2123":1,"2134":1,"2146":2,"2147":1,"2148":3,"2149":3,"2151":1,"2152":1,"2231":1}}],["scars",{"3":{"2071":1}}],["scarier",{"3":{"2035":1}}],["scar",{"3":{"1358":1,"1454":1}}],["scarcest",{"3":{"871":1}}],["scatter",{"3":{"1395":1,"1816":1,"1825":1,"1912":1,"1922":1}}],["scattering",{"3":{"1247":1,"1299":1,"1323":1,"1358":1,"1379":1,"1395":1,"1923":1,"1963":1,"2229":1}}],["scattered",{"3":{"118":1,"187":1,"360":1,"1201":1,"1212":1,"1237":1,"1247":2,"1260":1,"1270":1,"1285":2,"1300":3,"1310":1,"1313":1,"1322":2,"1323":2,"1347":1,"1349":6,"1351":1,"1394":2,"1395":1,"1401":2,"1414":1,"1416":2,"1435":1,"1556":1,"1812":1,"1818":2,"1821":1,"1826":1,"1881":1,"1929":1,"2135":1}}],["scatters",{"3":{"116":1}}],["scaffold",{"0":{"1649":1,"2088":1},"1":{"2086":1,"2087":1,"2088":1,"2089":1,"2090":1,"2091":1,"2092":1,"2093":1,"2094":1,"2095":1,"2096":1,"2097":1},"2":{"1688":1,"1698":1},"3":{"631":1,"633":1,"634":1,"635":1,"907":1,"923":1,"939":1,"942":1,"1034":1,"1133":1,"1285":4,"1435":3,"1487":3,"1601":2,"1645":2,"1648":1,"1655":1,"1660":2,"1673":1,"1680":1,"1682":1,"1683":4,"1684":4,"1685":6,"1686":1,"1688":3,"1691":1,"1692":1,"1696":1,"1698":2,"1700":3,"1701":1,"1704":2,"1718":3,"1723":1,"1726":2,"1728":1,"1729":1,"2003":1,"2086":1,"2088":1,"2097":2,"2209":1}}],["scaffolds",{"3":{"564":1,"698":1,"891":1,"1034":2,"1037":1,"1190":1,"1283":1,"1285":1,"1680":1,"1725":1,"1730":1,"2096":1,"2210":1}}],["scaffolded",{"3":{"41":1,"551":1,"749":1,"891":1,"921":1,"1270":2,"1285":4,"1395":1,"1435":5,"1496":1,"1645":1,"1648":1,"1649":1,"1650":1,"1651":1,"1652":1,"1653":1,"1654":1,"1655":1,"1663":1,"1674":1,"1683":1,"1684":4,"1685":4,"1686":1,"1689":1,"1696":1,"1698":1,"1700":4,"1719":1,"1726":1,"1728":1,"1729":1,"2003":1,"2100":1}}],["scaffolding",{"0":{"1660":1},"1":{"631":1,"632":1,"633":1,"634":1,"635":1,"636":1,"1725":1,"1726":1,"1727":1,"1728":1,"1729":1,"1730":1},"3":{"0":1,"534":1,"631":1,"633":1,"1212":1,"1285":1,"1427":1,"1428":1,"1435":3,"1437":2,"1441":1,"1487":1,"1601":1,"1698":1,"1725":1,"1726":2,"2061":1,"2097":1,"2231":1}}],["scalable",{"3":{"1237":1,"2212":1,"2216":1,"2217":1,"2219":1}}],["scalability",{"0":{"1548":1},"3":{"1216":1,"1223":1,"1228":1,"1229":8,"1231":1,"1233":1,"1237":3,"1241":1,"1242":1,"1243":1,"1247":16,"1252":1,"1259":8,"1260":1,"1263":1,"1269":1,"1270":18,"1271":1,"1274":1,"1278":1,"1285":33,"1286":1,"1299":10,"1300":10,"1305":1,"1306":1,"1308":11,"1309":4,"1310":21,"1312":1,"1315":1,"1322":9,"1323":8,"1336":1,"1338":10,"1342":1,"1346":1,"1349":13,"1354":1,"1357":1,"1358":61,"1366":1,"1368":5,"1374":1,"1379":16,"1387":1,"1394":19,"1395":43,"1401":16,"1405":1,"1412":1,"1414":11,"1416":10,"1421":1,"1426":1,"1435":13,"1437":1,"1449":1,"1454":1,"1455":1,"1457":2,"1460":1,"1495":1,"1498":1,"1525":1,"1529":1,"1530":1,"1532":1,"1534":1,"1576":1,"1580":1,"1581":1,"1582":1,"1590":1,"1594":1,"1595":1,"1596":1,"1597":1,"1795":1,"1799":1}}],["scalars",{"3":{"1234":1,"1237":1,"1285":2,"1349":9,"1358":1,"1368":6,"1395":3,"1414":1,"1435":1,"1685":2,"2185":1,"2191":1}}],["scalaronlyspec",{"3":{"1199":2,"1207":1,"1435":8}}],["scalaronlyqueryspec",{"3":{"1199":2,"1207":1,"1435":6}}],["scalar",{"3":{"0":1,"47":1,"49":1,"166":1,"799":3,"801":1,"802":1,"1050":2,"1053":1,"1132":1,"1133":1,"1236":1,"1237":1,"1241":1,"1247":2,"1280":2,"1285":11,"1299":1,"1310":6,"1322":2,"1341":5,"1349":22,"1352":1,"1358":22,"1362":1,"1368":10,"1394":1,"1395":27,"1400":1,"1401":2,"1409":1,"1414":11,"1435":9,"1472":1,"1488":1,"1495":1,"1533":1,"1576":1,"1655":1,"1662":1,"1665":3,"1726":1,"1748":1,"1809":1,"1848":1,"1852":1,"1857":2,"1858":1,"1861":1,"1865":1,"1928":1,"2034":1}}],["scaling",{"0":{"1969":1},"3":{"48":1,"58":2,"387":1,"389":1,"706":1,"740":1,"742":1,"759":1,"768":1,"1299":2,"1322":1,"1479":1,"1567":1,"1723":1,"1947":1,"1970":1}}],["scales",{"3":{"133":1,"282":1,"760":1,"765":1,"781":1,"799":1,"1125":1,"1270":1,"1299":1,"1300":1,"1308":1,"1338":1,"1358":3,"1394":1,"1395":1,"1525":1,"1548":1,"1670":1,"1895":1,"2099":1,"2201":1}}],["scaled",{"1":{"662":1,"663":1,"664":1,"665":1,"666":1,"667":1,"668":1,"669":1,"670":1},"3":{"0":1,"24":1,"58":1,"234":1,"242":1,"245":2,"283":1,"662":1,"664":2,"667":2,"732":1,"766":1,"768":1,"769":1,"793":1,"1212":1,"1233":1,"1237":1,"1300":2,"1308":1,"1316":1,"1335":1,"1338":2,"1441":1,"1446":1,"1466":1,"1472":1,"1479":1,"1523":1,"1530":1,"1567":1,"1590":1,"1599":1,"1847":1,"1854":1,"1915":1,"1922":1,"1923":1,"1970":1,"1971":1,"2000":1,"2102":1,"2187":1,"2231":2}}],["scale",{"0":{"1255":1},"2":{"766":1},"3":{"0":5,"15":1,"20":1,"48":1,"58":1,"60":1,"62":2,"68":1,"100":1,"141":1,"176":1,"225":1,"234":1,"243":1,"283":1,"285":1,"291":1,"390":2,"435":1,"458":1,"480":1,"483":1,"486":1,"487":1,"488":1,"489":1,"490":2,"491":1,"492":1,"493":1,"494":2,"495":1,"517":1,"538":1,"700":1,"765":2,"766":2,"768":1,"869":1,"871":1,"882":1,"995":1,"1011":1,"1101":1,"1152":1,"1154":1,"1156":1,"1213":1,"1229":1,"1237":1,"1255":1,"1256":1,"1259":8,"1270":1,"1275":1,"1285":1,"1299":1,"1308":2,"1322":3,"1323":6,"1335":1,"1338":1,"1349":2,"1350":1,"1358":3,"1379":1,"1395":10,"1397":1,"1401":1,"1414":1,"1456":1,"1461":1,"1464":1,"1466":9,"1473":1,"1524":1,"1525":4,"1530":1,"1531":1,"1533":1,"1535":1,"1536":3,"1548":2,"1553":3,"1564":1,"1567":3,"1571":1,"1574":1,"1576":6,"1584":1,"1590":2,"1598":1,"1637":2,"1664":1,"1675":1,"1677":2,"1678":2,"1679":1,"1794":1,"1796":1,"1840":1,"1883":1,"1899":1,"1922":1,"1934":1,"1939":1,"1941":1,"1971":1,"2000":1,"2001":1,"2009":1,"2013":1,"2041":1,"2065":1,"2074":1,"2084":1,"2156":1,"2227":1}}],["scanfile",{"3":{"1435":2}}],["scanpage",{"3":{"1416":1}}],["scanoutcome",{"3":{"1199":2,"1203":1,"1207":1,"1395":4}}],["scanoutcomekind",{"3":{"1199":3,"1203":1,"1207":1,"1395":5}}],["scanonmainthreadasync",{"2":{"682":1},"3":{"682":1,"1416":1}}],["scangridasync",{"2":{"616":1,"620":1,"621":1,"2056":1},"3":{"616":1,"618":2,"620":1,"621":1,"1432":1,"1435":4,"1486":1,"1487":2,"1590":1,"2056":1}}],["scananddeleteasync",{"2":{"251":1},"3":{"251":1,"1300":2}}],["scanasync",{"2":{"621":1},"3":{"0":1,"618":1,"621":1,"682":1,"1395":1,"1416":4,"1432":1,"1435":4,"1486":1,"1487":2,"1590":1,"2056":1}}],["scanmodules",{"3":{"1315":1,"1322":1,"1661":1}}],["scanmodule",{"3":{"121":1,"1315":1,"1322":2,"1435":1,"1661":1}}],["scanmoduleapplicationservices",{"2":{"117":1,"122":1,"127":1,"922":1,"1262":1,"1315":1,"1353":1,"1403":1,"1650":1,"1661":1,"1829":1,"1882":1,"1885":1,"1955":1,"2094":1},"3":{"117":1,"122":1,"127":1,"922":1,"1259":1,"1262":2,"1270":5,"1271":2,"1315":2,"1322":15,"1349":1,"1353":1,"1358":15,"1395":8,"1401":2,"1403":1,"1414":12,"1650":1,"1661":1,"1829":1,"1882":2,"1885":1,"1955":1,"2094":2,"2097":1}}],["scannableassemblylocations",{"3":{"1435":5}}],["scannable",{"3":{"1300":1,"1394":1,"1395":2,"1435":2}}],["scanners",{"3":{"1310":1}}],["scanner",{"2":{"685":1},"3":{"684":1,"685":2,"690":2,"691":1,"694":1,"734":1,"872":1,"1271":2,"1299":1,"1300":3,"1310":1,"1322":9,"1349":1,"1395":20,"1414":4,"1415":4,"1416":7,"1435":2,"1626":1}}],["scanneravailable",{"2":{"684":1,"685":1},"3":{"684":1,"685":1,"690":1,"1395":1}}],["scannedpages",{"3":{"1435":3}}],["scannedpasswordhasherset",{"3":{"1435":1}}],["scannedsourcedirectories",{"3":{"1435":5}}],["scannedendpointset",{"3":{"1435":4}}],["scannedassemblies",{"3":{"1285":3}}],["scanned",{"3":{"0":2,"27":1,"160":1,"200":1,"545":1,"564":1,"566":1,"618":1,"622":1,"628":1,"691":1,"692":1,"781":1,"799":1,"870":1,"873":1,"910":1,"1118":1,"1135":1,"1163":1,"1204":1,"1271":3,"1285":3,"1299":2,"1300":4,"1310":1,"1322":1,"1323":1,"1348":1,"1387":1,"1395":24,"1413":1,"1414":1,"1416":2,"1433":2,"1435":39,"1437":1,"1442":1,"1454":1,"1458":2,"1466":1,"1474":1,"1488":1,"1495":3,"1525":2,"1590":2,"1625":1,"1626":2,"1642":2,"1660":1,"1700":1,"1719":1,"1729":1,"1814":1,"1882":1,"1955":1,"2096":1,"2120":1}}],["scanning",{"0":{"1882":1},"1":{"679":1,"680":1,"681":1,"682":1,"683":1,"684":1,"685":1,"686":1},"3":{"0":5,"5":1,"117":1,"413":1,"565":2,"618":1,"619":1,"679":1,"681":1,"682":2,"683":1,"689":2,"690":1,"691":2,"801":1,"966":1,"1055":1,"1114":1,"1116":2,"1118":3,"1119":1,"1161":1,"1175":1,"1192":1,"1212":1,"1213":2,"1259":2,"1262":1,"1270":2,"1271":1,"1285":4,"1299":2,"1300":2,"1308":1,"1310":7,"1311":1,"1318":1,"1322":8,"1323":1,"1338":2,"1349":3,"1358":20,"1368":6,"1379":2,"1394":3,"1395":32,"1401":5,"1403":1,"1414":13,"1415":2,"1416":12,"1426":1,"1432":1,"1435":17,"1436":1,"1437":1,"1454":1,"1466":3,"1468":1,"1469":1,"1487":1,"1495":1,"1547":1,"1626":1,"1630":2,"1650":1,"1668":1,"1955":1,"1958":1,"2056":1,"2092":1,"2094":1,"2231":1}}],["scans",{"3":{"0":5,"54":1,"122":1,"253":1,"265":1,"268":1,"341":1,"413":2,"545":4,"583":1,"585":1,"616":3,"618":10,"620":1,"621":1,"682":1,"689":2,"690":1,"691":1,"733":1,"870":3,"873":1,"953":1,"1050":1,"1108":1,"1133":1,"1170":1,"1229":1,"1237":2,"1250":1,"1256":1,"1259":2,"1270":3,"1275":1,"1279":1,"1281":1,"1285":6,"1299":4,"1300":2,"1310":2,"1311":1,"1315":2,"1318":1,"1322":6,"1323":4,"1338":1,"1349":3,"1358":5,"1394":3,"1395":18,"1414":2,"1415":1,"1416":8,"1420":1,"1430":1,"1435":63,"1437":2,"1466":2,"1484":1,"1486":1,"1489":1,"1491":1,"1525":2,"1530":1,"1531":1,"1576":1,"1590":2,"1595":2,"1599":1,"1604":1,"1630":2,"1653":1,"1668":1,"1738":1,"1740":1,"1809":1,"1823":1,"1874":1,"1880":1,"1882":1,"1897":1,"1928":1,"1955":1,"2056":1,"2219":1}}],["scan",{"0":{"1571":1},"3":{"0":17,"24":2,"109":2,"117":1,"121":1,"245":1,"248":2,"251":1,"257":1,"483":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":2,"492":2,"493":2,"494":3,"495":3,"537":1,"539":1,"546":2,"549":1,"552":1,"572":1,"583":4,"585":1,"587":1,"616":4,"618":8,"619":1,"620":3,"622":1,"626":3,"628":1,"629":2,"649":1,"682":5,"683":1,"684":6,"685":2,"686":1,"688":2,"690":6,"691":2,"692":1,"726":1,"733":3,"734":1,"735":1,"799":1,"810":1,"849":1,"867":3,"870":1,"871":1,"872":1,"874":1,"876":1,"881":2,"906":1,"922":4,"924":1,"926":3,"946":1,"1133":5,"1135":2,"1161":2,"1162":1,"1163":3,"1196":1,"1212":2,"1228":1,"1229":2,"1236":1,"1237":3,"1242":1,"1259":6,"1262":3,"1263":2,"1265":2,"1269":2,"1270":5,"1271":7,"1285":23,"1299":21,"1300":13,"1307":1,"1308":2,"1310":7,"1314":4,"1315":1,"1317":1,"1318":1,"1322":27,"1323":14,"1338":1,"1349":4,"1353":4,"1358":121,"1368":6,"1376":1,"1379":1,"1394":30,"1395":117,"1401":1,"1413":1,"1414":34,"1415":2,"1416":33,"1420":1,"1430":2,"1432":3,"1433":1,"1435":178,"1437":10,"1441":1,"1444":2,"1449":1,"1458":1,"1486":5,"1487":3,"1488":2,"1490":1,"1491":2,"1495":6,"1525":3,"1530":1,"1576":2,"1588":1,"1590":5,"1595":2,"1626":2,"1628":1,"1630":2,"1642":1,"1644":2,"1650":2,"1652":1,"1665":1,"1672":1,"1676":1,"1748":1,"1814":1,"1815":1,"1823":2,"1829":1,"1848":1,"1880":2,"1882":2,"1897":3,"1915":2,"1919":1,"1922":1,"1955":2,"1958":1,"1990":1,"2040":1,"2056":3,"2078":1,"2094":1,"2102":2,"2162":2,"2231":1}}],["sil",{"3":{"2074":1,"2149":1}}],["silver",{"3":{"1349":1,"1363":1,"1368":1,"1630":1}}],["silo",{"3":{"1149":1,"1152":1,"1154":1,"1155":1,"1157":2,"2231":1}}],["silenced",{"3":{"982":1,"1416":1,"1435":2}}],["silencesonlythattype",{"3":{"1435":7}}],["silencestherule",{"3":{"1435":5}}],["silences",{"3":{"27":1,"372":1,"1358":1,"1435":9}}],["silence",{"3":{"0":2,"122":1,"200":1,"255":1,"370":1,"399":1,"401":1,"591":1,"821":1,"863":1,"1013":1,"1310":1,"1338":1,"1394":1,"1416":1,"1435":5,"1466":1,"1491":1,"1669":1,"1922":1,"2147":1,"2158":1,"2167":1}}],["silently",{"1":{"1886":1,"1887":1,"1888":1,"1889":1,"1890":1,"1891":1},"3":{"0":15,"6":1,"20":1,"67":1,"68":1,"77":1,"98":1,"109":2,"110":1,"119":1,"121":1,"122":1,"132":1,"135":3,"142":1,"160":1,"170":1,"175":2,"178":1,"180":1,"188":1,"214":1,"216":1,"220":1,"248":1,"302":1,"310":1,"318":2,"335":2,"340":1,"344":1,"350":1,"359":1,"360":1,"361":1,"363":2,"364":1,"370":1,"371":1,"373":1,"387":2,"391":1,"397":2,"399":1,"403":1,"408":1,"413":1,"415":1,"427":1,"428":1,"447":1,"459":1,"461":2,"474":1,"478":1,"484":1,"499":1,"501":2,"509":1,"518":1,"530":1,"534":1,"549":1,"558":1,"573":1,"574":1,"576":2,"583":1,"585":1,"592":1,"608":1,"610":1,"611":1,"625":1,"628":1,"632":1,"634":1,"635":1,"658":1,"667":1,"676":1,"698":2,"699":1,"700":1,"706":1,"709":1,"717":1,"735":1,"741":1,"749":1,"750":1,"758":1,"765":1,"768":1,"775":1,"784":1,"789":1,"819":1,"821":1,"827":1,"837":1,"838":1,"840":1,"847":1,"856":1,"863":1,"889":1,"890":1,"899":2,"907":1,"930":1,"931":1,"953":1,"954":1,"964":1,"972":1,"988":1,"990":1,"991":1,"998":1,"1005":1,"1017":1,"1018":1,"1028":1,"1042":1,"1050":2,"1051":1,"1059":1,"1060":1,"1067":1,"1085":1,"1100":1,"1102":1,"1117":1,"1126":1,"1132":1,"1154":1,"1215":1,"1222":1,"1228":1,"1229":3,"1237":4,"1241":4,"1247":9,"1252":1,"1257":2,"1259":6,"1262":2,"1263":1,"1270":6,"1271":2,"1273":2,"1276":1,"1278":2,"1279":1,"1280":2,"1283":1,"1285":44,"1290":1,"1299":32,"1300":9,"1305":1,"1308":5,"1309":1,"1310":23,"1311":3,"1314":1,"1321":1,"1322":22,"1323":24,"1325":1,"1331":1,"1332":2,"1333":1,"1334":1,"1335":1,"1338":19,"1342":1,"1343":2,"1346":1,"1348":1,"1349":14,"1351":1,"1354":1,"1358":44,"1365":1,"1367":1,"1368":5,"1379":7,"1382":1,"1384":1,"1389":1,"1394":26,"1395":26,"1401":11,"1408":1,"1413":1,"1414":19,"1415":7,"1416":19,"1420":1,"1422":1,"1425":1,"1426":9,"1427":1,"1428":1,"1429":1,"1430":2,"1431":1,"1432":2,"1435":76,"1437":14,"1441":2,"1442":2,"1443":2,"1444":2,"1446":2,"1448":1,"1452":2,"1453":1,"1454":3,"1459":1,"1466":6,"1468":1,"1472":1,"1474":1,"1485":2,"1487":4,"1488":3,"1489":1,"1490":1,"1491":2,"1525":1,"1545":1,"1546":1,"1570":1,"1584":1,"1606":1,"1638":3,"1639":1,"1640":2,"1644":1,"1650":1,"1652":2,"1653":2,"1674":1,"1676":2,"1683":1,"1684":3,"1685":1,"1699":2,"1700":1,"1707":1,"1727":1,"1729":1,"1740":1,"1747":1,"1748":1,"1763":1,"1764":2,"1766":2,"1784":1,"1801":1,"1820":1,"1822":1,"1824":1,"1825":1,"1829":1,"1839":1,"1842":1,"1843":1,"1848":1,"1850":2,"1868":1,"1869":1,"1876":1,"1878":1,"1880":1,"1885":1,"1886":2,"1889":1,"1891":1,"1901":1,"1919":1,"1922":2,"1939":1,"1946":1,"1947":1,"1950":1,"1951":1,"1958":1,"1963":1,"1976":1,"1983":1,"1987":1,"1993":1,"1995":1,"2000":2,"2001":1,"2009":1,"2012":1,"2042":1,"2043":1,"2044":2,"2046":1,"2048":1,"2055":1,"2057":1,"2060":1,"2066":1,"2074":1,"2094":1,"2096":2,"2097":2,"2104":1,"2107":1,"2120":1,"2121":1,"2124":1,"2128":1,"2137":1,"2141":1,"2143":1,"2144":1,"2148":1,"2151":1,"2155":2,"2158":1,"2163":1,"2165":1,"2173":1,"2174":1,"2181":1,"2182":1,"2187":1,"2196":2,"2199":1,"2206":1,"2219":1,"2231":2}}],["silent",{"3":{"0":5,"70":1,"78":1,"122":1,"136":1,"155":1,"193":1,"201":1,"227":1,"230":1,"235":1,"340":1,"342":2,"361":1,"383":1,"401":1,"465":1,"500":1,"518":1,"519":1,"520":1,"538":1,"573":1,"583":1,"585":1,"609":1,"617":1,"642":1,"649":1,"666":1,"692":1,"726":1,"734":1,"740":1,"748":1,"757":1,"765":1,"782":1,"784":1,"790":1,"818":1,"829":1,"831":1,"838":1,"922":1,"924":1,"931":1,"971":1,"990":1,"997":1,"1036":1,"1094":1,"1102":1,"1108":1,"1191":1,"1224":1,"1229":2,"1237":2,"1247":3,"1249":1,"1259":3,"1269":1,"1270":5,"1273":1,"1276":1,"1285":13,"1299":2,"1308":2,"1309":1,"1310":10,"1315":1,"1319":1,"1322":8,"1323":4,"1326":1,"1337":1,"1338":11,"1349":2,"1352":1,"1358":22,"1378":1,"1388":1,"1394":3,"1395":9,"1401":4,"1414":5,"1416":10,"1419":1,"1426":2,"1432":2,"1435":43,"1437":1,"1443":2,"1454":1,"1459":1,"1465":1,"1466":1,"1471":1,"1475":1,"1485":1,"1487":4,"1488":1,"1491":1,"1495":1,"1552":1,"1560":1,"1584":2,"1598":2,"1638":2,"1653":1,"1666":1,"1670":1,"1683":1,"1698":1,"1811":1,"1842":1,"1855":1,"1861":1,"1867":1,"1875":1,"1888":1,"1944":1,"1948":1,"1951":1,"1957":2,"1984":1,"1985":1,"1993":1,"1999":1,"2065":1,"2066":1,"2106":1,"2111":1,"2141":1,"2158":1,"2168":1,"2186":1,"2191":1}}],["sigsegv",{"3":{"1416":1}}],["sigstore",{"3":{"374":1}}],["sighted",{"3":{"1395":1,"1416":6}}],["sight",{"3":{"1358":1,"1379":1,"1395":1,"1416":1,"2049":1}}],["sighting",{"3":{"1237":1,"1349":1}}],["sig",{"3":{"1299":1}}],["sigma",{"3":{"1012":2}}],["signer",{"3":{"1892":1,"1898":1}}],["signedout",{"3":{"905":1,"1289":1,"1981":1}}],["signed",{"3":{"0":5,"68":1,"110":1,"186":1,"188":1,"265":3,"285":1,"351":1,"372":1,"374":1,"384":2,"434":1,"499":2,"501":1,"616":2,"618":1,"689":1,"691":1,"759":1,"790":1,"792":1,"827":1,"853":2,"855":1,"904":4,"905":5,"906":3,"907":3,"1067":1,"1184":1,"1212":2,"1228":1,"1229":1,"1276":1,"1285":6,"1289":1,"1290":1,"1299":20,"1306":1,"1310":4,"1322":1,"1323":28,"1338":2,"1372":1,"1394":4,"1395":5,"1406":1,"1413":2,"1414":12,"1415":2,"1416":4,"1428":1,"1433":1,"1435":13,"1437":1,"1453":1,"1478":1,"1575":1,"1576":2,"1585":1,"1590":1,"1595":1,"1626":1,"1667":1,"1671":2,"1702":1,"1749":1,"1760":2,"1772":1,"1896":1,"1921":1,"1944":1,"1965":1,"1972":2,"1976":1,"1979":1,"1981":6,"1982":1,"1983":2,"1984":1,"2032":1,"2082":1,"2110":1,"2123":1,"2165":1,"2231":1}}],["signoutasync",{"3":{"1323":1,"1435":5,"1670":1}}],["signup",{"3":{"283":1,"1998":1,"2000":1}}],["signups",{"3":{"282":1}}],["significance",{"3":{"1630":3}}],["significant",{"3":{"46":1,"1536":1,"1551":1,"1570":2}}],["signinwithbrokerasync",{"3":{"1416":1}}],["signinasync",{"3":{"1416":5}}],["signinscheme",{"3":{"1310":1}}],["signingkey",{"3":{"1338":2,"1441":2}}],["signingcredentials",{"3":{"1299":2,"1435":1}}],["signingalgorithm=rs256",{"3":{"1435":1}}],["signingalgorithm",{"2":{"31":1,"1287":1,"1894":1},"3":{"31":2,"1212":1,"1287":1,"1299":4,"1466":2,"1640":1,"1894":1}}],["signing",{"0":{"1287":1},"2":{"31":1,"418":1,"1480":1},"3":{"0":4,"30":1,"31":3,"32":1,"349":1,"353":1,"418":2,"420":1,"423":2,"424":1,"428":1,"499":1,"501":1,"514":1,"598":1,"599":2,"674":1,"788":1,"789":2,"790":2,"791":1,"792":1,"793":2,"794":1,"904":1,"905":1,"906":1,"963":1,"1212":1,"1289":2,"1299":13,"1310":4,"1322":1,"1323":2,"1334":1,"1338":1,"1414":2,"1415":1,"1428":1,"1432":1,"1435":2,"1441":1,"1454":1,"1455":1,"1465":1,"1466":2,"1471":2,"1480":5,"1487":1,"1489":1,"1575":1,"1588":2,"1590":1,"1594":1,"1595":2,"1596":1,"1789":1,"1892":2,"1893":1,"1894":1,"1898":1,"1981":1,"1983":1,"2020":2,"2165":4,"2166":1,"2229":1,"2231":1}}],["signs",{"3":{"0":2,"30":1,"31":1,"350":3,"353":1,"599":2,"905":3,"907":2,"909":1,"1212":1,"1287":2,"1289":1,"1299":9,"1310":3,"1323":1,"1325":1,"1327":1,"1347":1,"1349":1,"1358":1,"1379":1,"1395":2,"1405":2,"1414":1,"1435":2,"1440":1,"1448":1,"1454":1,"1465":1,"1466":1,"1480":1,"1666":1,"1892":1,"1893":1,"1894":2,"1974":2,"1977":1,"2054":1}}],["sign",{"0":{"1292":1},"3":{"0":8,"186":1,"243":2,"258":2,"261":1,"265":1,"281":1,"349":1,"350":4,"352":1,"353":2,"433":2,"434":3,"498":1,"512":1,"513":1,"618":1,"648":1,"650":1,"664":1,"665":1,"666":1,"674":1,"743":1,"853":1,"903":1,"905":8,"906":3,"907":1,"909":1,"1057":1,"1058":2,"1059":5,"1060":1,"1061":1,"1063":1,"1184":1,"1185":1,"1188":1,"1237":1,"1282":1,"1285":2,"1287":1,"1289":2,"1292":4,"1294":1,"1299":34,"1310":5,"1322":3,"1323":31,"1334":1,"1335":1,"1338":3,"1358":1,"1406":1,"1407":1,"1408":1,"1414":3,"1416":16,"1435":31,"1437":1,"1441":1,"1454":1,"1455":1,"1465":1,"1487":3,"1510":1,"1576":1,"1626":1,"1629":1,"1630":1,"1636":1,"1640":3,"1665":1,"1667":1,"1669":1,"1670":1,"1741":1,"1764":2,"1896":1,"1935":2,"1969":1,"1971":2,"1972":4,"1974":1,"1975":1,"1976":3,"1977":1,"1978":2,"1979":1,"1981":3,"1982":2,"1983":1,"1984":1,"2011":1,"2076":1,"2143":1,"2193":3,"2194":1,"2195":1,"2197":1,"2201":1,"2202":3,"2219":1,"2231":1}}],["signalling",{"3":{"1282":1,"1285":2}}],["signalled",{"3":{"743":1,"782":1,"1259":1,"1275":1,"1358":1,"1414":2,"1435":1,"1445":1,"1466":1,"1650":1,"1685":1,"2184":1,"2231":1}}],["signalingentry",{"3":{"1199":2,"1207":1}}],["signals",{"3":{"0":2,"18":1,"19":1,"396":1,"406":1,"608":1,"609":1,"767":1,"827":1,"1040":2,"1042":1,"1045":1,"1247":1,"1254":2,"1257":1,"1259":7,"1270":1,"1274":2,"1285":12,"1310":1,"1323":1,"1338":2,"1349":1,"1358":2,"1395":1,"1401":1,"1414":1,"1416":2,"1426":1,"1432":1,"1435":4,"1454":1,"1466":1,"1473":1,"1487":1,"1525":1,"1567":1,"1576":1,"2117":1,"2153":2,"2155":1,"2157":2,"2158":1,"2164":2}}],["signal",{"0":{"2157":1},"3":{"0":6,"17":1,"18":1,"19":1,"20":1,"22":1,"72":1,"77":1,"79":1,"80":1,"108":1,"125":1,"147":1,"159":1,"216":1,"237":1,"255":1,"343":1,"350":1,"376":1,"395":1,"399":1,"400":1,"402":1,"403":1,"406":1,"407":2,"518":1,"527":1,"530":1,"549":1,"585":1,"607":1,"612":2,"627":2,"628":1,"702":1,"726":1,"727":1,"735":1,"740":1,"741":1,"742":1,"758":1,"789":1,"830":1,"840":1,"844":1,"845":2,"905":2,"906":1,"914":1,"916":1,"948":1,"990":1,"1018":1,"1044":3,"1045":1,"1068":2,"1069":1,"1074":1,"1100":1,"1126":1,"1188":1,"1247":2,"1254":2,"1256":1,"1257":1,"1259":28,"1270":5,"1274":1,"1275":4,"1285":20,"1289":1,"1299":11,"1300":1,"1308":1,"1310":4,"1316":1,"1321":1,"1322":3,"1323":6,"1338":6,"1346":1,"1349":1,"1354":1,"1358":13,"1368":2,"1379":1,"1389":1,"1394":6,"1395":11,"1401":6,"1414":1,"1416":10,"1425":1,"1432":1,"1435":12,"1437":1,"1440":2,"1442":4,"1449":1,"1454":2,"1455":4,"1458":1,"1464":1,"1466":11,"1475":2,"1485":1,"1487":2,"1490":1,"1533":1,"1535":1,"1540":1,"1629":1,"1640":1,"1664":1,"1665":1,"1674":1,"1676":2,"1723":1,"1734":1,"1768":2,"1840":1,"1887":1,"1888":1,"1890":1,"1918":1,"1922":1,"1981":1,"1984":1,"1987":1,"2035":1,"2054":1,"2055":1,"2135":1,"2151":1,"2154":3,"2156":1,"2157":1,"2159":1,"2191":3,"2231":1}}],["signalrconfigured",{"2":{"1934":1},"3":{"1934":1}}],["signalrextensions",{"3":{"225":1,"749":1,"1150":1,"1199":1,"1203":1,"1207":2,"1208":1,"1308":1,"1310":7}}],["signalrlivechannelpublishertests",{"3":{"1199":1,"1207":2}}],["signalrlivechannelpublisher",{"2":{"225":1,"380":1,"1302":1,"1306":1,"1399":1,"1943":1,"1945":1},"3":{"225":2,"380":1,"1150":1,"1199":3,"1203":1,"1207":2,"1302":1,"1306":4,"1308":13,"1322":1,"1399":1,"1495":1,"1943":1,"1945":2}}],["signalrpushnotificationsendertests",{"3":{"1199":1,"1207":2}}],["signalrpushnotificationsenderadditionaltests",{"3":{"1199":1,"1207":2}}],["signalrpushnotificationsender",{"2":{"225":1,"1302":1,"1306":1,"1931":1,"1934":1},"3":{"225":2,"1199":4,"1203":1,"1207":2,"1299":3,"1302":1,"1306":2,"1308":16,"1322":1,"1931":1,"1934":2}}],["signalr",{"0":{"1306":1},"1":{"222":1,"223":1,"224":1,"225":1,"226":1,"227":1,"228":1,"229":1,"230":1,"231":1,"1941":1,"1942":1,"1943":1,"1944":1,"1945":1,"1946":1,"1947":1,"1948":1,"1949":1},"3":{"0":8,"25":1,"27":1,"31":1,"55":1,"61":1,"92":2,"96":1,"98":1,"100":1,"101":1,"135":1,"216":1,"222":1,"225":7,"226":1,"227":1,"228":1,"229":2,"230":2,"379":1,"380":2,"397":1,"433":1,"434":2,"507":1,"508":1,"513":1,"555":1,"556":1,"608":1,"749":1,"759":1,"774":3,"827":1,"832":1,"837":1,"838":2,"1124":1,"1149":1,"1150":1,"1192":1,"1202":2,"1203":2,"1212":1,"1213":2,"1247":1,"1284":1,"1285":4,"1295":1,"1299":8,"1301":4,"1302":2,"1303":1,"1306":1,"1307":1,"1308":42,"1310":4,"1311":1,"1319":3,"1322":8,"1323":19,"1330":1,"1337":1,"1338":5,"1396":1,"1399":1,"1401":10,"1416":4,"1432":2,"1435":10,"1436":1,"1437":5,"1440":3,"1446":4,"1466":9,"1473":1,"1487":1,"1488":1,"1495":1,"1511":2,"1598":1,"1655":1,"1657":1,"1665":1,"1666":1,"1668":1,"1704":1,"1778":1,"1931":1,"1932":6,"1934":5,"1935":2,"1939":2,"1940":2,"1941":3,"1943":1,"1944":1,"1945":1,"1949":3,"2023":1,"2024":1,"2109":1,"2149":2,"2151":1,"2157":1,"2207":1,"2231":1}}],["signatureverification",{"3":{"1769":1}}],["signatureverificationfailed",{"2":{"790":1},"3":{"790":1}}],["signaturemissing",{"3":{"790":1}}],["signatures",{"3":{"0":2,"31":1,"135":2,"265":1,"471":1,"676":1,"798":1,"799":1,"1115":1,"1299":2,"1300":1,"1323":1,"1348":1,"1394":1,"1395":2,"1415":1,"1430":1,"1435":3,"1454":1,"1488":1,"1495":1,"1584":1,"1670":1,"2082":1,"2231":1}}],["signature",{"3":{"0":4,"29":1,"31":1,"34":1,"108":1,"110":1,"135":5,"207":1,"208":1,"210":1,"372":1,"458":2,"498":1,"499":1,"509":1,"757":1,"790":7,"792":1,"793":2,"799":2,"803":1,"879":1,"903":1,"905":1,"907":1,"908":1,"1034":1,"1049":1,"1059":1,"1116":1,"1133":1,"1134":1,"1184":1,"1212":1,"1218":1,"1229":1,"1241":1,"1246":1,"1247":3,"1259":1,"1270":6,"1285":17,"1288":1,"1289":1,"1299":14,"1300":2,"1308":2,"1309":4,"1310":3,"1322":3,"1323":13,"1338":2,"1358":21,"1388":1,"1394":6,"1395":11,"1414":4,"1416":5,"1430":1,"1435":13,"1437":1,"1440":1,"1488":1,"1533":1,"1584":1,"1684":1,"1701":2,"1733":1,"1748":1,"1764":1,"1769":2,"1802":2,"1803":1,"1806":2,"1809":2,"1896":1,"1952":1,"1953":1,"1967":2,"1971":1,"1979":1,"1984":1,"1994":1,"2065":1,"2125":2,"2165":5,"2167":1,"2168":2,"2196":1,"2231":2}}],["sizebytes",{"3":{"1349":5,"1368":1,"1394":2}}],["size=10",{"3":{"1270":1}}],["sizes",{"3":{"237":1,"312":1,"359":1,"1018":1,"1285":2,"1299":1,"1308":1,"1323":2,"1358":1,"1368":1,"1395":2,"1397":1,"1414":1,"1435":1,"1466":1,"1558":2,"1660":1,"1677":1,"1710":1,"1747":1,"1776":1,"1824":1,"2141":1}}],["sized",{"3":{"236":1,"719":1,"996":1,"1020":1,"1150":1,"1290":1,"1299":2,"1310":2,"1323":1,"1349":4,"1358":2,"1368":3,"1394":3,"1395":2,"1401":2,"1414":1,"1435":1,"1437":1,"1466":5,"1477":1,"1485":1,"1522":1,"1524":1,"1525":3,"1548":1,"1553":1,"1554":1,"1559":1,"1571":2,"1590":2,"1669":1,"1908":1,"2032":1,"2036":1}}],["size",{"0":{"1198":1},"2":{"1985":1},"3":{"137":1,"140":1,"311":1,"330":3,"331":1,"335":2,"337":1,"360":2,"434":1,"438":1,"558":2,"609":1,"673":1,"692":1,"740":1,"741":2,"766":1,"788":1,"872":1,"881":1,"915":1,"1116":2,"1118":1,"1187":1,"1198":1,"1201":1,"1228":1,"1229":2,"1242":1,"1243":2,"1244":2,"1245":1,"1247":13,"1259":1,"1285":7,"1299":4,"1308":4,"1310":4,"1319":1,"1322":11,"1323":18,"1343":1,"1349":11,"1358":11,"1379":1,"1388":2,"1394":26,"1395":16,"1397":1,"1401":1,"1405":1,"1414":9,"1416":1,"1421":2,"1435":1,"1437":2,"1466":1,"1478":2,"1495":1,"1525":3,"1528":1,"1559":1,"1576":1,"1588":1,"1595":1,"1598":2,"1630":3,"1638":1,"1639":2,"1661":1,"1668":1,"1672":1,"1677":2,"1713":2,"1763":1,"1764":1,"1776":3,"1796":1,"1816":1,"1844":1,"1910":1,"1985":2,"1986":1,"1987":2,"1990":1,"2049":1,"2071":1,"2125":2,"2127":2,"2141":1,"2142":2,"2158":1,"2166":3,"2173":1}}],["sizing",{"3":{"0":1,"682":1,"867":1,"875":1,"1299":1,"1308":1,"1323":1,"1349":1,"1368":2,"1389":1,"1395":3,"1454":1,"1464":1,"1466":3,"1468":2,"1525":1,"1533":1,"1567":1,"1573":1,"1574":3,"1576":2,"1584":1,"1601":1,"1674":1,"1675":1,"1798":1}}],["simulated",{"3":{"1435":3,"1670":1,"1706":2}}],["simulate",{"3":{"1323":1,"1416":1,"1435":1,"1706":1}}],["simultaneous",{"3":{"0":1,"159":1,"856":1,"1124":1,"1299":1,"1310":2,"1322":1,"1323":2,"1338":1,"1356":1,"1358":2,"1395":1,"1415":1,"1666":1,"1981":1,"1999":1,"2168":1}}],["simultaneously",{"3":{"0":1,"40":1,"501":1,"690":1,"907":1,"1322":2,"1338":2,"1379":1,"1395":4,"1435":2,"1442":2,"1637":1,"1669":1}}],["similarityscore",{"3":{"1349":1}}],["similarity",{"3":{"1349":4,"1358":7,"1375":2,"1379":1,"1389":1,"1394":2,"1631":1,"1639":1}}],["similarly",{"3":{"1349":1,"1637":1,"1638":1}}],["similarsessionpair",{"3":{"1199":4,"1203":1,"1207":1,"1346":1,"1349":2,"1358":2}}],["similar",{"3":{"158":1,"725":1,"827":1,"973":1,"1229":1,"1349":5,"1368":1,"1372":1,"1414":1,"1466":1,"1639":1,"1833":1}}],["simplify",{"3":{"1630":1}}],["simplifying",{"3":{"1394":1,"1640":1}}],["simplification",{"3":{"1338":1,"1401":1,"1416":1}}],["simplifies",{"3":{"310":1,"1416":1,"1637":2}}],["simplename",{"3":{"1435":3}}],["simpletypename",{"3":{"1435":1}}],["simplest",{"3":{"1285":2,"1322":1,"1352":1,"1358":5,"1379":1,"1382":1,"1394":2,"1395":1,"1435":5,"1444":1,"1616":1,"1680":1}}],["simple",{"3":{"136":1,"160":1,"161":1,"196":1,"341":1,"372":1,"699":1,"1074":1,"1117":1,"1196":2,"1247":1,"1270":1,"1285":1,"1308":1,"1310":3,"1322":6,"1323":4,"1338":1,"1340":1,"1356":1,"1358":3,"1368":1,"1379":1,"1394":4,"1395":7,"1401":1,"1414":4,"1415":1,"1435":8,"1637":2,"1640":1,"1852":1,"1874":1,"1957":1,"2078":1,"2237":1}}],["simpler",{"3":{"48":1,"811":1,"871":1,"1034":1,"1322":1,"1323":4,"1349":2,"1358":1,"1394":1,"1395":2,"1435":1,"1649":1,"1657":1,"1702":1,"1704":1,"1847":1,"2062":1}}],["simply",{"3":{"22":1,"24":1,"79":1,"117":1,"135":1,"137":1,"213":1,"224":1,"225":1,"235":1,"295":1,"342":1,"343":1,"399":1,"415":1,"464":1,"539":1,"585":1,"599":1,"676":1,"681":1,"709":1,"717":1,"751":1,"840":1,"907":1,"933":1,"963":1,"1042":1,"1059":1,"1084":1,"1232":1,"1234":1,"1237":2,"1239":1,"1241":1,"1243":1,"1247":5,"1251":1,"1254":1,"1256":1,"1259":5,"1270":8,"1271":1,"1276":1,"1285":12,"1296":1,"1299":10,"1300":4,"1301":1,"1308":8,"1309":1,"1310":13,"1315":1,"1322":7,"1323":15,"1338":6,"1342":1,"1349":6,"1352":1,"1358":19,"1368":1,"1374":1,"1379":3,"1388":1,"1394":15,"1395":18,"1400":1,"1401":4,"1414":8,"1415":5,"1416":22,"1426":1,"1430":1,"1435":18,"1437":2,"1454":1,"1466":2,"1487":1,"1639":1,"1640":1,"1809":1,"1840":1,"1850":1,"1862":1,"1869":1,"1879":1,"1884":1,"1888":1,"1915":1,"1945":1,"1947":1,"1976":1,"2000":1,"2005":1,"2009":1,"2065":1,"2091":1,"2096":1,"2116":1,"2138":1,"2155":1,"2187":1,"2195":1}}],["sid",{"2":{"903":1,"906":1,"909":1},"3":{"0":4,"903":1,"905":5,"906":2,"907":3,"909":1,"1059":1,"1289":3,"1295":2,"1299":17,"1310":3,"1323":1,"1437":2,"1666":1}}],["sideways",{"3":{"1435":2,"2127":1}}],["sidekick",{"3":{"1312":1}}],["sided",{"3":{"1299":1,"1338":1,"1368":1,"1435":1}}],["sidebar",{"3":{"881":1,"1323":7,"1408":1,"1413":2,"1414":3,"1437":1,"1711":1,"1712":1,"1749":1,"1772":1}}],["sides",{"3":{"122":1,"180":1,"243":1,"286":1,"556":1,"631":1,"640":1,"720":1,"827":1,"975":1,"1050":1,"1212":1,"1236":1,"1237":3,"1241":1,"1247":2,"1270":4,"1271":1,"1276":1,"1277":1,"1285":5,"1299":3,"1308":2,"1310":2,"1311":1,"1322":1,"1323":3,"1338":1,"1349":1,"1358":5,"1379":1,"1395":3,"1414":2,"1435":5,"1487":1,"1576":1,"1640":1,"1650":1,"1838":1,"1978":1,"2043":1,"2095":1,"2135":1,"2156":2}}],["sidesteps",{"3":{"92":1,"1270":1,"1285":2,"1299":1,"1322":1,"1358":1,"1395":2,"1401":1,"1414":1,"1435":1,"1466":1,"1871":1}}],["side",{"0":{"141":1,"926":1,"1227":1,"1265":1,"1305":1,"1633":1,"1840":1,"2017":1,"2018":1},"1":{"192":1,"193":1,"194":1,"195":1,"196":1,"197":1,"198":1,"199":1,"200":1,"201":1,"202":1,"203":1,"504":1,"505":1,"506":1,"507":1,"508":1,"509":1,"510":1,"511":1,"512":1,"513":1,"514":1,"878":1,"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"894":1,"895":1,"896":1,"897":1,"898":1,"899":1,"900":1,"901":1,"919":1,"920":1,"921":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1},"3":{"0":27,"15":1,"21":1,"24":3,"31":2,"39":1,"53":2,"66":1,"72":1,"73":2,"99":1,"100":1,"103":1,"109":4,"113":2,"117":1,"121":1,"122":3,"124":1,"126":1,"128":1,"135":1,"139":2,"141":2,"145":2,"147":2,"150":3,"166":2,"168":2,"175":1,"180":1,"192":1,"193":1,"194":1,"199":1,"201":1,"207":1,"208":1,"236":1,"239":1,"245":1,"248":1,"255":2,"258":1,"264":1,"265":3,"267":1,"273":2,"295":1,"298":1,"330":2,"335":1,"337":1,"342":1,"343":1,"346":1,"350":3,"351":1,"353":3,"360":1,"363":1,"379":1,"387":1,"388":1,"389":1,"391":1,"399":1,"400":1,"401":3,"408":1,"415":1,"418":1,"419":1,"420":1,"433":1,"434":1,"436":1,"451":1,"470":1,"490":1,"499":1,"501":2,"504":1,"507":3,"508":1,"509":1,"518":1,"536":1,"537":1,"545":1,"549":1,"551":1,"557":1,"560":1,"591":2,"599":2,"601":1,"612":1,"629":1,"633":2,"635":1,"640":1,"645":1,"648":1,"649":2,"650":2,"653":1,"657":1,"665":2,"668":2,"674":1,"684":1,"690":3,"692":3,"693":2,"696":1,"697":1,"698":2,"701":1,"702":2,"725":1,"741":1,"756":1,"758":1,"770":1,"785":1,"786":1,"790":2,"795":1,"818":2,"827":6,"829":2,"830":1,"833":1,"836":2,"839":2,"844":1,"845":1,"846":1,"848":2,"860":3,"861":1,"865":3,"870":1,"878":1,"880":1,"881":6,"883":2,"885":2,"894":1,"897":7,"898":2,"899":1,"901":1,"904":1,"905":1,"919":1,"920":1,"921":1,"922":2,"923":1,"924":1,"925":1,"926":2,"930":1,"935":1,"984":1,"995":1,"1012":3,"1017":1,"1018":3,"1020":2,"1021":2,"1028":1,"1041":1,"1044":2,"1066":1,"1067":1,"1068":1,"1085":1,"1093":1,"1094":2,"1100":1,"1115":1,"1118":1,"1126":1,"1160":1,"1182":1,"1184":4,"1185":1,"1187":2,"1202":1,"1203":1,"1212":14,"1219":1,"1220":2,"1222":2,"1225":2,"1227":2,"1228":1,"1229":9,"1231":1,"1233":1,"1237":11,"1238":2,"1240":1,"1242":2,"1243":2,"1245":2,"1247":28,"1248":1,"1249":2,"1252":3,"1253":1,"1258":3,"1259":24,"1260":4,"1261":1,"1262":1,"1263":6,"1265":2,"1268":1,"1269":3,"1270":71,"1271":7,"1274":1,"1278":3,"1281":1,"1283":3,"1284":3,"1285":42,"1286":1,"1291":1,"1292":1,"1297":1,"1299":55,"1300":7,"1303":1,"1304":2,"1305":2,"1307":2,"1308":30,"1309":6,"1310":37,"1311":20,"1312":1,"1316":2,"1317":4,"1321":1,"1322":36,"1323":127,"1324":6,"1325":1,"1329":2,"1336":3,"1338":33,"1341":2,"1343":1,"1348":1,"1349":47,"1351":1,"1352":1,"1354":4,"1356":2,"1358":134,"1361":1,"1365":2,"1367":2,"1368":12,"1369":1,"1373":2,"1374":1,"1377":1,"1379":20,"1382":2,"1384":1,"1386":1,"1387":3,"1388":1,"1389":1,"1390":2,"1391":4,"1392":1,"1394":87,"1395":124,"1398":2,"1399":1,"1400":1,"1401":35,"1404":1,"1405":1,"1412":1,"1413":1,"1414":48,"1415":19,"1416":35,"1421":1,"1423":2,"1426":6,"1428":1,"1429":1,"1432":2,"1434":5,"1435":84,"1436":1,"1437":14,"1440":4,"1442":3,"1443":1,"1444":1,"1446":5,"1448":1,"1450":1,"1454":3,"1455":1,"1460":2,"1466":7,"1474":1,"1475":3,"1486":1,"1487":4,"1488":1,"1491":3,"1493":1,"1495":7,"1525":13,"1530":1,"1531":1,"1533":4,"1534":2,"1542":1,"1544":1,"1546":2,"1552":1,"1559":3,"1560":2,"1562":1,"1565":1,"1571":4,"1574":3,"1575":1,"1576":9,"1581":2,"1584":2,"1585":1,"1588":1,"1590":5,"1595":4,"1596":1,"1597":1,"1598":3,"1626":5,"1629":1,"1630":2,"1631":8,"1633":1,"1634":1,"1637":1,"1638":3,"1639":2,"1640":4,"1646":1,"1650":4,"1652":1,"1660":2,"1664":3,"1665":3,"1666":3,"1667":3,"1668":2,"1669":2,"1674":2,"1676":1,"1684":1,"1686":1,"1701":1,"1719":1,"1720":1,"1725":2,"1747":1,"1748":1,"1749":2,"1756":1,"1758":2,"1760":1,"1764":1,"1766":2,"1772":1,"1780":1,"1799":1,"1804":5,"1807":1,"1809":1,"1813":1,"1814":2,"1819":1,"1820":2,"1842":2,"1844":1,"1852":1,"1853":1,"1855":2,"1882":4,"1901":2,"1902":1,"1905":1,"1909":2,"1910":2,"1911":1,"1912":1,"1915":2,"1918":3,"1919":1,"1923":1,"1926":2,"1927":1,"1928":1,"1934":1,"1935":1,"1941":1,"1943":1,"1944":1,"1945":1,"1951":2,"1952":2,"1955":4,"1956":2,"1958":1,"1965":2,"1967":3,"1969":1,"1971":2,"1972":1,"1974":2,"1976":1,"1978":2,"1981":2,"1982":1,"1983":1,"1984":2,"1987":2,"2001":1,"2003":2,"2005":3,"2017":1,"2018":1,"2027":2,"2029":3,"2035":1,"2055":1,"2063":1,"2069":1,"2071":2,"2072":1,"2073":1,"2076":2,"2077":2,"2078":1,"2082":1,"2084":1,"2085":1,"2126":1,"2135":1,"2137":1,"2140":1,"2155":2,"2158":2,"2161":1,"2164":3,"2165":1,"2166":1,"2170":1,"2173":1,"2178":1,"2181":1,"2186":1,"2188":1,"2191":2,"2192":1,"2197":1,"2229":1,"2231":5}}],["siblings",{"3":{"509":1,"511":1,"703":1,"773":1,"914":1,"980":1,"1212":1,"1234":1,"1247":2,"1258":1,"1280":1,"1281":1,"1285":3,"1299":2,"1300":1,"1310":2,"1323":5,"1325":2,"1329":1,"1335":1,"1338":5,"1349":10,"1352":3,"1358":28,"1368":1,"1370":1,"1379":1,"1381":1,"1394":11,"1395":9,"1400":1,"1401":4,"1403":1,"1410":1,"1414":4,"1415":1,"1416":30,"1430":2,"1435":11,"1440":3,"1466":1,"1488":1,"1495":1,"1576":1,"1590":2,"2164":1}}],["sibling",{"0":{"1947":1},"3":{"0":4,"194":1,"247":1,"255":1,"256":1,"258":1,"265":4,"291":1,"406":1,"443":1,"538":1,"568":1,"614":1,"630":1,"645":1,"657":1,"694":1,"711":1,"725":1,"741":1,"750":1,"753":1,"765":1,"770":1,"782":1,"852":1,"854":2,"865":1,"881":1,"905":1,"922":1,"926":1,"935":1,"963":1,"973":1,"975":2,"976":2,"1120":1,"1133":1,"1136":1,"1184":1,"1193":1,"1212":2,"1223":1,"1228":1,"1229":2,"1237":5,"1247":1,"1252":2,"1256":1,"1259":6,"1260":1,"1267":1,"1270":11,"1271":2,"1278":1,"1281":1,"1284":1,"1285":17,"1294":1,"1295":1,"1299":8,"1308":4,"1309":4,"1310":17,"1315":1,"1322":9,"1323":25,"1338":13,"1342":1,"1343":1,"1349":13,"1351":1,"1352":1,"1355":1,"1358":55,"1368":4,"1379":13,"1394":12,"1395":18,"1401":18,"1406":2,"1414":18,"1415":3,"1416":8,"1426":1,"1430":1,"1435":49,"1437":2,"1455":1,"1474":1,"1485":1,"1488":1,"1490":1,"1491":1,"1495":8,"1496":2,"1525":2,"1576":2,"1592":1,"1652":2,"1653":1,"1748":1,"1949":1,"2042":2,"2066":1,"2084":1,"2091":1,"2150":2,"2182":1}}],["situations",{"3":{"1416":1,"1981":1,"2166":1}}],["situation",{"3":{"1323":1,"1379":1,"1395":2,"1414":1,"1435":1,"1907":1}}],["sitting",{"3":{"178":1,"198":1,"235":1,"257":1,"486":1,"536":1,"545":1,"572":1,"826":1,"1004":1,"1050":1,"1117":1,"1259":1,"1270":1,"1299":3,"1301":1,"1322":2,"1323":4,"1338":1,"1358":1,"1379":3,"1394":1,"1395":3,"1414":1,"1415":2,"1416":1,"1435":6,"1442":2,"1462":1,"1464":1,"1466":2,"1748":1,"1764":1,"1842":1,"1901":1,"1902":1,"1965":1,"1972":1,"1979":1,"2053":1,"2061":1,"2064":1,"2066":1,"2074":1,"2148":1,"2169":1}}],["sitemap",{"3":{"609":1,"1299":1,"1466":3,"2157":3}}],["sites",{"3":{"0":9,"93":1,"94":1,"95":1,"104":1,"111":1,"128":3,"223":1,"230":2,"254":1,"257":1,"260":1,"261":1,"281":1,"310":1,"314":1,"323":1,"326":1,"330":1,"418":1,"429":1,"453":1,"454":1,"483":2,"484":2,"486":2,"487":2,"488":2,"489":2,"490":2,"491":2,"492":2,"493":2,"494":3,"516":1,"522":3,"523":2,"545":2,"549":1,"550":2,"552":3,"647":1,"668":1,"669":1,"688":1,"693":1,"696":4,"698":1,"700":1,"703":1,"752":1,"782":1,"819":1,"837":1,"884":1,"897":2,"899":1,"900":2,"994":2,"996":1,"999":2,"1036":1,"1100":1,"1127":1,"1132":1,"1134":1,"1175":2,"1176":1,"1237":3,"1240":1,"1247":5,"1250":1,"1262":1,"1267":1,"1270":2,"1271":4,"1276":1,"1285":19,"1299":19,"1300":6,"1309":1,"1310":5,"1317":2,"1322":5,"1323":12,"1338":1,"1349":7,"1358":8,"1368":1,"1386":1,"1394":4,"1395":13,"1401":1,"1414":1,"1416":4,"1426":1,"1435":20,"1437":1,"1441":1,"1495":1,"1534":1,"1809":1,"1814":1,"1850":1,"1881":1,"1994":3,"2062":1,"2155":1,"2156":1}}],["site",{"3":{"0":8,"67":1,"109":3,"111":1,"113":1,"118":1,"121":1,"184":1,"207":2,"209":1,"217":1,"230":2,"279":2,"285":1,"303":1,"308":1,"310":3,"318":2,"325":1,"336":1,"353":1,"386":1,"387":1,"395":3,"397":1,"419":1,"420":1,"424":1,"446":3,"472":1,"480":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"495":1,"516":1,"517":2,"523":1,"524":2,"545":3,"549":1,"650":1,"655":1,"657":2,"663":1,"665":2,"667":1,"669":1,"674":2,"696":1,"700":1,"733":1,"777":1,"782":1,"799":2,"800":1,"803":1,"819":2,"827":2,"832":1,"838":1,"846":1,"861":2,"889":1,"896":1,"900":1,"926":1,"934":1,"946":1,"948":1,"950":1,"952":1,"955":1,"964":2,"980":1,"981":1,"990":1,"1050":1,"1051":1,"1052":1,"1100":1,"1102":1,"1116":1,"1123":1,"1124":1,"1126":1,"1133":2,"1136":1,"1176":1,"1179":1,"1184":1,"1187":1,"1212":1,"1221":1,"1229":7,"1236":1,"1237":7,"1241":1,"1246":1,"1247":10,"1259":5,"1270":10,"1271":5,"1285":34,"1296":1,"1297":2,"1299":24,"1300":2,"1308":8,"1309":5,"1310":14,"1311":3,"1322":13,"1323":32,"1331":1,"1338":11,"1343":1,"1349":9,"1358":30,"1365":1,"1368":4,"1391":1,"1392":1,"1394":15,"1395":28,"1401":2,"1414":11,"1415":9,"1416":12,"1426":4,"1435":42,"1437":2,"1443":1,"1446":2,"1447":1,"1466":1,"1533":2,"1565":1,"1619":1,"1658":1,"1665":1,"1666":1,"1672":1,"1674":1,"1700":1,"1755":1,"1792":1,"1801":1,"1805":1,"1806":1,"1809":1,"1812":1,"1814":2,"1816":1,"1820":1,"1825":1,"1826":2,"1842":1,"1845":1,"1852":1,"1867":1,"1876":1,"1885":2,"1891":2,"1898":1,"1904":1,"1914":2,"1918":1,"1929":1,"1938":1,"1949":1,"1964":1,"1968":5,"1970":1,"1971":2,"1974":1,"1978":1,"1984":1,"1990":1,"1996":1,"1999":1,"2001":1,"2011":1,"2023":1,"2029":2,"2062":1,"2068":1,"2085":1,"2107":1,"2114":1,"2120":1,"2123":1,"2127":1,"2132":1,"2148":1,"2149":1,"2152":1,"2159":1,"2168":1,"2174":1,"2178":1,"2179":1,"2188":1,"2192":1,"2202":1,"2210":1,"2232":1}}],["sit",{"0":{"1302":1},"3":{"0":6,"25":1,"27":1,"64":1,"71":1,"80":1,"95":1,"147":1,"180":1,"182":1,"201":1,"242":1,"281":1,"287":1,"344":1,"423":1,"455":1,"459":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":2,"494":1,"495":2,"509":1,"511":1,"566":1,"572":1,"599":1,"609":1,"626":1,"627":1,"632":1,"633":1,"649":1,"674":2,"676":2,"751":1,"766":1,"798":1,"814":1,"815":1,"823":1,"832":1,"834":1,"862":1,"926":1,"954":1,"968":1,"980":1,"982":1,"992":1,"1051":1,"1055":1,"1067":1,"1117":1,"1144":1,"1168":1,"1192":1,"1211":1,"1212":2,"1213":1,"1220":1,"1229":1,"1230":1,"1237":1,"1249":1,"1258":1,"1259":1,"1260":1,"1269":1,"1270":3,"1271":3,"1275":1,"1278":1,"1283":1,"1284":1,"1285":6,"1299":3,"1310":7,"1315":1,"1316":2,"1321":1,"1322":4,"1323":11,"1329":1,"1331":1,"1338":8,"1346":1,"1349":4,"1353":1,"1358":16,"1370":1,"1371":1,"1377":1,"1378":1,"1379":2,"1388":1,"1390":1,"1394":5,"1395":9,"1401":2,"1402":1,"1413":1,"1414":6,"1415":1,"1416":3,"1420":2,"1430":1,"1435":19,"1437":2,"1440":1,"1444":1,"1446":2,"1452":1,"1458":1,"1466":2,"1488":3,"1489":2,"1495":2,"1497":2,"1522":2,"1530":1,"1550":1,"1573":3,"1576":10,"1580":1,"1581":3,"1582":1,"1583":1,"1590":6,"1595":2,"1596":1,"1598":2,"1626":2,"1629":1,"1639":1,"1766":2,"1780":2,"1783":1,"1809":1,"1850":1,"1854":1,"1855":1,"1863":1,"1882":1,"1897":1,"1903":1,"1926":1,"1955":1,"1974":1,"1999":1,"2009":1,"2052":1,"2110":1,"2171":1,"2195":1,"2197":1,"2202":1,"2207":1,"2231":1}}],["sits",{"0":{"1237":1,"1285":1,"1308":1},"3":{"0":5,"109":1,"122":3,"123":1,"135":1,"141":1,"149":1,"150":1,"152":1,"162":1,"178":1,"182":1,"218":1,"230":1,"237":1,"255":2,"259":1,"279":1,"287":1,"291":1,"295":1,"310":1,"317":1,"346":1,"349":1,"355":1,"366":1,"373":1,"380":1,"395":1,"407":1,"411":1,"416":1,"423":2,"427":1,"428":1,"457":1,"459":1,"462":1,"464":1,"491":1,"492":3,"493":3,"494":1,"495":1,"524":1,"536":1,"538":1,"544":1,"560":1,"564":1,"567":1,"575":1,"577":1,"591":1,"609":2,"619":1,"631":1,"633":2,"638":1,"665":2,"682":1,"692":1,"701":1,"706":1,"724":1,"736":1,"748":1,"749":3,"766":1,"768":1,"784":1,"803":1,"821":1,"823":1,"827":1,"856":1,"897":1,"905":1,"914":1,"953":1,"957":1,"963":2,"966":1,"967":1,"979":2,"980":1,"1004":1,"1014":1,"1018":1,"1034":1,"1052":1,"1054":1,"1059":1,"1066":1,"1077":1,"1093":1,"1124":1,"1125":1,"1137":1,"1167":1,"1212":3,"1229":1,"1230":1,"1233":1,"1237":2,"1247":1,"1259":31,"1263":7,"1265":1,"1270":15,"1271":4,"1278":1,"1284":2,"1285":10,"1296":1,"1298":1,"1299":9,"1300":2,"1303":1,"1308":3,"1309":1,"1310":9,"1315":3,"1322":21,"1323":14,"1327":1,"1329":1,"1335":1,"1336":1,"1338":7,"1342":1,"1346":1,"1347":2,"1349":4,"1350":1,"1358":12,"1368":2,"1370":1,"1371":1,"1372":1,"1374":1,"1379":1,"1387":1,"1392":1,"1394":10,"1395":23,"1401":3,"1403":1,"1409":1,"1414":5,"1415":4,"1416":7,"1420":1,"1424":1,"1428":1,"1432":1,"1434":1,"1435":15,"1437":1,"1442":1,"1446":1,"1452":1,"1454":1,"1455":1,"1459":1,"1466":3,"1472":1,"1473":1,"1474":2,"1478":1,"1485":1,"1489":1,"1491":4,"1502":1,"1503":2,"1506":1,"1522":1,"1523":2,"1525":2,"1526":1,"1533":2,"1543":1,"1553":1,"1573":1,"1576":2,"1581":2,"1587":1,"1590":3,"1591":1,"1630":2,"1647":1,"1652":1,"1661":1,"1694":1,"1748":1,"1756":1,"1757":1,"1765":1,"1783":1,"1798":1,"1799":1,"1803":1,"1814":1,"1820":7,"1824":1,"1827":1,"1830":1,"1838":1,"1855":1,"1880":1,"1888":1,"1889":1,"1901":2,"1915":3,"1917":1,"1920":1,"1921":3,"1928":1,"1937":1,"1943":1,"1946":1,"1952":1,"1971":1,"1987":1,"1999":3,"2006":1,"2031":1,"2045":1,"2047":1,"2049":1,"2063":1,"2077":1,"2090":1,"2095":2,"2099":1,"2125":2,"2135":1,"2148":1,"2155":1,"2157":1,"2160":1,"2164":1,"2167":1,"2170":1,"2176":1,"2178":1,"2194":1,"2198":1,"2231":1}}],["sixlaborslicensekey",{"3":{"1435":1}}],["sixlabors",{"2":{"145":1,"147":1},"3":{"145":2,"147":1,"1322":1,"1430":1,"1435":3,"1488":1}}],["sixty",{"3":{"1358":1,"1394":1,"1414":1,"1984":1}}],["sixteen",{"3":{"130":1,"138":2,"139":1,"141":2,"468":1,"554":1,"672":1,"1217":1,"1229":1,"1263":1,"1285":1,"1323":2,"1338":1,"1358":1,"1368":2,"1416":1,"1435":1,"1437":1,"1454":1,"1485":1,"1495":1}}],["sixth",{"3":{"19":1,"142":1,"258":1,"259":1,"261":1,"350":1,"688":1,"761":1,"776":1,"806":1,"1004":1,"1270":1,"1358":2,"1414":2,"1427":1,"1430":1,"1432":1,"1466":1,"1487":1,"1976":1}}],["six",{"0":{"140":1,"1444":1},"1":{"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1},"3":{"0":19,"62":1,"63":1,"115":1,"122":1,"126":1,"130":1,"136":1,"140":1,"142":1,"145":2,"147":1,"184":1,"186":1,"251":1,"252":1,"335":1,"413":1,"429":1,"440":1,"466":2,"468":1,"470":1,"476":2,"478":1,"526":1,"531":1,"545":1,"554":1,"572":3,"589":1,"593":1,"599":1,"603":1,"607":5,"609":3,"625":1,"632":1,"657":3,"658":1,"660":1,"672":1,"674":2,"696":3,"707":2,"757":2,"760":1,"798":1,"800":2,"804":2,"832":1,"867":1,"868":2,"869":1,"875":1,"881":1,"897":2,"905":1,"936":1,"938":4,"939":2,"940":4,"946":1,"954":2,"955":1,"980":1,"981":1,"982":2,"986":1,"991":1,"998":1,"1004":1,"1017":1,"1018":3,"1019":2,"1020":1,"1033":1,"1048":1,"1051":1,"1060":1,"1068":1,"1116":1,"1191":1,"1211":2,"1212":1,"1234":1,"1237":4,"1244":2,"1247":9,"1259":1,"1260":2,"1268":1,"1270":4,"1271":15,"1285":7,"1292":1,"1299":7,"1300":2,"1308":4,"1310":2,"1311":2,"1321":1,"1322":6,"1323":15,"1338":5,"1341":1,"1342":1,"1345":1,"1349":11,"1358":53,"1361":1,"1363":1,"1365":2,"1368":12,"1374":1,"1376":1,"1379":2,"1382":2,"1394":16,"1395":16,"1401":4,"1414":5,"1415":1,"1416":1,"1432":1,"1435":79,"1438":1,"1440":2,"1441":1,"1444":9,"1448":1,"1449":5,"1454":10,"1460":1,"1463":1,"1464":5,"1465":1,"1466":26,"1468":2,"1471":1,"1473":3,"1485":1,"1487":2,"1488":3,"1491":1,"1495":10,"1496":1,"1502":1,"1524":2,"1525":4,"1534":2,"1576":2,"1590":1,"1601":1,"1629":1,"1630":1,"1650":1,"1659":1,"1660":2,"1661":1,"1665":1,"1673":1,"1683":1,"1685":2,"1691":1,"1692":3,"1716":1,"1724":1,"1725":1,"1726":2,"1727":2,"1780":1,"1793":1,"1800":1,"1808":1,"1809":3,"1825":1,"1828":2,"1880":1,"1901":1,"1912":1,"1947":2,"1950":1,"1957":1,"1986":1,"2033":1,"2036":2,"2037":1,"2043":1,"2054":1,"2074":1,"2109":1,"2126":1,"2127":1,"2137":1,"2146":1,"2157":1,"2166":1,"2192":1,"2197":1,"2199":1,"2225":1,"2226":2,"2231":2,"2233":1}}],["sin",{"3":{"2046":1}}],["singular",{"3":{"872":1,"1323":1,"1358":3,"1576":1,"1694":1,"1726":3,"1727":3,"1728":1,"2088":1}}],["singly",{"3":{"386":1,"1401":1}}],["singles",{"3":{"1495":1}}],["singleserviceprovider",{"3":{"1199":2,"1207":1}}],["singleframesettlescript",{"3":{"1435":1}}],["singletop",{"3":{"1415":1}}],["singletons",{"3":{"522":1,"536":1,"651":1,"884":1,"1100":1,"1219":1,"1229":4,"1237":2,"1252":1,"1259":2,"1262":1,"1270":1,"1299":1,"1315":4,"1322":3,"1376":1,"1379":1,"1395":2,"1398":1,"1414":1,"1416":4,"1435":1,"1437":1,"1554":1,"1882":1,"2162":2}}],["singleton",{"3":{"0":6,"200":1,"201":1,"235":1,"303":1,"310":2,"413":1,"511":1,"518":2,"649":1,"698":1,"699":1,"700":1,"703":1,"790":1,"831":1,"832":1,"846":1,"850":1,"881":3,"946":1,"954":1,"980":1,"1050":1,"1089":1,"1091":2,"1100":3,"1124":2,"1133":2,"1212":2,"1229":1,"1237":2,"1247":1,"1253":1,"1259":7,"1262":1,"1267":1,"1269":1,"1270":3,"1277":2,"1278":1,"1279":2,"1282":1,"1285":46,"1299":19,"1300":2,"1308":2,"1309":1,"1310":27,"1314":1,"1315":2,"1319":1,"1322":22,"1323":19,"1331":2,"1338":12,"1349":3,"1354":2,"1358":9,"1367":1,"1368":2,"1394":4,"1395":18,"1401":2,"1412":1,"1414":5,"1415":6,"1416":64,"1423":1,"1426":10,"1435":18,"1437":2,"1442":2,"1487":1,"1525":1,"1538":1,"1555":1,"1576":1,"1590":1,"1668":1,"1749":1,"1772":1,"1809":1,"1848":3,"1850":1,"1880":1,"1919":1,"1946":1,"1999":1,"2094":1,"2117":1,"2120":2,"2165":1,"2182":1}}],["singlewriter",{"3":{"1395":1}}],["singleordefaultasync",{"3":{"1285":1}}],["singlecontextfactory",{"3":{"1199":2,"1207":1}}],["singlereader",{"2":{"1946":1,"2182":1},"3":{"0":2,"518":1,"1100":1,"1212":2,"1395":3,"1399":1,"1946":1,"2182":1}}],["single",{"0":{"94":1,"170":1,"1293":1,"2043":1},"1":{"83":1,"84":1,"85":1,"86":1,"87":1,"88":1,"496":1,"497":1,"498":1,"499":1,"500":1,"501":1,"502":1,"503":1,"1853":1,"1854":1,"1855":1,"1856":1,"1857":1,"1858":1,"1859":1,"1860":1,"1861":1,"2080":1,"2081":1,"2082":1,"2083":1,"2084":1,"2085":1},"3":{"0":39,"11":1,"15":1,"23":1,"24":1,"26":1,"30":1,"31":2,"32":1,"45":1,"46":2,"47":1,"48":1,"57":1,"58":1,"59":2,"60":1,"62":3,"67":1,"68":3,"69":1,"73":1,"83":1,"85":2,"86":2,"87":1,"91":1,"92":1,"99":1,"100":1,"115":1,"117":1,"121":1,"125":2,"128":1,"132":1,"135":2,"145":1,"147":1,"149":1,"157":3,"159":1,"160":1,"166":3,"174":1,"175":1,"176":2,"179":1,"180":1,"186":1,"194":1,"195":1,"196":2,"201":1,"202":1,"214":1,"216":1,"217":1,"220":1,"230":1,"233":1,"234":2,"235":3,"242":2,"243":2,"245":1,"246":1,"248":1,"249":1,"255":1,"259":1,"263":1,"264":1,"265":2,"266":1,"273":2,"281":1,"290":1,"310":2,"311":2,"312":1,"317":1,"318":3,"319":1,"320":1,"333":1,"340":1,"341":1,"350":3,"351":1,"352":1,"359":5,"360":1,"361":1,"365":1,"369":1,"370":2,"380":1,"382":3,"386":1,"388":1,"389":1,"390":2,"396":1,"397":1,"398":1,"405":1,"407":1,"412":1,"413":1,"418":1,"419":1,"420":2,"421":1,"422":1,"427":1,"428":2,"429":1,"446":2,"447":1,"448":2,"449":1,"459":1,"469":1,"475":1,"476":1,"491":1,"492":2,"496":1,"497":1,"499":2,"501":1,"503":1,"505":1,"506":2,"507":3,"508":1,"509":1,"510":1,"511":1,"518":1,"519":1,"526":1,"528":1,"536":1,"538":1,"539":1,"540":1,"545":4,"546":1,"549":1,"556":2,"564":1,"573":1,"582":2,"583":3,"584":1,"589":1,"608":1,"609":1,"618":1,"626":2,"627":1,"629":1,"633":1,"638":1,"649":1,"657":1,"659":1,"664":1,"665":3,"666":1,"674":3,"676":1,"681":1,"688":1,"690":3,"691":3,"693":1,"698":3,"702":1,"707":1,"709":1,"714":1,"731":1,"733":1,"735":2,"736":1,"741":1,"748":1,"756":1,"757":2,"758":1,"759":1,"766":1,"775":1,"781":1,"782":2,"790":2,"792":1,"795":1,"799":2,"808":1,"810":3,"819":1,"820":1,"821":1,"825":1,"826":1,"827":5,"830":2,"836":1,"837":1,"838":1,"853":1,"854":1,"855":2,"861":1,"862":1,"863":1,"872":1,"881":1,"889":1,"891":1,"893":1,"898":1,"904":2,"905":1,"906":1,"907":1,"910":1,"912":1,"922":2,"930":1,"941":1,"945":1,"946":3,"947":1,"954":1,"955":1,"971":2,"972":2,"973":1,"974":2,"976":1,"980":2,"981":1,"982":1,"988":2,"990":1,"996":4,"997":1,"1004":1,"1012":3,"1018":2,"1020":1,"1021":1,"1026":1,"1033":1,"1034":2,"1041":2,"1044":1,"1058":1,"1066":1,"1067":1,"1069":1,"1083":1,"1087":1,"1099":1,"1100":1,"1101":1,"1116":1,"1123":1,"1124":5,"1126":1,"1150":5,"1152":1,"1157":1,"1161":1,"1162":1,"1183":1,"1185":1,"1191":1,"1192":1,"1202":1,"1203":1,"1212":12,"1213":1,"1218":1,"1220":1,"1221":1,"1225":1,"1227":1,"1228":1,"1229":6,"1231":1,"1232":1,"1234":1,"1236":2,"1237":23,"1239":1,"1241":2,"1243":1,"1244":1,"1246":1,"1247":38,"1248":1,"1250":1,"1251":1,"1252":2,"1253":1,"1254":1,"1256":1,"1257":1,"1258":1,"1259":37,"1260":1,"1265":1,"1266":1,"1267":1,"1269":3,"1270":46,"1271":17,"1272":1,"1273":1,"1276":1,"1277":1,"1278":1,"1279":1,"1280":1,"1281":1,"1283":1,"1284":1,"1285":115,"1286":1,"1287":5,"1288":1,"1289":1,"1290":2,"1291":1,"1292":1,"1293":1,"1295":1,"1296":1,"1297":2,"1298":1,"1299":88,"1300":19,"1302":1,"1303":1,"1305":1,"1306":1,"1308":45,"1309":22,"1310":66,"1311":5,"1312":1,"1316":1,"1317":2,"1319":4,"1322":71,"1323":134,"1324":1,"1325":3,"1326":1,"1328":2,"1330":1,"1331":1,"1337":1,"1338":62,"1339":1,"1345":2,"1346":1,"1349":62,"1350":1,"1351":4,"1353":1,"1354":2,"1356":1,"1357":2,"1358":204,"1361":1,"1362":1,"1365":2,"1367":2,"1368":18,"1369":1,"1370":1,"1371":1,"1373":1,"1375":2,"1377":1,"1379":33,"1383":1,"1386":1,"1388":2,"1389":1,"1390":1,"1392":1,"1393":1,"1394":88,"1395":104,"1396":1,"1397":1,"1399":2,"1400":1,"1401":54,"1402":2,"1405":1,"1406":2,"1407":1,"1411":1,"1413":2,"1414":51,"1415":20,"1416":67,"1417":2,"1426":8,"1428":2,"1429":1,"1430":3,"1431":1,"1435":190,"1436":1,"1437":12,"1438":1,"1439":2,"1440":5,"1441":2,"1442":4,"1443":3,"1445":1,"1446":5,"1449":1,"1452":3,"1453":3,"1454":5,"1455":2,"1458":2,"1459":1,"1463":1,"1464":3,"1466":15,"1468":1,"1471":1,"1472":4,"1473":7,"1486":2,"1487":3,"1488":2,"1489":4,"1491":1,"1495":2,"1497":1,"1507":1,"1508":3,"1509":1,"1516":1,"1521":2,"1525":10,"1526":1,"1528":1,"1533":7,"1534":1,"1537":1,"1540":1,"1544":1,"1547":1,"1552":1,"1553":1,"1555":1,"1563":1,"1565":2,"1572":1,"1573":1,"1576":6,"1577":1,"1584":2,"1585":1,"1586":1,"1590":8,"1591":1,"1593":1,"1594":1,"1595":2,"1596":2,"1598":3,"1599":2,"1610":3,"1626":1,"1628":1,"1629":2,"1630":5,"1631":4,"1633":1,"1637":3,"1638":3,"1639":2,"1640":6,"1648":1,"1650":1,"1651":2,"1655":1,"1660":1,"1663":1,"1666":2,"1669":1,"1672":1,"1674":2,"1676":2,"1684":4,"1685":2,"1688":1,"1690":1,"1691":1,"1693":1,"1698":1,"1707":1,"1708":2,"1726":2,"1731":1,"1747":4,"1748":4,"1763":1,"1764":2,"1766":2,"1767":2,"1768":1,"1772":1,"1774":1,"1775":1,"1782":1,"1783":1,"1787":1,"1789":3,"1798":1,"1799":1,"1801":2,"1802":1,"1803":1,"1804":3,"1805":1,"1806":1,"1809":2,"1814":3,"1817":1,"1818":1,"1822":1,"1824":1,"1826":2,"1827":1,"1828":1,"1829":1,"1830":1,"1838":1,"1839":1,"1846":3,"1848":5,"1850":1,"1851":1,"1852":2,"1853":2,"1855":2,"1857":1,"1858":1,"1861":1,"1862":1,"1866":2,"1868":1,"1870":1,"1874":1,"1883":2,"1892":1,"1896":1,"1898":1,"1899":1,"1901":1,"1904":1,"1905":1,"1907":1,"1908":2,"1914":2,"1915":2,"1917":2,"1919":2,"1921":2,"1922":2,"1926":2,"1929":2,"1932":3,"1934":1,"1938":1,"1940":1,"1941":1,"1943":2,"1944":1,"1945":1,"1948":3,"1951":1,"1952":1,"1958":1,"1960":1,"1961":3,"1964":1,"1968":1,"1969":2,"1971":1,"1972":1,"1974":1,"1975":1,"1976":2,"1977":1,"1978":3,"1982":1,"1984":1,"1985":1,"1986":1,"1987":1,"1988":1,"1990":1,"1993":5,"1996":2,"1997":2,"1998":1,"1999":1,"2001":1,"2002":1,"2004":2,"2006":1,"2007":2,"2009":1,"2012":1,"2022":1,"2023":2,"2024":2,"2027":1,"2028":1,"2029":1,"2032":2,"2033":2,"2034":1,"2035":1,"2036":4,"2037":2,"2042":2,"2043":1,"2049":1,"2052":1,"2054":1,"2056":1,"2058":1,"2061":1,"2065":2,"2068":1,"2071":1,"2072":1,"2074":2,"2080":2,"2082":3,"2084":1,"2085":2,"2088":1,"2089":1,"2091":2,"2093":1,"2096":1,"2102":1,"2105":1,"2111":1,"2115":1,"2118":1,"2120":1,"2122":1,"2125":1,"2127":1,"2128":2,"2130":1,"2132":2,"2134":1,"2140":1,"2141":3,"2142":1,"2146":2,"2148":2,"2153":1,"2155":1,"2157":2,"2164":1,"2165":2,"2166":1,"2168":1,"2169":1,"2181":1,"2182":2,"2184":1,"2191":1,"2192":1,"2196":1,"2197":1,"2202":1,"2203":1,"2206":1,"2209":1,"2226":2,"2227":1,"2229":2,"2230":1,"2231":6}}],["sinks",{"3":{"401":1,"665":1,"1338":2,"1395":1}}],["sink",{"3":{"0":1,"395":1,"401":3,"914":1,"1271":1,"1285":1,"1310":1,"1325":1,"1332":1,"1338":5,"1358":2,"1415":1,"1416":2,"1435":2,"1444":1,"1466":2,"1669":1}}],["since",{"3":{"0":30,"18":1,"57":1,"91":1,"94":1,"98":1,"122":1,"126":1,"130":1,"135":2,"137":1,"138":3,"139":1,"140":2,"141":1,"142":1,"150":1,"157":1,"160":1,"166":2,"171":1,"175":1,"178":1,"202":1,"228":1,"229":1,"237":2,"243":1,"245":1,"251":1,"252":2,"254":3,"255":1,"256":2,"257":1,"258":1,"259":1,"265":2,"280":1,"291":1,"308":1,"314":1,"323":1,"324":2,"340":1,"349":1,"360":1,"365":1,"386":1,"390":1,"402":1,"413":1,"423":1,"427":1,"446":1,"448":2,"450":1,"465":1,"469":1,"490":2,"492":1,"493":1,"494":1,"499":1,"501":1,"511":1,"518":1,"526":1,"527":1,"543":1,"545":1,"550":1,"572":3,"600":1,"604":1,"626":1,"631":2,"633":1,"634":1,"636":1,"638":2,"640":1,"664":1,"665":1,"675":1,"677":1,"681":1,"696":1,"702":1,"703":1,"708":1,"711":1,"716":1,"720":2,"728":1,"789":1,"799":1,"800":1,"803":1,"805":2,"809":2,"826":1,"853":1,"857":1,"870":1,"875":1,"889":1,"898":1,"905":1,"907":1,"947":1,"948":1,"959":1,"988":1,"991":2,"1004":1,"1006":1,"1012":2,"1014":1,"1017":1,"1033":1,"1034":1,"1043":1,"1049":1,"1050":2,"1052":1,"1059":1,"1069":1,"1075":2,"1083":1,"1124":1,"1156":1,"1177":1,"1192":1,"1212":4,"1229":5,"1237":3,"1244":1,"1247":8,"1252":1,"1254":2,"1256":2,"1258":1,"1259":3,"1262":1,"1263":2,"1268":1,"1270":8,"1271":1,"1275":2,"1282":1,"1284":1,"1285":35,"1289":2,"1299":19,"1300":6,"1303":1,"1307":1,"1308":5,"1309":1,"1310":26,"1316":1,"1321":1,"1322":13,"1323":32,"1330":1,"1332":1,"1336":1,"1338":15,"1343":1,"1349":9,"1350":1,"1358":29,"1368":3,"1374":1,"1378":1,"1379":10,"1392":1,"1394":15,"1395":30,"1397":1,"1399":1,"1400":1,"1401":7,"1405":1,"1407":1,"1414":16,"1415":5,"1416":6,"1423":1,"1424":1,"1426":5,"1428":1,"1430":1,"1435":64,"1437":20,"1440":3,"1441":2,"1444":2,"1448":1,"1452":1,"1453":1,"1454":6,"1456":2,"1458":1,"1459":1,"1466":9,"1481":1,"1485":1,"1489":2,"1495":21,"1496":2,"1497":1,"1499":1,"1525":2,"1533":1,"1547":1,"1576":1,"1590":1,"1630":1,"1634":1,"1653":1,"1655":1,"1669":1,"1683":1,"1700":1,"1720":1,"1721":1,"1726":2,"1728":2,"1731":1,"1749":2,"1799":1,"1838":1,"1839":1,"1842":1,"1843":2,"1844":1,"1850":1,"1871":1,"1888":1,"1946":1,"1987":3,"2066":1,"2141":1,"2150":1}}],["sacrificing",{"3":{"2217":1}}],["sacrificed",{"3":{"1285":1}}],["saas",{"3":{"1939":1}}],["sa1203",{"3":{"1394":1}}],["sa1211",{"2":{"1688":1,"1698":1,"2088":1},"3":{"0":1,"490":1,"491":1,"492":1,"493":1,"495":1,"633":2,"1685":1,"1688":1,"1698":1,"1726":1,"2088":1}}],["sa1210",{"2":{"635":1,"1688":1,"2088":1},"3":{"0":1,"490":1,"491":1,"492":1,"493":1,"495":1,"633":2,"635":1,"1688":1,"1726":1,"2088":1}}],["sake",{"3":{"1338":1,"2155":1}}],["sa",{"3":{"1338":2,"1440":1}}],["sails",{"3":{"1997":1}}],["sail",{"3":{"1323":1}}],["said",{"3":{"0":1,"22":1,"27":1,"43":1,"62":1,"130":1,"138":1,"160":1,"200":1,"220":1,"256":1,"306":1,"353":1,"374":1,"408":1,"425":2,"426":1,"443":1,"624":1,"642":1,"683":1,"720":1,"761":1,"794":1,"837":1,"958":1,"1017":2,"1052":1,"1054":1,"1055":1,"1062":1,"1091":1,"1259":1,"1270":1,"1299":1,"1322":2,"1323":2,"1358":2,"1395":3,"1416":1,"1426":1,"1430":1,"1495":1,"1991":2}}],["safari",{"3":{"1323":5,"1416":1,"1435":2,"1605":1,"1715":1,"1739":1}}],["safer",{"3":{"584":1,"1285":1,"1300":1,"1338":1,"1435":1,"1997":1}}],["safedomaineventhandlertests",{"3":{"536":1,"1199":1,"1207":5,"1259":2,"1437":1,"2164":2}}],["safedomaineventhandler",{"2":{"534":1,"1252":1,"1664":1,"2164":1},"3":{"534":1,"536":2,"537":1,"1199":2,"1203":1,"1207":2,"1252":5,"1259":25,"1395":1,"1437":1,"1495":2,"1664":1,"2164":3}}],["safely",{"3":{"350":1,"508":1,"829":2,"883":1,"1237":1,"1259":1,"1271":1,"1285":4,"1299":3,"1310":1,"1311":1,"1323":3,"1357":1,"1358":1,"1394":1,"1395":3,"1401":2,"1414":1,"1416":3,"1426":1,"1435":1,"1437":1,"1471":1,"1562":1,"1568":1,"1645":1,"1664":1,"1839":1,"1976":1,"2184":1}}],["safeguards",{"3":{"1921":2}}],["safeguard",{"3":{"182":1,"1358":1,"1435":3,"1488":1,"1525":1}}],["safety",{"0":{"1254":1,"1560":1,"1933":1},"3":{"0":1,"6":1,"19":1,"158":1,"160":1,"209":1,"255":1,"330":1,"335":1,"382":1,"435":1,"472":1,"501":1,"522":1,"523":1,"524":1,"564":1,"800":1,"801":1,"803":1,"882":1,"1068":1,"1116":1,"1132":1,"1135":1,"1216":1,"1229":1,"1237":2,"1243":1,"1247":1,"1255":1,"1257":1,"1259":7,"1263":2,"1270":5,"1271":6,"1273":1,"1285":3,"1299":3,"1301":1,"1308":4,"1310":4,"1322":2,"1323":29,"1338":2,"1349":3,"1352":1,"1358":54,"1379":1,"1383":2,"1394":44,"1395":16,"1401":10,"1413":1,"1414":8,"1416":3,"1426":1,"1435":24,"1437":3,"1454":3,"1457":1,"1466":1,"1498":1,"1507":1,"1525":2,"1529":1,"1531":1,"1534":1,"1576":1,"1585":1,"1590":1,"1594":1,"1596":1,"1651":1,"1670":1,"1795":1,"1804":1,"1816":1,"1887":1,"1940":1,"1948":2,"1951":1,"1958":1,"1970":1,"1977":1,"1986":2,"2200":1,"2201":1}}],["safe",{"1":{"1899":1,"1900":1,"1901":1,"1902":1,"1903":1,"1904":1,"1905":1,"1906":1,"1907":1,"1908":1,"1909":1,"1910":1,"1911":1},"3":{"0":8,"15":1,"20":1,"78":1,"79":1,"109":1,"111":1,"122":1,"145":1,"147":2,"158":1,"187":1,"189":1,"208":1,"245":1,"265":2,"291":1,"302":1,"309":1,"310":1,"335":1,"397":1,"398":1,"402":1,"413":2,"416":1,"443":1,"460":1,"506":1,"507":1,"530":1,"550":1,"556":1,"564":1,"565":1,"607":2,"609":1,"610":1,"681":1,"691":1,"694":1,"700":1,"706":1,"711":1,"743":1,"751":1,"758":1,"767":1,"776":1,"827":1,"832":1,"838":1,"848":1,"861":1,"869":1,"890":1,"926":1,"940":1,"942":1,"946":1,"965":1,"989":1,"1059":1,"1084":1,"1109":1,"1115":2,"1116":1,"1124":1,"1132":1,"1147":1,"1184":1,"1212":2,"1229":3,"1237":4,"1238":1,"1247":3,"1252":1,"1259":15,"1263":1,"1269":3,"1270":12,"1271":2,"1275":1,"1278":1,"1282":1,"1284":1,"1285":29,"1297":1,"1298":1,"1299":26,"1300":1,"1303":1,"1308":12,"1309":2,"1310":28,"1319":3,"1321":1,"1322":19,"1323":34,"1332":1,"1333":1,"1336":1,"1338":19,"1343":1,"1345":1,"1348":1,"1349":7,"1354":1,"1357":1,"1358":26,"1363":1,"1368":6,"1379":4,"1394":11,"1395":24,"1401":11,"1414":16,"1415":1,"1416":28,"1420":1,"1426":1,"1428":2,"1435":25,"1452":3,"1454":7,"1456":1,"1457":2,"1462":1,"1466":2,"1471":1,"1474":1,"1476":1,"1477":2,"1483":1,"1487":1,"1489":1,"1491":2,"1495":1,"1544":1,"1553":1,"1560":1,"1576":1,"1589":1,"1590":1,"1610":1,"1625":1,"1630":1,"1659":1,"1660":1,"1667":1,"1671":2,"1685":1,"1707":1,"1728":1,"1733":1,"1747":1,"1789":1,"1834":1,"1836":1,"1841":1,"1843":1,"1872":1,"1888":2,"1898":1,"1899":1,"1901":1,"1904":1,"1905":1,"1906":2,"1909":1,"1910":1,"1911":3,"1919":1,"1922":1,"1932":1,"1934":1,"1947":1,"1949":1,"1957":1,"1962":1,"1967":1,"1971":2,"1999":1,"2010":1,"2013":1,"2019":1,"2020":1,"2028":1,"2036":1,"2047":1,"2054":1,"2062":1,"2068":1,"2086":1,"2117":1,"2118":1,"2122":2,"2125":1,"2137":1,"2147":1,"2155":1,"2161":1,"2187":1,"2195":1,"2198":1,"2207":3,"2231":1}}],["saturate",{"3":{"1310":1}}],["saturated",{"3":{"1124":1,"1256":1,"1259":2,"1285":2,"1338":2,"1379":1,"1443":1}}],["satellites",{"3":{"1435":2}}],["satellite",{"3":{"265":2,"269":1,"1323":1,"1379":2,"1401":1}}],["sat",{"3":{"72":1,"179":1,"390":1,"424":1,"689":1,"1174":1,"1323":5,"1459":1,"1466":1,"1474":1,"1499":1}}],["satisfaction",{"3":{"1395":1}}],["satisfiable",{"3":{"593":1,"1308":2,"1322":1,"1414":1}}],["satisfiesloadbearinginvariants",{"3":{"1435":2}}],["satisfies",{"3":{"160":1,"470":1,"799":1,"1018":1,"1049":1,"1089":1,"1091":1,"1229":1,"1237":1,"1247":1,"1270":1,"1285":6,"1299":3,"1308":1,"1309":2,"1310":2,"1322":1,"1323":1,"1358":13,"1367":1,"1368":2,"1379":1,"1394":4,"1395":6,"1401":2,"1409":1,"1414":4,"1416":4,"1435":11,"1481":1,"1670":1,"1809":1,"1844":1,"2125":1,"2229":1}}],["satisfied",{"3":{"0":1,"59":2,"130":1,"135":1,"140":1,"166":1,"584":1,"905":1,"926":1,"1050":1,"1059":1,"1237":1,"1247":3,"1265":1,"1270":2,"1285":1,"1292":1,"1299":5,"1308":2,"1314":1,"1319":1,"1322":6,"1323":2,"1349":3,"1358":8,"1379":1,"1395":4,"1414":7,"1415":3,"1416":2,"1430":2,"1435":7,"1477":1,"1487":1,"1533":1,"1576":1,"1661":1,"1771":1,"1789":1,"1862":1,"1881":1,"2167":1,"2196":2}}],["satisfy",{"3":{"109":1,"518":1,"528":1,"1006":1,"1017":1,"1100":1,"1124":1,"1145":1,"1220":1,"1229":1,"1237":3,"1247":1,"1285":3,"1299":4,"1300":2,"1308":1,"1309":1,"1310":1,"1311":2,"1322":6,"1323":6,"1336":1,"1337":1,"1338":3,"1349":3,"1358":8,"1377":1,"1379":2,"1387":1,"1394":1,"1395":2,"1401":1,"1414":4,"1415":1,"1416":1,"1423":1,"1426":1,"1435":12,"1446":1,"1453":1,"1466":1,"1479":1,"1487":1,"1491":1,"1590":1,"1789":1,"1792":1,"1804":1,"1863":1,"1915":1,"1966":1,"2016":1,"2068":1}}],["satisfying",{"3":{"40":1,"803":1,"1299":2,"1309":1,"1349":1,"1395":1,"1415":1,"1416":1,"1435":1,"1437":1}}],["sawerrors",{"3":{"1229":1}}],["sawvalue",{"3":{"1229":2}}],["saw",{"3":{"26":1,"72":1,"236":1,"340":1,"873":1,"876":1,"881":1,"1017":1,"1052":1,"1055":1,"1169":1,"1259":1,"1285":1,"1299":2,"1310":3,"1322":1,"1323":2,"1349":2,"1358":2,"1394":1,"1395":3,"1435":2,"1868":1,"1869":1}}],["sandboxing",{"3":{"2238":1}}],["sandboxed",{"3":{"1525":1}}],["sandbox",{"3":{"1428":1}}],["sandwich",{"3":{"1414":1}}],["santiago",{"3":{"1349":1}}],["sans",{"3":{"1323":1,"2074":1}}],["sane",{"3":{"1228":1,"1229":1,"1299":1,"1323":1,"1338":1,"1395":1,"1675":1}}],["sanctions",{"3":{"913":1,"1395":1,"1590":1}}],["sanctioned",{"3":{"0":1,"260":1,"371":1,"392":1,"642":1,"912":1,"914":1,"996":1,"1229":1,"1237":1,"1247":1,"1285":4,"1379":1,"1415":2,"1416":1,"1435":2,"1454":1,"1805":1,"2231":1}}],["sanitization",{"3":{"1285":1,"1322":1,"1562":1}}],["sanitizes",{"3":{"1285":1,"1319":1,"1323":2,"1358":1}}],["sanitize",{"3":{"1270":2,"1322":1,"1323":4}}],["sanitized",{"3":{"1116":2,"1285":1,"1299":2,"1322":1,"1323":3,"1343":1,"1349":2,"1356":1,"1358":2,"1525":1,"1562":1,"1571":1,"1576":1,"1630":1,"1668":1}}],["sanitizer",{"3":{"1114":1,"1323":2,"2231":1}}],["sanitizefilename",{"2":{"443":1,"1116":1},"3":{"0":1,"443":1,"1116":1,"1285":1}}],["sanity",{"3":{"846":1,"1435":2,"1478":1,"1488":1}}],["saying",{"3":{"201":1,"350":1,"541":1,"576":1,"673":1,"1140":1,"1161":1,"1259":1,"1270":1,"1285":3,"1308":1,"1322":1,"1323":2,"1338":3,"1358":3,"1388":1,"1394":1,"1395":5,"1414":1,"1426":1,"1435":3,"1441":1,"1595":1,"1645":1,"1684":1,"1859":1,"1886":1,"1907":1,"1908":1,"1960":1,"1969":1,"1976":1,"1999":1,"2161":1,"2163":1}}],["say",{"0":{"1422":1,"1946":1},"3":{"0":1,"73":1,"109":1,"122":1,"123":1,"136":1,"171":1,"185":1,"220":1,"231":1,"260":1,"261":1,"284":1,"300":1,"353":1,"390":1,"444":1,"477":1,"564":1,"578":1,"590":1,"610":1,"619":1,"626":2,"627":1,"638":1,"660":1,"676":1,"684":2,"689":1,"702":1,"703":1,"741":1,"743":1,"756":1,"776":1,"790":1,"804":1,"864":1,"871":1,"899":1,"904":1,"905":1,"907":1,"921":1,"960":1,"988":1,"1062":1,"1067":1,"1079":1,"1082":1,"1085":1,"1107":1,"1216":1,"1231":1,"1233":1,"1237":2,"1247":1,"1253":1,"1259":2,"1270":1,"1271":1,"1285":8,"1292":1,"1296":1,"1299":5,"1308":1,"1310":10,"1315":1,"1322":9,"1323":5,"1338":1,"1349":2,"1358":17,"1365":1,"1368":2,"1394":5,"1395":10,"1401":2,"1414":8,"1416":2,"1417":1,"1422":1,"1430":1,"1435":14,"1437":1,"1442":1,"1473":1,"1485":1,"1487":1,"1488":1,"1552":1,"1590":2,"1595":1,"1637":1,"1650":1,"1684":1,"1685":1,"1688":1,"1697":1,"1701":1,"1747":1,"1748":1,"1764":1,"1798":1,"1802":1,"1809":1,"1822":1,"1841":1,"1847":1,"1852":1,"1853":1,"1861":1,"1901":1,"1903":1,"1907":1,"1908":1,"1910":1,"1928":1,"1994":1,"1999":1,"2006":1,"2011":1,"2012":1,"2043":1,"2056":1,"2063":1,"2085":1,"2086":1,"2155":1,"2157":1,"2162":1,"2174":1,"2179":1,"2202":1}}],["saysnothingaboutthecanonicaldeclaration",{"3":{"1435":3}}],["says",{"3":{"0":4,"13":1,"27":2,"74":1,"77":1,"135":2,"137":1,"138":1,"139":1,"142":1,"149":1,"170":1,"171":1,"187":1,"189":1,"200":3,"201":2,"254":1,"256":2,"259":1,"265":1,"317":1,"342":1,"343":1,"353":1,"391":1,"459":1,"461":1,"478":1,"490":1,"535":1,"537":1,"539":1,"540":1,"541":1,"549":1,"550":1,"556":1,"563":1,"564":1,"601":1,"611":1,"624":1,"625":1,"633":3,"636":1,"657":1,"659":1,"660":2,"665":2,"674":1,"690":1,"692":1,"703":1,"725":1,"740":1,"742":1,"743":1,"749":2,"751":1,"761":1,"765":1,"781":1,"784":1,"793":1,"798":1,"827":1,"832":1,"833":2,"861":1,"876":1,"880":3,"890":2,"891":1,"893":1,"897":2,"904":1,"907":1,"927":1,"932":1,"946":1,"955":1,"958":1,"964":1,"966":1,"967":1,"979":1,"988":1,"996":1,"997":1,"1014":1,"1018":3,"1026":1,"1037":1,"1050":2,"1051":1,"1058":1,"1067":1,"1074":1,"1079":1,"1085":1,"1091":1,"1093":1,"1099":1,"1119":1,"1124":1,"1133":1,"1167":3,"1176":2,"1190":1,"1212":1,"1227":1,"1229":4,"1237":6,"1241":1,"1247":6,"1248":1,"1259":12,"1262":1,"1266":1,"1269":1,"1270":17,"1271":1,"1276":1,"1277":1,"1278":1,"1285":30,"1289":1,"1290":1,"1296":1,"1299":21,"1300":2,"1304":1,"1308":3,"1309":2,"1310":23,"1314":1,"1315":1,"1317":1,"1319":1,"1322":18,"1323":29,"1336":1,"1337":1,"1338":20,"1341":2,"1345":1,"1346":1,"1347":1,"1349":10,"1353":1,"1354":1,"1355":1,"1356":1,"1358":58,"1366":1,"1368":3,"1379":3,"1392":1,"1394":22,"1395":47,"1399":1,"1401":11,"1409":1,"1413":1,"1414":24,"1415":1,"1416":9,"1418":1,"1429":2,"1432":1,"1435":42,"1437":2,"1440":1,"1441":1,"1444":2,"1452":1,"1454":4,"1459":1,"1461":1,"1466":8,"1469":1,"1472":1,"1473":2,"1475":2,"1480":1,"1487":1,"1488":2,"1489":1,"1491":2,"1495":2,"1499":1,"1552":1,"1595":1,"1638":1,"1645":1,"1660":1,"1684":3,"1701":1,"1720":1,"1722":1,"1726":1,"1728":1,"1764":1,"1768":1,"1793":1,"1798":1,"1814":2,"1830":1,"1834":1,"1835":1,"1842":1,"1879":2,"1886":1,"1891":1,"1905":1,"1914":1,"1943":1,"1944":1,"1959":1,"1962":1,"1964":2,"1981":1,"1991":2,"1993":2,"1999":2,"2000":1,"2001":1,"2011":1,"2028":1,"2029":1,"2032":1,"2038":1,"2044":1,"2046":1,"2055":1,"2104":1,"2123":2,"2125":1,"2130":1,"2150":1,"2155":1,"2157":2,"2161":1,"2163":1,"2166":1,"2167":2,"2170":1,"2171":1,"2172":1,"2174":1,"2176":1,"2178":1,"2195":1}}],["sast",{"3":{"2218":1,"2219":1}}],["sas",{"3":{"0":2,"441":1,"597":1,"599":1,"602":1,"638":2,"643":2,"1445":1,"1466":7,"1588":1,"1590":1,"1594":1,"1595":2,"1596":1,"2126":1}}],["savings",{"3":{"1026":1,"1454":1,"1458":1,"1748":2,"1749":1,"1763":2,"1766":2,"1768":1}}],["savingchangesasync",{"3":{"715":1,"1285":5}}],["savingchanges",{"2":{"21":1,"1251":1},"3":{"21":1,"715":1,"1251":1,"1274":3,"1285":11}}],["saving",{"3":{"0":1,"43":1,"197":1,"342":1,"391":1,"534":1,"709":1,"827":1,"1026":2,"1229":1,"1237":1,"1269":1,"1270":4,"1285":11,"1299":1,"1322":3,"1323":1,"1338":1,"1358":2,"1398":1,"1400":1,"1401":17,"1416":1,"1437":1,"1483":1,"1525":1,"1748":1,"2023":1,"2163":1,"2219":1}}],["savebutton",{"3":{"1435":1}}],["savebehindaninterface",{"3":{"1435":2}}],["saveaddressbutton",{"3":{"1435":1}}],["saveasync",{"3":{"1323":4}}],["savenamebutton",{"3":{"1435":1}}],["saver",{"3":{"1395":4,"1416":5}}],["saveresult",{"3":{"1323":2,"1394":27}}],["savepasswordasync",{"3":{"1414":1}}],["savepagefilters",{"3":{"1382":2,"1394":10}}],["saveprofileasync",{"3":{"1394":1}}],["savetitleasync",{"3":{"1394":2}}],["savetokens",{"3":{"1310":2}}],["savefilters",{"3":{"1323":3,"1382":2,"1394":8,"1395":1}}],["savewithidentityinsertasync",{"3":{"1285":2}}],["savecount",{"3":{"1435":3}}],["savechangedetectiontests",{"3":{"1199":1,"1207":5,"1285":2,"1437":2}}],["savechangesinterceptor",{"2":{"1838":1},"3":{"0":1,"715":1,"1212":3,"1285":2,"1838":1}}],["savechanges",{"0":{"1274":1},"2":{"124":1,"365":1,"809":1,"1700":1,"1838":1,"1843":1,"1845":1,"2090":1,"2091":1,"2160":1,"2163":1,"2168":1},"3":{"0":1,"124":1,"359":1,"365":1,"540":1,"715":1,"809":1,"1212":1,"1213":1,"1237":2,"1252":1,"1259":6,"1273":1,"1285":11,"1368":2,"1395":1,"1435":5,"1497":1,"1584":1,"1670":1,"1700":1,"1834":1,"1837":1,"1838":1,"1843":1,"1845":1,"2090":1,"2091":1,"2141":1,"2160":1,"2163":1,"2168":2}}],["savechangesasync",{"2":{"17":1,"27":1,"340":1,"444":1,"465":1,"466":1,"518":1,"522":1,"523":2,"524":1,"539":1,"714":1,"781":1,"1354":1,"1357":1,"1362":1,"1822":1,"1837":2,"1843":1,"1868":1,"1909":1,"1933":1,"2110":1,"2163":1},"3":{"0":1,"17":1,"27":1,"201":1,"340":1,"444":1,"459":1,"465":1,"466":1,"499":1,"518":1,"522":2,"523":4,"524":1,"539":1,"540":1,"714":1,"781":1,"897":2,"905":1,"1100":2,"1212":1,"1237":3,"1258":1,"1259":13,"1270":3,"1272":1,"1273":1,"1285":23,"1299":2,"1308":5,"1322":10,"1344":1,"1348":1,"1349":13,"1354":1,"1357":1,"1358":41,"1362":1,"1368":1,"1394":19,"1395":4,"1401":5,"1409":1,"1414":10,"1435":21,"1487":2,"1488":1,"1525":2,"1650":1,"1748":1,"1764":1,"1809":1,"1822":1,"1837":2,"1843":1,"1868":1,"1909":1,"1933":1,"2110":1,"2163":1}}],["savecurrentstate",{"3":{"881":1,"1323":3}}],["saveerror",{"3":{"265":1,"1323":1}}],["savedsortdescending",{"3":{"1323":1}}],["savedsortcolumn",{"3":{"1323":1}}],["savedpagesize",{"3":{"1323":2}}],["savedpage",{"3":{"1323":1}}],["savedchangesasync",{"3":{"1274":1,"1285":3}}],["savedchanges",{"3":{"1252":1,"1274":2,"1285":7}}],["saved",{"3":{"0":2,"120":2,"536":1,"538":1,"540":1,"609":1,"627":1,"725":1,"914":2,"926":1,"991":1,"1024":1,"1026":2,"1093":2,"1247":1,"1259":3,"1270":2,"1274":1,"1275":2,"1285":12,"1299":1,"1305":1,"1308":2,"1310":2,"1321":1,"1322":2,"1323":2,"1349":1,"1358":8,"1379":4,"1382":4,"1390":1,"1394":15,"1395":13,"1398":1,"1401":3,"1414":4,"1435":2,"1437":1,"1455":1,"1466":2,"1478":1,"1479":1,"1590":1,"1748":2,"1749":1,"1764":1,"1767":1,"1775":2,"1834":1,"1932":2,"1950":1,"2157":1,"2184":1}}],["save",{"2":{"268":1},"3":{"0":6,"17":2,"18":1,"20":2,"21":2,"26":2,"27":2,"39":1,"109":2,"120":1,"166":1,"195":1,"201":4,"225":1,"265":1,"268":1,"340":2,"342":1,"344":1,"388":1,"391":1,"435":1,"436":1,"516":2,"518":4,"522":2,"523":5,"524":3,"534":1,"536":1,"538":4,"539":4,"698":1,"715":1,"716":1,"717":1,"857":1,"897":1,"905":2,"908":2,"920":1,"921":1,"923":2,"926":6,"931":1,"988":2,"991":1,"1033":1,"1035":1,"1100":6,"1102":1,"1116":1,"1140":1,"1168":1,"1175":1,"1212":2,"1231":2,"1232":2,"1233":2,"1237":10,"1251":4,"1252":6,"1253":3,"1257":2,"1258":1,"1259":31,"1260":1,"1265":7,"1270":31,"1272":1,"1273":3,"1274":3,"1276":3,"1277":3,"1278":2,"1285":132,"1288":2,"1289":3,"1292":1,"1293":1,"1299":11,"1303":4,"1308":6,"1310":4,"1315":1,"1317":1,"1321":1,"1322":23,"1323":8,"1343":1,"1344":1,"1348":1,"1349":10,"1351":1,"1354":1,"1356":3,"1357":1,"1358":109,"1368":4,"1378":1,"1389":1,"1394":43,"1395":17,"1398":3,"1401":13,"1405":2,"1414":26,"1416":3,"1431":1,"1435":40,"1437":10,"1452":1,"1455":1,"1507":1,"1542":1,"1637":2,"1663":2,"1664":1,"1665":2,"1671":1,"1684":2,"1719":1,"1741":1,"1748":1,"1764":1,"1817":1,"1834":2,"1836":1,"1845":1,"1848":1,"1851":1,"1856":1,"1868":3,"1870":1,"1871":2,"1876":5,"1905":1,"1932":5,"1933":1,"1939":1,"1945":1,"1950":1,"1972":1,"2059":1,"2062":1,"2082":1,"2090":1,"2160":2,"2162":3,"2164":3,"2166":2,"2167":3,"2181":1,"2184":1,"2229":2,"2231":1}}],["savestate",{"3":{"1323":1}}],["savestagedasync",{"3":{"201":1,"1259":1}}],["saveskipped",{"3":{"926":1,"1270":3}}],["saves",{"3":{"0":2,"24":1,"27":2,"39":1,"195":1,"201":1,"499":1,"518":2,"522":3,"523":1,"524":1,"536":2,"540":2,"792":1,"897":1,"908":1,"921":1,"986":1,"988":3,"1042":1,"1045":1,"1061":1,"1100":1,"1115":1,"1143":1,"1237":2,"1258":1,"1259":4,"1260":1,"1265":2,"1270":3,"1278":1,"1285":10,"1288":1,"1299":5,"1308":4,"1322":3,"1323":3,"1345":2,"1351":1,"1354":1,"1358":17,"1368":3,"1372":1,"1394":4,"1395":6,"1401":1,"1410":1,"1413":1,"1414":7,"1430":1,"1435":12,"1454":1,"1471":1,"1475":1,"1478":1,"1637":1,"1868":2,"1932":2,"1981":1,"2062":1,"2090":1,"2091":1,"2160":1}}],["sagastatemachineinstance",{"2":{"809":1,"813":1},"3":{"809":1,"813":1}}],["sagas",{"3":{"0":3,"536":1,"926":1,"1011":1,"1012":1,"1576":1}}],["saga",{"1":{"533":1,"534":1,"535":1,"536":1,"537":1,"538":1,"539":1,"540":1,"541":1,"2160":1,"2161":1,"2162":1,"2163":1,"2164":1,"2165":1,"2166":1,"2167":1,"2168":1},"3":{"0":4,"230":1,"533":1,"534":1,"536":9,"537":2,"539":1,"674":1,"809":2,"810":3,"811":2,"812":3,"813":2,"815":2,"896":1,"987":1,"1041":1,"1044":1,"1212":2,"1308":1,"1435":1,"1525":2,"1546":1,"1571":1,"1590":5,"1599":2,"1664":1,"1754":2,"1764":1,"1778":2,"2112":1,"2159":2,"2160":2,"2162":2,"2166":1,"2167":4,"2168":1,"2179":1,"2188":1,"2205":1,"2214":2,"2231":2}}],["salient",{"3":{"1794":1}}],["salvages",{"3":{"1299":1}}],["salvaged",{"3":{"1018":1}}],["saleable",{"3":{"1763":1}}],["sale",{"3":{"539":1,"1025":1,"1027":1,"1028":3,"1741":1,"1749":2,"1768":1,"1772":2}}],["salesrouteauthorizationtests",{"3":{"1435":1,"1590":1}}],["salesintegrationtestfixture",{"3":{"1435":1}}],["salescrossservicefactory",{"3":{"1435":1}}],["salestestwebapplicationfactory",{"3":{"1435":1}}],["salesfakeusecase",{"3":{"1199":2,"1207":2}}],["salesfakeorder",{"3":{"1199":2,"1207":2}}],["salesfakeaggregate",{"3":{"1199":2,"1207":2}}],["salesfeatures",{"2":{"2135":1},"3":{"300":1,"1270":1,"2135":2}}],["salesuimodule",{"3":{"649":1,"1323":4}}],["salesuserdataexportsection",{"2":{"583":1},"3":{"585":1,"725":1,"1322":4}}],["salesmoduletests",{"3":{"1435":2}}],["salesmoduleseeder",{"3":{"1322":2}}],["salesmoduledbseeder",{"3":{"1285":2}}],["salesmodule",{"3":{"539":1,"540":2,"583":2,"584":1,"674":1,"676":1,"1322":1}}],["salesapp",{"2":{"538":1},"3":{"538":1}}],["salespermissiongrants",{"3":{"186":1}}],["salespermissions",{"2":{"318":1,"324":1,"1264":1,"1994":1},"3":{"186":1,"318":1,"324":1,"1264":1,"1994":1}}],["salesservice",{"2":{"96":1},"3":{"96":1}}],["sales",{"0":{"96":1,"1748":1},"1":{"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1},"2":{"62":1,"96":2,"493":1,"534":1,"794":1,"1587":1,"1747":1,"1750":1,"1765":1,"2166":1},"3":{"0":11,"24":1,"62":2,"72":6,"73":4,"80":3,"91":8,"94":1,"95":4,"96":12,"97":2,"98":1,"99":1,"100":1,"101":3,"102":1,"104":3,"117":2,"128":2,"165":1,"186":2,"195":4,"230":7,"235":2,"238":1,"243":3,"245":2,"250":1,"251":1,"252":1,"254":1,"257":1,"260":4,"261":1,"291":1,"295":1,"318":6,"324":1,"401":1,"448":1,"452":3,"453":1,"454":1,"459":1,"475":4,"476":4,"477":4,"493":1,"534":1,"535":2,"536":3,"539":4,"540":1,"545":3,"554":1,"556":2,"564":3,"572":2,"583":11,"584":1,"585":4,"586":1,"599":2,"602":1,"640":2,"643":1,"649":3,"657":2,"674":7,"676":2,"715":1,"725":1,"728":1,"743":2,"749":2,"752":1,"757":1,"774":3,"780":2,"782":9,"789":5,"790":20,"792":1,"793":13,"794":9,"795":1,"798":1,"804":1,"809":3,"813":4,"837":1,"838":4,"841":1,"868":1,"869":1,"870":1,"873":2,"876":3,"877":1,"881":3,"897":2,"900":2,"914":1,"926":6,"954":4,"988":4,"1023":1,"1025":9,"1026":11,"1027":2,"1029":2,"1054":1,"1083":1,"1085":1,"1161":1,"1207":6,"1212":3,"1233":1,"1237":10,"1259":3,"1264":4,"1265":1,"1270":14,"1285":18,"1299":19,"1300":1,"1308":4,"1309":4,"1310":23,"1311":4,"1322":10,"1323":16,"1338":13,"1435":30,"1485":1,"1540":1,"1587":2,"1588":1,"1590":21,"1595":3,"1599":6,"1630":1,"1741":1,"1745":17,"1747":1,"1748":4,"1750":14,"1751":1,"1756":1,"1760":1,"1762":1,"1763":16,"1764":40,"1765":1,"1768":9,"1769":3,"1770":1,"1771":5,"1774":2,"1784":1,"1838":1,"1873":1,"1874":1,"1962":1,"1994":1,"1996":1,"2024":1,"2086":1,"2109":1,"2110":1,"2112":1,"2130":1,"2135":1,"2137":1,"2160":1,"2165":2,"2166":3,"2167":1,"2186":1,"2188":1,"2219":1,"2227":1,"2231":1}}],["salted",{"0":{"1901":1},"3":{"309":1,"905":1,"1299":1}}],["saltsize",{"2":{"284":1,"944":1,"948":1},"3":{"284":1,"310":1,"944":1,"946":3,"948":1,"1299":5}}],["salt",{"3":{"0":7,"284":2,"309":1,"310":6,"311":2,"312":5,"314":2,"350":1,"352":1,"906":1,"944":1,"945":4,"946":10,"947":1,"948":3,"1212":3,"1285":1,"1290":1,"1291":2,"1299":27,"1322":3,"1409":1,"1414":7,"1435":2,"1576":1,"1629":1,"1640":4,"1666":1,"1672":1,"1746":1,"1764":2,"1899":2,"1901":7,"1904":3,"1976":2,"1977":1,"2066":1}}],["sample=",{"3":{"1435":1}}],["sampleid",{"3":{"1435":1}}],["sampleitem",{"3":{"1199":4,"1207":1,"1495":1}}],["samplev1tov2upcaster",{"3":{"1199":2,"1207":1}}],["samplemodel",{"3":{"1199":2,"1207":1}}],["samplegriddata",{"3":{"1199":1,"1207":1,"1435":4}}],["samplegridrow",{"3":{"1199":2,"1207":3,"1435":9}}],["samplegatewayhardeningtests",{"3":{"1199":1,"1207":1,"1435":1}}],["samplegatewayentrypoint",{"3":{"1199":2,"1207":1}}],["sampleapphostcollection",{"2":{"1429":1},"3":{"1199":2,"1207":2,"1429":2,"1486":1}}],["sampleapphostfixture",{"3":{"1069":1,"1199":3,"1207":2,"1429":2,"1486":1}}],["sampleapphosttests",{"3":{"1067":1,"1199":1,"1207":2,"1429":2,"1486":1}}],["sampler",{"3":{"398":1,"402":1,"1338":2,"1574":1,"1576":2,"1584":1,"1677":1}}],["sampleservice",{"3":{"1199":2,"1207":1}}],["samples",{"0":{"1678":1},"3":{"397":1,"599":2,"601":1,"781":1,"963":1,"1299":1,"1349":1,"1430":1,"1435":7,"1437":1,"1442":1,"1466":2,"1469":1,"1495":2,"1574":2,"1576":10,"1580":1,"1581":2,"1582":1,"1584":1,"1585":3,"1647":1,"1669":1,"1670":1,"1676":1,"2108":1,"2155":1}}],["sample",{"0":{"1363":1},"2":{"1452":1},"3":{"0":5,"76":1,"80":2,"181":1,"295":1,"397":1,"399":1,"405":1,"543":1,"550":1,"554":1,"556":1,"597":1,"599":2,"604":2,"718":1,"749":1,"780":1,"782":1,"861":8,"862":2,"863":2,"905":1,"913":1,"917":1,"964":1,"1050":1,"1067":8,"1069":2,"1090":1,"1229":1,"1237":2,"1264":1,"1270":2,"1285":1,"1310":1,"1322":1,"1349":3,"1358":1,"1363":6,"1368":11,"1379":5,"1414":1,"1430":1,"1435":28,"1436":1,"1437":1,"1440":1,"1442":1,"1445":1,"1452":3,"1466":2,"1485":2,"1486":1,"1487":1,"1489":1,"1495":1,"1525":1,"1574":3,"1575":1,"1576":10,"1580":1,"1581":3,"1582":2,"1585":1,"1601":1,"1653":1,"1660":1,"1672":1,"1673":2,"1676":1,"1682":1,"1684":2,"1685":2,"1686":1,"1687":2,"1688":3,"1689":1,"1696":2,"1716":1,"1717":1,"1718":1,"1726":6,"1727":1,"1728":1,"1799":1,"1814":1,"1890":1,"2062":1,"2088":1,"2109":1,"2155":1,"2159":2}}],["sampledeploymentobservabilitytests",{"2":{"1430":1},"3":{"1199":1,"1207":2,"1430":4,"1435":5,"1495":1,"1576":2,"1582":1,"1585":2}}],["sampled",{"3":{"0":1,"135":1,"397":1,"398":2,"399":1,"861":1,"1332":2,"1338":3,"1442":1,"1445":1,"1466":3,"1495":2,"1676":1,"2009":2,"2155":1,"2156":1,"2158":1}}],["samplingduration",{"3":{"819":1,"1338":3}}],["sampling",{"2":{"1329":1},"3":{"0":4,"68":1,"70":1,"71":1,"100":1,"395":1,"397":1,"398":2,"399":4,"402":1,"406":1,"914":1,"916":1,"918":1,"1212":1,"1259":1,"1311":1,"1329":2,"1332":2,"1338":7,"1437":1,"1442":3,"1466":3,"1468":1,"1567":1,"1590":1,"1676":1,"1677":1,"2005":1,"2009":1,"2029":1,"2031":1,"2036":1,"2155":1,"2158":2,"2159":1,"2231":1}}],["sameevent",{"3":{"1395":1}}],["samesponsor",{"3":{"1395":1}}],["samesession",{"3":{"1395":1}}],["samesitemode",{"3":{"1299":1}}],["samesite",{"2":{"1187":1,"1968":1},"3":{"1184":3,"1185":1,"1187":1,"1299":2,"1666":1,"1968":1}}],["samesite=none",{"3":{"1310":1}}],["samesite=strict",{"3":{"0":1,"1640":2}}],["samesite=lax",{"3":{"0":1,"207":3,"209":1,"350":1,"1187":1,"1297":2,"1299":1,"1310":1,"1323":2,"1533":2,"1967":1,"1968":1,"1970":1,"1971":1,"1974":1,"2082":1}}],["sameoriginproxytransformer",{"3":{"1184":2}}],["sameoriginproxytokenrefreshertests",{"3":{"1199":1,"1207":2,"1323":2}}],["sameoriginproxytokenrefresher",{"2":{"507":1,"509":1,"1581":1},"3":{"0":1,"507":5,"509":1,"1199":2,"1203":1,"1207":2,"1323":8,"1415":1,"1581":2}}],["sameoriginproxyheaders",{"3":{"1184":2,"1581":1}}],["sameoriginproxy",{"3":{"1184":8,"1581":1}}],["sameoriginproxyrequesthandler",{"2":{"1184":1},"3":{"1184":2}}],["sameoriginapiproxyhubtests",{"3":{"1184":2}}],["sameoriginapiproxycsrftests",{"3":{"1184":2}}],["sameoriginapiproxyendpoint",{"3":{"1184":5,"1185":1,"1186":3,"1187":1,"1581":1}}],["sameoriginapiproxyendpointextensions",{"3":{"1184":1}}],["sameoriginapiproxy",{"3":{"1184":1,"1581":1}}],["sameoriginapiproxysettingsvalidator",{"3":{"1184":1,"1185":1}}],["sameoriginapiproxysettings",{"3":{"1184":3}}],["sameoriginapiproxyserviceextensions",{"3":{"1184":3,"1186":1,"1581":1}}],["sameoriginapiproxytokentests",{"2":{"1184":1},"3":{"1184":2}}],["sameoriginapiproxyoptintests",{"2":{"1184":1},"3":{"1184":2}}],["sameoriginapiproxyorigintests",{"2":{"1184":1},"3":{"1184":2}}],["sameoriginapiproxyauthflowtests",{"2":{"1184":1},"3":{"1184":2}}],["sameoriginapiproxyrefreshoutcometests",{"2":{"1184":1},"3":{"1184":2}}],["sameoriginapiendpoint",{"2":{"1184":1},"3":{"1184":4}}],["sameness",{"3":{"284":1}}],["same",{"0":{"1225":1,"1228":1,"1292":1,"1294":1,"1316":1,"1824":1,"1841":1,"1909":1,"1975":1,"1982":1,"2045":1,"2072":1},"1":{"712":1,"713":1,"714":1,"715":1,"716":1,"717":1,"718":1,"719":1,"720":1,"721":1,"1181":1,"1182":1,"1183":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1,"2069":1,"2070":1,"2071":1,"2072":1,"2073":1,"2074":1,"2075":1,"2076":1,"2077":1,"2078":1,"2079":1},"3":{"0":71,"17":1,"18":1,"20":2,"21":2,"23":1,"24":6,"26":8,"30":2,"31":1,"32":1,"39":1,"41":1,"42":2,"46":1,"53":2,"57":1,"59":2,"61":1,"62":1,"68":1,"69":1,"70":1,"71":1,"72":1,"76":1,"78":1,"79":1,"87":1,"91":1,"92":2,"93":2,"94":1,"95":3,"96":3,"99":3,"100":2,"103":1,"109":9,"110":4,"117":1,"118":1,"120":2,"122":4,"125":3,"126":2,"130":1,"132":1,"133":1,"135":5,"136":5,"139":1,"142":1,"145":4,"146":1,"147":4,"148":1,"150":2,"151":1,"156":1,"157":6,"158":1,"159":1,"160":2,"162":1,"164":1,"165":2,"166":1,"167":1,"173":1,"175":3,"178":1,"180":3,"185":1,"186":2,"188":1,"193":2,"194":1,"195":2,"196":1,"197":3,"200":2,"201":5,"202":10,"207":1,"213":1,"217":1,"218":1,"220":1,"221":1,"225":3,"226":2,"227":1,"230":4,"231":1,"235":3,"236":1,"237":1,"239":1,"242":1,"243":7,"245":3,"247":1,"248":1,"251":1,"255":4,"257":1,"265":9,"266":1,"272":1,"273":2,"274":1,"275":2,"279":1,"282":1,"283":3,"285":2,"290":1,"291":1,"295":6,"300":1,"302":1,"309":1,"310":3,"313":2,"314":1,"318":5,"320":1,"321":1,"325":2,"329":1,"330":1,"331":1,"337":1,"340":6,"341":6,"342":2,"343":1,"344":2,"346":4,"348":1,"349":3,"350":8,"351":1,"352":2,"353":1,"358":1,"359":3,"361":3,"362":1,"365":1,"370":3,"371":1,"373":1,"375":1,"376":1,"380":2,"381":1,"383":1,"386":1,"388":3,"390":1,"391":1,"392":1,"395":1,"396":1,"397":6,"401":4,"402":1,"403":1,"404":3,"406":2,"407":1,"408":1,"409":1,"412":1,"413":3,"414":1,"415":3,"420":1,"422":1,"423":1,"424":2,"427":1,"434":1,"435":1,"440":1,"443":3,"444":1,"446":1,"448":5,"449":1,"450":2,"452":1,"455":1,"458":1,"459":3,"460":1,"461":2,"462":1,"463":2,"464":1,"470":3,"472":1,"473":2,"475":2,"476":1,"477":2,"480":1,"481":1,"482":1,"485":1,"486":1,"487":1,"488":2,"489":2,"490":1,"491":1,"492":2,"493":3,"494":4,"499":4,"500":1,"503":2,"506":2,"507":5,"508":2,"509":2,"513":1,"514":1,"517":3,"519":1,"520":1,"524":2,"526":1,"528":4,"529":2,"530":2,"532":2,"536":8,"537":2,"538":3,"539":7,"543":3,"544":1,"545":4,"546":1,"549":5,"550":1,"551":1,"555":1,"556":8,"557":2,"558":2,"560":2,"564":3,"565":1,"566":2,"572":1,"573":1,"574":2,"575":1,"577":2,"579":1,"582":2,"583":3,"584":2,"585":2,"587":1,"591":5,"592":7,"599":9,"600":1,"601":1,"602":1,"603":1,"604":1,"605":3,"607":1,"609":9,"610":2,"611":4,"613":1,"614":1,"617":2,"618":1,"619":1,"622":1,"624":3,"626":10,"627":1,"628":1,"629":3,"633":3,"635":2,"638":2,"639":1,"640":8,"641":3,"642":4,"644":4,"648":2,"649":2,"650":4,"651":1,"657":6,"658":5,"659":2,"665":1,"667":2,"668":2,"674":13,"675":1,"676":1,"681":1,"682":2,"683":1,"684":1,"686":1,"689":1,"690":6,"691":2,"692":1,"694":1,"698":6,"699":1,"702":2,"707":7,"709":1,"711":1,"712":1,"714":2,"715":3,"716":4,"718":1,"721":1,"724":3,"725":6,"726":4,"729":1,"732":2,"733":2,"734":1,"740":5,"741":7,"742":1,"743":3,"747":1,"748":1,"749":4,"750":3,"751":2,"756":3,"757":1,"758":1,"759":1,"760":1,"761":1,"766":4,"767":1,"770":1,"773":1,"774":3,"776":1,"778":3,"782":7,"783":3,"784":1,"785":1,"786":1,"789":1,"790":3,"794":1,"795":1,"798":1,"799":2,"801":1,"803":2,"804":3,"805":6,"806":1,"809":1,"810":2,"819":1,"821":1,"825":3,"827":12,"829":6,"830":1,"831":4,"832":5,"833":1,"837":3,"838":8,"839":1,"840":1,"846":2,"854":1,"856":1,"859":1,"861":6,"862":1,"863":3,"865":3,"868":3,"869":1,"870":2,"871":1,"872":2,"873":1,"875":1,"876":3,"877":1,"880":1,"881":10,"882":1,"883":1,"885":1,"889":8,"890":4,"891":3,"897":2,"899":1,"901":1,"903":1,"904":2,"905":8,"906":1,"907":3,"910":3,"913":1,"914":3,"916":9,"921":1,"922":3,"923":5,"924":1,"926":5,"930":2,"931":1,"933":4,"935":1,"937":3,"939":2,"941":1,"942":1,"946":3,"948":2,"950":2,"953":1,"954":1,"955":2,"957":1,"958":1,"963":4,"964":1,"965":2,"966":1,"967":1,"968":1,"971":2,"972":3,"976":1,"979":4,"980":3,"981":1,"982":5,"988":3,"992":1,"995":5,"996":3,"997":2,"998":4,"1000":1,"1004":2,"1012":2,"1016":1,"1018":10,"1019":2,"1020":1,"1022":1,"1026":3,"1027":1,"1028":2,"1033":1,"1034":8,"1035":3,"1041":2,"1042":7,"1044":4,"1049":1,"1050":7,"1051":1,"1052":2,"1055":1,"1058":2,"1059":5,"1061":4,"1066":2,"1067":1,"1068":4,"1069":2,"1074":3,"1075":4,"1076":1,"1079":1,"1084":1,"1090":1,"1091":3,"1093":1,"1100":1,"1108":2,"1116":3,"1117":1,"1118":2,"1123":1,"1124":8,"1126":3,"1127":1,"1133":3,"1134":1,"1137":1,"1142":2,"1145":1,"1147":1,"1150":1,"1162":1,"1167":2,"1168":1,"1175":2,"1176":1,"1178":1,"1179":1,"1181":1,"1182":1,"1183":1,"1184":5,"1185":1,"1186":1,"1187":1,"1193":1,"1211":3,"1212":14,"1214":1,"1216":1,"1217":1,"1218":2,"1219":1,"1220":2,"1222":1,"1223":2,"1225":3,"1226":1,"1227":1,"1228":1,"1229":26,"1231":6,"1232":1,"1233":4,"1234":1,"1235":3,"1236":1,"1237":54,"1239":3,"1241":3,"1242":3,"1243":1,"1244":5,"1247":48,"1248":1,"1249":1,"1251":2,"1252":3,"1253":7,"1254":1,"1255":2,"1256":5,"1257":1,"1258":5,"1259":73,"1260":1,"1261":2,"1262":1,"1263":5,"1265":7,"1267":6,"1270":103,"1271":33,"1272":1,"1273":3,"1274":6,"1275":8,"1276":2,"1277":2,"1278":1,"1279":1,"1280":4,"1282":3,"1283":2,"1284":1,"1285":270,"1287":3,"1288":5,"1289":2,"1291":1,"1292":1,"1293":4,"1294":3,"1295":1,"1296":4,"1297":2,"1299":188,"1300":40,"1301":3,"1302":1,"1303":3,"1304":2,"1305":2,"1306":2,"1307":1,"1308":52,"1309":14,"1310":155,"1311":13,"1312":2,"1315":1,"1316":3,"1317":6,"1319":7,"1321":1,"1322":151,"1323":244,"1325":2,"1326":1,"1327":4,"1329":2,"1330":1,"1331":3,"1332":1,"1333":2,"1334":1,"1335":1,"1336":6,"1337":2,"1338":118,"1339":1,"1340":1,"1341":2,"1342":3,"1343":1,"1344":1,"1346":4,"1347":2,"1348":1,"1349":115,"1351":5,"1352":7,"1354":4,"1355":6,"1356":2,"1357":2,"1358":426,"1360":1,"1361":2,"1366":1,"1368":37,"1369":1,"1370":2,"1371":1,"1372":1,"1373":1,"1375":2,"1376":2,"1377":2,"1378":2,"1379":92,"1380":1,"1382":1,"1383":5,"1384":2,"1386":2,"1387":1,"1388":1,"1389":1,"1390":4,"1391":5,"1392":1,"1394":235,"1395":290,"1396":1,"1397":2,"1398":2,"1399":3,"1400":2,"1401":89,"1403":2,"1404":1,"1405":4,"1406":4,"1407":3,"1408":2,"1409":2,"1410":2,"1412":1,"1414":143,"1415":46,"1416":96,"1417":1,"1418":1,"1420":1,"1421":1,"1422":3,"1424":5,"1425":1,"1426":26,"1427":1,"1428":2,"1429":1,"1430":2,"1431":2,"1432":5,"1434":10,"1435":423,"1437":32,"1439":5,"1440":12,"1441":7,"1442":6,"1443":4,"1444":9,"1445":5,"1446":4,"1449":1,"1452":6,"1453":13,"1454":28,"1455":6,"1456":3,"1457":1,"1458":9,"1459":4,"1460":2,"1461":1,"1462":2,"1464":9,"1465":1,"1466":53,"1468":1,"1469":2,"1470":1,"1472":4,"1473":6,"1474":6,"1475":3,"1476":1,"1477":3,"1480":2,"1485":4,"1487":10,"1488":13,"1489":6,"1490":2,"1491":18,"1492":1,"1495":24,"1497":1,"1499":1,"1503":1,"1505":1,"1506":2,"1507":2,"1508":1,"1511":1,"1512":1,"1525":11,"1529":1,"1530":3,"1533":1,"1536":1,"1544":1,"1553":1,"1556":2,"1564":1,"1569":2,"1576":10,"1580":1,"1581":4,"1584":1,"1588":1,"1590":13,"1595":3,"1597":1,"1598":3,"1599":2,"1601":1,"1607":1,"1610":3,"1626":5,"1629":2,"1630":12,"1631":8,"1634":2,"1636":1,"1637":5,"1638":11,"1639":8,"1640":14,"1645":1,"1647":2,"1650":6,"1651":3,"1652":5,"1653":1,"1655":1,"1660":1,"1661":3,"1662":1,"1663":2,"1664":1,"1666":1,"1667":2,"1668":1,"1669":3,"1670":3,"1672":2,"1673":1,"1676":3,"1678":1,"1680":1,"1683":1,"1684":1,"1685":4,"1688":1,"1691":1,"1692":2,"1694":1,"1696":1,"1698":2,"1700":3,"1701":4,"1704":3,"1706":2,"1707":3,"1716":5,"1718":1,"1721":1,"1726":6,"1728":1,"1729":1,"1731":1,"1734":2,"1735":1,"1736":1,"1741":1,"1747":4,"1748":7,"1749":1,"1750":2,"1755":1,"1758":1,"1760":1,"1764":13,"1765":1,"1766":8,"1768":2,"1771":1,"1775":1,"1779":2,"1781":2,"1782":2,"1783":1,"1784":2,"1789":2,"1790":2,"1794":2,"1795":1,"1799":1,"1801":1,"1804":7,"1805":1,"1809":8,"1814":3,"1819":1,"1820":3,"1822":1,"1824":2,"1826":2,"1828":1,"1832":2,"1833":3,"1836":1,"1837":1,"1838":3,"1839":1,"1840":1,"1841":4,"1843":1,"1844":2,"1845":2,"1846":1,"1847":1,"1848":3,"1849":1,"1850":1,"1852":4,"1853":1,"1857":1,"1860":2,"1861":1,"1865":1,"1867":1,"1868":4,"1869":4,"1871":2,"1872":1,"1873":4,"1874":3,"1875":3,"1876":2,"1877":1,"1882":2,"1883":3,"1887":1,"1888":2,"1890":1,"1892":3,"1896":1,"1898":3,"1901":2,"1902":1,"1904":1,"1905":4,"1906":1,"1907":5,"1908":7,"1909":3,"1910":2,"1911":5,"1913":1,"1914":1,"1915":4,"1917":2,"1918":1,"1919":3,"1921":1,"1922":5,"1923":2,"1924":1,"1925":2,"1926":7,"1927":3,"1928":2,"1929":2,"1933":4,"1934":2,"1935":1,"1940":1,"1941":1,"1942":2,"1943":2,"1944":2,"1946":2,"1947":8,"1950":2,"1951":1,"1952":1,"1953":1,"1954":2,"1955":1,"1959":1,"1961":4,"1962":1,"1963":1,"1967":1,"1971":1,"1973":2,"1974":1,"1975":2,"1976":1,"1977":1,"1979":1,"1980":2,"1981":3,"1982":4,"1983":2,"1986":1,"1987":4,"1988":2,"1991":1,"1992":1,"1993":4,"1994":11,"1995":1,"1996":1,"1998":1,"1999":2,"2000":4,"2001":1,"2006":1,"2007":2,"2008":1,"2009":2,"2010":1,"2011":2,"2012":1,"2013":1,"2016":1,"2017":1,"2018":1,"2019":3,"2022":1,"2023":1,"2024":1,"2025":2,"2027":2,"2028":2,"2029":2,"2030":1,"2031":2,"2033":1,"2035":1,"2037":1,"2040":1,"2041":2,"2042":3,"2043":5,"2044":1,"2045":3,"2046":6,"2047":1,"2048":2,"2052":1,"2054":2,"2055":1,"2056":1,"2060":3,"2061":1,"2062":1,"2064":1,"2065":1,"2066":2,"2068":1,"2069":3,"2070":1,"2072":1,"2073":1,"2074":4,"2076":2,"2077":2,"2079":2,"2080":7,"2081":2,"2082":5,"2083":4,"2085":3,"2088":2,"2090":1,"2091":1,"2099":2,"2100":1,"2102":1,"2104":1,"2106":1,"2109":1,"2110":9,"2111":2,"2112":3,"2114":5,"2115":2,"2116":1,"2117":2,"2119":2,"2121":2,"2122":1,"2123":1,"2124":1,"2125":3,"2130":5,"2131":2,"2133":3,"2134":1,"2136":1,"2137":1,"2138":1,"2140":1,"2141":2,"2143":3,"2145":1,"2146":1,"2148":1,"2149":1,"2150":2,"2153":3,"2154":3,"2155":5,"2156":5,"2157":8,"2158":1,"2159":1,"2160":1,"2163":3,"2164":1,"2165":2,"2166":6,"2167":3,"2168":3,"2169":1,"2171":2,"2175":1,"2176":3,"2180":1,"2181":1,"2182":1,"2183":1,"2184":1,"2185":3,"2187":1,"2188":2,"2189":4,"2191":5,"2192":3,"2195":2,"2197":1,"2198":1,"2201":6,"2209":1,"2228":1,"2229":3,"2231":8,"2239":2,"2240":1}}],["s",{"0":{"93":1,"95":1,"96":1,"138":1,"139":1,"1259":1,"1290":1,"1322":1,"1446":1,"1880":1,"1882":1,"1988":1,"2024":1,"2043":1,"2184":1},"1":{"1039":1,"1040":1,"1041":1,"1042":1,"1043":1,"1044":1,"1045":1,"1046":1},"2":{"1239":1,"1352":1,"1812":2},"3":{"0":269,"6":1,"7":2,"10":2,"13":1,"15":1,"20":2,"21":5,"22":1,"24":3,"25":1,"26":2,"27":6,"30":1,"31":7,"37":1,"38":2,"39":6,"40":2,"41":1,"42":4,"43":2,"46":2,"47":1,"49":1,"51":1,"52":2,"53":10,"54":2,"57":3,"59":5,"60":1,"62":5,"66":4,"68":1,"71":6,"72":6,"73":6,"74":1,"76":1,"77":1,"78":2,"79":1,"80":8,"81":4,"82":2,"91":7,"92":4,"93":6,"94":4,"95":9,"96":6,"97":3,"98":3,"99":6,"100":9,"101":3,"102":2,"103":4,"104":2,"109":9,"111":2,"115":3,"117":2,"121":2,"122":5,"123":2,"125":2,"126":2,"127":3,"128":1,"130":6,"131":1,"132":1,"134":1,"135":15,"136":8,"137":4,"138":2,"139":5,"140":3,"141":3,"142":5,"145":7,"147":5,"149":2,"150":2,"152":1,"157":3,"159":2,"160":5,"161":2,"162":1,"164":1,"165":1,"166":6,"168":1,"169":1,"170":2,"173":1,"174":2,"175":7,"177":1,"178":1,"179":4,"180":6,"184":3,"186":20,"187":2,"189":1,"190":1,"193":5,"194":1,"195":4,"196":1,"199":1,"200":2,"201":6,"202":2,"208":1,"209":2,"212":2,"215":1,"217":3,"218":4,"219":4,"225":6,"226":3,"228":3,"230":7,"231":2,"233":1,"234":1,"235":7,"237":5,"241":10,"242":1,"243":9,"245":4,"246":3,"247":5,"248":1,"249":2,"250":2,"251":3,"252":2,"253":1,"254":5,"255":10,"256":7,"257":5,"258":4,"259":9,"260":2,"261":4,"264":1,"265":15,"267":2,"268":1,"273":3,"274":1,"275":3,"276":1,"279":3,"280":1,"282":3,"284":2,"286":1,"290":4,"291":3,"292":2,"293":1,"295":3,"296":1,"298":2,"300":1,"301":1,"302":1,"303":1,"305":4,"306":1,"309":1,"310":6,"311":1,"313":1,"314":3,"317":2,"318":17,"320":3,"322":1,"324":3,"325":4,"326":6,"330":1,"332":1,"333":4,"340":1,"341":13,"342":2,"343":2,"344":2,"345":1,"348":5,"349":3,"350":15,"351":3,"352":6,"353":8,"354":1,"355":1,"359":3,"361":3,"364":1,"365":2,"366":1,"369":1,"370":1,"373":7,"374":1,"375":1,"382":1,"383":2,"386":8,"387":5,"388":2,"389":2,"390":7,"391":5,"392":8,"393":2,"396":1,"397":5,"400":5,"401":3,"402":7,"404":2,"407":4,"408":1,"409":2,"411":1,"412":2,"413":3,"414":2,"415":2,"418":4,"420":6,"421":1,"422":2,"423":1,"426":2,"427":2,"428":3,"429":3,"430":2,"434":3,"435":2,"441":2,"447":1,"448":11,"450":2,"451":3,"452":4,"453":1,"454":4,"455":1,"458":3,"459":12,"460":2,"461":2,"462":1,"463":5,"464":2,"465":7,"466":3,"468":7,"469":2,"472":1,"473":1,"474":2,"475":2,"476":4,"477":4,"478":1,"481":3,"483":1,"484":1,"487":1,"488":1,"489":2,"490":5,"491":1,"492":1,"493":1,"494":1,"497":8,"498":1,"499":9,"500":1,"501":2,"502":3,"503":1,"506":2,"507":3,"509":4,"510":1,"511":1,"512":1,"513":1,"517":3,"518":1,"522":1,"524":2,"527":1,"528":3,"530":5,"532":1,"534":5,"535":1,"536":10,"537":3,"538":2,"539":4,"540":4,"541":1,"543":5,"545":12,"548":1,"549":8,"550":4,"551":6,"554":1,"556":14,"557":3,"558":2,"562":2,"564":7,"565":1,"566":3,"568":1,"572":15,"573":3,"575":3,"576":1,"577":3,"578":3,"579":1,"582":1,"583":12,"584":2,"585":6,"586":1,"587":3,"590":1,"591":5,"592":1,"593":1,"598":3,"599":5,"601":3,"602":3,"603":3,"604":1,"607":19,"609":22,"610":2,"611":3,"612":2,"613":1,"614":2,"616":1,"617":4,"618":11,"619":3,"620":1,"622":1,"624":2,"626":10,"627":1,"629":4,"631":3,"632":1,"633":20,"634":2,"635":8,"636":3,"638":2,"640":13,"642":3,"643":1,"644":4,"649":14,"650":1,"651":8,"655":5,"656":1,"657":7,"658":4,"659":1,"660":1,"663":1,"664":1,"665":4,"666":1,"667":1,"668":1,"674":17,"675":1,"676":1,"677":4,"681":3,"682":7,"683":5,"684":6,"685":1,"688":1,"689":3,"690":4,"691":1,"692":1,"693":1,"694":1,"696":1,"697":1,"698":8,"699":2,"700":6,"702":2,"703":1,"706":1,"707":2,"708":3,"709":3,"711":1,"714":4,"715":7,"716":4,"717":5,"718":3,"719":1,"720":2,"724":1,"725":11,"726":4,"727":6,"729":1,"731":3,"732":2,"733":11,"734":3,"735":6,"736":2,"737":1,"739":2,"740":2,"741":6,"742":1,"743":4,"744":1,"745":1,"749":7,"750":2,"751":4,"755":2,"757":5,"758":1,"759":1,"760":3,"761":1,"764":8,"766":13,"767":2,"768":1,"769":3,"774":2,"775":1,"776":2,"780":6,"781":1,"782":9,"783":1,"784":3,"785":1,"789":1,"790":5,"791":3,"792":2,"793":1,"798":2,"799":4,"800":1,"802":1,"803":7,"804":1,"805":1,"808":2,"809":5,"810":8,"811":2,"812":3,"813":3,"814":1,"815":1,"817":3,"818":3,"819":4,"820":1,"821":1,"822":1,"825":8,"826":7,"827":28,"828":1,"829":7,"830":2,"831":5,"832":9,"833":6,"834":1,"836":1,"837":6,"838":20,"839":3,"840":7,"841":2,"842":1,"844":1,"845":1,"846":4,"847":3,"848":3,"849":3,"850":1,"853":3,"854":6,"855":1,"857":3,"860":2,"861":6,"862":1,"863":7,"864":4,"867":3,"868":2,"870":2,"873":4,"876":9,"877":2,"879":1,"880":1,"881":16,"882":1,"884":3,"889":4,"891":5,"892":3,"895":1,"896":1,"897":14,"898":1,"900":2,"901":1,"903":1,"904":5,"905":26,"906":7,"907":7,"908":1,"909":1,"910":2,"912":2,"913":4,"914":9,"916":6,"917":2,"918":3,"920":3,"921":4,"922":10,"923":2,"924":5,"925":1,"926":10,"927":1,"931":1,"932":1,"934":1,"944":1,"945":1,"946":3,"947":1,"949":1,"952":1,"953":1,"954":14,"956":4,"958":2,"959":2,"960":3,"962":1,"964":6,"965":3,"966":1,"971":4,"972":9,"973":4,"974":3,"979":4,"980":2,"981":1,"982":1,"983":2,"986":3,"987":1,"988":13,"990":3,"991":1,"992":1,"994":4,"995":4,"996":8,"997":1,"998":5,"999":2,"1003":1,"1004":6,"1005":2,"1006":2,"1007":1,"1010":1,"1011":1,"1012":4,"1013":1,"1014":2,"1016":1,"1017":2,"1018":22,"1019":4,"1020":1,"1021":2,"1022":1,"1024":2,"1025":1,"1026":9,"1028":5,"1029":1,"1030":1,"1033":4,"1034":9,"1035":2,"1036":3,"1037":2,"1038":1,"1039":1,"1040":1,"1042":12,"1043":3,"1044":5,"1045":1,"1048":3,"1049":5,"1050":8,"1051":5,"1052":5,"1054":3,"1055":3,"1057":1,"1058":7,"1059":9,"1060":2,"1061":3,"1063":1,"1066":3,"1067":15,"1068":2,"1069":9,"1070":3,"1073":3,"1074":9,"1075":5,"1076":1,"1077":1,"1078":1,"1079":1,"1081":1,"1082":4,"1083":1,"1085":2,"1086":2,"1089":3,"1090":2,"1091":14,"1092":3,"1093":8,"1094":2,"1099":3,"1100":1,"1103":2,"1104":1,"1108":1,"1114":1,"1115":1,"1116":18,"1117":5,"1118":3,"1119":2,"1122":1,"1123":3,"1124":17,"1125":5,"1126":2,"1127":3,"1128":1,"1133":7,"1135":3,"1140":2,"1142":2,"1144":2,"1147":1,"1149":1,"1150":5,"1151":1,"1155":1,"1156":1,"1157":1,"1160":2,"1161":4,"1162":1,"1168":3,"1170":1,"1175":5,"1176":4,"1178":2,"1182":1,"1184":11,"1186":1,"1187":1,"1191":1,"1192":2,"1193":3,"1194":2,"1196":3,"1201":1,"1202":2,"1203":2,"1211":1,"1212":40,"1213":3,"1214":6,"1215":1,"1216":3,"1218":1,"1219":1,"1221":1,"1223":2,"1224":1,"1226":1,"1227":1,"1228":3,"1229":56,"1230":4,"1231":4,"1232":2,"1233":4,"1235":2,"1236":5,"1237":145,"1238":2,"1239":10,"1240":3,"1241":5,"1242":2,"1243":2,"1244":5,"1246":3,"1247":211,"1248":1,"1249":5,"1251":4,"1252":2,"1253":7,"1254":1,"1256":2,"1257":1,"1258":8,"1259":213,"1260":3,"1262":8,"1263":9,"1264":11,"1265":18,"1266":3,"1267":7,"1268":3,"1269":4,"1270":355,"1271":90,"1272":1,"1273":10,"1274":1,"1275":6,"1276":10,"1277":5,"1278":15,"1279":4,"1280":7,"1281":4,"1282":6,"1283":2,"1284":3,"1285":884,"1286":2,"1287":1,"1288":6,"1289":16,"1290":1,"1291":2,"1292":5,"1293":4,"1294":4,"1295":3,"1296":10,"1297":5,"1299":711,"1300":75,"1301":3,"1302":1,"1303":8,"1304":2,"1305":1,"1306":8,"1307":3,"1308":289,"1309":70,"1310":507,"1311":63,"1313":5,"1314":8,"1315":12,"1316":3,"1317":8,"1318":4,"1319":6,"1320":1,"1321":7,"1322":481,"1323":911,"1324":1,"1325":8,"1326":5,"1327":7,"1328":1,"1329":4,"1330":3,"1331":5,"1332":10,"1333":4,"1334":1,"1335":5,"1336":4,"1337":8,"1338":430,"1339":3,"1340":5,"1341":10,"1342":2,"1343":3,"1344":2,"1345":3,"1346":6,"1347":9,"1348":5,"1349":479,"1350":4,"1351":13,"1352":12,"1353":6,"1354":12,"1355":3,"1356":13,"1357":6,"1358":1747,"1359":5,"1361":9,"1362":5,"1363":5,"1365":13,"1366":5,"1367":7,"1368":148,"1370":5,"1371":2,"1372":4,"1373":3,"1374":6,"1375":3,"1376":7,"1377":12,"1378":7,"1379":317,"1380":3,"1381":5,"1382":7,"1383":14,"1384":6,"1385":2,"1386":3,"1387":5,"1388":13,"1389":3,"1390":6,"1391":4,"1392":1,"1393":1,"1394":907,"1395":996,"1396":2,"1397":4,"1398":7,"1399":5,"1400":7,"1401":446,"1403":1,"1404":4,"1405":4,"1406":3,"1408":1,"1409":5,"1410":2,"1411":4,"1412":1,"1413":4,"1414":630,"1415":134,"1416":392,"1417":3,"1419":3,"1420":4,"1421":6,"1422":6,"1423":8,"1424":3,"1425":4,"1426":162,"1427":1,"1428":7,"1429":3,"1430":20,"1431":2,"1432":6,"1433":6,"1434":14,"1435":1772,"1436":27,"1437":83,"1438":1,"1440":31,"1441":7,"1442":37,"1443":4,"1444":8,"1445":8,"1446":22,"1448":3,"1449":5,"1450":6,"1451":2,"1452":7,"1453":9,"1454":44,"1455":17,"1456":4,"1457":4,"1458":11,"1459":11,"1460":8,"1462":1,"1464":10,"1465":4,"1466":77,"1468":1,"1469":4,"1470":3,"1471":2,"1472":6,"1473":5,"1474":12,"1475":21,"1476":1,"1477":1,"1478":2,"1479":1,"1480":3,"1482":1,"1483":2,"1485":9,"1486":4,"1487":30,"1488":30,"1489":16,"1490":9,"1491":30,"1493":1,"1495":75,"1496":2,"1497":3,"1499":5,"1506":1,"1508":1,"1511":2,"1512":2,"1514":1,"1516":1,"1517":1,"1521":1,"1522":4,"1523":5,"1524":3,"1525":42,"1526":2,"1527":2,"1528":1,"1530":9,"1531":2,"1533":14,"1535":1,"1536":1,"1540":1,"1541":1,"1542":1,"1545":2,"1546":1,"1552":3,"1557":1,"1558":1,"1564":1,"1566":2,"1568":2,"1570":1,"1572":1,"1574":6,"1575":1,"1576":41,"1577":2,"1578":2,"1581":10,"1582":1,"1584":8,"1585":2,"1586":1,"1587":1,"1588":5,"1590":42,"1591":1,"1594":3,"1595":21,"1596":3,"1597":2,"1598":8,"1599":7,"1606":2,"1609":1,"1610":2,"1614":1,"1620":2,"1624":1,"1625":2,"1626":6,"1628":1,"1629":23,"1630":31,"1631":16,"1634":5,"1635":1,"1636":3,"1637":6,"1638":20,"1639":10,"1640":22,"1643":1,"1644":1,"1649":3,"1650":13,"1651":1,"1652":10,"1653":1,"1655":7,"1657":1,"1660":3,"1661":3,"1662":7,"1663":3,"1664":4,"1665":6,"1666":3,"1667":3,"1668":5,"1669":1,"1670":4,"1673":2,"1674":2,"1675":1,"1676":4,"1677":2,"1678":2,"1679":2,"1682":3,"1683":6,"1684":17,"1685":9,"1686":2,"1687":3,"1688":2,"1689":1,"1692":1,"1694":1,"1696":2,"1698":1,"1699":1,"1700":6,"1701":6,"1702":3,"1705":1,"1706":5,"1707":4,"1708":4,"1709":1,"1717":2,"1718":5,"1719":2,"1721":2,"1722":1,"1723":4,"1725":2,"1726":20,"1727":7,"1728":7,"1729":5,"1730":4,"1740":3,"1741":1,"1743":1,"1747":8,"1748":8,"1749":1,"1750":2,"1755":1,"1758":3,"1759":1,"1760":2,"1763":8,"1764":22,"1766":8,"1767":1,"1768":10,"1769":3,"1770":2,"1771":1,"1772":1,"1777":1,"1778":3,"1780":1,"1781":1,"1783":1,"1788":1,"1789":4,"1791":1,"1794":1,"1796":1,"1798":2,"1799":1,"1801":2,"1804":1,"1809":6,"1810":2,"1812":5,"1814":11,"1816":2,"1820":4,"1822":2,"1823":4,"1824":3,"1825":1,"1826":1,"1828":3,"1829":2,"1830":2,"1831":2,"1832":1,"1833":2,"1838":5,"1839":6,"1840":2,"1842":5,"1843":2,"1847":2,"1848":7,"1850":8,"1851":2,"1852":1,"1854":2,"1855":5,"1856":2,"1858":3,"1859":1,"1860":2,"1861":1,"1864":3,"1865":4,"1868":2,"1870":4,"1871":5,"1873":3,"1874":5,"1875":6,"1876":4,"1877":1,"1878":2,"1879":1,"1880":5,"1881":4,"1882":1,"1883":3,"1884":1,"1885":1,"1888":2,"1890":5,"1891":1,"1893":1,"1894":4,"1895":6,"1896":4,"1897":6,"1898":5,"1902":1,"1904":1,"1905":1,"1907":4,"1908":4,"1909":4,"1910":2,"1911":3,"1913":1,"1914":1,"1918":1,"1919":11,"1920":1,"1921":17,"1922":13,"1923":1,"1924":1,"1926":7,"1927":1,"1928":5,"1929":1,"1933":5,"1934":2,"1935":1,"1936":3,"1937":1,"1939":1,"1940":1,"1943":2,"1944":2,"1945":5,"1946":5,"1947":1,"1948":3,"1949":4,"1951":1,"1952":1,"1953":3,"1954":1,"1955":2,"1956":1,"1957":3,"1960":1,"1961":4,"1962":3,"1963":2,"1964":1,"1965":1,"1966":1,"1967":2,"1969":3,"1970":1,"1971":2,"1972":3,"1973":3,"1974":1,"1975":2,"1976":4,"1977":8,"1978":1,"1980":1,"1981":6,"1982":5,"1984":3,"1986":1,"1988":3,"1989":1,"1990":2,"1992":3,"1993":6,"1994":9,"1995":2,"1996":3,"1997":1,"1999":14,"2000":4,"2001":3,"2003":1,"2005":7,"2007":5,"2008":4,"2009":8,"2011":4,"2012":1,"2013":1,"2014":1,"2017":1,"2018":4,"2020":2,"2022":2,"2023":8,"2024":4,"2026":2,"2027":1,"2029":1,"2030":1,"2031":3,"2032":1,"2033":3,"2034":1,"2035":1,"2036":1,"2037":2,"2040":2,"2041":5,"2042":9,"2043":3,"2045":4,"2046":3,"2047":1,"2049":1,"2051":1,"2052":2,"2054":7,"2055":3,"2056":4,"2058":2,"2059":5,"2060":1,"2061":2,"2062":5,"2063":1,"2064":1,"2065":3,"2067":1,"2068":1,"2069":1,"2071":3,"2072":1,"2073":3,"2074":4,"2075":4,"2076":5,"2077":2,"2078":2,"2079":1,"2081":2,"2082":3,"2084":4,"2085":1,"2086":1,"2087":1,"2088":3,"2089":1,"2090":1,"2092":1,"2093":2,"2094":7,"2095":1,"2096":6,"2098":1,"2100":2,"2102":3,"2103":3,"2104":4,"2105":2,"2106":2,"2107":1,"2108":3,"2111":1,"2112":3,"2113":1,"2114":2,"2117":3,"2119":2,"2120":1,"2121":1,"2125":4,"2126":2,"2127":2,"2128":1,"2130":14,"2131":3,"2132":1,"2135":2,"2136":2,"2137":6,"2140":1,"2141":3,"2142":2,"2143":1,"2145":1,"2147":1,"2149":6,"2150":2,"2152":2,"2153":2,"2155":5,"2156":2,"2157":8,"2159":3,"2160":3,"2162":3,"2163":2,"2164":3,"2165":1,"2166":9,"2167":7,"2168":3,"2169":2,"2170":1,"2171":2,"2173":2,"2175":2,"2176":2,"2177":4,"2178":3,"2179":6,"2184":6,"2185":1,"2186":1,"2187":3,"2188":2,"2189":4,"2191":7,"2192":6,"2193":2,"2194":3,"2196":2,"2197":2,"2199":1,"2201":5,"2202":2,"2214":1,"2215":1,"2219":3,"2221":1,"2227":1,"2229":1,"2231":17,"2232":1,"2235":1,"2238":2,"2240":1,"2243":1}}],["sunday",{"3":{"1459":1,"1525":1}}],["sundays",{"3":{"1458":1,"1590":1}}],["suffers",{"3":{"1323":1}}],["suffer",{"3":{"1243":1,"1247":1}}],["suffixing",{"3":{"1323":1}}],["suffixes",{"3":{"1323":1,"1435":7}}],["suffixed",{"3":{"1270":2,"1299":1,"1308":1,"1332":1,"1358":1,"1428":1,"1435":2,"1495":1,"2127":1}}],["suffixredactor",{"3":{"1199":2,"1207":1}}],["suffix",{"2":{"1006":1},"3":{"123":1,"246":1,"343":2,"441":2,"443":2,"444":1,"576":1,"607":1,"609":1,"611":1,"629":1,"868":1,"870":1,"873":1,"975":1,"1006":2,"1267":2,"1270":1,"1299":2,"1308":2,"1310":2,"1323":4,"1338":1,"1349":1,"1394":3,"1395":1,"1410":1,"1414":4,"1415":1,"1435":13,"1443":1,"1464":1,"1466":3,"1474":2,"1475":3,"1534":1,"1590":1,"1650":2,"1684":1,"1685":1,"1732":1,"1919":2,"1922":1,"1923":2,"2016":1,"2126":2,"2157":2,"2159":1}}],["sufficiency",{"3":{"1299":1,"1748":1,"1764":2}}],["sufficient",{"3":{"100":1,"810":1,"813":1,"815":1,"856":1,"1299":1,"1308":1,"1310":1,"1323":2,"1349":1,"1379":2,"1394":2,"1401":1,"1414":2,"1416":1,"1456":1,"1471":1,"1489":1,"1495":1,"1630":1,"1638":1,"1748":1,"1764":1,"1766":2,"1890":1}}],["sufficed",{"3":{"1435":1}}],["suffices",{"3":{"30":1,"1273":1,"1285":1,"1358":3}}],["suffice",{"3":{"0":1,"1212":1}}],["sustain",{"3":{"1457":1}}],["sustained",{"3":{"0":1,"70":1,"403":1,"612":1,"625":1,"1102":1,"1310":2,"1323":2,"1395":1,"1399":1,"1533":1,"1948":1,"2165":1}}],["suspenders",{"3":{"1453":1}}],["suspend",{"3":{"1395":1}}],["suspected",{"3":{"1322":1}}],["suspicious",{"3":{"801":1,"1414":1}}],["suggest",{"3":{"1340":1,"1435":1}}],["suggested",{"0":{"1194":1},"3":{"1075":1,"1285":1,"1708":1}}],["suggests",{"3":{"640":1,"768":1,"981":1,"987":1,"1252":1,"1270":1,"1466":1,"1726":1}}],["suggestions",{"3":{"1310":1,"1698":1,"1726":1}}],["suggestion",{"2":{"2088":1},"3":{"633":1,"1092":1,"1418":1,"1426":1,"1576":1,"1580":1,"1581":1,"1583":1,"1823":1,"1825":1,"2056":1,"2057":1,"2088":1,"2196":1}}],["sugar",{"3":{"0":1,"359":1,"655":1,"657":2,"660":1,"1168":1,"1259":1,"1285":2,"1760":1,"2141":1}}],["sums",{"3":{"1018":1,"1259":2,"1285":2,"1395":4}}],["sumselector",{"3":{"551":1,"1285":2}}],["summing",{"3":{"1285":2,"1395":1}}],["summer",{"3":{"1028":1,"1466":1,"1477":1}}],["summed",{"3":{"688":1,"1259":3,"1285":2,"1310":1,"1395":7,"1401":2}}],["summarizer",{"3":{"1434":1}}],["summarizes",{"3":{"1368":1,"1685":1}}],["summarize",{"3":{"1358":1,"2029":1}}],["summarized",{"3":{"369":1,"2229":1}}],["summaries",{"3":{"454":3,"1212":1,"1460":1,"1673":1,"1684":2}}],["summarypath",{"3":{"1474":1}}],["summary",{"0":{"1447":1,"1459":1,"1467":1,"1482":1,"1637":1,"1745":1,"2217":1},"3":{"0":2,"313":1,"376":1,"626":1,"627":1,"628":1,"642":1,"751":1,"757":1,"759":1,"766":3,"905":1,"946":1,"950":1,"972":1,"1006":2,"1237":2,"1259":1,"1263":1,"1270":2,"1277":1,"1285":3,"1299":6,"1308":1,"1310":3,"1311":1,"1322":2,"1323":5,"1338":2,"1349":7,"1358":28,"1379":1,"1394":13,"1395":10,"1401":5,"1406":1,"1413":1,"1414":4,"1415":11,"1416":7,"1430":1,"1432":1,"1435":14,"1437":3,"1451":1,"1454":5,"1456":1,"1458":1,"1460":1,"1473":1,"1474":4,"1487":2,"1488":1,"1491":1,"1525":3,"1560":1,"1606":1,"1607":1,"1631":1,"1638":3,"1668":1,"1670":1,"1684":1,"1685":1,"1700":1,"1728":1,"1740":1,"1741":1,"1747":2,"1749":1,"1759":1,"1764":2,"1766":2,"1768":1,"1956":1,"1957":1,"1994":4}}],["sumbyasync",{"3":{"543":1,"545":1,"551":1,"690":1,"692":1,"1285":9,"1395":4,"1814":1}}],["sum",{"3":{"0":1,"551":1,"592":1,"690":1,"692":2,"1011":1,"1012":3,"1259":1,"1270":1,"1285":4,"1338":1,"1358":1,"1365":1,"1394":1,"1395":11,"1401":1,"1454":1,"1466":2,"1486":1,"1491":1,"1495":7,"1497":1,"1685":1,"1763":2,"1797":2,"2046":1}}],["suiting",{"3":{"1338":1}}],["suitable",{"3":{"1322":2}}],["suit",{"3":{"165":1,"1308":1}}],["suits",{"3":{"0":1,"165":1,"650":1,"1270":1,"1285":1,"1299":1}}],["suite",{"0":{"2104":1},"2":{"2046":1},"3":{"0":4,"59":1,"132":1,"133":1,"135":3,"136":1,"137":4,"140":2,"143":1,"162":1,"218":1,"361":2,"546":1,"555":1,"556":1,"558":2,"571":1,"572":8,"573":1,"574":3,"575":2,"577":1,"591":6,"593":1,"616":2,"618":2,"621":1,"777":1,"838":1,"840":1,"859":1,"860":1,"861":2,"862":2,"863":1,"865":2,"906":1,"972":1,"973":1,"974":1,"1012":1,"1017":1,"1018":4,"1022":1,"1094":1,"1117":1,"1168":2,"1192":1,"1212":3,"1213":1,"1241":1,"1247":2,"1271":1,"1285":8,"1299":1,"1322":4,"1323":6,"1325":2,"1338":4,"1358":3,"1365":2,"1367":1,"1368":6,"1395":2,"1399":1,"1401":1,"1408":1,"1413":1,"1414":4,"1425":1,"1426":1,"1427":1,"1432":2,"1433":1,"1434":1,"1435":116,"1436":5,"1437":20,"1440":5,"1443":3,"1448":1,"1454":1,"1455":12,"1459":1,"1460":2,"1485":5,"1486":6,"1487":4,"1488":2,"1489":5,"1491":7,"1495":1,"1496":1,"1523":1,"1525":3,"1533":2,"1534":1,"1552":1,"1571":1,"1588":2,"1589":1,"1590":4,"1595":2,"1611":1,"1684":2,"1685":3,"1688":1,"1738":1,"1784":1,"1897":1,"1960":1,"2015":1,"2040":1,"2046":3,"2047":1,"2049":4,"2050":1,"2054":1,"2057":3,"2058":2,"2059":2,"2074":1,"2078":3,"2100":1,"2104":2,"2105":2,"2106":2,"2107":3,"2111":1,"2170":1,"2179":1,"2219":1,"2231":1}}],["suites",{"0":{"1437":1},"1":{"569":1,"570":1,"571":1,"572":1,"573":1,"574":1,"575":1,"576":1,"577":1,"578":1,"579":1},"3":{"0":2,"124":1,"143":1,"569":1,"572":3,"573":1,"574":6,"576":1,"577":1,"578":1,"622":1,"650":1,"773":1,"837":1,"841":1,"863":1,"953":2,"1212":1,"1247":1,"1270":1,"1285":1,"1322":1,"1358":1,"1394":1,"1414":1,"1427":2,"1428":1,"1431":1,"1432":1,"1434":2,"1435":25,"1437":2,"1459":1,"1484":2,"1486":3,"1487":2,"1488":1,"1491":1,"1493":1,"1495":7,"1499":1,"1525":1,"1659":2,"1660":1,"1667":1,"1670":3,"1683":1,"1684":1,"1689":1,"1897":1,"1922":1,"2040":1,"2054":2,"2231":1}}],["succeeding",{"3":{"350":1,"881":1,"906":1,"1285":1,"1299":1,"1323":2,"1394":1,"1435":1,"1766":1,"1913":1,"1976":1}}],["succeeded",{"3":{"243":1,"538":1,"707":1,"741":1,"791":1,"881":1,"896":1,"1229":1,"1247":3,"1259":1,"1265":2,"1270":2,"1278":1,"1299":1,"1303":1,"1310":4,"1322":6,"1323":5,"1342":1,"1349":4,"1358":6,"1379":1,"1388":1,"1394":4,"1395":2,"1401":2,"1409":1,"1414":4,"1416":1,"1432":1,"1435":3,"1437":2,"1466":1,"1487":1,"1489":1,"1491":1,"1524":1,"1590":2,"1598":1,"1839":1,"1844":1,"1851":1,"1907":1,"1909":1,"1921":1,"2155":1,"2163":1,"2167":1}}],["succeed",{"3":{"22":1,"109":1,"195":1,"196":1,"342":1,"415":1,"537":1,"854":1,"857":1,"996":1,"1258":1,"1259":2,"1265":1,"1271":1,"1285":2,"1299":3,"1322":3,"1323":2,"1358":3,"1414":1,"1416":1,"1435":3,"1453":1,"1454":1,"1667":1,"1764":1,"1905":1,"2036":1,"2121":1,"2125":2}}],["succeeds",{"3":{"0":4,"31":1,"53":1,"186":1,"234":1,"239":1,"255":1,"318":1,"511":1,"518":1,"549":1,"848":1,"856":1,"988":1,"1018":1,"1212":1,"1237":1,"1247":1,"1252":1,"1255":1,"1285":2,"1293":1,"1299":10,"1310":2,"1321":1,"1322":6,"1323":2,"1333":1,"1338":4,"1349":2,"1351":1,"1358":3,"1366":1,"1394":3,"1395":6,"1401":1,"1409":1,"1414":10,"1416":2,"1434":1,"1435":4,"1437":2,"1442":1,"1455":1,"1471":1,"1487":1,"1491":1,"1630":2,"1648":1,"1656":2,"1700":1,"1747":2,"1748":1,"1754":1,"1775":1,"1834":1,"1839":1,"1844":1,"1881":1,"1922":1,"1943":2,"1946":1,"1961":1,"1991":1,"2006":1,"2164":1}}],["successalert",{"3":{"1435":2}}],["successoutcome",{"3":{"1426":2}}],["successorevent",{"3":{"1199":3,"1207":1}}],["successors",{"3":{"905":1,"1255":1,"1285":1,"1299":1,"1435":3}}],["successor",{"3":{"0":4,"499":1,"846":1,"903":1,"905":3,"906":2,"1252":1,"1258":1,"1259":2,"1270":2,"1285":4,"1289":1,"1299":12,"1435":9,"1437":4,"1495":1,"1639":1,"1666":1,"1925":1,"1981":4,"1983":1,"1984":1}}],["successive",{"3":{"1395":1}}],["successcontrasttext",{"3":{"1323":1}}],["successfully",{"3":{"195":1,"265":1,"527":1,"539":1,"585":1,"818":1,"1259":3,"1270":1,"1299":2,"1323":4,"1358":3,"1394":1,"1435":1,"1454":1,"1638":1,"1640":2,"1652":1,"1676":1,"1842":1}}],["successful",{"2":{"1466":1},"3":{"0":5,"20":1,"21":1,"24":1,"109":1,"120":1,"234":1,"243":1,"258":1,"265":1,"281":1,"312":1,"498":1,"499":2,"500":1,"591":2,"626":1,"725":1,"727":1,"732":1,"733":1,"756":1,"757":1,"758":1,"854":1,"857":1,"881":1,"908":1,"922":1,"988":2,"991":1,"1020":1,"1140":1,"1183":1,"1184":1,"1212":2,"1217":1,"1220":2,"1227":1,"1229":2,"1233":1,"1237":2,"1247":1,"1259":3,"1263":1,"1265":1,"1270":7,"1274":1,"1285":5,"1287":1,"1288":1,"1293":1,"1299":14,"1300":1,"1308":2,"1310":5,"1317":1,"1322":7,"1323":10,"1349":1,"1357":1,"1358":26,"1368":1,"1379":3,"1390":1,"1394":20,"1395":6,"1397":1,"1401":3,"1406":1,"1414":8,"1416":1,"1435":2,"1437":2,"1454":2,"1455":1,"1459":1,"1462":1,"1466":1,"1473":2,"1474":4,"1487":2,"1489":1,"1525":2,"1576":1,"1588":1,"1590":3,"1595":2,"1597":1,"1626":2,"1640":3,"1650":2,"1708":2,"1766":1,"1773":1,"1894":1,"1897":1,"1910":1,"1967":1,"1981":1,"2033":1,"2036":1}}],["successes",{"3":{"0":1,"243":1,"258":1,"1285":1,"1323":1,"1437":1,"1576":1}}],["success",{"0":{"1917":1},"2":{"620":1,"855":1,"1099":1,"1221":1,"2189":1},"3":{"0":5,"109":4,"157":1,"178":1,"196":1,"225":1,"226":1,"230":1,"265":1,"312":1,"350":1,"423":1,"434":1,"435":1,"461":1,"537":1,"539":1,"583":1,"585":1,"620":1,"626":6,"640":1,"737":1,"756":2,"758":2,"766":1,"790":3,"854":2,"855":1,"861":1,"881":1,"897":1,"905":4,"922":1,"926":4,"988":1,"989":1,"996":2,"1018":2,"1075":1,"1089":1,"1099":1,"1103":1,"1168":1,"1212":2,"1217":1,"1219":2,"1220":6,"1221":5,"1223":3,"1227":1,"1229":31,"1236":1,"1237":7,"1247":1,"1252":1,"1256":2,"1259":5,"1264":1,"1265":3,"1270":18,"1271":2,"1285":11,"1288":1,"1289":1,"1291":1,"1293":1,"1299":32,"1303":1,"1308":13,"1310":22,"1317":1,"1321":1,"1322":43,"1323":55,"1338":7,"1345":1,"1349":24,"1357":2,"1358":96,"1364":1,"1365":1,"1368":2,"1378":1,"1379":12,"1388":1,"1394":75,"1395":52,"1401":21,"1406":1,"1407":1,"1414":38,"1415":2,"1416":15,"1424":2,"1426":2,"1432":5,"1435":28,"1437":5,"1444":3,"1454":18,"1455":1,"1459":1,"1466":2,"1472":1,"1473":1,"1474":3,"1487":4,"1491":9,"1525":7,"1560":1,"1576":2,"1588":1,"1590":6,"1595":2,"1640":1,"1650":3,"1652":1,"1685":2,"1701":2,"1708":1,"1748":1,"1769":1,"1804":10,"1820":1,"1821":2,"1895":1,"1897":1,"1902":1,"1910":1,"1911":1,"1922":1,"1923":2,"1929":1,"1932":3,"1939":1,"1974":1,"1975":1,"1981":1,"2000":1,"2044":1,"2045":1,"2062":1,"2066":1,"2091":1,"2095":2,"2155":1,"2162":1,"2164":1,"2168":2,"2176":1,"2188":1,"2189":2,"2195":1}}],["such",{"3":{"0":4,"57":1,"81":2,"145":1,"157":1,"175":1,"185":1,"193":1,"215":1,"234":1,"235":1,"249":1,"290":1,"296":1,"341":1,"352":1,"363":1,"369":1,"390":1,"391":1,"427":1,"428":1,"491":1,"509":1,"536":1,"538":1,"549":1,"605":1,"657":1,"698":1,"707":1,"720":1,"743":3,"767":1,"776":1,"782":1,"799":1,"801":1,"803":1,"868":1,"883":2,"889":1,"930":2,"948":1,"966":1,"980":2,"995":1,"1034":1,"1042":2,"1108":1,"1115":1,"1168":1,"1184":1,"1188":1,"1233":1,"1237":4,"1244":1,"1247":3,"1249":1,"1252":1,"1256":1,"1259":5,"1270":2,"1271":2,"1275":1,"1285":20,"1296":1,"1299":20,"1300":1,"1308":2,"1310":15,"1311":1,"1322":11,"1323":13,"1338":4,"1341":1,"1346":1,"1347":1,"1349":7,"1352":1,"1358":28,"1368":1,"1371":1,"1379":5,"1383":1,"1394":10,"1395":9,"1401":6,"1405":1,"1414":8,"1415":2,"1416":7,"1426":1,"1427":1,"1430":4,"1435":23,"1452":1,"1454":1,"1499":1,"1533":1,"1568":1,"1584":1,"1652":1,"1807":1,"1882":1,"1907":1,"1908":1,"1921":1,"1947":1,"2011":1,"2106":1,"2163":1,"2167":1,"2187":2,"2198":1}}],["superar",{"3":{"1684":2,"1685":2}}],["supervisedusercircle",{"3":{"1414":1}}],["supertype",{"3":{"1338":1}}],["superficial",{"3":{"1140":1}}],["supersession",{"3":{"1323":5,"1477":1}}],["supersets",{"3":{"1247":1}}],["superset",{"3":{"448":1,"564":1,"1270":1,"1308":1,"1310":4,"1435":1,"2130":1}}],["supersede",{"3":{"607":1,"610":1,"1394":3,"1437":1,"1455":1,"1891":1}}],["supersedes",{"1":{"262":1,"263":1,"264":1,"265":1,"266":1,"267":1,"268":1,"269":1},"3":{"0":3,"15":1,"22":1,"45":1,"142":1,"145":2,"262":1,"263":1,"313":1,"326":1,"390":1,"724":1,"739":1,"743":1,"792":1,"903":1,"944":1,"1098":1,"1120":1,"1212":4,"1285":1,"1286":1,"1299":3,"1323":3,"1394":2,"1435":1,"1437":1,"1452":1,"1454":1,"1458":1,"1495":1,"1526":1,"1577":1,"1590":1,"1591":1,"1638":1,"1736":1,"1783":1,"1798":1,"1946":1,"2080":1,"2084":1,"2231":4}}],["superseded",{"0":{"137":1},"3":{"0":8,"1":1,"27":1,"84":1,"91":2,"92":1,"109":1,"117":1,"122":1,"130":1,"136":3,"137":2,"142":1,"195":2,"197":1,"200":1,"264":1,"269":1,"308":1,"372":1,"395":3,"408":2,"409":1,"438":1,"443":1,"463":2,"465":1,"475":2,"476":1,"477":1,"490":1,"492":1,"493":1,"497":2,"516":1,"522":2,"523":1,"543":6,"547":3,"549":1,"607":1,"609":2,"611":1,"833":1,"867":1,"950":1,"1016":1,"1096":1,"1104":1,"1114":1,"1212":4,"1259":1,"1300":1,"1308":1,"1310":1,"1323":6,"1393":1,"1394":11,"1414":1,"1435":1,"1464":1,"1466":1,"1472":1,"1477":1,"1495":6,"1514":1,"1584":1,"1590":1,"1599":1,"1610":1,"1630":1,"1670":1,"1736":1,"2125":1,"2157":1,"2231":1}}],["superseding",{"3":{"0":2,"1":1,"130":1,"495":1,"1286":1,"1526":1,"2084":1}}],["suppose",{"3":{"1858":1}}],["supposedly",{"3":{"2080":1}}],["supposed",{"3":{"141":1,"673":1,"765":1,"789":1,"1019":1,"1322":2,"1358":1,"1426":1,"1454":1,"1802":1,"1905":1,"1951":1,"2041":1,"2049":1,"2098":1,"2129":1,"2134":1,"2146":1,"2153":1}}],["supportapiclient",{"3":{"1652":1,"1701":1}}],["supportemail",{"3":{"1358":1}}],["supporter",{"3":{"1349":1}}],["supportedversions",{"3":{"1310":1,"1435":2}}],["supportedincludes",{"3":{"1309":6,"1864":1}}],["supportedfk",{"3":{"1199":2,"1207":1}}],["supportedchild",{"3":{"1199":2,"1207":1}}],["supportedculturestests",{"3":{"1199":1,"1207":2,"1437":2}}],["supportedcultures",{"2":{"265":1,"268":1,"2082":1,"2085":1},"3":{"265":8,"268":1,"1199":15,"1203":1,"1207":2,"1237":2,"1310":15,"1323":11,"1414":7,"1416":5,"1435":12,"1437":1,"1495":3,"1576":1,"1652":1,"2082":4,"2084":1,"2085":1}}],["supportedoperators",{"2":{"330":1,"331":1},"3":{"330":1,"331":1,"1241":2,"1247":10,"1987":1}}],["supported",{"0":{"1712":1},"2":{"31":3},"3":{"0":1,"31":3,"148":1,"170":1,"216":1,"265":1,"330":1,"350":1,"351":1,"352":1,"448":3,"501":1,"549":1,"550":1,"572":1,"682":1,"741":1,"1033":1,"1050":2,"1108":1,"1132":2,"1212":2,"1213":1,"1229":1,"1237":1,"1241":1,"1243":2,"1244":1,"1247":8,"1259":1,"1271":1,"1277":1,"1278":1,"1285":13,"1299":4,"1300":1,"1309":4,"1310":17,"1322":1,"1323":4,"1335":1,"1338":6,"1358":4,"1375":1,"1379":3,"1394":1,"1403":1,"1404":1,"1415":1,"1416":9,"1435":9,"1437":1,"1442":1,"1443":1,"1466":1,"1486":1,"1487":1,"1499":1,"1533":1,"1558":3,"1566":1,"1576":1,"1590":1,"1626":1,"1631":1,"1639":3,"1640":1,"1666":1,"1669":1,"1673":1,"1710":1,"1726":1,"1728":1,"1733":1,"1760":1,"1859":1,"1866":1,"1867":1,"1901":1,"1976":1,"1977":1,"1978":1,"1987":1,"2054":1,"2082":1,"2130":3,"2132":1,"2141":1,"2148":1,"2231":1}}],["supporting",{"0":{"1268":1},"3":{"659":1,"683":1,"845":1,"1247":2,"1272":1,"1283":1,"1285":2,"1286":1,"1310":1,"1311":1,"1323":1,"1345":1,"1349":1,"1414":1,"1431":1,"1435":1,"1487":1,"1762":1,"1773":1}}],["supportshttpstrafficonly",{"3":{"1466":1}}],["supportsrelationaloperator",{"3":{"1285":1}}],["supportstransactions",{"2":{"1839":1},"3":{"988":1,"1285":2,"1839":1}}],["supportsoutbox",{"2":{"720":1,"1174":1,"1175":1,"1178":1,"1860":1},"3":{"166":1,"715":1,"720":1,"1174":1,"1175":1,"1178":1,"1259":3,"1274":1,"1285":4,"1860":1}}],["supports",{"3":{"0":1,"10":1,"57":1,"449":1,"527":1,"528":1,"618":1,"640":1,"642":1,"845":1,"846":1,"891":1,"1019":1,"1048":1,"1050":1,"1237":1,"1241":1,"1247":2,"1270":1,"1285":2,"1299":2,"1322":1,"1325":1,"1338":1,"1339":1,"1341":1,"1349":2,"1358":5,"1394":1,"1395":3,"1416":2,"1435":1,"1455":1,"1466":1,"1487":1,"1533":1,"1550":1,"1628":1,"1630":1,"1638":1,"1674":1,"1763":1,"1772":1,"1798":1,"1814":1,"1826":1,"1833":1,"1894":1,"2128":1}}],["supportsifmatchattributetests",{"3":{"1199":1,"1207":2,"1437":2}}],["supportsifmatchattribute",{"2":{"341":1,"922":1,"1374":1,"1650":1,"1873":1},"3":{"341":4,"343":1,"345":1,"922":1,"1199":22,"1203":1,"1207":2,"1285":1,"1299":3,"1310":17,"1346":1,"1349":2,"1358":17,"1374":1,"1379":16,"1401":9,"1495":1,"1650":2,"1873":1}}],["supportsifmatch",{"2":{"343":1,"346":1,"1634":1},"3":{"0":2,"341":1,"343":2,"346":1,"922":1,"1212":1,"1285":1,"1310":4,"1349":1,"1358":2,"1372":1,"1374":1,"1379":5,"1401":5,"1437":2,"1634":1,"1650":1,"1663":1,"1665":1,"1873":1}}],["support",{"0":{"1346":1,"1358":1,"1375":1,"1389":1},"1":{"1710":1,"1711":1,"1712":1,"1713":1,"1714":1,"1715":1},"2":{"1645":1,"1648":1,"1649":5,"1656":1,"1696":2,"1697":1,"1698":1,"1726":2,"1727":2,"2088":1},"3":{"0":4,"12":1,"24":1,"109":1,"150":1,"166":2,"243":1,"363":1,"447":1,"461":1,"552":1,"556":1,"633":1,"650":1,"690":1,"743":1,"800":1,"812":1,"845":1,"848":1,"853":1,"904":1,"931":1,"980":2,"1019":1,"1052":1,"1073":1,"1074":2,"1075":3,"1077":2,"1175":2,"1192":1,"1202":1,"1203":1,"1207":44,"1208":2,"1211":1,"1232":1,"1237":2,"1247":2,"1257":2,"1259":2,"1270":3,"1279":1,"1285":17,"1299":4,"1310":3,"1323":2,"1339":1,"1341":1,"1349":5,"1350":1,"1358":16,"1375":1,"1379":3,"1394":12,"1395":1,"1404":1,"1414":2,"1415":1,"1416":5,"1427":2,"1428":3,"1429":1,"1435":58,"1437":3,"1466":1,"1487":3,"1489":2,"1495":2,"1557":1,"1566":1,"1576":2,"1581":1,"1601":1,"1628":3,"1629":2,"1630":2,"1634":1,"1637":1,"1638":1,"1639":2,"1640":2,"1645":1,"1648":1,"1649":5,"1652":3,"1653":2,"1655":5,"1656":2,"1664":1,"1672":1,"1673":1,"1684":2,"1696":3,"1697":2,"1698":1,"1710":1,"1715":1,"1726":4,"1727":2,"1773":1,"1872":1,"1897":1,"1950":1,"1973":1,"2072":1,"2088":1,"2127":1,"2128":1,"2130":1,"2132":2,"2140":1,"2199":1,"2219":1,"2233":1}}],["suppresswhenunderprobe",{"3":{"1338":2}}],["suppressnextlocationchanged",{"3":{"1323":2}}],["suppressfinalize",{"3":{"1323":3,"1395":3}}],["suppressible",{"3":{"1285":2}}],["suppressing",{"3":{"1285":2,"1338":1,"1435":1,"1437":1,"1442":2,"1449":1,"1638":1}}],["suppression",{"3":{"0":2,"258":1,"370":1,"371":1,"619":1,"726":1,"774":2,"941":1,"980":1,"982":2,"1108":1,"1111":1,"1133":1,"1237":5,"1259":5,"1266":1,"1270":2,"1271":1,"1273":1,"1285":6,"1299":5,"1308":2,"1310":4,"1322":4,"1323":5,"1338":16,"1349":1,"1358":2,"1394":3,"1395":2,"1414":2,"1415":3,"1416":2,"1426":1,"1435":7,"1440":2,"1452":1,"1486":1,"1487":1,"1489":1,"1495":1,"1585":1,"1590":1,"2056":1}}],["suppressions",{"3":{"0":2,"371":1,"776":1,"980":1,"981":2,"982":2,"1237":2,"1310":1,"1323":1,"1416":1,"1487":1,"1551":1,"1576":1,"1590":1,"1599":1,"2078":1}}],["suppressmessageattribute",{"3":{"1237":1}}],["suppressmessage",{"2":{"1270":1,"1323":1,"1414":1},"3":{"142":1,"980":1,"1229":1,"1270":5,"1299":3,"1310":2,"1311":1,"1323":4,"1368":1,"1379":1,"1395":1,"1414":1,"1426":2,"1435":3}}],["suppress",{"3":{"135":1,"231":1,"790":1,"1108":1,"1273":1,"1285":1,"1460":1,"1525":1,"1590":2,"1599":1,"2042":1,"2159":1,"2165":1}}],["suppresses",{"3":{"18":1,"216":1,"225":1,"733":1,"819":1,"1034":1,"1256":1,"1259":1,"1271":1,"1285":2,"1308":1,"1323":2,"1338":2,"1358":1,"1395":1,"1401":1,"1414":1,"1435":2,"1466":1,"1595":1,"1676":1,"2156":1}}],["suppressed",{"3":{"0":4,"130":1,"135":1,"265":1,"370":2,"454":1,"674":1,"692":1,"726":1,"790":1,"939":1,"941":1,"980":2,"983":1,"1044":1,"1212":1,"1241":1,"1259":2,"1270":1,"1273":1,"1285":2,"1308":1,"1311":2,"1322":1,"1323":3,"1338":2,"1386":1,"1394":3,"1414":1,"1415":2,"1416":2,"1426":1,"1452":2,"1495":1,"1525":1,"1568":1,"1576":1,"1590":2,"1840":1,"1896":1,"1933":1,"2156":1,"2191":1}}],["supplements",{"3":{"1394":1}}],["supplementary",{"3":{"1388":1,"1629":1}}],["supplemental",{"3":{"1285":1}}],["supplementation",{"3":{"1285":1}}],["supplied",{"1":{"154":1,"155":1,"156":1,"157":1,"158":1,"159":1,"160":1,"161":1,"162":1},"3":{"0":2,"31":1,"103":1,"109":1,"111":1,"154":1,"157":2,"175":1,"186":2,"190":1,"231":1,"265":1,"318":2,"330":1,"332":1,"350":2,"359":1,"378":1,"381":1,"397":1,"409":1,"423":1,"427":1,"443":2,"549":1,"599":1,"649":1,"674":1,"733":1,"795":1,"861":1,"905":1,"1059":2,"1091":1,"1115":2,"1116":1,"1132":1,"1184":1,"1228":1,"1229":4,"1237":2,"1239":1,"1242":2,"1247":15,"1270":3,"1271":12,"1281":1,"1284":1,"1285":20,"1288":1,"1299":19,"1300":4,"1305":2,"1308":7,"1309":1,"1310":18,"1322":9,"1323":14,"1333":1,"1338":9,"1342":3,"1348":1,"1349":15,"1351":1,"1354":1,"1358":60,"1368":2,"1377":1,"1379":6,"1394":10,"1395":17,"1401":7,"1414":13,"1415":2,"1416":5,"1420":1,"1422":1,"1423":1,"1424":1,"1426":2,"1428":2,"1431":1,"1435":21,"1437":4,"1466":2,"1525":2,"1533":1,"1630":1,"1662":1,"1747":1,"1763":1,"1764":2,"1766":1,"1804":1,"1829":1,"1830":1,"1908":1,"1911":1,"1936":1,"1949":1,"1976":1,"1987":1,"1988":1,"1989":1,"1993":1,"1999":1,"2001":1,"2130":1,"2135":1,"2195":1,"2200":1}}],["supplies",{"3":{"0":3,"5":1,"25":1,"31":1,"67":1,"79":1,"132":1,"150":1,"157":1,"166":1,"186":2,"230":1,"235":1,"265":1,"266":1,"269":1,"310":2,"318":1,"350":1,"398":1,"413":1,"415":1,"448":2,"460":1,"461":1,"507":1,"544":1,"572":4,"583":1,"603":1,"609":1,"610":1,"633":1,"639":1,"648":1,"653":1,"674":1,"683":1,"724":1,"725":2,"731":1,"799":1,"827":1,"836":1,"846":1,"881":1,"922":1,"923":1,"946":1,"972":1,"1018":3,"1050":3,"1059":1,"1074":1,"1083":2,"1108":1,"1161":3,"1184":1,"1237":4,"1247":4,"1257":1,"1258":1,"1259":5,"1265":1,"1269":1,"1270":12,"1271":3,"1285":10,"1288":1,"1299":12,"1300":2,"1301":1,"1302":1,"1303":1,"1304":2,"1308":7,"1309":1,"1310":15,"1322":11,"1323":21,"1325":1,"1327":1,"1332":1,"1335":1,"1337":1,"1338":6,"1349":3,"1351":1,"1353":1,"1354":1,"1358":53,"1359":1,"1368":4,"1379":3,"1382":1,"1394":24,"1395":10,"1401":2,"1405":2,"1406":1,"1408":1,"1409":1,"1411":1,"1413":1,"1414":22,"1415":5,"1416":6,"1426":2,"1430":1,"1434":1,"1435":96,"1437":2,"1466":1,"1487":3,"1488":2,"1489":1,"1525":1,"1650":1,"1653":1,"1665":2,"1684":1,"1685":1,"1707":1,"1764":1,"1809":3,"1814":1,"1816":1,"1855":1,"1888":1,"1890":1,"1896":1,"1907":1,"1916":1,"1931":1,"1936":1,"1939":1,"1944":1,"1993":1,"1994":1,"1996":1,"2009":1,"2041":1,"2042":3,"2043":1,"2054":3,"2071":1,"2084":1,"2110":1,"2113":1,"2130":2,"2137":1,"2141":1}}],["supplyparameterfromquery",{"3":{"1323":3,"1394":1,"1435":2,"1525":1}}],["supplying",{"3":{"136":1,"341":1,"470":1,"640":1,"854":1,"1004":1,"1214":1,"1259":1,"1285":2,"1296":1,"1308":2,"1310":6,"1322":2,"1323":3,"1337":1,"1349":1,"1358":12,"1385":1,"1394":3,"1395":2,"1414":2,"1416":2,"1430":2,"1435":13,"1495":1,"1764":1,"1956":1,"2095":1}}],["supply",{"0":{"1568":1,"1672":1,"1737":1},"1":{"367":1,"368":1,"369":1,"370":1,"371":1,"372":1,"373":1,"374":1,"375":1,"376":1},"3":{"0":3,"117":1,"216":1,"314":1,"367":1,"369":3,"370":1,"371":1,"373":1,"376":2,"499":1,"529":1,"532":1,"548":1,"583":1,"590":1,"591":1,"592":1,"595":1,"599":1,"603":1,"625":1,"626":1,"756":1,"765":1,"870":1,"874":1,"922":1,"1017":1,"1058":1,"1070":1,"1110":1,"1162":1,"1168":1,"1212":1,"1214":1,"1216":1,"1247":1,"1249":1,"1259":1,"1265":2,"1270":1,"1285":4,"1299":4,"1300":1,"1310":6,"1322":3,"1323":9,"1325":1,"1338":6,"1341":1,"1349":1,"1358":5,"1361":1,"1368":2,"1370":2,"1379":6,"1394":4,"1395":1,"1401":2,"1414":3,"1416":7,"1429":1,"1430":1,"1432":1,"1435":19,"1442":1,"1444":2,"1449":1,"1452":2,"1453":3,"1454":2,"1458":7,"1459":1,"1460":3,"1462":1,"1464":1,"1466":1,"1474":1,"1482":1,"1485":3,"1487":1,"1488":1,"1490":1,"1492":1,"1498":1,"1525":11,"1533":1,"1534":1,"1575":1,"1576":2,"1585":1,"1590":6,"1599":1,"1648":1,"1650":1,"1672":1,"1722":1,"1781":1,"1795":1,"1798":1,"1850":1,"1874":1,"1890":1,"1895":1,"1982":1,"2041":1,"2100":1,"2103":1,"2113":1,"2149":1,"2151":1,"2231":1}}],["subrequests",{"3":{"1968":1}}],["subdirectories",{"3":{"1463":1}}],["subdomains",{"3":{"1338":1}}],["subdomain",{"3":{"698":1,"1184":1}}],["suburbs",{"3":{"1349":1,"1358":2}}],["subgroup",{"3":{"1346":1}}],["subverted",{"3":{"1285":1}}],["subagent",{"3":{"1011":1}}],["subfolders",{"3":{"607":1,"693":1}}],["submissions",{"3":{"1346":1,"1349":5,"1358":3,"1395":1,"1401":3,"1466":2,"1634":3,"1636":1,"1637":3}}],["submission",{"0":{"1480":1},"3":{"173":1,"179":1,"1018":1,"1323":1,"1349":2,"1358":2,"1359":1,"1365":2,"1368":1,"1379":3,"1394":1,"1395":3,"1398":1,"1400":1,"1401":6,"1414":1,"1446":1,"1466":1,"1470":1,"1480":3,"1481":1,"1482":1,"1622":1,"1628":1,"1629":2,"1630":3,"1631":4,"1637":3,"1638":4,"1639":6,"1763":1,"1764":1,"1766":1,"1998":1,"2000":1,"2199":1}}],["submitbutton",{"3":{"1435":2}}],["submitbuttontext",{"3":{"1395":1}}],["submitanswerasync",{"3":{"1395":6}}],["submitanswersasync",{"3":{"1395":11}}],["submitasync",{"3":{"1395":2,"1401":1}}],["submitfailed",{"3":{"1395":1}}],["submitfeedback",{"3":{"1395":2}}],["submitfeedbackasync",{"3":{"1395":3}}],["submitter",{"3":{"1397":1,"1401":1,"1631":1}}],["submittedquestions",{"3":{"1395":1,"1414":3}}],["submittedonutc",{"3":{"1349":2,"1395":2}}],["submitted",{"3":{"280":1,"282":1,"285":1,"690":1,"1018":2,"1238":1,"1247":1,"1299":2,"1322":5,"1323":3,"1348":1,"1349":11,"1358":12,"1365":1,"1368":2,"1379":3,"1394":3,"1395":12,"1397":1,"1398":1,"1401":5,"1409":1,"1414":4,"1435":1,"1437":1,"1454":1,"1533":1,"1628":1,"1629":1,"1630":1,"1631":3,"1637":1,"1638":2,"1639":5,"1764":1,"1998":1,"1999":2,"2001":2}}],["submittingwithoutatoken",{"3":{"1435":1}}],["submitting",{"3":{"1299":2,"1306":1,"1358":6,"1379":1,"1395":3,"1401":3,"1560":1,"1629":1,"1630":1,"1637":2,"1638":1,"1639":1,"1945":1,"1997":1}}],["submitreviewhandler",{"3":{"552":1,"1766":2}}],["submitquestionasync",{"3":{"1401":1}}],["submitquestionrequest",{"3":{"1199":8,"1203":1,"1207":2,"1395":3,"1401":8}}],["submitquestioncommandvalidatortests",{"3":{"1199":1,"1207":2}}],["submitquestioncommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1401":10}}],["submitquestioncommand",{"2":{"523":1},"3":{"1199":5,"1203":1,"1207":2,"1401":9}}],["submitquestionhandlertests",{"3":{"1199":1,"1207":3,"1435":1}}],["submitquestionhandler",{"2":{"522":1,"523":1,"524":1,"899":1,"900":1,"996":1,"1102":1,"1533":1},"3":{"0":2,"493":1,"494":1,"495":1,"522":2,"523":3,"524":3,"897":2,"899":2,"900":1,"996":2,"1100":2,"1102":2,"1199":1,"1203":1,"1207":2,"1247":2,"1349":4,"1358":2,"1395":5,"1398":8,"1401":17,"1435":2,"1495":1,"1533":1}}],["submit",{"3":{"160":1,"493":1,"494":1,"495":1,"522":1,"523":1,"524":1,"552":1,"897":3,"899":1,"900":1,"994":2,"996":4,"1100":1,"1203":3,"1207":6,"1247":2,"1265":1,"1270":1,"1299":1,"1323":9,"1358":13,"1368":1,"1372":1,"1379":7,"1394":10,"1395":32,"1396":1,"1398":1,"1401":34,"1414":2,"1435":14,"1466":1,"1487":6,"1525":3,"1533":1,"1560":3,"1590":1,"1606":1,"1607":1,"1625":1,"1626":1,"1631":4,"1637":1,"1638":1,"1639":1,"1740":1,"1741":1,"1745":1,"1747":1,"1764":2}}],["submitsitasthestoredset",{"3":{"1435":1}}],["submitscanasync",{"3":{"1395":1}}],["submits",{"3":{"156":1,"1348":1,"1349":3,"1351":1,"1368":1,"1379":1,"1394":1,"1395":2,"1398":1,"1401":2,"1431":1,"1435":6,"1487":1,"1631":3,"1764":2}}],["subtitled",{"3":{"1394":2}}],["subtitles",{"3":{"1394":1,"1479":1}}],["subtitle",{"3":{"1394":1,"1479":2,"1779":1,"1787":1,"1793":1,"1802":1,"1807":1,"1812":1,"1817":1,"1827":1,"1834":1,"1846":1,"1853":1,"1862":1,"1868":1,"1877":1,"1886":1,"1892":1,"1899":1,"1905":1,"1912":1,"1924":1,"1930":1,"1941":1,"1950":1,"1959":1,"1965":1,"1972":1,"1979":1,"1985":1,"1991":1,"1997":1,"2002":1,"2014":1,"2028":1,"2038":1,"2049":1,"2060":1,"2069":1,"2080":1,"2086":1,"2098":1,"2108":1,"2115":1,"2123":1,"2128":1,"2133":1,"2139":1,"2146":1,"2153":1,"2160":1,"2169":1,"2180":1,"2193":1,"2203":1}}],["subtitle2",{"3":{"1323":1}}],["subtitle1",{"3":{"1323":1}}],["subtag",{"3":{"1310":2}}],["subtrees",{"3":{"1486":3}}],["subtree",{"3":{"1285":1,"1332":1,"1338":2,"1435":1,"1442":1,"1486":2}}],["subtract",{"3":{"1394":1}}],["subtracted",{"3":{"1368":1}}],["subtracts",{"3":{"742":1,"1285":1,"1299":1,"1322":1,"1435":2,"1748":1}}],["subtractive",{"3":{"1299":1}}],["subtraction",{"3":{"741":1,"1019":1,"1358":1,"1361":1,"1368":1}}],["subtracting",{"3":{"137":1}}],["subtype",{"3":{"1259":1}}],["subtypes",{"3":{"1229":3,"1806":1}}],["subtly",{"3":{"274":1,"840":1,"1051":1,"1358":3,"1435":2,"1478":1,"1827":1,"2081":1}}],["subtlest",{"3":{"1290":1,"1299":1,"1338":1,"1416":1}}],["subtleties",{"3":{"1285":1,"1435":1}}],["subtlety",{"3":{"97":1,"1252":1,"1299":1,"1319":1,"1327":1,"1338":1,"1347":1,"1394":1,"1404":1,"1414":1,"1415":1,"1435":1,"1880":1}}],["subtle",{"3":{"574":1,"800":1,"862":1,"1229":1,"1247":3,"1258":1,"1259":2,"1262":1,"1270":1,"1274":1,"1285":1,"1299":1,"1308":3,"1310":2,"1322":4,"1323":4,"1331":1,"1349":3,"1358":1,"1379":1,"1394":1,"1395":3,"1414":1,"1435":5,"1453":1,"1814":1,"1817":1,"1821":1,"1861":1,"1880":1,"1885":1,"2040":1,"2073":1,"2117":1,"2135":1,"2238":1}}],["subtler",{"3":{"133":1,"610":1,"1237":1,"1331":1,"1416":1,"2099":1,"2105":1}}],["subquery",{"3":{"24":1,"1237":1,"1259":1,"1285":1,"1400":1,"1401":2}}],["sub",{"1":{"1941":1,"1942":1,"1943":1,"1944":1,"1945":1,"1946":1,"1947":1,"1948":1,"1949":1},"2":{"298":1,"910":1},"3":{"0":6,"68":1,"157":1,"161":1,"175":1,"259":1,"298":1,"303":3,"318":2,"324":1,"379":1,"413":1,"416":1,"493":1,"497":1,"545":1,"709":1,"905":3,"910":1,"954":1,"1004":1,"1006":1,"1011":1,"1018":4,"1019":1,"1042":1,"1238":1,"1269":1,"1270":1,"1285":2,"1287":2,"1288":1,"1295":2,"1299":19,"1310":2,"1322":1,"1323":2,"1331":1,"1338":3,"1349":3,"1358":2,"1361":1,"1365":1,"1368":3,"1370":1,"1371":1,"1379":3,"1394":2,"1395":3,"1414":2,"1431":1,"1435":12,"1473":1,"1487":1,"1491":1,"1525":3,"1528":1,"1529":1,"1530":1,"1531":1,"1534":1,"1581":1,"1584":1,"1593":1,"1599":1,"1746":1,"1764":1,"1770":1,"1778":1,"1814":1,"1941":1,"1956":1,"1981":1,"1994":1,"2032":1,"2036":1,"2042":1,"2137":1,"2185":1,"2207":1}}],["subclassable",{"3":{"622":1,"1285":1}}],["subclasses",{"3":{"0":8,"27":2,"76":1,"124":1,"130":1,"137":1,"139":1,"140":1,"147":1,"160":1,"281":1,"283":1,"305":1,"314":1,"465":1,"523":1,"549":1,"572":3,"573":1,"575":1,"577":1,"578":5,"609":1,"618":2,"631":2,"632":1,"635":1,"707":1,"723":1,"725":3,"741":1,"744":1,"749":1,"751":1,"809":1,"846":1,"881":1,"884":1,"905":1,"912":1,"964":1,"973":1,"976":1,"1004":1,"1018":2,"1074":2,"1091":1,"1136":1,"1161":1,"1162":1,"1163":1,"1168":1,"1212":1,"1214":1,"1231":1,"1237":3,"1247":6,"1252":1,"1259":5,"1262":1,"1265":1,"1270":7,"1271":1,"1285":5,"1299":7,"1310":6,"1311":1,"1321":1,"1322":4,"1323":5,"1353":1,"1358":8,"1383":1,"1394":4,"1397":1,"1401":1,"1405":1,"1408":1,"1414":4,"1417":1,"1426":2,"1428":1,"1430":2,"1435":46,"1437":2,"1475":1,"1486":3,"1487":2,"1488":8,"1495":2,"1525":3,"1576":5,"1581":2,"1653":1,"1665":1,"1825":1,"1856":1,"1895":1,"1982":1,"1999":1,"2041":1,"2054":3,"2056":1,"2100":1,"2103":1,"2104":1,"2106":1,"2110":1,"2137":1,"2231":1}}],["subclassed",{"3":{"0":3,"51":1,"80":1,"130":1,"265":1,"302":1,"572":4,"573":1,"574":1,"578":2,"638":1,"723":1,"972":1,"1050":1,"1054":1,"1161":1,"1168":1,"1247":1,"1259":1,"1270":3,"1285":1,"1299":1,"1309":1,"1310":3,"1322":9,"1358":1,"1394":4,"1395":1,"1429":1,"1435":71,"1487":3,"1488":2,"1525":1,"1575":1,"1576":2,"1581":1,"1582":1,"1585":2,"2041":1,"2054":1,"2100":1,"2130":1,"2177":1}}],["subclassing",{"3":{"0":2,"534":1,"545":1,"611":1,"619":1,"1075":1,"1247":2,"1270":1,"1271":2,"1309":1,"1310":3,"1322":3,"1358":3,"1401":3,"1415":1,"1435":2,"1487":3,"1488":1,"1576":1,"1653":1,"1666":1,"1814":1,"1890":1,"2107":1}}],["subclass",{"0":{"1405":1,"2103":1},"3":{"0":10,"27":1,"47":1,"54":1,"80":1,"109":1,"128":1,"134":1,"135":3,"136":2,"139":1,"142":1,"147":1,"218":1,"233":1,"235":2,"281":1,"283":1,"310":4,"341":1,"350":1,"351":1,"352":1,"446":1,"448":4,"449":1,"450":2,"455":1,"459":2,"461":1,"463":1,"465":1,"497":2,"498":1,"499":1,"500":1,"518":1,"523":2,"536":3,"540":1,"545":1,"551":1,"572":8,"573":1,"574":2,"575":1,"577":1,"585":2,"607":1,"609":3,"610":1,"611":1,"614":1,"616":1,"618":1,"631":2,"635":1,"640":1,"706":1,"720":1,"723":1,"724":1,"725":8,"726":1,"727":1,"749":2,"813":1,"814":1,"855":1,"881":3,"884":1,"905":2,"907":1,"909":1,"910":1,"914":2,"920":1,"922":1,"924":1,"926":3,"954":3,"956":1,"957":1,"960":2,"972":1,"973":1,"974":1,"1004":1,"1005":2,"1048":1,"1050":1,"1054":1,"1058":1,"1059":2,"1065":1,"1067":1,"1069":1,"1081":1,"1083":2,"1091":2,"1100":1,"1133":2,"1135":2,"1137":1,"1161":3,"1168":2,"1169":1,"1171":1,"1226":1,"1229":2,"1237":2,"1244":2,"1247":6,"1252":1,"1259":10,"1265":1,"1270":21,"1271":1,"1272":1,"1281":1,"1285":14,"1288":1,"1289":1,"1299":11,"1309":6,"1310":19,"1311":1,"1317":2,"1321":3,"1322":32,"1323":4,"1333":1,"1338":5,"1352":1,"1358":48,"1368":2,"1379":6,"1383":1,"1394":10,"1395":3,"1401":3,"1405":3,"1406":2,"1409":1,"1410":1,"1411":1,"1412":2,"1413":1,"1414":22,"1415":2,"1416":2,"1426":2,"1427":1,"1428":3,"1430":2,"1431":1,"1432":1,"1434":1,"1435":250,"1437":9,"1442":1,"1487":4,"1488":7,"1489":1,"1490":2,"1495":1,"1524":1,"1525":7,"1531":1,"1576":1,"1581":1,"1584":1,"1590":5,"1652":1,"1653":2,"1659":1,"1670":2,"1688":1,"1698":1,"1726":1,"1781":1,"1814":2,"1815":1,"1848":1,"1864":1,"1874":1,"1897":1,"1976":1,"1977":2,"1981":1,"1982":1,"1989":1,"1999":1,"2042":3,"2043":3,"2045":2,"2054":1,"2055":1,"2100":1,"2101":1,"2102":1,"2103":2,"2106":1,"2130":7,"2131":2,"2132":1,"2164":2,"2166":1,"2179":1,"2195":1,"2196":1,"2200":1}}],["subjective",{"3":{"1800":1}}],["subjects",{"3":{"1435":4,"1454":1,"2062":1}}],["subjectsnapshot",{"3":{"1199":2,"1207":1}}],["subjectkey",{"3":{"0":1,"690":2,"1212":1,"1395":22,"1414":2,"1630":2}}],["subject",{"0":{"1298":1},"1":{"722":1,"723":1,"724":1,"725":1,"726":1,"727":1,"728":1,"729":1},"2":{"31":1,"303":1,"1298":1,"2137":1},"3":{"0":5,"31":1,"38":1,"39":3,"41":2,"42":2,"95":1,"96":1,"136":1,"137":1,"157":2,"159":1,"161":1,"173":2,"175":1,"181":1,"230":1,"303":1,"358":1,"583":1,"585":1,"625":1,"690":2,"691":3,"692":1,"693":1,"698":1,"700":1,"716":1,"722":1,"724":4,"725":6,"727":3,"745":1,"765":1,"766":1,"868":1,"896":2,"905":1,"976":1,"1026":1,"1028":1,"1044":1,"1199":6,"1207":1,"1212":1,"1229":1,"1236":3,"1237":8,"1270":2,"1277":1,"1282":1,"1285":3,"1298":4,"1299":27,"1307":1,"1308":21,"1310":4,"1311":1,"1319":1,"1321":2,"1322":19,"1323":1,"1349":5,"1358":6,"1394":3,"1395":51,"1401":2,"1402":1,"1409":4,"1414":50,"1416":1,"1430":1,"1434":1,"1435":32,"1437":11,"1454":1,"1456":1,"1459":1,"1460":1,"1466":3,"1471":5,"1486":1,"1488":5,"1491":1,"1566":2,"1590":1,"1596":1,"1630":6,"1638":5,"1639":1,"1640":1,"1665":1,"1746":1,"1750":1,"1763":1,"1764":4,"1771":1,"1907":2,"1919":1,"1963":1,"1973":1,"1999":1,"2043":1,"2061":1,"2067":1,"2137":1,"2140":1,"2155":1,"2157":1,"2164":1,"2192":1,"2231":2,"2238":1}}],["subsuming",{"3":{"1435":1}}],["subsumes",{"3":{"1435":1,"1730":1}}],["subsequent",{"3":{"388":1,"390":1,"1247":2,"1259":1,"1285":2,"1308":1,"1310":2,"1323":1,"1358":1,"1455":1,"1474":1,"1490":1,"1629":1,"1634":3,"1638":1,"1640":1,"1907":1}}],["subsequently",{"3":{"91":1,"1323":1}}],["subsections",{"3":{"1466":1}}],["subsection",{"3":{"258":1,"1495":2}}],["subsets",{"3":{"0":1,"1242":1,"1247":1,"1323":1,"1347":1,"1349":2,"1435":2}}],["subset",{"3":{"0":1,"137":1,"140":1,"184":1,"186":1,"187":2,"741":1,"751":1,"954":1,"1215":1,"1242":1,"1244":1,"1245":1,"1247":6,"1259":1,"1271":1,"1285":3,"1299":6,"1331":1,"1349":1,"1372":1,"1376":2,"1379":8,"1395":4,"1404":1,"1408":1,"1414":6,"1416":1,"1485":1,"1495":4,"1525":1,"1559":1,"1613":1,"1616":1,"1617":1,"1620":2,"1626":1,"1960":1,"1962":1,"2074":1,"2110":1,"2149":2}}],["substantial",{"3":{"1435":1}}],["substantive",{"3":{"0":1,"77":1,"250":1,"251":1,"252":1,"257":1,"543":4,"1270":1,"1323":1,"1401":1,"1887":1}}],["substance",{"3":{"200":1,"256":1,"258":1,"464":1,"524":1,"703":1,"797":1,"875":1,"958":1,"1216":1,"1285":1,"1323":1,"1358":1,"1435":1,"1515":1,"1522":1,"1525":7,"1536":1,"1576":2,"1587":1,"1589":1,"1590":2,"1591":4,"1783":2,"1796":1,"1798":1}}],["substitutability",{"3":{"1237":1,"1270":1,"1285":2,"1299":1,"1358":1,"1416":3,"1426":1}}],["substitutable",{"3":{"641":1,"1308":1,"1394":1,"1416":1,"1435":1,"1537":1,"1841":1}}],["substituting",{"3":{"448":1,"545":1,"685":1,"1299":1,"1310":1,"1322":2,"1323":1,"1374":1,"1379":2,"1394":2,"1395":1,"1415":1,"1435":1,"1814":2}}],["substitutionpriority",{"3":{"1175":1}}],["substitutions",{"3":{"633":1}}],["substitution",{"0":{"170":1},"3":{"170":1,"543":1,"550":1,"1175":3,"1239":1,"1247":5,"1285":5,"1299":1,"1310":2,"1322":1,"1338":2,"1379":1,"1395":2,"1435":1,"1464":1,"1474":1,"1576":2,"1684":1,"1685":1,"1814":2,"1816":2,"1896":1,"1898":1,"1915":1,"2148":1,"2152":1}}],["substituteengine",{"3":{"1285":1}}],["substituteapiversioninurl",{"2":{"448":1},"3":{"448":1,"1310":1}}],["substituted",{"3":{"214":1,"359":1,"365":1,"369":1,"370":1,"838":1,"1237":1,"1259":1,"1285":1,"1323":1,"1335":1,"1338":1,"1349":1,"1358":3,"1374":1,"1379":3,"1394":1,"1395":1,"1435":3,"2141":1}}],["substitutes",{"3":{"170":1,"212":1,"954":1,"1279":1,"1285":5,"1299":1,"1308":2,"1311":1,"1322":1,"1323":4,"1338":1,"1358":2,"1379":1,"1394":1,"1395":1,"1416":3,"1435":3}}],["substitute",{"3":{"170":3,"546":1,"1034":1,"1177":1,"1259":1,"1285":4,"1310":2,"1323":5,"1358":1,"1361":1,"1367":1,"1372":1,"1394":1,"1395":2,"1416":1,"1480":1,"1645":1,"1896":1,"1991":1}}],["substituteunconfiguredengine",{"2":{"170":1},"3":{"170":1,"1285":1}}],["substrings",{"3":{"1310":1,"1394":1,"1435":2}}],["substring",{"3":{"0":1,"585":1,"633":2,"1270":1,"1285":2,"1338":1,"1358":2,"1394":1,"1414":1,"1435":4,"1454":1,"1685":1,"1726":2,"1730":1}}],["substrates",{"3":{"1437":1}}],["substrate",{"1":{"240":1,"241":1,"242":1,"243":1,"244":1,"245":1,"246":1,"247":1,"248":1,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"260":1,"261":1,"730":1,"731":1,"732":1,"733":1,"734":1,"735":1,"736":1,"737":1},"3":{"0":5,"124":1,"162":1,"239":1,"240":1,"241":2,"242":1,"243":3,"244":1,"247":3,"248":1,"253":3,"254":1,"259":3,"260":1,"282":1,"287":1,"730":1,"731":3,"733":5,"734":1,"735":2,"736":1,"737":2,"853":1,"1044":1,"1212":2,"1299":1,"1300":10,"1338":1,"1414":1,"1427":1,"1576":1,"1640":1,"1650":1,"1667":1,"1707":1,"1915":2,"1920":1,"2113":1,"2231":2}}],["subscribing",{"3":{"408":1,"784":2,"1322":1,"1323":1,"1338":1,"1349":4,"1358":1,"1395":4,"1416":2,"2178":1}}],["subscribers",{"3":{"1270":1,"1308":1,"1323":3,"1395":1,"1414":1,"1416":2,"1749":1}}],["subscriber",{"3":{"198":1,"382":1,"781":1,"783":1,"784":1,"1026":1,"1323":11,"1348":1,"1349":15,"1358":1,"1395":11,"1398":1,"1401":10,"1414":5,"1416":2,"1435":1,"1948":3}}],["subscribes",{"3":{"72":1,"273":1,"384":1,"391":1,"397":1,"404":1,"782":1,"790":1,"899":1,"1259":1,"1323":10,"1338":3,"1348":1,"1349":19,"1395":7,"1401":2,"1414":3,"1416":12,"1426":1,"1437":1,"1525":1,"1921":1,"2005":1,"2165":1}}],["subscribed",{"0":{"408":1},"3":{"0":1,"72":1,"255":1,"395":5,"400":1,"404":1,"407":1,"1259":1,"1301":1,"1308":4,"1323":2,"1338":2,"1401":1,"1416":1,"2005":1,"2155":2}}],["subscribe",{"3":{"0":1,"66":1,"273":1,"384":1,"404":1,"406":1,"819":1,"897":1,"1308":1,"1323":3,"1340":1,"1349":2,"1395":3,"1401":1,"1416":5,"1434":1,"1944":1,"2155":2,"2214":1,"2234":1,"2235":1,"2236":1}}],["subscription",{"2":{"1467":1,"1471":1},"3":{"0":1,"258":2,"259":1,"261":1,"276":1,"378":1,"384":1,"395":2,"400":1,"407":2,"408":3,"640":2,"641":1,"766":1,"767":1,"783":1,"784":1,"822":1,"900":1,"971":1,"973":2,"1322":1,"1323":10,"1326":1,"1338":2,"1395":5,"1399":2,"1400":1,"1401":9,"1414":2,"1416":7,"1435":4,"1437":2,"1439":1,"1442":1,"1454":2,"1462":1,"1464":1,"1465":1,"1466":6,"1467":1,"1469":2,"1471":2,"1473":1,"1475":1,"1483":2,"1949":1,"2042":1,"2072":1,"2155":2,"2158":1,"2179":1}}],["subscriptions",{"3":{"0":1,"380":1,"399":1,"1192":1,"1323":2,"1338":1,"1416":1,"1466":3,"1944":1}}],["subsystems",{"3":{"0":1,"1108":1,"1192":1,"1299":3,"1308":1,"1395":1}}],["subsystem",{"3":{"0":1,"165":1,"235":2,"237":1,"707":1,"1110":1,"1192":1,"1202":1,"1203":1,"1299":1,"1301":1,"1308":5,"1309":2,"1333":2,"1338":3,"1401":1,"1495":2,"1673":1,"1914":1}}],["surname",{"3":{"1310":1}}],["surround",{"3":{"2037":1}}],["surrounds",{"3":{"1903":1}}],["surrounding",{"3":{"139":1,"426":1,"490":1,"1299":2,"1310":1,"1322":1,"1323":2,"1349":1,"1358":4,"1379":1,"1394":1,"1395":6,"1414":2,"1415":1,"1416":1,"1435":2,"1437":1,"1727":1,"2058":1}}],["surrogate",{"3":{"1259":2,"1299":1,"1395":1,"1413":1,"1414":2}}],["surplus",{"3":{"1241":1,"1254":1,"1259":4,"1306":1,"1310":1}}],["surprisingly",{"3":{"1379":1}}],["surprising",{"3":{"692":1,"743":1,"1237":1,"1280":1,"1285":1,"1338":1,"1395":1,"1414":1,"1435":1,"1638":1,"2098":1}}],["surprises",{"3":{"1285":1,"1362":1,"1368":1,"1485":1,"1567":1,"1661":1}}],["surprise",{"3":{"399":1,"698":1,"741":1,"1093":1,"1229":1,"1285":3,"1310":2,"1338":2,"1349":2,"1394":1,"1405":1,"1414":2,"1454":1,"1678":1,"1685":1,"1880":1,"1884":1,"1954":1,"2045":1,"2064":1,"2158":1}}],["surprised",{"3":{"243":1,"1924":1,"1985":1}}],["surgically",{"3":{"1358":1}}],["surgical",{"3":{"1231":1,"1237":1}}],["surges",{"3":{"1456":1,"1525":1,"1678":1}}],["surgery",{"3":{"1011":1,"1012":1,"1435":1,"1495":1,"1570":1}}],["surge",{"3":{"0":1,"765":2,"766":3,"767":4,"769":1,"1212":1,"1456":5,"1460":2,"1466":4,"1468":1,"1475":1,"1524":1,"1525":4,"1529":1,"1530":2,"1531":1,"1533":1,"1590":3,"1596":1,"1677":2,"1678":1,"1679":1,"2231":1}}],["sure",{"3":{"215":1,"1299":1,"1323":1,"1876":1,"1929":1,"2104":1,"2149":1,"2152":1}}],["surfacing",{"3":{"39":1,"99":1,"501":1,"1259":1,"1263":1,"1271":1,"1285":1,"1299":1,"1310":2,"1311":1,"1322":1,"1323":2,"1338":1,"1349":2,"1382":1,"1394":3,"1395":2,"1401":4,"1416":1,"1426":2,"1435":1,"1983":1,"2071":1,"2219":1,"2231":1}}],["surfaced",{"3":{"39":2,"78":1,"265":1,"690":1,"729":1,"789":1,"846":1,"926":1,"1026":1,"1036":1,"1084":1,"1099":1,"1237":2,"1242":1,"1270":1,"1285":1,"1299":3,"1308":1,"1311":1,"1322":1,"1323":1,"1338":2,"1349":6,"1357":1,"1358":7,"1394":4,"1395":5,"1401":1,"1414":2,"1426":1,"1430":1,"1435":4,"1487":1,"1495":3,"1531":1,"1534":1,"1576":1,"1620":1,"1626":1,"1642":1,"1915":1,"1943":1,"1954":1,"2165":1,"2219":1}}],["surfaces",{"0":{"926":1},"3":{"0":5,"41":1,"43":1,"95":1,"109":1,"127":1,"189":1,"201":1,"215":1,"237":1,"245":1,"274":1,"300":2,"301":1,"303":1,"306":2,"340":1,"388":1,"390":1,"401":1,"441":1,"500":1,"514":1,"545":1,"550":1,"585":1,"612":1,"628":1,"648":1,"649":1,"650":1,"651":1,"688":1,"690":1,"732":1,"790":1,"802":1,"803":1,"861":1,"883":1,"913":1,"926":1,"966":1,"990":1,"996":1,"1004":1,"1043":1,"1051":1,"1212":2,"1247":2,"1259":1,"1263":1,"1270":6,"1271":1,"1278":1,"1285":5,"1286":1,"1296":1,"1299":7,"1300":2,"1306":1,"1308":1,"1309":1,"1310":7,"1322":1,"1323":17,"1338":5,"1346":1,"1349":2,"1351":1,"1354":1,"1355":1,"1358":22,"1378":1,"1379":1,"1387":1,"1393":1,"1394":13,"1395":33,"1396":1,"1400":1,"1401":16,"1414":3,"1415":3,"1416":6,"1432":2,"1433":1,"1435":18,"1437":7,"1443":1,"1446":1,"1453":1,"1455":1,"1466":2,"1486":1,"1487":1,"1488":1,"1495":1,"1525":6,"1530":2,"1531":2,"1533":1,"1534":1,"1575":1,"1576":1,"1581":1,"1590":2,"1595":1,"1626":2,"1635":1,"1650":1,"1697":1,"1713":2,"1759":1,"1868":1,"1922":1,"2073":1,"2074":1,"2076":1,"2082":2,"2112":1,"2126":1,"2136":1,"2137":1,"2152":1,"2231":3}}],["surface",{"0":{"1418":1,"1665":1,"1701":1,"1999":1,"2034":1,"2136":1},"1":{"297":1,"298":1,"299":1,"300":1,"301":1,"302":1,"303":1,"304":1,"305":1,"306":1,"977":1,"978":1,"979":1,"980":1,"981":1,"982":1,"983":1,"984":1,"1997":1,"1998":1,"1999":1,"2000":1,"2001":1},"3":{"0":16,"6":1,"24":1,"37":1,"42":1,"53":1,"54":2,"55":1,"58":2,"100":1,"103":1,"107":1,"109":1,"111":1,"121":1,"124":1,"130":1,"135":10,"138":1,"139":1,"141":1,"143":1,"145":2,"150":1,"162":1,"165":1,"166":1,"173":2,"177":2,"186":1,"189":1,"209":2,"210":1,"237":2,"256":1,"265":2,"275":1,"280":2,"287":1,"297":1,"299":1,"301":1,"305":1,"306":1,"314":1,"321":1,"329":1,"330":1,"331":1,"332":4,"334":1,"341":1,"349":1,"351":1,"355":1,"363":1,"384":1,"391":1,"398":1,"403":1,"415":2,"421":1,"435":1,"447":1,"450":1,"471":1,"474":1,"506":2,"507":1,"508":3,"513":1,"527":1,"543":1,"545":1,"547":1,"548":1,"549":2,"550":2,"551":2,"572":3,"583":2,"591":1,"609":1,"611":1,"617":1,"618":1,"649":1,"650":2,"674":2,"675":2,"676":1,"681":1,"682":2,"684":1,"690":2,"691":5,"692":1,"694":1,"700":3,"707":2,"715":2,"717":1,"723":1,"725":2,"727":1,"740":1,"741":1,"743":1,"745":1,"751":1,"773":1,"774":1,"775":2,"783":2,"784":1,"792":1,"795":1,"797":4,"799":1,"803":1,"804":1,"805":2,"806":1,"827":2,"832":1,"840":1,"848":1,"880":1,"881":1,"882":1,"885":1,"914":1,"920":3,"921":1,"942":1,"963":1,"964":3,"966":1,"968":1,"971":1,"976":1,"977":1,"979":2,"980":7,"981":1,"982":1,"983":1,"984":2,"988":1,"1008":1,"1020":1,"1025":1,"1043":1,"1044":1,"1048":1,"1049":2,"1051":1,"1052":3,"1053":1,"1054":1,"1055":2,"1059":3,"1073":1,"1074":1,"1076":1,"1091":1,"1118":1,"1132":1,"1133":2,"1134":1,"1187":1,"1192":1,"1202":1,"1203":1,"1212":4,"1213":1,"1217":1,"1220":1,"1227":1,"1229":6,"1237":3,"1238":1,"1247":7,"1248":1,"1249":1,"1256":1,"1259":4,"1263":1,"1270":9,"1271":1,"1275":1,"1276":2,"1277":1,"1278":1,"1284":1,"1285":32,"1289":1,"1290":1,"1296":2,"1298":2,"1299":33,"1300":3,"1308":14,"1309":1,"1310":42,"1311":8,"1315":1,"1317":3,"1319":1,"1322":17,"1323":59,"1331":2,"1335":1,"1337":1,"1338":8,"1347":2,"1349":18,"1353":1,"1355":2,"1356":1,"1357":1,"1358":49,"1365":1,"1366":1,"1367":1,"1368":8,"1369":2,"1370":2,"1372":2,"1374":1,"1379":11,"1380":2,"1391":1,"1394":41,"1395":65,"1400":2,"1401":22,"1402":2,"1405":2,"1408":3,"1413":2,"1414":41,"1415":9,"1416":17,"1420":1,"1428":1,"1430":3,"1435":69,"1436":1,"1437":11,"1442":1,"1444":1,"1448":1,"1449":1,"1452":1,"1453":1,"1454":2,"1464":1,"1466":2,"1475":2,"1486":2,"1487":2,"1491":1,"1495":3,"1523":1,"1525":2,"1530":1,"1533":1,"1569":1,"1576":7,"1584":4,"1590":2,"1596":1,"1598":2,"1625":1,"1626":1,"1629":1,"1630":2,"1639":1,"1641":1,"1643":2,"1661":2,"1664":1,"1672":2,"1685":1,"1714":1,"1715":1,"1719":1,"1747":1,"1760":1,"1770":1,"1780":1,"1791":1,"1814":1,"1847":1,"1851":1,"1867":1,"1875":1,"1928":5,"1929":2,"1931":1,"1935":1,"1937":1,"1939":1,"1940":1,"1957":1,"1970":2,"1984":1,"1985":1,"1986":3,"1987":2,"1988":1,"1989":3,"1996":1,"1997":1,"1999":1,"2000":1,"2001":3,"2006":2,"2011":1,"2016":1,"2034":1,"2036":1,"2037":2,"2040":1,"2043":1,"2050":2,"2054":1,"2077":1,"2078":1,"2079":1,"2084":2,"2099":1,"2105":1,"2113":3,"2121":2,"2124":1,"2128":1,"2131":1,"2136":1,"2158":1,"2167":1,"2168":1,"2171":1,"2172":1,"2190":1,"2192":2,"2199":1,"2200":1,"2202":2,"2207":2,"2209":1,"2231":6}}],["surveys",{"3":{"1074":1,"1628":1,"1638":1}}],["survey",{"3":{"1033":1,"1349":1,"1358":3,"1628":1,"1629":4,"1636":5,"1638":5,"1639":1}}],["surveyed",{"3":{"0":1,"1049":1,"1051":1}}],["survival",{"3":{"1358":1,"2167":1}}],["survivable",{"3":{"563":1,"698":1,"759":1,"1323":1,"1358":1,"1416":3,"2192":1}}],["survivor",{"3":{"1358":1,"1435":2}}],["survivors",{"3":{"1285":1,"1414":1}}],["surviving",{"1":{"1965":1,"1966":1,"1967":1,"1968":1,"1969":1,"1970":1,"1971":1},"3":{"0":1,"67":1,"122":1,"201":1,"497":1,"734":1,"856":1,"863":1,"1247":1,"1259":1,"1262":1,"1271":1,"1284":1,"1285":2,"1310":1,"1322":1,"1323":2,"1338":2,"1349":2,"1358":5,"1394":2,"1409":1,"1414":1,"1426":1,"1435":4,"1437":1,"1472":1,"1481":1,"1965":1,"2207":1}}],["survived",{"3":{"266":1,"839":1,"840":1,"908":1,"1358":2,"1414":1,"1988":1}}],["survive",{"0":{"1223":1,"2188":1},"3":{"0":2,"39":2,"49":1,"131":1,"166":1,"242":1,"265":1,"380":1,"390":1,"440":1,"490":1,"520":2,"610":1,"626":1,"708":1,"711":1,"846":1,"932":1,"933":1,"997":1,"1018":1,"1020":1,"1043":1,"1074":1,"1102":2,"1115":1,"1212":1,"1217":1,"1229":1,"1237":3,"1259":4,"1274":1,"1285":6,"1286":1,"1293":1,"1299":4,"1309":1,"1310":1,"1311":1,"1322":3,"1323":13,"1338":3,"1341":1,"1348":1,"1349":4,"1358":5,"1368":2,"1375":1,"1379":2,"1394":4,"1395":8,"1399":1,"1401":1,"1409":1,"1412":1,"1414":4,"1415":1,"1416":7,"1417":1,"1425":1,"1426":1,"1435":2,"1437":2,"1440":1,"1475":1,"1590":1,"1595":1,"1596":1,"1661":1,"1662":1,"1683":1,"1685":1,"1688":1,"1723":1,"1728":1,"1786":1,"1793":1,"1850":1,"1865":1,"1888":1,"1905":1,"1929":1,"1944":1,"1949":1,"1964":1,"2044":1,"2062":1,"2080":1,"2099":1,"2108":1,"2125":1,"2127":1,"2161":1,"2175":1,"2182":1,"2184":1,"2199":1,"2208":1}}],["survivesblockcommentsineveryposition",{"3":{"142":1}}],["survives",{"0":{"1306":1,"1865":1},"3":{"0":4,"24":1,"31":1,"91":1,"96":1,"101":1,"248":1,"458":1,"472":1,"497":1,"506":1,"524":1,"592":1,"633":1,"635":1,"638":1,"690":1,"699":1,"715":1,"716":1,"732":1,"783":1,"855":1,"862":1,"881":1,"897":1,"905":1,"988":2,"992":1,"996":2,"1018":1,"1019":1,"1054":1,"1093":1,"1115":1,"1116":1,"1212":3,"1237":1,"1239":1,"1247":4,"1250":1,"1251":1,"1259":4,"1270":5,"1274":1,"1285":9,"1286":1,"1287":1,"1299":6,"1300":2,"1303":1,"1308":3,"1310":5,"1311":1,"1317":1,"1322":9,"1323":16,"1332":1,"1335":1,"1336":1,"1338":2,"1349":1,"1356":1,"1358":13,"1368":1,"1378":1,"1379":2,"1394":9,"1395":8,"1401":1,"1414":8,"1415":3,"1416":5,"1426":1,"1435":11,"1440":1,"1452":1,"1453":1,"1485":1,"1491":2,"1565":1,"1650":1,"1663":1,"1698":1,"1714":1,"1726":1,"1789":1,"1791":1,"1814":1,"1839":1,"1851":1,"1857":1,"1862":1,"1922":1,"1932":1,"1942":1,"1964":1,"1971":1,"2009":1,"2023":1,"2046":1,"2075":1,"2088":1,"2112":1,"2120":1,"2156":1,"2159":1,"2164":1}}],["sovereignty",{"3":{"1566":1}}],["sonnet",{"3":{"1495":2}}],["sonaranalyzer",{"3":{"1213":1,"1394":2,"1416":1,"1452":1,"1902":1}}],["sonar",{"3":{"1133":1,"1237":2,"1266":1,"1270":1,"1299":2,"1308":1,"1311":1,"1368":1,"1379":1,"1394":1,"1435":1,"1576":1,"1672":1}}],["soak",{"3":{"1455":1}}],["sothecontractisnotvacuous",{"2":{"1435":1},"3":{"1435":1}}],["soi",{"3":{"1285":1,"2125":1}}],["sock",{"3":{"1429":1}}],["sockets",{"3":{"1299":1,"1338":2,"1435":1}}],["socketshttphandler",{"2":{"53":1,"100":1,"1328":1,"1449":1,"1662":1,"1676":1,"2005":1,"2010":1,"2013":1,"2018":1,"2029":1},"3":{"53":1,"100":1,"1311":4,"1328":1,"1338":11,"1379":1,"1415":3,"1440":1,"1442":2,"1449":1,"1662":1,"1676":1,"2005":1,"2010":1,"2013":1,"2018":1,"2029":1}}],["socket",{"3":{"0":2,"94":1,"95":1,"100":1,"109":1,"110":1,"235":1,"881":1,"1067":1,"1225":1,"1322":1,"1323":2,"1328":1,"1333":1,"1338":14,"1394":2,"1395":3,"1414":1,"1429":1,"1435":1,"1442":2,"1576":1,"1610":1,"1652":1,"1701":1}}],["socialsecuritynumber",{"2":{"364":1},"3":{"359":1,"364":1}}],["social",{"3":{"349":1,"657":1,"691":1,"1020":1,"1299":2,"1310":1,"1323":3,"1341":1,"1349":6,"1350":1,"1358":10,"1363":1,"1368":4,"1379":4,"1382":1,"1391":1,"1394":7,"1414":2,"1415":4,"1466":1,"1628":2,"1629":2,"1630":4,"1631":1,"1635":2,"1638":1,"1640":2,"1657":1,"1704":1,"1972":1,"1973":1,"1976":1,"1978":3,"2109":2}}],["southeast",{"3":{"975":1}}],["south",{"3":{"972":1,"1338":1,"1466":1,"2237":1}}],["sound",{"3":{"209":1,"659":1,"811":1,"1051":1,"1285":1,"1300":1,"1322":1,"1454":2,"1548":1,"1869":1,"1922":1,"1970":1,"1996":1}}],["sounds",{"3":{"136":1,"1150":1,"1285":1,"1338":1,"1798":1,"1950":1,"2128":1}}],["sourcing",{"1":{"1138":1,"1139":1,"1140":1,"1141":1,"1142":1,"1143":1,"1144":1,"1145":1,"1146":1,"1147":1},"3":{"0":1,"638":1,"1138":1,"1139":1,"1140":1,"1141":2,"1142":1,"1144":1,"1145":1,"1212":1,"1259":1,"1322":1,"1323":1,"1358":3,"1395":1,"1401":1,"1430":1,"1435":1,"1495":1,"1576":1,"1584":1,"2231":1}}],["source=",{"3":{"1338":1,"1440":1}}],["sourcecollectinghostapplicationbuilder",{"3":{"1199":2,"1207":1}}],["sourcecollectingconfigurationmanager",{"3":{"1199":2,"1207":1}}],["sourcetype",{"3":{"846":1,"1247":1,"1269":1,"1270":2,"1435":1}}],["sourcedatabase",{"2":{"1474":1},"3":{"1473":1,"1474":3}}],["sourced",{"1":{"1779":1,"1780":1,"1781":1,"1782":1,"1783":1,"1784":1,"1785":1,"1786":1},"3":{"0":1,"373":1,"602":1,"631":1,"643":1,"1083":1,"1085":1,"1116":1,"1142":1,"1145":1,"1285":6,"1299":2,"1311":1,"1343":1,"1349":4,"1358":8,"1361":1,"1368":4,"1375":1,"1377":1,"1379":1,"1395":3,"1414":1,"1435":1,"1488":1,"1599":1,"1629":2,"1778":1,"1779":2,"2029":1,"2204":1}}],["sourcename",{"2":{"634":1},"3":{"0":1,"634":1,"698":1,"1285":4,"1322":1,"1491":1}}],["sourcesbrandcolorfromtoken",{"3":{"1435":2}}],["sources",{"3":{"0":3,"26":1,"46":1,"47":1,"166":1,"169":1,"186":1,"265":1,"369":1,"370":1,"399":1,"407":1,"413":1,"640":1,"676":1,"715":1,"717":1,"827":1,"838":1,"905":1,"926":1,"988":2,"989":1,"1014":1,"1069":1,"1144":2,"1175":1,"1214":1,"1247":4,"1259":1,"1276":2,"1279":4,"1280":3,"1285":29,"1299":2,"1300":1,"1309":4,"1310":12,"1322":4,"1332":1,"1334":2,"1337":1,"1338":7,"1352":1,"1358":8,"1374":1,"1394":4,"1401":2,"1414":1,"1416":2,"1428":1,"1435":7,"1436":1,"1437":1,"1441":1,"1452":2,"1453":1,"1487":1,"1488":2,"1525":1,"1533":1,"1547":1,"1568":1,"1629":2,"1630":1,"1638":1,"1659":1,"1664":1,"1669":1,"1674":1,"1773":2,"1789":1,"1832":1,"1839":1,"1840":1,"1844":1,"1848":2,"1851":3,"1852":1,"1857":1,"1858":1,"1860":1,"1862":1,"1865":1,"1867":1,"1961":1,"1964":1,"2005":1,"2021":1,"2023":1,"2152":1,"2155":2,"2159":1,"2187":1,"2219":1,"2231":1}}],["source",{"0":{"1329":1,"1334":1,"1450":1,"1469":1,"1483":1,"1499":1,"1858":1,"1874":1,"2241":1},"1":{"1172":1,"1173":1,"1174":1,"1175":1,"1176":1,"1177":1,"1178":1,"1179":1,"1180":1},"2":{"138":1,"468":1,"469":1,"474":1,"477":1,"805":1,"809":1,"813":1,"1150":1,"1474":2},"3":{"0":53,"1":1,"3":1,"5":1,"10":1,"12":1,"15":5,"17":2,"24":13,"25":4,"26":10,"27":6,"31":2,"34":1,"38":1,"39":4,"42":2,"43":2,"47":4,"53":1,"57":1,"59":6,"60":1,"61":1,"62":1,"63":3,"68":3,"71":2,"72":6,"73":1,"74":4,"79":1,"80":6,"81":3,"82":3,"90":1,"93":3,"94":2,"95":8,"96":5,"98":5,"99":1,"100":2,"103":3,"109":16,"111":1,"112":1,"117":6,"121":3,"122":3,"123":4,"125":5,"126":5,"128":4,"130":1,"132":1,"135":5,"136":1,"138":2,"141":3,"142":4,"145":2,"146":1,"147":5,"150":5,"151":1,"152":1,"157":2,"160":3,"161":3,"162":1,"164":1,"166":17,"168":2,"169":1,"170":3,"171":4,"175":3,"178":3,"179":6,"180":7,"181":2,"184":1,"186":5,"189":4,"190":5,"193":1,"195":9,"196":2,"197":1,"198":3,"200":2,"201":3,"202":7,"203":2,"216":2,"217":2,"218":1,"219":5,"220":5,"225":1,"228":4,"230":9,"231":4,"235":15,"237":2,"238":1,"243":8,"245":9,"246":7,"250":1,"251":1,"252":2,"254":1,"255":5,"256":1,"257":2,"259":3,"260":4,"261":1,"265":11,"266":1,"273":2,"277":1,"280":1,"282":1,"283":2,"284":2,"285":3,"290":7,"295":12,"296":3,"303":4,"305":7,"306":4,"310":3,"314":4,"318":9,"324":1,"325":3,"328":2,"330":9,"331":2,"333":5,"336":4,"337":4,"341":17,"342":2,"343":3,"344":6,"345":2,"350":5,"352":4,"353":7,"359":1,"369":1,"370":4,"371":1,"372":1,"373":1,"379":1,"384":7,"387":2,"388":3,"390":6,"391":7,"392":4,"393":1,"395":4,"397":11,"399":1,"400":3,"401":7,"402":3,"403":1,"404":5,"406":1,"407":5,"408":4,"409":3,"412":1,"413":11,"415":3,"416":2,"418":11,"419":1,"420":3,"422":1,"423":7,"424":3,"425":2,"426":8,"427":2,"428":10,"429":5,"430":4,"433":2,"434":1,"435":1,"440":1,"442":1,"443":2,"444":3,"448":11,"450":1,"451":1,"452":2,"453":1,"454":1,"459":13,"461":1,"463":6,"464":4,"465":5,"466":2,"468":3,"469":4,"470":14,"474":1,"475":10,"476":13,"477":13,"478":1,"482":2,"483":3,"484":3,"486":11,"487":7,"488":5,"489":7,"490":7,"491":8,"492":9,"493":12,"494":8,"495":5,"497":13,"498":1,"499":6,"502":4,"507":20,"509":1,"511":6,"512":1,"513":5,"514":2,"522":2,"523":3,"524":2,"527":1,"528":1,"530":1,"535":1,"536":4,"537":1,"539":3,"544":2,"545":26,"547":1,"549":7,"550":3,"551":2,"552":4,"554":1,"556":25,"559":1,"564":7,"572":4,"576":3,"577":4,"583":33,"585":12,"586":5,"587":2,"592":1,"597":1,"599":6,"601":2,"603":3,"609":2,"616":2,"618":2,"621":1,"626":1,"629":2,"632":3,"633":3,"634":1,"636":1,"638":2,"640":14,"642":3,"644":3,"649":13,"651":1,"656":2,"657":17,"665":7,"668":4,"669":3,"672":4,"674":45,"675":3,"676":15,"677":2,"680":1,"682":1,"685":1,"690":1,"696":3,"697":2,"698":24,"699":3,"700":2,"701":4,"702":10,"703":9,"706":1,"707":9,"708":1,"709":2,"711":2,"715":6,"717":1,"720":3,"721":3,"724":5,"725":11,"727":3,"728":2,"731":1,"732":1,"733":8,"736":4,"740":5,"741":10,"742":1,"743":2,"744":2,"749":11,"751":2,"774":12,"777":1,"780":3,"782":19,"784":2,"789":4,"790":8,"792":3,"793":6,"794":4,"798":9,"799":8,"803":3,"804":5,"805":4,"806":1,"809":3,"813":4,"818":1,"819":16,"822":2,"825":1,"826":7,"827":18,"830":7,"831":3,"832":6,"833":14,"836":1,"837":5,"838":11,"839":2,"840":1,"841":2,"842":1,"846":9,"849":1,"850":1,"854":7,"857":4,"861":2,"863":1,"868":3,"869":6,"872":5,"873":3,"876":17,"877":4,"880":1,"881":14,"883":1,"884":1,"888":2,"889":1,"891":4,"892":1,"897":10,"900":2,"904":1,"905":13,"908":3,"909":1,"910":1,"912":1,"913":6,"914":2,"915":1,"916":1,"921":3,"922":9,"923":1,"924":3,"926":10,"929":1,"930":1,"931":7,"935":1,"939":1,"946":10,"949":1,"954":4,"959":1,"960":2,"963":4,"964":4,"966":1,"971":1,"972":6,"975":2,"979":5,"980":7,"982":2,"987":3,"988":17,"989":1,"990":4,"992":1,"995":4,"996":5,"998":1,"999":2,"1000":1,"1003":1,"1004":6,"1006":1,"1012":1,"1017":1,"1018":2,"1021":1,"1025":5,"1026":14,"1028":1,"1029":1,"1033":1,"1034":13,"1035":1,"1036":1,"1041":6,"1042":21,"1043":2,"1044":6,"1045":2,"1049":4,"1050":17,"1051":1,"1052":2,"1054":2,"1055":3,"1058":4,"1059":27,"1061":2,"1062":1,"1066":4,"1067":4,"1070":1,"1073":1,"1074":7,"1075":1,"1078":1,"1082":6,"1083":7,"1085":1,"1086":3,"1089":1,"1090":2,"1091":9,"1094":1,"1095":1,"1099":2,"1100":6,"1103":3,"1108":6,"1111":1,"1116":17,"1119":1,"1123":3,"1124":2,"1132":1,"1133":7,"1134":1,"1135":4,"1140":10,"1142":1,"1144":1,"1150":14,"1160":2,"1161":3,"1163":1,"1168":16,"1172":1,"1174":1,"1175":27,"1177":1,"1183":4,"1184":18,"1186":2,"1190":4,"1191":1,"1192":6,"1193":1,"1196":1,"1202":3,"1203":3,"1204":3,"1211":2,"1212":27,"1213":2,"1214":2,"1219":4,"1220":1,"1222":3,"1223":2,"1224":1,"1225":6,"1226":2,"1227":3,"1228":3,"1229":59,"1231":6,"1232":5,"1233":8,"1234":13,"1235":2,"1236":8,"1237":163,"1238":1,"1239":18,"1240":4,"1241":5,"1242":4,"1243":10,"1244":5,"1245":2,"1246":3,"1247":152,"1251":1,"1252":1,"1254":2,"1256":1,"1257":1,"1259":119,"1261":2,"1262":6,"1263":6,"1264":9,"1265":3,"1267":5,"1268":1,"1269":5,"1270":270,"1271":103,"1272":1,"1273":13,"1274":2,"1275":7,"1276":12,"1277":7,"1278":19,"1279":14,"1280":6,"1281":1,"1282":5,"1283":5,"1284":7,"1285":790,"1287":8,"1288":7,"1289":10,"1290":12,"1291":8,"1292":9,"1293":9,"1294":6,"1295":5,"1296":30,"1297":7,"1298":2,"1299":944,"1300":77,"1301":6,"1302":8,"1303":11,"1304":7,"1305":9,"1306":16,"1307":8,"1308":206,"1309":78,"1310":445,"1311":70,"1312":1,"1313":3,"1314":4,"1315":8,"1316":11,"1317":10,"1318":1,"1319":28,"1320":3,"1321":15,"1322":366,"1323":523,"1324":9,"1325":11,"1326":4,"1327":4,"1328":1,"1329":3,"1330":5,"1331":19,"1332":7,"1333":10,"1334":7,"1335":8,"1336":6,"1337":10,"1338":360,"1340":3,"1341":12,"1343":7,"1344":5,"1345":1,"1346":17,"1347":5,"1348":10,"1349":352,"1352":2,"1353":1,"1358":923,"1360":1,"1361":2,"1362":3,"1363":1,"1365":1,"1367":1,"1368":82,"1370":6,"1371":1,"1372":5,"1373":1,"1374":1,"1375":1,"1376":7,"1377":8,"1378":4,"1379":183,"1380":1,"1383":3,"1388":1,"1389":2,"1394":362,"1395":576,"1401":118,"1402":1,"1403":4,"1404":1,"1405":4,"1406":2,"1408":1,"1409":1,"1411":1,"1412":1,"1413":1,"1414":370,"1415":31,"1416":143,"1418":2,"1419":6,"1420":1,"1421":5,"1422":5,"1423":6,"1424":6,"1425":1,"1426":58,"1427":1,"1428":3,"1429":1,"1430":4,"1432":1,"1434":1,"1435":685,"1436":1,"1437":15,"1439":3,"1440":9,"1441":2,"1442":6,"1443":6,"1444":9,"1446":2,"1448":5,"1450":2,"1452":4,"1453":4,"1454":12,"1458":2,"1460":1,"1461":1,"1463":16,"1465":1,"1466":16,"1468":2,"1469":2,"1472":1,"1473":3,"1474":3,"1475":1,"1476":1,"1477":2,"1481":1,"1483":2,"1484":2,"1485":4,"1486":2,"1487":27,"1488":8,"1489":3,"1490":1,"1491":12,"1493":1,"1494":1,"1495":72,"1496":1,"1499":5,"1503":2,"1508":1,"1509":2,"1511":1,"1520":1,"1521":1,"1525":22,"1530":6,"1533":3,"1534":3,"1544":1,"1546":1,"1547":1,"1552":2,"1553":1,"1555":1,"1568":1,"1569":1,"1571":1,"1572":1,"1575":1,"1576":43,"1577":1,"1580":1,"1581":18,"1582":6,"1584":2,"1585":7,"1586":1,"1590":7,"1593":1,"1595":1,"1605":1,"1613":1,"1618":3,"1629":1,"1630":3,"1631":16,"1635":1,"1636":2,"1637":1,"1638":4,"1639":1,"1640":2,"1645":1,"1647":4,"1648":1,"1649":4,"1650":4,"1651":5,"1652":7,"1653":1,"1654":1,"1655":6,"1658":1,"1660":2,"1663":12,"1664":1,"1670":2,"1672":6,"1674":5,"1683":1,"1684":5,"1685":5,"1686":1,"1694":2,"1696":1,"1707":1,"1709":1,"1711":1,"1717":1,"1718":3,"1721":3,"1726":4,"1727":6,"1728":3,"1730":2,"1733":1,"1735":1,"1739":1,"1754":2,"1763":27,"1764":31,"1769":4,"1772":3,"1776":2,"1777":1,"1782":1,"1786":2,"1789":3,"1791":1,"1792":1,"1801":1,"1804":2,"1806":1,"1809":1,"1811":1,"1814":3,"1816":1,"1826":1,"1830":5,"1833":2,"1837":1,"1839":4,"1840":1,"1844":3,"1845":1,"1848":9,"1850":8,"1851":8,"1852":3,"1856":1,"1858":1,"1859":2,"1860":2,"1861":2,"1864":5,"1865":3,"1866":2,"1867":2,"1874":1,"1876":3,"1885":1,"1888":1,"1890":1,"1891":1,"1894":1,"1898":1,"1901":1,"1904":1,"1909":1,"1911":1,"1920":1,"1923":1,"1926":2,"1929":1,"1932":1,"1940":1,"1941":1,"1947":1,"1949":1,"1950":1,"1954":1,"1957":1,"1958":4,"1961":1,"1964":1,"1971":1,"1978":1,"1982":1,"1984":1,"1986":1,"1987":3,"1990":1,"1993":6,"1994":1,"1996":1,"1998":1,"1999":8,"2000":2,"2001":4,"2004":1,"2007":2,"2012":2,"2013":1,"2023":2,"2027":1,"2029":1,"2032":1,"2035":1,"2037":1,"2040":2,"2042":2,"2043":4,"2045":1,"2048":1,"2052":6,"2059":1,"2063":2,"2064":2,"2065":5,"2068":1,"2069":1,"2071":3,"2073":1,"2074":2,"2075":1,"2078":2,"2079":5,"2081":1,"2082":3,"2084":1,"2085":3,"2089":1,"2096":1,"2097":1,"2105":1,"2107":1,"2110":1,"2114":2,"2116":1,"2122":2,"2125":1,"2126":1,"2127":1,"2132":1,"2135":1,"2138":1,"2141":1,"2145":1,"2146":1,"2152":1,"2155":4,"2156":5,"2158":1,"2159":2,"2168":1,"2171":1,"2178":1,"2179":1,"2187":1,"2188":1,"2190":1,"2191":4,"2192":1,"2198":1,"2201":3,"2202":1,"2210":1,"2211":1,"2214":1,"2215":1,"2220":1,"2225":2,"2229":3,"2231":6,"2233":1,"2235":1,"2238":1,"2244":2}}],["someday",{"3":{"1435":1}}],["somefixture",{"3":{"1435":3}}],["somehandler",{"3":{"1395":1}}],["somehow",{"3":{"1334":1}}],["somerandomtype",{"3":{"1379":1}}],["somerandomhandler",{"3":{"1322":2}}],["someincidentalclass",{"3":{"1310":1}}],["someintegrationevent",{"2":{"1252":1},"3":{"1252":1}}],["someid",{"3":{"1237":1}}],["sometimes",{"3":{"1344":1,"1358":1,"1435":1,"2098":1}}],["sometime",{"3":{"1077":1}}],["somethingbroke",{"3":{"1435":4}}],["something",{"3":{"1":1,"79":1,"81":1,"103":1,"108":1,"110":1,"135":1,"138":1,"139":1,"140":1,"147":1,"150":1,"175":1,"258":1,"290":1,"449":1,"539":1,"626":1,"627":1,"681":1,"683":1,"734":1,"740":1,"751":1,"758":1,"781":1,"793":1,"799":1,"812":1,"817":1,"860":2,"905":1,"931":1,"989":2,"1019":1,"1059":1,"1066":1,"1067":1,"1068":2,"1074":1,"1102":1,"1140":1,"1220":1,"1237":2,"1247":1,"1248":1,"1259":4,"1265":1,"1270":2,"1280":1,"1285":12,"1291":1,"1292":1,"1299":7,"1301":1,"1308":5,"1310":4,"1319":1,"1322":9,"1323":16,"1338":3,"1343":1,"1349":4,"1351":1,"1358":17,"1367":1,"1368":1,"1379":1,"1388":1,"1394":4,"1395":12,"1401":3,"1408":1,"1414":5,"1415":1,"1416":9,"1426":2,"1434":1,"1435":19,"1442":2,"1450":1,"1454":1,"1458":1,"1459":1,"1466":1,"1477":1,"1485":1,"1491":3,"1511":1,"1636":1,"1650":1,"1652":1,"1683":1,"1685":2,"1692":1,"1786":1,"1793":1,"1803":1,"1814":2,"1816":2,"1835":1,"1841":1,"1846":1,"1864":1,"1866":1,"1880":2,"1889":1,"1899":1,"1912":1,"1932":1,"1942":2,"1948":1,"1951":1,"1962":1,"1964":1,"1981":1,"2001":2,"2015":1,"2033":1,"2042":1,"2048":1,"2055":1,"2063":1,"2093":1,"2097":1,"2098":1,"2115":1,"2117":1,"2123":2,"2125":1,"2127":1,"2131":1,"2137":3,"2156":1,"2157":1,"2180":1,"2181":1,"2182":1,"2202":1,"2231":1}}],["somebody",{"3":{"896":1,"1082":1,"1083":1,"1134":1,"1229":1,"1285":1,"1305":1,"1322":2,"1323":1,"1358":1,"1395":2,"1414":2,"1421":1,"1426":1,"1435":2,"1464":1,"1474":1,"1950":1,"2038":1,"2042":1,"2045":1,"2046":1,"2047":1,"2063":1,"2152":1,"2157":1,"2169":2,"2173":1,"2179":2,"2193":1,"2194":1,"2195":1,"2201":1,"2202":1}}],["someone",{"3":{"160":1,"215":1,"255":1,"318":1,"342":1,"343":1,"372":1,"373":1,"387":1,"407":1,"435":1,"538":1,"574":1,"592":1,"610":1,"627":1,"634":1,"692":1,"715":1,"717":1,"759":1,"765":1,"767":2,"812":1,"820":1,"821":1,"828":1,"855":1,"890":1,"965":1,"974":1,"1052":1,"1085":1,"1102":1,"1160":1,"1237":2,"1247":2,"1263":1,"1270":1,"1285":4,"1299":3,"1308":3,"1310":2,"1322":5,"1323":7,"1331":1,"1338":2,"1349":1,"1358":6,"1368":1,"1394":2,"1395":11,"1401":1,"1414":8,"1416":2,"1435":14,"1466":1,"1471":1,"1653":1,"1717":1,"1787":1,"1793":2,"1796":1,"1799":2,"1803":1,"1808":1,"1812":1,"1814":1,"1816":1,"1817":1,"1827":1,"1871":1,"1877":1,"1899":2,"1900":1,"1913":1,"1946":2,"1959":2,"1965":1,"1975":1,"1992":1,"1994":1,"1996":1,"2028":1,"2032":1,"2044":2,"2047":1,"2066":1,"2072":1,"2088":1,"2098":1,"2117":1,"2128":1,"2143":1,"2146":1,"2147":1,"2153":1}}],["someerror",{"3":{"109":1,"1220":1,"1229":1}}],["some",{"3":{"6":1,"52":1,"134":1,"188":1,"318":1,"358":1,"412":1,"517":1,"537":1,"574":1,"585":1,"676":1,"725":1,"757":1,"759":2,"800":1,"848":1,"891":1,"974":1,"995":1,"1052":1,"1135":1,"1143":1,"1153":1,"1215":1,"1216":1,"1247":1,"1271":3,"1285":4,"1299":3,"1309":1,"1310":1,"1311":1,"1319":1,"1322":2,"1323":6,"1338":2,"1349":2,"1358":6,"1368":1,"1379":4,"1394":1,"1395":3,"1414":3,"1415":2,"1416":3,"1435":10,"1437":1,"1452":2,"1453":1,"1454":1,"1466":1,"1475":1,"1497":1,"1525":1,"1530":1,"1536":2,"1626":1,"1630":1,"1653":1,"1707":1,"1775":1,"1800":1,"1813":2,"1815":1,"1816":1,"1828":1,"1913":1,"1985":1,"2058":1,"2072":1,"2078":1,"2113":1,"2121":1,"2135":1,"2139":1,"2201":1}}],["somewhere",{"3":{"0":1,"138":1,"150":1,"157":1,"255":1,"544":1,"609":1,"718":1,"809":1,"921":1,"989":1,"995":1,"1027":1,"1109":1,"1235":1,"1237":1,"1271":1,"1285":2,"1310":1,"1323":3,"1349":1,"1358":2,"1394":2,"1395":5,"1415":2,"1416":1,"1422":1,"1431":1,"1435":6,"1466":1,"1487":1,"1530":1,"1574":1,"1817":1,"1908":1,"1921":1,"1953":1,"1966":2,"1971":1,"1986":1,"2043":1,"2115":1,"2157":1,"2164":1,"2174":1,"2181":1,"2184":1,"2196":1}}],["soonest",{"3":{"1347":2,"1349":1}}],["sooner",{"3":{"19":1,"640":1,"1435":1}}],["soon",{"3":{"0":1,"234":1,"827":1,"1116":1,"1278":1,"1322":2,"1323":2,"1338":1,"1358":1,"1395":1,"1432":1,"1435":2,"1552":1,"1630":1,"1901":1,"2156":1,"2184":1,"2188":1}}],["solo",{"3":{"1685":1}}],["sold",{"3":{"1339":1,"1341":1,"1345":1,"1349":4,"1358":2,"1379":1,"1394":5}}],["solvable",{"3":{"1637":1}}],["solving",{"3":{"1323":1,"1357":1,"1442":1,"1538":1}}],["solve",{"3":{"390":1,"811":1,"948":1,"990":1,"1043":1,"1218":1,"1229":4,"1247":1,"1309":1,"1349":1,"1394":1,"1395":1,"1414":2,"1416":1,"1487":1,"1858":1,"1941":1,"2180":1,"2210":1}}],["solves",{"3":{"272":1,"555":1,"971":2,"1237":1,"1247":1,"1259":1,"1270":2,"1297":1,"1299":1,"1323":2,"1358":3,"1394":1,"1395":1,"1416":1,"1435":1,"1442":1,"1446":1,"1817":1,"1858":1,"1909":1,"2075":1,"2123":1}}],["solved",{"3":{"26":1,"601":1,"708":1,"827":1,"990":1,"1115":1,"1310":1,"1311":1,"1322":1,"1338":1,"1379":1,"1394":1,"1395":4,"1414":1,"1416":1,"1435":1,"1899":4,"1995":1,"2085":1,"2161":1}}],["solid",{"0":{"1537":1},"3":{"1216":1,"1229":1,"1234":1,"1237":4,"1241":1,"1244":1,"1247":4,"1259":4,"1261":1,"1264":1,"1270":16,"1271":5,"1278":1,"1285":12,"1290":1,"1299":19,"1300":1,"1308":9,"1309":2,"1310":15,"1315":1,"1321":1,"1322":12,"1323":15,"1338":1,"1342":1,"1349":5,"1352":1,"1358":60,"1368":2,"1370":1,"1379":6,"1383":1,"1394":17,"1395":19,"1401":8,"1404":1,"1414":8,"1415":2,"1416":16,"1426":5,"1430":1,"1435":11,"1498":1,"1525":1,"1534":1,"1576":1,"1580":1,"1581":1,"1582":1,"1583":1,"1587":1,"1590":1,"1596":1,"1783":1,"1794":1,"1795":1,"1798":1,"1970":1,"2089":1}}],["solutions",{"3":{"556":1,"633":1,"1484":1,"1485":1,"1653":1,"1695":1,"1725":1,"1730":1,"2088":1,"2219":1,"2223":1}}],["solutionfilename",{"2":{"136":1},"3":{"136":1,"1430":1,"1435":5}}],["solution",{"0":{"1215":1,"1485":1,"1648":1,"1682":1,"1694":1,"2088":1,"2119":1},"1":{"1484":1,"1485":1,"1486":1,"1487":1,"1488":1,"1489":1,"1490":1,"1491":1,"1492":1,"1493":1},"3":{"0":4,"132":1,"135":1,"137":2,"140":1,"142":1,"370":2,"470":2,"509":1,"609":1,"631":1,"632":2,"633":7,"634":1,"635":2,"733":1,"863":1,"870":1,"883":1,"905":1,"906":2,"938":1,"939":2,"942":1,"979":1,"980":1,"1034":1,"1067":2,"1190":1,"1192":1,"1212":1,"1215":3,"1237":1,"1247":1,"1259":1,"1271":1,"1285":2,"1295":1,"1299":5,"1308":3,"1310":1,"1322":4,"1349":2,"1358":1,"1395":12,"1401":2,"1427":2,"1435":9,"1440":1,"1444":1,"1452":5,"1453":6,"1454":3,"1484":1,"1485":8,"1486":12,"1487":2,"1489":3,"1491":2,"1493":2,"1523":1,"1525":2,"1530":1,"1576":2,"1590":1,"1595":1,"1598":1,"1601":2,"1645":3,"1646":1,"1648":2,"1649":1,"1653":1,"1655":1,"1656":1,"1657":1,"1660":8,"1661":1,"1673":1,"1674":1,"1680":1,"1683":4,"1684":4,"1685":4,"1689":1,"1691":2,"1693":1,"1694":1,"1699":1,"1700":2,"1702":1,"1703":2,"1704":2,"1706":1,"1716":1,"1717":3,"1721":3,"1724":2,"1725":4,"1726":6,"1727":8,"1728":10,"1729":1,"1730":4,"1780":2,"1809":1,"1828":1,"1938":1,"2043":1,"2055":1,"2086":2,"2088":1,"2096":5,"2097":4,"2098":2,"2099":1,"2100":3,"2102":1,"2104":1,"2210":1,"2219":1,"2223":1,"2238":1}}],["solely",{"3":{"77":1,"149":1,"776":1,"1322":2,"1323":1,"1324":1,"1358":3,"1368":1,"1395":1,"1414":1,"1485":1,"1487":1,"1640":1,"1886":1}}],["sole",{"1":{"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"294":1,"295":1,"296":1},"3":{"0":6,"20":1,"288":1,"291":3,"295":1,"296":2,"370":1,"563":1,"620":1,"756":2,"757":1,"762":1,"827":1,"833":1,"1212":1,"1229":1,"1237":1,"1247":1,"1270":2,"1285":2,"1310":2,"1323":2,"1349":1,"1358":5,"1395":6,"1414":1,"1415":1,"1416":1,"1435":5,"1454":1,"1466":2,"1468":1,"1472":1,"1475":1,"1476":1,"1487":2,"1495":3,"1533":1,"1651":1,"1663":1,"1749":1,"2231":2}}],["sortability",{"3":{"1394":1}}],["sortabletemplatecolumngrid",{"3":{"1435":2}}],["sortabletemplatecolumn",{"3":{"1435":1}}],["sortablegridcolumnsusepropertycolumn",{"3":{"1435":3}}],["sortable=",{"3":{"1394":1,"1435":5}}],["sortablecolumns",{"3":{"1435":2}}],["sortablecolumnconventiontests",{"3":{"1199":1,"1207":2,"1435":9,"1488":1}}],["sortablecolumnconventiontestsbase",{"3":{"1199":2,"1207":2,"1435":8}}],["sortablecolumnfitnesstests",{"3":{"1199":1,"1207":2,"1435":3,"1488":1}}],["sortable",{"3":{"332":1,"1247":1,"1285":1,"1310":1,"1323":2,"1349":1,"1358":3,"1382":1,"1394":10,"1395":1,"1414":4,"1435":14,"1488":1,"1628":1,"1639":2,"1670":1,"1747":1,"1764":1,"1987":1,"1989":1}}],["sortorder",{"3":{"1341":1,"1349":10,"1358":18,"1361":2,"1368":5,"1394":9}}],["sortby",{"3":{"1323":1}}],["sortproperty",{"3":{"1285":4}}],["sort=",{"3":{"1238":1,"1814":1}}],["sorttestentity",{"3":{"1199":2,"1207":1}}],["sortdescending",{"3":{"1323":1}}],["sortdefinition",{"3":{"881":1,"1323":1}}],["sortdirection",{"3":{"330":1,"740":1,"741":1,"881":1,"1247":2,"1310":1,"1323":2,"1394":2,"1395":1,"1414":4,"1639":1,"1987":1}}],["sorts",{"3":{"583":1,"633":1,"741":1,"1242":1,"1247":3,"1309":1,"1310":1,"1312":1,"1322":2,"1341":1,"1349":4,"1358":7,"1361":1,"1368":1,"1394":4,"1395":1,"1401":1,"1414":2,"1435":5,"1598":1,"1698":2,"1726":2,"1814":1,"1877":1,"1878":1,"1885":1,"2088":1}}],["sortkey",{"3":{"549":1,"1278":1,"1285":3,"1576":1}}],["sortvalue",{"3":{"549":1,"1228":1,"1229":3,"1285":1}}],["sorted",{"3":{"359":1,"549":1,"1059":1,"1200":1,"1238":1,"1242":1,"1247":3,"1270":1,"1285":2,"1299":8,"1314":1,"1322":1,"1323":2,"1349":4,"1353":1,"1358":2,"1368":1,"1370":1,"1394":9,"1395":2,"1401":2,"1426":1,"1435":5,"1495":1,"1497":1,"1876":1,"1987":1}}],["sortcolumn",{"2":{"42":1},"3":{"42":1,"330":1,"549":1,"740":1,"741":1,"881":1,"1228":1,"1229":2,"1247":4,"1285":1,"1310":1,"1323":3,"1368":1,"1394":2,"1395":1,"1414":4,"1639":2,"1764":1,"1987":1}}],["sorting",{"0":{"1242":1},"3":{"0":1,"319":1,"329":1,"333":1,"363":1,"545":1,"549":2,"591":1,"742":1,"1050":2,"1202":1,"1203":1,"1238":2,"1243":2,"1245":1,"1247":6,"1283":1,"1299":1,"1310":2,"1349":3,"1353":1,"1358":7,"1361":1,"1368":2,"1379":2,"1394":4,"1395":5,"1401":1,"1414":4,"1435":4,"1437":1,"1525":2,"1545":1,"1556":1,"1559":1,"1576":1,"1629":1,"1630":1,"1631":2,"1638":1,"1639":3,"1665":1,"1668":1,"1747":2,"1814":1,"1815":1,"1816":1,"1986":1,"1987":1,"1996":1}}],["sort",{"2":{"1352":1},"3":{"0":5,"122":1,"243":1,"328":1,"329":1,"330":1,"331":1,"361":1,"547":1,"549":8,"583":4,"585":2,"595":1,"633":1,"881":4,"882":1,"1050":2,"1053":1,"1192":1,"1212":1,"1228":2,"1229":9,"1242":4,"1243":6,"1244":3,"1245":1,"1247":22,"1259":1,"1263":1,"1270":1,"1278":2,"1285":17,"1314":1,"1322":5,"1323":22,"1341":1,"1343":1,"1349":31,"1352":1,"1353":1,"1356":2,"1358":120,"1361":2,"1368":15,"1379":15,"1382":1,"1391":3,"1394":42,"1395":8,"1401":14,"1405":3,"1414":11,"1435":5,"1437":5,"1495":1,"1497":1,"1525":2,"1599":1,"1606":1,"1629":5,"1630":3,"1631":8,"1638":5,"1639":9,"1668":1,"1674":1,"1685":1,"1688":2,"1698":1,"1714":1,"1719":1,"1726":2,"1740":1,"1749":1,"1750":1,"1768":1,"1772":1,"1814":2,"1880":2,"1885":2,"1922":1,"1927":1,"1987":2,"1990":3,"2069":3,"2071":2,"2075":1,"2078":1,"2079":1,"2088":2,"2143":1,"2144":1,"2231":1}}],["softer",{"3":{"1395":2}}],["softens",{"3":{"803":1}}],["softdeletingfixture",{"3":{"1199":2,"1207":1,"1435":5}}],["softdeletabletestentity",{"3":{"1199":3,"1207":1}}],["softdeletableentity",{"3":{"1199":3,"1207":1}}],["softdeletepredicate",{"3":{"1285":1}}],["softdeletefidelitytests",{"2":{"1534":1},"3":{"1199":1,"1207":2,"1534":1}}],["softdeletefilteronly",{"3":{"698":1}}],["softdeletefiltername",{"2":{"697":1},"3":{"697":1,"698":1,"1285":1}}],["softdeletefiltersql",{"2":{"889":2,"892":3,"1034":1,"1176":1},"3":{"0":2,"889":7,"891":1,"892":6,"1034":3,"1175":1,"1176":1,"1199":3,"1203":1,"1207":2,"1280":2,"1285":16,"1368":1,"1414":2,"1495":1}}],["softdeletequeryfiltertests",{"3":{"1199":1,"1207":5}}],["softdeleteenforcementtests",{"3":{"1199":2,"1207":4,"1285":4,"1435":13,"1488":2,"1576":1}}],["softdeleteenforcementtestsbase",{"3":{"39":2,"1199":3,"1207":2,"1435":10}}],["softdeleteenforcementfitnesstests",{"3":{"1199":1,"1207":3,"1435":22,"1488":1}}],["softdeletetestdbcontext",{"3":{"1199":6,"1207":2}}],["softdeleted",{"3":{"1285":1,"1395":1,"1401":1}}],["softdeletedmarkerfailed",{"2":{"459":1,"465":1},"3":{"459":1,"465":1,"1322":5,"1414":1}}],["softdeleteduserfilter",{"2":{"464":1,"1310":1,"2065":1},"3":{"459":2,"464":2,"466":1,"698":1,"749":2,"1310":3,"1435":1,"1665":1,"2065":1}}],["softdeleteduservalidatortests",{"3":{"1199":2,"1207":4,"1322":1,"1437":2}}],["softdeleteduservalidator",{"2":{"460":1,"463":1},"3":{"0":1,"459":4,"460":1,"463":3,"464":1,"465":1,"1199":4,"1203":1,"1207":2,"1299":1,"1321":3,"1322":8,"1403":1,"1414":3,"1495":2}}],["softdeletedusercachetests",{"3":{"1199":1,"1207":2,"1437":1}}],["softdeletedusercache",{"2":{"459":3,"461":2,"463":1,"2065":1},"3":{"0":1,"459":7,"461":4,"463":3,"464":1,"465":1,"1199":7,"1203":1,"1207":2,"1299":10,"1300":3,"1310":6,"1322":2,"1402":1,"1437":1,"2065":2}}],["softdeletedusermiddlewaretests",{"2":{"459":1,"466":1},"3":{"459":3,"463":1,"464":1,"466":2,"1199":1,"1207":2}}],["softdeletedusermiddleware",{"2":{"463":1,"464":1,"748":1,"752":1,"1922":1,"2065":1,"2068":1},"3":{"0":1,"459":7,"461":1,"463":4,"464":1,"465":1,"748":1,"749":4,"752":1,"1199":3,"1203":1,"1207":2,"1212":1,"1299":5,"1300":2,"1310":10,"1322":2,"1414":2,"1922":1,"2065":5,"2068":1}}],["softdeleteuniqueindexconventiontests",{"3":{"890":1,"891":1,"1199":1,"1207":8,"1285":1,"1437":1}}],["softdeleteuniqueindexconvention",{"2":{"889":1,"1212":1,"1280":1,"1361":1,"1411":1},"3":{"0":1,"888":1,"889":8,"890":1,"891":2,"892":1,"1199":2,"1203":1,"1207":2,"1212":1,"1280":3,"1285":28,"1361":2,"1368":9,"1395":7,"1411":1,"1414":4,"1495":2,"1576":1}}],["softdelete",{"3":{"0":2,"39":3,"549":2,"551":1,"699":1,"701":1,"1207":1,"1212":2,"1232":1,"1237":1,"1240":1,"1247":1,"1273":1,"1276":1,"1285":6,"1435":13,"1437":1,"1488":1,"1663":1,"1850":2}}],["softwarefactory",{"3":{"1394":1}}],["software",{"1":{"2211":1,"2212":1,"2213":1,"2214":1,"2215":1},"3":{"370":1,"1394":2,"1453":2,"1672":1,"1786":1,"1792":1,"1801":1,"1806":1,"1811":1,"1816":1,"1826":1,"1833":1,"1845":1,"1852":1,"1861":1,"1867":1,"1876":1,"1885":1,"1891":1,"1898":1,"1904":1,"1923":1,"1929":1,"1940":1,"1949":1,"1958":1,"1964":1,"1978":1,"1984":1,"1990":1,"1996":1,"2001":1,"2027":1,"2037":1,"2048":1,"2059":1,"2068":1,"2079":1,"2085":1,"2097":1,"2107":1,"2114":1,"2122":1,"2127":1,"2132":1,"2138":1,"2168":1,"2179":1,"2192":1,"2202":1,"2210":1,"2211":1,"2212":1,"2216":1,"2217":1,"2219":8}}],["soft",{"1":{"36":1,"37":1,"38":1,"39":1,"40":1,"41":1,"42":1,"43":1,"456":1,"457":1,"458":1,"459":1,"460":1,"461":1,"462":1,"463":1,"464":1,"465":1,"466":1,"886":1,"887":1,"888":1,"889":1,"890":1,"891":1,"892":1,"893":1,"2060":1,"2061":1,"2062":1,"2063":1,"2064":1,"2065":1,"2066":1,"2067":1,"2068":1},"3":{"0":14,"36":1,"38":3,"39":2,"40":1,"41":2,"313":1,"318":1,"326":1,"346":1,"358":1,"361":1,"362":2,"366":1,"456":1,"458":3,"459":3,"461":3,"462":2,"503":1,"538":1,"545":1,"549":1,"550":1,"551":1,"690":2,"698":4,"699":2,"700":1,"707":2,"710":1,"714":1,"715":1,"716":3,"721":2,"733":1,"734":1,"735":1,"748":1,"749":2,"781":1,"782":1,"886":1,"888":3,"889":7,"890":7,"891":4,"893":1,"905":1,"910":1,"926":1,"950":1,"1033":2,"1034":1,"1059":1,"1061":1,"1063":1,"1082":2,"1084":1,"1086":1,"1087":1,"1116":1,"1118":1,"1120":1,"1141":1,"1142":1,"1146":1,"1147":1,"1168":4,"1174":2,"1175":4,"1180":1,"1192":2,"1201":2,"1212":6,"1213":1,"1230":2,"1231":10,"1232":2,"1236":2,"1237":29,"1240":1,"1244":1,"1247":9,"1270":3,"1273":1,"1274":1,"1275":1,"1276":2,"1278":2,"1280":6,"1285":77,"1286":1,"1288":1,"1289":1,"1299":16,"1300":4,"1303":1,"1305":1,"1308":2,"1310":8,"1322":16,"1323":1,"1341":2,"1342":4,"1345":1,"1346":1,"1349":46,"1351":1,"1357":1,"1358":76,"1360":1,"1361":4,"1362":1,"1368":26,"1379":3,"1394":4,"1395":76,"1397":2,"1398":1,"1401":36,"1404":3,"1405":1,"1411":2,"1414":31,"1416":3,"1430":2,"1435":34,"1437":11,"1452":1,"1479":1,"1486":2,"1488":2,"1489":2,"1495":3,"1525":2,"1533":1,"1534":1,"1544":2,"1566":3,"1571":1,"1576":4,"1585":2,"1590":2,"1611":1,"1613":1,"1617":2,"1629":3,"1630":11,"1631":29,"1632":4,"1633":7,"1634":13,"1635":1,"1636":1,"1637":14,"1638":51,"1639":6,"1640":2,"1650":2,"1652":1,"1656":1,"1661":1,"1663":1,"1666":1,"1670":2,"1671":3,"1674":1,"1684":2,"1685":1,"1697":1,"1699":1,"1703":1,"1716":1,"1718":1,"1719":1,"1726":1,"1746":2,"1747":6,"1748":1,"1764":4,"1768":7,"1778":1,"1783":1,"1794":1,"1795":1,"1798":1,"1809":2,"1811":1,"1814":1,"1824":1,"1838":2,"1850":3,"1922":1,"1963":1,"2033":1,"2059":1,"2060":4,"2061":1,"2062":2,"2063":2,"2065":4,"2067":1,"2068":6,"2089":1,"2097":1,"2163":2,"2166":1,"2167":1,"2185":1,"2201":1,"2209":1,"2220":1,"2229":2,"2231":6}}],["so",{"0":{"96":1,"2020":1,"2164":1},"3":{"0":224,"1":2,"5":1,"15":3,"18":1,"19":2,"20":1,"21":2,"22":1,"23":2,"24":15,"26":4,"27":4,"30":1,"31":10,"32":2,"33":2,"34":1,"39":11,"41":5,"42":1,"43":1,"45":1,"46":2,"47":2,"53":4,"54":2,"59":4,"62":2,"63":1,"68":1,"71":2,"72":3,"73":2,"76":2,"78":3,"79":1,"80":3,"81":4,"88":1,"91":5,"92":1,"93":4,"94":2,"95":4,"97":2,"98":9,"99":2,"100":5,"101":1,"107":2,"109":23,"110":4,"111":3,"117":3,"118":2,"119":1,"120":2,"121":5,"122":7,"123":1,"124":1,"125":2,"126":4,"127":2,"128":2,"130":2,"132":2,"135":21,"136":2,"137":2,"139":4,"141":2,"142":2,"145":6,"147":7,"149":1,"150":5,"151":1,"152":1,"155":1,"157":6,"158":2,"159":4,"160":7,"161":2,"164":2,"165":1,"166":3,"167":1,"168":1,"170":2,"171":1,"173":1,"174":2,"175":3,"177":3,"178":4,"179":2,"180":4,"181":1,"184":2,"185":1,"186":9,"187":2,"189":2,"191":1,"193":2,"195":10,"196":2,"197":2,"198":1,"200":8,"201":8,"202":6,"203":3,"206":1,"207":1,"208":2,"213":2,"214":4,"215":2,"216":3,"217":2,"218":1,"219":3,"220":4,"223":2,"225":8,"227":3,"228":2,"230":5,"231":2,"233":3,"234":3,"235":13,"236":3,"237":6,"241":3,"242":1,"243":12,"244":3,"245":4,"246":1,"248":3,"249":2,"250":1,"253":1,"254":3,"255":9,"256":1,"257":1,"258":1,"259":4,"260":2,"261":2,"263":1,"264":1,"265":31,"266":2,"267":3,"268":1,"272":1,"273":5,"274":3,"275":2,"276":2,"279":2,"280":3,"281":3,"282":2,"283":3,"284":1,"285":3,"286":3,"290":2,"291":4,"292":2,"293":1,"295":5,"298":1,"300":2,"301":2,"302":2,"303":3,"304":1,"305":3,"306":2,"309":2,"310":4,"311":1,"312":2,"314":2,"317":1,"318":13,"319":2,"320":3,"322":1,"324":2,"325":4,"326":3,"330":5,"331":1,"332":1,"333":2,"335":5,"337":1,"339":1,"341":13,"342":4,"343":5,"344":2,"349":1,"350":15,"351":2,"352":4,"353":4,"358":5,"359":5,"360":3,"361":4,"365":4,"370":8,"371":2,"373":3,"374":1,"375":1,"380":1,"383":4,"384":2,"388":5,"390":6,"391":2,"392":1,"393":1,"395":4,"396":2,"397":13,"398":2,"399":2,"400":2,"401":5,"402":2,"403":1,"404":2,"405":2,"407":6,"408":1,"409":2,"413":7,"415":2,"418":2,"419":1,"420":3,"423":1,"424":2,"425":1,"426":2,"427":2,"428":1,"434":2,"435":3,"436":1,"440":1,"441":1,"442":1,"443":3,"444":4,"446":1,"448":5,"449":3,"450":3,"451":3,"452":1,"454":1,"457":1,"458":2,"459":13,"460":4,"461":3,"463":2,"464":3,"465":4,"466":1,"468":3,"469":1,"470":2,"471":1,"477":1,"478":2,"481":1,"482":1,"483":2,"484":3,"486":3,"487":2,"488":2,"489":2,"490":4,"491":4,"492":5,"493":7,"494":5,"495":3,"497":2,"498":2,"499":5,"501":5,"502":1,"505":1,"506":1,"507":7,"508":3,"509":1,"511":6,"512":1,"513":2,"514":2,"517":2,"518":6,"519":1,"520":1,"522":3,"523":3,"524":4,"526":3,"527":3,"528":5,"529":3,"530":5,"531":1,"532":1,"534":10,"536":8,"537":4,"538":6,"539":5,"540":4,"543":3,"544":1,"545":7,"546":2,"547":6,"549":13,"550":4,"551":8,"552":2,"555":1,"556":9,"557":4,"558":6,"559":1,"562":1,"564":7,"565":3,"566":3,"567":1,"572":8,"573":2,"574":2,"575":3,"576":2,"578":1,"583":8,"584":1,"585":10,"586":1,"589":1,"590":2,"591":3,"592":3,"593":5,"594":1,"597":1,"599":12,"600":1,"601":4,"602":1,"603":1,"605":1,"607":11,"609":14,"610":4,"611":5,"612":3,"613":1,"616":1,"617":1,"618":10,"619":2,"620":4,"624":7,"626":18,"627":2,"628":4,"629":3,"631":2,"632":2,"633":10,"634":2,"635":5,"636":2,"638":8,"640":12,"641":3,"642":6,"644":2,"648":1,"649":9,"650":3,"651":4,"655":1,"656":2,"657":12,"658":5,"659":3,"660":6,"664":1,"665":8,"666":1,"667":4,"668":2,"672":1,"674":11,"675":1,"676":4,"677":1,"680":1,"681":2,"682":5,"683":1,"684":8,"690":7,"691":8,"692":11,"693":1,"696":2,"698":16,"699":2,"700":4,"702":3,"703":1,"706":2,"707":6,"708":1,"709":4,"714":2,"715":5,"716":5,"717":5,"718":2,"719":2,"720":1,"723":1,"725":11,"726":1,"727":5,"728":1,"731":1,"732":1,"733":8,"734":1,"735":3,"736":1,"739":1,"740":2,"741":11,"742":1,"743":4,"747":1,"748":1,"749":8,"750":1,"751":3,"755":1,"756":1,"757":7,"758":5,"759":7,"760":2,"761":1,"762":1,"764":4,"766":6,"767":2,"768":4,"769":1,"773":1,"774":5,"775":4,"776":5,"778":1,"780":1,"781":1,"782":3,"783":1,"784":4,"785":1,"789":4,"790":9,"791":2,"792":6,"793":4,"797":1,"798":2,"799":5,"801":2,"804":1,"805":2,"809":1,"810":4,"812":2,"813":2,"814":2,"819":11,"820":1,"821":5,"825":2,"826":7,"827":19,"828":2,"829":8,"830":1,"831":4,"832":4,"833":4,"836":1,"837":4,"838":10,"839":1,"840":1,"844":1,"845":1,"846":7,"848":3,"849":1,"853":3,"854":6,"856":4,"857":4,"861":10,"862":3,"863":9,"867":1,"868":1,"869":3,"870":2,"871":1,"872":2,"873":3,"874":1,"875":4,"876":5,"877":1,"881":14,"882":3,"883":3,"884":2,"888":1,"889":5,"890":2,"891":3,"896":1,"897":10,"898":1,"899":3,"903":1,"904":2,"905":22,"906":2,"907":7,"908":2,"909":1,"912":1,"914":5,"916":4,"917":1,"920":2,"921":2,"922":8,"923":1,"924":2,"926":11,"927":1,"929":1,"930":2,"931":4,"932":4,"938":1,"939":2,"941":2,"945":1,"946":4,"948":5,"952":1,"953":1,"954":11,"955":3,"956":4,"958":2,"959":1,"960":2,"963":3,"964":8,"965":1,"966":2,"967":1,"971":1,"972":6,"973":3,"974":5,"975":1,"979":1,"980":3,"981":1,"982":3,"983":2,"986":1,"987":1,"988":12,"989":2,"990":4,"991":1,"995":2,"996":10,"997":1,"998":4,"999":2,"1003":1,"1004":4,"1005":3,"1006":3,"1012":4,"1013":1,"1014":2,"1016":2,"1017":3,"1018":26,"1019":8,"1020":7,"1021":2,"1024":1,"1026":4,"1028":5,"1029":1,"1030":1,"1033":2,"1034":5,"1036":3,"1041":3,"1042":8,"1043":2,"1044":4,"1045":2,"1049":1,"1050":11,"1051":1,"1052":4,"1054":2,"1055":3,"1058":1,"1059":13,"1060":1,"1061":4,"1062":3,"1065":1,"1066":5,"1067":14,"1068":3,"1069":3,"1074":3,"1075":4,"1076":1,"1077":2,"1079":1,"1081":1,"1082":2,"1083":5,"1085":5,"1089":2,"1090":3,"1091":17,"1092":4,"1093":6,"1100":6,"1107":1,"1108":5,"1109":2,"1115":3,"1116":15,"1117":4,"1118":7,"1119":1,"1123":2,"1124":14,"1125":3,"1126":2,"1127":2,"1128":1,"1132":4,"1133":12,"1134":3,"1135":5,"1136":1,"1144":1,"1145":1,"1150":2,"1154":3,"1155":1,"1156":1,"1160":2,"1161":4,"1162":1,"1163":1,"1168":6,"1169":1,"1170":3,"1174":1,"1175":6,"1176":1,"1177":3,"1178":2,"1183":1,"1184":7,"1185":1,"1186":2,"1187":2,"1190":1,"1191":4,"1193":1,"1196":2,"1210":1,"1212":32,"1213":2,"1214":2,"1215":2,"1216":2,"1218":1,"1219":1,"1220":4,"1221":2,"1222":2,"1223":3,"1224":2,"1225":3,"1226":1,"1227":3,"1228":4,"1229":54,"1230":1,"1231":6,"1232":5,"1233":6,"1234":8,"1235":2,"1236":4,"1237":100,"1239":10,"1240":4,"1241":15,"1242":6,"1243":7,"1244":6,"1246":3,"1247":180,"1248":1,"1249":4,"1250":1,"1251":3,"1252":9,"1253":7,"1254":2,"1255":4,"1256":8,"1257":6,"1258":7,"1259":179,"1260":1,"1261":2,"1262":3,"1263":20,"1264":6,"1265":11,"1266":1,"1267":11,"1269":13,"1270":230,"1271":53,"1273":11,"1274":10,"1275":12,"1276":11,"1277":3,"1278":8,"1279":4,"1280":12,"1281":3,"1282":7,"1283":10,"1284":3,"1285":563,"1286":2,"1287":8,"1288":7,"1289":9,"1290":2,"1291":7,"1292":11,"1293":9,"1294":1,"1295":2,"1296":7,"1297":6,"1298":2,"1299":397,"1300":97,"1301":2,"1302":2,"1303":7,"1304":3,"1305":3,"1306":9,"1307":4,"1308":169,"1309":40,"1310":424,"1311":47,"1312":1,"1313":3,"1314":6,"1315":13,"1316":10,"1317":11,"1319":12,"1320":2,"1321":11,"1322":393,"1323":659,"1324":2,"1325":7,"1326":4,"1327":7,"1328":1,"1329":3,"1330":1,"1331":10,"1332":12,"1333":3,"1334":4,"1335":6,"1336":10,"1337":10,"1338":332,"1340":2,"1341":9,"1342":3,"1343":4,"1344":2,"1345":2,"1346":8,"1347":7,"1348":7,"1349":293,"1351":13,"1352":10,"1353":4,"1354":14,"1355":4,"1356":9,"1357":7,"1358":989,"1359":1,"1361":7,"1363":5,"1364":2,"1365":10,"1366":2,"1367":5,"1368":93,"1370":3,"1371":3,"1372":5,"1373":5,"1374":5,"1375":3,"1376":3,"1377":5,"1378":10,"1379":171,"1380":1,"1381":3,"1382":6,"1383":4,"1384":4,"1385":2,"1386":1,"1387":3,"1388":14,"1389":5,"1390":8,"1391":4,"1392":5,"1393":2,"1394":563,"1395":690,"1396":2,"1397":2,"1398":11,"1399":4,"1400":6,"1401":222,"1402":1,"1403":1,"1404":7,"1405":10,"1406":3,"1407":6,"1408":10,"1409":9,"1410":6,"1411":4,"1412":7,"1413":5,"1414":355,"1415":84,"1416":334,"1418":4,"1419":3,"1420":5,"1421":7,"1422":11,"1423":6,"1424":7,"1425":3,"1426":71,"1427":3,"1428":16,"1429":9,"1430":25,"1431":11,"1432":10,"1433":6,"1434":13,"1435":1021,"1436":1,"1437":92,"1439":2,"1440":35,"1441":13,"1442":23,"1443":9,"1444":13,"1445":2,"1446":20,"1448":7,"1449":1,"1451":2,"1452":15,"1453":9,"1454":71,"1455":19,"1456":4,"1457":5,"1458":11,"1459":9,"1460":2,"1461":1,"1462":2,"1464":12,"1466":108,"1468":1,"1469":1,"1470":1,"1471":4,"1472":4,"1473":9,"1474":21,"1475":17,"1476":6,"1477":1,"1479":2,"1480":6,"1482":1,"1483":2,"1485":9,"1486":1,"1487":35,"1488":30,"1489":22,"1490":5,"1491":21,"1495":39,"1496":1,"1497":3,"1499":4,"1503":1,"1506":2,"1507":3,"1508":1,"1511":1,"1512":2,"1516":2,"1523":3,"1524":1,"1525":36,"1526":2,"1530":9,"1531":1,"1532":1,"1533":11,"1535":2,"1536":1,"1540":1,"1541":1,"1543":1,"1544":1,"1552":3,"1556":1,"1566":1,"1568":2,"1569":1,"1570":1,"1574":2,"1575":2,"1576":25,"1577":3,"1581":6,"1582":1,"1583":2,"1584":6,"1588":3,"1590":22,"1591":1,"1595":10,"1596":1,"1597":1,"1598":6,"1599":2,"1609":1,"1610":4,"1611":1,"1612":2,"1625":1,"1626":8,"1629":4,"1630":22,"1634":2,"1637":4,"1638":4,"1639":2,"1640":24,"1642":2,"1643":1,"1644":1,"1645":2,"1646":1,"1647":3,"1648":2,"1649":1,"1650":8,"1651":4,"1652":10,"1653":5,"1654":1,"1655":3,"1656":1,"1659":2,"1660":6,"1661":7,"1662":4,"1663":6,"1664":6,"1665":4,"1666":6,"1667":6,"1668":9,"1669":9,"1670":7,"1671":4,"1672":5,"1674":3,"1675":1,"1676":10,"1677":1,"1678":3,"1680":1,"1681":2,"1682":1,"1683":6,"1684":14,"1685":14,"1686":1,"1687":1,"1688":4,"1691":1,"1692":4,"1696":1,"1697":3,"1698":3,"1699":1,"1700":7,"1701":6,"1704":1,"1705":3,"1706":3,"1707":3,"1710":1,"1713":1,"1714":1,"1717":1,"1718":4,"1719":2,"1720":3,"1721":2,"1722":1,"1723":1,"1724":1,"1726":19,"1727":4,"1728":4,"1729":4,"1730":2,"1731":2,"1734":1,"1735":1,"1743":1,"1746":1,"1747":10,"1748":10,"1749":3,"1750":1,"1756":1,"1757":1,"1758":2,"1759":1,"1760":6,"1763":6,"1764":17,"1765":1,"1766":5,"1768":2,"1769":1,"1771":1,"1772":2,"1778":1,"1779":1,"1780":3,"1781":1,"1782":1,"1783":1,"1784":1,"1787":2,"1788":4,"1789":3,"1791":1,"1793":1,"1795":1,"1798":2,"1799":1,"1804":7,"1805":2,"1809":12,"1810":1,"1813":2,"1814":15,"1815":1,"1816":2,"1818":1,"1820":10,"1821":2,"1822":3,"1823":2,"1824":10,"1825":2,"1826":1,"1827":2,"1828":2,"1829":1,"1830":4,"1831":2,"1832":1,"1833":2,"1837":3,"1838":2,"1839":9,"1840":3,"1841":4,"1842":2,"1843":3,"1844":3,"1847":2,"1848":5,"1850":7,"1851":1,"1852":1,"1854":1,"1855":5,"1856":1,"1857":4,"1858":3,"1859":2,"1860":2,"1861":1,"1862":1,"1864":4,"1866":1,"1867":2,"1868":1,"1870":6,"1872":2,"1873":4,"1874":3,"1875":4,"1876":5,"1878":1,"1879":1,"1880":3,"1881":2,"1882":1,"1885":4,"1888":6,"1890":3,"1891":1,"1892":2,"1894":2,"1895":3,"1896":1,"1897":4,"1898":2,"1899":1,"1901":8,"1902":2,"1903":1,"1904":3,"1905":1,"1907":6,"1908":10,"1909":4,"1910":4,"1911":3,"1912":2,"1914":3,"1915":10,"1916":3,"1917":3,"1918":4,"1919":9,"1920":1,"1921":12,"1922":14,"1923":3,"1925":1,"1926":5,"1927":1,"1928":5,"1929":1,"1931":1,"1932":5,"1933":2,"1934":3,"1935":3,"1936":3,"1937":2,"1939":2,"1940":3,"1941":1,"1943":8,"1944":7,"1945":4,"1946":3,"1947":10,"1948":2,"1949":4,"1950":1,"1951":1,"1952":3,"1953":2,"1954":1,"1955":1,"1958":3,"1959":1,"1961":8,"1962":4,"1963":1,"1964":2,"1965":1,"1966":1,"1967":3,"1968":1,"1969":4,"1970":2,"1971":2,"1972":2,"1973":1,"1974":4,"1975":4,"1976":5,"1977":6,"1978":1,"1979":1,"1980":2,"1981":5,"1982":1,"1983":3,"1984":3,"1985":2,"1986":1,"1987":7,"1988":3,"1989":1,"1991":1,"1992":2,"1993":6,"1994":12,"1995":3,"1996":3,"1997":3,"1998":1,"1999":15,"2000":8,"2001":2,"2003":1,"2005":2,"2006":4,"2007":3,"2008":2,"2009":12,"2010":2,"2011":6,"2012":4,"2013":2,"2015":1,"2018":3,"2020":2,"2021":1,"2022":1,"2023":7,"2024":5,"2026":2,"2027":4,"2029":2,"2030":1,"2031":3,"2032":2,"2033":5,"2034":1,"2035":1,"2037":1,"2040":2,"2041":4,"2042":4,"2043":2,"2044":1,"2045":1,"2046":6,"2047":2,"2048":1,"2051":3,"2054":5,"2055":5,"2056":4,"2057":2,"2058":1,"2059":1,"2060":2,"2062":8,"2063":1,"2064":1,"2065":4,"2066":4,"2068":2,"2069":2,"2070":1,"2071":1,"2072":1,"2073":2,"2074":5,"2075":4,"2076":3,"2077":3,"2078":3,"2079":5,"2080":3,"2081":1,"2082":8,"2084":6,"2085":2,"2086":2,"2088":3,"2089":2,"2090":1,"2092":1,"2093":3,"2094":1,"2095":4,"2096":7,"2097":1,"2098":1,"2099":1,"2100":1,"2103":3,"2104":3,"2105":2,"2106":2,"2107":4,"2110":3,"2111":1,"2112":3,"2113":1,"2115":2,"2116":5,"2117":6,"2118":1,"2119":1,"2120":4,"2122":5,"2125":8,"2126":1,"2130":9,"2131":3,"2132":3,"2133":3,"2134":1,"2135":4,"2136":1,"2137":6,"2138":5,"2139":1,"2140":1,"2141":3,"2142":2,"2143":2,"2144":3,"2145":4,"2146":1,"2148":3,"2149":7,"2151":2,"2152":3,"2153":1,"2154":2,"2155":14,"2156":4,"2157":9,"2158":3,"2159":2,"2160":1,"2162":7,"2163":4,"2164":8,"2165":7,"2166":10,"2167":8,"2168":2,"2169":1,"2171":3,"2172":1,"2173":3,"2174":2,"2175":3,"2176":1,"2178":5,"2179":3,"2180":4,"2182":2,"2183":1,"2184":2,"2185":3,"2186":1,"2187":2,"2188":2,"2189":3,"2191":5,"2192":4,"2193":1,"2194":2,"2195":1,"2196":3,"2197":2,"2198":5,"2199":1,"2200":1,"2201":2,"2202":4,"2203":1,"2206":1,"2210":1,"2215":1,"2226":1,"2229":4,"2231":13,"2241":1}}],["stderr",{"3":{"790":1,"792":2,"794":1,"1455":1,"1464":1,"1466":2,"1588":1,"1590":1,"1595":1,"1596":1,"2165":1}}],["stdout",{"3":{"0":1,"401":2,"914":1,"916":1,"1338":1,"1441":1,"1446":1,"1450":1,"1455":1,"1464":1,"1466":3,"1475":1}}],["stylistic",{"3":{"979":1,"1229":1,"1310":1,"1358":1,"1416":1,"1435":1,"1459":1}}],["styling",{"3":{"682":1,"905":1,"1323":1,"1435":1,"1556":1,"1560":1}}],["stylefunc",{"3":{"1435":1}}],["stylefunc=",{"3":{"1435":1,"1525":1}}],["style=",{"3":{"1435":3,"1525":2,"1533":2,"1590":1,"1599":1}}],["styled",{"3":{"1395":1,"1414":1,"1416":1}}],["stylecop",{"3":{"1213":1,"1452":1,"1576":1,"1672":1}}],["stylesheets",{"3":{"1430":1,"1435":3}}],["stylesheet",{"3":{"1394":1,"1416":1,"1435":8,"1488":1,"1525":2,"1533":1,"1590":3,"1595":3,"1596":1,"2074":2,"2149":1}}],["styles",{"0":{"1212":1,"2229":1},"3":{"0":1,"214":1,"216":2,"469":1,"889":1,"981":1,"1052":1,"1229":1,"1285":1,"1323":2,"1338":1,"1350":1,"1358":2,"1395":2,"1435":3,"1438":1,"1442":1,"1525":2,"1533":1,"1534":1,"1556":2,"2148":1,"2151":1,"2229":1}}],["style",{"0":{"1214":1,"1878":1},"2":{"1004":1,"1214":1},"3":{"0":5,"207":1,"212":1,"213":1,"214":3,"215":1,"216":2,"219":1,"546":1,"725":1,"748":1,"855":1,"980":1,"1004":1,"1034":1,"1059":1,"1134":1,"1137":1,"1160":1,"1162":1,"1164":1,"1169":1,"1171":1,"1210":1,"1212":1,"1214":2,"1229":2,"1247":1,"1265":1,"1270":2,"1273":1,"1285":6,"1296":1,"1299":8,"1308":2,"1309":1,"1322":2,"1323":6,"1335":1,"1338":7,"1342":1,"1349":2,"1358":10,"1368":1,"1394":4,"1395":3,"1401":1,"1416":2,"1435":19,"1436":2,"1437":19,"1442":1,"1496":1,"1523":2,"1524":2,"1525":2,"1529":1,"1531":1,"1533":1,"1551":1,"1553":1,"1573":1,"1576":6,"1580":1,"1581":2,"1582":1,"1590":1,"1599":2,"1684":1,"1806":1,"1825":1,"1855":1,"1987":1,"2059":1,"2085":1,"2146":2,"2148":3,"2149":2,"2151":2,"2152":1,"2185":1,"2196":1}}],["stickiness",{"3":{"2137":2}}],["stick",{"3":{"1435":1}}],["sticks",{"3":{"1379":1,"1432":1}}],["stickysidebare2etests",{"3":{"1199":1,"1207":2,"1435":1,"1437":2}}],["sticky",{"3":{"664":1,"666":1,"669":1,"1310":3,"1437":2,"1466":4}}],["stitches",{"3":{"398":1,"400":1,"1308":1,"1379":1,"1395":1,"1652":1}}],["stillcarriestheauthippolicy",{"3":{"1999":1}}],["stillcoversframeworkevents",{"3":{"1435":1}}],["stillfails",{"3":{"1435":1}}],["stillassertsthetwoscanscannotoverlap",{"3":{"1435":1}}],["stillreported",{"3":{"1435":5}}],["stillreportsunhealthyratherthanthrowing",{"3":{"1338":1}}],["still",{"0":{"2052":1},"1":{"1047":1,"1048":1,"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1},"3":{"0":27,"13":1,"22":1,"23":1,"24":5,"26":2,"27":3,"39":1,"41":2,"42":1,"46":1,"47":1,"59":1,"62":1,"70":1,"71":2,"73":2,"80":1,"81":1,"91":4,"92":3,"93":1,"95":1,"96":1,"98":1,"104":1,"109":3,"111":3,"117":1,"120":1,"121":2,"123":1,"125":3,"126":2,"127":1,"130":1,"135":2,"137":1,"139":6,"140":1,"141":1,"142":4,"145":1,"147":2,"152":2,"155":1,"157":1,"159":1,"160":1,"166":1,"170":1,"175":1,"177":1,"178":5,"179":3,"186":1,"188":1,"190":2,"195":2,"198":1,"199":1,"200":4,"201":5,"202":1,"203":1,"207":1,"214":1,"215":2,"216":2,"220":1,"225":2,"231":1,"235":5,"236":1,"237":4,"241":1,"243":2,"245":1,"246":1,"248":2,"250":1,"253":1,"254":2,"255":2,"256":3,"257":4,"258":2,"259":6,"260":1,"261":1,"265":4,"267":1,"273":1,"275":1,"280":1,"283":1,"286":1,"293":1,"294":1,"296":2,"300":1,"302":1,"303":3,"310":1,"318":2,"320":2,"330":1,"335":1,"337":1,"342":1,"343":2,"350":1,"352":2,"358":1,"359":3,"361":5,"364":1,"366":2,"383":1,"384":1,"387":2,"390":3,"392":1,"397":2,"407":4,"408":2,"409":1,"413":2,"422":2,"423":1,"424":2,"425":1,"426":4,"428":2,"429":2,"442":1,"444":1,"447":1,"448":1,"449":1,"451":1,"454":1,"458":1,"459":3,"461":1,"462":1,"464":1,"465":1,"466":3,"478":1,"480":1,"487":2,"488":2,"489":3,"490":3,"491":7,"492":8,"493":6,"494":3,"495":2,"497":2,"499":1,"507":1,"511":1,"513":1,"518":1,"522":2,"523":3,"524":3,"528":1,"529":2,"536":3,"538":3,"539":4,"540":2,"544":1,"545":1,"546":1,"549":7,"550":4,"551":3,"552":2,"558":1,"564":2,"565":1,"572":3,"573":1,"577":2,"578":1,"583":3,"584":1,"585":2,"590":1,"592":1,"593":1,"599":1,"601":3,"602":1,"607":4,"609":2,"610":1,"618":1,"620":1,"624":3,"625":1,"626":3,"627":1,"628":1,"633":1,"634":1,"636":2,"640":1,"642":4,"651":3,"656":1,"657":3,"658":1,"661":1,"667":1,"668":1,"674":1,"681":1,"684":2,"688":2,"691":1,"692":1,"696":1,"698":3,"700":1,"702":2,"703":4,"707":1,"709":2,"715":2,"717":1,"718":1,"724":3,"732":1,"733":2,"735":1,"741":3,"743":2,"749":1,"751":1,"756":1,"757":1,"759":1,"761":1,"766":1,"767":1,"774":1,"776":1,"782":1,"789":1,"790":4,"792":1,"798":1,"800":2,"801":2,"803":3,"804":2,"805":2,"809":3,"812":1,"813":6,"814":1,"819":1,"825":1,"826":1,"827":3,"829":3,"830":1,"831":2,"832":2,"833":1,"837":1,"839":2,"840":3,"848":1,"849":3,"856":1,"861":1,"862":1,"867":1,"872":2,"876":1,"877":2,"881":1,"882":1,"888":2,"890":1,"891":1,"897":2,"898":3,"899":1,"905":2,"906":1,"907":6,"914":1,"920":2,"921":2,"924":1,"926":4,"931":1,"932":1,"939":1,"941":1,"942":1,"945":1,"946":2,"949":1,"956":2,"959":2,"965":1,"967":1,"971":1,"972":1,"974":1,"975":1,"981":1,"983":2,"988":3,"996":2,"998":1,"999":1,"1006":1,"1014":1,"1018":5,"1020":2,"1026":2,"1027":1,"1028":1,"1043":1,"1045":1,"1047":1,"1049":2,"1051":4,"1052":1,"1054":5,"1055":3,"1058":1,"1059":3,"1066":1,"1069":1,"1070":1,"1073":1,"1078":1,"1079":1,"1086":1,"1089":1,"1092":2,"1093":2,"1100":1,"1116":1,"1118":1,"1123":1,"1135":1,"1136":1,"1156":1,"1168":1,"1170":2,"1175":2,"1177":1,"1183":2,"1185":1,"1186":2,"1196":1,"1212":2,"1222":1,"1225":1,"1226":1,"1228":1,"1229":5,"1231":3,"1233":1,"1236":1,"1237":8,"1241":2,"1242":1,"1244":1,"1247":23,"1249":1,"1251":1,"1252":1,"1254":1,"1259":31,"1262":2,"1263":3,"1264":1,"1265":1,"1267":2,"1269":1,"1270":31,"1271":4,"1274":2,"1276":1,"1278":1,"1280":3,"1283":1,"1284":1,"1285":78,"1287":1,"1288":2,"1289":1,"1291":2,"1293":1,"1296":1,"1297":3,"1299":59,"1300":22,"1301":2,"1303":1,"1304":1,"1307":1,"1308":19,"1309":3,"1310":55,"1311":5,"1313":2,"1314":1,"1315":2,"1316":3,"1321":1,"1322":42,"1323":77,"1327":1,"1331":1,"1333":3,"1335":1,"1336":1,"1337":2,"1338":33,"1343":1,"1345":1,"1349":16,"1351":2,"1352":1,"1353":3,"1356":2,"1358":85,"1361":1,"1363":1,"1365":3,"1368":11,"1376":1,"1378":2,"1379":7,"1382":1,"1388":1,"1389":1,"1390":1,"1394":61,"1395":59,"1396":2,"1397":2,"1398":2,"1399":2,"1400":2,"1401":28,"1409":2,"1411":1,"1414":38,"1415":6,"1416":41,"1420":2,"1422":1,"1426":4,"1428":1,"1429":1,"1430":1,"1434":4,"1435":134,"1437":16,"1440":1,"1442":5,"1444":5,"1445":1,"1446":3,"1448":1,"1450":1,"1452":3,"1454":17,"1455":7,"1456":1,"1458":2,"1462":2,"1464":1,"1465":1,"1466":27,"1467":2,"1468":1,"1471":1,"1473":3,"1474":1,"1475":4,"1476":1,"1477":3,"1480":3,"1483":1,"1485":2,"1487":7,"1488":1,"1489":3,"1491":5,"1495":4,"1496":1,"1514":1,"1516":1,"1523":1,"1524":1,"1561":1,"1585":2,"1595":1,"1610":1,"1626":1,"1630":2,"1631":2,"1637":1,"1639":1,"1640":5,"1650":2,"1651":1,"1652":2,"1655":1,"1661":1,"1662":1,"1669":1,"1676":3,"1683":3,"1684":8,"1685":2,"1689":1,"1692":1,"1701":2,"1707":2,"1712":1,"1718":1,"1719":1,"1726":4,"1730":1,"1736":1,"1747":3,"1748":2,"1758":1,"1764":4,"1765":1,"1766":1,"1769":1,"1775":1,"1788":1,"1789":1,"1798":1,"1799":1,"1806":1,"1809":2,"1814":1,"1816":1,"1821":1,"1824":3,"1837":1,"1839":2,"1844":1,"1847":1,"1848":1,"1850":2,"1857":1,"1865":1,"1866":1,"1880":1,"1881":2,"1885":1,"1886":1,"1887":1,"1890":4,"1891":1,"1893":1,"1895":2,"1897":1,"1902":1,"1907":2,"1908":2,"1909":1,"1910":2,"1912":1,"1914":1,"1915":1,"1916":1,"1917":2,"1918":2,"1919":2,"1921":1,"1922":5,"1923":1,"1924":2,"1926":2,"1927":1,"1932":1,"1933":1,"1934":1,"1939":3,"1943":2,"1945":1,"1948":3,"1950":1,"1952":1,"1960":1,"1961":1,"1962":1,"1963":1,"1964":1,"1965":1,"1967":3,"1969":1,"1975":1,"1977":3,"1978":1,"1981":1,"1983":1,"1985":1,"1987":2,"1993":3,"1994":1,"1995":1,"1996":2,"1997":1,"1999":2,"2000":1,"2001":1,"2006":2,"2007":1,"2009":1,"2012":1,"2015":1,"2024":2,"2033":1,"2035":1,"2042":1,"2044":2,"2046":1,"2050":1,"2054":1,"2055":1,"2056":1,"2057":1,"2062":3,"2064":1,"2065":1,"2067":1,"2077":1,"2078":3,"2082":1,"2084":1,"2085":1,"2089":1,"2092":1,"2094":1,"2095":1,"2096":1,"2107":1,"2113":2,"2121":1,"2122":1,"2123":2,"2125":1,"2126":1,"2129":1,"2130":1,"2134":1,"2137":2,"2139":1,"2141":2,"2144":5,"2147":1,"2149":2,"2151":1,"2152":2,"2155":2,"2157":1,"2160":1,"2162":1,"2163":1,"2165":3,"2166":3,"2167":1,"2168":2,"2172":1,"2175":1,"2178":2,"2179":1,"2180":1,"2190":1,"2202":2,"2231":4}}],["steals",{"3":{"2145":1}}],["stealing",{"3":{"2046":1,"2072":1,"2149":1,"2157":1,"2163":1,"2190":1,"2196":1}}],["stealable",{"3":{"1984":1}}],["steal",{"3":{"1323":1,"1453":1,"1980":1}}],["steady",{"3":{"390":1,"397":1,"399":1,"996":1,"1259":2,"1300":3,"1322":3,"1458":1,"1676":2,"2153":1,"2156":1,"2158":1}}],["steers",{"3":{"1416":3}}],["steering",{"3":{"1299":1,"1414":1,"1736":1}}],["steer",{"3":{"1270":1,"1323":2}}],["steered",{"3":{"659":1}}],["stem",{"3":{"741":1,"1285":2,"1392":1,"1394":1}}],["stepnames",{"3":{"1310":2,"1435":2}}],["stepupquery",{"3":{"1199":2,"1207":1}}],["stepupcommand",{"3":{"1199":2,"1207":1}}],["steppable",{"3":{"6":1,"1358":1}}],["stepped",{"3":{"0":1,"859":1,"1270":1}}],["steps",{"3":{"0":5,"110":1,"168":1,"243":1,"256":1,"296":1,"360":1,"368":1,"459":2,"464":1,"528":2,"536":1,"562":1,"566":1,"593":1,"599":1,"602":1,"604":1,"609":2,"611":1,"633":1,"698":1,"747":1,"749":3,"790":1,"809":1,"810":2,"873":1,"881":1,"914":2,"923":1,"927":1,"1018":1,"1094":1,"1212":1,"1229":1,"1247":2,"1248":1,"1254":1,"1259":1,"1265":1,"1270":1,"1285":2,"1288":1,"1299":4,"1300":2,"1309":1,"1310":18,"1311":1,"1314":1,"1315":1,"1322":2,"1323":1,"1335":1,"1358":18,"1368":1,"1379":1,"1394":3,"1395":1,"1401":1,"1414":1,"1416":3,"1435":7,"1452":1,"1453":4,"1454":7,"1455":1,"1460":1,"1462":1,"1466":2,"1477":1,"1480":1,"1487":1,"1489":2,"1525":1,"1576":2,"1584":1,"1601":1,"1640":1,"1652":1,"1658":1,"1665":2,"1673":1,"1674":1,"1691":1,"1716":1,"1718":1,"1724":1,"1726":1,"1728":1,"1746":3,"1748":1,"1764":10,"1807":1,"1814":1,"1824":3,"1834":1,"1845":1,"1897":1,"1922":1,"2025":1,"2034":1,"2045":2,"2086":2,"2099":1,"2100":3,"2104":1,"2162":1,"2167":1,"2168":1,"2192":2,"2201":1,"2225":1}}],["step",{"0":{"1292":1,"2004":1,"2005":1,"2006":1,"2007":1,"2008":1,"2009":1,"2010":1,"2011":1,"2015":1,"2016":1,"2017":1,"2018":1,"2019":1,"2020":1,"2021":1,"2022":1,"2023":1,"2088":1,"2089":1,"2090":1,"2091":1,"2092":1,"2093":1,"2094":1,"2095":1,"2101":1,"2102":1,"2103":1,"2104":1,"2105":1,"2196":1},"2":{"451":1,"483":1},"3":{"0":16,"24":1,"57":1,"59":2,"62":1,"63":1,"109":2,"115":1,"117":1,"126":3,"128":2,"132":1,"135":2,"137":2,"140":3,"142":2,"147":1,"149":1,"217":1,"220":1,"243":1,"256":2,"258":2,"265":1,"291":2,"292":1,"295":1,"343":1,"365":1,"370":4,"371":1,"373":3,"375":1,"435":1,"451":2,"452":1,"453":1,"457":2,"459":4,"462":2,"464":6,"466":1,"483":1,"529":3,"530":1,"534":1,"535":4,"536":5,"563":1,"564":7,"566":4,"567":1,"584":1,"587":1,"600":1,"608":1,"624":1,"626":8,"627":1,"628":1,"629":2,"632":1,"633":1,"635":1,"640":1,"656":1,"659":1,"665":1,"666":1,"674":3,"675":1,"684":1,"696":1,"698":5,"700":1,"703":1,"725":1,"733":1,"735":1,"741":1,"747":1,"749":13,"751":6,"756":2,"757":9,"758":1,"766":5,"767":2,"769":1,"774":5,"776":1,"777":2,"791":1,"809":7,"810":2,"812":2,"813":1,"829":1,"839":1,"847":1,"861":2,"864":1,"867":1,"870":2,"873":1,"876":2,"877":1,"882":1,"907":2,"917":1,"920":1,"927":1,"941":1,"991":1,"1018":3,"1020":2,"1043":1,"1058":1,"1059":2,"1060":2,"1067":1,"1069":2,"1116":1,"1117":1,"1124":1,"1156":1,"1170":1,"1184":1,"1187":1,"1212":2,"1220":1,"1229":9,"1233":2,"1237":1,"1241":1,"1244":1,"1247":3,"1252":2,"1253":1,"1258":1,"1259":7,"1262":1,"1263":2,"1264":1,"1265":3,"1270":15,"1271":1,"1283":1,"1285":26,"1292":2,"1299":11,"1300":1,"1308":3,"1310":45,"1311":1,"1314":2,"1317":1,"1319":1,"1321":2,"1322":14,"1323":8,"1324":1,"1337":1,"1338":2,"1345":1,"1349":3,"1358":65,"1363":1,"1365":1,"1366":1,"1368":1,"1374":1,"1379":2,"1394":14,"1395":12,"1398":1,"1399":1,"1401":1,"1414":18,"1415":1,"1416":6,"1426":1,"1432":1,"1435":24,"1437":2,"1444":6,"1446":1,"1448":1,"1451":2,"1452":19,"1453":14,"1454":43,"1455":15,"1456":2,"1457":6,"1458":5,"1459":3,"1460":2,"1464":7,"1465":2,"1466":8,"1468":2,"1470":2,"1471":1,"1472":2,"1474":12,"1475":2,"1476":1,"1477":1,"1480":1,"1481":1,"1485":3,"1487":5,"1488":1,"1489":5,"1491":9,"1525":2,"1530":1,"1544":1,"1569":1,"1571":1,"1576":8,"1581":1,"1590":5,"1599":1,"1610":1,"1630":1,"1640":3,"1645":1,"1650":1,"1655":1,"1657":1,"1660":3,"1662":2,"1666":1,"1669":1,"1672":2,"1680":1,"1681":2,"1682":1,"1683":3,"1684":2,"1685":3,"1695":1,"1696":1,"1704":1,"1718":1,"1721":2,"1725":1,"1727":1,"1728":3,"1773":2,"1783":1,"1804":2,"1814":1,"1820":2,"1823":1,"1824":6,"1825":1,"1834":2,"1838":1,"1844":1,"1845":1,"1880":2,"1897":2,"1908":1,"1909":1,"1922":1,"1939":1,"1981":1,"2002":2,"2005":4,"2012":1,"2014":5,"2021":1,"2022":1,"2023":3,"2033":1,"2040":1,"2044":1,"2045":1,"2065":2,"2074":1,"2086":2,"2092":1,"2094":3,"2095":1,"2096":2,"2098":1,"2099":1,"2100":1,"2104":2,"2105":1,"2135":1,"2141":1,"2146":1,"2160":1,"2161":3,"2162":3,"2163":2,"2164":1,"2166":2,"2167":1,"2168":5,"2186":1,"2188":1,"2193":2,"2194":1,"2195":1,"2196":2,"2197":2,"2202":4,"2205":1,"2231":3,"2233":1}}],["stumbles",{"3":{"1911":1}}],["student",{"3":{"1394":1}}],["study",{"3":{"1323":2,"1331":1,"1394":1,"1395":1,"1415":1,"1778":1,"2108":1,"2233":1}}],["studying",{"3":{"1285":1,"1299":1,"1308":1,"1323":2,"1349":1,"1358":2,"1379":1,"1389":1,"1395":1,"1398":1}}],["studio",{"3":{"214":1,"219":1,"665":1,"1074":1,"1323":1,"1334":1,"1338":2,"2149":1}}],["studied",{"3":{"0":1,"1033":1,"1323":1}}],["stuffing",{"3":{"174":1,"280":1,"286":1,"403":1,"612":1,"1291":1,"1299":1,"1325":1,"1338":1,"1443":1,"1997":2,"1998":1,"2000":1}}],["stubviewport",{"3":{"1435":4}}],["stubregistrations",{"3":{"1322":1}}],["stubrefresher",{"3":{"1199":2,"1207":1}}],["stubpushnotificationuiservice",{"2":{"1433":1},"3":{"1199":2,"1207":2,"1433":2,"1435":4}}],["stubparent",{"3":{"1199":2,"1207":1}}],["stubnotificationinboxuiservice",{"2":{"1433":1},"3":{"1199":2,"1207":2,"1433":2,"1435":5}}],["stubentity",{"3":{"1199":2,"1207":1}}],["stuberrorlocalizer",{"3":{"1199":2,"1207":1}}],["stubuimodule",{"3":{"1199":2,"1207":1,"1323":1}}],["stubmap",{"3":{"1199":2,"1207":1,"1435":3}}],["stubmetricsbuilder",{"3":{"1199":2,"1207":1}}],["stubfeaturemanager",{"3":{"1199":2,"1207":1}}],["stubguardrail",{"3":{"1199":4,"1207":2,"1426":1,"1434":2,"1486":1}}],["stubtwofactorservice",{"3":{"1199":2,"1207":1}}],["stubtenantcontext",{"3":{"1199":2,"1207":1}}],["stubtoolpolicy",{"3":{"1199":3,"1207":3,"1426":1}}],["stubtool",{"3":{"1199":2,"1207":1}}],["stubtokenstorageservicetests",{"3":{"1199":1,"1207":2,"1435":1}}],["stubtokenstorageservice",{"2":{"1431":1},"3":{"954":1,"1199":8,"1207":2,"1323":1,"1427":1,"1431":2,"1435":7,"1487":1}}],["stublocalizer",{"3":{"1199":8,"1207":5}}],["stubloggingbuilder",{"3":{"1199":2,"1207":1}}],["stubwebhostenvironment",{"3":{"1199":2,"1207":1}}],["stubcurrentuserservice",{"3":{"1199":3,"1207":1}}],["stubchild",{"3":{"1199":2,"1207":1}}],["stubchanneljoinauthorizer",{"3":{"1199":2,"1207":1}}],["stubchatclient",{"3":{"1199":17,"1207":6,"1426":1,"1486":1}}],["stubcspprovider",{"3":{"1199":2,"1207":1}}],["stubclock",{"3":{"1199":2,"1207":1}}],["stubhandler",{"3":{"1199":4,"1207":2}}],["stubhttpmessagehandler",{"3":{"1199":16,"1207":2}}],["stubhttpclientfactory",{"3":{"1199":16,"1207":3}}],["stubhostenvironment",{"3":{"1199":10,"1207":5}}],["stubbed",{"3":{"583":1,"957":1,"1395":1,"1414":1,"1431":1,"1435":2,"1437":2}}],["stubsharedlocalizer",{"3":{"1199":2,"1207":1}}],["stubscopeprovider",{"3":{"1199":3,"1207":2}}],["stubs",{"0":{"1348":1},"3":{"53":3,"59":1,"63":1,"135":1,"413":1,"582":1,"583":9,"584":1,"585":2,"586":2,"587":2,"649":1,"1192":1,"1207":16,"1212":1,"1285":1,"1299":1,"1308":1,"1322":3,"1323":2,"1340":2,"1379":4,"1395":3,"1433":9,"1435":38,"1495":2,"1655":1,"1670":1,"1789":2,"1883":1,"1884":1,"1885":1,"2027":1}}],["stub",{"0":{"1411":1},"3":{"0":1,"53":1,"63":1,"583":9,"585":4,"649":1,"650":1,"954":1,"1212":1,"1299":3,"1308":6,"1310":2,"1311":2,"1313":3,"1314":2,"1322":9,"1323":11,"1348":2,"1349":23,"1358":1,"1377":2,"1379":3,"1395":13,"1397":1,"1401":4,"1411":1,"1412":1,"1414":13,"1416":1,"1426":1,"1430":1,"1431":1,"1434":2,"1435":52,"1436":2,"1488":1,"1489":1,"1495":1,"1576":1,"1650":1,"1652":1,"1661":1,"1750":1,"1771":1,"1881":3,"1883":1,"1884":1,"1885":1,"2016":1,"2018":1,"2052":1,"2093":2,"2112":1,"2117":1,"2122":1,"2231":1}}],["stuckageminutes",{"2":{"793":1},"3":{"0":1,"539":1,"540":1,"793":1}}],["stuck",{"3":{"0":2,"24":2,"174":1,"234":1,"235":1,"237":1,"536":1,"537":1,"538":2,"540":1,"792":1,"809":1,"811":1,"997":1,"1259":2,"1323":3,"1338":1,"1394":2,"1395":1,"1435":1,"1475":1,"1853":1,"1997":1,"2071":1,"2155":1,"2166":3,"2167":2}}],["stryker",{"3":{"1576":1}}],["stroke",{"3":{"1358":2}}],["strongpasswordrules",{"2":{"1828":1,"1833":1},"3":{"1199":5,"1203":1,"1207":1,"1271":9,"1293":1,"1299":7,"1322":1,"1414":5,"1828":2,"1833":1}}],["strongest",{"3":{"947":1,"1229":1,"1285":1,"1322":1,"1323":1,"1358":2,"1379":2,"1453":1,"1522":1,"1573":1,"1587":1}}],["stronger",{"3":{"0":2,"48":1,"537":1,"694":1,"785":1,"801":1,"804":1,"819":1,"840":1,"995":1,"1043":1,"1237":1,"1285":1,"1299":1,"1322":1,"1358":2,"1395":1,"1416":1,"1435":2,"1473":1,"1847":1,"2163":1,"2168":1}}],["strong",{"3":{"54":1,"727":1,"1019":1,"1237":1,"1271":2,"1299":5,"1310":1,"1323":1,"1349":1,"1394":1,"1414":3,"1525":5,"1526":1,"1536":2,"1540":2,"1543":1,"1574":1,"1576":9,"1581":1,"1590":3,"1591":1,"1637":1,"1665":1,"1796":1,"1811":1,"1827":1,"1833":2,"1901":1,"2106":1,"2143":1}}],["stronglytypedidapitests",{"3":{"1199":1,"1207":7,"1299":2}}],["stronglytypedidconverter",{"3":{"1199":2,"1203":1,"1207":1,"1299":6}}],["stronglytypedidconventiontests",{"3":{"1050":1,"1199":1,"1207":2,"1435":10}}],["stronglytypedidregistry",{"2":{"1280":1},"3":{"1050":2,"1199":4,"1203":1,"1207":2,"1280":1,"1285":3,"1299":11,"1809":1}}],["stronglytypedidvaluecomparer<",{"3":{"1285":2}}],["stronglytypedidvaluecomparer",{"2":{"1280":1,"1809":1},"3":{"1050":1,"1199":3,"1203":1,"1207":1,"1280":1,"1285":1,"1809":1}}],["stronglytypedidvalueconverter",{"2":{"1280":1,"1809":1},"3":{"798":1,"806":1,"1050":2,"1199":3,"1203":1,"1207":4,"1280":3,"1285":14,"1495":1,"1809":1}}],["stronglytypedidvalueparserdelegate",{"3":{"1199":2,"1203":1,"1207":1,"1299":3}}],["stronglytypedidvalueparser",{"3":{"1050":1,"1199":2,"1203":1,"1207":1,"1299":7}}],["stronglytypedidpersistencetests",{"2":{"1050":1},"3":{"1050":1,"1199":1,"1207":2,"1285":3}}],["stronglytypedidparametertransformer",{"2":{"1050":1},"3":{"1050":1,"1199":2,"1203":1,"1207":1,"1310":6,"1495":1}}],["stronglytypedidtests",{"3":{"1050":3,"1199":2,"1207":4,"1299":4,"1435":3}}],["stronglytypedidtestsbase",{"2":{"130":1,"1054":1},"3":{"0":1,"130":1,"1050":2,"1054":1,"1199":3,"1207":2,"1430":3,"1435":11}}],["stronglytypedidtypeconverter",{"2":{"1809":1},"3":{"1050":2,"1052":1,"1055":2,"1199":3,"1203":1,"1207":3,"1299":15,"1809":1}}],["stronglytypedidtypeconvertertests",{"2":{"1050":1},"3":{"1050":1,"1052":1,"1055":1,"1199":1,"1207":2,"1299":4}}],["stronglytypedidtypeconverters",{"2":{"1050":1,"1052":1,"1809":1},"3":{"1050":1,"1052":1,"1199":4,"1203":1,"1207":1,"1299":4,"1809":1}}],["stronglytypedidmappertests",{"3":{"1199":1,"1207":8,"1299":2}}],["stronglytypedidmappings",{"3":{"798":1,"806":1,"1050":3,"1199":2,"1203":1,"1207":2,"1299":8,"1809":1}}],["stronglytypedidmodelconfiguration",{"2":{"1050":1,"1280":1,"1809":1},"3":{"1050":3,"1199":1,"1203":1,"1207":2,"1280":2,"1285":19,"1299":2,"1495":1,"1809":1}}],["stronglytypedidjsonconverterfactory",{"2":{"967":1,"1050":1,"1809":1},"3":{"967":1,"1050":2,"1199":2,"1203":1,"1207":3,"1299":12,"1310":3,"1809":1}}],["stronglytypedid",{"2":{"798":1,"1241":1},"3":{"798":2,"799":1,"1050":3,"1198":1,"1199":11,"1203":1,"1207":4,"1241":1,"1247":3,"1299":31,"1310":4,"1495":2}}],["stronglytypedidfitnesstests",{"3":{"1198":1,"1199":2,"1207":3,"1435":13}}],["stronglytypedidfixtures",{"3":{"477":2,"1207":15,"1299":3,"1435":22}}],["stronglytypedidfilterstrategytests",{"3":{"1199":1,"1207":5,"1247":2}}],["stronglytypedidfilterstrategy",{"2":{"330":1,"337":1,"1241":2,"1247":1},"3":{"330":1,"337":1,"1050":2,"1199":2,"1203":1,"1207":4,"1241":11,"1247":8,"1299":1,"1495":3}}],["stronglytypedidschematransformer",{"3":{"1050":2,"1199":3,"1203":1,"1207":3,"1299":2,"1310":14,"1495":2}}],["stronglytypedidserializationtests",{"2":{"1050":1},"3":{"1050":1,"1199":1,"1207":3,"1299":4}}],["stronglytypedids",{"2":{"468":1},"3":{"468":1,"798":1,"801":1,"804":1,"1050":1,"1207":1,"1435":8,"1488":1}}],["stronglytypedidsarereadonlyrecordstructs",{"2":{"0":1,"130":1,"798":1,"801":1,"804":1,"1050":1},"3":{"0":1,"130":1,"798":1,"801":1,"804":1,"1050":1,"1435":6}}],["strongly",{"1":{"467":1,"468":1,"469":1,"470":1,"471":1,"472":1,"473":1,"474":1,"475":1,"476":1,"477":1,"478":1,"1047":1,"1048":1,"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1},"3":{"0":4,"135":2,"139":1,"330":1,"337":1,"454":1,"467":1,"469":1,"656":1,"962":1,"1047":1,"1049":1,"1050":1,"1212":2,"1231":1,"1236":1,"1237":6,"1241":1,"1247":4,"1270":1,"1285":13,"1299":14,"1308":1,"1310":10,"1319":1,"1322":2,"1335":1,"1338":2,"1339":1,"1349":2,"1358":3,"1361":1,"1394":4,"1395":1,"1402":1,"1416":1,"1430":1,"1435":23,"1440":1,"1495":2,"1553":1,"1576":1,"1580":1,"1640":1,"1809":1,"1811":1,"2152":1,"2231":3}}],["struggling",{"3":{"1338":2,"2036":1}}],["strung",{"3":{"1299":1}}],["struck",{"3":{"1026":2,"1027":1,"1028":1,"1030":1,"1495":4,"1514":1,"1748":2,"1749":3,"1763":1,"1764":1,"1766":1,"1767":1,"1768":1,"1772":2,"1775":1}}],["structurally",{"0":{"2198":1},"3":{"0":1,"827":1,"946":1,"973":1,"1100":1,"1217":1,"1226":1,"1229":3,"1231":1,"1236":1,"1237":4,"1247":1,"1270":2,"1271":1,"1285":5,"1299":3,"1309":3,"1310":2,"1323":2,"1338":3,"1349":7,"1358":19,"1368":2,"1379":4,"1394":13,"1395":4,"1401":3,"1414":4,"1416":2,"1435":10,"1452":1,"1458":1,"1464":1,"1488":2,"1575":1,"1576":1,"1706":1,"1926":1,"1991":1,"1996":1,"2001":2,"2037":1,"2115":1,"2122":1,"2153":1,"2159":1}}],["structural",{"0":{"1637":1},"3":{"0":3,"135":4,"140":1,"518":1,"523":1,"572":2,"573":1,"579":1,"595":1,"622":1,"657":1,"733":1,"831":1,"865":1,"882":1,"926":1,"957":1,"997":1,"1022":1,"1050":1,"1212":1,"1234":1,"1237":3,"1247":2,"1249":1,"1259":2,"1265":1,"1270":1,"1285":9,"1299":3,"1300":2,"1309":1,"1310":1,"1322":4,"1323":1,"1331":1,"1338":5,"1349":8,"1358":9,"1370":2,"1379":4,"1394":5,"1395":12,"1400":1,"1401":10,"1414":5,"1427":1,"1430":1,"1435":22,"1454":1,"1466":2,"1487":1,"1488":1,"1493":1,"1495":1,"1525":1,"1538":1,"1573":1,"1574":1,"1576":2,"1577":1,"1637":3,"1638":2,"1653":2,"1798":1,"1805":1,"1809":2,"1831":3,"1832":3,"1833":1,"1848":1,"1857":1,"1921":1,"1949":1,"2030":2,"2034":1,"2046":1,"2053":1,"2070":1,"2078":1,"2092":1,"2097":1,"2231":1}}],["structures",{"3":{"1300":2,"1763":1}}],["structure",{"0":{"1614":1,"1795":1},"3":{"0":1,"1":1,"58":1,"130":1,"134":1,"264":1,"571":1,"590":1,"595":1,"617":2,"865":1,"953":1,"955":1,"1004":1,"1237":1,"1270":1,"1271":1,"1299":6,"1300":1,"1310":1,"1323":5,"1338":1,"1349":1,"1358":3,"1376":1,"1379":1,"1394":2,"1395":2,"1401":1,"1404":1,"1414":4,"1427":1,"1435":3,"1444":3,"1454":1,"1485":1,"1493":1,"1554":1,"1557":1,"1606":1,"1620":1,"1639":1,"1653":1,"1740":1,"1749":1,"1772":1,"1774":1,"1793":1,"1801":1,"1931":1,"1990":1,"2040":1,"2041":1,"2054":2,"2058":1,"2106":1}}],["structured",{"3":{"0":1,"53":1,"265":1,"372":1,"1018":1,"1116":1,"1213":1,"1217":1,"1229":1,"1237":3,"1247":2,"1263":1,"1270":5,"1310":3,"1311":7,"1314":1,"1316":1,"1322":3,"1323":1,"1338":1,"1339":1,"1341":1,"1349":1,"1358":11,"1368":1,"1375":1,"1377":1,"1379":4,"1395":1,"1401":1,"1416":1,"1434":2,"1435":1,"1437":1,"1464":2,"1466":1,"1472":1,"1525":1,"1535":1,"1549":1,"1576":3,"1629":1,"1664":1,"1820":1,"1880":1,"1884":1,"1926":1,"1927":1,"1990":1,"2017":1,"2062":1,"2073":1,"2154":1,"2155":1}}],["struct",{"2":{"469":1,"1051":1},"3":{"0":6,"468":4,"469":4,"470":4,"471":1,"472":1,"473":1,"474":1,"477":3,"656":2,"661":1,"797":3,"798":2,"799":3,"802":1,"803":1,"804":1,"1048":1,"1049":1,"1050":6,"1051":2,"1052":1,"1203":27,"1205":2,"1207":47,"1212":1,"1229":2,"1237":1,"1241":1,"1242":1,"1247":8,"1253":1,"1259":12,"1271":1,"1275":1,"1277":1,"1279":1,"1285":20,"1286":1,"1288":1,"1299":47,"1309":4,"1310":2,"1311":1,"1358":1,"1395":6,"1414":1,"1422":2,"1426":3,"1435":31,"1809":5,"1811":1,"2174":1}}],["structs",{"1":{"467":1,"468":1,"469":1,"470":1,"471":1,"472":1,"473":1,"474":1,"475":1,"476":1,"477":1,"478":1,"796":1,"797":1,"798":1,"799":1,"800":1,"801":1,"802":1,"803":1,"804":1,"805":1,"806":1},"3":{"0":2,"467":1,"469":1,"470":1,"472":1,"477":1,"660":1,"661":1,"796":1,"799":2,"1049":1,"1212":2,"1231":1,"1285":1,"1299":2,"2231":2}}],["street",{"3":{"1358":1,"1390":1,"1416":5}}],["streak",{"3":{"1069":1,"1452":1,"1489":1}}],["streamboundedasync",{"3":{"1426":2}}],["streamrendering",{"3":{"1323":1}}],["streamwriter",{"2":{"744":1},"3":{"741":1,"744":1,"1310":3}}],["streamedguardrailtests",{"3":{"1199":1,"1207":3,"1434":2}}],["streamed",{"3":{"741":2,"745":1,"1089":2,"1091":3,"1093":2,"1365":1,"1370":1,"1414":1,"1417":1,"1422":3,"1423":1,"1426":9,"1434":2,"1436":1,"1486":1,"1576":2,"1584":1,"1598":1,"1987":2,"1990":1,"2174":2,"2178":1}}],["stream",{"3":{"443":1,"741":1,"742":1,"743":2,"914":2,"1091":1,"1093":1,"1116":1,"1139":1,"1141":1,"1142":1,"1143":1,"1144":1,"1147":1,"1212":1,"1229":1,"1233":1,"1237":1,"1285":8,"1300":2,"1310":7,"1322":2,"1323":6,"1338":1,"1349":3,"1356":2,"1358":2,"1368":1,"1372":1,"1388":1,"1394":4,"1395":1,"1413":1,"1414":10,"1415":2,"1416":16,"1421":2,"1422":1,"1424":2,"1426":5,"1435":5,"1442":2,"1445":1,"1446":1,"1450":1,"1455":1,"1466":4,"1475":1,"1478":2,"1482":1,"1628":1,"1629":1,"1676":1,"1907":1,"2009":1,"2125":1,"2156":1,"2173":2,"2176":1,"2231":1}}],["streamprefix",{"3":{"142":1}}],["streams",{"3":{"0":1,"318":1,"407":1,"743":1,"1117":1,"1212":1,"1285":2,"1310":3,"1323":1,"1338":1,"1358":2,"1374":1,"1379":2,"1388":1,"1414":1,"1416":1,"1464":1,"1466":1,"1985":1,"1994":1,"2159":1,"2191":1,"2231":1}}],["streaming",{"3":{"0":1,"136":2,"142":1,"741":5,"1091":2,"1093":1,"1310":1,"1311":8,"1379":1,"1394":1,"1416":2,"1421":3,"1422":2,"1423":1,"1424":3,"1426":8,"1435":4,"1525":1,"1548":1,"1926":1,"2043":3,"2173":3,"2174":2,"2176":1,"2178":2}}],["stresses",{"3":{"1285":1,"1435":1}}],["stress",{"3":{"966":1,"1052":1,"1548":1,"1576":1,"1581":1,"2113":1}}],["stretching",{"3":{"1466":1,"2001":1}}],["stretches",{"3":{"1333":1,"1442":1}}],["stretched",{"3":{"309":1,"914":1}}],["stretch",{"3":{"234":1,"491":1,"1338":1,"1442":1,"1653":1}}],["strengths",{"0":{"1524":1,"1575":1,"1589":1},"3":{"1323":1,"1526":1}}],["strengthen",{"3":{"1576":1}}],["strengthened",{"3":{"1291":1,"1299":1}}],["strengthens",{"3":{"831":1,"917":1}}],["strength",{"3":{"103":1,"214":1,"215":1,"341":1,"472":1,"801":1,"1299":2,"1310":1,"1322":2,"1323":1,"1335":1,"1338":3,"1347":1,"1349":2,"1414":5,"1442":1,"1473":1,"1576":1,"1640":7,"1764":1,"1766":3,"1824":1,"1873":1,"1902":1,"1909":1,"2147":1,"2148":1,"2170":1}}],["strike",{"3":{"1772":1}}],["strikethrough",{"3":{"1638":1,"1741":1}}],["striking",{"3":{"1370":1}}],["stride",{"3":{"1533":1,"1576":1}}],["stripping",{"3":{"1285":1,"1299":1,"1322":1,"1323":2,"1338":2,"1435":1,"2178":1}}],["stripped",{"3":{"166":1,"889":1,"1091":2,"1184":1,"1322":1,"1358":2,"1365":1,"1368":1,"1394":1,"1420":1,"1421":1,"1426":1,"1434":1,"1435":3,"1446":1,"1576":1,"1630":1,"2173":1}}],["striping",{"3":{"157":1,"1267":1,"1270":2,"1297":1,"1299":2,"1300":2,"1310":1,"1908":1}}],["strips",{"3":{"0":1,"169":1,"972":1,"1184":1,"1285":4,"1299":5,"1308":1,"1322":2,"1358":1,"1394":2,"1395":2,"1400":1,"1401":1,"1414":1,"1416":1,"1426":1,"1435":5,"1444":1,"1453":1,"1525":1,"2125":1,"2127":1,"2231":1}}],["strip",{"3":{"0":1,"440":1,"1115":1,"1212":1,"1285":2,"1299":1,"1321":1,"1322":1,"1349":1,"1351":1,"1352":1,"1358":2,"1368":2,"1379":1,"1391":2,"1394":3,"1395":1,"1401":1,"1435":1,"1437":1,"1495":1,"1533":1,"1662":1,"2125":1,"2127":2}}],["stripepaymentintentid",{"2":{"1748":1,"1753":1},"3":{"1748":1,"1753":1,"1763":1}}],["stripepaymentservice",{"2":{"72":1,"73":1,"794":1,"1751":1},"3":{"72":2,"73":2,"674":1,"790":3,"791":2,"792":1,"794":1,"1590":1,"1599":1,"1751":1,"1764":1,"1769":1,"2165":1}}],["stripeuri",{"3":{"1599":1}}],["stripesessionid",{"3":{"1285":1,"1763":1}}],["stripesettings",{"2":{"73":1,"1769":1},"3":{"72":1,"73":2,"1769":1}}],["stripes",{"3":{"996":1,"1267":1,"1270":3,"1299":2,"1300":3,"1322":2,"1437":1,"1908":1}}],["stripewebhooksecretprovider",{"2":{"790":1},"3":{"790":2,"2165":1}}],["stripewebhookregistrationservicetests",{"3":{"790":1,"2166":1}}],["stripewebhookregistrationservice",{"2":{"790":1,"792":1,"794":1,"2165":1},"3":{"674":1,"789":1,"790":3,"791":1,"792":4,"794":2,"1588":1,"1590":2,"1595":1,"1596":1,"2165":2}}],["striped",{"3":{"155":1,"157":2,"159":1,"995":1,"996":1,"1183":1,"1267":1,"1270":4,"1299":2,"1310":1,"1322":1,"1437":1,"1495":1,"1576":2,"1584":1,"1667":1,"1908":1,"1911":1}}],["stripeclient",{"2":{"73":1},"3":{"73":1}}],["stripeclientfactorytests",{"3":{"73":2}}],["stripeclientfactory",{"2":{"73":1},"3":{"72":1,"73":2,"674":1}}],["stripeexception",{"2":{"72":1},"3":{"72":1}}],["stripe",{"1":{"787":1,"788":1,"789":1,"790":1,"791":1,"792":1,"793":1,"794":1,"795":1},"2":{"1599":3},"3":{"0":13,"66":1,"72":5,"73":4,"96":2,"101":1,"157":1,"534":1,"535":2,"536":3,"537":1,"538":2,"539":1,"598":1,"599":1,"674":1,"698":1,"701":1,"733":2,"757":1,"759":1,"761":1,"787":1,"789":3,"790":8,"791":1,"792":5,"793":2,"794":2,"795":1,"827":2,"837":1,"838":1,"840":1,"841":1,"996":2,"1026":1,"1212":3,"1270":13,"1297":1,"1299":13,"1300":18,"1310":4,"1338":1,"1435":4,"1488":1,"1587":1,"1588":1,"1590":6,"1594":1,"1595":1,"1596":1,"1599":7,"1672":1,"1745":2,"1748":7,"1749":2,"1751":1,"1753":2,"1754":2,"1758":1,"1762":2,"1764":10,"1767":1,"1768":1,"1769":7,"1772":2,"1775":1,"1784":1,"1908":2,"1909":1,"1910":1,"1914":1,"1916":1,"1919":1,"2011":1,"2023":1,"2103":1,"2109":1,"2112":1,"2135":1,"2160":2,"2165":8,"2166":6,"2167":2,"2168":2,"2220":1,"2227":1,"2231":1}}],["strict=true",{"3":{"1590":1}}],["stricttransportsecurity",{"3":{"1338":1}}],["strictconformanttests",{"3":{"1198":1,"1199":2,"1207":1,"1435":6}}],["strictdriftedtests",{"3":{"1198":1,"1199":2,"1207":1,"1435":8}}],["strictness",{"3":{"461":1,"583":1,"584":2,"618":1,"1322":1}}],["strictest",{"3":{"215":1,"2149":1,"2152":1}}],["stricter",{"3":{"135":1,"177":1,"214":1,"217":1,"243":1,"1093":1,"1223":1,"1241":1,"1247":1,"1308":1,"1319":1,"1322":1,"1323":3,"1358":2,"1394":1,"1435":6,"1442":1,"1495":1,"1499":1,"1897":1,"2000":1,"2152":1}}],["strictly",{"3":{"0":3,"93":1,"95":1,"537":1,"591":1,"666":1,"846":1,"890":1,"914":1,"916":1,"1229":1,"1259":1,"1270":1,"1285":3,"1299":2,"1307":1,"1310":1,"1323":3,"1338":2,"1358":4,"1395":1,"1401":3,"1416":1,"1428":1,"1435":6,"1437":1,"1441":1,"1487":1,"1525":1,"1576":1,"1630":1,"1634":1,"1766":1,"1780":1,"1866":1,"2046":1,"2163":1}}],["strict",{"3":{"0":1,"58":1,"109":1,"187":1,"213":1,"214":1,"215":1,"217":2,"218":1,"219":1,"236":1,"388":1,"583":3,"584":1,"616":1,"618":7,"621":2,"953":1,"1184":4,"1191":1,"1200":1,"1229":2,"1237":1,"1247":3,"1262":1,"1270":2,"1285":2,"1299":2,"1308":2,"1310":1,"1323":1,"1333":1,"1338":3,"1358":3,"1394":1,"1395":1,"1397":1,"1401":1,"1404":1,"1408":1,"1414":4,"1432":1,"1435":25,"1442":1,"1454":3,"1479":1,"1485":1,"1487":1,"1495":1,"1497":1,"1516":1,"1533":2,"1584":1,"1620":1,"1637":1,"1639":1,"1670":1,"1771":1,"1921":1,"1960":1,"1966":1,"1976":1,"2056":1,"2146":2,"2147":1,"2148":1,"2150":1,"2152":1,"2226":1}}],["stringcontent",{"3":{"1435":2}}],["stringcomparemethod",{"3":{"1285":1}}],["stringcomparer",{"3":{"1237":1,"1247":4,"1285":4,"1299":4,"1300":1,"1308":2,"1309":1,"1310":1,"1322":3,"1323":8,"1338":5,"1349":1,"1358":2,"1394":3,"1395":4,"1401":1,"1416":2}}],["stringcomparison",{"3":{"1271":1,"1285":1,"1299":5,"1300":1,"1310":2,"1322":1,"1323":5,"1338":2,"1349":3,"1358":3,"1394":3,"1395":4,"1414":1,"1435":1}}],["stringwithqualityheadervalue",{"3":{"1323":1}}],["stringvalues",{"3":{"1310":1,"1323":1}}],["stringbuilder",{"3":{"1285":2,"1299":1,"1323":2,"1426":1}}],["stringfilterstrategytests",{"3":{"1199":1,"1207":3,"1247":1}}],["stringfilterstrategy",{"3":{"1199":2,"1203":1,"1207":2,"1241":4,"1247":10,"1358":1,"1495":1,"1814":1}}],["stringlocalizer",{"3":{"1323":1}}],["stringlocalizerpluralextensionstests",{"3":{"1199":1,"1207":3,"1585":1}}],["stringlocalizerpluralextensions",{"3":{"1199":1,"1203":1,"1207":2,"1208":1,"1323":5,"1495":1,"1576":2,"1585":1,"1590":1,"1595":1}}],["stringlength",{"3":{"827":1,"1299":3,"1308":4,"1322":3,"1323":5,"1338":4}}],["stringly",{"3":{"188":1,"1323":1,"1358":1,"1963":1}}],["stringifies",{"3":{"1247":1}}],["stringified",{"3":{"741":1,"1308":1,"1310":1,"1377":1}}],["stringidentity",{"3":{"1199":2,"1207":1}}],["stringincrementasync",{"2":{"178":1},"3":{"178":1,"1310":1}}],["stringstrategy",{"3":{"1247":3}}],["stringsplitoptions",{"3":{"1247":3}}],["stringsetasync",{"3":{"996":1,"1322":1}}],["strings",{"0":{"1393":1},"3":{"0":2,"86":2,"88":2,"164":1,"166":1,"188":1,"202":1,"230":1,"265":6,"286":1,"380":1,"448":1,"585":1,"599":2,"643":1,"673":1,"698":1,"700":1,"717":1,"974":1,"980":1,"983":1,"1059":1,"1175":1,"1229":1,"1232":1,"1237":3,"1247":2,"1259":1,"1270":5,"1271":3,"1278":1,"1281":1,"1285":15,"1299":17,"1308":10,"1310":9,"1322":3,"1323":28,"1334":1,"1338":7,"1349":14,"1358":19,"1368":1,"1379":1,"1393":2,"1394":11,"1395":14,"1401":4,"1408":2,"1414":14,"1415":4,"1416":12,"1426":2,"1435":22,"1441":1,"1454":1,"1466":10,"1469":1,"1472":2,"1473":1,"1476":2,"1488":1,"1489":1,"1523":1,"1525":1,"1530":1,"1547":1,"1552":1,"1553":1,"1563":2,"1590":2,"1595":1,"1652":1,"1665":1,"1668":2,"1669":1,"1670":1,"1683":1,"1685":1,"1694":1,"1726":2,"1789":1,"1919":1,"1927":1,"1963":1,"2011":1,"2080":1,"2082":2,"2130":1,"2135":1,"2138":1,"2170":1}}],["string",{"0":{"1304":1},"2":{"360":1,"397":1,"656":1,"658":1,"680":1,"684":1,"701":1,"776":1,"914":1,"959":1,"1132":1,"1332":1,"1355":1,"1442":1,"1445":1,"1466":1,"1540":1,"1686":1,"2009":1,"2077":1,"2145":1,"2155":1,"2158":1},"3":{"0":14,"24":1,"31":1,"33":1,"77":1,"86":1,"109":1,"111":1,"121":2,"159":1,"160":1,"164":1,"166":1,"168":1,"170":4,"171":1,"186":1,"190":1,"214":1,"216":1,"219":1,"225":1,"227":1,"229":1,"241":1,"243":2,"245":2,"249":1,"253":1,"255":1,"259":2,"260":2,"261":1,"265":11,"266":1,"286":1,"305":1,"314":1,"330":1,"335":1,"350":1,"358":1,"359":5,"360":1,"381":1,"386":1,"388":1,"390":1,"392":1,"397":1,"422":1,"425":1,"426":1,"429":1,"499":1,"547":1,"558":1,"572":1,"598":3,"599":8,"600":1,"601":2,"602":1,"604":1,"605":2,"609":1,"626":1,"633":1,"640":5,"643":1,"644":1,"656":1,"657":7,"658":1,"659":2,"680":5,"684":1,"691":1,"692":1,"698":7,"699":2,"701":1,"707":1,"708":2,"710":1,"715":1,"732":1,"733":1,"736":1,"740":1,"759":2,"827":1,"839":3,"881":3,"883":1,"888":1,"899":1,"904":1,"905":1,"906":1,"914":5,"922":1,"926":2,"959":2,"963":2,"964":2,"972":2,"974":1,"996":2,"1005":1,"1017":1,"1018":4,"1027":1,"1028":1,"1034":2,"1036":1,"1050":3,"1067":1,"1089":1,"1091":3,"1094":1,"1095":2,"1132":1,"1133":1,"1135":2,"1150":1,"1175":2,"1176":1,"1177":1,"1192":1,"1208":1,"1212":2,"1229":11,"1232":2,"1234":3,"1235":1,"1236":3,"1237":61,"1238":2,"1240":1,"1241":12,"1242":1,"1244":1,"1246":1,"1247":73,"1250":2,"1259":8,"1264":2,"1265":1,"1269":1,"1270":41,"1271":26,"1273":1,"1278":4,"1279":5,"1283":1,"1285":201,"1289":1,"1298":2,"1299":198,"1300":20,"1306":1,"1308":67,"1309":2,"1310":96,"1311":9,"1319":4,"1322":104,"1323":189,"1325":3,"1331":4,"1332":1,"1338":83,"1340":1,"1343":2,"1347":1,"1349":105,"1351":1,"1352":2,"1358":179,"1365":3,"1368":14,"1378":2,"1379":28,"1381":1,"1384":1,"1387":1,"1388":1,"1393":1,"1394":103,"1395":104,"1399":1,"1401":31,"1404":1,"1407":1,"1414":103,"1415":17,"1416":112,"1418":1,"1421":1,"1426":13,"1428":5,"1429":1,"1430":2,"1434":1,"1435":173,"1437":8,"1440":3,"1441":2,"1442":3,"1443":3,"1445":2,"1448":1,"1450":1,"1452":2,"1453":3,"1454":1,"1455":1,"1458":1,"1459":1,"1464":2,"1465":1,"1466":18,"1471":1,"1476":1,"1480":2,"1485":1,"1487":4,"1488":1,"1489":2,"1490":1,"1491":1,"1495":4,"1525":4,"1530":1,"1531":1,"1534":1,"1540":1,"1563":1,"1576":2,"1581":1,"1582":1,"1585":2,"1590":1,"1629":2,"1630":1,"1632":1,"1639":4,"1640":21,"1650":1,"1652":4,"1661":1,"1663":1,"1666":1,"1667":1,"1668":3,"1669":1,"1683":3,"1684":2,"1685":3,"1686":2,"1699":1,"1719":1,"1723":1,"1726":1,"1729":1,"1757":1,"1763":1,"1814":1,"1815":2,"1830":1,"1831":1,"1846":1,"1848":4,"1850":2,"1852":1,"1886":1,"1911":1,"1915":1,"1920":1,"1921":2,"1922":2,"1934":1,"1936":1,"1939":1,"1943":2,"1947":1,"1948":1,"1949":1,"1950":1,"1961":3,"1972":1,"1981":1,"1987":1,"1988":2,"1990":1,"1999":1,"2000":1,"2002":1,"2007":3,"2009":1,"2012":1,"2055":1,"2069":1,"2071":1,"2073":1,"2075":1,"2082":2,"2084":1,"2085":1,"2096":1,"2107":1,"2125":1,"2126":1,"2128":1,"2129":1,"2135":1,"2141":4,"2142":1,"2145":2,"2146":4,"2148":2,"2152":1,"2155":1,"2158":2,"2159":1,"2192":1,"2200":1,"2229":1,"2231":2}}],["straddle",{"3":{"1535":1}}],["straddles",{"3":{"1323":1,"1355":1,"1358":1}}],["stratification",{"3":{"1486":1}}],["strategic",{"3":{"1576":1,"1580":1,"1581":1,"1582":1,"1810":2,"2113":1}}],["strategies",{"3":{"0":1,"290":1,"295":1,"335":2,"1176":1,"1212":1,"1238":1,"1241":2,"1247":22,"1283":1,"1285":3,"1338":1,"1358":17,"1368":1,"1437":3,"1484":1,"1495":1,"1496":1,"1534":1,"1537":1,"1663":1,"1665":1,"1987":1,"2231":1}}],["strategy",{"0":{"1241":1,"1357":1,"1489":1,"1550":1,"1611":1},"1":{"394":1,"395":1,"396":1,"397":1,"398":1,"399":1,"400":1,"401":1,"402":1,"403":1,"404":1,"405":1,"406":1,"407":1,"408":1,"409":1,"445":1,"446":1,"447":1,"448":1,"449":1,"450":1,"451":1,"452":1,"453":1,"454":1,"455":1,"553":1,"554":1,"555":1,"556":1,"557":1,"558":1,"559":1,"560":1,"1072":1,"1073":1,"1074":1,"1075":1,"1076":1,"1077":1,"1078":1,"1079":1,"1172":1,"1173":1,"1174":1,"1175":1,"1176":1,"1177":1,"1178":1,"1179":1,"1180":1},"2":{"72":1,"407":2,"609":1,"988":1,"2155":1,"2157":1},"3":{"0":12,"17":1,"21":1,"27":1,"71":6,"72":2,"120":1,"153":1,"239":1,"290":3,"295":1,"296":3,"328":1,"330":3,"335":1,"394":1,"407":3,"435":1,"445":1,"459":1,"553":1,"573":1,"609":1,"653":1,"749":1,"819":7,"897":1,"948":1,"987":1,"988":5,"989":1,"991":1,"992":1,"1050":3,"1051":3,"1072":1,"1123":1,"1129":1,"1172":1,"1175":1,"1179":1,"1192":1,"1212":4,"1216":1,"1237":8,"1238":1,"1241":10,"1247":33,"1251":1,"1256":1,"1259":1,"1270":5,"1274":2,"1278":3,"1280":1,"1285":28,"1300":3,"1308":2,"1309":7,"1310":24,"1313":1,"1320":1,"1322":3,"1323":3,"1338":5,"1349":1,"1357":3,"1358":28,"1360":1,"1363":1,"1368":2,"1379":2,"1394":8,"1395":3,"1414":2,"1415":1,"1416":3,"1417":1,"1428":1,"1435":6,"1437":3,"1448":1,"1449":1,"1452":1,"1454":3,"1460":1,"1466":3,"1486":1,"1487":1,"1489":1,"1490":1,"1491":1,"1492":3,"1495":1,"1498":1,"1525":3,"1534":1,"1538":1,"1542":1,"1544":1,"1545":1,"1553":1,"1555":1,"1565":1,"1570":1,"1575":1,"1576":4,"1585":2,"1590":3,"1596":1,"1599":1,"1636":1,"1638":1,"1651":1,"1663":1,"1707":1,"1718":1,"1751":1,"1762":1,"1773":2,"1795":1,"1824":1,"1839":5,"1853":1,"1881":1,"1987":3,"1988":2,"1989":1,"1990":3,"1999":1,"2031":5,"2037":1,"2054":1,"2132":1,"2155":2,"2157":1,"2159":1,"2231":4}}],["stray",{"3":{"1091":1,"1299":1,"1394":2,"1395":1,"1424":1,"1435":2,"1495":3,"2149":1}}],["straightforward",{"3":{"1265":1,"1322":1,"1346":1}}],["straight",{"3":{"0":2,"102":1,"109":1,"121":1,"135":1,"150":1,"295":1,"423":1,"424":1,"459":1,"522":1,"523":1,"534":1,"545":1,"599":1,"674":1,"700":1,"749":1,"782":1,"790":1,"793":1,"799":1,"827":1,"881":1,"905":1,"987":1,"1018":1,"1042":1,"1077":1,"1100":1,"1116":1,"1183":1,"1187":1,"1194":1,"1237":1,"1244":1,"1247":2,"1252":1,"1256":1,"1257":1,"1259":6,"1263":1,"1264":1,"1270":11,"1271":1,"1278":2,"1285":23,"1288":1,"1299":10,"1300":1,"1306":1,"1308":5,"1310":11,"1317":1,"1322":4,"1323":23,"1338":6,"1346":1,"1349":6,"1358":41,"1368":4,"1374":1,"1379":7,"1382":1,"1388":1,"1391":1,"1394":20,"1395":18,"1401":1,"1414":14,"1415":3,"1416":3,"1435":11,"1437":2,"1466":2,"1478":1,"1584":1,"1650":1,"1668":1,"1729":1,"1772":1,"1822":1,"1839":2,"1843":1,"1850":1,"1858":1,"1901":1,"1916":1,"1921":1,"1944":1,"1954":2,"1956":1,"1974":1,"1985":1,"1990":1,"1993":1,"1997":1,"2046":1,"2065":1,"2068":1,"2095":1,"2123":1,"2136":1,"2156":1,"2161":1,"2164":1,"2185":1,"2203":1}}],["strangers",{"3":{"1395":1}}],["stranger",{"3":{"1017":1,"1414":1,"1453":1}}],["strangler",{"3":{"0":2,"53":1,"57":1,"59":1,"1012":1,"1311":1,"1435":1,"1525":1,"1531":1,"1543":1,"1575":1,"1576":1,"1585":1,"1590":1,"1662":1}}],["stranding",{"3":{"897":1,"1310":1,"1430":1}}],["strands",{"3":{"539":1,"1270":1,"1308":1,"1323":1,"1435":1,"2160":1,"2166":1}}],["strand",{"3":{"350":1,"1255":1,"1317":1,"1323":3,"1405":1,"1414":1,"1917":1,"1976":1}}],["strandedfixturehandler",{"3":{"1207":1,"1430":1,"1435":3}}],["strandedfixturehandlerbase",{"3":{"1199":2,"1207":1,"1430":1,"1435":7}}],["stranded",{"3":{"0":1,"534":1,"788":1,"793":1,"794":1,"1003":1,"1207":2,"1322":1,"1430":2,"1435":9,"1495":1,"1525":1,"1568":1,"1576":2,"2047":1,"2168":1}}],["stolen",{"3":{"500":1,"907":2,"908":1,"1299":3,"1321":2,"1322":1,"1414":1,"1435":1,"1900":1,"1904":2,"1979":1,"1980":1,"2139":2,"2143":1}}],["stood",{"3":{"111":1,"136":1,"285":1,"497":1,"1311":1,"1349":1,"1395":1,"1435":1,"1473":1,"2012":1,"2231":1}}],["stock",{"3":{"0":3,"99":2,"534":3,"535":3,"536":6,"538":4,"539":4,"540":2,"556":1,"793":2,"809":1,"1066":1,"1067":2,"1285":1,"1310":1,"1327":2,"1338":7,"1440":5,"1443":1,"1489":1,"1590":2,"1741":2,"1748":1,"1749":1,"1750":1,"1754":1,"1762":1,"1763":3,"1764":1,"1766":2,"1767":1,"1768":2,"1775":2,"2021":1,"2109":1,"2112":1,"2160":4,"2162":1,"2163":2,"2166":3,"2167":3,"2214":1}}],["storable",{"3":{"1368":2}}],["storagedelete",{"3":{"1466":1}}],["storagewrite",{"3":{"1466":1}}],["storageread",{"3":{"1466":2}}],["storageremove",{"3":{"1416":3}}],["storageaccesskey",{"3":{"1466":1}}],["storageallowsharedkeyaccess",{"3":{"1466":1}}],["storagev2",{"3":{"1466":1}}],["storageclearprefix",{"3":{"1416":1}}],["storageset",{"3":{"1416":3}}],["storageget",{"3":{"1416":1}}],["storagekey",{"3":{"1323":2}}],["storagenotconfigured",{"2":{"1116":1,"1388":1},"3":{"1116":1,"1358":1,"1388":1}}],["storage",{"0":{"1284":1,"2125":1},"1":{"163":1,"164":1,"165":1,"166":1,"167":1,"168":1,"169":1,"170":1,"171":1,"437":1,"438":1,"439":1,"440":1,"441":1,"442":1,"443":1,"444":1,"2123":1,"2124":1,"2125":1,"2126":1,"2127":1},"2":{"1483":1,"2126":1},"3":{"0":11,"163":1,"165":2,"166":1,"182":1,"207":1,"243":1,"245":3,"248":1,"251":1,"254":1,"257":1,"258":1,"259":2,"353":1,"359":1,"360":1,"361":1,"365":1,"387":1,"388":2,"403":1,"437":1,"438":2,"439":2,"441":2,"442":1,"443":5,"444":2,"459":1,"497":2,"505":2,"506":3,"507":8,"508":1,"509":4,"510":1,"511":2,"513":6,"514":3,"536":1,"555":1,"560":1,"598":2,"599":1,"602":2,"641":1,"663":1,"664":1,"665":6,"666":1,"670":1,"707":2,"708":1,"717":1,"732":1,"737":1,"853":1,"855":1,"903":1,"906":1,"972":1,"974":2,"1043":3,"1115":2,"1116":11,"1117":2,"1118":1,"1119":2,"1120":1,"1182":1,"1183":1,"1192":2,"1203":11,"1207":26,"1212":3,"1237":5,"1247":2,"1250":1,"1259":3,"1271":1,"1272":1,"1284":8,"1285":44,"1286":1,"1299":5,"1300":2,"1308":2,"1309":4,"1310":3,"1317":2,"1322":35,"1323":49,"1338":2,"1349":6,"1356":3,"1358":25,"1360":1,"1368":4,"1372":1,"1374":1,"1379":4,"1394":5,"1395":7,"1406":1,"1409":1,"1410":1,"1414":19,"1415":2,"1416":37,"1435":14,"1436":1,"1437":2,"1444":1,"1445":1,"1454":3,"1464":6,"1466":13,"1469":1,"1471":1,"1472":1,"1473":2,"1477":2,"1480":2,"1483":2,"1486":1,"1489":1,"1495":3,"1525":1,"1562":1,"1566":1,"1576":2,"1598":1,"1630":2,"1634":1,"1637":1,"1638":2,"1639":2,"1640":5,"1659":1,"1667":2,"1668":3,"1669":2,"1676":1,"1747":1,"1764":3,"1778":2,"1853":1,"1854":1,"1861":2,"1862":1,"1863":1,"1899":1,"1900":1,"1903":1,"1921":1,"1932":1,"1947":2,"1967":1,"1969":2,"1979":1,"2011":1,"2033":2,"2035":2,"2073":1,"2097":1,"2122":2,"2123":3,"2125":7,"2126":3,"2127":3,"2132":1,"2141":1,"2142":3,"2143":1,"2144":1,"2206":1,"2207":1,"2209":2,"2231":3}}],["stories",{"3":{"506":1,"508":1,"555":1,"560":1,"2130":1}}],["storing",{"3":{"206":1,"208":1,"995":1,"1092":1,"1259":3,"1270":1,"1285":5,"1299":2,"1308":1,"1310":1,"1322":1,"1323":4,"1338":1,"1349":4,"1358":1,"1361":1,"1368":4,"1394":4,"1395":2,"1416":3,"1435":3,"1630":1,"1899":1,"1978":1,"2123":1,"2124":1}}],["story",{"3":{"24":1,"71":1,"148":1,"168":1,"236":1,"349":1,"350":1,"365":1,"379":1,"413":1,"507":1,"633":1,"707":1,"916":1,"990":1,"1018":1,"1020":1,"1035":1,"1044":1,"1074":1,"1084":1,"1212":1,"1228":1,"1230":1,"1234":1,"1236":1,"1237":2,"1241":1,"1247":2,"1259":1,"1263":2,"1270":2,"1271":1,"1275":1,"1285":10,"1289":1,"1290":1,"1291":1,"1299":5,"1300":1,"1302":1,"1308":3,"1310":3,"1311":2,"1322":4,"1323":4,"1335":1,"1347":1,"1349":4,"1352":1,"1358":3,"1365":1,"1368":3,"1374":1,"1375":1,"1379":3,"1380":1,"1394":2,"1395":7,"1399":1,"1409":1,"1414":2,"1415":2,"1416":6,"1426":1,"1427":1,"1435":4,"1454":2,"1470":1,"1472":1,"1475":1,"1481":1,"1487":1,"1495":1,"1546":1,"1548":1,"1565":1,"1576":1,"1590":1,"1698":1,"1705":1,"1722":1,"1726":1,"1832":1,"1852":1,"1893":1,"1898":2,"1926":1,"1928":1,"1967":1,"1977":1,"1984":2,"1988":1,"2028":1,"2033":1,"2058":1,"2060":1,"2061":1,"2063":2,"2088":1,"2089":1,"2112":1,"2120":1,"2128":2,"2145":1,"2154":1,"2168":1,"2181":1,"2193":1,"2207":1,"2231":1}}],["storming",{"3":{"1437":1}}],["storms",{"3":{"174":1,"1546":1,"1565":1,"2013":1,"2161":1,"2181":1}}],["storm",{"3":{"0":3,"72":1,"407":1,"612":1,"792":1,"827":2,"883":1,"1259":1,"1329":1,"1338":3,"1401":1,"1435":1,"1437":1,"1442":1,"1464":2,"1667":1,"2006":1,"2072":1,"2165":1,"2231":1}}],["storehome",{"3":{"1590":6,"1594":1,"1595":3,"1596":2,"1599":2}}],["storehomepagecontent",{"3":{"1323":1}}],["storeharness",{"3":{"1199":2,"1207":1}}],["storegenerated",{"3":{"1175":1}}],["storeaccounts",{"3":{"1285":1}}],["storeapphostfixture",{"3":{"914":1,"1069":1}}],["storearchitecturemap",{"2":{"132":1,"1161":1},"3":{"132":1,"1161":1,"1430":1,"1435":3,"1488":2,"1589":1,"1590":4,"1599":1,"2041":1}}],["storetimezonesettings",{"2":{"675":1,"676":1},"3":{"675":1,"676":1}}],["storefronthostapplicationfactory",{"2":{"572":1},"3":{"572":1}}],["storefront",{"3":{"0":2,"217":1,"825":1,"830":1,"831":1,"832":1,"1027":1,"1028":2,"1124":1,"1415":1,"1435":1,"1599":2,"1749":1,"1763":2,"1764":1,"1766":1}}],["stores",{"3":{"0":6,"24":1,"26":1,"231":1,"243":1,"245":2,"256":1,"261":1,"286":1,"309":1,"420":1,"696":1,"702":1,"715":1,"727":1,"732":1,"848":1,"849":1,"854":1,"883":1,"904":1,"945":1,"947":1,"1012":1,"1025":1,"1026":1,"1034":1,"1035":1,"1068":1,"1116":1,"1229":1,"1237":2,"1247":1,"1259":4,"1263":1,"1270":5,"1273":1,"1275":2,"1284":1,"1285":13,"1292":2,"1299":7,"1300":5,"1310":8,"1317":1,"1321":1,"1322":7,"1323":11,"1338":6,"1349":2,"1356":1,"1358":5,"1368":2,"1394":8,"1395":4,"1401":1,"1405":1,"1408":1,"1414":10,"1415":1,"1416":11,"1432":1,"1435":6,"1437":1,"1440":1,"1453":1,"1455":1,"1464":1,"1466":1,"1487":1,"1495":1,"1539":1,"1552":1,"1630":3,"1640":1,"1659":1,"1666":1,"1853":1,"1901":1,"1908":1,"1910":1,"1916":1,"2039":1,"2142":1,"2148":1,"2186":1,"2231":1}}],["storedat",{"3":{"1323":1}}],["storedpermission",{"3":{"1435":1}}],["storedpermissions",{"3":{"1299":2,"1323":1}}],["storedpermissionroleadministrationservicetests",{"2":{"1199":1},"3":{"1199":1,"1207":4}}],["storedpermissionroleadministrationservice",{"2":{"1286":1,"1296":1,"2199":1},"3":{"1059":1,"1199":2,"1203":1,"1207":2,"1286":1,"1296":2,"1299":22,"1322":1,"1495":1,"2199":1}}],["storedhashes",{"3":{"1299":1}}],["storedstate",{"3":{"1199":2,"1207":1}}],["stored",{"0":{"1250":1,"2198":1},"1":{"2193":1,"2194":1,"2195":1,"2196":1,"2197":1,"2198":1,"2199":1,"2200":1,"2201":1,"2202":1},"2":{"1901":1},"3":{"0":12,"24":3,"27":1,"41":1,"126":1,"157":5,"160":1,"184":1,"186":2,"224":1,"225":1,"231":1,"243":1,"246":1,"248":1,"265":1,"284":1,"309":2,"310":6,"311":1,"314":1,"353":1,"358":1,"359":6,"360":1,"363":1,"364":1,"365":3,"387":1,"388":1,"395":1,"397":1,"399":1,"403":1,"423":1,"443":2,"499":11,"500":1,"501":2,"507":2,"511":1,"513":1,"528":1,"529":2,"530":1,"539":1,"574":1,"591":1,"605":1,"666":1,"672":1,"692":1,"707":1,"717":1,"740":1,"767":2,"790":1,"813":1,"849":1,"852":1,"854":1,"880":1,"881":2,"885":1,"904":1,"944":2,"945":4,"946":2,"948":5,"965":1,"971":1,"995":1,"996":1,"1017":1,"1018":3,"1026":3,"1034":1,"1035":1,"1042":2,"1044":1,"1050":1,"1057":1,"1059":6,"1060":2,"1061":4,"1063":1,"1114":1,"1115":2,"1116":3,"1118":2,"1135":1,"1140":1,"1183":1,"1212":7,"1229":2,"1234":2,"1237":4,"1247":2,"1250":2,"1251":1,"1256":2,"1259":11,"1270":7,"1271":2,"1282":2,"1283":2,"1284":1,"1285":35,"1290":2,"1291":2,"1292":1,"1293":1,"1294":1,"1296":3,"1299":75,"1300":13,"1308":2,"1310":29,"1320":1,"1322":15,"1323":48,"1338":5,"1341":2,"1343":1,"1346":3,"1349":14,"1351":1,"1355":1,"1356":1,"1358":37,"1361":3,"1368":11,"1379":7,"1381":1,"1383":1,"1388":2,"1390":1,"1394":18,"1395":13,"1401":3,"1404":1,"1408":5,"1410":1,"1413":1,"1414":24,"1416":18,"1431":1,"1434":1,"1435":12,"1437":2,"1442":1,"1453":2,"1454":2,"1461":1,"1464":2,"1466":4,"1491":1,"1495":3,"1562":1,"1566":1,"1576":2,"1584":1,"1629":6,"1630":8,"1632":1,"1637":1,"1638":4,"1639":3,"1640":20,"1663":1,"1685":2,"1731":1,"1746":2,"1748":2,"1763":3,"1764":3,"1766":2,"1778":1,"1848":1,"1853":1,"1860":1,"1899":1,"1901":4,"1907":3,"1908":4,"1921":1,"1932":1,"1963":1,"1974":1,"1978":1,"1979":1,"1981":1,"2042":1,"2046":1,"2077":1,"2082":2,"2124":1,"2127":1,"2140":2,"2141":4,"2143":1,"2156":1,"2185":1,"2191":1,"2192":1,"2193":1,"2198":3,"2199":3,"2201":3,"2202":2,"2207":2,"2210":1,"2231":4}}],["store",{"0":{"91":1,"96":1,"1478":1,"1479":2,"1480":1,"1602":1,"1994":1},"1":{"1586":1,"1587":1,"1588":1,"1589":1,"1590":1,"1591":1,"1592":1,"1593":1,"1594":1,"1595":1,"1596":1,"1597":1,"1598":1,"1599":1,"1680":1,"1681":1,"1682":1,"1683":1,"1684":1,"1685":1,"1686":1,"1687":1,"1688":1,"1689":1,"1690":1,"2108":1,"2109":1,"2110":1,"2111":1,"2112":1,"2113":1,"2114":1},"2":{"62":3,"80":1,"395":1,"401":1,"493":3,"564":1,"642":1,"1195":1,"1215":1,"1470":1,"1485":1,"1489":2,"1502":1,"1587":3,"1590":1,"1774":3,"2104":1,"2110":1,"2201":1},"3":{"0":117,"3":5,"7":2,"24":1,"27":3,"37":1,"39":1,"42":7,"59":5,"62":6,"63":1,"66":1,"68":1,"72":9,"73":5,"76":1,"80":8,"85":1,"86":1,"91":6,"93":5,"94":12,"95":5,"96":12,"97":2,"98":3,"99":2,"100":1,"101":2,"102":1,"103":3,"104":3,"109":3,"115":1,"117":3,"121":3,"127":3,"128":7,"132":1,"135":1,"145":6,"146":1,"147":5,"149":3,"150":1,"151":1,"152":5,"155":1,"157":4,"159":1,"161":1,"165":2,"166":2,"167":1,"170":1,"175":3,"180":11,"184":2,"186":5,"193":1,"195":17,"196":2,"200":1,"201":1,"202":1,"203":2,"212":1,"214":1,"217":6,"218":7,"223":1,"229":1,"230":11,"235":7,"238":1,"241":1,"242":1,"243":27,"245":10,"249":3,"250":4,"251":2,"252":3,"253":1,"254":2,"256":1,"257":2,"258":1,"259":3,"260":10,"261":8,"279":1,"281":2,"283":3,"285":1,"291":5,"293":1,"295":6,"300":1,"310":4,"314":2,"318":13,"320":1,"322":1,"324":3,"325":5,"326":3,"333":2,"341":5,"345":2,"350":1,"351":1,"352":1,"353":1,"359":3,"368":1,"373":3,"375":3,"386":4,"387":3,"388":1,"390":14,"391":13,"392":8,"393":7,"395":4,"397":1,"401":11,"403":13,"405":1,"409":1,"414":1,"418":1,"421":1,"422":2,"433":1,"435":2,"439":1,"446":2,"448":12,"452":19,"453":1,"454":4,"458":2,"459":5,"461":1,"462":1,"463":6,"464":2,"465":5,"466":1,"468":1,"470":7,"475":10,"476":11,"477":11,"478":2,"481":1,"482":1,"483":2,"486":1,"487":1,"488":2,"489":2,"490":2,"491":2,"492":3,"493":7,"494":4,"495":4,"497":12,"499":11,"500":1,"502":1,"507":10,"511":4,"513":6,"514":2,"527":1,"534":2,"535":2,"536":3,"537":2,"538":1,"539":6,"540":1,"543":5,"545":22,"547":1,"550":1,"551":1,"552":7,"554":2,"556":25,"562":1,"563":5,"564":20,"566":6,"567":8,"572":29,"574":1,"575":3,"576":1,"577":4,"578":8,"582":1,"583":30,"585":13,"586":7,"589":1,"591":7,"593":1,"594":3,"597":1,"598":1,"599":35,"600":5,"601":5,"602":3,"603":5,"604":3,"607":14,"609":52,"611":11,"612":6,"613":6,"616":4,"618":7,"620":3,"621":4,"624":8,"625":7,"626":37,"627":2,"628":6,"629":5,"638":4,"640":25,"641":1,"642":8,"643":5,"644":10,"649":10,"651":5,"655":5,"657":14,"658":1,"660":2,"663":1,"665":12,"666":1,"667":2,"668":9,"669":7,"674":17,"675":3,"676":5,"677":3,"681":2,"683":1,"689":1,"690":1,"691":2,"698":2,"702":2,"707":4,"710":1,"711":1,"713":1,"714":1,"715":4,"718":6,"719":4,"720":2,"723":1,"724":4,"725":20,"726":2,"727":2,"728":7,"733":5,"737":2,"739":1,"741":2,"743":6,"744":2,"745":1,"749":14,"751":4,"752":2,"755":1,"756":4,"757":24,"758":1,"759":6,"760":5,"761":6,"764":5,"765":4,"766":35,"767":2,"768":6,"769":5,"774":17,"780":4,"782":15,"789":6,"790":21,"792":4,"793":16,"794":9,"798":2,"799":1,"803":1,"804":5,"805":2,"806":1,"808":2,"809":5,"810":2,"813":5,"825":4,"826":8,"827":25,"829":1,"830":10,"831":7,"832":14,"833":7,"836":1,"837":13,"838":25,"839":6,"840":3,"841":6,"842":1,"846":1,"853":4,"854":5,"855":2,"856":2,"857":1,"859":1,"861":15,"863":9,"864":5,"867":1,"868":7,"869":14,"870":8,"872":2,"873":12,"875":2,"876":23,"877":7,"879":1,"881":7,"884":3,"889":3,"891":4,"892":1,"896":1,"897":10,"900":6,"903":2,"905":6,"906":1,"907":1,"908":1,"913":6,"914":7,"916":5,"917":2,"924":7,"926":7,"927":4,"930":1,"933":1,"939":1,"946":3,"949":1,"954":16,"956":1,"958":1,"959":2,"960":1,"964":1,"971":3,"972":4,"973":1,"974":3,"979":1,"980":3,"981":1,"983":1,"986":2,"988":7,"990":3,"991":3,"994":1,"996":2,"999":1,"1004":5,"1006":1,"1010":1,"1012":6,"1014":2,"1020":1,"1025":11,"1026":28,"1027":1,"1028":2,"1029":2,"1036":1,"1044":1,"1050":5,"1051":2,"1054":4,"1058":4,"1063":1,"1065":2,"1067":3,"1069":3,"1073":1,"1074":3,"1078":2,"1081":1,"1082":7,"1083":3,"1085":2,"1086":3,"1090":1,"1093":1,"1094":1,"1117":1,"1123":2,"1124":13,"1127":3,"1128":1,"1129":1,"1133":4,"1139":1,"1141":1,"1142":2,"1144":1,"1154":2,"1156":1,"1161":4,"1162":1,"1163":1,"1168":11,"1184":1,"1187":3,"1195":1,"1207":1,"1208":1,"1212":9,"1214":1,"1215":1,"1216":1,"1229":4,"1231":1,"1233":1,"1237":43,"1246":2,"1247":15,"1253":1,"1257":1,"1259":23,"1262":5,"1263":1,"1264":8,"1265":1,"1266":1,"1269":2,"1270":75,"1271":29,"1273":1,"1277":1,"1281":1,"1282":1,"1284":1,"1285":85,"1289":5,"1292":1,"1293":1,"1299":124,"1300":25,"1308":9,"1309":6,"1310":108,"1311":13,"1314":1,"1315":3,"1317":2,"1321":2,"1322":117,"1323":129,"1325":2,"1336":1,"1338":139,"1349":1,"1356":2,"1358":13,"1368":2,"1378":2,"1379":7,"1394":9,"1395":6,"1401":2,"1404":2,"1405":1,"1408":1,"1414":18,"1415":1,"1416":68,"1426":1,"1427":1,"1430":1,"1435":321,"1437":4,"1440":1,"1441":1,"1443":6,"1446":1,"1452":2,"1453":1,"1454":10,"1458":1,"1459":1,"1461":1,"1464":3,"1466":5,"1470":8,"1473":1,"1474":1,"1478":21,"1479":30,"1480":3,"1481":2,"1482":3,"1484":1,"1485":10,"1486":2,"1487":4,"1488":5,"1489":8,"1491":9,"1495":3,"1499":2,"1502":1,"1511":1,"1519":1,"1525":2,"1530":1,"1544":1,"1555":1,"1565":1,"1572":1,"1576":2,"1581":4,"1582":1,"1584":2,"1586":2,"1587":4,"1588":2,"1590":80,"1592":1,"1593":1,"1595":20,"1596":3,"1597":1,"1598":12,"1599":18,"1600":1,"1601":1,"1626":1,"1630":1,"1634":1,"1635":1,"1637":1,"1640":2,"1644":1,"1645":1,"1649":2,"1652":1,"1653":1,"1657":2,"1658":1,"1659":1,"1664":1,"1665":1,"1666":1,"1667":2,"1668":2,"1671":1,"1672":1,"1674":1,"1680":1,"1702":2,"1704":3,"1707":1,"1720":1,"1723":1,"1735":1,"1738":1,"1739":1,"1746":1,"1755":1,"1756":1,"1759":2,"1760":6,"1763":14,"1764":35,"1769":1,"1772":3,"1774":3,"1783":1,"1784":1,"1809":2,"1814":3,"1827":1,"1838":2,"1850":1,"1853":1,"1855":1,"1857":1,"1871":1,"1873":1,"1874":2,"1875":1,"1890":1,"1895":2,"1897":1,"1899":1,"1904":1,"1908":2,"1909":1,"1911":3,"1914":1,"1915":1,"1920":1,"1921":6,"1922":5,"1933":1,"1946":1,"1947":2,"1956":1,"1962":1,"1966":1,"1969":2,"1971":2,"1972":1,"1977":2,"1978":1,"1981":2,"1982":5,"1984":3,"1993":1,"1994":4,"1995":1,"1996":1,"1999":9,"2003":1,"2011":1,"2023":2,"2024":3,"2041":1,"2042":1,"2043":5,"2045":1,"2054":3,"2056":1,"2065":1,"2068":1,"2082":1,"2100":1,"2103":1,"2104":1,"2108":1,"2109":1,"2110":6,"2111":2,"2112":5,"2113":1,"2114":1,"2122":1,"2123":1,"2124":1,"2125":1,"2126":1,"2127":3,"2130":7,"2135":1,"2137":2,"2141":1,"2145":1,"2148":1,"2150":4,"2155":2,"2156":1,"2157":5,"2160":1,"2163":1,"2165":2,"2167":1,"2168":1,"2178":2,"2184":1,"2186":1,"2188":1,"2191":1,"2192":2,"2199":1,"2201":5,"2209":1,"2227":1,"2230":1,"2231":3}}],["stoptracingasync",{"3":{"1435":1}}],["stoplistenasync",{"3":{"1416":1}}],["stopdetecting",{"3":{"1416":1}}],["stopwords",{"3":{"1358":1}}],["stopwatch",{"2":{"1163":1,"1820":1},"3":{"1163":1,"1263":3,"1270":6,"1310":2,"1322":5,"1338":3,"1424":1,"1426":2,"1435":3,"1474":1,"1817":1,"1820":2}}],["stopasync",{"3":{"572":2,"1069":1,"1259":2,"1285":1,"1322":1,"1323":1,"1394":1,"1416":7,"1428":1,"1435":6,"1487":2,"2054":2}}],["stop",{"0":{"2071":1},"1":{"1802":1,"1803":1,"1804":1,"1805":1,"1806":1},"3":{"80":1,"81":2,"115":1,"156":2,"237":1,"283":1,"341":1,"433":1,"472":1,"557":1,"571":1,"595":1,"674":1,"751":1,"791":1,"827":1,"870":1,"897":2,"898":1,"910":1,"932":1,"948":1,"1006":1,"1124":1,"1133":1,"1134":1,"1212":1,"1237":2,"1240":1,"1247":1,"1251":1,"1259":3,"1270":1,"1285":2,"1299":3,"1310":6,"1322":4,"1323":4,"1325":1,"1327":1,"1338":4,"1349":1,"1356":1,"1358":7,"1365":1,"1368":1,"1394":3,"1395":9,"1414":5,"1416":4,"1435":11,"1437":3,"1442":1,"1455":2,"1466":2,"1488":1,"1489":1,"1525":1,"1588":1,"1594":1,"1595":1,"1605":1,"1653":1,"1670":1,"1683":1,"1726":1,"1728":1,"1739":1,"1748":1,"1764":1,"1795":1,"1801":1,"1802":1,"1811":1,"1858":1,"1867":1,"1890":1,"1892":1,"1923":1,"1979":1,"1999":1,"2000":3,"2015":1,"2048":1,"2049":1,"2065":1,"2079":1,"2084":1,"2096":1,"2147":1,"2149":1,"2168":1,"2179":1,"2205":1}}],["stopped",{"3":{"0":1,"67":1,"109":1,"170":1,"248":1,"318":1,"413":1,"497":1,"499":1,"527":1,"563":1,"741":1,"757":1,"832":1,"840":1,"876":1,"905":2,"932":1,"1005":2,"1019":1,"1247":2,"1259":1,"1270":2,"1285":3,"1308":1,"1310":1,"1311":1,"1321":1,"1322":4,"1323":4,"1327":1,"1331":1,"1338":4,"1358":2,"1394":1,"1395":2,"1398":1,"1401":2,"1414":1,"1416":4,"1434":1,"1435":12,"1437":2,"1448":1,"1450":1,"1454":1,"1466":1,"1477":1,"1488":2,"1495":1,"1698":1,"1726":1,"1764":1,"1825":1,"1918":2,"1945":1,"1991":1,"2038":1,"2044":1,"2096":1}}],["stoppingtoken",{"2":{"237":1},"3":{"237":1,"518":1,"1100":1,"1285":1,"1322":2,"1338":4,"1395":3}}],["stopping",{"3":{"0":3,"135":1,"235":1,"255":1,"626":1,"898":1,"1091":1,"1187":1,"1271":1,"1311":1,"1322":1,"1338":1,"1379":1,"1394":2,"1395":1,"1416":2,"1426":3,"1430":1,"1434":1,"1435":3,"1491":1,"1664":1,"1806":1,"1918":1,"1921":1}}],["stopsgracefully",{"3":{"1435":1}}],["stopsthewalk",{"3":{"1435":2}}],["stops",{"0":{"1988":1},"3":{"0":13,"1":1,"19":1,"24":2,"27":1,"57":1,"72":1,"81":1,"109":1,"111":1,"122":1,"137":1,"148":1,"160":1,"176":1,"216":1,"235":1,"241":1,"243":1,"265":1,"290":1,"361":1,"369":1,"370":1,"371":1,"391":1,"399":1,"401":1,"403":1,"408":1,"472":1,"528":1,"535":1,"539":1,"549":2,"593":1,"608":1,"611":1,"612":1,"614":1,"626":1,"632":1,"633":1,"648":1,"657":1,"698":1,"700":2,"707":1,"714":1,"741":1,"750":1,"782":1,"789":1,"800":1,"809":1,"810":1,"821":1,"827":1,"829":1,"838":1,"863":1,"869":1,"874":1,"889":1,"898":1,"899":1,"912":1,"921":2,"923":1,"924":1,"926":2,"948":1,"963":1,"973":1,"1020":1,"1022":1,"1033":1,"1042":1,"1049":1,"1051":1,"1067":1,"1093":1,"1153":1,"1163":1,"1212":2,"1229":2,"1233":1,"1237":1,"1247":2,"1256":2,"1259":8,"1270":7,"1274":1,"1277":1,"1280":1,"1285":16,"1299":11,"1300":1,"1308":2,"1310":8,"1311":1,"1321":1,"1322":9,"1323":11,"1326":1,"1338":12,"1349":2,"1358":15,"1368":3,"1379":1,"1394":12,"1395":14,"1399":2,"1400":1,"1401":1,"1404":1,"1414":8,"1415":2,"1416":5,"1422":1,"1424":1,"1426":4,"1430":1,"1435":22,"1437":3,"1440":3,"1442":1,"1448":1,"1454":1,"1464":1,"1466":3,"1471":1,"1474":1,"1487":1,"1488":2,"1489":1,"1491":1,"1493":2,"1530":1,"1544":1,"1653":1,"1664":1,"1680":1,"1684":1,"1691":1,"1701":1,"1716":1,"1721":1,"1728":1,"1768":1,"1823":1,"1848":1,"1850":1,"1875":1,"1901":2,"1935":1,"1950":1,"1963":1,"1987":1,"1989":1,"1993":1,"2000":1,"2028":1,"2040":2,"2044":1,"2047":1,"2055":1,"2065":1,"2066":1,"2068":1,"2074":1,"2081":1,"2096":2,"2115":1,"2141":1,"2145":1,"2147":2,"2149":1,"2156":1,"2158":1,"2161":1,"2163":1,"2165":1,"2167":1,"2169":1,"2174":1,"2179":1,"2192":1}}],["stakeholders",{"3":{"1540":1}}],["stake",{"3":{"1395":1,"1435":1,"1437":1,"2194":1}}],["stakes",{"3":{"530":1,"1285":1,"1323":1,"1349":1,"2147":1}}],["stash",{"3":{"1310":2,"1323":1,"1351":1}}],["stashes",{"3":{"350":1,"419":1,"1273":1,"1297":1,"1299":3,"1310":3,"1338":1,"1358":1,"1414":1,"1442":1,"1824":1,"1974":2}}],["stashed",{"3":{"214":1,"350":1,"420":1,"1299":2,"1310":3,"1335":1,"1975":1,"2148":1}}],["staffs",{"3":{"1349":1}}],["staff",{"3":{"689":1}}],["stably",{"3":{"1323":1,"1394":2}}],["stablepolls",{"3":{"1394":3}}],["stablepollsforcompletion",{"3":{"1394":1}}],["stable",{"3":{"0":3,"24":1,"264":1,"265":4,"266":1,"303":1,"341":1,"434":2,"449":1,"549":1,"593":1,"635":1,"742":1,"750":1,"751":1,"880":1,"891":1,"964":1,"1012":1,"1133":1,"1178":1,"1212":1,"1229":3,"1233":1,"1237":5,"1247":2,"1259":4,"1269":1,"1270":4,"1271":3,"1276":1,"1285":7,"1299":7,"1300":1,"1307":1,"1308":5,"1309":1,"1310":14,"1317":1,"1322":14,"1323":3,"1338":1,"1347":1,"1349":16,"1352":2,"1358":46,"1368":2,"1379":4,"1389":2,"1394":4,"1395":28,"1401":16,"1405":1,"1409":1,"1414":9,"1415":1,"1416":7,"1426":1,"1434":1,"1435":8,"1437":3,"1440":1,"1464":1,"1495":1,"1518":1,"1535":1,"1545":1,"1559":1,"1564":1,"1576":1,"1584":1,"1637":1,"1638":1,"1640":3,"1663":1,"1664":1,"1795":1,"1804":1,"1895":1,"1898":1,"1910":2,"1911":1,"1957":1,"2082":1,"2085":2,"2131":1,"2137":2,"2157":1,"2179":1,"2186":1,"2188":1,"2192":1}}],["stability",{"3":{"592":1,"1134":1,"1358":2,"1368":1,"1394":3,"1395":1,"1416":1,"1435":2,"1491":1,"1564":1}}],["starred",{"3":{"1395":1}}],["starborder",{"3":{"1323":1}}],["starhalf",{"3":{"1323":1}}],["stars",{"3":{"1323":4,"1384":1,"1390":1,"1394":2,"1395":1,"1747":1,"1764":1,"1766":1}}],["starving",{"3":{"1259":1}}],["starve",{"3":{"1124":1,"1128":1,"1254":1,"1259":1,"1275":1,"1323":1,"1446":1}}],["starved",{"3":{"556":1}}],["star",{"3":{"391":1,"1323":11,"1390":2,"1394":12,"1395":16,"1416":1,"1747":1,"1749":1,"1757":1,"1762":1,"1763":3,"1764":1,"1766":1,"1767":1,"1772":1,"1786":1,"1792":1,"1806":1,"1811":1,"1816":1,"1826":1,"1833":1,"1845":1,"1852":1,"1861":1,"1867":1,"1876":1,"1885":1,"1891":1,"1898":1,"1904":1,"1911":1,"1921":2,"1923":1,"1929":1,"1940":1,"1949":1,"1958":1,"1964":1,"1971":1,"1978":1,"1984":1,"1990":1,"1996":1,"2001":1,"2013":1,"2027":1,"2037":1,"2048":1,"2059":1,"2068":1,"2079":1,"2085":1,"2097":1,"2107":1,"2114":1,"2122":1,"2127":1,"2132":1,"2138":1,"2145":1,"2152":1,"2159":1,"2168":1,"2179":1,"2192":1,"2202":1,"2210":1}}],["startipaddress",{"3":{"1466":1}}],["startindex",{"3":{"1323":1}}],["starting",{"0":{"2015":1},"3":{"59":1,"256":1,"529":1,"674":6,"749":1,"1067":1,"1124":1,"1229":1,"1247":2,"1263":1,"1270":1,"1285":1,"1292":1,"1299":1,"1300":4,"1310":1,"1319":1,"1322":1,"1323":7,"1325":1,"1326":1,"1338":4,"1341":1,"1349":3,"1358":3,"1379":1,"1389":1,"1394":7,"1395":2,"1401":1,"1414":2,"1416":1,"1435":5,"1437":1,"1440":3,"1448":1,"1487":1,"1491":1,"1536":1,"1645":1,"1657":1,"1726":1,"1780":1,"1800":1,"1820":1,"1824":1,"1999":1,"2027":1,"2086":1,"2097":1,"2155":1}}],["startlocal",{"3":{"1349":1}}],["startutc",{"3":{"1349":2,"1394":1,"1395":1}}],["startuptext",{"3":{"1415":3}}],["startupresources",{"3":{"1415":2}}],["startupbudget",{"3":{"1069":1}}],["startupdelay",{"3":{"905":1,"1259":2,"1275":1,"1285":6,"1316":1,"1322":3}}],["startup",{"0":{"1327":1,"1447":1},"1":{"232":1,"233":1,"234":1,"235":1,"236":1,"237":1,"238":1,"239":1,"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"294":1,"295":1,"296":1,"787":1,"788":1,"789":1,"790":1,"791":1,"792":1,"793":1,"794":1,"795":1},"3":{"0":16,"15":1,"24":1,"31":3,"33":1,"34":1,"55":1,"61":1,"72":2,"91":1,"92":1,"93":1,"94":2,"99":2,"103":2,"122":2,"146":1,"147":1,"148":1,"150":1,"170":4,"175":1,"178":3,"179":2,"181":1,"193":2,"199":1,"200":1,"201":1,"212":1,"219":1,"220":2,"232":1,"235":1,"237":1,"243":3,"245":1,"255":1,"256":4,"258":1,"259":1,"265":4,"288":1,"290":2,"291":3,"292":1,"293":1,"295":3,"296":2,"310":1,"350":1,"387":1,"401":1,"406":1,"418":2,"423":1,"424":1,"448":4,"453":1,"459":4,"464":3,"501":1,"521":1,"536":1,"563":1,"564":1,"567":1,"568":1,"583":6,"585":1,"587":1,"599":3,"601":2,"603":2,"633":1,"640":2,"665":1,"666":1,"667":2,"674":5,"675":1,"678":1,"684":1,"698":2,"700":1,"702":1,"709":1,"711":1,"727":1,"732":1,"747":1,"749":7,"751":3,"756":3,"757":2,"762":1,"774":2,"776":3,"787":1,"790":2,"792":3,"794":1,"795":1,"821":1,"827":3,"830":1,"838":1,"846":1,"875":1,"905":1,"907":1,"913":1,"918":1,"929":1,"931":3,"932":2,"933":3,"934":1,"980":8,"1006":2,"1011":1,"1012":1,"1018":1,"1034":1,"1050":1,"1066":4,"1067":2,"1068":4,"1069":1,"1071":1,"1074":1,"1089":2,"1091":2,"1093":1,"1104":1,"1107":1,"1108":7,"1109":1,"1112":1,"1124":3,"1125":1,"1150":3,"1174":1,"1176":1,"1177":1,"1180":1,"1186":1,"1189":1,"1192":1,"1202":1,"1203":20,"1207":86,"1208":14,"1212":3,"1213":1,"1222":1,"1229":2,"1237":1,"1247":5,"1248":1,"1254":1,"1257":2,"1259":10,"1270":1,"1272":1,"1275":1,"1279":1,"1282":1,"1285":40,"1287":3,"1296":1,"1299":17,"1300":1,"1308":8,"1310":115,"1311":2,"1314":5,"1315":2,"1316":2,"1319":3,"1322":38,"1323":30,"1324":1,"1327":5,"1331":2,"1332":1,"1333":1,"1334":4,"1335":1,"1337":1,"1338":43,"1358":2,"1363":1,"1368":6,"1377":1,"1378":1,"1379":7,"1394":4,"1395":10,"1412":1,"1414":15,"1415":6,"1416":16,"1417":1,"1418":1,"1419":1,"1426":11,"1429":2,"1435":18,"1437":20,"1438":1,"1440":10,"1441":3,"1442":3,"1443":1,"1445":1,"1449":1,"1454":6,"1455":3,"1459":1,"1460":1,"1466":17,"1468":2,"1475":1,"1476":1,"1490":1,"1495":4,"1499":1,"1525":3,"1549":1,"1553":1,"1565":1,"1576":6,"1585":2,"1613":1,"1618":1,"1640":1,"1651":1,"1652":1,"1662":1,"1663":3,"1664":3,"1666":1,"1669":4,"1683":2,"1688":1,"1699":1,"1705":1,"1706":2,"1717":1,"1718":3,"1720":1,"1721":1,"1723":2,"1726":1,"1727":1,"1747":1,"1750":1,"1763":1,"1764":1,"1766":1,"1771":1,"1776":1,"1789":1,"1791":1,"1823":1,"1824":1,"1825":1,"1826":1,"1841":1,"1848":1,"1855":1,"1856":1,"1861":1,"1880":3,"1881":1,"1884":5,"1885":1,"1888":1,"1897":1,"1903":1,"1907":1,"1909":1,"1921":1,"1922":1,"1955":2,"1958":1,"1963":1,"1969":1,"1970":1,"1974":2,"1981":2,"1983":1,"1993":1,"1999":2,"2000":1,"2003":1,"2005":1,"2006":1,"2008":1,"2011":1,"2013":1,"2021":1,"2026":2,"2029":2,"2055":3,"2065":1,"2082":1,"2126":1,"2130":1,"2131":2,"2149":1,"2166":1,"2168":1,"2187":1,"2231":5}}],["startdatefunc",{"3":{"1358":2}}],["startdateselector",{"3":{"1358":2}}],["startdate",{"2":{"1629":1},"3":{"1347":1,"1349":8,"1352":1,"1358":9,"1368":3,"1374":1,"1394":9,"1395":2,"1629":4,"1630":3,"1634":2,"1639":2}}],["starttimefunc",{"3":{"1358":3}}],["starttimeselector",{"3":{"1358":3}}],["starttimestamp",{"3":{"1270":1}}],["starttime",{"3":{"1341":1,"1349":4,"1358":4,"1361":1,"1368":4,"1394":6}}],["startaddingitem",{"3":{"1394":2}}],["startactivity",{"3":{"1338":1,"2156":1}}],["startasync",{"2":{"135":1,"1612":1,"1618":1},"3":{"135":1,"200":1,"1069":1,"1259":3,"1285":1,"1322":1,"1323":5,"1435":4,"1610":1,"1612":1,"1618":1}}],["startcoreasync",{"3":{"1323":2}}],["starter",{"3":{"2238":1}}],["startenrollmentasync",{"3":{"1292":1,"1299":2}}],["startedhost",{"3":{"1435":3,"1487":1}}],["starteditingitem",{"3":{"1394":2}}],["starteditingprofile",{"3":{"1394":1}}],["startediting",{"3":{"1394":22}}],["startedat",{"3":{"1322":2,"1323":1}}],["started",{"0":{"1660":1,"2209":1},"1":{"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1698":1,"1699":1,"1700":1,"1701":1,"1702":1,"1703":1,"1704":1},"3":{"0":2,"24":1,"99":1,"170":1,"234":1,"348":1,"353":1,"397":4,"451":1,"490":1,"491":1,"492":1,"493":1,"495":1,"507":1,"513":1,"517":1,"527":1,"528":1,"572":1,"632":3,"640":1,"673":1,"706":2,"711":1,"799":1,"868":1,"869":1,"889":1,"938":1,"1005":1,"1050":1,"1067":2,"1190":1,"1194":1,"1251":1,"1259":1,"1270":2,"1285":1,"1299":3,"1310":2,"1322":2,"1323":12,"1327":1,"1332":1,"1338":7,"1358":1,"1394":9,"1395":3,"1408":1,"1416":4,"1426":1,"1428":1,"1429":2,"1435":8,"1437":1,"1440":2,"1443":1,"1466":3,"1487":1,"1488":1,"1525":1,"1534":1,"1576":7,"1599":1,"1601":1,"1605":1,"1645":1,"1657":1,"1660":1,"1668":1,"1672":1,"1673":1,"1680":1,"1688":2,"1690":1,"1691":1,"1716":1,"1717":1,"1724":1,"1725":1,"1729":1,"1739":1,"1778":1,"1947":1,"1949":2,"1974":1,"1975":1,"2011":1,"2040":1,"2054":1,"2055":2,"2097":2,"2156":1,"2214":1,"2225":1,"2227":1,"2231":1,"2233":2,"2235":1}}],["startobject",{"3":{"1229":2}}],["startoutboxactivity",{"3":{"397":1,"1259":1}}],["startscanningasync",{"3":{"1395":1}}],["startsattime",{"3":{"1394":1}}],["startsatdate",{"3":{"1394":1}}],["startsatlocal",{"3":{"1349":1,"1395":1,"1415":2}}],["startsat",{"2":{"1341":1},"3":{"1341":1,"1349":5,"1351":2,"1358":19,"1361":1,"1368":5,"1379":2,"1394":11,"1395":4,"1525":1,"1533":1,"1629":2,"1630":9,"1634":2,"1638":1,"1639":3,"1813":1}}],["startsatutc",{"3":{"1299":2,"1349":1,"1358":1,"1395":3}}],["startsync",{"3":{"1323":2}}],["startswithscheme",{"3":{"1416":2}}],["startswithsegments",{"3":{"1299":1,"1310":1,"1338":3}}],["startswith",{"3":{"1247":1,"1300":1,"1338":1,"1466":1,"1639":1}}],["starts",{"3":{"0":5,"104":1,"148":1,"195":1,"234":1,"235":2,"238":1,"241":1,"350":2,"395":1,"400":1,"402":1,"409":1,"413":1,"452":1,"518":1,"528":1,"545":1,"551":1,"556":1,"632":1,"640":1,"641":1,"675":1,"705":1,"733":1,"759":1,"762":1,"766":1,"776":1,"782":1,"792":1,"810":1,"827":1,"861":1,"875":1,"907":1,"914":1,"932":1,"965":1,"988":1,"1017":1,"1018":2,"1019":2,"1069":2,"1074":1,"1100":1,"1168":1,"1212":1,"1241":2,"1247":1,"1249":1,"1259":1,"1270":3,"1273":1,"1284":1,"1285":3,"1289":2,"1292":1,"1297":1,"1299":8,"1300":2,"1309":1,"1310":2,"1317":1,"1322":4,"1323":6,"1326":1,"1327":2,"1333":1,"1338":14,"1349":1,"1358":9,"1365":1,"1368":2,"1375":1,"1379":2,"1382":1,"1389":1,"1394":17,"1395":11,"1401":2,"1413":1,"1414":3,"1415":3,"1416":11,"1421":1,"1426":3,"1429":1,"1435":22,"1437":2,"1440":1,"1442":1,"1443":1,"1444":1,"1448":1,"1449":1,"1452":1,"1454":1,"1474":1,"1475":1,"1481":1,"1487":1,"1488":1,"1489":4,"1490":3,"1629":1,"1631":1,"1639":1,"1647":1,"1650":2,"1720":1,"1820":1,"1839":2,"1865":1,"1926":1,"1928":1,"1944":1,"1947":1,"1950":1,"1967":1,"1976":1,"2003":1,"2039":1,"2055":1,"2066":1,"2068":1,"2155":1,"2226":1}}],["start",{"0":{"1333":1,"1681":1,"1692":1,"1722":1,"2204":1,"2244":1},"1":{"232":1,"233":1,"234":1,"235":1,"236":1,"237":1,"238":1,"239":1},"2":{"1605":1,"1739":1},"3":{"0":3,"1":1,"24":1,"33":1,"99":1,"101":1,"135":1,"170":1,"195":1,"232":1,"236":3,"239":1,"256":1,"395":1,"427":1,"452":1,"486":1,"517":1,"539":1,"673":1,"675":2,"676":1,"698":2,"707":1,"709":2,"768":1,"795":1,"808":1,"818":1,"819":1,"820":1,"837":1,"846":1,"867":1,"869":1,"872":1,"875":1,"905":1,"918":1,"932":1,"1025":1,"1026":1,"1070":1,"1091":2,"1093":1,"1096":1,"1125":1,"1133":1,"1144":1,"1184":1,"1194":1,"1210":1,"1212":2,"1234":2,"1237":9,"1238":1,"1247":2,"1269":1,"1270":6,"1285":2,"1299":8,"1310":10,"1316":2,"1319":1,"1322":16,"1323":29,"1326":1,"1327":4,"1332":1,"1338":15,"1343":1,"1347":1,"1349":14,"1351":1,"1355":2,"1358":33,"1363":2,"1368":4,"1378":1,"1379":5,"1391":1,"1394":25,"1395":20,"1401":4,"1414":4,"1415":13,"1416":13,"1435":13,"1437":3,"1440":1,"1442":5,"1443":2,"1448":3,"1453":1,"1455":5,"1466":4,"1474":1,"1476":1,"1487":1,"1488":1,"1489":3,"1495":1,"1576":2,"1605":2,"1629":3,"1630":2,"1634":2,"1649":1,"1650":1,"1653":2,"1654":1,"1668":1,"1670":1,"1674":1,"1706":1,"1717":1,"1723":2,"1739":2,"1750":1,"1763":1,"1764":1,"1766":2,"1767":1,"1771":1,"1813":1,"1817":1,"1842":1,"1859":1,"1912":1,"1946":1,"1957":1,"1959":1,"1961":1,"2002":1,"2006":1,"2007":1,"2009":1,"2011":2,"2012":1,"2014":1,"2054":1,"2064":1,"2089":1,"2096":1,"2103":1,"2120":1,"2156":1,"2177":1,"2196":1,"2203":2,"2210":1,"2232":1,"2235":1}}],["staging",{"3":{"201":1,"543":1,"545":1,"600":1,"631":2,"633":4,"635":1,"636":2,"1034":1,"1258":1,"1259":7,"1285":3,"1310":1,"1323":1,"1440":1,"1457":1,"1466":1,"1476":1,"1684":1,"1685":1,"2106":1}}],["stagefileasync",{"3":{"1323":1}}],["stage",{"2":{"636":1},"3":{"0":5,"157":1,"160":1,"161":1,"490":1,"491":1,"492":1,"493":1,"495":1,"543":2,"545":1,"552":1,"599":1,"600":1,"631":1,"633":6,"635":1,"636":2,"757":1,"790":1,"867":2,"868":2,"869":8,"871":3,"873":2,"876":1,"877":1,"1034":2,"1037":1,"1212":3,"1237":1,"1247":3,"1259":3,"1270":5,"1271":4,"1285":1,"1299":4,"1310":4,"1322":1,"1323":1,"1338":1,"1358":7,"1394":2,"1401":2,"1414":6,"1426":1,"1435":6,"1437":1,"1444":12,"1449":1,"1454":3,"1466":2,"1476":5,"1481":1,"1495":1,"1590":1,"1763":1,"1907":2,"2110":1,"2114":1}}],["stages",{"3":{"0":1,"24":1,"201":1,"599":1,"1074":1,"1258":1,"1259":7,"1270":1,"1285":3,"1299":4,"1310":2,"1322":1,"1323":2,"1394":1,"1395":1,"1401":1,"1435":4,"1444":1,"1454":1,"1466":1,"1476":3,"1576":1,"1907":1,"1909":1}}],["staged",{"0":{"1476":1},"3":{"0":4,"21":1,"24":1,"193":1,"195":1,"201":3,"202":1,"384":1,"543":2,"598":1,"599":3,"601":1,"631":2,"633":3,"782":1,"905":1,"1212":2,"1258":1,"1259":11,"1285":3,"1299":1,"1322":2,"1394":1,"1416":1,"1435":12,"1440":1,"1465":1,"1466":3,"1468":1,"1470":1,"1476":2,"1482":1,"1489":1,"1499":2,"1525":1,"1576":1,"1645":1,"1690":1,"1700":1,"1704":1,"1724":1,"1730":2,"1780":1,"1909":1,"2088":1,"2231":1}}],["stanza",{"3":{"1463":1}}],["stance",{"3":{"39":1,"45":1,"1176":1,"1285":1,"1299":1,"1323":1,"1379":1,"1395":2,"1415":1,"1435":5,"1595":1,"1597":1}}],["standout",{"3":{"2052":1}}],["standalone",{"3":{"350":1,"396":1,"609":1,"631":1,"1271":1,"1285":1,"1308":2,"1322":1,"1323":1,"1340":1,"1349":3,"1358":3,"1379":1,"1394":4,"1395":1,"1401":2,"1414":3,"1431":1,"1435":3,"1440":2,"1442":1,"1466":2,"1488":1,"1610":1,"1629":2,"1631":1,"1634":1,"1637":5,"1638":2,"1647":1}}],["standards",{"3":{"1310":1,"2212":1,"2217":1}}],["standardized",{"3":{"608":1,"826":1,"1218":1,"1229":2,"1259":1,"1271":1,"1310":1,"1525":1,"1545":1}}],["standardizes",{"3":{"340":1,"536":1,"901":1,"1435":1,"1664":1,"1927":1,"2130":1}}],["standardize",{"3":{"300":1,"397":1,"448":1,"1237":1}}],["standard",{"0":{"2029":1},"3":{"0":6,"53":1,"62":1,"66":1,"67":1,"68":1,"70":1,"72":2,"74":1,"100":2,"109":1,"200":1,"265":2,"341":1,"350":1,"407":1,"481":1,"483":1,"497":1,"640":3,"665":1,"743":1,"764":1,"766":4,"768":1,"769":2,"799":1,"819":1,"822":1,"833":1,"846":1,"880":1,"882":1,"883":1,"938":1,"939":1,"1043":1,"1125":1,"1126":1,"1211":1,"1212":2,"1229":4,"1270":10,"1285":5,"1289":1,"1295":1,"1299":13,"1308":3,"1310":11,"1311":3,"1315":1,"1322":3,"1323":5,"1328":1,"1338":17,"1349":4,"1357":1,"1358":8,"1364":1,"1367":2,"1368":1,"1378":1,"1379":2,"1394":12,"1395":8,"1401":1,"1403":1,"1404":1,"1409":1,"1414":5,"1416":2,"1420":1,"1424":1,"1426":2,"1430":1,"1434":1,"1435":3,"1442":2,"1445":1,"1454":2,"1455":1,"1456":1,"1458":1,"1466":8,"1473":1,"1475":3,"1487":1,"1488":1,"1502":1,"1525":3,"1533":1,"1536":1,"1590":3,"1599":2,"1630":2,"1631":5,"1638":2,"1639":3,"1640":1,"1646":1,"1652":1,"1655":1,"1662":1,"1667":1,"1669":1,"1692":1,"1706":1,"1719":1,"1780":1,"1894":1,"1898":1,"1908":1,"1925":1,"1929":1,"1974":1,"1979":1,"1981":1,"2010":1,"2012":1,"2028":1,"2030":1,"2037":1,"2056":2,"2155":1,"2176":1,"2184":1,"2202":1,"2226":1,"2231":1}}],["stand",{"3":{"73":1,"95":1,"111":1,"126":1,"258":1,"476":1,"490":1,"491":1,"492":1,"493":1,"495":1,"601":1,"1016":1,"1074":1,"1077":1,"1128":1,"1154":1,"1211":1,"1270":1,"1283":1,"1285":2,"1311":1,"1338":2,"1349":2,"1358":2,"1368":1,"1379":2,"1395":1,"1401":1,"1414":1,"1416":3,"1427":1,"1435":10,"1442":1,"1466":1,"1502":1,"1576":1,"1601":1,"1662":2,"1805":1,"1928":1,"1941":1,"1994":1,"2007":1}}],["standing",{"3":{"0":2,"140":1,"177":1,"474":1,"490":1,"572":1,"591":1,"745":1,"768":1,"801":1,"811":1,"812":1,"840":1,"867":1,"912":1,"1054":1,"1090":1,"1094":1,"1247":1,"1299":1,"1300":2,"1322":2,"1338":1,"1358":3,"1391":1,"1394":1,"1395":5,"1414":1,"1426":2,"1435":5,"1459":2,"1460":1,"1471":1,"1473":1,"1489":1,"1491":1,"1495":3,"1525":2,"1691":1,"1725":1,"1808":1,"1895":1,"1928":1,"1949":1,"2112":1,"2130":1}}],["standstill",{"3":{"1124":1,"1310":2,"1338":2}}],["stands",{"3":{"0":4,"20":1,"54":1,"178":1,"257":1,"259":1,"508":1,"551":1,"620":1,"640":1,"649":1,"790":1,"797":1,"803":1,"804":1,"808":1,"813":1,"814":1,"827":1,"840":1,"848":1,"877":1,"1055":1,"1075":1,"1116":1,"1142":1,"1177":1,"1285":2,"1299":3,"1308":1,"1322":2,"1323":1,"1349":1,"1358":2,"1379":1,"1394":1,"1395":2,"1435":7,"1525":1,"1533":1,"1576":1,"1590":1,"1670":1,"1785":1,"1795":1,"1932":1,"2000":1,"2047":1,"2049":1,"2093":1,"2167":1,"2203":1}}],["stalling",{"3":{"1308":1,"1311":1,"1322":1,"1412":1}}],["stall",{"3":{"1300":1,"1395":3,"1401":1,"1414":1,"1450":1,"1726":1,"1943":1,"2004":1,"2012":1}}],["stalled",{"3":{"759":1,"1322":1,"1395":1,"1487":1,"2192":1}}],["stalls",{"3":{"0":1,"709":1,"914":1,"1256":1,"1426":1,"1439":1,"1455":1,"1466":1,"1489":1,"1576":1,"1581":1,"1652":1,"1688":1,"1697":1,"2173":1}}],["stales",{"3":{"1921":2}}],["stalerowversionconflicttests",{"3":{"1590":1,"1599":1}}],["staler",{"3":{"1394":1}}],["staled",{"3":{"1300":1,"1911":1,"1912":1,"1917":1,"1923":1}}],["staleundecoratedallowlisttests",{"3":{"1198":1,"1199":2,"1207":1,"1435":5}}],["staleallowlisttests",{"3":{"1198":1,"1199":2,"1207":1,"1435":7,"1495":1}}],["staleness",{"3":{"189":1,"243":1,"245":2,"305":1,"390":2,"391":1,"441":1,"444":1,"674":1,"735":1,"736":1,"924":1,"1060":1,"1102":1,"1270":4,"1282":1,"1299":1,"1300":5,"1310":2,"1323":14,"1349":1,"1358":1,"1394":1,"1401":2,"1435":2,"1495":1,"1533":1,"1555":1,"1576":2,"1585":1,"1590":2,"1596":1,"1650":1,"1667":1,"1922":1,"2126":1,"2127":1,"2202":1}}],["stale",{"3":{"0":5,"70":1,"104":1,"128":1,"130":1,"138":2,"140":1,"141":1,"171":1,"231":1,"235":1,"243":1,"255":2,"279":1,"324":1,"340":1,"341":2,"343":1,"344":1,"345":1,"373":1,"388":1,"426":1,"512":2,"572":2,"608":1,"610":1,"626":1,"628":2,"703":1,"716":1,"720":1,"733":2,"735":1,"751":1,"759":1,"790":3,"792":1,"793":1,"810":1,"841":1,"848":1,"873":1,"876":1,"877":1,"892":1,"905":1,"906":1,"927":1,"971":1,"972":2,"986":1,"991":1,"1028":1,"1042":1,"1045":1,"1065":1,"1101":1,"1175":1,"1184":1,"1212":2,"1229":1,"1237":3,"1249":1,"1259":3,"1265":1,"1270":7,"1271":1,"1285":6,"1299":6,"1300":5,"1305":1,"1308":2,"1310":5,"1322":1,"1323":23,"1331":1,"1338":4,"1346":1,"1349":3,"1351":1,"1358":17,"1372":1,"1374":1,"1378":1,"1379":4,"1392":1,"1394":19,"1395":24,"1398":1,"1401":3,"1405":1,"1410":1,"1414":5,"1415":1,"1416":8,"1435":21,"1437":3,"1452":2,"1459":1,"1474":3,"1487":2,"1488":1,"1489":1,"1495":18,"1524":1,"1525":5,"1533":2,"1545":1,"1548":1,"1555":2,"1560":1,"1570":2,"1576":1,"1590":5,"1595":1,"1596":1,"1599":1,"1634":1,"1638":1,"1647":1,"1650":2,"1667":1,"1684":1,"1688":1,"1700":1,"1701":1,"1707":3,"1719":1,"1729":1,"1747":3,"1748":1,"1764":1,"1821":1,"1868":1,"1869":1,"1870":1,"1875":2,"1876":2,"1897":2,"1911":1,"1912":1,"1913":1,"1917":2,"1919":1,"1922":2,"1923":1,"1947":1,"2012":1,"2033":1,"2036":1,"2042":2,"2044":2,"2045":1,"2054":1,"2096":1,"2126":1,"2162":1,"2166":3,"2187":1,"2192":1,"2198":1,"2201":1,"2231":1}}],["stamper",{"3":{"1322":1}}],["stampeding",{"3":{"1395":1,"1401":1}}],["stampedentity",{"3":{"1199":3,"1207":1}}],["stampedetestquery",{"3":{"1199":2,"1207":1}}],["stampede",{"3":{"0":1,"248":1,"508":1,"733":3,"737":1,"1192":1,"1267":3,"1270":10,"1300":9,"1322":1,"1495":1,"1667":1,"1707":1,"1916":2,"1919":1}}],["stamped",{"3":{"0":3,"26":1,"39":1,"214":1,"221":1,"345":1,"397":1,"434":1,"435":1,"500":1,"501":1,"507":1,"714":1,"715":1,"720":1,"905":1,"907":1,"1044":1,"1083":1,"1084":1,"1140":1,"1168":1,"1212":3,"1231":1,"1232":1,"1237":3,"1247":4,"1253":2,"1259":8,"1265":1,"1269":1,"1270":3,"1275":1,"1280":1,"1285":14,"1289":1,"1299":4,"1308":3,"1311":2,"1317":1,"1322":7,"1323":3,"1338":6,"1356":1,"1357":1,"1358":19,"1379":2,"1389":1,"1394":6,"1395":7,"1401":2,"1414":8,"1417":1,"1424":1,"1426":5,"1435":4,"1437":5,"1495":1,"1544":1,"1576":1,"1590":1,"1652":1,"1656":1,"1663":1,"1664":1,"1671":1,"1684":1,"1697":1,"1703":1,"1718":1,"1719":1,"1748":1,"1766":1,"1809":1,"1838":1,"1932":1,"1935":1,"1981":2,"1983":1,"2089":1,"2185":1,"2191":1,"2196":1,"2202":1,"2229":1}}],["stampauditfields",{"3":{"1285":2}}],["stampasync",{"3":{"1042":1,"1435":5,"2187":1}}],["stamptestdbcontext",{"3":{"1199":2,"1207":1}}],["stamporverifyinsert",{"3":{"698":1,"1285":2}}],["stampingtransformer",{"3":{"1199":2,"1207":1}}],["stamping",{"3":{"0":1,"21":1,"26":1,"39":1,"179":1,"202":1,"350":1,"698":1,"716":2,"826":1,"840":1,"847":1,"923":1,"926":1,"1083":1,"1163":1,"1232":1,"1237":4,"1244":1,"1247":3,"1259":3,"1265":1,"1270":1,"1272":1,"1274":1,"1280":1,"1285":10,"1299":3,"1303":1,"1308":1,"1310":3,"1322":2,"1323":3,"1338":4,"1341":1,"1358":7,"1368":2,"1395":2,"1397":1,"1401":3,"1411":1,"1414":2,"1437":1,"1525":1,"1576":2,"1590":1,"1662":1,"1663":2,"1716":1,"1838":1,"1843":1,"1870":1,"1976":1,"1999":1,"2051":1,"2073":1,"2148":1,"2220":1}}],["stamp",{"0":{"2187":1},"3":{"0":4,"213":1,"214":1,"341":1,"343":1,"365":1,"397":1,"716":1,"926":2,"988":1,"996":1,"1034":1,"1035":1,"1083":1,"1084":1,"1091":1,"1160":1,"1163":1,"1174":1,"1175":1,"1231":1,"1237":1,"1247":1,"1249":2,"1253":1,"1259":7,"1265":1,"1270":7,"1285":21,"1299":4,"1308":1,"1310":2,"1317":2,"1322":6,"1323":7,"1335":1,"1338":3,"1341":1,"1349":1,"1351":1,"1355":1,"1357":4,"1358":28,"1379":1,"1381":1,"1394":3,"1395":2,"1401":2,"1414":2,"1426":4,"1430":2,"1435":9,"1437":2,"1523":1,"1525":1,"1530":1,"1533":3,"1576":1,"1584":1,"1678":1,"1764":1,"1837":1,"1838":3,"1947":1,"2009":1,"2063":1,"2085":1,"2141":1,"2146":1,"2152":1,"2162":1,"2175":1,"2179":1,"2190":1,"2192":2,"2202":1}}],["stampsoftdeletetransition",{"3":{"1285":2}}],["stamps",{"3":{"0":8,"27":2,"37":1,"39":1,"41":2,"109":1,"195":1,"202":1,"214":1,"246":1,"339":1,"341":1,"343":1,"359":1,"365":1,"402":1,"499":1,"574":1,"698":2,"702":2,"707":1,"714":1,"715":3,"716":2,"740":1,"827":2,"833":1,"846":1,"849":1,"861":1,"905":1,"909":1,"921":1,"995":1,"1018":1,"1033":1,"1034":1,"1042":1,"1059":2,"1081":1,"1083":3,"1118":1,"1184":1,"1212":5,"1231":1,"1232":2,"1237":7,"1247":1,"1249":1,"1252":1,"1253":1,"1255":2,"1259":5,"1270":4,"1271":2,"1274":3,"1275":1,"1276":1,"1278":2,"1280":1,"1285":30,"1289":1,"1299":11,"1309":1,"1310":3,"1316":1,"1322":4,"1323":10,"1325":1,"1332":1,"1335":1,"1337":1,"1338":6,"1349":3,"1358":14,"1362":1,"1365":1,"1368":1,"1372":1,"1379":1,"1388":1,"1394":2,"1395":5,"1401":3,"1409":1,"1414":3,"1416":1,"1425":1,"1426":2,"1432":1,"1435":8,"1437":1,"1440":2,"1442":2,"1446":1,"1495":1,"1584":1,"1663":1,"1669":1,"1838":1,"1843":1,"1850":1,"1875":1,"1888":1,"1947":1,"2082":1,"2090":1,"2142":1,"2147":1,"2148":2,"2152":1,"2165":1,"2185":1,"2187":1,"2196":1,"2199":1,"2207":1,"2231":1}}],["stacked",{"3":{"1303":1,"1401":1,"1523":1,"1712":1}}],["stackexchangeredis",{"2":{"1338":1},"3":{"1338":1}}],["stackexchangerediscache",{"2":{"245":1,"249":1,"253":1,"286":1,"732":1},"3":{"245":1,"249":1,"253":1,"286":1,"732":1,"1300":2}}],["stackexchange",{"2":{"234":1,"245":1,"1300":1,"1914":1,"2042":1},"3":{"234":2,"245":1,"259":2,"1213":1,"1270":3,"1285":1,"1300":6,"1310":4,"1322":3,"1331":1,"1338":6,"1441":2,"1442":1,"1488":1,"1576":1,"1914":1,"2042":1}}],["stackalloc",{"3":{"1285":1}}],["stacks",{"3":{"125":1,"881":1,"1263":1,"1270":1,"1310":1,"1367":1,"1368":1,"1416":1,"1584":1,"2219":1}}],["stacking",{"3":{"0":1,"1028":1,"1323":1,"1338":1,"1395":1,"1414":1,"1415":1,"1442":1,"1458":1}}],["stack",{"0":{"1213":1,"1510":1,"2004":1},"1":{"1210":1,"1211":1,"1212":1,"1213":1,"1214":1,"1215":1,"1216":1},"3":{"0":7,"6":1,"73":1,"115":1,"125":5,"150":1,"340":1,"387":1,"401":1,"407":1,"483":1,"618":1,"619":1,"625":1,"640":1,"641":2,"644":1,"827":1,"837":1,"860":1,"883":1,"913":1,"953":1,"1066":1,"1067":2,"1069":1,"1070":1,"1154":1,"1192":1,"1210":1,"1212":1,"1218":1,"1226":1,"1229":3,"1230":1,"1236":1,"1237":1,"1247":1,"1252":1,"1259":5,"1261":1,"1263":2,"1270":5,"1285":6,"1299":6,"1308":1,"1310":3,"1312":1,"1315":1,"1317":2,"1322":5,"1323":13,"1327":1,"1338":12,"1349":2,"1358":3,"1394":1,"1395":5,"1401":3,"1414":2,"1415":4,"1416":11,"1429":3,"1435":9,"1437":2,"1438":1,"1439":2,"1440":3,"1442":1,"1443":1,"1445":1,"1446":1,"1448":3,"1449":1,"1455":7,"1463":1,"1466":2,"1485":1,"1486":2,"1487":1,"1488":2,"1489":4,"1491":4,"1495":2,"1535":1,"1581":1,"1590":1,"1598":1,"1639":1,"1646":1,"1652":1,"1662":1,"1668":2,"1672":1,"1762":1,"1780":3,"1799":1,"1803":2,"1805":1,"1806":2,"1816":1,"1826":1,"1833":1,"1845":1,"1852":1,"1861":1,"1867":1,"1873":1,"1876":1,"1898":1,"1904":1,"1905":1,"1911":1,"1923":1,"1924":1,"1929":1,"1940":1,"1949":1,"1951":1,"1957":1,"1958":1,"1964":1,"1984":1,"1990":1,"1992":1,"2001":1,"2002":1,"2011":1,"2012":1,"2013":1,"2027":1,"2037":1,"2048":1,"2052":1,"2055":2,"2056":1,"2059":2,"2074":2,"2075":1,"2107":1,"2127":1,"2132":1,"2149":1,"2152":1,"2156":1,"2159":1,"2164":1,"2168":1,"2179":1,"2208":1,"2211":2,"2212":1,"2216":1,"2217":1}}],["stat",{"3":{"1395":1,"1454":1,"1525":1}}],["stats",{"3":{"1395":12,"1486":1}}],["statistics",{"3":{"1338":1,"1523":1,"1525":1,"1530":1}}],["stating",{"0":{"1201":1},"3":{"128":1,"219":1,"463":1,"819":1,"828":1,"972":1,"1012":1,"1018":1,"1082":1,"1134":1,"1248":1,"1270":2,"1285":5,"1299":2,"1310":3,"1322":1,"1323":2,"1338":3,"1349":2,"1358":6,"1361":1,"1368":1,"1394":1,"1395":4,"1416":4,"1435":10,"1458":1,"1525":1,"1809":1,"1814":1,"1815":1,"1843":1,"1880":1,"1947":1,"1955":1,"2080":1,"2136":1}}],["staticwebassets",{"3":{"1435":2}}],["statically",{"3":{"1237":1,"1338":1,"1358":1,"1395":1,"1415":1,"1435":7,"2069":1}}],["staticcsppolicyprovider",{"2":{"214":1,"2010":1,"2148":1},"3":{"214":1,"1199":2,"1203":1,"1207":1,"1335":1,"1338":8,"2010":1,"2148":1}}],["statics",{"3":{"145":1,"150":1,"413":1,"1237":1,"1270":1,"1285":1,"1310":1,"1318":1,"1322":2,"1323":3,"1414":1,"1416":1,"1435":3}}],["static",{"2":{"883":1,"982":1,"1051":1,"1162":1,"1168":1,"1196":1,"1246":1,"1318":1,"1345":1},"3":{"0":14,"10":1,"15":1,"27":1,"59":2,"122":1,"135":3,"137":1,"140":1,"178":1,"189":2,"212":1,"213":2,"214":1,"215":1,"216":2,"219":1,"220":1,"258":1,"305":1,"318":1,"343":1,"350":1,"397":1,"413":1,"549":1,"555":1,"556":2,"640":2,"642":1,"657":3,"698":1,"741":2,"744":1,"782":1,"819":2,"826":1,"881":3,"883":2,"884":1,"897":1,"963":2,"964":3,"972":2,"975":1,"979":1,"980":8,"981":1,"982":1,"1026":1,"1050":6,"1051":1,"1052":1,"1109":1,"1117":1,"1124":1,"1126":1,"1162":1,"1163":1,"1167":3,"1168":7,"1171":1,"1175":2,"1176":1,"1177":1,"1178":1,"1196":2,"1212":3,"1214":2,"1219":1,"1221":1,"1228":1,"1229":9,"1234":2,"1235":2,"1237":32,"1239":3,"1241":3,"1243":1,"1246":2,"1247":43,"1259":21,"1266":1,"1268":3,"1270":39,"1271":11,"1274":1,"1275":1,"1285":98,"1296":1,"1299":70,"1300":14,"1306":1,"1308":35,"1309":7,"1310":58,"1311":4,"1315":1,"1318":3,"1319":1,"1322":75,"1323":96,"1325":1,"1329":1,"1335":1,"1337":1,"1338":82,"1339":1,"1340":1,"1342":1,"1343":1,"1345":1,"1349":52,"1351":1,"1352":2,"1353":1,"1358":71,"1365":2,"1367":1,"1368":11,"1379":16,"1382":1,"1392":2,"1393":1,"1394":46,"1395":81,"1401":16,"1404":2,"1414":45,"1415":11,"1416":26,"1421":2,"1422":1,"1425":1,"1426":14,"1432":1,"1433":1,"1435":126,"1437":2,"1440":1,"1442":2,"1446":1,"1452":1,"1454":2,"1459":1,"1464":1,"1467":1,"1468":1,"1487":3,"1488":3,"1495":4,"1524":1,"1525":3,"1555":2,"1571":1,"1576":1,"1590":2,"1610":1,"1630":1,"1650":1,"1652":1,"1666":1,"1670":1,"1697":2,"1701":1,"1804":1,"1809":5,"1810":2,"1811":1,"1830":1,"1855":1,"1867":1,"1955":1,"1958":1,"1974":1,"1979":1,"1988":1,"1993":1,"2022":1,"2035":1,"2074":1,"2075":1,"2080":1,"2081":1,"2082":1,"2089":1,"2118":1,"2125":1,"2146":1,"2147":1,"2148":2,"2149":2,"2150":2,"2151":1,"2152":4,"2174":1,"2218":1}}],["statequery",{"3":{"1416":1}}],["statehaschanged",{"2":{"1555":1},"3":{"1323":5,"1394":7,"1395":4,"1400":1,"1401":1,"1525":1,"1555":2,"1576":1,"1590":2}}],["state=",{"3":{"1310":1,"1416":2}}],["statetable",{"3":{"1285":1}}],["staterules",{"3":{"1199":3,"1203":1,"1207":1,"1271":8}}],["statemaxlength",{"3":{"1237":1,"1271":2}}],["statemanagementconventiontests",{"2":{"1524":1,"1590":1},"3":{"881":4,"884":1,"1199":2,"1207":4,"1323":1,"1435":16,"1488":2,"1524":1,"1525":2,"1534":1,"1590":2,"1599":1}}],["statemanagementconventiontestsbase",{"2":{"1525":1},"3":{"881":1,"884":1,"1199":3,"1207":2,"1435":14,"1525":2}}],["statements",{"3":{"27":1,"63":1,"82":1,"161":1,"171":1,"181":1,"268":1,"285":1,"345":1,"375":1,"409":1,"486":1,"502":1,"514":1,"526":1,"531":1,"534":1,"540":2,"578":1,"586":1,"604":1,"607":1,"660":1,"713":1,"718":1,"744":1,"761":1,"777":1,"814":1,"825":1,"833":2,"841":1,"857":1,"877":1,"900":1,"917":1,"927":1,"999":1,"1029":1,"1045":1,"1055":1,"1074":1,"1103":1,"1271":1,"1285":1,"1299":1,"1310":1,"1349":1,"1351":1,"1358":9,"1395":6,"1466":1,"1537":1}}],["statement",{"0":{"1246":1},"3":{"0":2,"135":1,"136":1,"140":1,"150":1,"200":1,"220":1,"341":2,"395":1,"423":1,"438":1,"443":1,"478":1,"488":1,"489":1,"518":1,"522":1,"523":1,"524":3,"563":1,"566":1,"587":1,"590":1,"607":2,"633":1,"677":1,"707":1,"716":1,"719":4,"827":1,"838":1,"973":1,"974":1,"996":1,"1005":1,"1007":1,"1049":1,"1100":1,"1114":1,"1120":1,"1123":1,"1132":2,"1133":7,"1134":2,"1135":2,"1141":1,"1175":2,"1190":1,"1212":1,"1235":1,"1237":1,"1242":1,"1243":1,"1246":1,"1247":2,"1252":1,"1259":3,"1270":3,"1271":2,"1275":1,"1278":1,"1285":21,"1299":3,"1300":1,"1308":1,"1309":1,"1322":3,"1323":4,"1338":3,"1347":1,"1349":5,"1358":11,"1368":1,"1395":6,"1401":1,"1414":1,"1415":1,"1416":1,"1422":1,"1430":1,"1435":13,"1437":1,"1446":1,"1450":1,"1488":4,"1525":1,"1688":1,"1698":1,"1726":1,"1748":1,"1794":1,"1864":1,"1870":1,"1871":1,"1875":1,"2030":1,"2032":1,"2042":1,"2088":1,"2125":1,"2202":1}}],["statecontainer",{"3":{"881":1,"884":1,"1435":2,"1525":1}}],["stateful",{"3":{"178":1,"458":1,"881":1,"1020":1,"1186":1,"1187":1,"1323":1,"1395":3,"1416":2,"1435":5,"1466":3,"1488":1,"1525":1,"1565":1,"1832":1}}],["stated",{"3":{"0":2,"24":1,"27":1,"67":1,"100":1,"109":1,"111":1,"115":1,"125":1,"126":1,"135":3,"160":1,"193":2,"200":1,"202":1,"223":1,"243":1,"342":1,"463":1,"477":1,"518":1,"522":1,"536":1,"543":1,"545":1,"556":1,"559":1,"579":1,"602":2,"628":1,"631":2,"632":1,"658":1,"703":1,"723":1,"725":1,"751":1,"766":1,"769":1,"792":1,"797":1,"799":2,"809":1,"819":1,"826":1,"827":3,"838":4,"861":1,"868":1,"869":1,"876":2,"883":1,"888":1,"906":1,"914":1,"931":1,"946":2,"963":1,"968":1,"972":1,"979":2,"988":1,"996":1,"1012":2,"1019":1,"1021":1,"1075":1,"1083":1,"1100":2,"1108":1,"1116":1,"1124":1,"1133":1,"1134":1,"1168":1,"1193":1,"1212":1,"1225":1,"1228":1,"1229":1,"1236":1,"1237":3,"1247":3,"1259":1,"1270":8,"1273":1,"1285":18,"1290":1,"1291":1,"1292":1,"1293":1,"1297":1,"1299":10,"1303":1,"1308":7,"1310":11,"1311":3,"1317":1,"1322":10,"1323":11,"1329":2,"1331":1,"1332":1,"1335":1,"1336":1,"1338":8,"1349":5,"1358":28,"1368":1,"1379":3,"1383":1,"1388":1,"1391":1,"1394":7,"1395":26,"1398":1,"1401":9,"1404":1,"1414":7,"1415":1,"1416":3,"1423":1,"1424":1,"1435":24,"1440":2,"1444":1,"1454":1,"1458":1,"1466":3,"1468":1,"1469":1,"1473":1,"1485":1,"1488":1,"1495":1,"1525":1,"1530":1,"1540":1,"1544":2,"1546":1,"1547":1,"1557":1,"1571":1,"1576":1,"1590":2,"1665":1,"1672":1,"1684":1,"1708":1,"1788":1,"1798":1,"1873":3,"1876":1,"1957":1,"2029":1,"2058":1,"2167":1,"2173":1,"2181":1,"2231":1}}],["statelessness",{"3":{"1322":1,"1426":1}}],["stateless",{"3":{"0":5,"310":1,"359":1,"365":1,"458":2,"460":4,"462":1,"498":2,"499":2,"500":1,"501":2,"503":2,"910":1,"946":1,"1059":1,"1176":1,"1184":1,"1187":1,"1212":3,"1259":1,"1285":4,"1292":2,"1299":7,"1308":1,"1310":2,"1322":3,"1338":2,"1349":1,"1358":4,"1394":3,"1395":1,"1401":1,"1416":6,"1440":1,"1538":1,"1548":1,"1576":1,"1590":1,"1979":2,"1980":1,"1981":1,"1983":1,"1984":3,"2065":2,"2141":1,"2197":1,"2231":2}}],["state",{"0":{"100":1,"720":1,"813":1,"1555":1,"1632":1,"1765":1,"1768":1,"1776":1,"2075":1},"1":{"779":1,"780":1,"781":1,"782":1,"783":1,"784":1,"785":1,"786":1},"2":{"426":1,"784":1,"1975":1,"2031":2},"3":{"0":23,"17":1,"27":3,"39":1,"42":1,"74":1,"93":1,"109":1,"120":1,"131":1,"137":1,"141":1,"152":1,"159":1,"177":1,"178":1,"181":1,"186":1,"188":1,"195":1,"201":1,"208":1,"227":1,"228":1,"243":2,"249":1,"252":1,"254":1,"256":1,"257":1,"258":1,"259":1,"265":1,"266":1,"267":1,"272":1,"279":1,"281":1,"283":1,"284":1,"285":1,"286":1,"302":1,"324":1,"341":1,"342":1,"345":1,"348":1,"350":1,"353":1,"363":1,"375":1,"382":2,"384":2,"389":1,"390":2,"397":1,"399":1,"413":1,"423":1,"426":1,"429":1,"434":1,"497":3,"499":2,"501":1,"506":1,"507":4,"508":1,"513":1,"518":1,"524":1,"534":1,"536":1,"537":5,"540":2,"547":1,"549":1,"550":1,"552":1,"554":1,"557":1,"566":1,"572":1,"578":1,"599":1,"609":1,"610":1,"611":2,"613":1,"618":3,"622":1,"651":1,"664":3,"667":1,"670":1,"698":2,"699":1,"700":1,"707":1,"709":1,"736":3,"758":1,"761":1,"768":1,"779":1,"782":9,"783":1,"784":5,"785":1,"793":1,"794":1,"808":2,"809":6,"810":10,"811":4,"812":2,"813":6,"814":1,"815":2,"819":2,"821":1,"833":1,"836":1,"838":1,"841":1,"855":3,"856":1,"868":1,"871":1,"876":2,"879":1,"881":7,"882":3,"883":1,"884":1,"896":1,"905":1,"909":1,"923":1,"926":7,"927":1,"946":1,"954":3,"959":1,"960":1,"962":1,"963":1,"972":1,"976":1,"986":1,"988":2,"989":3,"994":1,"996":2,"1014":1,"1021":2,"1030":1,"1050":2,"1052":1,"1065":1,"1067":2,"1068":1,"1081":1,"1089":2,"1095":1,"1100":1,"1116":1,"1117":1,"1119":1,"1123":1,"1124":2,"1125":2,"1139":1,"1140":5,"1141":1,"1142":1,"1145":1,"1147":1,"1150":5,"1151":1,"1152":4,"1154":2,"1155":2,"1161":3,"1162":2,"1167":1,"1169":3,"1171":1,"1175":1,"1184":1,"1199":64,"1207":1,"1212":7,"1216":1,"1226":1,"1227":1,"1229":5,"1230":1,"1231":2,"1232":1,"1233":1,"1237":14,"1240":1,"1246":1,"1247":9,"1249":1,"1252":1,"1259":22,"1261":1,"1263":2,"1264":1,"1265":4,"1267":1,"1269":1,"1270":25,"1271":10,"1274":4,"1275":2,"1278":2,"1285":55,"1289":1,"1292":3,"1299":35,"1300":10,"1302":1,"1308":20,"1310":24,"1316":1,"1317":1,"1321":1,"1322":40,"1323":201,"1326":1,"1333":2,"1338":18,"1341":1,"1342":2,"1344":2,"1346":1,"1349":57,"1351":1,"1353":1,"1354":1,"1356":1,"1358":69,"1365":1,"1368":4,"1377":1,"1379":16,"1382":4,"1383":2,"1384":1,"1389":2,"1390":2,"1394":184,"1395":121,"1396":3,"1401":80,"1404":2,"1408":3,"1410":1,"1413":2,"1414":39,"1415":6,"1416":87,"1426":3,"1430":2,"1431":2,"1432":1,"1433":1,"1435":113,"1437":12,"1440":2,"1446":2,"1450":1,"1453":2,"1454":4,"1458":1,"1459":1,"1466":1,"1469":1,"1471":1,"1472":1,"1473":1,"1474":1,"1477":1,"1480":1,"1483":1,"1485":1,"1487":2,"1488":1,"1490":3,"1495":4,"1498":1,"1524":3,"1525":8,"1534":2,"1540":1,"1541":1,"1546":1,"1548":2,"1554":1,"1555":10,"1560":3,"1561":3,"1562":1,"1571":2,"1576":8,"1585":2,"1587":1,"1590":9,"1596":1,"1598":1,"1599":3,"1606":2,"1607":3,"1630":3,"1631":2,"1632":3,"1637":1,"1640":1,"1642":5,"1644":1,"1650":3,"1651":1,"1653":1,"1659":2,"1664":1,"1668":1,"1670":2,"1676":1,"1685":1,"1700":1,"1708":1,"1714":2,"1740":2,"1741":1,"1746":5,"1747":6,"1748":7,"1749":3,"1754":1,"1763":3,"1764":5,"1768":9,"1772":3,"1774":1,"1775":2,"1795":1,"1804":1,"1807":1,"1808":2,"1809":1,"1819":1,"1821":2,"1831":1,"1839":3,"1843":1,"1845":1,"1848":1,"1850":1,"1851":1,"1861":1,"1870":1,"1875":1,"1876":1,"1882":1,"1889":1,"1915":1,"1917":3,"1919":1,"1936":1,"1939":1,"1947":2,"1948":3,"1949":2,"1970":2,"1971":1,"1974":2,"1975":1,"1978":1,"1983":1,"1994":1,"1999":2,"2000":1,"2001":3,"2012":1,"2031":2,"2059":1,"2062":1,"2064":1,"2069":2,"2074":1,"2075":3,"2077":1,"2078":1,"2079":2,"2080":1,"2081":1,"2085":1,"2089":1,"2090":3,"2099":1,"2117":2,"2118":1,"2122":1,"2153":1,"2155":1,"2158":1,"2162":1,"2166":4,"2167":6,"2172":1,"2181":1,"2184":1,"2185":1,"2193":1,"2229":1,"2231":5}}],["stateservice",{"3":{"881":1,"884":1,"1435":2,"1525":1,"1590":1}}],["states",{"3":{"0":6,"68":1,"85":1,"91":1,"98":1,"115":1,"130":1,"136":1,"138":1,"141":1,"156":1,"171":1,"195":1,"200":2,"268":1,"301":1,"318":2,"322":1,"341":1,"353":1,"378":1,"392":1,"393":1,"459":1,"465":1,"490":1,"524":1,"529":1,"544":1,"549":1,"555":3,"556":1,"572":1,"582":1,"584":1,"591":1,"609":1,"612":1,"618":4,"627":1,"630":1,"642":1,"643":1,"644":1,"649":1,"650":1,"756":1,"766":1,"782":1,"789":1,"790":1,"794":1,"797":1,"809":1,"827":1,"833":1,"836":1,"838":3,"849":1,"867":1,"869":1,"881":1,"905":1,"914":1,"922":2,"941":1,"946":1,"963":2,"964":1,"967":1,"972":1,"1003":1,"1004":1,"1017":1,"1018":1,"1066":1,"1067":1,"1089":1,"1091":1,"1108":1,"1128":1,"1143":1,"1160":1,"1167":1,"1212":1,"1229":6,"1237":13,"1247":14,"1259":15,"1270":20,"1271":3,"1273":1,"1275":2,"1277":1,"1278":1,"1283":1,"1285":53,"1296":1,"1299":49,"1300":4,"1308":16,"1309":1,"1310":26,"1311":2,"1322":21,"1323":60,"1332":1,"1337":1,"1338":45,"1342":1,"1349":23,"1358":110,"1361":3,"1364":1,"1368":3,"1374":1,"1379":14,"1388":1,"1394":50,"1395":81,"1399":1,"1401":31,"1406":1,"1413":1,"1414":37,"1415":5,"1416":11,"1422":1,"1429":1,"1435":76,"1437":3,"1439":1,"1446":2,"1448":1,"1452":1,"1453":1,"1454":9,"1456":1,"1458":1,"1464":2,"1466":11,"1472":1,"1474":1,"1482":1,"1485":1,"1487":1,"1488":2,"1490":2,"1491":1,"1495":1,"1524":1,"1525":1,"1544":1,"1556":1,"1557":1,"1559":1,"1560":2,"1561":1,"1564":2,"1576":5,"1584":2,"1590":1,"1595":2,"1632":1,"1633":1,"1638":3,"1642":2,"1644":2,"1650":1,"1652":2,"1655":2,"1668":1,"1685":1,"1707":1,"1719":1,"1748":1,"1765":6,"1766":1,"1768":1,"1770":1,"1798":1,"1868":1,"1870":2,"1873":1,"1875":1,"1876":1,"1916":1,"1959":1,"1960":1,"1963":1,"1969":1,"2023":1,"2027":1,"2042":2,"2055":1,"2067":1,"2111":1,"2164":1,"2167":1,"2182":1,"2188":1,"2197":1,"2231":2}}],["status202accepted",{"3":{"1379":1}}],["status201created",{"3":{"1310":1}}],["statusmaxlength",{"3":{"1349":2,"1358":2,"1368":1}}],["statusineligible",{"3":{"1343":2,"1349":1}}],["status500internalservererror",{"3":{"1310":1}}],["status429toomanyrequests",{"3":{"1338":1}}],["status400badrequest",{"3":{"1310":3}}],["status409conflict",{"3":{"1310":1}}],["status499clientclosedrequest",{"3":{"1310":2}}],["statuserrorcodeprefix",{"3":{"1299":1}}],["statuses",{"3":{"318":1,"326":1,"1310":1,"1349":1,"1358":7,"1394":3,"1395":1,"1401":1,"1629":2,"1630":1,"1631":1,"1632":2,"1634":1,"1638":1}}],["statuscriteria",{"2":{"1239":1,"1358":1},"3":{"1239":1,"1247":1,"1358":7}}],["statuscodes",{"3":{"1310":8,"1338":1,"1379":1}}],["statuscodetoerrortype",{"3":{"1299":1}}],["statuscoderesult",{"2":{"159":1,"1908":1,"1910":1},"3":{"157":1,"159":1,"1310":6,"1908":1,"1910":1}}],["statuscode",{"2":{"100":1,"1225":2,"2017":1},"3":{"100":1,"1225":2,"1270":1,"1310":10,"1311":14,"1377":1,"1379":1,"1435":6,"1437":1,"1926":2,"2017":1}}],["statusbucket",{"2":{"1350":1},"3":{"1199":3,"1203":2,"1207":2,"1350":1,"1358":11,"1495":1}}],["status=success",{"3":{"626":1}}],["status",{"0":{"3":1,"9":1,"15":1,"29":1,"37":1,"45":1,"51":1,"57":1,"66":1,"76":1,"84":1,"90":1,"107":1,"115":1,"130":1,"145":1,"155":1,"164":1,"173":1,"184":1,"193":1,"205":1,"212":1,"223":1,"233":1,"241":1,"263":1,"271":1,"279":1,"289":1,"298":1,"308":1,"316":1,"328":1,"339":1,"348":1,"357":1,"368":1,"378":1,"386":1,"395":1,"411":1,"418":1,"432":1,"438":1,"446":1,"457":1,"468":1,"480":1,"497":1,"505":1,"516":1,"526":1,"534":1,"543":1,"554":1,"562":1,"570":1,"581":1,"589":1,"597":1,"607":1,"616":1,"624":1,"638":1,"647":1,"655":1,"663":1,"672":1,"680":1,"688":1,"696":1,"705":1,"713":1,"723":1,"731":1,"739":1,"747":1,"755":1,"764":1,"772":1,"780":1,"788":1,"797":1,"808":1,"817":1,"825":1,"836":1,"844":1,"852":1,"859":1,"867":1,"879":1,"887":1,"895":1,"903":1,"912":1,"920":1,"929":1,"937":1,"944":1,"952":1,"962":1,"970":1,"978":1,"986":1,"994":1,"1002":1,"1010":1,"1016":1,"1024":1,"1032":1,"1040":1,"1048":1,"1057":1,"1065":1,"1073":1,"1081":1,"1089":1,"1098":1,"1106":1,"1114":1,"1122":1,"1131":1,"1139":1,"1149":1,"1159":1,"1166":1,"1173":1,"1182":1,"1765":1,"1872":1},"2":{"537":1,"574":1,"809":1,"813":1,"1593":1,"1632":1,"1748":1,"1807":1,"1811":1,"1925":1,"2166":1,"2167":1},"3":{"0":5,"1":1,"27":1,"42":1,"74":1,"100":2,"107":1,"109":8,"110":2,"111":3,"112":1,"138":1,"139":1,"140":1,"142":1,"150":1,"157":4,"203":1,"225":1,"230":2,"235":1,"261":1,"301":1,"340":1,"341":3,"343":1,"392":1,"403":1,"423":2,"424":1,"434":1,"435":1,"451":1,"458":1,"459":4,"460":1,"461":1,"497":2,"499":1,"502":1,"536":4,"537":1,"538":1,"556":1,"574":1,"591":1,"609":1,"631":1,"633":2,"660":1,"731":1,"741":1,"757":3,"759":1,"761":1,"766":1,"789":3,"790":2,"791":3,"794":1,"802":1,"809":3,"811":1,"812":1,"813":1,"856":1,"882":1,"909":2,"921":1,"1019":1,"1021":2,"1062":1,"1116":1,"1124":1,"1140":1,"1143":1,"1150":1,"1212":2,"1217":2,"1219":1,"1222":1,"1224":1,"1225":1,"1229":13,"1237":1,"1239":1,"1247":3,"1260":1,"1263":1,"1266":1,"1270":3,"1285":3,"1299":15,"1303":2,"1308":26,"1310":41,"1311":13,"1323":12,"1327":1,"1331":1,"1336":2,"1338":19,"1340":1,"1343":7,"1348":1,"1349":43,"1352":4,"1355":1,"1357":2,"1358":64,"1364":1,"1368":4,"1379":3,"1384":3,"1388":2,"1389":1,"1394":36,"1395":28,"1397":2,"1400":1,"1401":49,"1414":4,"1415":1,"1416":9,"1435":17,"1437":5,"1448":1,"1452":1,"1454":5,"1464":1,"1466":5,"1473":4,"1474":1,"1477":1,"1495":2,"1525":3,"1530":1,"1534":1,"1545":1,"1576":5,"1585":1,"1590":1,"1593":1,"1599":1,"1610":1,"1629":7,"1630":6,"1631":4,"1632":4,"1634":5,"1636":1,"1637":1,"1638":10,"1639":6,"1640":1,"1642":1,"1643":1,"1649":1,"1650":3,"1659":1,"1660":1,"1661":1,"1665":1,"1668":1,"1682":3,"1683":1,"1684":1,"1685":13,"1686":1,"1696":1,"1700":1,"1701":1,"1709":1,"1715":1,"1726":10,"1727":3,"1728":2,"1741":1,"1747":3,"1748":10,"1749":4,"1752":1,"1753":2,"1759":1,"1763":5,"1764":14,"1767":3,"1769":1,"1775":1,"1804":5,"1805":2,"1807":1,"1809":1,"1811":1,"1813":2,"1814":1,"1830":1,"1872":1,"1873":1,"1875":2,"1876":1,"1907":2,"1908":3,"1911":1,"1922":1,"1924":2,"1925":1,"1926":7,"1929":6,"1932":4,"1939":1,"1940":1,"1956":1,"1958":1,"2017":1,"2065":2,"2084":1,"2088":1,"2126":1,"2136":1,"2157":1,"2162":1,"2165":4,"2166":3,"2167":2,"2168":3,"2231":3}}],["staywithinthelinecap",{"3":{"1435":3}}],["stayed",{"3":{"148":1,"265":1,"592":1,"766":1,"832":1,"871":1,"1299":1,"1310":1,"1338":2,"1368":1,"1386":1,"1435":1,"1473":1,"1475":1,"1490":1,"2155":1}}],["staying",{"3":{"131":1,"212":1,"499":1,"906":1,"909":1,"1020":1,"1128":1,"1237":1,"1247":1,"1271":1,"1285":2,"1296":1,"1299":2,"1310":3,"1323":1,"1338":1,"1358":1,"1394":1,"1395":3,"1435":5,"1437":2,"1440":1,"1476":1,"1666":1,"2000":1}}],["stay",{"2":{"2042":1},"3":{"0":21,"27":2,"58":1,"62":1,"68":1,"72":1,"78":1,"91":1,"93":1,"96":1,"100":3,"111":1,"117":1,"122":1,"128":1,"138":1,"140":1,"141":1,"142":2,"149":1,"150":1,"186":2,"197":1,"201":1,"209":1,"219":1,"224":1,"265":2,"273":1,"274":1,"286":1,"351":1,"358":1,"362":1,"363":1,"369":1,"395":1,"398":1,"399":1,"401":1,"407":1,"412":1,"413":2,"442":1,"448":1,"460":2,"468":1,"482":2,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"498":2,"500":1,"509":1,"511":1,"518":1,"524":1,"528":1,"544":1,"547":1,"568":1,"590":1,"591":1,"609":3,"611":1,"618":1,"625":1,"649":1,"656":1,"657":1,"681":1,"683":1,"724":1,"735":1,"742":1,"785":1,"790":2,"799":1,"821":2,"827":1,"829":1,"832":1,"833":3,"838":1,"846":1,"863":1,"876":1,"877":1,"922":1,"923":1,"926":2,"939":1,"960":1,"964":2,"980":1,"996":1,"1004":1,"1018":1,"1026":1,"1036":1,"1045":1,"1049":1,"1059":4,"1074":1,"1075":2,"1091":1,"1096":1,"1109":1,"1123":1,"1141":1,"1168":1,"1175":1,"1177":1,"1184":1,"1201":2,"1210":1,"1212":5,"1217":1,"1220":1,"1229":4,"1233":1,"1235":1,"1237":8,"1239":2,"1241":1,"1247":2,"1252":1,"1257":1,"1259":5,"1267":1,"1269":1,"1270":10,"1271":2,"1280":1,"1285":23,"1290":1,"1294":1,"1299":10,"1300":4,"1308":8,"1309":1,"1310":12,"1316":1,"1321":1,"1322":9,"1323":22,"1337":1,"1338":9,"1346":1,"1349":6,"1352":1,"1354":1,"1358":45,"1361":1,"1368":4,"1376":1,"1379":9,"1392":1,"1394":14,"1395":23,"1400":1,"1401":3,"1408":1,"1414":3,"1415":8,"1416":8,"1422":1,"1423":1,"1432":1,"1434":1,"1435":66,"1437":5,"1441":1,"1446":1,"1454":1,"1455":1,"1466":2,"1473":1,"1475":1,"1488":4,"1489":1,"1491":1,"1507":1,"1508":1,"1514":1,"1525":3,"1526":1,"1533":1,"1535":1,"1559":1,"1568":1,"1571":1,"1576":2,"1581":1,"1582":1,"1590":2,"1591":1,"1598":1,"1626":1,"1653":1,"1655":1,"1662":1,"1664":2,"1667":1,"1671":1,"1676":1,"1685":1,"1692":1,"1702":1,"1721":1,"1757":1,"1764":1,"1804":1,"1809":1,"1814":2,"1841":1,"1842":1,"1885":1,"1890":1,"1897":2,"1915":1,"1919":1,"1932":1,"1966":1,"1970":1,"1976":1,"1990":1,"1999":1,"2000":1,"2009":1,"2027":1,"2041":1,"2042":1,"2046":1,"2057":1,"2082":3,"2093":1,"2103":1,"2119":1,"2130":1,"2134":1,"2138":1,"2140":1,"2149":2,"2157":1,"2158":1,"2166":1,"2168":1,"2178":1,"2201":1,"2231":3}}],["stays",{"0":{"2025":1},"1":{"2115":1,"2116":1,"2117":1,"2118":1,"2119":1,"2120":1,"2121":1,"2122":1},"3":{"0":26,"15":1,"20":1,"31":2,"38":1,"39":2,"54":2,"59":3,"62":1,"72":1,"91":1,"92":2,"96":1,"100":1,"109":1,"124":1,"134":1,"135":1,"139":1,"140":1,"141":1,"152":1,"158":1,"173":1,"178":2,"182":1,"184":1,"188":1,"193":1,"195":2,"201":1,"207":1,"219":1,"225":1,"229":1,"230":1,"233":1,"235":3,"237":1,"244":1,"250":1,"258":1,"260":2,"265":2,"295":1,"312":1,"313":1,"314":1,"317":1,"324":1,"342":3,"345":1,"350":1,"357":1,"359":3,"361":1,"362":1,"365":1,"380":1,"384":1,"392":1,"396":1,"398":1,"402":1,"404":1,"408":1,"413":2,"414":1,"425":1,"434":1,"448":1,"449":1,"451":1,"458":2,"460":1,"486":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"499":1,"501":1,"508":1,"528":1,"530":1,"538":1,"539":3,"545":1,"547":1,"549":1,"556":1,"591":1,"592":1,"607":1,"609":1,"617":1,"627":1,"633":1,"640":2,"644":1,"657":1,"668":1,"682":1,"683":1,"684":1,"692":1,"698":1,"711":1,"719":1,"725":1,"733":1,"741":1,"749":1,"751":1,"758":1,"774":1,"790":1,"793":1,"797":1,"799":1,"803":1,"810":1,"827":3,"830":1,"834":1,"836":1,"837":1,"838":1,"849":1,"853":1,"857":1,"863":1,"870":1,"873":1,"876":1,"897":1,"905":1,"914":6,"917":1,"926":2,"929":1,"946":1,"950":1,"954":1,"957":1,"964":2,"966":1,"973":1,"990":1,"1013":1,"1018":2,"1026":1,"1034":2,"1037":1,"1048":2,"1050":1,"1059":1,"1060":1,"1075":1,"1093":3,"1094":3,"1096":1,"1100":1,"1116":1,"1133":1,"1137":1,"1149":1,"1151":3,"1163":1,"1175":1,"1182":1,"1184":1,"1185":1,"1186":1,"1187":2,"1188":1,"1193":1,"1212":10,"1215":1,"1220":1,"1229":2,"1231":1,"1234":1,"1237":8,"1239":1,"1240":1,"1243":1,"1247":8,"1252":1,"1254":1,"1256":1,"1259":11,"1263":1,"1264":1,"1265":1,"1266":1,"1270":10,"1271":3,"1274":1,"1275":2,"1276":3,"1280":1,"1285":37,"1287":1,"1288":1,"1292":2,"1299":21,"1300":5,"1303":2,"1305":1,"1308":12,"1309":2,"1310":23,"1311":2,"1315":1,"1322":31,"1323":42,"1327":1,"1328":1,"1331":1,"1332":2,"1337":1,"1338":26,"1349":12,"1351":1,"1352":1,"1354":1,"1358":67,"1359":1,"1365":1,"1367":1,"1368":11,"1378":1,"1379":12,"1384":1,"1390":1,"1394":38,"1395":50,"1398":1,"1401":19,"1405":2,"1406":1,"1407":2,"1408":1,"1409":3,"1414":28,"1415":13,"1416":28,"1419":1,"1422":1,"1426":3,"1430":2,"1432":1,"1433":1,"1435":75,"1437":6,"1440":1,"1441":1,"1442":2,"1446":4,"1453":2,"1454":3,"1455":1,"1457":1,"1459":1,"1466":10,"1473":1,"1485":2,"1488":3,"1489":2,"1491":1,"1495":9,"1533":2,"1567":1,"1570":1,"1576":5,"1584":5,"1585":1,"1591":1,"1598":2,"1626":2,"1628":1,"1629":1,"1630":1,"1640":4,"1650":1,"1652":1,"1659":1,"1660":2,"1661":1,"1666":1,"1667":1,"1668":1,"1676":1,"1683":2,"1684":2,"1685":2,"1720":1,"1726":3,"1728":1,"1763":1,"1764":1,"1766":1,"1769":1,"1780":1,"1784":1,"1791":1,"1798":1,"1804":2,"1805":1,"1809":2,"1814":1,"1817":1,"1822":2,"1829":1,"1836":1,"1837":1,"1841":1,"1857":3,"1858":1,"1864":1,"1870":1,"1873":1,"1880":1,"1890":1,"1895":1,"1901":2,"1907":1,"1914":1,"1919":1,"1928":1,"1932":1,"1933":1,"1939":1,"1945":1,"1958":1,"1962":1,"1966":1,"1967":1,"1971":1,"1977":1,"1978":1,"1981":1,"1983":1,"1984":3,"1987":1,"1999":1,"2000":1,"2001":1,"2005":1,"2014":1,"2024":1,"2027":1,"2043":1,"2047":1,"2054":3,"2060":2,"2062":1,"2063":2,"2088":1,"2095":1,"2098":1,"2103":1,"2106":2,"2111":1,"2115":1,"2119":1,"2121":2,"2141":1,"2144":1,"2150":1,"2154":1,"2155":1,"2167":1,"2174":1,"2198":2,"2209":1,"2224":1,"2231":3}}],["mcp",{"3":{"2238":2}}],["mcpd",{"3":{"2223":1}}],["mcts",{"3":{"2223":1}}],["mct001",{"3":{"1416":2}}],["mcsa",{"3":{"2223":2}}],["mcr",{"3":{"640":1,"868":2,"1338":1,"1440":1,"1444":2,"1464":1}}],["mvp",{"3":{"1394":3}}],["mvc",{"2":{"300":1,"2136":1},"3":{"0":4,"157":1,"299":1,"300":1,"301":1,"448":1,"583":1,"657":1,"725":1,"726":1,"827":1,"962":1,"1049":1,"1050":3,"1051":2,"1052":1,"1213":2,"1235":2,"1237":5,"1299":5,"1307":1,"1308":10,"1310":34,"1322":1,"1378":1,"1379":2,"1395":7,"1401":2,"1414":3,"1435":12,"1437":1,"1446":1,"1665":1,"1809":3,"2130":2,"2136":2,"2219":1,"2223":1}}],["mgmt",{"3":{"1192":1}}],["m48",{"3":{"1437":1}}],["m4",{"3":{"1012":2,"1014":2,"1089":1,"1090":1,"1094":1,"1095":1,"1525":5,"1530":9,"1533":1,"1576":2,"1581":1,"1587":1,"1590":4,"1591":1,"1594":11,"1599":42}}],["m3",{"3":{"1012":2,"1014":1,"1525":2,"1530":5,"1576":2,"1588":2,"1590":1,"1591":1,"1594":4,"1595":1,"1599":11}}],["m2",{"3":{"1012":1,"1090":1}}],["mm",{"3":{"305":1,"1018":1,"1299":2,"1349":2,"1358":1,"1368":2,"1415":2,"1435":6,"1491":1,"1608":1,"1709":1,"1742":1,"2137":1}}],["mmcacommondomain",{"3":{"1435":2}}],["mmcaclientconfigbootstraptests",{"3":{"1199":1,"1207":3,"1323":2}}],["mmcaclientconfigbootstrap",{"3":{"1184":1,"1199":2,"1203":1,"1207":2,"1323":4,"1415":1,"1495":1}}],["mmcaculturebootstrap",{"2":{"265":1,"1525":1,"1652":1,"2082":1,"2085":1},"3":{"265":2,"1199":1,"1203":1,"1207":2,"1310":3,"1323":17,"1415":1,"1416":1,"1495":1,"1525":1,"1652":1,"2082":2,"2085":1}}],["mmcastore",{"2":{"764":1,"1470":1},"3":{"764":1,"766":2,"1470":1,"1590":1,"1599":2}}],["mmca0002",{"2":{"576":1},"3":{"576":1,"1488":1}}],["mmca0001",{"2":{"576":1},"3":{"576":1,"1488":1}}],["mmcalayerenforcement",{"2":{"576":1}}],["mmcaadcuiweb",{"3":{"1455":1,"1489":1}}],["mmcaadc",{"3":{"1455":2,"1489":1}}],["mmcaauthcookie",{"3":{"1323":4}}],["mmcaauthsession",{"2":{"507":1},"3":{"507":1,"1323":2}}],["mmcaapplicationpipelinebuilder",{"2":{"1262":1,"1312":1,"1315":1,"1661":1},"3":{"122":1,"1199":1,"1203":1,"1207":2,"1262":1,"1312":1,"1315":3,"1322":7,"1661":1}}],["mmcaopenapidesigntime",{"2":{"452":1},"3":{"452":1}}],["mmcagatewaysection",{"3":{"1325":1,"1338":1,"1443":1}}],["mmcagatewaytests",{"3":{"180":1,"827":1,"833":1}}],["mmcagateway",{"2":{"836":1,"841":1},"3":{"0":2,"827":1,"833":1,"836":1,"838":3,"841":1,"1337":2,"1338":12,"1437":2,"1446":3,"1655":1}}],["mmcagatewayhardeningtestsbasetests",{"3":{"1199":1,"1207":4,"1435":1,"1437":1,"1486":1}}],["mmcagatewayhardeningtestsbase",{"2":{"572":1,"578":1,"2054":1},"3":{"0":1,"572":8,"578":3,"1199":4,"1207":2,"1435":5,"1437":2,"1487":4,"1492":1,"1495":1,"2054":1}}],["mmcagenerateopenapidocument",{"2":{"451":1,"452":1,"1454":1},"3":{"0":1,"451":1,"452":1,"1454":1}}],["mmcathemeprovidersprerendertests",{"3":{"1199":1,"1207":2,"1323":1}}],["mmcathemeproviderstests",{"3":{"1199":2,"1207":2,"1323":1}}],["mmcathemeproviders",{"2":{"273":1,"275":1,"276":1,"2074":1,"2082":1},"3":{"0":2,"273":5,"275":1,"276":2,"1323":9,"1432":1,"1435":1,"1525":3,"2074":2,"2082":2}}],["mmcatheme",{"2":{"272":1,"273":1,"621":1,"2074":2,"2079":1,"2082":1,"2085":1},"3":{"0":2,"272":2,"273":2,"618":1,"621":1,"622":1,"1199":3,"1203":1,"1207":2,"1323":12,"1495":1,"1525":3,"1576":2,"1585":1,"2074":4,"2079":1,"2082":3,"2085":1}}],["mmca",{"0":{"95":1,"465":1,"1440":1,"1441":1,"1442":1,"1443":1,"1452":1,"1453":1,"1454":1,"1455":1,"1456":1,"1457":1,"1601":1,"1602":1,"1603":1,"1678":1,"1726":1,"1727":1,"1729":2,"1789":1,"1792":1,"1798":1,"1801":1,"1804":1,"1806":1,"1809":1,"1811":1,"1814":1,"1816":1,"1826":1,"1833":1,"1837":1,"1845":1,"1848":1,"1852":1,"1861":1,"1864":1,"1867":1,"1870":1,"1876":1,"1885":1,"1888":1,"1891":1,"1894":1,"1898":1,"1901":1,"1904":1,"1911":1,"1923":1,"1926":1,"1929":1,"1940":1,"1943":1,"1949":1,"1958":1,"1961":1,"1964":1,"1967":1,"1971":1,"1974":1,"1978":1,"1981":1,"1984":1,"1987":1,"1990":1,"1993":1,"1994":1,"1996":1,"1999":1,"2001":1,"2013":1,"2021":1,"2027":1,"2037":1,"2048":1,"2059":1,"2062":1,"2068":1,"2079":1,"2082":1,"2085":1,"2107":1,"2116":1,"2122":1,"2125":1,"2127":1,"2130":1,"2132":1,"2135":1,"2138":1,"2141":1,"2145":1,"2148":1,"2152":1,"2155":1,"2159":1,"2162":1,"2168":1,"2171":1,"2179":1,"2183":1,"2192":1,"2195":1,"2202":1,"2213":1,"2220":1,"2226":1},"1":{"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1,"1088":1,"1089":1,"1090":1,"1091":1,"1092":1,"1093":1,"1094":1,"1095":1,"1096":1,"1190":2,"1191":2,"1192":2,"1193":2,"1194":2,"1195":2,"1501":1,"1502":1,"1503":1,"1504":1,"1505":1,"1506":1,"1507":1,"1508":1,"1509":1,"1510":1,"1511":1,"1512":1,"1513":1,"1514":1,"1515":1,"1516":1,"1521":1,"1522":1,"1523":1,"1524":1,"1525":1,"1526":1,"1527":1,"1528":1,"1529":1,"1530":1,"1531":1,"1532":1,"1533":1,"1534":1,"1572":1,"1573":1,"1574":1,"1575":1,"1576":1,"1577":1,"1578":1,"1579":1,"1580":1,"1581":1,"1582":1,"1583":1,"1584":1,"1585":1,"1586":1,"1587":1,"1588":1,"1589":1,"1590":1,"1591":1,"1592":1,"1593":1,"1594":1,"1595":1,"1596":1,"1597":1,"1598":1,"1599":1,"1645":1,"1646":1,"1647":1,"1648":1,"1649":1,"1650":1,"1651":1,"1652":1,"1653":1,"1654":1,"1655":1,"1656":1,"1657":1,"1658":1,"1659":1,"1660":1,"1661":1,"1662":1,"1663":1,"1664":1,"1665":1,"1666":1,"1667":1,"1668":1,"1669":1,"1670":1,"1671":1,"1672":1,"1673":1,"1674":1,"1680":1,"1681":1,"1682":1,"1683":1,"1684":1,"1685":1,"1686":1,"1687":1,"1688":1,"1689":1,"1690":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1698":1,"1699":1,"1700":1,"1701":1,"1702":1,"1703":1,"1704":1,"1716":1,"1717":1,"1718":1,"1719":1,"1720":1,"1721":1,"1722":1,"1723":1,"1724":1,"1725":1,"1726":1,"1727":1,"1728":1,"1729":1,"1730":1,"1744":1,"1745":1,"1746":1,"1747":1,"1748":1,"1749":1,"1750":1,"1751":1,"1752":1,"1753":1,"1754":1,"1761":1,"1762":1,"1763":1,"1764":1,"1765":1,"1766":1,"1767":1,"1768":1,"1769":1,"1770":1,"1771":1,"1772":1,"1773":1,"1774":1,"1775":1,"1776":1,"1777":1,"1778":1,"2153":1,"2154":1,"2155":1,"2156":1,"2157":1,"2158":1,"2159":1,"2203":1,"2204":1,"2205":1,"2206":1,"2207":1,"2208":1,"2209":1,"2210":1,"2225":1,"2226":1,"2227":1,"2228":1,"2229":1,"2230":1,"2231":1,"2232":1,"2233":1,"2234":1},"2":{"24":1,"39":3,"53":1,"58":1,"59":2,"61":2,"62":3,"67":1,"68":1,"70":1,"78":1,"80":3,"85":1,"95":1,"99":1,"100":1,"107":2,"109":1,"111":1,"117":1,"124":1,"126":1,"130":1,"132":2,"135":2,"137":1,"138":1,"139":3,"140":1,"141":6,"142":6,"145":2,"147":1,"150":1,"152":2,"160":1,"180":1,"186":1,"190":1,"207":4,"214":2,"216":1,"225":2,"229":1,"241":2,"247":1,"250":1,"252":1,"255":2,"258":1,"259":2,"261":1,"265":1,"273":1,"281":2,"310":1,"359":2,"370":1,"373":1,"388":1,"395":6,"400":7,"401":2,"404":2,"406":1,"408":1,"412":1,"436":1,"460":1,"481":1,"482":3,"486":3,"487":3,"488":3,"489":3,"490":3,"491":3,"492":3,"493":6,"494":3,"495":3,"509":6,"511":3,"514":1,"526":2,"527":2,"528":3,"529":1,"530":1,"558":1,"564":2,"572":2,"576":1,"577":1,"578":2,"591":1,"599":1,"617":1,"618":1,"632":1,"633":1,"634":3,"635":1,"636":1,"638":3,"648":1,"649":1,"667":1,"681":1,"683":1,"684":1,"686":2,"731":1,"733":1,"780":1,"789":1,"821":1,"822":3,"823":3,"825":2,"826":3,"832":1,"836":1,"838":1,"840":1,"861":1,"880":1,"897":2,"899":1,"906":1,"912":1,"913":1,"914":2,"917":1,"936":1,"938":2,"939":7,"941":2,"942":1,"953":1,"954":1,"957":1,"963":1,"966":1,"982":1,"984":1,"1004":2,"1012":1,"1016":1,"1036":3,"1037":1,"1044":1,"1051":1,"1057":1,"1058":1,"1062":1,"1066":1,"1067":2,"1068":3,"1069":3,"1074":1,"1089":4,"1090":1,"1091":1,"1093":1,"1124":2,"1161":2,"1191":2,"1194":2,"1195":1,"1200":2,"1201":2,"1203":3,"1207":10,"1211":1,"1213":5,"1214":1,"1215":4,"1217":1,"1222":1,"1229":3,"1230":2,"1236":1,"1237":3,"1247":3,"1256":1,"1259":6,"1264":1,"1268":1,"1270":6,"1271":2,"1275":1,"1285":5,"1286":2,"1299":2,"1300":3,"1308":4,"1309":2,"1316":1,"1320":1,"1322":5,"1324":6,"1325":1,"1326":1,"1327":2,"1328":1,"1329":1,"1332":1,"1335":1,"1336":1,"1338":1,"1339":1,"1340":3,"1349":6,"1350":1,"1358":5,"1362":1,"1365":1,"1368":2,"1369":3,"1377":1,"1378":2,"1379":7,"1380":1,"1385":1,"1394":10,"1395":11,"1401":6,"1403":6,"1413":1,"1414":4,"1415":2,"1416":9,"1417":4,"1421":2,"1424":4,"1425":3,"1426":1,"1427":1,"1429":3,"1433":3,"1434":1,"1435":2,"1436":12,"1437":2,"1438":1,"1441":1,"1444":8,"1448":1,"1450":1,"1451":1,"1452":4,"1453":3,"1454":2,"1455":1,"1458":1,"1459":1,"1470":2,"1475":1,"1485":10,"1486":9,"1487":1,"1489":6,"1491":3,"1493":1,"1495":4,"1496":2,"1499":1,"1502":10,"1508":2,"1511":1,"1512":1,"1522":1,"1523":1,"1524":1,"1525":2,"1526":2,"1530":2,"1533":1,"1534":1,"1575":1,"1576":1,"1577":1,"1584":3,"1590":1,"1598":1,"1601":1,"1604":1,"1610":2,"1614":4,"1618":2,"1638":1,"1641":1,"1642":2,"1647":1,"1648":1,"1649":5,"1652":2,"1653":1,"1655":2,"1659":12,"1660":2,"1674":1,"1676":1,"1680":1,"1681":1,"1683":1,"1686":1,"1689":1,"1692":2,"1701":4,"1710":1,"1725":1,"1729":1,"1731":1,"1738":1,"1740":1,"1760":2,"1774":2,"1780":1,"1781":2,"1786":1,"1789":1,"1792":1,"1804":1,"1806":1,"1811":1,"1814":1,"1816":1,"1826":1,"1827":1,"1828":1,"1833":1,"1842":1,"1845":1,"1852":1,"1861":1,"1864":1,"1867":1,"1876":1,"1885":1,"1891":1,"1894":1,"1895":1,"1898":1,"1901":2,"1904":1,"1911":1,"1918":1,"1921":1,"1923":1,"1929":1,"1934":1,"1940":1,"1949":1,"1952":1,"1958":1,"1961":1,"1964":1,"1971":1,"1974":2,"1978":1,"1984":1,"1990":1,"1996":1,"2001":1,"2003":3,"2005":9,"2007":1,"2012":8,"2013":3,"2015":1,"2021":1,"2023":1,"2027":2,"2029":1,"2037":1,"2041":1,"2043":1,"2045":1,"2048":1,"2054":2,"2055":1,"2056":1,"2058":2,"2059":1,"2062":1,"2069":1,"2070":2,"2076":1,"2079":1,"2085":1,"2099":2,"2100":1,"2107":1,"2110":1,"2114":1,"2115":1,"2119":3,"2122":1,"2127":1,"2130":1,"2132":1,"2138":1,"2141":1,"2145":1,"2148":1,"2149":1,"2150":1,"2151":1,"2152":1,"2155":1,"2156":3,"2159":9,"2168":1,"2171":1,"2175":3,"2176":4,"2177":1,"2179":2,"2190":1,"2192":2,"2202":1,"2210":1},"3":{"0":139,"5":1,"11":1,"15":4,"17":4,"18":1,"19":3,"22":2,"23":1,"24":11,"25":8,"26":19,"27":24,"31":5,"34":2,"39":9,"42":4,"43":4,"46":1,"53":3,"54":2,"58":1,"59":17,"60":2,"61":4,"62":11,"63":7,"66":1,"67":1,"68":8,"70":5,"71":4,"72":14,"73":5,"74":14,"76":2,"78":1,"79":2,"80":14,"81":9,"82":4,"85":2,"93":8,"94":6,"95":17,"96":11,"97":2,"98":10,"99":3,"100":5,"103":6,"104":2,"107":2,"109":46,"111":6,"117":14,"121":15,"122":5,"123":8,"124":1,"125":10,"126":7,"127":4,"128":17,"130":6,"132":2,"135":26,"136":6,"137":5,"138":5,"139":6,"140":3,"141":7,"142":14,"145":3,"146":3,"147":19,"149":6,"150":13,"151":2,"152":9,"157":5,"160":8,"161":6,"164":1,"166":10,"168":1,"170":3,"171":3,"173":1,"175":7,"178":6,"179":12,"180":19,"181":4,"184":1,"186":27,"189":10,"190":11,"193":3,"195":21,"196":4,"198":6,"200":4,"201":3,"202":18,"203":4,"207":5,"208":1,"214":4,"216":5,"217":6,"218":12,"219":8,"220":10,"225":9,"228":8,"229":1,"230":18,"231":8,"235":24,"237":2,"238":1,"241":2,"243":44,"245":22,"246":14,"247":1,"250":2,"251":1,"252":3,"254":1,"255":11,"256":8,"257":1,"258":8,"259":15,"260":9,"261":5,"265":22,"268":2,"272":1,"273":4,"275":1,"281":3,"283":4,"284":4,"285":6,"290":2,"291":4,"295":24,"296":2,"300":4,"301":1,"303":8,"305":14,"306":8,"310":12,"312":1,"314":4,"318":20,"324":1,"325":10,"326":2,"328":1,"330":8,"331":1,"333":10,"336":1,"337":4,"341":37,"342":2,"343":6,"344":12,"345":4,"350":11,"352":8,"353":14,"359":9,"365":2,"366":1,"368":1,"369":2,"370":5,"373":6,"374":4,"375":5,"384":14,"387":4,"388":7,"390":12,"391":14,"392":8,"395":8,"397":13,"400":16,"401":19,"402":4,"403":9,"404":7,"406":1,"407":12,"408":8,"409":8,"412":4,"413":18,"415":3,"416":2,"418":27,"419":1,"420":3,"422":4,"423":11,"424":7,"425":7,"426":19,"427":4,"428":18,"429":12,"430":7,"434":3,"436":3,"442":2,"443":4,"444":6,"446":2,"448":24,"450":3,"451":3,"452":17,"453":2,"454":1,"457":1,"459":27,"460":1,"463":15,"464":10,"465":10,"466":5,"469":6,"470":31,"472":1,"475":28,"476":34,"477":34,"478":6,"481":2,"482":6,"483":2,"484":2,"486":27,"487":16,"488":10,"489":17,"490":24,"491":22,"492":22,"493":27,"494":12,"495":15,"497":20,"498":1,"499":10,"502":5,"507":44,"509":12,"511":15,"512":2,"513":10,"514":6,"522":4,"523":6,"524":4,"526":4,"527":7,"528":7,"529":1,"530":3,"531":4,"532":1,"535":2,"536":23,"538":3,"539":7,"540":3,"543":6,"544":2,"545":57,"547":5,"549":6,"550":7,"551":7,"552":16,"556":73,"558":6,"559":2,"562":3,"563":10,"564":45,"566":11,"567":3,"570":2,"572":83,"575":6,"576":12,"577":16,"578":12,"579":1,"582":1,"583":77,"585":26,"586":12,"587":2,"589":2,"590":1,"591":20,"592":2,"593":6,"594":5,"597":1,"599":69,"600":8,"601":14,"602":3,"603":8,"604":3,"607":1,"608":1,"609":78,"611":11,"612":3,"613":7,"616":2,"617":1,"618":21,"620":1,"621":1,"624":10,"625":13,"626":75,"627":3,"628":10,"629":12,"631":4,"632":5,"633":23,"634":8,"635":8,"636":4,"638":4,"640":46,"642":12,"643":3,"644":8,"648":1,"649":43,"651":5,"652":2,"656":2,"657":28,"659":1,"664":2,"665":24,"667":1,"668":11,"669":10,"674":92,"675":6,"676":22,"677":1,"681":3,"682":6,"683":1,"684":1,"685":1,"686":2,"688":1,"689":2,"690":5,"691":2,"692":2,"693":1,"696":3,"697":3,"698":33,"700":3,"702":19,"703":13,"705":1,"706":2,"707":8,"713":1,"715":10,"718":2,"719":5,"720":8,"723":1,"724":8,"725":31,"727":7,"728":5,"731":1,"732":2,"733":16,"736":8,"739":1,"740":4,"741":10,"742":1,"743":2,"744":4,"749":38,"751":8,"756":6,"757":35,"758":3,"759":8,"760":4,"761":5,"765":5,"766":51,"768":8,"769":3,"774":41,"776":7,"780":2,"782":48,"784":4,"789":7,"790":21,"792":7,"793":12,"794":8,"798":20,"799":14,"803":6,"804":7,"805":4,"806":2,"808":1,"809":7,"810":1,"813":7,"814":2,"818":2,"819":34,"821":1,"822":8,"823":3,"825":9,"826":20,"827":58,"830":14,"831":10,"832":26,"833":32,"836":1,"837":16,"838":29,"839":6,"840":1,"841":6,"846":5,"849":5,"850":2,"853":2,"854":15,"855":2,"857":12,"860":1,"861":18,"863":3,"864":1,"865":1,"868":13,"869":31,"870":8,"872":4,"873":8,"875":4,"876":28,"877":13,"880":3,"881":43,"883":4,"884":3,"888":4,"889":2,"890":2,"891":7,"892":2,"897":23,"899":1,"900":4,"904":2,"905":22,"906":14,"907":1,"908":6,"912":1,"913":27,"914":16,"915":4,"916":1,"917":6,"921":2,"922":9,"923":1,"924":7,"926":20,"927":1,"930":1,"931":7,"932":1,"936":1,"938":2,"939":12,"941":2,"942":1,"945":1,"946":21,"950":1,"953":1,"954":59,"955":1,"956":10,"957":1,"958":9,"959":10,"960":15,"963":9,"964":18,"966":3,"971":11,"972":21,"973":1,"974":5,"975":7,"979":15,"980":21,"982":3,"983":1,"984":1,"986":2,"987":4,"988":15,"990":3,"991":2,"994":2,"995":9,"996":13,"998":3,"999":7,"1003":5,"1004":36,"1006":6,"1007":4,"1010":5,"1012":14,"1013":1,"1016":3,"1017":7,"1018":24,"1021":2,"1022":1,"1025":11,"1026":28,"1028":2,"1029":2,"1034":26,"1036":3,"1037":6,"1041":8,"1042":38,"1043":3,"1044":8,"1045":5,"1049":10,"1050":49,"1051":1,"1052":6,"1054":8,"1055":6,"1057":2,"1058":10,"1059":57,"1062":4,"1065":2,"1066":12,"1067":25,"1068":5,"1069":13,"1070":5,"1073":4,"1074":24,"1075":6,"1078":6,"1082":12,"1083":13,"1085":5,"1086":4,"1088":1,"1089":11,"1090":7,"1091":48,"1093":5,"1094":6,"1095":5,"1099":4,"1100":13,"1103":6,"1108":15,"1116":39,"1117":1,"1119":5,"1122":1,"1123":11,"1124":46,"1125":9,"1126":7,"1127":4,"1128":4,"1129":2,"1132":4,"1133":26,"1135":2,"1136":4,"1137":1,"1140":18,"1142":3,"1150":26,"1154":2,"1156":2,"1160":7,"1161":17,"1163":1,"1164":1,"1168":45,"1170":1,"1171":1,"1173":1,"1174":4,"1175":54,"1180":1,"1182":3,"1183":8,"1184":39,"1185":3,"1186":4,"1187":2,"1189":1,"1190":4,"1191":9,"1192":1,"1194":2,"1195":3,"1199":5034,"1200":3,"1201":3,"1203":2321,"1204":6,"1206":108,"1207":15600,"1208":230,"1209":136,"1211":13,"1212":40,"1213":9,"1214":11,"1215":7,"1217":1,"1219":6,"1220":2,"1222":7,"1223":4,"1224":2,"1225":8,"1226":4,"1227":6,"1228":4,"1229":129,"1230":4,"1231":12,"1232":10,"1233":14,"1234":24,"1235":4,"1236":19,"1237":370,"1238":1,"1239":26,"1240":8,"1241":12,"1242":6,"1243":10,"1244":5,"1245":2,"1246":6,"1247":346,"1249":8,"1250":1,"1251":3,"1252":4,"1253":1,"1254":4,"1255":1,"1256":3,"1257":10,"1258":5,"1259":290,"1260":1,"1261":4,"1262":21,"1263":4,"1264":25,"1265":8,"1266":5,"1267":10,"1268":4,"1269":4,"1270":558,"1271":210,"1273":10,"1274":4,"1275":5,"1276":14,"1277":8,"1278":18,"1279":18,"1280":7,"1281":2,"1282":4,"1283":4,"1284":14,"1285":1492,"1286":2,"1287":16,"1288":13,"1289":20,"1290":24,"1291":12,"1292":18,"1293":18,"1294":12,"1295":10,"1296":60,"1297":14,"1298":4,"1299":2184,"1300":153,"1301":14,"1302":17,"1303":20,"1304":12,"1305":16,"1306":32,"1307":16,"1308":502,"1309":140,"1310":965,"1311":140,"1312":1,"1313":6,"1314":10,"1315":24,"1316":23,"1317":19,"1318":6,"1319":57,"1320":5,"1321":31,"1322":896,"1323":1502,"1324":28,"1325":16,"1326":9,"1327":11,"1328":3,"1329":11,"1330":10,"1331":34,"1332":23,"1333":20,"1334":12,"1335":15,"1336":14,"1337":22,"1338":705,"1339":2,"1340":21,"1341":24,"1343":15,"1344":10,"1345":2,"1346":32,"1347":10,"1348":20,"1349":820,"1350":1,"1351":19,"1352":21,"1353":10,"1354":6,"1355":4,"1356":3,"1357":3,"1358":3233,"1360":4,"1361":4,"1362":6,"1363":5,"1364":2,"1365":7,"1366":3,"1367":4,"1368":211,"1369":4,"1370":14,"1371":2,"1372":8,"1373":2,"1375":2,"1376":14,"1377":17,"1378":6,"1379":453,"1380":3,"1383":2,"1385":1,"1388":2,"1389":4,"1394":1016,"1395":1455,"1396":3,"1397":3,"1398":2,"1399":4,"1400":5,"1401":447,"1402":4,"1403":23,"1404":3,"1405":11,"1406":3,"1407":1,"1408":3,"1409":6,"1410":1,"1411":5,"1412":8,"1413":14,"1414":942,"1415":279,"1416":881,"1417":5,"1418":4,"1419":12,"1420":2,"1421":8,"1422":10,"1423":10,"1424":11,"1425":7,"1426":194,"1427":13,"1428":10,"1429":19,"1430":45,"1431":9,"1432":23,"1433":13,"1434":23,"1435":3865,"1436":131,"1437":169,"1438":3,"1439":3,"1440":23,"1441":20,"1442":31,"1443":7,"1444":21,"1445":1,"1446":13,"1448":10,"1449":1,"1450":6,"1451":4,"1452":14,"1453":12,"1454":34,"1455":4,"1456":2,"1457":1,"1458":30,"1459":16,"1460":3,"1461":3,"1463":14,"1464":1,"1466":54,"1469":1,"1470":4,"1471":3,"1472":2,"1473":2,"1474":3,"1475":4,"1476":1,"1477":2,"1478":1,"1479":1,"1480":9,"1484":7,"1485":65,"1486":82,"1487":93,"1488":54,"1489":34,"1490":5,"1491":48,"1493":1,"1495":250,"1496":2,"1497":3,"1499":4,"1501":1,"1502":13,"1503":1,"1508":4,"1511":1,"1512":1,"1517":1,"1519":3,"1520":1,"1521":4,"1522":3,"1523":4,"1524":6,"1525":114,"1526":4,"1528":2,"1530":18,"1531":2,"1533":10,"1534":14,"1568":1,"1569":1,"1572":2,"1573":1,"1575":2,"1576":85,"1577":2,"1579":1,"1581":24,"1582":5,"1583":1,"1584":9,"1585":11,"1586":4,"1587":3,"1588":1,"1590":79,"1591":1,"1592":2,"1593":3,"1595":8,"1596":1,"1598":2,"1599":8,"1600":1,"1601":7,"1604":2,"1605":1,"1610":17,"1614":4,"1618":9,"1619":1,"1626":11,"1630":1,"1638":1,"1641":1,"1642":2,"1645":8,"1647":11,"1648":9,"1649":12,"1650":4,"1651":2,"1652":18,"1653":3,"1654":4,"1655":4,"1657":8,"1658":5,"1659":17,"1660":9,"1663":1,"1672":3,"1673":2,"1674":6,"1675":2,"1676":4,"1677":3,"1678":2,"1680":5,"1681":3,"1683":2,"1684":12,"1685":10,"1686":2,"1687":1,"1688":1,"1689":3,"1690":4,"1691":3,"1692":4,"1693":4,"1694":3,"1698":1,"1700":2,"1701":6,"1702":3,"1703":1,"1704":4,"1705":2,"1706":1,"1707":1,"1709":1,"1710":1,"1711":1,"1712":1,"1713":1,"1716":2,"1717":1,"1721":2,"1723":2,"1724":1,"1725":10,"1726":13,"1727":3,"1728":2,"1729":2,"1730":2,"1731":2,"1735":5,"1738":2,"1739":1,"1740":1,"1744":1,"1747":1,"1755":2,"1756":1,"1760":5,"1761":1,"1762":1,"1763":17,"1764":33,"1769":2,"1772":3,"1774":2,"1777":2,"1779":1,"1780":2,"1781":3,"1783":3,"1784":3,"1786":4,"1787":1,"1789":3,"1792":4,"1793":1,"1798":2,"1800":1,"1801":4,"1802":2,"1804":2,"1805":1,"1806":3,"1807":1,"1809":4,"1811":3,"1814":8,"1815":1,"1816":3,"1819":1,"1825":1,"1826":4,"1827":2,"1828":1,"1833":4,"1837":2,"1842":1,"1845":4,"1846":1,"1850":1,"1852":3,"1853":1,"1859":1,"1861":4,"1864":2,"1867":4,"1876":4,"1878":1,"1879":1,"1883":2,"1885":3,"1888":1,"1890":1,"1891":4,"1893":1,"1894":3,"1895":1,"1896":1,"1898":3,"1901":4,"1902":1,"1904":4,"1905":1,"1909":1,"1911":3,"1912":1,"1918":1,"1921":2,"1923":4,"1926":2,"1928":2,"1929":5,"1930":2,"1934":1,"1938":1,"1940":4,"1941":1,"1946":3,"1949":3,"1950":1,"1952":1,"1954":1,"1958":3,"1960":1,"1961":1,"1962":2,"1964":4,"1967":5,"1969":2,"1971":4,"1974":2,"1977":1,"1978":4,"1980":1,"1984":4,"1990":4,"1993":8,"1994":8,"1996":5,"1998":1,"1999":19,"2000":1,"2001":4,"2002":2,"2003":7,"2005":13,"2007":1,"2012":9,"2013":6,"2014":1,"2015":1,"2021":1,"2023":3,"2027":4,"2029":1,"2037":3,"2038":2,"2040":5,"2041":5,"2042":3,"2043":18,"2045":11,"2046":3,"2048":4,"2049":2,"2051":1,"2054":7,"2055":1,"2056":3,"2058":2,"2059":4,"2060":1,"2061":1,"2062":1,"2063":3,"2065":5,"2066":1,"2068":2,"2069":1,"2070":2,"2074":2,"2076":2,"2079":3,"2080":1,"2082":14,"2084":7,"2085":4,"2086":1,"2088":1,"2096":2,"2097":5,"2098":1,"2099":3,"2100":8,"2104":1,"2105":1,"2106":1,"2107":5,"2108":1,"2109":2,"2110":6,"2111":2,"2112":1,"2113":2,"2114":4,"2115":1,"2116":1,"2118":2,"2119":5,"2121":3,"2122":6,"2123":1,"2126":5,"2127":3,"2130":10,"2132":4,"2133":2,"2135":1,"2136":1,"2137":1,"2138":3,"2139":1,"2141":1,"2145":4,"2148":3,"2149":6,"2150":1,"2151":1,"2152":7,"2153":2,"2155":12,"2156":5,"2157":9,"2159":13,"2160":1,"2162":1,"2163":1,"2165":1,"2167":3,"2168":6,"2170":1,"2171":2,"2175":3,"2176":4,"2177":3,"2178":5,"2179":6,"2181":1,"2188":1,"2189":1,"2190":1,"2192":5,"2194":1,"2201":2,"2202":5,"2203":4,"2208":1,"2209":1,"2210":3,"2220":1,"2225":2,"2226":23,"2227":3,"2228":1,"2230":3,"2231":12,"2233":5,"2235":1,"2239":1,"2241":1,"2243":2,"2244":1}}],["mx",{"3":{"265":1,"1310":1,"1416":1,"1437":1}}],["mtp",{"0":{"2057":1},"3":{"1192":1,"1213":2,"1215":2,"1455":1,"1485":6,"1492":1,"1493":1,"1610":1,"1653":1,"1684":1,"1685":1,"2057":4,"2059":1}}],["mt",{"2":{"146":1,"147":1},"3":{"146":1,"147":1,"150":1,"1435":1,"1490":1}}],["mfa",{"2":{"909":1,"1063":1,"2196":1,"2202":1},"3":{"0":1,"126":1,"905":1,"909":1,"1059":1,"1063":1,"1270":5,"1292":1,"1299":11,"1436":2,"2196":4,"2202":1}}],["m",{"2":{"2076":1},"3":{"0":1,"69":1,"137":1,"138":1,"649":2,"758":1,"980":2,"1259":2,"1270":1,"1323":6,"1435":2,"1523":2,"1525":2,"1528":1,"1529":1,"1530":8,"1531":4,"1533":1,"1534":1,"1576":1,"1580":5,"1584":4,"1585":1,"1590":1,"1594":1,"1595":3,"1599":1,"1726":1,"1729":1,"2076":2,"2219":1}}],["myprofile",{"3":{"1414":1}}],["mypointstests",{"3":{"1199":1,"1207":2,"1395":2,"1437":2}}],["mypoints",{"3":{"1199":3,"1203":1,"1207":2,"1395":26}}],["mypointsdto",{"3":{"1199":7,"1203":1,"1207":2,"1395":12}}],["mypointspage",{"3":{"1199":3,"1207":2}}],["myvotebypoll",{"3":{"1401":1}}],["myvoteoptionid",{"3":{"1395":1,"1398":1,"1401":9}}],["mymoderatedquestions",{"3":{"1401":1}}],["myupvote",{"3":{"1400":1,"1401":9}}],["mysql",{"3":{"2218":1,"2219":1}}],["mysteriously",{"3":{"2125":1}}],["mystery",{"3":{"1314":1}}],["myself",{"0":{"2061":1},"3":{"1435":2}}],["myscheduleroute",{"3":{"1415":2}}],["myschedulesessionids",{"3":{"1394":3}}],["myschedule",{"3":{"1394":1}}],["myapp",{"3":{"1310":1}}],["myrolenames",{"3":{"1299":1}}],["myordersquery",{"3":{"1199":2,"1207":1}}],["mybadgetests",{"3":{"1199":1,"1207":2,"1395":1,"1437":2}}],["mybadge",{"3":{"1199":2,"1203":1,"1207":2,"1323":1,"1395":23,"1437":1}}],["mybadgedto",{"3":{"1199":7,"1203":1,"1207":2,"1395":14}}],["mybadgepage",{"3":{"1199":3,"1207":2}}],["my",{"3":{"0":1,"317":1,"649":1,"690":3,"694":1,"905":1,"907":1,"1194":1,"1247":1,"1267":2,"1289":1,"1299":2,"1308":3,"1310":4,"1323":2,"1358":3,"1384":1,"1390":3,"1394":16,"1395":24,"1401":3,"1413":1,"1414":4,"1415":2,"1416":1,"1435":2,"1440":1,"1486":1,"1607":1,"1626":3,"1630":2,"1666":2,"1727":1,"1749":3,"1758":1,"1767":1,"1772":3,"1783":1,"1793":1,"1800":1,"1879":1,"1919":2,"2002":1,"2212":1,"2215":1,"2237":1,"2241":1,"2243":1}}],["msg",{"3":{"1454":1}}],["mssqllocaldb",{"3":{"1489":1}}],["mssql",{"3":{"1213":1,"1338":1,"1435":1,"1440":1,"1613":1,"1615":1,"1616":1,"1617":1,"1774":1}}],["ms",{"3":{"0":3,"556":1,"861":7,"862":1,"881":2,"1212":1,"1237":1,"1270":1,"1308":1,"1322":2,"1323":2,"1346":1,"1394":1,"1416":1,"1428":1,"1432":6,"1435":18,"1452":1,"1473":1,"1487":3,"1576":1,"1706":4}}],["msbuild",{"3":{"0":2,"133":1,"135":1,"138":1,"147":1,"266":1,"570":1,"869":1,"1196":1,"1415":1,"1430":2,"1435":2,"1454":1,"1458":2,"1488":1,"1490":1,"1503":1,"1576":2,"1590":1,"1649":1,"1670":1,"1781":1,"2040":2,"2047":1,"2048":1,"2099":2,"2105":2,"2107":1,"2229":1}}],["mp",{"3":{"0":1,"1323":2}}],["mb",{"3":{"0":3,"440":1,"442":1,"875":3,"1116":1,"1117":1,"1118":1,"1212":1,"1322":4,"1346":1,"1349":1,"1372":1,"1379":2,"1388":3,"1394":2,"1410":1,"1413":1,"1414":5,"1416":1,"1442":2,"1446":4,"1450":2,"1455":2,"1464":1,"1466":8,"1477":1,"1489":1,"1630":2,"1639":2,"1747":2,"1764":2,"2125":3,"2127":1,"2165":1}}],["muscle",{"3":{"1144":1}}],["mustcascadesoftdelete",{"3":{"1435":2}}],["mustquote",{"3":{"1310":2}}],["mustnotexceed",{"2":{"1074":1,"1488":2,"1490":1},"3":{"1074":1,"1435":4,"1488":2,"1490":1}}],["must",{"0":{"1290":1,"2188":1},"3":{"0":21,"4":1,"16":1,"17":1,"18":2,"19":1,"24":3,"25":1,"30":1,"37":1,"38":1,"39":1,"41":1,"42":1,"55":1,"62":2,"68":2,"70":1,"92":2,"95":1,"97":2,"98":3,"100":3,"102":2,"108":1,"109":1,"111":2,"117":1,"119":1,"120":1,"126":1,"134":1,"135":1,"149":1,"157":1,"160":1,"168":2,"176":1,"177":3,"178":1,"188":1,"194":1,"195":2,"196":1,"197":1,"198":1,"199":1,"201":1,"209":1,"213":2,"216":1,"217":1,"219":1,"224":1,"235":1,"243":1,"253":1,"255":1,"265":1,"272":1,"279":1,"280":1,"290":1,"302":3,"309":1,"317":2,"318":1,"320":1,"332":1,"333":1,"336":1,"343":1,"348":1,"349":1,"352":1,"358":2,"359":1,"361":2,"362":1,"363":1,"366":1,"380":1,"382":1,"388":1,"390":3,"401":1,"407":1,"412":1,"415":1,"419":1,"424":1,"428":1,"433":1,"434":1,"435":1,"441":1,"448":1,"450":1,"459":1,"465":1,"482":1,"490":1,"498":2,"501":1,"506":2,"509":1,"511":1,"517":3,"518":2,"520":1,"521":1,"527":1,"536":1,"538":2,"541":1,"544":1,"545":1,"549":3,"556":1,"564":1,"572":1,"583":1,"587":1,"590":2,"591":2,"601":1,"626":1,"632":2,"633":1,"645":1,"657":1,"659":1,"667":2,"674":1,"676":1,"681":1,"683":1,"690":1,"698":1,"707":3,"709":3,"716":1,"718":1,"724":1,"757":3,"759":1,"761":1,"762":2,"766":1,"769":1,"774":1,"775":1,"783":2,"790":1,"792":1,"799":1,"809":2,"813":1,"820":1,"821":1,"823":1,"827":2,"829":1,"832":1,"838":1,"840":1,"846":2,"855":1,"856":1,"861":1,"889":1,"897":2,"898":1,"905":1,"914":1,"916":1,"922":2,"926":2,"932":1,"946":1,"948":1,"964":1,"972":2,"976":1,"987":1,"989":1,"990":1,"996":1,"997":1,"1014":1,"1017":1,"1018":2,"1019":1,"1026":1,"1036":1,"1041":2,"1042":1,"1043":1,"1044":1,"1066":1,"1067":1,"1068":1,"1074":1,"1079":1,"1083":2,"1091":4,"1099":2,"1100":3,"1102":2,"1104":1,"1116":4,"1117":1,"1124":1,"1126":1,"1133":1,"1135":1,"1145":1,"1155":1,"1168":3,"1170":1,"1175":1,"1184":1,"1185":2,"1186":4,"1187":1,"1188":1,"1191":1,"1200":1,"1211":1,"1212":5,"1213":1,"1214":2,"1215":1,"1219":1,"1222":1,"1223":1,"1225":1,"1228":1,"1229":7,"1231":3,"1233":2,"1236":2,"1237":17,"1238":2,"1239":1,"1243":1,"1247":22,"1252":1,"1254":1,"1255":1,"1259":26,"1262":2,"1263":2,"1264":6,"1265":3,"1269":4,"1270":38,"1271":15,"1273":3,"1274":1,"1275":1,"1278":1,"1279":1,"1283":1,"1285":85,"1289":1,"1295":1,"1296":1,"1299":64,"1300":10,"1308":17,"1309":10,"1310":40,"1311":7,"1315":2,"1317":3,"1322":46,"1323":65,"1325":1,"1327":2,"1331":2,"1332":2,"1335":1,"1336":4,"1337":3,"1338":51,"1341":1,"1343":3,"1345":1,"1346":1,"1349":28,"1351":3,"1352":2,"1353":1,"1355":1,"1357":1,"1358":132,"1365":4,"1367":1,"1368":4,"1371":1,"1372":1,"1376":1,"1377":3,"1378":3,"1379":19,"1382":1,"1389":1,"1390":1,"1392":1,"1393":1,"1394":41,"1395":65,"1396":1,"1398":1,"1401":29,"1409":1,"1412":1,"1414":35,"1415":16,"1416":45,"1421":1,"1422":3,"1423":1,"1424":1,"1426":7,"1428":3,"1430":1,"1434":2,"1435":279,"1437":42,"1440":2,"1442":4,"1443":1,"1444":1,"1446":3,"1452":4,"1453":2,"1454":13,"1456":3,"1459":1,"1464":2,"1466":12,"1471":1,"1472":1,"1473":2,"1474":1,"1475":3,"1476":1,"1477":1,"1478":1,"1479":1,"1480":5,"1485":3,"1486":1,"1487":4,"1488":10,"1490":1,"1491":1,"1495":1,"1533":1,"1541":1,"1543":1,"1566":1,"1568":1,"1569":1,"1574":1,"1576":3,"1581":2,"1584":2,"1588":1,"1590":2,"1595":2,"1610":1,"1612":1,"1629":7,"1630":17,"1631":9,"1634":12,"1637":1,"1638":5,"1639":5,"1640":8,"1647":1,"1650":1,"1652":3,"1655":1,"1656":1,"1664":1,"1674":1,"1684":1,"1685":4,"1699":1,"1723":3,"1727":1,"1747":1,"1748":2,"1763":6,"1764":10,"1766":25,"1780":1,"1781":1,"1789":1,"1814":1,"1820":1,"1822":1,"1823":2,"1825":1,"1826":1,"1827":2,"1832":1,"1844":1,"1852":1,"1875":1,"1882":1,"1884":1,"1897":4,"1905":1,"1908":1,"1909":1,"1910":1,"1914":1,"1915":1,"1917":2,"1918":2,"1921":1,"1922":2,"1923":2,"1932":1,"1935":1,"1941":1,"1942":1,"1945":1,"1946":1,"1947":2,"1948":1,"1949":3,"1959":1,"1966":2,"1970":1,"1973":1,"1980":1,"1993":1,"1995":1,"1996":1,"1999":1,"2000":4,"2001":1,"2006":1,"2012":1,"2020":1,"2021":1,"2024":4,"2026":2,"2038":1,"2041":1,"2042":2,"2046":1,"2047":1,"2048":1,"2057":2,"2067":2,"2084":1,"2092":1,"2094":2,"2103":2,"2104":1,"2106":1,"2117":1,"2119":1,"2121":1,"2126":1,"2131":1,"2137":1,"2140":1,"2141":3,"2142":1,"2143":1,"2146":1,"2148":1,"2149":1,"2156":1,"2157":2,"2166":3,"2167":2,"2168":1,"2176":1,"2177":1,"2182":3,"2184":1,"2191":1,"2192":1,"2196":1}}],["much",{"3":{"235":1,"384":1,"490":1,"491":1,"492":1,"493":1,"494":1,"625":1,"749":1,"815":1,"819":1,"869":1,"897":1,"1033":1,"1044":1,"1243":1,"1247":1,"1259":1,"1270":2,"1285":4,"1289":1,"1294":1,"1322":1,"1323":7,"1338":4,"1347":1,"1349":1,"1358":2,"1367":1,"1377":1,"1394":2,"1395":8,"1401":1,"1414":1,"1416":2,"1428":1,"1430":1,"1435":4,"1487":2,"1691":1,"1725":1,"1783":1,"1817":1,"1945":1,"1965":1,"1998":1,"2001":1,"2028":1,"2063":1,"2085":1,"2086":1,"2157":1,"2190":1,"2191":1,"2192":1}}],["mute",{"3":{"1435":2}}],["muted",{"3":{"590":1,"2074":1}}],["mutually",{"3":{"667":1,"1191":1,"1196":1,"1299":1,"1322":1,"1394":1,"1395":1,"1426":1,"1748":1,"1764":1}}],["mutual",{"1":{"993":1,"994":1,"995":1,"996":1,"997":1,"998":1,"999":1,"1000":1},"3":{"0":1,"55":1,"135":1,"159":2,"161":1,"993":1,"996":1,"1212":1,"1237":8,"1260":1,"1269":1,"1270":2,"1285":1,"1299":1,"1300":2,"1311":1,"1349":1,"1435":1,"1437":1,"2029":1,"2231":1}}],["mutant",{"3":{"2058":1}}],["mutability",{"3":{"1338":1,"1395":2}}],["mutablepropertyviolations",{"3":{"1435":1}}],["mutableoptions",{"3":{"1199":2,"1207":1}}],["mutablefixtureid",{"3":{"1199":2,"1207":1,"1435":9}}],["mutabletenantcontext",{"3":{"1199":2,"1207":1}}],["mutableauthenticationstateprovider",{"2":{"954":1,"1487":1,"1490":1},"3":{"954":1,"1199":2,"1207":1,"1435":3,"1487":1,"1490":1}}],["mutable",{"3":{"0":1,"340":1,"341":1,"342":1,"343":1,"373":1,"629":1,"881":1,"884":1,"954":1,"962":1,"963":1,"1050":1,"1170":1,"1237":1,"1280":1,"1285":4,"1299":8,"1300":1,"1309":3,"1310":1,"1322":3,"1323":8,"1338":8,"1349":6,"1358":5,"1379":1,"1394":5,"1395":3,"1401":5,"1414":1,"1416":1,"1426":1,"1431":1,"1435":18,"1451":1,"1458":1,"1474":1,"1487":2,"1488":1,"1490":2,"1524":1,"1525":2,"1541":1,"1555":2,"1571":1,"1576":1,"1590":1,"1629":1,"1631":2,"1638":1,"1659":1,"1670":1,"1685":1,"1868":1,"1874":1,"1876":1,"2051":1}}],["mutator",{"3":{"1290":1,"1299":2,"1309":1,"1342":1,"1344":1,"1358":15,"1395":3,"1401":1}}],["mutators",{"3":{"0":1,"109":1,"690":1,"782":1,"1308":1,"1309":4,"1310":2,"1343":1,"1349":1,"1358":3,"1395":2,"1630":1}}],["mutating",{"0":{"1410":1},"3":{"80":1,"258":1,"318":1,"359":1,"388":2,"390":1,"727":1,"905":1,"1247":1,"1270":2,"1282":1,"1285":4,"1292":1,"1300":1,"1308":1,"1310":3,"1322":1,"1323":2,"1338":1,"1358":4,"1374":1,"1379":3,"1394":2,"1395":1,"1401":1,"1414":8,"1428":1,"1435":1,"1459":2,"1555":1,"1663":1,"1700":1,"1922":1,"1994":1,"2141":1}}],["mutationcontexttests",{"3":{"926":1,"1199":1,"1207":1,"1270":1}}],["mutationcontexthandlertests",{"2":{"926":1},"3":{"926":1,"1199":1,"1207":1}}],["mutationcontext",{"3":{"0":1,"926":4,"1199":15,"1203":1,"1207":2,"1260":1,"1265":5,"1270":16,"1351":1,"1358":9,"1410":1,"1414":5,"1665":1}}],["mutation",{"3":{"0":1,"120":1,"241":1,"242":1,"243":1,"244":1,"255":2,"318":3,"322":1,"341":1,"344":1,"386":1,"387":1,"390":3,"715":1,"721":1,"792":1,"905":1,"920":1,"921":2,"922":3,"926":10,"988":1,"1153":1,"1167":1,"1168":1,"1169":1,"1229":1,"1237":3,"1259":2,"1263":1,"1265":6,"1270":28,"1278":1,"1285":14,"1289":1,"1299":5,"1300":5,"1310":7,"1322":3,"1323":3,"1338":1,"1339":1,"1349":10,"1351":2,"1353":1,"1358":51,"1368":1,"1378":1,"1379":15,"1394":9,"1395":7,"1401":9,"1414":10,"1416":2,"1426":1,"1430":1,"1435":7,"1437":1,"1555":1,"1575":2,"1576":2,"1590":1,"1631":1,"1650":1,"1684":1,"1685":1,"1700":1,"1747":2,"1748":1,"1749":1,"1752":1,"1760":1,"1764":2,"1820":1,"1917":2,"1921":1,"1922":2,"1940":1,"1994":1,"2058":2,"2078":1,"2162":2}}],["mutations",{"3":{"0":2,"24":1,"193":1,"201":2,"320":1,"926":1,"1155":1,"1258":1,"1259":4,"1270":3,"1285":1,"1305":1,"1308":2,"1322":1,"1323":2,"1345":1,"1349":2,"1358":4,"1379":8,"1394":2,"1395":1,"1401":2,"1435":1,"1454":2,"1464":1,"1598":1,"1630":1,"1633":2,"1637":1,"1650":1,"1684":1,"1685":2,"1699":1,"1726":1,"1747":1,"1768":1,"1909":1,"1995":1}}],["mutatefirstmultimemberevent",{"3":{"1435":1}}],["mutatetrackedasync",{"3":{"1395":3}}],["mutatechildasync",{"3":{"1394":2}}],["mutatecoreasync",{"2":{"523":1,"921":1},"3":{"0":1,"523":1,"921":1,"926":1,"1270":4,"1358":5,"1401":1}}],["mutateattemptscopetests",{"2":{"926":1},"3":{"926":1,"1199":1,"1207":1}}],["mutateasync",{"3":{"0":1,"922":1,"926":6,"1265":1,"1270":12,"1351":1,"1358":35,"1388":1,"1394":6,"1398":1,"1401":6}}],["mutated",{"3":{"914":1,"922":1,"926":2,"1091":1,"1150":1,"1155":1,"1229":1,"1237":1,"1265":1,"1270":6,"1285":4,"1299":2,"1308":1,"1310":1,"1322":2,"1338":2,"1349":9,"1358":9,"1394":4,"1395":5,"1414":2,"1416":1,"1435":4,"1452":1,"1555":1,"1646":1,"2173":1}}],["mutateentityhandlercore",{"2":{"523":2,"926":1,"1100":1,"1265":1},"3":{"523":2,"926":1,"1100":1,"1199":3,"1203":1,"1207":1,"1212":1,"1260":1,"1265":1,"1270":9,"1358":6,"1401":1,"1414":1,"1495":2,"1665":1}}],["mutateentityhandlerbasetests",{"3":{"1199":1,"1207":1,"1270":3}}],["mutateentityhandlerbase",{"2":{"344":1,"518":1,"523":1,"922":1,"926":1,"1100":1,"1265":1,"1398":1,"1870":1,"2162":1},"3":{"0":3,"341":1,"344":2,"345":1,"444":1,"518":1,"523":3,"921":2,"922":2,"923":1,"926":4,"927":1,"1100":2,"1199":25,"1203":1,"1207":6,"1260":1,"1265":17,"1270":17,"1310":2,"1351":1,"1354":1,"1358":91,"1398":1,"1401":16,"1414":5,"1495":2,"1870":1,"2162":1}}],["mutateentitypayloadhandlerbase",{"3":{"0":1,"926":1,"1199":6,"1203":1,"1207":1,"1260":1,"1265":1,"1270":3,"1351":1,"1358":3,"1410":1,"1414":2}}],["mutate",{"3":{"0":3,"827":1,"920":3,"921":1,"926":8,"927":1,"1068":1,"1153":1,"1212":1,"1237":1,"1247":3,"1260":1,"1261":1,"1265":3,"1270":9,"1285":6,"1299":1,"1300":1,"1308":1,"1323":1,"1341":1,"1342":1,"1349":2,"1358":25,"1368":1,"1379":2,"1394":5,"1395":1,"1398":1,"1401":3,"1414":5,"1422":1,"1435":5,"1542":1,"1650":2,"1665":1,"1807":1,"1819":1,"1890":1,"1936":1,"2229":1}}],["mutates",{"3":{"0":1,"17":1,"341":1,"766":1,"767":1,"1212":1,"1237":1,"1247":1,"1259":2,"1270":1,"1285":4,"1308":1,"1310":3,"1322":2,"1323":1,"1349":4,"1351":1,"1358":8,"1368":1,"1379":2,"1394":2,"1395":1,"1401":5,"1414":1,"1435":5,"1454":1,"1456":1,"1457":1,"1459":1,"1590":1,"1631":1,"1910":1,"2090":2}}],["mudnumericfield",{"3":{"1701":1}}],["mudnavgroup",{"3":{"1323":1}}],["mudpagination",{"3":{"1590":1}}],["mudpopoverprovider",{"3":{"1435":2,"1652":1}}],["mudprogresscircular",{"3":{"1323":1}}],["mudproviderhandles",{"3":{"1199":3,"1207":1,"1431":1,"1435":3,"1487":1}}],["mudmenu",{"3":{"1435":1}}],["mudmessagebox",{"3":{"1414":2,"1435":1,"1487":1,"1525":2}}],["mudrating",{"3":{"1394":2,"1395":1}}],["mudcheckbox",{"3":{"1435":2}}],["mudchip",{"3":{"1394":2}}],["mudcard",{"3":{"1394":2}}],["muddialogprovider",{"3":{"1435":2,"1652":1}}],["muddivider",{"3":{"1394":1}}],["muddatagridrowselector",{"3":{"1435":1}}],["muddatagrid",{"2":{"1714":1,"2071":1,"2078":1},"3":{"0":1,"881":1,"883":1,"1323":14,"1382":1,"1394":6,"1395":1,"1414":2,"1432":1,"1435":6,"1525":1,"1714":1,"1740":1,"1749":7,"2071":2,"2078":1}}],["mudimage",{"3":{"1394":2}}],["muditem",{"3":{"1394":1}}],["mudicon",{"3":{"1323":1}}],["mudiconbutton",{"3":{"1323":2}}],["mudbutton",{"3":{"1323":1,"1435":3}}],["mudblazor",{"0":{"1431":1},"1":{"1323":1},"2":{"265":1,"1697":1},"3":{"0":4,"213":1,"214":1,"215":1,"216":2,"263":1,"265":8,"267":3,"268":1,"556":2,"558":1,"618":2,"620":1,"647":1,"649":3,"650":1,"651":2,"940":1,"954":5,"956":2,"960":1,"1125":1,"1192":1,"1202":1,"1203":1,"1212":1,"1213":1,"1322":1,"1323":128,"1335":1,"1338":4,"1382":1,"1394":51,"1395":11,"1401":2,"1414":4,"1415":5,"1427":1,"1431":3,"1432":2,"1435":25,"1437":3,"1442":1,"1487":7,"1488":1,"1490":2,"1495":2,"1525":2,"1533":1,"1535":1,"1556":1,"1576":8,"1585":1,"1590":4,"1599":1,"1646":1,"1652":2,"1659":1,"1668":3,"1669":1,"1670":1,"1680":1,"1686":1,"1697":1,"1711":2,"1740":1,"1762":1,"1772":1,"2042":1,"2054":1,"2056":1,"2071":3,"2072":1,"2074":2,"2078":4,"2084":3,"2088":1,"2146":1,"2148":1,"2149":3,"2151":1,"2220":1,"2226":1}}],["mudlink",{"3":{"1323":1,"1394":5,"1416":1,"1435":1}}],["mudlocalizer",{"2":{"267":1},"3":{"265":1,"267":1,"1323":22,"2084":1}}],["mudswitch",{"3":{"1395":2}}],["mudsimpletable",{"2":{"1530":1},"3":{"1394":2,"1395":2,"1525":1,"1530":1}}],["mudsnackbarprovider",{"3":{"1323":1,"1435":2,"1652":1}}],["mudselect",{"3":{"618":1,"1394":1,"1395":1,"1487":1}}],["mudavatar",{"3":{"1394":2,"1414":1,"1490":1}}],["mudautocomplete",{"3":{"1394":2}}],["mudappdialogservice",{"2":{"649":1},"3":{"649":1,"954":1,"1199":2,"1203":1,"1207":2,"1323":22}}],["mudappbar",{"3":{"273":1}}],["mudalert",{"3":{"109":1,"1323":1,"1394":1,"1395":2,"1435":2}}],["mudform",{"2":{"128":1,"2044":1},"3":{"109":1,"117":1,"128":1,"1323":7,"1383":1,"1394":25,"1395":3,"1414":1,"1435":3,"1488":1,"1525":1,"1534":1,"1590":1,"2044":1}}],["mudtext",{"3":{"1323":1}}],["mudtextfield",{"3":{"1323":1,"1394":2,"1395":2,"1401":2}}],["mudtoastservicetests",{"3":{"1199":1,"1207":2,"1323":3}}],["mudtoastservice",{"3":{"649":1,"954":1,"1199":3,"1203":1,"1207":2,"1323":18,"1495":1}}],["mudtable",{"2":{"554":1},"3":{"554":1,"556":1,"1323":1}}],["mudtablepager",{"2":{"651":1,"2056":1},"3":{"0":1,"618":2,"651":1,"1435":2,"1487":2,"2056":1}}],["mudtranslations",{"2":{"267":1,"268":1,"2084":1},"3":{"265":2,"267":1,"268":2,"1199":2,"1203":1,"1207":2,"1323":10,"1435":1,"1495":1,"1576":2,"2084":3}}],["mudtheme",{"2":{"2074":1},"3":{"0":1,"273":2,"1323":6,"2074":2,"2082":1}}],["mudthemeprovider",{"2":{"272":1,"273":1,"2074":1,"2085":1},"3":{"0":1,"272":1,"273":1,"1212":1,"1323":4,"1435":1,"1576":1,"1652":1,"2074":1,"2085":1,"2231":1}}],["mud",{"3":{"0":2,"273":1,"275":1,"954":1,"1323":6,"1435":7,"1652":1,"2074":1}}],["multidimensional",{"3":{"2219":1}}],["multidevicerefreshsessionsandauditdeletioncolumns",{"3":{"1209":2}}],["multithreaded",{"3":{"2219":1}}],["multiline",{"3":{"1395":1,"1454":1}}],["multifactormethodrecoverycode",{"3":{"1299":1}}],["multifactormethodtotp",{"3":{"1299":1}}],["multifactormethodfor",{"3":{"1299":1}}],["multifactormethod",{"3":{"1299":4}}],["multifactor",{"2":{"2196":1},"3":{"1299":7,"2196":1}}],["multifactorrequired",{"2":{"1263":1},"3":{"126":1,"1263":1,"1270":1,"1820":1}}],["multihandlereventhandler2",{"3":{"1199":2,"1207":1}}],["multihandlereventhandler1",{"3":{"1199":2,"1207":1}}],["multihandlerevent",{"3":{"1199":4,"1207":1}}],["multipartformdatacontent",{"3":{"1394":2,"1414":2}}],["multipart",{"3":{"1116":1,"1346":1,"1349":2,"1388":1,"1394":4,"1410":1,"1414":3,"1446":1,"1630":2,"1639":1,"1747":1,"1764":1}}],["multiplying",{"3":{"1247":1,"1322":1,"1395":1}}],["multiply",{"3":{"657":1,"685":1,"716":1,"819":1,"1229":1,"1237":1,"1242":1,"1247":4,"1285":2,"1338":2,"1358":1,"1448":1,"1862":1,"1924":1,"2031":1}}],["multiplicity",{"3":{"1395":1}}],["multiplication",{"3":{"0":1,"178":1,"827":1,"1338":1}}],["multipliers",{"3":{"1338":1}}],["multiplier",{"3":{"714":1,"742":1,"1338":2,"2012":1}}],["multiplies",{"0":{"2034":1},"3":{"509":1,"682":1,"700":1,"740":1,"783":1,"1232":1,"1237":1,"1285":1,"1299":1,"1336":1,"1401":1,"1416":1,"2026":1,"2034":1,"2036":1,"2037":1}}],["multiplied",{"3":{"19":1,"23":1,"1126":1,"1256":1,"1259":2,"1275":1,"1338":2,"1394":1,"1415":1,"1840":1,"2026":1,"2037":1}}],["multiplexed",{"3":{"1328":1,"1338":1,"1442":1}}],["multiplexer",{"3":{"0":2,"155":1,"159":1,"178":1,"243":2,"245":1,"257":1,"260":1,"261":1,"733":1,"996":1,"1300":11,"1322":1,"1331":1,"1338":8,"1437":1,"1442":1,"1576":1,"1665":1,"1667":1,"1908":1,"1910":1,"1922":3}}],["multiple",{"1":{"163":1,"164":1,"165":1,"166":1,"167":1,"168":1,"169":1,"170":1,"171":1},"3":{"0":1,"10":1,"46":1,"135":1,"163":1,"839":1,"1085":1,"1237":1,"1247":1,"1259":1,"1270":2,"1285":3,"1299":2,"1310":2,"1311":1,"1322":2,"1323":1,"1346":1,"1349":1,"1358":3,"1368":1,"1394":2,"1396":1,"1416":1,"1428":1,"1435":4,"1463":1,"1487":1,"1536":1,"1543":1,"1554":1,"1555":1,"1629":2,"1638":3,"1667":1,"1766":1,"1772":1,"1791":1,"1939":1,"2026":1,"2219":1}}],["multisourceorderconfiguration",{"3":{"1199":1,"1207":1}}],["multisourceorder",{"3":{"1199":3,"1207":1}}],["multisourcecustomerconfiguration",{"3":{"1199":1,"1207":1}}],["multisourcecustomer",{"3":{"1199":4,"1207":1}}],["multisourcetestevent",{"3":{"1199":1,"1207":1}}],["multisourcesqliteintegrationtests",{"2":{"164":1,"2052":1,"2059":1},"3":{"164":1,"1198":1,"1199":2,"1207":8,"1285":1,"1495":1,"2052":1,"2059":1}}],["multisessionspeaker",{"3":{"1199":9,"1203":1,"1207":1,"1346":1,"1349":3,"1358":3,"1394":1}}],["multiselect",{"3":{"757":1,"1454":1}}],["multicast",{"3":{"0":1,"380":1,"382":1,"1323":2,"1399":2,"1401":1,"1944":1,"1948":1}}],["multi",{"1":{"262":1,"263":1,"264":1,"265":1,"266":1,"267":1,"268":1,"269":1,"695":1,"696":1,"697":1,"698":1,"699":1,"700":1,"701":1,"702":1,"703":1,"902":1,"903":1,"904":1,"905":1,"906":1,"907":1,"908":1,"909":1,"910":1},"3":{"0":9,"12":1,"30":1,"68":2,"84":1,"86":1,"149":1,"158":1,"166":1,"227":1,"243":1,"245":1,"258":1,"262":1,"264":1,"283":1,"340":1,"386":1,"390":1,"392":1,"413":1,"442":1,"462":1,"491":1,"501":1,"517":1,"535":1,"536":1,"555":1,"657":1,"659":1,"660":1,"670":1,"695":1,"706":1,"708":1,"748":1,"750":1,"809":2,"810":2,"827":1,"897":1,"902":1,"926":1,"988":1,"990":1,"997":1,"998":1,"1012":1,"1013":1,"1067":1,"1212":6,"1217":1,"1237":1,"1247":2,"1259":1,"1267":1,"1270":6,"1271":1,"1276":1,"1282":1,"1285":24,"1286":1,"1295":1,"1299":13,"1300":4,"1310":7,"1315":1,"1322":6,"1323":9,"1331":1,"1332":1,"1335":1,"1338":5,"1349":6,"1358":5,"1366":1,"1379":5,"1394":8,"1414":2,"1415":2,"1416":1,"1426":1,"1435":11,"1436":1,"1437":3,"1440":1,"1444":1,"1448":1,"1453":1,"1454":1,"1458":1,"1465":1,"1473":1,"1488":1,"1495":4,"1498":1,"1525":3,"1528":1,"1533":1,"1540":2,"1546":1,"1547":1,"1565":1,"1569":1,"1571":1,"1576":3,"1598":1,"1628":2,"1630":2,"1637":1,"1652":1,"1660":1,"1663":2,"1666":1,"1672":1,"1674":2,"1676":1,"1685":1,"1697":1,"1712":1,"1719":1,"1726":2,"1773":1,"1779":1,"1794":1,"1798":1,"1852":1,"1868":1,"1919":1,"1939":1,"1947":1,"1948":1,"1949":1,"1970":1,"1984":2,"2000":1,"2007":1,"2032":1,"2033":1,"2036":2,"2052":1,"2080":1,"2084":1,"2085":1,"2119":1,"2161":1,"2219":4,"2220":1,"2226":2,"2229":1,"2231":4,"2239":1}}],["mobility",{"3":{"1629":1}}],["mobileviewport",{"3":{"1435":1}}],["mobilepage",{"3":{"1323":1}}],["mobilepagesize",{"3":{"881":1,"1323":1,"1394":2}}],["mobilelist",{"3":{"1323":2}}],["mobiletoprowe2etests",{"3":{"1199":1,"1207":2,"1435":4}}],["mobiletotalitems",{"3":{"881":1,"1323":1,"1394":1}}],["mobilecardlist",{"2":{"2058":1},"3":{"2058":1}}],["mobilecardlisttests",{"3":{"1199":1,"1207":2}}],["mobilecurrentpage",{"3":{"881":1,"1323":3,"1394":1}}],["mobileitems",{"3":{"881":1,"1323":1,"1394":1}}],["mobileinfinitescrolllisttests",{"3":{"1199":1,"1207":2,"1323":1}}],["mobileinfinitescrolllist",{"2":{"112":1,"1382":1,"2058":1,"2072":1},"3":{"0":1,"109":2,"111":1,"112":2,"1199":18,"1203":1,"1207":2,"1323":12,"1382":1,"1394":12,"1395":1,"1525":1,"1576":1,"1590":1,"1599":1,"2058":1,"2072":1}}],["mobilenavkeyboarde2etests",{"2":{"616":1},"3":{"616":1,"618":1,"1199":1,"1207":2}}],["mobilereleaserunbook",{"0":{"1480":1},"3":{"422":1,"423":1,"424":1,"425":1,"426":2,"1415":1,"1469":1,"1480":16,"1481":1,"1482":1,"1525":1}}],["mobileredirectscheme",{"3":{"415":1,"1415":1,"1416":4}}],["mobile",{"0":{"1668":1},"1":{"417":1,"418":1,"419":1,"420":1,"421":1,"422":1,"423":1,"424":1,"425":1,"426":1,"427":1,"428":1,"429":1,"430":1},"3":{"0":2,"340":1,"387":1,"417":1,"419":1,"618":1,"649":1,"652":1,"881":3,"905":1,"907":1,"1124":1,"1212":1,"1299":3,"1310":3,"1322":1,"1323":28,"1349":1,"1379":1,"1382":2,"1384":1,"1394":37,"1395":9,"1401":2,"1410":1,"1413":1,"1414":5,"1415":2,"1416":5,"1435":7,"1437":5,"1458":1,"1470":1,"1471":1,"1480":1,"1486":1,"1489":1,"1495":1,"1523":2,"1525":5,"1530":3,"1533":2,"1558":2,"1576":2,"1590":2,"1594":1,"1595":1,"1596":1,"1598":1,"1628":1,"1639":1,"1640":4,"1666":1,"1668":3,"1672":1,"1674":1,"1711":2,"1712":1,"1713":4,"1873":1,"1975":2,"2071":1,"2072":4,"2078":3,"2115":1,"2120":1,"2231":1}}],["mouse",{"3":{"1605":1,"1739":1}}],["mount=type=secret",{"3":{"1444":1}}],["mounted",{"3":{"869":1,"1299":3,"1308":1,"1310":1,"1322":2,"1323":4,"1379":4,"1394":3,"1395":5,"1401":2,"1414":5,"1454":1,"1457":1,"2148":1}}],["mounts",{"3":{"217":1,"723":1,"871":1,"1310":1,"1323":2,"1379":1,"1394":2,"1395":1,"1415":1,"1431":1,"1665":1}}],["mount",{"3":{"0":1,"871":1,"1299":1,"1395":1,"1444":1,"1665":1,"2152":1}}],["mo",{"3":{"1435":2,"1533":1,"1670":1}}],["motion",{"3":{"1432":1,"1435":1}}],["motive",{"3":{"1270":1,"1299":2,"1394":1,"1401":1}}],["motivation",{"3":{"1322":2,"1338":1,"1358":1,"1394":1,"1414":1,"1435":1}}],["motivations",{"3":{"1237":1}}],["motivating",{"3":{"1285":1,"1323":2,"1395":1}}],["motivate",{"3":{"1259":1}}],["motivates",{"3":{"485":1,"1295":1,"1308":1,"1358":1}}],["motivated",{"3":{"0":1,"105":1,"318":1,"686":1,"829":1,"836":1,"1395":1,"1412":1,"1435":1,"1437":1,"1466":3,"2026":1}}],["motor",{"3":{"1416":2}}],["moot",{"3":{"915":1,"1394":1}}],["mojibake",{"3":{"741":1,"1310":1}}],["mornings",{"3":{"1456":1}}],["morning",{"3":{"689":1,"1341":1,"1349":1,"1358":2,"1363":1,"1368":1}}],["more",{"0":{"1334":1,"1375":1,"2050":1,"2199":1},"3":{"0":11,"7":1,"27":1,"33":1,"49":1,"81":1,"95":1,"96":1,"109":2,"111":2,"126":1,"130":1,"135":1,"137":1,"141":1,"145":1,"185":1,"194":1,"195":1,"243":1,"245":1,"259":1,"264":1,"340":1,"350":1,"361":2,"365":2,"382":1,"390":1,"391":1,"392":1,"413":1,"423":1,"461":1,"463":1,"470":1,"497":1,"518":1,"530":2,"549":2,"551":2,"566":1,"572":1,"590":1,"592":1,"597":1,"600":1,"601":1,"609":3,"633":1,"635":1,"673":1,"674":3,"690":1,"706":1,"708":1,"723":1,"725":1,"743":1,"758":1,"767":1,"782":1,"800":1,"827":1,"829":1,"833":1,"838":1,"849":1,"854":1,"856":2,"862":1,"871":2,"881":1,"905":1,"907":1,"920":1,"924":1,"926":2,"937":1,"941":3,"979":1,"980":1,"981":1,"982":2,"987":2,"995":1,"1004":1,"1006":1,"1013":1,"1019":2,"1020":1,"1034":1,"1036":1,"1051":2,"1061":1,"1067":1,"1069":2,"1085":1,"1089":1,"1091":1,"1100":1,"1101":1,"1116":2,"1134":1,"1142":1,"1143":1,"1145":1,"1154":1,"1156":2,"1163":1,"1168":1,"1170":1,"1219":1,"1229":6,"1237":3,"1243":1,"1252":1,"1254":1,"1255":1,"1256":1,"1258":1,"1259":7,"1265":1,"1268":1,"1270":11,"1273":1,"1275":2,"1277":1,"1278":1,"1281":1,"1283":1,"1284":3,"1285":21,"1293":1,"1299":12,"1300":3,"1308":2,"1310":12,"1313":1,"1316":1,"1319":1,"1321":1,"1322":8,"1323":23,"1325":1,"1327":1,"1338":10,"1346":1,"1347":1,"1349":7,"1351":2,"1358":33,"1363":1,"1368":2,"1374":1,"1378":1,"1379":5,"1394":20,"1395":21,"1400":1,"1401":2,"1404":1,"1406":1,"1408":2,"1414":11,"1415":1,"1416":5,"1420":1,"1426":3,"1430":2,"1435":33,"1437":3,"1440":3,"1441":3,"1442":1,"1443":1,"1454":2,"1455":2,"1456":1,"1459":1,"1464":2,"1466":8,"1474":1,"1477":1,"1487":2,"1488":2,"1491":3,"1495":3,"1502":1,"1507":1,"1533":1,"1630":1,"1636":3,"1637":1,"1639":1,"1650":1,"1653":1,"1661":1,"1672":1,"1682":1,"1692":1,"1694":1,"1723":1,"1726":1,"1763":6,"1764":1,"1777":1,"1780":1,"1792":1,"1793":1,"1798":1,"1801":1,"1804":2,"1805":1,"1809":1,"1810":2,"1825":1,"1827":1,"1832":1,"1835":1,"1844":1,"1850":2,"1851":1,"1869":1,"1873":1,"1879":1,"1891":1,"1893":3,"1901":1,"1906":1,"1908":1,"1914":1,"1922":1,"1924":1,"1928":1,"1929":2,"1941":1,"1944":1,"1948":1,"1957":1,"1961":1,"1964":1,"1970":2,"1971":1,"1972":1,"1974":1,"1985":1,"1988":1,"1990":1,"1994":2,"2000":1,"2009":1,"2034":1,"2041":1,"2042":1,"2045":1,"2046":2,"2048":1,"2049":1,"2054":2,"2056":2,"2059":3,"2061":1,"2062":1,"2066":1,"2072":2,"2084":1,"2085":1,"2103":1,"2120":1,"2122":1,"2125":1,"2130":2,"2134":1,"2135":1,"2144":1,"2154":1,"2155":3,"2157":1,"2167":2,"2179":1,"2182":1,"2186":1,"2191":1,"2192":1,"2196":1,"2198":1,"2200":1,"2201":1,"2212":1,"2237":1}}],["mockable",{"3":{"1285":2,"1323":3,"1414":1}}],["mocks",{"3":{"1199":28,"1207":17,"1285":2,"1322":2,"1323":1,"1358":2,"1394":1,"1395":1,"1435":2,"1437":1,"1490":1,"1684":1}}],["mocking",{"3":{"956":1,"1213":1,"1285":1,"1322":1,"1358":1,"1435":2,"1439":1,"1684":1,"2059":1}}],["mocked",{"3":{"914":1,"998":1,"1285":1,"1322":2,"1323":1,"1358":1,"1395":1,"1435":7,"1437":3,"1486":1,"1489":1,"1611":1,"1653":1,"1670":1,"1684":2,"2051":1}}],["mock",{"3":{"414":1,"1271":1,"1285":5,"1299":3,"1322":1,"1323":5,"1358":1,"1394":1,"1395":5,"1428":1,"1435":19,"1437":2,"1452":3,"1487":1,"1537":1,"1550":1,"1576":1,"1616":2,"1685":1,"2052":1}}],["moq",{"3":{"0":1,"954":1,"956":2,"960":1,"1213":1,"1368":2,"1435":5,"1487":1,"1685":2,"2057":1,"2218":1}}],["momentarily",{"3":{"1401":1,"1453":1}}],["moments",{"3":{"135":1,"1323":1}}],["moment",{"3":{"0":1,"20":1,"24":1,"54":1,"160":1,"351":1,"358":1,"499":2,"527":1,"539":1,"557":1,"565":1,"628":1,"692":1,"714":1,"741":1,"765":1,"767":1,"768":1,"798":1,"800":1,"828":1,"840":1,"889":1,"891":1,"971":1,"1090":1,"1153":1,"1160":1,"1212":1,"1223":1,"1237":1,"1247":2,"1249":1,"1254":1,"1256":1,"1259":4,"1265":1,"1270":2,"1271":2,"1275":1,"1285":10,"1299":5,"1300":1,"1308":1,"1309":1,"1310":5,"1311":2,"1317":1,"1322":6,"1323":6,"1329":1,"1338":4,"1349":2,"1357":3,"1358":11,"1368":1,"1389":1,"1392":1,"1394":2,"1395":7,"1401":2,"1404":1,"1414":5,"1415":1,"1416":6,"1435":9,"1437":2,"1464":2,"1466":1,"1488":2,"1533":1,"1576":1,"1584":1,"1652":1,"1653":1,"1707":1,"1791":1,"1804":1,"1835":1,"1857":1,"1862":1,"1886":1,"1887":1,"1898":1,"1912":1,"1917":1,"1928":1,"1949":1,"1959":1,"1971":1,"1973":1,"1977":1,"1978":1,"1984":1,"1987":1,"1997":1,"2005":1,"2039":1,"2044":1,"2062":1,"2080":1,"2115":1,"2125":3,"2126":1,"2128":1,"2134":1,"2139":1,"2145":1,"2149":1,"2165":1,"2166":1,"2170":1,"2193":2}}],["mon",{"3":{"1455":1,"1459":2,"1489":1}}],["monetary",{"3":{"1237":1}}],["moneytestdbcontext",{"3":{"1199":2,"1207":1}}],["moneytests",{"3":{"1199":1,"1207":2}}],["moneytowire",{"3":{"657":1}}],["moneyserializationtests",{"3":{"1199":1,"1207":2,"1437":2}}],["moneyextensionstests",{"3":{"1199":1,"1207":3,"1323":1}}],["moneyextensions",{"3":{"980":1,"1199":1,"1203":1,"1207":2,"1208":2,"1237":1,"1323":4,"1495":1,"1590":2,"1595":1,"1599":1}}],["moneyfromwire",{"3":{"657":2}}],["moneyv1",{"3":{"0":1,"657":1}}],["money",{"2":{"655":1,"658":1,"659":1,"660":3,"1025":1},"3":{"0":3,"535":1,"536":1,"655":2,"657":29,"658":5,"659":5,"660":7,"689":1,"767":1,"768":1,"793":1,"1020":1,"1025":2,"1026":1,"1168":1,"1199":13,"1203":1,"1207":2,"1208":1,"1212":1,"1230":1,"1234":13,"1237":42,"1247":1,"1281":2,"1285":11,"1310":1,"1323":13,"1394":1,"1421":1,"1434":1,"1437":3,"1454":4,"1491":1,"1495":2,"1540":1,"1576":1,"1584":2,"1590":3,"1595":1,"1599":3,"1661":2,"1748":1,"1763":6,"1764":2,"1765":1,"1769":1,"1809":4,"2160":1,"2162":1,"2168":1}}],["monadic",{"3":{"1220":1,"1229":1,"1804":1}}],["months",{"3":{"888":1,"1019":1,"1435":2,"1660":1,"1793":1,"1808":1,"1912":1,"1947":1,"1950":1,"2038":1,"2042":1,"2099":1,"2146":1,"2199":1}}],["month",{"3":{"827":1,"1020":2,"1118":1,"1270":2,"1394":1,"1456":1,"1464":1,"1466":8,"1473":1,"1475":1,"1708":1,"1957":1,"1972":1,"2042":1,"2056":1,"2157":1}}],["monthlybudgetamount",{"3":{"1466":1,"1590":1}}],["monthly",{"3":{"0":1,"591":1,"609":1,"625":1,"626":1,"627":1,"628":1,"765":2,"827":1,"870":1,"1020":1,"1116":1,"1435":1,"1445":1,"1454":2,"1455":1,"1457":1,"1459":2,"1466":8,"1468":2,"1473":2,"1474":1,"1483":1,"1523":1,"1524":1,"1525":1,"1530":1,"1534":1,"1588":2,"1590":1,"1594":1,"1595":3,"1597":1,"1677":1,"1708":1,"2033":2,"2046":1,"2210":1}}],["mondays",{"3":{"1474":1,"2033":1}}],["monday",{"3":{"625":3,"766":1,"1437":1,"1454":1,"1455":1,"1456":3,"1459":3,"1460":1,"1474":1,"1483":1,"1489":1,"1491":1,"1533":1,"1590":2,"1598":1}}],["monitors",{"3":{"1416":1}}],["monitored",{"3":{"1323":1,"1464":1,"1466":2}}],["monitoring",{"3":{"109":1,"827":1,"1310":1,"1416":1,"1466":2,"1473":1,"1926":1,"2219":1}}],["monitor",{"1":{"2153":1,"2154":1,"2155":1,"2156":1,"2157":1,"2158":1,"2159":1},"3":{"0":2,"395":1,"397":2,"401":1,"402":1,"790":1,"914":1,"997":1,"1068":1,"1213":1,"1332":4,"1338":11,"1378":1,"1441":2,"1442":3,"1449":1,"1466":6,"1473":2,"1525":1,"1576":1,"1667":1,"1669":1,"1778":1,"2009":3,"2013":1,"2152":2,"2153":3,"2155":2,"2156":1,"2159":2,"2168":1,"2208":1,"2214":1}}],["monotonic",{"3":{"1389":1,"1394":1,"1395":1,"1416":1,"1437":1}}],["monotonically",{"3":{"690":1,"1285":1,"1323":1,"1395":2,"1464":1}}],["monopolizing",{"3":{"176":1}}],["mono",{"3":{"0":1,"27":1,"39":1,"109":1,"1161":1,"1162":1,"1435":12}}],["monolithic",{"3":{"1358":1,"1395":1}}],["monoliths",{"3":{"1211":1,"2211":1}}],["monolith",{"0":{"1508":1,"1652":1,"1662":1,"1782":1,"1789":1},"1":{"56":1,"57":1,"58":1,"59":1,"60":1,"61":1,"62":1,"63":1,"64":1,"1787":1,"1788":1,"1789":1,"1790":1,"1791":1,"1792":1,"1846":1,"1847":1,"1848":1,"1849":1,"1850":1,"1851":1,"1852":1,"2086":1,"2087":1,"2088":1,"2089":1,"2090":1,"2091":1,"2092":1,"2093":1,"2094":1,"2095":1,"2096":1,"2097":1},"3":{"0":10,"20":1,"30":2,"31":3,"32":1,"47":1,"56":1,"58":2,"59":2,"60":1,"61":1,"62":2,"105":1,"196":1,"201":1,"224":1,"242":1,"243":2,"247":1,"282":1,"329":1,"396":1,"405":1,"448":1,"582":3,"583":1,"587":1,"639":1,"640":1,"664":1,"674":1,"734":1,"781":1,"783":1,"845":1,"931":1,"933":2,"1212":4,"1248":1,"1252":1,"1257":3,"1259":16,"1270":2,"1273":1,"1279":1,"1280":2,"1285":15,"1286":1,"1287":1,"1299":7,"1300":3,"1306":1,"1308":1,"1309":1,"1310":6,"1311":4,"1312":1,"1319":2,"1322":10,"1338":1,"1349":2,"1358":4,"1395":4,"1401":2,"1414":2,"1428":1,"1435":5,"1446":1,"1470":1,"1488":1,"1507":1,"1508":1,"1533":1,"1535":1,"1543":2,"1576":3,"1640":1,"1645":2,"1646":2,"1647":1,"1651":4,"1652":2,"1655":4,"1657":1,"1658":1,"1662":3,"1669":1,"1674":1,"1682":1,"1691":1,"1696":1,"1778":2,"1779":2,"1780":1,"1782":3,"1785":1,"1786":1,"1787":4,"1788":1,"1789":5,"1791":1,"1792":3,"1814":1,"1841":3,"1845":1,"1846":5,"1847":1,"1848":4,"1849":1,"1852":2,"1857":1,"1863":1,"1877":1,"1883":5,"1885":1,"1887":2,"1892":2,"1894":1,"1895":2,"1898":1,"1915":1,"1923":1,"1925":1,"1985":1,"1990":1,"2003":1,"2007":1,"2008":1,"2013":1,"2014":2,"2015":1,"2016":1,"2019":2,"2027":1,"2034":1,"2042":1,"2086":1,"2110":1,"2129":1,"2130":1,"2134":1,"2153":1,"2154":1,"2168":1,"2179":1,"2192":1,"2202":1,"2203":1,"2204":1,"2206":1,"2209":1,"2212":2,"2213":2,"2217":1,"2218":1,"2220":1,"2225":1,"2226":1,"2227":1,"2229":2,"2231":1,"2238":2,"2242":1}}],["movable",{"3":{"373":1,"1247":1,"1435":1}}],["moving",{"3":{"0":4,"33":1,"59":1,"164":1,"166":1,"286":1,"291":1,"295":1,"374":1,"591":1,"609":1,"610":1,"627":1,"631":1,"633":1,"635":1,"691":1,"838":1,"856":1,"891":1,"982":1,"1003":1,"1019":1,"1034":1,"1051":1,"1212":1,"1233":1,"1237":2,"1259":2,"1263":1,"1270":2,"1271":1,"1281":1,"1285":3,"1292":1,"1299":2,"1300":1,"1308":1,"1310":3,"1322":6,"1341":3,"1349":4,"1352":1,"1358":15,"1368":2,"1379":2,"1391":1,"1394":12,"1395":2,"1401":3,"1414":3,"1416":1,"1427":1,"1435":6,"1437":1,"1446":1,"1458":1,"1464":1,"1476":2,"1491":1,"1533":1,"1576":1,"1584":1,"1630":2,"1637":1,"1654":1,"1726":1,"1734":1,"1764":1,"1800":1,"1853":1,"1884":1,"1903":1,"1967":1,"1974":1,"1989":1,"2045":1,"2046":1,"2096":1,"2106":1,"2186":2,"2197":1}}],["movenext",{"3":{"1435":2}}],["movenextasync",{"3":{"1426":1,"1435":2}}],["movement",{"3":{"1145":1,"1495":7,"1574":2,"1581":1,"1584":1,"1768":1}}],["move",{"0":{"139":1,"140":1,"142":1,"1856":1},"3":{"0":10,"24":3,"46":1,"59":1,"102":1,"130":2,"135":1,"136":1,"138":1,"140":1,"141":1,"145":1,"150":2,"165":1,"177":1,"242":1,"254":1,"390":2,"407":1,"440":1,"488":1,"492":1,"493":1,"494":2,"545":1,"590":1,"635":1,"640":1,"676":1,"688":1,"690":1,"691":1,"698":1,"725":2,"726":1,"727":1,"741":1,"765":1,"767":1,"790":1,"791":1,"812":1,"819":1,"827":1,"838":1,"842":1,"849":3,"882":1,"899":1,"921":1,"926":1,"932":1,"954":1,"971":1,"980":1,"1004":3,"1005":1,"1006":3,"1012":3,"1014":1,"1019":1,"1041":1,"1053":1,"1075":1,"1077":1,"1085":1,"1134":1,"1137":1,"1176":1,"1212":1,"1214":1,"1229":1,"1233":1,"1237":1,"1249":1,"1250":2,"1259":4,"1270":2,"1271":1,"1275":1,"1285":4,"1296":1,"1299":5,"1300":1,"1308":2,"1309":1,"1310":5,"1311":1,"1322":7,"1323":12,"1338":5,"1342":1,"1349":2,"1352":1,"1358":8,"1368":3,"1379":1,"1384":1,"1390":1,"1391":1,"1394":8,"1395":11,"1398":1,"1401":7,"1414":6,"1415":1,"1416":2,"1435":18,"1437":1,"1440":1,"1441":1,"1454":1,"1460":1,"1466":2,"1481":1,"1488":3,"1491":2,"1495":8,"1499":1,"1523":1,"1524":1,"1576":2,"1577":1,"1606":1,"1617":1,"1662":1,"1664":1,"1672":1,"1674":1,"1685":1,"1686":1,"1696":1,"1700":1,"1734":1,"1740":1,"1748":1,"1764":2,"1801":1,"1816":1,"1823":1,"1825":1,"1856":1,"1859":1,"1860":1,"1861":1,"1865":1,"1892":1,"1913":1,"1922":1,"1923":1,"1949":1,"1952":1,"1954":1,"2000":1,"2010":1,"2031":1,"2046":1,"2069":2,"2074":1,"2105":1,"2123":1,"2127":1,"2151":1,"2166":1,"2186":1,"2193":1,"2231":1}}],["moves",{"0":{"539":1},"3":{"0":7,"25":1,"57":1,"126":2,"141":1,"147":1,"150":1,"170":1,"198":1,"203":1,"216":1,"228":1,"253":1,"255":2,"258":1,"311":1,"386":1,"387":1,"392":1,"407":1,"459":1,"464":1,"490":1,"535":1,"536":1,"540":2,"591":1,"592":1,"638":1,"651":1,"691":1,"727":1,"743":1,"781":1,"799":1,"803":1,"804":1,"805":1,"812":1,"818":2,"831":1,"840":2,"891":2,"916":1,"955":1,"959":1,"971":1,"974":1,"990":1,"1004":1,"1006":2,"1012":3,"1018":3,"1069":1,"1077":1,"1083":1,"1089":1,"1092":1,"1093":1,"1125":1,"1126":1,"1129":1,"1136":1,"1153":1,"1184":1,"1212":1,"1229":1,"1259":3,"1263":1,"1270":1,"1271":1,"1285":4,"1299":2,"1308":2,"1310":4,"1322":2,"1323":5,"1325":1,"1338":3,"1349":2,"1351":1,"1354":1,"1358":4,"1368":1,"1379":1,"1389":1,"1394":4,"1395":7,"1401":2,"1412":1,"1414":4,"1416":1,"1421":1,"1425":1,"1434":1,"1435":10,"1437":1,"1440":1,"1444":1,"1454":4,"1459":1,"1466":2,"1475":3,"1495":3,"1574":1,"1581":1,"1606":2,"1643":1,"1647":1,"1659":1,"1665":1,"1685":1,"1694":1,"1700":1,"1726":1,"1740":2,"1741":1,"1747":1,"1764":1,"1798":1,"1811":1,"1822":1,"1842":2,"1853":1,"1855":1,"1857":1,"1883":1,"1895":1,"1960":1,"2042":1,"2046":1,"2057":1,"2079":1,"2085":1,"2140":1,"2143":1,"2145":1,"2151":1,"2159":1,"2160":1,"2175":1,"2176":2,"2231":2}}],["moved",{"0":{"95":1},"3":{"0":5,"43":1,"57":1,"59":1,"82":1,"91":1,"93":1,"99":1,"104":3,"111":1,"125":1,"126":1,"130":3,"137":1,"138":5,"139":7,"140":1,"142":2,"161":1,"167":1,"168":1,"171":1,"179":1,"180":1,"201":1,"223":1,"238":1,"241":1,"243":1,"248":1,"250":1,"251":2,"252":2,"254":3,"255":2,"256":3,"257":1,"258":1,"259":1,"261":2,"276":1,"296":1,"322":1,"323":1,"364":1,"365":1,"374":1,"383":1,"395":1,"407":1,"418":2,"424":1,"425":2,"426":1,"428":3,"429":4,"430":1,"446":1,"453":1,"457":2,"463":2,"464":1,"465":1,"468":1,"475":1,"476":1,"478":1,"487":1,"488":1,"489":1,"490":2,"491":1,"492":2,"493":3,"495":2,"497":1,"516":2,"522":1,"524":1,"528":1,"534":2,"536":1,"539":1,"540":2,"543":1,"547":1,"549":1,"551":1,"552":2,"559":1,"607":2,"609":1,"611":1,"618":1,"631":1,"632":1,"638":1,"647":1,"652":1,"677":1,"688":1,"693":1,"696":2,"714":1,"720":1,"747":2,"821":1,"822":1,"825":2,"832":2,"845":1,"867":1,"875":1,"877":1,"909":1,"926":1,"929":1,"934":1,"967":1,"975":1,"983":2,"994":1,"1016":2,"1021":2,"1037":1,"1040":1,"1049":2,"1054":1,"1078":1,"1099":1,"1127":1,"1133":1,"1237":1,"1247":1,"1259":2,"1270":1,"1271":1,"1285":2,"1299":1,"1310":1,"1322":4,"1323":6,"1338":2,"1341":1,"1349":6,"1358":2,"1365":1,"1366":1,"1368":2,"1370":1,"1379":5,"1394":8,"1395":6,"1401":2,"1408":1,"1414":4,"1415":1,"1416":1,"1430":1,"1435":27,"1437":1,"1444":1,"1446":2,"1452":1,"1454":1,"1466":1,"1473":1,"1474":1,"1475":2,"1488":2,"1489":1,"1495":55,"1499":1,"1523":1,"1525":1,"1530":2,"1534":1,"1543":1,"1588":1,"1590":1,"1595":1,"1610":1,"1638":1,"1728":1,"1763":1,"1765":1,"1815":1,"1887":1,"1888":1,"1995":1,"2047":1,"2050":1,"2165":1,"2168":1,"2170":1,"2189":1,"2191":1,"2231":1}}],["mostsevere",{"2":{"109":1,"1222":1,"1225":1},"3":{"109":2,"1222":1,"1225":1,"1229":2,"1310":1,"1311":2}}],["mostly",{"3":{"41":1,"135":1,"267":1,"409":1,"734":1,"1011":1,"1264":1,"1270":1,"1310":1,"1311":1,"1349":1,"1358":1,"1379":1,"1395":1,"1435":4,"1437":1,"1495":2,"1550":1,"1576":1,"1581":1,"1582":1,"1584":2,"1930":1,"2050":1,"2073":1}}],["most",{"0":{"2042":1},"3":{"0":5,"24":2,"69":1,"109":3,"110":1,"111":1,"132":1,"150":1,"157":1,"178":1,"235":1,"243":1,"255":1,"312":1,"329":1,"349":1,"351":1,"352":1,"390":1,"402":1,"459":2,"460":1,"463":1,"470":1,"472":1,"487":1,"500":1,"518":1,"556":1,"584":1,"590":1,"599":1,"625":1,"628":3,"641":1,"650":1,"692":1,"725":1,"742":1,"781":1,"811":1,"819":2,"821":1,"826":1,"830":1,"839":1,"840":1,"842":1,"854":1,"941":1,"947":1,"971":1,"973":1,"984":1,"998":1,"1004":1,"1020":2,"1033":1,"1051":1,"1058":1,"1067":1,"1075":1,"1085":1,"1115":1,"1116":1,"1125":1,"1126":1,"1140":2,"1154":1,"1191":1,"1200":1,"1212":3,"1216":1,"1218":1,"1224":1,"1226":1,"1229":4,"1237":5,"1247":2,"1251":1,"1252":1,"1254":2,"1255":1,"1259":6,"1264":1,"1265":1,"1267":1,"1270":7,"1271":3,"1274":1,"1282":1,"1285":20,"1289":1,"1290":1,"1299":8,"1300":3,"1308":2,"1309":2,"1310":15,"1311":2,"1315":1,"1321":1,"1322":10,"1323":13,"1332":1,"1336":1,"1337":1,"1338":11,"1339":2,"1342":1,"1346":2,"1347":1,"1349":7,"1351":1,"1357":1,"1358":32,"1360":1,"1362":1,"1365":1,"1368":4,"1371":1,"1372":1,"1375":1,"1378":1,"1379":4,"1389":1,"1394":13,"1395":22,"1396":1,"1398":1,"1400":1,"1401":12,"1402":1,"1404":1,"1413":1,"1414":6,"1416":19,"1425":1,"1427":1,"1430":1,"1435":22,"1437":3,"1440":1,"1441":1,"1442":1,"1444":1,"1446":1,"1452":1,"1454":8,"1457":1,"1459":1,"1464":1,"1466":2,"1474":1,"1488":4,"1491":2,"1495":2,"1524":1,"1525":2,"1526":1,"1530":1,"1536":3,"1573":2,"1577":1,"1584":1,"1624":1,"1626":1,"1629":2,"1630":2,"1631":1,"1637":1,"1638":1,"1640":4,"1655":1,"1665":2,"1676":1,"1682":1,"1701":1,"1764":3,"1768":1,"1781":1,"1794":1,"1795":1,"1796":1,"1798":1,"1801":4,"1802":1,"1804":2,"1805":2,"1812":2,"1822":1,"1829":1,"1839":1,"1842":1,"1846":1,"1848":1,"1857":1,"1859":1,"1877":1,"1899":2,"1907":1,"1916":1,"1921":1,"1926":4,"1930":1,"1932":1,"1958":1,"1959":1,"1963":1,"1972":2,"1973":1,"1977":1,"1985":1,"1986":1,"1988":1,"1997":2,"2000":1,"2001":1,"2009":1,"2014":2,"2028":2,"2031":1,"2032":1,"2035":1,"2037":1,"2046":2,"2048":4,"2049":1,"2065":1,"2067":1,"2068":2,"2077":1,"2078":1,"2080":1,"2086":1,"2094":1,"2099":1,"2103":1,"2105":1,"2107":1,"2110":1,"2113":1,"2120":2,"2123":1,"2128":1,"2130":1,"2134":1,"2145":1,"2154":2,"2169":2,"2170":1,"2188":1,"2194":1,"2219":1,"2229":1}}],["modreq",{"3":{"1168":1}}],["modificar",{"3":{"1685":1}}],["modification",{"3":{"564":1,"566":1,"1244":1,"1270":4,"1285":5,"1322":1,"1323":1,"1358":2,"1414":1,"1629":1,"1748":1,"1822":1}}],["modifier",{"3":{"1259":1,"1270":1,"1285":1,"1358":1,"1368":1,"1379":1,"1394":1,"1430":1,"1435":4,"1629":1}}],["modifiers",{"3":{"1214":1}}],["modified",{"2":{"2166":1},"3":{"344":1,"698":1,"714":1,"715":2,"716":1,"1274":1,"1277":1,"1285":16,"1322":1,"1437":1,"1495":2,"1544":1,"1631":1,"1632":1,"1635":1,"1754":1,"1838":1,"1850":1,"1870":1,"1875":1,"2166":1}}],["modifying",{"3":{"1285":1,"1358":1,"1457":1}}],["modify",{"3":{"0":1,"245":2,"248":1,"249":1,"253":1,"254":1,"256":1,"257":1,"279":1,"285":1,"286":1,"340":1,"732":1,"733":1,"854":1,"856":1,"857":1,"1285":1,"1291":1,"1299":4,"1300":6,"1394":1,"1437":3,"1541":1,"1630":1,"1637":1,"1868":1,"1876":2,"1914":1,"2000":1}}],["modalities",{"3":{"1394":1}}],["modally",{"3":{"682":1,"1416":4}}],["modal",{"3":{"0":1,"682":1,"684":1,"1323":1,"1416":8}}],["mode=complete",{"3":{"1471":1}}],["mode=form",{"3":{"1310":1,"1974":1}}],["mode=max",{"3":{"868":1,"1454":4,"1464":2}}],["moderating",{"3":{"1394":1,"1401":3,"1945":1}}],["moderationqueue",{"3":{"1401":2}}],["moderationaction",{"3":{"1199":6,"1203":1,"1207":2,"1401":12}}],["moderation",{"3":{"523":1,"897":2,"900":1,"1341":1,"1348":2,"1349":10,"1352":1,"1354":1,"1358":11,"1377":1,"1379":1,"1380":1,"1387":1,"1394":11,"1395":16,"1397":1,"1398":2,"1400":2,"1401":75,"1437":3,"1625":1,"1626":2,"1628":1,"1629":1,"1630":3,"1631":5,"1634":1,"1747":2,"1749":1,"1759":1,"1762":1,"1764":3,"1770":1}}],["moderateasync",{"3":{"1401":3}}],["moderatequestioncommand",{"3":{"1199":5,"1203":1,"1207":2,"1401":7,"1435":1}}],["moderatequestionhandlertests",{"3":{"1199":1,"1207":3}}],["moderatequestionhandler",{"2":{"522":1,"523":1,"1100":1,"1398":1},"3":{"522":2,"523":2,"897":2,"1100":2,"1199":2,"1203":1,"1207":2,"1247":1,"1270":2,"1349":2,"1395":4,"1398":10,"1401":8,"1435":1}}],["moderated",{"3":{"1192":1,"1202":1,"1203":1,"1349":1,"1401":2}}],["moderate",{"3":{"522":1,"523":1,"897":1,"1100":1,"1203":2,"1207":4,"1247":1,"1270":2,"1349":1,"1394":3,"1395":2,"1398":1,"1401":9,"1625":1,"1631":1,"1747":2,"1759":1,"1764":3,"1767":2,"1770":1}}],["moderators",{"3":{"1395":4,"1398":1,"1401":11,"1747":1,"1764":1}}],["moderator",{"3":{"391":1,"1349":1,"1395":3,"1396":1,"1401":20,"1437":1,"1629":1,"1631":2,"1763":1,"1764":3,"1767":1}}],["modernizing",{"3":{"2212":1,"2217":1,"2219":1}}],["modernize",{"3":{"1395":1}}],["modernization",{"3":{"1012":1,"1525":1,"1530":1,"1531":1,"1543":1,"1575":1,"1576":1,"2219":1}}],["modern",{"3":{"309":1,"743":1,"799":1,"1049":1,"1237":1,"1285":1,"1291":1,"1416":1,"1435":3,"1992":1,"2069":1,"2075":1,"2213":1,"2219":1,"2220":1,"2225":1,"2238":1,"2239":1}}],["modest",{"3":{"1473":1,"2032":2,"2059":1}}],["modes",{"1":{"504":1,"505":1,"506":1,"507":1,"508":1,"509":1,"510":1,"511":1,"512":1,"513":1,"514":1},"3":{"0":1,"16":1,"100":1,"101":1,"185":1,"504":1,"520":1,"536":1,"555":1,"626":2,"666":1,"880":1,"885":1,"1152":1,"1189":1,"1212":1,"1229":1,"1247":2,"1259":2,"1270":1,"1285":4,"1310":3,"1323":4,"1338":1,"1349":1,"1358":3,"1394":2,"1395":1,"1414":2,"1416":2,"1423":1,"1426":1,"1435":6,"1452":2,"1466":1,"1489":2,"1491":1,"1558":1,"1576":1,"1647":1,"1663":1,"1683":1,"1720":1,"1889":1,"1898":1,"1951":1,"2078":1,"2080":1,"2231":1}}],["model=",{"3":{"1595":1}}],["modeltype",{"3":{"1310":2}}],["modeltestcontext",{"3":{"1199":2,"1207":1}}],["modelmetadata",{"3":{"1310":2}}],["modelconfigurationbuilder",{"3":{"1285":1}}],["modelidvalue",{"3":{"1018":2,"1368":1,"1525":1}}],["modelid",{"2":{"1090":1,"1424":1},"3":{"1018":2,"1090":1,"1212":1,"1349":1,"1358":3,"1365":1,"1424":2,"1426":5,"1495":1}}],["modeling",{"3":{"168":1,"1229":1,"1285":1,"1308":1,"1323":1,"1349":5,"1358":3,"1379":2,"1394":2,"1401":1,"1414":1,"1416":1,"1435":1,"1472":1,"1803":1,"1810":1}}],["modelused",{"2":{"1018":1},"3":{"1018":1,"1349":9,"1358":2,"1361":1,"1368":2}}],["modelling",{"3":{"683":1,"1041":1,"1115":1,"1162":1,"1218":1,"1249":1,"1259":1,"1299":1,"1310":1,"1322":1,"1323":1,"1338":1,"1349":3,"1358":3,"1361":1,"1368":1,"1394":2,"1401":3,"1414":2,"1435":1,"1775":1,"2182":1}}],["modelled",{"3":{"0":1,"691":1,"1108":1,"1217":1,"1247":1,"1270":1,"1285":2,"1310":1,"1322":1,"1323":1,"1349":2,"1358":3,"1395":1,"1414":2,"1435":3,"1630":2}}],["modelbindingresult",{"3":{"1310":1}}],["modelbinding",{"3":{"1310":1}}],["modelbinders",{"3":{"330":1,"880":1,"883":1,"1203":1,"1207":4,"1241":1,"1247":1,"1310":3}}],["modelbinder",{"3":{"330":1,"741":1,"1310":1,"1379":1,"1987":1}}],["modelbuilderextensions",{"3":{"1199":2,"1203":1,"1207":2,"1208":1,"1281":3,"1285":14}}],["modelbuilderextensionstests",{"3":{"1198":1,"1199":2,"1207":6,"1285":2}}],["modelbuilder",{"2":{"697":1},"3":{"200":1,"697":1,"715":1,"1208":2,"1281":1,"1285":13,"1395":1}}],["modelvalidationtests",{"3":{"1199":1,"1207":7}}],["modelvalidation",{"2":{"117":1,"128":2,"1595":1},"3":{"117":2,"128":4,"1199":24,"1203":1,"1207":2,"1323":17,"1383":1,"1394":26,"1395":6,"1435":1,"1495":1,"1590":1,"1595":2,"1599":1}}],["modeled",{"3":{"109":1,"1285":3,"1299":3,"1308":3,"1341":2,"1349":5,"1358":2,"1379":3,"1394":2,"1395":4,"1401":2,"1416":1,"1530":1,"1531":1,"2238":1}}],["model",{"0":{"1228":1,"1280":1,"1467":1,"1629":1,"1651":1,"1760":1,"1770":1},"1":{"163":1,"164":1,"165":1,"166":1,"167":1,"168":1,"169":1,"170":1,"171":1,"1088":1,"1089":1,"1090":1,"1091":1,"1092":1,"1093":1,"1094":1,"1095":1,"1096":1,"1148":1,"1149":1,"1150":1,"1151":1,"1152":1,"1153":1,"1154":1,"1155":1,"1156":1,"1157":1,"1339":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":1,"1346":1,"1347":1,"1348":1,"1349":1,"1807":1,"1808":1,"1809":1,"1810":1,"1811":1,"1853":1,"1854":1,"1855":1,"1856":1,"1857":1,"1858":1,"1859":1,"1860":1,"1861":1},"2":{"1083":1,"1593":1},"3":{"0":36,"25":1,"26":1,"38":1,"39":1,"52":1,"53":1,"81":1,"109":2,"117":1,"121":1,"124":1,"126":1,"128":1,"163":1,"166":3,"167":1,"168":1,"185":1,"186":2,"187":1,"188":1,"193":1,"200":2,"201":1,"223":1,"226":1,"227":1,"228":1,"230":4,"274":1,"277":1,"284":1,"291":2,"293":1,"296":1,"313":1,"317":1,"318":3,"319":1,"320":1,"329":1,"332":2,"341":1,"346":1,"350":1,"358":1,"362":1,"384":1,"397":1,"408":1,"434":1,"458":1,"462":1,"470":1,"497":2,"498":1,"501":1,"507":1,"560":1,"563":2,"564":2,"566":1,"567":1,"572":1,"582":1,"587":2,"599":1,"600":1,"605":1,"612":1,"641":1,"650":1,"651":1,"656":1,"657":2,"670":1,"686":1,"690":1,"697":1,"698":5,"699":5,"707":3,"708":1,"720":1,"733":1,"737":1,"748":1,"755":1,"756":2,"758":1,"761":1,"762":1,"783":1,"790":1,"797":1,"798":1,"811":1,"821":1,"834":1,"842":1,"857":1,"889":5,"890":1,"891":4,"903":2,"904":1,"905":2,"906":1,"907":3,"910":1,"913":2,"914":5,"915":3,"918":1,"929":2,"931":1,"932":1,"934":1,"946":1,"950":1,"974":1,"1011":2,"1012":6,"1013":1,"1016":2,"1017":7,"1018":19,"1019":8,"1020":5,"1025":1,"1026":2,"1027":1,"1033":2,"1034":2,"1035":1,"1036":1,"1042":1,"1044":1,"1049":1,"1050":3,"1051":3,"1052":2,"1058":1,"1059":2,"1060":1,"1062":1,"1068":1,"1069":2,"1076":1,"1083":10,"1084":2,"1085":3,"1088":1,"1089":2,"1090":9,"1091":12,"1092":5,"1093":5,"1094":3,"1118":1,"1133":1,"1137":1,"1139":1,"1140":2,"1142":1,"1148":1,"1149":1,"1150":1,"1151":1,"1152":1,"1155":1,"1160":1,"1168":1,"1170":1,"1174":1,"1176":2,"1177":1,"1178":2,"1180":1,"1192":3,"1202":2,"1203":2,"1212":11,"1213":1,"1220":1,"1230":2,"1235":1,"1237":11,"1241":2,"1247":5,"1249":2,"1259":5,"1263":1,"1269":1,"1270":11,"1271":3,"1272":1,"1273":5,"1276":1,"1277":1,"1278":1,"1279":2,"1280":11,"1281":1,"1282":1,"1283":1,"1285":189,"1286":2,"1289":3,"1295":1,"1296":2,"1299":35,"1301":1,"1308":10,"1309":4,"1310":18,"1311":4,"1319":1,"1322":30,"1323":98,"1325":1,"1327":1,"1338":6,"1339":2,"1341":3,"1346":2,"1347":2,"1349":54,"1358":44,"1361":3,"1362":1,"1364":1,"1365":5,"1367":1,"1368":30,"1370":1,"1372":2,"1373":1,"1379":5,"1380":1,"1383":4,"1385":1,"1394":199,"1395":54,"1400":1,"1401":16,"1405":1,"1408":1,"1409":1,"1411":1,"1414":10,"1415":3,"1416":9,"1417":3,"1418":2,"1419":6,"1421":6,"1422":1,"1423":3,"1424":2,"1425":3,"1426":41,"1427":2,"1428":2,"1430":5,"1434":8,"1435":114,"1437":11,"1440":1,"1441":2,"1442":1,"1445":1,"1452":3,"1454":14,"1455":1,"1460":1,"1461":4,"1466":8,"1468":2,"1475":1,"1487":3,"1488":2,"1489":2,"1491":7,"1495":9,"1502":1,"1507":1,"1510":1,"1523":2,"1525":15,"1526":1,"1529":1,"1530":1,"1531":2,"1533":8,"1534":1,"1536":2,"1540":4,"1542":1,"1543":2,"1544":2,"1547":3,"1552":11,"1553":1,"1555":1,"1571":2,"1574":3,"1575":1,"1576":8,"1577":1,"1580":1,"1581":3,"1582":1,"1584":5,"1590":5,"1591":1,"1593":1,"1595":2,"1598":4,"1599":1,"1613":1,"1620":1,"1626":1,"1629":2,"1630":2,"1634":1,"1635":1,"1637":3,"1638":3,"1639":1,"1640":4,"1650":1,"1652":1,"1662":1,"1663":1,"1685":2,"1696":3,"1703":1,"1720":1,"1723":1,"1726":1,"1734":1,"1756":1,"1775":1,"1778":2,"1780":1,"1783":1,"1789":1,"1794":1,"1798":1,"1804":1,"1806":1,"1807":3,"1808":1,"1809":1,"1811":2,"1848":10,"1850":4,"1852":1,"1853":1,"1857":7,"1858":1,"1860":1,"1861":1,"1862":3,"1863":1,"1865":2,"1867":1,"1870":1,"1871":1,"1873":1,"1874":1,"1893":1,"1897":1,"1900":2,"1907":1,"1919":1,"1922":1,"1924":1,"1926":1,"1927":1,"1929":2,"1947":1,"1960":1,"1963":1,"1977":1,"1979":1,"1981":1,"1983":1,"1989":3,"1990":1,"1992":1,"1993":2,"1995":1,"2007":1,"2017":2,"2052":2,"2054":1,"2060":2,"2063":1,"2067":1,"2076":1,"2077":1,"2082":1,"2085":1,"2113":1,"2124":2,"2139":1,"2140":1,"2145":1,"2153":1,"2155":3,"2165":1,"2169":2,"2170":4,"2171":2,"2172":2,"2173":2,"2174":1,"2175":2,"2176":3,"2177":2,"2178":2,"2179":7,"2191":1,"2194":1,"2197":1,"2198":1,"2205":2,"2206":1,"2208":1,"2219":2,"2226":1,"2228":1,"2229":2,"2231":9,"2238":2}}],["modelsnapshot",{"3":{"1496":1}}],["modelstatedictionary",{"3":{"1310":1}}],["models",{"0":{"1346":1,"1355":1,"1383":1},"3":{"0":1,"95":1,"115":1,"117":1,"128":1,"165":1,"224":1,"242":1,"255":1,"659":1,"731":1,"810":1,"932":1,"1028":1,"1083":2,"1085":2,"1234":1,"1237":1,"1285":4,"1310":2,"1323":5,"1338":1,"1339":2,"1346":1,"1349":6,"1358":2,"1379":7,"1394":7,"1395":3,"1401":6,"1414":1,"1435":20,"1487":1,"1495":1,"1542":1,"1566":1,"1576":2,"1599":1,"1802":1,"2143":1}}],["mode",{"0":{"1270":1,"2075":1},"1":{"270":1,"271":1,"272":1,"273":1,"274":1,"275":1,"276":1,"277":1,"553":1,"554":1,"555":1,"556":1,"557":1,"558":1,"559":1,"560":1,"928":1,"929":1,"930":1,"931":1,"932":1,"933":1,"934":1,"935":1,"2080":1,"2081":1,"2082":1,"2083":1,"2084":1,"2085":1},"3":{"0":17,"18":1,"20":1,"23":1,"31":1,"100":1,"101":1,"135":1,"146":1,"147":1,"168":1,"216":1,"226":1,"234":1,"235":1,"236":1,"243":2,"245":5,"255":1,"259":1,"265":1,"270":1,"272":1,"273":1,"283":1,"361":1,"368":2,"373":3,"401":1,"483":1,"507":2,"508":1,"516":1,"518":1,"519":1,"520":1,"526":1,"527":1,"529":1,"530":2,"531":2,"532":1,"536":1,"537":1,"553":1,"555":4,"556":12,"557":2,"558":6,"560":2,"592":1,"609":1,"610":1,"618":2,"622":1,"626":2,"633":6,"648":1,"653":1,"684":1,"699":1,"700":1,"702":1,"716":1,"718":1,"740":1,"758":1,"765":1,"767":1,"809":1,"811":1,"825":1,"845":1,"856":2,"860":1,"863":1,"865":2,"867":3,"868":1,"873":2,"876":4,"877":1,"881":1,"883":1,"885":1,"923":1,"928":1,"940":1,"954":1,"955":1,"956":1,"982":1,"997":1,"1018":2,"1034":1,"1055":1,"1067":1,"1074":2,"1091":2,"1099":1,"1100":2,"1101":2,"1125":1,"1212":7,"1228":1,"1229":4,"1237":1,"1240":1,"1247":1,"1257":3,"1259":21,"1270":1,"1271":1,"1285":13,"1287":1,"1299":7,"1300":2,"1310":9,"1322":6,"1323":34,"1325":1,"1331":1,"1333":1,"1335":1,"1338":19,"1349":1,"1356":1,"1358":9,"1361":1,"1368":2,"1383":1,"1388":1,"1390":1,"1394":21,"1395":17,"1401":3,"1414":2,"1415":2,"1416":6,"1426":9,"1431":1,"1432":1,"1435":45,"1437":2,"1440":3,"1441":1,"1442":1,"1444":6,"1449":1,"1450":1,"1452":4,"1453":1,"1454":4,"1455":1,"1458":1,"1459":2,"1460":1,"1462":3,"1466":9,"1468":1,"1471":1,"1472":1,"1475":1,"1477":2,"1485":2,"1486":1,"1487":2,"1489":2,"1490":1,"1491":7,"1495":1,"1523":1,"1525":3,"1530":2,"1533":1,"1534":1,"1565":1,"1576":12,"1581":1,"1584":1,"1588":1,"1590":5,"1595":1,"1599":1,"1607":1,"1640":1,"1642":2,"1643":1,"1644":2,"1647":1,"1648":1,"1654":1,"1659":1,"1660":2,"1668":1,"1669":1,"1672":1,"1674":4,"1695":1,"1730":5,"1741":1,"1749":1,"1772":2,"1789":1,"1824":1,"1841":2,"1850":1,"1894":2,"1895":1,"1913":1,"1921":1,"1922":2,"1947":1,"1950":1,"1958":1,"1960":1,"1992":1,"1995":1,"2000":1,"2019":1,"2026":1,"2030":1,"2035":1,"2042":1,"2047":1,"2049":1,"2055":1,"2069":1,"2072":1,"2075":1,"2078":2,"2079":3,"2080":3,"2085":2,"2088":1,"2141":1,"2144":1,"2145":1,"2147":1,"2149":1,"2168":1,"2173":1,"2182":1,"2203":1,"2209":1,"2231":4}}],["modulo",{"3":{"1285":1,"1435":1,"1474":1}}],["modularity",{"3":{"1435":3}}],["modular",{"0":{"1508":1,"1782":1},"1":{"56":1,"57":1,"58":1,"59":1,"60":1,"61":1,"62":1,"63":1,"64":1,"1787":1,"1788":1,"1789":1,"1790":1,"1791":1,"1792":1,"2086":1,"2087":1,"2088":1,"2089":1,"2090":1,"2091":1,"2092":1,"2093":1,"2094":1,"2095":1,"2096":1,"2097":1},"3":{"0":1,"30":1,"56":1,"58":3,"62":1,"329":1,"396":1,"405":1,"582":1,"781":1,"905":1,"1211":1,"1212":1,"1259":2,"1270":1,"1289":1,"1299":5,"1309":1,"1311":2,"1322":3,"1323":1,"1358":1,"1394":1,"1395":2,"1401":2,"1435":3,"1454":1,"1488":1,"1533":1,"1535":1,"1543":1,"1645":1,"1646":1,"1658":1,"1674":1,"1691":1,"1778":1,"1779":2,"1780":1,"1786":1,"1787":1,"1792":1,"1863":1,"1877":1,"1885":1,"1985":1,"2008":1,"2086":1,"2154":1,"2203":1,"2204":1,"2209":1,"2211":1,"2212":2,"2213":2,"2217":1,"2218":1,"2220":1,"2226":1,"2229":1,"2238":2,"2242":1}}],["moduledefinition",{"3":{"1435":3}}],["moduledomains",{"3":{"1435":2}}],["moduledomain",{"3":{"1430":1,"1435":3}}],["modulebearingmap",{"3":{"1435":2}}],["modulelayerisolated",{"3":{"1435":1}}],["modulelayer",{"3":{"1435":1}}],["modulelessmap",{"3":{"1435":1}}],["moduleless",{"3":{"1435":1}}],["moduleloaderlogger",{"3":{"1310":1}}],["moduleloadertests",{"3":{"583":7,"586":1,"1199":1,"1207":15,"1314":1,"1322":2}}],["moduleloader",{"0":{"1880":1},"2":{"58":1,"59":1,"60":1,"63":1,"117":1,"295":2,"582":1,"587":1,"633":1,"636":1,"1377":1,"1412":1,"1508":1,"1699":1,"1700":1,"1883":1,"1885":1,"2018":1,"2025":1,"2093":1,"2096":1},"3":{"58":1,"59":3,"60":1,"63":3,"117":2,"122":1,"295":3,"582":1,"583":18,"584":5,"585":6,"586":1,"587":1,"633":1,"636":1,"674":1,"980":1,"1199":8,"1202":1,"1203":2,"1207":2,"1285":2,"1308":4,"1310":20,"1312":1,"1314":28,"1322":20,"1349":2,"1358":1,"1368":3,"1376":1,"1377":1,"1379":7,"1395":6,"1412":1,"1414":4,"1430":1,"1435":5,"1437":1,"1446":1,"1508":1,"1533":1,"1576":1,"1652":2,"1661":1,"1683":1,"1699":1,"1700":1,"1727":1,"1789":1,"1880":1,"1883":2,"1885":1,"2018":1,"2025":1,"2093":1,"2096":1}}],["moduletests",{"3":{"1435":2,"1488":1}}],["modulerequiredlayeroverrides",{"3":{"1435":3,"1488":1}}],["moduleinfrastructures",{"3":{"1435":1}}],["moduleinternallayersareisolated",{"3":{"1435":3}}],["moduleinternallayers",{"3":{"1435":2}}],["moduleinitializer",{"3":{"1432":1,"1435":2,"1487":1}}],["moduleisolationtests",{"3":{"1199":1,"1207":2,"1435":4,"1488":3,"1590":1}}],["moduleisolationtestsbasetests",{"3":{"1199":1,"1207":3,"1435":6,"1488":1}}],["moduleisolationtestsbase",{"3":{"587":1,"1199":2,"1207":2,"1430":1,"1435":8,"1488":1,"1653":1}}],["moduleof",{"3":{"1430":1,"1435":2}}],["modulepath",{"3":{"1323":3,"1416":1}}],["moduleprobemap",{"3":{"1199":2,"1207":1,"1435":6}}],["moduleenabled",{"2":{"1313":1,"1881":1},"3":{"1313":1,"1881":1}}],["moduleapis",{"3":{"1435":1}}],["moduleapplications",{"3":{"1435":2}}],["moduleapplication",{"3":{"1430":1,"1435":10}}],["moduleapplicationdbcontext",{"2":{"1362":1,"1403":1,"1411":1,"1499":1,"1684":1,"1685":1},"3":{"1199":3,"1203":3,"1207":6,"1362":5,"1368":17,"1395":8,"1403":2,"1411":2,"1414":6,"1495":2,"1499":1,"1650":1,"1684":1,"1685":1}}],["moduleassembly",{"2":{"1829":1},"3":{"1270":2,"1271":1,"1358":4,"1414":2,"1829":1}}],["moduleassemblies",{"2":{"1880":1},"3":{"0":1,"583":1,"1310":3,"1322":2,"1880":1}}],["modulehost",{"3":{"1310":8,"1311":1,"1322":3,"1379":1}}],["modulehostcontext",{"2":{"1880":1},"3":{"1199":2,"1203":1,"1207":2,"1310":14,"1314":3,"1322":6,"1880":1}}],["modulehostextensionstests",{"3":{"1199":1,"1207":2,"1310":5,"1437":2}}],["modulehostextensions",{"3":{"583":1,"674":1,"677":1,"980":2,"1199":1,"1203":1,"1207":2,"1208":1,"1310":9,"1314":3,"1322":6,"1338":1}}],["moduleconformancetestsbasetests",{"3":{"1199":1,"1207":9,"1435":32,"1488":4}}],["moduleconformancetestsbase",{"2":{"585":1,"1496":1},"3":{"585":2,"1199":6,"1207":2,"1322":2,"1427":1,"1430":3,"1435":15,"1437":1,"1488":2,"1495":2,"1496":1}}],["modulecontrollerfeatureprovidertests",{"3":{"1199":1,"1207":2}}],["modulecontrollerfeatureprovider",{"2":{"583":1},"3":{"583":2,"585":3,"1199":3,"1203":1,"1207":2,"1310":10,"1322":4}}],["modulecode",{"2":{"1133":1},"3":{"0":1,"1133":1,"1435":2}}],["modulename",{"2":{"1772":1},"3":{"405":1,"583":2,"585":1,"1270":5,"1285":2,"1299":1,"1310":3,"1314":2,"1322":5,"1323":2,"1379":1,"1414":1,"1772":1,"1855":1}}],["modulenames",{"2":{"80":1,"82":1},"3":{"80":1,"82":1,"1435":11}}],["modulenameconventionstests",{"3":{"1004":1,"1199":1,"1207":2,"1285":1,"1299":3,"1437":2}}],["modulenameconventions",{"3":{"0":1,"1004":2,"1199":5,"1203":1,"1207":2,"1263":1,"1270":3,"1285":8,"1299":10,"1661":1,"2042":1}}],["moduleshared",{"3":{"1435":5}}],["modulesdeclareeveryexpectedlayer",{"2":{"1488":1},"3":{"1435":2,"1488":2}}],["modulesdeclarelayers",{"3":{"1435":12}}],["modulessectionname",{"3":{"1323":1}}],["modulessettingstests",{"3":{"1199":1,"1207":2}}],["modulessettings",{"2":{"585":1,"1314":1,"1881":1,"2011":1,"2093":1},"3":{"0":1,"583":5,"584":1,"585":4,"586":1,"674":1,"979":1,"1199":10,"1203":1,"1207":2,"1310":18,"1312":1,"1314":2,"1319":4,"1322":9,"1435":4,"1661":1,"1881":1,"2011":1,"2093":1,"2097":1}}],["modulesbyname",{"3":{"1322":1}}],["modulescanning",{"2":{"115":1,"1262":1},"3":{"115":1,"122":1,"536":1,"552":1,"922":1,"927":1,"980":1,"1207":1,"1208":1,"1262":7,"1263":2,"1270":8,"1271":4,"1299":1,"1310":3,"1315":7,"1322":6,"1358":8,"1395":6,"1414":13}}],["modulesettings",{"2":{"585":1,"674":1,"1881":2},"3":{"0":1,"583":4,"584":1,"674":2,"677":1,"1199":5,"1203":1,"1207":2,"1312":1,"1314":1,"1319":4,"1322":5,"1881":2}}],["modules",{"0":{"1513":1,"2076":1},"1":{"1877":1,"1878":1,"1879":1,"1880":1,"1881":1,"1882":1,"1883":1,"1884":1,"1885":1},"2":{"576":1,"584":1,"586":1,"1004":1,"1638":1,"1883":1},"3":{"0":3,"42":2,"46":1,"52":1,"53":1,"54":1,"59":6,"60":1,"63":2,"72":3,"73":2,"80":6,"97":1,"109":1,"128":3,"132":1,"165":1,"184":1,"186":5,"230":6,"265":1,"295":4,"311":1,"318":1,"325":2,"344":1,"350":2,"352":3,"353":4,"360":1,"384":2,"388":2,"390":1,"391":2,"396":1,"401":1,"444":1,"448":2,"459":2,"463":2,"465":2,"466":1,"470":8,"475":8,"476":10,"477":8,"478":1,"491":1,"492":1,"493":2,"494":2,"495":2,"497":6,"499":2,"502":1,"522":1,"523":1,"524":1,"535":1,"536":1,"539":1,"545":15,"550":1,"551":1,"552":2,"556":8,"559":1,"572":1,"576":1,"577":2,"583":27,"584":1,"585":11,"586":5,"618":1,"633":1,"648":1,"649":4,"650":1,"651":1,"653":1,"657":6,"674":7,"675":2,"676":3,"690":1,"707":1,"724":3,"725":5,"728":2,"741":1,"743":2,"782":14,"784":2,"789":2,"790":7,"793":6,"794":4,"798":1,"799":1,"803":1,"804":1,"805":1,"809":1,"813":2,"846":1,"881":4,"889":1,"897":4,"900":1,"905":1,"924":4,"926":6,"946":2,"954":5,"956":1,"958":1,"960":1,"963":1,"980":2,"988":4,"995":1,"996":1,"1004":3,"1017":1,"1025":5,"1026":11,"1049":1,"1052":1,"1054":1,"1055":1,"1058":1,"1059":1,"1082":4,"1086":1,"1090":1,"1099":2,"1100":4,"1103":1,"1116":12,"1133":2,"1140":1,"1142":1,"1150":4,"1152":1,"1168":7,"1191":1,"1195":1,"1200":1,"1201":1,"1203":3,"1207":35,"1211":1,"1212":2,"1213":1,"1237":38,"1239":5,"1242":1,"1247":19,"1259":8,"1264":8,"1265":2,"1270":43,"1271":35,"1283":1,"1285":73,"1299":93,"1300":3,"1303":4,"1304":1,"1307":5,"1308":31,"1309":18,"1310":63,"1311":15,"1312":3,"1313":3,"1314":9,"1316":1,"1317":3,"1319":1,"1322":98,"1323":61,"1338":1,"1340":3,"1341":12,"1343":7,"1344":5,"1345":1,"1346":16,"1347":5,"1348":12,"1349":253,"1354":1,"1358":543,"1368":58,"1370":6,"1372":4,"1373":1,"1375":1,"1376":6,"1379":108,"1380":1,"1388":1,"1389":2,"1394":262,"1395":372,"1401":57,"1402":1,"1403":3,"1404":1,"1405":1,"1414":214,"1415":16,"1416":12,"1425":1,"1426":6,"1430":1,"1434":2,"1435":125,"1436":21,"1437":21,"1454":5,"1466":1,"1472":1,"1486":1,"1488":6,"1490":1,"1491":2,"1504":1,"1508":2,"1525":9,"1533":3,"1534":1,"1537":1,"1539":1,"1540":1,"1543":2,"1570":3,"1576":2,"1587":1,"1589":1,"1590":6,"1625":1,"1637":1,"1638":2,"1648":1,"1649":3,"1650":1,"1652":1,"1653":2,"1655":1,"1657":1,"1658":1,"1661":1,"1665":1,"1666":1,"1674":1,"1680":1,"1683":3,"1684":6,"1685":7,"1689":1,"1699":1,"1704":1,"1717":1,"1721":1,"1725":1,"1727":4,"1728":2,"1730":1,"1759":1,"1762":1,"1763":14,"1764":31,"1769":1,"1771":1,"1772":2,"1776":1,"1778":1,"1780":1,"1784":1,"1788":1,"1789":2,"1792":2,"1810":1,"1847":4,"1863":1,"1874":1,"1876":2,"1877":2,"1880":5,"1881":3,"1882":2,"1883":1,"1884":1,"1885":3,"1890":1,"1895":1,"1961":2,"1981":1,"2014":1,"2016":1,"2026":1,"2066":1,"2082":1,"2086":1,"2089":1,"2093":3,"2096":2,"2102":1,"2109":2,"2110":1,"2201":1,"2206":1,"2213":2,"2229":1,"2231":1}}],["module",{"0":{"1265":1,"1307":1,"1313":1,"1348":1,"1376":1,"1437":1,"1649":1,"1655":1,"1682":1,"1683":1,"1721":1,"1727":1,"1728":1,"1746":1,"1747":1,"1748":1,"1750":1,"1771":1,"1879":1,"1881":1,"1882":1,"1883":1,"2096":1},"1":{"56":1,"57":1,"58":1,"59":1,"60":1,"61":1,"62":1,"63":1,"64":1,"580":1,"581":1,"582":1,"583":1,"584":1,"585":1,"586":1,"587":1,"1312":1,"1313":1,"1314":1,"1315":1,"1316":1,"1317":1,"1318":1,"1319":1,"1320":1,"1321":1,"1322":1,"1339":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":1,"1346":1,"1347":1,"1348":1,"1349":1,"1395":1,"1402":1,"1403":1,"1404":1,"1405":1,"1406":1,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1413":1,"1414":1,"1415":1,"1680":1,"1681":1,"1682":1,"1683":1,"1684":1,"1685":1,"1686":1,"1687":1,"1688":1,"1689":1,"1690":1,"2014":1,"2015":1,"2016":1,"2017":1,"2018":1,"2019":1,"2020":1,"2021":1,"2022":1,"2023":1,"2024":1,"2025":1,"2026":1,"2027":1,"2086":1,"2087":1,"2088":1,"2089":1,"2090":1,"2091":1,"2092":1,"2093":1,"2094":1,"2095":1,"2096":1,"2097":1},"2":{"1730":1,"1770":1,"1881":1,"1883":1,"2102":1},"3":{"0":52,"24":1,"30":1,"47":3,"51":1,"53":6,"56":1,"57":1,"58":4,"59":12,"60":6,"62":3,"63":4,"80":2,"81":4,"82":1,"109":2,"117":3,"121":2,"122":2,"127":2,"128":2,"131":2,"132":1,"143":1,"166":2,"168":1,"184":1,"186":16,"187":7,"265":2,"295":4,"300":1,"318":2,"324":1,"325":1,"328":1,"329":1,"341":2,"350":2,"401":1,"405":4,"439":1,"459":2,"460":1,"470":2,"471":3,"472":1,"473":2,"474":1,"490":1,"493":1,"498":1,"536":3,"544":1,"545":3,"547":2,"556":1,"558":1,"562":1,"564":1,"572":4,"576":1,"580":1,"582":6,"583":30,"584":13,"585":20,"586":1,"587":7,"631":3,"632":2,"633":18,"635":8,"636":3,"639":1,"645":1,"648":4,"649":12,"650":6,"651":3,"673":1,"674":2,"688":1,"690":2,"692":2,"697":1,"698":1,"700":1,"707":2,"709":1,"725":3,"727":1,"750":1,"773":1,"778":1,"781":1,"782":3,"783":1,"784":1,"792":1,"798":2,"799":6,"801":1,"802":1,"803":1,"805":1,"809":1,"846":2,"880":1,"889":2,"891":1,"893":1,"905":2,"906":1,"912":1,"920":2,"921":2,"922":7,"923":2,"924":4,"926":3,"933":1,"956":1,"963":1,"979":1,"980":2,"1004":8,"1007":1,"1008":1,"1016":1,"1018":5,"1020":1,"1021":2,"1022":1,"1026":2,"1027":1,"1034":1,"1048":1,"1049":2,"1050":1,"1052":1,"1053":1,"1058":1,"1059":4,"1077":1,"1083":1,"1084":1,"1090":2,"1094":3,"1095":2,"1116":1,"1117":2,"1118":1,"1132":1,"1133":8,"1134":2,"1135":3,"1140":1,"1143":1,"1145":5,"1147":1,"1149":1,"1150":3,"1151":1,"1152":2,"1155":5,"1168":7,"1170":1,"1192":7,"1194":1,"1195":1,"1201":4,"1202":11,"1203":11,"1211":3,"1212":16,"1213":1,"1229":3,"1234":1,"1237":7,"1241":1,"1244":1,"1247":6,"1248":1,"1249":5,"1252":2,"1257":1,"1259":29,"1260":1,"1261":1,"1262":4,"1263":6,"1265":7,"1269":2,"1270":64,"1271":37,"1273":4,"1274":1,"1279":2,"1280":2,"1281":2,"1282":4,"1283":1,"1285":84,"1288":1,"1296":5,"1299":68,"1300":3,"1303":3,"1307":4,"1308":66,"1309":17,"1310":90,"1311":13,"1312":5,"1313":8,"1314":10,"1315":4,"1319":3,"1320":2,"1321":1,"1322":107,"1323":111,"1327":1,"1332":1,"1336":1,"1338":9,"1339":4,"1340":6,"1341":1,"1343":1,"1344":1,"1346":1,"1347":3,"1348":5,"1349":79,"1350":2,"1351":2,"1352":2,"1353":7,"1354":7,"1356":1,"1358":298,"1359":3,"1361":2,"1362":5,"1365":1,"1366":2,"1367":6,"1368":39,"1369":3,"1376":5,"1377":1,"1378":2,"1379":43,"1380":2,"1383":1,"1385":2,"1388":1,"1390":1,"1391":1,"1393":1,"1394":52,"1395":255,"1397":1,"1400":1,"1401":36,"1402":3,"1403":6,"1404":2,"1408":6,"1409":1,"1411":1,"1413":2,"1414":140,"1415":31,"1416":25,"1426":2,"1428":3,"1430":4,"1435":400,"1436":11,"1437":21,"1440":1,"1444":3,"1445":1,"1454":7,"1457":1,"1466":1,"1485":1,"1486":13,"1487":1,"1488":29,"1489":2,"1495":24,"1508":2,"1511":1,"1512":2,"1513":1,"1525":3,"1533":2,"1537":1,"1540":1,"1543":2,"1544":1,"1549":1,"1550":1,"1570":1,"1571":1,"1575":1,"1576":5,"1581":4,"1590":7,"1595":1,"1598":1,"1601":2,"1617":3,"1620":1,"1626":2,"1630":1,"1637":3,"1638":4,"1645":5,"1646":5,"1648":4,"1649":10,"1650":16,"1651":1,"1652":6,"1653":6,"1655":3,"1657":2,"1659":2,"1660":9,"1661":7,"1662":7,"1663":2,"1664":2,"1665":6,"1666":1,"1668":4,"1669":4,"1670":7,"1671":1,"1672":3,"1673":2,"1674":1,"1680":7,"1681":1,"1682":2,"1683":18,"1684":2,"1685":11,"1686":2,"1687":3,"1688":1,"1689":3,"1690":2,"1691":1,"1693":2,"1694":3,"1696":1,"1697":1,"1699":4,"1700":6,"1701":1,"1702":3,"1704":1,"1716":2,"1717":2,"1718":2,"1719":1,"1720":1,"1721":6,"1723":4,"1725":6,"1726":25,"1727":20,"1728":10,"1729":5,"1730":9,"1745":1,"1747":1,"1748":2,"1750":1,"1756":1,"1759":1,"1760":1,"1762":1,"1763":1,"1764":7,"1768":1,"1770":3,"1771":7,"1772":1,"1774":3,"1778":2,"1780":1,"1782":2,"1787":1,"1788":2,"1789":5,"1792":3,"1814":3,"1815":1,"1816":1,"1822":1,"1823":5,"1829":3,"1837":1,"1841":3,"1848":6,"1852":1,"1854":2,"1855":4,"1863":1,"1864":1,"1865":1,"1867":1,"1874":3,"1877":5,"1878":3,"1879":5,"1880":9,"1881":11,"1882":3,"1883":7,"1884":4,"1885":5,"1887":1,"1888":1,"1890":2,"1892":1,"1898":1,"1915":1,"1924":1,"1929":1,"1932":1,"1945":2,"1949":1,"1955":4,"1956":4,"1961":4,"1962":7,"1964":2,"1977":1,"1980":1,"2011":2,"2013":1,"2014":4,"2015":3,"2018":2,"2025":2,"2029":1,"2041":2,"2042":5,"2065":1,"2073":1,"2076":10,"2079":1,"2082":2,"2085":1,"2086":4,"2087":2,"2088":3,"2089":1,"2093":9,"2094":5,"2095":1,"2096":11,"2097":5,"2102":3,"2103":1,"2109":1,"2110":2,"2112":1,"2118":1,"2125":1,"2129":1,"2133":1,"2135":1,"2137":1,"2138":1,"2142":1,"2155":1,"2160":1,"2165":1,"2166":1,"2167":2,"2177":1,"2178":1,"2187":1,"2188":1,"2206":1,"2207":1,"2208":2,"2209":2,"2219":2,"2226":3,"2227":1,"2229":3,"2231":8,"2233":1}}],["miami",{"3":{"2219":1}}],["mientras",{"3":{"1685":1}}],["mi",{"3":{"1525":1,"1589":2,"1590":2,"1599":1}}],["mildly",{"3":{"1395":1}}],["miles",{"3":{"1394":1}}],["million",{"3":{"1228":1,"1229":1,"1466":1}}],["millions",{"3":{"1150":1}}],["millisecond",{"3":{"930":1,"1398":1,"1401":2,"1435":1,"1945":1}}],["milliseconds",{"3":{"235":1,"397":1,"1268":1,"1270":1,"1322":1,"1338":3,"1435":2,"1442":1,"1637":1,"2049":1,"2051":1,"2155":1}}],["might",{"3":{"1247":1,"1285":1,"1322":2,"1358":2,"1365":1,"1395":2,"1414":2,"1416":1,"1423":1,"1435":2,"1802":1,"1809":1,"1867":2,"1885":1,"1912":1,"1929":1,"1953":3}}],["migrating",{"3":{"294":1,"1035":1,"1048":1,"1051":1,"1116":1,"1322":1,"1415":1,"1435":1,"1443":1}}],["migrationid",{"3":{"1322":3}}],["migrationattribute",{"3":{"1322":1}}],["migrationapplyprooftests",{"3":{"1199":1,"1207":4,"1322":4,"1437":2,"1576":1,"1585":1}}],["migrationproof",{"3":{"1322":3}}],["migrationpolicy",{"3":{"1175":2}}],["migrationbuilder",{"2":{"566":1},"3":{"566":1,"1322":3}}],["migrationless",{"3":{"296":1,"1310":4}}],["migrationsassemblyprefix",{"3":{"1435":1}}],["migrationsassembly",{"3":{"1285":12}}],["migrationsfixture",{"2":{"1452":1},"3":{"564":1,"1199":1,"1203":1,"1206":1,"1207":3,"1318":1,"1322":6,"1486":1,"1489":1}}],["migrations",{"0":{"1651":1,"1687":1},"1":{"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"294":1,"295":1,"296":1},"2":{"291":1,"563":1,"564":2,"1037":1,"1044":1,"1180":1,"1368":1,"1496":1,"1613":1,"1651":1,"1656":1,"1687":2,"1696":3,"1718":1,"1721":2,"1726":1,"1727":3,"1728":2,"1730":1,"2096":1,"2191":1},"3":{"0":13,"49":1,"164":1,"166":1,"168":1,"169":1,"195":6,"200":2,"288":1,"290":7,"291":3,"295":2,"341":5,"343":1,"345":2,"350":2,"403":2,"563":2,"564":19,"568":2,"632":1,"633":5,"698":2,"699":1,"700":1,"701":1,"757":1,"872":1,"891":9,"905":3,"906":1,"918":1,"966":1,"1004":1,"1018":2,"1026":2,"1034":6,"1037":1,"1044":1,"1052":1,"1067":1,"1081":1,"1082":1,"1085":4,"1087":1,"1135":1,"1140":2,"1143":1,"1175":5,"1177":1,"1179":1,"1180":1,"1209":269,"1212":1,"1213":1,"1273":4,"1276":1,"1278":1,"1279":5,"1282":2,"1283":4,"1285":62,"1289":1,"1299":1,"1310":15,"1316":1,"1318":1,"1319":1,"1322":13,"1325":1,"1358":1,"1360":1,"1368":9,"1395":11,"1414":8,"1430":1,"1435":17,"1437":3,"1440":2,"1441":1,"1444":1,"1454":13,"1460":1,"1466":6,"1468":1,"1472":2,"1475":1,"1485":1,"1486":1,"1489":2,"1491":2,"1495":3,"1496":3,"1509":1,"1525":1,"1544":4,"1571":1,"1576":5,"1589":1,"1590":3,"1613":2,"1646":1,"1651":9,"1656":3,"1660":2,"1663":4,"1672":1,"1680":1,"1682":1,"1683":2,"1687":2,"1688":1,"1689":1,"1696":12,"1700":1,"1703":2,"1717":1,"1718":7,"1721":3,"1723":1,"1725":1,"1726":11,"1727":11,"1728":3,"1730":1,"1850":1,"1851":1,"2042":2,"2088":1,"2096":5,"2191":1,"2231":1}}],["migration",{"0":{"1696":1},"2":{"1696":1},"3":{"0":23,"15":2,"26":2,"41":2,"68":1,"78":1,"81":1,"167":1,"168":2,"195":4,"197":2,"200":3,"273":1,"275":2,"277":1,"290":1,"291":6,"292":3,"293":8,"294":1,"296":1,"308":1,"311":1,"348":2,"350":4,"352":1,"360":1,"403":1,"461":1,"474":2,"548":1,"562":4,"563":5,"564":10,"565":5,"566":6,"568":1,"598":1,"600":1,"609":1,"625":1,"630":1,"633":5,"635":1,"657":2,"691":1,"697":1,"698":1,"699":1,"700":1,"702":1,"707":1,"711":1,"715":2,"733":1,"756":3,"757":2,"759":3,"762":2,"797":4,"798":1,"799":4,"800":2,"801":1,"802":2,"803":1,"804":1,"805":1,"806":1,"810":1,"811":1,"812":1,"846":1,"848":2,"853":1,"855":1,"891":5,"893":1,"905":2,"907":3,"924":1,"929":1,"931":2,"932":1,"939":1,"947":1,"948":2,"965":1,"972":1,"973":1,"1004":1,"1016":1,"1026":1,"1034":2,"1035":1,"1037":1,"1042":1,"1044":1,"1046":1,"1048":2,"1049":2,"1050":1,"1051":4,"1052":1,"1054":1,"1059":1,"1060":1,"1061":1,"1082":1,"1085":5,"1089":1,"1145":1,"1174":1,"1175":1,"1177":1,"1178":2,"1179":1,"1212":4,"1259":4,"1273":3,"1280":1,"1283":1,"1285":19,"1289":1,"1292":1,"1299":7,"1308":2,"1310":4,"1315":1,"1318":3,"1322":24,"1325":1,"1338":1,"1349":1,"1368":8,"1401":1,"1407":1,"1414":5,"1428":1,"1435":5,"1437":1,"1454":21,"1459":1,"1460":2,"1466":8,"1468":1,"1470":1,"1473":1,"1476":1,"1477":1,"1489":1,"1491":1,"1495":2,"1496":1,"1507":1,"1525":2,"1544":1,"1576":1,"1585":1,"1590":3,"1599":1,"1613":1,"1640":3,"1649":1,"1650":1,"1651":3,"1660":2,"1662":1,"1663":2,"1683":1,"1687":3,"1688":1,"1696":3,"1700":1,"1708":1,"1717":1,"1718":3,"1720":1,"1721":1,"1723":1,"1726":3,"1727":3,"1728":2,"1730":1,"1764":1,"1783":2,"1786":1,"1787":1,"1809":1,"1841":1,"1847":1,"1850":2,"1851":1,"1852":1,"1853":1,"1874":1,"1875":1,"1888":2,"1890":1,"1947":1,"1977":1,"2007":1,"2012":1,"2032":1,"2037":1,"2082":2,"2084":1,"2096":1,"2187":1,"2191":2,"2201":1,"2219":3,"2231":4}}],["migrateasync",{"2":{"291":1},"3":{"291":1,"1278":1,"1285":4,"1310":3,"1322":2,"1576":1,"1663":1}}],["migrated",{"3":{"151":1,"200":1,"290":1,"563":1,"573":1,"633":1,"800":1,"1034":1,"1285":8,"1310":5,"1322":1,"1368":1,"1414":1,"1431":1,"1435":2,"1472":1,"1487":1,"1495":2,"1637":1,"1673":1,"1683":2,"1691":1,"1718":1,"1726":1,"1727":1,"1888":1,"2035":1,"2096":1}}],["migrates",{"3":{"0":3,"79":1,"294":1,"296":1,"468":1,"564":1,"698":1,"703":1,"1034":2,"1036":1,"1050":1,"1212":2,"1285":4,"1299":1,"1310":2,"1338":1,"1435":1,"1448":1,"1454":1,"1466":1,"1490":1,"1721":1,"1728":1}}],["migrate",{"2":{"291":1,"563":1,"1363":1,"1613":1,"1651":1,"1718":1},"3":{"0":2,"49":1,"102":1,"290":3,"291":4,"293":1,"295":1,"296":2,"311":1,"365":1,"563":1,"567":1,"846":1,"1212":1,"1285":3,"1310":6,"1319":1,"1322":4,"1363":1,"1416":1,"1428":1,"1435":1,"1437":2,"1454":2,"1466":2,"1472":2,"1489":1,"1569":1,"1613":1,"1651":1,"1652":1,"1664":1,"1683":1,"1718":2,"1791":1,"1904":1,"2054":1,"2231":1}}],["migrator",{"1":{"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"294":1,"295":1,"296":1},"3":{"0":2,"20":1,"288":1,"291":3,"292":1,"295":1,"296":2,"563":1,"756":2,"757":1,"762":1,"1180":1,"1192":1,"1212":1,"1285":1,"1323":1,"1395":2,"1454":1,"1466":1,"1468":1,"1472":1,"1475":1,"1476":1,"1495":2,"1651":1,"1663":1,"2231":2}}],["mime",{"3":{"1116":1,"1285":4,"1323":2,"1416":3,"2147":1}}],["mib",{"3":{"875":1,"1125":1,"1323":2,"1466":1}}],["microphone",{"3":{"1338":1,"1416":6}}],["microphone=",{"3":{"214":1,"2148":1}}],["micro",{"3":{"1135":1,"1285":1,"1358":1,"1416":2}}],["microbenchmark",{"3":{"860":1}}],["microsecond",{"3":{"820":1,"2031":1}}],["microseconds",{"3":{"71":1,"819":1,"827":1,"1126":1,"1270":1,"1323":1,"1336":1,"1338":1,"1947":1,"2023":1}}],["microserviceextraction",{"3":{"1575":1,"1576":1}}],["microserviceextractiontests",{"2":{"54":1,"59":1,"150":1,"1524":1,"1534":1,"1590":1,"1789":1,"1792":1,"1841":1,"2015":1,"2027":1,"2042":1,"2048":1},"3":{"54":1,"59":1,"62":1,"150":1,"1199":2,"1207":4,"1257":1,"1259":3,"1435":12,"1488":7,"1493":1,"1524":1,"1525":2,"1534":1,"1575":1,"1576":1,"1585":1,"1590":2,"1789":1,"1792":1,"1841":1,"2015":1,"2027":1,"2042":1,"2048":1}}],["microserviceextractiontestsbase",{"2":{"1074":1,"1077":1,"1653":1,"1662":1,"2041":1,"2103":1},"3":{"0":1,"1074":2,"1077":1,"1199":3,"1207":2,"1311":1,"1430":1,"1435":9,"1488":1,"1575":1,"1576":1,"1653":1,"1662":1,"2041":1,"2103":1}}],["microservice",{"3":{"846":1,"1034":1,"1211":1,"1214":1,"1248":1,"1259":1,"1285":2,"1310":2,"1311":2,"1314":1,"1322":3,"1379":1,"1439":1,"1487":1,"1488":1,"1525":1,"1645":1,"1646":1,"1691":1,"1780":1,"1814":1,"1846":1,"1881":1,"1894":1,"2004":1,"2016":1,"2053":1,"2226":1}}],["microservices",{"0":{"1543":1,"1782":1},"1":{"1787":1,"1788":1,"1789":1,"1790":1,"1791":1,"1792":1},"3":{"46":1,"60":1,"1216":1,"1229":1,"1237":1,"1247":2,"1257":1,"1259":8,"1270":3,"1272":1,"1273":1,"1280":1,"1285":13,"1286":1,"1289":1,"1296":1,"1299":14,"1300":3,"1302":1,"1306":1,"1308":11,"1309":3,"1310":13,"1311":7,"1312":1,"1322":14,"1323":5,"1325":1,"1337":1,"1338":12,"1348":1,"1349":15,"1353":1,"1354":1,"1358":14,"1362":1,"1368":3,"1369":1,"1377":1,"1379":7,"1390":1,"1394":4,"1395":36,"1401":14,"1409":1,"1412":1,"1414":15,"1435":49,"1440":1,"1443":1,"1445":1,"1446":1,"1449":1,"1466":1,"1468":1,"1498":1,"1525":1,"1529":1,"1531":1,"1535":1,"1544":1,"1575":1,"1576":1,"1585":1,"1589":1,"1590":1,"1594":1,"1595":1,"1596":1,"1658":1,"1674":1,"1778":1,"1779":1,"1782":1,"1783":1,"1786":2,"1787":3,"1791":1,"1792":2,"1795":1,"1798":1,"1841":1,"1845":1,"1846":1,"1867":1,"1885":1,"1891":1,"1894":1,"1898":1,"2008":1,"2013":1,"2025":1,"2027":2,"2114":1,"2168":1,"2192":1,"2203":1,"2204":1,"2210":1,"2211":1,"2212":3,"2213":1,"2216":1,"2217":2,"2218":1,"2220":1,"2227":2,"2242":1}}],["microsoft",{"0":{"2104":1},"2":{"0":1,"135":1,"137":1,"138":1,"253":1,"300":2,"401":2,"409":1,"451":1,"452":1,"676":1,"732":1,"733":1,"1090":1,"1091":2,"1213":2,"1247":1,"1259":1,"1270":2,"1299":1,"1300":2,"1310":6,"1311":4,"1322":2,"1323":6,"1338":3,"1368":3,"1415":1,"1416":1,"1426":6,"1476":1,"1610":1,"1684":1,"1685":1,"1721":1,"1726":1,"1727":1,"1814":1,"1864":1,"1914":1,"1915":1,"2011":1,"2042":2,"2098":1,"2099":1,"2104":1,"2119":1,"2136":1,"2176":1,"2177":1,"2179":1},"3":{"0":6,"135":2,"137":1,"138":1,"253":1,"300":2,"397":1,"401":2,"409":1,"413":1,"451":1,"452":1,"609":2,"640":1,"673":1,"676":1,"732":1,"733":1,"868":2,"870":1,"954":1,"980":1,"1018":1,"1036":1,"1090":3,"1091":5,"1116":1,"1154":1,"1212":1,"1213":11,"1215":1,"1244":1,"1247":3,"1259":4,"1270":12,"1285":25,"1299":18,"1300":6,"1308":18,"1309":2,"1310":37,"1311":4,"1322":25,"1323":54,"1338":21,"1358":20,"1368":26,"1379":4,"1394":6,"1395":11,"1401":1,"1414":13,"1415":9,"1416":7,"1421":1,"1424":2,"1426":25,"1431":1,"1435":65,"1436":1,"1437":1,"1440":1,"1444":2,"1445":1,"1452":1,"1454":1,"1458":1,"1464":2,"1465":1,"1466":10,"1471":1,"1476":1,"1480":1,"1484":1,"1485":1,"1487":1,"1488":4,"1576":4,"1585":1,"1610":1,"1653":1,"1670":1,"1684":2,"1685":2,"1721":1,"1726":1,"1727":1,"1814":1,"1826":1,"1864":2,"1914":1,"1915":1,"2011":1,"2042":2,"2057":1,"2098":1,"2099":1,"2100":1,"2104":2,"2107":1,"2119":1,"2136":1,"2176":1,"2177":1,"2179":1,"2211":2,"2212":1,"2216":1,"2217":1,"2219":1,"2223":1,"2237":2,"2239":2,"2240":2}}],["mixes",{"3":{"649":1,"1176":1,"1285":1,"1309":1,"1394":1,"1395":1}}],["mixedentity",{"3":{"1199":2,"1207":1}}],["mixed",{"0":{"92":1,"96":1},"3":{"0":3,"62":1,"91":2,"92":1,"93":1,"95":4,"96":2,"98":2,"99":2,"100":1,"101":1,"102":3,"104":1,"105":1,"229":1,"267":1,"692":1,"790":1,"795":1,"837":1,"838":1,"869":1,"981":1,"1034":1,"1178":1,"1279":1,"1285":4,"1306":1,"1308":2,"1311":1,"1323":2,"1327":2,"1336":1,"1338":7,"1358":1,"1395":1,"1399":1,"1414":1,"1437":1,"1440":1,"1441":1,"1448":1,"1466":2,"1551":1,"1556":1,"1669":1,"1945":1,"1949":1,"2024":3}}],["mix",{"3":{"392":1,"1018":1,"1020":1,"1229":1,"1296":1,"1358":2,"1394":1,"1414":1}}],["mixing",{"3":{"0":1,"95":1,"941":1,"990":1,"1035":1,"1237":1,"1300":1,"1310":1,"1323":1,"1414":1,"1435":1,"1437":1,"1554":1,"1629":1}}],["mitigates",{"3":{"237":1,"1825":1}}],["mitigated",{"3":{"13":1,"61":1,"88":1,"119":1,"188":1,"209":1,"812":1,"947":1,"1055":1,"1473":1,"1525":1,"1866":1,"1963":1}}],["mitigations",{"3":{"692":1,"1533":1,"1547":1}}],["mitigation",{"1":{"232":1,"233":1,"234":1,"235":1,"236":1,"237":1,"238":1,"239":1},"3":{"70":1,"121":1,"188":1,"232":1,"312":1,"318":1,"684":1,"725":1,"743":1,"883":1,"947":1,"1401":1,"1416":1,"1435":1,"1458":1,"1473":1,"1477":1,"1629":1,"1884":1,"1963":1,"2032":2,"2036":1,"2037":1,"2106":1}}],["mit",{"3":{"0":1,"650":1,"682":2,"683":1,"707":1,"708":1,"1059":1,"1322":1,"1660":1,"1947":1,"2238":1}}],["misclassified",{"3":{"1435":1}}],["misconstructed",{"3":{"1426":1}}],["misconfigurations",{"3":{"1437":1}}],["misconfiguration",{"3":{"0":1,"178":1,"214":1,"215":1,"401":1,"501":1,"674":1,"846":1,"1060":1,"1259":1,"1269":1,"1272":1,"1285":5,"1299":2,"1319":2,"1322":4,"1323":2,"1338":3,"1395":1,"1426":1,"1435":1,"1720":1,"1983":1,"2000":1}}],["misconfigured",{"3":{"0":1,"399":1,"415":1,"535":1,"700":1,"792":1,"1212":1,"1259":2,"1264":1,"1270":2,"1288":1,"1299":1,"1310":1,"1316":1,"1323":4,"1331":1,"1338":1,"1395":1,"1416":1,"1418":1,"1426":1,"1429":1,"1442":1,"1669":2,"1822":1,"1893":1,"2011":1,"2013":2,"2158":1,"2160":1}}],["miscounts",{"3":{"607":1}}],["mishandled",{"3":{"1415":1}}],["miswiring",{"3":{"1338":1}}],["misled",{"3":{"1435":1}}],["misleads",{"3":{"1435":1}}],["mislead",{"3":{"1368":1,"1414":1,"1435":2,"1473":1}}],["misleading",{"3":{"1323":1,"1358":1,"1414":1,"1416":1,"1466":1,"1926":1,"2047":1}}],["mislabeled",{"3":{"1310":1,"1358":1}}],["misbehaves",{"3":{"1326":1,"1338":1,"2133":1}}],["misbehaved",{"3":{"1311":1}}],["misbehaving",{"3":{"827":1,"848":1,"1338":1}}],["misrouted",{"3":{"1437":1}}],["misrouting",{"3":{"1310":1,"1437":1}}],["misreported",{"3":{"1322":1,"1395":1,"1435":1}}],["misreports",{"3":{"1110":1,"1338":1,"1480":1}}],["misregistration",{"3":{"1270":1}}],["misreadable",{"3":{"1466":1}}],["misreads",{"3":{"827":1,"1299":1,"1338":1}}],["misread",{"3":{"136":1,"1310":1,"1322":1,"1361":1,"2043":1}}],["misalignment",{"3":{"1495":1}}],["misaligned",{"3":{"1358":1,"1630":1}}],["misattributed",{"3":{"1435":1}}],["misapplication",{"3":{"1310":1}}],["misauthenticates",{"3":{"312":1}}],["misplaced",{"3":{"1435":1}}],["misplacedtokenfixtureservice",{"3":{"1199":2,"1207":1,"1435":2}}],["mispositioned",{"3":{"135":1}}],["misfires",{"3":{"1560":1}}],["misfire",{"3":{"1043":1,"1435":1,"2202":1}}],["mistype",{"3":{"1395":1}}],["mistyped",{"3":{"838":1,"1285":1,"1337":1,"1338":4,"1358":3,"1437":1}}],["mistakes",{"3":{"1310":1}}],["mistaken",{"3":{"109":1,"235":1,"1338":2,"1395":1,"1416":1,"1422":1,"1432":1,"1435":6,"1458":1,"1909":1,"1928":1}}],["mistake",{"3":{"0":1,"122":1,"132":1,"140":1,"215":1,"216":1,"290":1,"391":1,"465":1,"676":1,"727":1,"776":2,"801":1,"828":2,"1041":1,"1066":1,"1132":1,"1241":1,"1247":3,"1259":1,"1270":1,"1285":2,"1299":1,"1310":4,"1316":1,"1322":1,"1327":1,"1332":1,"1358":1,"1368":1,"1435":2,"1685":2,"1926":1,"1929":1,"1938":1,"1942":1,"2042":1,"2055":1,"2099":1,"2105":1,"2106":1,"2107":1,"2151":1,"2152":1,"2182":2}}],["misdiagnose",{"3":{"829":1}}],["misordered",{"3":{"751":1}}],["misordering",{"3":{"749":1,"1824":1}}],["misused",{"3":{"1395":1,"1435":1}}],["misuse",{"3":{"735":1,"1395":2,"1435":1,"1604":1,"1738":1}}],["misnamedtokenfixtureservice",{"3":{"1199":2,"1207":1,"1435":2}}],["misnamed",{"3":{"135":1,"1435":1}}],["mis",{"3":{"111":1,"324":1,"733":1,"1228":1,"1229":1,"1285":2,"1299":1,"1310":1,"1322":2,"1394":1,"1395":2,"1435":4,"1951":1}}],["misshapen",{"3":{"1323":1}}],["misspelled",{"3":{"840":1,"1310":1,"1338":1,"1426":1,"1651":1}}],["miss",{"3":{"157":1,"159":1,"166":1,"171":1,"343":1,"399":1,"404":1,"459":1,"463":1,"538":1,"549":1,"733":2,"734":1,"849":1,"964":1,"995":1,"1000":1,"1006":1,"1107":1,"1118":1,"1162":1,"1237":2,"1241":1,"1242":1,"1247":4,"1263":1,"1267":2,"1268":1,"1270":16,"1278":1,"1279":1,"1282":1,"1285":20,"1299":10,"1300":19,"1308":1,"1310":13,"1322":2,"1323":4,"1338":2,"1349":1,"1354":1,"1358":6,"1368":1,"1387":1,"1394":8,"1395":11,"1414":2,"1416":4,"1426":1,"1435":10,"1437":4,"1442":1,"1443":1,"1537":2,"1599":2,"1650":1,"1652":1,"1654":1,"1667":1,"1683":1,"1707":1,"1809":1,"1848":1,"1908":2,"1910":1,"1916":2,"1922":2,"1944":1,"1988":1,"1997":1,"2005":1,"2047":1,"2134":1,"2154":1,"2155":1,"2158":1}}],["misses",{"3":{"24":1,"159":1,"243":1,"255":1,"618":1,"762":1,"980":1,"1241":1,"1247":4,"1267":1,"1270":3,"1285":1,"1310":1,"1322":1,"1323":2,"1338":1,"1358":2,"1395":4,"1396":1,"1414":1,"1416":1,"1435":5,"1640":1,"1667":1,"1910":1,"1988":1}}],["missed",{"3":{"0":3,"235":1,"301":1,"520":1,"558":1,"627":1,"658":1,"707":2,"890":1,"1036":1,"1041":1,"1099":1,"1101":1,"1174":1,"1176":1,"1212":1,"1269":1,"1270":2,"1271":1,"1285":1,"1299":2,"1300":1,"1308":1,"1310":1,"1322":2,"1323":2,"1333":1,"1338":1,"1358":1,"1394":1,"1395":4,"1398":1,"1423":1,"1426":1,"1435":5,"1437":2,"1458":1,"1466":1,"1473":1,"1576":1,"1584":1,"1707":1,"1708":1,"1828":1,"1917":1,"1932":2,"1947":2,"1960":1,"2137":1,"2182":2,"2191":1,"2192":1,"2231":1}}],["missingsubscription",{"2":{"1471":1},"3":{"1471":1}}],["missingrequired",{"2":{"1686":1},"3":{"1686":2}}],["missingrecordingharness",{"3":{"1199":2,"1207":1}}],["missingroot",{"3":{"1435":1}}],["missingcode",{"3":{"1323":1}}],["missingaccesstokencode",{"3":{"1323":2}}],["missingaccesstoken",{"3":{"1323":2}}],["missingoverridefixture",{"3":{"1199":2,"1207":1,"1435":9}}],["missingtokenfixtureservice",{"3":{"1199":2,"1207":1,"1435":3}}],["missing",{"0":{"1754":1,"1775":1},"3":{"0":8,"67":1,"79":1,"102":1,"108":1,"136":1,"155":1,"159":1,"160":1,"188":1,"214":1,"230":1,"265":1,"283":1,"302":1,"305":1,"318":7,"320":2,"322":1,"323":1,"324":1,"326":2,"342":1,"350":3,"370":1,"371":1,"375":1,"383":1,"411":1,"415":1,"448":2,"484":1,"534":1,"538":2,"549":1,"554":1,"572":1,"573":1,"576":1,"583":1,"584":1,"585":1,"591":1,"599":1,"601":1,"602":1,"609":1,"610":1,"617":1,"626":2,"632":1,"633":2,"642":1,"648":1,"667":1,"673":1,"676":1,"684":1,"700":1,"707":1,"725":1,"757":1,"765":1,"780":1,"790":2,"791":1,"795":1,"819":1,"820":1,"828":1,"838":1,"842":1,"844":1,"871":1,"879":2,"881":1,"888":2,"915":1,"916":1,"921":1,"926":1,"948":1,"972":1,"974":1,"1018":1,"1042":1,"1049":1,"1058":1,"1067":1,"1084":1,"1092":1,"1093":1,"1109":1,"1163":1,"1184":1,"1188":1,"1212":1,"1246":1,"1250":1,"1259":2,"1265":1,"1266":1,"1270":8,"1271":2,"1281":1,"1285":12,"1296":1,"1299":21,"1300":3,"1308":2,"1310":16,"1311":3,"1319":1,"1322":13,"1323":32,"1335":1,"1338":6,"1342":1,"1348":1,"1349":8,"1358":48,"1368":5,"1371":1,"1379":10,"1383":1,"1394":15,"1395":19,"1401":9,"1410":1,"1414":9,"1415":1,"1416":10,"1420":1,"1421":1,"1426":4,"1429":1,"1430":1,"1431":1,"1435":53,"1437":2,"1439":1,"1441":2,"1442":1,"1448":2,"1453":1,"1454":3,"1458":2,"1465":1,"1466":6,"1471":1,"1479":1,"1488":1,"1490":1,"1491":1,"1495":29,"1523":2,"1524":1,"1525":3,"1530":2,"1557":1,"1576":1,"1590":1,"1599":1,"1604":1,"1630":1,"1639":1,"1640":3,"1667":1,"1683":1,"1684":1,"1699":2,"1701":1,"1718":1,"1726":2,"1727":1,"1728":2,"1730":1,"1738":1,"1747":1,"1748":2,"1764":1,"1802":1,"1838":1,"1842":1,"1843":1,"1861":1,"1880":1,"1886":1,"1888":1,"1912":1,"1923":1,"1943":1,"1947":1,"1950":1,"1958":1,"1963":2,"1967":1,"1974":2,"1985":1,"1986":2,"1992":1,"1993":3,"1994":3,"1995":2,"1996":2,"2000":4,"2011":1,"2032":1,"2043":1,"2044":1,"2055":2,"2066":1,"2080":1,"2096":1,"2099":1,"2106":2,"2121":1,"2122":1,"2130":1,"2137":1,"2148":1,"2166":1,"2167":2,"2168":2,"2172":1,"2179":1,"2187":1,"2188":1,"2193":1,"2194":1,"2231":3}}],["mismatched",{"3":{"511":1,"854":1,"1270":1,"1285":4,"1293":1,"1299":6,"1308":1,"1310":1,"1322":1,"1323":1,"1358":1,"1416":1,"1435":4,"1640":1,"1764":1}}],["mismatches",{"2":{"1996":1},"3":{"0":2,"501":1,"1270":2,"1414":1,"1495":4,"1634":1,"1669":1,"1760":1,"1996":1,"2231":1}}],["mismatch",{"3":{"0":1,"91":1,"245":1,"251":1,"254":1,"257":1,"259":1,"264":1,"318":1,"319":1,"322":2,"323":1,"350":1,"452":1,"499":2,"500":2,"609":1,"658":1,"659":1,"732":1,"907":1,"926":1,"1069":1,"1212":1,"1234":1,"1237":1,"1270":10,"1285":4,"1299":4,"1300":1,"1310":4,"1322":3,"1323":3,"1338":3,"1349":2,"1358":7,"1368":1,"1395":2,"1401":1,"1414":2,"1415":1,"1416":1,"1426":1,"1435":6,"1453":1,"1466":1,"1489":1,"1576":2,"1584":1,"1746":1,"1763":1,"1901":1,"1921":1,"1954":1,"1994":1,"1996":1,"2073":1,"2231":1}}],["midpointrounding",{"3":{"1358":1,"1368":1}}],["midsavecontextcreatingdbcontext",{"2":{"1497":1},"3":{"1198":1,"1199":3,"1207":1,"1495":1,"1497":1}}],["midnight",{"3":{"403":1,"612":1,"1347":1,"1349":1,"1354":2,"1392":1,"1394":6,"1630":1}}],["middle",{"0":{"139":1,"1422":1,"2052":1},"3":{"27":1,"130":1,"135":1,"139":1,"602":1,"1092":2,"1124":1,"1271":1,"1285":1,"1299":1,"1322":1,"1323":6,"1349":1,"1368":1,"1394":1,"1395":3,"1416":2,"1435":7,"1684":1,"1793":1,"1799":1,"1817":1,"1886":1,"1949":1,"2058":1,"2059":1,"2072":1,"2169":1,"2235":1}}],["middlewares",{"3":{"829":1,"1310":2}}],["middlewarepipelinestep",{"3":{"749":1,"1199":3,"1203":1,"1207":2,"1310":9}}],["middlewarepipelinestepnames",{"2":{"243":1,"256":1,"258":1,"259":1,"696":1,"749":1,"751":1,"1310":2},"3":{"243":2,"256":3,"258":2,"259":1,"261":1,"459":1,"696":1,"698":2,"749":2,"751":1,"1199":4,"1203":1,"1207":2,"1310":13,"1435":4}}],["middlewarepipelineordertests",{"2":{"572":1},"3":{"572":2,"749":4,"1199":2,"1207":4,"1310":1,"1435":10,"1437":1,"1486":1,"1487":3,"1488":3}}],["middlewarepipelineordertestsbase",{"2":{"572":1,"696":1,"698":1,"747":1,"749":1,"751":1,"1427":1,"1487":1,"2054":1},"3":{"0":1,"459":2,"464":2,"572":2,"696":1,"698":1,"747":1,"749":2,"751":1,"1199":3,"1207":2,"1310":8,"1427":1,"1435":15,"1437":1,"1487":5,"1488":1,"2054":1}}],["middlewarepipelinebuildertests",{"3":{"776":1,"777":1,"1199":1,"1207":2,"1310":1,"1437":1}}],["middlewarepipelinebuilder",{"2":{"103":1,"179":1,"259":1,"457":1,"459":2,"464":2,"465":1,"466":1,"696":1,"698":1,"747":3,"749":3,"1495":1,"1665":1},"3":{"0":1,"103":2,"179":2,"243":1,"256":1,"258":1,"259":1,"261":1,"457":1,"459":5,"464":6,"465":1,"466":1,"696":1,"698":5,"747":3,"749":14,"751":2,"774":2,"777":1,"1108":1,"1111":1,"1124":1,"1150":1,"1156":1,"1198":1,"1199":5,"1203":1,"1207":2,"1300":2,"1310":31,"1435":7,"1437":1,"1495":3,"1665":1,"2065":1}}],["middleware",{"0":{"2148":1},"1":{"211":1,"212":1,"213":1,"214":1,"215":1,"216":1,"217":1,"218":1,"219":1,"220":1,"221":1,"746":1,"747":1,"748":1,"749":1,"750":1,"751":1,"752":1,"753":1,"1310":1,"2146":1,"2147":1,"2148":1,"2149":1,"2150":1,"2151":1,"2152":1},"3":{"0":10,"31":2,"33":1,"57":1,"59":3,"86":1,"109":7,"118":1,"125":2,"135":1,"174":1,"175":1,"182":1,"211":1,"212":1,"214":3,"215":1,"217":1,"219":1,"234":1,"235":1,"236":1,"237":2,"243":1,"256":1,"265":1,"280":1,"282":1,"301":1,"306":1,"342":1,"397":2,"407":2,"409":2,"415":2,"459":11,"460":4,"462":3,"463":6,"464":4,"465":1,"466":2,"503":1,"571":1,"573":1,"696":2,"698":2,"700":1,"702":1,"703":1,"741":1,"746":1,"748":2,"749":5,"750":1,"753":1,"773":1,"774":1,"775":1,"778":2,"825":2,"826":2,"827":6,"833":1,"910":1,"1125":1,"1126":1,"1129":1,"1192":2,"1202":2,"1203":12,"1207":32,"1212":9,"1222":1,"1229":5,"1270":2,"1285":5,"1295":1,"1296":1,"1299":24,"1300":2,"1303":1,"1308":1,"1309":1,"1310":75,"1311":2,"1322":6,"1323":14,"1333":1,"1335":3,"1336":1,"1337":1,"1338":36,"1358":1,"1369":1,"1378":1,"1379":1,"1395":3,"1414":3,"1415":2,"1428":1,"1435":19,"1437":4,"1440":1,"1442":2,"1446":3,"1485":1,"1486":2,"1487":3,"1495":3,"1525":1,"1539":1,"1576":2,"1610":1,"1614":1,"1616":1,"1658":1,"1659":1,"1665":2,"1669":1,"1670":1,"1752":1,"1773":1,"1824":1,"1826":3,"1872":1,"1919":1,"1933":1,"1967":2,"1971":2,"1972":1,"1974":1,"1997":1,"1999":3,"2023":2,"2065":3,"2082":1,"2145":1,"2146":2,"2148":2,"2150":1,"2152":4,"2159":1,"2207":1,"2226":1,"2229":2,"2231":5}}],["mid",{"0":{"2065":1},"3":{"0":3,"20":1,"26":1,"194":1,"517":1,"674":1,"691":1,"707":1,"741":1,"743":1,"759":1,"767":1,"881":1,"954":1,"996":1,"998":1,"1042":1,"1101":1,"1247":1,"1251":1,"1255":2,"1259":3,"1267":1,"1270":4,"1276":1,"1285":6,"1299":1,"1310":6,"1322":8,"1323":8,"1338":1,"1358":3,"1368":1,"1379":2,"1389":1,"1394":8,"1395":15,"1399":1,"1401":1,"1414":2,"1416":3,"1431":1,"1435":6,"1437":1,"1454":1,"1455":1,"1459":1,"1474":2,"1487":2,"1495":3,"1683":1,"1748":1,"1764":1,"1765":1,"1834":1,"1840":1,"1888":1,"1908":1,"1910":1,"1947":1,"2034":1,"2065":1,"2068":1,"2157":1,"2187":1,"2231":1}}],["minoptions",{"3":{"1397":1,"1401":10}}],["minority",{"3":{"689":1,"741":1,"1379":1}}],["minor",{"2":{"1732":2},"3":{"147":1,"1004":1,"1075":1,"1323":1,"1495":1,"1516":1,"1525":3,"1531":1,"1536":1,"1575":1,"1576":1,"1590":2,"1598":1,"1672":1,"1674":1,"1732":3,"1734":1}}],["mind",{"3":{"1322":1,"1354":1,"1358":1,"1397":1}}],["mincount",{"3":{"1237":1}}],["minvalue",{"2":{"1351":1},"3":{"1299":1,"1310":2,"1349":2,"1351":1,"1358":1,"1368":1,"1394":2,"1464":1}}],["minvaluespec",{"3":{"1199":2,"1207":1,"1495":1}}],["minverskip=true",{"3":{"1453":1}}],["minver",{"3":{"0":1,"147":1,"939":1,"1212":1,"1213":1,"1452":1,"1453":3,"1576":1,"1732":1,"2111":2}}],["mini",{"3":{"2238":1}}],["miniprofiler",{"3":{"1262":2,"1270":6,"1278":1,"1283":1,"1285":15,"1310":4,"1315":1,"1322":1,"1437":1,"1663":1,"1752":1,"1773":2}}],["miniprofilerextensions",{"3":{"980":1,"1199":1,"1203":1,"1207":2,"1208":1,"1270":1,"1310":4,"1322":2}}],["miniature",{"0":{"1391":1},"3":{"1217":1,"1230":1,"1290":1,"1299":2,"1300":1,"1308":2,"1349":1,"1358":1,"1394":1,"1416":1,"1430":1,"1435":1,"2089":1}}],["minimisation",{"3":{"1395":1}}],["minimizing",{"3":{"1395":1,"1444":1,"1457":1}}],["minimizes",{"3":{"1394":1}}],["minimized",{"3":{"1358":1}}],["minimization",{"3":{"1237":1,"1358":2,"1395":1,"1414":2,"1416":1}}],["minimaltlsversion",{"3":{"1466":1}}],["minimality",{"3":{"1323":1}}],["minimalism",{"3":{"1299":1}}],["minimal",{"3":{"0":1,"31":1,"59":1,"448":2,"632":1,"674":1,"1004":1,"1050":1,"1213":1,"1229":1,"1237":1,"1247":1,"1259":2,"1283":1,"1285":6,"1288":1,"1299":8,"1308":1,"1310":8,"1312":1,"1322":4,"1323":6,"1338":1,"1349":2,"1375":1,"1379":2,"1394":2,"1395":2,"1401":3,"1414":2,"1415":3,"1416":1,"1428":1,"1429":1,"1435":21,"1437":1,"1440":1,"1442":1,"1444":1,"1453":1,"1488":1,"1491":1,"1541":1,"1545":1,"1554":1,"1562":1,"1637":2,"1657":1,"1809":1,"1879":1,"1897":1,"2054":1,"2093":1,"2130":2,"2220":1,"2227":1,"2231":1}}],["minimumtlsversion",{"3":{"1466":1}}],["minimumthroughput",{"2":{"100":1},"3":{"100":1,"819":1,"1311":2,"1338":2}}],["minimumroutes",{"3":{"1435":2}}],["minimumrazorfiles",{"3":{"1435":1}}],["minimumcreateforms=4",{"3":{"1590":1,"1599":1}}],["minimumcreateforms",{"3":{"1435":2,"1488":1,"1595":1}}],["minimumcodebehindfiles",{"3":{"1435":3,"1590":1}}],["minimumcommonpackagecount",{"3":{"1435":2}}],["minimumcommands",{"3":{"1435":2,"1488":2}}],["minimumcontainerapps",{"3":{"1435":1}}],["minimumbaseresources=25",{"3":{"1590":1}}],["minimumbaseresources",{"3":{"1435":4,"1488":1}}],["minimumscannedfiles=40",{"3":{"1590":1}}],["minimumscannedfiles=60",{"3":{"1525":1}}],["minimumscannedfiles",{"3":{"1435":5,"1488":1}}],["minimumscannedtypes",{"3":{"1435":7}}],["minimumsimilarity",{"2":{"1375":1},"3":{"1358":6,"1375":1,"1379":1}}],["minimumgovernedpages",{"3":{"1430":1,"1435":1,"1525":1}}],["minimumlevel",{"3":{"1338":1}}],["minimumlength",{"3":{"827":1,"1271":1,"1338":2}}],["minimumerrorcodes",{"2":{"1271":1},"3":{"1271":1,"1435":2}}],["minimumwait",{"3":{"1259":1,"1285":3,"1322":2}}],["minimumpathcount=10",{"3":{"1525":1,"1530":1}}],["minimumpathcountbecause",{"3":{"1435":1}}],["minimumpathcount",{"2":{"574":1},"3":{"574":1,"1435":3,"1487":1}}],["minimum",{"3":{"0":1,"24":1,"71":1,"74":1,"101":1,"130":1,"132":1,"137":1,"140":1,"142":1,"215":1,"401":1,"530":1,"610":1,"635":1,"821":1,"1018":2,"1020":1,"1215":1,"1259":2,"1271":2,"1299":1,"1308":1,"1309":1,"1322":1,"1329":2,"1332":1,"1338":3,"1358":6,"1395":2,"1401":2,"1414":4,"1415":1,"1430":1,"1435":6,"1440":1,"1448":1,"1453":2,"1454":6,"1459":1,"1460":1,"1464":1,"1466":1,"1476":1,"1485":2,"1489":1,"1491":3,"1525":1,"1576":2,"1585":1,"1590":1,"1601":1,"1639":1,"1640":2,"1677":1,"1704":1,"1713":3,"2029":1,"2031":1,"2044":1,"2046":1,"2057":1,"2104":1,"2106":1}}],["minimumalertspecs",{"2":{"607":1,"1475":1,"2045":1},"3":{"0":2,"607":2,"609":2,"1018":1,"1435":7,"1475":1,"1525":1,"2045":2}}],["minlength",{"3":{"657":1,"1237":2,"1299":1,"1466":1,"1471":1,"1473":1}}],["minratio",{"3":{"591":1}}],["minreplicas",{"2":{"291":1,"293":1,"995":1,"1154":1,"1156":1,"1255":1,"1468":1,"1840":1,"2032":1},"3":{"0":1,"20":1,"291":2,"293":1,"390":2,"995":1,"1154":1,"1156":1,"1255":1,"1259":2,"1454":1,"1466":4,"1468":2,"1473":1,"1525":2,"1533":1,"1840":1,"2032":1}}],["mine=true",{"3":{"1390":1,"1394":1,"1415":2}}],["mine",{"3":{"320":1,"1237":1,"1394":1,"1395":1,"1401":1,"1747":1,"1758":1,"1764":1,"1995":1}}],["minus",{"3":{"22":1,"707":1,"743":1,"1019":1,"1262":1,"1271":1,"1285":2,"1308":2,"1310":2,"1323":1,"1338":1,"1358":2,"1365":1,"1379":4,"1395":3,"1416":1,"1434":1,"1435":3,"1437":1,"1454":1,"1479":1,"1506":1,"1661":1,"1764":1,"1826":1}}],["minutes",{"2":{"539":1,"1474":1},"3":{"0":3,"18":1,"19":2,"22":1,"237":1,"386":1,"387":3,"392":2,"499":1,"517":1,"530":1,"535":1,"538":1,"539":6,"540":1,"609":2,"611":1,"619":1,"625":2,"627":3,"640":2,"675":1,"708":1,"757":1,"759":5,"760":2,"813":1,"818":1,"819":1,"905":1,"996":1,"1018":1,"1019":1,"1029":1,"1067":1,"1069":1,"1099":2,"1212":1,"1264":1,"1287":1,"1293":1,"1294":1,"1299":6,"1304":1,"1310":3,"1311":2,"1322":3,"1323":4,"1327":2,"1329":1,"1338":3,"1349":2,"1357":1,"1358":9,"1375":1,"1379":1,"1389":1,"1394":6,"1395":6,"1396":1,"1415":1,"1416":2,"1429":1,"1435":2,"1448":1,"1452":2,"1454":9,"1455":5,"1459":2,"1464":1,"1465":1,"1466":14,"1472":1,"1473":6,"1474":8,"1475":6,"1477":1,"1485":1,"1489":2,"1491":1,"1495":1,"1576":3,"1581":2,"1629":1,"1630":1,"1631":2,"1637":1,"1638":1,"1640":2,"1766":1,"1842":2,"1921":1,"1922":1,"1941":1,"1979":1,"1981":1,"1984":1,"2011":1,"2032":2,"2033":3,"2035":1,"2037":1,"2049":1,"2065":1,"2157":2,"2160":2,"2166":1,"2167":1}}],["minute",{"0":{"1718":1},"3":{"0":8,"175":2,"178":2,"179":1,"260":1,"280":1,"282":1,"350":1,"390":1,"391":1,"392":2,"422":1,"459":1,"517":1,"536":2,"591":1,"593":1,"607":1,"609":1,"626":1,"627":1,"640":1,"709":1,"759":1,"766":1,"809":2,"819":1,"854":2,"862":1,"914":1,"916":1,"1018":2,"1029":1,"1066":1,"1124":2,"1184":1,"1212":2,"1264":2,"1270":3,"1285":1,"1287":1,"1288":1,"1291":1,"1299":5,"1310":13,"1322":2,"1323":1,"1329":2,"1338":4,"1341":2,"1349":1,"1356":1,"1357":2,"1358":11,"1361":1,"1366":3,"1367":1,"1368":1,"1378":1,"1379":8,"1389":1,"1394":3,"1395":3,"1435":1,"1437":4,"1442":1,"1446":1,"1448":2,"1454":4,"1455":4,"1457":1,"1458":2,"1459":2,"1462":1,"1466":15,"1468":2,"1473":1,"1474":3,"1475":2,"1495":1,"1524":1,"1525":2,"1530":2,"1533":1,"1534":1,"1571":1,"1590":1,"1598":1,"1630":1,"1638":2,"1640":1,"1666":1,"1806":1,"1826":1,"1842":1,"1845":1,"1852":1,"1867":1,"1891":1,"1898":1,"1921":1,"1949":1,"1957":1,"1964":1,"1971":1,"1974":3,"1978":1,"1979":1,"1984":1,"1990":1,"1996":1,"1997":3,"1999":1,"2000":2,"2001":1,"2036":2,"2037":1,"2047":1,"2068":1,"2086":1,"2107":1,"2127":1,"2132":1,"2138":1,"2152":1,"2157":3,"2159":1,"2166":2,"2168":1,"2179":1,"2189":2,"2192":1,"2202":1}}],["minter",{"3":{"1395":1}}],["minted",{"3":{"0":7,"207":1,"208":1,"209":1,"350":1,"443":1,"470":1,"664":1,"690":1,"788":1,"790":2,"793":2,"794":3,"805":1,"826":1,"827":1,"829":1,"880":1,"881":2,"882":1,"905":1,"1067":1,"1116":2,"1212":3,"1259":1,"1264":1,"1270":2,"1292":2,"1299":14,"1308":1,"1310":6,"1322":1,"1323":2,"1335":1,"1338":2,"1341":1,"1347":1,"1349":1,"1356":1,"1379":2,"1394":6,"1395":10,"1408":1,"1414":1,"1416":1,"1435":1,"1437":1,"1441":1,"1466":2,"1489":1,"1630":2,"1640":1,"1666":1,"1764":1,"1892":1,"1967":2,"1969":2,"1970":1,"1972":1,"1981":1,"2165":1,"2196":1,"2197":1,"2231":1}}],["mint",{"3":{"30":1,"32":1,"177":1,"499":1,"500":1,"664":2,"665":2,"670":2,"792":3,"828":1,"881":1,"883":1,"904":1,"905":2,"906":1,"926":1,"1212":1,"1278":1,"1285":2,"1291":1,"1292":1,"1299":16,"1310":3,"1322":3,"1323":3,"1325":1,"1335":1,"1338":1,"1368":1,"1379":1,"1394":4,"1395":1,"1414":2,"1435":3,"1437":1,"1440":1,"1453":1,"1454":1,"1466":3,"1471":1,"1581":1,"1584":1,"1666":1,"1764":1,"1766":1,"1893":1,"1898":1,"1910":1,"1969":1,"1979":1,"2062":1,"2065":1,"2196":1,"2231":1}}],["mints",{"3":{"0":1,"32":1,"186":1,"349":1,"350":2,"353":1,"458":1,"499":1,"602":1,"665":1,"667":1,"668":2,"792":1,"827":1,"829":1,"880":1,"885":1,"905":3,"907":1,"1059":1,"1069":1,"1249":1,"1259":2,"1270":1,"1287":1,"1288":1,"1289":2,"1293":1,"1299":22,"1300":1,"1308":2,"1310":2,"1321":1,"1322":1,"1323":4,"1327":1,"1336":1,"1338":2,"1342":1,"1379":3,"1394":4,"1395":8,"1406":1,"1413":1,"1414":2,"1416":1,"1428":1,"1429":1,"1435":1,"1437":1,"1448":1,"1459":1,"1466":1,"1471":1,"1474":1,"1487":1,"1489":1,"1588":1,"1595":1,"1626":1,"1640":1,"1670":1,"1731":1,"1892":2,"1898":1,"1971":1,"1974":2,"1981":1,"1984":1,"2023":1,"2055":1,"2201":1}}],["mintable",{"3":{"0":1}}],["minting",{"3":{"0":2,"126":1,"295":1,"333":1,"351":1,"400":1,"827":1,"881":1,"908":1,"1030":1,"1116":1,"1259":1,"1263":1,"1270":1,"1285":1,"1286":1,"1294":1,"1299":5,"1303":1,"1308":2,"1323":1,"1336":1,"1338":1,"1347":1,"1349":3,"1376":2,"1379":1,"1394":1,"1395":8,"1401":1,"1408":2,"1414":2,"1416":2,"1431":1,"1444":1,"1446":1,"1448":1,"1454":1,"1490":1,"1971":1,"2023":1,"2165":1}}],["min",{"3":{"0":3,"24":1,"68":2,"177":1,"281":1,"330":1,"609":2,"612":1,"733":1,"1237":1,"1244":1,"1259":1,"1285":1,"1299":1,"1310":3,"1323":3,"1338":2,"1395":1,"1414":1,"1421":1,"1442":2,"1454":2,"1466":4,"1473":14,"1524":1,"1525":1,"1533":1,"1534":1,"1575":1,"1576":2,"1589":1,"1590":2,"1595":1,"1598":1,"1599":2,"1631":1,"1708":2,"1709":1,"1711":1,"1746":1,"1752":1,"1764":1,"1987":1,"1999":1}}],["mirrors",{"3":{"0":4,"26":1,"199":1,"247":1,"265":1,"268":1,"269":1,"321":1,"324":1,"351":1,"355":1,"362":1,"388":1,"471":1,"556":1,"599":1,"626":1,"644":1,"653":1,"698":2,"707":1,"725":1,"766":1,"832":1,"857":1,"881":1,"1042":1,"1059":2,"1063":1,"1067":1,"1068":1,"1108":1,"1212":3,"1229":2,"1237":4,"1242":1,"1247":1,"1259":2,"1270":3,"1271":1,"1285":6,"1294":1,"1299":1,"1300":2,"1302":1,"1308":1,"1309":2,"1310":8,"1311":3,"1321":1,"1322":4,"1323":16,"1338":4,"1349":10,"1352":1,"1358":11,"1368":4,"1371":1,"1373":1,"1378":2,"1379":3,"1383":1,"1394":6,"1395":10,"1401":6,"1406":1,"1411":1,"1413":1,"1414":5,"1415":1,"1416":2,"1429":1,"1435":16,"1437":1,"1440":1,"1443":1,"1454":2,"1489":1,"1525":1,"1560":1,"1569":1,"1590":2,"1598":1,"1652":1,"1661":1,"1665":1,"1824":1,"1921":1,"1926":1,"1967":1,"1994":1,"2017":1,"2074":1,"2075":1,"2186":1,"2190":1}}],["mirror",{"0":{"1607":1,"1741":1},"3":{"0":3,"122":1,"162":1,"181":1,"217":1,"219":1,"241":1,"346":1,"407":1,"448":1,"528":1,"576":1,"707":1,"729":1,"803":1,"860":1,"931":1,"1034":1,"1042":1,"1212":1,"1225":1,"1227":1,"1228":1,"1229":3,"1237":3,"1253":2,"1258":1,"1270":1,"1282":1,"1285":1,"1299":3,"1300":1,"1308":4,"1309":1,"1310":3,"1323":17,"1338":3,"1339":1,"1349":4,"1358":13,"1379":3,"1383":1,"1394":6,"1395":10,"1397":1,"1400":1,"1401":3,"1414":2,"1415":5,"1431":1,"1435":3,"1443":1,"1530":1,"1534":1,"1647":1,"1648":1,"1666":1,"1668":1,"1684":1,"1686":2,"1701":1,"1834":1,"1850":1,"1921":1,"1929":1,"1952":1,"2130":1,"2155":1,"2164":1,"2187":1}}],["mirroring",{"3":{"0":1,"53":1,"145":1,"243":1,"246":1,"698":1,"715":1,"799":1,"854":1,"1034":1,"1116":1,"1124":1,"1229":2,"1244":1,"1247":1,"1259":1,"1285":3,"1299":1,"1308":2,"1311":1,"1322":1,"1323":4,"1349":2,"1358":2,"1379":2,"1394":3,"1395":2,"1415":1,"1416":3,"1426":1,"1435":6,"1485":1,"1595":1,"1630":1,"1644":1,"1824":1,"1994":1,"2101":1}}],["mirrored",{"3":{"0":2,"109":1,"147":1,"374":1,"618":1,"649":1,"1067":1,"1068":1,"1211":1,"1290":1,"1311":1,"1323":3,"1329":1,"1338":3,"1343":1,"1349":1,"1395":4,"1416":2,"1435":1,"1442":1,"1477":1,"1487":1,"1525":1,"1531":1,"1590":1,"1668":1,"1672":1,"2055":1,"2078":1}}],["mezzanine",{"3":{"1358":1,"1629":1}}],["meziantou",{"2":{"138":1},"3":{"138":1,"265":1,"1213":1,"1452":1,"1576":1,"1672":1,"1902":1}}],["mega",{"3":{"1322":1}}],["megabytes",{"3":{"1394":3}}],["megabyte",{"3":{"390":1,"1117":1,"1125":1,"1323":1,"1358":1}}],["me",{"0":{"1786":1},"3":{"1228":2,"1229":2,"1270":1,"1285":2,"1299":1,"1308":1,"1310":1,"1311":1,"1323":1,"1358":4,"1377":1,"1394":1,"1395":12,"1401":1,"1414":9,"1416":1,"1435":1,"1630":2,"1639":3,"1786":1,"1858":1,"1879":1,"2097":1,"2165":1,"2168":1,"2210":1,"2242":1,"2243":1}}],["mesh",{"3":{"702":1,"1311":2,"1395":1}}],["messagingentitynotfoundexception",{"3":{"1322":1}}],["messagingexception",{"3":{"1322":2}}],["messaging",{"0":{"1546":1,"1664":1},"1":{"928":1,"929":1,"930":1,"931":1,"932":1,"933":1,"934":1,"935":1},"2":{"130":1,"150":1,"152":1,"203":1,"917":1,"929":1,"1074":1,"1075":1,"1077":1,"1078":2,"1259":1,"1322":1},"3":{"0":4,"15":1,"25":1,"26":2,"27":5,"68":1,"70":1,"74":1,"130":1,"135":1,"139":2,"145":1,"150":15,"152":2,"166":1,"171":1,"193":2,"196":2,"198":1,"200":1,"201":2,"202":6,"203":5,"255":1,"256":1,"259":1,"391":1,"638":1,"640":12,"641":2,"642":2,"644":1,"674":1,"702":4,"818":1,"819":5,"821":1,"822":2,"846":1,"849":1,"915":2,"917":1,"928":1,"929":1,"930":1,"931":4,"933":1,"934":2,"980":3,"982":2,"983":1,"1006":1,"1012":1,"1013":1,"1074":9,"1075":3,"1077":1,"1078":2,"1175":2,"1203":16,"1207":135,"1208":2,"1212":3,"1213":1,"1216":1,"1237":2,"1247":2,"1257":5,"1258":2,"1259":84,"1270":3,"1285":4,"1299":8,"1308":1,"1310":4,"1315":2,"1316":4,"1317":3,"1319":5,"1322":71,"1338":2,"1358":1,"1395":1,"1414":2,"1416":1,"1435":6,"1437":7,"1440":1,"1442":1,"1450":1,"1495":4,"1498":1,"1518":1,"1523":1,"1525":3,"1526":1,"1529":1,"1530":3,"1531":1,"1532":1,"1535":1,"1538":1,"1550":1,"1576":7,"1577":1,"1581":1,"1582":1,"1584":1,"1585":1,"1587":1,"1588":1,"1590":2,"1591":1,"1594":1,"1595":2,"1596":1,"1597":1,"1601":1,"1655":1,"1659":1,"1661":1,"1672":1,"1674":1,"1706":2,"1717":1,"1720":1,"1783":1,"1795":1,"1923":1,"1935":1,"2007":1,"2181":1,"2211":1,"2212":1,"2217":1,"2231":1}}],["messagepack",{"3":{"370":1,"1576":1}}],["messageheaders",{"2":{"202":1},"3":{"202":2,"702":1,"1199":5,"1203":1,"1207":2,"1259":5,"1299":6,"1322":2,"1495":1}}],["messageid",{"2":{"77":1,"195":1,"197":1,"200":1,"202":1,"203":2,"786":1,"847":1,"1273":1,"1507":1,"1844":1,"1887":1,"1888":1},"3":{"0":2,"77":1,"195":7,"197":2,"200":2,"201":2,"202":1,"203":2,"782":1,"786":1,"846":2,"847":1,"1074":1,"1212":2,"1247":4,"1249":3,"1258":1,"1259":26,"1269":1,"1270":2,"1273":1,"1285":1,"1308":1,"1322":2,"1349":3,"1435":2,"1437":2,"1507":1,"1576":2,"1844":1,"1887":1,"1888":1,"1909":4}}],["messages=",{"3":{"1435":1}}],["messages",{"3":{"0":1,"21":1,"24":1,"25":1,"27":1,"53":1,"109":1,"117":1,"195":1,"198":1,"225":1,"255":1,"265":1,"397":1,"433":1,"538":1,"629":2,"640":1,"656":1,"790":1,"810":1,"819":1,"1012":1,"1018":1,"1026":1,"1082":1,"1091":2,"1107":1,"1150":1,"1229":1,"1237":1,"1247":2,"1255":1,"1259":13,"1270":1,"1271":3,"1273":1,"1285":4,"1295":1,"1299":4,"1300":1,"1301":1,"1308":7,"1310":3,"1314":1,"1322":11,"1323":11,"1332":1,"1338":2,"1358":18,"1376":1,"1379":4,"1393":1,"1394":9,"1395":11,"1401":3,"1414":7,"1419":1,"1422":3,"1423":1,"1426":6,"1434":2,"1435":14,"1436":1,"1466":3,"1576":1,"1640":1,"1652":1,"1676":1,"1685":1,"1701":1,"1726":1,"1816":1,"1830":1,"1840":1,"1844":1,"1845":1,"1935":1,"1942":1,"2043":1,"2080":1,"2155":1,"2171":1}}],["message",{"0":{"1942":1,"2019":1},"1":{"816":1,"817":1,"818":1,"819":1,"820":1,"821":1,"822":1,"823":1,"1072":1,"1073":1,"1074":1,"1075":1,"1076":1,"1077":1,"1078":1,"1079":1},"2":{"301":1,"818":1,"1842":1},"3":{"0":10,"17":1,"19":4,"22":2,"24":4,"26":1,"27":1,"71":1,"72":1,"77":1,"109":11,"111":2,"113":1,"125":1,"136":1,"170":2,"193":2,"195":2,"196":1,"197":1,"198":1,"200":1,"201":4,"202":2,"219":1,"255":1,"265":9,"266":1,"267":1,"284":1,"290":1,"300":1,"301":1,"306":1,"342":1,"352":1,"353":1,"354":1,"391":1,"397":5,"399":2,"434":1,"536":2,"537":2,"538":2,"539":1,"546":1,"568":1,"574":1,"583":1,"624":1,"626":3,"628":1,"629":1,"640":2,"657":1,"659":1,"673":1,"674":1,"684":1,"688":1,"690":2,"691":1,"692":2,"696":1,"702":2,"766":3,"768":1,"789":3,"790":2,"816":1,"818":7,"819":7,"820":2,"821":2,"823":1,"838":1,"845":1,"846":1,"862":1,"875":1,"882":1,"905":1,"931":1,"963":1,"972":1,"973":1,"988":1,"989":1,"996":1,"1004":1,"1012":2,"1017":1,"1018":3,"1019":2,"1043":1,"1067":1,"1072":1,"1074":1,"1075":2,"1091":3,"1092":1,"1093":1,"1100":1,"1108":1,"1126":1,"1133":1,"1134":1,"1140":1,"1161":1,"1162":1,"1169":1,"1175":1,"1192":1,"1202":1,"1203":1,"1212":3,"1219":1,"1225":1,"1229":9,"1230":1,"1237":9,"1247":10,"1248":1,"1249":1,"1253":2,"1256":2,"1258":3,"1259":81,"1264":1,"1269":1,"1270":23,"1271":27,"1274":1,"1279":2,"1283":1,"1285":47,"1296":2,"1298":1,"1299":28,"1300":2,"1301":3,"1305":1,"1308":11,"1310":42,"1311":18,"1314":1,"1315":1,"1316":3,"1317":3,"1319":1,"1322":93,"1323":87,"1332":1,"1337":1,"1338":12,"1349":15,"1351":1,"1352":1,"1354":1,"1358":148,"1365":3,"1368":1,"1379":9,"1383":1,"1394":44,"1395":59,"1401":38,"1405":1,"1412":1,"1413":1,"1414":24,"1416":12,"1419":1,"1421":1,"1422":2,"1423":1,"1426":20,"1428":1,"1429":1,"1434":1,"1435":170,"1437":14,"1440":1,"1442":1,"1454":1,"1466":6,"1487":4,"1488":2,"1491":1,"1495":1,"1523":1,"1525":3,"1530":1,"1542":1,"1546":4,"1574":1,"1576":3,"1584":1,"1590":3,"1595":1,"1638":1,"1639":3,"1640":2,"1644":1,"1652":1,"1664":1,"1668":1,"1676":4,"1684":1,"1685":2,"1701":1,"1751":1,"1768":1,"1775":1,"1789":1,"1801":1,"1804":1,"1814":1,"1823":2,"1830":2,"1839":1,"1840":2,"1842":9,"1851":1,"1872":2,"1875":1,"1886":1,"1888":2,"1890":1,"1909":3,"1911":1,"1921":5,"1923":1,"1926":2,"1941":2,"1976":1,"1977":1,"1984":1,"1993":1,"2002":1,"2005":1,"2009":1,"2011":2,"2014":1,"2027":1,"2031":2,"2043":1,"2046":1,"2068":1,"2073":1,"2082":3,"2085":2,"2091":1,"2098":1,"2125":1,"2127":1,"2149":1,"2153":1,"2156":2,"2163":1,"2164":1,"2165":2,"2171":1,"2173":1,"2178":1,"2179":1,"2180":2,"2181":2,"2199":1,"2226":2,"2231":3,"2234":1,"2236":1}}],["messagebusbackpressuretests",{"2":{"1590":1},"3":{"1590":2,"1595":1}}],["messagebusprovider",{"2":{"150":1,"200":1,"201":1,"931":1,"1075":1,"1445":1,"1841":2},"3":{"0":2,"150":1,"200":1,"201":2,"640":1,"931":1,"964":1,"1075":1,"1199":4,"1203":1,"1207":1,"1212":1,"1319":1,"1322":4,"1325":1,"1338":3,"1445":1,"1841":2}}],["messagebus",{"2":{"640":2,"642":1,"644":1,"915":1,"933":1,"1440":1,"2012":1},"3":{"0":7,"15":3,"24":1,"25":1,"27":1,"193":2,"195":1,"198":2,"200":1,"203":1,"640":9,"642":1,"644":1,"819":1,"914":1,"915":1,"930":1,"931":3,"932":1,"933":2,"1212":2,"1257":2,"1259":10,"1285":1,"1322":6,"1325":1,"1326":1,"1338":4,"1428":1,"1435":2,"1440":4,"1445":3,"1450":1,"1466":4,"1507":1,"1576":1,"1581":1,"1590":1,"1611":1,"1652":1,"1662":1,"1664":1,"1674":1,"1720":1,"1723":2,"1841":1,"1842":1,"1844":1,"1909":1,"1910":1,"2007":2,"2012":1,"2021":1}}],["messagebussettingstests",{"3":{"1199":1,"1207":1}}],["messagebussettings",{"2":{"0":1,"145":1,"152":1,"201":1,"638":1,"644":1,"822":1,"929":1,"931":1,"1257":1,"1706":2,"1789":1,"1841":2,"1909":2,"2019":1,"2025":1},"3":{"0":1,"15":1,"24":1,"25":1,"27":2,"145":2,"150":1,"152":1,"193":1,"196":1,"198":1,"200":2,"201":2,"203":1,"638":1,"640":4,"644":1,"674":1,"818":1,"819":2,"822":1,"915":1,"929":1,"930":1,"931":2,"1199":11,"1203":1,"1207":3,"1257":3,"1259":20,"1285":2,"1312":1,"1315":1,"1319":2,"1322":24,"1325":1,"1338":3,"1435":2,"1495":1,"1533":1,"1576":1,"1581":1,"1582":1,"1585":1,"1590":1,"1706":3,"1789":1,"1841":2,"1909":2,"2019":1,"2025":1}}],["mediocre",{"3":{"1216":1,"1536":1,"1796":1}}],["medium",{"3":{"682":1,"1323":3,"1394":1,"1395":1,"1416":2,"2219":1,"2233":1,"2235":2}}],["median",{"3":{"1576":1,"1706":1}}],["mediapicker",{"3":{"439":1,"1414":1,"1416":6}}],["media",{"2":{"1711":1},"3":{"416":1,"682":4,"684":1,"691":1,"740":1,"741":1,"743":1,"1202":1,"1203":15,"1207":28,"1299":1,"1310":1,"1323":1,"1349":2,"1414":1,"1416":57,"1490":1,"1495":1,"1584":1,"1668":2,"1711":2,"1713":1,"2072":1,"2117":1}}],["mediate",{"3":{"1990":1}}],["mediated",{"3":{"0":1,"332":1}}],["mediators",{"3":{"1555":1}}],["mediator",{"3":{"1270":2,"1538":2,"1542":1}}],["mediatr",{"3":{"0":1,"1075":1,"2138":1}}],["meetups",{"3":{"1379":1}}],["meetup",{"3":{"1341":1,"1349":1}}],["meetcustomrule",{"3":{"1311":1,"1435":2}}],["meeting",{"3":{"734":1,"808":1,"1282":1,"1310":1,"1416":1,"1490":1,"1525":1,"1887":1}}],["meet",{"3":{"92":1,"255":1,"813":1,"1191":1,"1194":1,"1200":1,"1214":1,"1241":1,"1247":1,"1276":1,"1285":2,"1299":3,"1300":4,"1310":1,"1321":1,"1323":1,"1349":1,"1358":4,"1365":1,"1395":3,"1477":1,"1576":2,"1640":3,"1667":1,"1713":1,"1830":1,"1922":1,"1994":1,"2140":1}}],["meetsminimumsizeonmobileviewport",{"3":{"1713":1}}],["meets",{"3":{"53":1,"135":1,"136":1,"568":1,"811":1,"1231":1,"1247":1,"1271":1,"1285":1,"1299":1,"1310":1,"1325":1,"1345":1,"1358":2,"1359":1,"1395":2,"1414":1,"1416":3,"1435":5,"1475":1,"1487":1,"1525":1,"1548":1,"1557":1,"1576":1,"1640":2,"1832":1,"2135":1}}],["mermaid",{"3":{"1501":1,"1619":1,"1755":1}}],["merchandising",{"3":{"1025":1}}],["mere",{"3":{"681":1,"1270":1,"1296":1}}],["merely",{"0":{"2195":1},"3":{"24":1,"38":1,"39":1,"96":1,"135":2,"137":1,"218":1,"324":1,"353":1,"358":1,"359":1,"360":1,"371":1,"497":1,"540":1,"575":1,"690":1,"757":1,"800":2,"827":2,"904":1,"946":1,"1060":1,"1237":2,"1255":1,"1270":1,"1271":1,"1285":6,"1294":1,"1299":2,"1305":1,"1308":2,"1310":1,"1312":1,"1319":1,"1322":2,"1323":1,"1326":1,"1327":1,"1338":5,"1349":2,"1358":10,"1379":2,"1394":3,"1395":10,"1401":2,"1414":4,"1416":1,"1427":1,"1435":17,"1437":1,"1443":1,"1454":1,"1456":1,"1459":1,"1466":1,"1472":1,"1485":1,"1525":1,"1684":1,"1824":1,"1919":1,"1922":1,"2044":1,"2047":1,"2060":1,"2131":1,"2136":1,"2139":1,"2141":2}}],["merging",{"3":{"627":1,"1018":1,"1358":3,"1452":1,"1590":1,"1766":1}}],["merges",{"3":{"0":1,"418":1,"618":1,"838":2,"1091":1,"1271":1,"1285":1,"1299":1,"1308":1,"1310":1,"1358":6,"1395":1,"1414":1,"1435":1,"1452":1,"1454":2,"1491":1,"1571":1,"1576":1,"1581":1,"1590":1,"1904":1}}],["merge",{"3":{"0":3,"112":1,"135":1,"265":1,"564":2,"590":1,"591":1,"595":1,"618":2,"625":1,"626":3,"628":2,"629":1,"630":1,"642":2,"838":1,"863":2,"1212":2,"1247":3,"1285":1,"1299":2,"1309":1,"1310":2,"1322":4,"1338":2,"1358":1,"1414":4,"1435":1,"1452":3,"1453":1,"1454":5,"1455":1,"1458":1,"1477":1,"1484":1,"1485":3,"1489":1,"1491":12,"1495":2,"1525":6,"1530":1,"1534":1,"1553":1,"1564":1,"1569":1,"1571":1,"1576":12,"1580":3,"1581":3,"1582":1,"1585":1,"1588":3,"1589":1,"1590":12,"1594":1,"1595":5,"1597":1,"1604":1,"1615":1,"1642":1,"1661":1,"1672":1,"1715":3,"1764":1,"1799":2,"1810":1,"1875":1,"1901":1,"1922":2,"2056":1,"2072":1,"2078":1,"2109":1,"2133":1,"2231":1}}],["merged",{"3":{"0":2,"94":1,"109":1,"365":1,"368":1,"373":1,"424":1,"425":1,"526":1,"530":1,"543":1,"550":2,"572":1,"607":1,"832":2,"833":1,"1012":2,"1016":1,"1081":1,"1085":2,"1229":1,"1237":1,"1247":2,"1256":1,"1259":2,"1285":2,"1299":1,"1322":1,"1338":3,"1358":3,"1395":1,"1414":2,"1428":1,"1437":1,"1446":1,"1452":1,"1453":3,"1458":1,"1480":1,"1491":2,"1495":1,"2057":1}}],["merits",{"3":{"486":1,"801":1,"1018":1,"1054":1}}],["mechanic",{"3":{"715":1,"716":1,"1285":4,"1300":1,"1394":1,"1395":4,"1416":1,"1838":1}}],["mechanics",{"3":{"1":1,"147":1,"272":1,"329":1,"369":1,"506":1,"715":1,"861":1,"862":1,"1043":1,"1045":1,"1229":1,"1237":2,"1247":1,"1259":5,"1270":1,"1275":1,"1285":12,"1299":4,"1310":3,"1323":5,"1338":4,"1349":5,"1358":4,"1379":1,"1394":9,"1414":1,"1416":1,"1432":1,"1434":1,"1435":11,"1474":1,"1576":1,"1860":1,"2046":1,"2202":1}}],["mechanically",{"3":{"342":1,"801":1,"1191":2,"1192":1,"1204":1,"1270":1,"1285":1,"1358":2,"1368":1,"1416":1,"1435":5,"1479":1,"1494":1,"1495":7,"1499":1,"1874":1,"1925":1}}],["mechanical",{"3":{"0":1,"88":1,"135":1,"143":1,"267":1,"483":1,"486":1,"674":1,"980":1,"1069":1,"1094":1,"1270":2,"1285":1,"1308":1,"1310":1,"1323":1,"1349":2,"1358":4,"1368":1,"1395":1,"1430":1,"1435":7,"1454":1,"1458":2,"1459":1,"1495":8,"1584":1,"1652":1,"1672":1,"1674":1,"1734":1}}],["mechanisms",{"0":{"2182":1},"3":{"19":1,"67":1,"282":1,"319":1,"340":1,"358":1,"557":1,"812":1,"1144":1,"1236":2,"1237":1,"1285":2,"1299":1,"1303":1,"1309":1,"1310":1,"1322":1,"1323":4,"1358":4,"1378":1,"1394":10,"1395":3,"1416":1,"1435":4,"1496":1,"1516":1,"1705":1,"1707":1,"1783":1,"1815":1,"1857":1,"1994":1,"2067":2,"2068":1,"2140":1,"2166":2,"2180":1,"2192":1}}],["mechanism",{"0":{"1803":1,"1907":1,"1909":1,"2062":1,"2164":1},"3":{"0":10,"3":1,"24":1,"39":1,"68":1,"80":1,"86":1,"109":1,"142":1,"169":1,"186":1,"207":1,"235":1,"259":1,"265":2,"266":2,"292":1,"295":1,"317":2,"320":1,"340":1,"358":1,"361":2,"362":1,"366":1,"372":1,"389":1,"391":1,"413":1,"465":1,"508":1,"517":1,"522":1,"536":2,"537":1,"538":1,"539":1,"582":1,"598":1,"599":1,"605":1,"608":1,"624":1,"658":1,"678":1,"689":2,"696":1,"699":1,"715":1,"723":1,"764":4,"769":1,"808":1,"809":1,"815":1,"820":1,"827":2,"829":1,"844":1,"846":1,"847":1,"848":2,"849":2,"850":1,"871":1,"905":1,"931":1,"958":1,"959":1,"960":1,"981":1,"988":1,"1012":1,"1075":1,"1099":1,"1101":1,"1139":1,"1147":1,"1192":1,"1193":1,"1212":2,"1213":1,"1218":1,"1229":5,"1237":6,"1247":3,"1249":1,"1253":1,"1258":1,"1259":9,"1270":11,"1271":3,"1285":17,"1289":1,"1296":1,"1299":5,"1308":1,"1309":1,"1310":5,"1317":1,"1322":8,"1323":16,"1338":11,"1349":5,"1354":1,"1358":21,"1376":1,"1379":4,"1394":10,"1395":6,"1401":5,"1412":1,"1414":6,"1415":1,"1416":9,"1417":1,"1435":24,"1437":1,"1445":1,"1453":1,"1455":1,"1456":1,"1458":1,"1473":3,"1476":1,"1478":1,"1485":1,"1487":1,"1488":1,"1489":1,"1495":2,"1509":1,"1525":1,"1563":1,"1565":1,"1566":2,"1574":2,"1576":2,"1577":1,"1581":1,"1585":1,"1590":5,"1633":1,"1638":2,"1640":2,"1676":1,"1706":1,"1763":1,"1789":1,"1839":1,"1841":1,"1843":1,"1844":1,"1845":1,"1850":1,"1852":1,"1870":1,"1875":1,"1881":1,"1890":1,"1920":1,"1921":1,"1946":1,"1947":1,"1949":1,"1961":1,"1962":1,"1963":1,"1964":1,"1967":1,"1974":1,"1991":1,"1993":1,"1995":2,"1996":1,"1999":1,"2001":1,"2019":1,"2030":1,"2042":1,"2045":1,"2060":2,"2061":1,"2062":1,"2067":3,"2068":1,"2079":1,"2081":1,"2084":1,"2085":1,"2112":1,"2118":1,"2119":1,"2121":1,"2129":1,"2132":1,"2133":1,"2135":1,"2141":1,"2142":1,"2144":1,"2163":3,"2167":2,"2168":1,"2184":1,"2192":1,"2229":1,"2231":1}}],["mentor",{"3":{"2215":1,"2241":1}}],["mentorship",{"0":{"2241":1},"3":{"2215":1,"2218":1}}],["mentoring",{"3":{"2212":1,"2217":1,"2237":1,"2244":1}}],["mentioning",{"3":{"1414":1}}],["mentioned",{"3":{"370":1,"1006":1,"1631":1}}],["mention",{"3":{"256":1,"423":1,"638":1,"988":1,"1285":1,"1347":1,"1435":1,"1458":1,"1459":1,"1684":2}}],["mentions",{"3":{"0":1,"230":1,"418":1,"425":1,"426":1,"435":1,"497":1,"555":1,"593":1,"609":1,"700":1,"782":1,"1270":4,"1310":3,"1368":1,"1395":1,"1414":2,"1435":2,"1471":1,"1567":1,"1638":2}}],["mental",{"3":{"167":1,"906":1,"932":1,"1220":1,"1322":1,"1358":1,"1394":1,"1415":1,"1804":1}}],["menuitem",{"3":{"1435":1}}],["menus",{"3":{"265":1,"1323":3,"1435":1,"1525":1,"1558":1,"1590":1,"1606":1,"1652":1,"1740":1,"2084":1}}],["menu",{"3":{"0":1,"618":1,"647":1,"648":1,"649":1,"651":1,"1212":1,"1323":19,"1394":2,"1395":4,"1414":6,"1435":2,"1437":1,"1525":1,"1561":1,"1620":1,"1624":1,"1626":4,"1652":3,"1760":2,"2076":4,"2082":1}}],["measurably",{"3":{"1482":1}}],["measurable",{"3":{"255":1,"405":1,"799":1,"1270":1,"1271":1,"1394":1,"1401":1,"1426":1,"1801":2}}],["measuring",{"3":{"236":1,"474":1,"592":1,"1285":1,"1338":1,"1435":1,"1454":1,"1457":1,"1491":2,"2046":1}}],["measureasync",{"3":{"1435":1}}],["measureandassertasync",{"2":{"861":1},"3":{"861":1}}],["measurewebvitalsasync",{"2":{"861":1},"3":{"861":1,"1435":6}}],["measurewebvitalswithinteractionasync",{"2":{"859":1,"861":1,"864":1},"3":{"859":1,"861":2,"864":1,"1435":3}}],["measure",{"3":{"72":1,"407":1,"591":1,"617":1,"631":1,"865":1,"1271":1,"1285":1,"1323":1,"1338":1,"1358":1,"1415":1,"1416":1,"1435":1,"1457":1,"1466":1,"1487":1,"1490":1,"1491":2,"1520":1,"1530":1,"1536":2,"1709":1,"1796":1,"1901":1,"2192":1}}],["measuresarevokedrowfromitsrevocationnotitsexpiry",{"3":{"906":1}}],["measures",{"3":{"0":2,"140":1,"558":1,"591":2,"611":1,"797":1,"821":1,"859":1,"860":2,"861":3,"1259":1,"1263":1,"1270":1,"1322":2,"1323":2,"1338":1,"1394":1,"1424":1,"1435":5,"1437":1,"1454":5,"1460":1,"1466":2,"1491":2,"1495":1,"1525":1,"1590":1,"1669":1,"1796":2,"1820":1,"2023":1,"2045":1,"2086":1}}],["measurements",{"3":{"480":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"495":1,"591":1,"631":5,"636":1,"915":1,"1270":1,"1310":1,"1322":1,"1435":3,"1455":1,"1466":1,"1483":1,"1491":1,"1793":1}}],["measurement",{"3":{"0":1,"397":1,"487":2,"488":1,"489":1,"490":2,"491":1,"492":1,"493":1,"494":1,"591":3,"592":3,"799":1,"859":1,"860":1,"861":5,"863":1,"869":1,"875":1,"980":1,"1018":1,"1199":4,"1207":1,"1259":2,"1270":1,"1338":1,"1368":1,"1394":1,"1426":3,"1435":9,"1437":1,"1444":1,"1450":1,"1452":1,"1454":2,"1457":1,"1464":1,"1466":3,"1474":2,"1477":1,"1491":3,"1530":1,"1801":1,"2046":2,"2047":1,"2231":2}}],["measured",{"3":{"0":7,"137":1,"237":1,"397":1,"480":1,"483":1,"486":2,"487":2,"488":2,"489":2,"490":2,"491":2,"492":2,"493":2,"494":2,"590":1,"591":3,"592":1,"597":1,"604":1,"616":2,"620":1,"621":1,"632":1,"683":1,"797":2,"801":1,"803":1,"804":1,"805":1,"806":1,"827":1,"828":1,"860":2,"861":5,"862":3,"863":3,"869":1,"871":1,"914":2,"915":1,"947":1,"978":1,"980":1,"983":1,"986":1,"1011":1,"1012":1,"1048":1,"1054":1,"1101":1,"1125":1,"1212":1,"1270":2,"1285":1,"1299":2,"1322":2,"1323":2,"1338":2,"1343":1,"1357":1,"1394":1,"1395":1,"1401":1,"1415":1,"1424":1,"1435":14,"1437":1,"1442":1,"1446":1,"1454":4,"1457":1,"1459":1,"1460":1,"1464":3,"1466":3,"1473":2,"1474":2,"1475":1,"1482":1,"1483":1,"1491":4,"1495":4,"1499":1,"1522":1,"1524":1,"1525":4,"1528":1,"1533":2,"1548":1,"1559":1,"1565":1,"1567":1,"1570":1,"1574":3,"1575":1,"1576":8,"1580":1,"1581":1,"1582":1,"1590":4,"1595":1,"1669":1,"1677":2,"1706":10,"1708":1,"1709":1,"1922":2,"1984":1,"2023":1,"2033":3,"2036":1,"2042":1,"2046":2,"2057":1,"2058":1,"2078":1,"2155":1,"2176":1,"2231":3}}],["meanings",{"3":{"1299":1,"1322":1,"1338":1,"1629":1}}],["meaningless",{"3":{"691":1,"784":1,"1247":1,"1310":1,"1322":4,"1358":1,"1394":2,"1395":1,"1414":1,"1416":1,"1433":1,"1435":1,"1630":2,"1637":1,"1638":2,"2001":1}}],["meaningfulness",{"3":{"1486":1}}],["meaningfully",{"3":{"1322":1,"1358":1,"1416":1,"1435":1,"1491":1,"1949":1}}],["meaningful",{"3":{"305":1,"390":1,"499":1,"775":1,"918":1,"932":1,"1140":1,"1212":1,"1237":1,"1240":1,"1247":3,"1259":1,"1265":1,"1267":1,"1270":2,"1285":2,"1299":1,"1309":1,"1310":1,"1311":1,"1322":1,"1323":3,"1338":1,"1349":4,"1358":8,"1368":1,"1394":6,"1395":4,"1401":5,"1414":1,"1419":1,"1426":1,"1434":1,"1435":7,"1452":1,"1457":1,"1466":1,"1536":1,"1540":1,"1550":2,"1561":1,"1564":1,"1604":1,"1606":1,"1629":1,"1636":1,"1641":1,"1738":1,"1740":1,"1908":1,"2072":1,"2229":1,"2231":1}}],["meaning",{"3":{"284":1,"698":2,"758":1,"827":2,"1090":1,"1222":1,"1237":2,"1241":1,"1247":2,"1253":1,"1259":1,"1264":2,"1271":1,"1285":5,"1299":7,"1300":4,"1310":2,"1322":2,"1323":8,"1338":5,"1349":4,"1358":9,"1368":3,"1394":9,"1395":6,"1401":4,"1414":1,"1416":4,"1435":6,"1441":1,"1458":1,"1464":1,"1466":3,"1475":1,"1536":2,"1629":17,"1632":2,"1637":1,"1638":2,"1639":1,"1726":1,"1727":1,"1728":1,"1729":1,"1733":1,"1768":4,"1796":1,"1809":1,"1932":1,"2170":1,"2184":1}}],["meanwhile",{"3":{"261":1,"826":1,"1349":1,"1379":1,"1394":1}}],["mean",{"3":{"60":1,"110":1,"135":1,"198":1,"218":1,"310":1,"402":1,"414":1,"509":1,"591":2,"593":1,"620":2,"626":1,"697":1,"699":1,"757":1,"768":1,"776":1,"810":1,"819":1,"821":1,"829":1,"856":1,"861":1,"862":1,"863":1,"923":1,"981":2,"1006":1,"1018":1,"1043":1,"1067":1,"1091":1,"1092":1,"1116":1,"1188":1,"1259":3,"1270":1,"1275":1,"1285":7,"1299":4,"1300":1,"1308":4,"1309":2,"1310":2,"1322":4,"1323":2,"1332":1,"1338":6,"1349":6,"1358":6,"1394":4,"1395":8,"1401":1,"1414":4,"1416":5,"1421":1,"1426":1,"1435":4,"1437":1,"1442":1,"1454":2,"1466":1,"1471":1,"1477":1,"1478":1,"1488":1,"1491":2,"1629":1,"1637":1,"1861":1,"1862":1,"1921":1,"1932":1,"1978":1,"1999":1,"2000":1,"2014":1,"2027":1,"2031":1,"2036":1,"2047":1,"2056":1,"2078":1,"2097":1,"2126":1}}],["meantime",{"3":{"635":1,"988":1,"1117":1}}],["meant",{"3":{"0":2,"81":1,"110":1,"246":1,"302":1,"318":1,"342":1,"403":1,"460":1,"539":1,"540":1,"609":1,"648":1,"650":1,"683":1,"697":1,"698":1,"709":1,"765":1,"939":1,"945":1,"1074":1,"1169":1,"1174":1,"1229":2,"1239":1,"1247":4,"1259":2,"1270":1,"1271":2,"1285":3,"1299":8,"1310":2,"1322":2,"1323":4,"1349":1,"1354":1,"1358":3,"1378":2,"1379":1,"1394":2,"1395":4,"1401":2,"1412":1,"1414":1,"1426":1,"1435":14,"1454":1,"1474":1,"1676":1,"1697":1,"1725":1,"1814":1,"1871":1,"1873":1,"2001":1,"2031":1,"2137":1}}],["means",{"0":{"719":1,"2136":1},"1":{"1793":1,"1794":1,"1795":1,"1796":1,"1797":1,"1798":1,"1799":1,"1800":1,"1801":1},"3":{"0":9,"30":1,"32":1,"58":1,"79":1,"80":1,"119":1,"122":1,"132":1,"134":1,"135":1,"136":1,"149":1,"159":1,"160":2,"170":1,"175":1,"185":1,"187":1,"189":1,"194":1,"196":1,"201":1,"216":1,"226":2,"236":1,"237":1,"243":2,"244":1,"255":1,"266":1,"274":1,"282":1,"283":1,"292":1,"311":1,"318":1,"319":1,"320":1,"332":1,"341":1,"342":1,"343":1,"349":1,"351":2,"352":1,"359":1,"361":2,"363":1,"371":1,"372":1,"382":1,"384":1,"390":1,"399":1,"413":1,"441":2,"448":1,"449":3,"461":1,"500":2,"501":1,"508":1,"519":1,"520":1,"538":2,"546":1,"547":1,"549":1,"564":2,"573":1,"575":1,"583":2,"591":1,"598":1,"601":2,"602":1,"617":1,"619":5,"642":1,"650":1,"651":1,"665":1,"666":1,"667":1,"676":1,"682":1,"684":1,"691":1,"692":1,"698":2,"700":1,"708":1,"713":1,"716":1,"717":1,"718":1,"726":1,"727":1,"733":2,"741":3,"743":1,"750":2,"756":1,"766":1,"767":2,"768":1,"781":1,"782":1,"784":1,"792":2,"799":1,"800":1,"801":1,"810":1,"819":1,"821":1,"827":1,"838":1,"845":1,"855":2,"863":1,"872":1,"883":1,"905":1,"906":1,"916":1,"932":2,"945":1,"955":1,"965":2,"988":1,"989":1,"996":3,"997":1,"999":1,"1020":1,"1026":1,"1034":2,"1035":1,"1041":1,"1044":1,"1060":2,"1066":1,"1076":1,"1083":1,"1091":1,"1092":3,"1093":1,"1101":1,"1102":1,"1107":1,"1108":1,"1109":1,"1144":1,"1177":1,"1212":2,"1229":10,"1231":1,"1237":13,"1240":1,"1243":1,"1247":13,"1253":1,"1254":1,"1259":26,"1264":1,"1270":25,"1271":1,"1276":1,"1284":1,"1285":80,"1290":1,"1299":47,"1300":11,"1304":2,"1308":20,"1309":8,"1310":43,"1311":2,"1322":48,"1323":59,"1327":1,"1331":2,"1338":32,"1343":2,"1349":32,"1351":1,"1353":1,"1358":98,"1365":1,"1367":1,"1368":9,"1372":1,"1379":6,"1394":45,"1395":68,"1401":34,"1405":1,"1406":1,"1407":1,"1410":1,"1414":32,"1415":3,"1416":48,"1426":8,"1429":2,"1430":1,"1432":1,"1435":50,"1437":3,"1439":1,"1440":7,"1441":1,"1442":2,"1444":1,"1445":1,"1452":1,"1453":2,"1454":6,"1455":1,"1457":1,"1459":1,"1463":1,"1466":7,"1467":1,"1472":1,"1474":1,"1475":1,"1487":4,"1488":1,"1489":1,"1491":4,"1525":2,"1536":2,"1541":1,"1596":1,"1598":1,"1630":2,"1638":1,"1639":1,"1640":1,"1651":1,"1652":1,"1667":1,"1684":1,"1691":1,"1701":1,"1722":1,"1730":1,"1734":1,"1747":1,"1754":1,"1766":1,"1791":1,"1792":1,"1793":1,"1796":1,"1797":2,"1809":1,"1811":1,"1814":1,"1816":1,"1821":1,"1822":1,"1825":1,"1829":2,"1832":1,"1842":1,"1843":1,"1844":2,"1850":2,"1853":1,"1870":1,"1873":1,"1875":3,"1884":2,"1888":1,"1889":1,"1890":2,"1899":1,"1901":3,"1904":1,"1907":2,"1908":1,"1910":1,"1911":2,"1915":1,"1919":1,"1931":1,"1932":1,"1939":2,"1947":1,"1948":2,"1957":1,"1960":1,"1964":1,"1980":3,"1981":1,"1989":1,"1990":1,"1995":1,"1997":1,"2000":1,"2001":1,"2012":2,"2014":1,"2015":2,"2032":1,"2040":1,"2041":1,"2042":1,"2043":1,"2044":1,"2045":1,"2046":2,"2050":2,"2052":1,"2055":2,"2058":1,"2066":1,"2069":3,"2076":1,"2077":1,"2084":1,"2116":1,"2125":1,"2126":1,"2130":2,"2133":1,"2135":1,"2137":1,"2141":1,"2149":1,"2155":1,"2159":1,"2163":1,"2165":1,"2167":2,"2168":1,"2173":1,"2174":1,"2176":1,"2178":1,"2182":2,"2183":3,"2186":1,"2191":2,"2192":1,"2195":1,"2196":1,"2198":1,"2201":1,"2204":1,"2229":1,"2231":3}}],["memoizing",{"3":{"1259":1,"1285":1,"1310":1}}],["memoization",{"3":{"1247":1,"1394":1,"1555":1}}],["memoize",{"3":{"1247":1,"1394":1}}],["memoizes",{"3":{"244":1,"330":1,"337":1,"1050":1,"1247":2,"1285":2,"1322":1,"1358":1,"1394":2}}],["memoized",{"3":{"242":1,"682":1,"1241":2,"1247":2,"1299":1,"1309":1,"1394":1}}],["memorize",{"3":{"1270":1,"1310":1,"1904":1}}],["memorizing",{"3":{"1231":1,"1263":1,"1310":1,"1371":1,"1395":2}}],["memorystream",{"3":{"1322":1,"1323":1,"1379":1,"1414":3}}],["memorycache",{"3":{"1466":1}}],["memorycacheentryoptions",{"3":{"1300":3}}],["memorycacheservicetests",{"3":{"1199":1,"1207":2,"1300":3}}],["memorycacheservice",{"2":{"248":1,"252":1,"253":1,"731":1,"1915":1},"3":{"0":1,"243":2,"248":2,"252":1,"253":1,"731":1,"1199":4,"1203":1,"1207":2,"1299":6,"1300":48,"1322":2,"1495":1,"1576":1,"1915":2}}],["memorydiagnoser",{"2":{"591":1,"592":1,"2046":1},"3":{"591":2,"592":1,"1435":1,"1491":1,"2046":1}}],["memorydistributedcache",{"2":{"252":1,"1915":1},"3":{"243":1,"252":1,"1300":2,"1322":1,"1915":1}}],["memory",{"2":{"875":1},"3":{"0":6,"16":1,"17":1,"39":1,"43":1,"157":1,"158":1,"162":1,"178":2,"207":1,"209":1,"235":2,"241":1,"242":1,"243":5,"244":1,"245":3,"249":1,"259":1,"261":1,"283":1,"386":1,"390":2,"461":1,"507":5,"508":1,"514":1,"536":1,"540":1,"545":2,"546":1,"549":1,"551":1,"664":1,"665":1,"667":1,"688":1,"692":1,"699":1,"732":1,"733":1,"767":1,"792":1,"793":1,"794":1,"818":1,"827":1,"831":1,"856":2,"875":2,"881":2,"903":1,"905":1,"914":1,"916":1,"1050":1,"1052":1,"1085":1,"1117":1,"1123":1,"1125":1,"1126":1,"1150":2,"1152":2,"1154":1,"1155":1,"1183":1,"1212":1,"1229":1,"1237":4,"1239":3,"1243":1,"1247":11,"1250":1,"1259":1,"1270":1,"1278":1,"1281":1,"1285":22,"1289":2,"1296":1,"1297":3,"1299":12,"1300":5,"1308":2,"1309":2,"1310":15,"1322":3,"1323":43,"1331":1,"1333":1,"1335":1,"1336":1,"1338":12,"1345":1,"1349":6,"1353":1,"1356":1,"1358":46,"1361":1,"1366":1,"1368":5,"1378":1,"1379":1,"1382":1,"1390":1,"1394":13,"1395":17,"1400":1,"1401":3,"1414":2,"1416":5,"1426":1,"1428":1,"1430":1,"1435":24,"1437":9,"1441":3,"1442":2,"1456":1,"1464":1,"1466":6,"1472":1,"1475":1,"1525":2,"1534":1,"1548":1,"1559":1,"1561":1,"1576":3,"1581":1,"1585":1,"1589":1,"1590":1,"1610":1,"1613":1,"1640":4,"1650":1,"1666":1,"1667":1,"1674":1,"1714":1,"1720":1,"1813":2,"1814":2,"1816":1,"1823":1,"1855":1,"1867":1,"1884":1,"1903":1,"1908":1,"1915":1,"1922":4,"1923":1,"1928":1,"1949":1,"1965":1,"1967":2,"1969":2,"1970":1,"1981":1,"1985":1,"1987":1,"1999":1,"2000":2,"2006":1,"2026":1,"2051":1,"2073":1,"2075":2,"2078":1,"2080":1,"2107":1,"2127":1,"2166":2,"2179":1,"2198":1,"2231":2}}],["memberallowlistentry",{"3":{"1435":2}}],["membername",{"3":{"1323":2,"1426":1}}],["memberpath",{"3":{"1309":1,"1394":1}}],["memberexpression",{"3":{"1247":1,"1285":3,"1309":1,"1323":2,"1435":1}}],["memberless",{"3":{"657":1}}],["memberinit",{"3":{"330":1,"1242":1,"1247":3,"1285":1,"1310":2,"1987":1}}],["member",{"2":{"1161":1},"3":{"0":13,"24":1,"27":1,"121":2,"124":1,"126":1,"135":4,"142":1,"164":1,"186":1,"245":1,"248":1,"249":1,"255":1,"261":1,"335":1,"350":1,"443":1,"463":1,"501":1,"502":1,"545":1,"546":2,"549":1,"550":2,"583":1,"609":1,"633":2,"692":1,"698":2,"725":1,"726":1,"733":2,"734":1,"735":1,"741":2,"745":1,"749":1,"782":3,"783":2,"784":2,"854":1,"859":1,"861":2,"922":1,"963":3,"964":12,"965":2,"966":3,"967":1,"979":3,"980":6,"981":3,"982":3,"984":2,"1034":1,"1038":1,"1042":1,"1050":3,"1052":1,"1059":2,"1060":2,"1091":1,"1119":1,"1133":1,"1135":1,"1161":14,"1162":1,"1163":2,"1168":1,"1176":1,"1193":1,"1212":1,"1219":3,"1225":2,"1229":13,"1230":1,"1232":1,"1235":2,"1236":1,"1237":21,"1239":1,"1241":2,"1247":14,"1249":1,"1259":6,"1263":1,"1266":1,"1270":39,"1271":7,"1276":2,"1278":1,"1279":1,"1285":96,"1292":1,"1293":1,"1294":3,"1296":1,"1299":27,"1300":11,"1308":23,"1309":5,"1310":22,"1311":1,"1314":1,"1317":2,"1322":24,"1323":45,"1326":1,"1338":6,"1347":1,"1349":21,"1351":1,"1352":1,"1353":2,"1356":1,"1358":146,"1368":8,"1375":1,"1379":7,"1394":25,"1395":59,"1401":13,"1404":1,"1406":1,"1407":1,"1410":1,"1414":29,"1415":2,"1416":22,"1422":2,"1426":3,"1428":1,"1430":1,"1435":167,"1437":2,"1466":1,"1476":1,"1477":1,"1488":2,"1495":2,"1516":1,"1525":1,"1576":3,"1672":1,"1685":1,"1698":1,"1726":5,"1733":2,"1768":1,"1814":3,"1886":1,"1914":3,"1952":1,"1962":1,"1976":1,"1988":1,"2000":1,"2045":1,"2062":1,"2076":1,"2099":1,"2106":3,"2125":1,"2135":1,"2185":1}}],["memberslazy",{"3":{"1237":1}}],["membershipservice",{"3":{"1199":2,"1207":1}}],["membershipdto",{"3":{"1199":3,"1207":1}}],["membership",{"0":{"1203":1},"3":{"0":1,"284":1,"380":2,"381":1,"1152":1,"1154":1,"1200":1,"1247":6,"1259":1,"1285":2,"1295":1,"1299":5,"1301":1,"1306":2,"1308":4,"1309":1,"1310":1,"1311":1,"1323":9,"1349":1,"1358":3,"1379":1,"1394":3,"1395":5,"1399":1,"1401":1,"1435":5,"1495":5,"1637":1,"1944":4}}],["members",{"0":{"1299":1},"1":{"977":1,"978":1,"979":1,"980":1,"981":1,"982":1,"983":1,"984":1},"3":{"0":8,"39":2,"109":1,"112":1,"135":6,"136":1,"142":1,"166":1,"184":1,"201":1,"235":1,"258":1,"261":1,"276":1,"326":1,"486":1,"497":1,"499":1,"543":1,"545":1,"549":7,"550":4,"551":3,"552":1,"583":5,"633":1,"649":1,"650":1,"657":1,"674":2,"675":1,"677":1,"682":1,"733":1,"749":1,"782":2,"784":1,"819":1,"846":1,"849":1,"881":1,"884":1,"922":1,"963":1,"964":5,"966":2,"975":1,"977":1,"980":4,"982":1,"1018":1,"1027":1,"1034":1,"1036":1,"1045":1,"1050":2,"1133":2,"1135":1,"1168":3,"1175":2,"1193":1,"1198":1,"1212":4,"1214":3,"1220":1,"1229":3,"1232":1,"1235":2,"1236":1,"1237":19,"1239":2,"1247":11,"1249":1,"1259":9,"1262":1,"1269":1,"1270":12,"1280":2,"1285":62,"1287":1,"1295":1,"1298":1,"1299":37,"1300":5,"1303":1,"1308":16,"1309":5,"1310":17,"1311":3,"1313":1,"1322":27,"1323":69,"1333":1,"1338":10,"1346":2,"1348":1,"1349":46,"1358":106,"1379":7,"1394":36,"1395":55,"1401":40,"1404":1,"1408":1,"1411":1,"1413":1,"1414":35,"1415":5,"1416":23,"1426":6,"1430":2,"1435":125,"1437":3,"1487":3,"1488":1,"1495":21,"1497":2,"1537":1,"1574":1,"1576":1,"1647":1,"1653":1,"1765":1,"1804":1,"1809":1,"1814":1,"1848":1,"1857":1,"1870":1,"1879":1,"1925":1,"1947":1,"2093":1,"2106":1,"2166":1,"2231":1}}],["met",{"3":{"1017":1,"1145":1,"1152":1,"1153":1,"1155":1,"1248":1,"1322":3,"1358":1,"1395":1,"1414":1,"1417":1,"1435":1,"1444":1,"1525":6,"1526":1,"1530":1,"1536":5,"1575":1,"1576":10,"1577":1,"1581":2,"1590":1,"1796":1,"2013":1,"2204":1}}],["metastasizes",{"3":{"2115":1}}],["meta",{"3":{"1435":23,"1485":1,"1495":1}}],["metacharacters",{"3":{"1451":1}}],["metacharacter",{"3":{"1323":1}}],["metaphor",{"3":{"1229":1}}],["metapackages",{"3":{"940":1}}],["metapackage",{"1":{"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1},"3":{"0":3,"135":2,"139":1,"141":3,"142":1,"145":1,"526":1,"936":1,"937":1,"939":4,"940":3,"941":3,"942":1,"953":2,"957":1,"966":1,"984":1,"1096":1,"1211":1,"1212":1,"1453":1,"1502":1,"1526":1,"1576":1,"1659":1,"1660":1,"1692":1,"1780":1,"2171":1,"2226":2,"2231":2,"2233":1}}],["metadatanames",{"3":{"1310":1}}],["metadata",{"0":{"1236":1},"1":{"1309":1},"2":{"1368":1},"3":{"0":5,"13":1,"31":2,"90":1,"91":1,"93":1,"98":2,"99":1,"103":1,"173":2,"175":3,"179":1,"189":1,"318":1,"330":1,"435":1,"439":1,"440":1,"526":1,"528":1,"611":1,"749":2,"939":1,"981":1,"1089":1,"1093":1,"1107":1,"1115":1,"1133":1,"1136":1,"1192":1,"1202":1,"1203":1,"1208":1,"1212":1,"1229":4,"1230":1,"1236":1,"1237":2,"1238":1,"1244":2,"1245":1,"1247":4,"1259":1,"1262":1,"1271":1,"1285":18,"1286":1,"1296":3,"1299":15,"1308":9,"1309":15,"1310":20,"1311":8,"1322":10,"1323":1,"1338":1,"1339":1,"1349":4,"1358":10,"1368":16,"1379":1,"1395":5,"1404":1,"1408":1,"1414":9,"1415":2,"1416":1,"1426":1,"1433":1,"1435":19,"1437":1,"1440":2,"1442":1,"1446":1,"1449":1,"1453":1,"1466":3,"1495":1,"1628":1,"1629":2,"1630":2,"1638":1,"1639":1,"1640":15,"1662":1,"1663":1,"1666":1,"1667":1,"1702":1,"1804":2,"1814":1,"1816":1,"1824":1,"1866":1,"1897":2,"1903":1,"1904":1,"1924":1,"1926":2,"1927":1,"1962":1,"1987":1,"1993":1,"1999":2,"2008":1,"2017":1,"2018":1,"2123":2,"2124":1,"2125":2,"2127":2,"2231":1}}],["methodreference",{"3":{"1435":1}}],["methoddefinition",{"3":{"1435":1}}],["methodinfo",{"3":{"1270":1,"1285":3}}],["methodology",{"3":{"491":1,"797":1,"1495":2,"2113":1,"2231":1}}],["methodname",{"3":{"135":1,"1285":3,"1435":4}}],["methods",{"1":{"1807":1,"1808":1,"1809":1,"1810":1,"1811":1},"3":{"0":11,"68":1,"71":1,"109":1,"130":2,"135":5,"136":3,"137":2,"139":1,"142":1,"281":1,"380":1,"415":1,"499":1,"537":1,"547":1,"549":1,"578":1,"674":2,"749":1,"773":1,"774":2,"775":1,"776":1,"790":1,"854":1,"860":1,"914":1,"921":1,"922":2,"923":1,"924":1,"926":1,"963":1,"964":3,"966":1,"979":3,"980":1,"982":1,"1004":1,"1050":1,"1075":1,"1161":1,"1167":1,"1168":2,"1169":1,"1186":1,"1193":1,"1212":2,"1214":1,"1219":1,"1220":1,"1221":2,"1223":1,"1229":9,"1236":1,"1237":9,"1240":1,"1247":8,"1259":6,"1265":2,"1269":1,"1270":5,"1285":26,"1289":1,"1293":1,"1296":1,"1299":18,"1300":3,"1308":15,"1309":5,"1310":12,"1311":2,"1319":1,"1322":16,"1323":26,"1338":14,"1341":1,"1342":5,"1343":1,"1349":18,"1352":1,"1357":1,"1358":34,"1365":1,"1367":2,"1368":2,"1377":2,"1379":18,"1393":1,"1394":20,"1395":30,"1401":9,"1404":4,"1414":7,"1415":1,"1416":23,"1419":1,"1426":6,"1430":1,"1435":39,"1436":1,"1441":1,"1442":1,"1443":1,"1484":1,"1486":2,"1487":2,"1488":3,"1495":2,"1524":2,"1525":1,"1538":1,"1540":1,"1575":1,"1576":3,"1584":2,"1638":1,"1646":1,"1650":2,"1653":1,"1658":1,"1661":1,"1670":1,"1685":1,"1750":1,"1781":1,"1804":2,"1806":2,"1807":1,"1809":1,"1811":1,"1814":1,"1816":1,"1934":1,"1943":1,"2007":1,"2021":1,"2087":1,"2089":1,"2100":1,"2116":1,"2117":1,"2121":1,"2166":2,"2174":1,"2177":1,"2190":1,"2205":1,"2219":1,"2226":1,"2229":1}}],["method",{"2":{"160":1,"1993":1},"3":{"0":10,"5":3,"24":1,"27":1,"39":1,"93":1,"94":1,"95":2,"96":1,"98":2,"108":1,"109":1,"110":1,"113":1,"130":2,"135":11,"137":1,"139":2,"140":2,"157":1,"160":1,"225":1,"252":1,"254":1,"256":1,"295":1,"312":1,"318":1,"325":1,"336":1,"341":1,"395":1,"397":1,"407":1,"409":1,"459":1,"487":1,"499":1,"511":1,"512":1,"546":2,"548":1,"549":1,"592":1,"631":1,"632":1,"657":1,"665":1,"672":1,"674":6,"676":1,"677":1,"682":1,"715":1,"741":2,"748":1,"749":2,"750":1,"751":4,"776":1,"826":1,"827":1,"846":1,"849":1,"850":1,"855":1,"905":2,"921":1,"979":5,"980":7,"981":2,"982":3,"987":1,"988":1,"996":1,"1004":1,"1018":1,"1019":1,"1021":1,"1026":1,"1027":1,"1030":1,"1036":1,"1043":1,"1059":1,"1093":1,"1100":1,"1133":1,"1135":1,"1161":6,"1162":1,"1168":1,"1169":1,"1170":1,"1184":3,"1212":2,"1214":1,"1215":1,"1218":4,"1224":2,"1229":8,"1231":1,"1233":2,"1234":1,"1237":22,"1238":1,"1241":1,"1242":1,"1244":2,"1247":22,"1259":19,"1261":1,"1265":1,"1269":3,"1270":35,"1271":8,"1278":1,"1285":61,"1287":1,"1290":2,"1292":2,"1299":54,"1300":3,"1308":31,"1309":7,"1310":49,"1311":14,"1313":2,"1314":2,"1315":2,"1317":1,"1322":47,"1323":67,"1325":1,"1328":1,"1332":1,"1334":1,"1335":2,"1338":44,"1343":1,"1344":1,"1345":1,"1347":1,"1349":32,"1352":3,"1358":140,"1361":1,"1364":2,"1367":3,"1368":7,"1377":3,"1379":18,"1381":1,"1382":1,"1388":1,"1394":62,"1395":86,"1401":20,"1404":1,"1414":49,"1416":25,"1417":1,"1420":1,"1426":8,"1428":1,"1430":4,"1435":127,"1437":3,"1440":4,"1441":1,"1442":1,"1443":2,"1452":1,"1466":2,"1485":1,"1487":2,"1488":2,"1491":1,"1492":1,"1495":4,"1506":1,"1538":1,"1540":1,"1640":1,"1652":1,"1653":2,"1665":1,"1684":2,"1685":1,"1700":6,"1701":3,"1714":1,"1729":4,"1746":1,"1754":1,"1768":1,"1775":1,"1782":1,"1788":1,"1789":1,"1802":3,"1803":1,"1805":1,"1806":2,"1808":2,"1809":3,"1811":2,"1814":1,"1816":1,"1817":1,"1819":1,"1820":1,"1823":1,"1824":3,"1825":1,"1830":1,"1831":1,"1839":1,"1841":1,"1855":1,"1858":1,"1879":1,"1880":2,"1883":1,"1898":1,"1907":1,"1911":1,"1934":2,"1936":1,"1943":5,"1949":1,"1952":1,"1953":1,"1954":1,"1955":1,"1961":1,"1969":1,"1981":1,"1982":1,"1993":1,"1994":2,"1999":2,"2005":1,"2013":1,"2014":1,"2019":1,"2029":1,"2031":1,"2042":1,"2046":1,"2057":2,"2065":1,"2071":2,"2077":1,"2079":1,"2082":1,"2090":1,"2091":2,"2093":1,"2096":2,"2125":1,"2133":1,"2138":1,"2155":2,"2165":1,"2169":1,"2171":1,"2183":1,"2190":1,"2196":4,"2199":1,"2202":1,"2231":1}}],["metername",{"3":{"1247":2,"1259":2,"1270":1,"1285":2,"1310":2,"1322":3,"1338":1,"1426":4}}],["metering",{"0":{"2176":1},"3":{"1016":1,"1020":1,"1022":1,"1192":1,"1310":3,"1417":1,"1420":1,"1495":1,"2175":1,"2208":1,"2226":1}}],["meterlistener",{"3":{"897":1}}],["meterprovider",{"3":{"397":1,"1332":1,"1338":1,"1442":1,"2155":1}}],["meters",{"3":{"0":2,"395":2,"397":1,"400":2,"407":1,"408":1,"792":1,"818":1,"819":1,"822":1,"899":1,"1018":1,"1186":1,"1212":1,"1270":1,"1310":1,"1322":1,"1332":3,"1338":7,"1426":1,"1441":2,"1442":2,"1449":1,"1466":2,"1576":1,"1669":2,"1842":1,"2005":1,"2009":1,"2012":1,"2155":2,"2159":2}}],["metered",{"1":{"2169":1,"2170":1,"2171":1,"2172":1,"2173":1,"2174":1,"2175":1,"2176":1,"2177":1,"2178":1,"2179":1},"3":{"0":3,"173":2,"175":1,"177":1,"179":1,"279":1,"280":1,"287":1,"1016":1,"1018":1,"1091":2,"1202":1,"1203":1,"1212":1,"1310":4,"1323":1,"1349":2,"1357":2,"1358":5,"1368":2,"1394":1,"1424":1,"1667":1,"1778":1,"1997":1,"1999":1,"2168":2,"2169":2,"2192":1,"2208":1}}],["meter",{"0":{"407":1,"408":1},"3":{"0":14,"24":2,"66":1,"72":3,"121":1,"124":1,"126":1,"241":1,"255":4,"258":3,"259":1,"261":1,"395":12,"397":7,"399":2,"400":3,"404":5,"406":2,"407":3,"408":4,"409":2,"707":2,"731":1,"819":3,"821":3,"822":2,"823":1,"827":1,"829":1,"897":3,"899":2,"900":1,"901":1,"914":1,"916":1,"917":1,"1016":1,"1018":3,"1020":1,"1044":1,"1089":2,"1090":3,"1091":3,"1092":2,"1093":1,"1094":1,"1096":1,"1192":1,"1212":2,"1247":12,"1256":1,"1259":9,"1268":2,"1270":7,"1275":1,"1285":3,"1310":15,"1316":2,"1322":13,"1332":5,"1338":12,"1365":1,"1368":1,"1378":1,"1395":5,"1399":1,"1401":2,"1417":2,"1420":1,"1424":6,"1426":13,"1434":2,"1437":1,"1441":3,"1442":1,"1464":3,"1466":6,"1495":2,"1576":1,"1585":1,"1664":1,"1667":1,"1842":1,"1918":1,"1921":1,"1945":1,"1948":1,"2001":1,"2005":1,"2009":1,"2012":2,"2155":14,"2156":1,"2158":2,"2159":4,"2176":6,"2178":1,"2179":1,"2190":1,"2192":2}}],["metricalerts",{"3":{"609":1,"1466":1}}],["metricmeasurecolumn",{"2":{"609":1,"2157":1},"3":{"609":2,"1466":2,"2157":1}}],["metricstreamconfiguration",{"2":{"1332":1,"1442":1},"3":{"1332":1,"1338":1,"1442":1,"1576":1}}],["metricsinstrumentationtoggletests",{"3":{"1199":1,"1207":2,"1338":1,"1437":1}}],["metrics",{"2":{"407":1},"3":{"0":2,"15":1,"125":1,"395":4,"397":7,"398":1,"399":2,"401":1,"402":4,"404":1,"407":3,"409":5,"707":1,"709":1,"711":1,"859":1,"861":2,"897":1,"914":1,"916":1,"917":1,"1020":1,"1043":1,"1044":1,"1213":1,"1247":5,"1259":2,"1268":1,"1270":5,"1285":1,"1310":2,"1322":3,"1332":4,"1338":13,"1420":1,"1424":1,"1426":4,"1435":4,"1437":1,"1439":1,"1442":6,"1464":3,"1466":3,"1486":1,"1495":3,"1549":1,"1576":2,"1590":1,"1631":1,"1640":1,"1663":1,"1676":4,"1840":1,"1922":1,"2002":1,"2004":1,"2005":2,"2009":1,"2152":1,"2153":2,"2154":1,"2155":6,"2156":1,"2158":3,"2159":3,"2214":1,"2231":1}}],["metric",{"2":{"916":1},"3":{"0":7,"20":1,"343":1,"395":2,"397":2,"398":2,"527":1,"530":2,"607":2,"608":2,"609":4,"610":2,"611":1,"613":1,"727":1,"818":1,"860":1,"861":3,"862":2,"863":1,"897":2,"898":1,"899":1,"914":1,"916":3,"918":1,"1247":5,"1259":1,"1268":1,"1270":7,"1275":1,"1308":1,"1310":2,"1316":1,"1322":4,"1332":1,"1338":9,"1395":3,"1401":2,"1414":1,"1435":4,"1437":1,"1442":4,"1445":2,"1449":1,"1466":11,"1468":2,"1473":3,"1475":1,"1576":2,"1585":1,"1588":1,"1590":2,"1595":1,"1597":1,"1664":1,"1676":1,"1705":1,"1708":1,"1842":1,"1908":2,"1910":1,"1911":1,"1918":2,"2009":1,"2013":1,"2050":1,"2155":2,"2157":5,"2159":2}}],["ma0176",{"3":{"1435":1}}],["ma0136",{"3":{"1435":1}}],["ma0004",{"2":{"481":1,"482":1},"3":{"481":1,"482":2,"490":1,"491":1,"492":1}}],["ma0076",{"2":{"269":1},"3":{"0":1,"265":1,"269":1,"485":1,"1576":1,"2084":1}}],["magic",{"3":{"1270":1,"1284":1,"1285":2,"1299":2,"1308":2,"1310":4,"1322":4,"1323":1,"1349":1,"1352":1,"1358":1,"1394":2,"1395":1,"1401":2,"1410":1,"1414":7,"1435":1,"1437":1,"1466":1,"1525":1,"1553":1,"1556":1,"1630":1,"1640":2,"1858":1,"1910":1,"1957":1,"2125":1,"2127":2,"2135":1,"2138":1,"2219":1}}],["magazine",{"3":{"1074":1}}],["magnitude",{"3":{"592":1,"860":1,"1435":1,"1706":1,"2000":1}}],["macintosh",{"3":{"1323":2}}],["macos",{"3":{"1212":1,"1380":1,"1415":3,"1416":2,"1458":3,"1512":1,"1605":1,"1739":1,"1772":1,"2077":1,"2229":1}}],["mac",{"3":{"682":1,"684":1,"1285":1,"1323":1,"1416":4,"1659":1,"1668":1}}],["maccatalyst",{"3":{"412":1,"413":2,"434":1,"436":1,"1207":2,"1415":7,"1416":3,"1453":1,"1458":1,"2119":1}}],["machinery",{"1":{"1039":1,"1040":1,"1041":1,"1042":1,"1043":1,"1044":1,"1045":1,"1046":1},"3":{"0":2,"46":1,"164":1,"167":1,"225":1,"230":1,"255":1,"269":1,"272":1,"274":1,"447":1,"448":1,"455":1,"469":1,"470":1,"477":1,"536":1,"548":1,"556":1,"572":1,"676":1,"678":1,"697":1,"733":1,"744":1,"802":1,"845":1,"921":1,"1039":1,"1040":1,"1041":1,"1043":1,"1046":1,"1076":1,"1144":1,"1201":1,"1212":3,"1217":1,"1227":1,"1247":3,"1248":1,"1249":2,"1252":1,"1259":3,"1270":2,"1275":1,"1278":1,"1280":1,"1285":2,"1286":1,"1299":1,"1300":1,"1301":1,"1308":3,"1309":2,"1310":4,"1316":1,"1317":1,"1318":1,"1322":1,"1323":4,"1327":1,"1332":1,"1339":1,"1342":1,"1358":11,"1362":1,"1366":1,"1369":1,"1375":1,"1378":1,"1379":3,"1394":3,"1395":4,"1400":1,"1401":2,"1402":1,"1414":2,"1427":1,"1435":13,"1438":1,"1487":1,"1570":1,"1576":1,"1799":1,"1825":1,"1852":1,"1853":1,"1860":1,"1883":1,"1894":1,"1951":1,"2054":1,"2078":2,"2080":1,"2085":1,"2128":1,"2130":1,"2131":1,"2134":1,"2180":1,"2181":2,"2192":1,"2231":2}}],["machines",{"3":{"0":1,"27":1,"926":1,"1161":1,"1401":1,"1435":3}}],["machine",{"0":{"813":1,"1765":1},"3":{"0":3,"264":1,"265":5,"295":1,"450":1,"485":1,"537":2,"576":1,"584":1,"592":2,"593":1,"620":1,"641":2,"665":2,"725":1,"741":1,"782":1,"784":1,"791":1,"808":2,"809":1,"810":6,"811":2,"812":1,"813":1,"815":1,"914":1,"926":1,"1067":2,"1068":1,"1116":1,"1117":1,"1161":2,"1162":1,"1212":2,"1219":1,"1229":1,"1237":1,"1247":1,"1249":1,"1259":2,"1263":1,"1270":2,"1271":4,"1295":1,"1299":5,"1308":1,"1310":12,"1322":2,"1323":3,"1334":1,"1335":1,"1338":9,"1349":2,"1358":19,"1379":1,"1388":1,"1389":1,"1394":4,"1395":3,"1401":7,"1414":5,"1415":1,"1416":1,"1429":1,"1435":35,"1437":2,"1440":1,"1441":1,"1452":2,"1453":2,"1454":1,"1480":1,"1487":1,"1489":1,"1491":2,"1495":1,"1524":1,"1525":6,"1576":4,"1590":6,"1599":1,"1632":2,"1665":1,"1667":1,"1688":1,"1748":2,"1763":1,"1764":1,"1804":1,"1810":1,"1925":1,"1969":1,"2002":1,"2046":2,"2082":2,"2085":1,"2090":1,"2105":1,"2131":1,"2167":2,"2180":1,"2219":1,"2231":2}}],["mailaddress",{"2":{"1343":1},"3":{"1343":1,"1349":2,"1414":2,"1638":1,"1639":1}}],["mailing",{"3":{"1310":1,"1763":2}}],["mailmessage",{"3":{"1308":2,"1322":1}}],["mails",{"3":{"1293":1,"1406":1}}],["mailto",{"3":{"1271":1,"1416":2}}],["mailed",{"3":{"1184":1,"1299":1,"1323":1}}],["mailbox",{"3":{"1150":1,"1435":1}}],["maildevsmtpport",{"3":{"1338":1,"1440":1}}],["maildevhttpport",{"3":{"1338":1,"1440":1}}],["maildev",{"3":{"1069":1,"1301":1,"1319":1,"1325":1,"1338":6,"1439":1,"1440":4,"1443":2,"1445":2,"1489":1,"1525":1,"1590":1,"1659":1,"1937":1,"2004":2,"2012":1}}],["mail",{"2":{"72":1,"1343":1},"3":{"72":3,"230":3,"673":1,"674":2,"675":1,"926":1,"1059":1,"1108":2,"1203":4,"1207":12,"1294":1,"1299":1,"1301":2,"1308":12,"1310":1,"1319":2,"1322":7,"1338":2,"1343":1,"1349":2,"1407":1,"1414":3,"1435":1,"1437":1,"1438":1,"1466":1,"1511":2,"1931":1,"2002":1,"2003":1,"2011":1,"2012":1,"2234":1,"2236":1}}],["mainly",{"3":{"2108":1}}],["mainlauncher",{"3":{"428":1,"1415":3}}],["mainlayout",{"2":{"275":1,"276":1,"1606":1,"1643":2,"1740":1,"2076":1},"3":{"0":2,"273":6,"275":2,"276":2,"649":3,"652":1,"1323":10,"1395":1,"1525":3,"1576":4,"1581":1,"1606":1,"1626":1,"1643":5,"1652":1,"1686":1,"1740":1,"2076":3,"2082":4}}],["mainapplication",{"3":{"1199":1,"1203":1,"1207":2,"1212":1,"1415":13}}],["mainactivity",{"2":{"423":1},"3":{"418":3,"422":2,"423":2,"425":2,"426":2,"427":1,"428":1,"429":1,"1199":2,"1203":1,"1207":2,"1415":25,"1495":1}}],["mainpage",{"3":{"1199":2,"1203":1,"1207":2,"1212":1,"1415":20,"1416":3}}],["mainpagebase",{"3":{"1199":2,"1203":1,"1207":2,"1415":9,"1416":27,"1495":1}}],["mainthread",{"3":{"1416":6}}],["maintenance",{"3":{"0":1,"336":1,"360":1,"399":1,"423":1,"529":1,"809":1,"814":1,"1075":1,"1247":1,"1285":2,"1299":1,"1349":1,"1368":1,"1394":1,"1435":2,"1458":1,"1464":1,"1843":1,"1847":1,"2141":1}}],["maintainable",{"3":{"1435":1}}],["maintainability",{"3":{"0":1,"1011":1,"1495":1,"1525":1,"1533":1,"1535":1,"1551":1,"1552":1,"1570":1,"1590":1,"2041":1}}],["maintains",{"3":{"1285":1,"1300":1,"1322":1,"1368":1,"1435":1,"1915":1}}],["maintaining",{"3":{"486":1,"634":1,"1308":1,"1310":1,"1358":1,"1435":2,"1478":1,"1638":1}}],["maintainers",{"3":{"1435":1}}],["maintainer",{"3":{"150":1,"590":1,"1435":1,"1453":1,"1458":1,"2046":1}}],["maintained",{"0":{"1520":1},"3":{"0":1,"58":1,"135":1,"342":1,"584":1,"609":1,"620":1,"634":1,"642":1,"683":1,"759":1,"801":1,"839":1,"974":2,"1237":1,"1285":1,"1323":1,"1338":1,"1349":2,"1358":6,"1379":1,"1395":2,"1415":1,"1435":1,"1466":1,"1473":1,"1478":1,"1483":1,"1489":1,"1525":1,"1527":1,"1533":1,"1545":1,"1570":1,"1576":1,"1578":1,"1592":1,"1598":1,"1599":1,"1657":1,"1704":1,"1884":1,"2097":1,"2157":1,"2232":1}}],["maintain",{"3":{"136":1,"167":1,"311":1,"1323":1,"1394":1,"1414":1,"1491":1,"1637":1,"1876":1,"1884":1,"1941":1,"1993":1,"2047":1}}],["main",{"0":{"1466":1},"2":{"63":1,"66":1,"73":1,"296":1,"365":1,"373":1,"374":1,"375":1,"566":1,"593":1,"597":1,"601":1,"604":1,"607":2,"613":1,"629":1,"644":1,"719":1,"756":1,"761":1,"766":1,"794":1,"877":1,"920":1,"1016":1,"1128":1,"1457":1,"1462":1,"1463":1,"1475":1,"1477":1,"1582":1,"1588":1,"1596":1,"1606":1,"1643":1,"1740":1},"3":{"0":5,"15":1,"27":1,"47":1,"59":1,"62":1,"63":2,"66":1,"72":1,"73":1,"92":1,"93":6,"94":3,"95":1,"96":3,"104":3,"128":1,"135":1,"162":1,"180":2,"261":1,"265":1,"291":2,"296":1,"365":1,"368":1,"373":1,"374":2,"375":1,"397":2,"403":5,"409":2,"495":2,"526":1,"528":1,"530":1,"538":1,"543":1,"550":1,"564":1,"566":2,"572":1,"591":2,"593":1,"597":1,"599":57,"600":8,"601":11,"602":3,"603":2,"604":3,"607":6,"608":1,"609":44,"611":3,"612":2,"613":5,"629":2,"633":1,"640":12,"642":4,"643":3,"644":2,"648":1,"664":2,"665":5,"668":4,"669":4,"682":1,"684":1,"719":3,"720":2,"756":2,"757":6,"761":3,"765":5,"766":4,"768":1,"774":2,"790":2,"794":1,"827":3,"832":2,"837":2,"854":1,"869":3,"875":3,"877":3,"914":2,"915":1,"916":2,"917":2,"920":1,"992":1,"995":1,"998":1,"1016":1,"1017":2,"1018":1,"1020":2,"1043":1,"1044":1,"1045":1,"1116":2,"1119":1,"1125":1,"1128":1,"1154":2,"1156":2,"1212":1,"1229":2,"1299":1,"1308":1,"1323":4,"1330":1,"1338":3,"1349":3,"1358":4,"1379":1,"1394":1,"1395":5,"1415":10,"1416":7,"1430":2,"1435":19,"1437":1,"1440":7,"1441":5,"1442":8,"1445":27,"1446":1,"1449":2,"1450":2,"1452":5,"1453":4,"1454":18,"1455":1,"1456":1,"1457":1,"1459":3,"1461":1,"1462":5,"1463":1,"1464":8,"1465":8,"1466":302,"1469":9,"1471":5,"1472":10,"1473":19,"1475":26,"1476":5,"1477":3,"1479":1,"1480":8,"1481":3,"1482":1,"1483":6,"1485":1,"1488":3,"1491":7,"1495":19,"1523":2,"1524":2,"1525":17,"1530":2,"1531":2,"1533":3,"1534":4,"1553":1,"1576":10,"1581":4,"1582":2,"1584":1,"1585":3,"1588":3,"1589":1,"1590":21,"1595":5,"1596":4,"1599":2,"1606":2,"1610":1,"1615":1,"1631":18,"1638":1,"1640":6,"1643":2,"1664":1,"1674":1,"1675":1,"1678":1,"1682":1,"1705":1,"1708":1,"1715":1,"1726":2,"1727":1,"1728":1,"1730":1,"1740":2,"2033":1,"2045":1,"2076":1,"2099":1,"2109":1,"2156":2,"2157":8,"2167":1}}],["masquerading",{"3":{"1542":1,"2047":1}}],["massive",{"3":{"1548":1}}],["mass",{"3":{"666":1}}],["masstransitpackageids",{"2":{"1075":1},"3":{"1075":1,"1435":1,"1490":1}}],["masstransitstatemachine",{"2":{"809":1,"810":1,"813":1},"3":{"0":1,"809":1,"810":1,"813":1}}],["masstransit",{"1":{"144":1,"145":1,"146":1,"147":1,"148":1,"149":1,"150":1,"151":1,"152":1,"153":1,"1072":1,"1073":1,"1074":1,"1075":1,"1076":1,"1077":1,"1078":1,"1079":1},"2":{"54":1,"145":1,"146":1,"147":3,"149":1,"150":1,"152":2,"810":3,"1073":1,"1074":2,"1075":1,"1078":1,"1488":1,"1737":1,"2015":1},"3":{"0":14,"20":1,"54":1,"59":2,"69":1,"77":1,"78":1,"79":2,"131":2,"133":1,"143":1,"144":1,"145":13,"146":1,"147":8,"148":1,"149":3,"150":7,"152":5,"153":1,"194":1,"195":1,"201":1,"255":1,"369":1,"371":1,"376":1,"639":1,"640":5,"641":2,"645":1,"683":1,"810":6,"812":1,"814":1,"815":1,"818":3,"819":1,"820":1,"845":1,"846":1,"1013":1,"1072":1,"1073":2,"1074":12,"1075":6,"1077":1,"1078":2,"1212":3,"1213":1,"1214":2,"1257":4,"1258":3,"1259":26,"1310":5,"1311":1,"1316":1,"1322":30,"1326":3,"1338":5,"1349":1,"1354":2,"1358":1,"1395":1,"1412":1,"1414":2,"1430":1,"1435":29,"1436":1,"1437":3,"1440":1,"1443":3,"1445":3,"1453":2,"1454":1,"1466":8,"1486":1,"1488":8,"1489":1,"1490":3,"1507":1,"1522":1,"1525":2,"1575":2,"1576":6,"1584":1,"1585":1,"1587":1,"1589":1,"1590":2,"1598":1,"1610":1,"1648":1,"1653":1,"1654":1,"1655":1,"1659":1,"1662":1,"1664":1,"1670":1,"1672":1,"1737":1,"1751":1,"1789":2,"1800":2,"1841":2,"1842":3,"1888":2,"1909":1,"2012":1,"2015":1,"2019":1,"2041":1,"2042":1,"2047":2,"2103":1,"2110":1,"2111":1,"2155":1,"2167":2,"2231":4}}],["masked",{"3":{"1237":3,"1414":1,"1435":1,"1465":1}}],["masking",{"3":{"1230":1,"1237":2,"1299":1,"1395":1,"1435":3,"1574":1}}],["mask",{"3":{"415":1,"1237":1,"1310":4,"1322":1,"1405":1,"1435":2,"1458":1,"1495":1,"1548":1,"1674":1,"2121":1}}],["maskseverypiimember",{"3":{"1435":1}}],["masks",{"3":{"39":1,"227":1,"684":1,"1237":1,"1421":1,"1437":1,"1452":1,"1466":1,"1560":1,"1576":1,"2062":1}}],["masterclass",{"3":{"1454":1}}],["master",{"3":{"403":1,"719":1,"766":1,"1323":1,"1394":1,"1414":1,"1416":1,"1435":1,"1466":4,"1495":1,"1628":1,"1631":1,"1636":1,"1637":3}}],["malicious",{"3":{"1310":1,"1414":1,"1451":1,"1458":1,"1466":1}}],["malwarescanning",{"3":{"1116":1,"1119":1}}],["malware",{"3":{"0":1,"1114":1,"1116":1,"1118":1,"1119":1,"1468":1,"1469":1}}],["malformedresponsereason",{"3":{"1368":1}}],["malformedresponse",{"3":{"1299":1}}],["malformedresponsecode",{"3":{"1299":2}}],["malformedifmatchresult",{"3":{"1310":1}}],["malformedifmatch",{"2":{"341":1},"3":{"0":1,"341":1,"1310":1}}],["malformed",{"3":{"0":3,"122":1,"202":1,"255":1,"341":1,"361":1,"657":1,"707":1,"840":1,"854":1,"907":1,"948":1,"1016":1,"1021":1,"1228":1,"1229":1,"1236":1,"1237":2,"1247":4,"1263":2,"1270":1,"1285":6,"1291":1,"1293":1,"1299":12,"1310":5,"1317":1,"1322":9,"1323":6,"1338":2,"1349":3,"1358":11,"1364":1,"1368":3,"1379":1,"1394":1,"1395":4,"1401":2,"1414":2,"1435":3,"1437":5,"1640":3,"1650":1,"1719":1,"1766":1,"1820":1,"1873":1,"1921":1,"1953":2,"1958":2,"2092":1}}],["mariner",{"3":{"2219":1}}],["mariadb",{"3":{"2219":1}}],["martin",{"3":{"1807":1}}],["marten",{"3":{"0":1,"1144":2}}],["marshalling",{"3":{"1415":1}}],["marshalled",{"3":{"1394":1,"1416":1,"1555":1,"1576":1}}],["marshals",{"3":{"682":1,"1323":3,"1395":1,"1416":3}}],["marries",{"3":{"1358":1}}],["margin",{"3":{"1126":1,"1323":4,"1358":1,"1416":1,"1435":2,"1437":1,"1446":2,"1711":1}}],["marginal",{"3":{"0":2,"237":1,"627":1,"862":1,"863":1}}],["march",{"3":{"625":1}}],["markquestionansweredasync",{"3":{"1401":1}}],["markready",{"3":{"1338":3}}],["markreadasync",{"3":{"1308":1,"1323":4,"1435":1}}],["markread",{"3":{"1203":2,"1207":4,"1305":1,"1308":5,"1576":1}}],["marknotificationreadhandlertests",{"3":{"1199":1,"1207":4}}],["marknotificationreadhandler",{"2":{"1305":1,"1936":1},"3":{"1199":4,"1203":1,"1207":2,"1285":1,"1305":3,"1308":6,"1936":1}}],["marknotificationreadcommand",{"2":{"1305":1,"1938":1},"3":{"1199":7,"1203":1,"1207":2,"1305":1,"1308":10,"1938":1}}],["markanswered",{"3":{"1397":1,"1401":3}}],["markansweredasync",{"3":{"1395":2,"1401":1}}],["markallreadasync",{"3":{"1308":1,"1323":3,"1435":1}}],["markallread",{"3":{"1203":2,"1207":4,"1305":1,"1308":4,"1576":1}}],["markallnotificationsreadhandlertrackingtests",{"3":{"1199":1,"1207":4}}],["markallnotificationsreadhandlertests",{"3":{"1199":1,"1207":4,"1495":1}}],["markallnotificationsreadhandler",{"2":{"1305":1,"1936":1},"3":{"1199":5,"1203":1,"1207":2,"1285":1,"1305":2,"1308":4,"1936":1}}],["markallnotificationsreadcommand",{"2":{"1305":1},"3":{"1199":8,"1203":1,"1207":2,"1305":1,"1308":6}}],["markasdelivered",{"3":{"1768":2}}],["markascheckedout",{"2":{"1768":1},"3":{"1768":1}}],["markascancelled",{"2":{"2162":2},"3":{"536":1,"539":1,"1768":1,"2162":2,"2166":1}}],["markassent",{"2":{"1932":1},"3":{"1303":1,"1308":3,"1932":1}}],["markaspaymentfailed",{"2":{"2166":1},"3":{"536":1,"1768":1,"2166":1}}],["markaspaidmanually",{"3":{"1764":1,"1768":1}}],["markaspaid",{"3":{"536":1,"1768":1,"2166":1}}],["markasfailed",{"2":{"896":1,"1932":1},"3":{"225":1,"896":1,"1303":1,"1308":3,"1932":2}}],["markasread",{"2":{"1936":1},"3":{"225":1,"1305":2,"1308":11,"1495":1,"1576":1,"1936":1}}],["markdirty",{"3":{"1323":1,"1394":5,"1395":1}}],["markdown",{"3":{"564":1,"567":1,"611":1,"1285":1,"1435":2,"1452":1,"1456":1,"1474":1}}],["markdeletedasync",{"2":{"459":1,"461":1},"3":{"459":2,"461":1,"1299":2,"1322":1}}],["markinventoryrestored",{"2":{"538":1,"2163":1},"3":{"536":1,"538":1,"2163":1}}],["markings",{"3":{"1671":1}}],["marking",{"3":{"41":1,"196":2,"973":1,"1212":1,"1232":3,"1237":7,"1256":1,"1259":1,"1264":1,"1285":3,"1299":1,"1308":1,"1311":1,"1358":1,"1401":1,"1414":6,"1435":3,"1574":1,"1576":1,"1581":1,"1585":1,"1631":1,"2066":1}}],["markprocessedasync",{"2":{"195":1,"201":1},"3":{"195":1,"201":1,"1258":1,"1259":8,"1285":1,"1585":1}}],["mark",{"3":{"18":2,"20":1,"54":1,"290":1,"536":1,"538":1,"757":1,"761":1,"829":1,"1252":2,"1254":2,"1259":3,"1274":1,"1285":2,"1288":1,"1299":3,"1305":2,"1308":9,"1310":1,"1311":1,"1322":1,"1323":14,"1358":1,"1395":1,"1396":1,"1401":7,"1415":1,"1430":1,"1435":12,"1442":1,"1461":1,"1488":1,"1525":4,"1552":1,"1590":1,"1650":1,"1652":1,"1688":1,"1764":3,"1767":2,"1837":1,"1844":1,"1907":1,"1909":1,"1936":2,"2006":1,"2164":2,"2166":1,"2167":1}}],["markupstring",{"2":{"1562":1},"3":{"1525":1,"1562":1,"1576":1}}],["markupsnapshotresult",{"2":{"1431":1},"3":{"1199":2,"1207":1,"1431":1,"1435":2,"1495":1}}],["markupsnapshot",{"3":{"954":2,"1199":5,"1207":3,"1427":1,"1431":9,"1435":8,"1487":1,"1495":1,"1576":3,"1585":1}}],["markuproots",{"3":{"1435":3}}],["markup",{"3":{"0":2,"86":1,"265":2,"273":1,"618":3,"620":1,"648":1,"953":2,"954":3,"957":1,"1207":1,"1323":37,"1349":2,"1358":2,"1383":1,"1390":1,"1393":1,"1394":65,"1395":38,"1401":12,"1414":7,"1416":6,"1430":1,"1431":1,"1433":1,"1435":58,"1437":6,"1487":3,"1488":1,"1496":1,"1497":1,"1524":2,"1525":2,"1530":2,"1533":2,"1534":1,"1554":2,"1562":1,"1563":1,"1576":5,"1585":1,"1590":1,"1596":1,"1599":1,"1642":1,"1659":1,"1670":1,"1701":1,"2056":2,"2058":3,"2059":1,"2071":1,"2076":1,"2078":2}}],["marketing",{"3":{"1358":1,"1394":1,"1415":1,"2239":1}}],["markers",{"0":{"1232":1,"1236":1,"1266":1},"2":{"1270":1},"3":{"0":2,"121":2,"123":1,"125":1,"126":1,"160":1,"246":1,"247":1,"522":1,"523":1,"564":1,"741":1,"744":1,"923":1,"974":1,"1059":1,"1089":1,"1091":1,"1095":1,"1100":1,"1192":1,"1201":1,"1202":2,"1203":10,"1207":16,"1212":1,"1230":2,"1232":1,"1249":1,"1260":2,"1264":11,"1270":21,"1285":3,"1320":1,"1322":4,"1323":3,"1358":16,"1394":2,"1395":1,"1402":2,"1414":8,"1421":1,"1423":3,"1435":3,"1437":1,"1525":1,"1576":1,"1590":1,"1659":1,"1661":2,"1666":1,"1825":1,"2097":1,"2135":1,"2189":1}}],["marker",{"0":{"1264":1,"1822":1,"1916":1,"2135":1,"2163":1},"3":{"0":9,"51":1,"115":1,"117":1,"121":5,"122":3,"123":1,"126":2,"135":1,"242":1,"246":1,"265":2,"293":1,"295":1,"312":1,"402":2,"457":1,"459":11,"461":3,"463":1,"465":5,"466":3,"491":1,"492":1,"493":1,"522":2,"523":1,"534":2,"536":9,"537":2,"538":7,"539":1,"540":2,"541":1,"564":3,"565":1,"566":1,"567":1,"583":1,"618":1,"633":1,"640":1,"700":1,"713":1,"715":3,"716":1,"717":1,"741":1,"742":1,"766":1,"791":1,"792":1,"794":1,"809":1,"815":1,"891":1,"892":1,"893":1,"907":1,"910":1,"923":1,"927":1,"945":1,"947":1,"948":2,"972":7,"973":3,"974":1,"982":1,"988":1,"1042":1,"1059":2,"1091":2,"1093":1,"1095":1,"1100":1,"1192":1,"1202":1,"1203":1,"1212":3,"1232":3,"1236":1,"1237":14,"1239":1,"1247":2,"1249":2,"1259":6,"1260":2,"1262":1,"1264":5,"1266":3,"1267":3,"1269":1,"1270":64,"1273":1,"1277":1,"1281":2,"1282":1,"1285":20,"1296":1,"1299":21,"1300":2,"1308":9,"1309":2,"1310":19,"1311":8,"1312":1,"1315":2,"1317":1,"1322":42,"1323":22,"1326":1,"1332":2,"1338":14,"1339":2,"1349":11,"1353":1,"1358":40,"1368":3,"1379":6,"1393":1,"1394":17,"1395":33,"1400":1,"1401":6,"1404":1,"1409":2,"1414":38,"1415":2,"1423":2,"1426":5,"1434":1,"1435":56,"1437":5,"1440":1,"1443":1,"1454":1,"1477":1,"1488":1,"1495":3,"1506":1,"1525":1,"1530":1,"1531":1,"1576":2,"1581":1,"1590":2,"1595":1,"1652":1,"1661":1,"1665":1,"1666":2,"1671":1,"1748":1,"1752":2,"1763":1,"1764":4,"1773":1,"1822":2,"1826":1,"1838":1,"1850":1,"1901":1,"1907":1,"1913":1,"1919":1,"1922":1,"1928":2,"1932":1,"1987":2,"2062":1,"2091":3,"2094":1,"2095":1,"2097":2,"2133":1,"2138":2,"2160":1,"2163":11,"2167":6,"2168":5,"2183":1,"2188":2,"2193":1,"2198":1,"2202":1,"2231":3}}],["markerduration",{"2":{"459":1,"461":1,"463":1,"2065":1},"3":{"0":1,"459":2,"461":1,"463":1,"1299":3,"1310":2,"2065":1}}],["marked",{"3":{"0":2,"18":1,"20":1,"39":2,"41":1,"51":1,"54":2,"126":2,"160":2,"189":1,"484":1,"490":1,"534":1,"537":1,"538":1,"543":2,"690":1,"714":1,"905":1,"910":1,"980":1,"1003":1,"1203":1,"1212":1,"1229":1,"1237":6,"1252":3,"1259":2,"1264":1,"1270":1,"1277":1,"1285":5,"1299":6,"1308":1,"1310":2,"1311":4,"1322":1,"1341":1,"1346":1,"1348":1,"1349":5,"1356":1,"1358":10,"1379":12,"1401":6,"1404":1,"1414":3,"1421":1,"1435":20,"1437":2,"1440":1,"1458":1,"1466":1,"1488":1,"1495":2,"1507":1,"1576":5,"1639":1,"1671":1,"1698":1,"1726":1,"1736":1,"1748":3,"1763":2,"1766":2,"1820":1,"1838":1,"1839":1,"1929":1,"1974":1,"2062":1,"2088":1,"2097":1,"2164":1,"2196":1}}],["markstale",{"3":{"1323":2,"1576":2}}],["marksnothingcurrent",{"3":{"907":1}}],["marks",{"3":{"0":4,"157":1,"160":1,"344":1,"528":1,"538":1,"540":1,"543":1,"657":1,"674":1,"873":1,"876":1,"907":1,"1012":1,"1091":1,"1233":1,"1236":1,"1237":4,"1257":1,"1259":5,"1265":1,"1270":1,"1274":1,"1285":7,"1296":1,"1299":11,"1308":7,"1309":1,"1310":5,"1311":4,"1321":1,"1322":1,"1323":2,"1338":2,"1349":6,"1358":3,"1368":1,"1394":1,"1395":3,"1401":3,"1404":1,"1414":8,"1415":2,"1416":1,"1426":2,"1435":14,"1440":1,"1511":1,"1576":1,"1581":1,"1585":1,"1629":1,"1631":1,"1638":1,"1661":2,"1665":1,"1671":1,"1686":1,"1764":1,"1783":1,"1845":1,"1870":1,"1875":1,"1928":2,"1936":2,"2011":1,"2012":1,"2043":1,"2090":1,"2164":1}}],["making",{"0":{"1223":1,"2143":1},"3":{"0":3,"159":1,"178":1,"235":1,"237":1,"264":1,"286":1,"340":1,"436":1,"520":1,"551":1,"619":1,"651":1,"666":2,"683":1,"691":1,"698":1,"700":1,"709":1,"716":1,"724":1,"742":1,"767":2,"805":1,"819":1,"820":1,"827":2,"855":1,"890":1,"907":1,"965":1,"996":1,"1018":1,"1027":1,"1051":1,"1076":1,"1117":1,"1212":1,"1229":2,"1237":4,"1247":3,"1259":6,"1270":7,"1271":2,"1285":8,"1292":1,"1299":5,"1300":1,"1308":2,"1309":2,"1310":6,"1322":4,"1323":11,"1331":1,"1338":6,"1349":9,"1358":15,"1368":1,"1394":8,"1395":12,"1401":5,"1416":6,"1435":8,"1437":1,"1453":1,"1454":5,"1466":1,"1491":1,"1637":1,"1640":1,"1733":1,"1775":1,"1805":1,"1900":2,"1955":1,"2006":1,"2044":1,"2069":1,"2080":1,"2154":1,"2161":1}}],["makegenericmethod",{"3":{"1247":1,"1270":1,"1285":5}}],["makegenerictype",{"3":{"1229":1,"1237":1,"1247":1,"1259":1,"1285":5,"1299":2,"1322":1}}],["makes",{"0":{"1781":1,"1823":1,"1871":1},"1":{"1979":1,"1980":1,"1981":1,"1982":1,"1983":1,"1984":1},"3":{"0":20,"24":2,"26":1,"72":2,"100":1,"109":1,"110":3,"111":1,"116":1,"125":1,"136":1,"137":1,"147":1,"150":1,"151":1,"158":1,"160":1,"162":1,"168":1,"177":2,"182":1,"186":2,"187":1,"188":1,"189":1,"194":1,"195":1,"215":1,"217":1,"218":1,"219":1,"230":1,"236":1,"243":1,"244":1,"245":1,"255":2,"264":1,"265":3,"266":3,"273":1,"283":1,"284":1,"309":1,"311":1,"341":1,"342":2,"361":3,"374":1,"388":2,"397":1,"398":1,"407":1,"449":1,"458":1,"460":1,"465":1,"477":1,"484":1,"498":1,"500":1,"511":1,"530":2,"543":1,"546":2,"551":1,"556":1,"557":1,"558":1,"560":1,"563":2,"572":1,"573":1,"582":1,"584":2,"591":1,"592":3,"599":1,"600":1,"601":1,"603":1,"607":2,"609":1,"610":1,"619":1,"641":2,"649":1,"650":1,"656":2,"658":1,"667":1,"670":1,"672":1,"674":1,"675":1,"690":1,"691":4,"692":1,"694":1,"697":1,"698":2,"708":1,"716":3,"717":1,"718":1,"721":1,"725":2,"733":3,"734":1,"735":1,"742":2,"748":1,"750":1,"751":2,"766":1,"767":1,"773":1,"789":1,"791":1,"799":1,"800":1,"802":1,"812":1,"819":1,"820":1,"823":1,"827":3,"828":1,"838":1,"839":1,"840":1,"847":1,"856":2,"860":1,"862":3,"871":1,"873":1,"874":1,"881":1,"882":2,"883":2,"888":1,"890":2,"891":1,"896":3,"898":1,"901":1,"906":1,"918":1,"923":1,"924":1,"932":2,"938":1,"940":2,"942":1,"946":1,"948":2,"954":1,"965":1,"968":1,"972":1,"988":2,"989":1,"990":2,"992":1,"996":1,"997":1,"1005":1,"1006":1,"1012":1,"1018":1,"1019":1,"1020":2,"1025":1,"1027":1,"1033":1,"1036":1,"1044":1,"1051":3,"1052":2,"1060":2,"1067":2,"1076":1,"1084":2,"1087":1,"1091":1,"1092":3,"1093":1,"1109":1,"1116":1,"1123":1,"1124":1,"1129":2,"1132":1,"1133":1,"1135":1,"1137":1,"1147":1,"1150":1,"1162":1,"1167":1,"1169":1,"1212":1,"1223":1,"1229":11,"1231":1,"1237":10,"1247":11,"1250":1,"1252":1,"1253":1,"1257":1,"1259":25,"1265":2,"1266":1,"1269":1,"1270":28,"1271":4,"1275":1,"1276":1,"1278":2,"1282":1,"1283":1,"1285":75,"1289":1,"1290":1,"1291":1,"1293":1,"1299":50,"1300":6,"1308":17,"1309":5,"1310":42,"1311":4,"1313":1,"1315":1,"1316":1,"1319":1,"1322":34,"1323":70,"1324":1,"1325":1,"1326":1,"1327":2,"1329":1,"1332":3,"1333":1,"1335":1,"1336":2,"1337":1,"1338":60,"1342":1,"1343":1,"1345":1,"1349":17,"1352":1,"1354":1,"1355":1,"1356":1,"1358":122,"1361":1,"1363":1,"1365":1,"1368":16,"1375":1,"1379":7,"1391":1,"1392":1,"1394":50,"1395":94,"1396":1,"1400":1,"1401":30,"1404":1,"1405":1,"1411":1,"1414":41,"1415":10,"1416":33,"1420":1,"1421":2,"1423":2,"1424":1,"1426":1,"1429":1,"1430":1,"1434":2,"1435":143,"1437":7,"1440":2,"1442":2,"1446":2,"1452":3,"1453":1,"1454":7,"1455":2,"1456":1,"1457":1,"1459":1,"1464":1,"1466":8,"1471":1,"1473":2,"1474":1,"1475":2,"1476":1,"1479":1,"1487":5,"1488":1,"1489":1,"1490":1,"1491":3,"1525":2,"1533":2,"1535":1,"1567":1,"1576":1,"1632":1,"1637":1,"1648":1,"1651":1,"1657":1,"1660":1,"1661":2,"1666":1,"1676":1,"1683":1,"1684":1,"1685":3,"1687":1,"1696":1,"1700":1,"1707":1,"1710":1,"1719":1,"1728":1,"1730":2,"1736":1,"1748":2,"1754":1,"1764":1,"1778":1,"1789":2,"1794":2,"1796":1,"1807":1,"1809":1,"1810":1,"1811":1,"1812":1,"1813":1,"1814":1,"1818":1,"1824":1,"1838":1,"1839":2,"1840":1,"1841":1,"1842":1,"1843":2,"1845":1,"1848":2,"1852":1,"1853":1,"1875":1,"1878":1,"1882":1,"1883":2,"1894":1,"1898":1,"1901":1,"1904":1,"1906":1,"1907":1,"1915":1,"1921":1,"1922":2,"1923":1,"1938":1,"1942":1,"1943":2,"1945":2,"1946":2,"1949":1,"1957":1,"1962":1,"1969":1,"1970":1,"1979":1,"1981":1,"1982":1,"1988":1,"1992":1,"1995":1,"1996":1,"1997":1,"1999":1,"2000":1,"2009":1,"2015":1,"2019":2,"2020":1,"2021":1,"2023":2,"2028":1,"2029":1,"2030":1,"2033":1,"2038":1,"2039":1,"2041":1,"2042":1,"2043":1,"2044":1,"2056":1,"2060":2,"2063":1,"2065":1,"2069":1,"2072":1,"2082":1,"2086":1,"2097":1,"2111":1,"2131":1,"2132":1,"2136":1,"2140":1,"2141":1,"2143":2,"2144":4,"2145":1,"2155":2,"2156":1,"2159":1,"2162":1,"2163":1,"2164":2,"2165":1,"2166":1,"2168":2,"2169":2,"2176":1,"2179":2,"2183":1,"2184":1,"2185":1,"2187":1,"2191":1,"2192":1,"2201":2,"2202":1,"2207":1}}],["make",{"0":{"2009":1},"3":{"0":4,"1":1,"6":1,"20":1,"39":1,"41":1,"88":1,"109":3,"160":1,"195":1,"219":1,"257":1,"295":1,"305":1,"342":1,"361":1,"373":1,"381":1,"383":1,"390":1,"395":1,"396":1,"403":1,"469":1,"500":1,"518":1,"537":1,"583":1,"584":1,"585":1,"593":1,"609":1,"618":1,"626":1,"650":1,"656":1,"657":1,"675":1,"683":1,"698":1,"709":1,"726":1,"729":1,"740":1,"741":1,"749":1,"750":1,"776":1,"810":1,"815":1,"820":1,"827":1,"829":1,"838":2,"848":1,"861":1,"862":1,"872":1,"889":1,"898":1,"899":1,"903":1,"906":1,"907":1,"914":1,"915":1,"926":1,"931":1,"932":1,"945":1,"988":1,"989":1,"997":1,"1018":1,"1020":1,"1034":2,"1035":2,"1042":1,"1052":1,"1059":1,"1060":1,"1067":1,"1083":1,"1091":1,"1092":1,"1100":1,"1108":1,"1123":1,"1139":1,"1149":1,"1187":1,"1212":1,"1219":1,"1221":1,"1229":3,"1237":2,"1242":1,"1247":3,"1249":1,"1258":2,"1259":1,"1265":1,"1268":1,"1269":3,"1270":4,"1271":3,"1276":1,"1280":1,"1285":21,"1299":13,"1300":1,"1308":2,"1310":12,"1312":1,"1318":1,"1319":1,"1322":7,"1323":12,"1329":1,"1333":1,"1336":1,"1338":7,"1343":1,"1349":7,"1356":1,"1357":1,"1358":29,"1365":1,"1368":2,"1372":1,"1378":1,"1379":1,"1394":14,"1395":22,"1401":5,"1402":1,"1408":1,"1409":1,"1414":2,"1415":2,"1416":9,"1426":1,"1430":1,"1435":37,"1437":1,"1440":2,"1452":1,"1454":5,"1464":1,"1466":4,"1470":1,"1472":1,"1487":1,"1488":1,"1491":1,"1584":1,"1594":1,"1595":1,"1638":2,"1650":1,"1655":1,"1677":1,"1685":1,"1701":1,"1707":1,"1719":1,"1727":1,"1728":1,"1748":1,"1764":2,"1787":2,"1792":1,"1802":1,"1806":2,"1811":1,"1826":2,"1827":1,"1828":1,"1831":1,"1840":1,"1845":2,"1852":2,"1858":1,"1861":2,"1862":1,"1864":1,"1867":1,"1870":1,"1876":2,"1885":2,"1889":1,"1891":2,"1900":1,"1904":1,"1906":1,"1910":1,"1911":3,"1929":2,"1940":1,"1946":1,"1947":1,"1953":1,"1957":1,"1958":1,"1963":1,"1964":1,"1971":1,"1978":1,"1979":1,"1980":1,"1984":1,"1994":3,"1995":1,"1996":1,"1997":1,"2003":1,"2008":1,"2027":1,"2037":1,"2039":1,"2042":1,"2048":1,"2059":1,"2066":1,"2067":1,"2068":1,"2079":1,"2096":1,"2104":1,"2107":1,"2110":1,"2112":1,"2124":1,"2135":1,"2138":1,"2141":1,"2144":1,"2152":1,"2154":1,"2159":3,"2160":1,"2165":2,"2166":1,"2168":1,"2169":1,"2178":2,"2179":3,"2191":2,"2192":3,"2196":1,"2199":1,"2202":4,"2208":1,"2210":1,"2231":1}}],["mat",{"3":{"1523":8,"1524":10,"1588":6,"1597":1}}],["matrices",{"3":{"1455":1,"1525":1,"1534":1,"1610":1}}],["matrix",{"0":{"1498":1,"1715":1},"2":{"1455":1},"3":{"137":1,"146":1,"148":1,"265":1,"618":1,"619":1,"684":1,"756":1,"863":2,"868":1,"1192":1,"1212":1,"1216":1,"1247":1,"1299":1,"1323":2,"1401":1,"1416":2,"1432":2,"1435":3,"1437":1,"1444":1,"1449":1,"1452":1,"1454":5,"1455":10,"1459":1,"1460":1,"1464":1,"1489":4,"1491":3,"1495":1,"1525":2,"1532":1,"1558":1,"1576":7,"1590":4,"1595":1,"1598":1,"1610":1,"1631":2,"1638":1,"1673":1,"1710":1,"2072":1,"2075":1,"2111":1}}],["mates",{"3":{"1271":1}}],["materialised",{"3":{"1338":2,"1395":3}}],["materializing",{"3":{"1237":1,"1242":1,"1243":1,"1245":1,"1259":1,"1269":1,"1270":1,"1285":5,"1308":3,"1310":1,"1322":1,"1323":1,"1352":1,"1358":5,"1394":1,"1395":2,"1401":2,"1414":2,"1435":1,"1987":1,"2166":1}}],["materializers",{"3":{"1285":3}}],["materializer",{"3":{"657":1,"1237":1,"1437":1}}],["materialize",{"3":{"31":1,"549":1,"609":1,"659":1,"741":1,"1243":2,"1247":1,"1269":1,"1270":3,"1271":1,"1278":1,"1285":4,"1299":2,"1300":1,"1308":3,"1310":2,"1322":1,"1358":4,"1394":1,"1395":1,"1401":1,"1426":1,"1435":1,"1437":2,"1666":1,"1882":1,"1955":1,"1964":1}}],["materializes",{"3":{"0":1,"166":1,"186":1,"551":1,"819":1,"964":1,"966":1,"1212":1,"1231":1,"1237":1,"1239":1,"1243":1,"1247":3,"1259":1,"1263":1,"1270":1,"1271":2,"1285":13,"1296":1,"1299":5,"1308":4,"1309":2,"1310":2,"1323":5,"1349":1,"1358":8,"1379":1,"1394":3,"1395":5,"1401":3,"1414":3,"1416":1,"1422":1,"1426":3,"1435":5,"1466":1,"1809":1,"1814":1,"1843":1,"1858":1,"1860":1,"1894":1,"1961":1,"2089":1}}],["materialized",{"3":{"0":1,"47":1,"71":1,"122":1,"188":1,"335":1,"609":2,"658":1,"799":1,"988":1,"1091":1,"1229":1,"1239":1,"1243":4,"1244":1,"1247":11,"1259":2,"1270":4,"1285":9,"1287":1,"1299":3,"1308":3,"1309":4,"1310":1,"1322":1,"1323":1,"1358":25,"1395":9,"1401":1,"1405":1,"1414":2,"1422":3,"1426":1,"1430":1,"1435":2,"1466":1,"1473":1,"1475":1,"1963":1,"1987":1,"2157":2,"2231":1}}],["materialization",{"3":{"0":1,"330":1,"359":1,"365":1,"657":1,"990":1,"1226":1,"1229":2,"1244":1,"1247":5,"1270":2,"1278":1,"1283":1,"1285":9,"1299":1,"1308":5,"1309":1,"1322":1,"1342":1,"1358":2,"1361":1,"1368":1,"1394":1,"1395":6,"1401":3,"1435":1,"1804":1,"1805":1,"1810":1,"1814":1,"2141":1}}],["materials",{"0":{"1356":1,"1388":1},"1":{"1113":1,"1114":1,"1115":1,"1116":1,"1117":1,"1118":1,"1119":1,"1120":1},"3":{"0":2,"370":1,"371":1,"372":1,"374":1,"443":1,"1113":1,"1116":5,"1117":3,"1212":1,"1347":1,"1349":2,"1356":2,"1358":4,"1372":1,"1376":1,"1379":2,"1380":1,"1388":1,"1394":10,"1435":1,"1453":1,"1629":1,"1630":5,"1635":1,"1639":2,"2125":1,"2231":1}}],["material",{"3":{"0":1,"30":1,"31":1,"284":1,"359":3,"361":1,"599":1,"650":1,"673":1,"674":1,"792":1,"827":1,"906":1,"907":1,"944":1,"946":3,"948":2,"950":1,"1060":1,"1063":1,"1066":1,"1115":1,"1117":1,"1118":1,"1212":1,"1241":1,"1284":1,"1285":2,"1286":2,"1287":1,"1291":1,"1293":1,"1299":20,"1310":1,"1322":2,"1323":6,"1338":1,"1341":2,"1349":3,"1350":1,"1351":1,"1356":1,"1358":2,"1372":2,"1379":1,"1388":1,"1394":14,"1408":1,"1409":1,"1414":6,"1416":1,"1428":1,"1429":1,"1435":4,"1437":3,"1441":1,"1446":1,"1455":1,"1466":2,"1639":1,"1644":1,"1666":1,"1713":1,"1901":4,"1904":1,"1972":1,"1981":1,"2011":1,"2023":1,"2055":2,"2141":2,"2144":1,"2165":1,"2197":1,"2202":1,"2229":1,"2231":1,"2233":1}}],["materially",{"3":{"0":1,"259":1,"474":1,"549":1,"799":1,"987":1,"1323":1,"1435":1,"1466":1,"1533":1}}],["maturity×weight",{"3":{"1522":1,"1573":1,"1587":1}}],["maturity",{"0":{"1532":1,"1583":1,"1597":1,"1796":1},"2":{"1528":1},"3":{"1216":1,"1515":1,"1518":1,"1522":5,"1523":6,"1525":27,"1528":3,"1529":4,"1530":10,"1531":2,"1532":2,"1533":1,"1534":7,"1535":1,"1536":9,"1553":1,"1564":1,"1573":4,"1574":10,"1575":1,"1576":20,"1577":3,"1579":4,"1580":4,"1581":4,"1582":4,"1583":4,"1584":4,"1585":1,"1587":3,"1588":1,"1590":21,"1591":3,"1593":3,"1594":3,"1595":3,"1596":3,"1598":1,"1672":1,"1783":6,"1793":1,"1796":1,"1797":3,"1798":7,"1799":1,"1801":2,"1902":2,"1928":2,"2033":1,"2040":1,"2042":2,"2047":1,"2049":1,"2052":1,"2058":2,"2061":1,"2068":1,"2113":1,"2170":1,"2181":2,"2194":1,"2230":4}}],["mature",{"3":{"1216":1,"1536":1,"1577":1,"1783":1,"1796":1,"1832":1}}],["mathematics",{"3":{"2221":1}}],["math",{"3":{"281":1,"330":1,"337":1,"1229":1,"1244":3,"1247":7,"1259":1,"1285":1,"1299":3,"1310":3,"1323":6,"1347":1,"1349":6,"1358":2,"1368":1,"1394":1,"1395":14,"1400":1,"1401":1,"1414":4,"1416":4,"1421":1,"1434":1,"1437":1,"1454":2,"1987":1,"1999":1}}],["mattering",{"3":{"1322":1,"2096":1}}],["matters",{"0":{"1788":1,"1808":1,"1813":1,"1818":1,"1835":1,"1847":1,"1863":1,"1869":1,"1887":1,"1893":1,"1900":1,"1906":1,"1908":1,"1925":1,"1960":1,"1966":1,"1973":1,"1980":1,"1986":1,"1992":1,"1998":1,"2050":1,"2061":1,"2081":1,"2124":1,"2129":1,"2134":1,"2140":1,"2147":1,"2154":1,"2161":1,"2170":1,"2181":1,"2194":1},"3":{"160":1,"198":1,"216":1,"264":1,"380":1,"387":1,"390":1,"423":1,"551":1,"592":1,"702":1,"827":1,"845":1,"847":1,"875":1,"906":1,"941":2,"1186":1,"1223":1,"1229":1,"1237":3,"1238":1,"1247":3,"1259":2,"1270":4,"1271":1,"1285":17,"1299":7,"1308":5,"1310":6,"1314":1,"1322":7,"1323":9,"1338":13,"1344":1,"1346":1,"1358":18,"1365":1,"1368":1,"1378":1,"1379":1,"1394":10,"1395":13,"1397":1,"1401":2,"1414":7,"1416":6,"1420":1,"1426":1,"1435":17,"1437":2,"1440":1,"1444":1,"1446":1,"1452":3,"1453":1,"1454":1,"1455":1,"1466":2,"1474":2,"1487":1,"1489":1,"1554":1,"1648":1,"1683":1,"1781":1,"1812":1,"1816":1,"1839":1,"1843":1,"1877":1,"1885":1,"1888":1,"1897":1,"1901":1,"1933":1,"1936":1,"1942":1,"1949":1,"1961":1,"2006":1,"2009":1,"2028":1,"2035":1,"2038":1,"2048":1,"2056":1,"2059":1,"2072":1,"2074":1,"2094":1,"2097":1,"2098":1,"2117":1,"2127":1,"2154":1,"2155":1,"2192":1,"2200":1}}],["matter",{"0":{"2042":1},"3":{"122":1,"160":1,"219":1,"265":1,"783":1,"998":1,"1004":1,"1059":1,"1192":2,"1229":1,"1259":1,"1270":1,"1271":1,"1285":3,"1295":1,"1299":5,"1300":3,"1310":3,"1322":3,"1323":2,"1338":3,"1349":1,"1358":10,"1368":1,"1394":1,"1395":3,"1396":1,"1401":1,"1409":1,"1414":1,"1416":4,"1430":2,"1435":4,"1437":3,"1462":1,"1474":2,"1488":1,"1495":2,"1637":1,"1830":1,"1873":1,"1882":1,"1907":1,"1917":1,"1921":1,"2023":1,"2044":1,"2048":1,"2092":1,"2108":1,"2149":1,"2153":1,"2157":1,"2186":1,"2199":1}}],["matchtimeout",{"3":{"1426":2}}],["matchtimeoutmilliseconds",{"3":{"1237":2,"1308":1}}],["matchlocaleasync",{"3":{"1416":2}}],["matchmedia",{"2":{"273":1},"3":{"273":1,"2082":1}}],["matchingmethods",{"3":{"1435":1}}],["matching",{"0":{"1909":1},"3":{"0":8,"31":1,"80":1,"97":1,"126":1,"136":1,"158":1,"166":1,"180":1,"226":1,"243":2,"265":2,"272":1,"295":1,"318":1,"351":1,"352":1,"353":1,"365":1,"403":1,"420":1,"448":1,"470":1,"473":1,"491":1,"492":1,"493":2,"494":2,"495":1,"528":1,"536":2,"540":1,"543":1,"551":2,"554":1,"556":1,"575":1,"599":1,"603":1,"607":1,"609":2,"612":1,"643":1,"657":2,"692":3,"697":1,"698":1,"716":1,"749":1,"760":1,"764":1,"790":1,"827":1,"838":1,"839":1,"840":1,"842":1,"847":1,"882":1,"925":1,"939":1,"946":1,"948":1,"964":1,"970":1,"972":1,"975":1,"1059":1,"1089":1,"1133":1,"1191":1,"1196":1,"1212":1,"1229":4,"1230":1,"1234":1,"1237":5,"1239":1,"1247":6,"1258":1,"1259":3,"1270":9,"1271":3,"1276":1,"1278":1,"1285":28,"1287":1,"1288":1,"1290":1,"1292":3,"1299":18,"1300":3,"1308":8,"1309":3,"1310":20,"1311":3,"1321":1,"1322":9,"1323":20,"1326":1,"1336":1,"1338":16,"1343":2,"1349":15,"1351":1,"1352":1,"1358":37,"1365":1,"1368":4,"1373":1,"1379":4,"1394":13,"1395":17,"1398":1,"1401":7,"1404":1,"1414":3,"1416":12,"1426":10,"1429":1,"1435":65,"1437":1,"1442":2,"1444":1,"1445":1,"1446":2,"1453":1,"1454":2,"1463":1,"1466":1,"1475":1,"1487":2,"1488":1,"1491":1,"1495":2,"1525":1,"1571":2,"1576":2,"1585":1,"1588":2,"1590":2,"1598":1,"1629":2,"1630":1,"1631":2,"1638":1,"1640":8,"1661":1,"1669":1,"1820":1,"1850":1,"1858":1,"1860":1,"1893":1,"1894":1,"1901":1,"1905":1,"1915":1,"1974":1,"1977":1,"1978":1,"1993":1,"2024":1,"2026":1,"2045":1,"2062":1,"2130":1,"2156":1,"2157":1,"2158":1,"2163":1,"2231":1}}],["matchers",{"3":{"1426":1}}],["matcher",{"3":{"1237":1,"1308":2,"1310":1,"1323":1,"1338":1,"1346":1,"1349":1,"1437":1}}],["matchestherouteattributereality",{"3":{"1435":1}}],["matchesthecurrentcontract",{"2":{"1426":1},"3":{"1426":1}}],["matchessignature",{"3":{"1285":1}}],["matchesdeployedregion",{"3":{"972":1,"1435":2,"2042":1}}],["matches",{"0":{"720":1},"3":{"0":5,"39":1,"40":1,"74":1,"91":1,"96":1,"109":1,"139":1,"142":1,"179":1,"217":1,"218":1,"258":1,"265":1,"268":1,"318":1,"324":1,"335":1,"341":1,"345":1,"352":1,"372":1,"398":1,"402":1,"415":1,"500":1,"545":1,"551":2,"564":1,"611":4,"650":1,"655":1,"666":1,"688":1,"734":1,"740":1,"749":1,"769":1,"790":1,"801":1,"827":1,"847":1,"857":1,"871":1,"881":1,"904":1,"946":1,"955":1,"972":1,"973":1,"974":1,"975":2,"1004":1,"1006":2,"1018":1,"1040":1,"1091":1,"1118":1,"1135":1,"1177":1,"1194":1,"1196":1,"1202":1,"1212":1,"1223":1,"1229":1,"1234":1,"1237":2,"1240":1,"1241":1,"1247":8,"1259":3,"1262":1,"1270":6,"1271":5,"1275":1,"1283":2,"1285":25,"1291":1,"1296":1,"1299":17,"1300":3,"1305":1,"1308":4,"1309":4,"1310":18,"1311":1,"1316":1,"1322":8,"1323":26,"1332":1,"1336":1,"1338":8,"1346":1,"1349":4,"1351":1,"1352":2,"1357":1,"1358":40,"1363":1,"1368":1,"1379":5,"1384":1,"1394":11,"1395":13,"1401":3,"1412":1,"1414":15,"1415":2,"1416":9,"1419":1,"1420":1,"1423":1,"1426":8,"1431":1,"1434":1,"1435":33,"1437":1,"1439":1,"1440":2,"1442":2,"1452":2,"1454":1,"1455":1,"1466":1,"1474":2,"1475":2,"1479":2,"1480":1,"1487":1,"1488":1,"1495":1,"1523":1,"1524":1,"1525":2,"1530":1,"1566":1,"1570":1,"1574":1,"1576":6,"1581":1,"1584":3,"1585":1,"1588":2,"1590":3,"1595":2,"1596":1,"1630":1,"1631":1,"1638":2,"1640":2,"1650":1,"1653":1,"1685":2,"1729":1,"1748":1,"1749":1,"1764":1,"1766":2,"1770":1,"1828":1,"1850":1,"1871":1,"1903":1,"1919":1,"1922":1,"1927":1,"1947":1,"1951":1,"1977":1,"1994":1,"1995":2,"1999":1,"2042":1,"2044":2,"2047":1,"2082":1,"2096":1,"2156":1,"2165":1,"2186":1,"2198":1,"2231":1}}],["matchedhash",{"3":{"1299":1}}],["matched",{"3":{"0":2,"136":1,"142":1,"160":1,"186":1,"249":1,"265":2,"310":1,"353":1,"372":1,"538":2,"557":1,"564":1,"585":1,"586":1,"592":1,"649":1,"827":1,"837":1,"840":2,"877":1,"905":1,"946":1,"999":1,"1029":1,"1045":1,"1067":1,"1089":1,"1091":2,"1117":1,"1135":1,"1161":2,"1163":1,"1196":1,"1231":1,"1237":1,"1247":2,"1270":2,"1285":8,"1299":7,"1309":1,"1310":8,"1311":1,"1314":1,"1322":2,"1323":2,"1337":1,"1338":8,"1349":1,"1354":1,"1358":5,"1394":3,"1395":2,"1401":1,"1415":1,"1416":3,"1418":1,"1420":1,"1423":1,"1426":4,"1435":33,"1446":1,"1454":1,"1457":1,"1491":1,"1525":1,"1567":1,"1576":1,"1630":5,"1635":1,"1638":2,"1639":1,"1640":2,"1652":1,"1683":1,"1699":1,"1901":1,"1950":1,"2043":1,"2138":1,"2148":1,"2156":1,"2167":1}}],["matchasync",{"2":{"1220":1},"3":{"0":1,"109":2,"1220":2,"1229":3,"1804":2}}],["match",{"0":{"1873":1},"1":{"1868":1,"1869":1,"1870":1,"1871":1,"1872":1,"1873":1,"1874":1,"1875":1,"1876":1},"2":{"1004":1,"1806":1},"3":{"0":8,"93":1,"98":1,"102":1,"109":2,"152":1,"162":1,"173":1,"268":1,"279":1,"300":1,"314":1,"339":1,"340":1,"341":3,"342":3,"343":2,"344":1,"348":1,"350":2,"353":2,"363":1,"402":1,"420":1,"489":1,"499":1,"507":1,"511":1,"540":1,"547":1,"564":1,"572":1,"607":1,"609":2,"610":1,"611":2,"635":1,"658":1,"744":1,"766":1,"785":1,"789":1,"809":1,"813":1,"829":1,"833":1,"879":1,"881":2,"885":1,"907":1,"909":1,"916":1,"922":2,"925":1,"944":1,"965":1,"972":4,"973":1,"974":2,"975":1,"1004":3,"1006":1,"1021":1,"1026":1,"1040":1,"1042":1,"1091":3,"1095":1,"1119":1,"1120":1,"1133":1,"1135":1,"1150":1,"1212":1,"1220":2,"1229":5,"1234":1,"1237":6,"1247":5,"1259":3,"1264":1,"1265":2,"1270":8,"1271":1,"1273":1,"1285":25,"1292":2,"1294":1,"1299":24,"1300":5,"1304":1,"1306":1,"1308":4,"1309":3,"1310":27,"1322":9,"1323":29,"1338":13,"1346":3,"1348":1,"1349":14,"1354":3,"1358":53,"1368":5,"1372":1,"1374":1,"1379":9,"1381":1,"1388":2,"1394":17,"1395":17,"1401":18,"1405":3,"1412":1,"1413":1,"1414":10,"1415":3,"1416":5,"1423":2,"1426":7,"1431":3,"1435":51,"1437":2,"1442":4,"1450":1,"1454":2,"1455":1,"1456":1,"1458":1,"1460":1,"1466":1,"1474":1,"1475":3,"1480":1,"1488":3,"1491":3,"1495":7,"1516":1,"1525":4,"1530":1,"1540":1,"1544":1,"1561":1,"1576":1,"1590":1,"1595":1,"1599":2,"1610":2,"1626":1,"1630":3,"1631":5,"1634":3,"1637":1,"1639":1,"1640":7,"1650":3,"1653":1,"1661":1,"1663":1,"1688":1,"1719":1,"1727":1,"1747":4,"1748":4,"1764":5,"1778":1,"1795":1,"1804":3,"1806":1,"1809":1,"1815":1,"1831":1,"1868":2,"1870":1,"1871":1,"1873":3,"1875":2,"1876":3,"1881":1,"1922":1,"1925":1,"1944":1,"1972":2,"1974":1,"1975":1,"1976":1,"1977":1,"1978":2,"1994":1,"1999":1,"2002":1,"2032":1,"2106":1,"2112":1,"2125":1,"2135":1,"2143":1,"2159":1,"2163":1,"2165":1,"2187":1,"2197":1,"2206":1,"2231":1}}],["maxhandlerconstructordependencies",{"2":{"1430":1},"3":{"1430":1,"1435":8}}],["maxopenquestionsperuserpersession",{"3":{"1401":4}}],["maxoptions",{"3":{"1397":1,"1401":8}}],["maxoutcomes",{"3":{"1395":2}}],["maxoutputtokens",{"2":{"1418":1,"1421":1,"2172":1,"2173":1},"3":{"1018":1,"1090":1,"1091":3,"1094":1,"1365":1,"1368":3,"1418":1,"1421":1,"1426":5,"2172":1,"2173":1}}],["maxsizebytes",{"2":{"1477":1},"3":{"1477":1}}],["maxserviceconstructorparameters",{"3":{"1435":2}}],["maxsponsors",{"3":{"1394":1}}],["maxspeakersessions",{"3":{"1394":4}}],["maxsuggestions",{"3":{"1394":1}}],["maxsavepasses",{"3":{"1278":1,"1285":3}}],["maxpolls",{"3":{"1394":5}}],["maxpages",{"3":{"1394":1}}],["maxpagesize=500",{"3":{"1576":1}}],["maxpagesize",{"2":{"331":1,"337":1,"741":1,"743":1,"1228":1,"1245":1,"1987":1,"1990":1,"2200":1},"3":{"0":1,"330":2,"331":1,"337":2,"674":1,"741":1,"743":1,"1228":2,"1229":3,"1242":1,"1245":1,"1247":3,"1259":1,"1285":1,"1308":4,"1310":8,"1319":1,"1322":4,"1379":2,"1395":4,"1414":2,"1747":2,"1987":3,"1990":1,"2200":1}}],["maxpairs",{"3":{"1358":2}}],["maxmanualidattempts",{"3":{"1358":3}}],["maxlineoctets",{"3":{"1299":2}}],["maxlockoutseconds",{"2":{"1291":1},"3":{"1291":2,"1299":2}}],["maxlookupselectorcacheentries",{"3":{"1285":2}}],["maxlengtherror",{"3":{"1395":2}}],["maxlengthattribute",{"3":{"1323":1,"1394":1}}],["maxlength",{"2":{"657":1,"1352":1,"1832":1},"3":{"657":2,"1237":6,"1271":10,"1285":5,"1308":5,"1323":8,"1349":10,"1352":1,"1358":24,"1394":15,"1395":9,"1630":1,"1726":1,"1809":2,"1828":1,"1832":1}}],["maxrosterpages",{"3":{"1395":2}}],["maxroster",{"3":{"1394":2}}],["maxroleslength",{"3":{"1285":4,"1317":1,"1322":2}}],["maxrenderedrows",{"3":{"1435":1}}],["maxrendereditems",{"2":{"2078":1},"3":{"1323":2,"2078":1}}],["maxreturnedownquestions",{"3":{"1401":1}}],["maxreturnedquestions",{"3":{"1401":2}}],["maxretryattempts",{"3":{"1338":4,"1442":1}}],["maxretrydelay",{"2":{"987":1},"3":{"987":1,"1285":2}}],["maxretrycount",{"2":{"987":1},"3":{"987":1,"1285":2}}],["maxretrybackoffseconds",{"2":{"2187":1},"3":{"0":1,"1042":2,"1285":1,"2187":2}}],["maxretries",{"2":{"19":1,"409":1,"2031":1},"3":{"19":3,"24":1,"27":1,"397":2,"409":1,"819":1,"849":1,"1256":1,"1259":10,"1285":1,"1323":1,"1338":1,"1358":1,"2031":1}}],["maxrecentcount",{"3":{"1395":3}}],["maxrequestsperemail",{"2":{"1640":1},"3":{"1299":5,"1640":2,"1766":1}}],["maxrequestspertcpconnection",{"3":{"1285":1}}],["maxrequestbytes",{"2":{"1118":1,"1372":1},"3":{"1118":1,"1349":2,"1372":1,"1379":1}}],["maxregistrationsperipperhour",{"2":{"281":1,"1325":1,"1338":1,"1443":1},"3":{"1299":1,"1325":1,"1338":1,"1443":1,"1495":1}}],["maxreplicas",{"2":{"538":1,"664":1,"764":1,"765":1,"766":2,"767":1,"768":1,"995":1,"1154":1,"1156":1,"1468":1,"2167":1},"3":{"0":1,"390":2,"534":4,"538":1,"540":1,"664":1,"665":1,"764":1,"765":1,"766":2,"767":1,"768":1,"995":1,"1154":1,"1156":1,"1466":6,"1468":1,"1525":4,"1534":1,"2167":1}}],["maxtagsperexpression",{"3":{"1322":2}}],["maxtcpconnectionsperendpoint",{"3":{"1285":1}}],["maxtenantidlength",{"3":{"1285":2}}],["maxkeylength",{"3":{"1285":1}}],["maxerrorlength",{"3":{"1285":1,"1322":1}}],["maxentitytypelength",{"3":{"1285":1}}],["maxexportrows",{"2":{"743":1,"1987":1,"1990":1},"3":{"0":1,"674":1,"741":1,"743":1,"1212":1,"1229":1,"1310":4,"1319":1,"1322":4,"1987":2,"1990":1}}],["maxintervalseconds",{"3":{"1706":1}}],["maxinlinecodelines",{"3":{"1435":4,"1525":1}}],["maxinspectedentries",{"3":{"1285":1}}],["maximumlength",{"2":{"1827":1,"1832":1},"3":{"1271":15,"1299":3,"1308":3,"1358":38,"1401":4,"1827":1,"1828":1,"1832":1}}],["maximumlengthvalidator",{"3":{"1271":2,"1394":1}}],["maximum",{"3":{"71":1,"529":1,"819":1,"862":1,"1271":1,"1275":1,"1285":1,"1299":1,"1338":1,"1358":11,"1394":2,"1395":1,"1401":1,"1414":2,"1435":1,"1456":1,"1629":1,"1630":2,"1639":3,"1640":1,"1766":1,"1839":1,"1907":1,"2031":1}}],["maximal",{"3":{"216":1,"1323":1,"2151":1}}],["maxima",{"3":{"0":1,"861":2,"862":1,"1435":1,"1595":1}}],["maxfilemegabytes",{"3":{"1394":3}}],["maxfilebytes",{"3":{"1349":8,"1358":2,"1379":1,"1394":5}}],["maxfilters",{"3":{"1241":1,"1247":2,"1310":4}}],["maxfailedattempts",{"2":{"286":1,"1291":1,"2000":1},"3":{"175":1,"281":1,"286":1,"1291":1,"1299":4,"1999":1,"2000":1}}],["maxdirectfiles",{"2":{"1004":1},"3":{"1004":2,"1430":1,"1435":3,"2042":1}}],["maxdecodeddimension",{"2":{"442":1},"3":{"0":1,"442":1,"1322":3,"2125":1}}],["maxdecodedpixels",{"2":{"442":1},"3":{"0":1,"442":1,"1322":3,"2125":1}}],["maxvalidationattempts",{"2":{"1640":1},"3":{"854":1,"1293":1,"1299":5,"1640":2,"1766":1}}],["maxvalue",{"2":{"335":1,"1418":1},"3":{"335":1,"827":1,"1124":1,"1242":1,"1247":2,"1285":1,"1308":4,"1310":1,"1322":3,"1323":4,"1338":3,"1349":2,"1394":3,"1395":3,"1414":4,"1418":1,"1426":1,"1435":5,"1464":1,"1999":1}}],["maxavatarbytes",{"3":{"1414":7}}],["maxavatarrequestbytes",{"2":{"1410":1},"3":{"1410":1,"1414":4}}],["maxactivities",{"3":{"1394":1}}],["maxactivecircuits",{"2":{"830":1,"831":1,"1128":1},"3":{"830":1,"831":1,"832":1,"1124":3,"1128":1,"1212":1,"1323":4,"1415":1,"1999":1}}],["maxactivesessionsperuser",{"2":{"909":1,"1289":1},"3":{"0":1,"905":2,"909":1,"1289":1,"1299":4,"1981":2}}],["maxassetspersession",{"2":{"1356":1,"1388":1},"3":{"1349":2,"1356":1,"1358":2,"1379":1,"1388":1,"1394":1}}],["maxage",{"3":{"1299":2,"1323":3}}],["maxattempts",{"3":{"444":1,"1042":1,"1285":2,"1322":1,"2187":1}}],["maxcalldepth",{"3":{"1435":4}}],["maxcacheentries",{"2":{"337":1},"3":{"330":1,"333":1,"337":1,"1242":1,"1247":2}}],["maxcodebehindlines",{"2":{"1530":1},"3":{"1435":4,"1525":1,"1530":1}}],["maxcodelength",{"3":{"1299":3}}],["maxconstructordependencies",{"3":{"1430":1,"1435":6}}],["maxconsecutivefailures",{"3":{"1394":1}}],["maxcontrollerconstructordependencies",{"2":{"1430":1},"3":{"1430":1,"1435":6}}],["maxconnectionsperuser",{"2":{"1306":1,"1308":1,"1944":1},"3":{"0":1,"228":1,"384":1,"1306":1,"1308":3,"1322":3,"1944":1}}],["maxcorrelationidlength",{"3":{"1285":2}}],["maxcount",{"3":{"1237":1}}],["max",{"2":{"766":1,"768":1,"1456":1,"1528":1},"3":{"214":1,"217":1,"572":1,"626":1,"690":1,"698":1,"766":1,"768":2,"905":1,"1237":4,"1244":1,"1247":4,"1271":5,"1285":2,"1297":1,"1299":1,"1302":2,"1308":2,"1310":1,"1323":7,"1338":2,"1343":1,"1349":11,"1352":2,"1358":32,"1368":8,"1394":2,"1395":3,"1401":1,"1404":1,"1414":5,"1435":3,"1455":1,"1456":1,"1466":2,"1487":1,"1525":1,"1576":1,"1582":1,"1629":1,"1630":4,"1639":1,"1640":2,"1682":1,"1712":1,"1726":2,"1747":3,"1763":14,"1766":22,"1828":2,"1831":1,"1832":1,"1910":1,"1933":1,"1985":1,"2054":1,"2148":2}}],["maxby",{"3":{"109":1,"1222":1,"1229":1}}],["maxnetworkretries",{"2":{"73":2},"3":{"73":2}}],["maxnavigationdepth",{"2":{"333":1,"1242":1},"3":{"0":1,"333":1,"1241":1,"1242":1,"1247":6}}],["maxunboundedresultlimit",{"2":{"330":2,"331":1,"332":1,"333":1,"335":1,"552":1,"740":1,"741":1,"745":1,"1243":1,"1244":1,"1278":1,"1814":1,"1987":2,"1989":1,"1990":1},"3":{"0":1,"330":3,"331":1,"332":1,"333":1,"335":1,"337":1,"552":1,"740":1,"741":1,"745":1,"1229":2,"1243":1,"1244":2,"1247":6,"1278":1,"1285":3,"1309":1,"1576":1,"1814":1,"1987":2,"1989":1,"1990":1}}],["mayor",{"3":{"1684":1,"1685":2}}],["maybenullwhen",{"3":{"1270":1}}],["maybe",{"3":{"1237":1,"1285":1,"1807":1,"1817":1,"1972":1,"2002":1}}],["may",{"0":{"1422":2},"1":{"1991":1,"1992":1,"1993":1,"1994":1,"1995":1,"1996":1},"3":{"0":7,"18":1,"24":1,"27":1,"38":1,"71":1,"76":1,"77":1,"80":1,"81":3,"122":1,"126":1,"185":1,"186":1,"187":1,"188":1,"219":1,"225":1,"235":2,"243":2,"255":1,"266":1,"267":1,"317":3,"318":1,"325":1,"335":1,"342":1,"353":1,"384":1,"390":1,"433":1,"441":1,"530":1,"536":1,"556":2,"563":1,"564":1,"568":1,"620":1,"626":1,"628":1,"658":4,"674":1,"683":1,"727":1,"782":1,"793":1,"819":1,"863":1,"881":1,"891":1,"896":1,"916":1,"924":1,"989":2,"1014":1,"1020":1,"1042":1,"1059":1,"1075":1,"1090":1,"1091":1,"1102":1,"1107":1,"1112":1,"1116":1,"1168":1,"1187":1,"1193":1,"1211":1,"1220":1,"1228":1,"1229":2,"1230":1,"1237":5,"1241":1,"1242":1,"1244":1,"1247":14,"1254":1,"1255":1,"1256":2,"1259":7,"1263":3,"1264":2,"1265":1,"1269":4,"1270":9,"1273":1,"1275":1,"1278":1,"1279":1,"1283":1,"1284":1,"1285":28,"1286":1,"1289":1,"1295":1,"1296":1,"1299":16,"1300":1,"1304":1,"1306":1,"1308":11,"1310":13,"1319":2,"1322":16,"1323":21,"1329":1,"1338":10,"1341":1,"1347":3,"1349":14,"1352":1,"1356":3,"1358":61,"1368":1,"1372":2,"1374":1,"1379":5,"1388":1,"1394":5,"1395":16,"1398":1,"1401":8,"1402":1,"1404":1,"1408":1,"1414":18,"1416":6,"1417":3,"1426":4,"1430":1,"1435":22,"1437":4,"1454":1,"1455":1,"1466":3,"1487":1,"1488":2,"1491":1,"1552":1,"1584":3,"1595":1,"1625":1,"1629":4,"1630":7,"1631":1,"1634":2,"1638":1,"1639":1,"1640":1,"1707":1,"1754":2,"1763":1,"1764":1,"1766":1,"1775":5,"1791":1,"1804":1,"1815":1,"1836":1,"1839":4,"1909":1,"1922":1,"1959":2,"1963":1,"1972":1,"1990":2,"1991":5,"1993":1,"1994":1,"1996":5,"2031":1,"2076":1,"2097":1,"2106":1,"2126":1,"2141":1,"2166":1,"2169":1,"2170":1,"2184":1,"2185":1,"2187":1,"2197":1,"2198":1,"2207":1,"2219":3,"2231":1,"2239":1}}],["mangled",{"3":{"1379":1}}],["mangling",{"3":{"743":1}}],["manipulation",{"3":{"1437":1}}],["manipulations",{"3":{"1349":1}}],["manipulate",{"3":{"1370":1}}],["manifestation",{"3":{"1325":1,"1338":1}}],["manifests",{"2":{"1460":1},"3":{"422":1,"1415":1,"1435":1,"1454":3,"1460":1,"1464":3}}],["manifest",{"0":{"1199":1},"1":{"1196":1,"1197":1,"1198":1,"1199":1},"3":{"418":2,"423":1,"426":1,"1191":2,"1192":1,"1196":1,"1200":1,"1201":1,"1299":1,"1308":2,"1323":4,"1368":3,"1415":7,"1416":7,"1430":1,"1433":1,"1435":6,"1444":1,"1454":2,"1463":2,"1464":1,"1475":1,"1478":1,"1480":1,"1495":2,"1497":1,"1499":1,"1500":1,"1501":2,"1516":1,"1598":1,"2045":1}}],["manufacturing",{"3":{"2135":1}}],["manufacture",{"3":{"1435":2,"2138":1}}],["manufacturer",{"3":{"1299":1,"1629":1,"1763":1}}],["manufactures",{"3":{"1229":1,"1268":1}}],["manufactured",{"3":{"295":1,"1271":1}}],["manualidrangeexhausted",{"3":{"1358":2}}],["manualidrangeend",{"3":{"1349":2,"1358":2,"1394":1}}],["manualidrangestart",{"2":{"1363":1},"3":{"1349":3,"1351":1,"1358":2,"1363":2,"1368":2,"1394":1}}],["manualinstructions",{"2":{"633":1},"3":{"633":1}}],["manualcheckinasync",{"3":{"1395":4}}],["manualcheckin",{"3":{"1203":2,"1207":4,"1271":1,"1395":6}}],["manualcheckinhandlertests",{"3":{"1199":1,"1207":3}}],["manualcheckinhandler",{"3":{"1199":2,"1203":1,"1207":2,"1271":2,"1349":2,"1395":13}}],["manualcheckinrequestvalidatortests",{"3":{"1199":1,"1207":2}}],["manualcheckinrequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1395":6}}],["manualcheckinrequest",{"3":{"1199":10,"1203":1,"1207":2,"1395":10}}],["manualclock",{"3":{"1199":2,"1207":1}}],["manuals",{"3":{"1117":1}}],["manually",{"3":{"435":1,"767":1,"1247":2,"1308":1,"1349":3,"1354":1,"1395":2,"1416":1,"1456":1,"1466":1,"1626":1,"1629":3,"1630":1,"1632":1,"1636":1,"1637":1,"1638":4,"1640":2,"1643":1,"1764":1,"1767":1}}],["manual",{"0":{"1352":1,"1643":1},"1":{"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"1604":1,"1605":1,"1606":1,"1607":1,"1608":1,"1609":1,"1738":1,"1739":1,"1740":1,"1741":1,"1742":1,"1743":1},"3":{"0":2,"2":1,"4":1,"10":1,"13":1,"19":1,"20":1,"41":1,"293":1,"330":1,"331":1,"334":1,"435":1,"444":2,"512":1,"528":2,"530":1,"536":1,"549":1,"577":1,"620":1,"622":1,"626":1,"635":2,"659":1,"684":1,"685":2,"690":2,"757":1,"759":3,"765":1,"791":1,"793":1,"799":1,"889":1,"890":1,"966":1,"990":1,"1110":1,"1118":1,"1168":1,"1192":1,"1202":1,"1203":1,"1212":3,"1213":1,"1238":1,"1243":1,"1247":3,"1259":2,"1265":1,"1270":1,"1273":1,"1285":12,"1299":5,"1308":1,"1309":7,"1310":5,"1322":1,"1323":7,"1343":1,"1346":2,"1348":1,"1349":10,"1351":1,"1358":18,"1368":1,"1379":4,"1394":2,"1395":27,"1401":1,"1414":2,"1416":1,"1426":1,"1431":1,"1435":4,"1437":4,"1453":1,"1454":2,"1455":2,"1456":1,"1466":2,"1469":1,"1472":1,"1473":1,"1474":2,"1475":1,"1479":1,"1480":3,"1482":1,"1486":1,"1489":1,"1491":1,"1495":1,"1522":1,"1523":2,"1524":1,"1525":5,"1529":1,"1530":3,"1531":1,"1532":1,"1545":1,"1553":1,"1557":1,"1563":1,"1564":1,"1569":2,"1570":1,"1576":4,"1581":1,"1588":1,"1590":2,"1594":1,"1595":2,"1597":1,"1602":1,"1603":1,"1604":4,"1609":2,"1610":1,"1626":1,"1629":1,"1630":3,"1631":1,"1635":2,"1638":6,"1640":10,"1641":1,"1644":1,"1650":1,"1652":1,"1661":1,"1673":1,"1683":1,"1700":1,"1709":1,"1727":1,"1728":1,"1738":4,"1743":2,"1745":1,"1748":3,"1753":2,"1764":3,"1765":1,"1766":2,"1770":1,"1778":1,"1864":1,"1866":1,"1987":1,"1990":1,"2033":1,"2035":1,"2146":1,"2162":1,"2231":2}}],["managing",{"3":{"188":1,"1379":1,"1394":2,"1414":1,"1623":1,"1628":1,"1636":1}}],["managepollrows",{"3":{"1401":1}}],["managepolls",{"3":{"1401":4}}],["managepackageversionscentrally",{"2":{"1214":1},"3":{"1214":1}}],["manageable",{"3":{"1285":1,"1637":1}}],["managers",{"3":{"1394":1,"1576":1}}],["manager",{"1":{"807":1,"808":1,"809":1,"810":1,"811":1,"812":1,"813":1,"814":1,"815":1},"3":{"0":1,"807":1,"810":2,"812":1,"815":1,"1212":1,"1299":4,"1306":1,"1308":2,"1310":1,"1323":1,"1394":1,"1395":1,"1415":1,"1435":1,"1538":1,"1546":1,"1576":1,"1767":1,"1934":1,"2231":1}}],["manageroles",{"2":{"184":1,"1059":1,"2199":1,"2200":1},"3":{"0":1,"184":1,"186":1,"1059":2,"1299":14,"1310":2,"1323":5,"1414":4,"2199":1,"2200":1}}],["managed",{"0":{"1476":1},"1":{"437":1,"438":1,"439":1,"440":1,"441":1,"442":1,"443":1,"444":1,"596":1,"597":1,"598":1,"599":1,"600":1,"601":1,"602":1,"603":1,"604":1,"605":1,"2123":1,"2124":1,"2125":1,"2126":1,"2127":1},"2":{"599":1,"1454":1,"1465":1,"1469":1,"1476":2,"1483":1},"3":{"0":7,"234":1,"245":1,"259":1,"341":2,"342":1,"343":1,"437":1,"440":1,"443":2,"591":1,"596":1,"598":1,"599":4,"601":1,"639":1,"640":1,"641":1,"664":1,"665":1,"666":1,"670":1,"681":1,"682":1,"683":1,"684":1,"860":1,"1022":1,"1035":1,"1038":1,"1114":1,"1115":1,"1150":1,"1192":1,"1212":4,"1213":1,"1237":1,"1273":1,"1285":5,"1301":1,"1322":2,"1331":1,"1334":1,"1338":7,"1349":3,"1379":1,"1394":1,"1395":1,"1414":3,"1415":7,"1416":7,"1435":1,"1437":1,"1440":1,"1441":2,"1442":1,"1444":1,"1445":1,"1454":6,"1458":1,"1462":1,"1465":3,"1466":12,"1469":2,"1470":2,"1471":3,"1473":1,"1475":1,"1476":19,"1477":1,"1481":1,"1482":2,"1483":2,"1495":2,"1523":2,"1525":5,"1533":2,"1534":1,"1547":1,"1553":1,"1574":1,"1576":2,"1587":1,"1588":1,"1590":8,"1595":2,"1596":1,"1599":1,"1628":3,"1629":2,"1630":2,"1631":1,"1634":1,"1635":1,"1636":1,"1637":4,"1667":1,"1669":2,"1747":1,"1764":1,"1778":2,"1860":2,"1867":1,"1871":1,"1875":1,"1876":4,"1903":1,"2046":1,"2122":1,"2123":1,"2125":1,"2127":2,"2132":1,"2141":1,"2145":1,"2209":2,"2212":1,"2218":1,"2220":1,"2231":2}}],["manages",{"3":{"0":1,"1074":1,"1306":1,"1308":1,"1323":1,"1372":1,"1395":1,"1401":2,"1471":1,"1636":1,"1749":1,"1764":1,"1772":1,"1870":1,"1935":1}}],["managementrouteauthorizationtests",{"2":{"1534":1},"3":{"1199":1,"1207":2,"1435":1,"1525":2,"1534":2}}],["management",{"0":{"1555":1,"1568":1},"1":{"596":1,"597":1,"598":1,"599":1,"600":1,"601":1,"602":1,"603":1,"604":1,"605":1},"3":{"0":2,"32":1,"39":1,"186":1,"225":1,"301":1,"324":1,"359":1,"361":1,"369":1,"379":1,"381":1,"596":1,"640":2,"641":1,"881":1,"938":1,"954":1,"1026":1,"1192":1,"1202":1,"1203":1,"1212":1,"1213":1,"1214":1,"1216":2,"1259":2,"1299":11,"1307":1,"1308":1,"1310":7,"1322":9,"1323":32,"1325":1,"1326":2,"1338":5,"1349":5,"1358":2,"1379":4,"1382":1,"1388":1,"1389":1,"1394":46,"1395":14,"1401":8,"1413":1,"1414":6,"1416":10,"1428":1,"1430":1,"1435":12,"1436":1,"1437":9,"1439":1,"1440":2,"1443":3,"1445":1,"1452":1,"1454":1,"1466":2,"1486":2,"1495":1,"1498":1,"1524":2,"1525":2,"1533":1,"1534":2,"1551":1,"1568":1,"1576":2,"1585":1,"1587":1,"1590":1,"1596":1,"1599":1,"1610":2,"1620":1,"1624":2,"1625":1,"1626":7,"1628":6,"1629":1,"1631":5,"1635":1,"1636":2,"1637":4,"1638":2,"1640":2,"1643":1,"1648":1,"1665":1,"1666":2,"1670":1,"1684":1,"1685":1,"1746":1,"1747":4,"1748":1,"1749":2,"1762":3,"1766":1,"1770":3,"1772":1,"1795":2,"1801":1,"1941":1,"1962":1,"1994":1,"2004":2,"2054":1,"2062":1,"2134":1,"2144":1,"2219":1,"2231":2}}],["manageusers",{"2":{"184":1,"1059":1,"2200":1},"3":{"0":1,"184":1,"186":1,"1059":1,"1299":6,"1310":1,"1414":3,"2200":1}}],["manage",{"2":{"186":1,"638":1,"642":1,"1303":1},"3":{"0":6,"152":1,"185":1,"186":6,"187":1,"209":1,"378":1,"384":2,"638":1,"640":3,"642":2,"690":1,"694":1,"1026":1,"1078":1,"1116":3,"1120":1,"1296":4,"1299":6,"1302":1,"1303":3,"1308":13,"1323":5,"1346":1,"1347":7,"1349":11,"1356":2,"1358":1,"1372":1,"1376":1,"1379":5,"1388":1,"1391":1,"1394":1,"1395":16,"1401":29,"1408":3,"1413":2,"1414":5,"1416":1,"1437":1,"1454":1,"1466":5,"1495":1,"1549":1,"1588":1,"1590":1,"1595":1,"1596":1,"1620":1,"1625":2,"1626":5,"1628":1,"1630":5,"1631":5,"1637":1,"1639":2,"1650":1,"1652":2,"1686":1,"1747":3,"1748":3,"1759":1,"1762":2,"1766":1,"1767":8,"1770":1,"1959":1,"1960":1,"1961":1,"1962":4,"1964":1,"1970":1,"2071":1}}],["many",{"3":{"71":1,"135":1,"175":2,"186":1,"227":1,"258":1,"282":1,"310":1,"317":1,"319":1,"329":1,"333":1,"361":1,"472":1,"598":1,"600":1,"697":1,"740":1,"760":1,"819":1,"827":1,"829":1,"853":1,"881":2,"946":1,"1110":1,"1124":1,"1126":1,"1150":1,"1153":1,"1200":1,"1229":1,"1237":2,"1247":5,"1259":2,"1270":1,"1271":1,"1276":1,"1279":1,"1285":8,"1299":10,"1300":3,"1303":1,"1304":1,"1308":4,"1309":12,"1310":5,"1311":2,"1322":5,"1323":13,"1338":5,"1349":29,"1353":1,"1358":17,"1379":3,"1386":1,"1394":15,"1395":25,"1401":5,"1405":1,"1409":1,"1414":4,"1416":3,"1426":1,"1427":1,"1430":2,"1435":7,"1437":2,"1455":1,"1458":1,"1464":1,"1472":1,"1475":1,"1495":1,"1498":1,"1499":1,"1540":1,"1550":1,"1554":1,"1564":1,"1570":1,"1576":3,"1629":23,"1637":3,"1639":2,"1640":1,"1748":1,"1779":1,"1789":1,"1807":1,"1814":1,"1846":1,"1852":1,"1893":1,"1900":1,"1901":1,"1981":1,"1983":1,"1993":1,"1996":1,"1999":1,"2000":2,"2023":1,"2031":1,"2036":1,"2050":1,"2134":1,"2144":1,"2166":1,"2225":1}}],["mandating",{"3":{"1435":1}}],["mandated",{"3":{"1473":1}}],["mandates",{"3":{"120":1,"988":1,"1237":1,"1322":1,"1379":2,"1839":1}}],["mandate",{"3":{"0":1,"68":1}}],["mandatory",{"3":{"0":1,"674":1,"707":1,"1058":1,"1060":2,"1270":3,"1285":1,"1292":1,"1299":2,"1310":3,"1322":1,"1323":1,"1349":1,"1358":9,"1379":1,"1394":1,"1395":4,"1401":3,"1414":1,"1435":4,"1454":1,"1460":1,"1490":1,"1525":1,"1599":1,"1634":1,"1653":1,"1747":1,"1748":4,"1764":5,"1801":1,"1875":1,"2194":1,"2202":1}}],["major≥9",{"3":{"1575":1,"1576":1}}],["majorversion3",{"2":{"1488":1,"1490":1},"3":{"1435":2,"1488":1,"1490":1}}],["majorversion8",{"2":{"1074":1,"1488":1},"3":{"1074":1,"1435":2,"1488":1}}],["majority",{"3":{"1229":1,"1285":1,"1379":1,"1414":1,"1442":1,"1895":1,"1897":1}}],["majors",{"3":{"145":1,"413":2,"1430":1}}],["major",{"2":{"1732":1},"3":{"0":4,"147":6,"149":1,"150":2,"152":2,"1036":1,"1074":1,"1075":2,"1212":1,"1213":1,"1322":1,"1430":2,"1435":3,"1453":1,"1488":1,"1525":1,"1536":1,"1551":1,"1568":1,"1575":1,"1576":2,"1585":1,"1732":2,"1734":2,"1762":1,"2041":1,"2219":1}}],["maui=",{"3":{"1415":1}}],["mauiuiapplicationdelegate",{"3":{"1415":1}}],["mauiuimodule",{"2":{"651":1},"3":{"649":2,"651":2,"1323":2}}],["mauiwinuiapplication",{"3":{"1415":5}}],["mauimediapickerservice",{"3":{"1199":1,"1203":1,"1207":2,"1416":12}}],["mauimapnavigationservice",{"3":{"1199":1,"1203":1,"1207":2,"1416":9}}],["mauilocalnotificationservice",{"3":{"1199":1,"1203":1,"1207":2,"1416":17}}],["mauilocalcachestore",{"3":{"1199":1,"1203":1,"1207":2,"1416":14}}],["mauigeolocationservice",{"2":{"2118":1},"3":{"1199":1,"1203":1,"1207":2,"1416":11,"2118":1}}],["mauigeocodingservice",{"3":{"1199":1,"1203":1,"1207":2,"1416":10}}],["mauitexttospeechservice",{"3":{"1199":1,"1203":1,"1207":2,"1416":13}}],["mauitokenstorageservice",{"2":{"507":1,"510":1,"513":1},"3":{"0":1,"507":5,"510":1,"513":8,"1199":1,"1203":1,"1207":2,"1323":3,"1415":4,"1416":10,"1495":1}}],["mauispeechtotextservice",{"3":{"1199":1,"1203":1,"1207":2,"1416":10}}],["mauishareservice",{"2":{"2118":1},"3":{"1199":1,"1203":1,"1207":2,"1416":8,"2118":2}}],["mauiscreenshotservice",{"3":{"1199":1,"1203":1,"1207":2,"1416":7}}],["mauisecuretokenstore",{"2":{"510":1,"511":1,"513":1},"3":{"507":5,"509":1,"510":1,"511":7,"512":1,"513":2,"1199":1,"1203":1,"1207":2,"1323":1,"1416":11}}],["mauihapticfeedbackservice",{"2":{"2118":1},"3":{"1199":1,"1203":1,"1207":2,"1416":9,"2118":1}}],["mauiformfactor",{"3":{"1199":1,"1203":1,"1207":2,"1416":17}}],["mauifirebasemessagingservice",{"2":{"434":1,"436":1},"3":{"434":2,"436":2}}],["mauidevicepreferences",{"3":{"1199":1,"1203":1,"1207":2,"1416":16}}],["mauicommunitytoolkit",{"3":{"1416":1}}],["mauicontext",{"3":{"1415":3}}],["mauiconnectivitystatusservice",{"3":{"1199":1,"1203":1,"1207":2,"1416":11}}],["mauiclipboardservice",{"2":{"2118":1},"3":{"1199":1,"1203":1,"1207":2,"1416":7,"2118":1}}],["mauiculturestore",{"3":{"265":1,"268":1,"415":1,"1199":3,"1203":1,"1207":2,"1310":2,"1416":14}}],["mauicultureinitializer",{"3":{"265":1,"1199":2,"1203":1,"1207":2,"1323":1,"1415":1,"1416":12}}],["mauicultureapplier",{"3":{"265":1,"415":1,"1199":2,"1203":1,"1207":2,"1310":2,"1323":3,"1416":13}}],["mauibiometricauthenticator",{"3":{"1199":1,"1203":1,"1207":2,"1416":9}}],["mauibatterystatusservice",{"3":{"1199":1,"1203":1,"1207":2,"1416":11}}],["mauibarcodescannerservice",{"2":{"680":1,"682":1},"3":{"680":1,"682":5,"684":1,"1199":2,"1203":1,"1207":2,"1416":30}}],["mauibacknavigationbridge",{"2":{"1416":1},"3":{"413":1,"1199":2,"1203":1,"1207":3,"1323":14,"1415":1,"1416":4}}],["mauiapp",{"3":{"1415":3}}],["mauiapplication",{"3":{"1415":2}}],["mauiappcompatactivity",{"3":{"1415":1}}],["mauiappbuilder",{"3":{"265":1,"682":1,"980":1,"1208":1,"1416":9}}],["mauiaccessibilityannouncer",{"3":{"1199":1,"1203":1,"1207":2,"1416":9}}],["mauipubliclinkbuilder",{"3":{"1199":1,"1203":1,"1207":2,"1323":4,"1416":8,"1495":1}}],["mauipushregistrationservice",{"2":{"434":1,"436":1},"3":{"434":2,"436":2,"1199":1,"1203":1,"1207":2,"1416":18}}],["mauiprogram",{"2":{"683":1},"3":{"412":1,"413":1,"507":2,"511":4,"513":2,"649":2,"683":1,"1199":4,"1203":1,"1207":2,"1212":2,"1323":10,"1394":2,"1414":1,"1415":77,"1416":20,"1495":2}}],["mauiexternallinkservice",{"3":{"1199":1,"1203":1,"1207":2,"1416":8}}],["mauiexternalauthbroker",{"2":{"415":1,"418":1,"1975":1,"2121":1},"3":{"0":1,"415":2,"418":2,"420":1,"423":1,"428":1,"1199":1,"1203":1,"1207":2,"1323":1,"1416":13,"1495":1,"1975":1,"2121":1}}],["mauierrorhandlinginitializer",{"2":{"413":1},"3":{"413":1,"1199":2,"1203":1,"1207":2,"1416":29}}],["maui",{"0":{"1458":1},"1":{"410":1,"411":1,"412":1,"413":1,"414":1,"415":1,"416":1,"1416":1},"2":{"130":1,"138":1,"141":1,"142":1,"436":1,"481":1,"482":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"495":1,"509":1,"511":1,"528":1,"682":1,"686":1,"939":1,"942":1,"982":1,"984":1,"1415":2,"1416":4,"1453":1,"1502":1,"1503":1,"1584":1,"1598":1,"1659":1,"1674":1,"1731":1,"1780":1,"2099":1,"2119":2,"2122":1},"3":{"0":19,"59":1,"60":1,"130":2,"135":4,"138":1,"141":1,"142":1,"243":1,"265":4,"370":1,"373":2,"389":1,"410":1,"412":5,"413":15,"414":2,"415":6,"418":3,"419":2,"420":2,"422":1,"423":2,"426":1,"428":2,"429":1,"430":1,"432":1,"434":2,"436":2,"439":1,"481":2,"482":3,"483":1,"485":1,"486":4,"487":1,"488":1,"489":1,"490":3,"491":1,"492":1,"493":2,"494":1,"495":2,"505":1,"506":3,"507":11,"508":2,"509":10,"510":2,"511":12,"512":1,"513":6,"514":2,"528":4,"530":1,"531":2,"532":1,"555":1,"560":1,"649":1,"650":1,"653":1,"681":1,"682":5,"683":2,"684":2,"685":1,"686":1,"773":1,"854":1,"880":1,"939":2,"942":2,"980":8,"982":4,"983":2,"984":2,"1004":1,"1184":1,"1185":1,"1187":2,"1192":3,"1199":35,"1202":2,"1203":37,"1206":1,"1207":105,"1208":6,"1211":2,"1212":12,"1288":1,"1293":1,"1297":1,"1299":1,"1310":5,"1322":2,"1323":64,"1327":1,"1338":1,"1358":1,"1380":1,"1383":1,"1387":1,"1390":1,"1392":1,"1394":13,"1395":1,"1401":1,"1402":1,"1404":1,"1413":1,"1414":14,"1415":109,"1416":426,"1426":1,"1435":3,"1437":1,"1440":1,"1446":1,"1448":1,"1451":1,"1452":1,"1453":12,"1454":2,"1455":2,"1458":23,"1459":2,"1460":4,"1485":3,"1487":1,"1488":3,"1495":14,"1502":2,"1503":2,"1512":2,"1523":4,"1525":9,"1529":1,"1530":8,"1531":2,"1576":1,"1581":1,"1582":1,"1584":3,"1590":11,"1594":1,"1595":5,"1596":2,"1598":4,"1599":2,"1625":2,"1626":2,"1628":1,"1629":7,"1638":1,"1640":25,"1659":2,"1668":6,"1672":1,"1674":2,"1731":2,"1772":1,"1780":3,"1975":2,"2076":1,"2077":2,"2079":1,"2099":2,"2110":1,"2114":1,"2115":4,"2117":1,"2118":4,"2119":7,"2121":6,"2122":4,"2218":1,"2220":1,"2224":1,"2226":2,"2227":1,"2229":1,"2231":4}}],["made",{"3":{"0":7,"24":2,"26":1,"27":1,"110":1,"125":1,"135":1,"136":2,"149":1,"151":1,"157":1,"168":1,"170":1,"173":1,"201":1,"243":1,"255":1,"265":1,"303":1,"365":1,"372":1,"448":1,"454":1,"526":1,"537":1,"539":1,"550":1,"603":1,"609":1,"626":1,"706":1,"716":2,"724":1,"726":1,"734":1,"749":1,"799":1,"812":1,"819":1,"826":2,"827":3,"829":1,"832":1,"834":1,"837":2,"840":1,"860":1,"868":1,"896":1,"906":1,"923":1,"938":1,"954":1,"964":2,"965":2,"971":1,"1033":1,"1036":1,"1043":1,"1045":1,"1051":2,"1055":1,"1061":2,"1074":1,"1082":1,"1090":1,"1092":1,"1101":1,"1212":1,"1229":2,"1237":1,"1239":1,"1241":1,"1247":1,"1252":1,"1253":1,"1255":1,"1259":8,"1270":3,"1271":3,"1273":1,"1275":1,"1285":8,"1299":11,"1300":3,"1308":2,"1309":1,"1310":9,"1311":1,"1312":1,"1317":1,"1319":1,"1322":8,"1323":13,"1330":1,"1331":1,"1338":5,"1349":1,"1351":1,"1352":2,"1358":17,"1368":2,"1379":2,"1387":1,"1394":11,"1395":7,"1401":3,"1414":5,"1416":1,"1421":1,"1425":1,"1426":2,"1427":1,"1431":1,"1435":13,"1442":1,"1446":1,"1454":6,"1456":1,"1460":1,"1466":2,"1472":1,"1477":1,"1488":2,"1495":3,"1630":1,"1640":2,"1685":1,"1699":1,"1726":2,"1734":1,"1821":1,"1822":1,"1824":1,"1834":1,"1853":1,"1862":1,"1907":1,"1926":1,"1940":1,"1962":1,"2028":1,"2044":1,"2047":1,"2157":1,"2173":1,"2175":1,"2178":1,"2197":1,"2231":1}}],["mapget",{"3":{"1323":1}}],["mapgrpcservice",{"2":{"103":1,"179":1},"3":{"103":1,"179":1,"1310":2,"1311":1,"1379":4,"1414":1,"1999":1}}],["maphub",{"3":{"1310":1}}],["maphealthchecks",{"3":{"235":1,"1331":1,"1338":4}}],["mapformat",{"3":{"1310":5}}],["mapforwarder",{"2":{"57":1,"59":1,"836":1,"837":1,"840":1,"1508":1},"3":{"0":2,"57":1,"59":1,"836":1,"837":2,"838":1,"840":1,"1212":1,"1435":1,"1446":1,"1508":1,"2023":1}}],["maptype",{"3":{"1310":5}}],["maptoapiversion",{"3":{"448":2,"1310":4,"1379":1,"2130":2}}],["maptodtogenerated",{"3":{"1349":1,"1358":3}}],["maptodto",{"2":{"5":1,"6":1,"1952":1,"1955":1},"3":{"5":4,"6":1,"1270":1,"1308":3,"1310":3,"1349":3,"1358":51,"1395":5,"1401":5,"1414":3,"1952":2,"1955":1}}],["maptodtos",{"2":{"5":1,"7":1,"331":1,"1952":1},"3":{"5":2,"7":1,"331":1,"1244":1,"1308":2,"1310":2,"1358":20,"1395":3,"1401":1,"1414":1,"1952":1}}],["mapopenapi",{"2":{"2131":1},"3":{"454":1,"1310":1,"1590":1,"2131":1}}],["mapoidcdiscoveryendpoint",{"2":{"31":1,"751":1,"1666":1},"3":{"31":1,"751":1,"1310":1,"1666":1}}],["mapcontrollers",{"3":{"1310":1}}],["mapcommonscalarui",{"2":{"1928":1},"3":{"1310":2,"1495":1,"1576":2,"1928":1}}],["mapcommonsameoriginapiproxy",{"2":{"1184":1},"3":{"0":1,"1184":1}}],["mapcommonopenapi",{"2":{"446":1,"453":1,"454":2,"1928":1,"2131":1},"3":{"446":1,"448":1,"450":1,"453":1,"454":3,"1310":3,"1576":3,"1928":2,"2131":1}}],["mapchild",{"3":{"1265":1,"1270":3,"1358":9}}],["mapclientconfigendpoint",{"2":{"674":1},"3":{"674":1,"1323":8,"1525":1}}],["mapcultureendpoint",{"2":{"415":1},"3":{"265":1,"415":1,"1310":4,"1323":1,"1590":1,"1652":1,"1665":1}}],["mapcachedhealthchecks",{"2":{"233":1,"235":1,"238":1,"1331":1,"2006":1},"3":{"233":1,"235":1,"238":1,"1331":1,"1338":7,"2006":1}}],["mapnotificationhub",{"3":{"225":2,"1306":1,"1308":1,"1310":2,"1665":1}}],["mapasync",{"3":{"109":1,"1220":1,"1229":2,"1804":1}}],["mapappassociationendpoints",{"2":{"418":1,"423":1,"424":1,"425":1,"426":1,"428":1,"1480":1,"1665":1},"3":{"0":1,"418":1,"423":1,"424":1,"425":1,"426":1,"428":1,"429":1,"1310":1,"1480":1,"1665":1}}],["mapjwksendpoint",{"3":{"31":1,"827":1,"1310":1,"1666":1}}],["mapdefaultendpoints",{"0":{"2006":1},"2":{"235":1,"464":1,"913":1,"1330":1,"1331":1,"1676":1,"1719":1,"1726":1,"2006":1,"2013":1},"3":{"0":1,"235":1,"464":1,"913":1,"1212":1,"1330":1,"1331":1,"1337":1,"1338":10,"1441":1,"1442":2,"1446":1,"1652":1,"1669":1,"1676":1,"1719":1,"1726":1,"2006":1,"2013":1}}],["mapregistry",{"3":{"1199":2,"1207":1}}],["mapreverseproxy",{"2":{"59":1,"836":1,"838":1,"841":1,"1508":1},"3":{"0":1,"59":1,"827":1,"836":1,"838":1,"841":1,"1337":1,"1338":1,"1435":1,"1446":1,"1508":1}}],["maprazorcomponents",{"2":{"647":1,"1433":1},"3":{"0":1,"556":1,"647":1,"649":1,"1310":1,"1323":1,"1415":1,"1433":1,"1435":2}}],["mapssearchurl",{"3":{"1416":1}}],["mapspeakersessions",{"3":{"1358":1}}],["mapscalarapireference",{"3":{"1310":1}}],["mapsearchurl",{"3":{"1394":2}}],["mapsessionizequestiontype",{"3":{"1358":2}}],["mapsessioncookieendpoints",{"2":{"1666":1},"3":{"1299":2,"1666":1}}],["mapsentity",{"3":{"1285":1}}],["mapstaticassets",{"3":{"1435":1}}],["mapstatustostring",{"3":{"1270":1,"1308":3}}],["mapstoaprovisionedalert",{"2":{"609":1},"3":{"609":2,"1435":2}}],["mapster",{"3":{"3":1,"4":1}}],["maps",{"0":{"1830":1},"1":{"1501":1,"1502":1,"1503":1,"1504":1,"1505":1,"1506":1,"1507":1,"1508":1,"1509":1,"1510":1,"1511":1,"1512":1,"1513":1,"1514":1,"1515":1,"1516":1},"3":{"0":7,"5":1,"53":1,"60":1,"78":1,"95":1,"107":1,"109":7,"170":2,"186":3,"225":1,"235":2,"265":1,"282":1,"318":2,"324":1,"330":1,"341":1,"365":1,"405":1,"449":1,"454":2,"548":1,"585":1,"626":1,"655":1,"657":5,"660":2,"674":1,"707":1,"710":1,"713":1,"733":1,"751":1,"827":1,"838":1,"845":1,"854":1,"869":1,"905":2,"921":1,"1020":1,"1033":1,"1034":1,"1050":3,"1059":1,"1061":1,"1161":1,"1180":1,"1184":1,"1191":1,"1194":1,"1212":1,"1217":1,"1219":1,"1229":3,"1237":5,"1245":1,"1247":5,"1263":2,"1265":2,"1269":1,"1270":5,"1271":3,"1273":2,"1277":1,"1279":1,"1280":1,"1282":3,"1283":1,"1285":39,"1295":1,"1296":2,"1299":12,"1305":1,"1306":2,"1308":9,"1310":22,"1311":5,"1315":1,"1322":6,"1323":19,"1330":1,"1331":1,"1334":1,"1338":13,"1349":9,"1351":1,"1353":4,"1358":35,"1361":2,"1368":4,"1371":1,"1373":2,"1379":13,"1389":1,"1390":5,"1394":26,"1395":29,"1399":1,"1401":8,"1404":1,"1405":1,"1407":1,"1411":1,"1412":1,"1414":13,"1415":2,"1416":26,"1426":1,"1434":1,"1435":34,"1437":1,"1440":1,"1441":1,"1442":1,"1443":1,"1445":1,"1465":1,"1466":1,"1488":1,"1495":13,"1501":1,"1508":1,"1581":1,"1588":1,"1590":2,"1595":1,"1599":1,"1619":1,"1630":1,"1631":1,"1635":2,"1650":1,"1657":1,"1661":1,"1662":2,"1664":1,"1665":1,"1666":1,"1669":1,"1676":1,"1680":1,"1684":1,"1699":1,"1701":3,"1728":1,"1748":1,"1752":1,"1755":1,"1769":1,"1789":2,"1804":1,"1806":1,"1820":1,"1833":1,"1848":1,"1860":1,"1870":1,"1888":2,"1929":1,"1932":1,"1945":2,"1950":1,"1951":1,"1952":2,"1953":2,"1958":2,"1960":2,"1961":1,"1962":2,"1964":1,"1987":1,"1994":1,"2041":1,"2071":1,"2086":1,"2114":1,"2149":2,"2166":1,"2198":1,"2202":1,"2231":1,"2233":1}}],["map",{"0":{"1408":1,"1516":1,"1653":1,"1659":1},"2":{"80":1,"82":1,"973":1,"1224":1,"1950":1},"3":{"0":7,"26":1,"41":1,"54":1,"57":2,"59":1,"80":3,"81":1,"82":2,"109":1,"111":1,"135":4,"166":2,"184":3,"186":3,"305":1,"333":2,"341":1,"350":1,"359":1,"380":3,"413":1,"546":1,"549":1,"583":1,"585":1,"599":1,"631":2,"632":1,"633":1,"635":1,"659":1,"698":2,"749":1,"751":1,"826":1,"827":1,"833":1,"836":3,"838":1,"840":2,"842":3,"846":2,"905":1,"926":1,"964":1,"972":3,"973":1,"1004":2,"1011":2,"1054":1,"1058":1,"1059":1,"1133":2,"1161":6,"1162":1,"1168":1,"1188":1,"1190":1,"1196":1,"1212":2,"1220":1,"1224":1,"1225":1,"1229":5,"1236":1,"1237":3,"1239":1,"1241":3,"1242":7,"1243":1,"1247":16,"1265":3,"1269":1,"1270":11,"1279":1,"1281":1,"1285":14,"1290":1,"1296":1,"1299":13,"1300":1,"1303":3,"1308":4,"1310":10,"1311":3,"1319":1,"1320":1,"1322":13,"1323":5,"1338":3,"1340":1,"1347":4,"1349":4,"1350":1,"1351":1,"1353":1,"1356":1,"1358":50,"1361":1,"1363":1,"1368":22,"1376":1,"1379":7,"1384":3,"1386":2,"1390":1,"1394":25,"1395":42,"1399":2,"1401":2,"1403":1,"1405":1,"1408":7,"1413":1,"1414":12,"1415":1,"1416":7,"1427":2,"1430":5,"1435":479,"1437":4,"1444":1,"1454":5,"1466":2,"1468":1,"1486":1,"1488":11,"1491":1,"1492":1,"1495":2,"1508":1,"1514":2,"1524":1,"1525":6,"1530":1,"1534":1,"1570":1,"1571":1,"1576":4,"1581":1,"1584":1,"1585":1,"1599":1,"1620":1,"1625":3,"1626":1,"1628":1,"1629":1,"1635":1,"1648":1,"1649":1,"1653":5,"1659":1,"1660":2,"1661":2,"1668":1,"1670":1,"1672":2,"1674":1,"1699":1,"1700":2,"1701":2,"1727":3,"1728":1,"1729":1,"1734":2,"1759":1,"1764":1,"1781":1,"1789":1,"1804":1,"1806":1,"1814":2,"1816":1,"1833":1,"1839":1,"1848":1,"1875":1,"1882":1,"1890":1,"1893":1,"1928":2,"1929":1,"1934":1,"1943":1,"1945":1,"1950":2,"1951":3,"1952":1,"1955":4,"1957":2,"1958":2,"1961":2,"1963":1,"1964":1,"1989":1,"1990":2,"2017":1,"2022":1,"2041":2,"2042":1,"2048":1,"2096":3,"2097":1,"2100":1,"2102":1,"2103":4,"2110":1,"2119":1,"2128":1,"2141":1,"2177":1,"2196":2,"2198":1,"2203":2,"2235":1}}],["mapproperty",{"2":{"1956":1},"3":{"1308":2,"1358":4,"1956":1}}],["mappedadminport",{"3":{"1435":1}}],["mappedentityqueryservice",{"3":{"1199":2,"1207":1}}],["mappedorder",{"3":{"1199":4,"1207":1}}],["mappedorderid",{"3":{"1199":5,"1207":1}}],["mappeddto",{"3":{"1199":2,"1207":1}}],["mapped",{"3":{"0":5,"4":1,"15":1,"31":1,"59":1,"96":1,"110":1,"111":1,"225":1,"303":1,"335":2,"341":1,"343":1,"345":1,"415":2,"448":3,"453":1,"454":1,"545":2,"549":1,"583":1,"585":2,"640":1,"657":4,"682":1,"698":1,"702":1,"703":1,"749":1,"751":1,"929":1,"932":1,"935":2,"1011":1,"1042":1,"1046":1,"1089":1,"1094":1,"1132":1,"1133":2,"1140":1,"1150":1,"1212":1,"1218":1,"1234":1,"1237":1,"1247":4,"1253":1,"1269":1,"1270":4,"1271":2,"1273":3,"1277":1,"1278":1,"1282":1,"1285":16,"1287":2,"1296":1,"1299":12,"1303":1,"1306":2,"1308":10,"1310":12,"1311":2,"1315":1,"1317":1,"1319":1,"1322":9,"1323":4,"1331":1,"1338":7,"1349":19,"1358":18,"1361":1,"1362":1,"1368":4,"1379":2,"1394":2,"1395":16,"1401":5,"1404":1,"1414":11,"1416":2,"1435":12,"1446":1,"1454":2,"1466":1,"1476":2,"1485":1,"1488":3,"1495":4,"1576":2,"1599":1,"1631":1,"1684":1,"1719":1,"1803":1,"1814":1,"1841":1,"1888":1,"1999":1,"2024":1,"2130":2,"2137":1}}],["mapperignoretarget",{"3":{"1358":2}}],["mapper",{"0":{"1953":1,"1955":1},"2":{"1956":1,"1957":1},"3":{"3":1,"5":2,"6":2,"7":2,"333":1,"423":1,"424":1,"549":2,"657":1,"798":1,"806":1,"1026":1,"1050":1,"1201":1,"1212":1,"1213":1,"1220":1,"1229":1,"1241":1,"1243":1,"1244":2,"1247":3,"1260":1,"1265":2,"1269":2,"1270":14,"1282":1,"1299":5,"1308":18,"1310":13,"1323":3,"1349":11,"1351":1,"1352":4,"1353":2,"1358":161,"1368":1,"1374":1,"1379":3,"1395":13,"1400":1,"1401":8,"1414":8,"1435":2,"1437":2,"1480":1,"1486":1,"1525":1,"1531":1,"1545":1,"1650":1,"1652":1,"1726":1,"1804":1,"1809":1,"1830":1,"1848":1,"1950":4,"1951":1,"1952":3,"1953":6,"1954":7,"1955":2,"1956":3,"1957":7,"1958":4,"2229":1}}],["mappers",{"3":{"0":1,"3":4,"5":2,"6":1,"7":1,"117":1,"473":1,"548":1,"1262":2,"1269":2,"1270":4,"1310":4,"1315":2,"1322":2,"1349":1,"1350":1,"1352":2,"1358":23,"1395":6,"1403":1,"1414":3,"1436":2,"1437":3,"1495":1,"1525":1,"1649":1,"1650":1,"1660":1,"1661":3,"1685":1,"1700":1,"1774":1,"1856":1,"1882":3,"1950":1,"1952":1,"1955":3,"1956":2,"2051":1,"2094":1,"2231":1}}],["mapperly",{"0":{"1954":1},"3":{"0":3,"3":1,"5":2,"548":1,"549":1,"1050":4,"1051":1,"1212":1,"1213":2,"1270":3,"1299":2,"1305":1,"1308":11,"1346":1,"1349":7,"1352":1,"1356":1,"1358":48,"1395":11,"1400":1,"1401":6,"1403":1,"1414":7,"1437":1,"1649":1,"1650":1,"1661":1,"1809":1,"1954":4,"1955":1,"1956":1,"1957":1,"1958":2}}],["mappings",{"3":{"104":1,"658":1,"1308":1,"1361":1,"1394":1,"1773":1}}],["mapping",{"0":{"1280":1,"1352":1,"1926":1,"1951":1},"1":{"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"1310":1,"1950":1,"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1956":1,"1957":1,"1958":1},"2":{"1247":1,"1270":1},"3":{"0":7,"2":1,"3":1,"4":2,"6":5,"7":1,"26":1,"53":1,"92":1,"104":1,"109":1,"111":1,"151":1,"166":1,"185":1,"186":1,"188":1,"238":1,"265":1,"331":1,"334":1,"360":1,"369":1,"370":2,"371":1,"372":1,"420":1,"454":1,"470":1,"545":1,"549":2,"559":1,"572":1,"583":1,"657":1,"658":2,"659":1,"690":1,"707":1,"734":1,"741":1,"742":1,"749":1,"782":1,"790":1,"791":2,"799":2,"806":1,"833":1,"838":1,"846":1,"872":1,"874":1,"905":2,"906":2,"910":1,"922":1,"925":1,"964":1,"1011":1,"1018":1,"1026":1,"1034":3,"1042":1,"1049":2,"1050":2,"1051":4,"1052":1,"1069":1,"1175":3,"1192":3,"1202":4,"1203":10,"1207":26,"1212":1,"1213":3,"1224":1,"1225":2,"1229":3,"1237":2,"1238":1,"1244":2,"1245":1,"1247":10,"1265":1,"1269":2,"1270":12,"1271":4,"1273":2,"1280":1,"1281":3,"1282":2,"1285":51,"1289":1,"1299":22,"1303":1,"1306":1,"1308":10,"1309":1,"1310":45,"1311":5,"1316":1,"1322":3,"1323":10,"1331":2,"1338":3,"1346":1,"1349":4,"1352":3,"1358":64,"1359":1,"1364":1,"1365":1,"1368":17,"1369":1,"1374":1,"1377":1,"1379":19,"1394":15,"1395":44,"1400":1,"1401":11,"1403":1,"1408":1,"1410":1,"1413":1,"1414":16,"1415":3,"1416":3,"1435":14,"1436":1,"1437":3,"1440":1,"1442":1,"1449":1,"1452":1,"1454":1,"1460":1,"1486":1,"1487":2,"1488":1,"1495":2,"1497":1,"1525":1,"1545":1,"1570":1,"1576":1,"1584":1,"1599":1,"1650":1,"1661":2,"1665":1,"1672":1,"1685":1,"1778":1,"1804":2,"1806":1,"1809":3,"1824":1,"1830":1,"1853":1,"1855":3,"1861":1,"1922":1,"1923":2,"1924":1,"1926":1,"1928":1,"1929":2,"1934":1,"1949":1,"1950":4,"1951":3,"1952":2,"1953":2,"1954":4,"1955":2,"1956":1,"1957":2,"1958":9,"1959":1,"1961":2,"1963":1,"1964":1,"1987":1,"1990":1,"2017":1,"2024":1,"2054":1,"2059":1,"2145":1,"2165":1,"2166":1,"2191":1,"2207":2,"2231":1}}],["md5",{"3":{"1904":1}}],["md",{"0":{"1473":1,"1475":1,"1476":1,"1477":1,"1480":1},"2":{"137":1,"138":1,"140":1,"146":1,"147":1,"164":1,"375":1,"607":1,"609":1,"636":2,"971":1,"972":1,"974":1,"975":1,"1003":2,"1004":1,"1058":1,"1190":1,"1450":1,"1456":1,"1457":1,"1464":1,"1466":1,"1475":1,"1482":1,"1514":1,"1521":2,"1528":1,"1570":1,"1572":2,"1573":1,"1574":2,"1576":1,"1586":2,"1588":1,"1593":1,"1594":1,"1604":1,"1657":2,"1672":2,"1673":5,"1674":1,"1696":1,"1755":1,"2044":1,"2048":2,"2059":1,"2111":1,"2194":1},"3":{"0":12,"1":5,"27":1,"68":1,"130":4,"135":6,"136":3,"137":8,"138":4,"139":4,"140":5,"141":2,"142":4,"146":1,"147":5,"152":1,"164":1,"313":1,"317":1,"321":1,"324":1,"326":2,"358":2,"361":1,"362":2,"365":1,"366":2,"369":4,"371":1,"375":2,"376":3,"398":1,"406":1,"422":1,"423":1,"424":1,"425":1,"426":2,"435":1,"443":1,"469":1,"483":1,"490":1,"491":1,"492":1,"493":1,"495":1,"497":1,"502":1,"526":2,"527":1,"528":2,"531":1,"532":1,"550":1,"555":6,"556":1,"557":1,"558":2,"563":2,"564":7,"571":2,"577":1,"579":1,"582":1,"590":2,"591":3,"593":1,"594":3,"598":2,"599":1,"601":2,"605":1,"607":5,"608":1,"609":16,"611":6,"612":1,"614":1,"617":1,"618":1,"620":1,"621":1,"622":1,"626":5,"632":2,"633":5,"634":2,"636":4,"657":1,"660":1,"748":3,"756":4,"761":1,"765":1,"766":2,"773":2,"825":1,"827":1,"832":1,"857":1,"860":1,"861":1,"864":2,"892":1,"893":1,"896":5,"897":1,"899":1,"904":1,"914":2,"917":1,"937":1,"946":1,"950":1,"953":2,"954":1,"955":1,"959":1,"960":1,"971":4,"972":5,"974":1,"975":2,"987":2,"990":1,"991":1,"1003":5,"1004":8,"1006":2,"1007":5,"1011":1,"1012":3,"1017":1,"1018":5,"1058":2,"1062":1,"1069":1,"1089":1,"1090":2,"1091":1,"1092":1,"1093":3,"1094":2,"1095":5,"1116":1,"1119":1,"1124":2,"1127":1,"1128":1,"1132":2,"1136":1,"1137":1,"1160":2,"1164":1,"1171":1,"1180":1,"1182":1,"1186":3,"1188":1,"1189":1,"1190":2,"1191":4,"1192":11,"1193":2,"1200":3,"1201":1,"1202":28,"1203":28,"1211":2,"1212":5,"1214":1,"1216":5,"1237":4,"1270":3,"1285":8,"1299":17,"1300":7,"1303":2,"1307":1,"1308":4,"1310":1,"1322":1,"1323":21,"1338":3,"1346":1,"1349":4,"1358":20,"1379":20,"1394":11,"1395":7,"1401":2,"1402":1,"1404":1,"1409":2,"1410":1,"1414":6,"1415":2,"1416":4,"1426":1,"1427":2,"1429":2,"1430":4,"1435":47,"1437":1,"1439":1,"1450":2,"1452":7,"1453":3,"1454":1,"1456":2,"1457":1,"1458":3,"1461":1,"1464":1,"1465":4,"1466":22,"1469":6,"1470":4,"1471":1,"1472":1,"1473":37,"1474":1,"1475":32,"1476":12,"1477":5,"1478":1,"1479":2,"1480":17,"1481":9,"1482":10,"1483":4,"1484":2,"1485":3,"1486":2,"1487":1,"1488":6,"1491":4,"1493":4,"1495":46,"1496":2,"1497":1,"1500":3,"1501":5,"1502":1,"1514":1,"1516":5,"1520":1,"1521":4,"1523":3,"1524":4,"1525":50,"1526":1,"1527":1,"1528":1,"1530":5,"1531":1,"1532":1,"1533":3,"1534":5,"1570":1,"1572":3,"1573":1,"1574":4,"1575":3,"1576":72,"1577":1,"1578":1,"1581":11,"1582":1,"1584":6,"1585":13,"1586":4,"1588":6,"1589":1,"1590":32,"1592":1,"1593":3,"1594":1,"1595":9,"1596":3,"1597":3,"1598":1,"1599":4,"1604":1,"1616":1,"1630":1,"1645":1,"1647":1,"1648":2,"1653":1,"1657":3,"1672":2,"1673":5,"1674":2,"1691":1,"1696":1,"1705":1,"1708":1,"1731":1,"1733":1,"1734":1,"1755":1,"1764":1,"1837":1,"1894":1,"1993":1,"1999":3,"2000":3,"2032":1,"2033":1,"2040":1,"2041":1,"2042":1,"2044":1,"2045":2,"2048":2,"2059":1,"2111":2,"2116":1,"2117":1,"2119":1,"2125":1,"2150":1,"2154":1,"2157":1,"2161":1,"2170":2,"2171":2,"2177":1,"2178":3,"2181":2,"2194":3,"2238":2}}],["cmd",{"2":{"1605":1,"1739":1},"3":{"1605":1,"1739":1}}],["cms",{"3":{"1394":1}}],["ck",{"2":{"1590":1},"3":{"1590":1}}],["c4adff2",{"2":{"1760":1},"3":{"1590":1,"1760":1}}],["c4",{"3":{"1570":1}}],["cwv",{"3":{"1525":1,"1530":1,"1599":3}}],["c64b9fe2",{"3":{"1495":1}}],["c6369020",{"3":{"1471":1}}],["c911480",{"3":{"1495":2}}],["cyan",{"3":{"1479":3}}],["cyclic",{"3":{"1259":1,"1285":1}}],["cyclomatic",{"3":{"1237":1,"1247":1,"1311":1,"1358":1}}],["cyclonedx",{"2":{"373":1},"3":{"0":1,"370":4,"371":1,"372":1,"373":1,"374":1,"375":2,"870":1,"1444":1,"1453":4,"1454":1,"1525":1,"1575":1,"1576":5,"1672":1,"1737":1}}],["cyclefixtures",{"2":{"1435":1},"3":{"1207":8,"1435":15}}],["cycletestmap",{"3":{"1198":1,"1199":2,"1207":1,"1435":5,"1495":1}}],["cycles",{"0":{"1198":1},"2":{"135":1},"3":{"0":1,"19":1,"130":1,"135":3,"136":1,"139":1,"627":1,"1011":1,"1191":1,"1192":1,"1196":1,"1201":3,"1207":1,"1259":1,"1270":1,"1275":2,"1285":1,"1322":1,"1323":1,"1338":1,"1430":1,"1435":12,"1437":1,"1488":1,"1495":23,"1497":5,"1516":2,"1637":1,"1638":1,"1664":1,"1803":1,"1866":1,"1884":1,"1885":1}}],["cycle",{"0":{"139":1},"3":{"0":3,"17":1,"19":1,"22":1,"24":5,"26":2,"27":3,"99":1,"125":1,"130":1,"135":5,"139":1,"140":1,"534":1,"536":6,"539":2,"556":1,"583":2,"707":5,"846":1,"861":1,"876":1,"1012":1,"1014":1,"1017":1,"1036":2,"1085":1,"1089":2,"1090":1,"1201":2,"1212":1,"1229":1,"1237":8,"1247":4,"1251":1,"1253":2,"1254":4,"1255":2,"1256":1,"1259":25,"1269":1,"1270":2,"1275":6,"1285":16,"1308":1,"1314":1,"1316":4,"1322":27,"1323":4,"1327":2,"1329":1,"1338":6,"1349":2,"1358":2,"1368":1,"1394":3,"1408":1,"1414":1,"1415":2,"1416":1,"1430":1,"1435":35,"1437":2,"1440":2,"1442":1,"1447":1,"1448":1,"1449":1,"1452":1,"1455":1,"1466":2,"1485":1,"1489":1,"1490":1,"1495":9,"1497":2,"1516":2,"1570":1,"1663":1,"1705":1,"1880":1,"1884":1,"1885":1,"1888":1,"1947":1,"2009":1,"2155":1,"2156":1,"2164":1,"2166":8,"2187":3,"2192":1}}],["cn=user",{"3":{"1415":1}}],["c50d86f",{"3":{"1495":1}}],["c5",{"3":{"1354":1,"1358":1,"1640":1}}],["cbc",{"3":{"1285":1}}],["c2y6ydjf5",{"3":{"1285":1}}],["cpm",{"3":{"1214":1,"1590":2}}],["cpu",{"2":{"875":1},"3":{"31":1,"234":1,"361":1,"593":1,"869":1,"871":1,"875":5,"1299":1,"1333":1,"1338":4,"1442":2,"1466":9,"1533":1,"1999":1,"2006":1,"2010":1,"2144":1}}],["cdc",{"3":{"1525":1,"1544":1}}],["cd",{"1":{"1451":1,"1452":1,"1453":1,"1454":1,"1455":1,"1456":1,"1457":1,"1458":1,"1459":1,"1460":1},"3":{"1192":1,"1451":1,"1452":1,"1454":1,"1461":2,"1462":1,"1464":1,"1470":1,"1473":1,"1474":1,"1475":1,"1481":1,"1485":1,"1525":2,"1544":1,"1553":1,"1574":1,"1576":2,"1581":1,"1583":1,"1585":2,"1669":1,"1673":1,"2108":1,"2212":1,"2217":1,"2218":1,"2219":2}}],["cdn",{"3":{"441":1,"1116":1,"1310":2,"1414":1,"1466":1,"1480":1,"1665":1,"1972":1,"2074":1,"2126":1,"2149":4}}],["cf69cb8e",{"3":{"1495":3}}],["cfg",{"2":{"819":1},"3":{"640":2,"819":1,"1322":7}}],["cfbundleurltypes",{"2":{"420":1},"3":{"420":1}}],["ctrl",{"2":{"1605":1,"1739":1},"3":{"1605":1,"1739":1}}],["cts",{"3":{"1323":33,"1394":16,"1395":14,"1401":5,"1414":3,"1416":4,"1525":2,"1576":4}}],["ctor",{"3":{"1285":2,"1310":1,"1435":7,"1525":2,"1590":1}}],["ctorprobequeryhandler",{"3":{"1199":2,"1207":1}}],["ctorprobequery",{"3":{"1199":3,"1207":1}}],["ctorprobecommandhandler",{"3":{"1199":2,"1207":1}}],["ctorprobecommand",{"3":{"1199":3,"1207":1}}],["cte",{"3":{"1132":1}}],["ct",{"3":{"109":2,"434":1,"690":1,"819":1,"1212":1,"1259":3,"1270":3,"1285":6,"1322":9,"1323":27,"1358":2,"1379":4,"1394":60,"1395":23,"1435":8,"1918":1,"1943":1,"2031":1}}],["c→a",{"3":{"78":1,"1590":1,"1593":1,"1595":4,"1596":2,"1598":1}}],["c+",{"3":{"0":1,"1183":2,"1187":2}}],["c",{"0":{"1214":1},"1":{"977":1,"978":1,"979":1,"980":1,"981":1,"982":1,"983":1,"984":1,"1802":1,"1803":1,"1804":1,"1805":1,"1806":1},"3":{"0":3,"53":1,"142":1,"145":1,"147":1,"265":1,"274":1,"418":1,"543":1,"549":1,"692":1,"839":1,"840":1,"963":1,"964":2,"977":1,"979":2,"980":1,"1004":1,"1010":1,"1011":1,"1013":1,"1019":1,"1066":1,"1183":1,"1193":1,"1196":1,"1212":2,"1214":3,"1216":1,"1229":3,"1236":1,"1237":5,"1247":2,"1259":1,"1262":1,"1270":2,"1271":8,"1285":6,"1299":9,"1308":3,"1310":7,"1311":7,"1315":1,"1322":6,"1323":23,"1325":1,"1338":6,"1341":1,"1343":1,"1348":1,"1349":6,"1353":1,"1358":12,"1377":1,"1378":1,"1379":7,"1394":8,"1395":31,"1400":2,"1401":5,"1412":1,"1414":10,"1415":3,"1416":9,"1424":1,"1426":5,"1429":1,"1432":1,"1435":15,"1440":1,"1454":1,"1478":1,"1487":2,"1490":1,"1491":1,"1496":1,"1525":2,"1535":1,"1564":1,"1576":1,"1581":1,"1584":4,"1585":7,"1631":1,"1638":1,"1640":1,"1647":2,"1650":1,"1660":1,"1691":1,"1692":1,"1711":3,"1725":1,"1726":1,"1727":1,"1760":1,"1764":1,"1778":1,"1786":1,"1789":1,"1792":1,"1795":1,"1802":1,"1806":1,"1811":1,"1816":1,"1826":1,"1829":3,"1830":1,"1833":1,"1845":1,"1852":1,"1861":1,"1867":1,"1876":1,"1885":1,"1891":1,"1898":1,"1904":1,"1911":1,"1923":1,"1926":1,"1929":1,"1940":1,"1949":1,"1952":1,"1954":1,"1958":1,"1964":1,"1971":1,"1978":1,"1984":1,"1990":1,"1996":1,"2001":1,"2003":1,"2013":1,"2015":1,"2016":2,"2017":1,"2027":1,"2048":1,"2059":1,"2068":1,"2074":2,"2079":1,"2085":1,"2087":1,"2097":1,"2107":1,"2122":1,"2127":1,"2132":1,"2138":1,"2145":1,"2152":1,"2159":1,"2168":1,"2176":1,"2179":1,"2192":1,"2202":1,"2205":1,"2210":1,"2218":1,"2219":2,"2231":1}}],["cve",{"3":{"0":1,"370":1,"1444":1}}],["cs0103",{"2":{"1647":1},"3":{"1647":1}}],["cs0618",{"3":{"1416":1,"1435":1}}],["cs1591",{"3":{"1576":2,"1585":3,"1590":1}}],["cs9113",{"3":{"1435":3}}],["cs9107",{"3":{"1259":1,"1285":3}}],["cswinrt1028",{"3":{"1416":1}}],["cswinrt1030",{"3":{"1416":2}}],["cswinrt",{"3":{"1416":2}}],["css3",{"3":{"2219":1}}],["css",{"2":{"2074":1,"2078":1},"3":{"954":1,"1299":1,"1323":13,"1394":1,"1431":1,"1435":17,"1437":1,"1488":2,"1523":2,"1525":12,"1530":2,"1531":1,"1533":2,"1534":1,"1556":1,"1564":1,"1576":11,"1581":6,"1582":2,"1585":1,"1590":7,"1594":1,"1595":2,"1596":2,"1599":4,"1711":3,"1713":1,"2072":1,"2074":9,"2078":1,"2149":2}}],["csrf",{"0":{"1968":1},"3":{"0":2,"207":1,"209":2,"1184":7,"1185":3,"1186":1,"1187":2,"1299":2,"1323":1,"1416":1,"1533":1,"1581":1,"1970":1,"1971":2}}],["cs",{"2":{"43":2,"63":1,"93":1,"95":1,"112":1,"115":3,"142":1,"145":4,"152":3,"171":2,"181":2,"203":1,"220":2,"254":1,"256":1,"258":1,"259":4,"337":5,"345":3,"354":2,"364":1,"378":1,"395":1,"409":1,"418":1,"424":1,"425":1,"428":2,"452":1,"453":1,"464":1,"465":1,"466":1,"514":2,"543":4,"545":1,"547":1,"552":7,"558":1,"559":1,"572":1,"597":1,"604":1,"618":1,"621":1,"638":1,"644":2,"649":1,"651":1,"652":1,"693":5,"720":2,"728":2,"736":1,"744":1,"747":3,"750":1,"752":1,"822":3,"832":2,"833":3,"841":1,"846":1,"849":1,"850":1,"883":1,"892":5,"916":1,"917":2,"929":1,"949":4,"960":1,"967":1,"975":2,"991":1,"999":1,"1029":4,"1050":3,"1062":1,"1074":6,"1078":2,"1128":3,"1133":1,"1135":1,"1214":1,"1262":2,"1299":1,"1310":2,"1328":2,"1443":2,"1444":1,"1496":2,"1531":1,"1534":4,"1576":1,"1581":2,"1585":1,"1590":2,"1595":1,"1596":1,"1618":1,"1683":1,"1684":1,"1685":1,"1688":1,"1766":8,"1828":1,"1877":1,"1884":1,"1894":2,"1901":1,"2010":1,"2029":1,"2147":1,"2158":8,"2187":1,"2189":1},"3":{"0":6,"15":2,"17":2,"19":5,"22":2,"23":1,"24":24,"25":5,"26":13,"27":23,"31":5,"34":1,"39":13,"42":2,"43":4,"53":1,"59":5,"60":1,"61":1,"62":1,"63":4,"68":4,"70":2,"71":4,"72":7,"73":4,"74":8,"79":1,"80":10,"81":6,"82":2,"93":6,"94":2,"95":15,"96":6,"98":5,"99":1,"100":2,"103":3,"104":2,"109":38,"110":3,"111":7,"112":4,"115":3,"117":12,"121":15,"122":15,"123":4,"125":7,"126":16,"127":2,"128":9,"135":9,"136":3,"138":1,"139":10,"142":6,"145":7,"147":3,"150":25,"152":4,"157":11,"159":2,"160":9,"161":12,"164":1,"166":14,"168":1,"170":5,"171":10,"175":2,"178":9,"179":7,"180":5,"181":4,"186":35,"188":1,"189":5,"190":6,"193":2,"195":9,"196":2,"198":3,"200":8,"201":9,"202":11,"203":6,"216":4,"217":3,"218":4,"219":7,"220":10,"225":11,"228":4,"230":12,"231":4,"235":25,"237":5,"238":3,"243":37,"245":15,"246":8,"250":6,"251":12,"252":15,"254":19,"255":7,"256":17,"257":16,"258":16,"259":15,"260":11,"261":19,"265":12,"268":7,"272":1,"281":1,"283":2,"284":2,"285":3,"290":1,"295":12,"296":1,"300":4,"301":1,"303":4,"305":8,"306":5,"310":30,"312":5,"314":8,"318":67,"319":4,"320":2,"323":7,"324":8,"325":8,"326":13,"328":1,"330":37,"331":8,"332":7,"333":5,"334":2,"336":3,"337":12,"341":23,"342":1,"343":5,"344":6,"345":9,"350":21,"352":7,"353":7,"354":2,"359":62,"360":8,"361":9,"364":1,"365":3,"378":1,"384":8,"387":2,"388":4,"390":10,"391":8,"392":4,"393":5,"395":2,"397":66,"399":6,"400":5,"401":15,"402":16,"403":1,"404":7,"407":12,"408":7,"409":6,"412":1,"413":12,"415":4,"416":1,"418":11,"419":1,"420":2,"422":2,"423":6,"424":3,"425":4,"426":6,"427":2,"428":9,"429":2,"430":2,"434":7,"435":3,"436":8,"442":1,"443":5,"444":9,"448":45,"450":3,"452":1,"453":7,"454":3,"455":1,"459":35,"461":5,"463":15,"464":17,"465":14,"466":8,"469":4,"470":17,"472":1,"474":1,"475":10,"476":14,"477":14,"478":2,"482":2,"486":6,"487":6,"488":5,"489":6,"490":6,"491":7,"492":8,"493":8,"494":6,"495":5,"497":12,"498":1,"499":45,"500":1,"501":5,"502":4,"507":63,"508":1,"509":1,"511":13,"512":1,"513":15,"514":8,"522":8,"523":8,"524":2,"535":1,"536":73,"537":1,"538":15,"539":14,"540":13,"543":11,"544":2,"545":44,"546":4,"547":6,"549":25,"550":9,"551":3,"552":21,"556":47,"558":4,"559":6,"564":8,"572":81,"574":2,"575":4,"577":10,"578":9,"583":60,"584":12,"585":28,"586":6,"587":1,"591":14,"592":3,"597":1,"599":4,"601":2,"603":3,"604":1,"609":7,"618":35,"620":3,"621":4,"622":1,"633":4,"635":1,"636":3,"638":1,"640":24,"642":5,"644":5,"649":28,"651":5,"652":4,"656":1,"657":72,"658":4,"659":6,"660":10,"665":8,"668":4,"669":3,"674":82,"675":3,"676":14,"677":11,"682":10,"684":2,"685":2,"690":40,"691":4,"692":3,"693":9,"697":1,"698":26,"700":4,"702":9,"703":7,"706":1,"707":15,"710":8,"715":7,"718":1,"719":1,"720":11,"724":8,"725":21,"727":3,"728":6,"732":1,"733":16,"736":12,"740":7,"741":31,"743":14,"744":6,"747":3,"749":48,"750":1,"751":12,"752":2,"774":28,"776":7,"777":6,"782":51,"783":1,"784":4,"789":5,"790":20,"791":4,"792":10,"793":6,"794":4,"798":12,"799":5,"801":3,"803":5,"804":3,"805":2,"806":4,"809":6,"813":6,"814":8,"818":1,"819":21,"821":3,"822":7,"826":5,"827":47,"829":6,"830":9,"831":15,"832":21,"833":15,"837":6,"838":10,"839":2,"841":4,"846":5,"847":1,"849":4,"850":4,"854":5,"857":4,"861":11,"862":4,"863":4,"864":3,"880":1,"881":60,"883":8,"884":4,"888":3,"889":20,"890":5,"891":8,"892":10,"897":34,"899":3,"900":8,"904":1,"905":55,"906":18,"907":15,"908":5,"909":11,"910":2,"913":5,"914":15,"915":2,"916":3,"917":2,"921":3,"922":11,"923":5,"924":4,"926":29,"927":8,"929":1,"930":1,"931":9,"934":3,"946":19,"948":5,"949":5,"954":21,"955":2,"956":1,"959":1,"960":7,"963":6,"964":12,"965":3,"966":7,"967":5,"971":2,"972":6,"973":1,"974":5,"975":4,"979":8,"980":71,"981":1,"982":10,"983":7,"987":6,"988":36,"990":3,"991":4,"995":4,"996":18,"997":2,"998":9,"999":4,"1003":1,"1004":20,"1005":2,"1006":4,"1007":3,"1017":2,"1018":38,"1020":3,"1021":9,"1025":5,"1026":29,"1028":2,"1029":9,"1034":19,"1037":5,"1041":4,"1042":20,"1043":1,"1044":4,"1045":6,"1049":5,"1050":25,"1052":3,"1054":2,"1055":9,"1058":4,"1059":56,"1061":1,"1062":3,"1066":4,"1067":20,"1069":5,"1070":3,"1074":22,"1075":1,"1078":3,"1082":11,"1083":7,"1085":2,"1086":9,"1090":3,"1091":64,"1092":5,"1093":13,"1094":5,"1095":7,"1099":2,"1100":17,"1102":2,"1103":5,"1108":15,"1111":6,"1116":37,"1117":2,"1119":4,"1123":8,"1124":37,"1125":7,"1126":6,"1127":2,"1128":6,"1129":2,"1132":1,"1133":17,"1135":3,"1136":3,"1140":11,"1142":1,"1146":2,"1150":13,"1156":3,"1160":2,"1161":8,"1162":4,"1163":4,"1168":32,"1169":1,"1170":3,"1174":1,"1175":39,"1176":6,"1177":7,"1178":3,"1179":2,"1183":4,"1184":36,"1185":6,"1186":7,"1187":1,"1188":1,"1190":1,"1191":6,"1204":1,"1207":5200,"1208":115,"1209":137,"1212":6,"1214":2,"1219":13,"1220":24,"1221":11,"1222":9,"1223":15,"1224":6,"1225":11,"1226":7,"1227":13,"1228":21,"1229":244,"1231":31,"1232":18,"1233":18,"1234":43,"1235":14,"1236":27,"1237":524,"1239":59,"1240":36,"1241":96,"1242":48,"1243":54,"1244":56,"1245":6,"1246":16,"1247":328,"1249":15,"1250":5,"1251":6,"1252":13,"1253":16,"1254":17,"1255":10,"1256":36,"1257":17,"1258":19,"1259":572,"1261":4,"1262":41,"1263":84,"1264":34,"1265":60,"1266":17,"1267":28,"1268":18,"1269":48,"1270":588,"1271":178,"1273":19,"1274":6,"1275":17,"1276":10,"1277":8,"1278":29,"1279":14,"1280":16,"1281":25,"1282":9,"1283":11,"1284":9,"1285":2161,"1287":35,"1288":33,"1289":54,"1290":19,"1291":21,"1292":52,"1293":47,"1294":30,"1295":18,"1296":100,"1297":38,"1298":9,"1299":1284,"1300":360,"1301":8,"1302":11,"1303":19,"1304":8,"1305":11,"1306":26,"1307":11,"1308":416,"1309":118,"1310":937,"1311":100,"1313":12,"1314":35,"1315":51,"1316":39,"1317":44,"1318":6,"1319":64,"1320":3,"1321":42,"1322":887,"1323":1171,"1324":7,"1325":14,"1326":12,"1327":6,"1328":5,"1329":5,"1330":5,"1331":34,"1332":20,"1333":14,"1334":6,"1335":7,"1336":6,"1337":13,"1338":394,"1340":3,"1341":78,"1342":42,"1343":46,"1344":8,"1345":6,"1346":47,"1347":19,"1348":25,"1349":983,"1351":48,"1352":38,"1353":15,"1354":15,"1355":12,"1356":14,"1357":10,"1358":3290,"1360":3,"1361":46,"1362":5,"1363":14,"1364":8,"1365":16,"1366":6,"1367":7,"1368":109,"1370":27,"1371":29,"1372":45,"1373":16,"1374":37,"1375":4,"1376":15,"1377":18,"1378":9,"1379":349,"1381":25,"1382":27,"1383":24,"1384":16,"1385":6,"1386":11,"1387":15,"1388":14,"1389":11,"1390":17,"1391":21,"1392":10,"1393":6,"1394":951,"1395":1514,"1396":5,"1397":24,"1398":44,"1399":13,"1400":20,"1401":479,"1402":1,"1403":21,"1404":13,"1405":26,"1406":6,"1407":8,"1408":11,"1409":13,"1410":7,"1411":7,"1412":11,"1413":13,"1414":808,"1415":209,"1416":1116,"1418":7,"1419":7,"1420":4,"1421":11,"1422":7,"1423":6,"1424":9,"1425":3,"1426":272,"1428":28,"1429":22,"1430":90,"1431":42,"1432":50,"1433":18,"1434":37,"1435":2035,"1436":37,"1437":331,"1439":2,"1440":95,"1441":39,"1442":110,"1443":39,"1444":1,"1445":2,"1446":39,"1447":4,"1448":25,"1449":8,"1450":3,"1454":4,"1466":12,"1475":11,"1480":1,"1486":1,"1487":69,"1488":36,"1489":6,"1490":3,"1491":21,"1495":307,"1496":4,"1497":2,"1523":6,"1524":8,"1525":227,"1530":20,"1531":16,"1533":10,"1534":55,"1574":3,"1575":11,"1576":247,"1581":29,"1582":10,"1583":1,"1584":7,"1585":66,"1588":5,"1589":5,"1590":148,"1595":22,"1596":9,"1598":6,"1599":57,"1604":1,"1610":3,"1618":6,"1626":2,"1650":2,"1652":5,"1653":1,"1655":2,"1683":2,"1684":25,"1685":35,"1688":1,"1696":1,"1706":5,"1707":1,"1711":1,"1726":1,"1727":2,"1738":1,"1748":1,"1756":1,"1759":2,"1760":2,"1763":19,"1764":30,"1766":83,"1769":2,"1814":1,"1828":2,"1877":1,"1884":1,"1894":2,"1895":1,"1901":1,"1938":1,"1952":1,"1981":22,"1982":3,"1983":3,"1993":41,"1994":36,"1999":38,"2000":12,"2005":1,"2007":1,"2010":1,"2029":1,"2031":2,"2043":7,"2045":2,"2063":3,"2065":8,"2082":12,"2084":8,"2096":2,"2116":9,"2117":8,"2118":4,"2120":3,"2121":3,"2125":19,"2126":6,"2130":23,"2131":3,"2135":15,"2136":3,"2137":11,"2142":1,"2147":1,"2148":7,"2149":3,"2150":7,"2151":3,"2155":33,"2156":13,"2158":14,"2160":1,"2162":9,"2163":6,"2164":5,"2165":5,"2166":15,"2167":5,"2171":2,"2172":2,"2173":5,"2174":9,"2175":1,"2176":3,"2177":1,"2178":8,"2183":3,"2184":2,"2185":4,"2186":2,"2187":2,"2188":3,"2189":4,"2190":2,"2191":1,"2195":1,"2196":4,"2197":3,"2198":6,"2200":2,"2201":10}}],["csvcontenttype",{"3":{"1310":1}}],["csvwritertests",{"3":{"1199":1,"1207":2,"1437":1}}],["csvwriter",{"2":{"741":1},"3":{"741":5,"743":1,"1199":3,"1203":1,"1207":2,"1310":25,"1437":1,"1495":1}}],["csvs",{"3":{"725":2}}],["csvhelper",{"3":{"0":1,"741":1,"1310":1}}],["csv",{"1":{"738":1,"739":1,"740":1,"741":1,"742":1,"743":1,"744":1,"745":1},"3":{"0":3,"318":3,"321":1,"324":1,"330":1,"696":1,"738":1,"740":5,"741":9,"742":2,"743":4,"744":1,"745":1,"1212":1,"1229":1,"1310":22,"1322":1,"1358":3,"1370":1,"1371":2,"1379":13,"1437":1,"1486":1,"1497":1,"1525":2,"1574":1,"1576":1,"1581":1,"1585":1,"1659":1,"1665":4,"1719":1,"1987":3,"1990":1,"1994":2,"1996":1,"2231":1}}],["cspoptions",{"3":{"1323":2}}],["cspnonce",{"2":{"2148":1},"3":{"214":1,"1199":3,"1203":1,"1207":1,"1335":1,"1338":6,"1442":1,"1525":1,"1576":2,"1669":1,"2148":2}}],["csppolicy",{"2":{"2010":1},"3":{"214":1,"1199":7,"1203":1,"1207":1,"1323":3,"1335":1,"1338":11,"2010":1,"2148":1}}],["csproj",{"2":{"152":1,"872":1,"1215":1,"1727":1,"2040":1},"3":{"0":1,"135":1,"147":1,"152":2,"265":1,"370":1,"375":1,"422":1,"426":1,"429":1,"430":1,"486":4,"487":1,"489":1,"490":1,"491":1,"492":1,"493":1,"526":1,"528":1,"572":2,"576":1,"591":2,"593":1,"609":2,"613":1,"665":1,"669":1,"826":2,"827":1,"861":1,"872":1,"876":4,"913":4,"914":1,"917":1,"939":1,"954":11,"956":4,"958":2,"960":2,"971":1,"972":1,"979":1,"1059":1,"1067":3,"1090":1,"1091":6,"1094":2,"1212":1,"1214":1,"1215":1,"1247":1,"1322":2,"1323":3,"1324":2,"1338":5,"1340":5,"1343":1,"1349":1,"1358":4,"1379":1,"1394":2,"1395":3,"1414":2,"1415":5,"1416":1,"1433":1,"1434":1,"1435":36,"1436":1,"1437":2,"1440":8,"1444":4,"1453":2,"1475":1,"1480":9,"1485":2,"1487":2,"1488":3,"1493":1,"1523":1,"1525":6,"1530":1,"1534":1,"1576":1,"1585":1,"1590":2,"1599":1,"1648":2,"1684":1,"1685":1,"1726":1,"1727":5,"2040":1,"2045":1,"2178":2}}],["csprng",{"3":{"0":1,"1212":1,"1299":5}}],["csp",{"0":{"2148":1},"1":{"211":1,"212":1,"213":1,"214":1,"215":1,"216":1,"217":1,"218":1,"219":1,"220":1,"221":1,"2146":1,"2147":1,"2148":1,"2149":1,"2150":1,"2151":1,"2152":1},"3":{"0":4,"211":1,"213":1,"214":2,"215":1,"216":2,"672":1,"676":1,"1108":1,"1109":1,"1111":1,"1112":1,"1183":1,"1189":1,"1212":2,"1323":11,"1335":4,"1338":25,"1390":1,"1394":3,"1436":1,"1440":1,"1449":1,"1495":3,"1524":1,"1525":4,"1534":1,"1576":3,"1584":1,"1585":1,"1589":1,"1590":2,"1659":2,"1669":1,"1778":2,"2010":3,"2011":1,"2145":1,"2146":4,"2147":2,"2148":3,"2149":2,"2151":2,"2152":6,"2159":1,"2207":1,"2231":2}}],["csharp",{"2":{"136":1,"1214":2,"2043":1},"3":{"0":1,"136":2,"1213":1,"1214":2,"1435":3,"2043":2}}],["celebrated",{"3":{"2034":1}}],["celebration",{"3":{"1395":1}}],["cellclass=",{"3":{"1394":1,"1590":1}}],["cellstyle=",{"3":{"1525":1,"1533":1,"1590":1,"1599":1}}],["cells",{"3":{"1310":2,"1401":5,"1435":2}}],["cell",{"3":{"743":2,"1310":4,"1394":3,"1395":1,"1401":1,"1435":2,"1487":2,"1495":2,"1499":1,"1525":1,"1533":1,"1590":2,"1987":1}}],["cede",{"3":{"1079":1}}],["ceding",{"3":{"1078":1}}],["census",{"3":{"474":1,"475":1,"476":1,"797":4,"803":1,"804":1,"805":1,"806":1,"1048":1,"1049":1,"1051":1,"1054":1,"2231":3}}],["cents",{"3":{"1764":1,"1769":1}}],["centric",{"3":{"1358":1,"1435":1,"1539":1,"1576":1}}],["centre",{"3":{"1285":1,"1678":1}}],["centralising",{"3":{"1338":2}}],["centralised",{"3":{"1270":1,"1300":1,"1322":1,"1338":2}}],["centralizing",{"3":{"132":1,"215":1,"750":1,"1229":1,"1237":2,"1247":1,"1270":2,"1285":5,"1299":2,"1308":1,"1310":2,"1322":1,"1343":1,"1349":1,"1358":2,"1379":1,"1394":1,"1395":1,"1401":3,"1404":1,"1414":1,"1416":1,"1435":3,"2022":1,"2151":1}}],["centralize",{"3":{"1285":1,"1335":1,"1861":1,"2152":1}}],["centralizes",{"3":{"60":1,"1338":1,"1395":1,"1416":1,"1430":1,"1435":2,"2022":1,"2146":1}}],["centralized",{"1":{"211":1,"212":1,"213":1,"214":1,"215":1,"216":1,"217":1,"218":1,"219":1,"220":1,"221":1},"3":{"0":2,"211":1,"283":1,"1117":1,"1237":2,"1270":1,"1285":2,"1291":1,"1299":2,"1300":1,"1308":1,"1310":3,"1322":1,"1323":5,"1338":2,"1349":1,"1358":3,"1379":1,"1393":1,"1394":2,"1395":4,"1401":1,"1414":1,"1432":1,"1525":2,"1547":1,"1556":1,"1574":1,"1575":1,"1576":4,"1590":1,"1804":1,"2152":1,"2194":1}}],["centralus",{"2":{"972":1},"3":{"972":1}}],["centrally",{"3":{"31":1,"665":1,"847":1,"1212":1,"1231":2,"1237":2,"1270":1,"1274":1,"1285":2,"1322":1,"1323":3,"1347":1,"1349":2,"1358":1,"1368":1,"1394":1,"1401":1,"1525":1,"1544":1,"1576":2,"1705":1,"1706":2,"1737":1,"1749":1,"1772":1,"1809":1,"2229":1}}],["central",{"3":{"0":2,"186":1,"201":1,"301":1,"465":1,"669":1,"715":1,"800":1,"938":1,"954":2,"956":1,"958":2,"959":2,"960":1,"972":1,"1213":1,"1214":1,"1229":1,"1247":1,"1260":1,"1285":1,"1299":1,"1310":1,"1322":3,"1323":2,"1338":1,"1349":1,"1358":1,"1368":1,"1394":2,"1401":1,"1414":1,"1415":2,"1429":1,"1435":2,"1459":1,"1464":1,"1466":2,"1488":1,"1489":1,"1525":1,"1534":1,"1551":1,"1568":2,"1576":1,"1590":1,"1648":1,"1662":1,"1684":1,"1685":1,"1705":1,"1706":1,"1851":1,"2042":2}}],["centers",{"3":{"2219":1}}],["centered",{"3":{"1479":1,"1668":1}}],["centerpiece",{"3":{"1323":1,"1395":1,"2071":1}}],["center",{"3":{"440":1,"1285":2,"1323":1,"1394":1,"1428":1,"1435":1,"1525":1,"1533":1,"1576":1,"1788":1,"2125":1,"2127":1}}],["ceil",{"2":{"435":1},"3":{"435":1}}],["ceilings",{"3":{"0":3,"591":3,"592":1,"593":2,"595":1,"859":1,"861":1,"862":1,"1125":2,"1126":1,"1212":1,"1322":1,"1358":2,"1415":2,"1430":1,"1435":15,"1437":1,"1533":1,"1534":1,"1576":3,"1587":1,"1590":1,"1596":1,"1598":2,"1667":1,"1672":1,"2046":1,"2047":1,"2231":1}}],["ceiling",{"3":{"0":15,"22":1,"48":1,"150":1,"177":1,"178":1,"233":1,"235":2,"237":3,"330":2,"337":1,"438":1,"442":1,"549":1,"575":1,"591":3,"593":1,"607":3,"609":6,"651":1,"733":3,"736":1,"741":4,"742":1,"743":1,"745":1,"760":1,"764":4,"765":1,"766":1,"768":1,"769":1,"792":1,"827":2,"829":1,"831":2,"832":4,"833":1,"860":1,"861":4,"862":2,"863":1,"997":1,"1016":3,"1018":5,"1019":2,"1020":1,"1042":2,"1067":1,"1074":2,"1075":1,"1076":1,"1090":2,"1092":2,"1094":2,"1095":1,"1096":1,"1099":1,"1100":1,"1122":1,"1124":5,"1125":1,"1126":2,"1128":1,"1129":1,"1186":1,"1212":2,"1228":1,"1229":4,"1238":1,"1243":3,"1244":1,"1247":15,"1270":1,"1271":4,"1275":1,"1285":5,"1288":1,"1296":1,"1299":6,"1300":6,"1305":1,"1308":4,"1309":1,"1310":6,"1311":1,"1322":6,"1323":20,"1333":1,"1338":12,"1349":3,"1358":15,"1365":2,"1367":1,"1368":2,"1370":1,"1379":7,"1394":8,"1395":11,"1414":7,"1415":6,"1416":1,"1417":2,"1419":1,"1421":1,"1426":4,"1435":65,"1437":1,"1446":1,"1454":1,"1462":1,"1464":5,"1466":12,"1468":1,"1469":1,"1488":5,"1491":1,"1495":2,"1525":6,"1533":3,"1576":6,"1581":2,"1582":1,"1583":1,"1585":1,"1590":2,"1596":1,"1598":1,"1663":1,"1665":2,"1667":1,"1669":1,"1670":1,"1706":4,"1707":2,"1816":1,"1922":1,"1928":1,"1986":1,"1987":2,"1990":2,"1999":6,"2000":2,"2001":2,"2026":1,"2046":1,"2047":1,"2125":2,"2144":1,"2153":1,"2171":1,"2173":1,"2179":3,"2181":1,"2187":2,"2189":1,"2231":1}}],["cero",{"3":{"1684":1,"1685":2}}],["ceremony",{"3":{"110":1,"111":1,"683":1,"938":1,"1109":1,"1220":1,"1237":1,"1259":1,"1285":1,"1308":1,"1322":2,"1323":1,"1339":1,"1341":1,"1349":2,"1358":4,"1391":1,"1394":1,"1395":7,"1414":5,"1416":3,"1435":1,"1636":1,"1805":1,"1807":1,"1810":1}}],["certification",{"3":{"2223":1}}],["certifications",{"0":{"2223":1},"3":{"2223":1}}],["certificates",{"3":{"1187":1,"1466":1}}],["certificate",{"3":{"0":1,"418":2,"420":1,"423":1,"424":1,"698":1,"1066":2,"1067":3,"1068":2,"1069":1,"1285":2,"1310":3,"1327":2,"1338":3,"1415":2,"1429":2,"1448":2,"1452":2,"1455":5,"1459":1,"1480":1,"1486":1,"1487":2,"1489":4,"1491":1}}],["certs",{"3":{"1067":1,"1069":1,"1429":1,"1455":1}}],["cert",{"2":{"418":1,"423":1,"424":1,"1480":1},"3":{"418":1,"423":1,"424":1,"1285":1,"1415":3,"1480":1}}],["certainly",{"3":{"1435":2,"1887":1}}],["certainty",{"3":{"1423":1,"1426":1}}],["certain",{"3":{"10":1,"1285":1,"1358":2,"1452":1,"1489":1,"2058":1}}],["cecil",{"3":{"0":1,"27":1,"39":1,"109":1,"1161":1,"1162":1,"1435":18}}],["cqrscontractinspectortests",{"3":{"1199":1,"1207":12,"1266":1,"1270":8,"1437":2}}],["cqrscontractinspector",{"2":{"1266":1,"1661":1},"3":{"1199":2,"1203":1,"1207":4,"1260":1,"1266":12,"1270":18,"1495":1,"1661":1}}],["cqrscontractmismatch",{"2":{"1266":1},"3":{"1199":3,"1203":1,"1207":1,"1260":1,"1266":1,"1270":6}}],["cqrscontractmismatchkind",{"2":{"1260":1,"1266":1},"3":{"1199":4,"1203":1,"1207":1,"1260":1,"1266":1,"1270":7}}],["cqrsmetricstests",{"3":{"1199":1,"1207":5,"1437":1}}],["cqrsmetricsprobequery",{"3":{"1199":2,"1207":1}}],["cqrsmetricsprobecommand",{"3":{"1199":2,"1207":1}}],["cqrsmetrics",{"2":{"126":1,"397":1,"405":1,"1268":1,"2158":1},"3":{"121":2,"126":2,"397":4,"399":1,"405":1,"1199":7,"1203":1,"1207":2,"1260":1,"1263":4,"1268":9,"1270":30,"1300":1,"1437":1,"1495":1,"2155":2,"2158":1}}],["cqrs",{"0":{"1506":1,"1542":1,"1936":1},"1":{"114":1,"115":1,"116":1,"117":1,"118":1,"119":1,"120":1,"121":1,"122":1,"123":1,"124":1,"125":1,"126":1,"127":1,"128":1,"1260":1,"1261":1,"1262":1,"1263":1,"1264":1,"1265":1,"1266":1,"1267":1,"1268":1,"1269":1,"1270":1,"1817":1,"1818":1,"1819":1,"1820":1,"1821":1,"1822":1,"1823":1,"1824":1,"1825":1,"1826":1,"2133":1,"2134":1,"2135":1,"2136":1,"2137":1,"2138":1},"2":{"121":1,"124":1,"126":2,"400":1,"822":1,"823":1,"1268":3,"2005":1,"2012":1,"2155":1,"2159":1},"3":{"0":8,"17":2,"27":4,"77":1,"114":1,"117":1,"121":4,"124":1,"126":2,"127":2,"128":3,"135":3,"139":3,"142":2,"160":2,"186":1,"246":1,"258":1,"299":1,"300":2,"301":1,"306":1,"395":6,"396":1,"397":6,"398":2,"400":1,"404":1,"405":1,"406":1,"407":1,"408":1,"409":1,"545":4,"552":1,"572":2,"578":1,"608":1,"748":1,"819":1,"822":1,"823":1,"827":1,"897":2,"1003":1,"1004":1,"1006":2,"1042":1,"1058":1,"1059":1,"1075":1,"1132":1,"1133":3,"1191":1,"1192":2,"1194":1,"1200":1,"1202":3,"1203":3,"1207":99,"1211":1,"1212":4,"1213":1,"1216":1,"1218":1,"1223":1,"1229":2,"1231":1,"1237":4,"1238":1,"1245":1,"1246":1,"1247":5,"1249":1,"1259":23,"1260":3,"1268":3,"1270":37,"1271":4,"1285":15,"1296":1,"1299":12,"1300":5,"1301":1,"1302":1,"1303":1,"1308":22,"1310":20,"1314":1,"1315":2,"1322":16,"1323":6,"1332":1,"1338":2,"1344":1,"1349":32,"1350":1,"1351":1,"1354":1,"1358":52,"1369":2,"1370":1,"1379":8,"1389":1,"1394":2,"1395":33,"1401":32,"1402":1,"1408":1,"1412":1,"1414":30,"1416":1,"1430":17,"1435":240,"1436":2,"1437":1,"1442":1,"1454":1,"1486":1,"1487":1,"1488":2,"1495":5,"1498":1,"1504":1,"1525":2,"1533":1,"1534":2,"1535":1,"1542":1,"1576":9,"1581":2,"1582":2,"1583":1,"1585":2,"1589":1,"1590":4,"1596":1,"1645":1,"1658":1,"1659":1,"1661":1,"1665":1,"1669":1,"1674":1,"1691":1,"1716":1,"1719":1,"1778":2,"1779":2,"1780":1,"1783":1,"1785":1,"1795":1,"1802":1,"1816":1,"1817":2,"1826":3,"1827":1,"1839":1,"1878":1,"1887":1,"1913":1,"1931":1,"1940":1,"1946":1,"1961":1,"1964":1,"2005":1,"2009":1,"2012":1,"2051":1,"2097":1,"2110":1,"2114":1,"2132":2,"2133":1,"2136":1,"2138":1,"2145":1,"2153":2,"2155":10,"2159":2,"2161":1,"2180":1,"2181":1,"2198":1,"2203":1,"2205":2,"2212":2,"2214":1,"2216":1,"2217":1,"2218":1,"2220":1,"2226":3,"2229":1,"2231":3,"2238":2}}],["clutter",{"3":{"2009":1}}],["clustertraceheadername",{"3":{"1435":1}}],["clustertraceechoheader",{"3":{"1435":1}}],["clusterheader",{"3":{"1435":1}}],["clusterheadername",{"3":{"1338":1}}],["clusterconfig",{"3":{"1338":4}}],["clustered",{"3":{"1331":1,"1338":3,"1442":1,"1495":4,"1514":1,"1798":1}}],["clusterprofile",{"3":{"1199":2,"1207":1}}],["clusterid",{"3":{"838":1,"1337":1,"1338":5}}],["clustering",{"3":{"641":1,"1043":1,"1154":1,"1466":2}}],["clusterrequestoverrides",{"2":{"838":1,"1337":1,"1655":1},"3":{"838":2,"1337":1,"1338":3,"1446":1,"1655":1}}],["clusterrequestdefaults",{"2":{"836":1,"838":1,"1337":1,"1655":1},"3":{"0":1,"836":1,"838":3,"1337":1,"1338":5,"1437":1,"1446":1,"1655":1}}],["clusters",{"3":{"91":1,"827":1,"838":7,"839":1,"841":1,"1286":2,"1299":1,"1337":1,"1338":4,"1437":4,"1446":8,"1495":1,"1655":1,"1778":1}}],["cluster",{"0":{"1337":1},"2":{"234":1},"3":{"0":14,"57":1,"59":1,"91":2,"93":2,"98":4,"100":4,"102":1,"234":2,"235":3,"236":1,"245":1,"259":2,"520":1,"572":4,"578":3,"790":3,"827":10,"829":2,"833":2,"836":2,"838":21,"839":3,"840":2,"841":2,"842":1,"1152":1,"1212":1,"1237":2,"1247":1,"1259":1,"1267":1,"1270":3,"1285":1,"1289":1,"1297":2,"1300":4,"1311":1,"1322":3,"1324":2,"1331":1,"1337":10,"1338":61,"1346":1,"1349":1,"1355":2,"1358":1,"1435":10,"1437":11,"1441":1,"1442":1,"1445":1,"1446":16,"1454":1,"1487":1,"1495":3,"1655":7,"1659":1,"1662":2,"1764":1,"1916":1,"2023":1,"2054":1,"2219":1}}],["cleverness",{"3":{"1908":1}}],["clever",{"3":{"1789":1}}],["cleavestack",{"3":{"2225":1,"2233":2}}],["clearlinkedspeakerasync",{"2":{"1349":1},"3":{"1349":1}}],["clearlocalcacheasync",{"3":{"1323":3}}],["clearly",{"3":{"1259":1,"1285":1,"1338":1,"1416":1,"1435":1,"1726":1,"2127":1}}],["clearmobileitems",{"3":{"1323":1}}],["clearallpools",{"3":{"1489":1}}],["clearauthentication",{"3":{"1428":1,"1435":1,"1487":1}}],["clearasync",{"3":{"1323":5,"1416":9}}],["clearable",{"3":{"248":1,"1300":1}}],["cleardraft",{"3":{"1388":1,"1394":4}}],["cleardirty",{"3":{"1323":1}}],["cleardomainevents",{"2":{"1231":1},"3":{"1231":1,"1237":4,"1285":4,"1809":1}}],["clearest",{"3":{"799":1,"1231":1,"1270":3,"1278":1,"1308":1,"1309":1,"1310":1,"1323":3,"1328":1,"1338":2,"1347":1,"1349":1,"1358":5,"1372":1,"1388":1,"1394":9,"1395":3,"1401":1,"1409":1,"1416":2,"1430":1,"1454":1,"2042":1}}],["cleared",{"3":{"0":2,"21":1,"24":1,"26":1,"219":2,"512":1,"749":1,"751":1,"783":1,"989":1,"1014":1,"1066":1,"1124":1,"1127":1,"1185":1,"1259":1,"1263":1,"1274":2,"1285":4,"1297":1,"1310":3,"1317":1,"1322":4,"1323":7,"1337":1,"1338":2,"1349":3,"1354":1,"1358":4,"1388":1,"1389":1,"1394":19,"1395":3,"1401":2,"1414":6,"1416":1,"1426":1,"1432":1,"1437":1,"1634":1,"1637":2,"1638":2,"1640":1,"1672":1,"1747":2,"1748":1,"1753":1,"1764":1,"1839":1,"1922":1}}],["cleartokensasync",{"2":{"511":1},"3":{"511":1,"1323":5,"1416":2,"1435":2}}],["cleartextport",{"3":{"95":1,"1330":1,"1338":3,"1441":1}}],["cleartext",{"3":{"31":1,"33":1,"53":2,"55":1,"60":1,"61":1,"91":5,"92":3,"94":1,"96":1,"97":7,"98":6,"99":3,"100":3,"101":1,"102":1,"103":2,"235":2,"749":2,"1067":3,"1308":1,"1310":5,"1311":6,"1322":3,"1325":1,"1327":2,"1330":1,"1333":2,"1336":1,"1338":17,"1378":3,"1379":1,"1395":3,"1414":3,"1429":2,"1437":1,"1440":5,"1446":4,"1448":1,"1466":5,"1475":1,"1487":1,"1495":1,"1655":2,"1662":1,"1669":1,"1824":1,"1897":2,"1903":1,"1945":1,"2008":1,"2018":1,"2022":1,"2024":11}}],["clears→nuget",{"3":{"1525":1}}],["clears",{"3":{"0":1,"21":1,"39":1,"123":1,"175":1,"207":1,"220":1,"246":1,"350":1,"355":1,"370":1,"391":1,"397":1,"402":1,"404":1,"556":1,"790":1,"854":1,"857":1,"881":1,"905":1,"997":1,"1091":1,"1184":2,"1229":1,"1231":1,"1237":2,"1259":2,"1267":1,"1273":1,"1285":12,"1288":1,"1292":1,"1293":1,"1299":6,"1300":1,"1308":2,"1310":3,"1322":4,"1323":27,"1332":1,"1338":4,"1348":1,"1349":3,"1358":9,"1372":1,"1379":4,"1389":1,"1394":44,"1395":10,"1401":4,"1406":1,"1409":1,"1414":6,"1416":8,"1432":1,"1435":14,"1437":1,"1442":1,"1487":1,"1525":1,"1562":1,"1610":1,"1633":1,"1638":2,"1640":7,"1746":1,"1747":1,"1750":1,"1752":1,"1763":1,"1764":2,"1766":5,"1768":1,"1775":1,"1902":1,"1917":1,"1919":1,"1921":2,"1922":1,"1923":1,"2009":1,"2078":1,"2156":1,"2159":1,"2190":1,"2196":2}}],["clearing",{"3":{"0":1,"279":1,"500":1,"512":1,"861":1,"1026":1,"1233":1,"1237":1,"1252":1,"1259":1,"1270":1,"1274":2,"1275":1,"1285":3,"1310":3,"1322":1,"1323":5,"1337":1,"1338":4,"1342":1,"1344":1,"1358":3,"1394":4,"1395":2,"1398":1,"1401":1,"1414":3,"1416":1,"1435":3,"1442":1,"1487":1,"1747":2,"1763":1,"1766":1,"1768":1,"1775":1,"2156":1}}],["clear",{"2":{"1839":1},"3":{"0":1,"21":1,"170":1,"207":1,"212":1,"219":1,"243":3,"258":2,"259":1,"261":1,"395":1,"440":1,"499":1,"507":1,"618":1,"707":1,"715":1,"716":1,"733":1,"766":1,"861":1,"972":1,"988":1,"1027":1,"1028":1,"1237":5,"1247":1,"1281":1,"1285":9,"1299":4,"1300":2,"1308":3,"1310":1,"1317":2,"1322":11,"1323":23,"1332":1,"1338":4,"1342":1,"1349":1,"1354":1,"1358":6,"1379":5,"1382":1,"1389":1,"1394":26,"1395":4,"1412":1,"1414":3,"1416":7,"1435":18,"1437":2,"1474":1,"1479":1,"1487":1,"1495":1,"1540":1,"1544":1,"1545":1,"1555":1,"1571":1,"1576":3,"1637":1,"1640":1,"1666":1,"1667":1,"1745":3,"1747":1,"1748":2,"1749":3,"1764":1,"1767":2,"1768":1,"1772":1,"1774":1,"1839":1,"1840":1,"1917":1,"1923":1,"2056":1,"2078":1,"2084":1,"2125":1,"2127":1,"2131":1,"2139":1,"2145":1}}],["cleanest",{"3":{"1322":1,"1323":1,"1349":1,"1368":1,"1415":1,"1416":1,"1799":1,"2031":1,"2033":1,"2077":1}}],["cleaned",{"3":{"19":1,"707":1,"1274":1,"1322":1,"1358":2,"2064":1}}],["cleans",{"3":{"1082":1,"1322":1,"1414":1,"1459":1}}],["cleanly",{"3":{"187":1,"234":1,"266":1,"310":1,"414":1,"641":1,"799":1,"1247":1,"1270":2,"1273":1,"1284":1,"1285":2,"1308":2,"1310":1,"1322":4,"1323":4,"1349":1,"1358":1,"1368":1,"1394":1,"1395":1,"1401":2,"1414":1,"1416":6,"1435":5,"1440":1,"1459":1,"1466":1,"1480":1,"1525":1,"1554":1,"1830":1,"1885":1,"1928":1,"1959":1,"2013":1,"2060":1,"2211":1,"2213":1,"2220":1}}],["cleanuptestcontext",{"3":{"1199":2,"1207":1}}],["cleanupintervalhours",{"2":{"1256":1,"1289":1,"2187":1},"3":{"905":1,"1042":1,"1256":1,"1259":2,"1285":3,"1289":1,"1299":1,"1844":1,"2187":1}}],["cleanup",{"0":{"1356":1},"3":{"0":1,"21":1,"24":1,"39":1,"40":1,"546":1,"699":1,"790":1,"905":1,"1082":1,"1086":2,"1116":1,"1118":1,"1134":1,"1247":1,"1259":3,"1270":3,"1285":11,"1302":1,"1310":2,"1322":3,"1323":2,"1344":1,"1349":6,"1354":1,"1356":1,"1358":11,"1395":1,"1414":6,"1416":1,"1435":4,"1466":1,"1474":4,"1487":1,"1495":3,"1633":3,"1638":1}}],["clean",{"0":{"1503":1,"1539":1},"2":{"1359":1},"3":{"0":1,"54":1,"58":1,"131":1,"135":2,"136":1,"248":1,"282":1,"373":1,"507":1,"512":1,"529":1,"546":1,"757":1,"759":1,"988":1,"1211":3,"1212":1,"1216":1,"1217":1,"1221":1,"1229":3,"1230":1,"1237":3,"1241":1,"1243":1,"1245":1,"1247":4,"1249":1,"1259":3,"1270":5,"1271":1,"1272":1,"1285":20,"1288":1,"1299":6,"1300":8,"1302":2,"1308":10,"1309":5,"1310":13,"1311":1,"1312":1,"1313":1,"1322":9,"1323":10,"1338":2,"1340":2,"1341":1,"1344":1,"1345":1,"1348":1,"1349":7,"1351":2,"1353":1,"1358":40,"1359":2,"1368":7,"1379":6,"1381":1,"1394":4,"1395":21,"1401":1,"1403":2,"1414":15,"1415":3,"1416":9,"1426":1,"1435":55,"1437":3,"1444":4,"1454":5,"1458":1,"1466":2,"1475":1,"1487":1,"1488":3,"1492":1,"1495":23,"1498":1,"1521":1,"1522":1,"1524":1,"1525":4,"1534":1,"1535":1,"1553":1,"1561":1,"1575":1,"1576":4,"1585":1,"1586":1,"1587":1,"1589":1,"1590":1,"1593":1,"1599":1,"1610":1,"1637":1,"1638":1,"1645":1,"1658":1,"1691":1,"1709":1,"1762":1,"1779":2,"1780":1,"1781":1,"1783":1,"1793":2,"1795":1,"1796":1,"1798":1,"1804":1,"1814":1,"1830":1,"1881":1,"1901":1,"1914":1,"1922":1,"1930":1,"1959":1,"1965":1,"2001":1,"2007":1,"2038":1,"2039":2,"2043":2,"2048":1,"2051":1,"2054":1,"2070":1,"2075":1,"2098":1,"2107":1,"2108":1,"2115":1,"2125":3,"2203":1,"2212":2,"2215":1,"2216":1,"2217":1,"2218":1,"2219":1,"2220":1,"2224":1,"2226":1,"2229":1,"2238":3,"2242":1,"2244":1}}],["cldr",{"3":{"1323":2,"1576":1}}],["cl",{"2":{"609":1,"1464":1,"1475":2},"3":{"609":1,"1464":1,"1466":2,"1475":2}}],["clrtype",{"2":{"697":1},"3":{"697":1,"1285":3}}],["clr",{"0":{"1241":1},"3":{"0":1,"24":2,"330":1,"471":1,"633":1,"636":1,"798":1,"799":1,"803":2,"805":1,"848":1,"849":1,"963":2,"964":2,"965":2,"1049":1,"1233":1,"1235":2,"1237":7,"1241":2,"1247":5,"1250":2,"1259":9,"1269":1,"1270":1,"1276":1,"1277":1,"1278":1,"1280":2,"1281":1,"1285":33,"1299":3,"1309":5,"1317":1,"1322":5,"1338":1,"1349":1,"1368":4,"1379":1,"1394":1,"1395":1,"1414":2,"1416":2,"1432":1,"1435":2,"1585":1,"1814":1,"1848":1,"1857":1,"1987":1,"1990":1,"2186":1,"2192":1}}],["cls=",{"3":{"1435":1}}],["cls",{"3":{"0":3,"860":1,"861":6,"863":2,"1212":1,"1432":3,"1435":9,"1525":1,"1559":1,"1581":1,"1590":1,"1595":1,"1670":1,"2078":1}}],["cliff",{"3":{"1891":1}}],["clipped",{"3":{"1652":1}}],["clip",{"3":{"1416":1,"1426":1}}],["clips",{"3":{"1285":1,"1668":1}}],["clipboard",{"2":{"412":1,"2115":1},"3":{"412":2,"1192":1,"1202":1,"1203":1,"1394":1,"1416":22,"1495":1,"1668":1,"2115":2}}],["climbs",{"3":{"1435":1}}],["climb",{"3":{"801":1}}],["climbing",{"3":{"799":1,"801":1,"1049":1,"1464":1}}],["clickjacks",{"3":{"2147":1}}],["clickjacking",{"3":{"1323":1,"1338":1,"2147":1}}],["clickandverifyasync",{"2":{"1670":1},"3":{"1432":1,"1435":1,"1487":1,"1670":1}}],["clickandwaitforurlasync",{"2":{"1432":1},"3":{"1432":1,"1435":1,"1487":1}}],["clickablecard",{"3":{"1323":1}}],["clickablecardtests",{"3":{"1199":1,"1207":2}}],["clickable",{"3":{"1323":1,"1358":1,"1435":2}}],["clickbuttonbytext",{"3":{"1431":1,"1435":2}}],["clicked",{"3":{"608":1,"1289":1,"1394":1,"1395":1,"1414":1,"1435":1,"2033":1,"2157":1}}],["clicks",{"3":{"517":1,"519":1,"743":1,"954":1,"1310":1,"1382":1,"1394":2,"1416":1,"1435":22,"1487":4,"1972":1}}],["clicking",{"3":{"156":1,"905":1,"1299":1,"1323":2,"1388":1,"1401":1,"1435":3,"1462":1,"1487":1}}],["click",{"3":{"0":2,"413":1,"519":1,"520":1,"743":1,"766":1,"861":1,"862":1,"905":1,"907":1,"1020":1,"1299":2,"1323":13,"1338":1,"1358":1,"1391":1,"1394":7,"1395":1,"1401":2,"1414":2,"1416":11,"1432":2,"1435":19,"1455":1,"1473":1,"1487":3,"1553":1,"1557":2,"1564":1,"1626":1,"1628":1,"1965":1,"2116":1,"2120":1}}],["cli",{"3":{"0":1,"665":1,"764":1,"766":1,"790":1,"1334":1,"1338":2,"1440":1,"1454":1,"1456":1,"1457":1,"1463":1,"1464":1,"1471":2,"1473":1,"1474":1,"1681":1,"2033":1,"2165":1}}],["cliente",{"3":{"1685":2}}],["clienteventformatvalidationtests",{"3":{"1199":1,"1207":2}}],["clientwidth",{"3":{"1435":1}}],["clientmodel",{"3":{"1426":1}}],["clientoptions",{"2":{"1419":1},"3":{"1419":1,"1426":2}}],["clienttoken",{"3":{"1358":1}}],["clientname",{"3":{"1338":1}}],["clientnameprefix",{"3":{"1338":1}}],["clientuseragent",{"3":{"1310":2,"1414":1}}],["clienturlvalidationtests",{"3":{"1199":1,"1207":2,"1437":2}}],["clientfactory",{"3":{"1213":1,"1311":2,"1435":5}}],["clientinterceptorcontext",{"3":{"1311":1}}],["clientip",{"3":{"1337":1,"1338":6,"1435":1,"1446":1}}],["clientipaddress",{"3":{"1310":2,"1414":1}}],["clientippartition",{"3":{"827":1,"1323":1,"1336":1,"1338":1}}],["clientid",{"2":{"351":1,"352":1},"3":{"0":1,"350":2,"351":1,"352":1,"1310":6,"1323":3,"1471":2,"1666":1,"1974":4,"1977":1}}],["clientconfigpath",{"3":{"1323":2}}],["clientconfigendpointtests",{"3":{"1199":2,"1207":4,"1323":4,"1436":1,"1486":2}}],["clientconfigendpointextensions",{"2":{"1323":1},"3":{"674":1,"677":1,"1184":1,"1198":1,"1199":3,"1203":1,"1207":2,"1208":1,"1323":14,"1495":1}}],["clientconfigbuilder",{"3":{"1198":1,"1199":2,"1203":1,"1207":2,"1323":11,"1495":1}}],["clientconfig",{"3":{"674":1,"1184":1,"1203":2,"1207":6,"1208":1,"1323":13,"1486":1}}],["clientcascade",{"2":{"1083":1},"3":{"0":1,"1083":1}}],["clientkey",{"2":{"231":1},"3":{"225":1,"231":1,"1247":1}}],["client",{"0":{"1377":1,"2018":1,"2029":1,"2031":1},"1":{"154":1,"155":1,"156":1,"157":1,"158":1,"159":1,"160":1,"161":1,"162":1,"504":1,"505":1,"506":1,"507":1,"508":1,"509":1,"510":1,"511":1,"512":1,"513":1,"514":1,"878":1,"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1},"2":{"407":1,"599":1,"601":1,"603":1,"667":1,"669":1,"1075":1,"1424":1,"1442":1,"1444":1,"1467":1,"1471":2,"1530":1,"1676":1,"2176":1},"3":{"0":40,"53":5,"59":2,"60":1,"66":1,"67":1,"68":1,"70":2,"71":2,"72":5,"73":4,"74":2,"86":1,"92":1,"95":1,"96":1,"97":3,"100":2,"103":1,"104":1,"109":8,"110":1,"111":3,"113":2,"122":1,"131":1,"134":1,"135":2,"136":1,"142":2,"154":1,"156":2,"157":8,"158":1,"159":1,"160":4,"166":2,"168":2,"175":4,"177":1,"179":2,"180":4,"182":1,"194":1,"206":2,"213":1,"225":2,"229":1,"230":1,"231":3,"233":1,"234":2,"235":3,"237":2,"241":2,"243":2,"245":4,"247":1,"258":3,"259":2,"261":3,"264":1,"265":5,"267":2,"272":1,"279":1,"280":3,"285":1,"332":1,"333":2,"335":1,"340":2,"341":6,"342":4,"343":3,"348":1,"350":1,"353":1,"379":2,"380":1,"381":2,"382":1,"383":1,"384":2,"387":1,"389":1,"398":1,"402":1,"404":1,"406":1,"407":3,"408":1,"418":1,"420":2,"423":1,"425":1,"430":1,"433":2,"434":4,"435":1,"436":1,"439":1,"440":1,"443":1,"447":2,"449":3,"455":1,"498":1,"499":2,"500":2,"501":2,"504":1,"506":1,"507":6,"508":1,"509":1,"510":1,"536":1,"545":1,"551":1,"554":1,"555":4,"556":7,"557":2,"558":1,"559":1,"560":1,"571":1,"572":5,"583":2,"591":1,"598":1,"599":5,"601":1,"603":1,"608":1,"612":1,"638":1,"640":2,"649":1,"650":1,"665":3,"667":1,"669":1,"674":2,"677":1,"684":2,"740":1,"741":1,"742":1,"743":2,"749":2,"750":1,"773":1,"774":1,"789":1,"792":1,"795":1,"817":1,"822":1,"826":3,"827":10,"829":1,"833":2,"837":1,"860":3,"861":1,"865":2,"876":5,"878":1,"879":1,"880":6,"881":13,"882":6,"883":2,"884":1,"885":4,"897":1,"904":1,"905":2,"907":2,"910":2,"914":1,"922":1,"954":1,"988":1,"996":2,"998":1,"1016":1,"1017":1,"1018":5,"1020":1,"1034":2,"1035":2,"1050":1,"1052":1,"1059":1,"1067":2,"1075":1,"1082":1,"1085":1,"1089":1,"1090":1,"1091":6,"1092":3,"1093":3,"1112":1,"1116":1,"1124":4,"1125":1,"1126":2,"1182":1,"1183":2,"1184":5,"1185":1,"1186":1,"1187":1,"1189":1,"1192":2,"1199":1,"1202":1,"1203":2,"1206":1,"1207":3,"1212":12,"1213":1,"1217":1,"1218":1,"1225":3,"1227":1,"1228":1,"1229":9,"1237":2,"1239":1,"1241":5,"1242":2,"1243":1,"1244":1,"1247":26,"1259":1,"1268":1,"1270":3,"1271":2,"1284":2,"1285":20,"1289":2,"1293":1,"1294":1,"1297":1,"1299":45,"1300":4,"1301":2,"1304":1,"1305":1,"1307":2,"1308":46,"1310":92,"1311":34,"1313":2,"1314":1,"1315":2,"1322":34,"1323":222,"1326":1,"1327":2,"1331":2,"1332":1,"1333":1,"1334":1,"1335":3,"1336":1,"1337":3,"1338":67,"1346":3,"1348":1,"1349":28,"1351":3,"1352":1,"1354":2,"1356":1,"1357":3,"1358":76,"1359":1,"1364":2,"1365":5,"1368":12,"1369":1,"1370":1,"1377":3,"1379":37,"1383":4,"1386":1,"1387":1,"1388":2,"1394":97,"1395":110,"1396":1,"1401":48,"1403":1,"1405":2,"1407":1,"1410":1,"1412":1,"1413":2,"1414":58,"1415":40,"1416":34,"1419":2,"1420":5,"1421":2,"1422":1,"1424":4,"1426":34,"1428":2,"1431":2,"1432":1,"1434":4,"1435":76,"1436":4,"1437":22,"1440":6,"1442":3,"1443":1,"1444":2,"1446":9,"1454":4,"1455":1,"1459":1,"1460":1,"1466":14,"1467":1,"1471":5,"1476":1,"1486":1,"1487":5,"1488":1,"1489":1,"1490":3,"1491":2,"1495":6,"1502":1,"1525":13,"1530":3,"1531":3,"1533":2,"1544":1,"1553":1,"1555":2,"1559":3,"1560":2,"1562":5,"1563":1,"1571":3,"1576":4,"1584":1,"1585":2,"1588":1,"1590":4,"1594":1,"1595":5,"1596":2,"1625":1,"1626":2,"1630":4,"1637":1,"1638":5,"1639":4,"1640":6,"1650":1,"1652":5,"1655":2,"1661":2,"1662":4,"1665":1,"1666":2,"1667":3,"1668":1,"1669":1,"1670":1,"1676":1,"1684":3,"1701":5,"1706":1,"1719":1,"1748":2,"1764":1,"1772":2,"1789":4,"1804":2,"1814":1,"1830":2,"1839":1,"1855":1,"1867":1,"1869":1,"1870":2,"1871":3,"1872":2,"1873":2,"1874":1,"1875":4,"1876":2,"1881":2,"1883":1,"1904":1,"1905":4,"1907":7,"1908":2,"1910":4,"1911":3,"1917":1,"1920":1,"1924":2,"1925":2,"1926":3,"1929":1,"1933":1,"1934":1,"1935":2,"1936":1,"1941":2,"1943":3,"1944":2,"1945":1,"1948":1,"1949":2,"1965":3,"1967":3,"1971":4,"1972":1,"1974":4,"1975":1,"1978":2,"1979":2,"1980":1,"1981":2,"1983":1,"1984":1,"1985":1,"1986":1,"1987":1,"1989":1,"1997":1,"1999":2,"2000":1,"2001":2,"2008":1,"2009":1,"2014":1,"2016":2,"2017":1,"2018":3,"2021":1,"2022":1,"2023":4,"2027":2,"2028":2,"2029":4,"2030":3,"2031":1,"2037":3,"2042":1,"2043":1,"2054":4,"2073":1,"2076":1,"2077":2,"2078":1,"2079":2,"2080":1,"2081":1,"2082":2,"2085":1,"2093":1,"2103":2,"2106":1,"2123":2,"2125":4,"2128":3,"2129":2,"2130":2,"2131":1,"2132":3,"2146":1,"2150":1,"2155":2,"2157":1,"2162":1,"2165":1,"2166":1,"2169":2,"2172":1,"2173":1,"2174":1,"2176":1,"2179":1,"2231":5,"2238":1}}],["clientstreamingserverhandler",{"3":{"1311":1}}],["clientstate",{"3":{"1310":4}}],["clientstateitemkey",{"3":{"1310":2}}],["clientstamped",{"3":{"1175":2}}],["clientsecret",{"3":{"350":1,"1310":1,"1974":1}}],["clientsetnull",{"2":{"1082":1,"1085":1},"3":{"0":1,"1082":1,"1085":1}}],["clients",{"0":{"100":1},"2":{"1329":1},"3":{"0":5,"53":2,"57":1,"59":2,"60":1,"63":1,"66":1,"74":1,"97":1,"98":3,"135":1,"157":1,"174":1,"225":2,"314":1,"380":1,"383":1,"387":1,"560":1,"583":1,"586":1,"587":1,"638":2,"640":1,"645":1,"657":1,"773":1,"823":1,"881":1,"882":1,"1077":1,"1091":2,"1184":1,"1192":2,"1202":1,"1203":1,"1212":3,"1213":1,"1247":1,"1285":4,"1288":1,"1295":1,"1299":6,"1300":1,"1301":1,"1306":5,"1308":13,"1310":13,"1311":1,"1315":1,"1319":1,"1322":14,"1323":5,"1325":1,"1328":3,"1329":1,"1338":9,"1349":1,"1352":1,"1358":5,"1378":1,"1379":4,"1394":4,"1395":15,"1398":1,"1401":4,"1405":1,"1409":1,"1413":1,"1414":9,"1417":1,"1426":1,"1435":8,"1437":2,"1440":4,"1442":1,"1446":4,"1449":1,"1466":1,"1489":1,"1495":1,"1508":1,"1537":1,"1554":1,"1590":1,"1629":2,"1630":2,"1637":1,"1638":4,"1639":3,"1640":11,"1659":2,"1667":1,"1668":2,"1670":1,"1771":1,"1789":2,"1905":1,"1906":1,"1910":1,"1911":2,"1931":1,"1934":3,"1943":1,"1945":1,"1949":1,"2008":1,"2018":1,"2022":2,"2024":2,"2027":1,"2030":1,"2036":1,"2037":1,"2082":1,"2110":1,"2169":1,"2213":1,"2220":2,"2226":1,"2231":1}}],["clobbering",{"3":{"1323":1,"1869":1}}],["clobbers",{"3":{"1323":1,"2071":1}}],["clobber",{"3":{"1322":2,"1358":1,"1414":1,"1428":1,"1429":1,"1435":2,"1489":1}}],["cloning",{"3":{"820":1,"1285":1}}],["cloned",{"3":{"1091":1}}],["clones",{"3":{"698":1,"1285":5,"1426":1,"1452":1,"1850":1}}],["clone",{"3":{"468":1,"657":1,"1285":4,"1358":1,"1421":1,"1463":1,"2002":1,"2003":1,"2173":1}}],["clothes",{"3":{"341":1}}],["cloud",{"0":{"1445":1,"2228":1},"3":{"0":1,"175":1,"177":1,"219":1,"397":1,"405":1,"641":1,"749":1,"972":1,"1011":1,"1043":1,"1124":1,"1213":1,"1308":1,"1310":2,"1337":1,"1338":2,"1349":1,"1363":3,"1368":4,"1438":1,"1441":1,"1449":1,"1456":1,"1466":2,"1553":1,"1567":1,"1569":1,"1574":1,"1576":1,"1669":1,"1706":3,"1935":1,"2009":2,"2012":1,"2026":1,"2127":1,"2153":2,"2155":1,"2159":1,"2184":2,"2192":1,"2211":1,"2212":3,"2213":1,"2215":1,"2216":1,"2217":2,"2218":1,"2219":2,"2220":1,"2227":1,"2237":1,"2238":2,"2239":2}}],["closures",{"3":{"1308":2,"1435":1}}],["closure",{"3":{"122":1,"135":2,"465":1,"466":1,"1067":1,"1222":1,"1229":2,"1247":3,"1259":1,"1270":1,"1285":2,"1300":1,"1310":1,"1322":2,"1323":1,"1338":2,"1358":1,"1394":1,"1416":1,"1435":8}}],["closing",{"3":{"0":3,"122":1,"217":1,"252":1,"254":1,"265":1,"390":1,"392":1,"458":1,"492":1,"523":1,"603":1,"607":1,"664":1,"699":1,"764":1,"856":1,"872":1,"897":1,"920":2,"1033":1,"1052":1,"1108":1,"1229":1,"1239":1,"1247":2,"1258":1,"1270":2,"1285":1,"1299":3,"1310":2,"1322":5,"1323":4,"1339":1,"1341":1,"1349":2,"1358":7,"1379":1,"1391":1,"1394":4,"1401":5,"1414":3,"1416":1,"1426":1,"1435":9,"1449":1,"1454":1,"1466":1,"1495":1,"1523":1,"1576":2,"1636":1,"1640":1,"1701":1,"1844":1,"1850":1,"1922":1,"1945":1,"2033":1,"2078":1,"2084":1,"2162":1}}],["closepollasync",{"3":{"1401":2}}],["closeasync",{"3":{"1395":2,"1401":1}}],["closely",{"3":{"1270":1,"1299":1,"1310":2,"1319":1,"1338":1,"1349":1,"1354":1,"1358":3,"1394":1,"1395":1,"1405":1,"1416":2,"1420":1,"1435":2,"1440":1,"1454":1,"1455":1}}],["closelivepollcommand",{"3":{"1199":6,"1203":1,"1207":2,"1401":11}}],["closelivepollhandlertests",{"3":{"1199":1,"1207":3}}],["closelivepollhandler",{"2":{"522":1,"1398":1},"3":{"522":2,"523":2,"1100":2,"1199":2,"1203":1,"1207":2,"1270":2,"1349":2,"1395":4,"1398":6,"1401":17}}],["closer",{"3":{"915":1,"1779":1}}],["close",{"3":{"0":5,"21":1,"26":1,"99":1,"122":1,"197":1,"265":1,"273":1,"320":1,"329":1,"335":1,"342":1,"350":1,"390":1,"491":1,"507":1,"522":1,"523":1,"558":1,"642":1,"832":1,"837":1,"840":1,"848":1,"916":1,"921":1,"926":1,"988":2,"996":1,"1061":1,"1075":1,"1100":1,"1108":1,"1123":1,"1124":2,"1125":1,"1128":1,"1203":2,"1207":4,"1247":1,"1259":1,"1262":1,"1265":1,"1270":9,"1271":1,"1277":1,"1283":1,"1285":3,"1291":1,"1299":5,"1300":1,"1308":1,"1310":1,"1311":2,"1322":3,"1323":13,"1337":1,"1338":1,"1349":1,"1354":1,"1358":9,"1378":1,"1379":1,"1382":1,"1394":7,"1395":9,"1397":1,"1398":1,"1400":1,"1401":38,"1414":4,"1415":1,"1426":1,"1435":12,"1437":1,"1440":1,"1447":1,"1448":1,"1458":2,"1466":1,"1487":2,"1488":1,"1489":1,"1495":1,"1525":1,"1533":2,"1575":1,"1581":1,"1606":1,"1631":2,"1740":1,"1871":1,"1901":1,"1933":1,"1976":1,"1977":1,"1986":1,"1990":1,"1997":1,"1999":3,"2054":1,"2084":1,"2201":1}}],["closedrawer",{"3":{"1590":1}}],["closedgenericarguments",{"3":{"1435":1}}],["closedhandlertype",{"3":{"1259":3}}],["closed",{"0":{"870":1,"1235":1},"1":{"1105":1,"1106":1,"1107":1,"1108":1,"1109":1,"1110":1,"1111":1,"1112":1},"2":{"384":1},"3":{"0":18,"24":1,"76":1,"80":2,"109":1,"122":1,"135":1,"195":1,"201":1,"212":1,"214":1,"215":1,"216":1,"224":1,"233":2,"235":2,"237":2,"267":1,"275":1,"298":1,"305":1,"316":1,"324":1,"325":2,"326":1,"341":1,"368":1,"373":1,"374":1,"384":1,"414":1,"421":1,"423":1,"424":1,"459":2,"461":1,"463":1,"501":1,"512":1,"528":1,"530":1,"531":1,"535":1,"545":1,"549":1,"562":1,"566":1,"567":1,"572":2,"574":2,"591":1,"599":1,"624":1,"626":1,"628":1,"629":1,"657":1,"659":1,"698":1,"699":1,"700":1,"743":1,"759":1,"768":1,"776":2,"784":1,"833":1,"834":1,"840":1,"867":3,"868":1,"871":1,"872":2,"873":1,"906":1,"912":1,"922":4,"926":1,"963":1,"964":3,"988":1,"1018":1,"1042":1,"1050":2,"1052":1,"1059":1,"1091":2,"1105":1,"1108":3,"1169":1,"1176":1,"1177":1,"1178":1,"1212":1,"1223":1,"1229":7,"1234":1,"1235":2,"1237":14,"1240":1,"1241":3,"1247":13,"1259":5,"1263":2,"1264":2,"1265":2,"1267":2,"1268":1,"1270":44,"1271":5,"1273":1,"1275":1,"1278":1,"1285":18,"1296":1,"1299":21,"1300":1,"1306":1,"1308":8,"1309":2,"1310":23,"1311":1,"1315":1,"1317":1,"1322":22,"1323":18,"1333":1,"1338":4,"1349":14,"1351":2,"1352":1,"1353":2,"1357":1,"1358":120,"1365":1,"1368":1,"1371":1,"1379":10,"1382":1,"1383":1,"1394":20,"1395":18,"1397":1,"1398":1,"1400":1,"1401":30,"1402":1,"1403":1,"1405":1,"1414":24,"1416":4,"1420":1,"1421":1,"1422":2,"1426":6,"1435":32,"1437":5,"1441":1,"1446":1,"1452":1,"1454":2,"1458":3,"1466":1,"1488":1,"1495":1,"1525":4,"1530":1,"1574":2,"1576":1,"1581":1,"1585":2,"1589":1,"1590":4,"1596":1,"1599":1,"1629":1,"1631":2,"1652":1,"1669":1,"1685":2,"1700":1,"1701":2,"1706":1,"1770":1,"1814":1,"1822":2,"1829":1,"1850":2,"1911":1,"1926":1,"1961":1,"1962":1,"1983":1,"1984":1,"1988":1,"1994":1,"1995":1,"1996":1,"1999":1,"2037":1,"2045":1,"2046":1,"2135":1,"2139":1,"2149":1,"2151":1,"2152":2,"2160":2,"2171":1,"2174":1,"2178":1,"2179":2,"2183":1,"2189":1,"2192":1,"2200":2,"2202":1,"2231":1}}],["closesentinel",{"3":{"1323":1}}],["closest",{"3":{"1263":1,"1270":3,"1310":1,"1322":1,"1820":1}}],["closes",{"3":{"0":6,"40":1,"42":1,"69":1,"110":1,"121":1,"197":1,"201":1,"208":1,"230":1,"265":1,"284":2,"305":1,"318":1,"331":1,"350":1,"360":1,"365":1,"381":1,"387":1,"418":1,"459":1,"474":1,"538":1,"539":1,"599":1,"609":2,"612":1,"702":2,"741":1,"755":1,"757":1,"798":1,"827":2,"829":2,"832":1,"839":1,"893":1,"916":1,"1018":1,"1028":1,"1053":1,"1124":2,"1125":1,"1128":1,"1229":2,"1237":2,"1241":1,"1247":7,"1253":1,"1259":8,"1267":1,"1270":5,"1271":1,"1278":1,"1280":1,"1285":10,"1299":9,"1300":1,"1306":1,"1308":1,"1310":5,"1315":2,"1322":9,"1323":4,"1326":1,"1333":1,"1336":1,"1338":9,"1349":3,"1356":1,"1358":23,"1379":4,"1388":1,"1394":7,"1395":7,"1398":1,"1401":7,"1414":7,"1415":2,"1416":4,"1419":1,"1429":2,"1434":1,"1435":30,"1437":4,"1444":1,"1445":1,"1452":1,"1455":1,"1458":1,"1464":1,"1466":4,"1473":1,"1475":1,"1487":1,"1491":2,"1581":1,"1599":1,"1631":1,"1640":1,"1650":1,"1789":1,"1842":1,"1843":1,"1870":1,"1885":1,"1896":1,"1898":1,"1908":1,"1919":1,"1921":1,"1928":1,"1929":1,"1944":1,"1949":1,"1967":1,"1971":2,"1978":1,"1999":1,"2001":2,"2023":1,"2026":1,"2035":1,"2055":1,"2064":1,"2068":1,"2142":1,"2145":1,"2177":1}}],["clockreadfixtures",{"3":{"1207":18,"1435":26}}],["clockreadtests",{"3":{"1161":3,"1199":2,"1207":5,"1430":1,"1435":20,"1576":1,"1590":1,"1598":1}}],["clockreadtestsbase",{"3":{"0":1,"1161":4,"1162":1,"1199":3,"1207":2,"1430":3,"1435":9}}],["clockreadsin",{"3":{"1435":1}}],["clockreads",{"3":{"1160":1,"1161":3,"1162":2,"1163":2,"1207":1,"1430":1,"1435":2}}],["clocks",{"3":{"1028":1,"1323":1,"1395":1}}],["clock",{"0":{"1947":1},"1":{"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1},"3":{"0":4,"19":1,"39":1,"62":1,"178":1,"255":1,"305":1,"386":2,"387":2,"390":1,"392":2,"396":1,"590":1,"640":1,"711":1,"724":1,"726":1,"727":1,"860":1,"905":1,"954":1,"1028":1,"1029":1,"1090":1,"1117":1,"1158":1,"1160":2,"1161":3,"1163":4,"1212":1,"1237":1,"1249":1,"1254":1,"1256":3,"1259":8,"1269":1,"1285":8,"1292":1,"1299":16,"1305":1,"1308":7,"1310":2,"1316":1,"1322":4,"1323":14,"1331":1,"1338":2,"1341":1,"1346":1,"1347":1,"1349":11,"1355":2,"1358":15,"1363":1,"1368":2,"1378":1,"1379":1,"1394":1,"1395":16,"1401":2,"1416":3,"1417":1,"1421":1,"1424":1,"1426":2,"1430":2,"1435":35,"1437":9,"1466":1,"1485":1,"1491":1,"1495":2,"1534":1,"1576":2,"1581":1,"1584":1,"1585":2,"1598":1,"1599":2,"1639":1,"1663":1,"1670":1,"1730":1,"1749":1,"1921":1,"1936":1,"1947":1,"1949":2,"2046":1,"2065":1,"2153":1,"2173":1,"2179":1,"2182":1,"2231":1}}],["clarify",{"3":{"1635":1,"1754":1,"1775":3}}],["clarification",{"3":{"1638":1,"1640":2}}],["clarifications",{"0":{"1638":1},"3":{"337":1,"1638":2}}],["clarifies",{"3":{"1638":18,"1639":1}}],["clarified",{"3":{"3":1,"45":1}}],["clarity",{"3":{"1368":1,"1416":1}}],["clash",{"3":{"1237":1,"1349":1}}],["class=",{"3":{"1415":1}}],["classname",{"3":{"1285":6}}],["classreference",{"2":{"1318":1,"1353":1},"3":{"1199":27,"1203":16,"1207":16,"1310":5,"1312":1,"1318":2,"1322":16,"1340":1,"1349":6,"1353":1,"1358":5,"1368":4,"1376":1,"1379":3,"1395":17,"1403":1,"1414":23,"1495":1}}],["classifiable",{"3":{"1229":1}}],["classifications",{"3":{"1243":1,"1247":2,"1661":1}}],["classification",{"0":{"1219":1},"3":{"688":1,"1091":1,"1217":1,"1219":2,"1221":1,"1225":1,"1229":4,"1230":1,"1247":7,"1270":2,"1271":4,"1285":6,"1309":7,"1349":1,"1358":8,"1368":1,"1394":1,"1395":2,"1432":1,"1435":2,"1487":1,"1516":1,"1629":3,"1636":3,"1668":1,"1763":1,"1768":1,"1804":2,"1866":1,"2061":1,"2062":1}}],["classifiers",{"3":{"1367":1}}],["classifier",{"3":{"593":1,"1019":1,"1243":1,"1285":3,"1322":2,"1338":1,"1401":1,"1422":1,"1435":7,"1452":2,"1454":5,"1487":1,"1491":1,"1495":17,"1530":1,"1629":1}}],["classifies",{"0":{"1222":1},"3":{"100":1,"564":1,"626":1,"893":1,"988":1,"1229":3,"1245":1,"1247":2,"1283":2,"1285":7,"1309":1,"1310":3,"1323":1,"1338":1,"1358":3,"1394":1,"1395":2,"1424":1,"1435":3,"1452":1,"1454":1,"1839":1,"1867":1}}],["classified",{"3":{"10":2,"109":1,"110":1,"539":1,"1006":1,"1091":1,"1184":1,"1229":1,"1260":1,"1266":1,"1270":1,"1285":1,"1288":1,"1299":2,"1303":1,"1308":1,"1309":1,"1323":1,"1358":3,"1390":1,"1394":1,"1442":1,"1566":1}}],["classifystatus",{"3":{"1358":11}}],["classifyentries",{"3":{"1285":4}}],["classifynavigationproperty",{"3":{"1247":1,"1309":1}}],["classifying",{"3":{"109":1,"110":1,"1247":1,"1285":5,"1309":1,"1349":1,"1358":2,"1392":1,"1395":2,"1435":1,"1444":1,"2068":1}}],["classify",{"0":{"1864":1},"2":{"1494":1,"1500":1},"3":{"0":1,"107":1,"620":1,"692":1,"1191":1,"1222":2,"1229":1,"1247":1,"1285":5,"1299":1,"1323":1,"1338":1,"1349":2,"1358":1,"1394":1,"1432":1,"1435":4,"1466":1,"1487":1,"1494":1,"1495":22,"1497":1,"1500":1,"1523":1,"1525":1,"1806":1,"1864":1,"1939":1}}],["classic",{"3":{"0":3,"31":1,"208":1,"481":1,"801":1,"828":1,"963":1,"979":1,"980":7,"981":1,"982":1,"1212":2,"1229":1,"1237":2,"1247":1,"1270":1,"1299":3,"1310":4,"1322":1,"1323":5,"1338":1,"1349":1,"1358":1,"1379":1,"1394":2,"1395":1,"1414":1,"1415":1,"1416":5,"1426":1,"1435":7,"1440":1,"1466":2,"1487":1,"1584":1,"1651":1,"1663":1,"1795":1,"1896":1,"1992":1,"2006":1,"2044":1}}],["classes",{"0":{"1987":1},"1":{"1056":1,"1057":1,"1058":1,"1059":1,"1060":1,"1061":1,"1062":1,"1063":1},"3":{"0":10,"4":1,"7":1,"47":1,"48":1,"130":1,"132":1,"135":1,"136":1,"137":1,"140":1,"160":1,"314":1,"511":1,"513":1,"591":1,"616":5,"618":2,"657":1,"672":1,"674":4,"676":1,"677":1,"728":1,"794":1,"926":1,"954":2,"960":1,"980":1,"983":1,"1056":1,"1117":1,"1168":2,"1175":1,"1192":2,"1202":1,"1203":1,"1211":1,"1212":2,"1214":1,"1229":1,"1234":1,"1237":5,"1247":1,"1259":1,"1270":1,"1271":5,"1279":1,"1285":13,"1299":3,"1308":2,"1309":2,"1310":1,"1311":3,"1315":1,"1320":1,"1322":11,"1323":6,"1329":1,"1338":4,"1339":1,"1349":5,"1351":1,"1358":9,"1359":1,"1362":1,"1368":3,"1384":1,"1394":2,"1395":5,"1398":1,"1415":2,"1416":5,"1427":3,"1430":1,"1432":1,"1435":39,"1436":1,"1437":2,"1442":1,"1449":1,"1486":1,"1487":5,"1488":7,"1495":16,"1496":4,"1497":2,"1499":1,"1522":1,"1524":1,"1525":3,"1530":1,"1533":1,"1534":1,"1537":1,"1538":1,"1550":1,"1576":3,"1581":1,"1582":1,"1587":1,"1589":1,"1590":5,"1599":2,"1652":1,"1653":1,"1661":2,"1665":1,"1684":1,"1781":1,"1807":1,"1809":1,"1810":1,"1849":1,"1853":1,"1930":2,"1950":2,"1955":1,"1956":1,"1957":1,"1958":1,"1985":1,"1990":1,"2041":2,"2054":1,"2059":1,"2069":1,"2079":1,"2087":1,"2100":1,"2101":1,"2103":1,"2113":1,"2125":1,"2137":2,"2177":1,"2202":1,"2231":1}}],["class",{"0":{"1273":1,"1360":1,"1848":1},"1":{"1031":1,"1032":1,"1033":1,"1034":1,"1035":1,"1036":1,"1037":1,"1038":1},"2":{"1318":1,"1618":1,"1993":1},"3":{"0":11,"5":1,"6":1,"7":1,"11":1,"24":1,"45":2,"47":3,"53":1,"138":1,"150":1,"164":1,"167":1,"189":1,"214":1,"216":1,"230":1,"233":3,"235":3,"236":1,"237":1,"243":1,"253":1,"258":3,"264":1,"265":1,"283":2,"305":2,"318":3,"322":1,"326":1,"340":1,"343":1,"350":1,"364":1,"412":1,"428":1,"446":1,"448":2,"450":1,"455":1,"459":2,"463":3,"464":1,"492":1,"497":1,"511":1,"516":1,"523":1,"524":1,"534":1,"536":2,"537":2,"543":1,"545":2,"554":1,"555":1,"557":1,"573":1,"610":1,"616":1,"618":5,"619":1,"621":1,"633":2,"635":1,"639":1,"640":2,"657":2,"659":3,"660":1,"672":1,"674":5,"675":3,"676":1,"677":1,"681":1,"682":1,"715":1,"720":1,"724":2,"725":2,"726":2,"733":2,"741":1,"776":1,"793":2,"794":1,"797":1,"798":1,"801":1,"805":1,"811":1,"819":1,"821":1,"826":1,"827":1,"831":1,"832":2,"847":1,"855":1,"861":1,"862":1,"880":1,"882":1,"889":1,"893":1,"905":1,"910":1,"923":1,"926":1,"955":2,"963":2,"974":1,"979":1,"980":2,"989":2,"990":1,"1006":1,"1031":1,"1032":1,"1033":2,"1034":3,"1035":2,"1048":1,"1051":1,"1061":2,"1067":1,"1075":1,"1083":1,"1085":1,"1087":1,"1102":1,"1133":1,"1137":1,"1146":1,"1156":1,"1167":3,"1168":3,"1170":2,"1171":1,"1173":1,"1175":2,"1176":2,"1177":2,"1179":1,"1185":1,"1187":1,"1191":1,"1201":1,"1203":1552,"1204":1,"1205":1,"1207":4145,"1212":7,"1214":2,"1215":1,"1221":1,"1229":14,"1231":1,"1233":1,"1234":1,"1236":1,"1237":54,"1239":3,"1240":1,"1241":1,"1244":1,"1247":85,"1249":1,"1257":1,"1258":1,"1259":68,"1262":1,"1265":1,"1269":1,"1270":76,"1271":32,"1273":4,"1275":1,"1276":1,"1277":1,"1278":1,"1279":1,"1285":289,"1287":1,"1290":2,"1294":1,"1296":1,"1299":165,"1300":24,"1303":1,"1304":1,"1308":91,"1309":14,"1310":160,"1311":21,"1315":1,"1316":1,"1318":2,"1319":2,"1320":2,"1322":184,"1323":291,"1325":1,"1328":1,"1329":1,"1338":155,"1343":2,"1345":1,"1349":124,"1351":2,"1352":1,"1353":1,"1358":582,"1360":3,"1362":2,"1364":1,"1365":2,"1368":86,"1370":1,"1371":1,"1372":5,"1373":1,"1374":1,"1375":1,"1376":1,"1379":112,"1380":1,"1382":1,"1384":1,"1391":1,"1394":266,"1395":329,"1401":102,"1403":1,"1404":2,"1405":4,"1406":1,"1409":1,"1411":1,"1413":1,"1414":205,"1415":45,"1416":164,"1426":30,"1428":2,"1431":3,"1432":1,"1435":787,"1436":4,"1437":5,"1440":3,"1442":3,"1443":1,"1452":3,"1453":1,"1466":3,"1485":1,"1487":10,"1488":9,"1490":3,"1495":9,"1497":1,"1509":2,"1525":3,"1533":2,"1537":1,"1565":1,"1567":1,"1570":1,"1576":3,"1590":1,"1596":1,"1612":1,"1618":1,"1640":2,"1650":2,"1653":3,"1663":2,"1664":1,"1667":1,"1671":1,"1674":1,"1684":2,"1685":1,"1696":1,"1700":1,"1712":1,"1713":1,"1718":1,"1733":1,"1775":1,"1788":1,"1793":1,"1795":1,"1804":3,"1807":1,"1809":2,"1810":2,"1812":1,"1825":1,"1830":1,"1833":2,"1846":1,"1848":5,"1852":1,"1853":2,"1855":5,"1856":4,"1860":1,"1861":2,"1864":1,"1867":1,"1888":1,"1895":1,"1908":2,"1940":1,"1943":1,"1951":1,"1954":3,"1955":2,"1957":3,"1961":1,"1986":1,"1993":1,"1994":5,"1995":1,"1996":1,"2001":1,"2011":1,"2016":1,"2041":1,"2042":1,"2053":1,"2057":2,"2065":1,"2069":1,"2072":1,"2073":1,"2076":1,"2086":1,"2098":1,"2103":1,"2104":1,"2106":1,"2115":1,"2121":1,"2130":2,"2131":1,"2136":1,"2138":1,"2149":1,"2155":1,"2162":3,"2164":5,"2166":2,"2168":1,"2177":1,"2186":1,"2193":1,"2197":1,"2198":1,"2214":1,"2231":4}}],["clamping",{"3":{"740":1,"1017":1,"1395":4,"1416":2,"1426":2,"2173":1}}],["clamp",{"2":{"1242":1},"3":{"330":1,"331":1,"337":1,"744":1,"1019":1,"1229":1,"1242":1,"1243":1,"1244":2,"1247":11,"1291":1,"1299":1,"1305":1,"1308":2,"1310":1,"1368":3,"1395":7,"1414":5,"1426":1,"1437":1,"1495":1,"1576":3,"1585":1,"1985":1,"1987":1,"1990":2,"2179":1}}],["clamped",{"3":{"279":1,"337":1,"682":1,"741":1,"1018":2,"1090":1,"1094":1,"1228":1,"1229":3,"1243":1,"1247":2,"1256":1,"1259":2,"1275":1,"1291":1,"1308":2,"1310":3,"1323":1,"1365":1,"1395":8,"1414":2,"1416":1,"1417":1,"1434":1,"1437":2,"1816":1,"2173":1}}],["clamps",{"3":{"0":1,"330":3,"337":1,"549":1,"741":1,"744":1,"1018":1,"1091":1,"1092":1,"1094":1,"1242":1,"1245":1,"1247":1,"1285":1,"1305":1,"1310":2,"1365":1,"1368":1,"1379":2,"1394":1,"1395":2,"1405":1,"1408":1,"1414":1,"1421":1,"1426":3,"1987":1,"2200":1}}],["claude",{"0":{"1458":1},"2":{"636":1,"1003":1,"1190":1,"1450":1,"1458":1,"1459":1,"1570":1,"1657":1,"2048":1,"2059":1},"3":{"1":1,"469":1,"550":1,"599":3,"626":2,"636":1,"766":1,"1003":1,"1004":2,"1018":1,"1190":1,"1192":1,"1211":1,"1270":2,"1285":3,"1299":6,"1310":1,"1322":1,"1338":3,"1365":1,"1368":1,"1379":1,"1414":1,"1435":15,"1439":1,"1450":2,"1451":1,"1452":2,"1453":1,"1458":29,"1459":4,"1460":2,"1466":2,"1469":1,"1495":4,"1525":1,"1570":1,"1575":1,"1576":5,"1581":1,"1590":3,"1616":1,"1648":1,"1657":2,"1837":1,"1894":1,"2040":1,"2048":1,"2059":1,"2212":1,"2218":1,"2219":1,"2224":1,"2238":4,"2242":1}}],["clauses",{"3":{"699":1,"1229":1,"1238":1,"1245":1,"1247":2,"1270":1,"1285":3,"1323":2,"1338":2,"1358":3,"1394":1,"1414":2,"1424":1,"1435":1,"1814":2}}],["clause",{"3":{"0":3,"109":1,"110":1,"145":1,"341":1,"342":1,"343":1,"697":1,"703":1,"887":1,"889":2,"890":3,"892":1,"988":2,"1073":1,"1075":1,"1132":1,"1220":1,"1229":1,"1237":2,"1241":2,"1247":5,"1263":1,"1271":3,"1280":1,"1285":11,"1299":1,"1308":1,"1310":2,"1322":2,"1323":2,"1338":2,"1349":2,"1358":11,"1368":1,"1395":1,"1401":1,"1411":1,"1414":7,"1415":5,"1426":1,"1435":4,"1455":1,"1661":1,"1812":2,"1816":1,"1843":1,"1858":1,"1868":1,"1871":1,"1876":1,"1986":1,"1990":1,"2231":2}}],["claimvalue",{"3":{"1414":1}}],["claimbaseduseridprovidertests",{"3":{"1199":1,"1207":4}}],["claimbaseduseridprovider",{"2":{"1295":1,"1934":1},"3":{"1199":3,"1203":1,"1207":2,"1286":1,"1295":3,"1299":9,"1308":1,"1934":1}}],["claimdueasync",{"2":{"2187":1},"3":{"1042":1,"1285":1,"2187":1}}],["claimwait",{"2":{"1099":1},"3":{"996":1,"1099":1,"1270":1,"1358":1,"1401":2}}],["claimtoken",{"3":{"1285":1}}],["claimtimetolive",{"2":{"1099":1},"3":{"996":1,"1099":1,"1270":1,"1358":1,"1401":2}}],["claimtype",{"3":{"318":1,"698":1,"1285":2,"1299":3,"1310":1,"1322":1,"1395":1,"1993":1}}],["claimtypes",{"2":{"157":1,"161":1,"246":1,"303":2,"324":1,"954":1,"1295":1,"1371":1,"1994":1,"1995":1,"2137":1},"3":{"157":1,"161":1,"186":1,"246":1,"303":2,"318":2,"324":2,"954":1,"1295":1,"1299":13,"1310":5,"1322":1,"1371":1,"1379":5,"1395":5,"1435":7,"1961":1,"1994":1,"1995":1,"2137":1}}],["claiming",{"0":{"1255":1},"3":{"862":1,"1018":1,"1049":1,"1247":1,"1259":2,"1270":1,"1285":1,"1310":1,"1322":3,"1323":1,"1358":1,"1401":1,"1435":1,"1800":1,"1840":1,"1890":1}}],["claimants",{"3":{"1435":1}}],["claimant",{"3":{"708":1,"1435":3,"2187":1,"2192":1}}],["claimable",{"3":{"353":1,"995":1,"1259":2,"1322":1,"1405":1,"1976":1}}],["claimeligibleasync",{"3":{"707":1,"1259":1}}],["claimeduntil",{"3":{"1285":5,"2185":1}}],["claimedby",{"3":{"1285":4,"2185":1}}],["claimed",{"1":{"2128":1,"2129":1,"2130":1,"2131":1,"2132":1},"3":{"82":1,"137":1,"164":1,"477":1,"499":1,"660":1,"690":1,"696":1,"707":2,"709":1,"876":1,"927":1,"995":1,"1237":1,"1247":1,"1259":4,"1279":1,"1285":4,"1289":1,"1299":1,"1310":1,"1322":3,"1323":1,"1338":3,"1358":1,"1379":1,"1414":1,"1435":3,"1488":1,"1495":3,"1778":1,"1947":1,"2117":1,"2122":1,"2127":1,"2128":1,"2138":1,"2180":1,"2209":1}}],["claimkey",{"2":{"524":1},"3":{"524":1,"996":1,"1358":1,"1401":1}}],["claimsidentity",{"3":{"1299":2,"1322":2,"1323":2,"1435":6}}],["claimsonlybrowsertokens",{"2":{"1184":1},"3":{"1184":1}}],["claimsprincipal",{"2":{"1295":1,"2185":1},"3":{"507":1,"1042":1,"1285":2,"1295":1,"1299":19,"1308":2,"1310":1,"1322":3,"1323":1,"1395":1,"1414":1,"1431":1,"1435":9,"2185":1}}],["claimsprincipalextensionstests",{"3":{"906":1,"1199":1,"1207":2}}],["claimsprincipalextensions",{"2":{"126":1,"186":1,"246":1,"905":1,"1059":2,"1270":1,"1288":1,"1292":1,"1295":1,"1661":1},"3":{"0":1,"126":3,"157":1,"161":1,"186":2,"246":2,"303":1,"649":1,"905":4,"907":1,"1059":4,"1199":1,"1203":1,"1207":2,"1270":2,"1286":1,"1288":1,"1292":3,"1295":5,"1296":2,"1299":35,"1310":4,"1394":3,"1435":1,"1661":1,"2137":1}}],["claims",{"0":{"1295":1},"3":{"0":5,"15":1,"20":2,"24":1,"43":1,"93":1,"109":1,"181":1,"186":2,"191":1,"207":1,"208":1,"209":2,"259":1,"265":1,"303":1,"344":1,"350":2,"499":2,"507":1,"531":1,"538":1,"571":1,"592":1,"611":1,"649":1,"690":1,"691":1,"696":1,"697":1,"707":2,"720":1,"727":1,"749":1,"857":1,"905":1,"972":1,"973":1,"996":1,"1018":1,"1042":1,"1059":2,"1184":3,"1185":1,"1186":1,"1192":1,"1202":1,"1203":1,"1255":1,"1259":4,"1270":2,"1275":1,"1285":3,"1286":1,"1287":2,"1288":1,"1295":2,"1299":43,"1308":2,"1310":12,"1316":1,"1322":7,"1323":7,"1338":5,"1354":1,"1358":4,"1368":1,"1376":1,"1379":2,"1395":9,"1401":9,"1404":1,"1405":1,"1414":10,"1415":1,"1435":21,"1437":2,"1448":2,"1487":1,"1488":1,"1491":1,"1495":3,"1525":1,"1576":1,"1626":1,"1630":1,"1631":3,"1637":1,"1638":1,"1640":6,"1652":1,"1664":1,"1746":1,"1764":1,"1770":1,"1800":1,"1824":1,"1840":1,"1861":1,"1896":3,"1946":1,"1947":1,"1962":1,"1967":2,"1970":2,"1971":2,"1974":2,"1981":1,"2044":1,"2045":1,"2052":1,"2077":1,"2167":1,"2187":1,"2193":1}}],["claim",{"0":{"2055":1,"2187":1,"2189":1,"2196":1},"1":{"704":1,"705":1,"706":1,"707":1,"708":1,"709":1,"710":1,"711":1,"969":1,"970":1,"971":1,"972":1,"973":1,"974":1,"975":1,"976":1},"2":{"494":1,"495":1,"1996":1},"3":{"0":28,"15":1,"20":1,"22":2,"24":3,"26":1,"31":1,"46":1,"62":1,"72":1,"103":1,"115":1,"121":1,"126":4,"145":1,"157":1,"161":1,"173":3,"175":1,"186":7,"191":1,"197":1,"246":1,"250":1,"251":1,"252":1,"254":2,"256":1,"257":1,"258":1,"259":1,"279":2,"298":2,"303":3,"318":18,"320":6,"321":1,"322":2,"323":1,"324":3,"325":1,"326":4,"344":2,"349":1,"350":4,"351":1,"352":2,"365":1,"372":1,"373":1,"400":1,"403":1,"426":1,"447":1,"449":1,"459":1,"463":1,"464":1,"465":1,"477":1,"489":1,"490":2,"491":1,"492":1,"493":1,"494":1,"495":1,"497":3,"498":1,"499":2,"500":1,"518":1,"520":1,"524":4,"526":1,"538":1,"543":2,"550":1,"554":1,"574":1,"582":1,"592":1,"638":1,"650":1,"690":1,"696":1,"698":5,"700":1,"703":1,"704":1,"707":4,"708":1,"709":1,"710":1,"711":1,"743":1,"748":1,"749":1,"758":1,"764":2,"799":2,"800":2,"811":1,"833":1,"862":1,"903":3,"905":9,"906":3,"907":3,"909":1,"959":1,"969":1,"971":3,"972":4,"973":1,"976":1,"994":1,"995":5,"996":3,"999":1,"1000":2,"1018":3,"1019":1,"1021":1,"1022":1,"1041":1,"1042":2,"1043":1,"1046":1,"1057":1,"1058":1,"1059":6,"1060":1,"1061":1,"1063":1,"1066":1,"1067":1,"1074":1,"1075":1,"1085":1,"1099":3,"1100":3,"1103":2,"1104":1,"1108":1,"1116":1,"1118":1,"1212":4,"1213":1,"1232":1,"1233":1,"1237":5,"1247":1,"1255":3,"1256":1,"1259":18,"1263":1,"1269":1,"1270":17,"1275":2,"1276":2,"1280":1,"1285":28,"1287":3,"1289":3,"1292":1,"1295":6,"1296":5,"1299":127,"1303":1,"1308":2,"1310":24,"1316":2,"1317":1,"1322":29,"1323":9,"1338":3,"1347":2,"1348":1,"1349":3,"1356":1,"1357":2,"1358":36,"1366":2,"1368":1,"1371":3,"1372":2,"1374":1,"1375":1,"1379":25,"1387":2,"1388":2,"1394":20,"1395":20,"1401":28,"1404":1,"1405":4,"1408":2,"1409":1,"1412":1,"1413":1,"1414":36,"1415":1,"1429":1,"1431":1,"1435":29,"1437":14,"1440":1,"1448":2,"1449":1,"1472":1,"1473":3,"1487":1,"1490":2,"1495":6,"1524":1,"1525":1,"1533":1,"1576":2,"1581":1,"1582":1,"1620":3,"1625":1,"1626":3,"1630":2,"1631":2,"1637":1,"1638":3,"1639":1,"1640":12,"1659":1,"1663":2,"1664":1,"1666":2,"1667":1,"1718":2,"1722":2,"1723":2,"1756":1,"1760":1,"1770":1,"1777":1,"1796":2,"1820":1,"1824":1,"1840":1,"1847":1,"1848":1,"1850":4,"1895":1,"1907":1,"1932":1,"1946":1,"1947":2,"1949":2,"1961":4,"1964":2,"1973":1,"1976":2,"1977":1,"1978":1,"1981":2,"1982":1,"1983":1,"1984":1,"1993":7,"1994":8,"1995":10,"1996":6,"1999":1,"2001":1,"2042":3,"2047":1,"2048":1,"2052":1,"2059":1,"2076":1,"2084":1,"2108":1,"2109":1,"2121":1,"2124":1,"2128":1,"2137":2,"2142":1,"2167":1,"2182":1,"2185":3,"2188":1,"2189":1,"2192":3,"2193":1,"2196":7,"2201":2,"2202":2,"2214":1,"2219":1,"2231":3,"2235":1}}],["cuba",{"3":{"2219":1,"2221":1}}],["culprit",{"3":{"1455":1}}],["cultural",{"3":{"1435":1}}],["culture+theme",{"3":{"1437":1}}],["cultureandtheme",{"3":{"1435":1}}],["cultureinvariant",{"3":{"1349":1,"1426":1}}],["cultureinfo",{"2":{"265":1,"1236":1,"1323":1},"3":{"265":3,"1050":1,"1236":1,"1237":3,"1241":1,"1247":5,"1270":1,"1271":6,"1285":3,"1299":11,"1308":15,"1310":4,"1311":1,"1322":5,"1323":35,"1338":2,"1349":5,"1358":13,"1379":2,"1394":31,"1395":14,"1401":4,"1414":5,"1415":2,"1416":16,"1426":1,"1435":3,"1590":1}}],["culture=",{"3":{"1310":1,"1435":1}}],["cultureendpointtests",{"3":{"1199":1,"1207":2}}],["culturedelegatinghandler",{"2":{"2082":1,"2085":1},"3":{"265":2,"881":1,"1199":2,"1203":1,"1207":2,"1323":11,"1495":1,"2082":2,"2085":1}}],["cultureswitch",{"3":{"1435":1}}],["cultureswitchertests",{"3":{"1199":1,"1207":3,"1437":2}}],["cultureswitcher",{"2":{"273":1,"276":1},"3":{"1323":5,"1576":1,"2082":2}}],["culturescope",{"3":{"1199":2,"1207":1}}],["cultures",{"3":{"265":1,"1108":1,"1299":1,"1310":3,"1323":4,"1416":1,"1435":4,"1486":1,"1563":1,"1590":2,"1665":1,"2082":1}}],["culture",{"3":{"0":6,"86":3,"87":1,"263":1,"264":1,"265":41,"267":1,"268":1,"269":1,"273":1,"415":3,"459":1,"485":1,"555":1,"556":1,"558":1,"560":2,"684":1,"686":1,"861":1,"1050":1,"1108":2,"1111":1,"1192":2,"1203":4,"1207":8,"1230":1,"1236":3,"1237":4,"1241":1,"1247":3,"1271":5,"1285":2,"1290":1,"1295":3,"1299":23,"1304":1,"1308":4,"1310":43,"1317":1,"1322":12,"1323":145,"1338":1,"1343":1,"1349":2,"1358":12,"1378":1,"1379":2,"1393":2,"1394":18,"1395":18,"1401":3,"1404":1,"1414":23,"1415":6,"1416":53,"1435":28,"1437":9,"1495":4,"1512":1,"1525":2,"1533":1,"1563":4,"1576":5,"1590":3,"1595":1,"1599":1,"1606":1,"1607":1,"1643":1,"1652":5,"1659":1,"1661":1,"1665":2,"1668":3,"1686":1,"1740":1,"1741":1,"1746":2,"1993":1,"2047":2,"2080":1,"2081":3,"2082":11,"2083":1,"2084":5,"2085":6,"2231":1}}],["cumulative",{"3":{"1442":1,"1466":1}}],["cues",{"3":{"1416":1}}],["cue",{"3":{"353":1,"1323":1,"1416":3,"1640":2}}],["curriculum",{"3":{"1778":1,"1785":1,"2203":1,"2235":1}}],["currencies",{"3":{"1237":1,"1323":1}}],["currencymismatch",{"3":{"1234":1,"1237":2,"1310":1}}],["currencytests",{"3":{"1199":1,"1207":2}}],["currencyjsonconvertertests",{"3":{"1199":2,"1207":4}}],["currencyjsonconverter",{"2":{"659":1,"660":1,"964":1,"1230":1,"1235":1},"3":{"657":1,"659":1,"660":1,"964":1,"1198":1,"1199":4,"1203":2,"1207":3,"1230":1,"1234":1,"1235":1,"1237":9,"1310":11}}],["currency",{"2":{"655":1,"658":1,"659":1,"660":2},"3":{"0":2,"86":1,"124":1,"125":1,"536":1,"655":1,"656":1,"657":25,"658":5,"659":7,"660":8,"790":1,"1011":1,"1026":1,"1198":1,"1199":16,"1203":1,"1207":3,"1230":2,"1234":13,"1237":75,"1270":1,"1281":1,"1285":12,"1299":1,"1310":17,"1322":1,"1323":19,"1437":1,"1497":1,"1505":1,"1563":1,"1584":1,"1590":2,"1599":3,"1661":2,"1684":1,"1686":1,"1763":8,"1764":1,"1766":4,"1769":1,"1809":1}}],["currentisdirty",{"3":{"1576":1}}],["currentdevicesessionid",{"3":{"1435":1}}],["currentdomain",{"3":{"1285":1}}],["currentfixtimeout",{"3":{"1416":2}}],["currentformatversion",{"3":{"1299":1,"1322":1,"1414":1}}],["currentgeneration",{"3":{"1394":3}}],["currentcount",{"3":{"1394":2}}],["currentculture",{"3":{"265":5,"1299":1,"1323":11,"1394":4,"1395":5,"1401":1,"1416":1,"1590":2,"1599":1}}],["currentpasswordfield",{"3":{"1435":1}}],["currentpassword",{"3":{"1299":2,"1323":1,"1414":3,"1435":1,"1640":1}}],["currentpagestate",{"3":{"1323":2}}],["currentpage",{"3":{"1227":1,"1229":2,"1323":9,"1416":1,"1525":1,"1630":1,"1804":1}}],["currentmultifactormethod",{"3":{"1299":4}}],["currentsessionid",{"3":{"1299":5}}],["currentsaveuserid",{"2":{"1274":1},"3":{"1273":1,"1274":1,"1285":5}}],["currenttransaction",{"3":{"1285":3}}],["currenttype",{"3":{"1247":1}}],["currenttenantid",{"2":{"1850":1},"3":{"698":3,"1237":2,"1276":3,"1285":10,"1850":2}}],["currenteventdefaultstests",{"3":{"1199":1,"1207":2}}],["currenteventdefaults",{"2":{"1347":1,"1390":1},"3":{"1199":5,"1203":1,"1207":2,"1347":2,"1349":7,"1390":1,"1394":6,"1495":1}}],["currenteventselectortests",{"3":{"1199":1,"1207":3,"1436":1}}],["currenteventselector",{"2":{"1347":1,"1382":1,"1391":1,"1392":2,"1400":1},"3":{"1199":16,"1203":1,"1207":2,"1347":6,"1349":28,"1354":1,"1355":1,"1358":7,"1382":1,"1391":2,"1392":2,"1394":28,"1395":4,"1400":1,"1495":1}}],["currenteventnotificationscopeprovidertests",{"2":{"1486":1},"3":{"1199":1,"1207":2,"1395":1,"1437":1,"1486":1}}],["currenteventnotificationscopeprovider",{"2":{"1304":1},"3":{"1199":3,"1203":1,"1207":2,"1304":2,"1308":2,"1323":2,"1395":7,"1437":1}}],["currentvalues",{"3":{"1285":1}}],["currentvalue",{"3":{"698":1,"715":1,"1231":1,"1237":1,"1274":1,"1285":3}}],["currentuiculture",{"3":{"265":7,"1323":6,"1415":2,"1416":5}}],["currentuserrole",{"3":{"725":1,"1321":1,"1322":5,"1414":10}}],["currentuserid",{"2":{"727":1},"3":{"725":1,"727":1,"1310":1,"1321":1,"1322":3,"1323":2,"1414":6}}],["currentuserservicetests",{"3":{"1199":1,"1207":4}}],["currentuserserviceadditionaltests",{"3":{"1199":1,"1207":2}}],["currentuserserviceextensionstests",{"3":{"1199":1,"1207":2,"1271":2}}],["currentuserserviceextensions",{"2":{"1372":1},"3":{"388":2,"980":1,"1199":3,"1203":2,"1207":4,"1208":2,"1271":11,"1349":2,"1372":3,"1379":19,"1395":8,"1495":1}}],["currentuserservice",{"2":{"303":1,"1208":1,"1295":1,"1307":1,"1358":1,"1379":1},"3":{"303":1,"318":1,"1199":9,"1203":1,"1207":2,"1208":2,"1259":2,"1271":3,"1285":3,"1286":1,"1295":6,"1299":22,"1307":1,"1308":2,"1310":10,"1317":1,"1322":1,"1358":15,"1379":10,"1395":7,"1401":1,"1414":3}}],["currentuser",{"3":{"121":1,"126":1,"1270":6,"1414":2}}],["currentusertargetingcontextaccessortests",{"3":{"1199":1,"1207":2,"1437":2}}],["currentusertargetingcontextaccessor",{"2":{"303":1,"2137":1},"3":{"0":1,"303":2,"1199":3,"1203":1,"1207":2,"1299":2,"1310":7,"2137":3}}],["currently",{"3":{"54":1,"72":1,"85":1,"135":1,"273":1,"306":1,"370":1,"422":1,"547":1,"678":1,"751":2,"800":1,"811":1,"839":1,"996":1,"1018":1,"1229":1,"1247":1,"1259":1,"1270":2,"1271":1,"1285":7,"1299":4,"1300":1,"1304":1,"1308":4,"1310":2,"1322":4,"1323":9,"1338":1,"1349":1,"1358":5,"1368":2,"1377":1,"1394":1,"1395":4,"1414":4,"1416":2,"1435":7,"1479":1,"1575":1,"1631":1,"1633":1,"1638":2,"1732":1,"1763":1,"1895":1,"1928":1,"2035":1,"2084":1,"2131":1,"2217":1}}],["currentoutboxorigin",{"2":{"26":1,"1253":1},"3":{"26":1,"1253":1,"1259":3,"1285":4}}],["current",{"0":{"720":1,"1347":1},"2":{"452":1,"453":1,"682":1,"1246":1,"1415":1,"1424":1},"3":{"0":5,"25":1,"27":2,"29":1,"39":1,"57":1,"63":1,"74":1,"86":1,"87":1,"100":1,"112":1,"115":1,"137":2,"140":1,"142":1,"145":4,"152":2,"157":1,"164":1,"173":1,"181":1,"198":1,"202":1,"220":1,"227":1,"250":1,"251":1,"252":2,"254":1,"256":1,"257":2,"258":1,"259":1,"263":1,"273":1,"281":2,"285":3,"308":1,"309":1,"310":2,"311":1,"312":4,"328":1,"336":2,"337":1,"341":1,"342":1,"345":1,"353":2,"354":1,"359":5,"360":2,"361":1,"364":1,"365":5,"366":1,"375":1,"393":1,"395":8,"397":1,"407":1,"429":1,"434":1,"448":1,"451":1,"452":1,"453":1,"463":1,"464":2,"465":1,"466":1,"474":1,"476":1,"483":1,"497":4,"499":1,"502":1,"507":1,"512":1,"514":1,"520":1,"526":2,"534":1,"540":1,"552":2,"554":3,"559":1,"562":1,"567":1,"574":1,"583":1,"609":1,"613":1,"618":1,"621":1,"624":1,"629":1,"635":1,"649":1,"669":1,"682":1,"684":2,"696":2,"703":1,"707":2,"710":1,"714":1,"716":1,"720":1,"761":1,"777":1,"794":2,"810":1,"814":1,"825":4,"827":1,"833":2,"841":1,"849":1,"850":1,"873":1,"876":1,"877":1,"895":1,"905":3,"906":2,"907":1,"909":2,"910":1,"912":2,"914":1,"927":1,"945":1,"946":1,"949":1,"960":1,"994":1,"996":1,"1018":2,"1021":3,"1062":1,"1065":1,"1067":1,"1069":1,"1085":1,"1086":1,"1095":1,"1111":1,"1116":2,"1117":1,"1119":2,"1127":1,"1128":1,"1139":1,"1140":3,"1141":1,"1145":1,"1167":1,"1171":1,"1178":1,"1192":2,"1202":1,"1203":1,"1212":1,"1220":1,"1229":3,"1232":1,"1237":5,"1246":2,"1247":2,"1251":1,"1259":4,"1260":1,"1262":1,"1264":1,"1270":8,"1271":2,"1273":1,"1275":1,"1276":2,"1281":1,"1283":2,"1285":38,"1286":1,"1297":1,"1299":29,"1300":1,"1308":5,"1310":23,"1311":2,"1317":1,"1322":10,"1323":41,"1331":1,"1332":1,"1336":1,"1338":19,"1341":1,"1348":1,"1349":5,"1355":2,"1356":2,"1358":18,"1363":1,"1379":1,"1390":1,"1391":2,"1394":40,"1395":22,"1400":1,"1401":6,"1414":14,"1415":12,"1416":31,"1424":2,"1426":7,"1430":1,"1434":1,"1435":21,"1436":1,"1437":4,"1446":1,"1450":1,"1452":1,"1454":2,"1460":1,"1464":1,"1466":4,"1472":1,"1475":2,"1477":1,"1478":1,"1480":1,"1483":2,"1486":3,"1487":1,"1491":1,"1495":32,"1499":1,"1520":1,"1525":2,"1530":1,"1560":1,"1569":1,"1570":2,"1571":1,"1576":3,"1588":1,"1590":3,"1599":1,"1609":1,"1626":1,"1629":4,"1630":2,"1631":4,"1637":1,"1638":5,"1639":2,"1640":7,"1647":2,"1660":1,"1663":1,"1685":2,"1715":1,"1743":1,"1746":1,"1747":1,"1748":1,"1763":1,"1764":3,"1766":1,"1804":1,"1812":1,"1838":1,"1843":1,"1859":1,"1870":1,"1871":2,"1903":1,"1904":1,"1932":1,"1935":1,"1947":1,"1967":1,"1987":1,"2009":1,"2018":1,"2023":1,"2045":1,"2059":1,"2071":1,"2076":1,"2082":1,"2095":1,"2100":1,"2130":1,"2132":1,"2141":5,"2145":1,"2162":1,"2224":1}}],["curiously",{"3":{"1237":1,"1285":1,"1299":1,"1435":1}}],["curl",{"3":{"387":1,"426":1,"757":1,"1454":1,"1466":1,"1873":1}}],["curating",{"3":{"1394":1}}],["curation",{"3":{"184":1,"186":1,"1347":1,"1349":1,"1358":3,"1376":1,"1379":6,"1620":2,"1626":1}}],["curates",{"3":{"1339":1,"1379":1,"1404":1,"1414":2}}],["curated",{"3":{"265":1,"1323":1,"1349":1,"1629":1}}],["curate",{"3":{"185":1,"1346":1,"1414":1,"1959":1}}],["curve",{"3":{"22":1,"23":2,"27":1,"709":1,"1299":1,"1832":1,"1957":1}}],["cursor",{"3":{"0":1,"549":3,"1228":5,"1229":21,"1272":1,"1278":1,"1285":16,"1437":4,"1663":1}}],["custommetrics",{"3":{"1466":1}}],["customdomainexception",{"3":{"1435":2}}],["customdeleteorderhandler",{"3":{"1199":2,"1207":1}}],["customincreaseorderhandler",{"3":{"1199":2,"1207":1}}],["customize",{"3":{"1415":1}}],["customizes",{"3":{"1354":1,"1435":1,"1437":1}}],["customized",{"3":{"749":1,"1435":1,"1437":1,"1824":1}}],["customizing",{"3":{"1310":2}}],["customization",{"3":{"471":1,"749":1,"751":1,"1310":4,"1435":3,"1437":1}}],["customloggingintegrationeventhandler",{"3":{"1199":2,"1207":1}}],["customschemacontext",{"3":{"1199":2,"1207":1}}],["customexceptionthrowingfixture",{"3":{"1199":2,"1207":1,"1435":2}}],["customerinvariants",{"2":{"1809":1},"3":{"1766":2,"1809":1}}],["customeridentifiertype",{"3":{"1770":1,"1809":1}}],["customerid",{"2":{"42":1,"1994":1},"3":{"42":1,"96":1,"318":1,"782":1,"1198":2,"1199":10,"1207":2,"1599":1,"1746":1,"1748":2,"1763":5,"1764":2,"1768":2,"1809":2,"1994":4}}],["customername",{"2":{"1685":1},"3":{"1683":1,"1685":14,"1726":4}}],["customerdetail",{"3":{"1590":2,"1595":1,"1599":1}}],["customerbuilder",{"3":{"1435":1}}],["customerlookupservice",{"3":{"1323":1}}],["customerlist",{"3":{"1323":1,"1590":2}}],["customervalidationrules",{"3":{"1271":1}}],["customeremailrules",{"3":{"1271":1,"1595":1}}],["customererasedhandlertests",{"3":{"1590":1}}],["customererasedhandler",{"2":{"391":1,"1747":1,"1921":1},"3":{"391":3,"897":1,"1747":1,"1764":1,"1768":1,"1921":1}}],["customererased",{"2":{"76":1,"391":1,"1596":1,"1747":1,"1750":1,"1768":1},"3":{"76":1,"80":2,"195":2,"391":1,"1590":3,"1595":1,"1596":1,"1747":1,"1750":1,"1764":1,"1768":2,"1921":1}}],["customerrenamedv3",{"3":{"1199":4,"1207":1}}],["customerrenamedv2",{"3":{"1199":6,"1207":1}}],["customerrenamedv1",{"3":{"1199":7,"1207":1}}],["customercreate",{"3":{"1595":2}}],["customercreaterequestvalidator",{"3":{"1271":2}}],["customercreaterequest",{"3":{"1270":1}}],["customerchangeaddressrequestvalidator",{"2":{"1271":1},"3":{"1271":3}}],["customerchanged",{"2":{"1746":1},"3":{"782":1,"1746":5,"1768":1}}],["customerconfiguration",{"3":{"657":2,"660":2,"1237":5,"1285":2,"1766":1,"1809":1}}],["customercontactlookupguard",{"2":{"103":1,"1595":1},"3":{"103":1,"1595":1}}],["customerservice",{"3":{"545":1,"550":1,"881":1,"1323":1}}],["customerscontroller",{"2":{"318":2,"324":3,"326":1,"1873":1},"3":{"318":8,"324":4,"326":2,"1299":4,"1310":4,"1873":1,"1994":6}}],["customersmanage",{"2":{"318":1,"324":1,"326":1,"1994":1},"3":{"318":4,"324":1,"326":1,"1994":1}}],["customersgrpcservice",{"2":{"103":1,"1588":1,"1595":1},"3":{"103":2,"1588":1,"1590":1,"1595":1}}],["customers",{"2":{"1756":1},"3":{"80":1,"317":1,"318":1,"320":1,"324":1,"545":1,"583":1,"657":1,"782":1,"1237":2,"1271":5,"1311":1,"1435":1,"1590":1,"1741":2,"1745":5,"1746":13,"1748":1,"1749":6,"1752":1,"1756":1,"1760":3,"1762":1,"1763":2,"1764":1,"1767":2,"1769":1,"1772":1,"1775":2,"1993":1,"1995":1}}],["customer",{"0":{"1753":1,"1758":1,"1850":1},"2":{"320":1,"655":1,"660":1,"924":1,"1746":3,"1756":2,"1760":1,"1770":1,"1982":1,"1991":1,"1995":1,"2112":1},"3":{"0":3,"42":2,"103":1,"188":1,"230":1,"317":3,"318":10,"320":2,"324":1,"326":1,"459":1,"536":2,"539":1,"544":1,"545":1,"655":1,"657":5,"660":1,"697":2,"699":1,"743":2,"782":1,"793":1,"809":1,"897":2,"900":1,"924":1,"990":1,"1026":1,"1237":8,"1259":1,"1270":3,"1271":2,"1285":3,"1287":1,"1296":1,"1299":7,"1309":1,"1322":4,"1338":1,"1394":1,"1435":5,"1487":2,"1576":1,"1588":2,"1590":3,"1594":1,"1595":4,"1596":1,"1599":1,"1685":7,"1686":1,"1700":1,"1726":1,"1745":5,"1746":17,"1747":5,"1748":4,"1749":9,"1750":3,"1753":1,"1754":1,"1756":5,"1758":1,"1759":3,"1760":3,"1762":1,"1763":24,"1764":29,"1766":15,"1767":21,"1768":11,"1769":3,"1770":8,"1771":1,"1772":2,"1774":2,"1775":5,"1776":4,"1850":2,"1874":1,"1905":1,"1906":1,"1963":1,"1982":1,"1991":6,"1992":2,"1993":2,"1994":10,"1995":3,"1996":1,"2109":1,"2112":2,"2160":1,"2162":1,"2164":2,"2180":2,"2188":1,"2219":1}}],["custom",{"3":{"0":4,"135":1,"216":3,"220":1,"273":1,"275":1,"335":1,"350":1,"355":1,"396":1,"397":1,"398":1,"399":1,"418":3,"420":2,"422":3,"481":1,"640":1,"645":1,"672":3,"676":1,"698":1,"735":1,"905":1,"1075":1,"1076":1,"1077":1,"1185":1,"1212":2,"1241":3,"1247":4,"1269":1,"1270":1,"1285":2,"1287":1,"1295":1,"1297":1,"1299":2,"1308":2,"1309":1,"1310":7,"1322":6,"1323":9,"1335":1,"1338":7,"1349":4,"1358":4,"1379":2,"1414":1,"1415":4,"1416":4,"1435":19,"1487":1,"1525":1,"1556":1,"1557":1,"1640":1,"1964":1,"1975":2,"2074":2,"2077":1,"2151":1,"2155":1,"2231":1}}],["cut",{"0":{"1931":1},"3":{"396":1,"397":2,"530":1,"610":1,"881":1,"914":1,"1044":1,"1161":1,"1254":1,"1259":2,"1277":1,"1323":3,"1351":1,"1358":1,"1366":1,"1395":2,"1416":1,"1435":5,"1453":2,"1455":1,"1466":4,"1490":1,"1495":1,"1525":1,"1533":2,"1652":1,"1660":1,"1676":2,"1694":1,"1726":1,"1735":1,"1782":1,"1787":1,"1840":1,"1940":1,"2009":1,"2062":1,"2157":1,"2191":1,"2208":1}}],["cutoffs",{"3":{"1160":1}}],["cutoff",{"3":{"24":1,"536":2,"905":1,"1162":1,"1254":1,"1256":1,"1259":5,"1285":3,"1711":2,"1712":1,"2166":4}}],["cutover",{"0":{"1472":1,"1477":1},"3":{"0":3,"25":2,"27":1,"57":1,"193":1,"198":1,"422":1,"425":1,"426":1,"429":1,"1192":2,"1299":1,"1415":2,"1465":1,"1466":4,"1469":1,"1470":4,"1472":5,"1473":1,"1477":8,"1481":3,"1482":4,"1483":3,"1495":1,"1533":2,"1576":2}}],["cuts",{"3":{"18":1,"409":1,"1259":1,"1285":1,"1310":1,"1323":1,"1350":1,"1466":1,"1830":1,"2157":1}}],["cutting",{"0":{"1361":1,"1752":1,"1773":1,"2065":1,"2207":1},"3":{"0":5,"57":1,"116":2,"117":1,"119":1,"122":1,"265":1,"396":1,"826":1,"834":1,"906":1,"914":1,"1011":1,"1191":1,"1192":1,"1200":1,"1202":1,"1203":1,"1210":1,"1212":2,"1216":1,"1231":1,"1232":1,"1259":1,"1260":1,"1261":2,"1267":1,"1270":4,"1271":3,"1273":1,"1280":1,"1285":10,"1299":1,"1308":3,"1310":5,"1311":1,"1312":1,"1315":1,"1322":2,"1323":8,"1325":1,"1337":1,"1338":2,"1349":1,"1358":4,"1394":2,"1395":2,"1414":4,"1427":1,"1430":1,"1435":7,"1436":3,"1449":1,"1495":1,"1501":1,"1506":1,"1508":1,"1525":2,"1533":1,"1535":2,"1541":1,"1546":1,"1564":1,"1570":1,"1576":4,"1590":1,"1598":1,"1629":1,"1655":1,"1661":1,"1663":2,"1779":1,"1795":1,"1809":1,"1817":2,"1818":1,"1819":1,"1826":3,"1833":1,"1855":1,"1878":1,"1913":1,"1915":1,"1936":1,"1940":1,"2007":1,"2021":1,"2068":1,"2069":1,"2073":1,"2091":1,"2095":1,"2133":1,"2138":3,"2147":1,"2210":1,"2219":2,"2229":1,"2231":1}}],["ccpa",{"3":{"0":1,"38":1,"1212":2,"1230":1,"1237":1,"1298":1,"1310":1,"1322":2,"1435":1,"1488":1,"1498":1,"1566":1,"1746":1,"1764":1,"2060":1,"2061":1,"2068":1,"2220":1,"2229":1,"2231":1}}],["camps",{"3":{"2224":1}}],["camp",{"3":{"1787":1}}],["camel",{"3":{"1685":2,"1726":2}}],["camelcasing",{"3":{"1310":1}}],["camelcased",{"3":{"1310":1,"1323":1}}],["camelcasename",{"3":{"1247":3}}],["camelcase",{"2":{"741":1,"2087":1},"3":{"0":1,"741":2,"1214":1,"1242":2,"1244":1,"1247":6,"1299":1,"1310":6,"1323":3,"1395":1,"1415":1,"1435":1,"2087":1}}],["came",{"3":{"26":1,"284":1,"291":1,"610":1,"633":1,"732":1,"743":1,"905":1,"941":1,"1081":1,"1083":1,"1091":1,"1115":1,"1247":2,"1253":1,"1259":4,"1285":3,"1299":1,"1300":2,"1310":2,"1311":1,"1322":1,"1323":7,"1338":1,"1349":1,"1358":2,"1368":2,"1394":1,"1395":1,"1401":1,"1416":2,"1435":3,"1437":2,"1466":1,"1473":1,"1475":1,"1727":1,"1763":1,"1789":1,"1925":1,"2096":1,"2185":1,"2203":1}}],["camerabarcodereaderview",{"3":{"1416":2}}],["cameras",{"3":{"1395":1,"1416":1}}],["camera=",{"3":{"214":1,"2148":1}}],["cameradescription",{"2":{"684":1},"3":{"0":1,"682":1,"684":1,"1415":1,"1416":3}}],["camera",{"2":{"681":1},"3":{"0":2,"413":2,"426":1,"439":1,"681":4,"682":4,"683":3,"684":1,"689":1,"690":1,"1212":1,"1322":1,"1338":1,"1394":1,"1395":12,"1413":1,"1414":1,"1415":3,"1416":58,"1437":2,"1626":1,"2125":1,"2231":2}}],["ca5392",{"2":{"1523":1},"3":{"1523":1,"1525":2,"1530":1}}],["ca5394",{"3":{"1259":1,"1323":1}}],["ca5359",{"3":{"1415":1}}],["ca5404",{"3":{"1299":1}}],["ca1822",{"3":{"1415":1}}],["ca1859",{"3":{"1299":1}}],["ca1819",{"3":{"1237":2,"1299":1,"1310":1,"1349":2,"1414":1}}],["ca1721",{"3":{"1299":1}}],["ca1708",{"3":{"0":1,"258":1,"980":1,"981":1,"982":1,"983":1,"1310":2,"1311":1,"1323":4,"1338":11,"1435":2,"1487":1}}],["ca1068",{"3":{"1435":1}}],["ca1008",{"3":{"1349":1,"1368":1,"1395":1}}],["ca1000",{"3":{"1237":1}}],["ca1056",{"3":{"1299":1,"1323":1}}],["ca1054",{"3":{"1237":1,"1323":2,"1358":1,"1414":1}}],["ca1032",{"3":{"1311":4}}],["ca1033",{"3":{"1270":1}}],["ca1031",{"3":{"692":1,"1247":1,"1270":1,"1308":3,"1310":1,"1323":2,"1338":2,"1395":2,"1415":1,"1416":5,"1426":1,"1933":1}}],["ca1308",{"3":{"972":1,"1237":1,"1299":3,"1322":1,"1358":1,"1394":1,"1435":1}}],["ca2000",{"3":{"1310":1,"1322":1,"1426":2}}],["ca2007",{"1":{"479":1,"480":1,"481":1,"482":1,"483":1,"484":1,"485":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"495":1},"2":{"481":1,"482":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1},"3":{"0":2,"479":1,"481":1,"482":3,"483":2,"486":3,"487":3,"488":2,"489":3,"490":7,"491":7,"492":7,"493":6,"494":1,"1212":1,"2231":1}}],["ca2100",{"3":{"1285":1,"1435":2,"1489":1}}],["ca2227",{"3":{"674":1,"1322":1}}],["cautious",{"3":{"1358":1,"1414":1}}],["cautionary",{"3":{"2024":1}}],["caution",{"3":{"543":2,"550":1,"1495":1}}],["caught",{"3":{"21":1,"39":2,"110":1,"178":1,"235":3,"237":1,"345":1,"361":1,"427":1,"459":2,"558":1,"618":2,"675":2,"725":1,"759":1,"776":1,"819":1,"837":1,"854":1,"863":1,"883":1,"896":1,"901":1,"916":1,"941":2,"966":1,"1034":1,"1036":1,"1067":1,"1168":1,"1170":1,"1229":2,"1236":1,"1247":1,"1269":1,"1270":4,"1285":3,"1299":1,"1300":2,"1310":3,"1311":2,"1322":4,"1323":12,"1338":5,"1349":1,"1357":1,"1358":6,"1365":1,"1368":1,"1379":1,"1394":6,"1395":3,"1414":1,"1415":1,"1416":3,"1426":2,"1435":17,"1452":1,"1458":1,"1464":1,"1495":3,"1533":1,"1545":1,"1672":1,"1685":1,"1799":1,"1800":1,"1809":1,"1843":1,"1851":1,"1884":1,"1908":1,"1932":1,"1939":1,"2031":1,"2084":1}}],["causes",{"3":{"1222":1,"1323":1,"1338":1,"1358":1,"1414":1,"2001":1,"2013":1}}],["caused",{"3":{"451":1,"556":1,"789":1,"790":1,"981":1,"1006":1,"1066":1,"1299":1,"1310":1,"1322":1,"1323":1,"1358":4,"1395":3,"1401":1,"1424":1,"1426":1,"1435":1,"1472":1,"1533":1,"1664":1,"2176":1}}],["cause",{"3":{"0":1,"156":1,"343":2,"915":1,"948":1,"1065":1,"1285":2,"1299":1,"1308":1,"1310":1,"1322":2,"1338":1,"1394":2,"1414":1,"1416":1,"1435":2,"1437":3,"1466":2,"1475":1,"1489":1,"1685":1,"1875":1,"1912":1,"1933":1,"1968":1,"1988":1}}],["caveats",{"3":{"1191":1,"1193":1,"1229":7,"1237":18,"1247":14,"1259":13,"1270":39,"1271":6,"1285":50,"1299":45,"1300":8,"1308":7,"1309":5,"1310":33,"1311":3,"1322":49,"1323":52,"1338":53,"1349":35,"1358":177,"1368":7,"1379":9,"1394":44,"1395":88,"1401":15,"1414":40,"1415":7,"1416":27,"1426":3,"1435":88,"1576":1,"1590":1,"2132":1}}],["caveat",{"3":{"0":1,"135":1,"159":1,"188":1,"197":1,"245":3,"283":1,"320":1,"352":1,"361":1,"446":1,"450":2,"461":1,"558":1,"574":1,"601":1,"605":1,"607":1,"611":2,"642":1,"667":2,"751":1,"821":1,"829":1,"863":3,"865":1,"998":1,"1010":2,"1231":1,"1236":1,"1259":1,"1270":2,"1281":1,"1285":2,"1295":1,"1300":1,"1309":1,"1317":1,"1322":1,"1323":1,"1325":1,"1333":1,"1338":3,"1358":4,"1368":1,"1394":4,"1395":1,"1401":1,"1421":1,"1422":1,"1435":1,"1439":1,"1467":1,"1495":3,"1590":2,"1598":2,"1890":1,"1922":1,"1963":1,"1995":1,"2062":1,"2067":1,"2076":1,"2113":1,"2130":1,"2131":2,"2137":1}}],["cash",{"3":{"1764":1}}],["cashed",{"3":{"1435":1,"1473":1}}],["casting",{"3":{"1285":1,"1299":1,"1395":3,"1398":1,"1401":4,"1416":1}}],["cast",{"3":{"1237":4,"1247":3,"1259":1,"1268":1,"1270":1,"1272":1,"1285":3,"1299":1,"1300":1,"1308":1,"1311":1,"1312":1,"1322":1,"1323":5,"1338":1,"1379":1,"1394":1,"1395":4,"1396":1,"1400":1,"1401":12,"1414":2,"1435":4}}],["casts",{"3":{"1229":1,"1237":1,"1259":1,"1270":1,"1285":1,"1322":1,"1323":1,"1379":1,"1401":2,"1435":1,"1631":1}}],["castvoteasync",{"3":{"1395":4,"1401":1}}],["castvoterequest",{"3":{"1199":4,"1203":1,"1207":2,"1395":2,"1401":6}}],["castvotecommandvalidatortests",{"3":{"1199":1,"1207":2}}],["castvotecommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1401":5}}],["castvotecommand",{"3":{"1199":8,"1203":1,"1207":2,"1395":2,"1401":8}}],["castvote",{"3":{"1150":1,"1203":3,"1207":6,"1397":1,"1401":11}}],["castvotehandlertests",{"3":{"1199":1,"1207":3,"1435":2}}],["castvotehandler",{"2":{"383":1},"3":{"383":1,"1150":1,"1156":1,"1199":2,"1203":1,"1207":2,"1395":1,"1397":2,"1398":2,"1401":18}}],["casualty",{"3":{"751":1}}],["casing",{"3":{"135":1,"572":1,"574":1,"585":1,"964":1,"1089":1,"1094":1,"1247":1,"1285":1,"1299":1,"1322":4,"1323":1,"1349":1,"1358":3,"1394":2,"1395":1,"1404":1,"1409":1,"1414":6,"1415":1,"1435":1,"1437":2,"1452":1,"1454":1,"1630":1}}],["cascadingforeignkeysareexplicitlyoptedin",{"3":{"1435":4}}],["cascadingparameter",{"3":{"1395":2}}],["cascadingauthenticationstate",{"2":{"1640":1,"2077":1},"3":{"1323":1,"1395":2,"1435":1,"1640":1,"2077":1}}],["cascadingchild",{"3":{"1199":3,"1207":1}}],["cascadingdeletes",{"2":{"1083":1},"3":{"1083":1,"1435":3}}],["cascading",{"3":{"0":1,"39":1,"556":1,"954":1,"1081":1,"1116":1,"1168":1,"1280":1,"1285":2,"1300":1,"1323":1,"1358":1,"1394":6,"1395":2,"1401":3,"1431":1,"1435":11,"1487":2,"1490":2,"1555":1,"1638":1,"1661":1,"1775":1,"2063":1,"2231":1}}],["cascaded",{"3":{"1322":1,"1349":1,"1358":2,"1394":1,"1395":1,"1437":1,"1491":1}}],["cascadedelete",{"2":{"1116":1,"1345":1},"3":{"1116":1,"1345":1,"1349":4,"1358":2}}],["cascadefixtures",{"3":{"1207":21,"1435":43}}],["cascadechildfixture",{"3":{"1199":7,"1207":1,"1435":12}}],["cascadesoftdelete",{"3":{"1207":1,"1435":9,"1488":1,"1576":1}}],["cascadesoftdeleteconventiontests",{"3":{"1199":2,"1207":4,"1435":19,"1488":2,"1576":1}}],["cascadesoftdeleteconventiontestsbase",{"3":{"1199":3,"1207":2,"1435":11,"1585":2}}],["cascadesoftdeletefitnesstests",{"3":{"1199":1,"1207":3,"1435":31,"1488":1}}],["cascades",{"3":{"0":1,"1082":1,"1084":1,"1087":1,"1231":1,"1237":2,"1270":1,"1280":1,"1285":1,"1322":1,"1345":2,"1349":2,"1358":2,"1379":1,"1397":1,"1401":1,"1430":1,"1431":1,"1435":10,"1454":1,"1488":1,"1489":1,"1495":1,"1543":1,"1634":1,"1637":2,"2054":1,"2063":1}}],["cascade",{"0":{"1345":1},"2":{"1082":1,"1083":1,"1280":1,"2063":1},"3":{"0":7,"27":1,"37":1,"39":2,"43":3,"459":1,"926":1,"1082":4,"1083":6,"1084":3,"1085":3,"1117":1,"1120":2,"1170":1,"1212":1,"1237":2,"1265":1,"1270":3,"1280":3,"1285":7,"1339":1,"1341":1,"1342":2,"1345":3,"1349":31,"1351":2,"1353":1,"1358":31,"1361":2,"1368":4,"1401":5,"1414":2,"1430":1,"1435":43,"1437":1,"1495":1,"1565":1,"1576":4,"1585":2,"1629":1,"1630":1,"1631":2,"1634":5,"1637":23,"1638":21,"1663":1,"1670":1,"1685":1,"1699":1,"1726":1,"1754":1,"1775":1,"2063":3}}],["caseoptedindefaultsource",{"3":{"1199":2,"1207":1}}],["casenosettings",{"3":{"1199":2,"1207":1}}],["casenooptin",{"3":{"1199":2,"1207":1}}],["casenamedsource",{"3":{"1199":3,"1207":2}}],["casemappingshape",{"3":{"1199":3,"1207":2}}],["caseenableddefaultsource",{"3":{"1199":2,"1207":1}}],["casewrongsource",{"3":{"1199":2,"1207":1}}],["caseidentity",{"3":{"1199":2,"1207":1}}],["caseconference",{"3":{"1199":2,"1207":1}}],["casedefaultsettings",{"3":{"1199":2,"1207":1}}],["cased",{"3":{"1091":1,"1322":1,"1323":1,"1338":1,"1358":2,"1394":1,"1424":1,"1426":1,"1487":1}}],["case",{"0":{"1246":1,"1321":1,"1351":1,"1951":1,"2189":1},"2":{"1035":1},"3":{"0":10,"32":1,"42":1,"109":2,"116":2,"117":1,"121":1,"126":3,"133":1,"135":2,"136":1,"150":1,"156":1,"157":1,"158":1,"160":1,"175":1,"186":1,"189":1,"196":1,"197":1,"201":1,"225":1,"227":1,"245":2,"246":1,"249":1,"301":1,"318":2,"333":1,"340":1,"358":1,"379":1,"386":1,"389":1,"390":1,"391":1,"396":1,"402":1,"435":1,"460":1,"461":1,"473":1,"517":2,"519":1,"534":1,"539":1,"540":1,"544":1,"564":1,"582":1,"583":1,"585":4,"586":1,"601":1,"609":2,"633":3,"650":1,"667":1,"683":1,"690":2,"709":1,"737":1,"741":1,"749":1,"765":1,"766":1,"782":1,"789":1,"791":1,"799":1,"802":1,"810":1,"811":1,"820":1,"821":1,"827":1,"831":1,"861":1,"863":1,"864":2,"875":1,"881":2,"883":2,"889":1,"897":2,"898":1,"905":2,"907":1,"926":2,"933":1,"938":1,"939":1,"940":1,"964":1,"965":1,"972":1,"974":1,"980":1,"982":1,"988":1,"1003":1,"1004":1,"1018":11,"1019":2,"1020":6,"1034":1,"1035":1,"1045":1,"1049":1,"1050":1,"1060":1,"1091":4,"1099":1,"1101":1,"1108":2,"1109":2,"1112":1,"1116":1,"1123":1,"1155":1,"1177":1,"1212":1,"1220":1,"1221":1,"1223":2,"1229":4,"1234":2,"1235":1,"1237":9,"1241":1,"1242":1,"1246":1,"1247":17,"1250":1,"1259":11,"1260":3,"1261":1,"1263":5,"1264":4,"1269":1,"1270":16,"1271":9,"1273":1,"1276":1,"1278":1,"1280":2,"1285":47,"1289":1,"1292":1,"1294":1,"1295":1,"1296":2,"1299":44,"1300":7,"1305":1,"1308":7,"1309":2,"1310":20,"1311":7,"1312":1,"1314":2,"1317":1,"1322":25,"1323":41,"1330":1,"1331":2,"1333":1,"1336":1,"1338":27,"1341":1,"1343":1,"1349":14,"1351":1,"1352":1,"1354":1,"1357":1,"1358":82,"1361":1,"1364":1,"1368":5,"1371":1,"1377":1,"1379":8,"1382":2,"1391":1,"1394":29,"1395":26,"1397":1,"1401":13,"1402":1,"1403":1,"1404":1,"1414":33,"1415":6,"1416":10,"1419":1,"1426":18,"1430":2,"1431":1,"1432":1,"1434":10,"1435":82,"1437":17,"1442":1,"1452":2,"1454":8,"1455":1,"1457":1,"1466":2,"1469":1,"1473":1,"1479":1,"1485":1,"1487":1,"1488":2,"1491":14,"1495":6,"1511":1,"1525":6,"1530":1,"1533":1,"1534":1,"1539":1,"1541":1,"1567":1,"1576":4,"1581":1,"1582":1,"1599":1,"1616":1,"1629":2,"1630":6,"1631":2,"1634":2,"1637":1,"1638":2,"1639":2,"1640":5,"1648":1,"1650":3,"1666":1,"1677":1,"1685":3,"1696":1,"1707":1,"1726":4,"1729":1,"1730":4,"1748":1,"1754":1,"1767":3,"1775":1,"1778":1,"1804":1,"1809":1,"1814":1,"1816":1,"1817":2,"1819":1,"1822":2,"1824":1,"1826":1,"1829":1,"1839":1,"1848":1,"1852":3,"1855":1,"1857":1,"1866":2,"1873":1,"1880":1,"1902":1,"1908":3,"1913":3,"1921":1,"1922":2,"1940":1,"1961":1,"1963":1,"1969":1,"1976":1,"1987":1,"1992":1,"1993":2,"1999":1,"2001":1,"2024":1,"2029":1,"2033":1,"2042":3,"2069":1,"2082":1,"2086":1,"2091":1,"2095":1,"2104":1,"2105":1,"2107":2,"2108":1,"2116":1,"2117":1,"2122":1,"2133":2,"2134":1,"2135":1,"2138":1,"2140":2,"2148":1,"2153":1,"2157":1,"2159":1,"2162":1,"2164":1,"2166":2,"2182":1,"2188":1,"2189":1,"2192":1,"2196":1,"2231":1,"2233":1}}],["casesettingsonly",{"3":{"1199":2,"1207":1}}],["cases",{"0":{"1631":1,"1767":1},"1":{"1350":1,"1351":1,"1352":1,"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1},"3":{"0":1,"33":1,"126":1,"135":1,"136":1,"138":1,"142":1,"160":1,"314":1,"324":1,"335":1,"350":1,"365":1,"466":1,"539":1,"656":1,"790":1,"791":1,"794":1,"845":1,"972":1,"988":1,"998":1,"1018":3,"1020":1,"1050":1,"1091":1,"1116":1,"1192":2,"1201":1,"1202":3,"1203":3,"1217":1,"1219":1,"1229":4,"1237":1,"1247":3,"1260":2,"1263":1,"1270":1,"1271":1,"1285":8,"1299":8,"1302":1,"1305":1,"1308":3,"1310":3,"1321":1,"1322":4,"1323":3,"1338":10,"1349":3,"1350":2,"1353":1,"1358":17,"1364":1,"1368":4,"1379":1,"1394":3,"1395":7,"1401":2,"1402":2,"1409":1,"1414":9,"1415":1,"1416":2,"1426":3,"1427":1,"1430":2,"1434":2,"1435":15,"1437":8,"1443":1,"1453":1,"1486":1,"1491":1,"1495":5,"1499":1,"1525":1,"1552":1,"1576":1,"1610":3,"1631":2,"1640":1,"1645":1,"1648":1,"1649":1,"1659":1,"1666":2,"1728":1,"1748":1,"1820":1,"1855":1,"1908":1,"1931":1,"1940":1,"1954":1,"1961":1,"1981":1,"2113":1,"2135":1,"2137":1,"2142":1,"2166":1}}],["caracteres",{"3":{"1684":2,"1685":2}}],["car",{"3":{"1415":1}}],["carve",{"3":{"200":1,"265":2,"317":1,"322":1,"324":1,"326":1,"562":1,"783":1,"862":1,"1285":4,"1374":1,"1379":2,"1401":1,"1430":1,"1432":2,"1435":1,"1464":1,"1466":1,"1495":1,"1576":1,"1629":1,"1634":1}}],["carved",{"3":{"200":1,"1322":1,"1440":1}}],["career",{"3":{"1394":1,"2219":1}}],["careless",{"3":{"1107":1,"1135":1,"1904":1}}],["carelessly",{"3":{"829":1}}],["careful",{"3":{"839":1,"1259":1,"1299":1,"1322":1,"1323":1,"1358":1,"1395":2,"1401":1,"1424":1,"1430":1,"1435":2,"1941":1,"1999":1,"2071":1,"2166":1}}],["carefully",{"3":{"365":1,"1234":1,"1237":1,"1259":1,"1270":1,"1300":1,"1322":1,"1323":1,"1331":1,"1340":1,"1343":1,"1349":2,"1358":2,"1395":2,"1414":2,"1435":1,"1466":1,"1485":1,"1802":1}}],["cares",{"3":{"784":1,"827":1,"1041":1,"1295":1,"1338":4,"1349":1,"1358":2,"1394":1,"1414":1,"1416":1,"1426":1,"1428":1,"1435":2,"2090":1,"2182":1}}],["care",{"3":{"55":1,"740":1,"842":1,"1194":1,"1229":1,"1255":1,"1259":1,"1273":1,"1311":1,"1322":1,"1358":1,"1395":1,"1435":2,"1452":1,"1781":1,"1782":1,"1791":1,"1881":1,"1904":1,"2090":1,"2106":1}}],["carte",{"3":{"1435":1}}],["cartesian",{"3":{"1285":3,"1862":1,"1863":1}}],["cartdrawer",{"3":{"1323":1,"1590":7,"1595":3,"1596":1,"1599":2,"1749":3,"1772":1}}],["cartbutton",{"3":{"1323":1,"1590":1,"1595":1,"1596":1}}],["carts",{"3":{"1296":1,"1741":1,"1749":2,"1760":1,"1762":1,"1767":2,"1772":2,"2109":1}}],["cartstateservice",{"2":{"879":1,"883":1,"884":1,"1598":1},"3":{"0":1,"879":1,"881":2,"883":1,"884":2,"1323":1,"1590":5,"1598":1,"1599":3}}],["cartid",{"3":{"1237":1}}],["cart",{"3":{"0":1,"318":1,"320":1,"535":1,"881":1,"990":1,"1025":1,"1026":2,"1027":3,"1028":1,"1237":1,"1270":1,"1299":1,"1310":1,"1323":3,"1576":1,"1590":5,"1595":1,"1598":2,"1599":3,"1711":1,"1712":2,"1713":1,"1740":1,"1741":2,"1745":5,"1747":1,"1748":18,"1749":20,"1750":1,"1752":1,"1753":2,"1754":2,"1756":1,"1757":1,"1758":1,"1760":1,"1762":1,"1763":13,"1764":17,"1766":8,"1767":12,"1768":2,"1770":2,"1772":10,"1774":2,"1775":10,"1994":5,"1995":1,"2076":1,"2160":1}}],["cardinalities",{"3":{"1639":1}}],["cardinality",{"3":{"0":2,"407":1,"897":2,"898":1,"1150":1,"1152":1,"1212":1,"1247":3,"1300":1,"1310":2,"1395":3,"1401":3,"1918":1}}],["cardclass",{"3":{"1323":1}}],["cardcontent",{"3":{"1323":1}}],["cardelevation",{"3":{"1323":1}}],["cardelementid",{"3":{"1323":1}}],["cardformatting",{"3":{"1323":1,"1590":2}}],["cardtemplate",{"3":{"1323":1}}],["card",{"3":{"539":1,"684":1,"685":1,"690":1,"881":2,"953":1,"1264":1,"1270":1,"1323":12,"1382":1,"1384":1,"1394":32,"1395":9,"1401":13,"1414":3,"1415":3,"1435":3,"1437":2,"1489":1,"1523":2,"1525":2,"1530":1,"1590":2,"1598":2,"1626":2,"1642":1,"1668":1,"1711":1,"1712":2,"1749":4,"1772":4,"2078":1}}],["cardsperpage",{"3":{"1394":1}}],["cardstyle",{"3":{"1323":1}}],["cardselector",{"3":{"1323":2}}],["cards",{"3":{"0":1,"1323":2,"1382":2,"1394":10,"1395":1,"1401":3,"1413":1,"1414":1,"1576":1,"1713":1,"1741":1,"1757":1,"1772":1}}],["carriage",{"3":{"885":1,"947":1}}],["carriertrackingurls",{"3":{"1766":1}}],["carriers",{"3":{"1285":1,"1299":1,"1312":1}}],["carrier",{"0":{"1219":1},"3":{"0":1,"340":1,"342":1,"672":1,"905":1,"1042":1,"1124":1,"1219":1,"1229":1,"1247":3,"1285":8,"1287":1,"1299":7,"1308":1,"1311":1,"1317":2,"1322":1,"1323":2,"1338":1,"1349":3,"1358":3,"1401":2,"1416":3,"1748":3,"1749":1,"1762":1,"1763":3,"1764":7,"1765":1,"1766":2,"1767":2,"1768":2,"1769":1,"1775":2,"1804":1,"2185":1}}],["carried",{"3":{"0":3,"77":1,"94":1,"99":2,"135":1,"201":2,"258":1,"265":1,"318":1,"400":1,"407":1,"447":1,"448":1,"455":1,"463":1,"464":2,"465":2,"492":1,"529":1,"536":1,"607":1,"626":1,"702":2,"707":1,"719":1,"743":1,"747":1,"800":1,"827":1,"839":1,"853":1,"867":1,"872":1,"921":1,"973":1,"980":1,"1006":2,"1010":2,"1012":1,"1014":3,"1024":1,"1054":1,"1065":1,"1075":1,"1099":2,"1212":1,"1229":2,"1247":2,"1263":1,"1264":1,"1270":1,"1285":8,"1299":15,"1310":5,"1311":1,"1320":1,"1322":8,"1323":8,"1338":4,"1343":1,"1348":1,"1349":7,"1358":14,"1368":3,"1379":3,"1394":8,"1395":16,"1401":3,"1412":1,"1414":7,"1416":4,"1426":1,"1431":1,"1435":5,"1454":1,"1466":3,"1477":1,"1488":1,"1495":1,"1525":1,"1530":1,"1577":1,"1590":1,"1633":1,"1736":1,"1763":1,"1804":1,"1824":1,"1853":1,"1982":1,"1993":1,"2042":1,"2049":1,"2125":1,"2129":1,"2130":1,"2166":1,"2175":1,"2202":1}}],["carrieshardenedsecurityheaders",{"3":{"1435":1}}],["carriesanactivehealthcheckprobingalive",{"3":{"1435":1}}],["carriesaservicetag",{"3":{"1435":1}}],["carriesnoinlinestyles",{"3":{"1435":2}}],["carriesservicecontractattribute",{"3":{"1311":1,"1435":3}}],["carries",{"0":{"95":1},"3":{"0":35,"1":1,"23":1,"24":1,"26":2,"39":2,"54":1,"55":1,"57":1,"61":1,"68":1,"72":1,"73":1,"78":1,"91":2,"93":1,"95":2,"109":2,"111":1,"115":1,"122":1,"123":1,"125":1,"126":2,"132":1,"135":1,"140":2,"141":1,"142":1,"149":1,"157":1,"165":1,"166":1,"168":1,"171":1,"173":1,"175":1,"180":2,"184":1,"186":3,"188":1,"189":1,"195":3,"197":1,"200":1,"212":1,"214":2,"215":1,"216":1,"217":1,"219":1,"220":1,"225":1,"229":2,"243":1,"255":2,"258":1,"259":1,"265":2,"275":1,"282":1,"284":1,"293":1,"295":1,"300":1,"302":1,"305":1,"308":1,"318":3,"324":1,"325":2,"326":1,"333":1,"341":5,"343":1,"346":2,"348":1,"349":1,"350":4,"353":3,"359":1,"360":1,"365":1,"372":1,"373":1,"374":1,"380":1,"384":1,"388":1,"392":1,"395":2,"399":1,"400":1,"401":1,"402":2,"403":1,"404":2,"405":1,"406":1,"407":1,"408":1,"418":3,"422":1,"423":2,"426":2,"439":1,"448":2,"451":1,"452":1,"458":1,"459":1,"461":1,"462":1,"470":1,"471":1,"474":1,"475":1,"476":1,"477":1,"484":1,"487":1,"488":2,"489":2,"490":2,"491":1,"492":2,"493":3,"494":4,"495":2,"499":1,"528":2,"536":2,"539":1,"543":1,"545":4,"546":1,"547":2,"549":2,"551":1,"556":1,"558":1,"564":3,"565":1,"572":4,"575":1,"578":1,"591":1,"599":5,"601":1,"604":1,"605":1,"607":2,"609":4,"618":2,"624":1,"625":1,"626":4,"631":1,"633":4,"634":1,"638":1,"640":3,"642":2,"649":1,"651":2,"656":1,"657":4,"659":1,"664":1,"668":1,"676":1,"689":1,"690":3,"691":2,"692":3,"698":3,"700":1,"702":1,"707":2,"709":1,"710":1,"713":1,"714":2,"720":1,"725":4,"733":1,"737":2,"741":1,"749":1,"757":2,"764":1,"766":4,"781":1,"782":7,"783":1,"784":2,"788":1,"790":4,"792":1,"793":1,"794":1,"799":2,"803":1,"804":2,"805":1,"806":1,"815":1,"818":1,"819":1,"825":1,"826":1,"827":2,"830":1,"833":2,"838":1,"848":1,"849":2,"850":1,"852":1,"854":1,"857":1,"859":1,"861":2,"876":2,"881":3,"889":1,"899":1,"903":1,"905":6,"907":1,"916":1,"920":1,"922":3,"924":1,"926":2,"931":1,"939":2,"944":1,"946":1,"948":2,"954":2,"959":1,"960":1,"962":1,"964":2,"971":1,"972":3,"988":1,"996":1,"1004":6,"1008":1,"1011":1,"1012":2,"1014":1,"1017":1,"1018":7,"1019":1,"1021":1,"1026":1,"1029":1,"1034":1,"1041":2,"1055":1,"1058":1,"1059":4,"1062":2,"1065":1,"1067":4,"1068":1,"1069":1,"1073":1,"1074":1,"1083":2,"1091":1,"1094":1,"1096":1,"1116":3,"1118":1,"1123":1,"1124":2,"1125":1,"1133":1,"1135":1,"1140":2,"1150":1,"1157":1,"1162":1,"1168":1,"1173":1,"1177":1,"1184":1,"1211":1,"1212":7,"1213":1,"1217":1,"1220":2,"1225":1,"1228":2,"1229":12,"1231":1,"1233":1,"1237":18,"1238":1,"1240":1,"1241":2,"1242":1,"1247":7,"1249":1,"1253":4,"1259":15,"1262":1,"1263":1,"1264":2,"1265":3,"1266":1,"1270":22,"1271":7,"1272":1,"1273":2,"1275":3,"1276":3,"1277":1,"1278":3,"1279":1,"1280":1,"1283":1,"1284":3,"1285":50,"1287":1,"1288":1,"1289":2,"1290":1,"1291":1,"1293":1,"1295":1,"1296":3,"1298":1,"1299":64,"1300":5,"1302":1,"1303":2,"1304":2,"1306":1,"1307":1,"1308":20,"1309":2,"1310":50,"1311":7,"1315":1,"1319":3,"1321":1,"1322":47,"1323":63,"1324":1,"1327":2,"1329":1,"1332":2,"1336":1,"1337":1,"1338":41,"1341":5,"1342":1,"1344":2,"1346":3,"1347":1,"1348":1,"1349":73,"1351":4,"1352":2,"1353":1,"1356":2,"1357":1,"1358":156,"1365":4,"1366":1,"1368":11,"1370":1,"1372":4,"1373":1,"1375":2,"1376":1,"1378":1,"1379":51,"1383":1,"1385":1,"1388":3,"1390":2,"1391":1,"1392":1,"1393":1,"1394":51,"1395":130,"1396":2,"1397":1,"1398":3,"1399":1,"1401":42,"1403":1,"1404":2,"1405":3,"1406":4,"1407":4,"1409":2,"1410":2,"1412":1,"1413":2,"1414":51,"1415":8,"1416":21,"1420":1,"1421":1,"1422":1,"1424":2,"1426":7,"1427":1,"1429":1,"1430":2,"1431":1,"1435":110,"1437":18,"1438":1,"1440":5,"1441":1,"1442":1,"1443":2,"1444":1,"1446":4,"1448":2,"1452":1,"1453":1,"1454":9,"1455":3,"1457":1,"1458":3,"1459":2,"1464":2,"1466":19,"1469":1,"1472":1,"1473":3,"1474":2,"1475":1,"1481":1,"1487":3,"1488":3,"1489":2,"1490":1,"1491":4,"1495":3,"1503":2,"1507":1,"1511":1,"1512":1,"1516":1,"1521":1,"1524":1,"1525":4,"1530":1,"1533":2,"1534":1,"1546":1,"1547":1,"1576":12,"1581":2,"1584":1,"1588":2,"1590":10,"1595":5,"1596":1,"1598":4,"1599":1,"1626":1,"1630":5,"1633":1,"1637":1,"1639":1,"1640":3,"1642":1,"1650":1,"1652":2,"1653":1,"1655":2,"1659":1,"1664":1,"1665":1,"1669":1,"1672":1,"1676":1,"1682":1,"1683":1,"1684":1,"1685":2,"1692":1,"1700":1,"1701":1,"1702":2,"1711":1,"1718":1,"1721":1,"1726":1,"1727":1,"1747":5,"1748":2,"1749":1,"1750":1,"1754":1,"1757":2,"1759":3,"1763":2,"1766":1,"1768":7,"1770":2,"1771":1,"1772":2,"1775":1,"1798":3,"1804":3,"1809":1,"1814":3,"1820":3,"1824":2,"1830":1,"1838":1,"1839":2,"1848":2,"1850":1,"1853":1,"1855":1,"1869":1,"1870":1,"1873":1,"1875":1,"1876":1,"1881":1,"1887":1,"1888":1,"1901":1,"1907":2,"1908":1,"1915":1,"1917":1,"1921":1,"1924":1,"1926":2,"1932":2,"1933":1,"1938":1,"1939":1,"1942":1,"1944":1,"1946":1,"1949":1,"1961":2,"1963":1,"1964":1,"1965":1,"1967":2,"1972":1,"1974":3,"1976":1,"1981":1,"1991":1,"1993":1,"1994":1,"1996":1,"2012":1,"2031":1,"2033":1,"2045":1,"2054":1,"2055":1,"2056":2,"2058":1,"2062":2,"2063":1,"2072":1,"2073":1,"2078":2,"2081":1,"2086":1,"2088":1,"2096":1,"2104":1,"2106":2,"2109":2,"2123":1,"2125":1,"2129":1,"2130":4,"2131":1,"2136":1,"2137":2,"2149":1,"2150":1,"2151":1,"2155":4,"2157":1,"2163":1,"2164":1,"2165":1,"2172":1,"2176":2,"2179":1,"2182":2,"2185":1,"2193":1,"2196":2,"2226":1,"2229":1,"2231":7}}],["carrynomutablestaticstate",{"3":{"1435":3}}],["carrytheservicetagoftheowningapp",{"3":{"1435":1}}],["carrythetightratelimiterpolicy",{"2":{"180":1},"3":{"180":1}}],["carry",{"0":{"1235":1},"3":{"0":13,"1":2,"15":1,"24":1,"71":1,"81":1,"91":1,"98":1,"99":1,"109":1,"125":1,"126":1,"135":3,"136":1,"138":2,"139":1,"142":1,"147":1,"155":1,"161":1,"170":2,"176":1,"177":2,"179":1,"186":2,"195":1,"200":1,"207":2,"217":1,"225":1,"241":1,"259":1,"260":1,"261":1,"265":3,"279":1,"291":1,"300":1,"318":4,"324":2,"341":2,"343":2,"345":1,"350":1,"352":1,"355":1,"359":1,"361":1,"387":1,"390":1,"396":1,"401":2,"403":2,"404":1,"407":1,"419":1,"438":1,"443":1,"446":1,"450":1,"451":1,"461":1,"470":1,"490":1,"497":1,"505":1,"526":1,"530":1,"536":1,"543":1,"549":2,"558":1,"564":1,"572":1,"578":2,"583":1,"591":1,"594":1,"599":2,"603":1,"607":2,"609":3,"611":1,"618":2,"626":1,"629":1,"634":1,"635":1,"636":1,"640":1,"649":1,"657":1,"658":1,"659":1,"661":1,"668":1,"689":1,"690":2,"698":2,"718":1,"719":1,"724":1,"739":2,"741":1,"742":1,"743":1,"755":1,"764":1,"766":1,"774":1,"782":1,"783":1,"799":3,"801":1,"802":1,"826":1,"828":1,"831":1,"832":1,"838":3,"869":1,"873":1,"881":1,"884":1,"891":1,"905":1,"907":2,"926":1,"963":1,"964":1,"972":1,"982":1,"996":1,"1011":1,"1018":1,"1020":1,"1026":1,"1034":1,"1042":1,"1043":1,"1044":1,"1050":1,"1083":1,"1085":2,"1091":1,"1110":1,"1116":4,"1125":1,"1126":1,"1127":1,"1141":1,"1155":1,"1175":1,"1184":1,"1185":3,"1212":3,"1228":1,"1229":4,"1231":1,"1234":1,"1236":1,"1237":11,"1247":3,"1252":1,"1253":1,"1255":1,"1259":6,"1264":1,"1265":1,"1266":1,"1270":13,"1271":4,"1275":1,"1276":1,"1284":1,"1285":31,"1289":1,"1296":1,"1299":30,"1300":3,"1308":5,"1310":25,"1311":4,"1316":1,"1317":2,"1318":1,"1322":8,"1323":28,"1327":2,"1336":1,"1338":17,"1341":1,"1343":2,"1344":3,"1346":1,"1349":18,"1352":1,"1358":65,"1359":1,"1361":2,"1365":1,"1368":8,"1370":1,"1372":2,"1376":1,"1379":14,"1381":1,"1383":1,"1387":2,"1388":1,"1389":1,"1391":2,"1394":33,"1395":29,"1398":1,"1400":1,"1401":16,"1404":1,"1414":24,"1415":2,"1416":4,"1422":1,"1426":1,"1427":1,"1430":1,"1433":1,"1435":44,"1437":6,"1442":2,"1443":1,"1444":1,"1446":4,"1447":1,"1452":2,"1453":1,"1454":2,"1455":1,"1457":1,"1464":2,"1466":14,"1468":1,"1472":1,"1473":1,"1475":3,"1480":1,"1484":1,"1488":3,"1490":1,"1495":3,"1525":4,"1533":1,"1535":1,"1542":1,"1574":1,"1575":1,"1576":3,"1577":1,"1581":1,"1584":1,"1585":1,"1590":2,"1595":1,"1620":1,"1625":1,"1626":2,"1629":1,"1630":1,"1631":2,"1634":1,"1640":3,"1653":1,"1661":1,"1664":1,"1665":1,"1671":1,"1685":1,"1687":1,"1722":1,"1726":1,"1747":2,"1756":1,"1757":1,"1758":1,"1759":1,"1760":2,"1763":1,"1764":4,"1766":2,"1770":1,"1829":1,"1838":1,"1850":1,"1875":1,"1882":1,"1901":1,"1902":1,"1907":1,"1910":1,"1937":1,"1941":1,"1945":1,"1947":1,"1948":1,"1949":2,"1962":3,"1963":1,"1968":1,"1973":1,"1977":1,"1992":1,"1994":3,"1995":1,"2000":1,"2017":1,"2030":1,"2045":1,"2046":1,"2055":2,"2069":1,"2074":1,"2078":1,"2085":1,"2097":1,"2125":1,"2130":2,"2141":1,"2144":1,"2153":2,"2155":1,"2159":1,"2163":1,"2188":1,"2190":1,"2191":3,"2197":1,"2202":1,"2231":1}}],["carrying",{"3":{"0":4,"20":1,"53":1,"81":1,"109":2,"110":1,"111":1,"117":1,"122":1,"128":1,"147":1,"160":1,"180":1,"189":1,"193":1,"202":1,"212":1,"217":1,"225":2,"231":1,"243":2,"244":1,"247":1,"265":1,"305":1,"318":1,"328":1,"344":1,"349":1,"350":4,"365":1,"370":1,"387":1,"413":1,"420":1,"443":2,"448":1,"499":1,"538":1,"557":1,"609":1,"618":1,"626":1,"632":1,"688":1,"690":2,"715":2,"725":3,"740":1,"741":2,"782":1,"790":1,"804":1,"805":1,"810":1,"813":1,"827":1,"860":1,"863":1,"876":1,"881":1,"897":1,"914":1,"920":2,"921":1,"926":1,"945":1,"966":1,"971":1,"980":2,"987":1,"988":2,"996":1,"1004":1,"1026":2,"1034":1,"1042":1,"1049":1,"1050":1,"1051":1,"1054":1,"1067":1,"1119":1,"1168":1,"1175":1,"1212":2,"1219":1,"1223":1,"1224":1,"1225":1,"1229":9,"1237":8,"1239":1,"1240":1,"1241":1,"1243":1,"1245":1,"1247":4,"1249":1,"1256":2,"1259":6,"1263":3,"1265":1,"1266":1,"1270":16,"1271":3,"1273":1,"1275":1,"1277":1,"1283":1,"1285":26,"1293":1,"1296":2,"1299":22,"1300":2,"1303":1,"1304":1,"1308":11,"1309":2,"1310":20,"1311":5,"1316":1,"1317":1,"1319":1,"1322":26,"1323":28,"1331":1,"1334":1,"1335":1,"1338":11,"1346":1,"1348":1,"1349":8,"1351":1,"1352":1,"1357":1,"1358":37,"1360":1,"1363":1,"1368":3,"1372":1,"1373":2,"1377":1,"1378":1,"1379":12,"1383":2,"1388":1,"1390":1,"1394":28,"1395":47,"1397":1,"1401":21,"1404":1,"1405":1,"1408":1,"1409":1,"1414":27,"1415":6,"1416":10,"1422":2,"1425":1,"1426":1,"1429":2,"1433":1,"1435":53,"1437":8,"1445":1,"1451":1,"1452":1,"1457":1,"1464":1,"1466":2,"1487":2,"1488":2,"1491":1,"1533":1,"1576":2,"1626":1,"1630":1,"1640":1,"1650":1,"1665":2,"1666":1,"1684":2,"1701":1,"1722":1,"1748":2,"1749":1,"1764":1,"1768":3,"1789":1,"1791":1,"1804":2,"1805":1,"1814":1,"1820":1,"1839":1,"1864":1,"1870":1,"1907":1,"1908":1,"1921":2,"1923":1,"1926":3,"1928":1,"1932":2,"1936":1,"1973":1,"1974":1,"1975":2,"1976":2,"1977":1,"1987":1,"1993":1,"1994":1,"2017":1,"2035":2,"2054":1,"2056":1,"2082":1,"2109":1,"2125":1,"2135":1,"2148":1,"2157":1,"2174":1,"2185":1,"2229":1}}],["calcifies",{"3":{"1787":2}}],["calculation",{"3":{"1797":1,"2074":1}}],["calculations",{"3":{"1229":1}}],["calculated",{"3":{"1630":1,"1763":1}}],["calculateduration",{"2":{"1341":1},"3":{"1341":1,"1349":1}}],["calculatesimilarity",{"3":{"1358":2}}],["calculatejaccardindex",{"3":{"1358":1}}],["calmly",{"3":{"1395":1}}],["calm",{"3":{"1395":2}}],["calscale",{"3":{"1299":1}}],["calibrate",{"3":{"1229":1}}],["calibrated",{"3":{"0":1,"861":3,"1435":2,"1595":1}}],["calibration",{"3":{"862":1,"1263":1,"1338":1,"1368":1,"1576":2,"1577":1,"1591":1}}],["calibrating",{"3":{"862":1,"1237":1}}],["calendarexportmappertests",{"3":{"1199":1,"1207":2,"1358":2}}],["calendarexportmapper",{"2":{"1355":1},"3":{"1199":6,"1203":1,"1207":2,"1299":5,"1349":3,"1355":4,"1358":16,"1395":1,"1525":1}}],["calendars",{"0":{"1374":1},"3":{"1043":2,"1203":2,"1207":6,"1299":10,"1358":4,"1495":1}}],["calendar",{"0":{"1355":1},"3":{"706":1,"1192":2,"1299":13,"1323":4,"1349":1,"1350":1,"1355":1,"1358":18,"1374":3,"1379":7,"1395":2,"1435":4,"1437":1,"1454":2,"1495":2,"1534":1,"1630":1,"1639":1,"1659":1,"1947":2}}],["callout",{"3":{"1495":1}}],["callvirt",{"3":{"1435":3}}],["callcount",{"3":{"1426":1}}],["calldurationhistogramname",{"3":{"1426":1}}],["calldeadline",{"3":{"1308":1,"1311":1,"1379":3,"1395":3,"1414":2}}],["callrefreshasync",{"3":{"1299":1}}],["callgraphindex",{"3":{"1199":2,"1207":2,"1435":12,"1488":1,"1670":1}}],["callable",{"3":{"265":1,"497":1,"1271":1,"1285":3,"1358":3,"1394":3,"1395":1,"1416":1,"1435":1,"1547":1}}],["callee",{"3":{"39":1,"1161":1,"1323":1,"1435":7}}],["callees",{"3":{"39":1,"1435":2}}],["called",{"3":{"13":1,"39":2,"81":1,"92":1,"109":2,"117":1,"122":1,"201":1,"203":1,"243":1,"259":1,"260":1,"265":1,"268":1,"284":1,"325":2,"326":2,"336":1,"341":1,"344":1,"350":1,"397":2,"400":1,"407":1,"413":1,"423":3,"426":2,"428":2,"497":1,"499":2,"591":1,"652":1,"674":1,"697":1,"698":2,"717":1,"741":1,"743":1,"822":1,"837":1,"875":1,"881":1,"889":2,"905":1,"908":1,"926":2,"931":2,"947":1,"1006":1,"1108":1,"1170":1,"1175":1,"1191":1,"1229":4,"1237":9,"1247":9,"1254":1,"1259":9,"1262":1,"1263":1,"1266":1,"1270":13,"1273":1,"1274":1,"1280":1,"1285":20,"1296":1,"1299":18,"1300":3,"1306":1,"1308":14,"1309":2,"1310":24,"1311":5,"1314":2,"1315":1,"1317":1,"1322":22,"1323":22,"1332":1,"1334":1,"1338":6,"1349":9,"1351":1,"1358":30,"1368":6,"1377":1,"1379":2,"1385":1,"1388":1,"1394":13,"1395":30,"1401":5,"1408":1,"1414":11,"1415":3,"1416":16,"1426":5,"1435":21,"1441":4,"1442":2,"1456":1,"1459":1,"1466":1,"1472":1,"1473":1,"1487":2,"1495":2,"1525":1,"1555":1,"1599":2,"1661":1,"1779":1,"1824":1,"1908":1,"1924":1,"1935":2,"1947":1,"1953":1,"1981":2,"1993":1,"1994":1,"2042":1,"2104":1,"2118":1,"2155":2,"2163":1,"2166":2}}],["callerfilepathattribute",{"3":{"1435":1}}],["callerfilepath",{"3":{"1431":1,"1435":3}}],["calleruserid",{"3":{"1401":4}}],["callerhasprivilegedrole",{"3":{"1322":2}}],["callermembername",{"3":{"1311":2,"1379":1}}],["callerisorganizer",{"2":{"1353":1},"3":{"1270":3,"1353":1,"1358":6,"1372":1,"1379":1,"1401":15}}],["callerspeakerid",{"3":{"1401":15}}],["callerscopedquerycachetests",{"3":{"1199":1,"1207":5,"1264":1,"1270":2}}],["callers",{"3":{"0":6,"33":1,"109":1,"157":1,"175":1,"230":1,"243":1,"244":1,"258":1,"265":1,"320":1,"335":1,"388":1,"435":1,"449":1,"481":1,"497":1,"502":1,"507":1,"513":1,"543":1,"545":1,"550":2,"552":1,"659":1,"674":1,"827":1,"889":1,"899":1,"980":1,"988":1,"995":1,"996":1,"998":1,"1153":1,"1155":2,"1176":1,"1212":2,"1220":1,"1229":1,"1236":1,"1237":2,"1239":1,"1241":1,"1242":1,"1243":1,"1247":10,"1259":2,"1263":1,"1267":1,"1270":12,"1271":2,"1278":1,"1285":20,"1299":13,"1300":7,"1301":1,"1308":7,"1310":15,"1311":2,"1315":1,"1322":7,"1323":28,"1336":1,"1338":4,"1349":13,"1358":24,"1368":2,"1372":1,"1374":1,"1379":4,"1394":6,"1395":13,"1401":2,"1414":8,"1415":1,"1416":20,"1429":1,"1435":4,"1437":3,"1440":1,"1441":1,"1442":1,"1466":1,"1495":1,"1595":1,"1629":1,"1630":2,"1634":3,"1638":1,"1639":2,"1700":1,"1734":1,"1748":1,"1789":1,"1806":1,"1814":2,"1907":1,"1911":1,"1914":1,"1921":1,"1936":1,"1986":1,"1996":1,"2023":1,"2125":1,"2132":1,"2231":2}}],["caller",{"0":{"1936":1,"2184":1},"2":{"1843":1},"3":{"0":25,"31":1,"39":2,"42":2,"43":1,"53":1,"72":1,"90":1,"95":1,"96":1,"103":2,"107":1,"108":2,"109":5,"110":3,"111":1,"115":1,"122":3,"123":2,"125":2,"126":2,"135":1,"147":1,"157":5,"158":1,"159":2,"173":2,"175":3,"177":2,"178":1,"179":2,"180":3,"184":1,"186":2,"189":1,"201":1,"225":3,"228":2,"230":2,"231":1,"235":2,"241":2,"243":3,"246":5,"259":1,"284":1,"317":2,"318":12,"324":2,"330":1,"333":3,"340":1,"341":2,"342":3,"344":2,"350":1,"359":1,"365":1,"383":1,"384":2,"387":2,"388":3,"389":1,"400":1,"435":1,"447":2,"448":2,"449":2,"450":1,"459":2,"461":1,"465":1,"481":1,"490":1,"507":2,"517":1,"518":2,"519":1,"538":1,"544":1,"549":7,"550":1,"551":2,"552":1,"572":1,"597":1,"598":1,"599":3,"600":1,"619":1,"649":1,"656":1,"657":1,"658":1,"660":1,"674":2,"682":1,"683":1,"684":3,"690":2,"715":1,"717":2,"725":2,"727":3,"733":4,"735":2,"740":3,"741":1,"743":1,"749":1,"751":1,"766":1,"769":1,"789":1,"790":2,"791":2,"792":2,"793":1,"825":1,"826":2,"827":12,"829":1,"833":1,"854":1,"861":3,"864":1,"881":5,"896":1,"897":12,"899":1,"905":12,"906":2,"908":1,"921":2,"922":2,"924":1,"926":3,"948":1,"980":1,"986":1,"987":1,"988":5,"989":1,"990":2,"992":2,"996":2,"997":2,"998":1,"1018":1,"1019":1,"1021":1,"1026":1,"1042":3,"1043":1,"1050":1,"1059":2,"1091":3,"1092":2,"1093":2,"1100":2,"1101":2,"1116":2,"1123":1,"1124":1,"1126":2,"1132":1,"1133":3,"1140":1,"1150":1,"1161":2,"1168":1,"1169":2,"1176":1,"1212":2,"1219":1,"1227":1,"1228":3,"1229":16,"1231":1,"1232":1,"1237":19,"1238":2,"1241":4,"1242":3,"1243":1,"1244":1,"1247":43,"1251":1,"1253":1,"1259":18,"1262":1,"1263":6,"1264":8,"1265":5,"1267":6,"1269":1,"1270":100,"1271":20,"1273":1,"1276":3,"1278":1,"1282":1,"1284":2,"1285":130,"1286":2,"1288":1,"1289":2,"1292":2,"1295":2,"1296":4,"1298":1,"1299":120,"1300":18,"1301":1,"1303":2,"1304":1,"1305":2,"1306":2,"1307":2,"1308":46,"1309":7,"1310":87,"1311":13,"1317":1,"1321":1,"1322":82,"1323":111,"1325":1,"1331":1,"1336":1,"1338":38,"1342":1,"1345":3,"1347":2,"1349":46,"1351":1,"1352":2,"1355":1,"1356":6,"1357":2,"1358":252,"1364":1,"1365":2,"1367":2,"1368":6,"1371":1,"1372":4,"1374":3,"1375":1,"1377":2,"1378":2,"1379":65,"1388":3,"1394":37,"1395":195,"1398":3,"1400":5,"1401":126,"1406":1,"1407":1,"1409":2,"1410":2,"1413":1,"1414":62,"1415":3,"1416":55,"1418":1,"1421":5,"1422":10,"1423":1,"1424":1,"1425":1,"1426":27,"1431":2,"1434":1,"1435":68,"1436":1,"1437":17,"1446":1,"1454":2,"1455":2,"1466":3,"1486":1,"1487":2,"1495":1,"1506":1,"1507":1,"1547":1,"1552":2,"1576":2,"1588":1,"1590":1,"1595":1,"1626":1,"1630":4,"1631":8,"1638":1,"1639":4,"1640":2,"1652":2,"1655":1,"1662":1,"1664":1,"1665":1,"1670":1,"1671":2,"1685":2,"1697":1,"1702":1,"1707":2,"1722":2,"1736":1,"1747":2,"1758":1,"1763":2,"1764":7,"1766":2,"1770":1,"1802":3,"1803":1,"1804":3,"1805":1,"1806":2,"1809":1,"1812":1,"1814":3,"1816":1,"1820":6,"1822":1,"1823":1,"1824":1,"1830":2,"1838":1,"1839":1,"1843":3,"1851":1,"1856":1,"1870":1,"1872":1,"1873":1,"1874":1,"1875":2,"1876":3,"1882":1,"1897":1,"1907":2,"1908":5,"1910":2,"1911":1,"1918":4,"1919":6,"1921":3,"1922":3,"1923":3,"1924":2,"1925":1,"1926":1,"1929":1,"1932":2,"1933":2,"1936":2,"1937":1,"1939":1,"1940":2,"1943":1,"1944":2,"1946":2,"1949":2,"1961":1,"1962":1,"1981":4,"1987":1,"1990":1,"1991":1,"1992":3,"1993":10,"1994":6,"1995":1,"1996":2,"1999":3,"2001":2,"2015":1,"2018":1,"2023":6,"2037":1,"2054":1,"2056":1,"2065":1,"2077":1,"2089":1,"2095":1,"2116":2,"2128":2,"2129":2,"2130":2,"2131":1,"2132":1,"2136":1,"2138":1,"2142":1,"2164":1,"2165":3,"2167":1,"2168":1,"2170":1,"2173":4,"2174":2,"2178":2,"2179":2,"2184":2,"2189":1,"2192":2,"2195":2,"2196":1}}],["callbackpath",{"3":{"1310":1}}],["callbackscheme",{"3":{"1415":2}}],["callbacks",{"3":{"180":1,"248":1,"413":1,"744":1,"1259":1,"1322":1,"1323":4,"1384":1,"1389":1,"1394":2,"1401":8,"1415":5,"1416":5,"1446":1,"1554":1,"1559":1,"1972":1}}],["callback",{"1":{"417":1,"418":1,"419":1,"420":1,"421":1,"422":1,"423":1,"424":1,"425":1,"426":1,"427":1,"428":1,"429":1,"430":1},"3":{"0":6,"243":1,"248":3,"265":1,"348":1,"350":4,"351":1,"355":1,"383":1,"397":1,"413":2,"417":1,"418":2,"420":3,"422":1,"427":2,"428":1,"463":1,"518":1,"556":3,"733":1,"831":2,"832":3,"1100":1,"1128":1,"1212":1,"1223":1,"1229":5,"1243":1,"1247":3,"1259":1,"1285":1,"1299":4,"1300":15,"1308":1,"1309":3,"1310":14,"1315":1,"1322":9,"1323":32,"1332":1,"1338":9,"1379":1,"1390":1,"1394":25,"1395":8,"1399":1,"1401":3,"1405":1,"1414":5,"1415":8,"1416":33,"1435":2,"1442":2,"1495":1,"1598":1,"1661":1,"1666":1,"1668":1,"1867":1,"1882":1,"1897":1,"1915":1,"1943":1,"1944":1,"1946":1,"1972":2,"1974":5,"1975":1,"1978":1,"2073":1,"2120":2,"2121":1,"2231":1}}],["calling",{"3":{"0":5,"91":1,"115":1,"214":1,"224":1,"237":2,"243":1,"265":2,"283":1,"310":1,"380":1,"409":1,"418":1,"459":1,"638":1,"657":1,"676":1,"696":1,"700":1,"733":2,"735":1,"741":1,"751":1,"766":2,"840":1,"880":1,"881":1,"906":1,"912":1,"922":1,"924":1,"959":1,"960":1,"979":1,"1012":1,"1016":1,"1017":1,"1018":1,"1020":1,"1042":1,"1090":2,"1134":1,"1184":1,"1212":1,"1229":2,"1232":1,"1234":1,"1237":5,"1241":1,"1251":1,"1259":5,"1263":1,"1270":13,"1271":3,"1275":1,"1285":24,"1290":1,"1299":11,"1300":2,"1303":1,"1308":8,"1310":13,"1311":2,"1314":1,"1315":1,"1316":1,"1322":11,"1323":17,"1335":1,"1338":12,"1349":7,"1352":1,"1358":33,"1364":1,"1365":1,"1368":4,"1375":1,"1377":1,"1379":7,"1394":11,"1395":12,"1401":11,"1414":5,"1415":2,"1416":13,"1424":1,"1426":5,"1431":1,"1435":18,"1437":2,"1466":1,"1475":1,"1487":2,"1525":2,"1539":1,"1552":1,"1576":1,"1577":1,"1640":1,"1686":1,"1789":1,"1804":1,"1820":1,"1843":1,"1881":1,"1913":1,"1943":1,"1982":1,"2018":1,"2026":1,"2073":1,"2084":1,"2112":1,"2149":2,"2169":1,"2170":1,"2184":2,"2231":1}}],["calls",{"1":{"50":1,"51":1,"52":1,"53":1,"54":1,"55":1},"3":{"0":17,"26":1,"27":2,"35":1,"39":2,"41":1,"50":1,"52":2,"57":1,"59":2,"64":1,"74":1,"96":1,"105":1,"120":1,"127":1,"135":1,"175":1,"179":1,"180":1,"185":1,"186":1,"195":2,"200":1,"201":1,"202":1,"206":1,"207":1,"209":1,"214":1,"215":1,"220":1,"225":1,"226":1,"230":1,"243":3,"245":1,"255":2,"256":3,"257":1,"258":2,"259":3,"260":5,"261":3,"279":1,"280":1,"281":2,"283":1,"284":1,"285":1,"303":1,"308":1,"310":1,"318":1,"326":1,"340":1,"341":2,"343":1,"350":4,"354":1,"359":1,"362":1,"370":2,"374":1,"387":1,"390":1,"391":1,"395":2,"397":1,"401":1,"402":3,"405":1,"413":1,"418":1,"420":2,"423":2,"425":1,"426":1,"428":2,"429":1,"430":1,"434":1,"435":1,"453":1,"459":2,"461":1,"464":1,"492":1,"493":1,"494":1,"497":2,"499":2,"502":1,"506":1,"507":1,"517":1,"518":1,"519":1,"522":2,"523":3,"524":1,"534":1,"536":2,"539":3,"545":1,"546":1,"550":2,"551":1,"552":1,"572":1,"583":4,"585":1,"599":2,"603":1,"609":1,"626":1,"640":2,"642":1,"644":1,"649":2,"651":1,"665":2,"667":1,"674":3,"677":2,"682":1,"684":1,"690":1,"696":1,"698":2,"705":1,"707":1,"708":1,"715":3,"724":1,"725":1,"733":4,"749":5,"750":1,"774":2,"819":3,"826":1,"827":2,"829":1,"832":1,"836":1,"837":2,"838":2,"839":2,"857":2,"861":1,"880":1,"881":1,"883":2,"884":1,"885":1,"905":1,"913":1,"922":2,"923":1,"926":1,"946":1,"954":2,"955":1,"956":1,"964":1,"972":1,"979":2,"987":2,"988":2,"996":1,"1011":1,"1012":3,"1013":1,"1017":2,"1018":5,"1020":1,"1022":1,"1034":1,"1042":2,"1050":2,"1052":1,"1055":1,"1059":3,"1062":1,"1090":3,"1091":1,"1093":1,"1094":2,"1095":1,"1100":1,"1101":1,"1108":1,"1133":2,"1161":2,"1162":1,"1168":2,"1179":1,"1183":2,"1185":1,"1202":2,"1203":2,"1212":6,"1213":1,"1222":1,"1229":5,"1231":1,"1233":2,"1235":2,"1236":1,"1237":17,"1240":1,"1244":1,"1245":1,"1247":18,"1249":1,"1251":1,"1256":1,"1257":1,"1258":1,"1259":31,"1262":2,"1263":2,"1265":4,"1270":34,"1271":15,"1274":2,"1275":1,"1276":1,"1280":1,"1281":1,"1282":1,"1285":95,"1286":1,"1291":2,"1296":2,"1299":50,"1300":9,"1307":1,"1308":39,"1309":4,"1310":70,"1311":13,"1314":1,"1315":3,"1316":1,"1322":57,"1323":117,"1325":2,"1328":1,"1329":1,"1331":1,"1332":4,"1335":2,"1338":48,"1339":1,"1343":1,"1345":1,"1346":1,"1348":2,"1349":42,"1351":2,"1352":1,"1353":2,"1354":1,"1358":147,"1359":1,"1361":1,"1364":2,"1365":1,"1367":1,"1368":14,"1371":2,"1375":1,"1376":2,"1377":3,"1379":21,"1381":1,"1382":1,"1383":1,"1384":1,"1387":1,"1394":86,"1395":90,"1398":1,"1399":1,"1400":2,"1401":41,"1404":1,"1408":1,"1413":1,"1414":38,"1415":12,"1416":72,"1420":2,"1422":2,"1423":1,"1425":1,"1426":18,"1428":1,"1430":1,"1431":1,"1435":109,"1437":4,"1440":6,"1441":2,"1442":7,"1443":3,"1446":3,"1449":1,"1453":1,"1454":3,"1455":1,"1456":1,"1465":1,"1466":8,"1475":1,"1487":4,"1488":2,"1489":2,"1490":5,"1491":4,"1495":1,"1507":1,"1525":8,"1526":1,"1533":1,"1536":1,"1543":1,"1547":1,"1548":1,"1552":4,"1567":1,"1571":2,"1576":2,"1581":2,"1590":1,"1591":1,"1598":1,"1599":1,"1604":1,"1631":2,"1638":1,"1639":1,"1640":1,"1646":1,"1650":2,"1652":6,"1655":2,"1662":1,"1676":1,"1684":1,"1700":2,"1718":1,"1729":2,"1738":1,"1780":1,"1788":1,"1789":1,"1792":1,"1798":1,"1809":1,"1816":1,"1824":1,"1828":1,"1829":1,"1830":2,"1838":1,"1839":2,"1864":1,"1868":1,"1870":1,"1873":1,"1880":1,"1881":1,"1882":1,"1883":2,"1884":1,"1907":1,"1909":3,"1912":1,"1915":1,"1917":1,"1921":1,"1926":3,"1928":1,"1932":4,"1934":2,"1937":2,"1939":2,"1943":1,"1945":1,"1959":1,"1962":1,"1967":2,"1969":2,"1970":1,"1974":2,"1977":1,"1999":2,"2000":2,"2005":2,"2009":1,"2013":1,"2014":2,"2015":2,"2016":1,"2017":2,"2019":1,"2021":3,"2023":2,"2027":1,"2028":1,"2029":2,"2036":1,"2047":1,"2054":2,"2062":2,"2073":1,"2077":1,"2082":1,"2090":1,"2091":1,"2095":1,"2096":1,"2116":1,"2117":2,"2118":3,"2125":1,"2126":2,"2141":2,"2142":1,"2148":1,"2151":1,"2152":1,"2155":4,"2159":1,"2163":1,"2170":3,"2171":2,"2177":1,"2178":2,"2185":1,"2188":1,"2198":1,"2201":2,"2231":2}}],["call",{"0":{"1421":1,"1422":1,"1424":1,"2155":1,"2165":1,"2171":1},"2":{"629":1,"766":1,"767":1,"1089":1,"1424":1,"1455":1,"1456":1,"1459":1,"2176":1},"3":{"0":39,"17":1,"24":1,"26":1,"27":1,"39":1,"41":1,"53":1,"68":1,"71":3,"73":1,"74":2,"93":1,"94":2,"95":5,"97":1,"98":2,"99":1,"100":3,"104":1,"109":5,"110":1,"111":5,"113":1,"117":1,"121":3,"122":1,"126":1,"127":2,"128":3,"135":3,"156":1,"180":1,"188":2,"200":1,"202":1,"207":1,"208":1,"209":2,"210":1,"216":1,"223":1,"230":7,"231":1,"237":1,"241":1,"243":5,"245":4,"246":1,"250":1,"253":1,"254":1,"255":2,"256":2,"257":1,"258":1,"259":3,"260":3,"261":2,"265":1,"273":1,"279":4,"281":1,"283":1,"285":1,"295":2,"303":1,"308":1,"310":5,"314":2,"320":1,"323":1,"325":2,"330":1,"336":1,"353":1,"361":1,"387":1,"390":2,"391":1,"392":1,"395":2,"397":1,"398":1,"401":3,"407":2,"408":2,"413":1,"418":2,"420":1,"424":1,"426":1,"427":1,"428":1,"429":1,"430":1,"434":3,"438":1,"444":2,"446":3,"448":5,"449":1,"453":1,"454":2,"459":5,"460":1,"463":1,"465":1,"466":2,"472":1,"481":2,"483":1,"489":1,"499":2,"502":1,"507":1,"508":1,"509":1,"511":1,"513":1,"514":1,"517":2,"518":1,"522":3,"523":2,"524":3,"536":2,"538":3,"539":1,"544":1,"545":3,"548":1,"549":4,"550":2,"552":2,"556":4,"559":1,"567":1,"583":1,"584":3,"585":1,"591":1,"592":2,"593":1,"594":1,"599":1,"603":2,"618":2,"626":1,"628":1,"629":2,"632":1,"633":2,"641":1,"642":1,"647":2,"649":3,"650":1,"655":1,"656":1,"657":1,"658":1,"661":1,"663":2,"665":6,"667":1,"668":1,"669":2,"672":2,"674":7,"676":1,"682":1,"690":3,"696":5,"698":2,"700":2,"703":1,"707":2,"715":2,"716":1,"724":2,"725":1,"731":1,"733":7,"735":1,"741":1,"748":3,"749":7,"750":1,"751":2,"752":2,"757":1,"759":1,"766":4,"767":1,"768":1,"774":4,"777":1,"782":1,"784":2,"789":1,"790":1,"791":1,"797":1,"798":1,"799":3,"800":1,"802":1,"803":2,"810":1,"814":2,"819":6,"821":2,"826":2,"827":5,"829":1,"831":1,"832":1,"837":1,"838":1,"846":2,"848":1,"850":2,"854":1,"861":4,"881":6,"882":2,"883":1,"889":2,"890":1,"896":1,"897":9,"899":2,"900":2,"905":1,"913":1,"920":1,"922":2,"924":2,"926":3,"930":1,"934":2,"946":3,"948":1,"949":1,"950":1,"952":1,"954":5,"955":2,"957":1,"964":2,"965":2,"966":1,"972":1,"980":1,"988":4,"989":1,"990":1,"994":2,"995":1,"996":3,"999":1,"1012":1,"1016":1,"1017":2,"1018":8,"1019":3,"1020":2,"1021":1,"1028":2,"1034":1,"1035":1,"1050":2,"1051":4,"1052":1,"1059":1,"1067":1,"1089":2,"1090":1,"1091":7,"1092":5,"1093":5,"1094":1,"1095":1,"1099":1,"1100":1,"1102":2,"1103":1,"1126":1,"1127":1,"1132":2,"1133":3,"1134":3,"1136":1,"1150":1,"1156":1,"1168":3,"1170":1,"1175":3,"1176":3,"1182":1,"1183":1,"1184":1,"1185":1,"1186":3,"1188":1,"1212":5,"1217":1,"1220":2,"1221":2,"1226":1,"1229":10,"1234":1,"1236":2,"1237":20,"1239":1,"1240":1,"1241":1,"1243":1,"1244":2,"1246":1,"1247":54,"1249":1,"1252":2,"1253":2,"1259":25,"1261":1,"1262":6,"1263":5,"1265":2,"1267":1,"1269":1,"1270":41,"1271":14,"1273":3,"1275":1,"1278":3,"1279":1,"1282":1,"1285":122,"1286":1,"1288":1,"1289":1,"1290":1,"1292":2,"1296":1,"1297":2,"1299":77,"1300":19,"1301":2,"1302":1,"1303":1,"1308":26,"1309":10,"1310":78,"1311":24,"1312":1,"1314":2,"1315":4,"1317":4,"1321":1,"1322":86,"1323":179,"1325":1,"1326":1,"1327":1,"1328":1,"1330":3,"1331":2,"1332":3,"1334":1,"1336":2,"1337":3,"1338":95,"1341":1,"1342":1,"1346":2,"1349":60,"1353":1,"1357":4,"1358":209,"1361":3,"1364":2,"1365":9,"1366":1,"1367":4,"1368":25,"1371":1,"1376":1,"1377":2,"1379":39,"1381":2,"1382":1,"1383":2,"1384":1,"1386":2,"1387":2,"1389":3,"1391":2,"1392":1,"1394":129,"1395":155,"1397":2,"1400":1,"1401":38,"1407":1,"1408":1,"1409":2,"1412":2,"1413":1,"1414":76,"1415":18,"1416":79,"1417":3,"1419":2,"1420":3,"1421":2,"1422":4,"1424":4,"1426":55,"1429":1,"1430":1,"1431":1,"1434":1,"1435":158,"1437":14,"1440":7,"1441":8,"1442":5,"1443":4,"1444":1,"1445":2,"1446":3,"1447":1,"1448":1,"1449":1,"1452":1,"1455":3,"1456":1,"1459":2,"1465":1,"1466":5,"1469":1,"1471":1,"1474":3,"1475":1,"1476":1,"1478":1,"1481":2,"1485":1,"1486":1,"1487":4,"1488":5,"1489":4,"1490":1,"1491":1,"1495":3,"1525":9,"1529":1,"1530":2,"1531":1,"1533":1,"1543":1,"1552":3,"1554":1,"1565":2,"1571":1,"1580":1,"1581":2,"1590":2,"1595":1,"1598":1,"1599":1,"1626":3,"1629":1,"1630":1,"1631":4,"1635":1,"1636":1,"1638":1,"1650":1,"1652":4,"1658":1,"1662":1,"1663":1,"1664":1,"1666":1,"1667":3,"1668":1,"1669":2,"1670":1,"1672":1,"1674":1,"1683":4,"1684":2,"1685":3,"1700":1,"1718":2,"1719":1,"1720":1,"1723":1,"1733":1,"1748":1,"1764":3,"1782":1,"1788":2,"1789":5,"1791":1,"1802":1,"1804":1,"1805":5,"1807":1,"1809":4,"1810":1,"1812":1,"1814":5,"1816":2,"1817":1,"1820":1,"1823":2,"1824":2,"1834":1,"1838":2,"1839":1,"1841":1,"1842":1,"1847":1,"1850":1,"1864":3,"1866":1,"1880":2,"1881":1,"1882":3,"1885":2,"1894":1,"1897":1,"1901":1,"1907":2,"1914":3,"1915":3,"1918":2,"1919":1,"1922":2,"1923":2,"1926":1,"1931":1,"1932":1,"1933":2,"1935":1,"1936":1,"1938":1,"1939":2,"1945":1,"1950":1,"1953":1,"1954":1,"1959":1,"1961":1,"1962":2,"1963":1,"1965":1,"1967":1,"1969":3,"1970":3,"1971":1,"1972":1,"1978":1,"1988":1,"1991":1,"1993":1,"1994":3,"1999":3,"2005":2,"2007":1,"2008":1,"2009":1,"2010":1,"2011":1,"2012":1,"2015":1,"2016":2,"2017":1,"2018":3,"2019":1,"2023":3,"2028":1,"2029":4,"2031":3,"2032":1,"2034":1,"2037":1,"2045":1,"2046":1,"2056":1,"2062":2,"2069":1,"2070":1,"2073":2,"2075":1,"2078":2,"2079":1,"2081":1,"2082":2,"2085":1,"2086":1,"2092":1,"2094":1,"2095":1,"2097":1,"2112":1,"2118":2,"2130":3,"2132":2,"2134":1,"2135":2,"2144":1,"2149":2,"2150":1,"2151":1,"2153":2,"2155":5,"2156":1,"2159":3,"2162":1,"2163":1,"2164":2,"2165":1,"2166":3,"2167":2,"2168":1,"2169":3,"2170":2,"2171":2,"2173":5,"2174":2,"2175":1,"2176":2,"2178":5,"2179":6,"2185":1,"2188":2,"2189":1,"2192":1,"2197":1,"2198":1,"2201":3,"2208":1,"2229":1,"2231":2}}],["cachingtestentity",{"3":{"1199":2,"1207":1}}],["cachingdecoratortenantscopingtests",{"3":{"1199":1,"1207":2,"1437":2}}],["cachingdecoratorconstructorselectiontests",{"2":{"1437":1},"3":{"1199":1,"1207":6,"1437":2}}],["cachingcommanddecoratortests",{"3":{"1199":1,"1207":6}}],["cachingcommanddecorator",{"2":{"1260":1,"1752":1,"1773":1,"1917":1},"3":{"698":1,"1199":6,"1203":1,"1207":2,"1260":1,"1263":9,"1267":2,"1270":30,"1300":8,"1322":1,"1358":11,"1435":1,"1752":1,"1773":1,"1917":1}}],["cachingquerydecoratortests",{"3":{"1199":1,"1207":13,"1270":1}}],["cachingquerydecorator",{"2":{"123":1,"246":1,"1223":1,"1267":1,"1916":1,"1922":1},"3":{"123":2,"246":2,"674":2,"698":1,"733":1,"1199":6,"1203":1,"1207":3,"1223":2,"1229":2,"1260":1,"1263":6,"1267":11,"1268":1,"1270":56,"1299":6,"1300":16,"1322":5,"1435":1,"1466":1,"1495":2,"1916":1,"1922":1}}],["caching",{"0":{"1378":1,"1667":1,"1707":1,"1921":1},"1":{"240":1,"241":1,"242":1,"243":1,"244":1,"245":1,"246":1,"247":1,"248":1,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"260":1,"261":1,"385":1,"386":1,"387":1,"388":1,"389":1,"390":1,"391":1,"392":1,"393":1,"1300":1,"1817":1,"1818":1,"1819":1,"1820":1,"1821":1,"1822":1,"1823":1,"1824":1,"1825":1,"1826":1},"2":{"253":1,"259":1,"732":1,"733":1,"736":1,"999":1,"1300":2,"1338":1,"1914":1,"1915":1},"3":{"0":13,"25":1,"115":1,"117":2,"118":1,"121":3,"122":5,"123":1,"124":1,"126":2,"157":1,"160":1,"162":1,"186":1,"190":1,"235":1,"239":1,"240":1,"242":3,"243":16,"244":1,"245":5,"246":2,"247":2,"252":1,"253":1,"255":4,"256":1,"258":2,"259":3,"260":3,"261":3,"285":2,"300":1,"343":1,"385":1,"387":1,"389":1,"390":2,"391":1,"392":1,"400":1,"460":1,"674":4,"677":1,"701":1,"702":1,"732":3,"733":8,"736":4,"737":1,"740":1,"789":1,"885":1,"897":4,"924":1,"980":2,"996":6,"999":2,"1011":1,"1149":1,"1150":2,"1151":1,"1157":1,"1192":3,"1202":3,"1203":19,"1207":69,"1208":4,"1212":7,"1223":1,"1229":1,"1237":2,"1241":1,"1247":7,"1259":6,"1261":2,"1263":2,"1264":1,"1267":1,"1268":1,"1270":36,"1271":1,"1278":1,"1285":14,"1295":1,"1299":8,"1300":99,"1308":2,"1309":1,"1310":34,"1312":1,"1315":7,"1319":5,"1322":23,"1323":22,"1331":2,"1338":9,"1351":2,"1358":16,"1374":2,"1378":1,"1379":4,"1394":8,"1395":5,"1408":1,"1414":6,"1416":2,"1420":2,"1426":2,"1431":1,"1435":4,"1436":1,"1437":6,"1440":1,"1441":1,"1444":1,"1486":1,"1495":10,"1506":4,"1525":3,"1548":2,"1555":1,"1559":1,"1576":5,"1599":1,"1601":1,"1650":5,"1653":2,"1659":1,"1661":3,"1667":2,"1684":2,"1685":1,"1699":2,"1707":1,"1719":1,"1720":2,"1726":1,"1729":1,"1747":1,"1785":1,"1816":1,"1817":3,"1819":1,"1820":3,"1822":1,"1823":1,"1826":1,"1850":2,"1878":1,"1913":1,"1914":1,"1915":1,"1917":1,"1918":1,"1921":1,"1922":2,"1923":2,"2030":1,"2091":1,"2094":1,"2095":3,"2096":1,"2110":2,"2133":1,"2134":1,"2135":2,"2138":1,"2155":1,"2189":1,"2205":1,"2207":1,"2226":2,"2227":1,"2229":1,"2231":2}}],["cacheexpireson",{"3":{"1395":1}}],["cacheexpirationhours",{"2":{"157":1},"3":{"157":1,"1310":3}}],["cachettl",{"3":{"1394":2}}],["cachetag",{"3":{"1310":1}}],["cachecontrol",{"3":{"1285":4,"1322":1,"1338":1}}],["cacheinvalidating",{"3":{"1270":4}}],["cacheinvalidatingtestcommand",{"3":{"1199":3,"1207":1}}],["cachereachable",{"3":{"1310":1}}],["cachereadfailurequery",{"3":{"1199":2,"1207":1}}],["cachereadfailuremetricquery",{"3":{"1199":2,"1207":1}}],["cachereadcanceledquery",{"3":{"1199":2,"1207":1}}],["cacherequestasync",{"2":{"261":1},"3":{"243":1,"261":1,"1310":1}}],["cachepipelinetestcommand",{"3":{"1199":2,"1207":1}}],["cacheprobeentity",{"3":{"1199":2,"1207":1}}],["cacheprefix",{"2":{"924":1,"925":1,"1917":1,"1919":1,"2091":1},"3":{"0":1,"117":1,"922":1,"924":1,"925":1,"926":1,"1264":1,"1265":1,"1270":15,"1300":1,"1310":1,"1322":1,"1358":51,"1379":1,"1414":6,"1650":1,"1684":1,"1685":1,"1917":2,"1919":1,"2091":1}}],["cachemissmetricquery",{"3":{"1199":2,"1207":1}}],["cachehitmetricquery",{"3":{"1199":2,"1207":1}}],["cacheability",{"3":{"389":1}}],["cacheabletestquery",{"3":{"1199":3,"1207":1}}],["cacheable",{"3":{"123":2,"243":1,"246":2,"633":1,"1270":5,"1300":4,"1310":3,"1699":1,"1725":1,"1726":1,"1729":1,"1919":2,"1922":1,"2096":1}}],["cachevarybyrules",{"2":{"243":1,"246":1,"247":1,"740":2,"1921":1},"3":{"243":1,"246":1,"247":1,"388":1,"740":2,"1310":2,"1323":1,"1921":1}}],["cacheoptionstests",{"3":{"1199":1,"1207":2,"1300":1}}],["cacheoptions",{"2":{"243":2,"252":1,"254":1,"736":1,"1707":2,"1915":1},"3":{"243":4,"252":2,"254":4,"256":2,"257":2,"259":1,"261":1,"736":1,"1199":7,"1203":1,"1207":2,"1300":26,"1707":2,"1915":1}}],["cachekeys",{"3":{"1700":1,"1726":1,"1729":1,"2096":1}}],["cachekeynamespace",{"2":{"996":1,"999":1},"3":{"733":2,"996":1,"999":1,"1199":8,"1203":1,"1207":1,"1300":20,"1322":6,"1495":2}}],["cachekeylocks",{"3":{"733":1,"1199":2,"1203":1,"1207":1,"1260":1,"1267":1,"1270":2,"1299":1,"1300":4}}],["cachekey",{"2":{"123":1,"248":1,"1729":1,"1919":1,"2096":1},"3":{"117":1,"123":2,"246":1,"248":1,"1264":1,"1270":12,"1285":1,"1323":6,"1358":1,"1495":1,"1650":1,"1684":1,"1700":1,"1729":1,"1919":2,"2095":1,"2096":1}}],["cachekeyprefixoptions",{"3":{"674":1,"1199":4,"1203":1,"1207":1,"1300":9,"1322":1,"1495":2}}],["cachekeyprefix",{"3":{"25":1,"246":1,"996":1,"998":1,"999":1,"1207":2,"1300":35,"1310":1,"1319":1,"1322":1,"1495":2}}],["cachesettingstests",{"3":{"1199":1,"1207":2,"1300":1,"1322":2}}],["cachesettings",{"2":{"731":2,"1707":1,"1915":1},"3":{"674":1,"731":1,"733":3,"1192":1,"1199":6,"1203":1,"1207":2,"1300":30,"1319":2,"1322":5,"1495":1,"1707":2,"1915":1}}],["cacheservicegetorcreatetests",{"3":{"1199":1,"1207":3,"1300":1}}],["cacheservice",{"3":{"459":1,"465":1,"1299":1,"1322":1,"1414":1}}],["cacheseconds",{"2":{"1061":1,"2198":1,"2201":1},"3":{"0":3,"235":1,"830":1,"833":1,"1059":1,"1061":2,"1285":3,"1296":1,"1299":1,"1331":1,"1338":2,"1446":1,"2198":1,"2201":2}}],["caches",{"3":{"33":1,"162":1,"235":1,"243":2,"245":1,"390":1,"391":1,"441":1,"459":1,"507":1,"545":2,"592":1,"733":1,"735":1,"827":1,"830":1,"868":1,"964":1,"1052":1,"1055":1,"1229":1,"1237":3,"1239":1,"1241":1,"1242":2,"1247":8,"1259":3,"1270":5,"1278":1,"1279":1,"1285":15,"1287":1,"1299":11,"1300":3,"1304":1,"1309":1,"1322":4,"1323":4,"1333":1,"1338":1,"1358":4,"1384":1,"1386":1,"1394":4,"1395":3,"1410":1,"1414":1,"1416":1,"1420":1,"1435":2,"1437":1,"1452":1,"1454":1,"1455":1,"1566":1,"1665":1,"1671":1,"1747":1,"1752":1,"1773":2,"1814":2,"1848":1,"1911":1,"1913":1,"1914":1,"1915":1,"1916":1,"1921":2,"1922":3,"1923":1,"1933":1,"2023":1,"2068":1,"2126":1,"2171":1}}],["cachedirectory",{"3":{"1416":1}}],["cacheduntil",{"3":{"1394":3}}],["cacheduration",{"2":{"463":1,"2095":1},"3":{"117":1,"463":1,"1264":1,"1270":8,"1300":2,"1358":2,"1395":1,"1495":1,"1650":1,"2095":1}}],["cachedresult",{"3":{"1310":1}}],["cachedroles",{"3":{"1285":2}}],["cachedkeyset",{"3":{"1299":1}}],["cachedsuccess",{"3":{"1229":1}}],["cachedoublecheckmetricquery",{"3":{"1199":2,"1207":1}}],["cachedpage",{"3":{"1199":2,"1203":1,"1207":1,"1323":6}}],["cachedcompile",{"2":{"591":1},"3":{"591":2}}],["cachedhealthreportprovidertests",{"3":{"1199":1,"1207":4,"1338":2}}],["cachedhealthreportprovider",{"2":{"233":1,"235":1,"830":1,"833":1,"1446":1},"3":{"0":1,"233":1,"235":3,"830":3,"833":1,"1199":3,"1203":1,"1207":3,"1331":3,"1338":20,"1446":1,"1495":2}}],["cached",{"3":{"0":9,"21":1,"24":1,"155":1,"157":3,"159":2,"160":2,"174":1,"176":1,"184":1,"201":1,"235":1,"243":2,"245":1,"248":1,"255":2,"330":1,"387":1,"388":2,"390":1,"391":3,"392":1,"459":5,"460":1,"461":1,"463":1,"545":1,"697":1,"698":2,"733":2,"741":2,"743":1,"825":1,"830":1,"905":1,"922":1,"924":1,"1028":2,"1061":1,"1091":3,"1116":1,"1134":1,"1212":2,"1221":1,"1229":2,"1230":1,"1236":1,"1237":6,"1238":1,"1242":3,"1243":1,"1244":1,"1247":15,"1249":1,"1250":2,"1252":1,"1259":8,"1263":1,"1270":18,"1278":4,"1285":23,"1293":1,"1294":1,"1297":1,"1299":17,"1300":10,"1306":1,"1308":3,"1309":5,"1310":23,"1321":1,"1322":3,"1323":22,"1331":1,"1338":8,"1349":3,"1351":1,"1358":31,"1368":1,"1372":2,"1375":2,"1378":2,"1379":10,"1390":4,"1394":23,"1395":12,"1401":1,"1405":1,"1406":1,"1414":6,"1415":3,"1416":6,"1425":2,"1426":2,"1435":6,"1437":4,"1446":1,"1455":1,"1457":3,"1506":1,"1534":1,"1555":2,"1588":1,"1590":2,"1595":1,"1630":3,"1637":1,"1639":4,"1650":1,"1661":2,"1667":1,"1747":3,"1757":1,"1764":3,"1773":1,"1814":1,"1820":1,"1822":1,"1850":1,"1864":1,"1866":2,"1894":2,"1907":3,"1908":2,"1910":2,"1912":1,"1913":1,"1914":1,"1915":1,"1916":2,"1917":1,"1919":1,"1921":4,"1922":2,"1923":1,"1944":1,"1971":1,"1987":1,"1998":1,"1999":1,"2046":1,"2065":1,"2095":1,"2135":1,"2148":1,"2149":1,"2155":1,"2175":2,"2201":1,"2231":2}}],["cache",{"0":{"1293":1,"1914":1,"1920":1},"1":{"240":1,"241":1,"242":1,"243":1,"244":1,"245":1,"246":1,"247":1,"248":1,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"260":1,"261":1,"851":1,"852":1,"853":1,"854":1,"855":1,"856":1,"857":1,"1912":1,"1913":1,"1914":1,"1915":1,"1916":1,"1917":1,"1918":1,"1919":1,"1920":1,"1921":1,"1922":1,"1923":1},"2":{"255":1,"258":1,"400":1,"731":1,"897":1,"899":1,"1707":1,"1918":1,"1921":1},"3":{"0":47,"15":1,"25":4,"116":1,"117":2,"120":2,"122":1,"140":1,"155":1,"157":8,"158":1,"159":4,"161":3,"175":1,"176":1,"177":1,"181":1,"186":1,"190":1,"195":1,"212":1,"217":4,"233":1,"234":1,"235":7,"236":1,"237":3,"240":1,"241":6,"242":3,"243":17,"244":2,"245":3,"246":4,"247":1,"248":4,"249":2,"253":1,"255":6,"256":1,"258":3,"259":4,"282":1,"283":3,"285":1,"328":1,"330":1,"333":2,"337":2,"343":1,"350":1,"351":1,"373":2,"382":1,"386":3,"387":4,"388":3,"389":1,"390":4,"391":1,"392":1,"400":3,"441":1,"443":1,"444":1,"459":13,"460":2,"461":7,"463":6,"464":2,"465":3,"549":1,"550":1,"592":6,"593":1,"595":1,"657":1,"665":1,"666":2,"672":2,"674":3,"698":4,"699":3,"700":1,"731":5,"732":5,"733":5,"734":4,"735":3,"736":1,"737":3,"740":5,"742":2,"743":1,"744":1,"745":2,"749":1,"753":1,"757":1,"788":1,"792":2,"793":1,"794":1,"826":4,"828":1,"851":1,"852":1,"853":1,"854":3,"855":2,"856":2,"869":3,"871":1,"872":2,"879":1,"881":4,"885":1,"896":3,"897":7,"899":2,"922":1,"923":2,"924":1,"995":2,"996":6,"997":2,"998":4,"999":2,"1000":1,"1012":1,"1028":2,"1029":1,"1042":1,"1058":1,"1059":2,"1091":1,"1093":2,"1103":1,"1116":4,"1150":1,"1156":1,"1192":2,"1202":2,"1203":2,"1212":6,"1213":1,"1223":2,"1229":7,"1235":1,"1237":9,"1239":1,"1241":3,"1242":3,"1244":1,"1247":28,"1249":1,"1259":10,"1260":1,"1263":7,"1264":1,"1265":1,"1267":9,"1268":2,"1270":87,"1273":1,"1275":1,"1278":1,"1284":1,"1285":33,"1286":1,"1287":1,"1291":1,"1293":3,"1297":1,"1299":59,"1300":98,"1303":1,"1308":5,"1309":6,"1310":70,"1315":1,"1319":3,"1322":43,"1323":49,"1331":6,"1336":1,"1338":42,"1347":3,"1349":18,"1351":1,"1358":128,"1359":1,"1366":1,"1367":1,"1368":5,"1372":3,"1374":3,"1378":8,"1379":49,"1387":3,"1390":1,"1394":43,"1395":24,"1396":1,"1399":1,"1401":2,"1405":1,"1406":1,"1408":1,"1414":21,"1415":1,"1416":29,"1420":2,"1426":3,"1428":1,"1435":9,"1436":2,"1437":27,"1438":1,"1440":1,"1441":5,"1442":5,"1444":2,"1445":1,"1446":1,"1452":5,"1453":1,"1454":12,"1455":5,"1460":1,"1464":8,"1466":3,"1468":2,"1486":3,"1491":2,"1495":8,"1506":1,"1510":1,"1525":3,"1533":1,"1534":1,"1544":1,"1548":1,"1576":7,"1590":6,"1599":4,"1629":1,"1630":1,"1634":1,"1636":1,"1637":1,"1639":2,"1640":4,"1650":7,"1659":1,"1661":2,"1663":2,"1666":4,"1667":9,"1668":1,"1669":1,"1671":2,"1674":1,"1694":1,"1701":1,"1707":10,"1726":1,"1747":5,"1752":2,"1764":5,"1773":1,"1778":1,"1817":1,"1818":1,"1820":7,"1821":3,"1826":1,"1848":1,"1850":4,"1907":4,"1908":14,"1910":3,"1911":6,"1912":5,"1913":2,"1914":1,"1915":5,"1916":4,"1917":6,"1918":1,"1919":7,"1920":5,"1921":12,"1922":15,"1923":9,"1933":2,"1948":1,"1974":2,"1978":1,"1987":2,"1988":5,"1999":1,"2000":6,"2001":2,"2002":1,"2005":2,"2006":3,"2011":3,"2023":2,"2046":3,"2065":3,"2068":1,"2075":1,"2078":1,"2088":1,"2095":3,"2125":1,"2126":3,"2127":1,"2134":1,"2135":1,"2148":4,"2149":1,"2152":1,"2155":8,"2159":1,"2178":2,"2179":1,"2183":1,"2198":3,"2202":3,"2207":1,"2231":9}}],["cat",{"3":{"2219":1}}],["categorized",{"3":{"1630":1}}],["categorization",{"3":{"1394":1,"1763":1}}],["categoriessynced",{"3":{"1349":1,"1358":1}}],["categoriesmanage",{"3":{"1349":2,"1358":1,"1372":1,"1379":5}}],["categoriescontroller",{"2":{"1873":1,"1985":1},"3":{"897":1,"900":1,"1310":2,"1590":1,"1873":1,"1985":1}}],["categoriescache",{"2":{"392":1,"393":1},"3":{"175":1,"392":1,"393":1,"1349":1,"1378":1,"1379":2}}],["categories",{"0":{"1492":1,"1795":1},"1":{"1779":1,"1780":1,"1781":1,"1782":1,"1783":1,"1784":1,"1785":1,"1786":1},"2":{"1358":1,"1379":1,"1394":1},"3":{"0":2,"109":1,"110":1,"111":1,"186":1,"545":1,"556":1,"782":1,"881":2,"897":1,"1004":1,"1011":2,"1012":5,"1193":1,"1202":1,"1203":53,"1207":158,"1212":1,"1216":4,"1224":1,"1229":7,"1237":2,"1247":1,"1270":7,"1300":1,"1309":3,"1310":1,"1317":1,"1321":1,"1323":2,"1341":2,"1343":1,"1346":1,"1349":31,"1351":1,"1352":1,"1353":1,"1357":2,"1358":175,"1362":1,"1366":1,"1368":6,"1379":23,"1380":1,"1381":3,"1382":1,"1383":4,"1384":1,"1386":2,"1394":53,"1414":2,"1415":1,"1416":1,"1435":6,"1436":1,"1437":2,"1446":1,"1451":1,"1457":1,"1464":1,"1466":2,"1470":1,"1494":1,"1498":2,"1501":1,"1518":2,"1522":2,"1525":1,"1531":1,"1535":2,"1553":1,"1564":1,"1573":4,"1577":1,"1579":2,"1582":1,"1585":2,"1587":3,"1590":1,"1591":1,"1593":1,"1596":1,"1597":1,"1620":1,"1624":1,"1628":4,"1629":1,"1630":3,"1631":5,"1635":2,"1636":1,"1637":2,"1638":4,"1639":1,"1672":2,"1676":1,"1741":1,"1745":4,"1747":9,"1749":6,"1762":1,"1763":1,"1767":3,"1770":1,"1772":1,"1776":1,"1778":1,"1779":1,"1783":3,"1793":1,"1795":4,"1798":1,"1800":1,"1801":1,"1806":1,"2108":1,"2109":2,"2113":1,"2114":1,"2140":1,"2161":1,"2204":1}}],["categorydto",{"3":{"1747":1}}],["categorydetail",{"3":{"1590":1,"1599":1}}],["categorydistribution",{"3":{"1349":2,"1394":1}}],["categorydistributiondto",{"3":{"1199":10,"1203":1,"1207":4,"1346":1,"1349":10,"1358":4,"1379":1}}],["categorybuilder",{"3":{"1435":1}}],["categoryweight",{"3":{"1358":2}}],["categorygroups",{"3":{"1358":1}}],["categorygroupdistribution",{"2":{"1346":1},"3":{"1199":6,"1203":1,"1207":1,"1346":1,"1349":4,"1358":1}}],["categorytitlerequiredkey",{"3":{"1394":1}}],["categorytitles",{"3":{"1394":4}}],["categorytitle",{"3":{"1349":1,"1394":8}}],["categorytests",{"3":{"1199":1,"1207":2}}],["categorylisttests",{"3":{"1590":2,"1599":1}}],["categorylistpage",{"3":{"1435":1}}],["categorylist",{"3":{"1323":1,"1590":2}}],["categoryentityqueryservice",{"3":{"1247":2}}],["categoryname",{"2":{"1989":1},"3":{"1247":3,"1989":1}}],["categorycreated",{"3":{"1630":1,"1633":1}}],["categorycreate",{"3":{"1595":1}}],["categorycreaterequest",{"3":{"1270":1}}],["categoryconfiguration",{"3":{"1285":1,"1368":1,"1766":1}}],["categorycommandvalidatortests",{"3":{"1207":2,"1358":1}}],["categorychanged",{"2":{"1633":1},"3":{"782":2,"1199":4,"1203":1,"1207":2,"1259":2,"1344":1,"1349":10,"1358":4,"1384":1,"1394":1,"1630":1,"1633":1,"1747":1,"1768":1}}],["categoryscore",{"3":{"1358":1}}],["categorysyncstrategytests",{"3":{"1199":1,"1207":2,"1358":1}}],["categorysyncstrategy",{"3":{"1199":3,"1203":1,"1207":2,"1357":1,"1358":11}}],["categoryservice",{"3":{"881":1,"1323":1,"1394":1}}],["category=aieval",{"3":{"1018":1,"1434":1,"1437":1,"1454":1,"1491":1}}],["categoryinvariantstests",{"3":{"1199":1,"1207":2}}],["categoryinvariants",{"2":{"1343":1,"1349":1,"1358":1,"1368":1},"3":{"1198":1,"1199":13,"1203":1,"1207":2,"1343":5,"1349":15,"1358":25,"1368":5,"1766":2}}],["categoryid",{"2":{"891":1,"1775":1},"3":{"891":1,"1271":1,"1349":9,"1358":21,"1361":1,"1368":3,"1379":9,"1394":4,"1633":1,"1639":1,"1754":1,"1763":1,"1775":1}}],["categoryitemid",{"3":{"1349":13,"1358":24,"1368":6,"1379":10,"1394":8,"1630":2,"1634":2}}],["categoryitemidentifiertype",{"3":{"1349":9,"1358":14,"1379":4,"1394":12}}],["categoryiteminfo",{"2":{"1386":1},"3":{"1199":15,"1203":1,"1207":1,"1386":1,"1394":14}}],["categoryitemnamemaxlength",{"2":{"1368":1},"3":{"1349":2,"1358":5,"1368":2}}],["categoryitemnames",{"3":{"1349":2,"1394":1}}],["categoryitemname",{"3":{"1349":1,"1394":1}}],["categoryitemnamerules",{"3":{"1199":4,"1203":1,"1207":1,"1352":1,"1358":13}}],["categoryitemnavigationpopulatortests",{"3":{"1199":1,"1207":2,"1358":2}}],["categoryitemnavigationpopulator",{"3":{"1199":3,"1203":1,"1207":2,"1309":3,"1358":4}}],["categoryitemcount",{"2":{"1353":1},"3":{"1349":3,"1353":1,"1358":5}}],["categoryitemconfiguration",{"3":{"1199":2,"1203":1,"1207":2,"1349":1,"1358":2,"1361":2,"1368":10}}],["categoryitemchanged",{"2":{"1344":1,"1633":1},"3":{"1199":2,"1203":1,"1207":2,"1344":1,"1349":10,"1358":5,"1630":1,"1633":1}}],["categoryitemlookupservice",{"2":{"1386":1},"3":{"1199":3,"1203":1,"1207":2,"1299":2,"1379":1,"1386":2,"1394":13}}],["categoryitemdtomappertests",{"3":{"1199":1,"1207":2}}],["categoryitemdtomapper",{"3":{"1199":9,"1203":1,"1207":2,"1349":4,"1352":2,"1358":11}}],["categoryitemdto",{"3":{"1199":13,"1203":1,"1207":2,"1346":1,"1349":15,"1358":9,"1379":1,"1383":1,"1394":13}}],["categoryitemdistribution",{"2":{"1346":1},"3":{"1199":6,"1203":1,"1207":1,"1346":1,"1349":7,"1358":3}}],["categoryitemsexcept",{"3":{"1394":1}}],["categoryitemservice",{"2":{"1381":1},"3":{"881":1,"1199":2,"1203":1,"1207":2,"1381":3,"1394":7}}],["categoryitemssynced",{"3":{"1349":1,"1358":1}}],["categoryitems",{"2":{"1353":1},"3":{"1309":2,"1349":12,"1353":1,"1358":25,"1362":1,"1368":2,"1379":3,"1394":8,"1446":1,"1629":1,"1631":4,"1635":1,"1638":8,"1639":1}}],["categoryitemscontrollertests",{"3":{"1199":1,"1207":2}}],["categoryitemscontroller",{"2":{"1370":1,"1371":1},"3":{"1199":2,"1203":1,"1207":4,"1310":2,"1358":24,"1370":2,"1371":1,"1372":1,"1373":1,"1379":25,"1435":1}}],["categoryitemsortrules",{"3":{"1199":4,"1203":1,"1207":1,"1358":12}}],["categoryitem",{"2":{"891":1},"3":{"891":1,"1198":1,"1199":16,"1203":1,"1207":2,"1237":1,"1339":1,"1341":2,"1342":1,"1343":1,"1349":43,"1358":41,"1368":9,"1379":6,"1394":1,"1629":6,"1630":2,"1631":2,"1633":2,"1634":3,"1637":5,"1638":6,"1639":3}}],["categoryassignparentupdatehandler",{"2":{"543":1,"551":1,"924":1,"927":1},"3":{"543":1,"545":1,"550":1,"551":1,"924":1,"927":1,"1270":2}}],["category",{"0":{"1216":1,"1449":1,"1460":1,"1468":1,"1515":1},"1":{"1009":1,"1010":1,"1011":1,"1012":1,"1013":1,"1014":1,"1793":1,"1794":1,"1795":1,"1796":1,"1797":1,"1798":1,"1799":1,"1800":1,"1801":1},"2":{"11":1,"804":1,"924":1,"1593":1,"1989":1,"2109":1},"3":{"0":7,"11":1,"109":4,"110":3,"111":1,"168":1,"188":1,"390":1,"403":1,"550":1,"804":1,"819":1,"860":1,"882":1,"924":2,"963":1,"972":1,"1009":1,"1010":1,"1011":5,"1012":4,"1013":2,"1014":1,"1017":1,"1022":1,"1028":1,"1090":2,"1190":1,"1192":2,"1193":2,"1194":1,"1198":1,"1199":51,"1201":1,"1202":1,"1203":2,"1207":3,"1216":3,"1222":1,"1229":4,"1237":5,"1241":1,"1247":10,"1259":1,"1270":2,"1285":3,"1300":1,"1309":5,"1310":1,"1311":1,"1314":1,"1316":1,"1319":1,"1321":1,"1322":2,"1323":6,"1338":4,"1339":1,"1341":3,"1342":4,"1343":1,"1346":2,"1347":1,"1349":123,"1350":2,"1351":1,"1352":1,"1353":1,"1358":263,"1361":3,"1368":14,"1370":2,"1374":1,"1375":1,"1379":17,"1381":1,"1384":1,"1385":1,"1386":2,"1389":1,"1394":98,"1395":1,"1415":1,"1416":3,"1417":1,"1426":2,"1430":2,"1435":11,"1436":1,"1437":3,"1439":1,"1449":1,"1455":1,"1456":1,"1457":1,"1458":1,"1460":1,"1466":5,"1468":1,"1486":1,"1491":1,"1492":1,"1495":4,"1496":1,"1497":1,"1498":2,"1517":2,"1518":2,"1520":1,"1522":1,"1525":7,"1526":1,"1528":2,"1529":1,"1530":1,"1531":3,"1532":1,"1534":3,"1535":2,"1536":10,"1552":1,"1574":2,"1576":3,"1577":1,"1579":1,"1580":1,"1581":1,"1582":3,"1583":3,"1584":1,"1585":1,"1587":1,"1590":5,"1591":2,"1593":1,"1594":1,"1596":2,"1597":2,"1599":2,"1610":1,"1628":1,"1629":11,"1630":3,"1631":31,"1633":5,"1634":2,"1635":3,"1636":4,"1637":5,"1638":16,"1639":7,"1672":2,"1673":1,"1701":1,"1741":1,"1745":6,"1747":8,"1749":6,"1754":3,"1763":9,"1766":5,"1767":1,"1768":4,"1772":2,"1775":4,"1776":1,"1778":1,"1779":2,"1783":8,"1786":1,"1792":1,"1793":2,"1795":5,"1796":4,"1797":2,"1798":6,"1799":3,"1800":1,"1801":5,"1826":1,"1844":1,"1902":1,"1924":1,"1928":1,"1929":1,"1956":1,"1989":1,"2039":2,"2041":1,"2042":2,"2047":3,"2049":1,"2050":1,"2052":1,"2054":1,"2058":2,"2059":1,"2061":4,"2068":1,"2084":1,"2109":1,"2113":1,"2114":1,"2132":1,"2152":1,"2154":2,"2159":1,"2161":1,"2168":1,"2170":1,"2179":1,"2181":1,"2192":1,"2194":1,"2202":1,"2203":1,"2204":1,"2210":1,"2213":1,"2220":1,"2230":2,"2231":2,"2244":1}}],["catalyst",{"3":{"682":1,"684":1,"1415":2,"1416":4,"1659":1,"1668":1}}],["catalogroutepaths",{"3":{"1590":1}}],["catalogrouteauthorizationtests",{"3":{"1435":1,"1590":1}}],["catalogintegrationtestfixture",{"3":{"1435":1}}],["catalogcrossservicefactory",{"3":{"1435":1}}],["catalogcache",{"2":{"392":1,"393":1},"3":{"392":1,"393":3}}],["catalogtestwebapplicationfactory",{"3":{"1435":1}}],["cataloged",{"3":{"1427":1,"1435":1}}],["catalogued",{"3":{"1300":1,"1346":1,"1349":1,"1402":1,"1495":1}}],["catalogue",{"3":{"974":1,"1237":2,"1310":2,"1323":2,"1343":2,"1347":1,"1368":1,"1393":1,"1394":4,"1435":1}}],["cataloguimodule",{"3":{"649":1,"1323":2,"1590":1}}],["cataloguserdataexportsection",{"2":{"583":1,"728":1},"3":{"725":1,"728":2}}],["catalogmoduletests",{"3":{"1435":2}}],["catalogmoduleseeder",{"3":{"1322":2}}],["catalogmoduledbseeder",{"3":{"1285":2}}],["catalogmodule",{"2":{"586":1},"3":{"583":2,"584":1,"586":2,"675":1,"676":1,"1322":1}}],["catalogbrowse",{"2":{"557":1,"558":1},"3":{"556":6,"557":1,"558":1,"1323":1,"1590":9,"1595":1,"1596":1,"1599":1}}],["catalogproductdetail",{"3":{"556":1,"1323":1,"1590":1,"1595":1,"1596":1}}],["catalogpermissiongrants",{"3":{"186":1}}],["catalogpermissions",{"2":{"1264":1},"3":{"186":1,"1026":1,"1264":1,"1766":1}}],["catalogs",{"3":{"387":1,"1435":1,"1754":1}}],["catalogfeatures",{"2":{"1747":1,"1764":1},"3":{"300":1,"1270":1,"1747":1,"1764":1}}],["catalog",{"0":{"1684":1,"1747":1},"1":{"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1,"1658":1,"1659":1,"1660":1,"1661":1,"1662":1,"1663":1,"1664":1,"1665":1,"1666":1,"1667":1,"1668":1,"1669":1,"1670":1,"1671":1,"1672":1,"1673":1,"1674":1},"2":{"62":1,"493":1,"1587":1,"1750":2,"1768":2,"2200":1},"3":{"0":3,"42":4,"62":1,"80":5,"91":4,"93":1,"94":2,"95":1,"97":2,"99":1,"101":2,"104":1,"109":2,"117":2,"128":4,"136":1,"142":1,"184":1,"185":1,"186":2,"195":5,"235":2,"238":1,"243":8,"245":2,"249":1,"250":2,"251":2,"252":2,"254":2,"257":2,"259":1,"260":6,"261":3,"291":1,"295":1,"325":1,"326":1,"341":1,"345":1,"387":2,"390":4,"391":8,"392":4,"393":1,"401":2,"403":1,"446":1,"448":6,"452":3,"453":1,"454":1,"459":1,"468":1,"470":4,"475":4,"476":7,"477":4,"493":1,"545":9,"552":2,"556":6,"564":1,"572":4,"583":13,"585":4,"586":4,"599":2,"602":1,"640":2,"643":1,"649":3,"650":1,"657":5,"658":1,"674":4,"675":2,"676":2,"715":1,"725":1,"728":3,"733":1,"743":1,"749":2,"752":1,"757":2,"759":1,"774":2,"780":1,"782":9,"790":1,"798":2,"804":4,"837":2,"838":2,"861":1,"864":1,"869":1,"876":1,"881":1,"889":2,"891":1,"897":10,"900":2,"914":1,"924":5,"927":3,"954":7,"990":1,"1023":1,"1024":1,"1025":7,"1026":24,"1028":2,"1029":3,"1030":1,"1054":1,"1059":7,"1082":4,"1085":1,"1086":2,"1168":6,"1207":1,"1212":2,"1237":7,"1247":4,"1262":1,"1265":1,"1270":14,"1271":5,"1285":14,"1296":2,"1299":25,"1300":6,"1306":1,"1310":29,"1311":4,"1322":18,"1323":29,"1338":15,"1347":2,"1349":12,"1358":1,"1368":2,"1379":14,"1392":1,"1394":10,"1395":30,"1400":1,"1401":2,"1404":1,"1413":1,"1414":6,"1430":1,"1435":49,"1437":1,"1466":1,"1485":1,"1487":1,"1488":1,"1495":2,"1499":1,"1540":1,"1587":2,"1590":24,"1595":6,"1596":1,"1598":1,"1599":10,"1601":1,"1620":2,"1626":1,"1658":1,"1670":1,"1680":1,"1682":1,"1684":5,"1685":1,"1686":1,"1726":1,"1727":1,"1741":3,"1745":26,"1747":9,"1748":4,"1749":6,"1750":15,"1754":1,"1756":2,"1757":1,"1759":1,"1760":1,"1762":4,"1763":15,"1764":32,"1766":5,"1767":3,"1768":12,"1770":3,"1771":6,"1772":7,"1774":2,"1776":1,"1784":1,"1874":1,"1892":1,"1917":2,"1921":4,"1922":2,"1956":1,"1959":1,"1962":2,"2054":1,"2086":1,"2109":2,"2110":1,"2112":1,"2130":6,"2137":1,"2155":1,"2199":3,"2200":3,"2202":1,"2227":1,"2231":1}}],["catastrophic",{"3":{"0":1,"791":1,"861":1,"1237":1,"1308":1,"1322":1,"1576":1,"1705":1,"1706":1,"1835":1,"1848":1,"2078":1,"2165":1}}],["catchable",{"3":{"1259":1,"1435":2}}],["catchesadomainreachinganothermodulesapplication",{"3":{"1435":1}}],["catches",{"3":{"18":1,"125":1,"127":1,"132":1,"133":1,"135":1,"148":1,"225":1,"255":1,"345":1,"465":1,"486":1,"490":1,"491":1,"492":1,"493":1,"494":1,"518":1,"566":1,"573":1,"593":1,"609":1,"698":1,"709":1,"718":1,"767":2,"863":1,"897":1,"956":1,"982":1,"988":1,"990":1,"1018":1,"1019":2,"1021":1,"1102":1,"1169":1,"1237":1,"1254":1,"1259":2,"1270":1,"1285":4,"1299":2,"1310":2,"1311":4,"1314":1,"1322":3,"1323":9,"1338":3,"1358":3,"1379":1,"1395":6,"1409":1,"1414":3,"1415":2,"1416":6,"1422":1,"1435":17,"1437":2,"1454":3,"1455":1,"1457":1,"1474":1,"1475":1,"1487":2,"1488":1,"1491":1,"1495":2,"1523":1,"1525":1,"1534":1,"1604":3,"1653":1,"1738":3,"1804":1,"1805":1,"1922":1,"1926":2,"1928":2,"1929":1,"1960":1,"1998":1,"2017":1,"2035":1,"2040":1,"2047":1,"2099":2,"2105":2,"2107":1,"2125":1,"2168":1,"2174":1}}],["catching",{"3":{"0":1,"590":1,"881":1,"1247":1,"1270":1,"1285":3,"1299":1,"1303":1,"1322":2,"1323":8,"1394":2,"1395":5,"1414":2,"1415":1,"1416":5,"1426":1,"1430":1,"1435":9,"1437":1,"1454":2,"1491":2,"1662":1,"1668":1,"1701":1,"2001":1,"2046":1,"2047":1,"2099":1,"2116":1,"2164":1}}],["catch",{"0":{"1918":1},"2":{"21":1,"111":1,"512":1,"537":1,"684":1,"896":1,"899":1,"1218":1,"1802":1,"1803":1,"1918":1,"2168":1},"3":{"0":7,"21":1,"107":1,"108":1,"109":6,"110":2,"111":2,"135":1,"180":3,"231":1,"258":1,"295":1,"303":1,"310":1,"344":1,"413":1,"426":1,"434":1,"435":1,"436":1,"466":1,"512":1,"517":1,"523":1,"529":1,"536":2,"537":1,"559":1,"565":1,"574":1,"619":1,"684":1,"692":2,"700":1,"707":1,"709":1,"724":1,"726":1,"727":1,"765":1,"790":1,"801":1,"810":1,"819":1,"827":2,"840":3,"842":1,"861":1,"896":1,"897":2,"899":1,"914":1,"972":1,"988":1,"1018":1,"1019":2,"1093":1,"1218":1,"1225":1,"1229":4,"1233":1,"1247":6,"1254":1,"1259":7,"1263":2,"1270":5,"1276":1,"1285":13,"1288":2,"1299":6,"1300":4,"1301":1,"1303":1,"1308":9,"1310":10,"1311":3,"1322":19,"1323":33,"1333":1,"1336":1,"1338":7,"1349":1,"1351":1,"1356":1,"1357":1,"1358":15,"1365":2,"1377":1,"1379":23,"1394":12,"1395":23,"1398":1,"1401":6,"1414":9,"1415":2,"1416":20,"1424":2,"1426":3,"1434":2,"1435":35,"1437":4,"1446":3,"1452":1,"1454":1,"1465":1,"1466":1,"1486":1,"1487":1,"1488":3,"1495":6,"1523":1,"1546":1,"1576":1,"1661":1,"1669":1,"1670":1,"1764":1,"1773":1,"1802":1,"1803":3,"1804":1,"1805":1,"1811":1,"1834":1,"1851":1,"1857":1,"1880":1,"1918":1,"1919":1,"1926":3,"1932":2,"1933":2,"1939":1,"1945":2,"1951":1,"1953":1,"1958":1,"1969":1,"1979":1,"1993":1,"1997":1,"2031":1,"2040":1,"2042":1,"2048":1,"2053":1,"2058":1,"2066":3,"2069":1,"2074":1,"2077":1,"2078":1,"2098":1,"2164":1,"2166":2,"2168":1,"2176":1,"2231":1}}],["cantidad",{"3":{"1685":1}}],["canvas",{"3":{"1479":3}}],["canevaluatepolicy",{"3":{"1416":1}}],["canmoderate",{"3":{"1401":7}}],["canmanage=",{"3":{"1394":2}}],["canmanage",{"3":{"1388":1,"1394":2}}],["canreview",{"3":{"1747":1,"1764":1}}],["canrendereventpicker",{"3":{"1394":2}}],["canreadsessionassetsasync",{"3":{"1358":6}}],["canreadtoken",{"3":{"1323":1}}],["canbookmark",{"3":{"1394":1}}],["cansubmitlink",{"3":{"1394":2}}],["canserve",{"3":{"1323":4,"1394":2}}],["canned",{"3":{"1358":1,"1431":2,"1433":1,"1435":14,"1670":1}}],["cannot",{"0":{"1234":1,"1935":1,"2024":1,"2112":1},"1":{"1868":1,"1869":1,"1870":1,"1871":1,"1872":1,"1873":1,"1874":1,"1875":1,"1876":1},"3":{"0":35,"17":1,"19":2,"24":2,"26":1,"31":3,"32":1,"33":1,"38":1,"39":1,"41":1,"68":2,"78":1,"81":1,"91":1,"92":2,"93":1,"94":1,"98":1,"100":1,"103":1,"109":1,"110":2,"119":1,"120":1,"122":1,"123":1,"130":1,"133":1,"134":1,"135":3,"136":1,"137":3,"147":1,"149":1,"150":1,"157":2,"158":1,"160":1,"168":1,"187":1,"189":1,"201":1,"203":1,"206":1,"207":1,"212":1,"213":2,"214":1,"215":3,"216":1,"224":1,"225":1,"227":1,"233":1,"234":1,"235":4,"236":1,"243":1,"248":2,"249":1,"253":1,"255":3,"265":4,"267":1,"279":1,"280":1,"284":1,"285":1,"309":2,"310":1,"317":2,"318":3,"319":1,"324":1,"326":1,"330":2,"331":2,"341":2,"342":1,"343":2,"350":3,"353":1,"359":2,"363":1,"365":1,"370":1,"371":1,"380":1,"384":1,"387":1,"390":1,"397":3,"399":1,"408":1,"412":2,"413":1,"420":1,"422":1,"423":1,"433":1,"435":1,"449":2,"451":1,"459":1,"461":1,"465":1,"498":1,"500":1,"506":1,"509":3,"511":1,"513":1,"517":3,"518":2,"519":2,"526":1,"528":1,"529":5,"530":3,"535":1,"536":2,"537":3,"545":2,"546":2,"547":2,"549":1,"551":1,"556":2,"557":1,"558":1,"564":1,"565":1,"568":1,"571":1,"574":1,"576":2,"584":1,"585":1,"591":1,"593":1,"601":2,"608":1,"609":2,"610":1,"617":1,"619":1,"620":2,"625":3,"628":1,"633":2,"635":2,"640":1,"649":1,"651":1,"658":1,"660":1,"664":1,"674":1,"676":1,"681":1,"689":2,"690":1,"691":2,"692":1,"698":4,"699":1,"709":1,"714":2,"717":1,"718":3,"725":1,"726":2,"733":3,"734":1,"741":3,"742":3,"743":1,"757":2,"758":1,"762":1,"767":1,"773":1,"775":1,"784":1,"789":1,"790":1,"792":1,"793":1,"794":1,"800":1,"810":2,"818":1,"819":1,"821":1,"827":6,"828":1,"832":1,"840":1,"848":1,"853":1,"854":5,"855":2,"857":1,"861":2,"862":1,"870":1,"871":1,"872":1,"881":3,"883":1,"888":1,"889":3,"896":1,"903":1,"904":2,"905":5,"906":1,"908":1,"914":1,"921":1,"922":3,"924":1,"926":4,"931":1,"939":1,"940":1,"955":1,"956":1,"957":1,"966":1,"972":1,"974":1,"988":2,"996":5,"997":2,"998":1,"1004":1,"1017":1,"1018":3,"1019":2,"1020":2,"1026":2,"1028":2,"1029":1,"1033":1,"1034":1,"1042":2,"1043":2,"1044":1,"1050":1,"1052":2,"1059":3,"1060":1,"1067":2,"1074":1,"1083":1,"1085":2,"1091":2,"1092":2,"1093":1,"1100":1,"1101":2,"1102":1,"1110":1,"1115":1,"1116":4,"1124":2,"1125":1,"1126":1,"1132":2,"1133":2,"1134":1,"1135":1,"1145":1,"1155":1,"1160":1,"1162":1,"1175":1,"1176":1,"1177":1,"1184":1,"1185":4,"1191":1,"1212":8,"1221":1,"1223":1,"1224":1,"1226":1,"1228":1,"1229":10,"1232":1,"1233":1,"1234":2,"1235":1,"1237":11,"1239":3,"1240":1,"1241":6,"1243":4,"1247":31,"1248":1,"1252":1,"1253":2,"1254":2,"1256":1,"1257":1,"1259":15,"1262":1,"1263":1,"1265":3,"1267":1,"1270":29,"1271":12,"1273":2,"1274":2,"1275":2,"1276":2,"1277":2,"1278":3,"1279":1,"1280":2,"1282":1,"1283":1,"1285":74,"1287":2,"1288":1,"1289":2,"1291":2,"1292":2,"1293":2,"1296":3,"1297":1,"1299":63,"1300":16,"1301":1,"1304":3,"1306":2,"1307":1,"1308":24,"1309":7,"1310":59,"1311":6,"1315":1,"1316":1,"1317":1,"1319":4,"1321":3,"1322":63,"1323":90,"1325":2,"1326":1,"1327":1,"1330":1,"1331":4,"1335":1,"1336":3,"1338":55,"1343":1,"1345":1,"1347":1,"1348":1,"1349":41,"1351":3,"1352":1,"1353":1,"1356":2,"1357":1,"1358":209,"1361":3,"1365":5,"1366":2,"1367":1,"1368":14,"1370":1,"1371":1,"1372":1,"1373":1,"1374":1,"1377":1,"1378":1,"1379":28,"1383":1,"1384":1,"1385":1,"1388":2,"1390":1,"1391":1,"1392":1,"1394":56,"1395":99,"1397":1,"1398":5,"1400":3,"1401":41,"1404":2,"1405":4,"1406":2,"1407":1,"1408":1,"1413":2,"1414":60,"1415":9,"1416":28,"1418":1,"1420":1,"1422":2,"1425":1,"1426":12,"1427":2,"1428":1,"1429":3,"1430":5,"1431":1,"1432":1,"1434":3,"1435":143,"1437":20,"1440":6,"1441":3,"1442":3,"1443":2,"1444":2,"1445":1,"1446":1,"1448":1,"1451":1,"1452":11,"1453":5,"1454":17,"1455":1,"1457":1,"1458":7,"1459":1,"1464":4,"1466":14,"1468":1,"1471":1,"1473":3,"1474":3,"1475":1,"1476":2,"1480":2,"1481":1,"1482":1,"1483":1,"1485":3,"1486":1,"1487":5,"1488":4,"1489":3,"1490":1,"1491":5,"1495":2,"1497":1,"1499":1,"1512":1,"1523":1,"1525":8,"1530":3,"1533":3,"1536":1,"1552":1,"1574":2,"1576":4,"1581":1,"1583":1,"1584":2,"1588":1,"1590":2,"1595":1,"1604":2,"1610":2,"1629":2,"1630":8,"1631":3,"1634":3,"1637":1,"1638":10,"1639":3,"1640":9,"1641":1,"1643":1,"1647":1,"1650":2,"1652":1,"1653":1,"1660":2,"1665":2,"1666":4,"1667":1,"1668":2,"1669":3,"1670":3,"1672":1,"1675":1,"1676":2,"1678":1,"1680":1,"1684":6,"1685":9,"1691":1,"1700":2,"1701":1,"1705":1,"1706":1,"1721":2,"1722":1,"1724":1,"1725":1,"1726":2,"1727":1,"1728":1,"1729":1,"1730":1,"1731":1,"1736":1,"1738":2,"1747":2,"1748":2,"1754":2,"1763":2,"1764":8,"1766":11,"1771":1,"1787":1,"1792":1,"1794":1,"1796":1,"1802":2,"1804":3,"1805":1,"1808":1,"1809":3,"1811":2,"1813":3,"1814":4,"1815":1,"1816":1,"1831":3,"1833":1,"1834":1,"1837":1,"1838":1,"1841":1,"1845":1,"1848":1,"1850":2,"1857":2,"1858":2,"1860":1,"1861":1,"1862":1,"1863":1,"1867":1,"1868":1,"1873":1,"1875":2,"1876":1,"1884":1,"1887":2,"1888":1,"1893":2,"1896":1,"1897":2,"1900":1,"1901":2,"1905":1,"1907":1,"1911":1,"1915":1,"1917":1,"1919":2,"1921":1,"1922":1,"1923":1,"1924":1,"1926":2,"1932":2,"1936":2,"1939":1,"1940":1,"1941":1,"1943":2,"1944":2,"1945":1,"1947":1,"1949":3,"1951":1,"1952":1,"1953":1,"1956":1,"1959":1,"1964":1,"1965":1,"1967":1,"1969":1,"1971":2,"1973":1,"1975":3,"1976":1,"1977":1,"1979":3,"1980":3,"1981":2,"1984":1,"1985":1,"1986":2,"1987":2,"1991":2,"1993":4,"1994":1,"1995":1,"1996":2,"1997":2,"1998":1,"1999":2,"2000":2,"2001":4,"2013":1,"2015":1,"2018":1,"2023":1,"2024":3,"2026":1,"2027":1,"2029":1,"2036":1,"2039":1,"2040":2,"2042":2,"2044":2,"2048":2,"2055":2,"2060":1,"2062":1,"2067":2,"2070":2,"2073":1,"2081":1,"2082":2,"2084":1,"2088":1,"2089":1,"2096":2,"2097":1,"2099":1,"2103":1,"2105":1,"2106":2,"2108":1,"2112":1,"2114":1,"2115":1,"2119":3,"2121":1,"2122":1,"2126":1,"2128":1,"2130":1,"2132":2,"2135":1,"2140":1,"2141":2,"2143":1,"2145":2,"2146":4,"2149":1,"2151":1,"2152":4,"2153":1,"2155":1,"2157":1,"2159":2,"2160":1,"2163":2,"2164":2,"2165":2,"2166":2,"2167":1,"2168":4,"2171":1,"2173":1,"2175":2,"2177":1,"2179":2,"2180":1,"2182":1,"2184":1,"2187":1,"2191":1,"2192":2,"2193":1,"2194":2,"2195":1,"2197":2,"2198":1,"2200":1,"2202":3,"2206":1,"2229":1,"2231":4}}],["cangobackasync",{"3":{"1323":1}}],["canaries",{"3":{"1452":1}}],["canary",{"3":{"0":4,"135":1,"265":1,"530":2,"564":4,"624":2,"626":2,"631":1,"633":5,"757":1,"1012":1,"1032":2,"1034":4,"1036":1,"1037":3,"1435":2,"1453":2,"1454":1,"1459":1,"1460":3,"1485":1,"1491":3,"1492":1,"1553":1,"1573":1,"1576":2,"1663":1,"1672":1,"2104":1}}],["canauthenticate",{"3":{"1416":1}}],["canacceptupvote",{"3":{"1397":1,"1401":2}}],["canacceptvote",{"3":{"1397":1,"1401":6}}],["canadminister",{"3":{"1323":1,"1414":1}}],["canjoinasync",{"3":{"1285":1,"1308":2}}],["candid",{"3":{"1270":1,"1368":1,"1435":1,"1442":1,"1791":1,"1890":1,"1977":1,"2058":1,"2131":1}}],["candidate",{"3":{"0":2,"24":1,"145":1,"150":2,"256":1,"325":1,"326":1,"907":1,"1075":2,"1076":1,"1077":1,"1196":1,"1237":3,"1259":3,"1270":2,"1275":1,"1285":2,"1288":1,"1299":7,"1300":2,"1302":1,"1308":1,"1310":1,"1323":5,"1338":2,"1349":2,"1358":4,"1394":2,"1437":1,"1495":1,"1637":1,"2048":1}}],["candidates",{"3":{"0":2,"145":2,"150":2,"153":1,"1074":1,"1075":1,"1077":1,"1079":2,"1259":1,"1310":1,"1323":1,"1338":2,"1354":1,"1414":1,"1435":4,"1672":1,"2187":1}}],["canproject",{"3":{"1244":1,"1247":2,"1270":1,"1308":2}}],["canparse",{"3":{"1241":1,"1247":11}}],["canparsevalues",{"3":{"1299":4}}],["canparsevalue",{"2":{"335":1},"3":{"335":1,"1241":2,"1247":14}}],["canwrite",{"3":{"1242":1,"1247":2}}],["canclose",{"3":{"1435":1}}],["cancheckin",{"3":{"1395":2}}],["canconvertto",{"3":{"1299":1}}],["canconvertfrom",{"3":{"1299":1}}],["canconvert",{"2":{"1223":1},"3":{"1223":1,"1229":1,"1237":2,"1299":2}}],["cancelordercommand",{"2":{"2162":1},"3":{"1590":1}}],["cancelorderhandler",{"2":{"534":1,"793":1,"1874":1,"2162":1},"3":{"534":3,"536":8,"540":2,"793":2,"1590":1,"1748":1,"1764":1,"1766":1,"1874":1,"2162":2}}],["cancelall",{"3":{"1416":1}}],["cancelallasync",{"3":{"1416":3}}],["cancelasync",{"3":{"1416":5}}],["cancelable",{"3":{"1358":1}}],["cancelanddisposecurrent",{"3":{"1323":2}}],["cancelafter",{"3":{"1263":1,"1323":1}}],["canceledoutcome",{"3":{"1426":2}}],["cancelediting",{"3":{"1394":11}}],["canceled",{"3":{"1089":1,"1310":1,"1323":1,"1424":1,"2176":1}}],["cancels",{"3":{"237":1,"518":1,"556":1,"592":1,"813":1,"926":1,"1100":1,"1212":1,"1247":1,"1263":1,"1270":2,"1299":1,"1315":1,"1322":2,"1323":12,"1338":2,"1379":1,"1394":7,"1395":5,"1401":9,"1414":3,"1416":8,"1435":1,"1455":1,"1491":1,"1667":1,"1764":1,"1820":1,"2046":1,"2160":1}}],["canceltext",{"2":{"684":1},"3":{"0":1,"682":1,"684":1,"1323":3,"1415":1,"1416":3}}],["canceller",{"3":{"1338":1}}],["cancelled",{"2":{"793":1,"1753":1},"3":{"0":4,"109":1,"135":1,"440":1,"534":1,"536":5,"538":2,"539":2,"682":3,"790":1,"793":4,"897":1,"1091":1,"1212":1,"1259":2,"1263":1,"1270":4,"1285":1,"1288":1,"1299":1,"1308":1,"1322":1,"1323":6,"1338":3,"1394":10,"1395":7,"1398":1,"1401":2,"1414":4,"1416":14,"1421":1,"1430":1,"1435":3,"1437":1,"1454":3,"1455":1,"1459":1,"1474":2,"1482":1,"1525":1,"1685":1,"1748":1,"1753":1,"1754":1,"1764":3,"1765":1,"1766":1,"1922":1,"2095":1,"2125":1,"2160":1,"2162":2,"2163":2,"2166":3,"2167":1,"2173":1,"2231":1}}],["cancelloading",{"3":{"1323":1}}],["cancellingsection",{"3":{"1199":2,"1207":1,"1322":1}}],["cancelling",{"3":{"0":1,"535":1,"788":1,"793":2,"813":1,"1259":1,"1270":1,"1323":7,"1338":1,"1394":4,"1395":7,"1426":1,"1455":1,"1766":1}}],["cancellable",{"3":{"0":1,"135":1,"683":1,"1270":1,"1299":1,"1394":1,"1395":2,"1414":1,"1416":1,"1435":2,"1454":1,"1764":1,"1765":2,"1766":1,"1767":1,"1917":1}}],["cancellations",{"3":{"1310":1}}],["cancellationfixtures",{"3":{"1207":12,"1435":10}}],["cancellationtestmap",{"3":{"1198":1,"1199":2,"1207":1,"1435":5,"1495":1}}],["cancellationtokenfixtures",{"3":{"1207":6,"1435":8}}],["cancellationtokenfitnesstests",{"3":{"1198":1,"1199":2,"1207":3,"1435":10,"1488":1,"1495":1}}],["cancellationtokenconventiontests",{"3":{"135":1,"139":1,"142":2,"1199":1,"1207":2,"1308":1,"1435":11,"1488":1}}],["cancellationtokenconventiontestsbase",{"2":{"1430":1},"3":{"135":2,"139":1,"1199":2,"1207":2,"1430":3,"1435":8,"1495":1}}],["cancellationtokenexemptmethods",{"3":{"135":1,"1435":4}}],["cancellationtokens",{"2":{"135":1},"3":{"135":3,"139":1,"1207":1,"1430":1,"1435":7,"1488":1}}],["cancellationtokensource",{"2":{"121":1,"743":1,"1383":1,"2069":1,"2071":1},"3":{"743":1,"1263":1,"1270":1,"1323":15,"1383":1,"1394":25,"1395":4,"1401":6,"1414":3,"1416":2,"1426":1,"1435":1,"2069":1,"2071":1}}],["cancellationtoken",{"2":{"130":1,"143":1,"225":1,"743":1,"879":1,"897":1,"996":1,"1261":2,"1288":1,"1323":1,"1917":1,"1918":1,"2091":1},"3":{"0":5,"130":1,"135":2,"143":1,"225":1,"235":1,"459":1,"682":3,"707":1,"725":1,"733":1,"743":1,"879":1,"881":2,"897":5,"922":1,"996":1,"1247":10,"1259":14,"1261":2,"1263":1,"1269":1,"1270":30,"1285":74,"1288":1,"1299":52,"1300":12,"1303":1,"1308":30,"1309":4,"1310":28,"1314":1,"1322":40,"1323":56,"1338":15,"1349":4,"1358":44,"1368":2,"1379":11,"1394":39,"1395":43,"1401":7,"1414":14,"1416":125,"1430":2,"1435":58,"1917":1,"1918":2,"2091":1,"2231":1}}],["cancellation",{"3":{"0":4,"21":2,"27":2,"109":2,"135":1,"178":1,"459":1,"465":1,"518":1,"534":1,"536":2,"538":1,"559":1,"683":1,"881":2,"882":2,"896":2,"897":5,"898":2,"988":1,"1100":1,"1212":1,"1247":4,"1255":1,"1259":6,"1263":1,"1270":14,"1285":10,"1300":2,"1308":3,"1310":3,"1322":7,"1323":21,"1333":1,"1336":1,"1338":3,"1357":1,"1358":5,"1365":1,"1368":2,"1379":2,"1382":1,"1394":15,"1395":17,"1398":2,"1401":4,"1414":2,"1416":16,"1421":1,"1422":1,"1426":2,"1430":2,"1435":8,"1437":6,"1474":1,"1525":1,"1534":1,"1576":2,"1590":1,"1639":1,"1652":1,"1664":1,"1665":1,"1670":1,"1701":1,"1748":3,"1754":4,"1764":2,"1766":2,"1774":1,"1775":2,"1805":1,"1916":2,"1918":3,"2066":1,"2069":1,"2071":2,"2079":1,"2095":1,"2112":1,"2116":1,"2162":3,"2164":1,"2167":1,"2179":1}}],["cancel",{"2":{"268":1},"3":{"0":3,"265":1,"268":1,"517":1,"519":1,"536":2,"556":1,"682":3,"684":2,"793":4,"809":1,"1101":1,"1212":1,"1270":1,"1323":9,"1358":1,"1394":18,"1395":8,"1414":1,"1416":17,"1429":1,"1435":2,"1444":1,"1452":2,"1454":5,"1455":2,"1458":1,"1459":3,"1590":1,"1626":1,"1741":1,"1745":2,"1748":2,"1753":1,"1754":1,"1764":1,"1767":4,"2075":1,"2078":1,"2162":1,"2166":1,"2231":1}}],["canonicalization",{"3":{"1323":1}}],["canonicalizing",{"3":{"1285":1}}],["canonicalizes",{"3":{"1299":1,"1323":1}}],["canonicalize",{"3":{"220":1,"1323":1}}],["canonicalized",{"3":{"219":1,"1323":1,"2149":1}}],["canonically",{"3":{"333":1}}],["canonical",{"3":{"0":1,"98":1,"160":1,"175":1,"202":1,"265":1,"309":1,"324":1,"325":1,"333":1,"366":1,"498":1,"529":1,"782":1,"872":1,"912":1,"944":1,"946":3,"950":1,"963":1,"964":1,"1018":2,"1019":1,"1020":1,"1116":2,"1192":1,"1212":2,"1221":1,"1227":1,"1229":2,"1234":1,"1237":1,"1247":2,"1270":1,"1271":2,"1278":1,"1279":2,"1285":6,"1299":6,"1308":2,"1310":3,"1322":1,"1323":3,"1338":2,"1349":4,"1351":1,"1358":4,"1379":2,"1395":2,"1410":1,"1414":6,"1434":2,"1435":9,"1437":1,"1442":1,"1491":1,"1495":1,"1501":1,"1514":1,"1521":1,"1525":3,"1572":1,"1576":3,"1586":1,"1590":1,"1629":1,"1630":1,"1640":4,"1798":1,"1804":1,"1848":1,"1904":1,"1954":1,"2041":1,"2111":1,"2231":1,"2232":1}}],["can",{"0":{"1235":1,"2043":1},"1":{"1793":1,"1794":1,"1795":1,"1796":1,"1797":1,"1798":1,"1799":1,"1800":1,"1801":1,"1950":1,"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1956":1,"1957":1,"1958":1},"3":{"0":25,"1":1,"5":1,"10":1,"24":1,"26":2,"27":1,"30":1,"31":1,"32":2,"39":1,"42":2,"48":1,"60":1,"68":1,"69":2,"70":1,"72":1,"77":1,"95":1,"97":1,"100":1,"101":1,"103":1,"109":3,"110":1,"111":2,"117":2,"119":1,"121":1,"122":2,"134":1,"135":2,"136":2,"137":1,"140":1,"145":1,"146":1,"150":1,"156":1,"157":2,"159":1,"166":1,"167":1,"170":1,"174":1,"175":1,"177":3,"178":2,"179":1,"184":1,"185":1,"189":2,"194":1,"199":1,"201":1,"202":1,"208":1,"210":1,"214":1,"215":1,"217":1,"231":1,"234":2,"235":1,"236":2,"237":4,"243":1,"245":3,"255":1,"256":1,"259":1,"264":1,"265":3,"266":1,"275":1,"280":1,"283":2,"286":2,"293":1,"299":1,"303":2,"305":2,"310":1,"317":1,"318":1,"319":1,"320":1,"333":1,"340":2,"341":4,"343":1,"344":1,"352":1,"353":1,"358":3,"359":1,"360":1,"365":1,"373":1,"378":1,"380":2,"384":1,"386":1,"390":3,"391":1,"396":1,"397":2,"399":1,"415":2,"420":1,"421":1,"422":1,"427":3,"428":1,"435":2,"441":1,"442":1,"447":1,"449":1,"459":1,"461":2,"474":1,"478":1,"502":1,"506":1,"508":1,"509":1,"511":1,"513":1,"518":1,"519":1,"520":2,"528":1,"529":1,"530":4,"536":1,"538":1,"539":3,"540":1,"544":1,"545":1,"546":1,"549":5,"556":1,"558":2,"564":1,"565":1,"571":1,"572":2,"573":1,"583":2,"592":1,"598":1,"601":1,"610":2,"611":1,"617":3,"619":1,"620":2,"626":4,"627":1,"628":1,"632":1,"633":2,"635":1,"638":1,"640":3,"642":2,"649":1,"651":1,"657":2,"658":3,"665":1,"667":1,"673":3,"675":2,"676":2,"681":1,"682":1,"684":1,"690":4,"691":1,"692":4,"697":3,"698":2,"707":1,"709":2,"714":1,"716":2,"717":1,"718":2,"721":1,"725":3,"726":2,"727":3,"733":2,"735":2,"740":2,"741":1,"742":1,"743":2,"749":2,"750":1,"751":3,"757":3,"758":1,"759":1,"767":1,"768":3,"774":2,"775":1,"783":1,"784":2,"790":1,"791":3,"792":2,"799":2,"800":1,"810":1,"811":1,"815":1,"819":2,"821":2,"826":2,"827":5,"828":2,"829":2,"832":1,"839":1,"840":1,"853":1,"855":1,"856":1,"862":2,"863":1,"876":1,"881":1,"882":1,"891":1,"896":1,"897":1,"898":1,"903":1,"904":2,"905":5,"906":2,"907":2,"909":1,"916":1,"922":2,"924":1,"926":1,"930":1,"933":1,"940":1,"941":1,"952":1,"953":1,"954":1,"956":3,"960":1,"964":1,"965":1,"966":1,"971":2,"972":1,"973":1,"974":1,"982":1,"987":1,"988":2,"989":2,"990":2,"995":2,"996":3,"998":1,"1005":1,"1006":3,"1013":1,"1017":1,"1018":4,"1019":3,"1020":1,"1026":1,"1028":2,"1033":1,"1034":2,"1042":2,"1044":1,"1050":1,"1051":2,"1052":3,"1058":2,"1059":6,"1066":1,"1067":1,"1068":1,"1069":1,"1074":1,"1083":2,"1084":1,"1091":1,"1092":1,"1093":1,"1100":2,"1101":1,"1102":1,"1115":1,"1116":5,"1117":1,"1118":2,"1126":1,"1134":2,"1147":1,"1160":2,"1162":2,"1169":1,"1174":1,"1175":1,"1176":1,"1178":1,"1183":2,"1185":2,"1186":1,"1190":1,"1191":1,"1200":1,"1210":1,"1212":6,"1216":1,"1217":2,"1218":1,"1219":1,"1220":1,"1222":1,"1225":2,"1229":18,"1231":2,"1236":1,"1237":38,"1239":1,"1240":2,"1241":2,"1242":2,"1243":1,"1244":1,"1247":57,"1248":1,"1253":1,"1254":3,"1255":1,"1256":2,"1259":29,"1262":3,"1263":3,"1265":1,"1267":1,"1269":1,"1270":68,"1271":13,"1273":2,"1274":1,"1276":2,"1278":6,"1279":1,"1280":3,"1282":1,"1283":1,"1284":3,"1285":171,"1287":3,"1288":1,"1289":1,"1291":2,"1292":4,"1293":1,"1295":1,"1296":3,"1298":1,"1299":111,"1300":26,"1305":1,"1306":3,"1307":2,"1308":43,"1309":14,"1310":112,"1311":10,"1312":1,"1314":2,"1315":1,"1316":4,"1317":1,"1319":1,"1321":1,"1322":105,"1323":177,"1325":1,"1327":2,"1329":2,"1330":1,"1332":2,"1333":1,"1335":2,"1337":1,"1338":77,"1340":2,"1341":4,"1342":3,"1343":2,"1344":1,"1346":2,"1347":1,"1348":1,"1349":98,"1351":3,"1352":4,"1353":1,"1354":2,"1355":1,"1356":2,"1358":244,"1361":2,"1362":1,"1365":4,"1367":3,"1368":19,"1370":2,"1371":1,"1372":4,"1373":1,"1374":4,"1375":1,"1377":1,"1378":1,"1379":34,"1381":1,"1382":2,"1383":1,"1385":1,"1387":1,"1388":4,"1389":2,"1390":1,"1394":114,"1395":161,"1397":3,"1398":1,"1399":2,"1400":1,"1401":52,"1403":1,"1405":1,"1406":2,"1407":1,"1408":3,"1410":2,"1412":1,"1414":86,"1415":19,"1416":103,"1417":1,"1419":1,"1421":4,"1422":1,"1424":1,"1426":18,"1428":4,"1429":4,"1430":3,"1432":1,"1433":1,"1434":3,"1435":204,"1437":16,"1440":4,"1441":1,"1442":5,"1444":1,"1445":2,"1446":2,"1448":1,"1450":1,"1451":2,"1452":4,"1453":4,"1454":28,"1455":6,"1457":3,"1458":4,"1460":1,"1462":1,"1463":1,"1464":2,"1466":24,"1473":1,"1474":2,"1475":1,"1477":1,"1478":2,"1480":1,"1483":2,"1485":1,"1487":8,"1488":1,"1489":4,"1491":7,"1497":2,"1499":2,"1503":1,"1506":1,"1525":2,"1530":1,"1533":3,"1536":2,"1540":1,"1543":1,"1546":1,"1549":2,"1552":1,"1561":2,"1563":1,"1566":1,"1569":1,"1571":4,"1576":1,"1588":2,"1590":3,"1595":1,"1598":1,"1606":1,"1609":1,"1612":1,"1614":1,"1625":3,"1626":2,"1629":3,"1630":19,"1631":7,"1634":5,"1637":4,"1638":5,"1640":18,"1644":1,"1649":1,"1650":1,"1651":1,"1652":2,"1660":1,"1662":1,"1663":1,"1664":1,"1666":1,"1667":2,"1668":1,"1669":1,"1670":1,"1672":1,"1673":1,"1674":1,"1675":1,"1676":1,"1678":1,"1683":1,"1684":2,"1685":4,"1686":1,"1688":1,"1692":1,"1698":1,"1700":2,"1701":2,"1705":1,"1707":1,"1719":1,"1726":3,"1727":2,"1728":2,"1730":2,"1740":1,"1743":1,"1748":1,"1753":1,"1754":1,"1757":1,"1760":2,"1763":1,"1764":7,"1766":12,"1768":1,"1769":1,"1772":1,"1775":2,"1785":1,"1787":1,"1793":1,"1795":1,"1796":3,"1798":1,"1799":1,"1800":2,"1801":1,"1802":2,"1803":2,"1804":3,"1805":1,"1806":2,"1807":2,"1808":2,"1809":1,"1810":1,"1811":2,"1813":1,"1814":4,"1816":5,"1818":1,"1820":1,"1823":1,"1824":3,"1825":2,"1831":1,"1832":1,"1835":1,"1836":1,"1837":1,"1838":1,"1839":2,"1840":2,"1843":2,"1844":2,"1846":1,"1847":2,"1851":2,"1854":2,"1855":1,"1860":1,"1862":1,"1863":2,"1864":2,"1867":1,"1870":2,"1871":1,"1872":1,"1873":1,"1874":1,"1876":2,"1881":2,"1884":1,"1889":2,"1891":4,"1892":4,"1893":1,"1896":1,"1897":1,"1898":1,"1899":1,"1901":3,"1903":1,"1904":1,"1907":3,"1908":4,"1909":1,"1910":4,"1911":1,"1915":2,"1917":2,"1918":1,"1919":1,"1921":2,"1922":3,"1923":1,"1925":1,"1926":1,"1928":1,"1932":1,"1933":1,"1937":1,"1939":1,"1941":1,"1943":2,"1944":1,"1945":4,"1947":4,"1948":2,"1949":3,"1950":1,"1951":1,"1953":3,"1954":1,"1956":1,"1959":5,"1960":1,"1961":2,"1962":2,"1963":1,"1964":2,"1965":2,"1966":4,"1967":1,"1968":1,"1969":1,"1970":1,"1971":2,"1974":1,"1976":1,"1977":1,"1978":2,"1979":4,"1981":1,"1992":2,"1993":1,"1994":3,"1995":1,"1996":1,"1997":1,"1998":1,"2000":7,"2001":3,"2003":1,"2004":2,"2007":1,"2008":1,"2009":3,"2010":1,"2011":3,"2012":2,"2020":1,"2023":4,"2024":1,"2025":2,"2026":1,"2027":1,"2028":2,"2029":1,"2031":1,"2032":1,"2033":2,"2034":1,"2036":2,"2037":1,"2040":1,"2042":3,"2043":2,"2044":1,"2046":1,"2047":1,"2048":1,"2049":1,"2051":1,"2054":1,"2055":4,"2056":1,"2060":1,"2062":1,"2064":1,"2065":1,"2066":2,"2067":2,"2068":1,"2075":1,"2081":1,"2082":1,"2085":2,"2086":2,"2090":1,"2093":1,"2094":2,"2097":1,"2103":1,"2104":1,"2105":1,"2106":2,"2108":3,"2112":2,"2113":1,"2118":1,"2120":1,"2121":2,"2123":1,"2125":2,"2126":3,"2127":1,"2129":2,"2130":1,"2131":1,"2132":2,"2134":2,"2136":2,"2139":3,"2140":4,"2145":3,"2146":1,"2148":1,"2149":1,"2152":3,"2155":3,"2157":1,"2158":2,"2160":1,"2162":2,"2163":2,"2165":1,"2166":2,"2167":2,"2168":2,"2169":3,"2170":1,"2177":1,"2179":3,"2183":1,"2186":1,"2187":2,"2190":1,"2191":2,"2192":2,"2193":1,"2194":1,"2195":1,"2196":1,"2197":1,"2198":3,"2199":2,"2200":1,"2201":2,"2202":4,"2204":1,"2205":1,"2206":1,"2207":1,"2229":1,"2231":4}}],["cadences",{"3":{"767":1,"1466":1,"1468":1,"1483":1,"1887":1}}],["cadence",{"3":{"0":3,"22":3,"62":2,"143":1,"607":2,"609":1,"626":1,"627":1,"634":1,"642":1,"706":1,"1018":1,"1036":1,"1259":4,"1299":1,"1300":1,"1322":4,"1338":2,"1349":2,"1389":2,"1394":4,"1442":1,"1449":1,"1454":2,"1458":1,"1466":8,"1468":2,"1473":3,"1475":3,"1482":1,"1491":1,"1520":1,"1588":2,"1590":1,"1591":1,"1594":1,"1595":1,"1597":1,"1660":1,"1723":1,"1840":2,"1844":1,"2036":1}}],["capgbpermonth",{"3":{"1466":1}}],["capgbpermonth=",{"3":{"1116":1,"1119":1}}],["capital",{"3":{"1378":1}}],["capitalized",{"3":{"1310":1,"1379":1}}],["capitalization",{"3":{"1299":1,"1310":1,"1666":1}}],["capitals",{"3":{"1285":1,"1323":1}}],["captioning",{"3":{"1629":1,"1630":1,"1636":1}}],["captions",{"3":{"1479":1}}],["caption",{"3":{"1323":9,"1395":3,"1435":3,"1479":5}}],["captive",{"3":{"1259":1,"1358":1,"1416":2}}],["capturingmessagebus",{"3":{"1199":2,"1207":1}}],["capturinghandler",{"3":{"1199":4,"1207":3}}],["capturinghttpmessagehandlertests",{"3":{"1199":1,"1207":2,"1435":2}}],["capturinghttpmessagehandler",{"2":{"1431":1},"3":{"954":1,"1199":24,"1207":4,"1427":1,"1431":8,"1435":12,"1487":1}}],["capturinglogger",{"3":{"1199":4,"1207":2}}],["capturing",{"0":{"1251":1},"3":{"481":1,"483":1,"716":2,"1237":1,"1251":1,"1259":1,"1274":1,"1285":6,"1299":1,"1309":1,"1310":1,"1322":1,"1323":1,"1337":1,"1338":2,"1358":1,"1416":4,"1435":4,"1455":1,"1576":1,"1659":1,"1670":1,"1763":1}}],["captureasync",{"3":{"1435":3}}],["captureavatarasync",{"3":{"1414":1}}],["capturetofileasync",{"3":{"1416":3}}],["capturephotoasync",{"3":{"1416":4}}],["capturejsruntime",{"3":{"1416":1}}],["captureexclusiontable",{"3":{"1285":1}}],["captureeventsandpersisttooutbox",{"3":{"1285":4}}],["captureentry",{"3":{"1285":4}}],["captureorigin",{"3":{"1285":3}}],["capturemodifiedproperties",{"3":{"1285":3}}],["capturechanges",{"3":{"1285":4}}],["capturecontext",{"2":{"1277":1},"3":{"1199":2,"1203":1,"1207":1,"1277":1,"1285":3}}],["captureheaders",{"3":{"1259":2,"1435":2}}],["captures",{"3":{"15":1,"142":1,"202":1,"261":1,"350":1,"373":1,"396":1,"420":1,"423":1,"481":1,"572":1,"676":1,"702":1,"707":1,"715":1,"717":1,"931":1,"1042":1,"1091":1,"1233":1,"1237":1,"1247":2,"1259":4,"1273":1,"1285":8,"1299":2,"1308":2,"1310":1,"1317":1,"1322":3,"1323":1,"1338":3,"1339":1,"1344":1,"1348":1,"1349":3,"1358":8,"1362":1,"1368":2,"1379":1,"1394":3,"1395":9,"1401":1,"1405":1,"1414":3,"1416":10,"1426":1,"1428":1,"1435":8,"1440":1,"1478":2,"1479":2,"1487":1,"1501":1,"1524":1,"1525":1,"1664":1,"1838":1,"1839":1,"1880":1,"1907":1,"1947":1,"1975":1,"2054":1,"2090":1}}],["capturedstate",{"3":{"1199":3,"1203":1,"1207":1,"1237":1,"1274":1,"1285":6}}],["capturedrequest",{"3":{"1199":4,"1207":2,"1431":1,"1435":5}}],["capturedmeasurement",{"3":{"1199":2,"1207":1}}],["capturedcounter",{"3":{"1199":2,"1207":1}}],["captured",{"0":{"1253":1},"3":{"0":1,"21":1,"26":2,"27":1,"109":1,"120":1,"202":1,"243":1,"330":1,"359":1,"397":1,"399":1,"403":1,"483":1,"498":1,"500":2,"501":1,"682":1,"698":1,"716":1,"720":2,"781":1,"786":1,"860":1,"1020":1,"1043":1,"1212":2,"1231":1,"1232":1,"1233":1,"1237":7,"1246":1,"1247":1,"1251":1,"1252":1,"1253":1,"1259":10,"1270":3,"1274":4,"1279":1,"1285":29,"1299":3,"1300":1,"1308":3,"1309":2,"1310":8,"1317":1,"1322":10,"1323":3,"1325":1,"1326":2,"1327":1,"1338":9,"1349":10,"1354":1,"1357":1,"1358":14,"1379":1,"1394":18,"1395":5,"1397":1,"1401":4,"1405":1,"1409":1,"1414":3,"1415":1,"1416":6,"1435":6,"1437":2,"1454":1,"1466":1,"1478":2,"1479":1,"1495":1,"1507":1,"1566":1,"1570":1,"1630":1,"1640":1,"1664":1,"1676":1,"1685":1,"1824":1,"1842":1,"1843":1,"1893":1,"1979":2,"1980":1,"2110":1,"2191":1}}],["capture",{"0":{"1478":2},"3":{"0":6,"21":8,"202":1,"439":1,"440":1,"481":1,"490":1,"698":1,"713":1,"714":2,"715":2,"716":1,"717":2,"720":1,"721":1,"749":1,"751":2,"905":1,"1036":1,"1212":1,"1213":1,"1218":1,"1229":2,"1233":1,"1237":5,"1249":2,"1251":4,"1253":3,"1255":1,"1259":14,"1270":1,"1272":1,"1274":4,"1277":1,"1285":30,"1299":2,"1308":2,"1310":3,"1311":1,"1317":2,"1322":1,"1338":1,"1349":1,"1358":1,"1394":2,"1395":1,"1401":1,"1414":2,"1416":19,"1432":2,"1435":14,"1437":1,"1454":1,"1470":1,"1478":15,"1479":4,"1481":1,"1482":1,"1487":3,"1536":2,"1551":1,"1574":1,"1576":1,"1583":1,"1663":1,"1671":1,"1766":1,"1796":1,"1803":1,"1824":2,"1837":1,"1838":1,"2052":1,"2125":1,"2231":2}}],["capacities",{"3":{"1358":1}}],["capacity",{"3":{"413":1,"519":1,"591":1,"628":1,"630":1,"768":2,"827":2,"1100":1,"1101":1,"1124":1,"1125":1,"1270":1,"1300":1,"1310":3,"1323":3,"1336":1,"1338":2,"1349":4,"1358":18,"1368":1,"1379":4,"1394":13,"1395":4,"1399":1,"1415":1,"1416":3,"1454":3,"1457":3,"1458":1,"1460":1,"1466":4,"1483":1,"1522":2,"1523":2,"1524":1,"1525":1,"1529":1,"1530":1,"1532":1,"1533":1,"1548":1,"1576":2,"1581":1,"1588":1,"1595":1,"1628":1,"1629":2,"1630":2,"1636":1,"1669":1,"1707":1,"1840":1,"1946":1,"2120":2}}],["capable",{"3":{"92":1,"96":1,"100":1,"810":1,"838":1,"841":1,"988":2,"1243":1,"1247":1,"1285":2,"1300":3,"1338":1,"1358":1,"1368":1,"1394":1,"1395":1,"1416":2,"1446":1,"2072":1}}],["capabilitiesjsmodule",{"2":{"2118":1},"3":{"1199":10,"1203":1,"1207":2,"1323":5,"1416":22,"2118":1}}],["capabilities",{"0":{"2200":1},"1":{"1959":1,"1960":1,"1961":1,"1962":1,"1963":1,"1964":1},"2":{"1175":1,"1415":2,"1416":9},"3":{"0":3,"185":1,"186":6,"187":2,"190":1,"245":1,"266":1,"283":1,"304":1,"324":1,"412":1,"413":7,"414":2,"415":2,"416":2,"418":1,"420":1,"423":1,"427":1,"428":2,"430":1,"434":1,"436":1,"461":1,"556":1,"682":5,"684":1,"689":1,"820":1,"980":1,"1034":1,"1059":1,"1060":1,"1107":1,"1175":3,"1176":1,"1200":1,"1203":89,"1207":200,"1208":1,"1238":1,"1270":1,"1285":1,"1296":4,"1299":7,"1308":1,"1310":1,"1316":1,"1322":1,"1323":9,"1349":3,"1376":1,"1379":1,"1394":1,"1395":8,"1408":2,"1414":8,"1415":3,"1416":418,"1435":1,"1437":1,"1495":5,"1499":1,"1576":1,"1598":1,"1620":1,"1625":1,"1626":1,"1628":1,"1629":1,"1631":1,"1637":1,"1638":1,"1661":1,"1666":1,"1959":2,"1960":1,"1962":1,"1964":2,"2116":1,"2117":3,"2118":1,"2120":3,"2121":1,"2122":2,"2193":1,"2194":1,"2200":1,"2202":1,"2207":1}}],["capabilityfallbacktests",{"3":{"1199":1,"1207":2,"1416":32,"1437":2}}],["capability",{"0":{"1231":1,"1408":1,"1809":1,"2116":1},"1":{"410":1,"411":1,"412":1,"413":1,"414":1,"415":1,"416":1,"679":1,"680":1,"681":1,"682":1,"683":1,"684":1,"685":1,"686":1,"1047":1,"1048":1,"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1,"1416":1,"1658":1,"1659":1,"1660":1,"1661":1,"1662":1,"1663":1,"1664":1,"1665":1,"1666":1,"1667":1,"1668":1,"1669":1,"1670":1,"1671":1,"1672":1,"1673":1,"1674":1,"2115":1,"2116":1,"2117":1,"2118":1,"2119":1,"2120":1,"2121":1,"2122":1},"2":{"1759":1},"3":{"0":14,"39":1,"115":1,"126":3,"184":2,"185":4,"186":4,"188":2,"191":1,"243":2,"258":1,"300":1,"301":1,"317":2,"318":1,"319":1,"324":2,"358":1,"410":1,"411":1,"413":5,"414":2,"415":2,"426":1,"434":1,"439":1,"440":1,"468":1,"482":1,"510":1,"549":1,"558":1,"649":1,"650":1,"653":1,"656":1,"661":1,"665":1,"679":1,"681":4,"682":2,"683":2,"686":2,"689":1,"691":1,"694":2,"696":1,"698":1,"705":1,"706":1,"708":1,"713":1,"717":1,"723":1,"725":1,"739":1,"741":1,"749":1,"797":1,"798":2,"802":1,"804":2,"806":1,"820":2,"954":1,"957":1,"964":1,"965":3,"966":2,"968":1,"979":1,"1003":1,"1004":1,"1034":1,"1047":1,"1048":1,"1049":2,"1050":1,"1051":1,"1052":3,"1053":1,"1054":1,"1055":1,"1060":1,"1115":1,"1116":2,"1118":1,"1120":1,"1132":1,"1176":1,"1178":1,"1191":1,"1192":4,"1194":2,"1195":1,"1200":1,"1201":2,"1202":3,"1203":3,"1212":5,"1217":1,"1228":1,"1229":2,"1230":1,"1231":1,"1232":1,"1237":5,"1247":2,"1263":4,"1264":1,"1270":12,"1271":1,"1285":10,"1286":1,"1296":3,"1299":23,"1300":4,"1302":2,"1303":2,"1307":1,"1308":19,"1309":3,"1310":6,"1314":1,"1321":1,"1322":11,"1323":14,"1338":1,"1346":1,"1347":8,"1349":20,"1356":2,"1358":11,"1366":1,"1371":2,"1372":8,"1376":2,"1379":29,"1380":1,"1387":1,"1390":1,"1394":10,"1395":44,"1401":6,"1403":1,"1404":1,"1405":1,"1408":5,"1413":5,"1414":22,"1415":10,"1416":104,"1421":1,"1426":2,"1430":1,"1435":11,"1437":4,"1480":2,"1490":2,"1495":13,"1504":1,"1512":1,"1513":1,"1516":1,"1525":4,"1530":1,"1533":1,"1540":1,"1541":1,"1574":1,"1576":2,"1581":2,"1582":1,"1584":1,"1598":3,"1601":1,"1620":2,"1625":3,"1626":4,"1630":1,"1631":1,"1639":3,"1652":1,"1658":2,"1659":2,"1668":1,"1672":1,"1674":1,"1732":1,"1759":2,"1760":1,"1769":1,"1770":1,"1778":2,"1809":3,"1820":4,"1822":2,"1859":1,"1867":1,"1902":1,"1903":1,"1920":1,"1931":1,"1947":1,"1958":1,"1959":3,"1960":4,"1961":6,"1962":1,"1963":4,"1964":3,"1977":1,"1994":3,"2062":2,"2082":1,"2114":2,"2115":3,"2116":4,"2117":2,"2119":1,"2121":1,"2122":6,"2125":2,"2126":1,"2136":1,"2137":2,"2138":1,"2142":1,"2197":1,"2198":1,"2201":1,"2202":1,"2209":2,"2231":6}}],["capping",{"3":{"176":1,"332":1,"1242":1,"1247":1,"1285":1,"1310":1,"1349":1,"1401":1,"1596":1}}],["capped",{"3":{"0":5,"19":1,"22":1,"23":1,"328":1,"330":1,"333":2,"335":1,"378":1,"384":1,"707":1,"854":1,"1026":1,"1042":2,"1095":1,"1118":1,"1212":1,"1237":1,"1238":1,"1243":2,"1247":3,"1256":1,"1259":6,"1275":2,"1278":2,"1285":6,"1290":1,"1291":1,"1293":1,"1299":6,"1308":1,"1310":1,"1316":1,"1322":2,"1323":2,"1338":1,"1343":1,"1356":1,"1358":2,"1368":2,"1370":1,"1387":1,"1394":16,"1395":3,"1401":2,"1414":1,"1435":2,"1442":1,"1466":1,"1577":1,"1588":1,"1630":2,"1631":1,"1639":1,"1640":3,"1664":1,"1666":1,"1667":1,"1712":1,"1747":3,"1764":3,"1783":1,"1799":1,"1814":2,"1828":2,"1840":1,"1922":1,"1936":1,"1947":1,"1987":2,"1997":1,"1999":1,"2047":1,"2177":1,"2187":1}}],["capseconds",{"3":{"1285":1}}],["caps",{"3":{"0":2,"27":1,"175":2,"280":2,"282":3,"330":1,"333":1,"334":1,"361":1,"552":1,"669":1,"691":1,"692":1,"740":1,"830":1,"832":1,"853":1,"907":1,"1042":1,"1118":1,"1212":2,"1237":1,"1241":2,"1247":3,"1259":2,"1285":5,"1293":1,"1299":2,"1304":1,"1306":1,"1308":2,"1309":1,"1322":3,"1323":2,"1343":2,"1349":16,"1358":5,"1383":2,"1387":1,"1394":9,"1395":1,"1397":2,"1400":1,"1401":4,"1410":1,"1414":1,"1416":1,"1435":7,"1441":1,"1446":1,"1456":3,"1466":3,"1525":1,"1533":1,"1576":2,"1590":1,"1598":3,"1599":1,"1670":1,"1944":1,"1987":1,"1989":1,"1998":1,"1999":1,"2001":3,"2144":1,"2169":1,"2231":1}}],["cap",{"0":{"2053":1},"3":{"0":16,"19":4,"23":3,"26":1,"27":1,"57":1,"59":2,"174":1,"175":2,"176":3,"177":2,"178":3,"179":1,"180":1,"182":1,"223":1,"225":1,"228":1,"280":1,"282":1,"283":1,"287":1,"335":2,"350":1,"395":2,"403":2,"434":1,"438":1,"442":1,"500":2,"501":2,"524":2,"607":2,"609":2,"611":1,"612":3,"710":1,"719":2,"741":8,"743":2,"744":1,"749":1,"767":1,"825":1,"826":2,"827":2,"828":1,"831":1,"855":1,"856":1,"905":6,"906":4,"907":2,"909":1,"996":3,"1005":1,"1006":2,"1059":1,"1116":3,"1119":2,"1124":1,"1212":2,"1237":2,"1242":2,"1243":2,"1247":10,"1254":1,"1256":1,"1259":9,"1273":1,"1275":2,"1282":1,"1284":1,"1285":3,"1289":1,"1293":1,"1294":1,"1299":23,"1306":1,"1308":5,"1310":9,"1322":15,"1323":10,"1325":1,"1338":1,"1343":1,"1346":2,"1349":14,"1356":4,"1358":12,"1370":1,"1372":1,"1379":1,"1382":1,"1383":1,"1388":4,"1389":2,"1391":1,"1394":32,"1395":12,"1398":2,"1400":1,"1401":18,"1405":1,"1408":1,"1413":1,"1414":7,"1415":1,"1416":1,"1435":14,"1437":3,"1446":2,"1455":3,"1459":2,"1464":7,"1466":21,"1468":1,"1472":1,"1477":2,"1487":1,"1488":1,"1524":1,"1525":4,"1530":3,"1531":2,"1533":2,"1574":1,"1580":1,"1582":1,"1583":1,"1585":1,"1590":6,"1595":2,"1596":2,"1598":1,"1630":1,"1631":33,"1634":1,"1639":4,"1661":1,"1666":1,"1766":1,"1816":1,"1824":1,"1828":1,"1976":1,"1981":4,"1982":2,"1983":4,"1984":1,"1987":1,"1990":1,"1997":2,"1998":2,"1999":2,"2000":3,"2001":5,"2013":1,"2023":2,"2059":1,"2072":1,"2078":1,"2125":1,"2165":1,"2168":1,"2197":1,"2231":1}}],["chronological",{"3":{"1308":1,"1382":1,"1394":2,"1395":1,"1631":2,"1639":4}}],["chronologically",{"3":{"741":1,"1358":1,"1379":1,"1391":1,"1394":2}}],["chrome",{"3":{"265":4,"267":1,"275":1,"860":1,"861":1,"954":1,"1323":28,"1394":4,"1395":2,"1401":1,"1415":3,"1435":6,"1437":1,"1525":1,"1558":1,"1576":1,"1590":1,"1605":1,"1608":1,"1643":1,"1652":1,"1668":1,"1686":2,"1715":1,"1739":1,"1742":1,"2078":3,"2084":1}}],["chromium",{"3":{"0":4,"265":2,"558":1,"591":1,"618":2,"619":2,"625":3,"626":2,"756":1,"861":1,"863":3,"1212":3,"1323":2,"1413":1,"1432":3,"1435":7,"1437":2,"1454":1,"1455":7,"1459":1,"1460":2,"1462":2,"1485":1,"1487":4,"1489":2,"1491":5,"1523":1,"1525":8,"1530":3,"1531":1,"1533":3,"1534":1,"1576":6,"1585":1,"1590":10,"1595":5,"1598":3,"1599":1,"1642":1,"1715":2,"1738":1,"1774":1,"2056":2,"2072":1,"2078":2}}],["cheat",{"3":{"691":1,"1368":1}}],["cheaper",{"3":{"627":1,"675":1,"741":1,"947":1,"964":1,"965":1,"981":1,"1019":1,"1041":1,"1051":1,"1092":1,"1124":1,"1144":1,"1229":1,"1285":4,"1299":2,"1310":1,"1322":1,"1323":1,"1328":1,"1338":2,"1358":6,"1394":4,"1395":3,"1401":1,"1414":1,"1416":1,"1435":3,"1442":1,"1490":1,"1630":1,"1677":1,"1707":1,"1950":1,"1999":1,"2048":1,"2149":1}}],["cheapest",{"3":{"110":1,"150":1,"235":1,"242":1,"266":1,"319":1,"742":1,"915":1,"973":1,"1019":1,"1234":1,"1237":2,"1247":3,"1259":1,"1270":1,"1285":1,"1299":1,"1308":1,"1310":2,"1323":1,"1358":3,"1368":1,"1391":1,"1394":2,"1395":3,"1416":3,"1435":7,"1466":1,"1487":1,"1530":1,"1801":1,"1809":1,"2053":1,"2138":1}}],["cheaply",{"3":{"244":1,"460":1,"1052":1,"1142":1,"1277":1,"1285":1,"1288":1,"1299":2,"1308":1,"1310":1,"1358":3,"1394":1,"1395":1,"1401":1,"1414":1,"1435":6,"1604":1,"1738":1,"2023":1,"2092":1,"2125":1}}],["cheap",{"3":{"0":2,"18":1,"110":1,"111":1,"165":1,"174":1,"236":1,"237":1,"295":1,"309":1,"529":1,"546":1,"565":2,"619":1,"641":1,"684":1,"734":1,"751":1,"758":1,"767":1,"776":1,"783":1,"811":1,"813":1,"826":2,"855":1,"871":1,"932":1,"953":1,"962":1,"965":1,"989":1,"1005":2,"1049":1,"1075":1,"1099":1,"1123":1,"1154":1,"1212":1,"1229":1,"1237":1,"1242":1,"1247":3,"1259":2,"1270":1,"1271":1,"1285":9,"1291":1,"1299":3,"1300":2,"1310":5,"1322":2,"1323":5,"1338":2,"1349":1,"1358":11,"1368":3,"1375":1,"1379":5,"1386":1,"1394":9,"1395":14,"1401":3,"1414":1,"1416":4,"1426":4,"1435":5,"1437":2,"1448":1,"1454":4,"1455":2,"1466":1,"1474":1,"1491":1,"1530":1,"1533":1,"1588":1,"1640":1,"1676":2,"1677":1,"1788":1,"1791":1,"1809":1,"1831":2,"1901":1,"1933":1,"1957":1,"1998":1,"1999":2,"2097":1,"2143":1,"2147":1,"2157":1,"2178":1,"2179":1,"2183":1,"2196":1}}],["checkpoint",{"3":{"1428":2,"1648":1,"1653":1}}],["checkpreconditions",{"3":{"1395":4}}],["checknotlocked",{"3":{"1414":1}}],["checknameprefix",{"3":{"1338":1}}],["checkname",{"2":{"1220":1},"3":{"1220":1,"1338":1}}],["checkkey",{"3":{"1338":2,"1440":1}}],["checkhealthasync",{"3":{"1338":9}}],["checkboxes",{"3":{"1323":2,"1435":1}}],["checkbox",{"3":{"1323":6,"1395":1,"1435":4,"1495":1,"1625":1,"2139":1}}],["checkdate",{"2":{"1220":1},"3":{"1220":1}}],["checkownership",{"2":{"725":1,"1321":1,"1666":1},"3":{"725":1,"1321":1,"1322":4,"1414":2,"1666":1}}],["checkoutoutcome",{"3":{"1599":1}}],["checkoutandpayasync",{"3":{"1599":1}}],["checkoutcommand",{"3":{"988":1,"1270":2,"1748":2}}],["checkoutpreflight",{"2":{"1025":1,"1029":1},"3":{"897":1,"900":1,"1025":2,"1026":2,"1029":2,"1599":1}}],["checkouts",{"3":{"566":1,"567":1}}],["checkoutdomainservice",{"2":{"1025":1},"3":{"0":1,"1025":2,"1764":1,"1766":1}}],["checkoutdeadlinescheduler",{"2":{"534":1,"539":1,"540":1,"814":1},"3":{"0":1,"534":1,"539":5,"540":3,"674":2,"677":1,"814":2,"1599":1}}],["checkouthandler",{"2":{"535":1,"539":1,"677":1,"813":1,"814":1,"986":1,"990":1,"991":1,"1025":1,"1599":1},"3":{"0":2,"534":3,"535":2,"539":3,"540":1,"677":1,"813":2,"814":2,"986":1,"988":4,"990":2,"991":1,"1025":2,"1026":1,"1029":1,"1590":2,"1599":3,"1764":1,"1766":1,"2160":1}}],["checkout",{"1":{"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1},"2":{"790":2,"1764":1,"1769":2},"3":{"0":6,"534":3,"535":3,"536":2,"539":6,"562":1,"566":2,"567":1,"674":1,"782":1,"790":2,"793":3,"794":1,"811":1,"813":2,"814":1,"881":2,"897":5,"900":3,"988":2,"1023":1,"1025":3,"1026":2,"1027":3,"1028":2,"1029":1,"1212":1,"1425":2,"1452":1,"1453":4,"1454":2,"1474":1,"1482":1,"1499":1,"1587":1,"1590":3,"1599":5,"1741":1,"1745":4,"1748":6,"1749":4,"1750":1,"1751":1,"1752":1,"1754":3,"1756":1,"1758":1,"1762":2,"1764":14,"1766":4,"1767":3,"1768":2,"1769":3,"1772":2,"1774":2,"1775":3,"2160":3,"2162":1,"2166":3,"2231":1}}],["checklist",{"0":{"1571":1,"1656":1,"1689":1,"1703":1},"3":{"620":1,"622":1,"1395":1,"1401":1,"1435":1,"1471":4,"1480":1,"1495":1,"1576":1,"1584":1,"1643":1,"1874":1,"2037":1}}],["checklockoutasync",{"3":{"281":2,"1299":3,"1999":1}}],["checkregistrationratelimitasync",{"2":{"281":1,"1999":1},"3":{"281":1,"1299":4,"1999":1}}],["checkable",{"3":{"72":1,"620":1,"1084":1,"1270":3,"1285":1,"1299":3,"1323":1,"1358":2,"1379":1,"1414":1,"1487":1,"1882":1}}],["checkinbycredentialasync",{"3":{"1395":2}}],["checkinidentifiertype",{"3":{"1395":1}}],["checkininvariants",{"2":{"690":1},"3":{"690":1,"1199":2,"1203":1,"1207":2,"1395":8}}],["checkintests",{"3":{"1199":1,"1207":2,"1395":1,"1437":1}}],["checkinasync",{"3":{"1395":2}}],["checkinauthorizationtests",{"3":{"1199":1,"1207":2,"1437":2}}],["checkinattendee",{"3":{"1203":2,"1207":4,"1271":2,"1395":9}}],["checkinattendeehandlertests",{"3":{"1199":1,"1207":3,"1437":1}}],["checkinattendeehandler",{"3":{"1199":2,"1203":1,"1207":2,"1271":3,"1349":2,"1395":21,"1437":1}}],["checkinattendeerequestvalidatortests",{"3":{"1199":1,"1207":2}}],["checkinattendeerequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1395":7}}],["checkinattendeerequest",{"3":{"1199":11,"1203":1,"1207":2,"1395":12}}],["checkinandpointstests",{"3":{"1199":1,"1207":2,"1437":2}}],["checkindtomapper",{"3":{"1199":1,"1203":1,"1207":2,"1395":7}}],["checkindto",{"3":{"1199":2,"1203":1,"1207":2,"1395":11}}],["checkinerrorcodes",{"3":{"1199":5,"1203":1,"1207":2,"1395":16}}],["checkinresultdto",{"3":{"1199":10,"1203":1,"1207":2,"1395":20}}],["checkinrow",{"3":{"1199":2,"1207":1}}],["checkinconfiguration",{"3":{"690":1,"691":1,"1199":1,"1203":1,"1207":2,"1285":1,"1395":24}}],["checkinprocessor",{"2":{"690":1},"3":{"690":2,"1199":5,"1203":1,"1207":3,"1349":5,"1358":2,"1395":73,"1435":1}}],["checkinmanage",{"2":{"690":1},"3":{"690":1,"1395":12}}],["checkinservicetests",{"3":{"1199":1,"1207":3,"1395":1,"1437":1}}],["checkinservice",{"3":{"1199":8,"1203":1,"1207":2,"1395":35}}],["checkinsettingstests",{"3":{"1199":1,"1207":2,"1395":1}}],["checkinsettings",{"2":{"690":1},"3":{"674":1,"690":1,"1199":4,"1203":1,"1207":2,"1349":2,"1395":9}}],["checkinstate",{"3":{"1199":2,"1203":1,"1207":1,"1395":8}}],["checkinscontrollertests",{"3":{"1199":1,"1207":3,"1395":2,"1437":2}}],["checkinscontroller",{"2":{"693":1},"3":{"690":3,"693":1,"1199":2,"1203":1,"1207":2,"1395":82}}],["checkinscopenamestests",{"3":{"1199":1,"1207":2,"1395":1}}],["checkinscopenames",{"3":{"1199":11,"1203":1,"1207":2,"1395":11}}],["checkinscope",{"2":{"688":1,"799":1,"803":1,"1052":1,"1054":1},"3":{"688":1,"690":2,"799":1,"803":1,"1052":1,"1054":1,"1199":34,"1203":1,"1207":2,"1395":37,"1414":1}}],["checkinscanroundtriptests",{"3":{"1199":1,"1207":2,"1437":2}}],["checkinscantests",{"3":{"1199":1,"1207":2,"1395":1,"1437":2}}],["checkinscanpage",{"3":{"1199":3,"1207":2}}],["checkinscan",{"2":{"693":1},"3":{"684":3,"685":3,"690":5,"693":1,"1199":2,"1203":1,"1207":4,"1395":56,"1416":1}}],["checkins",{"2":{"554":1,"1207":1,"1395":5},"3":{"0":1,"554":1,"556":3,"674":1,"690":11,"693":3,"799":1,"837":1,"838":1,"881":1,"1052":1,"1055":1,"1203":50,"1207":174,"1237":2,"1271":10,"1285":4,"1323":2,"1349":1,"1358":3,"1395":174,"1414":3,"1435":2,"1437":6,"1446":1,"1486":1,"1525":1}}],["checking",{"3":{"0":1,"390":1,"427":1,"460":1,"507":2,"579":1,"675":1,"827":2,"973":1,"1133":1,"1168":1,"1229":2,"1259":1,"1263":1,"1270":1,"1285":5,"1299":3,"1308":1,"1323":7,"1338":4,"1349":1,"1358":4,"1379":2,"1394":2,"1395":4,"1414":1,"1415":1,"1416":3,"1430":1,"1435":9,"1437":2,"1454":1,"1488":1,"1495":1,"1629":1,"1640":1,"1668":1,"1811":1,"1907":1,"1946":1,"1951":1,"1968":1,"1992":1,"2044":1}}],["checkin",{"2":{"474":1,"797":1,"801":2,"803":1,"1048":1,"1054":1,"1055":1,"1626":1},"3":{"0":2,"186":1,"474":1,"690":7,"691":1,"694":1,"797":1,"799":3,"801":4,"803":2,"1048":1,"1052":2,"1054":1,"1055":2,"1199":16,"1203":1,"1207":2,"1212":1,"1237":2,"1395":84,"1437":5,"1486":1,"1525":1,"1626":2,"1630":2,"2231":1}}],["checkemailconfirmed",{"2":{"2195":1},"3":{"1299":3,"2195":2}}],["checker",{"3":{"0":1,"2046":3}}],["checkedout",{"3":{"1748":2,"1749":1,"1763":1,"1764":1}}],["checkedin",{"3":{"1395":2}}],["checkedinon",{"3":{"1395":17,"1414":2}}],["checkedinbyuserid",{"2":{"799":1},"3":{"690":1,"799":1,"1395":12,"1630":1}}],["checked",{"3":{"0":2,"109":2,"135":1,"139":1,"160":1,"186":1,"209":1,"243":2,"250":2,"252":2,"254":1,"256":2,"257":2,"258":1,"259":1,"260":1,"261":2,"284":1,"318":1,"323":1,"335":3,"342":1,"350":2,"390":1,"397":1,"418":2,"423":1,"424":1,"426":1,"438":1,"442":1,"463":1,"464":1,"465":1,"489":1,"490":1,"507":1,"572":1,"576":1,"584":1,"585":1,"591":1,"611":1,"617":1,"628":1,"633":1,"642":1,"657":1,"660":1,"674":1,"692":1,"715":1,"718":1,"759":1,"766":1,"798":1,"811":1,"827":1,"838":1,"846":1,"867":1,"945":1,"959":1,"975":1,"1034":1,"1037":1,"1059":1,"1067":1,"1074":1,"1091":1,"1118":1,"1124":1,"1161":1,"1168":1,"1212":1,"1229":1,"1237":1,"1247":3,"1259":2,"1263":1,"1270":8,"1273":1,"1277":2,"1285":10,"1296":1,"1297":1,"1299":12,"1300":1,"1305":1,"1308":2,"1310":8,"1316":1,"1322":7,"1323":6,"1338":3,"1343":1,"1349":7,"1358":26,"1368":1,"1372":1,"1379":4,"1383":1,"1388":1,"1389":2,"1394":4,"1395":12,"1401":6,"1414":10,"1416":2,"1418":1,"1426":4,"1430":2,"1435":23,"1444":1,"1452":2,"1454":1,"1456":1,"1458":1,"1465":1,"1474":1,"1480":1,"1486":1,"1495":3,"1562":1,"1570":1,"1574":1,"1576":1,"1599":1,"1626":1,"1630":1,"1661":1,"1698":1,"1720":1,"1721":1,"1726":3,"1728":1,"1747":2,"1748":1,"1749":1,"1753":1,"1754":1,"1758":1,"1764":1,"1766":1,"1772":2,"1775":1,"1822":1,"1928":1,"1950":1,"1954":1,"1956":1,"1957":1,"1961":1,"1970":1,"1979":1,"1981":1,"2040":1,"2054":1,"2088":1,"2095":1,"2231":1}}],["checkstatusasync",{"3":{"1416":1}}],["checks",{"0":{"1331":1},"1":{"1959":1,"1960":1,"1961":1,"1962":1,"1963":1,"1964":1},"3":{"0":6,"12":1,"70":1,"74":1,"79":1,"100":1,"126":1,"132":1,"134":1,"135":1,"158":1,"166":1,"170":1,"187":1,"191":1,"201":1,"207":1,"233":2,"235":3,"236":1,"237":2,"243":1,"259":1,"282":1,"283":1,"287":1,"300":1,"349":1,"359":1,"402":2,"435":1,"453":1,"507":1,"536":1,"545":1,"558":1,"564":1,"566":1,"571":1,"572":1,"574":2,"577":1,"583":2,"591":1,"599":3,"600":1,"611":2,"626":1,"629":1,"651":1,"657":2,"659":1,"690":1,"700":1,"736":1,"744":1,"749":1,"757":3,"784":1,"826":1,"827":2,"829":1,"833":2,"834":1,"910":1,"913":1,"924":1,"956":1,"960":1,"990":1,"996":1,"1004":1,"1005":1,"1050":1,"1059":1,"1066":2,"1068":1,"1077":1,"1078":1,"1108":1,"1109":1,"1110":1,"1160":1,"1168":1,"1220":2,"1228":1,"1229":8,"1234":5,"1237":12,"1239":1,"1241":6,"1242":1,"1247":19,"1259":1,"1261":1,"1263":1,"1264":1,"1267":1,"1270":9,"1271":1,"1276":2,"1285":19,"1287":1,"1288":2,"1297":2,"1299":28,"1300":3,"1304":1,"1308":6,"1309":2,"1310":21,"1311":3,"1313":1,"1314":1,"1322":11,"1323":20,"1324":1,"1327":2,"1329":1,"1330":1,"1331":4,"1332":1,"1336":1,"1337":3,"1338":38,"1342":1,"1343":2,"1346":1,"1347":3,"1348":1,"1349":23,"1351":2,"1354":1,"1356":1,"1358":69,"1364":1,"1368":6,"1378":2,"1379":5,"1394":16,"1395":21,"1397":1,"1398":1,"1401":23,"1414":12,"1415":2,"1416":7,"1426":5,"1429":3,"1430":1,"1433":1,"1435":47,"1437":5,"1440":1,"1441":1,"1442":9,"1446":2,"1449":1,"1452":2,"1453":3,"1454":3,"1458":1,"1465":1,"1466":3,"1471":2,"1485":2,"1487":1,"1488":1,"1489":1,"1491":4,"1495":13,"1496":1,"1525":1,"1530":2,"1547":1,"1549":2,"1550":2,"1557":1,"1562":1,"1564":1,"1570":1,"1571":1,"1573":1,"1576":10,"1581":1,"1590":1,"1595":2,"1607":1,"1610":1,"1626":2,"1631":1,"1640":4,"1659":1,"1660":1,"1667":1,"1669":2,"1672":1,"1676":1,"1683":1,"1741":1,"1758":1,"1760":1,"1763":1,"1764":2,"1799":1,"1801":1,"1804":2,"1810":1,"1814":2,"1820":1,"1823":1,"1831":2,"1833":1,"1844":1,"1864":1,"1881":1,"1896":1,"1901":1,"1908":1,"1916":1,"1922":1,"1932":1,"1959":3,"1964":1,"1967":1,"1968":1,"1973":3,"1995":1,"1996":1,"2003":1,"2006":2,"2009":1,"2011":1,"2029":1,"2039":1,"2041":2,"2046":1,"2054":1,"2055":2,"2056":1,"2092":1,"2097":1,"2098":1,"2106":1,"2121":1,"2133":1,"2134":1,"2141":1,"2154":1,"2163":1,"2166":1,"2174":1,"2199":1,"2202":1,"2207":1,"2226":1}}],["check",{"0":{"1696":1},"1":{"687":1,"688":1,"689":1,"690":1,"691":1,"692":1,"693":1,"694":1},"3":{"0":24,"11":1,"24":1,"59":1,"80":1,"93":2,"94":1,"99":3,"109":1,"110":1,"115":1,"121":1,"122":2,"126":3,"133":1,"135":3,"157":2,"164":1,"166":1,"171":2,"175":1,"186":1,"188":1,"189":1,"195":2,"196":1,"200":1,"201":3,"202":2,"209":1,"225":1,"231":1,"233":1,"234":3,"235":7,"237":3,"239":1,"241":1,"245":3,"246":1,"247":1,"257":1,"259":1,"265":2,"268":1,"279":2,"284":2,"285":1,"290":1,"295":1,"296":1,"302":1,"305":1,"313":1,"316":1,"317":2,"318":6,"320":1,"322":2,"326":2,"340":1,"341":3,"342":2,"350":1,"352":2,"360":1,"370":1,"371":1,"373":1,"384":2,"386":1,"388":1,"390":1,"391":2,"402":1,"406":1,"413":1,"423":1,"427":2,"429":1,"448":3,"453":1,"458":1,"459":4,"460":1,"461":1,"464":1,"490":1,"499":1,"505":1,"507":1,"513":2,"529":1,"536":3,"551":1,"556":1,"564":1,"565":2,"566":1,"572":5,"574":2,"583":1,"584":1,"585":2,"591":1,"592":2,"594":3,"611":2,"617":1,"618":2,"619":1,"625":1,"626":1,"633":2,"636":3,"638":1,"642":1,"681":2,"683":2,"684":1,"685":1,"687":1,"688":1,"689":4,"690":5,"691":6,"692":2,"694":2,"701":1,"709":1,"724":1,"725":1,"729":2,"733":2,"743":1,"749":3,"756":1,"757":1,"758":1,"764":1,"765":1,"766":2,"767":2,"768":2,"769":1,"790":1,"799":3,"801":2,"827":6,"829":2,"834":1,"838":2,"839":1,"840":1,"847":1,"869":1,"876":1,"891":1,"905":3,"923":1,"924":1,"946":2,"948":1,"950":1,"962":1,"971":1,"974":1,"997":1,"1018":2,"1033":1,"1034":3,"1036":1,"1037":2,"1042":1,"1050":1,"1067":1,"1074":1,"1077":1,"1092":1,"1093":1,"1100":1,"1110":2,"1111":1,"1117":2,"1118":1,"1155":1,"1163":2,"1170":1,"1175":1,"1190":1,"1192":2,"1211":1,"1212":4,"1213":1,"1229":3,"1231":1,"1237":19,"1241":1,"1244":1,"1247":15,"1255":1,"1259":6,"1263":4,"1264":2,"1265":1,"1267":1,"1270":46,"1271":12,"1275":1,"1280":2,"1285":58,"1287":1,"1288":4,"1289":1,"1292":2,"1294":1,"1295":1,"1296":3,"1299":62,"1300":6,"1303":1,"1306":2,"1307":1,"1308":11,"1309":3,"1310":29,"1311":2,"1315":1,"1316":1,"1321":1,"1322":29,"1323":38,"1324":1,"1325":1,"1326":1,"1327":3,"1328":1,"1331":9,"1332":1,"1336":4,"1338":109,"1342":1,"1343":2,"1346":1,"1347":1,"1348":2,"1349":50,"1352":3,"1354":1,"1356":2,"1357":3,"1358":181,"1363":2,"1365":1,"1368":18,"1371":1,"1372":1,"1374":1,"1379":20,"1383":1,"1388":3,"1394":33,"1395":218,"1398":1,"1399":1,"1401":30,"1404":1,"1405":4,"1409":1,"1410":1,"1411":1,"1413":1,"1414":35,"1415":11,"1416":19,"1420":1,"1421":1,"1426":7,"1429":2,"1430":1,"1432":2,"1435":81,"1436":4,"1437":27,"1440":6,"1441":2,"1442":11,"1443":1,"1445":1,"1446":1,"1448":3,"1452":2,"1453":2,"1454":10,"1456":6,"1459":2,"1466":9,"1474":5,"1475":4,"1478":1,"1483":2,"1484":1,"1486":7,"1487":2,"1488":2,"1490":1,"1491":3,"1495":23,"1499":3,"1513":1,"1523":3,"1524":1,"1525":7,"1529":1,"1530":3,"1532":1,"1533":5,"1535":1,"1546":1,"1575":1,"1576":8,"1581":1,"1582":1,"1585":5,"1589":1,"1590":9,"1594":1,"1595":4,"1597":1,"1598":1,"1599":8,"1613":1,"1624":1,"1626":8,"1628":2,"1629":1,"1630":13,"1631":1,"1637":1,"1638":6,"1643":1,"1650":2,"1651":1,"1659":1,"1661":2,"1662":1,"1665":1,"1667":2,"1669":2,"1676":1,"1678":1,"1686":1,"1718":1,"1730":1,"1746":2,"1748":1,"1754":2,"1764":7,"1766":1,"1771":1,"1775":4,"1776":1,"1781":1,"1799":3,"1800":1,"1801":1,"1804":1,"1807":1,"1808":2,"1810":1,"1817":1,"1820":6,"1822":2,"1824":1,"1825":2,"1831":4,"1832":1,"1841":1,"1870":1,"1871":1,"1874":1,"1876":1,"1880":1,"1885":1,"1887":1,"1888":1,"1892":1,"1898":1,"1901":1,"1908":2,"1911":2,"1914":1,"1916":1,"1921":1,"1922":2,"1928":1,"1929":2,"1932":1,"1933":2,"1939":2,"1940":2,"1944":1,"1953":2,"1958":1,"1967":1,"1971":1,"1976":1,"1977":2,"1981":1,"1983":1,"1984":1,"1991":5,"1992":1,"1993":1,"1994":3,"1995":3,"1996":3,"2006":1,"2007":1,"2009":1,"2023":3,"2033":2,"2038":2,"2040":1,"2041":1,"2042":1,"2047":2,"2048":2,"2054":4,"2055":1,"2056":1,"2058":1,"2065":2,"2078":1,"2098":1,"2099":1,"2105":1,"2107":2,"2115":1,"2118":1,"2121":1,"2122":1,"2125":2,"2127":3,"2130":2,"2133":2,"2134":1,"2135":2,"2137":2,"2138":2,"2141":2,"2157":1,"2158":1,"2159":2,"2163":1,"2166":1,"2168":5,"2171":1,"2172":1,"2173":1,"2176":1,"2179":1,"2183":1,"2192":1,"2196":1,"2199":1,"2202":2,"2227":1,"2231":2}}],["chips",{"3":{"1323":2,"1384":1,"1394":4,"1495":1,"1741":1,"1749":1}}],["chip",{"3":{"0":1,"618":1,"905":1,"1323":2,"1384":1,"1394":18,"1401":1,"1435":3,"1525":1,"1741":1,"1749":2,"1772":1}}],["childitem",{"3":{"1727":1}}],["childid",{"3":{"1237":1,"1394":2}}],["childrules",{"3":{"1358":2}}],["childresult",{"3":{"1349":4}}],["children",{"3":{"0":2,"39":4,"341":1,"397":1,"402":1,"922":1,"926":1,"1168":1,"1212":1,"1231":1,"1237":6,"1247":2,"1270":1,"1280":1,"1285":2,"1309":4,"1323":1,"1332":2,"1338":5,"1341":2,"1342":4,"1345":1,"1349":41,"1351":1,"1358":38,"1368":1,"1379":1,"1381":1,"1384":3,"1386":1,"1394":20,"1397":1,"1400":1,"1401":5,"1435":25,"1442":2,"1488":1,"1555":1,"1574":1,"1629":2,"1631":5,"1634":2,"1637":8,"1638":8,"1639":3,"1646":1,"1650":2,"1663":1,"1665":1,"1676":1,"1684":3,"1719":1,"1747":2,"1748":2,"2009":2,"2060":1,"2063":2,"2156":1}}],["childforeignkeyselector",{"3":{"1309":3,"1358":13,"1401":2}}],["childfk",{"3":{"1309":1,"1358":1,"1864":1}}],["childless",{"3":{"1237":1,"1349":4,"1353":1}}],["childlessfixture",{"3":{"1199":2,"1207":1,"1435":5}}],["childd",{"3":{"1199":3,"1207":1}}],["childcollectiontypenames",{"3":{"1435":1}}],["childcollection",{"3":{"1247":1,"1309":3}}],["childc",{"3":{"1199":2,"1207":1}}],["childb",{"3":{"1199":2,"1207":1}}],["childa",{"3":{"1199":2,"1207":1}}],["childmodel",{"3":{"1199":3,"1207":1}}],["childentityhandlerbase",{"3":{"1207":2,"1265":6,"1270":6,"1358":43}}],["childentityhandlerbasetests",{"3":{"1199":1,"1207":1,"1270":2}}],["childentitydeletepathtests",{"3":{"1199":1,"1207":1}}],["childentitydeletepath",{"2":{"1385":1},"3":{"1199":6,"1203":1,"1207":1,"1323":1,"1385":1,"1394":12}}],["childentityservices",{"3":{"881":1,"1207":5,"1323":2,"1385":5,"1394":39}}],["childentityservicebasetests",{"3":{"1199":1,"1207":5,"1323":1}}],["childentityservicebase",{"2":{"879":1,"883":2,"1385":1},"3":{"0":2,"879":2,"881":5,"883":3,"1199":6,"1203":1,"1207":2,"1299":2,"1323":12,"1385":1,"1394":14,"1495":1}}],["childentity",{"3":{"341":1,"1199":4,"1207":1,"1285":1}}],["childnavigationdescriptortests",{"3":{"1199":1,"1207":4}}],["childnavigationdescriptor",{"2":{"11":1,"1864":1},"3":{"11":1,"1199":7,"1203":1,"1207":2,"1309":17,"1358":22,"1401":3,"1864":2}}],["child",{"0":{"1385":1},"3":{"0":3,"10":1,"11":1,"39":3,"43":2,"164":1,"339":1,"341":5,"344":1,"397":1,"599":1,"633":2,"782":1,"921":2,"926":3,"1115":1,"1116":1,"1117":1,"1140":1,"1167":1,"1168":8,"1170":2,"1191":1,"1212":1,"1231":10,"1232":2,"1237":32,"1243":4,"1244":1,"1247":9,"1259":2,"1260":1,"1265":9,"1270":21,"1271":2,"1278":1,"1285":17,"1309":23,"1310":6,"1322":1,"1323":5,"1338":8,"1339":1,"1341":2,"1342":5,"1344":3,"1345":1,"1346":1,"1349":127,"1352":2,"1353":4,"1358":268,"1368":6,"1370":2,"1373":2,"1379":24,"1385":1,"1392":1,"1394":49,"1395":8,"1400":1,"1401":26,"1415":1,"1435":41,"1437":1,"1464":2,"1466":1,"1495":1,"1525":1,"1576":3,"1585":1,"1590":1,"1629":10,"1630":10,"1631":10,"1632":1,"1633":1,"1634":5,"1636":2,"1637":10,"1638":5,"1639":3,"1649":1,"1650":1,"1660":1,"1661":1,"1665":2,"1667":1,"1682":1,"1683":2,"1685":7,"1699":1,"1700":2,"1703":1,"1718":1,"1726":14,"1727":4,"1728":5,"1729":3,"1730":2,"1763":1,"1809":3,"1862":1,"1864":1,"1870":3,"1875":4,"1876":1,"1987":2,"2009":1,"2153":1,"2156":2}}],["chunking",{"3":{"1319":1,"1322":2}}],["chunks",{"3":{"1306":1,"1308":1,"1322":1}}],["chunk",{"3":{"435":1,"1285":1,"1322":5,"1382":2,"1394":1,"1478":1}}],["chunked",{"3":{"0":1,"434":1,"1322":1,"1934":1}}],["churns",{"3":{"1435":1,"1437":1,"1747":1,"1988":1}}],["churning",{"3":{"1401":1,"1435":1}}],["churn",{"3":{"0":1,"87":1,"785":1,"798":1,"800":1,"891":1,"1237":1,"1259":1,"1285":1,"1323":1,"1337":1,"1338":3,"1401":1,"1435":3,"1491":1,"1495":1,"1676":1}}],["chaos",{"3":{"1565":1,"1576":1}}],["chasing",{"3":{"1439":1,"1445":1}}],["chased",{"3":{"1550":1}}],["chases",{"3":{"1435":1,"2152":1}}],["chase",{"3":{"1277":1,"1285":1,"1349":1,"1414":1,"1671":1,"1838":1}}],["chapter",{"0":{"1449":1,"1460":1,"1492":1},"3":{"1191":1,"1192":5,"1193":1,"1194":1,"1200":2,"1201":2,"1202":1,"1203":1,"1210":1,"1212":3,"1214":1,"1217":4,"1223":2,"1225":1,"1227":1,"1229":20,"1238":1,"1240":1,"1248":2,"1251":1,"1271":2,"1272":1,"1285":2,"1286":1,"1299":2,"1300":1,"1301":1,"1308":3,"1309":4,"1310":1,"1311":3,"1312":4,"1315":4,"1319":1,"1320":1,"1321":1,"1322":3,"1323":10,"1324":1,"1338":6,"1339":3,"1344":1,"1345":1,"1346":1,"1349":7,"1350":2,"1358":8,"1359":1,"1360":2,"1366":1,"1368":11,"1369":2,"1379":1,"1380":1,"1394":4,"1395":14,"1396":3,"1400":1,"1401":3,"1402":3,"1414":7,"1415":6,"1416":7,"1417":2,"1427":1,"1435":8,"1437":1,"1438":1,"1450":3,"1451":1,"1454":4,"1459":1,"1461":3,"1462":1,"1464":1,"1469":1,"1470":2,"1472":1,"1473":2,"1474":1,"1475":1,"1481":2,"1484":4,"1493":3,"1495":61,"1496":1,"1497":2,"1498":1,"1499":1,"1502":1,"1504":1,"1516":7,"1673":1,"1811":2,"1814":1,"1816":2,"1833":1,"1861":1,"1911":1,"1923":1,"1958":1,"2013":1,"2029":2,"2037":1,"2079":1,"2122":1,"2145":1,"2229":2}}],["chapters",{"0":{"1192":1},"3":{"329":1,"751":1,"1011":1,"1012":1,"1014":1,"1191":1,"1192":2,"1194":1,"1200":1,"1201":1,"1210":2,"1212":1,"1216":1,"1271":1,"1319":1,"1338":1,"1451":1,"1470":1,"1484":2,"1495":25,"1497":1,"1498":2,"1501":2,"1513":1,"1516":2,"1673":2}}],["chatrole",{"3":{"1426":2}}],["chatresponseupdate",{"3":{"1426":2}}],["chatresponseformat",{"2":{"1018":1,"1365":1},"3":{"1018":1,"1365":1}}],["chatresponse",{"2":{"1016":1,"1019":1,"1092":1},"3":{"1016":1,"1018":2,"1019":1,"1091":1,"1092":1,"1368":2,"1426":7,"1434":1,"1491":1}}],["chatclientbuilder",{"2":{"1420":1,"1426":1},"3":{"1420":1,"1426":3}}],["chatclient",{"3":{"1368":2}}],["chatclientmetadata",{"2":{"1091":1,"1424":1},"3":{"1091":1,"1424":1,"1426":3}}],["chatoptions",{"2":{"1418":1,"1423":1,"2173":1},"3":{"1368":4,"1418":1,"1423":1,"1426":17,"2173":1}}],["chatmessage",{"3":{"1368":2,"1426":4}}],["chatfinishreason",{"2":{"1365":1},"3":{"1365":1,"1368":2}}],["chatty",{"3":{"1401":1,"1543":1,"1567":1}}],["chattoolpolicy",{"2":{"1095":1},"3":{"1091":1,"1092":1,"1095":2,"1199":4,"1203":1,"1207":2,"1421":2,"1426":11,"1495":1}}],["chatter",{"3":{"18":1,"1259":2,"1338":2,"1401":1,"1442":1,"1455":1,"1489":1,"1676":1,"2009":1,"2158":1}}],["chatguardrailexception",{"2":{"1021":1,"1089":1,"1365":1,"2174":1},"3":{"1018":1,"1021":1,"1089":1,"1091":1,"1199":5,"1203":1,"1207":2,"1365":1,"1368":2,"1422":4,"1426":8,"1495":1,"2174":3}}],["chat",{"1":{"2169":1,"2170":1,"2171":1,"2172":1,"2173":1,"2174":1,"2175":1,"2176":1,"2177":1,"2178":1,"2179":1},"3":{"404":1,"406":1,"408":1,"1018":1,"1089":1,"1091":6,"1112":1,"1118":1,"1192":2,"1202":1,"1203":9,"1207":16,"1323":1,"1358":3,"1359":1,"1365":3,"1368":4,"1395":1,"1421":2,"1422":5,"1424":2,"1426":31,"1436":3,"1525":3,"1576":1,"1584":1,"1778":1,"2168":2,"2169":2,"2173":1,"2192":1,"2208":1}}],["challengeprovider",{"3":{"1310":1}}],["challenges",{"3":{"1310":1,"1486":1,"1897":1}}],["challengesecondfactorasync",{"2":{"1059":1,"2195":1},"3":{"1059":1,"1299":2,"2195":2}}],["challengeasync",{"2":{"2195":1},"3":{"1299":4,"1322":1,"2195":1}}],["challenged",{"3":{"1299":1}}],["challenge",{"3":{"0":1,"189":1,"350":1,"352":1,"353":1,"420":1,"1059":2,"1188":1,"1212":1,"1292":2,"1297":1,"1299":15,"1310":4,"1322":9,"1323":4,"1395":1,"1405":1,"1414":1,"1416":1,"1435":3,"1437":1,"1488":1,"1489":1,"1974":2,"1975":2,"1977":1,"2193":2,"2196":2,"2201":1}}],["chartable",{"3":{"2155":1}}],["charting",{"3":{"1270":1}}],["chart",{"3":{"1247":1,"1401":1,"1960":1,"1964":1,"2158":1}}],["charters",{"3":{"1504":1,"1516":1}}],["charter",{"3":{"1202":1,"1323":1,"1338":1,"1495":1}}],["charset",{"3":{"1346":2,"1358":4,"1364":1,"1368":1,"1383":2}}],["charset=utf",{"3":{"1310":2}}],["chars",{"3":{"690":1,"1302":1,"1349":1,"1358":3,"1629":1,"1639":1,"1763":9}}],["charges",{"3":{"1027":1,"1820":1}}],["charge",{"3":{"201":1,"1026":2,"1027":1,"1029":1,"1243":1,"1247":1,"1263":1,"1416":1,"1459":1,"1763":1,"1906":1,"2079":1}}],["charged",{"3":{"0":1,"122":1,"1024":1,"1028":2,"1270":1,"1748":2,"1749":1,"1763":1,"1766":1,"1775":1,"1905":1}}],["characters",{"3":{"231":1,"397":1,"409":1,"674":1,"676":1,"698":1,"827":2,"1018":1,"1026":2,"1091":1,"1234":1,"1237":3,"1247":1,"1259":1,"1271":6,"1276":1,"1285":4,"1287":1,"1289":1,"1299":5,"1308":1,"1310":1,"1319":1,"1323":9,"1336":1,"1338":1,"1343":1,"1346":1,"1349":2,"1358":33,"1368":2,"1394":1,"1395":2,"1401":6,"1414":1,"1416":1,"1421":1,"1426":2,"1435":2,"1629":3,"1630":3,"1631":2,"1640":1,"1684":2,"1685":2,"1763":1,"1764":1,"1766":11,"1828":1,"1932":1,"1933":1,"1981":1,"2011":1,"2173":2,"2178":1}}],["character",{"3":{"26":1,"373":1,"427":1,"657":1,"698":1,"710":1,"741":1,"832":1,"905":1,"931":2,"972":1,"1018":1,"1228":1,"1229":1,"1232":1,"1237":2,"1241":2,"1270":1,"1271":2,"1281":1,"1285":1,"1299":4,"1308":2,"1310":2,"1322":1,"1323":10,"1338":4,"1343":2,"1346":1,"1349":5,"1358":13,"1361":3,"1394":7,"1395":4,"1414":2,"1416":7,"1425":1,"1426":1,"1432":2,"1435":3,"1640":1,"1828":1,"1841":2,"1850":1,"2126":1,"2149":1}}],["char",{"3":{"0":1,"905":1,"1212":1,"1247":1,"1285":1,"1299":1,"1323":6,"1349":4,"1487":1}}],["chain+cost",{"3":{"1525":1}}],["chainable",{"3":{"1271":1,"1323":1}}],["chaining",{"3":{"0":1,"749":1,"846":1,"1247":1,"1259":1,"1285":4,"1299":2,"1308":5,"1310":4,"1311":2,"1322":1,"1323":2,"1349":2,"1358":2,"1368":1,"1379":2,"1394":2,"1395":5,"1414":4,"1416":4,"1426":2,"1435":4,"1890":1}}],["chained",{"3":{"0":3,"258":1,"261":1,"827":2,"828":1,"831":1,"832":1,"905":1,"1124":1,"1128":1,"1212":1,"1299":2,"1323":3,"1336":1,"1338":3,"1358":2,"1368":1,"1435":2,"1437":1,"1466":1,"1487":1,"1488":1,"1669":1,"1683":1,"2023":1}}],["chains",{"1":{"1862":1,"1863":1,"1864":1,"1865":1,"1866":1,"1867":1},"3":{"0":2,"117":1,"121":2,"125":1,"195":1,"448":1,"672":4,"674":2,"752":1,"826":1,"827":1,"846":1,"872":1,"1212":1,"1224":1,"1247":2,"1260":1,"1270":5,"1271":1,"1285":4,"1299":5,"1310":5,"1322":1,"1323":3,"1338":3,"1358":16,"1376":1,"1379":1,"1395":2,"1401":2,"1415":1,"1418":1,"1435":2,"1440":1,"1442":1,"1443":1,"1444":1,"1542":1,"1544":1,"1785":1,"1820":1,"1828":2,"1832":1,"1861":1,"1862":1,"1863":1,"1867":1,"1888":1,"1890":1,"1891":1,"2007":1,"2021":1,"2206":1}}],["chain",{"0":{"1231":1,"1232":1,"1568":1,"1672":1,"1737":1},"1":{"367":1,"368":1,"369":1,"370":1,"371":1,"372":1,"373":1,"374":1,"375":1,"376":1},"3":{"0":10,"99":1,"107":1,"109":2,"110":2,"111":1,"115":4,"121":3,"122":2,"126":2,"127":1,"128":1,"259":1,"350":1,"367":1,"369":3,"370":1,"371":1,"373":2,"376":2,"388":1,"389":1,"395":1,"402":1,"404":2,"407":1,"408":1,"409":1,"499":1,"500":2,"501":2,"529":1,"532":1,"549":1,"574":1,"590":1,"591":1,"592":1,"595":1,"625":1,"626":1,"642":1,"665":1,"672":4,"674":6,"675":4,"676":7,"677":2,"692":2,"698":2,"701":1,"707":1,"711":1,"715":1,"721":1,"748":1,"749":2,"751":1,"756":1,"765":1,"819":1,"821":1,"823":1,"826":1,"852":1,"854":2,"870":1,"874":1,"905":1,"906":1,"908":1,"915":1,"923":1,"925":1,"964":1,"988":1,"1046":1,"1058":2,"1075":1,"1212":3,"1214":1,"1216":1,"1220":2,"1229":7,"1230":2,"1231":1,"1232":1,"1235":1,"1237":3,"1240":1,"1241":1,"1247":17,"1259":1,"1260":1,"1262":2,"1269":2,"1270":17,"1271":14,"1278":2,"1282":1,"1283":1,"1285":23,"1289":2,"1299":5,"1300":1,"1308":1,"1309":1,"1310":25,"1321":1,"1322":10,"1323":9,"1325":1,"1326":1,"1328":1,"1332":2,"1338":15,"1349":9,"1352":1,"1358":28,"1368":4,"1379":1,"1394":3,"1395":3,"1401":5,"1402":1,"1406":1,"1407":1,"1414":8,"1415":5,"1416":7,"1420":1,"1426":3,"1430":2,"1435":54,"1437":5,"1440":1,"1442":2,"1444":2,"1447":1,"1449":1,"1452":2,"1453":3,"1454":4,"1455":1,"1458":7,"1459":1,"1460":3,"1462":1,"1464":1,"1467":2,"1474":1,"1482":1,"1485":3,"1486":1,"1488":2,"1490":1,"1491":2,"1492":1,"1495":1,"1498":1,"1502":1,"1506":1,"1523":1,"1525":12,"1529":1,"1530":1,"1532":1,"1533":1,"1534":1,"1575":1,"1576":2,"1585":1,"1590":9,"1598":1,"1599":1,"1650":1,"1666":1,"1672":1,"1752":1,"1795":1,"1798":1,"1804":1,"1805":1,"1809":1,"1814":1,"1820":2,"1824":4,"1825":2,"1826":1,"1827":2,"1828":1,"1833":1,"1839":1,"1855":1,"1863":2,"1867":1,"1921":1,"1926":1,"1929":1,"1976":1,"1982":1,"1983":1,"2007":1,"2009":1,"2011":4,"2012":2,"2023":1,"2073":1,"2089":1,"2138":1,"2155":1,"2156":2,"2183":1,"2189":2,"2192":1,"2202":2,"2226":1,"2231":3}}],["chances",{"3":{"882":1,"938":1,"1358":1}}],["chance",{"3":{"234":1,"235":1,"411":1,"413":1,"673":1,"750":1,"1090":1,"1265":1,"1270":1,"1285":2,"1311":1,"1338":1,"1358":1,"1379":1,"1395":1,"1412":1,"1414":2,"1415":2,"1416":6,"1668":1,"2169":1}}],["channelreader",{"3":{"1395":2}}],["channelreferencecountertests",{"3":{"1199":1,"1207":1,"1323":1}}],["channelreferencecounter",{"3":{"1199":3,"1203":1,"1207":2,"1323":9,"1495":1}}],["channeljoinrefused",{"3":{"1308":1}}],["channelsync",{"3":{"1323":2}}],["channelsubscriptions",{"3":{"1323":2}}],["channelsubscription",{"3":{"1198":1,"1199":2,"1203":1,"1207":1,"1323":7,"1495":2}}],["channels",{"0":{"1511":1},"1":{"1941":1,"1942":1,"1943":1,"1944":1,"1945":1,"1946":1,"1947":1,"1948":1,"1949":1,"2180":1,"2181":1,"2182":1,"2183":1,"2184":1,"2185":1,"2186":1,"2187":1,"2188":1,"2189":1,"2190":1,"2191":1,"2192":1},"2":{"1399":1},"3":{"109":1,"110":1,"111":1,"225":3,"384":1,"433":1,"434":1,"1285":2,"1299":1,"1301":1,"1303":1,"1308":6,"1310":1,"1319":1,"1322":1,"1323":3,"1395":4,"1399":1,"1416":3,"1435":1,"1495":1,"1555":1,"1668":1,"1676":1,"1778":2,"1805":1,"1941":2,"1946":2,"1949":1,"2164":1,"2179":2,"2180":1,"2202":1,"2205":1,"2207":1}}],["channelkeyregexcache",{"3":{"1308":1}}],["channelkeymatchtimeout",{"3":{"1308":2}}],["channelkey",{"3":{"0":1,"380":1,"1285":4,"1308":17,"1323":8,"1395":10,"1401":3,"1943":1}}],["channelkeypattern",{"2":{"0":1,"380":1,"1304":1,"1308":1,"1401":1,"1944":1},"3":{"0":1,"380":1,"1304":1,"1306":1,"1308":2,"1322":2,"1401":1,"1944":1}}],["channel",{"0":{"1304":1,"1934":1,"1935":1,"1937":1,"1944":1},"1":{"222":1,"223":1,"224":1,"225":1,"226":1,"227":1,"228":1,"229":1,"230":1,"231":1,"377":1,"378":1,"379":1,"380":1,"381":1,"382":1,"383":1,"384":1,"431":1,"432":1,"433":1,"434":1,"435":1,"436":1,"1097":1,"1098":1,"1099":1,"1100":1,"1101":1,"1102":1,"1103":1,"1104":1},"2":{"1100":1,"1946":1,"2182":1},"3":{"0":18,"25":2,"27":1,"92":1,"95":1,"105":1,"110":1,"222":1,"223":2,"225":5,"226":1,"228":4,"229":1,"230":3,"255":1,"265":2,"273":1,"343":2,"377":1,"378":2,"380":3,"381":2,"382":2,"383":1,"384":6,"431":1,"434":1,"517":2,"518":2,"519":1,"521":1,"522":1,"608":1,"610":1,"640":1,"856":1,"857":1,"882":1,"896":1,"897":3,"914":2,"915":1,"916":1,"926":1,"930":1,"1066":1,"1097":1,"1099":2,"1100":5,"1101":2,"1102":1,"1104":1,"1112":1,"1150":1,"1157":1,"1192":6,"1202":2,"1203":2,"1212":5,"1226":1,"1229":1,"1259":1,"1260":1,"1265":1,"1270":6,"1284":1,"1285":9,"1291":1,"1299":3,"1301":3,"1306":5,"1307":2,"1308":40,"1311":1,"1319":6,"1322":21,"1323":40,"1327":1,"1338":1,"1358":8,"1379":7,"1394":1,"1395":52,"1396":2,"1398":8,"1399":4,"1400":3,"1401":84,"1414":6,"1416":11,"1428":1,"1435":7,"1437":3,"1440":1,"1444":1,"1447":1,"1459":1,"1465":1,"1466":2,"1487":1,"1489":1,"1495":5,"1511":1,"1598":2,"1643":1,"1676":1,"1732":1,"1901":1,"1921":1,"1934":2,"1935":2,"1936":1,"1937":2,"1939":1,"1940":4,"1942":1,"1943":5,"1944":6,"1946":5,"1947":1,"1948":2,"1949":4,"2024":1,"2078":1,"2082":2,"2157":1,"2182":5,"2189":3,"2191":1,"2192":2,"2231":6}}],["changequantity",{"3":{"1685":1}}],["changeorderitemquantityrequest",{"3":{"1685":1}}],["changeorderstatusrequest",{"2":{"1685":1,"1701":1},"3":{"1685":2,"1701":1}}],["changeoption",{"3":{"1401":4}}],["changeitemquantityasync",{"3":{"1685":1}}],["changeitemquantityhandler",{"3":{"1685":1}}],["changeitemquantitycommand",{"2":{"1685":1},"3":{"1685":2}}],["changeitemquantity",{"2":{"1683":1},"3":{"1683":1,"1685":4}}],["changename",{"2":{"1746":1},"3":{"1435":1,"1746":1,"1768":1}}],["changerole",{"3":{"1408":1,"1414":2}}],["changeroleasync",{"3":{"1323":1,"1414":1}}],["changeable",{"3":{"1358":1}}],["changeaddress",{"2":{"1746":1},"3":{"1271":2,"1435":1,"1746":1,"1768":1}}],["changetracking",{"3":{"1285":3}}],["changetracker",{"2":{"1231":1,"1233":1,"1278":1,"1839":1},"3":{"715":1,"988":1,"1231":1,"1233":1,"1237":2,"1273":1,"1278":1,"1285":10,"1839":1}}],["changevariantskuhandler",{"3":{"1285":1}}],["changevariantskucommand",{"3":{"1270":1}}],["changevariantprice",{"3":{"1285":1}}],["changevariantpricehandler",{"3":{"1285":2}}],["changevariantpricecommand",{"3":{"1270":1}}],["changeemail",{"2":{"1746":1},"3":{"1237":1,"1435":1,"1746":1,"1768":1}}],["changepreferencesasync",{"3":{"1310":1}}],["changepreferences",{"3":{"1203":4,"1207":10,"1290":2,"1299":4,"1321":1,"1322":10,"1414":9,"1435":1,"1495":2}}],["changepreferenceshandlertests",{"3":{"1199":1,"1207":2}}],["changepreferenceshandler",{"3":{"1199":4,"1203":1,"1207":2,"1322":3,"1405":2,"1414":9}}],["changepreferenceshandlerbasetests",{"3":{"1199":1,"1207":4,"1322":1}}],["changepreferenceshandlerbase",{"2":{"1290":1,"1321":1},"3":{"1199":3,"1203":1,"1207":2,"1290":3,"1299":6,"1321":2,"1322":10,"1414":6}}],["changepreferencesrequest",{"2":{"1290":1},"3":{"1199":12,"1203":1,"1207":2,"1290":2,"1299":6,"1310":1,"1322":3,"1414":4,"1495":1}}],["changepreferencescommandvalidator",{"3":{"1199":1,"1203":1,"1207":2,"1270":1,"1414":5}}],["changepreferencescommand",{"3":{"572":1,"1199":8,"1203":1,"1207":2,"1299":2,"1322":6,"1405":3,"1414":11,"1435":3,"1488":1}}],["changeprice",{"2":{"1028":1},"3":{"1028":1}}],["changepasswordfailed",{"3":{"1525":1}}],["changepasswordbutton",{"3":{"1435":1}}],["changepasswordasync",{"3":{"1310":1,"1323":3,"1435":1}}],["changepasswordcommand",{"3":{"1199":5,"1203":1,"1207":2,"1270":2,"1299":2,"1310":2,"1322":6,"1405":2,"1414":12}}],["changepasswordrequestvalidatortests",{"3":{"1199":1,"1207":2}}],["changepasswordrequestvalidator",{"2":{"1403":1},"3":{"1199":2,"1203":1,"1207":2,"1271":2,"1299":2,"1403":1,"1414":9,"1590":1}}],["changepasswordrequest",{"2":{"1288":1},"3":{"1199":15,"1203":1,"1207":2,"1288":1,"1299":4,"1310":1,"1322":1,"1323":2,"1414":5,"1590":1}}],["changepassword",{"2":{"314":1,"1290":1,"1499":1},"3":{"285":1,"310":3,"314":2,"353":1,"908":1,"926":1,"946":1,"1059":1,"1062":1,"1203":3,"1207":8,"1290":2,"1293":1,"1299":8,"1310":1,"1321":1,"1322":14,"1404":1,"1414":18,"1435":2,"1495":1,"1499":1,"1590":1,"1768":1}}],["changepasswordhandlertests",{"3":{"1199":1,"1207":2,"1435":1}}],["changepasswordhandler",{"2":{"310":1,"314":1},"3":{"281":1,"310":3,"314":4,"926":1,"1199":3,"1203":1,"1207":2,"1299":2,"1322":3,"1405":2,"1414":15}}],["changepasswordhandlerbasetests",{"3":{"1199":1,"1207":4,"1322":1,"1437":2}}],["changepasswordhandlerbase",{"2":{"281":1,"285":1,"310":1,"314":1,"353":1,"908":1,"946":1,"949":1,"1062":1,"1290":1,"1402":1},"3":{"281":2,"285":2,"310":4,"314":3,"353":2,"908":3,"909":1,"946":2,"949":1,"1059":2,"1062":2,"1199":3,"1203":1,"1207":2,"1290":2,"1299":8,"1321":4,"1322":12,"1402":1,"1414":11,"1495":1,"1576":1}}],["changelog",{"2":{"1673":1},"3":{"27":1,"135":2,"137":2,"138":2,"139":2,"140":2,"142":2,"286":1,"326":1,"365":2,"366":2,"449":1,"497":2,"502":1,"657":1,"660":1,"698":1,"825":1,"832":1,"857":1,"990":1,"991":2,"1003":2,"1004":3,"1007":2,"1091":1,"1095":3,"1119":1,"1124":2,"1127":1,"1128":2,"1132":2,"1136":2,"1160":2,"1173":1,"1576":7,"1584":2,"1585":2,"1672":3,"1673":1,"1674":1,"1733":1,"1734":1,"1736":1,"2064":1,"2130":1,"2231":1}}],["changedby",{"3":{"715":1,"1285":4,"1299":2,"1322":1}}],["changedon",{"2":{"715":1,"720":1,"1273":1},"3":{"715":3,"720":1,"1270":1,"1273":1,"1285":10,"1322":1}}],["changed",{"0":{"1277":1},"2":{"781":1,"923":1},"3":{"0":4,"1":1,"24":1,"25":1,"26":2,"27":1,"34":1,"43":1,"51":1,"82":1,"103":1,"104":1,"112":1,"115":1,"120":1,"123":1,"125":1,"126":1,"127":1,"130":1,"135":1,"136":1,"138":1,"139":4,"140":1,"141":1,"142":2,"149":1,"152":1,"164":1,"171":1,"178":1,"181":1,"189":1,"190":1,"200":1,"202":1,"203":1,"220":1,"238":1,"249":1,"250":1,"251":1,"252":1,"254":1,"256":1,"257":1,"258":2,"259":3,"260":1,"261":1,"265":1,"268":1,"276":1,"284":1,"296":1,"303":1,"314":1,"322":1,"323":1,"324":1,"336":1,"337":1,"339":1,"345":1,"354":1,"363":1,"366":1,"375":1,"393":1,"407":1,"416":1,"423":1,"424":1,"425":2,"426":1,"428":1,"429":1,"430":1,"451":1,"453":1,"454":2,"463":1,"465":1,"474":2,"475":1,"476":1,"477":1,"478":1,"495":1,"502":1,"514":1,"522":2,"523":1,"524":1,"531":1,"538":1,"545":1,"549":1,"550":1,"551":2,"552":1,"559":1,"564":1,"567":1,"573":1,"577":1,"578":1,"594":1,"602":1,"604":1,"607":7,"613":1,"616":4,"617":1,"621":1,"626":1,"629":1,"636":1,"652":1,"660":1,"669":1,"677":1,"685":1,"688":1,"691":1,"693":2,"710":1,"714":1,"715":2,"716":1,"717":3,"720":1,"728":1,"744":1,"752":1,"777":1,"781":1,"782":1,"783":1,"797":1,"800":1,"803":1,"804":1,"805":1,"806":1,"813":1,"814":1,"822":1,"832":1,"833":2,"836":1,"838":1,"840":2,"841":1,"849":1,"850":1,"857":1,"864":1,"877":1,"881":1,"891":2,"892":1,"900":1,"908":1,"909":1,"917":1,"921":1,"923":1,"927":1,"933":1,"934":1,"949":1,"960":1,"967":1,"980":1,"983":1,"1007":1,"1016":1,"1019":1,"1028":1,"1042":1,"1052":1,"1054":1,"1058":1,"1070":1,"1078":1,"1086":1,"1093":1,"1095":1,"1103":1,"1111":1,"1118":1,"1119":1,"1128":1,"1136":1,"1142":1,"1146":1,"1156":1,"1180":1,"1232":1,"1237":5,"1242":1,"1247":1,"1257":1,"1259":1,"1265":1,"1270":4,"1271":1,"1277":1,"1279":1,"1280":1,"1285":8,"1299":1,"1300":2,"1308":2,"1310":2,"1322":17,"1323":9,"1338":5,"1342":1,"1349":9,"1358":2,"1368":1,"1379":3,"1394":10,"1395":9,"1398":1,"1400":3,"1401":18,"1404":1,"1406":1,"1408":1,"1414":5,"1435":7,"1437":2,"1444":1,"1446":1,"1452":3,"1454":3,"1466":3,"1473":1,"1475":1,"1484":1,"1491":2,"1495":32,"1499":1,"1525":1,"1530":1,"1546":1,"1553":1,"1630":4,"1631":1,"1633":2,"1634":1,"1638":6,"1640":3,"1655":1,"1671":1,"1685":1,"1703":1,"1726":1,"1733":1,"1734":1,"1747":2,"1766":1,"1768":2,"1775":1,"1838":3,"1849":1,"1870":1,"1875":1,"1876":1,"1886":1,"1889":1,"1910":1,"1911":1,"1917":1,"2026":1,"2106":1,"2149":1,"2166":1,"2191":1,"2196":1,"2199":1,"2231":1}}],["changeshoppingcartitemquantity",{"2":{"1768":1},"3":{"1768":1}}],["changestatusasync",{"3":{"1701":2}}],["changestatus",{"2":{"1700":1},"3":{"1685":3,"1700":1,"1701":1,"1726":1}}],["changes+supply",{"3":{"1525":1}}],["changes",{"0":{"1734":1,"1768":1},"2":{"562":1,"564":1,"593":1,"604":1,"624":1,"626":1,"756":1,"1454":1,"1491":1,"1595":1},"3":{"0":4,"16":1,"17":1,"18":1,"20":1,"24":3,"71":1,"73":1,"74":1,"78":2,"79":1,"95":1,"136":1,"145":2,"147":3,"150":1,"160":1,"193":1,"201":1,"217":1,"219":1,"245":1,"246":2,"291":1,"305":1,"311":1,"312":1,"351":1,"353":1,"360":1,"390":2,"392":1,"413":1,"422":1,"434":1,"441":1,"499":1,"536":1,"539":1,"549":1,"562":1,"563":1,"564":4,"568":1,"572":1,"576":1,"593":1,"599":1,"600":1,"602":1,"604":2,"624":1,"626":3,"629":1,"698":1,"699":1,"717":1,"723":1,"726":1,"733":1,"740":1,"741":1,"750":1,"756":1,"767":1,"791":1,"793":1,"797":1,"799":1,"819":1,"825":6,"827":2,"830":1,"833":1,"838":1,"840":1,"844":1,"854":1,"903":1,"905":1,"907":1,"920":1,"921":1,"930":1,"954":2,"982":1,"1006":1,"1012":2,"1016":1,"1018":2,"1020":1,"1021":1,"1030":1,"1034":2,"1048":1,"1077":1,"1095":1,"1116":1,"1145":1,"1184":1,"1212":4,"1213":1,"1231":2,"1233":1,"1237":7,"1247":2,"1249":1,"1250":1,"1257":1,"1259":9,"1270":2,"1273":2,"1274":1,"1277":1,"1278":1,"1280":1,"1285":23,"1299":7,"1300":2,"1308":8,"1309":1,"1310":11,"1311":1,"1317":1,"1321":1,"1322":7,"1323":22,"1338":4,"1344":1,"1349":8,"1358":15,"1368":1,"1372":1,"1378":1,"1379":4,"1384":1,"1388":1,"1394":15,"1395":17,"1396":1,"1397":1,"1401":6,"1404":1,"1412":1,"1413":1,"1414":14,"1415":3,"1416":6,"1426":5,"1431":1,"1435":20,"1437":5,"1439":1,"1441":2,"1442":2,"1444":1,"1445":1,"1446":2,"1452":2,"1454":11,"1457":2,"1460":1,"1466":7,"1475":1,"1476":1,"1477":1,"1482":1,"1485":1,"1487":2,"1491":1,"1495":5,"1525":2,"1533":1,"1540":1,"1545":2,"1546":1,"1547":1,"1552":1,"1555":1,"1560":1,"1568":1,"1571":1,"1585":1,"1590":2,"1595":2,"1604":1,"1606":1,"1607":1,"1613":1,"1630":6,"1631":2,"1633":2,"1634":1,"1637":1,"1638":2,"1639":1,"1650":1,"1662":1,"1668":1,"1672":1,"1674":1,"1684":6,"1685":7,"1700":1,"1703":1,"1726":2,"1732":2,"1733":1,"1734":1,"1738":1,"1740":1,"1746":3,"1747":1,"1748":3,"1764":5,"1768":2,"1775":1,"1841":1,"1850":2,"1852":1,"1862":2,"1865":1,"1873":1,"1883":1,"1888":3,"1891":1,"1895":1,"1898":1,"1912":1,"1915":1,"1919":1,"1960":2,"1964":1,"1973":1,"1979":2,"2012":2,"2016":1,"2044":1,"2056":1,"2062":1,"2072":1,"2077":1,"2084":1,"2086":1,"2089":1,"2090":1,"2111":1,"2117":1,"2125":1,"2126":1,"2143":1,"2149":1,"2159":1,"2186":2,"2197":1,"2219":2,"2229":2,"2231":2}}],["change",{"0":{"1695":1,"1838":1,"2111":1},"1":{"712":1,"713":1,"714":1,"715":1,"716":1,"717":1,"718":1,"719":1,"720":1,"721":1,"1731":1,"1732":1,"1733":1,"1734":1,"1735":1,"1736":1,"1737":1},"2":{"1466":1},"3":{"0":28,"21":2,"26":1,"27":4,"30":1,"32":1,"41":1,"46":1,"54":1,"58":1,"59":2,"60":4,"63":1,"70":1,"71":1,"78":1,"79":1,"81":2,"94":1,"99":1,"111":1,"118":1,"119":1,"121":1,"131":1,"133":1,"134":1,"135":6,"136":3,"141":1,"142":1,"143":1,"147":3,"149":2,"150":1,"151":3,"158":1,"160":1,"164":1,"166":1,"167":1,"170":1,"186":2,"187":2,"200":1,"224":1,"241":1,"243":2,"253":1,"254":1,"255":2,"265":3,"273":1,"279":1,"285":1,"301":2,"303":2,"310":1,"311":1,"312":1,"314":1,"330":1,"332":2,"333":1,"340":1,"348":1,"353":2,"359":1,"364":1,"365":5,"370":1,"390":2,"391":3,"393":1,"397":2,"414":2,"422":1,"425":1,"429":1,"433":1,"447":1,"449":1,"451":1,"454":1,"465":1,"472":1,"474":1,"486":1,"490":1,"497":3,"499":1,"509":1,"526":1,"528":2,"547":1,"549":1,"550":1,"551":1,"563":1,"564":2,"566":1,"568":1,"583":1,"584":2,"591":2,"592":1,"609":1,"610":3,"618":1,"619":1,"620":2,"624":1,"625":1,"628":1,"629":1,"640":2,"641":1,"644":1,"650":1,"651":1,"657":1,"659":1,"665":1,"668":1,"691":1,"692":1,"703":1,"706":1,"712":1,"714":2,"715":4,"716":2,"719":1,"726":1,"727":1,"731":1,"733":1,"740":1,"742":1,"747":1,"749":1,"750":1,"751":2,"760":1,"765":1,"766":1,"767":1,"776":2,"784":1,"793":1,"799":3,"800":2,"803":2,"804":1,"818":1,"819":1,"825":1,"829":3,"832":1,"833":1,"836":1,"840":1,"845":1,"853":1,"854":2,"856":1,"857":1,"860":2,"861":1,"862":1,"863":1,"869":1,"870":1,"881":1,"883":3,"891":1,"903":1,"905":2,"907":1,"908":1,"916":1,"921":1,"924":1,"926":2,"931":2,"932":1,"939":1,"942":1,"946":1,"947":1,"948":1,"955":1,"958":1,"964":2,"965":1,"966":1,"972":1,"980":1,"981":2,"982":1,"986":1,"987":1,"988":4,"989":1,"990":1,"991":1,"1003":1,"1004":2,"1008":1,"1011":1,"1012":1,"1014":1,"1016":1,"1017":1,"1018":8,"1019":4,"1020":2,"1027":1,"1034":2,"1035":1,"1041":1,"1042":3,"1043":3,"1044":1,"1049":1,"1050":2,"1051":2,"1052":1,"1058":1,"1059":4,"1060":1,"1061":2,"1077":1,"1082":1,"1084":2,"1085":2,"1086":1,"1091":2,"1094":2,"1096":1,"1119":1,"1126":2,"1129":1,"1142":2,"1147":1,"1169":1,"1175":1,"1188":1,"1196":1,"1202":1,"1203":1,"1212":7,"1213":1,"1214":1,"1229":6,"1230":2,"1232":1,"1233":1,"1236":1,"1237":24,"1241":1,"1246":1,"1247":10,"1249":2,"1251":2,"1252":1,"1255":1,"1259":14,"1260":1,"1261":1,"1264":1,"1267":1,"1269":2,"1270":17,"1271":5,"1272":2,"1273":2,"1274":1,"1275":1,"1277":3,"1278":2,"1280":1,"1281":2,"1282":1,"1285":72,"1288":2,"1292":1,"1293":2,"1295":1,"1299":32,"1300":4,"1303":1,"1305":2,"1308":15,"1309":1,"1310":29,"1311":2,"1317":2,"1320":1,"1321":1,"1322":34,"1323":38,"1325":1,"1332":1,"1337":1,"1338":21,"1339":2,"1341":1,"1342":1,"1344":3,"1346":1,"1347":1,"1349":41,"1351":2,"1354":1,"1357":2,"1358":92,"1360":1,"1361":1,"1365":1,"1368":10,"1369":1,"1370":1,"1377":1,"1379":10,"1388":1,"1393":1,"1394":37,"1395":39,"1401":25,"1402":3,"1404":2,"1405":1,"1406":2,"1407":1,"1409":1,"1413":3,"1414":55,"1415":3,"1416":20,"1420":1,"1425":1,"1426":6,"1431":1,"1434":5,"1435":65,"1437":11,"1440":2,"1441":1,"1444":2,"1452":4,"1454":19,"1458":1,"1460":2,"1462":1,"1464":1,"1466":16,"1473":5,"1474":1,"1475":5,"1476":1,"1477":3,"1480":1,"1487":4,"1491":8,"1495":43,"1520":1,"1523":2,"1525":1,"1530":4,"1533":1,"1534":1,"1537":1,"1541":1,"1544":1,"1545":2,"1546":1,"1552":5,"1568":1,"1569":2,"1570":3,"1576":6,"1584":2,"1590":1,"1591":1,"1595":1,"1598":3,"1601":1,"1607":2,"1615":1,"1625":1,"1629":2,"1630":1,"1631":3,"1632":1,"1633":2,"1637":2,"1638":4,"1640":10,"1642":1,"1643":1,"1645":1,"1650":2,"1652":1,"1661":1,"1663":2,"1666":2,"1667":1,"1672":1,"1673":1,"1674":1,"1676":1,"1691":1,"1695":1,"1696":1,"1700":1,"1702":1,"1707":1,"1709":1,"1718":4,"1723":3,"1726":1,"1731":1,"1733":1,"1734":3,"1735":3,"1736":2,"1741":2,"1745":10,"1746":6,"1747":6,"1748":1,"1749":1,"1759":1,"1764":2,"1766":2,"1767":6,"1774":1,"1775":3,"1787":1,"1789":4,"1792":1,"1795":1,"1804":1,"1806":1,"1808":1,"1809":2,"1811":1,"1820":1,"1825":1,"1838":4,"1839":1,"1843":2,"1845":1,"1846":1,"1848":3,"1850":1,"1851":1,"1852":2,"1853":2,"1854":1,"1856":6,"1860":1,"1868":2,"1888":3,"1889":1,"1891":3,"1895":1,"1898":1,"1901":1,"1912":1,"1915":1,"1917":1,"1922":1,"1925":1,"1928":2,"1929":1,"1931":2,"1935":1,"1937":1,"1938":1,"1940":1,"1964":2,"1981":1,"1988":1,"1989":2,"1990":1,"1994":2,"2007":2,"2009":2,"2012":1,"2014":1,"2016":1,"2019":2,"2027":1,"2033":1,"2043":3,"2044":1,"2046":1,"2050":1,"2062":1,"2064":2,"2067":1,"2077":1,"2078":1,"2082":1,"2086":2,"2088":2,"2097":1,"2099":1,"2105":1,"2106":3,"2110":1,"2111":2,"2114":1,"2116":1,"2122":1,"2129":1,"2130":1,"2135":2,"2141":1,"2142":2,"2145":1,"2149":1,"2153":1,"2155":1,"2159":3,"2174":1,"2182":2,"2187":1,"2191":4,"2192":1,"2193":1,"2194":1,"2198":2,"2200":1,"2201":1,"2202":1,"2229":1,"2231":2}}],["changing",{"0":{"1888":1},"3":{"0":4,"40":1,"136":1,"160":1,"194":1,"208":1,"311":1,"365":1,"448":1,"476":1,"478":1,"497":1,"547":1,"692":1,"701":1,"732":1,"735":1,"741":1,"768":1,"819":1,"829":1,"905":1,"907":2,"948":1,"980":1,"1014":1,"1018":1,"1020":1,"1059":1,"1071":1,"1092":1,"1095":1,"1173":1,"1182":1,"1229":3,"1237":1,"1285":1,"1289":1,"1299":5,"1308":2,"1310":6,"1311":1,"1322":1,"1323":3,"1338":1,"1344":1,"1349":1,"1358":6,"1394":5,"1395":2,"1401":2,"1414":3,"1416":1,"1419":1,"1426":1,"1435":4,"1473":1,"1630":1,"1638":4,"1682":1,"1707":1,"1733":3,"1766":1,"1801":1,"1804":1,"1853":1,"1855":1,"1874":2,"1888":1,"1901":1,"1922":1,"1960":1,"1964":1,"1971":1,"2025":1,"2026":1,"2031":1,"2043":1,"2064":1,"2193":1,"2198":1,"2201":1}}],["chore",{"3":{"832":1,"1237":1,"1435":1,"1950":1}}],["choreography",{"3":{"537":1,"541":1,"809":4,"811":1,"815":1,"2167":1}}],["choreographed",{"1":{"533":1,"534":1,"535":1,"536":1,"537":1,"538":1,"539":1,"540":1,"541":1},"3":{"533":1,"536":1,"815":1,"1147":1,"1576":1,"2162":1,"2168":1}}],["chokepoint",{"3":{"1285":1,"1358":2,"2073":1}}],["choke",{"3":{"826":1,"829":1}}],["choosable",{"3":{"422":1}}],["choosing",{"3":{"70":1,"248":1,"295":1,"427":1,"520":1,"855":1,"896":1,"926":1,"964":1,"998":1,"1051":1,"1074":1,"1102":1,"1259":1,"1270":3,"1285":3,"1300":2,"1310":2,"1320":1,"1322":2,"1323":2,"1358":4,"1368":1,"1379":2,"1394":2,"1395":2,"1414":1,"1435":1,"1851":1,"1984":1,"2095":1,"2191":1}}],["chooses",{"3":{"234":1,"305":1,"1184":1,"1247":1,"1270":1,"1299":2,"1308":1,"1310":1,"1320":1,"1322":1,"1323":1,"1394":2,"1395":4,"1401":1,"1414":1,"1416":1,"1426":1,"1435":2,"1902":1,"2062":1}}],["choose",{"3":{"0":2,"157":1,"226":1,"481":1,"557":1,"640":1,"966":1,"996":1,"999":1,"1160":1,"1185":1,"1232":1,"1237":1,"1240":1,"1278":1,"1322":1,"1323":3,"1358":5,"1379":1,"1382":1,"1395":3,"1414":1,"1416":1,"1456":1,"1647":1,"1898":1,"1923":1,"1940":1,"1951":1,"2001":1,"2122":1,"2145":1,"2159":1,"2167":1,"2180":1}}],["chose",{"3":{"1":1,"91":1,"101":1,"305":1,"329":1,"469":1,"727":1,"743":1,"827":1,"853":1,"933":1,"963":1,"1004":1,"1049":1,"1082":1,"1085":2,"1093":1,"1109":1,"1213":1,"1285":2,"1310":3,"1322":4,"1323":3,"1337":1,"1338":3,"1346":1,"1358":2,"1379":1,"1394":2,"1395":5,"1414":1,"1416":2,"1422":1,"1426":1,"1435":2,"1897":1,"2024":1,"2042":2,"2063":2,"2174":1}}],["chosen",{"0":{"1360":1,"1934":1,"2116":1},"3":{"0":4,"1":1,"27":1,"102":1,"243":1,"255":1,"265":1,"353":1,"447":1,"450":1,"458":1,"461":1,"501":1,"546":1,"556":1,"584":1,"591":1,"657":1,"684":1,"732":1,"811":1,"821":1,"862":1,"889":1,"1013":1,"1084":1,"1092":1,"1144":1,"1212":2,"1229":1,"1235":1,"1244":1,"1247":1,"1269":2,"1270":2,"1285":10,"1299":6,"1300":2,"1301":1,"1308":1,"1310":7,"1322":3,"1323":11,"1325":1,"1337":1,"1338":5,"1349":2,"1358":10,"1368":3,"1372":1,"1379":1,"1394":11,"1395":6,"1401":5,"1414":3,"1416":1,"1421":1,"1428":1,"1434":1,"1435":5,"1437":1,"1440":1,"1454":1,"1459":1,"1473":2,"1474":1,"1491":1,"1495":1,"1512":1,"1544":1,"1555":1,"1566":1,"1576":2,"1629":1,"1652":1,"1665":1,"1714":1,"1841":1,"1888":1,"1914":1,"1943":1,"1983":1,"2023":1,"2063":1,"2077":1,"2082":2,"2114":1,"2115":1,"2129":1,"2130":1,"2131":1,"2159":1,"2176":1,"2231":3}}],["choices",{"3":{"0":2,"97":1,"449":1,"633":1,"880":2,"905":1,"953":1,"1212":1,"1229":1,"1257":1,"1259":1,"1266":1,"1270":1,"1271":1,"1285":3,"1299":4,"1308":3,"1309":1,"1310":2,"1311":1,"1322":1,"1323":4,"1336":1,"1338":1,"1349":3,"1368":3,"1372":1,"1374":1,"1379":1,"1394":3,"1395":2,"1414":1,"1415":1,"1416":1,"1435":6,"1466":1,"1491":1,"1629":1,"1674":1,"1782":1,"1788":1,"1828":1,"1966":1,"1983":1,"2015":1,"2027":1,"2042":1,"2051":1,"2074":1,"2126":1,"2129":1,"2130":1}}],["choice",{"3":{"0":7,"20":1,"31":1,"32":1,"54":1,"62":1,"68":1,"87":1,"97":1,"98":1,"101":1,"157":1,"165":2,"168":1,"170":1,"175":1,"178":1,"201":1,"236":1,"265":2,"273":1,"274":1,"291":1,"309":1,"469":1,"508":1,"551":1,"555":1,"558":1,"560":1,"633":1,"640":2,"642":1,"647":1,"666":1,"676":1,"686":1,"690":1,"792":1,"808":1,"810":1,"811":1,"833":1,"853":1,"874":1,"876":1,"913":1,"914":1,"947":1,"953":1,"954":1,"955":1,"963":1,"968":1,"980":1,"981":1,"1032":1,"1034":3,"1037":1,"1049":1,"1052":1,"1073":1,"1077":1,"1082":1,"1125":1,"1162":1,"1185":1,"1212":4,"1229":3,"1231":1,"1237":3,"1247":4,"1248":1,"1249":1,"1259":6,"1270":6,"1271":1,"1282":1,"1285":21,"1287":1,"1299":12,"1300":1,"1303":1,"1308":1,"1309":1,"1310":8,"1311":1,"1313":1,"1315":1,"1322":13,"1323":25,"1326":2,"1338":17,"1349":8,"1352":1,"1354":1,"1358":24,"1361":1,"1368":3,"1378":1,"1379":4,"1390":1,"1394":17,"1395":23,"1396":1,"1401":4,"1414":10,"1415":2,"1416":15,"1422":1,"1426":2,"1435":24,"1438":1,"1440":5,"1444":1,"1452":1,"1454":1,"1458":1,"1459":1,"1464":1,"1466":2,"1470":1,"1471":1,"1474":2,"1495":1,"1533":1,"1546":2,"1576":2,"1595":1,"1596":1,"1598":4,"1631":1,"1639":1,"1664":1,"1668":2,"1677":1,"1719":1,"1723":1,"1746":1,"1790":1,"1804":1,"1809":1,"1825":1,"1848":1,"1853":2,"1855":1,"1860":2,"1861":2,"1865":1,"1883":1,"1908":1,"1922":1,"1934":1,"1940":1,"1993":1,"1997":1,"2024":1,"2026":1,"2027":1,"2046":1,"2066":1,"2080":3,"2082":1,"2083":2,"2085":1,"2116":1,"2149":1,"2164":1,"2181":1,"2229":1,"2231":1}}],["cipher",{"3":{"2145":2}}],["ciphertext",{"2":{"365":1,"2145":1},"3":{"358":1,"359":8,"360":2,"361":4,"363":2,"365":2,"1283":2,"1285":11,"1674":1,"2138":1,"2139":2,"2141":7,"2142":2,"2143":2,"2144":1,"2145":5}}],["cil",{"3":{"1435":1}}],["circle",{"3":{"1394":1,"1416":2}}],["circularity",{"3":{"1435":2}}],["circular",{"3":{"0":1,"583":1,"768":1,"1285":1,"1310":1,"1414":1,"1435":2,"1440":1,"1539":1,"1576":1,"1640":1,"1880":1,"1884":1,"2008":1}}],["circuitbreakersamplingduration",{"3":{"1338":2}}],["circuitbreaker",{"3":{"819":1,"1358":1}}],["circuitopencounter",{"2":{"1256":1},"3":{"1256":1,"1259":2,"1322":2}}],["circuitopen",{"3":{"819":1}}],["circuitoptions",{"2":{"831":1,"1123":1,"1125":1},"3":{"0":1,"831":1,"832":1,"1123":2,"1125":1,"1323":6,"1415":2,"1999":1}}],["circuiting",{"3":{"284":1,"946":1,"1229":1,"1231":1,"1237":1,"1247":1,"1268":1,"1270":3,"1285":1,"1299":2,"1310":1,"1319":1,"1322":1,"1349":2,"1358":2,"1379":2,"1386":1,"1394":3,"1395":1,"1401":2,"1414":2,"1435":1,"1576":1,"1665":1,"1706":1}}],["circuited",{"3":{"71":1,"819":1,"820":1,"1270":1,"1310":1,"1338":1,"1401":1}}],["circuithandler",{"2":{"1124":1,"1125":1,"1999":1},"3":{"0":1,"1124":2,"1125":1,"1323":8,"1415":1,"1999":2}}],["circuits",{"3":{"0":1,"70":1,"109":1,"117":1,"121":1,"126":1,"175":1,"179":1,"300":1,"318":1,"459":1,"556":1,"640":1,"690":1,"790":2,"820":1,"821":1,"830":1,"832":1,"948":1,"964":1,"1123":3,"1124":2,"1125":1,"1126":1,"1128":3,"1212":1,"1224":1,"1229":2,"1237":3,"1239":1,"1245":1,"1247":2,"1259":2,"1268":1,"1270":8,"1271":1,"1285":5,"1299":7,"1308":7,"1310":9,"1322":2,"1323":16,"1338":3,"1345":1,"1346":1,"1349":3,"1358":27,"1388":1,"1390":1,"1394":8,"1395":9,"1401":7,"1409":1,"1414":6,"1415":4,"1416":4,"1426":1,"1435":2,"1440":1,"1454":1,"1460":1,"1466":1,"1533":1,"1576":1,"1598":1,"1611":1,"1804":1,"1820":1,"1830":1,"1932":1,"1961":1,"1993":1,"1999":2,"2000":1,"2007":1,"2031":1,"2095":2,"2136":1,"2202":1}}],["circuit",{"0":{"100":1,"1128":2},"2":{"819":1,"2031":1,"2155":1},"3":{"0":11,"27":2,"66":1,"67":2,"70":1,"71":1,"72":2,"73":1,"100":1,"109":1,"110":1,"113":1,"122":1,"124":1,"125":1,"175":1,"180":1,"189":1,"223":1,"241":1,"243":1,"258":1,"264":1,"265":1,"272":1,"359":1,"407":2,"413":1,"486":1,"506":3,"507":4,"549":1,"555":2,"557":2,"558":1,"575":1,"607":1,"609":3,"817":1,"819":6,"821":1,"822":1,"825":1,"828":1,"830":1,"831":3,"832":4,"833":1,"879":1,"881":3,"884":1,"920":1,"1020":1,"1074":1,"1079":1,"1122":3,"1123":2,"1124":15,"1125":4,"1126":1,"1128":5,"1129":1,"1168":1,"1184":3,"1192":2,"1212":2,"1213":1,"1220":2,"1229":2,"1237":1,"1256":2,"1259":7,"1263":2,"1265":1,"1268":3,"1270":17,"1271":1,"1285":8,"1299":1,"1300":1,"1303":1,"1306":1,"1308":2,"1309":1,"1310":5,"1311":5,"1316":1,"1322":5,"1323":103,"1329":2,"1338":18,"1349":2,"1358":17,"1364":1,"1379":2,"1394":12,"1395":18,"1400":1,"1401":7,"1414":4,"1415":9,"1416":30,"1432":1,"1435":20,"1436":2,"1437":6,"1440":3,"1442":2,"1446":1,"1452":1,"1454":1,"1455":2,"1464":1,"1466":10,"1488":1,"1489":1,"1495":1,"1524":1,"1525":5,"1533":1,"1543":1,"1559":2,"1565":2,"1576":6,"1584":1,"1598":1,"1652":1,"1664":1,"1667":1,"1697":1,"1700":1,"1706":1,"1707":1,"1804":1,"1873":1,"1920":1,"1932":1,"1945":1,"1999":7,"2001":2,"2005":3,"2009":1,"2018":1,"2028":1,"2029":4,"2031":2,"2036":1,"2037":2,"2069":1,"2073":2,"2075":2,"2080":2,"2081":1,"2117":2,"2135":1,"2141":1,"2155":4,"2157":1,"2159":1,"2231":1}}],["cicd",{"3":{"1192":1,"1212":16,"1461":1,"1493":1,"1495":7,"1498":3}}],["citizen",{"3":{"1299":1}}],["citing",{"3":{"0":1,"137":2,"428":1,"990":1,"1259":1,"1299":1,"1322":1,"1323":2,"1358":1}}],["citymaxlength",{"3":{"1237":1,"1271":1}}],["city",{"3":{"1237":1,"1271":2,"1285":1,"1323":4,"1435":2,"1763":1,"1828":1}}],["cityrules",{"3":{"1199":3,"1203":1,"1207":1,"1271":7}}],["citable",{"3":{"861":2,"1432":1,"1435":3,"1525":1,"1533":2}}],["citations",{"0":{"138":1,"139":1,"140":1,"142":1},"3":{"0":1,"27":1,"34":1,"43":1,"74":1,"104":1,"112":1,"115":2,"128":1,"130":2,"137":1,"138":2,"139":2,"142":2,"145":2,"152":1,"171":1,"184":1,"190":1,"203":1,"231":1,"238":1,"249":1,"268":1,"296":1,"322":1,"328":2,"337":2,"345":1,"354":1,"365":1,"366":1,"375":1,"393":1,"395":8,"409":3,"429":1,"446":2,"453":1,"468":3,"476":1,"478":1,"490":1,"493":1,"495":1,"497":1,"514":1,"534":3,"540":1,"551":1,"554":3,"559":1,"578":1,"586":1,"589":1,"594":1,"597":2,"621":1,"624":1,"636":1,"638":3,"644":1,"647":1,"652":1,"669":1,"672":4,"680":2,"685":1,"688":1,"696":2,"710":1,"713":1,"723":1,"728":1,"744":1,"752":1,"764":3,"769":1,"777":1,"780":2,"794":1,"797":1,"803":2,"822":1,"825":3,"841":1,"844":1,"849":1,"850":1,"864":1,"867":1,"875":1,"892":1,"900":1,"927":1,"929":1,"934":2,"949":1,"975":1,"983":1,"1003":1,"1006":1,"1007":1,"1029":1,"1037":1,"1045":1,"1055":1,"1062":1,"1078":1,"1086":1,"1095":1,"1103":1,"1111":1,"1128":1,"1136":1,"1146":1,"1156":1,"1395":2,"1451":1,"1495":16,"1499":1,"1535":1,"1795":1}}],["citation",{"0":{"137":1},"3":{"0":2,"82":2,"137":1,"138":1,"139":1,"141":1,"142":1,"145":2,"252":1,"256":1,"258":2,"336":1,"364":1,"378":1,"395":3,"409":1,"425":1,"426":1,"428":2,"429":1,"474":1,"478":1,"492":1,"534":4,"540":1,"543":1,"552":1,"559":1,"567":1,"597":1,"604":1,"607":5,"613":1,"624":2,"629":1,"677":1,"693":1,"755":1,"761":1,"780":1,"806":1,"825":1,"833":1,"867":1,"877":1,"892":1,"909":1,"912":1,"917":1,"958":1,"960":1,"1006":1,"1013":1,"1021":1,"1119":1,"1380":2,"1395":1,"1461":1,"1495":22,"1496":1,"1498":1,"1499":1,"2231":1}}],["cites",{"3":{"252":1,"392":1,"492":1,"534":1,"549":1,"594":1,"782":1,"865":1,"893":1,"968":1,"986":1,"987":1,"1006":1,"1062":1,"1070":1,"1193":1,"1237":2,"1270":1,"1299":2,"1311":1,"1323":1,"1338":2,"1358":6,"1368":1,"1395":4,"1414":1,"1435":5,"1454":1,"1470":1,"1495":10,"1517":1,"1999":1}}],["cited",{"3":{"29":1,"139":1,"140":2,"250":1,"258":1,"259":2,"261":1,"276":2,"423":1,"430":1,"463":1,"490":1,"493":1,"547":1,"551":1,"584":1,"586":1,"587":1,"607":1,"720":1,"780":1,"825":1,"867":1,"875":1,"877":1,"916":1,"1006":1,"1011":1,"1065":1,"1074":1,"1190":1,"1193":1,"1236":1,"1237":1,"1270":2,"1285":1,"1299":3,"1300":1,"1310":3,"1322":1,"1323":1,"1338":1,"1349":2,"1358":1,"1379":1,"1394":2,"1395":2,"1416":5,"1435":1,"1450":2,"1461":1,"1470":1,"1484":1,"1495":9,"1496":1,"1579":1,"1596":1,"1672":1,"2049":1}}],["cite",{"3":{"0":1,"95":1,"121":1,"136":1,"139":1,"286":1,"395":1,"407":1,"493":1,"556":1,"660":1,"748":1,"869":1,"1299":1,"1338":1,"1450":1,"1495":7,"1609":1,"1743":1}}],["ci",{"0":{"1363":1,"1452":1,"1615":1},"1":{"561":1,"562":1,"563":1,"564":1,"565":1,"566":1,"567":1,"568":1,"588":1,"589":1,"590":1,"591":1,"592":1,"593":1,"594":1,"595":1,"615":1,"616":1,"617":1,"618":1,"619":1,"620":1,"621":1,"622":1,"1451":1,"1452":1,"1453":1,"1454":1,"1455":1,"1456":1,"1457":1,"1458":1,"1459":1,"1460":1},"2":{"374":1,"514":1,"589":1,"594":1,"604":1,"624":1,"1215":2,"1458":1,"1530":1,"1615":1,"1616":1,"1618":1},"3":{"0":17,"130":3,"132":3,"135":4,"136":1,"137":3,"138":1,"140":6,"142":4,"148":1,"218":3,"263":1,"265":1,"291":1,"368":1,"369":2,"370":7,"371":2,"373":10,"374":1,"375":1,"401":1,"412":1,"413":1,"414":1,"446":1,"450":2,"451":2,"483":1,"486":1,"509":4,"511":1,"514":1,"526":1,"527":1,"530":2,"531":2,"555":1,"556":1,"557":1,"558":1,"561":1,"562":1,"564":3,"572":1,"573":1,"574":1,"575":2,"576":1,"577":1,"588":1,"589":2,"590":2,"591":8,"592":1,"593":5,"594":1,"599":3,"601":1,"604":3,"609":4,"615":1,"618":1,"619":1,"621":1,"624":3,"625":1,"626":7,"630":1,"631":2,"633":5,"634":2,"635":3,"675":1,"700":1,"766":2,"767":1,"768":1,"862":1,"863":3,"864":1,"865":1,"868":1,"869":2,"871":2,"873":1,"876":1,"893":1,"906":1,"913":1,"914":3,"917":1,"941":1,"954":3,"960":1,"982":2,"983":1,"1012":2,"1017":2,"1018":1,"1020":1,"1034":8,"1037":2,"1066":2,"1067":3,"1069":1,"1070":1,"1074":1,"1091":1,"1192":1,"1212":2,"1214":1,"1215":6,"1216":1,"1237":1,"1273":1,"1285":2,"1310":2,"1323":1,"1325":3,"1338":8,"1358":2,"1363":2,"1368":4,"1379":3,"1414":2,"1425":1,"1426":4,"1427":2,"1428":2,"1430":2,"1432":4,"1433":4,"1434":1,"1435":76,"1437":15,"1439":1,"1440":4,"1441":1,"1443":1,"1444":2,"1449":1,"1451":2,"1452":44,"1453":7,"1454":12,"1455":5,"1456":1,"1458":6,"1459":2,"1460":6,"1461":2,"1462":2,"1463":1,"1464":1,"1466":3,"1467":1,"1470":1,"1473":1,"1474":1,"1475":1,"1480":2,"1481":1,"1484":2,"1485":35,"1486":6,"1487":3,"1488":9,"1489":12,"1490":1,"1491":39,"1492":2,"1493":3,"1495":2,"1502":1,"1520":1,"1522":1,"1523":5,"1524":4,"1525":60,"1529":1,"1530":10,"1531":2,"1533":8,"1534":7,"1536":2,"1544":1,"1547":1,"1550":2,"1551":1,"1552":1,"1553":2,"1557":2,"1564":1,"1568":1,"1569":2,"1571":1,"1573":1,"1575":4,"1576":65,"1581":7,"1582":2,"1583":1,"1584":3,"1585":9,"1588":1,"1589":1,"1590":40,"1591":3,"1594":1,"1595":6,"1596":1,"1598":1,"1599":2,"1604":2,"1610":6,"1613":1,"1615":1,"1616":3,"1618":2,"1641":1,"1642":1,"1653":1,"1658":1,"1660":1,"1663":1,"1668":1,"1669":1,"1670":1,"1672":1,"1673":1,"1674":1,"1695":1,"1705":1,"1706":2,"1715":4,"1730":2,"1737":1,"1738":2,"1756":2,"1760":1,"1780":1,"1783":1,"1784":1,"1792":1,"1796":1,"1799":2,"1922":2,"1928":1,"1929":1,"2002":1,"2046":2,"2050":1,"2057":1,"2058":3,"2059":1,"2078":1,"2096":1,"2100":1,"2104":3,"2107":1,"2108":1,"2119":2,"2121":1,"2122":2,"2131":2,"2132":1,"2170":1,"2175":1,"2178":1,"2212":1,"2217":1,"2218":1,"2219":2,"2231":2,"2238":2}}],["crtp",{"3":{"1435":2}}],["cry",{"3":{"1299":1}}],["cryptoprovidercache",{"3":{"1299":1,"1322":1}}],["crypto",{"0":{"2143":1},"3":{"311":1,"360":1,"602":1,"665":1,"667":1,"1213":1,"1299":2,"1335":1,"1338":3,"1435":2,"1441":1,"1466":3,"1495":1,"1904":1,"1969":1,"2141":1,"2145":1}}],["cryptographic",{"3":{"372":1,"1259":1,"1299":4,"1322":1,"1338":1,"1414":1,"1435":1,"1437":1,"1629":2,"1640":3}}],["cryptographicexception",{"2":{"359":1,"361":1,"2141":1},"3":{"359":4,"361":1,"1285":2,"2141":3}}],["cryptographically",{"3":{"209":2,"359":1,"1299":1,"1323":1,"1640":1,"1901":1,"1904":1,"1970":2,"2141":1}}],["cryptographicoperationstype",{"3":{"1435":1}}],["cryptographicoperations",{"2":{"179":1,"310":1,"827":1,"854":1,"946":2,"1291":1,"1292":1,"1293":1,"1336":1,"1901":1,"1904":1},"3":{"0":1,"179":1,"310":1,"827":1,"854":1,"946":2,"1291":1,"1292":1,"1293":1,"1299":8,"1336":1,"1338":3,"1435":3,"1901":1,"1904":1}}],["cryptography",{"2":{"665":1},"3":{"0":1,"664":1,"665":1,"1059":1,"1285":1,"1292":1,"1299":5,"1310":1,"1323":1,"1338":3,"1394":3,"1426":1,"1435":1,"1441":1,"1466":1,"1904":1,"2193":1,"2197":1}}],["cruiser",{"3":{"2048":1}}],["crumb",{"3":{"1323":2,"1394":5}}],["crucial",{"3":{"1309":1}}],["crucially",{"3":{"1223":1,"1251":1,"1259":1,"1270":1,"1280":1,"1299":1,"1322":1,"1338":2,"1358":1,"1401":1,"1414":1,"1416":1,"1864":1}}],["crux",{"3":{"1285":1,"1313":1,"1881":1,"1967":1}}],["crutch",{"3":{"1229":1}}],["crud",{"0":{"1387":1,"1388":1},"1":{"779":1,"780":1,"781":1,"782":1,"783":1,"784":1,"785":1,"786":1},"2":{"115":1,"1262":1,"1270":1},"3":{"0":1,"115":1,"122":2,"328":1,"341":1,"344":1,"444":1,"490":1,"523":1,"779":1,"782":2,"783":1,"784":1,"785":1,"881":4,"883":1,"921":1,"922":5,"924":1,"926":4,"927":1,"963":1,"980":1,"1100":1,"1203":14,"1207":87,"1208":1,"1212":1,"1233":1,"1249":1,"1259":3,"1262":2,"1263":1,"1265":8,"1270":45,"1271":3,"1285":1,"1299":1,"1310":5,"1322":5,"1323":14,"1349":10,"1351":1,"1353":1,"1358":79,"1370":3,"1372":1,"1379":14,"1381":2,"1385":1,"1388":1,"1391":1,"1394":68,"1395":3,"1401":4,"1414":6,"1435":1,"1437":2,"1525":1,"1539":1,"1576":5,"1590":1,"1610":2,"1611":1,"1615":1,"1616":1,"1620":1,"1624":1,"1625":1,"1626":1,"1628":1,"1630":1,"1631":12,"1637":2,"1638":3,"1650":1,"1665":1,"1741":1,"1746":1,"1749":1,"1756":1,"1759":1,"1823":1,"1873":1,"1984":1,"1990":3,"2073":1,"2231":1}}],["crudentitycontrollerbasetests",{"3":{"1199":1,"1207":5,"1310":2}}],["crudentitycontrollerbase",{"2":{"328":1,"925":1,"1665":1,"1873":1},"3":{"0":2,"328":1,"341":1,"922":2,"925":1,"1199":2,"1203":1,"1207":2,"1212":2,"1270":6,"1299":1,"1310":14,"1495":1,"1665":1,"1873":1}}],["crlf",{"3":{"741":1,"1299":4,"1310":2,"1435":1,"1437":1,"1478":1,"1495":1}}],["crisp",{"3":{"1815":1}}],["crios",{"3":{"1323":1}}],["cries",{"3":{"590":1,"1435":1}}],["criterion",{"3":{"265":1,"371":1,"1011":1,"1012":1,"1018":1,"1019":1,"1020":1,"1093":1,"1247":2,"1358":2,"1365":1,"1368":1,"1435":1,"1490":1,"1495":1,"1523":1,"1525":15,"1526":1,"1530":3,"1531":2,"1536":1,"1575":1,"1576":12,"1577":1,"1581":2,"1584":1,"1590":2,"1796":1,"2141":1}}],["criteria",{"1":{"1535":1,"1536":1,"1537":1,"1538":1,"1539":1,"1540":1,"1541":1,"1542":1,"1543":1,"1544":1,"1545":1,"1546":1,"1547":1,"1548":1,"1549":1,"1550":1,"1551":1,"1552":1,"1553":1,"1554":1,"1555":1,"1556":1,"1557":1,"1558":1,"1559":1,"1560":1,"1561":1,"1562":1,"1563":1,"1564":1,"1565":1,"1566":1,"1567":1,"1568":1,"1569":1,"1570":1,"1571":1},"2":{"319":1,"546":1,"547":1,"550":1},"3":{"0":4,"168":1,"318":1,"319":1,"376":1,"544":1,"545":4,"546":1,"547":1,"548":1,"549":3,"550":1,"591":1,"1010":1,"1011":2,"1012":9,"1013":3,"1014":3,"1017":3,"1018":1,"1019":1,"1090":1,"1212":1,"1216":2,"1239":11,"1240":2,"1243":3,"1245":1,"1247":49,"1278":1,"1285":5,"1309":1,"1310":2,"1341":1,"1349":3,"1352":1,"1358":43,"1365":1,"1368":1,"1371":1,"1379":2,"1430":1,"1435":11,"1437":4,"1466":1,"1518":2,"1525":5,"1530":2,"1533":1,"1535":5,"1536":6,"1537":1,"1538":1,"1539":1,"1540":1,"1541":1,"1542":1,"1543":1,"1544":1,"1545":1,"1546":1,"1547":1,"1548":1,"1549":1,"1550":1,"1551":1,"1552":2,"1553":1,"1554":1,"1555":1,"1556":1,"1557":1,"1558":1,"1559":1,"1560":1,"1561":1,"1562":1,"1563":1,"1564":1,"1565":1,"1566":1,"1567":1,"1568":1,"1569":1,"1570":1,"1574":1,"1576":9,"1580":1,"1581":2,"1583":1,"1584":2,"1590":2,"1636":1,"1661":1,"1795":1,"1796":1,"1801":1,"1814":9,"1816":1,"1858":1,"1993":2,"2039":1,"2050":1,"2170":2,"2178":1}}],["critically",{"3":{"235":1,"1237":1,"1270":1,"1299":1,"1341":1,"1414":1,"1435":3,"1451":1,"1804":1,"1894":1}}],["critical",{"0":{"1618":1},"2":{"791":1,"792":1,"794":1,"2165":1,"2167":1},"3":{"0":1,"767":1,"790":6,"791":1,"792":3,"794":1,"812":1,"870":1,"995":1,"1259":1,"1285":1,"1299":1,"1302":1,"1310":1,"1394":11,"1414":1,"1416":5,"1444":1,"1448":1,"1454":3,"1455":1,"1459":1,"1462":2,"1465":1,"1491":1,"1558":1,"1564":1,"1568":1,"1638":1,"1648":1,"1813":1,"2165":3,"2167":1}}],["crack",{"3":{"1899":1,"1901":1,"1904":1}}],["cracking",{"3":{"311":1,"945":1,"1299":2,"1435":1}}],["craft",{"3":{"1323":1,"1896":1,"2068":1}}],["craftsmanship",{"3":{"1300":1,"1551":1}}],["crafted",{"3":{"1270":1,"1323":1,"1358":4,"1435":3}}],["crawler",{"3":{"607":1,"609":1,"1466":2,"2157":3}}],["crashing",{"3":{"1237":1,"1259":1,"1285":1,"1299":1,"1379":1,"1395":1,"1400":1,"1401":2}}],["crashed",{"3":{"1125":1,"1322":2,"1323":1,"1474":1}}],["crashes",{"3":{"0":1,"16":1,"18":1,"146":1,"147":1,"150":1,"1074":1,"1213":1,"1285":1,"1323":1,"1435":1,"1490":1,"2127":1}}],["crash",{"3":{"0":4,"19":1,"24":1,"148":1,"195":1,"197":1,"199":1,"201":3,"226":1,"413":2,"520":1,"524":1,"537":1,"539":1,"601":1,"603":1,"676":1,"692":2,"714":1,"716":1,"756":1,"897":1,"931":2,"933":1,"996":1,"1016":1,"1018":1,"1022":1,"1041":1,"1074":1,"1076":1,"1102":1,"1116":1,"1254":1,"1257":1,"1258":1,"1259":12,"1269":1,"1270":1,"1285":3,"1317":1,"1322":5,"1323":2,"1338":4,"1348":1,"1356":1,"1358":5,"1366":1,"1367":1,"1368":2,"1379":1,"1395":4,"1407":1,"1414":6,"1416":16,"1435":1,"1454":1,"1490":2,"1575":1,"1576":1,"1630":1,"1659":1,"1668":1,"1720":1,"1723":1,"1838":1,"1841":1,"1844":1,"1946":1,"2012":1,"2125":1,"2163":1,"2189":1}}],["crowd",{"3":{"390":1,"1323":2,"1394":1,"1954":1}}],["cronformatexception",{"3":{"1322":1}}],["cronexpression",{"3":{"707":3,"1269":1,"1270":2,"1285":1,"1322":3,"1441":1}}],["cronschedule",{"3":{"1322":1}}],["crons",{"3":{"625":1,"626":2,"1437":1,"1455":1,"1489":1,"1491":2,"1590":1,"1598":1}}],["cronosnextoccurrencetests",{"3":{"1199":1,"1207":2,"1437":2}}],["cronos",{"3":{"0":1,"707":2,"708":1,"709":1,"1059":1,"1212":1,"1213":1,"1270":2,"1322":3,"1947":1}}],["cron",{"1":{"704":1,"705":1,"706":1,"707":1,"708":1,"709":1,"710":1,"711":1,"2180":1,"2181":1,"2182":1,"2183":1,"2184":1,"2185":1,"2186":1,"2187":1,"2188":1,"2189":1,"2190":1,"2191":1,"2192":1},"2":{"707":1},"3":{"0":5,"627":2,"628":1,"640":1,"704":1,"707":6,"708":1,"709":3,"766":1,"767":1,"768":1,"1000":1,"1041":1,"1043":1,"1046":1,"1212":1,"1213":1,"1260":1,"1269":2,"1270":5,"1322":16,"1366":1,"1437":2,"1455":1,"1456":1,"1458":1,"1459":1,"1464":1,"1473":1,"1474":1,"1481":1,"1495":1,"1523":1,"1525":5,"1530":1,"1576":2,"1581":1,"1588":1,"1589":1,"1590":4,"1595":1,"1663":3,"1778":1,"1946":2,"1947":3,"1949":3,"2033":1,"2036":1,"2164":1,"2179":2,"2180":1,"2182":1,"2191":1,"2202":1,"2205":1,"2231":1}}],["cropped",{"3":{"1479":1}}],["cropping",{"3":{"1479":1}}],["crops",{"3":{"1322":1,"2125":1}}],["crop",{"3":{"0":1,"440":1,"1212":1,"1285":1,"1322":1,"1414":1,"1495":1,"2127":1}}],["crossrepocomparison",{"3":{"1521":1,"1527":1,"1572":1,"1578":1,"1586":1,"1592":1}}],["crosslayerviolation",{"3":{"1435":2}}],["crosstenantwritedetail",{"3":{"1310":1}}],["crosstenantwritetitle",{"3":{"1310":1}}],["crosstenantwriteexception",{"2":{"107":1,"109":1,"698":2,"1276":1,"1285":1,"1850":1},"3":{"0":1,"107":1,"109":2,"698":2,"1199":5,"1203":1,"1207":2,"1237":1,"1276":2,"1285":6,"1310":5,"1850":1}}],["crossservicespeakerlinktests",{"2":{"1534":1},"3":{"1199":1,"1207":2,"1437":1,"1534":1,"1610":1}}],["crossservicesmoketests",{"3":{"1199":1,"1207":2}}],["crossserviceuserregisteredtests",{"2":{"1534":1},"3":{"1199":1,"1207":2,"1437":1,"1534":1,"1610":1}}],["crossservicetestbase",{"3":{"1199":6,"1207":2,"1435":2}}],["crossservicecollection",{"3":{"1199":2,"1207":2}}],["crossservicedatasource",{"2":{"1428":1},"3":{"1199":4,"1207":1,"1428":1,"1435":2,"1487":1}}],["crossservice",{"2":{"1207":1,"1436":1,"1485":1,"1486":1,"1489":2,"1525":1,"1533":1,"1590":1,"1614":1},"3":{"914":3,"1199":13,"1203":1,"1206":1,"1207":53,"1435":11,"1436":2,"1437":3,"1466":1,"1485":1,"1486":1,"1487":1,"1489":2,"1495":1,"1525":3,"1533":1,"1590":3,"1610":3,"1614":1}}],["crossservicefixture",{"2":{"914":1},"3":{"914":3,"1199":8,"1207":2,"1435":4,"1436":2,"1487":2}}],["crossservicefixturebasetests",{"3":{"1199":1,"1207":3,"1435":1,"1437":1,"1486":1}}],["crossservicefixturebase",{"2":{"914":1,"1428":1,"1670":1},"3":{"640":1,"914":4,"916":1,"1066":1,"1199":4,"1207":3,"1212":1,"1427":1,"1428":7,"1435":16,"1437":2,"1487":2,"1495":1,"1670":1}}],["crosssourcespecificationtests",{"3":{"1199":1,"1207":4,"1495":1}}],["crosssourcespecification",{"2":{"168":1,"545":1,"549":1,"1239":1,"1499":1,"1576":1,"1661":1,"1858":2,"1860":1,"1861":1},"3":{"168":2,"545":2,"547":1,"549":1,"1199":4,"1203":1,"1207":2,"1212":1,"1239":11,"1247":9,"1352":2,"1358":11,"1435":2,"1437":1,"1495":3,"1499":1,"1576":4,"1661":1,"1858":2,"1860":1,"1861":1}}],["crossevent",{"3":{"1358":2}}],["crossentitynavigationfinder",{"3":{"1199":2,"1207":1,"1427":1,"1430":1,"1435":4}}],["crossed",{"3":{"255":1,"1322":1,"1395":1,"1414":1,"1435":1,"1466":1,"1972":1,"2009":1}}],["crosses",{"0":{"1225":1,"1857":1},"3":{"0":2,"109":2,"249":1,"265":1,"396":1,"551":1,"645":1,"656":1,"657":1,"658":2,"696":1,"786":1,"789":1,"930":1,"1018":1,"1212":1,"1237":2,"1243":1,"1259":2,"1299":2,"1306":1,"1308":1,"1310":3,"1323":2,"1331":1,"1349":4,"1352":1,"1356":1,"1358":3,"1378":1,"1379":3,"1394":2,"1395":9,"1399":1,"1401":2,"1414":1,"1415":2,"1416":2,"1426":1,"1435":1,"1437":1,"1633":1,"1637":2,"1839":1,"1861":1,"1886":1,"1891":1,"1915":1,"1918":1,"1921":1,"1926":1,"1943":1,"1952":1,"2072":1,"2125":1,"2154":1,"2229":1}}],["crossdatasourcedegradeconventiontests",{"2":{"164":1},"3":{"164":1,"1199":1,"1207":7,"1285":1,"1437":1}}],["crossdatasourcedegradeconvention",{"2":{"46":1,"47":1,"166":1,"1280":1,"1309":1,"1472":1,"1481":1,"1576":1,"1848":1,"1852":1,"1857":1,"1861":1,"1865":1},"3":{"46":1,"47":1,"166":1,"1199":2,"1203":1,"1207":2,"1247":1,"1280":3,"1285":26,"1309":4,"1395":1,"1435":1,"1472":1,"1481":1,"1576":2,"1848":1,"1852":1,"1857":1,"1861":1,"1865":1}}],["crossing",{"0":{"1348":1,"1412":1,"1945":1},"3":{"0":2,"255":1,"390":1,"427":1,"826":1,"1292":1,"1322":2,"1323":1,"1338":1,"1349":3,"1358":3,"1395":1,"1401":1,"1415":1,"1435":2,"1630":1,"1921":1,"2072":1}}],["cross",{"0":{"1345":1,"1361":1,"1399":1,"1401":1,"1404":1,"1459":1,"1468":1,"1493":1,"1527":1,"1558":1,"1578":1,"1592":1,"1750":1,"1752":1,"1771":1,"1773":1,"1858":1,"2189":1,"2207":1},"1":{"8":1,"9":1,"10":1,"11":1,"12":1,"13":1,"28":1,"29":1,"30":1,"31":1,"32":1,"33":1,"34":1,"35":1,"50":1,"51":1,"52":1,"53":1,"54":1,"55":1,"771":1,"772":1,"773":1,"774":1,"775":1,"776":1,"777":1,"778":1,"993":1,"994":1,"995":1,"996":1,"997":1,"998":1,"999":1,"1000":1,"1415":1,"1710":1,"1711":1,"1712":1,"1713":1,"1714":1,"1715":1,"1892":1,"1893":1,"1894":1,"1895":1,"1896":1,"1897":1,"1898":1},"2":{"1188":1},"3":{"0":28,"8":1,"10":3,"28":1,"31":1,"47":2,"49":1,"50":1,"52":1,"54":1,"57":1,"59":2,"61":3,"62":3,"63":1,"64":2,"68":1,"77":1,"81":1,"91":1,"97":1,"99":1,"105":2,"116":2,"117":1,"119":1,"122":2,"135":1,"136":1,"150":2,"151":1,"152":1,"157":2,"159":1,"162":1,"166":3,"168":2,"169":2,"171":1,"207":1,"214":2,"215":1,"216":1,"217":1,"245":2,"253":1,"255":1,"264":1,"265":3,"266":1,"273":1,"313":1,"331":1,"336":1,"343":1,"381":1,"386":2,"387":1,"391":1,"396":1,"398":1,"399":1,"400":1,"405":1,"446":1,"459":1,"470":1,"474":1,"506":2,"507":1,"509":1,"517":1,"521":2,"524":2,"528":1,"531":1,"535":1,"536":1,"541":1,"545":1,"547":1,"549":3,"554":1,"578":1,"583":2,"584":3,"585":1,"587":1,"607":1,"611":1,"618":1,"619":2,"624":2,"625":2,"626":11,"627":1,"628":4,"629":2,"640":5,"642":1,"659":1,"661":1,"665":1,"667":1,"692":1,"698":1,"699":1,"717":1,"731":1,"733":1,"747":1,"756":2,"764":1,"771":1,"774":4,"776":1,"777":1,"781":1,"782":1,"799":4,"801":1,"802":1,"809":2,"810":1,"826":1,"834":1,"861":1,"895":1,"896":1,"897":1,"900":1,"906":1,"907":1,"914":5,"915":2,"930":1,"931":1,"950":1,"964":1,"980":1,"987":1,"990":1,"992":1,"993":1,"996":1,"1011":1,"1018":1,"1022":1,"1024":1,"1026":1,"1027":1,"1028":1,"1030":1,"1043":1,"1049":1,"1050":1,"1053":1,"1061":1,"1066":2,"1067":1,"1068":1,"1069":1,"1070":1,"1075":3,"1083":1,"1086":1,"1104":1,"1108":1,"1140":1,"1147":1,"1150":1,"1183":1,"1184":1,"1185":1,"1187":1,"1188":1,"1190":1,"1191":2,"1192":10,"1193":2,"1200":3,"1201":3,"1202":6,"1203":6,"1210":2,"1212":20,"1213":1,"1216":3,"1231":1,"1232":2,"1237":3,"1239":3,"1243":1,"1244":2,"1245":2,"1247":11,"1252":1,"1257":1,"1258":1,"1259":15,"1260":1,"1261":2,"1265":1,"1267":1,"1270":13,"1271":3,"1273":1,"1276":1,"1278":1,"1280":3,"1285":38,"1286":1,"1287":1,"1297":1,"1299":15,"1300":6,"1307":2,"1308":21,"1309":15,"1310":23,"1311":6,"1312":2,"1313":1,"1315":2,"1319":1,"1322":19,"1323":27,"1325":4,"1327":3,"1337":1,"1338":21,"1339":4,"1340":2,"1341":1,"1342":1,"1344":3,"1348":1,"1349":37,"1351":1,"1352":1,"1353":2,"1354":4,"1358":80,"1362":2,"1366":1,"1368":4,"1374":1,"1377":1,"1378":1,"1379":16,"1380":1,"1386":1,"1390":1,"1394":29,"1395":70,"1397":1,"1400":1,"1401":29,"1402":1,"1403":3,"1404":2,"1405":1,"1409":2,"1412":2,"1413":1,"1414":39,"1415":11,"1416":27,"1426":3,"1427":2,"1428":1,"1429":1,"1430":2,"1432":2,"1435":150,"1436":4,"1437":25,"1438":1,"1439":1,"1440":2,"1441":1,"1442":1,"1443":1,"1445":2,"1448":4,"1451":3,"1452":1,"1453":1,"1454":12,"1455":4,"1458":1,"1459":15,"1460":1,"1461":1,"1462":4,"1464":2,"1466":5,"1470":1,"1472":2,"1473":1,"1474":2,"1475":2,"1481":4,"1484":2,"1485":4,"1486":2,"1487":5,"1488":4,"1489":16,"1491":13,"1492":1,"1495":28,"1496":1,"1497":4,"1498":1,"1499":3,"1501":1,"1506":1,"1508":1,"1509":2,"1521":1,"1522":1,"1523":4,"1524":1,"1525":23,"1527":1,"1529":1,"1530":6,"1531":1,"1532":2,"1533":11,"1534":5,"1535":2,"1541":1,"1544":1,"1546":1,"1553":2,"1555":1,"1564":1,"1569":2,"1570":1,"1571":2,"1572":1,"1573":1,"1575":1,"1576":15,"1578":1,"1584":1,"1585":1,"1586":1,"1590":19,"1593":1,"1594":1,"1595":1,"1596":1,"1598":4,"1599":4,"1601":1,"1609":1,"1610":6,"1611":2,"1616":1,"1617":3,"1626":1,"1629":2,"1630":2,"1633":3,"1634":4,"1637":9,"1638":6,"1640":1,"1650":1,"1652":1,"1653":1,"1655":4,"1659":1,"1660":1,"1661":4,"1662":3,"1663":5,"1664":2,"1666":1,"1667":1,"1669":1,"1670":1,"1673":1,"1674":1,"1681":1,"1698":1,"1710":1,"1721":1,"1728":1,"1731":1,"1743":1,"1747":1,"1748":1,"1750":1,"1751":1,"1763":1,"1764":5,"1768":2,"1774":1,"1778":1,"1779":1,"1782":1,"1785":1,"1789":1,"1791":3,"1792":1,"1795":2,"1809":1,"1817":2,"1818":1,"1819":1,"1822":1,"1824":1,"1826":3,"1832":1,"1833":1,"1844":1,"1848":4,"1850":1,"1851":1,"1852":3,"1855":1,"1857":1,"1859":2,"1860":2,"1861":3,"1862":1,"1864":3,"1865":2,"1866":2,"1867":2,"1875":1,"1878":1,"1881":1,"1883":1,"1884":1,"1885":1,"1891":1,"1892":1,"1895":1,"1896":1,"1898":1,"1910":1,"1913":1,"1915":1,"1918":1,"1920":1,"1922":1,"1936":2,"1940":1,"1945":1,"1949":1,"1967":1,"1968":3,"1973":1,"1974":1,"1987":1,"1998":1,"2007":1,"2008":1,"2015":1,"2019":1,"2021":1,"2026":3,"2028":1,"2034":1,"2052":2,"2055":1,"2066":1,"2069":1,"2073":1,"2081":1,"2082":2,"2085":3,"2091":1,"2093":1,"2095":1,"2097":1,"2106":1,"2112":2,"2133":1,"2138":3,"2145":2,"2147":1,"2148":1,"2149":2,"2150":1,"2151":1,"2155":1,"2159":1,"2161":1,"2167":1,"2168":1,"2184":1,"2193":1,"2201":1,"2207":1,"2210":1,"2212":1,"2217":1,"2218":1,"2219":2,"2226":1,"2227":1,"2229":5,"2231":10,"2242":1}}],["cref",{"3":{"1415":2,"1495":1,"1525":1}}],["creep",{"3":{"698":1,"2047":1}}],["creds",{"3":{"1525":2}}],["credible",{"3":{"1144":1,"1903":1,"2042":1}}],["credibilityexperience",{"3":{"1368":1}}],["credibilityexperiencescore",{"3":{"1349":1,"1358":1,"1368":1}}],["credibility",{"3":{"1018":1,"1019":1,"1020":1,"1349":1,"1358":2,"1365":2}}],["credit",{"3":{"1489":1}}],["credits",{"3":{"777":1,"1590":2,"1804":1,"1909":1}}],["credited",{"3":{"428":1,"703":1,"1909":1}}],["credentialroutes",{"2":{"180":1},"3":{"180":1}}],["credentials",{"0":{"1404":1},"3":{"0":2,"313":1,"349":1,"351":1,"362":1,"432":1,"433":1,"434":2,"436":1,"498":1,"507":1,"527":1,"598":2,"601":2,"643":1,"665":1,"774":3,"775":3,"776":1,"905":1,"926":1,"945":2,"947":1,"950":1,"972":1,"1283":1,"1285":2,"1287":1,"1299":14,"1308":2,"1310":7,"1311":1,"1322":6,"1323":9,"1335":2,"1338":2,"1395":3,"1405":2,"1409":1,"1411":1,"1414":9,"1416":23,"1428":1,"1432":1,"1435":6,"1440":1,"1442":1,"1453":2,"1454":1,"1463":1,"1464":1,"1466":3,"1468":1,"1471":4,"1474":1,"1480":1,"1481":1,"1487":3,"1489":1,"1525":1,"1553":1,"1576":1,"1581":1,"1588":1,"1595":2,"1638":1,"1640":8,"1647":1,"1666":1,"1668":1,"1670":1,"1672":1,"1681":1,"1692":1,"1763":2,"1768":1,"1776":1,"1895":1,"1897":1,"1900":1,"1935":1,"1939":1,"1980":1,"2062":1,"2066":1,"2087":1,"2121":1,"2223":1}}],["credentialed",{"3":{"0":1,"432":1,"436":1,"1187":1,"1338":2,"1415":1,"1416":12,"1897":1}}],["credential",{"0":{"1406":1},"3":{"0":8,"173":1,"174":1,"176":1,"177":1,"179":1,"180":2,"189":1,"212":1,"217":1,"279":1,"280":1,"281":2,"284":4,"286":1,"309":2,"313":2,"314":3,"348":1,"349":1,"350":1,"352":1,"353":2,"355":1,"358":1,"362":1,"403":1,"435":1,"458":1,"498":1,"499":2,"526":1,"528":1,"529":1,"530":2,"532":1,"572":1,"597":1,"598":1,"599":2,"600":1,"602":2,"605":1,"612":1,"632":1,"634":1,"640":1,"641":1,"642":2,"643":3,"667":1,"670":1,"684":1,"688":1,"690":6,"691":1,"692":1,"718":2,"766":1,"768":1,"792":1,"853":1,"855":1,"869":1,"871":1,"872":2,"874":1,"904":1,"906":2,"908":1,"938":1,"945":3,"946":1,"947":1,"948":4,"950":3,"1017":2,"1018":1,"1020":3,"1022":1,"1057":1,"1058":1,"1059":1,"1063":1,"1118":1,"1183":1,"1212":1,"1241":1,"1264":1,"1270":1,"1282":1,"1285":4,"1289":1,"1290":2,"1291":2,"1292":1,"1293":1,"1299":41,"1310":2,"1311":1,"1321":1,"1322":12,"1323":9,"1325":1,"1334":1,"1338":14,"1358":1,"1365":1,"1367":1,"1368":1,"1394":1,"1395":87,"1402":2,"1403":1,"1404":2,"1405":1,"1406":3,"1408":2,"1414":22,"1416":8,"1426":1,"1435":22,"1437":8,"1440":1,"1443":1,"1444":1,"1445":1,"1446":1,"1454":8,"1456":2,"1457":1,"1459":1,"1460":1,"1461":2,"1464":5,"1465":1,"1466":14,"1467":2,"1471":2,"1473":1,"1474":1,"1476":2,"1480":2,"1491":2,"1576":1,"1594":1,"1595":1,"1630":3,"1640":4,"1649":1,"1660":1,"1669":1,"1671":1,"1702":1,"1731":2,"1764":1,"1897":2,"1898":1,"1901":3,"1904":1,"1966":1,"1970":1,"1972":1,"1977":3,"1978":3,"1979":3,"1981":1,"1997":2,"1998":2,"1999":1,"2000":4,"2001":2,"2054":1,"2065":1,"2116":1,"2140":1,"2152":1,"2165":1,"2172":1}}],["credentialpathprefixes",{"2":{"0":1,"217":1,"1338":1,"2148":1},"3":{"0":1,"217":1,"1338":6,"2148":1}}],["cream",{"1":{"2049":1,"2050":1,"2051":1,"2052":1,"2053":1,"2054":1,"2055":1,"2056":1,"2057":1,"2058":1,"2059":1},"3":{"2049":2,"2059":1,"2209":1}}],["creada",{"3":{"265":1}}],["creado",{"3":{"265":2}}],["creator",{"3":{"1361":1,"1629":1}}],["creatable",{"3":{"1310":2}}],["creational",{"3":{"1525":1,"1538":1}}],["creations",{"3":{"27":1,"1435":1}}],["creation",{"3":{"17":1,"351":1,"352":1,"524":1,"698":3,"782":1,"784":2,"790":2,"792":1,"794":1,"906":1,"988":1,"1174":1,"1175":2,"1213":1,"1229":1,"1234":1,"1249":1,"1254":1,"1259":2,"1271":1,"1278":1,"1285":10,"1299":3,"1308":3,"1310":3,"1323":1,"1338":1,"1349":7,"1356":1,"1358":9,"1363":1,"1368":1,"1379":1,"1394":12,"1395":4,"1401":2,"1414":6,"1416":2,"1428":1,"1435":1,"1466":1,"1471":3,"1480":1,"1489":1,"1495":2,"1629":1,"1630":5,"1631":2,"1633":1,"1634":1,"1638":4,"1639":1,"1650":1,"1684":1,"1685":1,"1718":1,"1726":3,"1727":1,"1728":1,"1749":1,"1750":1,"1764":1,"1766":1,"1769":1,"1772":1,"1774":1,"1840":1,"1977":1,"1978":2,"2165":1}}],["creating",{"3":{"0":1,"7":1,"291":1,"632":1,"711":1,"715":1,"781":1,"1270":1,"1280":1,"1285":4,"1299":1,"1308":3,"1310":2,"1322":1,"1349":3,"1358":10,"1379":1,"1395":1,"1401":1,"1416":2,"1435":5,"1466":1,"1471":1,"1473":1,"1489":2,"1628":1,"1630":2,"1631":1,"1638":3,"1663":1,"1683":1,"1721":1,"1728":1,"1748":1,"1987":1,"1998":1}}],["create→",{"3":{"1576":1}}],["create→result",{"3":{"1525":1}}],["createwindow",{"3":{"1415":3,"1416":1}}],["createmodel",{"3":{"1495":2}}],["createmodule",{"3":{"1435":1}}],["createmauiapp",{"3":{"1415":15,"1416":2}}],["createmigrationprooftable",{"2":{"564":1,"1318":1},"3":{"564":2,"1199":3,"1203":1,"1207":2,"1318":3,"1322":5}}],["createorderhandler",{"3":{"1685":1}}],["createorreactivate",{"3":{"1395":5}}],["createorupdateinstallationasync",{"3":{"1322":1}}],["createroomasync",{"3":{"1394":2}}],["createrequest",{"3":{"1496":1}}],["createrequestclientasync",{"2":{"881":1},"3":{"881":2,"1323":1,"1394":1}}],["createresult",{"3":{"1394":4}}],["createresetpasswordcommand",{"3":{"1310":1,"1414":2}}],["createreadonly",{"3":{"1285":7}}],["createrefreshusermissingerror",{"2":{"497":1,"499":1},"3":{"497":1,"499":1,"1299":1}}],["createx",{"3":{"1358":1}}],["createxhandler",{"2":{"1353":1},"3":{"1353":1,"1358":1}}],["createnewcategory",{"3":{"1358":1}}],["createnewanswerasync",{"3":{"1349":1,"1358":2}}],["createbatchasync",{"2":{"1379":1},"3":{"1379":8}}],["createbuilder",{"3":{"1338":1,"1435":1}}],["createbookmarkasync",{"3":{"1395":2}}],["createbookmarkhandlertests",{"3":{"1199":1,"1207":3}}],["createbookmarkhandler",{"3":{"1199":2,"1203":1,"1207":2,"1285":3,"1349":1,"1358":3,"1379":1,"1395":16,"1435":1}}],["createbookmarkrequestvalidatortests",{"3":{"1199":1,"1207":2}}],["createbookmarkrequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1395":8}}],["createbookmarkrequest",{"3":{"1199":12,"1203":1,"1207":2,"1395":12}}],["createbootstraploggerfactory",{"2":{"401":1,"1332":1},"3":{"401":2,"1310":1,"1332":1,"1338":2}}],["createbounded",{"2":{"1100":1},"3":{"1100":1,"1395":1}}],["createifnotexists",{"3":{"1322":1}}],["createinheritedasync",{"3":{"1435":2}}],["createinventoryitemhandler",{"3":{"897":2}}],["createinstance",{"2":{"583":1,"585":1,"1314":1},"3":{"583":1,"585":1,"1229":1,"1237":1,"1247":1,"1285":4,"1299":2,"1314":1,"1322":1}}],["createindex",{"2":{"564":1},"3":{"564":1}}],["createloggerconfiguration",{"2":{"1332":1},"3":{"1332":1,"1338":2}}],["createlogger",{"3":{"1310":1,"1338":2,"1416":1}}],["createlinkedtimeout",{"2":{"1421":1},"3":{"1421":1,"1426":1}}],["createlinkedtokensource",{"2":{"121":1}}],["createlink",{"2":{"1356":1},"3":{"1342":1,"1349":3,"1356":1,"1358":2}}],["createlimitedpartition",{"3":{"1310":9,"1999":1}}],["createlivepollhandlertests",{"3":{"1199":1,"1207":3}}],["createlivepollhandler",{"3":{"1199":2,"1203":1,"1207":2,"1349":7,"1398":2,"1401":11,"1495":1}}],["createlivepollcommandvalidatortests",{"3":{"1199":1,"1207":2}}],["createlivepollcommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1401":6}}],["createlivepollcommand",{"3":{"1199":8,"1203":1,"1207":2,"1401":9}}],["createlivepollrequestvalidatortests",{"3":{"1199":1,"1207":2}}],["createlivepollrequestvalidator",{"3":{"1199":3,"1203":1,"1207":2,"1401":8}}],["createlivepollrequest",{"3":{"1199":14,"1203":1,"1207":2,"1395":2,"1401":17}}],["createhost",{"3":{"1435":2,"1487":1}}],["createhostasync",{"3":{"1299":1}}],["createhandler",{"3":{"1310":3,"1358":1,"1379":1,"1395":1}}],["createfile",{"3":{"1342":1,"1349":3,"1358":2}}],["createforwardedheadersoptions",{"2":{"1337":1},"3":{"1337":1,"1338":3}}],["createforgotpasswordcommand",{"3":{"1310":1,"1414":2}}],["createfactory",{"3":{"1285":2,"1435":2,"1487":1}}],["createfailure",{"3":{"1270":24,"2135":1}}],["createfetchcts",{"2":{"559":1},"3":{"556":1,"559":1,"881":1,"1323":3}}],["createquestionasync",{"3":{"1394":1}}],["createquestioncommand",{"3":{"1358":1}}],["createquestionhandlertests",{"3":{"1199":1,"1207":2,"1358":2}}],["createquestionhandler",{"2":{"1638":1},"3":{"1199":2,"1203":1,"1207":2,"1270":2,"1351":1,"1358":23,"1394":2,"1638":1}}],["createquery",{"3":{"725":3,"1310":3,"1414":2}}],["createproducthandlertests",{"3":{"1684":1}}],["createproducthandler",{"3":{"1684":1}}],["createproviderchatclient",{"2":{"1091":1,"1420":1,"2171":1},"3":{"1091":1,"1420":1,"1426":2,"2171":1}}],["createpage",{"3":{"1495":1}}],["createpartnerasync",{"3":{"1394":2}}],["createpartnerhandlertests",{"3":{"1199":1,"1207":2,"1358":4,"1495":1}}],["createpartnerhandler",{"3":{"1199":2,"1203":1,"1207":2,"1349":2,"1351":3,"1358":18}}],["createpostgresql",{"2":{"909":1,"1034":1},"3":{"905":1,"909":1,"1034":1,"1285":2}}],["createthingasync",{"3":{"1435":3}}],["createticketcommandvalidator",{"3":{"1199":1,"1207":1,"1435":6}}],["createticketcommand",{"3":{"1199":5,"1207":1,"1435":10}}],["createtickethandler",{"3":{"782":1,"1199":1,"1207":1,"1435":5}}],["createtenantscope",{"2":{"703":1},"3":{"698":1,"703":1,"1259":4,"1285":8,"1310":1}}],["createtable",{"2":{"564":1},"3":{"564":1,"1322":1}}],["createversion7",{"3":{"690":1,"1395":2}}],["createcategoryasync",{"3":{"1394":2}}],["createcouponcommand",{"2":{"2095":1},"3":{"2095":1}}],["createcore",{"3":{"1349":7}}],["createcoreasync",{"3":{"0":1,"926":1,"1265":1,"1270":3,"1358":15}}],["createcontainers",{"3":{"1435":1}}],["createcontext",{"3":{"1270":3}}],["createconferencecategorycommand",{"3":{"1358":1}}],["createconferencecategoryhandlertests",{"3":{"1199":1,"1207":2,"1358":2,"1435":1}}],["createconferencecategoryhandler",{"3":{"1199":2,"1203":1,"1207":2,"1270":2,"1351":1,"1358":23}}],["createconverter",{"2":{"1223":1,"1237":1},"3":{"1223":1,"1229":2,"1237":4,"1299":3}}],["createchangepasswordcommand",{"2":{"1405":1},"3":{"1310":2,"1405":1,"1414":2}}],["createchangepreferencescommand",{"2":{"1405":1},"3":{"1299":1,"1310":1,"1405":1,"1414":2}}],["createchained",{"2":{"827":1,"1124":1},"3":{"827":1,"1124":1,"1323":1,"1338":1}}],["createcheckoutsessionhandler",{"3":{"1764":1}}],["createcheckoutsessioncommand",{"2":{"1264":1},"3":{"1264":2}}],["createcheckoutsessioncommandvalidator",{"2":{"674":1},"3":{"674":2,"1590":1}}],["createcheckoutsession",{"3":{"674":1,"1264":1,"1764":1}}],["createclientwithtoken",{"3":{"881":1,"1323":4}}],["createclient",{"3":{"572":1,"1308":2,"1428":1,"1435":7,"1487":2,"1610":1}}],["createaccountlink",{"3":{"1435":1}}],["createaccesstokenforsession",{"2":{"905":1},"3":{"905":1,"1299":3}}],["createaccesstoken",{"2":{"907":1,"2196":1},"3":{"0":1,"905":1,"907":1,"1059":1,"1288":2,"1289":1,"1299":4,"1405":1,"1414":1,"2196":1}}],["createactivityasync",{"3":{"1394":2}}],["createactivityhandlertests",{"3":{"1199":1,"1207":2,"1358":2,"1437":1}}],["createactivityhandler",{"3":{"1199":3,"1203":1,"1207":2,"1270":2,"1351":2,"1358":15}}],["createauthenticatedclientasync",{"2":{"881":1},"3":{"881":1,"1323":6,"1394":6,"1395":3,"1414":1}}],["createasyncscope",{"3":{"1259":2,"1358":3,"1401":1}}],["createasync",{"2":{"318":1,"324":1,"913":1,"1067":1,"1069":1},"3":{"318":1,"324":1,"913":1,"1067":1,"1069":1,"1310":6,"1358":2,"1379":29,"1395":8,"1401":2,"1489":1,"1994":1}}],["createunsafe",{"3":{"1237":1}}],["createunderclaimasync",{"2":{"524":1,"1102":1},"3":{"0":1,"524":2,"1100":2,"1102":1,"1401":4}}],["createusingazureservicebus",{"2":{"640":1},"3":{"640":1}}],["createuser",{"2":{"314":1},"3":{"314":1,"1285":2,"1288":1,"1299":6,"1405":1,"1414":3}}],["createsinglekeyring",{"3":{"1285":1}}],["createsessionpollasync",{"3":{"1401":1}}],["createsessionasync",{"3":{"1394":2}}],["createsessionrequest",{"2":{"1829":1},"3":{"1271":1,"1829":1}}],["createsessioncommand",{"2":{"1829":1},"3":{"1271":1,"1358":1,"1829":1}}],["createsessionhandlertests",{"3":{"1199":1,"1207":2,"1358":2}}],["createsessionhandler",{"3":{"1199":2,"1203":1,"1207":2,"1285":3,"1351":7,"1358":21,"1394":2,"1435":2}}],["createscope",{"3":{"1259":1}}],["createsponsorasync",{"3":{"1394":3}}],["createsponsorhandlertests",{"3":{"1199":1,"1207":2,"1358":2,"1437":1}}],["createsponsorhandler",{"3":{"1199":2,"1203":1,"1207":2,"1270":2,"1349":1,"1351":1,"1358":13}}],["createspeakerasync",{"3":{"1394":3}}],["createspeakercommand",{"3":{"1358":1}}],["createspeakerhandlertests",{"3":{"1199":1,"1207":2,"1358":2}}],["createspeakerhandler",{"3":{"1199":2,"1203":1,"1207":2,"1270":2,"1351":1,"1358":20}}],["createsqlite",{"2":{"909":1},"3":{"905":1,"909":1,"1285":2}}],["createsqlserver",{"2":{"909":1},"3":{"905":1,"909":1,"1285":2,"1435":1}}],["creates",{"0":{"1313":1},"3":{"25":1,"27":1,"42":1,"157":1,"195":1,"197":1,"200":1,"225":1,"230":1,"350":1,"352":1,"524":1,"572":1,"599":1,"610":1,"665":1,"698":1,"705":1,"792":1,"881":1,"926":1,"1034":1,"1212":1,"1223":1,"1259":1,"1270":2,"1285":7,"1299":9,"1306":1,"1308":2,"1310":7,"1322":4,"1323":15,"1328":1,"1338":7,"1351":2,"1358":17,"1372":1,"1373":2,"1388":1,"1394":9,"1395":4,"1401":7,"1414":1,"1415":4,"1416":9,"1426":2,"1435":12,"1437":3,"1440":2,"1466":5,"1471":3,"1487":3,"1490":1,"1629":1,"1631":3,"1634":1,"1637":1,"1638":1,"1639":1,"1640":1,"1649":1,"1652":2,"1670":1,"1683":2,"1687":1,"1691":1,"1699":1,"1721":1,"1725":1,"1729":1,"1746":4,"1747":1,"1748":3,"1749":1,"1750":2,"1751":1,"1756":1,"1763":1,"1764":2,"1766":1,"1768":2,"1769":1,"1848":1,"1905":1,"1908":1,"1932":2,"1976":1,"1977":1,"2047":1,"2054":1,"2112":1,"2157":1,"2165":1}}],["createeventasync",{"3":{"1394":2}}],["createeventcommand",{"3":{"1358":1}}],["createeventhandlertests",{"3":{"1199":1,"1207":2,"1358":1,"1435":1}}],["createeventhandler",{"3":{"1199":2,"1203":1,"1207":2,"1270":2,"1351":1,"1358":10}}],["createexecutionstrategy",{"2":{"1273":1},"3":{"1273":1,"1285":2}}],["createexternal",{"2":{"354":1,"1976":1},"3":{"0":1,"350":2,"354":1,"1404":1,"1414":4,"1976":1}}],["createentityasync",{"2":{"1955":1},"3":{"1270":1,"1310":2,"1358":12,"1955":1}}],["createentityhandlerbasetests",{"3":{"1199":1,"1207":1,"1270":1}}],["createentityhandlerbase",{"2":{"922":1,"1265":1,"1269":1,"1665":1},"3":{"922":1,"926":1,"1199":13,"1203":1,"1207":2,"1260":1,"1265":4,"1269":2,"1270":8,"1310":7,"1349":1,"1351":1,"1358":74,"1665":1}}],["createentityhandler",{"2":{"922":1,"1265":1},"3":{"0":1,"922":2,"1199":3,"1203":1,"1207":2,"1260":1,"1265":3,"1270":7,"1310":1,"1322":2,"1576":1}}],["createdroom",{"3":{"1394":1}}],["createdactivity",{"3":{"1394":1}}],["createdat",{"3":{"905":3,"1285":1,"1299":2,"1435":2}}],["createdatabasesasync",{"3":{"1435":1}}],["createdataction",{"3":{"157":1}}],["createdataprotectionkeyvaultkey",{"2":{"602":1,"604":1,"668":1},"3":{"0":1,"602":1,"604":1,"668":1,"1466":1}}],["createdatroute",{"3":{"157":1,"330":1,"1310":2,"1379":10,"1987":1}}],["createdsession",{"3":{"1394":1}}],["createdcount",{"3":{"1358":1}}],["createdbcontext",{"3":{"1285":1}}],["createdby",{"2":{"1634":1},"3":{"470":1,"545":1,"715":1,"1237":4,"1239":1,"1247":6,"1285":2,"1358":9,"1368":4,"1379":2,"1437":1,"1629":1,"1630":6,"1631":4,"1634":4,"1637":1,"1638":1,"1639":2,"1814":1}}],["createdevent",{"3":{"1394":1}}],["createdelegate",{"3":{"1285":2,"1299":1}}],["createdefault",{"2":{"459":1,"464":1,"466":2,"698":1,"747":1,"749":1},"3":{"459":1,"464":1,"466":2,"698":1,"747":1,"749":1,"774":1,"1310":6,"1435":5}}],["createdtime",{"2":{"757":1},"3":{"757":1,"1454":1,"1466":1}}],["createdon",{"2":{"1140":1},"3":{"39":2,"539":1,"714":1,"1140":1,"1212":1,"1231":2,"1237":3,"1274":2,"1285":4,"1308":5,"1322":2,"1358":4,"1368":1,"1395":15,"1401":4,"1414":13,"1435":3,"1599":2,"1629":1,"1630":2,"1639":5,"1663":1,"1809":1,"2089":1,"2166":1,"2185":1}}],["created",{"2":{"781":1,"1682":1},"3":{"0":2,"157":1,"178":1,"265":2,"290":3,"296":2,"350":2,"353":1,"408":1,"440":1,"539":1,"599":2,"600":1,"610":1,"633":1,"640":1,"665":1,"668":1,"670":1,"690":1,"693":1,"707":1,"716":1,"721":1,"781":1,"782":1,"784":1,"789":1,"790":6,"791":2,"881":1,"905":1,"988":1,"991":1,"1033":1,"1034":1,"1096":1,"1116":1,"1212":1,"1229":2,"1247":3,"1249":1,"1259":4,"1270":3,"1274":1,"1275":1,"1278":2,"1280":1,"1285":17,"1289":1,"1299":5,"1300":1,"1308":9,"1310":6,"1315":1,"1322":6,"1323":12,"1338":7,"1341":2,"1343":1,"1349":30,"1351":1,"1358":36,"1363":1,"1368":5,"1370":1,"1379":4,"1394":27,"1395":22,"1398":1,"1401":14,"1414":6,"1415":4,"1416":4,"1424":2,"1426":1,"1428":1,"1435":11,"1437":3,"1452":1,"1454":2,"1464":2,"1466":6,"1471":1,"1473":2,"1474":1,"1478":1,"1489":1,"1495":4,"1537":1,"1544":1,"1576":1,"1595":1,"1613":1,"1629":5,"1630":5,"1631":2,"1632":2,"1633":8,"1634":1,"1635":1,"1636":1,"1637":1,"1638":5,"1640":1,"1645":1,"1651":2,"1652":2,"1682":1,"1683":1,"1688":1,"1689":1,"1718":2,"1726":1,"1727":1,"1730":1,"1746":2,"1747":2,"1763":2,"1764":2,"1766":2,"1874":1,"1894":1,"1905":1,"1907":1,"1932":1,"1953":1,"2073":1,"2125":1,"2165":2,"2166":2,"2176":1,"2231":2}}],["create",{"0":{"1648":1,"1687":1,"2101":1},"1":{"919":1,"920":1,"921":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1},"2":{"220":1,"231":1,"354":1,"601":1,"661":1,"698":1,"700":1,"703":1,"741":1,"747":1,"749":1,"801":2,"965":1,"968":1,"1051":1,"1067":1,"1124":1,"1135":1,"1167":1,"1169":1,"1170":1,"1397":1,"1471":1,"1810":1,"1811":1,"1953":1,"2047":1,"2089":1,"2092":1,"2103":1},"3":{"0":8,"5":1,"73":1,"200":2,"220":1,"231":1,"290":1,"318":1,"328":1,"329":1,"330":3,"334":1,"348":1,"350":4,"354":1,"527":1,"564":1,"601":1,"609":1,"641":1,"642":1,"657":15,"659":1,"660":1,"661":1,"690":4,"691":1,"693":1,"698":1,"700":1,"703":1,"732":1,"734":2,"741":1,"747":1,"749":2,"782":1,"799":1,"801":2,"803":1,"839":1,"882":1,"888":2,"891":1,"897":4,"905":1,"907":1,"919":1,"920":1,"921":1,"922":4,"924":1,"925":2,"926":1,"965":1,"968":1,"1033":1,"1050":1,"1051":1,"1067":1,"1085":1,"1091":1,"1124":1,"1135":1,"1167":1,"1168":7,"1169":1,"1170":2,"1184":1,"1203":38,"1207":84,"1212":3,"1229":1,"1234":2,"1237":22,"1259":1,"1260":3,"1265":4,"1270":41,"1271":10,"1285":42,"1289":1,"1299":10,"1300":8,"1303":1,"1304":1,"1308":15,"1310":45,"1311":1,"1322":9,"1323":21,"1326":1,"1338":5,"1341":2,"1342":2,"1348":2,"1349":79,"1350":1,"1351":10,"1352":4,"1353":3,"1356":1,"1357":2,"1358":381,"1368":8,"1370":2,"1372":1,"1373":1,"1374":2,"1377":1,"1379":37,"1383":3,"1391":3,"1394":147,"1395":74,"1397":3,"1398":1,"1401":61,"1404":1,"1405":1,"1411":1,"1414":22,"1416":1,"1419":1,"1420":1,"1426":9,"1428":1,"1435":34,"1437":5,"1440":2,"1466":6,"1471":6,"1473":1,"1475":1,"1476":2,"1480":2,"1481":1,"1486":2,"1487":1,"1488":5,"1489":2,"1495":2,"1525":7,"1533":2,"1534":2,"1590":2,"1595":2,"1599":1,"1607":1,"1626":3,"1629":3,"1630":4,"1631":9,"1637":3,"1638":9,"1639":2,"1640":2,"1645":1,"1650":4,"1651":1,"1652":2,"1653":1,"1655":1,"1663":1,"1665":1,"1670":1,"1684":13,"1685":12,"1686":2,"1688":1,"1696":1,"1697":1,"1718":1,"1719":1,"1724":1,"1726":2,"1727":2,"1741":3,"1745":4,"1746":3,"1747":2,"1748":2,"1749":8,"1752":1,"1753":1,"1764":3,"1767":4,"1768":9,"1773":1,"1809":4,"1810":2,"1811":1,"1907":1,"1921":1,"1932":1,"1952":2,"1953":1,"1955":2,"1976":2,"1978":1,"1985":1,"1986":1,"1987":2,"1990":1,"2044":1,"2047":1,"2089":1,"2092":1,"2103":1,"2127":1,"2162":1,"2231":2}}],["cobertura",{"2":{"1454":1},"3":{"1454":2,"1491":1,"1590":1}}],["coffee",{"3":{"1339":1,"1341":1,"1349":3,"1358":1,"1363":1,"1368":2,"1391":1,"1394":1}}],["covariant",{"3":{"1269":1,"1270":1,"1310":2,"1322":1}}],["coverlet",{"3":{"1213":1,"1610":2,"1616":1}}],["covering",{"3":{"138":1,"160":1,"268":1,"494":1,"609":1,"618":1,"635":1,"790":1,"980":1,"996":1,"1083":1,"1247":1,"1285":8,"1308":1,"1310":1,"1311":1,"1322":1,"1323":6,"1338":1,"1349":3,"1357":1,"1358":12,"1379":1,"1394":1,"1395":4,"1415":2,"1416":1,"1432":1,"1435":12,"1436":2,"1437":2,"1448":1,"1452":1,"1454":1,"1455":1,"1466":1,"1488":1,"1490":1,"1495":2,"1525":2,"1576":1,"1590":1,"1653":1,"1670":1,"1683":1,"1685":1,"1747":1,"1922":1,"2033":1,"2036":1,"2037":1,"2042":1,"2078":1,"2157":1,"2189":1,"2231":1,"2239":1}}],["cover",{"0":{"1607":1,"1741":1,"1785":1},"1":{"1997":1,"1998":1,"1999":1,"2000":1,"2001":1},"3":{"23":1,"52":1,"72":1,"118":1,"135":2,"157":1,"170":1,"265":1,"333":1,"388":1,"398":1,"401":1,"461":1,"466":1,"528":1,"530":1,"531":1,"563":1,"578":1,"582":1,"618":1,"651":1,"682":1,"690":1,"700":1,"718":1,"719":1,"751":1,"768":1,"789":1,"810":1,"881":1,"954":1,"966":1,"1018":1,"1026":1,"1075":1,"1119":1,"1187":1,"1219":1,"1235":1,"1242":1,"1247":1,"1259":1,"1265":1,"1285":5,"1299":2,"1300":2,"1308":1,"1310":1,"1319":1,"1321":1,"1323":4,"1338":2,"1358":3,"1379":2,"1395":5,"1416":1,"1424":1,"1427":1,"1435":17,"1437":6,"1444":1,"1455":1,"1458":1,"1464":1,"1482":1,"1486":1,"1487":1,"1533":1,"1550":1,"1576":1,"1581":2,"1582":1,"1596":1,"1631":1,"1645":1,"1665":1,"1666":1,"1668":1,"1775":1,"1997":2,"1998":1,"2000":1,"2001":1,"2005":2,"2024":1,"2033":1,"2047":1,"2058":1,"2075":1,"2078":1,"2103":1,"2155":1,"2207":1,"2235":1}}],["covered",{"0":{"2114":1},"3":{"0":1,"100":1,"164":1,"177":1,"186":1,"215":1,"236":1,"249":1,"265":1,"295":1,"358":1,"419":1,"601":1,"611":1,"620":1,"632":1,"657":1,"702":1,"718":1,"743":1,"768":1,"790":1,"832":1,"889":1,"907":1,"998":1,"1011":1,"1017":1,"1050":2,"1091":1,"1104":1,"1124":1,"1212":1,"1214":1,"1237":7,"1247":4,"1259":1,"1263":1,"1270":4,"1271":6,"1283":1,"1285":33,"1289":1,"1299":7,"1300":3,"1308":6,"1309":1,"1310":9,"1316":1,"1322":18,"1323":31,"1331":1,"1338":38,"1349":2,"1358":92,"1379":1,"1394":6,"1395":42,"1414":10,"1416":8,"1435":29,"1437":6,"1440":1,"1442":1,"1443":3,"1451":1,"1452":1,"1453":2,"1454":3,"1459":1,"1461":1,"1466":2,"1481":1,"1487":2,"1488":1,"1489":1,"1495":3,"1496":1,"1525":3,"1533":1,"1560":1,"1576":3,"1584":2,"1590":3,"1595":1,"1610":1,"1626":1,"1644":1,"1663":1,"1665":1,"1698":1,"1699":1,"1706":2,"1714":1,"1727":1,"1754":1,"1792":1,"1801":1,"1806":1,"1809":1,"1811":1,"1816":1,"1826":1,"1827":1,"1830":1,"1833":1,"1845":1,"1852":1,"1861":1,"1867":1,"1876":1,"1885":1,"1889":1,"1891":1,"1898":1,"1904":1,"1911":1,"1913":1,"1923":1,"1929":1,"1940":1,"1949":1,"1958":1,"1964":1,"1971":1,"1978":1,"1984":1,"1990":1,"1991":1,"1996":1,"1999":1,"2000":1,"2001":2,"2005":1,"2013":1,"2027":1,"2029":1,"2037":1,"2040":1,"2048":1,"2059":1,"2068":1,"2079":1,"2085":1,"2096":1,"2097":1,"2107":1,"2114":1,"2122":1,"2127":1,"2132":1,"2138":1,"2145":1,"2152":1,"2159":1,"2166":1,"2168":1,"2179":1,"2192":1,"2193":1,"2198":1,"2202":1,"2238":1}}],["covers",{"0":{"141":1},"3":{"0":7,"35":1,"41":1,"130":1,"135":1,"142":1,"160":1,"173":1,"178":1,"179":1,"182":1,"189":1,"226":1,"233":1,"235":1,"241":1,"265":1,"318":2,"328":1,"343":1,"358":1,"359":2,"361":1,"365":1,"369":1,"373":1,"402":1,"418":1,"427":1,"434":1,"440":1,"459":1,"461":1,"482":1,"486":1,"506":1,"518":1,"528":2,"529":1,"535":1,"539":1,"555":1,"572":2,"590":1,"591":1,"607":1,"609":2,"618":1,"620":1,"625":1,"626":2,"648":1,"665":1,"674":1,"675":1,"690":1,"701":1,"762":1,"766":1,"794":1,"802":1,"809":1,"818":1,"832":3,"838":1,"844":1,"861":1,"880":1,"897":1,"926":1,"946":1,"964":4,"1016":1,"1018":1,"1019":1,"1020":1,"1074":1,"1116":3,"1123":1,"1132":1,"1135":1,"1161":1,"1163":1,"1170":1,"1175":1,"1185":1,"1195":1,"1212":1,"1217":1,"1229":3,"1230":1,"1238":1,"1247":5,"1248":1,"1258":1,"1259":4,"1260":1,"1263":1,"1270":5,"1271":1,"1272":1,"1280":1,"1283":1,"1285":22,"1286":1,"1299":11,"1300":2,"1301":2,"1308":2,"1310":27,"1312":1,"1315":1,"1319":1,"1322":11,"1323":31,"1324":1,"1332":1,"1338":8,"1339":1,"1349":2,"1350":2,"1358":22,"1359":1,"1364":1,"1368":4,"1369":1,"1379":1,"1380":1,"1394":9,"1395":55,"1396":1,"1401":9,"1402":1,"1414":16,"1415":3,"1416":8,"1417":1,"1421":1,"1427":1,"1434":1,"1435":89,"1436":1,"1437":64,"1446":1,"1448":1,"1449":1,"1452":1,"1453":1,"1454":5,"1464":1,"1466":8,"1470":1,"1473":1,"1474":1,"1475":2,"1480":1,"1484":1,"1486":1,"1488":1,"1489":2,"1491":3,"1495":1,"1523":1,"1525":12,"1531":1,"1532":1,"1534":1,"1576":4,"1582":1,"1584":2,"1590":5,"1595":1,"1611":1,"1625":1,"1626":1,"1631":1,"1638":4,"1644":1,"1652":1,"1663":1,"1669":1,"1684":1,"1685":2,"1763":1,"1809":1,"1820":1,"1824":1,"1842":1,"1854":1,"1890":1,"1909":1,"1915":1,"1922":1,"1928":1,"2000":2,"2001":1,"2033":1,"2090":1,"2097":1,"2117":1,"2126":1,"2140":1,"2141":1,"2145":1,"2157":1,"2166":1,"2167":1,"2173":1,"2182":2,"2188":1}}],["coverage",{"0":{"138":1,"1495":1,"1498":1,"1642":1,"2057":1},"1":{"1494":1,"1495":1,"1496":1,"1497":1,"1498":1,"1499":1,"1500":1},"2":{"1454":1,"1460":1},"3":{"0":7,"67":1,"71":1,"74":1,"115":1,"122":1,"127":2,"130":1,"132":1,"138":2,"265":5,"267":3,"269":1,"305":1,"312":1,"361":1,"365":1,"373":1,"547":1,"551":1,"566":1,"572":1,"574":1,"593":1,"607":4,"608":1,"609":1,"619":1,"620":1,"717":1,"729":1,"782":1,"837":1,"840":1,"863":2,"874":1,"891":1,"892":2,"914":2,"918":1,"982":1,"990":1,"1020":1,"1037":1,"1066":1,"1076":1,"1085":1,"1192":2,"1194":1,"1212":2,"1213":1,"1216":2,"1237":1,"1270":1,"1271":1,"1280":1,"1285":2,"1299":9,"1310":14,"1338":1,"1349":2,"1368":2,"1394":2,"1395":1,"1414":1,"1416":1,"1433":1,"1435":62,"1437":4,"1452":3,"1454":20,"1455":2,"1458":2,"1459":1,"1460":2,"1462":1,"1466":2,"1468":1,"1485":5,"1486":1,"1487":2,"1488":2,"1489":2,"1491":10,"1492":1,"1494":1,"1495":12,"1496":1,"1499":1,"1524":1,"1525":3,"1533":3,"1534":1,"1550":1,"1564":2,"1575":2,"1576":8,"1580":1,"1581":1,"1585":1,"1590":6,"1594":1,"1595":1,"1596":1,"1599":1,"1610":3,"1641":1,"1642":1,"1660":1,"1670":1,"1672":1,"1798":1,"1890":1,"1963":1,"2033":1,"2047":1,"2049":2,"2057":4,"2058":2,"2059":2,"2084":2,"2157":1,"2219":2}}],["coercing",{"3":{"1237":1}}],["coerced",{"3":{"1237":1,"1285":1,"1299":1,"1310":1}}],["coerces",{"3":{"1236":1,"1237":1,"1259":2}}],["coexisting",{"3":{"2078":1}}],["coexisted",{"3":{"1452":1}}],["coexists",{"3":{"1401":1}}],["coexist",{"3":{"0":1,"59":1,"111":1,"293":1,"448":2,"1052":1,"1229":1,"1299":1,"1303":1,"1322":1,"1358":1,"1395":1,"1415":1,"1435":1,"1544":1,"1805":1,"1815":1,"2128":1,"2130":2,"2132":1,"2138":1}}],["coined",{"3":{"1414":2}}],["coining",{"3":{"1408":1,"1414":1}}],["coincides",{"3":{"1379":1}}],["coincide",{"3":{"1323":1}}],["coincidentally",{"3":{"1299":1,"1310":1}}],["coin",{"3":{"1126":1,"1906":1,"2028":1}}],["cohesion",{"3":{"1191":1,"1200":1,"1313":1,"1322":1,"1358":3,"1395":2,"1414":1,"1435":12,"1496":1,"1516":1,"1524":1,"1525":1,"1533":1,"1534":1,"1570":1,"1576":4,"1581":1,"1585":1,"1590":1,"1670":1,"1931":1,"1940":1,"2047":1,"2093":1}}],["cohesive",{"3":{"1003":1,"1201":1,"1212":1,"1299":2,"1322":1,"1351":1,"1394":1,"1395":1,"1415":1,"1435":5,"1495":1,"1525":2,"1533":1,"1541":2,"1554":1,"1571":1,"1590":1,"1940":1,"2229":1}}],["coherence",{"3":{"302":1,"896":1,"1310":6,"1323":1,"1922":1,"2137":1,"2155":1}}],["coherently",{"3":{"1358":1,"1416":1}}],["coherent",{"0":{"2009":1},"3":{"0":1,"98":1,"369":1,"398":1,"1270":2,"1300":1,"1323":7,"1338":1,"1358":1,"1380":1,"1394":1,"1414":1,"1415":2,"1416":6,"1435":2,"1453":1,"1556":1,"1570":1,"1915":1,"1923":1,"2024":1,"2111":1,"2231":1}}],["codified",{"3":{"1247":1,"1338":1}}],["coding",{"3":{"243":1,"254":1,"1242":1,"1247":1,"1299":1,"1310":2,"1323":1,"1340":1,"1349":2,"1358":2,"1379":1,"1394":2,"1395":1,"1414":3,"1416":1,"1435":4,"1474":1,"1569":1,"1915":1}}],["codeowners",{"3":{"1672":1}}],["codebehinds",{"3":{"1435":3}}],["codebases",{"0":{"1502":1},"3":{"1211":1,"1212":1,"1299":1,"1358":1,"1499":1,"1781":1,"1877":1,"2038":1,"2041":1,"2054":1,"2169":1}}],["codebase",{"0":{"1212":1,"2229":1},"3":{"131":1,"151":1,"469":1,"725":1,"800":1,"801":1,"804":1,"805":1,"1052":1,"1124":1,"1140":1,"1190":1,"1210":1,"1212":1,"1214":3,"1216":1,"1217":1,"1218":1,"1221":1,"1229":7,"1231":1,"1236":1,"1237":1,"1247":3,"1248":1,"1259":1,"1270":6,"1271":3,"1272":1,"1280":1,"1285":7,"1299":13,"1300":3,"1301":1,"1308":2,"1309":2,"1310":2,"1311":1,"1322":4,"1323":5,"1328":1,"1338":8,"1339":1,"1349":2,"1350":1,"1358":18,"1362":1,"1367":1,"1368":6,"1379":7,"1394":4,"1395":7,"1401":4,"1409":1,"1414":2,"1415":4,"1416":4,"1426":1,"1427":1,"1430":2,"1435":14,"1484":1,"1488":1,"1490":1,"1491":1,"1535":1,"1551":1,"1660":1,"1673":1,"1785":1,"1793":1,"1804":1,"1827":1,"1829":1,"1830":1,"1847":1,"1853":1,"1926":1,"1959":1,"1970":1,"2015":1,"2048":1,"2049":1,"2074":1,"2086":1,"2098":1,"2138":1,"2145":1,"2229":1,"2232":1}}],["codereusedacrossbranchesofonetype",{"2":{"1435":1},"3":{"1435":2}}],["codepath",{"3":{"1394":1}}],["codecoverage",{"2":{"1610":1},"3":{"1610":1}}],["codec",{"3":{"1228":1,"1311":2,"1394":3,"1437":1}}],["code=true",{"3":{"1452":1}}],["code=false",{"3":{"1452":1}}],["code=",{"3":{"423":1,"1415":1,"1416":1,"1974":1}}],["codesignprovision",{"3":{"1480":1}}],["codesignentitlements",{"2":{"1480":1},"3":{"1480":1}}],["codes",{"3":{"0":4,"201":1,"265":1,"266":1,"341":1,"343":1,"419":2,"538":1,"576":1,"609":1,"759":1,"790":1,"794":1,"1028":1,"1058":1,"1059":1,"1212":1,"1217":1,"1229":2,"1235":1,"1237":3,"1259":1,"1263":1,"1270":2,"1271":8,"1285":2,"1292":6,"1299":24,"1308":1,"1310":10,"1311":1,"1322":11,"1323":6,"1336":1,"1338":2,"1343":1,"1349":3,"1358":31,"1374":1,"1379":4,"1394":3,"1395":16,"1401":1,"1403":1,"1414":6,"1435":28,"1437":1,"1471":1,"1488":1,"1495":1,"1545":1,"1576":2,"1581":2,"1582":1,"1590":1,"1595":1,"1638":1,"1640":6,"1665":1,"1682":1,"1684":1,"1685":3,"1726":1,"1747":1,"1752":1,"1924":1,"1929":1,"1976":1,"2075":1,"2117":1,"2157":1,"2163":1,"2165":1,"2166":1,"2197":3,"2201":1,"2231":1}}],["coded",{"1":{"787":1,"788":1,"789":1,"790":1,"791":1,"792":1,"793":1,"794":1,"795":1},"3":{"0":1,"32":1,"88":1,"109":2,"117":1,"173":1,"265":3,"447":1,"787":1,"790":1,"794":1,"827":1,"861":1,"1270":1,"1271":2,"1285":3,"1299":6,"1300":6,"1308":2,"1310":6,"1311":3,"1322":2,"1323":9,"1338":4,"1349":2,"1358":22,"1368":2,"1394":2,"1395":3,"1401":3,"1426":1,"1435":8,"1437":1,"1464":1,"1479":1,"1487":1,"1488":2,"1525":2,"1546":1,"1563":3,"1576":2,"1590":2,"1595":1,"1596":2,"1652":3,"1653":1,"1665":1,"1701":1,"1820":1,"1885":1,"1926":1,"2082":1,"2129":1,"2168":1,"2202":1}}],["code",{"0":{"720":1,"1214":1,"1551":1,"1612":1,"1790":1,"1841":1,"1988":1,"2099":1},"1":{"606":1,"607":1,"608":1,"609":1,"610":1,"611":1,"612":1,"613":1,"614":1,"1461":1,"1462":1,"1463":1,"1464":1,"1465":1,"1466":1,"1467":1,"1468":1,"1469":1,"2133":1,"2134":1,"2135":1,"2136":1,"2137":1,"2138":1},"2":{"107":1,"264":1,"266":1,"269":1,"276":1,"301":1,"562":1,"564":1,"567":1,"604":1,"626":1,"660":1,"1458":1,"1975":1,"2085":1},"3":{"0":32,"1":1,"6":1,"21":1,"24":1,"27":2,"32":1,"51":1,"52":1,"53":1,"57":2,"59":2,"60":1,"63":2,"74":1,"93":1,"107":2,"109":11,"110":1,"111":1,"118":1,"122":1,"125":1,"128":2,"135":4,"136":3,"145":1,"149":1,"151":1,"157":3,"160":1,"165":1,"167":2,"171":2,"177":1,"180":2,"186":1,"188":1,"190":1,"217":1,"220":1,"224":1,"225":1,"226":1,"230":1,"242":1,"243":3,"244":1,"248":1,"249":2,"258":2,"264":1,"265":6,"266":2,"267":1,"268":1,"269":1,"276":1,"285":1,"290":1,"292":1,"299":1,"300":1,"301":2,"305":1,"306":2,"310":3,"316":1,"317":1,"322":1,"325":1,"326":1,"328":1,"335":1,"341":2,"342":1,"343":2,"350":8,"351":3,"352":2,"353":1,"354":1,"359":2,"360":1,"365":1,"370":1,"373":1,"381":1,"383":2,"391":1,"397":1,"401":1,"409":1,"412":1,"415":1,"418":1,"419":1,"420":5,"421":2,"422":1,"423":2,"424":1,"425":1,"426":1,"428":2,"433":1,"434":1,"435":1,"448":1,"451":1,"452":1,"459":2,"465":1,"472":1,"481":1,"482":3,"483":2,"486":1,"488":1,"489":2,"490":5,"491":2,"492":2,"493":3,"494":1,"495":1,"497":3,"502":2,"506":1,"507":1,"508":1,"511":1,"517":1,"518":1,"530":1,"536":1,"537":1,"540":1,"543":2,"545":4,"546":2,"549":1,"550":1,"555":1,"557":1,"560":1,"562":1,"563":1,"564":2,"565":1,"566":1,"567":1,"573":1,"582":2,"583":2,"584":2,"585":1,"586":1,"590":1,"591":1,"592":2,"593":1,"599":2,"604":2,"606":1,"609":1,"617":2,"618":1,"626":5,"633":2,"635":1,"638":2,"639":1,"640":5,"642":2,"649":1,"657":8,"658":1,"659":1,"660":2,"673":1,"674":4,"676":1,"681":2,"682":1,"683":2,"684":1,"689":2,"691":3,"692":2,"698":1,"700":2,"708":2,"713":1,"716":1,"725":1,"732":1,"733":2,"735":1,"736":1,"741":4,"742":1,"744":1,"749":2,"751":1,"757":4,"758":1,"759":1,"765":1,"766":2,"789":3,"790":4,"791":1,"794":1,"799":1,"800":1,"811":1,"812":1,"819":1,"825":1,"827":2,"829":1,"831":1,"832":2,"833":2,"836":1,"837":2,"839":2,"840":3,"845":1,"846":1,"856":1,"873":2,"876":1,"877":2,"880":1,"882":1,"884":1,"897":2,"905":2,"909":1,"914":1,"916":1,"932":1,"933":1,"934":1,"939":2,"940":1,"946":2,"948":2,"953":1,"963":1,"964":4,"971":1,"973":1,"976":2,"979":1,"980":1,"988":3,"990":2,"999":1,"1003":1,"1004":5,"1005":2,"1014":1,"1018":3,"1019":6,"1021":1,"1029":1,"1033":1,"1040":1,"1043":1,"1045":1,"1051":1,"1058":2,"1059":4,"1060":3,"1066":1,"1068":1,"1074":1,"1075":1,"1082":1,"1091":3,"1092":2,"1093":1,"1095":1,"1100":1,"1108":1,"1124":1,"1126":1,"1132":1,"1133":1,"1134":1,"1135":1,"1140":1,"1150":1,"1161":4,"1163":1,"1186":1,"1190":5,"1192":1,"1193":2,"1212":24,"1214":2,"1216":3,"1217":1,"1219":2,"1220":1,"1221":2,"1225":1,"1229":21,"1231":1,"1234":3,"1235":1,"1237":46,"1241":1,"1247":19,"1248":1,"1257":2,"1258":1,"1259":23,"1263":4,"1265":2,"1270":30,"1271":53,"1275":1,"1281":1,"1285":79,"1288":1,"1292":7,"1295":1,"1299":150,"1300":19,"1301":1,"1302":1,"1304":2,"1307":1,"1308":26,"1309":10,"1310":122,"1311":25,"1312":2,"1313":1,"1314":1,"1319":1,"1321":4,"1322":98,"1323":90,"1324":3,"1328":1,"1332":1,"1337":2,"1338":36,"1341":2,"1342":1,"1343":3,"1344":1,"1346":4,"1347":1,"1348":1,"1349":40,"1351":2,"1352":5,"1354":2,"1357":3,"1358":304,"1363":2,"1364":7,"1368":19,"1369":2,"1370":2,"1374":1,"1376":2,"1378":1,"1379":36,"1381":3,"1383":2,"1384":2,"1387":2,"1388":1,"1393":1,"1394":188,"1395":168,"1396":1,"1398":2,"1401":69,"1402":1,"1405":1,"1406":1,"1408":1,"1412":1,"1413":1,"1414":86,"1415":13,"1416":64,"1423":1,"1426":7,"1428":1,"1430":7,"1435":215,"1436":1,"1437":7,"1440":3,"1442":1,"1444":2,"1445":2,"1446":1,"1448":2,"1449":1,"1450":1,"1451":2,"1452":8,"1454":30,"1458":19,"1459":2,"1460":3,"1461":2,"1462":4,"1466":21,"1473":3,"1474":3,"1475":1,"1476":1,"1480":1,"1481":1,"1485":3,"1486":1,"1487":2,"1488":9,"1490":2,"1491":17,"1495":15,"1496":1,"1498":2,"1499":1,"1508":3,"1514":1,"1515":1,"1517":1,"1520":1,"1523":3,"1524":1,"1525":10,"1529":2,"1530":5,"1531":3,"1532":1,"1533":3,"1534":5,"1536":1,"1537":2,"1538":1,"1541":1,"1546":1,"1547":1,"1551":2,"1552":1,"1553":1,"1554":1,"1559":1,"1570":3,"1571":1,"1573":1,"1576":11,"1580":1,"1581":1,"1585":2,"1588":1,"1590":11,"1594":1,"1595":3,"1596":2,"1597":1,"1599":4,"1601":1,"1625":2,"1626":1,"1629":2,"1630":3,"1639":1,"1640":2,"1645":1,"1650":2,"1652":3,"1655":3,"1657":1,"1660":2,"1661":1,"1662":2,"1665":3,"1668":1,"1670":3,"1672":2,"1675":1,"1680":2,"1682":1,"1684":4,"1685":5,"1687":1,"1699":1,"1701":1,"1702":1,"1704":1,"1707":1,"1719":1,"1723":3,"1724":1,"1725":1,"1726":2,"1754":1,"1763":1,"1769":1,"1776":1,"1780":1,"1781":1,"1782":2,"1783":2,"1788":1,"1789":5,"1792":2,"1793":1,"1795":1,"1799":1,"1801":1,"1804":5,"1805":1,"1809":2,"1810":1,"1814":4,"1816":2,"1818":2,"1821":1,"1825":1,"1826":1,"1830":3,"1841":3,"1846":2,"1848":3,"1849":1,"1852":4,"1857":1,"1863":1,"1864":1,"1865":1,"1867":2,"1870":1,"1874":1,"1876":1,"1877":2,"1880":1,"1883":1,"1887":1,"1892":1,"1895":1,"1897":1,"1898":2,"1899":1,"1901":1,"1903":1,"1907":2,"1908":3,"1910":1,"1914":2,"1915":2,"1919":1,"1922":2,"1923":1,"1926":2,"1937":1,"1943":3,"1947":1,"1949":1,"1951":2,"1954":3,"1955":1,"1956":1,"1957":1,"1958":2,"1961":1,"1963":1,"1971":1,"1972":1,"1974":8,"1975":6,"1976":1,"1977":2,"1978":4,"1999":2,"2000":2,"2001":1,"2002":1,"2009":2,"2012":3,"2013":1,"2014":2,"2015":2,"2016":1,"2019":2,"2023":1,"2025":2,"2027":1,"2033":1,"2035":3,"2037":2,"2039":1,"2040":1,"2042":6,"2043":2,"2044":3,"2045":1,"2046":1,"2050":1,"2054":1,"2057":2,"2062":1,"2069":1,"2074":1,"2077":2,"2082":8,"2085":3,"2086":2,"2087":1,"2095":1,"2096":3,"2097":1,"2098":2,"2099":1,"2107":2,"2110":2,"2111":2,"2115":1,"2120":2,"2122":1,"2123":1,"2128":1,"2132":3,"2133":2,"2134":2,"2135":1,"2136":1,"2141":2,"2145":3,"2148":1,"2153":1,"2155":1,"2157":2,"2159":2,"2165":2,"2168":4,"2170":1,"2177":1,"2178":1,"2179":3,"2183":2,"2195":3,"2196":2,"2198":2,"2199":2,"2201":2,"2202":2,"2205":1,"2212":1,"2218":2,"2219":4,"2220":1,"2224":3,"2226":1,"2229":2,"2230":1,"2231":6,"2233":2,"2238":2,"2242":1}}],["codeanalysistreatwarningsaserrors",{"2":{"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"981":1},"3":{"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"981":1}}],["codeanalysis",{"2":{"0":1,"135":1,"137":1,"138":1,"1229":1,"1270":1,"1323":1,"1414":1,"2099":1},"3":{"0":1,"135":2,"137":1,"138":1,"1229":1,"1270":2,"1299":3,"1323":3,"1395":1,"1414":1,"1435":1,"2099":1}}],["coached",{"3":{"2219":1}}],["coalesced",{"3":{"1285":1}}],["coalesces",{"3":{"1100":1,"1285":2,"1358":1,"1435":1}}],["coalesce",{"3":{"518":1,"1299":2,"1322":1,"1323":1,"1379":1,"1395":2,"1414":1}}],["coalescing",{"3":{"0":1,"519":1,"1229":1,"1237":1,"1285":1,"1310":1,"1322":2,"1323":3,"1349":1,"1414":2,"1435":1}}],["coarseness",{"3":{"2001":1}}],["coarselocationonly",{"3":{"1416":2}}],["coarse",{"2":{"426":1},"3":{"177":1,"178":2,"185":1,"283":1,"426":1,"572":1,"574":1,"872":1,"1177":1,"1299":1,"1349":2,"1395":1,"1401":1,"1414":1,"1416":7,"1430":1,"1435":3,"1536":1,"1590":1,"1595":1,"1596":1,"1875":1,"2000":3}}],["coarser",{"3":{"60":1,"784":1,"1310":1,"1416":1}}],["copilot",{"3":{"2212":1,"2218":1,"2219":2,"2223":1,"2224":1}}],["copied",{"3":{"0":1,"24":2,"27":1,"110":1,"216":1,"359":1,"450":1,"638":1,"640":1,"658":2,"698":1,"715":1,"750":1,"791":1,"836":1,"955":1,"972":3,"974":1,"1018":2,"1067":1,"1107":1,"1167":1,"1212":1,"1255":1,"1259":4,"1271":1,"1278":1,"1285":4,"1299":1,"1308":1,"1310":1,"1321":1,"1323":3,"1338":5,"1349":4,"1358":5,"1368":1,"1394":3,"1397":1,"1401":2,"1414":3,"1415":2,"1416":4,"1426":1,"1429":1,"1434":2,"1435":2,"1437":1,"1444":1,"1454":1,"1466":1,"1472":1,"1527":1,"1578":1,"1590":1,"1629":1,"1655":1,"1780":1,"1812":1,"1816":1,"1827":1,"1990":1,"2010":1,"2042":2,"2087":1,"2128":1,"2131":1,"2141":1,"2147":1}}],["copies",{"3":{"0":7,"94":2,"95":1,"145":1,"219":1,"325":1,"418":1,"497":1,"511":3,"600":1,"624":1,"633":1,"707":1,"708":1,"721":1,"729":1,"831":2,"832":2,"872":2,"880":1,"882":1,"884":1,"935":1,"954":1,"962":1,"1024":1,"1025":1,"1030":1,"1053":1,"1091":1,"1142":1,"1152":1,"1168":1,"1219":1,"1229":1,"1233":1,"1237":3,"1247":2,"1259":3,"1270":3,"1271":2,"1285":9,"1299":3,"1300":2,"1309":1,"1310":4,"1311":2,"1321":1,"1322":11,"1323":6,"1335":1,"1338":7,"1358":15,"1379":2,"1383":1,"1394":24,"1395":15,"1401":1,"1414":13,"1416":7,"1424":1,"1425":1,"1426":4,"1435":5,"1437":2,"1442":1,"1444":6,"1458":1,"1459":1,"1470":1,"1474":2,"1482":1,"1495":6,"1685":1,"1893":1,"1922":1,"1947":1,"2018":1,"2041":3,"2069":2,"2123":1,"2147":1,"2175":1,"2231":1}}],["cope",{"3":{"1358":3}}],["copes",{"3":{"1323":1,"1358":1}}],["copytext",{"3":{"1416":3}}],["copywifiasync",{"3":{"1394":1}}],["copyonly",{"2":{"635":1,"1728":1},"3":{"635":1,"1728":1}}],["copying",{"3":{"132":1,"698":1,"758":1,"906":1,"1170":1,"1229":2,"1251":1,"1285":3,"1299":2,"1310":1,"1322":2,"1338":2,"1349":2,"1358":4,"1395":1,"1398":1,"1401":1,"1412":1,"1414":2,"1434":1,"1435":3,"1444":2,"1475":1,"1576":1,"1780":1,"2031":1,"2189":1,"2202":1}}],["copy",{"1":{"1827":1,"1828":1,"1829":1,"1830":1,"1831":1,"1832":1,"1833":1},"2":{"563":1,"759":1,"868":1},"3":{"0":10,"122":1,"123":1,"215":1,"219":1,"220":1,"246":1,"290":1,"296":1,"318":1,"320":1,"325":1,"359":1,"365":1,"374":1,"390":1,"451":1,"460":1,"511":1,"556":1,"563":1,"566":1,"598":1,"601":2,"610":1,"611":1,"632":3,"633":2,"634":1,"640":1,"650":1,"674":2,"690":1,"707":1,"724":2,"725":1,"733":4,"735":2,"750":1,"757":1,"759":1,"782":1,"784":1,"790":1,"792":1,"813":1,"821":1,"826":1,"827":3,"829":2,"832":2,"834":1,"847":1,"854":1,"855":1,"856":1,"868":3,"869":1,"872":2,"926":1,"973":2,"1019":1,"1058":1,"1065":1,"1066":1,"1068":2,"1091":2,"1107":1,"1122":1,"1124":1,"1126":1,"1167":2,"1184":2,"1185":1,"1212":2,"1229":4,"1231":1,"1236":1,"1237":3,"1239":1,"1247":4,"1254":1,"1259":3,"1267":1,"1270":3,"1271":7,"1272":1,"1274":1,"1275":1,"1277":1,"1285":17,"1299":13,"1300":8,"1301":1,"1308":3,"1310":8,"1322":13,"1323":23,"1328":1,"1336":1,"1337":1,"1338":11,"1349":5,"1354":1,"1355":1,"1358":28,"1372":1,"1379":1,"1383":1,"1394":29,"1395":34,"1401":4,"1414":16,"1415":2,"1416":24,"1425":2,"1426":2,"1430":1,"1435":15,"1437":2,"1441":1,"1442":2,"1444":3,"1452":2,"1454":4,"1458":2,"1459":4,"1460":1,"1466":2,"1467":2,"1472":2,"1473":5,"1474":8,"1475":1,"1477":2,"1481":2,"1483":1,"1487":1,"1488":1,"1491":2,"1495":4,"1500":1,"1525":1,"1554":1,"1626":1,"1630":1,"1640":1,"1648":2,"1649":1,"1652":1,"1653":1,"1657":1,"1663":1,"1671":1,"1672":1,"1683":1,"1684":1,"1702":1,"1704":1,"1707":1,"1709":3,"1723":1,"1727":1,"1729":1,"1730":1,"1750":1,"1760":1,"1763":1,"1764":1,"1768":1,"1778":1,"1781":1,"1809":1,"1812":1,"1817":1,"1820":1,"1826":1,"1827":2,"1832":2,"1833":3,"1838":1,"1850":1,"1876":1,"1892":2,"1895":1,"1919":1,"1921":1,"1922":1,"1923":1,"1982":1,"1999":1,"2005":1,"2009":1,"2013":1,"2023":1,"2026":1,"2033":3,"2055":1,"2059":1,"2069":1,"2071":1,"2079":1,"2096":1,"2100":1,"2110":1,"2115":1,"2116":2,"2118":1,"2133":1,"2134":1,"2138":1,"2142":1,"2146":2,"2147":1,"2149":1,"2151":1,"2152":1,"2155":1,"2165":1,"2175":2,"2205":1,"2231":2,"2235":1}}],["co",{"3":{"0":1,"60":1,"265":1,"266":1,"1004":1,"1116":1,"1237":1,"1247":2,"1270":2,"1271":1,"1285":1,"1308":3,"1310":1,"1322":1,"1323":4,"1330":1,"1338":2,"1341":1,"1343":1,"1349":2,"1351":1,"1356":1,"1358":3,"1369":1,"1373":1,"1376":1,"1377":2,"1379":1,"1393":1,"1394":7,"1395":4,"1401":2,"1404":1,"1414":1,"1415":4,"1430":1,"1435":12,"1440":1,"1576":3,"1590":1,"1647":1,"1650":1,"1652":1,"1654":1,"1855":1,"1864":1,"1866":1,"2042":1,"2082":1,"2112":1}}],["cooperation",{"3":{"1285":1}}],["cooperative",{"3":{"1270":1}}],["cooperating",{"3":{"352":1,"1322":1,"1333":1,"1760":1,"1814":1,"1848":1,"1934":1,"1977":1}}],["cooperate",{"3":{"1238":1,"1259":1,"1349":1,"1414":1}}],["cooldown",{"3":{"344":1,"1638":2}}],["coordinating",{"3":{"1285":1,"2043":1}}],["coordination",{"3":{"0":1,"170":1,"810":1,"811":1,"1285":2,"1308":1,"1323":2,"1338":1,"1358":1,"1848":1}}],["coordinate",{"3":{"1259":1,"1358":1,"1416":4,"1435":2,"2051":1,"2106":1}}],["coordinates",{"3":{"255":1,"439":1,"809":1,"810":1,"883":1,"1285":5,"1322":2,"1323":1,"1339":1,"1349":1,"1416":8,"1458":1,"1466":1,"1471":1,"1922":1,"2123":2,"2124":1}}],["coordinated",{"3":{"30":1,"1011":1,"1272":1,"1299":1,"1300":1,"1308":1,"1322":1,"1323":2,"1435":2,"1525":1,"1568":1,"1571":1,"1575":1,"1576":1,"1893":1,"2237":1,"2239":1}}],["coordinator",{"3":{"0":3,"808":1,"809":3,"810":7,"811":1,"812":1,"813":1,"814":1,"815":2,"1147":1,"1212":1,"1278":2,"1285":2,"1345":1,"1395":10,"1437":1,"1848":1,"2167":2}}],["cookiename",{"3":{"1435":1}}],["cookieoptions",{"3":{"1299":1}}],["cookietokenreadertests",{"3":{"1199":1,"1207":2,"1299":1}}],["cookietokenreader",{"2":{"1297":1,"1967":1},"3":{"1199":9,"1203":1,"1207":2,"1297":5,"1299":9,"1310":3,"1323":5,"1967":1}}],["cookierequestcultureprovider",{"3":{"265":1,"1416":1}}],["cookiesessionrefreshmiddlewaretests",{"3":{"1199":1,"1207":3,"1299":2}}],["cookiesessionrefreshmiddlewareextensions",{"2":{"1297":1},"3":{"1199":2,"1203":1,"1207":1,"1297":1,"1299":2}}],["cookiesessionrefreshmiddleware",{"2":{"1297":1,"1967":1},"3":{"1199":3,"1203":1,"1207":3,"1208":1,"1297":4,"1299":8,"1581":1,"1967":1}}],["cookiesessionrefreshertests",{"3":{"1199":1,"1207":5,"1299":1}}],["cookiesessionrefresher",{"2":{"207":1,"1297":1,"1967":1},"3":{"207":1,"1183":1,"1184":1,"1185":1,"1199":4,"1203":1,"1207":5,"1286":1,"1297":17,"1299":22,"1310":3,"1581":1,"1967":1}}],["cookies",{"0":{"1297":1,"1967":1},"3":{"0":4,"207":3,"208":1,"209":1,"349":1,"351":1,"355":1,"507":1,"558":1,"664":1,"667":1,"670":1,"1108":1,"1109":1,"1183":1,"1184":5,"1185":1,"1212":2,"1297":2,"1299":17,"1310":1,"1323":11,"1335":2,"1338":4,"1435":2,"1437":1,"1441":1,"1466":1,"1510":1,"1525":1,"1576":1,"1659":1,"1967":4,"1968":2,"1969":1,"1970":1,"1971":2,"1973":1,"2231":2}}],["cookie",{"1":{"204":1,"205":1,"206":1,"207":1,"208":1,"209":1,"210":1,"1965":1,"1966":1,"1967":1,"1968":1,"1969":1,"1970":1,"1971":1,"2080":1,"2081":1,"2082":1,"2083":1,"2084":1,"2085":1},"3":{"0":11,"204":1,"207":6,"208":3,"209":5,"221":1,"265":11,"266":2,"269":2,"272":1,"273":9,"274":1,"277":2,"287":1,"350":4,"351":1,"352":1,"388":1,"458":2,"462":1,"506":2,"507":11,"508":4,"509":1,"510":1,"514":3,"555":2,"556":2,"557":1,"558":1,"560":1,"616":1,"618":1,"664":3,"665":3,"668":1,"774":1,"827":1,"1107":1,"1108":2,"1111":1,"1182":1,"1183":4,"1184":9,"1185":3,"1186":1,"1187":4,"1188":2,"1189":1,"1192":1,"1202":1,"1203":1,"1212":2,"1286":2,"1297":6,"1299":46,"1310":18,"1322":1,"1323":67,"1335":1,"1338":3,"1394":2,"1405":1,"1414":4,"1415":4,"1416":3,"1432":1,"1433":1,"1435":25,"1437":2,"1441":2,"1466":3,"1486":1,"1487":1,"1495":4,"1512":1,"1524":1,"1525":2,"1530":1,"1533":4,"1534":1,"1576":3,"1581":1,"1585":1,"1589":1,"1590":3,"1638":1,"1640":11,"1642":1,"1659":1,"1666":3,"1667":1,"1668":3,"1758":1,"1760":2,"1778":1,"1921":1,"1964":1,"1965":2,"1966":2,"1967":7,"1968":2,"1970":5,"1971":7,"1974":6,"1976":1,"1978":1,"2079":1,"2080":3,"2081":3,"2082":9,"2083":1,"2084":4,"2085":5,"2207":1,"2209":1,"2231":4}}],["cosmetic",{"3":{"202":1,"311":1,"926":1,"1123":1,"1241":1,"1247":1,"1271":1,"1285":3,"1309":1,"1322":1,"1323":1,"1326":1,"1338":1,"1346":1,"1358":3,"1383":1,"1390":1,"1394":2,"1395":2,"1414":1,"1416":1,"1435":2,"1495":1,"1878":1,"1925":1,"2119":1}}],["cosmosindexedentity",{"3":{"1199":3,"1207":1}}],["cosmosintidvaluegeneratortests",{"3":{"1199":1,"1207":2,"1285":2}}],["cosmosintidvaluegenerator",{"2":{"166":1,"1663":1,"1855":1},"3":{"166":2,"1199":3,"1203":1,"1207":2,"1281":2,"1285":12,"1663":1,"1855":1}}],["cosmosdatasourceengine",{"3":{"1175":2,"1177":1}}],["cosmosdatabasenameof",{"3":{"1285":1}}],["cosmosdatabasename",{"3":{"166":1,"1285":12,"1338":1,"1440":1}}],["cosmosdb",{"2":{"170":1,"889":1,"1174":1,"1855":1,"1856":1},"3":{"166":1,"170":1,"889":1,"1034":1,"1174":1,"1213":1,"1285":17,"1322":1,"1855":1,"1856":1}}],["cosmosdbcontext",{"2":{"26":1,"47":1,"200":1,"343":1,"698":1,"701":1,"720":1,"1509":1,"1856":1,"1860":1},"3":{"26":1,"47":1,"166":3,"171":1,"200":2,"343":2,"345":1,"698":1,"701":1,"715":2,"720":1,"988":1,"1042":1,"1199":3,"1203":1,"1207":2,"1212":2,"1272":1,"1273":2,"1274":2,"1285":16,"1338":1,"1509":1,"1663":1,"1848":1,"1856":1,"1860":1}}],["cosmosconnectionstring",{"3":{"166":1,"1285":4,"1440":1}}],["cosmosconfigurationportabilitytests",{"2":{"164":1},"3":{"164":1,"1198":1,"1199":2,"1207":7,"1285":4,"1495":3}}],["cosmos",{"3":{"0":12,"10":3,"26":1,"45":1,"164":1,"166":7,"168":4,"169":1,"170":1,"171":2,"195":1,"200":2,"290":1,"296":1,"343":2,"345":1,"715":1,"717":1,"721":1,"889":1,"890":1,"891":2,"905":1,"931":1,"988":2,"990":2,"1004":1,"1033":2,"1036":1,"1042":1,"1045":1,"1050":1,"1083":2,"1085":1,"1133":1,"1134":1,"1135":1,"1144":1,"1175":3,"1176":1,"1177":2,"1179":1,"1212":8,"1213":2,"1237":1,"1239":1,"1247":6,"1252":1,"1259":4,"1273":1,"1274":1,"1279":4,"1280":5,"1281":4,"1285":95,"1299":1,"1309":1,"1310":4,"1316":1,"1319":1,"1320":1,"1322":4,"1325":1,"1338":2,"1358":1,"1360":1,"1368":2,"1379":1,"1395":2,"1414":1,"1435":4,"1437":2,"1440":4,"1488":1,"1495":1,"1499":3,"1509":1,"1525":2,"1576":1,"1584":2,"1751":1,"1762":1,"1773":1,"1814":1,"1839":1,"1848":1,"1852":1,"1853":4,"1854":1,"1855":3,"1856":1,"1857":5,"1858":3,"1859":4,"1860":5,"1861":1,"1863":1,"1875":1,"2063":1,"2212":1,"2218":2,"2219":1,"2229":1,"2231":1}}],["costcenter",{"3":{"1576":1,"1590":1}}],["costly",{"3":{"1435":1}}],["costtagconventiontests",{"3":{"1199":1,"1207":2,"1435":4,"1466":2,"1524":1,"1525":2,"1534":1,"1590":1}}],["costing",{"3":{"765":1,"856":1,"1299":1,"1322":1,"1323":1,"1395":1,"1437":1}}],["costs",{"0":{"1332":1},"3":{"0":23,"1":1,"24":2,"26":1,"41":1,"92":1,"95":1,"108":1,"111":1,"135":2,"136":1,"160":1,"195":1,"196":1,"233":1,"234":1,"237":2,"255":1,"267":2,"350":1,"360":2,"400":1,"401":1,"402":1,"435":2,"442":1,"448":1,"459":3,"461":1,"483":1,"529":1,"549":1,"551":1,"565":1,"619":1,"628":1,"635":1,"640":1,"659":1,"681":1,"683":1,"690":1,"691":1,"715":1,"726":2,"733":1,"735":1,"747":1,"751":2,"759":1,"767":2,"768":1,"775":1,"791":1,"792":1,"797":2,"827":2,"830":1,"853":1,"856":1,"872":1,"904":1,"906":2,"923":1,"931":1,"932":2,"946":1,"965":2,"982":1,"998":1,"1005":1,"1019":2,"1020":1,"1027":1,"1051":1,"1099":1,"1109":1,"1117":1,"1143":1,"1153":1,"1228":2,"1229":2,"1246":1,"1247":6,"1259":5,"1270":4,"1271":1,"1278":1,"1285":12,"1293":1,"1299":10,"1300":1,"1306":2,"1308":1,"1309":3,"1310":5,"1322":8,"1323":11,"1331":1,"1338":11,"1358":20,"1368":1,"1379":3,"1384":1,"1394":13,"1395":12,"1401":3,"1414":6,"1416":4,"1421":1,"1434":2,"1435":5,"1437":1,"1439":1,"1442":1,"1454":7,"1456":2,"1464":1,"1466":2,"1473":1,"1489":1,"1491":1,"1525":1,"1533":1,"1552":1,"1630":1,"1650":1,"1651":1,"1662":1,"1671":1,"1716":1,"1720":1,"1723":1,"1749":1,"1764":1,"1772":1,"1789":1,"1791":1,"1810":1,"1838":1,"1841":1,"1842":1,"1848":1,"1850":1,"1857":1,"1875":1,"1901":1,"1922":2,"1941":1,"1970":1,"1971":2,"1976":1,"1977":1,"1978":1,"1983":1,"1988":1,"1989":1,"1999":1,"2006":1,"2012":1,"2043":1,"2065":1,"2086":1,"2149":1,"2155":1,"2157":1,"2165":1,"2168":1,"2170":1,"2173":1,"2180":1,"2192":1,"2193":1,"2202":1,"2231":1,"2238":1}}],["cost",{"0":{"1228":1,"1424":1,"1456":1,"1567":1,"1676":1,"1678":1,"2046":1},"1":{"763":1,"764":1,"765":1,"766":1,"767":1,"768":1,"769":1,"770":1,"1675":1,"1676":1,"1677":1,"1678":1,"1679":1},"2":{"1466":1},"3":{"0":14,"1":1,"20":1,"26":1,"48":1,"58":1,"68":1,"86":1,"102":2,"111":1,"125":2,"137":2,"138":1,"139":1,"140":1,"150":1,"151":1,"160":1,"167":1,"168":2,"178":1,"200":1,"216":1,"235":1,"237":1,"245":2,"246":1,"267":2,"274":1,"279":1,"283":1,"284":1,"291":1,"305":1,"312":1,"331":1,"351":1,"353":1,"360":1,"372":1,"395":6,"396":2,"397":5,"398":2,"399":4,"400":1,"402":1,"403":2,"404":3,"405":1,"406":2,"407":1,"408":1,"409":2,"425":1,"426":1,"460":1,"469":1,"471":1,"472":1,"481":1,"511":1,"535":1,"541":1,"549":1,"551":1,"556":1,"566":1,"573":1,"590":1,"592":3,"593":2,"595":4,"601":1,"608":1,"609":1,"614":1,"619":1,"620":1,"625":3,"626":1,"627":2,"628":1,"635":1,"641":1,"650":1,"656":1,"658":1,"676":1,"684":1,"690":1,"691":1,"707":1,"708":2,"716":2,"725":1,"727":1,"732":1,"733":3,"742":1,"756":1,"763":1,"764":3,"765":2,"766":38,"768":11,"769":4,"798":1,"799":2,"800":1,"801":2,"811":1,"812":2,"820":2,"827":2,"828":1,"829":1,"834":1,"848":1,"855":1,"861":1,"862":1,"863":1,"865":2,"871":1,"882":1,"883":1,"891":1,"899":1,"907":1,"915":2,"917":1,"918":1,"926":1,"931":1,"933":1,"940":1,"948":2,"968":1,"971":1,"973":1,"974":1,"979":1,"981":1,"982":1,"989":1,"998":1,"1008":1,"1011":2,"1012":1,"1013":1,"1017":1,"1018":2,"1019":2,"1020":1,"1025":1,"1028":1,"1036":1,"1043":1,"1044":1,"1051":1,"1061":1,"1068":1,"1069":1,"1074":1,"1075":2,"1076":1,"1077":2,"1085":1,"1093":1,"1101":2,"1116":1,"1117":1,"1119":2,"1126":2,"1147":1,"1152":2,"1153":1,"1154":2,"1162":1,"1170":1,"1178":1,"1192":1,"1212":3,"1216":1,"1232":2,"1237":3,"1247":8,"1254":1,"1255":1,"1256":1,"1259":19,"1263":1,"1270":9,"1275":1,"1282":1,"1285":21,"1299":10,"1300":3,"1308":3,"1309":3,"1310":10,"1311":1,"1322":14,"1323":15,"1326":1,"1328":1,"1332":3,"1333":1,"1336":2,"1337":1,"1338":32,"1341":1,"1348":1,"1349":9,"1358":19,"1365":1,"1366":1,"1368":1,"1375":1,"1379":5,"1391":1,"1394":11,"1395":14,"1400":1,"1401":9,"1412":1,"1414":6,"1416":2,"1417":1,"1420":2,"1421":1,"1424":2,"1426":8,"1429":1,"1434":1,"1435":9,"1437":5,"1441":1,"1442":5,"1445":1,"1446":2,"1449":1,"1450":1,"1451":1,"1453":1,"1454":12,"1455":1,"1456":17,"1458":3,"1459":5,"1460":5,"1462":1,"1464":5,"1466":29,"1468":2,"1474":2,"1475":7,"1477":3,"1482":1,"1485":2,"1488":2,"1489":1,"1491":2,"1495":2,"1498":1,"1522":1,"1524":3,"1525":15,"1529":1,"1530":2,"1531":2,"1533":4,"1534":2,"1549":2,"1552":2,"1553":1,"1567":5,"1570":1,"1571":1,"1573":1,"1574":8,"1576":17,"1577":1,"1580":1,"1582":1,"1583":2,"1584":9,"1589":1,"1590":8,"1596":2,"1598":4,"1599":3,"1601":1,"1637":1,"1640":2,"1659":1,"1667":2,"1669":2,"1673":2,"1675":5,"1676":2,"1677":1,"1678":5,"1679":1,"1696":1,"1707":1,"1795":1,"1798":2,"1803":1,"1805":1,"1808":1,"1820":1,"1822":1,"1825":1,"1838":2,"1863":1,"1866":1,"1875":1,"1884":3,"1895":1,"1908":2,"1909":1,"1922":1,"1924":1,"1931":1,"1932":1,"1939":2,"1957":2,"1983":1,"1986":1,"1988":1,"2000":3,"2005":1,"2010":1,"2013":1,"2023":1,"2031":1,"2034":1,"2045":1,"2046":1,"2047":2,"2048":1,"2053":1,"2054":1,"2055":1,"2056":1,"2058":2,"2059":1,"2066":2,"2074":1,"2085":1,"2086":1,"2111":1,"2113":2,"2125":1,"2126":1,"2134":1,"2143":1,"2147":2,"2151":1,"2153":1,"2154":1,"2155":1,"2156":1,"2158":4,"2159":1,"2161":2,"2169":2,"2170":1,"2171":1,"2173":1,"2176":1,"2178":1,"2188":1,"2191":1,"2202":1,"2219":1,"2231":2,"2239":1}}],["col",{"3":{"1175":5,"1525":1}}],["colocating",{"3":{"1349":1}}],["colon",{"3":{"564":1,"1323":2,"1338":2,"1358":1,"1383":1,"1394":1,"1441":1}}],["coloured",{"3":{"1435":1}}],["colour",{"3":{"0":1,"756":1,"905":1,"910":1,"1323":1,"1395":1,"1435":1}}],["colored",{"3":{"2074":1}}],["colors",{"3":{"1323":4,"1384":1,"1394":2,"1415":1,"1479":2,"1576":1,"1581":1,"2074":2}}],["color",{"3":{"0":1,"273":2,"1310":1,"1323":15,"1384":3,"1394":11,"1435":1,"1488":1,"1557":2,"1590":1,"1599":1,"1643":1,"1668":1,"1670":1,"2074":3,"2078":1,"2080":1,"2082":1,"2085":1,"2106":1}}],["colder",{"3":{"1416":3}}],["cold",{"0":{"1333":1},"1":{"232":1,"233":1,"234":1,"235":1,"236":1,"237":1,"238":1,"239":1},"3":{"0":2,"25":1,"31":1,"33":1,"100":1,"232":1,"234":1,"236":4,"239":1,"246":1,"390":1,"413":1,"556":2,"559":1,"717":1,"733":1,"735":1,"762":1,"867":1,"869":1,"875":1,"881":1,"1059":1,"1067":1,"1069":1,"1270":1,"1285":1,"1286":1,"1287":1,"1297":2,"1299":2,"1300":1,"1303":1,"1308":1,"1323":6,"1338":8,"1358":1,"1378":1,"1379":2,"1392":1,"1394":2,"1395":3,"1414":3,"1415":8,"1416":11,"1429":1,"1435":2,"1437":1,"1440":1,"1442":3,"1444":1,"1448":2,"1449":1,"1455":2,"1466":3,"1489":3,"1533":1,"1576":1,"1667":1,"1668":1,"1706":1,"1799":1,"1933":1,"2006":3,"2120":1,"2198":1,"2202":2}}],["colleague",{"3":{"1116":1,"1341":1,"1349":1,"1356":1,"1358":1}}],["collectively",{"3":{"1460":1}}],["collecting",{"3":{"1247":1,"1253":1,"1271":1,"1338":2,"1358":1,"1434":1,"1435":8,"1454":1}}],["collectionincludecache",{"3":{"1285":1}}],["collectiondefinition",{"3":{"640":1,"1338":2,"1435":4,"1437":1,"1487":1}}],["collectionresulttests",{"3":{"1199":1,"1207":2}}],["collectionresult",{"2":{"1217":1,"1227":1,"1228":1,"1804":1},"3":{"549":1,"1199":34,"1203":1,"1207":1,"1217":1,"1227":1,"1228":1,"1229":10,"1310":4,"1323":3,"1370":1,"1379":3,"1381":1,"1394":2,"1804":1,"1987":1}}],["collections",{"2":{"1229":1},"3":{"0":1,"545":1,"551":1,"926":1,"1050":1,"1115":1,"1117":1,"1212":1,"1229":2,"1237":4,"1243":2,"1247":19,"1259":2,"1265":2,"1270":2,"1279":1,"1285":10,"1299":4,"1308":1,"1309":5,"1310":2,"1311":1,"1322":1,"1323":5,"1338":2,"1342":5,"1345":1,"1349":20,"1351":1,"1352":1,"1358":35,"1394":15,"1395":3,"1401":4,"1414":4,"1435":3,"1525":1,"1638":2,"1862":1,"1864":1,"2232":1}}],["collection",{"2":{"1952":1},"3":{"0":3,"10":1,"11":1,"81":1,"122":1,"265":1,"317":1,"318":4,"319":1,"320":1,"321":1,"324":1,"544":1,"545":1,"548":1,"551":1,"572":1,"583":2,"598":1,"651":1,"676":1,"741":1,"743":2,"921":1,"926":2,"1067":2,"1107":1,"1108":1,"1116":1,"1117":1,"1168":2,"1212":1,"1217":1,"1221":2,"1229":6,"1230":1,"1231":4,"1234":1,"1237":13,"1240":1,"1243":2,"1244":2,"1247":6,"1259":1,"1260":1,"1262":1,"1265":4,"1270":9,"1271":1,"1276":2,"1278":1,"1281":1,"1285":26,"1296":1,"1299":10,"1308":9,"1309":17,"1310":24,"1311":1,"1315":1,"1319":2,"1322":13,"1323":13,"1327":3,"1336":1,"1338":22,"1341":2,"1342":2,"1349":61,"1352":1,"1358":126,"1361":1,"1368":10,"1379":10,"1394":18,"1395":30,"1401":15,"1414":13,"1416":6,"1420":1,"1427":1,"1428":2,"1429":3,"1432":1,"1435":73,"1436":1,"1437":5,"1448":1,"1452":2,"1454":2,"1464":1,"1466":1,"1485":1,"1487":6,"1490":1,"1495":1,"1576":2,"1590":1,"1610":1,"1628":1,"1630":2,"1631":1,"1634":1,"1637":2,"1650":3,"1661":2,"1665":1,"1676":1,"1682":1,"1683":1,"1685":4,"1700":3,"1718":1,"1726":2,"1727":2,"1728":1,"1729":3,"1746":1,"1747":2,"1748":1,"1754":1,"1760":1,"1763":2,"1764":2,"1775":1,"1804":1,"1809":1,"1814":1,"1823":1,"1825":2,"1829":2,"1855":1,"1864":1,"1882":2,"1921":1,"1952":2,"1974":1,"1987":3,"1993":3,"1994":2,"1996":2,"2054":1,"2055":3,"2089":1,"2090":1,"2231":1,"2232":1}}],["collectasync",{"3":{"861":1,"1432":1,"1435":3}}],["collects",{"3":{"760":1,"861":1,"1091":1,"1161":1,"1229":1,"1247":1,"1259":3,"1285":4,"1299":2,"1300":1,"1309":2,"1310":1,"1322":1,"1323":1,"1338":1,"1349":1,"1358":5,"1368":1,"1394":6,"1395":2,"1401":1,"1414":1,"1426":1,"1430":1,"1434":1,"1435":6,"1437":1,"1453":1,"1454":1,"1455":1,"1487":2,"1491":2,"1638":2,"1843":1,"1864":1,"2042":1,"2057":1,"2167":1}}],["collect",{"3":{"539":1,"1270":1,"1309":1,"1338":2,"1358":1,"1396":1,"1414":1,"1430":1,"1435":4,"1454":1,"1455":2,"1488":1,"1489":1,"1769":1,"1867":1,"1974":1,"2148":1}}],["collected",{"3":{"352":1,"538":1,"725":1,"792":1,"972":1,"1218":1,"1270":1,"1271":1,"1285":3,"1299":2,"1314":1,"1322":3,"1323":1,"1338":3,"1349":2,"1358":2,"1394":1,"1395":1,"1435":7,"1455":1,"1566":1,"1768":1,"1803":1,"1977":1,"2140":1}}],["collector",{"3":{"0":1,"861":3,"1435":6,"1487":1,"1496":1,"1670":1}}],["colliding",{"3":{"312":1,"610":1,"1285":1,"1310":2,"1311":1,"1322":1,"1323":2,"1338":1,"1349":1,"1358":4,"1401":1,"1435":4}}],["collided",{"3":{"1400":1}}],["collides",{"3":{"295":1,"1338":1,"1395":2,"1435":2,"1440":1,"1495":1}}],["collide",{"3":{"99":1,"246":1,"996":1,"998":1,"1285":1,"1296":1,"1299":1,"1310":1,"1322":4,"1323":1,"1330":1,"1338":1,"1349":3,"1351":1,"1358":4,"1361":1,"1363":1,"1368":3,"1379":2,"1394":1,"1395":4,"1401":2,"1414":1,"1428":1,"1435":4,"1437":2,"1473":1,"1477":1,"1638":1,"1683":1,"1730":1,"1919":1,"1976":1,"2096":1}}],["collisions",{"3":{"1267":1,"1270":1,"1299":1,"1300":2,"1358":1,"1394":1,"1487":1,"1638":2}}],["collision",{"3":{"0":1,"157":1,"340":1,"342":1,"733":1,"891":1,"905":1,"999":1,"1191":1,"1270":2,"1282":1,"1285":5,"1299":2,"1322":1,"1349":1,"1358":10,"1368":1,"1394":2,"1395":1,"1401":1,"1415":1,"1416":1,"1435":16,"1474":1,"1477":1,"1495":2,"1868":1,"1875":1,"1907":1}}],["collateral",{"3":{"2023":1}}],["collation",{"3":{"1349":1,"1358":5}}],["collaborate",{"3":{"1358":1,"1395":1,"1414":1,"1435":1}}],["collaborates",{"3":{"1354":1}}],["collaborations",{"3":{"1377":1}}],["collaboration",{"3":{"1308":1,"1435":1,"2218":1}}],["collaborating",{"3":{"1285":1,"1522":1,"1587":1}}],["collaborators",{"0":{"1384":1},"3":{"1057":1,"1270":1,"1285":2,"1299":1,"1310":1,"1322":3,"1323":1,"1338":1,"1358":7,"1380":1,"1384":1,"1394":5,"1395":1,"1401":1,"1435":11,"1437":2,"1488":1}}],["collaborator",{"3":{"0":1,"534":1,"539":1,"1025":1,"1060":1,"1285":2,"1292":1,"1322":2,"1358":1,"1394":4,"1395":4,"1435":14,"2195":1,"2201":1,"2202":1}}],["collapsible",{"3":{"649":1,"1323":2,"1394":1}}],["collapsing",{"3":{"0":1,"63":1,"283":1,"386":1,"675":1,"783":1,"964":1,"1124":1,"1220":1,"1229":1,"1247":1,"1259":1,"1270":1,"1279":2,"1285":4,"1299":7,"1310":3,"1322":1,"1323":7,"1336":1,"1338":3,"1358":7,"1379":1,"1394":3,"1395":1,"1401":2,"1415":1,"1416":3,"1435":4,"1533":1,"1669":1,"1804":1,"2023":1,"2166":1}}],["collapsed",{"3":{"60":1,"791":1,"882":1,"1204":1,"1285":5,"1294":1,"1297":1,"1299":1,"1310":1,"1323":1,"1349":1,"1358":2,"1394":3,"1395":1,"1435":1,"1495":1,"1533":1,"2025":1}}],["collapse",{"3":{"0":2,"111":1,"137":1,"164":1,"166":3,"170":1,"179":1,"333":1,"507":1,"680":1,"693":1,"881":1,"945":1,"996":2,"1247":1,"1259":1,"1270":2,"1279":3,"1284":1,"1285":16,"1291":1,"1299":9,"1300":1,"1321":1,"1322":1,"1323":9,"1338":4,"1358":2,"1394":5,"1395":6,"1414":1,"1416":7,"1428":1,"1435":8,"1437":1,"1442":1,"1446":2,"1466":1,"1488":1,"1489":1,"1530":1,"1533":2,"1651":2,"1663":2,"1711":1,"1789":1,"1848":3,"1851":1,"1852":1,"1980":1,"2023":1,"2052":1}}],["collapses",{"3":{"0":2,"47":1,"459":1,"490":1,"513":1,"592":1,"690":1,"790":1,"827":1,"854":1,"905":1,"996":1,"1116":1,"1212":1,"1225":1,"1237":1,"1247":1,"1259":1,"1270":3,"1271":1,"1280":1,"1284":1,"1285":10,"1293":2,"1299":2,"1308":2,"1310":5,"1321":1,"1322":3,"1323":2,"1325":1,"1337":1,"1338":5,"1349":3,"1358":7,"1394":6,"1395":11,"1414":4,"1416":2,"1435":4,"1437":1,"1440":1,"1454":1,"1533":1,"1630":1,"1640":2,"1651":1,"1661":1,"1666":1,"1683":1,"1764":1,"1789":1,"1846":1,"1848":3,"1857":1,"1869":1,"1908":1,"1921":1,"2028":1,"2039":1,"2046":1,"2047":1,"2165":1}}],["columnprefix",{"3":{"1285":3}}],["columnname",{"3":{"1285":1}}],["columnwidth",{"3":{"1199":4,"1203":1,"1207":2,"1277":2,"1285":5,"1495":1}}],["column",{"0":{"2143":1},"3":{"0":11,"26":2,"275":1,"311":1,"330":1,"333":2,"337":2,"341":2,"343":1,"358":1,"359":4,"360":3,"361":4,"363":2,"364":1,"365":1,"499":1,"501":1,"549":3,"564":1,"566":2,"655":1,"656":1,"657":7,"659":1,"690":1,"691":1,"696":1,"697":1,"698":2,"703":4,"717":1,"721":1,"741":2,"743":1,"744":1,"766":1,"809":2,"811":1,"812":1,"881":2,"889":5,"890":1,"892":1,"903":1,"904":1,"905":1,"906":1,"907":1,"910":2,"964":4,"965":1,"966":1,"1018":3,"1020":2,"1033":1,"1035":1,"1050":5,"1051":2,"1052":3,"1053":1,"1058":1,"1085":1,"1135":1,"1175":4,"1228":1,"1229":3,"1234":1,"1237":8,"1239":1,"1241":2,"1242":4,"1243":1,"1244":3,"1247":24,"1259":2,"1260":1,"1270":1,"1271":3,"1276":3,"1277":1,"1278":1,"1280":4,"1281":1,"1282":1,"1283":2,"1285":119,"1289":2,"1290":1,"1296":1,"1299":9,"1308":6,"1309":1,"1310":13,"1317":1,"1322":7,"1323":10,"1341":3,"1343":1,"1349":24,"1353":1,"1358":65,"1361":7,"1368":34,"1374":1,"1379":16,"1382":2,"1391":1,"1394":26,"1395":27,"1397":1,"1401":2,"1404":1,"1407":1,"1413":1,"1414":29,"1435":12,"1437":4,"1444":1,"1454":2,"1459":1,"1488":1,"1491":1,"1495":3,"1525":1,"1576":2,"1590":3,"1599":1,"1629":2,"1630":1,"1638":4,"1639":3,"1640":1,"1662":1,"1671":1,"1674":1,"1682":1,"1685":1,"1712":1,"1722":1,"1726":4,"1748":1,"1749":2,"1766":1,"1768":1,"1809":1,"1814":1,"1848":1,"1850":2,"1855":1,"1857":1,"1861":1,"1874":1,"1875":1,"1876":1,"1902":1,"1932":1,"1933":1,"1939":1,"1985":1,"1989":1,"1990":1,"2069":2,"2080":2,"2082":1,"2083":1,"2084":2,"2085":1,"2139":2,"2140":3,"2141":3,"2142":3,"2143":4,"2144":4,"2145":6,"2185":1,"2193":1,"2231":2}}],["columns",{"1":{"2139":1,"2140":1,"2141":1,"2142":1,"2143":1,"2144":1,"2145":1},"3":{"0":6,"26":3,"41":1,"166":1,"313":1,"328":1,"330":1,"341":1,"348":1,"350":3,"352":1,"358":3,"359":1,"360":1,"361":1,"549":1,"564":3,"657":2,"658":1,"660":1,"715":1,"717":1,"741":3,"742":2,"799":1,"802":1,"853":2,"891":1,"905":1,"907":1,"950":1,"1024":2,"1026":3,"1030":1,"1043":1,"1060":1,"1061":1,"1074":1,"1075":1,"1116":1,"1133":1,"1175":1,"1212":2,"1234":1,"1237":3,"1239":1,"1241":2,"1242":1,"1243":1,"1244":1,"1245":1,"1247":7,"1251":1,"1253":2,"1259":6,"1269":1,"1270":2,"1277":1,"1280":2,"1285":25,"1299":4,"1308":4,"1310":6,"1322":3,"1323":2,"1349":7,"1358":8,"1361":1,"1362":1,"1368":14,"1379":1,"1394":8,"1395":16,"1400":1,"1401":1,"1405":1,"1406":1,"1411":1,"1414":19,"1435":2,"1437":2,"1466":1,"1523":1,"1525":1,"1530":1,"1590":1,"1606":1,"1640":1,"1655":1,"1663":2,"1665":1,"1670":1,"1674":1,"1684":2,"1685":1,"1740":1,"1809":1,"1814":1,"1855":1,"1865":1,"1902":1,"1976":1,"1977":2,"1987":1,"2071":1,"2079":1,"2084":1,"2138":1,"2139":2,"2140":1,"2142":1,"2144":4,"2145":2,"2152":1,"2185":1,"2191":1,"2197":1,"2207":2,"2231":1}}],["courtesy",{"3":{"1388":1,"1395":1}}],["courses",{"3":{"2224":1}}],["course",{"3":{"1322":1}}],["cousin",{"3":{"1230":1,"1270":1,"1358":1}}],["coupon",{"2":{"2086":1,"2089":1,"2092":1},"3":{"0":1,"1028":1,"2086":1,"2089":1,"2092":1}}],["couple",{"3":{"92":1,"1211":1,"1299":2,"1311":1,"1322":1,"1358":2,"1395":1,"1435":3,"1688":1,"1780":1,"1867":1,"1902":1,"1964":1,"2015":1}}],["couples",{"3":{"0":1,"116":1,"185":1,"544":1,"1299":1,"1435":1,"1539":1,"1545":1,"1862":1,"1893":1,"1959":1,"1990":1}}],["coupled",{"3":{"0":2,"265":1,"329":1,"545":1,"547":1,"585":1,"1069":1,"1077":1,"1271":1,"1288":1,"1299":1,"1323":1,"1358":2,"1435":7,"1488":1,"1814":1,"1862":1,"1931":1,"1960":1,"1989":1,"2026":1,"2027":1,"2078":1,"2231":1}}],["couplings",{"3":{"1788":1}}],["coupling",{"3":{"0":1,"33":1,"52":1,"102":1,"135":1,"159":1,"226":1,"255":1,"458":1,"547":1,"549":1,"628":1,"827":1,"924":1,"1011":1,"1012":1,"1202":1,"1203":1,"1229":1,"1236":1,"1237":2,"1259":3,"1271":1,"1299":1,"1308":1,"1310":3,"1312":1,"1322":2,"1335":1,"1338":2,"1349":4,"1358":3,"1390":1,"1395":3,"1414":1,"1415":4,"1430":1,"1435":16,"1441":1,"1454":1,"1495":1,"1525":1,"1533":1,"1541":2,"1544":1,"1570":3,"1590":1,"1637":1,"1660":1,"1726":1,"1791":1,"1799":1,"1815":1,"1828":1,"1846":1,"1847":1,"1853":1,"1863":1,"1950":1,"1951":2}}],["could",{"3":{"0":4,"19":1,"20":1,"21":2,"22":1,"72":1,"109":1,"111":1,"120":1,"135":1,"142":1,"170":1,"188":1,"213":1,"228":1,"235":1,"245":1,"246":1,"248":1,"254":1,"256":1,"265":2,"266":1,"318":1,"335":1,"391":1,"419":1,"448":1,"451":1,"490":1,"528":1,"529":1,"536":3,"537":1,"538":1,"539":1,"558":1,"612":1,"627":2,"664":1,"674":1,"696":1,"725":2,"749":1,"760":1,"800":1,"801":2,"826":2,"827":1,"833":1,"838":1,"853":1,"889":1,"897":1,"898":1,"905":3,"907":1,"915":1,"922":1,"926":1,"939":1,"945":2,"947":1,"956":1,"971":1,"979":1,"988":1,"1017":1,"1018":1,"1019":2,"1026":1,"1059":1,"1067":1,"1100":1,"1116":1,"1128":1,"1174":1,"1219":1,"1222":1,"1223":1,"1229":3,"1233":1,"1237":4,"1242":1,"1247":3,"1252":1,"1253":1,"1259":3,"1265":1,"1270":2,"1271":1,"1274":1,"1285":16,"1291":1,"1298":1,"1299":16,"1300":3,"1303":1,"1308":4,"1310":12,"1322":15,"1323":16,"1331":1,"1338":8,"1349":2,"1356":1,"1358":28,"1368":2,"1379":3,"1383":1,"1389":1,"1394":9,"1395":31,"1401":6,"1408":1,"1414":17,"1416":5,"1435":23,"1437":2,"1439":1,"1445":1,"1453":3,"1454":1,"1466":4,"1472":1,"1473":1,"1474":1,"1487":2,"1491":1,"1494":1,"1495":1,"1545":1,"1595":1,"1637":2,"1638":3,"1640":1,"1684":1,"1688":1,"1698":1,"1726":1,"1754":1,"1764":1,"1779":1,"1805":1,"1806":2,"1821":1,"1823":1,"1880":1,"1897":1,"1917":1,"1923":1,"1969":1,"1994":1,"2011":1,"2061":1,"2064":1,"2072":1,"2084":1,"2088":1,"2123":1,"2128":2,"2130":1,"2160":1,"2162":1,"2163":1,"2165":1,"2166":1,"2167":1,"2168":1,"2199":1}}],["county",{"3":{"2219":2}}],["countoccurrences",{"3":{"1435":1}}],["countinvalid",{"3":{"1401":1}}],["countingqueryableexecutor",{"3":{"1199":3,"1207":1}}],["countingsweep",{"3":{"1199":2,"1207":1,"1322":1}}],["countingfailurehandler",{"3":{"1199":2,"1207":1}}],["countingcheck",{"3":{"1199":2,"1207":1}}],["counting",{"3":{"0":3,"27":1,"125":1,"280":1,"284":1,"476":1,"477":1,"593":1,"631":1,"782":1,"799":2,"803":2,"804":1,"805":1,"1004":1,"1005":1,"1018":1,"1240":1,"1245":1,"1247":3,"1256":1,"1259":1,"1270":3,"1278":1,"1285":2,"1299":1,"1306":1,"1308":2,"1310":2,"1323":3,"1338":1,"1349":3,"1353":1,"1354":1,"1358":4,"1394":1,"1395":6,"1401":1,"1414":1,"1435":6,"1446":1,"2027":1,"2042":1,"2047":1,"2105":1}}],["countcategoryitems",{"3":{"1358":4}}],["countries",{"3":{"1323":1}}],["countrymaxlength",{"3":{"1237":1,"1271":1}}],["country",{"3":{"1237":1,"1271":4,"1285":1,"1323":1,"1426":1,"1763":1}}],["countryrules",{"3":{"1199":3,"1203":1,"1207":1,"1271":5,"1828":1}}],["countunpaginatedasync",{"2":{"1243":1},"3":{"1243":1,"1247":2}}],["countable",{"3":{"898":1,"1395":3,"1398":1}}],["countasync",{"2":{"546":1},"3":{"0":1,"545":1,"546":1,"549":1,"550":1,"552":1,"1247":1,"1285":16,"1308":3,"1395":5,"1401":2,"1414":1,"1435":3,"1814":5}}],["countdown",{"3":{"556":1,"1349":1,"1392":2,"1394":7,"1415":1,"1437":1,"1736":1}}],["countbyasync",{"3":{"543":1,"545":1,"551":1,"1285":10,"1358":2,"1395":3,"1814":1}}],["countpendingasync",{"2":{"24":1,"409":1,"2156":1,"2190":1},"3":{"24":1,"397":1,"409":1,"1259":3,"1285":4,"1322":1,"2156":1,"2190":1}}],["counterexample",{"3":{"1435":1}}],["counterreadoptions",{"3":{"1300":2}}],["countermeasure",{"3":{"1299":1}}],["counterweights",{"3":{"1118":1}}],["counterweight",{"3":{"530":1,"974":1,"1358":1,"1395":1,"1466":1,"2180":1}}],["counterparts",{"3":{"982":1,"1229":1,"1300":1,"1311":1,"1319":1,"1338":1,"1349":1,"1358":2,"1391":1,"1401":1,"1414":1,"1495":1}}],["counterpart",{"3":{"0":2,"143":1,"230":1,"265":1,"313":1,"362":2,"514":1,"543":1,"550":1,"591":1,"595":1,"707":1,"748":1,"749":1,"766":1,"842":1,"950":1,"980":1,"1212":1,"1229":2,"1237":2,"1259":3,"1269":1,"1270":3,"1271":1,"1275":1,"1285":13,"1299":3,"1305":1,"1308":5,"1310":4,"1311":2,"1322":3,"1323":9,"1338":3,"1341":1,"1349":7,"1351":1,"1358":14,"1379":1,"1394":8,"1395":5,"1401":5,"1414":2,"1415":1,"1416":6,"1430":1,"1432":1,"1435":21,"1443":1,"1454":1,"1487":2,"1488":1,"1553":1,"1909":1,"2047":1,"2076":1}}],["counters",{"0":{"2176":1},"3":{"0":3,"124":1,"173":1,"177":1,"178":1,"245":1,"248":1,"255":2,"279":1,"283":2,"285":1,"286":2,"287":1,"379":1,"400":3,"404":1,"408":1,"609":1,"698":1,"700":1,"707":1,"732":1,"734":1,"737":1,"855":1,"899":1,"1016":1,"1018":2,"1020":1,"1089":1,"1090":1,"1093":1,"1268":2,"1270":6,"1285":3,"1288":1,"1291":1,"1293":1,"1299":5,"1300":4,"1310":3,"1316":1,"1322":1,"1349":1,"1358":2,"1394":1,"1395":1,"1417":1,"1424":2,"1426":3,"1434":1,"1437":4,"1441":2,"1442":2,"1466":1,"1576":1,"1640":3,"1664":1,"1665":1,"1666":1,"1764":1,"1766":1,"1914":1,"1919":1,"1934":1,"1940":1,"2000":2,"2005":1,"2153":1,"2155":5,"2156":1,"2159":3,"2179":1,"2190":1}}],["counter",{"3":{"0":8,"121":1,"126":1,"157":1,"241":1,"242":1,"245":5,"247":1,"248":2,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":2,"257":1,"279":4,"283":1,"284":1,"285":1,"395":2,"397":2,"398":1,"399":2,"406":1,"407":1,"518":1,"608":1,"707":2,"731":1,"732":2,"733":2,"734":1,"735":1,"736":2,"818":1,"819":2,"823":1,"827":1,"849":1,"854":2,"896":1,"897":2,"898":1,"899":1,"996":1,"997":1,"1074":1,"1090":1,"1092":1,"1093":1,"1100":1,"1101":1,"1102":1,"1126":1,"1150":1,"1212":2,"1247":11,"1256":3,"1259":3,"1267":1,"1270":6,"1281":1,"1285":4,"1291":2,"1293":3,"1294":1,"1299":18,"1300":16,"1308":1,"1310":26,"1316":1,"1322":9,"1323":17,"1336":1,"1338":4,"1343":1,"1349":5,"1358":6,"1394":19,"1395":3,"1401":5,"1415":1,"1426":2,"1428":1,"1435":4,"1437":4,"1441":2,"1452":1,"1487":1,"1489":1,"1495":1,"1576":1,"1640":5,"1764":1,"1766":1,"1842":2,"1848":1,"1902":1,"1914":2,"1915":1,"1921":1,"1946":1,"1998":1,"1999":1,"2000":6,"2001":3,"2005":2,"2009":1,"2054":1,"2141":1,"2155":7,"2158":1,"2179":1}}],["counted",{"3":{"0":5,"121":1,"126":1,"130":1,"135":1,"138":2,"139":1,"175":1,"255":1,"279":1,"383":1,"408":1,"423":1,"474":1,"484":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"556":1,"575":1,"577":1,"655":1,"672":3,"798":1,"799":2,"801":1,"803":1,"805":1,"832":1,"916":1,"996":1,"1091":2,"1093":1,"1247":1,"1256":1,"1259":1,"1263":2,"1267":1,"1270":2,"1285":1,"1299":2,"1310":7,"1322":1,"1323":4,"1338":1,"1349":1,"1356":1,"1358":11,"1395":6,"1399":1,"1401":4,"1415":1,"1421":1,"1435":2,"1437":1,"1486":1,"1488":1,"1495":3,"1584":1,"1638":1,"1668":1,"1763":1,"1816":1,"1908":1,"1910":1,"1918":1,"1921":2,"1922":1,"1948":2,"1982":1,"1999":2,"2173":1,"2178":1}}],["countstheliteralcatalog",{"3":{"1435":2}}],["countsthedatacarryingcommands",{"3":{"1435":3}}],["countsbypolloption",{"3":{"1401":1}}],["countsbyquestion",{"3":{"1401":1}}],["countscoredsince",{"3":{"1394":5}}],["countsessionspercategoryitem",{"3":{"1358":4}}],["counts",{"0":{"137":1,"140":1,"1205":1,"1206":1,"1733":1},"3":{"0":6,"24":1,"73":2,"74":1,"125":1,"130":3,"135":4,"136":1,"137":2,"140":1,"157":1,"184":1,"228":1,"235":1,"255":2,"280":1,"281":1,"319":1,"386":1,"390":1,"395":1,"397":1,"399":1,"400":2,"474":2,"475":1,"477":2,"480":1,"483":1,"486":1,"487":2,"488":1,"489":1,"490":1,"491":3,"492":1,"493":1,"494":1,"495":3,"524":1,"526":1,"530":1,"538":2,"545":1,"577":1,"585":1,"597":1,"604":2,"607":2,"609":1,"616":3,"621":1,"629":1,"634":1,"649":1,"677":1,"706":1,"733":1,"741":1,"780":3,"819":1,"827":1,"832":1,"836":1,"883":1,"888":1,"889":1,"896":1,"972":1,"974":1,"978":1,"983":1,"986":1,"1004":3,"1006":1,"1091":1,"1093":1,"1124":1,"1150":1,"1152":1,"1168":1,"1186":1,"1212":1,"1237":1,"1247":2,"1256":1,"1259":5,"1270":1,"1271":1,"1273":1,"1275":1,"1285":9,"1288":1,"1299":2,"1300":1,"1303":1,"1308":3,"1310":7,"1322":5,"1323":14,"1332":1,"1333":1,"1338":8,"1346":4,"1348":1,"1349":18,"1354":1,"1358":31,"1365":1,"1378":1,"1379":5,"1387":2,"1389":3,"1394":10,"1395":15,"1398":1,"1400":1,"1401":17,"1414":4,"1415":2,"1416":2,"1422":1,"1426":1,"1429":1,"1434":1,"1435":30,"1437":1,"1452":1,"1454":3,"1466":2,"1473":1,"1475":1,"1483":1,"1484":1,"1486":3,"1489":1,"1493":1,"1494":1,"1495":16,"1497":1,"1534":1,"1572":1,"1576":1,"1581":2,"1590":1,"1598":1,"1631":3,"1636":1,"1638":1,"1640":3,"1653":1,"1658":1,"1664":2,"1667":1,"1668":1,"1672":2,"1673":1,"1674":1,"1814":1,"1903":1,"1916":1,"1921":1,"1922":1,"1936":1,"1948":1,"1996":1,"1999":3,"2001":2,"2023":1,"2033":1,"2158":1,"2169":1,"2171":1,"2173":1,"2174":1,"2178":1,"2191":1,"2231":2}}],["count",{"0":{"1128":1,"1796":1,"2050":1},"2":{"20":1,"80":1,"82":1,"121":1,"126":1,"397":1,"819":1,"821":1,"1256":1,"1268":2,"1309":1,"1353":1,"1664":1,"1842":1,"2031":1,"2155":2,"2158":1,"2190":2},"3":{"0":25,"3":1,"20":1,"24":1,"71":1,"80":1,"82":1,"100":1,"121":1,"125":1,"126":1,"130":2,"135":1,"136":2,"137":2,"138":1,"140":1,"141":1,"142":3,"146":1,"147":1,"152":1,"160":1,"178":1,"195":1,"203":1,"225":1,"254":1,"255":1,"258":1,"260":2,"310":1,"311":1,"335":1,"379":1,"390":2,"392":2,"393":1,"395":2,"397":3,"408":1,"409":1,"418":1,"423":1,"426":3,"452":2,"453":1,"468":1,"475":1,"476":1,"492":2,"493":2,"494":2,"495":1,"522":2,"523":2,"524":4,"526":1,"527":1,"528":1,"549":2,"550":2,"554":3,"572":2,"589":1,"592":1,"607":1,"608":1,"609":6,"610":1,"611":2,"616":3,"624":1,"631":3,"634":1,"635":3,"640":1,"655":1,"672":1,"696":1,"699":1,"700":2,"759":1,"768":1,"775":1,"797":3,"799":4,"800":1,"801":1,"803":1,"804":1,"805":2,"819":5,"820":1,"821":1,"860":1,"862":1,"883":2,"884":2,"896":1,"897":1,"899":1,"905":2,"906":1,"914":1,"937":1,"946":2,"948":1,"952":2,"959":1,"972":1,"975":1,"986":2,"991":1,"996":2,"999":1,"1004":2,"1005":1,"1018":1,"1020":2,"1042":1,"1048":1,"1049":1,"1054":1,"1055":1,"1069":1,"1073":1,"1078":1,"1100":2,"1116":1,"1118":1,"1122":1,"1124":4,"1125":3,"1126":1,"1136":1,"1178":1,"1192":2,"1196":1,"1202":1,"1205":1,"1211":1,"1212":2,"1221":1,"1228":3,"1229":11,"1237":4,"1240":2,"1243":4,"1247":13,"1253":1,"1256":3,"1259":10,"1264":1,"1268":2,"1270":10,"1271":1,"1275":1,"1278":1,"1285":30,"1299":13,"1300":2,"1302":1,"1305":3,"1308":30,"1309":3,"1310":9,"1314":1,"1316":1,"1317":1,"1322":13,"1323":62,"1329":1,"1336":2,"1338":8,"1346":1,"1349":13,"1353":1,"1354":1,"1356":1,"1357":1,"1358":59,"1368":1,"1370":1,"1374":1,"1379":4,"1388":1,"1394":44,"1395":82,"1398":4,"1400":4,"1401":75,"1405":1,"1414":6,"1415":2,"1421":1,"1424":1,"1426":3,"1430":2,"1434":1,"1435":77,"1437":5,"1441":1,"1442":1,"1445":1,"1452":4,"1453":2,"1454":2,"1455":1,"1457":2,"1460":1,"1466":2,"1472":1,"1473":2,"1474":2,"1475":2,"1477":1,"1486":1,"1487":1,"1488":4,"1495":19,"1499":1,"1502":1,"1514":1,"1520":1,"1523":1,"1524":1,"1525":5,"1530":3,"1533":2,"1534":1,"1546":1,"1576":6,"1581":1,"1590":2,"1594":1,"1595":2,"1596":1,"1599":1,"1606":1,"1610":1,"1611":1,"1616":1,"1631":8,"1640":3,"1645":1,"1648":1,"1664":3,"1672":1,"1674":2,"1691":1,"1708":1,"1728":1,"1731":1,"1740":1,"1749":3,"1763":1,"1764":1,"1766":2,"1772":2,"1779":1,"1795":1,"1817":1,"1840":1,"1842":1,"1873":1,"1878":1,"1880":1,"1884":1,"1901":3,"1903":1,"1904":1,"1911":1,"1918":2,"1936":1,"1939":1,"1941":1,"1945":1,"1949":1,"1981":1,"1985":1,"1987":1,"1997":1,"1998":1,"1999":5,"2001":1,"2026":2,"2031":3,"2041":1,"2042":3,"2044":1,"2045":2,"2046":1,"2050":1,"2051":1,"2054":1,"2059":1,"2069":1,"2081":1,"2105":1,"2111":1,"2112":1,"2115":1,"2155":8,"2156":1,"2157":1,"2158":1,"2173":1,"2176":1,"2179":2,"2181":1,"2190":4,"2192":1,"2231":3}}],["comfort",{"3":{"1799":1}}],["comfortable",{"3":{"1153":1,"1323":2,"1558":1,"1590":1,"1595":1,"1596":1}}],["comfortably",{"3":{"907":1,"996":1,"1270":1,"1275":1,"1322":1,"1323":1,"1395":1,"1401":1,"1435":1,"1466":1}}],["coming",{"3":{"0":1,"24":1,"1040":1,"1259":1,"1285":1,"1306":1,"1394":1,"1416":1,"1446":1,"1785":1,"2057":1}}],["com",{"2":{"1480":1},"3":{"0":2,"528":1,"640":1,"855":2,"868":2,"1291":2,"1299":7,"1323":3,"1338":1,"1346":1,"1350":1,"1357":1,"1358":6,"1364":1,"1368":2,"1394":6,"1414":1,"1416":2,"1435":7,"1440":1,"1444":2,"1464":1,"1471":1,"1480":2,"1630":2,"1786":1,"1792":1,"1801":1,"1806":1,"1811":1,"1816":1,"1826":1,"1833":1,"1845":1,"1852":1,"1861":1,"1867":1,"1876":1,"1885":1,"1891":1,"1898":1,"1904":1,"1911":1,"1923":1,"1929":1,"1940":1,"1949":1,"1958":1,"1964":1,"1971":1,"1978":1,"1984":1,"1990":1,"1996":1,"2001":1,"2013":1,"2027":1,"2037":1,"2048":1,"2059":1,"2068":1,"2079":1,"2085":1,"2097":1,"2107":1,"2114":1,"2122":1,"2127":1,"2132":1,"2138":1,"2145":1,"2152":1,"2159":1,"2168":1,"2179":1,"2192":1,"2202":1,"2210":1,"2219":2,"2220":1,"2239":2,"2243":1}}],["combobox",{"3":{"0":1,"618":2,"620":2,"651":1,"1435":4,"1487":3,"1590":1,"1740":1,"2056":1}}],["combinable",{"3":{"1285":1}}],["combinations",{"3":{"932":1,"946":1,"1285":1,"1323":1,"1640":1}}],["combination",{"3":{"0":1,"92":1,"148":1,"556":1,"684":1,"872":1,"931":1,"1091":1,"1092":1,"1141":1,"1212":1,"1237":1,"1247":2,"1285":1,"1310":1,"1342":1,"1349":1,"1358":3,"1394":1,"1395":3,"1414":1,"1416":1,"1435":2,"1437":1,"1490":1,"1528":1,"1630":1,"1841":1,"1843":1,"1946":1,"2124":1,"2175":1}}],["combinatorial",{"3":{"146":1}}],["combinators",{"3":{"0":1,"109":1,"110":1,"111":1,"547":2,"549":2,"550":1,"1219":1,"1220":1,"1229":8,"1239":1,"1247":10,"1804":1,"1806":1,"1814":1,"1816":1}}],["combinator",{"3":{"0":1,"107":1,"109":2,"545":1,"1217":1,"1220":2,"1229":5,"1247":8,"1661":1,"1804":1,"1806":1,"1814":1}}],["combiningacute",{"3":{"1323":1}}],["combining",{"3":{"1220":1,"1229":1,"1231":2,"1237":1,"1247":1,"1285":2,"1323":2,"1335":1,"1338":2,"1342":1,"1349":2,"1358":1,"1414":1,"1416":1,"1435":2,"1813":1}}],["combines",{"3":{"964":1,"1270":2,"1278":1,"1308":2,"1338":1,"1349":5,"1358":12,"1394":1,"1395":1,"1397":1,"1401":4,"1414":3,"1416":2,"1435":1,"1487":1}}],["combine",{"2":{"43":1,"110":1,"1169":1,"1170":1,"1220":1,"1222":1,"1224":1,"1342":1,"1700":1,"1806":1},"3":{"0":3,"39":3,"43":2,"109":1,"110":1,"545":1,"549":1,"657":1,"1168":6,"1169":2,"1170":2,"1220":2,"1222":1,"1224":1,"1229":5,"1234":1,"1237":8,"1239":1,"1244":1,"1247":7,"1271":1,"1308":4,"1310":2,"1311":2,"1323":2,"1341":1,"1342":3,"1343":1,"1349":31,"1358":4,"1394":7,"1395":10,"1401":5,"1404":1,"1414":4,"1426":1,"1435":5,"1525":1,"1650":1,"1661":2,"1684":3,"1700":1,"1804":1,"1805":1,"1806":2,"1809":1,"1814":1,"1816":1,"1926":1,"1961":1}}],["combined",{"3":{"0":1,"43":1,"57":1,"59":4,"60":1,"62":1,"63":1,"166":1,"195":1,"350":1,"483":1,"487":1,"488":1,"489":1,"490":1,"491":2,"492":2,"493":2,"494":2,"495":2,"547":1,"562":1,"582":1,"584":1,"743":1,"790":1,"889":1,"890":1,"1239":1,"1247":1,"1270":3,"1285":4,"1323":2,"1338":1,"1343":1,"1349":6,"1358":8,"1394":2,"1395":2,"1401":7,"1414":1,"1435":4,"1437":1,"1440":1,"1445":1,"1454":1,"1455":1,"1466":1,"1485":2,"1486":1,"1495":2,"1525":1,"1618":1,"1650":1,"1672":1,"1764":1,"1774":1,"1816":1,"1858":1,"1883":1,"2025":1}}],["computation",{"3":{"681":1,"1026":1,"1027":1,"1349":1,"1358":3,"1394":1,"1395":2,"1435":1,"1499":1,"1803":1}}],["computing",{"3":{"629":1,"1019":1,"1259":1,"1267":1,"1285":2,"1299":2,"1323":1,"1338":2,"1349":1,"1358":3,"1368":1,"1401":4,"1416":1,"1435":2}}],["computer",{"3":{"1912":1,"2221":1}}],["computerequestbodyhashasync",{"3":{"1310":2}}],["computeretrybackoffseconds",{"2":{"19":2,"1042":2,"1259":1,"1275":1,"2187":1},"3":{"19":2,"1042":2,"1259":3,"1275":1,"1285":2,"2187":1}}],["computeancestors",{"3":{"1435":1}}],["computeoptions",{"3":{"1394":3}}],["computelivewindowutc",{"3":{"1358":1}}],["computelegacyhash",{"2":{"945":1},"3":{"0":1,"310":1,"945":1,"946":1}}],["computecategorydistribution",{"3":{"1358":1}}],["computevirtualwindow",{"3":{"1323":1}}],["computewaittime",{"2":{"1042":1,"1254":1},"3":{"1042":1,"1254":1,"1259":3,"1275":1,"1285":7,"1322":1}}],["compute",{"3":{"707":1,"973":1,"1267":1,"1279":1,"1310":2,"1314":1,"1322":2,"1323":1,"1349":2,"1351":1,"1354":1,"1358":5,"1395":3,"1401":2,"1416":3,"1435":2,"1445":1,"1466":2,"1567":1,"1676":2,"1677":2,"1878":1,"1880":1,"1919":2,"2154":1,"2179":1}}],["computespeakerlocality",{"3":{"1358":1}}],["computespeakeroverlap",{"3":{"1358":2}}],["computes",{"3":{"583":1,"626":1,"1091":1,"1212":1,"1229":1,"1242":1,"1247":3,"1259":2,"1263":1,"1267":1,"1270":4,"1285":8,"1299":3,"1309":1,"1310":1,"1322":2,"1323":7,"1338":5,"1349":1,"1351":1,"1358":14,"1372":1,"1394":3,"1395":5,"1400":2,"1401":7,"1416":4,"1430":1,"1435":10,"1495":1,"1919":1,"2148":2,"2166":1}}],["computepbkdf2hash",{"3":{"310":1,"946":1,"1299":1}}],["computedhash",{"3":{"1299":1}}],["computed",{"0":{"1797":1},"2":{"1901":1},"3":{"0":3,"19":1,"157":1,"159":1,"219":1,"335":1,"633":1,"650":1,"905":1,"926":1,"990":1,"1018":1,"1026":2,"1091":1,"1191":1,"1192":1,"1193":1,"1200":1,"1227":1,"1229":2,"1237":2,"1247":5,"1254":1,"1259":3,"1267":1,"1269":1,"1270":8,"1285":11,"1299":6,"1308":1,"1309":4,"1310":4,"1322":3,"1323":13,"1330":1,"1331":1,"1338":4,"1341":3,"1349":12,"1351":1,"1353":2,"1358":23,"1361":1,"1368":8,"1382":2,"1390":1,"1394":21,"1395":18,"1401":11,"1408":1,"1411":1,"1414":11,"1416":1,"1425":1,"1426":1,"1435":5,"1437":1,"1466":1,"1479":1,"1495":1,"1576":1,"1584":1,"1591":1,"1629":6,"1630":1,"1638":2,"1639":3,"1640":3,"1685":3,"1748":2,"1763":5,"1764":1,"1766":2,"1804":1,"1839":1,"1866":1,"1901":1,"1919":1,"1947":1,"1987":1,"2175":1,"2179":1}}],["complains",{"3":{"1906":1,"1922":1}}],["complaint",{"3":{"1322":1}}],["comply",{"3":{"1435":1}}],["compliment",{"3":{"2199":1}}],["complication",{"3":{"1358":1}}],["compliance",{"0":{"1566":1,"1671":1},"3":{"716":1,"971":1,"972":1,"973":1,"976":1,"1168":1,"1207":2,"1216":1,"1232":1,"1236":1,"1237":6,"1253":1,"1259":2,"1269":1,"1270":3,"1276":1,"1282":1,"1285":14,"1298":1,"1299":4,"1307":1,"1308":5,"1310":2,"1316":1,"1322":8,"1323":2,"1349":1,"1352":1,"1358":4,"1379":2,"1394":2,"1395":29,"1401":1,"1402":1,"1404":1,"1409":2,"1414":25,"1416":5,"1435":22,"1437":2,"1453":1,"1460":1,"1478":1,"1479":3,"1482":1,"1488":1,"1492":1,"1498":1,"1525":1,"1533":1,"1534":1,"1568":1,"1574":1,"1576":2,"1580":1,"1581":1,"1582":1,"1583":1,"1590":1,"1596":1,"1671":1,"1677":1,"1783":1,"1795":1,"1798":1,"1800":1,"1844":2,"1845":1,"2042":1,"2061":1,"2066":1,"2140":1}}],["compliantstringfixtureid",{"3":{"1199":2,"1207":1,"1435":5}}],["compliantfixtureid",{"3":{"1199":2,"1207":1,"1435":8}}],["compliantfixtureservice",{"3":{"1199":2,"1207":1,"1435":3}}],["compliant",{"3":{"38":1,"41":1,"1050":1,"1168":1,"1299":1,"1402":1,"1435":17,"1479":1,"2060":1,"2067":1}}],["complexities",{"3":{"1638":1}}],["complexity",{"3":{"61":1,"68":1,"87":1,"1214":1,"1237":1,"1247":1,"1254":1,"1271":3,"1293":1,"1299":5,"1300":1,"1311":1,"1322":2,"1323":2,"1358":1,"1394":1,"1414":2,"1638":1,"1640":2,"1766":1,"1791":1,"1831":1,"1898":1,"1939":1,"2026":2,"2036":1}}],["complex",{"3":{"1052":1,"1055":1,"1285":1,"1358":2,"2078":1}}],["complementary",{"3":{"541":1,"624":1,"626":2,"1247":1,"1299":1,"1338":1,"1358":1,"1395":1,"1435":3,"1462":1,"1485":2,"1491":1,"1492":1,"1604":1,"1738":1,"1976":1,"1997":1,"2040":1,"2075":1,"2163":1}}],["complementing",{"3":{"503":1,"1310":1,"1401":1}}],["complements",{"0":{"1889":1},"3":{"122":1,"282":1,"321":1,"370":1,"579":1,"758":1,"766":1,"770":1,"1308":1,"1310":1,"1311":1,"1323":1,"1395":1,"1435":5,"1437":1,"1454":2,"1456":1,"1462":1,"1559":1,"1562":1,"1564":1,"1610":1,"1641":1,"1705":1,"1891":1,"2092":1}}],["complement",{"3":{"0":1,"626":1,"860":1,"1259":1,"1338":1,"1394":1,"1395":1,"1435":1,"1454":3,"1460":1,"1485":1,"1524":1,"1525":1,"1530":1,"1590":1,"1595":1,"1599":1,"1604":1,"1738":1,"1831":1}}],["completing",{"3":{"920":1,"1310":1,"1323":1,"1358":2,"1394":4,"1395":1,"1410":1,"1416":2,"1466":1,"1473":1,"2231":1}}],["completions",{"1":{"1056":1,"1057":1,"1058":1,"1059":1,"1060":1,"1061":1,"1062":1,"1063":1,"2169":1,"2170":1,"2171":1,"2172":1,"2173":1,"2174":1,"2175":1,"2176":1,"2177":1,"2178":1,"2179":1},"3":{"0":1,"1056":1,"1059":1,"1062":1,"1212":1,"1270":1,"1275":1,"1286":1,"1299":7,"1322":2,"1414":1,"1778":2,"2168":2,"2169":1,"2192":1,"2202":1,"2208":1,"2231":1}}],["completion",{"3":{"0":4,"189":1,"348":1,"350":1,"353":3,"419":2,"420":2,"422":1,"423":2,"426":1,"428":1,"430":1,"1091":1,"1107":1,"1108":1,"1112":1,"1270":6,"1285":1,"1299":3,"1310":2,"1322":3,"1323":11,"1338":5,"1379":1,"1389":1,"1394":4,"1414":2,"1415":2,"1416":16,"1420":1,"1426":1,"1435":2,"1666":2,"1668":1,"1974":1,"1975":2,"2169":1,"2171":1,"2231":1}}],["completable",{"3":{"681":1,"684":1}}],["completeenrollmentasync",{"2":{"1292":1},"3":{"1292":1,"1299":2,"1322":1}}],["completely",{"3":{"922":1,"1265":1,"1285":2,"1299":1,"1310":1,"1315":1,"1322":2,"1323":1,"1466":1,"1814":1,"1823":1,"1834":1,"1998":2,"2157":1}}],["complete",{"0":{"926":1},"2":{"161":1},"3":{"0":6,"39":1,"109":1,"160":1,"161":1,"164":1,"168":1,"181":1,"212":1,"214":1,"215":1,"216":1,"217":1,"265":1,"272":1,"273":1,"360":1,"361":1,"420":3,"423":1,"428":1,"434":1,"440":1,"537":1,"574":1,"577":1,"585":2,"597":1,"632":1,"649":1,"675":1,"682":1,"741":1,"743":1,"774":1,"811":1,"916":1,"920":1,"963":1,"965":1,"1049":1,"1108":1,"1212":1,"1219":1,"1229":1,"1247":1,"1259":2,"1265":1,"1285":2,"1299":9,"1308":2,"1310":8,"1321":1,"1322":8,"1323":9,"1325":1,"1329":1,"1335":2,"1338":7,"1339":1,"1349":3,"1351":1,"1356":1,"1358":10,"1368":6,"1394":5,"1395":4,"1405":1,"1408":1,"1409":1,"1410":1,"1414":9,"1415":2,"1416":9,"1426":1,"1431":1,"1435":8,"1441":1,"1442":1,"1443":1,"1452":1,"1455":1,"1467":1,"1471":1,"1476":1,"1484":1,"1487":1,"1488":1,"1495":1,"1496":1,"1525":1,"1530":1,"1531":2,"1574":1,"1576":3,"1581":1,"1590":1,"1598":2,"1610":1,"1631":1,"1652":1,"1653":1,"1657":1,"1660":1,"1669":1,"1686":1,"1704":1,"1749":1,"1764":1,"1848":1,"1890":1,"1897":1,"1930":1,"1931":1,"1935":1,"1940":1,"1963":1,"1975":2,"2000":1,"2033":1,"2082":2,"2084":1,"2125":1,"2142":1,"2148":2,"2151":1,"2152":1,"2164":1,"2199":1,"2200":1,"2207":1,"2230":1,"2232":1}}],["completes",{"3":{"0":2,"235":2,"539":1,"844":1,"897":1,"925":1,"1018":1,"1212":1,"1259":2,"1285":2,"1299":3,"1310":2,"1317":1,"1322":1,"1323":4,"1333":1,"1338":4,"1349":1,"1356":1,"1358":9,"1375":1,"1379":1,"1394":2,"1401":1,"1414":1,"1416":4,"1435":4,"1437":1,"1442":1,"1466":1,"1706":1,"1754":2,"1783":1,"2066":1,"2231":1}}],["completeness",{"3":{"0":2,"265":2,"399":1,"574":1,"725":1,"727":1,"838":2,"840":2,"1310":2,"1368":3,"1395":1,"1414":1,"1435":11,"1437":2,"1591":1,"1629":1,"1637":1,"1652":1,"1668":1,"1670":2,"2085":1,"2158":1}}],["completeasync",{"2":{"202":1,"350":1,"355":1,"419":2,"423":1,"1909":1,"1974":2,"1975":1},"3":{"0":2,"24":1,"195":1,"201":4,"202":1,"350":3,"355":1,"418":1,"419":2,"420":1,"423":1,"1258":2,"1259":9,"1310":2,"1322":1,"1414":1,"1415":1,"1909":1,"1974":2,"1975":1}}],["completedstable",{"3":{"1394":3}}],["completedall",{"3":{"1394":3}}],["completedtask",{"3":{"1259":3,"1270":1,"1285":3,"1308":2,"1309":3,"1310":3,"1322":5,"1323":2,"1338":1,"1394":1,"1416":17,"1435":6,"1864":1}}],["completed",{"2":{"719":1,"790":1,"1764":1,"1769":1,"1820":1},"3":{"0":2,"237":1,"352":1,"397":1,"539":1,"626":3,"628":2,"719":1,"790":2,"792":1,"794":1,"795":1,"809":1,"821":1,"823":1,"988":1,"1042":1,"1048":1,"1086":1,"1212":1,"1251":1,"1259":1,"1263":2,"1270":4,"1275":1,"1285":5,"1299":1,"1309":1,"1317":1,"1322":8,"1323":8,"1338":1,"1349":2,"1358":8,"1379":1,"1394":2,"1395":4,"1414":3,"1416":14,"1422":1,"1426":5,"1435":2,"1454":1,"1466":4,"1469":1,"1470":1,"1473":1,"1491":1,"1590":2,"1639":1,"1764":1,"1768":1,"1769":1,"1820":1,"1977":1,"2219":1}}],["comprehensive",{"3":{"2040":1,"2059":1}}],["compressible",{"3":{"442":1}}],["compressionlevel",{"3":{"1310":2}}],["compression",{"3":{"0":1,"749":1,"1310":7,"1378":1,"1435":1}}],["compressed",{"3":{"0":1,"438":1,"442":1,"591":1,"1322":3,"1559":1,"2125":1}}],["compromises",{"3":{"1310":2}}],["compromise",{"3":{"500":1,"904":2,"906":1,"1270":2,"1299":1,"1300":1,"1322":1,"1349":1,"1435":1,"1638":1}}],["compromised",{"3":{"32":1,"358":1,"602":1,"642":1,"904":1,"1212":1,"1287":1,"1299":2,"1414":1,"1452":1,"1466":2}}],["compilable",{"3":{"1416":1,"1435":1}}],["compilation",{"3":{"6":1,"576":1,"1213":1,"1247":1,"1309":1,"1323":1,"1358":4,"1395":1,"1416":1,"1672":1,"1954":1,"2040":1}}],["compiling",{"3":{"0":1,"908":1,"921":1,"979":1,"1059":1,"1091":1,"1134":1,"1247":1,"1268":1,"1270":2,"1289":1,"1299":1,"1300":1,"1304":1,"1308":2,"1310":1,"1323":3,"1352":1,"1358":2,"1416":1,"1426":1,"1435":5,"1452":1,"1726":1,"2093":1,"2125":1,"2195":1}}],["compiles",{"3":{"26":1,"53":1,"160":1,"373":1,"634":1,"697":1,"799":1,"801":1,"872":1,"920":1,"938":2,"980":1,"1066":1,"1132":2,"1237":1,"1239":1,"1247":5,"1259":3,"1270":5,"1300":1,"1309":1,"1311":1,"1323":1,"1349":1,"1358":19,"1368":1,"1394":4,"1395":1,"1414":2,"1415":4,"1416":5,"1426":3,"1429":1,"1430":1,"1435":15,"1610":4,"1655":1,"1662":1,"1684":1,"1729":1,"1735":1,"1736":1,"1789":1,"1799":1,"1809":1,"1814":1,"1824":2,"1829":1,"1862":1,"1951":1,"2016":1,"2043":1,"2055":1,"2059":1,"2096":1,"2199":1}}],["compiledpermission",{"3":{"1435":1}}],["compiledpermissions",{"3":{"1299":2}}],["compiledexpressioncache",{"3":{"1309":1}}],["compiled",{"0":{"2055":1},"3":{"0":6,"132":1,"184":2,"186":1,"330":1,"333":1,"359":1,"365":1,"422":1,"426":1,"454":1,"545":1,"546":1,"576":1,"591":1,"592":1,"595":1,"674":2,"699":1,"707":1,"848":1,"869":1,"946":1,"979":1,"980":1,"982":1,"983":1,"1057":1,"1058":1,"1059":6,"1060":1,"1061":1,"1063":1,"1067":1,"1161":2,"1213":1,"1214":1,"1237":1,"1238":1,"1241":1,"1242":3,"1247":18,"1252":1,"1259":2,"1276":1,"1278":3,"1282":1,"1285":18,"1296":3,"1299":36,"1303":1,"1304":1,"1308":5,"1309":7,"1310":4,"1318":1,"1322":9,"1323":18,"1340":1,"1347":1,"1349":1,"1352":1,"1358":12,"1379":3,"1394":2,"1395":6,"1408":2,"1413":1,"1414":4,"1415":2,"1416":2,"1426":1,"1427":1,"1430":1,"1431":1,"1435":66,"1437":2,"1444":1,"1454":1,"1465":1,"1485":2,"1488":2,"1490":1,"1503":1,"1530":1,"1576":4,"1625":2,"1664":1,"1669":1,"1670":1,"1674":1,"1781":1,"1849":1,"1850":2,"1864":1,"1901":1,"1922":1,"1944":1,"1955":1,"1961":1,"1963":1,"1987":1,"1988":1,"2027":1,"2040":2,"2041":1,"2046":1,"2053":1,"2099":1,"2141":1,"2193":1,"2198":4,"2199":3,"2202":3}}],["compilergeneratedattribute",{"3":{"1435":2}}],["compilerservices",{"3":{"980":1,"1311":1,"1435":3}}],["compiler",{"3":{"0":5,"81":1,"135":2,"160":2,"459":1,"465":1,"472":1,"547":1,"620":1,"633":1,"657":1,"784":1,"798":1,"799":1,"800":1,"821":1,"839":1,"840":2,"955":1,"956":1,"971":1,"980":3,"981":2,"982":1,"1004":1,"1005":1,"1036":2,"1050":1,"1052":1,"1161":1,"1162":1,"1174":1,"1212":1,"1218":1,"1229":1,"1234":1,"1237":1,"1247":1,"1259":1,"1270":8,"1285":7,"1299":7,"1309":1,"1310":4,"1311":1,"1322":2,"1323":6,"1338":5,"1358":16,"1368":1,"1394":1,"1395":5,"1401":2,"1414":3,"1415":1,"1416":1,"1435":45,"1448":1,"1449":1,"1487":1,"1499":1,"1736":1,"1802":1,"1803":1,"1809":2,"1832":1,"1887":1,"1891":1,"1918":1,"1950":2,"1954":1,"1956":1,"1958":1,"1995":1}}],["compile",{"0":{"576":1,"2040":1,"2105":1},"1":{"1950":1,"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1956":1,"1957":1,"1958":1},"3":{"0":7,"3":1,"5":1,"6":1,"130":1,"132":1,"133":1,"135":2,"136":1,"265":1,"310":1,"312":1,"412":1,"413":1,"422":1,"425":1,"461":1,"469":1,"472":2,"570":1,"590":1,"592":1,"599":1,"604":1,"609":1,"633":1,"650":1,"698":3,"798":1,"801":1,"838":1,"839":1,"846":1,"848":1,"869":1,"871":1,"882":1,"883":2,"923":1,"926":2,"933":1,"939":1,"948":1,"966":1,"1004":1,"1033":1,"1050":2,"1052":1,"1067":1,"1091":1,"1093":1,"1133":1,"1135":1,"1212":5,"1213":2,"1214":2,"1229":1,"1232":1,"1237":2,"1242":1,"1247":4,"1259":4,"1270":3,"1271":1,"1285":10,"1290":1,"1299":16,"1300":2,"1308":3,"1309":2,"1310":8,"1322":8,"1323":10,"1338":4,"1342":1,"1349":4,"1352":3,"1358":46,"1379":3,"1380":1,"1394":5,"1395":13,"1400":1,"1401":3,"1404":1,"1414":12,"1415":3,"1416":4,"1423":1,"1426":2,"1430":3,"1435":47,"1437":1,"1452":1,"1454":1,"1466":2,"1488":5,"1490":2,"1493":1,"1503":1,"1574":1,"1575":1,"1576":12,"1580":1,"1581":3,"1582":1,"1583":1,"1584":2,"1585":1,"1590":1,"1610":1,"1649":1,"1650":1,"1659":1,"1661":1,"1670":1,"1684":1,"1685":1,"1700":1,"1721":1,"1726":2,"1727":2,"1728":1,"1729":1,"1781":1,"1783":1,"1799":1,"1809":1,"1850":1,"1864":1,"1887":2,"1948":1,"1949":1,"1950":2,"1951":2,"1952":1,"1954":3,"1957":1,"1958":3,"2040":2,"2043":2,"2048":1,"2070":1,"2076":1,"2084":1,"2096":1,"2099":2,"2105":2,"2107":2,"2115":2,"2119":1,"2157":1,"2207":1,"2213":1,"2229":1,"2231":1}}],["company",{"3":{"1358":1,"2219":1}}],["companions",{"3":{"1247":1,"1285":1,"1323":1,"1435":1}}],["companion",{"0":{"1195":1},"3":{"39":1,"66":1,"72":1,"73":1,"78":1,"170":1,"207":1,"406":1,"424":1,"622":1,"659":1,"718":1,"782":1,"825":1,"1147":1,"1190":1,"1237":1,"1241":1,"1247":2,"1259":2,"1270":2,"1284":1,"1285":1,"1299":2,"1310":3,"1322":12,"1323":1,"1349":4,"1358":2,"1379":1,"1390":1,"1394":1,"1395":4,"1414":2,"1415":1,"1426":1,"1427":1,"1435":11,"1437":1,"1458":1,"1473":1,"1475":1,"1478":1,"1480":1,"1485":1,"1486":1,"1487":1,"1491":1,"1576":1,"1601":1,"1625":1,"1657":1,"1673":1,"1755":1,"1854":1,"1967":1,"2075":1,"2227":1,"2233":1}}],["compacthtml",{"3":{"1435":2}}],["compact",{"3":{"881":1,"1223":1,"1229":2,"1233":1,"1285":1,"1288":1,"1291":1,"1299":2,"1310":2,"1323":4,"1327":1,"1343":1,"1349":1,"1358":3,"1383":1,"1391":1,"1394":2,"1395":3,"1401":4,"1402":1,"1415":2,"1432":1,"1435":1,"1558":1,"1590":1,"1595":1,"1596":1}}],["compacted",{"3":{"618":1,"1487":1,"2056":1}}],["compatibility",{"1":{"307":1,"308":1,"309":1,"310":1,"311":1,"312":1,"313":1,"314":1},"3":{"79":1,"99":1,"146":1,"148":1,"151":1,"307":1,"365":1,"556":1,"558":1,"565":1,"566":1,"799":1,"950":1,"965":1,"1033":1,"1211":1,"1229":1,"1247":1,"1259":4,"1285":2,"1299":3,"1322":1,"1323":2,"1338":1,"1349":2,"1358":2,"1435":3,"1454":1,"1503":1,"1545":1,"1731":1,"1747":1,"1888":2,"1891":1,"2111":1}}],["compatible",{"3":{"0":1,"78":1,"136":1,"149":1,"215":1,"412":1,"435":1,"511":1,"568":1,"626":1,"680":1,"681":1,"689":1,"734":1,"757":1,"762":1,"954":1,"1061":1,"1091":1,"1213":2,"1259":1,"1285":3,"1299":1,"1310":1,"1323":1,"1338":1,"1358":1,"1394":1,"1418":1,"1426":1,"1431":1,"1454":1,"1463":1,"1466":1,"1487":1,"1533":1,"1543":1,"1576":2,"1732":1,"1888":1,"2003":1,"2119":1,"2201":2}}],["comparability",{"3":{"1801":1}}],["comparable",{"3":{"735":1,"766":1,"1012":1,"1014":1,"1285":1,"1349":2,"1358":2,"1395":1,"1416":1,"1435":2,"1535":2,"1536":1,"1591":1,"1793":1,"1794":1,"1861":1}}],["comparator",{"3":{"1401":1}}],["comparatively",{"3":{"108":1,"1803":1}}],["comparing",{"3":{"619":1,"964":1,"972":1,"1018":1,"1019":1,"1033":1,"1229":1,"1285":2,"1289":1,"1299":3,"1300":1,"1310":1,"1322":3,"1323":3,"1349":1,"1358":7,"1365":1,"1372":1,"1374":1,"1379":1,"1394":3,"1395":2,"1401":1,"1414":1,"1435":4,"1452":1,"1489":1,"1491":1,"1638":1,"1797":1,"1994":1,"2042":1,"2046":1,"2243":1}}],["comparisons",{"3":{"1241":2,"1247":7,"1273":1,"1285":4,"1358":10,"1395":2,"1495":1,"1629":1,"1630":1,"1634":1,"1636":1,"1638":1}}],["comparison",{"0":{"1527":1,"1578":1,"1592":1},"3":{"0":3,"150":1,"178":1,"179":1,"243":1,"261":1,"279":1,"309":2,"310":1,"311":1,"322":1,"342":1,"627":1,"766":1,"827":2,"838":1,"854":1,"889":1,"946":3,"954":1,"970":1,"972":3,"973":1,"975":1,"997":1,"1020":2,"1033":1,"1038":1,"1074":1,"1161":1,"1184":1,"1229":1,"1237":4,"1247":5,"1259":1,"1270":1,"1271":4,"1280":1,"1285":9,"1293":1,"1294":1,"1299":19,"1300":1,"1310":3,"1322":1,"1323":7,"1336":1,"1338":8,"1349":4,"1351":2,"1354":1,"1358":28,"1368":1,"1394":2,"1395":7,"1401":2,"1414":8,"1416":1,"1426":1,"1435":17,"1437":2,"1521":1,"1586":1,"1629":1,"1630":1,"1672":1,"1764":1,"1871":2,"1873":1,"1876":3,"1898":1,"1899":1,"1901":4,"1904":1,"1995":1,"2072":1,"2106":1,"2231":1}}],["comparemembers",{"3":{"1435":1}}],["comparebyscorethenindex",{"3":{"1358":1}}],["compareexchange",{"2":{"1336":1},"3":{"1336":1,"1338":2}}],["compareto",{"3":{"1285":2}}],["comparers",{"3":{"1299":1}}],["comparertype",{"3":{"1050":1,"1285":2}}],["comparer",{"3":{"585":1,"1050":1,"1212":1,"1222":1,"1229":1,"1237":2,"1247":1,"1285":8,"1299":7,"1323":3,"1338":1,"1358":6,"1394":1,"1395":2,"1430":1,"1435":4,"1437":2,"1495":1}}],["compared",{"0":{"1901":1},"3":{"0":1,"186":1,"499":1,"591":1,"592":1,"628":1,"649":1,"838":1,"946":2,"948":1,"974":1,"1247":3,"1270":2,"1279":1,"1285":3,"1299":8,"1300":1,"1322":2,"1323":2,"1338":1,"1349":1,"1358":4,"1368":1,"1394":1,"1395":4,"1397":1,"1414":1,"1416":1,"1435":6,"1437":1,"1474":1,"1630":1,"1640":1,"1698":1,"1766":1,"1961":2,"2046":1}}],["compares",{"3":{"0":2,"136":1,"318":1,"320":1,"341":1,"350":1,"363":1,"572":1,"608":1,"633":1,"715":1,"727":1,"827":1,"872":1,"889":1,"964":1,"1050":1,"1186":1,"1234":1,"1237":2,"1241":1,"1247":2,"1266":1,"1270":1,"1285":8,"1288":1,"1292":1,"1296":1,"1299":13,"1322":4,"1323":7,"1336":1,"1338":2,"1349":4,"1352":1,"1358":12,"1368":1,"1394":7,"1395":3,"1414":7,"1431":1,"1434":1,"1435":13,"1437":3,"1454":1,"1455":1,"1466":1,"1487":1,"1489":1,"1726":1,"1838":1,"1901":3,"1993":1,"1995":1,"2023":1,"2045":1,"2046":1,"2058":1,"2106":1,"2143":1}}],["compare",{"2":{"549":1},"3":{"0":3,"157":1,"310":1,"318":1,"319":1,"342":2,"343":1,"344":1,"484":1,"490":1,"491":1,"492":1,"493":1,"549":1,"633":1,"838":1,"857":1,"862":1,"973":1,"996":2,"1212":1,"1229":2,"1237":7,"1270":4,"1285":8,"1291":1,"1296":1,"1299":5,"1308":1,"1309":1,"1310":2,"1315":1,"1322":5,"1323":9,"1338":1,"1349":3,"1358":15,"1368":2,"1379":2,"1394":4,"1395":5,"1401":1,"1404":1,"1414":3,"1416":1,"1435":19,"1437":1,"1454":1,"1536":1,"1576":2,"1667":1,"1748":1,"1802":1,"1809":1,"1870":1,"1871":2,"1876":2,"1904":2,"1907":1,"1908":1,"1911":2,"1993":3,"2183":1}}],["compensated",{"3":{"1358":1}}],["compensate",{"3":{"536":1,"1323":1,"2166":1,"2168":1}}],["compensates",{"3":{"536":1,"1415":1,"1435":1,"1748":1}}],["compensating",{"3":{"0":4,"49":1,"166":1,"293":1,"534":2,"536":7,"538":2,"776":1,"809":3,"810":2,"1212":2,"1280":2,"1285":4,"1358":1,"1368":1,"1395":2,"1414":1,"1435":2,"1476":1,"1525":1,"1533":2,"1595":1,"1748":2,"1764":1,"1791":1,"1848":1,"1851":1,"1857":2,"1865":1,"2112":1,"2162":4,"2163":1,"2164":1,"2167":3,"2168":1,"2188":1,"2205":1,"2231":2}}],["compensations",{"3":{"541":1}}],["compensation",{"0":{"2162":1},"1":{"533":1,"534":1,"535":1,"536":1,"537":1,"538":1,"539":1,"540":1,"541":1,"2160":1,"2161":1,"2162":1,"2163":1,"2164":1,"2165":1,"2166":1,"2167":1,"2168":1},"3":{"0":4,"533":1,"534":2,"536":4,"537":1,"538":3,"539":1,"541":2,"793":1,"809":1,"810":1,"813":2,"815":1,"896":2,"901":1,"926":1,"987":2,"992":1,"1012":1,"1093":1,"1147":1,"1212":2,"1285":1,"1525":1,"1546":2,"1571":1,"1576":1,"1590":2,"1664":1,"1748":1,"1754":1,"1778":2,"2159":2,"2160":2,"2162":2,"2166":3,"2167":2,"2168":3,"2179":1,"2205":1,"2214":1,"2231":1}}],["competition",{"3":{"1395":2}}],["competitors",{"3":{"1285":1,"2143":1}}],["competing",{"3":{"0":1,"341":1,"342":2,"1285":2,"1322":2,"1416":1,"1435":2,"1452":1,"1454":1,"1650":1,"1874":1,"1876":1,"2163":1}}],["competencies",{"0":{"2218":1}}],["competent",{"3":{"708":1}}],["competes",{"3":{"905":1,"1322":1,"1368":1}}],["compete",{"3":{"517":1,"1258":2,"1259":2,"1270":1,"1316":1,"1322":1,"1358":3,"1458":1}}],["compound",{"3":{"1259":1,"1338":1,"1686":1}}],["compounds",{"3":{"1259":1,"1323":1}}],["compounding",{"3":{"872":1}}],["composability",{"3":{"546":1,"1154":1,"1358":1}}],["composable",{"1":{"1812":1,"1813":1,"1814":1,"1815":1,"1816":1},"3":{"0":1,"110":1,"544":1,"1202":1,"1203":1,"1229":2,"1247":2,"1265":1,"1270":1,"1271":1,"1285":1,"1308":1,"1349":1,"1358":4,"1395":1,"1415":1,"1426":1,"1661":1,"1811":1,"1812":2,"1813":1,"1816":1,"1831":1,"1996":2,"2205":1}}],["composing",{"3":{"43":1,"545":1,"550":1,"552":1,"749":1,"826":1,"1018":1,"1227":1,"1239":1,"1247":3,"1271":1,"1285":1,"1298":1,"1299":2,"1300":1,"1315":1,"1322":1,"1323":8,"1338":1,"1346":1,"1349":1,"1358":9,"1394":1,"1395":1,"1414":2,"1426":1,"1435":2,"1478":1,"1479":1,"1804":1,"1814":1,"1832":1,"1990":1}}],["compositor",{"0":{"1479":1}}],["compositeformat",{"3":{"1368":1}}],["compositekeything",{"3":{"1199":3,"1207":1}}],["composites",{"3":{"0":1,"545":1,"547":1,"549":1,"1234":1,"1247":6,"1394":1}}],["composite",{"3":{"0":2,"265":1,"268":1,"368":1,"370":2,"374":1,"375":1,"545":2,"546":1,"549":1,"550":2,"551":1,"564":1,"591":1,"594":1,"599":1,"624":1,"626":2,"629":1,"644":1,"1247":4,"1269":1,"1270":1,"1271":6,"1278":1,"1285":6,"1299":1,"1323":1,"1346":2,"1349":7,"1358":21,"1361":4,"1368":11,"1375":1,"1379":1,"1386":1,"1394":13,"1395":6,"1411":1,"1414":2,"1416":1,"1452":1,"1453":1,"1454":1,"1466":1,"1530":1,"1576":1,"1581":1,"1639":1,"1665":1,"1828":1,"2135":1,"2231":1}}],["compositional",{"3":{"1285":1}}],["compositiontestentity",{"3":{"1199":4,"1207":1}}],["composition",{"0":{"1315":1,"1321":1,"1353":1,"1448":1,"1485":1,"1934":1,"2055":1},"1":{"580":1,"581":1,"582":1,"583":1,"584":1,"585":1,"586":1,"587":1,"646":1,"647":1,"648":1,"649":1,"650":1,"651":1,"652":1,"653":1,"1312":1,"1313":1,"1314":1,"1315":1,"1316":1,"1317":1,"1318":1,"1319":1,"1320":1,"1321":1,"1322":1,"1415":1,"1484":1,"1485":1,"1486":1,"1487":1,"1488":1,"1489":1,"1490":1,"1491":1,"1492":1,"1493":1},"3":{"0":9,"115":2,"122":1,"123":1,"135":1,"265":1,"326":1,"543":4,"545":1,"547":1,"549":3,"550":3,"580":1,"582":2,"583":2,"584":2,"585":1,"587":1,"591":1,"646":1,"650":1,"651":1,"680":1,"682":1,"749":1,"825":1,"826":1,"847":1,"856":1,"881":1,"890":1,"913":1,"914":2,"980":1,"984":1,"1016":2,"1018":3,"1021":1,"1066":1,"1192":7,"1202":7,"1203":7,"1212":3,"1229":3,"1247":12,"1260":1,"1269":2,"1270":4,"1271":5,"1276":1,"1284":1,"1285":18,"1299":1,"1300":3,"1301":1,"1306":1,"1308":15,"1309":2,"1310":12,"1311":2,"1312":4,"1313":1,"1316":1,"1319":4,"1320":1,"1321":2,"1322":21,"1323":17,"1327":1,"1328":1,"1338":6,"1339":1,"1347":1,"1349":2,"1351":1,"1352":1,"1353":1,"1358":43,"1367":1,"1368":5,"1369":1,"1378":1,"1379":10,"1380":1,"1391":1,"1394":6,"1395":14,"1399":1,"1401":3,"1402":1,"1412":1,"1414":11,"1415":17,"1416":21,"1417":1,"1420":1,"1426":2,"1427":1,"1435":21,"1437":3,"1448":2,"1484":1,"1486":1,"1487":2,"1489":1,"1491":2,"1495":9,"1512":1,"1525":3,"1554":2,"1576":3,"1585":1,"1640":1,"1649":1,"1659":1,"1661":4,"1789":1,"1790":1,"1814":1,"1828":1,"1832":1,"1833":2,"1883":1,"1885":1,"1914":1,"1922":1,"1923":1,"1934":1,"1940":2,"1943":1,"1947":1,"1955":1,"2031":1,"2055":1,"2059":1,"2073":1,"2076":1,"2114":1,"2118":1,"2122":2,"2171":1,"2226":1,"2231":3}}],["composeconnectionstring",{"3":{"1435":1}}],["composeconfirmationlink",{"3":{"1322":1}}],["composemessage",{"3":{"1285":1}}],["composer",{"3":{"1247":2,"1379":2,"1388":1,"1394":14,"1435":1}}],["composers",{"3":{"1247":2,"1394":1,"1437":1}}],["composeresetlink",{"2":{"857":1},"3":{"854":1,"857":1,"1322":3}}],["composebody",{"3":{"854":1,"1322":2}}],["composeadminconnectionstring",{"2":{"640":1},"3":{"640":1,"1435":1}}],["composedpipeline",{"2":{"121":1,"127":1},"3":{"121":1,"127":1,"1435":2}}],["composed",{"0":{"1316":1,"1828":1},"3":{"0":5,"109":1,"117":1,"265":1,"319":1,"443":1,"544":1,"545":1,"546":1,"547":1,"549":3,"573":1,"599":1,"657":1,"698":1,"699":1,"854":1,"1091":1,"1094":1,"1150":1,"1168":1,"1237":1,"1239":3,"1247":8,"1262":1,"1269":1,"1270":4,"1271":3,"1272":1,"1276":1,"1285":4,"1293":1,"1299":2,"1305":1,"1308":4,"1310":1,"1322":3,"1323":6,"1324":1,"1338":3,"1349":3,"1352":2,"1358":24,"1368":2,"1379":5,"1380":1,"1393":1,"1394":11,"1395":4,"1401":2,"1415":3,"1416":3,"1424":1,"1426":1,"1434":1,"1435":5,"1437":2,"1454":2,"1466":2,"1478":1,"1479":2,"1485":1,"1508":1,"1525":1,"1554":1,"1576":1,"1630":1,"1650":1,"1668":1,"1684":1,"1700":1,"1736":1,"1813":1,"1814":4,"1815":1,"1816":1,"1817":1,"1820":1,"1826":1,"1827":1,"1850":1,"1882":1,"1914":1}}],["composesubject",{"3":{"854":1,"1322":2}}],["composes",{"3":{"0":5,"39":1,"103":1,"109":2,"115":1,"188":1,"246":1,"265":1,"317":1,"319":1,"331":2,"351":1,"545":2,"571":1,"648":1,"657":1,"701":2,"702":1,"827":2,"896":1,"913":1,"917":1,"1018":2,"1089":1,"1154":1,"1168":1,"1212":3,"1220":1,"1229":3,"1232":1,"1237":1,"1238":1,"1246":1,"1247":3,"1267":1,"1270":3,"1271":3,"1276":1,"1283":1,"1285":15,"1299":3,"1308":5,"1310":2,"1311":1,"1312":2,"1317":1,"1322":1,"1323":15,"1336":1,"1337":1,"1338":1,"1348":1,"1349":8,"1352":1,"1358":20,"1359":1,"1365":1,"1367":1,"1368":4,"1379":2,"1388":1,"1394":18,"1395":6,"1411":1,"1414":4,"1415":3,"1416":4,"1417":1,"1426":4,"1434":1,"1435":7,"1437":1,"1466":1,"1491":2,"1506":1,"1525":2,"1576":1,"1631":1,"1652":1,"1662":1,"1671":1,"1701":1,"1804":2,"1814":2,"1823":1,"1824":1,"1850":1,"1882":1,"1919":1,"1963":1,"1991":1,"2179":2,"2229":1,"2231":1}}],["compose",{"0":{"1479":1,"1505":1,"1652":1},"1":{"1827":1,"1828":1,"1829":1,"1830":1,"1831":1,"1832":1,"1833":1},"3":{"0":2,"109":1,"117":1,"187":1,"319":1,"545":1,"618":1,"649":1,"698":1,"749":2,"819":1,"1018":1,"1091":1,"1202":1,"1203":1,"1212":1,"1232":1,"1237":3,"1238":1,"1247":2,"1259":1,"1262":1,"1269":1,"1270":3,"1271":1,"1278":1,"1285":7,"1299":3,"1308":3,"1310":2,"1312":1,"1322":5,"1323":6,"1338":2,"1349":1,"1352":1,"1357":1,"1358":5,"1367":1,"1368":1,"1379":1,"1390":1,"1394":2,"1395":2,"1401":4,"1414":1,"1415":1,"1416":1,"1421":1,"1426":2,"1435":6,"1439":1,"1449":1,"1470":1,"1478":2,"1479":18,"1481":1,"1482":1,"1554":1,"1652":1,"1661":1,"1666":1,"1717":1,"1726":1,"1778":1,"1814":1,"1816":2,"1823":1,"1827":2,"1833":2,"1854":1,"1993":1,"1996":1,"2004":1,"2037":1,"2079":1,"2169":1,"2205":1,"2226":1}}],["componentization",{"3":{"1416":1}}],["componenttype",{"3":{"1323":1,"1394":1,"1415":4}}],["componentbase",{"2":{"1323":1},"3":{"1323":9,"1382":1,"1394":2,"1395":2,"1414":4}}],["componentmodel",{"3":{"1259":1,"1285":4,"1299":11,"1308":2,"1310":2,"1322":4,"1323":12,"1338":3,"1394":10,"1395":2,"1426":2}}],["componentspage",{"3":{"1642":1,"1713":1}}],["componentspagee2etests",{"3":{"618":1,"1199":1,"1207":2,"1435":2,"1576":2,"1642":1,"1713":1}}],["componentsgallery",{"3":{"1323":1,"1435":1}}],["componentssnapshottests",{"3":{"1199":3,"1207":6,"1416":1,"1435":3,"1437":4,"1524":1,"1525":2,"1530":1,"1534":1}}],["components",{"1":{"1323":1},"2":{"650":1,"1323":3,"1415":1},"3":{"0":3,"88":1,"109":2,"135":1,"265":1,"273":1,"275":1,"370":1,"412":1,"413":4,"415":1,"426":1,"490":1,"556":7,"560":1,"618":1,"648":1,"649":2,"650":2,"651":2,"653":1,"682":2,"861":2,"863":1,"864":1,"931":1,"954":1,"1004":1,"1018":1,"1059":5,"1062":1,"1119":1,"1184":1,"1202":1,"1203":9,"1207":94,"1212":1,"1213":1,"1229":2,"1279":1,"1285":2,"1299":1,"1310":3,"1322":1,"1323":97,"1338":1,"1380":1,"1388":2,"1394":31,"1395":15,"1401":2,"1413":1,"1414":5,"1415":16,"1416":60,"1425":1,"1427":1,"1431":3,"1432":1,"1433":2,"1435":39,"1436":4,"1437":16,"1452":1,"1453":1,"1485":1,"1486":2,"1487":3,"1490":1,"1524":1,"1525":4,"1529":1,"1530":2,"1531":1,"1534":1,"1554":7,"1555":2,"1556":1,"1563":1,"1571":2,"1576":10,"1585":4,"1590":1,"1596":1,"1640":2,"1642":2,"1643":3,"1652":2,"1668":2,"1686":3,"1759":1,"1897":1,"2042":1,"2053":1,"2072":1,"2074":2,"2076":2,"2077":1,"2078":1,"2079":1,"2115":1,"2117":1,"2118":1,"2120":2,"2121":1,"2175":1,"2201":1,"2226":1}}],["component",{"0":{"1431":1,"1554":1,"2077":1},"1":{"951":1,"952":1,"953":1,"954":1,"955":1,"956":1,"957":1,"958":1,"959":1,"960":1},"2":{"1380":1},"3":{"0":12,"88":1,"109":4,"135":4,"139":2,"165":1,"189":1,"201":1,"265":2,"266":2,"267":1,"273":5,"275":1,"374":1,"375":1,"397":1,"407":1,"412":1,"413":1,"423":1,"427":1,"482":3,"484":1,"511":1,"556":3,"577":1,"647":1,"649":7,"650":1,"651":1,"682":3,"683":3,"684":1,"689":1,"694":1,"827":2,"881":1,"910":1,"915":1,"931":1,"951":1,"952":1,"953":4,"954":6,"955":2,"956":3,"957":1,"960":2,"996":1,"999":1,"1004":2,"1057":1,"1059":2,"1201":1,"1202":1,"1203":1,"1212":4,"1213":2,"1216":1,"1229":1,"1237":1,"1285":3,"1299":2,"1308":1,"1309":1,"1310":9,"1322":3,"1323":178,"1332":1,"1338":4,"1349":2,"1358":3,"1380":2,"1382":1,"1383":1,"1384":1,"1388":3,"1389":1,"1390":1,"1392":1,"1394":118,"1395":41,"1401":16,"1408":1,"1413":2,"1414":7,"1415":22,"1416":100,"1425":1,"1426":1,"1427":2,"1428":1,"1430":1,"1431":3,"1433":1,"1435":79,"1436":5,"1437":9,"1442":1,"1446":1,"1449":1,"1454":3,"1460":1,"1464":1,"1466":4,"1473":2,"1475":1,"1486":1,"1487":4,"1490":5,"1495":3,"1496":1,"1498":1,"1512":1,"1525":5,"1529":1,"1530":2,"1531":1,"1535":1,"1543":1,"1554":2,"1555":2,"1556":3,"1564":2,"1571":2,"1576":3,"1585":1,"1590":1,"1626":1,"1642":1,"1644":1,"1652":2,"1659":2,"1668":6,"1670":2,"1701":2,"1710":1,"1713":1,"1749":1,"1758":2,"1772":1,"1795":1,"1864":1,"1867":1,"2042":2,"2053":1,"2054":1,"2058":1,"2071":2,"2074":1,"2076":2,"2077":2,"2078":2,"2079":3,"2082":4,"2084":1,"2085":1,"2115":4,"2116":2,"2117":2,"2118":2,"2120":2,"2122":3,"2146":1,"2155":2,"2201":1,"2229":1,"2231":2}}],["comes",{"3":{"0":3,"101":1,"195":2,"229":1,"318":1,"360":1,"402":1,"413":1,"446":1,"572":1,"599":1,"601":1,"635":1,"651":1,"665":1,"673":1,"678":1,"690":1,"692":1,"697":1,"716":1,"726":1,"741":2,"766":1,"768":1,"818":1,"905":1,"915":1,"939":1,"953":1,"972":1,"982":1,"988":1,"996":1,"1018":1,"1042":1,"1050":1,"1115":1,"1184":1,"1225":1,"1228":1,"1229":1,"1237":2,"1244":2,"1247":2,"1254":1,"1255":1,"1259":2,"1270":3,"1275":2,"1285":16,"1293":1,"1299":5,"1300":1,"1308":7,"1310":10,"1311":1,"1321":1,"1322":5,"1323":12,"1331":1,"1335":1,"1338":3,"1349":5,"1358":36,"1363":1,"1368":3,"1372":1,"1379":3,"1387":1,"1394":23,"1395":24,"1401":6,"1414":9,"1416":3,"1418":1,"1421":1,"1424":1,"1434":1,"1435":4,"1437":2,"1440":1,"1446":1,"1450":1,"1454":1,"1459":1,"1465":1,"1466":1,"1473":1,"1476":1,"1483":1,"1490":1,"1491":1,"1497":1,"1590":1,"1626":1,"1652":1,"1701":2,"1709":1,"1726":1,"1824":1,"1836":1,"1922":1,"1926":1,"1947":1,"1950":1,"1953":1,"1981":1,"1996":1,"2004":1,"2010":1,"2014":1,"2017":1,"2018":1,"2033":1,"2073":1,"2095":2,"2110":1,"2111":1,"2139":1,"2145":1,"2160":2,"2164":1,"2167":1,"2183":1}}],["come",{"0":{"140":1,"1441":1,"1786":1},"3":{"0":2,"95":1,"135":1,"219":1,"265":1,"273":1,"282":1,"329":1,"360":1,"372":1,"397":1,"400":1,"401":1,"402":1,"529":1,"544":1,"583":1,"617":1,"626":1,"633":1,"640":1,"649":1,"657":1,"703":1,"726":1,"741":2,"774":1,"818":1,"827":1,"889":1,"905":1,"907":1,"913":1,"914":1,"954":1,"1069":1,"1090":1,"1092":1,"1099":1,"1124":2,"1186":1,"1194":1,"1195":2,"1211":1,"1217":1,"1231":1,"1237":1,"1241":1,"1242":1,"1247":4,"1256":1,"1259":4,"1262":1,"1271":1,"1285":16,"1299":3,"1305":1,"1308":4,"1310":2,"1311":1,"1314":1,"1315":1,"1322":2,"1323":18,"1327":1,"1330":1,"1331":1,"1338":12,"1342":1,"1343":1,"1344":1,"1349":9,"1353":1,"1358":6,"1368":2,"1383":1,"1387":1,"1394":14,"1395":8,"1398":1,"1401":7,"1408":1,"1409":1,"1413":2,"1414":5,"1415":2,"1416":5,"1424":1,"1426":2,"1432":1,"1435":11,"1437":3,"1440":1,"1441":2,"1442":2,"1444":1,"1448":1,"1453":1,"1454":1,"1466":1,"1484":1,"1488":1,"1491":1,"1502":1,"1626":1,"1630":2,"1645":1,"1650":1,"1676":1,"1701":1,"1729":1,"1730":1,"1759":1,"1760":1,"1764":1,"1825":1,"1842":1,"1875":1,"1880":1,"1981":1,"1984":1,"1985":1,"1986":1,"2001":1,"2052":1,"2075":1,"2080":1,"2090":1,"2111":1,"2127":1,"2141":1}}],["commodity",{"3":{"1435":1,"1899":1}}],["commons",{"3":{"1435":2}}],["commontags",{"2":{"1468":1},"3":{"1435":1,"1464":1,"1466":2,"1468":1,"1525":1,"1534":1,"1590":1}}],["commonest",{"3":{"1270":1}}],["commonauditdeletioncolumnsandoutboxordering",{"3":{"1209":6}}],["commonarchitecturemap",{"2":{"80":1,"132":1,"1161":1,"1890":1,"2102":1},"3":{"80":2,"132":1,"1161":1,"1199":33,"1207":2,"1322":2,"1323":2,"1435":69,"1486":1,"1488":4,"1576":1,"1890":1,"2102":1}}],["commonv1141pushnotificationdedupindexsoftdeletefilter",{"3":{"1209":2,"1285":1}}],["commonv1120outboxleaseandsoftdeleteindexfilters",{"3":{"564":1,"891":4,"1209":8}}],["commonvalidationrules",{"3":{"1207":12,"1271":36,"1299":2,"1358":67,"1585":1,"1828":1}}],["commonvalidationrulestests",{"3":{"1199":1,"1207":8,"1271":4}}],["commoninvariantstests",{"3":{"1199":1,"1207":3,"1237":1}}],["commoninvariants",{"2":{"1271":1,"1349":2,"1358":1,"1401":1,"1404":1,"2084":1},"3":{"1199":25,"1203":1,"1207":2,"1230":1,"1234":3,"1237":28,"1271":9,"1308":6,"1310":2,"1343":1,"1349":36,"1358":13,"1395":11,"1401":18,"1404":1,"1414":7,"1435":1,"1661":1,"1684":1,"2084":2}}],["commonly",{"3":{"122":1,"1263":1,"1270":1,"1271":1,"1281":1,"1290":1,"1294":1,"2014":1}}],["commonforwardedheaderstests",{"3":{"1199":1,"1207":2}}],["commonforwardedheadersextensions",{"3":{"220":1,"749":2,"1124":1,"1186":1,"1199":1,"1203":1,"1207":2,"1208":1,"1310":7,"1495":1}}],["commonforwardedheaders",{"2":{"220":1,"747":1,"749":1,"1124":1},"3":{"0":1,"220":2,"747":1,"749":4,"751":2,"1124":3,"1199":4,"1203":1,"1207":2,"1310":16,"1495":1}}],["common",{"0":{"95":1,"465":1,"1441":1,"1442":1,"1443":1,"1452":1,"1453":1,"1601":1,"1798":1,"1837":1,"2021":1,"2226":1},"1":{"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1,"1088":1,"1089":1,"1090":1,"1091":1,"1092":1,"1093":1,"1094":1,"1095":1,"1096":1,"1190":1,"1191":1,"1192":1,"1193":1,"1194":1,"1195":1,"1323":1,"1417":1,"1418":1,"1419":1,"1420":1,"1421":1,"1422":1,"1423":1,"1424":1,"1425":1,"1426":1,"1572":1,"1573":1,"1574":1,"1575":1,"1576":1,"1577":1,"1578":1,"1579":1,"1580":1,"1581":1,"1582":1,"1583":1,"1584":1,"1585":1,"1645":1,"1646":1,"1647":1,"1648":1,"1649":1,"1650":1,"1651":1,"1652":1,"1653":1,"1654":1,"1655":1,"1656":1,"1657":1,"1658":1,"1659":1,"1660":1,"1661":1,"1662":1,"1663":1,"1664":1,"1665":1,"1666":1,"1667":1,"1668":1,"1669":1,"1670":1,"1671":1,"1672":1,"1673":1,"1674":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1698":1,"1699":1,"1700":1,"1701":1,"1702":1,"1703":1,"1704":1,"1716":1,"1717":1,"1718":1,"1719":1,"1720":1,"1721":1,"1722":1,"1723":1,"1724":1,"1777":1,"1778":1},"2":{"24":1,"39":3,"53":1,"61":2,"67":1,"68":1,"70":1,"78":1,"85":1,"99":1,"100":1,"107":2,"109":1,"111":1,"117":1,"124":1,"126":1,"130":1,"132":2,"135":2,"137":1,"138":1,"139":3,"140":1,"141":6,"142":6,"145":2,"147":1,"150":1,"152":2,"160":1,"180":1,"186":1,"190":1,"207":2,"214":2,"216":1,"225":2,"241":2,"247":1,"250":1,"252":1,"255":2,"258":1,"259":2,"261":1,"265":1,"268":3,"273":1,"281":2,"310":1,"359":2,"370":1,"373":1,"388":1,"395":4,"400":7,"404":2,"406":1,"408":1,"412":1,"436":1,"460":1,"481":1,"482":3,"486":3,"487":3,"488":3,"489":3,"490":3,"491":3,"492":3,"493":3,"494":3,"495":3,"509":6,"511":3,"514":1,"526":1,"527":2,"528":3,"558":1,"572":2,"576":1,"577":1,"578":2,"591":1,"617":1,"618":1,"634":1,"635":1,"638":3,"648":1,"649":2,"681":1,"683":1,"684":1,"686":2,"731":1,"733":1,"789":1,"821":1,"822":3,"823":3,"825":2,"826":3,"832":1,"836":1,"838":1,"840":1,"861":1,"880":1,"897":2,"899":1,"906":1,"912":1,"914":1,"917":1,"936":1,"938":2,"939":6,"941":2,"942":1,"953":1,"954":1,"957":1,"963":1,"966":1,"982":1,"984":1,"1004":2,"1012":1,"1016":1,"1036":3,"1044":1,"1051":1,"1057":1,"1058":1,"1062":1,"1066":1,"1067":2,"1068":3,"1069":1,"1074":1,"1089":3,"1090":1,"1091":1,"1093":1,"1124":2,"1161":2,"1191":1,"1194":1,"1200":1,"1201":1,"1203":1,"1211":1,"1213":5,"1214":1,"1215":2,"1217":1,"1222":1,"1229":3,"1230":2,"1236":1,"1237":3,"1247":3,"1256":1,"1259":6,"1264":1,"1268":1,"1270":6,"1271":1,"1275":1,"1285":5,"1286":2,"1299":2,"1300":3,"1308":2,"1309":2,"1316":1,"1320":1,"1322":5,"1324":5,"1325":1,"1326":1,"1327":1,"1328":1,"1329":1,"1332":1,"1335":1,"1336":1,"1338":1,"1340":1,"1350":1,"1385":1,"1413":1,"1415":2,"1416":9,"1417":4,"1424":1,"1427":1,"1429":3,"1433":3,"1435":2,"1436":3,"1438":1,"1444":1,"1450":1,"1451":1,"1452":4,"1453":3,"1455":1,"1459":1,"1475":1,"1479":1,"1486":2,"1487":1,"1489":2,"1495":3,"1496":2,"1502":8,"1508":1,"1512":1,"1522":1,"1524":1,"1525":1,"1526":2,"1534":1,"1575":1,"1576":1,"1577":1,"1584":3,"1598":1,"1604":1,"1614":2,"1641":1,"1642":1,"1647":1,"1649":5,"1652":2,"1653":1,"1655":2,"1659":12,"1660":1,"1674":1,"1676":1,"1692":1,"1701":5,"1710":1,"1731":1,"1738":1,"1740":1,"1760":1,"1780":1,"1781":2,"1789":1,"1792":1,"1804":1,"1806":1,"1811":1,"1814":1,"1816":1,"1826":1,"1827":1,"1828":1,"1833":1,"1842":1,"1845":1,"1852":1,"1861":1,"1864":1,"1867":1,"1876":1,"1885":1,"1891":1,"1894":1,"1895":1,"1898":1,"1901":2,"1904":1,"1911":1,"1918":1,"1921":1,"1923":1,"1929":1,"1934":1,"1940":1,"1949":1,"1952":1,"1958":1,"1961":1,"1964":1,"1971":1,"1974":1,"1978":1,"1984":1,"1990":1,"1996":1,"2001":1,"2003":2,"2005":9,"2007":1,"2012":8,"2013":3,"2015":1,"2021":1,"2023":1,"2027":2,"2029":1,"2037":1,"2041":1,"2043":1,"2045":1,"2048":1,"2054":2,"2056":1,"2058":2,"2059":1,"2062":1,"2069":1,"2070":2,"2076":1,"2079":1,"2085":1,"2099":2,"2100":1,"2107":1,"2110":1,"2114":1,"2115":1,"2119":3,"2122":1,"2127":1,"2130":1,"2132":1,"2138":1,"2141":1,"2145":1,"2148":1,"2149":1,"2150":1,"2151":1,"2152":1,"2155":1,"2156":3,"2159":9,"2168":1,"2171":1,"2176":1,"2177":1,"2179":2,"2190":1,"2192":2,"2202":1,"2210":1},"3":{"0":96,"4":1,"5":1,"11":1,"15":4,"17":4,"18":1,"19":3,"22":2,"23":1,"24":11,"25":9,"26":18,"27":17,"31":5,"34":2,"39":9,"43":4,"46":1,"53":3,"54":2,"59":2,"61":4,"63":2,"67":1,"68":7,"69":1,"70":5,"71":4,"72":6,"74":14,"76":1,"78":2,"79":2,"80":6,"81":10,"82":4,"85":1,"95":3,"98":2,"99":3,"100":5,"103":5,"107":2,"108":1,"109":42,"110":1,"111":5,"115":1,"117":7,"121":10,"122":5,"123":10,"124":1,"125":10,"126":7,"128":4,"130":6,"132":4,"133":1,"134":1,"135":25,"136":7,"137":6,"138":5,"139":6,"140":4,"141":7,"142":14,"145":5,"146":3,"147":23,"149":2,"150":11,"152":5,"157":5,"160":8,"161":6,"164":2,"166":10,"168":1,"170":3,"171":3,"173":1,"175":5,"178":6,"179":11,"180":5,"181":2,"186":18,"189":11,"190":12,"193":2,"196":5,"197":1,"198":7,"200":4,"201":3,"202":14,"203":2,"207":2,"208":1,"214":4,"216":5,"217":2,"218":2,"219":6,"220":8,"225":9,"228":8,"230":8,"231":8,"235":12,"237":2,"238":1,"241":2,"243":28,"245":8,"246":17,"247":1,"250":2,"251":1,"252":3,"254":1,"255":11,"256":8,"257":2,"258":8,"259":13,"260":3,"261":5,"265":30,"268":5,"272":1,"273":3,"275":1,"281":3,"284":7,"285":6,"286":1,"290":2,"295":6,"296":2,"300":4,"301":1,"303":8,"305":14,"306":8,"310":6,"312":1,"314":6,"318":9,"325":3,"326":2,"328":1,"330":8,"331":1,"333":12,"336":1,"337":4,"341":29,"342":2,"343":6,"344":11,"345":2,"350":2,"352":2,"353":8,"359":6,"365":2,"366":1,"368":1,"369":2,"370":4,"373":10,"374":2,"375":2,"384":11,"388":1,"390":2,"391":6,"392":2,"395":6,"397":11,"400":16,"401":5,"402":3,"404":7,"406":1,"407":12,"408":8,"409":6,"412":2,"413":14,"414":1,"415":3,"416":2,"418":6,"419":1,"420":3,"423":4,"424":1,"426":4,"427":3,"428":7,"430":4,"434":3,"436":3,"442":3,"443":4,"444":4,"448":8,"450":3,"453":1,"454":2,"457":1,"459":16,"460":2,"463":6,"464":6,"465":4,"466":1,"468":3,"469":9,"470":12,"475":6,"476":8,"477":13,"481":2,"482":6,"484":2,"486":25,"487":14,"488":8,"489":15,"490":19,"491":15,"492":15,"493":15,"494":4,"495":9,"497":13,"498":1,"499":4,"502":3,"507":30,"509":12,"511":9,"512":2,"513":6,"514":5,"522":2,"523":4,"524":2,"526":3,"527":4,"528":6,"530":1,"531":3,"532":1,"534":1,"536":17,"538":2,"539":3,"540":4,"543":1,"544":2,"545":13,"547":1,"549":6,"550":2,"552":3,"556":16,"558":2,"562":1,"564":4,"566":1,"570":2,"572":13,"576":9,"577":8,"578":2,"579":1,"583":20,"585":2,"586":6,"587":2,"590":1,"591":13,"592":2,"593":3,"594":1,"597":1,"599":6,"601":3,"603":2,"604":1,"609":5,"617":2,"618":12,"620":1,"621":1,"622":1,"624":1,"626":18,"628":2,"629":5,"632":2,"633":2,"634":5,"635":2,"638":4,"640":15,"642":2,"644":2,"648":1,"649":27,"650":1,"652":2,"656":2,"657":9,"665":9,"668":2,"669":1,"674":68,"675":4,"676":18,"677":1,"681":2,"682":6,"683":2,"684":1,"685":1,"686":2,"688":1,"689":1,"690":1,"691":1,"692":2,"693":1,"696":2,"697":3,"698":30,"700":3,"702":18,"703":13,"705":1,"706":2,"707":8,"713":1,"715":10,"720":6,"723":1,"724":4,"725":8,"727":4,"731":1,"732":2,"733":11,"736":9,"739":1,"740":4,"741":9,"742":1,"744":1,"747":1,"749":6,"766":2,"774":6,"776":7,"782":14,"789":3,"790":1,"792":3,"798":12,"799":9,"803":4,"804":5,"805":3,"806":3,"809":3,"810":1,"813":2,"814":2,"818":2,"819":34,"821":1,"822":8,"823":3,"825":5,"826":6,"827":20,"830":5,"831":2,"832":9,"833":14,"836":1,"837":2,"838":5,"840":1,"846":5,"849":5,"850":2,"854":4,"857":7,"860":1,"861":10,"863":3,"864":1,"865":1,"880":3,"881":36,"883":5,"884":4,"888":4,"890":2,"892":2,"897":15,"899":1,"900":2,"904":2,"905":23,"906":14,"907":2,"908":7,"912":2,"913":14,"914":5,"915":2,"917":4,"920":1,"921":3,"922":9,"923":1,"926":5,"930":1,"931":7,"936":1,"938":3,"939":10,"940":1,"941":2,"942":1,"945":1,"946":18,"949":1,"950":1,"953":2,"954":25,"955":1,"956":5,"957":1,"958":4,"959":6,"960":6,"963":7,"964":14,"966":3,"971":2,"972":12,"975":7,"979":9,"980":11,"982":3,"983":1,"984":1,"987":4,"988":4,"990":2,"991":1,"995":6,"996":9,"999":3,"1003":5,"1004":19,"1006":5,"1007":3,"1010":2,"1012":7,"1014":1,"1016":3,"1018":6,"1034":21,"1036":3,"1037":2,"1041":8,"1042":38,"1043":2,"1044":7,"1045":4,"1049":7,"1050":38,"1051":1,"1052":4,"1054":4,"1055":4,"1057":2,"1058":8,"1059":58,"1062":4,"1066":9,"1067":20,"1068":5,"1069":6,"1070":3,"1073":2,"1074":19,"1075":4,"1077":1,"1078":2,"1082":4,"1083":6,"1085":3,"1086":2,"1088":1,"1089":10,"1090":1,"1091":38,"1093":3,"1094":2,"1095":2,"1100":4,"1103":4,"1108":15,"1116":6,"1117":1,"1119":4,"1122":2,"1123":7,"1124":27,"1125":7,"1126":6,"1127":3,"1128":4,"1132":4,"1133":12,"1135":1,"1136":1,"1137":1,"1140":14,"1150":16,"1160":7,"1161":7,"1163":1,"1164":1,"1168":22,"1170":1,"1171":1,"1173":1,"1174":4,"1175":54,"1176":1,"1180":1,"1182":3,"1183":8,"1184":40,"1185":3,"1186":4,"1187":2,"1189":1,"1190":1,"1191":5,"1192":7,"1194":1,"1195":4,"1199":3047,"1200":2,"1201":6,"1202":6,"1203":1199,"1204":3,"1206":43,"1207":9681,"1208":186,"1211":11,"1212":19,"1213":8,"1214":8,"1215":4,"1216":1,"1217":1,"1219":6,"1220":3,"1221":1,"1222":7,"1223":4,"1224":2,"1225":7,"1226":4,"1227":6,"1228":4,"1229":131,"1230":3,"1231":12,"1232":10,"1233":14,"1234":24,"1235":4,"1236":19,"1237":279,"1238":1,"1239":17,"1240":8,"1241":12,"1242":4,"1243":10,"1244":4,"1245":2,"1246":6,"1247":318,"1249":9,"1250":2,"1251":3,"1252":4,"1253":1,"1254":4,"1255":1,"1256":3,"1257":10,"1258":5,"1259":282,"1261":4,"1262":12,"1263":4,"1264":5,"1265":4,"1266":2,"1267":10,"1268":4,"1269":4,"1270":427,"1271":145,"1273":10,"1274":4,"1275":5,"1276":14,"1277":8,"1278":18,"1279":18,"1280":6,"1281":6,"1282":4,"1283":4,"1284":14,"1285":1336,"1286":2,"1287":16,"1288":13,"1289":20,"1290":24,"1291":12,"1292":18,"1293":18,"1294":12,"1295":10,"1296":60,"1297":12,"1298":4,"1299":1965,"1300":132,"1301":13,"1302":17,"1303":10,"1304":10,"1305":16,"1306":18,"1307":10,"1308":352,"1309":104,"1310":817,"1311":76,"1313":2,"1314":10,"1315":22,"1316":23,"1317":15,"1318":7,"1319":57,"1320":5,"1321":31,"1322":726,"1323":1378,"1324":14,"1325":5,"1326":5,"1327":5,"1328":3,"1329":11,"1330":2,"1331":15,"1332":22,"1333":14,"1334":2,"1335":7,"1336":15,"1337":16,"1338":465,"1340":4,"1341":1,"1347":1,"1349":75,"1350":1,"1351":1,"1352":2,"1353":1,"1358":498,"1360":2,"1361":4,"1362":4,"1365":2,"1367":1,"1368":23,"1370":2,"1371":3,"1375":1,"1377":3,"1378":1,"1379":38,"1380":1,"1381":1,"1382":5,"1383":7,"1385":7,"1387":2,"1388":1,"1391":1,"1394":155,"1395":141,"1398":1,"1399":1,"1401":36,"1403":2,"1413":5,"1414":188,"1415":36,"1416":784,"1417":6,"1418":4,"1419":12,"1420":2,"1421":6,"1422":11,"1423":10,"1424":8,"1425":2,"1426":165,"1427":11,"1428":9,"1429":18,"1430":37,"1431":9,"1432":21,"1433":13,"1434":14,"1435":3072,"1436":41,"1437":67,"1438":2,"1440":21,"1441":11,"1442":37,"1443":4,"1444":3,"1446":10,"1447":1,"1448":5,"1449":1,"1450":2,"1451":2,"1452":13,"1453":12,"1454":11,"1455":3,"1458":17,"1459":7,"1460":3,"1466":25,"1469":1,"1473":1,"1475":1,"1479":2,"1480":1,"1484":2,"1485":23,"1486":38,"1487":74,"1488":43,"1489":14,"1490":3,"1491":26,"1495":245,"1496":2,"1497":2,"1499":2,"1502":12,"1503":1,"1504":2,"1508":1,"1512":1,"1513":1,"1516":1,"1519":1,"1520":1,"1521":2,"1522":2,"1523":2,"1524":3,"1525":69,"1526":4,"1528":1,"1530":15,"1531":1,"1533":5,"1534":8,"1543":1,"1549":1,"1554":1,"1568":1,"1569":1,"1572":3,"1573":1,"1574":1,"1575":2,"1576":108,"1577":2,"1579":1,"1580":1,"1581":35,"1582":8,"1583":1,"1584":8,"1585":14,"1586":2,"1587":1,"1588":3,"1590":40,"1591":1,"1592":1,"1593":2,"1594":3,"1595":15,"1596":3,"1597":1,"1598":4,"1599":8,"1600":1,"1604":1,"1610":1,"1614":2,"1618":4,"1626":5,"1630":1,"1640":3,"1641":1,"1642":1,"1645":3,"1647":11,"1648":3,"1649":6,"1652":16,"1653":2,"1654":3,"1655":2,"1657":1,"1658":2,"1659":17,"1660":1,"1674":3,"1675":1,"1676":2,"1680":1,"1681":1,"1691":2,"1692":3,"1694":2,"1698":1,"1701":7,"1702":1,"1705":1,"1706":1,"1707":1,"1710":1,"1711":2,"1716":1,"1725":1,"1726":4,"1730":1,"1731":2,"1735":1,"1738":1,"1740":1,"1760":4,"1763":3,"1769":2,"1774":1,"1777":2,"1779":1,"1780":2,"1781":3,"1783":1,"1784":1,"1786":2,"1787":1,"1789":3,"1792":4,"1793":1,"1798":2,"1800":1,"1801":4,"1802":2,"1804":3,"1805":1,"1806":3,"1807":1,"1809":4,"1811":3,"1814":4,"1816":3,"1819":1,"1825":1,"1826":4,"1827":2,"1828":1,"1833":4,"1837":2,"1842":1,"1845":4,"1846":1,"1852":3,"1853":1,"1859":1,"1860":1,"1861":4,"1864":2,"1867":4,"1876":4,"1878":1,"1879":1,"1885":3,"1888":1,"1890":4,"1891":4,"1893":1,"1894":3,"1895":1,"1896":1,"1898":3,"1901":4,"1902":1,"1904":4,"1905":1,"1908":2,"1909":1,"1911":3,"1912":1,"1918":1,"1921":2,"1922":1,"1923":4,"1926":2,"1928":1,"1929":5,"1930":2,"1934":1,"1938":1,"1940":4,"1941":1,"1946":1,"1949":3,"1950":1,"1952":1,"1954":2,"1958":3,"1960":1,"1961":1,"1962":1,"1964":4,"1967":2,"1971":4,"1974":1,"1978":4,"1980":1,"1984":4,"1990":4,"1992":1,"1993":7,"1994":1,"1996":4,"1998":1,"1999":9,"2000":1,"2001":4,"2002":2,"2003":3,"2005":12,"2007":1,"2012":9,"2013":6,"2014":2,"2015":1,"2021":1,"2023":3,"2027":4,"2029":1,"2035":1,"2037":3,"2038":2,"2040":5,"2041":6,"2042":1,"2043":10,"2045":3,"2046":2,"2048":4,"2049":2,"2051":1,"2054":6,"2056":2,"2058":2,"2059":4,"2060":1,"2061":1,"2062":1,"2063":2,"2065":4,"2068":2,"2069":1,"2070":2,"2074":1,"2076":1,"2079":3,"2080":1,"2082":14,"2084":6,"2085":3,"2086":1,"2088":1,"2097":4,"2098":1,"2099":4,"2100":3,"2104":1,"2105":3,"2106":1,"2107":7,"2108":1,"2110":4,"2111":3,"2112":1,"2114":4,"2115":1,"2116":1,"2118":2,"2119":5,"2121":3,"2122":6,"2123":1,"2126":1,"2127":3,"2130":4,"2132":4,"2133":2,"2135":1,"2136":1,"2137":2,"2138":3,"2139":1,"2141":1,"2145":4,"2148":3,"2149":5,"2150":1,"2151":1,"2152":5,"2153":1,"2155":10,"2156":3,"2159":13,"2162":1,"2168":4,"2170":2,"2171":3,"2176":1,"2177":3,"2178":1,"2179":7,"2181":2,"2190":1,"2192":5,"2194":2,"2202":4,"2203":3,"2210":3,"2220":1,"2226":23,"2228":1,"2230":1,"2231":6,"2233":3}}],["communities",{"3":{"2237":1}}],["communitytoolkit",{"3":{"1415":2,"1416":2}}],["community",{"1":{"2237":1,"2238":1,"2239":1,"2240":1,"2241":1,"2242":1},"2":{"1363":1},"3":{"0":2,"150":2,"1035":1,"1075":1,"1077":1,"1079":1,"1339":1,"1341":3,"1349":11,"1363":2,"1368":7,"1383":1,"1391":1,"1394":3,"1416":1,"1628":1,"1630":5,"2214":1,"2215":2,"2224":1,"2237":2,"2238":1,"2239":3,"2240":1,"2244":1}}],["communicate",{"3":{"1358":1,"1771":1}}],["communicates",{"3":{"1237":1}}],["communication",{"0":{"1771":1},"3":{"59":1,"1349":1,"1358":2,"1435":1,"1488":1,"1543":1,"1555":1,"1769":1}}],["comma",{"3":{"26":1,"219":1,"330":1,"633":1,"972":1,"974":1,"1241":1,"1247":7,"1253":1,"1259":1,"1299":3,"1310":1,"1317":1,"1322":2,"1323":2,"1379":2,"1435":2,"1495":1,"1987":1}}],["command+handler",{"3":{"1590":1}}],["command+handler+request+validator+mapper",{"3":{"1590":1}}],["command+validator+handler+dto",{"3":{"1525":1}}],["commandorrequestsuffix",{"3":{"1435":1}}],["commandwithitsownvalidator",{"3":{"1435":2}}],["commandwithnovalidation",{"3":{"1435":2}}],["commandcoveredthroughtherequestbridge",{"3":{"1435":3}}],["commandpipeline",{"3":{"1435":2}}],["commandinventory",{"3":{"1435":5}}],["commandflags",{"3":{"1322":1}}],["commandtype",{"3":{"1285":3,"1322":2,"2185":1}}],["commandtypecache",{"3":{"1285":2}}],["commandtimeout",{"3":{"1285":3}}],["commandtimeoutseconds",{"2":{"71":1,"2031":1},"3":{"71":1,"819":1,"1285":4,"1319":1,"1663":1,"2031":1}}],["commandhandlers",{"3":{"1435":1}}],["commandhandlersreturnresult",{"3":{"1435":1}}],["commandhandleraskingforthewriterepository",{"3":{"1435":2}}],["commandhandler",{"3":{"1270":1}}],["commandvalidators",{"3":{"1207":1,"1414":1,"1435":12,"1488":1}}],["commandvalidatorfixtures",{"3":{"1207":48,"1435":52}}],["commandvalidatortests",{"3":{"1207":4,"1358":7}}],["commandvalidatorcoveragefitnesstests",{"2":{"1488":1},"3":{"1199":1,"1207":3,"1435":14,"1488":1}}],["commandvalidatorcoveragetests",{"3":{"128":2,"1199":1,"1207":2,"1299":2,"1358":1,"1414":6,"1435":6,"1488":2}}],["commandvalidatorcoveragetestsbase",{"2":{"117":1,"128":1},"3":{"117":2,"128":2,"1199":2,"1207":2,"1435":11}}],["commanddecoratorpipelinetests",{"3":{"1199":1,"1207":6,"1270":1}}],["commandduration",{"2":{"397":1},"3":{"397":1,"1270":3}}],["commandname",{"3":{"405":1,"1270":5,"1310":1}}],["commandrequestvalidatortests",{"3":{"1199":1,"1207":7,"1271":1}}],["commandrequestvalidator",{"2":{"117":1,"128":1,"922":1,"1665":1,"1829":1,"1833":1},"3":{"0":2,"117":1,"128":1,"922":2,"1199":6,"1203":1,"1207":2,"1263":1,"1270":5,"1271":10,"1299":4,"1315":1,"1321":1,"1322":4,"1358":6,"1414":10,"1435":4,"1665":1,"1829":2,"1833":1}}],["commandsandqueries",{"3":{"1435":1}}],["commandshavevalidators",{"3":{"1435":2}}],["commands",{"1":{"919":1,"920":1,"921":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"1039":1,"1040":1,"1041":1,"1042":1,"1043":1,"1044":1,"1045":1,"1046":1,"1260":1,"1261":1,"1262":1,"1263":1,"1264":1,"1265":1,"1266":1,"1267":1,"1268":1,"1269":1,"1270":1,"2133":1,"2134":1,"2135":1,"2136":1,"2137":1,"2138":1,"2180":1,"2181":1,"2182":1,"2183":1,"2184":1,"2185":1,"2186":1,"2187":1,"2188":1,"2189":1,"2190":1,"2191":1,"2192":1},"2":{"2190":4},"3":{"0":7,"115":1,"116":1,"117":1,"121":2,"122":2,"126":2,"223":1,"236":1,"388":1,"422":1,"426":1,"516":1,"518":2,"522":2,"523":1,"599":1,"672":1,"750":1,"753":1,"919":1,"926":1,"988":2,"991":1,"1012":1,"1014":1,"1028":1,"1039":1,"1043":1,"1044":1,"1082":1,"1085":1,"1100":1,"1192":2,"1193":1,"1202":2,"1203":2,"1212":4,"1229":1,"1259":1,"1260":1,"1261":1,"1263":2,"1264":2,"1265":1,"1267":1,"1270":25,"1271":7,"1285":13,"1300":1,"1308":1,"1310":4,"1321":1,"1322":4,"1323":1,"1338":6,"1349":5,"1351":3,"1356":1,"1358":16,"1368":1,"1370":1,"1377":1,"1379":4,"1398":1,"1401":5,"1414":5,"1435":29,"1437":1,"1445":1,"1464":1,"1466":1,"1469":3,"1488":1,"1495":1,"1496":1,"1505":1,"1506":1,"1525":1,"1542":2,"1569":2,"1576":1,"1650":1,"1659":1,"1661":2,"1665":2,"1667":1,"1670":1,"1681":1,"1685":1,"1699":1,"1720":1,"1726":1,"1729":1,"1752":1,"1773":1,"1778":2,"1819":1,"1820":1,"1827":1,"1829":2,"1917":1,"1945":1,"1946":1,"2011":1,"2035":1,"2059":1,"2087":1,"2088":1,"2095":3,"2096":1,"2110":1,"2132":2,"2133":2,"2135":1,"2137":1,"2138":1,"2145":1,"2164":1,"2179":2,"2180":1,"2186":1,"2190":4,"2191":2,"2192":1,"2202":1,"2205":2,"2229":1,"2231":2}}],["command",{"0":{"1275":1,"1356":1,"1439":1,"1729":1,"1829":1,"2004":1,"2091":1,"2162":1},"1":{"2002":1,"2003":1,"2004":1,"2005":1,"2006":1,"2007":1,"2008":1,"2009":1,"2010":1,"2011":1,"2012":1,"2013":1,"2086":1,"2087":1,"2088":1,"2089":1,"2090":1,"2091":1,"2092":1,"2093":1,"2094":1,"2095":1,"2096":1,"2097":1},"2":{"1369":1,"1822":1,"1870":1,"2184":1},"3":{"0":24,"24":1,"26":1,"27":2,"71":1,"92":1,"109":1,"113":1,"115":1,"117":3,"121":2,"122":7,"125":1,"126":1,"127":1,"128":1,"157":1,"225":1,"229":1,"230":2,"234":2,"235":3,"236":1,"245":1,"259":1,"299":2,"300":1,"341":1,"373":2,"383":2,"396":1,"397":4,"404":2,"405":4,"406":1,"408":1,"434":1,"438":1,"441":1,"444":1,"459":2,"466":1,"486":1,"490":2,"491":1,"492":1,"493":1,"516":1,"518":4,"522":3,"523":3,"528":1,"534":1,"536":4,"537":1,"539":2,"540":1,"541":2,"633":3,"690":1,"714":1,"735":1,"790":1,"794":2,"808":1,"809":1,"810":1,"813":1,"815":1,"854":1,"869":2,"876":2,"896":1,"897":2,"898":2,"900":1,"920":6,"921":2,"922":10,"923":5,"925":2,"926":20,"927":1,"986":1,"987":2,"988":2,"991":1,"994":1,"995":1,"1003":1,"1010":1,"1011":2,"1013":2,"1016":1,"1018":2,"1019":1,"1022":2,"1024":1,"1030":1,"1034":1,"1042":6,"1043":1,"1044":4,"1046":1,"1068":1,"1086":1,"1099":1,"1100":5,"1101":1,"1102":2,"1104":1,"1116":2,"1132":2,"1133":2,"1150":1,"1175":1,"1177":1,"1202":1,"1203":1,"1212":4,"1213":2,"1217":1,"1218":1,"1224":1,"1233":2,"1237":1,"1238":1,"1246":1,"1247":7,"1252":2,"1259":6,"1260":4,"1262":1,"1263":13,"1264":4,"1265":10,"1266":1,"1267":4,"1268":2,"1270":240,"1271":25,"1273":2,"1275":7,"1277":1,"1285":60,"1288":1,"1296":1,"1299":20,"1300":8,"1303":3,"1305":1,"1306":1,"1308":29,"1310":22,"1311":1,"1312":2,"1315":5,"1317":10,"1321":4,"1322":90,"1331":1,"1338":9,"1345":1,"1346":3,"1348":1,"1349":18,"1351":4,"1353":2,"1356":7,"1358":413,"1366":2,"1368":14,"1369":1,"1372":1,"1373":4,"1374":1,"1375":1,"1379":43,"1389":2,"1394":7,"1395":53,"1398":3,"1401":75,"1402":1,"1405":1,"1406":3,"1409":1,"1410":3,"1414":108,"1430":4,"1435":142,"1436":5,"1437":4,"1439":3,"1441":1,"1442":2,"1444":1,"1445":1,"1449":1,"1452":1,"1453":1,"1454":2,"1457":3,"1463":1,"1464":2,"1466":3,"1474":2,"1477":2,"1482":1,"1486":1,"1487":1,"1488":3,"1491":1,"1495":10,"1507":1,"1525":2,"1533":1,"1541":1,"1546":1,"1547":1,"1569":2,"1571":1,"1576":6,"1584":1,"1590":3,"1601":1,"1630":1,"1645":1,"1649":1,"1650":11,"1653":1,"1657":2,"1660":4,"1661":2,"1665":1,"1667":1,"1670":1,"1682":1,"1684":7,"1685":2,"1688":1,"1690":1,"1691":2,"1693":1,"1696":2,"1700":5,"1701":4,"1718":1,"1725":2,"1726":7,"1728":2,"1729":3,"1764":1,"1773":3,"1778":1,"1780":1,"1802":1,"1816":1,"1817":2,"1820":5,"1821":2,"1822":5,"1824":1,"1825":1,"1826":3,"1827":3,"1829":5,"1830":2,"1832":1,"1833":2,"1839":1,"1856":1,"1870":1,"1874":1,"1878":1,"1882":3,"1912":2,"1913":1,"1917":6,"1919":5,"1922":2,"1932":2,"1933":1,"1943":3,"1945":2,"1946":1,"1947":1,"1949":2,"1961":1,"2001":1,"2002":2,"2003":1,"2004":1,"2005":2,"2011":1,"2012":2,"2029":1,"2035":1,"2047":1,"2086":2,"2091":2,"2092":1,"2094":1,"2095":5,"2096":2,"2097":1,"2104":1,"2110":1,"2132":1,"2135":4,"2136":1,"2137":1,"2138":1,"2153":1,"2155":2,"2156":2,"2162":2,"2166":1,"2167":1,"2168":3,"2180":1,"2182":3,"2183":3,"2184":2,"2185":2,"2186":2,"2187":2,"2188":4,"2189":3,"2190":1,"2191":6,"2192":2,"2205":1,"2208":2,"2209":1,"2210":1,"2226":1,"2229":1,"2231":2}}],["commerce",{"0":{"1602":1},"3":{"1519":1,"1587":1,"1600":1,"1660":1,"1672":2,"1680":1,"1762":2,"1784":1,"2003":1,"2109":1,"2114":1,"2220":1,"2227":2,"2230":1}}],["commercially",{"3":{"1379":1,"1453":1}}],["commercial",{"3":{"0":5,"145":1,"146":1,"147":1,"150":1,"650":1,"683":1,"810":1,"1074":1,"1075":2,"1077":1,"1079":1,"1213":1,"1259":1,"1322":1,"1341":2,"1349":1,"1368":1,"1379":1,"1383":1,"1391":1,"1394":3,"1430":1,"1435":3,"1488":1,"1490":1,"1568":1,"1575":1,"1576":1,"1630":1,"1648":1,"1670":1,"1672":1,"1737":1,"2167":1,"2225":1,"2233":1}}],["commentid",{"3":{"1650":1}}],["commenting",{"3":{"627":1,"1458":1}}],["commentary",{"3":{"1270":1}}],["commentedoutgrid",{"3":{"1435":1}}],["commented",{"3":{"0":1,"142":1,"1212":2,"1259":3,"1285":1,"1299":2,"1308":1,"1322":1,"1338":2,"1358":1,"1368":2,"1395":1,"1416":2,"1435":6,"2229":1,"2231":1}}],["comment",{"2":{"429":1,"1458":3},"3":{"0":3,"93":1,"95":2,"104":2,"109":1,"137":1,"138":1,"140":1,"142":1,"147":2,"148":1,"149":1,"152":1,"245":1,"250":1,"251":1,"256":4,"257":1,"258":1,"259":1,"275":1,"276":1,"291":1,"322":1,"370":1,"371":1,"372":1,"373":2,"374":1,"390":1,"393":2,"422":1,"424":1,"425":2,"426":2,"428":3,"429":3,"448":3,"453":1,"454":1,"459":1,"464":1,"468":1,"470":1,"476":1,"478":1,"490":1,"491":1,"492":2,"493":1,"512":1,"528":1,"530":1,"531":1,"540":1,"543":1,"547":1,"556":1,"559":1,"564":1,"576":1,"593":1,"626":1,"627":1,"629":1,"636":1,"638":2,"640":1,"669":1,"674":2,"676":1,"682":1,"685":1,"703":1,"741":1,"743":1,"749":6,"750":1,"751":1,"757":1,"765":1,"766":1,"769":1,"774":1,"777":1,"819":1,"833":3,"837":4,"839":4,"841":2,"847":1,"870":1,"871":1,"876":4,"877":1,"954":1,"982":1,"1019":1,"1074":4,"1075":1,"1076":1,"1116":2,"1124":1,"1133":1,"1135":1,"1161":1,"1162":1,"1168":1,"1212":1,"1213":1,"1219":1,"1222":1,"1227":1,"1228":1,"1229":21,"1231":1,"1232":1,"1233":1,"1235":1,"1237":30,"1239":1,"1243":1,"1246":2,"1247":42,"1259":58,"1270":48,"1271":8,"1273":3,"1275":1,"1277":1,"1278":2,"1282":3,"1283":2,"1285":156,"1299":166,"1300":5,"1308":46,"1309":4,"1310":73,"1311":5,"1315":2,"1316":1,"1322":50,"1323":229,"1325":2,"1326":1,"1327":1,"1328":1,"1329":2,"1331":3,"1332":2,"1338":166,"1341":3,"1342":2,"1345":1,"1346":1,"1349":87,"1355":1,"1358":165,"1361":1,"1363":2,"1364":1,"1365":1,"1366":1,"1367":1,"1368":33,"1379":44,"1388":1,"1394":158,"1395":222,"1400":1,"1401":90,"1404":1,"1413":1,"1414":111,"1415":25,"1416":50,"1418":1,"1421":1,"1422":1,"1426":5,"1435":163,"1440":10,"1441":3,"1442":6,"1443":2,"1444":10,"1446":2,"1450":4,"1451":1,"1452":7,"1453":1,"1454":35,"1455":3,"1456":2,"1457":2,"1458":10,"1459":4,"1462":1,"1464":3,"1466":36,"1471":2,"1472":3,"1474":5,"1477":1,"1478":1,"1479":1,"1482":1,"1485":2,"1487":5,"1488":6,"1489":2,"1490":1,"1491":4,"1495":11,"1525":2,"1576":2,"1584":1,"1588":2,"1590":4,"1595":4,"1629":2,"1650":2,"1652":1,"1684":5,"1685":6,"1726":1,"1727":1,"1737":1,"1781":1,"1789":1,"1800":2,"1814":1,"1815":1,"1820":1,"1876":1,"1885":1,"1888":1,"1895":1,"1901":1,"1944":1,"1994":1,"1999":2,"2003":1,"2023":1,"2038":1,"2043":1,"2047":3,"2048":2,"2056":1,"2074":2,"2084":1,"2097":1,"2098":1,"2103":1,"2111":1,"2116":1,"2121":1,"2125":2,"2130":1,"2155":1,"2156":1,"2157":1,"2158":1,"2162":1,"2163":2,"2165":1,"2166":2,"2167":1,"2171":1,"2174":1,"2177":1,"2195":1,"2196":1}}],["comments",{"2":{"1729":1},"3":{"0":1,"119":1,"121":1,"147":1,"220":1,"256":1,"276":1,"286":1,"290":1,"296":1,"390":2,"393":1,"468":1,"526":1,"543":1,"545":1,"547":1,"556":1,"616":1,"626":4,"629":1,"642":1,"700":1,"750":1,"751":1,"833":1,"868":1,"869":1,"875":1,"982":1,"986":1,"989":1,"1129":1,"1219":1,"1226":1,"1229":1,"1237":2,"1259":2,"1262":1,"1270":1,"1285":9,"1299":7,"1300":2,"1308":3,"1310":5,"1323":7,"1337":1,"1338":9,"1343":1,"1349":5,"1358":9,"1379":6,"1394":4,"1395":11,"1401":6,"1414":2,"1415":2,"1416":1,"1426":1,"1435":6,"1437":1,"1442":1,"1450":2,"1466":1,"1469":3,"1474":1,"1475":1,"1485":1,"1506":1,"1551":1,"1576":2,"1584":1,"1628":1,"1650":2,"1652":1,"1675":1,"1685":1,"1700":3,"1729":1,"1815":1,"1825":2,"1826":1,"1895":1,"1994":1,"2012":1,"2048":1,"2155":1}}],["commitfailingdbcontext",{"3":{"1198":1,"1199":3,"1207":1,"1495":1}}],["commitments",{"3":{"1395":1}}],["commitment",{"3":{"168":1,"971":1,"1212":1,"1237":1,"1368":1,"1395":1,"2032":1,"2042":1}}],["committransaction",{"2":{"1821":1},"3":{"1285":5,"1821":1}}],["committable",{"3":{"1277":1}}],["committee",{"3":{"1017":1,"1365":1,"1368":1,"1394":1,"2239":1}}],["committedsources",{"2":{"1839":1,"1851":1},"3":{"988":1,"1285":1,"1839":1,"1851":1}}],["committed",{"0":{"451":1,"577":1,"1465":1},"1":{"588":1,"589":1,"590":1,"591":1,"592":1,"593":1,"594":1,"595":1},"3":{"0":8,"24":1,"120":2,"130":1,"135":2,"201":2,"225":1,"255":1,"369":1,"370":3,"371":1,"373":1,"390":1,"444":1,"446":1,"451":4,"452":2,"453":1,"454":2,"459":1,"461":1,"465":1,"535":1,"536":3,"537":1,"570":1,"572":2,"573":1,"577":2,"588":1,"591":4,"592":1,"692":1,"715":1,"765":1,"770":1,"782":1,"793":1,"809":1,"860":1,"865":1,"869":1,"876":2,"896":1,"897":3,"972":1,"973":1,"974":1,"976":1,"988":3,"989":1,"990":1,"1044":2,"1067":2,"1142":1,"1212":2,"1229":1,"1247":3,"1251":1,"1258":1,"1259":12,"1263":3,"1270":6,"1275":1,"1278":2,"1285":11,"1300":1,"1303":1,"1308":1,"1310":3,"1317":1,"1322":7,"1323":1,"1356":1,"1357":2,"1358":5,"1394":1,"1395":2,"1398":1,"1401":7,"1414":4,"1427":1,"1428":2,"1430":2,"1431":1,"1434":1,"1435":29,"1437":7,"1444":1,"1452":5,"1453":2,"1454":5,"1455":1,"1458":1,"1459":1,"1460":1,"1478":1,"1485":1,"1487":4,"1489":2,"1491":4,"1495":1,"1523":1,"1524":1,"1525":6,"1530":5,"1531":2,"1533":1,"1534":1,"1547":1,"1575":1,"1576":11,"1585":1,"1590":4,"1662":1,"1663":1,"1667":1,"1672":2,"1684":2,"1737":1,"1748":1,"1764":2,"1768":1,"1769":1,"1783":1,"1793":1,"1799":1,"1800":1,"1820":1,"1821":2,"1836":1,"1838":1,"1839":2,"1917":5,"1922":2,"1928":1,"1929":2,"1932":1,"1939":1,"1945":1,"1947":1,"2043":3,"2046":3,"2048":2,"2054":1,"2058":1,"2061":1,"2078":1,"2099":1,"2160":2,"2163":2,"2166":2,"2168":1,"2191":2,"2210":1,"2214":1,"2231":3}}],["committing",{"3":{"0":1,"986":1,"988":1,"1212":1,"1259":1,"1285":2,"1299":2,"1358":2,"1401":3,"1409":1,"1454":1,"1458":1,"1525":1,"1844":1,"1928":1,"2043":1,"2065":1}}],["commit",{"0":{"452":1,"1839":1},"1":{"985":1,"986":1,"987":1,"988":1,"989":1,"990":1,"991":1,"992":1},"3":{"0":23,"15":1,"20":2,"27":2,"91":1,"94":1,"115":1,"120":2,"147":1,"195":1,"226":1,"350":1,"368":1,"373":2,"374":1,"383":3,"424":2,"425":2,"426":1,"444":2,"459":2,"463":1,"465":2,"466":1,"516":1,"518":4,"521":3,"522":3,"523":2,"524":2,"530":1,"534":1,"535":1,"536":3,"537":1,"538":2,"539":1,"540":1,"541":1,"563":2,"577":1,"620":1,"628":1,"629":1,"633":1,"642":1,"692":1,"714":2,"715":1,"716":1,"758":1,"759":1,"781":1,"782":1,"809":1,"863":2,"868":1,"870":2,"875":1,"897":5,"898":1,"900":2,"931":1,"933":1,"985":1,"986":2,"987":2,"988":26,"989":7,"990":6,"991":4,"992":3,"1019":1,"1042":2,"1043":1,"1044":1,"1100":3,"1104":3,"1116":4,"1118":2,"1185":1,"1212":2,"1233":1,"1252":2,"1254":1,"1259":7,"1262":1,"1263":2,"1265":1,"1270":12,"1273":1,"1274":1,"1278":7,"1285":41,"1288":2,"1289":1,"1299":4,"1300":1,"1308":2,"1310":1,"1317":2,"1322":13,"1351":1,"1356":2,"1358":31,"1368":1,"1395":7,"1398":1,"1401":20,"1405":1,"1409":2,"1414":23,"1435":10,"1437":3,"1451":1,"1453":5,"1454":5,"1458":2,"1472":6,"1474":1,"1477":4,"1482":1,"1491":1,"1495":20,"1507":1,"1523":1,"1525":1,"1533":1,"1576":1,"1590":6,"1599":3,"1626":2,"1630":1,"1650":6,"1652":1,"1654":1,"1661":1,"1663":2,"1664":2,"1666":1,"1671":1,"1672":1,"1684":1,"1685":1,"1698":1,"1720":1,"1726":2,"1732":1,"1748":2,"1760":2,"1764":3,"1768":1,"1773":1,"1800":1,"1818":1,"1821":1,"1826":1,"1836":1,"1837":2,"1838":1,"1839":12,"1844":1,"1846":1,"1851":4,"1852":1,"1860":1,"1887":1,"1940":1,"1943":3,"1949":2,"1957":1,"1976":1,"1985":1,"2056":1,"2095":1,"2112":1,"2159":1,"2160":2,"2161":1,"2162":2,"2163":1,"2167":2,"2168":3,"2184":1,"2191":2,"2192":1,"2231":2}}],["commits",{"0":{"1212":1,"2229":1},"3":{"0":3,"24":1,"94":1,"193":1,"195":1,"201":1,"352":1,"424":1,"444":1,"446":1,"452":2,"466":1,"517":1,"535":1,"536":2,"538":1,"539":3,"562":1,"564":2,"744":1,"793":1,"897":1,"900":1,"988":2,"1041":2,"1116":1,"1142":1,"1174":1,"1182":1,"1210":1,"1214":1,"1252":1,"1258":1,"1259":4,"1263":1,"1270":1,"1272":1,"1275":1,"1278":1,"1285":9,"1299":2,"1310":1,"1317":1,"1322":4,"1323":2,"1349":1,"1358":6,"1395":2,"1401":6,"1404":1,"1409":2,"1414":8,"1430":1,"1435":2,"1437":2,"1452":1,"1472":1,"1477":1,"1489":1,"1491":1,"1495":9,"1505":1,"1525":1,"1650":1,"1654":1,"1672":1,"1764":1,"1799":1,"1818":1,"1821":1,"1834":1,"1837":1,"1838":1,"1839":2,"1851":1,"1909":1,"1923":1,"1932":2,"1939":1,"1949":1,"1977":1,"2062":1,"2090":1,"2160":1,"2162":2,"2163":1,"2167":1,"2168":2,"2180":2,"2182":1}}],["corner",{"3":{"1846":1}}],["cornerstone",{"3":{"1778":1}}],["corporate",{"3":{"1323":1}}],["corpus",{"0":{"1434":1},"3":{"309":1,"945":2,"1016":1,"1018":5,"1019":5,"1020":2,"1091":3,"1092":2,"1093":1,"1299":1,"1426":10,"1427":1,"1434":7,"1436":1,"1437":3,"1491":5,"1495":4,"1576":1}}],["corollary",{"3":{"1237":1,"1996":1}}],["corspolicyallowall",{"2":{"774":1,"1108":1},"3":{"774":1,"1108":1,"1310":1}}],["corspolicyallowspecificorigins",{"2":{"774":1,"1108":1},"3":{"774":1,"1108":1,"1310":1}}],["cors",{"0":{"1335":1},"2":{"774":1,"776":1,"777":1},"3":{"0":5,"59":1,"60":1,"572":1,"749":1,"773":2,"774":6,"775":1,"776":3,"777":1,"778":4,"826":3,"834":1,"1107":1,"1108":1,"1109":1,"1111":1,"1112":1,"1184":2,"1185":1,"1187":2,"1189":1,"1212":1,"1310":10,"1323":1,"1335":4,"1337":4,"1338":9,"1378":1,"1428":1,"1435":3,"1437":1,"1442":1,"1446":2,"1466":2,"1487":1,"1652":1,"1665":1,"1669":1,"1670":1,"1672":1,"1686":1,"1701":1,"1897":2,"1903":1,"2022":2,"2054":1,"2150":1,"2231":1}}],["corrupting",{"3":{"1308":1,"1478":1}}],["corruption",{"3":{"0":1,"51":1,"53":2,"68":1,"1012":1,"1311":3,"1357":1,"1358":1,"1466":1,"1473":2,"1525":1,"1531":1,"1543":1,"1576":1,"1585":1,"1590":1,"1629":1,"1638":1,"1662":1,"1708":1,"2032":1,"2163":1}}],["corrupted",{"3":{"360":1,"692":1,"1226":1,"1229":1,"1395":1,"1437":1,"1466":1,"2141":1}}],["corrupt",{"3":{"312":1,"743":1,"947":1,"1223":1,"1229":2,"1237":1,"1285":1,"1299":1,"1310":1,"1323":2,"1394":2,"1401":1,"1416":5}}],["correspond",{"3":{"1368":1,"1395":1}}],["correspondence",{"3":{"1358":1}}],["corresponds",{"3":{"1338":1,"1395":1,"1640":2}}],["corresponding",{"3":{"838":1,"1237":1,"1247":1,"1270":1,"1271":1,"1279":1,"1285":1,"1299":1,"1322":1,"1323":3,"1358":1,"1394":1,"1416":1,"1435":4,"1465":1,"1516":1,"1626":1,"1640":1,"1653":1,"1764":1}}],["correspondingly",{"3":{"0":1,"549":1,"551":1,"1323":1,"2040":1}}],["correctfollowing",{"3":{"1525":1}}],["correcting",{"3":{"1395":1,"1454":1,"1459":1,"1495":1,"1775":1}}],["correction",{"3":{"22":1,"115":1,"137":2,"251":1,"254":1,"256":1,"260":1,"261":1,"323":1,"364":1,"365":1,"423":1,"424":1,"428":2,"490":1,"492":1,"493":1,"567":1,"616":1,"682":1,"684":1,"876":1,"1156":1,"1270":1,"1271":1,"1299":1,"1323":2,"1338":1,"1358":1,"1395":1,"1437":1,"1454":1,"1495":2,"1533":1,"1630":1,"1638":1,"1748":1,"1764":2,"1766":2,"1768":2,"1769":1,"1775":1}}],["corrections",{"3":{"21":1,"248":1,"259":1,"335":1,"383":1,"497":2,"764":1,"833":1,"857":1,"1040":1,"1495":6,"2231":1}}],["correctable",{"3":{"1322":1,"1765":1}}],["correctamente",{"3":{"265":1}}],["corrects",{"3":{"137":1,"138":1,"141":1,"171":1,"275":1,"616":1,"909":1,"1323":2,"1358":1,"1395":1,"1414":1,"1435":1,"1764":1}}],["correct",{"0":{"2164":1},"3":{"0":1,"20":1,"110":1,"119":1,"134":1,"136":1,"137":1,"157":1,"159":1,"196":1,"224":1,"235":1,"242":1,"243":1,"244":1,"251":1,"252":2,"254":2,"266":2,"281":1,"319":1,"320":1,"343":1,"360":1,"363":1,"391":1,"395":2,"490":1,"492":1,"493":1,"509":1,"534":1,"537":1,"539":1,"611":1,"633":1,"664":1,"665":1,"692":1,"699":1,"716":1,"735":1,"742":2,"743":1,"748":1,"768":1,"801":1,"809":1,"811":1,"818":1,"820":1,"821":1,"826":1,"827":1,"828":2,"829":1,"830":1,"837":1,"876":1,"890":1,"924":1,"930":1,"932":1,"933":1,"946":2,"948":1,"955":1,"965":2,"972":1,"989":2,"996":1,"998":1,"1019":1,"1093":1,"1100":1,"1118":1,"1135":1,"1160":1,"1169":1,"1212":1,"1219":1,"1229":2,"1237":1,"1247":7,"1252":1,"1259":5,"1260":1,"1270":6,"1285":22,"1299":9,"1300":1,"1308":2,"1310":5,"1311":1,"1322":7,"1323":13,"1338":6,"1349":3,"1358":16,"1368":1,"1379":1,"1394":5,"1395":11,"1401":3,"1414":8,"1416":15,"1435":18,"1440":1,"1441":1,"1442":1,"1454":5,"1466":2,"1473":1,"1476":1,"1478":1,"1488":3,"1495":3,"1497":3,"1547":1,"1557":1,"1562":1,"1576":2,"1584":1,"1590":1,"1595":1,"1630":1,"1636":1,"1637":2,"1638":1,"1639":1,"1650":1,"1653":1,"1665":1,"1684":1,"1685":2,"1707":1,"1745":1,"1748":1,"1749":1,"1764":1,"1766":1,"1767":1,"1784":1,"1804":1,"1824":1,"1825":1,"1832":1,"1837":1,"1842":2,"1843":2,"1844":1,"1851":1,"1869":1,"1883":1,"1884":1,"1893":1,"1894":1,"1897":1,"1903":2,"1909":2,"1910":1,"1912":1,"1913":1,"1926":1,"1941":1,"1946":1,"1948":1,"1995":1,"2025":1,"2033":2,"2048":1,"2049":1,"2060":2,"2065":1,"2067":1,"2073":1,"2095":1,"2106":1,"2112":1,"2117":1,"2126":1,"2141":1,"2151":1,"2164":1,"2167":1,"2168":1,"2191":1}}],["correctly",{"3":{"0":2,"160":1,"235":1,"507":1,"550":1,"571":1,"573":2,"790":1,"849":1,"904":1,"1246":1,"1247":1,"1275":1,"1285":3,"1296":1,"1299":3,"1300":1,"1310":3,"1322":4,"1323":1,"1338":2,"1352":1,"1358":6,"1368":1,"1394":3,"1395":3,"1415":1,"1416":2,"1426":1,"1428":1,"1435":14,"1436":1,"1437":1,"1456":1,"1465":1,"1495":1,"1555":2,"1563":1,"1637":1,"1670":1,"1684":1,"1685":1,"1707":1,"1814":1,"1908":1,"1922":1,"1939":1,"2054":1,"2069":1,"2084":1}}],["correctness",{"0":{"1878":1},"3":{"0":2,"20":1,"227":2,"244":1,"291":1,"295":1,"312":1,"320":1,"483":1,"538":1,"549":2,"566":1,"633":1,"642":1,"666":1,"698":1,"708":1,"733":1,"743":1,"827":1,"855":1,"996":2,"997":1,"1154":1,"1247":2,"1259":2,"1263":1,"1270":5,"1285":5,"1299":4,"1300":3,"1308":1,"1309":1,"1310":1,"1322":2,"1323":6,"1338":3,"1349":1,"1358":9,"1379":2,"1394":4,"1395":6,"1401":2,"1409":1,"1414":3,"1416":2,"1426":1,"1435":2,"1436":1,"1475":1,"1533":1,"1555":2,"1706":1,"1707":2,"1818":1,"1820":1,"1825":1,"1840":1,"1875":1,"1877":1,"1880":1,"1885":2,"1914":1,"1915":1,"1920":2,"1922":1,"1923":1,"1939":1,"1946":1,"1969":1,"1995":1,"2140":1,"2167":1}}],["corrected",{"3":{"0":2,"27":1,"43":1,"57":1,"63":1,"74":1,"82":1,"94":1,"130":3,"137":1,"140":1,"145":2,"161":1,"173":1,"181":1,"203":1,"220":1,"231":1,"256":2,"258":1,"263":1,"268":1,"273":1,"279":2,"285":1,"298":1,"345":1,"348":2,"365":1,"375":1,"407":1,"418":4,"426":1,"444":1,"446":2,"464":1,"465":1,"466":1,"468":3,"492":1,"502":1,"514":1,"526":1,"531":2,"534":5,"540":1,"549":1,"554":1,"578":1,"586":1,"589":1,"604":1,"607":2,"624":2,"638":1,"660":1,"672":1,"680":1,"685":1,"688":1,"693":1,"696":2,"713":1,"723":2,"736":1,"744":1,"761":1,"764":1,"777":1,"780":1,"797":1,"803":1,"806":1,"822":1,"833":1,"841":1,"857":1,"864":1,"877":1,"892":1,"900":1,"909":1,"917":1,"927":1,"960":1,"962":1,"986":1,"1010":1,"1029":1,"1037":1,"1048":1,"1055":1,"1070":1,"1103":1,"1323":3,"1437":1,"1454":1,"1495":20,"1499":2,"1766":1,"1768":1,"2084":1,"2231":2}}],["correlatable",{"3":{"1349":1}}],["correlating",{"3":{"1338":2,"1344":1,"1640":1}}],["correlationheader",{"3":{"1435":1}}],["correlationcontexttests",{"3":{"1199":1,"1207":2}}],["correlationcontext",{"3":{"1199":7,"1203":1,"1207":2,"1259":1,"1270":1,"1285":2,"1310":10,"1322":1}}],["correlation",{"0":{"1336":1},"3":{"0":9,"26":3,"59":1,"125":1,"193":1,"202":3,"395":2,"397":3,"398":2,"400":2,"407":1,"409":1,"572":1,"608":1,"664":1,"707":1,"714":1,"716":1,"749":1,"808":1,"810":1,"813":1,"815":1,"825":1,"826":5,"827":3,"829":1,"834":1,"899":1,"916":1,"1042":1,"1202":1,"1203":1,"1212":2,"1251":1,"1253":3,"1259":5,"1263":1,"1270":5,"1275":1,"1277":2,"1285":10,"1299":4,"1310":23,"1317":1,"1322":6,"1324":1,"1336":1,"1337":1,"1338":9,"1349":1,"1395":1,"1401":3,"1435":6,"1437":3,"1446":1,"1486":1,"1487":1,"1543":1,"1549":2,"1571":1,"1576":1,"1590":1,"1659":1,"1669":3,"1670":1,"1820":1,"2009":1,"2023":2,"2027":1,"2054":1,"2154":1,"2167":1,"2192":1,"2214":1,"2231":2}}],["correlationidmiddlewaretests",{"3":{"1199":1,"1207":2}}],["correlationidmiddleware",{"2":{"400":1,"409":1,"826":1,"2009":1,"2013":1,"2023":1},"3":{"397":8,"400":1,"409":2,"698":1,"826":2,"1199":3,"1203":1,"1207":2,"1285":1,"1310":13,"1336":1,"1338":2,"1437":1,"2009":1,"2013":1,"2023":1}}],["correlationid",{"2":{"15":1,"26":1,"810":1,"2185":1},"3":{"0":1,"15":1,"26":1,"715":1,"810":1,"1259":6,"1263":1,"1270":7,"1285":7,"1299":1,"1310":8,"1322":3,"1435":1,"1665":1,"2185":1}}],["correlate",{"3":{"813":1,"1322":1,"1395":3,"1401":4,"2192":1}}],["correlates",{"3":{"0":1,"856":1,"1285":1,"1664":1}}],["correlated",{"3":{"0":1,"24":1,"1255":1,"1259":1,"1270":4,"1400":1,"1401":2}}],["corelayers",{"2":{"1488":1},"3":{"1435":2,"1488":1}}],["cores",{"3":{"875":1,"1466":1}}],["corepublicresources",{"2":{"574":1},"3":{"574":1,"1435":3,"1487":1}}],["core",{"0":{"1505":1,"1642":1,"1661":1,"1763":1,"2205":1,"2218":1},"1":{"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1,"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1,"1272":1,"1273":1,"1274":1,"1275":1,"1276":1,"1277":1,"1278":1,"1279":1,"1280":1,"1281":1,"1282":1,"1283":1,"1284":1,"1285":1,"1862":1,"1863":1,"1864":1,"1865":1,"1866":1,"1867":1,"2139":1,"2140":1,"2141":1,"2142":1,"2143":1,"2144":1,"2145":1},"2":{"145":1,"147":1,"149":1,"152":1,"810":1,"960":1,"1067":1,"1074":1,"1075":1,"1225":1,"1311":1,"1425":1,"2175":1},"3":{"0":8,"10":1,"15":3,"24":5,"25":4,"26":9,"27":4,"31":1,"38":1,"39":4,"43":2,"54":1,"59":2,"62":1,"63":1,"68":2,"70":1,"71":3,"72":2,"74":5,"79":1,"80":1,"81":2,"82":1,"100":1,"109":8,"110":1,"111":2,"117":1,"121":3,"122":2,"123":4,"125":3,"126":3,"142":1,"145":4,"147":3,"149":1,"150":3,"152":1,"157":1,"161":1,"164":1,"166":8,"170":3,"171":2,"174":1,"185":1,"186":3,"190":1,"193":1,"196":2,"198":3,"200":2,"201":1,"202":7,"203":1,"207":1,"228":4,"230":4,"231":4,"235":1,"243":1,"246":6,"255":2,"265":2,"284":2,"285":3,"290":1,"291":1,"292":1,"295":2,"303":2,"305":5,"306":1,"310":3,"312":1,"314":2,"318":2,"328":1,"330":7,"331":1,"333":5,"336":1,"337":3,"341":8,"343":2,"344":6,"352":1,"353":2,"358":1,"359":5,"365":1,"370":1,"384":5,"387":2,"391":2,"395":2,"396":1,"397":10,"398":1,"400":1,"404":2,"405":1,"407":2,"408":1,"409":2,"411":1,"415":1,"442":1,"443":2,"444":2,"448":1,"451":1,"459":5,"463":2,"465":2,"469":4,"470":6,"471":1,"475":2,"476":3,"477":4,"481":1,"482":1,"497":6,"498":1,"499":4,"502":2,"522":1,"523":2,"524":1,"536":4,"539":1,"544":3,"545":9,"549":3,"550":1,"551":1,"552":1,"556":1,"558":1,"562":1,"563":1,"564":2,"572":1,"576":1,"583":6,"586":3,"591":1,"592":1,"593":1,"640":4,"644":1,"656":1,"657":5,"664":1,"665":1,"674":16,"676":7,"692":1,"697":1,"698":13,"700":1,"702":9,"703":6,"706":1,"707":3,"715":5,"720":3,"724":2,"725":2,"727":1,"732":1,"733":4,"736":3,"740":1,"741":4,"748":1,"782":3,"798":5,"799":4,"800":1,"804":1,"809":1,"810":2,"813":1,"818":1,"819":12,"822":1,"826":1,"831":1,"833":1,"837":1,"840":1,"846":4,"849":1,"850":1,"853":1,"854":3,"857":3,"858":1,"860":2,"861":2,"862":1,"881":2,"888":2,"890":1,"892":1,"897":2,"904":1,"905":4,"906":4,"908":3,"914":1,"915":1,"916":1,"917":1,"921":1,"922":8,"923":1,"926":5,"930":1,"931":7,"936":1,"939":1,"946":7,"953":1,"960":1,"962":1,"963":4,"964":5,"979":2,"987":3,"988":1,"995":3,"996":2,"999":1,"1004":2,"1018":2,"1034":8,"1036":1,"1037":1,"1041":4,"1042":19,"1043":1,"1044":2,"1045":2,"1049":4,"1050":13,"1052":2,"1055":2,"1058":3,"1059":20,"1067":1,"1074":4,"1075":2,"1081":1,"1082":3,"1083":2,"1086":1,"1091":7,"1100":2,"1103":1,"1107":1,"1108":3,"1116":2,"1119":1,"1124":1,"1132":1,"1133":5,"1140":7,"1150":5,"1160":1,"1168":4,"1174":1,"1175":26,"1192":1,"1202":3,"1203":3,"1211":1,"1212":5,"1213":4,"1217":2,"1219":3,"1220":1,"1222":1,"1223":2,"1225":3,"1226":3,"1227":2,"1228":2,"1229":36,"1230":1,"1231":7,"1232":5,"1233":8,"1234":12,"1235":1,"1236":7,"1237":94,"1239":7,"1240":4,"1241":7,"1242":2,"1243":6,"1244":2,"1246":4,"1247":119,"1249":1,"1251":1,"1259":63,"1260":1,"1261":2,"1262":4,"1263":2,"1264":2,"1265":3,"1266":1,"1267":5,"1268":1,"1269":1,"1270":141,"1271":55,"1272":2,"1273":5,"1274":2,"1275":2,"1276":7,"1277":4,"1278":10,"1279":9,"1280":4,"1281":1,"1282":2,"1283":2,"1284":7,"1285":547,"1287":5,"1288":7,"1289":9,"1290":12,"1291":6,"1292":9,"1293":8,"1294":6,"1295":5,"1296":17,"1298":2,"1299":582,"1300":47,"1301":6,"1302":8,"1303":4,"1304":5,"1305":7,"1306":9,"1307":2,"1308":121,"1309":46,"1310":87,"1311":13,"1313":1,"1314":3,"1315":9,"1316":9,"1317":7,"1318":2,"1319":24,"1320":2,"1321":15,"1322":231,"1323":25,"1329":3,"1331":1,"1332":3,"1335":1,"1336":1,"1337":1,"1338":44,"1340":1,"1342":2,"1349":29,"1350":1,"1351":1,"1358":219,"1359":1,"1360":1,"1361":2,"1362":1,"1368":12,"1379":14,"1394":5,"1395":84,"1401":16,"1413":1,"1414":77,"1415":5,"1416":3,"1417":1,"1418":2,"1419":6,"1420":1,"1421":3,"1422":5,"1423":5,"1424":3,"1425":2,"1426":39,"1429":2,"1430":1,"1432":3,"1434":4,"1435":81,"1436":7,"1437":8,"1440":2,"1441":1,"1442":6,"1446":1,"1452":1,"1453":1,"1455":2,"1460":1,"1464":1,"1466":5,"1485":4,"1486":2,"1487":4,"1488":3,"1489":2,"1495":8,"1496":1,"1525":3,"1530":2,"1533":1,"1534":1,"1558":1,"1559":1,"1575":2,"1576":13,"1581":3,"1582":1,"1584":2,"1585":2,"1590":1,"1595":1,"1598":1,"1601":1,"1604":1,"1628":1,"1629":1,"1639":1,"1640":1,"1641":1,"1642":1,"1645":1,"1647":1,"1652":1,"1659":9,"1660":1,"1663":1,"1667":1,"1670":2,"1676":1,"1681":1,"1691":1,"1692":2,"1706":1,"1707":1,"1738":1,"1751":1,"1778":2,"1781":1,"1789":1,"1792":1,"1804":2,"1805":1,"1806":1,"1809":3,"1810":1,"1812":1,"1814":6,"1816":2,"1824":1,"1828":1,"1852":1,"1861":2,"1862":2,"1863":1,"1867":1,"1894":1,"1895":1,"1902":1,"1921":1,"1928":1,"1933":1,"1939":1,"1969":1,"1987":1,"1988":1,"1993":2,"1997":1,"1999":5,"2001":1,"2005":2,"2009":1,"2015":2,"2025":1,"2027":2,"2031":1,"2038":1,"2039":1,"2042":2,"2049":1,"2052":1,"2054":2,"2058":1,"2060":1,"2063":2,"2065":3,"2070":1,"2078":1,"2098":1,"2103":2,"2106":1,"2138":1,"2139":3,"2141":1,"2145":3,"2152":2,"2153":1,"2155":2,"2159":1,"2175":1,"2206":1,"2207":1,"2210":1,"2214":1,"2218":3,"2219":1,"2224":1,"2226":4,"2231":4,"2233":1,"2235":1}}],["cone",{"1":{"2049":1,"2050":1,"2051":1,"2052":1,"2053":1,"2054":1,"2055":1,"2056":1,"2057":1,"2058":1,"2059":1},"3":{"2049":2,"2059":1,"2209":1}}],["conviction",{"3":{"2038":1}}],["convey",{"3":{"1395":1}}],["conveyed",{"3":{"1358":1,"1557":1}}],["conveys",{"3":{"332":1,"1989":1}}],["convene",{"3":{"2239":1}}],["convenient",{"3":{"1051":1,"1285":1,"1323":1,"1338":1,"1379":2,"1394":1,"1395":1,"1414":1,"1416":1,"1435":2,"1800":1,"1954":1,"1983":1,"2038":1,"2039":1}}],["conveniences",{"3":{"1270":1,"1394":1,"1435":1,"1804":1}}],["convenience",{"3":{"0":1,"318":1,"501":1,"782":1,"826":1,"1144":1,"1229":1,"1237":1,"1244":2,"1247":1,"1270":2,"1278":1,"1285":5,"1299":1,"1300":1,"1308":1,"1310":2,"1314":1,"1322":1,"1323":7,"1349":1,"1358":2,"1368":1,"1394":1,"1395":3,"1401":3,"1414":1,"1415":1,"1416":2,"1435":9,"1626":1,"1639":2,"1640":1,"1884":1,"1914":1,"1951":1,"1955":1,"1958":2,"1988":1,"1993":1,"2012":1,"2177":1,"2178":1}}],["conventiontests",{"3":{"1435":1}}],["conventionforeignkeysrestrictdeletes",{"3":{"1435":4}}],["conventiondeletes",{"2":{"1083":1},"3":{"1083":1,"1435":3}}],["conventionally",{"3":{"1237":1}}],["conventional",{"3":{"318":1,"657":1,"853":1,"1226":1,"1270":2,"1285":1,"1299":2,"1310":1,"1322":5,"1323":1,"1338":2,"1340":1,"1349":1,"1358":2,"1361":1,"1368":2,"1394":1,"1395":3,"1414":1,"1422":1,"1435":3,"1441":1,"2073":1,"2231":1}}],["conventionsource",{"3":{"1285":2}}],["conventions",{"0":{"1214":1,"1280":1},"1":{"1210":1,"1211":1,"1212":1,"1213":1,"1214":1,"1215":1,"1216":1},"3":{"0":1,"40":1,"164":1,"169":1,"707":1,"711":1,"801":1,"888":1,"889":1,"890":1,"891":1,"1004":2,"1020":1,"1035":1,"1050":2,"1051":1,"1083":2,"1086":1,"1090":1,"1174":1,"1176":1,"1178":1,"1192":2,"1202":1,"1203":6,"1207":46,"1210":1,"1212":1,"1229":1,"1247":2,"1259":3,"1270":1,"1271":1,"1272":1,"1273":1,"1280":7,"1281":1,"1285":29,"1299":9,"1309":1,"1310":4,"1323":1,"1349":1,"1361":2,"1368":1,"1379":4,"1395":7,"1402":1,"1414":3,"1426":3,"1430":2,"1435":9,"1436":6,"1437":2,"1451":1,"1487":1,"1488":2,"1495":1,"1525":1,"1536":2,"1545":1,"1551":2,"1556":1,"1570":2,"1573":1,"1576":1,"1590":1,"1653":1,"1670":3,"1686":1,"1689":1,"1700":1,"1701":1,"1774":1,"1796":1,"1810":1,"1951":1,"1957":1,"2062":1,"2063":1,"2087":2,"2097":1,"2103":3,"2210":1}}],["convention",{"0":{"1882":1,"1951":1},"1":{"89":1,"90":1,"91":1,"92":1,"93":1,"94":1,"95":1,"96":1,"97":1,"98":1,"99":1,"100":1,"101":1,"102":1,"103":1,"104":1,"105":1,"1001":1,"1002":1,"1003":1,"1004":1,"1005":1,"1006":1,"1007":1,"1008":1},"2":{"1083":1,"2063":1},"3":{"0":22,"3":1,"4":1,"5":1,"6":1,"47":1,"53":2,"76":2,"79":1,"80":2,"89":1,"107":1,"117":1,"122":1,"134":1,"139":1,"140":1,"141":1,"142":1,"147":1,"162":1,"167":1,"169":1,"175":1,"216":1,"279":1,"283":1,"299":1,"301":1,"302":1,"305":1,"342":1,"343":1,"360":1,"381":1,"382":1,"388":1,"455":1,"469":1,"472":1,"473":1,"482":1,"483":1,"550":1,"564":1,"565":2,"583":1,"585":1,"620":1,"676":1,"713":1,"718":1,"726":1,"741":1,"743":1,"782":1,"784":1,"785":2,"801":2,"802":1,"845":1,"887":1,"888":1,"889":11,"890":3,"891":2,"892":1,"893":2,"899":1,"916":1,"922":1,"924":2,"947":1,"948":1,"956":1,"989":1,"1001":1,"1003":1,"1004":2,"1005":1,"1008":1,"1018":1,"1019":2,"1034":4,"1035":1,"1050":3,"1051":4,"1052":1,"1083":8,"1084":2,"1085":1,"1087":1,"1091":1,"1134":1,"1137":1,"1160":2,"1162":1,"1164":1,"1168":2,"1171":1,"1192":2,"1210":1,"1212":1,"1214":1,"1229":1,"1237":4,"1239":1,"1247":8,"1249":2,"1255":1,"1259":7,"1270":14,"1271":9,"1280":13,"1285":66,"1299":36,"1308":3,"1309":1,"1310":27,"1311":2,"1314":1,"1315":1,"1322":19,"1323":18,"1334":1,"1338":2,"1349":12,"1352":1,"1353":1,"1358":82,"1361":4,"1368":12,"1379":5,"1394":11,"1395":43,"1399":1,"1401":12,"1414":31,"1415":3,"1416":3,"1417":1,"1424":1,"1430":3,"1435":87,"1436":1,"1437":3,"1454":2,"1460":1,"1466":2,"1472":1,"1487":1,"1488":2,"1491":1,"1492":1,"1495":3,"1496":1,"1499":1,"1525":3,"1538":1,"1540":1,"1544":1,"1574":2,"1576":6,"1583":1,"1584":1,"1585":2,"1590":2,"1595":1,"1629":1,"1630":1,"1631":1,"1648":2,"1650":4,"1655":3,"1660":1,"1661":1,"1663":1,"1671":1,"1672":1,"1684":1,"1685":1,"1700":1,"1702":1,"1719":1,"1729":1,"1735":1,"1789":1,"1791":1,"1792":1,"1793":1,"1799":1,"1801":2,"1809":1,"1810":1,"1829":3,"1848":2,"1855":1,"1857":3,"1859":1,"1874":1,"1876":1,"1881":1,"1882":1,"1885":1,"1888":1,"1890":1,"1901":1,"1948":1,"1950":2,"1951":3,"1953":1,"1955":1,"1957":2,"1958":2,"1999":2,"2012":1,"2030":1,"2038":2,"2042":3,"2044":1,"2048":1,"2052":1,"2062":1,"2063":2,"2092":1,"2094":1,"2096":1,"2099":1,"2106":1,"2136":1,"2137":1,"2162":1,"2166":1,"2167":1,"2176":1,"2231":3}}],["conversely",{"3":{"1395":1,"1549":1}}],["conversation",{"3":{"1358":1,"1422":1,"1426":1}}],["conversion",{"3":{"0":1,"109":1,"111":1,"360":1,"545":1,"547":1,"550":1,"657":1,"698":1,"1237":4,"1269":1,"1270":2,"1271":1,"1280":1,"1285":4,"1299":6,"1310":1,"1323":1,"1338":1,"1349":4,"1355":1,"1358":15,"1368":9,"1394":13,"1395":4,"1414":3,"1416":1,"1426":1,"1437":2,"1576":1,"1638":1,"2145":1}}],["conversions",{"3":{"0":1,"110":1,"657":1,"798":1,"806":1,"964":2,"1034":1,"1050":2,"1191":1,"1203":11,"1207":56,"1220":1,"1229":1,"1237":4,"1273":1,"1277":1,"1280":1,"1281":4,"1285":32,"1361":1,"1368":1,"1394":1,"1495":3,"1661":1}}],["converge",{"3":{"539":1,"838":1,"840":1,"881":1,"1310":1,"1323":3,"1368":1,"1395":2,"1401":2,"1410":1,"1414":2,"1440":1,"2165":1,"2166":1}}],["convergence",{"3":{"260":1,"840":1,"1414":1,"1464":1}}],["converges",{"3":{"109":1,"1416":1,"1435":1,"1764":3,"1805":1,"2060":1}}],["converged",{"0":{"91":1},"3":{"0":2,"97":1,"101":1,"148":1,"755":1,"1090":1,"1598":1,"2024":1}}],["converttimefromutc",{"2":{"1638":1},"3":{"1638":1}}],["converttimetoutc",{"2":{"1347":1},"3":{"1347":1,"1349":2,"1394":1}}],["convertto",{"3":{"1299":1}}],["convertfrom",{"3":{"1299":2}}],["convertfromrsasecuritykey",{"2":{"1299":1},"3":{"1299":1}}],["convertfrominvariantstring",{"3":{"1285":1}}],["convertname",{"3":{"1247":1}}],["convertible",{"3":{"1237":1}}],["converting",{"3":{"0":1,"110":1,"826":1,"964":1,"1237":2,"1270":3,"1310":3,"1322":1,"1338":1,"1349":2,"1358":1,"1377":1,"1395":3,"1426":1,"1435":1,"1769":1}}],["converted",{"3":{"881":1,"964":1,"1050":1,"1234":1,"1235":1,"1244":1,"1270":1,"1285":3,"1288":1,"1299":1,"1322":2,"1323":1,"1347":1,"1349":2,"1358":4,"1379":1,"1394":3,"1395":2,"1401":1,"1414":4,"1638":1,"2066":1,"2184":1}}],["convertertype",{"3":{"1050":1,"1285":2,"1299":1}}],["converters",{"2":{"964":1,"1235":1},"3":{"0":3,"472":1,"656":1,"658":1,"659":3,"800":1,"963":1,"964":4,"1050":2,"1051":1,"1192":1,"1202":1,"1203":1,"1212":1,"1223":1,"1229":7,"1235":1,"1237":2,"1272":1,"1281":1,"1285":2,"1310":4,"1430":1,"1435":1,"1495":1,"1661":3}}],["converter",{"0":{"2141":1},"1":{"356":1,"357":1,"358":1,"359":1,"360":1,"361":1,"362":1,"363":1,"364":1,"365":1,"366":1},"3":{"0":2,"356":1,"357":1,"358":2,"359":10,"361":4,"362":2,"363":2,"364":1,"365":8,"366":1,"470":1,"471":1,"605":1,"655":1,"656":1,"657":4,"659":1,"660":1,"798":2,"800":1,"806":1,"963":2,"964":7,"965":3,"967":1,"1034":1,"1035":1,"1050":4,"1051":2,"1052":2,"1055":2,"1192":1,"1212":1,"1223":3,"1229":7,"1237":10,"1241":1,"1247":1,"1270":1,"1272":1,"1280":1,"1283":2,"1285":44,"1299":5,"1308":3,"1310":10,"1349":1,"1368":7,"1395":2,"1414":6,"1435":5,"1495":1,"1497":1,"1576":1,"1902":2,"2062":2,"2139":2,"2141":8,"2142":4,"2143":2,"2144":1,"2231":1}}],["convert",{"3":{"461":1,"905":2,"1077":1,"1299":9,"1310":1,"1323":2,"1331":1,"1338":1,"1358":1,"1394":2,"1426":1,"1435":2,"1524":1,"1575":1,"1584":1,"1767":1,"1801":1}}],["converts",{"3":{"0":3,"87":1,"109":4,"122":1,"235":1,"295":1,"610":1,"627":1,"675":1,"726":1,"758":1,"767":1,"820":1,"881":1,"947":1,"964":1,"1034":1,"1050":2,"1052":1,"1055":1,"1143":1,"1225":1,"1226":1,"1229":1,"1237":1,"1244":1,"1247":6,"1270":3,"1271":2,"1299":3,"1308":2,"1310":4,"1314":1,"1322":1,"1323":7,"1331":1,"1338":4,"1349":3,"1357":1,"1358":8,"1368":2,"1379":1,"1394":10,"1395":8,"1414":7,"1416":1,"1435":2,"1442":1,"1474":1,"1488":2,"1701":2,"1764":1,"1830":1,"2011":1,"2031":1,"2037":2,"2202":1}}],["condensed",{"3":{"1999":1,"2007":1,"2083":1,"2155":1}}],["conduct",{"3":{"1672":1}}],["conditioned",{"3":{"631":1,"633":2,"1285":1,"1534":1,"1590":1,"1873":1}}],["conditions",{"3":{"140":1,"188":1,"341":1,"549":1,"626":1,"766":1,"1073":1,"1247":1,"1270":3,"1274":1,"1309":1,"1310":3,"1323":2,"1358":4,"1368":1,"1394":1,"1416":1,"1454":1,"1462":1,"1917":1,"1963":1,"2195":1,"2202":1}}],["condition",{"3":{"1":1,"135":1,"138":2,"141":1,"195":1,"200":1,"201":1,"319":1,"342":1,"343":1,"474":2,"529":1,"567":1,"624":1,"626":4,"628":1,"629":1,"633":2,"684":1,"757":1,"764":2,"766":2,"797":1,"798":1,"800":1,"808":1,"809":1,"813":1,"831":1,"863":1,"922":1,"931":1,"997":1,"998":1,"1067":1,"1139":1,"1149":1,"1220":1,"1247":3,"1270":1,"1285":4,"1299":1,"1310":3,"1322":1,"1323":2,"1327":1,"1338":1,"1357":1,"1358":10,"1394":3,"1395":4,"1414":1,"1415":1,"1416":2,"1428":1,"1432":1,"1435":9,"1440":1,"1442":1,"1443":2,"1448":1,"1452":1,"1453":1,"1454":7,"1458":1,"1466":1,"1474":1,"1485":1,"1491":5,"1523":1,"1524":1,"1525":3,"1530":1,"1590":5,"1595":1,"1598":1,"1599":1,"1639":1,"1652":1,"1686":1,"1875":1}}],["conditionality",{"3":{"1530":1}}],["conditionalweaktable",{"2":{"1274":1},"3":{"1274":1,"1285":9,"1322":1}}],["conditionalwrite",{"3":{"1270":1}}],["conditionalwriteroottouchtests",{"3":{"1199":1,"1207":2}}],["conditionalwriteconventiontests",{"3":{"1199":2,"1207":4,"1310":2,"1379":2,"1436":2,"1437":4}}],["conditionals",{"3":{"1247":1,"1285":1,"1323":1,"1349":1,"1395":1,"1760":1}}],["conditionally",{"3":{"245":1,"674":1,"996":1,"1176":1,"1180":1,"1259":1,"1278":1,"1285":2,"1310":2,"1323":3,"1338":1,"1358":2,"1363":1,"1394":1,"1395":2,"1415":1,"1454":1,"1465":1,"1466":1,"1525":1,"1576":1,"1772":1,"1813":1,"1957":1,"2011":1}}],["conditional",{"0":{"1374":1},"3":{"0":8,"6":2,"109":1,"259":1,"260":1,"261":1,"339":1,"340":1,"341":1,"342":2,"343":2,"344":1,"401":1,"413":1,"535":1,"556":1,"599":3,"609":1,"624":3,"626":1,"703":1,"749":1,"764":1,"809":1,"879":1,"905":2,"920":1,"921":1,"922":2,"995":1,"996":2,"998":1,"1108":1,"1110":1,"1212":2,"1237":3,"1255":1,"1259":1,"1265":1,"1270":7,"1275":1,"1278":1,"1285":13,"1289":1,"1299":10,"1300":2,"1305":1,"1308":4,"1309":1,"1310":14,"1315":1,"1322":4,"1323":7,"1332":1,"1338":3,"1349":2,"1358":48,"1372":1,"1374":1,"1379":11,"1388":1,"1394":15,"1395":12,"1401":6,"1414":2,"1415":2,"1416":3,"1418":1,"1420":1,"1426":2,"1435":4,"1436":2,"1437":2,"1442":1,"1454":3,"1466":7,"1468":1,"1525":2,"1530":2,"1531":1,"1564":1,"1576":2,"1589":1,"1590":1,"1599":1,"1630":1,"1639":1,"1650":1,"1684":1,"1701":1,"1726":1,"1747":2,"1748":1,"1764":3,"1824":1,"1855":1,"1870":2,"1873":2,"1875":2,"1915":1,"1922":1,"1951":2,"1954":1,"1957":1,"2122":1,"2157":1,"2160":1,"2184":1,"2231":1}}],["conn",{"3":{"1466":1,"1495":1,"1576":1}}],["connecttimeout",{"3":{"1338":1}}],["connectasync",{"3":{"1338":1}}],["connecting",{"3":{"1190":1,"1437":1,"1454":1}}],["connectivitychangedeventargs",{"3":{"1416":1}}],["connectivitychanged",{"3":{"1416":7}}],["connectivity",{"3":{"291":1,"412":1,"413":1,"1192":1,"1202":1,"1203":1,"1323":1,"1336":1,"1394":1,"1416":15,"1454":1,"1495":1,"1668":1,"2117":1}}],["connectionid",{"3":{"1308":1}}],["connectionmode",{"3":{"1285":2}}],["connectionmultiplexer",{"2":{"1300":1},"3":{"733":1,"1300":4,"1338":2}}],["connectionname",{"3":{"243":1,"1331":2,"1338":5}}],["connectionsperuser",{"3":{"1308":1,"1322":1}}],["connectionstringexpression",{"2":{"1326":1},"3":{"1326":1,"1338":2}}],["connectionstringfor",{"3":{"1285":7}}],["connectionstringrequired",{"2":{"1175":1},"3":{"1175":1}}],["connectionstringsettingstests",{"3":{"1199":1,"1207":1}}],["connectionstringsettings",{"2":{"1175":1,"1272":1,"1279":1,"1319":1},"3":{"674":1,"1175":3,"1177":1,"1199":20,"1203":1,"1207":2,"1272":1,"1279":2,"1285":27,"1319":4,"1322":3}}],["connectionstringsettingsvalidatortests",{"3":{"1199":1,"1207":2,"1285":1,"1437":2}}],["connectionstringsettingsvalidator",{"2":{"170":1,"676":1,"1279":1,"1319":1},"3":{"170":2,"676":2,"1199":3,"1203":1,"1207":2,"1279":2,"1285":31,"1319":4,"1322":2}}],["connectionstrings",{"2":{"640":1,"1331":1,"1440":1,"1445":1,"1651":1,"1721":1,"1728":1},"3":{"166":1,"640":3,"676":1,"1034":1,"1279":3,"1285":17,"1322":6,"1331":1,"1334":2,"1338":4,"1435":2,"1440":1,"1442":1,"1445":1,"1466":2,"1651":1,"1721":1,"1728":1}}],["connectionstring",{"2":{"440":1,"601":1,"604":1,"640":1,"644":1},"3":{"0":1,"170":1,"434":1,"440":1,"601":1,"604":1,"640":3,"644":1,"1067":1,"1285":10,"1322":13,"1338":3,"1428":1,"1435":2,"1440":1,"1466":2,"2125":1}}],["connections",{"0":{"2010":1},"2":{"1442":1,"1676":1},"3":{"0":3,"174":1,"223":1,"378":1,"382":1,"384":1,"608":1,"818":1,"1035":1,"1124":1,"1273":1,"1285":3,"1295":1,"1299":1,"1301":2,"1306":2,"1308":5,"1322":3,"1323":1,"1328":2,"1338":2,"1435":3,"1442":3,"1466":5,"1473":1,"1489":1,"1652":1,"1666":1,"1676":1,"1839":1,"1944":2,"1948":1,"2010":1,"2013":1,"2157":1}}],["connectionaborted",{"2":{"135":1},"3":{"0":1,"135":2,"1308":3,"1435":3}}],["connection",{"2":{"175":1,"397":1,"914":1,"1332":1,"1336":1,"1442":1,"1445":1,"1466":1,"2009":1,"2155":1,"2158":1},"3":{"0":11,"71":1,"72":1,"100":2,"109":1,"155":1,"159":1,"164":2,"166":2,"168":1,"170":4,"171":1,"175":1,"179":1,"225":1,"227":1,"228":3,"234":2,"235":4,"236":1,"237":2,"241":1,"243":2,"245":1,"259":2,"260":2,"261":1,"358":1,"379":2,"380":2,"381":2,"384":2,"390":1,"392":1,"397":2,"572":1,"598":3,"599":7,"601":2,"602":1,"604":1,"605":1,"608":2,"610":1,"633":1,"640":5,"642":1,"643":2,"644":1,"673":1,"698":3,"699":1,"700":2,"733":1,"736":1,"742":1,"749":1,"819":2,"820":1,"827":1,"830":1,"832":1,"881":1,"905":1,"914":5,"988":2,"996":2,"998":1,"1034":3,"1036":1,"1043":1,"1066":1,"1067":1,"1123":2,"1133":1,"1150":1,"1175":4,"1176":1,"1192":1,"1212":3,"1225":1,"1259":2,"1273":1,"1276":1,"1278":1,"1279":7,"1283":1,"1285":91,"1298":1,"1299":11,"1300":1,"1306":5,"1308":19,"1310":17,"1311":3,"1319":3,"1322":30,"1323":29,"1325":3,"1327":2,"1328":2,"1329":1,"1331":4,"1332":1,"1333":3,"1334":1,"1336":1,"1337":1,"1338":63,"1348":1,"1377":1,"1378":1,"1379":4,"1394":2,"1395":6,"1399":2,"1401":1,"1414":2,"1415":2,"1416":3,"1428":6,"1429":4,"1435":31,"1437":4,"1440":3,"1441":3,"1442":7,"1443":3,"1445":2,"1446":4,"1448":1,"1450":1,"1454":1,"1459":1,"1466":26,"1472":2,"1473":2,"1476":3,"1480":2,"1485":1,"1487":4,"1489":1,"1495":2,"1525":1,"1547":1,"1590":1,"1651":3,"1652":2,"1662":1,"1663":3,"1667":2,"1669":2,"1670":1,"1676":3,"1683":4,"1687":1,"1696":1,"1701":1,"1718":2,"1719":1,"1723":1,"1726":1,"1789":1,"1805":1,"1846":1,"1848":4,"1850":1,"1852":1,"1905":1,"1915":1,"1922":1,"1926":1,"1934":6,"1939":1,"1941":2,"1942":1,"1943":2,"1944":5,"1948":1,"1949":1,"1999":2,"2001":1,"2002":1,"2006":1,"2007":3,"2009":1,"2010":1,"2011":1,"2031":1,"2052":1,"2055":1,"2117":1,"2126":1,"2139":1,"2140":1,"2155":2,"2157":2,"2158":1,"2159":1,"2192":1,"2229":1,"2231":2}}],["connect",{"3":{"212":1,"213":1,"214":3,"215":1,"216":2,"219":4,"225":1,"1193":1,"1299":1,"1310":1,"1323":12,"1338":7,"1339":1,"1341":1,"1349":3,"1358":1,"1363":1,"1368":1,"1391":1,"1394":1,"1437":2,"1440":1,"1445":1,"1451":1,"1466":2,"1476":1,"1524":1,"1525":2,"1635":1,"2146":1,"2147":1,"2149":4,"2151":1,"2152":2,"2243":1}}],["connects",{"3":{"0":1,"272":1,"382":1,"1184":1,"1358":1,"1396":1,"1435":2,"1466":2,"1472":1,"1488":1,"1595":1,"1948":1,"2046":1}}],["connected",{"3":{"0":3,"135":2,"139":1,"193":1,"200":2,"224":1,"226":1,"227":1,"272":1,"433":1,"1123":1,"1237":1,"1257":1,"1285":1,"1301":1,"1303":1,"1306":1,"1308":7,"1323":1,"1394":2,"1395":1,"1401":2,"1414":1,"1435":9,"1437":2,"1931":1,"1935":1,"1939":1,"1945":1,"2038":1}}],["conjure",{"3":{"1414":1}}],["conjunct",{"3":{"1237":1}}],["conjoined",{"3":{"1237":1}}],["concise",{"3":{"1310":1}}],["concretions",{"3":{"1308":1,"1537":1}}],["concretion",{"3":{"1308":2}}],["concretes",{"3":{"1435":1}}],["concreteclasses",{"3":{"1435":11}}],["concretely",{"3":{"1271":1,"1285":1,"1311":1,"1323":1,"1358":5,"1395":1,"1416":1,"1435":1,"1789":1,"2015":1}}],["concretedomainexception",{"3":{"1199":2,"1207":1}}],["concrete",{"0":{"1956":1},"3":{"0":3,"5":1,"7":1,"47":1,"76":2,"78":1,"80":3,"81":1,"117":1,"136":1,"151":1,"160":1,"166":1,"185":1,"189":1,"243":1,"255":1,"318":1,"329":1,"331":1,"405":1,"449":1,"468":1,"469":1,"470":2,"474":1,"477":1,"518":1,"543":1,"545":1,"583":1,"657":1,"674":2,"675":1,"714":1,"725":3,"726":1,"782":1,"799":1,"848":1,"914":1,"921":1,"922":2,"926":1,"963":2,"964":3,"965":2,"966":1,"1058":1,"1060":2,"1100":2,"1168":4,"1169":1,"1170":1,"1212":9,"1217":1,"1223":1,"1226":1,"1229":6,"1230":1,"1231":2,"1234":1,"1235":1,"1236":1,"1237":11,"1239":2,"1240":1,"1244":1,"1247":12,"1249":1,"1257":1,"1259":14,"1261":1,"1262":1,"1265":1,"1266":1,"1270":7,"1271":1,"1278":1,"1281":2,"1285":36,"1286":1,"1287":1,"1288":2,"1299":20,"1300":1,"1301":1,"1302":1,"1308":11,"1309":9,"1310":21,"1311":2,"1314":2,"1315":1,"1319":1,"1321":1,"1322":27,"1323":9,"1330":1,"1331":1,"1332":1,"1333":1,"1338":3,"1349":8,"1351":1,"1353":1,"1358":87,"1359":1,"1362":2,"1365":1,"1368":8,"1369":1,"1370":1,"1379":7,"1380":1,"1382":1,"1394":28,"1395":20,"1401":1,"1411":1,"1414":17,"1416":6,"1420":1,"1426":3,"1427":2,"1428":1,"1435":76,"1437":1,"1454":2,"1464":1,"1487":2,"1488":2,"1489":1,"1490":1,"1495":2,"1497":1,"1525":1,"1535":1,"1537":1,"1576":2,"1584":2,"1633":1,"1645":1,"1650":3,"1655":1,"1665":1,"1696":1,"1699":2,"1718":1,"1796":1,"1803":1,"1805":1,"1809":1,"1814":1,"1819":1,"1823":1,"1828":1,"1847":1,"1848":1,"1855":1,"1870":1,"1878":1,"1880":1,"1882":1,"1888":1,"1890":3,"1894":1,"1914":1,"1921":1,"1928":3,"1929":3,"1930":2,"1940":1,"1951":1,"1952":1,"1986":1,"1987":1,"2069":1,"2071":1,"2073":1,"2077":1,"2078":1,"2079":2,"2170":1,"2229":1}}],["concat",{"3":{"1270":2,"1299":1,"1300":2,"1322":1,"1394":2,"1466":1,"1495":3}}],["concatenating",{"3":{"1132":1,"1323":1,"1349":1}}],["concatenation",{"3":{"86":1,"265":2,"1018":1,"1247":1,"1310":1,"1323":1,"1358":1,"1435":4,"1525":1,"1563":1}}],["concatenates",{"3":{"219":1,"359":1,"651":1,"1242":1,"1323":1,"1338":1,"1358":1,"1394":2,"1395":1,"1421":1,"1426":1}}],["concatenated",{"3":{"0":2,"230":1,"665":1,"1018":1,"1133":2,"1135":2,"1212":1,"1241":1,"1247":1,"1285":2,"1299":2,"1323":3,"1358":1,"1394":1,"1395":1,"1415":1,"1430":1,"1435":4,"1552":1,"1563":1,"1814":1,"1988":1}}],["concluding",{"3":{"486":1,"1466":1,"1475":1}}],["concluded",{"3":{"626":2,"640":1,"1075":1,"1437":1,"1454":2,"1474":1,"1491":1,"1525":2}}],["concludes",{"3":{"529":1,"1394":1,"1444":1,"1454":1,"1455":1,"1629":1}}],["conclude",{"3":{"230":1,"626":2,"820":1,"1285":1,"1454":2,"1491":1,"1525":1,"2031":1}}],["conclusions",{"3":{"624":1,"640":1}}],["conclusion",{"3":{"0":2,"122":1,"230":1,"241":1,"256":1,"624":3,"626":2,"627":1,"640":1,"803":1,"804":1,"1051":1,"1090":1,"1263":1,"1270":2,"1299":1,"1308":1,"1358":2,"1454":4,"1455":1,"1491":2,"1820":1,"1951":1,"2169":1,"2188":1}}],["concentrating",{"3":{"1285":1,"1299":1,"1322":1,"1368":1}}],["concentrate",{"3":{"1637":1}}],["concentrates",{"3":{"799":1,"802":1,"1053":1,"1110":1,"1415":2}}],["concentrated",{"3":{"799":1,"1285":1}}],["concert",{"3":{"1270":1}}],["concerned",{"3":{"1299":2,"1939":1}}],["concern",{"0":{"1878":1},"3":{"40":1,"49":1,"70":1,"73":1,"117":1,"119":1,"134":1,"139":1,"156":1,"162":2,"176":1,"177":1,"188":1,"223":1,"302":1,"317":1,"340":1,"342":1,"346":1,"351":1,"413":1,"416":1,"447":1,"648":1,"673":1,"683":1,"692":2,"698":1,"699":1,"714":1,"717":1,"827":2,"846":1,"1091":1,"1191":1,"1192":1,"1194":1,"1200":1,"1201":1,"1229":1,"1259":2,"1260":1,"1264":1,"1267":1,"1270":8,"1271":1,"1281":1,"1285":7,"1299":7,"1308":3,"1310":8,"1311":2,"1315":1,"1322":7,"1323":8,"1338":3,"1352":1,"1358":18,"1359":1,"1368":2,"1379":9,"1386":1,"1394":10,"1395":7,"1401":5,"1414":7,"1416":6,"1421":1,"1422":1,"1426":1,"1435":16,"1489":2,"1495":3,"1506":2,"1510":1,"1514":1,"1553":1,"1576":3,"1600":1,"1637":3,"1639":1,"1640":1,"1752":1,"1814":1,"1818":1,"1820":1,"1822":1,"1825":2,"1826":4,"1830":1,"1833":1,"1851":1,"1870":1,"1885":1,"1913":1,"1914":1,"1926":1,"1927":1,"1963":1,"1968":1,"1973":1,"1991":3,"2000":1,"2069":1,"2071":1,"2106":1,"2129":1,"2133":2,"2137":1,"2138":2,"2147":1,"2174":1,"2179":2}}],["concerns",{"0":{"1752":1,"2062":1},"3":{"0":1,"39":1,"61":1,"116":3,"117":2,"194":1,"255":1,"611":1,"673":1,"826":2,"1011":1,"1013":1,"1076":1,"1191":1,"1195":1,"1200":1,"1212":1,"1231":1,"1232":1,"1237":2,"1259":1,"1260":1,"1261":1,"1270":3,"1285":6,"1299":2,"1300":1,"1310":11,"1311":2,"1312":1,"1322":1,"1323":6,"1324":1,"1338":2,"1346":1,"1358":8,"1359":1,"1379":2,"1394":2,"1395":8,"1414":2,"1415":1,"1416":8,"1435":3,"1489":1,"1504":1,"1525":1,"1533":1,"1535":2,"1537":1,"1539":1,"1541":1,"1546":1,"1550":1,"1554":1,"1564":1,"1574":1,"1598":1,"1637":2,"1640":1,"1663":1,"1679":1,"1779":1,"1795":3,"1806":1,"1809":1,"1817":2,"1818":1,"1819":1,"1825":1,"1826":2,"1915":1,"1932":1,"2022":1,"2062":1,"2068":1,"2071":1,"2073":1,"2095":1,"2138":1,"2207":1,"2229":1}}],["concession",{"3":{"557":1,"1435":1}}],["conceptual",{"3":{"1259":1,"1338":1,"1637":1}}],["conceptually",{"3":{"1201":1,"1259":1,"1299":1,"1322":1,"1338":2,"1435":1}}],["concepts",{"1":{"1210":1,"1211":1,"1212":1,"1213":1,"1214":1,"1215":1,"1216":1},"3":{"1192":1,"1210":1,"1212":1,"1213":1,"1230":1,"1234":1,"1247":1,"1285":2,"1299":1,"1308":1,"1358":1,"1368":1,"1394":1,"1501":1,"1540":1,"1866":1,"1957":1,"2229":1}}],["concept",{"1":{"1501":1,"1502":1,"1503":1,"1504":1,"1505":1,"1506":1,"1507":1,"1508":1,"1509":1,"1510":1,"1511":1,"1512":1,"1513":1,"1514":1,"1515":1,"1516":1},"3":{"0":1,"319":1,"690":1,"1004":1,"1074":1,"1190":1,"1193":1,"1217":1,"1229":18,"1237":40,"1247":42,"1259":45,"1270":61,"1271":11,"1285":179,"1299":167,"1300":12,"1308":61,"1309":12,"1310":101,"1311":6,"1322":104,"1323":164,"1338":75,"1349":118,"1358":395,"1368":43,"1379":65,"1394":169,"1395":237,"1401":102,"1414":132,"1415":20,"1416":104,"1426":26,"1435":417,"1495":11,"1501":1,"1717":1,"1726":1,"1775":1,"1813":1}}],["concurrencyconflictdetector",{"2":{"1358":1},"3":{"1358":2}}],["concurrencyconventiontests",{"3":{"341":2,"1199":2,"1207":4,"1435":38,"1488":2,"1525":1,"1576":1,"1590":1}}],["concurrencyconventiontestsbase",{"2":{"341":1,"1650":1,"1653":1,"1874":1},"3":{"341":2,"1199":3,"1207":2,"1310":1,"1435":10,"1650":1,"1653":1,"1874":1}}],["concurrencylimiter",{"3":{"1323":1}}],["concurrencylimiteroptions",{"3":{"1323":1,"1338":1}}],["concurrencytokenrequest",{"3":{"1495":2}}],["concurrencytrackingtokenstorage",{"3":{"1199":2,"1207":1}}],["concurrencytagof",{"3":{"341":1,"881":1,"1310":1,"1323":2}}],["concurrencypartitionkey",{"3":{"1323":1}}],["concurrencypartition",{"3":{"827":1,"1323":1,"1336":1,"1338":1}}],["concurrencyetagtests",{"3":{"1199":1,"1207":2,"1299":2,"1437":1}}],["concurrencyetag",{"2":{"341":1,"881":1},"3":{"341":5,"881":1,"1199":17,"1203":1,"1207":2,"1299":20,"1310":8,"1323":2,"1349":2,"1381":1,"1388":1,"1394":7,"1395":5,"1437":1,"1495":3,"1661":1}}],["concurrency",{"0":{"1908":1},"1":{"338":1,"339":1,"340":1,"341":1,"342":1,"343":1,"344":1,"345":1,"346":1,"1868":1,"1869":1,"1870":1,"1871":1,"1872":1,"1873":1,"1874":1,"1875":1,"1876":1},"2":{"341":3},"3":{"0":10,"109":1,"136":1,"195":1,"201":1,"338":1,"339":1,"340":2,"341":5,"342":1,"343":5,"345":2,"346":1,"536":2,"538":1,"539":1,"544":1,"548":1,"792":1,"827":4,"828":2,"830":1,"831":1,"832":1,"833":1,"856":1,"881":1,"885":1,"905":1,"920":1,"921":2,"923":1,"926":1,"995":1,"996":2,"998":1,"1030":1,"1035":2,"1122":1,"1124":6,"1128":3,"1140":1,"1150":1,"1151":1,"1152":1,"1153":1,"1154":1,"1155":2,"1186":1,"1188":1,"1192":1,"1203":7,"1207":22,"1212":4,"1230":1,"1231":3,"1237":6,"1265":3,"1270":12,"1273":1,"1275":2,"1278":1,"1280":1,"1285":34,"1289":1,"1299":22,"1300":4,"1310":26,"1315":2,"1316":1,"1322":14,"1323":9,"1338":5,"1349":22,"1351":1,"1357":2,"1358":59,"1360":1,"1361":1,"1367":1,"1368":5,"1379":4,"1394":32,"1395":10,"1398":1,"1400":1,"1401":17,"1415":1,"1416":1,"1430":1,"1435":7,"1437":8,"1446":1,"1452":1,"1454":2,"1455":1,"1458":1,"1459":1,"1460":2,"1488":1,"1495":5,"1524":1,"1525":3,"1533":1,"1534":1,"1544":2,"1576":3,"1585":1,"1590":5,"1595":1,"1613":1,"1617":1,"1630":1,"1634":2,"1637":2,"1638":2,"1650":2,"1663":3,"1669":1,"1670":1,"1684":2,"1706":1,"1719":1,"1764":1,"1778":1,"1804":1,"1809":1,"1822":1,"1860":2,"1867":1,"1868":3,"1869":2,"1870":2,"1872":1,"1875":2,"1876":3,"1926":1,"1999":2,"2001":2,"2023":2,"2058":1,"2103":1,"2163":1,"2166":1,"2167":1,"2168":1,"2185":2,"2192":1,"2206":1,"2231":1}}],["concurrentrequests",{"3":{"1466":1}}],["concurrentmessagelimit",{"2":{"1581":1,"1706":1},"3":{"1322":5,"1576":1,"1581":1,"1590":1,"1706":1}}],["concurrentdictionary",{"2":{"996":1,"1236":1,"1908":1,"1915":1},"3":{"243":1,"996":1,"1236":1,"1237":1,"1241":1,"1247":8,"1259":7,"1270":2,"1285":11,"1299":4,"1300":5,"1308":3,"1309":3,"1315":1,"1322":5,"1416":3,"1495":1,"1908":1,"1915":1}}],["concurrently",{"3":{"0":1,"380":1,"832":1,"868":1,"1124":1,"1270":4,"1285":1,"1310":3,"1323":2,"1338":1,"1358":3,"1368":1,"1394":1,"1416":2,"1454":3,"1908":2,"1944":1}}],["concurrent",{"3":{"0":3,"58":1,"62":1,"178":1,"195":1,"201":1,"223":1,"225":1,"245":1,"286":1,"340":1,"344":1,"390":2,"436":1,"501":1,"507":2,"508":1,"513":1,"517":1,"536":2,"540":1,"541":1,"690":1,"721":1,"830":1,"854":1,"857":1,"996":1,"1125":1,"1155":1,"1237":1,"1247":4,"1258":1,"1259":6,"1270":5,"1285":10,"1288":1,"1297":1,"1299":11,"1300":4,"1303":1,"1306":1,"1308":3,"1309":2,"1310":3,"1322":4,"1323":12,"1325":1,"1338":2,"1349":2,"1351":4,"1358":21,"1368":2,"1381":1,"1394":8,"1395":14,"1398":1,"1401":2,"1414":1,"1415":1,"1416":3,"1435":3,"1437":2,"1442":1,"1457":1,"1466":2,"1473":1,"1528":1,"1533":1,"1576":2,"1629":1,"1637":2,"1638":2,"1650":1,"1707":1,"1747":1,"1748":1,"1764":2,"1773":1,"1867":1,"1876":1,"1908":1,"1933":2,"2000":1,"2163":2,"2166":1,"2168":2}}],["confuse",{"3":{"1299":1,"1300":1,"1349":1}}],["confused",{"3":{"1285":1,"1435":3,"1560":1,"1949":1}}],["confusing",{"3":{"692":1,"933":1,"1299":1,"1414":1,"1426":1,"1435":1,"1488":1,"1491":1}}],["confusion",{"3":{"370":1,"1299":1,"1310":1,"1536":1,"1575":1,"1576":1,"1639":1,"1666":1,"1672":1,"1896":1,"1898":1,"2124":1,"2127":1,"2201":1}}],["conforminggrid",{"3":{"1435":3}}],["conformingmarkup",{"3":{"1435":1}}],["conforming",{"3":{"1435":3,"1663":1}}],["conform",{"3":{"1435":1,"1638":1}}],["conformanttests",{"3":{"1198":1,"1199":4,"1207":2,"1435":19,"1495":1}}],["conformant",{"3":{"674":1,"785":1,"1435":2}}],["conformance",{"1":{"569":1,"570":1,"571":1,"572":1,"573":1,"574":1,"575":1,"576":1,"577":1,"578":1,"579":1},"2":{"572":1,"579":1},"3":{"0":2,"115":1,"121":1,"124":1,"126":1,"143":1,"217":1,"218":2,"448":1,"450":1,"459":1,"464":1,"569":1,"571":1,"572":8,"573":1,"577":1,"579":2,"617":2,"618":1,"619":2,"622":1,"749":1,"773":1,"776":1,"778":1,"842":1,"953":2,"955":1,"957":1,"1207":34,"1212":1,"1262":2,"1310":5,"1323":1,"1427":2,"1435":75,"1437":1,"1484":1,"1486":1,"1487":5,"1488":2,"1491":3,"1493":1,"1523":1,"1530":1,"1557":1,"1570":1,"1590":1,"1659":1,"1670":2,"1824":1,"1825":1,"1826":1,"2039":1,"2040":1,"2054":2,"2059":1,"2130":1,"2131":1,"2231":2}}],["confounds",{"3":{"610":1}}],["confound",{"3":{"609":1,"1435":1,"1466":1,"1473":1}}],["confers",{"3":{"1299":1,"1962":2,"1964":2}}],["confer",{"3":{"186":1,"1270":1,"1299":1,"1960":1}}],["conferencedbcontext",{"2":{"1848":1},"3":{"1848":1}}],["conference→engagement",{"3":{"1533":1,"1610":1,"1611":1}}],["conference→engagement→notification→conference",{"3":{"1525":1}}],["conference↔engagement",{"3":{"1525":1}}],["conferenceimage",{"3":{"1466":1}}],["conferenceintegrationtestbase",{"3":{"1199":32,"1207":2,"1299":1}}],["conferenceintegrationtestcollection",{"3":{"1199":5,"1207":2}}],["conferenceintegrationtestfixture",{"3":{"1199":37,"1207":2,"1435":1}}],["conferencecache",{"2":{"1375":1},"3":{"1358":1,"1375":1,"1378":1,"1379":2}}],["conferencecategories",{"3":{"1358":4,"1379":3,"1394":9,"1446":1,"1626":1,"1631":2,"1639":2}}],["conferencecategoriescontrollertests",{"3":{"1199":1,"1207":2}}],["conferencecategoriescontroller",{"2":{"1370":1,"1371":1},"3":{"1199":3,"1203":1,"1207":2,"1349":3,"1358":21,"1370":2,"1371":1,"1372":1,"1374":1,"1379":8}}],["conferencecategoryformfields",{"3":{"1394":4}}],["conferencecategoryformmodel",{"2":{"1383":1},"3":{"1199":3,"1203":1,"1207":2,"1383":2,"1394":8}}],["conferencecategory",{"3":{"1379":2,"1394":40,"1495":1}}],["conferencecategoryidentifiertype",{"3":{"1349":6,"1358":17,"1379":2,"1394":7}}],["conferencecategoryitemspanel",{"2":{"1384":1},"3":{"1199":1,"1203":1,"1207":2,"1384":2,"1394":20}}],["conferencecategoryitemeditmodel",{"2":{"1383":1},"3":{"1199":2,"1203":1,"1207":2,"1383":2,"1394":7}}],["conferencecategoryvalidationrules",{"3":{"1207":3,"1358":17}}],["conferencecategoryvalidationrulestests",{"3":{"1199":1,"1207":6}}],["conferencecategorynavigationpopulatortests",{"3":{"1199":1,"1207":2,"1358":2}}],["conferencecategorynavigationpopulator",{"3":{"1199":3,"1203":1,"1207":2,"1309":1,"1353":1,"1358":8}}],["conferencecategoryentityqueryservicetests",{"3":{"1199":1,"1207":2,"1358":2}}],["conferencecategoryentityqueryservice",{"3":{"1199":3,"1203":1,"1207":2,"1349":2,"1353":2,"1358":11}}],["conferencecategoryeditmodel",{"3":{"1199":2,"1203":1,"1207":2,"1394":5}}],["conferencecategoryconfiguration",{"2":{"1361":1},"3":{"1199":2,"1203":1,"1207":2,"1349":4,"1361":2,"1368":3}}],["conferencecategorycreaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1358":12}}],["conferencecategorycreaterequestvalidatortests",{"2":{"1199":1},"3":{"1199":1,"1207":2,"1358":2}}],["conferencecategorycreaterequestmapper",{"3":{"1199":1,"1203":1,"1207":2,"1358":11}}],["conferencecategorycreaterequest",{"3":{"1199":9,"1203":1,"1207":2,"1270":1,"1358":18,"1379":2}}],["conferencecategorycreate",{"3":{"1199":1,"1203":1,"1207":2,"1394":20}}],["conferencecategorycreatemodel",{"3":{"1199":2,"1203":1,"1207":2,"1394":7}}],["conferencecategorycreatepage",{"3":{"1199":5,"1207":2}}],["conferencecategoryupdateappliertests",{"3":{"1199":1,"1207":2,"1358":2}}],["conferencecategoryupdateapplier",{"3":{"1199":2,"1203":1,"1207":2,"1270":1,"1353":1,"1358":5}}],["conferencecategoryupdaterequestvalidatortests",{"3":{"1199":1,"1207":2,"1358":2}}],["conferencecategoryupdaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1358":6}}],["conferencecategoryupdaterequest",{"3":{"1199":9,"1203":1,"1207":2,"1358":12,"1379":1}}],["conferencecategorytitlerules",{"3":{"1199":4,"1203":1,"1207":1,"1358":9}}],["conferencecategorylist",{"2":{"1382":1},"3":{"1199":1,"1203":1,"1207":2,"1323":2,"1349":1,"1382":2,"1394":16}}],["conferencecategorylistpage",{"3":{"1199":3,"1207":2}}],["conferencecategorydetails",{"2":{"1394":1},"3":{"1394":1}}],["conferencecategorydetail",{"2":{"1383":1},"3":{"1199":1,"1203":1,"1207":2,"1383":2,"1394":22}}],["conferencecategorydetailpage",{"3":{"1199":4,"1207":2}}],["conferencecategorydtomappertests",{"3":{"1199":1,"1207":2}}],["conferencecategorydtomapper",{"3":{"1199":7,"1203":1,"1207":2,"1349":2,"1358":8}}],["conferencecategorydtotests",{"3":{"1199":1,"1207":2}}],["conferencecategorydto",{"3":{"1199":21,"1203":1,"1207":2,"1346":2,"1349":11,"1358":13,"1379":1,"1394":19}}],["conferencecategoryservice",{"2":{"1381":1},"3":{"881":1,"1199":1,"1203":1,"1207":2,"1381":3,"1394":6}}],["conferencecrudregistrationtests",{"3":{"1199":1,"1207":2,"1270":2,"1358":2,"1437":2}}],["conferencecrossservicefactory",{"3":{"1199":2,"1207":2,"1435":2}}],["conferenceevent",{"3":{"1358":1}}],["conferenceentityconfigurationtests",{"3":{"1199":1,"1207":4,"1436":1,"1437":1}}],["conferenceerrorresourcestests",{"3":{"1199":1,"1207":2,"1379":2,"1436":1,"1437":1}}],["conferenceerrorresources",{"2":{"1376":1},"3":{"1199":2,"1203":1,"1207":2,"1310":1,"1376":3,"1379":7,"1495":1}}],["conferencetestwebapplicationfactory",{"3":{"1199":2,"1207":2,"1435":2}}],["conferencetestdbcontext",{"3":{"1199":2,"1207":1}}],["conferencetrackinfo",{"2":{"1392":1},"3":{"1199":2,"1203":1,"1207":1,"1392":1,"1394":4,"1495":2}}],["conferenceaiguardrailsregistrationtests",{"2":{"1434":1,"1486":1,"1525":1},"3":{"1199":1,"1207":3,"1368":1,"1434":2,"1436":1,"1486":1,"1525":2,"1534":1}}],["conferenceroutepaths",{"2":{"1393":1,"1394":1},"3":{"1199":39,"1203":1,"1207":2,"1323":1,"1393":4,"1394":70,"1395":5,"1415":1}}],["conferencereadaudience",{"2":{"243":1,"388":1,"1306":1,"1347":1,"1372":1,"1378":1,"1390":1,"1921":1},"3":{"243":1,"388":2,"1199":8,"1203":1,"1207":2,"1306":1,"1308":2,"1347":4,"1349":7,"1372":2,"1378":1,"1379":7,"1390":1,"1394":6,"1495":2,"1921":1}}],["conferences",{"0":{"2239":1},"3":{"683":1,"1394":2,"2109":1,"2211":1,"2215":2,"2239":1,"2240":1,"2242":1,"2244":2}}],["conferenceuimodule",{"3":{"649":1,"1199":2,"1203":1,"1207":2,"1323":2,"1394":18,"1525":3}}],["conferenceuihostapplicationfactory",{"2":{"218":1},"3":{"218":2,"572":1,"575":1,"1199":7,"1207":2,"1436":2,"1486":1}}],["conferencepubliccache",{"2":{"386":1,"392":1},"3":{"386":1,"392":1,"1349":1,"1378":1,"1495":1}}],["conferencepermissiongrantstests",{"3":{"1199":1,"1207":2,"1436":1,"1486":1,"1495":1}}],["conferencepermissiongrants",{"2":{"1347":1,"1372":1,"1376":2},"3":{"186":1,"1116":2,"1119":1,"1199":3,"1203":1,"1207":2,"1347":5,"1349":6,"1372":1,"1376":5,"1379":5,"1414":1,"1495":2,"1525":1}}],["conferencepermissions",{"2":{"188":1,"1018":1,"1192":1,"1264":1,"1347":1,"1371":1,"1372":2,"1375":1,"1376":1,"1379":1,"1963":1},"3":{"186":1,"188":1,"1018":1,"1116":1,"1192":1,"1199":30,"1203":1,"1207":2,"1264":1,"1346":2,"1347":7,"1349":16,"1358":8,"1371":1,"1372":6,"1375":1,"1376":2,"1379":44,"1394":1,"1495":3,"1963":1}}],["conferencefeatures",{"2":{"300":1,"1018":1,"1021":1,"1099":1,"1103":1,"2135":1},"3":{"300":1,"1018":1,"1021":1,"1099":1,"1103":1,"1199":5,"1203":1,"1207":2,"1270":1,"1299":1,"1346":7,"1349":10,"1358":5,"1379":2,"1395":1,"1495":2,"1534":1,"2135":2}}],["conferencemodule",{"2":{"1313":1,"1348":1,"1369":1,"1879":1,"1883":1},"3":{"583":3,"585":1,"1199":3,"1203":1,"1207":2,"1313":4,"1322":3,"1348":2,"1349":7,"1358":1,"1368":1,"1369":1,"1376":4,"1379":11,"1435":2,"1879":1,"1883":1}}],["conferencemoduleseeder",{"2":{"1363":1,"1369":1,"1376":1},"3":{"295":1,"1199":1,"1203":1,"1207":2,"1322":2,"1363":3,"1368":4,"1369":1,"1376":3,"1379":8}}],["conferencemoduledbseedertests",{"3":{"1199":1,"1207":3,"1368":6,"1437":1}}],["conferencemoduledbseeder",{"2":{"295":1,"1363":1,"1376":1},"3":{"295":2,"1199":3,"1203":1,"1207":2,"1285":2,"1349":4,"1358":1,"1363":12,"1368":9,"1376":1,"1379":3,"1495":1}}],["conference",{"0":{"1376":1,"1377":1,"1401":1,"1603":1},"1":{"1339":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":1,"1346":1,"1347":1,"1348":1,"1349":1,"1350":1,"1351":1,"1352":1,"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1,"1359":1,"1360":1,"1361":1,"1362":1,"1363":1,"1364":1,"1365":1,"1366":1,"1367":1,"1368":1,"1369":1,"1370":1,"1371":1,"1372":1,"1373":1,"1374":1,"1375":1,"1376":1,"1377":1,"1378":1,"1379":1,"1380":1,"1381":1,"1382":1,"1383":1,"1384":1,"1385":1,"1386":1,"1387":1,"1388":1,"1389":1,"1390":1,"1391":1,"1392":1,"1393":1,"1394":1,"1627":1,"1628":1,"1629":1,"1630":1,"1631":1,"1632":1,"1633":1,"1634":1,"1635":1,"1636":1,"1637":1,"1638":1,"1639":1,"1640":1,"2108":1,"2109":1,"2110":1,"2111":1,"2112":1,"2113":1,"2114":1},"2":{"47":1,"59":1,"536":1,"564":1,"1116":1,"1203":1,"1207":8,"1271":1,"1339":1,"1340":3,"1343":1,"1346":1,"1348":1,"1349":6,"1358":4,"1362":1,"1366":1,"1368":2,"1369":3,"1377":1,"1378":2,"1379":7,"1380":1,"1394":10,"1426":1,"1434":1,"1436":3,"1437":1,"1444":1,"1463":1,"1472":1,"1473":1,"1485":1,"1486":2,"1495":1,"1524":1,"1612":1,"1614":1,"1630":1,"2029":1,"2033":1,"2034":1,"2035":1,"2037":1,"2110":1},"3":{"0":11,"31":1,"47":1,"52":2,"55":1,"58":4,"59":2,"60":1,"61":1,"62":2,"63":1,"68":1,"77":2,"80":1,"85":1,"86":1,"93":4,"95":1,"97":1,"99":1,"104":1,"117":2,"128":3,"164":1,"165":1,"175":2,"181":2,"184":1,"186":6,"187":1,"195":4,"203":2,"235":2,"238":1,"241":1,"243":10,"245":2,"249":1,"250":2,"251":2,"252":2,"254":2,"257":2,"259":2,"260":6,"261":3,"291":1,"295":5,"325":6,"341":1,"344":2,"350":2,"353":3,"379":1,"387":2,"388":5,"390":6,"392":3,"393":1,"401":2,"433":1,"434":1,"435":1,"446":1,"448":6,"452":1,"453":1,"454":1,"468":3,"470":6,"472":1,"475":8,"476":4,"477":6,"478":6,"487":1,"491":2,"492":3,"493":3,"494":2,"495":2,"520":1,"536":3,"545":13,"550":3,"551":1,"552":1,"554":1,"556":4,"559":2,"564":1,"572":6,"578":1,"583":13,"585":4,"599":2,"602":1,"640":2,"643":1,"649":3,"657":10,"665":1,"674":2,"681":1,"689":1,"690":5,"691":2,"692":1,"715":1,"733":1,"743":3,"744":2,"749":2,"752":1,"757":2,"765":1,"766":1,"774":2,"780":3,"782":8,"798":5,"803":5,"805":5,"827":1,"831":2,"832":1,"837":4,"838":2,"839":1,"841":1,"868":2,"869":7,"870":2,"872":1,"875":3,"876":1,"877":2,"881":5,"891":1,"914":2,"920":1,"924":3,"926":3,"927":1,"954":7,"956":3,"958":3,"960":1,"980":2,"988":1,"991":1,"995":3,"998":2,"999":1,"1004":7,"1017":5,"1018":16,"1021":2,"1022":1,"1049":4,"1054":5,"1082":4,"1085":1,"1090":5,"1093":2,"1094":5,"1095":2,"1099":4,"1102":1,"1103":2,"1115":1,"1116":33,"1117":1,"1118":2,"1119":1,"1124":2,"1125":1,"1140":1,"1150":2,"1168":2,"1192":10,"1194":2,"1199":1102,"1201":2,"1202":15,"1203":735,"1206":16,"1207":3404,"1208":13,"1209":48,"1211":3,"1212":8,"1237":26,"1239":10,"1242":2,"1247":23,"1259":2,"1262":1,"1264":4,"1270":47,"1271":29,"1283":1,"1285":49,"1299":24,"1300":14,"1303":1,"1304":1,"1306":2,"1308":2,"1309":31,"1310":54,"1311":27,"1313":7,"1317":2,"1322":26,"1323":34,"1324":1,"1325":2,"1327":3,"1330":1,"1331":4,"1332":1,"1333":2,"1334":1,"1338":21,"1339":5,"1340":17,"1341":25,"1343":14,"1344":12,"1345":2,"1346":36,"1347":18,"1348":35,"1349":863,"1350":7,"1351":19,"1352":21,"1353":11,"1354":8,"1355":5,"1356":4,"1357":5,"1358":2868,"1359":5,"1360":3,"1361":2,"1362":5,"1363":9,"1364":2,"1365":5,"1366":6,"1367":4,"1368":230,"1369":9,"1370":14,"1371":1,"1372":11,"1373":2,"1374":3,"1375":4,"1376":16,"1377":19,"1378":9,"1379":504,"1380":8,"1381":2,"1382":2,"1383":1,"1385":1,"1388":2,"1389":4,"1390":2,"1391":1,"1392":2,"1393":2,"1394":1025,"1395":252,"1396":1,"1400":3,"1401":72,"1404":1,"1405":1,"1412":2,"1414":37,"1415":28,"1416":10,"1425":2,"1426":23,"1434":12,"1435":70,"1436":48,"1437":53,"1440":9,"1441":15,"1442":1,"1444":2,"1445":3,"1446":8,"1447":1,"1448":2,"1454":5,"1455":3,"1456":5,"1457":10,"1459":1,"1461":1,"1463":3,"1464":2,"1466":40,"1472":3,"1473":7,"1475":2,"1485":4,"1486":15,"1487":3,"1488":4,"1489":3,"1491":6,"1495":57,"1499":2,"1513":2,"1516":1,"1519":1,"1522":3,"1524":4,"1525":58,"1528":1,"1529":1,"1530":4,"1531":2,"1533":5,"1534":7,"1581":1,"1595":1,"1600":1,"1610":11,"1611":1,"1612":2,"1614":1,"1616":1,"1617":1,"1618":1,"1620":2,"1621":1,"1624":2,"1625":3,"1626":12,"1627":1,"1628":8,"1629":10,"1630":10,"1631":6,"1633":22,"1634":1,"1635":1,"1636":5,"1637":51,"1638":3,"1639":4,"1640":4,"1649":1,"1655":1,"1672":1,"1677":2,"1784":1,"1854":1,"1858":1,"1859":1,"1864":1,"1868":1,"1873":2,"1879":2,"1881":1,"1883":1,"1921":6,"1935":1,"1941":2,"1960":1,"1961":1,"1962":2,"1994":3,"2003":1,"2005":1,"2007":2,"2008":1,"2015":1,"2018":1,"2020":1,"2021":2,"2023":1,"2029":3,"2032":1,"2033":2,"2034":1,"2035":2,"2037":1,"2054":1,"2086":1,"2108":1,"2109":3,"2110":1,"2112":2,"2114":1,"2122":1,"2126":2,"2130":7,"2135":1,"2137":1,"2155":1,"2178":4,"2209":1,"2213":3,"2215":2,"2220":4,"2227":6,"2230":1,"2238":4,"2239":5,"2243":1}}],["conflate",{"3":{"1218":1,"1640":1,"1895":1}}],["conflated",{"3":{"486":1,"1013":1}}],["conflates",{"3":{"108":1,"176":1,"829":1,"1229":1,"1310":1,"1803":1,"1998":1}}],["conflating",{"3":{"40":1,"1379":1,"1395":1,"1435":1,"1814":1,"1816":1,"1859":1,"2013":1,"2067":1,"2129":1}}],["conflictkindinflight",{"3":{"1310":1}}],["conflictkindbodymismatch",{"3":{"1310":1}}],["conflictkindtag",{"3":{"1310":1}}],["conflicts",{"3":{"38":1,"1285":4,"1638":1,"2068":1}}],["conflicting",{"3":{"0":1,"340":1,"633":1,"888":1,"1279":1,"1285":6,"1310":1,"1610":1,"1650":1,"1683":1,"1875":1,"2096":1}}],["conflict",{"0":{"1871":1},"1":{"2060":1,"2061":1,"2062":1,"2063":1,"2064":1,"2065":1,"2066":1,"2067":1,"2068":1},"2":{"1805":1,"1872":1},"3":{"0":2,"108":1,"109":4,"157":1,"339":1,"342":3,"343":3,"344":1,"348":1,"350":1,"539":2,"996":1,"998":1,"1019":1,"1147":1,"1219":1,"1222":1,"1229":4,"1237":2,"1285":11,"1288":2,"1299":4,"1310":19,"1311":1,"1322":1,"1346":1,"1349":3,"1357":1,"1358":12,"1367":1,"1379":1,"1394":3,"1395":11,"1398":1,"1401":3,"1405":1,"1414":1,"1426":1,"1435":6,"1437":2,"1442":1,"1454":1,"1466":1,"1486":1,"1487":1,"1566":1,"1631":1,"1638":1,"1639":2,"1640":2,"1650":2,"1661":1,"1663":1,"1746":1,"1773":1,"1783":1,"1798":1,"1804":2,"1805":2,"1872":4,"1873":2,"1875":3,"1876":2,"1908":1,"1925":2,"1933":1,"1953":1,"2005":1,"2059":1,"2060":3,"2061":1,"2155":1,"2162":1,"2209":1}}],["confining",{"3":{"1368":1}}],["confines",{"3":{"1430":1}}],["confine",{"3":{"1310":1}}],["confined",{"3":{"0":1,"493":1,"495":1,"557":1,"799":1,"979":1,"980":1,"1126":1,"1285":1,"1323":1,"1338":1,"1368":1,"1395":1,"1416":1,"1435":4,"1723":1,"2231":1}}],["confident",{"3":{"1495":2,"1793":1}}],["confidential",{"3":{"360":1,"1322":1,"2141":1}}],["confidentiality",{"3":{"358":1,"359":1,"360":2,"363":1,"1285":1,"1902":1,"2140":1,"2141":2,"2145":1}}],["confidence",{"3":{"593":1,"2028":1,"2042":1,"2049":1}}],["confirmnewpasswordfield",{"3":{"1435":1}}],["confirmdeleteasync",{"3":{"1432":1,"1435":1,"1487":1}}],["confirmpasswordfield",{"3":{"1435":1}}],["confirmpassword",{"3":{"1323":2}}],["confirmtext",{"3":{"1323":2}}],["confirmtwofactorenrollmenthandlerbase",{"2":{"1299":1,"1321":1,"2197":1},"3":{"1199":2,"1203":1,"1207":2,"1299":4,"1321":3,"1322":6,"1495":1,"2197":1}}],["confirmsonceandreportssuccess",{"3":{"1435":1}}],["confirms",{"3":{"790":1,"1299":1,"1323":2,"1358":4,"1368":1,"1394":4,"1395":2,"1414":1,"1416":1,"1435":6,"1454":1,"1456":1,"1495":2,"1498":1,"1764":1}}],["confirming",{"3":{"676":1,"1259":1,"1294":1,"1299":1,"1322":1,"1323":1,"1379":1,"1395":1,"1414":2}}],["confirmableauthenticationservice",{"3":{"1199":2,"1207":1}}],["confirmableauthuser",{"3":{"1199":3,"1207":1}}],["confirmableuser",{"3":{"1199":4,"1207":1}}],["confirmable",{"3":{"907":1,"1092":1,"1552":1,"2178":1}}],["confirmasync",{"3":{"649":1,"1323":3,"1414":1}}],["confirmationalert",{"3":{"1435":2}}],["confirmations",{"3":{"1741":1}}],["confirmationservice",{"2":{"1323":1},"3":{"1323":3}}],["confirmationstate",{"3":{"1199":2,"1203":1,"1207":1,"1323":4,"1495":1}}],["confirmationthrottled",{"3":{"1299":1}}],["confirmationurl",{"3":{"1299":1,"1760":1}}],["confirmation",{"0":{"1294":1,"1407":1},"1":{"2193":1,"2194":1,"2195":1,"2196":1,"2197":1,"2198":1,"2199":1,"2200":1,"2201":1,"2202":1},"2":{"1760":1},"3":{"0":5,"173":1,"175":2,"176":1,"177":1,"230":1,"348":1,"350":2,"535":2,"618":1,"650":2,"672":1,"733":1,"735":1,"907":1,"1057":1,"1058":2,"1059":6,"1060":1,"1062":1,"1063":1,"1092":1,"1192":1,"1212":1,"1286":2,"1294":2,"1299":25,"1321":1,"1322":13,"1323":21,"1349":3,"1394":10,"1395":3,"1401":2,"1402":1,"1404":1,"1413":1,"1414":24,"1416":1,"1421":1,"1426":7,"1432":1,"1435":11,"1437":1,"1466":1,"1486":1,"1495":6,"1552":1,"1569":1,"1576":1,"1626":3,"1632":1,"1640":1,"1642":1,"1652":1,"1668":1,"1748":1,"1754":1,"1760":3,"1762":1,"1763":1,"1764":1,"1766":1,"1768":1,"1770":1,"1775":2,"1778":1,"1834":1,"1963":1,"1976":1,"2000":1,"2011":1,"2160":3,"2166":1,"2168":1,"2192":1,"2193":3,"2197":2,"2201":3,"2202":1,"2207":2,"2210":1}}],["confirmemailasync",{"3":{"1323":3,"1414":1,"1435":1}}],["confirmemailhandlertests",{"3":{"1199":1,"1207":2,"1299":1,"1414":2}}],["confirmemailhandler",{"2":{"1407":1},"3":{"1199":2,"1203":1,"1207":2,"1299":1,"1322":1,"1323":1,"1407":2,"1414":5,"1495":1}}],["confirmemailhandlerbase",{"2":{"2197":1},"3":{"1199":3,"1203":1,"1207":2,"1299":6,"1321":4,"1322":7,"1414":3,"1495":1,"2197":1}}],["confirmemailpagetests",{"3":{"1199":1,"1207":2,"1435":1}}],["confirmemailpagetestsbase",{"2":{"577":1,"960":1,"1431":1},"3":{"0":1,"577":2,"960":1,"1199":3,"1207":2,"1323":2,"1431":3,"1435":2}}],["confirmemailcommand",{"2":{"1407":1},"3":{"1199":5,"1203":1,"1207":2,"1299":2,"1323":1,"1407":2,"1414":8,"1435":1}}],["confirmemailrequestvalidator",{"2":{"1294":1},"3":{"1199":1,"1203":1,"1207":1,"1294":1,"1299":3}}],["confirmemailrequest",{"3":{"1199":10,"1203":1,"1207":1,"1294":1,"1299":2,"1322":1,"1323":1,"1414":3}}],["confirmemail",{"2":{"1062":1},"3":{"577":1,"1059":1,"1062":2,"1199":2,"1203":3,"1207":9,"1294":1,"1299":3,"1322":2,"1323":12,"1407":2,"1413":1,"1414":8,"1435":2,"1495":2,"1525":2,"1590":1,"1626":1,"1760":1}}],["confirmemailtests",{"3":{"577":2,"578":2,"1199":1,"1207":2,"1435":1}}],["confirmedtoolspropertykey",{"3":{"1426":1}}],["confirmed",{"2":{"1421":1},"3":{"193":1,"254":1,"350":2,"353":1,"490":1,"799":1,"905":1,"909":1,"947":1,"1012":1,"1059":1,"1091":1,"1196":1,"1294":1,"1299":6,"1321":1,"1322":7,"1323":6,"1394":1,"1395":4,"1404":1,"1405":1,"1407":1,"1414":9,"1421":2,"1426":6,"1435":2,"1476":1,"1495":15,"1525":1,"1560":1,"1629":2,"1630":1,"1632":1,"1666":1,"1674":1,"1754":1,"1763":1,"1771":1,"1776":1,"1845":1,"1972":1,"1976":1,"2168":1,"2197":2}}],["confirm",{"3":{"0":1,"140":1,"175":1,"570":1,"577":1,"794":1,"907":1,"1059":1,"1062":1,"1116":1,"1235":1,"1285":1,"1299":2,"1310":1,"1321":1,"1322":7,"1323":14,"1349":3,"1356":1,"1358":3,"1371":1,"1379":5,"1382":1,"1394":15,"1395":4,"1401":1,"1413":2,"1414":8,"1416":2,"1431":2,"1435":10,"1466":1,"1473":1,"1477":2,"1487":1,"1488":1,"1525":2,"1531":1,"1585":1,"1590":2,"1595":1,"1599":1,"1606":1,"1607":1,"1626":1,"1630":1,"1652":1,"1656":1,"1696":1,"1697":1,"1709":1,"1740":1,"1741":2,"1754":1,"1760":1,"1767":1,"1769":1,"1775":2,"1994":1,"2168":1,"2195":1}}],["configfilter",{"3":{"1495":1}}],["configkey",{"3":{"1338":4}}],["configs",{"3":{"1192":1,"1360":1,"1361":1,"1495":1,"1499":1,"1852":1}}],["config",{"0":{"1360":1,"1361":1},"1":{"297":1,"298":1,"299":1,"300":1,"301":1,"302":1,"303":1,"304":1,"305":1,"306":1},"2":{"369":1,"370":1,"372":1,"527":1,"632":1,"764":1,"869":1,"938":1,"1648":1},"3":{"0":14,"34":1,"60":1,"93":1,"95":1,"117":1,"164":1,"189":1,"225":1,"297":1,"300":2,"348":1,"350":1,"369":1,"370":5,"372":2,"418":2,"419":1,"420":1,"422":1,"428":1,"429":1,"484":1,"490":1,"491":1,"492":1,"493":1,"527":1,"556":1,"557":1,"619":1,"632":1,"633":5,"635":1,"674":1,"677":1,"749":1,"764":1,"766":1,"826":1,"827":1,"829":2,"840":2,"869":2,"914":1,"938":1,"1012":1,"1034":1,"1037":1,"1059":3,"1075":1,"1184":1,"1192":5,"1212":7,"1241":2,"1247":5,"1270":2,"1299":12,"1306":1,"1308":1,"1310":7,"1313":1,"1314":1,"1319":2,"1322":2,"1323":12,"1335":2,"1337":2,"1338":15,"1348":1,"1349":1,"1361":2,"1368":1,"1379":2,"1414":1,"1415":8,"1426":1,"1428":1,"1430":1,"1435":11,"1436":1,"1437":8,"1440":1,"1446":3,"1450":1,"1454":3,"1466":1,"1486":2,"1487":2,"1495":5,"1499":1,"1509":1,"1520":1,"1525":5,"1533":1,"1547":2,"1552":1,"1553":3,"1562":1,"1568":1,"1571":1,"1575":1,"1576":4,"1582":1,"1589":1,"1590":4,"1610":1,"1613":1,"1618":1,"1648":1,"1652":2,"1660":1,"1664":1,"1665":1,"1789":1,"1820":1,"1841":1,"1848":1,"1849":1,"1852":1,"1853":1,"1856":1,"1881":2,"1892":1,"1893":1,"1895":1,"1897":1,"1898":2,"1903":2,"1957":1,"1964":1,"1974":1,"1975":2,"1978":1,"1988":2,"2000":1,"2001":1,"2007":1,"2010":2,"2022":1,"2093":1,"2097":1,"2135":1,"2137":1,"2138":2,"2141":1,"2219":1,"2231":2}}],["configuring",{"3":{"402":1,"1285":1,"1299":1,"1300":1,"1310":1,"1319":1,"1323":1,"1338":1,"1466":1}}],["configurability",{"3":{"776":1}}],["configurablewarmuptask",{"3":{"1199":2,"1207":1}}],["configurable",{"0":{"1707":1},"3":{"0":4,"157":1,"189":1,"279":1,"312":1,"318":1,"381":1,"425":1,"461":1,"834":1,"883":1,"905":1,"1285":3,"1292":1,"1296":1,"1299":4,"1300":1,"1310":3,"1322":1,"1323":1,"1338":4,"1349":2,"1358":1,"1423":1,"1426":1,"1435":1,"1495":1,"1576":1,"1668":1,"1669":1,"1670":1,"1707":1,"1751":1,"1766":2,"1944":1,"1949":1,"1993":1,"2197":1}}],["configurator",{"3":{"572":1,"1259":1,"1310":1,"1322":2,"1338":5}}],["configurationchanges",{"3":{"1415":3}}],["configurationkey",{"3":{"1322":2}}],["configurationassemblies",{"3":{"1285":2}}],["configurationbuilder",{"3":{"1050":1,"1285":2,"1435":1}}],["configurationoauthuisettings",{"2":{"350":1},"3":{"350":1,"1199":2,"1203":1,"1207":2,"1310":1,"1323":8,"1415":2,"1495":1}}],["configurationmanager",{"2":{"237":1,"1334":1},"3":{"237":1,"665":1,"1334":1,"1338":2,"1428":1,"1435":1,"1437":1,"1441":1,"1442":1,"1610":1,"1612":1}}],["configurationsource",{"3":{"1285":3}}],["configurations",{"0":{"1362":1},"3":{"10":1,"97":1,"135":1,"633":1,"657":1,"889":1,"891":1,"1034":1,"1192":1,"1202":1,"1203":1,"1213":1,"1234":1,"1237":5,"1247":1,"1271":2,"1273":1,"1279":1,"1280":1,"1281":2,"1285":31,"1307":1,"1309":1,"1315":1,"1318":1,"1322":2,"1349":1,"1359":1,"1362":2,"1368":8,"1395":6,"1400":1,"1408":1,"1414":1,"1435":4,"1437":1,"1466":1,"1495":1,"1640":1,"1649":1,"1650":2,"1651":1,"1721":1,"1726":2,"1727":1,"1773":1,"1848":1,"1931":1,"2052":1}}],["configuration",{"0":{"1281":1,"1319":1,"1334":2,"1418":1,"1849":1,"1855":1,"2011":1},"1":{"671":1,"672":1,"673":1,"674":1,"675":1,"676":1,"677":1,"678":1,"835":1,"836":1,"837":1,"838":1,"839":1,"840":1,"841":1,"842":1,"1312":1,"1313":1,"1314":1,"1315":1,"1316":1,"1317":1,"1318":1,"1319":1,"1320":1,"1321":1,"1322":1},"2":{"261":1,"440":1,"696":1,"705":1,"713":1,"776":1,"832":1,"914":1,"979":1,"1061":1,"1310":1,"1323":1,"1334":1,"1338":1,"1416":1,"1772":1,"1838":1,"1935":1,"2093":1,"2126":1,"2150":1},"3":{"0":19,"25":1,"30":1,"31":7,"32":1,"46":1,"47":2,"57":3,"59":3,"60":1,"63":1,"72":1,"73":1,"95":4,"96":1,"98":1,"140":1,"150":1,"164":2,"166":6,"167":3,"170":1,"182":1,"186":1,"198":1,"200":2,"213":1,"214":2,"216":1,"219":1,"225":2,"228":1,"231":1,"235":1,"243":3,"261":1,"280":1,"290":1,"295":1,"300":1,"301":1,"302":1,"318":1,"330":2,"358":1,"359":3,"361":2,"365":2,"373":1,"384":1,"395":2,"401":1,"407":1,"409":1,"413":1,"415":1,"421":1,"422":1,"425":1,"432":1,"434":2,"440":1,"451":1,"470":1,"501":1,"529":1,"530":1,"536":2,"539":1,"556":1,"583":5,"584":1,"585":3,"586":1,"597":1,"598":1,"599":7,"601":3,"603":1,"633":1,"640":1,"641":1,"642":1,"644":1,"650":1,"651":1,"657":2,"658":1,"659":1,"663":1,"665":3,"666":1,"667":1,"671":1,"673":2,"674":6,"676":3,"678":1,"690":1,"691":1,"696":1,"698":3,"700":1,"705":1,"707":3,"708":2,"713":1,"715":1,"733":1,"750":1,"765":1,"767":1,"770":1,"774":2,"776":2,"778":1,"790":1,"791":1,"792":2,"795":2,"810":1,"819":1,"823":1,"827":9,"829":5,"831":1,"832":7,"833":2,"835":1,"836":2,"838":9,"839":4,"840":4,"842":3,"846":1,"854":1,"863":1,"876":1,"883":1,"889":3,"890":4,"891":3,"905":1,"914":2,"916":2,"929":2,"931":2,"933":1,"934":1,"945":1,"947":1,"948":1,"954":1,"973":1,"976":1,"979":5,"998":1,"999":1,"1004":1,"1018":4,"1033":2,"1034":4,"1040":1,"1043":1,"1059":1,"1060":1,"1061":2,"1068":1,"1075":1,"1078":1,"1083":2,"1084":1,"1085":1,"1089":1,"1091":7,"1092":2,"1094":3,"1108":1,"1109":1,"1124":2,"1126":1,"1127":1,"1149":1,"1175":4,"1178":1,"1184":1,"1192":1,"1202":1,"1203":19,"1207":127,"1208":2,"1212":11,"1232":3,"1237":12,"1243":1,"1247":5,"1257":1,"1259":13,"1264":1,"1270":8,"1271":4,"1272":2,"1273":3,"1276":2,"1279":6,"1280":3,"1281":5,"1282":1,"1283":1,"1285":198,"1287":2,"1293":1,"1296":1,"1299":27,"1300":15,"1301":1,"1303":2,"1306":2,"1308":21,"1309":2,"1310":43,"1311":1,"1312":2,"1313":1,"1314":3,"1315":1,"1316":3,"1319":7,"1320":2,"1322":80,"1323":74,"1324":1,"1325":2,"1330":1,"1332":2,"1333":1,"1334":4,"1336":1,"1337":6,"1338":123,"1343":1,"1349":16,"1358":23,"1360":2,"1361":2,"1362":2,"1365":2,"1368":33,"1378":2,"1379":4,"1394":3,"1395":54,"1401":4,"1402":1,"1405":1,"1406":1,"1411":1,"1414":23,"1415":16,"1416":18,"1418":1,"1419":3,"1420":1,"1422":1,"1423":2,"1426":9,"1428":3,"1430":1,"1432":1,"1435":38,"1436":3,"1437":15,"1440":2,"1441":9,"1442":2,"1443":1,"1445":1,"1446":5,"1449":1,"1454":1,"1456":1,"1466":13,"1467":1,"1468":1,"1469":1,"1471":1,"1476":1,"1480":1,"1483":1,"1486":3,"1487":4,"1488":2,"1491":1,"1495":4,"1508":1,"1525":5,"1546":1,"1553":2,"1576":7,"1625":1,"1628":1,"1630":3,"1638":3,"1640":1,"1650":1,"1651":2,"1655":2,"1659":1,"1660":1,"1662":1,"1663":3,"1666":3,"1667":1,"1669":3,"1670":1,"1674":1,"1719":1,"1720":1,"1723":3,"1726":4,"1727":1,"1733":1,"1763":1,"1769":2,"1772":1,"1782":1,"1787":1,"1789":1,"1824":1,"1829":1,"1838":1,"1846":1,"1848":5,"1849":1,"1850":1,"1853":2,"1855":3,"1856":2,"1857":1,"1859":1,"1861":2,"1871":1,"1880":1,"1881":3,"1901":1,"1902":1,"1915":1,"1921":1,"1934":1,"1935":3,"1940":1,"1943":1,"1951":2,"1969":1,"1996":1,"1999":3,"2000":1,"2007":1,"2009":1,"2011":1,"2012":1,"2013":2,"2019":1,"2023":4,"2025":1,"2026":2,"2027":2,"2047":1,"2052":1,"2063":2,"2093":1,"2125":2,"2126":1,"2135":1,"2141":2,"2142":1,"2144":1,"2148":2,"2149":1,"2150":2,"2151":1,"2159":1,"2165":1,"2166":2,"2171":1,"2172":1,"2173":1,"2179":1,"2195":1,"2198":1,"2202":1,"2229":2,"2231":4}}],["configuretestservices",{"3":{"1435":1}}],["configuretestenvironment",{"2":{"1428":1},"3":{"1428":1,"1435":2,"1487":1,"1489":1}}],["configuretestfeatureflags",{"2":{"1428":1,"1670":1},"3":{"1428":1,"1435":2,"1487":1,"1670":1}}],["configurerouteasync",{"3":{"1338":2}}],["configurerefreshsessions",{"2":{"1282":1},"3":{"1282":1,"1285":3}}],["configurereceiveendpoint",{"3":{"640":1,"1435":1}}],["configureclusterasync",{"3":{"1338":2}}],["configureconsumers",{"3":{"1259":1}}],["configureconcurrencytokens",{"2":{"341":1,"343":1,"1871":1,"2163":1},"3":{"341":1,"343":1,"1285":1,"1525":1,"1576":1,"1871":1,"2163":1}}],["configureconventions",{"2":{"0":1,"798":1,"806":1,"889":1,"1034":1,"1050":1,"1052":1,"1083":1,"1086":1,"1212":1,"1280":1},"3":{"0":2,"798":1,"806":1,"889":1,"1034":1,"1050":1,"1052":1,"1083":1,"1086":1,"1212":1,"1280":2,"1285":11,"1809":1}}],["configurekestrel",{"3":{"1338":3}}],["configureprimaryhttpmessagehandler",{"3":{"1338":2}}],["configurepermissiongrants",{"2":{"1285":1},"3":{"1285":4}}],["configurefallback",{"3":{"1299":3}}],["configurewarnings",{"3":{"1285":2}}],["configureinprocesstokenvalidation",{"2":{"1428":1,"1487":1},"3":{"1428":1,"1435":3,"1437":1,"1487":1}}],["configureinternalcommands",{"2":{"1042":1,"1045":1},"3":{"1042":1,"1045":1,"1285":1}}],["configureinbox",{"3":{"200":1,"1259":1,"1285":1}}],["configureoptions",{"3":{"1299":1}}],["configureopentelemetry",{"2":{"401":1,"1089":1,"1328":1,"1676":1,"2159":1},"3":{"397":1,"401":1,"1089":1,"1259":1,"1310":1,"1322":1,"1328":1,"1332":1,"1338":6,"1442":2,"1466":1,"1576":1,"1676":3,"2155":2,"2159":1}}],["configureoutbox",{"2":{"931":1,"934":1},"3":{"931":1,"934":1,"1285":1}}],["configureessentials",{"3":{"1415":2}}],["configureemulatorhost",{"3":{"640":1,"1322":3}}],["configureendpoints",{"2":{"642":1},"3":{"640":2,"642":1,"1322":2}}],["configureendpointswithhealthprobe",{"2":{"98":1,"1378":1,"1412":1,"1440":1,"1669":1},"3":{"0":1,"95":1,"96":1,"98":2,"1330":1,"1338":4,"1378":1,"1395":1,"1412":1,"1440":1,"1441":1,"1669":1}}],["configureendpointdefaults",{"2":{"95":1,"98":1},"3":{"95":1,"98":2,"1338":1}}],["configureapplicationpartmanager",{"3":{"1310":1}}],["configureappconfiguration",{"2":{"914":1},"3":{"914":1,"1435":1,"1489":1}}],["configureall",{"3":{"1310":5,"1323":2}}],["configureaudittrail",{"2":{"720":1},"3":{"715":2,"720":1,"1285":4}}],["configureaspnetcoremetrics",{"2":{"397":1,"409":1},"3":{"397":1,"409":1}}],["configureawait",{"1":{"479":1,"480":1,"481":1,"482":1,"483":1,"484":1,"485":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"495":1},"2":{"481":1,"482":1,"483":1,"484":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"495":1},"3":{"0":3,"479":1,"481":3,"482":2,"483":1,"484":2,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":2,"493":2,"494":2,"495":1,"1212":1,"1229":1,"1247":1,"1259":1,"1285":1,"1299":1,"1308":1,"1309":1,"1311":1,"1322":4,"1338":1,"1358":6,"1368":1,"1379":3,"1401":1,"1414":1,"1416":1,"1435":4,"2231":2}}],["configuremetrics",{"2":{"395":1,"407":1,"409":1},"3":{"395":1,"407":1,"409":1,"1332":1,"1338":3,"2155":1}}],["configurehttp2withhealthprobe",{"3":{"93":1,"94":1}}],["configurehttpclientdefaults",{"2":{"67":1,"100":1,"883":1,"1328":1,"1706":1},"3":{"67":1,"100":1,"883":1,"1311":2,"1328":1,"1338":6,"1440":1,"1442":1,"1706":1}}],["configuresharedenvironment",{"3":{"1435":1}}],["configuresensitivedatalogging",{"3":{"1285":4}}],["configureservices",{"2":{"1487":1},"3":{"572":1,"1435":5,"1487":1,"1489":1}}],["configurescheduler",{"2":{"710":1,"1036":1},"3":{"707":1,"710":1,"1036":1,"1285":1,"1322":1}}],["configures",{"3":{"70":1,"93":1,"164":1,"170":8,"186":1,"214":2,"318":1,"324":1,"341":1,"352":1,"446":1,"448":1,"454":1,"640":2,"674":1,"766":1,"819":1,"832":1,"918":1,"1034":1,"1036":1,"1273":1,"1285":16,"1299":6,"1300":3,"1308":3,"1310":5,"1322":6,"1323":3,"1338":2,"1368":1,"1395":2,"1414":1,"1416":2,"1426":1,"1435":6,"1442":2,"1489":1,"1525":1,"1576":1,"1581":1,"1669":1,"1670":1,"1707":1,"1720":1,"1850":1,"1994":1,"2005":1,"2013":1,"2029":1,"2054":1,"2121":1,"2149":1}}],["configure",{"2":{"1823":1},"3":{"0":2,"164":1,"170":1,"214":2,"215":1,"318":1,"401":1,"459":1,"464":2,"640":1,"674":1,"698":1,"733":3,"747":1,"749":4,"751":3,"914":1,"916":1,"997":1,"1188":1,"1279":1,"1285":27,"1299":7,"1300":2,"1308":2,"1310":15,"1315":1,"1322":4,"1323":5,"1332":1,"1338":16,"1361":2,"1368":12,"1394":1,"1395":15,"1414":1,"1415":1,"1416":1,"1423":1,"1428":2,"1435":9,"1442":2,"1487":2,"1489":1,"1495":1,"1684":1,"1723":1,"1823":1,"1824":1,"1844":1,"1855":1,"1977":1,"1994":1,"2010":1,"2112":1,"2148":2,"2150":1,"2151":1}}],["configuredasyncdisposable",{"3":{"1435":1}}],["configuredatagridlistpagehost",{"2":{"954":1,"959":1,"960":1,"1431":1},"3":{"0":1,"954":1,"959":1,"960":1,"1431":1,"1435":3,"1487":1}}],["configuredpatternoptions",{"2":{"1426":1},"3":{"1426":2}}],["configuredprovider",{"2":{"1094":1},"3":{"1094":1,"1426":1}}],["configuredengines",{"3":{"1285":1}}],["configured",{"0":{"2005":1},"3":{"0":10,"31":2,"73":1,"95":1,"100":1,"101":1,"135":1,"139":1,"157":1,"158":1,"159":1,"170":3,"175":2,"177":1,"178":1,"179":2,"186":1,"200":1,"214":1,"225":2,"235":1,"241":1,"243":2,"245":1,"249":1,"261":1,"265":1,"279":1,"280":1,"283":1,"285":1,"300":1,"303":1,"320":3,"330":1,"337":1,"343":2,"346":1,"349":1,"350":2,"351":1,"355":1,"381":1,"398":1,"406":1,"413":1,"448":1,"464":1,"499":1,"501":3,"524":1,"530":1,"536":2,"538":1,"575":1,"601":1,"640":1,"641":1,"665":2,"668":1,"674":1,"675":1,"676":3,"690":1,"698":1,"740":1,"741":2,"742":1,"776":1,"789":2,"790":5,"792":2,"794":1,"818":1,"819":1,"821":1,"827":6,"829":1,"838":1,"841":1,"849":1,"869":1,"906":1,"915":1,"930":1,"931":1,"996":1,"1017":1,"1018":2,"1034":1,"1042":1,"1059":1,"1083":3,"1089":2,"1091":4,"1092":2,"1108":2,"1116":2,"1126":1,"1150":1,"1184":1,"1188":1,"1212":1,"1237":2,"1247":1,"1251":1,"1258":1,"1259":6,"1269":1,"1270":4,"1271":2,"1276":1,"1279":2,"1280":2,"1282":1,"1284":1,"1285":49,"1287":3,"1292":1,"1293":1,"1296":1,"1299":27,"1300":12,"1301":2,"1306":1,"1308":10,"1309":1,"1310":34,"1312":1,"1319":4,"1322":19,"1323":23,"1332":1,"1335":1,"1336":4,"1338":21,"1349":4,"1356":1,"1357":1,"1358":11,"1364":1,"1365":1,"1367":1,"1368":7,"1378":2,"1379":2,"1394":3,"1395":35,"1399":1,"1401":2,"1412":1,"1414":5,"1416":7,"1417":1,"1423":1,"1424":2,"1426":10,"1432":1,"1435":14,"1437":7,"1440":1,"1444":1,"1446":1,"1454":2,"1455":1,"1457":1,"1458":1,"1464":1,"1466":7,"1487":4,"1488":1,"1491":1,"1525":1,"1530":1,"1547":1,"1562":1,"1576":3,"1590":1,"1630":3,"1631":1,"1634":1,"1636":1,"1638":1,"1640":3,"1652":1,"1662":1,"1663":3,"1666":1,"1669":2,"1671":1,"1674":3,"1683":1,"1699":1,"1707":1,"1718":1,"1720":1,"1722":1,"1726":1,"1727":1,"1764":1,"1769":1,"1800":1,"1828":1,"1841":1,"1846":1,"1852":1,"1871":1,"1895":1,"1920":1,"1922":1,"1927":1,"1932":1,"1943":2,"1973":2,"1974":1,"1975":1,"1978":1,"1981":1,"1983":1,"1993":1,"1995":3,"2000":1,"2011":2,"2023":1,"2026":2,"2027":1,"2029":1,"2031":1,"2054":1,"2057":1,"2063":2,"2128":1,"2131":1,"2137":1,"2163":1,"2165":4,"2166":1,"2167":1,"2171":1,"2173":1,"2198":1,"2231":1}}],["configurebrokertransport",{"2":{"150":1,"638":1,"819":1,"822":1,"1075":1,"1706":1},"3":{"0":2,"150":1,"638":1,"640":2,"819":1,"822":1,"1075":1,"1322":6,"1706":2}}],["conspicuously",{"3":{"1395":1}}],["conspicuous",{"3":{"1285":1}}],["consolidating",{"3":{"1285":1,"1414":1,"2071":1}}],["consolidation",{"3":{"1285":1,"1310":1,"1394":1,"1495":4,"1599":1}}],["consolidated",{"3":{"1259":1,"1271":1,"1310":1,"1485":1,"1486":1,"1495":3,"1525":1}}],["consolidates",{"3":{"782":1,"1310":1,"1349":2,"1675":1}}],["consolidate",{"3":{"435":1,"782":1}}],["consoles",{"3":{"1480":1}}],["console",{"2":{"1549":1},"3":{"214":1,"401":1,"481":1,"776":1,"914":2,"1041":1,"1044":1,"1322":2,"1323":2,"1332":1,"1338":2,"1394":7,"1445":1,"1446":1,"1450":1,"1466":1,"1474":1,"1479":4,"1549":1,"2004":2,"2012":1,"2146":1,"2149":1}}],["consciously",{"3":{"1435":2,"1520":1}}],["conscious",{"3":{"87":1,"1252":1,"1299":1,"1525":1,"1565":1,"1570":1,"1708":1}}],["conserving",{"3":{"1416":2}}],["conserve",{"3":{"1416":1}}],["conservative",{"3":{"529":1,"1237":1,"1285":3,"1300":2,"1349":1,"1416":3,"1454":1,"1915":1}}],["consented",{"3":{"1395":1}}],["consent",{"3":{"1395":2,"1416":1,"1566":1,"1573":1,"1574":3,"1576":2,"1580":1,"1581":2,"1583":1,"1590":1,"1596":1}}],["consensus",{"3":{"0":1,"996":1,"1270":1,"1908":1}}],["consequently",{"3":{"1300":1,"1368":1,"1395":1}}],["consequentialpropertykey",{"3":{"1426":1}}],["consequentially",{"3":{"1229":1}}],["consequential",{"2":{"1421":1},"3":{"1012":1,"1091":1,"1092":1,"1095":2,"1395":1,"1421":2,"1426":5,"1525":1,"1552":1,"1576":1}}],["consequences",{"0":{"435":1,"441":1,"1094":1,"1179":1,"1188":1},"3":{"0":2,"101":1,"123":1,"178":1,"436":1,"444":1,"527":1,"555":1,"629":1,"891":1,"1034":1,"1089":2,"1259":1,"1285":2,"1308":1,"1322":2,"1349":1,"1358":3,"1371":1,"1379":1,"1394":1,"1414":2,"1416":1,"1435":1,"1481":1,"1485":1,"1570":1,"1672":1,"1809":1,"1917":1,"1947":1,"1948":1,"2126":1}}],["consequence",{"3":{"0":3,"23":1,"24":1,"27":1,"96":1,"122":1,"200":1,"201":1,"353":1,"359":1,"390":1,"415":1,"543":3,"549":1,"611":2,"643":1,"659":1,"706":1,"790":1,"793":1,"829":2,"875":1,"914":1,"931":1,"941":1,"988":1,"1050":1,"1059":1,"1082":1,"1229":1,"1231":1,"1247":1,"1252":1,"1259":3,"1270":2,"1271":1,"1285":5,"1296":1,"1299":8,"1300":1,"1308":2,"1310":7,"1322":6,"1323":4,"1338":4,"1349":4,"1354":1,"1357":1,"1358":7,"1368":3,"1379":2,"1394":6,"1395":12,"1414":7,"1415":1,"1416":1,"1435":9,"1437":1,"1458":1,"1466":3,"1471":1,"1474":2,"1650":1,"1685":2,"1848":1,"1860":1,"1885":1,"1909":1,"1946":1,"1981":1,"2034":1,"2041":1,"2157":1,"2162":1,"2163":1,"2164":1,"2165":1}}],["consecutivefailures",{"2":{"1337":1},"3":{"827":1,"1337":1,"1338":1,"1394":1}}],["consecutive",{"3":{"281":1,"459":1,"609":1,"799":1,"803":1,"1003":1,"1006":1,"1007":1,"1048":1,"1054":2,"1242":1,"1247":1,"1284":1,"1285":1,"1291":1,"1306":1,"1389":2,"1394":2,"1414":1,"1437":1,"1455":1,"1473":1,"1491":1,"1640":1,"1715":1,"1999":1}}],["consultant",{"3":{"1394":1}}],["consulted",{"3":{"443":1,"1058":1,"1116":1,"1184":1,"1247":1,"1284":1,"1285":2,"1299":2,"1310":2,"1322":1,"1323":1,"1330":1,"1338":2,"1358":3,"1390":1,"1394":2,"1414":1,"1426":1,"1431":1,"1993":1,"2198":1}}],["consults",{"3":{"249":1,"295":1,"365":1,"384":1,"420":1,"819":1,"827":1,"885":1,"1117":1,"1189":1,"1237":1,"1242":1,"1247":1,"1285":3,"1306":1,"1310":3,"1322":1,"1323":1,"1358":2,"1405":1,"1435":1,"1669":1,"1842":1,"1944":1,"1975":1,"2141":1}}],["consult",{"3":{"0":1,"245":1,"318":1,"819":1,"846":1,"1025":1,"1212":1,"1236":1,"1285":1,"1300":1,"1308":1,"1349":1,"1358":1,"1416":1,"1426":1,"1473":2,"1475":1,"1477":1,"1996":1}}],["consulting",{"3":{"0":1,"1685":1}}],["consumable",{"3":{"955":1,"1349":1,"1437":1}}],["consumption",{"3":{"31":1,"195":1,"234":1,"373":1,"530":1,"543":2,"576":1,"674":1,"692":1,"941":1,"954":1,"1299":3,"1322":1,"1338":3,"1395":2,"1442":2,"1449":1,"1452":1,"1453":1,"1466":3,"1487":1,"1488":1,"1576":1,"1664":1,"1674":1,"1676":1,"1677":1,"2010":1,"2011":1}}],["consuming",{"0":{"1258":1},"3":{"0":2,"26":1,"53":2,"68":1,"72":1,"81":1,"195":2,"197":1,"200":1,"201":1,"202":1,"203":1,"273":1,"309":1,"370":1,"382":1,"390":1,"470":1,"498":1,"518":1,"557":1,"571":1,"572":1,"585":1,"617":1,"702":1,"921":1,"945":1,"956":2,"958":1,"960":1,"1024":1,"1067":1,"1100":1,"1229":1,"1231":1,"1270":5,"1285":2,"1299":8,"1303":2,"1307":1,"1308":10,"1310":2,"1311":3,"1322":5,"1323":8,"1325":2,"1326":1,"1338":7,"1349":1,"1358":1,"1394":4,"1395":3,"1414":3,"1416":4,"1426":1,"1429":1,"1431":1,"1435":3,"1443":1,"1473":1,"1499":1,"1525":1,"1573":1,"1576":2,"1587":1,"1650":1,"1662":1,"1735":1,"1809":1,"1897":1,"1932":1,"1948":1,"1952":1,"1957":1,"1980":1,"2008":1,"2011":1,"2032":1,"2033":1,"2054":1,"2082":1,"2110":1,"2155":1,"2157":1,"2187":1}}],["consumecontext",{"3":{"150":1,"1259":1,"1322":1}}],["consumesthetokenbeforesaving",{"3":{"1322":1}}],["consumes",{"3":{"0":1,"59":1,"124":1,"195":6,"203":1,"375":1,"391":1,"483":1,"507":1,"508":1,"543":1,"585":2,"587":1,"665":1,"694":1,"751":1,"880":1,"990":1,"1200":1,"1236":1,"1247":1,"1259":5,"1266":1,"1270":1,"1285":3,"1293":1,"1299":4,"1308":2,"1309":2,"1310":4,"1311":1,"1321":1,"1322":1,"1323":4,"1331":1,"1338":3,"1340":1,"1348":1,"1349":1,"1358":10,"1368":1,"1377":1,"1395":13,"1401":1,"1414":1,"1415":1,"1416":8,"1426":1,"1435":4,"1437":3,"1440":1,"1450":1,"1454":2,"1459":1,"1466":2,"1471":1,"1474":1,"1487":2,"1488":2,"1495":1,"1525":2,"1530":1,"1598":1,"1640":1,"1747":1,"1764":1,"1842":1,"1873":1,"1921":1,"1962":1,"2003":1,"2120":1}}],["consumed",{"3":{"0":3,"146":1,"177":1,"202":2,"223":1,"265":1,"368":1,"374":1,"383":1,"481":1,"545":1,"549":1,"692":1,"733":1,"829":1,"856":1,"1124":1,"1144":1,"1212":1,"1237":5,"1240":1,"1247":6,"1249":1,"1259":10,"1260":1,"1270":7,"1285":23,"1290":1,"1299":32,"1300":5,"1308":9,"1309":3,"1310":9,"1319":1,"1322":11,"1323":31,"1333":1,"1338":17,"1343":1,"1344":1,"1349":22,"1358":22,"1379":27,"1394":17,"1395":39,"1401":14,"1414":12,"1415":4,"1416":38,"1426":4,"1431":1,"1435":14,"1436":1,"1437":1,"1442":1,"1452":1,"1454":2,"1455":1,"1466":1,"1487":2,"1491":1,"1495":1,"1524":1,"1525":3,"1526":1,"1534":1,"1554":1,"1590":2,"1637":2,"1640":2,"1747":1,"2054":1,"2065":1,"2100":1,"2220":1,"2227":1}}],["consume",{"3":{"0":5,"21":1,"109":1,"145":1,"150":2,"194":2,"195":2,"196":1,"199":1,"201":1,"202":1,"247":1,"369":1,"391":1,"535":1,"548":1,"599":1,"626":1,"656":1,"815":1,"818":2,"819":1,"823":1,"832":1,"1042":1,"1044":1,"1074":1,"1076":1,"1091":1,"1122":1,"1140":1,"1147":1,"1225":1,"1229":1,"1237":1,"1247":1,"1259":6,"1269":1,"1270":2,"1271":3,"1285":1,"1299":8,"1308":1,"1311":1,"1317":1,"1322":7,"1323":6,"1324":1,"1325":1,"1338":3,"1349":3,"1358":4,"1388":1,"1394":2,"1395":2,"1401":1,"1407":1,"1412":1,"1414":2,"1416":4,"1427":1,"1430":1,"1432":1,"1434":1,"1435":5,"1437":3,"1443":1,"1466":1,"1487":1,"1502":1,"1576":1,"1581":1,"1590":1,"1637":1,"1647":1,"1648":1,"1668":1,"1723":1,"1764":1,"1783":1,"1784":1,"1842":2,"1886":1,"1909":1,"2041":1,"2108":1,"2155":1,"2161":1,"2187":1,"2191":1}}],["consumermodule",{"3":{"1322":1}}],["consumerecoverycodeasync",{"3":{"1299":2}}],["consumeroriginrestore",{"2":{"150":1,"202":1,"1074":1,"1253":1},"3":{"145":1,"150":3,"202":4,"702":1,"1074":1,"1199":3,"1203":1,"1207":2,"1253":1,"1259":2,"1299":2,"1312":1,"1317":4,"1322":9,"1495":2}}],["consumers",{"0":{"452":1,"1708":1,"1881":1,"2054":1},"2":{"1259":1,"1322":1},"3":{"0":23,"27":1,"32":1,"41":2,"62":1,"68":1,"69":1,"71":1,"76":1,"77":1,"78":4,"79":1,"91":1,"109":1,"110":1,"118":1,"119":1,"122":1,"127":1,"133":1,"135":2,"136":1,"143":1,"145":2,"146":1,"147":1,"148":1,"150":8,"151":1,"156":1,"160":1,"162":1,"166":1,"201":1,"202":6,"255":4,"256":1,"259":1,"264":1,"312":1,"341":1,"342":1,"359":1,"369":1,"370":1,"375":1,"390":1,"391":1,"435":1,"446":1,"447":1,"451":1,"452":1,"470":1,"490":1,"491":1,"527":1,"530":1,"543":1,"549":1,"550":1,"552":1,"570":1,"572":1,"573":1,"574":2,"576":1,"577":2,"579":1,"591":1,"593":1,"598":1,"600":1,"607":2,"609":3,"638":4,"640":2,"665":2,"668":1,"682":1,"692":1,"698":2,"700":1,"702":2,"715":1,"723":1,"724":1,"725":1,"731":1,"733":1,"736":1,"739":2,"742":1,"743":2,"799":1,"800":1,"819":2,"820":1,"821":1,"825":3,"827":3,"829":1,"832":2,"833":1,"836":1,"845":1,"846":1,"849":1,"854":1,"856":1,"867":2,"870":1,"873":1,"876":1,"891":2,"912":1,"914":1,"918":1,"922":1,"948":1,"956":1,"958":1,"963":1,"980":1,"981":1,"998":1,"1004":3,"1007":1,"1008":1,"1044":1,"1058":1,"1059":4,"1061":1,"1074":3,"1085":1,"1092":1,"1140":1,"1176":1,"1177":1,"1178":1,"1192":1,"1193":1,"1203":6,"1207":58,"1208":1,"1212":3,"1229":1,"1234":1,"1237":5,"1247":4,"1257":1,"1258":4,"1259":27,"1270":6,"1271":2,"1283":1,"1284":1,"1285":13,"1298":1,"1299":7,"1300":1,"1308":3,"1309":3,"1310":9,"1311":1,"1312":1,"1316":4,"1317":3,"1319":1,"1322":30,"1323":15,"1338":6,"1349":6,"1358":4,"1368":2,"1379":6,"1394":2,"1395":17,"1401":3,"1412":1,"1414":8,"1416":4,"1426":1,"1430":1,"1435":28,"1437":6,"1440":1,"1442":1,"1452":2,"1453":1,"1466":2,"1468":1,"1487":1,"1488":2,"1490":2,"1491":1,"1495":2,"1497":1,"1507":1,"1525":1,"1527":1,"1542":2,"1545":1,"1546":1,"1550":1,"1568":3,"1569":1,"1571":1,"1574":2,"1576":5,"1578":1,"1580":1,"1581":2,"1582":1,"1584":7,"1588":1,"1590":1,"1595":1,"1598":2,"1630":1,"1631":2,"1638":1,"1640":1,"1659":1,"1672":2,"1673":1,"1675":1,"1676":1,"1685":1,"1698":1,"1705":1,"1710":1,"1735":1,"1768":2,"1769":1,"1804":1,"1845":1,"1874":1,"1885":1,"1886":2,"1888":3,"1889":2,"1890":1,"1891":3,"1910":1,"1911":2,"1919":1,"1973":1,"2003":1,"2026":1,"2041":1,"2046":1,"2054":2,"2059":1,"2064":1,"2067":1,"2111":1,"2112":1,"2113":1,"2114":1,"2129":1,"2155":1,"2161":2,"2181":2,"2201":4,"2202":1,"2231":1}}],["consumer",{"0":{"451":1,"1258":1,"1677":1,"1735":1,"1842":1},"1":{"192":1,"193":1,"194":1,"195":1,"196":1,"197":1,"198":1,"199":1,"200":1,"201":1,"202":1,"203":1},"3":{"0":48,"15":2,"17":1,"24":2,"25":2,"26":1,"27":2,"38":1,"39":2,"41":3,"59":1,"61":1,"62":2,"66":1,"67":1,"68":1,"70":1,"72":2,"73":1,"77":1,"78":1,"80":4,"81":1,"91":2,"95":1,"97":1,"98":1,"100":1,"101":1,"109":1,"111":2,"116":1,"117":1,"124":1,"127":1,"132":1,"134":1,"135":4,"136":5,"137":2,"145":2,"147":1,"149":1,"150":1,"151":2,"152":1,"160":2,"175":1,"189":1,"192":1,"193":2,"194":3,"195":4,"196":1,"197":1,"198":4,"199":2,"201":1,"202":3,"203":3,"225":1,"226":1,"241":1,"247":1,"255":8,"256":1,"258":1,"265":1,"273":1,"279":1,"283":1,"305":1,"311":1,"340":1,"344":1,"346":1,"359":1,"361":1,"369":1,"373":1,"374":2,"376":1,"391":1,"401":1,"412":1,"418":1,"432":1,"438":1,"439":1,"440":1,"441":1,"443":1,"448":2,"450":1,"451":3,"455":1,"457":1,"465":1,"474":1,"480":1,"481":1,"483":2,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":2,"495":1,"527":1,"528":1,"530":1,"543":2,"545":2,"549":1,"550":1,"551":3,"564":4,"572":3,"573":1,"574":1,"576":1,"577":2,"578":1,"583":1,"594":2,"598":1,"599":2,"605":1,"607":3,"609":6,"610":2,"611":2,"614":1,"616":2,"618":2,"619":1,"620":1,"624":1,"626":2,"629":1,"640":1,"641":1,"645":1,"659":1,"660":1,"672":1,"674":4,"675":1,"676":1,"684":2,"689":1,"690":2,"691":3,"696":2,"697":1,"698":2,"700":1,"707":2,"708":1,"714":2,"715":2,"716":2,"717":2,"721":1,"723":1,"724":1,"725":3,"727":3,"729":1,"733":1,"737":1,"740":1,"741":3,"742":2,"743":2,"747":1,"749":1,"751":2,"759":1,"780":2,"782":2,"783":1,"786":1,"789":1,"799":1,"802":1,"818":1,"819":7,"820":2,"821":2,"825":2,"827":6,"829":2,"832":1,"833":3,"836":3,"839":1,"840":1,"844":1,"845":2,"846":2,"847":1,"848":4,"849":1,"853":3,"854":2,"855":1,"861":1,"862":1,"865":1,"881":1,"889":2,"891":2,"903":1,"905":5,"907":6,"908":1,"910":2,"913":1,"914":2,"920":1,"923":1,"924":1,"926":3,"927":1,"930":1,"931":1,"935":1,"937":1,"939":1,"940":1,"941":3,"942":1,"954":2,"957":1,"958":1,"959":1,"963":1,"964":1,"965":1,"966":1,"972":1,"973":1,"974":1,"976":1,"979":1,"980":1,"982":1,"984":1,"986":1,"988":2,"994":1,"996":3,"999":1,"1000":1,"1003":1,"1004":2,"1006":1,"1008":1,"1012":2,"1016":1,"1033":2,"1034":2,"1035":1,"1036":1,"1037":1,"1043":1,"1044":1,"1049":1,"1050":6,"1051":2,"1052":1,"1058":2,"1059":6,"1060":1,"1061":4,"1065":2,"1067":3,"1068":4,"1069":3,"1070":2,"1074":3,"1075":5,"1076":1,"1077":3,"1078":1,"1089":2,"1091":1,"1093":3,"1096":1,"1117":2,"1160":1,"1162":1,"1177":2,"1178":1,"1179":1,"1185":1,"1188":1,"1195":1,"1202":1,"1203":1,"1211":1,"1212":10,"1228":1,"1229":3,"1233":1,"1237":5,"1241":1,"1247":10,"1248":1,"1249":2,"1256":1,"1258":6,"1259":81,"1261":1,"1262":1,"1269":3,"1270":15,"1271":8,"1273":2,"1274":1,"1285":43,"1289":1,"1290":1,"1299":25,"1300":1,"1308":10,"1309":1,"1310":31,"1311":9,"1314":1,"1316":3,"1317":2,"1319":1,"1322":60,"1323":52,"1325":1,"1338":31,"1344":1,"1347":1,"1348":1,"1349":28,"1358":19,"1368":1,"1377":1,"1378":1,"1379":9,"1380":2,"1385":1,"1394":6,"1395":42,"1397":1,"1401":7,"1405":1,"1411":1,"1412":1,"1414":17,"1415":1,"1416":14,"1417":1,"1420":1,"1426":6,"1427":2,"1428":2,"1429":1,"1430":4,"1431":1,"1432":1,"1435":159,"1436":4,"1437":12,"1440":3,"1441":1,"1442":5,"1450":1,"1452":2,"1453":4,"1454":2,"1459":1,"1460":1,"1464":1,"1466":3,"1475":3,"1486":2,"1487":4,"1488":2,"1489":1,"1491":7,"1492":1,"1495":3,"1502":1,"1525":2,"1527":1,"1542":1,"1545":2,"1546":5,"1550":1,"1571":1,"1573":3,"1574":10,"1575":2,"1576":28,"1577":1,"1578":1,"1580":3,"1581":7,"1582":1,"1583":2,"1584":10,"1585":2,"1588":2,"1590":4,"1592":1,"1595":3,"1596":1,"1598":1,"1610":2,"1630":1,"1631":11,"1644":2,"1655":1,"1658":1,"1659":1,"1661":1,"1663":2,"1664":4,"1665":2,"1670":2,"1672":4,"1673":1,"1674":1,"1675":1,"1676":1,"1677":1,"1678":2,"1679":1,"1684":1,"1685":2,"1705":1,"1706":5,"1707":1,"1720":1,"1726":2,"1731":1,"1733":1,"1734":1,"1748":1,"1750":3,"1763":1,"1764":1,"1768":7,"1771":1,"1781":1,"1783":1,"1798":1,"1799":1,"1809":3,"1814":2,"1825":1,"1828":1,"1842":5,"1844":2,"1850":1,"1855":2,"1864":1,"1869":1,"1885":1,"1886":2,"1887":4,"1888":7,"1889":3,"1890":6,"1891":1,"1895":1,"1898":1,"1902":1,"1905":1,"1909":5,"1910":2,"1911":1,"1918":1,"1920":1,"1921":2,"1922":3,"1925":1,"1926":1,"1928":2,"1929":1,"1947":1,"1948":1,"1969":1,"2004":1,"2005":3,"2008":1,"2016":1,"2024":2,"2041":1,"2043":2,"2045":2,"2055":1,"2056":1,"2058":1,"2059":1,"2062":2,"2066":1,"2067":3,"2068":1,"2071":1,"2073":2,"2076":1,"2082":1,"2084":2,"2086":1,"2106":3,"2108":1,"2111":3,"2113":1,"2123":1,"2125":2,"2126":2,"2130":1,"2132":1,"2149":1,"2155":2,"2165":1,"2172":1,"2178":2,"2181":1,"2193":1,"2195":1,"2196":1,"2197":2,"2200":2,"2201":1,"2202":1,"2231":5}}],["consts",{"3":{"1394":2,"1395":2}}],["const",{"2":{"2074":1},"3":{"256":1,"258":1,"305":1,"310":1,"897":1,"946":1,"1229":1,"1237":1,"1242":1,"1243":1,"1247":4,"1270":6,"1271":1,"1285":7,"1299":29,"1300":3,"1303":1,"1308":12,"1310":8,"1311":1,"1322":3,"1323":9,"1338":20,"1343":2,"1347":1,"1349":16,"1358":11,"1368":1,"1383":1,"1394":3,"1395":16,"1401":12,"1404":1,"1414":13,"1415":1,"1416":2,"1426":2,"1435":11,"1576":1,"1652":1,"2074":1}}],["constantexpression",{"3":{"1241":1,"1247":1,"1285":1,"1988":1}}],["constantly",{"3":{"656":1,"661":1,"1323":1,"1569":1,"2049":1,"2059":2}}],["constants",{"3":{"0":1,"186":2,"188":1,"202":1,"300":1,"310":1,"318":1,"359":1,"657":1,"731":1,"749":1,"859":1,"861":2,"883":1,"905":1,"946":2,"1067":1,"1068":1,"1184":1,"1212":1,"1214":1,"1229":4,"1234":2,"1237":10,"1247":2,"1270":1,"1271":6,"1285":14,"1289":1,"1299":18,"1308":10,"1310":14,"1322":7,"1323":31,"1324":1,"1325":2,"1329":1,"1331":2,"1332":1,"1338":23,"1340":1,"1343":2,"1349":66,"1358":14,"1361":1,"1368":7,"1372":1,"1379":1,"1383":1,"1389":1,"1394":18,"1395":23,"1401":12,"1403":1,"1404":1,"1411":1,"1413":1,"1414":22,"1415":7,"1416":4,"1426":2,"1429":1,"1435":40,"1437":2,"1443":1,"1495":1,"1525":2,"1576":1,"1652":1,"1676":1,"1684":1,"1809":2,"1828":1,"1832":1,"1833":1,"1901":1,"1943":2,"1963":1,"1967":1,"1988":1,"2055":1,"2135":1,"2137":1,"2138":1,"2141":1}}],["constant",{"0":{"1901":1},"3":{"0":3,"24":1,"100":1,"179":1,"186":3,"213":1,"214":1,"217":1,"280":1,"305":3,"310":1,"312":1,"318":1,"391":1,"413":1,"418":1,"422":1,"423":2,"425":1,"457":1,"459":1,"463":1,"609":1,"619":2,"620":1,"697":1,"698":3,"699":1,"702":1,"740":1,"751":1,"790":1,"793":1,"827":4,"856":1,"880":1,"881":2,"885":1,"897":2,"905":2,"945":1,"946":3,"948":2,"980":1,"1017":1,"1018":4,"1067":1,"1090":1,"1094":1,"1095":1,"1100":1,"1134":1,"1161":1,"1175":1,"1212":2,"1232":1,"1233":1,"1237":6,"1247":5,"1255":1,"1259":4,"1267":1,"1270":1,"1271":15,"1276":2,"1285":16,"1287":1,"1289":1,"1291":2,"1293":1,"1294":2,"1296":1,"1299":28,"1302":1,"1304":1,"1308":11,"1310":18,"1311":1,"1322":17,"1323":12,"1325":2,"1327":1,"1329":1,"1332":3,"1335":1,"1336":2,"1338":14,"1343":4,"1347":1,"1349":26,"1352":1,"1358":73,"1365":3,"1366":1,"1368":7,"1379":1,"1383":1,"1388":1,"1392":1,"1394":28,"1395":23,"1397":1,"1398":1,"1401":19,"1404":1,"1414":9,"1415":5,"1416":29,"1422":1,"1426":1,"1430":1,"1435":31,"1440":1,"1442":2,"1487":1,"1525":1,"1640":2,"1650":1,"1666":1,"1667":1,"1669":1,"1682":1,"1711":1,"1726":2,"1764":2,"1766":1,"1770":1,"1832":1,"1850":2,"1901":3,"1904":2,"1915":2,"1918":1,"1934":1,"1957":1,"1981":1,"2023":1,"2055":1,"2056":1,"2072":1,"2074":1,"2117":1,"2122":1,"2137":4,"2146":1,"2147":1,"2148":1,"2152":1,"2192":1,"2238":1}}],["constrain",{"3":{"95":1,"725":1,"774":1,"1025":1,"1285":1,"1310":1,"1338":1,"1349":1,"1358":6,"1414":1,"2140":1}}],["constrains",{"3":{"71":1,"95":1,"216":1,"372":1,"427":1,"469":1,"470":1,"568":1,"630":1,"675":1,"732":1,"801":1,"804":1,"819":1,"889":1,"1049":1,"1232":1,"1237":2,"1270":1,"1271":1,"1280":1,"1285":3,"1290":1,"1299":2,"1308":3,"1310":1,"1311":1,"1322":1,"1338":1,"1358":11,"1374":1,"1394":1,"1395":3,"1414":5,"1435":1,"1474":1,"1809":1,"2031":1,"2151":1}}],["constrained",{"3":{"0":1,"782":1,"1018":2,"1050":1,"1090":1,"1212":1,"1231":2,"1237":3,"1247":4,"1259":7,"1270":9,"1271":5,"1285":11,"1296":1,"1299":4,"1309":1,"1310":5,"1322":10,"1323":4,"1349":2,"1352":2,"1358":22,"1365":1,"1368":1,"1394":3,"1401":1,"1416":1,"1435":5,"1495":1,"1525":1,"1952":1}}],["constraining",{"3":{"0":1,"1093":1,"1237":1,"1270":2,"1285":1,"1322":1,"1358":1,"2178":1}}],["constraints",{"0":{"1634":1,"1674":1},"3":{"0":1,"47":1,"61":1,"62":1,"92":1,"166":1,"168":1,"412":1,"657":1,"681":1,"725":1,"732":1,"848":1,"921":1,"1074":1,"1083":1,"1085":1,"1196":1,"1237":1,"1247":4,"1259":1,"1270":7,"1280":1,"1285":10,"1308":1,"1309":1,"1310":9,"1322":11,"1323":2,"1338":1,"1349":1,"1358":15,"1368":2,"1395":2,"1401":1,"1414":1,"1415":2,"1416":6,"1430":1,"1435":4,"1472":1,"1568":1,"1601":1,"1631":8,"1634":2,"1638":1,"1639":2,"1640":1,"1651":1,"1952":1,"1966":1,"2063":1}}],["constraint",{"3":{"0":4,"62":1,"92":1,"101":1,"109":1,"119":1,"145":1,"150":1,"159":1,"345":1,"363":1,"364":1,"411":1,"415":1,"464":1,"476":1,"641":1,"657":1,"691":1,"692":1,"723":1,"759":1,"792":1,"808":1,"810":1,"811":1,"814":1,"818":1,"825":1,"826":2,"827":1,"869":1,"915":1,"955":1,"1036":1,"1082":2,"1137":1,"1237":5,"1239":1,"1247":16,"1259":7,"1260":1,"1269":1,"1270":24,"1271":1,"1283":1,"1285":49,"1290":2,"1299":23,"1308":2,"1309":6,"1310":17,"1318":1,"1321":1,"1322":22,"1323":13,"1338":6,"1343":1,"1349":5,"1351":1,"1352":1,"1356":1,"1358":69,"1367":1,"1368":3,"1394":2,"1395":22,"1401":3,"1414":9,"1416":4,"1435":13,"1437":1,"1464":1,"1466":5,"1477":1,"1487":1,"1488":1,"1533":1,"1584":3,"1590":1,"1630":2,"1634":3,"1637":2,"1638":2,"1640":1,"1662":2,"1674":1,"1723":1,"1747":1,"1759":1,"1764":1,"1791":1,"1809":1,"1810":1,"1814":1,"1823":2,"1825":1,"1829":1,"1848":1,"1852":3,"1857":2,"1861":1,"1865":1,"1872":1,"1875":2,"1910":1,"1915":1,"1918":1,"1926":1,"1948":1,"1957":1,"1987":1,"1988":1,"2023":1,"2024":1,"2068":1,"2070":2,"2094":1,"2111":1,"2115":1,"2149":1,"2191":1}}],["constructevent",{"2":{"1764":1,"1769":1},"3":{"1764":1,"1769":1}}],["constructedexceptiontype",{"3":{"1435":2}}],["constructed",{"3":{"0":2,"72":1,"73":1,"109":1,"359":1,"365":1,"549":1,"572":1,"585":1,"657":1,"665":1,"674":1,"698":1,"733":1,"741":1,"784":1,"861":1,"922":2,"926":1,"1016":1,"1107":1,"1110":1,"1212":1,"1229":2,"1234":1,"1237":4,"1244":1,"1246":1,"1247":7,"1259":2,"1262":1,"1270":3,"1271":1,"1273":1,"1274":1,"1285":25,"1299":14,"1300":2,"1308":5,"1309":6,"1310":7,"1322":11,"1323":11,"1338":9,"1342":1,"1349":5,"1358":77,"1363":1,"1367":2,"1379":1,"1394":8,"1395":15,"1401":10,"1414":12,"1415":1,"1416":7,"1428":1,"1431":1,"1435":39,"1437":1,"1487":2,"1540":1,"1809":1,"1811":1,"1825":1,"1864":1,"2054":1,"2229":1}}],["construct",{"3":{"725":1,"726":1,"1018":1,"1060":1,"1229":1,"1237":1,"1247":1,"1270":1,"1271":2,"1278":1,"1285":4,"1299":2,"1308":2,"1310":1,"1322":1,"1323":3,"1338":2,"1349":6,"1358":6,"1395":2,"1414":1,"1426":1,"1435":13,"1440":1,"1630":1,"1650":1,"1808":1,"2169":1}}],["constructible",{"3":{"1285":2,"1322":1}}],["constructing",{"3":{"361":1,"435":1,"782":1,"1247":1,"1259":3,"1270":1,"1285":4,"1308":2,"1310":6,"1342":1,"1358":2,"1404":1,"1414":1,"1426":1,"1435":4,"2141":1}}],["constructions",{"3":{"837":1,"1358":1}}],["construction",{"0":{"1221":1},"3":{"0":9,"20":1,"24":1,"60":1,"71":1,"79":1,"121":1,"123":1,"219":1,"234":1,"248":1,"255":1,"291":1,"295":1,"359":4,"361":1,"545":1,"546":1,"547":1,"549":2,"552":1,"574":2,"640":1,"655":1,"657":1,"667":1,"683":1,"690":1,"698":2,"715":1,"743":1,"750":1,"829":2,"834":1,"846":2,"881":1,"891":1,"926":1,"1018":1,"1020":1,"1034":1,"1051":1,"1085":1,"1090":1,"1091":2,"1124":1,"1160":1,"1163":1,"1168":3,"1169":1,"1212":1,"1220":1,"1221":1,"1223":1,"1228":1,"1229":5,"1231":1,"1233":1,"1237":10,"1247":5,"1249":1,"1253":1,"1255":1,"1258":1,"1259":10,"1267":1,"1270":5,"1271":1,"1274":1,"1279":1,"1283":1,"1285":33,"1299":7,"1300":6,"1301":1,"1308":3,"1309":1,"1310":17,"1319":1,"1322":15,"1323":10,"1338":2,"1346":1,"1349":8,"1358":22,"1379":3,"1394":7,"1395":16,"1401":5,"1414":5,"1416":9,"1419":1,"1420":1,"1422":1,"1426":2,"1435":15,"1454":1,"1473":1,"1487":1,"1488":1,"1497":1,"1538":1,"1547":1,"1576":1,"1590":1,"1664":1,"1684":1,"1763":1,"1764":1,"1766":1,"1808":1,"1809":1,"1810":1,"1811":1,"1824":1,"1828":1,"1832":1,"1838":1,"1888":1,"1919":1,"1922":1,"1923":1,"1999":1,"2023":1,"2051":1,"2072":1,"2089":1,"2125":1,"2141":2,"2142":1,"2144":1,"2149":2,"2172":1,"2174":1,"2198":1}}],["constructs",{"3":{"166":1,"230":1,"556":1,"703":1,"861":1,"885":1,"922":1,"1016":1,"1018":1,"1035":1,"1090":1,"1229":1,"1247":1,"1259":3,"1265":1,"1270":4,"1271":3,"1285":10,"1296":1,"1299":1,"1300":1,"1308":2,"1310":10,"1314":2,"1322":3,"1323":2,"1338":9,"1349":8,"1358":13,"1368":3,"1376":1,"1379":4,"1394":2,"1395":2,"1401":7,"1414":5,"1415":1,"1416":4,"1426":2,"1435":18,"1437":1,"1490":1,"1590":1,"1684":1,"1937":1,"2171":1}}],["constructordependencies",{"3":{"1576":1,"1581":1,"1582":1,"1583":1,"1585":1}}],["constructordependencyfixtures",{"3":{"1207":18,"1435":28}}],["constructordependencycounttests",{"2":{"1590":1},"3":{"1199":1,"1207":2,"1370":1,"1379":3,"1414":1,"1435":9,"1488":1,"1495":2,"1525":1,"1533":2,"1534":1,"1590":2,"1596":1,"1598":1}}],["constructordependencycounttestsbasetests",{"2":{"1430":1},"3":{"1199":1,"1207":7,"1430":3,"1435":37,"1576":1}}],["constructordependencycounttestsbase",{"2":{"1430":1,"1488":1,"1653":1},"3":{"1199":5,"1207":2,"1430":3,"1435":26,"1488":1,"1495":1,"1653":1}}],["constructorarity",{"3":{"1435":1}}],["constructors",{"3":{"497":1,"499":1,"674":1,"964":1,"1221":1,"1223":1,"1226":2,"1229":13,"1247":2,"1271":2,"1285":3,"1308":5,"1310":1,"1311":6,"1338":1,"1349":10,"1358":1,"1395":4,"1401":2,"1414":1,"1422":1,"1426":3,"1435":5,"1437":1,"1525":1,"1537":1,"1585":1,"1650":1,"1804":1,"1810":1,"1811":1,"2103":1,"2141":1}}],["constructor",{"0":{"2089":1},"3":{"0":4,"122":1,"135":1,"160":1,"168":1,"230":1,"243":1,"245":1,"258":1,"281":2,"310":1,"314":1,"318":1,"323":1,"330":1,"359":3,"365":1,"388":1,"389":1,"435":1,"459":3,"465":2,"466":1,"474":1,"497":1,"499":1,"524":1,"545":2,"549":1,"578":1,"585":1,"633":3,"640":1,"642":1,"657":4,"673":1,"674":4,"676":1,"680":1,"692":1,"731":2,"733":2,"799":1,"801":2,"803":2,"827":1,"837":1,"838":1,"846":1,"854":1,"857":1,"881":1,"908":1,"909":1,"954":1,"964":1,"972":1,"994":1,"996":3,"999":1,"1054":1,"1059":3,"1061":1,"1089":1,"1090":1,"1091":1,"1094":1,"1167":1,"1168":4,"1169":1,"1193":1,"1212":1,"1221":1,"1227":3,"1228":1,"1229":17,"1231":1,"1234":2,"1237":12,"1240":1,"1244":2,"1247":24,"1252":1,"1259":19,"1261":1,"1267":1,"1268":1,"1269":2,"1270":24,"1271":13,"1273":1,"1278":1,"1285":100,"1287":1,"1288":1,"1292":1,"1299":70,"1300":12,"1306":1,"1308":36,"1309":8,"1310":27,"1311":6,"1315":1,"1316":1,"1319":1,"1322":45,"1323":58,"1335":2,"1338":28,"1341":1,"1342":2,"1349":34,"1352":1,"1358":219,"1362":1,"1368":9,"1370":2,"1377":1,"1379":17,"1394":24,"1395":43,"1398":1,"1401":25,"1404":2,"1405":1,"1406":1,"1414":46,"1415":8,"1416":37,"1422":1,"1425":1,"1426":15,"1435":107,"1437":5,"1487":3,"1488":1,"1495":2,"1524":1,"1525":1,"1533":1,"1534":1,"1576":2,"1581":1,"1582":1,"1583":1,"1585":1,"1587":1,"1590":2,"1596":1,"1598":1,"1599":1,"1667":1,"1670":1,"1672":1,"1684":2,"1688":1,"1698":1,"1701":1,"1726":1,"1809":3,"1810":4,"1811":1,"1819":1,"1828":1,"1829":1,"1944":1,"1982":1,"1993":1,"1999":3,"2023":1,"2086":1,"2088":3,"2089":2,"2097":1,"2141":1,"2148":2,"2175":1,"2193":1,"2195":1,"2201":3,"2202":1,"2231":1}}],["consists",{"3":{"1358":1}}],["consistency",{"0":{"1556":1},"3":{"0":1,"47":1,"49":1,"60":1,"61":1,"68":1,"166":1,"168":1,"169":1,"171":1,"273":1,"350":1,"535":1,"536":1,"784":1,"809":1,"971":1,"981":1,"1147":1,"1212":1,"1216":1,"1231":1,"1237":3,"1256":1,"1259":1,"1270":1,"1278":1,"1285":4,"1299":1,"1300":1,"1308":2,"1322":1,"1323":5,"1349":9,"1358":4,"1394":1,"1395":3,"1401":3,"1412":1,"1414":3,"1430":2,"1435":3,"1472":1,"1498":1,"1509":1,"1525":1,"1534":1,"1536":1,"1542":1,"1543":1,"1544":2,"1556":1,"1570":1,"1576":2,"1580":1,"1581":1,"1582":1,"1590":1,"1594":1,"1596":1,"1636":1,"1637":6,"1638":2,"1655":1,"1663":1,"1791":1,"1795":1,"1797":1,"1809":1,"1844":1,"1845":1,"1846":1,"1848":1,"1851":2,"1852":2,"1860":1,"1865":1,"1920":1,"1932":1,"2026":1,"2034":1,"2074":1,"2078":1,"2097":1,"2112":1,"2161":1,"2167":1,"2168":1,"2229":1,"2231":1}}],["consistently",{"3":{"102":1,"264":1,"578":1,"1216":1,"1270":2,"1271":1,"1299":2,"1310":1,"1322":2,"1323":3,"1358":7,"1394":2,"1485":1,"1525":2,"1536":1,"1538":1,"1544":1,"1551":1,"1556":1,"1571":1,"1576":1,"1590":1,"1630":1,"1796":1,"1801":1}}],["consistent",{"3":{"0":1,"7":1,"121":1,"136":1,"159":1,"195":1,"200":1,"244":1,"251":1,"252":1,"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"298":1,"302":1,"303":3,"332":1,"471":1,"484":1,"491":1,"492":1,"493":1,"509":1,"564":1,"644":1,"1118":1,"1167":1,"1218":1,"1226":1,"1229":5,"1237":3,"1247":1,"1259":1,"1271":1,"1285":3,"1299":6,"1308":3,"1310":7,"1311":2,"1322":4,"1323":4,"1338":1,"1339":1,"1348":1,"1349":5,"1354":1,"1358":15,"1368":1,"1369":1,"1379":1,"1394":5,"1395":4,"1401":4,"1402":1,"1412":1,"1414":3,"1416":3,"1428":1,"1432":1,"1435":10,"1440":1,"1487":1,"1488":1,"1491":1,"1525":3,"1536":1,"1545":2,"1551":1,"1560":2,"1561":1,"1563":1,"1569":1,"1576":2,"1590":1,"1629":1,"1634":1,"1638":2,"1640":1,"1670":1,"1678":1,"1796":1,"1804":1,"1830":1,"1831":1,"1925":1,"1938":1,"1989":1,"2054":1,"2080":1,"2137":1}}],["consideration",{"3":{"1416":1,"1629":1,"1630":1,"1638":1,"1639":1}}],["considerations",{"3":{"209":1}}],["consider",{"3":{"1358":1,"1434":1,"1754":1,"1775":1,"2097":1}}],["considers",{"3":{"675":1,"1259":1,"1285":1,"1310":1,"1323":1,"1338":1,"1638":1}}],["considered",{"3":{"0":1,"1":2,"387":1,"469":1,"470":1,"656":1,"733":1,"797":1,"800":1,"819":1,"1051":1,"1060":1,"1068":1,"1226":1,"1285":1,"1338":1,"1358":1,"1379":2,"1394":1,"1395":2,"1414":1,"1567":1,"1630":1}}],["contorted",{"3":{"1358":1}}],["contoso",{"2":{"1645":1,"1648":1,"1649":5,"1656":1,"1696":2,"1698":1,"1726":2,"1727":2,"2088":1},"3":{"556":1,"633":1,"1645":1,"1648":1,"1649":5,"1652":3,"1653":2,"1655":3,"1656":2,"1696":3,"1698":1,"1718":1,"1726":4,"1727":2,"2088":1}}],["contacts",{"3":{"1358":1,"1394":2}}],["contact",{"1":{"2243":1,"2244":1},"2":{"1237":1,"1271":1},"3":{"103":1,"390":1,"657":1,"1016":1,"1018":3,"1019":1,"1091":1,"1093":1,"1203":6,"1207":20,"1229":2,"1234":6,"1237":12,"1271":8,"1285":2,"1310":1,"1322":1,"1341":1,"1349":3,"1358":8,"1365":1,"1368":1,"1379":1,"1394":1,"1416":1,"1422":1,"1434":2,"1452":1,"1491":1,"1495":1,"1525":2,"1576":1,"1638":2,"1750":1,"1769":1,"1771":1,"1828":1,"1872":1,"2108":1,"2157":1,"2216":1,"2243":1}}],["contain",{"2":{"1729":1},"3":{"38":1,"258":1,"359":1,"401":1,"523":1,"568":1,"584":1,"1215":1,"1228":1,"1229":2,"1256":1,"1259":3,"1275":1,"1285":4,"1299":1,"1322":2,"1323":4,"1338":2,"1358":6,"1379":1,"1394":1,"1395":1,"1401":1,"1414":2,"1435":20,"1437":1,"1441":1,"1454":2,"1478":1,"1488":2,"1638":1,"1700":1,"1729":1,"1764":1,"1766":2,"1770":1,"1844":1,"2057":1,"2064":1}}],["containing",{"3":{"24":1,"177":1,"609":1,"633":1,"725":1,"980":1,"1228":1,"1237":2,"1255":1,"1270":1,"1297":1,"1299":2,"1308":1,"1310":1,"1323":4,"1338":2,"1358":6,"1394":1,"1395":1,"1416":1,"1435":15,"1457":1,"1487":1,"1638":2,"1640":3}}],["containment",{"3":{"0":1,"970":1,"975":1,"1018":1,"1020":1,"1036":1,"1247":1,"1323":1,"1358":3,"1368":1,"1395":1,"1670":1,"2231":1}}],["containsingle",{"3":{"1435":1}}],["containspredicate",{"3":{"1285":4,"1414":1}}],["containsregionclaim",{"2":{"972":1,"975":1},"3":{"972":2,"975":1}}],["contains",{"2":{"1239":1,"1858":1},"3":{"0":1,"94":1,"126":1,"135":1,"293":1,"459":1,"524":1,"583":1,"609":1,"741":1,"827":1,"926":1,"980":1,"981":1,"1018":1,"1085":1,"1234":2,"1237":3,"1239":2,"1241":3,"1247":20,"1259":2,"1265":1,"1270":3,"1285":10,"1299":5,"1309":2,"1310":3,"1322":4,"1323":2,"1338":3,"1349":3,"1358":46,"1368":2,"1394":18,"1395":7,"1401":3,"1414":5,"1416":1,"1435":41,"1437":1,"1441":1,"1446":1,"1453":2,"1454":1,"1458":1,"1465":1,"1466":2,"1470":1,"1485":1,"1525":1,"1595":1,"1597":1,"1598":1,"1599":1,"1639":3,"1640":1,"1650":1,"1734":1,"1735":1,"1748":1,"1754":1,"1760":1,"1763":4,"1828":1,"1848":1,"1858":2}}],["contained",{"3":{"0":1,"165":1,"196":1,"341":1,"389":1,"826":1,"837":1,"853":1,"945":1,"1033":1,"1145":1,"1155":1,"1237":2,"1271":1,"1299":1,"1300":1,"1308":2,"1309":2,"1310":1,"1322":1,"1323":2,"1338":1,"1349":5,"1358":5,"1365":1,"1379":1,"1394":2,"1395":2,"1401":5,"1406":1,"1414":1,"1415":1,"1435":3,"1485":1,"1495":1}}],["containerization",{"3":{"1553":1}}],["containerized",{"3":{"1310":1,"1338":1}}],["containerregistry",{"3":{"1464":1}}],["containerregistryloginevents",{"2":{"1464":1},"3":{"1464":1}}],["containerregistryrepositoryevents",{"2":{"1464":1},"3":{"1464":1}}],["containerresource",{"3":{"1338":1}}],["containerlifetime",{"2":{"1440":1},"3":{"1338":1,"1440":1}}],["containername",{"2":{"440":1},"3":{"440":1,"1322":2,"1466":1}}],["containerstarttimeout",{"3":{"640":1,"1435":2}}],["containers",{"1":{"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"1443":1,"1444":1,"1445":1,"1446":1,"1447":1,"1448":1,"1449":1,"1450":1},"3":{"0":2,"10":1,"62":1,"599":1,"869":1,"914":1,"915":1,"1069":1,"1324":1,"1331":1,"1358":1,"1435":2,"1438":2,"1440":2,"1444":1,"1448":1,"1449":1,"1461":1,"1466":5,"1467":1,"1486":1,"1489":2,"1860":1,"2003":1,"2007":1,"2012":1,"2212":1}}],["containerappconsolelogs",{"2":{"1475":1},"3":{"1466":1,"1475":1,"1590":1}}],["containerapps",{"3":{"1435":1,"1466":1}}],["containerappsystemlogs",{"2":{"609":1,"1464":1,"1475":1},"3":{"609":1,"1464":1,"1466":1,"1475":1}}],["containerappdeclaration",{"3":{"1435":1}}],["containerapp",{"2":{"563":1,"757":1,"1463":1},"3":{"0":1,"563":1,"757":1,"1212":1,"1463":7,"1473":1,"1475":1}}],["container",{"0":{"1384":1},"1":{"8":1,"9":1,"10":1,"11":1,"12":1,"13":1,"866":1,"867":1,"868":1,"869":1,"870":1,"871":1,"872":1,"873":1,"874":1,"875":1,"876":1,"877":1},"3":{"0":19,"8":1,"10":2,"31":1,"47":1,"61":1,"63":1,"91":2,"92":2,"93":1,"94":1,"95":1,"97":1,"98":2,"100":1,"102":1,"137":1,"159":1,"166":3,"168":1,"175":1,"202":1,"217":1,"234":2,"235":1,"242":1,"245":1,"291":3,"292":1,"374":1,"401":4,"402":1,"405":1,"413":1,"422":1,"440":2,"441":1,"443":1,"444":1,"451":1,"517":1,"527":1,"545":2,"550":1,"563":1,"564":1,"582":1,"583":1,"584":1,"585":1,"598":1,"599":2,"603":1,"626":1,"633":2,"639":1,"640":8,"641":1,"642":2,"643":1,"665":5,"666":2,"667":1,"669":2,"670":1,"733":2,"751":1,"756":2,"757":1,"758":1,"759":1,"764":1,"765":1,"766":3,"774":2,"783":1,"784":1,"790":1,"791":1,"792":1,"818":1,"820":1,"826":1,"827":5,"833":1,"837":1,"838":1,"866":1,"867":1,"868":1,"869":2,"870":1,"871":1,"872":1,"875":3,"913":2,"914":1,"915":1,"916":1,"924":1,"931":1,"971":1,"980":2,"995":1,"1004":1,"1017":3,"1022":1,"1034":1,"1037":1,"1052":1,"1067":3,"1069":1,"1101":1,"1107":1,"1116":6,"1117":1,"1118":3,"1123":2,"1124":2,"1125":4,"1154":1,"1156":1,"1175":1,"1176":2,"1178":2,"1192":1,"1202":1,"1203":1,"1212":5,"1244":1,"1247":1,"1259":4,"1261":1,"1269":1,"1270":2,"1271":3,"1281":1,"1283":1,"1284":1,"1285":29,"1299":3,"1300":9,"1306":1,"1308":10,"1309":1,"1310":11,"1311":2,"1314":2,"1315":1,"1321":2,"1322":22,"1323":16,"1324":2,"1325":2,"1326":2,"1330":1,"1331":2,"1332":1,"1336":1,"1338":39,"1339":1,"1341":1,"1349":4,"1356":3,"1358":8,"1368":1,"1377":1,"1379":1,"1384":1,"1394":13,"1395":6,"1400":1,"1401":15,"1414":5,"1415":4,"1416":11,"1420":1,"1426":3,"1428":1,"1429":1,"1435":32,"1437":7,"1439":2,"1440":6,"1441":2,"1442":3,"1443":4,"1444":3,"1445":7,"1446":11,"1448":1,"1449":1,"1454":13,"1456":4,"1457":1,"1462":1,"1463":1,"1464":6,"1466":51,"1468":2,"1471":2,"1472":3,"1473":3,"1475":2,"1476":2,"1477":1,"1480":1,"1483":1,"1485":1,"1486":1,"1487":2,"1488":2,"1489":1,"1495":2,"1523":1,"1524":1,"1525":4,"1533":2,"1534":1,"1554":1,"1576":2,"1588":2,"1590":4,"1594":1,"1596":1,"1610":2,"1611":1,"1615":1,"1616":1,"1630":2,"1638":1,"1640":1,"1647":2,"1652":1,"1653":1,"1669":4,"1676":2,"1681":1,"1688":2,"1691":1,"1692":1,"1717":4,"1723":1,"1751":1,"1762":1,"1773":1,"1791":1,"1809":1,"1819":1,"1826":1,"1842":1,"1853":1,"1855":2,"1863":1,"1867":1,"1878":1,"1881":1,"1882":1,"1885":1,"1915":1,"1922":1,"1937":1,"1955":1,"1969":3,"1999":3,"2003":1,"2005":1,"2007":1,"2009":1,"2010":1,"2012":3,"2018":1,"2023":1,"2024":3,"2032":1,"2036":1,"2042":1,"2052":2,"2055":1,"2059":2,"2109":2,"2112":1,"2125":5,"2126":1,"2127":2,"2156":1,"2172":1,"2174":1,"2179":1,"2183":1,"2189":1,"2192":1,"2212":1,"2218":1,"2220":1,"2228":1,"2231":5,"2238":1,"2242":1}}],["contrived",{"3":{"2112":1}}],["contributions",{"3":{"1323":2,"1671":1}}],["contribution",{"3":{"650":1,"1270":1,"1296":1,"1299":1,"1308":1,"1323":1,"1358":3,"1368":1,"1379":1,"1414":5,"1415":1,"1435":9,"2076":1}}],["contributing",{"3":{"135":1,"483":1,"563":3,"564":7,"591":3,"593":1,"594":1,"618":1,"621":1,"626":5,"724":1,"756":3,"766":3,"1091":2,"1299":2,"1322":3,"1338":1,"1349":1,"1356":1,"1379":1,"1394":1,"1415":1,"1435":2,"1458":2,"1485":3,"1491":4,"1495":1,"1512":1,"1525":6,"1576":7,"1581":2,"1585":1,"1590":3,"1599":1}}],["contributed",{"3":{"186":1,"1299":3,"1310":1,"1323":3,"1395":3,"1441":1,"1961":1,"2076":1}}],["contribute",{"3":{"166":1,"648":1,"650":1,"1178":1,"1285":2,"1299":1,"1322":1,"1323":1,"1338":2,"1358":1,"1379":1,"1395":4,"1414":1,"1415":1,"1435":1,"1442":2,"1466":1,"1486":1,"1626":1,"1883":1}}],["contributes",{"3":{"166":2,"171":1,"187":1,"552":1,"585":1,"649":2,"651":1,"725":1,"728":1,"846":1,"1212":1,"1254":1,"1259":2,"1271":3,"1275":1,"1285":5,"1296":1,"1299":1,"1310":1,"1311":6,"1313":1,"1314":1,"1315":1,"1316":1,"1322":4,"1323":9,"1331":1,"1338":1,"1358":15,"1368":1,"1378":2,"1394":6,"1395":4,"1401":1,"1403":1,"1409":1,"1411":1,"1413":1,"1414":9,"1415":3,"1435":9,"1437":2,"1441":1,"1442":1,"1466":1,"1760":1,"1880":1,"1881":1,"1934":1,"2043":1,"2076":1,"2162":1,"2231":1}}],["contributors",{"3":{"756":1,"1298":1,"1299":2,"1316":1,"1322":1,"1414":1,"1576":1,"1581":1,"2039":1,"2066":1}}],["contributor",{"2":{"2126":1},"3":{"0":1,"441":1,"599":1,"628":1,"665":2,"1116":1,"1119":1,"1298":2,"1299":4,"1322":4,"1338":1,"1414":2,"1415":1,"1466":4,"1471":2,"1473":1,"1474":1,"1476":1,"1570":1,"2040":1,"2047":1,"2048":1,"2059":1,"2126":1,"2196":1,"2237":1}}],["contravariance",{"3":{"1259":1,"1270":2,"1309":2}}],["contravariant",{"3":{"1259":2,"1261":1,"1270":4,"1309":1}}],["contradict",{"3":{"1394":1,"1570":1,"1584":1,"1764":1}}],["contradicts",{"3":{"1270":1}}],["contradictory",{"3":{"790":1,"1435":1}}],["contradicted",{"3":{"761":1,"972":1,"1435":1,"1488":1}}],["contradicting",{"3":{"364":1,"972":1}}],["contradiction",{"3":{"256":1,"828":1,"888":1,"1416":1,"1426":1,"1435":1}}],["contrasted",{"3":{"1394":1}}],["contrasting",{"3":{"1323":1,"1394":2,"1395":2}}],["contrasts",{"3":{"455":1,"967":1,"1247":1,"1259":1,"1426":1}}],["contrast",{"3":{"0":1,"24":1,"93":1,"473":1,"617":1,"618":1,"619":1,"621":1,"622":1,"1237":3,"1259":1,"1270":1,"1285":2,"1299":2,"1308":5,"1311":1,"1322":2,"1323":9,"1338":1,"1349":4,"1358":14,"1368":3,"1373":1,"1379":1,"1394":4,"1395":15,"1401":1,"1414":4,"1415":1,"1416":2,"1433":1,"1435":7,"1525":2,"1557":2,"1576":3,"1585":1,"1590":1,"1604":1,"1643":1,"1644":2,"1738":1,"2074":1,"2078":1,"2201":1}}],["contracted",{"3":{"1323":1}}],["contracttype",{"3":{"1247":1}}],["contractnames",{"3":{"1247":2}}],["contractually",{"3":{"1323":1,"1416":1,"1435":1}}],["contractual",{"3":{"1237":1,"1337":1,"1358":1,"1379":1,"1566":1,"1928":1}}],["contractimplementationtests",{"3":{"1199":2,"1207":4,"1435":17,"1488":3}}],["contractimplementationtestsbase",{"2":{"1662":1},"3":{"1199":3,"1207":2,"1311":3,"1435":10,"1662":1}}],["contract",{"0":{"451":1,"1266":1,"1270":1,"1313":1,"1418":1,"1435":1,"1545":1,"1639":1,"1915":1,"1927":1,"1947":1,"1987":1,"2043":1,"2044":1,"2056":1,"2116":1,"2130":1,"2165":1,"2175":1},"1":{"327":1,"328":1,"329":1,"330":1,"331":1,"332":1,"333":1,"334":1,"335":1,"336":1,"337":1,"542":1,"543":1,"544":1,"545":1,"546":1,"547":1,"548":1,"549":1,"550":1,"551":1,"552":1,"561":1,"562":1,"563":1,"564":1,"565":1,"566":1,"567":1,"568":1,"580":1,"581":1,"582":1,"583":1,"584":1,"585":1,"586":1,"587":1,"615":1,"616":1,"617":1,"618":1,"619":1,"620":1,"621":1,"622":1,"671":1,"672":1,"673":1,"674":1,"675":1,"676":1,"677":1,"678":1,"722":1,"723":1,"724":1,"725":1,"726":1,"727":1,"728":1,"729":1,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1,"878":1,"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"894":1,"895":1,"896":1,"897":1,"898":1,"899":1,"900":1,"901":1,"985":1,"986":1,"987":1,"988":1,"989":1,"990":1,"991":1,"992":1,"1165":1,"1166":1,"1167":1,"1168":1,"1169":1,"1170":1,"1171":1,"1310":1,"1985":1,"1986":1,"1987":1,"1988":1,"1989":1,"1990":1},"2":{"633":1,"636":1,"1266":1,"1369":1},"3":{"0":57,"24":5,"26":1,"37":1,"53":3,"54":3,"67":1,"76":1,"79":1,"81":6,"91":1,"107":1,"109":1,"110":1,"113":1,"122":1,"130":1,"135":3,"136":6,"143":1,"160":1,"178":1,"182":1,"193":1,"201":2,"202":3,"226":1,"230":1,"247":1,"255":4,"256":2,"265":1,"293":2,"296":1,"327":1,"328":1,"329":2,"330":1,"331":3,"332":2,"333":3,"335":1,"342":1,"376":1,"381":1,"384":1,"388":1,"411":1,"413":5,"415":1,"416":1,"420":2,"440":1,"443":1,"448":5,"449":1,"450":4,"452":1,"454":1,"469":1,"470":2,"473":1,"477":1,"542":1,"544":3,"545":1,"548":3,"549":4,"550":1,"551":1,"561":1,"564":7,"566":1,"568":1,"571":2,"572":22,"573":1,"574":3,"577":2,"579":2,"580":1,"582":1,"583":2,"584":2,"586":1,"595":1,"608":1,"615":1,"617":1,"618":1,"619":2,"620":1,"624":1,"626":1,"629":1,"631":3,"633":6,"634":1,"635":1,"636":3,"640":2,"648":2,"649":3,"650":2,"651":1,"653":1,"657":2,"658":1,"659":1,"671":1,"674":3,"675":1,"678":3,"682":3,"683":1,"684":1,"686":1,"699":1,"701":1,"707":2,"722":1,"725":4,"726":3,"729":1,"733":1,"735":1,"739":1,"741":3,"742":2,"743":2,"744":1,"745":3,"751":2,"756":2,"759":1,"761":1,"766":1,"776":1,"778":1,"780":1,"781":1,"782":3,"783":1,"784":1,"789":1,"790":2,"792":1,"793":1,"794":1,"795":1,"798":1,"800":1,"817":1,"823":1,"827":3,"834":1,"845":1,"846":4,"847":1,"849":1,"853":1,"854":1,"855":1,"856":1,"858":1,"862":1,"878":1,"879":1,"880":3,"881":3,"884":1,"885":1,"891":3,"892":1,"893":2,"894":1,"896":1,"897":5,"901":1,"904":1,"905":5,"907":3,"914":1,"924":1,"926":1,"931":1,"932":2,"935":1,"939":1,"963":1,"964":2,"965":1,"966":1,"973":1,"980":2,"984":1,"985":1,"987":2,"988":2,"990":2,"992":1,"996":4,"997":1,"998":2,"999":1,"1003":1,"1008":1,"1012":6,"1016":1,"1017":1,"1018":10,"1019":2,"1020":2,"1022":2,"1030":3,"1042":2,"1044":1,"1050":3,"1051":1,"1053":2,"1058":1,"1059":2,"1060":2,"1063":1,"1077":2,"1079":1,"1089":1,"1090":2,"1091":3,"1092":1,"1093":1,"1095":1,"1096":1,"1120":1,"1124":1,"1126":1,"1132":1,"1133":1,"1134":2,"1135":1,"1145":1,"1150":1,"1152":1,"1154":1,"1155":1,"1165":1,"1167":2,"1168":1,"1169":1,"1170":1,"1171":3,"1174":1,"1175":1,"1177":1,"1192":6,"1202":2,"1203":2,"1207":18,"1212":16,"1216":1,"1218":1,"1222":1,"1225":1,"1226":1,"1228":1,"1229":20,"1231":2,"1233":2,"1235":2,"1236":1,"1237":41,"1238":1,"1241":8,"1242":7,"1243":2,"1244":3,"1245":2,"1247":51,"1249":6,"1252":2,"1254":1,"1258":2,"1259":38,"1260":1,"1266":1,"1269":2,"1270":56,"1271":13,"1272":1,"1274":1,"1275":1,"1276":2,"1278":3,"1284":1,"1285":77,"1289":1,"1290":2,"1292":1,"1294":1,"1299":109,"1300":7,"1303":2,"1304":1,"1306":1,"1308":50,"1309":4,"1310":110,"1311":20,"1312":2,"1313":1,"1315":1,"1316":3,"1317":3,"1321":1,"1322":83,"1323":169,"1325":1,"1331":1,"1338":31,"1346":2,"1347":2,"1348":1,"1349":94,"1352":2,"1358":234,"1361":1,"1365":7,"1368":11,"1369":4,"1370":1,"1373":1,"1374":2,"1375":1,"1377":3,"1379":73,"1381":5,"1388":1,"1391":1,"1392":1,"1394":116,"1395":203,"1398":1,"1400":2,"1401":56,"1402":1,"1403":1,"1404":1,"1408":2,"1414":65,"1415":13,"1416":139,"1417":3,"1418":1,"1419":1,"1424":1,"1426":12,"1427":1,"1428":1,"1430":10,"1432":1,"1434":4,"1435":294,"1436":2,"1437":29,"1440":1,"1448":2,"1449":1,"1450":1,"1454":10,"1458":2,"1460":3,"1462":1,"1466":1,"1486":3,"1487":11,"1488":2,"1491":4,"1492":1,"1493":1,"1495":11,"1496":2,"1498":1,"1499":1,"1502":1,"1512":1,"1523":4,"1524":1,"1525":15,"1529":2,"1530":6,"1531":2,"1533":1,"1534":3,"1541":1,"1543":1,"1544":3,"1545":2,"1546":2,"1550":2,"1576":15,"1581":1,"1584":1,"1585":3,"1590":8,"1595":1,"1598":1,"1599":4,"1631":1,"1637":1,"1638":1,"1639":1,"1640":1,"1650":1,"1653":3,"1656":1,"1660":1,"1661":2,"1662":1,"1663":2,"1664":2,"1665":1,"1667":1,"1668":2,"1669":1,"1670":3,"1674":1,"1685":1,"1688":1,"1689":1,"1698":1,"1700":1,"1703":1,"1710":1,"1719":1,"1726":1,"1728":1,"1733":1,"1748":1,"1764":1,"1765":1,"1768":3,"1778":1,"1795":1,"1804":2,"1805":1,"1809":2,"1814":3,"1816":1,"1828":2,"1833":1,"1839":2,"1870":2,"1874":1,"1879":1,"1882":1,"1884":1,"1886":2,"1887":2,"1888":5,"1889":1,"1890":1,"1891":1,"1905":2,"1907":2,"1908":1,"1910":2,"1914":1,"1915":1,"1919":1,"1921":1,"1922":1,"1923":1,"1924":2,"1925":2,"1928":7,"1929":5,"1942":1,"1943":2,"1946":2,"1948":2,"1949":3,"1952":1,"1954":1,"1981":1,"1985":2,"1986":1,"1987":2,"1989":3,"1990":3,"1999":2,"2011":1,"2013":1,"2014":1,"2016":1,"2027":1,"2043":6,"2047":1,"2048":1,"2054":4,"2056":3,"2062":1,"2070":1,"2073":1,"2076":1,"2079":1,"2084":1,"2088":1,"2097":1,"2100":1,"2106":2,"2111":1,"2113":1,"2116":2,"2117":2,"2121":2,"2122":6,"2125":4,"2127":2,"2128":1,"2130":4,"2131":3,"2138":1,"2165":2,"2166":1,"2182":1,"2183":1,"2184":1,"2188":1,"2191":1,"2192":1,"2198":1,"2201":1,"2202":1,"2207":1,"2219":1,"2229":1,"2231":20}}],["contracts",{"0":{"1269":1,"1284":1,"1348":1,"2016":1},"1":{"50":1,"51":1,"52":1,"53":1,"54":1,"55":1,"1311":1,"1339":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":1,"1346":1,"1347":1,"1348":1,"1349":1,"1369":1,"1370":1,"1371":1,"1372":1,"1373":1,"1374":1,"1375":1,"1376":1,"1377":1,"1378":1,"1379":1,"1416":1},"2":{"53":1,"95":1,"1270":1,"1369":1,"1377":1,"1403":1,"1499":1,"1648":1,"1702":1,"1791":1,"1792":1,"2016":1,"2027":1},"3":{"0":9,"50":1,"51":1,"53":2,"54":1,"59":1,"80":2,"81":3,"82":1,"95":2,"96":1,"109":6,"136":4,"139":3,"142":1,"150":1,"339":1,"411":1,"413":2,"414":1,"434":1,"493":1,"571":1,"583":2,"587":1,"614":1,"617":1,"649":2,"657":2,"782":3,"846":1,"849":1,"914":1,"953":2,"955":2,"1006":2,"1024":2,"1026":4,"1027":1,"1059":2,"1067":1,"1074":1,"1091":3,"1192":11,"1199":11,"1202":12,"1203":28,"1206":4,"1207":102,"1208":8,"1212":5,"1227":1,"1229":2,"1230":1,"1247":2,"1248":1,"1259":4,"1260":3,"1261":2,"1269":2,"1270":20,"1271":5,"1272":1,"1284":1,"1285":5,"1286":3,"1290":2,"1292":2,"1299":3,"1302":1,"1303":1,"1306":2,"1308":33,"1310":8,"1311":33,"1319":1,"1321":2,"1322":4,"1323":5,"1338":1,"1339":2,"1340":1,"1346":3,"1349":22,"1351":1,"1352":1,"1354":1,"1358":22,"1368":3,"1369":3,"1377":6,"1379":23,"1394":11,"1395":51,"1399":1,"1401":9,"1403":1,"1408":1,"1412":4,"1414":19,"1415":3,"1416":18,"1417":1,"1423":1,"1426":7,"1430":11,"1435":188,"1436":2,"1437":4,"1440":1,"1445":1,"1449":1,"1454":5,"1475":1,"1486":1,"1487":1,"1488":6,"1491":2,"1492":1,"1495":13,"1496":1,"1497":1,"1499":3,"1523":1,"1525":4,"1530":1,"1543":3,"1545":3,"1554":1,"1575":2,"1576":7,"1584":2,"1585":1,"1590":3,"1598":1,"1648":1,"1655":5,"1658":1,"1659":2,"1662":2,"1664":1,"1668":1,"1670":1,"1684":2,"1685":1,"1686":1,"1702":1,"1768":1,"1771":1,"1782":1,"1789":3,"1791":1,"1792":1,"1887":1,"1928":1,"1929":1,"1942":1,"1952":1,"1955":1,"1958":1,"1962":1,"2016":2,"2027":1,"2029":1,"2040":1,"2043":8,"2054":1,"2055":1,"2059":1,"2066":1,"2110":1,"2114":1,"2115":2,"2116":1,"2117":3,"2121":1,"2207":1,"2213":1,"2220":1,"2226":3,"2231":3}}],["controlling",{"3":{"1299":4,"1358":1}}],["controllable",{"3":{"1259":1,"1285":1}}],["controlled",{"3":{"439":1,"1107":1,"1212":1,"1247":3,"1285":2,"1310":2,"1323":1,"1358":5,"1394":1,"1401":2,"1416":3,"1435":3,"1437":1,"1442":1,"1521":1,"1552":1,"1559":1,"1568":1,"1572":1,"1576":1,"1586":1,"1636":1,"1798":1,"1896":1,"2033":1,"2123":2}}],["controllerparameterdescriptor",{"3":{"1310":1}}],["controllerbase",{"3":{"1310":1,"1435":17}}],["controllerfeature",{"3":{"1310":1}}],["controllerconventiontests",{"3":{"1199":1,"1207":2,"1435":5,"1488":1}}],["controllerconventiontestsbase",{"2":{"1653":1},"3":{"1199":2,"1207":2,"1435":5,"1653":1}}],["controllermocks",{"3":{"1199":7,"1207":6}}],["controller",{"0":{"1370":1,"1371":1},"2":{"450":1,"455":1,"727":1},"3":{"0":7,"42":1,"121":1,"124":1,"160":1,"173":1,"186":1,"189":1,"258":1,"299":1,"300":1,"301":1,"302":1,"318":5,"320":3,"321":1,"325":3,"326":1,"329":2,"330":4,"331":1,"332":1,"341":1,"346":1,"350":1,"351":1,"352":2,"391":2,"448":1,"450":1,"454":1,"455":3,"549":1,"571":2,"572":1,"583":1,"585":2,"690":2,"715":1,"723":2,"725":8,"726":1,"727":1,"729":1,"739":2,"740":1,"741":4,"743":5,"744":2,"751":1,"790":1,"838":1,"854":2,"880":1,"900":1,"920":2,"921":2,"922":3,"924":1,"938":1,"1018":1,"1049":1,"1059":2,"1060":1,"1116":2,"1117":1,"1118":2,"1192":1,"1202":1,"1203":1,"1212":1,"1217":1,"1218":1,"1224":2,"1227":1,"1229":4,"1237":1,"1238":1,"1239":1,"1245":2,"1247":6,"1265":1,"1270":6,"1271":2,"1275":1,"1285":1,"1288":1,"1291":1,"1296":2,"1299":24,"1300":1,"1303":1,"1305":1,"1308":18,"1310":108,"1322":4,"1323":1,"1338":1,"1346":2,"1349":12,"1351":1,"1352":1,"1356":1,"1358":121,"1368":1,"1369":1,"1370":5,"1371":2,"1372":3,"1373":3,"1374":2,"1375":3,"1378":1,"1379":116,"1387":1,"1388":1,"1394":8,"1395":42,"1401":19,"1406":2,"1407":1,"1408":2,"1413":1,"1414":47,"1430":1,"1435":86,"1436":3,"1437":11,"1442":1,"1446":1,"1486":3,"1487":2,"1495":5,"1525":3,"1533":1,"1575":1,"1576":2,"1590":2,"1626":1,"1630":3,"1631":3,"1638":2,"1639":2,"1646":2,"1649":3,"1650":4,"1653":1,"1659":1,"1665":1,"1666":2,"1670":1,"1697":1,"1699":1,"1701":3,"1702":1,"1720":1,"1723":1,"1726":4,"1729":1,"1746":2,"1747":1,"1748":3,"1764":2,"1770":1,"1802":1,"1804":2,"1806":1,"1813":2,"1814":1,"1907":1,"1926":3,"1927":1,"1933":1,"1935":1,"1936":1,"1959":1,"1962":1,"1964":1,"1974":1,"1977":2,"1984":1,"1985":1,"1986":1,"1987":2,"1989":1,"1990":1,"1993":2,"1994":2,"1995":2,"1996":1,"1999":1,"2054":1,"2067":1,"2068":1,"2086":1,"2103":1,"2123":2,"2124":1,"2130":1,"2136":2,"2137":1,"2166":1,"2201":1,"2202":1,"2231":1}}],["controllersinheritapicontrollerbase",{"3":{"1435":1}}],["controllersaresealed",{"3":{"1435":1}}],["controllersdonotdependonentityframeworkcore",{"3":{"1435":1}}],["controllersdonotdependoninfrastructure",{"3":{"1435":1}}],["controllersexemptfromapicontrollerbase",{"3":{"1435":2}}],["controllers",{"1":{"327":1,"328":1,"329":1,"330":1,"331":1,"332":1,"333":1,"334":1,"335":1,"336":1,"337":1,"1985":1,"1986":1,"1987":1,"1988":1,"1989":1,"1990":1},"2":{"751":1,"1207":1,"1379":7,"1435":1},"3":{"0":5,"42":1,"59":1,"109":1,"132":1,"136":3,"137":1,"155":1,"160":3,"161":2,"186":1,"187":1,"243":2,"300":2,"306":1,"318":7,"320":1,"324":1,"325":5,"326":1,"327":1,"330":2,"334":1,"337":1,"341":2,"384":1,"391":1,"419":1,"423":1,"426":1,"428":1,"447":1,"448":5,"473":1,"545":3,"550":1,"585":2,"674":1,"690":2,"692":1,"725":4,"733":1,"740":1,"741":3,"743":9,"749":3,"751":4,"773":1,"775":1,"789":1,"790":1,"793":1,"826":2,"837":1,"839":1,"841":1,"854":1,"897":2,"900":1,"905":1,"906":1,"909":1,"921":1,"922":1,"923":2,"1004":3,"1018":1,"1026":2,"1028":1,"1049":1,"1059":4,"1060":1,"1099":1,"1103":1,"1116":1,"1192":1,"1202":1,"1203":74,"1207":374,"1212":3,"1213":2,"1224":1,"1227":1,"1229":5,"1236":1,"1239":2,"1244":1,"1245":1,"1247":11,"1270":6,"1285":1,"1289":1,"1293":1,"1296":1,"1299":59,"1300":1,"1301":1,"1302":1,"1303":1,"1305":1,"1307":3,"1308":28,"1310":125,"1322":14,"1338":2,"1346":2,"1347":1,"1349":11,"1350":1,"1358":173,"1366":1,"1368":1,"1369":2,"1370":10,"1371":8,"1372":4,"1373":3,"1374":1,"1375":1,"1379":129,"1385":1,"1389":1,"1394":10,"1395":52,"1401":31,"1402":1,"1403":1,"1405":1,"1407":1,"1409":1,"1414":55,"1415":1,"1430":3,"1435":66,"1436":2,"1437":21,"1446":1,"1466":2,"1486":2,"1488":2,"1495":9,"1525":3,"1534":3,"1539":1,"1576":3,"1581":2,"1585":1,"1590":5,"1596":1,"1598":2,"1599":1,"1634":1,"1655":1,"1665":4,"1672":1,"1684":1,"1685":1,"1752":1,"1764":4,"1778":1,"1811":1,"1812":2,"1814":2,"1824":1,"1873":3,"1897":1,"1921":1,"1927":1,"1928":2,"1931":2,"1937":2,"1940":1,"1960":1,"1984":1,"1985":2,"1990":1,"1994":11,"1996":1,"1999":3,"2000":1,"2022":1,"2023":1,"2109":1,"2130":2,"2131":1,"2136":1,"2207":1,"2226":1,"2231":1}}],["controls",{"0":{"1999":1},"1":{"1997":1,"1998":1,"1999":1,"2000":1,"2001":1},"2":{"682":1,"2119":1},"3":{"0":1,"227":1,"273":1,"369":1,"370":2,"373":1,"413":1,"592":1,"682":1,"685":1,"825":1,"830":1,"1126":1,"1229":1,"1285":1,"1299":1,"1310":4,"1322":2,"1323":3,"1338":1,"1358":4,"1388":1,"1394":4,"1395":1,"1401":2,"1414":2,"1416":3,"1435":7,"1437":1,"1466":2,"1476":1,"1525":1,"1533":2,"1556":1,"1571":1,"1576":1,"1606":1,"1625":1,"1630":3,"1636":1,"1676":1,"1713":1,"1740":1,"1749":1,"1767":1,"1772":1,"1997":2,"1998":1,"1999":1,"2000":1,"2001":2,"2042":1,"2082":1,"2108":1,"2119":1,"2195":1,"2207":1}}],["control",{"0":{"1803":1},"1":{"106":1,"107":1,"108":1,"109":1,"110":1,"111":1,"112":1,"113":1,"1802":1,"1803":1,"1804":1,"1805":1,"1806":1},"3":{"0":4,"106":1,"174":1,"175":1,"176":1,"177":3,"178":1,"179":1,"185":1,"212":1,"217":1,"221":1,"293":1,"296":1,"343":1,"358":2,"362":1,"376":1,"403":1,"427":2,"443":1,"595":1,"627":1,"699":1,"734":1,"743":1,"745":1,"756":1,"758":2,"765":1,"767":2,"768":1,"770":1,"776":2,"789":1,"876":2,"947":1,"1019":1,"1067":1,"1116":3,"1185":1,"1186":1,"1187":1,"1218":1,"1220":1,"1226":1,"1229":4,"1237":6,"1247":1,"1259":2,"1270":2,"1271":1,"1275":1,"1283":1,"1284":1,"1285":2,"1299":5,"1300":1,"1307":1,"1310":3,"1321":1,"1322":5,"1323":12,"1325":1,"1338":9,"1346":2,"1349":1,"1358":11,"1368":1,"1383":1,"1388":1,"1394":15,"1395":8,"1401":1,"1414":3,"1415":4,"1416":13,"1422":1,"1435":38,"1439":1,"1442":1,"1443":1,"1454":2,"1461":1,"1466":5,"1474":1,"1487":1,"1489":1,"1495":1,"1538":1,"1547":1,"1549":1,"1554":1,"1567":1,"1590":1,"1595":1,"1606":1,"1628":1,"1630":2,"1640":1,"1643":1,"1652":2,"1661":1,"1663":1,"1664":1,"1671":1,"1688":1,"1697":1,"1713":1,"1740":1,"1785":1,"1801":1,"1802":2,"1804":1,"1806":1,"1815":1,"1869":1,"1902":1,"1968":1,"1972":1,"1998":5,"1999":1,"2000":4,"2001":3,"2004":3,"2012":1,"2072":2,"2083":1,"2125":2,"2139":1,"2140":3,"2142":1,"2143":1,"2144":1,"2148":1,"2154":1,"2165":1,"2185":1,"2202":1,"2205":1,"2227":1}}],["contested",{"3":{"714":1,"1435":3}}],["contending",{"3":{"1947":1}}],["contends",{"3":{"1414":1}}],["contenders",{"3":{"1247":1}}],["contended",{"3":{"857":1,"1435":1,"1487":2}}],["contend",{"3":{"519":1,"708":1,"1358":1,"1401":2,"1629":1,"1637":1}}],["contentful",{"3":{"1394":1}}],["contentfilter",{"2":{"1365":1},"3":{"1018":1,"1365":1,"1368":1}}],["contentpage",{"3":{"1323":2,"1416":5}}],["contentpolicyregistrationtests",{"3":{"1199":1,"1207":2,"1576":1}}],["contentpolicyinjectionmode",{"3":{"1091":2,"1199":5,"1203":1,"1207":2,"1423":2,"1426":8}}],["contentpolicy",{"3":{"1021":1,"1089":1,"1091":1,"1093":1,"1365":1,"1423":1,"1426":3,"1525":2,"1576":1}}],["contentpolicyguardrailtests",{"2":{"1434":1},"3":{"1093":1,"1199":1,"1207":2,"1426":1,"1434":2,"1436":1,"1576":1}}],["contentpolicyguardrail",{"2":{"1021":1,"1089":1,"1094":1,"1095":1,"1365":1,"1367":1,"1426":1,"1585":1},"3":{"1018":2,"1021":1,"1089":1,"1091":6,"1093":2,"1094":1,"1095":1,"1198":1,"1199":6,"1203":1,"1207":2,"1365":1,"1367":1,"1423":3,"1426":24,"1495":1,"1525":2,"1576":2,"1584":2,"1585":1}}],["contentpolicysettings",{"2":{"676":1,"677":1,"1423":1,"1426":1},"3":{"674":1,"676":2,"677":1,"1091":5,"1093":1,"1198":1,"1199":5,"1203":1,"1207":2,"1423":3,"1426":17,"1525":1,"1576":1,"1584":1}}],["contentresult",{"3":{"1310":1}}],["contenttypemaxlength",{"2":{"1368":1},"3":{"1349":2,"1368":1}}],["contenttype=",{"3":{"1323":1}}],["contenttype",{"3":{"1285":2,"1322":6,"1323":2,"1349":3,"1368":2,"1414":1,"1416":6}}],["contentdisposition",{"3":{"1285":1,"1322":1}}],["contentsimilaritydto",{"3":{"1199":4,"1203":1,"1207":3,"1346":2,"1349":6,"1358":2,"1379":1}}],["contents",{"2":{"626":1},"3":{"626":1,"715":1,"766":2,"874":1,"1003":1,"1019":1,"1192":3,"1237":2,"1279":1,"1299":1,"1323":4,"1358":1,"1414":1,"1435":1,"1453":2,"1458":1,"1470":1,"1472":1,"1474":2,"1495":1,"1767":1}}],["contentsecuritypolicyreportonly",{"3":{"1338":1}}],["contentsecuritypolicy",{"2":{"214":1,"216":1,"1576":1,"2148":1},"3":{"214":1,"216":1,"1338":3,"1576":2,"2148":1}}],["contention",{"3":{"558":1,"863":2,"996":1,"1150":1,"1153":1,"1278":1,"1285":2,"1358":2,"1401":2,"1435":1,"1455":1,"1544":1,"1629":1,"1634":1,"1637":7,"1687":1,"1908":1}}],["contentmanagement",{"2":{"184":1,"1962":1},"3":{"184":1,"186":1,"1116":1,"1347":2,"1349":4,"1376":1,"1379":7,"1630":2,"1962":1}}],["contenteditors",{"3":{"1358":2,"1626":1}}],["contenteditor",{"2":{"187":1,"386":1,"388":1,"1347":1,"1620":1,"1625":1,"1629":1,"1632":1,"1634":1,"1636":1,"1637":1,"1959":1},"3":{"0":1,"186":1,"187":1,"386":1,"388":1,"1116":1,"1299":1,"1306":1,"1347":2,"1349":4,"1358":5,"1372":2,"1376":1,"1379":6,"1394":1,"1395":2,"1404":1,"1408":1,"1414":9,"1495":2,"1525":1,"1620":4,"1625":1,"1626":2,"1629":4,"1630":6,"1631":1,"1632":1,"1634":2,"1636":2,"1637":5,"1639":2,"1921":1,"1959":2,"1962":1}}],["content",{"0":{"1358":1,"1423":1},"1":{"738":1,"739":1,"740":1,"741":1,"742":1,"743":1,"744":1,"745":1},"2":{"443":1,"1284":1},"3":{"0":7,"90":1,"103":2,"139":1,"142":1,"157":1,"173":1,"175":1,"179":1,"185":1,"213":3,"214":3,"219":1,"384":1,"440":2,"443":3,"454":1,"478":1,"497":1,"529":1,"572":1,"574":1,"575":1,"633":1,"651":1,"664":1,"688":1,"693":1,"720":1,"725":3,"738":1,"740":2,"741":3,"747":1,"749":2,"757":1,"764":1,"774":1,"809":1,"813":1,"832":1,"869":1,"909":1,"1010":1,"1011":1,"1012":1,"1016":1,"1089":3,"1091":3,"1093":3,"1095":2,"1114":1,"1115":1,"1116":6,"1118":2,"1186":2,"1192":1,"1212":1,"1237":2,"1259":1,"1270":1,"1282":1,"1284":5,"1285":28,"1299":3,"1300":1,"1305":1,"1308":9,"1309":1,"1310":19,"1322":12,"1323":28,"1335":3,"1338":9,"1346":1,"1347":1,"1349":19,"1356":1,"1358":38,"1365":1,"1368":1,"1375":2,"1379":14,"1389":1,"1392":1,"1393":1,"1394":26,"1395":9,"1398":1,"1401":17,"1410":2,"1414":28,"1415":11,"1416":6,"1417":2,"1422":2,"1423":4,"1426":17,"1431":2,"1435":35,"1436":1,"1437":4,"1442":2,"1454":2,"1457":1,"1466":1,"1486":1,"1487":1,"1488":1,"1495":5,"1525":2,"1530":1,"1531":1,"1552":3,"1558":2,"1562":3,"1571":1,"1576":2,"1585":1,"1599":1,"1606":1,"1629":2,"1630":4,"1631":1,"1636":2,"1637":4,"1638":2,"1639":3,"1640":2,"1643":1,"1645":1,"1701":1,"1704":1,"1712":2,"1730":1,"1740":1,"1748":1,"1763":1,"1764":1,"1780":1,"1824":1,"1870":1,"1908":1,"1921":1,"1959":1,"1960":1,"1963":1,"1964":1,"1999":3,"2010":3,"2054":1,"2074":2,"2124":3,"2125":5,"2127":2,"2146":2,"2147":2,"2148":3,"2149":1,"2152":1,"2174":1,"2178":1,"2179":1,"2231":3,"2239":1}}],["contextless",{"3":{"2073":1}}],["contextual",{"3":{"1394":1}}],["contexts",{"0":{"1513":1},"3":{"0":1,"166":1,"591":2,"593":1,"618":1,"703":1,"988":1,"1035":2,"1175":1,"1226":1,"1237":1,"1259":1,"1278":1,"1282":1,"1285":17,"1323":1,"1358":3,"1395":1,"1414":2,"1416":1,"1435":1,"1442":1,"1452":1,"1485":1,"1486":1,"1489":1,"1491":1,"1495":1,"1525":1,"1540":1,"1543":1,"1544":1,"1576":6,"1581":2,"1582":1,"1583":1,"1637":2,"1638":1,"1659":1,"1663":1,"1696":1,"1699":1,"1718":1,"1799":1,"1810":1,"1848":1,"2009":1,"2109":1,"2112":1,"2114":1,"2238":1}}],["context",{"0":{"4":1,"10":1,"16":1,"30":1,"38":1,"46":1,"52":1,"58":1,"67":1,"77":1,"85":1,"97":1,"108":1,"116":1,"131":1,"146":1,"156":1,"165":1,"174":1,"185":1,"194":1,"206":1,"213":1,"224":1,"234":1,"242":1,"264":1,"272":1,"280":1,"290":1,"299":1,"309":1,"317":1,"329":1,"340":1,"349":1,"358":1,"369":1,"379":1,"387":1,"396":1,"412":1,"419":1,"433":1,"439":1,"447":1,"458":1,"469":1,"481":1,"498":1,"506":1,"517":1,"527":1,"535":1,"544":1,"555":1,"563":1,"571":1,"582":1,"590":1,"598":1,"608":1,"617":1,"625":1,"632":1,"639":1,"648":1,"656":1,"664":1,"673":1,"681":1,"689":1,"697":1,"706":1,"714":1,"724":1,"732":1,"740":1,"748":1,"756":1,"765":1,"773":1,"781":1,"789":1,"798":1,"809":1,"818":1,"826":1,"837":1,"845":1,"853":1,"860":1,"868":1,"880":1,"888":1,"896":1,"904":1,"913":1,"921":1,"930":1,"938":1,"945":1,"953":1,"963":1,"971":1,"979":1,"987":1,"995":1,"1003":1,"1011":1,"1017":1,"1025":1,"1033":1,"1041":1,"1049":1,"1058":1,"1066":1,"1074":1,"1082":1,"1090":1,"1099":1,"1107":1,"1115":1,"1123":1,"1132":1,"1140":1,"1150":1,"1160":1,"1167":1,"1174":1,"1183":1,"1195":1,"1273":1,"1317":1,"1340":1,"1362":1,"1403":1,"1404":1,"1481":1,"1502":1,"1848":1},"2":{"135":1,"459":1,"740":1,"1175":1,"1233":1,"1299":1,"1463":1},"3":{"0":24,"1":1,"15":2,"26":2,"27":3,"45":3,"47":2,"62":1,"135":2,"145":1,"150":1,"160":1,"164":1,"165":1,"166":2,"167":1,"193":1,"200":1,"201":1,"202":5,"229":1,"233":1,"265":1,"279":1,"296":1,"298":1,"302":2,"303":4,"340":1,"346":1,"359":3,"365":2,"366":1,"375":1,"392":1,"393":1,"395":1,"397":1,"400":1,"423":1,"459":1,"468":1,"470":1,"477":1,"481":5,"482":2,"483":3,"485":1,"490":2,"526":1,"539":1,"545":1,"555":1,"564":1,"578":1,"582":1,"587":1,"591":1,"593":1,"647":1,"660":1,"669":1,"696":1,"697":1,"698":12,"699":2,"700":2,"702":7,"707":2,"715":1,"716":2,"720":1,"731":1,"740":1,"748":1,"761":2,"794":1,"797":1,"804":1,"806":1,"825":1,"826":1,"827":1,"833":1,"836":1,"875":1,"877":2,"889":3,"893":1,"898":1,"905":5,"906":1,"910":2,"920":1,"926":11,"931":1,"962":1,"967":3,"988":9,"990":2,"991":1,"1007":1,"1019":1,"1021":1,"1032":1,"1033":2,"1034":4,"1035":1,"1036":3,"1038":2,"1040":1,"1042":6,"1050":4,"1053":1,"1054":1,"1059":2,"1061":1,"1062":1,"1083":3,"1084":2,"1086":1,"1087":1,"1089":1,"1093":1,"1103":1,"1107":1,"1110":1,"1128":1,"1133":1,"1137":2,"1150":1,"1173":1,"1174":3,"1175":5,"1177":1,"1180":1,"1192":2,"1201":1,"1202":4,"1203":11,"1207":32,"1212":4,"1219":1,"1229":5,"1233":1,"1237":4,"1247":1,"1249":1,"1252":2,"1253":3,"1254":1,"1256":1,"1257":2,"1258":1,"1259":47,"1270":33,"1271":1,"1273":9,"1274":7,"1275":2,"1276":4,"1277":1,"1278":10,"1280":2,"1282":1,"1283":1,"1285":245,"1295":2,"1296":1,"1299":44,"1308":17,"1309":2,"1310":32,"1311":8,"1312":1,"1315":1,"1316":1,"1317":4,"1318":1,"1322":35,"1323":16,"1325":1,"1332":1,"1335":1,"1338":31,"1339":1,"1340":1,"1342":1,"1344":2,"1349":14,"1358":58,"1362":4,"1368":11,"1369":1,"1377":1,"1379":11,"1394":3,"1395":24,"1396":1,"1401":5,"1402":1,"1403":2,"1404":1,"1405":2,"1409":1,"1411":1,"1414":30,"1416":7,"1426":1,"1432":1,"1435":33,"1437":6,"1440":1,"1452":2,"1454":1,"1455":1,"1456":1,"1463":2,"1470":1,"1487":1,"1488":1,"1495":2,"1509":1,"1525":2,"1533":2,"1540":1,"1542":1,"1555":1,"1560":1,"1570":1,"1574":1,"1576":6,"1580":1,"1585":1,"1606":1,"1628":1,"1629":8,"1630":3,"1631":2,"1633":7,"1634":2,"1637":11,"1638":9,"1640":1,"1650":4,"1651":3,"1663":2,"1664":2,"1665":1,"1669":1,"1672":1,"1696":3,"1699":1,"1718":2,"1719":1,"1726":1,"1727":2,"1740":1,"1750":1,"1789":1,"1799":1,"1800":1,"1804":1,"1809":2,"1831":1,"1839":2,"1848":7,"1850":6,"1852":1,"1868":1,"1922":1,"1929":1,"1930":2,"1940":1,"2050":2,"2052":1,"2112":1,"2118":1,"2137":1,"2141":3,"2156":1,"2164":1,"2168":1,"2178":1,"2184":2,"2185":1,"2192":1,"2198":2,"2231":5,"2238":1}}],["contiguous",{"3":{"1311":2}}],["contiguously",{"3":{"0":1}}],["continuation",{"3":{"1299":1,"1311":6,"1416":1,"1435":1,"2185":1}}],["continuations",{"3":{"482":1,"1804":1}}],["continuing",{"3":{"1247":1,"1323":1,"1338":2,"1395":1,"1446":1}}],["continuity",{"0":{"1565":1},"1":{"1705":1,"1706":1,"1707":1,"1708":1,"1709":1},"2":{"1329":1,"1366":1},"3":{"399":1,"1216":1,"1233":1,"1237":1,"1247":2,"1258":1,"1259":15,"1263":1,"1270":7,"1285":18,"1299":3,"1300":1,"1303":1,"1306":1,"1308":10,"1310":10,"1311":5,"1322":15,"1323":21,"1329":2,"1333":1,"1337":1,"1338":25,"1349":2,"1351":1,"1356":1,"1358":17,"1364":1,"1366":1,"1368":2,"1379":7,"1394":24,"1395":33,"1401":5,"1414":15,"1415":2,"1416":20,"1426":1,"1434":1,"1435":11,"1440":2,"1449":1,"1454":4,"1457":1,"1460":1,"1466":2,"1468":1,"1473":1,"1474":1,"1482":1,"1498":1,"1525":1,"1534":1,"1574":1,"1576":1,"1580":1,"1581":1,"1582":1,"1590":1,"1599":1,"1601":1,"1673":1,"1705":1,"1795":1,"2028":1,"2161":1}}],["continuous",{"3":{"68":1,"1299":1,"1416":1,"1451":1,"1452":1,"1454":1,"1708":1}}],["continuously",{"3":{"0":1,"62":1,"390":1,"501":1,"1153":1,"1285":1,"1323":1,"1435":1,"1474":1,"1983":1,"2224":1}}],["continued",{"3":{"1416":1,"1454":1}}],["continueuseractivity",{"3":{"1415":2}}],["continue",{"2":{"889":1},"3":{"0":2,"375":1,"491":1,"583":1,"618":1,"624":1,"626":3,"633":1,"638":1,"640":1,"642":1,"757":1,"873":1,"876":2,"877":2,"889":1,"913":1,"914":1,"917":1,"1034":1,"1037":1,"1069":2,"1075":1,"1259":3,"1270":1,"1285":3,"1299":1,"1310":2,"1327":1,"1333":1,"1338":6,"1358":1,"1368":4,"1389":1,"1394":5,"1395":1,"1437":1,"1440":1,"1444":2,"1445":1,"1448":1,"1449":1,"1452":1,"1454":5,"1455":1,"1458":1,"1459":1,"1464":1,"1466":1,"1489":4,"1491":4,"1525":5,"1533":1,"1576":7,"1590":1,"1598":1,"1599":1,"1749":1,"1772":1,"2193":1}}],["continues",{"3":{"0":1,"47":1,"141":1,"516":1,"897":1,"1259":2,"1285":1,"1299":4,"1310":2,"1322":4,"1338":1,"1358":1,"1395":1,"1444":1}}],["dhl",{"3":{"1748":1,"1764":1}}],["d1cae3ed",{"3":{"1590":1}}],["d5297706",{"3":{"1495":1}}],["d5fd0e9",{"2":{"424":1},"3":{"424":1}}],["d4",{"3":{"1435":1}}],["dwarf",{"3":{"1466":1}}],["dwarfs",{"3":{"1291":1}}],["dwelling",{"3":{"1435":1}}],["dp",{"3":{"2223":1}}],["dpi",{"3":{"1415":1}}],["dpapi",{"3":{"1415":1,"1416":2}}],["dcp",{"3":{"1310":3,"1489":1}}],["d841f798",{"3":{"1495":1}}],["d8",{"3":{"1285":1,"2125":1}}],["dst",{"3":{"1299":1,"1349":2,"1355":1,"1358":3,"1368":1,"1395":3}}],["dsl",{"3":{"1241":1,"1659":1,"1669":1,"1988":2}}],["dsar",{"1":{"722":1,"723":1,"724":1,"725":1,"726":1,"727":1,"728":1,"729":1},"3":{"0":1,"696":1,"722":1,"727":1,"1212":2,"1299":1,"1310":2,"1437":2,"1574":1,"1576":2,"1581":1,"1671":1,"2231":1}}],["dtend",{"3":{"1299":1}}],["dtstart",{"3":{"1299":1}}],["dtstamp",{"2":{"1355":1},"3":{"1299":4,"1355":1}}],["dt",{"3":{"1247":2}}],["dtu",{"3":{"767":1,"1445":1,"1456":1,"1466":1,"1472":1,"1477":1,"1482":1,"1483":1}}],["dtoorlookupsuffix",{"3":{"1435":1}}],["dtoprojector",{"3":{"1247":1,"1270":1}}],["dtomapper",{"2":{"331":1},"3":{"331":1,"1193":1,"1244":1,"1247":2,"1270":1,"1308":1,"1310":1,"1358":16,"1395":1,"1401":4,"1496":1,"1650":1}}],["dtotoentitypropertymap",{"2":{"331":1,"332":1,"333":1,"335":1,"1244":1,"1987":1,"1989":1},"3":{"331":1,"332":1,"333":1,"335":1,"1244":1,"1247":14,"1358":7,"1987":1,"1989":1}}],["dtosandrequests",{"3":{"1435":1}}],["dtos",{"2":{"1207":1,"1358":2,"1395":1,"1401":1},"3":{"4":1,"331":1,"340":1,"341":2,"549":1,"558":1,"657":2,"674":1,"799":1,"1026":1,"1049":1,"1168":1,"1192":2,"1202":2,"1203":30,"1207":98,"1244":1,"1245":1,"1247":3,"1270":5,"1271":1,"1285":2,"1288":1,"1299":8,"1308":13,"1310":15,"1311":2,"1322":1,"1323":2,"1340":2,"1346":1,"1349":35,"1352":4,"1358":67,"1373":2,"1379":3,"1394":4,"1395":18,"1400":1,"1401":13,"1403":2,"1414":6,"1430":1,"1435":11,"1437":3,"1454":1,"1488":2,"1495":2,"1525":2,"1531":1,"1541":1,"1545":1,"1629":2,"1630":1,"1638":1,"1649":1,"1670":1,"1856":1,"1870":1,"1928":1,"1987":1,"2070":1,"2077":1,"2141":1,"2226":1,"2231":1}}],["dto",{"1":{"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"1310":1,"1950":1,"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1956":1,"1957":1,"1958":1},"2":{"1954":1},"3":{"0":8,"2":1,"3":3,"5":1,"7":1,"42":1,"330":1,"331":3,"332":1,"333":2,"334":1,"335":1,"341":2,"342":1,"343":1,"360":1,"473":1,"543":1,"545":1,"549":1,"657":1,"690":1,"725":1,"741":1,"742":1,"799":1,"881":5,"882":1,"883":3,"922":1,"923":1,"926":9,"927":1,"1026":1,"1028":1,"1049":1,"1050":3,"1059":2,"1060":1,"1133":1,"1168":4,"1170":1,"1192":1,"1202":2,"1203":2,"1212":3,"1213":1,"1227":1,"1229":2,"1237":1,"1238":1,"1241":4,"1242":2,"1243":1,"1244":4,"1247":25,"1260":3,"1262":2,"1263":1,"1265":5,"1266":1,"1269":1,"1270":35,"1271":6,"1285":7,"1296":1,"1299":20,"1303":1,"1308":20,"1309":1,"1310":50,"1311":2,"1315":1,"1322":12,"1323":27,"1339":1,"1343":3,"1346":1,"1347":1,"1349":91,"1351":1,"1352":1,"1353":2,"1355":1,"1358":168,"1369":1,"1374":1,"1379":4,"1381":3,"1383":2,"1388":1,"1394":68,"1395":49,"1397":2,"1400":2,"1401":33,"1408":1,"1409":1,"1414":36,"1415":3,"1435":9,"1436":4,"1437":6,"1486":2,"1495":6,"1525":3,"1541":1,"1576":3,"1590":1,"1638":1,"1650":1,"1661":2,"1665":1,"1682":1,"1683":1,"1685":1,"1701":2,"1719":1,"1726":4,"1727":1,"1748":1,"1749":1,"1778":1,"1809":2,"1829":1,"1867":1,"1873":2,"1882":2,"1932":2,"1949":1,"1950":2,"1951":1,"1952":6,"1953":1,"1954":3,"1955":2,"1956":2,"1958":1,"1987":2,"1989":2,"1990":4,"2051":1,"2079":1,"2094":1,"2207":2,"2229":1,"2231":1}}],["dml",{"0":{"719":1},"3":{"719":1,"1435":1,"1466":1,"1495":1}}],["dd0b6a9",{"3":{"1495":1}}],["ddl",{"3":{"1466":1,"1476":1,"1718":1,"1726":1}}],["dd",{"3":{"305":1,"1018":1,"1299":2,"1349":2,"1358":1,"1368":2,"1435":6,"1491":1,"1608":1,"1709":1,"1742":1,"2137":1}}],["ddd",{"0":{"1637":1},"3":{"0":1,"469":1,"781":1,"1049":1,"1117":1,"1201":1,"1202":1,"1203":1,"1211":1,"1212":1,"1230":1,"1231":2,"1237":10,"1247":3,"1259":1,"1270":4,"1271":1,"1285":4,"1299":5,"1308":5,"1309":1,"1310":2,"1322":3,"1339":1,"1349":9,"1358":27,"1368":6,"1379":3,"1395":9,"1401":13,"1414":5,"1430":1,"1435":21,"1488":3,"1505":1,"1522":1,"1535":1,"1576":2,"1580":1,"1581":1,"1582":1,"1587":1,"1589":1,"1636":1,"1637":1,"1638":2,"1639":1,"1645":1,"1658":1,"1661":2,"1691":1,"1762":1,"1779":1,"1780":1,"1809":1,"1810":2,"2047":1,"2089":1,"2113":1,"2114":1,"2203":1,"2212":1,"2215":1,"2220":1,"2226":1,"2242":1,"2244":1}}],["d32",{"3":{"91":1}}],["dlls",{"3":{"1435":1}}],["dll",{"2":{"1444":6},"3":{"0":1,"869":1,"939":1,"1435":1,"1444":7}}],["d",{"3":{"0":1,"67":1,"91":1,"373":1,"585":1,"1126":1,"1237":1,"1271":2,"1299":3,"1306":1,"1322":1,"1323":4,"1349":1,"1358":30,"1374":1,"1376":1,"1379":1,"1395":1,"1416":1,"1426":1,"1435":9,"1453":1,"1525":1,"1565":1,"1576":1,"1590":1,"1610":1,"1631":1,"1714":1,"1764":1,"1934":1}}],["dynamiccode",{"3":{"1435":2}}],["dynamiccomponent",{"2":{"1323":1},"3":{"649":1,"1323":1}}],["dynamicerrors",{"3":{"1199":2,"1207":1,"1435":7}}],["dynamicqueryconfig",{"2":{"1247":1,"1988":1},"3":{"1199":9,"1203":1,"1207":2,"1241":4,"1247":19,"1495":2,"1988":1}}],["dynamically",{"3":{"329":1,"545":1,"1018":1,"1247":2,"1323":2,"1435":1,"1440":1,"1454":1,"1457":1,"1485":1,"1488":1,"1749":1,"1772":1,"1989":1}}],["dynamic",{"0":{"1241":1},"1":{"327":1,"328":1,"329":1,"330":1,"331":1,"332":1,"333":1,"334":1,"335":1,"336":1,"337":1,"1985":1,"1986":1,"1987":1,"1988":1,"1989":1,"1990":1},"3":{"0":1,"95":1,"215":1,"235":1,"327":1,"329":1,"330":2,"331":2,"332":1,"335":1,"545":1,"548":1,"549":1,"591":1,"593":1,"740":1,"766":1,"880":1,"881":1,"1192":1,"1202":1,"1203":1,"1212":1,"1213":2,"1238":3,"1241":4,"1242":1,"1243":2,"1245":1,"1247":37,"1259":1,"1285":1,"1309":1,"1310":3,"1323":3,"1330":1,"1335":1,"1338":10,"1341":1,"1349":1,"1358":3,"1361":1,"1368":1,"1370":1,"1379":1,"1394":3,"1395":8,"1401":1,"1406":1,"1415":1,"1435":6,"1437":4,"1440":3,"1443":1,"1495":3,"1584":1,"1665":1,"1719":1,"1778":1,"1814":3,"1815":2,"1816":1,"1927":1,"1985":2,"1987":2,"1988":1,"1989":1,"1990":2,"2010":1,"2152":3,"2207":1,"2219":1}}],["dade",{"3":{"2219":1}}],["dast",{"3":{"2218":1}}],["dashed",{"3":{"1757":1}}],["dashes",{"3":{"1285":1}}],["dash",{"3":{"1334":1,"1338":1,"1394":2,"1437":1,"1466":2}}],["dashboard",{"2":{"1459":1},"3":{"0":3,"126":1,"390":1,"397":2,"407":1,"556":1,"618":1,"707":1,"709":1,"792":1,"861":1,"898":1,"913":3,"914":2,"915":2,"916":1,"917":1,"918":1,"1017":2,"1018":2,"1020":1,"1043":2,"1067":1,"1091":1,"1092":2,"1116":1,"1212":1,"1213":1,"1259":1,"1270":3,"1310":1,"1311":1,"1322":1,"1332":1,"1338":3,"1339":1,"1346":4,"1349":20,"1358":22,"1365":1,"1375":1,"1379":9,"1384":3,"1387":1,"1388":1,"1394":46,"1395":13,"1401":1,"1424":2,"1426":3,"1434":1,"1437":5,"1438":1,"1439":2,"1440":1,"1442":2,"1445":1,"1459":1,"1475":6,"1482":1,"1487":1,"1489":2,"1523":1,"1525":1,"1531":1,"1607":1,"1620":1,"1623":1,"1624":1,"1625":2,"1626":4,"1631":3,"1639":2,"1652":4,"1656":2,"1688":1,"1697":2,"1699":2,"1703":1,"1723":1,"1754":1,"1910":1,"1947":1,"2002":3,"2004":3,"2009":6,"2013":2,"2046":1,"2153":2,"2155":2,"2156":1,"2157":1,"2159":1,"2176":2,"2228":1}}],["dashboards",{"1":{"911":1,"912":1,"913":1,"914":1,"915":1,"916":1,"917":1,"918":1},"3":{"0":1,"402":1,"409":1,"911":1,"1212":1,"1300":1,"1316":1,"1322":1,"1323":1,"1331":1,"1332":1,"1338":1,"1435":1,"1437":2,"1442":2,"1481":1,"1486":1,"1549":1,"1569":1,"1573":1,"1576":2,"1577":1,"1581":1,"1582":1,"1676":1,"2006":1,"2009":1,"2231":1}}],["damaging",{"3":{"1899":1}}],["damage",{"3":{"237":1,"293":1,"818":1,"1323":2,"1338":1,"1358":1,"1395":1,"1979":1}}],["dangling",{"3":{"1322":1,"1358":2,"1382":1,"1394":3,"1414":1,"1638":1,"1851":1}}],["danger",{"3":{"1285":1}}],["dangerously",{"3":{"1300":1}}],["dangerousacceptanyservercertificatevalidator",{"3":{"1285":1}}],["dangerous",{"3":{"932":1,"1107":1,"1132":1,"1133":1,"1299":1,"1358":3,"1464":1,"1466":1,"1472":1,"1488":1,"1839":1,"1918":1,"2043":1,"2193":1}}],["dance",{"3":{"1109":1,"1285":3,"1310":1,"1323":1,"1342":1,"1351":1,"1395":3,"1397":1,"1401":3,"1414":1,"1972":1,"1978":1,"2126":1}}],["daemon",{"3":{"625":1,"642":1,"1338":1,"1429":1,"1435":5,"1448":1,"1454":1,"1459":1,"1485":2,"1489":2}}],["dailyquotagb",{"2":{"719":1,"1464":1,"1468":1},"3":{"719":1,"1464":3,"1466":2,"1468":1}}],["daily",{"3":{"0":1,"395":1,"401":1,"403":2,"709":1,"719":2,"1270":1,"1285":2,"1332":1,"1338":1,"1441":1,"1442":1,"1444":1,"1450":1,"1454":3,"1460":1,"1464":6,"1466":6,"1468":1,"1495":1,"1590":1,"1859":1,"1928":1}}],["dateless",{"3":{"1435":1}}],["dateformat",{"3":{"1394":1}}],["dateonly",{"3":{"1234":1,"1237":6,"1299":3,"1349":4,"1358":8,"1368":1,"1394":4,"1435":6,"1629":3,"1630":1,"1639":4}}],["dateoccurred",{"2":{"77":1,"1160":1,"1162":1,"1163":1,"1584":1,"1887":1,"1888":1},"3":{"0":2,"77":1,"782":1,"846":1,"1160":1,"1161":1,"1162":1,"1163":1,"1212":1,"1247":3,"1249":2,"1259":8,"1269":1,"1270":2,"1349":2,"1395":2,"1435":2,"1437":1,"1495":1,"1576":1,"1584":1,"1887":1,"1888":1}}],["dates",{"3":{"86":2,"265":1,"490":1,"493":1,"656":1,"1073":1,"1074":2,"1237":1,"1307":1,"1308":3,"1349":1,"1358":5,"1368":3,"1386":1,"1394":6,"1395":5,"1414":1,"1437":1,"1563":1,"1590":1,"1629":1,"1630":1,"1763":1,"1775":1}}],["datetimestyles",{"3":{"1247":1,"1299":1,"1308":3,"1395":2}}],["datetimekind",{"3":{"1237":1,"1285":6,"1308":2,"1358":1,"1368":2,"1395":4,"1414":2,"1435":1,"1437":3}}],["datetimefilterstrategytests",{"3":{"1199":1,"1207":3}}],["datetimefilterstrategy",{"2":{"1241":1,"1814":1},"3":{"1199":2,"1203":1,"1207":2,"1241":5,"1247":9,"1814":1}}],["datetimeoffset",{"2":{"1161":1},"3":{"1161":2,"1285":3,"1299":4,"1310":1,"1322":4,"1323":2,"1338":3,"1349":4,"1358":4,"1379":2,"1394":2,"1395":18,"1414":3,"1416":4,"1430":1,"1435":12,"1630":1,"1638":1}}],["datetime",{"2":{"708":1,"1033":1,"1160":2,"2051":1},"3":{"0":1,"330":1,"708":1,"1033":1,"1034":2,"1160":3,"1161":5,"1237":6,"1247":8,"1259":6,"1273":1,"1285":17,"1299":12,"1308":8,"1310":1,"1322":12,"1323":4,"1341":2,"1349":21,"1358":12,"1361":1,"1368":1,"1377":1,"1379":4,"1394":12,"1395":22,"1401":4,"1414":13,"1415":1,"1430":2,"1435":30,"1437":1,"1452":1,"1489":1,"1495":3,"1525":1,"1576":2,"1590":1,"1630":2,"1634":2,"1638":2,"1639":4,"1640":1,"1936":1,"1987":1,"2051":1}}],["datetimerangetests",{"3":{"1199":1,"1207":2,"1237":1}}],["datetimerange",{"2":{"659":1,"660":1},"3":{"0":1,"657":9,"659":3,"660":1,"1199":3,"1203":1,"1207":2,"1230":1,"1234":4,"1237":10,"1629":1,"1630":1,"1634":1,"1661":1}}],["datediff",{"3":{"1361":1,"1368":2}}],["dated",{"3":{"0":1,"104":1,"138":2,"139":3,"140":1,"141":1,"142":1,"371":1,"409":1,"429":1,"486":1,"767":1,"869":1,"1018":1,"1021":1,"1042":1,"1045":1,"1075":1,"1077":1,"1090":1,"1212":1,"1323":1,"1338":1,"1349":1,"1358":1,"1365":1,"1368":1,"1416":2,"1435":2,"1437":1,"1476":1,"1491":1,"1495":1,"1525":2,"1529":1,"1530":1,"1532":1,"1534":1,"1576":1,"1588":1,"1590":3,"1594":1,"1595":2,"1596":1,"1597":1,"1604":1,"1609":1,"1671":1,"1738":1,"1743":1,"2184":1}}],["daterangetests",{"3":{"1199":1,"1207":2}}],["daterange",{"2":{"659":1,"660":1},"3":{"0":1,"657":9,"659":3,"660":1,"1199":3,"1203":1,"1207":2,"1230":1,"1234":7,"1237":15,"1310":1,"1349":1,"1358":1,"1437":1,"1576":1,"1629":1,"1630":2,"1634":2,"1661":1,"1809":1}}],["date",{"3":{"0":2,"1":3,"136":2,"138":1,"139":1,"252":1,"254":1,"259":1,"298":2,"305":7,"349":1,"492":1,"540":2,"624":1,"627":1,"717":1,"825":1,"867":1,"1018":1,"1220":1,"1237":1,"1247":1,"1285":2,"1299":11,"1308":4,"1310":1,"1322":1,"1346":1,"1349":7,"1351":3,"1358":20,"1361":1,"1363":1,"1368":4,"1379":1,"1392":3,"1394":31,"1395":8,"1414":3,"1430":2,"1435":23,"1452":1,"1454":2,"1466":1,"1473":1,"1474":1,"1476":1,"1495":1,"1525":1,"1563":1,"1590":1,"1608":1,"1629":1,"1630":8,"1634":3,"1638":1,"1709":1,"1742":1,"1763":2,"1764":1,"1766":1,"1768":2,"1813":1,"2002":1,"2084":1,"2137":4,"2231":1}}],["datacimex",{"3":{"2219":1}}],["datacontractattribute",{"3":{"1299":1}}],["datacontractserializer",{"2":{"657":1},"3":{"657":1}}],["datacontract",{"2":{"659":1,"963":1},"3":{"0":2,"657":1,"659":1,"725":1,"963":1,"964":2,"1227":1,"1229":7,"1234":1,"1237":5,"1299":1,"1310":1,"1804":1}}],["dataresidency",{"3":{"1488":1}}],["dataresidencytests",{"2":{"972":1,"1524":1},"3":{"971":1,"972":5,"974":2,"1199":1,"1207":2,"1435":8,"1488":1,"1492":1,"1524":1,"1525":2,"1534":1,"1590":1}}],["dataresidencytestsbasetests",{"3":{"972":2,"975":1,"1576":1,"1581":1}}],["dataresidencytestsbase",{"2":{"972":1,"975":1,"1574":1,"2041":1,"2042":1},"3":{"0":1,"972":2,"973":1,"974":2,"975":2,"1199":2,"1207":2,"1212":1,"1430":1,"1435":8,"1495":2,"1574":1,"1576":1,"1581":1,"1653":1,"2041":1,"2042":1}}],["dataannotation",{"3":{"1394":1}}],["dataannotationsvalidator",{"3":{"1323":2}}],["dataannotationsmodelvalidator",{"2":{"1383":1},"3":{"1199":26,"1203":1,"1207":2,"1323":19,"1383":1,"1394":19,"1395":4}}],["dataannotations",{"3":{"117":1,"128":2,"819":1,"1259":1,"1271":1,"1285":3,"1299":9,"1308":3,"1310":2,"1319":1,"1322":6,"1323":22,"1338":3,"1383":1,"1394":29,"1395":3,"1426":2,"1435":1,"1590":2,"1595":2,"1596":1,"1599":1}}],["datagrid",{"3":{"1323":3,"1394":1,"1395":2,"1523":2,"1525":1,"1529":1,"1530":2}}],["datagridlistpagebasetests",{"2":{"2078":1},"3":{"1199":1,"1207":5,"1323":1,"1435":1,"2078":1}}],["datagridlistpagebase",{"0":{"2071":1},"2":{"151":1,"556":1,"558":1,"559":1,"879":1,"884":1,"885":1,"1382":1,"1585":1,"1596":1,"1668":1,"1714":1,"2071":1,"2072":1,"2075":1,"2078":1,"2079":1},"3":{"0":2,"151":1,"556":14,"558":2,"559":4,"879":1,"881":4,"883":1,"884":2,"885":1,"1199":12,"1203":1,"1207":3,"1212":1,"1323":50,"1382":1,"1394":18,"1395":2,"1414":2,"1435":4,"1523":1,"1525":7,"1530":1,"1531":1,"1576":6,"1585":3,"1590":1,"1595":1,"1596":1,"1668":1,"1714":1,"2071":1,"2072":1,"2075":1,"2078":2,"2079":1}}],["dataintegritytests",{"3":{"1199":1,"1207":2,"1437":1}}],["datapoints",{"3":{"914":1,"1442":1,"1466":2}}],["dataprotectionkek",{"3":{"1466":1}}],["dataprotectionkeyscontainer",{"3":{"1466":1}}],["dataprotectionkeyvaultkeyuri",{"2":{"602":1,"604":1,"668":1},"3":{"0":1,"602":1,"604":1,"668":1,"1466":1}}],["dataprotectionstorage",{"3":{"665":1}}],["dataprotectionstorageready",{"2":{"604":1,"663":1,"665":1,"668":1,"1969":1},"3":{"0":1,"604":1,"663":1,"665":2,"668":1,"1969":1}}],["dataprotectionextensionstests",{"3":{"665":1,"1199":1,"1207":2,"1338":1,"1437":2}}],["dataprotectionextensions",{"3":{"665":4,"668":1,"1199":1,"1203":1,"1207":2,"1335":2,"1338":5,"1441":3,"1466":2}}],["dataprotection",{"1":{"662":1,"663":1,"664":1,"665":1,"666":1,"667":1,"668":1,"669":1,"670":1},"2":{"665":2,"668":1,"1338":1},"3":{"0":3,"425":1,"662":1,"664":2,"665":14,"667":2,"668":3,"669":1,"1207":3,"1212":1,"1324":1,"1335":3,"1338":8,"1437":2,"1441":5,"1444":1,"1466":21,"1468":1,"1495":2,"1969":4,"1971":1,"2231":2}}],["datamatrix",{"3":{"682":1,"685":1,"1416":1}}],["datamemberattribute",{"3":{"1299":1}}],["datamember",{"2":{"659":1},"3":{"0":1,"657":1,"659":1,"964":2,"1227":1,"1229":7,"1234":1,"1237":7,"1299":2,"1804":1}}],["datahost",{"3":{"423":1}}],["datasets",{"3":{"1559":1}}],["datasetrowcount",{"3":{"1435":1}}],["datasubject",{"2":{"1435":1},"3":{"1435":1}}],["datasubjectsample",{"3":{"1199":2,"1207":1,"1435":3,"1495":1,"1576":1}}],["datastorageregion",{"3":{"972":1,"1435":2,"2042":1}}],["datascheme",{"3":{"423":1,"1415":1}}],["datasource=",{"3":{"1435":2}}],["datasource=name",{"3":{"1285":1}}],["datasourcemodelcachekeyfactory",{"2":{"1201":1,"1273":1,"1848":1,"1852":1},"3":{"1198":1,"1199":3,"1201":1,"1203":1,"1207":2,"1273":2,"1285":6,"1395":1,"1497":1,"1848":1,"1852":1}}],["datasourcebranchingfitnesstests",{"2":{"1177":1,"1179":1},"3":{"1175":1,"1177":1,"1179":1}}],["datasourceenginecapabilities",{"2":{"1175":1},"3":{"1175":2,"1176":1,"1177":1,"1178":2}}],["datasourceengines",{"2":{"1179":1,"1585":1},"3":{"0":1,"1175":3,"1176":2,"1177":1,"1178":1,"1179":2,"1576":1,"1585":1}}],["datasourceentrysettings",{"2":{"1279":1,"1322":1},"3":{"166":2,"171":1,"1175":2,"1199":16,"1203":1,"1207":2,"1279":2,"1285":19,"1319":2,"1322":4,"1435":1}}],["datasourcessettings",{"2":{"1272":1,"1279":1},"3":{"674":1,"1199":16,"1203":1,"1207":2,"1272":1,"1279":2,"1285":19,"1319":2,"1322":3}}],["datasourceservicetests",{"3":{"1199":1,"1207":3,"1285":1}}],["datasourceserviceadditionaltests",{"3":{"1199":1,"1207":4,"1285":1}}],["datasourceservice",{"3":{"166":1,"1175":1,"1199":4,"1203":1,"1207":2,"1247":1,"1279":3,"1285":12,"1322":3}}],["datasources",{"2":{"170":1,"1440":1,"1472":1,"1489":1,"1613":1,"1651":1,"1700":1,"1721":1,"1728":1,"1730":1,"2096":1},"3":{"47":1,"166":6,"170":5,"633":1,"676":2,"698":4,"703":1,"1004":1,"1034":2,"1067":1,"1175":13,"1203":14,"1207":124,"1276":1,"1279":12,"1285":74,"1299":1,"1310":2,"1319":6,"1322":5,"1325":1,"1327":1,"1331":1,"1338":7,"1428":1,"1435":8,"1437":2,"1440":5,"1442":1,"1445":1,"1472":1,"1489":2,"1576":1,"1585":1,"1613":1,"1651":3,"1683":2,"1700":1,"1718":1,"1721":1,"1726":1,"1727":1,"1728":1,"1730":1,"1789":1,"1848":2,"2007":1,"2096":1}}],["datasourceresolvertests",{"2":{"164":1},"3":{"164":1,"170":2,"1199":1,"1207":2,"1285":11}}],["datasourceresolver",{"2":{"46":1,"47":1,"991":1,"1033":1,"1279":1,"1320":1,"1789":1,"1848":1,"1852":1},"3":{"46":1,"47":1,"166":4,"170":2,"171":1,"991":1,"1033":1,"1175":2,"1199":13,"1203":1,"1207":3,"1259":1,"1279":3,"1283":1,"1285":109,"1320":1,"1322":2,"1325":1,"1338":1,"1435":1,"1437":1,"1533":1,"1789":1,"1848":2,"1852":1}}],["datasourcename",{"2":{"910":1,"2198":1},"3":{"0":1,"905":4,"910":1,"1059":1,"1061":1,"1273":1,"1282":2,"1285":11,"1289":1,"1296":1,"1299":3,"1322":1,"1435":1,"2198":1}}],["datasourcekey",{"2":{"165":1,"169":1,"697":1,"699":1,"701":1},"3":{"0":1,"47":1,"165":1,"166":2,"169":1,"697":1,"698":1,"699":1,"701":1,"1199":91,"1203":1,"1207":2,"1212":1,"1259":2,"1278":2,"1279":3,"1285":77,"1310":1,"1322":1,"1435":6,"1663":1,"1789":1,"1848":3,"1850":1}}],["datasource",{"2":{"889":1,"1174":1,"1179":1,"1320":1,"1360":1,"1855":2,"1856":1},"3":{"0":4,"24":3,"166":3,"170":3,"195":1,"201":1,"889":1,"905":1,"1034":2,"1036":1,"1174":1,"1175":3,"1179":1,"1199":100,"1203":1,"1207":1,"1212":1,"1259":11,"1270":1,"1277":1,"1279":1,"1281":1,"1283":1,"1285":82,"1310":1,"1316":1,"1320":1,"1322":17,"1338":2,"1360":1,"1368":3,"1395":2,"1435":5,"1440":1,"1663":1,"1773":1,"1855":3,"1856":1}}],["dataexport",{"2":{"723":1,"725":1,"727":1,"1298":1,"1409":1,"1671":1},"3":{"0":1,"723":1,"725":1,"727":1,"1203":8,"1207":16,"1298":1,"1299":4,"1310":4,"1409":2,"1414":24,"1576":1,"1671":1}}],["dataexportcontrollerbasetests",{"3":{"1199":1,"1207":6,"1310":2,"1437":2}}],["dataexportcontrollerbase",{"2":{"723":1,"725":1,"728":1,"1060":1,"1298":1,"1671":1},"3":{"0":1,"723":2,"725":4,"728":1,"1060":1,"1199":4,"1203":1,"1207":2,"1298":1,"1299":7,"1310":9,"1322":4,"1409":1,"1414":13,"1495":1,"1576":1,"1671":1}}],["data",{"0":{"1298":1,"1356":1,"1363":1,"1544":1,"1555":1,"1566":1,"1663":1,"1776":1,"2073":1,"2206":1},"1":{"542":1,"543":1,"544":1,"545":1,"546":1,"547":1,"548":1,"549":1,"550":1,"551":1,"552":1,"722":1,"723":1,"724":1,"725":1,"726":1,"727":1,"728":1,"729":1,"878":1,"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"969":1,"970":1,"971":1,"972":1,"973":1,"974":1,"975":1,"976":1,"1172":1,"1173":1,"1174":1,"1175":1,"1176":1,"1177":1,"1178":1,"1179":1,"1180":1},"2":{"1476":1},"3":{"0":25,"12":1,"24":2,"38":7,"39":9,"40":1,"41":5,"42":1,"46":2,"47":1,"48":1,"68":1,"69":1,"80":1,"95":2,"96":2,"117":1,"120":1,"122":1,"128":1,"135":1,"165":1,"167":2,"184":1,"188":1,"195":1,"208":1,"214":1,"219":1,"235":1,"245":1,"248":1,"267":1,"273":1,"275":1,"290":1,"295":4,"310":1,"314":1,"317":2,"318":1,"358":5,"359":2,"360":2,"362":1,"365":2,"388":1,"398":1,"399":1,"402":1,"403":2,"409":1,"428":1,"435":1,"440":1,"441":2,"459":1,"464":1,"537":1,"539":1,"542":1,"545":2,"549":2,"550":1,"555":1,"556":1,"557":1,"572":1,"583":1,"585":1,"587":1,"591":1,"597":1,"598":1,"602":1,"604":1,"605":1,"609":2,"610":1,"612":1,"618":1,"650":1,"659":1,"664":1,"665":3,"666":2,"670":1,"674":1,"676":2,"682":1,"684":1,"689":1,"690":2,"697":3,"698":1,"699":2,"700":3,"707":1,"714":3,"715":3,"716":2,"722":1,"724":6,"725":10,"726":2,"727":4,"728":1,"742":1,"743":1,"745":1,"747":1,"749":1,"757":1,"758":1,"759":1,"798":1,"810":1,"827":2,"838":1,"839":2,"853":1,"855":1,"878":1,"880":1,"881":2,"884":1,"885":1,"896":2,"905":2,"906":1,"907":1,"920":1,"945":1,"947":1,"948":1,"963":1,"964":2,"966":1,"969":1,"971":2,"972":1,"974":1,"987":2,"988":1,"992":1,"1004":1,"1011":1,"1012":1,"1018":1,"1019":1,"1020":1,"1026":1,"1028":1,"1044":2,"1059":2,"1060":1,"1069":1,"1082":2,"1107":1,"1108":1,"1110":1,"1116":3,"1118":1,"1132":1,"1142":2,"1143":1,"1172":1,"1184":2,"1185":2,"1192":4,"1201":1,"1202":3,"1203":3,"1207":8,"1212":9,"1216":3,"1218":1,"1229":8,"1231":1,"1232":2,"1233":2,"1236":8,"1237":45,"1238":1,"1239":2,"1240":1,"1242":1,"1243":3,"1247":18,"1251":2,"1256":2,"1257":1,"1259":30,"1263":2,"1264":1,"1267":1,"1269":1,"1270":28,"1271":3,"1272":3,"1273":1,"1275":1,"1276":1,"1277":4,"1282":4,"1283":3,"1284":1,"1285":197,"1289":2,"1292":1,"1296":1,"1298":2,"1299":74,"1300":10,"1303":1,"1305":1,"1306":2,"1307":3,"1308":43,"1309":15,"1310":41,"1311":1,"1312":1,"1314":1,"1315":1,"1316":2,"1317":1,"1319":2,"1321":1,"1322":78,"1323":77,"1325":2,"1327":2,"1338":28,"1342":2,"1343":2,"1346":1,"1349":59,"1351":1,"1352":5,"1355":1,"1356":2,"1357":1,"1358":156,"1360":1,"1362":1,"1363":1,"1365":1,"1368":36,"1372":1,"1374":1,"1379":20,"1380":1,"1382":1,"1384":2,"1389":1,"1390":1,"1391":1,"1392":1,"1394":96,"1395":150,"1398":1,"1399":2,"1400":1,"1401":48,"1402":2,"1409":5,"1411":1,"1414":105,"1415":4,"1416":13,"1419":1,"1422":2,"1426":3,"1428":2,"1430":2,"1432":4,"1435":149,"1436":1,"1437":18,"1440":3,"1442":1,"1443":1,"1448":3,"1453":1,"1454":4,"1455":2,"1460":1,"1461":1,"1464":2,"1466":26,"1468":1,"1472":5,"1473":4,"1474":1,"1475":2,"1476":4,"1477":8,"1478":1,"1480":1,"1481":2,"1482":1,"1483":1,"1486":3,"1487":6,"1488":7,"1489":1,"1491":1,"1492":1,"1495":5,"1496":1,"1498":3,"1507":1,"1523":2,"1524":2,"1525":6,"1529":1,"1530":1,"1533":3,"1534":2,"1539":1,"1543":2,"1544":1,"1547":2,"1548":2,"1552":1,"1554":4,"1555":2,"1558":2,"1559":1,"1560":2,"1562":1,"1566":5,"1567":1,"1571":4,"1574":1,"1576":6,"1580":1,"1581":2,"1583":1,"1584":1,"1585":3,"1589":1,"1590":5,"1596":2,"1599":2,"1601":1,"1606":2,"1620":1,"1628":5,"1629":8,"1630":8,"1631":12,"1633":1,"1634":1,"1635":3,"1636":2,"1637":4,"1638":12,"1639":2,"1640":4,"1648":1,"1653":1,"1659":2,"1661":1,"1662":1,"1663":1,"1664":2,"1665":1,"1666":1,"1669":1,"1670":1,"1671":3,"1676":1,"1684":1,"1685":1,"1701":1,"1706":3,"1708":1,"1712":1,"1713":1,"1717":1,"1721":1,"1726":1,"1728":1,"1733":1,"1740":2,"1745":1,"1746":4,"1747":1,"1750":2,"1752":1,"1756":1,"1758":1,"1763":2,"1764":6,"1771":1,"1773":2,"1775":1,"1776":2,"1778":1,"1782":1,"1783":1,"1789":4,"1791":1,"1792":2,"1794":2,"1795":3,"1798":1,"1803":1,"1804":1,"1806":1,"1807":1,"1809":1,"1811":1,"1812":1,"1814":1,"1816":1,"1824":1,"1831":2,"1835":1,"1837":2,"1838":3,"1847":1,"1848":1,"1851":1,"1852":1,"1854":2,"1856":1,"1858":1,"1860":1,"1861":1,"1862":2,"1864":1,"1867":2,"1869":1,"1872":1,"1901":1,"1902":1,"1919":2,"1921":2,"1922":3,"1923":1,"1941":1,"1947":1,"1949":1,"1950":1,"1952":2,"1953":1,"1956":1,"1958":1,"1987":1,"1992":1,"1993":1,"1996":2,"2009":1,"2011":2,"2012":1,"2021":1,"2028":1,"2032":1,"2033":2,"2035":1,"2037":1,"2039":1,"2042":1,"2046":1,"2052":3,"2054":1,"2056":1,"2058":1,"2060":4,"2061":2,"2062":8,"2063":1,"2064":4,"2066":4,"2067":6,"2068":4,"2069":1,"2072":2,"2075":1,"2079":1,"2084":1,"2090":2,"2097":1,"2110":1,"2113":1,"2124":2,"2125":4,"2126":2,"2127":2,"2139":4,"2140":6,"2141":3,"2142":1,"2145":3,"2149":1,"2153":1,"2155":1,"2157":1,"2158":2,"2159":3,"2163":1,"2170":1,"2174":1,"2178":1,"2190":1,"2191":2,"2198":1,"2199":2,"2202":1,"2206":1,"2213":1,"2218":3,"2219":4,"2223":1,"2229":1,"2231":9,"2235":1,"2240":1}}],["databasecreated",{"3":{"1435":1}}],["databaseauditingpolicy",{"3":{"1435":1}}],["databasefacade",{"3":{"1310":1}}],["databaserestoredrilltests",{"2":{"1706":1},"3":{"1199":1,"1207":3,"1437":1,"1576":2,"1585":1,"1706":3}}],["databaseinitializationextensionstests",{"2":{"1495":1},"3":{"1198":1,"1199":2,"1207":7,"1285":1,"1310":2,"1322":3,"1495":2}}],["databaseinitializationextensions",{"2":{"290":1,"1034":1},"3":{"290":2,"295":1,"296":1,"453":1,"583":1,"1034":2,"1199":1,"1203":1,"1207":2,"1208":1,"1285":12,"1310":5,"1322":8,"1368":2,"1414":1,"1576":1}}],["databaseinitstrategy",{"2":{"290":1,"291":2,"295":1,"296":1,"563":1,"1466":1,"1613":1,"1651":1,"1663":1,"1718":1},"3":{"290":1,"291":2,"295":1,"296":1,"563":1,"698":1,"1310":1,"1319":1,"1322":3,"1466":1,"1489":1,"1613":1,"1651":1,"1663":1,"1718":1}}],["databaseinitstrategy=migrate",{"3":{"0":1,"572":1,"1212":1,"1454":1,"1475":1}}],["databasenameprefix",{"3":{"1435":1,"1487":1,"1489":1}}],["databasename",{"2":{"1445":1},"3":{"195":1,"201":1,"1259":8,"1285":3,"1435":2,"1440":1,"1445":1,"1683":1,"1727":1}}],["databases",{"0":{"1613":1,"2052":1},"1":{"1853":1,"1854":1,"1855":1,"1856":1,"1857":1,"1858":1,"1859":1,"1860":1,"1861":1},"3":{"0":3,"10":1,"47":2,"49":1,"61":1,"67":1,"165":1,"166":2,"295":1,"390":1,"439":1,"628":1,"676":1,"717":1,"719":1,"762":1,"766":2,"913":1,"971":1,"1025":1,"1042":1,"1059":1,"1061":1,"1212":1,"1259":3,"1273":1,"1275":1,"1278":1,"1279":2,"1285":28,"1309":1,"1310":1,"1319":1,"1322":3,"1324":1,"1325":1,"1331":1,"1349":1,"1358":6,"1368":2,"1379":2,"1395":4,"1401":1,"1414":2,"1428":1,"1429":1,"1435":12,"1438":1,"1439":1,"1440":2,"1445":2,"1448":1,"1454":2,"1459":3,"1460":1,"1466":11,"1468":5,"1470":3,"1472":4,"1473":4,"1474":5,"1476":2,"1477":3,"1481":3,"1495":1,"1524":2,"1525":3,"1534":1,"1588":1,"1590":2,"1595":1,"1599":1,"1651":1,"1663":1,"1669":1,"1677":1,"1683":1,"1687":1,"1688":2,"1697":1,"1709":1,"1723":1,"1782":1,"1791":1,"1846":4,"1848":3,"1851":1,"1852":2,"1853":1,"1854":1,"1857":1,"1859":1,"1861":1,"1863":1,"2002":1,"2003":1,"2004":1,"2007":1,"2013":1,"2026":1,"2033":3,"2035":1,"2036":2,"2037":2,"2052":2,"2059":1,"2066":1,"2108":1,"2191":1,"2198":1,"2206":1,"2228":1}}],["database",{"0":{"1273":1,"1279":1,"1282":1,"1428":1,"1472":1,"1509":1,"1848":1,"1870":1,"2034":1,"2163":1},"1":{"44":1,"45":1,"46":1,"47":1,"48":1,"49":1,"1846":1,"1847":1,"1848":1,"1849":1,"1850":1,"1851":1,"1852":1,"1868":1,"1869":1,"1870":1,"1871":1,"1872":1,"1873":1,"1874":1,"1875":1,"1876":1},"2":{"291":1,"1133":1,"1273":1,"1460":1,"1466":3,"1474":3},"3":{"0":27,"10":3,"17":1,"24":1,"38":1,"41":1,"44":1,"45":4,"46":2,"47":7,"48":1,"49":1,"58":1,"59":1,"61":1,"64":1,"66":2,"67":1,"71":3,"73":1,"77":1,"94":1,"109":1,"135":1,"153":1,"158":1,"165":2,"166":10,"167":1,"169":1,"170":2,"171":1,"174":1,"176":1,"195":3,"196":2,"197":1,"199":1,"201":2,"202":2,"218":1,"255":1,"290":2,"291":3,"292":2,"293":2,"294":2,"295":2,"296":1,"330":1,"331":1,"341":3,"342":2,"343":2,"350":2,"358":4,"359":1,"390":1,"391":1,"403":1,"451":1,"458":2,"459":4,"460":1,"461":2,"471":1,"473":1,"500":1,"501":1,"535":3,"536":1,"537":4,"538":1,"541":1,"544":1,"545":1,"546":2,"548":1,"549":1,"551":2,"563":1,"565":1,"568":2,"572":3,"573":1,"574":1,"575":1,"582":1,"583":1,"587":1,"598":1,"599":3,"600":1,"601":1,"605":1,"625":1,"628":1,"631":2,"632":1,"633":6,"645":1,"676":1,"688":1,"690":2,"691":1,"692":1,"697":2,"698":4,"700":1,"702":5,"703":1,"707":3,"708":1,"709":2,"711":1,"713":1,"714":3,"715":3,"716":1,"717":1,"718":2,"719":4,"735":2,"742":1,"743":1,"756":1,"758":1,"759":1,"765":1,"766":2,"767":1,"769":1,"782":1,"793":1,"799":1,"805":1,"809":1,"810":1,"815":1,"817":1,"819":5,"820":2,"821":2,"823":1,"853":1,"856":1,"888":1,"889":1,"890":1,"891":2,"893":1,"897":1,"904":1,"905":3,"906":2,"907":1,"913":1,"918":2,"922":1,"930":1,"932":1,"933":1,"972":1,"974":1,"987":3,"988":1,"990":1,"992":2,"995":2,"996":1,"1000":2,"1018":1,"1030":1,"1032":1,"1033":3,"1034":5,"1036":1,"1037":1,"1038":1,"1043":2,"1050":1,"1052":1,"1059":1,"1061":1,"1066":1,"1067":2,"1068":1,"1074":1,"1082":2,"1083":3,"1085":3,"1116":1,"1120":1,"1133":1,"1135":1,"1137":1,"1140":1,"1145":1,"1149":1,"1150":5,"1151":1,"1152":1,"1153":1,"1173":1,"1192":1,"1202":3,"1203":3,"1212":6,"1213":2,"1228":1,"1229":2,"1230":1,"1231":2,"1236":2,"1237":11,"1238":2,"1239":2,"1242":1,"1243":2,"1244":1,"1247":16,"1248":1,"1251":2,"1253":3,"1254":4,"1256":1,"1259":33,"1263":2,"1269":1,"1270":17,"1271":3,"1272":3,"1273":5,"1274":1,"1275":2,"1276":4,"1277":2,"1278":5,"1279":10,"1280":5,"1281":3,"1282":5,"1283":2,"1285":289,"1289":3,"1291":1,"1296":2,"1299":29,"1300":2,"1302":1,"1308":15,"1309":9,"1310":29,"1312":1,"1315":1,"1319":7,"1320":2,"1322":47,"1323":1,"1324":1,"1325":3,"1327":1,"1331":5,"1332":1,"1338":27,"1341":7,"1342":2,"1343":1,"1349":52,"1353":2,"1354":1,"1356":2,"1358":110,"1359":1,"1360":2,"1361":2,"1362":3,"1363":1,"1364":1,"1366":1,"1368":31,"1378":1,"1379":4,"1394":9,"1395":82,"1398":1,"1400":2,"1401":14,"1403":1,"1404":1,"1405":1,"1408":1,"1409":1,"1411":1,"1412":2,"1414":46,"1416":1,"1427":1,"1428":10,"1430":1,"1435":68,"1436":1,"1437":13,"1440":13,"1442":5,"1445":3,"1448":3,"1450":1,"1452":2,"1454":4,"1456":1,"1459":6,"1460":1,"1461":1,"1462":1,"1466":45,"1468":2,"1470":3,"1472":10,"1473":12,"1474":17,"1475":4,"1476":4,"1477":11,"1481":7,"1482":3,"1483":1,"1485":1,"1487":7,"1489":7,"1490":2,"1491":3,"1509":1,"1522":1,"1523":1,"1533":5,"1540":1,"1543":2,"1544":2,"1546":1,"1567":1,"1571":1,"1575":1,"1576":1,"1587":1,"1589":1,"1590":4,"1595":2,"1596":1,"1599":1,"1601":1,"1629":3,"1630":6,"1636":2,"1637":3,"1638":6,"1639":1,"1640":4,"1649":1,"1650":4,"1651":4,"1652":7,"1653":2,"1655":2,"1660":5,"1661":4,"1662":3,"1663":5,"1666":3,"1667":1,"1669":2,"1670":3,"1673":1,"1674":5,"1677":1,"1682":1,"1683":10,"1684":3,"1685":3,"1687":2,"1688":1,"1691":2,"1694":1,"1695":1,"1696":5,"1697":1,"1699":2,"1700":2,"1703":2,"1704":1,"1705":1,"1706":1,"1707":1,"1716":1,"1717":4,"1718":2,"1720":3,"1721":3,"1723":3,"1725":2,"1726":12,"1727":6,"1728":5,"1730":2,"1747":2,"1748":1,"1763":1,"1764":1,"1766":1,"1773":2,"1777":1,"1778":1,"1779":1,"1780":1,"1782":2,"1784":1,"1785":1,"1788":1,"1789":4,"1791":1,"1792":3,"1795":1,"1799":1,"1805":1,"1807":1,"1809":1,"1813":1,"1814":3,"1816":2,"1820":3,"1830":1,"1831":2,"1834":2,"1836":1,"1838":1,"1839":2,"1840":2,"1845":1,"1846":6,"1847":3,"1848":11,"1850":3,"1851":1,"1852":11,"1853":1,"1854":5,"1857":3,"1859":1,"1861":1,"1862":2,"1863":3,"1865":1,"1866":1,"1867":4,"1868":2,"1870":2,"1871":1,"1872":1,"1874":2,"1875":2,"1876":3,"1885":1,"1886":1,"1897":1,"1899":1,"1900":1,"1902":2,"1912":1,"1919":1,"1920":1,"1921":2,"1922":2,"1932":2,"1933":1,"1936":1,"1939":1,"1940":1,"1945":2,"1947":3,"1950":1,"1953":2,"1964":1,"1979":2,"1981":1,"1983":1,"1984":1,"1986":1,"1987":2,"1990":1,"1998":1,"2007":4,"2026":1,"2031":3,"2033":5,"2034":6,"2035":3,"2036":4,"2037":2,"2046":1,"2048":1,"2049":2,"2051":1,"2052":3,"2053":1,"2054":4,"2055":1,"2059":2,"2060":1,"2063":2,"2070":1,"2088":1,"2096":3,"2110":2,"2112":2,"2114":1,"2127":1,"2134":1,"2139":6,"2140":4,"2143":2,"2145":4,"2160":3,"2161":2,"2163":2,"2167":1,"2168":4,"2169":1,"2179":1,"2184":1,"2190":1,"2192":3,"2206":3,"2213":1,"2219":3,"2220":1,"2229":3,"2231":4,"2242":1}}],["darkened",{"3":{"2074":1}}],["darkens",{"3":{"1323":2}}],["darktheme",{"3":{"1237":1,"1414":2}}],["darkmodee2etests",{"2":{"1642":1,"1644":1},"3":{"618":1,"1199":1,"1207":2,"1323":2,"1435":1,"1576":4,"1585":2,"1642":1,"1644":1}}],["dark",{"1":{"270":1,"271":1,"272":1,"273":1,"274":1,"275":1,"276":1,"277":1,"2080":1,"2081":1,"2082":1,"2083":1,"2084":1,"2085":1},"3":{"0":3,"270":1,"272":1,"273":2,"274":1,"275":1,"299":1,"396":1,"555":2,"556":1,"558":2,"618":4,"622":3,"1192":1,"1212":1,"1234":1,"1237":2,"1299":1,"1310":1,"1323":27,"1332":1,"1338":3,"1401":2,"1404":1,"1414":5,"1415":2,"1435":6,"1437":2,"1479":1,"1486":1,"1495":2,"1498":1,"1512":1,"1525":2,"1556":1,"1576":12,"1585":2,"1590":2,"1595":2,"1607":1,"1642":5,"1644":6,"1652":1,"1668":1,"1741":1,"2074":2,"2078":4,"2079":1,"2080":4,"2082":2,"2085":3,"2133":2,"2159":1,"2209":1,"2231":2}}],["dayoffset",{"3":{"1368":2}}],["daylight",{"3":{"709":1,"1269":1,"1270":1,"1437":1}}],["days",{"2":{"629":1},"3":{"0":5,"38":1,"41":1,"93":1,"489":1,"499":2,"501":2,"502":1,"513":1,"591":2,"626":2,"627":1,"628":1,"629":2,"642":1,"706":1,"767":1,"805":2,"905":1,"1020":2,"1150":1,"1212":1,"1259":1,"1287":1,"1299":5,"1322":2,"1323":1,"1409":1,"1416":1,"1435":1,"1444":1,"1452":1,"1454":6,"1458":1,"1459":1,"1460":2,"1464":5,"1466":7,"1473":3,"1474":1,"1475":4,"1477":1,"1489":1,"1524":1,"1525":2,"1533":1,"1588":1,"1590":2,"1595":2,"1598":2,"1630":1,"1636":1,"1637":1,"1640":3,"1746":1,"1764":1,"1766":1,"1844":1,"1981":1,"2060":1,"2062":1,"2064":1}}],["day",{"0":{"1475":1},"1":{"270":1,"271":1,"272":1,"273":1,"274":1,"275":1,"276":1,"277":1},"3":{"0":5,"41":1,"130":1,"186":1,"243":2,"265":1,"270":1,"273":1,"275":1,"311":1,"379":1,"387":1,"390":1,"423":1,"424":1,"491":2,"492":1,"501":1,"520":1,"529":1,"566":1,"594":1,"609":2,"626":1,"628":2,"689":1,"690":1,"706":2,"707":1,"708":1,"717":1,"726":1,"757":1,"765":1,"766":2,"767":1,"781":1,"785":1,"792":1,"831":1,"832":1,"848":1,"907":2,"916":1,"1003":1,"1013":2,"1018":1,"1020":1,"1082":1,"1085":1,"1102":1,"1124":1,"1125":1,"1134":1,"1143":1,"1150":1,"1154":1,"1162":1,"1183":1,"1192":1,"1212":1,"1237":2,"1247":1,"1270":2,"1287":1,"1288":1,"1294":1,"1297":1,"1299":4,"1300":1,"1308":2,"1322":1,"1323":3,"1338":1,"1347":1,"1348":1,"1349":3,"1355":1,"1358":5,"1368":2,"1377":1,"1378":1,"1379":2,"1392":1,"1394":10,"1395":23,"1396":1,"1400":1,"1401":9,"1415":3,"1434":1,"1435":5,"1437":6,"1446":1,"1450":1,"1454":6,"1455":1,"1456":3,"1457":3,"1458":1,"1461":1,"1464":12,"1466":15,"1468":1,"1470":1,"1473":2,"1474":1,"1475":2,"1481":2,"1486":1,"1489":1,"1491":2,"1495":5,"1498":1,"1512":1,"1513":1,"1524":3,"1525":6,"1528":2,"1529":1,"1530":2,"1531":1,"1533":1,"1551":2,"1576":1,"1584":1,"1588":1,"1590":5,"1625":2,"1626":1,"1628":3,"1629":5,"1630":1,"1640":1,"1662":1,"1674":1,"1677":1,"1694":1,"1697":1,"1763":1,"1764":1,"1766":2,"1782":1,"1787":2,"1788":1,"1789":1,"1794":1,"1799":1,"1825":1,"1846":2,"1852":1,"1853":1,"1861":1,"1862":1,"1863":1,"1869":1,"1893":1,"1899":2,"1904":1,"1921":1,"1924":1,"1941":1,"1947":2,"1950":1,"1957":2,"1959":1,"1962":1,"1979":1,"1981":1,"1983":2,"1985":1,"2002":1,"2014":2,"2015":1,"2027":1,"2033":1,"2055":1,"2067":1,"2074":1,"2079":1,"2080":1,"2085":1,"2086":1,"2096":1,"2097":1,"2098":1,"2109":1,"2132":3,"2137":1,"2145":1,"2179":2,"2192":1,"2205":1,"2219":1,"2227":1,"2231":1,"2237":1,"2238":2,"2239":2}}],["dns",{"3":{"0":1,"100":1,"109":1,"234":1,"235":1,"881":1,"1187":1,"1225":1,"1323":1,"1328":1,"1333":1,"1338":6,"1394":2,"1395":2,"1414":1,"1442":2,"1446":1,"1466":4,"1631":1,"1639":1,"1652":1,"1701":1}}],["duties",{"3":{"1365":2}}],["duty",{"3":{"757":1,"1270":1,"1299":1,"1401":1,"2062":1}}],["duplexstreamingserverhandler",{"3":{"1311":1}}],["duplex",{"3":{"1311":3}}],["duplicating",{"3":{"186":1,"273":1,"282":1,"351":1,"423":1,"767":1,"914":1,"1229":1,"1259":2,"1270":2,"1285":3,"1308":1,"1310":3,"1322":2,"1323":3,"1336":1,"1349":1,"1358":4,"1379":1,"1394":4,"1395":2,"1401":2,"1414":1,"1415":1,"1435":3,"1556":1,"1827":1,"2082":1}}],["duplication",{"3":{"133":1,"135":1,"255":1,"325":1,"405":1,"651":2,"681":1,"724":1,"819":1,"837":1,"1011":1,"1043":1,"1061":1,"1220":1,"1247":1,"1259":1,"1270":1,"1285":1,"1299":3,"1300":1,"1308":1,"1310":3,"1322":1,"1323":6,"1338":6,"1358":11,"1368":1,"1394":5,"1395":2,"1401":1,"1414":4,"1426":1,"1435":2,"1495":1,"1525":2,"1916":1,"2041":1,"2074":1,"2076":2,"2079":1,"2097":1,"2131":1,"2191":1,"2197":1}}],["duplicate→409",{"3":{"1610":1}}],["duplicatecode",{"3":{"1435":3}}],["duplicateticketerrors",{"3":{"1199":2,"1207":1,"1435":11}}],["duplicated",{"3":{"0":1,"61":1,"219":1,"255":1,"399":1,"400":1,"404":1,"463":1,"490":1,"534":1,"538":1,"558":1,"609":1,"819":1,"829":1,"834":1,"837":1,"839":1,"848":1,"899":1,"996":1,"1011":1,"1043":1,"1059":1,"1118":1,"1224":1,"1229":1,"1247":2,"1259":2,"1268":1,"1270":6,"1271":2,"1285":1,"1299":2,"1300":1,"1308":1,"1310":2,"1311":1,"1322":2,"1323":6,"1338":4,"1349":4,"1358":15,"1379":1,"1388":1,"1394":6,"1395":5,"1401":3,"1414":4,"1435":2,"1442":1,"1445":1,"1452":1,"1466":1,"1495":4,"1541":1,"1550":1,"1564":2,"1581":1,"1631":1,"1829":1,"1928":1,"2012":1,"2054":1,"2072":2,"2149":1,"2156":1,"2158":2,"2167":1}}],["duplicates",{"3":{"0":2,"159":3,"162":1,"549":1,"649":1,"811":1,"846":1,"891":2,"915":2,"988":1,"989":1,"995":1,"1140":1,"1237":1,"1240":1,"1258":1,"1259":2,"1270":1,"1271":2,"1285":2,"1299":3,"1310":5,"1322":3,"1323":1,"1337":1,"1338":2,"1349":3,"1358":3,"1368":1,"1393":1,"1394":5,"1395":1,"1401":2,"1414":1,"1416":1,"1453":1,"1495":6,"1525":1,"1576":1,"1630":5,"1638":1,"1764":1,"1839":1,"1844":1,"1869":1,"1908":6,"1909":1,"1910":2,"1911":4}}],["duplicate",{"3":{"0":8,"18":1,"24":3,"156":1,"157":2,"159":1,"195":3,"197":1,"198":1,"201":5,"295":1,"330":1,"465":2,"508":1,"518":1,"528":1,"529":1,"557":1,"558":1,"610":1,"688":1,"691":1,"692":2,"749":1,"790":1,"821":1,"834":1,"871":1,"890":1,"897":1,"905":1,"966":1,"988":1,"996":5,"998":1,"1018":1,"1020":1,"1022":1,"1043":1,"1099":1,"1100":1,"1142":1,"1147":1,"1212":2,"1229":1,"1231":1,"1237":1,"1247":6,"1251":1,"1254":1,"1258":2,"1259":14,"1265":1,"1269":1,"1270":8,"1271":1,"1283":1,"1285":17,"1287":1,"1288":1,"1289":1,"1296":1,"1299":11,"1308":5,"1309":1,"1310":15,"1311":2,"1316":1,"1322":10,"1323":13,"1327":1,"1338":8,"1342":2,"1346":1,"1349":15,"1358":46,"1366":1,"1368":2,"1375":1,"1379":7,"1388":1,"1394":7,"1395":26,"1401":6,"1414":2,"1416":2,"1430":1,"1435":17,"1437":5,"1440":1,"1453":1,"1466":2,"1471":1,"1472":1,"1487":1,"1495":16,"1534":1,"1546":2,"1565":1,"1576":1,"1630":3,"1631":2,"1639":1,"1660":1,"1664":1,"1665":1,"1748":1,"1764":2,"1766":1,"1768":1,"1773":2,"1831":1,"1839":2,"1844":1,"1845":1,"1888":1,"1889":1,"1904":1,"1906":1,"1908":4,"1909":1,"1910":2,"1911":3,"1946":1,"1987":1,"1990":1,"1999":1,"2161":1,"2164":1,"2165":1,"2181":1}}],["dumb",{"3":{"1349":1,"1394":1,"1554":1,"1576":1,"1585":1}}],["dumber",{"3":{"1323":1}}],["dummybearerauthority",{"3":{"1435":1}}],["dummy",{"3":{"946":1,"949":1,"1435":1,"1489":1}}],["dumped",{"3":{"1893":1}}],["dumping",{"3":{"1310":1,"1323":1,"1415":1,"1539":1}}],["dump",{"3":{"854":1,"904":1,"1244":1,"1293":1,"1299":2,"1323":1,"1395":1,"1576":1,"1984":1}}],["dueon",{"3":{"1322":1}}],["due",{"2":{"2190":1},"3":{"0":2,"539":2,"707":3,"709":1,"813":1,"1040":2,"1042":3,"1045":2,"1275":4,"1285":11,"1316":1,"1322":15,"1395":1,"1435":1,"1466":1,"1630":1,"1638":1,"1947":1,"2155":2,"2184":1,"2187":2,"2190":2,"2192":1}}],["durably",{"3":{"1251":1,"1401":3,"1414":1,"1435":1}}],["durable",{"0":{"1356":1,"1366":1},"1":{"222":1,"223":1,"224":1,"225":1,"226":1,"227":1,"228":1,"229":1,"230":1,"231":1,"2180":1,"2181":1,"2182":1,"2183":1,"2184":1,"2185":1,"2186":1,"2187":1,"2188":1,"2189":1,"2190":1,"2191":1,"2192":1},"3":{"0":12,"222":1,"223":1,"225":4,"229":1,"230":3,"379":1,"380":1,"382":1,"404":1,"406":1,"422":1,"433":1,"438":1,"440":1,"459":1,"466":1,"516":1,"517":1,"518":1,"521":1,"522":1,"523":1,"524":1,"536":1,"541":1,"706":2,"711":1,"809":1,"810":2,"813":1,"815":1,"856":1,"897":2,"898":1,"901":1,"933":1,"987":1,"988":3,"989":2,"990":1,"995":2,"996":2,"1000":1,"1018":1,"1022":1,"1024":1,"1030":1,"1044":2,"1074":1,"1075":2,"1076":1,"1099":1,"1100":2,"1102":1,"1104":1,"1116":1,"1120":1,"1142":1,"1152":1,"1154":1,"1192":1,"1212":4,"1247":1,"1259":1,"1272":1,"1274":1,"1278":1,"1285":12,"1299":2,"1301":1,"1303":1,"1308":10,"1322":6,"1323":5,"1349":1,"1351":1,"1356":2,"1358":7,"1359":1,"1368":1,"1375":1,"1379":8,"1389":1,"1395":1,"1396":2,"1397":1,"1399":1,"1401":3,"1407":1,"1409":1,"1410":2,"1414":11,"1416":2,"1437":2,"1454":1,"1475":1,"1495":2,"1511":2,"1630":1,"1664":1,"1674":1,"1734":1,"1778":2,"1788":1,"1789":1,"1799":1,"1836":1,"1839":5,"1841":1,"1842":1,"1860":2,"1909":2,"1911":1,"1931":1,"1932":3,"1934":2,"1936":1,"1939":2,"1940":2,"1941":3,"1942":3,"1943":1,"1946":3,"1947":2,"1948":4,"1949":7,"1980":1,"2005":1,"2019":1,"2080":1,"2090":1,"2126":1,"2155":1,"2164":3,"2167":1,"2168":1,"2179":2,"2180":2,"2182":1,"2189":1,"2191":1,"2192":2,"2202":1,"2205":2,"2231":3}}],["durability",{"3":{"0":1,"230":2,"444":1,"711":1,"933":1,"987":1,"1018":1,"1140":1,"1259":2,"1270":1,"1285":1,"1301":1,"1358":4,"1366":2,"1368":1,"1379":1,"1395":1,"1416":1,"1473":2,"1844":1,"1932":1,"2090":1}}],["durationmeasurement",{"3":{"1199":2,"1207":1,"1434":1}}],["durationms",{"3":{"609":1,"1466":1}}],["durationthreshold",{"3":{"861":1}}],["durations",{"3":{"731":1,"733":1,"1259":1,"1358":1}}],["durationhistogram",{"3":{"707":1,"1285":1,"1322":1}}],["duration",{"2":{"402":1,"407":3,"409":1,"1089":1,"1424":2,"1639":1,"2158":1,"2176":2,"2190":1},"3":{"0":6,"72":2,"100":1,"115":1,"125":2,"243":1,"395":4,"397":4,"398":1,"402":1,"404":1,"405":1,"407":5,"408":2,"409":1,"608":2,"609":1,"707":1,"709":2,"743":1,"856":1,"996":1,"1089":1,"1091":1,"1234":2,"1237":1,"1259":3,"1263":2,"1267":1,"1268":1,"1270":18,"1275":1,"1285":3,"1289":1,"1299":1,"1300":2,"1310":3,"1316":1,"1322":7,"1323":2,"1329":1,"1332":1,"1338":10,"1341":2,"1343":2,"1349":5,"1358":3,"1361":1,"1368":5,"1394":1,"1416":9,"1424":4,"1426":10,"1435":1,"1440":1,"1442":2,"1459":1,"1465":1,"1466":4,"1473":2,"1474":1,"1525":1,"1576":1,"1629":1,"1630":3,"1631":1,"1634":2,"1637":1,"1639":1,"1640":1,"1650":1,"1663":1,"1676":1,"1708":1,"1820":1,"1840":1,"1908":1,"1916":1,"1919":1,"1947":1,"2005":2,"2009":1,"2065":1,"2095":1,"2155":8,"2157":2,"2158":1,"2159":1,"2176":4,"2190":1,"2214":1,"2231":1}}],["during",{"3":{"0":2,"17":1,"19":1,"21":1,"71":1,"120":1,"159":1,"207":2,"235":1,"261":1,"267":1,"273":1,"293":1,"332":1,"383":1,"506":1,"507":1,"555":1,"556":1,"558":1,"560":1,"599":1,"649":1,"664":1,"733":2,"743":1,"749":1,"762":1,"768":1,"811":1,"819":1,"820":1,"827":1,"848":1,"881":1,"915":1,"954":1,"1152":1,"1154":1,"1233":1,"1237":5,"1259":3,"1265":1,"1270":2,"1271":1,"1274":1,"1285":11,"1287":2,"1297":1,"1299":8,"1308":1,"1310":1,"1322":4,"1323":31,"1337":1,"1338":7,"1346":1,"1349":7,"1358":8,"1366":1,"1379":1,"1390":1,"1392":1,"1394":7,"1395":10,"1401":9,"1407":1,"1414":5,"1415":4,"1416":13,"1426":2,"1431":1,"1432":1,"1435":9,"1437":1,"1440":2,"1442":1,"1454":1,"1455":1,"1466":1,"1467":1,"1481":1,"1487":2,"1533":1,"1581":1,"1629":6,"1630":6,"1631":3,"1632":1,"1633":1,"1635":1,"1636":1,"1637":7,"1638":7,"1640":1,"1699":1,"1707":1,"1754":1,"1911":1,"1967":1,"1971":1,"2008":1,"2015":1,"2029":1,"2075":1,"2084":1,"2118":1,"2134":1,"2164":1,"2231":1}}],["duality",{"3":{"1358":1}}],["dual",{"0":{"1507":1,"1895":1},"1":{"14":1,"15":1,"16":1,"17":1,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1,"24":1,"25":1,"26":1,"27":1,"297":1,"298":1,"299":1,"300":1,"301":1,"302":1,"303":1,"304":1,"305":1,"306":1,"525":1,"526":1,"527":1,"528":1,"529":1,"530":1,"531":1,"532":1,"1248":1,"1249":1,"1250":1,"1251":1,"1252":1,"1253":1,"1254":1,"1255":1,"1256":1,"1257":1,"1258":1,"1259":1,"1892":1,"1893":1,"1894":1,"1895":1,"1896":1,"1897":1,"1898":1},"3":{"0":3,"14":1,"17":1,"20":1,"21":1,"22":1,"24":1,"26":1,"27":1,"64":1,"143":1,"209":1,"229":1,"297":1,"397":1,"498":1,"525":1,"532":1,"680":1,"874":1,"935":1,"942":1,"945":2,"984":1,"1004":1,"1008":1,"1043":1,"1192":2,"1202":2,"1203":2,"1212":3,"1213":1,"1237":3,"1247":3,"1248":1,"1259":5,"1270":3,"1285":1,"1286":1,"1288":1,"1299":3,"1310":2,"1311":1,"1323":1,"1344":1,"1349":1,"1358":1,"1368":1,"1394":1,"1395":1,"1401":1,"1414":2,"1435":4,"1442":2,"1449":1,"1454":1,"1466":2,"1495":1,"1507":1,"1542":1,"1547":1,"1570":2,"1573":1,"1575":1,"1576":2,"1584":1,"1585":1,"1590":1,"1834":2,"1837":1,"1841":1,"1845":2,"1891":1,"1892":1,"1895":4,"1897":1,"1898":2,"1971":1,"2013":1,"2180":1,"2207":1,"2220":1,"2229":1,"2231":2}}],["dbs",{"3":{"1472":4,"1495":1,"1525":1,"1534":1,"1565":1,"1569":1,"1589":1,"1590":3,"1595":1,"1598":1,"1677":1}}],["dbseedertests",{"3":{"1199":1,"1207":3,"1285":2}}],["dbseeder",{"2":{"295":1,"1363":1},"3":{"295":2,"1199":4,"1203":1,"1207":2,"1283":2,"1285":9,"1363":1,"1368":4,"1414":2}}],["dbsets",{"0":{"1362":1}}],["dbsetremovingfixture",{"3":{"1199":2,"1207":1,"1435":10}}],["dbset",{"2":{"544":1,"1359":1,"1699":1,"1718":1},"3":{"39":1,"544":1,"545":1,"1259":1,"1278":1,"1285":10,"1359":1,"1362":4,"1368":16,"1395":6,"1414":1,"1435":17,"1495":1,"1650":1,"1684":1,"1685":2,"1696":1,"1699":1,"1718":1,"1843":1}}],["dbname",{"3":{"1435":1}}],["dbcontextattribute",{"3":{"1322":1}}],["dbcontextarg",{"3":{"1285":2}}],["dbcontextoptionsbuilder",{"3":{"1435":3}}],["dbcontextoptions",{"2":{"1368":1},"3":{"1285":3,"1368":4,"1395":2,"1414":1}}],["dbcontexts",{"2":{"1285":1,"1499":1},"3":{"26":2,"71":1,"166":2,"171":1,"200":2,"203":1,"295":2,"310":3,"314":1,"341":1,"343":1,"470":1,"536":2,"540":1,"674":1,"697":1,"698":5,"700":1,"702":2,"703":4,"707":2,"710":1,"715":2,"798":1,"819":2,"888":1,"889":1,"905":2,"931":1,"946":1,"987":2,"988":2,"1034":6,"1042":4,"1050":1,"1059":1,"1061":1,"1083":1,"1133":1,"1140":3,"1146":1,"1150":1,"1175":4,"1203":27,"1207":164,"1208":1,"1212":1,"1232":1,"1237":3,"1251":1,"1259":2,"1263":1,"1273":6,"1278":2,"1281":1,"1283":2,"1285":106,"1299":6,"1310":1,"1317":1,"1322":1,"1349":1,"1358":2,"1362":2,"1363":1,"1368":11,"1395":8,"1403":1,"1411":1,"1414":7,"1437":4,"1495":3,"1499":1,"1576":2,"2063":1}}],["dbcontextfactorytests",{"3":{"1199":1,"1207":2}}],["dbcontextfactorytenanttests",{"3":{"1199":1,"1207":3,"1322":1}}],["dbcontextfactorytransactiontests",{"3":{"988":1,"1199":1,"1207":6,"1437":1}}],["dbcontextfactorysaveintegritytests",{"3":{"1199":1,"1207":6,"1285":2,"1437":2}}],["dbcontextfactorymigrationtargettests",{"3":{"1199":1,"1207":4,"1285":2,"1322":2,"1437":2}}],["dbcontextfactoryadditionaltests",{"3":{"1199":1,"1207":4}}],["dbcontextfactorycommitambiguitytests",{"3":{"988":1,"1199":1,"1207":8,"1285":4,"1437":2,"1495":1}}],["dbcontextfactory",{"2":{"46":1,"71":1,"120":1,"170":1,"700":1,"987":1,"991":1,"1133":1,"1135":1,"1253":1,"1320":1,"1839":1,"1850":1,"1851":1},"3":{"0":1,"26":1,"46":1,"71":1,"120":1,"170":1,"536":1,"538":1,"540":2,"698":3,"700":2,"702":1,"703":1,"819":2,"822":1,"987":2,"988":7,"990":2,"991":2,"1133":2,"1135":1,"1136":1,"1175":3,"1199":12,"1203":1,"1207":3,"1252":1,"1253":2,"1259":2,"1263":6,"1270":9,"1274":2,"1278":6,"1285":112,"1299":4,"1317":1,"1320":1,"1322":6,"1358":3,"1435":4,"1437":2,"1495":3,"1576":2,"1839":2,"1848":1,"1850":1,"1851":1}}],["dbcontext",{"0":{"1651":1},"2":{"21":1,"39":1,"365":1,"875":1,"1033":1,"1083":1,"1251":1,"1318":1,"1359":1,"1539":1,"1846":1,"1856":1},"3":{"0":1,"21":1,"39":1,"47":1,"48":1,"59":1,"122":1,"359":1,"365":1,"574":1,"707":1,"875":1,"1033":1,"1083":1,"1192":1,"1201":1,"1202":1,"1203":1,"1212":1,"1213":1,"1237":1,"1251":1,"1259":1,"1263":1,"1265":1,"1270":3,"1271":1,"1273":1,"1278":1,"1285":38,"1289":1,"1299":1,"1309":1,"1318":1,"1321":1,"1322":6,"1336":1,"1338":1,"1349":1,"1351":1,"1358":5,"1359":1,"1362":1,"1368":1,"1395":6,"1414":3,"1435":10,"1436":1,"1437":1,"1446":1,"1466":2,"1496":1,"1539":1,"1649":1,"1650":1,"1652":1,"1655":1,"1657":1,"1699":1,"1846":1,"1856":1,"2022":1,"2098":1,"2141":1}}],["dba",{"3":{"0":1,"1082":1,"1246":1,"2140":1,"2219":1}}],["dbo",{"2":{"719":1,"1445":1,"1468":1,"1472":1},"3":{"0":2,"719":3,"1259":2,"1285":13,"1299":1,"1322":1,"1368":1,"1414":1,"1435":4,"1437":2,"1445":1,"1466":3,"1468":1,"1472":2,"1576":1,"1683":1}}],["dbupdateconcurrencyexception",{"2":{"341":1,"343":1,"344":1,"345":1,"388":1,"393":1,"536":1,"1378":1,"1871":1,"1872":1,"1873":1,"2163":1,"2166":1},"3":{"341":2,"343":1,"344":1,"345":1,"388":1,"393":1,"536":2,"1237":2,"1285":7,"1310":3,"1358":2,"1378":1,"1871":1,"1872":1,"1873":1,"2163":1,"2166":1}}],["dbupdateexception",{"2":{"201":1,"342":1,"343":1,"345":1,"1310":1,"1872":1,"1875":1,"1933":1},"3":{"109":1,"195":1,"201":1,"342":1,"343":1,"345":1,"1259":3,"1285":3,"1288":1,"1299":1,"1308":1,"1310":5,"1358":2,"1368":2,"1395":1,"1872":2,"1875":1,"1933":1}}],["dbupdateexceptionhandler",{"2":{"125":1,"342":1,"343":1,"345":1,"1872":1,"1926":1},"3":{"0":1,"109":2,"125":2,"342":2,"343":2,"345":1,"407":1,"1199":4,"1203":1,"1207":2,"1310":13,"1773":1,"1872":1,"1926":1}}],["dbupdate",{"2":{"1805":1},"3":{"0":1,"1310":2,"1752":1,"1805":1}}],["db",{"1":{"695":1,"696":1,"697":1,"698":1,"699":1,"700":1,"701":1,"702":1,"703":1},"2":{"1476":1,"1588":1,"1596":1,"1718":1},"3":{"0":9,"10":2,"12":1,"18":1,"48":1,"68":1,"93":1,"94":1,"109":1,"117":1,"164":1,"265":2,"273":1,"318":1,"350":1,"545":1,"590":1,"593":1,"695":1,"698":1,"699":1,"700":1,"1033":1,"1133":1,"1134":1,"1175":1,"1212":7,"1237":1,"1247":3,"1274":1,"1279":1,"1285":8,"1322":2,"1325":1,"1338":1,"1349":1,"1358":1,"1379":2,"1414":2,"1435":1,"1437":4,"1440":1,"1445":1,"1454":1,"1455":1,"1466":6,"1468":1,"1470":1,"1472":1,"1473":6,"1474":3,"1475":1,"1476":3,"1477":5,"1481":1,"1489":2,"1490":1,"1493":1,"1524":1,"1525":3,"1533":3,"1542":1,"1543":1,"1548":1,"1576":1,"1588":1,"1590":1,"1595":1,"1596":1,"1598":2,"1599":2,"1610":2,"1613":1,"1617":1,"1650":1,"1651":1,"1652":2,"1653":1,"1656":2,"1665":1,"1676":1,"1699":1,"1706":1,"1708":1,"1709":1,"1718":1,"1751":1,"1752":1,"1762":1,"1773":1,"1774":1,"1814":1,"1853":2,"1854":1,"1855":1,"2007":1,"2032":1,"2033":3,"2035":1,"2095":1,"2212":1,"2218":2,"2219":1,"2229":1,"2231":2}}],["diluted",{"3":{"2057":1}}],["dilutes",{"3":{"1491":1}}],["dichotomy",{"3":{"1954":1}}],["dictating",{"3":{"1416":1}}],["dictations",{"3":{"1416":1}}],["dictation",{"3":{"1401":10,"1416":5}}],["dictated",{"3":{"1322":2,"1415":1}}],["dictates",{"3":{"1299":1,"1338":1}}],["dictionaries",{"3":{"1285":1,"1299":1,"1310":1,"1323":1,"1384":1,"1394":5,"1414":1,"1435":2}}],["dictionary",{"2":{"1881":1},"3":{"0":2,"109":2,"111":1,"157":1,"201":2,"243":1,"248":2,"330":1,"341":1,"359":1,"365":1,"583":2,"585":1,"674":1,"741":1,"881":1,"990":1,"1050":4,"1175":1,"1178":2,"1229":1,"1231":1,"1237":4,"1241":3,"1242":1,"1243":1,"1247":8,"1259":2,"1270":11,"1279":2,"1285":29,"1299":14,"1306":1,"1308":6,"1309":2,"1310":10,"1314":1,"1319":3,"1322":12,"1323":29,"1338":6,"1349":1,"1358":33,"1379":6,"1386":1,"1394":58,"1395":23,"1401":4,"1414":4,"1416":4,"1435":10,"1437":1,"1631":2,"1639":1,"1650":1,"1804":1,"1809":1,"1873":1,"1881":1,"1911":1,"1926":1,"1987":2,"1990":1,"2075":1,"2141":1,"2142":1}}],["dip",{"3":{"1310":3,"1322":3,"1395":1,"1435":2,"1525":1,"1537":2,"1576":2,"1581":1,"1583":1}}],["dirties",{"3":{"1454":2,"1650":1}}],["dirtied",{"3":{"1285":1}}],["dirtying",{"3":{"1454":1}}],["dirty",{"3":{"1265":1,"1270":1,"1285":1,"1323":5,"1383":2,"1394":13,"1395":1,"1435":1,"1444":1,"1449":1,"1454":4,"1460":1,"1488":1,"1495":1,"1525":1,"1560":3,"1571":1,"1572":1,"1576":1,"1585":1,"1590":2,"1595":1,"1870":1,"2044":1}}],["dir",{"2":{"861":1,"863":1,"1455":1},"3":{"861":2,"863":1,"1432":1,"1435":2,"1455":1,"1590":1}}],["directreadsofeitherclocktype",{"3":{"1435":2}}],["directed",{"3":{"1430":1,"1435":3,"1440":1,"1488":1,"1768":1,"2008":1}}],["directsave",{"3":{"1435":1}}],["directsavinghandler",{"3":{"1199":2,"1207":1,"1435":5}}],["directs",{"3":{"31":1,"359":1,"545":1,"605":1,"782":1,"1299":1}}],["directories",{"3":{"0":2,"141":1,"1005":1,"1133":1,"1135":1,"1351":1,"1435":4}}],["directory",{"2":{"53":1,"141":1,"145":1,"146":1,"147":1,"149":1,"152":1,"369":1,"370":2,"371":1,"373":1,"452":1,"468":1,"470":1,"478":1,"526":1,"528":1,"631":1,"632":3,"707":1,"733":1,"869":1,"939":1,"979":1,"980":1,"981":1,"983":1,"984":1,"1069":1,"1074":1,"1076":1,"1214":1,"1444":1,"1452":1,"1453":1,"1454":1,"1458":1,"1459":1,"1463":1,"1476":1,"1490":1,"1647":1,"1648":2,"1654":1,"1655":1,"1683":1,"1684":1,"1699":1,"1702":1,"1727":1,"1737":1,"2016":1,"2040":1,"2041":1,"2086":1,"2096":1,"2111":1},"3":{"0":6,"53":1,"135":5,"138":2,"139":1,"140":1,"141":1,"142":3,"145":2,"146":1,"147":7,"149":1,"150":2,"152":5,"268":1,"369":1,"370":8,"371":2,"373":1,"375":1,"413":1,"451":1,"452":7,"468":2,"470":4,"478":3,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"526":2,"528":1,"545":1,"564":1,"578":3,"591":1,"599":2,"618":1,"621":1,"631":1,"632":5,"665":3,"669":1,"682":1,"685":1,"707":1,"733":1,"810":1,"814":1,"825":4,"861":3,"864":1,"869":1,"881":1,"913":3,"939":1,"954":3,"956":4,"958":4,"959":3,"972":3,"979":5,"980":7,"981":3,"982":1,"983":1,"984":1,"990":1,"1004":1,"1069":2,"1070":1,"1074":4,"1075":1,"1076":1,"1078":4,"1091":3,"1212":1,"1213":4,"1214":2,"1270":1,"1285":1,"1308":1,"1310":1,"1311":3,"1323":2,"1349":2,"1358":2,"1379":1,"1394":2,"1395":1,"1416":11,"1426":1,"1430":1,"1432":1,"1435":37,"1444":1,"1452":3,"1453":6,"1454":4,"1455":4,"1457":1,"1458":3,"1459":1,"1463":3,"1465":1,"1466":2,"1476":2,"1478":2,"1485":1,"1487":1,"1488":2,"1489":2,"1490":1,"1514":1,"1522":1,"1525":5,"1526":1,"1534":3,"1575":2,"1576":6,"1585":3,"1590":8,"1595":1,"1599":3,"1647":1,"1648":3,"1654":1,"1655":3,"1683":1,"1684":1,"1685":1,"1699":1,"1702":1,"1725":1,"1727":1,"1737":1,"1809":1,"2016":1,"2040":1,"2041":1,"2046":1,"2052":1,"2086":1,"2096":1,"2111":1,"2219":1}}],["directional",{"3":{"91":1,"97":1,"98":1,"100":1,"972":1,"974":1,"975":3,"1435":4,"1619":1,"1755":1,"2024":2}}],["direction",{"0":{"1321":1},"3":{"0":2,"141":1,"193":2,"335":1,"401":1,"402":1,"404":1,"486":1,"520":1,"566":1,"691":2,"743":1,"776":2,"813":1,"931":1,"932":1,"1004":1,"1059":1,"1091":2,"1093":1,"1123":1,"1124":1,"1212":1,"1225":1,"1229":1,"1243":1,"1244":1,"1247":3,"1250":1,"1265":1,"1270":2,"1282":1,"1285":7,"1299":2,"1300":2,"1308":1,"1309":2,"1310":2,"1311":1,"1322":1,"1323":8,"1338":1,"1340":2,"1349":2,"1354":2,"1358":35,"1368":1,"1383":1,"1394":5,"1395":7,"1401":1,"1414":4,"1415":3,"1416":2,"1421":1,"1435":16,"1442":1,"1447":1,"1466":1,"1475":3,"1488":1,"1504":1,"1516":1,"1525":1,"1533":1,"1591":1,"1841":1,"1932":1,"1999":1,"2000":1,"2021":1,"2042":1,"2045":1,"2147":2,"2180":1,"2191":1,"2198":1,"2202":1}}],["directions",{"3":{"0":2,"24":1,"170":1,"180":1,"201":1,"235":1,"359":2,"608":1,"627":1,"718":1,"766":1,"838":1,"861":1,"890":1,"931":1,"946":1,"972":1,"997":1,"1091":1,"1134":1,"1211":1,"1212":1,"1233":1,"1257":1,"1285":2,"1299":1,"1310":1,"1311":1,"1322":2,"1323":3,"1349":1,"1358":3,"1390":1,"1391":1,"1394":4,"1395":3,"1401":1,"1414":2,"1416":2,"1420":1,"1430":2,"1434":1,"1435":10,"1437":2,"1452":1,"1454":4,"1464":1,"1466":1,"1475":1,"1480":1,"1488":1,"1491":2,"1576":1,"1662":1,"1669":1,"1798":1,"1853":1,"1909":1,"1956":2,"1980":1,"1999":1,"2023":1,"2043":2,"2044":2,"2045":1,"2048":1,"2141":2,"2181":1}}],["directives",{"3":{"212":1,"214":2,"219":1,"1059":1,"1062":1,"1285":5,"1310":1,"1322":1,"1323":4,"1335":1,"1338":2,"1349":1,"1358":1,"1395":2,"1414":2,"1415":1,"1442":1,"1576":1,"1590":1,"1652":1,"1688":1,"2148":2,"2149":2}}],["directive",{"3":{"0":1,"214":2,"215":1,"219":2,"676":1,"938":1,"1057":1,"1059":1,"1323":13,"1338":2,"1394":5,"1395":1,"1401":1,"1414":2,"1576":1,"1698":1,"1726":1,"2149":3,"2152":2,"2231":1}}],["directapitokenrefreshertests",{"3":{"1199":1,"1207":3,"1323":4}}],["directapitokenrefresher",{"2":{"507":1,"510":1,"511":1,"513":1,"514":1},"3":{"0":1,"507":8,"510":1,"511":1,"513":3,"514":3,"1199":3,"1203":1,"1207":2,"1299":1,"1323":10,"1415":3}}],["directly",{"3":{"0":6,"15":1,"31":2,"59":2,"63":1,"81":1,"94":1,"96":1,"97":1,"98":1,"100":1,"117":1,"166":1,"186":1,"206":1,"230":1,"235":1,"237":2,"241":1,"247":1,"280":1,"281":1,"304":1,"326":1,"341":1,"370":1,"390":1,"418":1,"428":1,"439":1,"497":1,"507":1,"523":1,"535":1,"536":1,"556":1,"566":1,"572":1,"576":1,"583":1,"599":1,"618":1,"649":1,"651":1,"672":1,"674":4,"698":1,"700":1,"703":1,"707":1,"733":1,"751":2,"758":1,"767":1,"782":2,"831":1,"861":1,"881":5,"905":1,"912":1,"921":1,"924":1,"953":1,"954":3,"956":1,"960":1,"964":1,"979":1,"987":1,"988":1,"1004":2,"1019":1,"1054":1,"1066":1,"1068":1,"1100":1,"1107":1,"1110":1,"1116":1,"1129":1,"1134":1,"1168":1,"1170":1,"1184":1,"1211":1,"1212":1,"1214":2,"1221":1,"1229":4,"1237":5,"1246":1,"1247":13,"1248":1,"1259":17,"1263":1,"1269":1,"1270":11,"1271":5,"1273":1,"1274":1,"1278":1,"1279":2,"1285":69,"1292":1,"1296":1,"1299":37,"1300":6,"1308":11,"1309":2,"1310":21,"1311":4,"1319":2,"1322":26,"1323":36,"1324":1,"1329":1,"1338":22,"1349":27,"1350":1,"1352":1,"1354":1,"1355":1,"1358":86,"1361":1,"1368":11,"1373":1,"1379":13,"1382":1,"1383":1,"1394":22,"1395":27,"1398":1,"1399":1,"1401":6,"1405":1,"1407":1,"1414":21,"1415":5,"1416":20,"1426":2,"1429":1,"1431":1,"1433":1,"1435":107,"1437":7,"1440":3,"1442":1,"1444":2,"1445":2,"1446":1,"1448":1,"1452":1,"1453":2,"1454":3,"1455":1,"1456":3,"1459":3,"1463":1,"1464":1,"1465":1,"1466":3,"1471":1,"1472":1,"1474":1,"1478":1,"1479":1,"1485":2,"1487":1,"1489":1,"1490":2,"1495":4,"1500":1,"1525":1,"1526":1,"1533":1,"1539":1,"1540":1,"1545":1,"1546":1,"1577":1,"1591":1,"1595":1,"1630":2,"1640":1,"1647":1,"1650":1,"1652":1,"1655":1,"1665":1,"1670":1,"1684":1,"1685":1,"1697":1,"1700":1,"1701":1,"1729":1,"1748":2,"1764":1,"1765":1,"1771":1,"1782":1,"1788":1,"1789":2,"1803":1,"1807":1,"1814":1,"1815":1,"1820":1,"1841":1,"1843":1,"1856":1,"1862":1,"1873":1,"1889":1,"1914":1,"1919":1,"1922":1,"1945":1,"1953":1,"1961":1,"1991":1,"1994":1,"1999":1,"2000":1,"2022":1,"2024":1,"2029":1,"2040":1,"2042":1,"2055":1,"2068":1,"2070":1,"2073":1,"2075":1,"2086":1,"2090":1,"2125":1,"2154":1,"2155":1,"2164":1,"2166":1,"2231":1,"2238":1}}],["direct",{"3":{"0":9,"27":1,"33":1,"39":1,"41":1,"91":2,"93":3,"98":2,"100":1,"104":1,"137":1,"140":1,"150":1,"230":2,"245":1,"260":1,"330":1,"370":1,"506":1,"507":1,"509":1,"665":1,"698":1,"700":1,"884":1,"913":1,"931":1,"954":1,"956":1,"958":2,"981":1,"986":1,"988":2,"1004":2,"1005":1,"1036":1,"1125":1,"1161":1,"1212":1,"1229":3,"1237":1,"1243":1,"1247":1,"1259":3,"1268":1,"1271":3,"1273":1,"1285":10,"1299":2,"1300":1,"1308":4,"1310":2,"1322":3,"1323":7,"1338":3,"1343":1,"1348":1,"1349":3,"1352":1,"1354":1,"1358":15,"1363":1,"1368":2,"1379":1,"1395":3,"1414":2,"1416":1,"1430":1,"1435":38,"1437":1,"1440":1,"1452":1,"1466":1,"1487":1,"1488":2,"1490":1,"1503":1,"1543":1,"1554":1,"1566":1,"1576":1,"1610":1,"1631":1,"1633":2,"1637":1,"1638":1,"1661":1,"1670":2,"1749":1,"1772":1,"1788":1,"1789":1,"1909":1,"1919":1,"1987":1,"1992":1,"2014":1,"2031":1,"2034":1,"2040":2,"2047":1,"2060":1,"2063":1,"2078":1,"2135":1,"2239":1}}],["dim",{"3":{"691":1}}],["dimensional",{"3":{"1416":2,"1794":1}}],["dimensions",{"3":{"442":1,"766":1,"768":1,"1270":1,"1322":3,"1358":2,"1368":1,"1424":2,"1426":4,"1473":1,"1598":1,"1794":2,"2047":1,"2127":1,"2176":3,"2179":1}}],["dimension",{"3":{"438":1,"442":1,"740":1,"1093":1,"1270":3,"1285":1,"1349":1,"1358":2,"1395":1,"1435":1,"1466":2,"1478":1,"1636":1,"1637":1,"1986":1}}],["dietary",{"3":{"1349":3}}],["die",{"3":{"1270":1,"1310":1,"1322":1,"1454":1,"2002":1,"2069":1}}],["died",{"3":{"565":1,"1299":1,"1322":2,"1401":1,"1409":1,"1414":1,"1435":2,"1440":1,"1728":1,"1908":1}}],["dies",{"3":{"20":1,"707":1,"855":1,"996":2,"1044":1,"1115":1,"1255":1,"1259":1,"1270":2,"1275":1,"1300":1,"1317":1,"1322":5,"1358":3,"1368":1,"1395":1,"1416":1,"1669":1,"1764":1,"1840":1,"2044":1,"2180":1,"2188":1,"2191":1}}],["div",{"2":{"1557":1,"1642":1},"3":{"1323":1,"1395":1,"1416":1,"1557":2}}],["dives",{"0":{"2214":1},"3":{"2203":1,"2225":1,"2235":1,"2244":1}}],["diverted",{"3":{"1437":1,"1495":1}}],["diversity3",{"3":{"1394":1}}],["diverging",{"3":{"785":1,"1310":1,"1394":1}}],["diverged",{"3":{"837":1,"1323":1,"1338":1,"1458":1,"2069":1,"2079":1}}],["divergent",{"3":{"511":1,"642":1,"1285":1,"1300":1,"1401":1,"1415":1,"1568":2}}],["divergence",{"3":{"0":1,"216":1,"290":1,"342":1,"484":1,"549":1,"585":1,"831":1,"840":2,"1042":2,"1044":1,"1270":2,"1285":1,"1322":3,"1323":1,"1326":1,"1338":2,"1358":1,"1395":1,"1414":1,"1435":1,"1437":1,"2151":1,"2187":1,"2191":1}}],["divergences",{"3":{"0":1,"912":1,"1414":1}}],["diverges",{"3":{"325":1,"665":1,"707":1,"766":1,"1116":1,"1322":1,"1323":1,"1395":1,"1435":1,"1454":2,"1527":1,"1569":1,"1578":1}}],["diverge",{"3":{"0":1,"290":1,"609":1,"640":1,"698":1,"766":1,"1034":1,"1058":1,"1270":2,"1271":1,"1285":1,"1323":3,"1349":1,"1358":7,"1378":1,"1394":2,"1395":2,"1414":1,"1435":1,"1439":1,"1453":1,"1466":1,"1837":1,"1922":1,"2042":1,"2112":1,"2192":1}}],["dive",{"0":{"2234":1,"2236":1},"3":{"1194":1,"1358":1,"1778":45,"1782":1,"1789":1,"1820":1,"1827":1,"1913":1,"1919":1,"1934":1,"2029":2,"2159":1,"2231":1}}],["diving",{"3":{"1462":1}}],["dividing",{"3":{"1349":1,"1358":1,"1799":2}}],["divide",{"3":{"1395":1,"1435":1,"1466":1}}],["dividers",{"3":{"1323":1}}],["divider",{"3":{"1323":1}}],["divided",{"3":{"1310":1,"1358":1,"1797":1}}],["divides",{"3":{"591":1,"1310":2,"1358":1,"2046":1}}],["division",{"3":{"423":1,"426":1,"628":1,"1090":1,"1229":1,"1257":1,"1259":2,"1285":3,"1299":1,"1322":1,"1323":1,"1338":1,"1344":1,"1358":1,"1368":1,"1379":1,"1394":4,"1395":1,"1401":2,"1405":1,"1414":2,"1435":1,"1933":1,"2067":1,"2112":1}}],["diamond",{"3":{"1247":1,"1270":1}}],["dialling",{"3":{"1401":1}}],["dialing",{"3":{"1399":1}}],["dialed",{"3":{"1308":1}}],["dialects",{"3":{"1176":1,"1395":1,"1924":1}}],["dialect",{"3":{"1175":4,"1176":2,"1178":1,"1927":2,"1985":1}}],["dials",{"3":{"1300":1,"1338":1,"1443":1}}],["dial",{"3":{"1285":1,"1300":1}}],["dialogs",{"3":{"649":1,"650":1,"1213":1,"1323":5,"1414":1,"1558":1,"1606":2,"1668":1,"1740":2}}],["dialog",{"2":{"1686":1},"3":{"265":1,"650":1,"907":1,"954":1,"955":1,"1323":11,"1394":9,"1395":1,"1401":2,"1416":2,"1431":2,"1435":9,"1487":1,"1495":1,"1557":1,"1606":1,"1607":1,"1668":2,"1670":1,"1686":2,"1740":1,"1741":2,"1749":1,"1759":2}}],["diagram",{"0":{"2039":1},"3":{"122":1,"1262":1,"1270":1,"1323":1,"1495":3,"1501":1,"1525":1,"1590":2,"1619":1,"1626":1,"1755":1,"1760":1,"1792":2,"1820":1,"2038":2,"2048":1,"2098":5,"2104":1,"2107":1,"2171":1}}],["diagrams",{"3":{"121":1,"1501":2,"1516":2,"1561":1,"1570":1,"1625":2,"1859":1,"2108":1}}],["diagnosability",{"3":{"1435":1}}],["diagnosable",{"3":{"0":1,"862":1,"1085":1,"1296":1,"1310":1,"1435":1,"1455":2}}],["diagnosing",{"3":{"1338":1}}],["diagnosis",{"3":{"20":1,"1259":1,"1285":1,"1323":1,"1338":1,"1414":1}}],["diagnosed",{"3":{"401":1,"1323":1,"1338":1,"1435":2}}],["diagnose",{"3":{"24":1,"390":1,"1466":1,"1664":1,"2055":1}}],["diagnostically",{"3":{"1310":1}}],["diagnostics",{"2":{"1229":1,"1270":1,"1323":1,"1338":1,"1414":1},"3":{"39":1,"135":2,"341":1,"395":1,"403":1,"483":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"633":1,"861":1,"863":1,"1229":1,"1247":1,"1259":6,"1270":7,"1285":4,"1299":4,"1310":6,"1322":5,"1323":3,"1332":1,"1338":11,"1358":2,"1395":2,"1401":1,"1414":1,"1416":2,"1426":5,"1435":4,"1455":2,"1489":1,"1639":1,"1669":1,"1957":1}}],["diagnostic",{"2":{"135":1,"490":1,"491":1,"492":1,"493":1,"1525":1},"3":{"0":1,"109":1,"135":2,"265":1,"403":2,"405":1,"406":1,"490":2,"491":2,"492":2,"493":2,"574":1,"633":1,"719":2,"1126":1,"1247":1,"1270":1,"1285":3,"1299":1,"1310":3,"1323":1,"1338":1,"1358":6,"1395":2,"1401":1,"1414":4,"1435":4,"1458":1,"1464":1,"1466":6,"1488":1,"1590":1,"1954":1,"2101":1}}],["digits",{"3":{"972":1,"1018":1,"1237":1,"1285":1,"1292":1,"1299":2,"1304":1,"1308":1,"1310":1,"1323":1,"1346":1,"1349":1,"1352":1,"1358":2,"1368":1,"1394":3,"1395":3,"1416":1,"1630":1,"2197":1}}],["digital",{"3":{"424":1,"426":1,"1310":4}}],["digit",{"3":{"235":1,"972":1,"974":1,"975":1,"1020":1,"1271":2,"1299":5,"1322":1,"1323":4,"1338":1,"1358":2,"1416":1,"1426":2,"1442":1,"1464":1,"1640":1,"1828":1}}],["digests",{"3":{"0":1,"877":1,"906":1,"1299":6,"1981":1,"2234":1,"2236":1}}],["digest",{"3":{"0":5,"373":1,"867":1,"868":1,"870":2,"872":1,"873":2,"876":1,"877":1,"905":4,"906":1,"907":1,"945":1,"946":6,"948":1,"1212":3,"1282":1,"1285":4,"1289":1,"1299":15,"1435":4,"1437":2,"1444":8,"1449":1,"1454":1,"1457":2,"1666":2,"1671":1,"1899":1,"1901":4,"1979":1,"1981":4,"1984":1}}],["didactic",{"3":{"635":1}}],["didn",{"3":{"60":1,"61":1,"1555":2}}],["did",{"3":{"0":1,"21":1,"24":2,"42":1,"96":1,"115":1,"126":1,"141":1,"149":1,"157":1,"158":1,"160":1,"175":1,"201":1,"202":1,"203":1,"230":1,"242":1,"243":1,"245":1,"249":2,"256":2,"258":1,"259":1,"260":1,"283":2,"322":1,"333":1,"335":1,"341":1,"342":1,"343":1,"350":1,"353":2,"363":1,"365":2,"374":1,"390":1,"402":1,"433":1,"475":1,"476":1,"477":1,"478":1,"486":2,"487":1,"488":2,"489":1,"490":2,"491":1,"492":1,"511":1,"517":1,"524":1,"536":1,"539":1,"549":1,"550":1,"551":1,"583":1,"586":1,"626":1,"628":1,"635":1,"638":1,"642":1,"648":1,"675":1,"681":1,"689":1,"700":1,"702":1,"709":2,"724":1,"725":1,"726":1,"733":1,"735":1,"740":1,"743":1,"789":1,"798":1,"812":1,"832":1,"867":1,"877":1,"889":1,"891":1,"905":1,"907":1,"908":1,"926":3,"938":1,"988":3,"1006":1,"1051":3,"1066":1,"1067":1,"1069":1,"1091":1,"1092":1,"1124":1,"1142":1,"1212":1,"1232":1,"1237":1,"1241":1,"1244":1,"1247":3,"1250":1,"1258":1,"1259":6,"1265":1,"1266":1,"1270":4,"1275":1,"1277":1,"1285":18,"1292":1,"1299":4,"1300":3,"1308":4,"1310":5,"1311":1,"1317":2,"1322":7,"1323":15,"1327":2,"1338":6,"1343":1,"1349":4,"1358":6,"1365":1,"1366":1,"1368":2,"1379":1,"1390":1,"1394":11,"1395":11,"1401":2,"1407":1,"1414":9,"1416":11,"1417":2,"1424":1,"1426":3,"1435":5,"1437":1,"1441":1,"1442":2,"1448":2,"1450":1,"1454":6,"1466":4,"1473":1,"1474":1,"1475":1,"1477":1,"1489":1,"1491":1,"1495":2,"1525":1,"1683":1,"1685":1,"1696":1,"1727":1,"1729":1,"1779":2,"1800":1,"1808":1,"1839":1,"1844":1,"1862":1,"1868":1,"1875":1,"1891":1,"1898":1,"1899":1,"1905":1,"1909":1,"1917":2,"1921":1,"1932":1,"1939":1,"1955":1,"1958":1,"1964":1,"1996":1,"2000":1,"2011":1,"2036":1,"2061":1,"2080":1,"2094":1,"2097":1,"2123":2,"2147":1,"2155":1,"2159":1,"2165":1,"2169":1,"2176":1,"2179":2,"2185":1,"2187":1,"2196":2,"2198":1}}],["di",{"0":{"1367":1,"1823":1,"2094":1},"1":{"977":1,"978":1,"979":1,"980":1,"981":1,"982":1,"983":1,"984":1},"3":{"0":6,"27":1,"119":1,"155":1,"157":2,"200":1,"241":1,"258":1,"341":1,"343":1,"359":1,"395":1,"401":3,"435":1,"483":1,"536":1,"550":1,"583":1,"632":1,"649":1,"651":1,"725":1,"797":1,"798":1,"802":1,"827":1,"920":1,"926":1,"977":1,"979":1,"980":3,"983":2,"997":1,"1042":1,"1051":1,"1059":2,"1175":1,"1176":1,"1178":2,"1192":3,"1202":2,"1203":2,"1212":2,"1213":1,"1214":1,"1237":1,"1244":1,"1247":3,"1254":1,"1257":1,"1259":10,"1265":2,"1269":1,"1270":16,"1271":1,"1273":1,"1274":1,"1275":2,"1282":1,"1283":1,"1285":34,"1293":1,"1299":13,"1300":3,"1301":1,"1308":21,"1309":4,"1310":28,"1311":2,"1312":1,"1313":1,"1314":1,"1315":1,"1322":13,"1323":28,"1331":1,"1332":1,"1338":9,"1349":5,"1351":1,"1354":2,"1358":25,"1359":1,"1367":1,"1368":4,"1369":1,"1379":8,"1394":7,"1395":22,"1398":1,"1401":7,"1412":1,"1414":19,"1415":7,"1416":19,"1417":1,"1420":1,"1421":1,"1426":6,"1428":1,"1430":1,"1435":24,"1436":1,"1437":7,"1487":1,"1488":1,"1495":2,"1512":1,"1525":1,"1537":1,"1539":1,"1576":1,"1581":1,"1584":2,"1590":1,"1637":1,"1649":3,"1650":3,"1652":2,"1655":1,"1657":1,"1660":1,"1664":1,"1667":1,"1700":1,"1729":1,"1747":1,"1764":1,"1790":1,"1825":2,"1826":2,"1829":1,"1873":1,"1880":1,"1885":1,"1907":1,"1908":1,"1914":1,"1947":1,"1955":1,"2015":1,"2073":1,"2084":1,"2094":1,"2097":3,"2114":1,"2141":1,"2162":2,"2168":1,"2179":1,"2187":1,"2197":1,"2201":2,"2231":2}}],["diffing",{"3":{"1435":1,"2078":1}}],["diffed",{"3":{"721":1,"767":1,"1310":1,"1414":1,"1929":1}}],["differed",{"3":{"724":1,"1437":1}}],["differences",{"3":{"0":2,"219":1,"454":1,"624":1,"629":1,"636":1,"769":1,"868":1,"1034":1,"1126":1,"1270":1,"1285":2,"1308":1,"1309":1,"1322":1,"1323":1,"1349":1,"1372":1,"1379":1,"1394":5,"1395":1,"1398":1,"1416":1,"1435":2,"1466":2}}],["difference",{"3":{"0":2,"24":1,"175":1,"218":1,"245":1,"266":1,"388":1,"425":1,"497":1,"499":1,"522":1,"539":1,"573":1,"577":1,"584":1,"592":1,"599":1,"603":1,"626":1,"633":2,"639":1,"641":2,"716":1,"723":1,"726":1,"800":1,"820":2,"825":1,"827":1,"831":1,"838":1,"839":1,"840":1,"996":1,"1034":1,"1040":1,"1051":1,"1052":1,"1099":1,"1132":1,"1150":1,"1212":1,"1229":1,"1237":2,"1247":3,"1257":1,"1259":1,"1263":1,"1270":3,"1271":1,"1285":6,"1299":4,"1300":1,"1306":1,"1308":1,"1310":2,"1322":2,"1323":8,"1338":3,"1341":1,"1349":7,"1358":31,"1361":1,"1368":2,"1379":4,"1382":1,"1394":6,"1395":13,"1401":3,"1414":6,"1415":2,"1416":4,"1435":16,"1454":1,"1458":1,"1488":1,"1496":1,"1536":1,"1630":1,"1684":1,"1726":1,"1727":1,"1781":1,"1816":1,"1823":1,"1860":1,"1878":1,"1925":1,"1929":1,"1942":2,"2006":1,"2038":1,"2079":1,"2104":1,"2109":1,"2118":1,"2132":1,"2173":1,"2183":1,"2186":1,"2195":1,"2199":2}}],["differentcurrencies",{"2":{"1584":1},"3":{"1584":2}}],["differentiation",{"3":{"1395":1}}],["differently",{"3":{"153":1,"160":1,"303":1,"341":1,"447":1,"520":1,"574":1,"603":1,"651":1,"819":1,"828":1,"829":1,"840":1,"854":1,"906":1,"924":1,"933":1,"996":1,"1060":1,"1117":1,"1124":1,"1174":1,"1176":1,"1247":1,"1259":1,"1285":4,"1299":8,"1300":3,"1310":1,"1322":4,"1323":4,"1338":2,"1342":1,"1349":1,"1358":5,"1368":1,"1379":1,"1395":1,"1414":1,"1434":1,"1435":5,"1454":1,"1726":1,"1729":1,"1747":1,"1818":1,"1873":1,"1908":1,"2067":1,"2129":1,"2197":1,"2227":1}}],["different",{"0":{"719":1,"1228":1,"1294":1,"1781":1},"3":{"0":11,"10":1,"24":1,"26":1,"68":1,"77":1,"91":1,"95":1,"97":1,"135":2,"136":1,"137":1,"147":1,"156":1,"157":1,"159":3,"161":1,"166":2,"177":1,"186":1,"195":1,"201":1,"202":1,"216":1,"217":1,"225":1,"227":1,"236":1,"242":3,"244":3,"248":1,"255":3,"256":1,"266":1,"274":1,"283":1,"299":1,"301":1,"317":2,"318":2,"320":1,"340":1,"350":1,"353":1,"359":2,"388":1,"390":1,"401":1,"447":2,"457":1,"465":1,"474":1,"483":1,"500":1,"506":2,"509":1,"528":1,"530":1,"537":1,"556":1,"574":1,"577":1,"608":1,"609":2,"617":2,"619":1,"626":1,"634":2,"639":1,"640":1,"650":1,"651":1,"666":2,"684":1,"689":1,"690":1,"692":1,"697":1,"698":1,"699":1,"702":1,"707":1,"713":1,"714":1,"715":1,"718":1,"726":2,"732":2,"733":1,"740":1,"741":2,"749":1,"758":1,"773":1,"774":1,"790":1,"791":1,"799":1,"809":1,"820":3,"821":1,"827":4,"829":4,"830":1,"832":2,"856":1,"869":1,"888":1,"890":1,"891":1,"899":1,"956":1,"957":1,"974":1,"980":2,"989":1,"995":2,"997":1,"1011":1,"1014":1,"1018":3,"1021":1,"1028":1,"1033":1,"1035":1,"1043":4,"1049":1,"1067":1,"1079":1,"1091":2,"1092":1,"1099":1,"1116":1,"1124":1,"1125":1,"1126":2,"1134":1,"1144":1,"1183":1,"1212":1,"1229":1,"1232":1,"1236":1,"1237":3,"1239":1,"1240":1,"1242":1,"1247":10,"1259":8,"1261":1,"1267":3,"1270":9,"1271":2,"1273":1,"1276":1,"1279":1,"1280":1,"1283":1,"1285":35,"1289":1,"1296":1,"1299":17,"1300":7,"1301":1,"1305":1,"1306":1,"1308":4,"1309":7,"1310":13,"1322":21,"1323":33,"1326":2,"1338":20,"1342":2,"1344":1,"1349":11,"1350":1,"1352":1,"1354":1,"1358":72,"1361":1,"1367":1,"1368":5,"1371":1,"1374":1,"1378":1,"1379":12,"1388":2,"1390":1,"1394":21,"1395":41,"1401":5,"1405":1,"1408":1,"1414":21,"1415":4,"1416":14,"1418":1,"1422":2,"1426":4,"1429":1,"1430":1,"1434":3,"1435":61,"1437":2,"1440":1,"1441":1,"1442":1,"1445":1,"1446":1,"1454":4,"1457":1,"1458":1,"1459":1,"1461":1,"1466":2,"1470":1,"1471":1,"1474":2,"1484":1,"1485":1,"1487":3,"1488":2,"1491":3,"1495":6,"1497":2,"1535":1,"1536":1,"1569":1,"1626":1,"1629":3,"1637":1,"1638":5,"1640":4,"1700":1,"1718":1,"1780":1,"1793":1,"1796":1,"1803":2,"1815":2,"1819":1,"1821":1,"1824":1,"1827":1,"1831":1,"1838":1,"1848":4,"1850":1,"1852":1,"1857":1,"1859":1,"1861":2,"1862":1,"1863":1,"1864":1,"1865":1,"1869":1,"1886":1,"1887":3,"1889":2,"1895":1,"1901":1,"1907":4,"1908":2,"1910":3,"1911":1,"1915":1,"1921":2,"1922":2,"1924":2,"1933":2,"1949":1,"1951":1,"1952":2,"1958":2,"1959":1,"1961":1,"1962":1,"1963":1,"1976":1,"1981":1,"1985":2,"1991":2,"1993":2,"1994":2,"1996":1,"1998":3,"1999":2,"2000":2,"2023":1,"2032":1,"2035":1,"2036":1,"2040":2,"2041":1,"2042":2,"2046":1,"2062":1,"2068":2,"2069":1,"2072":1,"2076":1,"2081":2,"2085":1,"2108":3,"2109":1,"2112":4,"2113":2,"2129":2,"2132":1,"2133":1,"2141":2,"2143":1,"2151":1,"2155":1,"2163":1,"2166":1,"2191":4,"2192":3,"2198":1}}],["differ",{"3":{"0":2,"32":1,"97":1,"202":1,"244":1,"282":1,"318":1,"359":1,"491":1,"558":2,"626":1,"633":1,"640":1,"641":1,"642":1,"667":1,"689":1,"691":1,"764":1,"766":1,"827":1,"828":1,"829":1,"831":1,"877":1,"889":1,"973":1,"980":1,"1035":1,"1042":1,"1058":1,"1116":1,"1124":1,"1127":1,"1132":1,"1175":1,"1212":1,"1229":1,"1247":3,"1249":1,"1263":1,"1270":2,"1271":1,"1285":2,"1299":2,"1300":1,"1308":2,"1310":4,"1322":6,"1323":4,"1338":5,"1349":1,"1358":6,"1368":1,"1391":1,"1394":6,"1395":5,"1401":1,"1414":2,"1415":1,"1416":5,"1435":16,"1446":1,"1465":1,"1466":3,"1488":1,"1495":2,"1666":1,"1685":1,"1711":1,"1860":1,"1946":1,"1998":1,"2077":1,"2187":1,"2229":1}}],["differs",{"3":{"0":1,"27":1,"30":1,"62":1,"91":1,"93":1,"126":1,"195":1,"452":1,"472":1,"585":1,"618":1,"640":1,"668":2,"698":1,"766":1,"783":1,"819":1,"828":2,"831":1,"916":1,"982":1,"1035":1,"1037":1,"1212":1,"1247":2,"1249":1,"1259":1,"1262":1,"1270":1,"1271":1,"1285":8,"1294":1,"1299":2,"1308":1,"1309":2,"1310":4,"1319":1,"1322":2,"1323":9,"1337":1,"1338":2,"1349":2,"1358":7,"1368":1,"1379":3,"1394":8,"1395":19,"1398":1,"1401":1,"1414":7,"1415":2,"1416":2,"1426":2,"1428":1,"1435":9,"1440":1,"1441":1,"1446":1,"1454":1,"1455":1,"1458":1,"1466":1,"1474":1,"1484":1,"1485":1,"1489":1,"1640":1,"1655":1,"1768":3,"1790":1,"1825":1,"1860":1,"1921":1,"1933":1,"1975":1,"1976":1,"1995":1,"2031":1,"2056":1,"2075":1,"2077":2,"2125":1}}],["differing",{"3":{"0":1,"1034":1,"1212":1,"1237":1,"1299":1,"1310":1,"1322":3,"1323":2,"1349":2,"1358":1,"1394":2,"1395":1,"1414":1,"1435":3,"1901":1}}],["diffable",{"3":{"610":1,"708":1}}],["diffs",{"3":{"0":1,"1212":2,"1237":1,"1274":1,"1285":2,"1299":1,"1430":1,"1435":1,"1437":2,"1454":1,"1491":1,"1533":1,"1576":1,"1662":1,"1928":2,"1929":2,"2043":1,"2044":1,"2058":1,"2078":1,"2231":1}}],["diff",{"2":{"452":1,"566":1},"3":{"0":6,"135":1,"160":1,"451":2,"452":1,"562":1,"564":2,"565":1,"566":7,"567":1,"572":1,"573":1,"592":1,"608":1,"619":1,"714":2,"715":2,"716":1,"750":1,"766":2,"838":1,"840":2,"863":1,"964":1,"972":1,"981":2,"982":2,"1005":1,"1019":2,"1020":1,"1177":1,"1178":1,"1270":1,"1285":5,"1299":2,"1322":1,"1323":1,"1358":2,"1379":1,"1395":1,"1426":1,"1435":15,"1444":1,"1452":2,"1454":12,"1458":1,"1462":3,"1485":2,"1491":3,"1495":5,"1523":3,"1525":1,"1529":1,"1530":4,"1531":1,"1545":1,"1576":2,"1585":1,"1590":1,"1660":1,"1672":1,"1683":1,"1728":1,"1824":1,"1838":1,"1875":1,"2042":1,"2046":1,"2157":1,"2194":1,"2199":1}}],["disguise",{"3":{"2127":1}}],["disguised",{"3":{"1394":1}}],["dissolves",{"3":{"1954":1}}],["dissolved",{"3":{"130":1,"135":1,"139":1,"1004":1,"1435":1,"1495":1}}],["dismissquestionasync",{"3":{"1401":1}}],["dismissasync",{"3":{"1395":2,"1401":1}}],["dismissals",{"3":{"1416":2}}],["dismissal",{"3":{"1323":1,"1398":1,"1401":3,"1416":1}}],["dismiss",{"3":{"1395":2,"1396":1,"1397":1,"1401":15,"1414":1,"1525":1}}],["dismisses",{"3":{"1323":1,"1631":1}}],["dismissed",{"3":{"1323":4,"1394":1,"1395":1,"1397":1,"1398":1,"1400":1,"1401":17,"1416":2,"1629":1,"1631":1}}],["dismissing",{"3":{"649":1,"1323":2,"1401":1,"1631":1}}],["disjunction",{"3":{"1323":1}}],["disjunctive",{"3":{"1247":1}}],["disjoint",{"3":{"0":2,"241":1,"247":1,"253":1,"731":1,"733":3,"734":1,"736":1,"737":1,"827":1,"1212":1,"1285":1,"1299":1,"1300":3,"1310":1,"1323":1,"1358":1,"1378":1,"1435":1,"1437":1,"1667":1,"1915":1,"2231":1}}],["disqualifies",{"3":{"1244":1}}],["disqualify",{"3":{"335":1,"336":1,"1244":2,"1247":3,"1270":1,"1308":1,"1309":1}}],["disqualifying",{"3":{"335":1,"1244":1,"1247":1}}],["disregard",{"3":{"1091":1,"1426":1}}],["disincentive",{"3":{"692":1}}],["disk",{"3":{"401":1,"451":1,"1124":1,"1163":1,"1299":1,"1323":1,"1338":1,"1368":1,"1415":2,"1416":1,"1426":1,"1434":1,"1435":12,"1452":1,"1467":1,"1485":1,"1495":2,"2139":2,"2143":1,"2145":1}}],["disabilities",{"3":{"1628":1}}],["disabling",{"0":{"1881":1},"3":{"0":1,"557":1,"1259":1,"1299":5,"1310":2,"1322":3,"1338":2,"1395":1,"1432":1,"1435":1,"1464":1,"1466":1,"1489":1,"1723":1}}],["disablement",{"3":{"1322":1,"2165":1}}],["disableantiforgery",{"3":{"1299":1}}],["disableasync",{"3":{"1292":1,"1299":1,"1322":2}}],["disableaspnetcoremetrics",{"2":{"397":1},"3":{"0":1,"395":1,"397":2,"409":1}}],["disabletwofactorhandlerbase",{"2":{"1321":1,"2197":1},"3":{"1199":2,"1203":1,"1207":2,"1299":2,"1321":3,"1322":5,"1495":1,"2197":1}}],["disablelocalcacheread",{"3":{"733":2,"1300":1}}],["disablelocalcachewrite",{"2":{"733":1},"3":{"733":2,"1300":1}}],["disableunderlyingdata",{"2":{"733":1,"734":1,"1300":1},"3":{"733":1,"734":1,"1300":1}}],["disablehealthchecks",{"2":{"245":1,"1331":1},"3":{"245":1,"1331":1,"1338":4,"1441":1}}],["disablehttpclientmetrics=true",{"3":{"1442":1,"1445":1,"1676":1}}],["disablehttpclientmetrics",{"2":{"1475":1,"1667":1},"3":{"0":1,"395":1,"397":1,"407":1,"1332":1,"1338":1,"1442":1,"1466":1,"1475":1,"1576":1,"1667":1,"2009":1,"2155":1,"2159":1}}],["disable",{"2":{"1551":1,"1844":1},"3":{"0":2,"19":1,"39":1,"207":1,"259":1,"388":1,"600":1,"774":1,"789":1,"907":1,"1059":1,"1060":1,"1212":1,"1237":4,"1285":3,"1299":11,"1300":1,"1308":3,"1310":6,"1322":4,"1323":2,"1338":7,"1358":1,"1378":1,"1401":1,"1414":1,"1416":3,"1435":1,"1454":1,"1465":1,"1466":1,"1495":1,"1523":1,"1551":1,"1595":1,"1844":1,"1881":1,"1885":1,"1968":1,"2064":1,"2093":1,"2107":1,"2165":1,"2168":1,"2198":1,"2231":1}}],["disablescrolltracking",{"3":{"1323":1}}],["disables",{"3":{"0":1,"60":1,"236":1,"481":1,"583":1,"618":1,"620":1,"690":1,"827":1,"905":1,"917":1,"1109":1,"1211":1,"1256":1,"1259":1,"1299":1,"1308":1,"1310":1,"1313":1,"1322":3,"1323":1,"1338":3,"1394":1,"1401":4,"1414":1,"1435":1,"1453":1,"1454":1,"1466":3,"1487":1,"1590":1,"1630":1,"1638":1,"1877":1,"1884":1,"2056":1,"2202":1}}],["disableruntimemetrics=true",{"3":{"1442":1,"1445":1,"1676":1}}],["disableruntimemetrics",{"2":{"1475":1,"1667":1},"3":{"0":1,"395":1,"397":1,"1332":1,"1338":1,"1442":1,"1466":1,"1475":1,"1576":1,"1667":1,"2009":1,"2155":1,"2159":1}}],["disabledstubhook",{"3":{"1435":1}}],["disabledsessionbookmarkvalidationservice",{"2":{"583":1,"1313":1,"1348":1,"1376":1},"3":{"583":2,"585":1,"1199":2,"1203":1,"1207":2,"1313":1,"1348":3,"1349":11,"1376":1,"1379":3,"1414":1}}],["disabled=",{"3":{"1323":1}}],["disabledmodulenames",{"2":{"1314":1,"1881":1},"3":{"1314":1,"1322":2,"1881":1}}],["disabledfakeexportservice",{"3":{"1199":3,"1207":1,"1435":6}}],["disabledfeaturehandlertests",{"3":{"1199":1,"1207":2}}],["disabledfeaturehandler",{"2":{"306":1,"1927":1,"2136":1,"2138":1},"3":{"0":1,"300":2,"306":2,"1199":4,"1203":1,"1207":2,"1310":14,"1395":2,"1927":1,"2136":3,"2138":1}}],["disabledusernotificationexportservice",{"2":{"1307":1},"3":{"585":1,"1199":3,"1203":1,"1207":2,"1307":1,"1308":9,"1414":1,"1437":1}}],["disableduserengagementexportservice",{"3":{"585":1,"1199":2,"1203":1,"1207":2,"1395":9}}],["disabledusersalesexportservice",{"2":{"583":1},"3":{"583":2,"585":1}}],["disabledattendeequeryservicetests",{"3":{"1199":1,"1207":2,"1486":1}}],["disabledattendeequeryservice",{"2":{"586":1,"1411":1},"3":{"585":1,"586":1,"1199":3,"1203":1,"1207":2,"1349":1,"1411":2,"1414":9}}],["disabledbookmarkcountservicetests",{"3":{"1199":1,"1207":2,"1486":1}}],["disabledbookmarkcountservice",{"3":{"585":1,"1199":3,"1203":1,"1207":2,"1322":1,"1349":1,"1358":3,"1395":11,"1414":1}}],["disabledcustomerservice",{"3":{"583":2}}],["disabledproductvariantservice",{"2":{"583":1,"1750":1,"1771":1},"3":{"583":2,"1750":1,"1771":1}}],["disabledeventlivevalidationservicetests",{"3":{"1199":1,"1207":2,"1436":1}}],["disabledeventlivevalidationservice",{"2":{"583":1,"1313":1,"1348":1,"1376":1},"3":{"583":2,"585":1,"1199":3,"1203":1,"1207":2,"1313":1,"1348":3,"1349":19,"1358":1,"1376":1,"1379":3,"1495":1}}],["disabled",{"0":{"1411":1,"2136":1,"2195":1},"2":{"300":1,"306":1},"3":{"0":5,"15":1,"31":1,"53":3,"59":2,"63":2,"124":1,"299":1,"300":3,"301":1,"302":1,"304":2,"306":3,"397":1,"482":1,"556":1,"565":1,"582":2,"583":10,"584":1,"585":11,"587":2,"607":1,"725":1,"729":1,"789":1,"790":1,"792":1,"827":1,"914":1,"931":2,"932":1,"1018":2,"1060":1,"1093":1,"1212":2,"1257":1,"1258":1,"1259":3,"1263":3,"1270":11,"1283":1,"1285":8,"1287":1,"1299":2,"1307":1,"1308":11,"1310":22,"1311":2,"1312":1,"1313":3,"1314":3,"1317":1,"1319":2,"1322":23,"1323":6,"1338":5,"1340":2,"1348":2,"1349":12,"1358":3,"1365":1,"1368":3,"1377":1,"1379":5,"1390":1,"1394":8,"1395":23,"1397":1,"1401":5,"1411":1,"1412":1,"1414":5,"1416":1,"1419":1,"1420":1,"1426":2,"1430":1,"1435":13,"1436":1,"1437":1,"1454":1,"1458":1,"1464":1,"1466":2,"1468":1,"1469":1,"1471":1,"1473":1,"1486":3,"1487":1,"1488":1,"1503":1,"1507":1,"1525":2,"1534":1,"1550":1,"1551":1,"1560":2,"1576":1,"1625":2,"1652":1,"1660":1,"1661":2,"1665":3,"1670":1,"1719":1,"1750":1,"1769":1,"1771":1,"1789":2,"1820":1,"1841":1,"1881":6,"1883":1,"1885":1,"1895":1,"1927":2,"2018":2,"2050":1,"2093":1,"2112":1,"2133":1,"2134":1,"2135":3,"2136":2,"2137":1,"2138":4,"2155":1,"2165":1,"2172":1,"2179":2,"2195":1,"2202":1,"2231":2}}],["disallowed",{"3":{"1358":2,"1422":1,"2174":1}}],["disambiguated",{"3":{"1415":1}}],["disambiguates",{"3":{"1395":1}}],["disambiguate",{"3":{"1338":1,"1414":3,"1435":1}}],["disambiguation",{"3":{"1338":2,"1435":2,"1495":1}}],["disarm",{"3":{"1299":2}}],["disarmed",{"3":{"1299":1}}],["disarms",{"3":{"819":1,"905":1,"1299":1,"1842":1}}],["disappearance",{"3":{"1416":1}}],["disappears",{"3":{"818":1,"840":1,"1247":1,"1285":1,"1323":1,"1338":1,"1349":1,"1394":1,"1435":4,"1441":1,"1466":1,"1726":1,"1857":1,"1901":1,"1993":1,"2075":1}}],["disappear",{"3":{"303":1,"1247":1,"1308":1,"1366":1,"1432":1,"1435":1,"1437":1,"2192":1}}],["disappearing",{"3":{"137":1,"1416":1,"1435":1}}],["disappeared",{"3":{"135":1,"454":1,"1435":1,"1473":1,"1672":1}}],["disaster",{"0":{"1473":1},"3":{"68":1,"601":1,"602":1,"608":1,"609":1,"614":1,"971":1,"972":1,"1192":1,"1435":1,"1437":1,"1465":1,"1466":4,"1469":1,"1470":3,"1471":3,"1473":35,"1474":1,"1475":2,"1477":1,"1481":3,"1482":3,"1483":2,"1495":1,"1524":2,"1525":1,"1533":2,"1534":1,"1565":2,"1589":1,"1590":2,"1598":1,"1705":1,"1706":1,"1708":1,"1851":1,"2032":1,"2033":1,"2042":1}}],["disagreeing",{"3":{"940":1,"1270":1,"1308":1,"1323":2,"1347":1,"1915":1,"2010":1}}],["disagrees",{"3":{"890":1,"905":1,"1093":1,"1269":1,"1285":2,"1299":1,"1308":1,"1395":1,"1435":1,"1452":1,"1495":1,"1560":1,"2045":1}}],["disagree",{"3":{"0":1,"136":1,"530":1,"556":1,"617":1,"742":1,"811":1,"827":1,"889":1,"905":1,"926":1,"1026":1,"1034":1,"1079":1,"1142":1,"1178":1,"1190":1,"1241":1,"1247":2,"1251":1,"1270":2,"1271":2,"1280":1,"1285":5,"1287":1,"1299":9,"1308":1,"1310":2,"1321":1,"1322":2,"1323":3,"1349":2,"1351":1,"1358":11,"1365":1,"1368":1,"1394":3,"1395":5,"1401":6,"1408":1,"1414":5,"1416":1,"1435":5,"1437":1,"1487":1,"1647":1,"1694":1,"1721":1,"1728":1,"1766":1,"1923":1,"1961":1,"2023":1,"2072":1}}],["disagreed",{"3":{"0":1,"335":1,"837":1,"1322":1,"1338":1,"1358":1,"1414":1}}],["disagreement",{"3":{"0":1,"691":1,"1266":1,"1270":4,"1299":1,"1721":1,"1728":1}}],["disclaims",{"3":{"1590":1}}],["discloses",{"3":{"1247":1,"1640":1,"1764":1}}],["disclose",{"3":{"856":1,"1299":1}}],["disclosed",{"3":{"854":1,"1395":1}}],["disclosure",{"3":{"388":1,"1191":1,"1200":1,"1270":1,"1310":1,"1358":2,"1395":5,"1921":1}}],["discharges",{"3":{"1394":1}}],["discharge",{"3":{"1322":1}}],["discharged",{"3":{"0":1,"1010":2,"1394":1}}],["discussion",{"3":{"1349":1,"1358":1,"1466":1}}],["discuss",{"3":{"1310":1}}],["discussed",{"3":{"256":1,"1394":1}}],["discrete",{"3":{"1379":1}}],["discrepancy",{"3":{"1190":1}}],["discriminating",{"3":{"1435":2}}],["discrimination",{"3":{"100":1,"1435":13,"1630":1}}],["discriminate",{"3":{"1358":1}}],["discriminates",{"3":{"1270":2,"1435":4}}],["discriminated",{"3":{"920":1,"926":1,"1265":1,"1270":4,"1322":1,"1395":1,"1414":1,"1958":1}}],["discriminators",{"3":{"1349":1,"1368":1}}],["discriminator",{"1":{"779":1,"780":1,"781":1,"782":1,"783":1,"784":1,"785":1,"786":1},"3":{"0":3,"665":4,"667":2,"669":1,"697":1,"733":1,"779":1,"780":1,"782":10,"783":1,"784":1,"786":1,"923":1,"926":3,"963":1,"964":2,"968":1,"1212":2,"1233":1,"1237":2,"1270":1,"1285":2,"1300":1,"1308":1,"1335":1,"1338":1,"1349":7,"1354":1,"1358":5,"1368":1,"1394":1,"1395":16,"1397":1,"1401":2,"1768":1,"1969":1,"1971":1,"2231":2}}],["disciplines",{"3":{"1395":1}}],["disciplined",{"3":{"537":1,"1285":1,"1416":1,"1525":1,"2160":1}}],["discipline",{"0":{"1221":1,"1918":1},"1":{"2069":1,"2070":1,"2071":1,"2072":1,"2073":1,"2074":1,"2075":1,"2076":1,"2077":1,"2078":1,"2079":1},"3":{"0":1,"41":1,"69":1,"79":1,"80":1,"133":1,"135":1,"136":1,"148":1,"162":1,"196":1,"342":1,"346":1,"370":1,"376":1,"398":2,"414":1,"415":1,"449":1,"472":1,"537":1,"549":1,"565":1,"568":1,"592":1,"610":1,"620":1,"622":1,"658":1,"676":1,"700":1,"702":1,"729":1,"750":1,"801":1,"839":1,"842":1,"844":1,"845":2,"847":1,"863":1,"865":1,"882":1,"890":1,"955":1,"990":1,"1012":1,"1044":1,"1076":1,"1223":1,"1229":4,"1237":2,"1254":1,"1259":1,"1270":1,"1285":5,"1299":4,"1302":1,"1310":7,"1316":1,"1319":2,"1322":3,"1323":3,"1338":1,"1345":1,"1349":2,"1351":1,"1358":13,"1368":5,"1373":1,"1383":1,"1385":1,"1394":4,"1395":12,"1397":1,"1401":2,"1414":4,"1416":5,"1435":39,"1454":2,"1466":2,"1525":1,"1537":1,"1554":1,"1574":1,"1575":1,"1576":2,"1670":1,"1794":1,"1804":1,"1832":1,"1833":1,"1850":1,"1852":1,"1858":1,"1874":1,"1884":1,"1888":2,"1890":3,"1909":1,"1923":2,"1934":1,"1935":1,"1940":1,"1943":1,"1948":1,"1953":1,"1971":1,"2033":1,"2037":1,"2043":1,"2068":1,"2069":2,"2097":1,"2106":1,"2119":1,"2130":1,"2155":1,"2158":1,"2163":1,"2189":1,"2191":1,"2209":1,"2211":1}}],["discourage",{"3":{"1569":1}}],["discouraging",{"3":{"530":1}}],["discounted",{"3":{"1028":1,"1741":1,"1747":1,"1748":1,"1749":2,"1763":2,"1764":1,"1766":1,"1767":1,"1772":1,"1775":1}}],["discount",{"3":{"0":2,"387":1,"390":2,"392":2,"1026":8,"1027":2,"1028":5,"1029":1,"1030":3,"1160":1,"1212":1,"1745":6,"1747":11,"1749":5,"1763":4,"1764":1,"1766":14,"1767":1,"1768":2,"1772":1,"1831":1}}],["discounting",{"3":{"0":1,"1026":1,"1766":1}}],["discounts",{"3":{"0":1,"1767":1}}],["disconnection",{"3":{"1639":1}}],["disconnects",{"3":{"1323":2}}],["disconnect",{"3":{"109":1,"612":1,"1310":1,"1323":2,"1668":1,"1926":1}}],["disconnectedcircuit",{"3":{"1415":1}}],["disconnectedcircuitretentionperiod",{"2":{"1123":1,"1999":1},"3":{"1123":1,"1323":1,"1999":1}}],["disconnectedcircuitretentionseconds",{"2":{"830":1,"831":1,"832":1},"3":{"830":1,"831":1,"832":1,"1323":2}}],["disconnectedcircuitmaxretained",{"2":{"830":1,"831":1,"832":1,"1123":1,"1124":1,"1999":1},"3":{"830":1,"831":1,"832":1,"1123":1,"1124":1,"1323":4,"1999":1}}],["disconnected",{"3":{"0":2,"507":1,"608":1,"831":1,"1124":1,"1247":1,"1285":1,"1310":3,"1323":10,"1415":1,"1416":1,"1437":1,"1639":1,"2157":1}}],["discoverrunbookalertheadings",{"3":{"1435":1}}],["discoverroutedpages",{"3":{"1435":1}}],["discoverdocumentedroutes",{"3":{"1435":1}}],["discovermembers",{"3":{"964":1,"1237":2}}],["discoveralertspecs",{"3":{"1435":1}}],["discoverability",{"3":{"227":1,"1270":1}}],["discoverable",{"3":{"86":1,"450":1,"555":1,"609":1,"1259":1,"1285":4,"1299":1,"1310":1,"1322":2,"1323":1,"1338":1,"1349":1,"1358":2,"1395":1,"1414":3,"1435":2,"1459":1,"1538":1,"1563":1,"1569":1,"1570":1,"2131":1}}],["discoverandregister",{"2":{"117":1,"633":1,"636":1,"1314":1,"1377":1,"1412":1,"1880":1,"1882":1},"3":{"117":2,"583":2,"633":1,"636":1,"1308":1,"1310":3,"1314":1,"1322":4,"1377":1,"1379":2,"1395":2,"1412":1,"1414":1,"1880":3,"1882":1}}],["discovering",{"3":{"69":1,"908":1,"965":1,"1051":1,"1270":1,"1322":1,"1323":1,"1329":1,"1358":2,"1435":1,"1439":1,"2045":1,"2202":1}}],["discovers",{"3":{"63":1,"135":1,"295":1,"584":1,"586":1,"609":1,"649":1,"905":1,"1212":1,"1259":1,"1285":3,"1299":1,"1308":1,"1310":3,"1312":1,"1322":1,"1323":1,"1338":1,"1358":2,"1376":1,"1379":1,"1395":3,"1414":5,"1415":1,"1433":1,"1435":6,"1457":1,"1475":1,"1485":1,"1661":1,"1670":1,"1683":2,"1727":1,"1728":1,"1809":1,"1877":1,"1885":1,"1895":1,"1933":1,"2093":1,"2096":1,"2104":1}}],["discovered",{"1":{"1877":1,"1878":1,"1879":1,"1880":1,"1881":1,"1882":1,"1883":1,"1884":1,"1885":1},"3":{"0":2,"53":1,"59":1,"63":1,"446":1,"454":1,"583":2,"609":1,"619":1,"648":1,"673":2,"792":1,"840":1,"964":1,"1051":1,"1082":1,"1084":1,"1135":1,"1212":1,"1235":1,"1237":2,"1259":3,"1270":3,"1285":7,"1308":4,"1310":5,"1314":1,"1322":6,"1323":2,"1338":1,"1358":16,"1368":2,"1379":1,"1395":8,"1401":5,"1414":9,"1415":1,"1416":1,"1426":1,"1430":1,"1435":24,"1437":1,"1440":2,"1466":2,"1475":2,"1485":1,"1508":1,"1543":1,"1549":1,"1650":2,"1652":1,"1789":1,"1876":1,"1877":1,"1882":1,"1950":1,"2044":2,"2045":1,"2057":1,"2076":1,"2092":1,"2131":1,"2202":1,"2206":1,"2213":1,"2231":1}}],["discover",{"3":{"0":1,"57":1,"62":1,"401":1,"536":1,"583":1,"610":1,"1052":1,"1067":1,"1092":1,"1237":1,"1259":1,"1275":1,"1285":1,"1308":1,"1309":1,"1310":1,"1312":1,"1322":4,"1323":2,"1338":1,"1353":1,"1368":2,"1379":1,"1394":3,"1437":1,"1457":1,"1466":1,"1625":1,"1788":1,"1829":1,"1895":1,"1898":1,"2026":1,"2106":1,"2128":1,"2134":1,"2157":1,"2162":1,"2167":1}}],["discovery",{"0":{"1314":1,"1955":1},"1":{"28":1,"29":1,"30":1,"31":1,"32":1,"33":1,"34":1,"35":1},"3":{"0":9,"28":1,"30":1,"31":8,"32":3,"33":5,"35":2,"53":1,"55":1,"61":1,"91":1,"92":3,"93":1,"95":1,"96":1,"97":1,"98":5,"102":1,"105":1,"132":1,"175":1,"182":1,"234":1,"235":3,"237":1,"239":1,"401":1,"448":1,"527":1,"583":3,"584":2,"585":1,"609":1,"628":1,"650":1,"749":2,"751":1,"758":1,"827":5,"834":1,"837":1,"838":2,"868":1,"913":2,"963":1,"966":1,"967":1,"1018":1,"1052":1,"1066":1,"1184":1,"1202":1,"1203":1,"1212":7,"1215":1,"1280":1,"1281":1,"1285":16,"1287":2,"1299":1,"1308":3,"1309":2,"1310":35,"1311":7,"1314":4,"1315":1,"1322":9,"1323":8,"1324":1,"1327":1,"1328":1,"1332":1,"1333":2,"1336":1,"1338":20,"1349":2,"1358":6,"1368":3,"1377":1,"1378":1,"1379":10,"1395":3,"1414":5,"1415":1,"1435":17,"1436":1,"1437":4,"1438":1,"1440":11,"1441":1,"1443":1,"1445":2,"1446":3,"1449":2,"1454":2,"1459":1,"1466":7,"1468":1,"1474":1,"1485":2,"1487":1,"1488":4,"1525":1,"1546":1,"1575":1,"1576":3,"1640":1,"1646":1,"1652":1,"1655":1,"1659":2,"1662":3,"1666":2,"1669":2,"1670":1,"1676":1,"1686":1,"1701":1,"1706":1,"1717":1,"1723":2,"1726":1,"1771":2,"1772":1,"1784":1,"1789":1,"1791":1,"1824":1,"1880":3,"1884":1,"1894":1,"1895":2,"1896":1,"1897":2,"1898":5,"1903":1,"1955":1,"1958":1,"1999":1,"2001":1,"2003":2,"2005":1,"2006":1,"2008":2,"2013":3,"2018":2,"2020":1,"2021":2,"2022":2,"2023":2,"2024":1,"2026":1,"2029":1,"2045":1,"2055":1,"2057":1,"2076":1,"2107":1,"2110":1,"2130":1,"2131":3,"2132":2,"2229":1,"2231":1}}],["discardunstartedconnectionasync",{"3":{"1323":1}}],["discardabandonedcapture",{"2":{"715":1,"1274":1},"3":{"715":1,"1274":1,"1285":3}}],["discarding",{"3":{"518":1,"707":1,"1270":1,"1278":1,"1285":1,"1310":1,"1323":3,"1416":5,"1435":2,"1946":1}}],["discards",{"3":{"21":1,"201":1,"255":1,"265":1,"520":1,"538":1,"540":1,"690":1,"1259":2,"1271":1,"1285":2,"1308":1,"1322":3,"1323":9,"1345":1,"1358":1,"1389":1,"1395":3,"1401":3,"1414":1,"1416":1,"1437":1,"1475":1,"1480":1,"1666":1}}],["discarded",{"3":{"0":2,"21":1,"534":1,"539":1,"640":1,"716":1,"854":1,"863":1,"991":1,"1100":1,"1229":1,"1259":1,"1285":1,"1293":1,"1299":4,"1310":2,"1322":1,"1323":3,"1349":1,"1358":8,"1395":3,"1401":1,"1414":1,"1415":1,"1416":4,"1435":1,"1491":1,"1640":2,"1766":1,"1821":1,"1946":1,"1948":1,"2182":1}}],["discard",{"3":{"0":1,"715":1,"721":1,"988":1,"1101":1,"1233":1,"1237":1,"1259":1,"1270":1,"1310":1,"1323":5,"1358":2,"1368":1,"1394":2,"1395":6,"1399":1,"1401":2,"1415":1,"1416":1,"1435":3,"1437":1,"1525":1,"1530":1,"1546":1,"1588":1,"1594":1,"1595":2,"1597":1,"1685":1,"1850":1}}],["distant",{"3":{"1310":1,"1358":1}}],["distancekmto",{"3":{"1394":1,"1416":2}}],["distance",{"3":{"632":1,"1378":1,"1394":4,"1416":6,"1435":1,"1437":1,"1555":1,"1921":1}}],["distorts",{"3":{"1285":1}}],["disturbs",{"3":{"1379":3,"1764":1}}],["disturbed",{"3":{"1322":1}}],["disturbing",{"3":{"1310":2,"1358":2,"1394":1,"1401":1,"1435":1}}],["disturb",{"3":{"1079":1,"1310":1}}],["distilled",{"0":{"1678":1},"3":{"601":1,"1469":1,"1501":1,"1678":1}}],["distinguishable",{"3":{"790":1,"907":1,"926":1,"1093":1,"1259":1,"1270":4,"1308":1,"1310":2,"1322":2,"1323":4,"1358":5,"1394":2,"1395":7,"1414":1,"1435":1,"1495":1,"2178":1}}],["distinguishing",{"3":{"309":1,"1259":1,"1309":1,"1310":1,"1323":1,"1338":1,"1349":2,"1358":4,"1379":1,"1394":2,"1395":4,"1435":2}}],["distinguish",{"3":{"158":1,"343":1,"612":1,"1285":1,"1299":4,"1310":2,"1319":1,"1322":3,"1323":3,"1358":5,"1394":4,"1395":4,"1401":1,"1414":1,"1416":1,"1435":7,"1437":1,"1497":1,"1629":1,"1981":1}}],["distinguished",{"3":{"111":1,"1270":1,"1341":1,"1349":1,"1358":1,"1395":3,"1555":1,"1636":1}}],["distinguishes",{"3":{"10":1,"536":1,"549":1,"676":1,"700":1,"758":1,"803":1,"805":1,"881":1,"922":1,"1237":1,"1259":1,"1265":1,"1285":2,"1299":2,"1322":1,"1323":1,"1358":3,"1379":1,"1382":1,"1394":9,"1395":4,"1401":1,"1414":2,"1435":5,"1671":1,"1820":1}}],["distincterrorcodecount",{"3":{"1435":3}}],["distinctcodecount",{"3":{"1435":2}}],["distincts",{"3":{"1358":1}}],["distinctness",{"3":{"1285":1}}],["distinctly",{"3":{"1285":1,"1322":3,"1323":1,"1368":1,"1394":1,"1395":1,"1938":1}}],["distinctby",{"3":{"1271":1,"1394":2,"1829":1}}],["distinctive",{"3":{"1247":1,"1349":2,"1394":1,"1395":1,"1435":2}}],["distinctions",{"3":{"1395":4}}],["distinction",{"3":{"0":2,"136":1,"187":1,"691":1,"837":1,"855":1,"882":1,"906":1,"1225":1,"1247":1,"1249":1,"1259":3,"1270":1,"1285":5,"1299":4,"1308":3,"1310":1,"1322":1,"1323":2,"1338":2,"1347":1,"1349":4,"1358":3,"1379":2,"1394":2,"1395":4,"1401":1,"1414":2,"1416":4,"1421":1,"1427":1,"1429":1,"1435":8,"1455":2,"1459":1,"1475":1,"1488":1,"1576":1,"1650":1,"1955":1,"1981":1,"2043":1}}],["distinct",{"0":{"1206":1},"2":{"647":1},"3":{"0":7,"19":1,"92":1,"135":1,"157":1,"162":2,"166":2,"174":2,"225":1,"229":1,"242":1,"248":1,"284":1,"312":1,"340":1,"346":2,"358":1,"359":1,"470":1,"472":1,"587":1,"607":1,"610":1,"612":1,"618":1,"619":1,"647":1,"651":1,"690":1,"699":1,"741":1,"828":1,"1132":1,"1134":1,"1191":2,"1192":1,"1196":1,"1197":1,"1200":1,"1202":1,"1204":1,"1206":1,"1231":1,"1237":3,"1241":2,"1242":1,"1247":3,"1259":2,"1261":1,"1271":2,"1279":1,"1285":16,"1299":7,"1300":2,"1301":1,"1308":5,"1309":5,"1310":6,"1322":6,"1323":12,"1324":1,"1331":1,"1338":3,"1346":1,"1349":5,"1350":1,"1358":29,"1368":2,"1379":3,"1394":9,"1395":24,"1396":1,"1401":4,"1405":1,"1414":3,"1415":2,"1416":9,"1430":1,"1435":28,"1437":2,"1442":1,"1466":2,"1486":1,"1495":31,"1498":1,"1530":1,"1542":1,"1630":2,"1651":1,"1664":1,"1669":1,"1684":1,"1701":1,"1706":1,"1746":1,"1764":1,"1864":1,"1869":1,"1899":1,"1932":1,"1988":2,"2045":1,"2140":1,"2195":1}}],["distrust",{"3":{"940":1}}],["distro",{"3":{"395":1,"397":1,"402":1,"1332":2,"1338":5,"1442":2,"1667":1,"2009":2,"2153":1,"2155":1,"2156":1,"2159":2}}],["distributing",{"3":{"2027":1}}],["distributions",{"3":{"1349":2}}],["distribution",{"0":{"1197":1},"3":{"33":1,"530":1,"598":1,"1299":1,"1310":1,"1349":4,"1358":12,"1375":1,"1379":1,"1389":1,"1394":2,"1415":1,"1424":1,"1426":2,"1534":1,"1584":1,"1631":1,"1639":1,"1893":1}}],["distributedlock",{"3":{"1576":1}}],["distributedapplicationbuilder",{"3":{"1437":1}}],["distributedapplication",{"2":{"1429":1,"2055":1},"3":{"1338":1,"1429":1,"1488":1,"2055":1}}],["distributedapplicationtestingbuilder",{"2":{"912":1,"913":2,"914":1,"1066":1,"1067":1,"1069":1,"1212":1,"1611":1},"3":{"0":2,"912":1,"913":2,"914":1,"1066":1,"1067":1,"1069":1,"1212":1,"1611":1}}],["distributedcacheentryoptions",{"2":{"243":1,"254":1},"3":{"243":1,"254":1,"1300":5}}],["distributedcache",{"3":{"0":1,"1091":1}}],["distributedcacheservicetests",{"3":{"1199":1,"1207":3,"1300":2,"1495":1}}],["distributedcacheserviceredistests",{"2":{"1300":1},"3":{"1199":1,"1207":2,"1300":3,"1437":2,"1495":1}}],["distributedcacheservice",{"2":{"245":1,"248":1,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"256":1,"257":1,"259":1,"261":1,"285":1,"286":1,"731":1,"733":1,"736":1,"1441":1,"1452":1,"1485":1,"1915":1,"1922":1},"3":{"0":1,"243":2,"245":4,"248":2,"249":1,"250":1,"251":3,"252":2,"253":1,"254":3,"256":4,"257":2,"259":2,"261":1,"285":1,"286":1,"731":1,"732":1,"733":3,"736":1,"1199":8,"1203":1,"1207":2,"1229":4,"1299":1,"1300":58,"1322":2,"1338":2,"1437":2,"1441":1,"1466":1,"1485":1,"1486":1,"1495":1,"1576":2,"1915":2,"1922":1}}],["distributed",{"0":{"2007":1},"1":{"2002":1,"2003":1,"2004":1,"2005":1,"2006":1,"2007":1,"2008":1,"2009":1,"2010":1,"2011":1,"2012":1,"2013":1},"2":{"182":1,"2000":1},"3":{"0":5,"25":1,"61":1,"135":1,"157":2,"158":1,"159":1,"162":1,"173":1,"177":1,"178":5,"182":2,"235":2,"241":1,"243":5,"244":2,"245":4,"246":1,"253":1,"259":1,"261":1,"282":1,"283":3,"398":2,"461":1,"516":1,"517":1,"520":1,"535":1,"538":1,"732":1,"788":1,"792":1,"794":1,"852":1,"995":1,"1000":1,"1011":1,"1012":1,"1091":1,"1202":1,"1203":1,"1212":1,"1213":2,"1223":1,"1229":3,"1257":1,"1259":4,"1270":2,"1285":1,"1293":1,"1299":6,"1300":28,"1310":20,"1311":2,"1319":1,"1322":4,"1324":1,"1331":2,"1338":6,"1357":1,"1358":4,"1368":2,"1378":1,"1420":1,"1426":3,"1437":2,"1439":1,"1440":1,"1441":2,"1442":1,"1453":1,"1464":1,"1466":3,"1495":2,"1525":1,"1533":2,"1543":2,"1546":1,"1549":1,"1576":2,"1585":1,"1638":1,"1655":1,"1665":2,"1667":2,"1674":1,"1788":1,"1791":1,"1844":1,"1845":1,"1846":1,"1848":2,"1851":1,"1852":1,"1893":1,"1908":1,"1909":1,"1911":2,"1914":1,"1915":6,"1916":1,"1919":2,"1922":3,"1933":1,"1946":1,"2000":5,"2001":1,"2002":3,"2004":1,"2006":1,"2009":1,"2013":1,"2018":1,"2026":1,"2066":1,"2154":1,"2159":1,"2167":1,"2168":1,"2192":1,"2208":1}}],["disprove",{"3":{"2238":1}}],["dispensation",{"3":{"1533":1}}],["disputed",{"3":{"1395":1}}],["displacing",{"3":{"1974":1}}],["displaces",{"3":{"1416":1}}],["displace",{"3":{"1358":1}}],["displaced",{"3":{"624":2,"1184":1,"1395":1,"1921":1}}],["displaying",{"3":{"1323":1,"1435":1}}],["displaynameunavailable",{"3":{"1395":1}}],["displaynamemaxlength",{"3":{"1395":5}}],["displayname",{"3":{"690":1,"1299":1,"1338":1,"1358":1,"1394":1,"1395":25,"1409":1,"1414":1}}],["displaystatus",{"3":{"1323":1}}],["displays",{"3":{"690":1,"1349":2,"1436":1,"1630":1,"1638":1,"1665":1}}],["displayed",{"3":{"135":1,"265":1,"1323":3,"1349":1,"1358":2,"1394":5,"1401":1,"1628":1,"1629":7,"1630":5,"1636":1}}],["display",{"0":{"1386":1},"1":{"679":1,"680":1,"681":1,"682":1,"683":1,"684":1,"685":1,"686":1},"3":{"0":2,"135":1,"265":2,"402":1,"679":1,"682":2,"683":1,"688":1,"690":2,"691":1,"897":1,"963":1,"964":1,"990":1,"1027":1,"1028":1,"1212":1,"1235":1,"1237":2,"1247":1,"1278":2,"1285":2,"1292":1,"1294":1,"1299":3,"1310":6,"1313":1,"1322":2,"1323":11,"1332":1,"1338":7,"1341":1,"1343":2,"1349":36,"1352":1,"1358":30,"1368":2,"1379":1,"1386":1,"1394":49,"1395":35,"1401":11,"1414":3,"1415":1,"1416":2,"1426":1,"1435":3,"1437":2,"1442":1,"1487":1,"1576":1,"1590":1,"1629":11,"1630":6,"1632":1,"1636":2,"1638":3,"1639":4,"1640":3,"1655":1,"1668":1,"1747":2,"1749":3,"1763":2,"1764":2,"1775":1,"2074":1,"2093":1,"2149":2,"2231":1}}],["disposables",{"3":{"1323":1,"1395":1}}],["disposable",{"3":{"1247":2,"1273":1,"1285":1,"1299":2,"1322":2,"1323":3,"1338":2,"1395":8,"1399":1,"1414":2,"1416":2,"1435":2}}],["disposal",{"3":{"572":1,"881":1,"916":1,"996":1,"998":1,"1270":2,"1285":7,"1322":2,"1323":22,"1388":1,"1394":32,"1395":10,"1401":11,"1414":1,"1416":3,"1435":4,"1437":2,"1487":2,"1489":2,"1848":1,"1896":1,"2054":1,"2071":1}}],["disposing",{"3":{"996":1,"1270":2,"1285":1,"1299":1,"1322":3,"1323":10,"1338":1,"1394":5,"1395":5,"1416":2,"1424":1,"1426":2,"1435":1,"1667":1,"2176":1}}],["dispositiontype",{"3":{"1285":1}}],["disposition",{"3":{"0":1,"443":1,"725":3,"741":2,"1116":2,"1284":1,"1285":7,"1310":2,"1349":1,"1356":1,"1416":1,"1630":1,"2125":1}}],["disposehostsasync",{"3":{"1435":1}}],["disposehandler",{"3":{"1338":2,"1435":3}}],["disposetrackingresponse",{"3":{"1199":2,"1207":1}}],["disposes",{"3":{"401":1,"1259":2,"1285":3,"1299":1,"1301":1,"1310":1,"1322":1,"1323":17,"1338":3,"1394":6,"1395":4,"1401":10,"1414":4,"1416":7,"1426":2,"1428":1,"1435":8,"1437":1,"1487":3,"1490":1,"1937":1,"2055":1,"2069":1}}],["disposed",{"3":{"109":1,"524":1,"1259":2,"1270":4,"1274":1,"1285":4,"1287":1,"1299":2,"1308":1,"1322":3,"1323":42,"1338":3,"1394":16,"1395":7,"1414":1,"1416":1,"1424":1,"1426":1,"1431":1,"1435":3,"1525":1,"1576":3,"1701":1,"2071":2,"2078":1,"2176":1}}],["disposeasync",{"2":{"1490":2},"3":{"0":1,"135":1,"1067":1,"1285":3,"1322":2,"1323":8,"1338":1,"1395":4,"1401":5,"1416":2,"1428":2,"1435":9,"1487":3,"1490":2}}],["dispose",{"3":{"0":1,"135":1,"401":1,"996":1,"1246":1,"1247":2,"1259":3,"1285":8,"1299":5,"1310":1,"1322":2,"1323":40,"1338":5,"1383":1,"1394":24,"1395":15,"1414":1,"1416":13,"1426":2,"1431":1,"1435":6,"1441":1,"1576":2,"1590":2,"1610":1,"2069":1}}],["dispatchchanneleventasync",{"3":{"1323":3}}],["dispatchcache",{"3":{"1259":1,"1322":1}}],["dispatchable",{"3":{"1358":4}}],["dispatchandfinalizeasync",{"2":{"1274":1},"3":{"1274":1,"1285":3}}],["dispatchasync",{"3":{"1259":6,"1285":2,"1435":2}}],["dispatchtohandlersasync",{"3":{"1259":1}}],["dispatchmessagesasync",{"2":{"2164":1},"3":{"1259":2,"2164":1}}],["dispatchfailed",{"2":{"897":1},"3":{"897":1,"1247":5}}],["dispatchlaghistogram",{"3":{"707":1,"1259":3}}],["dispatching",{"3":{"18":1,"20":1,"58":1,"230":1,"767":1,"881":1,"1252":1,"1255":1,"1257":1,"1259":7,"1274":1,"1278":1,"1285":3,"1322":1,"1344":1,"1349":1,"1358":1,"1374":1,"1379":4,"1395":1,"1401":1,"1435":1,"1437":1,"1488":1,"1576":1,"1840":2}}],["dispatched",{"3":{"15":1,"16":1,"17":1,"18":3,"19":1,"20":1,"21":1,"26":1,"230":1,"291":1,"642":1,"692":1,"702":1,"781":1,"786":1,"1140":1,"1237":2,"1249":1,"1251":1,"1252":3,"1256":1,"1259":16,"1271":1,"1285":5,"1288":1,"1299":1,"1308":4,"1349":15,"1351":1,"1358":13,"1394":1,"1395":7,"1401":21,"1414":9,"1507":2,"1576":1,"1630":1,"1633":1,"1636":1,"1637":1,"1638":1,"1650":1,"1676":1,"1718":1,"1720":1,"1788":1,"1837":1,"1860":1}}],["dispatches",{"0":{"1841":1},"2":{"1369":1},"3":{"0":1,"15":1,"24":1,"166":1,"225":2,"230":1,"265":1,"536":1,"538":1,"846":1,"930":1,"931":1,"1011":1,"1042":1,"1233":1,"1237":2,"1247":1,"1252":1,"1257":2,"1259":20,"1285":5,"1310":8,"1311":1,"1316":1,"1322":1,"1323":3,"1343":1,"1349":1,"1358":6,"1369":1,"1374":1,"1379":33,"1394":4,"1395":9,"1401":2,"1407":1,"1414":5,"1415":1,"1430":1,"1435":4,"1507":1,"1782":1,"1814":1,"1855":1,"2090":1,"2136":1,"2155":1,"2162":1,"2164":3,"2187":1}}],["dispatcher",{"3":{"0":1,"81":1,"135":1,"418":1,"420":1,"428":1,"538":1,"784":1,"1042":1,"1140":1,"1192":1,"1202":1,"1203":1,"1237":1,"1241":1,"1247":2,"1248":1,"1252":1,"1256":1,"1257":1,"1259":18,"1262":1,"1270":1,"1274":1,"1285":6,"1308":1,"1310":3,"1395":3,"1401":2,"1414":1,"1415":8,"1416":11,"1435":3,"1486":2,"1525":1,"1538":1,"1542":1,"1576":1,"1664":1,"1668":1,"1888":1,"2120":1,"2164":1,"2167":1,"2181":1,"2185":1}}],["dispatch",{"0":{"1247":1,"1252":1,"1507":1},"1":{"14":1,"15":1,"16":1,"17":1,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1,"24":1,"25":1,"26":1,"27":1,"1248":1,"1249":1,"1250":1,"1251":1,"1252":1,"1253":1,"1254":1,"1255":1,"1256":1,"1257":1,"1258":1,"1259":1},"2":{"24":1,"400":1,"626":1,"766":1,"768":1,"897":1,"899":1,"901":1,"1398":1,"1455":1,"1456":1,"1457":1,"1473":1,"1474":2,"1588":1,"1598":1,"1918":1,"1945":1,"2033":1,"2155":1},"3":{"0":14,"14":1,"15":2,"16":2,"17":3,"18":3,"19":1,"20":6,"21":4,"22":1,"24":3,"25":1,"26":2,"27":3,"64":1,"71":1,"113":1,"115":1,"120":3,"168":1,"171":1,"196":1,"200":1,"229":1,"314":1,"383":1,"400":1,"518":1,"626":5,"639":1,"692":1,"707":1,"766":1,"768":1,"819":1,"845":1,"846":1,"863":1,"881":5,"897":2,"899":1,"901":1,"931":2,"933":2,"935":1,"987":1,"988":3,"989":1,"992":1,"1010":1,"1011":1,"1013":2,"1075":1,"1076":1,"1192":2,"1202":1,"1203":1,"1212":3,"1230":1,"1233":3,"1237":7,"1241":2,"1247":6,"1248":2,"1249":1,"1251":2,"1254":1,"1255":1,"1257":2,"1259":44,"1263":1,"1270":7,"1274":5,"1278":1,"1285":23,"1299":3,"1310":5,"1322":6,"1323":7,"1344":1,"1349":3,"1358":4,"1368":3,"1374":1,"1379":4,"1394":5,"1395":18,"1398":2,"1401":7,"1405":1,"1411":1,"1414":8,"1416":5,"1435":8,"1437":2,"1442":1,"1445":1,"1451":1,"1454":8,"1455":6,"1456":1,"1457":1,"1459":10,"1472":1,"1473":1,"1474":8,"1482":1,"1489":2,"1491":1,"1495":1,"1497":1,"1507":1,"1523":1,"1525":4,"1530":1,"1533":1,"1537":1,"1542":1,"1570":1,"1576":2,"1581":1,"1582":1,"1588":1,"1590":4,"1595":2,"1598":1,"1599":2,"1610":2,"1633":1,"1637":1,"1661":1,"1664":3,"1720":1,"1748":1,"1837":3,"1839":2,"1841":1,"1845":3,"1918":1,"1943":1,"1945":1,"1949":1,"2005":1,"2025":1,"2031":1,"2033":1,"2073":1,"2155":3,"2158":1,"2164":1,"2220":1,"2231":2}}],["douglasville",{"3":{"2243":1}}],["douglas",{"3":{"2219":1}}],["doubly",{"3":{"1161":1,"1416":1,"1454":1,"1525":1,"1590":1}}],["doubling",{"3":{"178":1,"390":1,"741":1,"1270":1,"1310":1,"1323":3,"1349":1,"1395":1,"1416":1,"1495":1,"1984":1}}],["doublebooked",{"3":{"1358":1,"1435":1}}],["doublebookederror",{"3":{"1358":2}}],["doubled",{"0":{"141":1},"3":{"0":1,"125":1,"551":1,"556":1,"559":2,"982":1,"1214":1,"1310":1,"1358":1,"1488":1,"1493":1,"2042":1}}],["doubles",{"3":{"0":1,"195":1,"282":1,"782":2,"838":1,"848":1,"875":1,"954":1,"955":1,"1060":1,"1237":1,"1269":2,"1270":3,"1285":2,"1299":3,"1310":1,"1317":1,"1322":2,"1323":3,"1338":1,"1349":1,"1358":3,"1368":1,"1394":2,"1395":1,"1399":1,"1401":1,"1402":1,"1435":8,"1436":2,"1458":1,"1466":1,"1487":2,"1495":2,"1650":1,"1653":1,"1659":1,"1670":1,"1672":1,"1931":1,"1941":1,"1964":1,"2054":1}}],["double",{"0":{"2075":1},"3":{"0":8,"20":1,"122":1,"156":2,"157":2,"160":1,"195":1,"201":1,"379":1,"493":1,"494":1,"495":1,"497":2,"501":1,"502":1,"517":1,"536":2,"538":1,"556":2,"557":2,"558":1,"647":1,"690":1,"692":1,"733":1,"743":2,"827":1,"829":1,"905":1,"953":1,"954":3,"955":1,"956":1,"960":1,"997":1,"1042":1,"1174":1,"1175":1,"1212":1,"1231":1,"1237":2,"1247":1,"1255":1,"1259":2,"1265":1,"1267":1,"1270":11,"1271":1,"1280":1,"1285":10,"1287":1,"1288":1,"1295":1,"1297":1,"1299":12,"1300":4,"1310":13,"1322":10,"1323":14,"1334":1,"1338":5,"1349":4,"1351":1,"1358":16,"1368":3,"1379":2,"1394":1,"1395":5,"1401":3,"1411":1,"1414":2,"1416":8,"1426":1,"1428":1,"1431":1,"1435":19,"1437":3,"1466":1,"1487":2,"1493":1,"1495":1,"1525":1,"1533":3,"1560":2,"1590":1,"1666":1,"1667":1,"1670":1,"1748":1,"1809":2,"1840":1,"1905":1,"1908":1,"1910":1,"1911":1,"1914":1,"1916":1,"1983":1,"2060":1,"2164":1,"2166":1,"2231":1}}],["dodge",{"3":{"1800":1,"2185":1}}],["dollar",{"3":{"1533":1}}],["dollars",{"3":{"1323":1,"1456":1}}],["dogfooding",{"3":{"1486":1,"1487":1}}],["dogfoods",{"3":{"572":1,"707":1,"715":1}}],["doasync",{"3":{"1435":1}}],["doomed",{"3":{"1285":1,"1323":2,"1395":1,"1416":1,"1437":1}}],["doors",{"3":{"1349":1,"1358":1,"2065":1}}],["door",{"3":{"0":1,"443":1,"544":1,"689":4,"690":1,"691":2,"827":1,"1115":1,"1123":1,"1125":1,"1132":1,"1133":1,"1134":1,"1212":1,"1237":1,"1247":1,"1310":1,"1338":1,"1349":1,"1358":16,"1392":1,"1394":1,"1395":27,"1401":1,"1414":1,"1415":1,"1466":1,"1626":2,"1628":1,"1630":2,"1700":1,"1779":1,"1782":1,"1999":1,"2025":1,"2065":1,"2068":1,"2089":1,"2138":1}}],["dormant",{"3":{"691":1,"1087":1,"1264":1,"1358":1,"1395":6,"1400":1,"1401":1,"1416":2,"1523":1,"1525":2,"1533":1,"1822":1,"1859":1}}],["dozens",{"3":{"1005":1,"1124":1,"1285":1,"1312":1,"1351":1,"1358":1,"1394":2,"1395":1,"1435":1,"1957":1,"1985":1}}],["dozen",{"3":{"527":1,"673":1,"1035":1,"1313":1,"1323":1,"1338":1,"1352":1,"1394":3,"1395":3,"1400":2,"1401":2,"1435":2,"1827":1,"1879":1,"2058":1,"2201":1}}],["dominant",{"3":{"1229":1,"1360":1,"1455":1,"1489":1,"1495":3,"1504":1,"1516":1}}],["dominates",{"3":{"1247":1,"1495":1,"1676":1}}],["dominated",{"3":{"1067":1,"1286":1,"1394":1,"1454":2,"1491":2}}],["dominate",{"3":{"396":1,"1259":1,"1285":1,"1338":1,"1442":1,"1466":1,"1676":1,"1840":1,"2009":1,"2051":1,"2156":1}}],["dom",{"3":{"506":2,"507":1,"508":1,"1187":1,"1323":8,"1394":1,"1415":1,"1416":1,"1431":1,"1435":6,"1455":1,"1487":1,"1530":1,"2072":1,"2078":1}}],["domainpurity",{"3":{"1575":1,"1576":1}}],["domainpuritytests",{"2":{"1524":1,"2048":1},"3":{"1199":2,"1207":4,"1435":13,"1488":5,"1492":1,"1524":1,"1525":1,"1575":1,"1576":1,"1590":1,"1599":1,"2042":1,"2048":1}}],["domainpuritytestsbase",{"2":{"2041":1,"2103":1,"2107":1},"3":{"1199":3,"1207":2,"1430":1,"1435":9,"1488":1,"1653":1,"2041":1,"2103":1,"2107":1}}],["domainorinfrastructure",{"3":{"1435":1}}],["domainfactories",{"3":{"1435":2,"1488":1}}],["domainfactoriesreturnresult",{"2":{"657":1,"1585":1},"3":{"657":1,"1430":1,"1435":1,"1576":1,"1585":1}}],["domainassemblylocations",{"3":{"1435":1}}],["domainaggregaterootshavenopublicconstructors",{"2":{"1576":1},"3":{"1435":1,"1576":1}}],["domainandapplicationdonotreadtheclock",{"2":{"1161":1},"3":{"1161":1,"1435":4}}],["domainandapplication",{"2":{"1161":1,"1435":1},"3":{"1161":1,"1435":1}}],["domaindoesnotdependonapplication",{"3":{"1435":1}}],["domainindex",{"3":{"1299":1}}],["domaininvariantviolationexception",{"2":{"109":1,"881":1,"1217":1,"1226":1,"1229":1,"1805":1},"3":{"0":2,"109":1,"881":1,"1199":4,"1203":1,"1207":2,"1217":1,"1226":3,"1229":7,"1299":1,"1323":1,"1805":1}}],["domainhelpertests",{"3":{"1199":1,"1207":2,"1237":2}}],["domainhelper",{"3":{"980":1,"982":1,"1199":1,"1203":1,"1207":2,"1208":1,"1214":1,"1230":2,"1236":7,"1237":7,"1394":11,"1395":3}}],["domainthrowfixtures",{"3":{"1207":21,"1435":22}}],["domainthrowfitnesstests",{"3":{"1199":1,"1207":3,"1435":25,"1488":1}}],["domainthrowtests",{"3":{"1199":1,"1207":2,"1435":6,"1488":1}}],["domainthrowtestsbase",{"3":{"1199":2,"1207":2,"1435":7}}],["domainthrows",{"3":{"980":1,"982":1,"1207":1,"1435":10,"1488":1}}],["domainthrowsonlyargumentguards",{"2":{"980":1,"983":1},"3":{"980":1,"983":1,"1435":6}}],["domaintypes",{"3":{"81":1,"1435":2}}],["domains",{"3":{"418":1,"420":1,"422":1,"1168":3,"1415":1,"1435":2,"1480":2,"1598":1,"1628":1,"2108":2,"2109":1,"2113":1}}],["domainentityproperties",{"3":{"1435":1}}],["domainentitystate",{"2":{"963":1,"968":1,"1233":1,"1249":1,"1339":1,"1354":1,"1397":1,"1684":1},"3":{"0":2,"690":1,"782":6,"963":2,"964":2,"968":1,"1199":75,"1203":1,"1207":2,"1212":1,"1230":1,"1233":2,"1237":3,"1249":1,"1259":6,"1299":1,"1339":1,"1344":1,"1349":38,"1354":1,"1358":22,"1395":18,"1397":1,"1401":15,"1630":3,"1632":1,"1633":15,"1638":1,"1664":1,"1684":1,"1768":1}}],["domainentities",{"3":{"1435":2}}],["domainexposesaggregateroots",{"3":{"1430":1,"1576":1}}],["domainexceptiontests",{"3":{"1199":1,"1207":3,"1229":1}}],["domainexception",{"2":{"1217":1,"1226":1,"1639":1},"3":{"109":1,"1199":6,"1203":1,"1207":2,"1217":1,"1226":6,"1229":12,"1310":4,"1435":1,"1639":2}}],["domainexceptionhandler",{"2":{"1226":1,"1926":1},"3":{"109":2,"1199":4,"1203":1,"1207":2,"1226":1,"1229":6,"1310":9,"1773":1,"1926":1}}],["domaineventcaptureexclusiontests",{"3":{"1199":1,"1207":6,"1285":2,"1437":2}}],["domaineventoccurrencestamp",{"2":{"1161":1},"3":{"1161":1}}],["domaineventsavefixtures",{"3":{"1207":27,"1435":34}}],["domaineventsavechangesinterceptoroutboxdisabledtests",{"3":{"1199":1,"1207":2,"1285":2}}],["domaineventsavechangesinterceptoroutboxroutingtests",{"3":{"1199":2,"1207":9,"1237":1,"1285":2}}],["domaineventsavechangesinterceptortests",{"3":{"1199":1,"1207":6,"1285":2}}],["domaineventsavechangesinterceptor",{"2":{"20":1,"120":1,"540":1,"715":1,"931":1,"1201":1,"1233":1,"1251":1,"1274":1,"1285":1},"3":{"0":1,"20":1,"26":1,"120":1,"166":1,"536":1,"538":1,"540":1,"715":3,"931":2,"988":2,"1175":1,"1198":1,"1199":57,"1201":1,"1203":1,"1207":5,"1233":6,"1237":6,"1251":4,"1252":3,"1253":1,"1254":1,"1259":17,"1270":1,"1274":3,"1285":28,"1322":1,"1349":2,"1435":7,"1495":4,"1497":1,"2164":1,"2167":1}}],["domainevents",{"2":{"1161":1,"1207":2,"1349":6,"1395":1,"1401":1,"1414":1},"3":{"81":1,"255":1,"536":2,"782":19,"784":2,"1026":1,"1160":1,"1161":1,"1203":33,"1207":98,"1231":1,"1237":5,"1247":1,"1249":4,"1252":2,"1259":20,"1270":1,"1281":1,"1285":6,"1308":3,"1341":1,"1344":5,"1349":45,"1358":1,"1368":1,"1395":10,"1401":8,"1414":5,"1435":6,"1437":2,"1495":1,"1576":2,"1685":1,"1768":1,"1809":1,"1855":1}}],["domainevent",{"3":{"26":2,"782":1,"1259":2,"1285":1,"1395":3,"2188":1}}],["domaineventhandlers",{"3":{"230":1,"390":1,"522":2,"545":2,"690":1,"782":5,"897":3,"1026":1,"1100":2,"1203":5,"1207":28,"1237":2,"1247":4,"1259":2,"1308":1,"1349":2,"1354":1,"1358":2,"1395":8,"1398":2,"1401":5,"1435":3,"1437":2,"1495":2,"1525":1,"1650":1,"1764":3}}],["domaineventhandlersavefitnesstests",{"3":{"1199":1,"1207":3,"1435":20,"1488":1}}],["domaineventhandlersavetests",{"3":{"27":2,"1199":1,"1207":2,"1435":6,"1488":1}}],["domaineventhandlersavetestsbase",{"3":{"17":2,"27":1,"1199":2,"1207":2,"1435":9}}],["domaineventhandlersaves",{"3":{"17":1,"27":1,"1207":1,"1435":13,"1488":1}}],["domaineventhandlersdonotsave",{"2":{"17":1,"27":1},"3":{"17":1,"27":1,"1435":7}}],["domaineventdispatchertests",{"3":{"1199":1,"1207":7}}],["domaineventdispatcheradditionaltests",{"3":{"1199":1,"1207":13}}],["domaineventdispatcher",{"2":{"17":1,"846":1,"848":1,"1252":1,"1269":1,"1650":1},"3":{"0":1,"17":1,"538":1,"846":1,"848":1,"1140":1,"1199":8,"1203":1,"1207":2,"1247":2,"1252":4,"1259":36,"1269":1,"1270":3,"1322":3,"1349":4,"1358":1,"1395":1,"1435":1,"1437":1,"1576":1,"1650":1,"2167":1}}],["domain",{"0":{"1233":1,"1344":1,"1345":1,"1354":1,"1540":1,"1629":1,"1686":1,"1768":1,"1831":1,"2090":1},"1":{"654":1,"655":1,"656":1,"657":1,"658":1,"659":1,"660":1,"661":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1,"1230":1,"1231":1,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":1,"1248":1,"1249":1,"1250":1,"1251":1,"1252":1,"1253":1,"1254":1,"1255":1,"1256":1,"1257":1,"1258":1,"1259":1,"1339":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":1,"1346":1,"1347":1,"1348":1,"1349":1,"1807":1,"1808":1,"1809":1,"1810":1,"1811":1},"2":{"39":3,"146":1,"1006":1,"1161":1,"1207":2,"1230":1,"1236":1,"1237":1,"1320":1,"1340":3,"1349":6,"1395":2,"1401":2,"1403":1,"1414":1,"1452":1,"1649":2,"1659":1,"1780":1,"1930":1,"2062":1},"3":{"0":23,"4":1,"15":1,"16":1,"17":2,"18":1,"19":1,"20":1,"24":2,"26":1,"27":2,"39":11,"40":1,"43":3,"52":1,"53":1,"54":2,"58":2,"59":2,"60":2,"62":1,"76":1,"80":3,"81":7,"108":1,"109":4,"110":2,"115":1,"117":1,"120":1,"124":1,"128":1,"131":2,"139":1,"146":1,"150":1,"156":2,"165":1,"167":1,"168":1,"185":1,"194":1,"224":1,"225":3,"226":1,"230":1,"255":1,"265":2,"318":1,"332":1,"341":5,"342":2,"350":1,"353":1,"359":1,"360":1,"370":1,"383":1,"391":1,"422":2,"469":2,"470":4,"473":1,"475":2,"476":3,"477":2,"497":5,"499":3,"502":2,"518":2,"522":2,"536":7,"537":1,"545":3,"546":1,"549":5,"556":1,"558":1,"572":4,"576":1,"584":1,"591":1,"592":1,"593":1,"634":1,"640":1,"654":1,"655":1,"656":1,"657":11,"658":1,"659":1,"690":5,"692":1,"698":3,"707":1,"715":2,"718":1,"781":1,"782":24,"783":1,"784":2,"785":1,"791":1,"798":2,"799":2,"802":1,"809":1,"810":1,"846":2,"881":1,"882":1,"883":1,"888":1,"897":1,"900":1,"904":1,"905":1,"906":1,"921":1,"922":1,"926":1,"930":1,"933":1,"939":1,"954":1,"963":3,"966":1,"971":1,"980":4,"982":2,"1004":8,"1006":1,"1018":1,"1024":1,"1025":2,"1026":4,"1027":2,"1029":1,"1033":1,"1034":1,"1037":1,"1041":1,"1049":1,"1050":5,"1051":1,"1052":1,"1054":1,"1055":1,"1058":1,"1059":4,"1074":2,"1078":1,"1082":1,"1083":4,"1085":1,"1091":3,"1093":1,"1100":1,"1104":1,"1116":4,"1140":4,"1141":2,"1142":1,"1147":1,"1150":1,"1158":1,"1160":3,"1161":14,"1162":1,"1168":26,"1169":1,"1170":2,"1171":1,"1183":1,"1185":1,"1187":2,"1191":1,"1192":5,"1194":2,"1198":18,"1199":250,"1200":1,"1201":4,"1202":8,"1203":151,"1206":8,"1207":869,"1208":2,"1211":5,"1212":13,"1213":1,"1214":2,"1216":1,"1217":1,"1220":1,"1224":1,"1226":1,"1229":11,"1230":6,"1231":10,"1232":4,"1233":5,"1234":3,"1235":1,"1236":6,"1237":159,"1238":2,"1239":9,"1240":1,"1247":69,"1248":2,"1249":14,"1252":2,"1257":2,"1259":63,"1262":2,"1263":1,"1265":3,"1270":19,"1271":25,"1272":2,"1274":2,"1275":1,"1278":1,"1280":1,"1284":1,"1285":93,"1289":1,"1290":5,"1292":1,"1294":1,"1296":1,"1299":70,"1301":1,"1302":6,"1303":1,"1304":2,"1305":2,"1308":63,"1309":25,"1310":41,"1311":8,"1312":1,"1315":1,"1318":2,"1320":1,"1322":39,"1323":12,"1338":1,"1339":6,"1340":13,"1341":12,"1342":3,"1343":16,"1344":9,"1345":4,"1346":1,"1348":2,"1349":361,"1350":3,"1351":3,"1352":2,"1353":1,"1354":4,"1355":1,"1357":2,"1358":502,"1359":4,"1360":3,"1361":1,"1362":1,"1368":34,"1369":1,"1374":1,"1376":1,"1377":1,"1378":1,"1379":28,"1394":18,"1395":176,"1396":2,"1397":3,"1398":3,"1401":112,"1402":1,"1403":7,"1404":6,"1408":1,"1409":1,"1411":1,"1412":1,"1414":119,"1415":1,"1416":7,"1426":1,"1428":2,"1430":14,"1435":403,"1436":9,"1437":16,"1454":2,"1485":1,"1486":7,"1487":1,"1488":28,"1491":1,"1492":1,"1495":31,"1497":1,"1498":1,"1502":1,"1503":2,"1504":1,"1505":1,"1507":2,"1525":17,"1534":4,"1537":1,"1538":2,"1539":5,"1540":2,"1543":1,"1545":2,"1550":2,"1552":1,"1571":1,"1574":1,"1575":2,"1576":25,"1580":1,"1581":5,"1582":1,"1584":3,"1585":2,"1590":5,"1599":1,"1630":8,"1631":9,"1632":1,"1633":6,"1636":2,"1637":11,"1638":9,"1639":1,"1640":3,"1646":1,"1649":5,"1650":5,"1652":2,"1653":4,"1655":1,"1659":3,"1660":1,"1661":5,"1662":2,"1664":2,"1665":1,"1670":3,"1680":2,"1682":1,"1684":8,"1685":8,"1686":2,"1688":1,"1692":1,"1699":1,"1700":3,"1718":1,"1723":1,"1726":4,"1729":2,"1746":4,"1747":2,"1748":2,"1750":1,"1752":1,"1754":1,"1762":1,"1763":13,"1764":12,"1768":4,"1769":1,"1771":1,"1774":2,"1775":2,"1778":1,"1780":2,"1781":1,"1782":1,"1788":1,"1789":2,"1795":1,"1799":1,"1802":1,"1804":4,"1805":2,"1806":1,"1807":4,"1808":1,"1809":4,"1811":5,"1814":3,"1821":2,"1828":1,"1830":3,"1831":5,"1832":4,"1833":3,"1837":5,"1838":1,"1841":1,"1843":1,"1850":1,"1852":1,"1853":3,"1855":1,"1860":1,"1861":1,"1863":1,"1864":2,"1870":2,"1876":1,"1882":1,"1883":1,"1890":1,"1921":1,"1928":1,"1930":2,"1931":2,"1932":1,"1933":1,"1936":2,"1940":1,"1943":2,"1945":1,"1949":2,"1950":1,"1952":2,"1953":2,"1989":1,"1993":1,"2015":2,"2019":1,"2027":1,"2038":2,"2039":2,"2041":1,"2042":7,"2050":1,"2051":2,"2053":1,"2062":3,"2063":2,"2070":2,"2073":1,"2082":3,"2084":3,"2085":1,"2086":1,"2087":1,"2089":3,"2090":3,"2092":1,"2094":1,"2096":1,"2097":3,"2098":2,"2099":1,"2100":1,"2103":6,"2104":2,"2108":1,"2109":4,"2110":2,"2112":2,"2113":2,"2124":1,"2138":1,"2141":1,"2162":3,"2163":2,"2164":2,"2168":1,"2169":1,"2170":1,"2177":2,"2179":1,"2182":1,"2185":1,"2188":1,"2205":2,"2212":1,"2213":1,"2216":1,"2217":1,"2218":1,"2226":6,"2229":5,"2231":4,"2238":2}}],["dosomething",{"3":{"1414":1}}],["dossier",{"3":{"1299":1}}],["dos",{"3":{"283":3,"1338":1,"2001":1}}],["dots",{"3":{"1116":1,"1284":1,"1285":1,"1310":2}}],["dot",{"3":{"564":1,"1241":1,"1247":1,"1285":1,"1349":1,"1435":1}}],["dotted",{"3":{"265":1,"333":1,"1126":1,"1241":3,"1247":8,"1270":1,"1299":1,"1310":1,"1323":6,"1358":4,"1435":1,"1665":1}}],["dotnetref",{"3":{"1323":2}}],["dotnetobjectreference",{"3":{"1323":7,"1416":3}}],["dotnet",{"2":{"132":1,"135":1,"291":1,"371":1,"372":1,"373":1,"374":1,"451":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"495":1,"527":1,"563":1,"634":1,"635":1,"675":1,"869":1,"906":1,"930":1,"932":1,"938":1,"1004":1,"1044":1,"1066":1,"1190":1,"1324":1,"1438":1,"1525":1,"1605":1,"1645":1,"1647":1,"1651":1,"1654":1,"1656":1,"1660":1,"1680":1,"1681":1,"1687":1,"1689":1,"1692":1,"1703":1,"1716":1,"1717":1,"1718":1,"1725":1,"1728":1,"1729":1,"1739":1,"1780":1,"1786":1,"1792":1,"1806":1,"1811":1,"1826":1,"1845":1,"1852":1,"1861":1,"1867":1,"1876":1,"1885":1,"1891":1,"1898":1,"1904":1,"1911":1,"1923":1,"1929":1,"1940":1,"1949":1,"1958":1,"1964":1,"1971":1,"1978":1,"1984":1,"1996":1,"2001":1,"2002":1,"2003":1,"2027":1,"2037":1,"2059":1,"2079":1,"2086":1,"2088":1,"2097":1,"2100":1,"2106":1,"2107":1,"2114":1,"2122":1,"2127":1,"2132":1,"2138":1,"2145":1,"2152":1,"2168":1,"2191":1,"2192":1,"2202":1,"2209":1,"2210":1},"3":{"0":7,"132":1,"135":2,"140":1,"291":2,"370":2,"371":1,"372":1,"373":4,"374":1,"397":1,"451":1,"483":1,"486":1,"487":1,"488":1,"489":1,"490":3,"491":3,"492":3,"493":4,"495":1,"527":1,"528":2,"563":1,"564":1,"599":1,"626":1,"632":1,"633":6,"634":1,"635":2,"675":1,"696":1,"698":1,"707":1,"715":1,"782":1,"861":1,"868":2,"869":2,"906":1,"914":2,"930":1,"932":1,"938":1,"982":1,"1004":1,"1044":1,"1066":1,"1067":1,"1069":1,"1190":1,"1212":2,"1214":1,"1273":1,"1283":3,"1285":18,"1299":1,"1310":1,"1322":3,"1324":1,"1325":1,"1327":1,"1338":1,"1429":1,"1435":4,"1438":1,"1440":1,"1444":12,"1448":1,"1452":7,"1453":5,"1454":6,"1455":1,"1458":4,"1460":1,"1463":2,"1466":1,"1485":4,"1486":1,"1489":3,"1491":2,"1525":2,"1576":1,"1590":1,"1605":1,"1645":1,"1647":3,"1648":2,"1649":2,"1650":2,"1651":2,"1653":2,"1654":2,"1656":4,"1660":3,"1663":1,"1676":1,"1680":1,"1681":3,"1683":1,"1685":1,"1687":1,"1689":2,"1692":2,"1696":1,"1700":2,"1703":4,"1716":1,"1717":4,"1718":3,"1725":2,"1726":3,"1727":2,"1728":2,"1729":1,"1739":1,"1780":1,"1786":2,"1792":1,"1806":1,"1811":1,"1816":1,"1826":1,"1833":1,"1845":1,"1852":1,"1861":1,"1867":1,"1876":1,"1885":1,"1891":1,"1898":1,"1904":1,"1911":1,"1923":1,"1929":1,"1940":1,"1949":1,"1958":1,"1964":1,"1971":1,"1978":1,"1984":1,"1990":1,"1996":1,"2001":1,"2002":1,"2003":2,"2013":2,"2027":1,"2037":1,"2057":1,"2059":1,"2079":1,"2085":1,"2086":1,"2088":1,"2096":2,"2097":2,"2100":3,"2106":1,"2107":1,"2114":1,"2122":1,"2127":1,"2132":1,"2138":1,"2145":1,"2152":1,"2155":1,"2159":1,"2168":1,"2179":1,"2191":1,"2192":1,"2202":1,"2209":1,"2210":1,"2231":1}}],["doing",{"3":{"72":1,"225":1,"236":1,"237":1,"373":1,"539":2,"549":1,"592":1,"633":1,"698":2,"757":1,"831":1,"867":1,"898":2,"1067":1,"1233":1,"1259":2,"1262":1,"1270":1,"1285":7,"1289":1,"1299":3,"1300":1,"1308":2,"1310":2,"1311":1,"1322":4,"1323":3,"1338":4,"1347":1,"1349":3,"1351":1,"1355":1,"1358":7,"1368":1,"1394":3,"1395":6,"1401":1,"1414":1,"1416":7,"1426":1,"1435":5,"1440":1,"1441":1,"1443":1,"1454":1,"1469":1,"1831":1,"2071":1,"2080":1,"2096":1,"2129":1,"2208":1}}],["donor",{"3":{"1495":1}}],["donotcoverthatpair",{"3":{"1435":1}}],["donotexceedconstructordependencyceiling",{"3":{"1435":18}}],["done",{"1":{"1899":1,"1900":1,"1901":1,"1902":1,"1903":1,"1904":1},"3":{"401":1,"435":1,"526":1,"709":1,"856":1,"857":1,"1124":1,"1237":1,"1239":1,"1270":3,"1285":2,"1303":1,"1308":1,"1309":1,"1310":1,"1317":1,"1323":6,"1338":1,"1358":1,"1394":3,"1401":1,"1416":1,"1435":1,"1495":1,"1528":1,"1530":1,"1574":2,"1588":1,"1595":1,"1610":1,"1640":1,"1650":1,"1663":1,"1700":1,"1727":1,"1778":1,"1839":1,"1878":1,"1898":1,"1899":1,"1934":1,"1936":1,"1999":1,"2096":1,"2100":1,"2128":1,"2193":1,"2207":1}}],["don",{"1":{"1827":1,"1828":1,"1829":1,"1830":1,"1831":1,"1832":1,"1833":1,"2123":1,"2124":1,"2125":1,"2126":1,"2127":1},"3":{"47":1,"1310":1,"1466":1,"1473":1,"1560":1,"1568":1,"1590":1,"1637":1,"1650":1,"1653":1,"1778":2,"1827":1,"1971":1,"2079":1,"2122":1,"2123":1,"2132":1,"2205":1,"2209":1}}],["docresource",{"3":{"1435":1}}],["doc",{"3":{"33":1,"68":2,"98":1,"135":1,"138":1,"139":3,"142":1,"251":1,"256":3,"257":1,"258":1,"322":1,"359":1,"364":1,"448":1,"453":1,"468":2,"476":1,"492":1,"543":1,"625":1,"682":1,"700":1,"751":1,"782":4,"784":1,"801":1,"819":1,"827":1,"837":1,"971":1,"976":1,"988":1,"990":1,"1003":1,"1074":1,"1190":1,"1219":2,"1226":1,"1228":1,"1229":15,"1231":1,"1232":1,"1233":1,"1235":1,"1237":30,"1239":1,"1247":46,"1259":69,"1262":1,"1263":1,"1264":1,"1269":1,"1270":59,"1271":11,"1273":1,"1275":1,"1277":1,"1278":2,"1282":3,"1283":2,"1285":135,"1299":113,"1300":5,"1308":50,"1309":3,"1310":84,"1311":5,"1314":1,"1315":1,"1316":1,"1322":68,"1323":159,"1325":1,"1326":1,"1329":2,"1331":2,"1332":1,"1338":145,"1341":2,"1346":1,"1349":98,"1358":100,"1368":3,"1379":50,"1390":1,"1394":147,"1395":162,"1401":68,"1414":99,"1415":3,"1416":55,"1418":1,"1421":1,"1422":1,"1426":6,"1430":1,"1435":279,"1442":1,"1450":1,"1487":3,"1488":3,"1489":1,"1495":8,"1525":4,"1533":2,"1584":1,"1585":2,"1590":2,"1595":1,"1596":1,"1599":1,"1684":1,"1685":5,"1814":1,"1820":1,"1895":2,"1993":1,"1994":1,"1999":1,"2003":1,"2038":1,"2040":1,"2042":2,"2043":1,"2044":5,"2047":1,"2065":1,"2084":1,"2103":1,"2111":1,"2116":1,"2121":1,"2128":1,"2148":1,"2158":1}}],["docs",{"0":{"1480":1},"3":{"1":2,"70":1,"99":1,"135":1,"140":1,"142":1,"147":2,"324":1,"358":2,"361":1,"362":1,"366":1,"370":1,"422":1,"423":1,"424":1,"425":1,"426":2,"490":1,"491":1,"492":1,"493":1,"495":1,"564":1,"582":1,"590":2,"599":1,"626":2,"756":2,"765":1,"766":1,"773":2,"860":1,"914":1,"1003":1,"1011":1,"1012":3,"1017":1,"1090":2,"1094":1,"1095":1,"1190":1,"1212":2,"1216":2,"1237":2,"1270":3,"1299":9,"1308":1,"1310":2,"1322":3,"1323":23,"1349":5,"1358":13,"1379":7,"1394":9,"1395":7,"1401":3,"1415":1,"1416":5,"1427":1,"1429":2,"1435":9,"1452":6,"1454":4,"1458":1,"1459":1,"1460":1,"1469":1,"1480":2,"1481":1,"1482":1,"1500":1,"1501":1,"1514":1,"1521":1,"1524":1,"1525":8,"1527":1,"1530":1,"1533":1,"1534":1,"1570":3,"1571":1,"1572":1,"1574":1,"1575":2,"1576":15,"1578":1,"1580":1,"1581":3,"1582":1,"1585":2,"1586":1,"1590":2,"1592":1,"1593":1,"1595":1,"1599":1,"1662":1,"1792":2,"1801":1,"1806":1,"1814":1,"1826":2,"1845":1,"1852":1,"1867":1,"1876":1,"1885":1,"1891":2,"1898":1,"1904":1,"1908":2,"1922":1,"1929":1,"1949":1,"1964":1,"1971":1,"1978":1,"1984":1,"1990":1,"1996":1,"1999":2,"2000":2,"2001":2,"2068":1,"2085":2,"2097":2,"2107":1,"2111":1,"2114":1,"2116":1,"2117":1,"2119":1,"2127":1,"2132":1,"2150":1,"2152":1,"2157":1,"2159":1,"2168":1,"2171":1,"2179":1,"2192":1,"2202":1,"2210":1,"2233":1}}],["docx",{"3":{"0":1,"443":1,"1116":2,"1284":1,"1346":1,"1349":1,"1394":1,"1630":1}}],["documentlanguagetests",{"3":{"1199":1,"1207":2,"1437":2}}],["documentformats",{"2":{"1284":1},"3":{"1199":5,"1203":1,"1207":1,"1284":1,"1285":5,"1358":2}}],["documentname",{"3":{"448":1,"1310":2,"2130":1}}],["documentingly",{"3":{"1349":1}}],["documenting",{"3":{"101":1,"251":1,"254":1,"257":1,"332":1,"557":1,"699":1,"805":1,"1247":1,"1285":2,"1299":1,"1310":2,"1322":2,"1323":1,"1338":3,"1379":1,"1395":1,"1495":1,"1551":1,"1590":1,"1658":1,"1989":1}}],["documentation",{"0":{"1570":1,"1673":1,"2044":1},"3":{"69":1,"135":1,"200":3,"230":1,"359":1,"364":2,"365":2,"425":1,"426":2,"459":1,"465":1,"466":1,"472":1,"529":2,"545":1,"547":1,"549":1,"593":1,"627":1,"862":1,"982":1,"986":1,"1006":1,"1011":1,"1012":1,"1067":1,"1070":1,"1092":1,"1108":2,"1124":2,"1133":1,"1161":1,"1216":1,"1221":1,"1229":1,"1237":3,"1247":1,"1270":3,"1271":1,"1299":4,"1308":3,"1310":6,"1311":2,"1321":1,"1323":3,"1338":5,"1349":3,"1351":1,"1358":9,"1368":2,"1394":2,"1395":5,"1414":2,"1427":1,"1435":43,"1446":1,"1452":1,"1456":1,"1458":1,"1485":1,"1488":1,"1491":1,"1492":1,"1495":1,"1498":1,"1570":1,"1581":1,"1584":1,"1590":1,"1591":1,"1600":1,"1685":1,"1795":1,"1940":1,"2013":1,"2044":1,"2046":1,"2055":1,"2141":1,"2143":1,"2162":1,"2164":3,"2166":2,"2173":1,"2174":1,"2176":1,"2177":1,"2179":1,"2188":2,"2195":1,"2201":1,"2232":1}}],["documentcontentsniffertests",{"3":{"1199":1,"1207":2,"1285":1}}],["documentcontentsniffer",{"2":{"443":1,"1116":1,"1284":1,"1356":1},"3":{"0":1,"443":2,"1116":2,"1199":4,"1203":1,"1207":3,"1212":1,"1284":2,"1285":20,"1346":1,"1349":1,"1356":1,"1358":5,"1495":2}}],["documents",{"0":{"452":1,"577":1},"2":{"452":1,"453":1},"3":{"0":4,"68":1,"121":1,"135":1,"189":1,"256":1,"290":1,"365":1,"418":1,"443":1,"446":1,"448":1,"451":2,"452":2,"453":1,"454":3,"570":1,"572":1,"573":1,"631":1,"635":1,"651":1,"744":1,"749":1,"756":1,"827":1,"946":1,"973":1,"979":1,"990":1,"996":1,"1016":1,"1050":1,"1114":1,"1126":1,"1133":1,"1229":3,"1247":2,"1259":2,"1270":3,"1271":1,"1278":1,"1285":7,"1299":9,"1309":1,"1310":12,"1322":6,"1323":8,"1338":6,"1351":1,"1358":13,"1368":1,"1379":3,"1394":6,"1395":12,"1398":1,"1401":2,"1405":1,"1409":1,"1414":4,"1415":2,"1416":8,"1426":2,"1435":11,"1452":1,"1454":4,"1457":1,"1466":2,"1472":1,"1473":2,"1495":2,"1499":1,"1523":1,"1525":2,"1530":2,"1574":1,"1576":2,"1585":1,"1595":1,"1639":1,"1650":1,"1652":1,"1669":1,"1678":1,"1683":1,"1685":1,"1697":1,"1700":1,"1860":1,"1882":1,"2094":1,"2131":1,"2132":1,"2231":1,"2232":1}}],["document",{"0":{"454":1,"1894":1},"1":{"1761":1,"1762":1,"1763":1,"1764":1,"1765":1,"1766":1,"1767":1,"1768":1,"1769":1,"1770":1,"1771":1,"1772":1,"1773":1,"1774":1,"1775":1,"1776":1},"2":{"450":1},"3":{"0":2,"31":3,"32":1,"102":1,"165":1,"166":2,"234":1,"239":1,"371":1,"395":2,"415":1,"418":1,"423":1,"424":1,"438":1,"443":2,"446":5,"448":4,"450":4,"451":4,"452":2,"453":1,"454":5,"472":1,"487":1,"490":1,"564":1,"570":1,"571":2,"572":2,"573":2,"577":3,"585":1,"625":1,"626":1,"724":1,"725":3,"726":2,"727":1,"743":1,"882":1,"905":1,"910":1,"974":1,"976":1,"1010":1,"1011":1,"1013":1,"1021":1,"1050":3,"1114":1,"1115":1,"1116":1,"1117":1,"1120":1,"1212":1,"1213":1,"1270":1,"1273":1,"1284":3,"1285":15,"1287":3,"1298":1,"1299":16,"1308":2,"1310":38,"1321":1,"1322":12,"1323":16,"1338":2,"1355":3,"1358":16,"1368":1,"1379":2,"1395":20,"1398":1,"1403":1,"1405":1,"1409":2,"1414":20,"1416":7,"1432":2,"1435":25,"1437":1,"1438":1,"1440":1,"1446":1,"1454":1,"1466":2,"1469":1,"1473":1,"1474":1,"1477":1,"1480":2,"1484":1,"1487":4,"1495":1,"1520":1,"1523":4,"1525":2,"1529":1,"1530":5,"1531":1,"1544":1,"1575":1,"1576":2,"1584":1,"1598":1,"1619":1,"1630":2,"1639":1,"1665":2,"1668":1,"1671":1,"1675":1,"1710":1,"1716":1,"1754":1,"1755":1,"1761":1,"1764":1,"1771":2,"1824":1,"1844":1,"1853":1,"1857":1,"1861":1,"1895":2,"1898":2,"1928":3,"1929":1,"2006":1,"2032":1,"2054":1,"2098":1,"2121":1,"2130":1,"2131":3,"2219":1,"2231":1,"2232":1}}],["documented",{"3":{"0":10,"37":1,"53":1,"107":1,"118":1,"122":1,"136":1,"178":1,"189":1,"200":2,"216":2,"237":1,"245":1,"248":1,"253":1,"291":1,"329":1,"333":1,"348":2,"359":1,"363":1,"415":1,"441":1,"450":1,"465":1,"484":1,"526":1,"527":1,"528":1,"545":1,"564":1,"571":1,"583":1,"599":1,"601":1,"602":1,"617":1,"620":1,"631":3,"635":1,"640":2,"657":1,"659":1,"674":1,"682":1,"690":1,"698":1,"732":1,"748":1,"749":1,"750":1,"808":1,"861":1,"897":2,"901":1,"907":2,"926":1,"930":1,"931":1,"932":1,"964":2,"966":2,"972":1,"973":1,"980":2,"981":1,"986":1,"988":1,"991":1,"1004":1,"1005":1,"1006":1,"1018":1,"1050":1,"1091":1,"1108":1,"1125":1,"1134":1,"1139":1,"1140":1,"1149":1,"1161":1,"1162":1,"1169":1,"1175":1,"1211":1,"1212":2,"1229":2,"1231":1,"1235":1,"1237":9,"1242":1,"1244":2,"1247":2,"1254":1,"1255":1,"1259":10,"1269":1,"1270":18,"1271":3,"1276":1,"1283":1,"1284":1,"1285":38,"1291":1,"1295":1,"1299":27,"1300":2,"1308":8,"1310":10,"1311":5,"1312":2,"1315":1,"1316":1,"1320":2,"1322":23,"1323":32,"1328":1,"1330":1,"1331":1,"1338":11,"1342":1,"1347":1,"1349":8,"1354":1,"1356":1,"1358":44,"1363":1,"1365":1,"1368":1,"1379":2,"1383":1,"1394":15,"1395":23,"1401":12,"1411":1,"1414":26,"1415":4,"1416":22,"1418":1,"1430":1,"1432":2,"1435":32,"1437":1,"1440":1,"1441":2,"1442":1,"1444":1,"1449":1,"1452":2,"1453":1,"1454":1,"1466":4,"1469":1,"1471":1,"1472":1,"1473":2,"1474":1,"1480":1,"1482":1,"1487":3,"1488":1,"1490":1,"1495":12,"1499":1,"1523":1,"1525":13,"1530":1,"1531":1,"1533":3,"1534":1,"1536":1,"1542":1,"1544":1,"1545":2,"1546":1,"1547":1,"1561":2,"1565":4,"1569":2,"1570":3,"1574":1,"1575":1,"1576":21,"1580":1,"1581":1,"1582":1,"1583":1,"1584":8,"1590":5,"1598":1,"1599":1,"1631":1,"1637":1,"1638":1,"1639":1,"1640":1,"1641":1,"1646":1,"1655":1,"1664":1,"1669":1,"1670":1,"1672":2,"1674":1,"1685":1,"1708":1,"1796":1,"1800":1,"1814":1,"1820":1,"1825":1,"1841":1,"1909":1,"2000":1,"2029":1,"2033":2,"2035":1,"2044":1,"2054":1,"2060":1,"2073":1,"2087":1,"2097":1,"2118":1,"2126":1,"2148":1,"2158":1,"2161":1,"2163":1,"2171":1,"2173":1,"2181":1,"2194":1,"2198":1,"2215":1,"2220":1,"2228":1,"2231":4,"2241":1}}],["dockeravailabilitytests",{"3":{"1199":1,"1207":2,"1486":1}}],["dockeravailability",{"3":{"1067":1,"1199":3,"1207":2,"1427":1,"1429":4,"1487":2}}],["dockerfile",{"3":{"867":1,"868":6,"869":19,"870":4,"872":3,"873":6,"876":14,"877":6,"1444":37,"1449":6,"1454":2,"1463":8}}],["dockerfiles",{"0":{"1444":1},"3":{"0":1,"868":1,"869":1,"871":1,"877":3,"1212":1,"1444":3,"1449":2,"1454":1,"1463":1}}],["docker",{"0":{"2052":1},"1":{"2049":1,"2050":1,"2051":1,"2052":1,"2053":1,"2054":1,"2055":1,"2056":1,"2057":1,"2058":1,"2059":1},"2":{"373":1,"869":1,"871":1,"1069":1},"3":{"0":3,"149":1,"152":1,"373":1,"625":1,"627":1,"642":1,"868":1,"869":1,"871":1,"873":1,"914":1,"915":1,"935":1,"1034":1,"1067":1,"1069":3,"1074":1,"1078":1,"1327":2,"1338":5,"1429":5,"1435":6,"1437":5,"1438":1,"1439":1,"1444":2,"1448":2,"1452":1,"1454":6,"1457":1,"1459":1,"1463":2,"1464":1,"1485":3,"1486":2,"1487":1,"1489":5,"1491":3,"1590":1,"1598":1,"1601":1,"1610":3,"1613":1,"1615":1,"1647":1,"1660":1,"1681":1,"1688":1,"1691":1,"1692":1,"1716":1,"1717":3,"1784":1,"2003":1,"2004":1,"2012":1,"2048":1,"2049":4,"2052":1,"2058":1,"2059":2,"2209":1,"2212":1,"2217":1,"2218":1}}],["downscale",{"3":{"1456":1}}],["downside",{"3":{"1440":1}}],["downstreamcheckprefix",{"3":{"1435":2}}],["downstreamreadinesscache",{"2":{"833":1},"3":{"830":1,"833":1}}],["downstreamservices",{"3":{"1435":1}}],["downstreamservicehealthcheck",{"2":{"402":1,"1442":1},"3":{"402":1,"827":1,"1199":3,"1203":1,"1207":2,"1332":1,"1336":2,"1338":12,"1442":1,"1495":2}}],["downstreams",{"3":{"99":2,"827":1,"1338":3,"1435":2,"2023":1}}],["downstreamprobeversion",{"2":{"99":2,"1446":1},"3":{"99":2,"1199":4,"1203":1,"1207":1,"1336":1,"1338":7,"1446":1}}],["downstream",{"0":{"99":1,"1336":1,"1677":1},"3":{"0":5,"25":1,"35":1,"53":1,"59":1,"97":1,"99":5,"100":1,"101":1,"146":1,"174":1,"349":1,"351":1,"369":1,"402":1,"459":1,"529":1,"572":2,"757":2,"761":2,"783":1,"826":2,"827":10,"828":2,"829":3,"830":1,"833":1,"834":1,"1066":1,"1068":1,"1212":2,"1247":1,"1252":1,"1259":1,"1263":1,"1270":1,"1271":2,"1282":1,"1283":1,"1285":6,"1295":1,"1299":4,"1300":1,"1308":4,"1310":18,"1311":5,"1322":7,"1323":6,"1324":1,"1327":3,"1328":1,"1331":2,"1336":8,"1337":2,"1338":47,"1343":1,"1349":5,"1358":13,"1394":1,"1395":7,"1401":8,"1408":1,"1414":4,"1415":1,"1416":3,"1426":4,"1428":1,"1429":1,"1432":2,"1435":19,"1437":4,"1440":2,"1441":1,"1442":1,"1446":9,"1452":2,"1454":1,"1466":1,"1471":1,"1475":1,"1486":1,"1487":8,"1543":1,"1574":3,"1576":2,"1577":1,"1581":1,"1582":1,"1584":1,"1669":1,"1670":1,"1676":1,"1783":1,"1810":1,"1811":1,"1820":1,"1835":1,"1851":1,"1894":1,"1953":1,"1973":2,"1976":1,"1978":2,"2006":1,"2009":1,"2018":1,"2023":2,"2027":1,"2054":1,"2055":1,"2065":1,"2113":2,"2114":2,"2126":1,"2135":1,"2171":1}}],["downward",{"3":{"1310":1,"1435":1}}],["downcasts",{"3":{"1270":1}}],["downgrading",{"3":{"641":1}}],["downgrades",{"3":{"1446":1}}],["downgraded",{"3":{"342":1,"1229":1,"1271":1,"1310":1,"1311":2,"1333":1,"1437":1,"1472":1,"1477":2,"1926":1}}],["downgrade",{"0":{"1477":2},"3":{"0":1,"98":1,"109":1,"235":1,"768":1,"790":1,"1067":1,"1192":1,"1222":1,"1229":1,"1299":1,"1326":1,"1338":3,"1379":1,"1435":2,"1442":1,"1443":1,"1465":1,"1466":3,"1469":1,"1472":2,"1477":7,"1481":3,"1482":2,"1483":2,"1590":1}}],["down",{"0":{"2009":1},"2":{"565":1},"3":{"0":7,"1":1,"58":1,"81":1,"100":1,"159":1,"234":1,"235":1,"256":1,"257":1,"293":1,"310":2,"351":1,"364":1,"413":1,"424":1,"475":1,"476":1,"490":4,"492":1,"494":1,"517":1,"543":1,"545":1,"546":1,"556":1,"563":1,"564":4,"565":1,"633":1,"640":1,"666":1,"684":1,"692":1,"700":1,"702":1,"707":1,"726":1,"749":1,"756":2,"757":1,"765":1,"766":1,"767":1,"821":1,"827":1,"853":1,"854":1,"859":1,"861":2,"868":1,"871":1,"881":1,"884":1,"912":1,"953":1,"963":1,"1003":1,"1004":1,"1018":2,"1019":1,"1050":1,"1082":1,"1084":1,"1091":1,"1101":1,"1118":1,"1133":1,"1152":1,"1186":1,"1229":3,"1235":1,"1238":1,"1247":5,"1259":3,"1270":5,"1285":7,"1299":5,"1306":1,"1308":5,"1309":1,"1310":2,"1320":1,"1322":6,"1323":24,"1332":1,"1333":1,"1338":12,"1349":5,"1351":1,"1357":1,"1358":16,"1368":4,"1372":2,"1379":1,"1394":24,"1395":15,"1401":6,"1414":3,"1415":5,"1416":10,"1421":1,"1426":2,"1435":26,"1437":3,"1440":1,"1442":1,"1444":1,"1446":3,"1454":6,"1466":3,"1475":2,"1477":1,"1479":1,"1523":1,"1530":1,"1536":1,"1555":1,"1565":1,"1685":1,"1725":1,"1730":1,"1747":1,"1748":1,"1764":1,"1783":1,"1796":1,"1798":3,"1800":1,"1809":1,"1824":2,"1826":2,"1836":1,"1863":2,"1908":1,"1910":1,"1911":3,"1943":1,"1945":2,"1947":1,"1971":1,"1999":1,"2006":1,"2027":1,"2031":1,"2033":1,"2036":1,"2037":2,"2039":1,"2042":1,"2043":1,"2055":1,"2056":1,"2061":1,"2063":1,"2066":1,"2068":1,"2069":1,"2088":1,"2117":1,"2164":1,"2168":2,"2173":1,"2188":1,"2191":1,"2197":1}}],["downloading",{"3":{"1358":1}}],["downloadable",{"3":{"1308":1}}],["downloaded",{"3":{"0":1,"555":1,"556":1,"743":1,"1116":1,"1118":1,"1341":1,"1349":1,"1415":1,"1453":1,"1455":1,"1466":1,"2066":1}}],["downloads",{"3":{"0":2,"527":1,"557":1,"1116":1,"1117":1,"1182":1,"1184":1,"1285":2,"1358":1,"1388":1,"1415":1,"1630":1,"2081":1,"2149":1}}],["download",{"3":{"0":1,"527":1,"530":1,"556":1,"725":2,"1116":1,"1117":1,"1184":1,"1284":1,"1285":3,"1299":1,"1310":3,"1322":1,"1323":9,"1343":1,"1349":2,"1356":1,"1358":2,"1379":1,"1394":1,"1409":1,"1414":3,"1437":2,"1454":1,"1466":1,"1668":1,"1671":1,"2216":1}}],["downtime",{"3":{"0":1,"361":1,"365":1,"1283":1,"1544":1,"1663":1,"1671":1,"2144":1,"2145":1}}],["do",{"0":{"1302":1,"1421":1,"1955":1,"2054":1},"1":{"2180":1,"2181":1,"2182":1,"2183":1,"2184":1,"2185":1,"2186":1,"2187":1,"2188":1,"2189":1,"2190":1,"2191":1,"2192":1},"3":{"0":3,"15":1,"17":1,"18":1,"24":1,"27":1,"47":1,"62":1,"63":1,"91":1,"97":1,"100":2,"102":1,"108":1,"109":2,"110":1,"115":1,"122":1,"123":1,"133":2,"146":1,"147":2,"156":1,"158":1,"170":1,"175":1,"176":1,"185":1,"187":1,"224":1,"228":1,"234":1,"235":1,"236":1,"243":1,"245":2,"255":1,"256":1,"265":2,"283":3,"306":1,"317":1,"319":1,"335":1,"345":1,"351":1,"353":1,"401":1,"408":1,"449":1,"450":1,"451":2,"459":3,"461":1,"466":1,"473":1,"481":2,"482":1,"497":1,"502":1,"518":2,"539":1,"541":1,"549":1,"557":1,"566":1,"573":1,"592":1,"611":1,"622":1,"625":2,"626":1,"642":1,"658":1,"665":2,"676":1,"681":1,"683":1,"684":1,"689":2,"692":1,"698":1,"725":2,"735":1,"743":2,"748":1,"749":1,"751":2,"757":1,"782":2,"783":1,"789":1,"790":1,"800":1,"802":1,"803":1,"811":1,"819":1,"821":1,"827":2,"829":1,"838":1,"861":1,"868":1,"869":2,"873":1,"875":1,"877":1,"889":1,"896":1,"897":2,"905":2,"907":1,"912":1,"914":1,"923":1,"926":2,"966":1,"971":1,"982":1,"987":1,"988":1,"989":1,"995":1,"996":1,"1019":1,"1020":1,"1026":1,"1034":1,"1041":2,"1049":1,"1053":1,"1090":1,"1091":2,"1116":1,"1117":1,"1120":1,"1141":1,"1151":2,"1168":1,"1170":1,"1174":1,"1184":1,"1229":2,"1235":1,"1237":4,"1239":2,"1240":1,"1241":1,"1244":2,"1247":9,"1248":2,"1249":1,"1253":2,"1256":1,"1258":1,"1259":7,"1269":1,"1270":14,"1271":3,"1278":2,"1285":34,"1286":1,"1289":1,"1296":1,"1299":14,"1300":6,"1302":1,"1308":6,"1309":5,"1310":23,"1311":1,"1312":1,"1316":1,"1317":1,"1322":20,"1323":21,"1331":2,"1338":14,"1341":1,"1344":1,"1347":1,"1349":21,"1351":2,"1352":1,"1353":1,"1356":1,"1357":1,"1358":37,"1368":3,"1372":1,"1376":1,"1379":9,"1384":1,"1386":1,"1394":15,"1395":29,"1398":1,"1400":2,"1401":8,"1402":1,"1414":9,"1415":4,"1416":32,"1417":2,"1422":1,"1426":3,"1430":1,"1431":1,"1434":1,"1435":60,"1437":3,"1441":2,"1442":2,"1443":1,"1444":1,"1452":2,"1453":1,"1454":2,"1466":3,"1469":1,"1470":1,"1471":2,"1472":1,"1473":1,"1474":2,"1475":2,"1487":2,"1488":2,"1489":3,"1490":2,"1491":1,"1495":1,"1502":1,"1512":1,"1525":1,"1531":1,"1533":3,"1538":1,"1552":2,"1571":5,"1585":1,"1595":2,"1599":1,"1629":1,"1630":1,"1631":1,"1634":1,"1637":2,"1640":2,"1645":1,"1648":1,"1650":1,"1674":1,"1675":1,"1681":1,"1682":1,"1683":1,"1684":1,"1691":1,"1692":1,"1700":1,"1705":1,"1722":1,"1723":1,"1726":1,"1727":2,"1728":1,"1729":1,"1734":1,"1740":1,"1747":1,"1748":1,"1758":1,"1766":2,"1768":1,"1778":1,"1781":1,"1787":1,"1792":2,"1793":1,"1796":1,"1798":1,"1799":1,"1801":2,"1803":1,"1804":1,"1806":2,"1809":1,"1811":1,"1814":2,"1816":1,"1818":2,"1824":1,"1825":2,"1835":2,"1844":1,"1845":2,"1846":1,"1848":1,"1849":1,"1850":1,"1856":3,"1859":1,"1861":1,"1866":2,"1867":1,"1870":1,"1875":2,"1876":2,"1880":1,"1882":1,"1887":1,"1890":1,"1897":3,"1898":3,"1901":1,"1903":1,"1908":1,"1911":1,"1914":1,"1919":1,"1922":1,"1923":1,"1926":1,"1929":1,"1931":1,"1938":1,"1945":2,"1946":1,"1949":3,"1951":1,"1954":1,"1955":1,"1958":2,"1959":3,"1960":1,"1964":1,"1965":1,"1969":1,"1972":1,"1974":1,"1980":2,"1981":1,"1985":1,"1990":1,"1991":1,"1992":2,"1993":2,"1995":1,"1996":2,"1997":2,"2000":2,"2001":2,"2010":1,"2023":2,"2031":1,"2039":2,"2041":2,"2042":1,"2046":2,"2047":1,"2059":1,"2062":2,"2063":1,"2068":2,"2076":1,"2079":1,"2084":1,"2085":1,"2090":1,"2092":1,"2093":1,"2096":2,"2099":2,"2100":1,"2106":3,"2108":1,"2115":1,"2117":2,"2118":1,"2127":1,"2130":1,"2133":1,"2134":1,"2135":1,"2138":1,"2141":1,"2152":1,"2153":1,"2154":2,"2155":1,"2160":4,"2164":2,"2165":1,"2166":1,"2169":1,"2170":2,"2179":4,"2180":3,"2181":1,"2182":3,"2194":2,"2195":1,"2198":1,"2201":1,"2202":3,"2205":1}}],["doesn",{"3":{"1556":1,"1562":1,"1565":1,"1748":1,"1754":1,"1775":1}}],["doesnotcalltheservice",{"3":{"1435":1}}],["doesnotcalltheserviceandshowstheform",{"3":{"1435":1}}],["doesnotleakpseudosentinel",{"3":{"1435":1}}],["doesnotflag",{"3":{"1435":1}}],["doesnotreport",{"3":{"1435":6}}],["doesnotshareitsduplicateguardacrossbuilders",{"3":{"1338":1}}],["doesnotthrowandcommitsonce",{"3":{"988":1}}],["doesnotuserawqueryablesurfaces",{"2":{"0":1,"700":1,"1814":1},"3":{"0":1,"545":1,"700":1,"1435":2,"1814":1}}],["does",{"0":{"1265":1,"1423":1,"1676":1,"1796":1,"1837":1,"2186":1},"3":{"0":27,"15":1,"17":1,"19":1,"20":1,"21":1,"23":1,"24":1,"26":2,"27":1,"31":1,"32":2,"39":4,"41":1,"49":1,"62":1,"68":1,"71":1,"72":1,"80":1,"81":2,"91":1,"95":1,"96":1,"109":7,"111":2,"117":1,"121":1,"122":2,"124":1,"126":2,"128":1,"131":1,"135":3,"136":2,"137":1,"147":1,"148":1,"150":1,"158":1,"160":2,"161":1,"164":1,"170":1,"175":1,"178":1,"180":1,"181":1,"185":1,"186":1,"187":1,"188":1,"193":1,"195":2,"197":1,"200":2,"202":2,"206":1,"207":1,"208":1,"209":1,"216":2,"225":1,"234":1,"236":1,"237":4,"242":1,"243":2,"245":3,"253":1,"255":2,"256":2,"260":1,"265":6,"266":1,"273":1,"275":1,"280":1,"282":1,"283":3,"291":2,"299":1,"300":1,"304":1,"305":1,"306":2,"310":2,"311":1,"312":1,"318":6,"320":1,"325":1,"330":1,"333":1,"341":4,"342":1,"343":4,"344":1,"350":4,"353":1,"365":2,"366":1,"369":2,"370":1,"371":1,"372":1,"380":3,"384":1,"388":3,"389":1,"391":1,"396":1,"397":3,"400":1,"404":2,"405":1,"416":1,"420":1,"422":1,"428":1,"442":2,"443":1,"444":1,"448":3,"450":1,"459":1,"460":1,"463":1,"470":1,"490":2,"491":1,"492":1,"493":1,"499":2,"509":1,"517":2,"520":2,"523":1,"526":1,"527":1,"528":1,"535":1,"536":2,"538":1,"539":2,"543":1,"544":1,"547":1,"549":1,"550":1,"551":1,"552":1,"556":2,"558":3,"563":1,"564":4,"572":1,"573":2,"574":1,"582":1,"583":2,"585":3,"591":1,"592":2,"593":4,"598":1,"599":2,"600":2,"601":1,"602":1,"607":3,"608":1,"609":6,"610":1,"611":3,"618":1,"620":2,"624":1,"626":4,"628":2,"633":2,"635":3,"640":2,"642":2,"649":3,"651":1,"657":2,"660":1,"664":1,"665":1,"666":1,"668":2,"673":1,"674":3,"676":2,"678":2,"682":3,"684":1,"685":1,"689":2,"691":1,"692":1,"693":1,"697":2,"698":3,"699":1,"700":1,"705":1,"707":2,"708":2,"709":1,"711":1,"714":2,"715":2,"716":1,"717":1,"718":1,"723":1,"724":1,"725":5,"726":3,"733":2,"735":1,"736":1,"740":2,"741":1,"742":1,"743":3,"748":2,"749":3,"751":1,"757":1,"759":1,"764":2,"765":2,"766":4,"767":1,"768":2,"770":1,"774":1,"775":1,"776":1,"781":1,"782":2,"784":1,"789":4,"790":3,"792":1,"793":2,"794":1,"795":2,"799":1,"800":2,"809":1,"810":3,"811":1,"812":1,"813":1,"815":1,"818":1,"819":3,"820":1,"821":1,"825":2,"826":1,"827":8,"829":2,"830":1,"831":1,"833":1,"834":1,"836":2,"837":1,"838":1,"840":1,"842":1,"849":2,"853":2,"854":1,"856":2,"860":1,"863":2,"869":2,"870":1,"873":2,"875":1,"876":2,"881":2,"883":2,"885":1,"888":2,"889":1,"890":2,"891":2,"896":1,"897":1,"899":2,"905":5,"907":1,"910":1,"913":1,"914":1,"915":1,"923":2,"924":1,"926":4,"931":1,"932":1,"937":1,"946":2,"947":1,"953":1,"954":1,"956":2,"958":1,"960":1,"963":2,"965":2,"966":1,"971":2,"972":4,"973":1,"979":1,"980":1,"982":3,"990":2,"992":2,"995":5,"996":3,"1006":1,"1013":1,"1018":3,"1020":2,"1025":1,"1026":1,"1028":1,"1034":2,"1035":2,"1036":1,"1041":1,"1042":2,"1050":4,"1052":4,"1053":1,"1059":1,"1061":1,"1062":1,"1066":1,"1067":1,"1069":2,"1070":1,"1074":2,"1075":1,"1076":1,"1077":1,"1079":2,"1082":1,"1084":1,"1090":4,"1091":6,"1092":1,"1093":1,"1100":2,"1102":2,"1107":1,"1108":2,"1110":1,"1115":1,"1116":1,"1117":1,"1119":1,"1123":1,"1124":4,"1125":1,"1132":1,"1133":2,"1134":2,"1135":2,"1142":1,"1152":1,"1157":1,"1160":1,"1161":2,"1163":1,"1168":3,"1182":1,"1183":2,"1184":1,"1186":1,"1188":1,"1193":1,"1212":4,"1216":1,"1217":1,"1218":1,"1220":1,"1223":1,"1225":1,"1226":1,"1228":2,"1229":16,"1231":1,"1232":1,"1233":1,"1235":6,"1237":26,"1240":1,"1241":5,"1242":2,"1243":1,"1244":1,"1247":31,"1248":2,"1251":1,"1252":2,"1256":1,"1258":1,"1259":23,"1260":1,"1261":2,"1263":3,"1264":4,"1265":2,"1266":1,"1267":2,"1270":49,"1271":12,"1273":3,"1274":1,"1275":3,"1278":2,"1279":2,"1280":1,"1282":1,"1285":113,"1286":1,"1287":1,"1288":1,"1289":3,"1290":1,"1292":3,"1293":2,"1294":2,"1295":1,"1296":1,"1299":81,"1300":11,"1303":2,"1306":4,"1307":2,"1308":22,"1309":8,"1310":64,"1311":9,"1312":2,"1314":3,"1316":2,"1317":3,"1319":6,"1321":1,"1322":67,"1323":110,"1326":1,"1327":2,"1329":1,"1331":1,"1332":1,"1334":1,"1335":1,"1336":1,"1337":1,"1338":54,"1339":1,"1340":1,"1341":1,"1342":1,"1343":3,"1344":1,"1345":1,"1347":2,"1348":2,"1349":49,"1351":4,"1352":2,"1354":2,"1356":1,"1357":2,"1358":214,"1361":3,"1362":2,"1364":2,"1365":2,"1368":27,"1372":1,"1374":1,"1375":1,"1377":2,"1379":26,"1386":1,"1388":3,"1389":2,"1394":103,"1395":117,"1397":1,"1399":7,"1401":38,"1404":5,"1405":2,"1407":2,"1408":2,"1409":3,"1412":1,"1414":67,"1415":11,"1416":45,"1417":2,"1422":2,"1423":2,"1424":1,"1425":1,"1426":18,"1427":1,"1428":1,"1430":4,"1431":1,"1432":3,"1434":5,"1435":183,"1437":12,"1440":5,"1441":2,"1442":3,"1444":3,"1445":2,"1446":2,"1448":1,"1452":5,"1453":2,"1454":26,"1455":4,"1456":2,"1457":1,"1458":2,"1459":5,"1460":1,"1464":2,"1466":27,"1470":1,"1472":1,"1474":7,"1475":5,"1477":2,"1479":2,"1480":4,"1482":2,"1487":6,"1488":10,"1489":8,"1490":1,"1491":9,"1495":3,"1523":1,"1524":1,"1525":5,"1527":1,"1530":2,"1531":1,"1533":5,"1552":1,"1571":3,"1576":2,"1578":1,"1581":1,"1584":2,"1590":2,"1595":1,"1598":4,"1607":1,"1626":1,"1629":1,"1630":6,"1631":1,"1634":2,"1635":1,"1637":10,"1638":13,"1639":3,"1640":3,"1645":1,"1648":1,"1650":4,"1652":5,"1654":1,"1663":1,"1667":1,"1669":1,"1674":1,"1676":4,"1682":1,"1683":4,"1684":2,"1685":2,"1687":1,"1690":1,"1698":1,"1699":1,"1700":2,"1701":1,"1702":1,"1708":1,"1716":1,"1722":3,"1723":1,"1724":1,"1725":1,"1726":6,"1727":2,"1728":1,"1729":1,"1741":1,"1747":1,"1748":1,"1749":1,"1750":1,"1759":1,"1760":1,"1763":1,"1764":5,"1768":1,"1775":1,"1782":1,"1789":4,"1791":1,"1792":2,"1798":1,"1800":1,"1801":1,"1802":4,"1803":1,"1804":3,"1809":4,"1812":1,"1814":1,"1819":2,"1820":1,"1822":1,"1823":2,"1824":2,"1827":1,"1831":4,"1832":1,"1835":1,"1836":2,"1837":1,"1838":1,"1840":2,"1841":1,"1842":2,"1843":2,"1844":1,"1846":1,"1848":3,"1850":1,"1851":2,"1853":1,"1855":1,"1856":2,"1858":4,"1860":1,"1863":1,"1864":1,"1865":1,"1866":1,"1867":1,"1868":1,"1869":2,"1870":1,"1873":1,"1874":1,"1875":2,"1878":2,"1879":1,"1880":2,"1881":2,"1882":3,"1886":1,"1889":1,"1890":2,"1895":2,"1899":1,"1900":1,"1904":1,"1906":2,"1907":2,"1910":1,"1911":1,"1912":1,"1913":1,"1914":1,"1915":1,"1916":2,"1918":2,"1921":6,"1922":3,"1925":1,"1926":2,"1927":1,"1929":1,"1930":1,"1932":3,"1934":2,"1935":1,"1937":1,"1939":2,"1940":1,"1942":1,"1944":1,"1945":2,"1951":1,"1954":1,"1957":2,"1959":1,"1960":1,"1961":2,"1963":1,"1964":2,"1965":1,"1967":1,"1968":1,"1970":1,"1975":2,"1976":1,"1977":1,"1978":1,"1981":1,"1983":2,"1987":2,"1993":1,"1994":4,"1995":3,"1997":1,"1998":1,"1999":4,"2000":3,"2001":1,"2002":1,"2006":2,"2007":2,"2009":1,"2010":1,"2015":2,"2019":2,"2020":1,"2023":3,"2024":1,"2027":1,"2029":1,"2031":3,"2034":1,"2035":1,"2036":1,"2037":1,"2040":2,"2042":1,"2043":3,"2045":2,"2046":3,"2047":1,"2052":1,"2054":1,"2056":1,"2060":2,"2062":1,"2065":2,"2067":1,"2069":4,"2071":4,"2072":1,"2075":1,"2076":1,"2078":1,"2079":1,"2080":1,"2081":1,"2082":1,"2084":3,"2091":3,"2092":1,"2097":2,"2098":2,"2099":1,"2104":1,"2105":1,"2107":1,"2108":1,"2110":1,"2112":1,"2113":2,"2117":1,"2121":2,"2125":5,"2126":1,"2130":1,"2131":3,"2133":1,"2135":1,"2138":1,"2141":1,"2142":1,"2147":2,"2149":3,"2151":1,"2153":2,"2154":1,"2155":2,"2159":1,"2161":1,"2162":3,"2163":1,"2165":2,"2166":1,"2167":2,"2170":1,"2171":1,"2173":1,"2178":1,"2179":1,"2180":2,"2182":3,"2184":1,"2185":1,"2191":2,"2192":1,"2194":1,"2196":1,"2231":1}}],["drudgery",{"3":{"1358":1}}],["dry",{"3":{"1229":1,"1237":1,"1271":1,"1299":1,"1322":1,"1349":1,"1435":1,"1495":2,"2069":1}}],["drown",{"3":{"1526":1}}],["drove",{"3":{"1021":1,"1346":1,"1349":1,"1367":1,"1368":1,"1491":1}}],["dropdatabaseasync",{"3":{"1435":1}}],["dropdeferred",{"2":{"1285":1},"3":{"1274":1,"1285":4,"1839":1}}],["dropdowns",{"3":{"1244":1,"1247":2,"1285":1,"1310":1,"1323":2,"1343":1,"1349":1,"1370":1,"1394":1,"1639":1,"1747":2,"2073":1}}],["dropdown",{"3":{"329":1,"330":1,"1310":4,"1379":1,"1394":5,"1631":1,"1772":1,"1986":1,"1987":1}}],["dropforeignkey",{"2":{"566":1},"3":{"566":1}}],["dropindex",{"2":{"293":1},"3":{"0":2,"293":1,"564":4,"1454":2}}],["droptable",{"2":{"293":1},"3":{"0":1,"293":1,"564":2,"1454":2}}],["dropcolumn",{"2":{"293":1,"563":1},"3":{"0":1,"293":1,"563":1,"564":2,"1454":2}}],["dropschema",{"2":{"566":1},"3":{"566":1}}],["drops",{"3":{"0":5,"20":1,"120":1,"132":1,"150":1,"243":2,"261":1,"383":2,"395":1,"397":3,"398":1,"399":1,"409":1,"505":1,"511":2,"512":1,"543":2,"549":1,"564":1,"565":1,"572":1,"583":1,"586":1,"591":1,"609":1,"633":2,"640":1,"649":1,"674":1,"675":1,"698":1,"700":1,"709":1,"775":1,"831":1,"867":1,"870":1,"881":1,"891":2,"897":1,"917":1,"930":1,"988":2,"1042":1,"1102":1,"1124":2,"1184":1,"1191":1,"1212":1,"1215":1,"1229":1,"1240":1,"1247":4,"1265":1,"1271":1,"1276":1,"1278":1,"1280":2,"1285":10,"1299":1,"1308":1,"1309":1,"1310":5,"1311":3,"1322":4,"1323":9,"1332":1,"1338":1,"1349":4,"1358":16,"1361":1,"1368":1,"1374":1,"1379":4,"1394":8,"1395":10,"1414":3,"1415":1,"1416":5,"1428":1,"1435":17,"1437":3,"1442":2,"1444":1,"1454":1,"1459":1,"1465":1,"1466":3,"1473":1,"1477":1,"1487":1,"1490":1,"1491":1,"1495":1,"1544":1,"1590":1,"1599":1,"1665":1,"1682":3,"1684":1,"1726":3,"1747":1,"1766":1,"1784":1,"1840":1,"1850":1,"1858":1,"1865":1,"1875":1,"1917":1,"1921":2,"1943":1,"1947":1,"1949":1,"2046":1,"2054":1,"2057":1,"2074":1,"2085":1,"2120":1,"2155":1,"2156":1,"2159":1,"2187":1}}],["dropoldest",{"2":{"383":1,"518":1,"520":1,"1099":1,"1100":1,"1101":1,"1102":1,"1946":1,"2182":1},"3":{"0":4,"383":1,"518":2,"520":1,"1099":1,"1100":3,"1101":1,"1102":1,"1212":2,"1395":7,"1399":2,"1943":1,"1946":2,"2182":2}}],["drop",{"0":{"1477":1},"2":{"1332":1,"1442":1},"3":{"0":5,"383":1,"395":1,"397":1,"551":1,"564":1,"565":2,"566":3,"573":1,"617":1,"698":1,"732":1,"741":1,"869":1,"871":1,"890":1,"893":1,"897":1,"905":1,"907":2,"910":1,"916":1,"932":1,"1019":1,"1085":1,"1100":1,"1101":1,"1124":1,"1237":1,"1247":2,"1257":1,"1259":2,"1270":2,"1274":1,"1285":7,"1289":1,"1299":2,"1300":2,"1310":3,"1322":1,"1323":7,"1332":3,"1336":1,"1338":7,"1358":3,"1368":1,"1379":1,"1385":1,"1394":10,"1395":5,"1399":1,"1401":2,"1414":1,"1416":4,"1427":1,"1435":8,"1437":1,"1442":2,"1444":1,"1454":1,"1455":1,"1462":1,"1466":7,"1469":1,"1470":1,"1472":1,"1477":4,"1479":1,"1481":2,"1482":2,"1483":1,"1489":2,"1495":2,"1523":1,"1533":2,"1544":1,"1576":1,"1590":1,"1595":2,"1610":1,"1616":1,"1652":2,"1667":1,"1668":1,"1676":1,"1683":1,"1686":1,"1687":1,"1688":1,"1701":1,"1726":1,"1764":1,"1811":1,"1852":1,"1861":1,"1921":1,"1923":1,"1939":1,"1943":2,"1946":1,"1949":2,"1957":1,"1958":1,"1972":1,"2009":2,"2023":1,"2025":1,"2044":1,"2141":1,"2155":4,"2159":4,"2192":1,"2196":1,"2231":1}}],["droppable",{"3":{"2001":1}}],["dropprimarykey",{"2":{"566":1},"3":{"566":1}}],["dropping",{"3":{"0":3,"24":1,"26":1,"175":1,"196":1,"402":1,"512":1,"545":1,"549":1,"607":1,"633":1,"698":1,"749":1,"838":1,"869":1,"988":1,"989":1,"1012":1,"1028":1,"1241":2,"1247":1,"1257":1,"1259":2,"1270":1,"1285":3,"1299":1,"1310":3,"1322":1,"1323":2,"1338":2,"1354":1,"1358":7,"1379":1,"1394":1,"1395":5,"1415":2,"1416":2,"1426":2,"1437":1,"1442":1,"1466":3,"1472":1,"1488":2,"1489":1,"1525":1,"1704":1,"1726":1,"1814":1,"1946":1,"2000":1,"2186":1}}],["droppedcount",{"2":{"383":1,"1948":1},"3":{"383":1,"1100":1,"1395":4,"1943":1,"1948":1}}],["dropped",{"3":{"0":5,"1":2,"17":1,"19":1,"26":1,"45":1,"47":1,"72":1,"109":1,"110":1,"175":1,"265":1,"279":1,"312":1,"324":1,"335":1,"353":1,"383":1,"386":1,"397":2,"398":1,"399":1,"407":2,"409":1,"427":1,"428":1,"468":1,"518":2,"526":1,"534":1,"535":1,"538":1,"592":1,"607":1,"633":1,"638":1,"664":1,"703":1,"764":2,"766":2,"856":1,"881":1,"914":1,"916":1,"931":1,"988":2,"991":1,"1116":1,"1117":1,"1119":1,"1123":1,"1139":1,"1144":1,"1149":1,"1163":1,"1191":1,"1196":2,"1202":1,"1212":1,"1241":2,"1247":2,"1252":1,"1263":2,"1270":2,"1285":6,"1299":1,"1300":3,"1308":1,"1310":7,"1322":2,"1323":8,"1338":5,"1358":9,"1378":1,"1379":1,"1394":7,"1395":12,"1398":1,"1401":2,"1414":4,"1415":3,"1416":4,"1426":2,"1435":6,"1437":4,"1440":1,"1442":1,"1458":1,"1466":4,"1468":1,"1472":1,"1474":1,"1477":1,"1487":2,"1491":1,"1495":24,"1499":1,"1546":1,"1638":1,"1663":1,"1676":2,"1696":1,"1701":1,"1805":1,"1812":1,"1813":1,"1839":3,"1921":1,"1928":1,"1943":1,"1987":1,"1999":2,"2001":1,"2009":1,"2088":1,"2155":2,"2163":1,"2167":1,"2181":1}}],["dressed",{"3":{"1019":1}}],["drew",{"3":{"571":1}}],["drama",{"3":{"2160":1,"2165":1}}],["dragging",{"3":{"1285":1,"1300":1,"1358":1,"1394":1,"1395":1,"1403":1,"1435":1,"1454":1,"1804":1,"2164":1}}],["dragged",{"3":{"608":1,"1217":1,"1466":1,"2157":1}}],["drags",{"3":{"673":1,"1067":1,"1285":2,"1310":1,"1324":1,"1349":1,"1435":1,"1437":1,"1458":1,"1584":1,"1862":1,"2003":1,"2072":1,"2171":1}}],["drag",{"3":{"640":1,"1061":1,"1285":1,"1299":1,"1305":1,"1308":1,"1435":3,"1458":1,"2070":1}}],["drafts",{"3":{"464":1}}],["draft",{"2":{"384":1},"3":{"384":1,"1349":1,"1379":2,"1383":1,"1388":2,"1394":5,"1395":3,"1397":1,"1398":1,"1401":20,"1435":1,"1629":1,"1631":2}}],["draw",{"3":{"799":1,"1285":2,"1299":1,"1300":1,"1308":1,"1323":1,"1349":2,"1394":2,"1395":2,"1401":1,"1416":3,"1571":1,"1787":1,"1792":1,"2001":1,"2145":1,"2202":1,"2240":1}}],["drawing",{"2":{"681":1,"1479":2},"3":{"681":1,"1414":1,"1435":1,"1479":3}}],["drawers",{"3":{"650":1,"1323":1}}],["drawer",{"3":{"648":1,"649":1,"1323":4,"1576":1,"1590":1,"1599":2,"1668":1,"1711":1,"1712":2,"1713":1,"1740":1,"1741":2,"1749":2,"1758":1,"1767":1,"1772":1,"2076":1}}],["draws",{"3":{"310":1,"359":1,"499":1,"946":1,"1059":2,"1259":3,"1262":1,"1270":1,"1285":5,"1299":3,"1308":1,"1310":1,"1322":2,"1323":3,"1338":2,"1349":1,"1358":3,"1368":2,"1379":1,"1394":2,"1395":1,"1414":2,"1416":2,"1432":1,"1435":1,"1454":1,"1981":1,"2054":1,"2141":1,"2200":1}}],["drawn",{"3":{"24":1,"136":1,"1262":1,"1285":2,"1299":1,"1310":1,"1322":1,"1323":1,"1358":1,"1368":1,"1395":1,"1416":1,"1486":1,"1493":1,"1516":1,"1630":1,"1792":1,"1998":1,"2043":1,"2098":1,"2135":1,"2238":1}}],["drainallasync",{"3":{"1259":2,"1275":1,"1285":2}}],["drainable",{"3":{"1237":1}}],["drainer",{"3":{"1248":1}}],["drained",{"3":{"0":1,"78":1,"702":1,"845":1,"848":1,"849":1,"1068":1,"1237":1,"1254":1,"1259":4,"1285":2,"1322":1,"1379":1,"1395":1,"1401":1,"1414":1,"1435":1,"1477":1,"1807":1,"1841":1,"1888":1,"1889":1,"1890":1,"1946":1,"2019":1}}],["draining",{"3":{"256":1,"573":1,"847":2,"1257":2,"1259":2,"1275":1,"1285":1,"1322":1,"1366":1,"1416":1,"1437":1,"1664":1,"1890":1,"1944":1}}],["drain",{"0":{"2187":1},"1":{"515":1,"516":1,"517":1,"518":1,"519":1,"520":1,"521":1,"522":1,"523":1,"524":1,"1097":1,"1098":1,"1099":1,"1100":1,"1101":1,"1102":1,"1103":1,"1104":1},"3":{"0":6,"15":1,"20":1,"24":1,"78":1,"383":1,"515":1,"516":1,"517":1,"518":6,"520":1,"541":1,"573":1,"706":2,"709":1,"897":1,"1016":1,"1018":1,"1022":1,"1043":1,"1068":1,"1097":1,"1100":4,"1101":1,"1102":1,"1150":1,"1152":1,"1157":1,"1212":2,"1233":1,"1237":1,"1259":7,"1275":4,"1285":4,"1306":2,"1308":4,"1317":1,"1322":2,"1338":2,"1366":1,"1367":1,"1368":1,"1395":20,"1399":1,"1401":9,"1416":1,"1435":3,"1437":1,"1466":2,"1495":1,"1524":1,"1576":1,"1664":1,"1674":1,"1838":1,"1845":1,"1888":1,"1891":1,"1943":2,"1945":1,"1946":2,"1948":2,"1949":2,"2153":1,"2155":2,"2159":2,"2182":1,"2186":1,"2192":1,"2231":2}}],["drainsource",{"3":{"1285":1}}],["drains",{"3":{"0":2,"25":1,"27":1,"47":1,"124":1,"193":1,"198":1,"255":1,"413":1,"518":1,"571":1,"583":1,"673":1,"698":1,"701":1,"703":1,"1034":1,"1037":1,"1041":1,"1100":1,"1140":1,"1212":1,"1237":1,"1254":2,"1257":1,"1258":1,"1259":8,"1273":1,"1275":3,"1285":7,"1317":1,"1322":4,"1323":1,"1395":2,"1398":1,"1401":1,"1414":1,"1415":1,"1416":6,"1435":2,"1437":1,"1525":1,"1576":1,"1664":1,"1789":1,"1811":1,"1836":1,"1840":2,"1850":2,"2011":1,"2120":1,"2156":1,"2159":1,"2182":1}}],["dr",{"0":{"1473":1,"1474":2,"2035":1},"3":{"0":3,"68":2,"69":1,"70":1,"87":1,"625":4,"626":5,"628":4,"756":1,"971":1,"972":1,"973":1,"974":1,"1212":3,"1451":1,"1454":2,"1459":15,"1460":2,"1462":1,"1466":1,"1470":3,"1472":1,"1473":7,"1474":58,"1475":1,"1481":4,"1482":8,"1483":2,"1485":2,"1495":4,"1524":3,"1525":10,"1533":1,"1534":2,"1587":1,"1589":3,"1590":9,"1595":1,"1599":3,"1705":2,"1709":2,"2033":3,"2036":1,"2037":1,"2231":1}}],["drilling",{"3":{"1525":1,"1554":1}}],["drills",{"3":{"1473":1,"1706":1,"2037":1}}],["drillresult",{"3":{"1199":2,"1207":1}}],["drill",{"0":{"1474":3,"1709":1},"2":{"1474":3},"3":{"0":2,"68":1,"69":1,"70":1,"625":7,"626":3,"627":1,"628":3,"630":1,"1212":1,"1437":1,"1451":1,"1454":1,"1459":23,"1460":2,"1466":1,"1470":4,"1473":16,"1474":78,"1481":6,"1482":11,"1483":4,"1495":5,"1524":3,"1525":8,"1534":2,"1574":1,"1576":3,"1581":1,"1585":1,"1589":2,"1590":7,"1599":2,"1669":1,"1673":1,"1705":4,"1706":5,"1708":4,"1709":5,"1799":1,"2028":1,"2033":10,"2034":1,"2036":4,"2037":4,"2145":1,"2231":1}}],["drilled",{"0":{"2033":1},"1":{"2028":1,"2029":1,"2030":1,"2031":1,"2032":1,"2033":1,"2034":1,"2035":1,"2036":1,"2037":1},"3":{"0":1,"68":2,"69":1,"71":1,"294":1,"568":1,"608":1,"614":1,"625":1,"630":1,"758":1,"762":1,"1212":1,"1466":1,"1473":1,"1474":1,"1524":1,"1525":1,"1534":1,"1587":1,"1589":1,"1590":1,"1599":1,"1783":1,"2027":1,"2028":3,"2033":2,"2036":2,"2037":1,"2208":1,"2231":1}}],["driving",{"3":{"0":2,"39":1,"572":1,"650":1,"1018":1,"1229":1,"1259":1,"1285":2,"1299":1,"1310":1,"1323":3,"1338":1,"1401":1,"1416":2,"1430":1,"1435":9,"1436":1,"1437":1,"1576":1,"1998":1,"2053":1}}],["drive",{"3":{"864":1,"953":1,"1162":1,"1229":3,"1230":1,"1248":1,"1256":1,"1259":4,"1285":3,"1316":1,"1319":1,"1322":1,"1323":8,"1334":1,"1338":1,"1341":1,"1349":1,"1358":4,"1368":1,"1395":3,"1401":1,"1414":1,"1416":1,"1427":1,"1435":7,"1437":1,"1486":1,"1488":2,"1512":1,"1525":1,"1565":1,"1590":1,"1867":1,"1947":1,"1999":1,"2078":1,"2168":1,"2238":1}}],["driven",{"0":{"1354":1,"1507":1,"1540":1,"1542":1},"1":{"297":1,"298":1,"299":1,"300":1,"301":1,"302":1,"303":1,"304":1,"305":1,"306":1},"3":{"0":5,"96":1,"117":2,"126":1,"136":1,"142":1,"157":1,"165":1,"200":1,"297":1,"359":1,"365":1,"402":1,"507":1,"549":1,"564":1,"572":1,"574":1,"599":1,"690":1,"706":2,"759":1,"838":1,"856":1,"881":2,"916":1,"923":1,"927":1,"947":1,"1012":1,"1050":1,"1124":2,"1194":1,"1202":1,"1203":1,"1212":3,"1213":1,"1216":2,"1225":1,"1230":1,"1231":2,"1234":1,"1236":1,"1237":11,"1238":1,"1239":1,"1245":1,"1247":5,"1249":1,"1259":28,"1260":1,"1269":2,"1270":10,"1271":2,"1280":1,"1285":22,"1299":2,"1303":1,"1308":17,"1310":7,"1311":3,"1315":1,"1316":1,"1322":12,"1323":20,"1325":1,"1332":1,"1338":2,"1339":1,"1342":1,"1344":1,"1345":1,"1349":52,"1351":1,"1354":1,"1358":84,"1368":1,"1369":1,"1379":9,"1382":1,"1383":1,"1389":1,"1394":7,"1395":36,"1401":23,"1402":1,"1404":1,"1412":1,"1414":26,"1415":1,"1416":10,"1420":1,"1426":1,"1434":1,"1435":62,"1436":1,"1437":8,"1440":2,"1442":2,"1445":2,"1449":2,"1453":1,"1454":1,"1462":1,"1466":1,"1475":1,"1486":1,"1487":3,"1488":3,"1492":1,"1495":5,"1498":2,"1511":1,"1524":1,"1525":3,"1534":2,"1545":1,"1546":1,"1550":1,"1556":1,"1567":1,"1576":6,"1580":1,"1581":1,"1582":1,"1584":1,"1585":1,"1590":3,"1596":1,"1599":1,"1620":1,"1626":1,"1637":1,"1651":1,"1663":1,"1667":1,"1751":1,"1795":2,"1799":1,"1811":1,"1814":2,"1820":1,"1861":1,"1887":1,"1891":2,"1928":3,"1932":1,"1947":1,"1955":1,"2012":1,"2044":1,"2071":1,"2078":1,"2095":1,"2097":1,"2113":1,"2141":1,"2161":1,"2166":1,"2181":1,"2211":1,"2212":3,"2215":1,"2216":1,"2217":2,"2218":2,"2229":2,"2237":1,"2238":2,"2239":2}}],["drives",{"3":{"0":1,"70":1,"142":1,"166":1,"176":1,"239":1,"330":1,"536":2,"572":2,"625":1,"627":1,"657":1,"832":1,"838":1,"859":1,"861":3,"863":1,"864":1,"907":2,"925":1,"954":1,"1217":1,"1229":3,"1274":1,"1285":6,"1299":2,"1308":1,"1309":2,"1310":7,"1314":1,"1322":4,"1323":12,"1336":1,"1338":6,"1341":1,"1349":8,"1358":9,"1368":2,"1379":5,"1394":4,"1395":8,"1401":1,"1405":2,"1414":2,"1415":2,"1416":4,"1424":1,"1426":1,"1427":1,"1432":2,"1434":1,"1435":26,"1437":10,"1455":1,"1486":1,"1487":2,"1488":1,"1489":1,"1491":1,"1512":1,"1610":2,"1649":1,"1663":1,"1725":1,"1731":1,"1764":1,"1804":1,"1864":1,"1880":1,"1987":1,"2023":1,"2043":1,"2054":2,"2074":1,"2176":1}}],["driver",{"3":{"0":1,"96":1,"388":1,"1435":4,"1454":2}}],["drifting",{"3":{"148":1,"841":1,"1247":1,"1259":1,"1271":1,"1299":1,"1300":1,"1308":2,"1310":2,"1323":1,"1331":1,"1338":2,"1358":5,"1368":1,"1379":1,"1394":1,"1401":2,"1414":1,"1416":1,"1430":1,"1435":9,"1440":1,"1442":1,"2041":1,"2079":1,"2096":1}}],["drift",{"3":{"0":5,"26":1,"57":1,"61":1,"70":1,"94":1,"100":1,"126":1,"130":2,"136":1,"137":1,"138":1,"215":1,"242":1,"256":1,"265":1,"291":1,"293":1,"296":2,"331":1,"341":1,"372":1,"373":1,"426":1,"449":1,"484":1,"493":1,"511":1,"526":1,"529":1,"530":1,"545":1,"558":1,"567":1,"608":1,"619":1,"634":2,"658":1,"691":1,"734":2,"764":2,"766":8,"767":3,"768":2,"783":1,"784":1,"821":1,"831":1,"832":1,"836":1,"838":2,"839":1,"872":1,"883":1,"940":1,"973":1,"1006":1,"1014":1,"1018":3,"1019":2,"1020":1,"1101":1,"1124":1,"1212":2,"1224":1,"1227":1,"1229":3,"1237":1,"1240":1,"1253":1,"1262":1,"1270":3,"1271":2,"1278":1,"1285":6,"1299":10,"1300":2,"1304":1,"1308":3,"1310":9,"1311":1,"1319":1,"1322":3,"1323":9,"1329":2,"1335":1,"1338":9,"1349":9,"1355":1,"1358":39,"1365":1,"1368":2,"1371":1,"1379":1,"1383":1,"1394":10,"1395":17,"1397":1,"1401":9,"1404":1,"1408":1,"1414":4,"1415":2,"1426":2,"1430":2,"1435":35,"1437":2,"1440":1,"1441":1,"1442":1,"1452":2,"1453":3,"1454":6,"1456":4,"1460":2,"1466":2,"1468":2,"1475":2,"1478":1,"1488":3,"1489":1,"1491":1,"1495":28,"1499":1,"1523":1,"1524":1,"1525":12,"1527":1,"1530":1,"1534":1,"1544":1,"1545":1,"1552":1,"1553":2,"1561":1,"1575":1,"1576":8,"1578":1,"1581":1,"1584":1,"1585":1,"1590":7,"1591":1,"1599":1,"1613":1,"1653":1,"1665":1,"1670":2,"1672":1,"1676":1,"1685":1,"1730":1,"1804":1,"1808":1,"1817":1,"1818":1,"1826":1,"1827":1,"1829":1,"1833":1,"1861":1,"1927":1,"1928":1,"1961":1,"1986":2,"1990":1,"2005":1,"2010":1,"2013":1,"2023":1,"2041":2,"2042":1,"2048":1,"2072":1,"2074":2,"2082":1,"2129":1,"2130":1,"2134":1,"2152":1,"2157":1,"2158":1,"2175":1,"2192":1}}],["driftedmarkedcommandhandler",{"3":{"1199":2,"1207":1}}],["driftedmarkedcommand",{"3":{"1199":3,"1207":1,"1270":1}}],["driftedtests",{"3":{"1198":1,"1199":4,"1207":2,"1435":23,"1495":2}}],["drifted",{"3":{"0":2,"136":2,"137":1,"140":1,"142":1,"161":1,"213":1,"251":1,"252":2,"254":2,"257":1,"258":1,"259":1,"291":1,"373":1,"466":1,"609":1,"644":1,"648":1,"744":1,"857":1,"876":2,"952":1,"959":1,"1020":1,"1321":1,"1322":1,"1323":3,"1329":1,"1338":1,"1349":1,"1379":1,"1383":1,"1395":1,"1430":1,"1435":17,"1459":1,"1473":1,"1475":1,"1486":1,"1488":2,"1489":1,"1495":7,"1499":1,"2043":2,"2045":1,"2147":1}}],["drifts",{"3":{"0":1,"185":1,"329":1,"517":1,"634":1,"706":1,"791":1,"973":1,"1006":1,"1237":1,"1395":1,"1414":1,"1435":6,"1452":1,"1661":1,"1672":1,"1700":1,"1729":1,"1833":1,"1947":1,"1959":2,"1985":1,"1990":1,"2049":1,"2096":1,"2146":1,"2154":1}}],["degenerate",{"3":{"1247":1,"1323":1}}],["degrees",{"3":{"1314":1,"1416":2}}],["degree",{"3":{"583":2,"1314":2,"1322":2,"1416":1,"1880":3}}],["degradable",{"3":{"1310":2,"1669":1}}],["degradationwarned",{"3":{"1322":2}}],["degradation",{"3":{"68":1,"71":1,"182":1,"235":2,"413":1,"682":1,"725":1,"727":1,"898":1,"901":1,"996":2,"1259":1,"1270":1,"1285":1,"1299":4,"1300":1,"1308":3,"1310":3,"1315":1,"1322":6,"1323":4,"1331":2,"1338":3,"1348":1,"1349":4,"1357":1,"1358":4,"1368":2,"1394":2,"1395":5,"1414":1,"1415":1,"1416":17,"1437":4,"1442":3,"1449":1,"1466":1,"1523":2,"1525":2,"1543":1,"1558":1,"1565":1,"1576":1,"1637":1,"1706":2,"1909":1,"1911":1,"2006":1,"2066":1,"2161":1}}],["degrading",{"3":{"212":1,"601":1,"684":1,"700":1,"1237":1,"1283":1,"1285":1,"1299":2,"1310":1,"1323":2,"1338":1,"1349":1,"1358":2,"1379":2,"1395":1,"1400":1,"1401":1,"1414":1,"1416":1,"1454":1,"1466":1,"1850":1,"1858":1,"1926":1,"1932":1,"2196":1}}],["degradeforeignkey",{"3":{"1285":2}}],["degradetestcontext",{"3":{"1199":2,"1207":1}}],["degradecustomer",{"3":{"1198":1,"1199":4,"1207":1}}],["degradeorder",{"3":{"1198":1,"1199":4,"1207":1}}],["degraded",{"2":{"157":1,"159":1,"161":1,"1908":1,"1910":1},"3":{"157":2,"159":1,"161":1,"440":1,"583":2,"585":1,"603":1,"676":1,"726":1,"821":1,"932":1,"997":1,"1247":1,"1270":1,"1285":1,"1299":1,"1300":2,"1303":1,"1308":2,"1310":9,"1322":9,"1336":3,"1338":5,"1395":1,"1414":1,"1416":10,"1435":2,"1442":1,"1576":1,"1626":1,"1665":1,"1857":1,"1858":1,"1908":2,"1910":1,"1933":1,"2005":1,"2012":1,"2125":1,"2155":1}}],["degrade",{"3":{"49":1,"166":1,"167":1,"168":1,"169":1,"413":1,"585":1,"665":1,"724":1,"997":1,"1083":1,"1086":1,"1212":2,"1228":1,"1247":1,"1270":3,"1285":4,"1300":1,"1308":1,"1310":1,"1322":3,"1323":3,"1338":1,"1355":1,"1358":4,"1376":1,"1379":1,"1394":3,"1395":7,"1414":3,"1416":6,"1435":1,"1437":4,"1466":2,"1499":1,"1509":1,"1530":1,"1662":1,"1707":1,"1848":1,"1859":1,"1861":1,"1863":1,"1867":1,"1969":1,"2052":1,"2078":1,"2118":1,"2125":1,"2229":1}}],["degradesgracefully",{"3":{"1706":1}}],["degradestotheplaininbox",{"3":{"1435":1}}],["degrades",{"0":{"2066":1},"3":{"0":2,"178":1,"235":1,"255":1,"266":1,"350":1,"391":1,"461":2,"507":1,"511":1,"584":1,"792":1,"854":1,"896":1,"905":1,"995":1,"1212":1,"1229":1,"1236":1,"1237":1,"1247":2,"1250":1,"1259":2,"1264":1,"1266":1,"1270":2,"1282":1,"1284":1,"1285":5,"1289":1,"1298":1,"1299":8,"1300":5,"1308":1,"1310":4,"1311":2,"1321":1,"1322":8,"1323":5,"1327":1,"1331":1,"1338":5,"1349":1,"1358":10,"1377":1,"1379":4,"1394":12,"1395":7,"1400":1,"1401":3,"1409":1,"1414":6,"1415":2,"1416":14,"1437":1,"1442":2,"1443":1,"1466":1,"1475":1,"1533":1,"1640":1,"1665":1,"1667":1,"1684":1,"1707":1,"1728":1,"1750":1,"1771":1,"1857":1,"1916":1,"1921":1,"1922":1,"1923":1,"1951":1,"1976":1,"1981":1,"1990":1,"2000":1,"2006":1,"2066":1,"2082":1,"2085":1,"2117":1,"2118":1,"2122":1,"2231":1}}],["dequeued",{"3":{"1322":1}}],["deque",{"2":{"1452":1},"3":{"1213":1,"1435":2,"1487":1}}],["dereferenced",{"3":{"1358":1,"1379":2}}],["dereference",{"3":{"1299":1,"1323":1,"1371":1,"1395":1,"1466":1}}],["dereferences",{"3":{"448":1,"1310":1,"1358":3,"1435":2,"2130":1}}],["dereferencing",{"3":{"1299":1,"1435":1}}],["deregistration",{"3":{"1247":1}}],["derivable",{"3":{"1395":1,"1435":1,"1637":1}}],["derivatives",{"3":{"1598":1}}],["derivative",{"3":{"741":1,"743":1,"1050":1,"1051":1,"1235":2,"1237":2,"1394":2}}],["derivations",{"3":{"0":1,"963":1,"964":1,"1227":1,"1285":1,"1358":1,"1804":1}}],["derivation",{"3":{"0":2,"279":1,"309":2,"539":1,"946":1,"963":1,"1004":1,"1006":1,"1018":1,"1281":1,"1299":9,"1310":1,"1394":2,"1395":3,"1435":5,"1452":1,"1453":1,"1855":1,"1901":2}}],["deriving",{"3":{"0":1,"32":1,"202":1,"218":1,"634":1,"723":1,"725":1,"739":1,"742":1,"782":2,"854":1,"883":1,"932":1,"946":1,"963":3,"966":1,"1026":1,"1042":1,"1092":1,"1237":1,"1239":1,"1270":2,"1279":1,"1285":11,"1299":4,"1308":3,"1310":2,"1322":1,"1323":2,"1349":3,"1358":11,"1368":2,"1381":1,"1394":5,"1395":2,"1401":2,"1414":3,"1415":1,"1435":20,"1440":2,"2054":1,"2079":1,"2175":1,"2183":1}}],["derivequestionentity",{"3":{"1358":1}}],["derive",{"3":{"0":1,"111":1,"305":1,"459":1,"465":1,"540":1,"651":1,"657":1,"780":2,"782":1,"784":1,"881":3,"954":1,"956":1,"960":1,"963":1,"1034":1,"1058":1,"1212":3,"1229":2,"1235":1,"1237":2,"1247":2,"1252":1,"1259":5,"1270":1,"1271":1,"1281":1,"1285":2,"1299":6,"1300":1,"1308":2,"1310":2,"1312":1,"1316":1,"1322":6,"1323":5,"1338":2,"1341":1,"1344":2,"1349":5,"1352":1,"1358":4,"1360":1,"1368":1,"1370":2,"1379":2,"1382":1,"1394":6,"1395":7,"1398":1,"1401":3,"1414":1,"1416":1,"1435":5,"1453":2,"1584":1,"1662":1,"1768":2,"1828":1,"1855":1,"1856":1,"1885":1,"1924":1,"2029":1,"2197":1}}],["derivedoverride",{"3":{"1999":1}}],["derivedatasourcekey",{"3":{"1285":2}}],["derivedupdatecommandtests",{"2":{"926":1},"3":{"926":1,"1199":1,"1207":1}}],["derived",{"1":{"631":1,"632":1,"633":1,"634":1,"635":1,"636":1},"3":{"0":5,"135":1,"147":1,"175":1,"186":1,"208":1,"235":1,"273":1,"328":1,"350":1,"507":1,"539":1,"585":1,"609":2,"631":1,"633":1,"780":2,"922":1,"923":1,"926":2,"927":1,"946":2,"954":1,"964":1,"1004":1,"1017":1,"1018":1,"1026":1,"1034":1,"1037":1,"1116":1,"1125":1,"1161":1,"1212":1,"1221":1,"1229":5,"1235":1,"1237":12,"1240":1,"1242":1,"1244":1,"1247":3,"1256":1,"1259":5,"1263":1,"1265":5,"1270":17,"1271":1,"1285":11,"1290":1,"1299":21,"1308":1,"1309":3,"1310":12,"1311":1,"1316":1,"1320":1,"1322":9,"1323":13,"1325":1,"1338":8,"1341":1,"1346":1,"1347":1,"1349":5,"1353":2,"1358":21,"1361":1,"1368":6,"1371":1,"1379":1,"1382":1,"1384":1,"1394":28,"1395":15,"1401":6,"1414":5,"1415":1,"1416":3,"1426":3,"1430":2,"1435":40,"1437":4,"1440":2,"1442":1,"1453":1,"1454":1,"1464":1,"1466":1,"1469":1,"1475":1,"1487":1,"1491":1,"1495":5,"1528":1,"1537":1,"1570":1,"1579":1,"1593":1,"1629":4,"1630":2,"1634":1,"1639":1,"1650":1,"1657":1,"1665":2,"1666":1,"1683":1,"1694":1,"1704":1,"1714":1,"1726":2,"1754":1,"1763":5,"1764":1,"1766":2,"1776":1,"1804":1,"1809":1,"1848":2,"1855":1,"1879":1,"1961":1,"1964":2,"1981":1,"1999":3,"2042":1,"2045":1,"2074":1,"2082":1,"2088":1,"2109":1,"2231":1}}],["derives",{"3":{"0":6,"25":1,"81":1,"109":1,"166":1,"231":1,"310":1,"370":1,"459":1,"499":1,"501":1,"534":1,"536":2,"545":1,"549":1,"575":1,"585":1,"618":1,"741":1,"751":1,"879":1,"884":1,"905":1,"909":1,"922":1,"925":1,"926":2,"944":1,"946":1,"954":1,"964":1,"972":1,"974":1,"1004":1,"1036":1,"1050":1,"1140":1,"1212":1,"1213":1,"1229":1,"1237":3,"1259":4,"1270":2,"1271":2,"1276":1,"1285":17,"1287":2,"1291":1,"1296":1,"1299":10,"1308":3,"1309":1,"1310":10,"1317":1,"1322":5,"1323":6,"1327":1,"1338":2,"1341":1,"1349":6,"1351":2,"1354":2,"1356":2,"1358":29,"1363":1,"1368":2,"1370":1,"1378":1,"1379":5,"1387":1,"1388":1,"1394":8,"1395":4,"1401":4,"1414":2,"1415":1,"1416":1,"1426":1,"1430":2,"1435":24,"1448":1,"1453":2,"1487":2,"1491":1,"1809":1,"1901":2,"1907":1,"1926":1,"2042":1,"2071":1,"2073":1,"2162":1,"2164":1,"2166":1}}],["deal",{"3":{"1349":1,"1930":1}}],["deactivation",{"3":{"497":2,"499":1,"1754":2,"1775":1}}],["deactivating",{"3":{"458":1}}],["deactivate",{"2":{"497":1,"1754":1,"1775":1},"3":{"497":1,"499":1,"1323":1,"1435":2,"1754":1,"1768":1,"1775":1}}],["deactivated",{"3":{"284":1,"460":1,"499":1,"1299":2,"1416":1,"1454":1,"1460":1,"1746":2,"1982":1,"2065":2}}],["death",{"3":{"444":1}}],["deadletteredon",{"2":{"2185":1},"3":{"1285":5,"1322":2,"2185":1}}],["deadlettered",{"2":{"1273":1},"3":{"1273":1}}],["deadlettercounter",{"3":{"707":1,"1259":2,"1285":1}}],["deadletterretentiondays",{"2":{"818":1,"1256":1},"3":{"0":2,"20":1,"24":1,"818":1,"1256":1,"1259":1,"1285":1,"1466":1,"1495":1,"1576":1,"1844":1}}],["deadlocking",{"3":{"1492":1}}],["deadlock",{"0":{"1327":1},"3":{"55":1,"481":1,"1066":1,"1068":1,"1285":1,"1300":1,"1311":1,"1327":2,"1338":1,"1377":1,"1440":3,"1491":1,"1548":1,"1669":1,"1683":1,"1791":1,"2008":1,"2029":1,"2055":1}}],["deadlocks",{"3":{"0":1,"483":1,"1212":1,"1299":1,"1429":1,"1652":1,"1699":1,"2021":1}}],["deadlineexceeded",{"3":{"100":1,"1395":1}}],["deadlinescheduler",{"2":{"814":1},"3":{"814":1}}],["deadlines",{"3":{"0":1,"810":1,"1160":1,"1212":1,"1379":1,"2167":1,"2168":1}}],["deadline",{"0":{"539":1,"813":1},"3":{"0":5,"237":1,"534":2,"536":2,"538":2,"539":8,"541":2,"724":1,"725":1,"808":1,"809":2,"810":3,"812":1,"813":3,"815":2,"1067":1,"1153":1,"1212":2,"1285":1,"1299":2,"1306":1,"1308":2,"1311":3,"1322":2,"1358":1,"1377":2,"1379":10,"1395":8,"1412":1,"1414":4,"1429":1,"1432":1,"1435":4,"1437":1,"1448":1,"1466":1,"1475":1,"1480":1,"1495":1,"1599":1,"1779":1,"1926":1,"1945":1,"2160":1,"2164":1,"2166":4,"2167":3,"2168":1,"2231":1}}],["dead",{"0":{"1256":1},"2":{"20":1,"397":1,"1256":1,"2190":1},"3":{"0":11,"15":4,"17":1,"19":2,"20":3,"21":1,"22":2,"23":1,"24":7,"26":1,"27":3,"71":1,"122":1,"125":1,"195":1,"223":1,"229":1,"230":3,"305":1,"310":1,"395":4,"396":1,"397":3,"398":1,"399":2,"404":2,"406":1,"409":1,"413":2,"444":1,"464":2,"465":1,"497":1,"536":1,"537":1,"539":1,"607":1,"608":1,"609":4,"611":2,"627":1,"708":1,"709":1,"751":1,"756":1,"791":1,"810":2,"815":1,"817":1,"818":2,"819":1,"820":1,"823":1,"848":1,"849":2,"896":1,"907":1,"935":1,"963":1,"1011":1,"1012":1,"1041":1,"1042":3,"1044":1,"1046":1,"1075":1,"1076":1,"1099":1,"1100":1,"1144":1,"1212":2,"1233":2,"1237":4,"1247":1,"1255":1,"1256":6,"1258":1,"1259":33,"1270":1,"1271":3,"1275":3,"1285":15,"1299":1,"1300":2,"1308":2,"1310":7,"1316":1,"1317":2,"1322":15,"1323":8,"1329":1,"1338":2,"1354":1,"1358":7,"1390":2,"1391":1,"1394":9,"1395":9,"1401":1,"1414":3,"1416":10,"1435":11,"1437":5,"1442":1,"1455":1,"1464":1,"1466":4,"1468":1,"1475":3,"1481":1,"1488":1,"1495":5,"1523":1,"1525":5,"1530":2,"1546":3,"1551":1,"1571":1,"1576":2,"1588":1,"1590":4,"1595":3,"1598":1,"1614":1,"1616":1,"1660":1,"1664":4,"1676":1,"1840":1,"1842":1,"1844":1,"1909":1,"1921":1,"1922":2,"1943":1,"1949":1,"1983":1,"2005":1,"2006":1,"2031":2,"2120":1,"2153":1,"2155":4,"2157":1,"2158":1,"2164":2,"2166":1,"2168":1,"2181":4,"2182":1,"2186":1,"2189":1,"2190":3,"2192":4}}],["de",{"3":{"219":1,"649":1,"651":1,"1059":1,"1140":1,"1270":1,"1271":2,"1285":2,"1299":3,"1310":3,"1322":2,"1323":3,"1338":2,"1358":5,"1368":1,"1394":4,"1414":2,"1432":1,"1435":2,"1495":1,"1525":1,"1685":4,"1764":1,"1829":1,"2149":1}}],["detour",{"3":{"1322":2}}],["determinable",{"0":{"1450":1,"1469":1,"1483":1,"1499":1},"3":{"530":1,"599":1,"629":1,"665":1,"1190":1,"1237":1,"1247":1,"1259":2,"1285":10,"1299":1,"1310":5,"1322":10,"1323":2,"1338":8,"1358":13,"1368":2,"1379":4,"1394":8,"1395":6,"1401":2,"1414":8,"1415":5,"1416":9,"1435":2,"1457":1,"1466":1,"1469":2,"1495":1}}],["determinism",{"3":{"360":1,"364":1,"1285":3,"1299":2,"1358":1,"1435":1,"1437":1,"1464":1,"2143":1}}],["deterministically",{"3":{"543":1,"549":1,"905":1,"954":1,"1256":1,"1259":3,"1263":1,"1270":1,"1299":1,"1300":1,"1305":1,"1311":1,"1322":1,"1323":1,"1363":1,"1382":1,"1394":1,"1395":2,"1416":2,"1426":1,"1435":3,"1437":1,"1453":1,"1487":1,"1591":1}}],["deterministic",{"3":{"0":1,"23":1,"157":1,"295":1,"359":2,"360":1,"361":2,"363":2,"545":1,"549":1,"551":1,"591":1,"592":2,"721":1,"742":1,"905":1,"907":1,"1019":2,"1163":1,"1227":1,"1229":2,"1283":2,"1285":10,"1289":1,"1299":6,"1308":1,"1310":3,"1322":1,"1323":2,"1349":2,"1354":1,"1358":6,"1368":1,"1379":1,"1391":2,"1394":6,"1395":3,"1401":2,"1414":1,"1416":1,"1426":2,"1427":1,"1431":1,"1434":3,"1435":13,"1437":3,"1454":4,"1464":1,"1478":2,"1487":1,"1491":3,"1495":1,"1525":1,"1550":1,"1576":2,"1589":1,"1590":1,"1674":1,"1804":1,"1981":1,"2051":1,"2059":1,"2141":2,"2143":1,"2144":1,"2145":1,"2169":1}}],["determine",{"3":{"1394":1,"1414":1,"1453":1,"1630":1,"1775":1}}],["determined",{"3":{"283":1,"990":1,"1285":1,"1323":1,"1491":1,"1494":1,"1630":1,"1640":1,"2000":1}}],["determines",{"3":{"20":1,"162":1,"389":1,"1285":2,"1394":1,"1629":4,"1638":1,"2001":1,"2050":1}}],["detectability",{"3":{"1980":1}}],["detectable",{"3":{"498":1,"906":1,"907":1,"1270":1,"1285":1,"1299":1,"1358":1,"1365":1,"1368":2,"1499":1}}],["detectchanges",{"3":{"1273":1,"1285":2,"1584":1}}],["detectchangesscope",{"3":{"1199":2,"1203":1,"1207":1,"1273":1,"1285":3,"1495":1}}],["detectchangesonce",{"3":{"715":1,"1273":1,"1285":3}}],["detect",{"2":{"443":1},"3":{"443":1,"1116":1,"1259":1,"1270":2,"1273":1,"1285":7,"1299":1,"1323":1,"1338":1,"1349":1,"1358":5,"1395":1,"1416":1,"1435":1,"1466":1,"1876":1}}],["detector",{"3":{"305":1,"339":1,"344":1,"1020":1,"1282":1,"1285":2,"1308":1,"1322":1,"1358":1,"1367":2,"1395":5,"1423":1,"1435":4,"1437":1,"1466":2,"1525":1,"1530":1,"1531":1,"1576":1,"1706":1,"1984":2}}],["detects",{"3":{"291":1,"948":1,"974":1,"1237":1,"1285":3,"1299":2,"1331":1,"1338":4,"1351":1,"1358":1,"1379":1,"1394":2,"1395":1,"1416":1,"1429":2,"1430":1,"1435":5,"1456":2,"1660":1,"1727":1,"1728":1,"1875":1,"1902":1}}],["detected",{"3":{"0":1,"259":1,"382":1,"741":1,"792":1,"1067":1,"1168":1,"1285":1,"1310":2,"1338":3,"1349":1,"1365":1,"1416":2,"1435":1,"1442":1,"1454":1,"1570":1,"1728":1}}],["detecting",{"3":{"0":1,"593":2,"733":1,"1068":1,"1283":1,"1358":1,"1435":1,"1590":1,"1728":1}}],["detectiontestdbcontext",{"3":{"1199":2,"1207":1}}],["detection",{"1":{"496":1,"497":1,"498":1,"499":1,"500":1,"501":1,"502":1,"503":1,"1979":1,"1980":1,"1981":1,"1982":1,"1983":1,"1984":1},"3":{"0":3,"403":1,"496":1,"497":1,"498":1,"499":1,"500":2,"501":2,"506":1,"510":1,"608":1,"609":1,"612":1,"614":1,"692":1,"715":2,"733":2,"759":1,"762":1,"801":1,"804":1,"903":1,"904":1,"905":3,"907":3,"910":1,"1058":1,"1212":1,"1237":1,"1247":1,"1259":2,"1270":2,"1273":1,"1282":1,"1285":16,"1286":1,"1289":2,"1299":12,"1322":4,"1323":6,"1337":1,"1338":3,"1358":1,"1395":1,"1405":1,"1414":3,"1416":13,"1432":1,"1435":9,"1436":1,"1437":3,"1454":1,"1460":1,"1466":3,"1468":1,"1475":1,"1485":1,"1495":1,"1525":1,"1530":1,"1531":1,"1584":1,"1638":1,"1640":3,"1659":1,"1711":1,"1721":1,"1728":1,"1764":1,"1778":1,"1978":1,"1979":1,"1980":1,"1981":1,"1982":1,"1983":1,"1984":1,"2145":1,"2193":1,"2207":1,"2219":1,"2231":1}}],["detachobserverasync",{"3":{"1323":1}}],["detaching",{"3":{"1323":1,"1358":1,"1394":1,"1435":1}}],["detached",{"3":{"235":1,"237":1,"898":1,"1251":1,"1259":1,"1285":3,"1338":2,"1349":1,"1358":1,"1453":1}}],["detaches",{"3":{"0":1,"201":2,"1259":1,"1274":1,"1285":1,"1358":3}}],["detach",{"3":{"24":1,"1285":1,"1323":1,"1358":3}}],["detailpage",{"3":{"1495":1}}],["detailpagebasetests",{"3":{"1199":1,"1207":3,"1323":1}}],["detailpagebase",{"3":{"1192":1,"1199":2,"1203":1,"1207":2,"1323":4,"1383":3,"1394":22,"1495":4}}],["detailhref",{"3":{"1323":1}}],["detailed",{"3":{"1311":2,"1312":1,"1323":1,"1339":1,"1440":1,"1629":1,"1640":1,"1662":1,"1763":1}}],["detail",{"0":{"1383":1,"1821":1,"1871":1,"1908":1},"2":{"1925":1},"3":{"0":1,"1":1,"62":1,"72":1,"109":4,"261":1,"342":1,"556":2,"557":1,"559":1,"699":1,"725":1,"748":1,"806":1,"861":2,"905":1,"988":1,"1016":1,"1018":2,"1026":1,"1082":1,"1093":1,"1116":1,"1190":1,"1229":2,"1237":3,"1246":1,"1247":9,"1252":1,"1259":4,"1270":5,"1271":1,"1275":1,"1285":15,"1289":1,"1299":10,"1300":1,"1308":5,"1310":27,"1311":9,"1313":1,"1322":7,"1323":29,"1332":1,"1338":7,"1344":1,"1349":8,"1358":21,"1362":1,"1365":1,"1368":3,"1379":7,"1383":4,"1384":3,"1385":1,"1386":2,"1387":5,"1388":2,"1390":1,"1391":2,"1394":89,"1395":18,"1398":1,"1401":6,"1409":2,"1414":14,"1415":1,"1416":7,"1435":17,"1437":2,"1442":1,"1452":1,"1454":1,"1466":1,"1475":2,"1478":1,"1480":1,"1486":1,"1495":3,"1523":1,"1525":2,"1530":1,"1534":2,"1590":2,"1599":2,"1626":12,"1630":1,"1631":2,"1634":2,"1635":2,"1638":2,"1639":2,"1640":1,"1685":2,"1699":1,"1712":1,"1722":1,"1747":1,"1749":7,"1757":2,"1758":1,"1759":2,"1767":2,"1772":2,"1805":1,"1829":1,"1850":1,"1872":1,"1904":1,"1908":1,"1925":1,"1938":1,"1987":1,"2030":1,"2041":1,"2042":1,"2044":1,"2054":1,"2057":1,"2093":1,"2125":1,"2135":1,"2149":1,"2173":1,"2192":1}}],["detailsurl",{"3":{"1394":4}}],["details",{"1":{"1924":1,"1925":1,"1926":1,"1927":1,"1928":1,"1929":1},"3":{"0":2,"175":1,"341":2,"343":1,"409":1,"453":1,"497":1,"572":4,"574":4,"578":2,"657":1,"741":1,"819":1,"1018":1,"1019":1,"1091":1,"1212":1,"1221":1,"1224":1,"1226":1,"1227":1,"1229":6,"1247":1,"1251":1,"1259":2,"1263":1,"1270":1,"1271":1,"1276":1,"1285":5,"1299":3,"1300":1,"1310":15,"1311":1,"1322":1,"1323":6,"1327":1,"1338":2,"1339":1,"1346":1,"1348":1,"1349":2,"1354":1,"1358":9,"1368":1,"1379":2,"1382":1,"1394":12,"1395":8,"1401":8,"1405":1,"1414":2,"1420":1,"1422":1,"1434":2,"1435":4,"1437":2,"1441":1,"1442":1,"1448":1,"1453":1,"1454":2,"1458":1,"1486":1,"1487":1,"1488":2,"1491":1,"1525":2,"1545":1,"1550":1,"1590":2,"1629":3,"1630":3,"1639":1,"1650":1,"1652":1,"1684":1,"1750":1,"1764":1,"1767":4,"1769":2,"1771":1,"1772":1,"1778":1,"1838":1,"1840":1,"1858":1,"1872":1,"1907":1,"1918":1,"1923":1,"1924":1,"1925":1,"1926":3,"1929":1,"1987":1,"2018":1,"2054":1,"2066":1,"2071":1,"2073":1,"2074":1,"2130":1,"2174":1,"2186":1,"2196":1,"2207":1}}],["deeply",{"3":{"1394":1}}],["deeplinkroute",{"3":{"1395":2,"1416":5}}],["deeplinkrouteshapetests",{"3":{"1199":1,"1207":2,"1416":1}}],["deeplinkrouteeventargs",{"3":{"1199":4,"1203":1,"1207":2,"1416":12}}],["deeplinkrouteguard",{"2":{"418":1,"428":1},"3":{"418":1,"428":2}}],["deeplinkdispatchertests",{"3":{"1199":1,"1207":2,"1416":1}}],["deeplinkdispatcher",{"2":{"418":1,"427":1,"428":1,"430":1,"1415":1,"2117":1,"2120":1},"3":{"0":1,"418":1,"427":2,"428":2,"430":2,"1199":6,"1203":1,"1207":2,"1415":4,"1416":17,"1495":1,"2117":1,"2120":2}}],["deeplinklistenertests",{"3":{"1199":1,"1207":2,"1416":1}}],["deeplinklistener",{"2":{"2120":1},"3":{"0":1,"413":1,"1415":5,"1416":10,"1598":2,"2120":1}}],["deepens",{"3":{"2113":1}}],["deepen",{"3":{"1435":1}}],["deepest",{"3":{"1247":1,"1358":1,"2134":1}}],["deeper",{"3":{"237":1,"1247":1,"1310":1,"1333":1,"1358":3,"1380":1,"1401":1,"1415":1,"1426":1,"1435":4,"1474":1,"1571":1,"1576":1,"1820":1,"1863":1,"1959":1,"2033":1}}],["deep",{"0":{"2120":1,"2214":1,"2234":1,"2236":1},"1":{"417":1,"418":1,"419":1,"420":1,"421":1,"422":1,"423":1,"424":1,"425":1,"426":1,"427":1,"428":1,"429":1,"430":1},"3":{"0":3,"1":1,"121":1,"122":1,"206":1,"208":1,"353":1,"399":1,"413":1,"417":1,"418":1,"424":1,"549":1,"616":1,"618":1,"649":1,"690":1,"854":1,"872":1,"883":1,"1184":1,"1185":1,"1187":1,"1192":2,"1194":1,"1202":1,"1203":1,"1212":1,"1228":1,"1229":4,"1247":1,"1285":5,"1293":1,"1297":1,"1299":2,"1308":1,"1310":2,"1322":3,"1323":19,"1349":4,"1358":1,"1390":1,"1394":3,"1395":14,"1401":1,"1415":14,"1416":22,"1435":3,"1444":1,"1495":3,"1525":1,"1554":2,"1561":1,"1576":2,"1584":1,"1590":1,"1598":2,"1625":1,"1626":1,"1640":2,"1644":1,"1663":1,"1665":1,"1668":3,"1758":1,"1759":1,"1760":1,"1764":1,"1778":45,"1782":1,"1785":1,"1789":1,"1820":2,"1825":1,"1827":1,"1913":1,"1919":1,"1934":1,"1965":1,"1974":1,"1975":1,"1977":1,"2029":2,"2040":1,"2058":2,"2059":2,"2075":1,"2079":1,"2120":2,"2158":1,"2159":1,"2203":1,"2217":1,"2225":1,"2231":2,"2235":1,"2244":1}}],["debe",{"3":{"1684":1,"1685":2}}],["debounce",{"3":{"1590":1}}],["debounceinterval=",{"3":{"1395":1}}],["debounces",{"3":{"1394":1}}],["debounced",{"3":{"881":1,"1323":2,"1384":1,"1394":2,"1525":1,"1559":1,"2071":1}}],["debuggable",{"3":{"1285":1,"1416":1}}],["debuggability",{"3":{"6":1,"1951":1}}],["debugger",{"3":{"1259":1,"1338":1,"1414":1}}],["debugging",{"3":{"399":1,"402":1,"407":1,"641":1,"1259":1,"1310":1,"1332":1,"1338":2,"1414":1,"1429":1,"1435":1,"1437":1,"1442":1,"1487":1,"1640":1,"1912":1,"2158":2}}],["debug",{"3":{"0":1,"401":1,"1109":1,"1256":1,"1259":4,"1263":1,"1270":6,"1271":1,"1310":4,"1322":4,"1323":2,"1332":1,"1338":1,"1395":7,"1415":9,"1435":1,"1443":1,"1480":1,"1574":1,"1576":3,"1581":1,"1584":1,"1647":3,"1654":1,"1676":2,"1893":1,"1916":1,"1951":1}}],["debt",{"3":{"0":2,"298":1,"302":2,"607":1,"674":1,"1011":1,"1012":1,"1014":1,"1299":4,"1349":1,"1395":1,"1435":6,"1495":1,"1521":1,"1525":1,"1528":1,"1533":1,"1570":1,"1590":1,"2137":2}}],["density",{"0":{"1714":1},"3":{"881":1,"1323":13,"1415":2,"1523":4,"1525":3,"1529":1,"1530":3,"1531":1,"1532":1,"1556":1,"1558":1,"1576":1,"1590":1,"1595":3,"1596":2,"1668":1,"1714":1,"2078":1}}],["dense=",{"3":{"1523":1,"1525":2,"1530":1,"1531":1,"1590":1,"1595":1,"1596":1,"1714":1}}],["densegrid",{"2":{"1523":1,"1530":1,"1531":1,"1594":1,"1596":1,"1714":1},"3":{"1323":4,"1523":2,"1525":2,"1530":1,"1531":1,"1576":3,"1594":1,"1595":1,"1596":1,"1714":2}}],["densest",{"3":{"1285":1,"1323":1}}],["denser",{"3":{"691":1,"1323":1}}],["dense",{"2":{"1523":1},"3":{"650":1,"1237":1,"1256":1,"1323":4,"1379":2,"1395":1,"1523":1,"1558":1}}],["denormalize",{"3":{"1395":1,"1768":1}}],["denormalized",{"3":{"1030":1,"1117":1,"1341":1,"1345":1,"1349":2,"1358":2,"1379":1,"1394":5,"1397":1,"1401":3,"1414":2,"1629":2,"1749":1,"1750":2,"1768":2,"1772":1}}],["denormalization",{"3":{"1394":1,"1395":1,"1634":1,"1685":1}}],["denormalizationbackfillservice",{"3":{"674":1}}],["denormalizing",{"3":{"1349":1,"1394":1,"1401":1,"1637":1}}],["denominators",{"3":{"1012":4,"1526":3,"1536":1,"1552":1,"1577":3,"1590":1,"1591":4,"1593":1,"1598":1,"2170":1}}],["denominator",{"3":{"0":1,"1014":1,"1323":1,"1435":1,"1535":1}}],["denies",{"3":{"126":2,"188":1,"318":2,"1059":1,"1264":2,"1270":5,"1296":1,"1299":6,"1338":1,"1358":3,"1416":3,"1435":1,"1666":2,"1820":1,"1963":1,"1993":2,"1996":1,"2196":2}}],["denied",{"2":{"121":1,"126":1,"1268":1,"1421":1,"2155":1},"3":{"0":3,"121":1,"126":1,"317":1,"318":2,"440":1,"682":2,"729":1,"960":1,"1091":1,"1212":1,"1263":1,"1268":1,"1270":7,"1271":5,"1275":1,"1282":1,"1296":2,"1299":3,"1315":1,"1322":1,"1358":1,"1394":1,"1395":6,"1416":12,"1421":2,"1426":1,"1435":1,"1506":1,"1534":1,"1569":1,"1610":1,"1661":1,"1720":1,"1820":3,"1993":2,"2095":1,"2125":1,"2155":1}}],["denials",{"3":{"121":1,"126":3,"1263":1,"1270":1,"1996":1}}],["denial",{"3":{"0":1,"121":1,"126":2,"318":1,"793":1,"952":1,"954":1,"956":1,"960":1,"1263":1,"1270":7,"1299":4,"1322":1,"1358":1,"1395":1,"1401":1,"1416":10,"1435":2,"1576":1,"1820":1,"1994":1,"2000":1,"2095":1,"2116":1}}],["denying",{"3":{"1268":1,"1270":3,"1298":1,"1299":2,"1322":1,"1338":1,"1598":1,"2155":1}}],["denylist",{"3":{"0":1,"972":3,"973":2,"974":1,"975":1,"1212":1,"1323":2}}],["deny",{"0":{"2198":1},"2":{"2146":1},"3":{"0":3,"126":1,"184":1,"214":1,"318":3,"322":1,"324":1,"326":1,"458":1,"954":1,"956":1,"960":1,"1059":2,"1060":2,"1263":1,"1270":1,"1271":1,"1296":1,"1299":7,"1310":1,"1322":1,"1335":1,"1338":1,"1343":1,"1347":1,"1356":1,"1358":2,"1378":1,"1379":1,"1408":1,"1414":1,"1435":3,"1442":1,"1466":1,"1525":1,"1630":1,"1993":1,"1994":1,"1995":1,"2146":1,"2148":1,"2198":1,"2202":1,"2238":1}}],["demos",{"3":{"2108":1}}],["demonstrably",{"3":{"1764":1}}],["demonstrable",{"3":{"1285":1}}],["demonstrating",{"3":{"2220":1}}],["demonstration",{"3":{"1270":1,"1271":1,"1338":1,"1351":1,"1358":1}}],["demonstrator",{"3":{"1533":1}}],["demonstrates",{"3":{"551":1,"620":1,"698":1,"1058":1,"1299":1,"1310":1,"1323":1,"1358":4,"1375":1,"1435":3,"1486":1,"1720":1,"2056":1,"2225":1}}],["demonstrated",{"3":{"0":2,"941":1,"966":1,"1090":1,"1401":1,"1576":1,"1590":2,"1705":1,"1706":1,"2108":1}}],["demonstrate",{"3":{"0":1,"62":1,"449":1,"651":1,"1338":1,"1487":1,"1574":1,"1575":1,"1883":1,"2112":2,"2128":1,"2132":1,"2213":1}}],["demote",{"3":{"1299":1,"1310":1,"1338":1,"1446":1}}],["demoted",{"3":{"619":1,"743":1,"1323":2,"2056":1}}],["demotion",{"3":{"1270":1}}],["demoting",{"3":{"1270":1,"1413":1}}],["demo",{"3":{"698":1,"1436":1,"1697":1,"1776":1,"1788":1,"1795":1,"1957":1,"1965":1,"1972":1}}],["demanded",{"0":{"1870":1},"3":{"1237":1,"1299":1,"1414":1}}],["demands",{"3":{"147":2,"150":1,"265":1,"626":1,"674":2,"709":1,"1229":1,"1259":1,"1299":3,"1300":1,"1310":1,"1321":1,"1326":1,"1358":2,"1414":2,"1415":1,"1425":1,"1435":5,"1453":1,"1454":2,"1458":1,"1525":1,"1674":1,"1793":1,"2011":2,"2034":1}}],["demand",{"0":{"1326":1},"3":{"0":2,"86":1,"126":1,"186":1,"471":1,"499":1,"507":1,"591":1,"1060":1,"1125":1,"1229":1,"1237":1,"1247":2,"1263":1,"1270":1,"1285":3,"1296":3,"1299":5,"1310":3,"1317":1,"1322":1,"1323":5,"1326":1,"1341":1,"1349":1,"1358":1,"1394":2,"1395":1,"1414":1,"1435":3,"1437":1,"1439":1,"1454":2,"1458":2,"1459":1,"1474":1,"1481":1,"1491":1,"1576":1,"1588":1,"1801":1,"1820":1,"1876":1,"1961":1,"1964":2,"2012":1,"2033":1,"2046":1,"2140":1,"2161":1,"2179":2,"2198":1,"2231":1}}],["deflated",{"3":{"1491":1}}],["deflate",{"3":{"1454":1}}],["deflating",{"3":{"1454":1}}],["defining",{"3":{"1299":8,"1308":2,"1323":1,"1338":2,"1358":2,"1395":1,"1414":1,"1435":9,"1576":1,"1638":1,"1915":1,"1948":1,"2027":1,"2158":1}}],["definingprojectname",{"3":{"1067":1}}],["definitive",{"3":{"1358":1}}],["definitions",{"3":{"53":1,"54":1,"471":1,"881":1,"1266":1,"1280":1,"1285":2,"1300":1,"1311":1,"1323":1,"1349":1,"1358":6,"1379":1,"1395":2,"1435":4,"1446":1,"1493":1,"1631":1,"1857":1,"1961":1,"1993":1}}],["definition",{"0":{"1463":1},"2":{"1729":1},"3":{"33":1,"428":1,"619":1,"767":1,"810":1,"1102":1,"1117":1,"1160":1,"1229":1,"1231":1,"1237":1,"1239":1,"1247":1,"1259":1,"1270":3,"1285":3,"1299":3,"1308":1,"1310":2,"1311":1,"1323":6,"1338":4,"1346":1,"1349":8,"1358":27,"1368":4,"1379":1,"1395":4,"1414":5,"1416":1,"1432":1,"1435":11,"1466":2,"1585":1,"1630":1,"1636":1,"1637":1,"1640":1,"1700":1,"1729":1,"1801":1,"1961":2,"1979":1}}],["definite",{"3":{"1349":2}}],["definitely",{"3":{"1270":1,"1349":2,"1887":1}}],["definelayers",{"2":{"2102":1},"3":{"1430":1,"1435":23,"2102":1}}],["define",{"0":{"2016":1,"2089":1},"3":{"342":1,"449":1,"988":1,"1229":3,"1237":1,"1247":1,"1285":1,"1299":1,"1310":2,"1322":1,"1323":1,"1332":1,"1349":2,"1358":1,"1368":1,"1435":2,"1442":1,"1575":1,"1637":1,"1638":2,"1655":1,"1708":1,"1826":1,"1954":1,"1996":1,"2012":1,"2014":1,"2048":1,"2122":1,"2155":1}}],["defined",{"0":{"2041":1},"3":{"0":2,"11":1,"54":1,"215":1,"272":1,"331":1,"350":1,"352":1,"395":2,"404":1,"426":1,"428":1,"608":1,"749":1,"790":1,"850":1,"860":1,"861":2,"862":1,"881":1,"991":1,"1124":1,"1237":1,"1240":1,"1257":1,"1259":2,"1271":2,"1276":1,"1285":5,"1299":4,"1300":2,"1308":3,"1310":5,"1319":1,"1322":1,"1323":4,"1335":1,"1338":5,"1349":1,"1351":1,"1358":23,"1394":6,"1395":2,"1401":3,"1408":1,"1414":3,"1416":3,"1435":2,"1456":1,"1457":2,"1478":1,"1479":1,"1487":1,"1525":2,"1541":1,"1546":1,"1555":1,"1558":1,"1565":3,"1566":1,"1571":1,"1576":1,"1581":1,"1590":2,"1630":3,"1633":1,"1705":1,"1713":1,"1763":1,"1789":1,"1914":1,"1928":2,"2015":1,"2054":1,"2072":1,"2110":1,"2146":1,"2152":1,"2181":1,"2219":1}}],["definescope",{"3":{"1270":2}}],["defines",{"3":{"0":1,"125":1,"175":1,"184":3,"186":5,"256":1,"342":1,"350":1,"406":1,"407":1,"452":1,"536":1,"558":1,"564":1,"571":1,"614":1,"707":1,"870":1,"897":1,"1016":1,"1018":1,"1212":1,"1234":1,"1236":1,"1237":2,"1285":2,"1299":5,"1300":1,"1302":1,"1308":3,"1322":1,"1323":5,"1358":2,"1368":1,"1379":1,"1394":2,"1395":2,"1408":1,"1414":5,"1415":1,"1416":1,"1435":5,"1442":1,"1473":1,"1488":1,"1535":1,"1539":1,"1576":1,"1637":1,"1638":2,"1640":1,"1809":1,"1931":1,"1962":4,"1977":1,"2038":1,"2041":1,"2074":1,"2082":1,"2116":1,"2155":1,"2164":1,"2229":1}}],["defeated",{"3":{"716":1,"1358":1,"1435":1}}],["defeating",{"0":{"1333":1},"3":{"157":1,"591":1,"593":1,"1270":1,"1414":1,"1491":1}}],["defeat",{"3":{"151":1,"528":1,"741":1,"941":1,"1093":1,"1299":1,"1310":2,"1311":1,"1322":1,"1323":1,"1416":1,"1435":1,"1440":1,"1666":1,"1974":1,"2018":1}}],["defeats",{"3":{"100":1,"235":1,"360":1,"998":1,"1299":1,"1310":1,"1311":1,"1338":1,"1422":1,"1426":1,"1430":1,"1435":1,"1575":1,"1908":1,"2029":1,"2127":1,"2174":1,"2178":1}}],["defer",{"3":{"459":1,"614":1,"810":1,"1285":1,"1322":1,"1338":2,"1401":1,"1415":1,"1433":1,"1435":1,"1497":1,"2192":1}}],["defersessionpersist",{"3":{"1323":1}}],["defers",{"3":{"20":1,"609":1,"675":1,"815":1,"821":1,"918":1,"1116":1,"1271":2,"1285":1,"1299":2,"1310":2,"1338":1,"1355":1,"1358":1,"1394":1,"1407":1,"1414":6,"1415":1,"1416":1,"1421":1,"1435":1,"1475":1,"1581":1,"1721":1,"1949":1,"1958":1,"2011":1}}],["deferring",{"3":{"735":1,"801":1,"989":1,"1285":1,"1310":2,"1322":1,"1323":3,"1338":1,"1414":1,"1415":1,"1426":1}}],["deferrals",{"3":{"1285":1,"1570":1,"1593":1}}],["deferral",{"3":{"0":7,"468":1,"469":1,"473":1,"474":2,"518":1,"521":1,"522":1,"726":1,"797":3,"798":2,"799":2,"800":1,"808":2,"810":1,"811":1,"812":2,"813":1,"827":1,"914":1,"917":1,"1048":1,"1051":1,"1053":1,"1054":1,"1100":1,"1104":1,"1212":1,"1323":2,"1395":2,"1576":1,"2167":2,"2231":1}}],["deferredtable",{"3":{"1285":2}}],["deferreddispatch",{"2":{"1233":1,"1497":1},"3":{"1198":1,"1199":2,"1203":1,"1207":1,"1233":1,"1274":1,"1285":4,"1495":2,"1497":2}}],["deferred",{"0":{"1275":1,"1317":1,"2183":1},"1":{"796":1,"797":1,"798":1,"799":1,"800":1,"801":1,"802":1,"803":1,"804":1,"805":1,"806":1,"807":1,"808":1,"809":1,"810":1,"811":1,"812":1,"813":1,"814":1,"815":1},"3":{"0":16,"20":1,"26":1,"68":1,"100":1,"111":1,"120":2,"125":1,"383":1,"468":2,"470":1,"474":1,"518":1,"541":1,"601":1,"680":2,"692":1,"698":1,"725":1,"727":1,"729":1,"733":1,"741":1,"796":1,"799":1,"804":1,"807":1,"827":1,"881":1,"914":1,"988":5,"992":1,"1042":2,"1044":1,"1046":1,"1048":1,"1049":1,"1051":1,"1117":3,"1192":1,"1212":3,"1217":1,"1229":1,"1233":1,"1237":1,"1252":1,"1263":2,"1264":2,"1270":5,"1272":1,"1275":3,"1278":2,"1285":19,"1296":1,"1299":2,"1310":1,"1312":1,"1317":3,"1322":7,"1323":2,"1338":1,"1349":1,"1356":1,"1358":7,"1394":1,"1395":3,"1398":1,"1401":2,"1414":1,"1415":3,"1416":2,"1432":1,"1435":2,"1466":1,"1507":1,"1523":2,"1525":3,"1528":1,"1529":1,"1531":1,"1533":2,"1576":3,"1577":1,"1581":2,"1584":7,"1598":3,"1610":3,"1611":1,"1630":1,"1639":2,"1650":1,"1687":1,"1821":1,"1839":2,"1943":1,"2014":1,"2183":2,"2185":1,"2192":4,"2231":2}}],["defensiveness",{"3":{"1395":1}}],["defensive",{"3":{"359":1,"365":1,"1247":1,"1285":6,"1288":1,"1296":1,"1299":3,"1300":1,"1310":6,"1323":3,"1338":1,"1349":2,"1358":5,"1414":1,"1416":1,"1442":1,"1880":1,"2125":1,"2142":1}}],["defensively",{"3":{"359":1,"1285":1,"1323":1,"1394":2,"1395":1,"1415":2,"1426":1,"1435":1,"1910":1,"1924":1,"1985":1}}],["defensible",{"3":{"0":1,"691":1,"699":1,"821":1,"862":1,"898":1,"1220":1,"1395":1,"1435":1,"1525":1,"1530":1,"1911":1,"1971":1}}],["defenses",{"3":{"1299":1,"1435":1,"1552":1,"1575":1,"1824":1}}],["defense",{"0":{"1902":1},"3":{"207":1,"370":1,"390":1,"973":1,"1185":1,"1247":4,"1263":1,"1270":2,"1284":1,"1285":1,"1299":5,"1308":1,"1310":4,"1323":4,"1335":1,"1368":1,"1395":1,"1401":1,"1414":3,"1426":1,"1435":1,"1547":1,"1576":1,"1626":1,"1666":1,"1672":1,"1756":1,"1822":1,"1968":1,"2039":1,"2072":1,"2125":1,"2200":1}}],["defences",{"3":{"280":1,"1338":1}}],["defence",{"2":{"1364":1},"3":{"0":1,"128":1,"443":1,"725":1,"757":1,"758":1,"1019":1,"1020":1,"1115":1,"1116":1,"1118":1,"1124":2,"1299":1,"1310":1,"1338":3,"1364":1,"1368":1,"1394":1,"1414":2,"1454":1,"1466":2,"1474":1,"1491":1,"2000":1,"2147":1}}],["defending",{"1":{"1997":1,"1998":1,"1999":1,"2000":1,"2001":1},"3":{"1229":1,"1338":1,"1414":1,"1778":1,"1996":1,"1997":1,"2207":2}}],["defend",{"3":{"1125":1,"1285":1,"1308":1,"1395":1,"1435":1,"1437":3,"1899":1}}],["defended",{"3":{"659":1,"868":1,"1224":1,"1299":1,"1394":1,"1401":1,"1414":1,"1435":6,"1525":1,"1825":1}}],["defenderforstoragesettings",{"2":{"1469":1},"3":{"1116":1,"1119":1,"1466":1,"1469":1}}],["defender",{"3":{"0":1,"403":1,"1116":2,"1118":1,"1466":1}}],["defends",{"3":{"0":1,"1124":1,"1249":1,"1338":1,"1358":1,"1435":3,"1566":1,"2140":1}}],["defect",{"3":{"0":1,"46":1,"135":1,"249":1,"390":2,"415":1,"474":1,"549":2,"619":1,"799":1,"800":2,"801":2,"803":1,"856":1,"890":1,"955":1,"966":1,"1004":1,"1049":1,"1050":1,"1247":1,"1270":2,"1285":3,"1299":1,"1300":1,"1310":2,"1323":6,"1338":1,"1349":1,"1355":1,"1358":8,"1394":3,"1395":1,"1401":1,"1416":1,"1435":22,"1437":1,"1466":1,"1472":1,"1534":1,"1585":1,"1590":1,"1596":1,"1598":3,"1609":1,"1642":2,"1670":1,"1674":1,"1743":1,"1798":1,"1840":1,"1908":1,"2062":1,"2182":1}}],["defects",{"3":{"0":1,"157":1,"517":1,"1299":2,"1310":1,"1323":1,"1401":1,"1908":1,"2050":2}}],["defaultkeyid",{"3":{"1435":1,"1487":1}}],["defaultkeyversion",{"3":{"1285":1}}],["defaultinterval",{"3":{"1435":1}}],["defaulting",{"3":{"0":5,"178":1,"226":1,"229":1,"243":1,"255":1,"274":1,"333":1,"351":1,"421":1,"539":1,"549":2,"640":1,"819":1,"820":1,"922":1,"973":1,"1116":1,"1184":1,"1229":1,"1237":1,"1241":1,"1243":1,"1247":4,"1249":1,"1257":1,"1259":4,"1267":1,"1270":2,"1271":3,"1273":1,"1277":2,"1279":1,"1285":15,"1299":10,"1300":3,"1308":5,"1310":5,"1316":1,"1322":14,"1323":17,"1325":1,"1331":1,"1335":1,"1337":1,"1338":6,"1349":10,"1358":10,"1365":1,"1375":1,"1379":2,"1386":1,"1391":1,"1394":8,"1395":9,"1401":1,"1411":1,"1414":2,"1418":3,"1422":1,"1423":1,"1426":1,"1432":1,"1435":18,"1440":1,"1454":1,"1464":1,"1466":2,"1469":1,"1473":1,"1475":1,"1483":1,"1487":1,"1683":1,"1694":1,"1921":1,"1969":1,"2172":2,"2231":1}}],["defaultissuer",{"3":{"1435":1,"1487":1}}],["defaultleadminutes",{"3":{"1395":2}}],["defaultlocalizationinterceptor",{"3":{"265":1,"1323":2}}],["defaultversionpolicy",{"3":{"1338":1}}],["defaultrouteheadername",{"3":{"1338":1}}],["defaultredactionplaceholder",{"3":{"1426":1}}],["defaultrequestversion",{"3":{"1338":1}}],["defaultrequestheaders",{"3":{"1323":2}}],["defaultretrydelay",{"3":{"1323":1}}],["defaultresolutionorder",{"3":{"1285":1}}],["defaultculture",{"3":{"1435":1}}],["defaultclusterheadername",{"3":{"1338":1}}],["defaultcleartextport",{"3":{"95":2,"1338":2}}],["defaultconnectionname",{"3":{"1338":3}}],["defaultbaseaddress",{"3":{"1435":3}}],["defaultbaseurl",{"3":{"1435":1,"1487":1}}],["defaultbackoff",{"3":{"1323":1,"1395":1}}],["defaultbrokerresourcename",{"2":{"1325":1},"3":{"1325":1,"1338":1,"1440":1}}],["defaultborderradius",{"3":{"1323":1}}],["defaultjwkspath",{"3":{"1310":2}}],["defaulthttpcontext",{"3":{"1310":1}}],["defaultheaders",{"3":{"1310":3}}],["defaultmaxoutputtokens",{"3":{"1426":1}}],["defaultmaxexportrows",{"3":{"741":1,"1310":1,"1322":1}}],["defaultmaildevresourcename",{"3":{"1338":1}}],["defaultmessagetimetolive",{"3":{"1322":1,"1435":1}}],["defaultmessage",{"3":{"1285":2}}],["defaultpassword",{"3":{"1435":3}}],["defaultprivatekeypem",{"3":{"1428":1,"1435":1,"1487":1}}],["defaultprobepath",{"2":{"99":1},"3":{"99":1,"1338":3,"1440":1}}],["defaultprotocols",{"3":{"95":1,"1330":1,"1338":4}}],["defaultpublickeypem",{"3":{"1428":1,"1435":1,"1487":1}}],["defaultport",{"3":{"1338":2}}],["defaultpopulatelocktimeout",{"3":{"1270":1,"1322":3}}],["defaultwidth",{"3":{"1270":1,"1299":2}}],["defaultname",{"3":{"1259":2,"1285":8,"1322":1}}],["defaultdatasourceresolver",{"3":{"1199":12,"1207":1}}],["defaultduration",{"2":{"243":1,"254":1,"257":1,"736":1,"1707":1,"1915":2},"3":{"243":2,"254":3,"257":2,"733":1,"736":1,"1300":18,"1707":2,"1915":2}}],["defaultoidcdiscoverypath",{"3":{"1310":1}}],["defaultoauthuisettings",{"3":{"1199":2,"1203":1,"1207":2,"1323":7,"1415":1}}],["defaultoptions",{"2":{"1091":1},"3":{"1091":1,"1426":1}}],["default=warning",{"3":{"790":1,"1441":1}}],["defaulttooauthauthentication",{"3":{"1466":1}}],["defaulttoautonegotiation",{"3":{"1338":2}}],["defaulttitlefor",{"3":{"1394":1}}],["defaulttimeout",{"3":{"1323":3,"1426":1,"1435":4,"1487":1}}],["defaultthreadcurrentuiculture",{"3":{"1323":1,"1416":1}}],["defaultthreadcurrentculture",{"3":{"1323":1,"1416":1}}],["defaultthreadcurrent",{"2":{"265":1},"3":{"265":2,"1323":1}}],["defaultttl",{"3":{"243":1,"1323":4}}],["defaultemail",{"3":{"1435":3}}],["defaultemulatorimage",{"3":{"640":1,"1435":1}}],["defaultenginedbcontextfactories",{"3":{"1495":1}}],["defaultendpointname",{"3":{"1338":2}}],["defaultentityconfigurationassemblyprovider",{"2":{"1203":1,"1281":1,"1322":1},"3":{"1199":3,"1203":1,"1207":2,"1281":3,"1285":11,"1322":1,"1435":1}}],["defaultentityconfigurationassemblyprovidertests",{"2":{"1199":1},"3":{"1199":1,"1207":2,"1285":1}}],["defaultexcludedpathprefixes",{"3":{"1285":1}}],["defaultexpiration",{"2":{"243":1,"254":1,"257":1},"3":{"243":1,"254":3,"257":1,"1300":4,"1310":3}}],["defaultexemptpathprefixes",{"2":{"189":1},"3":{"189":1,"1299":3}}],["defaulted",{"3":{"0":1,"583":1,"649":1,"650":1,"768":1,"827":1,"837":1,"861":1,"862":1,"1018":1,"1034":1,"1212":1,"1247":1,"1253":1,"1258":1,"1259":7,"1265":2,"1270":4,"1283":1,"1285":6,"1299":1,"1300":1,"1306":1,"1308":4,"1310":3,"1322":1,"1323":9,"1338":1,"1343":1,"1348":1,"1349":7,"1358":12,"1394":5,"1395":19,"1401":2,"1414":2,"1416":2,"1435":6,"1917":1,"1944":1,"2195":1,"2202":1}}],["defaultaction",{"3":{"1466":1}}],["defaultauthorizationservice",{"2":{"954":1,"956":1,"960":1},"3":{"954":1,"956":1,"960":1}}],["defaultauthorizationpolicyprovider",{"2":{"186":1,"1961":1},"3":{"186":1,"1299":2,"1961":1}}],["defaultapiversion",{"2":{"446":1},"3":{"446":1,"448":1,"1310":1,"2130":1}}],["defaultazurecredential",{"2":{"441":1,"598":1,"599":1,"664":1,"665":1,"670":1,"1334":1,"1335":1,"1441":1,"1969":1,"2125":1,"2126":1},"3":{"0":2,"440":1,"441":1,"598":1,"599":1,"664":1,"665":1,"670":1,"1212":1,"1322":4,"1334":1,"1335":1,"1338":6,"1441":1,"1466":5,"1576":1,"1969":1,"2125":1,"2126":1}}],["defaultstothederivedtypesassembly",{"3":{"1435":1}}],["defaultsettings",{"3":{"1576":1}}],["defaultservicebusemulatorresourcename",{"3":{"1338":1}}],["defaultseed",{"3":{"1199":2,"1203":1,"1207":1,"1279":1,"1285":3}}],["defaultsmtpport",{"3":{"1322":2}}],["defaultsortcolumn",{"3":{"1379":1}}],["defaultsort",{"3":{"1247":1}}],["defaultsspecification",{"3":{"1199":2,"1207":1}}],["defaultsqlserverdbcontextfactory",{"2":{"703":1},"3":{"703":1}}],["defaults",{"0":{"1441":1,"1442":1,"1677":1,"2118":1},"1":{"1324":1,"1325":1,"1326":1,"1327":1,"1328":1,"1329":1,"1330":1,"1331":1,"1332":1,"1333":1,"1334":1,"1335":1,"1336":1,"1337":1,"1338":1},"2":{"150":1},"3":{"0":15,"19":2,"22":1,"23":1,"25":1,"27":1,"31":3,"66":1,"68":1,"72":1,"73":1,"74":2,"95":3,"98":2,"100":3,"102":1,"145":1,"146":1,"147":1,"150":1,"168":1,"177":1,"189":1,"193":1,"198":1,"202":1,"217":1,"219":1,"225":2,"228":1,"230":1,"235":1,"243":2,"246":1,"255":1,"258":1,"263":1,"265":4,"314":1,"318":2,"325":1,"333":1,"335":1,"350":1,"384":1,"395":4,"397":3,"400":1,"402":1,"404":1,"406":2,"413":2,"414":1,"416":1,"434":2,"482":1,"536":1,"538":1,"539":1,"583":3,"585":2,"599":1,"600":1,"609":2,"622":1,"635":1,"640":2,"657":1,"668":1,"674":3,"690":1,"699":1,"720":1,"733":2,"736":1,"749":1,"809":1,"819":3,"821":1,"827":5,"832":2,"833":1,"837":1,"861":5,"862":1,"863":1,"897":1,"905":2,"913":2,"931":1,"954":1,"956":1,"957":1,"994":1,"998":1,"1004":1,"1018":3,"1026":1,"1042":1,"1045":1,"1050":1,"1059":1,"1067":1,"1082":1,"1089":1,"1091":1,"1092":1,"1093":1,"1094":1,"1119":1,"1124":3,"1135":1,"1160":1,"1175":1,"1192":1,"1202":1,"1203":1,"1212":3,"1237":1,"1241":2,"1244":3,"1247":7,"1249":3,"1257":1,"1258":1,"1259":11,"1270":4,"1271":1,"1275":1,"1276":2,"1277":1,"1283":1,"1284":1,"1285":21,"1287":1,"1292":1,"1296":2,"1299":35,"1300":10,"1301":1,"1308":5,"1310":26,"1311":3,"1319":2,"1322":22,"1323":39,"1324":2,"1325":1,"1329":2,"1332":1,"1334":1,"1336":1,"1337":3,"1338":79,"1349":8,"1354":1,"1358":28,"1363":1,"1367":1,"1368":4,"1379":1,"1383":1,"1392":1,"1394":17,"1395":13,"1401":2,"1414":10,"1415":5,"1416":24,"1419":1,"1426":5,"1428":1,"1432":1,"1434":1,"1435":44,"1436":1,"1437":9,"1438":2,"1440":1,"1441":1,"1442":6,"1443":1,"1445":1,"1446":6,"1454":2,"1457":1,"1465":1,"1466":6,"1469":3,"1474":1,"1476":1,"1480":3,"1486":1,"1487":2,"1489":1,"1490":1,"1495":4,"1536":1,"1554":1,"1556":1,"1560":1,"1576":3,"1581":1,"1590":1,"1595":1,"1630":2,"1639":1,"1655":1,"1659":4,"1661":1,"1662":3,"1669":3,"1673":1,"1675":1,"1706":1,"1733":1,"1797":1,"1800":1,"1824":1,"1841":1,"1842":1,"1894":1,"1907":1,"1915":2,"1920":1,"1922":1,"1935":1,"1969":1,"1988":1,"1993":2,"1994":1,"1999":1,"2000":2,"2010":1,"2013":3,"2017":1,"2029":2,"2030":1,"2037":1,"2042":1,"2045":1,"2061":1,"2074":1,"2082":1,"2118":1,"2121":3,"2122":2,"2130":1,"2148":1,"2154":1,"2155":1,"2158":1,"2159":1,"2166":1,"2167":1,"2187":1,"2197":2,"2198":1,"2226":1}}],["default",{"0":{"1326":1,"2063":1,"2117":1},"1":{"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1,"1047":1,"1048":1,"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1,"1080":1,"1081":1,"1082":1,"1083":1,"1084":1,"1085":1,"1086":1,"1087":1,"2153":1,"2154":1,"2155":1,"2156":1,"2157":1,"2158":1,"2159":1},"2":{"511":1,"513":1,"916":1,"1041":1,"1261":1,"1385":1,"1422":1,"1466":1,"1651":1,"2174":1},"3":{"0":76,"5":1,"7":2,"15":2,"17":3,"19":3,"20":1,"24":1,"25":1,"26":1,"27":1,"29":1,"31":4,"32":2,"38":1,"39":3,"41":1,"47":2,"55":1,"62":1,"67":1,"68":1,"71":1,"72":2,"73":2,"78":4,"79":2,"91":2,"92":4,"93":3,"94":1,"95":1,"96":5,"97":1,"98":7,"99":2,"100":1,"101":2,"102":2,"109":1,"121":1,"135":1,"147":1,"150":1,"152":1,"157":1,"158":1,"159":1,"165":1,"166":6,"170":2,"173":1,"175":7,"177":1,"178":5,"179":3,"184":1,"185":1,"186":2,"189":2,"193":1,"195":3,"200":4,"201":3,"203":1,"212":1,"214":6,"215":3,"216":4,"219":5,"220":2,"225":3,"226":1,"227":2,"228":2,"229":1,"230":2,"231":1,"233":1,"235":2,"236":1,"241":2,"243":5,"244":1,"245":2,"248":1,"249":1,"252":1,"254":2,"255":3,"258":1,"265":4,"273":2,"279":2,"280":3,"281":2,"283":1,"290":1,"302":1,"310":1,"318":8,"320":1,"322":1,"324":1,"326":1,"329":1,"330":1,"332":1,"335":2,"340":1,"344":1,"350":7,"351":1,"352":1,"353":1,"360":1,"369":1,"380":2,"381":1,"384":1,"386":2,"387":4,"388":3,"389":1,"395":2,"397":3,"398":1,"402":2,"407":1,"413":1,"414":1,"415":1,"420":2,"422":1,"432":1,"434":2,"436":2,"440":2,"443":1,"444":1,"446":1,"447":1,"448":3,"449":2,"458":1,"468":1,"499":4,"501":3,"502":1,"507":1,"511":1,"513":1,"520":1,"527":1,"549":1,"556":2,"572":1,"573":1,"578":1,"583":3,"584":1,"599":4,"602":1,"609":3,"626":2,"633":2,"635":1,"640":2,"642":1,"644":1,"649":1,"650":1,"656":1,"657":2,"660":1,"661":1,"664":1,"665":6,"666":1,"667":4,"668":6,"674":3,"682":4,"690":1,"691":1,"698":5,"699":2,"700":1,"707":6,"708":1,"709":4,"711":1,"713":1,"715":1,"731":2,"733":7,"734":1,"736":1,"737":1,"740":1,"741":4,"743":4,"749":2,"774":3,"775":2,"781":1,"782":1,"784":1,"790":1,"791":1,"793":1,"798":1,"804":1,"819":2,"820":2,"821":1,"826":2,"827":8,"829":1,"830":2,"831":1,"832":1,"837":1,"838":2,"841":1,"846":2,"854":2,"857":2,"861":2,"868":1,"881":3,"890":1,"897":2,"900":1,"905":8,"907":1,"912":1,"913":1,"914":1,"915":2,"916":1,"922":1,"924":1,"926":2,"932":1,"933":1,"935":2,"946":1,"953":1,"954":5,"956":2,"960":1,"961":1,"962":1,"964":4,"965":3,"966":1,"968":1,"971":1,"972":3,"974":1,"988":3,"991":1,"998":1,"999":2,"1004":1,"1006":1,"1012":2,"1018":3,"1020":1,"1033":1,"1034":1,"1035":1,"1036":1,"1040":1,"1041":1,"1044":1,"1047":1,"1049":3,"1050":5,"1052":1,"1053":1,"1054":1,"1055":1,"1059":2,"1061":1,"1067":1,"1069":1,"1080":1,"1081":1,"1082":2,"1083":2,"1084":1,"1089":3,"1090":1,"1091":5,"1092":1,"1094":1,"1108":1,"1109":1,"1114":1,"1116":4,"1117":1,"1118":1,"1119":2,"1122":1,"1124":7,"1127":1,"1133":2,"1134":1,"1135":4,"1161":1,"1168":1,"1175":5,"1177":1,"1179":1,"1184":2,"1188":1,"1212":9,"1223":1,"1229":7,"1231":2,"1232":1,"1235":1,"1236":2,"1237":24,"1241":2,"1242":1,"1244":2,"1247":23,"1249":1,"1253":1,"1254":2,"1255":1,"1256":4,"1257":1,"1258":2,"1259":56,"1261":1,"1263":1,"1264":1,"1267":2,"1269":2,"1270":50,"1271":10,"1273":2,"1274":1,"1275":1,"1276":3,"1277":1,"1279":9,"1280":3,"1284":1,"1285":197,"1286":1,"1287":7,"1288":2,"1289":4,"1291":3,"1293":3,"1294":1,"1295":1,"1296":4,"1299":110,"1300":55,"1301":4,"1303":2,"1304":1,"1306":3,"1307":1,"1308":64,"1309":2,"1310":101,"1311":1,"1312":1,"1313":1,"1316":1,"1317":2,"1319":15,"1320":1,"1321":1,"1322":103,"1323":135,"1325":4,"1327":3,"1330":3,"1332":3,"1333":2,"1334":2,"1335":4,"1337":5,"1338":89,"1341":1,"1342":2,"1343":2,"1348":3,"1349":37,"1352":1,"1353":1,"1354":1,"1358":147,"1364":1,"1368":18,"1371":1,"1372":1,"1377":1,"1378":5,"1379":14,"1382":2,"1385":1,"1388":2,"1391":3,"1394":75,"1395":54,"1397":1,"1398":1,"1399":2,"1401":40,"1404":2,"1405":2,"1406":1,"1407":1,"1414":26,"1415":14,"1416":270,"1420":2,"1422":3,"1423":1,"1426":12,"1429":2,"1430":3,"1431":1,"1432":3,"1435":204,"1437":12,"1439":1,"1440":12,"1441":4,"1442":8,"1443":3,"1445":5,"1446":3,"1448":1,"1449":1,"1452":3,"1453":1,"1454":10,"1455":2,"1457":1,"1464":2,"1465":3,"1466":32,"1469":1,"1471":1,"1473":2,"1474":6,"1475":1,"1476":3,"1480":2,"1483":1,"1487":8,"1488":2,"1491":1,"1495":7,"1525":7,"1526":1,"1533":1,"1536":1,"1537":1,"1538":1,"1539":1,"1540":1,"1541":1,"1542":1,"1543":1,"1544":1,"1545":1,"1546":1,"1547":1,"1548":1,"1549":1,"1550":1,"1551":1,"1552":1,"1553":1,"1554":1,"1555":1,"1556":1,"1557":1,"1558":1,"1559":1,"1560":1,"1561":1,"1562":2,"1563":1,"1564":1,"1565":1,"1566":2,"1567":1,"1568":1,"1569":1,"1570":1,"1573":1,"1574":1,"1576":22,"1577":1,"1580":1,"1581":1,"1582":3,"1584":3,"1585":2,"1590":1,"1591":2,"1595":1,"1599":1,"1620":2,"1626":1,"1629":8,"1630":4,"1631":1,"1632":1,"1636":2,"1637":1,"1638":3,"1639":6,"1640":4,"1651":2,"1652":1,"1661":1,"1662":1,"1663":1,"1666":2,"1667":3,"1668":1,"1669":2,"1676":7,"1683":4,"1684":1,"1696":1,"1703":1,"1706":1,"1707":3,"1717":2,"1718":1,"1720":1,"1726":2,"1727":1,"1730":2,"1733":2,"1756":1,"1766":3,"1769":2,"1773":1,"1778":1,"1789":1,"1809":3,"1810":1,"1825":1,"1840":4,"1841":1,"1842":3,"1843":1,"1844":3,"1848":3,"1850":1,"1852":1,"1855":2,"1861":1,"1868":2,"1879":1,"1880":1,"1881":1,"1888":6,"1891":1,"1895":1,"1897":1,"1903":1,"1904":1,"1905":1,"1907":1,"1909":2,"1910":1,"1911":1,"1914":2,"1915":2,"1916":1,"1919":2,"1920":1,"1921":7,"1924":1,"1932":2,"1933":1,"1934":1,"1937":1,"1940":1,"1941":1,"1943":1,"1944":3,"1945":1,"1947":2,"1949":1,"1951":1,"1952":2,"1961":1,"1964":2,"1969":2,"1970":1,"1974":2,"1975":1,"1976":2,"1977":2,"1978":2,"1981":5,"1983":2,"1986":1,"1988":1,"1989":2,"1993":5,"1994":1,"1995":3,"1996":1,"1999":8,"2000":2,"2001":1,"2005":1,"2007":1,"2008":1,"2009":2,"2010":1,"2011":1,"2012":1,"2023":1,"2024":2,"2029":1,"2030":1,"2042":1,"2045":2,"2055":1,"2060":1,"2062":3,"2063":3,"2064":1,"2067":1,"2068":1,"2074":2,"2075":1,"2076":1,"2081":1,"2082":4,"2085":1,"2093":1,"2097":1,"2117":6,"2118":2,"2120":1,"2122":3,"2125":4,"2126":1,"2127":2,"2128":2,"2129":2,"2130":3,"2132":3,"2137":1,"2141":1,"2148":6,"2149":4,"2150":1,"2151":3,"2152":4,"2153":2,"2154":1,"2155":2,"2156":1,"2157":1,"2159":1,"2165":1,"2168":2,"2174":1,"2179":2,"2186":1,"2191":1,"2195":1,"2196":1,"2198":2,"2201":1,"2202":1,"2208":1,"2214":1,"2231":6}}],["deducted",{"3":{"1525":1,"1576":1}}],["deduction",{"3":{"1523":1,"1525":2,"1590":2,"1598":1,"1609":1,"1743":1}}],["deductions",{"3":{"1523":1,"1525":1}}],["dedupable",{"3":{"881":1}}],["dedupkeyindex",{"3":{"1285":1}}],["dedupkeymaxlength",{"3":{"1285":2,"1308":1}}],["dedupkey",{"2":{"231":1,"890":1,"892":1,"1933":1,"1940":1},"3":{"225":2,"231":1,"889":2,"890":1,"892":1,"1175":1,"1285":6,"1303":4,"1308":16,"1932":3,"1933":2,"1940":1}}],["dedupes",{"3":{"1237":1,"1323":1}}],["dedupe",{"3":{"220":1,"1237":1,"1395":1}}],["deduped",{"3":{"197":1,"789":1,"1395":2}}],["deduping",{"3":{"194":2,"1259":1,"1437":1}}],["deduplicate",{"3":{"538":1,"1310":2,"1395":1,"1720":1,"1932":1,"1940":1,"2167":1}}],["deduplicates",{"3":{"199":1,"517":1,"518":1,"645":1,"1299":2,"1300":1,"1310":1,"1338":1,"1375":1,"1414":1}}],["deduplicated",{"3":{"0":1,"109":1,"157":1,"160":1,"166":1,"225":1,"827":1,"1100":1,"1285":1,"1299":2,"1310":3,"1379":1,"1394":1,"1415":1,"1495":2,"1909":1}}],["deduplication",{"3":{"157":1,"158":1,"231":1,"846":1,"1018":1,"1247":2,"1249":2,"1259":7,"1269":1,"1270":3,"1302":1,"1308":5,"1310":6,"1316":1,"1323":3,"1338":1,"1349":1,"1379":1,"1435":1,"1442":1,"1495":1,"1888":1,"1908":1,"1932":1,"1939":1,"1940":1}}],["deduplicating",{"3":{"0":2,"109":1,"156":1,"1100":1,"1323":1,"1331":1,"1379":1}}],["dedup",{"0":{"1933":1},"3":{"0":8,"24":1,"194":1,"195":2,"196":2,"197":4,"200":1,"201":4,"202":2,"223":2,"516":1,"518":2,"519":1,"520":1,"524":2,"541":1,"694":1,"702":1,"786":1,"795":1,"815":1,"817":1,"823":1,"847":1,"889":2,"892":1,"930":1,"1074":1,"1099":1,"1104":1,"1212":1,"1237":1,"1247":1,"1249":1,"1258":2,"1259":14,"1285":3,"1303":4,"1308":12,"1322":6,"1323":1,"1349":1,"1358":1,"1379":1,"1395":1,"1437":1,"1495":1,"1507":1,"1525":1,"1576":2,"1585":1,"1664":1,"1909":2,"1910":1,"1932":4,"1933":1,"1939":1,"2165":1}}],["dedups",{"3":{"0":3,"195":2,"255":1,"340":2,"346":2,"538":1,"541":1,"846":1,"882":1,"1212":2,"1259":2,"1270":1,"1394":1,"1495":1,"1525":1,"1664":1,"1844":1,"1869":2,"1909":1,"1921":1,"2163":2,"2231":2}}],["dedicated",{"1":{"738":1,"739":1,"740":1,"741":1,"742":1,"743":1,"744":1,"745":1},"3":{"0":9,"51":1,"91":3,"92":2,"93":3,"94":3,"96":1,"98":1,"99":1,"100":1,"186":1,"195":1,"229":2,"343":1,"350":1,"395":1,"403":1,"407":1,"413":2,"536":1,"591":1,"593":1,"612":1,"618":1,"638":1,"640":2,"649":1,"663":1,"665":1,"666":1,"669":1,"698":1,"738":1,"741":1,"825":1,"846":1,"847":1,"939":1,"1069":1,"1212":4,"1215":1,"1241":1,"1247":2,"1258":1,"1270":1,"1271":2,"1275":1,"1285":8,"1306":1,"1308":6,"1310":3,"1311":1,"1322":3,"1323":4,"1327":1,"1330":1,"1338":8,"1349":3,"1358":6,"1375":1,"1378":1,"1379":4,"1394":5,"1395":4,"1399":1,"1401":2,"1414":5,"1416":4,"1426":1,"1430":1,"1435":2,"1437":2,"1440":1,"1441":1,"1445":1,"1448":1,"1466":4,"1485":1,"1486":1,"1491":1,"1502":1,"1508":1,"1525":3,"1533":2,"1576":1,"1584":2,"1598":1,"1638":1,"1640":2,"1659":1,"1660":1,"1665":1,"1669":1,"1731":1,"1749":1,"1772":1,"1841":1,"1850":1,"1928":2,"1929":2,"1945":1,"1962":1,"1974":1,"2024":2,"2046":1,"2082":1,"2101":1,"2119":1,"2155":1,"2166":1,"2231":2}}],["deprovisioned",{"3":{"1567":1}}],["deprecation",{"0":{"1736":1},"3":{"447":1,"448":1,"1006":1,"1017":1,"1019":1,"1020":1,"1310":1,"1375":1,"1434":1,"1437":1,"1452":1,"1454":1,"1491":1,"2128":1,"2129":1,"2130":1,"2132":2}}],["deprecate",{"3":{"447":1,"449":1,"940":1,"2129":1,"2132":1}}],["deprecatedversions",{"3":{"1310":1}}],["deprecated",{"3":{"0":4,"109":1,"448":5,"449":2,"528":1,"572":1,"881":1,"1212":1,"1299":1,"1310":11,"1375":3,"1379":4,"1435":5,"1458":2,"1487":2,"1525":2,"1534":1,"1590":1,"2054":1,"2130":5,"2132":3,"2231":1}}],["depersonalizes",{"3":{"1401":1}}],["dependable",{"3":{"1310":1}}],["dependabot",{"2":{"149":1,"152":1,"1078":1},"3":{"0":2,"145":1,"147":2,"148":1,"149":6,"150":1,"152":4,"373":2,"870":1,"873":1,"1074":5,"1075":1,"1078":3,"1458":7,"1575":2,"1576":2,"1585":2,"1672":1}}],["dependants",{"3":{"1237":1}}],["depended",{"3":{"1034":1,"1191":1,"1200":1,"1322":2,"1414":1,"1504":1,"1880":1}}],["dependence",{"3":{"1323":1,"1358":1,"1395":1,"1401":1,"2001":1}}],["dependencies",{"2":{"584":1,"585":1,"1313":1,"1879":1,"1880":1,"2093":1,"2097":1},"3":{"0":1,"59":1,"122":1,"145":1,"146":1,"168":1,"237":1,"265":1,"371":1,"376":1,"583":5,"584":1,"585":6,"608":1,"609":1,"665":1,"708":1,"733":1,"742":1,"939":2,"940":2,"990":1,"1191":2,"1196":2,"1199":1,"1211":1,"1214":1,"1229":1,"1237":1,"1247":1,"1259":2,"1270":1,"1271":3,"1285":5,"1299":4,"1300":1,"1307":1,"1308":2,"1309":2,"1310":5,"1311":1,"1313":2,"1314":2,"1322":11,"1323":2,"1324":1,"1338":2,"1358":10,"1359":1,"1370":1,"1379":4,"1392":1,"1394":1,"1395":19,"1401":3,"1405":1,"1414":16,"1416":4,"1430":1,"1431":1,"1435":36,"1444":1,"1453":1,"1466":1,"1474":1,"1488":2,"1495":1,"1503":1,"1537":1,"1539":1,"1550":2,"1551":2,"1568":3,"1569":1,"1576":1,"1590":1,"1596":1,"1599":1,"1637":1,"1652":1,"1670":2,"1692":1,"1708":1,"1774":1,"1877":1,"1878":1,"1879":2,"1880":3,"1885":2,"1947":1,"1963":1,"2040":1,"2086":1,"2093":4,"2094":1,"2097":2,"2157":1,"2163":1,"2229":1}}],["dependencyversion",{"3":{"1575":1,"1576":1}}],["dependencyversiontests",{"2":{"1214":1,"1453":1,"1800":1,"2041":1,"2047":1},"3":{"147":1,"1074":1,"1199":1,"1207":2,"1214":1,"1435":10,"1453":1,"1488":3,"1490":2,"1492":1,"1576":1,"1800":1,"2041":1,"2047":1}}],["dependencyversiontestsbase",{"2":{"147":1,"1075":1,"1670":1},"3":{"0":1,"147":2,"150":1,"1074":1,"1075":1,"1199":2,"1207":2,"1212":1,"1430":5,"1435":7,"1488":2,"1490":1,"1575":1,"1576":1,"1585":1,"1670":1}}],["dependencycycles",{"3":{"1435":1}}],["dependencymodule",{"3":{"1322":2}}],["dependencyinjectionoutboxgatetests",{"3":{"1199":1,"1207":2,"1437":2}}],["dependencyinjectionbrokermessagingtests",{"2":{"1495":1},"3":{"1199":1,"1207":4,"1495":1,"1576":1}}],["dependencyinjectionadditionaltests",{"3":{"1199":1,"1207":2}}],["dependencyinjectionasserttests",{"3":{"1199":1,"1207":4,"1435":1,"1437":1,"1486":1}}],["dependencyinjectionassert",{"2":{"1496":1},"3":{"1199":4,"1207":2,"1427":1,"1435":7,"1437":1,"1487":2,"1495":1,"1496":1}}],["dependencyinjectionpushnotificationstests",{"3":{"1199":1,"1207":2}}],["dependencyinjectioninfrastructuretests",{"3":{"1175":1,"1199":1,"1207":2,"1285":2}}],["dependencyinjectiontests",{"2":{"1262":1},"3":{"996":1,"1199":6,"1207":13,"1262":2,"1270":1,"1322":1,"1435":6,"1437":1,"1486":1}}],["dependencyinjection",{"2":{"115":4,"145":1,"152":1,"203":1,"220":1,"354":1,"378":1,"514":1,"543":2,"552":1,"644":1,"652":1,"677":1,"696":1,"703":1,"736":1,"794":1,"822":1,"884":1,"917":1,"929":1,"991":1,"999":1,"1062":1,"1074":1,"1078":2,"1214":1,"1247":1,"1262":2,"1270":1,"1311":1,"1364":1,"1367":1,"1368":1,"1413":1,"1426":1,"1499":2,"1684":1,"1770":1},"3":{"24":1,"25":2,"27":4,"53":1,"68":3,"74":2,"96":1,"100":1,"109":1,"111":1,"112":1,"115":4,"121":1,"122":9,"126":1,"145":2,"150":4,"152":2,"180":1,"186":5,"193":1,"196":1,"198":1,"200":1,"201":1,"203":3,"216":2,"219":1,"220":2,"225":2,"228":1,"230":1,"231":2,"243":5,"245":1,"250":1,"251":1,"252":1,"254":1,"256":1,"257":1,"258":2,"259":3,"260":3,"261":4,"285":1,"300":1,"303":1,"306":1,"310":2,"318":7,"324":2,"326":1,"343":1,"352":1,"354":1,"378":1,"384":1,"413":6,"415":1,"434":2,"436":2,"459":2,"463":2,"464":2,"465":2,"466":2,"507":8,"511":3,"513":2,"514":1,"536":2,"543":3,"545":1,"549":2,"552":2,"583":2,"585":1,"640":7,"642":2,"644":2,"649":4,"652":1,"656":1,"657":1,"660":2,"674":23,"675":1,"676":5,"677":2,"682":1,"696":1,"698":1,"703":1,"707":1,"710":1,"715":2,"720":3,"725":1,"733":3,"736":3,"740":1,"790":1,"792":1,"794":1,"798":1,"804":1,"806":1,"819":3,"821":1,"822":3,"827":1,"846":1,"850":2,"881":1,"884":1,"905":2,"915":1,"917":1,"922":3,"924":4,"926":4,"927":3,"929":1,"931":4,"934":2,"946":1,"949":1,"954":2,"955":1,"960":1,"964":2,"965":1,"967":1,"979":6,"980":29,"982":5,"983":4,"988":1,"991":1,"996":5,"999":1,"1018":8,"1021":2,"1042":1,"1050":2,"1055":1,"1059":6,"1062":1,"1074":1,"1075":1,"1078":2,"1090":1,"1091":10,"1092":1,"1093":2,"1094":1,"1095":1,"1100":2,"1103":2,"1108":1,"1111":1,"1116":1,"1133":1,"1136":1,"1175":1,"1176":1,"1177":1,"1184":3,"1199":30,"1203":29,"1207":79,"1208":40,"1212":1,"1214":1,"1229":1,"1232":2,"1237":9,"1247":6,"1257":5,"1258":1,"1259":36,"1262":28,"1263":12,"1265":7,"1269":3,"1270":90,"1271":9,"1285":95,"1289":2,"1293":1,"1296":6,"1299":82,"1300":50,"1301":3,"1303":3,"1305":1,"1306":4,"1307":3,"1308":73,"1309":8,"1310":69,"1311":25,"1312":1,"1315":43,"1316":11,"1317":5,"1318":2,"1319":14,"1321":1,"1322":159,"1323":138,"1338":12,"1349":9,"1351":1,"1353":8,"1358":234,"1364":2,"1365":2,"1366":2,"1367":7,"1368":9,"1376":4,"1377":3,"1379":26,"1394":60,"1395":133,"1399":1,"1401":11,"1403":3,"1405":1,"1409":1,"1412":2,"1413":3,"1414":96,"1416":253,"1418":1,"1419":2,"1420":4,"1424":3,"1426":41,"1435":13,"1437":5,"1440":6,"1441":2,"1495":26,"1499":2,"1525":7,"1530":1,"1575":1,"1576":11,"1581":1,"1582":1,"1584":1,"1585":2,"1590":3,"1599":1,"1650":1,"1684":1,"1685":1,"1706":2,"1770":1,"1993":2,"1994":1,"1999":2,"2084":1,"2117":2,"2118":2,"2121":3,"2125":5,"2135":2,"2137":1,"2149":1,"2162":1,"2165":1,"2166":1,"2171":2,"2172":1,"2174":1,"2178":3,"2197":1,"2198":2,"2201":1}}],["dependency",{"0":{"1401":1,"1434":1,"1503":1,"1504":1,"1568":1},"1":{"1196":1,"1197":1,"1198":1,"1199":1,"2169":1,"2170":1,"2171":1,"2172":1,"2173":1,"2174":1,"2175":1,"2176":1,"2177":1,"2178":1,"2179":1},"3":{"0":6,"23":2,"33":1,"68":1,"71":1,"72":1,"73":1,"111":1,"122":1,"130":1,"135":2,"145":4,"147":1,"220":1,"227":1,"230":3,"234":2,"235":9,"236":1,"237":2,"263":1,"265":1,"281":1,"310":1,"312":1,"343":1,"369":3,"370":2,"372":1,"376":2,"381":1,"397":1,"403":1,"405":1,"408":1,"415":1,"440":1,"441":1,"454":1,"459":2,"465":1,"509":1,"545":2,"546":1,"547":1,"583":7,"584":1,"585":1,"591":1,"599":1,"601":1,"603":1,"607":1,"609":7,"611":1,"612":1,"665":1,"666":1,"682":1,"684":2,"707":3,"708":1,"734":1,"741":2,"745":1,"801":1,"811":1,"818":1,"821":1,"827":2,"828":1,"830":1,"861":1,"914":1,"939":1,"941":1,"954":2,"980":1,"994":1,"996":4,"999":1,"1018":1,"1036":1,"1043":1,"1050":1,"1051":3,"1059":1,"1066":1,"1067":2,"1068":2,"1069":1,"1075":2,"1077":1,"1090":1,"1091":1,"1092":3,"1093":1,"1117":2,"1124":1,"1135":1,"1162":1,"1179":1,"1185":1,"1191":4,"1192":1,"1194":1,"1195":1,"1196":1,"1200":2,"1201":1,"1210":1,"1211":3,"1212":2,"1214":1,"1216":1,"1229":1,"1230":2,"1237":2,"1244":1,"1247":6,"1250":1,"1256":2,"1259":9,"1270":14,"1271":1,"1275":1,"1284":2,"1285":22,"1286":1,"1288":1,"1292":1,"1296":1,"1299":26,"1300":7,"1302":1,"1306":1,"1307":1,"1308":20,"1309":4,"1310":18,"1312":1,"1314":6,"1319":2,"1322":38,"1323":34,"1327":2,"1330":1,"1331":3,"1332":2,"1334":2,"1335":1,"1336":2,"1338":38,"1346":1,"1348":1,"1349":12,"1357":1,"1358":43,"1368":11,"1376":1,"1379":17,"1390":2,"1394":10,"1395":35,"1399":1,"1401":8,"1402":1,"1406":1,"1414":14,"1415":5,"1416":22,"1417":2,"1418":1,"1421":1,"1426":15,"1427":1,"1428":1,"1429":1,"1430":3,"1431":2,"1434":2,"1435":96,"1437":4,"1440":1,"1441":1,"1442":6,"1444":1,"1445":1,"1448":1,"1449":2,"1451":1,"1452":5,"1453":3,"1454":4,"1458":3,"1460":1,"1464":2,"1466":10,"1468":1,"1473":1,"1474":1,"1475":6,"1482":1,"1487":1,"1488":3,"1489":1,"1490":1,"1491":1,"1492":1,"1495":5,"1497":1,"1498":1,"1499":1,"1500":1,"1501":2,"1516":1,"1524":1,"1525":3,"1533":2,"1534":1,"1539":1,"1543":1,"1547":1,"1549":1,"1550":1,"1552":1,"1562":1,"1565":2,"1568":2,"1570":2,"1571":1,"1575":1,"1576":9,"1581":1,"1584":1,"1585":2,"1590":3,"1598":1,"1599":1,"1610":1,"1652":1,"1653":1,"1659":1,"1661":1,"1662":1,"1669":3,"1670":1,"1672":2,"1676":2,"1708":1,"1737":1,"1750":2,"1771":2,"1778":1,"1781":1,"1784":1,"1795":1,"1804":1,"1806":1,"1814":4,"1830":1,"1832":2,"1840":2,"1842":1,"1864":1,"1876":1,"1877":2,"1880":4,"1881":3,"1882":1,"1884":1,"1885":1,"1933":1,"1934":1,"1939":1,"1944":1,"1947":1,"1969":1,"1970":1,"1999":1,"2006":2,"2007":1,"2009":3,"2013":2,"2023":1,"2029":1,"2036":1,"2038":2,"2039":2,"2040":3,"2042":2,"2046":1,"2047":1,"2048":3,"2052":1,"2053":1,"2055":1,"2070":1,"2078":2,"2097":1,"2098":1,"2103":1,"2125":2,"2126":2,"2134":1,"2156":2,"2157":2,"2168":3,"2169":4,"2170":1,"2171":1,"2172":1,"2173":2,"2177":1,"2178":1,"2179":4,"2192":1,"2208":1,"2229":1,"2231":2}}],["dependentforeignkey",{"3":{"1247":1,"1358":2}}],["dependents",{"3":{"0":1,"543":2,"547":1,"549":1,"1285":2,"1314":2,"1322":1,"1338":1}}],["dependent",{"2":{"1239":1,"1858":1},"3":{"0":2,"386":1,"387":1,"388":1,"392":1,"461":1,"462":1,"487":1,"550":1,"551":1,"582":1,"583":2,"584":1,"585":1,"692":1,"1029":1,"1066":1,"1162":1,"1191":1,"1196":1,"1199":2,"1207":1,"1237":1,"1239":3,"1247":6,"1271":1,"1314":1,"1322":4,"1323":3,"1327":1,"1338":3,"1349":1,"1358":8,"1374":1,"1379":1,"1391":1,"1394":5,"1395":2,"1401":1,"1416":2,"1435":5,"1437":5,"1440":1,"1443":1,"1485":1,"1489":1,"1491":1,"1688":1,"1858":1,"1880":2,"1921":1,"2049":1,"2051":1,"2059":1,"2231":1}}],["depend",{"0":{"2070":1},"2":{"1503":1},"3":{"54":1,"69":1,"185":1,"226":1,"265":1,"310":1,"389":1,"415":1,"509":1,"536":1,"555":1,"585":1,"592":1,"651":1,"698":2,"716":1,"862":1,"987":1,"1060":1,"1187":1,"1211":1,"1214":1,"1222":1,"1229":1,"1247":4,"1259":2,"1269":2,"1270":7,"1275":1,"1278":1,"1285":8,"1299":8,"1300":1,"1308":3,"1310":4,"1311":1,"1315":1,"1316":1,"1322":5,"1323":8,"1329":1,"1338":4,"1349":5,"1358":3,"1368":2,"1376":1,"1394":7,"1395":6,"1414":3,"1415":1,"1416":7,"1426":1,"1430":1,"1432":1,"1435":20,"1437":4,"1440":1,"1442":1,"1452":1,"1488":4,"1491":1,"1503":1,"1537":2,"1584":1,"1637":1,"1659":1,"1780":1,"1831":1,"1840":1,"1860":1,"1879":2,"1880":1,"1881":1,"1893":1,"1907":1,"1945":1,"1960":1,"2038":1,"2041":1,"2042":1,"2046":1,"2068":1,"2103":2}}],["depending",{"3":{"0":1,"244":1,"397":1,"507":1,"513":1,"547":1,"1035":1,"1126":1,"1237":1,"1259":1,"1285":5,"1299":4,"1302":1,"1308":1,"1310":1,"1323":1,"1338":2,"1349":1,"1358":3,"1368":2,"1379":2,"1394":2,"1395":4,"1401":2,"1414":1,"1435":3,"1455":1,"1595":1,"1726":1,"1780":1,"1849":1,"1897":1,"2042":1,"2155":2}}],["dependson",{"3":{"1466":1}}],["dependsonconstanttimecomparison",{"3":{"1435":1}}],["dependsonaslowkeyderivationfunction",{"3":{"1435":1}}],["depends",{"3":{"0":3,"24":1,"54":1,"123":1,"131":1,"159":1,"166":1,"177":1,"236":1,"243":1,"259":1,"303":1,"320":1,"342":1,"349":1,"372":1,"399":1,"460":1,"461":1,"494":1,"507":3,"509":1,"511":2,"513":2,"543":1,"544":1,"549":1,"556":1,"566":1,"574":1,"584":1,"599":1,"642":1,"667":1,"676":1,"682":1,"707":1,"709":1,"720":1,"727":1,"742":1,"748":1,"749":1,"753":3,"756":1,"801":1,"809":2,"827":3,"829":1,"890":1,"905":1,"926":1,"946":2,"988":1,"1020":1,"1059":1,"1091":1,"1093":1,"1170":1,"1191":1,"1193":1,"1200":1,"1211":3,"1212":1,"1229":16,"1237":36,"1241":1,"1247":45,"1257":1,"1259":41,"1269":1,"1270":64,"1271":11,"1273":1,"1284":1,"1285":175,"1291":1,"1299":166,"1300":13,"1306":1,"1308":68,"1309":14,"1310":95,"1311":10,"1319":1,"1322":97,"1323":165,"1338":69,"1340":1,"1345":1,"1348":1,"1349":114,"1350":1,"1358":385,"1368":32,"1379":55,"1381":1,"1382":1,"1394":169,"1395":212,"1401":87,"1403":1,"1412":1,"1414":112,"1415":20,"1416":116,"1422":1,"1426":31,"1435":413,"1455":2,"1459":1,"1464":2,"1466":1,"1488":1,"1489":1,"1497":1,"1502":1,"1552":1,"1576":1,"1659":1,"1665":1,"1683":1,"1780":1,"1790":1,"1792":1,"1843":1,"1845":1,"1848":1,"1862":1,"1877":1,"1882":1,"1914":2,"1923":1,"1934":1,"1999":1,"2000":2,"2077":1,"2093":2,"2097":1,"2098":2,"2099":1,"2116":1,"2121":1,"2122":1,"2174":1,"2177":1,"2190":1,"2198":1,"2231":1}}],["deps",{"3":{"1192":1,"1199":1,"1455":2}}],["departure",{"3":{"725":1}}],["departs",{"3":{"698":1,"1415":1}}],["depthbound",{"3":{"1435":2}}],["depthorinsightquality",{"3":{"1368":1}}],["depthorinsightqualityscore",{"3":{"1349":1,"1358":1,"1368":1}}],["depth",{"0":{"1902":1},"2":{"24":1,"1364":1,"2190":1},"3":{"0":1,"24":1,"27":2,"119":1,"207":1,"328":1,"333":1,"562":1,"566":2,"567":1,"650":1,"707":2,"725":1,"883":1,"1116":1,"1124":1,"1185":1,"1228":2,"1229":1,"1241":1,"1242":1,"1247":4,"1256":1,"1259":11,"1263":1,"1270":2,"1275":1,"1285":6,"1299":2,"1310":5,"1323":2,"1335":1,"1338":3,"1349":1,"1358":1,"1364":1,"1365":1,"1368":1,"1394":1,"1395":1,"1401":1,"1414":4,"1416":1,"1435":16,"1452":2,"1453":1,"1454":2,"1466":2,"1474":1,"1475":1,"1479":1,"1547":1,"1589":1,"1626":1,"1638":2,"1639":1,"1657":1,"1664":1,"1691":1,"1756":1,"1820":1,"1822":1,"1825":1,"1826":1,"1968":1,"2005":1,"2121":1,"2125":1,"2147":1,"2155":3,"2156":2,"2158":1,"2190":2,"2192":2}}],["deploynotificationhub=false",{"3":{"1480":1}}],["deploynotificationhub",{"2":{"1469":1,"1480":1,"1483":1},"3":{"1466":4,"1469":1,"1480":1,"1483":1}}],["deployability",{"3":{"1322":1,"1543":1,"1576":1,"1588":1,"1595":1,"1596":1}}],["deployable",{"3":{"46":1,"597":1,"599":2,"603":1,"610":1,"838":1,"868":1,"1237":1,"1395":1,"1435":2,"1438":1,"1444":1,"1449":1,"1454":1,"1463":1,"1466":2,"1476":2,"1543":1,"1787":2,"1792":1,"2109":1}}],["deployables",{"3":{"0":1,"61":1,"62":1,"599":1,"1338":2,"1435":1,"1441":1,"1466":2,"1791":1,"2026":1}}],["deploying",{"3":{"626":1,"1044":1,"1426":1,"1454":1,"1466":1,"1576":1,"2191":1}}],["deployers",{"3":{"1576":1,"1581":1}}],["deployer",{"3":{"69":1,"1577":1,"1582":1,"1679":1,"1903":1}}],["deployed",{"2":{"1330":1},"3":{"0":10,"18":1,"26":1,"58":1,"62":1,"79":1,"91":1,"95":1,"96":1,"136":2,"155":1,"159":1,"195":1,"259":1,"361":1,"390":1,"395":1,"396":1,"397":2,"459":1,"509":1,"550":1,"564":1,"591":2,"598":1,"599":1,"607":2,"609":2,"611":5,"618":1,"620":1,"625":1,"626":2,"629":1,"665":1,"667":3,"676":1,"690":1,"713":1,"725":1,"732":1,"757":1,"758":1,"765":1,"766":1,"773":1,"776":1,"827":2,"860":1,"861":2,"863":1,"913":1,"914":1,"915":1,"918":1,"933":1,"971":1,"972":2,"995":1,"1017":1,"1018":2,"1021":1,"1040":1,"1058":1,"1060":1,"1066":1,"1082":1,"1085":1,"1107":1,"1108":1,"1109":1,"1118":2,"1119":1,"1124":1,"1135":1,"1143":1,"1147":1,"1254":2,"1259":5,"1270":10,"1285":6,"1292":1,"1299":2,"1300":6,"1308":2,"1310":3,"1312":1,"1322":3,"1323":2,"1330":1,"1332":1,"1334":1,"1338":14,"1349":3,"1358":3,"1367":1,"1368":4,"1395":3,"1400":1,"1401":3,"1414":2,"1426":1,"1433":1,"1434":1,"1435":10,"1436":1,"1437":2,"1440":2,"1441":2,"1442":2,"1444":2,"1454":1,"1464":4,"1466":4,"1473":1,"1476":2,"1477":2,"1480":1,"1483":1,"1485":1,"1488":1,"1491":2,"1495":2,"1499":2,"1525":4,"1530":1,"1576":1,"1595":2,"1640":1,"1670":1,"1671":1,"1672":1,"1676":1,"1678":1,"1717":1,"1722":1,"1784":2,"1840":1,"1847":1,"1854":1,"1860":1,"1885":1,"1886":2,"1887":2,"1891":3,"1908":1,"1915":1,"1922":2,"1923":1,"1969":1,"2009":1,"2042":2,"2043":2,"2046":2,"2055":1,"2056":1,"2109":1,"2142":1,"2153":1,"2155":1,"2193":3,"2197":1,"2213":1,"2220":2,"2227":1,"2228":1}}],["deploymentmode",{"2":{"1469":1},"3":{"1469":1}}],["deploymentparameters",{"2":{"1465":1},"3":{"1465":1}}],["deployment",{"0":{"1465":1,"1467":1,"1553":1},"1":{"1461":1,"1462":1,"1463":1,"1464":1,"1465":1,"1466":1,"1467":1,"1468":1,"1469":1},"2":{"1330":1,"1334":1,"1574":1,"1673":1},"3":{"0":3,"31":1,"32":1,"46":1,"47":1,"55":1,"158":1,"159":1,"168":2,"174":1,"177":2,"193":1,"196":1,"216":1,"227":1,"234":1,"242":1,"243":1,"244":1,"245":1,"255":1,"283":1,"312":1,"343":1,"360":1,"361":1,"386":1,"397":1,"398":1,"403":1,"526":1,"598":1,"599":6,"600":1,"601":2,"608":1,"609":2,"611":3,"612":1,"640":1,"641":2,"650":1,"664":1,"665":4,"666":1,"667":1,"675":1,"700":2,"756":1,"790":1,"792":1,"820":1,"821":1,"827":6,"837":1,"838":1,"839":1,"870":1,"871":1,"915":1,"930":1,"933":1,"948":1,"972":2,"1042":1,"1044":1,"1059":1,"1076":1,"1092":1,"1109":1,"1192":1,"1216":1,"1237":1,"1255":2,"1257":1,"1259":8,"1285":9,"1299":10,"1300":3,"1308":1,"1310":13,"1319":3,"1322":21,"1323":14,"1330":2,"1331":2,"1332":1,"1333":1,"1334":1,"1335":1,"1336":1,"1337":2,"1338":22,"1349":2,"1356":1,"1357":1,"1379":2,"1390":1,"1394":2,"1395":10,"1399":1,"1401":1,"1414":1,"1415":1,"1416":1,"1423":1,"1426":3,"1428":1,"1430":1,"1435":14,"1437":1,"1440":2,"1441":1,"1442":1,"1444":1,"1445":1,"1449":1,"1452":3,"1453":2,"1454":8,"1460":2,"1461":2,"1462":2,"1465":1,"1466":21,"1467":1,"1468":1,"1469":2,"1471":3,"1473":1,"1475":2,"1480":1,"1482":1,"1485":1,"1487":1,"1488":2,"1491":1,"1492":1,"1495":3,"1498":1,"1524":1,"1525":2,"1533":1,"1534":1,"1553":1,"1573":1,"1574":4,"1576":21,"1577":1,"1580":2,"1581":4,"1582":2,"1583":1,"1585":3,"1590":1,"1598":1,"1599":1,"1629":1,"1669":2,"1673":2,"1676":2,"1677":1,"1705":1,"1722":2,"1723":1,"1783":1,"1795":1,"1799":3,"1841":1,"1842":1,"1848":2,"1851":1,"1892":1,"1897":1,"1903":2,"1910":1,"1914":1,"1916":1,"1935":1,"1939":1,"1969":1,"1998":1,"2000":3,"2009":1,"2021":1,"2035":1,"2045":1,"2108":1,"2112":2,"2140":1,"2149":2,"2151":1,"2155":1,"2156":1,"2157":4,"2159":1,"2191":2,"2231":1}}],["deployments",{"3":{"0":1,"224":1,"246":1,"382":1,"395":1,"397":1,"409":1,"434":1,"584":1,"698":1,"811":1,"973":1,"990":1,"1152":1,"1154":1,"1259":1,"1299":1,"1300":1,"1310":1,"1322":2,"1442":1,"1452":1,"1453":1,"1553":1,"1897":1,"1922":1,"1948":1,"2108":2,"2113":1,"2114":1}}],["deploys",{"3":{"0":2,"67":1,"403":2,"564":2,"590":1,"627":1,"766":1,"768":1,"863":1,"865":1,"869":1,"916":1,"1091":1,"1212":1,"1435":1,"1444":1,"1454":4,"1464":2,"1466":2,"1469":1,"1471":1,"1474":1,"1524":1,"1525":3,"1530":1,"1533":2,"1534":1,"1553":1,"1587":1,"1595":1,"1888":1}}],["deploy",{"0":{"1454":1,"1465":1,"2035":1,"2188":1},"1":{"623":1,"624":1,"625":1,"626":1,"627":1,"628":1,"629":1,"630":1,"763":1,"764":1,"765":1,"766":1,"767":1,"768":1,"769":1,"770":1,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1},"2":{"63":1,"291":1,"296":1,"375":1,"452":1,"567":1,"589":1,"594":1,"597":1,"604":1,"616":1,"620":1,"621":1,"624":2,"625":1,"626":1,"628":1,"629":2,"642":1,"668":1,"755":1,"756":2,"761":2,"764":2,"766":1,"767":2,"768":3,"769":1,"770":1,"863":1,"864":1,"867":1,"877":1,"975":1,"1075":1,"1456":1,"1457":1,"1459":2,"1460":2,"1461":1,"1462":2,"1469":1,"1474":1,"1477":1,"1481":1,"1523":1,"1524":1,"1530":1,"1531":1,"1597":1},"3":{"0":35,"25":1,"58":2,"60":1,"62":5,"63":2,"145":2,"150":2,"228":1,"234":1,"246":1,"290":1,"291":14,"292":1,"293":3,"295":1,"296":1,"299":1,"300":1,"301":1,"310":1,"360":1,"365":1,"374":1,"375":4,"405":1,"451":1,"452":3,"453":1,"517":1,"558":1,"563":9,"564":18,"565":1,"566":7,"567":3,"573":2,"589":2,"591":13,"594":3,"595":1,"597":1,"599":8,"600":2,"601":3,"602":1,"604":1,"609":1,"616":4,"618":5,"619":1,"620":6,"621":1,"623":1,"624":10,"625":11,"626":56,"627":10,"628":11,"629":12,"630":2,"638":1,"639":1,"640":6,"641":1,"642":5,"644":1,"665":3,"668":3,"669":3,"698":1,"706":1,"708":1,"718":1,"732":1,"733":1,"755":2,"756":10,"757":36,"758":6,"759":8,"760":5,"761":5,"762":1,"763":1,"764":5,"765":2,"766":23,"767":5,"768":9,"769":2,"770":2,"774":1,"776":1,"827":2,"840":1,"858":1,"860":1,"861":6,"862":1,"863":6,"864":2,"865":2,"867":1,"868":3,"869":4,"870":7,"871":1,"872":1,"873":3,"874":1,"875":2,"876":1,"877":4,"907":1,"914":1,"915":2,"917":1,"953":1,"971":3,"972":4,"973":2,"974":2,"975":1,"976":1,"1017":1,"1018":6,"1019":1,"1020":2,"1058":1,"1075":3,"1101":1,"1116":1,"1119":1,"1192":1,"1212":10,"1215":1,"1247":1,"1285":1,"1299":1,"1300":2,"1308":3,"1310":2,"1317":1,"1322":2,"1323":1,"1327":1,"1338":2,"1356":1,"1358":3,"1365":1,"1379":1,"1395":6,"1408":1,"1413":1,"1414":2,"1435":18,"1437":6,"1444":20,"1445":1,"1448":2,"1449":2,"1451":2,"1452":1,"1453":1,"1454":249,"1455":14,"1456":13,"1457":2,"1458":6,"1459":12,"1460":19,"1461":2,"1462":16,"1463":4,"1464":21,"1465":15,"1466":52,"1467":3,"1468":3,"1469":8,"1471":10,"1473":15,"1474":22,"1475":9,"1476":7,"1477":3,"1480":1,"1481":7,"1483":3,"1484":1,"1485":31,"1488":1,"1489":11,"1490":2,"1491":71,"1492":1,"1493":1,"1495":4,"1508":1,"1522":1,"1523":9,"1524":7,"1525":75,"1529":1,"1530":14,"1531":1,"1532":3,"1533":12,"1534":14,"1543":3,"1544":1,"1546":1,"1553":3,"1569":2,"1574":3,"1576":6,"1580":1,"1581":3,"1582":1,"1583":1,"1587":2,"1588":10,"1589":4,"1590":80,"1594":2,"1595":24,"1596":3,"1597":2,"1598":10,"1599":16,"1610":6,"1630":1,"1669":2,"1723":1,"1738":2,"1774":1,"1883":1,"1886":1,"1887":1,"1888":2,"1915":1,"1946":1,"1947":1,"1963":1,"2002":1,"2033":3,"2035":3,"2036":2,"2042":1,"2056":2,"2104":1,"2109":1,"2111":1,"2133":1,"2141":1,"2145":1,"2146":1,"2153":1,"2180":1,"2189":1,"2193":1,"2198":1,"2231":11}}],["devops",{"0":{"1553":1},"2":{"1334":1},"3":{"1192":6,"1194":2,"1212":25,"1216":2,"1259":1,"1285":3,"1299":3,"1308":1,"1310":7,"1322":7,"1323":3,"1330":1,"1331":1,"1333":1,"1334":1,"1338":13,"1356":1,"1357":1,"1368":1,"1379":1,"1395":4,"1414":1,"1415":1,"1416":1,"1426":1,"1435":6,"1440":1,"1444":1,"1445":1,"1449":1,"1452":1,"1453":1,"1454":4,"1460":1,"1461":1,"1462":1,"1466":1,"1468":1,"1471":1,"1476":1,"1480":1,"1482":1,"1485":1,"1488":1,"1491":1,"1492":1,"1493":2,"1495":55,"1496":1,"1498":11,"1525":1,"1534":1,"1576":1,"1580":1,"1581":1,"1582":1,"1583":1,"1589":1,"1590":1,"1599":1,"1673":1,"1795":1,"1799":1,"2013":1,"2037":1,"2212":2,"2216":1,"2217":1,"2218":2,"2219":2}}],["develop",{"3":{"1569":1,"1647":1,"1660":1}}],["developing",{"3":{"1536":1,"1796":1,"2219":1}}],["developed",{"3":{"1498":22,"1571":1,"2219":2}}],["developercertificate",{"2":{"1327":1,"1448":1},"3":{"1327":1,"1338":1,"1429":1,"1448":1}}],["developercertificateavailabilitytests",{"3":{"1199":1,"1207":2,"1486":1}}],["developercertificateavailability",{"2":{"1067":1,"1427":1,"1429":1,"1487":1},"3":{"1067":3,"1199":3,"1207":2,"1427":1,"1429":2,"1487":2}}],["developers",{"1":{"1627":1,"1628":1,"1629":1,"1630":1,"1631":1,"1632":1,"1633":1,"1634":1,"1635":1,"1636":1,"1637":1,"1638":1,"1639":1,"1640":1},"3":{"86":1,"527":1,"566":1,"1211":1,"1310":1,"1339":1,"1363":3,"1368":4,"1394":2,"1415":2,"1454":1,"1522":1,"1569":1,"1627":1,"1628":1,"1636":1,"1637":1,"1784":1,"2003":1,"2049":1,"2094":1,"2109":1,"2213":1,"2215":1,"2220":1,"2238":2,"2239":3,"2241":1}}],["developer",{"0":{"1569":1},"2":{"1326":1},"3":{"0":1,"265":1,"350":1,"486":1,"576":1,"640":2,"641":2,"642":1,"650":1,"665":2,"819":1,"820":1,"914":2,"916":1,"930":1,"932":1,"982":1,"1012":1,"1067":1,"1069":1,"1091":1,"1109":1,"1161":1,"1216":1,"1237":1,"1259":1,"1270":1,"1271":1,"1285":3,"1310":4,"1322":8,"1323":2,"1325":1,"1326":1,"1327":1,"1334":2,"1335":1,"1338":12,"1353":1,"1358":1,"1395":5,"1414":1,"1415":2,"1416":4,"1426":1,"1435":21,"1439":2,"1440":2,"1441":1,"1445":1,"1448":1,"1449":1,"1452":1,"1454":1,"1455":1,"1460":1,"1463":5,"1480":1,"1481":2,"1485":1,"1487":1,"1488":1,"1489":1,"1491":1,"1492":1,"1498":1,"1525":1,"1534":1,"1552":1,"1553":1,"1576":1,"1580":1,"1581":1,"1582":1,"1590":1,"1596":1,"1795":1,"1842":1,"1884":1,"1893":1,"1955":1,"1969":1,"2029":1,"2105":1,"2219":2,"2224":1,"2237":1}}],["development",{"0":{"2224":1},"2":{"92":1,"95":1,"96":1,"102":1,"573":1,"1428":1,"1489":1},"3":{"0":4,"31":2,"92":1,"93":1,"95":3,"96":1,"102":1,"104":1,"212":1,"214":2,"218":1,"219":2,"265":4,"290":1,"296":1,"397":1,"401":1,"451":1,"452":1,"573":1,"638":1,"639":1,"640":1,"666":1,"667":1,"774":3,"775":1,"790":1,"914":1,"915":1,"1066":1,"1067":1,"1068":1,"1091":1,"1108":7,"1110":1,"1112":1,"1183":1,"1212":1,"1259":1,"1273":2,"1283":1,"1285":13,"1297":1,"1299":1,"1308":2,"1310":19,"1319":4,"1322":13,"1323":8,"1332":1,"1335":2,"1338":10,"1379":1,"1394":1,"1395":1,"1411":1,"1414":4,"1415":1,"1416":1,"1420":2,"1426":3,"1428":2,"1429":1,"1433":1,"1435":6,"1437":2,"1440":3,"1442":3,"1452":1,"1459":1,"1466":1,"1485":1,"1486":1,"1487":2,"1488":1,"1489":3,"1590":1,"1630":1,"1639":1,"1652":1,"1654":1,"1660":1,"1662":1,"1663":1,"1665":1,"1666":2,"1718":1,"1722":1,"1841":1,"1842":1,"1897":3,"1903":2,"1924":1,"2015":1,"2027":1,"2082":1,"2125":1,"2131":1,"2148":1,"2149":2,"2155":1,"2171":2,"2212":2,"2217":1,"2218":1,"2220":1,"2224":1,"2239":1,"2242":1}}],["deviates",{"3":{"1369":1}}],["deviate",{"3":{"1358":1}}],["deviations",{"0":{"1375":1},"3":{"470":1,"1169":1,"1310":1,"1395":1}}],["deviation",{"3":{"0":1,"107":1,"109":1,"111":2,"1237":1,"1299":1,"1308":2,"1310":3,"1323":3,"1349":1,"1416":1,"1435":1,"1637":1}}],["devicetestchecklist",{"3":{"1480":1}}],["devicetype",{"3":{"1299":1,"1414":1,"1629":1,"1640":1}}],["deviceownerauthentication",{"3":{"1416":1}}],["devicecredential",{"3":{"1416":1}}],["devicecapabilitiesinitializer",{"2":{"413":1},"3":{"413":1,"1199":2,"1203":1,"1207":2,"1416":22}}],["devicefieldmaxlength",{"2":{"1404":1},"3":{"1404":1,"1414":2}}],["deviceformfactor",{"3":{"1299":1,"1414":1,"1629":1,"1640":1}}],["devicename",{"3":{"1299":1,"1414":1,"1629":1,"1640":1}}],["devicemanufacturer",{"3":{"1299":1,"1414":2,"1629":1,"1640":1}}],["devicemodel",{"3":{"1299":1,"1414":2,"1629":1,"1640":1}}],["deviceprefs",{"3":{"1416":6}}],["devicepreferencekeys",{"3":{"1199":3,"1203":1,"1207":2,"1416":13}}],["deviceplatform",{"3":{"1299":1,"1414":1,"1416":4,"1629":1,"1640":1}}],["deviceid",{"3":{"1299":1,"1414":2,"1629":1,"1640":6}}],["deviceinstallationrequest",{"2":{"1302":1,"1307":1},"3":{"1199":6,"1203":1,"1207":2,"1285":2,"1302":1,"1307":2,"1308":9,"1322":3,"1495":2}}],["deviceinfo",{"2":{"682":1},"3":{"682":1,"1416":11,"1640":1}}],["deviceuimodule",{"2":{"1625":1},"3":{"649":2,"652":1,"1199":2,"1203":1,"1207":2,"1323":6,"1415":18,"1495":1,"1598":2,"1625":1,"1626":1}}],["devicesettings",{"3":{"1395":1,"1415":1}}],["devicestorage",{"2":{"1416":1},"3":{"416":1,"1203":9,"1207":18,"1416":34,"2117":1}}],["devicestatus",{"2":{"1416":2},"3":{"416":1,"1203":13,"1207":26,"1416":39,"2117":2}}],["devices",{"0":{"1712":1,"1935":1},"3":{"0":2,"186":1,"225":1,"274":1,"434":1,"616":1,"618":1,"903":1,"905":2,"906":2,"907":1,"910":1,"1212":1,"1285":1,"1299":4,"1301":1,"1308":4,"1310":2,"1322":3,"1323":15,"1394":1,"1414":2,"1415":1,"1416":10,"1435":3,"1437":1,"1466":1,"1558":1,"1640":1,"1665":1,"1746":1,"1931":1,"1932":1,"1935":2,"1937":1,"1981":2,"1983":1,"2080":1,"2219":1}}],["devicescontrollertests",{"3":{"1199":1,"1207":2,"1308":2}}],["devicescontroller",{"2":{"1301":1,"1935":1,"1937":1,"1940":1},"3":{"0":1,"188":1,"434":1,"1199":2,"1203":1,"1207":2,"1285":4,"1301":1,"1302":1,"1307":3,"1308":10,"1310":1,"1322":4,"1495":3,"1935":1,"1937":1,"1940":1,"2136":1}}],["device",{"0":{"1289":1,"1307":1,"1558":1,"1981":1},"1":{"410":1,"411":1,"412":1,"413":1,"414":1,"415":1,"416":1,"679":1,"680":1,"681":1,"682":1,"683":1,"684":1,"685":1,"686":1,"902":2,"903":2,"904":2,"905":2,"906":2,"907":2,"908":2,"909":2,"910":2,"1416":1,"1979":1,"1980":1,"1981":1,"1982":1,"1983":1,"1984":1,"2115":1,"2116":1,"2117":1,"2118":1,"2119":1,"2120":1,"2121":1,"2122":1},"3":{"0":12,"188":1,"265":4,"266":1,"267":1,"273":1,"350":1,"353":1,"410":1,"411":1,"412":1,"413":1,"422":1,"426":1,"427":1,"433":2,"434":1,"435":3,"497":2,"499":1,"500":1,"501":5,"510":1,"556":1,"618":2,"649":1,"650":1,"653":1,"679":1,"681":2,"683":1,"686":1,"689":1,"691":2,"736":1,"902":2,"903":3,"904":3,"905":12,"906":7,"907":3,"910":1,"954":1,"1192":3,"1195":1,"1202":2,"1203":2,"1212":5,"1216":1,"1282":2,"1284":1,"1285":9,"1286":2,"1288":2,"1289":5,"1290":1,"1295":1,"1299":41,"1301":1,"1307":2,"1308":8,"1310":15,"1321":1,"1322":12,"1323":52,"1349":2,"1358":2,"1380":2,"1390":3,"1394":16,"1395":18,"1401":1,"1404":2,"1408":1,"1414":18,"1415":25,"1416":130,"1426":1,"1435":10,"1437":8,"1478":3,"1480":2,"1481":1,"1488":1,"1489":1,"1490":1,"1495":9,"1498":1,"1504":1,"1510":1,"1516":1,"1576":3,"1584":2,"1598":1,"1625":2,"1626":1,"1628":1,"1629":5,"1630":2,"1631":1,"1640":29,"1658":1,"1659":1,"1666":3,"1667":1,"1668":6,"1671":1,"1710":1,"1778":3,"1795":1,"1935":3,"1937":1,"1939":1,"1975":1,"1976":1,"1978":1,"1979":2,"1981":6,"1983":2,"1984":6,"2072":1,"2081":1,"2082":1,"2085":1,"2114":1,"2115":2,"2116":2,"2117":1,"2119":1,"2122":3,"2136":1,"2207":1,"2209":2,"2231":4}}],["dev",{"0":{"1363":1},"1":{"637":1,"638":1,"639":1,"640":1,"641":1,"642":1,"643":1,"644":1,"645":1,"1105":1,"1106":1,"1107":1,"1108":1,"1109":1,"1110":1,"1111":1,"1112":1},"3":{"0":6,"92":1,"95":2,"96":1,"102":1,"104":1,"290":1,"349":1,"351":1,"450":2,"451":1,"556":1,"557":1,"637":1,"643":1,"818":1,"820":1,"838":1,"1067":1,"1069":1,"1105":1,"1116":1,"1212":2,"1257":1,"1285":2,"1299":1,"1310":2,"1322":2,"1338":2,"1363":2,"1368":3,"1415":3,"1420":1,"1429":1,"1435":2,"1440":3,"1441":1,"1448":1,"1454":1,"1455":2,"1487":1,"1489":1,"1491":1,"1553":1,"1567":1,"1569":3,"1576":1,"1610":1,"1717":1,"1726":1,"1897":1,"1903":1,"1973":1,"2054":1,"2131":1,"2132":1,"2231":2}}],["december",{"3":{"2219":2}}],["decade",{"3":{"1435":1}}],["decays",{"3":{"1005":1,"1019":1,"1435":1}}],["deck",{"3":{"1115":2,"1117":1,"1118":1,"1339":1,"1341":1,"1349":2,"1356":1,"1388":2,"1394":2,"2238":1}}],["decks",{"3":{"0":1,"443":1,"1372":1,"1379":1,"1388":1,"1466":2,"1639":1}}],["decreasing",{"3":{"1766":1}}],["decreaseinventory",{"3":{"1745":1,"1748":2,"1768":1}}],["decreaseinventoryhandler",{"3":{"1270":1}}],["decreaseinventoryapplier",{"3":{"1270":1}}],["decrease",{"3":{"1270":1,"1495":1,"1741":2,"1745":1,"1748":1,"1766":1,"1767":1}}],["decreaseorderapplier",{"3":{"1199":3,"1207":1}}],["decreased",{"3":{"926":1,"1270":1}}],["decrementasync",{"2":{"1764":1},"3":{"1764":1}}],["decrementing",{"3":{"1322":1}}],["decremented",{"3":{"1125":1,"1323":1,"1834":1,"2160":1}}],["decrements",{"3":{"535":1,"1308":1,"1323":2,"1590":2,"1748":2,"1764":1,"2160":1}}],["decrement",{"3":{"201":1,"535":1,"1285":2,"1308":2,"1314":1,"1323":4,"1599":2,"1748":1,"1880":1,"2160":2}}],["decryptable",{"3":{"667":1}}],["decrypted",{"3":{"664":2,"1335":1,"1338":2,"1441":1,"1969":2}}],["decryption",{"3":{"359":1,"365":1,"665":1,"667":1,"670":1,"1283":1,"1285":1,"1338":1,"1441":1,"2141":1}}],["decrypting",{"3":{"359":2,"2141":2,"2145":1}}],["decrypts",{"3":{"358":2,"359":1,"361":1,"1285":4,"1902":1,"2139":2,"2141":1}}],["decrypt",{"2":{"359":1,"360":1,"2141":1},"3":{"0":1,"359":4,"360":2,"361":1,"1212":1,"1285":4,"1969":1,"2141":4,"2142":1,"2231":1}}],["declining",{"3":{"649":1,"827":1,"828":1,"1323":1,"1338":1,"1349":1,"1395":1,"1414":2,"1416":1}}],["declinequeue",{"3":{"1349":1,"1358":4,"1394":1}}],["decline",{"2":{"1632":1},"3":{"0":1,"742":1,"827":3,"840":1,"915":1,"979":1,"1310":1,"1323":1,"1343":1,"1349":1,"1416":1,"1454":1,"1491":1,"1629":2,"1630":3,"1631":1,"1632":1,"1634":2,"1639":1,"2134":1,"2138":1}}],["declinedsessions",{"3":{"1349":1}}],["declined",{"2":{"1632":1},"3":{"0":2,"151":1,"245":1,"286":1,"405":1,"539":1,"543":1,"659":1,"785":1,"800":1,"827":1,"832":1,"947":1,"1035":1,"1051":3,"1141":1,"1212":1,"1310":1,"1338":1,"1343":1,"1346":1,"1349":3,"1358":25,"1374":1,"1379":2,"1394":1,"1395":3,"1629":1,"1630":2,"1632":1,"1634":2,"1638":2,"2031":1}}],["declines",{"1":{"824":1,"825":1,"826":1,"827":1,"828":1,"829":1,"830":1,"831":1,"832":1,"833":1,"834":1},"3":{"0":2,"692":1,"706":1,"742":1,"745":1,"824":1,"825":1,"827":1,"834":1,"840":1,"948":1,"964":1,"968":1,"1017":1,"1212":1,"1310":1,"1323":1,"1336":1,"1338":3,"1394":1,"1421":1,"1947":1,"2027":1,"2231":1}}],["declarable",{"3":{"1285":1}}],["declaratively",{"3":{"1237":1,"1320":1,"1349":1,"1358":3,"1395":1,"1410":1,"1464":1,"1661":1}}],["declarative",{"3":{"12":1,"838":1,"1237":1,"1247":1,"1264":1,"1270":1,"1285":4,"1296":1,"1308":1,"1309":2,"1322":5,"1323":3,"1358":9,"1368":1,"1379":5,"1395":3,"1401":3,"1414":2,"1415":1,"1435":2,"1661":1,"1667":1,"1864":1,"1866":1,"1867":1,"1951":1,"1954":1,"2065":1,"2228":1}}],["declarativenavigationpopulatortests",{"3":{"1199":1,"1207":3}}],["declarativenavigationpopulator",{"2":{"11":1,"12":1,"1864":1},"3":{"11":1,"12":1,"1199":20,"1203":1,"1207":2,"1309":16,"1358":30,"1401":3,"1864":1}}],["declarationfailure",{"3":{"1435":2}}],["declarationrule",{"2":{"1435":2},"3":{"1435":6}}],["declaration",{"0":{"1383":1},"3":{"0":3,"102":1,"126":1,"130":4,"135":1,"138":1,"139":1,"141":1,"142":2,"160":2,"166":1,"184":1,"251":2,"256":2,"257":1,"336":1,"364":1,"388":1,"413":1,"428":1,"454":1,"463":1,"464":1,"471":1,"492":1,"543":1,"550":1,"585":4,"609":1,"618":1,"633":1,"682":1,"683":1,"726":1,"741":1,"819":1,"821":1,"827":1,"832":1,"834":1,"838":1,"848":1,"889":1,"906":1,"923":1,"954":1,"966":1,"967":1,"980":1,"981":3,"982":1,"1050":1,"1051":1,"1052":1,"1124":1,"1146":1,"1156":1,"1184":1,"1204":1,"1212":1,"1229":3,"1237":5,"1247":4,"1270":15,"1285":19,"1299":7,"1300":1,"1308":1,"1309":3,"1310":8,"1311":1,"1322":6,"1323":14,"1325":1,"1330":1,"1338":1,"1349":7,"1352":1,"1356":1,"1358":38,"1365":1,"1368":8,"1374":1,"1375":1,"1379":2,"1394":11,"1395":15,"1401":1,"1414":17,"1415":1,"1416":6,"1430":1,"1435":44,"1440":3,"1442":1,"1450":1,"1454":1,"1456":1,"1472":1,"1487":1,"1495":9,"1499":3,"1599":1,"1961":1,"2007":1,"2041":1,"2056":1,"2088":1,"2231":1}}],["declarations",{"2":{"1214":1},"3":{"0":4,"11":1,"130":2,"135":2,"136":1,"138":2,"139":1,"141":4,"142":1,"395":1,"531":1,"543":2,"550":2,"551":1,"572":1,"583":1,"607":1,"610":1,"621":1,"681":1,"966":1,"967":1,"980":1,"999":1,"1052":1,"1055":1,"1205":1,"1214":1,"1233":1,"1237":1,"1270":2,"1285":2,"1310":1,"1322":2,"1323":1,"1349":1,"1358":2,"1368":2,"1395":1,"1414":3,"1416":1,"1435":8,"1440":1,"1482":1,"1487":1,"1488":1,"1576":2,"1581":1,"1880":1,"2043":1,"2054":1,"2099":1}}],["declaringentitytype",{"3":{"1247":1,"1309":2}}],["declaringtype",{"2":{"10":1},"3":{"10":1,"1309":1,"1435":1,"1864":1}}],["declaring",{"0":{"1266":1,"1325":1},"3":{"0":2,"24":1,"39":2,"109":1,"131":1,"132":1,"330":1,"471":1,"584":1,"611":1,"642":1,"650":1,"676":1,"782":1,"829":1,"889":1,"1019":1,"1051":1,"1055":1,"1116":1,"1161":3,"1196":1,"1236":1,"1237":1,"1241":1,"1247":3,"1249":1,"1256":1,"1259":4,"1264":1,"1270":4,"1271":1,"1276":1,"1281":1,"1284":1,"1285":12,"1296":1,"1299":7,"1308":1,"1309":5,"1310":3,"1322":6,"1323":4,"1338":1,"1349":3,"1358":26,"1368":2,"1372":1,"1379":3,"1388":1,"1394":2,"1395":4,"1401":2,"1411":1,"1414":5,"1416":1,"1430":1,"1435":29,"1440":1,"1446":1,"1450":1,"1466":3,"1477":1,"1488":1,"1697":1,"1809":1,"1820":1,"1833":1,"1883":1,"1993":1,"2032":1,"2062":1,"2125":1}}],["declareatleastonehandler",{"3":{"1435":1}}],["declareanauthorizationdecision",{"2":{"189":1},"3":{"189":1,"1435":3}}],["declare",{"3":{"0":5,"5":1,"24":1,"59":1,"68":1,"71":1,"91":1,"95":1,"117":2,"136":1,"145":2,"147":2,"149":1,"155":1,"160":2,"162":1,"166":2,"184":2,"186":2,"189":1,"243":2,"265":2,"373":1,"448":1,"530":1,"602":1,"607":1,"609":2,"651":1,"674":2,"782":1,"784":1,"825":1,"827":2,"838":2,"846":1,"881":1,"889":1,"922":1,"923":1,"946":1,"963":1,"964":1,"976":1,"1054":1,"1059":1,"1074":1,"1085":1,"1116":1,"1117":1,"1237":4,"1242":1,"1247":3,"1255":1,"1259":4,"1266":1,"1270":8,"1279":2,"1285":14,"1299":9,"1300":1,"1308":2,"1309":1,"1319":1,"1322":2,"1323":10,"1337":1,"1338":2,"1344":1,"1348":1,"1349":1,"1358":14,"1361":2,"1362":1,"1368":3,"1373":3,"1379":3,"1383":1,"1394":2,"1395":4,"1401":2,"1405":1,"1408":1,"1414":7,"1415":1,"1416":3,"1430":2,"1435":27,"1454":1,"1473":1,"1488":3,"1490":1,"1599":1,"1626":1,"1652":1,"1653":1,"1666":2,"1696":1,"1718":1,"1720":1,"1752":1,"1826":2,"1878":1,"1885":1,"1952":1,"1954":1,"1960":1,"1961":1,"1964":1,"1993":1,"1996":1,"2013":1,"2021":1,"2032":1,"2034":1,"2036":1,"2037":2,"2045":1,"2079":1,"2084":1,"2097":2,"2102":1,"2113":1,"2125":1,"2131":1,"2135":1,"2157":1}}],["declaredpublicproperties",{"3":{"1435":1}}],["declaredengines",{"3":{"1285":1}}],["declaredresulttype",{"3":{"1270":3}}],["declarednamecache",{"3":{"1259":1,"1285":1}}],["declaredonly",{"2":{"966":1},"3":{"0":1,"135":1,"964":1,"966":1,"1212":1,"1237":2,"1323":1,"1435":6}}],["declared",{"0":{"2032":1},"3":{"0":12,"24":1,"31":1,"57":1,"76":1,"78":1,"81":3,"92":1,"93":1,"95":1,"96":1,"102":1,"135":6,"136":1,"138":1,"139":1,"155":1,"160":1,"166":1,"184":1,"185":1,"188":1,"225":1,"258":1,"260":1,"284":1,"290":1,"326":1,"330":1,"341":1,"346":1,"386":1,"388":1,"397":2,"409":1,"418":1,"423":2,"428":1,"430":1,"440":1,"443":1,"448":1,"470":1,"472":1,"473":1,"545":2,"549":1,"583":4,"607":2,"609":2,"612":2,"635":1,"649":1,"651":1,"657":2,"749":1,"764":1,"765":2,"766":2,"782":1,"790":1,"801":1,"827":2,"831":1,"836":1,"838":1,"846":1,"847":2,"848":2,"849":1,"875":1,"889":1,"891":1,"964":1,"966":3,"971":1,"979":1,"980":1,"990":1,"995":2,"1018":2,"1019":1,"1034":2,"1035":1,"1052":1,"1067":3,"1075":1,"1083":1,"1116":3,"1119":1,"1161":1,"1170":1,"1191":1,"1212":2,"1214":1,"1229":2,"1233":1,"1235":2,"1237":16,"1239":1,"1240":1,"1241":1,"1247":14,"1250":2,"1259":5,"1260":1,"1263":2,"1264":1,"1266":1,"1269":2,"1270":18,"1271":4,"1276":1,"1277":1,"1280":4,"1284":2,"1285":73,"1296":1,"1299":19,"1302":1,"1306":1,"1308":12,"1309":4,"1310":21,"1311":2,"1314":2,"1322":20,"1323":24,"1338":9,"1343":3,"1344":1,"1346":3,"1349":33,"1352":1,"1357":1,"1358":113,"1361":1,"1368":5,"1372":3,"1379":14,"1394":23,"1395":31,"1397":1,"1401":10,"1404":1,"1405":1,"1410":1,"1414":19,"1415":7,"1416":4,"1426":2,"1427":1,"1429":1,"1430":3,"1434":1,"1435":83,"1437":3,"1440":5,"1441":1,"1442":1,"1443":1,"1445":1,"1449":1,"1457":1,"1458":1,"1462":2,"1463":1,"1466":15,"1473":1,"1475":3,"1480":1,"1487":1,"1488":5,"1499":1,"1512":1,"1525":1,"1533":1,"1546":1,"1576":1,"1590":3,"1596":1,"1598":1,"1630":1,"1661":1,"1669":1,"1670":1,"1672":1,"1685":1,"1730":1,"1814":1,"1818":1,"1848":1,"1855":1,"1879":1,"1880":1,"1885":2,"1888":1,"1890":2,"1921":1,"1938":1,"1952":1,"1963":1,"1993":1,"1995":1,"1999":2,"2002":1,"2008":1,"2028":2,"2032":2,"2033":6,"2036":3,"2037":2,"2043":1,"2045":1,"2074":2,"2079":1,"2093":1,"2124":1,"2125":3,"2127":2,"2130":1,"2137":1,"2155":1,"2159":1,"2173":1,"2231":3}}],["declaresauthorizationanywhere",{"3":{"1435":1}}],["declareseveryexpectedlayer",{"3":{"1435":2,"1488":1}}],["declarespii",{"2":{"1435":1},"3":{"1435":1}}],["declares",{"0":{"1879":1},"3":{"0":12,"5":1,"7":1,"37":1,"42":1,"47":1,"59":1,"78":1,"80":1,"81":2,"94":1,"95":2,"98":1,"115":1,"121":2,"122":1,"128":1,"146":1,"147":1,"155":1,"157":1,"160":1,"180":2,"185":1,"186":4,"187":1,"189":2,"190":1,"298":1,"305":1,"310":2,"325":1,"341":1,"370":1,"408":1,"426":1,"442":1,"448":2,"468":4,"470":2,"475":2,"476":1,"477":1,"478":1,"486":1,"497":1,"523":3,"545":2,"549":2,"551":2,"552":1,"556":3,"576":1,"582":1,"583":7,"585":1,"599":2,"609":3,"611":1,"626":1,"631":2,"633":2,"640":2,"643":2,"650":1,"676":1,"698":2,"707":1,"725":2,"733":1,"764":1,"766":1,"767":1,"768":1,"783":1,"798":5,"804":2,"825":1,"827":6,"838":4,"842":2,"881":2,"889":1,"891":1,"914":1,"922":1,"954":1,"956":1,"959":1,"963":2,"964":1,"966":1,"972":2,"973":1,"980":3,"983":1,"1018":2,"1033":2,"1050":7,"1052":1,"1054":1,"1067":2,"1100":1,"1116":1,"1133":5,"1155":1,"1156":1,"1161":1,"1168":1,"1227":1,"1229":3,"1230":1,"1231":2,"1232":1,"1237":5,"1241":6,"1244":2,"1249":1,"1250":1,"1259":6,"1269":1,"1270":51,"1271":7,"1274":1,"1275":1,"1276":2,"1279":2,"1280":1,"1285":28,"1299":15,"1300":3,"1303":1,"1307":1,"1308":3,"1309":5,"1310":13,"1311":1,"1317":1,"1319":1,"1320":1,"1322":11,"1323":13,"1324":1,"1325":2,"1326":2,"1327":1,"1330":1,"1331":1,"1337":2,"1338":15,"1349":3,"1352":1,"1358":84,"1359":2,"1361":3,"1362":1,"1365":1,"1368":9,"1370":1,"1371":1,"1372":1,"1373":2,"1375":1,"1379":4,"1383":2,"1388":1,"1390":1,"1392":1,"1394":23,"1395":23,"1400":1,"1401":7,"1405":1,"1406":1,"1408":2,"1414":21,"1415":6,"1416":7,"1426":2,"1429":1,"1430":1,"1435":89,"1437":4,"1440":3,"1444":1,"1446":3,"1448":1,"1450":1,"1453":3,"1454":2,"1459":1,"1463":1,"1466":9,"1469":1,"1471":1,"1472":1,"1474":1,"1475":1,"1483":1,"1488":6,"1489":1,"1491":1,"1495":1,"1525":1,"1576":2,"1590":1,"1595":1,"1626":2,"1648":2,"1650":1,"1652":2,"1661":1,"1666":1,"1672":1,"1684":1,"1702":2,"1717":1,"1726":1,"1750":1,"1764":1,"1771":1,"1809":5,"1814":2,"1822":1,"1825":1,"1827":1,"1828":1,"1864":1,"1877":1,"1879":2,"1888":1,"1890":1,"1897":1,"1915":1,"1921":1,"1932":1,"1943":1,"1952":1,"1959":1,"1961":1,"1962":2,"1987":1,"1993":1,"1999":2,"2007":1,"2023":1,"2041":2,"2045":1,"2069":1,"2071":1,"2082":1,"2086":1,"2103":3,"2130":2,"2138":1,"2149":1,"2156":1,"2157":1,"2189":1,"2202":1,"2231":1}}],["decommission",{"3":{"1462":1,"1466":1}}],["decomposition",{"3":{"1394":4,"1401":3,"1599":1}}],["decomposed",{"3":{"1310":1,"1394":1,"1637":1}}],["decompression",{"3":{"1322":2,"2125":1}}],["decoupling",{"3":{"1270":1,"1299":1,"1358":1,"1401":1,"1415":1,"1525":1,"1538":1,"1576":1,"1590":1,"1830":1,"2133":1}}],["decouples",{"3":{"332":1,"1310":1,"1322":2,"1349":1,"1395":1,"1437":1,"1664":1,"1836":1,"1963":1,"1964":1,"1989":1}}],["decouple",{"1":{"1862":1,"1863":1,"1864":1,"1865":1,"1866":1,"1867":1},"3":{"187":1,"299":1,"1346":1,"1862":1,"2206":1}}],["decoupled",{"1":{"1309":1},"3":{"186":1,"301":1,"399":1,"404":1,"1192":1,"1202":1,"1203":1,"1259":1,"1308":1,"1309":2,"1310":2,"1322":2,"1323":3,"1338":1,"1349":3,"1358":1,"1373":1,"1379":1,"1394":1,"1395":1,"1401":1,"1414":1,"1416":1,"1440":1,"1537":1,"1545":1,"1553":1,"1669":1,"1991":1,"2156":1,"2158":1}}],["deconstructs",{"3":{"1435":1}}],["deconstruction",{"3":{"1299":2,"1323":1}}],["deconstruct",{"3":{"1050":1,"1234":1,"1237":1,"1285":1,"1299":1,"1435":1}}],["decodable",{"3":{"1873":1}}],["decoding",{"3":{"442":1,"1186":1,"1228":1,"1299":3,"1322":1,"1358":1,"1379":2,"1416":1}}],["decoder",{"3":{"683":1,"1229":1,"1310":1,"1311":5,"1377":1,"1379":3,"1395":2}}],["decoded",{"3":{"341":1,"346":1,"438":1,"442":1,"682":1,"684":1,"922":1,"1229":2,"1299":1,"1310":2,"1322":2,"1323":1,"1358":6,"1379":1,"1395":1,"1416":6,"1650":1,"1876":1,"2125":4,"2127":1}}],["decode",{"3":{"0":1,"359":1,"360":2,"365":1,"440":1,"442":1,"681":1,"682":1,"1115":1,"1116":1,"1229":1,"1247":2,"1285":6,"1299":2,"1310":1,"1311":2,"1322":4,"1379":2,"1416":4,"1437":1,"1495":1,"2125":3,"2127":3,"2142":1}}],["decodes",{"3":{"0":1,"341":1,"682":1,"683":1,"684":1,"922":1,"1241":1,"1247":2,"1271":1,"1299":3,"1310":4,"1311":1,"1322":2,"1379":1,"1395":2,"1416":1,"1873":1,"2125":1,"2141":1}}],["decoratable",{"3":{"1923":1}}],["decorate",{"3":{"1310":1,"1322":1,"1323":5,"1379":1,"1395":2,"1435":1}}],["decoratedentity",{"3":{"1199":2,"1207":1}}],["decorated",{"3":{"330":1,"1285":3,"1310":1,"1322":1,"1338":1,"1341":1,"1342":1,"1349":2,"1358":5,"1378":1,"1379":1,"1394":1,"1395":2,"1435":3,"1487":1,"1488":1,"1539":1,"1965":1,"1987":1,"2054":1,"2094":1,"2097":1}}],["decorates",{"3":{"0":1,"122":1,"175":2,"1042":1,"1059":1,"1091":1,"1285":1,"1296":1,"1299":1,"1317":1,"1322":2,"1323":1,"1379":1,"1395":1,"1420":1,"1487":1,"2185":1,"2198":1}}],["decorating",{"3":{"1285":2,"1323":2}}],["decoration",{"3":{"117":1,"122":2,"175":1,"415":1,"592":1,"681":1,"1045":1,"1270":1,"1285":3,"1289":1,"1299":1,"1315":1,"1322":3,"1338":1,"1358":4,"1379":1,"1394":5,"1395":4,"1414":1,"1416":3,"1435":6,"1488":1,"1685":1,"1697":1,"1700":1,"1826":2,"1910":1,"2116":1,"2121":1,"2171":1}}],["decoratively",{"3":{"1270":1,"1285":1}}],["decorative",{"3":{"0":1,"195":1,"305":1,"342":1,"684":1,"774":1,"1229":1,"1259":1,"1323":3,"1358":1,"1416":1,"2029":1}}],["decoratorpipelineseal",{"3":{"1199":2,"1203":1,"1207":1,"1312":1,"1315":1,"1322":7,"1495":1}}],["decoratorpipelineordertests",{"2":{"127":1,"1599":1},"3":{"121":4,"127":3,"572":4,"1199":3,"1207":8,"1262":3,"1315":1,"1322":4,"1414":2,"1435":11,"1437":2,"1486":1,"1487":3,"1488":3,"1599":1}}],["decoratorpipelineordertestsbase",{"2":{"121":1,"122":1,"126":1,"572":1,"579":1,"749":1,"1262":1,"1427":1,"1506":1,"2054":1},"3":{"0":1,"122":1,"126":1,"572":6,"574":1,"579":1,"749":1,"1199":3,"1207":2,"1262":5,"1427":1,"1435":17,"1437":1,"1487":2,"1488":1,"1495":1,"1506":1,"2054":2}}],["decorators",{"2":{"1270":1},"3":{"0":8,"115":4,"117":4,"119":1,"121":5,"122":5,"123":3,"125":3,"126":7,"127":1,"186":1,"242":1,"243":1,"246":3,"247":1,"300":2,"306":1,"395":2,"397":4,"406":1,"407":3,"522":1,"523":1,"571":1,"572":1,"674":1,"698":2,"701":1,"702":1,"732":1,"733":2,"735":1,"737":1,"827":1,"923":1,"987":1,"1004":2,"1058":1,"1059":3,"1100":1,"1192":1,"1203":21,"1207":208,"1212":2,"1223":1,"1229":1,"1246":2,"1260":2,"1261":1,"1262":5,"1263":16,"1264":3,"1265":1,"1266":1,"1267":4,"1268":3,"1270":97,"1271":11,"1275":1,"1283":1,"1285":5,"1296":1,"1299":12,"1300":12,"1308":2,"1310":6,"1312":1,"1315":2,"1317":1,"1322":17,"1323":1,"1338":1,"1358":16,"1395":1,"1401":1,"1414":3,"1426":1,"1435":7,"1436":1,"1437":10,"1495":1,"1525":1,"1537":1,"1541":1,"1565":1,"1576":3,"1650":1,"1652":1,"1699":1,"1716":1,"1773":1,"1817":1,"1820":3,"1822":1,"1823":2,"1824":1,"1825":2,"1826":1,"1850":1,"1878":2,"1882":3,"1885":1,"1913":1,"1914":2,"1918":1,"1919":3,"1921":1,"1922":1,"1923":2,"2009":1,"2135":2,"2155":2,"2198":1}}],["decorator",{"0":{"1506":1,"2095":1,"2135":1},"1":{"114":1,"115":1,"116":1,"117":1,"118":1,"119":1,"120":1,"121":1,"122":1,"123":1,"124":1,"125":1,"126":1,"127":1,"128":1,"1260":1,"1261":1,"1262":1,"1263":1,"1264":1,"1265":1,"1266":1,"1267":1,"1268":1,"1269":1,"1270":1,"1817":1,"1818":1,"1819":1,"1820":1,"1821":1,"1822":1,"1823":1,"1824":1,"1825":1,"1826":1},"3":{"0":15,"113":1,"114":1,"115":6,"117":3,"119":1,"120":1,"122":4,"123":2,"124":4,"125":3,"126":2,"128":3,"135":2,"143":1,"184":1,"242":1,"265":2,"299":2,"300":1,"303":1,"304":1,"383":1,"395":3,"397":1,"398":2,"406":1,"407":2,"409":1,"518":1,"521":1,"550":1,"571":1,"572":6,"573":2,"574":5,"578":1,"579":1,"692":1,"699":1,"700":1,"714":1,"716":1,"733":3,"735":2,"748":1,"749":1,"750":1,"753":1,"790":1,"905":1,"907":1,"909":1,"922":1,"923":1,"924":1,"925":1,"987":1,"992":1,"1042":1,"1043":2,"1046":1,"1104":1,"1192":4,"1202":3,"1203":3,"1212":7,"1213":3,"1223":1,"1229":2,"1237":1,"1246":2,"1247":1,"1259":1,"1260":5,"1261":4,"1262":1,"1263":2,"1264":3,"1266":1,"1267":4,"1268":2,"1269":1,"1270":98,"1271":9,"1278":1,"1285":22,"1299":7,"1300":4,"1308":11,"1310":7,"1312":1,"1314":1,"1315":2,"1317":2,"1319":2,"1322":27,"1323":10,"1338":1,"1346":1,"1349":2,"1351":1,"1358":67,"1379":2,"1394":1,"1395":9,"1401":13,"1405":1,"1414":26,"1417":1,"1426":3,"1430":1,"1435":36,"1436":1,"1437":2,"1486":2,"1487":3,"1495":1,"1505":1,"1506":1,"1525":2,"1533":1,"1538":2,"1542":1,"1576":7,"1584":2,"1590":1,"1650":5,"1653":1,"1659":1,"1661":2,"1670":1,"1719":1,"1729":1,"1773":1,"1778":1,"1785":1,"1816":1,"1817":3,"1818":1,"1819":3,"1820":4,"1821":1,"1822":3,"1824":3,"1825":2,"1826":4,"1827":1,"1830":2,"1880":1,"1882":1,"1885":1,"1913":1,"1914":2,"1915":1,"1916":1,"1917":5,"1919":2,"1923":2,"1943":1,"1961":1,"2086":1,"2096":1,"2097":1,"2110":1,"2133":3,"2135":4,"2136":1,"2138":1,"2155":2,"2159":1,"2165":1,"2179":2,"2180":1,"2181":1,"2183":1,"2185":1,"2189":1,"2192":1,"2193":1,"2205":1,"2226":3,"2229":1,"2231":3,"2238":1}}],["decisively",{"3":{"1349":1}}],["decisive",{"3":{"827":1,"1349":1}}],["decisionsupport",{"3":{"493":1,"494":1,"495":1,"995":1,"1018":2,"1082":1,"1099":1,"1203":34,"1207":86,"1264":1,"1270":2,"1285":1,"1346":5,"1349":27,"1358":71,"1366":1,"1368":1,"1389":1,"1394":1,"1437":4,"1495":3,"1525":1}}],["decisions",{"0":{"1201":1,"1647":1},"3":{"0":3,"64":1,"175":1,"242":1,"265":1,"387":1,"555":1,"618":1,"664":1,"724":1,"726":1,"736":1,"775":1,"825":1,"827":1,"867":1,"868":1,"905":1,"912":1,"975":1,"1016":1,"1021":1,"1051":1,"1093":1,"1109":1,"1162":1,"1270":1,"1271":1,"1285":4,"1286":1,"1299":6,"1301":1,"1310":10,"1311":2,"1322":3,"1323":4,"1329":1,"1338":2,"1349":1,"1358":6,"1368":1,"1388":1,"1392":1,"1394":3,"1395":8,"1408":1,"1414":5,"1416":4,"1426":1,"1428":1,"1435":6,"1437":1,"1454":4,"1456":1,"1466":2,"1470":2,"1475":1,"1487":1,"1488":2,"1491":1,"1520":1,"1533":1,"1551":1,"1570":3,"1571":1,"1584":3,"1637":1,"1638":1,"1639":1,"1685":1,"1707":1,"1726":2,"1754":1,"1777":1,"1862":1,"1873":1,"1899":1,"1946":2,"1999":1,"2028":1,"2037":1,"2149":1,"2171":1,"2178":1,"2190":1,"2198":1,"2214":1,"2219":1,"2220":1,"2231":1,"2235":1}}],["decision",{"0":{"5":1,"11":1,"17":1,"31":1,"39":1,"47":1,"53":1,"59":1,"68":1,"78":1,"86":1,"98":1,"109":1,"117":1,"132":1,"147":1,"157":1,"166":1,"175":1,"186":1,"195":1,"207":1,"214":1,"225":1,"235":1,"243":1,"265":1,"273":1,"281":1,"291":1,"300":1,"310":1,"318":1,"330":1,"341":1,"350":1,"359":1,"370":1,"380":1,"388":1,"397":1,"413":1,"420":1,"434":1,"440":1,"448":1,"459":1,"470":1,"482":1,"499":1,"507":1,"518":1,"528":1,"536":1,"545":1,"556":1,"564":1,"572":1,"583":1,"591":1,"599":1,"609":1,"618":1,"626":1,"633":1,"640":1,"649":1,"657":1,"665":1,"674":1,"682":1,"690":1,"698":1,"707":1,"715":1,"725":1,"733":1,"741":1,"749":1,"757":1,"766":1,"774":1,"782":1,"790":1,"799":1,"810":1,"819":1,"827":1,"838":1,"846":1,"854":1,"861":1,"869":1,"881":1,"889":1,"897":1,"905":1,"914":1,"922":1,"931":1,"939":1,"946":1,"954":1,"964":1,"972":1,"980":1,"988":1,"996":1,"1004":1,"1012":1,"1018":1,"1026":1,"1034":1,"1042":1,"1050":1,"1059":1,"1067":1,"1075":1,"1083":1,"1091":1,"1100":1,"1108":1,"1116":1,"1124":1,"1133":1,"1141":1,"1151":1,"1161":1,"1168":1,"1175":1,"1184":1,"1346":1,"1358":1,"1375":1,"1389":1,"2231":1},"1":{"0":1,"1":1},"3":{"0":18,"1":3,"3":1,"21":1,"22":2,"23":2,"24":2,"27":2,"34":1,"43":2,"57":2,"67":1,"71":3,"72":2,"73":2,"74":2,"81":1,"82":1,"85":1,"87":1,"104":1,"109":1,"112":1,"115":1,"121":1,"125":1,"128":2,"130":2,"135":3,"136":1,"137":2,"138":1,"139":1,"140":2,"141":1,"142":1,"145":1,"147":1,"149":1,"150":2,"151":1,"152":1,"155":2,"160":2,"161":1,"164":4,"165":1,"167":1,"170":1,"171":2,"173":1,"174":1,"175":1,"178":1,"181":2,"187":1,"190":1,"200":4,"201":1,"203":2,"219":2,"220":2,"230":1,"231":1,"233":5,"238":1,"245":2,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":3,"257":1,"258":3,"259":3,"260":2,"261":1,"264":1,"265":5,"267":2,"268":6,"269":1,"272":1,"275":1,"276":1,"279":1,"295":2,"296":2,"303":1,"306":4,"308":1,"310":1,"314":1,"317":1,"326":1,"329":1,"336":1,"337":2,"345":1,"354":2,"358":2,"364":2,"365":2,"366":1,"369":1,"375":3,"382":1,"383":1,"386":1,"390":1,"392":1,"393":2,"396":1,"399":1,"400":2,"409":3,"416":2,"419":1,"423":4,"424":1,"425":1,"426":2,"428":2,"429":1,"430":2,"436":1,"444":1,"447":1,"453":3,"454":1,"455":1,"463":3,"464":2,"465":1,"466":4,"468":4,"469":1,"472":1,"473":1,"474":3,"475":2,"476":3,"477":4,"478":3,"483":1,"486":2,"490":1,"495":1,"497":1,"502":1,"508":1,"511":1,"514":2,"516":1,"519":1,"520":1,"522":2,"523":1,"524":1,"526":2,"528":1,"530":1,"531":1,"534":1,"535":1,"536":1,"537":1,"540":1,"543":6,"549":3,"551":2,"552":2,"555":1,"557":1,"559":1,"560":2,"562":1,"567":1,"571":1,"574":1,"578":2,"582":1,"586":1,"591":1,"594":2,"601":1,"604":1,"607":7,"608":1,"611":1,"613":1,"616":4,"621":1,"627":1,"629":3,"632":1,"636":2,"638":1,"642":1,"644":1,"652":1,"656":1,"660":2,"667":1,"669":2,"677":1,"685":1,"688":3,"693":2,"696":1,"703":1,"706":1,"709":1,"710":1,"715":1,"716":1,"719":1,"720":1,"728":1,"731":1,"733":1,"734":1,"737":1,"744":1,"748":2,"751":1,"752":1,"758":1,"761":1,"769":2,"773":1,"775":1,"777":1,"790":2,"794":2,"795":1,"798":1,"800":2,"801":1,"802":2,"803":1,"804":1,"805":1,"806":1,"810":1,"814":1,"819":2,"820":1,"822":2,"825":6,"827":4,"828":1,"829":2,"831":3,"832":1,"833":1,"836":3,"839":2,"841":1,"849":2,"850":2,"853":1,"856":1,"857":2,"864":2,"865":1,"867":2,"869":1,"871":2,"874":1,"875":2,"877":3,"884":1,"892":2,"896":1,"900":1,"905":1,"907":1,"909":1,"912":1,"913":1,"914":1,"915":1,"916":1,"917":1,"924":1,"926":2,"927":2,"929":1,"932":1,"934":4,"944":2,"949":2,"955":1,"958":1,"959":1,"960":2,"963":1,"964":2,"966":1,"967":2,"968":2,"979":1,"980":1,"983":1,"987":1,"988":1,"991":1,"997":1,"1003":1,"1004":1,"1006":1,"1007":1,"1012":1,"1013":1,"1016":1,"1018":1,"1027":1,"1029":1,"1032":1,"1033":1,"1037":2,"1042":1,"1044":3,"1045":2,"1046":1,"1048":1,"1052":1,"1053":1,"1054":3,"1055":1,"1057":1,"1059":2,"1067":1,"1068":1,"1070":3,"1073":2,"1074":1,"1075":3,"1076":1,"1077":1,"1078":2,"1079":2,"1081":1,"1082":1,"1083":2,"1085":1,"1086":1,"1095":1,"1101":1,"1103":1,"1111":1,"1116":1,"1119":1,"1128":3,"1134":1,"1136":1,"1140":1,"1146":1,"1155":1,"1156":1,"1173":1,"1175":1,"1182":1,"1192":1,"1202":1,"1203":1,"1212":3,"1217":1,"1218":1,"1229":3,"1231":1,"1232":2,"1235":1,"1236":1,"1237":13,"1239":1,"1240":1,"1244":2,"1247":1,"1259":12,"1260":4,"1263":2,"1269":1,"1270":12,"1271":1,"1273":3,"1280":2,"1284":2,"1285":37,"1299":24,"1300":5,"1302":1,"1308":6,"1309":3,"1310":22,"1311":2,"1314":1,"1319":1,"1321":2,"1322":30,"1323":34,"1328":1,"1333":1,"1336":1,"1338":28,"1339":1,"1341":1,"1342":1,"1349":14,"1350":1,"1351":2,"1354":1,"1356":1,"1358":53,"1368":2,"1372":1,"1375":1,"1379":7,"1389":1,"1394":28,"1395":41,"1396":1,"1401":19,"1406":1,"1412":1,"1414":17,"1415":3,"1416":5,"1417":1,"1421":1,"1423":1,"1426":1,"1429":1,"1435":42,"1439":1,"1451":1,"1454":3,"1461":1,"1464":1,"1466":6,"1471":1,"1472":1,"1474":1,"1475":1,"1481":1,"1482":1,"1484":1,"1487":2,"1488":1,"1495":3,"1520":1,"1523":1,"1525":6,"1529":1,"1531":1,"1533":3,"1535":1,"1544":2,"1570":1,"1574":2,"1576":6,"1581":1,"1582":1,"1584":2,"1588":1,"1590":1,"1598":3,"1603":1,"1631":2,"1638":2,"1639":2,"1640":1,"1655":1,"1672":1,"1673":1,"1685":2,"1686":1,"1705":1,"1721":1,"1726":1,"1746":4,"1748":2,"1764":1,"1786":1,"1787":1,"1791":1,"1798":2,"1800":1,"1801":1,"1821":2,"1822":1,"1824":1,"1826":1,"1840":2,"1841":1,"1844":1,"1847":1,"1853":1,"1855":1,"1861":2,"1875":1,"1876":1,"1888":1,"1889":1,"1902":1,"1903":1,"1909":1,"1920":1,"1926":2,"1929":1,"1934":2,"1940":1,"1942":1,"1946":2,"1948":1,"1950":1,"1962":1,"1970":1,"1978":1,"1998":1,"1999":1,"2000":1,"2001":1,"2023":1,"2027":1,"2031":1,"2034":1,"2037":1,"2042":1,"2046":1,"2047":1,"2052":1,"2063":2,"2066":1,"2068":1,"2080":1,"2084":2,"2107":1,"2127":1,"2129":1,"2130":1,"2132":1,"2144":1,"2150":1,"2152":1,"2155":1,"2157":2,"2159":1,"2160":1,"2163":1,"2165":1,"2168":1,"2174":1,"2179":1,"2180":1,"2183":2,"2191":4,"2192":1,"2195":1,"2202":2,"2211":1,"2225":1,"2230":1,"2231":5,"2232":2,"2238":1,"2241":1,"2244":1}}],["decimals",{"3":{"1323":1,"1358":1,"1435":1,"1766":1}}],["decimalfilterstrategytests",{"3":{"1199":1,"1207":3}}],["decimalfilterstrategy",{"2":{"1241":1},"3":{"1199":2,"1203":1,"1207":2,"1241":4,"1247":9}}],["decimal",{"2":{"656":1,"1540":1,"1763":1},"3":{"330":1,"656":1,"657":2,"692":1,"717":1,"1234":1,"1237":3,"1247":11,"1271":1,"1285":6,"1299":1,"1322":1,"1323":8,"1341":1,"1349":3,"1358":2,"1361":1,"1365":1,"1368":9,"1394":4,"1395":2,"1416":1,"1435":5,"1495":2,"1540":1,"1599":2,"1684":5,"1685":1,"1763":3,"1809":2,"1987":1}}],["decidable",{"3":{"1415":1}}],["decide",{"0":{"1257":1},"3":{"0":1,"69":1,"150":1,"244":1,"299":1,"305":1,"536":1,"539":1,"681":1,"765":1,"827":1,"898":1,"987":1,"1018":1,"1033":1,"1035":1,"1100":1,"1107":1,"1115":1,"1187":1,"1236":1,"1237":1,"1244":1,"1247":2,"1249":1,"1259":3,"1278":1,"1285":7,"1295":1,"1299":6,"1308":2,"1310":2,"1322":6,"1323":5,"1338":2,"1349":5,"1350":1,"1358":7,"1360":1,"1367":1,"1368":2,"1379":3,"1394":7,"1395":4,"1401":2,"1414":2,"1416":4,"1422":1,"1425":1,"1426":3,"1435":5,"1466":1,"1484":1,"1638":1,"1647":1,"1669":1,"1685":1,"1716":1,"1726":2,"1852":1,"1872":1,"1875":1,"1876":1,"1911":1,"1949":1,"1958":1,"2067":1,"2142":1,"2168":2,"2189":1,"2192":2}}],["decides",{"3":{"0":2,"179":1,"201":1,"225":1,"243":1,"253":1,"258":1,"299":1,"318":1,"336":1,"343":1,"407":1,"535":1,"539":1,"540":1,"544":1,"563":1,"568":1,"571":1,"582":1,"583":1,"598":2,"605":1,"630":1,"639":3,"648":1,"653":1,"664":3,"673":2,"678":2,"689":1,"691":1,"734":1,"741":2,"748":1,"765":1,"770":1,"774":1,"781":3,"783":1,"784":1,"785":1,"786":1,"789":5,"791":1,"813":1,"888":1,"893":1,"896":1,"899":1,"924":1,"945":1,"953":1,"987":1,"1085":1,"1092":1,"1101":1,"1127":1,"1212":1,"1237":3,"1238":1,"1243":1,"1244":1,"1247":5,"1250":1,"1252":1,"1259":6,"1270":2,"1273":1,"1277":1,"1285":14,"1286":1,"1298":1,"1299":9,"1300":1,"1303":1,"1308":2,"1309":6,"1310":6,"1317":1,"1322":8,"1323":6,"1335":1,"1338":4,"1349":4,"1356":1,"1358":19,"1368":2,"1372":1,"1379":2,"1388":1,"1390":2,"1394":12,"1395":9,"1400":1,"1401":4,"1408":1,"1413":1,"1414":4,"1416":3,"1418":1,"1430":1,"1435":6,"1437":3,"1454":1,"1461":1,"1466":1,"1469":1,"1491":1,"1507":1,"1639":1,"1727":1,"1749":1,"1809":1,"1825":1,"1837":1,"1839":2,"1844":1,"1853":1,"1864":1,"1873":1,"1896":1,"1901":1,"1919":1,"1920":1,"1932":1,"1946":1,"1976":1,"1980":1,"1981":1,"1988":1,"1990":1,"2009":1,"2155":2,"2157":1,"2161":1,"2190":1,"2194":1,"2195":1}}],["decided",{"3":{"0":12,"125":1,"145":1,"200":1,"225":1,"255":1,"309":1,"360":1,"380":1,"443":1,"497":1,"498":1,"517":1,"538":1,"541":1,"598":1,"599":1,"608":1,"625":1,"648":1,"664":1,"673":1,"681":1,"683":1,"717":1,"724":2,"728":1,"731":1,"744":1,"790":1,"798":1,"809":1,"852":1,"870":1,"876":1,"880":2,"903":1,"926":1,"953":1,"992":1,"1012":1,"1028":1,"1075":1,"1116":1,"1117":2,"1229":1,"1237":1,"1241":1,"1243":1,"1259":2,"1265":2,"1270":4,"1280":1,"1285":4,"1299":7,"1300":2,"1308":1,"1323":5,"1335":1,"1338":2,"1341":1,"1346":1,"1347":1,"1349":7,"1356":1,"1358":18,"1360":1,"1368":2,"1394":6,"1395":9,"1398":1,"1400":1,"1401":3,"1414":2,"1416":4,"1420":1,"1421":1,"1424":1,"1435":5,"1474":1,"1525":1,"1544":1,"1546":1,"1665":1,"1726":1,"1747":1,"1821":1,"1932":2,"1959":1,"2032":1,"2125":1,"2134":1,"2167":1,"2176":1,"2180":1,"2231":1}}],["deciding",{"3":{"0":1,"109":1,"544":1,"555":1,"582":1,"598":1,"764":1,"775":1,"783":1,"805":1,"987":1,"1229":1,"1237":2,"1284":1,"1285":3,"1310":1,"1338":1,"1358":3,"1394":1,"1395":1,"1401":1,"1416":3,"1417":1,"1810":1,"1955":1,"1971":1,"2182":1}}],["del",{"3":{"1300":2,"1322":2,"1684":6,"1685":6}}],["delaying",{"3":{"1323":2}}],["delays",{"3":{"27":1,"520":1,"709":1,"883":1,"916":1,"1102":1,"1259":1,"1270":1,"1322":1,"1323":2,"1435":1,"1941":1,"2051":1}}],["delay",{"2":{"1162":1,"2184":1},"3":{"18":3,"71":1,"72":1,"293":1,"536":1,"819":1,"1042":2,"1162":1,"1254":2,"1259":8,"1270":3,"1275":2,"1285":7,"1316":1,"1322":14,"1323":2,"1329":1,"1338":1,"1394":2,"1429":1,"1435":7,"1437":1,"1487":1,"1663":1,"1839":1,"1844":1,"1851":1,"2031":1,"2166":2,"2184":1,"2187":2}}],["delayed",{"2":{"818":1,"1842":1},"3":{"0":3,"150":1,"627":1,"640":1,"642":1,"735":1,"818":1,"819":1,"820":1,"821":2,"1075":1,"1076":1,"1263":1,"1270":5,"1322":3,"1338":1,"1466":1,"1640":1,"1664":1,"1748":1,"1842":1,"1921":1,"1922":1}}],["deltas",{"3":{"0":1,"914":1,"1213":1,"1435":1}}],["delta",{"2":{"1688":1,"1698":1},"3":{"0":1,"480":1,"482":1,"484":2,"490":1,"491":1,"492":1,"493":2,"494":2,"495":1,"631":2,"633":2,"635":1,"665":1,"666":1,"832":1,"1212":1,"1352":1,"1358":34,"1401":2,"1487":2,"1489":2,"1495":13,"1574":5,"1575":5,"1688":1,"1698":1,"1726":2}}],["delegatingchatclient",{"2":{"1417":1,"1421":1,"1422":1,"1426":1,"2173":1,"2179":1},"3":{"1417":1,"1421":1,"1422":1,"1426":8,"2173":1,"2179":1}}],["delegatinghandler",{"3":{"1310":1,"1311":1,"1323":8}}],["delegating",{"3":{"0":1,"507":1,"508":1,"513":1,"926":1,"1034":1,"1042":2,"1091":2,"1192":1,"1212":1,"1229":5,"1231":1,"1237":1,"1241":1,"1244":1,"1247":1,"1259":1,"1262":1,"1269":1,"1270":1,"1271":2,"1285":2,"1299":5,"1308":2,"1309":3,"1310":2,"1323":11,"1338":3,"1342":1,"1349":7,"1354":1,"1358":14,"1375":1,"1379":2,"1394":7,"1395":6,"1398":1,"1401":3,"1404":1,"1409":1,"1412":1,"1414":3,"1416":3,"1417":1,"1426":2,"1430":1,"1435":45,"1495":1,"1956":1,"2073":1,"2169":1,"2179":1}}],["delegation",{"3":{"0":1,"267":1,"640":1,"777":1,"825":2,"827":1,"833":2,"922":1,"1237":3,"1240":1,"1259":1,"1270":1,"1285":2,"1309":1,"1338":4,"1349":3,"1351":1,"1358":5,"1379":2,"1394":2,"1395":2,"1401":2,"1466":1}}],["delegations",{"3":{"0":1,"825":1,"829":1,"1394":1,"1395":2}}],["delegatescheduledjob",{"3":{"1199":2,"1207":1}}],["delegates",{"3":{"0":2,"19":1,"27":1,"53":1,"109":1,"318":2,"326":1,"331":1,"459":1,"513":1,"536":1,"545":1,"549":1,"640":1,"680":1,"682":1,"686":1,"774":1,"814":1,"827":2,"1050":1,"1220":1,"1229":4,"1237":9,"1243":1,"1244":1,"1247":17,"1259":5,"1261":1,"1270":1,"1271":1,"1273":1,"1278":1,"1285":14,"1297":1,"1299":4,"1300":1,"1306":1,"1308":4,"1309":3,"1310":8,"1311":2,"1322":4,"1323":9,"1338":4,"1347":2,"1349":17,"1354":1,"1358":27,"1368":1,"1379":9,"1394":16,"1395":7,"1401":5,"1414":7,"1415":7,"1416":13,"1420":1,"1426":3,"1430":1,"1435":28,"1437":2,"1487":1,"1652":1,"1819":1,"1945":1,"1999":1,"2166":1,"2195":1}}],["delegated",{"3":{"0":1,"295":1,"320":1,"827":3,"829":1,"1322":3,"1338":1,"1349":1,"1352":1,"1353":1,"1358":2,"1368":1,"1382":1,"1394":3,"1395":3,"1401":1,"1414":1,"1416":1,"1574":1,"1903":1,"1962":1,"1995":1}}],["delegate",{"3":{"0":2,"11":1,"15":1,"21":1,"27":2,"109":4,"115":1,"135":1,"214":1,"326":1,"459":2,"534":1,"536":2,"539":2,"540":1,"545":1,"591":1,"592":1,"595":1,"675":1,"676":1,"677":1,"684":1,"749":1,"751":1,"819":1,"881":2,"987":2,"988":4,"989":1,"990":3,"1203":2,"1205":1,"1207":2,"1212":1,"1223":1,"1229":9,"1234":1,"1239":2,"1247":10,"1252":1,"1259":3,"1268":2,"1270":16,"1285":19,"1299":8,"1300":1,"1309":8,"1310":17,"1323":42,"1326":2,"1338":17,"1349":5,"1352":1,"1358":18,"1371":1,"1379":4,"1382":1,"1394":54,"1395":15,"1401":4,"1414":2,"1415":8,"1416":3,"1429":1,"1431":1,"1435":48,"1437":1,"1440":2,"1487":1,"1663":1,"1664":1,"1669":1,"1684":1,"1804":1,"1814":1,"1820":1,"1823":1,"1824":1,"1839":2,"1880":2,"2046":1,"2135":2,"2148":1,"2150":1}}],["deletable",{"3":{"312":1,"889":3,"890":1,"891":2,"1237":2,"1270":1,"1276":1,"1280":1,"1285":8,"1361":1,"1368":1,"1395":2,"1414":1,"1850":1}}],["deleting",{"3":{"0":4,"39":1,"40":1,"59":1,"248":1,"434":1,"440":1,"444":1,"459":1,"461":1,"462":1,"528":1,"781":1,"847":1,"907":1,"965":1,"997":1,"1059":1,"1116":2,"1231":1,"1237":2,"1280":2,"1285":3,"1299":4,"1300":1,"1307":1,"1310":2,"1317":1,"1322":4,"1323":2,"1345":1,"1349":4,"1351":1,"1358":13,"1361":1,"1368":1,"1379":1,"1395":1,"1401":3,"1409":1,"1414":3,"1435":7,"1466":2,"1472":1,"1488":1,"1630":6,"1634":3,"1637":1,"1638":10,"1640":1,"1726":1,"1754":1,"1775":1,"1888":1,"1949":1,"1999":1,"2065":1,"2068":1,"2106":1,"2126":1,"2199":1}}],["deletions",{"3":{"461":1,"1120":1,"1358":1,"1414":1,"1495":1}}],["deletion",{"3":{"0":3,"24":1,"38":3,"39":1,"438":1,"444":2,"458":2,"459":1,"462":1,"530":1,"618":1,"724":2,"725":1,"790":1,"791":2,"794":1,"832":1,"888":1,"947":1,"948":1,"1140":1,"1231":1,"1232":1,"1237":4,"1275":1,"1285":2,"1299":4,"1310":1,"1322":5,"1349":1,"1353":1,"1358":12,"1394":2,"1395":3,"1401":1,"1409":1,"1410":3,"1414":16,"1435":5,"1452":1,"1468":1,"1482":1,"1487":1,"1495":4,"1566":2,"1576":1,"1630":2,"1631":4,"1633":2,"1636":1,"1637":9,"1640":1,"1754":1,"1775":2,"1809":1,"1922":1,"2060":2,"2096":1,"2126":1,"2166":1,"2202":1}}],["delete+anonymize",{"3":{"1590":1}}],["deleteoverridethatonlydeletesitself",{"3":{"1435":1}}],["deletewhileopen",{"3":{"1401":1}}],["deletewithconfirmationasync",{"2":{"649":1,"1394":1},"3":{"649":1,"1323":3,"1394":6}}],["deleteroomasync",{"3":{"1394":2}}],["deletequestionasync",{"3":{"1394":2}}],["deleteproduct",{"3":{"1435":1}}],["deleteproducthandlertests",{"3":{"1684":1}}],["deleteproducthandler",{"2":{"924":1,"927":1},"3":{"924":1,"927":1}}],["deletepollasync",{"3":{"1401":1}}],["deletepartnerasync",{"3":{"1394":2}}],["deleteitemasync",{"3":{"1394":1}}],["deletefailed",{"3":{"1322":3,"1323":2}}],["deleteforbidden",{"3":{"1322":2}}],["deletehandler",{"3":{"1310":3,"1358":1}}],["deletelivepollhandler",{"3":{"1199":2,"1203":1,"1207":2,"1270":2,"1395":3,"1401":4}}],["deletebookmarkasync",{"3":{"1395":2}}],["deleteblobifexistsasync",{"3":{"1322":1}}],["deleteblobinternalcommandhandlerbasetests",{"3":{"1199":1,"1207":4}}],["deleteblobinternalcommandhandlerbase",{"2":{"1312":1,"1317":1,"1410":1},"3":{"1199":4,"1203":1,"1207":2,"1312":1,"1317":4,"1322":6,"1356":1,"1358":6,"1410":1,"1414":6,"1495":1}}],["deletebatchsize",{"3":{"1300":3}}],["deletebyidasync",{"3":{"881":1,"1323":3,"1394":5}}],["deletebehaviortestdbcontext",{"3":{"1199":2,"1207":1}}],["deletebehaviorconventiontests",{"3":{"1083":3,"1085":1,"1199":2,"1207":10,"1285":1,"1435":31,"1488":1,"1525":1}}],["deletebehaviorconventiontestsbase",{"2":{"1083":1,"1280":1,"1430":1,"2063":1},"3":{"0":1,"1083":2,"1199":3,"1207":2,"1280":1,"1285":1,"1430":3,"1435":8,"2063":2}}],["deletebehavior",{"2":{"1083":2,"1280":1,"2063":2},"3":{"1083":3,"1207":2,"1280":2,"1285":4,"1361":1,"1368":3,"1435":7,"1488":1,"2063":2}}],["deletebehaviorsourceannotation",{"3":{"1285":1}}],["deletebehaviorsource",{"3":{"0":1,"1083":1,"1280":1,"1430":1,"1435":3,"2063":1}}],["deletecategoryasync",{"3":{"1394":1}}],["deletecategoryhandler",{"2":{"924":1},"3":{"924":1,"1270":2}}],["deleteconfirm",{"3":{"1323":1,"1394":5,"1401":1}}],["deleteconfirmationtests",{"3":{"1199":1,"1207":2}}],["deleteconfirmation",{"3":{"649":1,"1323":5,"1394":21,"1401":2,"1576":1}}],["deleteconferencecategoryhandler",{"3":{"1199":2,"1203":1,"1207":2,"1270":2,"1358":9}}],["deletechildren",{"2":{"37":1,"39":1,"43":1,"1168":1,"1342":1},"3":{"37":1,"39":2,"43":1,"1168":1,"1231":1,"1237":2,"1342":1,"1349":8,"1358":2,"1401":3,"1435":7,"1576":1,"1585":1,"1661":1,"1809":1}}],["deleteuserasync",{"3":{"1323":1}}],["deleteusercommand",{"3":{"988":1,"1199":5,"1203":1,"1207":2,"1322":6,"1409":2,"1414":6}}],["deleteuserhandlertests",{"3":{"1199":1,"1207":2}}],["deleteuserhandler",{"2":{"463":1},"3":{"444":1,"459":3,"463":3,"464":2,"465":2,"466":2,"1199":2,"1203":1,"1207":2,"1285":1,"1322":7,"1409":2,"1414":18,"1495":4,"1525":1,"1534":2,"1590":1,"1599":1,"2126":1}}],["deleteuserhandlerbasetests",{"3":{"497":1,"1199":1,"1207":4,"1322":1,"1437":2}}],["deleteuserhandlerbase",{"2":{"457":1,"459":1,"461":1,"465":1,"466":1,"724":1,"728":1,"1290":1,"1402":1,"1671":1},"3":{"0":1,"457":1,"459":6,"461":3,"465":6,"466":1,"724":3,"725":3,"728":1,"910":1,"1199":3,"1203":1,"1207":2,"1285":2,"1290":2,"1299":7,"1321":2,"1322":11,"1402":1,"1409":1,"1414":11,"1495":4,"1576":1,"1671":1,"2065":1}}],["deleteuser",{"2":{"1322":1},"3":{"444":1,"459":3,"463":2,"465":3,"466":1,"724":1,"910":1,"988":1,"1203":3,"1207":8,"1285":1,"1290":1,"1299":5,"1321":1,"1322":13,"1409":1,"1414":7,"1495":1,"1499":1,"1525":1,"1576":1,"2065":1}}],["deleteaccountasync",{"3":{"1414":1}}],["deleteactivityasync",{"3":{"1394":2}}],["deleteanswerasync",{"3":{"1394":6,"1395":8}}],["deleteall",{"2":{"1345":1},"3":{"1345":1,"1349":4}}],["deleteavatarblob",{"2":{"444":1,"1410":1},"3":{"444":2,"1203":3,"1207":8,"1317":1,"1322":2,"1409":1,"1410":1,"1414":6}}],["deleteavatarblobinternalcommandvalidator",{"2":{"1410":1},"3":{"1199":1,"1203":1,"1207":2,"1410":2,"1414":4}}],["deleteavatarblobinternalcommandhandlertests",{"3":{"1199":1,"1207":2}}],["deleteavatarblobinternalcommandhandler",{"2":{"1410":1,"2126":1},"3":{"1199":2,"1203":1,"1207":2,"1317":1,"1322":3,"1410":2,"1414":7,"2126":1}}],["deleteavatarblobinternalcommand",{"2":{"440":1,"444":1,"1409":1,"1410":1,"2126":1},"3":{"440":1,"444":2,"1199":10,"1203":1,"1207":2,"1322":2,"1409":2,"1410":2,"1414":19,"1495":1,"2126":1}}],["deleteasync",{"2":{"318":1,"326":1,"434":1,"1385":1},"3":{"318":1,"326":1,"434":1,"881":2,"1285":3,"1300":2,"1308":2,"1310":4,"1322":6,"1323":4,"1358":7,"1379":18,"1381":2,"1385":1,"1388":1,"1394":26,"1395":5,"1401":2,"1414":7,"1428":1,"1435":1,"1487":1,"1495":1,"2054":1,"2073":1,"2125":1}}],["deleteeventasync",{"3":{"1394":2}}],["deleteeventhandlertests",{"3":{"1199":1,"1207":2,"1349":2,"1358":2}}],["deleteeventhandler",{"3":{"1116":1,"1199":2,"1203":1,"1207":2,"1322":1,"1349":4,"1351":9,"1358":11,"1495":2}}],["deleteentitycommand",{"2":{"1265":1},"3":{"1199":43,"1203":1,"1207":2,"1260":1,"1265":6,"1270":6,"1310":3,"1358":5,"1379":8,"1395":6,"1401":3,"1435":1,"1921":1}}],["deleteentityhandlertests",{"3":{"1199":1,"1207":3,"1270":1}}],["deleteentityhandlerextensiontests",{"2":{"926":1},"3":{"926":1,"1199":1,"1207":1}}],["deleteentityhandler",{"2":{"920":1,"1265":1},"3":{"0":1,"920":1,"926":3,"1199":11,"1203":1,"1207":2,"1260":1,"1265":8,"1270":7,"1285":2,"1322":2,"1351":1,"1358":29,"1395":2,"1401":8,"1576":2}}],["deleteerror",{"3":{"265":1,"1323":2}}],["deletesponsorasync",{"3":{"1394":2}}],["deletespeakerasync",{"3":{"1394":2}}],["deletespeakercommand",{"3":{"1270":1}}],["deletesessionasync",{"3":{"1394":1}}],["deletesessionassetcommandvalidator",{"3":{"1199":1,"1203":1,"1207":2,"1358":3}}],["deletesessionassetcommand",{"3":{"1199":6,"1203":1,"1207":2,"1358":7,"1379":1}}],["deletesessionassethandlertests",{"3":{"1199":1,"1207":2}}],["deletesessionassethandler",{"3":{"1116":1,"1199":2,"1203":1,"1207":2,"1322":1,"1349":1,"1356":2,"1358":9,"1495":1}}],["deletesessionassetblobinternalcommandhandler",{"2":{"1203":1,"1322":1,"1414":1},"3":{"1199":2,"1203":1,"1207":2,"1285":2,"1317":1,"1322":3,"1356":2,"1358":6,"1414":1}}],["deletesessionassetblobinternalcommandhandlertests",{"2":{"1199":1,"1207":1},"3":{"1199":1,"1207":2}}],["deletesessionassetblobinternalcommandvalidator",{"2":{"1199":1,"1203":1},"3":{"1199":2,"1203":1,"1207":2,"1358":5}}],["deletesessionassetblobinternalcommand",{"2":{"1495":1},"3":{"536":1,"1116":1,"1199":10,"1203":1,"1207":2,"1322":2,"1356":3,"1358":15,"1495":1}}],["deletesessionassetblob",{"2":{"536":1,"1116":1,"1630":1},"3":{"0":1,"536":2,"1116":2,"1203":3,"1207":6,"1285":1,"1317":1,"1322":2,"1356":1,"1358":6,"1630":1}}],["deletesessioncommand",{"3":{"1270":1}}],["deletesessionhandlertests",{"3":{"1199":1,"1207":2,"1349":1,"1358":2}}],["deletesessionhandler",{"3":{"1116":1,"1199":3,"1203":1,"1207":2,"1270":2,"1322":1,"1358":10,"1495":1}}],["deletes",{"3":{"0":1,"39":2,"435":1,"458":1,"459":1,"490":1,"592":1,"690":1,"698":1,"733":1,"790":1,"792":2,"831":1,"881":2,"905":1,"996":1,"1061":1,"1082":2,"1086":1,"1116":1,"1168":1,"1229":1,"1270":4,"1275":1,"1280":1,"1282":1,"1284":1,"1285":5,"1293":1,"1299":6,"1300":9,"1322":7,"1323":4,"1342":1,"1345":1,"1349":7,"1356":1,"1358":13,"1368":4,"1379":1,"1394":13,"1395":11,"1401":4,"1414":7,"1416":2,"1421":1,"1435":11,"1437":1,"1444":1,"1454":2,"1459":4,"1462":1,"1464":3,"1466":3,"1471":1,"1474":2,"1488":1,"1544":1,"1576":1,"1629":1,"1630":5,"1631":6,"1634":4,"1638":9,"1639":1,"1663":1,"1681":1,"1706":1,"1746":1,"1748":2,"1764":1,"1844":1,"1935":1,"1967":1,"2033":1,"2062":1,"2125":2,"2126":1,"2201":1}}],["deletedvote",{"3":{"1401":1}}],["deletedbookmark",{"3":{"1395":2}}],["deletedbynamespecification",{"3":{"1199":2,"1207":1}}],["deletedby",{"2":{"37":1,"41":1},"3":{"37":1,"39":1,"41":1,"1231":1,"1237":4,"1285":1}}],["deletedon",{"2":{"37":1,"41":1},"3":{"37":1,"39":1,"41":1,"1231":2,"1237":5,"1274":1,"1285":2,"1414":1,"1663":1}}],["deleted",{"1":{"456":1,"457":1,"458":1,"459":1,"460":1,"461":1,"462":1,"463":1,"464":1,"465":1,"466":1},"2":{"781":1,"785":1,"1082":1,"1354":1},"3":{"0":15,"39":3,"41":2,"62":1,"109":1,"135":1,"264":1,"308":1,"441":2,"456":1,"457":1,"458":1,"459":11,"461":4,"463":2,"465":3,"468":1,"503":1,"529":1,"538":1,"545":1,"551":1,"562":1,"607":1,"690":1,"698":2,"707":1,"715":1,"716":2,"717":1,"721":1,"748":1,"749":2,"781":1,"782":6,"784":1,"785":1,"790":1,"832":1,"833":1,"838":1,"848":1,"881":1,"888":1,"889":3,"890":4,"891":3,"905":2,"910":1,"926":1,"945":1,"966":1,"972":1,"1033":1,"1034":1,"1082":1,"1116":1,"1168":2,"1192":1,"1212":5,"1229":1,"1231":7,"1232":1,"1233":1,"1237":17,"1240":1,"1247":4,"1249":1,"1259":4,"1270":1,"1274":1,"1276":1,"1277":1,"1280":1,"1282":2,"1285":43,"1286":1,"1288":3,"1299":15,"1300":1,"1303":1,"1308":1,"1310":10,"1322":18,"1341":1,"1342":3,"1344":2,"1345":3,"1346":1,"1349":58,"1351":1,"1354":1,"1356":1,"1357":1,"1358":80,"1361":1,"1368":9,"1379":1,"1394":1,"1395":47,"1397":3,"1401":27,"1405":1,"1408":1,"1409":1,"1414":24,"1416":1,"1435":19,"1437":2,"1441":1,"1450":1,"1455":2,"1466":1,"1470":1,"1472":3,"1474":2,"1478":1,"1485":1,"1489":1,"1495":19,"1610":1,"1629":3,"1630":12,"1631":9,"1632":4,"1633":25,"1634":4,"1635":1,"1636":1,"1637":15,"1638":34,"1639":6,"1640":2,"1652":1,"1663":1,"1666":3,"1671":2,"1674":2,"1683":1,"1684":2,"1697":1,"1703":1,"1718":1,"1727":1,"1746":1,"1747":6,"1748":1,"1754":1,"1763":2,"1764":3,"1766":1,"1768":12,"1775":2,"1809":1,"1824":1,"1838":1,"1850":2,"1890":1,"1897":1,"1922":1,"1983":1,"2044":1,"2060":4,"2065":1,"2097":1,"2125":1,"2126":1,"2157":1,"2163":1,"2229":1,"2231":3}}],["delete",{"0":{"1385":1,"2063":1},"1":{"36":1,"37":1,"38":1,"39":1,"40":1,"41":1,"42":1,"43":1,"886":1,"887":1,"888":1,"889":1,"890":1,"891":1,"892":1,"893":1,"919":1,"920":1,"921":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"1080":1,"1081":1,"1082":1,"1083":1,"1084":1,"1085":1,"1086":1,"1087":1,"1950":1,"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1956":1,"1957":1,"1958":1,"2060":1,"2061":1,"2062":1,"2063":1,"2064":1,"2065":1,"2066":1,"2067":1,"2068":1},"2":{"38":1,"40":1,"43":1,"207":1,"458":1,"718":1,"719":1,"1082":1,"1140":1,"1477":1,"1686":1,"1745":1,"1935":1,"2033":1,"2060":1,"2063":1,"2202":1},"3":{"0":26,"36":1,"37":1,"38":5,"39":5,"40":2,"41":2,"43":2,"59":1,"157":1,"207":2,"243":1,"245":1,"257":1,"265":1,"291":1,"305":1,"313":1,"318":2,"326":1,"328":1,"329":1,"341":1,"346":1,"358":1,"361":1,"362":2,"366":1,"434":4,"435":2,"440":2,"458":3,"459":4,"461":1,"462":2,"463":1,"464":1,"466":1,"497":1,"499":1,"534":1,"549":1,"550":1,"609":1,"610":1,"611":1,"633":1,"640":1,"642":1,"690":1,"698":3,"699":2,"700":1,"707":1,"710":1,"713":1,"714":1,"715":1,"716":2,"718":1,"719":1,"721":1,"723":1,"725":1,"733":3,"774":1,"782":2,"790":1,"791":1,"853":1,"886":1,"888":3,"889":3,"890":2,"891":1,"893":1,"905":1,"907":1,"910":1,"919":1,"920":1,"921":1,"922":3,"924":2,"925":1,"926":5,"950":1,"996":1,"1033":1,"1059":1,"1061":1,"1063":1,"1069":1,"1080":1,"1081":1,"1082":6,"1083":3,"1084":3,"1085":2,"1086":2,"1087":1,"1116":8,"1117":1,"1118":2,"1120":2,"1140":1,"1141":1,"1142":1,"1146":1,"1147":1,"1168":4,"1170":1,"1174":2,"1175":5,"1180":1,"1192":1,"1201":2,"1202":1,"1203":8,"1207":18,"1212":9,"1213":1,"1230":2,"1231":9,"1232":2,"1236":2,"1237":37,"1240":1,"1244":1,"1247":7,"1259":2,"1260":2,"1265":5,"1270":27,"1272":1,"1273":1,"1274":2,"1275":1,"1276":1,"1277":1,"1278":2,"1280":5,"1285":87,"1289":1,"1290":3,"1299":32,"1300":13,"1305":1,"1307":3,"1308":7,"1310":18,"1315":1,"1317":2,"1322":44,"1323":20,"1339":1,"1341":5,"1342":6,"1343":1,"1344":2,"1345":4,"1349":97,"1351":2,"1352":1,"1353":3,"1354":2,"1356":4,"1358":185,"1360":1,"1361":3,"1362":1,"1368":25,"1370":4,"1372":1,"1374":1,"1379":22,"1382":1,"1385":1,"1387":2,"1391":1,"1394":95,"1395":90,"1397":3,"1401":53,"1404":7,"1405":2,"1409":2,"1410":1,"1411":2,"1413":1,"1414":69,"1416":6,"1430":3,"1435":146,"1437":17,"1445":1,"1452":2,"1454":1,"1464":3,"1466":3,"1468":1,"1472":1,"1473":2,"1474":1,"1477":4,"1486":2,"1487":1,"1488":2,"1489":3,"1495":4,"1525":4,"1534":1,"1544":2,"1566":4,"1569":1,"1571":1,"1576":7,"1585":2,"1590":1,"1606":1,"1607":1,"1610":1,"1611":1,"1613":1,"1617":2,"1629":3,"1630":8,"1631":40,"1632":2,"1634":9,"1636":1,"1637":6,"1638":14,"1639":3,"1640":1,"1642":1,"1643":1,"1650":5,"1656":1,"1661":1,"1663":1,"1665":2,"1666":1,"1667":1,"1668":1,"1670":3,"1671":2,"1683":1,"1684":5,"1685":7,"1686":2,"1688":1,"1698":1,"1699":1,"1700":1,"1709":1,"1716":1,"1719":3,"1726":4,"1728":1,"1740":1,"1741":4,"1745":13,"1746":6,"1747":17,"1748":1,"1749":2,"1750":1,"1764":4,"1767":2,"1768":16,"1778":2,"1783":1,"1794":2,"1795":1,"1798":1,"1809":6,"1811":1,"1814":1,"1838":1,"1850":2,"1908":1,"1911":1,"1914":1,"1915":1,"1921":2,"1922":1,"1935":1,"1950":1,"1963":1,"1985":1,"1986":1,"1990":1,"2033":1,"2046":1,"2059":1,"2060":9,"2061":1,"2062":7,"2063":6,"2065":2,"2067":1,"2068":7,"2088":1,"2089":1,"2097":1,"2125":1,"2137":1,"2163":1,"2165":1,"2166":1,"2167":1,"2185":1,"2201":2,"2202":1,"2207":1,"2209":1,"2220":1,"2229":2,"2231":7}}],["delimiting",{"3":{"1089":1,"1093":1,"2178":1}}],["delimiters",{"3":{"1019":1,"1323":1}}],["delimiter",{"3":{"1018":1,"1019":1,"1020":1,"1300":1,"1323":1,"1365":1,"1454":1}}],["delimited",{"3":{"0":1,"1017":1,"1018":2,"1090":1,"1212":1,"1338":1,"1365":2,"1368":1,"1434":1,"1437":1,"1491":1,"1495":1,"1525":1}}],["delimit",{"3":{"1018":1}}],["deliberateness",{"3":{"1435":1}}],["deliberate",{"0":{"1496":1,"1533":1,"1584":1,"1598":1},"3":{"0":5,"42":1,"53":1,"61":1,"81":1,"85":1,"122":2,"126":1,"135":1,"136":1,"149":1,"157":1,"175":2,"178":1,"189":1,"201":1,"230":1,"237":1,"255":1,"264":1,"265":1,"267":1,"302":1,"359":1,"361":1,"365":1,"372":1,"374":1,"382":1,"388":1,"391":1,"408":1,"446":1,"461":1,"469":1,"473":1,"536":1,"545":1,"566":1,"585":1,"607":1,"609":1,"628":1,"629":1,"661":1,"715":1,"725":1,"735":1,"743":1,"751":1,"764":1,"766":2,"767":1,"776":1,"785":1,"790":1,"792":2,"793":1,"802":1,"821":1,"825":1,"827":1,"831":1,"832":1,"840":1,"872":1,"897":1,"906":1,"913":1,"922":1,"931":1,"932":1,"941":1,"988":1,"996":1,"998":1,"1018":1,"1042":1,"1043":1,"1044":1,"1052":1,"1061":1,"1077":1,"1082":1,"1116":1,"1126":1,"1135":1,"1162":1,"1211":1,"1212":2,"1229":3,"1230":1,"1236":1,"1237":11,"1239":1,"1242":1,"1244":1,"1247":8,"1254":1,"1257":2,"1259":13,"1263":3,"1264":1,"1265":1,"1267":1,"1270":8,"1271":4,"1276":1,"1285":45,"1297":1,"1299":19,"1300":3,"1303":1,"1306":1,"1308":4,"1309":1,"1310":18,"1311":6,"1315":1,"1321":1,"1322":21,"1323":27,"1326":1,"1331":1,"1332":1,"1336":1,"1338":28,"1340":1,"1343":1,"1346":1,"1349":6,"1351":1,"1352":1,"1357":1,"1358":48,"1360":1,"1368":4,"1377":1,"1379":7,"1382":1,"1386":1,"1393":1,"1394":26,"1395":30,"1398":1,"1401":9,"1413":1,"1414":13,"1415":3,"1416":15,"1420":1,"1426":1,"1430":1,"1433":1,"1435":51,"1437":2,"1440":3,"1442":1,"1446":1,"1452":2,"1454":3,"1455":1,"1456":1,"1466":5,"1472":1,"1473":1,"1482":1,"1486":2,"1488":2,"1490":1,"1491":1,"1494":1,"1495":2,"1499":1,"1503":2,"1525":3,"1529":1,"1530":4,"1531":2,"1533":3,"1544":1,"1546":1,"1554":1,"1568":1,"1574":1,"1576":4,"1580":1,"1581":2,"1582":1,"1583":1,"1584":1,"1585":1,"1588":1,"1590":5,"1595":3,"1596":3,"1598":2,"1637":2,"1639":1,"1640":1,"1652":1,"1653":2,"1707":1,"1721":1,"1734":1,"1804":1,"1814":1,"1820":3,"1826":1,"1832":1,"1839":1,"1860":1,"1866":1,"1875":1,"1897":1,"1901":1,"1908":1,"1914":1,"1917":1,"1919":1,"1922":2,"1932":1,"1939":1,"1947":1,"1948":1,"1952":1,"1962":1,"1976":1,"1983":1,"1988":1,"1993":1,"1999":2,"2000":2,"2008":1,"2011":1,"2012":1,"2018":2,"2023":1,"2026":1,"2029":1,"2031":1,"2040":1,"2042":1,"2063":3,"2064":1,"2070":1,"2074":1,"2080":1,"2097":1,"2110":1,"2116":1,"2126":1,"2131":1,"2136":1,"2141":1,"2142":1,"2154":1,"2158":1,"2165":1,"2166":1,"2176":1,"2191":2,"2197":1,"2231":2}}],["deliberately",{"0":{"1367":1,"1388":1,"1720":1,"1901":1},"3":{"0":35,"23":1,"24":2,"26":1,"31":1,"45":1,"55":1,"62":1,"66":1,"68":1,"92":1,"97":1,"100":1,"109":2,"122":1,"124":1,"125":1,"135":3,"136":2,"142":1,"143":1,"147":1,"149":1,"164":1,"173":1,"178":1,"188":1,"200":1,"207":1,"219":1,"230":2,"235":1,"237":1,"243":1,"245":1,"248":2,"256":1,"265":5,"267":1,"273":1,"283":1,"290":1,"291":1,"309":1,"312":1,"314":1,"318":1,"322":1,"328":1,"353":1,"355":1,"358":1,"359":1,"365":1,"369":1,"372":1,"373":1,"379":1,"401":1,"402":1,"403":1,"413":2,"435":1,"448":3,"455":1,"459":2,"470":1,"474":1,"482":1,"494":1,"500":1,"501":2,"509":1,"517":1,"530":1,"535":1,"545":2,"546":1,"547":1,"549":5,"550":2,"556":1,"558":2,"564":1,"572":2,"576":1,"583":1,"591":2,"592":1,"593":1,"599":1,"600":2,"602":1,"603":1,"607":1,"609":1,"618":2,"620":1,"625":1,"626":1,"632":1,"633":1,"641":1,"649":1,"657":1,"665":6,"672":3,"682":1,"690":2,"692":1,"698":2,"700":1,"703":1,"707":1,"715":1,"725":1,"733":1,"741":2,"749":1,"751":1,"753":1,"757":1,"765":1,"766":2,"774":1,"778":1,"782":2,"786":1,"790":3,"795":1,"800":1,"819":2,"820":1,"825":1,"826":1,"827":4,"834":1,"836":1,"837":1,"848":1,"854":1,"869":2,"873":1,"881":1,"883":1,"889":1,"897":2,"904":1,"905":4,"907":3,"914":1,"926":2,"933":1,"939":1,"947":1,"963":1,"964":1,"971":1,"972":1,"988":5,"995":1,"998":1,"999":1,"1005":1,"1012":1,"1018":1,"1020":1,"1034":1,"1041":1,"1042":2,"1046":1,"1050":2,"1051":1,"1053":1,"1059":1,"1066":1,"1067":1,"1069":1,"1083":1,"1093":1,"1099":1,"1118":1,"1124":3,"1133":1,"1212":8,"1213":1,"1215":1,"1217":1,"1222":1,"1228":2,"1229":4,"1231":2,"1232":1,"1233":1,"1234":1,"1235":1,"1237":10,"1239":1,"1240":1,"1242":1,"1244":1,"1247":13,"1252":2,"1253":1,"1254":1,"1259":14,"1261":1,"1263":1,"1265":1,"1267":2,"1269":1,"1270":25,"1271":1,"1273":4,"1275":1,"1276":1,"1278":2,"1279":2,"1280":1,"1284":1,"1285":78,"1288":3,"1289":3,"1290":2,"1291":1,"1292":1,"1293":2,"1297":1,"1299":39,"1300":11,"1303":3,"1304":1,"1305":1,"1307":1,"1308":17,"1309":3,"1310":53,"1311":2,"1313":1,"1314":2,"1316":2,"1317":3,"1318":1,"1319":1,"1321":1,"1322":38,"1323":56,"1324":1,"1327":2,"1329":2,"1331":1,"1334":1,"1335":2,"1336":1,"1337":1,"1338":27,"1339":1,"1341":3,"1342":3,"1348":1,"1349":21,"1352":2,"1354":2,"1356":1,"1358":84,"1361":2,"1362":1,"1363":1,"1364":1,"1365":1,"1368":17,"1371":1,"1373":1,"1374":1,"1376":2,"1377":1,"1379":10,"1382":1,"1388":1,"1390":1,"1393":1,"1394":30,"1395":67,"1396":1,"1397":1,"1398":2,"1399":1,"1401":26,"1403":1,"1404":2,"1407":1,"1408":1,"1409":2,"1411":2,"1412":1,"1413":1,"1414":31,"1415":8,"1416":36,"1420":1,"1421":1,"1422":1,"1423":1,"1424":1,"1426":4,"1428":1,"1429":3,"1430":3,"1432":3,"1433":1,"1434":2,"1435":137,"1437":16,"1440":3,"1441":1,"1442":4,"1444":1,"1445":1,"1446":2,"1447":1,"1452":3,"1454":7,"1457":1,"1458":1,"1459":2,"1464":3,"1466":16,"1471":1,"1472":1,"1473":2,"1474":2,"1475":1,"1476":2,"1477":1,"1480":1,"1485":3,"1486":1,"1487":2,"1488":8,"1489":1,"1490":1,"1491":6,"1495":3,"1506":2,"1511":1,"1525":7,"1529":1,"1530":1,"1531":1,"1533":1,"1536":1,"1549":1,"1555":1,"1576":6,"1584":1,"1585":1,"1588":1,"1590":2,"1595":1,"1601":1,"1629":1,"1630":1,"1631":1,"1638":1,"1639":1,"1640":2,"1651":1,"1652":1,"1653":1,"1654":1,"1660":2,"1664":1,"1669":2,"1683":1,"1685":2,"1688":1,"1691":1,"1698":1,"1700":1,"1706":1,"1722":1,"1726":2,"1728":1,"1730":1,"1734":1,"1747":1,"1748":2,"1760":1,"1763":2,"1764":4,"1765":1,"1770":1,"1791":1,"1794":1,"1796":1,"1798":1,"1805":1,"1809":1,"1814":3,"1822":1,"1832":1,"1837":1,"1838":1,"1840":1,"1842":1,"1844":1,"1848":1,"1851":1,"1854":1,"1855":1,"1857":1,"1859":1,"1872":1,"1875":1,"1879":1,"1890":1,"1896":1,"1897":1,"1901":1,"1908":1,"1910":1,"1915":1,"1916":1,"1917":1,"1921":1,"1922":2,"1923":1,"1926":2,"1928":1,"1932":1,"1933":1,"1934":1,"1941":1,"1951":1,"1967":1,"1968":1,"1969":1,"1981":2,"1983":1,"1989":1,"1993":2,"1994":2,"1997":1,"2000":3,"2001":1,"2003":1,"2006":1,"2009":1,"2023":1,"2028":1,"2029":1,"2033":1,"2043":3,"2046":1,"2049":1,"2053":1,"2056":1,"2058":1,"2062":1,"2063":1,"2066":3,"2067":1,"2074":1,"2076":1,"2081":1,"2084":2,"2088":1,"2097":1,"2106":1,"2112":1,"2118":1,"2119":1,"2125":1,"2137":1,"2140":1,"2141":1,"2144":1,"2150":1,"2155":2,"2156":1,"2157":1,"2162":1,"2163":1,"2166":2,"2170":1,"2176":1,"2179":1,"2182":1,"2183":1,"2185":1,"2187":1,"2190":1,"2191":1,"2197":1,"2199":1,"2227":1,"2231":1}}],["deliverorderhandler",{"2":{"1874":1},"3":{"1764":1,"1874":1}}],["deliverat",{"3":{"1395":3,"1416":2}}],["deliverable",{"3":{"0":1,"24":1,"528":1,"800":1,"811":1,"1259":1,"1765":1}}],["deliveries",{"3":{"536":1,"702":1,"789":2,"1258":1,"1259":1,"1308":1,"1576":1,"1747":2,"1748":1,"1764":1,"1775":1,"2163":2,"2165":2,"2168":1}}],["delivering",{"3":{"391":1,"1299":1,"1338":1,"1395":1,"2161":1,"2212":1,"2216":1,"2217":1,"2219":2}}],["delivers",{"3":{"96":1,"896":1,"926":1,"988":1,"1091":1,"1212":1,"1247":1,"1270":1,"1285":2,"1308":1,"1344":1,"1349":1,"1358":1,"1414":1,"1415":1,"1466":1,"1664":1,"2176":1}}],["deliver",{"2":{"1770":1},"3":{"18":1,"91":1,"224":1,"225":1,"1101":1,"1212":1,"1229":1,"1254":1,"1257":1,"1285":1,"1299":1,"1301":1,"1308":1,"1322":1,"1323":1,"1395":1,"1424":1,"1745":2,"1748":2,"1764":3,"1770":1,"1774":1,"1836":1,"1845":1,"1932":2,"1940":1,"2024":1,"2213":1,"2219":1}}],["deliveredstate",{"3":{"1766":1}}],["deliveredon",{"3":{"1763":1,"1768":1}}],["delivered",{"3":{"0":2,"26":2,"27":2,"91":1,"120":1,"193":1,"202":1,"370":1,"725":1,"726":1,"854":1,"955":1,"1116":1,"1140":1,"1237":1,"1249":1,"1253":1,"1254":1,"1255":1,"1259":8,"1270":1,"1284":1,"1285":6,"1303":1,"1308":2,"1316":1,"1322":5,"1323":1,"1358":1,"1395":1,"1416":1,"1435":1,"1466":2,"1630":1,"1636":1,"1650":1,"1656":1,"1685":1,"1747":2,"1748":4,"1750":1,"1754":1,"1758":1,"1763":3,"1764":6,"1765":2,"1766":2,"1767":3,"1768":1,"1775":2,"1839":1,"1841":1,"1844":1,"1889":1,"1932":1,"1939":1,"1940":1,"2090":1,"2155":1,"2166":1,"2194":1,"2202":1,"2219":1,"2229":1}}],["delivery",{"0":{"1253":1,"1255":1,"1836":1,"1840":1,"2165":1},"1":{"431":1,"432":1,"433":1,"434":1,"435":1,"436":1},"3":{"0":9,"18":1,"20":1,"24":3,"26":1,"40":1,"67":1,"68":1,"80":1,"135":1,"156":1,"194":1,"195":1,"199":1,"201":2,"224":1,"225":5,"226":1,"228":1,"230":5,"255":3,"380":1,"383":1,"431":1,"433":1,"434":1,"435":2,"436":1,"518":1,"536":2,"540":2,"541":1,"605":1,"611":1,"640":1,"694":1,"696":1,"698":1,"702":3,"726":1,"757":1,"759":1,"761":1,"789":1,"790":4,"791":1,"792":3,"795":1,"815":1,"818":2,"820":2,"845":1,"846":2,"847":1,"849":1,"856":1,"896":1,"901":1,"930":1,"931":1,"933":1,"957":1,"988":1,"989":1,"1011":2,"1012":2,"1100":1,"1140":1,"1142":1,"1143":1,"1144":1,"1147":1,"1212":2,"1237":5,"1247":1,"1252":3,"1253":2,"1254":1,"1255":1,"1256":2,"1257":2,"1258":1,"1259":49,"1269":1,"1270":3,"1274":1,"1284":2,"1285":10,"1301":1,"1303":3,"1306":1,"1308":32,"1310":3,"1312":1,"1315":1,"1319":1,"1322":14,"1323":2,"1349":4,"1354":2,"1358":7,"1379":1,"1395":15,"1396":1,"1412":2,"1414":10,"1416":10,"1435":4,"1436":1,"1437":5,"1454":1,"1459":1,"1466":1,"1507":1,"1525":1,"1546":1,"1553":1,"1565":1,"1571":1,"1576":1,"1640":3,"1659":1,"1664":1,"1668":1,"1748":1,"1749":1,"1754":1,"1758":1,"1762":2,"1763":2,"1764":2,"1766":4,"1767":1,"1768":1,"1769":1,"1770":1,"1775":3,"1789":3,"1836":1,"1837":1,"1840":1,"1841":3,"1842":1,"1844":1,"1845":1,"1888":1,"1889":1,"1891":3,"1909":1,"1911":3,"1921":1,"1922":1,"1931":2,"1932":8,"1933":1,"1935":1,"1939":5,"1940":3,"1942":2,"1943":1,"1948":2,"1949":1,"2019":1,"2026":1,"2031":1,"2064":1,"2090":1,"2163":1,"2164":1,"2165":3,"2166":1,"2167":1,"2168":3,"2181":1,"2188":1,"2211":1,"2212":1,"2219":2,"2224":1,"2231":2}}],["desde",{"3":{"1685":1}}],["desk",{"3":{"1395":4,"1629":1}}],["desktops",{"3":{"741":1}}],["desktop",{"0":{"1668":1},"3":{"618":1,"1323":13,"1384":1,"1394":12,"1395":1,"1415":2,"1416":7,"1435":3,"1437":1,"1479":1,"1558":2,"1647":1,"1668":1,"1674":1,"1681":1,"1692":1,"1711":2,"1712":2,"1717":2,"2071":2,"2072":3,"2078":1,"2118":1}}],["desired",{"3":{"1299":3,"1322":1,"1323":1,"1401":2,"1416":1,"1763":2,"1775":1}}],["desire",{"3":{"1145":1}}],["designs",{"3":{"1799":1,"1801":1}}],["designsqliteentityconfiguration",{"3":{"1199":1,"1207":1}}],["designsqliteentity",{"3":{"1199":3,"1207":1}}],["designated",{"3":{"1663":1}}],["designation",{"3":{"1628":2}}],["designalphaentityconfiguration",{"3":{"1199":1,"1207":1}}],["designalphaentity",{"3":{"1199":3,"1207":1}}],["designing",{"3":{"1395":2,"1810":1,"2212":1,"2216":1,"2217":1}}],["designtimesqlitedbcontextfactory",{"2":{"1721":1,"1726":1,"1727":1,"1730":1},"3":{"1721":1,"1726":1,"1727":1,"1730":1}}],["designtimesqlserverdbcontextfactory",{"3":{"1209":4,"1285":6,"1358":1,"1368":1,"1395":2,"1414":3,"1495":1}}],["designtimeconnection",{"3":{"1435":1}}],["designtime",{"3":{"1285":3}}],["designtimedbcontexthelpertests",{"3":{"1199":1,"1207":10,"1285":2}}],["designtimedbcontexthelper",{"2":{"696":1,"698":1,"703":1,"707":1,"715":1,"1034":1,"1283":1},"3":{"696":2,"698":2,"703":3,"707":2,"710":1,"715":1,"905":2,"909":1,"1034":2,"1199":3,"1203":1,"1207":4,"1283":2,"1285":44,"1299":2,"1322":2,"1435":2}}],["designtimedbcontextoptions",{"2":{"0":1,"707":1,"905":1,"1283":1},"3":{"0":1,"707":2,"905":3,"907":1,"1199":3,"1203":1,"1207":2,"1283":2,"1285":14,"1495":1}}],["designpostgresqlentityconfiguration",{"3":{"1199":1,"1207":1}}],["designpostgresqlentity",{"3":{"1199":3,"1207":1}}],["designbetaentityconfiguration",{"3":{"1199":1,"1207":1}}],["designbetaentity",{"3":{"1199":3,"1207":1}}],["designed",{"3":{"139":1,"272":1,"319":1,"550":1,"611":1,"692":1,"1229":1,"1270":1,"1285":3,"1299":3,"1310":1,"1322":3,"1323":1,"1368":2,"1394":1,"1395":2,"1416":4,"1435":1,"1560":1,"1590":1,"1631":1,"1640":1,"1899":1,"1992":1,"2061":1,"2139":1,"2142":1,"2144":1,"2219":1,"2220":1}}],["designer",{"3":{"0":1,"564":1,"567":1,"1004":1,"1209":64,"1322":1,"1435":2,"1454":1,"1496":1}}],["design",{"0":{"1201":1,"1283":1,"1538":1,"1540":1,"1542":1,"1545":1,"1554":1,"1556":1,"1558":1,"1679":1},"1":{"83":1,"84":1,"85":1,"86":1,"87":1,"88":1,"1710":1,"1711":1,"1712":1,"1713":1,"1714":1,"1715":1,"1941":1,"1942":1,"1943":1,"1944":1,"1945":1,"1946":1,"1947":1,"1948":1,"1949":1},"2":{"1261":1,"1345":1,"1380":1,"1417":1},"3":{"0":10,"24":1,"27":1,"41":1,"59":1,"83":1,"99":1,"135":1,"141":1,"145":1,"159":1,"201":1,"209":1,"225":1,"230":1,"235":1,"244":1,"263":1,"267":1,"274":1,"282":1,"308":1,"311":1,"312":1,"318":1,"340":1,"350":1,"359":2,"365":1,"387":2,"399":2,"419":1,"435":1,"441":1,"451":2,"452":2,"453":1,"464":1,"465":1,"468":1,"470":1,"472":1,"490":1,"501":1,"585":1,"590":1,"607":1,"611":1,"618":1,"632":1,"633":1,"634":1,"640":1,"649":1,"650":1,"667":1,"698":2,"700":1,"703":2,"706":1,"707":5,"710":1,"727":1,"732":1,"733":1,"766":1,"792":1,"803":1,"809":1,"810":3,"811":2,"812":2,"818":1,"825":1,"827":2,"830":1,"840":1,"855":1,"867":1,"876":2,"882":1,"905":4,"907":4,"909":1,"914":1,"916":2,"926":1,"945":1,"950":1,"964":1,"966":1,"1000":1,"1020":1,"1033":2,"1034":2,"1036":1,"1043":1,"1053":1,"1059":2,"1063":1,"1074":1,"1084":1,"1107":1,"1110":1,"1175":1,"1177":1,"1182":1,"1183":1,"1193":1,"1202":1,"1203":5,"1207":8,"1212":1,"1213":1,"1214":1,"1216":6,"1218":2,"1222":1,"1225":1,"1226":1,"1228":1,"1229":19,"1230":1,"1231":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":20,"1239":2,"1240":1,"1241":1,"1245":1,"1247":15,"1248":1,"1249":1,"1252":2,"1253":1,"1259":14,"1261":1,"1263":1,"1264":1,"1266":2,"1270":27,"1271":11,"1272":1,"1273":4,"1278":1,"1281":1,"1283":4,"1285":91,"1287":1,"1299":46,"1300":5,"1301":1,"1308":20,"1309":12,"1310":62,"1311":8,"1313":1,"1316":1,"1319":1,"1321":1,"1322":27,"1323":93,"1327":1,"1332":1,"1334":1,"1336":2,"1338":22,"1339":1,"1342":1,"1345":1,"1346":1,"1349":59,"1352":1,"1355":1,"1357":1,"1358":156,"1368":2,"1369":1,"1375":1,"1376":1,"1379":35,"1380":1,"1381":1,"1388":3,"1394":51,"1395":103,"1396":1,"1397":1,"1401":31,"1402":1,"1404":1,"1405":3,"1406":1,"1407":1,"1414":39,"1415":6,"1416":29,"1417":1,"1426":3,"1428":2,"1430":2,"1435":94,"1437":1,"1446":1,"1452":1,"1454":6,"1459":2,"1466":1,"1470":1,"1472":1,"1473":1,"1477":1,"1480":1,"1487":3,"1488":1,"1490":1,"1491":1,"1492":3,"1495":4,"1498":5,"1499":1,"1523":1,"1525":8,"1529":1,"1530":1,"1531":2,"1533":3,"1534":4,"1536":1,"1537":1,"1545":1,"1550":1,"1556":1,"1558":1,"1561":2,"1563":1,"1567":1,"1571":1,"1573":1,"1575":1,"1576":9,"1580":2,"1581":5,"1582":2,"1584":5,"1585":2,"1590":6,"1594":1,"1595":1,"1596":2,"1598":3,"1599":3,"1601":1,"1629":2,"1637":2,"1638":1,"1639":1,"1640":1,"1650":1,"1651":2,"1652":1,"1663":1,"1666":1,"1673":1,"1674":1,"1683":1,"1688":2,"1696":2,"1697":1,"1710":1,"1713":1,"1718":3,"1726":2,"1748":1,"1778":1,"1783":1,"1788":1,"1795":7,"1796":1,"1799":5,"1811":1,"1815":1,"1826":2,"1828":1,"1841":1,"1845":1,"1850":1,"1851":1,"1852":2,"1853":1,"1884":2,"1895":1,"1902":1,"1910":2,"1919":2,"1922":4,"1923":1,"1928":2,"1929":1,"1932":1,"1934":1,"1941":1,"1942":1,"1946":1,"1949":1,"1952":2,"1958":1,"1961":1,"1966":1,"1967":1,"1968":1,"1969":1,"1970":1,"1971":1,"1977":1,"1980":1,"1983":1,"1999":1,"2023":1,"2033":1,"2036":1,"2047":2,"2048":1,"2051":2,"2055":1,"2065":1,"2067":1,"2070":1,"2078":3,"2079":2,"2084":1,"2085":1,"2095":1,"2097":2,"2100":1,"2105":1,"2113":2,"2115":1,"2116":1,"2126":2,"2128":1,"2130":2,"2132":1,"2141":1,"2145":1,"2149":1,"2158":1,"2161":1,"2163":1,"2164":1,"2170":1,"2171":1,"2181":1,"2184":1,"2192":1,"2194":1,"2197":1,"2202":2,"2207":1,"2211":1,"2212":2,"2216":1,"2217":1,"2218":3,"2219":2,"2225":1,"2229":1,"2231":2,"2238":3}}],["desynchronize",{"3":{"609":1,"1349":1,"1358":3,"1416":1}}],["deserve",{"3":{"842":1,"1299":1,"1347":1,"1488":1,"1803":1,"1952":1,"2139":1}}],["deserves",{"3":{"290":1,"784":1,"898":1,"1285":1,"1299":1,"1358":3,"1405":1,"1816":1,"1833":1}}],["deserializing",{"3":{"1237":1,"1323":1,"1394":1,"1395":1,"1437":1}}],["deserialization",{"3":{"255":1,"1229":2,"1237":1,"1259":1,"1285":1,"1310":1,"1323":2,"1341":1,"1343":1,"1349":1,"1357":1,"1358":2,"1395":2,"1435":1}}],["deserializecommand",{"3":{"1285":1}}],["deserializer",{"3":{"1223":1,"1229":1,"1342":1,"1349":1}}],["deserializeevent",{"2":{"848":1,"849":1,"850":1},"3":{"848":1,"849":1,"850":1,"1259":2}}],["deserializes",{"3":{"109":1,"1042":1,"1227":1,"1229":2,"1237":1,"1249":1,"1259":3,"1285":1,"1299":3,"1308":1,"1310":1,"1323":2,"1349":1,"1358":5,"1364":1,"1365":1,"1368":4,"1395":3,"1414":1,"1415":1,"1416":5,"1435":13,"1886":1,"1945":1,"2185":1}}],["deserialize",{"2":{"1365":1},"3":{"78":1,"382":1,"732":1,"845":1,"1229":1,"1237":1,"1259":3,"1299":1,"1300":4,"1323":2,"1365":2,"1368":1,"1395":6,"1414":1,"1415":3,"1416":2,"1426":3,"1437":1,"1888":1,"1948":1}}],["deserialized",{"3":{"26":1,"784":1,"1033":1,"1229":1,"1237":1,"1249":1,"1252":1,"1253":1,"1270":1,"1285":1,"1323":2,"1358":1,"1368":1,"1394":1,"1395":6,"1414":2,"1415":2,"1435":1}}],["despiteframeworkevents",{"3":{"1435":1}}],["despite",{"3":{"135":1,"208":1,"1247":1,"1259":1,"1322":3,"1349":1,"1358":1,"1395":1,"1435":3,"1442":1,"1487":1,"1495":2,"1637":1}}],["descent",{"3":{"1435":1}}],["descended",{"3":{"2009":1}}],["descendant",{"3":{"1332":1,"1379":1}}],["descendants",{"3":{"402":3,"1332":1,"1338":4,"1435":1,"1442":3,"2009":1}}],["descend",{"3":{"1308":1}}],["descends",{"3":{"373":1,"1435":1}}],["descending",{"3":{"27":1,"330":1,"549":3,"1228":1,"1229":2,"1240":1,"1247":5,"1270":2,"1285":5,"1308":3,"1310":1,"1323":2,"1349":1,"1358":5,"1395":2,"1401":4,"1414":1,"1435":3,"1531":1,"1639":1,"1987":1}}],["desc",{"3":{"1242":1,"1247":4,"1323":3,"1414":3,"1596":2,"1597":2,"1639":1}}],["descripción",{"3":{"1684":2}}],["descriptive",{"3":{"1175":1,"1214":1,"1319":1,"1358":1,"1368":1,"1401":2,"1416":1,"1629":1,"1638":1,"1763":1,"1781":1,"2040":1,"2099":1,"2105":1}}],["descriptionquality",{"3":{"1368":1}}],["descriptionqualityscore",{"3":{"1349":1,"1358":1,"1368":1}}],["descriptionmaxlength",{"2":{"1343":1,"1352":1},"3":{"1343":1,"1349":9,"1352":1,"1358":10,"1368":2,"1383":1,"1394":2}}],["descriptions",{"3":{"448":2,"677":1,"839":1,"1310":2,"1416":1,"1636":1}}],["description",{"2":{"528":1,"1017":1,"1684":2},"3":{"0":1,"43":1,"57":1,"96":1,"115":1,"244":1,"245":1,"360":3,"425":1,"428":1,"466":1,"512":1,"528":1,"609":1,"626":1,"633":1,"684":1,"790":1,"792":1,"837":1,"839":2,"921":1,"1017":2,"1184":1,"1247":1,"1270":1,"1285":1,"1299":5,"1310":5,"1323":4,"1338":5,"1349":16,"1358":54,"1365":1,"1368":7,"1392":1,"1394":11,"1395":1,"1416":11,"1426":3,"1434":1,"1435":4,"1466":6,"1473":1,"1475":1,"1487":1,"1495":3,"1499":1,"1629":7,"1630":1,"1634":3,"1640":6,"1660":1,"1681":1,"1684":6,"1686":2,"1696":1,"1726":4,"1727":1,"1728":1,"1745":2,"1747":2,"1749":1,"1763":26,"1766":6,"1767":3,"1768":1,"1770":1,"1772":1,"1774":1,"2088":1,"2096":1,"2145":1,"2157":1,"2165":1}}],["descriptors",{"3":{"12":1,"448":1,"583":1,"651":1,"1018":1,"1236":1,"1309":12,"1314":1,"1322":4,"1323":1,"1338":3,"1349":4,"1358":10,"1367":1,"1368":1,"1416":1,"1420":1,"1423":1,"1426":2,"1435":2,"1436":1,"1525":1,"2174":1}}],["descriptor",{"3":{"11":1,"122":2,"448":1,"649":3,"650":1,"1230":1,"1236":1,"1247":1,"1262":1,"1299":2,"1308":2,"1309":26,"1310":2,"1315":1,"1322":8,"1323":11,"1349":7,"1358":46,"1377":1,"1379":1,"1394":6,"1395":8,"1401":8,"1403":1,"1411":1,"1413":2,"1414":8,"1415":2,"1416":1,"1420":2,"1426":4,"1435":4,"1665":1,"1668":1,"1864":1,"2130":1,"2132":1}}],["describable",{"3":{"1416":1}}],["describing",{"3":{"359":1,"360":1,"497":1,"518":1,"534":1,"545":1,"551":1,"638":1,"684":1,"691":1,"944":1,"1100":1,"1229":1,"1237":1,"1247":1,"1277":1,"1282":1,"1285":8,"1299":3,"1308":4,"1309":1,"1310":4,"1322":4,"1323":1,"1338":2,"1349":4,"1358":3,"1379":1,"1394":2,"1395":3,"1401":1,"1414":1,"1416":3,"1426":2,"1435":4,"1461":1,"1630":2,"1948":1,"2047":1,"2141":1,"2145":3}}],["describeprotofile",{"3":{"1435":2}}],["describemember",{"3":{"1435":3}}],["describemismatch",{"3":{"1426":5}}],["describefailure",{"3":{"1394":2,"1426":1}}],["describeseverycorepublicresource",{"3":{"1435":1}}],["describesize",{"3":{"1394":2}}],["describes",{"3":{"0":1,"15":1,"93":1,"135":2,"139":1,"170":1,"171":1,"178":1,"201":1,"252":1,"256":1,"257":1,"258":1,"296":1,"305":1,"325":2,"335":1,"373":1,"375":1,"395":1,"400":1,"422":1,"424":1,"513":1,"522":1,"539":1,"549":1,"572":1,"578":1,"642":1,"714":1,"715":1,"736":1,"769":1,"794":1,"813":1,"833":1,"836":1,"837":1,"839":2,"870":1,"874":1,"876":1,"939":1,"980":1,"999":1,"1006":1,"1018":1,"1021":1,"1050":1,"1085":1,"1119":1,"1176":1,"1178":1,"1229":2,"1237":3,"1247":3,"1259":2,"1270":2,"1271":1,"1273":1,"1285":14,"1299":2,"1308":2,"1309":2,"1310":2,"1322":3,"1323":2,"1338":7,"1349":3,"1358":5,"1394":3,"1395":3,"1401":6,"1414":6,"1430":1,"1435":11,"1457":1,"1466":1,"1473":1,"1475":1,"1476":1,"1480":1,"1487":1,"1495":1,"1525":2,"1590":1,"1638":1,"1651":1,"1691":1,"1726":1,"1838":2,"1843":1,"1895":1,"2023":1,"2042":1,"2050":1,"2054":1,"2100":1,"2231":1}}],["describedevice",{"3":{"1323":2}}],["described",{"3":{"0":1,"20":1,"22":1,"29":1,"66":1,"95":1,"135":1,"138":2,"141":1,"142":1,"161":1,"249":1,"254":1,"258":1,"268":1,"322":1,"336":1,"340":1,"368":1,"423":1,"451":1,"463":2,"474":1,"497":1,"512":1,"534":2,"543":1,"549":1,"562":1,"566":1,"572":1,"607":1,"624":1,"638":1,"764":1,"825":1,"837":2,"864":1,"867":1,"874":1,"881":1,"980":1,"986":1,"1174":1,"1191":1,"1229":1,"1230":1,"1237":2,"1238":1,"1247":2,"1259":2,"1269":1,"1270":9,"1271":2,"1278":1,"1283":1,"1285":10,"1296":1,"1299":2,"1307":1,"1308":3,"1309":1,"1310":5,"1314":1,"1315":1,"1321":1,"1322":2,"1323":8,"1338":4,"1341":1,"1349":3,"1351":1,"1358":19,"1368":6,"1379":7,"1394":12,"1395":8,"1401":1,"1414":3,"1415":3,"1416":2,"1435":13,"1442":1,"1446":1,"1454":2,"1457":1,"1458":1,"1464":1,"1466":6,"1474":1,"1475":1,"1486":1,"1491":1,"1495":2,"1499":1,"1525":1,"1590":1,"1638":1,"1652":1,"1687":1,"1688":1,"1701":1,"1705":1,"1706":1,"1754":1,"1768":1,"1776":1,"1864":1,"1882":1,"2047":1,"2105":1,"2231":1}}],["describe",{"2":{"1266":1},"3":{"0":1,"27":1,"93":1,"130":1,"138":1,"257":1,"279":1,"306":1,"326":1,"360":1,"375":1,"395":1,"421":1,"497":1,"506":1,"552":1,"685":1,"688":1,"751":1,"820":1,"825":1,"861":1,"863":1,"876":1,"912":1,"1089":1,"1091":1,"1123":1,"1230":1,"1237":1,"1244":1,"1247":1,"1259":2,"1260":1,"1266":1,"1270":7,"1274":1,"1277":1,"1284":1,"1285":8,"1299":11,"1300":2,"1308":1,"1310":1,"1338":2,"1358":4,"1379":1,"1394":1,"1395":1,"1414":1,"1416":6,"1425":1,"1426":1,"1435":5,"1488":1,"1570":1,"1600":1,"1638":1,"1696":2,"1700":1,"1722":1,"1728":1,"1814":1,"1867":1,"2175":1,"2181":1}}],["destruction",{"3":{"1415":1,"1809":1}}],["destructive",{"3":{"0":1,"293":1,"565":1,"566":3,"759":1,"907":1,"1237":1,"1285":1,"1299":1,"1323":5,"1414":1,"1435":1,"1454":1,"1525":1,"1560":1,"1569":1,"1589":1,"1590":1,"2063":1}}],["destructured",{"3":{"1323":1}}],["destructures",{"3":{"1299":1,"1358":1,"1394":3}}],["destructuring",{"3":{"1237":1,"1394":1,"2062":1}}],["destroyed",{"3":{"1358":1,"1435":4}}],["destroying",{"3":{"1299":1,"1415":1}}],["destroys",{"3":{"853":1,"1236":1,"1275":1,"1285":1,"1358":1,"1394":1,"1414":1,"1415":1,"1455":1,"2063":1}}],["destroy",{"3":{"790":1,"1259":1,"1299":1,"1322":2,"1466":1,"2165":1}}],["destinationheader",{"3":{"1435":1}}],["destinationtype",{"3":{"1299":1}}],["destinations",{"3":{"420":1,"810":1,"837":1,"838":1,"1144":1,"1338":2,"1394":1,"1416":2,"1435":1}}],["destination",{"3":{"0":7,"57":1,"825":1,"827":9,"829":4,"833":2,"837":2,"838":2,"839":3,"842":1,"1212":1,"1323":3,"1324":1,"1337":3,"1338":15,"1394":1,"1415":1,"1435":4,"1437":3,"1446":6,"1464":1,"1606":1,"1740":1,"1975":1,"2023":1}}],["a85d00",{"3":{"1323":3}}],["a1b2c3d4",{"3":{"1285":1}}],["a11y",{"0":{"1557":1},"3":{"618":1,"619":1,"1216":1,"1435":7,"1437":1,"1487":1,"1489":1,"1490":1,"1492":1,"1498":1,"1525":2,"1529":1,"1531":1,"1532":1,"1533":1,"1557":1,"1564":1,"1576":2,"1585":1,"1590":1,"1594":2,"1595":1,"1596":1,"1597":1,"1604":1,"1738":1}}],["akka",{"2":{"1150":1},"3":{"1150":1}}],["aot",{"3":{"869":1,"1416":4}}],["awesomeassertions",{"2":{"960":1},"3":{"960":1,"1213":1,"1435":45,"1653":1,"2057":1}}],["awk",{"3":{"564":1,"1454":2}}],["awkwardly",{"3":{"1815":1,"1951":1}}],["awkward",{"3":{"6":1,"506":1,"1270":1,"1394":1,"1395":1,"1435":1,"1490":1,"1640":1,"1843":1,"1954":1,"2193":1}}],["aws",{"3":{"175":1}}],["awayfromzero",{"3":{"1599":1}}],["away",{"3":{"0":1,"24":1,"160":1,"499":1,"501":1,"558":1,"683":1,"691":1,"773":1,"812":1,"863":1,"881":1,"882":1,"897":2,"903":1,"905":1,"907":1,"948":1,"965":1,"1020":1,"1107":1,"1142":1,"1162":1,"1185":1,"1237":3,"1247":1,"1259":1,"1270":2,"1285":2,"1296":1,"1299":2,"1310":2,"1311":1,"1322":3,"1323":13,"1338":1,"1358":8,"1361":1,"1368":3,"1389":1,"1394":5,"1395":11,"1408":1,"1414":2,"1416":6,"1423":1,"1430":1,"1435":20,"1443":1,"1452":1,"1454":1,"1485":1,"1487":2,"1560":1,"1650":1,"1653":1,"1662":1,"1726":1,"1736":1,"1766":1,"1818":1,"1917":1,"1973":1,"1981":1,"1986":1,"2145":1,"2212":1}}],["awardable",{"3":{"1437":1,"1526":1,"1577":1,"1591":1}}],["awardasync",{"3":{"690":1,"1395":3,"1435":3}}],["awardcount",{"3":{"1395":1}}],["awardcall",{"3":{"1199":2,"1207":1}}],["awarded",{"3":{"1395":13,"1414":2,"1437":2,"1630":4,"1638":2}}],["awarder",{"3":{"1349":1,"1395":17,"1435":3,"1486":1}}],["awardermocks",{"3":{"1199":2,"1207":1}}],["awarding",{"3":{"691":1,"1414":1,"1435":1}}],["awards",{"3":{"0":1,"1349":2,"1358":1,"1395":13,"1437":1,"1489":1,"1630":5}}],["award",{"3":{"0":1,"688":1,"690":2,"691":3,"692":5,"693":1,"782":1,"1348":1,"1349":2,"1358":6,"1395":75,"1414":2,"1435":1,"1437":2,"1486":1,"1630":6,"1638":4}}],["awareness",{"3":{"27":1,"698":1,"1308":2,"1310":1,"1415":2,"1416":3,"1553":1,"1576":1,"1634":1,"2103":1,"2188":1}}],["aware",{"3":{"0":8,"86":2,"93":1,"94":1,"100":1,"136":1,"160":1,"164":1,"166":1,"265":1,"709":1,"819":1,"883":1,"920":1,"926":3,"1034":1,"1046":1,"1191":1,"1192":2,"1196":1,"1202":1,"1203":1,"1212":2,"1241":1,"1242":2,"1247":1,"1267":1,"1270":11,"1271":1,"1281":1,"1285":17,"1288":1,"1296":1,"1299":4,"1308":1,"1310":4,"1314":1,"1322":4,"1323":7,"1330":1,"1338":3,"1349":6,"1353":1,"1355":1,"1358":6,"1379":3,"1393":1,"1394":6,"1395":8,"1415":1,"1416":3,"1437":3,"1443":1,"1445":1,"1460":1,"1466":2,"1495":1,"1499":2,"1525":2,"1533":2,"1561":1,"1563":1,"1576":1,"1585":1,"1590":3,"1595":1,"1599":2,"1659":2,"1665":1,"1669":1,"1670":1,"1672":1,"1814":1,"1855":1,"1856":1,"1861":1,"1881":1,"1882":1,"2027":1,"2054":1}}],["awaitable",{"3":{"135":2,"1416":1,"1435":4}}],["awaiting",{"3":{"24":1,"514":1,"707":1,"809":1,"898":1,"1220":1,"1229":2,"1270":1,"1285":3,"1299":2,"1300":3,"1310":1,"1322":2,"1323":5,"1333":1,"1394":1,"1395":3,"1401":3,"1415":1,"1416":4,"1424":1,"1435":2,"1804":1,"1945":1}}],["awaited",{"3":{"0":2,"235":1,"383":1,"435":1,"436":1,"854":1,"896":1,"897":1,"901":1,"1067":1,"1229":2,"1247":1,"1259":2,"1270":3,"1285":1,"1309":2,"1310":1,"1322":1,"1323":6,"1371":1,"1379":3,"1382":1,"1394":7,"1395":2,"1401":2,"1414":1,"1416":2,"1429":2,"1448":1,"1769":1,"1946":1,"2055":2}}],["await",{"2":{"488":1,"494":1,"495":1,"1953":1},"3":{"0":3,"109":1,"481":1,"483":1,"484":1,"486":4,"487":1,"488":1,"489":2,"490":4,"491":5,"492":5,"493":6,"494":6,"495":3,"519":1,"743":1,"996":2,"1101":1,"1212":1,"1220":1,"1229":4,"1247":3,"1259":5,"1263":1,"1270":5,"1274":1,"1285":1,"1299":2,"1300":1,"1308":2,"1310":9,"1311":1,"1322":5,"1323":18,"1327":1,"1338":3,"1358":15,"1368":1,"1379":2,"1382":1,"1394":27,"1395":13,"1399":1,"1401":4,"1414":7,"1416":5,"1424":1,"1426":1,"1435":16,"1437":1,"1804":1,"1945":1,"1953":1,"2133":1,"2176":1,"2231":2}}],["awaits",{"3":{"0":1,"109":1,"230":1,"295":1,"481":1,"482":1,"486":1,"490":1,"491":2,"492":2,"493":1,"494":1,"522":3,"523":4,"524":2,"897":1,"926":1,"988":1,"1100":2,"1212":2,"1220":1,"1229":2,"1246":1,"1247":2,"1259":6,"1263":1,"1270":9,"1285":2,"1299":3,"1300":1,"1308":11,"1309":2,"1310":6,"1322":3,"1323":10,"1338":2,"1358":5,"1368":1,"1377":1,"1379":4,"1394":23,"1395":10,"1398":1,"1401":3,"1414":4,"1415":2,"1416":9,"1426":1,"1435":6,"1804":1,"1835":1,"1918":1,"1945":1,"2055":1}}],["afraid",{"3":{"1852":1}}],["afford",{"3":{"1058":1,"1299":1,"1322":1,"1323":1,"1454":1,"1949":2}}],["affordances",{"3":{"1323":4,"1368":1,"1394":3,"1414":1,"1416":6,"1435":2,"1558":1,"1760":2,"2125":1}}],["affordance",{"3":{"109":1,"413":1,"439":1,"440":1,"681":1,"682":1,"685":1,"1285":1,"1299":1,"1322":1,"1323":10,"1349":1,"1358":2,"1368":1,"1374":1,"1379":3,"1383":1,"1390":1,"1391":1,"1394":14,"1401":2,"1414":1,"1415":1,"1416":32,"1435":6,"1437":1,"1576":1,"1713":1,"1741":1,"1764":1,"1897":1,"1903":1,"2117":1,"2125":1}}],["affordable",{"3":{"0":1,"365":1,"608":1,"627":1,"1285":1,"1310":1,"1358":1,"1395":2,"1435":1}}],["affecting",{"3":{"1454":1,"1480":1,"1530":1,"1533":1}}],["affects",{"3":{"905":2,"1267":1,"1270":1,"1285":3,"1299":1,"1310":1,"1338":2,"1349":1,"1426":1,"1674":1,"1871":1}}],["affect",{"3":{"682":1,"1264":1,"1358":1,"1394":1,"1416":1,"1454":1,"1475":1}}],["affected",{"3":{"185":1,"387":1,"448":1,"990":1,"1021":1,"1249":1,"1259":1,"1270":1,"1285":5,"1323":1,"1338":1,"1394":1,"1400":1,"1401":6,"1435":1,"1495":3,"1630":1,"1637":1,"1734":1,"2194":1}}],["affinity",{"3":{"664":3,"996":1,"1338":1,"1466":2}}],["afternoon",{"3":{"1972":1,"2048":1,"2066":1,"2170":1}}],["aftersimulateddataloss",{"3":{"1706":1}}],["afterthought",{"3":{"528":1,"1090":1,"1323":1,"1358":1,"1395":1,"1414":1,"1416":1,"1936":1,"2140":1,"2159":1}}],["aftercommit",{"2":{"465":1,"466":1},"3":{"459":2,"465":1,"466":2,"1322":3,"1414":1}}],["afterward",{"3":{"159":1,"1259":1,"1285":1,"1322":2,"1358":1,"1401":1,"1965":1}}],["afterwards",{"3":{"0":1,"24":1,"25":1,"122":1,"198":1,"216":1,"360":1,"538":1,"549":1,"689":1,"702":2,"765":1,"954":1,"988":1,"1237":3,"1243":1,"1259":2,"1270":3,"1275":1,"1285":7,"1289":1,"1299":4,"1308":1,"1310":2,"1315":1,"1322":6,"1323":8,"1338":1,"1345":1,"1349":2,"1358":6,"1368":1,"1379":1,"1391":1,"1394":7,"1395":5,"1401":3,"1416":2,"1428":1,"1435":6,"1462":1,"1477":1,"1487":1,"1661":1,"1685":1,"1688":1,"1726":2,"1823":1,"1839":1,"2141":1,"2162":1,"2166":1,"2197":1}}],["after",{"0":{"1609":1,"1743":1,"2118":1},"2":{"1703":1},"3":{"0":22,"16":1,"17":3,"18":2,"20":2,"21":2,"24":1,"25":1,"27":2,"33":1,"68":1,"78":1,"99":1,"112":1,"115":1,"117":1,"119":1,"120":2,"122":1,"124":1,"135":2,"145":1,"157":1,"159":1,"160":1,"175":1,"182":2,"186":2,"188":1,"194":1,"195":2,"196":2,"197":1,"199":1,"201":6,"202":2,"220":1,"225":1,"229":1,"235":1,"248":1,"255":1,"258":1,"265":2,"273":2,"279":2,"284":1,"295":2,"330":1,"341":2,"355":1,"365":1,"371":1,"374":1,"382":1,"383":2,"384":1,"388":1,"391":1,"392":1,"412":1,"413":2,"418":1,"424":1,"433":1,"434":2,"442":1,"444":1,"451":1,"452":2,"459":4,"463":2,"464":2,"465":1,"468":2,"494":1,"499":2,"507":1,"514":1,"517":1,"518":2,"522":1,"524":1,"528":1,"530":1,"536":3,"537":2,"538":2,"539":3,"541":1,"543":1,"547":1,"549":1,"550":2,"556":1,"558":1,"565":1,"566":1,"572":1,"583":1,"584":1,"587":1,"590":1,"595":1,"609":2,"612":1,"624":1,"626":2,"628":1,"638":2,"639":1,"640":1,"649":1,"665":2,"672":2,"674":1,"675":1,"681":1,"682":1,"686":1,"692":1,"693":1,"696":1,"698":2,"703":1,"707":2,"714":1,"715":1,"716":1,"741":3,"748":1,"749":5,"751":2,"753":2,"756":1,"757":3,"758":1,"767":1,"774":1,"781":1,"782":1,"785":1,"791":1,"793":1,"794":1,"812":1,"813":1,"818":1,"821":1,"827":1,"828":1,"844":1,"846":1,"849":1,"857":2,"860":1,"861":2,"865":1,"874":1,"881":3,"889":1,"891":1,"897":2,"900":1,"904":2,"908":2,"909":1,"916":1,"922":1,"924":1,"953":1,"954":2,"956":1,"960":1,"966":1,"988":8,"989":1,"990":1,"991":2,"998":2,"1006":1,"1018":1,"1028":1,"1029":1,"1034":1,"1041":1,"1042":1,"1044":2,"1050":1,"1051":1,"1052":1,"1059":1,"1069":1,"1083":1,"1086":1,"1091":1,"1093":1,"1100":1,"1116":4,"1118":1,"1124":2,"1125":1,"1129":1,"1133":1,"1134":1,"1140":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":4,"1191":1,"1195":1,"1200":1,"1212":4,"1215":1,"1228":1,"1229":8,"1232":1,"1233":2,"1237":9,"1239":1,"1241":2,"1244":1,"1247":15,"1249":1,"1251":1,"1252":2,"1254":4,"1258":2,"1259":38,"1262":2,"1263":3,"1264":2,"1265":1,"1269":3,"1270":31,"1271":3,"1274":2,"1275":1,"1278":2,"1279":1,"1280":3,"1283":1,"1285":65,"1287":1,"1291":1,"1292":2,"1293":1,"1296":1,"1297":1,"1299":45,"1300":7,"1307":1,"1308":15,"1309":5,"1310":38,"1311":3,"1314":2,"1315":2,"1316":1,"1317":1,"1319":1,"1322":52,"1323":75,"1331":1,"1333":1,"1334":1,"1337":1,"1338":27,"1341":1,"1342":1,"1346":1,"1349":21,"1351":1,"1353":1,"1354":2,"1356":1,"1357":1,"1358":120,"1363":3,"1368":11,"1374":1,"1377":1,"1378":1,"1379":17,"1391":1,"1394":38,"1395":36,"1398":3,"1401":26,"1404":1,"1405":3,"1406":1,"1407":1,"1409":1,"1412":1,"1414":38,"1415":18,"1416":40,"1421":1,"1422":1,"1424":3,"1426":5,"1428":1,"1429":1,"1431":2,"1432":1,"1435":54,"1437":10,"1440":2,"1444":2,"1446":2,"1450":1,"1453":2,"1454":10,"1455":2,"1456":3,"1459":1,"1464":3,"1465":1,"1466":10,"1471":1,"1472":1,"1473":2,"1474":2,"1477":2,"1479":1,"1480":1,"1481":1,"1485":1,"1486":1,"1487":8,"1488":1,"1489":2,"1490":1,"1491":3,"1495":13,"1507":2,"1525":1,"1533":1,"1546":1,"1553":1,"1557":1,"1565":1,"1567":2,"1571":1,"1629":2,"1630":7,"1631":2,"1633":1,"1634":4,"1636":1,"1637":2,"1638":11,"1640":4,"1642":1,"1643":1,"1647":1,"1650":6,"1653":1,"1654":1,"1656":1,"1661":1,"1664":4,"1666":2,"1667":1,"1672":1,"1674":1,"1676":1,"1678":1,"1684":3,"1685":2,"1689":4,"1691":1,"1700":1,"1701":1,"1703":1,"1704":1,"1706":2,"1709":1,"1715":1,"1726":5,"1728":1,"1729":1,"1748":1,"1749":1,"1754":1,"1764":2,"1766":6,"1768":1,"1772":1,"1805":1,"1813":1,"1817":1,"1818":2,"1820":1,"1821":1,"1823":1,"1825":1,"1826":1,"1838":3,"1839":3,"1840":1,"1842":2,"1844":1,"1845":1,"1850":2,"1851":1,"1865":1,"1870":1,"1872":1,"1873":1,"1880":1,"1882":1,"1885":1,"1888":2,"1891":1,"1899":1,"1902":1,"1908":3,"1910":2,"1911":1,"1917":2,"1922":1,"1923":1,"1932":1,"1935":1,"1943":2,"1944":2,"1945":1,"1947":2,"1948":1,"1949":1,"1961":1,"1963":1,"1965":1,"1967":2,"1981":2,"1997":1,"1999":3,"2000":1,"2011":1,"2018":3,"2037":1,"2050":2,"2057":1,"2062":1,"2064":1,"2065":2,"2071":2,"2072":1,"2077":1,"2084":1,"2085":1,"2086":1,"2089":1,"2093":1,"2094":2,"2097":1,"2112":1,"2118":2,"2120":1,"2122":2,"2127":1,"2130":1,"2142":1,"2160":1,"2162":4,"2163":1,"2165":1,"2167":2,"2168":3,"2173":1,"2176":1,"2178":1,"2180":1,"2184":1,"2188":1,"2190":1,"2191":2,"2192":1,"2195":1,"2198":2,"2199":2,"2202":1,"2231":1}}],["avg",{"3":{"1466":1,"1763":1,"1766":1}}],["averse",{"3":{"1237":1}}],["averaged",{"3":{"1349":1,"1466":1}}],["averagerating",{"3":{"1349":1,"1358":1,"1763":1}}],["averages",{"3":{"875":1,"1349":2,"1394":1}}],["average",{"3":{"608":1,"609":1,"1323":1,"1349":3,"1358":1,"1394":2,"1454":1,"1466":4,"1473":2,"1491":1,"1763":1,"1764":2,"2157":1}}],["averaging",{"3":{"608":1,"609":1,"1466":1,"2157":2}}],["av0030",{"3":{"454":1}}],["av0024",{"3":{"448":1,"1310":1,"2130":1}}],["av0011",{"3":{"448":1,"1310":1,"2130":1}}],["avoidance",{"0":{"1327":1},"3":{"1309":1,"1358":2,"1395":2,"1634":1}}],["avoidable",{"3":{"1247":1,"1299":1,"1322":2,"1323":4,"1358":2,"1394":2,"1416":1}}],["avoiding",{"3":{"1041":1,"1308":1,"1310":2,"1338":1,"1358":3,"1394":1,"1395":1,"1401":1,"1416":1,"1435":1,"1440":1,"1446":1,"1452":1,"1629":1,"2182":1}}],["avoids",{"3":{"182":1,"209":1,"226":1,"266":1,"360":1,"508":1,"529":1,"547":1,"666":1,"741":1,"935":1,"1069":1,"1212":1,"1220":1,"1237":4,"1247":1,"1252":1,"1259":3,"1270":7,"1285":4,"1299":4,"1300":2,"1308":1,"1310":4,"1322":1,"1323":8,"1328":1,"1338":5,"1349":5,"1358":15,"1368":1,"1394":6,"1395":3,"1401":6,"1414":3,"1415":1,"1416":7,"1426":1,"1435":5,"1442":2,"1449":1,"1453":1,"1487":1,"1489":1,"1491":1,"1525":2,"1638":2,"1676":1,"1683":1,"1804":1,"2008":1}}],["avoid",{"3":{"3":1,"11":1,"55":1,"151":1,"272":1,"381":1,"415":1,"458":1,"460":1,"549":1,"556":1,"558":1,"559":1,"592":1,"792":1,"848":1,"924":1,"1005":1,"1229":1,"1237":1,"1244":1,"1247":3,"1248":1,"1270":2,"1285":9,"1299":7,"1306":1,"1308":1,"1310":1,"1322":1,"1323":2,"1338":1,"1349":1,"1358":2,"1379":1,"1395":3,"1401":6,"1414":1,"1415":1,"1416":1,"1426":1,"1455":1,"1466":1,"1475":1,"1525":1,"1555":1,"1559":1,"1564":1,"1610":1,"1638":2,"1639":1,"1760":1,"1791":1,"1863":1,"1953":1,"1957":1,"1958":1,"1980":1,"1988":1,"2012":1,"2034":1,"2049":1}}],["avoided",{"3":{"0":1,"472":1,"549":1,"691":1,"800":2,"941":1,"1237":2,"1270":1,"1285":1,"1323":3,"1358":1,"1368":1,"1394":1,"1395":2,"1401":1,"1415":1,"1416":1,"1814":1}}],["avatarblobname",{"3":{"1414":1}}],["avatarurl",{"2":{"2062":1},"3":{"1237":2,"1414":15,"1630":1,"2062":1}}],["avatar",{"2":{"1410":1},"3":{"0":3,"403":1,"438":3,"439":1,"440":3,"441":2,"443":2,"444":3,"459":1,"465":1,"466":2,"599":1,"665":1,"666":1,"725":1,"926":3,"1114":1,"1115":2,"1118":1,"1120":1,"1192":1,"1265":1,"1270":3,"1285":7,"1322":4,"1394":2,"1402":1,"1404":1,"1405":1,"1409":1,"1410":4,"1413":2,"1414":76,"1416":10,"1435":2,"1437":1,"1466":7,"1471":1,"1473":1,"1480":3,"1490":1,"1495":2,"1630":4,"1639":4,"1747":1,"2062":1,"2115":1,"2123":2,"2124":1,"2125":7,"2126":5,"2127":2}}],["avatarsize",{"3":{"1414":2}}],["avatars",{"0":{"1410":1},"1":{"437":1,"438":1,"439":1,"440":1,"441":1,"442":1,"443":1,"444":1},"3":{"0":2,"437":1,"440":2,"441":1,"443":3,"598":1,"665":1,"1116":2,"1118":1,"1212":1,"1285":2,"1317":1,"1394":2,"1414":2,"1416":3,"1466":7,"1495":2,"1630":2,"1635":1,"1639":1,"1764":1,"2125":4,"2126":2,"2127":1,"2231":2}}],["availability",{"3":{"0":3,"92":1,"236":2,"237":1,"283":1,"353":1,"402":1,"461":1,"530":1,"607":1,"609":6,"611":2,"681":1,"768":1,"793":1,"798":1,"914":1,"1187":1,"1299":2,"1323":5,"1331":1,"1333":1,"1338":6,"1394":1,"1401":1,"1415":2,"1416":5,"1435":1,"1442":1,"1454":1,"1466":11,"1468":1,"1473":2,"1475":1,"1629":1,"1638":1,"1671":1,"1676":1,"1706":1,"1707":1,"1708":1,"1775":1,"1910":1,"2000":1,"2009":1,"2066":1,"2121":1,"2157":1}}],["availabledestinationspolicy",{"3":{"1338":1}}],["available=false",{"3":{"1322":1}}],["availablequantity",{"2":{"1590":1,"1748":1},"3":{"1285":1,"1590":1,"1748":1,"1763":2}}],["available",{"2":{"727":1,"1298":1,"2066":1},"3":{"0":4,"7":1,"62":1,"111":1,"137":1,"159":1,"265":2,"280":1,"300":3,"303":1,"306":2,"361":1,"506":1,"527":1,"528":2,"537":1,"549":1,"556":1,"681":1,"691":1,"706":1,"725":3,"727":1,"741":1,"800":1,"818":1,"827":1,"840":2,"853":1,"914":1,"972":1,"1074":1,"1116":1,"1187":1,"1212":1,"1229":3,"1237":1,"1271":1,"1285":10,"1298":1,"1299":9,"1300":5,"1308":3,"1310":5,"1315":1,"1321":1,"1322":8,"1323":15,"1326":1,"1338":3,"1349":2,"1358":4,"1368":3,"1379":1,"1391":1,"1394":5,"1395":10,"1409":1,"1414":5,"1416":15,"1426":1,"1435":5,"1439":1,"1440":1,"1455":1,"1458":1,"1466":1,"1473":1,"1479":1,"1485":1,"1487":1,"1489":1,"1569":1,"1574":1,"1576":2,"1625":1,"1629":4,"1630":4,"1631":1,"1638":3,"1639":1,"1661":1,"1763":1,"1764":1,"1766":2,"1768":4,"1775":2,"1798":1,"1853":1,"1904":1,"1911":1,"1915":1,"2066":1,"2136":1,"2142":1,"2233":1}}],["azd",{"0":{"1463":1},"3":{"1463":8}}],["az",{"3":{"0":2,"563":1,"757":2,"759":1,"764":1,"766":3,"767":1,"768":2,"1116":1,"1119":1,"1212":1,"1440":1,"1444":1,"1454":2,"1464":2,"1466":5,"1469":1,"1471":5,"1472":1,"1473":7,"1474":6,"1475":1,"1477":5,"1480":2,"1481":1,"1483":2,"1576":2,"1581":1,"1582":1,"2033":3,"2035":1,"2223":4}}],["azurite",{"3":{"0":1,"440":1,"1322":3,"2125":1}}],["azuremessaging",{"3":{"1584":1}}],["azuremonitor",{"2":{"914":1},"3":{"914":1}}],["azureadtokenexchange",{"3":{"1471":1}}],["azureadonlyauthentication",{"2":{"599":1,"1476":1,"1533":1},"3":{"0":1,"599":1,"1466":1,"1476":1,"1525":2,"1533":1}}],["azurecosmosdbdatabaseresource",{"2":{"1440":1},"3":{"1338":1,"1440":1}}],["azureclientidenv",{"2":{"603":1},"3":{"603":1}}],["azurekeyvaultconfigurationoptions",{"3":{"1338":2}}],["azurenotificationhubnativepushsender",{"2":{"1312":1,"1319":1,"1931":1,"1935":1},"3":{"1199":2,"1203":1,"1207":2,"1285":1,"1312":1,"1319":1,"1322":10,"1495":2,"1931":1,"1935":1}}],["azurenotificationhubdeviceregistrartests",{"3":{"1199":1,"1207":2,"1322":1,"1437":1}}],["azurenotificationhubdeviceregistrar",{"2":{"1312":1,"1319":1,"1931":1,"1935":1},"3":{"435":1,"1199":3,"1203":1,"1207":2,"1285":1,"1312":1,"1319":1,"1322":17,"1437":1,"1931":1,"1935":1}}],["azureservicebus",{"2":{"1841":1},"3":{"640":1,"1319":1,"1322":1,"1445":1,"1466":2,"1841":1}}],["azureservicebustransport",{"2":{"150":1},"3":{"150":1}}],["azureblobfilestorageservice",{"2":{"443":1,"1119":1,"1312":1,"1319":1,"2125":1},"3":{"443":2,"1116":2,"1119":2,"1199":2,"1203":1,"1207":2,"1285":2,"1312":1,"1319":1,"1322":13,"1495":1,"2125":2}}],["azure",{"0":{"1463":1,"1471":2},"1":{"1461":1,"1462":1,"1463":1,"1464":1,"1465":1,"1466":1,"1467":1,"1468":1,"1469":1,"2153":1,"2154":1,"2155":1,"2156":1,"2157":1,"2158":1,"2159":1},"2":{"145":1,"147":1,"149":1,"150":1,"152":1,"599":1,"601":1,"603":1,"665":1,"667":1,"669":1,"766":1,"810":1,"1074":1,"1075":2,"1077":1,"1078":1,"1338":2,"1461":1,"1462":1,"1463":1,"1467":3,"1469":2,"1471":2,"1474":1,"1476":1,"2177":1},"3":{"0":15,"31":1,"47":1,"59":1,"61":1,"62":1,"91":1,"97":1,"98":1,"145":4,"147":2,"149":1,"150":3,"152":1,"159":1,"175":1,"201":1,"234":2,"245":1,"259":1,"291":1,"359":1,"390":1,"395":1,"397":2,"401":1,"402":1,"422":1,"434":2,"439":1,"440":1,"517":1,"599":4,"601":1,"603":1,"609":1,"611":2,"624":1,"625":1,"626":2,"627":1,"638":1,"639":1,"640":5,"641":2,"642":1,"665":9,"666":2,"667":2,"669":1,"756":2,"757":1,"765":1,"766":2,"768":2,"790":1,"810":2,"818":1,"819":1,"821":1,"823":1,"826":1,"827":1,"838":1,"868":1,"913":1,"914":1,"972":3,"973":1,"974":2,"975":1,"976":1,"1020":1,"1033":1,"1043":1,"1068":1,"1074":1,"1075":4,"1077":1,"1078":1,"1091":1,"1101":1,"1192":1,"1202":1,"1203":1,"1211":1,"1212":6,"1213":5,"1257":1,"1259":3,"1284":1,"1285":4,"1301":1,"1308":1,"1310":4,"1312":1,"1317":1,"1319":1,"1322":32,"1324":1,"1326":1,"1331":1,"1332":4,"1334":4,"1335":1,"1338":48,"1358":1,"1368":1,"1378":1,"1435":11,"1436":1,"1437":3,"1439":1,"1440":4,"1441":8,"1442":7,"1443":2,"1445":8,"1446":2,"1449":2,"1450":1,"1454":20,"1456":3,"1457":2,"1459":6,"1460":1,"1461":4,"1462":3,"1463":10,"1464":5,"1466":36,"1467":3,"1469":3,"1470":2,"1471":29,"1472":3,"1473":6,"1474":10,"1475":2,"1476":1,"1481":1,"1482":3,"1483":3,"1486":1,"1487":1,"1488":2,"1489":1,"1495":1,"1525":2,"1533":1,"1569":1,"1574":1,"1576":3,"1581":1,"1584":1,"1588":1,"1590":1,"1595":1,"1596":1,"1662":2,"1664":1,"1667":2,"1668":1,"1669":3,"1672":1,"1676":1,"1678":1,"1706":2,"1720":1,"1751":1,"1778":1,"1784":1,"1789":1,"1791":1,"1841":2,"1842":1,"1935":2,"1969":2,"2005":1,"2007":2,"2009":3,"2010":1,"2012":2,"2013":1,"2019":1,"2024":2,"2032":1,"2033":1,"2108":1,"2109":1,"2112":1,"2122":1,"2123":1,"2125":1,"2127":1,"2141":1,"2152":2,"2153":3,"2155":2,"2156":1,"2157":3,"2159":3,"2168":1,"2177":1,"2208":1,"2211":1,"2212":2,"2213":1,"2214":1,"2216":1,"2217":1,"2218":2,"2219":4,"2220":2,"2223":6,"2227":1,"2228":1,"2231":5,"2238":1,"2239":1,"2240":2,"2242":1,"2243":1}}],["a+aa",{"3":{"0":1}}],["aab",{"3":{"1480":1}}],["aad",{"2":{"1469":2,"1476":2},"3":{"1454":2,"1469":2,"1471":1,"1476":6,"1590":1}}],["aadsts700213",{"3":{"1454":1,"1466":1}}],["aasa",{"3":{"1212":1,"1480":1}}],["aa8297fb",{"3":{"1174":1}}],["aaaaaaaab9e=",{"3":{"1299":1,"1394":1}}],["aaa",{"3":{"620":1,"1713":1}}],["aa",{"0":{"1642":1},"1":{"615":1,"616":1,"617":1,"618":1,"619":1,"620":1,"621":1,"622":1},"3":{"0":1,"615":1,"617":1,"618":3,"619":1,"651":1,"865":1,"906":1,"953":1,"1212":1,"1213":1,"1323":7,"1395":1,"1413":1,"1432":2,"1435":4,"1437":2,"1460":1,"1486":1,"1487":4,"1489":1,"1493":1,"1525":1,"1530":1,"1534":1,"1557":2,"1576":8,"1590":2,"1595":1,"1604":1,"1641":1,"1643":1,"1644":1,"1668":1,"1670":1,"1673":1,"1713":1,"1715":1,"1738":1,"2054":1,"2056":4,"2059":1,"2074":1,"2078":2,"2212":1,"2220":1,"2231":1}}],["aieval",{"3":{"1491":1}}],["aiapikey",{"3":{"1454":1,"1465":1,"1466":3,"1469":1}}],["aitool",{"3":{"1426":5}}],["aitelemetryexporttests",{"3":{"1199":1,"1207":2,"1434":3}}],["aitelemetryname",{"2":{"408":1},"3":{"408":1,"1332":1,"1338":4}}],["aireadydotnet",{"3":{"1394":1}}],["airtight",{"3":{"1221":1,"1291":1,"1358":1}}],["aijsonutilities",{"2":{"1091":1,"1426":1},"3":{"1091":1,"1426":2}}],["aiproviderselectiontests",{"3":{"1199":1,"1207":3,"1426":1,"1486":1}}],["aiprovider",{"3":{"1089":1,"1094":1,"1426":1,"1495":2}}],["aiprovidervalidator",{"2":{"676":1},"3":{"676":2,"677":1,"1091":4,"1093":1,"1199":2,"1203":1,"1207":2,"1419":2,"1420":1,"1426":22}}],["aiservicecollectionextensions",{"2":{"1426":1},"3":{"1199":1,"1203":1,"1207":1,"1420":1,"1426":12,"1495":2}}],["aiservicecollectionextensionstests",{"3":{"1199":1,"1207":2,"1426":1,"1486":1}}],["aisettingstests",{"3":{"1199":1,"1207":2,"1486":1}}],["aisettings",{"2":{"1095":1,"1096":1,"1418":1,"2172":1},"3":{"674":1,"676":3,"1091":5,"1095":2,"1096":1,"1199":15,"1203":1,"1207":2,"1418":8,"1419":1,"1426":40,"1495":2,"1525":2,"1576":1,"1584":1,"2172":2}}],["aiscoring",{"3":{"1358":1}}],["aiscoringservice",{"3":{"1349":2,"1358":1}}],["aiscoringtokenceiling",{"2":{"609":1,"1468":1},"3":{"609":1,"1018":1,"1466":3,"1468":1}}],["aiscorelookup",{"3":{"1394":4}}],["aiscores",{"3":{"1349":3,"1394":4}}],["aiscoreresponse",{"3":{"1018":1,"1021":1,"1199":3,"1203":1,"1207":2,"1365":5,"1368":10,"1525":2}}],["aids",{"3":{"1629":1}}],["aidependencyisolationtests",{"3":{"1199":2,"1207":4,"1426":3,"1435":13,"1525":2,"1576":2}}],["aidependencyisolationtestsbase",{"2":{"1091":1,"1096":1,"1434":1,"2177":1,"2179":1},"3":{"0":1,"1091":2,"1096":1,"1199":3,"1207":2,"1338":1,"1430":3,"1434":1,"1435":9,"2177":2,"2179":1}}],["aid",{"3":{"265":1,"1109":1,"1110":1,"1863":1}}],["aiming",{"3":{"1473":1}}],["aimed",{"0":{"2045":1},"3":{"997":1,"1093":1,"1368":2,"1395":1,"1423":1,"1435":1,"1449":1,"2043":1,"2161":1}}],["aim",{"3":{"91":1,"1134":1,"1980":1,"2047":1}}],["aiusagemeter",{"2":{"408":1,"2176":1,"2179":1},"3":{"0":1,"404":1,"408":3,"1089":1,"1091":2,"1199":7,"1203":1,"1207":2,"1417":1,"1424":3,"1426":26,"1495":3,"1525":1,"1576":1,"2176":3,"2179":1}}],["ai",{"0":{"408":1,"1346":1,"1358":1,"1365":1,"1434":1,"1552":1},"1":{"1015":1,"1016":1,"1017":1,"1018":1,"1019":1,"1020":1,"1021":1,"1022":1,"1088":1,"1089":1,"1090":1,"1091":1,"1092":1,"1093":1,"1094":1,"1095":1,"1096":1,"1417":1,"1418":1,"1419":1,"1420":1,"1421":1,"1422":1,"1423":1,"1424":1,"1425":1,"1426":1},"2":{"141":1,"142":3,"395":1,"404":1,"406":1,"408":1,"1012":1,"1016":1,"1017":1,"1020":1,"1089":4,"1090":2,"1091":2,"1093":1,"1211":2,"1213":3,"1365":1,"1417":4,"1424":5,"1426":3,"1502":4,"1526":1,"1577":1,"2005":1,"2171":1,"2176":6,"2177":3,"2179":2},"3":{"0":22,"74":4,"135":1,"141":1,"142":4,"395":3,"404":3,"406":1,"408":5,"517":1,"607":3,"609":8,"624":2,"626":3,"672":1,"674":2,"676":5,"755":1,"756":2,"764":1,"766":1,"980":7,"983":2,"994":1,"995":1,"996":1,"1011":1,"1012":5,"1013":1,"1015":1,"1016":3,"1017":7,"1018":16,"1019":1,"1020":2,"1021":1,"1022":1,"1088":1,"1089":12,"1090":6,"1091":46,"1092":1,"1093":6,"1094":3,"1095":1,"1096":1,"1099":1,"1108":2,"1111":1,"1112":1,"1192":3,"1195":1,"1198":2,"1199":69,"1202":3,"1203":36,"1206":5,"1207":208,"1208":8,"1211":6,"1212":4,"1213":5,"1216":1,"1270":3,"1285":1,"1332":5,"1338":9,"1339":1,"1341":1,"1346":2,"1349":14,"1350":1,"1353":1,"1358":29,"1359":3,"1361":1,"1363":3,"1364":1,"1365":11,"1366":1,"1367":5,"1368":26,"1375":1,"1379":1,"1389":2,"1394":18,"1416":1,"1417":7,"1418":5,"1419":6,"1420":3,"1421":5,"1422":5,"1423":6,"1424":13,"1425":1,"1426":177,"1427":2,"1430":3,"1434":11,"1435":16,"1436":9,"1437":5,"1441":1,"1454":14,"1458":1,"1460":2,"1462":4,"1465":3,"1466":22,"1468":2,"1469":1,"1484":1,"1485":4,"1486":7,"1487":1,"1488":1,"1491":9,"1492":1,"1495":20,"1498":1,"1502":5,"1504":1,"1516":1,"1518":1,"1525":26,"1526":4,"1533":1,"1534":1,"1535":1,"1552":2,"1569":1,"1576":10,"1577":3,"1584":2,"1585":2,"1590":1,"1591":1,"1598":1,"1624":1,"1631":2,"1639":1,"1672":1,"1780":3,"1783":1,"1795":1,"1798":2,"1946":1,"2005":4,"2012":1,"2110":2,"2155":1,"2159":1,"2169":1,"2170":1,"2171":2,"2172":2,"2173":3,"2176":6,"2177":3,"2178":2,"2179":5,"2189":1,"2212":3,"2213":1,"2215":1,"2217":2,"2218":1,"2219":4,"2220":2,"2223":4,"2224":1,"2226":5,"2227":1,"2231":3,"2237":1,"2238":6,"2239":2,"2242":1}}],["aesthetic",{"3":{"839":1}}],["aesgcm",{"2":{"359":1,"360":1,"2141":2},"3":{"359":3,"360":1,"1285":2,"2141":4}}],["aes",{"1":{"356":1,"357":1,"358":1,"359":1,"360":1,"361":1,"362":1,"363":1,"364":1,"365":1,"366":1,"2139":1,"2140":1,"2141":1,"2142":1,"2143":1,"2144":1,"2145":1},"3":{"0":3,"39":1,"356":1,"359":2,"360":1,"361":2,"365":1,"1212":1,"1236":1,"1237":1,"1283":2,"1285":3,"1574":1,"1576":2,"1663":1,"1671":1,"1902":2,"1904":2,"2062":1,"2138":1,"2139":2,"2141":2,"2142":1,"2144":1,"2145":2,"2152":1,"2207":1,"2231":1}}],["axecore",{"2":{"1452":1},"3":{"1213":1,"1435":2,"1487":1}}],["axerunoptions",{"2":{"618":1},"3":{"618":1,"1435":2}}],["axe",{"0":{"1642":1},"3":{"0":2,"617":2,"618":3,"620":3,"1212":1,"1213":1,"1323":1,"1413":1,"1414":1,"1432":2,"1433":3,"1435":14,"1437":5,"1460":1,"1485":1,"1486":1,"1487":5,"1489":1,"1491":2,"1523":1,"1525":6,"1530":3,"1531":1,"1534":2,"1557":1,"1564":1,"1576":11,"1584":1,"1585":1,"1588":2,"1590":3,"1595":5,"1599":2,"1604":2,"1607":1,"1641":1,"1642":2,"1643":1,"1644":2,"1659":1,"1668":1,"1670":1,"1673":1,"1715":1,"1738":2,"1741":1,"2054":1,"2056":4,"2059":1,"2078":2,"2212":1,"2220":1,"2231":1}}],["axeoptions",{"2":{"618":2,"1487":1,"2056":1},"3":{"0":1,"618":5,"620":2,"1199":17,"1207":2,"1212":1,"1427":1,"1432":6,"1435":18,"1487":7,"1492":1,"1576":1,"1590":2,"2056":2}}],["axes",{"0":{"1191":1,"1516":1,"1783":1,"1796":1,"1798":1,"1854":1},"3":{"0":1,"635":1,"697":1,"776":1,"1191":1,"1200":1,"1216":1,"1285":3,"1322":2,"1446":1,"1466":1,"1515":1,"1516":1,"1528":2,"1536":2,"1576":1,"1579":1,"1591":1,"1593":1,"1660":1,"1683":1,"1685":1,"1698":1,"1730":1,"1783":2,"1793":2,"1796":1,"1798":1,"1800":1,"1801":1,"1854":1,"2230":1}}],["axis",{"0":{"1850":1,"2230":1},"3":{"0":5,"45":2,"81":1,"164":2,"165":2,"166":2,"169":2,"317":1,"318":1,"447":3,"455":1,"582":1,"587":1,"633":1,"683":1,"697":1,"701":2,"721":1,"729":1,"743":1,"774":1,"775":1,"924":1,"1034":2,"1038":1,"1079":1,"1087":1,"1191":2,"1192":2,"1200":1,"1219":1,"1229":1,"1285":9,"1299":2,"1309":1,"1322":1,"1323":1,"1338":1,"1395":3,"1426":1,"1435":4,"1451":1,"1488":1,"1504":1,"1522":2,"1525":3,"1528":1,"1536":1,"1567":1,"1573":2,"1574":1,"1576":4,"1577":1,"1579":1,"1583":1,"1587":2,"1590":3,"1591":5,"1593":1,"1682":4,"1683":1,"1686":1,"1700":1,"1726":8,"1727":1,"1728":1,"1730":1,"1783":1,"1797":2,"1801":1,"1804":1,"1850":2,"1852":2,"1854":4,"1859":1,"1901":1,"1902":1,"1990":1,"1991":1,"1992":1,"1994":1,"1995":1,"1996":2,"2061":2,"2068":2,"2088":1,"2113":2,"2129":4,"2131":1,"2132":3,"2231":1}}],["augments",{"3":{"1338":1}}],["augment",{"3":{"1285":1}}],["augmented",{"3":{"1229":1,"2219":1}}],["august",{"3":{"804":1,"1049":1,"1051":1,"1054":1,"1074":1,"1466":1,"1473":1,"2219":1}}],["audible",{"3":{"1259":1}}],["audio",{"3":{"426":1,"1416":1}}],["audiences",{"3":{"227":1,"1308":1,"1347":1,"1379":1,"1394":4,"1395":2,"1401":1,"1934":1}}],["audience",{"2":{"2011":1},"3":{"0":2,"31":1,"86":2,"225":1,"226":1,"227":1,"303":1,"325":1,"378":1,"384":1,"386":4,"388":3,"391":2,"392":1,"434":1,"435":1,"499":2,"674":1,"820":1,"1124":1,"1202":1,"1203":1,"1299":3,"1306":1,"1308":11,"1310":6,"1322":4,"1323":10,"1347":1,"1349":7,"1358":4,"1372":1,"1378":1,"1379":19,"1392":1,"1394":27,"1395":2,"1396":1,"1398":1,"1401":2,"1414":2,"1415":1,"1435":3,"1471":1,"1487":2,"1495":1,"1575":1,"1629":1,"1636":1,"1921":3,"1932":1,"1939":1,"1981":2,"1984":1,"2011":1,"2135":1,"2137":1}}],["auditor",{"3":{"1843":1}}],["audit+coverage",{"3":{"1576":1}}],["audit+sbom+licence",{"3":{"1525":1}}],["auditactionsandgroups",{"3":{"1466":1}}],["auditableaggregaterootentity<",{"3":{"1285":1,"1368":1}}],["auditableaggregaterootentitytests",{"3":{"1199":1,"1207":4,"1237":1}}],["auditableaggregaterootentityadditionaltests",{"2":{"1237":1},"3":{"1199":1,"1207":7,"1237":3}}],["auditableaggregaterootentity",{"2":{"39":1,"43":1,"459":1,"470":1,"725":1,"1168":1,"1230":1,"1231":1,"1251":1,"1339":1,"1341":1,"1397":1,"1402":1,"1650":1,"1661":1,"1809":1,"1811":1,"1837":1,"2089":1,"2090":1,"2110":1},"3":{"0":1,"39":2,"43":2,"459":1,"470":2,"725":1,"1168":4,"1199":112,"1203":1,"1207":2,"1230":1,"1231":9,"1237":24,"1251":1,"1259":1,"1270":8,"1285":12,"1299":5,"1308":3,"1310":4,"1322":12,"1339":1,"1341":1,"1349":21,"1358":5,"1368":1,"1395":6,"1397":1,"1401":5,"1402":1,"1414":2,"1435":23,"1437":1,"1495":2,"1576":2,"1585":1,"1650":1,"1661":1,"1809":1,"1811":1,"1837":1,"2089":2,"2090":1,"2110":1}}],["auditablebaseentity<",{"3":{"1237":1}}],["auditablebaseentitytests",{"3":{"1199":1,"1207":3,"1237":1}}],["auditablebaseentityadditionaltests",{"3":{"1199":1,"1207":3,"1237":1}}],["auditablebaseentity",{"2":{"38":1,"346":1,"458":1,"470":1,"714":1,"1035":1,"1140":1,"1168":1,"1230":1,"1231":1,"1232":1,"1290":1,"1341":1,"1811":1,"1870":1,"1952":1,"2060":1,"2089":1},"3":{"0":1,"38":1,"39":3,"341":2,"346":1,"458":1,"470":2,"536":1,"714":1,"1035":1,"1140":2,"1168":2,"1199":147,"1203":1,"1207":2,"1230":1,"1231":9,"1232":1,"1237":34,"1239":1,"1247":11,"1270":3,"1285":29,"1290":1,"1299":2,"1309":4,"1310":6,"1322":3,"1341":1,"1349":15,"1358":2,"1401":2,"1414":1,"1435":18,"1437":1,"1576":1,"1650":2,"1661":1,"1809":1,"1811":1,"1814":1,"1870":1,"1952":1,"2060":1,"2089":1,"2163":1}}],["auditable",{"3":{"0":2,"41":1,"230":1,"341":4,"343":1,"345":1,"370":1,"470":1,"536":2,"628":1,"675":1,"715":1,"862":1,"1034":1,"1084":1,"1150":1,"1237":4,"1270":4,"1285":4,"1299":1,"1300":1,"1316":1,"1322":1,"1323":1,"1349":2,"1357":1,"1358":5,"1362":1,"1368":1,"1395":4,"1401":1,"1414":1,"1435":6,"1566":1,"1568":1,"1576":1,"1629":1,"1630":1,"1663":1,"1876":1,"1897":1,"2163":2,"2185":1}}],["auditevent",{"3":{"1464":1,"1466":1}}],["auditedthing",{"3":{"1199":4,"1207":1}}],["audited",{"3":{"230":1,"373":1,"441":1,"667":1,"713":1,"715":2,"716":1,"717":2,"1083":1,"1214":1,"1280":1,"1285":5,"1299":1,"1395":1,"1414":2,"1445":1,"1452":1,"1466":2,"1488":2,"1551":1,"1562":1,"1571":1,"1737":1,"1763":1,"1838":1,"1970":1,"2126":1}}],["audittrailtableenabled",{"3":{"1285":1}}],["audittrailtestharness",{"3":{"1199":4,"1207":8}}],["audittrailtestcontext",{"3":{"1198":1,"1199":5,"1207":1}}],["audittrailreadertests",{"3":{"1199":1,"1207":2,"1285":1,"1437":1}}],["audittrailreader",{"2":{"1277":1},"3":{"1199":4,"1203":1,"1207":2,"1269":1,"1270":6,"1277":2,"1285":21,"1316":1,"1322":3,"1437":1}}],["audittrailmodelgatetests",{"3":{"1191":1,"1199":1,"1207":4,"1285":4,"1495":1}}],["audittraildatabases",{"3":{"1435":2}}],["audittraildatabasenames",{"2":{"719":1},"3":{"719":1,"1435":2,"1466":1}}],["audittraildmlauditing",{"2":{"719":1},"3":{"719":1,"1466":1}}],["audittraildiagnostics",{"2":{"719":1},"3":{"719":1,"1466":1}}],["audittrailentrydtotests",{"3":{"1199":1,"1207":2}}],["audittrailentrydto",{"3":{"1199":4,"1203":1,"1207":2,"1270":1,"1285":3,"1316":1,"1322":4}}],["audittrailentry",{"2":{"720":1,"1232":1,"1277":1,"1838":1},"3":{"715":2,"720":1,"905":1,"1199":12,"1203":1,"1207":2,"1232":1,"1237":1,"1270":1,"1273":1,"1277":2,"1285":22,"1289":1,"1299":1,"1310":1,"1322":1,"1495":1,"1838":2}}],["audittrailentries",{"2":{"715":2,"719":1,"720":1,"1273":2,"1277":1,"1445":1,"1468":1},"3":{"0":3,"715":3,"719":3,"720":1,"1212":2,"1273":2,"1277":1,"1285":8,"1435":4,"1445":1,"1466":3,"1468":1}}],["audittrailsavechangesinterceptortests",{"3":{"1199":1,"1207":2,"1237":2,"1285":1,"1437":2}}],["audittrailsavechangesinterceptor",{"2":{"715":1,"1232":1,"1236":1,"1269":1,"1277":1,"1316":1,"1838":1},"3":{"715":2,"720":3,"1198":1,"1199":8,"1203":1,"1207":4,"1232":1,"1236":2,"1237":9,"1269":1,"1277":3,"1285":40,"1316":1,"1322":2,"1495":2,"1576":1,"1838":1}}],["audittrailsettings",{"2":{"721":1,"1272":1,"1277":1},"3":{"674":1,"715":1,"720":1,"721":1,"1199":11,"1203":1,"1207":2,"1237":5,"1272":1,"1277":2,"1285":20,"1316":1,"1319":4,"1322":2,"1576":1}}],["audittrailcleanupjobtests",{"3":{"1199":1,"1207":2,"1285":1,"1437":2}}],["audittrailcleanupjob",{"2":{"720":1,"1277":1},"3":{"707":1,"715":2,"720":1,"1082":1,"1086":1,"1199":4,"1203":1,"1207":2,"1270":3,"1277":2,"1285":29,"1322":2,"1435":2,"1441":3}}],["audittrail",{"2":{"1285":1},"3":{"170":1,"713":1,"715":3,"720":3,"1082":1,"1191":1,"1203":7,"1207":42,"1232":1,"1236":1,"1237":6,"1270":5,"1273":1,"1277":6,"1285":31,"1310":1,"1319":1,"1322":4,"1437":3,"1441":1,"1486":1,"1495":2,"1584":1}}],["audit=all",{"3":{"147":1,"1575":1,"1576":2,"1585":1}}],["auditsources",{"2":{"1648":1},"3":{"1525":1,"1590":1,"1648":1}}],["auditstampfidelitytests",{"3":{"1199":1,"1207":2}}],["audits",{"3":{"130":1,"139":1,"1458":1,"1466":1,"1530":1}}],["auditsavechangesinterceptortests",{"3":{"1199":1,"1207":6,"1285":2,"1435":1}}],["auditsavechangesinterceptor",{"2":{"39":1,"43":1,"343":1,"714":1,"715":1,"1201":1,"1231":1,"1274":1},"3":{"39":3,"43":1,"343":2,"345":1,"714":1,"715":2,"1082":1,"1086":1,"1175":1,"1198":1,"1199":53,"1201":1,"1203":1,"1207":2,"1231":4,"1237":5,"1274":2,"1285":11,"1322":1,"1435":3,"1497":1,"1576":1,"1809":1}}],["auditingsettings",{"3":{"1435":1,"1466":1}}],["auditing",{"3":{"0":3,"395":1,"403":1,"642":1,"713":2,"714":1,"718":2,"719":3,"720":1,"1203":1,"1207":4,"1285":1,"1322":2,"1349":1,"1358":1,"1395":1,"1435":2,"1466":6,"1468":1,"1495":1,"1568":1,"1590":1,"1977":1}}],["audit",{"0":{"1277":1,"1458":1,"1500":1},"1":{"367":1,"368":1,"369":1,"370":1,"371":1,"372":1,"373":1,"374":1,"375":1,"376":1,"712":1,"713":1,"714":1,"715":1,"716":1,"717":1,"718":1,"719":1,"720":1,"721":1,"1494":1,"1495":1,"1496":1,"1497":1,"1498":1,"1499":1,"1500":1},"3":{"0":8,"26":1,"38":1,"39":4,"40":3,"137":1,"139":1,"140":1,"159":1,"166":1,"170":1,"188":1,"195":1,"197":1,"200":1,"225":2,"230":1,"245":2,"249":1,"265":1,"283":1,"302":1,"304":1,"318":2,"320":1,"322":1,"336":1,"337":1,"341":2,"346":1,"352":1,"361":1,"367":1,"368":1,"369":1,"370":4,"371":5,"372":4,"373":1,"374":3,"375":3,"390":1,"403":1,"423":1,"424":1,"425":1,"426":1,"428":1,"430":1,"434":1,"450":1,"458":1,"461":1,"470":1,"486":1,"530":2,"531":2,"532":1,"545":1,"573":1,"574":1,"590":1,"591":1,"600":1,"601":1,"611":1,"625":1,"667":1,"692":1,"696":1,"698":2,"711":1,"712":1,"715":3,"716":4,"717":1,"718":1,"719":1,"727":1,"737":1,"782":1,"785":1,"820":1,"825":1,"829":1,"874":1,"905":1,"1006":1,"1033":1,"1034":3,"1041":1,"1082":2,"1083":1,"1085":2,"1086":1,"1140":2,"1141":1,"1142":1,"1145":2,"1163":1,"1168":2,"1174":1,"1192":3,"1194":1,"1201":3,"1202":1,"1203":1,"1212":9,"1213":1,"1216":1,"1230":2,"1231":3,"1236":2,"1237":22,"1239":2,"1241":1,"1247":6,"1253":2,"1259":5,"1270":2,"1272":1,"1273":1,"1274":2,"1275":3,"1278":1,"1280":1,"1283":1,"1285":57,"1289":1,"1299":13,"1302":1,"1303":4,"1305":1,"1308":9,"1310":4,"1322":7,"1341":2,"1349":9,"1358":11,"1360":1,"1362":1,"1368":4,"1378":1,"1379":3,"1394":1,"1395":21,"1401":2,"1404":1,"1409":2,"1411":1,"1414":15,"1422":1,"1426":1,"1432":1,"1435":26,"1437":5,"1445":2,"1451":1,"1452":7,"1453":1,"1454":2,"1458":20,"1459":2,"1460":4,"1464":6,"1466":10,"1494":2,"1495":10,"1511":1,"1524":1,"1525":13,"1530":5,"1531":1,"1533":1,"1534":2,"1544":2,"1547":2,"1553":1,"1566":2,"1568":2,"1576":11,"1581":2,"1584":3,"1585":2,"1590":14,"1595":3,"1596":1,"1599":4,"1633":1,"1640":1,"1648":1,"1650":2,"1652":1,"1656":1,"1659":1,"1661":1,"1663":3,"1671":4,"1672":1,"1674":1,"1676":1,"1684":1,"1685":1,"1697":1,"1703":1,"1716":1,"1718":1,"1719":2,"1747":1,"1748":1,"1779":1,"1793":1,"1809":2,"1811":1,"1814":2,"1837":1,"1838":4,"1843":2,"1850":1,"1922":1,"1932":7,"1933":1,"1934":1,"1939":2,"1940":1,"1963":1,"1994":1,"1995":1,"2011":1,"2048":1,"2051":1,"2060":2,"2062":3,"2067":2,"2068":4,"2089":1,"2090":1,"2131":1,"2137":1,"2139":1,"2144":2,"2182":1,"2185":1,"2220":1,"2226":1,"2229":3,"2231":2}}],["autonomously",{"3":{"1631":1}}],["autonomous",{"3":{"1631":1}}],["autonomy",{"3":{"46":1,"48":1,"1435":1,"1466":3,"1851":1}}],["autorotate",{"3":{"1416":1}}],["autoreplenishment",{"3":{"1338":2}}],["autoawesome",{"3":{"1394":1}}],["autoinclude",{"3":{"1358":1}}],["autoincrement",{"3":{"1322":1}}],["autoincrementing",{"3":{"1322":1}}],["autoorient",{"3":{"1322":1}}],["autodeleteonidle",{"3":{"1322":1,"1435":1}}],["autodetectchangesenabled",{"3":{"1285":2}}],["autocompletes",{"3":{"1323":2}}],["autocomplete",{"3":{"1310":2,"1394":1,"1639":1}}],["autocrlf",{"2":{"1425":1,"2175":1},"3":{"1091":1,"1425":1,"1426":1,"2175":1}}],["automitigate",{"3":{"609":2,"1018":1,"1466":4}}],["automatable",{"3":{"1435":1,"1656":1,"1703":1}}],["automates",{"3":{"1576":1,"1845":1}}],["automate",{"3":{"758":1,"762":1,"1480":1,"1529":1,"1530":1,"1531":1,"1575":1}}],["automated",{"0":{"1607":1,"1642":1,"1741":1},"3":{"132":1,"282":1,"293":1,"361":1,"372":1,"528":1,"641":1,"696":1,"700":1,"756":1,"759":1,"791":1,"792":1,"821":1,"1192":1,"1358":1,"1394":1,"1435":3,"1437":2,"1452":2,"1453":1,"1454":1,"1456":1,"1458":3,"1460":1,"1466":1,"1470":1,"1471":1,"1485":1,"1487":1,"1490":1,"1495":1,"1523":2,"1524":1,"1525":4,"1529":1,"1530":4,"1532":1,"1553":2,"1557":1,"1564":1,"1565":1,"1567":1,"1571":2,"1576":3,"1580":1,"1581":1,"1583":1,"1584":1,"1590":4,"1604":1,"1630":1,"1641":1,"1644":1,"1677":1,"1738":1,"1740":1,"1799":1,"1842":1,"2001":1,"2033":1,"2098":1,"2144":1,"2165":1,"2219":2,"2220":1}}],["automating",{"3":{"1525":1,"1575":1}}],["automation",{"0":{"1458":1},"3":{"483":1,"490":1,"620":1,"1451":2,"1474":1,"1486":1,"1547":1,"1569":1,"1575":1,"1604":2,"1641":1,"1643":1,"1679":1,"1738":2,"2035":1,"2059":1}}],["automatically",{"3":{"12":1,"122":1,"141":1,"235":1,"265":1,"283":1,"547":1,"698":1,"739":1,"883":1,"889":1,"1216":1,"1229":1,"1237":2,"1259":1,"1263":1,"1264":1,"1270":1,"1271":1,"1274":1,"1285":6,"1300":1,"1308":1,"1309":1,"1310":5,"1311":1,"1315":1,"1322":1,"1323":3,"1338":2,"1349":1,"1358":4,"1361":1,"1367":1,"1368":1,"1394":2,"1395":4,"1401":2,"1414":1,"1416":1,"1426":1,"1430":1,"1435":6,"1439":1,"1442":2,"1453":1,"1456":2,"1464":1,"1466":2,"1488":1,"1489":1,"1525":7,"1536":1,"1550":1,"1570":1,"1574":1,"1576":7,"1581":1,"1583":3,"1590":1,"1632":1,"1634":2,"1638":3,"1652":1,"1662":1,"1665":1,"1706":2,"1723":1,"1766":1,"1783":1,"1796":1,"1798":1,"1848":1,"1861":1,"1865":1,"1867":1,"1912":1,"1923":1,"1944":1,"1999":1,"2004":1,"2010":1,"2018":1,"2034":1,"2040":1,"2089":1,"2092":1,"2095":1,"2097":1,"2163":1}}],["automatic",{"1":{"754":1,"755":1,"756":1,"757":1,"758":1,"759":1,"760":1,"761":1,"762":1},"3":{"0":4,"135":1,"175":1,"312":1,"320":1,"361":1,"380":1,"659":1,"715":1,"734":1,"741":1,"754":1,"757":1,"819":1,"874":1,"889":1,"1034":1,"1212":1,"1236":1,"1237":3,"1247":1,"1259":1,"1270":3,"1280":1,"1285":8,"1300":2,"1308":1,"1310":3,"1321":1,"1322":4,"1323":5,"1338":1,"1348":1,"1349":1,"1358":2,"1399":1,"1414":5,"1435":4,"1437":1,"1454":3,"1458":1,"1524":1,"1525":1,"1576":2,"1591":2,"1629":1,"1640":7,"1663":1,"1664":1,"1667":1,"1669":1,"1764":1,"1837":1,"1842":1,"1844":1,"1906":1,"1944":1,"1995":1,"2047":1,"2141":1,"2144":1,"2220":1,"2231":1}}],["automapper",{"1":{"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"1950":1,"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1956":1,"1957":1,"1958":1},"3":{"0":1,"2":1,"3":1,"4":1,"1212":1,"1213":1,"1346":1,"1352":1,"1358":1,"1488":1,"1576":1,"1778":1,"1949":1,"1950":3,"1954":1,"1955":1,"1957":4,"1958":2,"2042":1,"2207":1,"2231":1}}],["autoverify",{"3":{"0":1,"418":1,"420":1,"423":1,"1415":1}}],["auto",{"0":{"1955":1,"2035":1},"2":{"99":1,"1446":1},"3":{"0":9,"53":1,"91":1,"99":1,"117":1,"166":1,"241":1,"243":1,"244":1,"253":1,"259":2,"290":1,"291":1,"293":1,"295":1,"342":1,"348":1,"350":1,"352":1,"353":1,"396":3,"398":2,"423":1,"440":1,"549":1,"640":1,"757":2,"759":1,"766":1,"767":1,"788":1,"790":3,"791":1,"793":1,"820":1,"1004":1,"1123":1,"1125":1,"1129":1,"1212":3,"1244":1,"1247":2,"1259":3,"1269":1,"1270":7,"1271":3,"1273":1,"1280":1,"1285":8,"1299":2,"1300":1,"1308":1,"1310":2,"1311":1,"1322":3,"1323":6,"1332":1,"1336":3,"1337":1,"1338":9,"1349":1,"1354":4,"1355":1,"1358":8,"1368":1,"1378":1,"1379":2,"1394":5,"1395":6,"1398":1,"1401":7,"1405":1,"1412":1,"1414":18,"1415":4,"1416":12,"1432":1,"1435":21,"1437":4,"1442":1,"1446":1,"1454":1,"1466":5,"1489":1,"1495":1,"1509":1,"1525":4,"1534":1,"1565":1,"1571":1,"1576":4,"1587":1,"1589":1,"1590":2,"1610":2,"1611":1,"1626":1,"1638":5,"1640":2,"1650":2,"1655":1,"1661":1,"1662":1,"1670":1,"1746":1,"1748":1,"1750":1,"1753":1,"1766":1,"1768":1,"1789":1,"1810":1,"1829":1,"1857":1,"1870":1,"1882":1,"1915":1,"1958":2,"1976":1,"1999":1,"2016":1,"2027":1,"2062":1,"2092":1,"2094":1,"2112":2,"2127":2,"2153":3,"2155":2,"2159":3,"2165":1,"2229":1,"2231":1}}],["authfailurespikealert",{"2":{"2157":1},"3":{"2157":1}}],["authresult",{"3":{"1640":1}}],["authresponse",{"3":{"1199":3,"1207":1}}],["authz",{"3":{"1525":3,"1534":1,"1547":1,"1576":2,"1590":1,"1610":2,"1615":1,"1616":1}}],["authn",{"3":{"1525":1,"1547":1,"1576":1}}],["authpagepath",{"3":{"1487":1}}],["authprovider",{"3":{"1435":3}}],["authpreferencestests",{"3":{"1199":1,"1207":3}}],["authunavailable",{"3":{"1435":1}}],["authuiservicetests",{"3":{"1199":1,"1207":2,"1323":6}}],["authuiservice",{"2":{"418":1,"2077":1},"3":{"243":2,"258":2,"259":1,"261":1,"418":1,"420":1,"423":1,"426":1,"428":1,"430":1,"434":2,"436":1,"905":2,"909":1,"1199":3,"1203":1,"1207":2,"1299":7,"1323":43,"1435":3,"1495":1,"2077":1}}],["auth=1",{"3":{"1433":1,"1435":1}}],["authgracetimeout",{"3":{"1432":1,"1435":2}}],["authtimeout",{"3":{"1432":1,"1435":2}}],["authtightpolicyname",{"3":{"1325":1,"1338":1,"1443":1}}],["authsucceededwithingraceasync",{"2":{"1432":1},"3":{"1432":1,"1435":1,"1487":1}}],["authservice",{"3":{"1414":1}}],["authstateprovider",{"2":{"1414":1},"3":{"1414":2,"1640":1}}],["authstate",{"3":{"1395":1,"1401":2}}],["authipratelimitpartition",{"3":{"1310":4}}],["authippermitlimit",{"2":{"178":1,"1598":1,"2000":1},"3":{"175":1,"178":1,"1310":5,"1598":1,"2000":1}}],["authmodelvalidationtests",{"3":{"1199":1,"1207":2,"1323":1}}],["authoutcomerulestests",{"3":{"1199":1,"1207":2,"1435":4,"1437":2}}],["authoutcomerules",{"3":{"1199":3,"1207":3,"1427":1,"1432":4,"1435":9,"1487":6}}],["authoutcome",{"3":{"1199":4,"1207":1,"1432":1,"1435":4,"1487":1}}],["authoruserid",{"3":{"1685":3}}],["authorship",{"3":{"1358":3,"1401":1}}],["authors",{"3":{"123":1,"557":1,"748":1,"750":1,"846":1,"990":1,"1270":1,"1271":2,"1285":2,"1368":1,"1395":1,"1401":1,"1416":1,"1435":1,"1495":1,"1736":1,"1829":1}}],["authored",{"3":{"0":1,"122":1,"333":1,"881":1,"887":1,"889":1,"890":2,"891":1,"972":1,"973":1,"976":1,"1006":1,"1081":1,"1085":1,"1117":1,"1212":2,"1238":2,"1241":4,"1242":4,"1245":1,"1247":8,"1263":1,"1270":1,"1271":2,"1280":3,"1285":10,"1309":1,"1310":1,"1323":2,"1338":2,"1341":1,"1349":2,"1358":3,"1361":1,"1394":4,"1395":7,"1397":1,"1401":6,"1414":3,"1415":3,"1432":1,"1435":13,"1495":37,"1497":1,"1512":1,"1575":1,"1576":1,"1629":1,"1663":1,"1814":1,"1815":2,"1816":1,"2219":1,"2220":1,"2229":1,"2231":1}}],["author",{"3":{"0":2,"135":1,"136":1,"194":1,"196":1,"295":1,"333":1,"342":1,"472":1,"538":1,"676":1,"716":1,"732":1,"799":1,"846":1,"847":2,"880":1,"889":1,"890":1,"912":1,"1004":1,"1005":1,"1006":1,"1135":1,"1152":1,"1237":1,"1247":6,"1258":1,"1259":1,"1267":1,"1270":3,"1271":1,"1285":3,"1309":1,"1312":1,"1322":2,"1338":1,"1349":1,"1358":6,"1368":1,"1379":1,"1395":2,"1397":1,"1401":17,"1416":1,"1435":11,"1437":1,"1454":1,"1473":1,"1487":1,"1490":1,"1495":19,"1631":3,"1888":1,"1986":1,"2039":1,"2042":1,"2076":1,"2108":2,"2167":1}}],["authoring",{"3":{"384":1,"1270":1,"1401":8,"1435":1,"1495":22,"1525":1,"1533":1,"1630":1}}],["authoritative",{"3":{"0":2,"145":1,"146":1,"147":1,"150":1,"395":1,"397":1,"400":1,"404":1,"407":1,"408":1,"509":1,"536":2,"537":1,"638":1,"640":1,"642":1,"937":1,"1073":1,"1075":1,"1093":1,"1155":1,"1271":1,"1299":2,"1310":1,"1323":8,"1332":2,"1338":2,"1349":1,"1394":5,"1395":8,"1401":3,"1414":1,"1435":3,"1437":2,"1442":2,"1452":1,"1453":1,"1454":2,"1459":1,"1473":1,"1489":1,"1491":1,"1525":1,"1530":1,"1534":1,"1560":1,"1599":1,"1638":1,"1645":1,"1653":1,"1674":1,"1691":1,"1731":1,"2009":1,"2111":1,"2155":1,"2159":2,"2166":1,"2168":1,"2178":1}}],["authority",{"2":{"1325":1,"1440":1,"1443":1,"1466":1,"1612":1,"2008":1},"3":{"0":2,"31":6,"59":1,"91":3,"93":4,"98":2,"100":1,"101":1,"104":1,"235":3,"236":1,"237":1,"451":1,"825":1,"827":3,"828":1,"834":1,"1018":1,"1044":1,"1118":2,"1152":1,"1212":1,"1285":3,"1299":2,"1310":20,"1311":1,"1322":1,"1323":12,"1325":1,"1333":2,"1338":5,"1346":1,"1349":3,"1358":3,"1394":2,"1395":6,"1401":1,"1414":1,"1426":1,"1428":2,"1435":6,"1437":2,"1440":1,"1442":3,"1443":1,"1466":5,"1610":1,"1612":3,"1625":1,"1649":1,"1662":1,"1666":1,"1702":1,"1723":1,"1897":1,"1903":1,"2008":1,"2023":1,"2077":1,"2191":1,"2202":1}}],["authorizable",{"3":{"1395":1}}],["authorizationpolicies",{"3":{"1495":1}}],["authorizationpolicy",{"3":{"189":1,"827":1,"1299":3,"1446":3,"1702":1}}],["authorizationresult",{"3":{"1435":3}}],["authorizationheader",{"3":{"1311":1}}],["authorizationhandlercontext",{"3":{"1299":1}}],["authorizationhandler",{"3":{"1299":3}}],["authorizationmultifactordecoratortests",{"2":{"1270":1},"3":{"1199":1,"1207":5,"1264":1,"1270":1}}],["authorizationtests",{"3":{"1199":1,"1207":2,"1435":3,"1437":2}}],["authorizationtestsbase",{"3":{"1199":2,"1207":2,"1427":1,"1432":3,"1435":8,"1437":1,"1487":1,"1495":2}}],["authorizationoptions",{"2":{"189":1},"3":{"189":1,"1299":4}}],["authorizationextensionstests",{"3":{"1199":1,"1207":2}}],["authorizationextensions",{"2":{"1299":1},"3":{"186":1,"189":2,"190":1,"707":1,"710":1,"980":1,"1059":1,"1199":1,"1203":1,"1207":2,"1208":1,"1286":1,"1296":10,"1299":40,"1495":1,"1576":1}}],["authorizationquerydecoratortests",{"3":{"1199":1,"1207":4,"1270":3}}],["authorizationquerydecorator",{"2":{"126":1,"1260":1,"1263":1,"1296":1},"3":{"121":2,"126":2,"1199":4,"1203":1,"1207":2,"1260":1,"1263":4,"1270":16,"1296":1,"1322":1,"1435":1,"1495":1}}],["authorizationdenied",{"2":{"121":1},"3":{"121":1,"1270":1}}],["authorizationcommanddecoratortests",{"3":{"1199":1,"1207":5,"1270":3,"1437":2}}],["authorizationcommanddecorator",{"2":{"121":1,"126":1,"1260":1,"1263":1,"1296":1,"1315":1},"3":{"121":3,"126":3,"1199":4,"1203":1,"1207":2,"1260":1,"1263":4,"1268":1,"1270":22,"1296":1,"1299":1,"1315":1,"1322":1,"1435":1,"1495":1}}],["authorizationgate",{"2":{"115":1,"126":1,"1820":1,"1961":1,"1964":1},"3":{"0":1,"115":1,"126":8,"186":2,"923":1,"927":1,"1059":4,"1199":3,"1203":1,"1207":2,"1260":1,"1263":10,"1270":20,"1495":2,"1820":1,"1961":1,"1964":1}}],["authorization",{"0":{"1296":1,"1347":1,"1352":1,"1356":1,"1372":1,"1401":1,"1510":1,"1666":1,"1760":1,"1770":1},"1":{"183":1,"184":1,"185":1,"186":1,"187":1,"188":1,"189":1,"190":1,"191":1,"315":1,"316":1,"317":1,"318":1,"319":1,"320":1,"321":1,"322":1,"323":1,"324":1,"325":1,"326":1,"1286":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1292":1,"1293":1,"1294":1,"1295":1,"1296":1,"1297":1,"1298":1,"1299":1,"1959":1,"1960":1,"1961":1,"1962":1,"1963":1,"1964":1,"1991":1,"1992":1,"1993":1,"1994":1,"1995":1,"1996":1},"2":{"121":1,"126":1,"206":1,"227":1,"387":1,"388":1,"389":1,"826":1,"1004":1,"1263":2,"1268":1,"1271":1,"1308":1,"1471":1,"1965":1,"1971":1,"2018":1,"2155":1},"3":{"0":24,"31":1,"57":1,"59":3,"95":1,"115":2,"117":1,"121":5,"122":3,"124":2,"126":6,"183":1,"186":8,"189":5,"190":1,"206":1,"217":1,"227":1,"243":1,"304":1,"315":1,"317":1,"318":7,"321":1,"324":1,"325":2,"326":1,"387":1,"388":3,"389":1,"443":1,"459":2,"464":3,"466":1,"507":1,"544":1,"546":2,"612":1,"640":1,"643":2,"651":1,"674":1,"690":1,"707":1,"715":1,"725":1,"739":1,"743":1,"748":1,"749":3,"753":1,"757":1,"774":1,"825":3,"826":1,"827":6,"906":1,"907":1,"952":1,"954":1,"956":1,"960":2,"980":3,"1004":1,"1012":1,"1026":1,"1042":1,"1043":1,"1044":1,"1058":3,"1059":4,"1061":1,"1115":1,"1116":3,"1117":2,"1118":1,"1184":2,"1192":2,"1202":2,"1203":26,"1207":72,"1208":3,"1212":5,"1213":1,"1238":1,"1239":1,"1245":1,"1247":5,"1253":1,"1259":1,"1260":2,"1263":5,"1264":1,"1268":2,"1270":25,"1271":4,"1275":2,"1282":2,"1285":8,"1286":5,"1295":1,"1296":16,"1297":1,"1299":204,"1300":3,"1303":3,"1308":17,"1310":20,"1311":7,"1315":3,"1317":1,"1322":15,"1323":19,"1338":2,"1341":1,"1347":4,"1349":16,"1352":3,"1353":1,"1356":2,"1358":52,"1371":1,"1372":4,"1374":1,"1376":1,"1378":1,"1379":37,"1394":10,"1395":23,"1401":23,"1402":1,"1403":1,"1404":1,"1405":1,"1408":1,"1409":2,"1413":1,"1414":34,"1416":3,"1426":3,"1435":47,"1436":7,"1437":7,"1446":1,"1454":1,"1466":8,"1471":2,"1480":1,"1486":2,"1487":2,"1495":11,"1496":1,"1506":3,"1510":1,"1525":4,"1533":1,"1534":2,"1547":3,"1561":1,"1562":2,"1571":2,"1574":2,"1576":5,"1581":1,"1588":1,"1590":2,"1594":1,"1595":2,"1599":2,"1620":1,"1625":1,"1626":1,"1628":1,"1629":1,"1631":1,"1637":4,"1638":3,"1639":1,"1640":4,"1659":1,"1661":2,"1665":1,"1666":4,"1670":2,"1671":1,"1697":2,"1719":1,"1746":2,"1752":1,"1756":1,"1758":1,"1774":1,"1778":2,"1812":2,"1813":1,"1814":2,"1815":1,"1816":2,"1820":3,"1824":1,"1825":1,"1826":1,"1901":1,"1902":1,"1903":1,"1921":2,"1937":1,"1939":1,"1958":1,"1959":1,"1960":2,"1962":1,"1964":5,"1965":1,"1971":1,"1990":1,"1991":2,"1992":2,"1993":4,"1996":3,"2018":2,"2035":1,"2054":1,"2065":1,"2095":1,"2110":2,"2112":1,"2135":2,"2148":1,"2155":2,"2178":1,"2183":1,"2185":1,"2191":1,"2192":2,"2193":1,"2194":3,"2198":2,"2201":1,"2207":3,"2219":1,"2226":1,"2229":1,"2231":2}}],["authorizing",{"3":{"209":1,"1358":1}}],["authorizeview",{"2":{"509":1,"650":1,"953":1,"960":1,"1757":1,"1760":1,"2077":1},"3":{"507":1,"509":1,"650":1,"1323":3,"1395":1,"1435":1,"1590":1,"1640":1,"1670":1,"1757":2,"1760":1,"2077":1}}],["authorizer",{"3":{"378":1,"1308":3,"1944":1}}],["authorizerouteview",{"2":{"189":1,"649":1,"651":1,"652":1},"3":{"189":1,"649":1,"651":1,"652":1,"1299":1,"1435":3}}],["authorizeattribute",{"3":{"186":1,"1296":1,"1299":3,"1430":1,"1435":2,"1961":1}}],["authorizesessionwriteasync",{"3":{"1358":9}}],["authorizes",{"3":{"0":1,"191":1,"530":1,"749":1,"1059":1,"1299":4,"1310":1,"1321":1,"1322":1,"1356":1,"1358":6,"1401":4,"1414":1,"1626":2,"1961":1,"1992":1,"2201":1}}],["authorize",{"2":{"185":1,"188":1,"206":1,"207":1,"208":1,"555":1,"557":1,"906":1,"910":1,"1625":1,"1702":1,"1723":1,"1756":1,"1757":1,"1758":1,"1759":1,"1760":1,"1822":1,"1897":1,"1959":1,"1963":1,"1964":1,"1965":1,"1966":1,"1971":1,"2044":1},"3":{"0":2,"185":2,"186":1,"188":1,"206":2,"207":1,"208":1,"225":1,"235":1,"434":1,"529":1,"530":1,"555":1,"557":1,"618":1,"690":1,"707":1,"724":1,"725":2,"905":2,"906":1,"907":1,"910":1,"1116":1,"1212":1,"1247":1,"1263":1,"1270":4,"1297":1,"1299":11,"1305":1,"1306":1,"1308":6,"1310":10,"1322":4,"1323":7,"1333":1,"1338":1,"1349":4,"1356":1,"1358":5,"1372":6,"1374":1,"1376":1,"1379":14,"1387":2,"1390":1,"1394":16,"1395":18,"1401":6,"1413":1,"1414":14,"1415":1,"1416":3,"1426":3,"1430":2,"1433":1,"1435":29,"1437":2,"1487":1,"1525":1,"1533":1,"1576":2,"1590":4,"1595":1,"1598":1,"1599":1,"1625":1,"1626":9,"1639":1,"1640":1,"1649":1,"1650":1,"1702":1,"1723":1,"1746":3,"1748":1,"1756":1,"1757":1,"1758":1,"1759":2,"1760":3,"1764":1,"1822":1,"1824":1,"1897":1,"1934":1,"1947":1,"1959":4,"1963":2,"1964":2,"1965":3,"1966":1,"1967":1,"1970":1,"1971":2,"2044":2,"2231":1}}],["authorizedsessionids",{"3":{"1358":1}}],["authorized",{"3":{"0":3,"95":1,"96":1,"98":1,"384":1,"528":1,"599":1,"960":1,"1092":1,"1285":2,"1299":3,"1308":1,"1323":1,"1358":2,"1395":2,"1414":1,"1435":5,"1466":1,"1473":1,"1547":1,"1552":1,"1671":1,"1820":1,"1992":1,"2178":1}}],["autherrorcodes",{"3":{"1199":3,"1203":1,"1207":2,"1294":3,"1299":5,"1495":1}}],["authentic",{"3":{"1488":1}}],["authenticity",{"3":{"790":1,"1299":1}}],["authenticators",{"3":{"1416":2}}],["authenticator",{"3":{"1059":2,"1292":2,"1299":7,"1310":1,"1321":1,"1322":4,"1416":2,"2195":1,"2197":2,"2202":1}}],["authenticating",{"3":{"0":1,"348":1,"353":1,"1299":1,"1311":1,"1323":1,"1338":1,"1435":1,"1901":1}}],["authenticationcallback",{"3":{"1416":1}}],["authenticationtype",{"3":{"1322":4,"1435":1}}],["authenticationticket",{"3":{"1299":1,"1435":1}}],["authenticationproperties",{"3":{"1310":2}}],["authenticationheadervalue",{"2":{"1310":1,"1323":1},"3":{"1310":2,"1323":3}}],["authenticationhandler",{"3":{"1299":2,"1435":1}}],["authenticationbuilder",{"2":{"1208":1},"3":{"1208":1,"1299":3}}],["authenticationvalidatorstests",{"3":{"1199":1,"1207":2}}],["authenticationvalidators",{"2":{"1288":1},"3":{"1199":12,"1203":1,"1207":2,"1286":1,"1288":3,"1299":9,"1414":5,"1495":2}}],["authenticationrequest",{"2":{"1288":1},"3":{"1199":1,"1203":1,"1207":2,"1288":1,"1299":8}}],["authenticationresponse",{"2":{"420":1,"426":1,"1288":1,"1976":1},"3":{"350":1,"1199":27,"1203":1,"1207":2,"1288":1,"1299":16,"1310":3,"1323":4,"1414":2,"1435":1,"1976":1}}],["authenticationservicesettings",{"3":{"1299":1,"1414":3,"1435":1,"1495":2}}],["authenticationservicetests",{"3":{"1199":1,"1207":3,"1435":1}}],["authenticationserviceidentitycompletionstests",{"2":{"1199":1,"1299":1},"3":{"1199":1,"1207":7,"1299":1,"1435":1}}],["authenticationservice",{"2":{"283":1,"310":1,"314":1,"354":1,"502":1,"672":1,"949":1,"988":1,"1192":1,"1288":1,"1406":1,"1407":1,"1533":1,"1596":1,"1598":1,"1977":1,"1982":1},"3":{"281":1,"283":1,"310":5,"314":1,"350":17,"352":2,"353":1,"354":1,"497":2,"499":13,"502":2,"545":1,"552":1,"672":1,"674":4,"946":2,"949":1,"988":2,"1192":1,"1199":5,"1203":1,"1207":2,"1288":1,"1299":12,"1310":1,"1404":1,"1405":7,"1406":1,"1407":1,"1412":1,"1414":35,"1435":4,"1488":1,"1495":4,"1525":2,"1533":2,"1590":3,"1596":2,"1598":2,"1764":2,"1895":1,"1977":1,"1982":4,"1999":2}}],["authenticationservicebasetests",{"3":{"906":1,"1199":1,"1207":6,"1299":1,"1435":1}}],["authenticationservicebase",{"2":{"279":1,"281":1,"283":1,"284":1,"285":1,"310":1,"353":1,"470":1,"476":1,"498":1,"502":1,"503":1,"725":1,"949":1,"1058":1,"1060":1,"1061":1,"1192":1,"1288":1,"1292":1,"1402":1,"1666":1,"1895":1,"1981":1,"2195":1,"2202":1},"3":{"0":3,"279":1,"283":1,"284":2,"285":1,"310":4,"353":2,"470":2,"476":2,"497":2,"498":2,"499":21,"500":1,"501":2,"502":3,"503":1,"674":2,"725":1,"905":10,"906":1,"907":4,"909":1,"910":1,"946":1,"949":1,"1058":2,"1059":3,"1060":1,"1061":1,"1192":1,"1199":5,"1203":1,"1207":4,"1270":1,"1271":2,"1285":2,"1286":1,"1288":27,"1289":19,"1292":1,"1299":68,"1402":1,"1405":1,"1414":2,"1435":2,"1488":1,"1495":3,"1576":2,"1581":1,"1582":1,"1585":1,"1666":1,"1895":1,"1981":9,"1983":2,"1999":1,"2195":2,"2196":2,"2202":1}}],["authenticationstatetask",{"3":{"1394":1}}],["authenticationstate",{"3":{"0":1,"954":1,"1323":2,"1395":1,"1401":2,"1431":1,"1435":6,"1487":1,"1490":2}}],["authenticationstateprovider",{"2":{"508":1,"953":1,"954":1,"1431":1,"1490":1,"1640":1},"3":{"0":1,"508":1,"953":1,"954":2,"1323":8,"1394":2,"1414":2,"1431":1,"1435":9,"1487":1,"1490":1,"1640":1}}],["authentication=active",{"3":{"0":1,"599":1,"1466":1,"1476":1}}],["authentication",{"0":{"1288":1,"1405":1,"1510":1,"1640":1},"1":{"204":1,"205":1,"206":1,"207":1,"208":1,"209":1,"210":1,"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"504":1,"505":1,"506":1,"507":1,"508":1,"509":1,"510":1,"511":1,"512":1,"513":1,"514":1,"1286":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1292":1,"1293":1,"1294":1,"1295":1,"1296":1,"1297":1,"1298":1,"1299":1},"2":{"776":1,"833":1,"1292":2,"1294":2,"1310":1,"1321":1,"1325":1,"1440":1,"1443":1,"1466":4,"2008":1},"3":{"0":7,"26":1,"31":4,"34":1,"59":1,"160":1,"173":1,"175":1,"177":1,"178":1,"182":1,"186":1,"202":1,"204":1,"206":1,"207":1,"235":2,"278":1,"280":1,"313":2,"350":1,"352":1,"359":1,"363":1,"364":1,"365":1,"403":1,"422":1,"457":1,"458":1,"459":2,"462":2,"464":3,"466":1,"500":1,"504":1,"506":1,"528":1,"555":1,"599":2,"602":1,"612":1,"618":1,"665":1,"666":1,"674":1,"698":1,"719":2,"748":2,"749":6,"753":3,"774":1,"776":1,"778":1,"792":1,"827":4,"833":1,"852":1,"950":2,"980":2,"1011":1,"1012":1,"1058":1,"1059":3,"1061":1,"1108":1,"1111":1,"1184":1,"1192":1,"1202":1,"1203":3,"1207":7,"1208":2,"1212":1,"1213":2,"1253":1,"1259":1,"1270":3,"1271":1,"1273":1,"1282":1,"1283":1,"1285":5,"1286":2,"1292":4,"1294":4,"1297":2,"1299":48,"1300":2,"1310":49,"1319":2,"1321":1,"1322":4,"1323":13,"1325":1,"1333":2,"1334":1,"1335":1,"1338":7,"1379":2,"1394":4,"1395":7,"1401":1,"1405":1,"1407":1,"1414":14,"1415":3,"1416":3,"1431":1,"1433":1,"1435":33,"1436":1,"1440":1,"1441":1,"1442":2,"1443":1,"1464":1,"1466":8,"1487":2,"1495":4,"1525":1,"1547":2,"1574":1,"1621":1,"1625":2,"1626":2,"1628":4,"1629":1,"1630":5,"1631":3,"1634":1,"1637":2,"1638":1,"1639":2,"1640":9,"1646":1,"1649":2,"1665":1,"1668":1,"1670":1,"1702":2,"1720":1,"1723":1,"1746":1,"1757":1,"1762":2,"1763":1,"1764":2,"1766":1,"1824":5,"1897":1,"1898":2,"1901":1,"1903":1,"1969":1,"1971":3,"1974":1,"1981":1,"1984":1,"1998":1,"1999":3,"2000":2,"2001":2,"2008":1,"2065":2,"2141":2,"2143":1,"2165":1,"2167":1,"2185":1,"2193":2,"2194":1,"2202":2,"2207":1,"2219":2,"2220":1,"2231":2}}],["authenticateasspeaker",{"3":{"1487":1}}],["authenticateasattendee",{"3":{"1487":1,"1490":1}}],["authenticateasorganizer",{"3":{"1487":1}}],["authenticateasync",{"2":{"420":1,"430":1,"2116":1},"3":{"420":1,"426":1,"428":1,"430":1,"1323":3,"1414":1,"1415":1,"1416":6,"2116":1}}],["authenticateresult",{"3":{"1299":1,"1414":1,"1435":2}}],["authenticate",{"3":{"30":1,"279":1,"284":1,"527":1,"601":1,"789":1,"905":1,"946":1,"947":1,"1299":3,"1323":2,"1414":1,"1416":1,"1454":2,"1466":2,"1590":1,"1631":1,"1640":3,"1767":1,"1981":1,"2126":1,"2145":1,"2165":1}}],["authenticateduser",{"3":{"1435":2}}],["authenticateduserpath",{"3":{"1432":1,"1435":1}}],["authenticatedpage",{"3":{"1435":1}}],["authenticatedservicebaseretrytests",{"3":{"1199":1,"1207":3}}],["authenticatedservicebase",{"2":{"879":1,"881":1,"883":1,"884":1,"1387":1,"1388":1,"1413":1,"1667":1,"2073":1,"2079":1},"3":{"0":3,"827":2,"879":1,"881":6,"883":2,"884":2,"1199":23,"1203":1,"1207":2,"1212":1,"1323":55,"1338":2,"1387":1,"1388":1,"1394":17,"1395":18,"1401":1,"1413":1,"1414":1,"1495":2,"1599":1,"1667":1,"2073":1,"2079":1}}],["authenticated",{"0":{"1297":1,"1390":1,"1622":1,"1758":1,"1921":1,"1999":1},"1":{"172":1,"173":1,"174":1,"175":1,"176":1,"177":1,"178":1,"179":1,"180":1,"181":1,"182":1,"385":1,"386":1,"387":1,"388":1,"389":1,"390":1,"391":1,"392":1,"393":1},"3":{"0":8,"26":1,"31":1,"172":1,"175":5,"176":1,"177":2,"178":1,"179":1,"181":2,"182":1,"184":1,"188":1,"189":1,"191":1,"228":1,"234":1,"237":1,"243":2,"244":1,"247":1,"259":1,"265":1,"273":1,"280":2,"282":1,"334":1,"359":3,"360":2,"384":1,"385":1,"387":2,"388":1,"390":1,"434":1,"458":2,"459":2,"460":1,"461":1,"462":1,"507":1,"556":1,"649":1,"690":1,"725":1,"727":1,"749":1,"759":1,"825":1,"853":1,"945":1,"960":1,"1192":1,"1212":4,"1253":1,"1271":2,"1283":1,"1285":7,"1286":1,"1287":1,"1295":1,"1296":1,"1299":16,"1303":1,"1305":1,"1307":1,"1308":15,"1310":22,"1322":7,"1323":19,"1335":1,"1338":4,"1349":3,"1356":1,"1358":12,"1372":1,"1379":4,"1390":1,"1394":16,"1395":27,"1400":1,"1401":5,"1405":1,"1406":1,"1413":1,"1414":14,"1416":9,"1430":1,"1431":1,"1435":17,"1437":6,"1442":1,"1454":1,"1464":1,"1487":1,"1495":2,"1525":4,"1533":3,"1534":1,"1547":1,"1571":1,"1576":1,"1590":1,"1610":1,"1620":5,"1624":1,"1625":1,"1626":3,"1628":1,"1630":7,"1631":18,"1637":2,"1638":6,"1639":3,"1640":6,"1671":1,"1697":1,"1746":3,"1747":2,"1748":10,"1749":2,"1756":3,"1764":3,"1767":3,"1770":1,"1772":1,"1824":1,"1902":1,"1904":1,"1921":3,"1922":2,"1923":1,"1935":1,"1936":1,"1937":1,"1944":1,"1962":1,"1967":1,"1991":1,"1996":1,"1997":1,"1998":2,"1999":4,"2000":2,"2001":3,"2006":1,"2065":1,"2082":1,"2141":2,"2145":5,"2165":1,"2168":1,"2231":5}}],["authenticates",{"3":{"0":5,"59":1,"207":1,"227":1,"350":1,"598":1,"599":1,"601":1,"665":1,"825":1,"905":1,"972":1,"1183":1,"1185":1,"1289":1,"1299":4,"1308":1,"1310":3,"1322":1,"1323":1,"1338":3,"1405":1,"1414":2,"1433":1,"1435":2,"1446":1,"1454":1,"1464":1,"1466":3,"1471":1,"1546":1,"1640":1,"1666":1,"1731":1,"1901":1,"1904":1,"1965":1,"1967":1,"1974":1,"1981":1,"1992":1,"1999":1,"2125":1}}],["authclaimtypes",{"2":{"186":1,"303":1,"649":1,"1295":1,"2137":1,"2196":1},"3":{"126":1,"186":3,"303":2,"649":1,"905":4,"907":1,"1059":3,"1199":34,"1203":1,"1207":2,"1270":3,"1286":1,"1292":4,"1295":5,"1299":29,"1322":1,"1435":3,"1495":1,"2137":2,"2196":3}}],["authcontrollertests",{"3":{"1199":1,"1207":2}}],["authcontroller",{"2":{"1746":2},"3":{"0":1,"854":1,"1199":2,"1203":1,"1207":2,"1299":1,"1310":8,"1322":5,"1405":3,"1406":2,"1407":1,"1414":31,"1435":1,"1495":3,"1746":2,"1999":2}}],["authcontrollerbaseratelimittests",{"2":{"1999":1,"2000":1},"3":{"1199":1,"1207":3,"1310":1,"1437":2,"1495":2,"1999":2,"2000":2}}],["authcontrollerbasetests",{"3":{"906":1,"909":1,"1199":1,"1207":3,"1310":1}}],["authcontrollerbase",{"2":{"160":1,"161":1,"280":1,"1598":1,"1599":1,"1999":1,"2001":1},"3":{"0":4,"160":2,"161":2,"175":1,"280":1,"854":1,"905":6,"1199":6,"1203":1,"1207":2,"1289":4,"1299":15,"1310":30,"1405":1,"1414":2,"1435":3,"1495":2,"1598":1,"1599":1,"2001":1}}],["authdelegatinghandlertests",{"3":{"1199":1,"1207":2,"1323":2}}],["authdelegatinghandler",{"2":{"387":1,"514":1,"1183":1},"3":{"0":1,"387":1,"507":6,"514":3,"881":1,"1183":2,"1199":3,"1203":1,"1207":2,"1323":13,"1435":3}}],["auth",{"0":{"1666":1,"2207":1},"1":{"1892":1,"1893":1,"1894":1,"1895":1,"1896":1,"1897":1,"1898":1,"1965":1,"1966":1,"1967":1,"1968":1,"1969":1,"1970":1,"1971":1},"2":{"186":1,"190":1,"207":2,"281":2,"284":1,"350":5,"352":3,"353":2,"354":3,"511":2,"854":1,"1062":1,"1294":1,"1299":2,"1405":2,"1581":1,"1582":1,"1640":1,"1760":1,"1961":1,"1976":4,"1977":1,"1981":1},"3":{"0":31,"29":1,"31":2,"53":1,"59":1,"60":1,"121":1,"126":2,"136":1,"155":1,"157":1,"160":1,"161":2,"162":1,"173":5,"175":4,"176":1,"177":2,"178":3,"179":5,"180":8,"181":1,"182":2,"186":4,"190":2,"207":7,"209":3,"210":1,"217":1,"221":1,"227":1,"229":2,"236":1,"239":1,"243":2,"246":1,"279":2,"280":3,"281":3,"282":2,"284":3,"287":2,"303":2,"310":1,"318":1,"349":1,"350":7,"352":5,"353":4,"354":3,"379":1,"389":1,"403":1,"415":2,"416":1,"418":1,"420":6,"423":3,"426":3,"428":5,"430":2,"433":1,"434":1,"440":1,"459":2,"463":1,"470":1,"476":1,"497":4,"498":1,"499":4,"502":1,"506":1,"507":17,"508":3,"509":2,"510":2,"511":2,"513":2,"514":2,"536":2,"555":3,"556":2,"557":1,"599":1,"600":1,"608":1,"609":2,"611":1,"612":1,"616":1,"643":1,"649":5,"664":1,"666":1,"668":1,"670":1,"674":9,"676":1,"736":1,"757":1,"758":1,"775":1,"809":2,"827":2,"833":1,"834":1,"853":1,"854":6,"856":1,"857":1,"904":1,"905":30,"906":11,"907":2,"908":1,"909":4,"926":1,"946":4,"963":1,"980":1,"988":1,"996":1,"999":1,"1006":2,"1058":3,"1059":36,"1062":2,"1082":2,"1183":1,"1184":9,"1185":1,"1186":1,"1192":3,"1194":1,"1201":1,"1202":2,"1203":147,"1207":517,"1208":2,"1212":9,"1213":1,"1237":4,"1270":3,"1271":3,"1273":1,"1282":8,"1285":36,"1286":1,"1287":6,"1288":6,"1289":7,"1290":6,"1291":7,"1292":9,"1293":5,"1294":7,"1295":3,"1296":20,"1297":4,"1299":484,"1300":5,"1303":1,"1310":47,"1311":4,"1312":1,"1315":1,"1319":3,"1321":2,"1322":19,"1323":230,"1325":1,"1335":1,"1337":1,"1338":16,"1349":4,"1358":2,"1379":1,"1394":8,"1395":10,"1401":2,"1402":1,"1405":3,"1406":4,"1407":3,"1414":42,"1415":5,"1416":43,"1427":1,"1428":2,"1429":1,"1431":2,"1432":2,"1433":2,"1435":73,"1436":2,"1437":27,"1439":1,"1440":1,"1441":2,"1443":2,"1446":10,"1452":1,"1453":1,"1454":4,"1455":3,"1457":1,"1466":11,"1476":6,"1480":1,"1481":1,"1483":1,"1486":2,"1487":7,"1495":16,"1510":1,"1523":1,"1525":7,"1533":3,"1534":1,"1561":1,"1562":2,"1570":1,"1576":13,"1581":8,"1582":2,"1585":3,"1590":5,"1610":3,"1611":1,"1616":2,"1617":1,"1618":2,"1626":2,"1631":10,"1637":1,"1638":1,"1639":3,"1640":26,"1641":1,"1643":1,"1646":2,"1649":1,"1652":1,"1655":1,"1657":1,"1659":3,"1662":1,"1665":1,"1666":2,"1668":3,"1688":1,"1697":2,"1711":1,"1745":6,"1746":10,"1747":5,"1748":3,"1749":1,"1756":1,"1758":1,"1760":3,"1764":7,"1766":1,"1770":3,"1777":1,"1778":3,"1779":1,"1782":1,"1785":1,"1789":2,"1792":1,"1891":1,"1892":1,"1898":1,"1922":1,"1941":1,"1961":1,"1964":1,"1965":2,"1966":1,"1967":6,"1968":1,"1970":3,"1971":5,"1973":1,"1974":4,"1975":1,"1976":4,"1977":2,"1981":1,"1993":1,"1996":1,"1998":1,"1999":7,"2000":4,"2001":1,"2008":1,"2014":1,"2022":1,"2024":1,"2027":1,"2035":2,"2044":1,"2054":2,"2065":3,"2073":1,"2077":3,"2079":1,"2084":1,"2109":2,"2117":1,"2121":1,"2148":1,"2157":1,"2207":3,"2226":1,"2227":1,"2229":1,"2231":3,"2242":1}}],["abbreviates",{"3":{"1680":1}}],["abbreviated",{"3":{"1495":1}}],["abbreviating",{"3":{"1338":1}}],["abi",{"3":{"1416":1}}],["ability",{"3":{"58":1,"726":1,"1237":1,"1310":1,"1322":1,"1338":1,"1368":1,"1395":1,"1414":1,"1442":1,"1984":1}}],["able",{"3":{"380":1,"447":1,"549":1,"691":1,"760":1,"811":1,"829":1,"989":1,"1042":1,"1237":1,"1238":1,"1259":1,"1279":1,"1285":3,"1299":4,"1300":1,"1310":1,"1322":1,"1323":2,"1338":3,"1358":1,"1365":1,"1394":3,"1395":6,"1409":1,"1414":4,"1434":1,"1435":1,"1437":3,"1438":1,"1440":1,"1466":1,"1486":1,"1676":1,"1839":1,"1941":1,"1942":1,"1966":1,"1980":1,"1983":1,"2129":1,"2133":1,"2191":1}}],["abc",{"3":{"335":1,"1237":1,"1241":2,"1247":2}}],["abuse",{"3":{"175":1,"381":1,"827":1,"1125":1,"1126":1,"1323":3,"1325":1,"1338":3,"1395":3,"1401":1,"1415":1,"1443":1,"1533":1,"1598":1,"1639":1,"1640":1,"1944":1}}],["abuser",{"3":{"174":1,"1997":1,"1998":1}}],["abusive",{"3":{"174":1,"1401":1,"1997":1}}],["abac",{"3":{"0":1,"188":1,"320":1,"1576":1,"1582":1,"1963":1,"1995":2,"1996":1}}],["abandonaftercommitfailure",{"2":{"988":1},"3":{"988":2,"1285":2}}],["abandoning",{"3":{"332":1,"751":1,"923":1,"1323":1,"1389":1,"1416":1,"1989":1}}],["abandons",{"3":{"24":1,"237":1,"538":1,"1292":1,"1338":2,"1394":2}}],["abandoned",{"3":{"21":1,"24":1,"109":2,"235":1,"352":1,"793":1,"897":1,"1247":1,"1259":2,"1284":1,"1285":5,"1299":2,"1323":4,"1338":1,"1394":2,"1398":1,"1401":3,"1435":4,"1441":1,"1466":1,"1590":1,"1758":1,"1909":1,"1918":1,"1926":1,"1977":1}}],["abandon",{"3":{"0":1,"24":1,"27":1,"201":4,"255":1,"757":1,"1247":1,"1258":2,"1259":6,"1310":1,"1322":1,"1358":1,"1437":1,"1454":1,"1921":1}}],["aborterror",{"3":{"1416":1}}],["aborted",{"3":{"109":1,"1263":1,"1270":1,"1278":1,"1285":2,"1299":2,"1303":1,"1306":1,"1308":1,"1311":1,"1322":1,"1345":1,"1358":1,"1435":3,"1944":1}}],["aborting",{"3":{"757":1,"1300":1,"1314":1,"1322":1,"1323":1,"1357":1,"1358":2,"1368":1,"1395":1,"1437":1,"1803":1,"1880":1}}],["abort",{"3":{"72":1,"109":1,"1285":2,"1310":1,"1349":1,"1358":3,"1365":1,"1368":1,"1395":1,"1414":1,"1416":2,"1437":1,"1446":1,"1670":1,"1805":1,"1940":1}}],["aborts",{"3":{"43":2,"790":1,"881":1,"1042":1,"1259":1,"1322":2,"1338":1,"1349":1,"1358":3,"1395":1,"1414":2,"1683":1,"2184":1}}],["about",{"0":{"2055":1},"3":{"0":9,"19":3,"23":1,"32":1,"58":1,"60":1,"71":1,"72":1,"73":1,"74":2,"100":1,"104":1,"110":1,"125":1,"140":1,"142":1,"145":1,"150":1,"156":2,"160":2,"175":1,"193":1,"200":1,"254":1,"255":2,"284":1,"317":1,"358":1,"360":1,"365":1,"383":1,"390":3,"414":1,"423":1,"425":1,"428":1,"460":1,"477":1,"486":1,"491":1,"527":1,"536":1,"539":1,"543":2,"555":1,"563":1,"564":1,"566":1,"567":1,"585":1,"587":1,"589":1,"590":2,"617":1,"625":1,"628":1,"629":1,"632":1,"633":1,"676":2,"681":1,"683":1,"689":1,"698":1,"699":1,"702":1,"707":2,"708":1,"716":3,"724":1,"740":2,"742":1,"750":1,"751":1,"757":1,"759":2,"760":1,"764":1,"768":2,"784":1,"793":2,"798":2,"804":1,"805":1,"819":1,"820":1,"827":4,"838":3,"848":1,"854":1,"862":2,"869":1,"871":1,"880":1,"889":2,"890":1,"893":1,"897":2,"898":1,"900":1,"906":1,"914":1,"926":2,"930":1,"933":1,"938":1,"940":1,"953":1,"956":1,"963":1,"964":2,"971":1,"972":1,"976":1,"980":1,"981":2,"984":1,"998":1,"1007":1,"1018":1,"1027":1,"1033":3,"1035":1,"1049":2,"1050":1,"1051":2,"1066":1,"1067":2,"1068":1,"1077":1,"1085":1,"1089":1,"1093":1,"1116":1,"1118":1,"1125":1,"1142":1,"1154":1,"1155":1,"1176":2,"1194":1,"1212":1,"1229":3,"1237":3,"1239":1,"1241":1,"1247":3,"1252":2,"1254":2,"1259":6,"1264":1,"1265":1,"1267":1,"1269":1,"1270":10,"1273":1,"1276":1,"1277":1,"1285":25,"1294":1,"1295":1,"1299":29,"1300":4,"1308":4,"1309":1,"1310":11,"1311":2,"1317":2,"1321":2,"1322":20,"1323":40,"1336":2,"1337":1,"1338":13,"1339":1,"1340":1,"1346":1,"1347":2,"1349":9,"1354":1,"1358":51,"1365":2,"1368":6,"1370":1,"1379":5,"1394":16,"1395":42,"1396":1,"1398":1,"1401":13,"1414":28,"1415":3,"1416":10,"1426":2,"1428":1,"1429":1,"1432":1,"1435":70,"1439":1,"1442":1,"1444":2,"1445":1,"1446":2,"1448":1,"1450":1,"1452":3,"1453":1,"1454":6,"1455":2,"1458":1,"1459":1,"1464":5,"1466":11,"1471":1,"1472":1,"1473":7,"1474":1,"1475":3,"1477":1,"1483":1,"1487":1,"1488":2,"1489":3,"1491":3,"1495":2,"1525":1,"1528":1,"1552":1,"1585":1,"1629":1,"1637":1,"1640":1,"1648":1,"1649":1,"1650":1,"1653":1,"1684":1,"1685":3,"1691":1,"1698":1,"1699":1,"1707":1,"1724":1,"1725":1,"1726":1,"1728":2,"1731":1,"1764":1,"1781":1,"1791":1,"1793":1,"1800":1,"1802":3,"1807":1,"1823":1,"1827":1,"1834":1,"1842":1,"1854":1,"1855":1,"1862":1,"1869":1,"1873":1,"1885":1,"1889":1,"1901":2,"1902":1,"1906":1,"1908":1,"1915":1,"1918":1,"1921":1,"1931":1,"1943":1,"1947":2,"1949":1,"1951":1,"1952":1,"1953":1,"1961":1,"1963":1,"1970":1,"1972":1,"1977":1,"1981":2,"1991":2,"1993":1,"1995":1,"1997":1,"1999":3,"2000":1,"2001":1,"2028":1,"2029":1,"2030":1,"2032":4,"2035":1,"2036":1,"2040":1,"2041":1,"2042":2,"2044":1,"2046":2,"2049":1,"2052":1,"2057":2,"2058":3,"2059":1,"2065":1,"2066":1,"2074":1,"2085":1,"2086":3,"2090":1,"2095":1,"2098":1,"2099":1,"2100":1,"2108":1,"2113":1,"2115":1,"2117":1,"2122":1,"2126":1,"2131":1,"2134":1,"2137":1,"2141":1,"2150":1,"2155":2,"2156":1,"2157":1,"2164":2,"2165":1,"2166":3,"2173":1,"2179":1,"2180":1,"2182":1,"2183":1,"2184":1,"2190":1,"2192":1,"2193":2,"2196":1,"2197":2,"2199":1,"2211":1,"2231":1,"2238":1}}],["above",{"3":{"0":3,"1":1,"20":1,"22":2,"27":1,"62":1,"71":1,"93":3,"94":2,"95":2,"97":1,"98":2,"99":1,"100":2,"101":1,"104":1,"121":2,"122":1,"128":1,"131":1,"135":6,"136":3,"137":3,"138":3,"139":1,"142":1,"151":1,"160":2,"166":1,"175":2,"178":2,"179":1,"180":1,"181":1,"200":6,"218":1,"219":2,"230":1,"231":1,"237":1,"243":2,"245":3,"247":1,"248":1,"249":1,"250":1,"251":1,"253":1,"256":1,"257":1,"258":2,"265":1,"276":1,"291":1,"295":2,"303":2,"325":2,"330":2,"336":2,"337":1,"359":1,"364":1,"365":2,"374":1,"391":1,"392":2,"395":3,"400":1,"401":1,"403":1,"407":1,"415":1,"418":1,"423":1,"424":1,"425":2,"426":3,"428":1,"429":1,"441":1,"443":1,"444":1,"448":3,"450":1,"451":1,"471":1,"474":1,"475":2,"476":1,"477":3,"493":1,"497":2,"507":1,"511":1,"512":2,"523":3,"524":1,"528":1,"536":1,"540":1,"548":1,"549":3,"550":1,"551":4,"555":1,"556":1,"560":1,"566":2,"567":1,"572":2,"574":1,"575":2,"576":1,"577":1,"578":1,"584":1,"591":1,"592":1,"603":1,"604":1,"607":1,"609":3,"612":1,"613":2,"616":1,"626":2,"633":1,"635":1,"638":1,"642":2,"660":1,"665":1,"668":1,"669":1,"674":2,"675":1,"676":1,"688":1,"691":1,"702":1,"716":1,"719":1,"731":1,"733":1,"750":1,"751":1,"766":1,"767":1,"774":1,"790":1,"799":1,"801":1,"811":1,"812":1,"814":1,"819":2,"825":2,"826":1,"827":6,"829":1,"831":2,"833":1,"837":1,"839":1,"849":1,"860":1,"870":1,"874":1,"876":1,"891":1,"899":1,"906":1,"909":1,"914":1,"915":1,"926":2,"931":1,"954":1,"957":1,"975":1,"996":1,"1003":1,"1008":1,"1018":1,"1020":2,"1026":1,"1036":1,"1059":1,"1060":1,"1074":1,"1076":1,"1077":1,"1094":1,"1102":1,"1117":1,"1124":2,"1125":1,"1127":1,"1128":1,"1178":1,"1184":1,"1188":1,"1211":1,"1212":1,"1223":1,"1229":3,"1236":1,"1237":9,"1240":1,"1243":1,"1247":5,"1259":7,"1262":2,"1266":1,"1269":1,"1270":15,"1275":1,"1280":1,"1285":16,"1299":14,"1300":4,"1308":12,"1309":1,"1310":11,"1311":1,"1313":1,"1314":1,"1322":25,"1323":40,"1325":1,"1328":1,"1332":1,"1336":1,"1338":30,"1345":1,"1349":14,"1352":1,"1357":1,"1358":77,"1361":1,"1363":1,"1364":1,"1368":20,"1379":16,"1386":1,"1391":1,"1394":27,"1395":52,"1401":7,"1413":1,"1414":34,"1415":2,"1416":10,"1426":1,"1429":1,"1430":1,"1434":1,"1435":41,"1437":2,"1439":1,"1440":1,"1441":1,"1442":1,"1443":4,"1444":4,"1445":1,"1446":5,"1448":1,"1452":1,"1453":1,"1454":6,"1455":1,"1457":2,"1458":1,"1459":2,"1464":5,"1466":23,"1473":3,"1474":3,"1475":1,"1477":1,"1478":1,"1486":2,"1487":5,"1488":3,"1489":2,"1490":1,"1491":3,"1495":14,"1503":1,"1516":1,"1531":1,"1533":2,"1576":1,"1581":1,"1590":3,"1598":1,"1609":1,"1610":2,"1618":1,"1626":2,"1635":1,"1639":1,"1643":1,"1644":2,"1652":1,"1657":1,"1659":1,"1672":1,"1674":1,"1677":1,"1684":1,"1685":2,"1686":1,"1688":1,"1698":1,"1700":1,"1707":1,"1719":1,"1722":1,"1723":1,"1724":1,"1726":4,"1727":1,"1743":1,"1747":1,"1748":1,"1749":1,"1754":1,"1759":1,"1760":1,"1768":1,"1776":1,"1784":1,"1790":1,"1813":1,"1839":1,"1841":1,"1843":1,"1848":1,"1850":1,"1853":1,"1855":1,"1858":1,"1859":1,"1860":1,"1867":1,"1897":2,"1910":1,"1913":1,"1915":1,"1918":1,"1919":2,"1922":1,"1928":1,"1929":1,"1947":1,"1969":1,"1975":1,"1999":2,"2023":1,"2043":1,"2044":1,"2046":2,"2047":1,"2052":2,"2054":1,"2055":1,"2057":1,"2063":1,"2067":1,"2072":1,"2073":1,"2077":2,"2083":1,"2088":1,"2093":1,"2098":1,"2099":2,"2121":1,"2130":1,"2141":1,"2145":1,"2157":1,"2165":2,"2166":2,"2167":3,"2173":1,"2194":1,"2197":1,"2200":1,"2231":1}}],["abs",{"3":{"1299":2}}],["abstain",{"3":{"1299":1}}],["abstains",{"3":{"1299":1}}],["abstaining",{"3":{"1299":1}}],["abstractlocalizationinterceptor",{"3":{"1323":3}}],["abstracted",{"3":{"1323":2,"1640":1}}],["abstractvalidator",{"2":{"1352":1,"1828":1,"1829":1},"3":{"1271":13,"1299":9,"1308":1,"1352":1,"1358":102,"1395":2,"1401":8,"1414":4,"1435":7,"1828":2,"1829":1}}],["abstracting",{"3":{"1247":1,"1323":1}}],["abstraction",{"0":{"1914":1},"1":{"410":1,"411":1,"412":1,"413":1,"414":1,"415":1,"416":1,"1416":1},"3":{"0":5,"13":1,"39":1,"150":1,"225":1,"226":1,"230":1,"243":1,"248":1,"319":1,"359":1,"361":1,"380":1,"381":1,"410":1,"439":1,"459":1,"460":1,"507":1,"510":1,"513":1,"640":2,"707":1,"732":1,"799":1,"810":1,"815":1,"827":1,"846":2,"1033":1,"1059":1,"1075":1,"1090":1,"1091":1,"1092":1,"1120":1,"1133":1,"1192":3,"1202":3,"1203":3,"1212":1,"1213":2,"1243":1,"1247":3,"1259":2,"1270":4,"1271":1,"1285":18,"1299":13,"1300":5,"1302":1,"1308":5,"1309":2,"1310":9,"1315":1,"1322":3,"1323":10,"1338":4,"1349":2,"1354":1,"1358":15,"1368":1,"1394":5,"1395":16,"1401":1,"1413":1,"1414":3,"1415":2,"1416":12,"1426":5,"1428":1,"1435":25,"1437":1,"1489":1,"1490":1,"1495":1,"1525":1,"1598":1,"1640":1,"1668":1,"1789":1,"1814":3,"1816":1,"1841":1,"1866":1,"1935":1,"1945":1,"1947":1,"1949":1,"1993":1,"2000":1,"2027":1,"2077":1,"2115":2,"2116":1,"2117":1,"2119":1,"2122":1,"2125":1,"2141":1,"2144":1,"2152":1,"2179":1,"2192":1,"2231":3}}],["abstractions",{"2":{"109":1,"1229":1,"1652":1,"1701":1},"3":{"0":2,"39":1,"43":1,"109":6,"166":1,"225":1,"226":1,"230":1,"434":1,"506":1,"507":1,"508":1,"549":1,"725":1,"1050":1,"1074":1,"1090":1,"1168":1,"1203":12,"1207":40,"1212":1,"1219":3,"1220":1,"1222":1,"1227":1,"1228":1,"1229":25,"1237":2,"1270":4,"1271":6,"1278":1,"1285":9,"1299":9,"1300":1,"1301":2,"1308":6,"1309":1,"1310":2,"1311":8,"1322":2,"1323":7,"1338":1,"1349":5,"1350":1,"1358":21,"1368":1,"1380":1,"1394":5,"1395":10,"1401":3,"1414":5,"1435":21,"1437":2,"1488":2,"1495":1,"1508":1,"1537":1,"1576":3,"1652":1,"1655":1,"1659":1,"1701":1,"1788":1,"1792":1,"2015":1,"2025":1,"2027":2,"2042":1,"2110":1,"2226":1,"2229":1}}],["abstractfitnesscontrollerbase",{"3":{"1199":3,"1207":1,"1435":6}}],["abstractanonymousfixturecontrollerbase",{"3":{"1199":4,"1207":1,"1435":5}}],["abstracts",{"3":{"434":1,"507":2,"1323":1,"1415":1,"1416":2,"1495":1}}],["abstract",{"2":{"1051":1},"3":{"0":13,"47":1,"130":1,"132":1,"135":1,"136":1,"137":1,"140":1,"160":1,"166":1,"255":1,"314":1,"443":1,"536":1,"545":1,"549":2,"572":3,"583":1,"657":2,"723":1,"725":3,"726":2,"800":1,"881":1,"905":1,"907":1,"926":2,"953":1,"972":3,"973":1,"975":2,"1033":1,"1050":3,"1051":1,"1052":1,"1059":1,"1083":1,"1087":1,"1114":1,"1116":1,"1117":1,"1119":1,"1161":1,"1168":2,"1169":1,"1170":1,"1192":1,"1202":1,"1203":1,"1212":4,"1214":1,"1226":1,"1229":4,"1234":1,"1237":10,"1239":2,"1247":8,"1259":19,"1265":4,"1270":21,"1272":1,"1273":2,"1283":1,"1285":30,"1286":1,"1288":2,"1293":1,"1299":15,"1300":1,"1310":24,"1321":1,"1322":37,"1323":11,"1333":1,"1338":6,"1358":30,"1359":1,"1362":2,"1368":5,"1379":1,"1383":1,"1394":23,"1395":6,"1411":1,"1414":14,"1416":2,"1426":2,"1427":1,"1428":1,"1430":4,"1432":1,"1435":185,"1487":8,"1488":1,"1495":1,"1509":1,"1525":2,"1538":1,"1564":1,"1575":1,"1576":4,"1649":1,"1650":2,"1657":1,"1663":1,"1670":1,"1696":1,"1699":1,"1718":1,"1733":1,"1781":1,"1809":2,"1814":2,"1832":1,"1848":1,"1856":1,"1868":1,"1930":1,"2041":1,"2054":2,"2071":1,"2073":1,"2100":2,"2101":1,"2110":1,"2113":2,"2177":2,"2231":1}}],["absexp",{"3":{"245":1,"1300":1,"1437":1}}],["absent",{"1":{"807":1,"808":1,"809":1,"810":1,"811":1,"812":1,"813":1,"814":1,"815":1},"3":{"0":7,"66":1,"74":1,"135":1,"157":1,"158":1,"160":2,"177":1,"178":1,"259":1,"265":1,"303":1,"318":1,"343":1,"397":3,"402":1,"404":1,"409":1,"413":1,"415":1,"468":1,"469":1,"486":1,"490":1,"491":1,"492":1,"493":1,"501":1,"545":1,"583":1,"585":2,"599":2,"608":2,"610":1,"632":1,"658":1,"665":2,"673":1,"674":4,"684":1,"702":1,"703":1,"713":1,"726":1,"727":1,"741":1,"789":1,"798":1,"799":1,"807":1,"808":1,"809":3,"813":2,"827":3,"829":1,"904":1,"905":2,"915":1,"926":2,"946":1,"964":1,"972":1,"974":1,"997":1,"999":1,"1018":1,"1020":1,"1033":1,"1091":1,"1093":1,"1108":1,"1109":1,"1212":2,"1253":1,"1259":4,"1264":1,"1270":5,"1271":1,"1275":1,"1280":1,"1285":20,"1290":1,"1299":15,"1300":2,"1303":1,"1308":8,"1310":11,"1314":1,"1319":1,"1322":15,"1323":17,"1325":1,"1327":1,"1331":2,"1332":1,"1334":1,"1335":1,"1338":19,"1341":1,"1342":1,"1343":1,"1349":15,"1352":1,"1358":38,"1367":2,"1368":4,"1376":1,"1379":5,"1390":2,"1394":13,"1395":22,"1401":6,"1405":2,"1414":16,"1415":1,"1416":17,"1424":1,"1426":3,"1429":1,"1430":1,"1431":1,"1434":2,"1435":49,"1437":2,"1440":2,"1441":4,"1442":1,"1449":1,"1450":1,"1454":2,"1462":1,"1466":5,"1471":2,"1485":2,"1487":2,"1488":1,"1489":1,"1491":1,"1495":3,"1525":1,"1533":1,"1536":1,"1570":1,"1574":1,"1576":4,"1581":2,"1582":1,"1583":2,"1590":2,"1595":1,"1599":1,"1611":1,"1630":1,"1634":1,"1638":3,"1669":1,"1683":1,"1748":1,"1796":1,"1822":1,"1855":1,"1880":1,"1922":1,"1926":1,"1933":1,"1940":1,"1947":1,"1969":1,"1974":1,"1981":1,"1983":1,"1993":2,"2007":1,"2009":1,"2046":1,"2066":1,"2117":1,"2119":1,"2155":2,"2157":2,"2170":1,"2174":1,"2176":1,"2178":2,"2179":1,"2195":1,"2202":1,"2231":2}}],["absences",{"3":{"915":1,"1358":1,"1435":1,"2176":1}}],["absence",{"0":{"2172":1},"3":{"0":6,"109":1,"126":1,"136":1,"160":1,"189":1,"233":1,"401":1,"404":1,"468":1,"487":1,"583":3,"585":2,"592":1,"626":2,"627":1,"629":1,"643":1,"674":1,"690":1,"696":1,"698":1,"768":1,"800":1,"811":1,"813":1,"814":1,"838":1,"861":1,"876":1,"912":1,"913":1,"916":1,"931":1,"972":1,"994":1,"1033":1,"1050":1,"1052":1,"1059":2,"1092":1,"1093":1,"1141":1,"1150":1,"1212":1,"1225":1,"1247":1,"1259":2,"1264":1,"1270":1,"1274":1,"1277":1,"1285":6,"1295":1,"1299":3,"1300":1,"1322":3,"1323":5,"1338":3,"1349":9,"1358":11,"1379":1,"1394":5,"1395":9,"1414":3,"1416":7,"1420":1,"1426":1,"1435":10,"1446":1,"1466":1,"1475":1,"1584":1,"1673":1,"1684":1,"1701":1,"1707":1,"1730":1,"1793":1,"1794":1,"1820":1,"2000":1,"2178":1,"2179":1,"2192":1,"2196":1,"2198":1,"2202":1,"2231":1}}],["absorbing",{"3":{"1338":1,"1446":1,"1576":1}}],["absorbed",{"3":{"235":1,"743":1,"800":1,"883":1,"1150":1,"1258":1,"1259":3,"1322":1,"1323":2,"1349":1,"1358":2,"1379":1,"1395":4,"1435":1,"1449":1,"1495":1,"2111":1}}],["absorb",{"3":{"33":1,"111":1,"390":1,"609":1,"642":1,"799":1,"1157":1,"1259":1,"1270":1,"1299":1,"1300":1,"1310":2,"1401":1,"1416":1,"1466":1,"1533":1,"1946":1,"2026":1,"2164":1}}],["absorbs",{"3":{"18":1,"141":1,"162":1,"201":1,"239":1,"244":2,"735":1,"863":1,"897":1,"1254":1,"1299":1,"1326":1,"1338":2,"1394":1,"1443":1,"1455":1,"1570":1,"1670":1,"1805":1,"1921":1}}],["absorption",{"3":{"0":1,"201":1}}],["absoluteuri",{"3":{"1414":1}}],["absoluteurl",{"3":{"1323":2,"1383":1,"1394":12}}],["absoluteurlrules<",{"3":{"1271":2}}],["absoluteurlrules",{"2":{"1828":1},"3":{"1199":12,"1203":1,"1207":1,"1271":5,"1323":2,"1352":1,"1358":28,"1828":1}}],["absoluteurlattributetests",{"3":{"1199":1,"1207":5,"1437":2}}],["absoluteurlattribute",{"2":{"1668":1},"3":{"1199":1,"1203":1,"1207":2,"1271":3,"1323":8,"1394":7,"1668":1}}],["absolutepath",{"3":{"1338":1,"1435":5}}],["absolutes",{"3":{"1323":1}}],["absoluteexpirationrelativetonow",{"2":{"243":1,"254":1,"257":1,"1300":1},"3":{"243":1,"254":1,"257":1,"1300":3}}],["absolutely",{"3":{"0":1,"591":1,"1491":1,"2046":1,"2048":1,"2231":1}}],["absolute",{"3":{"0":4,"219":1,"243":1,"350":1,"420":1,"500":2,"501":2,"590":1,"591":2,"592":3,"631":1,"860":3,"862":2,"1116":1,"1184":1,"1212":2,"1237":3,"1271":3,"1285":1,"1299":2,"1300":2,"1310":1,"1322":7,"1323":23,"1333":1,"1338":6,"1343":1,"1349":3,"1358":29,"1368":1,"1387":1,"1394":6,"1395":1,"1416":5,"1426":2,"1435":4,"1437":1,"1442":1,"1466":1,"1491":2,"1525":1,"1630":4,"1639":1,"1665":1,"1707":1,"1748":1,"1763":1,"1764":1,"1766":1,"1975":1,"1983":2,"1984":1,"2046":4,"2125":1}}],["apm",{"3":{"1893":1}}],["aps",{"3":{"1322":3,"1416":1}}],["apache",{"3":{"0":1,"440":1,"1075":1,"1576":1,"1672":1,"1779":1,"1786":1,"1792":1,"1801":1,"1806":1,"1811":1,"1816":1,"1826":1,"1833":1,"1845":1,"1852":1,"1861":1,"1867":1,"1876":1,"1885":1,"1891":1,"1898":1,"1904":1,"1911":1,"1923":1,"1929":1,"1940":1,"1949":1,"1958":1,"1964":1,"1971":1,"1978":1,"1984":1,"1990":1,"1996":1,"2001":1,"2013":1,"2027":1,"2037":1,"2048":1,"2059":1,"2068":1,"2079":1,"2085":1,"2097":1,"2107":1,"2114":1,"2122":1,"2126":1,"2138":1,"2145":1,"2152":1,"2203":1,"2210":1,"2215":1,"2228":1,"2233":1,"2241":1}}],["apart",{"3":{"0":2,"23":1,"62":1,"109":1,"235":1,"397":1,"449":1,"626":2,"629":1,"648":1,"691":1,"767":1,"783":1,"784":1,"799":1,"881":1,"974":1,"1059":1,"1068":1,"1083":1,"1124":1,"1126":1,"1191":1,"1211":1,"1237":3,"1238":1,"1240":1,"1247":2,"1259":1,"1270":3,"1271":2,"1280":1,"1285":5,"1289":1,"1298":1,"1299":7,"1300":1,"1304":1,"1308":3,"1310":3,"1322":3,"1323":8,"1324":1,"1331":1,"1338":5,"1349":5,"1357":1,"1358":25,"1368":1,"1370":1,"1379":2,"1394":10,"1395":18,"1397":1,"1401":6,"1404":1,"1409":1,"1414":7,"1415":1,"1416":2,"1435":6,"1437":2,"1440":1,"1442":2,"1457":1,"1458":2,"1486":1,"1487":1,"1495":1,"1502":1,"1652":1,"1676":1,"1747":1,"1804":1,"1833":1,"1947":1,"1981":1,"2054":1,"2082":1,"2129":1,"2130":1,"2166":1}}],["april",{"3":{"0":1,"1074":1}}],["apnsplatform",{"3":{"1308":1}}],["apnspushdevicetokenprovider",{"2":{"434":1,"436":1},"3":{"434":2,"436":2,"1415":1,"1416":4}}],["apnstokenbridge",{"2":{"436":1},"3":{"436":1,"1415":3,"1416":1}}],["apns",{"3":{"0":2,"413":1,"432":1,"433":2,"434":1,"435":1,"436":1,"1285":1,"1307":1,"1308":3,"1322":7,"1415":6,"1416":15,"1466":1,"1480":1,"1495":1,"1668":2,"1931":1,"1935":1,"1939":1,"2121":1,"2231":1}}],["api1",{"3":{"1992":1}}],["apibase",{"3":{"1416":1}}],["apibaseaddress",{"3":{"1310":1}}],["api=1",{"3":{"1416":1}}],["apiuri",{"3":{"1323":1}}],["apiuserpreferencereader",{"3":{"1199":2,"1203":1,"1207":2,"1299":2,"1323":11,"1495":1}}],["apiuserpreferencewritertests",{"3":{"1199":1,"1207":2,"1323":1,"1437":2}}],["apiuserpreferencewriter",{"3":{"1199":3,"1203":1,"1207":3,"1299":2,"1323":31}}],["apioptions",{"3":{"1323":2}}],["apiece",{"3":{"1395":1}}],["apiexplorer",{"3":{"1310":1}}],["apiexplorersettings",{"3":{"1310":1}}],["apiendpoint",{"2":{"1188":1},"3":{"1184":2,"1188":1,"1323":18,"1415":2,"1435":1,"1440":1,"1488":1,"2149":1}}],["apifiledownloadbuttontests",{"3":{"1199":1,"1207":2,"1323":1,"1437":2}}],["apifiledownloadbutton",{"3":{"1184":2,"1199":2,"1203":1,"1207":2,"1323":12}}],["apikeycredential",{"2":{"1419":1},"3":{"1419":1,"1426":2}}],["apikey",{"2":{"1017":1,"2172":1},"3":{"1017":2,"1091":2,"1096":1,"1418":3,"1426":6,"1466":2,"1491":1,"2172":2}}],["apidescription",{"2":{"451":1,"452":1},"3":{"451":1,"452":1,"1310":1}}],["apiparameterdescription",{"2":{"448":1,"2130":1},"3":{"448":1,"2130":1}}],["apiparameterdescriptorbackfillprovidertests",{"3":{"1199":1,"1207":4}}],["apiparameterdescriptorbackfillprovider",{"2":{"448":1,"2130":1},"3":{"0":1,"448":2,"1199":3,"1203":1,"1207":2,"1310":12,"2130":2}}],["apiversioningtests",{"3":{"448":3,"572":3,"1199":1,"1207":2,"1435":2,"1487":1,"1525":1,"1534":1,"1590":1,"2130":2}}],["apiversion",{"2":{"450":1,"455":1,"1752":1,"1977":1,"2131":1},"3":{"330":1,"350":1,"448":4,"450":1,"455":2,"1308":2,"1310":7,"1375":3,"1379":5,"1414":5,"1435":3,"1488":1,"1495":1,"1525":1,"1590":2,"1752":1,"1977":1,"1987":1,"2130":3,"2131":2}}],["apicontrollerattribute",{"3":{"1435":1}}],["apicontroller",{"2":{"455":1,"1977":1},"3":{"330":1,"350":1,"455":1,"1308":1,"1310":7,"1379":2,"1414":4,"1435":2,"1977":1,"1987":1}}],["apicontrollerbasetests",{"3":{"1199":2,"1207":3,"1310":2}}],["apicontrollerbase",{"2":{"109":1,"111":1,"265":1,"300":1,"1224":1,"1497":1,"1804":1,"1929":1,"2017":1},"3":{"109":6,"111":1,"265":1,"300":2,"306":1,"1199":31,"1203":1,"1207":2,"1224":5,"1229":4,"1270":1,"1299":1,"1308":4,"1310":38,"1311":3,"1322":1,"1370":2,"1379":8,"1395":6,"1401":3,"1414":4,"1435":11,"1437":1,"1486":1,"1488":1,"1495":2,"1497":1,"1804":1,"1926":1,"1929":1,"2017":1}}],["apiclientname",{"3":{"1323":4}}],["apiclientregistrationtests",{"3":{"1199":1,"1207":2,"1323":1}}],["apiclient",{"3":{"0":2,"265":1,"507":1,"514":1,"881":1,"883":1,"1183":1,"1184":2,"1323":28,"1394":2,"1395":6,"1415":3,"1416":3,"1435":3,"2073":1}}],["apisectionname",{"2":{"1323":1},"3":{"1323":3}}],["apisettings",{"2":{"216":1,"219":1,"1184":1,"2151":1},"3":{"214":1,"216":2,"219":1,"649":1,"674":1,"881":1,"1184":2,"1188":1,"1199":20,"1203":1,"1207":2,"1323":21,"1416":1,"1435":1,"1495":1,"1652":1,"2149":2,"2151":2}}],["apis",{"1":{"1905":1,"1906":1,"1907":1,"1908":1,"1909":1,"1910":1,"1911":1},"3":{"0":1,"412":1,"447":1,"774":1,"1050":1,"1310":1,"1323":1,"1338":2,"1349":1,"1368":1,"1379":1,"1395":1,"1414":2,"1416":4,"1435":4,"1545":1,"1547":1,"1626":1,"1734":1,"1905":1,"1925":1,"1992":1,"2115":1,"2128":1,"2129":1,"2132":1,"2207":1}}],["api",{"0":{"138":1,"1545":1,"1639":1,"1665":1,"2165":1,"2200":1,"2207":1},"1":{"445":1,"446":1,"447":1,"448":1,"449":1,"450":1,"451":1,"452":1,"453":1,"454":1,"455":1,"1181":1,"1182":1,"1183":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1,"1310":1,"1369":1,"1370":1,"1371":1,"1372":1,"1373":1,"1374":1,"1375":1,"1376":1,"1377":1,"1378":1,"1379":1,"1997":1,"1998":1,"1999":1,"2000":1,"2001":1,"2128":1,"2129":1,"2130":1,"2131":1,"2132":1},"2":{"61":1,"160":1,"207":1,"250":1,"252":1,"388":1,"460":1,"509":1,"527":1,"789":1,"826":1,"938":1,"1020":2,"1207":1,"1271":1,"1335":1,"1369":1,"1379":7,"1454":1,"1465":1,"1469":1,"1471":1,"1488":1,"1491":1,"1503":1,"1614":1,"1647":1,"1649":2,"1652":1,"1701":1,"1792":1,"1806":1,"1811":1,"1816":1,"1826":1,"1845":1,"1852":1,"1867":1,"1876":1,"1885":1,"1891":1,"1898":1,"1911":1,"1923":1,"1929":1,"1940":1,"1964":1,"1971":1,"1974":1,"1978":1,"1990":1,"1996":1,"2001":1,"2013":1,"2114":1,"2132":1,"2168":1,"2179":1,"2192":1,"2202":1,"2210":1},"3":{"0":26,"4":1,"31":1,"34":1,"42":1,"58":1,"59":1,"61":1,"81":2,"99":1,"103":2,"109":15,"110":1,"111":2,"113":1,"125":2,"130":4,"135":7,"138":1,"139":1,"140":1,"141":1,"142":2,"143":1,"147":1,"150":1,"157":2,"160":5,"161":2,"166":1,"175":3,"178":3,"179":3,"181":1,"186":11,"189":4,"190":2,"206":2,"207":5,"208":2,"209":4,"210":1,"212":1,"213":2,"214":2,"215":1,"216":2,"220":2,"225":1,"235":1,"238":1,"241":1,"243":17,"245":1,"246":1,"250":1,"252":1,"255":4,"256":4,"258":4,"259":3,"265":6,"290":1,"295":2,"296":1,"300":3,"301":1,"303":2,"306":3,"318":13,"325":3,"330":4,"332":1,"337":1,"341":6,"342":1,"343":1,"345":1,"350":1,"352":3,"354":1,"384":2,"386":1,"387":1,"388":3,"391":2,"397":1,"400":1,"407":2,"409":1,"418":2,"419":2,"423":2,"424":1,"426":1,"428":1,"434":3,"445":1,"446":1,"448":20,"449":1,"450":1,"453":1,"454":2,"458":1,"459":4,"460":1,"463":1,"464":2,"470":1,"483":1,"506":3,"507":3,"509":3,"511":2,"517":2,"527":2,"528":6,"539":1,"540":1,"545":3,"549":1,"550":1,"551":1,"556":2,"558":2,"571":2,"572":1,"576":1,"579":1,"583":13,"585":1,"586":2,"591":1,"626":3,"627":2,"628":1,"629":1,"635":1,"656":1,"657":4,"660":1,"674":11,"675":2,"676":2,"690":1,"691":1,"692":1,"698":3,"707":1,"723":1,"725":6,"727":1,"728":1,"733":2,"734":1,"740":3,"741":6,"743":3,"744":1,"749":4,"751":1,"773":2,"774":4,"775":1,"776":2,"789":3,"790":2,"793":1,"799":1,"803":1,"826":1,"827":4,"829":1,"833":1,"854":1,"880":2,"881":6,"882":1,"883":1,"897":3,"905":1,"906":1,"921":1,"922":1,"938":2,"939":1,"940":1,"942":1,"956":1,"960":1,"963":1,"964":3,"965":1,"966":1,"968":1,"972":1,"975":1,"976":1,"979":2,"980":20,"981":1,"982":1,"983":1,"984":1,"995":1,"996":3,"999":1,"1003":1,"1004":7,"1006":3,"1007":1,"1008":2,"1011":1,"1012":1,"1017":2,"1018":3,"1020":2,"1026":2,"1034":2,"1035":1,"1049":1,"1050":5,"1052":2,"1053":1,"1055":2,"1058":1,"1059":3,"1061":2,"1067":2,"1076":1,"1078":1,"1091":1,"1099":2,"1103":1,"1108":2,"1116":3,"1117":1,"1119":1,"1124":4,"1150":2,"1161":1,"1163":1,"1170":1,"1174":1,"1175":1,"1176":1,"1181":1,"1182":1,"1183":5,"1184":9,"1185":3,"1186":6,"1187":5,"1188":2,"1192":4,"1198":6,"1199":387,"1201":2,"1202":6,"1203":198,"1206":10,"1207":1264,"1208":58,"1211":3,"1212":8,"1213":4,"1216":1,"1218":1,"1222":2,"1224":1,"1225":1,"1226":1,"1227":2,"1229":30,"1234":1,"1235":1,"1237":21,"1239":1,"1241":2,"1245":2,"1247":25,"1249":2,"1259":13,"1266":1,"1269":2,"1270":23,"1271":11,"1280":1,"1284":1,"1285":34,"1286":2,"1287":3,"1288":1,"1289":1,"1293":1,"1296":14,"1297":9,"1299":365,"1300":13,"1302":1,"1303":4,"1305":1,"1307":6,"1308":59,"1309":1,"1310":534,"1311":8,"1313":1,"1314":3,"1319":2,"1322":60,"1323":156,"1335":2,"1336":2,"1337":1,"1338":23,"1340":2,"1346":6,"1347":1,"1348":1,"1349":58,"1350":1,"1352":1,"1357":6,"1358":299,"1363":3,"1364":1,"1366":1,"1368":9,"1369":5,"1370":7,"1371":1,"1372":2,"1373":1,"1375":5,"1376":8,"1378":2,"1379":239,"1381":5,"1383":1,"1386":2,"1388":6,"1389":2,"1390":2,"1394":73,"1395":226,"1400":1,"1401":77,"1402":1,"1403":1,"1405":4,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1413":2,"1414":182,"1415":8,"1416":32,"1419":1,"1420":1,"1424":1,"1426":3,"1430":3,"1434":2,"1435":264,"1436":18,"1437":21,"1440":5,"1442":2,"1443":1,"1446":2,"1453":3,"1454":9,"1465":2,"1466":9,"1469":1,"1471":3,"1474":2,"1480":4,"1485":4,"1486":6,"1487":3,"1488":10,"1491":6,"1492":1,"1495":46,"1498":1,"1502":1,"1503":1,"1508":1,"1523":1,"1525":13,"1529":1,"1530":2,"1531":1,"1533":1,"1534":3,"1539":1,"1545":2,"1546":1,"1550":1,"1562":1,"1576":22,"1581":5,"1582":1,"1584":3,"1585":2,"1590":8,"1595":1,"1596":1,"1599":1,"1601":1,"1610":1,"1614":1,"1618":1,"1626":4,"1629":2,"1630":7,"1631":76,"1634":1,"1635":2,"1637":1,"1638":21,"1639":27,"1640":10,"1646":2,"1647":1,"1649":2,"1650":3,"1652":10,"1655":1,"1659":3,"1660":1,"1661":1,"1662":1,"1664":1,"1665":1,"1668":3,"1669":1,"1672":3,"1674":1,"1680":1,"1684":3,"1685":2,"1686":1,"1688":1,"1692":1,"1697":3,"1701":4,"1706":1,"1717":3,"1718":9,"1721":1,"1723":2,"1726":4,"1727":2,"1728":2,"1731":1,"1735":2,"1736":1,"1748":1,"1749":1,"1752":3,"1754":1,"1760":2,"1764":5,"1769":2,"1770":1,"1772":1,"1775":1,"1778":4,"1780":1,"1792":1,"1795":1,"1804":2,"1806":1,"1809":1,"1811":1,"1816":1,"1820":1,"1826":1,"1830":1,"1833":1,"1843":1,"1845":1,"1852":1,"1867":1,"1870":1,"1876":1,"1882":1,"1885":1,"1891":1,"1894":1,"1897":2,"1898":1,"1911":3,"1912":1,"1918":1,"1920":1,"1921":3,"1923":1,"1926":1,"1927":2,"1928":3,"1929":3,"1930":1,"1931":2,"1940":1,"1950":1,"1958":2,"1964":1,"1965":2,"1966":2,"1967":4,"1970":5,"1971":4,"1974":1,"1978":1,"1984":1,"1987":2,"1989":2,"1990":4,"1992":1,"1993":4,"1994":7,"1996":4,"1997":1,"1999":6,"2000":1,"2001":2,"2003":1,"2011":1,"2013":1,"2020":1,"2023":1,"2042":2,"2054":1,"2065":1,"2069":1,"2070":1,"2073":1,"2079":1,"2080":1,"2082":4,"2087":1,"2088":1,"2096":1,"2100":1,"2103":2,"2104":1,"2105":1,"2106":1,"2110":1,"2114":1,"2127":1,"2128":2,"2129":1,"2130":9,"2131":1,"2132":5,"2135":1,"2136":1,"2137":1,"2138":1,"2146":2,"2148":1,"2149":2,"2151":2,"2152":3,"2155":1,"2168":1,"2171":1,"2179":1,"2192":1,"2193":1,"2199":1,"2202":1,"2207":1,"2209":2,"2210":1,"2212":1,"2218":2,"2219":4,"2224":1,"2226":4,"2231":2,"2235":1,"2238":1}}],["app→db",{"3":{"1525":1}}],["apptraces",{"3":{"1466":3,"1475":1}}],["appthemebinding",{"3":{"1415":1}}],["apptheme",{"3":{"1415":2}}],["appxmanifest",{"3":{"1415":2}}],["appwidgetmanager",{"3":{"1415":1}}],["appwidgetprovider",{"3":{"1415":1}}],["appicon",{"3":{"1415":1}}],["appcontext",{"2":{"1035":1},"3":{"1034":1,"1035":1,"1426":2,"1435":1,"1488":1}}],["apparent",{"3":{"1214":1}}],["appaction",{"3":{"1415":3}}],["appactions",{"3":{"1415":5}}],["appactionsinitializer",{"2":{"428":1},"3":{"428":1,"1199":2,"1203":1,"1207":2,"1415":17}}],["appactionroutemaptests",{"3":{"1199":1,"1207":2,"1323":2,"1415":2,"1437":1}}],["appactionroutemap",{"2":{"428":1},"3":{"428":1,"1199":4,"1203":1,"1207":2,"1415":18}}],["appassociationendpointtests",{"3":{"1199":1,"1207":2,"1310":2}}],["appassociationendpointextensions",{"3":{"418":2,"423":1,"424":1,"1199":2,"1203":1,"1207":2,"1208":1,"1310":7,"1495":1}}],["appassociation",{"2":{"429":1},"3":{"418":2,"423":1,"424":1,"425":1,"426":1,"428":2,"429":2,"1310":2,"1480":1}}],["appassociationoptions",{"2":{"418":1,"425":1,"426":1,"428":1},"3":{"418":1,"425":1,"426":1,"428":1,"429":1,"1199":3,"1203":1,"1207":2,"1310":12,"1495":1}}],["appassembly",{"3":{"0":1,"649":1,"905":1,"1415":1}}],["appdatadirectory",{"3":{"1416":1}}],["appdelegate",{"3":{"1199":2,"1203":1,"1207":4,"1212":1,"1415":24,"1416":1,"1495":2}}],["appdependencies",{"2":{"1475":1},"3":{"402":1,"1332":1,"1338":2,"1442":1,"1445":1,"1446":1,"1466":4,"1475":1,"1676":2}}],["appdomainsource",{"3":{"1416":1}}],["appdomain",{"2":{"413":1},"3":{"413":1,"1212":1,"1285":5,"1310":1,"1314":2,"1322":1,"1415":2,"1416":5,"1880":1}}],["appraisal",{"3":{"2219":1}}],["appraiser",{"3":{"2219":1}}],["appresumedeventargs",{"3":{"1199":4,"1203":1,"1207":2,"1416":10,"1495":1}}],["apprendermode",{"3":{"556":1}}],["apprequests",{"2":{"612":1,"1449":1,"1450":1},"3":{"402":1,"612":1,"1332":1,"1338":2,"1442":2,"1445":1,"1446":1,"1449":1,"1450":1,"1466":6,"1475":1,"1676":1,"2009":1}}],["approvals",{"3":{"1454":1}}],["approval",{"3":{"1401":2,"1435":3,"1456":1,"1460":1,"1466":1,"1640":1,"1764":1}}],["approving",{"3":{"1349":1,"1395":1,"1398":1,"1401":1,"1425":1,"1458":1,"1631":1}}],["approvequestionasync",{"3":{"1401":1}}],["approveasync",{"3":{"1395":2,"1401":1}}],["approve",{"3":{"1395":2,"1396":1,"1397":1,"1401":17,"1435":1,"1525":1}}],["approves",{"3":{"1348":1,"1349":1,"1401":1,"1631":1}}],["approver",{"3":{"628":1}}],["approvedquestions",{"3":{"1401":2}}],["approved",{"3":{"522":1,"523":1,"524":1,"1100":1,"1285":1,"1310":1,"1348":1,"1349":4,"1395":4,"1397":2,"1398":1,"1400":3,"1401":46,"1435":1,"1495":14,"1629":2,"1631":8,"2042":1}}],["approximation",{"3":{"1285":1,"1426":1,"1499":2,"2058":1}}],["approximates",{"3":{"1435":1}}],["approximated",{"3":{"1435":1}}],["approximate",{"3":{"827":1,"1338":1,"1416":4,"1426":1,"1590":1}}],["approlename",{"3":{"609":1,"1466":1}}],["appropriately",{"3":{"1237":1,"1561":1}}],["appropriate",{"3":{"86":1,"1285":1,"1323":1,"1349":1,"1415":1,"1416":1,"1538":1,"1548":1,"1567":1,"1631":2,"1638":2,"1764":1}}],["approachingquota",{"2":{"403":1,"612":1},"3":{"403":1,"612":1}}],["approach",{"3":{"69":1,"79":1,"131":1,"455":1,"1060":1,"1259":1,"1285":1,"1394":1,"1435":7,"1444":1,"1637":1,"1638":1,"1818":1,"1829":1,"1888":1}}],["approaches",{"3":{"4":1,"1153":1,"1322":1,"1323":1,"1395":1,"1942":1}}],["appbarcomponenttypes",{"2":{"649":1,"650":1},"3":{"649":2,"650":1,"1323":3,"1394":1}}],["appbar",{"3":{"273":1,"1323":2,"1435":2,"2082":1}}],["appmetrics",{"3":{"0":1,"914":1,"1442":2,"1450":1,"1466":8,"1475":1,"1676":2}}],["appetite",{"3":{"2031":1}}],["appearance",{"3":{"1212":1,"1323":1,"2229":1}}],["appearing",{"3":{"629":1,"827":1,"840":1,"1368":2,"1395":1,"1416":1,"1435":6,"1467":1,"1487":2}}],["appeared",{"3":{"254":1,"454":1,"1323":2,"1491":1,"2118":1}}],["appear",{"3":{"37":1,"303":1,"502":1,"564":1,"799":1,"812":1,"964":1,"1116":1,"1247":2,"1270":1,"1284":1,"1285":5,"1299":3,"1308":1,"1310":1,"1322":3,"1323":4,"1338":3,"1349":2,"1358":6,"1394":7,"1395":6,"1401":2,"1403":1,"1414":2,"1415":3,"1416":4,"1432":1,"1435":26,"1437":4,"1440":1,"1466":3,"1469":1,"1488":1,"1495":1,"1498":1,"1624":1,"1625":2,"1626":1,"1630":1,"1699":1,"1759":1,"1763":1,"2015":1}}],["appears",{"3":{"0":5,"95":1,"265":1,"349":1,"390":1,"420":1,"450":1,"470":1,"490":1,"491":1,"492":2,"493":1,"545":1,"547":1,"549":1,"550":1,"556":1,"599":1,"634":1,"639":1,"649":1,"676":2,"700":1,"773":1,"782":1,"810":1,"827":3,"832":1,"833":1,"838":1,"881":1,"891":1,"963":1,"980":1,"1004":1,"1026":1,"1079":1,"1082":1,"1085":1,"1109":1,"1116":1,"1155":1,"1190":1,"1191":1,"1193":1,"1200":1,"1202":1,"1229":2,"1242":2,"1247":2,"1270":2,"1273":1,"1285":5,"1288":1,"1299":7,"1308":1,"1310":4,"1311":1,"1322":3,"1323":5,"1338":4,"1349":3,"1357":1,"1358":16,"1368":1,"1379":2,"1394":4,"1395":7,"1401":1,"1412":1,"1414":5,"1416":4,"1424":1,"1432":2,"1435":22,"1437":2,"1442":1,"1455":1,"1466":2,"1468":1,"1488":1,"1495":3,"1525":1,"1576":2,"1581":1,"1582":1,"1625":1,"1626":1,"1629":1,"1630":2,"1631":1,"1638":1,"1650":1,"1652":2,"1653":1,"1662":1,"1697":1,"1748":1,"1814":1,"1880":1,"1912":1,"1969":1,"1992":1,"2179":1}}],["appendix",{"0":{"1571":1}}],["appending",{"3":{"749":1,"889":1,"890":2,"1267":1,"1270":1,"1280":1,"1285":4,"1299":1,"1321":1,"1323":1,"1338":2,"1358":1,"1368":1,"1394":1,"1395":1,"1410":1,"1414":1,"1435":2,"1825":1,"2231":1}}],["appendquery",{"3":{"1310":2}}],["appendevent",{"3":{"1299":1}}],["appended",{"3":{"0":4,"21":1,"122":1,"166":1,"545":1,"566":1,"583":1,"601":1,"665":1,"889":1,"890":2,"1034":1,"1140":1,"1174":1,"1195":1,"1212":1,"1242":1,"1247":2,"1270":1,"1279":1,"1285":2,"1299":2,"1310":3,"1311":1,"1322":2,"1323":3,"1338":2,"1358":1,"1368":1,"1394":1,"1414":1,"1416":1,"1435":2,"1437":2,"1454":3,"1465":4,"1660":1,"1663":1,"1700":1,"1728":1,"1748":1,"1765":1,"1778":3,"1919":1,"2088":1}}],["appendline",{"3":{"1299":2,"1495":1}}],["appendable",{"3":{"1237":1}}],["appends",{"3":{"0":1,"123":1,"177":1,"219":1,"246":1,"350":1,"549":1,"633":1,"887":1,"891":1,"905":1,"1133":1,"1237":1,"1247":3,"1267":1,"1269":1,"1270":1,"1281":1,"1285":6,"1299":8,"1310":6,"1322":2,"1323":9,"1338":2,"1358":6,"1379":5,"1382":1,"1385":1,"1387":1,"1388":1,"1394":5,"1395":1,"1401":2,"1411":1,"1414":6,"1415":5,"1416":3,"1435":6,"1488":1,"1665":1,"1726":1,"1839":1,"1851":1,"1919":1,"2196":1,"2231":1}}],["append",{"2":{"1967":1},"3":{"0":1,"137":1,"138":1,"139":1,"140":1,"265":1,"420":1,"633":1,"690":1,"692":1,"719":1,"784":1,"889":1,"891":1,"1140":1,"1142":2,"1144":1,"1179":1,"1192":1,"1212":1,"1242":1,"1280":1,"1285":6,"1299":4,"1310":1,"1322":1,"1323":6,"1358":1,"1394":1,"1395":8,"1435":1,"1466":1,"1495":4,"1628":1,"1630":1,"1637":1,"1838":1,"1967":1,"1974":1,"2231":1}}],["apphostbicepparitytests",{"3":{"1199":1,"1207":2,"1436":2,"1437":2,"1486":1,"1488":2,"1492":1}}],["apphostenvironmentrequirement",{"2":{"1069":1,"1427":1,"1429":1},"3":{"1069":1,"1199":6,"1207":2,"1338":3,"1427":1,"1429":2,"1487":2}}],["apphostenvironmentgatetests",{"3":{"1199":1,"1207":2,"1486":1}}],["apphostenvironmentgate",{"2":{"1067":1,"1429":1,"1448":1,"2055":1},"3":{"1067":3,"1199":3,"1207":2,"1427":1,"1429":4,"1448":1,"1487":2,"2055":1}}],["apphostprobepathstests",{"3":{"1199":1,"1207":2,"1486":1}}],["apphostprobepaths",{"3":{"1067":1,"1199":5,"1207":2,"1427":1,"1429":2,"1487":2}}],["apphostreadinessbudgettests",{"3":{"1199":1,"1207":2,"1486":1}}],["apphostreadinessbudget",{"2":{"1429":1},"3":{"1067":1,"1199":5,"1207":2,"1338":2,"1427":1,"1429":2,"1448":2,"1487":2}}],["apphostcompositionsmoketests",{"3":{"914":1,"1067":1,"1069":1,"1486":1,"1495":1}}],["apphosttestbase",{"2":{"2055":1},"3":{"914":1,"1067":3,"1069":1,"1199":3,"1207":2,"1327":1,"1338":1,"1427":1,"1429":2,"1448":4,"1487":2,"2054":1,"2055":1}}],["apphosttests",{"2":{"1429":1,"1436":1,"1486":1,"1487":1,"1495":1},"3":{"913":2,"917":1,"1067":2,"1069":1,"1199":3,"1203":1,"1206":1,"1207":9,"1429":2,"1436":1,"1485":1,"1486":1,"1487":1,"1495":1}}],["apphostfixturebase",{"2":{"912":1,"1069":1,"1327":1,"1448":1,"1459":1,"2055":1},"3":{"912":1,"913":1,"914":2,"1067":5,"1069":1,"1070":1,"1199":5,"1207":4,"1212":1,"1327":1,"1338":5,"1427":1,"1429":7,"1435":3,"1448":5,"1459":1,"1487":4,"1489":1,"1495":1,"2054":1,"2055":1}}],["apphosts",{"3":{"99":1,"555":1,"556":1,"559":1,"1338":1,"1443":1,"1486":1}}],["apphost",{"0":{"1429":1,"1440":1,"1443":1,"1448":1,"2007":1,"2021":1,"2055":1},"1":{"1064":1,"1065":1,"1066":1,"1067":1,"1068":1,"1069":1,"1070":1,"1071":1},"2":{"913":1,"914":1,"1069":2,"1324":1,"1327":1,"1448":1,"1491":1,"1656":1,"2055":1},"3":{"0":13,"31":2,"55":1,"59":2,"63":2,"92":1,"93":2,"95":2,"96":2,"98":2,"99":1,"104":2,"164":1,"166":1,"350":2,"373":1,"554":3,"556":2,"557":1,"624":1,"626":1,"629":1,"632":3,"633":4,"638":3,"640":13,"641":1,"642":4,"644":5,"837":3,"838":2,"839":2,"841":1,"854":2,"857":2,"912":2,"913":12,"914":8,"915":1,"916":2,"917":5,"918":1,"939":1,"980":1,"1033":1,"1034":1,"1037":1,"1038":1,"1064":1,"1065":1,"1066":5,"1067":14,"1068":1,"1069":7,"1070":1,"1150":2,"1192":2,"1199":4,"1202":1,"1203":5,"1206":2,"1207":12,"1208":2,"1212":5,"1213":1,"1308":3,"1310":5,"1311":2,"1322":3,"1324":3,"1325":10,"1326":2,"1327":5,"1333":1,"1338":86,"1362":1,"1363":2,"1368":3,"1376":2,"1377":2,"1379":5,"1395":4,"1406":2,"1411":1,"1414":5,"1427":1,"1429":7,"1435":7,"1436":6,"1437":5,"1438":1,"1439":3,"1440":26,"1441":2,"1443":11,"1444":1,"1445":5,"1447":1,"1448":6,"1449":1,"1450":3,"1452":7,"1454":4,"1455":6,"1459":3,"1460":2,"1463":1,"1466":2,"1485":4,"1486":5,"1487":5,"1488":5,"1489":13,"1491":6,"1492":1,"1495":6,"1502":1,"1525":9,"1534":1,"1569":1,"1576":4,"1581":1,"1595":2,"1605":1,"1626":2,"1640":1,"1652":8,"1655":1,"1656":1,"1659":1,"1660":1,"1662":1,"1669":2,"1682":1,"1683":2,"1688":1,"1691":1,"1697":1,"1699":2,"1717":5,"1719":1,"1723":1,"1726":7,"1727":1,"1728":2,"1739":1,"1791":1,"1841":1,"1977":1,"2003":2,"2004":2,"2007":4,"2008":1,"2012":1,"2013":5,"2021":1,"2027":1,"2029":1,"2054":2,"2055":3,"2059":1,"2088":1,"2096":1,"2226":1,"2231":4}}],["app",{"0":{"1290":1,"1726":1,"1936":1,"2007":1,"2112":1},"1":{"417":1,"418":1,"419":1,"420":1,"421":1,"422":1,"423":1,"424":1,"425":1,"426":1,"427":1,"428":1,"429":1,"430":1,"631":1,"632":1,"633":1,"634":1,"635":1,"636":1,"1301":1,"1302":1,"1303":1,"1304":1,"1305":1,"1306":1,"1307":1,"1308":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1698":1,"1699":1,"1700":1,"1701":1,"1702":1,"1703":1,"1704":1,"1725":1,"1726":1,"1727":1,"1728":1,"1729":1,"1730":1,"1930":1,"1931":1,"1932":1,"1933":1,"1934":1,"1935":1,"1936":1,"1937":1,"1938":1,"1939":1,"1940":1,"2002":1,"2003":1,"2004":1,"2005":1,"2006":1,"2007":1,"2008":1,"2009":1,"2010":1,"2011":1,"2012":1,"2013":1},"2":{"220":1,"254":1,"256":1,"258":1,"418":1,"425":1,"426":1,"428":1,"454":1,"464":2,"466":1,"554":1,"555":1,"559":1,"648":1,"674":1,"760":1,"774":1,"776":1,"1091":1,"1612":1,"1698":1,"1726":1,"1786":1,"1920":1,"2003":1,"2074":1,"2078":1,"2088":1,"2150":1},"3":{"0":40,"38":1,"39":1,"46":1,"60":1,"68":2,"72":1,"85":1,"92":1,"93":1,"94":2,"95":2,"96":1,"98":1,"101":1,"102":3,"145":2,"147":3,"175":3,"180":1,"186":3,"208":2,"213":2,"215":1,"216":3,"217":1,"219":3,"220":1,"223":2,"224":1,"225":2,"226":1,"230":2,"235":3,"237":1,"241":1,"242":1,"243":3,"254":1,"256":1,"258":2,"259":1,"261":1,"264":1,"265":4,"267":1,"272":1,"273":3,"276":1,"281":1,"284":1,"293":1,"309":1,"310":4,"311":1,"314":3,"349":1,"350":6,"351":3,"352":1,"355":1,"360":1,"378":1,"381":1,"382":1,"405":1,"413":7,"414":1,"417":1,"418":6,"419":8,"420":4,"422":4,"423":3,"424":1,"425":1,"426":2,"427":5,"428":4,"429":1,"433":1,"435":1,"440":1,"441":1,"454":1,"457":1,"459":10,"460":2,"461":2,"463":5,"464":4,"465":5,"466":1,"470":1,"497":3,"498":3,"499":6,"500":1,"511":4,"512":1,"545":1,"551":2,"554":1,"555":2,"556":17,"559":2,"562":1,"563":1,"564":1,"566":1,"576":1,"598":8,"599":10,"600":1,"601":6,"603":1,"617":1,"618":2,"619":1,"620":1,"626":2,"631":5,"632":1,"633":13,"634":1,"635":7,"638":2,"640":5,"641":1,"643":4,"648":2,"649":2,"650":4,"659":1,"664":2,"667":1,"672":1,"674":1,"681":2,"682":1,"683":3,"723":2,"724":8,"725":11,"726":3,"727":3,"749":6,"757":9,"758":1,"759":6,"760":1,"764":1,"765":2,"766":1,"767":1,"768":1,"774":5,"776":1,"782":1,"790":1,"793":1,"827":1,"828":1,"832":3,"833":2,"837":1,"854":3,"857":1,"861":1,"862":1,"863":2,"873":1,"904":1,"905":1,"912":1,"913":2,"914":7,"915":3,"917":1,"918":2,"932":2,"938":1,"941":1,"946":1,"954":1,"959":1,"971":1,"995":1,"998":1,"1017":2,"1018":1,"1022":1,"1034":1,"1036":1,"1037":1,"1058":4,"1059":10,"1060":2,"1063":1,"1067":1,"1083":1,"1091":1,"1092":1,"1093":1,"1117":2,"1125":1,"1126":1,"1150":1,"1154":1,"1156":1,"1163":1,"1178":1,"1184":2,"1186":1,"1190":2,"1192":4,"1194":1,"1199":3,"1202":4,"1203":6,"1207":4,"1208":12,"1211":1,"1212":8,"1237":1,"1270":9,"1271":1,"1282":1,"1283":3,"1285":20,"1286":1,"1287":1,"1288":8,"1289":1,"1290":4,"1291":1,"1292":8,"1294":1,"1295":1,"1296":3,"1297":1,"1298":2,"1299":118,"1300":3,"1301":4,"1303":1,"1307":1,"1308":24,"1309":1,"1310":90,"1311":1,"1319":2,"1321":10,"1322":88,"1323":148,"1324":1,"1325":1,"1331":1,"1332":1,"1337":1,"1338":36,"1349":5,"1351":1,"1358":5,"1363":1,"1368":3,"1379":3,"1394":14,"1395":5,"1408":1,"1414":46,"1415":139,"1416":161,"1420":1,"1424":1,"1426":1,"1427":1,"1432":3,"1433":1,"1435":61,"1437":7,"1440":1,"1441":1,"1442":4,"1444":7,"1445":1,"1446":2,"1454":14,"1455":1,"1456":4,"1458":1,"1462":1,"1463":1,"1464":2,"1466":61,"1467":1,"1468":1,"1469":1,"1471":2,"1472":4,"1473":7,"1475":3,"1476":4,"1477":1,"1478":1,"1479":1,"1480":6,"1486":1,"1487":7,"1488":5,"1489":2,"1491":3,"1495":12,"1499":1,"1511":1,"1523":1,"1525":9,"1530":2,"1533":1,"1549":2,"1561":1,"1564":1,"1576":7,"1581":1,"1582":1,"1584":2,"1585":1,"1588":4,"1589":1,"1590":9,"1594":1,"1595":6,"1596":2,"1598":2,"1599":1,"1600":2,"1601":1,"1605":1,"1610":2,"1612":1,"1640":6,"1645":5,"1646":1,"1647":5,"1648":1,"1649":2,"1650":8,"1652":2,"1653":5,"1654":4,"1655":1,"1656":1,"1657":2,"1660":8,"1665":1,"1666":4,"1668":4,"1669":4,"1670":1,"1672":1,"1673":1,"1676":1,"1680":1,"1683":1,"1684":1,"1685":1,"1686":1,"1687":1,"1688":1,"1689":1,"1690":1,"1691":3,"1693":1,"1694":1,"1695":1,"1697":1,"1698":1,"1700":1,"1701":1,"1703":1,"1704":2,"1705":1,"1706":1,"1708":4,"1713":1,"1716":3,"1717":3,"1718":1,"1719":1,"1720":1,"1721":2,"1722":2,"1724":1,"1725":5,"1726":7,"1727":3,"1728":4,"1729":2,"1730":2,"1739":1,"1749":2,"1772":2,"1780":2,"1783":1,"1784":2,"1786":2,"1809":1,"1824":1,"1883":1,"1895":2,"1900":1,"1920":2,"1921":1,"1929":1,"1930":1,"1931":1,"1932":4,"1935":1,"1944":1,"1948":1,"1963":1,"1965":4,"1969":1,"1971":1,"1972":3,"1973":2,"1974":2,"1975":3,"1976":1,"1977":3,"1978":2,"1980":1,"1982":2,"1999":1,"2000":3,"2001":3,"2002":2,"2003":5,"2005":2,"2006":1,"2011":1,"2013":1,"2024":1,"2032":3,"2033":2,"2035":2,"2041":1,"2042":2,"2054":1,"2056":2,"2059":1,"2060":2,"2062":1,"2066":2,"2069":1,"2071":1,"2072":1,"2074":6,"2076":3,"2078":1,"2080":1,"2081":1,"2082":4,"2084":1,"2085":1,"2086":1,"2088":3,"2096":1,"2097":3,"2100":2,"2106":2,"2110":5,"2112":1,"2113":1,"2114":3,"2115":2,"2120":10,"2123":1,"2125":1,"2126":3,"2127":1,"2136":1,"2146":4,"2149":2,"2150":2,"2151":2,"2152":1,"2154":1,"2155":1,"2156":1,"2157":1,"2159":3,"2177":2,"2193":1,"2194":1,"2195":1,"2196":2,"2197":4,"2200":4,"2201":2,"2202":1,"2207":1,"2208":1,"2209":2,"2212":1,"2218":1,"2219":2,"2220":4,"2226":1,"2227":2,"2230":2,"2231":11,"2233":3,"2238":5}}],["applogsconfiguration",{"2":{"1464":1},"3":{"1464":1,"1466":1}}],["applockkeymigration",{"3":{"1495":1}}],["applock",{"3":{"1416":1}}],["applockenabled",{"3":{"1323":1,"1416":3}}],["appleoauthprivatekeypem",{"3":{"1466":1}}],["applenotification",{"3":{"1322":1}}],["applelogin",{"3":{"1310":1}}],["appleappsiteassociationpath",{"3":{"1310":1}}],["appleappid",{"2":{"429":1},"3":{"428":1,"429":1,"1310":1}}],["appleapplinkcomponents",{"2":{"428":1,"429":1},"3":{"428":1,"429":1,"1310":1}}],["appleenabled",{"3":{"0":1,"350":1,"1323":1}}],["apple",{"1":{"347":1,"348":1,"349":1,"350":1,"351":1,"352":1,"353":1,"354":1,"355":1,"1972":1,"1973":1,"1974":1,"1975":1,"1976":1,"1977":1,"1978":1},"2":{"1467":1},"3":{"0":3,"347":1,"348":1,"349":1,"350":8,"419":1,"420":1,"426":1,"1212":1,"1299":1,"1310":22,"1405":2,"1414":2,"1415":1,"1416":1,"1454":1,"1458":2,"1465":2,"1466":3,"1467":1,"1480":2,"1525":1,"1590":1,"1665":1,"1666":2,"1778":1,"1972":3,"1973":1,"1974":9,"1978":1,"2120":1,"2207":1}}],["applyupvoteasync",{"3":{"1401":2}}],["applydeeplinktarget",{"3":{"1323":1}}],["applyfreshdashboard",{"3":{"1394":4}}],["applyfragment",{"3":{"1323":1}}],["applyfiltertouniqueindexes",{"3":{"1285":1}}],["applyfilters",{"2":{"330":1,"335":1,"591":1,"593":1,"1243":1,"1987":1},"3":{"330":1,"335":1,"591":1,"593":1,"1241":1,"1243":1,"1247":6,"1394":16,"1414":4,"1814":1,"1987":1}}],["applyfieldselection",{"2":{"330":1,"1242":1,"1987":1},"3":{"330":1,"1242":2,"1247":3,"1987":1}}],["applyrestoredstate",{"3":{"1323":1}}],["applyrefreshsessionconfiguration",{"2":{"905":1,"1289":1},"3":{"905":1,"1285":2,"1289":1,"1299":2}}],["applycurrentpagefromurl",{"3":{"1323":1}}],["applyconfiguration",{"3":{"1285":7}}],["applyconfigurationsforentitiesincontext",{"2":{"1034":1},"3":{"1034":1,"1285":9,"1395":1}}],["applyeventdatedefaults",{"3":{"1394":1}}],["applyeventfilter",{"2":{"1391":1},"3":{"1382":1,"1391":1,"1394":9}}],["applyemulatorentityquotas",{"3":{"1322":1}}],["applyengineconventions",{"2":{"1176":1,"1285":1,"1855":1},"3":{"1176":1,"1281":1,"1285":3,"1368":1,"1855":1}}],["applybackpressure",{"3":{"1322":5,"1576":1}}],["applybetween",{"3":{"1247":5}}],["applyall",{"3":{"1395":2,"1414":2}}],["applyallconfigurations",{"3":{"1281":1,"1285":4}}],["applyadditionalfilters",{"3":{"1394":2}}],["applyaiscorefilters",{"3":{"1394":2}}],["applyasync",{"3":{"0":1,"922":1,"1212":1,"1270":5,"1323":3,"1358":5,"1416":1}}],["applyorderingstep",{"3":{"1285":3}}],["applyordering",{"3":{"1247":1,"1285":4}}],["applyincludes",{"2":{"1278":1},"3":{"1278":1,"1285":4}}],["applyincludescriteriaandfilters",{"2":{"1243":1},"3":{"1243":1,"1247":2,"1309":1}}],["applyin",{"3":{"1247":10}}],["applyinorrange",{"3":{"1247":4}}],["applying",{"3":{"0":1,"41":1,"135":1,"178":1,"248":1,"265":1,"291":1,"295":1,"318":1,"363":1,"388":1,"499":1,"633":1,"918":1,"1233":1,"1245":1,"1247":1,"1259":1,"1270":1,"1285":2,"1299":2,"1300":2,"1310":1,"1323":2,"1346":1,"1354":1,"1358":4,"1373":1,"1379":4,"1394":2,"1395":5,"1401":2,"1408":1,"1414":1,"1446":1,"1454":2,"1458":1,"1491":1,"1663":1,"1728":1,"1766":1,"1768":1,"1829":1,"1870":1,"1999":2,"2186":1,"2239":1}}],["applysettingsasync",{"3":{"1395":2}}],["applysearchterm",{"3":{"1395":1}}],["applyspeakerfilters",{"3":{"1394":1}}],["applystoredpreferencesandnavigateasync",{"3":{"1323":2}}],["applyshape",{"3":{"1240":1,"1247":1,"1285":7}}],["applysoftdeletefilters",{"2":{"697":1,"1146":1},"3":{"697":1,"698":1,"1146":1,"1285":2,"1525":1,"1576":1}}],["applysorting",{"2":{"330":1,"549":1,"552":1,"1243":1,"1987":1},"3":{"330":1,"549":1,"552":1,"1242":1,"1243":1,"1247":6,"1358":1,"1414":4,"1987":1}}],["applytenant",{"3":{"1285":2}}],["applytenantfilters",{"2":{"1232":1,"1850":1},"3":{"698":2,"1232":1,"1237":1,"1285":4,"1850":1}}],["applytoprocess",{"3":{"1416":8}}],["applyto",{"2":{"1026":1},"3":{"1026":1,"1766":1}}],["applypermissiongrantconfiguration",{"3":{"1285":2}}],["applypresenceorset",{"3":{"1247":2}}],["applypipeline",{"2":{"466":1,"703":1,"752":1},"3":{"459":1,"464":1,"466":1,"698":1,"703":1,"752":1,"1310":5}}],["applypaging",{"2":{"337":1},"3":{"330":1,"337":1,"549":1,"740":1,"1240":1,"1243":1,"1247":5,"1435":1}}],["apply",{"0":{"1792":1,"1801":1,"1806":1,"1811":1,"1816":1,"1826":1,"1833":1,"1845":1,"1852":1,"1861":1,"1867":1,"1876":1,"1885":1,"1891":1,"1898":1,"1904":1,"1911":1,"1923":1,"1929":1,"1940":1,"1949":1,"1958":1,"1964":1,"1971":1,"1978":1,"1984":1,"1990":1,"1996":1,"2001":1,"2013":1,"2027":1,"2037":1,"2048":1,"2059":1,"2068":1,"2079":1,"2085":1,"2107":1,"2122":1,"2127":1,"2132":1,"2138":1,"2145":1,"2152":1,"2159":1,"2168":1,"2179":1,"2192":1,"2202":1},"2":{"202":1,"1050":1,"1303":1,"1376":1,"1405":1,"1809":1,"2175":1},"3":{"0":3,"100":1,"175":2,"201":1,"202":2,"230":1,"255":1,"265":1,"290":3,"291":3,"292":1,"318":1,"390":1,"538":1,"572":1,"574":1,"740":1,"770":1,"789":1,"827":1,"845":1,"914":1,"922":1,"923":1,"1005":1,"1013":1,"1027":1,"1034":1,"1050":1,"1091":1,"1124":1,"1170":1,"1212":2,"1237":1,"1241":3,"1243":2,"1244":1,"1247":22,"1259":1,"1261":1,"1265":1,"1270":9,"1271":2,"1279":1,"1280":1,"1285":28,"1299":7,"1300":1,"1303":3,"1308":6,"1309":1,"1310":5,"1311":1,"1319":1,"1322":20,"1323":8,"1338":9,"1343":1,"1347":2,"1349":4,"1350":1,"1352":2,"1358":31,"1365":1,"1368":3,"1376":3,"1379":8,"1389":1,"1394":7,"1395":25,"1401":8,"1405":1,"1414":11,"1416":2,"1425":1,"1426":4,"1435":26,"1437":1,"1446":2,"1452":1,"1454":2,"1477":1,"1487":3,"1495":2,"1576":2,"1583":1,"1585":1,"1631":1,"1634":1,"1638":1,"1639":3,"1645":1,"1660":1,"1688":1,"1718":1,"1764":1,"1767":1,"1798":1,"1805":1,"1809":1,"1813":1,"1819":1,"1843":2,"1878":1,"1901":1,"1921":2,"1923":1,"1961":1,"1994":1,"1999":1,"2054":1,"2167":1,"2175":1,"2192":1,"2203":1}}],["applifecycle",{"3":{"1323":4}}],["applifecyclenotifier",{"2":{"2117":1},"3":{"1199":3,"1203":1,"1207":2,"1416":14,"1495":2,"2117":1}}],["appliable",{"3":{"1310":2}}],["applinks",{"3":{"426":1,"1310":3,"1415":1}}],["applicable",{"3":{"1310":2,"1322":1,"1435":2,"1553":1,"1639":1,"1640":1,"1770":1,"2100":1}}],["applicability",{"0":{"62":1},"3":{"1525":1,"1526":1,"1552":1,"1576":1,"1577":1,"2170":1}}],["application+infrastructure+api",{"3":{"1652":1}}],["applicationmodel",{"2":{"1415":1},"3":{"1338":2,"1415":2}}],["applicationpart",{"3":{"1310":1}}],["applicationpipelinecompositiontests",{"3":{"1199":1,"1207":7,"1270":1,"1315":1,"1322":5,"1437":2}}],["applicationnamekey",{"3":{"1322":1}}],["applicationname=",{"3":{"665":1}}],["applicationname",{"2":{"998":1,"999":1},"3":{"665":1,"667":1,"998":1,"999":1,"1322":3,"1335":1,"1338":3,"1466":2}}],["applicationnamespacetests",{"3":{"1199":1,"1207":2,"1322":1}}],["applicationnamespace",{"2":{"25":1,"198":1},"3":{"25":2,"198":2,"228":1,"384":1,"998":1,"999":1,"1199":3,"1203":1,"1207":2,"1300":12,"1312":1,"1319":4,"1322":5,"1495":2}}],["applicationid",{"2":{"424":1},"3":{"424":1,"1310":1}}],["applicationinsights",{"2":{"397":1,"914":1,"1332":1,"1442":1,"1445":1,"1466":1,"2009":1,"2155":1,"2158":1},"3":{"397":1,"914":1,"1332":1,"1338":1,"1442":1,"1445":1,"1466":2,"2009":1,"2155":1,"2158":1}}],["applicationurl",{"3":{"95":2}}],["applicationservices",{"3":{"1435":4}}],["applicationsettingstests",{"3":{"1199":1,"1207":2}}],["applicationsettings",{"2":{"290":1,"291":1,"330":1,"741":1,"1245":1,"1313":1,"1314":1,"1466":1,"1651":1,"1879":1,"2011":1,"2093":1},"3":{"0":1,"290":1,"291":1,"330":1,"583":1,"674":1,"741":4,"1199":46,"1203":1,"1207":2,"1245":1,"1285":5,"1308":8,"1310":16,"1312":1,"1313":2,"1314":1,"1319":3,"1322":15,"1358":4,"1379":8,"1395":11,"1414":11,"1435":2,"1454":1,"1466":1,"1576":1,"1651":1,"1879":1,"2011":1,"2093":1}}],["applicationsourcedirectories",{"3":{"1435":4,"1488":1}}],["applicationstate",{"3":{"1323":1}}],["applicationstarted",{"3":{"235":1,"1333":1,"1338":2,"1435":1}}],["applicationstopped",{"3":{"572":1,"1435":3,"1437":1,"1487":1,"1525":1,"2054":1}}],["applicationstopping",{"3":{"572":1,"1435":3,"1437":1,"1487":1,"1525":1,"2054":1}}],["applications",{"0":{"2227":1},"3":{"0":1,"25":1,"85":1,"228":1,"246":1,"257":1,"259":1,"481":2,"482":1,"543":1,"545":1,"550":2,"551":1,"555":1,"665":1,"698":1,"699":1,"742":1,"773":1,"774":1,"868":1,"869":1,"872":1,"880":1,"920":1,"926":1,"945":1,"1081":1,"1082":2,"1083":1,"1091":1,"1229":1,"1237":1,"1259":2,"1285":2,"1299":4,"1300":4,"1322":2,"1323":1,"1331":1,"1338":1,"1414":5,"1415":1,"1423":1,"1435":2,"1477":1,"1672":1,"1716":1,"1814":2,"1824":1,"1919":1,"2108":2,"2114":1,"2219":2,"2223":1,"2225":2,"2226":1,"2233":1,"2238":1}}],["application",{"0":{"1269":1,"1466":1,"1552":1},"1":{"646":1,"647":1,"648":1,"649":1,"650":1,"651":1,"652":1,"653":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1,"1350":1,"1351":1,"1352":1,"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1,"1415":1},"2":{"117":1,"225":1,"281":1,"310":1,"576":1,"1207":4,"1211":1,"1247":3,"1259":2,"1264":1,"1270":6,"1299":1,"1300":1,"1309":2,"1322":3,"1348":1,"1350":1,"1358":4,"1395":3,"1401":1,"1403":1,"1414":1,"1417":1,"1435":1,"1436":2,"1452":1,"1453":1,"1486":1,"1649":2,"1659":1,"1780":1,"1814":1,"1827":1,"1833":1,"1864":1,"1895":1,"1901":1,"1919":1,"1930":1,"1952":1,"1958":1,"1984":1,"2015":1,"2138":1},"3":{"0":33,"10":1,"15":1,"18":1,"24":1,"25":3,"27":2,"39":1,"47":1,"49":1,"52":2,"53":1,"54":2,"58":1,"59":3,"62":1,"63":1,"71":1,"81":1,"90":1,"103":1,"109":2,"110":1,"117":2,"121":2,"122":3,"123":4,"125":2,"126":2,"128":1,"131":1,"135":1,"150":3,"165":1,"166":1,"167":1,"175":1,"179":1,"186":1,"193":1,"198":4,"203":1,"223":1,"225":4,"226":1,"228":2,"230":7,"231":1,"241":1,"242":2,"243":4,"244":1,"245":2,"246":6,"255":1,"256":1,"281":2,"284":1,"285":1,"295":1,"300":1,"306":1,"310":5,"314":1,"318":4,"326":1,"328":1,"330":5,"331":1,"333":4,"335":1,"336":1,"337":3,"341":4,"343":1,"344":6,"350":1,"352":3,"353":4,"358":3,"359":3,"360":2,"365":1,"378":1,"380":1,"381":2,"384":4,"390":1,"391":3,"395":1,"396":1,"397":5,"399":1,"400":1,"401":3,"407":2,"408":1,"434":2,"440":2,"443":1,"444":2,"448":1,"459":11,"461":1,"463":7,"464":2,"465":6,"466":3,"470":1,"476":1,"481":2,"482":2,"483":1,"490":1,"493":3,"494":2,"495":2,"497":8,"498":1,"499":3,"502":2,"506":1,"508":1,"522":2,"523":3,"524":2,"535":1,"536":3,"539":3,"544":4,"545":26,"547":1,"549":6,"550":2,"551":5,"552":5,"555":1,"556":2,"558":1,"566":1,"572":2,"576":1,"582":1,"583":6,"584":1,"585":1,"586":3,"591":1,"593":2,"601":3,"602":1,"604":1,"633":2,"640":2,"646":1,"648":1,"649":1,"657":2,"665":2,"674":16,"676":1,"677":1,"690":3,"692":2,"698":1,"707":2,"708":1,"714":2,"715":1,"718":2,"723":1,"724":4,"725":6,"726":1,"727":1,"728":1,"729":1,"733":1,"736":1,"740":1,"741":4,"749":2,"782":6,"789":2,"790":4,"791":1,"793":2,"794":1,"795":1,"799":2,"813":2,"819":1,"827":4,"828":1,"845":1,"846":6,"848":2,"849":1,"850":1,"854":2,"857":2,"870":1,"875":1,"881":1,"888":1,"890":1,"897":6,"899":1,"900":1,"905":9,"906":2,"907":1,"908":3,"910":1,"914":1,"921":3,"922":8,"923":1,"924":4,"926":12,"927":1,"930":2,"932":1,"933":1,"938":2,"939":2,"940":1,"941":1,"946":5,"954":1,"956":1,"960":1,"979":1,"980":12,"982":2,"983":1,"987":3,"988":4,"994":1,"995":1,"996":4,"998":2,"999":1,"1004":8,"1006":1,"1012":1,"1017":2,"1018":3,"1020":1,"1022":1,"1025":2,"1026":2,"1033":1,"1034":1,"1035":1,"1036":1,"1037":1,"1038":1,"1041":1,"1042":3,"1049":1,"1050":3,"1051":1,"1058":1,"1059":7,"1067":1,"1068":1,"1069":1,"1074":5,"1078":1,"1082":2,"1090":2,"1091":7,"1093":2,"1099":1,"1100":5,"1116":3,"1119":1,"1133":6,"1140":1,"1142":1,"1150":3,"1158":1,"1161":5,"1168":4,"1170":1,"1174":1,"1175":1,"1192":2,"1194":1,"1199":1459,"1201":2,"1202":4,"1203":774,"1206":10,"1207":4526,"1208":22,"1211":4,"1212":11,"1213":2,"1214":1,"1216":2,"1223":1,"1224":1,"1229":12,"1230":1,"1236":1,"1237":14,"1238":3,"1239":6,"1240":1,"1241":5,"1242":3,"1243":6,"1244":2,"1246":4,"1247":160,"1248":2,"1249":1,"1252":3,"1256":1,"1257":6,"1258":1,"1259":63,"1260":2,"1261":3,"1262":3,"1263":1,"1264":11,"1265":3,"1266":1,"1267":5,"1268":2,"1269":4,"1270":311,"1271":101,"1272":1,"1273":1,"1276":1,"1278":5,"1279":3,"1281":1,"1284":9,"1285":234,"1287":1,"1288":6,"1289":2,"1290":6,"1291":3,"1292":5,"1293":6,"1294":3,"1295":1,"1296":8,"1299":338,"1300":48,"1301":4,"1302":4,"1303":5,"1304":1,"1305":6,"1306":1,"1307":5,"1308":160,"1309":87,"1310":68,"1311":8,"1312":3,"1313":2,"1314":4,"1315":11,"1316":1,"1317":6,"1318":3,"1319":14,"1321":15,"1322":292,"1323":22,"1324":1,"1332":2,"1335":1,"1336":2,"1338":13,"1339":1,"1340":2,"1345":1,"1346":4,"1347":1,"1348":2,"1349":82,"1350":4,"1351":20,"1352":21,"1353":10,"1354":8,"1355":4,"1356":4,"1357":3,"1358":1970,"1359":1,"1360":1,"1364":2,"1365":1,"1366":5,"1368":28,"1369":1,"1373":2,"1376":1,"1378":1,"1379":22,"1388":1,"1389":1,"1394":8,"1395":295,"1397":1,"1398":1,"1399":3,"1400":1,"1401":181,"1403":3,"1404":2,"1405":3,"1406":1,"1408":2,"1409":3,"1410":1,"1412":2,"1414":303,"1415":23,"1416":7,"1417":1,"1420":1,"1422":3,"1423":2,"1426":10,"1428":2,"1429":2,"1430":3,"1434":1,"1435":229,"1436":6,"1437":20,"1439":1,"1440":1,"1441":2,"1442":4,"1445":1,"1451":1,"1452":1,"1453":2,"1454":7,"1459":1,"1460":1,"1461":1,"1464":3,"1466":20,"1468":1,"1470":1,"1472":1,"1485":1,"1486":6,"1487":1,"1488":22,"1489":1,"1491":2,"1495":45,"1498":1,"1502":1,"1503":1,"1508":1,"1518":2,"1525":15,"1526":2,"1533":2,"1534":1,"1535":3,"1537":1,"1539":4,"1550":1,"1552":2,"1554":2,"1571":2,"1574":1,"1575":2,"1576":30,"1577":2,"1581":3,"1585":2,"1590":14,"1591":1,"1598":1,"1599":3,"1601":1,"1619":1,"1638":1,"1645":1,"1649":4,"1650":5,"1652":1,"1653":3,"1655":3,"1659":2,"1660":1,"1661":2,"1662":2,"1665":1,"1668":1,"1669":3,"1670":2,"1672":1,"1678":1,"1680":1,"1684":11,"1685":8,"1686":1,"1692":2,"1717":1,"1718":1,"1719":1,"1723":1,"1730":1,"1747":1,"1755":1,"1764":21,"1780":5,"1782":1,"1783":1,"1788":1,"1789":3,"1791":1,"1792":2,"1795":2,"1798":2,"1804":1,"1809":1,"1812":1,"1814":9,"1816":2,"1819":1,"1823":2,"1827":1,"1829":1,"1830":1,"1832":1,"1833":1,"1839":1,"1841":2,"1851":1,"1853":2,"1860":2,"1863":1,"1864":4,"1865":1,"1871":1,"1874":1,"1875":1,"1876":1,"1882":1,"1883":1,"1895":1,"1901":2,"1914":2,"1915":1,"1918":1,"1919":2,"1921":1,"1925":1,"1928":1,"1930":2,"1931":2,"1932":1,"1933":2,"1934":1,"1935":1,"1939":1,"1940":1,"1943":1,"1947":1,"1952":1,"1955":1,"1958":1,"1960":1,"1961":1,"1962":1,"1969":1,"1971":1,"1984":1,"1993":1,"1994":1,"1996":1,"1997":1,"1999":4,"2002":1,"2004":1,"2005":1,"2009":1,"2013":1,"2014":2,"2015":4,"2016":1,"2023":1,"2027":1,"2033":1,"2035":1,"2041":1,"2042":7,"2050":1,"2051":1,"2054":1,"2055":2,"2060":1,"2062":1,"2065":3,"2070":1,"2076":1,"2079":1,"2087":1,"2094":1,"2098":2,"2100":1,"2103":5,"2109":2,"2110":2,"2125":1,"2131":1,"2138":1,"2139":2,"2140":3,"2141":2,"2143":3,"2153":1,"2155":1,"2156":1,"2162":1,"2165":2,"2168":1,"2169":1,"2170":2,"2174":4,"2176":1,"2179":1,"2192":1,"2213":1,"2218":1,"2219":3,"2220":1,"2226":5,"2229":3,"2231":4,"2239":2}}],["applicationdbcontexteffactory",{"3":{"1495":1}}],["applicationdbcontexttests",{"3":{"1199":1,"1207":4}}],["applicationdbcontexttenantfiltertests",{"3":{"1199":1,"1207":2,"1285":3,"1437":2}}],["applicationdbcontext",{"2":{"0":1,"26":1,"47":1,"200":1,"201":1,"345":1,"696":1,"703":1,"713":1,"715":2,"720":1,"798":1,"806":1,"889":1,"892":1,"893":1,"1033":1,"1036":1,"1042":1,"1050":1,"1083":1,"1086":1,"1174":1,"1175":2,"1192":1,"1201":1,"1212":1,"1259":1,"1272":1,"1276":1,"1285":2,"1362":1,"1411":1,"1509":1,"1516":1,"1663":1,"1848":1,"1856":1,"2063":1,"2198":1},"3":{"0":3,"26":3,"47":1,"166":1,"200":4,"201":1,"203":1,"341":1,"345":1,"536":1,"540":1,"696":2,"697":1,"698":6,"702":1,"703":2,"707":1,"710":2,"713":1,"715":4,"720":3,"798":2,"806":2,"888":1,"889":3,"892":1,"893":1,"905":3,"907":1,"931":1,"934":1,"1033":1,"1034":3,"1036":1,"1037":1,"1042":4,"1050":2,"1055":1,"1059":2,"1061":1,"1062":1,"1083":2,"1086":2,"1140":3,"1146":1,"1150":1,"1156":1,"1174":1,"1175":4,"1192":1,"1198":1,"1199":97,"1201":1,"1202":1,"1203":2,"1207":3,"1212":3,"1232":4,"1233":1,"1237":10,"1251":2,"1253":2,"1258":2,"1259":16,"1272":1,"1273":9,"1274":2,"1276":2,"1278":1,"1280":6,"1281":1,"1282":1,"1285":153,"1299":6,"1322":13,"1358":1,"1362":2,"1368":9,"1395":9,"1411":1,"1414":2,"1435":6,"1436":2,"1495":4,"1497":1,"1509":1,"1516":1,"1525":1,"1576":3,"1650":1,"1663":1,"1809":1,"1848":1,"1856":1,"2063":2,"2163":1,"2198":2}}],["applicationlayers",{"3":{"1435":1}}],["applicationlayersdeclarehandlers",{"3":{"1435":1}}],["applicationlayer",{"2":{"0":1,"700":1,"1814":1},"3":{"0":1,"545":1,"700":1,"1435":5,"1814":1}}],["appliedid",{"3":{"1323":2}}],["applied",{"0":{"2141":1},"2":{"1140":1},"3":{"0":1,"23":3,"24":2,"27":1,"67":1,"72":1,"80":1,"98":1,"103":1,"116":1,"135":1,"136":1,"156":1,"179":2,"186":2,"200":1,"212":1,"231":1,"235":1,"237":1,"246":1,"248":1,"265":1,"292":1,"293":1,"318":1,"335":3,"343":1,"359":2,"361":1,"371":1,"376":1,"384":1,"400":1,"401":2,"403":1,"427":1,"430":1,"440":1,"459":1,"461":1,"464":1,"478":1,"483":2,"485":1,"535":1,"539":1,"548":1,"552":1,"557":1,"595":1,"640":1,"657":3,"658":1,"659":1,"666":1,"690":1,"697":1,"698":1,"707":2,"715":1,"740":1,"741":1,"751":1,"759":1,"766":1,"769":1,"798":1,"806":1,"827":2,"829":1,"830":1,"831":1,"832":1,"837":1,"838":1,"865":2,"872":2,"876":1,"883":1,"890":1,"905":1,"907":1,"988":1,"996":1,"1018":1,"1028":1,"1042":1,"1059":1,"1100":1,"1116":1,"1119":1,"1124":1,"1137":1,"1140":1,"1191":1,"1229":1,"1232":1,"1237":8,"1239":1,"1240":1,"1241":2,"1242":1,"1243":3,"1244":1,"1247":7,"1259":4,"1261":1,"1265":1,"1267":1,"1270":7,"1271":6,"1273":3,"1275":1,"1277":1,"1279":1,"1285":36,"1296":1,"1298":1,"1299":19,"1300":4,"1308":7,"1309":2,"1310":20,"1311":5,"1317":1,"1319":1,"1322":14,"1323":20,"1324":1,"1325":1,"1326":1,"1327":1,"1329":1,"1333":1,"1335":1,"1336":1,"1338":18,"1339":1,"1342":1,"1346":1,"1349":12,"1351":2,"1352":1,"1358":69,"1361":2,"1362":1,"1368":7,"1372":1,"1373":1,"1379":8,"1388":1,"1394":11,"1395":33,"1398":1,"1400":1,"1401":10,"1409":1,"1414":8,"1416":8,"1422":1,"1426":3,"1434":1,"1435":35,"1437":6,"1439":1,"1440":1,"1442":2,"1446":1,"1452":1,"1454":1,"1456":1,"1460":1,"1462":1,"1464":1,"1466":6,"1468":1,"1471":1,"1472":1,"1473":1,"1477":1,"1480":1,"1487":2,"1488":1,"1491":1,"1495":3,"1524":1,"1525":5,"1530":2,"1533":1,"1536":4,"1538":1,"1544":2,"1551":1,"1565":1,"1576":6,"1590":2,"1636":2,"1646":1,"1664":1,"1665":1,"1674":1,"1688":1,"1706":1,"1763":1,"1764":4,"1766":1,"1770":1,"1773":1,"1796":1,"1813":1,"1819":1,"1838":1,"1839":1,"1843":1,"1850":1,"1851":1,"1868":1,"1873":1,"1919":3,"1945":1,"1953":1,"1993":1,"2000":1,"2029":1,"2030":1,"2037":1,"2062":1,"2069":1,"2073":1,"2079":2,"2110":1,"2133":1,"2146":1,"2161":1,"2173":1,"2176":1,"2184":1,"2191":1,"2231":1}}],["appliertype",{"3":{"1270":1}}],["appliers",{"3":{"1262":1,"1265":1,"1270":1,"1315":1,"1322":2,"1358":1,"1661":1,"1874":1,"1882":1}}],["applier",{"3":{"0":3,"263":1,"265":2,"291":1,"293":1,"328":1,"920":2,"922":2,"923":1,"924":2,"925":1,"926":8,"1260":1,"1265":3,"1270":28,"1285":2,"1300":2,"1322":1,"1323":4,"1353":1,"1358":37,"1416":3,"1435":1,"1454":2,"1495":2,"1870":1,"1955":1}}],["appliesto",{"3":{"1368":2}}],["applies",{"0":{"1919":1},"1":{"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"294":1,"295":1,"296":1},"3":{"0":8,"80":1,"95":1,"100":2,"117":1,"130":1,"152":1,"162":1,"166":1,"173":2,"175":1,"193":1,"195":1,"243":1,"280":1,"288":1,"291":2,"292":1,"295":1,"318":3,"333":1,"340":1,"341":1,"345":1,"359":1,"370":1,"392":1,"397":1,"404":1,"458":1,"501":1,"511":1,"536":2,"549":1,"550":1,"551":2,"556":2,"562":1,"563":2,"564":2,"568":2,"572":1,"592":3,"607":1,"611":1,"618":1,"622":1,"628":1,"631":1,"634":2,"635":1,"640":1,"651":2,"674":1,"676":1,"684":1,"691":1,"698":1,"701":2,"711":1,"728":1,"733":1,"741":1,"743":1,"744":1,"749":4,"757":1,"768":1,"774":2,"817":1,"818":1,"838":1,"842":1,"846":1,"847":2,"875":1,"876":1,"883":1,"889":1,"891":1,"917":1,"926":1,"963":1,"965":1,"972":2,"973":1,"1000":1,"1017":1,"1018":1,"1034":1,"1037":1,"1044":1,"1049":1,"1050":1,"1052":2,"1059":1,"1074":1,"1108":1,"1116":2,"1124":1,"1127":1,"1128":1,"1134":1,"1162":1,"1193":1,"1212":3,"1229":3,"1237":4,"1240":1,"1243":1,"1244":2,"1247":13,"1253":1,"1259":29,"1261":1,"1262":1,"1270":9,"1271":6,"1273":3,"1275":2,"1276":1,"1281":1,"1285":47,"1291":1,"1299":32,"1300":2,"1303":2,"1305":1,"1308":1,"1309":2,"1310":31,"1311":4,"1313":1,"1317":2,"1322":28,"1323":55,"1327":1,"1328":1,"1330":1,"1332":1,"1337":1,"1338":16,"1347":2,"1349":17,"1352":1,"1356":1,"1358":54,"1362":1,"1365":1,"1368":6,"1372":1,"1379":8,"1394":24,"1395":39,"1400":1,"1401":18,"1408":1,"1413":1,"1414":14,"1415":14,"1416":14,"1417":1,"1420":1,"1421":1,"1426":4,"1432":1,"1435":59,"1437":5,"1440":2,"1441":1,"1442":2,"1446":1,"1453":1,"1454":2,"1456":1,"1466":4,"1469":1,"1476":1,"1478":1,"1487":1,"1488":1,"1489":1,"1491":2,"1509":1,"1576":2,"1626":1,"1630":1,"1638":3,"1639":2,"1640":1,"1649":1,"1651":1,"1663":1,"1665":1,"1666":1,"1672":1,"1700":1,"1718":1,"1727":1,"1729":1,"1749":1,"1754":1,"1775":1,"1814":2,"1820":2,"1842":2,"1855":1,"1868":1,"1891":1,"1902":1,"1909":1,"1927":1,"1934":1,"1952":1,"1993":3,"1994":1,"1996":1,"1999":1,"2011":1,"2024":1,"2044":1,"2056":1,"2067":1,"2096":1,"2097":1,"2110":1,"2125":3,"2131":1,"2133":1,"2135":1,"2155":1,"2159":1,"2163":1,"2166":1,"2191":1,"2192":2,"2198":1,"2231":2}}],["appsidentity",{"3":{"1466":3}}],["appsec",{"3":{"1338":1}}],["appsetting",{"3":{"312":1,"948":1}}],["appsettingsalt",{"3":{"1415":1}}],["appsettings",{"2":{"92":1,"95":1,"96":1,"102":2,"418":1,"422":1,"534":1,"584":1,"794":1,"836":1,"837":1,"838":1,"840":1,"841":1,"857":1,"933":1,"1066":1,"1107":1,"1128":1,"1178":1,"1428":1,"1480":1,"1489":1,"1655":1,"1683":1,"1700":1,"1718":1,"1719":1,"1721":1,"1722":1,"1728":1,"1849":1,"2096":1},"3":{"0":1,"59":1,"63":1,"92":3,"95":4,"96":2,"98":1,"102":2,"104":1,"179":1,"180":3,"195":7,"418":3,"422":2,"423":1,"424":1,"425":1,"426":4,"428":2,"429":3,"534":2,"536":1,"539":1,"540":1,"583":6,"584":1,"585":3,"633":1,"639":1,"641":1,"690":1,"691":1,"725":2,"727":1,"774":4,"776":1,"790":2,"792":1,"794":1,"827":9,"831":1,"832":13,"833":4,"836":1,"837":1,"838":11,"839":1,"840":2,"841":1,"854":4,"857":1,"933":1,"998":1,"999":1,"1017":1,"1018":4,"1021":1,"1066":1,"1091":1,"1107":1,"1116":1,"1124":9,"1125":1,"1126":1,"1128":1,"1178":1,"1184":1,"1285":7,"1299":5,"1300":3,"1308":1,"1310":2,"1311":2,"1312":1,"1319":1,"1322":3,"1323":3,"1336":1,"1337":2,"1338":22,"1379":18,"1394":1,"1395":6,"1406":1,"1414":1,"1415":14,"1416":1,"1418":1,"1426":1,"1428":1,"1429":1,"1435":7,"1437":1,"1440":5,"1445":1,"1446":26,"1449":1,"1450":1,"1466":4,"1480":2,"1487":2,"1489":1,"1495":3,"1525":10,"1530":1,"1531":1,"1534":1,"1547":1,"1590":4,"1626":3,"1655":1,"1683":2,"1700":1,"1718":1,"1719":1,"1721":1,"1722":1,"1727":1,"1728":1,"1760":1,"1848":1,"1849":1,"2096":1,"2149":1,"2166":1,"2172":1}}],["apps",{"0":{"94":1,"719":1,"1784":1,"1982":1,"2109":1,"2150":1},"1":{"1716":1,"1717":1,"1718":1,"1719":1,"1720":1,"1721":1,"1722":1,"1723":1,"1724":1,"2108":1,"2109":1,"2110":1,"2111":1,"2112":1,"2113":1,"2114":1},"2":{"759":1},"3":{"0":25,"31":1,"61":1,"62":1,"63":1,"91":1,"93":1,"94":2,"96":1,"97":2,"98":2,"104":1,"146":2,"147":1,"148":1,"175":2,"184":1,"206":2,"207":1,"208":1,"213":1,"214":2,"216":1,"233":1,"234":2,"235":1,"243":1,"245":1,"265":1,"283":2,"290":1,"291":1,"293":1,"299":1,"310":3,"314":2,"369":1,"390":2,"392":1,"395":1,"402":1,"412":1,"422":2,"457":2,"459":2,"463":1,"465":1,"483":1,"490":2,"491":1,"492":1,"493":1,"494":1,"497":3,"499":3,"500":1,"511":1,"517":1,"551":1,"552":1,"555":1,"556":2,"597":1,"599":9,"600":2,"602":1,"603":3,"618":1,"620":1,"625":1,"626":2,"629":3,"640":1,"648":1,"649":1,"650":1,"656":1,"665":3,"713":1,"718":1,"719":1,"724":2,"725":3,"726":2,"727":1,"748":1,"749":1,"756":2,"757":5,"759":2,"760":3,"764":1,"765":1,"766":2,"780":3,"784":1,"791":1,"826":1,"827":3,"832":1,"833":1,"838":1,"853":1,"854":1,"855":1,"857":1,"860":1,"861":3,"862":1,"863":3,"865":1,"867":2,"869":3,"875":2,"882":1,"913":1,"971":2,"972":2,"995":1,"996":1,"1058":3,"1060":2,"1101":1,"1123":1,"1124":1,"1154":1,"1156":1,"1202":1,"1203":1,"1212":5,"1237":4,"1247":4,"1259":2,"1264":1,"1270":7,"1271":2,"1285":12,"1294":1,"1299":13,"1308":4,"1310":12,"1312":1,"1321":2,"1322":21,"1323":14,"1324":1,"1338":26,"1358":1,"1414":2,"1415":1,"1416":8,"1432":1,"1435":18,"1437":3,"1441":1,"1442":3,"1446":5,"1454":9,"1464":2,"1466":43,"1468":2,"1471":1,"1472":1,"1473":6,"1476":5,"1477":1,"1485":2,"1487":1,"1491":3,"1495":2,"1523":1,"1524":1,"1525":4,"1533":2,"1534":1,"1554":1,"1564":1,"1573":1,"1574":2,"1576":3,"1581":1,"1588":2,"1590":1,"1595":1,"1601":1,"1644":1,"1645":1,"1649":1,"1657":1,"1660":2,"1669":1,"1672":1,"1673":1,"1675":1,"1676":3,"1691":1,"1704":2,"1705":1,"1716":1,"1717":1,"1725":1,"1726":1,"1778":1,"1781":1,"1783":1,"1784":1,"1791":1,"1798":1,"1810":1,"1838":1,"1874":2,"1899":1,"1902":1,"1903":1,"1908":1,"1952":1,"1957":1,"1962":1,"1967":1,"1971":1,"1981":1,"1999":2,"2000":1,"2005":2,"2009":1,"2010":1,"2024":2,"2032":1,"2036":1,"2042":2,"2046":1,"2054":1,"2058":1,"2076":1,"2082":1,"2086":1,"2107":1,"2108":3,"2109":1,"2110":2,"2111":1,"2112":2,"2113":3,"2122":1,"2137":1,"2146":1,"2149":1,"2151":1,"2152":1,"2157":2,"2193":3,"2197":2,"2201":1,"2202":1,"2209":2,"2212":1,"2213":1,"2218":1,"2220":1,"2227":1,"2228":1,"2231":1,"2238":1,"2242":1,"2244":1}}],["agile",{"3":{"2218":1,"2219":1}}],["aggregator",{"3":{"1307":1,"1308":4}}],["aggregating",{"3":{"1259":2,"1308":1,"1331":1,"1349":3,"1358":1,"1395":2}}],["aggregation",{"3":{"692":1,"1229":1,"1278":1,"1285":3,"1308":5,"1311":1,"1349":1,"1358":5,"1368":1,"1394":2,"1395":3,"1414":1,"1437":1,"1440":1,"1447":1,"2066":1}}],["aggregations",{"3":{"545":1,"547":1,"1395":1,"1435":1}}],["aggregatewithoutdeleteoverride",{"3":{"1435":1}}],["aggregatewithnochildentities",{"3":{"1435":2}}],["aggregateroots",{"2":{"1488":1},"3":{"1435":6,"1488":3}}],["aggregaterootshaveresultfactory",{"2":{"1576":1},"3":{"1430":1,"1576":1}}],["aggregaterootentity",{"3":{"1285":1}}],["aggregaterootentitycontrollerbasetests",{"3":{"1199":1,"1207":6,"1310":2}}],["aggregaterootentitycontrollerbase",{"2":{"318":1,"328":1,"921":1,"922":1,"925":1,"1665":1,"1990":1},"3":{"0":2,"318":1,"328":1,"330":5,"332":1,"334":1,"921":3,"922":1,"925":1,"1199":11,"1203":1,"1207":2,"1270":4,"1310":15,"1358":5,"1370":1,"1379":10,"1435":2,"1665":1,"1987":1,"1990":1}}],["aggregateconvention",{"3":{"1575":1,"1576":1}}],["aggregateconventiontests",{"2":{"1810":1,"2047":1},"3":{"1168":1,"1199":1,"1207":2,"1435":9,"1488":3,"1492":1,"1576":2,"1810":1,"2047":1}}],["aggregateconventiontestsbase",{"2":{"1168":1},"3":{"1168":2,"1199":2,"1207":2,"1430":4,"1435":28,"1488":1,"1495":1,"1576":1,"1585":1}}],["aggregatecapture",{"3":{"1199":3,"1203":1,"1207":1,"1274":1,"1285":5,"1495":2}}],["aggregated",{"3":{"107":1,"1017":1,"1220":1,"1285":2,"1349":2,"1358":2,"1379":2,"1394":2,"1395":3,"1414":1,"1426":1,"1534":1,"1640":2,"1764":1}}],["aggregate",{"0":{"1233":1,"1290":1,"1342":1,"1345":1,"1404":1,"2089":1},"1":{"919":1,"920":1,"921":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"1165":1,"1166":1,"1167":1,"1168":1,"1169":1,"1170":1,"1171":1},"2":{"1380":1},"3":{"0":27,"16":1,"17":1,"18":1,"21":4,"24":3,"39":4,"41":1,"42":1,"107":1,"109":1,"110":2,"164":1,"165":3,"166":1,"167":2,"168":1,"171":1,"175":1,"181":1,"225":1,"227":1,"230":1,"283":1,"314":1,"339":1,"340":1,"341":5,"342":1,"344":1,"350":2,"353":2,"402":1,"413":1,"443":1,"444":1,"459":2,"460":1,"466":1,"468":1,"470":2,"476":1,"497":2,"499":3,"536":2,"537":2,"541":1,"545":1,"551":1,"572":1,"609":1,"633":4,"636":1,"639":1,"656":1,"657":2,"658":1,"661":1,"688":1,"690":3,"691":1,"707":1,"724":1,"725":3,"726":1,"781":1,"782":2,"785":1,"799":1,"809":4,"810":3,"813":3,"815":2,"856":1,"881":1,"904":1,"905":1,"907":1,"919":1,"920":1,"921":7,"922":8,"923":4,"924":7,"925":1,"926":14,"930":1,"1004":10,"1006":1,"1025":1,"1026":1,"1027":1,"1028":1,"1041":2,"1042":1,"1043":1,"1052":1,"1058":1,"1082":2,"1114":1,"1116":3,"1117":2,"1120":1,"1140":5,"1141":1,"1142":5,"1143":2,"1144":2,"1145":1,"1147":1,"1150":1,"1160":1,"1161":1,"1165":1,"1167":1,"1168":6,"1169":1,"1170":2,"1171":1,"1191":1,"1192":2,"1201":1,"1202":3,"1203":3,"1212":10,"1220":1,"1222":1,"1224":1,"1226":1,"1229":5,"1230":1,"1231":4,"1233":9,"1237":44,"1240":1,"1247":4,"1248":2,"1249":2,"1251":3,"1255":1,"1259":16,"1260":1,"1265":12,"1270":85,"1272":1,"1274":4,"1275":1,"1278":2,"1280":1,"1282":1,"1285":58,"1286":1,"1288":1,"1289":1,"1290":1,"1292":1,"1293":1,"1294":1,"1299":31,"1300":1,"1303":3,"1308":28,"1310":22,"1311":2,"1317":1,"1321":2,"1322":43,"1323":5,"1327":1,"1338":2,"1339":3,"1340":1,"1341":6,"1342":2,"1343":2,"1344":2,"1345":3,"1346":1,"1348":1,"1349":155,"1351":4,"1352":5,"1353":6,"1354":1,"1356":2,"1358":422,"1365":1,"1368":26,"1370":3,"1373":1,"1379":45,"1384":1,"1385":2,"1394":29,"1395":104,"1397":3,"1398":1,"1401":55,"1402":2,"1403":1,"1404":5,"1405":1,"1406":1,"1407":2,"1408":1,"1409":2,"1410":1,"1412":1,"1414":65,"1430":3,"1435":72,"1436":2,"1437":7,"1440":2,"1441":1,"1442":1,"1446":1,"1449":1,"1466":1,"1486":4,"1487":1,"1488":6,"1489":1,"1495":6,"1497":1,"1516":1,"1525":5,"1540":1,"1544":1,"1546":1,"1576":5,"1581":1,"1582":1,"1584":3,"1585":2,"1590":3,"1629":18,"1630":9,"1631":17,"1633":1,"1634":8,"1636":2,"1637":27,"1638":5,"1639":6,"1640":1,"1645":1,"1646":1,"1649":1,"1650":6,"1653":1,"1659":1,"1660":1,"1661":2,"1664":2,"1665":1,"1670":1,"1676":1,"1682":1,"1684":6,"1685":6,"1688":1,"1694":2,"1696":2,"1698":1,"1699":1,"1700":5,"1703":1,"1718":2,"1719":1,"1726":16,"1727":5,"1728":4,"1729":8,"1746":1,"1747":1,"1748":3,"1749":1,"1763":2,"1764":2,"1768":1,"1772":1,"1804":1,"1806":1,"1809":3,"1810":3,"1811":1,"1835":1,"1837":3,"1841":1,"1855":1,"1860":1,"1868":1,"1870":3,"1874":1,"1875":4,"1921":1,"1926":1,"1932":3,"1933":1,"1936":2,"1947":1,"1952":1,"1976":2,"1981":1,"1984":1,"2000":1,"2009":1,"2019":1,"2042":1,"2062":2,"2063":1,"2064":1,"2065":1,"2067":1,"2082":1,"2084":1,"2086":3,"2088":3,"2089":4,"2090":4,"2091":1,"2096":1,"2097":2,"2103":1,"2109":3,"2116":1,"2122":1,"2163":2,"2166":1,"2167":1,"2168":1,"2180":1,"2182":3,"2191":2,"2192":1,"2197":2,"2226":1,"2231":3}}],["aggregatescascadesoftdeletetochildren",{"3":{"1435":14}}],["aggregateswithchildcollections",{"3":{"1435":2}}],["aggregates",{"0":{"1341":1,"1397":1},"1":{"1230":1,"1231":1,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":1,"1807":1,"1808":1,"1809":1,"1810":1,"1811":1},"3":{"0":2,"39":2,"95":1,"109":1,"383":1,"497":3,"691":1,"698":1,"781":1,"921":1,"922":1,"924":1,"1058":1,"1066":1,"1068":1,"1140":2,"1141":1,"1168":2,"1169":1,"1192":1,"1202":2,"1203":2,"1212":3,"1222":1,"1229":2,"1230":1,"1237":12,"1247":1,"1251":1,"1254":1,"1259":4,"1270":2,"1274":1,"1275":1,"1285":11,"1290":1,"1299":4,"1301":1,"1302":2,"1308":2,"1310":2,"1322":4,"1323":1,"1327":1,"1338":1,"1339":1,"1340":1,"1341":3,"1344":1,"1345":4,"1349":10,"1350":1,"1351":6,"1353":1,"1358":42,"1370":1,"1379":3,"1395":19,"1397":2,"1398":1,"1400":1,"1401":6,"1429":1,"1435":10,"1437":3,"1440":2,"1442":1,"1466":2,"1475":1,"1486":1,"1495":3,"1525":2,"1533":1,"1540":5,"1546":2,"1576":1,"1584":1,"1599":1,"1629":1,"1634":2,"1637":2,"1638":1,"1650":1,"1670":1,"1680":1,"1688":1,"1719":1,"1764":1,"1807":1,"1810":1,"1811":1,"1814":1,"1837":1,"1931":1,"1936":1,"2055":1,"2103":1,"2109":1,"2185":1,"2205":1,"2226":1,"2229":1,"2238":1}}],["aggressive",{"3":{"501":1,"907":1,"1983":1,"1984":1,"2036":1}}],["agreeing",{"3":{"1270":2,"1285":2,"1299":1,"1322":1,"1323":1,"1379":1,"1414":1,"1435":1}}],["agreeingmarkedqueryhandler",{"3":{"1199":2,"1207":1}}],["agreeingmarkedquery",{"3":{"1199":2,"1207":1}}],["agreeingmarkedcommandhandler",{"3":{"1199":2,"1207":1}}],["agreeingmarkedcommand",{"3":{"1199":2,"1207":1,"1270":1}}],["agrees",{"3":{"650":1,"703":1,"839":1,"974":1,"1229":1,"1323":1,"1401":1,"2038":2}}],["agreement",{"3":{"265":1,"301":1,"560":1,"781":1,"1005":1,"1270":1,"1285":1,"1299":1,"1300":1,"1323":1,"1338":1,"1358":2,"1394":2,"1395":1,"1435":1,"1652":1,"2085":1,"2137":1}}],["agreed",{"3":{"77":1,"648":1,"1270":1,"1299":1,"1323":1,"1358":3,"1394":1,"1395":3}}],["agree",{"3":{"0":2,"109":1,"217":1,"220":1,"243":1,"265":1,"272":1,"302":1,"443":1,"539":1,"540":1,"558":1,"611":1,"889":1,"890":1,"905":1,"907":1,"954":1,"956":1,"959":1,"1018":1,"1116":1,"1176":1,"1229":1,"1237":1,"1247":3,"1259":1,"1267":1,"1270":2,"1271":1,"1279":1,"1284":1,"1285":9,"1299":10,"1300":2,"1308":3,"1310":5,"1322":3,"1323":12,"1338":2,"1346":1,"1358":12,"1368":1,"1394":2,"1401":7,"1414":3,"1416":3,"1435":4,"1466":1,"1596":1,"1630":1,"1639":1,"1696":1,"1828":1,"1832":1,"1926":1,"1936":1,"1952":1,"2023":1,"2045":1,"2081":1,"2082":1,"2134":1,"2136":1,"2166":1}}],["ago",{"2":{"1474":1},"3":{"0":2,"535":1,"539":1,"905":3,"1247":1,"1285":2,"1358":2,"1435":2,"1454":1,"1464":2,"1474":2,"1843":1,"1886":1,"2160":2}}],["again",{"1":{"796":1,"797":1,"798":1,"799":1,"800":1,"801":1,"802":1,"803":1,"804":1,"805":1,"806":1,"1834":1,"1835":1,"1836":1,"1837":1,"1838":1,"1839":1,"1840":1,"1841":1,"1842":1,"1843":1,"1844":1,"1845":1},"3":{"0":3,"21":1,"130":2,"178":1,"251":2,"252":1,"254":1,"295":1,"335":1,"390":1,"395":2,"418":1,"425":1,"426":1,"443":1,"468":2,"488":1,"489":1,"508":1,"520":1,"524":1,"534":1,"539":1,"543":1,"551":1,"552":1,"607":1,"624":1,"696":1,"758":1,"776":1,"792":1,"796":1,"797":1,"799":1,"800":1,"819":1,"825":1,"827":1,"861":1,"869":1,"876":1,"905":1,"907":1,"934":1,"996":1,"1014":1,"1018":1,"1024":1,"1043":1,"1044":1,"1049":1,"1090":1,"1094":1,"1117":1,"1144":1,"1212":1,"1227":1,"1229":1,"1237":1,"1239":1,"1242":1,"1247":2,"1251":1,"1253":1,"1259":6,"1269":1,"1270":6,"1271":1,"1275":1,"1282":1,"1285":13,"1299":6,"1300":1,"1303":2,"1305":1,"1308":3,"1310":10,"1319":1,"1322":8,"1323":17,"1331":1,"1332":1,"1337":1,"1338":3,"1349":2,"1358":15,"1361":1,"1365":1,"1368":5,"1379":4,"1389":1,"1390":1,"1394":14,"1395":17,"1401":10,"1409":1,"1414":9,"1415":2,"1416":5,"1425":1,"1435":11,"1437":2,"1446":1,"1452":1,"1453":2,"1464":1,"1466":1,"1480":1,"1487":1,"1491":1,"1495":2,"1590":1,"1631":2,"1639":1,"1683":1,"1684":1,"1685":1,"1748":2,"1764":2,"1768":1,"1814":1,"1834":1,"1907":1,"1923":1,"1949":1,"1974":1,"1991":1,"2025":1,"2069":2,"2074":1,"2081":1,"2082":1,"2146":1,"2164":1,"2170":1,"2189":2,"2191":1,"2205":1,"2231":1}}],["against",{"0":{"1951":1,"2166":1},"1":{"1779":1,"1780":1,"1781":1,"1782":1,"1783":1,"1784":1,"1785":1,"1786":1,"1793":1,"1794":1,"1795":1,"1796":1,"1797":1,"1798":1,"1799":1,"1800":1,"1801":1},"3":{"0":37,"21":1,"31":2,"42":1,"53":1,"57":2,"62":1,"63":1,"80":1,"82":1,"90":1,"92":1,"98":1,"99":2,"100":1,"109":3,"121":1,"125":1,"128":1,"130":1,"135":3,"136":3,"137":3,"139":1,"143":1,"147":1,"150":2,"151":1,"159":1,"179":1,"182":1,"185":1,"186":1,"189":1,"196":1,"201":1,"206":1,"217":1,"220":1,"225":1,"234":1,"235":2,"245":2,"249":1,"250":1,"251":1,"252":1,"255":1,"257":1,"265":6,"280":1,"282":1,"284":2,"286":2,"295":1,"312":1,"314":1,"320":2,"322":1,"328":1,"333":1,"341":1,"342":1,"344":1,"350":1,"363":1,"365":1,"370":1,"371":1,"380":1,"405":1,"409":1,"427":1,"439":1,"446":1,"447":1,"451":1,"458":1,"459":1,"463":1,"464":1,"465":1,"466":1,"468":1,"472":2,"478":1,"486":1,"489":1,"497":3,"499":3,"502":1,"507":1,"510":1,"511":1,"536":2,"537":1,"538":2,"539":1,"543":1,"556":2,"563":1,"564":2,"566":1,"572":7,"573":1,"576":2,"577":1,"583":1,"585":3,"586":1,"591":3,"592":2,"593":1,"599":1,"608":1,"609":5,"610":1,"611":3,"618":1,"620":1,"625":1,"626":6,"628":1,"631":2,"632":1,"633":5,"640":3,"649":1,"657":1,"665":1,"682":1,"696":1,"698":2,"713":1,"715":2,"717":1,"718":1,"727":1,"733":2,"740":1,"743":1,"756":1,"759":2,"760":1,"765":3,"767":1,"768":2,"797":1,"798":1,"799":1,"804":1,"805":1,"810":2,"811":1,"812":1,"813":1,"818":1,"820":1,"821":1,"825":3,"827":3,"828":1,"832":1,"833":1,"837":1,"838":2,"842":2,"845":1,"859":1,"860":2,"861":4,"862":3,"863":2,"875":1,"876":1,"881":1,"890":1,"897":1,"905":3,"906":1,"910":1,"912":1,"914":1,"917":1,"918":1,"926":3,"930":1,"933":1,"941":1,"946":2,"948":1,"953":1,"954":1,"955":1,"966":1,"971":1,"973":1,"974":1,"976":1,"982":2,"983":1,"988":1,"989":1,"998":2,"1004":1,"1011":1,"1012":4,"1014":2,"1016":1,"1018":6,"1020":1,"1026":3,"1028":1,"1034":2,"1036":1,"1044":1,"1050":2,"1052":1,"1054":2,"1055":1,"1059":3,"1061":1,"1066":1,"1067":6,"1069":1,"1075":1,"1076":2,"1082":1,"1089":1,"1091":2,"1092":1,"1108":2,"1116":1,"1124":1,"1125":1,"1126":1,"1135":1,"1140":1,"1161":1,"1184":1,"1186":2,"1196":1,"1210":1,"1212":4,"1213":3,"1214":1,"1216":3,"1224":1,"1229":1,"1237":5,"1238":1,"1240":1,"1241":1,"1243":1,"1244":4,"1247":18,"1249":1,"1252":1,"1254":2,"1256":2,"1258":2,"1259":10,"1262":1,"1263":1,"1265":1,"1266":1,"1267":2,"1269":1,"1270":32,"1271":3,"1273":1,"1275":1,"1276":1,"1278":2,"1280":2,"1282":1,"1283":1,"1284":1,"1285":76,"1287":1,"1288":1,"1289":1,"1292":2,"1296":2,"1297":1,"1299":71,"1300":14,"1303":1,"1305":1,"1306":1,"1308":13,"1309":6,"1310":28,"1311":2,"1314":2,"1316":3,"1322":39,"1323":67,"1325":2,"1327":3,"1329":1,"1331":2,"1333":1,"1336":2,"1338":42,"1339":1,"1341":2,"1343":2,"1345":1,"1346":3,"1349":26,"1351":1,"1352":2,"1356":2,"1357":2,"1358":136,"1364":2,"1365":2,"1368":10,"1371":1,"1372":5,"1374":1,"1376":1,"1379":12,"1381":1,"1382":1,"1383":1,"1384":1,"1387":1,"1389":1,"1393":1,"1394":57,"1395":46,"1397":1,"1398":1,"1401":19,"1404":1,"1412":1,"1413":2,"1414":30,"1415":6,"1416":21,"1417":1,"1418":1,"1419":1,"1420":1,"1426":10,"1427":2,"1428":1,"1429":1,"1430":7,"1431":2,"1433":1,"1434":4,"1435":219,"1436":5,"1437":28,"1440":1,"1441":3,"1442":3,"1444":4,"1445":3,"1446":2,"1447":1,"1448":2,"1449":1,"1452":9,"1454":8,"1455":3,"1457":4,"1459":3,"1460":1,"1461":1,"1464":4,"1466":17,"1468":1,"1470":1,"1473":3,"1474":3,"1475":4,"1476":1,"1479":1,"1480":1,"1483":2,"1485":3,"1486":8,"1487":7,"1488":7,"1489":9,"1491":8,"1494":1,"1495":49,"1498":1,"1503":1,"1515":1,"1518":1,"1521":1,"1523":1,"1524":1,"1525":8,"1526":2,"1530":3,"1533":2,"1535":2,"1536":1,"1544":1,"1547":1,"1548":1,"1550":1,"1566":1,"1571":2,"1572":1,"1573":1,"1574":1,"1576":14,"1577":1,"1581":2,"1586":1,"1588":1,"1590":4,"1591":1,"1595":3,"1598":2,"1599":1,"1605":1,"1610":2,"1630":4,"1631":2,"1638":2,"1639":2,"1640":5,"1642":1,"1647":1,"1648":1,"1652":5,"1653":2,"1655":1,"1658":1,"1660":2,"1662":1,"1665":1,"1666":3,"1667":1,"1668":1,"1670":4,"1671":2,"1672":2,"1674":1,"1682":1,"1683":1,"1684":1,"1694":2,"1695":1,"1697":1,"1705":1,"1706":3,"1715":2,"1718":2,"1719":1,"1721":2,"1726":6,"1727":2,"1728":2,"1730":3,"1735":1,"1739":1,"1746":1,"1747":2,"1748":2,"1749":2,"1764":6,"1766":2,"1778":1,"1779":3,"1781":1,"1782":1,"1783":1,"1789":2,"1792":1,"1793":2,"1794":1,"1796":1,"1799":1,"1800":1,"1801":2,"1809":2,"1811":1,"1814":4,"1815":1,"1816":2,"1821":1,"1825":1,"1838":1,"1839":1,"1846":1,"1851":1,"1858":1,"1860":1,"1862":1,"1870":1,"1888":1,"1890":2,"1894":1,"1896":2,"1897":1,"1898":2,"1899":1,"1900":1,"1901":1,"1908":1,"1910":1,"1914":1,"1917":2,"1918":2,"1919":1,"1922":1,"1924":1,"1926":1,"1928":1,"1929":1,"1933":1,"1943":1,"1944":1,"1947":1,"1949":1,"1961":1,"1965":1,"1973":1,"1981":2,"1982":1,"1989":1,"1995":2,"1998":2,"1999":1,"2000":2,"2001":1,"2008":1,"2020":1,"2023":1,"2029":1,"2033":4,"2039":1,"2040":2,"2043":4,"2044":4,"2045":2,"2046":6,"2047":1,"2052":3,"2054":3,"2055":1,"2056":2,"2058":3,"2061":1,"2065":1,"2066":1,"2071":1,"2073":1,"2078":3,"2081":1,"2082":2,"2084":1,"2085":1,"2086":1,"2088":2,"2095":1,"2096":1,"2099":2,"2103":1,"2107":1,"2113":1,"2116":1,"2122":1,"2125":3,"2126":1,"2128":1,"2129":1,"2140":1,"2143":2,"2144":1,"2148":1,"2151":1,"2154":1,"2156":1,"2157":3,"2163":1,"2166":2,"2167":1,"2179":1,"2185":1,"2191":2,"2197":1,"2203":1,"2204":2,"2213":1,"2219":1,"2230":1,"2231":3,"2233":1,"2244":1}}],["agnostic",{"0":{"1360":1},"3":{"0":1,"59":1,"109":1,"110":1,"118":1,"164":1,"166":1,"167":1,"177":1,"265":2,"507":1,"690":1,"774":1,"1068":1,"1192":1,"1212":1,"1217":1,"1229":1,"1247":1,"1248":1,"1259":3,"1271":1,"1278":1,"1285":7,"1299":2,"1300":2,"1308":1,"1310":4,"1311":2,"1322":1,"1323":6,"1331":1,"1338":1,"1349":1,"1350":1,"1358":6,"1359":1,"1394":1,"1395":4,"1401":2,"1411":1,"1414":5,"1415":2,"1416":7,"1426":1,"1435":6,"1437":1,"1444":1,"1495":2,"1509":1,"1575":1,"1650":1,"1652":1,"1662":1,"1719":1,"1764":1,"1782":1,"1789":1,"1792":2,"1804":1,"1925":1,"1932":1,"1934":1,"1936":1,"2000":1,"2015":1,"2078":1,"2082":1,"2084":1,"2085":1,"2103":1,"2178":1,"2226":1,"2229":1}}],["aged",{"3":{"1464":1}}],["agerangespec",{"3":{"1199":2,"1207":1}}],["agegreaterthanspecification",{"3":{"1199":2,"1207":1}}],["agegreaterthanspec",{"3":{"1199":2,"1207":1}}],["agenda",{"3":{"387":1,"1308":5,"1349":1,"1350":1,"1357":1,"1358":4,"1368":2,"1435":3,"1437":1,"1488":1,"1630":1,"1921":2}}],["agents",{"2":{"636":1},"3":{"633":1,"636":2,"914":1,"917":1,"1004":1,"1007":1,"1167":1,"1212":1,"1495":1,"1525":3,"1530":1,"1569":1,"1576":2,"1590":1,"2238":2}}],["agentic",{"3":{"0":1,"1569":1,"1590":1,"2238":1}}],["agent",{"3":{"0":2,"1":1,"774":2,"905":1,"1013":1,"1067":1,"1069":1,"1090":2,"1169":1,"1282":1,"1285":2,"1299":3,"1310":1,"1323":2,"1338":1,"1394":1,"1405":1,"1414":2,"1429":1,"1435":5,"1437":1,"1448":1,"1464":1,"1552":3,"1576":2,"1581":1,"2170":1,"2220":1,"2238":5}}],["age=",{"3":{"1435":1}}],["age=0",{"3":{"217":1,"1323":1,"1338":1,"2148":1}}],["age=31536000",{"3":{"214":1,"1338":1,"1358":1,"2148":1}}],["ages",{"3":{"0":1,"903":1,"905":2,"907":1,"930":1,"1259":1,"1310":1,"1338":1,"1454":1,"1590":1,"1800":1,"1983":1}}],["age",{"2":{"24":1,"2155":1,"2190":1},"3":{"0":2,"15":1,"24":3,"200":1,"501":1,"536":2,"538":1,"540":1,"572":1,"591":1,"594":1,"625":1,"626":3,"627":1,"628":1,"629":1,"707":2,"733":1,"735":1,"809":1,"1256":1,"1259":5,"1285":3,"1297":1,"1299":1,"1300":2,"1323":5,"1394":2,"1444":1,"1454":1,"1459":1,"1464":1,"1466":1,"1474":1,"1475":1,"1487":1,"1664":1,"1981":1,"1983":2,"1984":1,"2054":1,"2155":3,"2158":1,"2166":2,"2167":1,"2190":3}}],["ahead",{"3":{"0":3,"62":1,"107":1,"109":1,"111":1,"170":1,"202":2,"214":1,"217":1,"265":1,"284":1,"290":1,"296":1,"348":1,"350":1,"353":1,"401":1,"459":1,"462":1,"464":1,"465":1,"534":1,"749":1,"751":1,"790":2,"848":1,"988":2,"1012":1,"1076":1,"1117":1,"1259":2,"1264":1,"1265":1,"1267":1,"1270":2,"1285":8,"1299":1,"1310":5,"1322":3,"1323":3,"1332":1,"1338":3,"1343":1,"1358":21,"1368":2,"1382":1,"1394":3,"1395":6,"1401":2,"1414":2,"1415":1,"1416":1,"1435":4,"1454":2,"1488":1,"1495":2,"1685":1,"1798":1,"1820":1,"1824":1,"1851":1,"1887":1,"1890":1,"1919":1,"1976":1,"2121":1,"2125":1,"2156":1}}],["amd64",{"3":{"1464":1}}],["am",{"0":{"2061":1},"3":{"1395":1,"1414":1,"1415":1,"1416":4,"1435":1,"2077":1,"2115":2,"2135":1,"2212":2}}],["amber",{"3":{"1323":2}}],["ambiguities",{"3":{"1638":1}}],["ambiguity",{"1":{"985":1,"986":1,"987":1,"988":1,"989":1,"990":1,"991":1,"992":1},"3":{"0":1,"109":1,"361":1,"592":1,"629":1,"985":1,"987":1,"988":5,"989":1,"990":1,"991":1,"1270":1,"1285":5,"1292":1,"1299":1,"1368":1,"1379":1,"1394":1,"1435":1,"1453":1,"1576":1,"1663":1,"1839":1,"2231":1}}],["ambiguousmatchexception",{"3":{"1323":2}}],["ambiguoussource",{"2":{"1839":1,"1851":1},"3":{"988":1,"1285":1,"1839":1,"1851":1}}],["ambiguous",{"0":{"1839":1},"3":{"0":1,"109":1,"318":1,"320":1,"324":1,"325":1,"326":1,"617":1,"709":1,"861":1,"988":3,"992":1,"1191":1,"1196":1,"1270":3,"1278":1,"1285":4,"1322":1,"1323":2,"1349":1,"1358":3,"1394":1,"1395":1,"1414":4,"1435":5,"1495":16,"1499":1,"1610":1,"1660":1,"1663":1,"1728":1,"1839":2,"1851":1}}],["ambiently",{"3":{"1285":1}}],["ambienthandler",{"3":{"1247":2}}],["ambientscope",{"2":{"1246":1},"3":{"1199":2,"1203":1,"1207":1,"1246":1,"1247":3,"1495":1}}],["ambientorigin",{"2":{"26":1,"202":1,"702":1,"1040":1,"1042":1,"1253":1,"1317":1,"2185":1,"2192":1},"3":{"0":1,"26":4,"202":3,"702":2,"1040":1,"1042":3,"1199":5,"1203":1,"1207":2,"1253":1,"1259":5,"1285":3,"1312":1,"1317":6,"1322":14,"1495":2,"2185":1,"2192":1}}],["ambient",{"1":{"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1},"3":{"0":3,"15":1,"202":1,"359":1,"365":1,"372":1,"583":1,"585":1,"899":1,"905":1,"926":1,"988":1,"1158":1,"1161":1,"1212":1,"1229":1,"1237":2,"1246":2,"1247":4,"1251":1,"1253":2,"1259":9,"1263":2,"1265":1,"1270":10,"1271":2,"1274":1,"1277":1,"1278":1,"1285":16,"1295":1,"1299":5,"1305":1,"1308":6,"1310":5,"1312":1,"1317":2,"1322":5,"1323":11,"1349":3,"1351":1,"1358":21,"1394":1,"1395":8,"1401":2,"1411":1,"1414":2,"1416":1,"1435":8,"1495":1,"1525":1,"1576":1,"1584":1,"1590":2,"1663":1,"1839":1,"2059":1,"2134":1,"2141":1,"2231":1}}],["america",{"3":{"1349":1,"1358":2,"1363":1,"1368":1,"1383":1,"1394":3,"1437":1,"1629":1,"1630":1,"1636":1,"2219":1}}],["american",{"3":{"1020":1,"1091":1,"1423":1}}],["amends",{"1":{"730":1,"731":1,"732":1,"733":1,"734":1,"735":1,"736":1,"737":1},"3":{"0":5,"247":2,"432":1,"730":1,"731":1,"817":1,"836":1,"929":1,"1212":5,"2031":1,"2231":2}}],["amendments",{"3":{"66":1,"72":1,"827":1,"1338":1}}],["amendment",{"0":{"81":1},"3":{"0":1,"76":1,"81":2,"82":1,"230":1,"241":1,"243":1,"249":1,"258":1,"395":1,"409":1,"411":2,"438":1,"825":2,"827":2,"1079":1,"1338":1,"1454":1,"1457":1,"1466":1,"1599":1,"1747":1,"1754":1,"1764":1,"2024":1}}],["amended",{"0":{"401":1,"402":1,"407":1},"3":{"0":16,"15":1,"57":1,"66":1,"76":1,"145":7,"184":1,"223":1,"241":3,"243":3,"244":1,"253":1,"263":1,"265":2,"328":2,"386":6,"395":14,"397":1,"408":1,"409":1,"411":1,"439":1,"446":1,"526":2,"534":3,"680":2,"688":2,"691":1,"737":1,"817":1,"842":1,"1073":1,"1212":1,"1495":1,"1576":1,"1630":1}}],["amqpendpoint",{"3":{"1338":1}}],["amqpendpointname",{"3":{"1338":1}}],["amqptargetport",{"3":{"1338":1}}],["amqp",{"3":{"626":1,"640":1,"1075":1,"1322":2,"1326":1,"1338":5,"1435":3,"1437":2,"1443":1,"1454":1}}],["amplify",{"3":{"2036":1}}],["amplifies",{"3":{"2006":1}}],["amplified",{"3":{"1338":1}}],["amplifier",{"3":{"369":1,"1310":1,"1323":2}}],["amplification",{"3":{"227":1,"717":1,"792":1,"1259":1,"1323":1,"1331":1,"1338":1,"1939":1}}],["ample",{"3":{"361":1,"1285":1,"1299":1,"2144":1}}],["amortized",{"3":{"1533":1}}],["amortizes",{"3":{"459":1}}],["amortises",{"3":{"1247":1}}],["amounts",{"3":{"1229":1,"1237":1,"1323":4,"1766":1}}],["amount",{"2":{"660":1},"3":{"284":1,"536":1,"592":1,"657":5,"658":1,"660":1,"683":1,"1026":2,"1027":1,"1234":1,"1237":6,"1247":1,"1281":1,"1285":3,"1323":2,"1388":1,"1394":3,"1395":3,"1401":1,"1414":1,"1435":5,"1466":1,"1748":2,"1763":3,"1764":2,"1766":3,"1769":1,"1775":1,"1809":1,"1908":1,"1940":1,"2046":1,"2072":1}}],["among",{"3":{"0":2,"111":1,"132":1,"175":1,"386":1,"549":1,"551":1,"577":1,"591":1,"628":1,"633":1,"655":1,"743":1,"765":1,"766":1,"891":1,"1116":1,"1124":1,"1196":1,"1213":1,"1229":1,"1237":1,"1239":1,"1243":1,"1247":1,"1270":1,"1285":2,"1308":1,"1322":2,"1323":2,"1338":1,"1349":2,"1358":6,"1368":3,"1394":1,"1403":1,"1414":2,"1416":2,"1426":1,"1435":3,"1466":1,"1468":1,"1495":8,"1525":1,"1581":1,"1582":1,"1630":1,"1634":1,"1721":1,"1823":1,"1838":1,"1962":1,"1999":1,"2042":1,"2086":1}}],["amr",{"2":{"2196":1},"3":{"0":1,"1059":1,"1299":1,"2196":1}}],["ac5b175",{"3":{"1495":1}}],["acompiledpermission",{"3":{"1435":1}}],["acontrollerthatinheritsitsdecisionfromanabstractbase",{"3":{"1435":3}}],["achieved",{"3":{"1338":1,"1394":1}}],["achieves",{"3":{"809":1}}],["acute",{"3":{"1323":1}}],["acme",{"2":{"1717":1,"1722":1},"3":{"1270":1,"1717":1,"1718":2,"1722":1,"1850":1,"1919":3}}],["acyclicfixtures",{"3":{"1207":1,"1435":2}}],["acyclicconsumer",{"3":{"1199":1,"1207":1,"1435":3}}],["acyclicity",{"3":{"846":1,"1247":1,"1270":1,"1435":4,"1670":1}}],["acyclic",{"3":{"507":1,"513":1,"1207":2,"1285":1,"1323":2,"1416":3,"1430":1,"1435":7,"1571":1}}],["acquiring",{"3":{"707":1,"1270":1,"1297":1,"1299":3,"1323":1,"1338":1,"1394":1,"1908":1,"1911":1}}],["acquireasynccore",{"3":{"1310":2}}],["acquireasync",{"3":{"1270":3,"1299":3,"1300":1}}],["acquireaccesstokenasync",{"2":{"507":1},"3":{"507":1,"1323":6,"1416":1,"1435":1}}],["acquires",{"3":{"0":1,"510":1,"524":1,"549":1,"996":1,"1145":1,"1299":1,"1300":2,"1310":1,"1323":3,"1395":1}}],["acquire",{"3":{"0":1,"157":2,"827":1,"863":1,"996":2,"998":1,"1270":2,"1285":1,"1299":1,"1300":1,"1310":1,"1315":1,"1322":7,"1323":3,"1401":1,"1416":1,"1435":1,"1437":1,"1576":2,"1908":1,"2173":1}}],["acquired",{"3":{"0":1,"688":1,"1299":1,"1310":6,"1322":1,"1323":4,"1435":1}}],["acquisitions",{"3":{"507":1}}],["acquisition",{"3":{"506":1,"507":1,"513":2,"550":1,"551":1,"996":3,"997":1,"1269":1,"1299":1,"1310":1,"1315":1,"1322":9,"1323":6,"1416":2,"1668":1,"1908":2}}],["acknowledging",{"3":{"1305":1,"1395":1,"1932":1}}],["acknowledge",{"3":{"1416":1}}],["acknowledges",{"3":{"1395":1,"2168":1}}],["acknowledgement",{"3":{"988":1,"1285":1,"1338":1,"1379":1,"1437":1,"1839":1}}],["acknowledged",{"3":{"790":1,"794":1,"1322":1,"1358":1,"1414":1}}],["acks",{"3":{"1259":2,"1322":1}}],["acked",{"3":{"1258":1,"1259":1,"1310":2,"1354":1,"1412":1,"1414":1,"1909":1}}],["acking",{"3":{"195":1,"1259":1,"1395":1}}],["ack",{"3":{"194":1,"201":2,"1259":2,"1322":2}}],["acl",{"3":{"0":1,"1575":1,"1576":1}}],["acted",{"3":{"1237":1,"1285":1,"1323":1,"1349":1,"1394":1,"1401":2}}],["acts",{"3":{"435":1,"538":1,"601":1,"741":1,"793":1,"813":1,"821":1,"1228":1,"1233":1,"1285":1,"1310":1,"1317":1,"1322":1,"1323":1,"1358":2,"1401":1,"1414":2,"1435":2,"1437":1,"1454":1,"1472":1,"1477":1,"1650":1,"1821":1,"1839":1,"1842":1,"2132":1}}],["actual",{"2":{"1456":1,"1457":1},"3":{"93":1,"235":1,"265":1,"368":1,"529":1,"666":1,"707":1,"765":1,"767":1,"800":1,"839":1,"1237":1,"1241":1,"1247":2,"1251":1,"1259":1,"1270":1,"1271":1,"1280":1,"1284":1,"1285":7,"1299":5,"1306":1,"1309":1,"1310":1,"1322":7,"1323":6,"1338":2,"1349":1,"1358":5,"1368":3,"1379":1,"1394":5,"1395":7,"1401":2,"1414":5,"1415":1,"1416":3,"1426":1,"1428":1,"1435":10,"1437":1,"1440":1,"1454":1,"1456":2,"1457":2,"1465":1,"1466":1,"1487":1,"1495":3,"1497":1,"1566":1,"1590":2,"1652":1,"1685":1,"1763":1,"1814":1,"1816":1,"1817":1,"1864":1,"1926":1,"2041":1,"2052":1,"2125":1,"2193":1}}],["actually",{"0":{"1362":1,"1398":1,"1424":1,"1871":1,"1882":1,"1908":1,"2110":1,"2143":1},"1":{"1793":1,"1794":1,"1795":1,"1796":1,"1797":1,"1798":1,"1799":1,"1800":1,"1801":1,"1950":1,"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1956":1,"1957":1,"1958":1,"2028":1,"2029":1,"2030":1,"2031":1,"2032":1,"2033":1,"2034":1,"2035":1,"2036":1,"2037":1},"3":{"0":2,"10":1,"21":1,"38":1,"70":1,"103":1,"109":1,"115":1,"125":1,"126":1,"130":1,"136":1,"150":1,"157":2,"177":1,"200":1,"215":1,"259":1,"286":1,"318":1,"335":2,"342":1,"343":1,"359":1,"369":1,"371":1,"373":1,"395":1,"401":1,"407":1,"428":1,"447":1,"461":2,"463":1,"506":1,"526":2,"543":2,"549":1,"564":1,"572":1,"585":1,"592":2,"601":1,"607":1,"626":1,"627":1,"630":1,"638":1,"639":1,"642":1,"650":1,"674":1,"682":1,"689":2,"690":1,"691":1,"714":1,"716":1,"718":1,"741":1,"758":1,"767":1,"773":1,"774":1,"789":1,"791":1,"797":1,"832":1,"836":1,"838":1,"861":1,"873":1,"876":1,"881":1,"905":1,"906":1,"914":1,"946":1,"962":1,"963":1,"971":1,"972":2,"1033":1,"1041":1,"1074":1,"1084":1,"1123":1,"1142":1,"1147":1,"1162":1,"1176":1,"1190":1,"1212":1,"1220":1,"1229":2,"1231":2,"1237":1,"1244":2,"1247":5,"1253":1,"1259":5,"1263":1,"1267":1,"1270":9,"1271":1,"1276":1,"1277":1,"1285":39,"1299":16,"1300":3,"1301":1,"1308":5,"1309":4,"1310":17,"1311":2,"1316":1,"1322":23,"1323":25,"1338":18,"1343":1,"1349":14,"1350":1,"1351":1,"1358":45,"1362":1,"1368":7,"1378":1,"1379":7,"1380":1,"1388":2,"1391":1,"1394":29,"1395":33,"1396":1,"1397":1,"1401":13,"1404":1,"1413":1,"1414":24,"1415":8,"1416":12,"1420":1,"1423":1,"1424":1,"1426":4,"1430":3,"1431":1,"1434":2,"1435":74,"1437":7,"1442":1,"1447":1,"1448":1,"1450":1,"1452":1,"1453":1,"1454":5,"1457":1,"1460":1,"1466":2,"1469":3,"1470":1,"1475":1,"1483":1,"1485":1,"1487":2,"1488":5,"1489":3,"1490":1,"1491":1,"1495":2,"1499":1,"1536":1,"1565":1,"1571":1,"1604":1,"1650":1,"1664":1,"1683":1,"1684":2,"1685":1,"1700":1,"1709":1,"1720":1,"1725":1,"1729":1,"1738":1,"1748":1,"1763":1,"1766":1,"1768":5,"1782":1,"1783":1,"1787":1,"1792":2,"1793":1,"1795":2,"1809":1,"1812":1,"1817":1,"1820":1,"1824":1,"1851":1,"1859":1,"1864":1,"1866":1,"1875":2,"1876":1,"1894":1,"1907":1,"1908":1,"1915":1,"1919":1,"1928":1,"1929":1,"1930":1,"1949":1,"1950":1,"1952":2,"1954":1,"1959":1,"1964":1,"1967":1,"1972":1,"1979":1,"1993":1,"1997":1,"1999":1,"2001":1,"2009":1,"2011":1,"2012":1,"2026":1,"2028":3,"2032":1,"2037":1,"2040":1,"2041":1,"2042":1,"2043":1,"2044":1,"2047":1,"2052":1,"2054":2,"2058":1,"2059":1,"2069":1,"2076":1,"2081":1,"2086":1,"2097":1,"2103":1,"2104":1,"2108":1,"2113":1,"2118":1,"2125":1,"2127":2,"2128":1,"2140":1,"2141":1,"2144":1,"2146":1,"2149":1,"2150":1,"2152":1,"2153":1,"2155":1,"2159":3,"2168":2,"2176":1,"2182":1,"2190":1,"2202":1,"2204":1,"2207":1,"2208":1,"2231":1}}],["act",{"0":{"1236":1},"3":{"39":1,"81":1,"120":1,"202":1,"215":1,"225":1,"231":1,"830":1,"888":1,"898":1,"905":2,"989":2,"1005":1,"1051":1,"1067":2,"1091":1,"1175":1,"1186":1,"1252":1,"1259":2,"1265":1,"1270":2,"1285":7,"1288":1,"1299":2,"1303":1,"1308":2,"1312":1,"1317":1,"1322":1,"1323":3,"1326":1,"1338":2,"1341":1,"1349":2,"1356":1,"1357":1,"1358":10,"1375":1,"1379":1,"1395":5,"1401":5,"1414":4,"1426":2,"1429":1,"1435":7,"1437":2,"1440":1,"1477":5,"1484":1,"1638":1,"1653":1,"1770":1,"1823":1,"1872":1,"1876":1,"1933":1,"1939":1,"1940":1,"2032":1,"2055":1,"2151":1,"2169":1,"2173":1,"2189":1}}],["actors",{"0":{"1620":1,"1756":1},"3":{"1152":1,"1154":1,"1349":1,"1437":1,"1631":21,"1637":1,"1640":6}}],["actor",{"1":{"1148":1,"1149":1,"1150":1,"1151":1,"1152":1,"1153":1,"1154":1,"1155":1,"1156":1,"1157":1},"2":{"1150":1},"3":{"0":1,"340":1,"718":1,"1148":1,"1149":1,"1150":7,"1151":2,"1152":2,"1153":1,"1154":2,"1155":1,"1212":2,"1299":4,"1358":5,"1394":1,"1395":2,"1435":1,"1437":1,"1458":1,"1495":1,"1525":1,"1561":2,"1590":1,"1619":2,"1620":1,"1625":1,"1631":5,"1638":1,"1755":3,"1756":1,"1760":2,"1767":2,"1843":1,"1868":1,"2231":1}}],["actingspeakerid",{"2":{"1356":1},"3":{"1356":1,"1358":14,"1372":1,"1379":5}}],["acting",{"3":{"290":1,"797":1,"802":1,"1274":1,"1285":1,"1299":2,"1358":7,"1376":1,"1395":1,"1401":1,"1414":4,"1416":2,"1437":1,"1764":1}}],["activitiescontrollertests",{"3":{"1199":1,"1207":2,"1437":2}}],["activitiescontroller",{"3":{"1199":4,"1203":1,"1207":2,"1349":4,"1358":21,"1370":2,"1371":3,"1372":3,"1374":2,"1379":11,"1435":1}}],["activitiescache",{"2":{"386":1},"3":{"386":1,"1349":1,"1378":1,"1379":2}}],["activitiesmanage",{"2":{"184":1},"3":{"184":1,"1349":2,"1358":2,"1372":1,"1379":2}}],["activities",{"0":{"1391":1},"2":{"1358":1,"1379":1,"1394":2},"3":{"15":1,"186":1,"418":1,"836":1,"881":1,"1004":1,"1043":1,"1203":36,"1207":96,"1270":2,"1271":1,"1341":4,"1345":2,"1346":1,"1347":2,"1349":30,"1350":2,"1351":3,"1352":2,"1358":114,"1360":1,"1361":1,"1362":1,"1363":1,"1368":11,"1374":1,"1379":8,"1380":1,"1381":3,"1382":5,"1383":3,"1390":1,"1391":5,"1394":46,"1414":1,"1435":1,"1436":1,"1437":5,"1446":1,"1525":4,"1620":1,"1624":1,"1625":3,"1626":7,"1638":1,"2109":1,"2231":1}}],["activitylabel",{"3":{"1395":2}}],["activitylistener",{"3":{"1338":2}}],["activitylist",{"2":{"1626":1},"3":{"1199":1,"1203":1,"1207":2,"1323":2,"1349":2,"1382":4,"1391":2,"1394":12,"1626":1}}],["activitykind",{"3":{"1338":4}}],["activityvalidationrules",{"3":{"1207":9,"1271":1,"1349":1,"1352":1,"1358":44}}],["activityvenueurlrules",{"3":{"1199":2,"1203":1,"1207":1,"1271":1,"1358":4}}],["activityvenuenamerules",{"3":{"1199":2,"1203":1,"1207":1,"1358":2}}],["activityvenueaddressrules",{"3":{"1199":2,"1203":1,"1207":1,"1358":2}}],["activitynavigationpopulatortests",{"3":{"1199":1,"1207":2,"1358":2}}],["activitynavigationpopulator",{"3":{"1199":3,"1203":1,"1207":2,"1349":1,"1353":1,"1358":10}}],["activitynamerules",{"3":{"1199":2,"1203":1,"1207":1,"1349":1,"1358":5}}],["activitybuilder",{"3":{"1199":3,"1207":2,"1435":1,"1437":1}}],["activityupdateappliertests",{"3":{"1199":1,"1207":2}}],["activityupdateapplier",{"3":{"1199":2,"1203":1,"1207":2,"1270":2,"1353":1,"1358":7}}],["activityupdaterequestvalidatortests",{"3":{"1199":1,"1207":2}}],["activityupdaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1358":11}}],["activityupdaterequest",{"3":{"1199":9,"1203":1,"1207":2,"1349":1,"1358":15,"1379":1}}],["activityformfields",{"3":{"1349":1,"1394":2}}],["activityformmodel",{"3":{"1199":3,"1203":1,"1207":2,"1349":1,"1383":2,"1394":9}}],["activityfieldrules",{"3":{"1199":3,"1203":1,"1207":1,"1352":1,"1358":18}}],["activitydetails",{"2":{"1393":1},"3":{"1393":1,"1394":1}}],["activitydetailtests",{"3":{"1199":1,"1207":2}}],["activitydetail",{"2":{"1391":1,"1626":1},"3":{"1199":2,"1203":1,"1207":2,"1349":1,"1383":2,"1391":2,"1394":20,"1626":1}}],["activitydescriptionrules",{"3":{"1199":2,"1203":1,"1207":1,"1358":7}}],["activitydtomappertests",{"3":{"1199":1,"1207":2,"1358":2}}],["activitydtomapper",{"3":{"1199":5,"1203":1,"1207":2,"1349":3,"1352":1,"1358":10}}],["activitydto",{"3":{"1199":20,"1203":1,"1207":2,"1346":2,"1349":16,"1358":14,"1379":1,"1381":1,"1394":14}}],["activityid",{"3":{"1349":2}}],["activityidentifiertype",{"2":{"468":1,"475":1,"803":1},"3":{"468":1,"475":1,"803":1,"1349":3,"1358":5,"1394":4}}],["activityinvariantstests",{"3":{"1199":1,"1207":2,"1437":1}}],["activityinvariants",{"2":{"1343":1,"1361":1},"3":{"1199":12,"1203":1,"1207":2,"1343":4,"1349":10,"1358":29,"1361":1,"1368":5}}],["activityevent",{"3":{"1368":1}}],["activityeventidrules",{"3":{"1199":2,"1203":1,"1207":1,"1358":12}}],["activityeditmodel",{"3":{"1199":3,"1203":1,"1207":2,"1394":7}}],["activitycontext",{"3":{"1259":1}}],["activityconfiguration",{"2":{"1360":1},"3":{"1199":1,"1203":1,"1207":2,"1349":3,"1360":2,"1361":5,"1368":4}}],["activitycreatetests",{"3":{"1199":1,"1207":2,"1435":1}}],["activitycreate",{"2":{"1391":1,"1626":1},"3":{"1199":2,"1203":1,"1207":2,"1349":3,"1391":2,"1394":19,"1525":1,"1626":1}}],["activitycreaterequestvalidatortests",{"3":{"1199":1,"1207":2,"1358":2}}],["activitycreaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1358":18}}],["activitycreaterequestmapper",{"3":{"1199":1,"1203":1,"1207":2,"1358":9}}],["activitycreaterequest",{"3":{"1199":10,"1203":1,"1207":2,"1270":1,"1349":1,"1358":23,"1379":1}}],["activitycreatemodel",{"3":{"1199":4,"1203":1,"1207":2,"1394":5}}],["activitychanged",{"2":{"780":1},"3":{"780":1,"782":1,"1199":3,"1203":1,"1207":2,"1344":1,"1349":10,"1358":2}}],["activitysortorderrules",{"3":{"1199":2,"1203":1,"1207":1,"1358":4}}],["activitysource",{"2":{"405":1,"1096":1},"3":{"405":1,"1096":1,"1285":1,"1426":1}}],["activityservice",{"3":{"881":1,"1199":5,"1203":1,"1207":2,"1381":3,"1394":19}}],["activitytraceflags",{"2":{"1332":1},"3":{"1332":2,"1338":4,"1442":1}}],["activitytests",{"3":{"1199":1,"1207":2,"1437":1}}],["activitytimerangerules",{"3":{"1199":2,"1203":1,"1207":1,"1352":2,"1358":7}}],["activitytimeoutheader",{"3":{"1435":1}}],["activitytimeout",{"2":{"837":1,"838":1},"3":{"837":1,"838":2,"1338":1,"1446":2}}],["activitytype",{"3":{"0":1,"690":1,"1212":1,"1395":25,"1414":3,"1630":1}}],["activity",{"2":{"692":1,"924":1,"1345":1,"1424":1},"3":{"0":1,"18":1,"395":2,"397":1,"399":2,"404":2,"406":1,"427":2,"428":1,"690":1,"692":3,"725":1,"827":1,"836":1,"837":1,"838":2,"842":1,"924":1,"1091":2,"1184":1,"1192":1,"1199":44,"1203":1,"1207":2,"1253":1,"1256":2,"1259":9,"1270":2,"1275":1,"1277":1,"1285":5,"1310":5,"1332":4,"1336":1,"1337":2,"1338":31,"1339":1,"1341":12,"1342":4,"1345":3,"1349":57,"1350":1,"1351":3,"1353":1,"1358":115,"1360":1,"1368":9,"1374":1,"1379":7,"1386":1,"1390":1,"1391":2,"1394":28,"1395":48,"1414":4,"1415":17,"1416":3,"1420":1,"1424":4,"1426":10,"1434":1,"1435":2,"1437":12,"1442":2,"1446":1,"1495":3,"1525":5,"1534":1,"1625":2,"1626":2,"1628":1,"1630":5,"1637":2,"1638":1,"1655":1,"1676":1,"1840":1,"2005":1,"2009":1,"2023":1,"2155":2,"2156":4,"2158":2,"2159":1}}],["activatable",{"3":{"1323":3}}],["activating",{"3":{"1285":1,"1292":1,"1299":1,"1321":1,"1416":1,"1435":1}}],["activationfailed",{"3":{"1454":1}}],["activations",{"3":{"1285":1}}],["activation",{"3":{"0":2,"558":1,"607":2,"609":4,"611":2,"755":1,"757":3,"758":1,"759":4,"760":3,"1150":1,"1270":1,"1285":4,"1299":1,"1322":1,"1395":4,"1415":8,"1416":2,"1435":2,"1441":1,"1454":6,"1460":4,"1464":2,"1466":7,"1468":3,"1473":2,"1475":3,"1476":1,"1481":1,"1495":1,"1590":1,"1763":1,"2035":2,"2037":1,"2157":1}}],["activatorutilities",{"3":{"1285":10}}],["activator",{"2":{"583":1,"585":1,"1314":1},"3":{"583":1,"585":1,"1229":2,"1237":1,"1247":1,"1285":3,"1299":2,"1314":1,"1322":1,"1435":1}}],["activate",{"3":{"1270":1,"1296":1,"1299":1,"1322":2,"1415":1,"1416":1,"1466":1}}],["activates",{"3":{"99":1,"234":1,"1278":1,"1285":1,"1292":1,"1299":1,"1310":1,"1321":1,"1416":3,"1430":3,"1435":3,"1440":1,"1442":1,"1454":2,"1590":1}}],["activated",{"3":{"0":1,"757":1,"760":1,"1431":1,"1435":3}}],["activeprobepath",{"3":{"1435":2}}],["activeprobetimeout",{"3":{"1435":1}}],["activeprobeinterval",{"3":{"1435":1}}],["activeutterance",{"3":{"1416":2}}],["activevote",{"3":{"1401":1}}],["activehealthcheckconfig",{"3":{"1338":3}}],["activehealthchecksexpected",{"2":{"578":1},"3":{"572":2,"578":1}}],["activecircuits",{"3":{"1323":4}}],["activespec",{"3":{"1199":2,"1207":1,"1495":1}}],["activerevisionsmode",{"2":{"757":1},"3":{"757":1,"1466":1}}],["actively",{"3":{"162":1,"174":1,"500":1,"1270":2,"1310":1,"1416":1,"1435":1,"1464":1,"1466":2,"1475":1,"1477":1,"1536":1,"1598":1,"2044":1}}],["active",{"1":{"456":1,"457":1,"458":1,"459":1,"460":1,"461":1,"462":1,"463":1,"464":1,"465":1,"466":1},"2":{"759":1},"3":{"0":7,"39":1,"40":1,"100":1,"120":1,"175":1,"195":1,"236":1,"265":4,"371":1,"372":1,"381":1,"390":1,"397":1,"402":2,"456":1,"458":1,"501":1,"545":1,"551":1,"572":5,"575":1,"578":2,"690":1,"715":1,"757":1,"759":1,"825":1,"827":5,"829":1,"831":1,"832":2,"833":6,"1026":3,"1042":2,"1124":2,"1125":1,"1168":2,"1231":1,"1237":1,"1247":2,"1274":1,"1285":14,"1292":1,"1293":1,"1299":13,"1310":2,"1311":2,"1322":3,"1323":28,"1328":1,"1329":1,"1332":2,"1337":2,"1338":24,"1349":7,"1358":10,"1384":1,"1394":16,"1395":32,"1397":2,"1399":1,"1400":1,"1401":24,"1412":1,"1414":6,"1416":8,"1423":1,"1435":14,"1437":1,"1440":1,"1442":5,"1446":3,"1454":2,"1466":3,"1476":1,"1487":1,"1525":5,"1533":1,"1561":1,"1589":1,"1590":1,"1599":1,"1626":1,"1629":3,"1630":3,"1631":2,"1632":1,"1634":2,"1638":7,"1639":1,"1640":2,"1642":1,"1652":1,"1662":1,"1666":1,"1670":1,"1676":2,"1746":2,"1748":3,"1749":3,"1760":1,"1763":2,"1764":6,"1766":5,"1767":1,"1768":1,"1772":2,"1809":1,"1860":1,"1983":1,"2009":1,"2029":1,"2054":1,"2062":1,"2068":1,"2082":1,"2084":1,"2085":1,"2155":1,"2157":1,"2184":2,"2219":1,"2237":2,"2240":1}}],["actiongroups",{"3":{"2157":1}}],["actiongroup",{"3":{"1576":1,"2157":1}}],["actionability",{"3":{"1435":1}}],["actionabletakeaways",{"3":{"1368":1}}],["actionabletakeawaysscore",{"3":{"1349":1,"1358":1,"1368":1}}],["actionable",{"3":{"1067":1,"1285":1,"1300":1,"1308":1,"1310":1,"1322":1,"1323":3,"1349":2,"1358":5,"1401":2,"1435":6,"1452":1,"1454":2,"1465":1,"1466":1,"1471":1,"1478":1,"1801":1,"2195":2,"2202":1}}],["actionview",{"3":{"1415":3}}],["actionid",{"3":{"1415":1}}],["actioned",{"3":{"1395":1}}],["action<",{"3":{"1394":1}}],["actioncolor",{"3":{"1323":1}}],["actiontext",{"3":{"1323":2}}],["actiondescriptor",{"2":{"1299":1},"3":{"1299":1}}],["actionresult",{"2":{"1227":1},"3":{"1227":1,"1310":1,"1379":1,"1414":2}}],["actions",{"1":{"1991":1,"1992":1,"1993":1,"1994":1,"1995":1,"1996":1},"2":{"626":1},"3":{"0":6,"149":1,"161":1,"175":2,"189":1,"273":1,"318":1,"324":1,"340":1,"343":1,"368":1,"370":2,"371":2,"372":2,"373":2,"374":5,"375":3,"413":1,"419":1,"448":1,"459":1,"591":2,"594":1,"626":17,"627":2,"628":2,"629":4,"692":1,"719":2,"725":1,"743":2,"854":1,"1012":1,"1074":1,"1116":1,"1247":1,"1270":1,"1285":4,"1293":1,"1299":7,"1308":5,"1310":25,"1322":1,"1323":16,"1349":3,"1358":11,"1368":1,"1370":2,"1371":1,"1372":1,"1374":1,"1379":18,"1394":4,"1395":5,"1401":9,"1405":2,"1406":2,"1407":1,"1408":1,"1413":2,"1414":18,"1415":7,"1416":4,"1431":1,"1435":14,"1437":1,"1451":1,"1452":5,"1453":4,"1454":4,"1455":2,"1456":1,"1458":2,"1460":2,"1464":1,"1465":1,"1466":6,"1467":1,"1471":2,"1473":1,"1474":5,"1481":1,"1482":1,"1483":2,"1487":2,"1490":1,"1495":2,"1525":3,"1530":2,"1533":1,"1552":1,"1558":2,"1560":1,"1576":4,"1581":2,"1582":1,"1584":1,"1585":1,"1590":2,"1598":2,"1625":2,"1626":1,"1629":1,"1630":4,"1631":13,"1632":1,"1640":2,"1746":1,"1748":1,"1749":3,"1759":2,"1869":1,"1873":1,"1897":1,"1924":1,"1929":1,"1990":1,"1991":1,"1999":1,"2000":2,"2082":1,"2130":1,"2207":1,"2218":1}}],["action",{"0":{"1349":1,"1371":1},"1":{"315":1,"316":1,"317":1,"318":1,"319":1,"320":1,"321":1,"322":1,"323":1,"324":1,"325":1,"326":1},"2":{"1369":1,"1918":1},"3":{"0":11,"24":1,"77":1,"136":1,"155":2,"157":8,"159":1,"160":8,"175":2,"197":1,"209":1,"214":1,"219":1,"226":1,"265":2,"299":1,"300":1,"315":1,"318":14,"319":1,"325":1,"326":1,"330":2,"332":1,"334":1,"340":1,"341":5,"343":2,"345":1,"368":1,"370":12,"371":2,"372":3,"374":3,"375":7,"428":1,"444":1,"448":1,"450":1,"523":1,"528":1,"530":1,"534":1,"536":3,"538":2,"591":4,"594":3,"609":2,"611":1,"624":1,"626":22,"628":2,"629":5,"643":1,"644":1,"674":1,"690":1,"694":1,"723":1,"725":4,"726":1,"729":1,"733":1,"741":2,"744":1,"749":1,"788":1,"793":1,"809":1,"868":1,"869":1,"873":1,"881":1,"883":1,"897":4,"903":1,"905":2,"907":2,"914":1,"921":1,"922":3,"923":1,"995":1,"996":1,"1019":1,"1116":1,"1117":1,"1118":1,"1212":4,"1217":1,"1218":1,"1220":2,"1229":4,"1237":2,"1244":1,"1247":10,"1249":1,"1259":2,"1264":1,"1270":2,"1285":6,"1296":4,"1299":33,"1300":2,"1303":1,"1305":1,"1308":13,"1309":2,"1310":78,"1315":1,"1322":9,"1323":24,"1338":6,"1349":6,"1358":68,"1368":1,"1369":1,"1370":2,"1371":4,"1372":6,"1373":3,"1374":5,"1375":2,"1379":61,"1384":1,"1390":2,"1391":2,"1394":26,"1395":37,"1398":2,"1401":42,"1407":1,"1408":2,"1409":2,"1414":34,"1415":15,"1416":6,"1430":2,"1435":51,"1437":4,"1451":2,"1452":8,"1453":3,"1454":2,"1458":7,"1466":9,"1469":2,"1473":1,"1475":3,"1487":1,"1488":1,"1495":3,"1525":5,"1529":1,"1530":1,"1533":1,"1552":1,"1555":1,"1576":4,"1580":2,"1581":4,"1582":1,"1585":1,"1588":1,"1590":3,"1594":1,"1595":1,"1626":1,"1630":3,"1631":23,"1632":1,"1633":1,"1637":1,"1638":2,"1650":2,"1652":1,"1665":1,"1666":1,"1684":5,"1685":6,"1726":2,"1729":1,"1747":1,"1763":1,"1775":1,"1802":1,"1804":3,"1813":1,"1869":1,"1870":1,"1872":2,"1873":6,"1875":1,"1887":1,"1907":6,"1908":4,"1910":1,"1911":1,"1918":3,"1945":1,"1970":1,"1987":2,"1989":1,"1990":1,"1991":1,"1992":1,"1993":9,"1994":2,"1996":3,"2120":1,"2130":1,"2136":2,"2148":1,"2149":1,"2157":1,"2162":3,"2165":1,"2167":2,"2168":1,"2231":1}}],["acrname",{"2":{"1464":1},"3":{"1454":1,"1464":4,"1465":2,"1466":1}}],["acrpurgetask",{"3":{"1464":1}}],["acrpush",{"3":{"1454":1,"1464":2,"1471":2}}],["acrpull",{"3":{"601":1,"1454":1,"1464":1,"1466":1,"1469":1}}],["acrloginserver",{"2":{"1454":1,"1464":1},"3":{"1454":1,"1464":1,"1466":1}}],["acr",{"2":{"1462":1},"3":{"0":1,"599":2,"600":1,"756":1,"757":1,"868":1,"1444":3,"1454":11,"1460":2,"1464":10,"1465":1,"1466":8,"1468":1,"1471":1,"1473":2,"1495":1,"1525":1,"1534":1,"1576":1,"1590":1,"2032":1,"2036":1}}],["acrosstwotypes",{"3":{"1435":3}}],["across",{"1":{"504":1,"505":1,"506":1,"507":1,"508":1,"509":1,"510":1,"511":1,"512":1,"513":1,"514":1,"1924":1,"1925":1,"1926":1,"1927":1,"1928":1,"1929":1},"3":{"0":33,"3":3,"7":1,"19":1,"23":1,"24":3,"26":1,"39":1,"49":2,"80":3,"92":1,"109":2,"126":1,"128":1,"130":1,"135":4,"136":2,"137":3,"138":2,"139":2,"141":3,"142":3,"145":2,"146":1,"147":1,"148":1,"150":2,"157":3,"158":2,"168":3,"169":1,"177":1,"178":1,"184":1,"186":1,"187":1,"199":2,"227":1,"265":1,"272":1,"274":1,"280":1,"298":1,"302":1,"303":1,"311":1,"312":1,"313":1,"320":1,"349":1,"352":1,"358":1,"359":1,"360":1,"391":1,"397":1,"400":1,"418":2,"421":1,"448":1,"451":1,"454":1,"457":1,"461":3,"468":2,"474":1,"475":2,"476":1,"477":1,"483":1,"484":2,"486":1,"487":5,"488":6,"489":7,"490":11,"491":9,"492":8,"493":10,"494":11,"495":9,"497":1,"499":1,"504":1,"506":1,"509":1,"511":1,"524":1,"528":1,"543":1,"545":1,"547":1,"550":2,"551":2,"552":1,"556":2,"557":1,"574":1,"583":1,"593":1,"610":1,"616":4,"617":1,"618":3,"628":1,"633":2,"650":1,"674":2,"675":1,"677":1,"690":1,"696":2,"698":1,"699":1,"700":1,"702":2,"707":2,"709":1,"717":1,"724":1,"727":1,"729":1,"733":3,"735":1,"757":2,"764":2,"780":4,"782":1,"783":1,"784":1,"790":1,"793":1,"798":3,"799":2,"800":1,"803":3,"804":3,"805":3,"806":1,"810":1,"819":1,"821":3,"823":2,"827":5,"837":1,"846":1,"867":1,"880":2,"881":1,"882":1,"883":1,"885":1,"889":1,"891":1,"905":2,"931":1,"946":1,"948":1,"950":1,"954":1,"955":1,"958":1,"963":1,"964":2,"965":1,"966":1,"973":1,"980":7,"981":2,"982":1,"983":1,"988":1,"996":2,"998":1,"1004":2,"1006":1,"1011":1,"1014":1,"1020":3,"1025":1,"1027":1,"1036":1,"1049":2,"1050":3,"1051":2,"1052":1,"1054":2,"1059":1,"1060":1,"1062":1,"1067":1,"1074":1,"1076":1,"1082":1,"1092":2,"1094":1,"1099":1,"1100":2,"1133":2,"1134":1,"1136":1,"1150":1,"1154":1,"1157":1,"1189":1,"1194":1,"1200":1,"1201":1,"1202":1,"1212":6,"1229":8,"1230":1,"1231":1,"1233":2,"1234":1,"1237":15,"1239":1,"1243":2,"1247":15,"1248":1,"1253":2,"1256":1,"1257":2,"1259":20,"1260":1,"1267":2,"1269":4,"1270":17,"1271":8,"1276":3,"1278":1,"1279":1,"1280":1,"1282":1,"1283":1,"1285":53,"1286":1,"1295":1,"1297":2,"1299":27,"1300":12,"1301":1,"1303":1,"1307":1,"1308":6,"1309":4,"1310":38,"1311":8,"1315":1,"1316":1,"1317":1,"1322":30,"1323":42,"1332":1,"1335":1,"1338":12,"1339":2,"1341":2,"1346":1,"1347":1,"1349":24,"1350":1,"1351":1,"1352":1,"1358":39,"1363":1,"1368":9,"1374":1,"1377":1,"1379":8,"1380":1,"1394":17,"1395":45,"1400":1,"1401":18,"1402":2,"1405":2,"1409":1,"1414":23,"1415":6,"1416":32,"1423":1,"1426":1,"1427":1,"1430":2,"1435":80,"1436":1,"1437":12,"1440":2,"1441":2,"1446":1,"1453":1,"1454":4,"1455":1,"1458":1,"1459":1,"1460":3,"1462":1,"1463":1,"1464":3,"1466":6,"1473":2,"1474":1,"1485":1,"1486":6,"1487":2,"1488":5,"1491":1,"1495":31,"1497":2,"1498":1,"1512":1,"1513":1,"1518":1,"1524":3,"1525":14,"1531":1,"1532":1,"1533":3,"1534":2,"1535":1,"1541":1,"1543":2,"1549":2,"1550":1,"1555":1,"1556":1,"1558":1,"1564":1,"1566":1,"1568":2,"1570":1,"1575":2,"1576":11,"1581":1,"1582":1,"1583":1,"1590":4,"1591":1,"1599":3,"1628":1,"1629":3,"1630":2,"1637":4,"1639":3,"1640":3,"1647":1,"1648":1,"1649":1,"1659":1,"1660":1,"1663":2,"1664":1,"1665":1,"1666":1,"1668":1,"1670":3,"1671":1,"1672":3,"1674":1,"1676":1,"1682":1,"1693":1,"1700":1,"1713":1,"1722":1,"1725":1,"1735":1,"1746":2,"1749":1,"1766":2,"1767":2,"1768":1,"1771":1,"1778":1,"1781":2,"1783":1,"1787":1,"1789":1,"1791":1,"1792":1,"1794":3,"1795":1,"1798":1,"1812":1,"1814":3,"1822":1,"1828":2,"1831":1,"1838":1,"1841":1,"1842":3,"1844":1,"1848":1,"1849":1,"1850":1,"1851":3,"1852":2,"1858":3,"1859":1,"1860":4,"1864":1,"1865":1,"1867":1,"1873":3,"1874":1,"1892":1,"1893":1,"1894":1,"1895":1,"1897":1,"1898":1,"1907":3,"1911":3,"1912":1,"1919":1,"1922":1,"1923":3,"1924":2,"1926":1,"1929":3,"1930":1,"1934":1,"1939":2,"1947":1,"1949":1,"1952":1,"1957":1,"1959":2,"1960":1,"1962":1,"1964":1,"1975":1,"1977":1,"1978":1,"1986":1,"1989":1,"1992":1,"1994":1,"1995":1,"1997":1,"2000":2,"2005":1,"2009":1,"2010":1,"2012":1,"2017":1,"2023":1,"2024":1,"2030":1,"2033":1,"2036":2,"2038":1,"2041":2,"2042":1,"2047":1,"2048":1,"2054":2,"2056":2,"2064":1,"2066":1,"2072":1,"2074":1,"2078":1,"2079":1,"2080":4,"2096":1,"2099":2,"2100":2,"2103":1,"2109":1,"2110":1,"2113":1,"2130":1,"2137":2,"2142":1,"2154":1,"2155":1,"2156":1,"2158":1,"2160":1,"2167":1,"2171":1,"2173":1,"2176":2,"2177":1,"2179":1,"2186":1,"2201":1,"2207":2,"2219":7,"2229":1,"2231":2,"2237":1,"2239":1,"2243":1}}],["acc",{"3":{"1454":1,"1462":1,"1466":1,"1469":1,"1470":1,"1471":2,"1472":1,"1473":1,"1474":1}}],["accrete",{"3":{"1474":1}}],["accreted",{"3":{"1435":1,"1791":1}}],["accrue",{"3":{"1395":1}}],["accrues",{"3":{"800":1}}],["accruing",{"3":{"692":1}}],["according",{"3":{"1089":1,"1281":1,"1299":1,"1310":1}}],["accordingly",{"3":{"298":2,"511":1,"512":1,"744":1,"892":1,"967":1,"1303":1,"1358":2,"1445":1,"1495":3,"2036":1}}],["accomplish",{"3":{"1625":1}}],["accompany",{"3":{"1322":1}}],["accompanying",{"3":{"372":1,"1416":1}}],["accommodates",{"3":{"1630":1}}],["accommodate",{"3":{"1237":1,"1435":2}}],["accommodations",{"3":{"1299":1,"1629":1,"1630":1}}],["accommodation",{"3":{"988":1}}],["accountlocked",{"3":{"1414":1}}],["accountname",{"3":{"1299":1}}],["accountentity",{"3":{"1199":3,"1207":1}}],["accounted",{"3":{"178":1,"402":1,"1332":1,"1338":1,"1435":1,"1442":1}}],["accountdeletiontests",{"3":{"1199":1,"1207":2}}],["accountdto",{"3":{"1199":3,"1207":1}}],["accounting",{"3":{"1101":1,"1270":2,"1395":1,"1435":5}}],["accounts",{"0":{"1408":1},"2":{"314":1},"3":{"314":1,"350":2,"352":1,"355":1,"487":1,"488":1,"489":1,"490":1,"1285":6,"1288":1,"1292":2,"1299":5,"1310":2,"1321":2,"1323":1,"1325":1,"1395":3,"1407":1,"1409":1,"1411":1,"1414":23,"1416":1,"1435":2,"1440":3,"1443":1,"1455":1,"1489":1,"1528":1,"1594":1,"1595":2,"1637":1,"1640":3,"1663":1,"1666":1,"1768":1,"1976":1,"1977":1,"1998":1}}],["accountability",{"3":{"40":1,"1237":1,"1566":1,"2062":1}}],["account",{"3":{"0":12,"126":1,"160":1,"174":1,"175":1,"176":1,"230":1,"243":1,"279":2,"280":2,"282":1,"283":1,"284":7,"286":1,"310":2,"348":4,"349":1,"350":11,"351":3,"352":5,"353":11,"403":2,"433":1,"435":1,"438":1,"441":1,"458":4,"459":8,"460":2,"465":1,"493":1,"497":4,"498":1,"499":4,"500":1,"524":1,"527":2,"528":1,"530":3,"602":1,"618":1,"649":1,"663":1,"665":7,"666":6,"667":1,"669":2,"670":2,"690":1,"724":1,"725":1,"727":1,"734":1,"749":1,"790":1,"792":2,"853":3,"854":4,"855":1,"857":1,"904":2,"905":5,"906":1,"908":2,"946":2,"948":1,"950":1,"1059":2,"1116":3,"1119":1,"1212":1,"1237":2,"1264":1,"1270":1,"1271":1,"1279":1,"1282":1,"1283":2,"1285":18,"1286":1,"1288":2,"1289":3,"1290":2,"1292":4,"1293":3,"1294":2,"1299":100,"1306":1,"1308":2,"1310":23,"1321":6,"1322":78,"1323":35,"1331":1,"1338":1,"1349":1,"1358":5,"1381":1,"1394":3,"1395":11,"1401":2,"1402":1,"1404":5,"1405":9,"1406":1,"1407":3,"1408":5,"1409":3,"1410":2,"1411":1,"1413":5,"1414":134,"1416":7,"1431":1,"1435":20,"1436":1,"1437":3,"1454":1,"1466":16,"1469":2,"1472":1,"1477":1,"1480":2,"1483":1,"1487":2,"1495":1,"1595":4,"1596":1,"1599":1,"1607":1,"1620":2,"1625":1,"1626":3,"1630":1,"1631":9,"1633":2,"1638":2,"1640":24,"1666":5,"1671":1,"1674":1,"1745":1,"1746":4,"1754":1,"1763":3,"1764":7,"1766":1,"1767":2,"1770":1,"1776":2,"1820":1,"1824":1,"1899":1,"1901":1,"1939":1,"1944":1,"1969":3,"1972":3,"1974":1,"1976":10,"1977":6,"1978":5,"1979":1,"1981":3,"1982":1,"1983":1,"1997":2,"1998":2,"1999":1,"2000":3,"2001":4,"2009":1,"2035":2,"2037":1,"2062":2,"2065":5,"2066":1,"2126":2,"2195":1,"2196":2,"2197":2,"2202":1,"2219":1,"2231":1}}],["accumulation",{"3":{"1020":1,"1299":2,"1394":2,"1414":1,"1426":1,"1684":1}}],["accumulating",{"3":{"861":1,"1270":1,"1271":2,"1285":1,"1299":1,"1322":5,"1382":1,"1395":2,"1422":1,"1426":1,"1435":3,"2189":1}}],["accumulators",{"3":{"1358":2}}],["accumulator",{"3":{"658":1,"1234":1,"1237":2,"1299":1,"1323":1}}],["accumulated",{"3":{"657":1,"826":1,"1259":1,"1285":2,"1300":1,"1309":1,"1323":2,"1394":2,"1395":1,"1432":1,"1435":2,"1472":1,"1551":1}}],["accumulate",{"3":{"657":1,"707":1,"717":1,"750":1,"757":1,"846":1,"1093":1,"1237":1,"1247":1,"1256":1,"1271":1,"1285":2,"1299":1,"1300":1,"1310":2,"1315":1,"1322":1,"1323":2,"1358":1,"1395":1,"1401":1,"1408":1,"1414":3,"1426":1,"1435":1,"1844":1}}],["accumulates",{"3":{"26":1,"186":1,"329":1,"1005":1,"1123":3,"1237":2,"1285":1,"1308":3,"1310":1,"1316":1,"1323":1,"1357":1,"1358":1,"1401":1,"1415":1,"1422":1,"1424":1,"1435":1,"1464":1,"1684":1,"1686":1,"1961":1,"1985":1,"1999":1}}],["accuracy",{"3":{"250":1,"1019":1,"1191":1,"1196":1,"1340":1,"1358":1,"1416":2,"1499":1}}],["accurately",{"3":{"190":1}}],["accurate",{"3":{"71":1,"245":1,"632":1,"768":1,"1323":2,"1416":1,"1426":2,"1499":1,"2097":1}}],["accidents",{"3":{"1948":1}}],["accidentally",{"3":{"386":1,"619":1,"1219":1,"1221":1,"1229":1,"1247":1,"1259":1,"1271":3,"1285":3,"1299":1,"1300":1,"1310":3,"1322":1,"1323":4,"1349":3,"1358":5,"1368":1,"1379":1,"1394":3,"1395":1,"1414":3,"1416":1,"1426":1,"1435":4,"1454":1,"1487":1,"1488":1,"1637":1,"1804":1,"1952":1,"2015":1,"2084":1}}],["accidental",{"3":{"68":1,"265":1,"768":1,"1285":3,"1308":1,"1309":1,"1349":2,"1358":2,"1368":1,"1394":1,"1395":3,"1414":1,"1435":3,"1453":1,"1473":1,"1488":1,"1525":1,"1551":1,"1555":1,"1568":1,"1637":1,"1685":1,"1708":1,"2032":1,"2187":1}}],["accident",{"3":{"0":1,"22":1,"25":1,"131":1,"236":1,"255":1,"558":1,"642":1,"700":1,"827":1,"832":1,"855":1,"907":1,"913":1,"1085":1,"1109":1,"1124":1,"1135":1,"1163":1,"1237":1,"1259":2,"1270":1,"1285":7,"1299":4,"1300":1,"1308":1,"1319":1,"1322":4,"1323":3,"1329":1,"1338":5,"1349":1,"1358":1,"1394":2,"1395":3,"1414":1,"1416":1,"1420":1,"1426":1,"1437":1,"1443":1,"1473":1,"1488":1,"1818":1,"1826":1,"1840":1,"1850":1,"1922":1,"1994":2,"2032":1,"2099":1,"2156":1,"2195":1}}],["accelerate",{"3":{"2224":1}}],["accelerated",{"3":{"461":1}}],["accent",{"3":{"1323":5,"1394":2,"1435":2,"1525":1,"1590":1,"1594":1,"1595":2,"1596":1}}],["accented",{"3":{"741":1,"1310":1,"1323":4,"1435":1,"1576":1,"1590":1,"1595":1}}],["accents",{"3":{"265":1,"1310":1,"1323":3,"1394":1}}],["accessing",{"3":{"1626":1}}],["accessiblelabel",{"3":{"1323":1}}],["accessible",{"3":{"617":1,"618":1,"619":1,"682":1,"954":1,"1026":1,"1323":7,"1414":1,"1431":1,"1435":9,"1437":1,"1454":1,"1487":1,"1490":1,"1606":1,"1619":1,"1621":1,"1629":1,"1630":2,"1631":3,"1639":1,"1640":1,"1642":1,"1643":1,"1740":2,"1749":2,"1755":1,"1757":1,"1772":2,"2056":1,"2227":1}}],["accessibilityinfomaxlength",{"3":{"1349":3,"1358":2,"1394":1}}],["accessibilityinfo",{"2":{"1630":1},"3":{"1349":4,"1358":11,"1368":3,"1379":2,"1394":4,"1495":1,"1629":2,"1630":2}}],["accessibilitytests",{"0":{"1607":1,"1741":1},"2":{"620":1,"621":1,"1595":1},"3":{"618":2,"620":1,"621":1,"1199":1,"1207":2,"1413":1,"1435":2,"1437":2,"1486":2,"1489":4,"1525":2,"1530":1,"1531":1,"1534":1,"1590":2,"1595":2,"1599":1,"1604":1,"1738":1}}],["accessibility",{"0":{"1432":1,"1557":1,"2056":1},"1":{"615":1,"616":1,"617":1,"618":1,"619":1,"620":1,"621":1,"622":1,"1604":1,"1605":1,"1606":1,"1607":1,"1608":1,"1609":1,"1641":1,"1642":1,"1643":1,"1644":1,"1738":1,"1739":1,"1740":1,"1741":1,"1742":1,"1743":1},"2":{"1416":1},"3":{"0":1,"276":1,"416":1,"615":1,"617":4,"618":1,"619":1,"620":2,"622":2,"860":1,"861":1,"862":1,"863":2,"955":1,"1170":1,"1202":1,"1203":5,"1207":8,"1213":1,"1214":1,"1216":1,"1323":15,"1349":4,"1358":14,"1368":3,"1379":5,"1394":7,"1395":6,"1401":1,"1413":1,"1414":3,"1416":35,"1427":2,"1432":3,"1435":26,"1437":3,"1452":1,"1460":1,"1487":4,"1489":1,"1490":1,"1491":1,"1492":1,"1495":1,"1498":1,"1523":1,"1525":3,"1529":1,"1530":2,"1531":1,"1532":2,"1534":1,"1557":1,"1560":1,"1564":1,"1573":1,"1576":4,"1584":1,"1585":1,"1587":1,"1588":3,"1590":3,"1594":2,"1595":2,"1596":1,"1597":2,"1601":1,"1604":1,"1628":1,"1629":2,"1630":2,"1636":2,"1641":2,"1668":3,"1670":2,"1672":1,"1673":1,"1738":1,"1795":1,"2074":1,"2106":1,"2212":1,"2220":1,"2231":1}}],["accessibilityviolationexception",{"2":{"618":1,"1427":1,"1432":1,"2056":1},"3":{"0":1,"618":2,"1199":2,"1207":2,"1427":1,"1432":2,"1435":6,"1487":3,"2056":1}}],["accessed",{"3":{"1435":1}}],["accesses",{"3":{"1323":1}}],["accesscoarselocation",{"3":{"1416":2}}],["accessservice",{"3":{"1358":5}}],["accessdeniedmessage",{"3":{"1271":3}}],["accesstokenkey",{"3":{"1416":1}}],["accesstokenlifetime",{"3":{"1299":4}}],["accesstokencookiename",{"3":{"1299":1}}],["accesstokenexpiry",{"2":{"1967":1},"3":{"1299":4,"1967":1}}],["accesstokenexpirationminutes",{"2":{"499":1,"1981":1},"3":{"499":1,"1299":4,"1981":1}}],["accesstoken",{"3":{"1184":1,"1299":6,"1310":1,"1323":13,"1416":1,"1435":7,"1640":2,"1967":1}}],["accesstokenprovider",{"2":{"1183":1},"3":{"1183":1,"1323":1,"1435":3}}],["accessorcache",{"3":{"1247":2}}],["accessors",{"3":{"135":2,"547":2,"657":1,"660":1,"674":1,"741":1,"1247":2,"1259":2,"1270":1,"1285":1,"1299":1,"1310":1,"1323":3,"1349":4,"1358":2,"1394":4,"1416":2,"1430":1,"1435":6}}],["accessor",{"3":{"26":1,"298":1,"303":2,"304":1,"549":1,"699":1,"1163":1,"1227":1,"1228":1,"1229":3,"1231":1,"1234":1,"1237":1,"1242":1,"1247":3,"1253":1,"1271":1,"1277":1,"1278":1,"1285":11,"1299":2,"1310":8,"1323":2,"1338":3,"1347":1,"1349":4,"1358":24,"1394":5,"1395":1,"1416":4,"1435":3,"1525":1,"1534":1,"1585":1,"1590":1,"1610":1,"1809":1,"2137":2}}],["access",{"0":{"2073":1},"1":{"496":1,"497":1,"498":1,"499":1,"500":1,"501":1,"502":1,"503":1,"542":1,"543":1,"544":1,"545":1,"546":1,"547":1,"548":1,"549":1,"550":1,"551":1,"552":1,"878":1,"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1},"2":{"207":1,"227":1,"229":1,"426":3,"511":1,"1760":1},"3":{"0":9,"31":2,"39":1,"41":1,"165":1,"166":1,"167":1,"171":1,"185":1,"186":1,"206":2,"207":6,"227":1,"229":1,"320":1,"350":2,"359":1,"403":2,"426":3,"458":2,"459":1,"461":1,"496":1,"498":3,"499":6,"500":3,"501":1,"503":2,"505":1,"506":3,"507":9,"508":1,"511":1,"512":2,"514":1,"527":1,"542":1,"545":2,"549":1,"605":1,"632":1,"718":1,"724":2,"727":1,"776":1,"798":1,"878":1,"880":1,"881":2,"884":1,"903":1,"904":1,"905":4,"906":1,"910":2,"973":1,"1059":1,"1117":1,"1133":1,"1168":3,"1170":1,"1183":2,"1184":3,"1187":1,"1196":1,"1212":2,"1228":1,"1229":1,"1239":1,"1241":1,"1247":7,"1271":4,"1272":1,"1276":1,"1279":1,"1285":14,"1287":2,"1288":2,"1289":2,"1296":2,"1297":5,"1299":75,"1300":1,"1303":1,"1305":1,"1308":6,"1310":17,"1321":2,"1322":14,"1323":52,"1338":1,"1346":1,"1347":1,"1349":3,"1358":7,"1361":2,"1368":5,"1379":3,"1380":1,"1394":10,"1395":19,"1407":1,"1408":1,"1409":1,"1414":17,"1416":14,"1426":1,"1435":15,"1437":3,"1444":1,"1453":1,"1454":1,"1456":1,"1458":1,"1459":1,"1466":6,"1476":1,"1486":1,"1523":3,"1525":4,"1533":1,"1534":2,"1543":1,"1547":1,"1554":1,"1561":1,"1566":3,"1576":2,"1581":2,"1589":1,"1590":2,"1598":1,"1610":2,"1620":2,"1622":1,"1623":1,"1626":1,"1629":2,"1630":1,"1631":2,"1634":1,"1636":3,"1637":1,"1638":2,"1639":1,"1640":14,"1647":1,"1652":1,"1666":2,"1746":3,"1752":1,"1756":2,"1760":1,"1763":1,"1764":2,"1766":2,"1768":1,"1770":2,"1771":1,"1814":3,"1848":1,"1922":1,"1964":1,"1965":3,"1967":11,"1971":2,"1972":2,"1974":3,"1979":3,"1980":1,"1981":5,"1983":1,"1984":4,"1988":1,"1995":1,"2062":2,"2065":3,"2067":1,"2068":1,"2073":1,"2125":1,"2127":1,"2140":1,"2199":1,"2202":1,"2227":1,"2231":2,"2239":1}}],["acceptverbs",{"3":{"1435":2}}],["acceptqueuesessions",{"3":{"1349":1}}],["acceptqueuesessioncount",{"3":{"1349":1}}],["acceptqueuecount",{"3":{"1349":1}}],["acceptqueue",{"3":{"1349":1,"1358":17,"1394":1}}],["acceptlanguage",{"3":{"1323":1}}],["acceptattribute",{"3":{"1349":1,"1358":2}}],["acceptable",{"3":{"149":1,"435":1,"507":1,"530":1,"791":1,"990":1,"1116":1,"1229":1,"1237":1,"1285":1,"1299":3,"1300":1,"1308":1,"1323":4,"1358":3,"1395":2,"1435":2,"1454":1,"1466":1,"1629":1,"1637":3,"1638":1,"1897":1,"1946":1,"1970":1,"2165":1}}],["acceptances",{"3":{"1358":1}}],["acceptance",{"1":{"787":1,"788":1,"789":1,"790":1,"791":1,"792":1,"793":1,"794":1,"795":1},"3":{"0":2,"68":1,"87":1,"305":1,"390":1,"454":1,"562":1,"621":1,"787":1,"790":3,"791":1,"792":2,"794":1,"857":3,"946":1,"976":1,"1020":1,"1212":1,"1299":1,"1358":6,"1368":1,"1375":1,"1379":1,"1401":1,"1476":1,"1525":1,"1565":2,"1584":1,"1598":1,"1629":2,"1630":1,"1708":1,"2126":1,"2165":2,"2167":1,"2168":2,"2189":1}}],["accepting",{"3":{"0":1,"291":1,"690":1,"767":1,"1247":2,"1267":1,"1270":3,"1299":2,"1300":1,"1310":3,"1323":3,"1349":2,"1358":4,"1394":2,"1395":3,"1399":1,"1412":1,"1426":1,"1435":2,"1454":1,"1952":1,"1956":1,"2032":1,"2037":1,"2124":1}}],["acceptsdirectandinheritedandoptedoutdeclarations",{"3":{"1435":2}}],["accepts",{"3":{"0":3,"68":1,"72":1,"81":1,"93":1,"136":1,"170":1,"198":1,"237":1,"303":1,"312":1,"335":1,"341":1,"350":1,"374":1,"402":1,"427":1,"443":1,"469":1,"535":1,"536":1,"540":1,"541":1,"620":1,"674":1,"690":1,"740":1,"741":1,"761":1,"764":1,"766":2,"769":1,"773":1,"785":1,"863":1,"975":1,"1017":1,"1033":1,"1044":1,"1100":1,"1116":1,"1133":1,"1135":1,"1176":1,"1184":2,"1229":1,"1237":1,"1242":1,"1247":2,"1264":1,"1270":2,"1271":2,"1285":12,"1296":1,"1299":6,"1308":4,"1309":1,"1310":7,"1322":3,"1323":10,"1326":1,"1338":3,"1349":4,"1358":10,"1368":1,"1383":2,"1394":15,"1395":6,"1401":3,"1413":1,"1414":5,"1416":3,"1429":1,"1435":23,"1437":2,"1440":1,"1454":1,"1466":2,"1491":1,"1525":2,"1530":1,"1576":1,"1590":2,"1595":2,"1599":1,"1630":1,"1639":1,"1652":1,"1684":1,"1764":1,"1842":1,"1911":1,"1949":1,"1957":1,"1961":1,"1981":1,"2032":1,"2035":1,"2036":1,"2043":1,"2125":2,"2161":1,"2191":1}}],["accept",{"2":{"742":1,"745":1,"1632":1},"3":{"0":3,"98":1,"103":1,"135":1,"265":4,"293":1,"309":1,"370":2,"371":3,"372":1,"375":1,"404":1,"440":1,"517":1,"520":1,"618":1,"733":1,"740":4,"742":2,"743":1,"745":1,"773":1,"775":1,"790":1,"826":1,"881":1,"1018":1,"1059":1,"1160":1,"1212":2,"1231":1,"1247":1,"1270":2,"1271":1,"1285":8,"1297":1,"1299":8,"1305":1,"1308":1,"1310":6,"1322":2,"1323":6,"1338":1,"1342":1,"1343":1,"1346":2,"1347":1,"1349":12,"1350":1,"1357":1,"1358":11,"1365":1,"1368":2,"1379":2,"1395":3,"1401":1,"1414":1,"1416":3,"1435":11,"1437":1,"1443":1,"1452":1,"1454":2,"1458":1,"1471":1,"1473":1,"1491":1,"1533":1,"1576":1,"1588":1,"1590":1,"1599":1,"1629":2,"1630":4,"1631":1,"1632":1,"1634":2,"1639":1,"1665":1,"1764":1,"1911":1,"1923":1,"1936":1,"1953":1,"1964":2,"1967":1,"2082":1,"2085":2,"2125":1,"2127":1,"2166":1,"2168":1,"2231":1}}],["acceptedsessions",{"3":{"1349":1}}],["acceptedsessioncount",{"3":{"1349":1}}],["acceptedcount",{"3":{"1349":1}}],["accepted",{"0":{"139":1,"1533":1,"1584":1,"1598":1},"2":{"1099":1,"1103":1,"1366":1,"1632":1,"2189":1},"3":{"0":10,"1":1,"3":1,"9":1,"15":1,"17":1,"27":1,"29":1,"37":1,"45":1,"51":1,"57":1,"66":1,"76":1,"80":1,"84":1,"88":1,"90":1,"107":1,"115":1,"130":2,"135":5,"136":1,"139":3,"145":1,"155":1,"164":1,"173":1,"177":1,"178":1,"184":1,"193":1,"205":1,"212":1,"223":1,"230":1,"233":1,"241":1,"245":2,"248":1,"249":1,"263":1,"267":2,"271":1,"279":2,"283":1,"286":1,"289":1,"295":1,"298":1,"308":1,"316":1,"328":1,"339":1,"344":1,"348":1,"357":1,"368":1,"370":2,"371":2,"372":1,"373":1,"378":1,"386":2,"390":1,"395":1,"411":1,"415":3,"418":1,"422":1,"432":1,"438":1,"441":1,"446":1,"457":1,"461":1,"468":1,"480":1,"497":1,"505":1,"516":1,"520":1,"526":1,"530":1,"534":1,"543":2,"544":1,"545":1,"547":2,"554":1,"555":1,"562":1,"566":1,"570":1,"581":1,"585":1,"589":1,"597":1,"607":1,"616":1,"620":1,"624":1,"631":1,"638":1,"647":1,"651":1,"655":1,"663":1,"672":1,"674":1,"680":1,"688":1,"690":1,"696":1,"705":1,"713":1,"723":1,"731":1,"733":1,"739":1,"747":1,"748":1,"751":3,"755":1,"756":1,"758":1,"764":1,"766":2,"772":1,"775":1,"776":1,"780":1,"788":1,"790":2,"797":2,"808":2,"815":1,"817":1,"825":1,"827":2,"836":1,"844":1,"848":1,"852":1,"856":1,"859":1,"867":1,"879":1,"887":1,"895":1,"903":1,"907":1,"912":1,"920":1,"929":1,"933":1,"937":1,"944":1,"952":1,"962":1,"970":1,"978":1,"986":1,"994":1,"996":1,"1002":1,"1010":1,"1012":1,"1016":1,"1024":1,"1028":1,"1032":1,"1036":1,"1040":1,"1048":1,"1052":1,"1057":1,"1065":1,"1073":1,"1081":1,"1089":1,"1098":1,"1099":1,"1102":1,"1103":2,"1106":1,"1108":1,"1114":1,"1116":1,"1118":2,"1122":1,"1126":1,"1131":1,"1139":2,"1149":1,"1159":1,"1166":1,"1173":1,"1182":1,"1212":4,"1247":2,"1270":1,"1284":2,"1285":3,"1292":1,"1299":13,"1300":3,"1308":2,"1310":4,"1321":1,"1322":9,"1323":8,"1327":1,"1338":7,"1343":2,"1346":1,"1349":11,"1350":1,"1351":1,"1352":2,"1356":1,"1358":74,"1366":1,"1368":2,"1374":1,"1375":1,"1379":7,"1389":1,"1394":10,"1395":9,"1398":1,"1401":4,"1414":13,"1416":4,"1429":2,"1435":15,"1437":2,"1452":1,"1454":1,"1458":1,"1473":2,"1475":1,"1476":1,"1482":1,"1495":1,"1514":1,"1520":1,"1523":2,"1525":6,"1529":1,"1530":4,"1531":3,"1533":8,"1553":1,"1571":1,"1574":3,"1575":1,"1576":3,"1580":3,"1581":1,"1582":2,"1583":2,"1584":6,"1585":1,"1588":3,"1590":10,"1594":3,"1595":6,"1596":4,"1597":1,"1598":3,"1599":1,"1629":3,"1630":10,"1631":5,"1632":2,"1634":2,"1637":3,"1639":4,"1640":3,"1740":2,"1763":1,"1764":1,"1766":2,"1767":1,"1814":2,"2000":2,"2031":1,"2037":1,"2121":2,"2125":1,"2126":1,"2157":1,"2165":2,"2189":1,"2190":1,"2197":1,"2231":48}}],["aca",{"0":{"1333":1},"3":{"0":3,"91":3,"92":2,"93":2,"95":1,"96":2,"98":2,"99":1,"100":2,"102":3,"293":1,"757":2,"827":1,"829":1,"913":2,"914":2,"1324":1,"1328":1,"1330":1,"1333":1,"1338":7,"1378":1,"1440":2,"1442":5,"1445":2,"1449":2,"1466":15,"1475":1,"1533":1,"1576":1,"1706":1}}],["alquimia",{"3":{"2219":1}}],["aloud",{"3":{"1394":1,"1416":7}}],["along",{"0":{"2233":1},"3":{"58":1,"81":1,"165":1,"186":1,"573":1,"616":1,"657":1,"697":1,"700":1,"711":1,"744":2,"778":1,"798":1,"826":1,"981":1,"991":1,"1175":1,"1178":1,"1217":1,"1251":1,"1259":3,"1271":2,"1285":4,"1292":1,"1296":1,"1299":2,"1300":2,"1303":1,"1311":2,"1322":1,"1323":2,"1342":1,"1349":7,"1358":6,"1394":3,"1395":6,"1401":4,"1403":1,"1408":1,"1414":2,"1435":4,"1440":1,"1442":1,"1454":1,"1464":1,"1466":2,"1485":1,"1491":1,"1804":1,"1842":1,"1890":1,"1937":1,"1976":1,"1982":1,"1998":1,"2018":2,"2070":1,"2095":1,"2165":1,"2182":1}}],["alongside",{"3":{"0":4,"31":1,"71":1,"78":1,"91":1,"95":1,"173":1,"195":1,"219":1,"225":1,"272":1,"312":1,"319":1,"341":1,"350":1,"363":1,"406":1,"418":1,"449":1,"470":1,"499":1,"534":1,"545":1,"549":1,"563":1,"572":1,"599":1,"609":2,"611":1,"617":1,"642":1,"643":1,"647":1,"657":1,"665":1,"696":1,"728":1,"785":1,"790":2,"804":1,"832":1,"892":1,"905":1,"926":1,"932":1,"960":1,"982":1,"1027":1,"1042":1,"1059":1,"1069":1,"1075":1,"1104":1,"1116":1,"1232":1,"1237":2,"1247":2,"1259":4,"1262":1,"1270":2,"1271":2,"1285":21,"1287":1,"1289":1,"1299":19,"1300":6,"1308":3,"1310":13,"1311":1,"1315":1,"1322":10,"1323":17,"1331":1,"1332":1,"1335":1,"1338":9,"1341":1,"1346":1,"1349":10,"1352":1,"1358":27,"1365":1,"1368":6,"1373":3,"1379":4,"1394":23,"1395":9,"1397":1,"1399":1,"1401":2,"1402":1,"1408":1,"1414":9,"1415":1,"1416":10,"1426":3,"1427":1,"1431":1,"1433":1,"1435":18,"1437":5,"1440":1,"1441":2,"1442":1,"1444":1,"1454":1,"1466":8,"1474":1,"1486":1,"1487":2,"1488":1,"1495":3,"1525":1,"1530":1,"1536":1,"1545":1,"1569":1,"1576":2,"1584":1,"1626":1,"1629":3,"1630":5,"1638":1,"1639":1,"1654":1,"1662":1,"1685":1,"1698":1,"1714":1,"1736":1,"1754":1,"1760":1,"1763":1,"1768":1,"1769":1,"1770":1,"1814":1,"1926":1,"1928":1,"1932":1,"1943":1,"1945":1,"1955":1,"1980":1,"1981":1,"1999":1,"2005":1,"2023":1,"2099":1,"2125":1,"2132":1,"2149":1,"2165":1,"2231":1}}],["alone",{"3":{"0":12,"23":1,"24":2,"38":1,"54":1,"57":1,"63":1,"69":1,"73":1,"74":1,"81":1,"95":1,"135":2,"180":1,"186":2,"203":1,"207":1,"255":1,"265":1,"268":1,"285":1,"312":1,"314":1,"318":1,"324":1,"325":1,"341":1,"386":1,"390":1,"391":1,"399":1,"409":1,"426":1,"434":1,"449":1,"452":1,"465":1,"497":1,"523":1,"536":2,"539":2,"547":1,"549":3,"589":1,"593":1,"601":1,"607":1,"616":1,"624":1,"635":1,"638":1,"655":1,"674":1,"675":1,"707":1,"733":1,"758":1,"766":1,"767":1,"789":1,"794":1,"799":1,"806":1,"826":1,"827":1,"828":1,"830":1,"849":1,"854":1,"883":1,"889":1,"891":1,"897":1,"905":2,"906":2,"907":1,"926":1,"938":1,"939":1,"972":1,"988":1,"994":1,"1018":1,"1026":2,"1059":2,"1061":1,"1065":1,"1067":1,"1074":1,"1083":1,"1091":3,"1094":1,"1116":1,"1124":1,"1127":1,"1128":1,"1134":1,"1212":1,"1225":1,"1229":4,"1237":6,"1242":1,"1247":1,"1259":3,"1263":1,"1270":5,"1271":1,"1280":2,"1285":19,"1289":3,"1299":10,"1300":4,"1308":3,"1309":1,"1310":10,"1319":1,"1321":1,"1322":12,"1323":16,"1331":1,"1338":12,"1348":1,"1349":10,"1358":27,"1361":1,"1368":2,"1376":1,"1378":1,"1379":2,"1394":4,"1395":14,"1401":6,"1405":1,"1414":6,"1415":1,"1416":6,"1423":1,"1426":3,"1432":1,"1435":33,"1437":4,"1446":1,"1454":1,"1457":1,"1466":4,"1469":1,"1473":1,"1476":1,"1480":1,"1491":2,"1495":2,"1506":1,"1525":1,"1547":1,"1557":1,"1576":3,"1590":2,"1637":1,"1640":1,"1650":1,"1669":1,"1684":1,"1687":1,"1726":1,"1729":1,"1741":1,"1748":1,"1764":1,"1772":1,"1785":1,"1793":1,"1804":1,"1814":1,"1831":1,"1848":1,"1870":2,"1875":1,"1932":3,"1935":1,"1968":1,"1981":2,"1984":3,"1988":1,"2000":1,"2007":1,"2037":1,"2043":1,"2057":1,"2063":1,"2140":1,"2159":1}}],["alarm",{"3":{"1416":1,"1466":1,"1475":2,"1524":1,"1525":1}}],["alarms",{"3":{"1416":1}}],["alarming",{"3":{"1323":1,"1435":1,"1967":1}}],["alan",{"3":{"1363":1,"1368":2}}],["alb",{"3":{"175":1}}],["alpine",{"3":{"1437":1}}],["alpha",{"3":{"1416":2,"1479":1}}],["alphanumerics",{"3":{"1299":1}}],["alphanumeric",{"3":{"1271":1,"1299":1,"1323":2,"1435":1}}],["alphabetical",{"3":{"1435":1,"1638":1,"1639":1}}],["alphabetically",{"3":{"166":1,"1034":1,"1279":1,"1285":5,"1394":2,"1435":1,"1653":1}}],["alphabet",{"3":{"1059":1,"1285":2,"1292":1,"1299":1,"1435":1,"1630":1}}],["alpn",{"1":{"89":1,"90":1,"91":1,"92":1,"93":1,"94":1,"95":1,"96":1,"97":1,"98":1,"99":1,"100":1,"101":1,"102":1,"103":1,"104":1,"105":1},"3":{"0":1,"31":1,"53":1,"89":1,"91":2,"97":2,"98":8,"99":1,"100":1,"1067":1,"1325":1,"1336":1,"1338":5,"1378":1,"1429":1,"1440":1,"1443":1,"1446":1,"1466":1,"1655":1,"2008":1,"2024":3}}],["almost",{"0":{"1370":1,"2070":1},"3":{"58":1,"265":1,"331":1,"386":1,"396":1,"740":1,"805":1,"881":1,"965":1,"1006":2,"1132":1,"1217":1,"1259":1,"1261":1,"1286":1,"1309":1,"1310":1,"1322":2,"1323":3,"1338":1,"1339":1,"1369":1,"1370":1,"1378":1,"1379":3,"1394":1,"1395":1,"1402":1,"1414":2,"1422":1,"1435":3,"1452":1,"1454":1,"1464":1,"1466":1,"1490":1,"1526":1,"1536":1,"1577":1,"1591":1,"1681":1,"1685":1,"1787":1,"1796":1,"1799":1,"1801":1,"1809":1,"1819":1,"1853":2,"1899":2,"1908":1,"1912":1,"1950":2,"1986":2,"1999":1,"2028":1,"2038":1,"2049":1,"2060":1,"2069":1,"2128":1,"2133":1,"2153":2}}],["algebra",{"3":{"1358":2}}],["alg=rs256",{"3":{"1299":1}}],["alg",{"2":{"31":1,"1896":1,"1898":1},"3":{"0":1,"31":1,"1184":1,"1299":3,"1575":2,"1576":1,"1896":4,"1898":1}}],["algorithms",{"3":{"178":1,"1894":1,"1898":1}}],["algorithm",{"0":{"1880":1,"1896":1},"3":{"0":4,"31":2,"32":1,"173":1,"178":1,"279":1,"280":1,"309":1,"310":1,"311":2,"312":1,"360":1,"499":2,"503":1,"674":1,"945":1,"946":2,"947":2,"963":2,"1212":1,"1237":1,"1247":1,"1286":1,"1287":2,"1290":1,"1291":3,"1299":22,"1300":3,"1310":18,"1314":1,"1322":3,"1349":2,"1358":3,"1394":4,"1435":3,"1437":1,"1466":1,"1487":2,"1495":2,"1525":1,"1576":2,"1595":1,"1652":1,"1666":3,"1771":1,"1877":1,"1880":1,"1885":1,"1896":4,"1898":3,"1901":6,"1902":1,"1903":1,"1904":1,"1981":3,"1984":2,"2011":1,"2093":1,"2142":1}}],["altitudes",{"3":{"1831":1,"1832":1,"1833":1}}],["altitude",{"0":{"1831":1},"3":{"1831":1,"1832":1,"2097":1}}],["alt",{"2":{"1605":1,"1739":1},"3":{"1323":1,"1394":1,"1414":1,"1437":1,"1490":1,"1557":1,"1605":1,"1739":1}}],["alt=",{"3":{"1323":1,"1394":1}}],["altogether",{"3":{"740":1,"1247":1,"1310":1,"1358":2,"1372":1,"1477":1}}],["alters",{"3":{"1426":1,"1733":1}}],["altered",{"3":{"1285":1,"1323":1,"1390":1,"1435":1}}],["alter",{"3":{"620":1,"1285":1,"1426":1,"1476":1,"1638":1}}],["altercolumn",{"2":{"566":1},"3":{"566":1}}],["alternation",{"3":{"626":1,"1308":1}}],["alternating",{"3":{"625":1,"626":2,"1455":3,"1460":1,"1491":1,"1492":1,"1533":1,"1598":1}}],["alternatives",{"0":{"151":1,"286":1,"405":1,"785":1,"1144":1,"1154":1,"1178":1,"1187":1},"3":{"1":2,"255":1,"256":1,"989":1,"1035":1,"1043":1,"1051":1,"1117":1,"1133":1,"1323":1,"1435":2,"1558":1,"1728":1,"2066":1}}],["alternative",{"3":{"0":4,"1":1,"99":1,"137":1,"255":1,"283":1,"343":1,"401":1,"461":1,"468":1,"469":1,"470":1,"474":1,"541":1,"551":1,"557":1,"627":1,"635":1,"676":1,"683":1,"699":1,"733":1,"792":1,"797":1,"799":3,"803":1,"804":1,"805":1,"809":1,"815":2,"828":1,"829":1,"855":1,"856":1,"871":1,"889":1,"901":1,"907":1,"924":1,"930":1,"947":1,"965":1,"981":2,"989":1,"1000":1,"1051":1,"1090":1,"1139":1,"1140":1,"1149":1,"1212":1,"1229":2,"1237":2,"1247":3,"1259":1,"1270":2,"1271":1,"1285":10,"1299":6,"1300":1,"1308":1,"1310":2,"1322":7,"1323":5,"1338":5,"1358":10,"1368":2,"1379":1,"1394":5,"1395":8,"1416":4,"1435":7,"1440":1,"1454":1,"1476":1,"1523":1,"1525":1,"1530":1,"1533":1,"1544":1,"1558":1,"1576":1,"1637":1,"1751":1,"1753":1,"1762":1,"1773":2,"1809":1,"1811":1,"1814":1,"1910":1,"1950":1,"2023":1,"2074":1,"2149":1,"2165":1,"2168":1}}],["alternates",{"3":{"109":1,"1525":1}}],["alternate",{"3":{"109":1,"1220":1,"1229":1,"1285":1,"1435":2,"1437":1,"1491":1,"1631":13,"1640":5}}],["although",{"3":{"540":1,"1259":1,"1323":1,"1349":1,"1358":1,"1395":2,"1523":1,"1535":1,"1576":2,"1595":2}}],["alttext",{"3":{"0":1,"682":1,"1394":1,"1395":1}}],["alertkeyregex",{"3":{"1435":1}}],["alertable",{"3":{"1310":1,"1358":1}}],["alertemailaddress",{"2":{"1465":1,"1471":1,"1473":1,"2157":1},"3":{"609":1,"1454":1,"1465":1,"1466":2,"1471":1,"1473":1,"1599":1,"2157":1}}],["alerted",{"3":{"403":2,"612":1,"1017":1,"1018":1,"1212":1}}],["alerting",{"1":{"606":1,"607":1,"608":1,"609":1,"610":1,"611":1,"612":1,"613":1,"614":1},"3":{"0":2,"125":1,"606":1,"765":1,"770":1,"1212":1,"1259":1,"1323":1,"1435":1,"1466":3,"1473":1,"1475":1,"1481":1,"1549":1,"1574":1,"1576":2,"1582":1,"1585":1,"1705":1,"2045":1,"2157":1,"2219":1,"2231":1}}],["alertseverityregex",{"3":{"1435":1}}],["alerts",{"3":{"0":6,"395":1,"403":1,"409":1,"607":3,"608":2,"609":10,"610":1,"611":5,"614":1,"759":1,"770":1,"791":1,"899":1,"914":1,"971":1,"1259":1,"1322":1,"1395":2,"1430":1,"1435":8,"1454":3,"1455":1,"1456":1,"1457":1,"1461":1,"1466":8,"1468":2,"1471":1,"1473":3,"1475":8,"1481":1,"1482":1,"1524":1,"1525":3,"1549":1,"1552":1,"1567":1,"1576":1,"1581":2,"1582":1,"1590":3,"1669":2,"1675":1,"1677":1,"1678":1,"1705":1,"1708":1,"2045":1,"2157":5,"2159":2,"2231":1}}],["alert",{"0":{"1475":1,"2045":1},"1":{"606":1,"607":1,"608":1,"609":1,"610":1,"611":1,"612":1,"613":1,"614":1},"2":{"607":1,"1473":1},"3":{"0":10,"24":1,"109":1,"125":1,"224":1,"255":1,"395":1,"400":1,"403":1,"407":1,"409":1,"606":1,"607":7,"608":6,"609":26,"610":5,"611":5,"612":3,"613":2,"649":1,"759":1,"765":1,"821":1,"823":1,"896":1,"901":1,"914":4,"916":1,"918":1,"971":1,"976":1,"1016":3,"1018":3,"1019":4,"1020":2,"1090":1,"1093":1,"1096":1,"1102":1,"1199":2,"1207":1,"1212":1,"1256":1,"1259":1,"1270":1,"1285":1,"1310":3,"1322":5,"1323":6,"1358":1,"1388":1,"1394":2,"1395":4,"1401":3,"1430":2,"1432":1,"1435":51,"1441":1,"1442":1,"1454":1,"1461":1,"1462":1,"1464":2,"1465":3,"1466":28,"1468":2,"1469":1,"1470":1,"1471":3,"1473":11,"1475":23,"1481":3,"1482":3,"1483":1,"1487":4,"1488":2,"1492":1,"1495":2,"1522":1,"1523":7,"1525":10,"1529":1,"1530":4,"1531":1,"1532":1,"1534":2,"1546":1,"1576":4,"1588":3,"1590":7,"1594":1,"1595":5,"1596":1,"1597":1,"1599":2,"1606":1,"1626":1,"1640":1,"1642":1,"1644":1,"1669":1,"1670":1,"1677":1,"1708":3,"1740":1,"1842":1,"1908":1,"1910":1,"1918":1,"1945":1,"2042":1,"2045":8,"2048":1,"2078":1,"2155":3,"2157":11,"2231":2}}],["align",{"3":{"1525":1,"1543":1,"2034":1}}],["aligns",{"3":{"1285":1}}],["alignment",{"3":{"147":1,"1299":1,"1322":1,"1358":1,"1495":3,"1525":1,"1638":1}}],["aligned",{"3":{"32":1,"86":1,"642":1,"650":1,"1297":1,"1299":2,"1305":1,"1310":2,"1323":1,"1349":1,"1358":2,"1395":2,"1415":1,"1416":1,"1533":1,"1534":1,"1563":1,"1711":1}}],["aliased",{"3":{"186":1,"1052":1,"1054":1,"1285":1,"1310":1,"1322":1,"1358":2,"1395":1,"1397":1,"1414":4,"1415":3,"1435":1}}],["aliases",{"0":{"1938":1},"1":{"467":1,"468":1,"469":1,"470":1,"471":1,"472":1,"473":1,"474":1,"475":1,"476":1,"477":1,"478":1,"796":1,"797":1,"798":1,"799":1,"800":1,"801":1,"802":1,"803":1,"804":1,"805":1,"806":1,"1047":1,"1048":1,"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1},"3":{"0":11,"467":1,"468":8,"470":3,"471":1,"472":3,"473":1,"474":1,"475":3,"476":3,"477":3,"478":3,"548":1,"633":1,"656":1,"660":1,"661":2,"796":1,"797":2,"798":3,"799":4,"805":1,"1047":1,"1049":4,"1050":3,"1051":2,"1054":2,"1055":1,"1192":1,"1196":1,"1202":1,"1203":1,"1212":7,"1231":2,"1280":1,"1285":3,"1299":2,"1308":7,"1310":2,"1343":1,"1349":42,"1358":38,"1368":1,"1379":10,"1393":1,"1394":31,"1395":32,"1401":23,"1408":2,"1414":4,"1415":1,"1430":1,"1435":4,"1454":1,"1488":1,"1495":1,"1540":1,"1576":1,"1590":1,"1649":1,"1650":1,"1661":2,"1684":1,"1685":1,"1688":1,"1726":1,"1809":4,"1811":1,"1938":2,"1940":1,"2088":1,"2229":1,"2231":4}}],["aliasing",{"3":{"0":1,"844":1,"848":1,"1414":1,"1890":1}}],["alias",{"3":{"0":2,"295":1,"342":1,"468":7,"469":2,"470":7,"471":1,"472":5,"474":1,"475":3,"476":3,"477":3,"478":3,"547":1,"633":2,"656":1,"675":1,"676":1,"698":1,"701":1,"797":4,"798":2,"799":3,"800":1,"801":1,"802":1,"803":6,"804":4,"805":4,"905":1,"910":1,"1048":2,"1049":1,"1051":1,"1053":1,"1054":2,"1175":1,"1177":1,"1196":1,"1212":3,"1231":2,"1237":11,"1247":3,"1259":2,"1271":1,"1285":13,"1295":1,"1299":26,"1300":1,"1308":12,"1310":4,"1322":10,"1323":5,"1337":1,"1338":1,"1343":1,"1349":36,"1358":64,"1368":2,"1379":10,"1381":1,"1394":28,"1395":40,"1397":1,"1401":11,"1403":1,"1414":22,"1415":1,"1435":7,"1446":1,"1464":1,"1479":1,"1495":1,"1525":1,"1610":1,"1648":5,"1649":1,"1650":1,"1660":1,"1683":2,"1684":1,"1694":1,"1698":2,"1699":2,"1700":1,"1726":5,"1727":3,"1809":4,"1815":1,"1962":1,"2088":2,"2096":2,"2229":1,"2231":2}}],["alike",{"3":{"0":2,"201":1,"243":1,"460":1,"490":1,"491":1,"492":1,"640":1,"827":1,"972":1,"1050":1,"1237":1,"1247":2,"1259":1,"1271":1,"1280":1,"1285":4,"1293":1,"1299":2,"1306":1,"1323":1,"1328":1,"1338":2,"1343":1,"1349":1,"1379":1,"1394":2,"1401":1,"1414":1,"1435":3,"1446":1,"1455":1,"1474":1,"1576":1,"1630":1,"1636":1,"1661":1,"1665":1,"1760":1,"1921":1,"2134":1,"2155":1}}],["aliveresponse",{"3":{"1435":1}}],["alive",{"2":{"1336":1},"3":{"0":7,"94":1,"99":4,"100":1,"175":1,"233":1,"235":1,"238":1,"239":1,"399":1,"402":2,"404":1,"572":1,"698":1,"757":1,"761":1,"827":6,"829":3,"830":1,"832":1,"833":1,"848":1,"849":1,"913":1,"1066":1,"1068":1,"1144":1,"1186":1,"1276":1,"1285":2,"1299":3,"1300":3,"1310":1,"1311":2,"1323":5,"1327":5,"1328":1,"1329":1,"1331":5,"1332":2,"1336":3,"1337":1,"1338":36,"1342":1,"1358":1,"1394":1,"1395":1,"1414":1,"1415":3,"1416":3,"1429":3,"1435":4,"1437":1,"1440":6,"1442":4,"1444":1,"1445":2,"1446":2,"1449":1,"1454":1,"1466":4,"1487":2,"1489":1,"1549":1,"1590":1,"1667":1,"1668":1,"1669":2,"1676":3,"1718":1,"1719":1,"1726":1,"1890":1,"1999":1,"2006":1,"2009":1,"2013":1,"2023":3,"2037":2,"2054":1,"2055":1,"2158":1}}],["alsoanalyzesqueryspecifications",{"3":{"1435":2}}],["alsoliftwhen",{"3":{"1325":2,"1338":3,"1440":1,"1443":4}}],["also",{"0":{"576":1,"1247":1},"3":{"0":24,"5":1,"19":1,"21":2,"24":2,"30":2,"46":1,"57":1,"62":1,"71":1,"72":1,"73":1,"74":1,"98":3,"100":1,"103":1,"109":1,"110":1,"122":2,"125":1,"126":1,"127":1,"136":1,"145":1,"147":1,"150":1,"152":2,"157":2,"161":1,"164":1,"166":1,"168":1,"175":2,"182":1,"188":1,"189":1,"193":1,"197":1,"200":1,"201":1,"212":2,"214":1,"225":2,"229":1,"234":2,"235":1,"241":1,"242":1,"248":2,"250":1,"255":2,"256":1,"258":1,"265":1,"279":1,"280":1,"283":1,"284":1,"295":1,"318":1,"320":1,"326":1,"328":1,"349":1,"350":1,"352":1,"364":1,"370":1,"374":2,"375":1,"390":1,"395":1,"397":1,"399":1,"402":1,"426":1,"433":1,"436":1,"446":1,"448":2,"452":1,"453":1,"458":2,"459":1,"462":1,"468":1,"475":1,"497":1,"499":2,"502":1,"505":1,"514":1,"526":1,"528":1,"529":1,"530":2,"531":1,"536":1,"538":2,"539":1,"540":1,"545":3,"547":2,"549":1,"550":2,"551":2,"552":1,"554":1,"556":1,"564":1,"570":1,"575":1,"576":1,"583":1,"598":1,"599":1,"607":2,"609":2,"612":1,"624":2,"626":1,"628":2,"633":2,"634":1,"649":1,"651":2,"652":1,"655":1,"657":3,"664":1,"665":1,"676":1,"690":1,"691":1,"692":1,"702":1,"708":1,"715":1,"717":1,"720":1,"732":1,"733":2,"735":1,"736":1,"743":1,"744":1,"759":1,"764":1,"765":1,"766":2,"774":1,"775":1,"781":1,"782":2,"790":1,"792":1,"798":1,"799":1,"809":1,"812":1,"817":1,"818":2,"821":2,"822":1,"825":1,"826":1,"827":1,"829":1,"833":3,"840":1,"846":1,"848":1,"854":1,"857":1,"863":2,"864":1,"868":2,"871":1,"877":1,"891":1,"907":1,"909":1,"913":1,"917":2,"920":1,"922":1,"926":1,"927":1,"934":1,"938":1,"940":1,"946":1,"954":2,"960":2,"963":1,"967":1,"971":1,"988":1,"1004":3,"1011":1,"1018":2,"1020":1,"1021":1,"1029":1,"1043":1,"1045":1,"1066":1,"1068":1,"1074":1,"1078":2,"1086":1,"1089":1,"1093":2,"1095":1,"1115":1,"1118":1,"1124":3,"1128":1,"1132":1,"1133":1,"1136":1,"1140":1,"1152":1,"1160":1,"1168":1,"1169":1,"1170":1,"1174":2,"1216":1,"1220":1,"1227":1,"1229":7,"1231":2,"1233":1,"1234":2,"1235":1,"1236":2,"1237":15,"1239":1,"1240":1,"1242":1,"1247":13,"1252":1,"1255":1,"1256":2,"1258":3,"1259":17,"1263":3,"1264":1,"1265":1,"1267":1,"1270":33,"1271":5,"1272":1,"1273":2,"1274":2,"1275":1,"1276":1,"1277":2,"1278":3,"1279":2,"1284":1,"1285":60,"1286":1,"1288":1,"1293":1,"1299":43,"1300":10,"1303":1,"1304":1,"1306":2,"1308":19,"1309":4,"1310":44,"1311":7,"1312":1,"1314":2,"1315":3,"1316":2,"1317":1,"1319":1,"1321":2,"1322":50,"1323":61,"1325":1,"1327":1,"1332":2,"1335":1,"1336":2,"1338":37,"1341":4,"1342":2,"1343":1,"1344":1,"1345":1,"1346":4,"1348":2,"1349":41,"1351":1,"1354":4,"1358":107,"1361":2,"1364":1,"1365":1,"1367":1,"1368":8,"1370":1,"1372":2,"1374":1,"1375":3,"1377":1,"1378":1,"1379":13,"1390":1,"1394":51,"1395":65,"1396":1,"1398":2,"1399":1,"1401":23,"1404":1,"1407":1,"1410":1,"1414":32,"1415":9,"1416":24,"1417":1,"1421":1,"1423":1,"1424":1,"1426":5,"1428":1,"1430":2,"1433":3,"1434":1,"1435":77,"1437":7,"1440":3,"1442":6,"1443":1,"1445":2,"1446":1,"1448":1,"1452":1,"1453":1,"1454":5,"1455":1,"1456":3,"1457":1,"1458":1,"1459":3,"1461":1,"1462":1,"1464":3,"1465":2,"1466":27,"1471":2,"1473":3,"1475":2,"1476":1,"1479":1,"1480":1,"1485":1,"1486":2,"1487":1,"1488":3,"1489":4,"1491":4,"1495":11,"1498":5,"1507":1,"1508":1,"1511":1,"1525":4,"1530":1,"1531":1,"1533":1,"1535":1,"1553":1,"1576":5,"1581":1,"1582":3,"1584":1,"1590":2,"1595":2,"1596":2,"1598":1,"1610":1,"1625":1,"1630":2,"1631":2,"1638":1,"1639":1,"1640":5,"1645":1,"1650":3,"1652":3,"1655":1,"1667":1,"1670":1,"1672":1,"1676":1,"1679":1,"1683":2,"1686":1,"1691":1,"1694":1,"1713":1,"1719":1,"1723":1,"1725":1,"1726":3,"1728":3,"1730":1,"1748":1,"1749":1,"1764":2,"1794":1,"1798":1,"1809":2,"1814":1,"1820":2,"1821":1,"1828":1,"1831":1,"1836":1,"1840":1,"1841":1,"1842":1,"1844":1,"1863":1,"1880":2,"1881":1,"1882":1,"1883":1,"1884":1,"1885":1,"1889":1,"1892":2,"1896":2,"1898":1,"1901":1,"1907":1,"1909":1,"1914":2,"1919":1,"1921":1,"1926":1,"1932":1,"1934":1,"1943":1,"1945":1,"1947":2,"1952":1,"1961":1,"1962":1,"1963":1,"1969":1,"1970":1,"1971":1,"1974":1,"1976":1,"1986":1,"1999":1,"2005":2,"2007":2,"2012":2,"2016":1,"2023":1,"2028":1,"2031":1,"2033":1,"2036":1,"2041":2,"2042":1,"2044":1,"2052":1,"2054":1,"2055":2,"2060":1,"2062":1,"2065":1,"2088":1,"2095":1,"2097":1,"2105":1,"2106":1,"2108":1,"2112":1,"2120":1,"2130":1,"2134":1,"2136":1,"2137":1,"2141":2,"2142":1,"2143":1,"2155":1,"2164":1,"2165":2,"2166":1,"2167":3,"2180":1,"2184":1,"2188":1,"2192":1,"2201":1,"2203":1,"2213":1,"2215":1,"2220":1,"2235":1}}],["allcarrytypeconstraints",{"3":{"1435":1}}],["allmetrics",{"3":{"1464":1,"1466":1}}],["allmmcacommonpackages",{"3":{"1435":2}}],["allmarkedread",{"3":{"1323":1}}],["allbyvalue",{"3":{"1414":1}}],["allfailed",{"3":{"1358":1}}],["allknownstatuses",{"3":{"1349":1}}],["allengines",{"3":{"1285":1}}],["allerrors",{"3":{"1229":1}}],["allspecification",{"3":{"1199":2,"1207":1}}],["allocate",{"3":{"1222":1,"1229":1,"1285":2,"1322":2,"1323":1,"1338":1,"1358":4,"2165":1}}],["allocates",{"3":{"1220":1,"1221":1,"1237":2,"1246":1,"1247":5,"1259":2,"1270":2,"1279":1,"1285":1,"1299":2,"1300":1,"1309":1,"1310":3,"1327":1,"1338":1,"1349":1,"1358":10,"1394":2,"1638":1}}],["allocated",{"3":{"438":1,"1221":1,"1229":3,"1237":1,"1247":3,"1270":5,"1271":1,"1285":1,"1299":1,"1300":1,"1310":2,"1322":2,"1338":5,"1358":3,"1394":2,"1395":1,"1435":3,"1437":1,"1440":1,"1638":2,"2125":1}}],["allocationceilingsbytes",{"2":{"591":1},"3":{"591":1}}],["allocation",{"3":{"0":1,"442":1,"471":1,"591":3,"592":1,"593":1,"595":1,"1212":1,"1213":1,"1220":1,"1229":5,"1237":3,"1244":1,"1247":9,"1259":3,"1263":1,"1270":4,"1285":5,"1299":13,"1300":2,"1308":1,"1310":5,"1311":1,"1322":6,"1323":2,"1327":1,"1338":6,"1349":1,"1358":19,"1379":1,"1394":1,"1395":2,"1401":2,"1414":2,"1415":1,"1416":3,"1422":1,"1426":1,"1435":4,"1437":1,"1440":1,"1466":1,"1491":1,"1576":4,"1638":1,"1667":1,"1799":1,"1804":1,"1820":1,"1922":1,"1954":2,"1957":1,"2046":1,"2195":1}}],["allocations",{"3":{"0":2,"589":1,"591":1,"592":1,"593":1,"860":1,"1237":1,"1491":1,"2046":1,"2047":1,"2048":1,"2231":1}}],["allocating",{"3":{"0":1,"1219":1,"1285":2,"1299":1,"1310":1,"1322":1,"1349":1,"1358":2,"1394":1}}],["allowinsecure",{"3":{"1466":3}}],["allowing",{"3":{"1285":1,"1308":1,"1322":1,"1335":1,"1338":2,"1358":1,"1368":1,"1395":1,"1414":1,"1416":1,"1454":1,"1466":1,"1775":1}}],["allowreadingfromstring",{"3":{"1358":1}}],["allowresponsebuffering",{"2":{"838":1},"3":{"838":1,"1338":1}}],["allowwriteablefields",{"3":{"1247":7}}],["allowmultiple",{"3":{"1237":3,"1299":2,"1309":3,"1322":3,"1323":2}}],["allowmissingownerattribute",{"2":{"1296":1},"3":{"318":1,"1199":3,"1203":1,"1207":2,"1296":4,"1299":9,"1495":2,"1993":2}}],["allowmissingowner",{"2":{"322":1,"326":1,"1995":1,"1996":1},"3":{"318":4,"322":1,"324":1,"326":1,"1299":1,"1666":1,"1993":1,"1994":1,"1995":1,"1996":1}}],["allowazureservices",{"3":{"1466":1,"1476":1,"1523":1,"1525":2,"1533":1,"1590":1}}],["allowall",{"3":{"774":2}}],["allowanyorigin",{"2":{"775":1},"3":{"774":1,"775":1,"1338":1}}],["allowanymethod",{"2":{"774":1},"3":{"774":2,"1338":1}}],["allowanyheader",{"2":{"774":1},"3":{"774":2,"1338":1}}],["allowanonymousattribute",{"3":{"1435":3}}],["allowanonymous",{"2":{"387":1,"388":1,"453":1,"454":1,"1062":1,"1375":1,"1531":1,"1588":1,"1631":1,"1646":1,"1649":1,"1688":1,"1697":1,"1702":1,"1720":1,"1757":1,"1760":1,"1897":1,"1922":1,"1962":1},"3":{"0":2,"189":3,"243":1,"387":1,"388":1,"448":2,"453":1,"454":1,"790":1,"854":1,"1062":1,"1116":1,"1212":1,"1293":1,"1299":7,"1306":1,"1310":13,"1322":1,"1323":3,"1338":2,"1349":1,"1358":15,"1370":1,"1372":3,"1374":1,"1375":1,"1378":1,"1379":24,"1395":3,"1407":1,"1414":5,"1415":1,"1435":26,"1437":1,"1488":1,"1525":1,"1531":1,"1533":1,"1588":1,"1590":1,"1595":1,"1626":1,"1630":2,"1631":3,"1640":4,"1646":1,"1649":1,"1650":1,"1688":1,"1697":3,"1702":1,"1720":1,"1746":3,"1747":9,"1748":1,"1757":1,"1760":1,"1897":3,"1921":3,"1922":1,"1962":1,"2130":1}}],["allowances",{"3":{"1435":1}}],["allowance",{"3":{"0":2,"135":2,"136":1,"139":3,"216":1,"219":1,"1067":1,"1126":1,"1263":1,"1297":1,"1310":5,"1323":3,"1336":1,"1338":1,"1415":1,"1429":1,"1435":15,"1437":1,"1448":1,"1464":2,"1487":1,"1491":1,"1674":1,"2074":1,"2149":1,"2151":1}}],["allowcredentials",{"2":{"774":1,"775":1},"3":{"774":2,"775":1,"1310":1,"1338":2}}],["allowcachelookup",{"2":{"740":1},"3":{"740":1,"1310":1}}],["allowcachestorage",{"2":{"740":1},"3":{"740":1,"1310":1,"1379":2}}],["allowblobpublicaccess",{"3":{"665":1}}],["allowdistributed",{"2":{"178":1},"3":{"178":1,"1310":6,"1999":1}}],["allowtools",{"2":{"1418":1,"2172":1},"3":{"0":1,"1089":1,"1091":2,"1093":1,"1418":1,"1420":1,"1421":2,"1426":6,"1525":1,"1576":1,"2172":1,"2173":1,"2178":1}}],["allowsharedkeyaccess",{"3":{"1466":1}}],["allowspecificorigins",{"2":{"774":1},"3":{"774":1}}],["allowsclientkey",{"2":{"333":1},"3":{"333":1,"1241":1,"1247":3}}],["allows",{"3":{"0":1,"11":1,"110":1,"126":1,"360":1,"412":1,"530":1,"626":1,"742":1,"774":2,"775":1,"776":1,"853":1,"926":1,"1026":1,"1029":1,"1091":1,"1150":1,"1196":1,"1239":1,"1263":1,"1270":1,"1271":1,"1285":3,"1299":2,"1310":3,"1323":4,"1338":5,"1347":1,"1349":1,"1358":3,"1368":2,"1379":3,"1408":1,"1414":1,"1422":1,"1423":2,"1426":2,"1435":4,"1446":1,"1456":1,"1457":1,"1466":2,"1516":1,"1590":1,"1629":1,"1639":1,"1677":1,"1686":1,"1748":1,"1764":1,"2141":1}}],["allowlisting",{"3":{"1435":4}}],["allowlistedcollaborator",{"3":{"1435":3}}],["allowlistedcommand",{"3":{"1435":2}}],["allowlistedhandler",{"3":{"1435":2}}],["allowlistedtype",{"3":{"1435":7}}],["allowlistednamespace",{"3":{"1435":5}}],["allowlisted",{"3":{"547":1,"1310":1,"1415":1,"1416":1,"1435":13,"1815":1}}],["allowlists",{"3":{"27":2,"1161":1,"1310":4,"1435":9}}],["allowlist",{"3":{"0":4,"17":1,"27":1,"39":2,"41":1,"109":1,"265":4,"350":2,"418":2,"420":4,"421":1,"426":1,"552":1,"1061":1,"1063":1,"1161":4,"1162":2,"1164":1,"1310":8,"1323":4,"1404":1,"1414":4,"1415":3,"1416":2,"1435":97,"1437":1,"1525":1,"1533":1,"1576":1,"1590":1,"1598":1,"1599":1,"1652":1,"1975":1,"2082":2,"2084":1,"2085":1,"2201":2}}],["allow",{"1":{"771":1,"772":1,"773":1,"774":1,"775":1,"776":1,"777":1,"778":1},"3":{"0":3,"128":1,"175":1,"177":1,"189":3,"219":3,"265":1,"350":1,"355":1,"418":1,"423":1,"427":1,"428":2,"626":1,"749":1,"766":1,"771":1,"773":1,"774":4,"775":2,"776":4,"778":1,"1107":1,"1108":1,"1111":1,"1116":2,"1124":1,"1127":1,"1175":2,"1179":1,"1212":2,"1241":2,"1243":1,"1247":5,"1270":1,"1271":1,"1284":1,"1299":1,"1308":1,"1310":1,"1322":1,"1323":2,"1335":3,"1337":2,"1338":8,"1341":1,"1343":2,"1346":1,"1349":4,"1350":1,"1352":5,"1355":1,"1358":22,"1365":1,"1368":3,"1379":2,"1390":1,"1393":1,"1394":6,"1395":4,"1401":2,"1415":2,"1416":2,"1422":1,"1426":9,"1435":32,"1437":1,"1442":2,"1454":1,"1466":3,"1487":1,"1495":1,"1525":2,"1533":1,"1576":1,"1590":1,"1599":1,"1630":2,"1634":1,"1636":1,"1639":1,"1652":1,"1665":1,"1670":4,"1672":1,"1726":1,"1814":1,"1816":1,"1897":5,"1988":1,"2147":1,"2150":1,"2174":1,"2202":1,"2231":2}}],["allowedpurgetypes",{"3":{"1435":2}}],["allowedpublicimplementations",{"3":{"1435":2}}],["allowedhandlers",{"3":{"1435":4}}],["allowedharddeletetypes",{"2":{"1061":1},"3":{"39":1,"1061":1,"1435":3}}],["allowedthrowingtypes",{"3":{"1435":2}}],["allowedsharedcodes",{"3":{"1435":3}}],["allowedsavingtypes",{"3":{"1435":2}}],["allowedstaticmembers",{"2":{"884":1},"3":{"881":1,"884":1,"1435":4}}],["allowedunvalidatedcommands",{"3":{"1435":3}}],["allowedanonymousendpoints",{"3":{"1435":10}}],["allowedauthenticators",{"3":{"1416":1}}],["allowedfileextensions",{"3":{"1349":4,"1358":2}}],["allowedfiles",{"2":{"546":1,"1134":1,"1135":1,"1136":1},"3":{"0":3,"545":2,"546":1,"1133":1,"1134":1,"1135":2,"1136":1,"1435":11,"1488":1,"1814":2}}],["allowedcodeprefixes",{"3":{"1435":2}}],["allowedcascadeexempttypes",{"3":{"1435":2}}],["allowedclockreaders",{"2":{"1161":1},"3":{"1161":2,"1430":1,"1435":1}}],["allowedcyclenamespaces",{"3":{"0":1,"135":2,"1435":6}}],["allowedorigins",{"2":{"774":1},"3":{"0":2,"774":5,"775":1,"776":2,"778":1,"1310":1,"1335":1,"1338":3,"1442":1}}],["allowedreturnurlschemes",{"3":{"0":1,"350":1,"420":1,"428":1,"429":1,"1310":1,"1415":1,"1416":1,"1975":1}}],["allowed",{"0":{"1421":1,"2070":1},"3":{"0":2,"81":1,"135":1,"333":1,"384":1,"390":1,"540":1,"766":1,"773":1,"774":1,"791":1,"896":1,"897":1,"914":1,"926":1,"1067":1,"1091":1,"1168":1,"1178":1,"1184":2,"1191":1,"1200":1,"1229":1,"1237":2,"1239":1,"1246":1,"1257":1,"1259":1,"1270":2,"1284":1,"1285":10,"1296":1,"1299":6,"1310":4,"1322":3,"1323":4,"1338":5,"1349":7,"1358":10,"1379":3,"1394":3,"1395":8,"1397":2,"1398":2,"1401":6,"1414":2,"1416":3,"1417":1,"1421":1,"1426":2,"1430":1,"1435":15,"1437":1,"1466":1,"1488":1,"1497":1,"1560":1,"1639":1,"1720":1,"1748":1,"1754":1,"1764":1,"1766":3,"1812":1,"1814":2,"1816":1,"1820":1,"1850":1,"1891":1,"1898":1,"1931":1,"1933":1,"1959":1,"1991":2,"2042":2,"2070":1,"2136":1,"2146":1,"2149":1}}],["all",{"0":{"1338":1,"1394":1,"1789":1,"1937":1,"2117":1,"2177":1},"2":{"184":1,"659":1,"1372":1,"2082":1},"3":{"0":68,"12":2,"23":2,"24":2,"26":1,"31":2,"46":1,"51":1,"54":1,"58":1,"59":1,"62":2,"73":1,"76":1,"78":2,"93":3,"95":1,"99":2,"107":1,"109":6,"111":3,"115":1,"118":1,"122":3,"125":1,"126":1,"133":1,"135":2,"138":1,"140":1,"142":1,"146":1,"147":3,"148":2,"149":1,"150":2,"152":2,"156":1,"157":1,"164":1,"165":1,"166":2,"168":1,"170":4,"174":1,"175":2,"176":1,"177":1,"179":3,"180":5,"184":2,"186":9,"189":1,"195":3,"201":2,"203":1,"207":1,"214":1,"215":1,"217":1,"219":2,"224":1,"225":1,"234":1,"237":1,"241":3,"242":1,"243":1,"245":3,"251":1,"253":1,"254":3,"255":1,"256":4,"257":2,"258":1,"259":4,"260":1,"261":1,"263":1,"265":6,"267":1,"279":1,"280":1,"283":2,"284":1,"291":1,"314":1,"319":1,"328":1,"330":1,"332":1,"335":1,"341":2,"343":2,"346":1,"350":1,"359":2,"373":1,"390":1,"396":1,"397":4,"398":1,"399":1,"400":1,"401":2,"402":1,"409":1,"412":1,"413":1,"414":1,"415":1,"419":1,"420":1,"425":1,"426":1,"427":1,"440":1,"454":1,"459":1,"464":2,"465":1,"471":1,"472":1,"477":1,"486":2,"487":1,"488":4,"489":4,"490":4,"491":3,"492":4,"493":1,"494":2,"499":1,"505":1,"506":2,"507":1,"508":1,"509":1,"511":1,"512":1,"513":1,"514":1,"522":1,"523":1,"531":1,"536":1,"537":1,"538":2,"539":1,"540":1,"544":1,"545":3,"546":1,"547":1,"549":5,"551":1,"555":2,"556":1,"566":1,"572":8,"574":1,"576":1,"577":1,"578":3,"582":1,"583":1,"585":2,"590":1,"591":2,"592":1,"593":1,"599":4,"600":2,"602":2,"603":2,"607":1,"609":6,"611":5,"612":1,"616":1,"618":3,"619":1,"620":1,"624":1,"626":5,"627":1,"628":1,"633":2,"635":4,"638":1,"640":2,"643":1,"644":1,"645":1,"649":1,"655":1,"657":3,"659":1,"660":1,"665":5,"666":1,"672":3,"674":6,"682":2,"683":2,"690":5,"692":1,"693":1,"696":1,"697":1,"698":2,"699":3,"700":1,"702":1,"707":1,"709":1,"714":1,"715":1,"716":1,"717":2,"720":1,"725":1,"733":1,"739":1,"740":2,"743":4,"747":1,"748":1,"749":2,"751":3,"759":1,"764":1,"766":3,"767":1,"768":1,"773":1,"774":1,"776":1,"781":1,"782":2,"784":1,"790":3,"793":1,"794":1,"798":2,"799":4,"800":2,"803":1,"809":2,"810":3,"814":1,"820":1,"821":1,"825":1,"826":2,"827":6,"829":2,"832":2,"837":2,"838":3,"840":3,"842":1,"853":3,"856":1,"859":1,"860":2,"861":3,"863":2,"867":1,"868":1,"869":1,"870":3,"872":1,"875":1,"877":1,"881":5,"889":1,"891":1,"892":1,"896":1,"905":6,"906":3,"914":1,"915":1,"920":1,"923":2,"924":1,"926":1,"930":1,"932":1,"933":1,"935":1,"940":1,"941":1,"946":1,"954":1,"955":1,"956":1,"958":1,"959":2,"963":1,"964":4,"972":1,"979":2,"980":2,"986":1,"987":2,"988":1,"995":1,"996":1,"998":2,"1004":3,"1005":1,"1006":2,"1010":1,"1012":1,"1013":1,"1014":1,"1016":1,"1018":3,"1021":1,"1026":2,"1033":1,"1036":2,"1041":1,"1043":1,"1044":1,"1050":2,"1051":1,"1052":1,"1054":2,"1055":2,"1059":1,"1060":1,"1070":1,"1074":3,"1075":2,"1078":1,"1081":2,"1083":1,"1084":1,"1085":1,"1091":1,"1092":2,"1107":1,"1109":1,"1115":2,"1116":1,"1124":1,"1132":1,"1133":1,"1134":1,"1135":1,"1136":1,"1137":1,"1150":1,"1152":1,"1168":2,"1169":1,"1171":1,"1175":2,"1184":1,"1191":1,"1192":1,"1196":1,"1200":1,"1201":1,"1202":1,"1203":1,"1211":1,"1212":9,"1213":1,"1214":4,"1220":3,"1222":1,"1224":2,"1225":1,"1227":1,"1229":11,"1230":1,"1231":1,"1232":4,"1234":3,"1235":1,"1237":21,"1239":1,"1241":2,"1242":1,"1243":3,"1246":1,"1247":35,"1248":1,"1249":1,"1252":5,"1257":3,"1258":2,"1259":40,"1263":4,"1264":1,"1265":3,"1267":1,"1268":1,"1270":44,"1271":16,"1273":1,"1274":1,"1275":3,"1277":2,"1279":1,"1280":2,"1281":2,"1282":1,"1284":4,"1285":131,"1286":2,"1289":1,"1291":1,"1293":2,"1296":2,"1299":65,"1300":19,"1301":1,"1305":1,"1306":1,"1308":27,"1309":12,"1310":89,"1311":15,"1313":1,"1314":1,"1317":2,"1319":3,"1322":58,"1323":122,"1324":3,"1325":4,"1329":1,"1331":2,"1332":6,"1333":1,"1334":2,"1337":4,"1338":86,"1340":1,"1341":3,"1342":2,"1343":1,"1346":2,"1347":3,"1348":4,"1349":91,"1351":4,"1352":1,"1353":1,"1356":1,"1357":1,"1358":218,"1359":1,"1360":1,"1361":4,"1363":1,"1364":1,"1365":1,"1367":1,"1368":34,"1370":1,"1372":2,"1373":1,"1374":1,"1375":1,"1376":2,"1377":1,"1378":3,"1379":52,"1381":1,"1382":3,"1383":2,"1384":3,"1387":1,"1388":1,"1389":2,"1390":3,"1391":1,"1393":1,"1394":108,"1395":145,"1398":3,"1401":40,"1405":2,"1407":1,"1408":3,"1410":1,"1413":3,"1414":72,"1415":18,"1416":57,"1417":1,"1418":1,"1420":1,"1421":2,"1422":1,"1424":1,"1426":7,"1427":1,"1428":2,"1429":1,"1430":2,"1431":2,"1432":1,"1435":238,"1437":17,"1439":1,"1440":12,"1441":4,"1442":8,"1443":3,"1444":7,"1445":3,"1446":9,"1447":1,"1448":1,"1449":2,"1452":5,"1453":6,"1454":17,"1455":3,"1459":3,"1460":2,"1462":1,"1464":4,"1465":1,"1466":52,"1472":2,"1473":6,"1474":2,"1475":5,"1476":6,"1477":1,"1479":2,"1485":6,"1486":3,"1487":15,"1488":7,"1489":10,"1490":4,"1491":11,"1494":2,"1495":44,"1497":2,"1498":1,"1499":1,"1502":1,"1503":1,"1508":1,"1513":1,"1518":1,"1524":3,"1525":21,"1527":1,"1530":2,"1533":4,"1534":6,"1536":2,"1540":1,"1560":1,"1562":1,"1568":3,"1575":2,"1576":12,"1577":1,"1578":1,"1581":1,"1584":2,"1585":1,"1586":1,"1588":2,"1590":21,"1592":1,"1593":1,"1595":6,"1596":1,"1598":1,"1599":3,"1610":1,"1621":1,"1622":1,"1623":1,"1624":1,"1625":1,"1626":4,"1628":2,"1629":11,"1630":16,"1631":16,"1632":4,"1633":2,"1634":8,"1635":1,"1636":3,"1637":7,"1638":10,"1639":11,"1640":12,"1643":1,"1645":2,"1650":2,"1651":2,"1652":2,"1653":3,"1656":1,"1660":2,"1662":1,"1664":1,"1665":2,"1666":1,"1669":1,"1670":2,"1672":2,"1674":1,"1680":1,"1681":1,"1682":1,"1683":1,"1684":4,"1685":7,"1686":1,"1690":1,"1692":1,"1693":1,"1695":1,"1700":1,"1703":1,"1704":1,"1706":1,"1707":1,"1715":1,"1717":1,"1718":2,"1719":3,"1720":1,"1724":1,"1725":2,"1726":6,"1727":3,"1728":2,"1735":1,"1737":1,"1745":1,"1746":1,"1747":3,"1748":7,"1749":3,"1752":6,"1753":1,"1754":1,"1758":2,"1759":2,"1763":2,"1764":10,"1766":4,"1767":6,"1768":4,"1770":1,"1772":1,"1773":3,"1774":1,"1775":2,"1776":3,"1778":5,"1779":1,"1780":1,"1781":1,"1783":5,"1787":1,"1788":1,"1793":1,"1795":1,"1798":2,"1799":1,"1800":1,"1801":1,"1804":4,"1805":1,"1806":1,"1807":2,"1810":1,"1814":4,"1820":2,"1828":1,"1832":1,"1839":1,"1840":1,"1841":1,"1848":2,"1850":3,"1851":1,"1853":1,"1855":2,"1856":1,"1858":3,"1859":3,"1860":1,"1863":1,"1864":2,"1868":1,"1873":1,"1874":1,"1875":3,"1880":2,"1882":1,"1883":3,"1887":1,"1893":1,"1894":1,"1899":1,"1902":1,"1907":1,"1908":2,"1915":1,"1919":1,"1921":1,"1922":2,"1926":2,"1930":1,"1931":3,"1932":1,"1934":2,"1937":1,"1939":1,"1940":1,"1941":1,"1943":1,"1944":1,"1945":3,"1946":2,"1947":1,"1948":1,"1951":1,"1952":1,"1953":1,"1955":1,"1959":2,"1961":1,"1962":2,"1969":2,"1970":1,"1974":2,"1976":2,"1977":1,"1978":1,"1981":2,"1984":2,"1986":1,"1987":2,"1988":1,"1989":1,"1991":1,"1994":2,"1996":1,"1997":2,"1998":3,"1999":2,"2000":2,"2001":1,"2002":1,"2006":1,"2009":1,"2017":1,"2023":2,"2028":1,"2029":1,"2031":1,"2032":1,"2033":2,"2035":2,"2036":1,"2037":1,"2041":2,"2042":3,"2045":1,"2046":1,"2052":1,"2053":1,"2054":2,"2056":2,"2062":3,"2066":1,"2068":1,"2069":1,"2070":1,"2071":1,"2072":2,"2076":1,"2078":1,"2082":3,"2085":1,"2086":1,"2088":2,"2089":1,"2093":1,"2096":3,"2097":1,"2107":1,"2110":1,"2111":1,"2116":3,"2117":3,"2119":1,"2120":3,"2123":1,"2126":1,"2127":1,"2130":1,"2137":1,"2141":1,"2142":1,"2146":1,"2147":2,"2148":1,"2149":2,"2153":2,"2155":7,"2157":4,"2158":1,"2159":1,"2160":1,"2166":4,"2167":1,"2168":1,"2169":1,"2171":1,"2172":1,"2173":1,"2175":1,"2176":1,"2177":1,"2179":2,"2184":1,"2188":1,"2191":1,"2193":1,"2195":2,"2196":1,"2203":1,"2209":1,"2214":1,"2219":2,"2227":1,"2229":1,"2231":2,"2233":2,"2241":1}}],["alreadyhaveaccountlink",{"3":{"1435":1}}],["alreadyanswered",{"3":{"1401":1}}],["alreadyvisited",{"3":{"1395":7}}],["alreadyreviewed",{"3":{"1764":1,"1766":1}}],["alreadyrevoked",{"3":{"1299":1}}],["alreadyrecorded",{"3":{"1395":9}}],["alreadyunpublished",{"3":{"1349":1,"1358":2}}],["alreadylinked",{"3":{"1349":1,"1358":1}}],["alreadypublished",{"3":{"1342":1,"1343":1,"1349":1,"1358":2,"1379":1}}],["alreadyprocessedasync",{"2":{"195":1,"201":1,"1258":1},"3":{"195":2,"201":1,"1258":1,"1259":6}}],["alreadyclosed",{"3":{"1299":1,"1435":6}}],["alreadyclaimed",{"3":{"1299":1}}],["alreadycheckedin",{"3":{"690":1,"1395":17}}],["alreadysoftdeletefilteredentity",{"3":{"1199":3,"1207":1}}],["alreadydeleted",{"2":{"1219":1},"3":{"39":1,"1219":1,"1229":1,"1237":1,"1322":1,"1358":1,"1809":1}}],["already",{"3":{"0":30,"18":1,"24":4,"26":3,"27":2,"39":3,"46":1,"53":1,"54":1,"58":1,"60":3,"67":1,"69":1,"71":4,"72":3,"73":1,"81":2,"95":2,"99":1,"100":2,"102":1,"109":2,"110":1,"113":1,"115":1,"120":2,"122":3,"123":2,"127":1,"130":1,"131":1,"135":4,"136":1,"140":1,"142":1,"147":1,"148":1,"150":2,"158":1,"160":1,"164":1,"165":1,"166":1,"167":1,"175":1,"176":1,"178":3,"179":2,"180":1,"184":1,"186":1,"189":2,"194":2,"195":1,"198":1,"200":4,"201":2,"202":2,"207":1,"214":1,"225":3,"228":1,"230":1,"237":1,"246":1,"255":2,"256":1,"259":1,"260":1,"264":1,"265":3,"266":2,"273":1,"274":2,"292":1,"295":1,"299":1,"309":1,"310":1,"314":1,"317":1,"318":2,"340":2,"341":1,"342":1,"349":1,"350":3,"351":1,"352":1,"353":2,"358":2,"362":1,"369":1,"370":1,"382":1,"383":2,"390":1,"391":1,"392":1,"398":1,"400":2,"401":2,"402":1,"404":2,"405":3,"407":1,"408":1,"412":1,"413":1,"415":1,"420":1,"423":1,"424":1,"448":1,"458":2,"459":5,"461":2,"465":2,"471":1,"483":1,"499":1,"500":1,"508":1,"517":2,"519":1,"527":2,"528":1,"534":2,"535":1,"536":6,"537":4,"538":1,"539":2,"544":2,"549":2,"555":1,"556":1,"558":1,"563":1,"564":1,"565":1,"566":1,"573":1,"578":1,"583":1,"599":3,"600":1,"601":1,"603":1,"609":3,"618":1,"627":1,"633":2,"640":1,"641":1,"647":1,"648":1,"650":2,"657":1,"658":1,"665":3,"666":1,"673":1,"675":1,"681":1,"682":1,"683":1,"689":1,"691":2,"692":1,"697":1,"698":4,"702":3,"706":1,"707":4,"708":2,"710":1,"714":1,"715":1,"716":1,"718":1,"724":1,"725":3,"726":1,"733":2,"734":1,"735":1,"740":1,"741":4,"742":1,"743":1,"748":1,"750":1,"756":1,"757":1,"765":2,"767":1,"773":1,"789":1,"790":4,"792":1,"793":1,"794":1,"800":1,"801":1,"803":1,"809":1,"810":4,"811":2,"813":1,"818":1,"819":3,"820":2,"826":1,"827":7,"828":1,"829":1,"832":2,"833":1,"837":1,"838":1,"839":2,"846":1,"847":2,"848":1,"849":1,"853":2,"854":1,"856":1,"860":2,"862":1,"864":1,"865":1,"870":1,"872":1,"877":1,"882":2,"883":1,"884":1,"889":2,"890":2,"891":1,"896":2,"897":2,"905":8,"906":2,"912":1,"914":4,"921":2,"922":3,"926":3,"930":1,"931":1,"932":2,"933":1,"934":1,"940":1,"945":1,"947":1,"964":1,"965":1,"971":2,"973":1,"980":1,"981":1,"982":2,"988":5,"989":3,"995":1,"996":5,"997":2,"1003":1,"1004":1,"1011":2,"1013":1,"1017":1,"1018":2,"1019":1,"1025":1,"1033":1,"1034":1,"1035":1,"1040":1,"1041":1,"1042":2,"1043":3,"1044":2,"1049":1,"1055":1,"1058":3,"1059":5,"1060":2,"1061":1,"1066":2,"1067":2,"1068":2,"1073":1,"1074":1,"1075":2,"1077":1,"1082":2,"1083":1,"1084":1,"1085":1,"1090":1,"1093":1,"1115":2,"1116":4,"1117":2,"1123":1,"1125":3,"1135":1,"1140":1,"1142":2,"1143":1,"1145":2,"1150":1,"1152":1,"1154":1,"1155":1,"1157":1,"1160":1,"1162":1,"1178":1,"1183":1,"1185":1,"1187":1,"1190":1,"1196":1,"1212":1,"1218":1,"1220":2,"1227":1,"1228":1,"1229":11,"1231":2,"1233":1,"1237":10,"1239":2,"1241":1,"1242":2,"1247":22,"1249":1,"1250":1,"1253":1,"1255":1,"1256":2,"1257":1,"1258":2,"1259":34,"1262":2,"1263":3,"1265":3,"1267":2,"1270":40,"1271":4,"1274":3,"1275":3,"1276":1,"1278":2,"1280":7,"1285":105,"1287":1,"1289":2,"1291":1,"1299":47,"1300":8,"1303":1,"1306":1,"1308":19,"1309":4,"1310":55,"1311":5,"1314":2,"1315":1,"1317":1,"1319":1,"1321":2,"1322":60,"1323":85,"1327":1,"1329":2,"1334":1,"1336":2,"1337":1,"1338":41,"1342":2,"1344":1,"1345":1,"1346":1,"1349":23,"1352":2,"1353":1,"1354":3,"1356":1,"1357":2,"1358":121,"1361":1,"1362":1,"1364":1,"1365":1,"1368":13,"1370":2,"1375":2,"1377":2,"1379":21,"1384":1,"1388":1,"1389":1,"1394":48,"1395":86,"1398":1,"1399":2,"1400":1,"1401":33,"1405":2,"1406":3,"1407":5,"1408":2,"1409":1,"1410":1,"1412":2,"1413":1,"1414":64,"1415":9,"1416":37,"1419":1,"1420":1,"1421":1,"1422":1,"1424":1,"1425":1,"1426":13,"1428":2,"1435":60,"1437":6,"1440":2,"1441":3,"1442":2,"1444":2,"1445":1,"1446":3,"1447":1,"1452":2,"1453":3,"1454":10,"1455":2,"1458":4,"1464":2,"1466":17,"1471":2,"1472":1,"1473":1,"1474":1,"1476":1,"1478":1,"1485":1,"1487":2,"1491":2,"1495":14,"1506":1,"1530":1,"1576":2,"1595":2,"1626":1,"1630":5,"1631":8,"1637":1,"1639":1,"1640":8,"1645":1,"1647":1,"1648":2,"1650":1,"1652":1,"1655":1,"1660":1,"1663":1,"1671":1,"1683":2,"1684":5,"1685":12,"1686":1,"1688":1,"1691":1,"1696":1,"1699":1,"1700":5,"1701":2,"1702":1,"1707":1,"1718":1,"1719":1,"1722":1,"1723":2,"1727":1,"1728":2,"1738":1,"1746":2,"1747":3,"1748":4,"1754":1,"1763":2,"1764":7,"1765":1,"1766":2,"1767":1,"1769":1,"1775":1,"1780":1,"1787":1,"1789":1,"1799":1,"1803":1,"1804":2,"1808":1,"1820":2,"1822":1,"1823":1,"1831":1,"1839":3,"1842":1,"1843":1,"1846":1,"1848":1,"1850":3,"1869":1,"1872":2,"1873":1,"1878":1,"1880":1,"1882":1,"1886":2,"1889":1,"1890":1,"1891":1,"1896":1,"1907":2,"1908":1,"1909":1,"1915":1,"1917":1,"1919":1,"1921":1,"1922":3,"1923":1,"1932":2,"1940":1,"1943":1,"1945":1,"1947":3,"1949":2,"1952":1,"1953":1,"1961":1,"1966":1,"1967":2,"1969":1,"1975":1,"1976":5,"1977":2,"1978":5,"1981":2,"1988":1,"1993":1,"1994":1,"1996":2,"1999":3,"2000":1,"2011":1,"2015":1,"2016":1,"2018":1,"2023":1,"2027":1,"2029":1,"2031":3,"2045":1,"2062":1,"2063":1,"2065":3,"2066":1,"2068":1,"2075":1,"2080":1,"2082":3,"2086":1,"2091":1,"2094":2,"2095":1,"2096":1,"2097":1,"2100":1,"2116":1,"2117":2,"2130":1,"2131":1,"2133":1,"2134":1,"2135":1,"2138":1,"2139":1,"2140":1,"2142":1,"2149":1,"2153":1,"2155":2,"2159":1,"2160":1,"2162":3,"2163":1,"2164":4,"2166":2,"2167":5,"2168":3,"2176":1,"2179":1,"2181":1,"2183":1,"2184":1,"2186":2,"2189":1,"2190":1,"2191":1,"2192":2,"2193":3,"2197":1,"2198":1,"2214":1,"2231":1,"2233":1}}],["alwaysonline",{"3":{"1416":1}}],["alwaysonlineconnectivitystatusservice",{"2":{"2117":1},"3":{"1199":5,"1203":1,"1207":2,"1416":14,"2117":2}}],["alwaysbypassedprefixes",{"3":{"1338":1}}],["alwaysfailsvalidator",{"3":{"1199":2,"1207":1}}],["alwaysretryexecutionstrategy",{"3":{"1199":2,"1207":1}}],["always",{"0":{"1363":1},"1":{"928":1,"929":1,"930":1,"931":1,"932":1,"933":1,"934":1,"935":1},"2":{"1482":1},"3":{"0":9,"24":1,"53":1,"58":1,"78":1,"123":1,"140":1,"160":1,"175":2,"202":1,"221":1,"230":2,"231":1,"243":2,"245":1,"255":1,"256":1,"258":1,"267":1,"272":1,"280":1,"310":2,"334":1,"339":1,"344":1,"349":1,"353":1,"359":1,"363":1,"383":1,"388":1,"401":1,"404":1,"407":1,"413":1,"420":1,"440":1,"501":1,"511":1,"513":1,"518":1,"536":1,"549":1,"564":1,"565":1,"583":2,"585":1,"593":1,"599":1,"609":1,"620":1,"626":2,"657":1,"684":1,"690":2,"698":2,"749":2,"766":1,"784":1,"818":1,"819":1,"827":1,"848":1,"856":1,"863":1,"881":1,"905":1,"924":1,"926":2,"928":1,"946":1,"1033":1,"1034":2,"1042":1,"1046":1,"1061":1,"1067":1,"1099":1,"1100":1,"1108":1,"1117":1,"1123":1,"1175":1,"1176":1,"1211":1,"1212":3,"1214":1,"1215":1,"1221":1,"1227":1,"1229":3,"1237":3,"1240":1,"1247":3,"1253":1,"1255":1,"1259":9,"1261":1,"1263":1,"1269":1,"1270":8,"1271":3,"1276":1,"1278":1,"1285":33,"1290":1,"1299":17,"1300":9,"1301":1,"1308":9,"1309":4,"1310":35,"1311":2,"1314":1,"1319":2,"1322":21,"1323":20,"1331":1,"1332":1,"1336":1,"1338":19,"1347":1,"1348":1,"1349":16,"1358":28,"1363":1,"1365":1,"1368":3,"1379":3,"1382":1,"1383":1,"1385":1,"1386":1,"1387":1,"1390":2,"1394":27,"1395":23,"1401":11,"1405":1,"1407":1,"1414":15,"1415":2,"1416":20,"1422":1,"1426":4,"1429":1,"1430":2,"1432":1,"1434":1,"1435":29,"1437":4,"1439":1,"1440":1,"1443":1,"1446":2,"1453":1,"1454":4,"1455":3,"1459":2,"1465":1,"1466":2,"1469":1,"1474":3,"1477":1,"1482":1,"1485":3,"1487":3,"1488":1,"1489":1,"1491":1,"1502":1,"1525":3,"1530":1,"1533":1,"1576":1,"1606":2,"1626":1,"1629":1,"1630":3,"1638":1,"1639":1,"1640":2,"1648":1,"1651":1,"1652":2,"1669":1,"1683":1,"1685":1,"1696":1,"1699":1,"1700":1,"1729":1,"1736":1,"1740":2,"1760":1,"1767":1,"1768":1,"1780":1,"1804":1,"1814":1,"1819":1,"1824":1,"1842":2,"1848":1,"1864":1,"1866":1,"1870":1,"1875":2,"1876":2,"1877":1,"1880":1,"1888":1,"1899":1,"1901":2,"1921":2,"1926":1,"1932":1,"1937":1,"1939":1,"1943":1,"1946":1,"1948":1,"1962":1,"1973":1,"1975":1,"1983":1,"1990":1,"1992":1,"1999":1,"2002":1,"2005":1,"2009":1,"2018":1,"2023":1,"2043":1,"2045":1,"2048":1,"2056":1,"2063":1,"2090":1,"2093":1,"2117":2,"2118":1,"2122":2,"2125":1,"2128":1,"2133":1,"2141":1,"2155":1,"2182":1,"2183":1,"2201":1,"2231":2}}],["asleep",{"3":{"1478":1}}],["aswellformedopenapidescribingtheapisurface",{"3":{"1435":1}}],["asreadonly",{"2":{"1342":1},"3":{"1342":1,"1349":3,"1368":1,"1395":1,"1401":1}}],["asreadonlylist",{"3":{"1323":1}}],["astronomically",{"3":{"1901":1}}],["astracking",{"3":{"0":1,"549":1,"551":1,"926":1,"1240":1,"1244":1,"1247":3,"1270":6,"1285":17,"1309":3,"1310":1,"1349":1,"1358":48,"1368":3,"1395":9,"1401":4,"1414":5,"1576":1,"1590":1}}],["asterisk",{"3":{"1323":2,"1394":1}}],["asnotracking",{"3":{"1259":1,"1285":6,"1305":1,"1308":1,"1525":1,"1576":2,"1590":1}}],["ascends",{"3":{"1285":1}}],["ascending",{"3":{"330":1,"549":3,"1175":1,"1191":1,"1200":1,"1211":1,"1242":1,"1247":2,"1285":1,"1310":1,"1323":1,"1338":1,"1343":1,"1349":3,"1358":4,"1391":1,"1394":14,"1395":1,"1401":1,"1497":2,"1531":1,"1630":4,"1638":2,"1639":15,"1987":1}}],["ascii",{"3":{"1285":7,"1299":1,"1304":1,"1308":1,"1323":3,"1346":1,"1349":1,"1395":3,"1414":1,"1416":1,"1820":1}}],["asc",{"3":{"1242":1,"1247":4,"1394":5,"1395":1,"1596":1,"1597":1,"1639":1}}],["asimplementedinterfaces",{"3":{"1394":4,"1414":1}}],["asia",{"3":{"1349":1}}],["aside",{"3":{"556":1,"1310":1,"1654":1}}],["asichatclient",{"2":{"1419":1,"2171":1},"3":{"0":1,"1091":2,"1419":2,"1426":5,"2171":1}}],["asker",{"3":{"1395":3,"1401":1}}],["asked",{"3":{"0":2,"24":1,"73":1,"108":1,"350":1,"365":1,"402":1,"529":1,"537":1,"549":1,"691":1,"714":1,"727":1,"733":1,"740":1,"924":1,"939":1,"1020":1,"1026":1,"1027":1,"1028":1,"1043":1,"1058":1,"1066":1,"1091":1,"1092":1,"1093":1,"1116":1,"1142":1,"1229":1,"1238":1,"1241":1,"1243":1,"1244":1,"1245":1,"1246":1,"1247":5,"1254":1,"1259":5,"1270":3,"1271":1,"1272":1,"1275":1,"1285":8,"1294":1,"1299":1,"1305":1,"1308":1,"1309":2,"1310":2,"1322":1,"1323":10,"1327":1,"1338":1,"1349":4,"1358":13,"1368":1,"1388":1,"1394":3,"1395":6,"1404":1,"1414":6,"1415":1,"1416":4,"1423":1,"1426":2,"1435":3,"1437":1,"1442":1,"1449":1,"1458":1,"1466":1,"1629":1,"1638":2,"1681":1,"1726":2,"1803":1,"1813":1,"1814":1,"1862":1,"1946":1,"1961":1,"1974":1,"1976":1,"2176":1,"2193":1}}],["ask",{"3":{"527":1,"536":1,"549":1,"649":1,"675":1,"684":1,"716":1,"725":2,"741":1,"798":1,"1019":1,"1237":2,"1244":1,"1247":3,"1259":1,"1270":2,"1273":1,"1277":1,"1278":1,"1282":1,"1285":9,"1299":7,"1300":2,"1308":3,"1310":1,"1322":2,"1323":7,"1327":1,"1338":4,"1358":5,"1368":1,"1372":1,"1379":2,"1394":4,"1395":9,"1402":1,"1414":4,"1415":1,"1416":4,"1426":1,"1435":7,"1454":1,"1631":1,"1685":2,"1696":1,"1767":1,"1793":1,"1795":1,"1801":1,"1814":1,"1839":1,"1843":1,"1915":1,"1949":1,"1972":1,"2055":1,"2066":1,"2097":2,"2109":1,"2115":1,"2123":1,"2169":1}}],["asking",{"3":{"24":1,"123":1,"254":1,"389":1,"508":1,"557":1,"690":1,"740":1,"741":1,"757":1,"758":1,"782":1,"819":1,"881":1,"890":1,"899":1,"932":1,"1024":1,"1229":1,"1232":1,"1237":2,"1240":1,"1247":2,"1259":2,"1267":1,"1276":1,"1277":1,"1285":14,"1299":1,"1300":1,"1322":2,"1323":1,"1349":1,"1358":14,"1379":1,"1385":1,"1394":6,"1401":1,"1414":1,"1416":2,"1421":1,"1435":1,"1437":1,"1454":1,"1533":1,"1584":1,"1630":1,"1638":1,"1663":1,"1748":1,"1764":1,"1919":1,"1944":1,"2031":1,"2047":1,"2074":1,"2168":1,"2173":1,"2194":1}}],["asksforafreshlinkandsayssowithoutconfirmingtheaccountexists",{"3":{"1435":1}}],["asks",{"3":{"0":1,"10":1,"24":1,"73":1,"81":1,"126":1,"200":1,"265":1,"350":2,"369":1,"407":1,"427":1,"428":1,"458":1,"459":1,"465":1,"536":1,"545":1,"546":1,"573":1,"577":2,"590":1,"592":1,"626":2,"650":1,"682":1,"690":1,"692":2,"698":1,"724":1,"725":1,"793":2,"860":1,"871":1,"996":1,"1003":1,"1027":1,"1042":1,"1067":1,"1090":1,"1091":2,"1092":1,"1116":1,"1142":1,"1161":1,"1228":2,"1234":1,"1237":1,"1243":2,"1245":1,"1247":2,"1259":1,"1264":1,"1270":4,"1271":1,"1285":18,"1299":4,"1300":1,"1303":1,"1306":1,"1308":2,"1309":2,"1310":6,"1312":1,"1317":2,"1321":1,"1322":6,"1323":22,"1331":1,"1332":1,"1336":1,"1338":5,"1348":1,"1349":3,"1351":1,"1356":1,"1358":24,"1368":2,"1379":3,"1394":12,"1395":11,"1401":5,"1407":1,"1410":1,"1415":3,"1416":6,"1417":1,"1430":1,"1432":1,"1434":1,"1435":13,"1437":2,"1454":1,"1474":1,"1475":1,"1491":2,"1523":1,"1525":1,"1685":2,"1726":1,"1747":1,"1764":2,"1768":1,"1775":1,"1787":1,"1794":1,"1809":1,"1814":1,"1822":1,"1843":1,"1850":1,"1864":1,"1865":1,"1867":1,"1881":1,"1895":1,"1932":1,"1971":1,"1976":1,"2046":1,"2062":1,"2077":2,"2082":1,"2097":1,"2108":1,"2115":1,"2116":1,"2122":2,"2140":2,"2148":1,"2153":1,"2161":2,"2162":2,"2166":2,"2168":1,"2170":1,"2173":1,"2176":1,"2178":1,"2181":1,"2190":1,"2193":1}}],["asymmetries",{"3":{"1323":1,"1358":1}}],["asymmetricsignatureprovider",{"3":{"1299":1}}],["asymmetrically",{"3":{"1212":1,"1322":1}}],["asymmetric",{"3":{"31":3,"32":1,"186":1,"401":1,"1034":1,"1287":1,"1299":6,"1394":1,"1395":1,"1435":1,"1798":1,"1892":1,"1893":1,"1894":1,"1896":1,"1897":1,"1898":3}}],["asymmetry",{"3":{"0":2,"71":1,"230":1,"260":1,"343":1,"392":1,"401":1,"457":1,"463":1,"464":1,"465":1,"609":1,"624":2,"656":1,"658":1,"766":1,"819":2,"820":1,"823":1,"876":1,"1229":2,"1234":1,"1267":1,"1270":2,"1276":1,"1285":6,"1308":1,"1310":1,"1322":4,"1331":1,"1338":1,"1349":5,"1358":15,"1394":3,"1395":4,"1401":1,"1414":1,"1416":1,"1435":1,"1437":1,"1440":1,"1442":1,"1485":1,"1491":1,"1542":1,"1638":1,"1875":1,"1919":1,"1937":1,"1963":1,"1988":1,"2008":1,"2031":1,"2084":1,"2231":1}}],["asyncapi",{"3":{"1523":2,"1525":1,"1529":1,"1530":2,"1531":1,"1545":1}}],["asyncmethods",{"3":{"1435":1}}],["asyncmethodsdeclaretrailingcancellationtoken",{"2":{"0":1,"135":1},"3":{"0":1,"135":1,"1435":2}}],["asyncretrypolicy",{"3":{"1323":1}}],["asyncduplexstreamingcall",{"3":{"1311":1}}],["asyncclientstreamingcall",{"3":{"1311":1}}],["asyncclockreadingfixture",{"3":{"1199":2,"1207":1,"1435":5}}],["asyncunarycall",{"3":{"1311":1}}],["asyncstatemachineattribute",{"3":{"1435":1}}],["asyncserverstreamingcall",{"3":{"1311":1}}],["asyncservicescope",{"2":{"1252":1},"3":{"1252":1}}],["asyncscopedreadcontroller",{"3":{"1199":2,"1207":1}}],["asynchrony",{"3":{"743":1,"1435":1}}],["asynchronously",{"3":{"248":1,"350":1,"1341":1,"1394":4,"1412":1,"1414":2,"1437":1,"1466":1,"1633":1,"1637":1,"1750":1,"1769":1,"1886":1,"2110":1}}],["asynchronous",{"0":{"1389":1},"3":{"52":1,"59":1,"109":1,"255":1,"447":1,"455":1,"727":1,"1220":2,"1229":1,"1253":1,"1259":8,"1285":6,"1310":3,"1311":1,"1322":4,"1323":4,"1348":1,"1349":1,"1358":4,"1371":1,"1379":5,"1389":1,"1394":6,"1414":1,"1435":6,"1764":1,"1804":1,"1922":1,"2015":1,"2019":1,"2129":1}}],["asynclocal",{"2":{"1246":1},"3":{"265":3,"1246":2,"1247":4,"1317":1,"1323":2,"1416":2}}],["async",{"3":{"27":1,"77":1,"135":1,"483":1,"743":2,"1012":1,"1161":7,"1192":1,"1220":1,"1229":6,"1247":2,"1259":1,"1270":3,"1283":1,"1285":9,"1299":4,"1300":3,"1308":2,"1309":1,"1310":3,"1311":1,"1322":2,"1323":16,"1338":1,"1349":3,"1358":11,"1379":2,"1394":4,"1395":7,"1401":7,"1414":6,"1415":1,"1416":9,"1430":2,"1435":33,"1488":1,"1490":1,"1495":3,"1523":2,"1525":4,"1530":1,"1543":1,"1545":1,"1548":3,"1555":1,"1557":1,"1576":3,"1590":2,"1804":1,"1886":1}}],["aspiration",{"3":{"1668":1,"2058":1}}],["aspirational",{"3":{"1237":1,"1270":1,"1299":1,"1323":1,"1324":1,"1394":1,"1435":4,"1488":1,"1557":1,"1787":1,"2036":1,"2059":1}}],["aspire010",{"3":{"1440":1,"1525":1}}],["aspire",{"0":{"99":1,"1441":1,"1442":1,"1443":1,"2021":1},"1":{"911":1,"912":1,"913":1,"914":1,"915":1,"916":1,"917":1,"918":1,"1324":1,"1325":1,"1326":1,"1327":1,"1328":1,"1329":1,"1330":1,"1331":1,"1332":1,"1333":1,"1334":1,"1335":1,"1336":1,"1337":1,"1338":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"1443":1,"1444":1,"1445":1,"1446":1,"1447":1,"1448":1,"1449":1,"1450":1,"2002":1,"2003":1,"2004":1,"2005":1,"2006":1,"2007":1,"2008":1,"2009":1,"2010":1,"2011":1,"2012":1,"2013":1},"2":{"61":1,"67":1,"99":1,"100":1,"135":1,"141":1,"142":1,"214":2,"241":1,"247":1,"259":1,"638":1,"821":1,"825":1,"826":1,"899":1,"912":2,"913":1,"914":1,"916":1,"917":1,"939":1,"1004":1,"1034":1,"1035":1,"1036":3,"1067":2,"1068":4,"1069":2,"1211":2,"1213":1,"1324":4,"1325":1,"1327":1,"1328":1,"1336":1,"1338":1,"1429":3,"1436":1,"1438":1,"1444":1,"1459":1,"1485":1,"1486":1,"1487":1,"1495":1,"1502":1,"1503":1,"1655":1,"1659":2,"1676":1,"1780":2,"1789":1,"2003":3,"2007":1,"2013":2,"2021":1,"2027":1,"2029":1,"2037":1,"2054":1,"2110":2,"2148":1,"2150":1,"2152":1,"2159":1},"3":{"0":28,"18":1,"31":2,"47":1,"53":1,"61":3,"66":1,"67":1,"72":1,"91":1,"92":1,"93":2,"95":4,"98":2,"99":6,"100":1,"135":1,"141":1,"142":1,"150":1,"166":3,"171":1,"179":2,"180":1,"189":1,"214":3,"216":1,"217":1,"219":1,"220":1,"221":1,"233":1,"234":1,"235":14,"237":3,"238":1,"241":2,"243":2,"245":2,"247":2,"255":2,"256":1,"258":2,"259":5,"260":1,"261":1,"390":1,"392":1,"395":3,"397":9,"399":1,"400":2,"401":2,"402":3,"404":1,"406":1,"407":1,"408":2,"409":1,"599":1,"601":1,"603":1,"618":1,"619":1,"632":1,"633":2,"638":1,"640":3,"642":1,"665":6,"668":1,"669":2,"674":5,"774":2,"776":1,"792":1,"818":1,"819":5,"821":1,"822":3,"825":1,"826":4,"827":7,"829":3,"830":2,"833":2,"837":2,"838":1,"854":1,"862":1,"883":1,"884":1,"897":2,"899":1,"900":1,"911":1,"912":3,"913":12,"914":4,"915":1,"916":1,"917":2,"918":1,"932":1,"938":1,"939":3,"941":1,"980":8,"996":1,"1004":2,"1034":5,"1035":3,"1036":3,"1037":1,"1044":2,"1065":2,"1066":2,"1067":13,"1068":6,"1069":6,"1070":3,"1089":1,"1103":1,"1108":3,"1150":1,"1154":2,"1157":1,"1184":1,"1191":1,"1192":3,"1199":119,"1200":1,"1202":3,"1203":52,"1206":7,"1207":366,"1208":30,"1211":4,"1212":9,"1213":3,"1247":3,"1259":4,"1268":1,"1270":3,"1285":5,"1287":1,"1300":2,"1308":2,"1310":9,"1311":7,"1322":15,"1323":3,"1324":9,"1325":5,"1326":2,"1327":8,"1328":2,"1329":2,"1330":2,"1331":9,"1332":7,"1333":7,"1334":1,"1335":3,"1336":8,"1338":237,"1349":1,"1363":1,"1364":1,"1367":1,"1368":1,"1377":1,"1379":2,"1395":2,"1406":1,"1414":3,"1427":2,"1429":16,"1434":3,"1435":6,"1436":13,"1437":10,"1438":4,"1439":1,"1440":39,"1441":9,"1442":20,"1443":5,"1444":1,"1445":1,"1446":3,"1447":1,"1448":3,"1449":1,"1450":1,"1452":1,"1453":1,"1454":3,"1455":7,"1459":2,"1463":1,"1466":7,"1472":1,"1475":2,"1481":1,"1485":6,"1486":9,"1487":9,"1488":2,"1489":8,"1491":3,"1495":32,"1498":2,"1502":3,"1503":2,"1504":1,"1525":3,"1530":1,"1534":1,"1550":1,"1553":1,"1569":2,"1574":1,"1576":19,"1581":1,"1582":1,"1584":2,"1585":1,"1590":1,"1595":1,"1599":1,"1601":1,"1605":1,"1611":1,"1640":1,"1646":2,"1647":2,"1652":3,"1653":1,"1655":2,"1659":2,"1660":2,"1667":1,"1669":2,"1673":2,"1676":2,"1680":1,"1681":1,"1682":1,"1683":3,"1686":1,"1688":1,"1691":1,"1692":3,"1694":1,"1697":1,"1700":1,"1701":1,"1704":1,"1706":2,"1716":1,"1717":5,"1719":1,"1723":1,"1724":1,"1725":1,"1726":10,"1728":1,"1730":1,"1734":1,"1739":1,"1751":1,"1762":2,"1773":1,"1778":1,"1780":2,"1782":1,"1789":2,"1791":1,"1792":1,"1840":1,"1841":1,"1842":1,"1915":1,"1922":1,"1937":1,"2001":1,"2002":4,"2003":4,"2004":1,"2007":1,"2009":2,"2012":4,"2013":5,"2014":1,"2018":1,"2021":2,"2023":1,"2026":1,"2027":1,"2029":3,"2037":1,"2054":1,"2056":1,"2088":1,"2110":3,"2148":2,"2150":1,"2152":1,"2153":1,"2155":5,"2156":1,"2158":1,"2159":3,"2191":1,"2208":2,"2218":1,"2220":1,"2226":6,"2228":2,"2231":1,"2238":3,"2242":1}}],["aspect",{"3":{"881":1,"885":1,"1479":3,"1635":2}}],["aspnet",{"2":{"877":1,"1449":1},"3":{"868":1,"877":1,"1444":4,"1449":1}}],["aspnetcore",{"2":{"95":1,"99":1,"234":1,"401":1,"409":1,"633":1,"665":1,"1036":1,"1091":1,"1110":1,"1299":1,"1310":2,"1311":1,"1323":5,"1330":1,"1331":1,"1333":1,"1338":3,"1442":1,"2042":1,"2098":1},"3":{"95":2,"99":1,"234":1,"235":1,"397":2,"401":1,"409":1,"572":1,"633":1,"665":2,"776":1,"869":1,"1036":1,"1091":1,"1110":1,"1213":6,"1299":9,"1308":5,"1310":22,"1311":1,"1322":1,"1323":23,"1330":1,"1331":1,"1333":1,"1338":21,"1368":1,"1379":1,"1394":3,"1395":1,"1415":1,"1416":1,"1428":1,"1435":25,"1440":1,"1441":1,"1442":1,"1444":1,"1466":1,"1488":3,"1489":1,"1576":2,"1676":2,"1999":1,"2042":1,"2098":1}}],["asp",{"2":{"446":1,"447":1,"448":1,"454":1,"2128":1,"2130":1},"3":{"0":2,"109":3,"111":1,"174":1,"185":1,"207":1,"243":1,"265":2,"387":2,"395":2,"396":1,"397":5,"398":1,"405":1,"409":1,"411":1,"415":1,"446":1,"447":1,"448":4,"451":1,"454":1,"481":3,"482":1,"572":1,"664":1,"665":1,"748":1,"826":1,"831":1,"840":1,"853":1,"914":1,"916":1,"917":1,"1124":1,"1191":1,"1200":1,"1202":1,"1203":1,"1212":2,"1213":2,"1217":1,"1229":2,"1230":1,"1249":1,"1259":1,"1270":1,"1285":1,"1299":23,"1300":2,"1306":1,"1307":1,"1308":11,"1310":57,"1311":2,"1322":1,"1323":20,"1331":1,"1332":2,"1335":1,"1336":1,"1337":1,"1338":30,"1340":1,"1350":1,"1358":9,"1366":1,"1368":1,"1379":11,"1395":10,"1401":2,"1403":1,"1414":15,"1415":4,"1416":2,"1429":1,"1430":1,"1435":19,"1437":3,"1441":1,"1442":3,"1452":1,"1459":1,"1464":1,"1466":3,"1487":1,"1489":1,"1504":1,"1539":1,"1576":1,"1639":1,"1670":1,"1676":1,"1781":1,"1804":2,"1824":1,"1894":1,"1921":1,"1928":1,"1969":1,"1997":1,"2001":1,"2005":2,"2009":1,"2038":1,"2042":1,"2054":1,"2103":1,"2128":1,"2130":2,"2152":1,"2153":1,"2155":2,"2159":1,"2218":1,"2219":2,"2223":1,"2224":1,"2231":1}}],["assurant",{"3":{"2219":1}}],["assumeuniversal",{"3":{"1308":2,"1395":3}}],["assumes",{"3":{"320":1,"352":1,"405":1,"448":1,"735":1,"837":1,"1229":1,"1270":1,"1285":1,"1292":1,"1299":1,"1310":2,"1323":3,"1358":4,"1394":2,"1395":1,"1414":1,"1434":1,"1435":2,"1584":1,"1639":1,"1717":2,"1788":1,"1814":1,"1846":1,"1884":1,"1977":1,"2015":1,"2197":1}}],["assume",{"3":{"266":1,"389":1,"449":1,"709":1,"756":1,"827":1,"1247":1,"1270":2,"1285":3,"1299":1,"1322":2,"1323":3,"1358":2,"1368":1,"1395":3,"1401":1,"1414":2,"1435":1,"1625":1,"1736":1,"1880":1,"1900":1,"1904":1,"1908":1,"2014":1,"2124":1,"2132":2}}],["assumedefaultversionwhenunspecified",{"2":{"448":1,"449":1,"2130":1,"2132":1},"3":{"448":2,"449":1,"1310":1,"2130":1,"2132":1}}],["assumed",{"0":{"2033":1},"3":{"0":1,"69":1,"97":1,"265":1,"590":1,"813":1,"860":1,"871":1,"1229":1,"1247":1,"1279":1,"1285":1,"1310":1,"1317":1,"1322":1,"1323":2,"1338":2,"1358":2,"1394":2,"1415":1,"1435":6,"1437":2,"1491":1,"1548":1,"1559":1,"1576":1,"1853":1,"1862":1,"1863":1,"1877":1,"2036":1,"2037":1,"2202":1}}],["assuming",{"3":{"170":1,"603":1,"810":1,"1270":1,"1285":1,"1299":1,"1322":2,"1323":3,"1368":1,"1394":1,"1416":1,"1466":2,"1867":1}}],["assumption",{"3":{"97":1,"101":1,"254":1,"461":1,"481":1,"751":1,"831":1,"840":1,"972":1,"1134":1,"1285":3,"1323":1,"1358":3,"1395":3,"1414":1,"1435":2,"1466":1,"1584":1,"1691":1,"1843":1,"1862":1,"2026":1}}],["assumptions",{"3":{"55":1,"1401":1,"1716":1,"1717":2}}],["assplitquery",{"2":{"1863":1},"3":{"1243":1,"1244":1,"1247":3,"1278":1,"1285":8,"1309":1,"1435":1,"1814":1,"1863":1}}],["assinglequery",{"3":{"1285":1}}],["assistive",{"3":{"1394":1,"1395":1,"1416":6,"1525":1,"1531":1,"1557":1,"1749":1}}],["assistant",{"3":{"1091":1,"1423":1,"1458":2,"1459":1,"2219":1}}],["assisted",{"3":{"1012":1,"1350":1,"1569":1,"1624":1,"2212":2,"2217":1,"2218":1,"2224":1,"2242":1}}],["assignaction",{"3":{"1309":4,"1358":19,"1401":3}}],["assignableroles",{"3":{"1323":1,"1414":2}}],["assignable",{"3":{"707":1,"889":1,"1237":1,"1285":2,"1323":1,"1401":1,"1413":2}}],["assign",{"3":{"1236":1,"1237":3,"1285":4,"1309":3,"1358":10,"1394":1,"1471":1,"1629":1,"1631":2,"1688":1,"1745":2,"1747":2,"1767":1,"1867":2}}],["assignparentcategory",{"3":{"545":1,"1270":1}}],["assignparentcategoryhandler",{"2":{"543":1,"551":1},"3":{"543":1,"551":1}}],["assigning",{"3":{"265":3,"924":1,"1229":1,"1285":1,"1309":1,"1323":3,"1342":1,"1349":6,"1358":5,"1395":4,"1401":1,"1416":2,"1435":1}}],["assigns",{"3":{"263":1,"265":1,"303":1,"470":1,"551":1,"633":1,"690":1,"698":1,"700":1,"703":1,"1229":3,"1231":2,"1237":4,"1246":1,"1247":1,"1270":1,"1285":7,"1302":1,"1308":1,"1309":3,"1322":3,"1323":9,"1338":4,"1341":1,"1349":15,"1358":24,"1394":2,"1395":6,"1401":2,"1404":1,"1414":5,"1416":4,"1435":5,"1497":1,"1630":1,"1631":1,"1638":1,"1726":1,"1809":1,"1864":1,"2088":1}}],["assignees",{"3":{"224":1}}],["assignedproperties",{"3":{"1285":1}}],["assigned",{"2":{"1843":1},"3":{"0":1,"238":1,"545":3,"550":1,"599":3,"600":1,"657":2,"667":1,"830":1,"1018":1,"1196":1,"1200":1,"1212":1,"1231":1,"1237":5,"1259":1,"1270":3,"1277":1,"1278":1,"1285":12,"1299":4,"1308":3,"1309":1,"1310":1,"1323":2,"1338":2,"1341":3,"1348":2,"1349":29,"1351":1,"1354":1,"1358":36,"1363":1,"1368":1,"1379":1,"1394":21,"1395":7,"1401":17,"1414":2,"1416":1,"1437":1,"1458":1,"1466":1,"1471":1,"1487":1,"1495":1,"1497":2,"1525":1,"1623":1,"1626":1,"1629":2,"1630":2,"1631":4,"1634":1,"1636":1,"1637":1,"1638":8,"1726":1,"1754":1,"1775":1,"1843":1,"1928":1,"2231":1}}],["assignments",{"3":{"26":1,"599":1,"600":1,"923":1,"1285":3,"1299":1,"1338":1,"1349":3,"1358":16,"1394":3,"1401":1,"1466":3,"1471":2,"1631":2,"1635":2,"1843":2,"1954":2,"1955":1,"1958":1}}],["assignment",{"3":{"0":3,"265":1,"302":1,"424":1,"545":1,"549":1,"599":1,"600":1,"602":1,"665":5,"666":1,"668":2,"669":1,"692":1,"698":1,"714":1,"801":2,"804":1,"926":1,"1050":1,"1116":1,"1191":1,"1192":1,"1229":1,"1270":1,"1285":5,"1296":1,"1299":1,"1308":1,"1322":3,"1323":4,"1338":5,"1349":3,"1358":28,"1379":3,"1387":1,"1394":9,"1395":4,"1401":6,"1414":2,"1416":1,"1435":2,"1437":1,"1441":1,"1464":1,"1466":5,"1469":1,"1471":3,"1473":1,"1480":2,"1630":2,"1669":1,"1864":1,"1869":1,"1954":1,"1969":2,"1971":1,"2126":1,"2137":1}}],["associating",{"3":{"1358":1}}],["associations",{"3":{"1310":1,"1349":3,"1358":2,"1435":1,"1610":1,"1635":1,"1638":1}}],["association",{"1":{"417":1,"418":1,"419":1,"420":1,"421":1,"422":1,"423":1,"424":1,"425":1,"426":1,"427":1,"428":1,"429":1,"430":1},"3":{"0":5,"417":1,"418":4,"419":1,"420":2,"421":1,"425":1,"426":1,"428":1,"429":1,"1192":1,"1212":1,"1299":1,"1310":9,"1338":1,"1349":17,"1358":17,"1368":1,"1379":7,"1382":1,"1394":7,"1415":4,"1435":3,"1440":1,"1495":2,"1576":1,"1598":1,"1629":2,"1630":3,"1636":2,"1638":1,"1639":1,"1665":1,"2120":3,"2231":1}}],["associates",{"3":{"1327":1,"1338":2,"1358":1,"2219":1}}],["associate",{"3":{"1323":1,"1349":1,"1638":1,"2223":2}}],["associated",{"3":{"0":1,"359":1,"360":1,"365":1,"418":1,"420":1,"422":1,"1283":1,"1285":3,"1323":1,"1358":4,"1415":1,"1480":2,"1557":1,"1560":1,"1598":1,"1626":1,"1629":4,"1630":2,"1631":2,"1638":3,"1766":1,"2141":1,"2145":1}}],["asselfwithinterfaces",{"3":{"1310":2,"1322":1,"1358":3}}],["assessments",{"3":{"2233":1}}],["assessed",{"3":{"1299":1,"1535":1,"1584":2}}],["assesses",{"3":{"1216":1,"1218":2,"1229":14,"1237":37,"1247":38,"1259":36,"1270":68,"1271":17,"1285":178,"1299":140,"1300":22,"1308":66,"1309":6,"1310":86,"1311":7,"1312":1,"1322":117,"1323":183,"1338":20,"1349":54,"1351":2,"1352":2,"1353":1,"1356":1,"1358":389,"1359":1,"1368":38,"1379":32,"1380":1,"1393":1,"1394":155,"1395":192,"1401":86,"1414":96,"1415":12,"1416":140,"1426":18,"1435":209,"1437":2,"1439":1,"1440":5,"1441":1,"1442":4,"1444":1,"1448":1,"1452":2,"1453":2,"1454":5,"1455":1,"1456":2,"1457":1,"1459":1,"1462":1,"1463":1,"1464":4,"1466":9,"1471":2,"1472":2,"1473":3,"1474":2,"1475":2,"1476":1,"1477":2,"1478":1,"1479":1,"1480":1,"1485":3,"1486":1,"1487":4,"1488":6,"1489":1,"1491":4}}],["assess",{"3":{"1270":1,"1285":1,"1299":2,"1323":1,"1338":1,"1358":1,"1368":3,"1379":2,"1394":4,"1395":6,"1416":4,"1435":19,"1553":1}}],["assertmessagesuffix",{"3":{"1435":1}}],["assertnoviolations",{"3":{"1435":1}}],["assertnodependency",{"3":{"1435":3}}],["assertnoaccessibilityviolationsasync",{"2":{"618":1,"1432":1,"1487":1,"1489":1,"2056":1},"3":{"618":1,"1432":1,"1435":5,"1487":3,"1489":1,"1492":1,"2056":1}}],["assertdarkpaletteasync",{"3":{"1435":2}}],["assertdatasourceasync",{"2":{"1069":1},"3":{"1067":2,"1069":1,"1338":2}}],["assertdisabledstubs",{"3":{"1435":8,"1437":1}}],["assertprobepresentunderdefaultculture",{"3":{"1435":2}}],["assertproblemdetailsshapeasync",{"2":{"572":1,"2054":1},"3":{"572":1,"1435":3,"2054":1}}],["assertprotocontract",{"3":{"1435":2}}],["assertpipeline",{"3":{"1435":3}}],["assertcaseasync",{"3":{"1426":2}}],["asserth2casync",{"2":{"2055":1},"3":{"1067":2,"1069":1,"1338":3,"2055":1}}],["asserthealthyasync",{"2":{"2055":1},"3":{"1067":1,"1069":1,"1338":2,"2055":1}}],["assertjwksasync",{"2":{"2055":1},"3":{"1067":1,"1069":1,"1338":2,"2055":1}}],["assertreadyasync",{"2":{"2055":1},"3":{"1067":1,"2055":1}}],["assertaliveasync",{"2":{"2055":1},"3":{"1067":1,"2055":1}}],["assertable",{"3":{"860":1,"1109":1,"1299":1,"1308":1,"1310":1,"1338":1,"1435":6}}],["assertwithinbudget",{"2":{"861":1},"3":{"861":2,"1435":3}}],["asserted",{"3":{"0":6,"71":1,"73":1,"160":2,"252":1,"350":1,"352":1,"353":1,"371":1,"448":1,"455":1,"463":1,"546":1,"590":1,"591":1,"609":1,"640":1,"690":1,"707":1,"741":1,"757":3,"766":1,"838":2,"863":1,"876":1,"883":1,"941":1,"966":1,"972":1,"996":1,"1067":1,"1069":1,"1083":1,"1212":2,"1237":1,"1262":1,"1270":3,"1285":5,"1299":6,"1308":1,"1310":7,"1322":3,"1323":2,"1338":7,"1358":8,"1379":2,"1394":3,"1395":4,"1414":5,"1415":3,"1416":3,"1426":1,"1427":1,"1430":3,"1432":1,"1434":2,"1435":41,"1437":8,"1450":1,"1454":3,"1466":1,"1487":2,"1491":1,"1499":1,"1525":1,"1534":1,"1576":3,"1595":1,"1599":1,"1640":1,"1700":1,"1706":1,"1897":1,"1976":2,"1999":1,"2130":1,"2131":1,"2231":1}}],["asserting",{"3":{"0":2,"54":1,"70":1,"250":1,"265":1,"558":1,"573":1,"616":1,"618":1,"627":1,"633":1,"676":1,"691":1,"717":1,"797":1,"827":1,"838":1,"972":1,"1068":1,"1084":2,"1163":1,"1212":1,"1262":1,"1270":1,"1285":2,"1299":4,"1311":1,"1322":2,"1323":2,"1338":2,"1349":1,"1358":7,"1395":1,"1414":2,"1426":1,"1427":1,"1429":1,"1431":1,"1434":1,"1435":49,"1437":4,"1454":1,"1460":1,"1487":1,"1491":2,"1576":1,"1584":1,"1685":1,"2054":1,"2055":1,"2056":1,"2106":1,"2121":1,"2128":1,"2150":1,"2231":2}}],["assertions",{"3":{"0":3,"147":1,"237":1,"531":1,"572":1,"573":1,"574":1,"616":5,"618":2,"621":1,"640":1,"832":1,"861":2,"863":1,"963":1,"967":1,"972":1,"1018":1,"1050":1,"1067":2,"1093":1,"1212":1,"1213":1,"1229":1,"1234":1,"1259":1,"1285":1,"1310":1,"1323":1,"1338":3,"1426":1,"1427":1,"1432":1,"1435":47,"1436":1,"1437":3,"1448":2,"1452":1,"1460":1,"1487":2,"1525":4,"1530":1,"1533":1,"1585":1,"1590":1,"1617":1,"1650":1,"1667":1,"1670":2,"1730":1,"2000":1,"2041":1,"2054":1,"2055":1}}],["assertion",{"0":{"2196":1},"3":{"0":5,"157":1,"218":1,"277":1,"318":1,"348":1,"350":3,"368":1,"526":1,"530":1,"572":2,"590":1,"592":1,"611":1,"618":1,"619":1,"718":1,"861":1,"862":2,"863":1,"914":2,"972":3,"973":1,"974":1,"975":1,"1059":1,"1066":1,"1067":2,"1068":1,"1069":2,"1262":1,"1270":3,"1278":1,"1285":2,"1299":5,"1327":2,"1338":7,"1358":1,"1368":1,"1379":2,"1395":3,"1401":1,"1405":3,"1414":8,"1426":4,"1428":1,"1429":1,"1431":1,"1432":1,"1435":105,"1436":1,"1437":6,"1448":1,"1452":3,"1453":2,"1466":1,"1487":3,"1488":1,"1489":3,"1490":3,"1491":4,"1525":3,"1530":2,"1533":1,"1534":1,"1576":1,"1590":2,"1644":1,"1653":1,"1670":1,"1684":1,"1706":2,"1713":1,"1882":1,"1972":1,"1974":1,"1976":4,"1977":1,"1995":1,"2042":1,"2044":1,"2054":1,"2058":1,"2179":1,"2196":1,"2202":1}}],["assertsomethingwasmeasured",{"2":{"861":1,"863":1},"3":{"861":1,"863":1}}],["asserts",{"3":{"0":3,"70":1,"72":1,"73":1,"74":1,"78":1,"122":2,"127":1,"136":1,"175":2,"180":1,"189":1,"265":2,"302":1,"312":2,"350":2,"352":1,"448":1,"451":1,"459":1,"563":1,"571":1,"572":8,"574":1,"576":1,"577":1,"578":1,"583":3,"585":1,"609":1,"618":3,"633":2,"665":1,"725":2,"749":1,"776":1,"831":2,"861":3,"890":1,"946":2,"955":1,"962":1,"963":1,"972":1,"1018":3,"1020":1,"1034":2,"1037":1,"1067":4,"1068":1,"1091":1,"1212":1,"1214":1,"1236":1,"1237":4,"1247":2,"1270":2,"1271":2,"1285":6,"1299":3,"1300":2,"1308":1,"1310":3,"1311":1,"1322":3,"1323":6,"1325":1,"1338":15,"1358":15,"1364":1,"1368":1,"1379":4,"1394":1,"1395":7,"1401":3,"1404":1,"1407":1,"1414":6,"1415":2,"1416":6,"1426":3,"1429":1,"1430":1,"1432":1,"1434":3,"1435":289,"1437":15,"1443":1,"1448":1,"1450":1,"1454":1,"1462":1,"1466":2,"1475":1,"1483":1,"1486":1,"1487":8,"1488":3,"1489":1,"1490":2,"1491":2,"1523":1,"1525":10,"1530":2,"1576":1,"1590":5,"1670":1,"1671":1,"1706":2,"1730":1,"1816":1,"1824":1,"1825":1,"1841":1,"1888":1,"1901":1,"1928":1,"1929":1,"1976":1,"1977":1,"1978":1,"1992":1,"1999":1,"2000":1,"2012":1,"2030":1,"2035":1,"2040":1,"2042":2,"2044":3,"2054":5,"2056":2,"2062":1,"2074":1,"2078":1,"2097":1,"2103":2,"2130":1,"2132":1,"2137":1,"2177":2,"2196":1,"2231":1}}],["assert",{"2":{"1070":1},"3":{"0":7,"122":1,"134":1,"150":2,"188":1,"353":1,"373":1,"415":1,"449":1,"590":1,"592":1,"614":1,"618":3,"619":1,"626":1,"757":1,"759":1,"776":1,"842":1,"860":1,"861":2,"862":1,"863":1,"891":1,"915":1,"945":1,"952":1,"953":1,"954":1,"955":1,"956":1,"960":1,"972":1,"973":1,"1018":2,"1051":1,"1067":3,"1070":1,"1074":1,"1085":1,"1213":1,"1237":1,"1247":1,"1270":6,"1271":1,"1280":1,"1285":7,"1299":1,"1308":1,"1310":2,"1322":2,"1323":2,"1327":1,"1338":7,"1358":2,"1368":1,"1379":2,"1395":4,"1414":5,"1416":1,"1427":2,"1428":1,"1429":1,"1431":1,"1432":1,"1435":65,"1437":7,"1448":1,"1488":6,"1490":1,"1491":1,"1503":1,"1550":1,"1599":1,"1610":1,"1650":1,"1670":1,"1781":1,"1901":1,"1950":1,"2040":1,"2042":1,"2046":2,"2053":1,"2054":2,"2063":1,"2099":1,"2102":1,"2103":1,"2106":1,"2121":1,"2132":1,"2164":1,"2166":1,"2179":1,"2231":1}}],["assembling",{"3":{"1247":1,"1299":1,"1355":1,"1395":1,"1435":2,"1437":1}}],["assemblies",{"3":{"0":4,"27":1,"41":1,"60":1,"80":2,"109":1,"132":2,"135":2,"265":2,"269":1,"341":1,"413":1,"573":1,"576":1,"583":2,"584":3,"649":3,"651":3,"726":1,"881":2,"884":1,"982":1,"1005":1,"1050":2,"1052":1,"1161":1,"1163":2,"1170":1,"1203":1,"1212":1,"1213":1,"1214":1,"1237":2,"1250":1,"1259":2,"1266":1,"1270":5,"1279":3,"1281":2,"1285":27,"1299":8,"1310":4,"1312":1,"1314":2,"1322":7,"1323":4,"1324":1,"1332":1,"1338":2,"1362":1,"1368":1,"1379":2,"1395":2,"1408":1,"1414":5,"1415":2,"1430":1,"1435":99,"1436":1,"1441":1,"1442":1,"1444":1,"1454":3,"1459":2,"1488":5,"1491":4,"1495":3,"1503":1,"1525":2,"1584":1,"1590":4,"1652":1,"1663":1,"1668":1,"1670":1,"1781":1,"1809":1,"1848":2,"1856":1,"1874":1,"1880":4,"1884":1,"1885":1,"1890":1,"2003":1,"2012":2,"2041":2,"2047":1,"2053":1,"2099":1,"2100":1,"2101":1,"2107":1,"2119":1,"2155":1,"2158":1}}],["assembler",{"3":{"1247":1}}],["assemble",{"3":{"660":1,"724":1,"1308":1,"1312":1,"1323":1,"1401":3,"1639":1,"2076":1,"2086":1}}],["assembled",{"0":{"1262":1,"1465":1,"1820":1},"3":{"0":1,"256":1,"545":1,"585":1,"599":2,"649":1,"727":1,"745":1,"846":1,"861":1,"1017":1,"1021":1,"1107":1,"1240":1,"1247":2,"1271":1,"1283":1,"1285":1,"1299":3,"1308":1,"1310":1,"1322":4,"1338":1,"1349":8,"1352":1,"1358":10,"1365":2,"1367":1,"1368":1,"1394":1,"1395":1,"1406":1,"1409":1,"1414":4,"1415":1,"1422":1,"1426":1,"1435":1,"1444":1,"1465":1,"1466":4,"1491":1,"1495":4,"1576":1,"1584":1,"1986":1}}],["assembles",{"3":{"0":1,"725":1,"1212":1,"1247":2,"1270":1,"1271":2,"1308":1,"1310":1,"1322":2,"1323":3,"1358":3,"1380":1,"1395":2,"1401":1,"1414":1,"1426":1,"1435":4,"2066":1,"2149":1}}],["assemblyfilters",{"3":{"1454":1}}],["assemblyprovider",{"3":{"1368":2,"1395":1}}],["assemblyname",{"2":{"1318":1},"3":{"1310":1,"1318":1,"1322":3,"1349":2,"1358":1,"1368":1,"1379":2,"1395":4,"1414":7,"1435":1}}],["assemblyqualifiedname",{"3":{"1259":1,"1285":1}}],["assemblyinfo",{"2":{"1496":1},"3":{"1209":1,"1496":1}}],["assemblyreference",{"2":{"1318":1},"3":{"1199":16,"1203":16,"1207":48,"1310":14,"1312":1,"1318":4,"1322":46,"1340":3,"1349":12,"1353":2,"1358":7,"1368":11,"1376":2,"1379":8,"1395":36,"1403":2,"1414":46,"1435":1,"1495":3}}],["assembly",{"0":{"1199":1,"1206":1,"1318":1},"2":{"1266":1,"1318":1,"1772":1,"2076":2},"3":{"0":8,"5":1,"24":4,"41":1,"54":1,"59":2,"63":1,"76":2,"80":2,"81":3,"128":1,"133":1,"135":2,"166":1,"290":1,"305":1,"408":1,"451":1,"454":1,"549":1,"564":3,"571":1,"583":9,"585":6,"586":1,"607":1,"609":4,"633":2,"640":1,"648":1,"649":3,"651":2,"684":1,"698":1,"699":1,"725":2,"726":1,"782":1,"844":1,"848":2,"849":4,"922":1,"939":2,"940":1,"954":1,"962":1,"963":1,"971":1,"972":1,"974":1,"980":2,"982":1,"1004":1,"1018":1,"1034":3,"1042":1,"1050":1,"1052":1,"1067":1,"1074":1,"1091":1,"1118":1,"1161":3,"1162":1,"1168":1,"1170":1,"1175":1,"1193":1,"1196":1,"1199":1,"1202":1,"1203":2,"1206":1,"1207":1,"1208":3,"1212":2,"1213":1,"1229":1,"1233":2,"1237":5,"1247":2,"1250":2,"1256":1,"1259":13,"1262":2,"1263":1,"1266":1,"1270":8,"1271":7,"1273":2,"1275":1,"1276":1,"1279":5,"1285":74,"1299":32,"1300":1,"1303":1,"1307":1,"1308":15,"1309":1,"1310":39,"1311":2,"1312":1,"1313":1,"1314":2,"1315":4,"1317":1,"1318":6,"1322":92,"1323":36,"1325":1,"1338":15,"1340":4,"1347":1,"1349":20,"1350":1,"1351":1,"1353":1,"1358":60,"1366":1,"1368":22,"1379":28,"1394":28,"1395":69,"1401":5,"1403":1,"1414":86,"1415":9,"1416":1,"1426":1,"1430":8,"1433":1,"1434":3,"1435":391,"1436":3,"1437":10,"1440":1,"1443":1,"1446":1,"1464":1,"1475":1,"1479":1,"1486":2,"1487":1,"1488":11,"1489":1,"1490":1,"1491":2,"1493":1,"1495":4,"1502":1,"1525":3,"1552":1,"1576":3,"1581":1,"1590":4,"1599":1,"1613":1,"1626":1,"1647":1,"1650":3,"1652":1,"1653":2,"1670":1,"1683":3,"1692":1,"1700":1,"1718":2,"1726":1,"1727":2,"1728":1,"1760":1,"1772":1,"1780":1,"1809":1,"1829":2,"1850":1,"1880":2,"1882":2,"1884":3,"1890":1,"1928":1,"1955":1,"2015":1,"2040":2,"2042":1,"2045":1,"2047":2,"2048":1,"2076":2,"2094":1,"2102":1,"2103":2,"2105":1,"2158":1,"2177":1,"2186":2,"2187":1}}],["assetcachecontrol",{"3":{"1358":1}}],["assetid",{"3":{"0":1,"1116":1,"1356":1,"1466":2,"1630":2}}],["assets",{"3":{"0":5,"186":1,"1116":6,"1117":2,"1299":1,"1310":2,"1317":1,"1323":5,"1341":1,"1345":2,"1346":1,"1347":2,"1349":14,"1356":2,"1358":14,"1361":1,"1368":2,"1379":1,"1388":1,"1394":2,"1415":4,"1433":1,"1435":2,"1436":1,"1458":1,"1466":4,"1478":4,"1479":5,"1481":1,"1598":1,"1630":3,"1639":2,"1665":1,"2109":1,"2126":1,"2149":1}}],["assetlinkspath",{"3":{"1310":1}}],["assetlinks",{"2":{"418":1,"419":1,"424":1,"2120":1},"3":{"0":1,"418":1,"419":1,"420":1,"424":1,"1212":1,"1310":2,"1415":2,"1480":1,"2120":1}}],["asset",{"3":{"0":3,"57":1,"59":1,"189":1,"217":1,"424":1,"426":1,"534":1,"536":1,"681":1,"683":1,"861":1,"945":1,"954":1,"1006":1,"1116":7,"1117":1,"1120":2,"1124":1,"1126":1,"1284":1,"1285":1,"1299":3,"1310":4,"1323":4,"1341":2,"1343":1,"1345":1,"1347":1,"1349":16,"1351":1,"1353":1,"1356":1,"1358":59,"1361":1,"1368":3,"1379":3,"1388":2,"1394":23,"1416":2,"1435":1,"1466":2,"1470":1,"1495":4,"1559":1,"1585":1,"1590":1,"1630":1,"1639":4,"1665":1,"1697":1}}],["as",{"0":{"576":1,"1250":1,"1253":1,"1274":1,"1334":1,"1343":1,"1356":2,"1377":1,"1388":1,"1432":1,"1733":1,"1870":2,"1894":1,"2044":1,"2056":1,"2099":1,"2163":1},"1":{"129":1,"130":1,"131":1,"132":1,"133":1,"134":1,"135":1,"136":1,"137":1,"138":1,"139":1,"140":1,"141":1,"142":1,"143":1,"542":1,"543":1,"544":1,"545":1,"546":1,"547":1,"548":1,"549":1,"550":1,"551":1,"552":1,"561":1,"562":1,"563":1,"564":1,"565":1,"566":1,"567":1,"568":1,"569":1,"570":1,"571":1,"572":1,"573":1,"574":1,"575":1,"576":1,"577":1,"578":1,"579":1,"606":1,"607":1,"608":1,"609":1,"610":1,"611":1,"612":1,"613":1,"614":1,"615":1,"616":1,"617":1,"618":1,"619":1,"620":1,"621":1,"622":1,"623":1,"624":1,"625":1,"626":1,"627":1,"628":1,"629":1,"630":1,"654":1,"655":1,"656":1,"657":1,"658":1,"659":1,"660":1,"661":1,"722":1,"723":1,"724":1,"725":1,"726":1,"727":1,"728":1,"729":1,"730":1,"731":1,"732":1,"733":1,"734":1,"735":1,"736":1,"737":1,"738":1,"739":1,"740":1,"741":1,"742":1,"743":1,"744":1,"745":1,"763":1,"764":1,"765":1,"766":1,"767":1,"768":1,"769":1,"770":1,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1,"951":1,"952":1,"953":1,"954":1,"955":1,"956":1,"957":1,"958":1,"959":1,"960":1,"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1,"969":1,"970":1,"971":1,"972":1,"973":1,"974":1,"975":1,"976":1,"977":1,"978":1,"979":1,"980":1,"981":1,"982":1,"983":1,"984":1,"1001":1,"1002":1,"1003":1,"1004":1,"1005":1,"1006":1,"1007":1,"1008":1,"1031":1,"1032":1,"1033":1,"1034":1,"1035":1,"1036":1,"1037":1,"1038":1,"1047":1,"1048":1,"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1,"1056":2,"1057":2,"1058":2,"1059":2,"1060":2,"1061":2,"1062":2,"1063":2,"1064":1,"1065":1,"1066":1,"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1,"1461":1,"1462":1,"1463":1,"1464":1,"1465":1,"1466":1,"1467":1,"1468":1,"1469":1,"1930":1,"1931":1,"1932":1,"1933":1,"1934":1,"1935":1,"1936":1,"1937":1,"1938":1,"1939":1,"1940":1,"2069":1,"2070":1,"2071":1,"2072":1,"2073":1,"2074":1,"2075":1,"2076":1,"2077":1,"2078":1,"2079":1},"3":{"0":165,"5":1,"11":1,"12":1,"15":1,"17":1,"18":2,"19":1,"24":6,"26":3,"27":1,"31":2,"39":2,"41":1,"47":1,"53":1,"57":3,"58":2,"59":1,"66":2,"68":1,"71":2,"72":2,"73":2,"74":2,"76":1,"77":1,"79":1,"81":2,"86":1,"87":1,"91":1,"92":1,"93":2,"94":1,"95":5,"96":3,"97":1,"98":3,"99":1,"100":2,"101":2,"103":1,"104":1,"107":2,"109":15,"111":1,"115":3,"120":1,"121":3,"122":2,"123":1,"126":3,"127":1,"128":2,"129":1,"130":1,"132":1,"135":11,"136":5,"137":4,"138":4,"139":5,"140":3,"141":1,"142":3,"145":7,"147":2,"155":3,"157":6,"159":1,"160":5,"161":2,"164":1,"167":1,"168":1,"169":1,"170":1,"171":2,"173":1,"174":1,"175":1,"177":1,"178":1,"180":2,"181":2,"186":8,"188":4,"189":1,"195":2,"197":1,"198":1,"200":4,"201":2,"202":6,"203":1,"206":1,"207":1,"209":1,"214":6,"215":1,"216":1,"219":1,"223":1,"225":2,"226":2,"230":6,"231":3,"233":4,"234":3,"235":6,"237":3,"243":8,"244":4,"245":8,"247":1,"248":2,"249":3,"250":1,"252":1,"253":1,"254":2,"255":2,"256":1,"257":2,"258":4,"259":4,"260":2,"264":1,"265":15,"267":2,"268":3,"273":4,"275":1,"279":1,"281":2,"282":1,"283":2,"285":2,"286":1,"290":3,"291":1,"295":1,"296":2,"299":1,"303":8,"304":1,"305":4,"306":1,"308":1,"309":1,"310":2,"313":1,"314":2,"318":12,"319":1,"320":2,"321":3,"322":2,"325":3,"326":2,"329":2,"335":2,"336":1,"337":1,"341":10,"344":1,"345":3,"348":1,"349":1,"350":6,"351":2,"352":1,"353":5,"354":1,"358":3,"359":7,"360":3,"361":2,"362":2,"363":1,"365":3,"366":2,"369":4,"370":2,"371":1,"373":2,"374":2,"375":2,"376":1,"379":1,"381":1,"382":2,"383":1,"384":1,"388":2,"390":4,"392":2,"395":5,"397":5,"399":1,"401":4,"403":1,"404":3,"405":1,"406":2,"407":1,"408":1,"409":5,"411":2,"413":1,"415":3,"418":4,"420":1,"422":2,"423":3,"424":1,"425":2,"426":1,"427":2,"428":2,"434":2,"438":2,"440":2,"442":1,"443":2,"444":1,"447":1,"448":2,"451":1,"453":1,"454":2,"459":6,"461":1,"463":2,"464":1,"466":1,"469":2,"470":2,"471":1,"474":2,"476":1,"477":2,"478":1,"481":2,"484":1,"485":1,"486":3,"487":2,"488":3,"489":3,"490":7,"491":5,"492":5,"493":4,"494":3,"495":2,"497":4,"499":2,"500":1,"501":1,"502":1,"507":3,"512":1,"513":1,"514":2,"516":2,"518":4,"522":2,"523":2,"524":3,"526":2,"528":2,"530":1,"531":1,"534":3,"535":2,"536":8,"537":2,"538":2,"539":3,"540":3,"541":1,"542":1,"543":2,"544":1,"545":4,"547":4,"548":2,"549":10,"550":5,"551":3,"552":1,"555":1,"556":3,"561":1,"562":1,"563":1,"564":6,"565":1,"566":1,"569":1,"570":1,"572":2,"573":4,"575":2,"578":2,"579":1,"582":4,"583":7,"584":2,"585":7,"586":1,"587":1,"591":3,"592":3,"593":1,"597":1,"598":2,"599":9,"600":4,"601":4,"602":3,"604":1,"606":1,"607":6,"608":2,"609":9,"610":2,"611":1,"612":3,"613":1,"615":1,"617":2,"618":3,"619":3,"620":3,"622":1,"623":1,"624":3,"625":1,"626":7,"627":2,"629":1,"631":1,"632":1,"633":6,"634":2,"635":2,"638":2,"640":1,"642":1,"648":1,"649":3,"653":1,"654":1,"655":1,"656":2,"657":9,"658":1,"660":1,"661":1,"665":2,"667":1,"669":1,"670":1,"674":2,"676":4,"682":5,"683":3,"684":2,"688":2,"689":1,"690":3,"691":3,"692":7,"693":1,"694":1,"698":8,"699":2,"702":5,"703":1,"706":1,"707":7,"709":1,"711":1,"715":3,"717":1,"720":3,"722":1,"724":2,"725":10,"727":2,"728":2,"729":1,"730":1,"732":3,"733":3,"734":1,"735":3,"736":1,"737":1,"738":1,"740":1,"741":7,"742":3,"743":6,"744":1,"747":4,"748":4,"749":4,"750":1,"751":1,"755":1,"757":7,"758":2,"761":1,"762":1,"763":1,"764":2,"765":2,"766":2,"768":1,"769":1,"773":3,"774":4,"776":1,"778":1,"782":3,"783":1,"784":4,"785":1,"789":3,"790":10,"791":2,"794":1,"798":1,"799":1,"801":2,"803":2,"804":2,"808":1,"809":2,"810":4,"813":2,"814":1,"815":2,"817":1,"819":5,"820":1,"825":5,"826":1,"827":16,"828":1,"829":4,"831":2,"832":3,"833":1,"834":1,"836":1,"837":7,"838":7,"839":4,"840":2,"842":3,"844":1,"845":3,"846":5,"849":1,"850":1,"854":3,"857":2,"858":1,"859":1,"860":2,"861":7,"863":3,"864":3,"867":1,"868":2,"869":2,"870":5,"871":1,"872":3,"873":3,"875":1,"876":1,"877":1,"879":2,"880":3,"881":10,"882":1,"883":1,"884":2,"888":3,"889":6,"890":2,"892":1,"893":2,"896":1,"897":3,"903":1,"904":4,"905":11,"906":2,"907":5,"908":1,"909":1,"912":3,"914":2,"915":1,"920":1,"922":3,"923":3,"926":10,"927":1,"930":1,"931":1,"933":2,"937":1,"938":2,"939":1,"946":2,"948":3,"951":1,"953":5,"954":2,"955":4,"956":1,"961":1,"964":2,"965":1,"966":3,"968":3,"969":1,"971":1,"972":2,"973":2,"974":2,"975":2,"976":1,"977":1,"979":4,"980":8,"981":2,"982":1,"983":1,"986":1,"987":2,"988":4,"989":1,"990":1,"992":1,"994":2,"995":1,"996":8,"999":1,"1000":1,"1001":1,"1003":1,"1004":4,"1005":1,"1006":1,"1010":1,"1012":6,"1013":1,"1014":4,"1016":1,"1017":3,"1018":8,"1019":2,"1020":1,"1021":1,"1022":1,"1026":2,"1028":1,"1031":1,"1032":2,"1033":2,"1034":3,"1035":1,"1036":3,"1037":1,"1041":2,"1042":4,"1044":1,"1046":1,"1047":1,"1050":10,"1051":6,"1052":4,"1055":1,"1056":2,"1057":1,"1058":1,"1059":7,"1061":2,"1062":1,"1064":1,"1065":4,"1066":2,"1067":8,"1068":3,"1069":2,"1070":1,"1071":1,"1073":2,"1074":2,"1075":2,"1076":2,"1082":1,"1083":2,"1084":1,"1089":2,"1090":1,"1091":11,"1092":2,"1095":1,"1099":1,"1100":4,"1101":1,"1102":2,"1108":4,"1109":1,"1110":1,"1115":2,"1116":11,"1117":2,"1118":2,"1119":2,"1123":2,"1124":6,"1125":1,"1126":1,"1127":1,"1132":2,"1133":4,"1139":1,"1140":3,"1142":4,"1143":1,"1149":1,"1151":1,"1153":1,"1155":2,"1158":1,"1160":1,"1161":7,"1163":1,"1164":1,"1168":6,"1169":1,"1173":1,"1175":3,"1176":1,"1178":2,"1179":1,"1184":6,"1185":2,"1186":2,"1187":1,"1188":2,"1189":1,"1190":3,"1191":1,"1192":1,"1193":1,"1196":2,"1201":1,"1209":1,"1211":1,"1212":48,"1216":2,"1217":1,"1218":1,"1219":1,"1220":2,"1225":2,"1228":1,"1229":38,"1231":6,"1232":3,"1233":4,"1234":4,"1235":1,"1236":2,"1237":75,"1239":7,"1240":1,"1241":4,"1242":2,"1243":5,"1244":3,"1245":1,"1247":107,"1248":1,"1249":2,"1251":1,"1252":1,"1253":4,"1254":1,"1255":3,"1256":3,"1257":1,"1258":1,"1259":96,"1260":1,"1262":1,"1263":9,"1264":1,"1265":5,"1266":2,"1267":2,"1268":3,"1269":4,"1270":175,"1271":29,"1272":1,"1273":7,"1274":4,"1275":3,"1276":8,"1277":3,"1278":5,"1279":1,"1280":5,"1281":3,"1282":4,"1283":2,"1285":423,"1286":5,"1287":2,"1288":2,"1289":7,"1290":1,"1292":5,"1293":1,"1294":1,"1295":2,"1296":6,"1297":1,"1298":2,"1299":334,"1300":53,"1301":1,"1303":6,"1304":2,"1305":1,"1306":2,"1307":3,"1308":122,"1309":29,"1310":240,"1311":21,"1312":3,"1314":5,"1315":4,"1316":2,"1317":12,"1319":6,"1321":1,"1322":256,"1323":438,"1324":2,"1325":4,"1326":1,"1327":5,"1328":1,"1331":7,"1332":6,"1333":4,"1334":2,"1335":2,"1336":7,"1337":4,"1338":211,"1341":8,"1342":8,"1343":3,"1346":6,"1347":1,"1348":6,"1349":226,"1350":2,"1351":7,"1352":7,"1353":3,"1354":3,"1355":2,"1356":7,"1357":10,"1358":802,"1361":4,"1362":1,"1363":2,"1364":3,"1365":5,"1367":5,"1368":57,"1369":1,"1370":1,"1371":1,"1372":2,"1373":1,"1374":3,"1376":2,"1377":3,"1378":5,"1379":97,"1380":1,"1381":3,"1382":2,"1383":2,"1386":1,"1388":5,"1389":3,"1390":5,"1391":2,"1392":2,"1393":1,"1394":400,"1395":474,"1396":2,"1397":1,"1398":4,"1399":5,"1400":1,"1401":140,"1402":1,"1403":1,"1404":2,"1405":3,"1406":4,"1407":4,"1409":1,"1410":3,"1411":1,"1412":2,"1413":3,"1414":236,"1415":47,"1416":196,"1418":1,"1419":1,"1420":6,"1422":2,"1423":2,"1424":4,"1425":1,"1426":49,"1427":1,"1428":1,"1429":4,"1430":12,"1431":5,"1432":10,"1433":1,"1434":4,"1435":569,"1436":3,"1437":30,"1440":13,"1441":6,"1442":4,"1443":5,"1444":3,"1445":1,"1446":3,"1448":1,"1449":4,"1451":1,"1452":6,"1453":4,"1454":30,"1455":6,"1456":4,"1457":8,"1458":10,"1459":4,"1460":8,"1461":4,"1462":1,"1464":12,"1465":2,"1466":76,"1467":1,"1468":2,"1469":5,"1471":4,"1472":6,"1473":12,"1474":1,"1475":13,"1476":3,"1477":4,"1478":3,"1479":2,"1480":2,"1481":2,"1483":1,"1484":3,"1485":3,"1486":2,"1487":19,"1488":10,"1489":4,"1490":1,"1491":19,"1493":2,"1495":31,"1497":1,"1499":6,"1502":2,"1507":2,"1508":3,"1512":1,"1516":2,"1520":1,"1521":1,"1522":1,"1523":5,"1524":1,"1525":24,"1526":2,"1528":1,"1530":3,"1532":1,"1533":6,"1534":1,"1536":1,"1537":1,"1542":1,"1544":1,"1546":2,"1550":2,"1551":1,"1552":3,"1553":2,"1557":2,"1562":1,"1564":1,"1566":1,"1568":1,"1569":2,"1570":4,"1571":1,"1572":1,"1573":1,"1574":3,"1575":2,"1576":33,"1579":1,"1581":2,"1582":2,"1583":1,"1584":7,"1585":3,"1586":1,"1587":1,"1588":3,"1590":11,"1593":2,"1594":3,"1595":8,"1596":1,"1597":3,"1598":5,"1599":2,"1600":1,"1604":1,"1609":1,"1610":6,"1614":1,"1619":1,"1625":2,"1626":4,"1627":1,"1629":5,"1630":25,"1631":5,"1632":1,"1633":2,"1634":1,"1636":4,"1637":5,"1638":20,"1639":8,"1640":21,"1641":1,"1645":2,"1647":3,"1650":8,"1652":2,"1653":3,"1655":6,"1658":1,"1659":1,"1660":1,"1661":3,"1662":1,"1663":10,"1664":3,"1665":3,"1666":3,"1667":4,"1668":1,"1669":5,"1672":5,"1673":2,"1674":3,"1675":1,"1676":2,"1680":1,"1681":1,"1683":1,"1684":8,"1685":10,"1686":1,"1688":2,"1691":2,"1692":2,"1698":2,"1699":1,"1700":2,"1701":5,"1705":1,"1706":2,"1708":1,"1709":1,"1710":1,"1716":1,"1717":1,"1719":2,"1721":1,"1722":3,"1723":1,"1725":1,"1726":5,"1727":4,"1729":1,"1731":2,"1734":1,"1736":1,"1738":1,"1741":1,"1743":1,"1744":1,"1747":5,"1748":7,"1749":1,"1754":1,"1755":1,"1759":2,"1760":1,"1761":1,"1762":1,"1763":4,"1764":9,"1766":8,"1767":2,"1769":1,"1770":1,"1773":1,"1775":1,"1778":1,"1779":1,"1780":1,"1781":1,"1783":5,"1785":1,"1789":1,"1796":1,"1798":3,"1801":3,"1802":1,"1803":2,"1804":4,"1805":2,"1806":3,"1809":5,"1810":1,"1811":1,"1812":2,"1813":2,"1814":7,"1815":1,"1820":2,"1821":1,"1823":1,"1824":3,"1825":2,"1826":2,"1827":1,"1830":1,"1831":1,"1832":2,"1833":3,"1835":2,"1838":1,"1839":1,"1843":1,"1845":1,"1850":6,"1852":2,"1859":2,"1860":1,"1864":2,"1865":1,"1867":4,"1868":1,"1870":3,"1871":1,"1872":2,"1874":1,"1875":1,"1876":6,"1877":2,"1878":1,"1880":1,"1881":1,"1882":5,"1883":2,"1884":1,"1888":2,"1891":1,"1893":1,"1896":1,"1897":3,"1898":1,"1901":4,"1902":2,"1903":1,"1904":1,"1906":1,"1907":8,"1908":3,"1909":5,"1910":3,"1914":1,"1915":3,"1918":4,"1919":1,"1920":1,"1921":2,"1922":5,"1923":2,"1925":1,"1926":4,"1927":1,"1929":3,"1930":3,"1931":1,"1932":2,"1933":1,"1935":1,"1936":2,"1938":1,"1939":3,"1940":5,"1941":1,"1942":1,"1943":2,"1946":2,"1947":2,"1948":2,"1949":3,"1951":1,"1953":3,"1954":2,"1955":1,"1957":1,"1961":1,"1962":1,"1963":5,"1964":1,"1965":1,"1967":2,"1969":2,"1970":3,"1971":2,"1973":1,"1974":1,"1975":1,"1976":2,"1977":2,"1978":3,"1979":2,"1981":3,"1982":1,"1983":2,"1984":2,"1985":1,"1987":1,"1988":3,"1992":1,"1993":5,"1994":2,"1995":4,"1996":8,"1997":1,"1998":4,"1999":8,"2000":2,"2001":1,"2002":1,"2003":1,"2006":1,"2007":2,"2008":1,"2009":2,"2010":3,"2011":2,"2012":1,"2013":1,"2014":1,"2015":1,"2017":1,"2018":1,"2021":1,"2023":3,"2024":1,"2027":1,"2031":3,"2032":1,"2033":7,"2035":1,"2036":3,"2039":1,"2040":2,"2042":3,"2043":3,"2045":3,"2046":5,"2047":4,"2048":2,"2050":3,"2052":1,"2054":2,"2055":1,"2056":4,"2057":1,"2058":3,"2059":1,"2062":1,"2063":5,"2064":2,"2065":1,"2066":2,"2067":1,"2068":1,"2069":2,"2073":1,"2074":3,"2076":1,"2077":2,"2078":1,"2079":2,"2080":2,"2082":3,"2084":4,"2085":3,"2088":1,"2089":1,"2090":1,"2095":2,"2097":1,"2099":2,"2100":1,"2103":1,"2104":2,"2105":1,"2106":3,"2107":3,"2110":1,"2111":1,"2114":1,"2118":1,"2120":1,"2121":2,"2123":2,"2124":1,"2125":6,"2127":3,"2128":1,"2129":1,"2134":4,"2135":1,"2137":7,"2138":1,"2139":1,"2140":1,"2141":3,"2142":1,"2144":2,"2145":2,"2146":1,"2147":1,"2148":2,"2149":4,"2150":1,"2151":1,"2152":2,"2153":1,"2155":6,"2156":2,"2157":5,"2158":1,"2159":3,"2160":6,"2161":3,"2162":1,"2163":1,"2164":1,"2165":5,"2166":2,"2167":5,"2168":4,"2169":1,"2170":2,"2171":4,"2173":1,"2174":2,"2176":2,"2177":3,"2178":4,"2179":4,"2181":1,"2182":4,"2184":3,"2185":1,"2188":4,"2189":4,"2191":5,"2192":1,"2193":1,"2194":1,"2195":1,"2197":1,"2198":4,"2201":2,"2202":5,"2203":1,"2207":1,"2209":1,"2213":3,"2214":2,"2218":1,"2219":2,"2225":2,"2228":1,"2229":3,"2231":53,"2232":2,"2233":1,"2238":3}}],["atroot",{"3":{"1323":3,"1416":2}}],["atom",{"3":{"1310":1}}],["atomicity",{"3":{"120":1,"201":1,"245":1,"248":1,"249":1,"279":1,"990":1,"1042":1,"1043":1,"1044":1,"1251":1,"1259":3,"1270":3,"1275":1,"1300":2,"1308":2,"1317":1,"1323":1,"1349":1,"1358":2,"1379":3,"1405":1,"1414":1,"1571":1,"1821":1,"1834":1,"1851":1,"1914":1,"2091":1,"2095":1,"2184":1}}],["atomically",{"3":{"18":1,"193":1,"341":1,"342":1,"499":1,"1043":1,"1259":3,"1285":1,"1289":1,"1299":1,"1308":1,"1322":2,"1338":1,"1348":1,"1349":1,"1358":1,"1373":1,"1379":2,"1395":7,"1414":2,"1416":2,"1437":1,"1454":1,"1466":1,"1836":1,"1871":1,"1876":1,"1889":1,"2213":1}}],["atomic",{"3":{"0":1,"20":1,"39":1,"43":1,"245":1,"253":1,"279":1,"285":1,"286":3,"497":1,"524":1,"534":1,"535":1,"536":1,"540":1,"708":1,"905":1,"996":1,"1251":1,"1259":1,"1270":3,"1274":1,"1285":3,"1289":1,"1291":1,"1299":4,"1300":6,"1308":1,"1322":5,"1338":1,"1351":1,"1358":6,"1379":4,"1395":4,"1398":1,"1401":1,"1414":4,"1435":1,"1437":1,"1495":1,"1525":1,"1542":1,"1576":2,"1589":1,"1590":2,"1596":1,"1599":2,"1666":2,"1667":1,"1748":2,"1764":3,"1822":1,"1835":1,"1840":1,"1845":1,"2000":3,"2160":1,"2181":1}}],["atop",{"3":{"1212":1}}],["atl",{"3":{"2240":1}}],["atlcloudconf",{"3":{"2239":1}}],["atlanta",{"0":{"2239":1},"1":{"1627":1,"1628":1,"1629":1,"1630":1,"1631":1,"1632":1,"1633":1,"1634":1,"1635":1,"1636":1,"1637":1,"1638":1,"1639":1,"1640":1},"3":{"86":1,"1211":1,"1238":1,"1339":1,"1346":1,"1349":2,"1358":3,"1363":2,"1368":2,"1394":3,"1415":2,"1435":1,"1454":1,"1522":1,"1627":1,"1628":1,"1636":1,"1784":1,"2003":1,"2109":1,"2211":1,"2213":2,"2215":4,"2220":2,"2227":1,"2237":3,"2238":4,"2239":4,"2240":4,"2243":1,"2244":1}}],["atldevcon",{"0":{"1477":1},"2":{"45":1,"1450":1,"1462":1,"1470":1,"1477":2,"1480":1,"1481":1,"2033":1,"2035":1},"3":{"0":4,"45":1,"47":2,"418":1,"420":1,"426":1,"428":1,"429":1,"1285":1,"1299":2,"1310":1,"1358":2,"1394":2,"1415":4,"1440":2,"1445":1,"1450":1,"1462":1,"1465":1,"1466":7,"1468":2,"1469":2,"1470":1,"1472":11,"1473":5,"1474":2,"1477":12,"1480":3,"1481":4,"1482":2,"1483":2,"1975":1,"2033":1,"2035":2,"2239":1}}],["attheceiling",{"3":{"1435":8}}],["attractive",{"3":{"1997":1}}],["attr",{"3":{"1285":1}}],["attributing",{"3":{"1437":1,"1488":1}}],["attributions",{"3":{"1495":2}}],["attributiontags",{"2":{"2176":1},"3":{"1426":1,"2176":1}}],["attribution",{"0":{"1678":1},"3":{"348":2,"418":1,"963":1,"1012":1,"1017":1,"1338":1,"1395":1,"1424":1,"1426":1,"1435":3,"1464":1,"1466":1,"1475":1,"1495":1,"1525":1,"1567":1,"1574":3,"1576":2,"1584":2,"1675":1,"1678":1,"1679":1,"1798":1,"2170":1,"2176":1}}],["attributable",{"3":{"26":1,"175":1,"176":1,"178":1,"827":1,"829":1,"898":1,"1019":1,"1237":1,"1310":1,"1379":1,"1435":1,"1454":1,"1464":1,"1466":3,"1552":1,"1567":1,"1590":1,"1677":1,"1998":1,"1999":1,"2000":1,"2001":2,"2023":1,"2176":1,"2179":1}}],["attributed",{"3":{"175":1,"423":1,"741":1,"799":1,"1010":1,"1161":2,"1279":1,"1285":3,"1308":1,"1322":1,"1336":1,"1338":2,"1358":8,"1395":1,"1414":1,"1424":2,"1426":1,"1435":2,"1495":1,"2176":1}}],["attributeusageattribute",{"3":{"1299":1}}],["attributeusage",{"2":{"160":1,"175":1},"3":{"160":1,"175":1,"1237":3,"1299":2,"1309":2,"1310":3,"1311":1,"1322":2,"1323":2,"1999":1}}],["attributetargets",{"2":{"160":1,"1993":2},"3":{"160":1,"1237":3,"1299":2,"1309":2,"1310":4,"1311":3,"1322":2,"1323":2,"1993":2}}],["attributes",{"0":{"1320":1},"2":{"39":1},"3":{"24":1,"39":1,"135":1,"189":1,"320":1,"350":1,"418":1,"446":1,"448":2,"450":3,"554":1,"556":1,"651":1,"726":1,"814":1,"841":1,"964":1,"965":1,"980":1,"992":1,"1018":1,"1118":2,"1133":1,"1175":1,"1192":2,"1196":1,"1202":2,"1203":6,"1207":18,"1212":1,"1214":1,"1230":2,"1233":1,"1236":3,"1237":6,"1247":1,"1259":3,"1270":1,"1271":1,"1279":1,"1285":8,"1297":1,"1299":10,"1308":3,"1309":4,"1310":18,"1312":1,"1320":2,"1321":1,"1322":1,"1323":9,"1338":2,"1349":7,"1357":1,"1358":5,"1368":2,"1371":1,"1375":1,"1379":8,"1394":8,"1395":3,"1401":2,"1405":1,"1414":7,"1415":2,"1432":1,"1435":30,"1437":2,"1525":1,"1539":1,"1576":2,"1599":1,"1629":3,"1631":1,"1640":1,"1661":1,"1666":1,"1678":1,"1684":1,"1760":1,"1763":1,"1849":1,"1956":1,"1959":1,"1960":1,"1977":1,"1995":3,"2001":1,"2130":3,"2131":3,"2132":1}}],["attribute",{"0":{"1907":1,"1961":1},"1":{"1853":1,"1854":1,"1855":1,"1856":1,"1857":1,"1858":1,"1859":1,"1860":1,"1861":1,"1905":1,"1906":1,"1907":1,"1908":1,"1909":1,"1910":1,"1911":1},"3":{"0":3,"10":2,"24":2,"39":1,"54":2,"121":1,"124":1,"135":1,"136":1,"155":1,"160":2,"166":2,"167":2,"175":2,"185":1,"188":1,"189":4,"257":1,"273":1,"305":1,"318":1,"341":2,"343":1,"418":1,"422":2,"423":1,"556":1,"659":1,"718":1,"725":1,"789":1,"819":1,"849":1,"906":1,"964":2,"965":1,"966":1,"982":1,"1050":4,"1052":1,"1083":1,"1103":1,"1116":2,"1118":1,"1126":1,"1212":3,"1229":3,"1233":1,"1235":1,"1236":2,"1237":29,"1247":3,"1250":1,"1259":9,"1270":3,"1280":2,"1281":3,"1285":20,"1291":1,"1296":1,"1299":31,"1308":10,"1309":7,"1310":28,"1311":10,"1317":1,"1320":1,"1322":27,"1323":18,"1332":1,"1338":5,"1342":1,"1346":1,"1349":20,"1356":2,"1358":42,"1359":1,"1360":1,"1368":2,"1371":2,"1372":2,"1379":25,"1387":1,"1390":1,"1394":14,"1395":28,"1401":4,"1414":19,"1415":5,"1416":1,"1426":1,"1430":1,"1432":1,"1435":70,"1437":5,"1446":2,"1466":2,"1509":1,"1574":2,"1576":4,"1581":1,"1584":2,"1590":1,"1595":1,"1625":1,"1626":3,"1629":17,"1640":2,"1642":1,"1650":1,"1664":1,"1670":1,"1677":1,"1685":1,"1697":1,"1746":1,"1752":1,"1757":1,"1773":2,"1778":1,"1804":1,"1843":1,"1848":1,"1853":3,"1855":5,"1856":1,"1860":1,"1861":3,"1864":1,"1873":2,"1897":1,"1903":1,"1904":1,"1905":3,"1907":2,"1908":1,"1909":2,"1911":1,"1928":5,"1958":1,"1959":2,"1963":2,"1964":1,"1993":2,"1995":1,"1999":8,"2000":5,"2001":3,"2029":1,"2038":1,"2044":1,"2074":1,"2084":1,"2137":1,"2138":1,"2186":2,"2189":1,"2192":1,"2206":1,"2207":1,"2229":1,"2231":2}}],["attacked",{"3":{"1997":1,"2001":1}}],["attackers",{"3":{"1566":1}}],["attacker",{"3":{"31":1,"280":1,"283":5,"353":1,"373":1,"422":1,"427":1,"439":1,"853":1,"906":1,"1124":1,"1212":1,"1287":1,"1291":2,"1299":13,"1310":1,"1322":1,"1323":4,"1338":2,"1346":1,"1358":2,"1395":1,"1401":2,"1407":1,"1414":2,"1415":3,"1416":3,"1434":1,"1435":1,"1437":1,"1466":1,"1896":4,"1899":2,"1900":1,"1901":3,"1980":1,"1998":2,"2000":5,"2123":2,"2140":1}}],["attacks",{"3":{"1323":1,"1435":1}}],["attack",{"0":{"1896":1},"3":{"280":1,"332":1,"508":1,"528":1,"612":1,"1299":1,"1323":1,"1338":1,"1365":1,"1368":1,"1395":2,"1414":1,"1437":1,"1444":1,"1464":1,"1576":1,"1896":2,"1898":2,"1989":1}}],["attachable",{"3":{"1368":1}}],["attachobserverasync",{"3":{"1323":2}}],["attachtenantaccessor",{"3":{"1285":1}}],["attachscopeaccessors",{"3":{"1259":1,"1285":1}}],["attaching",{"3":{"0":1,"180":1,"351":1,"507":1,"827":1,"1184":1,"1259":1,"1278":1,"1285":1,"1299":1,"1310":1,"1323":1,"1338":1,"1358":4,"1394":1,"1414":1,"1437":1}}],["attachments",{"3":{"1117":1}}],["attachment",{"2":{"443":1},"3":{"0":2,"443":1,"725":2,"741":1,"881":1,"1116":2,"1285":3,"1299":1,"1310":2,"1338":1,"1358":1,"1394":1,"1435":1,"1437":1,"1440":1,"1630":1}}],["attachmmcaapplifecycle",{"3":{"0":1,"413":1,"1415":2,"1416":4}}],["attached",{"3":{"0":2,"81":1,"179":1,"180":1,"383":1,"397":1,"598":1,"619":1,"657":1,"797":1,"827":1,"871":1,"896":1,"897":1,"907":1,"915":1,"1050":1,"1051":1,"1092":1,"1184":1,"1223":1,"1229":3,"1259":1,"1280":1,"1285":5,"1296":1,"1299":6,"1308":1,"1310":4,"1322":4,"1323":5,"1338":4,"1349":3,"1358":6,"1368":6,"1379":1,"1394":2,"1395":3,"1399":1,"1401":2,"1414":4,"1415":2,"1416":6,"1435":4,"1464":1,"1466":1,"1478":1,"1487":2,"1943":1,"2001":1,"2063":1,"2120":1,"2179":1,"2192":1}}],["attaches",{"3":{"0":1,"26":1,"42":1,"243":1,"387":1,"402":1,"454":1,"506":1,"507":1,"521":1,"599":1,"640":2,"767":1,"833":1,"885":1,"1104":1,"1183":1,"1184":1,"1253":1,"1271":2,"1285":3,"1296":1,"1299":3,"1306":1,"1310":10,"1311":1,"1322":3,"1323":6,"1325":1,"1327":1,"1336":1,"1337":1,"1338":3,"1349":1,"1358":11,"1368":1,"1377":1,"1378":1,"1382":1,"1394":5,"1395":3,"1401":1,"1405":1,"1413":1,"1414":2,"1415":1,"1416":1,"1435":2,"1466":2,"1588":1,"1595":1,"1905":1,"1921":1,"1932":1,"1976":1,"1999":1,"2153":1}}],["attach",{"3":{"0":1,"166":1,"175":1,"352":1,"599":1,"644":1,"832":2,"883":1,"1271":3,"1285":1,"1299":1,"1310":4,"1323":1,"1325":1,"1326":1,"1338":6,"1349":1,"1358":2,"1379":3,"1394":1,"1401":1,"1413":1,"1414":2,"1415":1,"1416":1,"1764":1,"1949":1,"1977":1,"1978":1,"2141":1}}],["attention",{"3":{"757":1,"1270":1,"1323":2,"1349":1,"1416":1,"1454":1,"2174":1}}],["attend",{"3":{"1358":1,"1629":1,"1636":1,"1637":1,"1640":1}}],["attended",{"3":{"689":1,"691":1,"1117":1}}],["attendee+speaker",{"3":{"1640":1}}],["attendee→403",{"3":{"1610":1}}],["attendeelookup",{"3":{"1395":1}}],["attendeelookupservicetests",{"3":{"1199":1,"1207":4,"1395":3}}],["attendeelookupservice",{"3":{"1199":3,"1203":1,"1207":3,"1395":19}}],["attendeename",{"3":{"1395":1}}],["attendeenotificationrecipientprovidertests",{"2":{"1486":1},"3":{"1199":1,"1207":2,"1437":1,"1486":1}}],["attendeenotificationrecipientprovider",{"2":{"1303":1,"1932":1},"3":{"1199":4,"1203":1,"1207":2,"1303":2,"1308":9,"1414":2,"1437":1,"1932":1}}],["attendeeprofiletests",{"3":{"1199":1,"1207":2,"1490":1}}],["attendeeclaims",{"2":{"1616":1},"3":{"1616":1}}],["attendeeclaimstests",{"3":{"1199":1,"1207":2,"1610":1}}],["attendeecheckedinpointshandlertests",{"3":{"1199":1,"1207":2,"1395":1}}],["attendeecheckedinpointshandler",{"3":{"1199":2,"1203":1,"1207":2,"1259":1,"1395":16}}],["attendeecheckedin",{"2":{"692":1,"694":1},"3":{"195":2,"692":1,"694":1,"1199":13,"1203":1,"1207":2,"1237":2,"1395":15,"1435":3,"1437":3,"1466":1,"1630":2,"1638":2}}],["attendeeauthtests",{"3":{"1199":1,"1207":3,"1490":1}}],["attendeeaccessdeniedtests",{"3":{"1199":2,"1207":4}}],["attendeequestionanswertests",{"3":{"1199":1,"1207":2}}],["attendeequeryservicebase",{"2":{"1414":1},"3":{"1414":2}}],["attendeequeryserviceclient",{"2":{"1414":1},"3":{"1414":2}}],["attendeequeryservicetests",{"3":{"1199":1,"1207":2}}],["attendeequeryservicegrpcadapter",{"2":{"1412":1},"3":{"1199":2,"1203":1,"1207":2,"1412":2,"1414":10}}],["attendeequeryservice",{"2":{"1308":1,"1414":2},"3":{"1199":6,"1203":1,"1207":2,"1308":1,"1412":2,"1414":13}}],["attendeefeedbacktests",{"3":{"1199":1,"1207":2}}],["attendeebookmarkedgecasetests",{"3":{"1495":1}}],["attendeebookmarktests",{"2":{"1610":1},"3":{"1199":2,"1207":4,"1610":1}}],["attendeebadges",{"3":{"1395":1}}],["attendeebadgetests",{"3":{"1199":1,"1207":2,"1395":2,"1437":1}}],["attendeebadgeinvariants",{"3":{"1199":2,"1203":1,"1207":2,"1395":5}}],["attendeebadgeconfiguration",{"3":{"690":1,"1199":1,"1203":1,"1207":2,"1395":11}}],["attendeebadge",{"3":{"690":3,"1199":9,"1203":1,"1207":2,"1395":22,"1437":1}}],["attendeerow",{"3":{"1199":2,"1203":1,"1207":1,"1395":4}}],["attendee",{"0":{"1355":1,"1622":1,"1625":1},"2":{"1620":1,"1625":1},"3":{"0":1,"243":1,"325":1,"350":1,"387":1,"545":1,"585":1,"586":1,"681":2,"683":1,"684":1,"688":1,"689":3,"690":5,"691":2,"692":4,"725":1,"799":1,"831":1,"853":1,"1115":1,"1117":1,"1124":1,"1192":2,"1207":20,"1247":2,"1299":3,"1301":1,"1303":2,"1304":1,"1307":1,"1308":8,"1311":2,"1323":1,"1339":1,"1348":1,"1349":13,"1350":1,"1358":20,"1368":5,"1372":1,"1376":1,"1379":9,"1380":1,"1390":1,"1391":1,"1394":18,"1395":231,"1400":1,"1401":26,"1404":2,"1405":1,"1411":1,"1412":3,"1414":34,"1415":1,"1435":4,"1437":12,"1440":1,"1486":3,"1495":1,"1607":1,"1610":5,"1620":6,"1623":1,"1624":1,"1625":2,"1626":7,"1628":7,"1629":7,"1630":8,"1631":44,"1636":5,"1637":8,"1638":3,"1640":8,"1813":1,"1921":1,"1932":1,"1976":1,"1994":1,"2239":1}}],["attendeesgrpcservice",{"3":{"1199":1,"1203":1,"1207":2,"1412":2,"1414":6}}],["attendeesummary",{"3":{"1199":8,"1203":1,"1207":1,"1395":10}}],["attendeeshareandexporttests",{"3":{"1199":1,"1207":2,"1437":1}}],["attendeesearchfield",{"3":{"1199":2,"1203":1,"1207":1,"1395":4}}],["attendeesearchpanel",{"3":{"556":2,"881":2,"1199":3,"1203":1,"1207":2,"1323":3,"1395":21}}],["attendees",{"3":{"0":1,"224":1,"688":1,"689":1,"690":1,"692":1,"831":1,"832":1,"1116":2,"1124":1,"1308":3,"1349":5,"1358":6,"1366":1,"1376":1,"1379":2,"1391":2,"1394":7,"1395":25,"1396":1,"1401":3,"1414":2,"1437":1,"1625":1,"1626":1,"1628":2,"1629":7,"1630":11,"1631":1,"1634":1,"1635":1,"1636":2,"1637":4,"1638":8,"1639":2,"1640":3,"2109":1,"2215":1,"2231":1,"2237":1,"2238":2,"2239":1,"2240":1}}],["attendancestatsdto",{"3":{"1199":9,"1203":1,"1207":2,"1395":16}}],["attendance",{"2":{"1395":1},"3":{"0":1,"186":1,"689":1,"1150":1,"1192":1,"1203":2,"1207":4,"1342":1,"1349":2,"1358":1,"1395":54,"1414":3,"1437":3,"1486":1,"1495":1,"1624":1,"1626":2,"1628":1,"1630":1}}],["attests",{"3":{"1395":2,"1414":1,"1453":1}}],["attest",{"3":{"374":1,"530":1,"1453":1,"1483":1}}],["attestation",{"2":{"374":1,"1453":1},"3":{"372":1,"374":3,"1453":2,"1576":2}}],["attestations",{"0":{"374":1},"2":{"374":1,"1453":1},"3":{"368":1,"374":1,"1453":1,"1575":1}}],["attested",{"3":{"372":1,"374":1,"1358":1}}],["attempting",{"3":{"1466":1,"1630":1,"1631":3,"1940":1}}],["attempttimeout",{"3":{"1338":3}}],["attemptlifetime",{"3":{"1323":1}}],["attemptacquirecore",{"3":{"1310":2}}],["attemptoutputcaching",{"3":{"1310":1}}],["attempted",{"3":{"21":1,"201":1,"397":1,"409":1,"757":1,"758":1,"1259":2,"1270":2,"1299":1,"1300":2,"1308":1,"1310":1,"1322":2,"1323":5,"1414":1,"1416":2,"1631":3,"2191":1}}],["attemptskey",{"3":{"1299":1,"1999":1}}],["attempts",{"3":{"19":1,"22":1,"23":2,"72":1,"73":1,"282":2,"757":1,"759":1,"827":1,"854":1,"880":1,"881":1,"883":1,"987":1,"988":1,"998":1,"1042":1,"1212":1,"1259":5,"1270":1,"1273":1,"1275":2,"1285":8,"1291":1,"1299":12,"1300":2,"1308":1,"1310":2,"1317":2,"1322":7,"1323":4,"1329":1,"1338":9,"1358":4,"1437":1,"1446":1,"1454":2,"1458":1,"1536":1,"1630":2,"1631":1,"1638":2,"1640":5,"1664":1,"1665":1,"1839":1,"1895":1,"1910":2,"1999":1,"2000":1,"2001":1,"2031":1,"2073":1,"2185":1,"2187":1,"2190":1}}],["attemptunitofworkkey",{"3":{"1358":1}}],["attemptunitofwork",{"2":{"523":1},"3":{"0":1,"523":1,"926":1,"1270":1,"1358":2}}],["attempt",{"2":{"407":1,"1329":1},"3":{"0":7,"19":2,"21":2,"22":1,"24":3,"70":1,"73":3,"100":1,"120":1,"159":1,"199":1,"201":1,"279":1,"281":1,"282":1,"284":1,"286":1,"343":1,"353":2,"397":1,"407":1,"409":1,"434":1,"539":1,"715":1,"727":1,"736":1,"760":1,"813":1,"818":1,"819":1,"821":1,"827":1,"833":2,"849":1,"854":2,"855":1,"881":6,"882":2,"883":1,"920":1,"926":3,"988":6,"990":1,"991":1,"992":1,"996":1,"1018":1,"1019":1,"1042":4,"1043":1,"1045":1,"1059":1,"1099":2,"1212":1,"1229":1,"1256":2,"1258":1,"1259":6,"1265":1,"1270":10,"1274":1,"1278":1,"1285":16,"1288":1,"1291":1,"1293":2,"1294":1,"1299":32,"1303":2,"1308":3,"1310":2,"1311":3,"1316":1,"1322":9,"1323":29,"1329":3,"1338":10,"1351":1,"1354":1,"1358":20,"1365":1,"1368":1,"1377":1,"1379":1,"1388":2,"1394":12,"1395":8,"1401":2,"1407":1,"1412":1,"1414":6,"1416":7,"1426":1,"1434":1,"1435":3,"1437":6,"1442":1,"1454":1,"1456":1,"1491":2,"1546":1,"1576":1,"1599":1,"1630":1,"1637":1,"1640":4,"1663":1,"1667":1,"1764":1,"1766":1,"1839":4,"1840":1,"1842":2,"1932":1,"1975":2,"1997":1,"2000":1,"2005":1,"2029":2,"2031":1,"2164":1,"2166":1,"2181":2,"2187":2,"2189":1,"2191":1,"2197":1}}],["at",{"0":{"1335":1,"1338":1,"1368":1,"1372":1,"1465":1,"1522":1,"1529":1,"1573":1,"1580":1,"1587":1,"1594":1,"1686":1,"1701":1,"1872":1,"1902":1,"1934":2,"2045":1,"2149":1,"2150":1,"2177":1},"1":{"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"294":1,"295":1,"296":1,"356":1,"357":1,"358":1,"359":1,"360":1,"361":1,"362":1,"363":1,"364":1,"365":1,"366":1,"1009":1,"1010":1,"1011":1,"1012":1,"1013":1,"1014":1,"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1},"2":{"626":1},"3":{"0":116,"5":1,"6":2,"10":1,"15":2,"17":1,"19":10,"20":1,"21":1,"22":4,"23":2,"24":30,"25":2,"26":4,"27":21,"29":1,"31":5,"32":1,"34":4,"39":8,"41":1,"43":1,"46":2,"53":1,"54":1,"57":3,"58":1,"59":8,"60":1,"61":1,"62":3,"63":1,"66":2,"67":1,"68":1,"69":1,"70":1,"71":4,"72":7,"73":2,"74":1,"76":2,"80":2,"81":5,"85":1,"91":2,"92":1,"93":4,"94":2,"95":6,"96":1,"99":3,"100":3,"102":1,"103":2,"104":1,"107":1,"108":1,"109":31,"110":5,"111":6,"115":7,"117":1,"118":1,"119":1,"121":4,"122":21,"125":2,"126":4,"127":1,"128":1,"130":7,"131":1,"132":2,"133":2,"135":19,"136":3,"137":5,"138":5,"139":7,"140":2,"141":3,"142":6,"145":2,"146":1,"147":3,"148":2,"149":3,"150":10,"155":1,"156":2,"157":2,"158":1,"159":1,"160":6,"161":4,"162":1,"164":1,"166":8,"168":1,"170":10,"173":1,"175":3,"176":1,"177":2,"178":8,"179":8,"180":8,"184":1,"186":10,"189":6,"190":1,"191":1,"193":5,"194":3,"195":2,"198":2,"199":2,"200":8,"201":7,"202":7,"203":2,"206":1,"207":4,"208":1,"209":2,"212":1,"213":1,"214":2,"215":1,"216":1,"218":1,"219":4,"220":2,"224":2,"225":4,"226":2,"228":2,"230":10,"231":3,"234":1,"235":10,"237":3,"238":2,"241":5,"242":2,"243":11,"244":1,"245":3,"246":4,"250":2,"251":3,"252":2,"253":1,"254":4,"255":3,"256":11,"257":11,"258":8,"259":10,"260":7,"261":16,"265":10,"266":1,"273":1,"276":2,"279":1,"280":2,"282":1,"283":2,"284":3,"288":1,"290":4,"291":1,"295":2,"298":2,"299":1,"300":3,"303":2,"305":9,"306":1,"309":1,"310":3,"311":1,"312":3,"313":1,"314":6,"318":8,"319":1,"321":1,"325":4,"326":5,"328":1,"330":8,"331":2,"333":4,"334":1,"335":3,"336":1,"337":1,"341":5,"343":2,"346":1,"349":1,"350":7,"351":2,"352":5,"353":6,"354":2,"356":1,"358":1,"359":9,"360":3,"361":4,"362":1,"364":1,"365":2,"366":2,"368":1,"369":1,"371":1,"373":9,"375":1,"379":1,"380":2,"384":3,"386":1,"387":1,"388":1,"389":1,"390":13,"391":6,"392":1,"393":9,"395":9,"397":26,"398":1,"399":4,"400":6,"401":10,"402":4,"403":8,"405":2,"407":7,"408":3,"409":2,"412":2,"413":2,"414":1,"415":1,"416":1,"418":8,"420":3,"422":1,"423":10,"424":3,"425":5,"426":16,"427":4,"428":27,"429":16,"430":7,"433":1,"434":1,"435":2,"436":1,"441":1,"442":1,"443":1,"444":2,"448":7,"451":1,"453":1,"454":1,"457":1,"459":11,"460":1,"461":3,"463":16,"464":11,"465":5,"466":4,"468":4,"469":1,"470":1,"471":2,"472":1,"475":1,"476":3,"477":1,"478":2,"483":3,"484":1,"486":6,"487":6,"488":4,"489":6,"490":20,"491":16,"492":17,"493":12,"494":1,"495":4,"497":4,"499":11,"500":1,"501":2,"502":1,"506":1,"508":1,"511":1,"512":1,"513":1,"514":1,"516":1,"520":2,"522":15,"523":20,"524":10,"528":2,"529":1,"532":1,"534":1,"535":4,"536":9,"537":2,"538":6,"539":9,"540":4,"543":5,"545":17,"546":1,"547":1,"549":17,"550":1,"551":1,"552":13,"555":2,"556":7,"557":2,"559":1,"563":1,"564":8,"565":1,"568":1,"570":1,"571":1,"572":2,"574":1,"575":1,"576":1,"577":1,"579":1,"583":6,"585":5,"586":2,"587":1,"590":1,"591":8,"594":3,"595":1,"598":2,"599":10,"600":1,"601":5,"602":8,"603":6,"604":6,"605":1,"607":1,"608":4,"609":48,"611":2,"612":8,"613":2,"617":1,"618":17,"619":1,"620":1,"622":1,"625":1,"626":11,"627":2,"628":4,"629":1,"631":4,"633":8,"635":3,"636":2,"638":1,"640":26,"641":2,"642":2,"643":1,"644":1,"645":1,"648":1,"649":10,"651":1,"652":1,"656":2,"657":15,"658":3,"664":1,"665":13,"666":2,"667":1,"668":7,"673":3,"674":21,"675":3,"676":8,"678":1,"682":4,"683":1,"684":5,"689":1,"690":13,"691":3,"692":5,"693":1,"694":1,"696":3,"697":3,"698":14,"699":2,"700":3,"702":3,"703":2,"706":1,"707":2,"708":2,"709":4,"711":1,"713":1,"714":1,"715":6,"716":2,"717":4,"719":4,"720":1,"721":1,"724":1,"725":3,"728":1,"729":2,"732":4,"733":9,"735":1,"740":6,"741":16,"743":9,"744":3,"745":1,"749":10,"751":2,"752":2,"753":2,"757":8,"758":1,"760":5,"765":3,"766":7,"767":3,"768":2,"769":1,"773":1,"774":5,"776":1,"782":6,"789":5,"790":10,"791":2,"792":3,"793":6,"794":1,"795":1,"797":3,"798":1,"799":4,"800":3,"801":1,"803":2,"804":2,"805":1,"809":2,"810":5,"812":3,"814":1,"815":2,"818":2,"819":10,"820":1,"821":5,"822":2,"823":1,"825":2,"826":5,"827":43,"828":2,"829":4,"830":9,"831":5,"832":13,"833":3,"837":3,"838":12,"840":2,"844":1,"846":4,"847":2,"848":2,"849":3,"850":6,"853":4,"854":3,"855":1,"857":2,"860":2,"861":15,"862":1,"863":3,"865":1,"867":3,"869":1,"870":3,"871":2,"872":2,"873":2,"874":1,"875":3,"876":2,"881":18,"883":4,"884":2,"888":1,"889":6,"891":4,"893":1,"896":2,"897":17,"898":2,"900":1,"904":1,"905":61,"906":33,"907":2,"908":1,"913":8,"914":17,"915":5,"916":3,"917":1,"918":1,"920":1,"921":1,"922":1,"923":2,"924":2,"925":1,"926":11,"930":2,"931":14,"933":2,"937":1,"938":1,"940":3,"942":1,"945":2,"946":10,"947":1,"948":4,"949":2,"950":1,"952":2,"954":1,"956":1,"958":1,"959":2,"960":1,"963":1,"964":10,"965":1,"966":3,"971":3,"972":11,"974":1,"979":4,"980":14,"981":1,"982":2,"984":1,"987":2,"988":1,"990":2,"991":1,"995":5,"996":8,"997":1,"998":3,"1000":1,"1004":9,"1005":3,"1006":2,"1008":1,"1009":1,"1012":9,"1014":3,"1016":1,"1017":4,"1018":44,"1019":5,"1020":5,"1023":1,"1026":6,"1027":2,"1028":4,"1033":3,"1034":3,"1037":1,"1042":6,"1044":4,"1045":2,"1048":2,"1049":1,"1050":2,"1051":1,"1052":3,"1054":2,"1058":1,"1059":7,"1060":1,"1062":2,"1066":1,"1067":6,"1068":1,"1069":3,"1074":6,"1075":12,"1077":1,"1078":2,"1082":6,"1083":2,"1084":2,"1086":2,"1087":1,"1089":2,"1090":6,"1091":37,"1092":3,"1093":4,"1094":1,"1095":4,"1099":2,"1100":27,"1101":2,"1102":3,"1103":4,"1107":2,"1108":6,"1109":1,"1110":1,"1112":1,"1115":1,"1116":19,"1118":1,"1123":1,"1124":13,"1125":3,"1126":4,"1129":1,"1132":3,"1133":7,"1134":3,"1135":5,"1136":1,"1140":7,"1142":1,"1146":3,"1147":1,"1150":5,"1156":2,"1160":4,"1161":1,"1162":1,"1163":1,"1168":9,"1169":2,"1173":2,"1174":1,"1175":4,"1176":2,"1177":1,"1182":2,"1184":4,"1185":1,"1186":1,"1187":1,"1192":1,"1194":1,"1202":1,"1203":1,"1212":28,"1213":2,"1214":3,"1215":1,"1216":2,"1220":3,"1221":1,"1222":2,"1225":2,"1227":1,"1228":3,"1229":31,"1231":8,"1232":6,"1233":6,"1234":6,"1236":5,"1237":70,"1238":2,"1239":7,"1240":13,"1241":27,"1242":16,"1243":8,"1244":7,"1246":4,"1247":111,"1248":2,"1249":1,"1251":1,"1252":2,"1253":2,"1254":3,"1255":2,"1256":4,"1257":7,"1258":2,"1259":124,"1260":1,"1261":2,"1262":6,"1263":10,"1264":15,"1265":6,"1266":1,"1267":4,"1268":3,"1269":4,"1270":178,"1271":47,"1273":24,"1274":7,"1275":9,"1276":3,"1277":12,"1278":10,"1279":7,"1280":8,"1281":9,"1282":2,"1283":7,"1284":4,"1285":420,"1287":2,"1288":1,"1289":2,"1290":3,"1291":3,"1292":1,"1293":2,"1296":8,"1297":3,"1299":273,"1300":59,"1301":5,"1303":2,"1304":1,"1305":1,"1306":2,"1308":84,"1309":34,"1310":368,"1311":25,"1312":1,"1313":5,"1314":2,"1315":6,"1316":4,"1317":6,"1319":7,"1321":1,"1322":234,"1323":326,"1324":2,"1325":10,"1326":5,"1327":3,"1328":1,"1329":8,"1330":3,"1331":8,"1332":15,"1333":6,"1334":4,"1335":5,"1336":4,"1337":12,"1338":207,"1340":1,"1341":9,"1342":12,"1343":13,"1344":4,"1345":1,"1346":5,"1347":7,"1348":4,"1349":211,"1350":1,"1351":10,"1352":7,"1353":7,"1354":5,"1355":2,"1356":6,"1357":7,"1358":657,"1359":1,"1361":11,"1362":3,"1363":2,"1364":1,"1365":4,"1367":1,"1368":78,"1370":3,"1371":1,"1372":8,"1373":5,"1374":2,"1375":2,"1376":2,"1377":1,"1378":5,"1379":113,"1381":6,"1382":11,"1383":5,"1384":4,"1385":2,"1386":6,"1387":7,"1388":17,"1389":7,"1390":12,"1391":10,"1392":8,"1393":2,"1394":289,"1395":424,"1396":1,"1397":7,"1398":5,"1399":7,"1400":3,"1401":156,"1403":6,"1404":4,"1405":9,"1406":1,"1407":1,"1408":9,"1409":6,"1410":2,"1411":1,"1412":6,"1413":2,"1414":213,"1415":62,"1416":146,"1418":7,"1419":4,"1420":5,"1421":1,"1422":1,"1423":3,"1424":3,"1426":32,"1428":17,"1429":5,"1430":20,"1431":5,"1432":13,"1433":5,"1434":4,"1435":505,"1437":53,"1438":2,"1439":2,"1440":22,"1441":8,"1442":18,"1443":6,"1444":3,"1445":3,"1446":18,"1447":1,"1448":4,"1449":2,"1451":2,"1452":6,"1453":7,"1454":28,"1455":5,"1456":5,"1457":4,"1458":2,"1459":11,"1460":2,"1462":2,"1463":2,"1464":4,"1465":4,"1466":88,"1467":3,"1468":2,"1469":1,"1471":2,"1472":6,"1473":8,"1474":9,"1475":12,"1476":4,"1477":4,"1478":2,"1479":5,"1480":2,"1481":2,"1482":1,"1483":1,"1485":9,"1486":1,"1487":29,"1488":15,"1489":15,"1490":7,"1491":22,"1493":1,"1495":61,"1496":2,"1497":1,"1498":1,"1499":6,"1502":1,"1503":1,"1507":1,"1508":1,"1509":1,"1512":1,"1516":1,"1521":2,"1522":1,"1523":6,"1524":5,"1525":90,"1526":5,"1528":1,"1529":4,"1530":14,"1531":4,"1533":19,"1534":5,"1536":2,"1539":1,"1543":2,"1545":2,"1546":1,"1547":2,"1548":1,"1549":1,"1550":2,"1551":1,"1552":1,"1553":1,"1555":1,"1564":1,"1565":1,"1567":1,"1569":1,"1571":1,"1572":2,"1573":5,"1574":3,"1575":2,"1576":71,"1577":7,"1579":1,"1580":1,"1581":15,"1582":7,"1583":3,"1584":12,"1585":22,"1586":2,"1587":2,"1588":3,"1589":2,"1590":102,"1591":2,"1593":1,"1595":15,"1596":15,"1597":4,"1598":12,"1599":12,"1610":1,"1613":1,"1620":1,"1625":2,"1626":9,"1629":5,"1630":12,"1631":5,"1634":1,"1636":2,"1637":5,"1638":4,"1639":1,"1640":12,"1645":4,"1648":2,"1650":4,"1651":2,"1652":7,"1653":1,"1654":1,"1655":2,"1658":1,"1659":2,"1660":4,"1661":1,"1662":1,"1663":2,"1664":4,"1665":1,"1667":4,"1668":3,"1669":5,"1670":1,"1671":3,"1672":4,"1674":1,"1676":1,"1677":1,"1682":1,"1683":3,"1684":7,"1685":10,"1686":1,"1688":3,"1689":2,"1692":2,"1695":1,"1697":1,"1698":1,"1699":3,"1700":1,"1701":1,"1704":1,"1706":1,"1707":1,"1718":2,"1719":1,"1720":2,"1721":1,"1723":1,"1726":7,"1727":3,"1728":4,"1730":2,"1737":1,"1739":1,"1746":1,"1747":5,"1748":3,"1749":1,"1754":1,"1756":1,"1757":1,"1759":3,"1760":3,"1763":4,"1764":10,"1766":6,"1768":3,"1775":8,"1776":1,"1779":1,"1780":1,"1781":2,"1782":1,"1783":3,"1787":1,"1788":1,"1789":2,"1790":1,"1792":1,"1793":3,"1794":1,"1795":4,"1796":3,"1798":3,"1799":1,"1802":1,"1803":2,"1804":4,"1805":1,"1806":2,"1809":7,"1810":1,"1812":1,"1813":1,"1814":9,"1816":1,"1817":2,"1819":1,"1820":4,"1823":1,"1824":2,"1825":4,"1826":2,"1828":3,"1829":1,"1830":1,"1831":2,"1832":2,"1833":2,"1834":1,"1836":1,"1838":2,"1839":1,"1840":2,"1841":4,"1842":2,"1844":1,"1845":1,"1847":3,"1848":3,"1850":3,"1851":2,"1853":3,"1855":2,"1856":1,"1857":1,"1861":1,"1863":1,"1864":3,"1865":1,"1866":1,"1867":1,"1868":2,"1873":1,"1875":1,"1876":2,"1878":1,"1881":1,"1883":4,"1884":2,"1885":3,"1887":1,"1889":2,"1890":2,"1891":2,"1892":1,"1893":1,"1894":3,"1895":1,"1898":2,"1899":1,"1901":4,"1902":2,"1904":4,"1907":4,"1908":3,"1909":2,"1911":4,"1913":1,"1914":2,"1916":2,"1917":1,"1918":1,"1919":2,"1921":2,"1922":3,"1923":2,"1927":1,"1928":3,"1932":1,"1933":2,"1934":2,"1935":1,"1936":1,"1937":1,"1938":1,"1939":1,"1940":2,"1941":2,"1942":1,"1943":3,"1945":1,"1946":2,"1947":5,"1948":1,"1949":4,"1950":2,"1951":2,"1952":1,"1953":2,"1954":2,"1955":4,"1958":2,"1959":1,"1961":2,"1962":2,"1963":1,"1965":1,"1966":1,"1967":2,"1969":4,"1970":3,"1971":1,"1972":2,"1974":5,"1976":2,"1977":2,"1978":2,"1981":10,"1982":1,"1983":1,"1987":4,"1989":1,"1991":3,"1993":6,"1994":10,"1996":1,"1997":3,"1998":1,"1999":13,"2000":4,"2001":2,"2002":1,"2003":1,"2004":1,"2006":2,"2007":1,"2009":3,"2010":1,"2011":6,"2012":1,"2013":4,"2015":1,"2018":1,"2023":6,"2025":1,"2026":1,"2028":3,"2029":2,"2030":1,"2031":5,"2032":2,"2033":3,"2035":1,"2039":1,"2040":1,"2041":1,"2042":4,"2043":4,"2045":4,"2046":2,"2047":3,"2048":2,"2049":3,"2050":2,"2053":1,"2054":2,"2056":2,"2057":3,"2058":3,"2061":1,"2062":5,"2063":1,"2065":8,"2066":1,"2068":1,"2069":2,"2070":1,"2071":1,"2072":1,"2074":1,"2076":2,"2079":2,"2081":2,"2082":2,"2084":3,"2085":7,"2088":1,"2089":1,"2090":1,"2092":1,"2094":2,"2096":2,"2097":1,"2098":2,"2099":2,"2101":1,"2105":1,"2107":1,"2108":1,"2110":3,"2111":2,"2113":3,"2114":3,"2115":1,"2117":5,"2119":2,"2120":1,"2122":2,"2125":2,"2126":1,"2127":3,"2130":1,"2133":3,"2134":2,"2136":2,"2137":4,"2138":6,"2139":1,"2140":2,"2141":5,"2142":1,"2143":1,"2144":3,"2146":3,"2147":1,"2148":2,"2149":6,"2150":2,"2152":2,"2154":1,"2155":12,"2156":6,"2157":23,"2158":3,"2159":1,"2160":1,"2161":5,"2162":2,"2163":5,"2164":1,"2165":11,"2166":3,"2167":7,"2168":2,"2170":4,"2171":6,"2172":3,"2173":2,"2174":5,"2176":1,"2177":2,"2181":3,"2187":1,"2188":3,"2191":5,"2192":1,"2193":1,"2194":3,"2195":4,"2196":3,"2198":3,"2199":2,"2200":3,"2201":2,"2203":2,"2207":1,"2210":1,"2211":1,"2213":2,"2215":1,"2219":1,"2220":2,"2226":2,"2227":1,"2229":4,"2231":22,"2233":1,"2241":1,"2243":1}}],["angular",{"3":{"2218":1,"2219":5}}],["angles",{"3":{"1358":1,"1435":1}}],["anglesharp",{"2":{"952":1,"956":1,"958":1,"959":1},"3":{"0":1,"952":1,"954":1,"956":1,"958":2,"959":1,"1435":2,"2231":1}}],["angle",{"3":{"1018":2,"1020":1,"1161":1,"1237":1,"1249":1,"1299":1,"1310":2,"1311":1,"1365":1,"1394":1,"1435":2,"1525":1,"2120":1}}],["anemic",{"1":{"1807":1,"1808":1,"1809":1,"1810":1,"1811":1},"3":{"1538":1,"1540":1,"1576":1,"1778":1,"1806":1,"1807":2,"1809":1,"1810":1,"1811":2,"1827":1,"1833":1,"2205":1}}],["anupcasterwhosesourceisanotherupcasterstarget",{"3":{"1435":1}}],["anundecoratedendpointthatisallowlisted",{"3":{"1435":3}}],["aninheritedattributeonthederivedcontroller",{"3":{"1435":3}}],["animates",{"3":{"1416":1}}],["animation",{"3":{"1358":1,"1432":1,"1435":1}}],["anr",{"3":{"1415":1,"1416":1}}],["anabstracthandlerstrandedfromitscontract",{"3":{"1435":3}}],["anabstracthandlerbesideitscontract",{"3":{"1435":3}}],["anabstracthandleroveragenericcontract",{"3":{"1435":3}}],["anaemic",{"3":{"1358":1}}],["anatomy",{"0":{"1351":1}}],["analogous",{"3":{"1322":1}}],["analogue",{"3":{"265":2,"1323":2,"1338":1,"1394":1,"1401":1,"1416":3,"1435":2,"1937":1}}],["analog",{"3":{"135":1,"1706":1,"2219":1}}],["analyser",{"3":{"1435":1}}],["analyses",{"3":{"1349":3,"1600":1}}],["analysismode=all",{"3":{"869":1,"1444":1,"1576":1}}],["analysis",{"1":{"1744":1,"1745":1,"1746":1,"1747":1,"1748":1,"1749":1,"1750":1,"1751":1,"1752":1,"1753":1,"1754":1},"3":{"0":2,"160":1,"360":1,"546":1,"1033":1,"1049":1,"1349":11,"1379":1,"1394":3,"1395":2,"1435":3,"1452":1,"1466":1,"1495":1,"1565":1,"1576":1,"1585":1,"1602":1,"1638":1,"1678":1,"1744":1,"1754":1,"1814":1,"2218":1}}],["analyze",{"3":{"1358":3}}],["analyzed",{"3":{"1247":1,"1626":1}}],["analyzes",{"3":{"1239":1,"1247":1,"1346":1,"1435":1}}],["analyzers",{"2":{"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"495":1,"2088":1},"3":{"0":1,"138":1,"481":1,"483":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":2,"495":1,"633":1,"982":2,"1170":1,"1213":4,"1214":1,"1229":1,"1237":2,"1247":2,"1285":1,"1308":1,"1358":2,"1394":1,"1395":1,"1452":2,"1523":1,"1525":2,"1530":1,"1536":1,"1551":2,"1553":1,"1569":1,"1571":1,"1576":2,"1585":1,"1590":4,"1595":2,"1596":1,"1648":1,"1653":1,"1656":1,"1660":1,"1672":1,"1682":1,"1684":1,"1695":1,"1703":1,"1718":1,"1725":1,"1780":1,"1796":1,"1803":1,"1902":1,"1904":1,"2088":1,"2130":1}}],["analyzer",{"2":{"135":1,"138":1,"1525":1},"3":{"0":3,"135":5,"138":1,"139":1,"140":1,"142":2,"265":1,"454":1,"482":2,"484":2,"486":1,"490":2,"491":2,"492":2,"493":2,"494":1,"497":2,"502":1,"558":1,"591":1,"631":2,"633":4,"776":1,"801":1,"869":1,"899":1,"980":3,"981":1,"1004":1,"1102":1,"1108":1,"1213":1,"1229":1,"1237":2,"1270":1,"1299":5,"1308":1,"1310":3,"1311":3,"1322":2,"1323":5,"1338":7,"1358":3,"1368":1,"1372":1,"1394":2,"1395":2,"1401":1,"1415":3,"1416":3,"1435":6,"1440":1,"1444":1,"1452":2,"1454":1,"1487":1,"1491":1,"1525":1,"1576":3,"1581":1,"1594":1,"1595":1,"1672":3,"1687":1,"1688":1,"1726":1,"1730":1,"1824":2,"1896":1,"2000":1,"2085":1,"2088":2,"2231":1}}],["analytical",{"3":{"0":1,"1012":1,"1358":2,"1394":2,"1525":2,"1544":1,"1576":1}}],["analytics",{"0":{"1358":1},"2":{"1464":1},"3":{"0":2,"395":1,"601":1,"604":1,"609":4,"718":1,"768":1,"914":1,"974":1,"1192":1,"1202":1,"1203":1,"1338":1,"1349":1,"1350":1,"1358":4,"1435":1,"1446":1,"1454":1,"1464":6,"1466":7,"1473":1,"1475":1,"1524":1,"1525":1,"1533":2,"1544":1,"1590":1,"1610":2,"1620":1,"1624":1,"1629":1,"1633":2,"1637":1,"1640":3,"1669":1,"1676":2,"1677":1,"1684":1,"1834":1,"2157":1,"2159":1,"2218":1,"2219":1,"2220":1,"2231":1}}],["ansi",{"3":{"741":1,"1310":2}}],["answerid",{"3":{"1394":6,"1395":1}}],["answering",{"3":{"0":4,"72":1,"99":1,"157":1,"234":1,"237":1,"284":1,"342":1,"353":3,"698":1,"735":1,"758":1,"790":1,"826":1,"827":1,"879":1,"881":1,"906":1,"1075":1,"1099":1,"1237":2,"1270":1,"1285":3,"1292":1,"1299":1,"1308":1,"1310":4,"1322":2,"1323":2,"1327":1,"1338":3,"1349":2,"1358":4,"1372":1,"1377":1,"1379":4,"1394":2,"1395":4,"1414":2,"1416":2,"1417":1,"1435":1,"1440":1,"1473":1,"1475":1,"1525":1,"1599":1,"1669":1,"1794":1,"1908":1,"2169":1,"2174":1}}],["answervaluemaxlength",{"3":{"1349":4,"1358":2,"1368":3,"1395":5}}],["answervalue",{"3":{"1349":23,"1358":39,"1368":3,"1379":16,"1395":5,"1629":3,"1630":2,"1631":2,"1634":3,"1638":1,"1639":2}}],["answerable",{"3":{"39":1,"402":1,"498":1,"1237":1,"1285":2,"1414":1,"1416":1,"1763":1}}],["answered",{"3":{"0":3,"109":1,"126":1,"176":1,"243":1,"286":1,"341":1,"399":1,"639":1,"690":1,"698":1,"741":1,"743":2,"757":1,"759":1,"809":1,"818":1,"854":1,"857":1,"882":1,"896":1,"901":1,"903":1,"905":1,"907":1,"922":1,"930":1,"996":2,"1049":1,"1052":1,"1055":1,"1066":1,"1093":1,"1142":1,"1145":1,"1153":1,"1184":4,"1263":1,"1270":1,"1285":3,"1289":1,"1299":12,"1300":2,"1310":8,"1312":1,"1319":1,"1323":7,"1327":1,"1336":1,"1338":2,"1349":6,"1358":33,"1379":8,"1386":1,"1394":10,"1395":21,"1396":1,"1400":1,"1401":21,"1407":1,"1414":3,"1416":2,"1426":1,"1429":1,"1435":3,"1440":1,"1454":1,"1525":1,"1533":1,"1629":1,"1631":3,"1637":1,"1640":1,"1747":1,"1764":1,"1809":1,"1814":1,"1820":1,"1850":1,"1856":1,"1911":1,"1915":1,"1932":1,"1983":1,"1991":1}}],["answer",{"0":{"93":1,"1422":1,"1789":1,"1804":1,"1809":1,"1814":1,"1848":1,"1864":1,"1870":1,"1888":1,"1894":1,"1901":1,"1926":1,"1943":1,"1961":1,"1967":1,"1974":1,"1981":1,"1987":1,"1993":1,"1999":1,"2062":1,"2082":1,"2116":1,"2125":1,"2130":1,"2135":1,"2141":1,"2148":1,"2155":1,"2162":1,"2171":1,"2183":1,"2195":1},"2":{"1334":1},"3":{"0":6,"26":1,"52":1,"81":1,"93":2,"94":2,"101":1,"126":1,"146":1,"166":2,"185":1,"186":1,"194":1,"202":1,"212":1,"235":2,"245":1,"265":2,"283":1,"284":1,"301":1,"325":1,"340":1,"342":2,"343":1,"344":1,"365":2,"390":1,"391":1,"392":1,"396":1,"434":1,"435":1,"497":1,"535":1,"536":1,"537":1,"539":1,"544":1,"573":2,"574":1,"577":1,"582":1,"583":1,"625":2,"673":1,"688":1,"689":1,"690":5,"693":2,"697":1,"706":1,"714":3,"717":3,"724":1,"725":1,"726":3,"740":1,"741":2,"742":1,"743":1,"756":1,"767":1,"773":1,"781":1,"783":2,"809":2,"813":1,"818":1,"821":1,"827":4,"829":1,"832":1,"833":1,"837":1,"896":2,"898":2,"905":1,"921":1,"926":2,"942":1,"946":1,"947":1,"953":1,"955":1,"963":1,"965":1,"995":1,"996":1,"1016":1,"1018":3,"1019":2,"1021":2,"1025":1,"1026":1,"1028":1,"1035":1,"1041":1,"1043":2,"1049":1,"1066":3,"1074":1,"1089":1,"1091":2,"1092":1,"1093":4,"1115":1,"1117":2,"1123":1,"1124":1,"1126":1,"1132":1,"1153":1,"1154":1,"1175":2,"1177":1,"1178":1,"1220":1,"1222":1,"1228":1,"1229":1,"1232":1,"1235":1,"1237":4,"1239":1,"1247":5,"1248":3,"1250":1,"1253":2,"1259":5,"1263":2,"1265":1,"1267":1,"1270":18,"1271":6,"1273":1,"1285":25,"1295":1,"1299":22,"1300":1,"1306":1,"1307":1,"1308":5,"1309":1,"1310":12,"1311":4,"1312":1,"1317":2,"1319":1,"1322":16,"1323":42,"1327":1,"1330":1,"1331":1,"1334":1,"1336":2,"1337":1,"1338":25,"1339":1,"1343":1,"1345":1,"1347":1,"1348":3,"1349":57,"1350":1,"1351":5,"1352":1,"1356":2,"1358":227,"1359":1,"1361":1,"1365":5,"1368":12,"1371":6,"1372":4,"1373":3,"1379":42,"1381":1,"1394":33,"1395":59,"1401":11,"1404":1,"1405":1,"1407":2,"1408":2,"1413":1,"1414":17,"1416":23,"1417":3,"1421":1,"1422":7,"1423":2,"1425":1,"1426":18,"1430":1,"1434":4,"1435":20,"1437":9,"1440":1,"1446":2,"1447":1,"1448":3,"1454":4,"1455":1,"1464":1,"1466":1,"1475":1,"1481":1,"1486":2,"1491":2,"1495":1,"1525":3,"1576":1,"1584":1,"1628":1,"1629":3,"1630":12,"1631":14,"1634":7,"1637":4,"1638":7,"1639":4,"1652":1,"1655":1,"1666":1,"1669":1,"1671":1,"1672":1,"1680":1,"1701":1,"1707":1,"1717":1,"1719":1,"1721":1,"1726":1,"1727":1,"1728":1,"1789":2,"1814":1,"1818":1,"1820":1,"1838":1,"1846":1,"1853":1,"1858":1,"1869":1,"1873":1,"1876":1,"1887":1,"1890":1,"1891":1,"1892":2,"1901":2,"1904":1,"1907":1,"1908":1,"1909":1,"1910":1,"1911":2,"1925":1,"1932":1,"1933":1,"1939":1,"1940":1,"1946":2,"1947":2,"1953":2,"1962":1,"1966":1,"1969":1,"1971":1,"1979":1,"1980":1,"1981":2,"1984":1,"1991":1,"1995":1,"1996":2,"1999":1,"2000":1,"2001":1,"2023":1,"2034":1,"2035":1,"2054":1,"2062":1,"2066":1,"2068":1,"2077":1,"2142":1,"2143":1,"2153":1,"2158":2,"2159":1,"2162":1,"2166":1,"2167":1,"2169":2,"2174":2,"2176":1,"2178":2,"2180":1,"2182":1,"2184":1,"2190":1,"2193":1}}],["answers",{"3":{"0":13,"24":1,"40":2,"41":1,"81":2,"92":1,"94":1,"96":1,"97":1,"109":1,"110":1,"111":1,"235":2,"236":3,"317":1,"318":1,"320":1,"324":1,"326":1,"333":2,"340":1,"343":1,"344":1,"353":1,"369":1,"499":1,"507":1,"524":1,"545":1,"549":1,"571":2,"583":1,"585":3,"590":1,"650":1,"652":1,"714":1,"725":1,"727":1,"732":1,"741":1,"743":1,"749":1,"767":1,"818":1,"827":5,"828":1,"853":1,"860":1,"876":1,"881":3,"905":3,"907":1,"922":1,"925":1,"926":2,"932":1,"938":1,"946":1,"948":1,"953":1,"956":2,"963":1,"966":1,"998":1,"1016":1,"1018":5,"1020":2,"1021":1,"1022":2,"1024":1,"1028":1,"1034":2,"1041":1,"1059":3,"1066":1,"1067":3,"1082":1,"1091":2,"1092":1,"1099":1,"1103":2,"1116":1,"1132":1,"1142":3,"1175":1,"1176":1,"1178":1,"1183":1,"1184":2,"1185":1,"1228":1,"1229":5,"1231":1,"1237":3,"1238":1,"1247":2,"1250":1,"1256":1,"1259":3,"1265":1,"1270":10,"1271":7,"1279":1,"1282":1,"1285":21,"1289":1,"1293":1,"1296":1,"1299":23,"1300":3,"1304":1,"1307":1,"1308":5,"1310":15,"1311":2,"1312":1,"1321":1,"1322":11,"1323":30,"1327":3,"1331":2,"1336":1,"1338":14,"1341":4,"1342":2,"1343":1,"1345":1,"1346":1,"1348":1,"1349":29,"1351":3,"1354":2,"1356":1,"1358":101,"1365":2,"1368":3,"1371":1,"1372":1,"1374":2,"1379":28,"1383":1,"1385":1,"1387":2,"1389":2,"1390":2,"1394":36,"1395":79,"1397":1,"1401":9,"1407":1,"1410":1,"1413":1,"1414":19,"1415":1,"1416":9,"1420":1,"1422":2,"1425":1,"1426":2,"1429":3,"1431":1,"1434":1,"1435":18,"1437":6,"1440":1,"1446":2,"1452":2,"1453":1,"1466":1,"1487":2,"1489":1,"1533":2,"1534":1,"1566":1,"1576":2,"1626":1,"1629":3,"1630":7,"1631":10,"1634":3,"1637":2,"1638":14,"1639":4,"1640":2,"1650":1,"1665":1,"1669":1,"1707":1,"1719":1,"1747":1,"1766":2,"1768":1,"1769":1,"1793":1,"1805":1,"1818":1,"1820":1,"1838":1,"1862":1,"1869":1,"1891":2,"1895":1,"1901":1,"1908":1,"1919":1,"1921":1,"1922":1,"1926":1,"1930":1,"1933":1,"1946":1,"1961":1,"1990":1,"1991":2,"1994":1,"2023":2,"2028":1,"2035":1,"2055":1,"2062":2,"2072":1,"2108":1,"2109":1,"2140":2,"2148":1,"2155":1,"2166":1,"2175":1,"2179":2,"2182":2,"2189":1,"2198":1,"2199":1}}],["annual",{"3":{"361":1,"1285":1,"1466":1,"1480":1,"2144":1}}],["annoying",{"3":{"2069":1}}],["announcing",{"3":{"1212":1,"1259":1,"1416":3,"2138":1}}],["announceasync",{"3":{"1416":4}}],["announceable",{"3":{"1308":1}}],["announcer",{"3":{"1395":1,"1416":2,"1668":1}}],["announcement",{"3":{"619":1,"620":1,"1323":2,"1358":2,"1395":5,"1409":1,"1414":4,"1416":8,"1525":1,"1531":1,"1641":1,"1643":1,"2115":1}}],["announcements",{"3":{"384":1,"433":1,"1401":2,"1416":7,"1604":1,"1738":1,"1935":1}}],["announce",{"3":{"573":1,"1212":1,"1323":1,"1349":1,"1395":1,"1401":2,"1414":1,"1416":5,"1435":1,"1606":4,"1607":1,"1740":4,"1741":2,"1834":1,"2229":1}}],["announces",{"3":{"0":1,"109":1,"1322":2,"1349":4,"1394":1,"1395":2,"1401":1,"1414":1,"1416":3,"1606":1,"1607":2,"1740":1,"1741":3,"1869":1,"1987":1}}],["announced",{"3":{"0":2,"170":1,"790":2,"1074":1,"1118":1,"1231":1,"1259":1,"1308":1,"1310":1,"1315":1,"1323":1,"1358":2,"1379":3,"1391":1,"1395":1,"1401":1,"1416":1,"1435":1,"1557":1,"1560":1,"1606":3,"1607":6,"1630":1,"1642":1,"1643":2,"1740":3,"1741":13,"2090":1,"2165":1}}],["annotates",{"3":{"166":1,"305":1}}],["annotate",{"3":{"135":2,"1285":1,"1310":1,"1435":2}}],["annotated",{"3":{"0":1,"135":1,"250":1,"251":1,"252":1,"254":1,"256":1,"257":1,"258":2,"259":1,"305":1,"324":1,"343":1,"725":1,"1243":1,"1259":1,"1270":1,"1281":1,"1285":2,"1299":1,"1308":1,"1323":1,"1349":1,"1358":5,"1394":2,"1395":3,"1435":2,"1457":1,"1576":1,"1732":1,"2137":1}}],["annotations",{"3":{"318":1,"655":1,"657":1,"674":2,"675":1,"676":1,"1270":1,"1285":5,"1299":1,"1322":4,"1323":2,"1338":5,"1383":1,"1394":4,"1395":1,"1414":1,"1419":1,"1426":1,"1435":2,"1584":1,"1804":1,"2011":2}}],["annotation",{"3":{"0":2,"158":1,"160":1,"170":1,"318":1,"626":1,"627":1,"659":1,"674":1,"676":2,"1083":1,"1084":1,"1279":1,"1280":2,"1285":9,"1299":2,"1309":1,"1310":4,"1319":1,"1322":2,"1323":1,"1338":2,"1349":2,"1394":3,"1395":2,"1415":1,"1426":1,"1435":3,"1437":1,"1454":1,"1640":1,"1669":1,"1993":1,"2063":2,"2231":1}}],["annotating",{"3":{"0":1,"160":1,"1285":1,"1322":1}}],["ancestry",{"3":{"526":1,"530":1,"531":1,"1453":1}}],["ancestorfullname",{"3":{"1435":1}}],["ancestor",{"3":{"402":1,"530":1,"1196":1,"1332":2,"1338":3,"1416":1,"1435":5,"1437":2,"1442":1,"1453":1,"2156":1}}],["ancestors",{"3":{"214":1,"219":2,"572":1,"1323":2,"1338":2,"1435":5,"1437":2,"1487":1,"1524":1,"1576":1,"1590":2,"1599":1,"2054":1,"2146":1,"2147":1,"2148":1,"2149":2}}],["anchortype",{"3":{"1435":1}}],["anchoring",{"3":{"1358":1,"1379":1,"1414":1,"1435":4}}],["anchor",{"0":{"2102":1},"3":{"34":1,"57":1,"139":2,"203":1,"250":1,"251":2,"252":4,"254":2,"256":2,"257":2,"258":2,"259":2,"260":1,"261":1,"276":1,"306":1,"323":2,"324":1,"354":1,"366":2,"392":1,"413":1,"415":1,"418":1,"420":1,"423":1,"424":2,"425":1,"426":2,"428":1,"429":1,"430":1,"464":2,"465":2,"492":2,"493":1,"495":1,"543":2,"547":1,"609":1,"631":2,"644":1,"747":1,"752":1,"761":1,"833":1,"876":1,"917":1,"949":1,"991":1,"994":1,"1122":1,"1127":1,"1184":1,"1263":1,"1308":3,"1310":17,"1311":1,"1318":2,"1322":21,"1323":10,"1340":1,"1349":2,"1353":1,"1358":9,"1368":1,"1376":2,"1379":6,"1395":9,"1401":1,"1403":2,"1414":6,"1416":19,"1430":1,"1435":34,"1448":1,"1466":1,"1488":2,"1495":8,"1497":1,"1499":1,"1598":2,"1640":1,"1683":1,"1685":1,"1728":1,"1749":1,"1757":1,"1758":1,"1772":1,"1975":1,"2102":2,"2107":2,"2110":1,"2120":2,"2121":1}}],["anchored",{"0":{"137":1,"138":1,"139":1},"3":{"0":2,"34":1,"112":1,"130":2,"137":3,"138":1,"139":4,"140":1,"141":1,"142":1,"171":1,"249":1,"254":1,"324":1,"354":2,"375":1,"378":1,"393":1,"446":2,"453":1,"468":2,"475":1,"476":1,"477":1,"478":1,"480":1,"486":1,"501":1,"514":1,"534":5,"540":2,"543":2,"550":1,"552":1,"554":3,"559":1,"567":1,"586":1,"589":1,"594":1,"597":3,"604":1,"607":5,"613":2,"621":1,"624":3,"629":1,"631":1,"636":1,"638":3,"647":1,"652":1,"669":1,"672":4,"677":1,"680":2,"685":1,"696":4,"703":1,"723":1,"744":1,"764":3,"769":1,"777":1,"780":5,"794":1,"806":1,"841":1,"844":1,"849":1,"867":1,"877":1,"895":1,"909":1,"917":1,"927":1,"929":1,"934":1,"949":1,"1007":1,"1021":1,"1045":1,"1055":2,"1078":1,"1111":1,"1119":1,"1146":1,"1323":1,"1346":1,"1349":1,"1363":1,"1368":1,"1423":1,"1435":6,"1495":2,"1660":1,"1683":1,"1728":2,"1983":1,"2000":1,"2231":1}}],["anchors",{"0":{"1318":1},"3":{"0":2,"43":1,"63":1,"94":1,"98":1,"104":3,"128":1,"137":1,"138":2,"139":2,"140":2,"142":3,"181":1,"203":1,"220":2,"231":1,"241":2,"249":1,"250":3,"251":3,"252":5,"254":4,"256":4,"257":4,"258":3,"259":7,"260":1,"261":1,"268":1,"285":1,"322":1,"324":1,"326":3,"336":1,"337":1,"345":2,"354":1,"364":1,"375":1,"386":1,"393":1,"395":1,"409":1,"418":4,"424":1,"425":2,"426":1,"428":2,"429":1,"463":1,"464":1,"465":1,"466":2,"490":3,"491":1,"492":1,"493":2,"497":3,"502":2,"522":1,"526":1,"543":3,"551":1,"552":2,"589":1,"604":1,"609":2,"611":1,"613":1,"616":3,"644":2,"655":2,"660":2,"669":1,"696":2,"703":5,"713":1,"720":2,"731":1,"736":1,"747":1,"749":1,"755":2,"761":1,"814":1,"825":1,"850":1,"875":1,"877":1,"879":1,"884":1,"912":1,"927":2,"958":1,"960":1,"967":1,"983":1,"986":1,"991":1,"994":1,"999":1,"1021":1,"1029":1,"1062":1,"1065":1,"1070":1,"1136":1,"1193":1,"1308":1,"1310":2,"1312":1,"1323":2,"1349":1,"1358":2,"1376":1,"1416":2,"1435":7,"1466":3,"1475":1,"1487":1,"1495":5,"1590":1,"2045":2,"2085":1}}],["anthropiccontentblock",{"3":{"1495":1}}],["anthropicclient",{"2":{"1419":1,"2171":1},"3":{"1419":1,"1426":2,"2171":1}}],["anthropicmessage",{"3":{"1495":1}}],["anthropicresponse",{"3":{"1495":1}}],["anthropicrequest",{"3":{"1495":1}}],["anthropicusage",{"3":{"1495":2}}],["anthropicjsonschemaformat",{"3":{"1495":2}}],["anthropicoutputconfig",{"3":{"1495":2}}],["anthropics",{"3":{"1458":1}}],["anthropicscoringservice",{"3":{"0":1,"1368":2,"1426":4,"1441":2,"1495":5,"2178":1}}],["anthropicaiservicecollectionextensions",{"3":{"1199":1,"1203":1,"1207":1,"1419":1,"1426":4}}],["anthropicaiproviderfactory",{"3":{"74":1,"1091":2,"1092":1,"1199":3,"1203":1,"1207":2,"1419":2,"1426":16}}],["anthropic",{"2":{"142":1,"1020":1,"1089":1,"1091":1,"1093":2,"1211":1,"1213":1,"1417":1,"1454":1,"1465":1,"1469":1,"1471":1,"1491":1,"1502":1,"2177":1},"3":{"0":2,"74":1,"142":1,"598":1,"599":1,"980":2,"983":1,"995":1,"1012":1,"1017":4,"1020":2,"1089":1,"1090":1,"1091":4,"1093":2,"1094":1,"1095":1,"1192":1,"1199":2,"1203":2,"1206":1,"1207":6,"1208":2,"1211":1,"1213":2,"1349":1,"1358":5,"1375":1,"1379":1,"1417":1,"1419":3,"1426":16,"1435":1,"1437":4,"1454":2,"1465":2,"1466":6,"1469":1,"1471":1,"1473":1,"1485":1,"1491":3,"1495":3,"1502":1,"2177":1,"2178":1,"2189":1,"2226":1}}],["anticipated",{"3":{"827":1,"1358":2,"1435":1,"1803":1,"1945":1}}],["anticipate",{"3":{"812":1,"1270":1,"1416":1}}],["anticipates",{"3":{"241":1,"245":1,"974":1,"1358":1,"1399":1}}],["antiforgery",{"3":{"0":1,"207":2,"664":3,"665":2,"670":1,"1184":1,"1187":1,"1212":1,"1299":3,"1310":2,"1335":2,"1338":4,"1441":1,"1466":2,"1525":1,"1968":2,"1969":2,"2231":1}}],["anti",{"3":{"0":3,"51":1,"53":2,"557":1,"591":1,"592":1,"688":1,"690":2,"691":1,"692":1,"854":1,"855":1,"859":1,"871":1,"1012":1,"1018":1,"1019":1,"1020":1,"1212":1,"1293":1,"1299":6,"1310":1,"1311":3,"1322":1,"1323":8,"1325":2,"1338":4,"1357":1,"1358":3,"1368":1,"1394":3,"1395":12,"1401":1,"1414":1,"1416":1,"1432":2,"1435":8,"1437":5,"1443":2,"1456":1,"1488":4,"1491":2,"1492":1,"1525":1,"1531":1,"1543":1,"1545":1,"1547":1,"1575":1,"1576":1,"1585":1,"1590":1,"1598":1,"1630":1,"1638":1,"1640":2,"1653":1,"1662":1,"1766":1,"1831":1,"2046":1,"2231":1}}],["anomalies",{"3":{"1358":1}}],["anomalous",{"3":{"1300":1}}],["anomaly",{"3":{"790":1,"1310":1,"1435":1}}],["anon",{"3":{"157":2,"161":1,"1310":1,"1907":1}}],["anonymizability",{"3":{"1670":1}}],["anonymizable",{"3":{"718":2,"1763":1}}],["anonymization",{"3":{"0":1,"37":1,"41":1,"42":4,"1201":1,"1237":2,"1322":4,"1414":2,"1437":2,"1566":1,"1639":1,"1671":1,"1764":1,"1783":1,"1902":2,"2061":1,"2062":1,"2063":1,"2067":1,"2220":1,"2231":1}}],["anonymizing",{"3":{"318":1,"326":1,"1142":1,"1763":1,"1766":1,"2065":1}}],["anonymizes",{"3":{"358":1,"716":1,"897":1,"1395":1,"1576":1,"1746":1,"1747":1,"1750":1,"1764":2,"1768":1,"2140":1}}],["anonymized",{"3":{"38":1,"42":1,"444":1,"1237":1,"1322":2,"1401":2,"1409":3,"1414":5,"1435":2,"1764":2,"2064":1,"2068":1}}],["anonymize",{"2":{"42":1,"355":1,"497":1,"718":1,"2062":1,"2063":1},"3":{"0":2,"39":1,"40":1,"42":1,"350":1,"355":1,"440":1,"497":2,"499":2,"690":1,"694":1,"718":1,"724":1,"725":1,"897":1,"1212":2,"1236":2,"1237":7,"1299":5,"1322":6,"1395":1,"1405":1,"1409":3,"1414":11,"1435":5,"1437":1,"1525":2,"1534":1,"1566":1,"1571":1,"1576":2,"1589":1,"1630":1,"1671":1,"1766":1,"1768":2,"2062":6,"2063":1,"2068":2,"2125":1,"2229":1}}],["anonymity",{"3":{"0":1,"184":1,"189":1,"446":1,"448":1,"1299":1,"1338":1,"1379":1,"1401":2,"1435":1,"2130":1}}],["anonymoususer",{"3":{"1435":2}}],["anonymousendpointsfortest",{"3":{"1435":3}}],["anonymousendpoints",{"3":{"1435":6}}],["anonymousendpointsof",{"3":{"1435":3}}],["anonymousendpointtests",{"3":{"1199":2,"1207":4,"1310":2,"1323":2,"1379":2,"1435":18,"1488":3,"1525":1,"1533":1,"1534":1,"1590":2}}],["anonymousendpointtestsbasetests",{"3":{"1198":1,"1199":7,"1207":14,"1435":44,"1488":1,"1495":1}}],["anonymousendpointtestsbase",{"2":{"189":1,"1585":1,"1897":1},"3":{"0":1,"189":2,"1199":10,"1207":2,"1435":38,"1585":1,"1897":1}}],["anonymousstate",{"3":{"1323":1}}],["anonymoussessionassettests",{"3":{"1199":1,"1207":2}}],["anonymously",{"3":{"1259":1,"1287":1,"1297":1,"1299":2,"1310":2,"1401":5,"1407":1,"1431":1,"1466":1,"1625":1,"1628":1,"1629":1,"1757":1}}],["anonymousauthenticationstateprovider",{"3":{"1495":2}}],["anonymousauthedgecasetests",{"3":{"1199":1,"1207":2}}],["anonymousauthtests",{"3":{"1199":1,"1207":2,"1610":1}}],["anonymousaccessdeniedtests",{"3":{"1199":2,"1207":4}}],["anonymousbookmarkaccessdeniedtests",{"3":{"1199":1,"1207":2}}],["anonymousconferencereadtests",{"2":{"1610":1},"3":{"1199":1,"1207":2,"1610":1}}],["anonymouscurrentuserservice",{"3":{"1199":2,"1207":1}}],["anonymousfixturecontroller",{"3":{"1199":3,"1207":1,"1435":9}}],["anonymouspartition",{"2":{"178":1},"3":{"178":1,"1310":5,"1999":1}}],["anonymoushubpermitlimit",{"2":{"175":1,"179":1},"3":{"0":1,"175":1,"179":1,"1310":5,"1999":1}}],["anonymous",{"0":{"1406":1,"1621":1,"1757":1},"2":{"57":1,"825":1},"3":{"0":15,"31":1,"33":1,"37":1,"42":3,"57":1,"59":2,"159":1,"173":5,"174":2,"175":9,"176":3,"177":4,"178":3,"179":3,"181":4,"189":1,"233":1,"242":1,"243":2,"244":3,"247":2,"265":2,"273":1,"279":1,"280":2,"287":2,"303":1,"306":1,"333":1,"349":1,"387":2,"390":1,"391":2,"403":1,"420":2,"441":1,"459":2,"464":1,"507":1,"527":1,"577":1,"618":1,"649":1,"748":1,"749":2,"757":3,"759":1,"789":1,"790":1,"792":1,"793":2,"794":1,"825":1,"826":4,"827":4,"828":1,"830":1,"834":1,"853":1,"954":2,"956":1,"960":1,"1059":1,"1115":1,"1116":2,"1118":2,"1184":1,"1207":12,"1212":4,"1246":1,"1253":1,"1259":1,"1264":1,"1270":2,"1275":1,"1278":1,"1285":2,"1293":1,"1299":18,"1310":42,"1322":8,"1323":30,"1331":2,"1336":1,"1338":10,"1342":1,"1343":1,"1349":12,"1358":24,"1371":1,"1374":2,"1379":23,"1380":2,"1388":1,"1390":1,"1394":16,"1395":13,"1401":4,"1406":3,"1407":1,"1413":1,"1414":19,"1415":2,"1416":1,"1431":1,"1432":1,"1433":1,"1435":59,"1437":10,"1446":5,"1454":1,"1455":1,"1466":6,"1487":1,"1488":1,"1490":2,"1525":2,"1590":3,"1595":3,"1596":1,"1607":1,"1610":2,"1620":1,"1622":1,"1626":1,"1630":4,"1639":7,"1640":2,"1642":1,"1665":1,"1670":1,"1672":1,"1702":1,"1741":2,"1747":4,"1748":1,"1749":1,"1756":2,"1757":1,"1758":1,"1760":2,"1764":3,"1767":7,"1770":1,"1772":1,"1774":1,"1824":3,"1897":1,"1910":1,"1921":4,"1962":1,"1965":1,"1996":1,"1997":5,"1998":4,"1999":6,"2000":6,"2001":5,"2006":1,"2023":3,"2027":1,"2035":1,"2065":1,"2082":2,"2124":1,"2126":1,"2165":2,"2167":1,"2192":1,"2195":1,"2231":2}}],["another",{"3":{"0":3,"21":1,"24":1,"47":1,"126":1,"131":1,"136":2,"137":1,"157":1,"159":1,"164":1,"168":1,"180":1,"187":1,"188":1,"225":1,"241":1,"247":1,"255":1,"318":1,"376":1,"386":1,"387":1,"434":1,"459":1,"566":1,"571":1,"634":1,"640":1,"650":2,"658":1,"699":1,"702":1,"709":1,"725":1,"727":1,"733":2,"735":1,"768":1,"792":1,"799":1,"827":1,"840":1,"862":2,"881":2,"905":4,"906":2,"907":1,"948":1,"972":1,"974":2,"1019":1,"1051":1,"1055":1,"1061":2,"1124":1,"1128":1,"1170":1,"1184":1,"1185":1,"1212":1,"1229":2,"1231":1,"1232":1,"1233":1,"1237":6,"1240":1,"1241":1,"1242":2,"1246":1,"1247":8,"1249":1,"1253":3,"1259":7,"1263":2,"1264":1,"1268":1,"1270":17,"1276":2,"1280":1,"1282":2,"1285":24,"1289":2,"1292":2,"1299":15,"1300":1,"1302":1,"1307":1,"1308":3,"1309":2,"1310":10,"1311":1,"1316":1,"1317":1,"1322":9,"1323":4,"1338":4,"1349":6,"1358":26,"1361":1,"1368":2,"1378":1,"1379":10,"1388":1,"1394":8,"1395":21,"1401":12,"1407":1,"1408":1,"1411":1,"1414":20,"1416":3,"1426":1,"1435":41,"1437":5,"1440":2,"1454":1,"1464":1,"1466":2,"1472":2,"1474":1,"1486":1,"1488":6,"1491":2,"1533":1,"1540":1,"1630":2,"1649":1,"1650":2,"1668":1,"1685":1,"1701":1,"1726":1,"1747":1,"1748":1,"1764":1,"1788":1,"1820":2,"1822":1,"1840":1,"1848":2,"1850":1,"1851":1,"1871":1,"1879":1,"1907":1,"1910":1,"1919":2,"1921":1,"1922":1,"1923":1,"1924":1,"1931":1,"1936":1,"1947":2,"1960":1,"1969":1,"1981":2,"1985":1,"1994":2,"1996":1,"2001":1,"2020":1,"2043":2,"2073":1,"2081":1,"2097":1,"2110":1,"2117":1,"2162":2,"2197":1,"2198":1,"2201":1}}],["anymore",{"3":{"1285":1}}],["anybody",{"3":{"1083":1,"1092":1,"1280":1,"1322":2,"1358":1,"2001":1,"2042":1,"2179":1}}],["anyone",{"3":{"0":3,"24":1,"224":1,"281":1,"284":1,"358":1,"441":1,"528":1,"530":1,"539":1,"633":1,"634":1,"689":1,"691":2,"692":1,"727":1,"735":1,"743":1,"805":1,"825":1,"905":1,"907":1,"914":1,"924":1,"926":1,"933":1,"938":1,"939":1,"971":1,"1042":1,"1044":1,"1116":1,"1118":2,"1232":1,"1237":1,"1270":1,"1285":4,"1289":1,"1299":4,"1300":1,"1322":2,"1341":1,"1349":1,"1358":6,"1379":1,"1388":1,"1395":5,"1401":2,"1405":1,"1415":2,"1435":6,"1453":1,"1466":4,"1480":1,"1483":1,"1637":1,"1639":1,"1640":1,"1764":1,"1807":2,"1873":1,"1892":1,"1930":1,"1937":1,"1949":1,"1981":1,"1994":2,"2044":1,"2098":1,"2123":1,"2126":1,"2139":1,"2190":1,"2191":1,"2215":1}}],["anything",{"0":{"1695":1},"3":{"0":2,"21":1,"54":1,"59":1,"109":1,"125":1,"136":1,"170":1,"201":1,"218":1,"225":1,"235":1,"265":1,"284":1,"291":1,"333":1,"335":1,"340":1,"350":1,"353":1,"370":1,"373":1,"380":2,"384":1,"402":1,"407":1,"415":1,"434":1,"439":1,"442":1,"518":1,"524":1,"546":1,"558":2,"566":1,"583":1,"585":1,"592":2,"611":1,"620":1,"626":1,"633":1,"642":1,"651":1,"659":1,"665":1,"676":1,"681":1,"696":1,"715":1,"717":1,"725":1,"731":1,"749":1,"751":1,"756":2,"766":1,"768":1,"774":1,"776":1,"790":1,"793":1,"821":1,"826":2,"827":1,"876":2,"897":1,"904":1,"907":1,"922":1,"926":1,"931":1,"938":1,"982":1,"988":1,"990":2,"995":1,"1018":1,"1026":1,"1048":1,"1052":1,"1066":1,"1075":1,"1091":2,"1093":1,"1100":2,"1101":1,"1118":1,"1124":1,"1126":2,"1129":1,"1133":1,"1134":1,"1175":2,"1184":1,"1193":1,"1212":1,"1228":2,"1229":5,"1233":1,"1237":5,"1243":1,"1244":1,"1247":3,"1251":1,"1259":8,"1268":1,"1270":9,"1271":1,"1284":1,"1285":31,"1288":1,"1289":1,"1292":1,"1299":17,"1300":4,"1303":2,"1306":1,"1307":1,"1308":2,"1310":24,"1311":4,"1322":29,"1323":27,"1325":1,"1326":2,"1331":2,"1338":20,"1342":2,"1349":9,"1351":1,"1358":39,"1361":1,"1368":4,"1378":3,"1379":2,"1384":1,"1390":1,"1394":20,"1395":17,"1401":5,"1404":1,"1407":1,"1414":12,"1415":7,"1416":8,"1417":1,"1420":1,"1422":1,"1423":1,"1426":6,"1427":1,"1432":1,"1434":1,"1435":20,"1437":5,"1441":1,"1442":1,"1444":1,"1446":1,"1454":4,"1455":2,"1456":1,"1458":2,"1459":2,"1465":1,"1466":2,"1471":1,"1472":1,"1474":1,"1481":1,"1489":1,"1491":1,"1512":1,"1590":1,"1630":2,"1631":1,"1651":1,"1652":2,"1661":1,"1665":1,"1667":1,"1675":1,"1676":1,"1682":1,"1684":1,"1701":1,"1703":1,"1718":2,"1728":1,"1768":1,"1775":1,"1804":1,"1823":2,"1839":1,"1846":1,"1855":1,"1858":1,"1868":1,"1873":1,"1885":1,"1891":1,"1893":1,"1907":1,"1923":1,"1932":2,"1933":1,"1939":1,"1940":1,"1944":1,"1958":1,"1965":1,"1971":1,"1973":1,"1979":1,"1981":1,"1999":1,"2005":1,"2007":1,"2009":1,"2023":1,"2033":1,"2038":1,"2048":1,"2060":1,"2088":1,"2108":1,"2120":1,"2125":2,"2141":1,"2144":1,"2148":1,"2155":1,"2157":1,"2164":1,"2171":1,"2178":1,"2179":1,"2182":2,"2184":1,"2191":1,"2192":1,"2193":1,"2202":1}}],["anyway",{"0":{"1226":1,"1719":1},"3":{"0":2,"135":1,"148":1,"201":1,"538":1,"539":1,"648":1,"691":1,"733":1,"743":1,"766":2,"826":1,"827":1,"839":1,"853":1,"869":1,"883":1,"941":1,"1027":1,"1178":1,"1214":1,"1252":1,"1254":1,"1259":3,"1270":1,"1285":5,"1299":1,"1308":2,"1310":4,"1322":1,"1323":2,"1338":1,"1358":4,"1383":1,"1394":2,"1395":4,"1405":1,"1414":2,"1415":1,"1416":5,"1426":1,"1435":7,"1454":1,"1466":1,"1581":1,"1653":1,"1683":1,"1728":1,"1820":1,"1839":2,"1900":1,"1904":1,"1908":1,"1921":1,"1942":1,"1945":1,"1946":2,"1951":1,"1953":1,"1965":1,"1999":1,"2023":1,"2076":1,"2100":1,"2191":1,"2193":1}}],["anywhere",{"0":{"94":1},"3":{"0":5,"81":1,"127":1,"170":2,"237":2,"324":1,"343":2,"373":1,"413":1,"422":1,"471":1,"477":1,"483":1,"490":1,"493":1,"529":1,"556":2,"564":1,"566":1,"591":1,"633":1,"635":1,"638":1,"642":1,"665":1,"676":1,"751":1,"757":1,"790":1,"797":1,"804":2,"827":1,"837":2,"868":1,"963":1,"974":1,"1050":2,"1075":1,"1082":1,"1134":1,"1141":1,"1183":1,"1229":3,"1237":1,"1244":1,"1247":1,"1270":1,"1271":1,"1285":9,"1299":3,"1305":1,"1308":4,"1322":2,"1323":8,"1338":2,"1356":1,"1358":13,"1368":3,"1379":3,"1394":4,"1395":10,"1401":2,"1413":1,"1414":8,"1416":1,"1422":1,"1430":1,"1435":12,"1440":1,"1454":1,"1464":2,"1473":1,"1475":2,"1491":1,"1495":3,"1525":2,"1576":1,"1583":1,"1625":1,"1653":1,"1683":1,"1725":1,"1728":1,"1803":1,"1809":1,"1841":1,"1934":1,"2045":1,"2120":1,"2128":1,"2183":1,"2196":1,"2198":1,"2200":1}}],["anyasync",{"3":{"0":1,"549":1,"550":1,"552":1,"1259":1,"1285":6,"1814":1}}],["any",{"1":{"771":1,"772":1,"773":1,"774":1,"775":1,"776":1,"777":1,"778":1},"3":{"0":32,"21":1,"30":1,"38":1,"39":3,"42":1,"53":1,"54":1,"59":1,"71":2,"72":1,"95":2,"96":1,"98":1,"100":1,"102":1,"109":1,"122":1,"125":1,"126":2,"132":1,"135":1,"137":1,"150":2,"155":1,"159":1,"160":1,"170":1,"171":1,"175":3,"177":2,"178":2,"180":2,"186":2,"193":1,"196":1,"200":1,"201":2,"217":1,"219":1,"243":3,"248":1,"259":2,"265":5,"284":1,"290":2,"300":1,"303":1,"305":1,"310":1,"318":4,"330":2,"333":1,"341":2,"342":1,"343":1,"358":1,"359":4,"361":2,"365":3,"370":3,"372":1,"374":1,"380":1,"384":1,"386":1,"387":2,"388":1,"397":1,"402":1,"405":1,"412":1,"415":1,"418":1,"427":1,"428":1,"434":1,"438":1,"448":2,"449":1,"459":2,"460":1,"461":1,"463":1,"465":2,"468":1,"469":1,"470":2,"477":1,"481":1,"486":1,"487":1,"497":1,"500":1,"507":1,"527":2,"536":1,"538":1,"540":1,"543":1,"545":2,"549":1,"550":2,"556":1,"564":1,"566":1,"573":1,"576":1,"583":4,"586":1,"597":1,"599":2,"601":1,"602":1,"609":1,"611":1,"618":2,"626":3,"627":1,"633":1,"648":1,"657":1,"672":1,"698":2,"700":1,"702":1,"707":2,"708":1,"709":1,"715":1,"723":1,"725":2,"733":1,"740":2,"741":3,"749":5,"750":1,"751":3,"757":2,"758":1,"766":2,"768":2,"771":1,"773":2,"774":1,"775":1,"776":3,"782":1,"790":3,"798":1,"799":1,"800":2,"801":1,"802":1,"803":2,"804":1,"809":1,"813":1,"818":1,"819":2,"826":2,"827":3,"833":1,"838":2,"845":1,"853":1,"869":1,"872":2,"881":2,"883":1,"891":2,"897":1,"904":1,"905":2,"906":1,"912":1,"921":1,"931":2,"939":1,"944":1,"945":1,"946":1,"948":1,"954":1,"963":1,"964":2,"971":1,"972":2,"979":1,"980":2,"981":1,"982":2,"986":1,"987":1,"988":1,"991":1,"1004":1,"1011":1,"1014":2,"1018":7,"1025":1,"1026":2,"1033":2,"1050":3,"1051":2,"1052":1,"1055":1,"1059":1,"1061":1,"1066":2,"1067":2,"1068":1,"1075":1,"1083":2,"1089":1,"1090":1,"1091":1,"1093":4,"1107":2,"1108":1,"1111":1,"1116":3,"1117":1,"1124":1,"1133":1,"1135":1,"1160":1,"1163":1,"1168":3,"1169":1,"1175":2,"1179":1,"1184":6,"1191":1,"1196":1,"1212":3,"1214":1,"1217":1,"1218":1,"1223":1,"1224":1,"1227":1,"1229":4,"1231":2,"1236":1,"1237":15,"1240":2,"1241":5,"1243":1,"1244":1,"1245":1,"1247":18,"1249":1,"1252":1,"1255":1,"1259":14,"1270":20,"1271":11,"1275":1,"1276":1,"1278":5,"1280":2,"1283":1,"1285":74,"1290":1,"1293":1,"1295":1,"1296":1,"1299":62,"1300":7,"1301":1,"1303":1,"1305":1,"1306":1,"1307":1,"1308":21,"1309":2,"1310":47,"1311":2,"1314":3,"1317":1,"1319":1,"1322":48,"1323":77,"1331":1,"1332":1,"1334":1,"1335":4,"1337":3,"1338":41,"1342":2,"1344":1,"1346":1,"1349":20,"1351":2,"1352":1,"1356":1,"1357":2,"1358":108,"1361":1,"1363":1,"1365":2,"1368":11,"1370":1,"1371":1,"1372":3,"1375":1,"1379":14,"1382":1,"1388":1,"1391":2,"1394":41,"1395":61,"1397":1,"1401":22,"1404":1,"1405":1,"1407":1,"1410":1,"1413":1,"1414":24,"1415":10,"1416":61,"1418":1,"1421":3,"1422":2,"1423":1,"1425":1,"1426":11,"1428":1,"1429":1,"1430":7,"1435":132,"1437":9,"1438":1,"1440":2,"1441":3,"1442":7,"1443":1,"1444":3,"1446":1,"1447":1,"1450":1,"1452":1,"1453":3,"1454":10,"1455":3,"1456":1,"1458":1,"1459":1,"1462":1,"1464":1,"1465":1,"1466":18,"1467":1,"1470":1,"1471":1,"1472":2,"1473":2,"1474":1,"1475":1,"1477":2,"1480":2,"1483":1,"1487":7,"1488":3,"1489":2,"1490":1,"1491":1,"1495":1,"1507":1,"1520":1,"1524":1,"1525":7,"1533":1,"1534":1,"1535":1,"1543":1,"1544":1,"1547":1,"1552":2,"1571":1,"1574":1,"1575":1,"1576":4,"1584":2,"1588":2,"1590":7,"1595":3,"1598":1,"1608":1,"1609":1,"1610":2,"1625":1,"1626":1,"1630":3,"1631":6,"1633":1,"1634":2,"1635":1,"1637":1,"1638":2,"1639":1,"1640":13,"1643":1,"1647":2,"1648":1,"1652":2,"1660":1,"1661":1,"1662":2,"1663":1,"1665":1,"1666":2,"1667":1,"1677":1,"1681":1,"1685":2,"1687":1,"1689":1,"1697":1,"1709":1,"1713":1,"1722":1,"1726":3,"1730":1,"1733":1,"1742":1,"1743":1,"1747":1,"1749":1,"1759":1,"1760":1,"1764":4,"1766":1,"1767":3,"1768":1,"1770":1,"1772":1,"1775":1,"1780":1,"1789":1,"1792":1,"1793":1,"1798":1,"1801":2,"1806":2,"1814":2,"1816":2,"1821":1,"1824":1,"1826":1,"1827":1,"1830":1,"1833":2,"1835":1,"1837":1,"1839":2,"1842":1,"1845":1,"1848":2,"1850":1,"1852":1,"1856":1,"1861":1,"1867":1,"1870":1,"1872":1,"1874":1,"1876":1,"1880":2,"1882":1,"1885":1,"1888":1,"1891":1,"1897":2,"1898":1,"1901":3,"1902":1,"1904":2,"1909":2,"1911":2,"1915":1,"1917":1,"1918":1,"1921":2,"1922":1,"1923":2,"1928":1,"1929":2,"1932":4,"1935":1,"1936":1,"1940":2,"1944":2,"1949":1,"1958":1,"1961":1,"1962":1,"1964":2,"1971":1,"1972":1,"1974":1,"1977":1,"1978":1,"1984":1,"1987":2,"1989":1,"1990":2,"1993":3,"1996":2,"1999":2,"2001":1,"2005":1,"2008":1,"2013":1,"2015":1,"2016":1,"2020":1,"2024":3,"2027":1,"2031":1,"2037":1,"2038":1,"2042":1,"2044":2,"2048":1,"2056":1,"2059":1,"2062":1,"2063":2,"2066":1,"2069":1,"2073":2,"2079":1,"2081":1,"2082":1,"2084":1,"2085":1,"2090":1,"2096":1,"2100":1,"2106":2,"2107":1,"2122":1,"2127":2,"2130":2,"2131":2,"2132":1,"2133":1,"2138":1,"2139":1,"2141":3,"2142":2,"2145":2,"2149":1,"2152":2,"2159":1,"2163":1,"2165":1,"2166":1,"2168":3,"2170":2,"2178":1,"2179":1,"2199":1,"2200":1,"2202":2,"2231":1,"2238":1}}],["an",{"0":{"1218":1,"1224":1,"1233":1,"1250":1,"1274":1,"1279":1,"1304":1,"1685":1,"1857":1,"1870":1,"1934":1,"1961":1,"1967":1,"1987":1,"1999":1,"2053":1,"2073":1,"2135":1,"2165":1,"2188":1},"1":{"172":1,"173":1,"174":1,"175":1,"176":1,"177":1,"178":1,"179":1,"180":1,"181":1,"182":1,"240":1,"241":1,"242":1,"243":1,"244":1,"245":1,"246":1,"247":1,"248":1,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"260":1,"261":1,"606":1,"607":1,"608":1,"609":1,"610":1,"611":1,"612":1,"613":1,"614":1,"730":1,"731":1,"732":1,"733":1,"734":1,"735":1,"736":1,"737":1,"771":1,"772":1,"773":1,"774":1,"775":1,"776":1,"777":1,"778":1,"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1,"1001":1,"1002":1,"1003":1,"1004":1,"1005":1,"1006":1,"1007":1,"1008":1,"1047":1,"1048":1,"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1,"1080":1,"1081":1,"1082":1,"1083":1,"1084":1,"1085":1,"1086":1,"1087":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1,"1725":1,"1726":1,"1727":1,"1728":1,"1729":1,"1730":1,"1834":1,"1835":1,"1836":1,"1837":1,"1838":1,"1839":1,"1840":1,"1841":1,"1842":1,"1843":1,"1844":1,"1845":1,"1886":1,"1887":1,"1888":1,"1889":1,"1890":1,"1891":1},"3":{"0":231,"1":1,"10":1,"15":3,"17":1,"18":1,"19":1,"20":4,"21":3,"22":2,"23":1,"24":15,"26":4,"27":6,"31":7,"33":1,"37":1,"39":8,"41":5,"42":2,"46":1,"47":1,"48":1,"53":2,"57":1,"62":5,"66":1,"67":1,"68":1,"71":2,"72":8,"73":3,"74":3,"76":1,"77":1,"78":5,"79":1,"81":7,"85":2,"86":1,"87":1,"92":2,"93":2,"94":2,"95":5,"96":4,"97":1,"99":3,"100":7,"101":1,"102":2,"103":2,"107":1,"108":2,"109":30,"110":5,"111":6,"113":1,"115":3,"116":1,"117":2,"120":1,"121":1,"122":6,"125":1,"126":1,"128":1,"130":1,"131":1,"132":2,"135":14,"136":4,"137":1,"139":1,"141":1,"145":1,"147":2,"150":3,"155":3,"156":1,"157":13,"158":1,"159":2,"160":8,"164":3,"165":3,"166":4,"167":3,"168":2,"170":2,"171":1,"172":1,"173":1,"174":1,"175":7,"176":4,"177":2,"178":3,"179":2,"181":2,"184":1,"185":2,"186":6,"187":1,"188":1,"189":5,"194":1,"195":4,"196":1,"197":1,"199":1,"200":4,"201":8,"202":4,"203":2,"206":5,"207":3,"208":1,"209":2,"212":2,"213":1,"214":4,"215":2,"216":2,"217":1,"219":2,"223":2,"224":2,"225":5,"227":1,"228":1,"229":1,"230":5,"233":2,"235":4,"236":1,"237":3,"240":1,"241":3,"242":1,"243":12,"245":6,"246":3,"248":3,"249":2,"250":1,"251":1,"252":1,"254":2,"255":10,"256":3,"258":4,"259":1,"260":1,"261":2,"265":15,"266":2,"267":2,"273":2,"274":1,"280":4,"281":1,"282":1,"283":5,"284":4,"285":1,"290":1,"291":1,"292":1,"293":3,"295":2,"296":1,"298":1,"299":1,"300":2,"301":1,"303":3,"305":4,"309":2,"312":2,"314":1,"317":1,"318":12,"319":3,"320":2,"321":1,"322":1,"324":3,"325":8,"326":1,"328":3,"329":1,"330":2,"331":1,"332":3,"333":2,"335":5,"336":1,"337":2,"340":2,"341":6,"342":2,"343":6,"344":1,"345":1,"348":3,"349":2,"350":15,"351":2,"352":4,"353":13,"358":3,"359":8,"360":1,"361":5,"363":1,"364":1,"365":1,"369":1,"370":4,"371":3,"372":3,"373":4,"375":1,"376":1,"378":1,"379":1,"380":2,"381":1,"382":1,"384":2,"386":1,"387":2,"388":4,"390":7,"391":5,"397":2,"398":4,"400":3,"402":2,"403":2,"404":3,"405":2,"407":3,"408":1,"409":1,"413":3,"415":1,"418":2,"420":4,"422":3,"423":2,"424":1,"425":1,"426":3,"427":4,"428":3,"430":1,"433":1,"434":1,"435":5,"438":1,"440":6,"443":2,"444":5,"446":1,"448":5,"450":1,"451":1,"452":1,"454":1,"458":2,"459":8,"461":4,"463":1,"465":4,"466":1,"468":1,"469":1,"470":1,"471":1,"472":2,"474":1,"475":1,"476":1,"481":1,"483":1,"484":1,"486":4,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"497":2,"499":4,"500":2,"501":2,"506":4,"507":8,"508":2,"509":2,"513":2,"514":1,"517":4,"518":4,"519":1,"520":1,"522":2,"523":1,"528":2,"529":1,"530":6,"534":6,"535":3,"536":8,"537":4,"538":5,"539":7,"540":1,"543":4,"544":4,"545":11,"546":3,"547":1,"548":1,"549":13,"551":3,"555":2,"556":8,"558":1,"559":1,"564":9,"565":1,"566":5,"571":2,"572":5,"573":1,"574":1,"577":2,"578":2,"582":1,"583":14,"584":1,"585":6,"586":5,"590":1,"591":3,"592":1,"598":2,"599":6,"601":4,"602":2,"606":1,"607":1,"608":7,"609":12,"610":1,"611":2,"612":4,"618":1,"619":1,"624":2,"626":6,"627":5,"628":4,"631":1,"632":1,"633":8,"634":2,"635":2,"639":1,"640":5,"642":2,"648":2,"649":5,"651":2,"656":3,"657":10,"658":4,"660":3,"661":1,"664":3,"665":6,"666":3,"672":2,"673":2,"674":12,"676":2,"677":1,"681":4,"682":6,"683":3,"689":9,"690":5,"691":3,"692":11,"696":4,"697":2,"698":8,"699":2,"700":3,"701":2,"702":3,"706":5,"707":5,"708":5,"709":1,"713":1,"714":3,"715":6,"716":2,"717":7,"718":2,"723":4,"724":3,"725":15,"726":3,"727":8,"729":1,"730":1,"731":1,"732":2,"733":5,"734":3,"735":2,"740":3,"741":7,"742":7,"743":6,"745":1,"748":2,"749":4,"756":1,"757":4,"758":1,"759":1,"760":1,"761":2,"764":1,"765":1,"766":5,"767":5,"768":8,"769":2,"771":1,"773":1,"774":2,"776":4,"781":2,"782":6,"783":4,"784":2,"789":5,"790":14,"791":3,"792":4,"793":5,"794":2,"795":1,"797":1,"798":2,"799":9,"802":1,"803":1,"804":1,"805":1,"806":1,"809":4,"810":3,"811":3,"813":5,"815":1,"817":3,"818":2,"819":9,"820":3,"821":4,"823":2,"825":3,"826":3,"827":23,"828":2,"829":10,"830":1,"831":2,"832":1,"833":4,"838":5,"839":3,"840":1,"841":1,"845":3,"846":2,"847":2,"848":5,"849":3,"852":1,"853":6,"854":6,"855":2,"856":4,"857":3,"859":3,"860":5,"861":6,"862":3,"863":1,"864":1,"868":2,"869":2,"870":2,"871":4,"874":1,"876":1,"880":2,"881":7,"882":2,"883":2,"888":2,"889":6,"890":2,"891":2,"896":2,"897":12,"898":2,"899":1,"901":1,"903":2,"904":3,"905":23,"906":4,"907":4,"908":2,"909":1,"912":2,"913":4,"914":7,"915":4,"916":3,"917":1,"918":1,"920":2,"921":3,"922":5,"923":1,"924":3,"926":11,"930":1,"931":2,"932":4,"933":7,"936":1,"938":4,"939":3,"941":1,"945":4,"946":6,"947":6,"948":9,"953":1,"954":4,"959":1,"960":1,"963":3,"964":7,"965":3,"966":1,"971":1,"972":5,"973":1,"974":2,"975":1,"979":1,"980":8,"981":3,"982":3,"988":13,"989":3,"990":1,"992":1,"995":1,"996":9,"997":1,"998":1,"999":2,"1001":1,"1003":1,"1004":3,"1005":2,"1006":2,"1012":4,"1013":3,"1016":1,"1017":6,"1018":14,"1019":6,"1020":8,"1022":1,"1024":2,"1025":1,"1026":9,"1027":3,"1028":5,"1029":1,"1032":1,"1033":5,"1034":9,"1035":2,"1037":3,"1041":3,"1042":10,"1043":4,"1044":4,"1045":2,"1046":2,"1047":1,"1049":3,"1050":13,"1051":4,"1052":5,"1053":1,"1055":2,"1058":5,"1059":13,"1060":2,"1061":3,"1066":3,"1067":16,"1068":2,"1069":1,"1074":1,"1075":2,"1077":2,"1078":1,"1079":1,"1080":1,"1082":3,"1083":3,"1084":3,"1085":2,"1089":5,"1090":4,"1091":18,"1092":5,"1093":5,"1095":1,"1096":1,"1099":2,"1100":6,"1101":2,"1102":2,"1107":4,"1108":4,"1109":3,"1115":5,"1116":17,"1117":4,"1118":4,"1119":1,"1123":1,"1124":6,"1125":1,"1126":5,"1133":8,"1134":2,"1135":4,"1140":7,"1141":1,"1142":7,"1143":3,"1144":1,"1145":2,"1147":1,"1150":5,"1151":1,"1152":4,"1153":1,"1154":1,"1155":5,"1157":1,"1158":1,"1161":10,"1162":4,"1163":3,"1164":2,"1167":1,"1168":11,"1169":1,"1170":8,"1171":1,"1174":2,"1175":5,"1176":4,"1177":5,"1178":2,"1179":1,"1183":2,"1184":8,"1185":2,"1186":1,"1187":2,"1188":2,"1190":2,"1191":1,"1196":1,"1210":2,"1212":52,"1214":3,"1215":1,"1217":3,"1219":3,"1220":7,"1221":3,"1222":3,"1223":1,"1224":3,"1225":3,"1228":6,"1229":70,"1230":1,"1231":10,"1232":4,"1233":9,"1234":5,"1235":1,"1236":11,"1237":135,"1238":2,"1239":13,"1240":3,"1241":14,"1242":8,"1243":10,"1244":7,"1245":4,"1246":4,"1247":174,"1248":3,"1249":9,"1250":4,"1251":1,"1252":9,"1253":4,"1254":3,"1255":2,"1256":11,"1257":5,"1258":5,"1259":163,"1260":3,"1261":2,"1262":5,"1263":10,"1264":8,"1265":10,"1266":2,"1267":2,"1268":4,"1269":11,"1270":236,"1271":55,"1272":3,"1273":4,"1274":12,"1275":12,"1276":9,"1277":3,"1278":10,"1279":6,"1280":9,"1281":5,"1282":7,"1283":1,"1284":8,"1285":586,"1286":2,"1287":9,"1288":6,"1289":13,"1290":4,"1291":5,"1292":13,"1293":5,"1294":3,"1295":2,"1296":13,"1297":6,"1298":4,"1299":496,"1300":68,"1301":3,"1302":4,"1303":7,"1304":4,"1305":3,"1306":6,"1307":8,"1308":137,"1309":34,"1310":367,"1311":48,"1313":3,"1314":7,"1315":11,"1316":9,"1317":14,"1318":1,"1319":8,"1320":1,"1321":8,"1322":415,"1323":563,"1324":1,"1325":3,"1327":5,"1328":1,"1329":2,"1330":3,"1331":6,"1332":8,"1333":5,"1334":2,"1335":5,"1336":5,"1337":9,"1338":242,"1339":4,"1341":18,"1342":9,"1343":10,"1344":2,"1345":1,"1346":3,"1347":3,"1348":6,"1349":313,"1350":3,"1351":10,"1352":11,"1353":1,"1354":6,"1355":2,"1356":6,"1357":15,"1358":1073,"1360":1,"1361":4,"1362":1,"1363":2,"1364":3,"1365":11,"1366":5,"1367":1,"1368":114,"1370":3,"1371":4,"1372":6,"1373":1,"1374":5,"1375":3,"1376":1,"1377":2,"1378":7,"1379":173,"1380":2,"1381":6,"1382":3,"1383":3,"1386":3,"1388":12,"1389":5,"1390":10,"1391":5,"1392":3,"1393":2,"1394":450,"1395":665,"1396":3,"1397":8,"1398":9,"1399":2,"1400":5,"1401":184,"1402":3,"1404":4,"1405":15,"1407":6,"1408":2,"1409":3,"1410":5,"1411":1,"1412":1,"1413":7,"1414":348,"1415":63,"1416":225,"1417":5,"1418":2,"1419":4,"1420":8,"1421":3,"1422":11,"1423":2,"1424":3,"1426":77,"1428":2,"1429":9,"1430":8,"1431":7,"1432":7,"1434":6,"1435":879,"1437":101,"1439":2,"1440":11,"1441":7,"1442":16,"1443":3,"1444":2,"1445":2,"1446":7,"1448":3,"1450":2,"1452":14,"1453":8,"1454":39,"1455":6,"1456":2,"1457":5,"1458":16,"1459":6,"1462":2,"1463":1,"1464":6,"1465":3,"1466":64,"1468":1,"1469":2,"1470":2,"1471":7,"1472":4,"1473":7,"1474":13,"1475":14,"1476":2,"1477":6,"1478":3,"1480":1,"1481":2,"1483":3,"1486":1,"1487":22,"1488":25,"1489":8,"1490":3,"1491":13,"1492":1,"1495":31,"1497":1,"1499":2,"1502":1,"1503":1,"1507":3,"1511":2,"1523":9,"1524":2,"1525":28,"1526":1,"1529":3,"1530":7,"1531":4,"1532":2,"1533":9,"1535":1,"1536":1,"1539":1,"1540":1,"1542":1,"1543":1,"1544":2,"1545":1,"1546":5,"1552":7,"1553":1,"1560":2,"1566":1,"1567":1,"1568":1,"1570":1,"1571":3,"1573":1,"1574":5,"1575":3,"1576":44,"1577":1,"1579":1,"1580":1,"1581":5,"1582":1,"1584":5,"1587":1,"1588":5,"1589":1,"1590":17,"1591":2,"1594":1,"1595":8,"1596":1,"1597":3,"1598":3,"1599":5,"1601":2,"1607":1,"1610":3,"1612":1,"1620":1,"1625":2,"1626":16,"1628":5,"1629":6,"1630":38,"1631":16,"1632":1,"1633":3,"1634":2,"1635":1,"1636":14,"1637":12,"1638":15,"1639":15,"1640":28,"1641":1,"1642":2,"1645":1,"1646":1,"1648":3,"1649":2,"1650":9,"1652":6,"1653":6,"1654":1,"1655":2,"1656":1,"1657":1,"1658":1,"1660":3,"1661":6,"1662":1,"1663":8,"1664":3,"1665":7,"1666":5,"1667":3,"1668":7,"1669":3,"1670":3,"1671":2,"1672":6,"1673":2,"1674":6,"1676":5,"1677":1,"1680":1,"1682":2,"1683":6,"1684":7,"1685":9,"1686":3,"1687":1,"1688":5,"1689":2,"1691":1,"1696":1,"1697":3,"1698":1,"1699":1,"1700":3,"1701":4,"1702":1,"1703":1,"1704":1,"1705":1,"1706":2,"1707":6,"1717":5,"1718":2,"1719":3,"1720":1,"1722":2,"1723":5,"1725":1,"1726":18,"1727":3,"1728":2,"1729":2,"1730":1,"1733":2,"1734":1,"1735":1,"1746":1,"1747":3,"1748":6,"1749":4,"1750":1,"1754":4,"1757":3,"1758":1,"1762":1,"1763":10,"1764":18,"1766":6,"1767":4,"1768":2,"1769":1,"1770":3,"1771":1,"1772":2,"1773":1,"1775":8,"1777":1,"1780":1,"1781":1,"1782":1,"1783":1,"1784":1,"1786":1,"1789":5,"1793":2,"1794":2,"1796":1,"1798":3,"1799":1,"1800":2,"1801":2,"1802":5,"1803":5,"1804":11,"1805":2,"1806":2,"1807":1,"1808":4,"1809":13,"1810":2,"1811":3,"1812":1,"1814":23,"1815":2,"1816":6,"1819":1,"1820":3,"1821":3,"1822":4,"1823":1,"1824":7,"1825":1,"1826":1,"1828":2,"1829":7,"1830":3,"1831":1,"1833":1,"1834":2,"1836":2,"1837":2,"1838":3,"1839":4,"1840":4,"1841":4,"1842":6,"1843":2,"1844":4,"1845":1,"1847":1,"1848":3,"1850":7,"1852":3,"1853":2,"1854":4,"1855":6,"1857":1,"1858":5,"1859":1,"1860":1,"1861":3,"1863":1,"1864":4,"1866":2,"1867":1,"1868":2,"1870":2,"1872":1,"1873":2,"1875":2,"1876":4,"1877":2,"1879":2,"1880":3,"1881":1,"1882":5,"1884":1,"1885":4,"1886":3,"1887":4,"1888":9,"1889":1,"1890":1,"1891":5,"1893":1,"1895":3,"1896":4,"1897":9,"1898":3,"1899":2,"1900":1,"1901":10,"1902":1,"1903":5,"1904":3,"1905":1,"1906":1,"1907":9,"1908":9,"1909":4,"1910":2,"1911":5,"1915":3,"1916":1,"1917":2,"1918":2,"1919":3,"1920":2,"1921":9,"1922":10,"1923":3,"1924":2,"1925":3,"1926":17,"1927":3,"1928":7,"1929":2,"1930":2,"1931":2,"1932":8,"1933":2,"1934":3,"1936":1,"1937":2,"1938":1,"1939":5,"1940":4,"1941":1,"1942":2,"1943":2,"1944":5,"1945":4,"1946":3,"1947":7,"1948":1,"1949":9,"1950":1,"1951":3,"1952":4,"1953":5,"1954":2,"1955":2,"1957":1,"1958":5,"1959":4,"1960":3,"1961":4,"1962":3,"1963":4,"1964":9,"1965":5,"1966":1,"1967":2,"1969":2,"1970":2,"1971":6,"1972":3,"1973":1,"1974":2,"1975":7,"1976":13,"1977":8,"1978":7,"1980":1,"1981":11,"1982":1,"1983":3,"1984":5,"1986":1,"1987":9,"1989":4,"1990":3,"1991":2,"1992":2,"1993":14,"1994":12,"1995":6,"1996":5,"1997":3,"1998":5,"1999":8,"2000":11,"2001":5,"2002":1,"2003":3,"2004":2,"2006":2,"2009":3,"2010":1,"2011":3,"2012":2,"2015":1,"2016":1,"2017":1,"2018":1,"2019":2,"2021":1,"2023":2,"2024":3,"2026":1,"2027":3,"2029":2,"2030":1,"2031":3,"2032":3,"2033":3,"2034":1,"2035":1,"2037":7,"2040":1,"2042":5,"2043":3,"2044":4,"2045":8,"2046":7,"2047":1,"2048":1,"2050":1,"2054":6,"2055":6,"2056":2,"2057":1,"2058":2,"2059":1,"2061":1,"2062":11,"2063":4,"2064":2,"2065":2,"2066":1,"2067":1,"2068":3,"2069":3,"2071":5,"2072":2,"2073":4,"2074":5,"2075":1,"2076":4,"2077":2,"2078":1,"2079":1,"2081":1,"2082":11,"2084":1,"2085":5,"2086":1,"2088":3,"2089":4,"2091":2,"2093":2,"2095":2,"2096":1,"2097":2,"2098":4,"2099":2,"2100":1,"2103":2,"2104":2,"2105":1,"2106":2,"2109":3,"2110":1,"2111":1,"2112":1,"2113":1,"2114":1,"2115":1,"2116":3,"2118":1,"2120":2,"2121":1,"2122":4,"2123":4,"2124":4,"2125":8,"2126":1,"2127":8,"2130":2,"2132":1,"2133":1,"2134":2,"2135":2,"2136":2,"2137":2,"2138":2,"2139":2,"2140":1,"2141":4,"2142":2,"2143":4,"2144":4,"2145":6,"2146":4,"2147":3,"2148":5,"2149":3,"2150":2,"2151":3,"2152":2,"2153":1,"2154":1,"2155":11,"2156":4,"2157":10,"2159":4,"2160":2,"2161":3,"2162":4,"2164":4,"2165":9,"2166":7,"2167":4,"2168":8,"2169":2,"2170":2,"2171":3,"2173":2,"2174":8,"2175":1,"2176":5,"2177":2,"2178":4,"2179":7,"2180":3,"2181":5,"2182":3,"2184":4,"2185":5,"2187":6,"2188":1,"2189":2,"2190":7,"2191":7,"2192":11,"2193":2,"2194":2,"2195":6,"2196":6,"2197":4,"2198":7,"2199":2,"2200":4,"2201":3,"2202":5,"2203":1,"2205":2,"2206":1,"2213":1,"2219":5,"2220":2,"2225":1,"2227":2,"2229":5,"2231":39,"2237":1,"2238":7,"2240":1}}],["andhasnowcag21aaviolations",{"3":{"1435":3}}],["andkeepstheformwitharesend",{"3":{"1435":1}}],["andaneditableoneisnot",{"3":{"1435":1}}],["andallowedsharedcodes",{"3":{"1435":3}}],["andalso",{"2":{"1858":1},"3":{"545":1,"549":1,"1239":1,"1247":5,"1814":1,"1858":1}}],["anddoesnotoverflowhorizontally",{"3":{"1435":1}}],["andbesealed",{"3":{"1435":1}}],["andisreportedsupported",{"3":{"1435":1}}],["andisreporteddeprecated",{"3":{"1435":1}}],["anding",{"3":{"546":1,"1247":1,"1358":1,"1394":1}}],["andpassesthroughnonpii",{"3":{"1435":1}}],["andeverydocumentedroute",{"3":{"1435":1}}],["anded",{"3":{"545":1,"1239":2,"1247":3,"1352":2,"1358":7,"1374":1,"1379":1,"1395":1,"1437":1,"1665":1,"1814":1}}],["andneveranadministrativecommand",{"3":{"1338":1}}],["andsignaturestherepodoesnotown",{"3":{"1435":1}}],["ands",{"3":{"545":1,"1247":3,"1285":1,"1310":1,"1358":5,"1379":3,"1384":1,"1394":2,"1395":5,"1814":4}}],["andspecification",{"2":{"319":1,"550":1},"3":{"319":1,"545":2,"549":3,"550":1,"552":1,"1199":7,"1203":1,"1207":1,"1239":1,"1247":13,"1358":2,"1379":2,"1814":2,"1993":1}}],["androidsigningkeypass",{"2":{"1480":1},"3":{"1480":1}}],["androidsigningstorepass",{"2":{"1480":1},"3":{"1480":1}}],["androidx",{"3":{"1416":3}}],["androidpackagename",{"2":{"424":1,"425":1,"426":1,"428":1,"429":1},"3":{"424":1,"425":1,"426":1,"428":1,"429":1,"1310":1}}],["androidcertfingerprints",{"2":{"423":1,"424":1,"425":1,"426":1,"428":1,"429":1},"3":{"418":1,"423":2,"424":2,"425":1,"426":1,"428":2,"429":2,"1310":1}}],["androidmanifest",{"3":{"418":1,"423":1,"426":1,"1415":1}}],["android",{"0":{"1478":1},"2":{"1480":1},"3":{"0":3,"265":1,"412":1,"413":2,"418":11,"419":1,"420":1,"422":2,"423":5,"424":1,"425":1,"426":5,"427":3,"428":2,"429":2,"433":1,"434":2,"436":2,"681":1,"682":1,"1207":6,"1212":1,"1310":7,"1322":1,"1323":4,"1346":1,"1358":1,"1380":1,"1395":3,"1415":69,"1416":38,"1437":1,"1453":1,"1454":1,"1455":1,"1458":4,"1478":3,"1480":7,"1512":1,"1525":3,"1530":3,"1531":1,"1590":1,"1598":1,"1629":1,"1659":1,"1665":1,"1668":2,"1772":1,"1935":1,"2077":1,"2119":1,"2229":1}}],["and",{"0":{"93":1,"95":1,"99":1,"137":1,"138":1,"141":1,"374":1,"452":1,"539":1,"577":1,"720":1,"813":1,"1128":1,"1214":1,"1221":1,"1225":1,"1226":1,"1242":1,"1247":1,"1251":1,"1255":1,"1256":2,"1257":1,"1258":1,"1263":1,"1267":1,"1270":1,"1278":1,"1280":1,"1281":1,"1282":1,"1283":1,"1284":1,"1291":1,"1296":1,"1299":1,"1302":1,"1304":1,"1306":1,"1307":1,"1313":1,"1314":1,"1315":1,"1317":1,"1327":1,"1330":1,"1331":1,"1332":1,"1335":1,"1341":1,"1344":1,"1346":1,"1347":1,"1348":1,"1352":1,"1353":1,"1354":1,"1355":1,"1358":1,"1362":1,"1363":1,"1365":1,"1366":1,"1367":1,"1374":1,"1375":1,"1376":1,"1377":1,"1378":1,"1383":1,"1384":2,"1385":1,"1390":1,"1391":1,"1393":1,"1397":1,"1398":1,"1399":1,"1400":1,"1401":1,"1404":1,"1408":1,"1409":1,"1411":1,"1412":1,"1422":1,"1426":1,"1432":1,"1435":1,"1458":1,"1472":1,"1474":1,"1477":1,"1485":1,"1489":1,"1491":1,"1519":1,"1533":1,"1584":1,"1598":1,"1647":1,"1648":1,"1651":1,"1652":1,"1653":1,"1660":1,"1663":1,"1664":1,"1666":1,"1667":1,"1668":1,"1669":1,"1670":1,"1671":1,"1672":1,"1695":1,"1706":1,"1729":1,"1768":1,"1796":1,"1804":1,"1820":1,"1830":1,"1841":1,"1869":1,"1880":1,"1888":1,"1895":1,"1909":1,"1920":1,"1933":1,"1937":1,"1944":1,"1954":1,"1961":1,"1968":1,"1987":1,"2008":1,"2009":1,"2010":1,"2012":1,"2032":1,"2035":1,"2040":1,"2053":1,"2057":2,"2061":1,"2066":1,"2089":1,"2091":1,"2096":1,"2097":1,"2101":1,"2106":1,"2119":1,"2125":1,"2136":1,"2171":1,"2185":1,"2189":1,"2196":1,"2198":1,"2206":1,"2207":1,"2208":1,"2209":1,"2238":1},"1":{"144":1,"145":1,"146":1,"147":1,"148":1,"149":1,"150":1,"151":1,"152":1,"153":1,"232":1,"233":1,"234":1,"235":1,"236":1,"237":1,"238":1,"239":1,"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"394":1,"395":1,"396":1,"397":1,"398":1,"399":1,"400":1,"401":1,"402":1,"403":1,"404":1,"405":1,"406":1,"407":1,"408":1,"409":1,"417":1,"418":1,"419":1,"420":1,"421":1,"422":1,"423":1,"424":1,"425":1,"426":1,"427":1,"428":1,"429":1,"430":1,"437":1,"438":1,"439":1,"440":1,"441":1,"442":1,"443":1,"444":1,"496":1,"497":1,"498":1,"499":1,"500":1,"501":1,"502":1,"503":1,"580":1,"581":1,"582":1,"583":1,"584":1,"585":1,"586":1,"587":1,"596":1,"597":1,"598":1,"599":1,"600":1,"601":1,"602":1,"603":1,"604":1,"605":1,"615":1,"616":1,"617":1,"618":1,"619":1,"620":1,"621":1,"622":1,"637":1,"638":1,"639":1,"640":1,"641":1,"642":1,"643":1,"644":1,"645":1,"646":1,"647":1,"648":1,"649":1,"650":1,"651":1,"652":1,"653":1,"679":1,"680":1,"681":1,"682":1,"683":1,"684":1,"685":1,"686":1,"687":1,"688":1,"689":1,"690":1,"691":1,"692":1,"693":1,"694":1,"787":1,"788":1,"789":1,"790":1,"791":1,"792":1,"793":1,"794":1,"795":1,"816":1,"817":1,"818":1,"819":1,"820":1,"821":1,"822":1,"823":1,"824":1,"825":1,"826":1,"827":1,"828":1,"829":1,"830":1,"831":1,"832":1,"833":1,"834":1,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1,"866":1,"867":1,"868":1,"869":1,"870":1,"871":1,"872":1,"873":1,"874":1,"875":1,"876":1,"877":1,"985":1,"986":1,"987":1,"988":1,"989":1,"990":1,"991":1,"992":1,"1072":1,"1073":1,"1074":1,"1075":1,"1076":1,"1077":1,"1078":1,"1079":1,"1105":1,"1106":1,"1107":1,"1108":1,"1109":1,"1110":1,"1111":1,"1112":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1,"1210":1,"1211":1,"1212":1,"1213":1,"1214":1,"1215":1,"1216":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"1443":1,"1444":1,"1445":1,"1446":1,"1447":1,"1448":1,"1449":1,"1450":1,"1451":1,"1452":1,"1453":1,"1454":1,"1455":1,"1456":1,"1457":1,"1458":1,"1459":1,"1460":1,"1779":1,"1780":1,"1781":1,"1782":1,"1783":1,"1784":1,"1785":1,"1786":1,"1817":1,"1818":1,"1819":1,"1820":1,"1821":1,"1822":1,"1823":1,"1824":1,"1825":1,"1826":1,"1846":1,"1847":1,"1848":1,"1849":1,"1850":1,"1851":1,"1852":1,"1877":1,"1878":1,"1879":1,"1880":1,"1881":1,"1882":1,"1883":1,"1884":1,"1885":1,"1924":1,"1925":1,"1926":1,"1927":1,"1928":1,"1929":1,"1930":1,"1931":1,"1932":1,"1933":1,"1934":1,"1935":1,"1936":1,"1937":1,"1938":1,"1939":1,"1940":1,"1972":1,"1973":1,"1974":1,"1975":1,"1976":1,"1977":1,"1978":1,"1979":1,"1980":1,"1981":1,"1982":1,"1983":1,"1984":1,"1985":1,"1986":1,"1987":1,"1988":1,"1989":1,"1990":1,"2028":1,"2029":1,"2030":1,"2031":1,"2032":1,"2033":1,"2034":1,"2035":1,"2036":1,"2037":1,"2060":1,"2061":1,"2062":1,"2063":1,"2064":1,"2065":1,"2066":1,"2067":1,"2068":1,"2080":2,"2081":2,"2082":2,"2083":2,"2084":2,"2085":2,"2108":1,"2109":1,"2110":1,"2111":1,"2112":1,"2113":1,"2114":1,"2146":1,"2147":1,"2148":1,"2149":1,"2150":1,"2151":1,"2152":1,"2153":1,"2154":1,"2155":1,"2156":1,"2157":1,"2158":1,"2159":1,"2160":1,"2161":1,"2162":1,"2163":1,"2164":1,"2165":1,"2166":1,"2167":1,"2168":1,"2180":1,"2181":1,"2182":1,"2183":1,"2184":1,"2185":1,"2186":1,"2187":1,"2188":1,"2189":1,"2190":1,"2191":1,"2192":1,"2193":1,"2194":1,"2195":1,"2196":1,"2197":1,"2198":1,"2199":1,"2200":1,"2201":1,"2202":1},"2":{"260":1,"319":1,"545":1,"1082":1,"1503":1,"1813":1,"2104":1},"3":{"0":938,"1":12,"3":1,"5":1,"10":2,"15":13,"16":1,"17":1,"18":2,"19":6,"20":9,"21":7,"22":3,"23":6,"24":29,"25":6,"26":15,"27":23,"29":2,"30":3,"31":15,"32":6,"33":6,"34":1,"35":1,"37":2,"38":4,"39":17,"40":4,"41":4,"42":10,"43":4,"47":6,"48":2,"49":2,"52":2,"53":8,"54":5,"55":2,"57":6,"58":4,"59":13,"60":5,"61":3,"62":8,"63":4,"64":1,"66":3,"67":3,"68":8,"69":3,"70":6,"71":17,"72":14,"73":4,"74":5,"76":3,"77":5,"78":5,"79":4,"80":11,"81":10,"82":2,"85":2,"86":3,"90":2,"91":11,"92":9,"93":14,"94":12,"95":14,"96":6,"97":6,"98":7,"99":7,"100":9,"101":4,"102":5,"103":2,"104":6,"105":1,"107":3,"108":2,"109":61,"110":8,"111":10,"112":4,"113":3,"115":14,"116":4,"117":11,"118":3,"120":5,"121":12,"122":17,"123":3,"124":2,"125":15,"126":18,"127":4,"128":11,"130":15,"131":2,"132":5,"133":1,"134":2,"135":63,"136":28,"137":12,"138":15,"139":23,"140":15,"141":8,"142":23,"143":4,"144":1,"145":22,"146":6,"147":11,"148":3,"149":4,"150":22,"151":3,"152":6,"153":3,"155":7,"156":2,"157":20,"158":3,"159":10,"160":20,"161":10,"162":1,"164":9,"165":3,"166":21,"167":2,"168":8,"169":2,"170":12,"171":8,"173":11,"174":5,"175":25,"176":4,"177":8,"178":19,"179":9,"180":11,"181":3,"182":7,"184":9,"185":3,"186":23,"187":5,"188":4,"189":14,"190":5,"191":1,"193":6,"194":4,"195":29,"196":2,"197":4,"198":3,"199":2,"200":11,"201":27,"202":12,"203":6,"206":5,"207":10,"208":1,"209":5,"210":1,"212":9,"213":9,"214":15,"215":5,"216":4,"217":10,"218":4,"219":18,"220":12,"221":1,"223":3,"224":2,"225":19,"226":1,"227":1,"228":3,"229":3,"230":24,"231":5,"232":1,"233":5,"234":9,"235":30,"236":2,"237":10,"238":3,"239":3,"241":10,"242":8,"243":36,"244":2,"245":20,"246":2,"247":6,"248":8,"249":4,"250":5,"251":3,"252":10,"253":5,"254":13,"255":26,"256":22,"257":20,"258":25,"259":32,"260":20,"261":18,"263":2,"264":3,"265":57,"266":5,"267":4,"268":5,"269":2,"272":2,"273":8,"274":3,"275":4,"276":5,"277":2,"278":1,"279":8,"280":6,"281":8,"282":7,"283":7,"284":5,"285":5,"286":7,"287":1,"290":6,"291":6,"292":4,"293":2,"294":1,"295":8,"296":7,"298":3,"299":1,"300":4,"301":3,"303":9,"304":2,"305":12,"306":5,"308":2,"309":4,"310":12,"311":3,"312":4,"313":1,"314":5,"316":1,"317":4,"318":29,"319":7,"320":3,"321":2,"322":4,"323":5,"324":12,"325":9,"326":11,"328":5,"329":5,"330":13,"331":5,"332":7,"333":10,"335":8,"336":3,"337":4,"339":1,"340":8,"341":27,"342":7,"343":13,"344":4,"345":6,"346":5,"348":6,"349":6,"350":35,"351":9,"352":6,"353":9,"354":4,"355":1,"358":4,"359":27,"360":10,"361":16,"363":2,"364":2,"365":16,"366":3,"368":6,"369":13,"370":14,"371":5,"372":4,"373":16,"374":8,"375":7,"376":3,"378":2,"379":5,"380":5,"381":3,"382":1,"383":4,"384":8,"386":11,"387":7,"388":10,"389":1,"390":14,"391":14,"392":10,"393":5,"394":1,"395":33,"396":6,"397":34,"398":7,"399":13,"400":9,"401":16,"402":19,"403":11,"404":9,"405":8,"406":6,"407":16,"408":9,"409":13,"411":3,"412":4,"413":23,"414":5,"415":10,"417":1,"418":17,"419":9,"420":9,"421":2,"422":7,"423":14,"424":6,"425":5,"426":11,"427":4,"428":17,"429":6,"430":4,"432":3,"433":5,"434":15,"435":6,"436":8,"437":1,"438":2,"439":4,"440":2,"441":2,"442":3,"443":15,"444":7,"446":4,"447":5,"448":24,"449":5,"450":5,"451":7,"452":5,"453":4,"454":10,"455":1,"457":3,"458":6,"459":27,"460":4,"461":7,"462":2,"463":12,"464":17,"465":15,"466":7,"468":13,"469":5,"470":10,"471":4,"472":6,"473":2,"474":6,"475":7,"476":7,"477":15,"478":5,"480":4,"481":4,"482":6,"483":4,"484":2,"486":11,"487":9,"488":8,"489":11,"490":26,"491":19,"492":23,"493":24,"494":17,"495":10,"496":1,"497":23,"498":8,"499":26,"500":7,"501":7,"502":6,"503":1,"505":2,"506":8,"507":28,"508":7,"509":6,"510":4,"511":8,"512":4,"513":8,"514":4,"516":3,"517":12,"518":13,"519":5,"520":5,"521":2,"522":12,"523":10,"524":11,"526":7,"527":8,"528":19,"529":5,"530":11,"531":5,"532":1,"534":19,"535":4,"536":53,"537":11,"538":17,"539":26,"540":10,"541":5,"543":21,"544":6,"545":43,"546":5,"547":14,"548":5,"549":46,"550":17,"551":17,"552":13,"554":12,"555":8,"556":32,"557":7,"558":8,"559":6,"560":4,"562":4,"563":4,"564":20,"565":5,"566":14,"567":3,"570":3,"571":6,"572":45,"573":2,"574":8,"575":6,"576":8,"577":10,"578":10,"579":2,"580":1,"582":7,"583":49,"584":8,"585":21,"586":5,"589":2,"590":4,"591":33,"592":9,"593":8,"594":4,"595":2,"596":1,"597":6,"598":7,"599":36,"600":6,"601":10,"602":11,"603":7,"604":7,"605":1,"607":37,"608":13,"609":60,"610":4,"611":16,"612":4,"613":4,"614":5,"615":1,"616":10,"617":6,"618":35,"619":6,"620":7,"621":7,"622":3,"624":11,"625":9,"626":62,"627":9,"628":11,"629":11,"630":5,"631":18,"632":14,"633":36,"634":7,"635":10,"636":11,"637":1,"638":12,"639":7,"640":33,"641":9,"642":15,"643":6,"644":6,"646":1,"647":4,"648":4,"649":39,"650":11,"651":6,"652":2,"655":5,"656":8,"657":51,"658":8,"659":11,"660":11,"661":1,"663":3,"664":8,"665":23,"666":8,"667":7,"668":9,"669":11,"670":1,"672":10,"673":4,"674":56,"675":4,"676":12,"677":8,"678":1,"679":1,"680":3,"681":7,"682":22,"683":10,"684":12,"685":3,"686":4,"687":1,"688":7,"689":10,"690":40,"691":19,"692":20,"693":6,"694":7,"696":17,"697":7,"698":51,"699":5,"700":10,"701":6,"702":14,"703":13,"705":3,"706":7,"707":40,"708":13,"709":11,"710":5,"711":6,"713":9,"714":4,"715":28,"716":10,"717":7,"718":2,"719":5,"720":6,"721":6,"723":5,"724":12,"725":38,"726":12,"727":13,"728":4,"729":2,"731":8,"732":5,"733":30,"734":4,"735":8,"736":6,"737":8,"739":2,"740":14,"741":30,"742":11,"743":22,"744":10,"745":8,"747":3,"748":5,"749":42,"750":7,"751":16,"752":2,"753":3,"755":4,"756":10,"757":34,"758":7,"759":11,"760":4,"761":7,"764":11,"765":12,"766":34,"767":11,"768":10,"769":4,"770":1,"773":11,"774":15,"775":8,"776":9,"777":6,"778":5,"780":9,"781":7,"782":27,"783":6,"784":7,"785":6,"786":1,"787":1,"788":1,"789":8,"790":32,"791":7,"792":15,"793":6,"794":6,"795":1,"797":11,"798":16,"799":23,"800":11,"801":8,"802":5,"803":14,"804":11,"805":7,"806":2,"808":1,"809":13,"810":13,"811":6,"812":5,"813":14,"814":1,"815":6,"816":1,"817":2,"818":12,"819":34,"820":9,"821":15,"822":5,"823":8,"824":1,"825":9,"826":20,"827":98,"828":6,"829":18,"830":11,"831":13,"832":37,"833":11,"834":7,"836":6,"837":22,"838":30,"839":12,"840":11,"841":5,"842":1,"844":3,"845":6,"846":12,"847":3,"848":6,"849":9,"850":2,"852":2,"853":7,"854":24,"855":8,"856":10,"857":12,"858":1,"859":3,"860":5,"861":27,"862":12,"863":18,"864":5,"865":2,"866":1,"867":4,"868":12,"869":20,"870":10,"871":6,"872":10,"873":8,"874":3,"875":13,"876":12,"877":11,"879":6,"880":8,"881":59,"882":8,"883":11,"884":9,"885":4,"888":4,"889":24,"890":8,"891":10,"892":6,"893":4,"896":12,"897":32,"898":6,"899":8,"900":5,"901":5,"903":7,"904":6,"905":73,"906":22,"907":24,"908":5,"909":5,"910":13,"912":4,"913":12,"914":36,"915":5,"916":11,"917":6,"918":5,"920":6,"921":12,"922":23,"923":11,"924":14,"925":4,"926":52,"927":8,"929":3,"930":10,"931":17,"932":6,"933":8,"934":1,"935":2,"937":3,"938":5,"939":11,"940":6,"941":6,"942":3,"944":2,"945":5,"946":30,"947":6,"948":4,"949":5,"950":2,"952":1,"953":11,"954":34,"955":9,"956":8,"957":5,"958":1,"959":5,"960":8,"962":2,"963":10,"964":28,"965":9,"966":7,"967":7,"968":4,"971":9,"972":28,"973":9,"974":7,"975":6,"976":2,"979":13,"980":37,"981":6,"982":13,"983":11,"984":3,"985":1,"986":5,"987":8,"988":47,"989":6,"990":8,"991":9,"992":5,"994":6,"995":8,"996":37,"997":10,"998":17,"999":9,"1000":3,"1002":1,"1003":7,"1004":40,"1005":5,"1006":9,"1007":5,"1008":2,"1010":2,"1011":12,"1012":36,"1013":4,"1014":7,"1016":18,"1017":14,"1018":90,"1019":23,"1020":19,"1021":10,"1022":3,"1024":3,"1025":8,"1026":26,"1027":10,"1028":6,"1029":5,"1030":3,"1032":2,"1033":11,"1034":39,"1035":10,"1036":8,"1037":8,"1040":2,"1041":12,"1042":26,"1043":17,"1044":16,"1045":7,"1046":1,"1048":4,"1049":17,"1050":53,"1051":22,"1052":15,"1053":4,"1054":10,"1055":9,"1057":3,"1058":13,"1059":48,"1060":8,"1061":9,"1062":7,"1063":1,"1065":1,"1066":16,"1067":34,"1068":10,"1069":11,"1070":3,"1071":1,"1072":1,"1073":6,"1074":32,"1075":25,"1076":5,"1077":5,"1078":4,"1079":6,"1081":4,"1082":15,"1083":11,"1084":4,"1085":7,"1086":3,"1087":2,"1089":17,"1090":16,"1091":75,"1092":17,"1093":22,"1094":12,"1095":9,"1096":8,"1099":11,"1100":28,"1101":6,"1102":6,"1103":5,"1104":3,"1105":1,"1107":7,"1108":13,"1109":3,"1110":3,"1111":4,"1112":3,"1114":3,"1115":14,"1116":43,"1117":18,"1118":8,"1119":7,"1120":1,"1122":2,"1123":11,"1124":42,"1125":10,"1126":8,"1127":10,"1128":6,"1129":5,"1132":12,"1133":27,"1134":5,"1135":8,"1136":2,"1137":3,"1139":3,"1140":13,"1141":3,"1142":6,"1143":2,"1144":10,"1145":2,"1146":1,"1147":2,"1149":3,"1150":10,"1151":2,"1152":5,"1153":3,"1154":9,"1155":2,"1156":2,"1157":6,"1158":1,"1160":5,"1161":25,"1162":5,"1163":4,"1164":3,"1167":6,"1168":30,"1169":6,"1170":6,"1171":3,"1173":1,"1174":5,"1175":28,"1176":7,"1177":5,"1178":5,"1179":2,"1180":2,"1182":3,"1183":8,"1184":30,"1185":10,"1186":8,"1187":10,"1188":5,"1189":3,"1190":5,"1191":12,"1192":13,"1193":3,"1194":1,"1195":3,"1196":2,"1200":4,"1201":7,"1202":29,"1203":29,"1210":1,"1211":14,"1212":162,"1213":20,"1214":9,"1215":4,"1216":4,"1217":8,"1218":9,"1219":8,"1220":17,"1221":4,"1222":7,"1223":11,"1224":3,"1225":8,"1226":3,"1227":4,"1228":11,"1229":235,"1230":21,"1231":27,"1232":13,"1233":13,"1234":30,"1235":7,"1236":16,"1237":395,"1238":16,"1239":22,"1240":15,"1241":45,"1242":24,"1243":27,"1244":29,"1245":7,"1246":11,"1247":515,"1248":9,"1249":14,"1250":4,"1251":10,"1252":20,"1253":15,"1254":8,"1255":7,"1256":18,"1257":14,"1258":8,"1259":628,"1260":22,"1261":8,"1262":14,"1263":51,"1264":19,"1265":42,"1266":11,"1267":21,"1268":6,"1269":25,"1270":881,"1271":237,"1272":10,"1273":29,"1274":22,"1275":38,"1276":26,"1277":16,"1278":32,"1279":19,"1280":31,"1281":16,"1282":17,"1283":22,"1284":22,"1285":2359,"1286":21,"1287":12,"1288":19,"1289":29,"1290":12,"1291":11,"1292":28,"1293":21,"1294":15,"1295":10,"1296":36,"1297":10,"1298":3,"1299":1620,"1300":309,"1301":17,"1302":12,"1303":26,"1304":10,"1305":12,"1306":21,"1307":11,"1308":579,"1309":141,"1310":1518,"1311":166,"1312":20,"1313":5,"1314":19,"1315":35,"1316":27,"1317":29,"1318":4,"1319":40,"1320":1,"1321":29,"1322":1370,"1323":2384,"1324":13,"1325":26,"1326":8,"1327":39,"1328":6,"1329":13,"1330":8,"1331":36,"1332":40,"1333":18,"1334":15,"1335":29,"1336":29,"1337":33,"1338":1180,"1339":15,"1340":14,"1341":53,"1342":19,"1343":28,"1344":11,"1345":15,"1346":43,"1347":16,"1348":19,"1349":1248,"1350":10,"1351":49,"1352":41,"1353":20,"1354":32,"1355":11,"1356":21,"1357":20,"1358":4110,"1359":9,"1360":2,"1361":33,"1362":10,"1363":21,"1364":7,"1365":47,"1366":11,"1367":14,"1368":382,"1369":7,"1370":22,"1371":12,"1372":29,"1373":12,"1374":27,"1375":11,"1376":14,"1377":19,"1378":21,"1379":694,"1380":8,"1381":20,"1382":23,"1383":26,"1384":21,"1385":6,"1386":12,"1387":16,"1388":28,"1389":16,"1390":20,"1391":16,"1392":13,"1393":7,"1394":2376,"1395":2556,"1396":10,"1397":19,"1398":34,"1399":21,"1400":20,"1401":1067,"1402":11,"1403":17,"1404":20,"1405":34,"1406":11,"1407":19,"1408":26,"1409":24,"1410":9,"1411":10,"1412":17,"1413":30,"1414":1369,"1415":345,"1416":1126,"1417":15,"1418":9,"1419":13,"1420":22,"1421":24,"1422":21,"1423":19,"1424":19,"1425":10,"1426":238,"1427":20,"1428":23,"1429":31,"1430":66,"1431":27,"1432":48,"1433":11,"1434":41,"1435":3886,"1436":52,"1437":506,"1438":9,"1439":6,"1440":106,"1441":52,"1442":85,"1443":27,"1444":47,"1445":22,"1446":62,"1447":3,"1448":26,"1449":14,"1450":7,"1451":12,"1452":59,"1453":44,"1454":254,"1455":36,"1456":11,"1457":17,"1458":40,"1459":37,"1460":19,"1461":3,"1462":18,"1463":8,"1464":35,"1465":10,"1466":402,"1467":2,"1468":8,"1469":18,"1470":12,"1471":21,"1472":19,"1473":47,"1474":50,"1475":55,"1476":21,"1477":28,"1478":4,"1479":13,"1480":19,"1481":22,"1482":7,"1483":10,"1484":6,"1485":34,"1486":71,"1487":129,"1488":99,"1489":63,"1490":14,"1491":105,"1492":8,"1493":5,"1494":1,"1495":384,"1496":9,"1497":10,"1498":1,"1499":10,"1500":1,"1501":2,"1502":11,"1503":8,"1504":1,"1505":2,"1506":3,"1507":3,"1508":7,"1510":2,"1511":2,"1512":3,"1513":1,"1514":3,"1515":1,"1516":3,"1517":3,"1518":4,"1520":3,"1521":1,"1522":6,"1523":21,"1524":19,"1525":224,"1526":3,"1527":2,"1528":2,"1529":8,"1530":34,"1531":13,"1532":1,"1533":40,"1534":21,"1535":11,"1536":5,"1537":2,"1538":3,"1539":2,"1540":3,"1541":1,"1542":6,"1543":5,"1544":6,"1545":4,"1546":13,"1547":9,"1548":1,"1549":5,"1550":3,"1551":1,"1552":14,"1553":6,"1554":4,"1555":2,"1556":1,"1557":3,"1558":3,"1559":1,"1560":3,"1561":4,"1562":4,"1563":3,"1564":5,"1565":7,"1566":5,"1567":2,"1568":2,"1569":9,"1570":10,"1571":16,"1573":11,"1574":26,"1575":15,"1576":213,"1577":8,"1578":2,"1579":1,"1580":8,"1581":62,"1582":17,"1583":6,"1584":32,"1585":23,"1587":6,"1588":14,"1589":1,"1590":129,"1591":4,"1592":1,"1593":3,"1594":8,"1595":54,"1596":16,"1597":2,"1598":16,"1599":24,"1600":1,"1601":2,"1603":1,"1604":2,"1606":8,"1607":2,"1610":12,"1613":1,"1619":1,"1620":3,"1621":1,"1622":1,"1623":1,"1624":3,"1625":12,"1626":38,"1628":24,"1629":32,"1630":130,"1631":43,"1632":5,"1633":4,"1634":14,"1635":9,"1636":9,"1637":33,"1638":61,"1639":30,"1640":84,"1641":2,"1642":3,"1643":4,"1644":1,"1645":10,"1646":8,"1647":7,"1648":5,"1649":7,"1650":40,"1651":7,"1652":37,"1653":19,"1654":5,"1655":14,"1656":4,"1657":5,"1658":2,"1659":20,"1660":16,"1661":32,"1662":17,"1663":24,"1664":19,"1665":41,"1666":35,"1667":26,"1668":41,"1669":39,"1670":40,"1671":10,"1672":27,"1673":15,"1674":13,"1675":3,"1676":18,"1677":4,"1678":2,"1679":2,"1680":7,"1681":3,"1682":8,"1683":25,"1684":43,"1685":66,"1686":16,"1687":2,"1688":12,"1689":3,"1690":4,"1691":9,"1692":5,"1693":1,"1694":4,"1695":3,"1696":8,"1697":10,"1698":5,"1699":11,"1700":19,"1701":11,"1702":7,"1703":4,"1704":2,"1705":7,"1706":10,"1707":4,"1708":1,"1709":2,"1710":3,"1711":2,"1712":2,"1713":1,"1714":5,"1715":1,"1716":6,"1717":10,"1718":17,"1719":14,"1720":11,"1721":4,"1722":4,"1723":11,"1724":2,"1725":6,"1726":59,"1727":18,"1728":18,"1729":5,"1730":10,"1731":4,"1732":3,"1734":8,"1736":1,"1738":2,"1740":8,"1741":7,"1746":7,"1747":20,"1748":24,"1749":15,"1750":4,"1751":3,"1754":10,"1755":1,"1756":2,"1757":7,"1758":2,"1759":9,"1760":9,"1762":6,"1763":21,"1764":69,"1765":2,"1766":21,"1767":14,"1768":32,"1769":10,"1770":8,"1771":1,"1772":8,"1774":1,"1775":15,"1776":4,"1777":1,"1778":14,"1779":8,"1780":10,"1781":4,"1782":7,"1783":14,"1784":4,"1785":2,"1786":4,"1787":8,"1788":3,"1789":18,"1791":8,"1792":8,"1793":5,"1794":4,"1795":31,"1796":5,"1797":1,"1798":11,"1799":10,"1800":6,"1801":10,"1802":5,"1803":6,"1804":26,"1805":9,"1806":7,"1807":4,"1808":4,"1809":40,"1810":9,"1811":8,"1812":2,"1813":6,"1814":61,"1815":6,"1816":16,"1817":9,"1818":3,"1819":5,"1820":20,"1821":5,"1822":6,"1823":9,"1824":22,"1825":11,"1826":8,"1827":8,"1828":9,"1829":7,"1830":5,"1831":6,"1832":7,"1833":10,"1834":2,"1835":3,"1836":3,"1837":4,"1838":11,"1839":19,"1840":10,"1841":13,"1842":18,"1843":11,"1844":9,"1845":7,"1846":8,"1847":2,"1848":18,"1849":2,"1850":27,"1851":10,"1852":12,"1853":8,"1854":5,"1855":13,"1856":5,"1857":7,"1858":6,"1859":10,"1860":10,"1861":12,"1862":7,"1863":3,"1864":9,"1865":7,"1866":2,"1867":8,"1868":10,"1869":2,"1870":10,"1871":3,"1872":4,"1873":12,"1874":8,"1875":14,"1876":10,"1877":5,"1878":5,"1879":3,"1880":9,"1881":5,"1882":9,"1883":5,"1884":4,"1885":9,"1886":9,"1887":11,"1888":18,"1889":2,"1890":9,"1891":14,"1892":5,"1893":3,"1894":6,"1895":4,"1896":5,"1897":19,"1898":11,"1899":3,"1900":2,"1901":21,"1902":7,"1903":6,"1904":12,"1905":5,"1906":4,"1907":12,"1908":30,"1909":10,"1910":13,"1911":17,"1912":1,"1913":3,"1914":8,"1915":15,"1916":9,"1917":11,"1918":5,"1919":20,"1920":6,"1921":38,"1922":43,"1923":19,"1924":8,"1925":2,"1926":16,"1927":5,"1928":12,"1929":9,"1930":10,"1931":11,"1932":26,"1933":8,"1934":6,"1935":13,"1936":8,"1937":9,"1938":4,"1939":14,"1940":14,"1941":11,"1942":4,"1943":14,"1944":9,"1945":14,"1946":15,"1947":27,"1948":9,"1949":18,"1950":8,"1951":6,"1952":10,"1953":5,"1954":5,"1955":8,"1956":2,"1957":8,"1958":9,"1959":4,"1960":4,"1961":8,"1962":10,"1963":8,"1964":13,"1965":11,"1966":5,"1967":13,"1968":1,"1969":18,"1970":9,"1971":12,"1972":15,"1973":4,"1974":17,"1975":9,"1976":21,"1977":12,"1978":20,"1979":10,"1980":3,"1981":23,"1982":5,"1983":10,"1984":17,"1985":8,"1986":6,"1987":23,"1988":7,"1989":9,"1990":15,"1991":6,"1992":4,"1993":21,"1994":29,"1995":13,"1996":20,"1997":8,"1998":9,"1999":57,"2000":28,"2001":24,"2002":9,"2003":7,"2004":6,"2005":20,"2006":7,"2007":8,"2008":4,"2009":23,"2010":8,"2011":12,"2012":9,"2013":13,"2014":6,"2015":4,"2016":7,"2017":4,"2018":5,"2019":7,"2020":3,"2021":3,"2022":7,"2023":22,"2024":15,"2025":4,"2026":9,"2027":17,"2028":8,"2029":12,"2030":2,"2031":16,"2032":6,"2033":18,"2034":9,"2035":14,"2036":9,"2037":18,"2038":7,"2039":5,"2040":7,"2041":8,"2042":31,"2043":21,"2044":7,"2045":13,"2046":14,"2047":18,"2048":11,"2049":8,"2050":2,"2051":6,"2052":7,"2053":4,"2054":38,"2055":15,"2056":25,"2057":6,"2058":7,"2059":12,"2060":9,"2061":3,"2062":20,"2063":8,"2064":3,"2065":12,"2066":15,"2067":7,"2068":13,"2069":14,"2070":3,"2071":9,"2072":8,"2073":3,"2074":18,"2075":10,"2076":14,"2077":7,"2078":19,"2079":19,"2080":16,"2081":3,"2082":22,"2083":2,"2084":15,"2085":19,"2086":8,"2087":4,"2088":12,"2089":5,"2090":7,"2091":2,"2092":2,"2093":4,"2094":6,"2095":13,"2096":17,"2097":16,"2098":6,"2099":5,"2100":5,"2101":3,"2102":4,"2103":10,"2104":5,"2105":2,"2106":9,"2107":6,"2108":6,"2109":17,"2110":11,"2111":7,"2112":9,"2113":12,"2114":8,"2115":11,"2116":7,"2117":9,"2118":9,"2119":9,"2120":10,"2121":12,"2122":10,"2123":11,"2124":3,"2125":29,"2126":13,"2127":12,"2128":9,"2129":6,"2130":32,"2131":12,"2132":14,"2133":10,"2134":7,"2135":10,"2136":1,"2137":20,"2138":10,"2139":4,"2140":4,"2141":31,"2142":7,"2143":9,"2144":10,"2145":17,"2146":12,"2147":10,"2148":18,"2149":23,"2150":8,"2151":6,"2152":20,"2153":20,"2154":6,"2155":47,"2156":17,"2157":30,"2158":17,"2159":26,"2160":14,"2161":9,"2162":13,"2163":13,"2164":17,"2165":20,"2166":39,"2167":24,"2168":27,"2169":10,"2170":7,"2171":10,"2172":7,"2173":11,"2174":10,"2175":11,"2176":14,"2177":5,"2178":13,"2179":32,"2180":9,"2181":16,"2182":12,"2183":2,"2184":9,"2185":14,"2186":3,"2187":10,"2188":6,"2189":18,"2190":10,"2191":19,"2192":26,"2193":12,"2194":6,"2195":10,"2196":7,"2197":12,"2198":11,"2199":12,"2200":5,"2201":23,"2202":16,"2203":4,"2204":2,"2205":4,"2206":4,"2207":10,"2208":4,"2209":10,"2210":6,"2211":2,"2212":11,"2213":8,"2214":3,"2215":6,"2216":3,"2217":5,"2218":1,"2219":36,"2220":12,"2223":2,"2224":5,"2226":10,"2227":5,"2228":1,"2229":18,"2230":2,"2231":105,"2232":5,"2233":3,"2234":1,"2235":3,"2236":1,"2237":3,"2238":14,"2239":4,"2240":1,"2241":4,"2242":2,"2243":1,"2244":5}}],["ar",{"3":{"2219":2}}],["arbiter",{"3":{"691":1,"1303":1,"1308":2,"1395":2}}],["arbitrating",{"3":{"1435":1}}],["arbitration",{"3":{"1285":2,"1358":1}}],["arbitrated",{"3":{"1394":1,"1984":1}}],["arbitrate",{"3":{"1278":1,"1285":2,"1308":1,"1940":1}}],["arbitrates",{"3":{"0":1,"903":1,"905":2,"1285":3,"1289":1,"1299":1,"1323":1,"1933":1,"1981":1}}],["arbitrarily",{"3":{"1261":1,"1394":1,"1819":1}}],["arbitrary",{"3":{"0":1,"110":1,"219":1,"320":1,"332":1,"380":1,"774":1,"940":1,"1212":1,"1237":1,"1270":1,"1285":3,"1299":2,"1300":1,"1308":2,"1311":1,"1322":3,"1323":11,"1335":1,"1338":1,"1349":3,"1355":1,"1358":3,"1368":1,"1394":2,"1395":1,"1401":1,"1414":2,"1415":1,"1430":1,"1435":4,"1442":1,"1457":1,"1466":1,"1639":1,"1988":1,"1989":1,"1990":1,"1995":1,"2036":1,"2231":1}}],["archunit",{"3":{"2048":1}}],["archunitnet",{"3":{"1539":1,"2048":1}}],["archetype",{"3":{"1379":1}}],["arch",{"3":{"1214":1,"1486":1,"1488":1,"1525":6,"1529":1,"1530":1,"1531":1,"1534":2,"1576":2,"1585":1,"1590":5,"1594":1,"1596":1,"1599":1,"2041":1,"2106":1}}],["archaeology",{"3":{"838":1,"1019":1,"1299":1,"1839":1}}],["archaeological",{"3":{"88":1}}],["architected",{"3":{"2219":5,"2237":1}}],["architecting",{"3":{"2212":1,"2217":1}}],["architect",{"1":{"2211":1,"2212":1,"2213":1,"2214":1,"2215":1},"3":{"2211":1,"2212":1,"2216":1,"2217":1,"2219":3,"2223":1}}],["architecturally",{"3":{"1814":1}}],["architectural",{"0":{"1212":1,"1661":1,"2229":1},"3":{"132":1,"643":1,"1004":1,"1210":1,"1239":1,"1259":1,"1270":1,"1285":1,"1309":2,"1395":1,"1427":1,"1435":7,"1438":1,"1446":1,"1466":1,"1470":1,"1488":1,"1522":1,"1601":1,"1794":1,"1799":1,"1801":1,"2098":1,"2212":1,"2217":1,"2219":1}}],["architecturalanalysis",{"3":{"1":1,"46":1,"1190":1,"1524":1,"1525":2,"1576":2,"1581":1,"1585":1}}],["architecturemap",{"3":{"1727":1,"2096":1}}],["architecturemapbase",{"2":{"975":1,"2102":1},"3":{"972":1,"974":1,"975":1,"1199":43,"1207":2,"1427":1,"1430":5,"1435":87,"1653":1,"2041":1,"2102":1}}],["architectureremediation",{"3":{"1593":1}}],["architecturerules",{"2":{"39":1,"135":2,"136":1,"142":1,"305":1,"576":1,"657":1,"846":1,"849":1,"1430":1,"1488":1,"2042":1,"2043":2,"2101":1,"2102":1,"2137":1},"3":{"0":1,"17":1,"27":1,"39":5,"54":1,"78":1,"80":2,"81":2,"82":1,"109":1,"132":1,"135":5,"136":3,"139":3,"142":1,"147":1,"160":3,"161":1,"168":1,"171":1,"305":2,"341":2,"576":1,"657":3,"782":4,"798":1,"801":1,"804":1,"846":1,"849":1,"963":1,"966":1,"980":1,"982":1,"1004":2,"1005":1,"1006":2,"1050":1,"1074":1,"1078":1,"1083":1,"1091":1,"1160":1,"1161":3,"1162":2,"1163":2,"1168":12,"1169":1,"1170":3,"1199":68,"1207":79,"1235":1,"1236":1,"1237":5,"1239":3,"1247":3,"1259":1,"1270":1,"1285":2,"1299":4,"1310":7,"1311":6,"1323":1,"1358":1,"1414":1,"1427":1,"1430":17,"1435":355,"1488":8,"1495":4,"1523":1,"1525":3,"1530":1,"1534":1,"1575":1,"1576":9,"1581":1,"1582":1,"1585":2,"1590":1,"1595":1,"1659":1,"1670":1,"1874":1,"2041":1,"2042":1,"2043":3,"2084":1,"2101":1,"2102":1,"2137":2}}],["architecturetesthelper",{"2":{"2041":1},"3":{"1435":1,"2041":1}}],["architecturetests",{"2":{"1688":1},"3":{"80":2,"128":1,"633":1,"635":1,"636":1,"1133":2,"1136":1,"1168":1,"1435":23,"1488":1,"1688":1}}],["architectureassert",{"3":{"1199":8,"1207":2,"1427":1,"1430":4,"1435":22,"1488":1}}],["architectureevaluation",{"3":{"1193":1}}],["architectureevaluationcriteria",{"2":{"375":1,"1521":1,"1572":1,"1576":1,"1586":1},"3":{"369":2,"371":1,"375":1,"376":1,"590":1,"594":1,"860":1,"864":1,"1003":1,"1007":1,"1011":1,"1090":1,"1092":1,"1093":1,"1216":1,"1521":1,"1525":14,"1526":1,"1530":1,"1572":1,"1576":9,"1577":1,"1586":1,"1590":3,"1595":2,"1597":1,"2154":1,"2161":1,"2170":1,"2178":1,"2181":1,"2194":1}}],["architecturescorecard",{"2":{"1528":1,"1593":1},"3":{"1006":1,"1007":1,"1012":3,"1017":1,"1090":1,"1094":1,"1095":1,"1216":3,"1495":1,"1525":1,"1528":1,"1576":2,"1590":1,"1593":1,"2170":1,"2178":2,"2181":1,"2194":1}}],["architecture",{"0":{"1216":1,"1430":1,"1488":1,"1503":1,"1539":1,"1541":1,"1544":1,"1546":1,"1552":1,"1554":1,"1561":1,"1570":1,"1640":1,"1653":1,"1794":1,"2212":1,"2230":1,"2231":1},"1":{"0":1,"1":1,"129":1,"130":1,"131":1,"132":1,"133":1,"134":1,"135":1,"136":1,"137":1,"138":1,"139":1,"140":1,"141":1,"142":1,"143":1,"1484":1,"1485":1,"1486":1,"1487":1,"1488":1,"1489":1,"1490":1,"1491":1,"1492":1,"1493":1,"1517":1,"1518":1,"1519":1,"1520":1,"1521":1,"1522":1,"1523":1,"1524":1,"1525":1,"1526":1,"1527":1,"1528":1,"1529":1,"1530":1,"1531":1,"1532":1,"1533":1,"1534":1,"1535":1,"1536":1,"1537":1,"1538":1,"1539":1,"1540":1,"1541":1,"1542":1,"1543":1,"1544":1,"1545":1,"1546":1,"1547":1,"1548":1,"1549":1,"1550":1,"1551":1,"1552":1,"1553":1,"1554":1,"1555":1,"1556":1,"1557":1,"1558":1,"1559":1,"1560":1,"1561":1,"1562":1,"1563":1,"1564":1,"1565":1,"1566":1,"1567":1,"1568":1,"1569":1,"1570":1,"1571":1,"1572":1,"1573":1,"1574":1,"1575":1,"1576":1,"1577":1,"1578":1,"1579":1,"1580":1,"1581":1,"1582":1,"1583":1,"1584":1,"1585":1,"1586":1,"1587":1,"1588":1,"1589":1,"1590":1,"1591":1,"1592":1,"1593":1,"1594":1,"1595":1,"1596":1,"1597":1,"1598":1,"1599":1,"1793":1,"1794":1,"1795":1,"1796":1,"1797":1,"1798":1,"1799":1,"1800":1,"1801":1,"2038":1,"2039":1,"2040":1,"2041":1,"2042":1,"2043":1,"2044":1,"2045":1,"2046":1,"2047":1,"2048":1,"2098":1,"2099":1,"2100":1,"2101":1,"2102":1,"2103":1,"2104":1,"2105":1,"2106":1,"2107":1},"2":{"78":1,"80":3,"132":1,"265":1,"572":1,"1161":1,"1211":1,"1214":1,"1251":1,"1359":1,"1380":1,"1393":1,"1417":1,"1427":1,"1435":2,"1475":1,"1485":1,"1496":1,"1522":1,"1524":1,"1525":1,"1575":1,"1576":1,"1653":1,"1659":1,"1727":1,"1781":1,"2041":1,"2043":1,"2045":1,"2048":1,"2054":1,"2100":1,"2107":1,"2110":2},"3":{"0":7,"1":1,"17":2,"27":6,"39":1,"41":2,"54":2,"58":1,"59":1,"62":2,"78":1,"80":10,"81":3,"82":1,"85":1,"107":1,"109":7,"117":2,"121":6,"127":4,"128":7,"129":1,"131":1,"132":1,"135":5,"136":3,"137":2,"139":1,"140":2,"142":1,"147":4,"150":1,"160":1,"168":1,"189":1,"265":2,"305":3,"341":6,"370":1,"376":1,"381":1,"477":3,"545":7,"552":2,"565":1,"571":2,"572":16,"576":1,"585":1,"587":1,"590":1,"594":1,"609":13,"613":3,"618":1,"622":1,"631":1,"632":1,"633":1,"635":2,"657":1,"676":1,"678":1,"700":1,"718":2,"719":2,"749":6,"782":7,"798":1,"846":1,"849":1,"868":1,"881":9,"884":1,"899":1,"938":1,"939":1,"946":3,"954":1,"966":1,"971":7,"972":16,"974":4,"975":4,"980":2,"982":2,"1003":1,"1004":12,"1006":1,"1007":1,"1011":1,"1012":2,"1017":1,"1018":3,"1022":1,"1033":1,"1050":10,"1052":1,"1054":1,"1055":1,"1067":2,"1074":4,"1083":7,"1085":2,"1090":1,"1091":1,"1118":1,"1132":1,"1133":10,"1136":2,"1160":1,"1161":11,"1168":14,"1175":2,"1190":2,"1192":2,"1194":1,"1199":354,"1201":1,"1202":1,"1203":4,"1206":3,"1207":1179,"1208":6,"1210":1,"1211":5,"1212":6,"1213":1,"1214":5,"1216":7,"1217":1,"1229":2,"1230":1,"1231":1,"1232":1,"1235":1,"1236":4,"1237":35,"1239":2,"1243":1,"1247":12,"1249":1,"1251":1,"1257":2,"1259":46,"1262":4,"1263":1,"1270":16,"1271":9,"1272":2,"1285":102,"1289":1,"1299":33,"1300":5,"1302":2,"1305":1,"1308":17,"1309":8,"1310":37,"1311":12,"1312":2,"1315":3,"1321":1,"1322":48,"1323":65,"1325":1,"1338":7,"1340":5,"1342":1,"1343":1,"1344":1,"1345":1,"1348":1,"1349":35,"1351":2,"1358":82,"1359":2,"1360":1,"1368":21,"1370":3,"1376":1,"1379":15,"1380":1,"1381":1,"1388":2,"1393":1,"1394":97,"1395":72,"1401":15,"1403":3,"1408":1,"1411":1,"1414":51,"1415":7,"1416":25,"1417":1,"1426":14,"1427":6,"1430":34,"1431":1,"1435":2306,"1436":2,"1440":1,"1445":1,"1446":1,"1449":1,"1451":1,"1452":1,"1454":8,"1456":1,"1458":1,"1460":3,"1466":9,"1468":1,"1470":1,"1472":2,"1475":4,"1477":1,"1482":2,"1484":2,"1485":3,"1486":6,"1487":7,"1488":39,"1490":1,"1491":4,"1492":2,"1493":1,"1495":22,"1496":2,"1498":8,"1517":1,"1518":3,"1520":1,"1521":3,"1522":3,"1524":9,"1525":58,"1526":3,"1527":1,"1528":1,"1529":3,"1530":7,"1531":3,"1532":1,"1533":8,"1534":12,"1535":7,"1536":1,"1539":1,"1550":1,"1552":1,"1553":1,"1570":4,"1571":2,"1572":3,"1574":1,"1575":4,"1576":59,"1577":3,"1578":1,"1579":1,"1580":2,"1581":14,"1582":7,"1583":2,"1584":2,"1585":10,"1586":3,"1587":3,"1589":1,"1590":42,"1591":2,"1592":1,"1593":2,"1594":1,"1595":3,"1596":3,"1597":1,"1598":1,"1599":7,"1603":1,"1630":2,"1637":1,"1640":1,"1645":1,"1646":1,"1649":1,"1652":1,"1653":5,"1656":1,"1658":2,"1659":2,"1660":3,"1670":1,"1672":3,"1674":2,"1680":1,"1682":1,"1683":1,"1685":3,"1689":1,"1691":1,"1695":1,"1700":2,"1718":1,"1719":1,"1725":1,"1727":2,"1728":3,"1762":1,"1768":1,"1774":1,"1777":1,"1778":1,"1779":3,"1780":2,"1781":3,"1783":4,"1785":1,"1786":1,"1792":3,"1793":3,"1795":12,"1796":1,"1797":1,"1798":4,"1801":4,"1806":1,"1811":1,"1816":1,"1826":1,"1830":1,"1833":2,"1845":1,"1851":1,"1852":1,"1859":1,"1861":1,"1867":1,"1876":1,"1885":1,"1891":2,"1898":1,"1901":2,"1904":3,"1914":1,"1915":1,"1923":1,"1928":1,"1929":3,"1930":1,"1940":1,"1949":1,"1958":1,"1964":1,"1978":1,"1984":1,"1990":1,"1996":1,"2001":1,"2014":1,"2015":1,"2027":2,"2030":1,"2032":1,"2037":2,"2038":2,"2039":5,"2040":3,"2041":1,"2042":2,"2043":12,"2045":7,"2048":6,"2049":1,"2050":1,"2051":1,"2052":2,"2053":1,"2054":3,"2058":1,"2059":2,"2061":1,"2062":1,"2063":1,"2068":2,"2070":1,"2079":1,"2084":1,"2085":1,"2096":2,"2097":4,"2098":2,"2099":1,"2100":3,"2101":3,"2106":1,"2107":7,"2108":1,"2110":5,"2113":1,"2114":3,"2119":1,"2122":1,"2125":1,"2127":1,"2132":2,"2138":1,"2152":1,"2159":1,"2168":2,"2170":2,"2177":2,"2178":1,"2179":2,"2181":1,"2192":2,"2202":2,"2203":2,"2204":1,"2209":2,"2210":1,"2211":3,"2212":5,"2213":2,"2215":1,"2216":2,"2217":2,"2218":4,"2219":4,"2220":4,"2223":1,"2224":1,"2225":3,"2226":3,"2229":2,"2231":1,"2232":3,"2235":1,"2238":6,"2241":1,"2242":2,"2243":1,"2244":3}}],["archival",{"3":{"1466":1,"1567":1,"1630":1,"1677":1}}],["archivetickethandler",{"3":{"1199":1,"1207":1,"1435":5}}],["archiveticketcommand",{"3":{"1199":3,"1207":1,"1435":9}}],["archived",{"3":{"372":1,"1468":1,"1590":1,"1599":1,"1677":1}}],["archives",{"3":{"0":1,"443":1,"1466":1}}],["archive",{"0":{"1477":1},"3":{"0":1,"45":1,"47":1,"195":1,"562":1,"717":1,"764":1,"766":3,"947":1,"1116":1,"1284":1,"1285":2,"1322":1,"1341":1,"1349":1,"1388":1,"1440":1,"1454":1,"1466":6,"1468":1,"1470":1,"1472":6,"1473":7,"1474":2,"1477":4,"1481":2,"1482":3,"1483":1,"1485":1,"1533":1,"1729":1,"2033":1,"2035":3,"2123":1}}],["arthur",{"3":{"2219":1}}],["art",{"3":{"38":1}}],["articulo",{"3":{"1590":1}}],["article",{"1":{"1777":1,"1778":1},"3":{"365":1,"527":1,"1074":2,"1271":1,"1358":4,"1777":1,"1778":3,"1779":1,"1785":2,"1789":2,"1793":1,"1802":1,"1809":1,"1814":1,"1827":1,"1830":1,"1838":1,"1839":1,"1843":1,"1848":1,"1850":2,"1854":4,"1859":1,"1897":1,"1930":1,"1934":1,"1941":1,"1946":2,"1947":1,"1950":1,"1952":1,"1956":1,"1963":1,"1967":1,"1991":3,"1993":1,"1996":1,"2010":1,"2014":1,"2028":4,"2029":3,"2031":1,"2032":1,"2036":1,"2040":1,"2045":1,"2046":1,"2048":1,"2054":1,"2060":1,"2061":1,"2084":1,"2088":1,"2090":1,"2097":2,"2099":1,"2108":1,"2121":1,"2125":1,"2132":3,"2138":2,"2145":3,"2152":3,"2155":1,"2157":1,"2159":3,"2164":1,"2167":1,"2168":3,"2179":3,"2182":2,"2183":1,"2188":1,"2192":2,"2193":5,"2198":1,"2201":1,"2202":3,"2203":1,"2210":2,"2214":3,"2234":1,"2235":1,"2236":1}}],["articles",{"0":{"1778":1},"3":{"0":1,"1011":1,"1012":1,"1014":1,"1859":1,"2028":1,"2030":1,"2203":1,"2214":1,"2233":1}}],["artificial",{"3":{"1414":1,"1435":1,"1488":1}}],["artifacts",{"0":{"1209":1,"1481":1},"3":{"150":1,"529":1,"591":3,"837":1,"861":1,"862":1,"1191":1,"1308":2,"1358":1,"1395":1,"1426":1,"1435":1,"1453":1,"1455":3,"1460":1,"1472":2,"1481":1,"1488":2,"1489":1,"1491":2,"1495":1,"1517":1,"1552":1,"1576":1,"1590":1,"1670":1,"2046":1,"2232":1}}],["artifact",{"0":{"451":1,"1425":1},"3":{"0":2,"371":1,"373":1,"450":1,"451":2,"483":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"591":1,"609":1,"627":1,"690":1,"700":1,"709":1,"861":2,"862":1,"863":2,"873":1,"973":1,"974":1,"1019":1,"1090":1,"1216":1,"1259":1,"1270":1,"1308":4,"1310":2,"1322":2,"1338":2,"1358":2,"1395":1,"1414":1,"1415":1,"1430":2,"1432":2,"1435":13,"1444":1,"1453":2,"1454":3,"1455":3,"1464":1,"1465":1,"1470":1,"1481":1,"1482":1,"1491":1,"1495":4,"1525":1,"1576":2,"1947":1,"2044":1,"2046":1,"2131":1,"2179":1}}],["argon2id",{"3":{"1901":1,"1903":1,"1904":1}}],["argv",{"3":{"1322":1}}],["args",{"3":{"869":1,"1247":1,"1285":5,"1308":1,"1323":9,"1401":1,"1415":2,"1416":10,"1435":1,"1576":1}}],["arguing",{"3":{"1018":1,"1435":1}}],["argues",{"3":{"813":1,"945":1,"1017":1,"1051":1,"1259":1,"1270":1,"1285":2,"1299":1,"1322":1,"1323":1,"1338":2,"1395":5,"1435":1,"1458":1,"1951":1,"2162":1}}],["argue",{"0":{"1786":1},"3":{"465":1,"820":1,"871":1,"1435":2,"1815":2,"1832":2,"1851":2,"1945":1,"1963":1,"1995":1,"2000":1,"2047":2,"2058":1,"2078":2,"2084":2,"2121":2,"2151":2}}],["argued",{"3":{"109":2,"463":1,"716":1,"799":1,"905":5,"1270":5,"1310":1,"1322":1,"1323":1,"1331":1,"1338":1,"1394":1,"1435":2,"1446":1}}],["argumentguards",{"3":{"1435":1}}],["argumentguardfixture",{"3":{"1199":2,"1207":1,"1435":2}}],["argumenttypes",{"3":{"1285":3}}],["argumentoutofrangeexception",{"2":{"1229":1,"1398":1},"3":{"1229":2,"1299":1,"1398":1,"1401":1,"1435":4}}],["argumentexception",{"2":{"359":1,"1221":1,"1237":1,"1247":1,"1311":1,"1426":1,"2141":1},"3":{"359":1,"897":1,"1221":1,"1229":3,"1237":2,"1247":2,"1270":1,"1285":4,"1299":8,"1308":2,"1310":3,"1311":1,"1322":4,"1323":5,"1338":1,"1395":1,"1416":11,"1426":2,"1435":3,"2141":1}}],["argumentnullexception",{"2":{"359":1,"897":1,"1070":1,"1229":1,"1311":1,"1327":1,"2141":1},"3":{"359":1,"897":1,"1067":1,"1070":1,"1229":2,"1237":2,"1247":3,"1259":1,"1270":1,"1271":4,"1285":1,"1308":6,"1309":1,"1310":1,"1311":1,"1322":2,"1323":3,"1327":1,"1338":3,"1349":2,"1358":12,"1379":2,"1394":5,"1395":10,"1401":3,"1414":2,"1416":2,"1435":8,"2141":1}}],["arguments",{"3":{"0":1,"39":1,"43":1,"81":1,"95":1,"126":1,"135":1,"388":1,"474":1,"640":1,"799":1,"837":1,"861":1,"946":1,"1059":2,"1091":1,"1095":1,"1168":1,"1229":1,"1240":1,"1247":9,"1259":2,"1270":1,"1271":3,"1285":13,"1296":1,"1299":13,"1308":3,"1310":10,"1322":7,"1323":22,"1338":6,"1352":1,"1358":27,"1368":1,"1388":1,"1394":3,"1395":18,"1414":4,"1415":1,"1416":4,"1435":10,"1437":1,"1441":1,"1474":1,"1523":1,"1525":1,"1530":1,"1534":1,"1666":1,"1700":1,"1726":1,"2023":1,"2193":1,"2195":1,"2202":1}}],["argument",{"0":{"1824":1},"2":{"1988":1},"3":{"0":4,"26":1,"31":2,"93":1,"98":1,"109":1,"122":1,"135":2,"150":1,"160":1,"175":1,"186":2,"209":1,"259":1,"265":1,"266":1,"303":1,"318":4,"320":1,"341":1,"350":2,"359":1,"422":1,"460":1,"539":1,"545":2,"551":1,"556":2,"559":1,"565":1,"691":1,"707":1,"716":1,"724":1,"733":1,"749":2,"799":3,"801":2,"809":1,"811":1,"813":1,"827":4,"837":1,"838":1,"839":3,"855":1,"867":2,"875":1,"897":1,"905":1,"917":1,"946":2,"964":2,"1049":1,"1059":1,"1062":1,"1092":1,"1093":1,"1108":1,"1116":1,"1119":1,"1156":1,"1157":1,"1162":1,"1168":1,"1170":1,"1220":1,"1229":1,"1237":6,"1244":1,"1247":34,"1253":1,"1259":5,"1263":1,"1266":1,"1270":11,"1271":4,"1285":34,"1291":1,"1296":1,"1299":14,"1300":4,"1308":9,"1309":6,"1310":11,"1311":1,"1322":9,"1323":19,"1327":1,"1338":9,"1349":5,"1358":44,"1368":5,"1379":5,"1381":1,"1394":18,"1395":38,"1401":3,"1414":23,"1416":5,"1426":4,"1431":1,"1435":61,"1441":1,"1444":1,"1452":1,"1454":4,"1458":1,"1464":1,"1474":1,"1488":1,"1489":1,"1576":1,"1637":1,"1655":2,"1666":1,"1684":2,"1700":2,"1729":1,"1787":1,"1805":1,"1806":1,"1820":1,"1825":1,"1826":1,"1858":1,"1873":1,"1897":1,"1903":1,"1945":1,"1951":1,"1970":1,"1974":1,"1976":1,"1988":2,"1993":2,"1994":1,"1995":1,"2023":1,"2024":2,"2071":1,"2125":1,"2134":1,"2141":1,"2169":1,"2179":1,"2194":1}}],["arg",{"2":{"871":1},"3":{"0":1,"91":1,"869":2,"871":1,"1212":1,"1285":3,"1310":1,"1401":1,"1444":2,"1454":3,"1465":2}}],["arise",{"3":{"1285":1}}],["arithmetically",{"3":{"1435":1}}],["arithmetic",{"0":{"1242":1},"3":{"335":1,"628":1,"658":1,"760":1,"799":1,"803":1,"804":1,"805":1,"1019":1,"1051":1,"1054":1,"1237":4,"1242":2,"1243":1,"1247":4,"1254":1,"1259":1,"1308":1,"1322":1,"1323":1,"1338":2,"1349":1,"1358":3,"1365":1,"1368":1,"1395":3,"1435":4,"1437":2,"1473":2,"1474":1,"1495":1,"1814":1}}],["arity",{"3":{"0":2,"328":1,"921":1,"924":1,"926":1,"1270":4,"1288":1,"1299":3,"1310":3,"1435":20,"1495":4,"1670":1}}],["ariarole",{"3":{"1435":4}}],["arial",{"3":{"1323":1}}],["arialabel",{"3":{"1323":6}}],["aria",{"3":{"0":1,"412":1,"618":2,"620":1,"1323":14,"1388":1,"1394":5,"1395":1,"1401":1,"1414":1,"1416":5,"1432":1,"1435":7,"1487":2,"1525":3,"1557":1,"1576":2,"1590":2,"1595":1,"1604":1,"1606":2,"1642":3,"1643":1,"1738":1,"1740":2,"2056":1}}],["arrows",{"3":{"1338":1,"1504":1,"1516":1,"2038":2}}],["arrow",{"3":{"1195":1,"1259":1,"1308":1,"1309":1,"1310":1,"1416":1,"1435":3,"1502":1}}],["arriving",{"3":{"1":1,"159":1,"202":2,"352":1,"428":1,"702":1,"809":1,"845":1,"1049":1,"1067":1,"1109":1,"1184":1,"1249":1,"1310":1,"1323":3,"1358":1,"1365":2,"1395":1,"1414":1,"1416":1,"1437":1,"1466":1,"1638":1,"1908":1,"1910":1,"2165":1,"2180":1}}],["arrive",{"3":{"21":1,"24":1,"110":1,"235":1,"402":1,"548":1,"549":1,"585":1,"601":1,"674":1,"678":1,"684":1,"717":1,"774":1,"789":1,"829":1,"870":1,"880":1,"965":1,"979":1,"1020":1,"1033":1,"1093":1,"1123":1,"1153":1,"1229":1,"1237":1,"1241":1,"1247":1,"1259":3,"1270":1,"1273":1,"1280":1,"1299":4,"1308":2,"1310":3,"1316":1,"1317":1,"1323":6,"1338":3,"1349":2,"1358":5,"1363":1,"1368":1,"1379":1,"1381":1,"1394":5,"1395":10,"1401":2,"1414":2,"1416":2,"1435":1,"1437":2,"1442":1,"1452":1,"1466":2,"1467":1,"1640":1,"1663":1,"1672":1,"1681":1,"1684":1,"1685":1,"1693":1,"1726":1,"1791":1,"1889":1,"1908":1,"1933":1,"1943":1,"1999":1,"2178":1,"2193":1}}],["arrived",{"3":{"0":1,"423":1,"563":1,"696":1,"728":1,"790":1,"809":2,"813":1,"1259":1,"1270":3,"1286":1,"1292":2,"1299":3,"1308":1,"1323":2,"1358":2,"1394":4,"1395":1,"1401":2,"1414":1,"1435":3,"1488":1,"1495":1,"1685":1,"1726":1,"1758":1,"1764":2,"1805":1,"1874":1,"1889":1,"2092":1,"2157":1,"2166":1}}],["arrives",{"3":{"0":3,"113":1,"148":1,"255":1,"265":1,"350":1,"353":1,"390":1,"403":1,"415":1,"423":1,"535":2,"539":1,"573":1,"583":1,"631":1,"633":1,"673":1,"674":1,"689":1,"692":1,"698":2,"714":1,"741":1,"827":1,"829":1,"831":1,"869":1,"881":1,"897":1,"914":1,"1018":1,"1050":1,"1091":1,"1123":1,"1232":1,"1237":1,"1247":1,"1259":1,"1261":1,"1271":1,"1285":5,"1299":4,"1300":1,"1304":1,"1308":1,"1310":2,"1319":1,"1322":4,"1323":9,"1325":1,"1334":1,"1338":6,"1349":1,"1357":2,"1358":18,"1365":1,"1368":2,"1379":2,"1394":20,"1395":20,"1400":1,"1401":5,"1414":6,"1416":3,"1432":1,"1435":8,"1441":1,"1442":1,"1443":1,"1446":3,"1451":1,"1454":1,"1475":1,"1487":1,"1625":1,"1660":1,"1684":2,"1685":2,"1699":1,"1723":1,"1726":3,"1727":1,"1748":1,"1814":1,"1882":1,"1889":1,"1908":1,"1910":1,"1921":1,"1959":1,"1991":1,"2015":1,"2026":1,"2040":1,"2066":1,"2088":1,"2100":1,"2148":1,"2149":1,"2153":1,"2160":2,"2166":2,"2168":1}}],["arrivals",{"3":{"1395":1}}],["arrival",{"3":{"0":2,"635":1,"689":1,"691":1,"726":1,"1285":1,"1378":1,"1394":2,"1395":19,"1415":1,"1431":1,"1435":1,"1466":1,"1630":1,"1660":1,"1685":1,"1698":1,"1726":2,"1914":2,"1999":1,"2001":1}}],["arranging",{"3":{"1670":1}}],["arranges",{"3":{"1440":1}}],["arrange",{"3":{"1435":8,"1487":2,"1823":1}}],["arranged",{"3":{"1343":1}}],["arrangement",{"3":{"0":2,"93":1,"96":1,"109":1,"837":1,"881":1,"930":1,"1247":1,"1285":1,"1308":1,"1310":1,"1349":1,"1358":2,"1379":1,"1394":1,"1435":3,"1437":1,"1489":1}}],["arrays",{"3":{"81":1,"696":1,"698":1,"1247":1,"1285":1,"1299":1,"1310":6,"1349":3,"1404":1,"1414":2,"1422":1,"1426":1,"1435":2,"1638":1}}],["array",{"3":{"0":3,"109":2,"111":3,"122":2,"135":1,"243":1,"260":1,"265":1,"359":1,"388":1,"393":1,"418":1,"420":1,"422":1,"423":1,"424":1,"591":1,"603":1,"609":6,"610":1,"881":1,"1175":2,"1225":1,"1229":9,"1237":1,"1239":1,"1242":2,"1247":8,"1259":3,"1263":1,"1270":2,"1271":4,"1285":10,"1299":10,"1310":10,"1322":3,"1323":4,"1338":3,"1349":6,"1358":17,"1390":1,"1394":7,"1395":4,"1401":1,"1414":5,"1426":2,"1435":17,"1466":9,"1472":2,"1576":1,"1585":1,"1631":1,"1639":1,"1804":4,"1850":1,"1858":1,"1921":1,"1926":1,"1928":1,"1975":1,"2157":2}}],["armphasetimerforeventend",{"3":{"1394":1}}],["arming",{"3":{"539":1,"1299":3,"1599":1,"2196":1}}],["armasync",{"2":{"814":1},"3":{"539":1,"540":1,"814":1}}],["armed",{"3":{"0":1,"534":2,"536":2,"537":1,"539":2,"905":1,"909":1,"1289":1,"1299":5,"1382":1,"1415":2}}],["arm",{"3":{"0":4,"62":1,"63":1,"164":1,"166":2,"171":1,"609":1,"610":1,"611":1,"626":1,"756":3,"758":1,"766":1,"814":1,"1034":2,"1075":4,"1100":1,"1247":13,"1270":4,"1285":3,"1310":4,"1322":1,"1338":1,"1394":4,"1395":4,"1401":6,"1414":1,"1415":2,"1416":3,"1435":5,"1454":5,"1458":1,"1463":1,"1465":2,"1466":11,"1469":1,"1471":3,"1474":1,"1480":1,"1588":1,"1590":1,"1595":1,"1596":1,"1685":1,"1999":1,"2033":1,"2157":1,"2196":1,"2218":1,"2219":1}}],["arms",{"3":{"0":3,"539":3,"794":1,"809":1,"813":2,"905":1,"1042":1,"1045":1,"1247":2,"1270":1,"1299":4,"1323":2,"1336":1,"1358":1,"1392":1,"1394":3,"1395":1,"1415":1,"1416":2,"1916":1,"2160":1,"2164":1,"2166":2,"2196":1}}],["around",{"0":{"1386":1},"3":{"0":2,"24":1,"58":1,"62":1,"71":1,"109":1,"113":1,"118":1,"126":1,"138":1,"150":1,"155":1,"260":1,"265":1,"281":1,"285":1,"396":1,"463":1,"534":1,"555":1,"607":5,"627":1,"638":2,"642":1,"650":1,"655":1,"684":1,"733":1,"764":1,"819":3,"826":1,"827":1,"836":1,"921":1,"926":1,"971":1,"987":1,"1019":1,"1020":1,"1043":1,"1049":1,"1051":1,"1066":1,"1090":1,"1133":1,"1169":1,"1229":1,"1237":2,"1246":1,"1247":3,"1256":1,"1259":4,"1260":1,"1262":1,"1267":1,"1270":6,"1285":3,"1289":1,"1290":1,"1293":2,"1299":13,"1308":3,"1309":2,"1310":4,"1312":1,"1321":1,"1322":5,"1323":9,"1338":3,"1349":5,"1352":1,"1354":1,"1356":1,"1358":16,"1368":2,"1374":1,"1379":3,"1394":5,"1395":11,"1401":7,"1402":1,"1414":4,"1415":1,"1416":3,"1434":1,"1435":8,"1437":6,"1466":2,"1480":1,"1487":1,"1495":6,"1536":1,"1556":1,"1584":1,"1634":1,"1639":1,"1640":1,"1642":1,"1650":2,"1674":1,"1699":1,"1711":1,"1719":1,"1748":1,"1789":1,"1796":1,"1810":1,"1817":1,"1826":1,"1850":1,"1903":1,"1957":1,"1965":1,"2031":1,"2037":1,"2061":1,"2071":1,"2074":2,"2078":2,"2109":1,"2153":1,"2155":1,"2173":1,"2177":1,"2179":1,"2208":1}}],["areflagged",{"3":{"1435":10}}],["areflaggedundertheirsourcemember",{"3":{"1435":4}}],["arepinnedtooneversion",{"3":{"1435":2}}],["arecomplete",{"3":{"1435":3}}],["arestserviceanswersoverh2c",{"2":{"1448":1},"3":{"1338":1,"1448":1}}],["arerestricted",{"2":{"1083":1},"3":{"1083":1,"1435":3}}],["areexplicitlyoptedin",{"2":{"1083":1},"3":{"1083":1,"1435":3}}],["arediscovered",{"2":{"609":1},"3":{"609":1,"1435":3}}],["areaccepted",{"3":{"1435":1}}],["areallowlisted",{"3":{"1435":2}}],["areas",{"0":{"1387":1,"1391":1},"3":{"1349":1,"1394":1,"1430":1,"1762":1}}],["areaof",{"3":{"1323":2}}],["area",{"3":{"442":1,"801":1,"1058":1,"1299":1,"1310":1,"1322":1,"1323":6,"1380":2,"1388":1,"1391":2,"1393":2,"1394":3,"1395":1,"1413":1,"1454":1,"1479":1,"1488":2,"1713":1,"2125":2}}],["are",{"0":{"93":1,"1316":1,"1362":1,"1472":1,"1520":1,"1803":1,"2143":1,"2199":1},"1":{"1105":1,"1106":1,"1107":1,"1108":1,"1109":1,"1110":1,"1111":1,"1112":1,"1862":1,"1863":1,"1864":1,"1865":1,"1866":1,"1867":1,"2028":1,"2029":1,"2030":1,"2031":1,"2032":1,"2033":1,"2034":1,"2035":1,"2036":1,"2037":1},"2":{"453":1},"3":{"0":102,"3":1,"4":1,"10":1,"12":1,"13":1,"15":1,"16":1,"17":4,"19":3,"21":2,"22":1,"23":3,"24":2,"26":2,"27":3,"30":1,"31":1,"33":1,"34":1,"39":7,"41":3,"43":1,"46":1,"47":2,"54":1,"59":2,"61":1,"62":1,"63":4,"64":1,"71":2,"73":2,"74":2,"77":1,"79":1,"80":1,"81":4,"82":1,"86":1,"88":1,"93":1,"94":1,"95":3,"99":1,"100":4,"101":1,"102":1,"103":1,"108":1,"109":5,"110":2,"115":1,"117":3,"120":1,"121":3,"122":2,"123":1,"125":2,"126":3,"127":1,"128":2,"130":6,"131":1,"134":2,"135":20,"136":10,"137":5,"138":3,"139":7,"140":3,"141":3,"142":12,"145":9,"147":3,"150":7,"152":1,"153":1,"155":2,"156":1,"157":2,"159":3,"160":3,"161":2,"166":4,"168":3,"171":1,"174":2,"175":6,"177":3,"178":5,"179":1,"181":2,"184":1,"186":4,"188":3,"189":1,"193":1,"194":1,"195":2,"198":2,"199":1,"201":2,"202":4,"203":1,"206":2,"207":1,"212":1,"214":2,"216":1,"218":1,"219":5,"220":1,"221":2,"224":2,"226":1,"229":1,"230":4,"231":3,"233":2,"235":4,"241":3,"242":2,"243":5,"244":2,"245":3,"246":3,"249":1,"250":2,"252":1,"253":2,"254":4,"255":4,"256":2,"257":1,"258":1,"259":4,"260":3,"265":11,"266":1,"267":1,"272":1,"274":1,"276":2,"281":1,"283":1,"285":2,"293":1,"295":1,"300":1,"302":1,"305":2,"306":1,"308":1,"309":1,"310":2,"312":1,"316":1,"317":2,"318":2,"319":1,"323":1,"324":3,"326":2,"328":1,"329":1,"330":1,"331":3,"332":3,"334":1,"335":1,"337":2,"341":4,"343":1,"344":1,"345":2,"349":1,"350":4,"351":2,"353":2,"354":2,"358":1,"359":1,"360":1,"361":3,"365":2,"368":1,"369":1,"370":4,"371":1,"372":1,"373":3,"374":2,"375":3,"378":1,"379":1,"380":2,"382":1,"383":2,"384":3,"387":1,"388":2,"391":2,"392":1,"393":2,"395":6,"396":1,"397":6,"398":2,"399":3,"400":2,"402":4,"405":1,"406":1,"407":3,"408":1,"409":5,"413":4,"414":1,"418":1,"419":1,"420":3,"422":1,"425":2,"429":1,"433":1,"434":3,"435":1,"436":1,"438":2,"439":2,"440":1,"444":2,"446":1,"448":2,"449":1,"450":1,"451":2,"452":1,"453":2,"459":1,"463":2,"464":2,"465":3,"466":2,"468":1,"470":4,"471":2,"472":2,"473":2,"475":1,"477":2,"480":2,"482":2,"484":1,"486":3,"487":1,"488":2,"489":2,"490":4,"491":5,"492":4,"493":6,"494":5,"495":2,"497":6,"498":1,"500":1,"501":2,"507":1,"508":1,"509":1,"511":3,"512":1,"513":1,"514":2,"517":1,"518":1,"519":1,"522":2,"523":2,"524":2,"526":2,"527":4,"528":4,"529":1,"530":4,"531":1,"534":9,"536":5,"537":2,"538":2,"539":3,"540":3,"543":3,"544":2,"545":5,"546":2,"547":3,"549":3,"550":2,"551":4,"552":2,"555":1,"556":8,"562":2,"564":3,"565":1,"567":1,"570":1,"571":1,"572":5,"573":1,"574":3,"575":3,"577":3,"578":5,"583":7,"584":2,"585":5,"586":3,"589":2,"590":1,"591":6,"592":4,"593":4,"594":1,"599":10,"600":2,"601":3,"602":1,"604":2,"607":10,"609":3,"610":1,"611":6,"612":3,"616":4,"617":1,"618":3,"620":4,"621":2,"624":5,"626":8,"627":1,"628":1,"629":4,"632":3,"633":4,"635":2,"638":6,"639":2,"640":6,"641":4,"642":3,"643":1,"644":1,"649":2,"650":3,"651":2,"656":1,"657":7,"658":3,"659":3,"660":1,"664":1,"665":5,"666":1,"669":1,"670":1,"672":2,"673":1,"674":5,"675":1,"676":2,"677":2,"680":1,"681":2,"682":4,"683":1,"684":3,"685":1,"688":1,"689":1,"690":10,"691":5,"692":3,"693":1,"698":7,"699":2,"700":5,"702":1,"703":2,"707":5,"708":3,"713":1,"714":1,"715":5,"717":2,"720":2,"721":1,"724":1,"725":4,"726":2,"727":1,"729":1,"731":1,"732":1,"733":2,"735":1,"736":2,"740":1,"741":5,"743":3,"744":2,"747":2,"749":7,"751":7,"755":1,"756":2,"758":1,"759":1,"760":3,"761":1,"764":5,"766":6,"767":1,"768":2,"769":1,"774":3,"775":1,"776":3,"780":2,"782":3,"784":2,"785":1,"786":1,"789":1,"790":3,"794":1,"797":1,"798":2,"799":5,"800":1,"801":1,"802":1,"803":6,"804":2,"805":4,"809":3,"810":3,"813":1,"814":1,"818":3,"819":5,"821":3,"822":1,"825":5,"826":5,"827":16,"828":1,"829":6,"830":3,"831":2,"832":6,"833":4,"836":1,"837":2,"838":4,"840":5,"844":1,"846":1,"848":2,"849":3,"850":2,"853":1,"854":2,"855":1,"857":1,"860":3,"861":6,"862":1,"863":2,"864":1,"867":2,"868":4,"869":1,"871":3,"872":3,"873":2,"874":2,"875":4,"876":3,"877":1,"879":2,"880":1,"881":5,"882":1,"883":4,"884":3,"889":2,"891":1,"892":2,"897":3,"900":2,"903":1,"904":2,"905":10,"906":2,"907":2,"910":2,"912":1,"913":1,"914":4,"916":2,"917":1,"918":1,"922":1,"923":2,"924":1,"926":4,"927":2,"930":2,"931":3,"932":2,"938":1,"941":1,"944":1,"946":5,"949":1,"953":1,"954":1,"958":1,"959":1,"960":1,"963":3,"964":8,"966":3,"967":1,"968":1,"971":1,"972":4,"973":3,"974":2,"975":1,"979":1,"980":6,"981":3,"982":3,"983":2,"986":1,"987":1,"988":6,"989":1,"991":2,"995":1,"996":3,"998":1,"999":1,"1003":1,"1004":6,"1005":1,"1006":2,"1011":1,"1012":3,"1014":1,"1016":3,"1017":1,"1018":15,"1019":1,"1020":2,"1021":3,"1024":2,"1025":1,"1026":3,"1027":1,"1028":1,"1029":1,"1030":2,"1032":1,"1033":2,"1034":8,"1036":1,"1037":2,"1042":2,"1043":1,"1044":1,"1049":2,"1050":9,"1051":3,"1052":8,"1054":5,"1055":4,"1058":2,"1059":8,"1061":2,"1065":1,"1067":6,"1069":3,"1073":1,"1074":8,"1075":5,"1077":1,"1078":2,"1079":1,"1082":3,"1083":1,"1084":1,"1085":1,"1089":1,"1090":1,"1091":12,"1092":4,"1093":7,"1095":1,"1096":1,"1099":1,"1100":2,"1101":1,"1102":1,"1103":1,"1105":1,"1107":1,"1108":4,"1115":1,"1116":7,"1117":4,"1118":3,"1119":2,"1123":2,"1124":6,"1125":1,"1126":3,"1127":1,"1128":1,"1133":2,"1135":1,"1140":3,"1142":1,"1143":1,"1150":2,"1152":1,"1160":1,"1161":4,"1163":3,"1168":10,"1169":1,"1174":1,"1175":4,"1176":1,"1178":1,"1182":1,"1184":4,"1186":1,"1188":1,"1190":1,"1191":10,"1193":2,"1196":4,"1200":1,"1201":4,"1202":1,"1203":2,"1211":2,"1212":20,"1213":3,"1214":3,"1215":2,"1216":1,"1217":2,"1218":5,"1220":1,"1221":2,"1222":1,"1223":3,"1225":1,"1226":3,"1227":6,"1228":1,"1229":24,"1230":2,"1231":8,"1232":4,"1233":1,"1234":6,"1235":1,"1236":5,"1237":71,"1238":4,"1239":4,"1240":2,"1241":3,"1242":1,"1243":4,"1244":3,"1246":1,"1247":72,"1248":1,"1249":4,"1251":2,"1252":4,"1253":2,"1254":2,"1256":4,"1257":1,"1258":1,"1259":53,"1260":2,"1261":3,"1262":2,"1263":6,"1264":5,"1265":3,"1266":3,"1267":4,"1269":5,"1270":113,"1271":36,"1272":1,"1273":4,"1274":2,"1275":2,"1276":4,"1277":5,"1278":4,"1279":4,"1280":2,"1282":7,"1283":2,"1284":5,"1285":287,"1286":3,"1287":2,"1288":4,"1289":7,"1290":3,"1291":2,"1292":4,"1293":4,"1294":2,"1295":1,"1296":7,"1297":1,"1298":1,"1299":174,"1300":37,"1301":2,"1302":1,"1303":3,"1306":4,"1308":82,"1309":38,"1310":172,"1311":19,"1312":3,"1313":2,"1314":1,"1315":6,"1316":4,"1317":6,"1318":2,"1319":5,"1320":2,"1321":3,"1322":128,"1323":260,"1324":2,"1325":4,"1327":1,"1329":3,"1331":8,"1332":5,"1333":1,"1334":1,"1335":2,"1336":3,"1337":4,"1338":178,"1340":1,"1341":13,"1342":4,"1343":6,"1344":2,"1345":3,"1346":6,"1348":6,"1349":179,"1351":3,"1352":5,"1353":2,"1354":4,"1356":3,"1357":1,"1358":493,"1361":6,"1362":2,"1363":4,"1365":6,"1366":1,"1367":4,"1368":60,"1369":3,"1370":1,"1371":1,"1372":5,"1373":2,"1374":3,"1375":1,"1376":3,"1377":2,"1378":4,"1379":92,"1381":2,"1382":6,"1383":2,"1384":1,"1388":3,"1389":2,"1390":2,"1391":4,"1392":2,"1393":3,"1394":234,"1395":354,"1396":1,"1397":9,"1398":6,"1400":4,"1401":99,"1402":3,"1404":3,"1405":7,"1406":1,"1407":2,"1408":4,"1409":2,"1410":1,"1411":1,"1412":1,"1413":5,"1414":154,"1415":47,"1416":139,"1417":2,"1420":1,"1421":3,"1422":3,"1423":6,"1424":1,"1425":1,"1426":18,"1427":4,"1428":7,"1429":3,"1430":11,"1431":1,"1432":4,"1433":3,"1434":6,"1435":517,"1436":1,"1437":53,"1440":24,"1441":12,"1442":22,"1443":5,"1444":4,"1445":2,"1446":10,"1447":1,"1448":4,"1450":1,"1451":4,"1452":16,"1453":6,"1454":50,"1455":7,"1456":5,"1457":6,"1458":2,"1459":6,"1461":3,"1462":5,"1464":7,"1465":7,"1466":61,"1467":1,"1469":4,"1470":2,"1471":7,"1472":2,"1473":11,"1474":4,"1475":5,"1476":4,"1477":2,"1478":3,"1479":2,"1480":7,"1481":1,"1483":3,"1484":5,"1485":11,"1486":6,"1487":21,"1488":19,"1489":9,"1490":2,"1491":9,"1493":1,"1494":2,"1495":55,"1496":5,"1497":4,"1498":1,"1499":5,"1501":1,"1502":2,"1506":1,"1507":3,"1508":1,"1511":1,"1514":1,"1516":3,"1518":1,"1520":2,"1522":1,"1523":4,"1524":1,"1525":35,"1526":1,"1530":16,"1531":5,"1532":1,"1533":7,"1535":4,"1536":2,"1537":2,"1538":1,"1539":1,"1540":1,"1542":3,"1543":4,"1544":3,"1545":3,"1546":6,"1547":3,"1549":1,"1550":1,"1551":1,"1552":4,"1553":3,"1554":1,"1557":1,"1561":1,"1562":2,"1564":1,"1565":1,"1567":1,"1568":1,"1569":2,"1570":3,"1571":12,"1573":2,"1574":5,"1576":41,"1577":4,"1579":2,"1580":2,"1581":7,"1582":2,"1583":2,"1584":13,"1585":2,"1587":1,"1590":20,"1591":2,"1593":2,"1595":6,"1596":3,"1598":1,"1600":1,"1604":1,"1606":3,"1610":3,"1613":1,"1620":1,"1622":1,"1625":4,"1626":6,"1629":27,"1630":74,"1631":16,"1632":6,"1633":4,"1634":17,"1635":6,"1636":3,"1637":17,"1638":48,"1639":17,"1640":30,"1643":1,"1644":3,"1645":2,"1647":1,"1648":2,"1649":1,"1650":6,"1651":3,"1652":10,"1653":2,"1655":1,"1657":1,"1660":3,"1661":4,"1663":1,"1664":1,"1665":4,"1666":2,"1667":5,"1668":1,"1669":3,"1670":3,"1671":1,"1672":2,"1674":2,"1676":9,"1678":1,"1679":3,"1681":1,"1682":2,"1683":4,"1684":7,"1685":13,"1688":2,"1690":1,"1692":2,"1694":2,"1696":2,"1697":3,"1698":1,"1699":4,"1700":4,"1701":2,"1702":1,"1705":1,"1706":1,"1707":1,"1709":1,"1711":1,"1716":1,"1717":3,"1718":2,"1719":3,"1720":3,"1726":13,"1727":3,"1728":2,"1729":1,"1730":1,"1732":1,"1733":1,"1734":1,"1735":4,"1737":3,"1738":1,"1740":3,"1741":1,"1747":6,"1748":3,"1749":4,"1750":1,"1754":4,"1757":2,"1758":2,"1759":1,"1760":4,"1763":1,"1764":19,"1765":2,"1766":4,"1768":3,"1770":1,"1771":2,"1773":1,"1775":9,"1776":2,"1778":1,"1780":2,"1781":3,"1782":2,"1783":1,"1786":1,"1787":1,"1788":3,"1789":2,"1791":2,"1793":2,"1794":1,"1795":1,"1797":1,"1798":1,"1800":3,"1801":2,"1803":4,"1804":2,"1805":3,"1806":1,"1807":2,"1808":2,"1809":9,"1810":7,"1811":4,"1813":2,"1814":10,"1815":3,"1817":3,"1818":1,"1819":1,"1820":1,"1821":2,"1822":1,"1823":1,"1824":5,"1825":3,"1827":1,"1829":1,"1831":2,"1836":1,"1837":1,"1838":1,"1839":8,"1840":3,"1841":3,"1842":2,"1843":5,"1844":3,"1847":1,"1848":1,"1849":1,"1850":2,"1851":1,"1852":2,"1853":2,"1854":1,"1855":1,"1859":1,"1860":6,"1861":1,"1862":1,"1863":1,"1864":2,"1871":1,"1874":1,"1875":2,"1878":2,"1880":1,"1882":2,"1883":1,"1884":3,"1886":1,"1887":3,"1888":1,"1889":1,"1890":3,"1891":1,"1892":1,"1895":1,"1897":2,"1898":1,"1899":2,"1900":4,"1901":3,"1902":3,"1903":2,"1904":1,"1906":2,"1908":7,"1909":1,"1910":5,"1912":1,"1914":1,"1915":2,"1916":2,"1917":1,"1918":1,"1919":6,"1920":1,"1921":4,"1922":7,"1923":1,"1926":1,"1928":1,"1930":1,"1932":2,"1933":1,"1934":3,"1935":4,"1938":1,"1939":1,"1940":1,"1941":2,"1942":1,"1943":1,"1946":1,"1947":1,"1948":5,"1949":1,"1950":2,"1951":3,"1952":4,"1953":2,"1954":1,"1955":1,"1956":1,"1957":1,"1958":2,"1959":3,"1960":3,"1961":3,"1963":8,"1965":2,"1966":1,"1967":2,"1968":3,"1970":2,"1972":3,"1976":4,"1977":1,"1978":3,"1979":1,"1980":1,"1981":1,"1982":1,"1983":3,"1985":1,"1986":2,"1987":2,"1989":4,"1991":1,"1993":2,"1994":8,"1995":2,"1996":1,"1997":2,"1998":1,"1999":8,"2000":4,"2001":2,"2003":1,"2006":1,"2008":1,"2009":5,"2010":2,"2011":1,"2012":6,"2014":1,"2019":1,"2021":1,"2023":4,"2024":2,"2026":5,"2027":1,"2028":3,"2029":4,"2032":3,"2033":2,"2034":2,"2035":2,"2036":3,"2037":2,"2039":1,"2040":2,"2041":2,"2042":6,"2043":4,"2046":2,"2047":3,"2048":1,"2050":1,"2051":1,"2052":1,"2053":3,"2054":4,"2055":3,"2056":4,"2058":1,"2059":2,"2060":2,"2061":1,"2062":2,"2063":1,"2066":2,"2067":3,"2068":2,"2069":1,"2071":2,"2073":1,"2074":1,"2076":2,"2078":3,"2079":1,"2080":4,"2082":3,"2085":2,"2086":4,"2087":4,"2089":1,"2090":1,"2094":2,"2095":2,"2096":4,"2097":7,"2099":1,"2100":5,"2102":1,"2103":3,"2104":2,"2106":3,"2107":1,"2108":5,"2109":2,"2110":4,"2111":2,"2113":1,"2114":1,"2115":1,"2116":2,"2117":1,"2120":2,"2123":4,"2124":6,"2125":3,"2126":2,"2127":2,"2129":1,"2130":3,"2131":4,"2132":2,"2133":1,"2134":1,"2135":2,"2137":5,"2138":2,"2139":3,"2141":5,"2142":2,"2143":2,"2144":3,"2145":1,"2146":1,"2147":3,"2148":3,"2149":5,"2150":1,"2151":1,"2153":4,"2155":5,"2156":4,"2157":2,"2158":6,"2159":2,"2161":1,"2162":2,"2163":3,"2164":2,"2166":4,"2167":1,"2170":3,"2171":2,"2172":1,"2173":4,"2175":2,"2176":2,"2177":1,"2178":5,"2180":1,"2181":1,"2182":1,"2184":1,"2187":2,"2188":2,"2189":2,"2190":3,"2191":1,"2194":1,"2197":2,"2198":1,"2199":2,"2200":1,"2201":4,"2202":2,"2206":1,"2208":1,"2210":5,"2213":1,"2215":1,"2226":1,"2229":5,"2231":5,"2233":2,"2241":1,"2243":1}}],["arenotthrottledevenafterthewindowisexhausted",{"3":{"1435":1}}],["arenotflagged",{"3":{"1435":10}}],["arenotificationsenabled",{"3":{"1416":1}}],["aren",{"3":{"0":1,"1537":1,"1542":1,"2161":1,"2181":1}}],["a",{"0":{"1":1,"91":1,"92":1,"93":1,"95":1,"137":1,"170":1,"451":1,"813":1,"1193":1,"1218":1,"1222":1,"1223":2,"1224":1,"1225":1,"1228":1,"1233":1,"1235":1,"1240":1,"1246":1,"1250":1,"1265":1,"1293":1,"1294":1,"1304":2,"1345":1,"1351":1,"1356":1,"1381":1,"1385":1,"1388":1,"1399":1,"1405":1,"1419":2,"1421":1,"1425":1,"1428":3,"1429":1,"1432":1,"1434":1,"1441":1,"1522":1,"1529":1,"1573":1,"1580":1,"1587":1,"1594":1,"1609":1,"1655":1,"1684":1,"1721":1,"1743":1,"1794":1,"1803":1,"1804":1,"1809":1,"1830":2,"1836":1,"1841":1,"1857":1,"1858":1,"1864":1,"1870":2,"1871":1,"1872":1,"1873":2,"1878":2,"1879":1,"1881":1,"1882":1,"1888":2,"1894":1,"1901":1,"1918":3,"1932":1,"1944":1,"1945":1,"1947":1,"1953":1,"1954":1,"1956":1,"1961":2,"1974":1,"2024":1,"2039":1,"2044":1,"2045":1,"2046":1,"2055":1,"2056":1,"2057":1,"2062":1,"2065":1,"2072":2,"2074":1,"2089":2,"2090":1,"2092":1,"2101":1,"2111":1,"2125":2,"2135":1,"2148":1,"2157":1,"2162":2,"2163":1,"2165":2,"2166":1,"2171":1,"2175":1,"2183":1,"2186":1,"2188":1,"2189":1,"2196":1,"2230":1},"1":{"114":1,"115":1,"116":1,"117":1,"118":1,"119":1,"120":1,"121":1,"122":1,"123":1,"124":1,"125":1,"126":1,"127":1,"128":1,"211":1,"212":1,"213":1,"214":1,"215":1,"216":1,"217":1,"218":1,"219":1,"220":1,"221":1,"240":1,"241":1,"242":1,"243":1,"244":1,"245":1,"246":1,"247":1,"248":1,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"260":1,"261":1,"327":1,"328":1,"329":1,"330":1,"331":1,"332":1,"333":1,"334":1,"335":1,"336":1,"337":1,"496":1,"497":1,"498":1,"499":1,"500":1,"501":1,"502":1,"503":1,"533":1,"534":1,"535":1,"536":1,"537":1,"538":1,"539":1,"540":1,"541":1,"561":1,"562":1,"563":1,"564":1,"565":1,"566":1,"567":1,"568":1,"569":1,"570":1,"571":1,"572":1,"573":1,"574":1,"575":1,"576":1,"577":1,"578":1,"579":1,"615":1,"616":1,"617":1,"618":1,"619":1,"620":1,"621":1,"622":1,"722":1,"723":1,"724":1,"725":1,"726":1,"727":1,"728":1,"729":1,"738":1,"739":1,"740":1,"741":1,"742":1,"743":1,"744":1,"745":1,"763":1,"764":1,"765":1,"766":1,"767":1,"768":1,"769":1,"770":1,"779":1,"780":1,"781":1,"782":1,"783":1,"784":1,"785":1,"786":1,"807":1,"808":1,"809":1,"810":1,"811":1,"812":1,"813":1,"814":1,"815":1,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1,"919":1,"920":1,"921":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"951":1,"952":1,"953":1,"954":1,"955":1,"956":1,"957":1,"958":1,"959":1,"960":1,"969":1,"970":1,"971":1,"972":1,"973":1,"974":1,"975":1,"976":1,"1031":1,"1032":1,"1033":1,"1034":1,"1035":1,"1036":1,"1037":1,"1038":1,"1039":1,"1040":1,"1041":1,"1042":1,"1043":1,"1044":1,"1045":1,"1046":1,"1064":1,"1065":1,"1066":1,"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"1130":1,"1131":1,"1132":1,"1133":1,"1134":1,"1135":1,"1136":1,"1137":1,"1181":1,"1182":1,"1183":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1,"1396":1,"1397":1,"1398":1,"1399":1,"1400":1,"1401":1,"1680":1,"1681":1,"1682":1,"1683":1,"1684":1,"1685":1,"1686":1,"1687":1,"1688":1,"1689":1,"1690":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1698":1,"1699":1,"1700":1,"1701":1,"1702":1,"1703":1,"1704":1,"1793":1,"1794":1,"1795":1,"1796":1,"1797":1,"1798":1,"1799":1,"1800":1,"1801":1,"1817":1,"1818":1,"1819":1,"1820":1,"1821":1,"1822":1,"1823":1,"1824":1,"1825":1,"1826":1,"1827":1,"1828":1,"1829":1,"1830":1,"1831":1,"1832":1,"1833":1,"1846":1,"1847":1,"1848":1,"1849":1,"1850":1,"1851":1,"1852":1,"1853":1,"1854":1,"1855":1,"1856":1,"1857":1,"1858":1,"1859":1,"1860":1,"1861":1,"1862":1,"1863":1,"1864":1,"1865":1,"1866":1,"1867":1,"1868":1,"1869":1,"1870":1,"1871":1,"1872":1,"1873":1,"1874":1,"1875":1,"1876":1,"1892":1,"1893":1,"1894":1,"1895":1,"1896":1,"1897":1,"1898":1,"1930":1,"1931":1,"1932":1,"1933":1,"1934":1,"1935":1,"1936":1,"1937":1,"1938":1,"1939":1,"1940":1,"2014":2,"2015":2,"2016":2,"2017":2,"2018":2,"2019":2,"2020":2,"2021":2,"2022":2,"2023":2,"2024":2,"2025":2,"2026":2,"2027":2,"2028":2,"2029":2,"2030":2,"2031":2,"2032":2,"2033":2,"2034":2,"2035":2,"2036":2,"2037":2,"2038":1,"2039":1,"2040":1,"2041":1,"2042":1,"2043":1,"2044":1,"2045":1,"2046":1,"2047":1,"2048":1,"2069":3,"2070":3,"2071":3,"2072":3,"2073":3,"2074":3,"2075":3,"2076":3,"2077":3,"2078":3,"2079":3,"2080":1,"2081":1,"2082":1,"2083":1,"2084":1,"2085":1,"2086":1,"2087":1,"2088":1,"2089":1,"2090":1,"2091":1,"2092":1,"2093":1,"2094":1,"2095":1,"2096":1,"2097":1,"2108":2,"2109":2,"2110":2,"2111":2,"2112":2,"2113":2,"2114":2,"2115":1,"2116":1,"2117":1,"2118":1,"2119":1,"2120":1,"2121":1,"2122":1,"2160":1,"2161":1,"2162":1,"2163":1,"2164":1,"2165":1,"2166":1,"2167":1,"2168":1,"2169":2,"2170":2,"2171":2,"2172":2,"2173":2,"2174":2,"2175":2,"2176":2,"2177":2,"2178":2,"2179":2},"3":{"0":949,"1":4,"5":6,"6":4,"7":2,"10":4,"11":4,"12":1,"13":1,"15":12,"17":9,"18":1,"19":14,"20":8,"21":7,"22":9,"23":7,"24":56,"25":7,"26":10,"27":21,"30":6,"31":9,"32":5,"33":5,"37":3,"38":4,"39":25,"41":8,"42":4,"43":2,"45":2,"46":5,"47":4,"48":1,"51":2,"52":4,"53":15,"54":5,"55":4,"57":5,"58":4,"59":11,"60":10,"61":4,"62":13,"63":3,"66":2,"67":6,"68":12,"69":4,"70":6,"71":20,"72":17,"73":7,"74":5,"76":4,"77":3,"78":15,"79":4,"80":11,"81":29,"82":1,"85":2,"86":1,"87":1,"88":3,"90":1,"91":11,"92":9,"93":8,"94":9,"95":7,"96":4,"97":7,"98":4,"99":12,"100":19,"101":3,"102":10,"103":8,"104":2,"107":5,"108":3,"109":98,"110":9,"111":24,"113":5,"114":1,"115":10,"116":1,"117":7,"118":1,"119":4,"120":7,"121":9,"122":40,"123":5,"124":2,"125":8,"126":21,"127":3,"128":3,"130":5,"131":3,"132":12,"133":3,"134":4,"135":65,"136":34,"137":15,"138":8,"139":16,"140":3,"141":4,"142":7,"143":1,"145":7,"146":4,"147":17,"148":7,"149":7,"150":21,"151":7,"152":1,"155":5,"156":6,"157":43,"158":8,"159":19,"160":19,"161":8,"162":2,"164":5,"165":8,"166":24,"167":6,"168":11,"170":24,"171":3,"173":6,"174":9,"175":18,"176":7,"177":20,"178":25,"179":5,"180":5,"181":3,"184":8,"185":8,"186":30,"187":6,"188":13,"189":9,"190":2,"193":9,"194":6,"195":15,"196":5,"197":6,"198":5,"199":2,"200":12,"201":31,"202":14,"203":1,"206":5,"207":4,"208":6,"209":8,"211":1,"212":5,"213":8,"214":16,"215":13,"216":12,"217":11,"218":6,"219":10,"220":3,"223":1,"224":9,"225":23,"226":5,"227":13,"228":8,"229":3,"230":20,"231":1,"233":6,"234":20,"235":47,"236":16,"237":23,"239":1,"240":1,"241":9,"242":6,"243":48,"244":3,"245":23,"246":7,"247":1,"248":7,"249":3,"250":1,"251":1,"252":1,"253":8,"254":8,"255":46,"256":10,"257":3,"258":11,"259":10,"260":3,"261":6,"263":1,"264":7,"265":78,"266":6,"267":10,"269":1,"272":4,"273":12,"275":3,"276":1,"279":6,"280":8,"281":12,"282":7,"283":12,"284":9,"285":2,"286":6,"287":1,"290":6,"291":10,"292":4,"293":9,"294":2,"295":8,"296":3,"298":5,"299":8,"300":6,"301":7,"302":10,"303":12,"304":1,"305":18,"306":2,"309":9,"310":14,"311":12,"312":14,"313":1,"314":5,"317":12,"318":50,"319":15,"320":17,"321":1,"323":1,"324":4,"325":5,"326":6,"327":1,"328":3,"329":10,"330":19,"331":4,"332":3,"333":14,"335":17,"336":2,"337":3,"339":2,"340":15,"341":50,"342":28,"343":30,"344":11,"345":3,"348":4,"349":7,"350":35,"351":9,"352":14,"353":18,"358":12,"359":36,"360":16,"361":26,"363":7,"364":1,"365":11,"366":1,"368":2,"369":7,"370":17,"371":8,"372":8,"373":18,"374":11,"375":4,"376":1,"379":6,"380":8,"381":2,"382":2,"383":7,"384":14,"386":9,"387":8,"388":11,"389":2,"390":22,"391":12,"392":7,"393":1,"395":12,"396":4,"397":27,"398":13,"399":10,"400":10,"401":14,"402":10,"403":8,"404":9,"405":7,"406":1,"407":14,"408":9,"409":5,"411":3,"412":3,"413":15,"414":4,"415":15,"416":2,"418":4,"419":6,"420":2,"421":1,"422":10,"423":2,"424":4,"425":5,"426":3,"427":10,"428":4,"429":1,"432":1,"433":8,"434":4,"435":9,"436":4,"438":2,"439":3,"440":4,"441":4,"442":7,"443":12,"444":6,"446":4,"447":11,"448":16,"449":12,"450":14,"451":10,"452":3,"454":6,"457":2,"458":12,"459":48,"460":6,"461":17,"462":4,"463":3,"464":6,"465":10,"466":2,"468":1,"469":6,"470":18,"471":2,"472":9,"473":1,"474":5,"477":4,"478":1,"481":5,"482":5,"483":7,"484":2,"485":1,"486":5,"487":3,"488":2,"489":3,"490":8,"491":8,"492":8,"493":6,"494":5,"495":3,"496":1,"497":7,"498":10,"499":23,"500":15,"501":17,"502":1,"503":2,"505":3,"506":7,"507":19,"508":4,"509":9,"511":7,"512":7,"513":7,"514":1,"516":3,"517":11,"518":22,"519":3,"520":17,"522":3,"523":4,"524":9,"526":5,"527":11,"528":18,"529":8,"530":18,"531":2,"533":1,"534":5,"535":9,"536":50,"537":18,"538":25,"539":29,"540":12,"541":5,"543":7,"544":7,"545":27,"546":13,"547":13,"548":2,"549":59,"550":8,"551":13,"552":1,"555":6,"556":16,"557":10,"558":11,"559":1,"560":1,"561":1,"562":3,"563":6,"564":28,"565":12,"566":18,"568":5,"569":1,"571":6,"572":32,"573":20,"574":16,"576":9,"577":8,"578":2,"579":3,"582":7,"583":41,"584":12,"585":36,"586":5,"587":5,"589":1,"590":10,"591":34,"592":25,"593":15,"594":1,"598":15,"599":22,"600":6,"601":12,"602":5,"603":5,"605":3,"607":9,"608":8,"609":43,"610":28,"611":12,"612":9,"613":1,"614":1,"615":1,"616":4,"617":12,"618":28,"619":12,"620":15,"622":1,"624":5,"625":19,"626":46,"627":21,"628":14,"629":5,"630":4,"631":4,"632":8,"633":43,"634":12,"635":12,"638":5,"639":7,"640":37,"641":16,"642":17,"643":4,"645":1,"648":12,"649":23,"650":23,"651":11,"652":1,"655":1,"656":14,"657":36,"658":18,"659":15,"660":5,"664":10,"665":24,"666":10,"667":12,"668":2,"669":1,"670":2,"672":2,"673":14,"674":26,"675":19,"676":29,"677":2,"678":2,"680":1,"681":16,"682":25,"683":24,"684":20,"685":2,"688":7,"689":18,"690":32,"691":41,"692":27,"693":1,"694":1,"696":4,"697":15,"698":72,"699":16,"700":17,"701":4,"702":14,"703":1,"705":1,"706":15,"707":61,"708":18,"709":28,"710":2,"711":3,"713":2,"714":15,"715":19,"716":25,"717":22,"718":6,"719":3,"720":8,"721":3,"722":1,"723":3,"724":12,"725":58,"726":24,"727":20,"728":3,"729":5,"731":5,"732":17,"733":49,"734":17,"735":19,"736":2,"737":3,"738":1,"740":24,"741":63,"742":20,"743":41,"744":3,"745":6,"747":2,"748":8,"749":25,"750":9,"751":24,"755":2,"756":7,"757":31,"758":16,"759":28,"760":6,"761":5,"762":1,"763":1,"764":4,"765":12,"766":24,"767":24,"768":17,"769":2,"770":3,"773":8,"774":7,"775":7,"776":19,"778":2,"779":1,"781":8,"782":27,"783":12,"784":23,"785":5,"786":2,"788":1,"789":14,"790":37,"791":14,"792":28,"793":11,"794":7,"795":4,"797":3,"798":16,"799":29,"800":12,"801":13,"802":4,"803":8,"804":4,"807":1,"808":2,"809":23,"810":24,"811":21,"812":10,"813":10,"814":3,"815":7,"817":1,"818":12,"819":42,"820":25,"821":20,"822":1,"823":3,"825":14,"826":21,"827":101,"828":18,"829":38,"830":12,"831":10,"832":18,"833":4,"834":2,"836":5,"837":13,"838":32,"839":18,"840":19,"842":3,"844":2,"845":8,"846":13,"847":8,"848":5,"849":5,"852":6,"853":18,"854":18,"855":13,"856":19,"857":6,"858":1,"859":2,"860":18,"861":36,"862":20,"863":18,"865":2,"867":2,"868":10,"869":12,"870":10,"871":9,"872":8,"873":8,"874":4,"875":7,"876":6,"877":1,"879":4,"880":5,"881":56,"882":20,"883":15,"884":2,"887":1,"888":9,"889":20,"890":15,"891":20,"892":1,"893":2,"896":8,"897":41,"898":18,"899":13,"900":2,"901":2,"903":13,"904":17,"905":95,"906":27,"907":36,"908":7,"909":5,"910":6,"912":4,"913":7,"914":22,"915":7,"916":14,"917":3,"918":3,"919":1,"920":14,"921":15,"922":33,"923":16,"924":11,"926":91,"927":1,"929":2,"930":16,"931":14,"932":12,"933":12,"935":1,"937":1,"938":4,"939":14,"940":9,"941":12,"942":1,"944":1,"945":10,"946":19,"947":10,"948":17,"951":1,"952":2,"953":15,"954":27,"955":16,"956":19,"957":5,"958":3,"959":2,"960":10,"963":26,"964":43,"965":20,"966":32,"967":1,"968":5,"969":1,"970":1,"971":18,"972":24,"973":20,"974":26,"975":7,"976":3,"979":16,"980":23,"981":15,"982":17,"983":1,"984":2,"986":1,"987":8,"988":45,"989":12,"990":15,"991":3,"992":1,"994":3,"995":14,"996":72,"997":14,"998":17,"999":2,"1000":3,"1003":13,"1004":28,"1005":14,"1006":14,"1007":1,"1008":3,"1010":1,"1011":9,"1012":19,"1013":9,"1014":1,"1016":6,"1017":24,"1018":101,"1019":52,"1020":39,"1021":4,"1022":4,"1024":1,"1025":11,"1026":20,"1027":13,"1028":21,"1029":7,"1030":4,"1031":1,"1032":2,"1033":24,"1034":31,"1035":13,"1036":13,"1037":4,"1038":1,"1039":1,"1040":4,"1041":10,"1042":40,"1043":22,"1044":17,"1045":4,"1046":2,"1048":6,"1049":12,"1050":59,"1051":31,"1052":29,"1053":3,"1054":1,"1055":7,"1058":16,"1059":42,"1060":19,"1061":14,"1064":1,"1065":2,"1066":25,"1067":59,"1068":19,"1069":15,"1070":2,"1071":3,"1073":1,"1074":19,"1075":19,"1076":15,"1077":11,"1078":1,"1079":4,"1082":13,"1083":15,"1084":7,"1085":19,"1086":1,"1087":1,"1089":12,"1090":29,"1091":75,"1092":39,"1093":37,"1094":6,"1095":4,"1099":13,"1100":23,"1101":11,"1102":16,"1103":2,"1107":14,"1108":13,"1109":11,"1110":7,"1112":1,"1114":3,"1115":22,"1116":58,"1117":30,"1118":17,"1119":3,"1122":3,"1123":8,"1124":45,"1125":15,"1126":27,"1128":4,"1129":6,"1130":1,"1132":18,"1133":25,"1134":17,"1135":21,"1137":5,"1139":1,"1140":12,"1141":6,"1142":8,"1143":7,"1144":3,"1145":6,"1147":3,"1149":1,"1150":21,"1151":1,"1152":5,"1153":4,"1154":7,"1155":6,"1156":1,"1157":2,"1160":9,"1161":18,"1162":16,"1163":9,"1164":1,"1167":10,"1168":22,"1169":13,"1170":8,"1171":2,"1173":1,"1174":3,"1175":13,"1176":11,"1177":9,"1178":5,"1179":4,"1181":1,"1182":2,"1183":13,"1184":25,"1185":19,"1186":5,"1187":11,"1188":3,"1190":7,"1191":8,"1192":2,"1193":7,"1194":1,"1196":8,"1200":5,"1201":3,"1202":2,"1203":2,"1204":1,"1210":1,"1211":7,"1212":204,"1213":10,"1214":13,"1215":6,"1216":7,"1217":13,"1218":5,"1219":10,"1220":18,"1221":8,"1222":13,"1223":10,"1224":5,"1225":7,"1226":4,"1227":6,"1228":19,"1229":271,"1230":6,"1231":24,"1232":17,"1233":17,"1234":19,"1235":11,"1236":20,"1237":437,"1238":1,"1239":22,"1240":13,"1241":56,"1242":26,"1243":18,"1244":29,"1245":7,"1246":22,"1247":679,"1248":7,"1249":32,"1250":8,"1251":6,"1252":12,"1253":16,"1254":19,"1255":13,"1256":20,"1257":16,"1258":18,"1259":620,"1260":12,"1261":8,"1262":12,"1263":72,"1264":33,"1265":50,"1266":18,"1267":27,"1268":14,"1269":38,"1270":1080,"1271":245,"1272":17,"1273":37,"1274":26,"1275":48,"1276":27,"1277":22,"1278":40,"1279":27,"1280":38,"1281":15,"1282":27,"1283":27,"1284":22,"1285":2529,"1286":12,"1287":18,"1288":19,"1289":40,"1290":7,"1291":17,"1292":33,"1293":37,"1294":11,"1295":6,"1296":37,"1297":19,"1298":6,"1299":1845,"1300":328,"1301":11,"1302":5,"1303":28,"1304":13,"1305":11,"1306":29,"1307":10,"1308":639,"1309":175,"1310":1660,"1311":150,"1312":10,"1313":12,"1314":30,"1315":21,"1316":30,"1317":53,"1318":10,"1319":47,"1320":4,"1321":21,"1322":1498,"1323":2655,"1324":5,"1325":15,"1326":11,"1327":34,"1328":4,"1329":22,"1330":9,"1331":39,"1332":38,"1333":22,"1334":11,"1335":22,"1336":41,"1337":34,"1338":1338,"1339":8,"1340":4,"1341":48,"1342":23,"1343":22,"1344":6,"1345":6,"1346":41,"1347":19,"1348":25,"1349":1288,"1350":8,"1351":41,"1352":38,"1353":8,"1354":24,"1355":6,"1356":33,"1357":31,"1358":4018,"1359":1,"1360":3,"1361":21,"1362":7,"1363":8,"1364":11,"1365":61,"1366":11,"1367":15,"1368":384,"1369":6,"1370":14,"1371":18,"1372":24,"1373":6,"1374":17,"1375":17,"1376":6,"1377":21,"1378":24,"1379":625,"1380":6,"1381":15,"1382":24,"1383":25,"1384":5,"1385":9,"1386":12,"1387":8,"1388":39,"1389":17,"1390":22,"1391":15,"1392":10,"1393":8,"1394":1974,"1395":2710,"1396":16,"1397":24,"1398":29,"1399":16,"1400":18,"1401":978,"1402":9,"1403":4,"1404":19,"1405":20,"1406":12,"1407":11,"1408":24,"1409":18,"1410":19,"1411":6,"1412":9,"1413":11,"1414":1251,"1415":273,"1416":1278,"1417":17,"1418":9,"1419":11,"1420":14,"1421":25,"1422":37,"1423":21,"1424":24,"1425":15,"1426":356,"1427":11,"1428":36,"1429":47,"1430":98,"1431":37,"1432":32,"1433":9,"1434":56,"1435":4166,"1436":16,"1437":461,"1438":7,"1439":11,"1440":73,"1441":32,"1442":83,"1443":18,"1444":28,"1445":13,"1446":62,"1447":4,"1448":20,"1449":2,"1450":5,"1451":9,"1452":73,"1453":40,"1454":264,"1455":55,"1456":18,"1457":16,"1458":51,"1459":36,"1460":11,"1461":5,"1462":6,"1463":4,"1464":48,"1465":9,"1466":332,"1467":3,"1468":7,"1469":6,"1470":1,"1471":19,"1472":16,"1473":49,"1474":64,"1475":44,"1476":8,"1477":20,"1478":11,"1479":16,"1480":11,"1481":11,"1482":4,"1483":4,"1484":4,"1485":41,"1486":17,"1487":158,"1488":97,"1489":75,"1490":28,"1491":113,"1492":2,"1493":1,"1495":134,"1496":2,"1497":9,"1498":2,"1499":14,"1501":1,"1502":3,"1503":3,"1506":6,"1507":12,"1508":4,"1509":1,"1510":2,"1511":6,"1512":5,"1513":1,"1516":2,"1517":1,"1520":1,"1522":3,"1523":17,"1524":15,"1525":160,"1526":5,"1527":2,"1528":1,"1529":1,"1530":36,"1531":7,"1533":54,"1534":7,"1535":7,"1536":19,"1537":2,"1538":3,"1539":1,"1540":2,"1541":5,"1542":2,"1543":8,"1544":7,"1545":3,"1546":14,"1547":4,"1548":2,"1549":1,"1550":2,"1551":1,"1552":22,"1553":8,"1554":2,"1555":5,"1556":4,"1557":1,"1558":1,"1560":1,"1564":2,"1565":4,"1566":1,"1567":2,"1568":5,"1569":6,"1570":2,"1571":14,"1573":3,"1574":18,"1575":14,"1576":208,"1577":6,"1578":2,"1580":5,"1581":29,"1582":7,"1583":2,"1584":36,"1585":10,"1587":5,"1588":18,"1589":2,"1590":91,"1591":8,"1594":4,"1595":36,"1596":4,"1597":5,"1598":27,"1599":6,"1601":2,"1604":6,"1606":6,"1607":1,"1608":3,"1609":2,"1610":9,"1611":1,"1612":1,"1613":2,"1614":1,"1615":1,"1616":1,"1620":5,"1625":5,"1626":24,"1628":5,"1629":41,"1630":115,"1631":53,"1632":2,"1633":10,"1634":17,"1635":2,"1636":39,"1637":40,"1638":75,"1639":32,"1640":113,"1641":1,"1642":7,"1643":1,"1644":1,"1645":13,"1646":11,"1647":5,"1648":6,"1649":5,"1650":50,"1651":10,"1652":29,"1653":22,"1654":2,"1655":20,"1656":1,"1657":3,"1658":4,"1659":1,"1660":19,"1661":31,"1662":16,"1663":40,"1664":21,"1665":23,"1666":30,"1667":33,"1668":22,"1669":41,"1670":27,"1671":13,"1672":23,"1673":4,"1674":23,"1675":1,"1676":16,"1677":5,"1678":4,"1680":5,"1681":3,"1682":4,"1683":13,"1684":54,"1685":43,"1686":7,"1687":2,"1688":7,"1691":12,"1692":4,"1693":3,"1694":2,"1695":5,"1696":5,"1697":4,"1698":5,"1699":11,"1700":16,"1701":35,"1702":3,"1703":3,"1704":2,"1705":6,"1706":15,"1707":20,"1708":4,"1709":5,"1713":4,"1714":3,"1715":2,"1716":1,"1717":12,"1718":10,"1719":10,"1720":11,"1721":10,"1722":7,"1723":19,"1724":1,"1725":8,"1726":50,"1727":17,"1728":21,"1729":8,"1730":13,"1731":5,"1732":1,"1733":10,"1734":6,"1735":3,"1736":4,"1737":4,"1738":6,"1740":6,"1741":3,"1742":3,"1743":2,"1746":1,"1747":28,"1748":25,"1749":18,"1750":6,"1754":8,"1756":1,"1757":3,"1758":5,"1759":7,"1760":10,"1762":2,"1763":50,"1764":77,"1765":1,"1766":42,"1767":25,"1768":28,"1769":2,"1770":6,"1771":3,"1772":9,"1773":2,"1774":1,"1775":19,"1777":2,"1778":10,"1779":10,"1780":16,"1781":9,"1782":11,"1783":12,"1784":5,"1785":4,"1786":3,"1787":14,"1788":10,"1789":34,"1791":12,"1792":9,"1793":9,"1794":7,"1795":8,"1796":9,"1797":3,"1798":19,"1799":20,"1800":20,"1801":11,"1802":6,"1803":6,"1804":51,"1805":18,"1806":14,"1807":7,"1808":5,"1809":45,"1810":10,"1811":3,"1812":6,"1813":7,"1814":46,"1815":12,"1816":20,"1817":14,"1819":6,"1820":35,"1821":8,"1822":17,"1823":3,"1824":19,"1825":17,"1826":10,"1827":13,"1828":21,"1829":9,"1830":13,"1831":9,"1832":21,"1833":16,"1834":8,"1835":1,"1836":1,"1837":3,"1838":21,"1839":23,"1840":14,"1841":16,"1842":16,"1843":9,"1844":4,"1845":8,"1846":10,"1847":6,"1848":25,"1849":2,"1850":29,"1851":9,"1852":16,"1853":18,"1854":2,"1855":14,"1856":1,"1857":8,"1858":5,"1859":3,"1860":8,"1861":15,"1862":10,"1863":8,"1864":14,"1865":4,"1866":9,"1867":15,"1868":14,"1869":6,"1870":12,"1871":2,"1872":7,"1873":18,"1874":10,"1875":32,"1876":25,"1877":9,"1878":7,"1879":6,"1880":23,"1881":15,"1882":9,"1883":7,"1884":15,"1885":13,"1886":10,"1887":8,"1888":28,"1889":4,"1890":18,"1891":16,"1892":11,"1893":9,"1894":6,"1895":8,"1896":6,"1897":19,"1898":14,"1899":13,"1900":4,"1901":35,"1902":5,"1903":13,"1904":16,"1905":8,"1906":6,"1907":27,"1908":49,"1909":17,"1910":38,"1911":26,"1912":8,"1913":4,"1914":14,"1915":35,"1916":14,"1917":16,"1918":17,"1919":20,"1920":10,"1921":52,"1922":48,"1923":16,"1924":9,"1925":15,"1926":30,"1927":7,"1928":16,"1929":20,"1930":8,"1931":5,"1932":40,"1933":12,"1934":12,"1935":8,"1936":8,"1937":1,"1938":8,"1939":30,"1940":18,"1941":11,"1942":6,"1943":27,"1944":16,"1945":27,"1946":28,"1947":50,"1948":15,"1949":37,"1950":12,"1951":14,"1952":6,"1953":18,"1954":12,"1955":8,"1956":8,"1957":14,"1958":20,"1959":5,"1960":10,"1961":23,"1962":7,"1963":13,"1964":18,"1965":14,"1966":8,"1967":12,"1968":4,"1969":21,"1970":17,"1971":24,"1972":13,"1973":6,"1974":18,"1975":10,"1976":27,"1977":16,"1978":27,"1979":26,"1980":4,"1981":27,"1982":6,"1983":20,"1984":24,"1985":15,"1986":12,"1987":26,"1988":20,"1989":7,"1990":31,"1991":15,"1992":8,"1993":37,"1994":37,"1995":18,"1996":16,"1997":17,"1998":13,"1999":59,"2000":46,"2001":32,"2002":19,"2003":9,"2004":5,"2005":17,"2006":18,"2007":5,"2008":4,"2009":20,"2010":11,"2011":15,"2012":16,"2013":8,"2014":14,"2015":6,"2016":3,"2017":3,"2018":6,"2019":4,"2020":2,"2021":2,"2022":2,"2023":36,"2024":15,"2025":4,"2026":8,"2027":13,"2028":13,"2029":17,"2030":3,"2031":23,"2032":9,"2033":31,"2034":8,"2035":10,"2036":14,"2037":15,"2038":15,"2039":4,"2040":15,"2041":9,"2042":48,"2043":34,"2044":15,"2045":15,"2046":37,"2047":30,"2048":23,"2049":9,"2050":6,"2051":2,"2052":12,"2053":5,"2054":25,"2055":28,"2056":24,"2057":9,"2058":11,"2059":12,"2060":8,"2061":3,"2062":26,"2063":18,"2064":5,"2065":14,"2066":30,"2067":4,"2068":10,"2069":23,"2070":6,"2071":11,"2072":15,"2073":19,"2074":22,"2075":13,"2076":15,"2077":6,"2078":19,"2079":17,"2080":20,"2081":9,"2082":19,"2083":6,"2084":19,"2085":11,"2086":21,"2087":2,"2088":11,"2089":4,"2090":1,"2091":11,"2092":1,"2093":11,"2094":1,"2095":15,"2096":8,"2097":15,"2098":16,"2099":15,"2100":3,"2101":1,"2102":3,"2103":8,"2104":13,"2105":7,"2106":24,"2107":12,"2108":9,"2109":15,"2110":5,"2111":11,"2112":21,"2113":10,"2114":8,"2115":23,"2116":15,"2117":9,"2118":5,"2119":1,"2120":13,"2121":9,"2122":24,"2123":17,"2124":5,"2125":48,"2126":15,"2127":8,"2128":19,"2129":13,"2130":26,"2131":18,"2132":19,"2133":14,"2134":14,"2135":17,"2136":8,"2137":26,"2138":23,"2139":14,"2140":15,"2141":37,"2142":14,"2143":10,"2144":17,"2145":20,"2146":15,"2147":6,"2148":22,"2149":26,"2150":7,"2151":10,"2152":20,"2153":6,"2154":9,"2155":55,"2156":11,"2157":41,"2158":9,"2159":29,"2160":15,"2161":6,"2162":15,"2163":20,"2164":21,"2165":31,"2166":30,"2167":25,"2168":30,"2169":20,"2170":8,"2171":11,"2172":8,"2173":20,"2174":16,"2175":8,"2176":19,"2177":14,"2178":16,"2179":39,"2180":10,"2181":9,"2182":20,"2183":7,"2184":14,"2185":11,"2186":5,"2187":22,"2188":9,"2189":14,"2190":6,"2191":32,"2192":41,"2193":14,"2194":14,"2195":10,"2196":20,"2197":8,"2198":13,"2199":12,"2200":8,"2201":24,"2202":30,"2203":8,"2204":1,"2205":4,"2206":6,"2207":2,"2208":7,"2209":11,"2210":1,"2211":1,"2212":2,"2213":8,"2214":6,"2219":15,"2220":7,"2224":1,"2225":3,"2226":11,"2227":7,"2229":26,"2230":2,"2231":160,"2233":1,"2235":2,"2238":12,"2239":3,"2244":4}}],["adb",{"2":{"1478":1},"3":{"1478":6}}],["ada",{"3":{"1363":1,"1368":2,"1435":1}}],["adaptation",{"3":{"1358":1,"1416":1,"1460":1,"1491":2}}],["adapted",{"3":{"1311":1,"1610":1}}],["adapterfactorytests",{"3":{"1199":1,"1207":2,"1426":3,"1436":1,"1486":1}}],["adapters",{"1":{"1416":1},"3":{"53":2,"74":1,"482":1,"1004":2,"1091":3,"1096":1,"1192":2,"1202":1,"1203":1,"1212":1,"1237":1,"1259":1,"1300":12,"1308":1,"1310":1,"1311":1,"1358":1,"1359":2,"1365":1,"1367":1,"1369":1,"1376":1,"1379":5,"1395":13,"1401":1,"1415":9,"1416":15,"1419":1,"1426":2,"1435":3,"1437":1,"1486":1,"1495":2,"1499":1,"1502":1,"1512":1,"1525":2,"1539":2,"1576":1,"1659":1,"1930":1,"1931":2,"1940":2,"2066":1,"2070":2,"2115":2,"2118":1,"2119":1,"2122":1,"2229":1}}],["adapter",{"0":{"1364":1,"1365":1,"1366":1,"1934":1},"3":{"0":4,"51":1,"53":5,"54":2,"117":1,"380":1,"584":1,"660":1,"725":1,"1016":1,"1020":1,"1022":1,"1075":1,"1076":1,"1077":2,"1089":1,"1091":7,"1092":3,"1093":1,"1094":2,"1150":1,"1192":2,"1202":2,"1203":2,"1212":1,"1213":1,"1258":1,"1259":3,"1271":2,"1285":2,"1295":2,"1299":7,"1300":17,"1306":2,"1308":26,"1310":1,"1311":14,"1322":3,"1323":6,"1338":2,"1348":2,"1349":9,"1358":22,"1359":2,"1364":2,"1365":1,"1367":2,"1368":14,"1377":4,"1379":15,"1394":1,"1395":30,"1399":3,"1401":3,"1403":1,"1414":11,"1415":4,"1416":79,"1417":1,"1418":1,"1419":3,"1421":1,"1424":1,"1426":18,"1434":1,"1435":1,"1436":1,"1437":1,"1454":1,"1495":5,"1525":2,"1538":1,"1552":1,"1576":3,"1590":1,"1662":1,"1734":1,"1789":3,"1914":2,"1915":4,"1919":1,"1922":1,"1926":1,"1931":1,"1937":1,"1945":2,"1947":1,"1948":2,"2016":3,"2017":1,"2018":2,"2025":1,"2027":2,"2118":2,"2122":1,"2169":1,"2171":2,"2179":1,"2226":1}}],["adapting",{"3":{"1285":1,"1323":2,"1394":1}}],["adapts",{"3":{"690":1,"1230":1,"1247":1,"1308":1,"1310":1,"1323":3,"1394":1,"1416":6,"1419":1,"1626":1}}],["adapt",{"3":{"413":1,"1416":3,"1558":1,"1648":1,"2120":1}}],["adherence",{"3":{"1358":3,"1416":4}}],["adequate",{"3":{"1323":1,"1536":1,"1558":1,"2170":1,"2178":1}}],["adee5058",{"2":{"563":1},"3":{"563":1,"1495":2}}],["adjudicated",{"3":{"1525":1,"1530":1,"1577":1}}],["adjustable",{"3":{"1797":1}}],["adjusting",{"3":{"1637":1,"1764":1}}],["adjustinventory",{"3":{"1270":1}}],["adjustinventorycommand",{"3":{"1270":1}}],["adjustinventoryhandler",{"3":{"897":2,"900":1,"1766":1}}],["adjusted",{"3":{"1358":1,"1766":1,"2194":1}}],["adjustment",{"3":{"1322":1,"1741":1,"1748":1,"1766":1}}],["adjustments",{"3":{"1237":1}}],["adjusttouniversal",{"3":{"1308":1,"1395":2}}],["adjust",{"3":{"343":1,"1310":2,"1338":1,"1358":1,"1395":1,"1536":1,"1638":1,"1741":1}}],["adjacency",{"3":{"583":1,"749":1,"751":1,"1310":4,"1314":1,"1322":1,"1435":3,"1487":1,"1665":1,"1824":1}}],["adjacencies",{"3":{"0":2,"464":1,"749":1,"750":1,"751":1,"1212":2,"1310":5,"1435":4,"1487":1,"1824":2,"2229":1,"2231":1}}],["adjacent",{"0":{"1299":1},"3":{"325":1,"371":1,"464":1,"535":1,"608":1,"974":1,"1052":1,"1270":2,"1284":1,"1299":1,"1394":3,"1415":1,"1824":1,"1869":1}}],["ad",{"3":{"158":1,"709":1,"809":1,"1216":1,"1237":1,"1285":1,"1299":1,"1322":2,"1323":4,"1349":3,"1358":3,"1395":4,"1401":1,"1435":1,"1466":2,"1474":1,"1488":1,"1536":2,"1544":1,"1812":1,"1929":1,"1990":1}}],["admissibility",{"3":{"1241":1}}],["admission",{"3":{"689":1,"691":1,"1308":1,"1323":1,"1415":1,"1628":1,"1630":1}}],["admit",{"3":{"1118":1,"1285":1,"1338":1,"1349":1,"1358":1,"1652":1}}],["admits",{"3":{"235":1,"237":1,"707":1,"827":1,"916":1,"948":1,"1049":1,"1091":1,"1241":1,"1242":1,"1300":1,"1306":1,"1323":1,"1338":2,"1371":1,"1395":1,"1590":1}}],["admitting",{"3":{"178":1,"682":1,"946":1,"1242":1,"1247":2,"1285":2,"1322":1,"1358":2,"1368":2,"1416":1,"1422":1,"1435":1,"2174":1}}],["admitted",{"3":{"0":1,"111":1,"237":1,"1116":1,"1122":1,"1124":1,"1128":1,"1212":1,"1247":1,"1308":2,"1338":1,"1435":3,"1639":1,"2064":1}}],["adminuserscontroller",{"3":{"2201":1}}],["adminuserenabled",{"2":{"1464":1,"1468":1},"3":{"1464":1,"1468":1}}],["admincreateforms",{"3":{"1435":1}}],["admincredentials",{"3":{"1199":1,"1207":1,"1432":1,"1435":5}}],["adminclient",{"3":{"1435":1}}],["adminpages",{"2":{"1626":1},"3":{"1435":1,"1626":1}}],["adminpaths",{"3":{"1432":1,"1435":2}}],["adminpanelsettings",{"3":{"1414":1}}],["adminplaneport",{"3":{"640":1,"1435":1}}],["admin123",{"3":{"1414":2,"1435":1,"1487":1}}],["admins",{"3":{"1395":2,"1401":7,"1748":1,"1770":1,"1868":1}}],["admintoken",{"3":{"1435":1}}],["admintargetport",{"3":{"1338":1}}],["admintestcontext",{"3":{"1199":2,"1207":1}}],["adminendpoint",{"3":{"1338":2}}],["adminendpointname",{"3":{"1338":1}}],["adminendpointexpression",{"2":{"1326":1},"3":{"1326":1,"1338":1}}],["administer",{"3":{"1414":1,"2199":1}}],["administered",{"3":{"1285":1}}],["administers",{"3":{"1284":1,"1414":2}}],["administrative",{"3":{"461":1,"497":1,"1299":4,"1338":2,"1394":1,"1413":1,"1414":2,"1629":1}}],["administrationrequests",{"3":{"1207":2,"1299":8}}],["administrationrequestvalidators",{"3":{"1207":2,"1299":9}}],["administrationcontrollerbasetests",{"3":{"1199":1,"1207":5,"1310":2}}],["administrationpermissions",{"2":{"1059":2,"1962":1,"2199":1,"2200":2},"3":{"1059":3,"1199":14,"1203":1,"1207":2,"1296":4,"1299":13,"1310":2,"1323":3,"1414":8,"1495":1,"1962":1,"2199":1,"2200":2}}],["administration",{"0":{"1408":1,"2200":1},"2":{"1299":1},"3":{"0":3,"19":1,"27":1,"186":1,"305":1,"444":1,"497":3,"499":1,"536":2,"552":1,"640":1,"649":1,"698":1,"809":2,"881":2,"931":1,"1042":1,"1044":2,"1057":1,"1058":1,"1059":19,"1060":1,"1061":1,"1082":3,"1086":2,"1203":28,"1207":110,"1212":2,"1256":1,"1257":1,"1259":11,"1275":6,"1285":17,"1286":2,"1296":5,"1299":61,"1310":23,"1317":1,"1319":1,"1322":11,"1323":58,"1326":1,"1338":1,"1402":3,"1408":4,"1413":3,"1414":34,"1435":11,"1437":1,"1440":1,"1466":1,"1495":4,"1525":2,"1534":1,"1620":3,"1624":1,"1625":1,"1626":1,"1759":2,"1961":1,"1962":1,"2192":1,"2193":1,"2199":1,"2200":1,"2201":1,"2202":1}}],["administrators",{"3":{"1285":1,"1299":1,"1310":1,"1323":1,"1762":1,"1763":1,"1775":1}}],["administrator",{"3":{"458":1,"714":1,"1299":1,"1303":1,"1323":1,"1338":1,"1405":1,"1414":2,"1466":1,"1636":1,"1637":1,"1764":4,"1767":1,"1776":1,"2223":1}}],["adminrolescontrollertests",{"3":{"1199":1,"1207":2}}],["adminrolescontroller",{"2":{"1408":1},"3":{"1199":2,"1203":1,"1207":2,"1299":1,"1310":2,"1408":2,"1414":6,"1495":2,"2201":2}}],["adminbypassroles",{"2":{"260":1,"261":1,"392":1,"393":1},"3":{"243":1,"260":2,"261":1,"392":1,"393":1,"1349":1,"1379":1}}],["admin",{"0":{"1759":1},"2":{"1465":1,"1467":1,"1469":2,"1471":1,"1476":2,"1636":1,"1756":1,"1759":1},"3":{"0":5,"24":1,"186":3,"233":1,"234":1,"235":2,"236":1,"245":1,"259":1,"317":1,"318":6,"320":3,"324":2,"340":1,"388":1,"390":1,"570":1,"578":1,"599":3,"600":2,"601":1,"618":1,"640":3,"642":1,"698":1,"719":1,"743":1,"897":1,"905":1,"1028":2,"1058":1,"1059":3,"1075":1,"1212":1,"1237":1,"1247":1,"1248":1,"1256":1,"1259":2,"1270":2,"1285":7,"1296":2,"1299":16,"1308":6,"1310":5,"1317":1,"1322":5,"1323":42,"1331":1,"1338":9,"1358":1,"1378":1,"1379":2,"1394":11,"1395":6,"1401":9,"1408":6,"1413":4,"1414":15,"1430":1,"1432":1,"1435":31,"1436":2,"1437":6,"1439":1,"1440":2,"1441":1,"1442":1,"1443":1,"1445":3,"1446":2,"1454":4,"1455":1,"1464":3,"1465":1,"1466":11,"1467":1,"1468":1,"1469":2,"1471":3,"1473":1,"1476":9,"1487":4,"1489":1,"1523":1,"1525":4,"1534":1,"1547":1,"1576":1,"1590":11,"1595":3,"1599":4,"1607":1,"1624":1,"1625":2,"1626":5,"1629":1,"1636":1,"1637":1,"1640":1,"1652":1,"1667":1,"1741":3,"1745":1,"1746":11,"1747":17,"1748":18,"1749":5,"1753":2,"1754":1,"1756":6,"1758":1,"1759":7,"1760":5,"1763":2,"1764":7,"1766":4,"1767":13,"1768":1,"1770":4,"1772":1,"1774":2,"1775":1,"1776":2,"1827":1,"1868":2,"1880":1,"1944":1,"1962":1,"1993":1,"1994":1,"2007":2,"2033":1,"2044":1,"2194":1,"2200":3,"2231":1}}],["adornments",{"3":{"1323":1}}],["adojobstore",{"2":{"1043":1},"3":{"1043":1}}],["ado",{"3":{"0":1,"1067":1,"1068":1,"1135":1,"1273":1,"1285":3}}],["adoptable",{"3":{"136":1,"923":1,"1416":1,"1491":1,"1928":1}}],["adopting",{"3":{"0":2,"24":1,"25":1,"41":1,"60":1,"62":1,"91":1,"160":1,"189":1,"228":1,"245":1,"305":1,"333":1,"352":2,"359":1,"472":1,"620":1,"657":1,"665":1,"667":1,"668":1,"670":1,"732":1,"733":1,"734":1,"854":1,"891":1,"1043":1,"1061":2,"1075":1,"1144":1,"1154":1,"1161":1,"1232":1,"1233":1,"1237":2,"1241":1,"1270":1,"1271":1,"1277":1,"1280":1,"1285":10,"1299":3,"1310":1,"1322":4,"1368":1,"1424":1,"1435":2,"1488":1,"1491":2,"1598":1,"1601":1,"1652":1,"1658":1,"1660":1,"1809":1,"1922":1,"1977":1,"2054":1,"2141":1,"2191":1,"2201":3}}],["adoption",{"0":{"1859":1},"3":{"0":17,"27":1,"128":1,"160":1,"186":1,"193":1,"195":1,"223":1,"230":2,"279":1,"298":1,"305":1,"308":1,"318":1,"320":1,"322":1,"324":1,"325":1,"326":1,"343":1,"350":2,"357":1,"358":1,"359":1,"360":1,"365":2,"401":1,"450":1,"468":2,"470":1,"477":1,"484":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":2,"527":1,"529":1,"530":1,"534":2,"536":1,"543":1,"545":2,"546":1,"550":3,"556":1,"564":1,"566":1,"572":1,"573":1,"574":1,"575":2,"578":1,"599":2,"603":1,"609":1,"611":1,"618":1,"626":1,"629":1,"632":1,"640":1,"649":1,"655":1,"657":2,"663":1,"667":1,"669":1,"696":1,"698":1,"702":1,"713":1,"715":1,"723":1,"725":1,"728":1,"733":2,"734":1,"741":1,"766":1,"769":1,"774":1,"790":1,"792":1,"825":2,"829":1,"832":1,"861":2,"863":1,"864":1,"879":1,"881":1,"891":2,"897":1,"900":2,"913":1,"920":1,"924":1,"926":1,"942":1,"954":1,"956":1,"963":2,"964":2,"968":1,"972":1,"981":1,"986":1,"988":1,"994":2,"996":2,"999":1,"1050":3,"1051":1,"1053":1,"1054":1,"1055":1,"1084":1,"1133":1,"1143":1,"1147":1,"1153":1,"1161":1,"1164":1,"1168":1,"1184":2,"1188":1,"1190":1,"1212":3,"1250":1,"1259":1,"1264":2,"1266":2,"1270":8,"1285":3,"1299":2,"1300":3,"1310":1,"1322":1,"1323":1,"1338":2,"1349":1,"1414":1,"1416":2,"1435":18,"1487":1,"1488":2,"1495":5,"1499":2,"1523":2,"1531":1,"1573":1,"1580":2,"1581":3,"1582":1,"1584":2,"1590":2,"1595":1,"1596":1,"1598":4,"1599":1,"1600":1,"1736":1,"1814":2,"1861":1,"1875":1,"1947":1,"1962":1,"1969":1,"1970":1,"1974":1,"1977":2,"1978":1,"2043":1,"2045":1,"2054":1,"2059":1,"2131":1,"2132":1,"2142":2,"2167":1,"2178":1,"2202":1,"2231":1}}],["adopt",{"3":{"0":6,"47":1,"149":1,"258":1,"265":1,"350":1,"395":1,"413":1,"448":1,"449":1,"599":1,"619":1,"649":1,"698":1,"706":1,"723":1,"725":1,"747":1,"749":1,"833":1,"838":1,"853":1,"913":1,"974":1,"1052":1,"1093":1,"1094":1,"1122":1,"1127":1,"1134":1,"1141":1,"1145":1,"1151":1,"1161":1,"1162":1,"1186":1,"1241":1,"1247":1,"1270":1,"1285":1,"1310":3,"1322":1,"1323":3,"1338":1,"1394":3,"1416":1,"1435":4,"1466":1,"1472":1,"1481":1,"1487":2,"1495":1,"1523":1,"1529":1,"1531":1,"1594":1,"1598":2,"1674":1,"1708":1,"1764":1,"1806":1,"1850":1,"1958":1,"1999":1,"2025":1,"2062":1,"2078":1,"2132":1,"2142":1,"2152":1,"2196":1,"2238":1}}],["adopts",{"3":{"0":4,"246":1,"305":1,"325":1,"400":1,"408":1,"446":1,"448":1,"572":2,"618":1,"619":1,"657":1,"665":1,"715":2,"790":1,"792":1,"794":1,"827":1,"1034":1,"1059":1,"1061":1,"1161":3,"1212":1,"1247":1,"1270":1,"1285":3,"1299":1,"1300":1,"1310":2,"1323":1,"1336":1,"1338":3,"1394":2,"1395":1,"1416":1,"1420":1,"1423":1,"1426":2,"1435":4,"1446":1,"1487":2,"1491":1,"1525":1,"1530":1,"1669":1,"2023":1,"2056":1,"2130":2,"2174":1,"2178":1,"2193":1}}],["adopter",{"3":{"0":3,"243":1,"245":1,"325":1,"360":1,"361":1,"551":1,"633":4,"634":2,"635":1,"651":2,"702":1,"726":1,"780":2,"782":2,"938":1,"939":1,"966":1,"1133":1,"1163":1,"1212":2,"1270":3,"1435":1,"1674":1,"1726":1,"1850":1,"1921":1,"1977":1,"2076":1,"2166":1,"2201":1}}],["adopters",{"3":{"0":3,"243":4,"244":1,"247":1,"249":1,"256":2,"324":1,"353":1,"415":1,"634":1,"635":1,"649":1,"782":1,"786":1,"1052":1,"1237":1,"1270":4,"1300":1,"1435":3,"1670":1,"1672":1,"1922":1}}],["adopted",{"0":{"1962":1,"2150":1},"1":{"1138":1,"1139":1,"1140":1,"1141":1,"1142":1,"1143":1,"1144":1,"1145":1,"1146":1,"1147":1,"1148":1,"1149":1,"1150":1,"1151":1,"1152":1,"1153":1,"1154":1,"1155":1,"1156":1,"1157":1},"3":{"0":23,"150":1,"153":1,"214":1,"218":1,"243":1,"259":1,"261":1,"302":1,"310":1,"350":1,"358":1,"359":1,"360":1,"365":1,"418":1,"454":1,"536":1,"545":1,"556":1,"572":1,"609":2,"642":1,"650":1,"655":1,"657":1,"664":1,"665":1,"667":1,"725":1,"726":1,"747":1,"790":1,"827":1,"854":1,"857":1,"954":1,"962":1,"973":1,"976":1,"1043":1,"1049":1,"1061":1,"1075":1,"1081":2,"1089":1,"1095":1,"1138":1,"1148":1,"1212":5,"1237":2,"1259":1,"1264":1,"1265":1,"1270":3,"1271":1,"1285":3,"1292":1,"1294":1,"1299":2,"1310":1,"1311":1,"1322":1,"1323":1,"1394":1,"1435":7,"1446":1,"1487":1,"1488":1,"1495":3,"1530":1,"1533":1,"1544":1,"1581":1,"1582":1,"1705":1,"1747":1,"1814":1,"1874":1,"1971":1,"2126":1,"2127":1,"2137":1,"2142":2,"2144":1,"2145":1,"2167":1,"2180":1,"2219":2,"2231":2}}],["advantage",{"3":{"2219":1}}],["advancing",{"3":{"1476":1}}],["advanceable",{"3":{"1395":1}}],["advanceabletimeprovider",{"3":{"1199":2,"1207":1}}],["advanced",{"3":{"1299":1,"1349":1,"1357":1,"1358":2,"2219":1}}],["advances",{"3":{"707":2,"848":1,"1229":2,"1247":3,"1270":2,"1322":2,"1323":1,"1338":1,"1394":2,"1426":1,"1437":1,"1875":1,"1947":1,"2231":1}}],["advance",{"3":{"0":1,"539":1,"690":1,"709":1,"810":1,"1075":1,"1212":1,"1323":2,"1394":1,"1481":1,"1911":1}}],["advice",{"3":{"1278":1,"1368":1,"1846":1,"2084":1}}],["advised",{"3":{"698":1}}],["advisories",{"3":{"0":1,"370":1,"618":1,"1435":1,"1452":1,"1454":1,"1458":1}}],["advisory",{"3":{"0":5,"135":1,"145":1,"150":1,"226":1,"265":1,"370":4,"371":3,"372":1,"373":1,"397":1,"440":1,"509":1,"584":1,"591":1,"617":1,"619":2,"624":3,"626":4,"640":1,"952":1,"956":1,"958":1,"1032":1,"1034":1,"1036":1,"1037":1,"1067":1,"1069":1,"1073":1,"1075":2,"1077":2,"1212":1,"1311":1,"1322":1,"1332":1,"1358":11,"1416":1,"1432":1,"1435":1,"1437":2,"1442":1,"1452":5,"1454":2,"1455":2,"1458":4,"1459":4,"1487":2,"1489":2,"1491":1,"1525":4,"1533":2,"1576":4,"1581":1,"1584":1,"1590":7,"1598":1,"1599":2,"1629":1,"1630":1,"1637":1,"1642":1,"1647":1,"1672":1,"1715":2,"2009":1,"2056":1,"2125":1,"2155":1,"2231":2,"2233":1}}],["adversarially",{"3":{"1495":2}}],["adversarial",{"3":{"1185":1,"1435":11,"1486":1,"1488":3,"1495":11,"1499":1,"1577":1,"1895":1,"1897":1}}],["advertised",{"3":{"741":1,"1379":1,"1395":1,"1488":1,"2062":1}}],["advertise",{"3":{"447":1,"1244":1,"1247":1,"1308":1,"2129":1}}],["advertises",{"3":{"31":1,"33":1,"448":1,"1218":1,"1229":1,"1270":1,"1287":1,"1310":2,"1323":1,"1349":1,"1375":1,"1379":1,"1435":2,"2130":1}}],["advertising",{"3":{"300":1,"1265":1,"1299":1,"1319":1,"1358":1,"2136":1}}],["adcpartnercollectionresult",{"3":{"1495":1}}],["adcpartnerinfo",{"3":{"1495":1}}],["adcprodstpys4way4uzb3g",{"2":{"1472":1,"1477":1},"3":{"1466":1,"1472":1,"1477":1}}],["adccollectionresult",{"3":{"1495":1}}],["adceventinfo",{"3":{"1495":1}}],["adcoauthuisettings",{"3":{"1495":1}}],["adckv",{"3":{"1473":1}}],["adc2026",{"3":{"1346":1,"1349":1}}],["adcapphostcollection",{"2":{"1448":1},"3":{"1199":2,"1203":1,"1207":1,"1327":1,"1338":5,"1448":1,"1495":1}}],["adcapphostsmoketests",{"2":{"1069":1,"1449":1},"3":{"914":1,"1067":2,"1069":2,"1070":1,"1199":1,"1203":1,"1207":2,"1327":2,"1338":11,"1448":10,"1449":2,"1495":2}}],["adcapphostfixture",{"2":{"1449":1},"3":{"914":1,"1069":2,"1070":1,"1199":3,"1203":1,"1207":3,"1327":2,"1338":6,"1448":7,"1449":1,"1495":2}}],["adcarchitecturemap",{"2":{"132":1,"1161":1,"2102":1},"3":{"132":1,"1161":1,"1199":42,"1207":2,"1322":1,"1340":1,"1395":1,"1435":58,"1486":1,"1488":5,"1525":5,"1534":1,"2041":1,"2102":1}}],["adchometicketingtests",{"3":{"1199":1,"1207":1}}],["adchometests",{"3":{"1199":1,"1207":5,"1349":1,"1437":1}}],["adchomepartnerstests",{"3":{"1199":1,"1207":1}}],["adchomepagecontent",{"3":{"1199":3,"1203":2,"1207":4,"1323":3,"1394":3,"1415":21}}],["adchomeservicedoubles",{"3":{"1199":3,"1207":1}}],["adchomecontent",{"2":{"1392":1},"3":{"1199":2,"1203":1,"1207":5,"1384":2,"1392":3,"1393":1,"1394":18,"1495":1}}],["adchome",{"2":{"554":1,"1392":1},"3":{"554":1,"556":1,"559":2,"1199":5,"1203":1,"1207":3,"1349":7,"1358":4,"1391":5,"1392":9,"1393":1,"1394":37,"1395":1,"1415":8,"1435":3,"1488":2,"1495":8,"1525":7,"1530":1,"1534":2}}],["adc",{"0":{"92":1,"93":1,"1440":1,"1454":1,"1455":1,"1456":1,"1457":1,"1513":1,"1603":1,"1678":1},"1":{"687":1,"688":1,"689":1,"690":1,"691":1,"692":1,"693":1,"694":1,"1190":1,"1191":1,"1192":1,"1193":1,"1194":1,"1195":1,"1339":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":1,"1346":1,"1347":1,"1348":1,"1349":1,"1350":1,"1351":1,"1352":1,"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1,"1359":1,"1360":1,"1361":1,"1362":1,"1363":1,"1364":1,"1365":1,"1366":1,"1367":1,"1368":1,"1369":1,"1370":1,"1371":1,"1372":1,"1373":1,"1374":1,"1375":1,"1376":1,"1377":1,"1378":1,"1379":1,"1380":1,"1381":1,"1382":1,"1383":1,"1384":1,"1385":1,"1386":1,"1387":1,"1388":1,"1389":1,"1390":1,"1391":1,"1392":1,"1393":1,"1394":1,"1395":1,"1396":1,"1397":1,"1398":1,"1399":1,"1400":1,"1401":1,"1402":1,"1403":1,"1404":1,"1405":1,"1406":1,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1413":1,"1414":1,"1415":1,"1461":1,"1462":1,"1463":1,"1464":1,"1465":1,"1466":1,"1467":1,"1468":1,"1469":1,"1521":1,"1522":1,"1523":1,"1524":1,"1525":1,"1526":1,"1527":1,"1528":1,"1529":1,"1530":1,"1531":1,"1532":1,"1533":1,"1534":1,"1627":1,"1628":1,"1629":1,"1630":1,"1631":1,"1632":1,"1633":1,"1634":1,"1635":1,"1636":1,"1637":1,"1638":1,"1639":1,"1640":1},"2":{"47":3,"58":1,"59":2,"80":1,"95":1,"229":1,"395":1,"401":1,"564":1,"642":1,"644":1,"667":1,"998":1,"999":1,"1069":2,"1191":1,"1194":1,"1200":1,"1201":1,"1203":2,"1207":10,"1215":1,"1271":1,"1308":2,"1324":1,"1327":1,"1339":1,"1340":2,"1349":6,"1358":5,"1362":1,"1368":2,"1369":3,"1377":1,"1378":2,"1379":7,"1380":1,"1394":10,"1395":11,"1401":6,"1403":6,"1414":4,"1426":1,"1434":1,"1436":9,"1437":2,"1439":1,"1441":1,"1444":7,"1445":1,"1448":1,"1454":2,"1456":1,"1457":1,"1458":1,"1470":1,"1472":4,"1473":3,"1474":1,"1480":1,"1485":9,"1486":7,"1489":2,"1491":1,"1493":1,"1495":1,"1499":1,"1502":1,"1508":1,"1511":1,"1523":1,"1524":1,"1525":1,"1530":2,"1533":1,"1610":2,"1614":2,"1618":2,"1638":1,"2033":4,"2034":4,"2035":4,"2036":1,"2037":1,"2104":1,"2110":1,"2201":1},"3":{"0":118,"3":6,"7":2,"17":1,"27":3,"31":3,"39":1,"47":5,"57":2,"58":2,"59":13,"60":2,"62":13,"63":5,"68":2,"76":1,"80":3,"85":1,"91":2,"92":2,"93":12,"94":2,"95":13,"96":4,"97":1,"98":8,"99":2,"100":1,"101":2,"102":1,"104":4,"109":3,"115":1,"117":3,"121":3,"127":3,"128":7,"132":1,"135":1,"145":6,"146":1,"147":3,"149":3,"150":1,"151":1,"152":3,"159":1,"164":1,"173":1,"175":5,"179":4,"180":7,"181":3,"184":3,"186":12,"189":1,"195":19,"200":1,"203":4,"214":1,"217":4,"218":7,"219":2,"220":3,"229":2,"230":2,"235":7,"238":1,"241":2,"243":16,"245":9,"249":1,"250":5,"251":2,"252":2,"254":2,"257":2,"258":1,"259":5,"260":9,"261":6,"279":1,"281":2,"283":3,"285":1,"291":4,"293":1,"295":13,"300":1,"310":4,"314":2,"318":7,"324":2,"325":7,"326":2,"333":1,"341":4,"344":3,"348":2,"350":11,"352":10,"353":10,"354":1,"359":2,"368":1,"373":2,"374":3,"375":1,"378":2,"384":7,"386":5,"387":3,"388":9,"390":11,"391":1,"392":6,"393":2,"395":2,"397":1,"401":10,"403":8,"405":1,"409":1,"412":2,"413":5,"414":1,"418":24,"422":5,"423":8,"424":7,"425":8,"426":18,"427":4,"428":16,"429":13,"430":3,"438":1,"439":1,"443":1,"444":2,"446":2,"448":14,"450":1,"451":3,"452":8,"453":3,"454":5,"455":1,"459":4,"463":6,"464":2,"465":5,"466":5,"468":2,"470":22,"472":1,"474":1,"475":13,"476":18,"477":14,"478":6,"480":1,"481":1,"482":1,"483":2,"486":1,"487":2,"488":2,"489":2,"490":3,"491":6,"492":5,"493":7,"494":9,"495":7,"497":12,"499":12,"502":4,"507":6,"511":4,"513":2,"514":1,"522":2,"523":2,"524":2,"527":1,"534":1,"536":4,"543":7,"545":22,"547":2,"550":4,"551":1,"552":5,"554":3,"556":33,"558":4,"559":2,"563":5,"564":17,"566":4,"567":7,"572":38,"574":2,"575":6,"576":1,"577":4,"578":7,"582":1,"583":31,"585":14,"586":1,"589":1,"591":7,"593":1,"594":3,"597":2,"598":1,"599":36,"600":5,"601":6,"602":7,"603":7,"604":3,"607":13,"608":2,"609":59,"611":10,"613":2,"616":5,"618":8,"619":1,"620":3,"621":4,"624":8,"625":7,"626":41,"627":2,"628":6,"629":7,"632":2,"638":5,"640":25,"641":1,"642":10,"643":4,"644":7,"647":1,"649":12,"651":2,"655":4,"657":9,"663":1,"664":3,"665":12,"666":1,"667":4,"668":8,"669":8,"674":16,"677":1,"681":2,"683":4,"684":1,"685":1,"686":1,"687":1,"688":2,"689":3,"690":6,"691":4,"692":1,"698":2,"702":1,"707":1,"715":1,"717":1,"719":5,"720":2,"723":2,"724":3,"725":22,"726":1,"727":3,"728":1,"733":3,"737":1,"739":1,"741":2,"743":4,"744":6,"745":1,"749":17,"751":4,"752":2,"755":2,"756":5,"757":19,"758":2,"759":4,"760":4,"761":3,"764":4,"765":2,"766":33,"767":1,"768":3,"769":4,"774":16,"780":4,"782":15,"784":4,"785":1,"790":1,"798":5,"799":4,"803":7,"804":2,"805":6,"806":1,"825":4,"826":8,"827":25,"829":1,"830":5,"831":14,"832":11,"833":14,"836":2,"837":11,"838":20,"839":4,"840":2,"841":3,"853":1,"854":6,"855":1,"857":3,"859":1,"861":15,"862":1,"863":10,"864":3,"867":2,"868":10,"869":23,"870":10,"872":1,"873":11,"875":5,"876":26,"877":10,"881":15,"884":1,"891":4,"892":1,"893":1,"897":6,"900":1,"912":1,"913":8,"914":14,"915":2,"916":2,"917":6,"920":1,"924":3,"926":8,"927":1,"939":1,"946":3,"949":1,"954":19,"956":4,"958":4,"959":3,"960":6,"963":3,"964":1,"971":10,"972":6,"973":2,"974":4,"979":1,"980":6,"981":1,"983":2,"986":2,"988":7,"991":3,"994":2,"995":5,"996":4,"998":10,"999":7,"1004":13,"1006":1,"1007":2,"1010":2,"1012":6,"1013":1,"1014":1,"1016":2,"1017":9,"1018":17,"1021":4,"1022":1,"1036":1,"1044":2,"1045":2,"1049":4,"1050":4,"1052":2,"1054":9,"1055":2,"1058":2,"1065":2,"1066":6,"1067":2,"1068":1,"1069":6,"1070":3,"1073":2,"1074":3,"1075":3,"1078":3,"1081":1,"1082":7,"1083":3,"1085":3,"1089":2,"1090":9,"1093":4,"1094":9,"1095":6,"1096":1,"1099":4,"1100":9,"1103":2,"1114":1,"1115":2,"1116":35,"1117":2,"1119":1,"1123":2,"1124":14,"1125":3,"1126":3,"1127":3,"1129":1,"1133":4,"1135":1,"1140":6,"1142":3,"1150":13,"1154":1,"1156":1,"1161":3,"1168":7,"1183":2,"1184":3,"1190":1,"1191":4,"1192":14,"1194":2,"1195":2,"1199":1987,"1200":3,"1201":4,"1202":12,"1203":1178,"1204":3,"1206":65,"1207":6030,"1208":44,"1209":136,"1211":4,"1212":31,"1213":3,"1214":1,"1215":3,"1216":1,"1229":2,"1230":1,"1237":62,"1238":1,"1239":13,"1242":3,"1244":2,"1247":63,"1259":15,"1262":2,"1264":10,"1266":1,"1270":94,"1271":70,"1285":147,"1299":189,"1300":24,"1301":2,"1303":13,"1304":3,"1306":16,"1307":9,"1308":182,"1309":32,"1310":148,"1311":70,"1312":1,"1313":5,"1314":1,"1317":5,"1321":2,"1322":130,"1323":183,"1324":14,"1325":18,"1326":6,"1327":9,"1328":1,"1330":9,"1331":23,"1332":1,"1333":7,"1334":11,"1335":11,"1337":5,"1338":194,"1339":3,"1340":17,"1341":24,"1343":15,"1344":10,"1345":2,"1346":32,"1347":10,"1348":20,"1349":768,"1350":1,"1351":19,"1352":21,"1353":10,"1354":7,"1355":4,"1356":3,"1357":3,"1358":2774,"1359":1,"1360":2,"1362":5,"1363":5,"1364":2,"1365":3,"1366":3,"1367":3,"1368":197,"1369":5,"1370":15,"1372":8,"1373":2,"1374":1,"1375":2,"1376":14,"1377":15,"1378":7,"1379":428,"1380":4,"1382":1,"1383":1,"1388":2,"1389":4,"1394":927,"1395":1370,"1396":4,"1397":3,"1398":2,"1399":5,"1400":5,"1401":419,"1402":8,"1403":21,"1404":4,"1405":14,"1406":5,"1407":3,"1408":9,"1409":10,"1410":1,"1411":7,"1412":8,"1413":13,"1414":853,"1415":258,"1416":88,"1425":3,"1426":39,"1427":2,"1430":6,"1432":1,"1434":13,"1435":815,"1436":95,"1437":117,"1438":2,"1439":5,"1440":11,"1441":16,"1442":11,"1443":4,"1444":19,"1445":11,"1446":4,"1448":5,"1449":1,"1450":4,"1451":3,"1452":2,"1453":1,"1454":26,"1455":1,"1456":6,"1457":2,"1458":18,"1459":15,"1460":4,"1461":3,"1463":14,"1464":2,"1465":1,"1466":48,"1469":4,"1470":4,"1471":7,"1472":9,"1473":25,"1474":10,"1475":14,"1476":6,"1477":5,"1478":1,"1479":1,"1480":10,"1481":2,"1484":2,"1485":35,"1486":46,"1487":23,"1488":24,"1489":18,"1490":2,"1491":30,"1493":1,"1495":147,"1497":4,"1499":3,"1502":1,"1504":1,"1508":2,"1511":1,"1513":1,"1519":1,"1521":2,"1522":1,"1523":8,"1524":5,"1525":119,"1528":1,"1529":1,"1530":21,"1531":6,"1532":1,"1533":15,"1534":10,"1572":1,"1576":8,"1581":10,"1582":1,"1584":3,"1587":2,"1588":4,"1590":23,"1592":1,"1595":8,"1597":2,"1598":4,"1600":1,"1604":1,"1605":1,"1610":17,"1614":2,"1618":6,"1619":1,"1626":10,"1627":1,"1628":1,"1629":1,"1630":4,"1636":1,"1637":1,"1638":1,"1640":1,"1644":1,"1645":1,"1648":1,"1649":2,"1652":2,"1653":1,"1655":2,"1657":2,"1658":1,"1672":1,"1674":1,"1675":1,"1676":1,"1677":3,"1678":2,"1702":1,"1704":2,"1705":1,"1708":3,"1709":1,"1723":1,"1735":1,"1747":1,"1755":1,"1764":1,"1783":1,"1784":1,"1809":2,"1814":3,"1827":1,"1838":1,"1850":1,"1858":1,"1859":3,"1860":1,"1861":1,"1864":1,"1873":1,"1874":2,"1879":1,"1883":2,"1890":1,"1895":2,"1897":1,"1915":1,"1921":6,"1922":1,"1926":1,"1932":2,"1936":1,"1945":2,"1946":2,"1947":1,"1962":2,"1969":1,"1977":5,"1978":2,"1982":6,"1994":5,"1995":1,"1999":13,"2000":1,"2003":2,"2007":1,"2012":2,"2023":3,"2024":2,"2029":1,"2032":2,"2033":7,"2034":4,"2035":4,"2036":3,"2037":2,"2041":1,"2042":2,"2043":3,"2045":6,"2046":1,"2054":3,"2056":1,"2062":2,"2066":3,"2067":1,"2082":2,"2084":1,"2100":1,"2102":1,"2104":2,"2109":1,"2110":5,"2111":2,"2112":6,"2113":1,"2126":3,"2130":9,"2131":1,"2135":1,"2136":1,"2137":2,"2149":2,"2150":5,"2155":2,"2156":1,"2157":8,"2167":1,"2178":4,"2189":1,"2191":1,"2201":4,"2227":1,"2230":1,"2231":4}}],["addview",{"3":{"1338":2}}],["addvalidationerror",{"3":{"1299":1}}],["addvalidatorsfromassemblycontaining",{"2":{"1271":1,"1318":1},"3":{"1271":1,"1299":1,"1308":3,"1318":1,"1322":5,"1414":1}}],["addvalidatorsfromassembly",{"2":{"1829":1},"3":{"1270":3,"1271":2,"1322":3,"1358":5,"1414":3,"1435":3,"1829":1}}],["addvariantcommand",{"2":{"991":1},"3":{"897":1,"988":1,"991":2}}],["addvariant",{"3":{"897":1}}],["addvarianthandler",{"2":{"898":1,"900":1,"901":1},"3":{"0":1,"897":3,"898":1,"900":3,"901":1,"1590":1,"1596":1,"1766":1,"1768":1}}],["addxdevicecapabilities",{"3":{"1416":1}}],["addx",{"3":{"1414":1}}],["addxxxui",{"3":{"1394":1}}],["addxui",{"3":{"1323":2,"1395":1}}],["addxmodule",{"3":{"1315":1}}],["addxmldatacontractserializerformatters",{"2":{"964":1},"3":{"964":1}}],["addwasmformfactor",{"3":{"1323":3,"1415":1,"1416":5}}],["addwarmuptask",{"2":{"238":1},"3":{"235":1,"238":1,"1333":1,"1338":4,"1378":1,"1379":1,"1395":1,"1414":1,"1442":1}}],["addwarmupreadiness",{"2":{"1328":1,"1333":1},"3":{"235":1,"1328":1,"1333":2,"1338":9,"1395":1,"1414":1,"1442":2,"1576":1}}],["addjsonstream",{"3":{"1323":3,"1415":1}}],["addjsonoptions",{"3":{"1310":1}}],["addjwtbearer",{"3":{"0":1,"827":2}}],["addgrpcclient",{"3":{"1311":1}}],["addgrpcreflection",{"3":{"1311":1}}],["addgrpc",{"3":{"1311":1,"1440":1}}],["addgrpcservicedefaults",{"0":{"2017":1},"2":{"1655":1,"1662":1,"1926":1,"2017":1,"2027":1},"3":{"1311":7,"1379":1,"1395":2,"1414":1,"1440":1,"1655":1,"1662":1,"1926":1,"2017":1,"2027":1}}],["addgithubprovider",{"3":{"1310":1}}],["addgithub",{"3":{"1310":1}}],["addgoogleprovider",{"3":{"1310":1}}],["addgoogle",{"3":{"1310":1}}],["addgatewayroutepolicies",{"2":{"1337":1},"3":{"1337":1,"1338":5}}],["addgatewayratelimiting",{"2":{"1336":1,"1338":1,"1446":1,"2023":1},"3":{"0":1,"674":1,"827":1,"1336":1,"1338":6,"1446":1,"2023":1}}],["addgatewaydownstreamhealthchecks",{"2":{"99":1,"827":1,"829":1,"1338":1,"1669":1,"2023":1},"3":{"0":1,"99":1,"827":1,"829":1,"1336":1,"1338":9,"1446":1,"1669":1,"2023":1}}],["addquestionansweruniqueupsertindexes",{"3":{"1209":2}}],["addlinerequest",{"3":{"1726":1}}],["addline",{"3":{"1726":1}}],["addlinkasync",{"3":{"1379":2,"1388":1,"1394":10}}],["addlink",{"3":{"1203":4,"1207":8,"1356":1,"1358":9,"1394":1}}],["addlivepollsessionidstatusindex",{"3":{"1209":2}}],["addlivepolls",{"3":{"1209":2}}],["addlogging",{"3":{"954":1,"1435":2}}],["addlocalization",{"2":{"954":1},"3":{"265":1,"954":1,"1310":2,"1323":3,"1431":1,"1435":1}}],["addlocalizationinterceptor",{"2":{"265":1},"3":{"265":1,"1323":2}}],["addrazorcomponents",{"3":{"1435":1}}],["addrabbitmq",{"2":{"2007":1},"3":{"1338":2,"1440":1,"1443":1,"2007":1}}],["addratelimiter",{"3":{"1323":1,"1338":5,"1435":1}}],["addrangeasync",{"3":{"1285":5,"1358":1}}],["addrange",{"3":{"1237":1,"1259":4,"1271":2,"1308":1,"1379":2,"1394":1,"1395":3,"1414":1}}],["addrawsqlqueryexecutor",{"3":{"1175":1}}],["addrow",{"3":{"1285":1}}],["addroleadministrationui",{"3":{"1059":1,"1323":3,"1414":2}}],["addroomnameuniqueindex",{"3":{"1209":2}}],["addroomcommandvalidatortests",{"3":{"1199":1,"1207":1,"1358":2}}],["addroomcommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1358":20}}],["addroomcommand",{"3":{"1199":7,"1203":1,"1207":2,"1358":8,"1373":1,"1379":3,"1394":1}}],["addroomrequest",{"3":{"1199":3,"1203":1,"1207":1,"1358":1,"1373":1,"1379":5,"1381":1,"1394":2}}],["addroomhandlertests",{"3":{"1199":1,"1207":2,"1358":1}}],["addroomhandler",{"3":{"926":1,"1199":2,"1203":1,"1207":2,"1270":3,"1349":2,"1358":13,"1394":2}}],["addroom",{"3":{"926":1,"1203":3,"1207":8,"1270":1,"1342":1,"1349":3,"1358":11,"1394":1}}],["addrequesttransform",{"3":{"1338":2}}],["addref",{"3":{"1323":2}}],["addreverseproxy",{"2":{"837":1},"3":{"837":1,"838":1,"1337":1,"1446":1,"2023":1}}],["address~~",{"3":{"1630":1}}],["addressaddressline2",{"3":{"1285":1}}],["addressaddressline1",{"3":{"1285":1}}],["addressable",{"3":{"572":1,"1150":1,"1270":1,"1285":1,"1310":1,"1349":2,"1358":5,"1401":1}}],["addresspropertyprefix",{"3":{"1285":1}}],["addresscolumn",{"3":{"1285":1}}],["addresscountry",{"3":{"1237":1,"1285":1}}],["addresscity",{"3":{"657":1,"1237":1,"1285":1}}],["addresszipcode",{"3":{"1237":1,"1285":1}}],["addressstate",{"3":{"1237":1,"1285":1}}],["addresstestdbcontext",{"3":{"1199":2,"1207":1}}],["addresstests",{"3":{"1199":1,"1207":2}}],["addressvalidationrules",{"2":{"1828":1},"3":{"1207":7,"1271":28,"1809":1,"1828":1}}],["addressvalidationrulestests",{"3":{"1199":1,"1207":3,"1271":8}}],["addressvalidator",{"2":{"1833":1},"3":{"1199":3,"1203":1,"1207":1,"1237":1,"1271":11,"1299":1,"1414":2,"1828":1,"1833":1}}],["addressline2maxlength",{"2":{"1271":1},"3":{"1237":1,"1271":1}}],["addressline2",{"3":{"1237":2,"1285":2,"1323":1,"1763":1}}],["addressline2rules",{"3":{"1199":3,"1203":1,"1207":1,"1271":5}}],["addressline1maxlength",{"2":{"1237":1,"1271":1},"3":{"1237":2,"1271":1}}],["addressline1rules",{"2":{"1828":1},"3":{"1199":3,"1203":1,"1207":1,"1237":2,"1271":7,"1828":1}}],["addressline1",{"3":{"657":2,"1234":1,"1237":5,"1271":1,"1285":3,"1323":1,"1763":2,"1766":1}}],["addressing",{"3":{"1152":1,"1285":2,"1323":2,"2018":1}}],["addressinvariantstests",{"3":{"1199":1,"1207":2}}],["addressinvariants",{"2":{"657":1,"658":1,"1237":3,"1271":4,"1828":1,"1832":1},"3":{"657":4,"658":1,"1198":1,"1199":13,"1203":1,"1207":2,"1229":2,"1230":1,"1234":4,"1237":16,"1271":24,"1285":2,"1766":1,"1809":2,"1828":1,"1832":1}}],["addressee",{"3":{"1308":1}}],["addressed",{"3":{"0":1,"751":1,"1264":1,"1270":3,"1284":1,"1299":1,"1322":1,"1349":1,"1358":5,"1394":1,"1401":1,"1414":1,"1809":1}}],["addresses",{"3":{"0":2,"100":1,"160":1,"175":1,"219":1,"280":1,"282":1,"284":1,"395":1,"403":1,"574":1,"658":1,"749":1,"828":1,"831":1,"832":1,"853":1,"888":1,"1017":1,"1091":1,"1093":1,"1124":1,"1237":2,"1270":2,"1285":1,"1299":4,"1308":7,"1310":4,"1311":2,"1321":2,"1322":3,"1323":1,"1333":1,"1338":1,"1358":7,"1368":1,"1394":4,"1405":2,"1406":1,"1407":2,"1414":6,"1416":3,"1435":5,"1440":1,"1446":1,"1454":1,"1472":1,"1473":1,"1638":1,"1640":1,"1889":2,"1934":1,"2001":1,"2195":1}}],["address",{"0":{"1407":1},"2":{"655":2,"660":1,"1907":1},"3":{"0":11,"59":1,"157":2,"159":1,"161":1,"175":1,"177":1,"179":1,"180":1,"225":1,"235":1,"280":2,"284":3,"348":1,"350":4,"352":2,"353":8,"363":1,"390":1,"403":1,"450":1,"655":2,"657":15,"658":1,"659":1,"660":2,"717":1,"733":1,"749":1,"827":5,"828":1,"829":1,"831":1,"833":1,"837":1,"838":2,"839":1,"841":1,"853":3,"854":5,"855":1,"856":2,"857":1,"881":1,"1018":1,"1020":1,"1059":1,"1124":2,"1126":1,"1184":2,"1188":1,"1198":1,"1199":14,"1203":1,"1207":2,"1212":3,"1229":2,"1230":1,"1234":7,"1237":39,"1241":1,"1247":2,"1270":1,"1271":43,"1285":13,"1288":3,"1291":1,"1292":1,"1293":6,"1294":3,"1299":71,"1308":4,"1310":11,"1311":1,"1321":1,"1322":36,"1323":49,"1326":1,"1329":1,"1336":1,"1338":18,"1343":1,"1346":1,"1349":6,"1354":4,"1358":28,"1363":1,"1364":2,"1367":1,"1368":3,"1379":1,"1388":2,"1390":2,"1393":1,"1394":16,"1395":3,"1401":1,"1404":1,"1405":5,"1406":4,"1407":6,"1414":55,"1415":3,"1416":35,"1423":1,"1426":1,"1431":2,"1432":1,"1435":34,"1437":5,"1440":5,"1443":1,"1446":2,"1466":2,"1475":1,"1491":1,"1495":1,"1497":1,"1525":1,"1540":1,"1576":1,"1595":1,"1628":1,"1629":4,"1630":1,"1638":8,"1640":34,"1652":1,"1661":2,"1665":1,"1666":1,"1718":1,"1726":1,"1741":1,"1745":2,"1746":3,"1749":2,"1763":6,"1764":8,"1766":8,"1767":2,"1768":1,"1789":1,"1809":3,"1824":1,"1827":1,"1828":2,"1832":1,"1833":1,"1895":1,"1907":1,"1910":1,"1972":4,"1976":5,"1977":1,"1978":3,"1997":1,"2005":1,"2022":1,"2023":1,"2075":1,"2131":1,"2139":1,"2143":1,"2195":1,"2197":1,"2234":1,"2236":1}}],["addredisdistributedcache",{"2":{"234":1,"245":1,"254":1,"257":1,"259":1,"260":1},"3":{"0":1,"234":1,"245":1,"254":1,"257":1,"259":1,"260":1,"1338":2,"1466":1}}],["addredisoutputcaching",{"2":{"241":1,"243":1,"247":1,"259":1,"260":1,"261":1,"390":1,"392":1,"393":1,"1331":1,"1667":1},"3":{"0":3,"241":1,"243":1,"247":1,"259":1,"260":2,"261":2,"390":1,"392":1,"393":1,"1331":2,"1338":6,"1441":1,"1525":1,"1599":1,"1667":1}}],["addredisclient",{"2":{"234":1,"257":1,"260":1},"3":{"0":2,"234":1,"245":1,"254":1,"257":1,"259":1,"260":1,"1300":2,"1322":1,"1338":2}}],["addrediscaching",{"2":{"237":1,"241":1,"245":2,"247":1,"260":1,"261":1,"1922":1},"3":{"0":3,"235":1,"237":1,"241":1,"245":2,"247":1,"259":1,"260":2,"261":2,"996":1,"1300":1,"1331":2,"1338":6,"1441":1,"1667":1,"1922":1}}],["addbunitpersistentcomponentstate",{"3":{"1435":2}}],["addbookmarkasync",{"3":{"1394":2}}],["addboundedblazorcircuits",{"2":{"831":1,"832":1,"1124":1},"3":{"831":1,"832":3,"1124":1,"1323":6,"1415":1}}],["addbrokermessaging",{"2":{"195":1,"196":1,"200":1,"201":1,"915":1,"917":1,"931":1,"934":1,"979":1,"1257":1,"1445":1,"1450":1,"1611":1,"2007":1,"2021":1},"3":{"195":1,"196":1,"200":1,"201":1,"640":3,"690":1,"915":1,"917":1,"931":1,"934":1,"979":1,"1257":1,"1259":10,"1310":1,"1315":1,"1316":1,"1319":2,"1322":12,"1325":1,"1338":3,"1379":1,"1440":3,"1443":2,"1445":1,"1450":1,"1466":1,"1533":1,"1590":2,"1611":1,"2007":1,"2021":1}}],["addbrowserdevicecapabilities",{"2":{"413":1,"1598":1,"2118":1},"3":{"0":1,"413":1,"1415":2,"1416":20,"1598":1,"2118":1}}],["addhours",{"3":{"1435":1}}],["addhostedservice",{"3":{"1100":1,"1259":1,"1285":1,"1316":1,"1322":5,"1395":4,"1441":1}}],["addhandler",{"3":{"1358":1}}],["addhybridcache",{"3":{"733":1,"1300":3,"1322":2,"1437":1}}],["addhttpforwarderwithservicediscovery",{"2":{"837":1,"1466":1},"3":{"837":1,"1466":1}}],["addhttpmessagehandler",{"3":{"507":1,"1310":1,"1323":1}}],["addhttpclient",{"3":{"1358":1,"1368":2,"1415":2,"1652":1,"1701":1}}],["addhttpclientinstrumentation",{"2":{"402":1,"1442":1},"3":{"402":1,"1442":1}}],["addhttpcontextaccessor",{"2":{"303":1},"3":{"303":1,"1310":3,"1311":1,"1323":2,"1414":2,"1435":2}}],["addunsupportedrange",{"3":{"1309":2}}],["addunsupported",{"3":{"1309":2}}],["addunchecked",{"3":{"1237":2}}],["adduseremailconfirmation",{"3":{"1209":2}}],["adduserlockedon",{"3":{"1209":2}}],["adduseravatar",{"3":{"1209":2}}],["adduseradministrationui",{"3":{"1059":1,"1323":4,"1414":3}}],["adduserpreferences",{"3":{"1209":2,"1495":2,"1590":1}}],["addusersessionbookmarksessionidindex",{"3":{"1209":2}}],["adduserdataexportsection",{"2":{"846":1,"1316":1,"1671":1},"3":{"583":2,"846":1,"1316":1,"1322":5,"1414":5,"1671":1}}],["adduiratelimiting",{"3":{"832":2,"1124":1,"1323":4}}],["adduimodule",{"2":{"652":1},"3":{"649":1,"652":1,"1323":6,"1394":14,"1414":5,"1668":1}}],["adduishared",{"2":{"412":1,"414":1,"415":1,"558":1,"647":1,"681":1,"686":1,"2121":1,"2122":1},"3":{"0":2,"243":1,"245":1,"258":2,"265":4,"273":1,"412":1,"413":2,"414":1,"415":1,"556":1,"558":1,"647":1,"649":2,"681":1,"686":1,"881":1,"954":1,"979":2,"1212":1,"1323":33,"1394":2,"1415":11,"1416":19,"1433":2,"1435":13,"1652":2,"2084":1,"2117":1,"2118":2,"2121":1,"2122":1}}],["adddays",{"3":{"1368":1}}],["adddataprotection",{"3":{"1338":1}}],["adddatabasehealthchecks",{"2":{"1331":1},"3":{"1331":1,"1338":2,"1442":1}}],["adddatabase",{"2":{"2096":1},"3":{"633":1,"1683":1,"2096":1}}],["adddbcontextfactory",{"3":{"1285":1}}],["adddevicecapabilities",{"3":{"1416":2}}],["adddevicecapabilitydefaults",{"2":{"413":1,"681":1,"682":1,"1490":1,"2117":1,"2122":1},"3":{"413":2,"681":1,"682":1,"1323":2,"1416":35,"1490":1,"2117":1,"2122":1}}],["adddebug",{"3":{"1415":1}}],["adddefaulthealthchecks",{"2":{"1328":1,"1331":1},"3":{"1328":1,"1331":1,"1338":4,"1442":1,"1576":1}}],["adddefaultpolicy",{"2":{"774":1},"3":{"774":1}}],["adddsarcontrollers",{"3":{"725":1}}],["adddomainevent",{"0":{"2090":1},"2":{"20":1,"1233":1,"1251":1,"1700":1,"1837":1,"2019":1,"2090":1,"2097":1,"2110":1},"3":{"20":2,"1231":1,"1233":1,"1237":4,"1251":1,"1252":1,"1259":1,"1285":1,"1344":1,"1348":1,"1349":8,"1358":3,"1395":3,"1414":6,"1664":1,"1700":1,"1809":1,"1837":1,"2019":1,"2090":2,"2097":1,"2110":1}}],["addfeaturemanagement",{"2":{"300":1,"2137":1},"3":{"300":1,"303":1,"1310":3,"1435":3,"2135":1,"2137":1}}],["addforwardedjwtbearercore",{"3":{"827":1,"1299":1,"1310":2}}],["addforwardedjwtbearer",{"2":{"31":1,"34":1,"1585":1,"1610":1,"1612":1,"1618":1,"1666":1,"1896":1,"1897":1,"1903":1},"3":{"31":3,"34":1,"827":1,"1212":1,"1299":2,"1310":16,"1435":2,"1489":1,"1525":1,"1585":1,"1610":1,"1612":1,"1618":1,"1666":1,"1896":1,"1897":1,"1903":1}}],["addtokenpermissiongrants",{"3":{"1414":2}}],["addtokenifnotstopword",{"3":{"1358":1}}],["addtocalendarbutton",{"3":{"1323":1}}],["addtocalendarbuttontests",{"3":{"1199":1,"1207":2}}],["addtogroupasync",{"3":{"1308":1,"1322":1}}],["addtransforms",{"3":{"1338":3}}],["addtransient",{"3":{"1308":1,"1322":1,"1323":1}}],["addtrustedcallerheader",{"2":{"180":1},"3":{"0":1,"180":1,"827":1,"1323":5}}],["addtwofactorauthentication",{"2":{"674":1,"1059":1,"1315":1,"1321":1,"2201":1},"3":{"674":1,"1059":1,"1270":1,"1299":3,"1315":1,"1321":1,"1322":1,"2201":1}}],["addtypedserviceclient",{"2":{"67":1,"68":1,"70":1,"74":1},"3":{"67":1,"68":2,"70":1,"74":1,"1310":2,"1315":1,"1322":2,"1338":1}}],["addtypedgrpcclient",{"0":{"2018":1}}],["addtypedgrpcclient",{"2":{"53":1,"67":1,"68":1,"70":1,"100":1,"585":1,"1327":1,"1655":1,"1662":1,"2008":1,"2018":1,"2027":1,"2029":1,"2030":1,"2037":1},"3":{"53":1,"67":1,"68":2,"70":1,"100":1,"585":1,"1308":3,"1310":1,"1311":7,"1322":1,"1327":1,"1338":3,"1377":1,"1379":4,"1395":2,"1414":2,"1440":3,"1466":1,"1576":1,"1655":1,"1662":1,"2008":1,"2018":1,"2027":1,"2029":1,"2030":1,"2037":1}}],["addnotificationui",{"3":{"1323":10,"1395":2}}],["addnotificationuserexportclient",{"3":{"1308":4,"1414":1}}],["addnotificationlivechannelclient",{"2":{"1945":1},"3":{"1308":4,"1945":1}}],["addnotificationmodule",{"2":{"1307":1},"3":{"1307":1,"1308":5}}],["addnotificationapplicationservices",{"2":{"1303":1,"1308":1},"3":{"1303":1,"1308":8}}],["addnotificationinfrastructure",{"2":{"1315":1},"3":{"1285":3,"1308":2,"1315":1,"1322":1}}],["addnotificationcontrollers",{"2":{"434":1,"725":1,"1665":1},"3":{"434":1,"725":1,"1308":10,"1665":1}}],["addnpgsql",{"3":{"0":1,"1034":1,"1338":1}}],["addnativepushnotifications",{"2":{"434":1,"1315":1,"1935":1},"3":{"0":1,"434":1,"1308":2,"1315":1,"1322":9,"1935":1}}],["addidentityui",{"3":{"1414":8}}],["addidentitymodule",{"3":{"1414":7}}],["addidentityattendeeclient",{"2":{"1412":1},"3":{"1412":1,"1414":6}}],["additemhandler",{"3":{"1685":1,"1764":1,"1766":2}}],["additemrequest",{"3":{"1685":1}}],["additem",{"3":{"1683":1,"1685":4,"1727":1,"1764":1}}],["additemasync",{"3":{"1394":1}}],["additemcommand",{"2":{"91":1},"3":{"91":1,"1270":1,"1685":1}}],["additively",{"3":{"0":2,"186":1,"265":1,"1270":1,"1310":5,"1337":1,"1414":1,"1666":1,"1961":1}}],["additive",{"3":{"0":7,"39":1,"41":1,"78":1,"93":1,"95":1,"150":1,"158":1,"255":1,"305":1,"546":1,"564":2,"565":1,"568":1,"599":1,"600":1,"698":1,"699":1,"784":1,"827":1,"852":1,"854":1,"903":1,"905":1,"907":2,"920":2,"922":1,"926":1,"1024":1,"1026":1,"1027":1,"1034":1,"1075":1,"1084":1,"1229":1,"1234":1,"1237":1,"1247":1,"1249":1,"1259":2,"1266":1,"1270":7,"1285":5,"1289":1,"1299":10,"1308":2,"1310":10,"1322":2,"1323":1,"1337":1,"1338":6,"1349":1,"1368":1,"1394":1,"1395":3,"1408":1,"1414":7,"1416":1,"1419":1,"1426":1,"1435":3,"1441":1,"1454":1,"1466":3,"1476":1,"1546":1,"1610":1,"1630":1,"1631":1,"1638":1,"1666":1,"1719":1,"1723":1,"1726":1,"1733":1,"1888":1,"1891":1,"1964":1,"2062":1,"2122":1,"2197":1}}],["additions",{"3":{"0":2,"99":1,"111":1,"135":1,"141":1,"344":1,"413":1,"718":1,"906":1,"1093":1,"1116":1,"1132":1,"1262":1,"1285":1,"1349":1,"1368":1,"1414":1,"1421":1,"1435":5,"1442":1,"1488":1,"1495":5,"1630":1,"1637":1,"1684":1,"1778":2,"2173":1,"2209":1}}],["additionalhandlers",{"3":{"1323":1}}],["additionaltoken",{"3":{"1323":4}}],["additionaltokenissuingpaths",{"2":{"1184":1},"3":{"1184":1}}],["additionalclaims",{"2":{"1287":1},"3":{"1287":1,"1299":4,"1487":1}}],["additionalrequestpatterns",{"2":{"1091":1,"1423":1},"3":{"1091":1,"1093":1,"1423":1,"1426":3}}],["additionalproperties",{"3":{"1018":1,"1365":1,"1426":8}}],["additionalportmappings",{"2":{"92":1,"95":1,"96":1,"102":1},"3":{"92":1,"95":2,"96":1,"102":1,"790":1,"1466":1}}],["additionalfiles",{"3":{"135":1}}],["additionalfilters",{"3":{"1323":3}}],["additionalfilter",{"2":{"889":1},"3":{"0":1,"889":2,"1285":6,"1395":2}}],["additionally",{"3":{"18":1,"126":1,"132":1,"135":1,"140":1,"207":1,"212":1,"214":1,"235":1,"383":1,"556":1,"562":1,"618":1,"626":1,"633":1,"666":1,"691":1,"692":1,"698":1,"725":1,"827":1,"832":1,"881":1,"1229":1,"1256":1,"1259":2,"1263":1,"1270":2,"1285":3,"1300":1,"1308":3,"1310":3,"1322":3,"1323":2,"1331":1,"1333":1,"1338":3,"1349":4,"1351":1,"1352":1,"1358":2,"1368":1,"1372":1,"1379":5,"1395":3,"1397":1,"1401":5,"1414":1,"1415":1,"1416":3,"1426":3,"1427":1,"1430":1,"1435":7,"1442":1,"1444":1,"1454":2,"1487":1,"1590":1,"1626":1,"1634":1,"1638":1,"1639":1,"1640":2,"1642":1,"1756":1,"1820":1,"1968":1,"1999":1}}],["additionalassemblies=",{"3":{"1323":1}}],["additionalassemblies",{"2":{"649":1,"651":1,"2076":1},"3":{"0":1,"649":1,"651":2,"905":1,"1212":1,"1285":2,"1323":1,"1415":1,"2076":1}}],["additional",{"2":{"1458":1},"3":{"0":1,"92":1,"395":1,"401":1,"1280":1,"1285":3,"1310":1,"1322":1,"1323":1,"1338":1,"1358":1,"1394":2,"1414":1,"1416":1,"1435":1,"1458":1,"1629":1,"1640":2,"1775":1}}],["addition",{"3":{"0":1,"71":1,"135":1,"145":1,"197":1,"265":1,"564":1,"566":1,"649":1,"657":1,"686":1,"903":1,"915":1,"1034":1,"1237":1,"1285":2,"1299":3,"1310":2,"1349":1,"1379":1,"1394":4,"1414":4,"1435":3,"1437":2,"1454":1,"1458":1,"1584":1,"1637":1,"1638":1,"1640":2,"1653":1,"1778":1}}],["addinitscript",{"3":{"1435":1}}],["addinitscriptasync",{"3":{"1435":1}}],["addinterceptor",{"3":{"1311":1}}],["addinterceptors",{"2":{"715":1},"3":{"715":1}}],["addinternalcommands",{"2":{"1044":1,"2191":1},"3":{"674":1,"1044":1,"1209":8,"1315":1,"1317":1,"1322":2,"2191":1}}],["addinteractiveserverrendermode",{"2":{"556":1},"3":{"556":1,"1435":1}}],["addinteractiveservercomponents",{"2":{"556":1,"831":1,"832":1,"1124":1},"3":{"556":1,"831":1,"832":1,"1124":1,"1323":2,"1415":1,"1435":1}}],["addinteractivewebassemblyrendermode",{"2":{"556":1},"3":{"556":1}}],["addinteractivewebassemblycomponents",{"2":{"556":1},"3":{"556":1}}],["addinclude",{"3":{"549":1,"1240":1,"1247":1,"1435":1}}],["addinboxmessages",{"2":{"200":1},"3":{"195":1,"200":1,"1209":8,"1590":1}}],["adding",{"0":{"1721":1},"2":{"983":1},"3":{"0":2,"7":1,"78":2,"88":1,"96":1,"100":1,"135":2,"158":1,"166":1,"168":1,"187":1,"255":1,"265":3,"293":1,"318":1,"361":1,"365":1,"372":1,"390":1,"391":2,"397":3,"415":1,"443":1,"448":1,"465":1,"470":1,"490":1,"552":1,"564":2,"574":1,"585":1,"642":1,"648":1,"698":1,"707":1,"708":1,"725":2,"740":1,"741":2,"743":1,"759":1,"781":1,"783":2,"784":1,"819":1,"827":2,"838":2,"839":1,"855":1,"862":1,"913":1,"921":1,"923":1,"948":1,"983":1,"986":1,"1011":1,"1012":1,"1016":1,"1048":1,"1052":1,"1068":1,"1109":1,"1144":1,"1147":1,"1174":1,"1179":1,"1212":3,"1229":2,"1237":5,"1243":1,"1247":5,"1249":1,"1259":6,"1262":1,"1270":3,"1271":1,"1278":1,"1285":17,"1299":7,"1308":2,"1309":3,"1310":17,"1311":1,"1322":5,"1323":12,"1338":8,"1342":1,"1343":1,"1349":7,"1351":2,"1358":42,"1361":1,"1368":6,"1379":6,"1385":1,"1388":1,"1394":16,"1395":7,"1400":1,"1401":1,"1414":14,"1415":1,"1416":2,"1426":1,"1435":17,"1437":2,"1440":1,"1446":1,"1466":1,"1472":2,"1487":1,"1488":2,"1495":2,"1525":2,"1541":2,"1575":1,"1637":1,"1642":1,"1645":1,"1652":2,"1657":1,"1676":1,"1685":1,"1688":2,"1691":1,"1697":1,"1719":1,"1721":1,"1726":2,"1728":2,"1754":1,"1766":3,"1768":1,"1775":1,"1780":1,"1825":1,"1834":1,"1840":1,"1857":1,"1864":1,"1867":1,"1888":1,"1910":1,"1912":1,"1931":1,"1944":1,"1957":2,"1960":1,"1971":1,"2027":1,"2029":1,"2076":1,"2077":1,"2078":1,"2079":1,"2082":2,"2084":1,"2099":2,"2103":1,"2105":1,"2107":1,"2130":1,"2141":1,"2146":1,"2171":1,"2193":1,"2194":1,"2219":1,"2229":2}}],["addinfrastructurehealthchecks",{"2":{"166":1,"1034":1,"1331":1,"1441":1,"1442":1,"1669":1,"1726":1},"3":{"166":1,"1034":1,"1331":1,"1338":6,"1437":1,"1441":1,"1442":1,"1669":1,"1726":1}}],["addinfrastructure",{"2":{"157":1,"230":1,"257":1,"260":1,"344":1,"672":1,"931":1,"949":1,"979":1,"996":1,"1285":1,"1318":1,"1367":1,"1823":1,"1841":1,"1850":1,"1908":1,"1934":1,"1937":1,"2094":1},"3":{"0":1,"117":2,"157":1,"230":1,"243":1,"257":1,"259":2,"260":1,"281":1,"310":1,"344":1,"434":1,"672":1,"674":3,"733":1,"931":2,"946":1,"949":1,"979":2,"996":2,"1059":1,"1212":1,"1259":7,"1270":1,"1279":1,"1285":14,"1299":2,"1300":3,"1308":4,"1310":1,"1315":1,"1316":2,"1317":3,"1318":1,"1319":2,"1322":20,"1323":2,"1367":1,"1368":1,"1395":2,"1435":1,"1823":1,"1841":1,"1850":1,"1908":1,"1934":1,"1937":1,"1999":1,"2094":1,"2097":1}}],["addorupdate",{"3":{"1308":1}}],["addorderitemrequest",{"3":{"1685":1}}],["addordersmodule",{"3":{"1650":1,"1652":1}}],["addorderstatusandcreatedonindexes",{"3":{"564":1}}],["addorderlinecount",{"3":{"1599":1}}],["addorderlinecommand",{"3":{"1199":3,"1207":1}}],["addorderlinelistprice",{"3":{"1026":1}}],["addorderby",{"3":{"549":1,"1240":1,"1247":2,"1435":1}}],["addoutcome",{"3":{"1395":2}}],["addoutboxorigin",{"3":{"1209":8}}],["addoutboxinboxretentionindexes",{"3":{"564":2,"1209":8,"1590":1}}],["addoutputcache",{"2":{"245":1,"390":1,"393":1,"1922":1},"3":{"243":3,"245":1,"390":2,"393":1,"1300":1,"1310":2,"1338":1,"1922":1}}],["addoutputcacheevictionhandler",{"2":{"255":1,"258":1,"390":1,"391":1,"393":1,"1378":1},"3":{"0":1,"255":1,"258":1,"390":1,"391":1,"393":1,"1259":2,"1310":5,"1378":1,"1525":1}}],["addoption",{"3":{"1401":5}}],["addoptions",{"3":{"0":1,"674":3,"676":1,"707":1,"733":1,"827":1,"832":1,"1212":1,"1285":1,"1299":2,"1300":1,"1308":1,"1319":1,"1322":4,"1323":3,"1338":3,"1395":2,"2012":1}}],["addoperationtransformer",{"3":{"1310":1}}],["addopenaiprovider",{"2":{"1419":1},"3":{"1091":1,"1419":1,"1426":5}}],["addopenapi",{"2":{"454":1,"1928":1},"3":{"448":1,"454":5,"1050":1,"1310":3,"1928":1}}],["addopentelemetryexporters",{"2":{"397":1,"1332":1,"2155":1,"2156":1},"3":{"397":1,"1332":1,"1338":4,"1442":1,"1466":1,"2155":1,"2156":1}}],["addmaildev",{"3":{"1325":2,"1338":1,"1440":1,"1443":1,"1669":1}}],["addmasstransit",{"3":{"1322":2}}],["addmauiformfactor",{"3":{"1323":1,"1415":3,"1416":5}}],["addmauiblazorwebview",{"3":{"1212":1,"1415":1}}],["addmauipushdevicetokenprovider",{"2":{"413":1,"434":1,"436":1,"1668":1},"3":{"0":1,"413":1,"434":1,"436":1,"1415":3,"1416":5,"1668":1}}],["addmauidevicecapabilities",{"2":{"411":1,"413":1,"415":1,"1416":1,"2117":1,"2118":1,"2121":1},"3":{"0":1,"411":2,"413":3,"415":1,"1416":30,"2117":1,"2118":1,"2121":1}}],["addminiprofiler",{"3":{"1310":1}}],["addminiprofilerifenabled",{"2":{"1665":1},"3":{"1310":1,"1665":1}}],["addmultitenancytests",{"3":{"1199":1,"1207":2,"1285":2,"1437":2}}],["addmultitenancy",{"2":{"696":1,"979":1,"1232":1,"1850":1},"3":{"674":1,"696":1,"698":1,"979":1,"1232":1,"1237":2,"1270":2,"1285":6,"1310":1,"1315":1,"1316":1,"1322":4,"1525":1,"1850":1}}],["addmudservices",{"2":{"954":1},"3":{"265":1,"954":1,"1323":5,"1415":1,"1435":6}}],["addmetrics",{"3":{"1420":1,"1426":1}}],["addmeter",{"2":{"404":1,"407":1,"408":1},"3":{"258":1,"261":1,"404":1,"407":1,"408":1,"1247":1,"1270":1,"1338":2,"1441":1,"2158":1}}],["addmemorycache",{"3":{"1300":2}}],["addmessagebroker",{"2":{"644":1,"2007":1},"3":{"640":1,"644":1,"1325":2,"1338":3,"1440":1,"1443":1,"1669":1,"2007":1}}],["addmvc",{"3":{"448":1}}],["addmoduleordersinfrastructure",{"2":{"1650":1},"3":{"1650":1}}],["addmodule",{"3":{"1414":1}}],["addmoduleidentityinfrastructure",{"3":{"1414":4}}],["addmoduleidentityapplication",{"3":{"1414":6}}],["addmoduleidentityapi",{"3":{"1405":1,"1414":6}}],["addmoduleengagementinfrastructure",{"3":{"1395":6}}],["addmoduleengagementapplication",{"3":{"1395":4}}],["addmoduleengagementapi",{"3":{"318":1,"1379":1,"1395":12,"1994":1}}],["addmoduleconferenceinfrastructure",{"2":{"1367":1},"3":{"1367":1,"1368":2,"1379":2}}],["addmoduleconferenceapplication",{"3":{"1358":2,"1379":2}}],["addmoduleconferenceapi",{"2":{"1347":1,"1376":1},"3":{"1347":1,"1376":1,"1379":3}}],["addmodulenotificationapplication",{"3":{"1308":4,"1437":1}}],["addmodulesalesinfrastructure",{"2":{"790":1},"3":{"790":1}}],["addmodulehealthchecks",{"2":{"1699":1},"3":{"583":1,"1310":6,"1665":1,"1699":1}}],["addmodulehost",{"2":{"59":1,"63":1,"672":1,"675":1,"984":1,"1880":1},"3":{"0":1,"59":1,"63":1,"583":1,"672":1,"674":1,"675":1,"980":1,"984":1,"1310":8,"1314":2,"1322":4,"1662":1,"1669":1,"1880":1}}],["addmmcachatclient",{"2":{"1094":1,"1367":1,"1420":1,"1423":1,"1426":1,"1491":1,"2171":1,"2174":1},"3":{"1018":2,"1091":1,"1094":1,"1367":2,"1368":4,"1420":1,"1423":1,"1426":18,"1491":2,"1525":1,"2171":1,"2174":1}}],["addmmcaapplicationpipeline",{"2":{"115":1,"117":1,"122":1,"126":1,"127":1,"128":1,"1262":1,"1315":1,"1661":1,"1823":1,"1882":1},"3":{"0":3,"115":1,"117":1,"122":2,"126":1,"127":1,"128":1,"1262":2,"1310":1,"1311":1,"1315":2,"1322":11,"1395":1,"1414":2,"1435":2,"1437":1,"1661":1,"1823":2,"1882":1}}],["addmmcagatewaytests",{"3":{"1199":1,"1207":2,"1338":9,"1437":2}}],["addmmcagateway",{"2":{"825":1,"829":1,"1508":1},"3":{"0":3,"674":1,"825":1,"827":1,"829":1,"1337":2,"1338":11,"1446":4,"1508":1,"1662":1,"2023":1}}],["addenda",{"0":{"1638":1}}],["addengagementui",{"3":{"1395":6,"1415":1}}],["addengagementuserexportclient",{"3":{"1395":4,"1414":1}}],["addengagementmodule",{"3":{"1395":11}}],["addengagementbookmarkcountclient",{"2":{"1377":1},"3":{"1322":1,"1358":1,"1377":1,"1395":5}}],["addentityframework",{"3":{"1310":1}}],["addentityconfigurationassembly",{"2":{"1315":1},"3":{"1285":1,"1315":1,"1322":1}}],["addentitycrudtests",{"3":{"1199":1,"1207":1}}],["addentitycrud",{"2":{"328":1,"920":1,"924":1,"925":1,"1351":1,"1353":1},"3":{"0":3,"122":1,"328":1,"572":1,"920":2,"922":2,"924":1,"925":1,"926":2,"1262":1,"1265":2,"1270":8,"1315":1,"1322":4,"1349":3,"1351":1,"1353":1,"1358":21,"1665":1,"1882":1}}],["addentityupdate",{"2":{"122":1,"1882":1},"3":{"0":1,"122":1,"926":2,"1262":1,"1265":1,"1270":2,"1315":1,"1322":4,"1358":5,"1665":1,"1882":1}}],["addentityupdateverb",{"2":{"122":1,"1265":1},"3":{"0":1,"122":1,"926":1,"1262":1,"1265":1,"1270":2,"1315":1,"1322":4,"1665":1}}],["adder",{"3":{"1285":1}}],["adderrorlocalization",{"3":{"1310":8,"1414":1}}],["adderrors",{"3":{"1229":1}}],["adderrorresources",{"2":{"636":1,"979":1,"1649":1,"1652":1,"1683":1,"1700":1,"1727":1,"2096":1},"3":{"979":1,"1310":9,"1378":1,"1379":1,"1395":3,"1414":2,"1590":1,"1649":1,"1652":1,"1665":1,"1683":1,"1700":1,"1727":1,"2096":1}}],["adderrorresources<",{"3":{"633":1,"636":1}}],["addexternallogincookie",{"3":{"1310":1}}],["addexternalauthproviders",{"2":{"350":1,"1666":1,"1974":1,"1977":1,"1978":1},"3":{"0":1,"350":2,"1310":1,"1414":1,"1666":1,"1974":1,"1977":1,"1978":1}}],["addexplicitmigrationsassemblies",{"3":{"1285":1}}],["addeventlistener",{"3":{"1416":1}}],["addeventticketingurl",{"3":{"1209":2}}],["addeventsponsorshippacketurl",{"3":{"1209":2}}],["addeventspeaker",{"3":{"1203":3,"1207":8,"1270":1,"1349":3,"1358":9,"1368":1}}],["addeventspeakerhandlertests",{"3":{"1199":1,"1207":2,"1358":1}}],["addeventspeakerhandler",{"3":{"1199":2,"1203":1,"1207":2,"1270":2,"1358":11}}],["addeventspeakercommandvalidatortests",{"3":{"1199":1,"1207":1,"1358":1}}],["addeventspeakercommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1358":5}}],["addeventspeakercommand",{"3":{"1199":7,"1203":1,"1207":2,"1358":8,"1379":4}}],["addeventspeakerrequest",{"3":{"1199":3,"1203":1,"1207":1,"1358":1,"1373":1,"1379":5}}],["addeventorganizercontactemail",{"3":{"1209":2}}],["addeventquestionmoderationdefault",{"3":{"1209":2}}],["addeventquestionanswer",{"3":{"1203":3,"1207":8,"1349":4,"1351":1,"1358":14,"1525":1}}],["addeventquestionanswercommandvalidatortests",{"3":{"1199":1,"1207":1,"1358":1}}],["addeventquestionanswercommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1358":11}}],["addeventquestionanswercommand",{"3":{"1199":7,"1203":1,"1207":2,"1358":12,"1379":4}}],["addeventquestionanswerrequest",{"2":{"1373":1},"3":{"1199":3,"1203":1,"1207":1,"1358":1,"1373":1,"1379":6}}],["addeventquestionanswerhandlertests",{"3":{"1199":1,"1207":2,"1358":1}}],["addeventquestionanswerhandler",{"3":{"690":1,"1199":2,"1203":1,"1207":2,"1349":7,"1351":2,"1358":20}}],["addeventupcaster",{"2":{"846":1,"848":1,"850":1,"1249":1,"1269":1,"1890":1},"3":{"0":1,"80":1,"846":1,"848":1,"850":1,"1247":3,"1249":1,"1259":3,"1269":1,"1270":3,"1315":1,"1322":3,"1890":1}}],["addemailconfirmation",{"2":{"2201":1},"3":{"674":1,"1059":1,"1299":2,"1315":1,"1321":1,"1322":1,"2201":1}}],["addedroomkey",{"3":{"1358":1}}],["added",{"2":{"784":1,"1754":1},"3":{"0":7,"3":1,"11":1,"21":1,"22":1,"27":1,"45":1,"47":1,"57":1,"93":2,"94":1,"109":1,"111":1,"121":1,"135":2,"138":1,"139":1,"140":1,"145":2,"149":1,"177":1,"184":1,"195":1,"201":3,"216":2,"219":1,"228":1,"248":1,"252":1,"254":1,"256":4,"258":1,"259":1,"317":1,"323":1,"342":1,"348":2,"364":1,"374":1,"383":1,"395":1,"397":3,"401":1,"408":1,"448":1,"450":1,"468":4,"475":1,"477":1,"489":1,"511":1,"526":1,"550":1,"558":1,"564":6,"565":1,"566":2,"575":1,"601":1,"611":1,"616":3,"618":2,"624":3,"626":1,"629":1,"633":1,"640":1,"655":1,"656":1,"665":1,"676":1,"690":3,"697":1,"698":3,"703":1,"715":3,"725":2,"741":1,"744":1,"745":1,"750":1,"759":1,"766":1,"780":2,"782":7,"784":2,"805":1,"812":1,"819":1,"825":1,"826":1,"827":1,"829":3,"838":2,"862":1,"869":1,"871":1,"889":1,"915":1,"921":1,"960":1,"988":3,"1004":2,"1018":2,"1026":1,"1034":2,"1051":1,"1062":1,"1108":1,"1110":1,"1117":1,"1185":1,"1195":1,"1212":2,"1222":1,"1229":3,"1233":1,"1237":4,"1247":2,"1249":1,"1251":1,"1259":8,"1270":13,"1271":2,"1274":2,"1277":1,"1278":1,"1280":3,"1282":1,"1285":34,"1299":8,"1300":2,"1308":5,"1310":13,"1314":1,"1315":1,"1322":15,"1323":12,"1325":1,"1331":1,"1332":1,"1334":2,"1338":18,"1342":2,"1344":1,"1348":1,"1349":50,"1358":26,"1368":5,"1379":1,"1385":1,"1394":14,"1395":33,"1401":12,"1408":1,"1414":6,"1415":2,"1416":1,"1424":1,"1435":24,"1437":9,"1440":1,"1441":2,"1442":3,"1446":1,"1454":8,"1464":2,"1466":3,"1475":2,"1477":1,"1485":1,"1488":1,"1491":1,"1495":69,"1496":4,"1497":2,"1499":1,"1504":1,"1518":1,"1533":1,"1571":1,"1590":1,"1596":1,"1610":2,"1630":8,"1631":2,"1632":1,"1633":11,"1640":4,"1650":1,"1651":1,"1652":1,"1655":1,"1660":1,"1665":1,"1679":1,"1683":3,"1684":1,"1685":2,"1692":1,"1696":1,"1700":1,"1702":1,"1726":1,"1727":1,"1728":1,"1730":1,"1741":1,"1746":1,"1748":2,"1754":1,"1763":1,"1766":1,"1768":13,"1775":2,"1825":1,"1839":1,"1850":1,"1855":1,"1857":1,"1875":1,"1896":1,"1914":1,"1931":1,"1932":1,"2000":1,"2012":1,"2030":1,"2062":1,"2080":2,"2084":1,"2091":1,"2095":1,"2096":1,"2130":1,"2131":1,"2155":1,"2157":1,"2202":2,"2231":1}}],["addchatclient",{"3":{"1420":1,"1426":2}}],["addcheckinsandattendeebadges",{"3":{"1209":2}}],["addchildentityhandlerbase",{"2":{"1260":1,"1265":1},"3":{"1199":7,"1203":1,"1207":1,"1260":1,"1265":1,"1270":2,"1358":8}}],["addcategoryitemasync",{"3":{"1394":1}}],["addcategoryitem",{"3":{"1203":3,"1207":6,"1270":1,"1349":4,"1358":13}}],["addcategoryitemhandlertests",{"3":{"1199":1,"1207":2}}],["addcategoryitemhandler",{"3":{"1199":2,"1203":1,"1207":2,"1270":5,"1349":1,"1358":8}}],["addcategoryitemcommandvalidatortests",{"3":{"1199":1,"1207":1}}],["addcategoryitemcommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1358":13}}],["addcategoryitemcommand",{"3":{"1199":7,"1203":1,"1207":2,"1358":13,"1379":4}}],["addcategoryitemrequest",{"3":{"1199":3,"1203":1,"1207":1,"1358":1,"1373":1,"1379":7}}],["addcaching",{"2":{"249":1,"250":1,"251":1,"252":1,"253":1,"260":1,"672":1,"677":1,"999":1,"1908":1},"3":{"0":3,"157":1,"243":1,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"256":1,"257":1,"259":1,"260":1,"672":1,"674":3,"677":1,"733":1,"996":1,"999":1,"1270":3,"1300":9,"1315":1,"1319":1,"1322":7,"1437":1,"1576":1,"1667":1,"1908":1,"1915":1}}],["addcors",{"3":{"1338":1}}],["addcompensatingindex",{"3":{"1285":1}}],["addcommentrequest",{"3":{"1650":1}}],["addcomment",{"3":{"1650":1,"1700":1}}],["addcommandrequestvalidator",{"2":{"926":1,"1263":1},"3":{"0":1,"926":1,"1263":1,"1270":1,"1271":2,"1322":4,"1358":1}}],["addcommonui",{"3":{"1576":1}}],["addcommonuifacades",{"2":{"647":1,"649":1,"652":1,"954":1,"955":1,"957":1},"3":{"0":2,"647":1,"649":1,"652":1,"954":1,"955":1,"957":1,"1323":10,"1435":3}}],["addcommonmauipubliclinkbuilder",{"3":{"1323":2,"1394":1,"1415":4,"1416":3}}],["addcommonmauitokenstorage",{"2":{"412":1,"507":1,"511":1,"513":1},"3":{"0":1,"412":1,"507":2,"511":1,"513":1,"1415":3,"1416":5}}],["addcommonwebformfactor",{"3":{"1323":3,"1415":1,"1416":2}}],["addcommonresponsecompression",{"3":{"1310":3}}],["addcommonratelimiting",{"2":{"175":1,"826":1,"1598":1,"2001":1,"2023":1},"3":{"175":3,"826":1,"1310":10,"1338":1,"1437":1,"1534":1,"1598":1,"1665":1,"1999":1,"2001":1,"2023":1}}],["addcommongatewaycors",{"2":{"774":1,"1335":1,"1669":1},"3":{"774":2,"1335":1,"1338":3,"1442":1,"1669":1}}],["addcommonkeyvaultconfiguration",{"2":{"599":1,"603":2,"665":1,"1441":1,"1449":1,"1574":1,"1585":1,"1669":1},"3":{"599":1,"603":2,"665":1,"1334":1,"1338":3,"1426":2,"1441":1,"1449":1,"1466":1,"1495":1,"1574":1,"1576":1,"1585":1,"1669":1}}],["addcommonopenapihostregistrationtests",{"2":{"454":1},"3":{"454":1}}],["addcommonopenapi",{"2":{"446":1,"453":1,"454":2,"1928":1},"3":{"446":2,"448":2,"453":1,"454":3,"1050":2,"1310":8,"1576":3,"1665":1,"1928":2}}],["addcommonexceptionhandlers",{"2":{"109":1,"112":1},"3":{"109":1,"112":1,"1310":9}}],["addcommonauthentication",{"2":{"31":1,"350":1,"1612":1,"1618":1,"1974":1},"3":{"31":2,"350":1,"674":1,"1299":1,"1310":8,"1319":1,"1487":1,"1525":1,"1612":1,"1618":1,"1974":1}}],["addcommonapiversioning",{"2":{"446":1,"448":1,"449":1,"450":1,"453":1,"1928":1,"2130":1,"2131":1,"2132":1},"3":{"0":1,"446":1,"448":3,"449":1,"450":1,"453":1,"1212":1,"1310":8,"1379":1,"1665":1,"1928":1,"2130":2,"2131":1,"2132":1}}],["addcommonsameoriginapiproxy",{"2":{"1182":1,"1184":1,"1186":1},"3":{"0":1,"1182":1,"1184":1,"1186":1}}],["addcommonservertokenstorage",{"2":{"507":1},"3":{"507":1,"1323":3,"1415":1,"1416":1}}],["addcommonserilog",{"2":{"395":1,"401":1},"3":{"0":1,"395":4,"401":2,"1332":2,"1338":3,"1441":1,"1669":1}}],["addcommonsecurityheaders",{"2":{"214":1,"216":1,"220":1,"1335":1,"2010":1,"2013":1,"2148":1,"2149":1,"2150":1,"2151":1},"3":{"0":1,"214":4,"216":2,"220":2,"1323":3,"1335":2,"1338":10,"1415":1,"1435":1,"1442":1,"1576":1,"2010":1,"2013":1,"2148":1,"2149":2,"2150":2,"2151":2}}],["addcommoncors",{"2":{"775":1,"776":1,"777":1,"1335":1},"3":{"0":1,"774":2,"775":1,"776":1,"777":1,"1212":1,"1310":2,"1335":1,"1338":1}}],["addcommondataprotection",{"2":{"665":1,"667":1,"668":1,"1334":1,"1335":1,"1441":1,"1449":1,"1669":1,"1969":1},"3":{"0":1,"665":3,"667":1,"668":1,"1212":1,"1334":1,"1335":1,"1338":3,"1441":1,"1449":1,"1466":1,"1495":1,"1669":1,"1969":2}}],["addcommonhybridcachetests",{"3":{"1199":1,"1207":2}}],["addcommonhybridcache",{"2":{"241":1,"245":1,"253":1,"259":1,"260":1,"261":1,"731":1,"735":1,"736":1,"1915":1},"3":{"0":2,"241":1,"243":1,"245":1,"253":1,"259":1,"260":3,"261":1,"731":1,"733":2,"735":1,"736":1,"1212":1,"1300":5,"1315":1,"1322":5,"1441":1,"1915":2}}],["addcommonhybridcachewhenredisconfigured",{"2":{"0":1,"241":1,"243":1,"245":1,"261":1,"279":1,"281":1,"283":1,"285":1,"733":1,"736":1},"3":{"0":1,"241":1,"243":1,"245":1,"261":1,"279":1,"283":1,"285":1,"733":1,"736":1}}],["addcommonblazorcsp",{"2":{"214":1,"216":1,"219":1,"220":1,"1335":1,"1524":1,"2149":1,"2150":1},"3":{"0":1,"214":1,"216":2,"219":1,"220":2,"1323":7,"1335":1,"1338":2,"1415":1,"1524":1,"1525":1,"2149":2,"2150":1}}],["addcontainer",{"3":{"1338":2}}],["addcontrollers",{"3":{"1308":3,"1310":1}}],["addcontentpolicyguardrail",{"2":{"1089":1,"1091":1,"1094":1,"1423":1},"3":{"1089":1,"1091":1,"1094":1,"1368":1,"1423":1,"1426":5,"1525":1,"1576":1}}],["addconfigfilter",{"3":{"1338":4}}],["addconfigurationassembly",{"3":{"1285":1,"1358":1,"1395":1,"1414":1}}],["addconferenceui",{"3":{"1394":4}}],["addconferenceeventlivevalidationclient",{"2":{"1377":1},"3":{"1349":2,"1377":1,"1379":2}}],["addconferencemodule",{"2":{"1313":1,"1879":1},"3":{"1313":1,"1376":1,"1379":5,"1879":1}}],["addconferencesessionvalidationclient",{"2":{"1311":1,"1377":1},"3":{"1311":2,"1349":1,"1377":1,"1379":2}}],["addconferenceaiguardrails",{"2":{"1018":1,"1021":1,"1094":1,"1095":1,"1367":1,"1434":1},"3":{"1018":3,"1021":1,"1094":3,"1095":1,"1367":1,"1368":5,"1426":2,"1434":1,"1525":2}}],["addconsumer",{"3":{"1259":2}}],["addclientauthsessioncookiesync",{"2":{"507":1},"3":{"507":1,"1323":4}}],["addpolicy",{"3":{"1310":1,"1338":3}}],["addpooleddbcontextfactory",{"3":{"1285":1}}],["addpoints",{"3":{"1209":2}}],["addpermissiongrants",{"3":{"1209":2}}],["addpermissions",{"2":{"188":1,"190":1,"349":1,"710":1,"1376":1,"1962":1,"1963":1,"1964":1},"3":{"0":1,"186":4,"188":1,"190":1,"349":1,"707":1,"710":1,"1059":1,"1270":1,"1296":1,"1299":8,"1303":1,"1308":3,"1310":1,"1322":1,"1376":1,"1379":2,"1395":4,"1414":4,"1495":1,"1525":1,"1652":2,"1666":1,"1961":1,"1962":2,"1963":1,"1964":1}}],["addpartners",{"3":{"1209":2}}],["addpiiredactionguardrail",{"2":{"1091":1,"1094":1,"1420":1,"1423":1},"3":{"1091":1,"1094":1,"1368":1,"1420":1,"1423":1,"1426":4,"1525":1}}],["addproductimage",{"3":{"1768":1}}],["addproductvariantdiscounts",{"3":{"1026":1}}],["addproject",{"3":{"1488":1,"1726":1}}],["addprocessor",{"3":{"1332":1,"1338":4}}],["addproblemdetails",{"3":{"109":1,"1310":1}}],["addpushnotificationdedupkey",{"3":{"1209":2}}],["addpushnotificationscopekey",{"3":{"1209":2}}],["addpushnotifications",{"2":{"227":1,"231":1,"380":1,"1934":1,"1943":1,"1948":1},"3":{"0":2,"225":3,"227":1,"231":1,"380":1,"674":1,"1306":1,"1308":11,"1315":1,"1319":2,"1322":4,"1466":1,"1934":2,"1943":2,"1948":1}}],["addpublicendpointpolicy",{"2":{"243":1,"259":1,"390":1,"1667":1,"1921":1},"3":{"0":2,"243":3,"259":1,"388":2,"390":1,"1300":1,"1310":2,"1378":1,"1667":1,"1921":2}}],["addazurekeyvault",{"3":{"1338":2}}],["addazureblobfilestorage",{"2":{"440":1,"1116":1,"1669":1,"2125":1,"2126":1,"2127":1},"3":{"0":1,"440":1,"1116":1,"1285":1,"1315":1,"1322":9,"1669":1,"2125":1,"2126":1,"2127":1}}],["addable",{"3":{"1308":1,"1395":1}}],["addactivity",{"3":{"1209":2}}],["addanthropicaiprovider",{"2":{"1094":1,"1095":1,"1419":1},"3":{"1018":1,"1091":1,"1094":1,"1095":1,"1419":1,"1426":6}}],["addadditionalassemblies",{"2":{"558":1,"651":1},"3":{"558":1,"651":1,"1415":1,"1435":1}}],["addassociationrequest",{"3":{"1379":1}}],["addaspnetcoreinstrumentation",{"2":{"402":1,"409":1},"3":{"402":1,"409":1}}],["addasync",{"2":{"883":1,"1385":1},"3":{"0":1,"881":2,"883":1,"1270":1,"1285":7,"1299":2,"1323":5,"1358":7,"1381":2,"1385":1,"1394":24,"1395":2,"1401":2,"1435":4,"2073":1}}],["addattributes",{"2":{"1052":1},"3":{"0":1,"1050":1,"1052":1,"1299":2}}],["addappleprovider",{"3":{"1310":1}}],["addapple",{"3":{"1310":1}}],["addapplicationpart",{"3":{"1308":1}}],["addapplicationprofiling",{"2":{"117":1,"1262":1,"1661":1,"1820":1},"3":{"117":1,"1262":1,"1270":8,"1315":1,"1322":4,"1437":1,"1661":1,"1820":1}}],["addapplicationdecorators",{"2":{"117":1,"119":1,"121":1,"122":1,"127":1,"587":1,"632":1,"922":1,"979":1,"1262":1,"1506":1,"1650":1,"1652":1,"1699":1,"1823":1,"1826":1,"1882":1,"2094":1,"2097":1,"2135":1},"3":{"0":3,"117":4,"119":1,"121":2,"122":2,"127":1,"572":2,"587":1,"632":1,"922":1,"979":1,"1212":1,"1262":3,"1263":1,"1270":15,"1299":2,"1308":1,"1310":6,"1315":5,"1322":17,"1358":2,"1414":1,"1435":6,"1437":1,"1506":1,"1650":1,"1652":1,"1699":1,"1823":4,"1826":1,"1882":2,"2094":2,"2097":2,"2135":1}}],["addapplication",{"2":{"127":1,"979":1,"980":1,"982":1,"1318":1,"1880":1},"3":{"0":1,"117":1,"121":1,"122":1,"127":1,"572":1,"979":3,"980":4,"982":1,"1214":1,"1247":1,"1259":1,"1262":2,"1270":2,"1271":1,"1299":1,"1310":3,"1314":1,"1315":2,"1318":1,"1322":8,"1414":1,"1435":3,"1437":1,"1880":1,"1882":1,"2097":1}}],["addapiparameterdescriptorbackfill",{"2":{"2130":1},"3":{"1310":2,"2130":1}}],["addapiversioning",{"2":{"454":1},"3":{"454":1,"1310":2}}],["addapiexplorer",{"3":{"448":1}}],["addapi",{"2":{"306":1,"656":1,"965":1,"966":1,"1823":1,"2094":1},"3":{"0":3,"117":2,"265":1,"300":1,"303":2,"306":1,"318":1,"326":1,"583":1,"656":1,"657":1,"740":1,"744":1,"964":4,"965":1,"966":1,"979":2,"1050":2,"1237":5,"1299":1,"1310":24,"1322":2,"1395":1,"1435":1,"1823":1,"1993":3,"2094":1,"2097":1,"2135":1,"2137":1}}],["addautomapper",{"2":{"1957":1},"3":{"1957":1}}],["addauthentication",{"2":{"1974":1},"3":{"350":1,"827":1,"1299":2,"1310":1,"1435":1,"1974":1}}],["addauthorizationcore",{"2":{"960":1},"3":{"954":2,"960":2,"1415":1,"1435":3,"1487":1,"1490":1}}],["addauthorization",{"2":{"59":1,"833":1,"1446":1},"3":{"0":1,"59":1,"827":2,"833":1,"1299":1,"1435":2,"1446":1}}],["addauthorizationpolicies",{"2":{"186":1,"189":1,"1299":1,"1697":1,"1962":1},"3":{"0":1,"186":1,"189":2,"1270":1,"1296":3,"1299":15,"1310":2,"1322":2,"1697":2,"1962":1}}],["addaudittrailandscheduler",{"3":{"1209":6}}],["addaudittrailtests",{"3":{"1199":1,"1207":2,"1285":2}}],["addaudittrail",{"2":{"713":1,"717":1,"979":1,"1232":1,"1277":1,"1838":1},"3":{"0":1,"674":1,"713":1,"715":3,"717":1,"720":3,"979":1,"1212":2,"1232":1,"1237":2,"1269":1,"1270":3,"1274":1,"1277":1,"1285":10,"1315":1,"1316":1,"1322":4,"1441":1,"1663":1,"1838":1}}],["addshoppingcartitem",{"3":{"1764":1,"1768":1}}],["addsomething",{"3":{"1379":1}}],["addsource",{"3":{"1338":3}}],["addsqlite",{"3":{"1338":1}}],["addsqlserver",{"3":{"1338":1,"1466":1}}],["addsupportedrange",{"3":{"1309":2}}],["addsupported",{"3":{"1309":2}}],["addschematransformer",{"3":{"1310":1}}],["addscheme",{"3":{"1299":1,"1435":1}}],["addscheduledjob",{"2":{"710":1},"3":{"707":1,"710":1,"846":1,"1270":1,"1285":1,"1316":1,"1322":3}}],["addscheduledjobstests",{"3":{"1199":1,"1207":4,"1322":2,"1437":2}}],["addscheduledjobs",{"2":{"705":1,"708":1,"720":1,"979":1,"1947":1},"3":{"0":1,"674":1,"705":1,"707":2,"708":1,"715":1,"720":1,"979":1,"1270":3,"1285":5,"1315":1,"1316":1,"1322":8,"1441":2,"1445":1,"1495":1,"1947":1}}],["addscoped",{"3":{"1285":1,"1303":1,"1308":1,"1310":1,"1322":2,"1323":9,"1358":2,"1394":6,"1395":5,"1414":3,"1416":4,"1652":1}}],["addspeakerquestionanswer",{"2":{"1638":1},"3":{"1349":2,"1495":1,"1638":1}}],["addspeakercategoryitem",{"3":{"1203":3,"1207":8,"1270":1,"1349":2,"1358":8}}],["addspeakercategoryitemhandlertests",{"3":{"1199":1,"1207":2,"1358":2}}],["addspeakercategoryitemhandler",{"3":{"1199":2,"1203":1,"1207":2,"1270":2,"1358":19}}],["addspeakercategoryitemcommandvalidatortests",{"3":{"1199":1,"1207":1,"1358":1}}],["addspeakercategoryitemcommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1358":6}}],["addspeakercategoryitemcommand",{"3":{"1199":7,"1203":1,"1207":2,"1358":11,"1379":4}}],["addspeakercategoryitemrequest",{"2":{"1373":1},"3":{"1199":3,"1203":1,"1207":1,"1358":1,"1373":1,"1379":3}}],["addsponsorvisitcheckins",{"3":{"1209":2}}],["addsponsors",{"3":{"1209":2,"1368":6,"1495":1}}],["addsessionscoreresponseguardrail",{"2":{"1367":1,"1368":1,"1525":1},"3":{"1367":1,"1368":3,"1525":1}}],["addsessionspeakerasync",{"3":{"1394":1}}],["addsessionspeaker",{"3":{"1203":3,"1207":8,"1270":1,"1349":6,"1358":10,"1368":1}}],["addsessionspeakerhandlertests",{"3":{"1199":1,"1207":2,"1358":2}}],["addsessionspeakerhandler",{"3":{"1199":2,"1203":1,"1207":2,"1270":2,"1349":1,"1358":10}}],["addsessionspeakercommandvalidatortests",{"3":{"1199":1,"1207":1,"1358":1}}],["addsessionspeakercommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1358":5}}],["addsessionspeakercommand",{"3":{"1199":7,"1203":1,"1207":2,"1358":9,"1379":4}}],["addsessionspeakerrequest",{"3":{"1199":3,"1203":1,"1207":1,"1358":1,"1373":1,"1379":3}}],["addsessioncookieauthentication",{"2":{"1524":1},"3":{"1299":2,"1524":1,"1525":1}}],["addsessioncategoryitemasync",{"3":{"1394":1}}],["addsessioncategoryitem",{"3":{"1203":3,"1207":8,"1349":3,"1358":15}}],["addsessioncategoryitemhandlertests",{"3":{"1199":1,"1207":2,"1358":2}}],["addsessioncategoryitemhandler",{"3":{"1199":2,"1203":1,"1207":2,"1349":1,"1358":13}}],["addsessioncategoryitemcommandvalidatortests",{"3":{"1199":1,"1207":1,"1358":1}}],["addsessioncategoryitemcommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1358":7}}],["addsessioncategoryitemcommand",{"3":{"1199":7,"1203":1,"1207":2,"1358":13,"1379":4}}],["addsessioncategoryitemrequest",{"2":{"1373":1},"3":{"1199":3,"1203":1,"1207":1,"1358":1,"1373":1,"1379":3}}],["addsessionquestions",{"3":{"1209":2}}],["addsessionquestionanswer",{"3":{"1203":3,"1207":8,"1348":1,"1349":4,"1358":15}}],["addsessionquestionanswercommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1358":6}}],["addsessionquestionanswercommandvalidatortests",{"2":{"1199":1},"3":{"1199":1,"1207":1,"1358":1}}],["addsessionquestionanswercommand",{"3":{"1199":7,"1203":1,"1207":2,"1358":10,"1379":3}}],["addsessionquestionanswerrequest",{"2":{"1373":1},"3":{"1199":3,"1203":1,"1207":1,"1358":1,"1373":1,"1379":3}}],["addsessionquestionanswerhandlertests",{"3":{"1199":1,"1207":2,"1358":2}}],["addsessionquestionanswerhandler",{"3":{"690":1,"1199":2,"1203":1,"1207":2,"1348":1,"1349":3,"1358":14}}],["addsessiondurationcomputedcolumn",{"3":{"1209":2}}],["addsessionasset",{"3":{"1209":2}}],["addsessionassetlinkhandlertests",{"3":{"1199":1,"1207":2}}],["addsessionassetlinkhandler",{"3":{"1199":2,"1203":1,"1207":2,"1349":2,"1356":2,"1358":9,"1495":1}}],["addsessionassetlinkcommand",{"3":{"1199":5,"1203":1,"1207":2,"1358":7,"1379":1}}],["addsessionaiscorepromptversion",{"3":{"1018":1,"1209":2,"1349":1,"1368":1}}],["addserverauthsessioncookie",{"3":{"1299":1,"1310":4,"1323":1}}],["addservicediscovery",{"2":{"1328":1},"3":{"1328":1,"1336":1,"1338":2,"1442":1}}],["addservicediscoverydestinationresolver",{"2":{"827":1,"837":1,"1446":1},"3":{"827":1,"837":1,"1337":1,"1338":1,"1446":2}}],["addservicedefaults",{"0":{"1328":1,"2005":1},"2":{"61":1,"72":1,"235":1,"236":1,"401":1,"404":1,"406":1,"407":1,"408":1,"663":1,"668":1,"833":1,"883":1,"913":1,"917":1,"918":1,"938":1,"1324":1,"1328":1,"1333":1,"1334":1,"1676":1,"1719":1,"1723":1,"1726":1,"2005":1,"2009":1,"2010":1,"2013":1,"2029":1,"2030":1,"2037":1,"2156":1,"2159":1},"3":{"0":5,"61":1,"72":1,"235":1,"236":1,"397":1,"401":6,"404":1,"406":1,"407":1,"408":1,"663":1,"665":2,"668":1,"833":1,"883":1,"913":1,"917":1,"918":1,"938":1,"1212":1,"1323":1,"1324":1,"1328":2,"1332":2,"1333":2,"1334":2,"1336":1,"1337":1,"1338":19,"1379":1,"1395":1,"1441":4,"1442":3,"1486":1,"1495":1,"1576":2,"1652":2,"1669":1,"1676":1,"1706":1,"1719":1,"1723":1,"1726":1,"2005":2,"2009":1,"2010":1,"2013":1,"2029":2,"2030":1,"2037":1,"2155":1,"2156":1,"2159":1}}],["addservicebusemulatorbroker",{"2":{"638":1,"640":1,"644":1,"1325":1,"1326":1,"1443":1,"1669":1,"2007":1,"2012":1},"3":{"638":1,"640":1,"644":1,"1325":1,"1326":1,"1338":6,"1440":1,"1443":1,"1525":1,"1669":1,"2007":1,"2012":1}}],["addservices",{"2":{"949":1,"1937":1},"3":{"230":1,"310":1,"946":1,"949":2,"1259":3,"1270":1,"1299":1,"1315":2,"1316":1,"1317":1,"1319":1,"1322":4,"1934":1,"1937":1}}],["addserilog",{"2":{"401":1,"1332":1,"1441":1},"3":{"0":1,"401":1,"1332":2,"1338":4,"1441":1}}],["addsignalr",{"2":{"231":1},"3":{"225":1,"231":1,"1934":1}}],["addsingleton",{"2":{"216":1},"3":{"216":1,"220":2,"300":1,"649":1,"682":1,"733":2,"832":1,"1300":1,"1310":1,"1322":1,"1323":9,"1338":3,"1395":1,"1414":3,"1416":4,"1426":1,"1435":3,"2149":1}}],["addstandardresiliencehandler",{"2":{"1706":1,"2018":1,"2029":1},"3":{"1311":2,"1338":4,"1706":1,"2018":1,"2029":1}}],["addstackexchangeredisoutputcache",{"2":{"243":1,"250":1,"251":1,"252":1,"254":1,"256":1,"257":1,"259":1,"260":1,"390":1},"3":{"243":1,"250":1,"251":1,"252":1,"254":1,"256":2,"257":1,"259":2,"260":1,"390":1,"1338":2}}],["addstackexchangeredis",{"3":{"225":1}}],["addstoredpermissiongrants",{"2":{"674":1,"1061":1,"1282":1,"1408":1,"2198":1,"2200":1,"2201":1},"3":{"674":1,"1059":3,"1061":1,"1273":1,"1282":1,"1285":8,"1296":1,"1299":2,"1315":1,"1322":1,"1408":1,"2198":1,"2200":1,"2201":1}}],["addstronglytypedids",{"2":{"798":1,"804":1,"806":1,"1051":1,"1052":1,"1809":1},"3":{"0":2,"798":1,"804":1,"806":1,"1050":2,"1051":1,"1052":1,"1247":1,"1280":1,"1285":3,"1310":1,"1315":1,"1322":1,"1809":2}}],["addsalesuserexportclient",{"2":{"96":1,"104":1},"3":{"96":1,"104":1}}],["adds",{"0":{"92":1,"1361":1},"2":{"450":1},"3":{"0":10,"15":1,"55":1,"87":1,"93":1,"94":1,"121":1,"130":1,"150":1,"157":1,"214":1,"219":1,"225":1,"233":1,"235":2,"246":1,"255":1,"264":1,"265":1,"275":1,"326":1,"330":1,"350":1,"352":1,"355":1,"370":1,"379":1,"390":1,"393":1,"395":1,"397":1,"400":1,"401":1,"402":2,"411":1,"450":1,"508":1,"524":1,"530":1,"540":1,"564":1,"565":1,"566":1,"572":1,"583":2,"586":1,"599":1,"616":1,"642":1,"649":1,"651":1,"665":1,"674":3,"691":1,"700":1,"707":1,"715":2,"718":1,"719":1,"740":1,"755":1,"756":1,"757":1,"759":1,"774":1,"800":1,"810":2,"819":1,"820":1,"827":5,"828":1,"834":1,"846":1,"853":1,"854":1,"861":1,"869":1,"881":2,"916":1,"922":1,"923":1,"926":2,"931":1,"933":1,"937":1,"954":4,"957":1,"964":1,"1018":1,"1035":1,"1038":1,"1042":1,"1043":1,"1050":1,"1052":1,"1059":1,"1065":1,"1083":4,"1089":1,"1093":1,"1129":1,"1135":1,"1152":1,"1168":2,"1175":1,"1184":2,"1212":2,"1214":1,"1219":1,"1228":1,"1229":1,"1230":1,"1231":3,"1237":10,"1244":1,"1247":1,"1249":3,"1252":1,"1258":1,"1259":7,"1262":1,"1270":9,"1271":4,"1273":2,"1276":1,"1277":1,"1278":2,"1280":2,"1285":27,"1287":1,"1291":1,"1299":15,"1300":3,"1307":1,"1308":4,"1310":27,"1311":4,"1314":1,"1315":2,"1316":1,"1317":1,"1319":4,"1321":2,"1322":17,"1323":18,"1324":1,"1325":1,"1328":1,"1331":2,"1332":6,"1335":1,"1337":2,"1338":29,"1349":9,"1351":2,"1354":1,"1358":55,"1361":1,"1368":6,"1374":3,"1377":1,"1378":2,"1379":8,"1381":2,"1393":1,"1394":38,"1395":23,"1401":4,"1405":1,"1413":1,"1414":6,"1415":3,"1416":3,"1420":1,"1426":6,"1428":1,"1429":1,"1431":1,"1435":38,"1437":3,"1440":5,"1441":3,"1442":2,"1443":2,"1446":2,"1447":1,"1454":2,"1458":1,"1462":2,"1466":2,"1480":1,"1485":1,"1487":3,"1488":4,"1490":1,"1495":4,"1525":1,"1535":1,"1574":1,"1575":1,"1576":1,"1590":2,"1624":1,"1630":1,"1631":1,"1639":1,"1640":1,"1649":1,"1650":2,"1662":1,"1665":2,"1668":2,"1669":1,"1713":1,"1721":1,"1748":1,"1749":2,"1764":1,"1775":2,"1808":1,"1809":3,"1828":1,"1838":2,"1841":1,"1842":1,"1850":1,"1877":1,"1879":1,"1881":1,"1919":1,"1921":1,"1934":1,"1935":1,"1941":1,"1970":1,"1975":1,"1977":2,"1987":1,"2000":1,"2001":1,"2005":1,"2009":1,"2011":1,"2023":2,"2031":1,"2038":1,"2052":1,"2065":1,"2066":1,"2084":1,"2089":2,"2098":1,"2102":1,"2120":1,"2125":2,"2148":1,"2155":3,"2159":1,"2174":1,"2176":1,"2231":1,"2239":1}}],["add",{"0":{"1683":1,"1700":1,"1728":1,"2092":1,"2105":1},"2":{"527":1,"659":1,"721":1,"938":1,"1044":1,"1584":1,"1651":1,"1687":1,"1792":1,"1826":1,"1845":1,"1852":1,"1861":1,"1867":1,"1885":1,"1891":1,"1898":1,"1904":1,"1911":1,"1923":1,"1929":1,"1940":1,"1949":1,"1958":1,"1971":1,"1978":1,"1984":1,"2001":1,"2037":1,"2059":1,"2107":1,"2114":1,"2121":1,"2122":1,"2127":1,"2132":1,"2138":1,"2145":1,"2152":1,"2168":1,"2191":1,"2202":1,"2210":1},"3":{"0":8,"1":1,"5":1,"69":1,"100":1,"195":1,"207":1,"230":1,"235":1,"236":1,"264":1,"265":2,"312":1,"333":1,"341":1,"350":1,"360":1,"365":1,"372":1,"397":1,"404":1,"413":2,"415":1,"459":1,"472":1,"494":1,"495":1,"517":1,"527":1,"528":1,"537":1,"627":1,"631":1,"633":3,"635":3,"636":1,"649":1,"650":1,"657":1,"659":1,"665":1,"674":1,"675":1,"691":1,"698":1,"707":1,"715":2,"721":1,"733":1,"734":1,"759":1,"809":1,"811":2,"820":1,"821":1,"827":1,"829":1,"834":1,"881":1,"882":1,"883":1,"905":1,"922":2,"938":1,"939":1,"964":1,"979":3,"981":2,"984":1,"997":1,"1004":1,"1018":1,"1026":2,"1027":2,"1030":1,"1035":1,"1044":1,"1052":1,"1058":1,"1059":1,"1060":1,"1061":1,"1062":1,"1068":1,"1085":2,"1089":1,"1093":1,"1157":1,"1176":1,"1177":1,"1179":3,"1186":1,"1193":1,"1196":1,"1212":3,"1214":1,"1229":2,"1234":3,"1237":10,"1238":1,"1241":1,"1243":1,"1247":4,"1251":1,"1259":6,"1265":1,"1270":12,"1283":1,"1285":19,"1286":2,"1289":2,"1290":2,"1296":1,"1299":6,"1300":2,"1308":4,"1309":2,"1310":10,"1311":2,"1315":1,"1319":1,"1322":9,"1323":25,"1332":1,"1338":9,"1342":1,"1343":1,"1349":12,"1350":1,"1356":1,"1358":85,"1365":1,"1368":6,"1370":2,"1373":1,"1374":1,"1379":27,"1381":2,"1384":1,"1385":1,"1388":2,"1390":1,"1394":44,"1395":10,"1398":3,"1400":1,"1401":9,"1414":11,"1415":9,"1416":17,"1417":1,"1426":3,"1428":1,"1432":1,"1435":13,"1437":4,"1444":1,"1452":1,"1454":1,"1466":3,"1471":1,"1476":2,"1486":1,"1489":2,"1491":1,"1495":4,"1496":1,"1523":1,"1525":1,"1529":2,"1530":1,"1575":1,"1580":1,"1581":1,"1584":2,"1588":1,"1594":3,"1595":2,"1597":1,"1598":1,"1599":1,"1615":1,"1616":1,"1618":1,"1629":1,"1630":2,"1631":11,"1637":1,"1638":1,"1640":1,"1646":2,"1647":1,"1648":1,"1649":2,"1650":6,"1651":2,"1652":1,"1653":4,"1655":1,"1656":1,"1660":1,"1662":1,"1665":1,"1667":1,"1670":1,"1673":1,"1677":1,"1678":1,"1680":1,"1681":1,"1682":2,"1683":1,"1684":1,"1685":3,"1686":2,"1687":1,"1688":2,"1689":1,"1690":1,"1691":1,"1696":2,"1700":2,"1701":2,"1702":1,"1703":1,"1718":3,"1719":1,"1721":1,"1723":1,"1725":1,"1726":5,"1727":4,"1728":2,"1729":2,"1730":2,"1736":1,"1741":1,"1745":2,"1747":1,"1748":1,"1749":4,"1753":1,"1754":3,"1757":1,"1760":1,"1763":1,"1764":4,"1766":1,"1767":4,"1768":1,"1772":2,"1774":2,"1775":3,"1785":1,"1792":1,"1804":2,"1806":1,"1811":2,"1814":1,"1816":1,"1822":1,"1823":1,"1826":1,"1833":1,"1843":1,"1845":1,"1848":1,"1852":1,"1861":1,"1864":1,"1867":1,"1876":1,"1882":1,"1884":1,"1885":1,"1889":1,"1891":3,"1898":2,"1904":1,"1906":1,"1911":1,"1923":1,"1924":1,"1929":2,"1940":1,"1945":1,"1949":1,"1958":1,"1959":2,"1962":1,"1964":1,"1969":1,"1971":1,"1972":4,"1978":1,"1984":1,"1990":1,"1996":1,"1998":1,"2001":1,"2013":1,"2022":1,"2023":1,"2027":1,"2028":2,"2029":1,"2031":1,"2037":1,"2040":2,"2047":2,"2048":2,"2059":1,"2068":1,"2074":1,"2076":1,"2079":1,"2080":1,"2085":1,"2086":1,"2090":1,"2096":5,"2097":1,"2098":2,"2101":1,"2103":1,"2104":1,"2105":1,"2107":2,"2114":1,"2118":1,"2121":1,"2122":2,"2127":1,"2128":1,"2132":1,"2137":1,"2138":1,"2141":1,"2145":2,"2146":1,"2152":1,"2155":1,"2159":4,"2168":2,"2169":1,"2179":1,"2191":1,"2192":1,"2193":2,"2201":1,"2202":1,"2210":1,"2233":1}}],["adr",{"0":{"1":1,"1474":1,"1506":1,"1507":1,"1508":1,"1509":1,"1511":1,"2024":1},"1":{"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":1,"9":1,"10":1,"11":1,"12":1,"13":1,"14":1,"15":1,"16":1,"17":1,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1,"24":1,"25":1,"26":1,"27":1,"28":1,"29":1,"30":1,"31":1,"32":1,"33":1,"34":1,"35":1,"36":1,"37":1,"38":1,"39":1,"40":1,"41":1,"42":1,"43":1,"44":1,"45":1,"46":1,"47":1,"48":1,"49":1,"50":1,"51":1,"52":1,"53":1,"54":1,"55":1,"56":1,"57":1,"58":1,"59":1,"60":1,"61":1,"62":1,"63":1,"64":1,"65":1,"66":1,"67":1,"68":1,"69":1,"70":1,"71":1,"72":1,"73":1,"74":1,"75":1,"76":1,"77":1,"78":1,"79":1,"80":1,"81":1,"82":1,"83":1,"84":1,"85":1,"86":1,"87":1,"88":1,"89":1,"90":1,"91":1,"92":1,"93":1,"94":1,"95":1,"96":1,"97":1,"98":1,"99":1,"100":1,"101":1,"102":1,"103":1,"104":1,"105":1,"106":1,"107":1,"108":1,"109":1,"110":1,"111":1,"112":1,"113":1,"114":1,"115":1,"116":1,"117":1,"118":1,"119":1,"120":1,"121":1,"122":1,"123":1,"124":1,"125":1,"126":1,"127":1,"128":1,"129":1,"130":1,"131":1,"132":1,"133":1,"134":1,"135":1,"136":1,"137":1,"138":1,"139":1,"140":1,"141":1,"142":1,"143":1,"144":1,"145":1,"146":1,"147":1,"148":1,"149":1,"150":1,"151":1,"152":1,"153":1,"154":1,"155":1,"156":1,"157":1,"158":1,"159":1,"160":1,"161":1,"162":1,"163":1,"164":1,"165":1,"166":1,"167":1,"168":1,"169":1,"170":1,"171":1,"172":1,"173":1,"174":1,"175":1,"176":1,"177":1,"178":1,"179":1,"180":1,"181":1,"182":1,"183":1,"184":1,"185":1,"186":1,"187":1,"188":1,"189":1,"190":1,"191":1,"192":1,"193":1,"194":1,"195":1,"196":1,"197":1,"198":1,"199":1,"200":1,"201":1,"202":1,"203":1,"204":1,"205":1,"206":1,"207":1,"208":1,"209":1,"210":1,"211":1,"212":1,"213":1,"214":1,"215":1,"216":1,"217":1,"218":1,"219":1,"220":1,"221":1,"222":1,"223":1,"224":1,"225":1,"226":1,"227":1,"228":1,"229":1,"230":1,"231":1,"232":1,"233":1,"234":1,"235":1,"236":1,"237":1,"238":1,"239":1,"240":1,"241":1,"242":1,"243":1,"244":1,"245":1,"246":1,"247":1,"248":1,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"260":1,"261":1,"262":2,"263":2,"264":2,"265":2,"266":2,"267":2,"268":2,"269":2,"270":1,"271":1,"272":1,"273":1,"274":1,"275":1,"276":1,"277":1,"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"294":1,"295":1,"296":1,"297":1,"298":1,"299":1,"300":1,"301":1,"302":1,"303":1,"304":1,"305":1,"306":1,"307":1,"308":1,"309":1,"310":1,"311":1,"312":1,"313":1,"314":1,"315":1,"316":1,"317":1,"318":1,"319":1,"320":1,"321":1,"322":1,"323":1,"324":1,"325":1,"326":1,"327":1,"328":1,"329":1,"330":1,"331":1,"332":1,"333":1,"334":1,"335":1,"336":1,"337":1,"338":1,"339":1,"340":1,"341":1,"342":1,"343":1,"344":1,"345":1,"346":1,"347":1,"348":1,"349":1,"350":1,"351":1,"352":1,"353":1,"354":1,"355":1,"356":1,"357":1,"358":1,"359":1,"360":1,"361":1,"362":1,"363":1,"364":1,"365":1,"366":1,"367":1,"368":1,"369":1,"370":1,"371":1,"372":1,"373":1,"374":1,"375":1,"376":1,"377":1,"378":1,"379":1,"380":1,"381":1,"382":1,"383":1,"384":1,"385":1,"386":1,"387":1,"388":1,"389":1,"390":1,"391":1,"392":1,"393":1,"394":1,"395":1,"396":1,"397":1,"398":1,"399":1,"400":1,"401":1,"402":1,"403":1,"404":1,"405":1,"406":1,"407":1,"408":1,"409":1,"410":1,"411":1,"412":1,"413":1,"414":1,"415":1,"416":1,"417":1,"418":1,"419":1,"420":1,"421":1,"422":1,"423":1,"424":1,"425":1,"426":1,"427":1,"428":1,"429":1,"430":1,"431":1,"432":1,"433":1,"434":1,"435":1,"436":1,"437":1,"438":1,"439":1,"440":1,"441":1,"442":1,"443":1,"444":1,"445":1,"446":1,"447":1,"448":1,"449":1,"450":1,"451":1,"452":1,"453":1,"454":1,"455":1,"456":1,"457":1,"458":1,"459":1,"460":1,"461":1,"462":1,"463":1,"464":1,"465":1,"466":1,"467":1,"468":1,"469":1,"470":1,"471":1,"472":1,"473":1,"474":1,"475":1,"476":1,"477":1,"478":1,"479":1,"480":1,"481":1,"482":1,"483":1,"484":1,"485":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"495":1,"496":1,"497":1,"498":1,"499":1,"500":1,"501":1,"502":1,"503":1,"504":1,"505":1,"506":1,"507":1,"508":1,"509":1,"510":1,"511":1,"512":1,"513":1,"514":1,"515":1,"516":1,"517":1,"518":1,"519":1,"520":1,"521":1,"522":1,"523":1,"524":1,"525":1,"526":1,"527":1,"528":1,"529":1,"530":1,"531":1,"532":1,"533":1,"534":1,"535":1,"536":1,"537":1,"538":1,"539":1,"540":1,"541":1,"542":1,"543":1,"544":1,"545":1,"546":1,"547":1,"548":1,"549":1,"550":1,"551":1,"552":1,"553":1,"554":1,"555":1,"556":1,"557":1,"558":1,"559":1,"560":1,"561":1,"562":1,"563":1,"564":1,"565":1,"566":1,"567":1,"568":1,"569":1,"570":1,"571":1,"572":1,"573":1,"574":1,"575":1,"576":1,"577":1,"578":1,"579":1,"580":1,"581":1,"582":1,"583":1,"584":1,"585":1,"586":1,"587":1,"588":1,"589":1,"590":1,"591":1,"592":1,"593":1,"594":1,"595":1,"596":1,"597":1,"598":1,"599":1,"600":1,"601":1,"602":1,"603":1,"604":1,"605":1,"606":1,"607":1,"608":1,"609":1,"610":1,"611":1,"612":1,"613":1,"614":1,"615":1,"616":1,"617":1,"618":1,"619":1,"620":1,"621":1,"622":1,"623":1,"624":1,"625":1,"626":1,"627":1,"628":1,"629":1,"630":1,"631":1,"632":1,"633":1,"634":1,"635":1,"636":1,"637":1,"638":1,"639":1,"640":1,"641":1,"642":1,"643":1,"644":1,"645":1,"646":1,"647":1,"648":1,"649":1,"650":1,"651":1,"652":1,"653":1,"654":1,"655":1,"656":1,"657":1,"658":1,"659":1,"660":1,"661":1,"662":1,"663":1,"664":1,"665":1,"666":1,"667":1,"668":1,"669":1,"670":1,"671":1,"672":1,"673":1,"674":1,"675":1,"676":1,"677":1,"678":1,"679":1,"680":1,"681":1,"682":1,"683":1,"684":1,"685":1,"686":1,"687":1,"688":1,"689":1,"690":1,"691":1,"692":1,"693":1,"694":1,"695":1,"696":1,"697":1,"698":1,"699":1,"700":1,"701":1,"702":1,"703":1,"704":1,"705":1,"706":1,"707":1,"708":1,"709":1,"710":1,"711":1,"712":1,"713":1,"714":1,"715":1,"716":1,"717":1,"718":1,"719":1,"720":1,"721":1,"722":1,"723":1,"724":1,"725":1,"726":1,"727":1,"728":1,"729":1,"730":2,"731":2,"732":2,"733":2,"734":2,"735":2,"736":2,"737":2,"738":1,"739":1,"740":1,"741":1,"742":1,"743":1,"744":1,"745":1,"746":1,"747":1,"748":1,"749":1,"750":1,"751":1,"752":1,"753":1,"754":1,"755":1,"756":1,"757":1,"758":1,"759":1,"760":1,"761":1,"762":1,"763":1,"764":1,"765":1,"766":1,"767":1,"768":1,"769":1,"770":1,"771":1,"772":1,"773":1,"774":1,"775":1,"776":1,"777":1,"778":1,"779":1,"780":1,"781":1,"782":1,"783":1,"784":1,"785":1,"786":1,"787":1,"788":1,"789":1,"790":1,"791":1,"792":1,"793":1,"794":1,"795":1,"796":1,"797":1,"798":1,"799":1,"800":1,"801":1,"802":1,"803":1,"804":1,"805":1,"806":1,"807":2,"808":2,"809":2,"810":2,"811":2,"812":2,"813":2,"814":2,"815":2,"816":1,"817":1,"818":1,"819":1,"820":1,"821":1,"822":1,"823":1,"824":1,"825":1,"826":1,"827":1,"828":1,"829":1,"830":1,"831":1,"832":1,"833":1,"834":1,"835":1,"836":1,"837":1,"838":1,"839":1,"840":1,"841":1,"842":1,"843":1,"844":1,"845":1,"846":1,"847":1,"848":1,"849":1,"850":1,"851":1,"852":1,"853":1,"854":1,"855":1,"856":1,"857":1,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1,"866":1,"867":1,"868":1,"869":1,"870":1,"871":1,"872":1,"873":1,"874":1,"875":1,"876":1,"877":1,"878":1,"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"886":1,"887":1,"888":1,"889":1,"890":1,"891":1,"892":1,"893":1,"894":1,"895":1,"896":1,"897":1,"898":1,"899":1,"900":1,"901":1,"902":1,"903":1,"904":1,"905":1,"906":1,"907":1,"908":1,"909":1,"910":1,"911":1,"912":1,"913":1,"914":1,"915":1,"916":1,"917":1,"918":1,"919":1,"920":1,"921":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"928":1,"929":1,"930":1,"931":1,"932":1,"933":1,"934":1,"935":1,"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1,"943":1,"944":1,"945":1,"946":1,"947":1,"948":1,"949":1,"950":1,"951":1,"952":1,"953":1,"954":1,"955":1,"956":1,"957":1,"958":1,"959":1,"960":1,"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1,"969":1,"970":1,"971":1,"972":1,"973":1,"974":1,"975":1,"976":1,"977":1,"978":1,"979":1,"980":1,"981":1,"982":1,"983":1,"984":1,"985":1,"986":1,"987":1,"988":1,"989":1,"990":1,"991":1,"992":1,"993":1,"994":1,"995":1,"996":1,"997":1,"998":1,"999":1,"1000":1,"1001":1,"1002":1,"1003":1,"1004":1,"1005":1,"1006":1,"1007":1,"1008":1,"1009":1,"1010":1,"1011":1,"1012":1,"1013":1,"1014":1,"1015":1,"1016":1,"1017":1,"1018":1,"1019":1,"1020":1,"1021":1,"1022":1,"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1,"1031":1,"1032":1,"1033":1,"1034":1,"1035":1,"1036":1,"1037":1,"1038":1,"1039":1,"1040":1,"1041":1,"1042":1,"1043":1,"1044":1,"1045":1,"1046":1,"1047":1,"1048":1,"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1,"1056":1,"1057":1,"1058":1,"1059":1,"1060":1,"1061":1,"1062":1,"1063":1,"1064":1,"1065":1,"1066":1,"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"1072":1,"1073":1,"1074":1,"1075":1,"1076":1,"1077":1,"1078":1,"1079":1,"1080":1,"1081":1,"1082":1,"1083":1,"1084":1,"1085":1,"1086":1,"1087":1,"1088":1,"1089":1,"1090":1,"1091":1,"1092":1,"1093":1,"1094":1,"1095":1,"1096":1,"1097":1,"1098":1,"1099":1,"1100":1,"1101":1,"1102":1,"1103":1,"1104":1,"1105":1,"1106":1,"1107":1,"1108":1,"1109":1,"1110":1,"1111":1,"1112":1,"1113":1,"1114":1,"1115":1,"1116":1,"1117":1,"1118":1,"1119":1,"1120":1,"1121":1,"1122":1,"1123":1,"1124":1,"1125":1,"1126":1,"1127":1,"1128":1,"1129":1,"1130":1,"1131":1,"1132":1,"1133":1,"1134":1,"1135":1,"1136":1,"1137":1,"1138":1,"1139":1,"1140":1,"1141":1,"1142":1,"1143":1,"1144":1,"1145":1,"1146":1,"1147":1,"1148":1,"1149":1,"1150":1,"1151":1,"1152":1,"1153":1,"1154":1,"1155":1,"1156":1,"1157":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1,"1165":1,"1166":1,"1167":1,"1168":1,"1169":1,"1170":1,"1171":1,"1172":1,"1173":1,"1174":1,"1175":1,"1176":1,"1177":1,"1178":1,"1179":1,"1180":1,"1181":1,"1182":1,"1183":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1,"1985":1,"1986":1,"1987":1,"1988":1,"1989":1,"1990":1},"3":{"0":211,"1":5,"2":1,"8":1,"14":1,"15":2,"19":1,"20":3,"24":4,"26":4,"28":1,"30":1,"31":1,"33":2,"35":4,"36":1,"38":1,"39":2,"40":1,"41":2,"44":1,"45":2,"46":1,"47":3,"50":1,"52":1,"53":2,"55":1,"56":1,"57":2,"59":9,"61":2,"62":2,"64":4,"65":1,"66":2,"67":2,"68":1,"71":1,"72":2,"75":1,"76":1,"77":2,"79":1,"80":1,"81":1,"83":1,"84":2,"85":1,"87":1,"89":1,"92":1,"96":1,"97":2,"98":2,"105":4,"106":1,"109":5,"113":4,"114":1,"117":3,"120":1,"121":3,"124":7,"125":1,"126":1,"128":2,"129":1,"130":1,"131":5,"133":3,"134":1,"135":3,"136":3,"140":1,"143":8,"144":1,"145":1,"147":1,"148":1,"149":1,"150":3,"153":4,"154":1,"156":3,"157":1,"158":4,"159":1,"160":4,"162":8,"163":1,"164":3,"165":2,"166":6,"168":2,"169":5,"172":1,"174":1,"177":1,"178":2,"182":7,"183":1,"184":1,"186":2,"188":2,"191":4,"192":1,"193":1,"194":4,"195":2,"196":2,"197":3,"199":6,"202":3,"204":1,"206":1,"207":1,"208":1,"210":4,"211":1,"221":3,"222":1,"223":1,"225":4,"227":1,"229":8,"230":10,"232":1,"235":1,"236":1,"239":4,"240":1,"241":4,"242":6,"243":9,"244":3,"245":5,"247":11,"248":1,"249":3,"253":2,"254":1,"255":12,"256":5,"258":2,"259":4,"262":2,"263":1,"264":5,"265":2,"266":2,"267":1,"269":7,"270":1,"272":2,"273":2,"275":1,"277":3,"278":1,"279":3,"280":2,"281":1,"282":3,"283":2,"284":1,"287":4,"288":1,"290":1,"291":1,"293":3,"294":3,"295":1,"297":1,"299":2,"300":1,"301":1,"304":7,"305":1,"307":1,"308":2,"313":5,"314":1,"315":1,"317":3,"318":3,"319":1,"320":4,"321":5,"322":2,"324":4,"326":2,"327":1,"328":1,"330":3,"331":2,"334":5,"336":1,"337":2,"338":1,"340":3,"341":1,"342":1,"343":1,"346":8,"347":1,"348":1,"349":12,"350":5,"351":1,"352":1,"355":7,"356":1,"358":6,"360":2,"361":4,"362":5,"365":1,"366":2,"367":1,"369":5,"370":1,"371":2,"373":1,"376":3,"377":1,"379":2,"380":1,"382":1,"383":2,"385":1,"386":1,"390":1,"391":1,"394":1,"396":4,"398":3,"400":5,"401":1,"402":1,"404":2,"406":6,"407":2,"408":1,"410":1,"412":1,"413":2,"414":2,"417":1,"419":2,"420":2,"423":2,"424":1,"425":1,"426":4,"428":3,"429":1,"430":1,"431":1,"432":1,"433":1,"434":2,"437":1,"438":1,"439":1,"440":2,"443":2,"444":1,"445":1,"446":1,"447":1,"449":1,"450":1,"454":1,"455":4,"456":1,"457":2,"458":3,"459":4,"460":1,"461":1,"462":6,"463":2,"464":3,"466":1,"467":1,"468":2,"469":2,"470":1,"471":3,"472":1,"473":6,"474":3,"475":1,"476":1,"477":2,"479":1,"481":1,"485":3,"490":1,"491":1,"492":1,"493":2,"496":1,"497":4,"498":3,"499":1,"500":1,"503":5,"504":1,"506":4,"508":2,"509":2,"510":3,"511":1,"515":1,"516":3,"517":4,"518":3,"520":1,"521":5,"522":2,"524":1,"525":1,"526":1,"528":1,"529":1,"531":1,"532":3,"533":1,"534":1,"535":3,"536":4,"537":4,"538":2,"539":1,"541":9,"542":1,"543":1,"544":6,"546":1,"548":12,"549":7,"551":1,"553":1,"554":5,"555":7,"556":6,"557":2,"558":1,"560":5,"561":1,"563":2,"565":3,"568":6,"569":1,"571":5,"572":6,"573":3,"574":2,"575":1,"576":1,"577":3,"578":2,"579":7,"580":1,"582":5,"586":1,"587":5,"588":1,"590":5,"591":1,"592":2,"594":1,"595":5,"596":1,"598":4,"599":5,"601":2,"605":4,"606":1,"608":2,"610":2,"611":1,"612":1,"614":4,"615":1,"616":1,"617":2,"618":2,"619":3,"622":3,"623":1,"624":1,"625":3,"626":2,"627":1,"630":2,"631":2,"632":2,"633":2,"634":1,"635":1,"637":1,"639":2,"642":1,"645":6,"646":1,"647":1,"648":4,"649":1,"650":1,"651":1,"653":4,"654":1,"656":3,"657":3,"658":2,"659":2,"660":1,"661":4,"662":1,"664":4,"665":1,"666":1,"667":3,"670":4,"671":1,"673":3,"676":2,"678":4,"679":1,"681":3,"682":3,"683":4,"684":4,"686":5,"687":1,"689":2,"690":3,"691":1,"694":6,"695":1,"697":2,"698":2,"700":1,"701":9,"702":1,"704":1,"706":2,"707":6,"708":1,"709":2,"711":7,"712":1,"714":2,"715":5,"716":2,"717":1,"721":8,"722":1,"724":1,"725":5,"726":1,"727":2,"729":7,"730":2,"731":4,"732":4,"733":6,"734":2,"735":1,"737":7,"738":1,"740":2,"741":5,"742":1,"743":4,"745":8,"746":1,"748":4,"749":7,"750":6,"751":2,"753":9,"754":1,"756":6,"757":2,"758":1,"759":3,"761":1,"762":7,"763":1,"764":1,"765":5,"766":1,"768":2,"769":2,"770":4,"771":1,"773":8,"774":1,"775":1,"776":3,"777":2,"778":7,"779":1,"781":2,"782":2,"783":2,"784":2,"785":1,"786":7,"787":1,"789":6,"790":4,"792":2,"795":7,"796":1,"797":4,"798":3,"799":5,"800":2,"801":3,"802":8,"803":1,"804":1,"807":2,"808":2,"809":5,"810":9,"811":2,"812":2,"813":2,"815":9,"816":1,"817":3,"818":2,"819":2,"821":4,"823":8,"824":1,"825":4,"826":4,"827":18,"828":2,"829":3,"834":10,"835":1,"836":2,"837":1,"838":2,"839":1,"840":2,"842":8,"843":1,"844":2,"845":3,"847":3,"848":1,"849":1,"851":1,"852":2,"853":2,"854":3,"856":3,"857":3,"858":1,"860":3,"861":1,"862":2,"863":2,"865":5,"866":1,"868":1,"869":1,"870":1,"872":1,"873":1,"874":5,"878":1,"880":5,"881":7,"882":4,"885":7,"886":1,"888":3,"889":1,"891":3,"892":1,"893":4,"894":1,"895":1,"896":6,"897":2,"899":2,"900":3,"901":7,"902":1,"903":1,"904":2,"905":3,"907":3,"910":12,"911":1,"912":1,"913":2,"914":3,"916":4,"918":9,"919":1,"920":1,"921":2,"922":6,"923":3,"924":2,"925":8,"926":1,"927":1,"928":1,"929":2,"930":2,"931":1,"932":2,"933":2,"935":6,"936":1,"937":2,"938":1,"939":3,"940":1,"941":1,"942":5,"943":1,"944":1,"945":1,"946":1,"947":2,"948":1,"950":6,"951":1,"953":6,"954":3,"955":7,"956":2,"957":9,"960":1,"961":1,"962":1,"963":3,"964":4,"965":4,"966":2,"968":7,"969":1,"971":3,"972":1,"973":2,"976":7,"977":1,"979":2,"980":2,"981":1,"984":6,"985":1,"987":6,"988":2,"989":1,"992":8,"993":1,"994":1,"995":4,"1000":6,"1001":1,"1003":2,"1004":1,"1005":1,"1006":2,"1008":5,"1009":1,"1010":1,"1012":4,"1013":1,"1015":1,"1016":2,"1017":1,"1018":3,"1020":1,"1022":7,"1023":1,"1024":1,"1025":3,"1026":6,"1027":2,"1030":8,"1031":1,"1032":2,"1033":2,"1034":1,"1035":2,"1036":1,"1038":7,"1039":1,"1040":1,"1041":4,"1042":2,"1044":3,"1046":6,"1047":1,"1048":4,"1049":5,"1050":4,"1051":7,"1052":4,"1053":8,"1054":1,"1056":1,"1057":2,"1058":2,"1060":1,"1061":1,"1063":5,"1064":1,"1065":1,"1069":2,"1071":5,"1072":1,"1073":4,"1074":4,"1075":1,"1076":1,"1077":1,"1078":1,"1079":10,"1080":1,"1082":1,"1083":1,"1084":2,"1087":6,"1088":1,"1089":1,"1090":2,"1092":1,"1093":1,"1096":8,"1097":1,"1098":1,"1099":2,"1100":4,"1101":2,"1102":3,"1103":1,"1104":7,"1105":1,"1107":1,"1108":2,"1112":6,"1113":1,"1114":2,"1115":1,"1116":2,"1118":1,"1120":7,"1121":1,"1123":3,"1126":1,"1129":5,"1130":1,"1132":2,"1134":3,"1137":5,"1138":1,"1139":3,"1142":2,"1144":4,"1145":2,"1147":8,"1148":1,"1150":1,"1152":3,"1154":2,"1155":2,"1157":9,"1158":1,"1160":1,"1162":2,"1164":2,"1165":1,"1167":4,"1169":3,"1171":5,"1172":1,"1173":3,"1174":1,"1176":1,"1180":7,"1181":1,"1182":3,"1183":1,"1186":2,"1189":8,"1192":18,"1193":1,"1202":4,"1203":4,"1211":2,"1212":40,"1213":7,"1214":1,"1217":1,"1219":1,"1222":1,"1225":1,"1227":2,"1228":2,"1229":11,"1231":4,"1232":2,"1233":3,"1234":1,"1235":1,"1236":1,"1237":28,"1238":5,"1239":2,"1241":1,"1243":1,"1244":1,"1247":23,"1248":4,"1249":2,"1251":1,"1254":1,"1256":1,"1257":2,"1258":2,"1259":70,"1260":2,"1261":1,"1262":1,"1263":4,"1264":1,"1265":1,"1267":1,"1269":6,"1270":61,"1271":8,"1273":2,"1274":1,"1275":1,"1276":1,"1277":2,"1278":3,"1279":1,"1280":2,"1281":1,"1282":2,"1283":2,"1284":3,"1285":172,"1286":15,"1288":1,"1289":1,"1291":2,"1292":1,"1293":1,"1296":2,"1297":2,"1298":1,"1299":153,"1300":23,"1301":5,"1302":2,"1303":2,"1306":2,"1307":1,"1308":38,"1309":14,"1310":121,"1311":21,"1312":4,"1313":1,"1315":4,"1316":6,"1317":1,"1319":4,"1320":1,"1321":4,"1322":115,"1323":181,"1324":4,"1325":7,"1327":4,"1329":1,"1330":1,"1331":2,"1332":2,"1333":2,"1335":3,"1336":3,"1337":2,"1338":100,"1342":1,"1344":1,"1346":3,"1347":1,"1348":3,"1349":70,"1351":2,"1352":4,"1353":2,"1354":4,"1356":1,"1357":1,"1358":231,"1360":2,"1362":1,"1363":1,"1364":1,"1365":1,"1366":3,"1367":1,"1368":13,"1370":2,"1371":2,"1372":2,"1373":1,"1374":2,"1375":1,"1377":3,"1378":8,"1379":38,"1383":1,"1386":1,"1388":3,"1390":1,"1393":2,"1394":81,"1395":183,"1396":2,"1397":1,"1398":1,"1399":3,"1400":3,"1401":69,"1403":3,"1404":4,"1405":7,"1406":1,"1407":1,"1408":1,"1409":2,"1410":2,"1411":2,"1412":6,"1413":1,"1414":142,"1415":27,"1416":106,"1417":2,"1418":1,"1420":2,"1422":1,"1426":29,"1427":4,"1428":2,"1429":4,"1430":10,"1431":3,"1432":1,"1434":2,"1435":229,"1437":76,"1440":6,"1441":1,"1445":3,"1446":3,"1448":3,"1449":1,"1452":3,"1453":8,"1454":5,"1457":1,"1459":4,"1460":3,"1461":3,"1466":20,"1467":1,"1468":5,"1470":2,"1472":4,"1473":1,"1474":1,"1475":1,"1476":1,"1480":1,"1481":6,"1482":1,"1485":2,"1486":2,"1487":7,"1488":10,"1489":1,"1490":1,"1491":3,"1492":3,"1493":10,"1495":104,"1498":2,"1499":4,"1501":1,"1502":6,"1503":1,"1507":1,"1508":3,"1509":2,"1511":1,"1512":5,"1513":1,"1514":2,"1518":1,"1520":1,"1523":2,"1524":3,"1525":28,"1526":3,"1530":2,"1533":5,"1534":5,"1535":2,"1540":1,"1542":1,"1544":2,"1545":1,"1546":1,"1547":2,"1553":1,"1572":2,"1574":2,"1575":8,"1576":83,"1577":3,"1581":8,"1582":1,"1584":9,"1585":4,"1590":20,"1591":3,"1595":1,"1598":7,"1599":2,"1625":1,"1626":2,"1628":1,"1629":2,"1630":10,"1631":1,"1635":2,"1639":4,"1640":2,"1641":2,"1645":1,"1647":2,"1648":1,"1649":1,"1650":6,"1651":1,"1652":4,"1653":5,"1654":1,"1655":5,"1657":1,"1659":4,"1660":1,"1661":3,"1662":3,"1663":4,"1664":2,"1665":2,"1666":4,"1667":4,"1668":1,"1669":4,"1670":2,"1671":1,"1672":5,"1673":1,"1674":5,"1679":1,"1681":1,"1683":2,"1684":2,"1685":1,"1692":3,"1694":1,"1696":1,"1698":1,"1702":1,"1704":1,"1705":1,"1706":1,"1707":2,"1718":1,"1719":12,"1720":3,"1722":2,"1723":8,"1725":1,"1726":2,"1727":1,"1731":1,"1734":1,"1736":1,"1746":3,"1747":5,"1748":5,"1750":1,"1754":1,"1758":1,"1759":1,"1760":2,"1763":1,"1764":9,"1768":4,"1771":1,"1777":1,"1778":4,"1780":4,"1782":1,"1783":4,"1789":4,"1791":1,"1792":4,"1795":2,"1798":3,"1800":1,"1801":1,"1803":1,"1804":1,"1805":1,"1806":3,"1809":1,"1819":1,"1823":1,"1825":1,"1826":6,"1838":1,"1839":1,"1840":2,"1841":3,"1842":2,"1844":1,"1845":3,"1847":1,"1850":1,"1851":1,"1852":4,"1854":3,"1855":2,"1857":1,"1860":2,"1863":1,"1866":1,"1867":3,"1868":2,"1869":2,"1874":2,"1876":2,"1883":1,"1885":3,"1887":1,"1888":2,"1889":1,"1890":2,"1891":5,"1895":2,"1898":4,"1901":1,"1902":4,"1905":2,"1909":1,"1914":1,"1915":1,"1919":1,"1920":3,"1921":1,"1922":2,"1923":1,"1928":1,"1931":1,"1932":3,"1934":1,"1935":1,"1940":1,"1941":1,"1942":1,"1945":2,"1946":1,"1947":1,"1948":1,"1949":6,"1950":1,"1951":1,"1952":1,"1954":1,"1957":1,"1958":1,"1964":3,"1969":2,"1970":1,"1971":5,"1973":1,"1975":1,"1977":1,"1978":2,"1982":1,"1983":1,"1984":3,"1985":1,"1986":1,"1987":4,"1989":1,"1990":7,"1993":2,"1994":1,"1995":1,"1996":4,"1999":7,"2000":4,"2001":4,"2005":1,"2007":2,"2008":2,"2011":1,"2019":1,"2020":1,"2021":1,"2022":1,"2023":4,"2024":1,"2026":2,"2027":6,"2028":2,"2029":1,"2031":3,"2032":1,"2033":2,"2034":2,"2036":1,"2037":2,"2042":2,"2043":1,"2045":1,"2046":1,"2054":2,"2055":1,"2056":1,"2058":1,"2059":3,"2061":2,"2062":2,"2063":1,"2064":4,"2065":3,"2067":2,"2068":6,"2080":4,"2082":3,"2084":5,"2085":3,"2086":2,"2097":1,"2098":1,"2099":1,"2105":2,"2106":1,"2107":4,"2111":1,"2115":1,"2116":1,"2117":1,"2119":1,"2120":2,"2122":1,"2125":5,"2126":2,"2127":2,"2129":2,"2130":4,"2131":2,"2132":2,"2135":1,"2136":1,"2137":3,"2138":2,"2139":1,"2140":3,"2142":2,"2145":1,"2146":1,"2148":3,"2149":3,"2150":3,"2151":2,"2152":3,"2154":1,"2155":2,"2157":3,"2159":2,"2160":1,"2161":6,"2162":1,"2163":4,"2164":1,"2165":6,"2167":7,"2168":4,"2169":1,"2178":5,"2179":3,"2180":2,"2181":1,"2182":10,"2183":3,"2184":3,"2185":1,"2187":2,"2189":1,"2191":9,"2192":5,"2193":1,"2194":1,"2197":3,"2198":1,"2201":3,"2202":2,"2203":1,"2207":1,"2231":27}}],["adrs",{"0":{"1514":1},"3":{"0":4,"1":1,"131":1,"324":2,"555":1,"789":1,"1011":1,"1013":1,"1190":1,"1212":3,"1270":1,"1285":5,"1308":1,"1310":1,"1312":1,"1316":1,"1349":1,"1358":3,"1395":1,"1416":2,"1427":1,"1470":2,"1493":1,"1495":8,"1501":1,"1516":1,"1533":1,"1536":1,"1551":1,"1570":4,"1571":2,"1576":4,"1590":2,"1657":1,"1658":1,"1662":1,"1672":1,"1673":1,"1704":1,"1724":1,"1782":1,"1786":1,"1791":1,"1792":1,"1796":1,"2001":1,"2027":1,"2085":1,"2210":1,"2218":1,"2220":1,"2231":2}}]],"serializationVersion":2}} \ No newline at end of file +{"v":2,"n":2246,"o":{"fields":["t","d","i","b"],"storeFields":["u","d","k","t","x","e"],"idField":"id"},"i":{"documentCount":2246,"nextId":2246,"documentIds":{"0":0,"1":1,"2":2,"3":3,"4":4,"5":5,"6":6,"7":7,"8":8,"9":9,"10":10,"11":11,"12":12,"13":13,"14":14,"15":15,"16":16,"17":17,"18":18,"19":19,"20":20,"21":21,"22":22,"23":23,"24":24,"25":25,"26":26,"27":27,"28":28,"29":29,"30":30,"31":31,"32":32,"33":33,"34":34,"35":35,"36":36,"37":37,"38":38,"39":39,"40":40,"41":41,"42":42,"43":43,"44":44,"45":45,"46":46,"47":47,"48":48,"49":49,"50":50,"51":51,"52":52,"53":53,"54":54,"55":55,"56":56,"57":57,"58":58,"59":59,"60":60,"61":61,"62":62,"63":63,"64":64,"65":65,"66":66,"67":67,"68":68,"69":69,"70":70,"71":71,"72":72,"73":73,"74":74,"75":75,"76":76,"77":77,"78":78,"79":79,"80":80,"81":81,"82":82,"83":83,"84":84,"85":85,"86":86,"87":87,"88":88,"89":89,"90":90,"91":91,"92":92,"93":93,"94":94,"95":95,"96":96,"97":97,"98":98,"99":99,"100":100,"101":101,"102":102,"103":103,"104":104,"105":105,"106":106,"107":107,"108":108,"109":109,"110":110,"111":111,"112":112,"113":113,"114":114,"115":115,"116":116,"117":117,"118":118,"119":119,"120":120,"121":121,"122":122,"123":123,"124":124,"125":125,"126":126,"127":127,"128":128,"129":129,"130":130,"131":131,"132":132,"133":133,"134":134,"135":135,"136":136,"137":137,"138":138,"139":139,"140":140,"141":141,"142":142,"143":143,"144":144,"145":145,"146":146,"147":147,"148":148,"149":149,"150":150,"151":151,"152":152,"153":153,"154":154,"155":155,"156":156,"157":157,"158":158,"159":159,"160":160,"161":161,"162":162,"163":163,"164":164,"165":165,"166":166,"167":167,"168":168,"169":169,"170":170,"171":171,"172":172,"173":173,"174":174,"175":175,"176":176,"177":177,"178":178,"179":179,"180":180,"181":181,"182":182,"183":183,"184":184,"185":185,"186":186,"187":187,"188":188,"189":189,"190":190,"191":191,"192":192,"193":193,"194":194,"195":195,"196":196,"197":197,"198":198,"199":199,"200":200,"201":201,"202":202,"203":203,"204":204,"205":205,"206":206,"207":207,"208":208,"209":209,"210":210,"211":211,"212":212,"213":213,"214":214,"215":215,"216":216,"217":217,"218":218,"219":219,"220":220,"221":221,"222":222,"223":223,"224":224,"225":225,"226":226,"227":227,"228":228,"229":229,"230":230,"231":231,"232":232,"233":233,"234":234,"235":235,"236":236,"237":237,"238":238,"239":239,"240":240,"241":241,"242":242,"243":243,"244":244,"245":245,"246":246,"247":247,"248":248,"249":249,"250":250,"251":251,"252":252,"253":253,"254":254,"255":255,"256":256,"257":257,"258":258,"259":259,"260":260,"261":261,"262":262,"263":263,"264":264,"265":265,"266":266,"267":267,"268":268,"269":269,"270":270,"271":271,"272":272,"273":273,"274":274,"275":275,"276":276,"277":277,"278":278,"279":279,"280":280,"281":281,"282":282,"283":283,"284":284,"285":285,"286":286,"287":287,"288":288,"289":289,"290":290,"291":291,"292":292,"293":293,"294":294,"295":295,"296":296,"297":297,"298":298,"299":299,"300":300,"301":301,"302":302,"303":303,"304":304,"305":305,"306":306,"307":307,"308":308,"309":309,"310":310,"311":311,"312":312,"313":313,"314":314,"315":315,"316":316,"317":317,"318":318,"319":319,"320":320,"321":321,"322":322,"323":323,"324":324,"325":325,"326":326,"327":327,"328":328,"329":329,"330":330,"331":331,"332":332,"333":333,"334":334,"335":335,"336":336,"337":337,"338":338,"339":339,"340":340,"341":341,"342":342,"343":343,"344":344,"345":345,"346":346,"347":347,"348":348,"349":349,"350":350,"351":351,"352":352,"353":353,"354":354,"355":355,"356":356,"357":357,"358":358,"359":359,"360":360,"361":361,"362":362,"363":363,"364":364,"365":365,"366":366,"367":367,"368":368,"369":369,"370":370,"371":371,"372":372,"373":373,"374":374,"375":375,"376":376,"377":377,"378":378,"379":379,"380":380,"381":381,"382":382,"383":383,"384":384,"385":385,"386":386,"387":387,"388":388,"389":389,"390":390,"391":391,"392":392,"393":393,"394":394,"395":395,"396":396,"397":397,"398":398,"399":399,"400":400,"401":401,"402":402,"403":403,"404":404,"405":405,"406":406,"407":407,"408":408,"409":409,"410":410,"411":411,"412":412,"413":413,"414":414,"415":415,"416":416,"417":417,"418":418,"419":419,"420":420,"421":421,"422":422,"423":423,"424":424,"425":425,"426":426,"427":427,"428":428,"429":429,"430":430,"431":431,"432":432,"433":433,"434":434,"435":435,"436":436,"437":437,"438":438,"439":439,"440":440,"441":441,"442":442,"443":443,"444":444,"445":445,"446":446,"447":447,"448":448,"449":449,"450":450,"451":451,"452":452,"453":453,"454":454,"455":455,"456":456,"457":457,"458":458,"459":459,"460":460,"461":461,"462":462,"463":463,"464":464,"465":465,"466":466,"467":467,"468":468,"469":469,"470":470,"471":471,"472":472,"473":473,"474":474,"475":475,"476":476,"477":477,"478":478,"479":479,"480":480,"481":481,"482":482,"483":483,"484":484,"485":485,"486":486,"487":487,"488":488,"489":489,"490":490,"491":491,"492":492,"493":493,"494":494,"495":495,"496":496,"497":497,"498":498,"499":499,"500":500,"501":501,"502":502,"503":503,"504":504,"505":505,"506":506,"507":507,"508":508,"509":509,"510":510,"511":511,"512":512,"513":513,"514":514,"515":515,"516":516,"517":517,"518":518,"519":519,"520":520,"521":521,"522":522,"523":523,"524":524,"525":525,"526":526,"527":527,"528":528,"529":529,"530":530,"531":531,"532":532,"533":533,"534":534,"535":535,"536":536,"537":537,"538":538,"539":539,"540":540,"541":541,"542":542,"543":543,"544":544,"545":545,"546":546,"547":547,"548":548,"549":549,"550":550,"551":551,"552":552,"553":553,"554":554,"555":555,"556":556,"557":557,"558":558,"559":559,"560":560,"561":561,"562":562,"563":563,"564":564,"565":565,"566":566,"567":567,"568":568,"569":569,"570":570,"571":571,"572":572,"573":573,"574":574,"575":575,"576":576,"577":577,"578":578,"579":579,"580":580,"581":581,"582":582,"583":583,"584":584,"585":585,"586":586,"587":587,"588":588,"589":589,"590":590,"591":591,"592":592,"593":593,"594":594,"595":595,"596":596,"597":597,"598":598,"599":599,"600":600,"601":601,"602":602,"603":603,"604":604,"605":605,"606":606,"607":607,"608":608,"609":609,"610":610,"611":611,"612":612,"613":613,"614":614,"615":615,"616":616,"617":617,"618":618,"619":619,"620":620,"621":621,"622":622,"623":623,"624":624,"625":625,"626":626,"627":627,"628":628,"629":629,"630":630,"631":631,"632":632,"633":633,"634":634,"635":635,"636":636,"637":637,"638":638,"639":639,"640":640,"641":641,"642":642,"643":643,"644":644,"645":645,"646":646,"647":647,"648":648,"649":649,"650":650,"651":651,"652":652,"653":653,"654":654,"655":655,"656":656,"657":657,"658":658,"659":659,"660":660,"661":661,"662":662,"663":663,"664":664,"665":665,"666":666,"667":667,"668":668,"669":669,"670":670,"671":671,"672":672,"673":673,"674":674,"675":675,"676":676,"677":677,"678":678,"679":679,"680":680,"681":681,"682":682,"683":683,"684":684,"685":685,"686":686,"687":687,"688":688,"689":689,"690":690,"691":691,"692":692,"693":693,"694":694,"695":695,"696":696,"697":697,"698":698,"699":699,"700":700,"701":701,"702":702,"703":703,"704":704,"705":705,"706":706,"707":707,"708":708,"709":709,"710":710,"711":711,"712":712,"713":713,"714":714,"715":715,"716":716,"717":717,"718":718,"719":719,"720":720,"721":721,"722":722,"723":723,"724":724,"725":725,"726":726,"727":727,"728":728,"729":729,"730":730,"731":731,"732":732,"733":733,"734":734,"735":735,"736":736,"737":737,"738":738,"739":739,"740":740,"741":741,"742":742,"743":743,"744":744,"745":745,"746":746,"747":747,"748":748,"749":749,"750":750,"751":751,"752":752,"753":753,"754":754,"755":755,"756":756,"757":757,"758":758,"759":759,"760":760,"761":761,"762":762,"763":763,"764":764,"765":765,"766":766,"767":767,"768":768,"769":769,"770":770,"771":771,"772":772,"773":773,"774":774,"775":775,"776":776,"777":777,"778":778,"779":779,"780":780,"781":781,"782":782,"783":783,"784":784,"785":785,"786":786,"787":787,"788":788,"789":789,"790":790,"791":791,"792":792,"793":793,"794":794,"795":795,"796":796,"797":797,"798":798,"799":799,"800":800,"801":801,"802":802,"803":803,"804":804,"805":805,"806":806,"807":807,"808":808,"809":809,"810":810,"811":811,"812":812,"813":813,"814":814,"815":815,"816":816,"817":817,"818":818,"819":819,"820":820,"821":821,"822":822,"823":823,"824":824,"825":825,"826":826,"827":827,"828":828,"829":829,"830":830,"831":831,"832":832,"833":833,"834":834,"835":835,"836":836,"837":837,"838":838,"839":839,"840":840,"841":841,"842":842,"843":843,"844":844,"845":845,"846":846,"847":847,"848":848,"849":849,"850":850,"851":851,"852":852,"853":853,"854":854,"855":855,"856":856,"857":857,"858":858,"859":859,"860":860,"861":861,"862":862,"863":863,"864":864,"865":865,"866":866,"867":867,"868":868,"869":869,"870":870,"871":871,"872":872,"873":873,"874":874,"875":875,"876":876,"877":877,"878":878,"879":879,"880":880,"881":881,"882":882,"883":883,"884":884,"885":885,"886":886,"887":887,"888":888,"889":889,"890":890,"891":891,"892":892,"893":893,"894":894,"895":895,"896":896,"897":897,"898":898,"899":899,"900":900,"901":901,"902":902,"903":903,"904":904,"905":905,"906":906,"907":907,"908":908,"909":909,"910":910,"911":911,"912":912,"913":913,"914":914,"915":915,"916":916,"917":917,"918":918,"919":919,"920":920,"921":921,"922":922,"923":923,"924":924,"925":925,"926":926,"927":927,"928":928,"929":929,"930":930,"931":931,"932":932,"933":933,"934":934,"935":935,"936":936,"937":937,"938":938,"939":939,"940":940,"941":941,"942":942,"943":943,"944":944,"945":945,"946":946,"947":947,"948":948,"949":949,"950":950,"951":951,"952":952,"953":953,"954":954,"955":955,"956":956,"957":957,"958":958,"959":959,"960":960,"961":961,"962":962,"963":963,"964":964,"965":965,"966":966,"967":967,"968":968,"969":969,"970":970,"971":971,"972":972,"973":973,"974":974,"975":975,"976":976,"977":977,"978":978,"979":979,"980":980,"981":981,"982":982,"983":983,"984":984,"985":985,"986":986,"987":987,"988":988,"989":989,"990":990,"991":991,"992":992,"993":993,"994":994,"995":995,"996":996,"997":997,"998":998,"999":999,"1000":1000,"1001":1001,"1002":1002,"1003":1003,"1004":1004,"1005":1005,"1006":1006,"1007":1007,"1008":1008,"1009":1009,"1010":1010,"1011":1011,"1012":1012,"1013":1013,"1014":1014,"1015":1015,"1016":1016,"1017":1017,"1018":1018,"1019":1019,"1020":1020,"1021":1021,"1022":1022,"1023":1023,"1024":1024,"1025":1025,"1026":1026,"1027":1027,"1028":1028,"1029":1029,"1030":1030,"1031":1031,"1032":1032,"1033":1033,"1034":1034,"1035":1035,"1036":1036,"1037":1037,"1038":1038,"1039":1039,"1040":1040,"1041":1041,"1042":1042,"1043":1043,"1044":1044,"1045":1045,"1046":1046,"1047":1047,"1048":1048,"1049":1049,"1050":1050,"1051":1051,"1052":1052,"1053":1053,"1054":1054,"1055":1055,"1056":1056,"1057":1057,"1058":1058,"1059":1059,"1060":1060,"1061":1061,"1062":1062,"1063":1063,"1064":1064,"1065":1065,"1066":1066,"1067":1067,"1068":1068,"1069":1069,"1070":1070,"1071":1071,"1072":1072,"1073":1073,"1074":1074,"1075":1075,"1076":1076,"1077":1077,"1078":1078,"1079":1079,"1080":1080,"1081":1081,"1082":1082,"1083":1083,"1084":1084,"1085":1085,"1086":1086,"1087":1087,"1088":1088,"1089":1089,"1090":1090,"1091":1091,"1092":1092,"1093":1093,"1094":1094,"1095":1095,"1096":1096,"1097":1097,"1098":1098,"1099":1099,"1100":1100,"1101":1101,"1102":1102,"1103":1103,"1104":1104,"1105":1105,"1106":1106,"1107":1107,"1108":1108,"1109":1109,"1110":1110,"1111":1111,"1112":1112,"1113":1113,"1114":1114,"1115":1115,"1116":1116,"1117":1117,"1118":1118,"1119":1119,"1120":1120,"1121":1121,"1122":1122,"1123":1123,"1124":1124,"1125":1125,"1126":1126,"1127":1127,"1128":1128,"1129":1129,"1130":1130,"1131":1131,"1132":1132,"1133":1133,"1134":1134,"1135":1135,"1136":1136,"1137":1137,"1138":1138,"1139":1139,"1140":1140,"1141":1141,"1142":1142,"1143":1143,"1144":1144,"1145":1145,"1146":1146,"1147":1147,"1148":1148,"1149":1149,"1150":1150,"1151":1151,"1152":1152,"1153":1153,"1154":1154,"1155":1155,"1156":1156,"1157":1157,"1158":1158,"1159":1159,"1160":1160,"1161":1161,"1162":1162,"1163":1163,"1164":1164,"1165":1165,"1166":1166,"1167":1167,"1168":1168,"1169":1169,"1170":1170,"1171":1171,"1172":1172,"1173":1173,"1174":1174,"1175":1175,"1176":1176,"1177":1177,"1178":1178,"1179":1179,"1180":1180,"1181":1181,"1182":1182,"1183":1183,"1184":1184,"1185":1185,"1186":1186,"1187":1187,"1188":1188,"1189":1189,"1190":1190,"1191":1191,"1192":1192,"1193":1193,"1194":1194,"1195":1195,"1196":1196,"1197":1197,"1198":1198,"1199":1199,"1200":1200,"1201":1201,"1202":1202,"1203":1203,"1204":1204,"1205":1205,"1206":1206,"1207":1207,"1208":1208,"1209":1209,"1210":1210,"1211":1211,"1212":1212,"1213":1213,"1214":1214,"1215":1215,"1216":1216,"1217":1217,"1218":1218,"1219":1219,"1220":1220,"1221":1221,"1222":1222,"1223":1223,"1224":1224,"1225":1225,"1226":1226,"1227":1227,"1228":1228,"1229":1229,"1230":1230,"1231":1231,"1232":1232,"1233":1233,"1234":1234,"1235":1235,"1236":1236,"1237":1237,"1238":1238,"1239":1239,"1240":1240,"1241":1241,"1242":1242,"1243":1243,"1244":1244,"1245":1245,"1246":1246,"1247":1247,"1248":1248,"1249":1249,"1250":1250,"1251":1251,"1252":1252,"1253":1253,"1254":1254,"1255":1255,"1256":1256,"1257":1257,"1258":1258,"1259":1259,"1260":1260,"1261":1261,"1262":1262,"1263":1263,"1264":1264,"1265":1265,"1266":1266,"1267":1267,"1268":1268,"1269":1269,"1270":1270,"1271":1271,"1272":1272,"1273":1273,"1274":1274,"1275":1275,"1276":1276,"1277":1277,"1278":1278,"1279":1279,"1280":1280,"1281":1281,"1282":1282,"1283":1283,"1284":1284,"1285":1285,"1286":1286,"1287":1287,"1288":1288,"1289":1289,"1290":1290,"1291":1291,"1292":1292,"1293":1293,"1294":1294,"1295":1295,"1296":1296,"1297":1297,"1298":1298,"1299":1299,"1300":1300,"1301":1301,"1302":1302,"1303":1303,"1304":1304,"1305":1305,"1306":1306,"1307":1307,"1308":1308,"1309":1309,"1310":1310,"1311":1311,"1312":1312,"1313":1313,"1314":1314,"1315":1315,"1316":1316,"1317":1317,"1318":1318,"1319":1319,"1320":1320,"1321":1321,"1322":1322,"1323":1323,"1324":1324,"1325":1325,"1326":1326,"1327":1327,"1328":1328,"1329":1329,"1330":1330,"1331":1331,"1332":1332,"1333":1333,"1334":1334,"1335":1335,"1336":1336,"1337":1337,"1338":1338,"1339":1339,"1340":1340,"1341":1341,"1342":1342,"1343":1343,"1344":1344,"1345":1345,"1346":1346,"1347":1347,"1348":1348,"1349":1349,"1350":1350,"1351":1351,"1352":1352,"1353":1353,"1354":1354,"1355":1355,"1356":1356,"1357":1357,"1358":1358,"1359":1359,"1360":1360,"1361":1361,"1362":1362,"1363":1363,"1364":1364,"1365":1365,"1366":1366,"1367":1367,"1368":1368,"1369":1369,"1370":1370,"1371":1371,"1372":1372,"1373":1373,"1374":1374,"1375":1375,"1376":1376,"1377":1377,"1378":1378,"1379":1379,"1380":1380,"1381":1381,"1382":1382,"1383":1383,"1384":1384,"1385":1385,"1386":1386,"1387":1387,"1388":1388,"1389":1389,"1390":1390,"1391":1391,"1392":1392,"1393":1393,"1394":1394,"1395":1395,"1396":1396,"1397":1397,"1398":1398,"1399":1399,"1400":1400,"1401":1401,"1402":1402,"1403":1403,"1404":1404,"1405":1405,"1406":1406,"1407":1407,"1408":1408,"1409":1409,"1410":1410,"1411":1411,"1412":1412,"1413":1413,"1414":1414,"1415":1415,"1416":1416,"1417":1417,"1418":1418,"1419":1419,"1420":1420,"1421":1421,"1422":1422,"1423":1423,"1424":1424,"1425":1425,"1426":1426,"1427":1427,"1428":1428,"1429":1429,"1430":1430,"1431":1431,"1432":1432,"1433":1433,"1434":1434,"1435":1435,"1436":1436,"1437":1437,"1438":1438,"1439":1439,"1440":1440,"1441":1441,"1442":1442,"1443":1443,"1444":1444,"1445":1445,"1446":1446,"1447":1447,"1448":1448,"1449":1449,"1450":1450,"1451":1451,"1452":1452,"1453":1453,"1454":1454,"1455":1455,"1456":1456,"1457":1457,"1458":1458,"1459":1459,"1460":1460,"1461":1461,"1462":1462,"1463":1463,"1464":1464,"1465":1465,"1466":1466,"1467":1467,"1468":1468,"1469":1469,"1470":1470,"1471":1471,"1472":1472,"1473":1473,"1474":1474,"1475":1475,"1476":1476,"1477":1477,"1478":1478,"1479":1479,"1480":1480,"1481":1481,"1482":1482,"1483":1483,"1484":1484,"1485":1485,"1486":1486,"1487":1487,"1488":1488,"1489":1489,"1490":1490,"1491":1491,"1492":1492,"1493":1493,"1494":1494,"1495":1495,"1496":1496,"1497":1497,"1498":1498,"1499":1499,"1500":1500,"1501":1501,"1502":1502,"1503":1503,"1504":1504,"1505":1505,"1506":1506,"1507":1507,"1508":1508,"1509":1509,"1510":1510,"1511":1511,"1512":1512,"1513":1513,"1514":1514,"1515":1515,"1516":1516,"1517":1517,"1518":1518,"1519":1519,"1520":1520,"1521":1521,"1522":1522,"1523":1523,"1524":1524,"1525":1525,"1526":1526,"1527":1527,"1528":1528,"1529":1529,"1530":1530,"1531":1531,"1532":1532,"1533":1533,"1534":1534,"1535":1535,"1536":1536,"1537":1537,"1538":1538,"1539":1539,"1540":1540,"1541":1541,"1542":1542,"1543":1543,"1544":1544,"1545":1545,"1546":1546,"1547":1547,"1548":1548,"1549":1549,"1550":1550,"1551":1551,"1552":1552,"1553":1553,"1554":1554,"1555":1555,"1556":1556,"1557":1557,"1558":1558,"1559":1559,"1560":1560,"1561":1561,"1562":1562,"1563":1563,"1564":1564,"1565":1565,"1566":1566,"1567":1567,"1568":1568,"1569":1569,"1570":1570,"1571":1571,"1572":1572,"1573":1573,"1574":1574,"1575":1575,"1576":1576,"1577":1577,"1578":1578,"1579":1579,"1580":1580,"1581":1581,"1582":1582,"1583":1583,"1584":1584,"1585":1585,"1586":1586,"1587":1587,"1588":1588,"1589":1589,"1590":1590,"1591":1591,"1592":1592,"1593":1593,"1594":1594,"1595":1595,"1596":1596,"1597":1597,"1598":1598,"1599":1599,"1600":1600,"1601":1601,"1602":1602,"1603":1603,"1604":1604,"1605":1605,"1606":1606,"1607":1607,"1608":1608,"1609":1609,"1610":1610,"1611":1611,"1612":1612,"1613":1613,"1614":1614,"1615":1615,"1616":1616,"1617":1617,"1618":1618,"1619":1619,"1620":1620,"1621":1621,"1622":1622,"1623":1623,"1624":1624,"1625":1625,"1626":1626,"1627":1627,"1628":1628,"1629":1629,"1630":1630,"1631":1631,"1632":1632,"1633":1633,"1634":1634,"1635":1635,"1636":1636,"1637":1637,"1638":1638,"1639":1639,"1640":1640,"1641":1641,"1642":1642,"1643":1643,"1644":1644,"1645":1645,"1646":1646,"1647":1647,"1648":1648,"1649":1649,"1650":1650,"1651":1651,"1652":1652,"1653":1653,"1654":1654,"1655":1655,"1656":1656,"1657":1657,"1658":1658,"1659":1659,"1660":1660,"1661":1661,"1662":1662,"1663":1663,"1664":1664,"1665":1665,"1666":1666,"1667":1667,"1668":1668,"1669":1669,"1670":1670,"1671":1671,"1672":1672,"1673":1673,"1674":1674,"1675":1675,"1676":1676,"1677":1677,"1678":1678,"1679":1679,"1680":1680,"1681":1681,"1682":1682,"1683":1683,"1684":1684,"1685":1685,"1686":1686,"1687":1687,"1688":1688,"1689":1689,"1690":1690,"1691":1691,"1692":1692,"1693":1693,"1694":1694,"1695":1695,"1696":1696,"1697":1697,"1698":1698,"1699":1699,"1700":1700,"1701":1701,"1702":1702,"1703":1703,"1704":1704,"1705":1705,"1706":1706,"1707":1707,"1708":1708,"1709":1709,"1710":1710,"1711":1711,"1712":1712,"1713":1713,"1714":1714,"1715":1715,"1716":1716,"1717":1717,"1718":1718,"1719":1719,"1720":1720,"1721":1721,"1722":1722,"1723":1723,"1724":1724,"1725":1725,"1726":1726,"1727":1727,"1728":1728,"1729":1729,"1730":1730,"1731":1731,"1732":1732,"1733":1733,"1734":1734,"1735":1735,"1736":1736,"1737":1737,"1738":1738,"1739":1739,"1740":1740,"1741":1741,"1742":1742,"1743":1743,"1744":1744,"1745":1745,"1746":1746,"1747":1747,"1748":1748,"1749":1749,"1750":1750,"1751":1751,"1752":1752,"1753":1753,"1754":1754,"1755":1755,"1756":1756,"1757":1757,"1758":1758,"1759":1759,"1760":1760,"1761":1761,"1762":1762,"1763":1763,"1764":1764,"1765":1765,"1766":1766,"1767":1767,"1768":1768,"1769":1769,"1770":1770,"1771":1771,"1772":1772,"1773":1773,"1774":1774,"1775":1775,"1776":1776,"1777":1777,"1778":1778,"1779":1779,"1780":1780,"1781":1781,"1782":1782,"1783":1783,"1784":1784,"1785":1785,"1786":1786,"1787":1787,"1788":1788,"1789":1789,"1790":1790,"1791":1791,"1792":1792,"1793":1793,"1794":1794,"1795":1795,"1796":1796,"1797":1797,"1798":1798,"1799":1799,"1800":1800,"1801":1801,"1802":1802,"1803":1803,"1804":1804,"1805":1805,"1806":1806,"1807":1807,"1808":1808,"1809":1809,"1810":1810,"1811":1811,"1812":1812,"1813":1813,"1814":1814,"1815":1815,"1816":1816,"1817":1817,"1818":1818,"1819":1819,"1820":1820,"1821":1821,"1822":1822,"1823":1823,"1824":1824,"1825":1825,"1826":1826,"1827":1827,"1828":1828,"1829":1829,"1830":1830,"1831":1831,"1832":1832,"1833":1833,"1834":1834,"1835":1835,"1836":1836,"1837":1837,"1838":1838,"1839":1839,"1840":1840,"1841":1841,"1842":1842,"1843":1843,"1844":1844,"1845":1845,"1846":1846,"1847":1847,"1848":1848,"1849":1849,"1850":1850,"1851":1851,"1852":1852,"1853":1853,"1854":1854,"1855":1855,"1856":1856,"1857":1857,"1858":1858,"1859":1859,"1860":1860,"1861":1861,"1862":1862,"1863":1863,"1864":1864,"1865":1865,"1866":1866,"1867":1867,"1868":1868,"1869":1869,"1870":1870,"1871":1871,"1872":1872,"1873":1873,"1874":1874,"1875":1875,"1876":1876,"1877":1877,"1878":1878,"1879":1879,"1880":1880,"1881":1881,"1882":1882,"1883":1883,"1884":1884,"1885":1885,"1886":1886,"1887":1887,"1888":1888,"1889":1889,"1890":1890,"1891":1891,"1892":1892,"1893":1893,"1894":1894,"1895":1895,"1896":1896,"1897":1897,"1898":1898,"1899":1899,"1900":1900,"1901":1901,"1902":1902,"1903":1903,"1904":1904,"1905":1905,"1906":1906,"1907":1907,"1908":1908,"1909":1909,"1910":1910,"1911":1911,"1912":1912,"1913":1913,"1914":1914,"1915":1915,"1916":1916,"1917":1917,"1918":1918,"1919":1919,"1920":1920,"1921":1921,"1922":1922,"1923":1923,"1924":1924,"1925":1925,"1926":1926,"1927":1927,"1928":1928,"1929":1929,"1930":1930,"1931":1931,"1932":1932,"1933":1933,"1934":1934,"1935":1935,"1936":1936,"1937":1937,"1938":1938,"1939":1939,"1940":1940,"1941":1941,"1942":1942,"1943":1943,"1944":1944,"1945":1945,"1946":1946,"1947":1947,"1948":1948,"1949":1949,"1950":1950,"1951":1951,"1952":1952,"1953":1953,"1954":1954,"1955":1955,"1956":1956,"1957":1957,"1958":1958,"1959":1959,"1960":1960,"1961":1961,"1962":1962,"1963":1963,"1964":1964,"1965":1965,"1966":1966,"1967":1967,"1968":1968,"1969":1969,"1970":1970,"1971":1971,"1972":1972,"1973":1973,"1974":1974,"1975":1975,"1976":1976,"1977":1977,"1978":1978,"1979":1979,"1980":1980,"1981":1981,"1982":1982,"1983":1983,"1984":1984,"1985":1985,"1986":1986,"1987":1987,"1988":1988,"1989":1989,"1990":1990,"1991":1991,"1992":1992,"1993":1993,"1994":1994,"1995":1995,"1996":1996,"1997":1997,"1998":1998,"1999":1999,"2000":2000,"2001":2001,"2002":2002,"2003":2003,"2004":2004,"2005":2005,"2006":2006,"2007":2007,"2008":2008,"2009":2009,"2010":2010,"2011":2011,"2012":2012,"2013":2013,"2014":2014,"2015":2015,"2016":2016,"2017":2017,"2018":2018,"2019":2019,"2020":2020,"2021":2021,"2022":2022,"2023":2023,"2024":2024,"2025":2025,"2026":2026,"2027":2027,"2028":2028,"2029":2029,"2030":2030,"2031":2031,"2032":2032,"2033":2033,"2034":2034,"2035":2035,"2036":2036,"2037":2037,"2038":2038,"2039":2039,"2040":2040,"2041":2041,"2042":2042,"2043":2043,"2044":2044,"2045":2045,"2046":2046,"2047":2047,"2048":2048,"2049":2049,"2050":2050,"2051":2051,"2052":2052,"2053":2053,"2054":2054,"2055":2055,"2056":2056,"2057":2057,"2058":2058,"2059":2059,"2060":2060,"2061":2061,"2062":2062,"2063":2063,"2064":2064,"2065":2065,"2066":2066,"2067":2067,"2068":2068,"2069":2069,"2070":2070,"2071":2071,"2072":2072,"2073":2073,"2074":2074,"2075":2075,"2076":2076,"2077":2077,"2078":2078,"2079":2079,"2080":2080,"2081":2081,"2082":2082,"2083":2083,"2084":2084,"2085":2085,"2086":2086,"2087":2087,"2088":2088,"2089":2089,"2090":2090,"2091":2091,"2092":2092,"2093":2093,"2094":2094,"2095":2095,"2096":2096,"2097":2097,"2098":2098,"2099":2099,"2100":2100,"2101":2101,"2102":2102,"2103":2103,"2104":2104,"2105":2105,"2106":2106,"2107":2107,"2108":2108,"2109":2109,"2110":2110,"2111":2111,"2112":2112,"2113":2113,"2114":2114,"2115":2115,"2116":2116,"2117":2117,"2118":2118,"2119":2119,"2120":2120,"2121":2121,"2122":2122,"2123":2123,"2124":2124,"2125":2125,"2126":2126,"2127":2127,"2128":2128,"2129":2129,"2130":2130,"2131":2131,"2132":2132,"2133":2133,"2134":2134,"2135":2135,"2136":2136,"2137":2137,"2138":2138,"2139":2139,"2140":2140,"2141":2141,"2142":2142,"2143":2143,"2144":2144,"2145":2145,"2146":2146,"2147":2147,"2148":2148,"2149":2149,"2150":2150,"2151":2151,"2152":2152,"2153":2153,"2154":2154,"2155":2155,"2156":2156,"2157":2157,"2158":2158,"2159":2159,"2160":2160,"2161":2161,"2162":2162,"2163":2163,"2164":2164,"2165":2165,"2166":2166,"2167":2167,"2168":2168,"2169":2169,"2170":2170,"2171":2171,"2172":2172,"2173":2173,"2174":2174,"2175":2175,"2176":2176,"2177":2177,"2178":2178,"2179":2179,"2180":2180,"2181":2181,"2182":2182,"2183":2183,"2184":2184,"2185":2185,"2186":2186,"2187":2187,"2188":2188,"2189":2189,"2190":2190,"2191":2191,"2192":2192,"2193":2193,"2194":2194,"2195":2195,"2196":2196,"2197":2197,"2198":2198,"2199":2199,"2200":2200,"2201":2201,"2202":2202,"2203":2203,"2204":2204,"2205":2205,"2206":2206,"2207":2207,"2208":2208,"2209":2209,"2210":2210,"2211":2211,"2212":2212,"2213":2213,"2214":2214,"2215":2215,"2216":2216,"2217":2217,"2218":2218,"2219":2219,"2220":2220,"2221":2221,"2222":2222,"2223":2223,"2224":2224,"2225":2225,"2226":2226,"2227":2227,"2228":2228,"2229":2229,"2230":2230,"2231":2231,"2232":2232,"2233":2233,"2234":2234,"2235":2235,"2236":2236,"2237":2237,"2238":2238,"2239":2239,"2240":2240,"2241":2241,"2242":2242,"2243":2243,"2244":2244,"2245":2245},"fieldIds":{"t":0,"d":1,"i":2,"b":3},"fieldLength":{"0":[null,3,21,5392],"1":[4,3,null,152],"2":[null,7,null,7],"3":[1,7,2,61],"4":[1,7,4,27],"5":[1,7,12,95],"6":[1,7,3,80],"7":[2,7,2,61],"8":[null,7,null,7],"9":[1,7,null,1],"10":[1,7,8,91],"11":[1,7,12,67],"12":[1,7,1,57],"13":[2,7,1,39],"14":[null,7,null,7],"15":[1,7,11,237],"16":[1,7,null,41],"17":[1,7,9,181],"18":[1,7,7,139],"19":[2,7,7,265],"20":[5,7,16,230],"21":[5,7,8,171],"22":[5,7,1,157],"23":[5,7,null,125],"24":[5,7,18,649],"25":[5,7,2,138],"26":[5,7,14,319],"27":[5,7,12,486],"28":[null,10,null,10],"29":[1,10,3,28],"30":[1,10,null,87],"31":[1,10,20,356],"32":[1,10,null,120],"33":[2,10,null,98],"34":[5,10,3,48],"35":[1,10,1,36],"36":[null,8,null,8],"37":[1,8,6,54],"38":[1,8,5,119],"39":[1,8,19,438],"40":[1,8,1,78],"41":[2,8,4,185],"42":[5,8,5,142],"43":[5,8,12,111],"44":[null,5,null,5],"45":[1,5,2,63],"46":[1,5,6,88],"47":[1,5,13,173],"48":[1,5,1,52],"49":[2,5,null,53],"50":[null,9,null,9],"51":[1,9,2,46],"52":[1,9,3,61],"53":[1,9,16,217],"54":[1,9,5,118],"55":[2,9,4,73],"56":[null,13,null,13],"57":[1,13,3,149],"58":[1,13,4,123],"59":[1,13,14,339],"60":[1,13,1,133],"61":[2,13,8,124],"62":[1,13,10,229],"63":[5,13,8,124],"64":[1,13,null,29],"65":[null,6,null,6],"66":[1,6,3,96],"67":[1,6,6,98],"68":[1,6,10,216],"69":[1,6,null,76],"70":[2,6,10,122],"71":[5,6,9,275],"72":[5,6,19,282],"73":[5,6,12,196],"74":[5,6,4,172],"75":[null,8,null,8],"76":[1,8,5,109],"77":[1,8,5,96],"78":[1,8,12,168],"79":[1,8,1,110],"80":[2,8,17,219],"81":[8,8,14,289],"82":[5,8,7,95],"83":[null,9,null,9],"84":[1,9,null,28],"85":[1,9,3,50],"86":[1,9,1,94],"87":[1,9,null,41],"88":[2,9,null,40],"89":[null,14,null,14],"90":[1,14,null,36],"91":[9,14,15,226],"92":[14,14,18,216],"93":[22,14,15,274],"94":[18,14,7,213],"95":[20,14,24,351],"96":[20,14,18,234],"97":[1,14,6,173],"98":[1,14,15,258],"99":[19,14,19,245],"100":[17,14,16,300],"101":[1,14,4,109],"102":[2,14,7,123],"103":[5,14,7,154],"104":[5,14,8,157],"105":[1,14,null,38],"106":[null,9,null,9],"107":[1,9,9,109],"108":[1,9,null,77],"109":[1,9,17,968],"110":[1,9,8,231],"111":[2,9,14,279],"112":[5,9,9,67],"113":[1,9,2,73],"114":[null,8,null,8],"115":[1,8,9,202],"116":[1,8,null,60],"117":[1,8,16,297],"118":[1,8,null,61],"119":[2,8,1,56],"120":[5,8,7,127],"121":[5,8,18,296],"122":[5,8,13,493],"123":[5,8,5,118],"124":[1,8,7,94],"125":[5,8,5,212],"126":[5,8,22,323],"127":[5,8,8,102],"128":[5,8,11,207],"129":[null,8,null,8],"130":[1,8,11,234],"131":[1,8,1,84],"132":[1,8,14,189],"133":[1,8,1,83],"134":[2,8,2,77],"135":[5,8,25,855],"136":[8,8,16,421],"137":[15,8,13,299],"138":[15,8,19,265],"139":[16,8,17,366],"140":[12,8,7,240],"141":[14,8,19,215],"142":[14,8,21,411],"143":[1,8,1,72],"144":[null,10,null,10],"145":[1,10,21,244],"146":[1,10,10,107],"147":[1,10,23,312],"148":[1,10,3,116],"149":[2,10,9,134],"150":[3,10,18,377],"151":[2,10,5,102],"152":[5,10,20,134],"153":[1,10,null,39],"154":[null,9,null,9],"155":[1,9,10,126],"156":[1,9,1,93],"157":[1,9,15,422],"158":[1,9,1,99],"159":[2,9,10,228],"160":[5,9,17,391],"161":[5,9,15,188],"162":[1,9,2,98],"163":[null,11,null,11],"164":[1,11,10,192],"165":[1,11,3,121],"166":[1,11,12,415],"167":[1,11,null,87],"168":[2,11,4,188],"169":[1,11,1,48],"170":[10,11,12,262],"171":[5,11,7,146],"172":[null,11,null,11],"173":[1,11,2,161],"174":[1,11,null,124],"175":[1,11,15,426],"176":[1,11,null,101],"177":[2,11,1,208],"178":[5,11,14,362],"179":[5,11,13,251],"180":[5,11,13,216],"181":[5,11,4,103],"182":[1,11,5,95],"183":[null,8,null,8],"184":[1,8,11,141],"185":[1,8,3,133],"186":[1,8,22,471],"187":[1,8,1,94],"188":[2,8,7,145],"189":[5,8,15,217],"190":[5,8,12,87],"191":[1,8,1,36],"192":[null,9,null,9],"193":[1,9,6,201],"194":[1,9,null,101],"195":[1,9,14,375],"196":[1,9,3,134],"197":[2,9,3,115],"198":[5,9,2,108],"199":[1,9,2,81],"200":[5,9,17,305],"201":[5,9,16,435],"202":[5,9,14,303],"203":[5,9,13,149],"204":[null,9,null,9],"205":[1,9,null,2],"206":[1,9,3,96],"207":[1,9,16,170],"208":[1,9,1,120],"209":[2,9,2,116],"210":[1,9,null,41],"211":[null,11,null,11],"212":[1,11,4,110],"213":[1,11,null,106],"214":[1,11,15,280],"215":[1,11,1,139],"216":[2,11,15,198],"217":[5,11,11,210],"218":[5,11,4,121],"219":[5,11,13,338],"220":[5,11,15,191],"221":[1,11,null,36],"222":[null,13,null,13],"223":[1,13,2,95],"224":[1,13,null,103],"225":[1,13,16,375],"226":[1,13,3,111],"227":[2,13,9,136],"228":[5,13,1,130],"229":[1,13,10,116],"230":[5,13,12,372],"231":[5,13,10,144],"232":[null,12,null,12],"233":[1,12,5,124],"234":[1,12,10,185],"235":[1,12,12,544],"236":[1,12,1,177],"237":[2,12,9,287],"238":[5,12,3,71],"239":[1,12,2,60],"240":[null,15,1,15],"241":[1,15,14,229],"242":[1,15,2,124],"243":[1,15,18,673],"244":[1,15,3,140],"245":[2,15,14,399],"246":[5,15,15,198],"247":[1,15,15,121],"248":[5,15,13,203],"249":[5,15,5,138],"250":[5,15,7,120],"251":[5,15,8,155],"252":[5,15,12,186],"253":[5,15,15,150],"254":[5,15,16,254],"255":[5,15,16,459],"256":[5,15,15,305],"257":[5,15,12,252],"258":[5,15,19,326],"259":[5,15,17,392],"260":[5,15,12,196],"261":[5,15,16,273],"262":[null,8,null,8],"263":[1,8,null,52],"264":[1,8,3,120],"265":[1,8,23,923],"266":[1,8,4,141],"267":[2,8,5,147],"268":[5,8,11,138],"269":[1,8,5,69],"270":[null,6,null,6],"271":[1,6,null,10],"272":[1,6,7,95],"273":[1,6,18,272],"274":[1,6,1,81],"275":[2,6,4,110],"276":[5,6,8,58],"277":[1,6,1,33],"278":[null,9,null,9],"279":[1,9,7,160],"280":[1,9,2,153],"281":[1,9,16,156],"282":[1,9,1,132],"283":[2,9,7,208],"284":[5,9,11,184],"285":[5,9,10,136],"286":[2,9,7,125],"287":[1,9,3,59],"288":[null,13,null,13],"289":[1,13,null,5],"290":[1,13,4,146],"291":[1,13,13,212],"292":[1,13,2,73],"293":[2,13,4,131],"294":[1,13,null,36],"295":[5,13,14,296],"296":[5,13,7,139],"297":[null,10,null,10],"298":[1,10,7,95],"299":[1,10,1,80],"300":[1,10,19,177],"301":[1,10,3,111],"302":[2,10,3,124],"303":[5,10,16,212],"304":[1,10,4,57],"305":[5,10,13,253],"306":[5,10,7,106],"307":[null,12,null,12],"308":[1,12,null,58],"309":[1,12,null,120],"310":[1,12,20,310],"311":[1,12,1,122],"312":[2,12,2,176],"313":[1,12,1,78],"314":[5,12,12,192],"315":[null,11,null,11],"316":[1,11,1,31],"317":[1,11,2,140],"318":[1,11,17,617],"319":[1,11,7,140],"320":[2,11,7,186],"321":[1,11,4,71],"322":[5,11,8,96],"323":[5,11,7,71],"324":[5,11,16,226],"325":[5,11,13,207],"326":[5,11,13,188],"327":[null,10,null,10],"328":[1,10,9,122],"329":[1,10,null,115],"330":[1,10,13,366],"331":[1,10,11,141],"332":[2,10,7,152],"333":[5,10,15,220],"334":[1,10,5,56],"335":[5,10,17,231],"336":[5,10,2,94],"337":[5,10,13,149],"338":[null,8,null,8],"339":[1,8,null,33],"340":[1,8,2,178],"341":[1,8,18,530],"342":[1,8,6,235],"343":[2,8,13,286],"344":[5,8,12,165],"345":[5,8,8,139],"346":[1,8,6,96],"347":[null,13,null,13],"348":[1,13,null,109],"349":[1,13,2,161],"350":[1,13,16,589],"351":[1,13,5,170],"352":[2,13,15,230],"353":[5,13,15,306],"354":[5,13,16,93],"355":[1,13,3,70],"356":[null,13,null,13],"357":[1,13,null,29],"358":[1,13,null,180],"359":[1,13,18,498],"360":[1,13,3,274],"361":[2,13,4,318],"362":[1,13,null,71],"363":[5,13,2,96],"364":[5,13,7,89],"365":[5,13,8,317],"366":[5,13,1,81],"367":[null,14,null,14],"368":[1,14,1,76],"369":[1,14,5,159],"370":[1,14,22,329],"371":[1,14,6,158],"372":[2,14,5,138],"373":[5,14,16,325],"374":[11,14,13,148],"375":[5,14,8,170],"376":[1,14,null,72],"377":[null,12,null,12],"378":[1,12,3,51],"379":[1,12,null,108],"380":[1,12,13,180],"381":[1,12,1,87],"382":[2,12,1,100],"383":[5,12,9,166],"384":[5,12,7,195],"385":[null,8,null,8],"386":[1,8,10,190],"387":[1,8,6,220],"388":[1,8,15,255],"389":[1,8,1,80],"390":[2,8,12,407],"391":[5,8,9,253],"392":[5,8,11,180],"393":[5,8,12,117],"394":[null,6,null,6],"395":[1,6,17,305],"396":[1,6,1,139],"397":[1,6,26,552],"398":[1,6,3,163],"399":[2,6,2,171],"400":[5,6,16,223],"401":[5,6,18,344],"402":[6,6,16,299],"403":[5,6,2,226],"404":[5,6,11,175],"405":[2,6,3,115],"406":[1,6,4,89],"407":[7,6,19,286],"408":[9,6,14,172],"409":[5,6,18,255],"410":[null,9,null,9],"411":[1,9,3,66],"412":[1,9,12,134],"413":[1,9,15,464],"414":[1,9,2,96],"415":[2,9,12,234],"416":[5,9,null,53],"417":[null,12,null,12],"418":[1,12,23,308],"419":[1,12,6,133],"420":[1,12,14,258],"421":[1,12,null,56],"422":[2,12,4,184],"423":[5,12,16,309],"424":[5,12,12,168],"425":[5,12,10,175],"426":[5,12,19,293],"427":[5,12,3,151],"428":[5,12,16,278],"429":[5,12,12,155],"430":[5,12,9,91],"431":[null,10,null,10],"432":[1,10,1,51],"433":[1,10,2,106],"434":[1,10,16,299],"435":[1,10,6,192],"436":[5,10,15,150],"437":[null,8,null,8],"438":[1,8,null,87],"439":[1,8,null,83],"440":[1,8,12,210],"441":[1,8,2,118],"442":[5,8,6,115],"443":[5,8,14,222],"444":[5,8,5,178],"445":[null,6,null,6],"446":[1,6,8,141],"447":[1,6,5,122],"448":[1,6,15,423],"449":[1,6,5,137],"450":[2,6,12,158],"451":[11,6,11,155],"452":[12,6,19,138],"453":[5,6,11,117],"454":[12,6,16,212],"455":[1,6,7,79],"456":[null,10,null,10],"457":[1,10,3,72],"458":[1,10,6,148],"459":[1,10,19,570],"460":[1,10,7,143],"461":[2,10,7,259],"462":[1,10,1,86],"463":[5,10,12,285],"464":[5,10,14,282],"465":[10,10,11,287],"466":[5,10,13,191],"467":[null,11,null,11],"468":[1,11,14,183],"469":[1,11,12,143],"470":[1,11,14,281],"471":[1,11,14,120],"472":[2,11,7,160],"473":[1,11,5,80],"474":[5,11,5,134],"475":[5,11,5,116],"476":[5,11,13,147],"477":[5,11,11,196],"478":[5,11,7,98],"479":[null,9,null,9],"480":[1,9,null,47],"481":[1,9,12,117],"482":[1,9,14,128],"483":[1,9,10,153],"484":[2,9,3,116],"485":[1,9,null,37],"486":[5,9,14,211],"487":[5,9,14,193],"488":[5,9,14,185],"489":[5,9,14,214],"490":[5,9,17,406],"491":[5,9,17,354],"492":[5,9,17,368],"493":[5,9,19,367],"494":[5,9,13,218],"495":[5,9,14,195],"496":[null,14,null,14],"497":[1,14,12,306],"498":[1,14,2,120],"499":[1,14,17,432],"500":[1,14,1,171],"501":[2,14,6,213],"502":[5,14,9,152],"503":[1,14,3,54],"504":[null,10,null,10],"505":[1,10,2,51],"506":[1,10,1,148],"507":[1,10,14,451],"508":[1,10,3,151],"509":[2,10,14,185],"510":[1,10,3,52],"511":[5,10,17,251],"512":[5,10,3,106],"513":[5,10,8,193],"514":[5,10,14,150],"515":[null,11,null,11],"516":[1,11,1,89],"517":[1,11,3,196],"518":[1,11,14,281],"519":[1,11,2,94],"520":[2,11,2,138],"521":[1,11,null,47],"522":[5,11,14,229],"523":[5,11,14,238],"524":[5,11,12,222],"525":[null,12,null,12],"526":[1,12,7,166],"527":[1,12,11,180],"528":[1,12,19,320],"529":[1,12,1,164],"530":[2,12,6,315],"531":[5,12,2,133],"532":[1,12,null,71],"533":[null,9,null,9],"534":[1,9,14,231],"535":[1,9,3,154],"536":[1,9,16,712],"537":[1,9,6,236],"538":[2,9,10,332],"539":[15,9,12,402],"540":[5,9,14,250],"541":[1,9,2,120],"542":[null,10,null,10],"543":[1,10,12,278],"544":[1,10,3,180],"545":[1,10,18,643],"546":[1,10,9,179],"547":[2,10,10,245],"548":[1,10,2,106],"549":[5,10,16,770],"550":[5,10,12,298],"551":[5,10,13,329],"552":[5,10,15,212],"553":[null,10,null,10],"554":[1,10,9,111],"555":[1,10,4,172],"556":[1,10,14,618],"557":[1,10,4,196],"558":[2,10,16,265],"559":[5,10,14,118],"560":[1,10,1,85],"561":[null,11,null,11],"562":[1,11,5,92],"563":[1,11,10,170],"564":[1,11,19,435],"565":[1,11,1,146],"566":[2,11,12,238],"567":[5,11,5,104],"568":[1,11,null,68],"569":[null,9,null,9],"570":[1,9,null,61],"571":[1,9,null,138],"572":[1,9,16,610],"573":[1,9,2,193],"574":[2,9,4,224],"575":[5,9,2,127],"576":[13,9,15,156],"577":[14,9,11,179],"578":[5,9,8,157],"579":[1,9,3,87],"580":[null,10,null,10],"581":[1,10,null,8],"582":[1,10,1,124],"583":[1,10,13,612],"584":[1,10,8,219],"585":[2,10,13,419],"586":[5,10,7,135],"587":[1,10,2,90],"588":[null,11,null,11],"589":[1,11,4,71],"590":[1,11,1,173],"591":[1,11,18,480],"592":[1,11,4,286],"593":[2,11,6,250],"594":[5,11,4,95],"595":[1,11,null,67],"596":[null,12,null,12],"597":[1,12,6,84],"598":[1,12,2,160],"599":[1,12,19,629],"600":[1,12,null,189],"601":[2,12,16,271],"602":[5,12,4,200],"603":[5,12,10,134],"604":[5,12,19,125],"605":[1,12,null,85],"606":[null,13,null,13],"607":[1,13,11,296],"608":[1,13,null,206],"609":[1,13,21,771],"610":[1,13,null,213],"611":[2,13,6,300],"612":[5,13,7,206],"613":[5,13,4,75],"614":[1,13,null,76],"615":[null,15,null,15],"616":[1,15,8,123],"617":[1,15,3,174],"618":[1,15,17,525],"619":[1,15,4,206],"620":[2,15,6,206],"621":[5,15,8,102],"622":[1,15,null,74],"623":[null,9,null,9],"624":[1,9,6,205],"625":[1,9,2,224],"626":[1,9,22,648],"627":[1,9,null,273],"628":[2,9,1,240],"629":[5,9,10,212],"630":[1,9,null,46],"631":[null,9,7,178],"632":[1,9,17,214],"633":[1,9,22,617],"634":[1,9,10,157],"635":[2,9,9,263],"636":[5,9,18,145],"637":[null,9,null,9],"638":[1,9,13,199],"639":[1,9,2,100],"640":[1,9,16,701],"641":[1,9,2,176],"642":[2,9,16,326],"643":[5,9,null,121],"644":[5,9,13,172],"645":[1,9,2,52],"646":[null,9,null,9],"647":[1,9,10,77],"648":[1,9,6,133],"649":[1,9,19,490],"650":[1,9,9,245],"651":[2,9,13,226],"652":[5,9,10,83],"653":[1,9,2,49],"654":[null,8,null,8],"655":[1,8,12,101],"656":[1,8,3,159],"657":[1,8,18,640],"658":[1,8,15,227],"659":[2,8,14,208],"660":[5,8,16,183],"661":[1,8,2,56],"662":[null,10,null,10],"663":[1,10,2,37],"664":[1,10,2,184],"665":[1,10,20,491],"666":[1,10,null,165],"667":[2,10,6,187],"668":[5,10,11,190],"669":[5,10,4,152],"670":[1,10,1,63],"671":[null,6,null,6],"672":[1,6,7,125],"673":[1,6,1,149],"674":[1,6,14,687],"675":[1,6,12,204],"676":[2,6,14,328],"677":[5,6,12,156],"678":[1,6,null,52],"679":[null,11,null,11],"680":[1,11,3,59],"681":[1,11,9,172],"682":[1,11,18,368],"683":[1,11,9,209],"684":[2,11,14,246],"685":[5,11,3,98],"686":[1,11,5,57],"687":[null,11,null,11],"688":[1,11,2,124],"689":[1,11,null,203],"690":[1,11,21,674],"691":[1,11,3,362],"692":[2,11,14,400],"693":[5,11,9,98],"694":[1,11,2,87],"695":[null,14,null,14],"696":[1,14,12,222],"697":[1,14,13,174],"698":[1,14,17,687],"699":[1,14,5,198],"700":[2,14,13,248],"701":[1,14,7,97],"702":[5,14,7,249],"703":[5,14,15,171],"704":[null,15,null,15],"705":[1,15,2,40],"706":[1,15,2,157],"707":[1,15,17,553],"708":[1,15,5,196],"709":[2,15,1,252],"710":[5,15,9,85],"711":[1,15,3,110],"712":[null,11,null,11],"713":[1,11,5,108],"714":[1,11,4,185],"715":[1,11,17,424],"716":[1,11,6,230],"717":[2,11,4,205],"718":[5,11,5,149],"719":[12,11,16,155],"720":[15,11,15,177],"721":[1,11,5,91],"722":[null,10,null,10],"723":[1,10,8,99],"724":[1,10,4,223],"725":[1,10,14,559],"726":[1,10,6,214],"727":[2,10,12,269],"728":[5,10,7,104],"729":[1,10,6,96],"730":[null,12,1,12],"731":[1,12,13,146],"732":[1,12,12,174],"733":[1,12,21,598],"734":[1,12,4,178],"735":[2,12,8,224],"736":[5,12,15,146],"737":[1,12,2,115],"738":[null,11,null,11],"739":[1,11,1,73],"740":[1,11,15,280],"741":[1,11,18,633],"742":[1,11,2,215],"743":[2,11,12,476],"744":[5,11,13,172],"745":[1,11,3,98],"746":[null,12,null,12],"747":[1,12,11,116],"748":[1,12,5,153],"749":[1,12,13,603],"750":[1,12,2,167],"751":[2,12,13,343],"752":[5,12,4,70],"753":[1,12,null,60],"754":[null,9,null,9],"755":[1,9,2,84],"756":[1,9,5,204],"757":[1,9,14,525],"758":[1,9,null,237],"759":[2,9,8,302],"760":[5,9,5,136],"761":[5,9,7,158],"762":[1,9,null,74],"763":[null,10,null,10],"764":[1,10,7,175],"765":[1,10,2,216],"766":[1,10,24,497],"767":[1,10,9,224],"768":[2,10,15,268],"769":[5,10,8,116],"770":[1,10,2,62],"771":[null,16,null,16],"772":[1,16,null,5],"773":[1,16,null,150],"774":[1,16,16,346],"775":[1,16,6,140],"776":[2,16,14,237],"777":[5,16,4,79],"778":[1,16,null,88],"779":[null,12,null,12],"780":[1,12,13,107],"781":[1,12,9,127],"782":[1,12,12,474],"783":[1,12,4,138],"784":[2,12,9,228],"785":[2,12,6,97],"786":[1,12,1,61],"787":[null,13,null,13],"788":[1,13,null,40],"789":[1,13,6,219],"790":[1,13,18,649],"791":[1,13,2,185],"792":[2,13,9,336],"793":[5,13,9,256],"794":[5,13,15,206],"795":[1,13,null,76],"796":[null,13,null,13],"797":[1,13,3,125],"798":[1,13,14,298],"799":[1,13,12,421],"800":[1,13,5,188],"801":[2,13,4,185],"802":[1,13,null,102],"803":[5,13,13,241],"804":[5,13,13,210],"805":[5,13,11,179],"806":[5,13,4,104],"807":[null,13,null,13],"808":[1,13,null,81],"809":[1,13,11,284],"810":[1,13,10,279],"811":[1,13,1,143],"812":[2,13,1,164],"813":[17,13,13,219],"814":[5,13,7,113],"815":[1,13,null,119],"816":[null,12,null,12],"817":[1,12,null,60],"818":[1,12,5,216],"819":[1,12,17,600],"820":[1,12,2,215],"821":[2,12,10,307],"822":[5,12,11,91],"823":[1,12,7,117],"824":[null,11,null,11],"825":[1,11,6,271],"826":[1,11,9,354],"827":[1,11,17,1237],"828":[1,11,2,184],"829":[2,11,7,360],"830":[5,11,8,202],"831":[5,11,9,278],"832":[5,11,19,412],"833":[5,11,15,329],"834":[1,11,null,134],"835":[null,7,null,7],"836":[1,7,11,135],"837":[1,7,15,328],"838":[1,7,16,481],"839":[1,7,2,223],"840":[2,7,8,260],"841":[5,7,7,100],"842":[1,7,2,108],"843":[null,7,null,7],"844":[1,7,1,82],"845":[1,7,2,133],"846":[1,7,15,277],"847":[1,7,2,126],"848":[2,7,6,192],"849":[5,7,18,194],"850":[5,7,11,76],"851":[null,6,null,6],"852":[1,6,null,65],"853":[1,6,4,190],"854":[1,6,15,372],"855":[1,6,2,167],"856":[2,6,2,208],"857":[5,6,13,206],"858":[null,14,null,14],"859":[1,14,3,74],"860":[1,14,null,202],"861":[1,14,18,571],"862":[1,14,null,241],"863":[2,14,9,312],"864":[5,14,6,104],"865":[1,14,null,84],"866":[null,8,null,8],"867":[1,8,3,147],"868":[1,8,8,223],"869":[1,8,16,348],"870":[7,8,3,213],"871":[1,8,5,173],"872":[2,8,2,173],"873":[5,8,3,201],"874":[1,8,null,85],"875":[5,8,6,203],"876":[5,8,3,239],"877":[5,8,7,176],"878":[null,8,null,8],"879":[1,8,12,89],"880":[1,8,8,154],"881":[1,8,17,938],"882":[1,8,1,205],"883":[2,8,13,261],"884":[5,8,13,137],"885":[1,8,2,76],"886":[null,10,null,10],"887":[1,10,null,21],"888":[1,10,4,137],"889":[1,10,15,353],"890":[1,10,4,201],"891":[2,10,16,281],"892":[5,10,11,128],"893":[1,10,1,67],"894":[null,7,null,7],"895":[1,7,null,27],"896":[1,7,3,196],"897":[1,7,20,552],"898":[1,7,1,156],"899":[2,7,12,184],"900":[5,7,4,136],"901":[1,7,5,88],"902":[null,10,null,10],"903":[1,10,1,133],"904":[1,10,4,168],"905":[1,10,19,1139],"906":[1,10,8,406],"907":[2,10,12,450],"908":[5,10,7,124],"909":[5,10,13,173],"910":[1,10,7,189],"911":[null,10,null,10],"912":[1,10,7,115],"913":[1,10,11,254],"914":[1,10,21,551],"915":[1,10,4,177],"916":[2,10,17,263],"917":[5,10,13,144],"918":[1,10,1,113],"919":[null,16,null,16],"920":[1,16,4,156],"921":[1,16,5,194],"922":[1,16,13,394],"923":[1,16,12,198],"924":[2,16,12,238],"925":[1,16,8,74],"926":[11,16,13,705],"927":[5,16,7,143],"928":[null,13,null,13],"929":[1,13,5,75],"930":[1,13,9,170],"931":[1,13,14,282],"932":[1,13,3,175],"933":[2,13,6,162],"934":[5,13,2,49],"935":[1,13,1,76],"936":[null,10,2,10],"937":[1,10,null,58],"938":[1,10,12,146],"939":[1,10,17,225],"940":[1,10,null,134],"941":[2,10,4,147],"942":[1,10,6,74],"943":[null,6,null,6],"944":[1,6,2,59],"945":[1,6,2,166],"946":[1,6,16,445],"947":[1,6,null,156],"948":[2,6,4,209],"949":[5,6,9,66],"950":[1,6,1,91],"951":[null,10,null,10],"952":[1,10,3,57],"953":[1,10,7,189],"954":[1,10,17,490],"955":[1,10,6,180],"956":[2,10,5,197],"957":[1,10,6,83],"958":[5,10,2,100],"959":[5,10,9,125],"960":[5,10,17,201],"961":[null,14,null,14],"962":[1,14,2,63],"963":[1,14,15,293],"964":[1,14,15,501],"965":[1,14,8,184],"966":[2,14,16,267],"967":[5,14,8,102],"968":[1,14,8,95],"969":[null,11,null,11],"970":[1,11,null,21],"971":[1,11,5,240],"972":[1,11,13,420],"973":[1,11,4,236],"974":[2,11,8,241],"975":[5,11,9,136],"976":[1,11,1,97],"977":[null,11,null,11],"978":[1,11,null,9],"979":[1,11,16,215],"980":[1,11,18,553],"981":[1,11,11,188],"982":[2,11,14,297],"983":[5,11,16,135],"984":[1,11,11,88],"985":[null,8,null,8],"986":[1,8,4,88],"987":[1,8,12,184],"988":[1,8,13,626],"989":[1,8,2,169],"990":[2,8,3,215],"991":[5,8,16,147],"992":[1,8,2,97],"993":[null,8,null,8],"994":[1,8,1,94],"995":[1,8,10,211],"996":[1,8,13,606],"997":[1,8,5,159],"998":[2,8,7,270],"999":[5,8,11,157],"1000":[1,8,1,78],"1001":[null,10,null,10],"1002":[1,10,null,13],"1003":[1,10,4,163],"1004":[1,10,21,506],"1005":[1,10,1,150],"1006":[2,10,8,282],"1007":[5,10,null,96],"1008":[1,10,1,67],"1009":[null,9,null,9],"1010":[1,9,null,57],"1011":[1,9,null,188],"1012":[1,9,3,393],"1013":[1,9,null,113],"1014":[2,9,null,149],"1015":[null,6,null,6],"1016":[1,6,12,203],"1017":[1,6,5,309],"1018":[1,6,17,1026],"1019":[1,6,2,359],"1020":[2,6,5,408],"1021":[5,6,10,162],"1022":[1,6,2,104],"1023":[null,11,null,11],"1024":[1,11,1,75],"1025":[1,11,5,144],"1026":[1,11,18,457],"1027":[1,11,null,159],"1028":[2,11,5,208],"1029":[5,11,9,115],"1030":[1,11,3,80],"1031":[null,8,null,8],"1032":[1,8,1,40],"1033":[1,8,13,256],"1034":[1,8,19,556],"1035":[1,8,16,174],"1036":[2,8,17,198],"1037":[5,8,7,153],"1038":[1,8,4,61],"1039":[null,13,null,13],"1040":[1,13,1,66],"1041":[1,13,6,197],"1042":[1,13,14,491],"1043":[1,13,5,262],"1044":[2,13,10,257],"1045":[5,13,11,134],"1046":[1,13,null,51],"1047":[null,14,null,14],"1048":[1,14,5,95],"1049":[1,14,13,259],"1050":[1,14,17,658],"1051":[1,14,14,319],"1052":[2,14,16,329],"1053":[1,14,2,100],"1054":[5,14,10,180],"1055":[5,14,14,189],"1056":[null,13,null,13],"1057":[1,13,6,68],"1058":[1,13,14,249],"1059":[1,13,16,703],"1060":[1,13,7,185],"1061":[2,13,12,202],"1062":[5,13,13,110],"1063":[1,13,1,49],"1064":[null,9,null,9],"1065":[1,9,null,68],"1066":[1,9,19,306],"1067":[1,9,22,596],"1068":[1,9,8,205],"1069":[2,9,21,285],"1070":[5,9,5,108],"1071":[1,9,null,41],"1072":[null,13,null,13],"1073":[1,13,2,106],"1074":[1,13,22,411],"1075":[1,13,16,354],"1076":[1,13,3,153],"1077":[2,13,7,147],"1078":[5,13,11,122],"1079":[1,13,null,113],"1080":[null,13,null,13],"1081":[1,13,null,69],"1082":[1,13,8,255],"1083":[1,13,17,264],"1084":[1,13,1,141],"1085":[2,13,4,231],"1086":[5,13,3,104],"1087":[1,13,null,55],"1088":[null,10,null,10],"1089":[1,10,19,278],"1090":[1,10,10,309],"1091":[1,10,20,839],"1092":[1,10,5,288],"1093":[2,10,11,405],"1094":[1,10,12,182],"1095":[5,10,9,153],"1096":[1,10,3,92],"1097":[null,13,null,13],"1098":[1,13,null,8],"1099":[1,13,16,205],"1100":[1,13,15,439],"1101":[1,13,3,147],"1102":[2,13,4,162],"1103":[5,13,10,108],"1104":[1,13,null,69],"1105":[null,11,null,11],"1106":[1,11,null,5],"1107":[1,11,3,127],"1108":[1,11,16,292],"1109":[1,11,1,108],"1110":[2,11,2,83],"1111":[5,11,2,64],"1112":[1,11,null,59],"1113":[null,6,null,6],"1114":[1,6,1,63],"1115":[1,6,8,208],"1116":[1,6,20,772],"1117":[1,6,4,283],"1118":[2,6,3,236],"1119":[5,6,5,175],"1120":[1,6,null,65],"1121":[null,12,null,12],"1122":[1,12,1,68],"1123":[1,12,3,192],"1124":[1,12,18,595],"1125":[1,12,6,233],"1126":[2,12,1,241],"1127":[10,12,1,105],"1128":[9,12,9,129],"1129":[1,12,null,78],"1130":[null,10,null,10],"1131":[1,10,null,5],"1132":[1,10,12,202],"1133":[1,10,16,380],"1134":[1,10,1,191],"1135":[2,10,9,215],"1136":[5,10,5,73],"1137":[1,10,null,78],"1138":[null,7,null,7],"1139":[1,7,null,56],"1140":[1,7,12,223],"1141":[1,7,null,71],"1142":[1,7,1,148],"1143":[2,7,null,80],"1144":[2,7,null,117],"1145":[3,7,null,92],"1146":[5,7,1,36],"1147":[1,7,null,82],"1148":[null,8,null,8],"1149":[1,8,null,59],"1150":[1,8,8,268],"1151":[1,8,null,38],"1152":[1,8,null,115],"1153":[2,8,null,80],"1154":[2,8,2,133],"1155":[3,8,null,98],"1156":[5,8,3,75],"1157":[1,8,null,65],"1158":[null,15,null,15],"1159":[1,15,null,5],"1160":[1,15,6,158],"1161":[1,15,21,326],"1162":[1,15,5,165],"1163":[2,15,8,145],"1164":[1,15,null,40],"1165":[null,6,null,6],"1166":[1,6,null,5],"1167":[1,6,2,105],"1168":[1,6,14,453],"1169":[1,6,3,150],"1170":[2,6,5,156],"1171":[1,6,1,61],"1172":[null,15,null,15],"1173":[1,15,null,47],"1174":[1,15,7,160],"1175":[1,15,18,464],"1176":[1,15,7,185],"1177":[2,15,5,147],"1178":[2,15,6,164],"1179":[1,15,8,72],"1180":[1,15,2,54],"1181":[null,18,null,18],"1182":[1,18,1,84],"1183":[1,18,8,161],"1184":[1,18,15,556],"1185":[1,18,4,203],"1186":[2,18,4,191],"1187":[2,18,2,177],"1188":[1,18,9,103],"1189":[1,18,null,53],"1190":[null,6,4,139],"1191":[7,6,6,264],"1192":[1,6,12,752],"1193":[7,6,4,169],"1194":[3,6,4,121],"1195":[5,6,2,48],"1196":[null,5,7,171],"1197":[2,5,null,42],"1198":[6,5,null,145],"1199":[6,5,12,5067],"1200":[null,5,4,149],"1201":[9,5,15,205],"1202":[4,5,null,590],"1203":[2,5,20,3087],"1204":[null,4,2,50],"1205":[3,4,null,15],"1206":[6,4,null,108],"1207":[2,4,27,5872],"1208":[3,4,12,247],"1209":[8,4,null,128],"1210":[null,9,null,62],"1211":[4,9,14,216],"1212":[7,9,13,2598],"1213":[9,9,22,482],"1214":[7,9,22,331],"1215":[5,9,12,111],"1216":[7,9,2,269],"1217":[null,4,14,197],"1218":[8,4,3,130],"1219":[5,4,13,176],"1220":[5,4,14,279],"1221":[7,4,9,142],"1222":[6,4,16,187],"1223":[6,4,12,206],"1224":[7,4,12,123],"1225":[12,4,16,209],"1226":[8,4,6,124],"1227":[6,4,12,186],"1228":[10,4,13,294],"1229":[3,4,24,1938],"1230":[null,9,14,245],"1231":[7,9,13,484],"1232":[7,9,14,283],"1233":[7,9,14,363],"1234":[6,9,12,440],"1235":[9,9,14,270],"1236":[8,9,16,377],"1237":[4,9,24,3024],"1238":[null,8,12,234],"1239":[6,8,17,465],"1240":[7,8,12,288],"1241":[7,8,16,689],"1242":[6,8,16,452],"1243":[8,8,14,436],"1244":[6,8,13,484],"1245":[6,8,13,170],"1246":[9,8,12,221],"1247":[10,8,26,3371],"1248":[null,8,null,165],"1249":[5,8,13,360],"1250":[9,8,5,123],"1251":[8,8,12,226],"1252":[13,8,12,364],"1253":[7,8,12,296],"1254":[8,8,12,290],"1255":[6,8,10,222],"1256":[9,8,18,487],"1257":[10,8,12,319],"1258":[8,8,13,321],"1259":[7,8,25,3329],"1260":[null,7,12,312],"1261":[6,7,12,164],"1262":[11,7,14,393],"1263":[9,7,15,744],"1264":[11,7,18,401],"1265":[12,7,12,599],"1266":[7,7,13,215],"1267":[7,7,14,370],"1268":[3,7,18,212],"1269":[8,7,13,526],"1270":[8,7,24,3956],"1271":[null,2,24,1849],"1272":[null,4,12,195],"1273":[9,4,18,584],"1274":[5,4,13,470],"1275":[6,4,14,605],"1276":[8,4,12,520],"1277":[6,4,12,358],"1278":[7,4,15,733],"1279":[6,4,12,394],"1280":[6,4,13,391],"1281":[8,4,14,486],"1282":[5,4,12,306],"1283":[10,4,12,336],"1284":[8,4,12,405],"1285":[6,4,13,324],"1286":[4,4,22,7011],"1287":[null,3,15,335],"1288":[7,3,12,377],"1289":[4,3,13,514],"1290":[6,3,12,555],"1291":[8,3,12,231],"1292":[5,3,13,284],"1293":[10,3,14,398],"1294":[8,3,14,402],"1295":[9,3,14,237],"1296":[4,3,13,228],"1297":[4,3,12,591],"1298":[5,3,12,315],"1299":[6,3,13,144],"1300":[5,3,29,5991],"1301":[null,2,25,2164],"1302":[null,9,13,331],"1303":[10,9,14,209],"1304":[6,9,16,508],"1305":[13,9,13,224],"1306":[3,9,14,280],"1307":[8,9,17,471],"1308":[10,9,13,281],"1309":[4,9,25,3270],"1310":[null,9,23,1316],"1311":[null,8,28,5805],"1312":[null,5,19,1800],"1313":[null,5,12,336],"1314":[8,5,12,178],"1315":[5,5,15,432],"1316":[10,5,12,606],"1317":[9,5,14,497],"1318":[9,5,14,579],"1319":[2,5,12,124],"1320":[5,5,12,703],"1321":[4,5,12,120],"1322":[10,5,14,472],"1323":[7,5,25,5426],"1324":[null,10,35,7559],"1325":[null,5,11,225],"1326":[6,5,24,572],"1327":[11,5,14,255],"1328":[11,5,19,524],"1329":[5,5,16,173],"1330":[7,5,14,266],"1331":[9,5,16,209],"1332":[8,5,15,588],"1333":[7,5,23,645],"1334":[6,5,14,394],"1335":[9,5,13,229],"1336":[11,5,15,468],"1337":[9,5,21,555],"1338":[11,5,12,537],"1339":[6,5,32,5033],"1340":[null,7,15,251],"1341":[5,7,16,205],"1342":[6,7,13,636],"1343":[5,7,17,483],"1344":[6,7,17,470],"1345":[6,7,12,230],"1346":[8,7,12,238],"1347":[8,7,14,634],"1348":[6,7,15,382],"1349":[9,7,14,395],"1350":[6,7,22,4712],"1351":[null,6,5,205],"1352":[8,6,14,751],"1353":[8,6,19,634],"1354":[8,6,14,349],"1355":[7,6,15,556],"1356":[9,6,14,238],"1357":[10,6,14,457],"1358":[6,6,12,458],"1359":[7,6,26,7039],"1360":[null,5,9,142],"1361":[10,5,13,140],"1362":[11,5,15,484],"1363":[10,5,15,197],"1364":[12,5,12,301],"1365":[3,5,14,192],"1366":[8,5,18,628],"1367":[14,5,12,232],"1368":[9,5,12,293],"1369":[6,5,30,2856],"1370":[null,8,15,160],"1371":[7,8,12,295],"1372":[9,8,15,357],"1373":[8,8,14,440],"1374":[7,8,12,214],"1375":[12,8,13,445],"1376":[7,8,13,278],"1377":[10,8,13,251],"1378":[9,8,16,386],"1379":[10,8,18,524],"1380":[7,8,21,3438],"1381":[null,4,10,194],"1382":[10,4,12,286],"1383":[7,4,12,380],"1384":[10,4,17,402],"1385":[7,4,12,275],"1386":[9,4,14,151],"1387":[13,4,12,262],"1388":[7,4,12,241],"1389":[9,4,14,539],"1390":[7,4,12,326],"1391":[9,4,13,477],"1392":[9,4,12,314],"1393":[3,4,12,260],"1394":[5,4,12,180],"1395":[7,4,26,5766],"1396":[null,7,23,6280],"1397":[null,12,5,208],"1398":[6,12,13,393],"1399":[10,12,16,489],"1400":[10,12,15,353],"1401":[8,12,12,489],"1402":[10,12,28,3772],"1403":[null,10,12,258],"1404":[4,10,15,255],"1405":[12,10,12,453],"1406":[8,10,16,699],"1407":[8,10,13,243],"1408":[7,10,14,285],"1409":[7,10,12,408],"1410":[6,10,13,485],"1411":[5,10,15,326],"1412":[6,10,12,261],"1413":[8,10,15,383],"1414":[3,10,16,411],"1415":[5,10,29,4992],"1416":[null,9,26,2414],"1417":[null,12,22,4142],"1418":[null,4,14,255],"1419":[6,4,13,207],"1420":[11,4,12,202],"1421":[7,4,12,365],"1422":[10,4,16,358],"1423":[11,4,12,369],"1424":[8,4,13,296],"1425":[8,4,24,366],"1426":[6,4,15,181],"1427":[8,4,27,2039],"1428":[null,4,15,384],"1429":[10,4,14,555],"1430":[7,4,19,551],"1431":[8,4,13,1023],"1432":[6,4,12,505],"1433":[12,4,12,749],"1434":[3,4,16,231],"1435":[9,4,17,563],"1436":[5,4,31,9526],"1437":[4,4,21,564],"1438":[4,4,20,3654],"1439":[null,4,7,124],"1440":[5,4,2,234],"1441":[6,4,24,1310],"1442":[12,4,20,706],"1443":[8,4,21,1339],"1444":[8,4,16,538],"1445":[3,4,24,657],"1446":[4,4,20,519],"1447":[5,4,23,991],"1448":[3,4,3,102],"1449":[5,4,18,495],"1450":[6,4,12,350],"1451":[4,4,11,199],"1452":[null,4,3,175],"1453":[4,4,24,966],"1454":[4,4,21,776],"1455":[4,4,34,2297],"1456":[4,4,26,871],"1457":[5,4,15,329],"1458":[5,4,26,388],"1459":[9,4,26,680],"1460":[3,4,21,601],"1461":[6,4,13,403],"1462":[null,6,4,153],"1463":[5,6,13,279],"1464":[6,6,16,154],"1465":[7,6,22,774],"1466":[8,6,19,312],"1467":[7,6,31,3542],"1468":[3,6,17,100],"1469":[4,6,16,315],"1470":[4,6,24,283],"1471":[null,3,7,203],"1472":[7,3,28,485],"1473":[11,3,14,415],"1474":[6,3,16,889],"1475":[9,3,26,824],"1476":[8,3,20,806],"1477":[8,3,20,352],"1478":[10,3,14,410],"1479":[6,3,8,251],"1480":[8,3,12,297],"1481":[6,3,22,466],"1482":[6,3,6,263],"1483":[3,3,4,194],"1484":[4,3,13,226],"1485":[null,4,1,152],"1486":[7,4,20,647],"1487":[4,4,22,819],"1488":[5,4,19,1811],"1489":[6,4,22,1451],"1490":[5,4,21,1031],"1491":[3,4,18,435],"1492":[9,4,23,1397],"1493":[8,4,null,179],"1494":[2,4,6,128],"1495":[null,4,4,48],"1496":[3,4,22,4093],"1497":[9,4,17,224],"1498":[4,4,13,279],"1499":[4,4,4,207],"1500":[7,4,16,343],"1501":[6,4,2,24],"1502":[null,4,null,56],"1503":[9,4,14,143],"1504":[7,4,11,104],"1505":[8,4,null,60],"1506":[9,4,1,42],"1507":[10,4,6,109],"1508":[13,4,10,142],"1509":[12,4,13,113],"1510":[13,4,11,86],"1511":[4,4,null,40],"1512":[12,4,7,96],"1513":[9,4,9,119],"1514":[8,4,null,53],"1515":[6,4,2,59],"1516":[6,4,1,24],"1517":[9,4,4,141],"1518":[null,2,null,38],"1519":[2,2,null,48],"1520":[3,2,null,13],"1521":[4,2,null,69],"1522":[null,4,3,69],"1523":[3,4,4,165],"1524":[3,4,13,431],"1525":[3,4,17,410],"1526":[1,4,24,2766],"1527":[2,4,4,151],"1528":[3,4,null,53],"1529":[null,5,6,101],"1530":[5,5,null,182],"1531":[2,5,17,804],"1532":[2,5,11,400],"1533":[2,5,1,99],"1534":[3,5,18,991],"1535":[1,5,18,724],"1536":[null,3,null,154],"1537":[5,3,null,260],"1538":[3,3,3,107],"1539":[3,3,null,116],"1540":[3,3,3,109],"1541":[4,3,4,145],"1542":[4,3,null,104],"1543":[5,3,null,114],"1544":[3,3,null,165],"1545":[3,3,1,202],"1546":[4,3,null,131],"1547":[4,3,null,222],"1548":[2,3,null,176],"1549":[3,3,2,112],"1550":[3,3,2,107],"1551":[4,3,null,122],"1552":[5,3,3,106],"1553":[5,3,null,229],"1554":[3,3,null,204],"1555":[5,3,4,142],"1556":[5,3,2,143],"1557":[6,3,null,126],"1558":[4,3,2,139],"1559":[6,3,null,104],"1560":[5,3,2,123],"1561":[5,3,null,126],"1562":[5,3,null,111],"1563":[4,3,2,117],"1564":[3,3,null,90],"1565":[5,3,null,148],"1566":[5,3,null,166],"1567":[5,3,null,130],"1568":[4,3,null,143],"1569":[5,3,null,140],"1570":[5,3,3,187],"1571":[4,3,2,183],"1572":[4,3,null,282],"1573":[null,4,4,72],"1574":[3,4,2,167],"1575":[3,4,12,372],"1576":[3,4,17,419],"1577":[1,4,20,2968],"1578":[2,4,3,177],"1579":[3,4,null,53],"1580":[null,5,null,76],"1581":[5,5,null,182],"1582":[2,5,15,769],"1583":[2,5,14,375],"1584":[2,5,null,132],"1585":[3,5,17,685],"1586":[1,5,14,722],"1587":[null,4,3,62],"1588":[3,4,4,163],"1589":[3,4,15,329],"1590":[3,4,2,188],"1591":[1,4,20,2212],"1592":[2,4,null,144],"1593":[3,4,null,29],"1594":[null,5,10,106],"1595":[5,5,4,205],"1596":[2,5,18,874],"1597":[2,5,17,445],"1598":[2,5,3,129],"1599":[3,5,20,533],"1600":[1,5,16,920],"1601":[null,2,null,42],"1602":[4,2,3,123],"1603":[5,2,null,12],"1604":[4,2,null,24],"1605":[null,8,6,120],"1606":[1,8,6,60],"1607":[6,8,10,147],"1608":[11,8,null,93],"1609":[2,8,null,32],"1610":[3,8,null,37],"1611":[null,8,18,430],"1612":[4,8,7,81],"1613":[9,8,14,77],"1614":[1,8,7,70],"1615":[2,8,15,57],"1616":[1,8,2,36],"1617":[6,8,13,94],"1618":[2,8,1,63],"1619":[2,8,14,65],"1620":[null,2,null,35],"1621":[1,2,8,108],"1622":[3,2,null,17],"1623":[5,2,null,19],"1624":[2,2,null,23],"1625":[2,2,1,59],"1626":[6,2,12,233],"1627":[2,2,17,538],"1628":[null,6,null,12],"1629":[3,6,null,255],"1630":[3,6,16,937],"1631":[3,6,17,1411],"1632":[5,6,14,975],"1633":[4,6,13,147],"1634":[4,6,13,204],"1635":[4,6,15,436],"1636":[6,6,12,175],"1637":[2,6,11,323],"1638":[3,6,14,756],"1639":[4,6,16,1103],"1640":[4,6,14,866],"1641":[4,6,16,1192],"1642":[null,4,3,63],"1643":[9,4,13,160],"1644":[4,4,6,119],"1645":[4,4,7,106],"1646":[null,6,7,213],"1647":[4,6,6,114],"1648":[5,6,17,209],"1649":[8,6,20,220],"1650":[7,6,12,192],"1651":[12,6,14,683],"1652":[6,6,13,179],"1653":[9,6,21,693],"1654":[8,6,15,412],"1655":[6,6,14,102],"1656":[12,6,18,352],"1657":[2,6,14,120],"1658":[4,6,6,132],"1659":[null,4,null,106],"1660":[2,4,14,318],"1661":[4,4,12,334],"1662":[2,4,16,516],"1663":[4,4,12,316],"1664":[3,4,12,487],"1665":[3,4,15,358],"1666":[3,4,12,517],"1667":[4,4,13,465],"1668":[4,4,12,447],"1669":[6,4,12,459],"1670":[4,4,12,489],"1671":[4,4,13,560],"1672":[3,4,13,219],"1673":[5,4,8,406],"1674":[2,4,7,178],"1675":[3,4,11,319],"1676":[null,6,null,73],"1677":[6,6,17,373],"1678":[7,6,null,133],"1679":[9,6,null,88],"1680":[9,6,null,33],"1681":[null,10,4,141],"1682":[3,10,11,105],"1683":[7,10,6,167],"1684":[5,10,19,371],"1685":[7,10,19,647],"1686":[9,10,18,798],"1687":[7,10,21,203],"1688":[4,10,5,70],"1689":[9,10,13,232],"1690":[2,10,8,85],"1691":[4,10,null,51],"1692":[null,9,1,183],"1693":[3,9,12,137],"1694":[5,9,null,21],"1695":[4,9,5,110],"1696":[8,9,1,71],"1697":[5,9,17,160],"1698":[3,9,12,168],"1699":[5,9,13,162],"1700":[4,9,14,192],"1701":[4,9,15,314],"1702":[5,9,18,296],"1703":[2,9,7,109],"1704":[2,9,12,107],"1705":[4,9,1,128],"1706":[null,6,null,118],"1707":[9,6,13,312],"1708":[9,6,5,251],"1709":[9,6,1,121],"1710":[5,6,null,85],"1711":[null,8,3,52],"1712":[1,8,5,79],"1713":[2,8,null,48],"1714":[2,8,null,85],"1715":[2,8,7,50],"1716":[2,8,1,92],"1717":[null,8,3,107],"1718":[4,8,6,160],"1719":[4,8,14,269],"1720":[4,8,8,256],"1721":[4,8,4,157],"1722":[4,8,14,138],"1723":[7,8,7,129],"1724":[5,8,4,205],"1725":[4,8,null,66],"1726":[null,5,6,162],"1727":[2,5,18,731],"1728":[2,5,20,353],"1729":[4,5,13,349],"1730":[4,5,13,189],"1731":[5,5,10,192],"1732":[null,4,6,118],"1733":[2,4,7,66],"1734":[4,4,1,81],"1735":[5,4,10,140],"1736":[2,4,null,59],"1737":[1,4,1,80],"1738":[2,4,4,45],"1739":[null,8,4,126],"1740":[1,8,6,63],"1741":[6,8,14,175],"1742":[11,8,null,164],"1743":[2,8,null,32],"1744":[3,8,null,37],"1745":[null,4,null,10],"1746":[3,4,8,195],"1747":[4,4,15,272],"1748":[4,4,15,549],"1749":[4,4,16,654],"1750":[3,4,6,350],"1751":[4,4,16,168],"1752":[3,4,6,67],"1753":[7,4,10,98],"1754":[5,4,3,50],"1755":[6,4,12,235],"1756":[null,2,2,43],"1757":[1,2,8,99],"1758":[3,2,4,86],"1759":[5,2,5,116],"1760":[2,2,12,160],"1761":[2,2,18,263],"1762":[null,4,null,10],"1763":[3,4,null,148],"1764":[4,4,11,574],"1765":[3,4,21,1079],"1766":[5,4,6,78],"1767":[3,4,15,571],"1768":[3,4,null,236],"1769":[6,4,19,424],"1770":[3,4,18,210],"1771":[3,4,13,168],"1772":[4,4,7,129],"1773":[3,4,11,268],"1774":[4,4,12,140],"1775":[2,4,10,134],"1776":[6,4,13,340],"1777":[7,4,1,105],"1778":[null,5,null,45],"1779":[5,5,null,480],"1780":[null,17,null,124],"1781":[3,17,14,218],"1782":[7,17,6,134],"1783":[7,17,3,143],"1784":[10,17,null,232],"1785":[8,17,null,82],"1786":[5,17,2,80],"1787":[7,17,7,87],"1788":[null,7,null,128],"1789":[3,7,null,119],"1790":[11,7,15,354],"1791":[7,7,null,32],"1792":[3,7,3,163],"1793":[5,7,9,205],"1794":[null,14,null,134],"1795":[6,14,null,89],"1796":[6,14,null,200],"1797":[12,14,null,160],"1798":[5,14,null,56],"1799":[9,14,null,233],"1800":[7,14,null,185],"1801":[3,14,2,170],"1802":[5,14,null,219],"1803":[null,11,5,127],"1804":[8,11,3,137],"1805":[9,11,16,518],"1806":[3,11,12,208],"1807":[5,11,14,219],"1808":[null,13,4,128],"1809":[3,13,5,98],"1810":[10,13,16,599],"1811":[3,13,6,152],"1812":[5,13,15,204],"1813":[null,8,4,120],"1814":[3,8,3,121],"1815":[9,8,16,660],"1816":[3,8,1,161],"1817":[5,8,15,304],"1818":[null,12,null,111],"1819":[3,12,null,96],"1820":[6,12,4,105],"1821":[10,12,16,346],"1822":[4,12,6,134],"1823":[11,12,7,167],"1824":[7,12,7,153],"1825":[9,12,5,329],"1826":[3,12,3,227],"1827":[5,12,13,218],"1828":[null,10,10,155],"1829":[6,10,15,202],"1830":[7,10,12,169],"1831":[13,10,13,180],"1832":[8,10,1,142],"1833":[3,10,13,161],"1834":[5,10,16,202],"1835":[null,11,null,111],"1836":[3,11,null,71],"1837":[8,11,1,99],"1838":[9,11,8,127],"1839":[9,11,13,242],"1840":[6,11,14,335],"1841":[4,11,9,237],"1842":[10,11,12,264],"1843":[7,11,15,248],"1844":[7,11,13,202],"1845":[3,11,10,217],"1846":[5,11,8,163],"1847":[null,9,1,114],"1848":[3,9,3,112],"1849":[9,9,12,332],"1850":[6,9,3,44],"1851":[8,9,13,383],"1852":[3,9,6,209],"1853":[5,9,11,241],"1854":[null,11,3,176],"1855":[4,11,null,86],"1856":[7,11,13,248],"1857":[6,11,13,95],"1858":[7,11,3,154],"1859":[6,11,16,188],"1860":[4,11,3,147],"1861":[3,11,8,205],"1862":[5,11,13,221],"1863":[null,12,2,137],"1864":[3,12,2,128],"1865":[10,12,16,243],"1866":[5,12,3,118],"1867":[3,12,7,133],"1868":[5,12,9,219],"1869":[null,16,5,155],"1870":[8,16,null,111],"1871":[14,16,13,199],"1872":[8,16,8,116],"1873":[8,16,4,101],"1874":[10,16,13,247],"1875":[7,16,12,163],"1876":[3,16,9,251],"1877":[5,16,14,260],"1878":[null,8,2,98],"1879":[9,8,1,100],"1880":[6,8,11,84],"1881":[7,8,12,257],"1882":[7,8,12,159],"1883":[9,8,12,201],"1884":[7,8,6,134],"1885":[3,8,4,143],"1886":[5,8,9,215],"1887":[null,8,2,132],"1888":[3,8,2,128],"1889":[13,8,8,276],"1890":[5,8,null,92],"1891":[3,8,8,230],"1892":[5,8,7,218],"1893":[null,10,null,118],"1894":[3,10,null,121],"1895":[11,10,15,159],"1896":[8,10,7,191],"1897":[9,10,8,153],"1898":[3,10,10,326],"1899":[5,10,8,249],"1900":[null,10,1,134],"1901":[3,10,null,88],"1902":[12,10,14,355],"1903":[9,10,2,173],"1904":[3,10,3,162],"1905":[5,10,9,250],"1906":[null,9,2,132],"1907":[3,9,1,91],"1908":[5,9,12,288],"1909":[6,9,13,392],"1910":[13,9,13,238],"1911":[3,9,5,264],"1912":[5,9,9,285],"1913":[null,12,1,116],"1914":[8,12,null,82],"1915":[3,12,13,211],"1916":[4,12,16,346],"1917":[8,12,5,134],"1918":[9,12,7,197],"1919":[7,12,18,184],"1920":[7,12,14,336],"1921":[6,12,4,132],"1922":[9,12,20,506],"1923":[3,12,13,533],"1924":[5,12,13,279],"1925":[null,9,2,121],"1926":[3,9,4,99],"1927":[10,9,13,326],"1928":[6,9,4,106],"1929":[3,9,9,278],"1930":[5,9,14,233],"1931":[null,14,3,109],"1932":[6,14,12,164],"1933":[6,14,14,406],"1934":[11,14,5,209],"1935":[11,14,15,194],"1936":[9,14,12,172],"1937":[12,14,12,166],"1938":[11,14,13,123],"1939":[6,14,9,79],"1940":[3,14,5,270],"1941":[5,14,14,291],"1942":[null,11,null,188],"1943":[6,11,2,102],"1944":[8,11,12,293],"1945":[9,11,12,198],"1946":[7,11,15,286],"1947":[10,11,9,250],"1948":[7,11,7,375],"1949":[3,11,5,207],"1950":[5,11,7,368],"1951":[null,12,8,180],"1952":[6,12,4,186],"1953":[4,12,14,192],"1954":[7,12,6,161],"1955":[10,12,3,170],"1956":[10,12,5,151],"1957":[3,12,5,98],"1958":[3,12,2,188],"1959":[5,12,11,237],"1960":[null,7,9,164],"1961":[3,7,null,126],"1962":[14,7,18,264],"1963":[3,7,12,172],"1964":[3,7,7,207],"1965":[5,7,14,253],"1966":[null,10,3,160],"1967":[3,10,1,108],"1968":[10,10,15,230],"1969":[8,10,2,80],"1970":[9,10,3,241],"1971":[3,10,2,197],"1972":[5,10,11,286],"1973":[null,14,1,214],"1974":[3,14,1,137],"1975":[10,14,17,294],"1976":[7,14,11,175],"1977":[6,14,14,310],"1978":[3,14,15,264],"1979":[5,14,13,304],"1980":[null,13,1,164],"1981":[3,13,null,117],"1982":[12,13,17,493],"1983":[10,13,7,129],"1984":[3,13,4,231],"1985":[5,13,7,259],"1986":[null,11,7,151],"1987":[3,11,1,147],"1988":[11,11,15,411],"1989":[10,11,12,185],"1990":[3,11,8,171],"1991":[5,11,14,286],"1992":[null,11,3,137],"1993":[3,11,null,129],"1994":[8,11,15,420],"1995":[5,11,17,462],"1996":[3,11,9,229],"1997":[5,11,14,298],"1998":[null,10,null,201],"1999":[3,10,null,173],"2000":[16,10,17,775],"2001":[3,10,12,508],"2002":[5,10,10,403],"2003":[null,9,2,146],"2004":[1,9,11,156],"2005":[9,9,null,122],"2006":[6,9,14,244],"2007":[9,9,6,181],"2008":[9,9,16,200],"2009":[10,9,13,147],"2010":[10,9,17,389],"2011":[9,9,12,164],"2012":[8,9,13,272],"2013":[5,9,15,241],"2014":[5,9,14,236],"2015":[null,7,null,112],"2016":[5,7,7,135],"2017":[6,7,9,97],"2018":[6,7,15,103],"2019":[7,7,12,144],"2020":[11,7,8,92],"2021":[9,7,3,70],"2022":[12,7,12,121],"2023":[7,7,null,76],"2024":[8,7,17,434],"2025":[12,7,15,227],"2026":[6,7,5,77],"2027":[3,7,1,181],"2028":[5,7,15,267],"2029":[null,15,null,167],"2030":[10,15,15,256],"2031":[5,15,4,83],"2032":[11,15,17,294],"2033":[8,15,1,192],"2034":[8,15,14,305],"2035":[7,15,5,115],"2036":[9,15,9,199],"2037":[3,15,2,213],"2038":[5,15,11,274],"2039":[null,12,1,110],"2040":[6,12,1,120],"2041":[6,12,5,188],"2042":[4,12,17,224],"2043":[5,12,17,468],"2044":[11,12,17,350],"2045":[10,12,8,203],"2046":[11,12,13,277],"2047":[8,12,5,346],"2048":[3,12,5,269],"2049":[5,12,12,255],"2050":[null,14,2,142],"2051":[7,14,null,98],"2052":[6,14,5,109],"2053":[9,14,4,156],"2054":[6,14,null,72],"2055":[10,14,15,480],"2056":[9,14,14,268],"2057":[6,14,16,280],"2058":[9,14,2,127],"2059":[3,14,6,191],"2060":[5,14,10,266],"2061":[null,11,5,169],"2062":[10,11,null,130],"2063":[11,11,15,344],"2064":[9,11,13,206],"2065":[6,11,2,118],"2066":[9,11,10,260],"2067":[11,11,7,263],"2068":[3,11,5,144],"2069":[5,11,3,268],"2070":[null,17,4,209],"2071":[10,17,4,93],"2072":[6,17,12,195],"2073":[8,17,5,207],"2074":[10,17,13,198],"2075":[10,17,13,333],"2076":[8,17,12,173],"2077":[11,17,16,209],"2078":[9,17,10,169],"2079":[3,17,12,297],"2080":[5,17,14,253],"2081":[null,15,null,196],"2082":[3,15,1,137],"2083":[8,15,19,447],"2084":[5,15,1,46],"2085":[3,15,16,329],"2086":[5,15,17,299],"2087":[null,13,8,203],"2088":[1,13,7,88],"2089":[5,13,12,227],"2090":[12,13,12,124],"2091":[8,13,5,109],"2092":[8,13,13,90],"2093":[6,13,3,74],"2094":[4,13,12,107],"2095":[8,13,6,129],"2096":[10,13,14,204],"2097":[6,13,16,287],"2098":[5,13,11,297],"2099":[null,6,4,168],"2100":[6,6,13,199],"2101":[1,6,10,171],"2102":[10,6,1,44],"2103":[9,6,9,78],"2104":[10,6,11,176],"2105":[9,6,9,136],"2106":[10,6,1,121],"2107":[5,6,7,254],"2108":[5,6,10,183],"2109":[null,11,null,122],"2110":[3,11,12,185],"2111":[4,11,16,225],"2112":[5,11,5,163],"2113":[8,11,5,183],"2114":[3,11,2,183],"2115":[4,11,6,156],"2116":[null,13,9,194],"2117":[10,13,12,178],"2118":[8,13,12,239],"2119":[8,13,14,173],"2120":[10,13,9,120],"2121":[7,13,15,216],"2122":[3,13,10,199],"2123":[5,13,11,247],"2124":[null,10,1,153],"2125":[3,10,null,117],"2126":[13,10,14,530],"2127":[3,10,13,276],"2128":[5,10,12,281],"2129":[null,7,2,154],"2130":[3,7,1,138],"2131":[8,7,17,395],"2132":[3,7,10,220],"2133":[5,7,10,262],"2134":[null,10,4,163],"2135":[3,10,null,144],"2136":[10,10,19,301],"2137":[8,10,13,130],"2138":[3,10,15,316],"2139":[5,10,11,235],"2140":[null,11,1,150],"2141":[3,11,null,155],"2142":[9,11,19,417],"2143":[6,11,4,213],"2144":[11,11,1,165],"2145":[3,11,3,208],"2146":[5,11,14,282],"2147":[null,10,1,182],"2148":[3,10,3,153],"2149":[8,10,20,359],"2150":[6,10,15,465],"2151":[6,10,13,159],"2152":[3,10,10,175],"2153":[5,10,10,269],"2154":[null,9,1,201],"2155":[3,9,null,156],"2156":[9,9,34,699],"2157":[10,9,14,314],"2158":[8,9,15,500],"2159":[3,9,23,241],"2160":[5,9,14,386],"2161":[null,10,3,202],"2162":[3,10,null,164],"2163":[12,10,13,255],"2164":[7,10,13,299],"2165":[8,10,12,317],"2166":[12,10,12,424],"2167":[8,10,16,570],"2168":[3,10,9,397],"2169":[5,10,13,411],"2170":[null,13,null,163],"2171":[3,13,null,184],"2172":[12,13,9,199],"2173":[6,13,12,138],"2174":[7,13,5,248],"2175":[8,13,12,211],"2176":[7,13,14,140],"2177":[6,13,22,230],"2178":[11,13,17,151],"2179":[3,13,3,300],"2180":[5,13,15,385],"2181":[null,14,4,175],"2182":[3,14,null,180],"2183":[4,14,5,211],"2184":[9,14,9,129],"2185":[8,14,10,198],"2186":[6,14,15,236],"2187":[8,14,2,125],"2188":[5,14,14,235],"2189":[10,14,4,154],"2190":[10,14,11,221],"2191":[2,14,22,176],"2192":[3,14,6,315],"2193":[5,14,15,395],"2194":[null,10,null,192],"2195":[3,10,2,152],"2196":[11,10,13,193],"2197":[12,10,11,190],"2198":[8,10,11,238],"2199":[9,10,10,222],"2200":[9,10,6,168],"2201":[9,10,13,132],"2202":[3,10,11,292],"2203":[5,10,10,365],"2204":[null,7,null,124],"2205":[2,7,null,47],"2206":[2,7,null,117],"2207":[3,7,null,83],"2208":[7,7,null,213],"2209":[4,7,null,91],"2210":[6,7,2,144],"2211":[4,7,6,119],"2212":[null,6,null,67],"2213":[5,6,null,130],"2214":[3,6,null,129],"2215":[5,6,null,89],"2216":[3,6,null,74],"2217":[null,1,null,36],"2218":[2,1,null,80],"2219":[2,1,null,114],"2220":[2,1,null,435],"2221":[4,1,null,168],"2222":[1,1,null,13],"2223":[1,1,null,4],"2224":[1,1,null,56],"2225":[2,1,null,45],"2226":[null,3,null,79],"2227":[2,3,null,214],"2228":[3,3,null,116],"2229":[6,3,null,42],"2230":[6,3,null,369],"2231":[5,3,null,60],"2232":[3,3,null,1754],"2233":[3,3,null,57],"2234":[6,3,null,103],"2235":[6,3,null,12],"2236":[null,1,null,75],"2237":[6,1,null,12],"2238":[null,2,null,61],"2239":[3,2,null,205],"2240":[4,2,null,95],"2241":[2,2,null,32],"2242":[3,2,null,49],"2243":[4,2,null,42],"2244":[null,1,null,65],"2245":[4,1,null,56]},"averageFieldLength":[3.968844739888356,9.106411398040967,10.024084510575292,284.37266251113107],"storedFields":{"0":{"u":"/docs/adr/index.html","d":"Architecture Decision Records","k":"Architecture Decision Records","x":"Accepted ADRs explaining why the core cross-cutting patterns exist. Read these before changing a pattern they describe: they capture context and trade-offs that aren't obvious…"},"1":{"u":"/docs/adr/index.html#writing-a-new-adr","d":"Architecture Decision Records","k":"Architecture Decision Records","t":"Writing a new ADR","x":"Start from docs-src/adr/template.md in this repo (it is not rendered; the generator only picks up NNN-.md), which carries the structure every record uses: Status (Proposed /…"},"2":{"u":"/docs/adr/001-manual-dto-mapping.html","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records"},"3":{"u":"/docs/adr/001-manual-dto-mapping.html#status","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records","t":"Status","x":"Accepted. Mechanism clarified 2026-06-26: the per-entity mappers are Riok.Mapperly source-generated (compile-time), not hand-written line by line. The decision to avoid runtime…"},"4":{"u":"/docs/adr/001-manual-dto-mapping.html#context","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records","t":"Context","x":"Domain entities must be mapped to DTOs for API responses. The two common approaches are: 1. Manual mapping classes (IEntityDTOMapper ) 2. Convention-based reflection mapping…"},"5":{"u":"/docs/adr/001-manual-dto-mapping.html#decision","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records","t":"Decision","x":"Use explicit, per-entity DTO mappers (each a Riok.Mapperly [Mapper] partial class whose MapToDTO body is source-generated at compile time) registered via Scrutor assembly…"},"6":{"u":"/docs/adr/001-manual-dto-mapping.html#rationale","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records","t":"Rationale","x":"- Compile-time safety: Mapping errors surface at build time, not runtime. Property renames break the build rather than silently mapping null. - Testability: Each mapper is a…"},"7":{"u":"/docs/adr/001-manual-dto-mapping.html#trade-offs","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records","t":"Trade-offs","x":"- More files (35 DTO mappers across Store + ADC: 22 in ADC, 13 in Store, plus the parallel IEntityRequestMapper classes). The interface's default MapToDTOs implementation is…"},"8":{"u":"/docs/adr/002-navigation-populators.html","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records"},"9":{"u":"/docs/adr/002-navigation-populators.html#status","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records","t":"Status","x":"Accepted"},"10":{"u":"/docs/adr/002-navigation-populators.html#context","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records","t":"Context","x":"The application supports multiple database backends (SQL Server, Cosmos DB, SQLite). EF Core's .Include() works for SQL Server but fails for Cosmos DB cross-container…"},"11":{"u":"/docs/adr/002-navigation-populators.html#decision","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records","t":"Decision","x":"Each entity that has unsupported navigations gets a INavigationPopulator implementation. A DeclarativeNavigationPopulator base class (added in MMCA.Common) allows populators to…"},"12":{"u":"/docs/adr/002-navigation-populators.html#rationale","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records","t":"Rationale","x":"- Multi-DB support: The query pipeline automatically falls back from Include to NavigationPopulator when the data source reports navigations as unsupported. - Batch efficiency:…"},"13":{"u":"/docs/adr/002-navigation-populators.html#trade-offs","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records","t":"Trade-offs","x":"- Extra abstraction layer for SQL Server (where Include works fine). Mitigated: the populator is only called when the query pipeline's metadata says navigations are unsupported.…"},"14":{"u":"/docs/adr/003-outbox-dual-dispatch.html","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records"},"15":{"u":"/docs/adr/003-outbox-dual-dispatch.html#status","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-07-19 (integration-event routing via IMessageBus, lease-based claims for safe scale-out, dead-letter visibility, post-commit dispatch; see Revision below).…"},"16":{"u":"/docs/adr/003-outbox-dual-dispatch.html#context","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Context","x":"Domain events must be reliably published after aggregate changes are persisted. Two failure modes exist: 1. In-process dispatch fails (e.g., handler throws): the event is lost if…"},"17":{"u":"/docs/adr/003-outbox-dual-dispatch.html#decision","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Decision","x":"Use a dual-dispatch strategy: 1. Outbox persistence: Domain events are serialized into OutboxMessage rows within the same database transaction as the aggregate changes. This…"},"18":{"u":"/docs/adr/003-outbox-dual-dispatch.html#rationale","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Rationale","x":"- Guaranteed delivery: The outbox table is written atomically with the aggregate changes. Even if the process crashes after persistence, the background processor catches up. -…"},"19":{"u":"/docs/adr/003-outbox-dual-dispatch.html#trade-offs","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Trade-offs","x":"- Domain event handlers must be idempotent (this is a good practice regardless). - The outbox table grows until processed entries are cleaned up: OutboxCleanupService purges rows…"},"20":{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-07-19","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-07-19)","x":"Four changes from the 2026-07-19 full review: 1. Integration events route through the outbox to IMessageBus, never local dispatch. An IIntegrationEvent raised via AddDomainEvent…"},"21":{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-07-24","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-07-24)","x":"Three capture-side corrections found in a code review. None change the dual-dispatch decision; they close gaps between what it promised and what the interceptor did. 1. Capture…"},"22":{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-08-01","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-08-01)","x":"One retry-pacing correction. The dual-dispatch decision is unchanged; the Trade-offs above described a cadence the processor no longer has. 1. Retry backoff is explicit, and it…"},"23":{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-08-07","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"One retry-pacing refinement. The decision and the curve are unchanged; the waits are no longer identical across a batch. 1. The retry backoff carries random jitter. The…"},"24":{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-08-26","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-08-26)","x":"Five changes. The dual-dispatch decision is unchanged; what changes is what happens to a message that must not overtake its predecessor, to one that never made it out, and to the…"},"25":{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-09-07","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The dispatch model is unchanged. What changed is the name the outbox's downstream resources take by default, from the 2026-09-07 security review (SEC-Common-53).…"},"26":{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-09-11","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"The dispatch model is unchanged: same table, same claim lease, same dual dispatch, same retry and dead-letter policy. What changed is what a row carries, because the hop was…"},"27":{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-10-01","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The dual-dispatch decision is unchanged. One rule is added to it (Decision item 4), and several statements made by earlier revisions no longer describe the code. The…"},"28":{"u":"/docs/adr/004-authentication-dual-fetch.html","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records"},"29":{"u":"/docs/adr/004-authentication-dual-fetch.html#status","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Status","x":"Accepted. Updated 2026-09-03 (JwtSettings / JwtSigningAlgorithm cited at their real Auth/ paths, and the validator registration described with its current signature and…"},"30":{"u":"/docs/adr/004-authentication-dual-fetch.html#context","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Context","x":"When the modular monolith is extracted into per-module service hosts behind a gateway (ADR-008), every service must authenticate the same end-user JWT, but only one service…"},"31":{"u":"/docs/adr/004-authentication-dual-fetch.html#decision","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Decision","x":"Validate cross-service tokens with asymmetric (RS256) signatures plus JWKS / OIDC discovery, keeping the symmetric (HS256) shared-secret path as the in-process monolith option.…"},"32":{"u":"/docs/adr/004-authentication-dual-fetch.html#rationale","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Rationale","x":"- No shared signing key. Only Identity can mint tokens; every other service holds only the public key it fetched, so a compromised non-Identity service cannot forge tokens, and…"},"33":{"u":"/docs/adr/004-authentication-dual-fetch.html#trade-offs","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Trade-offs","x":"- More moving parts than a shared secret. RS256 needs key generation, distribution of the public half, a JWKS endpoint, and discovery wiring, versus one symmetric string. -…"},"34":{"u":"/docs/adr/004-authentication-dual-fetch.html#revision-2026-10-01","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Anchor-only refresh; no decision or rationale changed. The AddForwardedJwtBearer registration lives in the partial file…"},"35":{"u":"/docs/adr/004-authentication-dual-fetch.html#related","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Related","x":"ADR-007 (gRPC calls forward the validated JWT downstream via JwtForwardingClientInterceptor), ADR-008 (the extraction that split issuer and validator into separate processes),…"},"36":{"u":"/docs/adr/005-soft-delete-vs-erasure.html","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records"},"37":{"u":"/docs/adr/005-soft-delete-vs-erasure.html#status","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Status","x":"Accepted. Updated 2026-06-27 (documented the [Pii] → IAnonymizable build-time guard and PiiRedactor). Updated 2026-08-26 (DeletedOn/DeletedBy stamps, the DeleteChildren cascade…"},"38":{"u":"/docs/adr/005-soft-delete-vs-erasure.html#context","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Context","x":"The framework's default deletion model is soft-delete: AuditableBaseEntity.Delete() sets IsDeleted = true and EF Core global query filters exclude the row from normal queries.…"},"39":{"u":"/docs/adr/005-soft-delete-vs-erasure.html#decision","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Decision","x":"Separate the two concerns and provide an extension point for each, rather than overloading soft-delete: 1. Soft-delete stays the default for lifecycle/state management (hide +…"},"40":{"u":"/docs/adr/005-soft-delete-vs-erasure.html#rationale","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Rationale","x":"- Right tool per concern: soft-delete answers \"is this record active?\"; erasure answers \"has this person's data been removed?\". Conflating them (e.g. hard-deleting inside…"},"41":{"u":"/docs/adr/005-soft-delete-vs-erasure.html#trade-offs","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Trade-offs","x":"- Erasure is opt-in per entity, but the opt-in is guarded: an aggregate exposing a [Pii]-marked property that does not implement IAnonymizable fails the architecture fitness…"},"42":{"u":"/docs/adr/005-soft-delete-vs-erasure.html#revision-2026-09-07","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Anonymization keeps the row and its foreign keys: Anonymize() overwrites the personal fields and leaves the entity's relationships intact, because the aggregate still has to hang…"},"43":{"u":"/docs/adr/005-soft-delete-vs-erasure.html#revision-2026-10-01","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision changed. One description in Decision 1 was corrected: it said composing Result.Combine(DeleteChildren (lines), base.Delete()) aborts the cascade \"before the root is…"},"44":{"u":"/docs/adr/006-database-per-service.html","d":"ADR-006: Database per Service","k":"Architecture Decision Records"},"45":{"u":"/docs/adr/006-database-per-service.html#status","d":"ADR-006: Database per Service","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-07). Supersedes the earlier \"deliberately one shared database\" stance. Clarified 2026-06-27: the single context class became one sealed context class per engine…"},"46":{"u":"/docs/adr/006-database-per-service.html#context","d":"ADR-006: Database per Service","k":"Architecture Decision Records","t":"Context","x":"When the modules were first extracted into independently-deployable services, all services in an app still pointed at a single shared SQL database with a single OutboxMessages…"},"47":{"u":"/docs/adr/006-database-per-service.html#decision","d":"ADR-006: Database per Service","k":"Architecture Decision Records","t":"Decision","x":"Adopt database-per-service: each service owns its own physical database with its own OutboxMessages table. - One sealed concrete context class per engine, one instance per…"},"48":{"u":"/docs/adr/006-database-per-service.html#rationale","d":"ADR-006: Database per Service","k":"Architecture Decision Records","t":"Rationale","x":"- Removes the shared-outbox race (the sharpest cost of the shared DB) without an OriginService filter: physical isolation is simpler and stronger than a logical filter. - Real…"},"49":{"u":"/docs/adr/006-database-per-service.html#trade-offs","d":"ADR-006: Database per Service","k":"Architecture Decision Records","t":"Trade-offs","x":"- No cross-database FKs or transactions. Relationships that span services degrade to scalar IDs; consistency across services is eventual (outbox + broker), not transactional. -…"},"50":{"u":"/docs/adr/007-grpc-extraction.html","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records"},"51":{"u":"/docs/adr/007-grpc-extraction.html#status","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-08-23 (the [ServiceContract] marker now has a dedicated fitness rule behind it, ServiceContractPurityTestsBase, subclassed in all four repos; it is a…"},"52":{"u":"/docs/adr/007-grpc-extraction.html#context","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records","t":"Context","x":"Once modules became separate service processes, the in-process interface calls between them (e.g. Conference → Engagement's IBookmarkCountService, Engagement → Conference's…"},"53":{"u":"/docs/adr/007-grpc-extraction.html#decision","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records","t":"Decision","x":"Use gRPC, exposed through MMCA.Common.Grpc, with a contract-package convention: - .Contracts projects hold the .proto definitions plus a gRPC adapter that implements the same…"},"54":{"u":"/docs/adr/007-grpc-extraction.html#rationale","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records","t":"Rationale","x":"- No business-logic rewrite: the gRPC adapter implements the interface modules already depend on; swapping in-process for cross-process is a registration change. - Transport…"},"55":{"u":"/docs/adr/007-grpc-extraction.html#trade-offs","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records","t":"Trade-offs","x":"- Bidirectional pairs need care. Conference ↔ Engagement is a mutual gRPC pair; the AppHost deliberately omits a reciprocal WaitFor to avoid a startup deadlock: transient \"peer…"},"56":{"u":"/docs/adr/008-service-extraction-topology.html","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records"},"57":{"u":"/docs/adr/008-service-extraction-topology.html#status","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Status","x":"Accepted. Amended by ADR-089 (2026-08-18): the Gateway keeps the route-to-service map this record gave it, but stops expressing it as MapForwarder calls in code. YARP…"},"58":{"u":"/docs/adr/008-service-extraction-topology.html#context","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Context","x":"ADC began as a modular monolith: one MMCA.ADC.WebAPI host loaded every module (Identity, Conference, Engagement, Notification) in-process via the ModuleLoader, sharing one…"},"59":{"u":"/docs/adr/008-service-extraction-topology.html#decision","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Decision","x":"Extract one service host per module: MMCA.ADC.{Identity,Conference,Engagement,Notification}.Service and front them with a single YARP reverse-proxy Gateway (MMCA.ADC.Gateway,…"},"60":{"u":"/docs/adr/008-service-extraction-topology.html#rationale","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Rationale","x":"- No business-logic rewrite. Because a service is just the monolith with one module enabled, extraction was a hosting/wiring change, not a domain change, and the module-isolation…"},"61":{"u":"/docs/adr/008-service-extraction-topology.html#trade-offs","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Trade-offs","x":"- Operational complexity. Four deployables plus a Gateway, service discovery, a broker, and per-service databases, versus one process. Mitigated locally by Aspire orchestration…"},"62":{"u":"/docs/adr/008-service-extraction-topology.html#applicability","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Applicability","x":"This ADR is framed around ADC (the first repo extracted), but the same topology is now the framework's standard extraction shape, not an ADC-only choice. MMCA.Store followed it:…"},"63":{"u":"/docs/adr/008-service-extraction-topology.html#revision-2026-10-01","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Three statements are corrected against the code; the topology itself is unchanged. The extracted service hosts do not rely on Disabled stubs for their peers: each passes only its…"},"64":{"u":"/docs/adr/008-service-extraction-topology.html#related","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (outbox dual dispatch), ADR-004 (cross-service token validation via JWKS), ADR-006 (database per service), and ADR-007 (gRPC cross-service calls) are the facet decisions…"},"65":{"u":"/docs/adr/009-resilience-and-recovery-objectives.html","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records"},"66":{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#status","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-14). Amended by ADR-087 (2026-08-18): the resilience objective extends past outbound HTTP and gRPC clients for the first time, to the outbox's broker publish,…"},"67":{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#context","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Context","x":"The framework already supplies the mechanisms for surviving partial failure: a standard Polly resilience handler (timeout / retry / circuit breaker), the outbox for at-least-once…"},"68":{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#decision","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Decision","x":"1. Resilience is a framework invariant, not a per-call choice. Every outbound HttpClient and gRPC client registered through the framework's extension methods (AddTypedGrpcClient,…"},"69":{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#rationale","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Rationale","x":"- Invariant over discipline. A fitness function turns \"remember to add resilience\" into a build gate: the same approach the framework already uses for the layer rules and the…"},"70":{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#trade-offs","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Trade-offs","x":"- The named gate (ResilienceHandlerTests, MMCA.Common.Grpc.Tests) asserts every option the gRPC client path (AddTypedGrpcClient) configures…"},"71":{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#revision-2026-08-18","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"This record's first Decision point scoped resilience to \"every outbound HttpClient and gRPC client registered through the framework's extension methods\". That scope was accurate…"},"72":{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#revision-2026-09-11","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"Two amendments, both about parts of this posture the record could not previously see or reach. (a) The standard handler is now observable. Decision point 1 makes the resilience…"},"73":{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#revision-2026-09-19","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Revision (2026-09-19)","x":"That consumer-side companion is now in MMCA.Store's main. StripeClientFactory builds the SDK client over an explicitly bounded HTTP stack instead of Stripe.net's defaults, and…"},"74":{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#revision-2026-10-01","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changes; the current-state sections are brought back in line with the code. Decision point 1 no longer says the gRPC client matches the global HTTP…"},"75":{"u":"/docs/adr/010-integration-event-schema-versioning.html","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records"},"76":{"u":"/docs/adr/010-integration-event-schema-versioning.html#status","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-19). Updated 2026-06-27 (Helpdesk enforcement gap closed; all three consumers now gate the convention). Updated 2026-08-14 (ADC now gates seven events, and a…"},"77":{"u":"/docs/adr/010-integration-event-schema-versioning.html#context","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Context","x":"Integration events cross service boundaries (Identity → Conference, Conference ↔ Engagement, …) and are resolved by consumers solely by their type string: the outbox serializes…"},"78":{"u":"/docs/adr/010-integration-event-schema-versioning.html#decision","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Decision","x":"1. Every integration event carries an explicit SchemaVersion. BaseIntegrationEvent exposes public virtual int SchemaVersion = 1;. It is serialized with the payload…"},"79":{"u":"/docs/adr/010-integration-event-schema-versioning.html#rationale","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Rationale","x":"- A signal, enforced. A version field plus a build-gating convention test turns \"remember the contract\" into something the tooling checks: the same invariant-over-discipline…"},"80":{"u":"/docs/adr/010-integration-event-schema-versioning.html#trade-offs","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Trade-offs","x":"- SchemaVersion is a signal, not a mechanism: by itself it does not stop a consumer breaking on a real reshape. The load-bearing half is the discipline (new type + upcaster). At…"},"81":{"u":"/docs/adr/010-integration-event-schema-versioning.html#amendment-2026-09-11-integration-event-payload-purity","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Amendment (2026-09-11): integration-event payload purity","x":"Versioning answers \"how may this contract change\". It does not answer \"what may be in it in the first place\", and that gap is where the more expensive failure lives. An event…"},"82":{"u":"/docs/adr/010-integration-event-schema-versioning.html#revision-2026-10-01","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; two statements and one citation were corrected against source. The amendment claimed both purity rules are vacuous for a module-less map; only…"},"83":{"u":"/docs/adr/011-single-locale-i18n.html","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records"},"84":{"u":"/docs/adr/011-single-locale-i18n.html#status","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records","t":"Status","x":"Superseded by ADR-027 (2026-06-27). Originally Accepted (2026-06-19). The \"if multi-locale is ever required\" scope below is the blueprint ADR-027 implements; this record is…"},"85":{"u":"/docs/adr/011-single-locale-i18n.html#context","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records","t":"Context","x":"The MMCA applications (the ADC conference app, the Store) and the MMCA.Common.UI library currently ship a single locale (en-US). The architecture rubric scores…"},"86":{"u":"/docs/adr/011-single-locale-i18n.html#decision","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records","t":"Decision","x":"1. Single-locale (en-US) is an explicit non-goal for now. User-facing strings are inline in markup; dates/numbers use invariant or fixed formatting where appropriate. 2. The…"},"87":{"u":"/docs/adr/011-single-locale-i18n.html#rationale","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records","t":"Rationale","x":"- Recording the decision converts an implicit rubric-zero into a conscious, revisitable choice: the same posture as the single-region DR acceptance in ADR-009. - Premature i18n…"},"88":{"u":"/docs/adr/011-single-locale-i18n.html#trade-offs","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Adding a locale later touches every view plus the formatting paths: a real but bounded effort, accepted. - Hard-coded strings make a future extraction larger; mitigated by the…"},"89":{"u":"/docs/adr/012-grpc-host-transport.html","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records"},"90":{"u":"/docs/adr/012-grpc-host-transport.html#status","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Status","x":"Accepted (re-verified against source 2026-09-03). Revised 2026-09-07 (the rate-limit and HTTPS-redirect exemptions that used to key on a caller-set Content-Type: application/grpc…"},"91":{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-06-22-store-converged-to-profile-a","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-06-22): Store converged to Profile A","x":"Store originally chose Profile B, but its cross-service gRPC failed in Azure Container Apps. With Http1AndHttp2 Kestrel + transport: 'auto' ingress on a cleartext endpoint there…"},"92":{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-07-09-adc-notification-adds-a-mixed-endpoint-profile-per-endpoint-protocols","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-07-09): ADC Notification adds a mixed-endpoint profile (per-endpoint protocols)","x":"The live-channel push pipeline (ADR-039) gave ADC's Notification service an inbound cleartext gRPC server (LiveChannelPushService.PushToChannel, called best-effort by Engagement…"},"93":{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-07-25-probe-listeners-are-adcs-answer-not-tcp-probes-and-gateway-routed-jwks-is-a-local-only-rule","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-07-25): probe listeners are ADC's answer, not TCP probes; and gateway-routed JWKS is a local-only rule","x":"Two claims above were written from an earlier state of the code and no longer describe either app. 1. ADC probes never touch the traffic endpoint; TCP probes were then…"},"94":{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-07-28-the-probe-listener-is-the-single-pattern-in-both-apps-no-tcp-probes-anywhere","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-07-28): the probe listener is the single pattern in both apps (no TCP probes anywhere)","x":"Store PR 55 (commit 297064bb, merged 2026-07-27) ported ADC's dedicated probe listener to Store, so the Store-only tcpSocket exception recorded in the 2026-07-25 update above is…"},"95":{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-08-07-the-probe-listener-moved-into-mmcacommon-and-notifications-grpc-endpoint-carries-a-second-service","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-08-07): the probe listener moved into MMCA.Common, and Notification's gRPC endpoint carries a second service","x":"1. One shared framework method, not a per-service file. The KestrelConfiguration.cs copies the two updates above cite no longer exist in either app. The pattern was extracted…"},"96":{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-08-14-stores-sales-runs-the-mixed-endpoint-profile-too-so-no-pure-profile-b-host-remains","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-08-14): Store's Sales runs the mixed-endpoint profile too, so no pure Profile B host remains","x":"Sales gained an inbound gRPC edge of its own (IUserSalesExportService, the Identity-driven data-subject export), and it resolved that the same way ADC's Notification did: not by…"},"97":{"u":"/docs/adr/012-grpc-host-transport.html#context","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Context","x":"Once modules were extracted into separate service hosts (ADR-008) that call each other synchronously over gRPC (ADR-007), each service's Kestrel had to serve both REST traffic…"},"98":{"u":"/docs/adr/012-grpc-host-transport.html#decision","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Decision","x":"Pick one of two coherent transport profiles per app, and wire the gateway forwarder and JWKS discovery to match. Use when services must serve gRPC on cleartext (any bidirectional…"},"99":{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-08-29-aspire-local-health-gating-for-http2-only-endpoints-and-the-downstream-probe-negotiates-its-version","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-08-29): Aspire-local health gating for Http2-only endpoints, and the downstream probe negotiates its version","x":"Two v1.167.0 framework additions close the last two operational gaps this profile family carried. 1. Profile A resources are now health-gated locally. Aspire's stock…"},"100":{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-08-29-east-west-grpc-clients-state-their-own-circuit-breaker-v11680","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-08-29): east-west gRPC clients state their own circuit breaker (v1.168.0)","x":"The health checks above are the gateway's view of a downstream. An east-west call (AddTypedGrpcClient, MMCA.Common/Source/Presentation/MMCA.Common.Grpc/DependencyInjection.cs)…"},"101":{"u":"/docs/adr/012-grpc-host-transport.html#rationale","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Rationale","x":"- The Kestrel protocol choice is the root constraint; the gateway-forward mode and the JWKS authority are downstream consequences, not independent knobs. Documenting them as a…"},"102":{"u":"/docs/adr/012-grpc-host-transport.html#trade-offs","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Two profiles to keep straight. A service that gains an inbound gRPC edge must migrate from Profile B to Profile A and move its gateway cluster onto the HTTP/2 exact version…"},"103":{"u":"/docs/adr/012-grpc-host-transport.html#revision-2026-09-07","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The transport profiles are unchanged. What changed is how the rest of the pipeline recognises an h2c gRPC request, from the 2026-09-07 security review (SEC-Common-44). Two…"},"104":{"u":"/docs/adr/012-grpc-host-transport.html#revision-2026-10-01","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed: both apps still run Profile A on their gRPC-serving hosts and the mixed-endpoint profile on ADC Notification and Store Sales. This pass…"},"105":{"u":"/docs/adr/012-grpc-host-transport.html#related","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Related","x":"- ADR-004 (cross-service token validation via JWKS / OIDC discovery), ADR-007 (gRPC cross-service calls), ADR-008 (monolith → services + gateway topology), ADR-039 (live-channel…"},"106":{"u":"/docs/adr/013-result-pattern.html","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records"},"107":{"u":"/docs/adr/013-result-pattern.html#status","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-07-21 (exception-handler chain / ProblemDetails edge contract documented). Revised 2026-08-26 (ErrorType.Unexpected, severity-ranked status selection for…"},"108":{"u":"/docs/adr/013-result-pattern.html#context","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Context","x":"Operations at every layer fail in expected ways: input is invalid, a domain invariant is broken, a requested entity is missing, a uniqueness conflict occurs, the caller lacks…"},"109":{"u":"/docs/adr/013-result-pattern.html#decision","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Decision","x":"Model expected failures as values using Result / Result (MMCA.Common.Shared.Abstractions), not exceptions. - A Result is either success or failure; a failure carries one or more…"},"110":{"u":"/docs/adr/013-result-pattern.html#rationale","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Rationale","x":"- Failures are in the signature. A method that can fail returns Result , so the caller cannot silently ignore the failure path the way an uncaught exception allows. - Category,…"},"111":{"u":"/docs/adr/013-result-pattern.html#trade-offs","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Trade-offs","x":"- More ceremony at call sites than letting an exception bubble; the combinators absorb most of it. - Two error channels coexist (Result for expected, exceptions for exceptional).…"},"112":{"u":"/docs/adr/013-result-pattern.html#revision-2026-10-01","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Citations were re-anchored to the current source after line shifts: the gRPC status table, ToRpcException ranking and trailer writes in…"},"113":{"u":"/docs/adr/013-result-pattern.html#related","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Related","x":"ADR-007 (Result over the wire via gRPC, the second edge the shared severity ranking now serves), ADR-014 (the decorator pipeline returns Result.Failure to short-circuit a command…"},"114":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records"},"115":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#status","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-07-19 (Transactional semantics: rollback on business failure + post-commit event dispatch; see Revision below). Revised 2026-08-18 (the pipeline order…"},"116":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#context","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Context","x":"Commands and queries share cross-cutting concerns: validation, transactions, cache invalidation, logging / timing, and feature gating. Putting that logic inside each handler…"},"117":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#decision","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Decision","x":"Use single-responsibility handlers behind a Scrutor-composed decorator pipeline. - ICommandHandler and IQueryHandler (MMCA.Common.Application) are one handler per use case, each…"},"118":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#rationale","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Rationale","x":"- Thin, testable handlers. A handler has no transaction, logging, or caching plumbing, so it is unit-tested in isolation. - One place to read and change the pipeline. The order…"},"119":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#trade-offs","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Trade-offs","x":"- Registration order is the reverse of execution order (a Scrutor foot-gun), mitigated by the inline ordering comments in AddApplicationDecorators(). - Decorators must be…"},"120":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#revision-2026-07-19","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Revision (2026-07-19)","x":"Two Transactional-decorator semantics changed with the 2026-07-19 full review: - A returned business failure now rolls the transaction back. Previously a handler returning…"},"121":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#revision-2026-08-18","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Two decorators were added to both chains, so the order recorded in the Decision above is no longer the shipped one. The registration site is unchanged in kind:…"},"122":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#revision-2026-08-26","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Revision (2026-08-26)","x":"One decorator joins the query chain, and the registration sequence stops being a rule consumers have to remember. The query line in the Revision (2026-08-18) above is superseded;…"},"123":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#revision-2026-09-07","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"One decorator's key composition changed (SEC-Common-19 / SEC-Common-37). CachingQueryDecorator builds its entry key as TenantCacheKey.Scope(tenantContext,…"},"124":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#related","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Related","x":"ADR-013 (Result, the short-circuit currency of the pipeline, and the Failure error type the timeout decorator reuses because the taxonomy has no timeout member), ADR-003…"},"125":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#revision-2026-09-11","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"One decorator behaviour changed on both chains, and it is a logging decision rather than a pipeline one: the order, the markers, the sealing rule and the short-circuit currency…"},"126":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#revision-2026-09-19","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Revision (2026-09-19)","x":"One decorator pair changed inside, and the pipeline around it did not: the order is still FeatureGate - Authorization - Logging - Caching - Validating - Timeout - Transactional -…"},"127":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#revision-2026-09-25","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Revision (2026-09-25)","x":"The consumer fitness tests changed, not the pipeline. ADC's DecoratorPipelineOrderTests now builds its registration sequence through AddMmcaApplicationPipeline, the helper its…"},"128":{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#revision-2026-10-01","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The Decision gains one bullet stating where each kind of validation lives, so the Validating decorator's role is read against its neighbours rather than in isolation. The chain…"},"129":{"u":"/docs/adr/015-architecture-fitness-functions.html","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records"},"130":{"u":"/docs/adr/015-architecture-fitness-functions.html#status","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-08-18 (two new rule families, namespace dependency cycles and trailing CancellationToken declarations, plus a third enforcement layer: a compile-time…"},"131":{"u":"/docs/adr/015-architecture-fitness-functions.html#context","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Context","x":"The codebase rests on invariants that are easy to state and easy to erode by accident: clean- architecture layer flow (Domain depends on nothing above it), module isolation (no…"},"132":{"u":"/docs/adr/015-architecture-fitness-functions.html#decision","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Decision","x":"Enforce architectural invariants as automated checks that gate the build, in two layers (a third joined them on 2026-08-18: see the Revision at the end). 1. Compile-time guard.…"},"133":{"u":"/docs/adr/015-architecture-fitness-functions.html#rationale","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Rationale","x":"- Invariant over discipline. Turning \"do not do X\" into a red build is the only enforcement that scales. It is the same lever used by the layer rules, the resilience gate…"},"134":{"u":"/docs/adr/015-architecture-fitness-functions.html#trade-offs","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Trade-offs","x":"- The tests assert structure / registration, not runtime behavior. ADR-009's test proves a client wires resilience, not that its policy values are correct; parameter tuning stays…"},"135":{"u":"/docs/adr/015-architecture-fitness-functions.html#revision-2026-08-18","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Two new rule families joined the shared library, and a third enforcement layer joined the two the Decision above describes. The counts in MMCA.Common/FACTS.md move with them: 102…"},"136":{"u":"/docs/adr/015-architecture-fitness-functions.html#revision-2026-08-18-section-b-rule-families","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-08-18): Section B rule families","x":"A second entry on the same date, kept separate rather than folded into the one above because it lands with a different wave and changes a number that revision states. Two rule…"},"137":{"u":"/docs/adr/015-architecture-fitness-functions.html#revision-2026-08-23-superseded-counts-a-re-anchored-citation-and-the-real-test-floor","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-08-23): superseded counts, a re-anchored citation, and the real test floor","x":"No rule family joined or left the library in this entry. It corrects three things the two 2026-08-18 revisions above state, and it is kept as its own entry rather than edited…"},"138":{"u":"/docs/adr/015-architecture-fitness-functions.html#revision-2026-09-01-the-public-api-gates-real-coverage-and-re-anchored-citations","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-09-01): the public API gate's real coverage, and re-anchored citations","x":"No rule family joined or left the library in this entry and no decision changed. It corrects the public-API figures the first 2026-08-18 revision recorded, which had gone stale…"},"139":{"u":"/docs/adr/015-architecture-fitness-functions.html#revision-2026-09-03-the-accepted-cycles-middle-node-the-feature-folder-move-re-anchored-citations","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-09-03): the accepted cycle's middle node, the feature-folder move, re-anchored citations","x":"No rule family joined or left the library in this entry and no decision changed. One accepted allowance changed shape, the files that hold the rules and the tests moved, and the…"},"140":{"u":"/docs/adr/015-architecture-fitness-functions.html#revision-2026-09-11-the-restated-counts-come-out-six-citations-move","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-09-11): the restated counts come out, six citations move","x":"No rule family joined or left the library in this entry and no decision changed. It removes the one kind of number this record keeps failing to keep current and re-anchors the…"},"141":{"u":"/docs/adr/015-architecture-fitness-functions.html#revision-2026-09-19-the-gate-covers-eighteen-projects-and-the-unshipped-side-nearly-doubled","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-09-19): the gate covers eighteen projects, and the unshipped side nearly doubled","x":"No rule family joined or left the library in this entry and no decision changed. It corrects the public-API gate's file and declaration figures, which had gone stale in the…"},"142":{"u":"/docs/adr/015-architecture-fitness-functions.html#revision-2026-10-01-rs0026--rs0027-become-errors-the-baselines-roll-over-citations-move","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-10-01): RS0026 / RS0027 become errors, the baselines roll over, citations move","x":"No rule family joined or left the library in this entry. One public API gate setting changed, the baseline figures moved with the v1.216.0 release (MMCA.Common/FACTS.md:14), and…"},"143":{"u":"/docs/adr/015-architecture-fitness-functions.html#related","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Related","x":"ADR-009 (resilience gate), ADR-010 (event-version gate), ADR-016 (MassTransit pin gate, and the lockstep release cadence the public API baseline is pinned to), ADR-006/007/008…"},"144":{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records"},"145":{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#status","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-15). Amended (2026-07-28): the fitness function now gates two commercial-license majors (MassTransit and SixLabors.ImageSharp), so the decision is restated as…"},"146":{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#context","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Context","x":"MMCA.Common publishes its MMCA.Common. NuGet package set (see FACTS.md for the authoritative list and count) consumed by three downstream repos: the two production apps (Store,…"},"147":{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#decision","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Decision","x":"1. Version the whole MMCA.Common. package set in lockstep. All packages share one version (MinVer, derived from a single vX.Y.Z git tag); a release tags every package (see…"},"148":{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#rationale","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Rationale","x":"- One version, one compatibility story. Lockstep removes the N-package matrix: \"everything on vX.Y.Z\" is the only supported combination, which is the right trade for a small…"},"149":{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#trade-offs","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Trade-offs","x":"- A consumer cannot adopt a single package in isolation: it takes the whole set at the new version. - Lockstep will bump a package whose code did not change (acceptable: the…"},"150":{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#transport-exit-options","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Transport exit options","x":"The pin has a horizon. MassTransit v8 is the free major and its community support ends at the end of 2026 (a maintainer statement about the package, not something this repository…"},"151":{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#alternatives-rejected","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Alternatives rejected","x":"- Opt-in flags and compatibility shims for a framework change, with consumers upgraded per page or per PR. Declined outright: no virtual opt-in properties, no parallel old and…"},"152":{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#revision-2026-10-01","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision, pin, gate or rationale changed: MassTransit stays below major 9 and ImageSharp below major 4, and every repo still sits on one MassTransit patch. The current-state…"},"153":{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#related","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (the fitness function that enforces the pins), ADR-003 / ADR-006 (MassTransit is the broker transport behind the outbox and database-per-service flows). ADR-118 revisits…"},"154":{"u":"/docs/adr/017-request-idempotency.html","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records"},"155":{"u":"/docs/adr/017-request-idempotency.html#status","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-08-01: the guard around execute-and-store is now an IDistributedLock resolved from DI (Redis-backed wherever a connection multiplexer is registered, which…"},"156":{"u":"/docs/adr/017-request-idempotency.html#context","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Context","x":"Write endpoints (POST / PUT / PATCH) are exposed to client retries and double-submits: a flaky network, an impatient user double-clicking, or a resilience pipeline re-issuing a…"},"157":{"u":"/docs/adr/017-request-idempotency.html#decision","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Decision","x":"Provide opt-in, client-driven request idempotency as an MVC action filter in MMCA.Common.API. - Opt-in per action. [Idempotent] (IdempotentAttribute, a ServiceFilterAttribute…"},"158":{"u":"/docs/adr/017-request-idempotency.html#rationale","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Rationale","x":"- Safety at the edge, not in every handler. Deduplication lives in one filter, so a handler stays a thin use case (ADR-014) and does not grow ad-hoc \"did I already do this?\"…"},"159":{"u":"/docs/adr/017-request-idempotency.html#trade-offs","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Trade-offs","x":"- Cross-instance mutual exclusion follows Redis, so it is a deployment property, not a guarantee. Every ADC and Store service host registers a Redis IConnectionMultiplexer when a…"},"160":{"u":"/docs/adr/017-request-idempotency.html#revision-2026-08-18","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"The last Trade-off above is the one this revision addresses, and it does so by changing what is required. Nothing here makes an endpoint idempotent. What it requires is that…"},"161":{"u":"/docs/adr/017-request-idempotency.html#revision-2026-10-01","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The Decision and Trade-offs are corrected to the filter as it runs today. Four statements had drifted. The cache-key subject is FindUserIdValue() (sub, then…"},"162":{"u":"/docs/adr/017-request-idempotency.html#related","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (handler idempotency for outbox/event consumers, a distinct concern), ADR-013 (Result is the response the filter caches/replays), ADR-014 (the filter keeps the handler…"},"163":{"u":"/docs/adr/018-polyglot-persistence.html","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records"},"164":{"u":"/docs/adr/018-polyglot-persistence.html#status","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Status","x":"Accepted. The framework plumbing is complete, covered by unit and integration tests (DataSourceResolverTests, CrossDataSourceDegradeConventionTests, EntityTypeConfigurationTests,…"},"165":{"u":"/docs/adr/018-polyglot-persistence.html#context","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Context","x":"ADR-006 (database-per-service) splits storage along the Name axis: several physically separate databases, all on the same engine (SQL Server), one per service. A second,…"},"166":{"u":"/docs/adr/018-polyglot-persistence.html#decision","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Decision","x":"Support four storage engines behind one entity model and one set of repository abstractions, selected per entity configuration. 1. DataSource engine enum: SQLServer (full…"},"167":{"u":"/docs/adr/018-polyglot-persistence.html#rationale","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Rationale","x":"- Right store per access pattern, as a configuration decision. The engine becomes an attribute on a configuration class, not a rewrite. The same domain entity, application…"},"168":{"u":"/docs/adr/018-polyglot-persistence.html#trade-offs","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Trade-offs","x":"- No cross-engine JOINs, FKs, or transactions. This is the ADR-006 cost made sharper: across engines it is a hard limit, not a deployment choice. A query spanning engines (for…"},"169":{"u":"/docs/adr/018-polyglot-persistence.html#related","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Related","x":"ADR-006 (database-per-service: the Name axis this ADR's Engine axis is orthogonal to; they share DataSourceKey), ADR-002 (navigation populators bridge the relationships the…"},"170":{"u":"/docs/adr/018-polyglot-persistence.html#revision-2026-08-29-engine-substitution-for-a-single-engine-host","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Revision (2026-08-29): engine substitution for a single-engine host","x":"Decision item 4 says engines never collapse into each other, and that held while every host in this workspace configured SQL Server. A host that configures only SQLite (or only…"},"171":{"u":"/docs/adr/018-polyglot-persistence.html#revision-2026-10-01","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision changed; this pass corrects statements that overreached the code and refreshes stale citations. Decision items 5 and 6, the Trade-offs and Related now say that the…"},"172":{"u":"/docs/adr/019-rate-limiting.html","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records"},"173":{"u":"/docs/adr/019-rate-limiting.html#status","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-08-01 (the auth-ip per-IP anonymous-authentication limiter, which the shared auth controller applies to login and register by default, is recorded as the…"},"174":{"u":"/docs/adr/019-rate-limiting.html#context","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Context","x":"Every service exposes read and write endpoints to the public internet through the gateway (ADR-008). Abusive or runaway clients (scrapers, credential stuffing, retry storms, a…"},"175":{"u":"/docs/adr/019-rate-limiting.html#decision","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Decision","x":"Rate limiting is layered, and the always-on global limiter is authenticated-only. 1. A global limiter that only caps authenticated callers. AddCommonRateLimiting…"},"176":{"u":"/docs/adr/019-rate-limiting.html#rationale","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Rationale","x":"- Limit the traffic that is both attributable and expensive. An authenticated request is tied to a principal and usually drives the database; capping per-principal stops a single…"},"177":{"u":"/docs/adr/019-rate-limiting.html#trade-offs","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Trade-offs","x":"- The global limiter only protects the authenticated surface. The anonymous surface is covered endpoint by endpoint instead: the anonymous credential endpoints (login, register,…"},"178":{"u":"/docs/adr/019-rate-limiting.html#revision-2026-08-18","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"The layering above is unchanged: the global limiter is still authenticated-only, infrastructure and anonymous traffic are still exempt, auth-ip still covers login and register by…"},"179":{"u":"/docs/adr/019-rate-limiting.html#revision-2026-09-07","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The layering is unchanged: the global limiter is still authenticated-only, login and register still carry auth-ip, and infrastructure paths are still exempt. Four things below it…"},"180":{"u":"/docs/adr/019-rate-limiting.html#revision-2026-09-10","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Revision (2026-09-10)","x":"Both gateways now scope auth-tight the same way, and the trusted-caller exemption's client half ships in the framework. Two items, both landed. 1. Store's gateway splits the…"},"181":{"u":"/docs/adr/019-rate-limiting.html#revision-2026-10-01","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Two current-state statements in the Decision were corrected. The ADC Conference output-cache list now reads as a sample of a larger…"},"182":{"u":"/docs/adr/019-rate-limiting.html#related","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the JWKS/discovery traffic the limiter exempts, and the authenticated principal it keys on), ADR-008 (the gateway edge this protects), ADR-017 (request idempotency, the…"},"183":{"u":"/docs/adr/020-permission-based-authorization.html","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records"},"184":{"u":"/docs/adr/020-permission-based-authorization.html#status","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-25, amended 2026-07-10 and 2026-08-23). Revised 2026-09-07 (a fallback policy requiring an authenticated caller is on by default, so anonymity has to be…"},"185":{"u":"/docs/adr/020-permission-based-authorization.html#context","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Context","x":"The default answer in ASP.NET Core is pure role-based access control (RBAC): an endpoint declares [Authorize(Policy = \"RequireOrganizer\")] against a named policy that calls…"},"186":{"u":"/docs/adr/020-permission-based-authorization.html#decision","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Decision","x":"Authorize on capabilities resolved from roles: a permission layer over RBAC, and the framework's one authorization model. Nothing is pre-registered per role name. - A central…"},"187":{"u":"/docs/adr/020-permission-based-authorization.html#rationale","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Rationale","x":"- Capabilities decouple endpoints from roles. A route says what it does (conference:sessions:manage), and who may do it is a registry decision, so adding ContentEditor with a…"},"188":{"u":"/docs/adr/020-permission-based-authorization.html#trade-offs","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Trade-offs","x":"- It is still RBAC, not ABAC. The model resolves role to permission; it does not evaluate resource or attribute conditions. Per-resource ownership (\"a customer may read only…"},"189":{"u":"/docs/adr/020-permission-based-authorization.html#revision-2026-09-07","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Permission-based authorization is unchanged. What changed is the default for an endpoint that declares nothing (SEC-Common-16 / SEC-ADC-03). 1. An undecorated endpoint now…"},"190":{"u":"/docs/adr/020-permission-based-authorization.html#revision-2026-10-01","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed: the framework still declares no role names and still gates its own endpoints and navigation on capabilities. The role-vocabulary paragraph now…"},"191":{"u":"/docs/adr/020-permission-based-authorization.html#related","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the authenticated principal and claims this keys on, including the optional permission claim), ADR-008 (each extracted service authorizes independently, so the registry…"},"192":{"u":"/docs/adr/021-consumer-inbox-idempotency.html","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records"},"193":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#status","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-09; adoption reviewed 2026-07-15, inventory refreshed 2026-10-01). Revised 2026-08-18 (the inbox stays opt-in, but being off is no longer silent: a…"},"194":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#context","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Context","x":"ADR-003 makes integration-event delivery at-least-once: the outbox guarantees a published event is not lost, and the MassTransit broker redelivers on consumer failure.…"},"195":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#decision","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Decision","x":"Add an opt-in inbox that records each successfully-processed integration event by its MessageId and skips redeliveries. - Every event carries a MessageId. BaseDomainEvent stamps…"},"196":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#rationale","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Rationale","x":"- Dedup once, not in every handler. A single consume-edge check turns \"every handler author must remember to be idempotent against redelivery\" into a framework guarantee for the…"},"197":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#trade-offs","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Trade-offs","x":"- Not exactly-once. The crash-after-handler-before-inbox window reprocesses once, so handlers must stay idempotent for it; the inbox narrows the duplicate window, it does not…"},"198":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#revision-2026-09-07","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Inbox idempotency is unchanged. The names the consumer endpoints take are not (SEC-Common-53). MessageBus:EndpointPrefix left unset used to mean \"no prefix\"; it now defaults to…"},"199":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#related","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox and at-least-once delivery whose consumer side this deduplicates; handler idempotency is still required for the crash window), ADR-006 (the inbox lives in the…"},"200":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#revision-2026-08-18","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"The decision is unchanged: the inbox is still opt-in and NoOpInboxStore is still the default. What changed is that the default is now loud. 1. A broker-connected host with no…"},"201":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#revision-2026-08-26","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Revision (2026-08-26)","x":"Two changes, and the first one reverses this record's central choice. The inbox is no longer opt-in where redelivery is possible, and its row is no longer a separate write. 1.…"},"202":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#revision-2026-09-11","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"Dedup semantics are unchanged: same MessageId key, same TryBeginAsync to handlers to CompleteAsync path, same staged row, same unique index as the race guard. What changed is…"},"203":{"u":"/docs/adr/021-consumer-inbox-idempotency.html#revision-2026-10-01","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision changed. The consumer inventory did: ADC Conference now consumes UserDeleted beside UserRegistered…"},"204":{"u":"/docs/adr/022-browser-session-cookie-auth.html","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records"},"205":{"u":"/docs/adr/022-browser-session-cookie-auth.html#status","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Status","x":"Accepted."},"206":{"u":"/docs/adr/022-browser-session-cookie-auth.html#context","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Context","x":"The apps are Blazor Web Apps: a server-rendered (SSR) prerender pass runs on the first request, then an interactive phase (Blazor Server or WebAssembly) takes over.…"},"207":{"u":"/docs/adr/022-browser-session-cookie-auth.html#decision","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Decision","x":"Carry the session in HttpOnly cookies and add an authentication scheme that reads them during SSR prerender. The mechanism ships in MMCA.Common.API (SessionCookies/) with a…"},"208":{"u":"/docs/adr/022-browser-session-cookie-auth.html#rationale","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Rationale","x":"- Fixes the fresh-GET prerender gap. Without a server-readable session, every deep-link or F5 to an [Authorize] page would redirect to /login despite a valid session; the cookie…"},"209":{"u":"/docs/adr/022-browser-session-cookie-auth.html#trade-offs","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Trade-offs","x":"- A non-validating auth scheme exists. SessionCookieAuthenticationHandler trusts a cookie it does not cryptographically verify. This is sound only because (a) the cookie is…"},"210":{"u":"/docs/adr/022-browser-session-cookie-auth.html#related","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the JWT/JWKS validation the API performs on every call, which is why the SSR handler can skip signature validation), ADR-008 (the gateway and topology the UI talks to),…"},"211":{"u":"/docs/adr/023-security-response-headers.html","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records"},"212":{"u":"/docs/adr/023-security-response-headers.html#status","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-02). Revised 2026-09-01 (the static default is now a complete hardened baseline: it ships script-src 'self' 'wasm-unsafe-eval' and style-src 'self'…"},"213":{"u":"/docs/adr/023-security-response-headers.html#context","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Context","x":"Every client-facing host (the YARP Gateway and the Blazor UI web host in each app) must stamp the same hardened HTTP response headers: X-Content-Type-Options, X-Frame-Options,…"},"214":{"u":"/docs/adr/023-security-response-headers.html#decision","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Decision","x":"Ship one security-headers middleware in MMCA.Common.Aspire (MMCA.Common.Aspire.Security), registered with AddCommonSecurityHeaders(configuration?, configure?) and inserted early…"},"215":{"u":"/docs/adr/023-security-response-headers.html#rationale","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Rationale","x":"- One hardened default, defined once. Centralizing the header set removes per-host drift and makes a new edge host secure by default rather than by remembering to copy headers. -…"},"216":{"u":"/docs/adr/023-security-response-headers.html#trade-offs","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Trade-offs","x":"- The baseline is complete, not maximal. Shipping a policy that works for a Blazor/MudBlazor host means the default carries style-src 'unsafe-inline' (MudBlazor injects styles at…"},"217":{"u":"/docs/adr/023-security-response-headers.html#revision-2026-09-07","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Two changes from the 2026-09-07 security review. 1. Credential-carrying paths get a stricter referrer and cache posture. SecurityHeadersSettings.CredentialPathPrefixes…"},"218":{"u":"/docs/adr/023-security-response-headers.html#revision-2026-09-10","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Revision (2026-09-10)","x":"Both UI origins are now pinned by a test, not just Store's. ADC has MMCA.ADC/Tests/Hosts/MMCA.ADC.UI.Web.Tests/SecurityHeadersTests.cs:17-18, a one-line subclass of the…"},"219":{"u":"/docs/adr/023-security-response-headers.html#revision-2026-09-19","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Revision (2026-09-19)","x":"The Blazor policy is wider than the Decision above recorded, and the UI host's forwarded-headers options clear their allow-lists on purpose. The provider is no longer the copy…"},"220":{"u":"/docs/adr/023-security-response-headers.html#revision-2026-10-01","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; one Trade-offs statement was corrected and the current homes of the forwarded-headers posture are recorded. 1. The registration-order trade-off…"},"221":{"u":"/docs/adr/023-security-response-headers.html#related","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Related","x":"ADR-019 (rate limiting, the other always-on edge protection living in the same Aspire layer), ADR-022 (browser session-cookie auth, the other browser-edge security control),…"},"222":{"u":"/docs/adr/024-push-notifications.html","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records"},"223":{"u":"/docs/adr/024-push-notifications.html#status","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27, amended 2026-07-15). Revised 2026-08-07 (transactional email recorded as an app-level concern outside the channel model; see Revision below). Revised…"},"224":{"u":"/docs/adr/024-push-notifications.html#context","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Context","x":"The framework needs to deliver user-facing notifications (an organizer broadcasting a schedule change, a per-user alert). Two delivery models each fail on their own. A pure…"},"225":{"u":"/docs/adr/024-push-notifications.html#decision","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Decision","x":"Deliver notifications over two channels from one application use case, with the transport and the recipient policy both behind abstractions. - A durable per-user inbox plus a…"},"226":{"u":"/docs/adr/024-push-notifications.html#rationale","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Rationale","x":"- Each channel covers the other's failure mode. The inbox guarantees eventual delivery to offline users; the push gives connected users immediacy. Persisting the inbox before…"},"227":{"u":"/docs/adr/024-push-notifications.html#trade-offs","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Fan-out write amplification. One UserNotification row is written per recipient, so a broadcast to a large audience is a large insert. This is fine for the current per-event /…"},"228":{"u":"/docs/adr/024-push-notifications.html#revision-2026-09-07","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Two bounds were added under the delivery model, from the 2026-09-07 security review. 1. A connection cap per user. NotificationHub refuses a connection once the caller already…"},"229":{"u":"/docs/adr/024-push-notifications.html#related","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox dual-dispatch path, which is distinct: that carries service-to-service integration events, this carries user-facing notifications), ADR-004 (the /hubs…"},"230":{"u":"/docs/adr/024-push-notifications.html#revision-2026-08-07","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"Records transactional email, a delivery path the channel model above never mentions. The decision is unchanged: this closes a documentation gap so the asymmetry reads as…"},"231":{"u":"/docs/adr/024-push-notifications.html#revision-2026-10-01","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The opt-in deduplication key is now scoped to the sender. SendPushNotificationHandler no longer stores or looks up the client-supplied DedupKey as-is: it derives the persisted…"},"232":{"u":"/docs/adr/025-startup-warmup-readiness.html","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records"},"233":{"u":"/docs/adr/025-startup-warmup-readiness.html#status","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27). Revised 2026-07-28: /health/ready now excludes optional-tagged checks as well as live-tagged ones (see Decision), and the absence of a warm-up timeout was…"},"234":{"u":"/docs/adr/025-startup-warmup-readiness.html#context","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Context","x":"On the Azure Container Apps Consumption plan a replica that has been idle is CPU-throttled, and a scale-from-zero or scaled-out replica starts cold. The first authenticated…"},"235":{"u":"/docs/adr/025-startup-warmup-readiness.html#decision","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Decision","x":"Ship a small warm-up subsystem in MMCA.Common.Aspire, wired into AddServiceDefaults() so every host gets it. - A readiness gate that starts closed. WarmupReadinessGate…"},"236":{"u":"/docs/adr/025-startup-warmup-readiness.html#rationale","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Rationale","x":"- Keep cold replicas out of rotation, briefly. Gating readiness on warm-up means the platform does not send a user request to a replica that is still doing its first handshakes,…"},"237":{"u":"/docs/adr/025-startup-warmup-readiness.html#trade-offs","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Trade-offs","x":"- A replica can enter rotation not fully warm. The gate is opened in a finally once the Task.WhenAll over every registered task returns, that is, once each task has completed,…"},"238":{"u":"/docs/adr/025-startup-warmup-readiness.html#revision-2026-10-01","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Citations were refreshed: the /health/ready predicate, the MapCachedHealthChecks helper and the uncached /alive mapping now live in…"},"239":{"u":"/docs/adr/025-startup-warmup-readiness.html#related","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the OIDC discovery document the built-in task pre-fetches, and the auth-side view of the same cold-start), ADR-009 (the Polly resilience pipeline that absorbs the lazy…"},"240":{"u":"/docs/adr/026-caching-strategy.html","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records"},"241":{"u":"/docs/adr/026-caching-strategy.html#status","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27, amended 2026-07-10, 2026-07-23, 2026-07-25, 2026-08-14). Amended by ADR-077 (2026-08-13): Tier 1's substrate gains a third, opt-in implementation…"},"242":{"u":"/docs/adr/026-caching-strategy.html#context","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Context","x":"The framework needs caching in two distinct places. Inside the application pipeline, query results are memoized and invalidated on mutation (the Caching decorators of ADR-014,…"},"243":{"u":"/docs/adr/026-caching-strategy.html#decision","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Decision","x":"Cache in two tiers, each with its own substrate. - One abstraction. ICacheService (MMCA.Common.Application/Interfaces/ICacheService.cs) exposes GetAsync / SetAsync / RemoveAsync…"},"244":{"u":"/docs/adr/026-caching-strategy.html#rationale","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Rationale","x":"- One substrate, swapped by environment. Keeping ICacheService as the only thing application code sees lets the deployment decide memory vs distributed. The auto-swap (presence…"},"245":{"u":"/docs/adr/026-caching-strategy.html#trade-offs","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Trade-offs","x":"- Memory mode is per-replica. In the in-process store each replica caches independently; a scaled-out deployment that did not wire Redis would see cross-replica staleness bounded…"},"246":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-09-07","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Four changes from the 2026-09-07 security review. The tiers themselves are unchanged. 1. Tier-1 keys are caller-scoped where the result is caller-scoped (SEC-Common-19 /…"},"247":{"u":"/docs/adr/026-caching-strategy.html#related","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Related","x":"ADR-014 (the Caching decorators and IQueryCacheable / ICacheInvalidating markers that consume this substrate), ADR-019 (output caching as the anonymous-traffic lever, and…"},"248":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-07-24","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-07-24)","x":"Three substrate corrections from a code review. 1. An optional key namespace (Cache:KeyPrefix). Services sharing one cache instance also share one keyspace, and nothing stopped…"},"249":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-07-25","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-07-25)","x":"An audit against the code. No behavior changed; the ADR text did. 1. The IncrementAsync entry above was wrong. It described a Redis INCR override. There is no such override, and…"},"250":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-07-28","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-07-28)","x":"Line anchors only, re-verified against the current source. No decision, no behavior, and no substantive prose changed. 1. Tier 2. Store Catalog's…"},"251":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-01","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-01)","x":"Line anchors only, re-verified against the current source. No decision, no behavior, and no substantive prose changed. 1. AddCaching. Now at…"},"252":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-07","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"Line anchors only, re-verified against the current source. No decision, no behavior, and no substantive prose changed. 1. AddCaching. Now at…"},"253":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-13","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-13)","x":"Tier 1 is amended by ADR-077, which is where the decision and its trade-offs are recorded. The three points that change the reading of this record: 1. A third substrate, opted…"},"254":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-14","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-14)","x":"One substrate correction plus a line-anchor re-verification. No decision and no behavior changed. 1. The 30-second default now has a named home, CacheOptions.DefaultDuration.…"},"255":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-18","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Every previous revision moved Tier 1. This one moves Tier 2, and it is the first change to the output-cache edge since ADR-040. Tier 2 as decided here is per-process by…"},"256":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-23","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"One correction of substance plus a line-anchor re-verification. No decision and no behavior changed. 1. The counter trade-off now names the contradiction a reader will hit.…"},"257":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-31","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-31)","x":"Line anchors only, re-verified against the current source. No decision, no behavior, and no substantive prose changed. 1. AddCaching. Now at…"},"258":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-09-01","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-09-01)","x":"One amendment of substance (an optional third tier is recorded) plus line anchors for the files the 2026-08-31 entry did not re-read. No behavior changed, and neither server tier…"},"259":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-09-03","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-09-03)","x":"Two substrate corrections plus a line-anchor and file-path re-verification. No decision changed; what changed is where the wiring lives and which substrate the applications…"},"260":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-09-25","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-09-25)","x":"One wiring convergence, one count correction, and a line-anchor re-verification of the service call sites and the substrate registrations. No decision changed, and the ADC-only…"},"261":{"u":"/docs/adr/026-caching-strategy.html#revision-2026-10-01","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"One wiring correction, one recorded client-tier detail, and a line-anchor re-verification. No decision and no rationale changed. 1. The hybrid opt-in is a framework helper now,…"},"262":{"u":"/docs/adr/027-multi-locale-i18n.html","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records"},"263":{"u":"/docs/adr/027-multi-locale-i18n.html#status","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27, amended 2026-07-02, 2026-07-03, 2026-07-09, 2026-07-29, and 2026-09-15: the MudBlazor localization interceptor is replaced so no dependency assigns the…"},"264":{"u":"/docs/adr/027-multi-locale-i18n.html#context","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Context","x":"ADR-011 recorded single-locale (en-US) as a deliberate, revisitable non-goal and sketched what re-introducing i18n would entail. That revisit has now happened: the framework adds…"},"265":{"u":"/docs/adr/027-multi-locale-i18n.html#decision","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Decision","x":"1. Supported cultures are an explicit allowlist: en-US (default) + es. Adding a locale is adding a .es.resx sibling set and one allowlist entry, not new infrastructure. 2.…"},"266":{"u":"/docs/adr/027-multi-locale-i18n.html#rationale","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Rationale","x":"- Keying error localization on the existing Error.Code is the cheapest correct extension point. The codes are already stable and already cross the wire; localizing at the edge…"},"267":{"u":"/docs/adr/027-multi-locale-i18n.html#trade-offs","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Every view and every user-facing message is touched: a large, mostly mechanical sweep, accepted as the cost ADR-011 always named. - WASM Spanish formatting needs ICU…"},"268":{"u":"/docs/adr/027-multi-locale-i18n.html#revision-2026-10-01","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; four statements were corrected to match the code and several citations were refreshed. Decision 2: the composite-key example now matches the…"},"269":{"u":"/docs/adr/027-multi-locale-i18n.html#related","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Related","x":"ADR-011 (superseded), ADR-013 (the Error.Code this localizes on), ADR-015 (the i18n gates now live here: the MA0076 culture-less formatting build gate and the…"},"270":{"u":"/docs/adr/028-dark-theme-mode.html","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records"},"271":{"u":"/docs/adr/028-dark-theme-mode.html#status","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27; revised 2026-07-15; revised 2026-08-31)."},"272":{"u":"/docs/adr/028-dark-theme-mode.html#context","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Context","x":"MMCATheme (MMCA.Common.UI/Theme/MMCATheme.cs) has always defined a complete, brand-tuned PaletteDark alongside PaletteLight, but MudThemeProvider was hard-wired to light: no…"},"273":{"u":"/docs/adr/028-dark-theme-mode.html#decision","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Decision","x":"1. Bind the existing theme. The shared MainLayout renders a single component (MMCA.Common.UI/Layout/MainLayout.razor:14), which owns the four Mud providers plus the Day/Dark…"},"274":{"u":"/docs/adr/028-dark-theme-mode.html#rationale","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Rationale","x":"- Reusing the i18n cookie/profile machinery means one persistence model for both user preferences, instead of two subtly different ones. Theme and locale are the same shape of…"},"275":{"u":"/docs/adr/028-dark-theme-mode.html#trade-offs","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Trade-offs","x":"- The same FOUC hazard as locale is not yet closed for theme. The SSR data-theme/inline-script read is unimplemented (Decision 3), so the first paint can briefly flash the wrong…"},"276":{"u":"/docs/adr/028-dark-theme-mode.html#revision-2026-10-01","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Anchor refresh only: no decision or rationale changed. The snackbar live-region wrapper above the @code block moved the MmcaThemeProviders members, so the Theme parameter, the…"},"277":{"u":"/docs/adr/028-dark-theme-mode.html#related","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Related","x":"ADR-027 (shares the cookie source-of-truth and the User preference migration, and is the model for the theme no-flash SSR bootstrap that is not yet wired), ADR-022 (the SSR…"},"278":{"u":"/docs/adr/029-authentication-brute-force-protection.html","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records"},"279":{"u":"/docs/adr/029-authentication-brute-force-protection.html#status","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27). Updated 2026-07-02 (the check/increment/reset call sequence was hoisted into AuthenticationServiceBase ; the adoption note and the \"convention the consumer…"},"280":{"u":"/docs/adr/029-authentication-brute-force-protection.html#context","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Context","x":"ADR-019's global rate limiter is principal-keyed: it caps requests per authenticated principal, and anonymous traffic is exempt with one metered exception, the configured…"},"281":{"u":"/docs/adr/029-authentication-brute-force-protection.html#decision","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Decision","x":"Provide a framework ILoginProtectionService (MMCA.Common.Application.Auth) with a single implementation LoginProtectionService (MMCA.Common.Infrastructure.Auth), registered…"},"282":{"u":"/docs/adr/029-authentication-brute-force-protection.html#rationale","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Rationale","x":"- Complements ADR-019 rather than duplicating it. ADR-019 carries two limiter layers and this is the third on top of them: its global limiter caps authenticated throughput per…"},"283":{"u":"/docs/adr/029-authentication-brute-force-protection.html#trade-offs","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Trade-offs","x":"- Cache-scoped state weakens under scale-out without Redis. In memory mode the counters are per-replica and evaporate on restart, so a multi-replica deployment that did not wire…"},"284":{"u":"/docs/adr/029-authentication-brute-force-protection.html#revision-2026-09-07","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The lockout model is unchanged. Three things about the order and cost of the login path changed, all from the 2026-09-07 security review. 1. The account-state gate runs after the…"},"285":{"u":"/docs/adr/029-authentication-brute-force-protection.html#revision-2026-10-01","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The login lockout and registration throttle are unchanged. Two current-state statements were corrected. First, ResetPasswordHandlerBase is no longer the only framework call site…"},"286":{"u":"/docs/adr/029-authentication-brute-force-protection.html#alternatives-rejected","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Alternatives rejected","x":"- Making the failed-attempt and registration counters atomic. The increment in DistributedCacheService.IncrementAsync is a read-modify-write through IDistributedCache and is…"},"287":{"u":"/docs/adr/029-authentication-brute-force-protection.html#related","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Related","x":"ADR-019 (the layered limiter: a principal-keyed global cap that exempts this anonymous surface, its one metered anonymous exception being the real-time hub paths, plus the per-IP…"},"288":{"u":"/docs/adr/030-startup-sole-migrator.html","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records"},"289":{"u":"/docs/adr/030-startup-sole-migrator.html#status","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27)."},"290":{"u":"/docs/adr/030-startup-sole-migrator.html#context","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Context","x":"Under database-per-service (ADR-006), each service owns its own database and its own migrations project, so something must apply pending migrations on every deploy. The…"},"291":{"u":"/docs/adr/030-startup-sole-migrator.html#decision","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Decision","x":"In Azure Container Apps, every service host runs ApplicationSettingsDatabaseInitStrategy = Migrate in production and is the sole migrator of its own database: it applies its…"},"292":{"u":"/docs/adr/030-startup-sole-migrator.html#rationale","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Rationale","x":"- One migrator, one mechanism. The code that owns the schema applies the schema; there is no second tool to keep in lockstep and no ordering race between a deploy step and…"},"293":{"u":"/docs/adr/030-startup-sole-migrator.html#trade-offs","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Auto-migrate-in-production is what \"None\" exists to prevent. An unintended or destructive migration would ship itself on the next deploy. The apps accept this; the build-time…"},"294":{"u":"/docs/adr/030-startup-sole-migrator.html#related","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Related","x":"ADR-006 (database-per-service: why each service owns and migrates its own database), ADR-025 (readiness gating keeps traffic off a still-migrating replica), ADR-009 (RTO/RPO +…"},"295":{"u":"/docs/adr/030-startup-sole-migrator.html#revision-2026-08-07","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"The sole-migrator decision extends to seed data: the same startup owner that applies the schema also runs the module seeders, in the same call, on the same boot. The Decision…"},"296":{"u":"/docs/adr/030-startup-sole-migrator.html#revision-2026-10-01","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed: every service host still sets DatabaseInitStrategy = 'Migrate' in production and remains the sole migrator of its database. The Context now…"},"297":{"u":"/docs/adr/031-feature-flag-management.html","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records"},"298":{"u":"/docs/adr/031-feature-flag-management.html#status","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27). Revised 2026-08-18 (a targeting-context accessor is now registered, so the built-in Targeting and Percentage filters give consistent per-user bucketing…"},"299":{"u":"/docs/adr/031-feature-flag-management.html#context","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Context","x":"The apps need to decouple release from deploy: ship code dark, flip a kill switch, or roll a feature out to a percentage of users without a redeploy. A flag has to be enforceable…"},"300":{"u":"/docs/adr/031-feature-flag-management.html#decision","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Decision","x":"Standardize on Microsoft.FeatureManagement, configured from the \"FeatureManagement\" configuration section and registered once in AddAPI (services.AddFeatureManagement() +…"},"301":{"u":"/docs/adr/031-feature-flag-management.html#rationale","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Rationale","x":"- Release decoupled from deploy. A kill switch or a percentage rollout becomes a configuration change, not a code change: the central reason feature management exists. - Two…"},"302":{"u":"/docs/adr/031-feature-flag-management.html#trade-offs","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Trade-offs","x":"- The two enforcement points must agree. A flag gated on the controller but not the handler (or vice versa) is a half-protected feature; no fitness rule asserts both are wired,…"},"303":{"u":"/docs/adr/031-feature-flag-management.html#revision-2026-08-18","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Progressive rollout is now usable, because the targeting context exists. The Decision above listed the Percentage / TimeWindow / Targeting filters as \"available\", and the last…"},"304":{"u":"/docs/adr/031-feature-flag-management.html#related","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Related","x":"ADR-014 (the decorator pipeline whose outermost slot FeatureGate fills, and the ordering that puts it first, now with Authorization registered directly inside it so a disabled…"},"305":{"u":"/docs/adr/031-feature-flag-management.html#revision-2026-09-11","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"The expiry gap the Trade-offs recorded is closed. \"The framework provides no expiry or staleness check\" was true from this record's acceptance until now. What it cost was never…"},"306":{"u":"/docs/adr/031-feature-flag-management.html#revision-2026-10-01","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The disabled response is not-found, but not anonymous. The Decision and Rationale said both surfaces emit the HandleFailure not-found shape, so a disabled feature is…"},"307":{"u":"/docs/adr/032-password-hashing.html","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records"},"308":{"u":"/docs/adr/032-password-hashing.html#status","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Status","x":"Superseded by ADR-102 (2026-08-31). Originally Accepted (2026-06-29, adoption note revised 2026-07-06, registration note revised 2026-08-01, call-site hoist recorded 2026-08-23).…"},"309":{"u":"/docs/adr/032-password-hashing.html#context","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Context","x":"Identity stores a credential as a (salt, hash) pair, never plaintext. The framework needs one canonical hasher that every consuming Identity flow shares, so the key-derivation…"},"310":{"u":"/docs/adr/032-password-hashing.html#decision","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Decision","x":"Provide a single IPasswordHasher (MMCA.Common.Application.Interfaces.Infrastructure, IPasswordHasher.cs:6) with one implementation PasswordHasher…"},"311":{"u":"/docs/adr/032-password-hashing.html#rationale","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Rationale","x":"- One framework-owned primitive, not per-app crypto. Putting the algorithm, work factor, salt size, and comparison in a single shared type means a future hardening (raising…"},"312":{"u":"/docs/adr/032-password-hashing.html#trade-offs","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Trade-offs","x":"- The legacy branch is a permanent correctness dependency that looks deletable. Its load-bearing role is invisible from the method body alone, so it is the single most…"},"313":{"u":"/docs/adr/032-password-hashing.html#related","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (cross-service JWT / JWKS authentication: the hasher gates credential verification that issues the tokens that ADR-004 then validates across services), ADR-005…"},"314":{"u":"/docs/adr/032-password-hashing.html#revision-2026-08-23","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"The decision is unchanged: one framework-owned IPasswordHasher, PBKDF2-HMAC-SHA512 for new passwords, salt-length dispatch on verification. What changed is where the last two…"},"315":{"u":"/docs/adr/033-resource-ownership-authorization.html","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records"},"316":{"u":"/docs/adr/033-resource-ownership-authorization.html#status","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-02, revised 2026-07-25, 2026-08-01, 2026-08-31). Revised 2026-10-01 (the fail-closed owner gate and the per-record ownership check are now framework code in…"},"317":{"u":"/docs/adr/033-resource-ownership-authorization.html#context","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Context","x":"ADR-020 added a permission (capability) layer over RBAC: it answers \"what may this role do\", resolving a role to a permission so an endpoint can require a capability instead of a…"},"318":{"u":"/docs/adr/033-resource-ownership-authorization.html#decision","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Decision","x":"Provide a row/resource-level ownership axis in MMCA.Common.API (the Authorization folder), with two enforcement points keyed on the caller's owner claim (customerid by default)…"},"319":{"u":"/docs/adr/033-resource-ownership-authorization.html#rationale","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Rationale","x":"- Reject-one and filter-many are genuinely two mechanisms. A single-resource route has an id to compare, so a short action filter that 403s on a mismatch is the cheapest correct…"},"320":{"u":"/docs/adr/033-resource-ownership-authorization.html#trade-offs","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Opt-in per controller/handler. Neither point is automatic: a controller that forgets the [ServiceFilter] or omits the ownership spec from a query leaks across customers, the…"},"321":{"u":"/docs/adr/033-resource-ownership-authorization.html#related","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Related","x":"ADR-020 (the role/permission RBAC layer this complements, and whose explicit 020-permission-based-authorization.md:159-160 scope-out this fills), ADR-034 (the generic entity…"},"322":{"u":"/docs/adr/033-resource-ownership-authorization.html#revision-2026-07-25","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Revision (2026-07-25)","x":"An audit against the code. No behavior changed; the ADR text did. 1. The per-mutation check's failure shape was described as one branch, and it is two. ValidateOwnershipAsync was…"},"323":{"u":"/docs/adr/033-resource-ownership-authorization.html#revision-2026-08-01","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Revision (2026-08-01)","x":"Anchor-only correction. No behavior changed; OrdersController was refactored (a constructor parameter added, GetOwnershipSpecification() and the IsAdmin property extracted,…"},"324":{"u":"/docs/adr/033-resource-ownership-authorization.html#revision-2026-08-31","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Revision (2026-08-31)","x":"Behavior changed on both adopters since the 2026-08-01 pass, so this is not an anchor refresh. 1. The ambiguous null specification is now gated.…"},"325":{"u":"/docs/adr/033-resource-ownership-authorization.html#revision-2026-09-10","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Revision (2026-09-10)","x":"ADC adopts both enforcement points, so the \"Engagement Bookmarks only\" framing above is no longer the whole inventory. The Adoption text describes ADC through the action-filter…"},"326":{"u":"/docs/adr/033-resource-ownership-authorization.html#revision-2026-10-01","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The fail-closed gate this record named as a candidate for extraction (Revision 2026-09-10, item 3) is now framework code. MMCA.Common v1.216.0 (MMCA.Common/CHANGELOG.md:26) adds…"},"327":{"u":"/docs/adr/034-generic-entity-query-layer.html","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records"},"328":{"u":"/docs/adr/034-generic-entity-query-layer.html#status","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-30). Amended (2026-07-23): the filter strategy registry now also covers long/long? via LongFilterStrategy. Amended (2026-07-25): the keyed by-id fast path is…"},"329":{"u":"/docs/adr/034-generic-entity-query-layer.html#context","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Context","x":"Every module exposes many entities, and most of them need the same read and write surface: list, page, look up for a dropdown, fetch by id, create, delete. Hand writing a…"},"330":{"u":"/docs/adr/034-generic-entity-query-layer.html#decision","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Decision","x":"Give every entity a generic REST resource surface and a bounded dynamic query contract, supplied by two controller bases over a shared query pipeline. 1. Generic read controller.…"},"331":{"u":"/docs/adr/034-generic-entity-query-layer.html#rationale","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Rationale","x":"- Write the resource surface once, inherit it everywhere. The verbs, routes, filter contract, pagination, and header are defined once on the two bases; a concrete controller…"},"332":{"u":"/docs/adr/034-generic-entity-query-layer.html#trade-offs","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- The wire contract tracks the entity model. Filterable, sortable, and projectable surface is the entity's property set. A model change is an API change unless mediated by the…"},"333":{"u":"/docs/adr/034-generic-entity-query-layer.html#revision-2026-09-07","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The query layer's shape is unchanged. What a caller is allowed to name is not, from the 2026-09-07 security review. 1. Client keys resolve against the DTO contract (SEC-Common-25…"},"334":{"u":"/docs/adr/034-generic-entity-query-layer.html#related","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-001 (manual DTO mapping: the generic controllers project through IEntityDTOMapper), ADR-002 (navigation populators: the unsupported-include path), ADR-013 (Result pattern at…"},"335":{"u":"/docs/adr/034-generic-entity-query-layer.html#revision-2026-07-24","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Revision (2026-07-24)","x":"Filter and pagination corrections from a code review. The contract shape is unchanged; these close cases where the engine widened a result set instead of narrowing it, or…"},"336":{"u":"/docs/adr/034-generic-entity-query-layer.html#revision-2026-07-25","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Revision (2026-07-25)","x":"Citation maintenance from an ADR audit. No decision or behavior changed; the source anchors above were rebased to their current declaration and call-site lines. 1. The fast-path…"},"337":{"u":"/docs/adr/034-generic-entity-query-layer.html#revision-2026-10-01","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; this records clarifications and refreshed citations from an ADR audit. 1. MaxPageSize is clamped to the pipeline ceiling. The resolved setting…"},"338":{"u":"/docs/adr/035-optimistic-concurrency.html","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records"},"339":{"u":"/docs/adr/035-optimistic-concurrency.html#status","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-02). Revised 2026-09-07 (a conditional write always stamps the aggregate root, so If-Match is enforced even when only child rows changed, and a…"},"340":{"u":"/docs/adr/035-optimistic-concurrency.html#context","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Context","x":"Every mutable aggregate in the framework is edited through a load-modify-save handler: the update use case fetches the tracked entity, applies the request, and calls…"},"341":{"u":"/docs/adr/035-optimistic-concurrency.html#decision","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Decision","x":"Give every auditable entity a database-managed RowVersion concurrency token, round-trip it through the client as an HTTP entity tag, and stamp the client's last-seen value as…"},"342":{"u":"/docs/adr/035-optimistic-concurrency.html#rationale","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Rationale","x":"- Database-managed token over a hand-maintained version field. A SQL Server rowversion auto-increments on the server on every write; no domain code sets or reads it (the setter…"},"343":{"u":"/docs/adr/035-optimistic-concurrency.html#trade-offs","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Trade-offs","x":"- The rewrite keys on the conflict outcome, not on its cause. The filter turns any 409 result the action returns under an If-Match request into a 412…"},"344":{"u":"/docs/adr/035-optimistic-concurrency.html#revision-2026-09-07","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Two additions from the 2026-09-07 security review. 1. If-Match is enforced even when the root row is otherwise unchanged (SEC-Common-77). IWriteRepository.TouchConcurrencyToken…"},"345":{"u":"/docs/adr/035-optimistic-concurrency.html#revision-2026-10-01","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision changed; three current-state statements were corrected and stale citations refreshed. (1) Only tables mapped from an auditable entity carry RowVersion; the…"},"346":{"u":"/docs/adr/035-optimistic-concurrency.html#related","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Related","x":"ADR-017 (HTTP request idempotency, which dedups retries of the same request, the mirror-image concern to two distinct edits racing here, and whose declared-intent gate sits at…"},"347":{"u":"/docs/adr/036-external-oauth-login.html","d":"ADR-036: External OAuth Login (Federated Google/GitHub/Apple) with Local-JWT Exchange","k":"Architecture Decision Records"},"348":{"u":"/docs/adr/036-external-oauth-login.html#status","d":"ADR-036: External OAuth Login (Federated Google/GitHub/Apple) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-02, migration attribution corrected 2026-07-06, native-callback redirect branch added 2026-07-17 per ADR-043, email-verified account-takeover guard before…"},"349":{"u":"/docs/adr/036-external-oauth-login.html#context","d":"ADR-036: External OAuth Login (Federated Google/GitHub/Apple) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Context","x":"The framework's Identity story so far is entirely first-party: a user registers with an email and password, the credentials are hashed (ADR-032), and Identity mints its own RS256…"},"350":{"u":"/docs/adr/036-external-oauth-login.html#decision","d":"ADR-036: External OAuth Login (Federated Google/GitHub/Apple) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Decision","x":"Add an opt-in external-login path that federates Google, GitHub and Apple sign-in at the edge and immediately exchanges the external identity for the app's own local JWT pair,…"},"351":{"u":"/docs/adr/036-external-oauth-login.html#rationale","d":"ADR-036: External OAuth Login (Federated Google/GitHub/Apple) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Rationale","x":"- Terminate federation at the edge, keep one internal identity. Exchanging the external principal for a local JWT the moment the callback returns means every downstream concern…"},"352":{"u":"/docs/adr/036-external-oauth-login.html#trade-offs","d":"ADR-036: External OAuth Login (Federated Google/GitHub/Apple) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Trade-offs","x":"- Opt-in per app, and easy to half-wire. The flow needs four cooperating pieces (scheme registration, the controller subclass, the service override, and the OAuthUIBaseUrl…"},"353":{"u":"/docs/adr/036-external-oauth-login.html#revision-2026-09-07","d":"ADR-036: External OAuth Login (Federated Google/GitHub/Apple) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Four changes from the 2026-09-07 security review. 1. The empty credential an external account carries is explicitly non-authenticating (SEC-Common-01). This record already said…"},"354":{"u":"/docs/adr/036-external-oauth-login.html#revision-2026-10-01","d":"ADR-036: External OAuth Login (Federated Google/GitHub/Apple) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Anchor refresh only: no decision or rationale changed. The Decision and Trade-offs citations into ADC's AuthenticationService.cs are re-anchored to the current file…"},"355":{"u":"/docs/adr/036-external-oauth-login.html#related","d":"ADR-036: External OAuth Login (Federated Google/GitHub/Apple) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the RS256/JWKS token this flow exchanges the external identity for, and validates everywhere after), ADR-022 (the browser cookies that carry the resulting session),…"},"356":{"u":"/docs/adr/037-field-level-encryption-at-rest.html","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records"},"357":{"u":"/docs/adr/037-field-level-encryption-at-rest.html#status","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-06; revised 2026-07-24, 2026-07-25, 2026-08-15, 2026-08-18). Revised 2026-08-18: the versioned-envelope converter is no longer unpublished, it is included in…"},"358":{"u":"/docs/adr/037-field-level-encryption-at-rest.html#context","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Context","x":"Transparent database encryption (TDE) protects the data files as a whole, but it decrypts transparently for anyone who can query the database, so a leaked backup restored on a…"},"359":{"u":"/docs/adr/037-field-level-encryption-at-rest.html#decision","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Decision","x":"Provide a single framework-owned EF Core value converter that transparently encrypts string columns at rest with authenticated encryption, applied per property in an entity…"},"360":{"u":"/docs/adr/037-field-level-encryption-at-rest.html#rationale","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Rationale","x":"- Authenticated, not merely confidential. AES-GCM binds a 128-bit tag to the ciphertext (EncryptedStringConverter.cs:81, :201), so a tampered or truncated value fails to decrypt…"},"361":{"u":"/docs/adr/037-field-level-encryption-at-rest.html#trade-offs","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Latent today, proven by tests rather than production. The plumbing is complete and unit-tested, but no entity configuration wires it, so the encrypt/decrypt round-trip, the…"},"362":{"u":"/docs/adr/037-field-level-encryption-at-rest.html#related","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Related","x":"ADR-005 (soft-delete vs erasure: the other sensitive-data control, which names this converter as the mechanism for erasure fields that must stay retrievable,…"},"363":{"u":"/docs/adr/037-field-level-encryption-at-rest.html#revision-2026-07-24","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Revision (2026-07-24)","x":"Documented a constraint the converter always had but did not state: the ciphertext is non-deterministic. Every write uses a fresh random nonce, which is the correct property for…"},"364":{"u":"/docs/adr/037-field-level-encryption-at-rest.html#revision-2026-07-25","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Revision (2026-07-25)","x":"Documentation-only correction, no behavior change. Item 1 of the Decision still illustrated the converter with builder.Property(e = e.Email), contradicting the 2026-07-24…"},"365":{"u":"/docs/adr/037-field-level-encryption-at-rest.html#revision-2026-08-15","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Revision (2026-08-15)","x":"Behavior change, not a documentation correction. The stored layout is now a versioned envelope: Base64 of [key version (1)] [nonce (12)] [ciphertext (N)] [tag (16)] rather than…"},"366":{"u":"/docs/adr/037-field-level-encryption-at-rest.html#revision-2026-10-01","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Anchor-only refresh; no decision, rationale or trade-off changed. The three citations of ADR-005 in Context, Trade-offs and Related now point at the record's canonical home and…"},"367":{"u":"/docs/adr/038-supply-chain-provenance.html","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records"},"368":{"u":"/docs/adr/038-supply-chain-provenance.html#status","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-06; revised 2026-07-21). Revised 2026-09-07 (MMCA.Common pins every action by commit SHA and every global tool by version, restores in locked mode, fails the…"},"369":{"u":"/docs/adr/038-supply-chain-provenance.html#context","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Context","x":"MMCA.Common is a published framework: on every v tag (release.yml:3-5) it packs its NuGet packages and pushes them to both GitHub Packages (release.yml:117-118) and nuget.org…"},"370":{"u":"/docs/adr/038-supply-chain-provenance.html#decision","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Decision","x":"Treat supply-chain integrity as a set of build-gating controls, the same invariant-over-discipline posture ADR-015 applies to architecture rules. Four controls, each a hard gate:…"},"371":{"u":"/docs/adr/038-supply-chain-provenance.html#rationale","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Rationale","x":"- Provenance is a gate, not a document. A hard-failing SBOM step means the bill of materials cannot silently go missing on a release: the artifact is produced or the release…"},"372":{"u":"/docs/adr/038-supply-chain-provenance.html#trade-offs","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The SBOM is generated and archived, not yet signed or attested (superseded 2026-09-22; see the Revision below). The gate proves a bill of materials exists for each release…"},"373":{"u":"/docs/adr/038-supply-chain-provenance.html#revision-2026-09-07","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The provenance chain gained the controls that make it reproducible under an attacker, from the 2026-09-07 security review. 1. Actions are pinned by commit SHA, not by a mutable…"},"374":{"u":"/docs/adr/038-supply-chain-provenance.html#revision-2026-09-22-attestations-and-one-implementation-of-each-gate","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Revision (2026-09-22): attestations, and one implementation of each gate","x":"Two of the trade-offs above have moved. First, every .nupkg a release packs now carries a signed SLSA build-provenance attestation: release.yml calls…"},"375":{"u":"/docs/adr/038-supply-chain-provenance.html#revision-2026-10-01","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; the current-state sections now describe the gates where they live today. Context names both registries a tag publishes to: GitHub Packages…"},"376":{"u":"/docs/adr/038-supply-chain-provenance.html#related","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Related","x":"ADR-016 (lockstep versioning + the MassTransit-v8 license pin; this record extends dependency governance from versioning and licensing into supply-chain provenance and…"},"377":{"u":"/docs/adr/039-live-channel-push.html","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records"},"378":{"u":"/docs/adr/039-live-channel-push.html#status","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-09). Revised 2026-09-07 (channel subscription can be gated by an app-supplied authorizer, connections are capped per user, the backplane channel is namespaced…"},"379":{"u":"/docs/adr/039-live-channel-push.html#context","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Context","x":"Conference-day features (live polls, session Q&A, live result counters) need sub-second fan-out of small events to whoever is looking at a page right now. The existing…"},"380":{"u":"/docs/adr/039-live-channel-push.html#decision","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Decision","x":"One realtime transport, two publisher boundaries: - NotificationHub stays the single hub and gains its first client-invokable methods: JoinChannel / LeaveChannel map the calling…"},"381":{"u":"/docs/adr/039-live-channel-push.html#rationale","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Rationale","x":"- One WebSocket per client keeps connection management, token refresh, reconnect, and backplane behavior in one place; channel membership is a property of the existing…"},"382":{"u":"/docs/adr/039-live-channel-push.html#trade-offs","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Ephemeral means lossy: a client that connects after an event was published never sees it. Features must treat channel events as cache-invalidation hints over fetchable state,…"},"383":{"u":"/docs/adr/039-live-channel-push.html#revision-2026-07-24","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Revision (2026-07-24)","x":"Two corrections from a code review; the best-effort, per-session-ordered decision is unchanged. 1. Broadcasts are enqueued after commit, not during the command. CastVoteHandler…"},"384":{"u":"/docs/adr/039-live-channel-push.html#revision-2026-09-07","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The channel model is unchanged: a client subscribes to a channel key and the server pushes to it. What a signed-in client is allowed to subscribe to, and how much it may hold…"},"385":{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records"},"386":{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#status","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-10). Amended (2026-07-10): explicit query-string variance parity with the built-in default policy (the initial release accidentally dropped it, collapsing every…"},"387":{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#context","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Context","x":"The framework's read-scaling design leans on ASP.NET Core output caching: anonymous-readable endpoints ([AllowAnonymous] GETs like event/session/speaker catalogs) carry named…"},"388":{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#decision","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Decision","x":"MMCA.Common.API ships PublicEndpointOutputCachePolicy, an IOutputCachePolicy that mirrors the built-in default policy with one deliberate difference: it does not disable cache…"},"389":{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#rationale","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Rationale","x":"- The response payload, not the request's auth state, is what determines cacheability. For a user-independent payload, Authorization is noise; refusing to cache on it turns the…"},"390":{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#trade-offs","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Trade-offs","x":"- Consumers must audit which named policies move to AddPublicEndpointPolicy. Policies on permission-gated endpoints (e.g. an organizer dashboard) must NOT move; if such an…"},"391":{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#revision-2026-09-10","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Revision (2026-09-10)","x":"Store now uses the cross-service eviction path too, and the first case is an Application-layer handler rather than a controller. The trade-off above says to check which process…"},"392":{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#revision-2026-09-25","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Revision (2026-09-25)","x":"Store's ProductsCache TTL is 60 seconds, because its payload moves on the clock. A product payload carries each variant's effective price, and a discount window opening or…"},"393":{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#revision-2026-10-01","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Citations in Context, Decision and Trade-offs are re-anchored to the current hosts: ADC Conference Program.cs (the adminBypassRoles array at…"},"394":{"u":"/docs/adr/041-observability-and-telemetry.html","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records"},"395":{"u":"/docs/adr/041-observability-and-telemetry.html#status","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-10). Amended (2026-07-23) to document the Telemetry:DisableHttpClientMetrics and Telemetry:DisableRuntimeMetrics cost knobs and to correct the…"},"396":{"u":"/docs/adr/041-observability-and-telemetry.html#context","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Context","x":"The framework is a modular monolith whose modules extract into standalone services (ADR-008), so the same telemetry has to make sense whether a request stays in one process or…"},"397":{"u":"/docs/adr/041-observability-and-telemetry.html#decision","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Decision","x":"Standardize telemetry in the shared Aspire service defaults, add framework-specific instrumentation for the CQRS and outbox paths, and expose cost knobs with fail-safe defaults.…"},"398":{"u":"/docs/adr/041-observability-and-telemetry.html#rationale","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Rationale","x":"- Instrument only where auto-instrumentation is blind. The CQRS RED histograms and the outbox dead-letter counter cover the two framework-owned hot paths; everything else (HTTP,…"},"399":{"u":"/docs/adr/041-observability-and-telemetry.html#trade-offs","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Trade-offs","x":"- Custom instrumentation carries a maintenance cost. The Aspire package has no reference to Application or Infrastructure by design, so the meter and activity-source names are…"},"400":{"u":"/docs/adr/041-observability-and-telemetry.html#revision-2026-08-18","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Two meters and one hop. Two new failure counters, each on its own meter. cache.eviction.failed, tagged cachetag, on MMCA.Common.OutputCache…"},"401":{"u":"/docs/adr/041-observability-and-telemetry.html#amended-2026-08-31","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Amended (2026-08-31)","x":"The log side of this record. Until now it named only the OpenTelemetry logging call inside ConfigureOpenTelemetry (Extensions.cs:132); what a host actually WRITES its application…"},"402":{"u":"/docs/adr/041-observability-and-telemetry.html#amended-2026-09-03-probe-telemetry","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Amended (2026-09-03): probe telemetry","x":"Health probes were the trace bill. Container Apps liveness and readiness probes, the gateway's downstream aggregate probes, YARP active health checks and the availability web…"},"403":{"u":"/docs/adr/041-observability-and-telemetry.html#revision-2026-09-07","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Telemetry gained the properties that make it usable as evidence, from the 2026-09-07 security review. 1. The daily ingestion cap is alerted on (SEC-ADC-47 / SEC-Store-55). A…"},"404":{"u":"/docs/adr/041-observability-and-telemetry.html#revision-2026-09-11","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"An eighth meter, a second trace source, a second poll loop kept off the bill, and one meter this pipeline does not carry. The durable internal-command queue exports through this…"},"405":{"u":"/docs/adr/041-observability-and-telemetry.html#alternatives-rejected","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Alternatives rejected","x":"- An ActivitySource span per command and query in the CQRS pipeline, tagged by module. Evaluated 2026-09-16 against a modular-monolith template that ships one, and declined. The…"},"406":{"u":"/docs/adr/041-observability-and-telemetry.html#related","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox whose dead-letter counter and poll-span filtering this defines), ADR-014 (the CQRS decorator pipeline that emits the RED histograms as a byproduct of its…"},"407":{"u":"/docs/adr/041-observability-and-telemetry.html#amended-2026-09-11-the-polly-meter","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Amended (2026-09-11): the Polly meter","x":"A ninth meter, one cost knob, and one log level. Polly's meter is subscribed. AddServiceDefaults puts the standard resilience handler on every HttpClient and every gRPC typed…"},"408":{"u":"/docs/adr/041-observability-and-telemetry.html#revision-2026-09-19-the-ai-meter-is-subscribed","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Revision (2026-09-19): the AI meter is subscribed","x":"A tenth meter, and a fourth trace source. MMCA.Common.AI is in the chain now. The subscription block ends with .AddMeter(AiTelemetryName)…"},"409":{"u":"/docs/adr/041-observability-and-telemetry.html#revision-2026-10-01","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"A third metrics cost knob, and the citations follow the telemetry code into its own file. ASP.NET Core metrics are no longer unconditional. ConfigureMetrics now starts by calling…"},"410":{"u":"/docs/adr/042-device-capability-abstraction.html","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records"},"411":{"u":"/docs/adr/042-device-capability-abstraction.html#status","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-10, amended 2026-07-17, 2026-07-23, 2026-08-14, 2026-08-29 and 2026-09-03). The 2026-08-29 amendment adds the UseMmcaMauiErrorHandling last-chance handlers and…"},"412":{"u":"/docs/adr/042-device-capability-abstraction.html#context","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Context","x":"The consumer apps ship the same Blazor component set through three heads: MAUI Blazor Hybrid (Android/iOS/MacCatalyst/Windows), Blazor Server SSR, and WebAssembly. Native device…"},"413":{"u":"/docs/adr/042-device-capability-abstraction.html#decision","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Decision","x":"Add a per-capability contract layer to MMCA.Common.UI and a dedicated package, MMCA.Common.UI.Maui, carrying the native implementations. - One small interface per capability, no…"},"414":{"u":"/docs/adr/042-device-capability-abstraction.html#rationale","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Rationale","x":"- A god IDeviceCapabilities interface would force every head to implement everything and turn each new capability into a breaking change; per-capability contracts are open/closed…"},"415":{"u":"/docs/adr/042-device-capability-abstraction.html#trade-offs","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Trade-offs","x":"- A second build runner raises release surface: ubuntu and windows must both succeed for a whole release. Accepted; the publish-maui job is gated by the same tag and SBOM…"},"416":{"u":"/docs/adr/042-device-capability-abstraction.html#revision-2026-10-01","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. The Decision bullet on safe defaults no longer points at a Fallbacks/ folder, which does not exist: each null fallback sits beside its contract…"},"417":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records"},"418":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#status","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-15). Revised 2026-07-28 (the Android https App Links leg is recorded as shipped, the outstanding Android item is restated as the served certificate fingerprint,…"},"419":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#context","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Context","x":"Three mobile flows all need a URL to leave the web world and land inside the MAUI app: 1. Shared links and QR codes. The share sheet and QR codes carry ordinary https web URLs.…"},"420":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#decision","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Decision","x":"- Custom-scheme returnUrl allowlist in the framework. CompleteAsync consults OAuth:AllowedReturnUrlSchemes (a config array, default empty). When the challenge's stashed returnUrl…"},"421":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#rationale","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Rationale","x":"- Reusing the single-use-code exchange keeps the token-never-in-URL invariant identical across web and native; the only new surface is WHERE the code lands. - A scheme allowlist…"},"422":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#trade-offs","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Trade-offs","x":"- The app-facing hostname is baked into store binaries (intent filters, entitlements). The apps currently ride the Azure Container Apps default domain, which changes if the…"},"423":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-07-28","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-07-28)","x":"Correction pass from an ADR audit. No decision or behavior changed; the Status section had the Android leg backwards and the Decision section attributed the token exchange to the…"},"424":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-08-01","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-08-01)","x":"Status pass from an ADR audit. No decision and no behavior changed; the one item the previous revision left open is closed, and the anchor that revision itself introduced had…"},"425":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-08-07","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"Anchor and precision pass from an ADR audit. No decision and no behavior changed. 1. The two Program.cs anchors moved one line. MMCA.ADC commit 886fa189 (PR 100, merged…"},"426":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-08-31","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-08-31)","x":"Anchor and count pass from an ADR audit. No decision and no behavior changed. 1. The ADC Program.cs association block moved again. MMCA.ADC commit 6323a7b9 (PR 155) shifted it…"},"427":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-09-07","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The callback routing is unchanged. The boundary where a platform-supplied URI enters the app is now validated (SEC-Common-88 / SEC-ADC-65). DeepLinkDispatcher.Publish…"},"428":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-09-11","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"Anchor and correction pass from an ADR audit. No decision and no behavior changed. One citation pointed at a file that does not exist. 1. There is no DeepLinkRouteGuard in ADC.…"},"429":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-09-25","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-09-25)","x":"Anchor pass over the MMCA.ADC citations. No decision and no behavior changed. 1. The ADC association block moved up six lines. In…"},"430":{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-10-01","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Anchor pass from an ADR audit. No decision and no rationale changed. In the Decision's client-flow bullet the completion page now calls the service at…"},"431":{"u":"/docs/adr/044-native-push-delivery.html","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records"},"432":{"u":"/docs/adr/044-native-push-delivery.html#status","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-11). Amends ADR-024. The framework pipeline is implemented and inert by default; each consumer switches it on by provisioning a notification hub with platform…"},"433":{"u":"/docs/adr/044-native-push-delivery.html#context","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records","t":"Context","x":"ADR-024 established two notification channels: a durable per-user UserNotification inbox (the source of truth) and a transient SignalR push behind IPushNotificationSender. Both…"},"434":{"u":"/docs/adr/044-native-push-delivery.html#decision","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records","t":"Decision","x":"- Azure Notification Hubs as the delivery fan-out. One hub abstracts both platforms behind one API, holds the platform credentials outside our code, and its installation model…"},"435":{"u":"/docs/adr/044-native-push-delivery.html#consequences","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records","t":"Consequences","x":"- Sends fan out per 20-user chunk and per platform: an audience of N users costs ceil(N/20) 2 hub calls. Acceptable at conference scale; a template-based send can consolidate…"},"436":{"u":"/docs/adr/044-native-push-delivery.html#revision-2026-10-01","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The client half of the pipeline is no longer Null-only. MMCA.Common.UI.Maui ships credentialed token providers behind the opt-in AddMauiPushDeviceTokenProvider()…"},"437":{"u":"/docs/adr/045-managed-file-storage-and-avatars.html","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records"},"438":{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#status","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-11). Records the BR-116 amendment (ADC): avatar photos are IN scope, powered by two new framework extension points. The framework legs are implemented; each…"},"439":{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#context","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Context","x":"The MAUI capability program (ADR-042) brought MediaPicker/camera within reach, and ADC amended BR-116 to include user avatar photos. That needs binary blob storage (the databases…"},"440":{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#decision","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Decision","x":"- IFileStorageService (Application): upload-by-blob-name returning the public URI, plus idempotent delete. Default is an unconfigured Null implementation whose uploads fail with…"},"441":{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#consequences","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Consequences","x":"- The avatars container is public-read by design: avatar URLs render in tags on anonymous-visible surfaces without SAS plumbing. The random blob suffix prevents enumeration; the…"},"442":{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#revision-2026-09-07","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The 2 MB compressed upload cap bounds the bytes on the wire; it does not bound what those bytes decode to. A highly compressible image declares its dimensions in a header that…"},"443":{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#revision-2026-09-12","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Revision (2026-09-12)","x":"This record scoped managed uploads to avatars and said so: \"no other managed uploads exist\". That statement is superseded by ADR-123, which publishes speaker session materials…"},"444":{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#revision-2026-10-01","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Avatar blob deletion no longer happens as an inline storage call. Every path that retires an avatar schedules a DeleteAvatarBlobInternalCommand…"},"445":{"u":"/docs/adr/046-http-api-versioning.html","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records"},"446":{"u":"/docs/adr/046-http-api-versioning.html#status","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-15). Revised 2026-08-01 (anonymity is granted by each per-service subclass, not by ServiceInfoControllerBase; corrected the ADR-034 cross-reference, which puts…"},"447":{"u":"/docs/adr/046-http-api-versioning.html#context","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Context","x":"The framework's REST surface is served by controllers hosted in extracted service processes behind a YARP gateway. As those services evolve, a response shape has to be able to…"},"448":{"u":"/docs/adr/046-http-api-versioning.html#decision","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Decision","x":"Standardize one header-based API-versioning setup in MMCA.Common.API, adopt it in every service host through a single registration call, and keep it exercised by a shared fitness…"},"449":{"u":"/docs/adr/046-http-api-versioning.html#rationale","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Rationale","x":"- Header selection keeps URLs stable. Routing stays version-free, so gateway route maps, client URL builders, and OpenAPI paths do not fork per version; a caller opts into a…"},"450":{"u":"/docs/adr/046-http-api-versioning.html#trade-offs","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Trade-offs","x":"- The class-level version attributes are not inherited. Each per-service subclass must repeat the [ApiVersion(...)] and routing attributes (the same inheritance caveat ADR-036…"},"451":{"u":"/docs/adr/046-http-api-versioning.html#revision-2026-09-22-the-consumer-contract-is-a-committed-artifact","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Revision (2026-09-22): the consumer contract is a committed artifact","x":"The trade-off above described the OpenAPI document as a dev/CI artifact that nothing outside a running host could diff. MMCA.ADC now writes it to disk at build time.…"},"452":{"u":"/docs/adr/046-http-api-versioning.html#revision-2026-09-25-both-consumers-commit-and-gate-their-openapi-documents","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Revision (2026-09-25): both consumers commit and gate their OpenAPI documents","x":"MMCA.Store now carries the same gate, so the committed contract is a property of both consumers rather than of MMCA.ADC alone. Each repo's Directory.Build.props references…"},"453":{"u":"/docs/adr/046-http-api-versioning.html#revision-2026-10-01","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Re-anchored the citations in Decision, Trade-offs and Related, which had moved: AddCommonApiVersioning and its options…"},"454":{"u":"/docs/adr/046-http-api-versioning.html#revision-2026-10-01-v12170-the-host-registers-the-openapi-document","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Revision (2026-10-01, v1.217.0): the host registers the OpenAPI document","x":"What changed. AddCommonOpenApi used to register the documents itself through the versioning builder (AddApiVersioning().AddOpenApi(), one document per discovered API version),…"},"455":{"u":"/docs/adr/046-http-api-versioning.html#related","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Related","x":"ADR-010 (integration-event schema versioning: the asynchronous, SchemaVersion-carried, consumer-resolved axis this deliberately contrasts with; HTTP versioning here is…"},"456":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records"},"457":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#status","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-15). Revised 2026-08-07 (validator hoisted into a shared generic, the 30-second constant moved, the two apps revoke at different speeds). Revised 2026-08-23:…"},"458":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#context","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Context","x":"Soft-delete is the framework's default deletion model (ADR-005): AuditableBaseEntity.Delete() sets IsDeleted = true and EF global query filters hide the row, but the record…"},"459":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#decision","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Decision","x":"Add a shared-pipeline middleware, SoftDeletedUserMiddleware (Source/Presentation/MMCA.Common.API/Middleware/SoftDeletedUserMiddleware.cs:31, BR-133), that rejects an…"},"460":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#rationale","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Rationale","x":"- Bounds the stateless-JWT revocation gap cheaply. Stateless JWT (ADR-004) has no built-in revocation, so a deactivated account would otherwise stay usable for the full remaining…"},"461":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#trade-offs","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Trade-offs","x":"- Revocation is bounded, not immediate. A soft-deleted user whose status is cached as not-deleted keeps passing until that cache entry expires (up to 30 seconds), unless the…"},"462":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#related","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Related","x":"ADR-005 (soft-delete is the deletion model whose still-authenticated tokens this middleware revokes; deleting a user is a soft-delete, not a row removal), ADR-004 (the stateless…"},"463":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#revision-2026-08-07","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"Re-verified against current source. The decision is unchanged, but three things it described have moved: the validator implementation, the home of the 30-second constant, and the…"},"464":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#revision-2026-08-23","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"Re-verified against current source. The decision, the cache design, the fail-open policy and the per-app asymmetry are all unchanged; what moved is where the middleware's…"},"465":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#revision-2026-09-03-mmcacommon-11850","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Revision (2026-09-03, MMCA.Common 1.185.0)","x":"Re-verified against current source. The middleware, the cache design, the 30-second window and the fail-open policy are all unchanged. What changed is who writes the deleted…"},"466":{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#revision-2026-10-01","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Re-verified against current source (MMCA.Common v1.216.0). The decision, the middleware, the 30-second window, the fail-open policy and the shared marker write are unchanged. One…"},"467":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records"},"468":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#status","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-15). Revised 2026-07-21 (corrected the empty-placeholder-folder inventory and the Directory.Build.props and ADC User source citations). Revised 2026-07-28…"},"469":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#context","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Context","x":"Every entity needs an identity type. The framework's base entity is generic over that type: BaseEntity constrains it to notnull and exposes a single required init Id…"},"470":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#decision","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Decision","x":"Model every identifier as a primitive named through a global-using alias, declared per module, not as a wrapper struct. - Identity is a primitive behind an alias. Each module…"},"471":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#rationale","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Rationale","x":"- Readable signatures at zero runtime cost. GetRepository () reads as intent while the CLR sees a plain int. There is no allocation, boxing, or wrapper indirection per…"},"472":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#trade-offs","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Trade-offs","x":"- No compile-time protection against swapping same-typed identifiers. An alias is a type synonym, not a distinct type. Because most aliases resolve to int, the compiler will not…"},"473":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#related","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Related","x":"ADR-001 (the per-entity DTO mappers are parameterized by this identifier type, IEntityDTOMapper ), ADR-034 (the generic entity controllers and query contract ride on the same…"},"474":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#revision-2026-08-18","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"No decision, no behavior and no citation in this record changed. What changed is the standing of the deferral it records. The last Trade-offs entry above (\"Revisiting the trade…"},"475":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#revision-2026-08-23","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"No decision and no rationale changed. Two counts did, both because Conference gained an alias. The workspace census below is superseded by the Revision (2026-09-11), and…"},"476":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#revision-2026-09-11","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"No decision and no rationale changed. The workspace alias count did, because Store Catalog gained the Reviews aggregate's two identifiers. The census in this revision is itself…"},"477":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#revision-2026-09-19","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Revision (2026-09-19)","x":"No decision and no rationale changed. Three facts stated above did, two of them counts and one of them a claim about what exists. Conference's alias file declares nineteen…"},"478":{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#revision-2026-10-01","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision and no rationale changed. One Trade-offs statement and three Decision citations did. The Trade-offs entry on identifier swapping still treated SpeakerIdentifierType…"},"479":{"u":"/docs/adr/049-library-configureawait-policy.html","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records"},"480":{"u":"/docs/adr/049-library-configureawait-policy.html#status","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-20; measurements re-anchored 2026-08-07, 2026-08-14, 2026-08-18, 2026-08-23, 2026-08-31, 2026-09-01, 2026-09-03, 2026-09-11 and 2026-09-19). Revised 2026-09-19:…"},"481":{"u":"/docs/adr/049-library-configureawait-policy.html#context","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Context","x":"MMCA.Common ships as NuGet packages consumed by host applications, not as an application itself. Library code that awaits without ConfigureAwait(false) captures the caller's…"},"482":{"u":"/docs/adr/049-library-configureawait-policy.html#decision","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Decision","x":"Packaged non-UI framework code awaits with ConfigureAwait(false); UI component packages and application code do not. - Enforcement is a build gate, not a convention. The…"},"483":{"u":"/docs/adr/049-library-configureawait-policy.html#rationale","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Rationale","x":"- Correctness for the one consumer that already has a context. The MAUI head consumes Infrastructure/Application/API packages through DI; a sync-over-async call anywhere in that…"},"484":{"u":"/docs/adr/049-library-configureawait-policy.html#trade-offs","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Visual noise in framework source. Every await in Source/ (except UI packages) carries .ConfigureAwait(false) (324 sites at adoption; 1,115 gated sites as of the 2026-10-01…"},"485":{"u":"/docs/adr/049-library-configureawait-policy.html#related","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Related","x":"ADR-042 (the MAUI package whose synchronization context motivates the policy), ADR-027 (the same \"machine-boundary hygiene as a build gate\" posture applied to culture-explicit…"},"486":{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-08-07","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"An audit against the code. The policy did not change; three statements about it did. 1. The exemption covers three packages, not the two the Decision named. The glob is…"},"487":{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-08-14","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-08-14)","x":"A re-measurement only. The policy, the gate and the exemption are unchanged; the counts the document quotes were a week old and had moved by roughly 9%. 1. Framework site counts,…"},"488":{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-08-18","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"A re-measurement only, in the same terms as the 2026-08-14 pass. The policy, the gate and the exemption are unchanged; two of the three counted figures moved. 1. Framework site…"},"489":{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-08-23","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"A re-measurement only, in the same terms as the 2026-08-18 pass. The policy, the gate and the exemption are unchanged; both counted figures moved. 1. Framework site counts,…"},"490":{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-08-31","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-08-31)","x":"A re-measurement plus a correction to two file anchors. The policy, the gate and the exemption are unchanged. Every counted figure moved, two of the surrounding narratives did…"},"491":{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-09-01","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-09-01)","x":"A re-measurement only, in the same terms as the 2026-08-31 pass. The policy, the gate and the exemption are unchanged, the .editorconfig anchors that shifted last pass are still…"},"492":{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-09-03","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-09-03)","x":"A re-measurement plus one anchor correction, in the same terms as the 2026-09-01 pass. The policy, the gate and the exemption are unchanged, the .editorconfig anchors are still…"},"493":{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-09-11","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"A re-measurement plus one anchor correction, in the same terms as the 2026-09-03 pass. The policy, the gate and the exemption are unchanged and the .editorconfig anchors are…"},"494":{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-09-19","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-09-19)","x":"A re-measurement in the same terms as the 2026-09-11 pass, covering the two counted figures that move (framework sites and the consumer-scale upper bound) and the…"},"495":{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-10-01","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"A count and anchor refresh only. No decision or rationale changed: the policy, the gate (MMCA.Common/.editorconfig:833) and the UI exemption (:836) stand as written. The…"},"496":{"u":"/docs/adr/050-jwt-refresh-token-rotation.html","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records"},"497":{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#status","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Status","x":"Superseded by ADR-097 (2026-09-01). Originally Accepted (2026-07-21, storage model recorded as superseded 2026-08-26). The body below is retained as the historical record (its…"},"498":{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#context","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Context","x":"Identity issues two credentials on every successful sign-in: a short-lived, stateless JWT access token that every service validates by signature and expiry (ADR-004), and a…"},"499":{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#decision","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Decision","x":"Mint a stateless JWT access token plus a single, server-stored refresh token that rotates on every use, with a token mismatch triggering revocation. - Access token is stateless;…"},"500":{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#rationale","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Rationale","x":"- Short access token plus refresh keeps the hot path stateless. Every service validates the access token with no store lookup (ADR-004); the short exp bounds the revocation gap,…"},"501":{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#trade-offs","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Trade-offs","x":"- One refresh token per user means one live session. A new login overwrites the single stored token, so signing in on a second device invalidates the first device's refresh…"},"502":{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#revision-2026-10-01","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Three statements were corrected against current source. The removed UpdateRefreshToken and RevokeRefreshToken no longer appear in either Domain…"},"503":{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#related","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the stateless RS256/JWKS access token this refresh flow reissues, and the algorithm pinning GetPrincipalFromExpiredToken relies on), ADR-032 (the password hashing that…"},"504":{"u":"/docs/adr/051-client-auth-token-lifecycle.html","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records"},"505":{"u":"/docs/adr/051-client-auth-token-lifecycle.html#status","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-23). Revised 2026-08-14 (SetTokensAsync writes the refresh token and the access token under one shared guard, so a failed refresh-token write also drops both…"},"506":{"u":"/docs/adr/051-client-auth-token-lifecycle.html#context","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Context","x":"ADR-022 and ADR-050 describe the two server halves of authentication: the Blazor host's HttpOnly session cookie that survives SSR prerender (ADR-022), and the Identity service's…"},"507":{"u":"/docs/adr/051-client-auth-token-lifecycle.html#decision","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Decision","x":"Model the client token lifecycle as three small abstractions (ITokenStorageService, the freshness-checked persistence every caller consumes; ISecureTokenStore, raw persistence…"},"508":{"u":"/docs/adr/051-client-auth-token-lifecycle.html#rationale","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Rationale","x":"- One application surface, three storage stories. Pages, services, and the HTTP pipeline talk to ITokenStorageService and AuthenticationStateProvider only; the head-specific…"},"509":{"u":"/docs/adr/051-client-auth-token-lifecycle.html#trade-offs","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Trade-offs","x":"- The browser heads depend on the same-origin UI host. SameOriginProxyTokenRefresher only works where the UI host serves the /auth/session/ endpoints; a browser head deployed…"},"510":{"u":"/docs/adr/051-client-auth-token-lifecycle.html#related","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Related","x":"ADR-022 (the Blazor host's HttpOnly session cookie and the /auth/session/ endpoints the browser refresher proxies through), ADR-050 (the single rotating refresh token with reuse…"},"511":{"u":"/docs/adr/051-client-auth-token-lifecycle.html#revision-2026-08-07","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"The MAUI half of ITokenStorageService is no longer app-local. The original Decision left the SecureStorage-backed implementation in each app because it depends on the MAUI…"},"512":{"u":"/docs/adr/051-client-auth-token-lifecycle.html#revision-2026-08-14","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Revision (2026-08-14)","x":"SetTokensAsync closed a gap the original hoist left open. Point 3 above previously described the method as writing the refresh token first and dropping both tokens only when the…"},"513":{"u":"/docs/adr/051-client-auth-token-lifecycle.html#revision-2026-08-31","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Revision (2026-08-31)","x":"MAUI storage is two classes, and the freshness check the browser heads always had is now on every head. 1. A raw store beneath the storage service. ISecureTokenStore is the third…"},"514":{"u":"/docs/adr/051-client-auth-token-lifecycle.html#revision-2026-10-01","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Citations in Decision and Trade-offs are re-anchored to the current source (DirectApiTokenRefresher, JsFetchSessionCookieSync,…"},"515":{"u":"/docs/adr/052-background-job-execution.html","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records"},"516":{"u":"/docs/adr/052-background-job-execution.html#status","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Status","x":"Superseded by ADR-121 (2026-09-11). The expensive half of this decision (Wait full mode, in-queue dedup, a 409 refusal, the session-scoring queue and drain) moved to durable…"},"517":{"u":"/docs/adr/052-background-job-execution.html#context","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Context","x":"Some requests trigger work that cannot run inside the request: an AI scoring pass over an event's sessions takes minutes and issues one paid API call per session, and a…"},"518":{"u":"/docs/adr/052-background-job-execution.html#decision","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Decision","x":"In-process background work runs as a bounded queue plus a single-reader hosted drain. Nothing starts an untracked Task from a request. - A bounded Channel per job kind,…"},"519":{"u":"/docs/adr/052-background-job-execution.html#rationale","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Rationale","x":"- The host lifetime is the point. A BackgroundService is the only in-process shape the host can cancel and await. Everything else in the decision follows from wanting that. - The…"},"520":{"u":"/docs/adr/052-background-job-execution.html#trade-offs","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Trade-offs","x":"- In-process only. The queue does not survive a restart and does not span replicas. Accepted because every current job is either ephemeral (a lost broadcast is a missed UI…"},"521":{"u":"/docs/adr/052-background-job-execution.html#related","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox, for work that must be durable and cross-process, and the post-commit deferral this relies on), ADR-008 (the broker, for cross-service work), ADR-014 (the…"},"522":{"u":"/docs/adr/052-background-job-execution.html#revision-2026-08-23","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"The decision is unchanged: post-commit work is still enqueued only once the write is durable. What changed is the record of how. This ADR stated the domain-event handler as the…"},"523":{"u":"/docs/adr/052-background-job-execution.html#revision-2026-08-31","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Revision (2026-08-31)","x":"The decision and the safety property are unchanged: post-commit work is still enqueued only once the write is durable. What changed is who keeps the ordering for shape 2. Three…"},"524":{"u":"/docs/adr/052-background-job-execution.html#revision-2026-09-19","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Revision (2026-09-19)","x":"The decision and the safety property are unchanged: post-commit work is still enqueued only once the write is durable. What changed is where the save sits in the one remaining…"},"525":{"u":"/docs/adr/053-dual-registry-package-publishing.html","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records"},"526":{"u":"/docs/adr/053-dual-registry-package-publishing.html#status","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-25). Amended (2026-07-25) to put the pre-decision Context statements in the past tense, to record the MMCA. ID prefix reservation as then-pending, to scope the…"},"527":{"u":"/docs/adr/053-dual-registry-package-publishing.html#context","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Context","x":"The MMCA.Common. packages have shipped to GitHub Packages since the first release (the package list and its count are generated and CI-gated in MMCA.Common/FACTS.md:19-43, so…"},"528":{"u":"/docs/adr/053-dual-registry-package-publishing.html#decision","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Decision","x":"Every release publishes to both registries, from the same tag, in the same workflow run. - release.yml keeps its existing dotnet nuget push to…"},"529":{"u":"/docs/adr/053-dual-registry-package-publishing.html#rationale","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Rationale","x":"- The install line has to be true. Documentation that cannot be followed is worse than no documentation, because the reader concludes the project is broken rather than that the…"},"530":{"u":"/docs/adr/053-dual-registry-package-publishing.html#trade-offs","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Trade-offs","x":"- A published version can never be withdrawn. nuget.org allows unlisting, not deletion. A bad release is now permanent public history, which raises the stakes on the release…"},"531":{"u":"/docs/adr/053-dual-registry-package-publishing.html#revision-2026-10-01","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision changed; the record is re-pinned to release.yml as of MMCA.Common v1.216.0 and two statements are corrected. Re-pinned: the GitHub Packages pushes (release.yml:118,…"},"532":{"u":"/docs/adr/053-dual-registry-package-publishing.html#related","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Related","x":"ADR-016 (lockstep versioning: every package ships at one version, so both registries receive the same set of ids per release, enumerated in MMCA.Common/FACTS.md:19-43), ADR-038…"},"533":{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records"},"534":{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#status","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-25). Amended (2026-07-28): Store's reconciliation sweep now derives from PeriodicBackgroundService, so the shared-loop and adoption paragraphs are rewritten and…"},"535":{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#context","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Context","x":"Checkout spans a boundary no transaction covers. CheckOutHandler commits the order insert, the cart transition and the atomic conditional stock decrements in one local…"},"536":{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#decision","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Decision","x":"Multi-step workflows are choreographed sagas: each step raises a domain event, and the follow-up or compensating action lives in its own handler. A periodic reconciliation sweep…"},"537":{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#rationale","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Rationale","x":"- No two-phase commit is available, and none is wanted. Transactions are per data source and best-effort sequential (ADR-006), and an external payment provider cannot enlist in a…"},"538":{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#trade-offs","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Trade-offs","x":"- Inconsistency is bounded, not eliminated. Between the cancellation commit and the compensation commit, stock is held against a cancelled order. Between a dropped webhook and…"},"539":{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#revision-2026-09-11-the-unpaid-order-deadline-moves-onto-the-order-and-the-sweep-becomes-its-backstop","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Revision (2026-09-11): the unpaid-order deadline moves onto the order, and the sweep becomes its backstop","x":"Expiring an unpaid order is no longer something a scan notices. Every order now arms its own deadline, and the sweep's expiry pass stays exactly where it was, underneath.…"},"540":{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#revision-2026-10-01","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The idempotency guarantee is now one transaction rather than one SaveChanges. OrderCancelledSagaHandler runs the whole restoration as a single ExecuteInTransactionAsync delegate…"},"541":{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#related","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox delivery and retry this leans on for compensation redelivery; this record says what the redelivered handler must do), ADR-006 (which accepts \"no…"},"542":{"u":"/docs/adr/055-repository-and-specification-contract.html","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records"},"543":{"u":"/docs/adr/055-repository-and-specification-contract.html#status","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-25). Revised 2026-08-01 (qualified the \"referenced nowhere\" claim about IEntityReader / IEntityQuerier: an ADC doc comment now names IEntityQuerier, though no…"},"544":{"u":"/docs/adr/055-repository-and-specification-contract.html#context","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Context","x":"Every read an application handler performs has to come from somewhere, and the shape of that contract decides whether the module can still be lifted into its own service later…"},"545":{"u":"/docs/adr/055-repository-and-specification-contract.html#decision","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Decision","x":"Data access is repository plus specification: interface-segregated read interfaces for the operations, expression-tree specifications for the predicates, and a build-failing…"},"546":{"u":"/docs/adr/055-repository-and-specification-contract.html#rationale","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Rationale","x":"- A narrow interface is the enforcement, not a style preference. A handler that asks for IEntityReader cannot reach TableNoTracking, because the member is not on the interface.…"},"547":{"u":"/docs/adr/055-repository-and-specification-contract.html#trade-offs","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- The ISP split is guidance, not yet a wired dependency. (Superseded by the Revision (2026-08-21) below: the split has real dependents shipped in both MMCA.ADC and MMCA.Store,…"},"548":{"u":"/docs/adr/055-repository-and-specification-contract.html#related","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-007 and ADR-008 (the extraction promise the queryable ban exists to protect), ADR-015 (the fitness-function machinery that runs this rule and its per-repo maps), ADR-014 (the…"},"549":{"u":"/docs/adr/055-repository-and-specification-contract.html#revision-2026-08-18","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Five changes, four of them widening the contract and one of them fixing a correctness defect. The decision this record states is unchanged: data access is still repository plus…"},"550":{"u":"/docs/adr/055-repository-and-specification-contract.html#revision-2026-08-21","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Revision (2026-08-21)","x":"Nothing in the contract changed; its consumers did. This revision records the first real adoption of the two surfaces this record had honestly flagged as unconsumed: the narrow…"},"551":{"u":"/docs/adr/055-repository-and-specification-contract.html#revision-2026-08-31","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Revision (2026-08-31)","x":"Again nothing in the decision changed. Two things did: the querier grew a second time, and the reference application joined the two production apps as a consumer of the narrow…"},"552":{"u":"/docs/adr/055-repository-and-specification-contract.html#revision-2026-10-01","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; the consumers grew again and the anchors moved. MMCA.ADC now declares IEntityReader / IEntityQuerier at forty read-only sites across…"},"553":{"u":"/docs/adr/056-blazor-render-mode-strategy.html","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records"},"554":{"u":"/docs/adr/056-blazor-render-mode-strategy.html#status","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-28). Revised 2026-08-14: re-anchored the host, base-class and AppHost citations to their current lines; scoped the \"only @rendermode attributes\" enumeration to…"},"555":{"u":"/docs/adr/056-blazor-render-mode-strategy.html#context","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Context","x":"Both web applications are Blazor Web Apps: a static server-rendered (SSR) prerender pass produces the first HTML, then an interactive runtime takes over, either a Blazor Server…"},"556":{"u":"/docs/adr/056-blazor-render-mode-strategy.html#decision","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Decision","x":"Run one render mode for the entire routable component tree, chosen at the application root, default InteractiveAuto, with prerendering left on and the resulting double fetch…"},"557":{"u":"/docs/adr/056-blazor-render-mode-strategy.html#rationale","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Rationale","x":"- InteractiveAuto gets both halves without asking page authors to choose. The first visit gets the Server circuit's immediate interactivity while the WASM bundle downloads in the…"},"558":{"u":"/docs/adr/056-blazor-render-mode-strategy.html#trade-offs","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Trade-offs","x":"- Everything shared has to run in both runtimes. The WASM-compatibility layer rule (MMCA.Common.LayerEnforcement.targets:102-115) forbids the shared UI package from touching…"},"559":{"u":"/docs/adr/056-blazor-render-mode-strategy.html#revision-2026-10-01","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. The prerender-skip guard bullet now lists four stated reasons rather than three: ADCHome skips its prerender fetch because an un-timed call to a…"},"560":{"u":"/docs/adr/056-blazor-render-mode-strategy.html#related","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Related","x":"ADR-022 (reads the HttpOnly session cookie during the SSR prerender pass this decision keeps enabled), ADR-027 (flows one culture through the SSR to Server to WASM sequence this…"},"561":{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records"},"562":{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#status","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-28). Revised 2026-08-01: the diff now fails closed in both repos (the true is gone and MMCA.Store's build-and-test checkout sets fetch-depth: 0), so the…"},"563":{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#context","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Context","x":"ADR-030 decides who applies a migration: every service host runs DatabaseInitStrategy = Migrate and self-applies its pending EF Core migrations at startup as the sole migrator,…"},"564":{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#decision","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Decision","x":"Schema changes follow expand/contract, and a CI step enforces the contract half. - Expand now, contract later, as a written rule. Adding nullable columns, new tables and new…"},"565":{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#rationale","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Rationale","x":"- Rollback is one-way for schema, so the check belongs where the drop is still cheap. The only moment a destructive migration can be reconsidered for free is the PR that adds it;…"},"566":{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#trade-offs","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- Three operations, not a model of compatibility. AlterColumn narrowing a type or flipping a column to NOT NULL, DropForeignKey, DropPrimaryKey, DropSchema, RenameColumn and a…"},"567":{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#revision-2026-10-01","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Both deploy.yml files grew, so every workflow citation is re-anchored to the current lines: the startup-migrate note…"},"568":{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#related","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-030 (decides that each service self-applies its migrations at startup, which is precisely why a rolled-back revision meets the new schema; this ADR constrains what those…"},"569":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records"},"570":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#status","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-28; revised 2026-08-14, 2026-08-18, 2026-08-23, and 2026-09-03). Revised 2026-09-22 (the layer rules also ship as compile-time MSBuild targets inside…"},"571":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#context","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Context","x":"ADR-015 turned the architecture invariants into build-gating tests, and drew its own boundary explicitly: the fitness suite asserts \"structure / registration, not runtime…"},"572":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#decision","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Decision","x":"Ship the runtime conformance suites in the MMCA.Common.Testing package as abstract behavioral bases that each consuming host subclasses, and run every one of them against a host…"},"573":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#rationale","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Rationale","x":"- Runtime conformance is the half ADR-015 excluded. Structural rules answer \"is the code shaped correctly\"; these suites answer \"does the composed host behave correctly\". A host…"},"574":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#trade-offs","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Trade-offs","x":"- Opt-in per host, exactly like ADR-015. The framework ships the suites; a host gets the gate only once someone writes the subclass. That is the same audit-the-inventory caveat,…"},"575":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#revision-2026-09-10","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Revision (2026-09-10)","x":"The security-headers suite reaches both UI web hosts, so the adoption inventory above is no longer Gateway-only for it. ADC added MMCA.ADC/Tests/Hosts/MMCA.ADC.UI.Web.Tests,…"},"576":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#revision-2026-09-22-the-layer-rules-also-ship-as-compile-time-targets","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Revision (2026-09-22): the layer rules also ship as compile-time targets","x":"The runtime suites above judge compiled assemblies, which places the first failure in the architecture test tier. MMCA.Common.Shared now also packs…"},"577":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#revision-2026-09-25-committed-openapi-documents-and-page-test-bases-in-testingui","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Revision (2026-09-25): committed OpenAPI documents, and page-test bases in Testing.UI","x":"The live-document guard now sits beside a committed document in both consumers. MMCA.ADC and MMCA.Store each write Source/Services/ /openapi/ .json at build time, commit it, and…"},"578":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#revision-2026-10-01","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; the seven bases still ship in MMCA.Common.Testing and are subclassed as inventoried above. Three statements are corrected. The gateway probe…"},"579":{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#related","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (the structural / registration fitness layer this complements; its stated non-goal, \"not runtime behavior\", is exactly this ADR's scope, and the two tiers ship as two…"},"580":{"u":"/docs/adr/059-module-contract-and-composition.html","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records"},"581":{"u":"/docs/adr/059-module-contract-and-composition.html#status","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-28; revised 2026-08-14)."},"582":{"u":"/docs/adr/059-module-contract-and-composition.html#context","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Context","x":"The framework's headline claim is that an application is built as a modular monolith and later extracted into services without rewriting business logic. ADR-008 states the…"},"583":{"u":"/docs/adr/059-module-contract-and-composition.html#decision","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Decision","x":"Make IModule the single composition contract, discover implementations by reflection, register them in topological dependency order, and represent a disabled module by stub…"},"584":{"u":"/docs/adr/059-module-contract-and-composition.html#rationale","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Rationale","x":"- Reflection discovery keeps hosts out of the module registration business. A host names the assemblies and gets whatever IModule types they contain: no per-module registration…"},"585":{"u":"/docs/adr/059-module-contract-and-composition.html#trade-offs","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Trade-offs","x":"- The composition root has to name every module assembly. Discovery scans exactly the list it is handed (ModuleLoader.cs:59-65), so adding a module to a host is a third edit…"},"586":{"u":"/docs/adr/059-module-contract-and-composition.html#revision-2026-10-01","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The decision is unchanged; four statements are corrected against the code and the citations are re-anchored. A GetTypes() failure is no longer a skip: a…"},"587":{"u":"/docs/adr/059-module-contract-and-composition.html#related","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Related","x":"ADR-008 (the extraction topology that consumes this model: \"a service is the monolith with one module enabled\" is a statement about ModuleLoader plus the Disabled stubs, cited…"},"588":{"u":"/docs/adr/060-performance-regression-gate.html","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records"},"589":{"u":"/docs/adr/060-performance-regression-gate.html#status","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-28). Revised 2026-08-01 (corrected the count in Trade-offs: the single ratio floor names two of the eight benchmarks, so six, not seven, are gated on…"},"590":{"u":"/docs/adr/060-performance-regression-gate.html#context","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Context","x":"Rubric section 12 asks for hot-path efficiency that is measured, not assumed (Website/docs-src/governance/ArchitectureEvaluationCriteria.md:386). MMCA.Common has a…"},"591":{"u":"/docs/adr/060-performance-regression-gate.html#decision","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Decision","x":"Measure the hot-path suite on every code PR and verify the results against a committed baseline that carries two rule kinds: absolute allocation ceilings where the measurement is…"},"592":{"u":"/docs/adr/060-performance-regression-gate.html#rationale","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Rationale","x":"- A ratio is a property of the code; an absolute nanosecond count is a property of the runner. Both benchmarks in a floor run in the same process, on the same machine, in the…"},"593":{"u":"/docs/adr/060-performance-regression-gate.html#trade-offs","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The Short job cannot see small latency regressions. Three warmup and three iterations (ci.yml:386) give wide confidence intervals: enough for a 1000x floor and for counting…"},"594":{"u":"/docs/adr/060-performance-regression-gate.html#revision-2026-10-01","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. The consumer load-freshness jobs no longer carry the recency query and the break-glass check inline: both MMCA.ADC and MMCA.Store now call the…"},"595":{"u":"/docs/adr/060-performance-regression-gate.html#related","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (structural fitness functions, which explicitly stop at structure and registration; this is their runtime-cost counterpart), ADR-038 (the other build-gating control set,…"},"596":{"u":"/docs/adr/061-runtime-secret-management.html","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records"},"597":{"u":"/docs/adr/061-runtime-secret-management.html#status","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-01; vault-backed configuration source recorded and citations re-anchored 2026-08-23; gateway synthetic-traffic secret recorded and citations re-anchored…"},"598":{"u":"/docs/adr/061-runtime-secret-management.html#context","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Context","x":"A Container App can hold a credential two ways: as a literal value in the app's own secrets collection, or as a reference to a Key Vault secret that the platform resolves at…"},"599":{"u":"/docs/adr/061-runtime-secret-management.html#decision","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Decision","x":"Every production secret lives in Azure Key Vault and reaches the app as a keyVaultUrl secret reference resolved by a user-assigned managed identity; the same identity also lets a…"},"600":{"u":"/docs/adr/061-runtime-secret-management.html#rationale","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Rationale","x":"- A reference has one home; a literal has as many homes as it has consumers. Three vault secrets in each repo are referenced by more than one app: Redis by all four ADC services…"},"601":{"u":"/docs/adr/061-runtime-secret-management.html#trade-offs","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Trade-offs","x":"- One identity means vault-wide read for every app that carries it. A Key Vault Secrets User grant is scoped to the vault, so any app running as the shared identity can read…"},"602":{"u":"/docs/adr/061-runtime-secret-management.html#revision-2026-09-07","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Two changes from the 2026-09-07 security review. 1. Per-service Service Bus SAS rules (SEC-ADC-26 / SEC-Store-38). One namespace-wide rule shared by every service means any one…"},"603":{"u":"/docs/adr/061-runtime-secret-management.html#revision-2026-09-10","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Revision (2026-09-10)","x":"ADC's Gateway now reads the vault like every other deployable, closing the one adoption difference above. MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/Program.cs:66 calls…"},"604":{"u":"/docs/adr/061-runtime-secret-management.html#revision-2026-10-01","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Every main.bicep, deploy.yml, Program.cs, sample and CI citation in the sections above is re-anchored, and the secret counts (nineteen in ADC,…"},"605":{"u":"/docs/adr/061-runtime-secret-management.html#related","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Related","x":"ADR-037 (037-field-level-encryption-at-rest.md:108-110 directs a consumer to keep the field-encryption key in Key Vault but decides no delivery mechanism, and nothing wires that…"},"606":{"u":"/docs/adr/062-slo-alerting-as-code.html","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records"},"607":{"u":"/docs/adr/062-slo-alerting-as-code.html#status","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-01). Revised 2026-08-18: Store's two operational extras (the outbox-dead-letter scheduled query rule and the outside-in Gateway availability web test with its…"},"608":{"u":"/docs/adr/062-slo-alerting-as-code.html#context","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Context","x":"ADR-041 standardized what the fleet emits: RED histograms off the CQRS pipeline, an outbox dead-letter counter, correlation ids, exporters, and the cost knobs that keep ingestion…"},"609":{"u":"/docs/adr/062-slo-alerting-as-code.html#decision","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Decision","x":"Declare each consumer's SLO alerts as data in its Bicep template, materialize them as Log Analytics scheduled query rules, and make the alert-to-runbook pairing a build gate…"},"610":{"u":"/docs/adr/062-slo-alerting-as-code.html#rationale","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Rationale","x":"- Alerts as data, not as portal state. One array is reviewable in a PR, diffable across environments, and re-deployable; the rules, the workbook, and the notification channel are…"},"611":{"u":"/docs/adr/062-slo-alerting-as-code.html#trade-offs","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- It is a text gate over IaC, not a check against deployed state. The base matches literal anchors and regexes in the template and headings in markdown. It proves the two files…"},"612":{"u":"/docs/adr/062-slo-alerting-as-code.html#revision-2026-09-07","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The alert-to-runbook model is unchanged. Store's rule set grew by three, in one block from the 2026-09-07 security review (MMCA.Store/infra/main.bicep:458-459). 1. A security…"},"613":{"u":"/docs/adr/062-slo-alerting-as-code.html#revision-2026-10-01","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Both templates grew by 19 lines above the alert block, so every main.bicep citation in the current-state sections is re-anchored: sloAlertSpecs…"},"614":{"u":"/docs/adr/062-slo-alerting-as-code.html#related","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-041 (the telemetry this alerts on top of: it defines emission, instrumentation and cost knobs and stops before thresholds, severities and runbooks), ADR-009 (recovery…"},"615":{"u":"/docs/adr/063-accessibility-conformance-gate.html","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records"},"616":{"u":"/docs/adr/063-accessibility-conformance-gate.html#status","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-01). Revised 2026-08-14: refreshed the E2ETestBase helper line anchors (explanatory comments were added above ScanGridAsync), the two consumer suite scan counts…"},"617":{"u":"/docs/adr/063-accessibility-conformance-gate.html#context","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Context","x":"Accessibility was documented before it was enforced. The narrative guide (common-ACCESSIBILITY.md, rubric section 21) named WCAG 2.1 AA as the target for the shared…"},"618":{"u":"/docs/adr/063-accessibility-conformance-gate.html#decision","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Decision","x":"Ship WCAG 2.1 AA as a named, versioned test contract in MMCA.Common.Testing.E2E, assert it from the package's own workflow bases, and wire it as a required merge check and a…"},"619":{"u":"/docs/adr/063-accessibility-conformance-gate.html#rationale","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Rationale","x":"- A named constant is the contract. Putting the rule set in a shipped, referenced symbol rather than in each repo's test setup means \"what WCAG 2.1 AA means here\" has exactly one…"},"620":{"u":"/docs/adr/063-accessibility-conformance-gate.html#trade-offs","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- Best-practice rules are out of scope, deliberately. Findings axe would classify as best practice (and anything WCAG AAA) are not measured at all, so the gate can be green on a…"},"621":{"u":"/docs/adr/063-accessibility-conformance-gate.html#revision-2026-10-01","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Citations are re-anchored to current source: the Identity base assertions (UserLoginTestsBase.cs:82, UserRegistrationTestsBase.cs:95,…"},"622":{"u":"/docs/adr/063-accessibility-conformance-gate.html#related","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (architecture fitness functions: the structural tier this parallels at the browser tier, and the same invariant-over-discipline posture), ADR-058 (runtime conformance…"},"623":{"u":"/docs/adr/064-deploy-recency-gates.html","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records"},"624":{"u":"/docs/adr/064-deploy-recency-gates.html#status","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-01). Revised 2026-08-07: the MMCA.Helpdesk workflow inventory below was corrected (it also carries release-templates.yml, and its ci.yml runs two jobs, not…"},"625":{"u":"/docs/adr/064-deploy-recency-gates.html#context","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Context","x":"A production rollout in both deployed apps waits on a list of jobs in deploy.needs (MMCA.ADC/.github/workflows/deploy.yml:1185, MMCA.Store/.github/workflows/deploy.yml:1136).…"},"626":{"u":"/docs/adr/064-deploy-recency-gates.html#decision","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Decision","x":"A production deploy is blocked not only on green tests but on proof of recency for out-of-band verification: four gates assert that a real drill, a real load run, a real broker…"},"627":{"u":"/docs/adr/064-deploy-recency-gates.html#rationale","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Rationale","x":"- A proof with no expiry date is documentation, not a control. ADR-009 already required the drill to be recorded, and recording it was the honest half of the problem; a record…"},"628":{"u":"/docs/adr/064-deploy-recency-gates.html#trade-offs","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Trade-offs","x":"- An unrelated stale proof blocks an unrelated deploy. A one-line hotfix does not ship when the monthly k6 cron did not fire, and the failure surfaces after merge: the gate job…"},"629":{"u":"/docs/adr/064-deploy-recency-gates.html#revision-2026-10-01","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The four gate bodies no longer live in each repo's deploy.yml. MMCA.Common v1.216.0 (463) added the composite action MMCA.Common/.github/actions/freshness-gate/action.yml, and…"},"630":{"u":"/docs/adr/064-deploy-recency-gates.html#related","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Related","x":"ADR-009 (states the recovery objectives and requires that a restore be drilled and recorded; this record decides that a deploy is blocked on how recently that drill, and the…"},"631":{"u":"/docs/adr/065-scaffolding-templates.html","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","x":"Status: Accepted (2026-08-02). Revised 2026-08-07: the staged analyzer delta relaxes three rules rather than one; mmca-module prints seven wire-ups rather than five, and a…"},"632":{"u":"/docs/adr/065-scaffolding-templates.html#context","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","t":"Context","x":"Build by hand is accurate and complete, and phases 1 through 6 of it are transcription work (common-BUILD-BY-HAND.md:98 through :1255). Its own instruction for the load-bearing…"},"633":{"u":"/docs/adr/065-scaffolding-templates.html#decision","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","t":"Decision","x":"Ship a dotnet new template pack, MMCA.Templates, containing four templates: The template content is the MMCA.Helpdesk reference application itself, staged at pack time.…"},"634":{"u":"/docs/adr/065-scaffolding-templates.html#rationale","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","t":"Rationale","x":"Deriving from the seed rather than maintaining a template tree is the whole design. A hand-maintained copy of a 12-project solution drifts within one release, and drift in a…"},"635":{"u":"/docs/adr/065-scaffolding-templates.html#trade-offs","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","t":"Trade-offs","x":"- One documented one-time fixup in every generated app, above, covering all three relaxed rules. The alternative to the SA1210 half of the delta was moving every app-local using…"},"636":{"u":"/docs/adr/065-scaffolding-templates.html#revision-2026-10-01","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. The frozen wire contract is now keyed by each event's [EventName] value rather than its CLR type name…"},"637":{"u":"/docs/adr/066-broker-transport-selection.html","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records"},"638":{"u":"/docs/adr/066-broker-transport-selection.html#status","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-07). Revised 2026-08-14 (source citations re-anchored; the ADC AppHost comment that used to say no WithBroker() was wired has been corrected in code, so the…"},"639":{"u":"/docs/adr/066-broker-transport-selection.html#context","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Context","x":"ADR-003 decides that integration events leave an aggregate through the outbox and are published by OutboxProcessor via IMessageBus, and it settles the dispatch question…"},"640":{"u":"/docs/adr/066-broker-transport-selection.html#decision","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Decision","x":"Keep one IMessageBus abstraction with a three-value transport selector, choose the value at the deployment edge (never in application code), configure both broker transports…"},"641":{"u":"/docs/adr/066-broker-transport-selection.html#rationale","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Rationale","x":"- The transport is a deployment fact, so it lives at the deployment edge. The only difference between a laptop and production is two environment variables set by the AppHost or…"},"642":{"u":"/docs/adr/066-broker-transport-selection.html#trade-offs","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Trade-offs","x":"- Two brokers means two behaviors to keep aligned. Configuration parity is enforced by one code path, but the products still differ (Service Bus supports delayed redelivery…"},"643":{"u":"/docs/adr/066-broker-transport-selection.html#revision-2026-09-07","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The transport selection is unchanged. The credential topology under it is (SEC-ADC-26 / SEC-Store-38). Both repos previously sourced their Service Bus connection strings from one…"},"644":{"u":"/docs/adr/066-broker-transport-selection.html#revision-2026-10-01","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The local Service Bus emulator path is no longer ADC-only. Store's AppHost selects its broker once on STOREBROKER=servicebus: set, it calls AddServiceBusEmulatorBroker over the…"},"645":{"u":"/docs/adr/066-broker-transport-selection.html#related","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox that feeds IMessageBus; this ADR picks the transport underneath it), ADR-016 (the MassTransit v8 pin the emulator tier must work within, which is why the…"},"646":{"u":"/docs/adr/067-ui-module-shell-composition.html","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records"},"647":{"u":"/docs/adr/067-ui-module-shell-composition.html#status","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-07). Revised 2026-08-29: records the component-vendor choice (MudBlazor) that the Context already scoped to this ADR, and the IToastService / IAppDialogService…"},"648":{"u":"/docs/adr/067-ui-module-shell-composition.html#context","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Context","x":"ADR-059 decided how a module plugs into the server: an IModule implementation is discovered by reflection, registered in topological order, and a host composes an application out…"},"649":{"u":"/docs/adr/067-ui-module-shell-composition.html#decision","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Decision","x":"Ship the application shell in the framework package and let each module plug into it by implementing IUIModule, resolved from DI as IEnumerable . - The contract is four members,…"},"650":{"u":"/docs/adr/067-ui-module-shell-composition.html#rationale","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Rationale","x":"- One composition model across both tiers. A module already declares its server-side surface through IModule (ADR-059); declaring its UI surface through IUIModule means \"add a…"},"651":{"u":"/docs/adr/067-ui-module-shell-composition.html#trade-offs","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Trade-offs","x":"- Assembly is required even when it carries no route. A host-only module that contributes only a layout component still has to return an assembly, which then joins…"},"652":{"u":"/docs/adr/067-ui-module-shell-composition.html#revision-2026-10-01","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Re-anchored the citations that moved: Routes.razor (inject :7, router :12-14, AuthorizeRouteView :27-50), NavMenu.razor (inject :9, filters…"},"653":{"u":"/docs/adr/067-ui-module-shell-composition.html#related","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Related","x":"ADR-059 (the server-side IModule contract this mirrors in the presentation layer), ADR-056 (the render-mode strategy for the web heads, which decides how these components render…"},"654":{"u":"/docs/adr/068-value-objects-as-validated-primitives.html","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records"},"655":{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#status","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-07). Revised 2026-08-31 (Money's non-validating construction paths named, Currency's converter counted among the serialization annotations, Address added to the…"},"656":{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#context","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Context","x":"A domain model has two kinds of small type: the identity of a thing, and a value the thing carries. ADR-048 recorded the identity half: identifiers stay primitives named through…"},"657":{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#decision","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Decision","x":"Model a domain value that carries an invariant as an immutable record value object with a Result-returning factory; keep identifiers primitive (ADR-048). - One abstract record…"},"658":{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#rationale","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Rationale","x":"- The invariant belongs to the type, not to every caller. A string email can be validated in one handler and not the next; an Email cannot exist unvalidated, because the only…"},"659":{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#trade-offs","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Trade-offs","x":"- The pattern is not uniformly applied. Only three of the seven types have a companion Invariants class; the rest inline their checks. Only two of the four multi-field values…"},"660":{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#revision-2026-10-01","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision changed; four statements were corrected and anchors refreshed. (1) The Money sugar paragraph claimed an external caller cannot assemble a Money whose currency was…"},"661":{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#related","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Related","x":"ADR-048 (the deliberate opposite call for identifiers: primitives behind aliases, wrapper structs rejected, because identifiers cross process boundaries constantly and carry no…"},"662":{"u":"/docs/adr/069-shared-data-protection-key-ring.html","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records"},"663":{"u":"/docs/adr/069-shared-data-protection-key-ring.html#status","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-07). Updated 2026-08-14: Store's adoption has landed and is live (its own dedicated storage account, gated on dataProtectionStorageReady), and the ADC call-site…"},"664":{"u":"/docs/adr/069-shared-data-protection-key-ring.html#context","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Context","x":"ASP.NET Core's DataProtection default keeps the key ring in memory, per process. That is correct for a single-process host and wrong for a scaled-out one: every replica generates…"},"665":{"u":"/docs/adr/069-shared-data-protection-key-ring.html#decision","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Decision","x":"Add one opt-in registration call, AddCommonDataProtection, that persists the key ring to a single Azure blob so every replica of a host shares one ring…"},"666":{"u":"/docs/adr/069-shared-data-protection-key-ring.html#rationale","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Rationale","x":"- The key ring is the smallest thing that has to be shared. Sticky sessions would paper over the symptom while making a replica restart a mass sign-out, and a shared cache would…"},"667":{"u":"/docs/adr/069-shared-data-protection-key-ring.html#trade-offs","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Trade-offs","x":"- The key ring is encrypted at rest only where gate 2 was switched on. Both templates ship the path and both default it off (2026-09-10 revision), so on default parameters the…"},"668":{"u":"/docs/adr/069-shared-data-protection-key-ring.html#revision-2026-09-10","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Revision (2026-09-10)","x":"Gate 2 ships in both templates, default off, so \"configured nowhere\" is retired. The framework side is unchanged: AddCommonDataProtection…"},"669":{"u":"/docs/adr/069-shared-data-protection-key-ring.html#revision-2026-10-01","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; this revision refreshes citations only. Re-anchored in Context, Decision and the Store adoption notes: the ADC replica caps, sticky UI ingress,…"},"670":{"u":"/docs/adr/069-shared-data-protection-key-ring.html#related","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Related","x":"ADR-022 (the browser session cookies whose decryption this makes replica-independent, together with the antiforgery tokens the SSR pages mint), ADR-008 (the multi-host topology…"},"671":{"u":"/docs/adr/070-fail-fast-configuration-contract.html","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records"},"672":{"u":"/docs/adr/070-fail-fast-configuration-contract.html#status","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-07). Revised 2026-08-14 (source citations re-anchored). Revised 2026-08-23 (inventory re-counted after the password-reset vertical and the opt-in feature waves:…"},"673":{"u":"/docs/adr/070-fail-fast-configuration-contract.html#context","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Context","x":"Every host in the workspace reads a dozen or more configuration sections: connection strings, SMTP, JWT key material, outbox tuning, message-bus provider, module enablement,…"},"674":{"u":"/docs/adr/070-fail-fast-configuration-contract.html#decision","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Decision","x":"Bind every settings section through a validating chain that runs at startup, and read the bound value through IOptions of the concrete settings class. - One binding shape, used…"},"675":{"u":"/docs/adr/070-fail-fast-configuration-contract.html#rationale","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Rationale","x":"- A boot failure is cheaper than a first-use failure. A host that will not start is caught by the deployment, by a local dotnet run, or by CI. A host that starts and fails on the…"},"676":{"u":"/docs/adr/070-fail-fast-configuration-contract.html#trade-offs","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- Nothing enforces it. There is no architecture fitness test asserting that a new AddOptions call carries ValidateDataAnnotations().ValidateOnStart(). The uniformity above is…"},"677":{"u":"/docs/adr/070-fail-fast-configuration-contract.html#revision-2026-10-01","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; the inventory and several descriptions were brought back in line with source. The framework now has thirty-one registrations on the chain rather…"},"678":{"u":"/docs/adr/070-fail-fast-configuration-contract.html#related","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-025 (startup warm-up and readiness gating: this contract decides what happens before a host reaches that machinery), ADR-031 (feature flags read from configuration, whose…"},"679":{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records"},"680":{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#status","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-12). Amended 2026-08-13: the composition-time string trade-off below was resolved in v1.147.0 by a deferred-resolution overload; see the updated trade-off…"},"681":{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#context","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Context","x":"ADC's badge check-in feature (ADR-072) needs two things that look like one thing: an attendee's device has to show a QR code, and an organizer's device has to read one. They are…"},"682":{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#decision","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Decision","x":"Split the feature by what it actually depends on: QR display ships as a shared component, barcode scanning ships as an ADR-042 capability whose native half is opt-in per head. -…"},"683":{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#rationale","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Rationale","x":"- Rendering a QR is not a device concern, so making it one would have been ceremony. As a capability it would have needed an interface, a null fallback and a native override for…"},"684":{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#trade-offs","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Trade-offs","x":"- The scan page's text is the caller's delegate, not a string (resolved in v1.147.0). cancelText and cameraDescription arrive as Func and are invoked once per scan, when the…"},"685":{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#revision-2026-10-01","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. The package pin citations are re-anchored to MMCA.Common/Directory.Packages.props:191 (QRCoder 1.8.0) and :225 (ZXing.Net.Maui.Controls 0.10.4).…"},"686":{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#related","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Related","x":"ADR-042 (the capability pattern this extends: contract in MMCA.Common.UI, native implementation in MMCA.Common.UI.Maui, override after AddUIShared), ADR-072 (the ADC feature that…"},"687":{"u":"/docs/adr/072-qr-badge-check-in-and-points.html","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records"},"688":{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#status","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13). Amended (2026-08-14): ADC shipped two attendee-self-recorded scan surfaces (sponsor booth visits and room self check-in), a third CheckInScope, a sixth…"},"689":{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#context","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Context","x":"ADC wanted two conference-day capabilities that turn out to be one mechanism. Organizers want to know who actually attended which session, which the schedule cannot tell them: a…"},"690":{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#decision","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Decision","x":"AttendeeBadge (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Domain/Badges/AttendeeBadge.cs:17-24) is one row per user holding a single Guid Credential, minted on first…"},"691":{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#rationale","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Rationale","x":"- An opaque credential makes the server the only interpreter. A JWT or HMAC badge would verify offline, but the scanning device is online by necessity (it has to write a check-in…"},"692":{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#trade-offs","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Trade-offs","x":"- The badge credential is a bearer value. Anyone who photographs an attendee's screen can be checked in as that attendee. The mitigations are that a badge scan is organizer-side,…"},"693":{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#revision-2026-10-01","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"One content fact changed and is corrected in place in the Decision: a fourth handler raises the feedback events. The event-side batch path,…"},"694":{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#related","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Related","x":"ADR-071 (the framework halves this consumes: the QR component on /my-badge and the scanner capability behind /check-in), ADR-003 (the outbox path AttendeeCheckedIn and the two…"},"695":{"u":"/docs/adr/073-multi-tenancy-model.html","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records"},"696":{"u":"/docs/adr/073-multi-tenancy-model.html#status","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13). The implementation lands in the MMCA.Common enterprise capability wave release, alongside the scheduler, audit trail, DSAR export, and CSV export work. It…"},"697":{"u":"/docs/adr/073-multi-tenancy-model.html#context","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Context","x":"MMCA.Common already partitions data along two axes and neither of them is a tenant. ADR-006 partitions by source name (every entity resolves to a DataSourceKey(Engine, Name),…"},"698":{"u":"/docs/adr/073-multi-tenancy-model.html#decision","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Decision","x":"Ship shared-schema tenancy as a second named query filter, with per-tenant database routing as a configuration override on the same source key, both opt-in and both inert until a…"},"699":{"u":"/docs/adr/073-multi-tenancy-model.html#rationale","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Rationale","x":"- A query filter is the only place the rule cannot be forgotten. Per-handler Where clauses are correct until the tenth handler, and the tenth handler is a data leak rather than a…"},"700":{"u":"/docs/adr/073-multi-tenancy-model.html#trade-offs","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Reads are on discipline where writes are on an invariant. A consumer calling EF's own parameterless IgnoreQueryFilters() on a raw Table surface drops the tenant filter along…"},"701":{"u":"/docs/adr/073-multi-tenancy-model.html#related","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Related","x":"ADR-006 (the source-name axis this composes with: an override re-points a DataSourceKey without changing it, and the per-source outbox this record drains once per tenant),…"},"702":{"u":"/docs/adr/073-multi-tenancy-model.html#revision-2026-09-11","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"The gap this closes was never written down here. Everything above is about the tenant inside a request: the middleware resolves it, the named filter composes on it, the write…"},"703":{"u":"/docs/adr/073-multi-tenancy-model.html#revision-2026-10-01","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The body now agrees with the column the 2026-09-11 Revision added. \"Background work drains and migrates per tenant\" still stated that there was deliberately no OutboxMessage…"},"704":{"u":"/docs/adr/074-recurring-job-scheduler.html","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records"},"705":{"u":"/docs/adr/074-recurring-job-scheduler.html#status","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13; revised 2026-08-14, 2026-08-18, 2026-08-31). The implementation lands in the MMCA.Common \"enterprise capability wave\" release and is opt-in: a host calls…"},"706":{"u":"/docs/adr/074-recurring-job-scheduler.html#context","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Context","x":"The framework had two kinds of background work and neither of them is a schedule. OutboxProcessor…"},"707":{"u":"/docs/adr/074-recurring-job-scheduler.html#decision","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Decision","x":"A persistent job store plus a single-runner claim lease, reusing the exact idiom the outbox proved. The outbox claims a batch with an ExecuteUpdateAsync that sets LockedUntil and…"},"708":{"u":"/docs/adr/074-recurring-job-scheduler.html#rationale","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Rationale","x":"- The lease is already proven under production load. Multi-replica correctness for recurring work is the hard part, and it was solved once for the outbox: an atomic claim update,…"},"709":{"u":"/docs/adr/074-recurring-job-scheduler.html#trade-offs","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Trade-offs","x":"- A polling loop is not a real-time scheduler. Worst-case start lag is one polling interval, 30 seconds at the default, so sub-minute precision is not on offer. A job that must…"},"710":{"u":"/docs/adr/074-recurring-job-scheduler.html#revision-2026-10-01","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. The job-store field list now names LastError, a nullable string the entry already carries…"},"711":{"u":"/docs/adr/074-recurring-job-scheduler.html#related","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox whose claim-lease idiom and smart wait this reuses verbatim, and whose at-least-once posture it inherits along with the idempotency obligation on job bodies),…"},"712":{"u":"/docs/adr/075-audit-trail.html","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records"},"713":{"u":"/docs/adr/075-audit-trail.html#status","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13; corrected 2026-08-14: the adoption sweep and the ApplicationDbContext line citations). The implementation lands in the MMCA.Common \"enterprise capability…"},"714":{"u":"/docs/adr/075-audit-trail.html#context","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Context","x":"The framework already answers \"who touched this row last\". Every AuditableBaseEntity carries CreatedOn/By and LastModifiedOn/By, stamped by AuditSaveChangesInterceptor on the way…"},"715":{"u":"/docs/adr/075-audit-trail.html#decision","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Decision","x":"AuditTrailSaveChangesInterceptor (Infrastructure Persistence/AuditTrail/) joins the interceptors ApplicationDbContext.OnConfiguring already passes to…"},"716":{"u":"/docs/adr/075-audit-trail.html#rationale","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Rationale","x":"- IAuditableEntity is a statement about a business row, and an audit row is not one. The interface means \"this row stamps who created and last modified it and participates in…"},"717":{"u":"/docs/adr/075-audit-trail.html#trade-offs","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Write amplification is real and it is on the caller's latency path. An entity with twenty changed properties writes twenty rows inside the caller's transaction, so an audited…"},"718":{"u":"/docs/adr/075-audit-trail.html#revision-2026-09-07","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Two additions from the 2026-09-07 security review. 1. The [Pii] convention is now checked in both directions (SEC-Store-19 / SEC-Store-24). The shared base already obliged a…"},"719":{"u":"/docs/adr/075-audit-trail.html#revision-2026-09-25-both-apps-record-trail-dml-by-different-means","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Revision (2026-09-25): both apps record trail DML, by different means","x":"Revision item 2 above previously held for Store only: ADC's server-level audit carried no statement auditing, so a rewrite of dbo.AuditTrailEntries by the shared admin login left…"},"720":{"u":"/docs/adr/075-audit-trail.html#revision-2026-10-01-retention-has-no-fallback-and-the-current-state-text-matches-the-code","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Revision (2026-10-01): retention has no fallback, and the current-state text matches the code","x":"- Retention runs on the scheduler or not at all. The Decision previously named a PeriodicBackgroundService subclass as the fallback for a host without the scheduler. No such…"},"721":{"u":"/docs/adr/075-audit-trail.html#related","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the same-transaction write this copies wholesale, including the retry-discard and the Add-only mutation rule), ADR-005 (soft-delete, [Pii] and erasure: why the trail…"},"722":{"u":"/docs/adr/076-data-subject-export.html","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records"},"723":{"u":"/docs/adr/076-data-subject-export.html#status","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13). Revised 2026-08-14 (the API-surface section corrected to the shipped mechanism, an abstract DataExportControllerBase a subclass mounts, not an…"},"724":{"u":"/docs/adr/076-data-subject-export.html#context","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Context","x":"A data-subject access request is a legal obligation with a clock on it: the person asks for a copy of the personal data held about them, and the operator has a deadline to hand…"},"725":{"u":"/docs/adr/076-data-subject-export.html#decision","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Decision","x":"The framework takes the part that is the same in both apps; the app keeps the part that is not. A consumer's export handler becomes a subclass that supplies a role test and a set…"},"726":{"u":"/docs/adr/076-data-subject-export.html#rationale","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Rationale","x":"- The two halves of a handler have different owners. The ownership gate, the aggregate load, the fan-out, the per-section catch and the envelope are the same decisions in both…"},"727":{"u":"/docs/adr/076-data-subject-export.html#trade-offs","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- Best-effort degradation can return a quietly incomplete package. Available = false is the only signal, and nothing forces a caller, a UI, or the subject to read it. A section…"},"728":{"u":"/docs/adr/076-data-subject-export.html#revision-2026-10-01","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Store now registers a second export section: alongside Sales it contributes CatalogUserDataExportSection (SectionName \"Catalog\",…"},"729":{"u":"/docs/adr/076-data-subject-export.html#related","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-005 (the erasure half of the same privacy obligation, whose IAnonymizable opt-in and [Pii] guard are this contract's mirror: one erases what the other copies), ADR-033 (the…"},"730":{"u":"/docs/adr/077-hybridcache-substrate.html","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records"},"731":{"u":"/docs/adr/077-hybridcache-substrate.html#status","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13). Amends ADR-026: Tier 1's substrate gains a third implementation beside MemoryCacheService and DistributedCacheService. It is opt-in through…"},"732":{"u":"/docs/adr/077-hybridcache-substrate.html#context","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Context","x":"ADR-026 settled Tier 1 as one abstraction (ICacheService) over two implementations chosen at startup: in-process memory when no real IDistributedCache is present, Redis…"},"733":{"u":"/docs/adr/077-hybridcache-substrate.html#decision","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Decision","x":"Ship HybridCacheService as a third ICacheService implementation, opt-in per host, under a disjoint keyspace. This is the structural rule the design is built around, and…"},"734":{"u":"/docs/adr/077-hybridcache-substrate.html#rationale","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Rationale","x":"- The disjoint keyspace is the decision; everything else is implementation. Rather than trusting a second implementation to write a shape compatible with the first, this record…"},"735":{"u":"/docs/adr/077-hybridcache-substrate.html#trade-offs","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Invalidation does not reach other replicas' L1 immediately. A remove evicts the L2 entry and the calling replica's L1; every other replica keeps its copy for up to…"},"736":{"u":"/docs/adr/077-hybridcache-substrate.html#revision-2026-10-01","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Two decisions joined the record. First, ICacheService gained GetFromSharedStoreAsync (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:62), which…"},"737":{"u":"/docs/adr/077-hybridcache-substrate.html#related","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Related","x":"ADR-026 (amended by this record: its Tier 1 substrate gains a third implementation, its 30-second default TTL becomes the local-cache bound as well, its prefix-invalidation model…"},"738":{"u":"/docs/adr/078-csv-export-endpoint.html","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records"},"739":{"u":"/docs/adr/078-csv-export-endpoint.html#status","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13; revised 2026-08-18, 2026-08-31, 2026-09-19). The endpoint ships in the MMCA.Common \"enterprise capability wave\" release (v1.150.0), its scoping hook in…"},"740":{"u":"/docs/adr/078-csv-export-endpoint.html#context","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Context","x":"The request is \"export what you filtered\". The generic entity surface of ADR-034 already accepts a full query vocabulary on the paged route…"},"741":{"u":"/docs/adr/078-csv-export-endpoint.html#decision","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Decision","x":"EntityControllerBase gains a virtual [HttpGet(\"export\")] ExportAsync(...) action (Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:248, method at…"},"742":{"u":"/docs/adr/078-csv-export-endpoint.html#rationale","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Rationale","x":"- A route is an unambiguous request; an Accept header is a preference. Given a cache policy that ignores Accept and a pipeline configured to never return 406, a client that…"},"743":{"u":"/docs/adr/078-csv-export-endpoint.html#trade-offs","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Trade-offs","x":"- Every derivative gains a bulk read whether its owner wanted one or not. The only gate is the controller's existing authorization posture. A resource that was safe to page 20…"},"744":{"u":"/docs/adr/078-csv-export-endpoint.html#revision-2026-10-01","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. The CSV machinery now lives outside the controller, in the internal static EntityCsvExporter…"},"745":{"u":"/docs/adr/078-csv-export-endpoint.html#related","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Related","x":"ADR-034 (the generic entity surface and query contract this extends, and the MaxUnboundedResultLimit ceiling that forced the page loop), ADR-040 (the output-cache policy whose…"},"746":{"u":"/docs/adr/079-shared-http-middleware-pipeline.html","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records"},"747":{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#status","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14). Revised 2026-08-19 (refreshed the WebApplicationBuilderExtensions.cs cross-reference anchor, which moved to :555). Revised 2026-08-21: the order became…"},"748":{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#context","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Context","x":"In ASP.NET Core, middleware order is behavior, not style: a rate limiter placed before authentication partitions every request as anonymous, an HTTPS redirect placed in front of…"},"749":{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#decision","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Decision","x":"Ship the edge as one ordered pipeline in the framework, UseCommonMiddlewarePipeline (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationExtensions.cs:48), and…"},"750":{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#rationale","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Rationale","x":"- Order is behavior, so it belongs to the framework, not to each host. Four of the adjacencies above fail silently when reversed: the limiter stops limiting, the tenant resolver…"},"751":{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#trade-offs","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Trade-offs","x":"- Two of this record's original costs are retired (2026-08-21). As accepted, nothing froze the order (no test referenced the method; the adjacencies were protected by comments…"},"752":{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#revision-2026-10-01","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Anchor refresh only; no decision or rationale changed. Refreshed citations: the private ApplyPipeline helper (WebApplicationExtensions.cs:163-179), the lazy validator resolution…"},"753":{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#related","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Related","x":"ADR-014 (the in-process sibling: one fixed decorator order for commands and queries), ADR-019 (depends on forwarded headers before the limiter and on the limiter after…"},"754":{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records"},"755":{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#status","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14). Revised 2026-09-03 (the smoke gate is now two tiers: a revision activation gate runs before the HTTP probes, and the rollback selector filters on…"},"756":{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#context","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Context","x":"Both production apps deploy to Azure Container Apps from a single deploy.yml job on push to main, and every gate runs before anything rolls out. The deploy job waits on eleven…"},"757":{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#decision","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Decision","x":"Roll out one revision at a time, verify it from outside, and auto-revert the image only when the verification fails. - Single-revision rollout. Every container app runs…"},"758":{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#rationale","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Rationale","x":"- ARM success is the wrong success signal. The smoke gate converts \"the control plane accepted the template\" into \"the fleet is serving the revision this run built\", which is the…"},"759":{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#trade-offs","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Trade-offs","x":"- Schema is never rolled back, so a bad migration is fix-forward only. The image reverts and the database does not, so the previous release resumes against the new schema. This…"},"760":{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#revision-2026-09-10","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Revision (2026-09-10)","x":"ADC's activation tier is now one shared budget over the whole fleet, matching Store. The attempt loop is the outer loop and the apps are iterated inside it: for i in $(seq 1 30)…"},"761":{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#revision-2026-10-01","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Three current-state statements are corrected; the rollout and revision-only rollback model is unchanged. First, the Gateway is not readiness-gated on /health/ready: in both repos…"},"762":{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#related","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Related","x":"ADR-057 (built on this model: revision-only rollback is why every migration must be backward compatible one release back), ADR-030 (startup migration as sole migrator, the reason…"},"763":{"u":"/docs/adr/081-cost-baseline-deploy-gate.html","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records"},"764":{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#status","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14). Revised 2026-08-31 (the read-only claim is qualified: one az config set on the runner's own CLI precedes the queries; citations re-anchored). Revised…"},"765":{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#context","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Context","x":"Both deployed apps run a deliberately small production footprint: every Container App is declared with maxReplicas: 2 and every SQL database with the Basic tier…"},"766":{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#decision","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Decision","x":"The cost baseline is asserted by a read-only reusable workflow that both runs weekly and sits in deploy.needs, so an un-reverted scale-up blocks the next production deploy. - One…"},"767":{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#rationale","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Rationale","x":"- Configuration drift is the leading indicator; spend is the lagging one. The budget notification fires at 80% of actual spend, after the money is gone, and names a number rather…"},"768":{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#trade-offs","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- A legitimate scale-up blocks deploys until the baseline is edited. Standing up extra capacity for a real event and then shipping a fix during it requires a pull request against…"},"769":{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#revision-2026-10-01","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Store's SQL check now matches its own stated window. It reads [sku.tier, sku.name] (MMCA.Store/.github/workflows/cost-guard.yml:96-97) and passes a database only through…"},"770":{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#related","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-064 (the sibling deploy-precondition record, which decides the four proof-of-recency gates and enumerates this one only in passing; its break-glass input does not apply…"},"771":{"u":"/docs/adr/082-two-tier-cors-posture.html","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records"},"772":{"u":"/docs/adr/082-two-tier-cors-posture.html#status","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14)."},"773":{"u":"/docs/adr/082-two-tier-cors-posture.html#context","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Context","x":"Both deployed applications put a YARP gateway in front of per-module service hosts (ADR-008), and the browser and MAUI clients talk to the gateway origin while the services…"},"774":{"u":"/docs/adr/082-two-tier-cors-posture.html#decision","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Decision","x":"Ship two cross-origin policies from the framework: an allow-listed one for service hosts and a deliberately broader one for gateways. - Service hosts register two named policies…"},"775":{"u":"/docs/adr/082-two-tier-cors-posture.html#rationale","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Rationale","x":"- A proxy cannot allow-list what it does not own. The gateway has no controllers and no knowledge of which headers the fronted services accept, so a header allow-list there would…"},"776":{"u":"/docs/adr/082-two-tier-cors-posture.html#trade-offs","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Trade-offs","x":"- The gateway policy is broad on two of three axes. Any header and any method are accepted for an allow-listed origin. The origin list is the only lever there, so a mistake in…"},"777":{"u":"/docs/adr/082-two-tier-cors-posture.html#revision-2026-10-01","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision changed. Citations were re-anchored to the current source: AddCommonCors and its policy lines (WebApplicationBuilderExtensions.cs:124-141), the pipeline Cors step…"},"778":{"u":"/docs/adr/082-two-tier-cors-posture.html#related","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Related","x":"ADR-079 (the shared middleware pipeline whose fixed order places the environment-selected CORS policy between routing and authentication), ADR-008 (the gateway plus per-module…"},"779":{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records"},"780":{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#status","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14). Revised 2026-08-23: the adopter counts were refreshed (ADC Conference's ActivityChanged joined the base-derived set) and three source citations were…"},"781":{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#context","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Context","x":"ADR-003 decides how a domain event moves: captured into the outbox inside SaveChangesAsync, dispatched in-process after commit, or published to the broker when it is an…"},"782":{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#decision","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Decision","x":"Every generic CRUD lifecycle transition of an entity raises one event type for that entity, carrying a DomainEntityState discriminator; handlers filter on State. - One base…"},"783":{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#rationale","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Rationale","x":"- One type per entity is one subscription surface. A subscriber declares interest in the entity, then decides which transitions matter, instead of the container deciding for it…"},"784":{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#trade-offs","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Trade-offs","x":"- Every selective handler pays a filter. A handler that cares about one transition has to open with a State guard and return (SpeakerDeletedHandler.cs:29-30 is the shape to…"},"785":{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#alternatives-rejected","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Alternatives rejected","x":"- Splitting each XChanged event into per-fact past-tense events (SpeakerRenamed, SessionCancelled, and so on) with every handler and bridge updated to match. Proposed 2026-08-26,…"},"786":{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#related","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (how these events are captured and dispatched; this ADR decides only their shape), ADR-010 (schema versioning for the discriminator once it crosses a service boundary),…"},"787":{"u":"/docs/adr/084-stripe-webhook-ingress.html","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records"},"788":{"u":"/docs/adr/084-stripe-webhook-ingress.html#status","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14). Revised 2026-09-03. Revised 2026-09-07 (the webhook action carries its own request size limit, the auto-minted signing secret is shared through the…"},"789":{"u":"/docs/adr/084-stripe-webhook-ingress.html#context","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Context","x":"Four ADRs already cover how a message crosses a boundary in this workspace. ADR-003 decides how an event leaves a service (outbox, at-least-once). ADR-021 decides how a…"},"790":{"u":"/docs/adr/084-stripe-webhook-ingress.html#decision","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Decision","x":"Treat third-party webhook ingress as its own contract with two halves: an acceptance-coded endpoint and a self-registering, self-provisioning endpoint registration at startup. -…"},"791":{"u":"/docs/adr/084-stripe-webhook-ingress.html#rationale","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Rationale","x":"- The caller's protocol decides the response vocabulary. Stripe reads a status code as \"keep retrying\" or \"stop\", not as \"this succeeded\" or \"this failed\". Mapping every…"},"792":{"u":"/docs/adr/084-stripe-webhook-ingress.html#trade-offs","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Trade-offs","x":"- A startup service that writes to a live third-party account. Booting a Sales replica creates and deletes webhook endpoints in the real Stripe account…"},"793":{"u":"/docs/adr/084-stripe-webhook-ingress.html#revision-2026-09-07","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Four changes from the 2026-09-07 security review. The ingress contract (raw body, signature verified before parsing, anonymous endpoint) is unchanged. 1. The webhook action…"},"794":{"u":"/docs/adr/084-stripe-webhook-ingress.html#revision-2026-10-01","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The ingress contract (acceptance-coded status codes, anonymous raw-body endpoint, startup self-registration with marker-scoped deletion) is unchanged. This revision brings the…"},"795":{"u":"/docs/adr/084-stripe-webhook-ingress.html#related","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (outbound at-least-once delivery, the other end of the same family), ADR-021 (broker-side inbound dedup, which never sees a webhook), ADR-017 (client-supplied idempotency…"},"796":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records"},"797":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#status","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-18). Revised 2026-08-23 (the alias count and the migration-surface census were recounted, the census gained a stated methodology, and the CheckIn and…"},"798":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#context","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Context","x":"ADR-048 decided that every entity identity is a primitive named through a per-module global using {Entity}IdentifierType = ... alias, and recorded the cost in one line of…"},"799":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#decision","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Decision","x":"Keep the aliases. The wrapper-struct alternative is evaluated in this record, priced, and deferred again, this time against explicit triggers. Inside a module an identifier is…"},"800":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#rationale","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Rationale","x":"- The cost is paid once and the benefit accrues per defect avoided, and the defect count is currently zero. No production incident in any of the four repos has been traced to a…"},"801":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#trade-offs","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The exposure is unmitigated, not reduced. This record buys no safety whatsoever. Every transposition ADR-048 could not catch is still uncatchable today, and the CheckIn…"},"802":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#related","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Related","x":"ADR-048 (the decision this record revisits and upholds; its Status now points here), ADR-068 (the deliberate opposite case: domain values carry invariants and therefore do get…"},"803":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#revision-2026-08-23","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"The decision, the priced alternative, the three triggers and the trade-offs are unchanged. What changed is arithmetic and three citations. The alias count is 44 across 10 files,…"},"804":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#revision-2026-09-11","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Revision (2026-09-11)","x":"The decision, the priced alternative, the three triggers and the trade-offs are unchanged. What changed is arithmetic and one Context fact. The alias count is 46 across the same…"},"805":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#revision-2026-09-19","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Revision (2026-09-19)","x":"The decision, the priced alternative, the three triggers and the trade-offs are unchanged. What changed is arithmetic and the non-int bullet. The alias count is 48 across the…"},"806":{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#revision-2026-10-01","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision, trigger, rationale or trade-off changed. The migration-surface census is re-measured on the same rule: 3,869 lines across 1,209 files (Common 296/117, ADC 2,282/694,…"},"807":{"u":"/docs/adr/086-process-manager-deferred.html","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records"},"808":{"u":"/docs/adr/086-process-manager-deferred.html#status","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-18) as a documented deferral. Nothing ships with this record: no state machine, no correlation store, no new package. What ships is the shape the coordinator…"},"809":{"u":"/docs/adr/086-process-manager-deferred.html#context","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Context","x":"ADR-054 decided how this workspace achieves cross-boundary consistency without two-phase commit: choreography. Each step of a workflow raises a domain event, each compensating…"},"810":{"u":"/docs/adr/086-process-manager-deferred.html#decision","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Decision","x":"Defer the process manager, and record its shape so the deferral is a design decision rather than an omission. A durable multi-step workflow coordinator in this workspace is a…"},"811":{"u":"/docs/adr/086-process-manager-deferred.html#rationale","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Rationale","x":"- Choreography is genuinely correct for the workflow that exists. This is not a case of the simpler option being tolerated. Checkout's saga state is two fields on Order, and an…"},"812":{"u":"/docs/adr/086-process-manager-deferred.html#trade-offs","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The first workflow to hit the trigger pays the full cost at once, under whatever deadline made it appear. Deferral moves the work onto the critical path of the feature that…"},"813":{"u":"/docs/adr/086-process-manager-deferred.html#revision-2026-09-11-the-first-per-instance-deadline-exists-and-it-is-not-a-state-machine","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Revision (2026-09-11): the first per-instance deadline exists, and it is not a state machine","x":"One of the three properties this record listed as absent has arrived, from an unexpected direction. MMCA.Store now gives every unpaid order its own scheduled deadline:…"},"814":{"u":"/docs/adr/086-process-manager-deferred.html#revision-2026-10-01","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Two current-state statements are refreshed. The MassTransit pin is 8.5.11 for all three packages, still v8…"},"815":{"u":"/docs/adr/086-process-manager-deferred.html#related","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Related","x":"ADR-054 (the accepted mechanism this record defers an alternative to: choreographed compensation, the persisted aggregate marker, and the reconciliation sweep that would remain…"},"816":{"u":"/docs/adr/087-broker-poison-message-handling.html","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records"},"817":{"u":"/docs/adr/087-broker-poison-message-handling.html#status","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-18). Amends ADR-009: the outbox's broker publish gains a circuit breaker, which is the first resilience policy this workspace applies to something other than an…"},"818":{"u":"/docs/adr/087-broker-poison-message-handling.html#context","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Context","x":"Delivery in this workspace has always been at-least-once with retries on both legs: the outbox retries a failed publish with jittered exponential backoff and eventually…"},"819":{"u":"/docs/adr/087-broker-poison-message-handling.html#decision","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Decision","x":"Three changes, each scoped to one failure: second-level redelivery configured per transport, a fault consumer with its own meter, and a circuit breaker around the outbox's broker…"},"820":{"u":"/docs/adr/087-broker-poison-message-handling.html#rationale","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Rationale","x":"- The transport asymmetry follows a real capability difference, not a preference. RabbitMQ needs a plugin the dev container lacks; Service Bus does not. A single default would be…"},"821":{"u":"/docs/adr/087-broker-poison-message-handling.html#trade-offs","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Trade-offs","x":"- Delayed redelivery is off where the plugin problem lives. RabbitMQ is the local transport and also a plausible self-hosted production transport; both get default-off, so the…"},"822":{"u":"/docs/adr/087-broker-poison-message-handling.html#revision-2026-10-01","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. The broker transport wiring now lives in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs…"},"823":{"u":"/docs/adr/087-broker-poison-message-handling.html#related","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox publish leg this breaker wraps, and the retry, jittered backoff and dead-lettering that BrokenCircuitException reuses unchanged), ADR-066 (the transport…"},"824":{"u":"/docs/adr/088-gateway-edge-responsibilities.html","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records"},"825":{"u":"/docs/adr/088-gateway-edge-responsibilities.html#status","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-18). Extends ADR-019 with a fourth, edge-tier layer whose posture is the deliberate opposite of the service tier's authenticated-only global limiter; nothing in…"},"826":{"u":"/docs/adr/088-gateway-edge-responsibilities.html#context","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Context","x":"ADR-008 made the Gateway the only client entry point and gave it three jobs: the route-to-service map, CORS, and forwarding the caller's Authorization header. Nothing was added…"},"827":{"u":"/docs/adr/088-gateway-edge-responsibilities.html#decision","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Decision","x":"Ship a gateway edge kit in a Gateway namespace inside MMCA.Common.Aspire, owning exactly three responsibilities, and record three more as deliberately declined. A fourth section,…"},"828":{"u":"/docs/adr/088-gateway-edge-responsibilities.html#rationale","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Rationale","x":"- The edge is the only place that sees a request exactly once. That is what makes ensure-at-the-edge correct and mint-per-service wrong: not that the service version is broken,…"},"829":{"u":"/docs/adr/088-gateway-edge-responsibilities.html#trade-offs","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The limiter closes over an eagerly-bound copy of the settings (GatewayRateLimitingExtensions.cs:278-279, consumed at :308 and :310), so an IOptionsMonitor reload never reaches…"},"830":{"u":"/docs/adr/088-gateway-edge-responsibilities.html#revision-2026-09-07","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Two changes from the 2026-09-07 security review. 1. Health endpoints are cached server-side, and the bypass stays (SEC-Common-71 / SEC-ADC-17 / SEC-ADC-56). /health and…"},"831":{"u":"/docs/adr/088-gateway-edge-responsibilities.html#revision-2026-09-10","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Revision (2026-09-10)","x":"Both public UI hosts now carry the own-host hardening, so item 2 above is no longer a Store-only remediation. ADC's conference UI is externally reachable on its own FQDN, which…"},"832":{"u":"/docs/adr/088-gateway-edge-responsibilities.html#revision-2026-09-20","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Revision (2026-09-20)","x":"The decision the last revision declined to take is taken here: the own-host UI hardening is framework code. Two near-identical copies in two consumers is the shape that says a…"},"833":{"u":"/docs/adr/088-gateway-edge-responsibilities.html#revision-2026-10-01","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Both consumers now turn active destination probing off. The framework half of the delegation is unchanged: active checks stay opt-in…"},"834":{"u":"/docs/adr/088-gateway-edge-responsibilities.html#related","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Related","x":"ADR-008 (the record that made the Gateway the only entry point and gave it routing, CORS and auth forwarding; this is the first record to add cross-cutting behavior to it),…"},"835":{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records"},"836":{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#status","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-18; revised 2026-08-23: ADC's table gained a 27th route, /Activities, on 2026-08-19; revised 2026-08-31: section 5 now states the activity timeout where it…"},"837":{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#context","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Context","x":"Before this record, both gateways built their route table by hand, in code. ADC made 26 MapForwarder calls and Store made 10. Neither host called AddReverseProxy or…"},"838":{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#decision","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Decision","x":"Make configuration the single source of the gateway route table, and pin it with a test. Each gateway calls…"},"839":{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#rationale","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Rationale","x":"- The drift already happened, in the repository that has the most gateway tests. ADC is the careful consumer, and it still carried three unpinned routes, an off-by-one comment…"},"840":{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#trade-offs","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Trade-offs","x":"- The compiler stopped helping. A misspelled cluster reference, a malformed path pattern or a route that shadows another is a runtime failure, discovered as a 404 or a 502, where…"},"841":{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#revision-2026-10-01","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision changed. Two current-state statements were corrected. The Rationale example of an ungated comment drifting no longer holds for ADC, whose conference label now reads…"},"842":{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#related","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Related","x":"ADR-008 (amended: the Gateway keeps the route-to-service map it was given, now expressed as configuration rather than as forwarder registrations), ADR-088 (the other half of this…"},"843":{"u":"/docs/adr/090-event-upcaster-registration.html","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records"},"844":{"u":"/docs/adr/090-event-upcaster-registration.html#status","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-21). Completes the follow-up named in ADR-010: that record established the versioning policy (a SchemaVersion signal plus a new-type-and-upcaster discipline for…"},"845":{"u":"/docs/adr/090-event-upcaster-registration.html#context","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Context","x":"ADR-010 splits event evolution into a signal and a discipline. The signal (SchemaVersion, a fitness-function-gated property on every integration event) shipped with ADR-010…"},"846":{"u":"/docs/adr/090-event-upcaster-registration.html#decision","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Decision","x":"1. A typed upcaster abstraction, in the Application layer. IEventUpcaster (Source/Core/MMCA.Common.Application/Interfaces/Events/IEventUpcaster.cs) is a pure payload mapping from…"},"847":{"u":"/docs/adr/090-event-upcaster-registration.html#rationale","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Rationale","x":"- The discipline becomes a mechanism. ADR-010's own framing (a thing that matters is a check, not a comment) now applies to the upcaster half: the transform has a first-class…"},"848":{"u":"/docs/adr/090-event-upcaster-registration.html#trade-offs","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Trade-offs","x":"- The upcast walk advances by declared target type, not runtime type. A misbehaving upcaster that returns an instance of some other type cannot send the walk into an unvalidated…"},"849":{"u":"/docs/adr/090-event-upcaster-registration.html#revision-2026-09-03","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Revision (2026-09-03)","x":"The decision, the mechanism, and both delivery paths are unchanged. What changed is one trade-off's premise and three file locations. 1. Outbox type resolution is no longer…"},"850":{"u":"/docs/adr/090-event-upcaster-registration.html#revision-2026-10-01","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision, mechanism, or rationale changed; this revision refreshes citations only. AddEventUpcaster is defined in…"},"851":{"u":"/docs/adr/091-cache-backed-password-reset.html","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records"},"852":{"u":"/docs/adr/091-cache-backed-password-reset.html#status","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-22). Extends ADR-029 (the cache-backed login-protection idiom this record reuses) and ADR-032 (which decided how a password is stored, never how a user who has…"},"853":{"u":"/docs/adr/091-cache-backed-password-reset.html#context","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Context","x":"Both consumer apps shipped authenticated password change (PUT /Auth/password) and nothing for a user who cannot sign in at all. The recorded fallback in MMCA.ADC's specification…"},"854":{"u":"/docs/adr/091-cache-backed-password-reset.html#decision","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Decision","x":"1. The reset token is a cache record, not a schema change. IPasswordResetTokenService (Source/Core/MMCA.Common.Application/Auth/IPasswordResetTokenService.cs) is two methods,…"},"855":{"u":"/docs/adr/091-cache-backed-password-reset.html#rationale","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Rationale","x":"- No migration is the whole point. A reset credential is short-lived by nature, and the expiry semantics a reset needs (a TTL, a single use, an attempt cap) are native to a cache…"},"856":{"u":"/docs/adr/091-cache-backed-password-reset.html#trade-offs","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Trade-offs","x":"- Cache eviction invalidates outstanding tokens. A Redis restart, an eviction under memory pressure, or a fall back to the in-memory store on a different replica all silently…"},"857":{"u":"/docs/adr/091-cache-backed-password-reset.html#revision-2026-10-01","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Three behaviors changed after acceptance, and four statements are corrected: one was omitted at acceptance, two were wrong as written, and one had drifted from a later revision…"},"858":{"u":"/docs/adr/092-web-vitals-budget-gate.html","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records"},"859":{"u":"/docs/adr/092-web-vitals-budget-gate.html#status","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-23). Revised 2026-09-01: the measurement flow itself now ships as an IPage extension that every suite routes through, and the framework gallery measures three…"},"860":{"u":"/docs/adr/092-web-vitals-budget-gate.html#context","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Context","x":"Rubric section 23 asks for client-side performance that is measured rather than assumed, naming Core Web Vitals (LCP, INP, CLS) or an equivalent as the evidence…"},"861":{"u":"/docs/adr/092-web-vitals-budget-gate.html#decision","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Decision","x":"Ship the measurement infrastructure, the measurement flow and the assert mechanics in MMCA.Common.Testing.E2E, default the budget to the Core Web Vitals good band, and let the…"},"862":{"u":"/docs/adr/092-web-vitals-budget-gate.html#rationale","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Rationale","x":"- The good band is an external contract, which is what makes an absolute ceiling defensible here. ADR-060 refused absolute latency because a nanosecond count is a property of the…"},"863":{"u":"/docs/adr/092-web-vitals-budget-gate.html#trade-offs","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- The gate is ui-scoped and may legitimately skip. Both apps gate e2e-gate on a ui change filter (ADC deploy.yml:838, Store :810) and deploy accepts skipped for it (ADC :1230,…"},"864":{"u":"/docs/adr/092-web-vitals-budget-gate.html#revision-2026-10-01","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. One sentence is corrected: the gallery's components test was described as the only case in the whole adoption set that drives an interaction,…"},"865":{"u":"/docs/adr/092-web-vitals-budget-gate.html#related","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-063 (the structural sibling: the same package, the same Playwright suite and the same deploy gate, applied to WCAG 2.1 AA instead of load performance), ADR-060 (the backend…"},"866":{"u":"/docs/adr/093-container-image-posture.html","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records"},"867":{"u":"/docs/adr/093-container-image-posture.html#status","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-23) for the three build decisions below. The two runtime postures below were recorded as undecided at that date: they described what the images did and the…"},"868":{"u":"/docs/adr/093-container-image-posture.html#context","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Context","x":"Eleven Dockerfiles produce every deployable container in the two Azure-hosted applications: six in MMCA.ADC (four services, the Gateway, the Blazor web host) and five in…"},"869":{"u":"/docs/adr/093-container-image-posture.html#decision","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Decision","x":"1. The GitHub Packages credential is a BuildKit secret, never an ARG or ENV. Both applications' nuget.config source-maps MMCA. to GitHub Packages, so every restore inside an…"},"870":{"u":"/docs/adr/093-container-image-posture.html#runtime-postures-closed-2026-09-07","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Runtime postures (closed 2026-09-07)","x":"The base image is pinned by digest. All eleven images name the runtime and the SDK image by sha256, with the 10.0 tag left in place as a readable suffix (ADC…"},"871":{"u":"/docs/adr/093-container-image-posture.html#rationale","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Rationale","x":"- A secret that is never a layer cannot leak from a layer. BuildKit secret mounts are the only mechanism that keeps the credential out of the image, the build cache and docker…"},"872":{"u":"/docs/adr/093-container-image-posture.html#trade-offs","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Trade-offs","x":"- Eleven copies drift independently. There is no shared base Dockerfile and no test that compares them, so a fix applied to one image is applied to one image. The ReadyToRun…"},"873":{"u":"/docs/adr/093-container-image-posture.html#revision-2026-09-07","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"The open postures this record listed are closed in both consumers, from the 2026-09-07 security review. 1. Base images are pinned by digest (SEC-ADC-32 / SEC-ADC-54 /…"},"874":{"u":"/docs/adr/093-container-image-posture.html#related","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Related","x":"ADR-038 (supply-chain provenance: it gates the package graph with lock files, a vulnerability audit and an SBOM, and stops at the repository boundary, so the image layers this…"},"875":{"u":"/docs/adr/093-container-image-posture.html#revision-2026-09-03","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Revision (2026-09-03)","x":"Container sizing is uniform, so the ReadyToRun argument applies evenly. Decision 3 justified ReadyToRun by the CPU the replicas land on and cited a split: four ADC apps at 0.25…"},"876":{"u":"/docs/adr/093-container-image-posture.html#revision-2026-09-10","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Revision (2026-09-10)","x":"Locked mode is back on Store's four non-UI images, and the inventory is stated per image rather than by one example. The anchor in Revision (2026-09-07) item 3 pointed at a line…"},"877":{"u":"/docs/adr/093-container-image-posture.html#revision-2026-10-01","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Three statements in Context and decision 4 no longer matched the files and are corrected in place. The digest pair is shared, not per…"},"878":{"u":"/docs/adr/094-client-entity-data-access.html","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records"},"879":{"u":"/docs/adr/094-client-entity-data-access.html#status","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-23). Revised 2026-08-31: GetByIdAsync is recorded with its real signature (id, includeChildren, CancellationToken; a missing entity is a NotFound failure, there…"},"880":{"u":"/docs/adr/094-client-entity-data-access.html#context","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Context","x":"ADR-034 decided the server half of entity data access: a generic controller base with a dynamic query contract, where filters arrive as filters[Property].operator /…"},"881":{"u":"/docs/adr/094-client-entity-data-access.html#decision","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Decision","x":"Client-side entity data access goes through one hand-written base hierarchy in MMCA.Common.UI. - One HTTP root: AuthenticatedServiceBase…"},"882":{"u":"/docs/adr/094-client-entity-data-access.html#rationale","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Rationale","x":"- A hand-written typed base beats a generated client here because the surface is already generic. ADR-034 collapsed N entity endpoints into one shape, so there is exactly one…"},"883":{"u":"/docs/adr/094-client-entity-data-access.html#trade-offs","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- No generated client means drift is caught at runtime, not at build time. A server-side rename of a query parameter or a DTO property does not fail the UI build; it fails the…"},"884":{"u":"/docs/adr/094-client-entity-data-access.html#revision-2026-10-01","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The Blazor Server per-circuit state rule is recorded as a Decision bullet. It was already enforced but not written down: UI assemblies carry no mutable static members outside a…"},"885":{"u":"/docs/adr/094-client-entity-data-access.html#related","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-034 (the server surface this contract calls, and the filter grammar the client constructs), ADR-017 (the server-side filter whose client half is specified here: who mints the…"},"886":{"u":"/docs/adr/095-soft-delete-unique-indexes.html","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records"},"887":{"u":"/docs/adr/095-soft-delete-unique-indexes.html#status","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-23). Revised 2026-08-26 (the convention appends its clause to a hand-authored filter instead of skipping the index)."},"888":{"u":"/docs/adr/095-soft-delete-unique-indexes.html#context","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Context","x":"ADR-005 makes deletion soft: an IAuditableEntity sets IsDeleted = true (MMCA.Common/Source/Core/MMCA.Common.Domain/Interfaces/IAuditableEntity.cs:11) and a named global query…"},"889":{"u":"/docs/adr/095-soft-delete-unique-indexes.html#decision","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Decision","x":"Make the filter a convention: every unique index on a soft-deletable entity excludes deleted rows, automatically, in every context of every consumer. - A model-finalizing…"},"890":{"u":"/docs/adr/095-soft-delete-unique-indexes.html#rationale","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Rationale","x":"- The database should agree with what the application shows. The query filter already says a soft-deleted row does not exist; a unique index that disagrees is the one place the…"},"891":{"u":"/docs/adr/095-soft-delete-unique-indexes.html#trade-offs","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Trade-offs","x":"- It moves schema in consumers, invisibly from the entity configuration. Adopting the convention is a database-contract change: nothing in an entity configuration changed, but…"},"892":{"u":"/docs/adr/095-soft-delete-unique-indexes.html#revision-2026-10-01","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Engine coverage now includes PostgreSQL: SoftDeleteFilterSql.Build emits \"IsDeleted\" = false there, because the flag is a real boolean column,…"},"893":{"u":"/docs/adr/095-soft-delete-unique-indexes.html#related","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Related","x":"ADR-005 (decides soft-delete over erasure and owns the query filter that hides the row, but says nothing about uniqueness: this ADR closes that gap), ADR-057 (the expand/contract…"},"894":{"u":"/docs/adr/096-best-effort-side-effects.html","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records"},"895":{"u":"/docs/adr/096-best-effort-side-effects.html#status","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-23). Revised 2026-08-31. Revised 2026-09-19. Revised 2026-09-25 (the ADR-054 cross-reference is re-anchored onto that record's current best-effort trade-off)."},"896":{"u":"/docs/adr/096-best-effort-side-effects.html#context","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Context","x":"A command that has already committed often has follow-up work attached to it: evict the output-cache entries the write invalidated, broadcast the new state to a live channel,…"},"897":{"u":"/docs/adr/096-best-effort-side-effects.html#decision","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Decision","x":"One framework helper defines the contract, and a swallow that does not go through it is a deliberate, documented exception. - BestEffort.ExecuteAsync(operation, logger, action,…"},"898":{"u":"/docs/adr/096-best-effort-side-effects.html#rationale","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Rationale","x":"- One policy beats five local leniencies. Each feature record is still right about its own degradation; what they could not each decide is the shape of the swallow. A single…"},"899":{"u":"/docs/adr/096-best-effort-side-effects.html#trade-offs","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- Nothing gates use of the helper. There is no fitness rule, analyzer or architecture test that fails a build for a hand-rolled catch (Exception) that should have been a…"},"900":{"u":"/docs/adr/096-best-effort-side-effects.html#revision-2026-10-01","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; the adoption inventory and several statements about it are corrected. Adoption is twelve call sites, not eleven: Store's inventory set path is a…"},"901":{"u":"/docs/adr/096-best-effort-side-effects.html#related","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-024 (push delivery failure is non-fatal and recorded rather than raised, one of the local leniencies this policy generalizes), ADR-026 (eviction is best-effort, and its…"},"902":{"u":"/docs/adr/097-multi-device-refresh-sessions.html","d":"ADR-097: Multi-Device Refresh Sessions (Hashed, Rotating, Per Device)","k":"Architecture Decision Records"},"903":{"u":"/docs/adr/097-multi-device-refresh-sessions.html#status","d":"ADR-097: Multi-Device Refresh Sessions (Hashed, Rotating, Per Device)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-26). Supersedes the storage model of ADR-050 (one plaintext refresh-token column on the user row); the rotation and reuse-detection policy that record decided…"},"904":{"u":"/docs/adr/097-multi-device-refresh-sessions.html#context","d":"ADR-097: Multi-Device Refresh Sessions (Hashed, Rotating, Per Device)","k":"Architecture Decision Records","t":"Context","x":"ADR-050 stores a user's refresh token as a single nullable RefreshToken string plus its RefreshTokenExpiry on the app's User aggregate. That model settles rotation and reuse…"},"905":{"u":"/docs/adr/097-multi-device-refresh-sessions.html#decision","d":"ADR-097: Multi-Device Refresh Sessions (Hashed, Rotating, Per Device)","k":"Architecture Decision Records","t":"Decision","x":"Refresh tokens become rows in their own table: one row per signed-in device, hashed at rest, chained on rotation. - RefreshSession is a flat framework record, not an aggregate.…"},"906":{"u":"/docs/adr/097-multi-device-refresh-sessions.html#rationale","d":"ADR-097: Multi-Device Refresh Sessions (Hashed, Rotating, Per Device)","k":"Architecture Decision Records","t":"Rationale","x":"- A credential at rest is a credential. Hashing is what turns a database read from \"mint tokens for every signed-in user\" into \"hold a list of digests\". The unsalted digest is…"},"907":{"u":"/docs/adr/097-multi-device-refresh-sessions.html#trade-offs","d":"ADR-097: Multi-Device Refresh Sessions (Hashed, Rotating, Per Device)","k":"Architecture Decision Records","t":"Trade-offs","x":"- This is a breaking change with a data migration attached. IAuthUser loses RefreshToken, RefreshTokenExpiry, UpdateRefreshToken and RevokeRefreshToken (IAuthUser.cs:9-14, the…"},"908":{"u":"/docs/adr/097-multi-device-refresh-sessions.html#revision-2026-09-07","d":"ADR-097: Multi-Device Refresh Sessions (Hashed, Rotating, Per Device)","k":"Architecture Decision Records","t":"Revision (2026-09-07)","x":"Credential rotation now ends the session family (SEC-Common-02). A stolen refresh chain otherwise survived the exact remediation a user performs on discovering it: the rotation…"},"909":{"u":"/docs/adr/097-multi-device-refresh-sessions.html#revision-2026-10-01","d":"ADR-097: Multi-Device Refresh Sessions (Hashed, Rotating, Per Device)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; the current-state sections now match the code. Content corrected in place: the session cap is read through a protected virtual…"},"910":{"u":"/docs/adr/097-multi-device-refresh-sessions.html#related","d":"ADR-097: Multi-Device Refresh Sessions (Hashed, Rotating, Per Device)","k":"Architecture Decision Records","t":"Related","x":"ADR-050 (the single-column model this record replaces, and the source of the rotation and reuse-detection policy it keeps), ADR-004 (the stateless RS256 access token this flow…"},"911":{"u":"/docs/adr/098-aspire-orchestration-not-testing-or-dashboards.html","d":"ADR-098: Aspire for Orchestration, Not for Testing or Production Dashboards","k":"Architecture Decision Records"},"912":{"u":"/docs/adr/098-aspire-orchestration-not-testing-or-dashboards.html#status","d":"ADR-098: Aspire for Orchestration, Not for Testing or Production Dashboards","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-28). Revised 2026-08-31: the sanctioned nightly AppHost smoke test is implemented, and every citation below is re-verified against current source. Revised…"},"913":{"u":"/docs/adr/098-aspire-orchestration-not-testing-or-dashboards.html#context","d":"ADR-098: Aspire for Orchestration, Not for Testing or Production Dashboards","k":"Architecture Decision Records","t":"Context","x":"Aspire is used on exactly two surfaces here. Orchestration. Each app has an AppHost that composes the local stack from one file: ADC's provisions a persistent SQL Server…"},"914":{"u":"/docs/adr/098-aspire-orchestration-not-testing-or-dashboards.html#decision","d":"ADR-098: Aspire for Orchestration, Not for Testing or Production Dashboards","k":"Architecture Decision Records","t":"Decision","x":"The tiers that exist keep their shape, and DistributedApplicationTestingBuilder stays out of them. - Per-service tier: one in-process host, real SQL, mocked cross-service edges.…"},"915":{"u":"/docs/adr/098-aspire-orchestration-not-testing-or-dashboards.html#rationale","d":"ADR-098: Aspire for Orchestration, Not for Testing or Production Dashboards","k":"Architecture Decision Records","t":"Rationale","x":"- Test at the boundary that ships. A service is deployed as its own container app (MMCA.ADC/infra/main.bicep:1624, :1851, :2002, :2135), configured entirely through environment…"},"916":{"u":"/docs/adr/098-aspire-orchestration-not-testing-or-dashboards.html#trade-offs","d":"ADR-098: Aspire for Orchestration, Not for Testing or Production Dashboards","k":"Architecture Decision Records","t":"Trade-offs","x":"- Nothing below the E2E lane tests the AppHost's own wiring. A bad reference or a missing environment injection in Program.cs is caught by the E2E gate or by a developer, and the…"},"917":{"u":"/docs/adr/098-aspire-orchestration-not-testing-or-dashboards.html#revision-2026-10-01","d":"ADR-098: Aspire for Orchestration, Not for Testing or Production Dashboards","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Two statements were incomplete and are corrected in place. First, the framework's AppHost testing package is also exercised per PR in…"},"918":{"u":"/docs/adr/098-aspire-orchestration-not-testing-or-dashboards.html#related","d":"ADR-098: Aspire for Orchestration, Not for Testing or Production Dashboards","k":"Architecture Decision Records","t":"Related","x":"ADR-041 (the shared Aspire OpenTelemetry baseline and the sampling / metric-toggle knobs this record's production half configures), ADR-062 (the alert rules and the workbook that…"},"919":{"u":"/docs/adr/099-generic-write-side-entity-commands.html","d":"ADR-099: Generic Write-Side Entity Commands (Create, Update, Delete Without a Handler per Aggregate)","k":"Architecture Decision Records"},"920":{"u":"/docs/adr/099-generic-write-side-entity-commands.html#status","d":"ADR-099: Generic Write-Side Entity Commands (Create, Update, Delete Without a Handler per Aggregate)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-29, framework v1.170.0). Extends ADR-034, which gave every entity a generic read surface plus create and delete, by completing the write half: a generic update…"},"921":{"u":"/docs/adr/099-generic-write-side-entity-commands.html#context","d":"ADR-099: Generic Write-Side Entity Commands (Create, Update, Delete Without a Handler per Aggregate)","k":"Architecture Decision Records","t":"Context","x":"ADR-034 records the read side of the generic resource layer and stops one verb short. Its AggregateRootEntityControllerBase ships [HttpPost] create and [HttpDelete(\"{id}\")]…"},"922":{"u":"/docs/adr/099-generic-write-side-entity-commands.html#decision","d":"ADR-099: Generic Write-Side Entity Commands (Create, Update, Delete Without a Handler per Aggregate)","k":"Architecture Decision Records","t":"Decision","x":"Ship the generic write side as four additive pieces plus a registration helper. 1. The aggregate keeps the mutation, behind one interface. IEntityUpdateApplier…"},"923":{"u":"/docs/adr/099-generic-write-side-entity-commands.html#rationale","d":"ADR-099: Generic Write-Side Entity Commands (Create, Update, Delete Without a Handler per Aggregate)","k":"Architecture Decision Records","t":"Rationale","x":"- The one thing that varies per aggregate is the one thing the module writes. Load, concurrency stamping, NotFound, save, DTO projection, cache invalidation and validation are…"},"924":{"u":"/docs/adr/099-generic-write-side-entity-commands.html#trade-offs","d":"ADR-099: Generic Write-Side Entity Commands (Create, Update, Delete Without a Handler per Aggregate)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The update request is the write contract, so it tracks the aggregate. Exactly the coupling ADR-034 records for the read side, on the other axis: what a caller may change is…"},"925":{"u":"/docs/adr/099-generic-write-side-entity-commands.html#related","d":"ADR-099: Generic Write-Side Entity Commands (Create, Update, Delete Without a Handler per Aggregate)","k":"Architecture Decision Records","t":"Related","x":"ADR-034 (the read side plus create and delete this record completes; its AggregateRootEntityControllerBase is what CrudEntityControllerBase derives from), ADR-083 (the events the…"},"926":{"u":"/docs/adr/099-generic-write-side-entity-commands.html#revision-2026-08-30-the-five-surfaces-that-complete-the-write-side","d":"ADR-099: Generic Write-Side Entity Commands (Create, Update, Delete Without a Handler per Aggregate)","k":"Architecture Decision Records","t":"Revision (2026-08-30): the five surfaces that complete the write side","x":"The decision above shipped the generic update and left the bases where it found them. Reading real write handlers against those bases surfaced five shapes that still forced a…"},"927":{"u":"/docs/adr/099-generic-write-side-entity-commands.html#revision-2026-10-01","d":"ADR-099: Generic Write-Side Entity Commands (Create, Update, Delete Without a Handler per Aggregate)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision changed; two statements in the current-state sections were corrected and stale citations re-anchored. The Rationale claimed the generic update inherits every ADR-014…"},"928":{"u":"/docs/adr/100-outbox-opt-in-resolved-from-messaging-mode.html","d":"ADR-100: The Outbox Is Resolved from the Messaging Mode, Not Always On","k":"Architecture Decision Records"},"929":{"u":"/docs/adr/100-outbox-opt-in-resolved-from-messaging-mode.html#status","d":"ADR-100: The Outbox Is Resolved from the Messaging Mode, Not Always On","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-29, framework v1.170.0). Amends ADR-003 (the outbox runs when the transport needs it, rather than in every host unconditionally) and follows the three-valued…"},"930":{"u":"/docs/adr/100-outbox-opt-in-resolved-from-messaging-mode.html#context","d":"ADR-100: The Outbox Is Resolved from the Messaging Mode, Not Always On","k":"Architecture Decision Records","t":"Context","x":"ADR-003 gives every host a transactional outbox: domain and integration events are written to OutboxMessages in the same transaction as the aggregate changes, OutboxProcessor…"},"931":{"u":"/docs/adr/100-outbox-opt-in-resolved-from-messaging-mode.html#decision","d":"ADR-100: The Outbox Is Resolved from the Messaging Mode, Not Always On","k":"Architecture Decision Records","t":"Decision","x":"Make the outbox a three-valued setting resolved from the transport, gate the components that cost something, keep the schema, and refuse the one combination that cannot work. 1.…"},"932":{"u":"/docs/adr/100-outbox-opt-in-resolved-from-messaging-mode.html#rationale","d":"ADR-100: The Outbox Is Resolved from the Messaging Mode, Not Always On","k":"Architecture Decision Records","t":"Rationale","x":"- The outbox is a transport decision, not a persistence one. Whether an event must survive a process boundary is exactly the question MessageBus:Provider already answers, so…"},"933":{"u":"/docs/adr/100-outbox-opt-in-resolved-from-messaging-mode.html#trade-offs","d":"ADR-100: The Outbox Is Resolved from the Messaging Mode, Not Always On","k":"Architecture Decision Records","t":"Trade-offs","x":"- The default for an in-process host changed. A host on InProcess messaging (or with no MessageBus section at all, which resolves to the same provider) no longer runs the durable…"},"934":{"u":"/docs/adr/100-outbox-opt-in-resolved-from-messaging-mode.html#revision-2026-10-01","d":"ADR-100: The Outbox Is Resolved from the Messaging Mode, Not Always On","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Re-anchored the startup-guard citations in Decision 4 (DependencyInjection.Messaging.cs:196, guard :198, throw :200-201) and the…"},"935":{"u":"/docs/adr/100-outbox-opt-in-resolved-from-messaging-mode.html#related","d":"ADR-100: The Outbox Is Resolved from the Messaging Mode, Not Always On","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox itself: the dual-dispatch contract, the processor, the retry and dead-letter policy, all unchanged for a host that runs it), ADR-021 (the consumer-side…"},"936":{"u":"/docs/adr/101-common-metapackage.html","d":"ADR-101: An MMCA.Common Metapackage for the Core Six","k":"Architecture Decision Records"},"937":{"u":"/docs/adr/101-common-metapackage.html#status","d":"ADR-101: An MMCA.Common Metapackage for the Core Six","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-29, framework v1.170.0). Extends ADR-053 (the metapackage ships to both registries from the same tag, through the same workflow) and inherits ADR-016 unchanged…"},"938":{"u":"/docs/adr/101-common-metapackage.html#context","d":"ADR-101: An MMCA.Common Metapackage for the Core Six","k":"Architecture Decision Records","t":"Context","x":"ADR-053 made the install path credential-free: dotnet add package MMCA.Common.API works for anyone, from nuget.org, with no token and no hand-written nuget.config. What it did…"},"939":{"u":"/docs/adr/101-common-metapackage.html#decision","d":"ADR-101: An MMCA.Common Metapackage for the Core Six","k":"Architecture Decision Records","t":"Decision","x":"Publish a metapackage named MMCA.Common that carries dependencies and no code. 1. It bundles the Core 6, in layer order. Source/MMCA.Common/MMCA.Common.csproj:26-31 references…"},"940":{"u":"/docs/adr/101-common-metapackage.html#rationale","d":"ADR-101: An MMCA.Common Metapackage for the Core Six","k":"Architecture Decision Records","t":"Rationale","x":"- The common case should cost one line. Six references is not hard, it is repetitive and order-independent noise that a reader has to verify rather than read, and getting it…"},"941":{"u":"/docs/adr/101-common-metapackage.html#trade-offs","d":"ADR-101: An MMCA.Common Metapackage for the Core Six","k":"Architecture Decision Records","t":"Trade-offs","x":"- A consumer mixing both forms has more pins to keep in step, not fewer. An app taking MMCA.Common plus MMCA.Common.UI and the Testing. set still has to sweep every entry to the…"},"942":{"u":"/docs/adr/101-common-metapackage.html#related","d":"ADR-101: An MMCA.Common Metapackage for the Core Six","k":"Architecture Decision Records","t":"Related","x":"ADR-053 (dual-registry publishing, which the metapackage joins with no workflow change), ADR-016 (one version for every package, one sweep per consumer: the property that makes…"},"943":{"u":"/docs/adr/102-pbkdf2-only-password-hashing.html","d":"ADR-102: PBKDF2-Only Password Hashing","k":"Architecture Decision Records"},"944":{"u":"/docs/adr/102-pbkdf2-only-password-hashing.html#status","d":"ADR-102: PBKDF2-Only Password Hashing","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-31). Supersedes ADR-032. Revised 2026-09-11: verification now carries a canonical-material length guard (it rejects any stored hash or salt that is not exactly…"},"945":{"u":"/docs/adr/102-pbkdf2-only-password-hashing.html#context","d":"ADR-102: PBKDF2-Only Password Hashing","k":"Architecture Decision Records","t":"Context","x":"ADR-032 recorded a hasher with two verification paths: PBKDF2-HMAC-SHA512 for new credentials, and an HMAC-SHA512 recompute for rows written under an older scheme, selected at…"},"946":{"u":"/docs/adr/102-pbkdf2-only-password-hashing.html#decision","d":"ADR-102: PBKDF2-Only Password Hashing","k":"Architecture Decision Records","t":"Decision","x":"Hashing and verification are PBKDF2-only. There is one framework IPasswordHasher and one code path through it, in both directions. - One interface, one implementation, one…"},"947":{"u":"/docs/adr/102-pbkdf2-only-password-hashing.html#rationale","d":"ADR-102: PBKDF2-Only Password Hashing","k":"Architecture Decision Records","t":"Rationale","x":"- A verification path that serves no rows is pure risk. With the production stores confirmed free of legacy-format credentials, the branch could only ever be reached by a…"},"948":{"u":"/docs/adr/102-pbkdf2-only-password-hashing.html#trade-offs","d":"ADR-102: PBKDF2-Only Password Hashing","k":"Architecture Decision Records","t":"Trade-offs","x":"- A legacy-format row that reaches production now fails verification. Restoring an old backup, importing a credential from an external system in the legacy shape, or missing a…"},"949":{"u":"/docs/adr/102-pbkdf2-only-password-hashing.html#revision-2026-10-01","d":"ADR-102: PBKDF2-Only Password Hashing","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Anchor refresh only; no decision or rationale changed. The Decision's citations are re-anchored to current source: the TryAddSingleton registration (DependencyInjection.cs:304),…"},"950":{"u":"/docs/adr/102-pbkdf2-only-password-hashing.html#related","d":"ADR-102: PBKDF2-Only Password Hashing","k":"Architecture Decision Records","t":"Related","x":"ADR-032 (the superseded record: the same hasher, its parameters and its call-site hoisting, plus the legacy-compatibility design this reverses), ADR-004 (cross-service JWT / JWKS…"},"951":{"u":"/docs/adr/103-bunit-component-test-tier.html","d":"ADR-103: bUnit Component-Test Tier as a Shipped Package","k":"Architecture Decision Records"},"952":{"u":"/docs/adr/103-bunit-component-test-tier.html#status","d":"ADR-103: bUnit Component-Test Tier as a Shipped Package","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-31). Revised 2026-09-03: one trade-off overstated how far the AngleSharp advisory pin travels. See Revision (2026-09-03) at the end. Revised 2026-09-19: four…"},"953":{"u":"/docs/adr/103-bunit-component-test-tier.html#context","d":"ADR-103: bUnit Component-Test Tier as a Shipped Package","k":"Architecture Decision Records","t":"Context","x":"Three test tiers in this workspace are decided in writing and one is not. ADR-015 gates structure with NetArchTest. ADR-058 ships the runtime conformance suites as abstract bases…"},"954":{"u":"/docs/adr/103-bunit-component-test-tier.html#decision","d":"ADR-103: bUnit Component-Test Tier as a Shipped Package","k":"Architecture Decision Records","t":"Decision","x":"Ship the component-test tier as a package. MMCA.Common.Testing.UI (MMCA.Common/Source/Hosting/MMCA.Common.Testing.UI/MMCA.Common.Testing.UI.csproj:3-4) is one of the MMCA.Common.…"},"955":{"u":"/docs/adr/103-bunit-component-test-tier.html#rationale","d":"ADR-103: bUnit Component-Test Tier as a Shipped Package","k":"Architecture Decision Records","t":"Rationale","x":"- The setup is what a component test gets wrong, so the setup is what the framework should own. Every item in the Decision is a choice with one correct answer per repo and a…"},"956":{"u":"/docs/adr/103-bunit-component-test-tier.html#trade-offs","d":"ADR-103: bUnit Component-Test Tier as a Shipped Package","k":"Architecture Decision Records","t":"Trade-offs","x":"- Loose JSInterop proves nothing about JS. A component test can render a component that calls a JS module which does not exist, because the loose mode answers with defaults…"},"957":{"u":"/docs/adr/103-bunit-component-test-tier.html#related","d":"ADR-103: bUnit Component-Test Tier as a Shipped Package","k":"Architecture Decision Records","t":"Related","x":"ADR-058 (the runtime conformance tier this sits below: same delivery shape, different question, and its bases need a booted host where these need only a renderer), ADR-063 and…"},"958":{"u":"/docs/adr/103-bunit-component-test-tier.html#revision-2026-09-03","d":"ADR-103: bUnit Component-Test Tier as a Shipped Package","k":"Architecture Decision Records","t":"Revision (2026-09-03)","x":"The decision and the mechanism are unchanged. One trade-off's premise was wrong. \"The bUnit version is pinned per repo\" said the AngleSharp advisory pin has to be repeated across…"},"959":{"u":"/docs/adr/103-bunit-component-test-tier.html#revision-2026-09-19","d":"ADR-103: bUnit Component-Test Tier as a Shipped Package","k":"Architecture Decision Records","t":"Revision (2026-09-19)","x":"The decision and the mechanism are unchanged. Four facts had drifted since the record was written. The bunit line is now pinned at 2.11.3, not 2.9.0, and all three central…"},"960":{"u":"/docs/adr/103-bunit-component-test-tier.html#revision-2026-10-01","d":"ADR-103: bUnit Component-Test Tier as a Shipped Package","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The authorization mechanism changed; the rest of the decision is unchanged. The base no longer registers a permissive IsAuthenticatedAuthorizationService. After…"},"961":{"u":"/docs/adr/104-smart-enums-as-opt-in-capability.html","d":"ADR-104: Plain Enums by Default, Enumeration as the Opt-In Smart-Enum Base","k":"Architecture Decision Records"},"962":{"u":"/docs/adr/104-smart-enums-as-opt-in-capability.html#status","d":"ADR-104: Plain Enums by Default, Enumeration as the Opt-In Smart-Enum Base","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-31). Revised 2026-09-09: ADR-115 copies this record's posture verbatim for strongly typed identifiers: shipped, tested, wired into JSON, EF Core, MVC binding…"},"963":{"u":"/docs/adr/104-smart-enums-as-opt-in-capability.html#context","d":"ADR-104: Plain Enums by Default, Enumeration as the Opt-In Smart-Enum Base","k":"Architecture Decision Records","t":"Context","x":"A bounded set of named values shows up everywhere in this workspace: the state that triggered a domain event, an error category, a rate-limiting algorithm, a message-bus…"},"964":{"u":"/docs/adr/104-smart-enums-as-opt-in-capability.html#decision","d":"ADR-104: Plain Enums by Default, Enumeration as the Opt-In Smart-Enum Base","k":"Architecture Decision Records","t":"Decision","x":"A plain C enum is the default for a bounded set. Enumeration is the opt-in base for a set whose members need behavior or data, and nothing in the framework pushes a consumer…"},"965":{"u":"/docs/adr/104-smart-enums-as-opt-in-capability.html#rationale","d":"ADR-104: Plain Enums by Default, Enumeration as the Opt-In Smart-Enum Base","k":"Architecture Decision Records","t":"Rationale","x":"- The default should be the cheap type. A CLR enum costs nothing at a call boundary, needs no serializer or EF registration, and is what a .NET reader expects for a set of names.…"},"966":{"u":"/docs/adr/104-smart-enums-as-opt-in-capability.html#trade-offs","d":"ADR-104: Plain Enums by Default, Enumeration as the Opt-In Smart-Enum Base","k":"Architecture Decision Records","t":"Trade-offs","x":"- A capability nobody uses is a capability nobody has stress-tested. The contract is pinned by 33 test methods against private fixtures, not by a production set with real…"},"967":{"u":"/docs/adr/104-smart-enums-as-opt-in-capability.html#revision-2026-10-01","d":"ADR-104: Plain Enums by Default, Enumeration as the Opt-In Smart-Enum Base","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. The remark on Enumeration now states the same three assertions the Context lists and says the fitness rule names no record and inspects no…"},"968":{"u":"/docs/adr/104-smart-enums-as-opt-in-capability.html#related","d":"ADR-104: Plain Enums by Default, Enumeration as the Opt-In Smart-Enum Base","k":"Architecture Decision Records","t":"Related","x":"ADR-037 (the precedent for recording a shipped, tested and unadopted framework capability as a decision rather than leaving it to read as unfinished adoption), ADR-068 (the seven…"},"969":{"u":"/docs/adr/105-data-residency-build-gate.html","d":"ADR-105: The Published Data-Residency Claim as a Build Gate","k":"Architecture Decision Records"},"970":{"u":"/docs/adr/105-data-residency-build-gate.html#status","d":"ADR-105: The Published Data-Residency Claim as a Build Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-01). Revised 2026-10-01 (region matching is a whole-token comparison rather than plain containment; see Revision below)."},"971":{"u":"/docs/adr/105-data-residency-build-gate.html#context","d":"ADR-105: The Published Data-Residency Claim as a Build Gate","k":"Architecture Decision Records","t":"Context","x":"Both deployed apps publish a privacy policy at their repo root, and each has a section that tells a user where their personal data is stored (MMCA.ADC/PRIVACY.md:61,…"},"972":{"u":"/docs/adr/105-data-residency-build-gate.html#decision","d":"ADR-105: The Published Data-Residency Claim as a Build Gate","k":"Architecture Decision Records","t":"Decision","x":"The published data-residency claim is a build-gated assertion. A shared fitness base parses the region where a repo actually provisions its PII-bearing storage from that repo's…"},"973":{"u":"/docs/adr/105-data-residency-build-gate.html#rationale","d":"ADR-105: The Published Data-Residency Claim as a Build Gate","k":"Architecture Decision Records","t":"Rationale","x":"- The statement with the most legal weight had the least enforcement. Every other compliance claim in these repos is backed by code a rule can inspect: erasure by IAnonymizable,…"},"974":{"u":"/docs/adr/105-data-residency-build-gate.html#trade-offs","d":"ADR-105: The Published Data-Residency Claim as a Build Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- It proves the policy agrees with a file, not with Azure. Both extractors read committed text. A database provisioned by hand into another region, a restore into a different…"},"975":{"u":"/docs/adr/105-data-residency-build-gate.html#revision-2026-10-01","d":"ADR-105: The Published Data-Residency Claim as a Build Gate","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Region matching moved from plain containment to a whole-token comparison. The protected static ContainsRegionClaim…"},"976":{"u":"/docs/adr/105-data-residency-build-gate.html#related","d":"ADR-105: The Published Data-Residency Claim as a Build Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-009 (the single-region acceptance a consumer must declare: this record is what keeps the published claim honest about which region that is), ADR-005 (the erasure path guarded…"},"977":{"u":"/docs/adr/106-extension-members-as-public-di-surface.html","d":"ADR-106: C# Extension Members as the Public DI Registration Surface","k":"Architecture Decision Records"},"978":{"u":"/docs/adr/106-extension-members-as-public-di-surface.html#status","d":"ADR-106: C# Extension Members as the Public DI Registration Surface","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-01; counts re-measured 2026-10-01)."},"979":{"u":"/docs/adr/106-extension-members-as-public-di-surface.html#context","d":"ADR-106: C# Extension Members as the Public DI Registration Surface","k":"Architecture Decision Records","t":"Context","x":"Every host in this workspace boots the same way: a Program.cs calls a short list of Add methods on IServiceCollection, one per layer, and all of the framework's wiring sits…"},"980":{"u":"/docs/adr/106-extension-members-as-public-di-surface.html#decision","d":"ADR-106: C# Extension Members as the Public DI Registration Surface","k":"Architecture Decision Records","t":"Decision","x":"The framework's public dependency-injection surface is written as C extension members: extension(T) blocks inside public static class types, compiled under LangVersion preview in…"},"981":{"u":"/docs/adr/106-extension-members-as-public-di-surface.html#rationale","d":"ADR-106: C# Extension Members as the Public DI Registration Surface","k":"Architecture Decision Records","t":"Rationale","x":"- One Add name per layer is the point of the surface. A host reads as a list of layers (Program.cs:78, :79, :101, :132), and grouping the registrations by receiver in a single…"},"982":{"u":"/docs/adr/106-extension-members-as-public-di-surface.html#trade-offs","d":"ADR-106: C# Extension Members as the Public DI Registration Surface","k":"Architecture Decision Records","t":"Trade-offs","x":"- A preview language feature under a floating SDK is a moving target. No global.json pins an SDK version and CI installs dotnet-version: '10.0.x'…"},"983":{"u":"/docs/adr/106-extension-members-as-public-di-surface.html#revision-2026-10-01","d":"ADR-106: C# Extension Members as the Public DI Registration Surface","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed: the framework's DI surface is still written as extension(T) blocks compiled under LangVersion preview, and both emitted shapes are still…"},"984":{"u":"/docs/adr/106-extension-members-as-public-di-surface.html#related","d":"ADR-106: C# Extension Members as the Public DI Registration Surface","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (the RS0016/RS0017 baseline that freezes both emitted shapes of every extension member, and the fitness-rule tier that had to learn about ExtensionMarkerAttribute),…"},"985":{"u":"/docs/adr/107-transaction-execution-contract.html","d":"ADR-107: Transaction Execution and Commit-Ambiguity Contract","k":"Architecture Decision Records"},"986":{"u":"/docs/adr/107-transaction-execution-contract.html#status","d":"ADR-107: Transaction Execution and Commit-Ambiguity Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-03; adoption counts and consumer state re-measured 2026-09-19). Revised 2026-09-25 (MMCA.ADC's ITransactional count corrected to six, adding…"},"987":{"u":"/docs/adr/107-transaction-execution-contract.html#context","d":"ADR-107: Transaction Execution and Commit-Ambiguity Contract","k":"Architecture Decision Records","t":"Context","x":"Every transactional write in this workspace funnels through one method. IUnitOfWork.ExecuteInTransactionAsync…"},"988":{"u":"/docs/adr/107-transaction-execution-contract.html#decision","d":"ADR-107: Transaction Execution and Commit-Ambiguity Contract","k":"Architecture Decision Records","t":"Decision","x":"ExecuteInTransactionAsync is a re-entrant, retriable, commit-once unit: the whole delegate is the retriable work, the commit phase is deliberately outside the retry, and a commit…"},"989":{"u":"/docs/adr/107-transaction-execution-contract.html#rationale","d":"ADR-107: Transaction Execution and Commit-Ambiguity Contract","k":"Architecture Decision Records","t":"Rationale","x":"- Retrying the operation is safe; retrying the commit is not. A transient failure before the commit leaves nothing durable, so re-running the delegate against a cleared change…"},"990":{"u":"/docs/adr/107-transaction-execution-contract.html#trade-offs","d":"ADR-107: Transaction Execution and Commit-Ambiguity Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- The ambiguity is a hard failure the caller must handle, and today nobody catches it. A host with no [Idempotent] coverage on the affected endpoint surfaces it as an unhandled…"},"991":{"u":"/docs/adr/107-transaction-execution-contract.html#revision-2026-10-01","d":"ADR-107: Transaction Execution and Commit-Ambiguity Contract","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The commit phase gained a pre-commit step. Since v1.215.0 (MMCA.Common/CHANGELOG.md:53), a successful attempt runs FlushEnrolledCommandsBeforeCommitAsync before TryCommit…"},"992":{"u":"/docs/adr/107-transaction-execution-contract.html#related","d":"ADR-107: Transaction Execution and Commit-Ambiguity Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-014 (the Transactional decorator that is the main caller, and whose 2026-07-19 revision decided the failed-Result rollback this record implements), ADR-006…"},"993":{"u":"/docs/adr/108-distributed-lock-primitive.html","d":"ADR-108: Cross-Replica Mutual Exclusion via IDistributedLock","k":"Architecture Decision Records"},"994":{"u":"/docs/adr/108-distributed-lock-primitive.html#status","d":"ADR-108: Cross-Replica Mutual Exclusion via IDistributedLock","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-03). Revised 2026-09-19 (the second consumer moved: ADC's AI scoring pass now runs as the internal command ScoreEventSessionsInternalCommandHandler under…"},"995":{"u":"/docs/adr/108-distributed-lock-primitive.html#context","d":"ADR-108: Cross-Replica Mutual Exclusion via IDistributedLock","k":"Architecture Decision Records","t":"Context","x":"Both deployed apps run more than one replica of every service. ADC's Conference container app is declared with minReplicas: 1, maxReplicas: 2 (MMCA.ADC/infra/main.bicep:1851,…"},"996":{"u":"/docs/adr/108-distributed-lock-primitive.html#decision","d":"ADR-108: Cross-Replica Mutual Exclusion via IDistributedLock","k":"Architecture Decision Records","t":"Decision","x":"The framework ships one cross-replica mutual-exclusion primitive, IDistributedLock: a non-reentrant, TTL-bounded, explicitly best-effort lock with an owner-scoped idempotent…"},"997":{"u":"/docs/adr/108-distributed-lock-primitive.html#rationale","d":"ADR-108: Cross-Replica Mutual Exclusion via IDistributedLock","k":"Architecture Decision Records","t":"Rationale","x":"- The degraded mode had to be visible, not silent. Registering nothing when Redis is absent would push a null check onto every caller, and registering a no-op lock would make a…"},"998":{"u":"/docs/adr/108-distributed-lock-primitive.html#trade-offs","d":"ADR-108: Cross-Replica Mutual Exclusion via IDistributedLock","k":"Architecture Decision Records","t":"Trade-offs","x":"- Failover can hand the same key to two holders. The lock inherits Redis's failover behavior (RedisDistributedLock.cs:19-23), so a primary loss between acquire and replicate is a…"},"999":{"u":"/docs/adr/108-distributed-lock-primitive.html#revision-2026-10-01","d":"ADR-108: Cross-Replica Mutual Exclusion via IDistributedLock","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Two statements no longer matched the code. First, adoption grew to four call sites: the framework's own PasswordResetTokenService takes IDistributedLock as a constructor…"},"1000":{"u":"/docs/adr/108-distributed-lock-primitive.html#related","d":"ADR-108: Cross-Replica Mutual Exclusion via IDistributedLock","k":"Architecture Decision Records","t":"Related","x":"ADR-017 (the HTTP idempotency filter, the first consumer, whose 409-on-lock-miss this record explains), ADR-026 (the cache registration this lock is registered beside, and the…"},"1001":{"u":"/docs/adr/109-feature-by-folder-convention.html","d":"ADR-109: Feature-by-Folder Layout as an Enforced Convention","k":"Architecture Decision Records"},"1002":{"u":"/docs/adr/109-feature-by-folder-convention.html#status","d":"ADR-109: Feature-by-Folder Layout as an Enforced Convention","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-03; folder inventory and IDE0130 exception list refreshed 2026-09-19)."},"1003":{"u":"/docs/adr/109-feature-by-folder-convention.html#context","d":"ADR-109: Feature-by-Folder Layout as an Enforced Convention","k":"Architecture Decision Records","t":"Context","x":"The rubric's section 5 asks that \"code is organized by feature/capability, so a change touches one cohesive slice\", with features \"grouped by use case ..., not by horizontal…"},"1004":{"u":"/docs/adr/109-feature-by-folder-convention.html#decision","d":"ADR-109: Feature-by-Folder Layout as an Enforced Convention","k":"Architecture Decision Records","t":"Decision","x":"Folder structure is a governed architectural property, not a matter of taste: an aggregate or feature names a folder, a folder holds at most twelve direct code files, namespaces…"},"1005":{"u":"/docs/adr/109-feature-by-folder-convention.html#rationale","d":"ADR-109: Feature-by-Folder Layout as an Enforced Convention","k":"Architecture Decision Records","t":"Rationale","x":"- A wide folder has stopped naming a feature. The rule's own statement of intent is that a folder \"that accumulates dozens of direct code files has stopped naming a feature and…"},"1006":{"u":"/docs/adr/109-feature-by-folder-convention.html#trade-offs","d":"ADR-109: Feature-by-Folder Layout as an Enforced Convention","k":"Architecture Decision Records","t":"Trade-offs","x":"- Layout changes are public API breaks on a lockstep package family. Because the namespace is the folder, a reorganization is a breaking release for every consumer (ADR-016),…"},"1007":{"u":"/docs/adr/109-feature-by-folder-convention.html#revision-2026-10-01","d":"ADR-109: Feature-by-Folder Layout as an Enforced Convention","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Citations were re-anchored: the rubric section 5 text (ArchitectureEvaluationCriteria.md:211, :214), the v1.183.0 and v1.184.0 changelog entries…"},"1008":{"u":"/docs/adr/109-feature-by-folder-convention.html#related","d":"ADR-109: Feature-by-Folder Layout as an Enforced Convention","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (the fitness-function tier this rule joins, and the public-API baseline that records FolderWidthTestsBase), ADR-016 (lockstep versioning: a layout change lands in every…"},"1009":{"u":"/docs/adr/110-rubric-v2-category-realignment.html","d":"ADR-110: Rubric Version 2, Category Realignment at 34","k":"Architecture Decision Records"},"1010":{"u":"/docs/adr/110-rubric-v2-category-realignment.html#status","d":"ADR-110: Rubric Version 2, Category Realignment at 34","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-04; section 16 scope corrected 2026-09-04: MMCA.ADC scores the category, recorded in ADR-111; content refreshed 2026-09-19: MMCA.Common now scores section 16,…"},"1011":{"u":"/docs/adr/110-rubric-v2-category-realignment.html#context","d":"ADR-110: Rubric Version 2, Category Realignment at 34","k":"Architecture Decision Records","t":"Context","x":"The 34-category Architecture Evaluation Criteria (Website/docs-src/governance/ArchitectureEvaluationCriteria.md) is the only basis on which the three scorecards are scored, and…"},"1012":{"u":"/docs/adr/110-rubric-v2-category-realignment.html#decision","d":"ADR-110: Rubric Version 2, Category Realignment at 34","k":"Architecture Decision Records","t":"Decision","x":"The rubric is versioned, and version 2 keeps 34 categories with stable numbering by replacing the two overlap-heavy categories in place and adding criteria to eleven others. 1.…"},"1013":{"u":"/docs/adr/110-rubric-v2-category-realignment.html#rationale","d":"ADR-110: Rubric Version 2, Category Realignment at 34","k":"Architecture Decision Records","t":"Rationale","x":"Replacing in place preserves every NN citation in the backlogs, every §NN in the ADRs and the scorecard history, the command document's Part A/B/C dispatch split, and the literal…"},"1014":{"u":"/docs/adr/110-rubric-v2-category-realignment.html#trade-offs","d":"ADR-110: Rubric Version 2, Category Realignment at 34","k":"Architecture Decision Records","t":"Trade-offs","x":"- The index trend line carries a denominator change. The scorecard headers and the backlog headers record both the old and the new fractions for the cycle where it happened. -…"},"1015":{"u":"/docs/adr/111-ai-session-scoring-governance.html","d":"ADR-111: AI Session Scoring Governance","k":"Architecture Decision Records"},"1016":{"u":"/docs/adr/111-ai-session-scoring-governance.html#status","d":"ADR-111: AI Session Scoring Governance","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-04). Extended by ADR-120 (2026-09-11): the rules here that belong to calling a model rather than to scoring a session (a bounded call, a versioned and hashed…"},"1017":{"u":"/docs/adr/111-ai-session-scoring-governance.html#context","d":"ADR-111: AI Session Scoring Governance","k":"Architecture Decision Records","t":"Context","x":"MMCA.ADC ships one product feature that calls a language model. An organizer, looking at the session selection dashboard for an event, can trigger an AI scoring pass that rates…"},"1018":{"u":"/docs/adr/111-ai-session-scoring-governance.html#decision","d":"ADR-111: AI Session Scoring Governance","k":"Architecture Decision Records","t":"Decision","x":"A product feature that calls a model is governed like any other production dependency: the call sits behind a port, the model and the prompt are versioned and persisted with…"},"1019":{"u":"/docs/adr/111-ai-session-scoring-governance.html#rationale","d":"ADR-111: AI Session Scoring Governance","k":"Architecture Decision Records","t":"Rationale","x":"- A version you do not persist is not a version. The prompt version is worth something only because it is on the row next to the score. An organizer looking at a number two…"},"1020":{"u":"/docs/adr/111-ai-session-scoring-governance.html#trade-offs","d":"ADR-111: AI Session Scoring Governance","k":"Architecture Decision Records","t":"Trade-offs","x":"- Recorded-response replay cannot see model drift. Every free-tier case answers from a response captured at a point in time, so the tier that runs on every deploy is blind to…"},"1021":{"u":"/docs/adr/111-ai-session-scoring-governance.html#revision-2026-10-01","d":"ADR-111: AI Session Scoring Governance","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The guardrails are now one module-owned composition rather than a single framework registration. AddConferenceAiGuardrails registers the framework's PiiRedactionGuardrail and…"},"1022":{"u":"/docs/adr/111-ai-session-scoring-governance.html#related","d":"ADR-111: AI Session Scoring Governance","k":"Architecture Decision Records","t":"Related","x":"ADR-110 (the rubric category this record answers: section 16, AI-Native Application Architecture, scored for MMCA.ADC because of this feature), ADR-061 (the Key Vault reference…"},"1023":{"u":"/docs/adr/112-catalog-owned-effective-pricing.html","d":"ADR-112: Catalog Owns Effective Pricing; Sales Snapshots It at Checkout","k":"Architecture Decision Records"},"1024":{"u":"/docs/adr/112-catalog-owned-effective-pricing.html#status","d":"ADR-112: Catalog Owns Effective Pricing; Sales Snapshots It at Checkout","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-07). Revised 2026-09-07: an order line freezes the list price and the promotion label beside the charged unit price, so an order answers what it saved without…"},"1025":{"u":"/docs/adr/112-catalog-owned-effective-pricing.html#context","d":"ADR-112: Catalog Owns Effective Pricing; Sales Snapshots It at Checkout","k":"Architecture Decision Records","t":"Context","x":"MMCA.Store sells product variants, and a variant's price is one Money on the variant row…"},"1026":{"u":"/docs/adr/112-catalog-owned-effective-pricing.html#decision","d":"ADR-112: Catalog Owns Effective Pricing; Sales Snapshots It at Checkout","k":"Architecture Decision Records","t":"Decision","x":"Promotional pricing is a variant-level owned value object, not an aggregate. VariantDiscount…"},"1027":{"u":"/docs/adr/112-catalog-owned-effective-pricing.html#rationale","d":"ADR-112: Catalog Owns Effective Pricing; Sales Snapshots It at Checkout","k":"Architecture Decision Records","t":"Rationale","x":"A discount that belongs to exactly one variant, is replaced rather than edited, and has no life of its own once the variant is gone is a value object by every test we apply…"},"1028":{"u":"/docs/adr/112-catalog-owned-effective-pricing.html#trade-offs","d":"ADR-112: Catalog Owns Effective Pricing; Sales Snapshots It at Checkout","k":"Architecture Decision Records","t":"Trade-offs","x":"- Cached reads lag a window boundary by up to 60 seconds. The three write commands implement ICacheInvalidating and their controllers evict the catalog:products output-cache tag,…"},"1029":{"u":"/docs/adr/112-catalog-owned-effective-pricing.html#revision-2026-10-01","d":"ADR-112: Catalog Owns Effective Pricing; Sales Snapshots It at Checkout","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Two statements no longer matched the code and are corrected in place; the decision itself is unchanged. The products output-cache policy now expires after 60 seconds rather than…"},"1030":{"u":"/docs/adr/112-catalog-owned-effective-pricing.html#related","d":"ADR-112: Catalog Owns Effective Pricing; Sales Snapshots It at Checkout","k":"Architecture Decision Records","t":"Related","x":"- ADR-005, ADR-006: each service owns its database, and cross-service copies are denormalized rather than joined. - ADR-007: [ServiceContract] purity, the rule that keeps the…"},"1031":{"u":"/docs/adr/113-postgresql-as-a-first-class-engine.html","d":"ADR-113: PostgreSQL as a First-Class Engine","k":"Architecture Decision Records"},"1032":{"u":"/docs/adr/113-postgresql-as-a-first-class-engine.html#status","d":"ADR-113: PostgreSQL as a First-Class Engine","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-09). Extends ADR-006 (one sealed context class per engine) and ADR-018 (engine as a routing decision) with a fourth engine. Revised 2026-10-01 (the Helpdesk…"},"1033":{"u":"/docs/adr/113-postgresql-as-a-first-class-engine.html#context","d":"ADR-113: PostgreSQL as a First-Class Engine","k":"Architecture Decision Records","t":"Context","x":"The framework has shipped three database engines since its first release: SQL Server, Azure Cosmos DB and SQLite. Every one of them is reached the same way, because ADR-006 fixed…"},"1034":{"u":"/docs/adr/113-postgresql-as-a-first-class-engine.html#decision","d":"ADR-113: PostgreSQL as a First-Class Engine","k":"Architecture Decision Records","t":"Decision","x":"PostgreSQL is added as a fourth first-class engine, mirroring the SQL Server footprint one for one, and differing from it only where the server forces a difference. 1. The engine…"},"1035":{"u":"/docs/adr/113-postgresql-as-a-first-class-engine.html#rationale","d":"ADR-113: PostgreSQL as a First-Class Engine","k":"Architecture Decision Records","t":"Rationale","x":"The shape follows from the alternatives that were weighed and declined: - A snakecase naming convention by default. Rejected. It is the PostgreSQL community norm, but it would…"},"1036":{"u":"/docs/adr/113-postgresql-as-a-first-class-engine.html#trade-offs","d":"ADR-113: PostgreSQL as a First-Class Engine","k":"Architecture Decision Records","t":"Trade-offs","x":"- Every consumer's package graph gains Npgsql.EntityFrameworkCore.PostgreSQL. It is referenced unconditionally from MMCA.Common.Infrastructure, exactly as the Cosmos, SQLite and…"},"1037":{"u":"/docs/adr/113-postgresql-as-a-first-class-engine.html#revision-2026-10-01","d":"ADR-113: PostgreSQL as a First-Class Engine","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The two rollout follow-ups are no longer plans. The Helpdesk canary ships as the advisory postgresql-canary job (MMCA.Helpdesk/.github/workflows/ci.yml:146-155,…"},"1038":{"u":"/docs/adr/113-postgresql-as-a-first-class-engine.html#related","d":"ADR-113: PostgreSQL as a First-Class Engine","k":"Architecture Decision Records","t":"Related","x":"ADR-006 (one sealed context per engine, one instance per database), ADR-018 (the engine axis this record adds a member to), ADR-035 (the application-managed RowVersion token…"},"1039":{"u":"/docs/adr/114-internal-commands-durable-job-queue.html","d":"ADR-114: Internal Commands, a Job Queue Built on the Outbox's Machinery","k":"Architecture Decision Records"},"1040":{"u":"/docs/adr/114-internal-commands-durable-job-queue.html#status","d":"ADR-114: Internal Commands, a Job Queue Built on the Outbox's Machinery","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-09). Rides the outbox machinery of ADR-003 without extending it. Revised 2026-09-19 (three corrections to match the code: a schedule outside a transaction…"},"1041":{"u":"/docs/adr/114-internal-commands-durable-job-queue.html#context","d":"ADR-114: Internal Commands, a Job Queue Built on the Outbox's Machinery","k":"Architecture Decision Records","t":"Context","x":"The framework has had two ways to move work off the request thread and neither of them is a job queue. The transactional outbox (ADR-003) carries events. OutboxMessage…"},"1042":{"u":"/docs/adr/114-internal-commands-durable-job-queue.html#decision","d":"ADR-114: Internal Commands, a Job Queue Built on the Outbox's Machinery","k":"Architecture Decision Records","t":"Decision","x":"Deferred work is an ordinary CQRS command, scheduled into a per-source InternalCommands table and executed later through the normal decorator pipeline. Five parts carry that. 1.…"},"1043":{"u":"/docs/adr/114-internal-commands-durable-job-queue.html#rationale","d":"ADR-114: Internal Commands, a Job Queue Built on the Outbox's Machinery","k":"Architecture Decision Records","t":"Rationale","x":"Four alternatives were weighed, and each one gave up the atomicity guarantee or duplicated a vocabulary the framework already owns: Hangfire. It is the obvious answer and it…"},"1044":{"u":"/docs/adr/114-internal-commands-durable-job-queue.html#trade-offs","d":"ADR-114: Internal Commands, a Job Queue Built on the Outbox's Machinery","k":"Architecture Decision Records","t":"Trade-offs","x":"Consumers take one migration per relational data source. The table is in the model of every relational source (SQL Server, PostgreSQL and SQLite after ADR-113), so a consumer…"},"1045":{"u":"/docs/adr/114-internal-commands-durable-job-queue.html#revision-2026-10-01","d":"ADR-114: Internal Commands, a Job Queue Built on the Outbox's Machinery","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Two statements no longer matched the code. First, a schedule made outside a transaction now saves…"},"1046":{"u":"/docs/adr/114-internal-commands-durable-job-queue.html#related","d":"ADR-114: Internal Commands, a Job Queue Built on the Outbox's Machinery","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the claim-lease, backoff and dead-letter machinery this record borrows as an idiom), ADR-014 (the decorator chain a deferred command runs through unchanged), ADR-074…"},"1047":{"u":"/docs/adr/115-strongly-typed-identifiers-opt-in.html","d":"ADR-115: Strongly Typed Identifiers as an Opt-In Capability, Aliases Still the Default","k":"Architecture Decision Records"},"1048":{"u":"/docs/adr/115-strongly-typed-identifiers-opt-in.html#status","d":"ADR-115: Strongly Typed Identifiers as an Opt-In Capability, Aliases Still the Default","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-09). Revised 2026-09-19 (the alias count and the migration-surface census this record quotes were re-measured, the CheckIn parameter run was corrected from…"},"1049":{"u":"/docs/adr/115-strongly-typed-identifiers-opt-in.html#context","d":"ADR-115: Strongly Typed Identifiers as an Opt-In Capability, Aliases Still the Default","k":"Architecture Decision Records","t":"Context","x":"ADR-048 chose primitive identifier aliases over wrapper structs. ADR-085 re-opened that choice on 2026-08-18, priced the migration in numbers (44 aliases, 43 of them int, 3,192…"},"1050":{"u":"/docs/adr/115-strongly-typed-identifiers-opt-in.html#decision","d":"ADR-115: Strongly Typed Identifiers as an Opt-In Capability, Aliases Still the Default","k":"Architecture Decision Records","t":"Decision","x":"The framework ships strongly typed identifiers as an opt-in capability. The primitive aliases remain the default identifier model, no existing entity, consumer or test migrates,…"},"1051":{"u":"/docs/adr/115-strongly-typed-identifiers-opt-in.html#rationale","d":"ADR-115: Strongly Typed Identifiers as an Opt-In Capability, Aliases Still the Default","k":"Architecture Decision Records","t":"Rationale","x":"- The gap was the plumbing, not the type. A readonly record struct wrapping an int is four lines any team can write. What stops teams is the six boundaries around it: EF mapping…"},"1052":{"u":"/docs/adr/115-strongly-typed-identifiers-opt-in.html#trade-offs","d":"ADR-115: Strongly Typed Identifiers as an Opt-In Capability, Aliases Still the Default","k":"Architecture Decision Records","t":"Trade-offs","x":"- Two identifier styles can now coexist in one codebase, and nothing prevents that. A repo can wrap some identifiers and leave others primitive, and the reader cannot tell from…"},"1053":{"u":"/docs/adr/115-strongly-typed-identifiers-opt-in.html#related","d":"ADR-115: Strongly Typed Identifiers as an Opt-In Capability, Aliases Still the Default","k":"Architecture Decision Records","t":"Related","x":"ADR-048 (the primitive alias decision this record leaves in force), ADR-085 (the priced deferral and its three revisit triggers, none of which is the trigger here), ADR-104 (the…"},"1054":{"u":"/docs/adr/115-strongly-typed-identifiers-opt-in.html#revision-2026-09-19","d":"ADR-115: Strongly Typed Identifiers as an Opt-In Capability, Aliases Still the Default","k":"Architecture Decision Records","t":"Revision (2026-09-19)","x":"The decision is unchanged: the framework still ships the capability, the aliases are still the default, and adoption is still zero. Four facts this record borrowed were…"},"1055":{"u":"/docs/adr/115-strongly-typed-identifiers-opt-in.html#revision-2026-10-01","d":"ADR-115: Strongly Typed Identifiers as an Opt-In Capability, Aliases Still the Default","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"The decision is unchanged: the framework still ships the capability, the aliases are still the default, and adoption is still zero. Three statements were corrected against source…"},"1056":{"u":"/docs/adr/116-identity-completions-opt-in.html","d":"ADR-116: Identity Completions Ship as Opt-In Base Classes, Not as Framework Features","k":"Architecture Decision Records"},"1057":{"u":"/docs/adr/116-identity-completions-opt-in.html#status","d":"ADR-116: Identity Completions Ship as Opt-In Base Classes, Not as Framework Features","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-09; revised 2026-09-19: item 10 now scopes the \"no @page directive\" claim to what this decision ships, because MMCA.Common.UI itself ships 13 of them in the…"},"1058":{"u":"/docs/adr/116-identity-completions-opt-in.html#context","d":"ADR-116: Identity Completions Ship as Opt-In Base Classes, Not as Framework Features","k":"Architecture Decision Records","t":"Context","x":"The framework already owns the hard, uniform parts of identity. AuthenticationServiceBase carries login, registration, refresh-token rotation with reuse detection and revocation…"},"1059":{"u":"/docs/adr/116-identity-completions-opt-in.html#decision","d":"ADR-116: Identity Completions Ship as Opt-In Base Classes, Not as Framework Features","k":"Architecture Decision Records","t":"Decision","x":"The four identity completions ship as opt-in extension points: contracts and abstract bases in Application, implementations in Infrastructure behind their own Add calls, and…"},"1060":{"u":"/docs/adr/116-identity-completions-opt-in.html#rationale","d":"ADR-116: Identity Completions Ship as Opt-In Base Classes, Not as Framework Features","k":"Architecture Decision Records","t":"Rationale","x":"Six shapes were considered for these four capabilities, and each rejection is what produced the opt-in posture above: - Ship a framework User entity with two-factor and…"},"1061":{"u":"/docs/adr/116-identity-completions-opt-in.html#trade-offs","d":"ADR-116: Identity Completions Ship as Opt-In Base Classes, Not as Framework Features","k":"Architecture Decision Records","t":"Trade-offs","x":"- A consumer that adopts nothing sees no behaviour change and no new configuration. The only edit that reaches an unadopted consumer is the widened AuthenticationServiceBase…"},"1062":{"u":"/docs/adr/116-identity-completions-opt-in.html#revision-2026-10-01","d":"ADR-116: Identity Completions Ship as Opt-In Base Classes, Not as Framework Features","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Item 10 said no confirmation page ships in MMCA.Common.UI. One now does: ConfirmEmail.razor carries @page \"/confirm-email\" and [AllowAnonymous]…"},"1063":{"u":"/docs/adr/116-identity-completions-opt-in.html#related","d":"ADR-116: Identity Completions Ship as Opt-In Base Classes, Not as Framework Features","k":"Architecture Decision Records","t":"Related","x":"ADR-020 (the compiled role-to-permission registry stored grants layer over), ADR-091 (the token-service design email confirmation mirrors), ADR-032 (the narrow-contract precedent…"},"1064":{"u":"/docs/adr/117-apphost-integration-test-base.html","d":"ADR-117: AppHost Integration Testing as a Shipped Package","k":"Architecture Decision Records"},"1065":{"u":"/docs/adr/117-apphost-integration-test-base.html#status","d":"ADR-117: AppHost Integration Testing as a Shipped Package","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-09). Adds the AppHost test tier ADR-098 left out, as a package rather than a per-repo copy. Revised 2026-09-11: both consumer tiers now subclass the package…"},"1066":{"u":"/docs/adr/117-apphost-integration-test-base.html#context","d":"ADR-117: AppHost Integration Testing as a Shipped Package","k":"Architecture Decision Records","t":"Context","x":"The AppHost is the only file that states how a whole stack fits together: which project resources exist, which database each one owns, which broker they share, where JWKS…"},"1067":{"u":"/docs/adr/117-apphost-integration-test-base.html#decision","d":"ADR-117: AppHost Integration Testing as a Shipped Package","k":"Architecture Decision Records","t":"Decision","x":"Ship the AppHost test tier as a package, MMCA.Common.Testing.Aspire, so a consumer's smoke tier is a subclass and a set of assertions rather than a copied fixture. Make the…"},"1068":{"u":"/docs/adr/117-apphost-integration-test-base.html#rationale","d":"ADR-117: AppHost Integration Testing as a Shipped Package","k":"Architecture Decision Records","t":"Rationale","x":"Six shapes were weighed, and each rejection is a property the package keeps: - Leave it as a per-repo copy. Rejected: that is what produced roughly a hundred lines of budget and…"},"1069":{"u":"/docs/adr/117-apphost-integration-test-base.html#trade-offs","d":"ADR-117: AppHost Integration Testing as a Shipped Package","k":"Architecture Decision Records","t":"Trade-offs","x":"- A consumer's smoke tier is a subclass. MMCA.ADC.AppHost.SmokeTests is two files. The fixture is AdcAppHostFixture : AppHostFixtureBase…"},"1070":{"u":"/docs/adr/117-apphost-integration-test-base.html#revision-2026-10-01","d":"ADR-117: AppHost Integration Testing as a Shipped Package","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Decision 9 is corrected: it showed Assert.SkipWhen(!Fixture.IsAvailable, Fixture.SkipReason!) as the consumer's skip, a form that throws…"},"1071":{"u":"/docs/adr/117-apphost-integration-test-base.html#related","d":"ADR-117: AppHost Integration Testing as a Shipped Package","k":"Architecture Decision Records","t":"Related","x":"ADR-098 (the orchestration posture this tier tests without changing), ADR-058 (the shipped-test-tier-as-a-package pattern this record follows), ADR-016 (the lockstep release this…"},"1072":{"u":"/docs/adr/118-message-transport-library-policy.html","d":"ADR-118: Message Transport Library Policy (MassTransit v8 Pin and Exit Strategy)","k":"Architecture Decision Records"},"1073":{"u":"/docs/adr/118-message-transport-library-policy.html#status","d":"ADR-118: Message Transport Library Policy (MassTransit v8 Pin and Exit Strategy)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-11). Promotes the MassTransit clause inside ADR-016 into a decision of its own: ADR-016 owns the release policy that the pin is enforced under, this record owns…"},"1074":{"u":"/docs/adr/118-message-transport-library-policy.html#context","d":"ADR-118: Message Transport Library Policy (MassTransit v8 Pin and Exit Strategy)","k":"Architecture Decision Records","t":"Context","x":"MassTransit is the only message-broker library in this workspace, and it is pinned to 8.5.11 across all three of its packages (MMCA.Common/Directory.Packages.props:128-130, the…"},"1075":{"u":"/docs/adr/118-message-transport-library-policy.html#decision","d":"ADR-118: Message Transport Library Policy (MassTransit v8 Pin and Exit Strategy)","k":"Architecture Decision Records","t":"Decision","x":"Stay on MassTransit v8 behind IMessageBus, and keep the outbox and inbox custom precisely because that is what keeps a transport swap cheap. Record the exit triggers now, and the…"},"1076":{"u":"/docs/adr/118-message-transport-library-policy.html#rationale","d":"ADR-118: Message Transport Library Policy (MassTransit v8 Pin and Exit Strategy)","k":"Architecture Decision Records","t":"Rationale","x":"- The pin is enforceable, so it is honest. A comment beside a version number is a hope; a fitness function that parses Directory.Packages.props is the reason the v9 crash was…"},"1077":{"u":"/docs/adr/118-message-transport-library-policy.html#trade-offs","d":"ADR-118: Message Transport Library Policy (MassTransit v8 Pin and Exit Strategy)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The workspace sits on a library version with a finite support horizon. Nothing forces a move today, and the exit triggers above are what convert \"we should look at this…"},"1078":{"u":"/docs/adr/118-message-transport-library-policy.html#revision-2026-10-01","d":"ADR-118: Message Transport Library Policy (MassTransit v8 Pin and Exit Strategy)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision, trigger, replacement order or rationale changed. Facts restated from source: all five pinned MassTransit entries are at the v8 patch 8.5.11…"},"1079":{"u":"/docs/adr/118-message-transport-library-policy.html#related","d":"ADR-118: Message Transport Library Policy (MassTransit v8 Pin and Exit Strategy)","k":"Architecture Decision Records","t":"Related","x":"ADR-016 (the lockstep release policy this pin is enforced under; its 2026-08-28 amendment first sketched exit options, and this record takes ownership of that list. ADR-016's…"},"1080":{"u":"/docs/adr/119-restrict-delete-by-default.html","d":"ADR-119: Delete Behavior Is an Explicit Business Rule (Restrict by Default)","k":"Architecture Decision Records"},"1081":{"u":"/docs/adr/119-restrict-delete-by-default.html#status","d":"ADR-119: Delete Behavior Is an Explicit Business Rule (Restrict by Default)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-11). Inverts EF Core's cascading default for every relationship in every repo built on this framework, and stamps each foreign key with where its delete…"},"1082":{"u":"/docs/adr/119-restrict-delete-by-default.html#context","d":"ADR-119: Delete Behavior Is an Explicit Business Rule (Restrict by Default)","k":"Architecture Decision Records","t":"Context","x":"EF Core picks a delete behavior for you. A required relationship gets Cascade, an optional one gets ClientSetNull, and neither choice appears anywhere a reviewer reads. The…"},"1083":{"u":"/docs/adr/119-restrict-delete-by-default.html#decision","d":"ADR-119: Delete Behavior Is an Explicit Business Rule (Restrict by Default)","k":"Architecture Decision Records","t":"Decision","x":"Restrict is the default delete behavior for every relationship nobody configured, a cascade is an opt-in with a stated reason, and the finished model records which of the two…"},"1084":{"u":"/docs/adr/119-restrict-delete-by-default.html#rationale","d":"ADR-119: Delete Behavior Is an Explicit Business Rule (Restrict by Default)","k":"Architecture Decision Records","t":"Rationale","x":"- The safe default is the one that fails loudly. Restrict turns an unintended parent delete into an error at the point of the delete, where it can be read and fixed. Cascade…"},"1085":{"u":"/docs/adr/119-restrict-delete-by-default.html#trade-offs","d":"ADR-119: Delete Behavior Is an Explicit Business Rule (Restrict by Default)","k":"Architecture Decision Records","t":"Trade-offs","x":"- One migration per service database, authored and merged on 2026-09-11. The FK constraints change, so each database needed a migration: four in ADC (Conference, Identity,…"},"1086":{"u":"/docs/adr/119-restrict-delete-by-default.html#revision-2026-10-01","d":"ADR-119: Delete Behavior Is an Explicit Business Rule (Restrict by Default)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. The Context list of genuine hard deletes is completed: it now also names the framework's outbox cleanup…"},"1087":{"u":"/docs/adr/119-restrict-delete-by-default.html#related","d":"ADR-119: Delete Behavior Is an Explicit Business Rule (Restrict by Default)","k":"Architecture Decision Records","t":"Related","x":"ADR-006 (one context class per engine over one abstract base, which is what makes this a single registration), ADR-005 (the soft-delete posture that keeps cascades dormant at…"},"1088":{"u":"/docs/adr/120-governed-chat-client-boundary.html","d":"ADR-120: Governed Language-Model Boundary (MMCA.Common.AI)","k":"Architecture Decision Records"},"1089":{"u":"/docs/adr/120-governed-chat-client-boundary.html#status","d":"ADR-120: Governed Language-Model Boundary (MMCA.Common.AI)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-11). Extends ADR-111, which keeps every scoring-specific rule it states: this record moves the parts that are not about session scoring into the framework, as…"},"1090":{"u":"/docs/adr/120-governed-chat-client-boundary.html#context","d":"ADR-120: Governed Language-Model Boundary (MMCA.Common.AI)","k":"Architecture Decision Records","t":"Context","x":"Rubric section 16, AI-Native Application Architecture, asks one question of a product feature that calls a language model: is that dependency governed like any other external…"},"1091":{"u":"/docs/adr/120-governed-chat-client-boundary.html#decision","d":"ADR-120: Governed Language-Model Boundary (MMCA.Common.AI)","k":"Architecture Decision Records","t":"Decision","x":"The framework owns the language-model boundary as one optional package, MMCA.Common.AI, that names no vendor. A provider arrives as an adapter package contributing one factory,…"},"1092":{"u":"/docs/adr/120-governed-chat-client-boundary.html#rationale","d":"ADR-120: Governed Language-Model Boundary (MMCA.Common.AI)","k":"Architecture Decision Records","t":"Rationale","x":"- These rules belong to the call, not to the feature. An output ceiling, a timeout, a tool gate and a token counter are true of every model call anybody will ever write here.…"},"1093":{"u":"/docs/adr/120-governed-chat-client-boundary.html#trade-offs","d":"ADR-120: Governed Language-Model Boundary (MMCA.Common.AI)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Off by absence puts a null on the consumer. Because nothing is registered when Ai:Enabled is false, a feature resolves the client with GetService and holds a nullable…"},"1094":{"u":"/docs/adr/120-governed-chat-client-boundary.html#consequences","d":"ADR-120: Governed Language-Model Boundary (MMCA.Common.AI)","k":"Architecture Decision Records","t":"Consequences","x":"- ADC's SessionScoringService runs on IChatClient (SessionScoringService.cs:46-49). The port IAiScoringService stays, PromptVersion stays and keeps being persisted with every…"},"1095":{"u":"/docs/adr/120-governed-chat-client-boundary.html#revision-2026-10-01","d":"ADR-120: Governed Language-Model Boundary (MMCA.Common.AI)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed; the current-state sections now match the code. ADC adopted the package fully: the host calls AddAnthropicAiProvider()…"},"1096":{"u":"/docs/adr/120-governed-chat-client-boundary.html#related","d":"ADR-120: Governed Language-Model Boundary (MMCA.Common.AI)","k":"Architecture Decision Records","t":"Related","x":"ADR-111 (the record this one extends: every scoring-specific rule, the prompt-change protocol, the golden and live evaluation tiers, the input and output guardrails and the…"},"1097":{"u":"/docs/adr/121-ephemeral-in-process-work-queue.html","d":"ADR-121: Ephemeral In-Process Work Queue (Bounded Channel plus Hosted Drain)","k":"Architecture Decision Records"},"1098":{"u":"/docs/adr/121-ephemeral-in-process-work-queue.html#status","d":"ADR-121: Ephemeral In-Process Work Queue (Bounded Channel plus Hosted Drain)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-11). Supersedes ADR-052."},"1099":{"u":"/docs/adr/121-ephemeral-in-process-work-queue.html#context","d":"ADR-121: Ephemeral In-Process Work Queue (Bounded Channel plus Hosted Drain)","k":"Architecture Decision Records","t":"Context","x":"ADR-052 ran two different kinds of work through one mechanism: ephemeral broadcasts that must not put a gRPC round trip on the hot path of a vote, and an AI scoring pass that…"},"1100":{"u":"/docs/adr/121-ephemeral-in-process-work-queue.html#decision","d":"ADR-121: Ephemeral In-Process Work Queue (Bounded Channel plus Hosted Drain)","k":"Architecture Decision Records","t":"Decision","x":"Ephemeral in-process work runs as a bounded channel plus a single-reader hosted drain. Nothing starts an untracked Task from a request, and nothing that must run lands here. - A…"},"1101":{"u":"/docs/adr/121-ephemeral-in-process-work-queue.html#rationale","d":"ADR-121: Ephemeral In-Process Work Queue (Bounded Channel plus Hosted Drain)","k":"Architecture Decision Records","t":"Rationale","x":"- The host lifetime is the point. A BackgroundService is the only in-process shape the host can cancel and await. Everything else in the decision follows from wanting that, and a…"},"1102":{"u":"/docs/adr/121-ephemeral-in-process-work-queue.html#trade-offs","d":"ADR-121: Ephemeral In-Process Work Queue (Bounded Channel plus Hosted Drain)","k":"Architecture Decision Records","t":"Trade-offs","x":"- In-process only. The queue does not survive a restart and does not span replicas. Accepted because every job left here is ephemeral by definition. Work that must survive a…"},"1103":{"u":"/docs/adr/121-ephemeral-in-process-work-queue.html#revision-2026-10-01","d":"ADR-121: Ephemeral In-Process Work Queue (Bounded Channel plus Hosted Drain)","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Two Context and Related statements are corrected: the scoring trigger no longer answers 202 Accepted unconditionally, because a…"},"1104":{"u":"/docs/adr/121-ephemeral-in-process-work-queue.html#related","d":"ADR-121: Ephemeral In-Process Work Queue (Bounded Channel plus Hosted Drain)","k":"Architecture Decision Records","t":"Related","x":"ADR-114 (the durable, leased, retrying queue that owns expensive and must-run work), ADR-108 (the cross-replica claim that replaced this record's per-replica dedup), ADR-039…"},"1105":{"u":"/docs/adr/122-dev-only-relaxations-fail-closed.html","d":"ADR-122: Dev-Only Relaxations Are Environment-Gated and Fail Closed","k":"Architecture Decision Records"},"1106":{"u":"/docs/adr/122-dev-only-relaxations-fail-closed.html#status","d":"ADR-122: Dev-Only Relaxations Are Environment-Gated and Fail Closed","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-11)."},"1107":{"u":"/docs/adr/122-dev-only-relaxations-fail-closed.html#context","d":"ADR-122: Dev-Only Relaxations Are Environment-Gated and Fail Closed","k":"Architecture Decision Records","t":"Context","x":"Several capabilities are useful locally and dangerous in a deployed environment: EF Core rendering parameter values into logs and exception messages, an SMTP session that skips…"},"1108":{"u":"/docs/adr/122-dev-only-relaxations-fail-closed.html#decision","d":"ADR-122: Dev-Only Relaxations Are Environment-Gated and Fail Closed","k":"Architecture Decision Records","t":"Decision","x":"A relaxation applies only when the environment is positively identified as Development. An unknown, absent, or unparseable environment yields the production posture. Three…"},"1109":{"u":"/docs/adr/122-dev-only-relaxations-fail-closed.html#rationale","d":"ADR-122: Dev-Only Relaxations Are Environment-Gated and Fail Closed","k":"Architecture Decision Records","t":"Rationale","x":"- Unknown is not safe. The failure this prevents is not a developer flipping a flag on purpose, it is a flag arriving somewhere nobody looked. Treating an absent environment as…"},"1110":{"u":"/docs/adr/122-dev-only-relaxations-fail-closed.html#trade-offs","d":"ADR-122: Dev-Only Relaxations Are Environment-Gated and Fail Closed","k":"Architecture Decision Records","t":"Trade-offs","x":"- ASPNETCOREENVIRONMENT is the trust root. A host that misreports itself as Development gets every relaxation at once. The rule concentrates the risk on one value instead of…"},"1111":{"u":"/docs/adr/122-dev-only-relaxations-fail-closed.html#revision-2026-10-01","d":"ADR-122: Dev-Only Relaxations Are Environment-Gated and Fail Closed","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Citations were re-anchored to the current source: the AI gate (DependencyInjection.cs:262-266, remarks :257-260), the RequireHttpsMetadata…"},"1112":{"u":"/docs/adr/122-dev-only-relaxations-fail-closed.html#related","d":"ADR-122: Dev-Only Relaxations Are Environment-Gated and Fail Closed","k":"Architecture Decision Records","t":"Related","x":"ADR-070 (binds and validates settings at startup; this record governs when a validated setting may be honored), ADR-023 (the HSTS and CSP posture relaxed here), ADR-082 (the two…"},"1113":{"u":"/docs/adr/123-speaker-session-assets.html","d":"ADR-123: Speaker-Published Session Materials","k":"Architecture Decision Records"},"1114":{"u":"/docs/adr/123-speaker-session-assets.html#status","d":"ADR-123: Speaker-Published Session Materials","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-12). Extends ADR-045 from images to documents: the framework gains a document content sniffer, a blob-name sanitizer and stored response headers, and ADC gains…"},"1115":{"u":"/docs/adr/123-speaker-session-assets.html#context","d":"ADR-123: Speaker-Published Session Materials","k":"Architecture Decision Records","t":"Context","x":"A speaker finishes a talk and forty people want the deck. Until now ADC's only answer was Session.ResourceLinks, a free-text field imported from Sessionize: the speaker types…"},"1116":{"u":"/docs/adr/123-speaker-session-assets.html#decision","d":"ADR-123: Speaker-Published Session Materials","k":"Architecture Decision Records","t":"Decision","x":"Session materials are a separate Conference aggregate, written by the session's own speakers or by an organizer, stored in a public-read container under an unguessable blob name,…"},"1117":{"u":"/docs/adr/123-speaker-session-assets.html#rationale","d":"ADR-123: Speaker-Published Session Materials","k":"Architecture Decision Records","t":"Rationale","x":"Four alternatives were weighed. A child collection on Session. The obvious DDD answer, and wrong for this entity. Session is imported from Sessionize and re-imported on every…"},"1118":{"u":"/docs/adr/123-speaker-session-assets.html#trade-offs","d":"ADR-123: Speaker-Published Session Materials","k":"Architecture Decision Records","t":"Trade-offs","x":"- The container is public-read, so a URL is a credential. Anyone holding a blob URL can fetch it, forever, with no session and no referrer check. That is accepted for the same…"},"1119":{"u":"/docs/adr/123-speaker-session-assets.html#revision-2026-10-01","d":"ADR-123: Speaker-Published Session Materials","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"Two parts of the decision changed and the rest of the record was re-anchored. The options-carrying IFileStorageService.UploadAsync overload is no longer a default interface…"},"1120":{"u":"/docs/adr/123-speaker-session-assets.html#related","d":"ADR-123: Speaker-Published Session Materials","k":"Architecture Decision Records","t":"Related","x":"ADR-045 (the storage abstraction, the image path this record is the document sibling of, and the avatar-only scope statement this supersedes), ADR-114 (the durable queue the blob…"},"1121":{"u":"/docs/adr/124-blazor-circuit-ceiling-ui-edge.html","d":"ADR-124: The Server-Rendered UI Host Is Its Own Hardened Edge","k":"Architecture Decision Records"},"1122":{"u":"/docs/adr/124-blazor-circuit-ceiling-ui-edge.html#status","d":"ADR-124: The Server-Rendered UI Host Is Its Own Hardened Edge","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-19). Revised 2026-09-25 (the hardening kit is one framework copy in MMCA.Common.UI.Web/Hardening/, first shipped in v1.206.0, which both UI hosts consume; the…"},"1123":{"u":"/docs/adr/124-blazor-circuit-ceiling-ui-edge.html#context","d":"ADR-124: The Server-Rendered UI Host Is Its Own Hardened Edge","k":"Architecture Decision Records","t":"Context","x":"ADR-019 layers rate limiting, and ADR-088 makes the Gateway the layer that carries the outermost one. Both records describe traffic that arrives through the Gateway. The…"},"1124":{"u":"/docs/adr/124-blazor-circuit-ceiling-ui-edge.html#decision","d":"ADR-124: The Server-Rendered UI Host Is Its Own Hardened Edge","k":"Architecture Decision Records","t":"Decision","x":"The server-rendered UI host defends itself, in three layers bound from two configuration sections the host owns, in both ADC and Store, using one framework kit. - A ceiling on…"},"1125":{"u":"/docs/adr/124-blazor-circuit-ceiling-ui-edge.html#rationale","d":"ADR-124: The Server-Rendered UI Host Is Its Own Hardened Edge","k":"Architecture Decision Records","t":"Rationale","x":"- A separate origin is a separate edge. The Gateway cannot defend a hostname it never receives a request for, so the choice was between routing the UI through the Gateway and…"},"1126":{"u":"/docs/adr/124-blazor-circuit-ceiling-ui-edge.html#trade-offs","d":"ADR-124: The Server-Rendered UI Host Is Its Own Hardened Edge","k":"Architecture Decision Records","t":"Trade-offs","x":"- Both ceilings are per replica, in memory. The effective allowance is the configured number multiplied by the replica count (two at most for ADC's UI today), so two replicas…"},"1127":{"u":"/docs/adr/124-blazor-circuit-ceiling-ui-edge.html#revision-2026-09-25-one-framework-kit-one-forwarded-header-posture","d":"ADR-124: The Server-Rendered UI Host Is Its Own Hardened Edge","k":"Architecture Decision Records","t":"Revision (2026-09-25): one framework kit, one forwarded-header posture","x":"The hardening kit this record decides now lives once, in MMCA.Common.UI.Web/Hardening/, first shipped in v1.206.0 (MMCA.Common/CHANGELOG.md:270-282). ADC and Store each import it…"},"1128":{"u":"/docs/adr/124-blazor-circuit-ceiling-ui-edge.html#revision-2026-10-01-the-circuit-transport-and-the-circuit-count","d":"ADR-124: The Server-Rendered UI Host Is Its Own Hardened Edge","k":"Architecture Decision Records","t":"Revision (2026-10-01): the circuit transport and the circuit count","x":"Two behaviours of the kit changed in v1.213.0 (MMCA.Common/CHANGELOG.md:82,145,148), and the Decision above now states both. First, the chained limiter no longer applies the…"},"1129":{"u":"/docs/adr/124-blazor-circuit-ceiling-ui-edge.html#related","d":"ADR-124: The Server-Rendered UI Host Is Its Own Hardened Edge","k":"Architecture Decision Records","t":"Related","x":"ADR-019 (the layered rate-limiting posture this adds a layer to), ADR-088 (the Gateway edge whose limiter this host is outside of, and which the registration comments name…"},"1130":{"u":"/docs/adr/125-parameterized-sql-only.html","d":"ADR-125: Parameterized SQL Only, Enforced by a Fitness Gate","k":"Architecture Decision Records"},"1131":{"u":"/docs/adr/125-parameterized-sql-only.html#status","d":"ADR-125: Parameterized SQL Only, Enforced by a Fitness Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-19)."},"1132":{"u":"/docs/adr/125-parameterized-sql-only.html#context","d":"ADR-125: Parameterized SQL Only, Enforced by a Fitness Gate","k":"Architecture Decision Records","t":"Context","x":"The supported way to read data is the repository plus specification contract (ADR-055), and LINQ answers almost everything. It does not answer a window function, a recursive CTE…"},"1133":{"u":"/docs/adr/125-parameterized-sql-only.html#decision","d":"ADR-125: Parameterized SQL Only, Enforced by a Fitness Gate","k":"Architecture Decision Records","t":"Decision","x":"Give raw SQL exactly one door whose signature makes the unsafe call uncompilable, and ban the four raw EF members in module code with a fitness test rather than a guideline. -…"},"1134":{"u":"/docs/adr/125-parameterized-sql-only.html#rationale","d":"ADR-125: Parameterized SQL Only, Enforced by a Fitness Gate","k":"Architecture Decision Records","t":"Rationale","x":"- A signature outranks a guideline. Both halves of this decision aim at the same thing from different directions: the contract removes the unsafe call from the type system, and…"},"1135":{"u":"/docs/adr/125-parameterized-sql-only.html#trade-offs","d":"ADR-125: Parameterized SQL Only, Enforced by a Fitness Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- FormattableString blocks the accident, not the intent. FormattableStringFactory.Create builds a FormattableString out of an already-concatenated string with no interpolation…"},"1136":{"u":"/docs/adr/125-parameterized-sql-only.html#revision-2026-10-01","d":"ADR-125: Parameterized SQL Only, Enforced by a Fitness Gate","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Citations refreshed: the CHANGELOG anchors (MMCA.Common/CHANGELOG.md:1038, :1108-1117, :1118-1121, :1153-1159), the executor registration…"},"1137":{"u":"/docs/adr/125-parameterized-sql-only.html#related","d":"ADR-125: Parameterized SQL Only, Enforced by a Fitness Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-055 (the repository plus specification path this escape hatch sits beside, and the reason the hatch stays narrow), ADR-015 (the fitness-function style this rule is written…"},"1138":{"u":"/docs/adr/126-event-sourcing-not-adopted.html","d":"ADR-126: Event Sourcing Is Not Adopted","k":"Architecture Decision Records"},"1139":{"u":"/docs/adr/126-event-sourcing-not-adopted.html#status","d":"ADR-126: Event Sourcing Is Not Adopted","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-22) as a documented rejection. Nothing ships with this record: no event store, no stream table, no projection host, no new package. What ships is the reason…"},"1140":{"u":"/docs/adr/126-event-sourcing-not-adopted.html#context","d":"ADR-126: Event Sourcing Is Not Adopted","k":"Architecture Decision Records","t":"Context","x":"Event sourcing keeps an append-only log of the facts that happened to an aggregate and treats that log as the system of record. Current state becomes a fold over the log, a…"},"1141":{"u":"/docs/adr/126-event-sourcing-not-adopted.html#decision","d":"ADR-126: Event Sourcing Is Not Adopted","k":"Architecture Decision Records","t":"Decision","x":"Do not adopt event sourcing. Aggregates stay current-state EF rows that raise domain events for notification, carry audit fields, and soft-delete. No repository gains an event…"},"1142":{"u":"/docs/adr/126-event-sourcing-not-adopted.html#rationale","d":"ADR-126: Event Sourcing Is Not Adopted","k":"Architecture Decision Records","t":"Rationale","x":"- No workload asks the question event sourcing answers. Neither ADC nor MMCA.Store has an aggregate that needs replayable per-aggregate history or a \"state as of\" query.…"},"1143":{"u":"/docs/adr/126-event-sourcing-not-adopted.html#trade-offs","d":"ADR-126: Event Sourcing Is Not Adopted","k":"Architecture Decision Records","t":"Trade-offs","x":"- A late adoption costs more than an early one. The module that eventually needs a stream converts an aggregate that already has production data, deployed migrations and reads…"},"1144":{"u":"/docs/adr/126-event-sourcing-not-adopted.html#alternatives-rejected","d":"ADR-126: Event Sourcing Is Not Adopted","k":"Architecture Decision Records","t":"Alternatives rejected","x":"- Marten on PostgreSQL, weighed 2026-09-22 and rejected. Marten is the credible .NET event store and the specific option this record weighed. It was dropped on two grounds rather…"},"1145":{"u":"/docs/adr/126-event-sourcing-not-adopted.html#when-to-revisit","d":"ADR-126: Event Sourcing Is Not Adopted","k":"Architecture Decision Records","t":"When to revisit","x":"Revisit when an aggregate acquires a business requirement for full replayable history or temporal queries: a financial ledger whose every movement must be reconstructible, or a…"},"1146":{"u":"/docs/adr/126-event-sourcing-not-adopted.html#revision-2026-10-01","d":"ADR-126: Event Sourcing Is Not Adopted","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Two citations were re-anchored: the soft-delete global query filter now points at ApplySoftDeleteFilters…"},"1147":{"u":"/docs/adr/126-event-sourcing-not-adopted.html#related","d":"ADR-126: Event Sourcing Is Not Adopted","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (at-least-once delivery of domain events, the mechanism a per-aggregate event log would duplicate rather than replace), ADR-021 (the consume-edge inbox that makes…"},"1148":{"u":"/docs/adr/127-actor-model-not-adopted.html","d":"ADR-127: The Actor Model Is Not Adopted","k":"Architecture Decision Records"},"1149":{"u":"/docs/adr/127-actor-model-not-adopted.html#status","d":"ADR-127: The Actor Model Is Not Adopted","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-09-22) as a documented rejection. Nothing ships with this record: no grain interface, no silo, no placement configuration, no new package. What ships is the reason…"},"1150":{"u":"/docs/adr/127-actor-model-not-adopted.html#context","d":"ADR-127: The Actor Model Is Not Adopted","k":"Architecture Decision Records","t":"Context","x":"The actor model gives every logical entity its own single-threaded unit of execution holding its state in memory and serializes all messages to that entity through it; a…"},"1151":{"u":"/docs/adr/127-actor-model-not-adopted.html#decision","d":"ADR-127: The Actor Model Is Not Adopted","k":"Architecture Decision Records","t":"Decision","x":"Do not adopt the actor model, and do not introduce an actor runtime as a hosting layer. Per-entity state stays in the owning module's database behind optimistic concurrency, read…"},"1152":{"u":"/docs/adr/127-actor-model-not-adopted.html#rationale","d":"ADR-127: The Actor Model Is Not Adopted","k":"Architecture Decision Records","t":"Rationale","x":"- No workload has the shape. Actors earn their cost on high-cardinality per-entity in-memory state with single-writer semantics, and nothing here has all three: entity counts are…"},"1153":{"u":"/docs/adr/127-actor-model-not-adopted.html#trade-offs","d":"ADR-127: The Actor Model Is Not Adopted","k":"Architecture Decision Records","t":"Trade-offs","x":"- A genuinely actor-shaped workload will arrive without groundwork, and the first adoption pays the learning cost under whatever deadline produced the requirement. - Some…"},"1154":{"u":"/docs/adr/127-actor-model-not-adopted.html#alternatives-rejected","d":"ADR-127: The Actor Model Is Not Adopted","k":"Architecture Decision Records","t":"Alternatives rejected","x":"- Microsoft Orleans hosted under Aspire, weighed 2026-09-22 and rejected. Orleans is the .NET virtual-actor runtime and the option this record weighed. Aspire already composes…"},"1155":{"u":"/docs/adr/127-actor-model-not-adopted.html#when-to-revisit","d":"ADR-127: The Actor Model Is Not Adopted","k":"Architecture Decision Records","t":"When to revisit","x":"Revisit when a real-time per-entity state workload appears that optimistic concurrency plus Redis cannot carry: an entity mutated by enough concurrent callers that the…"},"1156":{"u":"/docs/adr/127-actor-model-not-adopted.html#revision-2026-10-01","d":"ADR-127: The Actor Model Is Not Adopted","k":"Architecture Decision Records","t":"Revision (2026-10-01)","x":"No decision or rationale changed. Citations refreshed: the vote handler's class declaration (CastVoteHandler.cs:20) and its poll repository call (:33), OnModelCreating…"},"1157":{"u":"/docs/adr/127-actor-model-not-adopted.html#related","d":"ADR-127: The Actor Model Is Not Adopted","k":"Architecture Decision Records","t":"Related","x":"ADR-007, ADR-008 and ADR-059 (the boundary a future silo would live behind), ADR-026 and ADR-077 (the caching tiers that absorb the read pressure, and the in-process residency…"},"1158":{"u":"/docs/adr/128-time-as-an-input.html","d":"ADR-128: Time as an Input (No Ambient Clock Reads in Domain and Application)","k":"Architecture Decision Records"},"1159":{"u":"/docs/adr/128-time-as-an-input.html#status","d":"ADR-128: Time as an Input (No Ambient Clock Reads in Domain and Application)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-10-01)."},"1160":{"u":"/docs/adr/128-time-as-an-input.html#context","d":"ADR-128: Time as an Input (No Ambient Clock Reads in Domain and Application)","k":"Architecture Decision Records","t":"Context","x":"Expiry windows, payment deadlines, discount windows, overdue checks and cutoffs are business rules, and every one of them branches on \"now\". When the rule reads DateTime.UtcNow…"},"1161":{"u":"/docs/adr/128-time-as-an-input.html#decision","d":"ADR-128: Time as an Input (No Ambient Clock Reads in Domain and Application)","k":"Architecture Decision Records","t":"Decision","x":"Domain and Application code takes time as an input and never reads the ambient clock. A handler injects TimeProvider and passes the instant into the domain method; an aggregate…"},"1162":{"u":"/docs/adr/128-time-as-an-input.html#rationale","d":"ADR-128: Time as an Input (No Ambient Clock Reads in Domain and Application)","k":"Architecture Decision Records","t":"Rationale","x":"- A test can only drive what it can supply. Passing the instant in makes every time-dependent branch a plain input, so an expiry, a cutoff or an overdue state is one argument…"},"1163":{"u":"/docs/adr/128-time-as-an-input.html#trade-offs","d":"ADR-128: Time as an Input (No Ambient Clock Reads in Domain and Application)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The rule bans five getters, not the idea of a clock. A read through any other path (a static TimeProvider.System.GetUtcNow(), Environment.TickCount, Stopwatch) is not matched…"},"1164":{"u":"/docs/adr/128-time-as-an-input.html#related","d":"ADR-128: Time as an Input (No Ambient Clock Reads in Domain and Application)","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (the fitness-function style and the shared TestsBase package this rule is shipped in), ADR-125 (the precedent for a fitness-gated convention with its own record and an…"},"1165":{"u":"/docs/adr/129-tactical-aggregate-contract.html","d":"ADR-129: The Tactical Aggregate Contract","k":"Architecture Decision Records"},"1166":{"u":"/docs/adr/129-tactical-aggregate-contract.html#status","d":"ADR-129: The Tactical Aggregate Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-10-01)."},"1167":{"u":"/docs/adr/129-tactical-aggregate-contract.html#context","d":"ADR-129: The Tactical Aggregate Contract","k":"Architecture Decision Records","t":"Context","x":"Every entity in the four repos is written the same way: a sealed class over a framework base, a private constructor, a static Create(...) returning Result , private setters,…"},"1168":{"u":"/docs/adr/129-tactical-aggregate-contract.html#decision","d":"ADR-129: The Tactical Aggregate Contract","k":"Architecture Decision Records","t":"Decision","x":"Entities follow one tactical contract. The construction, encapsulation and placement parts are fitness-gated by EntityConventionTestsBase; the child-access part and the use of…"},"1169":{"u":"/docs/adr/129-tactical-aggregate-contract.html#rationale","d":"ADR-129: The Tactical Aggregate Contract","k":"Architecture Decision Records","t":"Rationale","x":"- One contract, one record. The pieces were each correct in their own ADR, but an agent writing a new entity needs the whole shape at once, and a reviewer needs to know which…"},"1170":{"u":"/docs/adr/129-tactical-aggregate-contract.html#trade-offs","d":"ADR-129: The Tactical Aggregate Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- Part of the contract is not enforced. Child access through GetChildOrNotFound, SetItems and the remove, restore and cascade-delete helpers, the existence of an Invariants class…"},"1171":{"u":"/docs/adr/129-tactical-aggregate-contract.html#related","d":"ADR-129: The Tactical Aggregate Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-013 (the Result factories this contract requires), ADR-068 (value-object factories and the static Invariants class this contract reuses for entities), ADR-126 (an aggregate…"},"1172":{"u":"/docs/adr/130-per-engine-data-source-strategy.html","d":"ADR-130: Per-Engine Data Source Strategy (Engine Facts Instead of Branching on the Enum)","k":"Architecture Decision Records"},"1173":{"u":"/docs/adr/130-per-engine-data-source-strategy.html#status","d":"ADR-130: Per-Engine Data Source Strategy (Engine Facts Instead of Branching on the Enum)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-10-01). Targeted at MMCA.Common v1.218.0, which is unreleased at the time of writing (the CHANGELOG carries no entry for it yet). Refines ADR-018 (engine as a…"},"1174":{"u":"/docs/adr/130-per-engine-data-source-strategy.html#context","d":"ADR-130: Per-Engine Data Source Strategy (Engine Facts Instead of Branching on the Enum)","k":"Architecture Decision Records","t":"Context","x":"MMCA.Common persists through four engines named by one shipped public enum, DataSource…"},"1175":{"u":"/docs/adr/130-per-engine-data-source-strategy.html#decision","d":"ADR-130: Per-Engine Data Source Strategy (Engine Facts Instead of Branching on the Enum)","k":"Architecture Decision Records","t":"Decision","x":"One internal strategy object per engine answers every engine question, looked up through a static registry keyed by the shipped enum. Call sites read engine facts; they no longer…"},"1176":{"u":"/docs/adr/130-per-engine-data-source-strategy.html#rationale","d":"ADR-130: Per-Engine Data Source Strategy (Engine Facts Instead of Branching on the Enum)","k":"Architecture Decision Records","t":"Rationale","x":"- One place per engine. An engine's behavior is now one class, and a fifth engine is one class plus one registry line (DataSourceEngines.cs:15-16); a missed branch can no longer…"},"1177":{"u":"/docs/adr/130-per-engine-data-source-strategy.html#trade-offs","d":"ADR-130: Per-Engine Data Source Strategy (Engine Facts Instead of Branching on the Enum)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Breaking for consumers. The RequestIdentityInsert rename has no alias (IUnitOfWork.cs:45); a Cosmos-default host that injects IRawSqlQueryExecutor now fails at startup…"},"1178":{"u":"/docs/adr/130-per-engine-data-source-strategy.html#alternatives-rejected","d":"ADR-130: Per-Engine Data Source Strategy (Engine Facts Instead of Branching on the Enum)","k":"Architecture Decision Records","t":"Alternatives rejected","x":"- Engines registered in DI (weighed 2026-10-01). Engines as IDataSourceEngine services resolved from the container. Rejected because the conventions, EntityTypeConfiguration and…"},"1179":{"u":"/docs/adr/130-per-engine-data-source-strategy.html#consequences","d":"ADR-130: Per-Engine Data Source Strategy (Engine Facts Instead of Branching on the Enum)","k":"Architecture Decision Records","t":"Consequences","x":"- Consumer upgrade. Rename RequestIdentityInsert calls to RequestExplicitKeyInsert; on a Cosmos-default host, remove any IRawSqlQueryExecutor dependency; on SQLite with…"},"1180":{"u":"/docs/adr/130-per-engine-data-source-strategy.html#related","d":"ADR-130: Per-Engine Data Source Strategy (Engine Facts Instead of Branching on the Enum)","k":"Architecture Decision Records","t":"Related","x":"ADR-006 (one sealed context per engine), ADR-018 (engines behind one model and the resolver), ADR-113 (the fourth engine), ADR-030 (the startup migrator that reads Migrations),…"},"1181":{"u":"/docs/adr/131-same-origin-api-proxy.html","d":"ADR-131: Same-Origin API Proxy (a Backend-for-Frontend That Keeps Tokens Out of Browser Script)","k":"Architecture Decision Records"},"1182":{"u":"/docs/adr/131-same-origin-api-proxy.html#status","d":"ADR-131: Same-Origin API Proxy (a Backend-for-Frontend That Keeps Tokens Out of Browser Script)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-10-01). Targeted at MMCA.Common v1.218.0, which is unreleased at the time of writing. Records the owner's TD-08 decision for Option A of the token-storage design…"},"1183":{"u":"/docs/adr/131-same-origin-api-proxy.html#context","d":"ADR-131: Same-Origin API Proxy (a Backend-for-Frontend That Keeps Tokens Out of Browser Script)","k":"Architecture Decision Records","t":"Context","x":"A Blazor WebAssembly client calls the API through the gateway, which is a different origin from the UI host, so the HttpOnly session cookies the UI host writes (ADR-022) never…"},"1184":{"u":"/docs/adr/131-same-origin-api-proxy.html#decision","d":"ADR-131: Same-Origin API Proxy (a Backend-for-Frontend That Keeps Tokens Out of Browser Script)","k":"Architecture Decision Records","t":"Decision","x":"The UI host serves the API on its own origin and forwards to the gateway server-side, attaching the bearer it reads from the HttpOnly cookie. The browser never holds a token that…"},"1185":{"u":"/docs/adr/131-same-origin-api-proxy.html#rationale","d":"ADR-131: Same-Origin API Proxy (a Backend-for-Frontend That Keeps Tokens Out of Browser Script)","k":"Architecture Decision Records","t":"Rationale","x":"- Tokens out of script, with no infrastructure prerequisite. An injected script can still call the API as the user while the page is open, but it cannot carry a token away: the…"},"1186":{"u":"/docs/adr/131-same-origin-api-proxy.html#trade-offs","d":"ADR-131: Same-Origin API Proxy (a Backend-for-Frontend That Keeps Tokens Out of Browser Script)","k":"Architecture Decision Records","t":"Trade-offs","x":"- One extra hop. Every browser API call and every hub frame passes through the UI host before the gateway (SameOriginApiProxyEndpoint.cs:315-319). - The UI host becomes a…"},"1187":{"u":"/docs/adr/131-same-origin-api-proxy.html#alternatives-rejected","d":"ADR-131: Same-Origin API Proxy (a Backend-for-Frontend That Keeps Tokens Out of Browser Script)","k":"Architecture Decision Records","t":"Alternatives rejected","x":"- Option B: a same-site cookie sent straight to the API (weighed 2026-10-01). Rejected because it needs UI and gateway under one registrable domain in every environment (DNS,…"},"1188":{"u":"/docs/adr/131-same-origin-api-proxy.html#consequences","d":"ADR-131: Same-Origin API Proxy (a Backend-for-Frontend That Keeps Tokens Out of Browser Script)","k":"Architecture Decision Records","t":"Consequences","x":"- Adoption, per Blazor Web host. Register after the session-cookie and token registrations, map next to the session-cookie endpoints after UseAuthorization, configure the section…"},"1189":{"u":"/docs/adr/131-same-origin-api-proxy.html#related","d":"ADR-131: Same-Origin API Proxy (a Backend-for-Frontend That Keeps Tokens Out of Browser Script)","k":"Architecture Decision Records","t":"Related","x":"ADR-022 (the HttpOnly session cookie the proxy reads), ADR-051 (client token lifecycle across render modes), ADR-023 (security headers and the CSP), ADR-082 (the gateway CORS…"},"1190":{"u":"/docs/onboarding/index.html","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","x":"A teaching guide for an experienced .NET engineer who is new to this codebase. It walks every first-party type, explaining not just what each type is but how it works and why it…"},"1191":{"u":"/docs/onboarding/index.html#how-the-guide-is-organized-two-axes","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","t":"How the guide is organized, two axes","x":"The guide has two organizing axes that work together. 1. Primary axis, functional grouping. Every type lives in exactly one functional group: the capability or cross-cutting…"},"1192":{"u":"/docs/onboarding/index.html#chapters","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","t":"Chapters","x":"---"},"1193":{"u":"/docs/onboarding/index.html#legend-how-to-read-a-type-section","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","t":"Legend, how to read a type section","x":"Every type gets one section using this template: {TypeName} {Assembly} · {namespace} · {file:line} · Level {n} · {kind} - What it is: one or two plain-language sentences. -…"},"1194":{"u":"/docs/onboarding/index.html#suggested-reading-paths","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","t":"Suggested reading paths","x":"- Framework-first (recommended). Primer → group-01 → upward. You meet the MMCA.Common foundations before the MMCA.ADC features that build on them; this matches dependency order…"},"1195":{"u":"/docs/onboarding/index.html#the-companion-projects-context","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","t":"The companion projects (context)","x":"This guide covers MMCA.Common (the framework) and MMCA.ADC (one consumer). MMCA.Store is out of scope. The dependency arrow is why the Common framework groups (1–16) come before…"},"1196":{"u":"/docs/onboarding/00-dependency-manifest.html","d":"Phase 1: Dependency Manifest & Leveling","k":"Onboarding Guide","x":"Each distinct type node is assigned a Level by longest-path layering over its first-party dependencies (base/interface, generic constraints, field/property/param/return types,…"},"1197":{"u":"/docs/onboarding/00-dependency-manifest.html#level-distribution","d":"Phase 1: Dependency Manifest & Leveling","k":"Onboarding Guide","t":"Level distribution"},"1198":{"u":"/docs/onboarding/00-dependency-manifest.html#cycles-scc-size--1-47","d":"Phase 1: Dependency Manifest & Leveling","k":"Onboarding Guide","t":"Cycles (SCC size > 1): 47"},"1199":{"u":"/docs/onboarding/00-dependency-manifest.html#manifest-by-level-then-assembly","d":"Phase 1: Dependency Manifest & Leveling","k":"Onboarding Guide","t":"Manifest (by level, then assembly)"},"1200":{"u":"/docs/onboarding/00-group-taxonomy.html","d":"Phase 1b - Functional Group Taxonomy","k":"Onboarding Guide","x":"This is the primary axis of the guide. Every one of the 5,234 distinct first-party type nodes from 00-inventory.md is assigned to exactly one functional group - its primary home:…"},"1201":{"u":"/docs/onboarding/00-group-taxonomy.html#design-notes-boundary-decisions-worth-stating-up-front","d":"Phase 1b - Functional Group Taxonomy","k":"Onboarding Guide","t":"Design notes (boundary decisions worth stating up front)","x":"- Cycles are kept whole. The 13 dependency cycles (SCCs) from the manifest are never split across groups. Notably the ApplicationDbContext AuditSaveChangesInterceptor…"},"1202":{"u":"/docs/onboarding/00-group-taxonomy.html#the-groups-ordered","d":"Phase 1b - Functional Group Taxonomy","k":"Onboarding Guide","t":"The groups (ordered)","x":"Reconciliation: 2311 production types across 27 groups + 2923 test/testing types in G25 = 5234 (matches the inventory's distinct-node count). No type appears twice; none dropped.…"},"1203":{"u":"/docs/onboarding/00-group-taxonomy.html#group-membership","d":"Phase 1b - Functional Group Taxonomy","k":"Onboarding Guide","t":"Group membership","x":"group-01-result-error-handling.md 16 types The Result/Error railway that every operation returns instead of throwing; pagination result shapes. group-02-domain-building-blocks.md…"},"1204":{"u":"/docs/onboarding/00-inventory.html","d":"Phase 0: Type Inventory","k":"Onboarding Guide","x":"Generated mechanically by a Roslyn syntactic parse of every in-scope .cs file under MMCA.Common/Source, MMCA.Common/Tests, MMCA.ADC/Source, MMCA.ADC/Tests. - Files scanned: 4000…"},"1205":{"u":"/docs/onboarding/00-inventory.html#counts-by-kind","d":"Phase 0: Type Inventory","k":"Onboarding Guide","t":"Counts by kind"},"1206":{"u":"/docs/onboarding/00-inventory.html#counts-by-assembly-distinct-nodes","d":"Phase 0: Type Inventory","k":"Onboarding Guide","t":"Counts by assembly (distinct nodes)"},"1207":{"u":"/docs/onboarding/00-inventory.html#full-inventory","d":"Phase 0: Type Inventory","k":"Onboarding Guide","t":"Full inventory"},"1208":{"u":"/docs/onboarding/00-inventory.html#extensiont-blocks","d":"Phase 0: Type Inventory","k":"Onboarding Guide","t":"extension(T) blocks"},"1209":{"u":"/docs/onboarding/00-inventory.html#generated--excluded-artifacts-no-type-sections-written","d":"Phase 0: Type Inventory","k":"Onboarding Guide","t":"Generated / excluded artifacts (no type sections written)","x":"138 files excluded as generated (EF migrations, snapshots, .g.cs, AssemblyInfo)."},"1210":{"u":"/docs/onboarding/00-primer.html","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","x":"This chapter teaches the cross-cutting things once, so the per-type chapters can stay focused. Read it before the group chapters (start with group-01). Everything here is either…"},"1211":{"u":"/docs/onboarding/00-primer.html#1-the-big-picture","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"1. The big picture","x":"Two codebases are in scope: - MMCA.Common: a framework, published as twenty-two NuGet packages (the count and list are owned by MMCA.Common/FACTS.md) to nuget.org (the documented…"},"1212":{"u":"/docs/onboarding/00-primer.html#2-architectural-styles-this-codebase-commits-to","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"2. Architectural styles this codebase commits to","x":"These are the recurring ideas. Each is taught fully at its first concrete appearance in a group chapter; here is the orientation so the vocabulary is familiar. - Domain-Driven…"},"1213":{"u":"/docs/onboarding/00-primer.html#3-the-external-stack-bcl--nuget-external-level-0","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"3. The external stack (BCL / NuGet, \"external Level 0\")","x":"These are not first-party and get no per-type sections. Versions are from MMCA.Common/Directory.Packages.props and MMCA.ADC/Directory.Packages.props (Central Package Management,…"},"1214":{"u":"/docs/onboarding/00-primer.html#4-c-build-and-code-style-conventions","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"4. C#, build, and code-style conventions","x":"- .NET 10.0, LangVersion: preview: required because the codebase uses C extension types (extension(T) syntax, see below). - Central Package Management (CPM). All NuGet versions…"},"1215":{"u":"/docs/onboarding/00-primer.html#5-the-solution--test-layout","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"5. The solution / test layout","x":"- .slnx: the human solution (XML format). .slnf, a solution filter used in CI to build a subset fast (MMCA.Store.CI.slnf, MMCA.ADC.CI.slnf). - Microsoft Testing Platform, not…"},"1216":{"u":"/docs/onboarding/00-primer.html#6-the-34-category-architecture-evaluation-lens","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"6. The 34-category architecture-evaluation lens","x":"This codebase is also scored against a 34-category rubric (Website/docs-src/governance/ArchitectureEvaluationCriteria.md, published at ). This guide weaves the rubric in so you…"},"1217":{"u":"/docs/onboarding/group-01-result-error-handling.html","d":"1. Result & Error Handling","k":"Onboarding Guide","x":"What this group covers. This is the first capability chapter, and it is deliberately first because the pattern it teaches underpins almost every other one in the guide. Before…"},"1218":{"u":"/docs/onboarding/group-01-result-error-handling.html#why-a-return-value-instead-of-an-exception","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"Why a return value instead of an exception","x":"Exceptions are expensive (stack capture and unwinding), they are invisible in a method's signature, and they conflate programmer errors with business outcomes. \"This order ID…"},"1219":{"u":"/docs/onboarding/group-01-result-error-handling.html#three-pieces-classification-carrier-envelope","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"Three pieces: classification, carrier, envelope","x":"ErrorType (MMCA.Common/Source/Core/MMCA.Common.Shared/Abstractions/ErrorType.cs:10) is the classification axis, a nine-member enum whose per-member doc comments name the HTTP…"},"1220":{"u":"/docs/onboarding/group-01-result-error-handling.html#the-railway-in-one-picture","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"The railway, in one picture","x":"\"Railway-oriented programming\" is the mental model: imagine two parallel tracks, a success track and a failure track. An operation that takes the current result as input is…"},"1221":{"u":"/docs/onboarding/group-01-result-error-handling.html#construction-discipline-and-the-invariant-it-buys","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"Construction discipline, and the invariant it buys","x":"You cannot new a Result directly: its constructors are internal (Result.cs:211, Result.cs:217), so the only way to produce one is through the static factory methods on the base…"},"1222":{"u":"/docs/onboarding/group-01-result-error-handling.html#severity-not-position-classifies-a-failure","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"Severity, not position, classifies a failure","x":"Result.Combine aggregates errors in evaluation order, so if a transport picked the first error to classify the whole failure, an incidental validation failure evaluated early…"},"1223":{"u":"/docs/onboarding/group-01-result-error-handling.html#making-a-result-survive-a-round-trip","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"Making a result survive a round trip","x":"The construction discipline above creates a problem the moment a result must be serialized: because Result 's constructors are internal and its properties are get-only,…"},"1224":{"u":"/docs/onboarding/group-01-result-error-handling.html#how-a-failure-becomes-an-http-response","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"How a failure becomes an HTTP response","x":"Follow a typical write through the layers. A domain factory or aggregate method validates its inputs and returns Result.Combine(...); a command handler in the application layer…"},"1225":{"u":"/docs/onboarding/group-01-result-error-handling.html#and-how-the-same-failure-crosses-a-service-boundary-or-reaches-the-browser","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"And how the same failure crosses a service boundary, or reaches the browser","x":"On the gRPC side, ResultGrpcExtensions carries a mirror of that table, ErrorType to Grpc.Core.StatusCode, in its own FrozenDictionary…"},"1226":{"u":"/docs/onboarding/group-01-result-error-handling.html#the-two-exceptions-and-why-they-exist-anyway","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"The two exceptions, and why they exist anyway","x":"A return-value pattern still needs a fallback for the places where you genuinely cannot return one. DomainException…"},"1227":{"u":"/docs/onboarding/group-01-result-error-handling.html#offset-paging-the-read-side-envelopes","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"Offset paging: the read-side envelopes","x":"The read side needs shapes of its own. A query that returns a list wraps it in CollectionResult , a thin [DataContract] record…"},"1228":{"u":"/docs/onboarding/group-01-result-error-handling.html#keyset-paging-the-same-envelope-family-a-different-cost-model","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"Keyset paging: the same envelope family, a different cost model","x":"Offset paging answers \"give me page 37\" and costs the database a scan of the 36 pages before it. KeysetPageRequest…"},"1229":{"u":"/docs/onboarding/group-01-result-error-handling.html#where-this-leads","d":"1. Result & Error Handling","k":"Onboarding Guide","t":"Where this leads","x":"With these sixteen types you have the full error-and-result vocabulary the rest of the guide assumes. You will see Result returned by the domain building blocks of Chapter 2…"},"1230":{"u":"/docs/onboarding/group-02-domain-building-blocks.html","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","x":"What this group covers. This is the DDD heart of the framework, the small, dependency-light primitives every business model in MMCA.Common and MMCA.ADC is built from. There are…"},"1231":{"u":"/docs/onboarding/group-02-domain-building-blocks.html#the-entity-chain-one-capability-per-rung","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"The entity chain, one capability per rung","x":"Read the chain bottom-up. BaseEntity (MMCA.Common/Source/Core/MMCA.Common.Domain/Entities/BaseEntity.cs:34) carries a single required init identifier of the per-entity alias…"},"1232":{"u":"/docs/onboarding/group-02-domain-building-blocks.html#three-opt-in-markers-beside-the-chain","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"Three opt-in markers beside the chain","x":"Not every cross-cutting capability belongs on the inheritance chain, because not every entity should pay for it. ITenantEntity…"},"1233":{"u":"/docs/onboarding/group-02-domain-building-blocks.html#how-a-domain-event-leaves-an-aggregate","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"How a domain event leaves an aggregate","x":"The runtime flow ties this group to the events/outbox group. A command handler loads an aggregate, calls a business method, and that method calls AddDomainEvent(...); the event…"},"1234":{"u":"/docs/onboarding/group-02-domain-building-blocks.html#value-objects-invalid-instances-cannot-exist","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"Value objects, invalid instances cannot exist","x":"The second family models concepts with no identity: two Money(10, USD) are equal because their values match, not because they are the same row. ValueObject is the cheapest…"},"1235":{"u":"/docs/onboarding/group-02-domain-building-blocks.html#smart-enumerations-a-closed-set-that-can-carry-behavior","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"Smart enumerations, a closed set that can carry behavior","x":"Enumeration (MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:77) is the answer to a recurring shape a CLR enum handles badly: a closed set of named members…"},"1236":{"u":"/docs/onboarding/group-02-domain-building-blocks.html#governance-markers-metadata-that-other-layers-act-on","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"Governance markers, metadata that other layers act on","x":"The last family is tiny attributes and helpers that carry intent the rest of the stack reads reflectively. PiiAttribute…"},"1237":{"u":"/docs/onboarding/group-02-domain-building-blocks.html#where-this-group-sits","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"Where this group sits","x":"Everything above is consumed by the layers that follow: every module entity (for example the Conference domain, Engagement, and Identity modules) derives from one of the three…"},"1238":{"u":"/docs/onboarding/group-03-querying-specifications.html","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","x":"What this group covers. Every read in MMCA.Common and ADC (\"list the published events\", \"get session 42\", \"the speakers in Atlanta, page 3, sorted by name, with only the name and…"},"1239":{"u":"/docs/onboarding/group-03-querying-specifications.html#the-specification-pattern-the-trusted-predicate","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"The Specification pattern, the trusted predicate","x":"ISpecification (MMCA.Common/Source/Core/MMCA.Common.Domain/Interfaces/ISpecification.cs:12) exposes two faces of one rule: a Criteria expression tree that EF Core translates to…"},"1240":{"u":"/docs/onboarding/group-03-querying-specifications.html#queryspecification-a-whole-read-in-one-object","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"QuerySpecification, a whole read in one object","x":"A plain specification is only a predicate, which leaves includes, ordering, paging, and tracking to be threaded through every layer as loose arguments. QuerySpecification…"},"1241":{"u":"/docs/onboarding/group-03-querying-specifications.html#dynamic-filtering-one-strategy-per-clr-type","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"Dynamic filtering, one Strategy per CLR type","x":"User filters arrive as a Dictionary , property name to operator key plus raw string value, parsed from the query string by QueryFilterModelBinder at the API edge, which caps a…"},"1242":{"u":"/docs/onboarding/group-03-querying-specifications.html#sorting-sparse-fieldsets-and-paging-arithmetic","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"Sorting, sparse fieldsets, and paging arithmetic","x":"QueryFieldService (MMCA.Common/Source/Core/MMCA.Common.Application/Services/QueryFieldService.cs:16) owns the rest of read shaping. ApplySorting (QueryFieldService.cs:184, with a…"},"1243":{"u":"/docs/onboarding/group-03-querying-specifications.html#the-pipeline-two-entity-paths-plus-projection-pushdown","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"The pipeline: two entity paths plus projection pushdown","x":"IEntityQueryPipeline (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/IEntityQueryPipeline.cs:10) is the execution contract, implemented by the sealed…"},"1244":{"u":"/docs/onboarding/group-03-querying-specifications.html#the-query-service-the-public-face","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"The query service, the public face","x":"IEntityQueryService (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Mapping/IEntityQueryService.cs:19) and its concrete EntityQueryService…"},"1245":{"u":"/docs/onboarding/group-03-querying-specifications.html#end-to-end-one-list-request","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"End to end, one list request","x":"The request reaches a read controller, EntityControllerBase (Group 12), which resolves MaxPageSize per request from ApplicationSettings, falling back to 500 when unset…"},"1246":{"u":"/docs/onboarding/group-03-querying-specifications.html#query-tags-naming-the-use-case-behind-a-statement","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"Query tags, naming the use case behind a statement","x":"Two types in the Services/Query folder serve the operator reading a query store rather than the read path itself. QueryTagScope…"},"1247":{"u":"/docs/onboarding/group-03-querying-specifications.html#also-filed-here-best-effort-dispatch-and-the-upcaster-registry","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"Also filed here: best-effort dispatch and the upcaster registry","x":"Four types in this group are not part of the read path at all; they are co-located in MMCA.Common.Application/Services and are grouped by that folder. BestEffort…"},"1248":{"u":"/docs/onboarding/group-04-events-outbox.html","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","x":"What this chapter covers. This group is the codebase's event spine: how an aggregate says \"something happened\", how that fact is persisted so it cannot be lost, and how it…"},"1249":{"u":"/docs/onboarding/group-04-events-outbox.html#the-two-kinds-of-event","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"The two kinds of event","x":"Everything starts with two marker interfaces in the Domain layer. IDomainEvent is the base contract: a DateOccurred timestamp (when the business action happened, not when it was…"},"1250":{"u":"/docs/onboarding/group-04-events-outbox.html#stored-identity-what-a-row-remembers-an-event-as","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Stored identity: what a row remembers an event as","x":"An event in memory is a CLR type; an event in a table is a string. Which string it is decides whether a row survives a refactoring, and EventNameResolver is the single cached…"},"1251":{"u":"/docs/onboarding/group-04-events-outbox.html#raising-and-capturing-where-the-outbox-is-written","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Raising and capturing: where the outbox is written","x":"Aggregates raise events by calling AddDomainEvent() (see AuditableAggregateRootEntity in G02), which simply buffers them on the entity. Nothing is dispatched yet; the events ride…"},"1252":{"u":"/docs/onboarding/group-04-events-outbox.html#the-routing-split-local-events-dispatch-in-process-integration-events-wait-for-the-bus","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"The routing split: local events dispatch in-process, integration events wait for the bus","x":"Here is the detail that most people get wrong, and it is the heart of the design. After the transaction commits (SavedChanges), the interceptor does not treat all captured events…"},"1253":{"u":"/docs/onboarding/group-04-events-outbox.html#who-the-delivery-runs-as-the-captured-origin","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Who the delivery runs as: the captured origin","x":"A delivered event runs a poll cycle later, on another thread and possibly in another process, so without help it would run as an anonymous, tenant-less system call: a handler…"},"1254":{"u":"/docs/onboarding/group-04-events-outbox.html#the-safety-net-how-the-processor-schedules-itself","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"The safety net: how the processor schedules itself","x":"The OutboxProcessor is a BackgroundService and the most intricate type in the group; most of its complexity is about not wasting work. It exists because the steps between commit…"},"1255":{"u":"/docs/onboarding/group-04-events-outbox.html#claiming-scale-out-and-ordered-delivery","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Claiming, scale-out, and ordered delivery","x":"Because a deployment may run more than one replica, each cycle claims its eligible prefix with a lease (LockedUntil plus LockToken,…"},"1256":{"u":"/docs/onboarding/group-04-events-outbox.html#failures-dead-letters-and-keeping-the-table-and-telemetry-bounded","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Failures, dead-letters, and keeping the table (and telemetry) bounded","x":"Delivery failures split into outcomes worth keeping straight. A transient failure (a handler or broker publish throwing) increments the row's RetryCount, records LastError, and…"},"1257":{"u":"/docs/onboarding/group-04-events-outbox.html#the-pluggable-transport-and-the-posture-flags-that-decide-it","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"The pluggable transport, and the posture flags that decide it","x":"Here is the boundary that makes a module extractable without rewriting its handlers. Application code that wants to publish an integration event depends on IEventBus (or on the…"},"1258":{"u":"/docs/onboarding/group-04-events-outbox.html#consuming-from-the-broker-the-inbox-and-the-generic-consumer","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Consuming from the broker: the inbox and the generic consumer","x":"On the receiving side of a broker hop, application code keeps writing plain IIntegrationEventHandler implementations; there is no MassTransit-specific consumer class to author…"},"1259":{"u":"/docs/onboarding/group-04-events-outbox.html#putting-it-together-one-events-life","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Putting it together, one event's life","x":"To see the whole spine at once, follow a single integration event from a producer service to a consumer service in broker mode. (1) A command mutates an aggregate, which raises…"},"1260":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","x":"What this group covers. Every write and every read in an MMCA application is a use case: a small, single-purpose object (a command or a query) handed to a handler that does…"},"1261":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#the-shape-thin-handlers-fat-pipeline","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"The shape: thin handlers, fat pipeline","x":"A handler is deliberately tiny. ICommandHandler (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Contracts/ICommandHandler.cs:9) and IQueryHandler…"},"1262":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#how-the-pipeline-is-assembled-scrutor-registration-versus-execution-order","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"How the pipeline is assembled (Scrutor, registration versus execution order)","x":"The wiring lives in the partial DependencyInjection class (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:24, with the module scan in…"},"1263":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#why-this-exact-order-and-what-each-layer-guards","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Why this exact order, and what each layer guards","x":"The nesting order is a deliberate cost-and-correctness argument, spelled out in the registration XML-doc (DependencyInjection.cs:87-109): - Feature-gating is outermost so a…"},"1264":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#opt-in-by-marker-interface-pay-only-for-what-you-use","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Opt-in by marker interface, pay only for what you use","x":"The pipeline is registered for every handler, but most decorators are dormant unless the use case asks for them. The switch is a set of tiny marker / role interfaces in…"},"1265":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#the-generic-write-side-three-verbs-a-module-does-not-have-to-write","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"The generic write side: three verbs a module does not have to write","x":"Below the pipeline sits a second body of shared code: the workflows every aggregate write repeats. They are ordinary handlers, so the decorators wrap them exactly as they wrap a…"},"1266":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#declaring-the-contract-the-opt-in-request-markers","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Declaring the contract: the opt-in request markers","x":"ICommand (.../UseCases/ICommand.cs:31) and IQuery (.../UseCases/IQuery.cs:26) let a request DTO declare the result type its handler produces. Both are body-less interfaces whose…"},"1267":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#tenant-scoping-and-the-two-lock-tables","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Tenant scoping and the two lock tables","x":"Both caching decorators are multi-tenant aware, and the reason is a nice illustration of where a cross-cutting concern has to live. ICacheService is a singleton and therefore…"},"1268":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#two-supporting-pieces","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Two supporting pieces","x":"Two small helpers make the short-circuit decorators possible. ResultFailureFactory…"},"1269":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#the-other-application-layer-contracts-in-this-group","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"The other Application-layer contracts in this group","x":"Several contracts sit beside the pipeline rather than inside it. They are declared here, in the Application layer, and implemented in Infrastructure or the composition root,…"},"1270":{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#where-this-fits-and-the-failure-mode-contract","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Where this fits, and the failure-mode contract","x":"These contracts sit in the Application layer of Clean Architecture (primer §1), above Domain and below Infrastructure and the API. The API layer (G12) resolves a closed handler…"},"1271":{"u":"/docs/onboarding/group-06-validation.html","d":"6. Validation","k":"Onboarding Guide","x":"What this group covers. This is the framework-level validation kit that MMCA.Common.Application ships so every module validates input the same way. It has four parts: (1) a set…"},"1272":{"u":"/docs/onboarding/group-07-persistence-ef-core.html","d":"7. Persistence & EF Core","k":"Onboarding Guide","x":"What this group covers. This is the framework's data-access engine: everything between a domain aggregate and a row in a database. It is the single largest group in the guide…"},"1273":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#one-base-context-one-class-per-engine-one-instance-per-database","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"One base context, one class per engine, one instance per database","x":"ApplicationDbContext (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:46) is an abstract primary-constructor class over EF's…"},"1274":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#savechanges-as-an-interceptor-pipeline","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"SaveChanges as an interceptor pipeline","x":"The base context resolves its interceptors from DI in OnConfiguring (ApplicationDbContext.cs:290-322), and registration order is execution order. The audit interceptor runs…"},"1275":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#deferred-work-the-internal-command-queue","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Deferred work, the internal-command queue","x":"The same \"write it in the transaction, run it afterwards\" reasoning the outbox applies to events is applied to work by the internal-command queue (ADR-114).…"},"1276":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#the-tenant-boundary-read-filter-plus-write-guard","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"The tenant boundary, read filter plus write guard","x":"Multi-tenancy (ADR-073) is two independent halves that meet in this group. The read half is the named Tenant query filter the base context applies to every non-owned…"},"1277":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#recording-what-changed-the-audit-trail","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Recording what changed, the audit trail","x":"AuditTrailSaveChangesInterceptor (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:63) is the fourth interceptor…"},"1278":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#repositories-specifications-and-the-unit-of-work","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Repositories, specifications, and the unit of work","x":"Handlers do not touch a DbContext directly. They ask a UnitOfWork (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:13) for a repository. The…"},"1279":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#routing-an-entity-to-its-database","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Routing an entity to its database","x":"The heart of ADR-006 is that every entity resolves to a DataSourceKey (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/DataSourceKey.cs:15),…"},"1280":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#one-registry-answers-every-engine-question","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"One registry answers every engine question","x":"Four engines would otherwise mean a four-way switch in every component that maps a key, quotes a column, picks a migrations assembly or decides whether a source has an outbox.…"},"1281":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#three-model-finalizing-conventions-and-the-identifier-mapping","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Three model-finalizing conventions and the identifier mapping","x":"The base context adds all three of its conventions in ConfigureConventions (ApplicationDbContext.cs:378-399), and each exists because a cross-cutting policy above would otherwise…"},"1282":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#entity-configuration-and-engine-portability","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Entity configuration and engine portability","x":"Concrete entity configurations derive from the engine-aware EntityTypeConfiguration…"},"1283":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#two-tables-one-database-owns-refresh-sessions-and-permission-grants","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Two tables one database owns, refresh sessions and permission grants","x":"Most of the framework's own tables are infrastructure every relational source needs. Two are not: both are Identity-module data that exactly one database owns, so both are mapped…"},"1284":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#encryption-seeding-design-time-and-the-shared-helpers","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Encryption, seeding, design time, and the shared helpers","x":"A handful of supporting pieces round out the EF side. EncryptedStringConverter…"},"1285":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#the-storage-image-and-push-contracts","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"The storage, image, and push contracts","x":"The group also carries the Application-layer contracts for the storage-adjacent services that are not EF at all. Each has a null default in the container so a host that has not…"},"1286":{"u":"/docs/onboarding/group-07-persistence-ef-core.html#where-this-group-sits","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Where this group sits","x":"Persistence is the concrete floor the abstract domain stands on. The entity bases and audit contracts from Group 02 are what the interceptors stamp and the query filters hide;…"},"1287":{"u":"/docs/onboarding/group-08-auth.html","d":"8. Authentication & Authorization","k":"Onboarding Guide","x":"What this group covers. This is the security spine of the framework: how a caller proves who they are (authentication), how the system decides what they may do (authorization),…"},"1288":{"u":"/docs/onboarding/group-08-auth.html#tokens-one-signing-switch-two-validation-worlds","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Tokens: one signing switch, two validation worlds","x":"The framework mints two credentials on every successful sign-in: a short-lived access token (a JWT, 15 minutes by default,…"},"1289":{"u":"/docs/onboarding/group-08-auth.html#the-shared-authentication-workflow","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"The shared authentication workflow","x":"Login, registration, refresh, revocation, and device listing are not re-implemented per app. They live once in AuthenticationServiceBase…"},"1290":{"u":"/docs/onboarding/group-08-auth.html#refresh-sessions-one-row-per-device","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Refresh sessions: one row per device","x":"A refresh token is no longer a column on the user row. Every issue opens its own RefreshSession (MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:31), so signing…"},"1291":{"u":"/docs/onboarding/group-08-auth.html#what-the-apps-user-aggregate-must-expose","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"What the app's User aggregate must expose","x":"The shared workflows never see an app's User class. They see four small Domain-layer contracts, each sized to one workflow, which is the [Rubric §1, SOLID] interface-segregation…"},"1292":{"u":"/docs/onboarding/group-08-auth.html#passwords-and-brute-force-protection","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Passwords and brute-force protection","x":"Password material is handled by PasswordHasher (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordHasher.cs:12), which hashes with PBKDF2-HMAC-SHA512 at 600,000…"},"1293":{"u":"/docs/onboarding/group-08-auth.html#the-second-factor-one-optional-step-in-the-same-sign-in","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"The second factor: one optional step in the same sign-in","x":"Two-factor authentication ships as contracts plus a stateless implementation, never as a feature that turns itself on: the account row belongs to the app, and making a second…"},"1294":{"u":"/docs/onboarding/group-08-auth.html#forgot-password-a-cache-backed-single-use-token","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Forgot password: a cache-backed single-use token","x":"A user who has lost the password cannot present one, so this flow is anonymous by necessity, which makes every one of its responses a potential account-enumeration oracle. It is…"},"1295":{"u":"/docs/onboarding/group-08-auth.html#email-confirmation-the-same-token-shape-a-different-prefix","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Email confirmation: the same token shape, a different prefix","x":"Confirming an address is the forgot-password flow's twin, and it is built that way on purpose: member for member, IEmailConfirmationTokenService…"},"1296":{"u":"/docs/onboarding/group-08-auth.html#reading-identity-from-claims","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Reading identity from claims","x":"Once a request is authenticated, downstream code needs the caller's identity without re-parsing the JWT, and it needs one answer no matter which pipeline produced the principal.…"},"1297":{"u":"/docs/onboarding/group-08-auth.html#authorization-permissions-and-ownership","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Authorization: permissions and ownership","x":"There is one authorization model here, and it is capabilities, not role names. The single AddAuthorizationPolicies() extension in AuthorizationExtensions…"},"1298":{"u":"/docs/onboarding/group-08-auth.html#session-cookies-keeping-ssr-authenticated","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Session cookies: keeping SSR authenticated","x":"The final cluster solves a Blazor-specific problem: an interactive Blazor app keeps its access token in browser memory, but a cold server-side render (a new tab, an F5, an…"},"1299":{"u":"/docs/onboarding/group-08-auth.html#privacy-the-data-subject-export-package","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Privacy: the data-subject export package","x":"Three members of this group belong to the privacy surface that sits beside erasure. UserDataExportDTO (MMCA.Common/Source/Core/MMCA.Common.Shared/Privacy/UserDataExportDTO.cs:15)…"},"1300":{"u":"/docs/onboarding/group-08-auth.html#shared-primitives-and-adjacent-members","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Shared primitives and adjacent members","x":"Several group members are general-purpose primitives that landed in this chapter because of how the dependency grouping fell, though one of them is now load-bearing for auth.…"},"1301":{"u":"/docs/onboarding/group-09-caching.html","d":"9. Caching","k":"Onboarding Guide","x":"What this group covers. Caching in this codebase is small, deliberate, and woven into the CQRS pipeline rather than scattered across handlers. The group is nine types: one port…"},"1302":{"u":"/docs/onboarding/group-10-notifications.html","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","x":"What this group covers. This is the notification subsystem, the machinery that turns \"an organizer wants to tell every attendee something\" into messages that actually reach…"},"1303":{"u":"/docs/onboarding/group-10-notifications.html#the-layering-and-why-the-pieces-sit-where-they-do","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"The layering, and why the pieces sit where they do","x":"The dependency flow of the group mirrors the framework's Clean Architecture story ([Rubric §3, Clean Architecture]). The Domain layer holds the two aggregates, PushNotification…"},"1304":{"u":"/docs/onboarding/group-10-notifications.html#the-broadcast-send-flow-end-to-end","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"The broadcast send flow, end to end","x":"Sending a notification is a command-side vertical slice ([Rubric §5, Vertical Slice], [Rubric §6, CQRS & Event-Driven]). An organizer POSTs to NotificationsController, which is…"},"1305":{"u":"/docs/onboarding/group-10-notifications.html#scope-keys-one-string-that-narrows-a-broadcast-an-inbox-and-a-live-channel","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"Scope keys: one string that narrows a broadcast, an inbox, and a live channel","x":"NotificationScopeKey is the small type that ties three otherwise unrelated paths together. It owns the format (event:{id} via ForEvent, session:{id} via ForSession, both rendered…"},"1306":{"u":"/docs/onboarding/group-10-notifications.html#the-inbox-side","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"The inbox side","x":"Reading and acknowledging notifications is the query/command counterpart, served by InboxController under the same feature gate and a plain [Authorize], so any authenticated user…"},"1307":{"u":"/docs/onboarding/group-10-notifications.html#the-signalr-transport-and-how-it-survives-extraction","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"The SignalR transport, and how it survives extraction","x":"NotificationHub is intentionally thin (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Notifications/NotificationHub.cs:24-27): it is [Authorize]d, and beyond ASP.NET's…"},"1308":{"u":"/docs/onboarding/group-10-notifications.html#the-module-host-native-device-registration-and-the-privacy-export","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"The module host, native-device registration, and the privacy export","x":"On the ADC side the whole capability is packaged by NotificationModule (MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.API/NotificationModule.cs:15), an IModule that…"},"1309":{"u":"/docs/onboarding/group-10-notifications.html#where-this-group-sits","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"Where this group sits","x":"Upstream, this group depends on the domain building blocks of Group 02 (both aggregates derive from AuditableAggregateRootEntity ), the Result pattern of Group 01, the CQRS…"},"1310":{"u":"/docs/onboarding/group-11-navigation-populators.html","d":"11. Navigation Metadata & Populators (EF-decoupled eager loading)","k":"Onboarding Guide","x":"EF Core gives you .Include() for eager loading, and for a single SQL Server database that is the right tool. But this codebase is a database-per-service modular monolith…"},"1311":{"u":"/docs/onboarding/group-12-api-hosting-mapping.html","d":"12. API Hosting, Middleware, Idempotency & DTO/Contract Mapping","k":"Onboarding Guide","x":"What this group covers. This is the ASP.NET Core edge of the framework: the layer that turns an HTTP request into a domain call and turns a Result back into an HTTP response.…"},"1312":{"u":"/docs/onboarding/group-13-grpc-contracts.html","d":"13. gRPC & Inter-Service Contracts","k":"Onboarding Guide","x":"What this chapter is about. Once the ADC modules stopped sharing a process and became four separate service hosts (Identity, Conference, Engagement, Notification), the in-process…"},"1313":{"u":"/docs/onboarding/group-14-module-system-composition.html","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","x":"What this chapter covers. This is the wiring layer, the code that turns a pile of layered assemblies into a running host. It answers three questions a new host author asks: how…"},"1314":{"u":"/docs/onboarding/group-14-module-system-composition.html#the-module-contract-and-the-boundary-it-creates","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"The module contract and the boundary it creates","x":"A module is the unit of cohesion above a feature slice: Conference, Engagement, Identity, Notification. Each one implements IModule…"},"1315":{"u":"/docs/onboarding/group-14-module-system-composition.html#discovery-and-kahn-ordered-registration","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Discovery and Kahn-ordered registration","x":"ModuleLoader (MMCA.Common/Source/Core/MMCA.Common.Application/Modules/ModuleLoader.cs:16) is the engine. Its one DiscoverAndRegister overload (ModuleLoader.cs:59-65) takes the…"},"1316":{"u":"/docs/onboarding/group-14-module-system-composition.html#the-two-composition-roots-and-the-pipeline-that-seals-them","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"The two composition roots, and the pipeline that seals them","x":"Service registration itself lives in two static DependencyInjection classes, each using a C extension(IServiceCollection services) block (see primer §4 for the extension(T)…"},"1317":{"u":"/docs/onboarding/group-14-module-system-composition.html#opt-in-platform-features-are-composed-the-same-way","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Opt-in platform features are composed the same way","x":"Four capabilities are registered beside the roots rather than inside them, and they share one discipline: registering a feature is not the same as turning it on.…"},"1318":{"u":"/docs/onboarding/group-14-module-system-composition.html#deferred-work-and-the-context-that-travels-with-it","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Deferred work, and the context that travels with it","x":"The last two things AddInfrastructure composes are a queue for work a request wants done later, and the machinery that lets whatever drains a queue act as the identity that…"},"1319":{"u":"/docs/onboarding/group-14-module-system-composition.html#assembly-anchors","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Assembly anchors","x":"Several pieces of machinery need a Type whose Assembly identifies a layer: Scrutor's FromAssemblyOf () scans, FluentValidation's AddValidatorsFromAssemblyContaining (), and…"},"1320":{"u":"/docs/onboarding/group-14-module-system-composition.html#configuration-binding-the-settings-family","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Configuration binding, the Settings family","x":"Everything a host operator tunes arrives as a strongly-typed settings object bound from an appsettings.json section, each carrying a static readonly string SectionName so the…"},"1321":{"u":"/docs/onboarding/group-14-module-system-composition.html#the-two-routing-attributes","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"The two routing attributes","x":"Two attributes, both in MMCA.Common.Infrastructure, both Inherited = true so they ride down a configuration class hierarchy, encode where an entity is stored declaratively: the…"},"1322":{"u":"/docs/onboarding/group-14-module-system-composition.html#shared-user-use-case-bases-composition-in-the-other-direction","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Shared user use-case bases: composition in the other direction","x":"The chapter's last family is composition at the handler level rather than the container level. ADC and Store each own an Identity module, and thirteen of their account use cases…"},"1323":{"u":"/docs/onboarding/group-14-module-system-composition.html#end-to-end-one-hosts-boot","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"End-to-end: one host's boot","x":"Reading MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs top to bottom shows the whole chapter cooperating. The host calls AddInfrastructure(builder.Configuration)…"},"1324":{"u":"/docs/onboarding/group-15-common-ui-framework.html","d":"15. Common UI Framework (MudBlazor components, theme, base pages)","k":"Onboarding Guide","x":"What this chapter covers. MMCA.Common.UI is the Blazor presentation package, and it is one of the two layers (with Grpc) allowed to reference Shared only: its single…"},"1325":{"u":"/docs/onboarding/group-16-aspire-orchestration.html","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","x":"This chapter covers the hosting boundary: how the distributed MMCA system is composed and run, locally as a single dotnet run, and in Azure Container Apps (ACA) as a set of…"},"1326":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#the-orchestrator-declaring-the-resource-graph","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"The orchestrator: declaring the resource graph","x":"When you run dotnet run --project Source/Hosting/MMCA.ADC.AppHost, Aspire executes Program.cs top to bottom, building a resource model rather than starting anything immediately.…"},"1327":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#broker-parity-rabbitmq-by-default-the-service-bus-emulator-on-demand","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Broker parity: RabbitMQ by default, the Service Bus emulator on demand","x":"RabbitMQ is a different broker from the one production runs, so anything that only misbehaves on Azure Service Bus (entity-name limits, topic and subscription provisioning,…"},"1328":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#startup-ordering-health-gated-waits-and-the-grpc-deadlock-avoidance-trick","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Startup ordering, health-gated waits, and the gRPC deadlock-avoidance trick","x":"Aspire's WaitFor builds a startup dependency graph from the resource model: a service does not start until the resources it waits on report healthy. What \"healthy\" means for a…"},"1329":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#the-service-baseline-addservicedefaults","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"The service baseline: AddServiceDefaults()","x":"Every running host calls one method early in Program.cs: AddServiceDefaults() from the single framework-grade Extensions in MMCA.Common.Aspire (Level 11,…"},"1330":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#one-source-of-truth-for-resilience-numbers","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"One source of truth for resilience numbers","x":"The numbers that pipeline uses are not literals in the Aspire package. They live in HttpResilienceDefaults (Level 0,…"},"1331":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#listeners-and-probes-one-kestrel-profile-per-host-shape","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Listeners and probes: one Kestrel profile per host shape","x":"Before a service can answer a health probe it has to be listening on a protocol the prober speaks, and that is not free when the same port serves inbound gRPC.…"},"1332":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#health-checks-liveness-readiness-and-the-optional-tag","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Health checks: liveness, readiness, and the \"optional\" tag","x":"MapDefaultEndpoints() (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.Health.cs:123) exposes the three-probe surface the platform reads: /health (every check, for…"},"1333":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#telemetry-what-gets-exported-and-what-it-costs","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Telemetry: what gets exported, and what it costs","x":"ConfigureOpenTelemetry() (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.Telemetry.cs:71) wires logging with formatted messages and scopes (:73-77), metrics, and…"},"1334":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#warm-up-defeating-aca-cold-start","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Warm-up: defeating ACA cold-start","x":"The warm-up subsystem exists for one concrete failure mode: the \"first request fails, second succeeds\" pattern on a CPU-throttled idle ACA replica, where lazy initialization…"},"1335":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#configuration-secrets-the-vault-as-one-more-configuration-source","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Configuration secrets: the vault as one more configuration source","x":"Connection strings, RSA signing keys and OAuth client secrets have to reach the process somehow, and KeyVaultConfigurationExtensions (Level 0,…"},"1336":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#security-headers-cors-and-the-shared-key-ring-at-the-host-edge","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Security headers, CORS, and the shared key ring at the host edge","x":"The next boundary in this group hardens the request and response edge. The shared response-header middleware is defined entirely in…"},"1337":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#the-gateway-edge-kit-correlation-rate-limiting-downstream-readiness","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"The gateway edge kit: correlation, rate limiting, downstream readiness","x":"A YARP gateway is the one process every client request passes through, and it is also the one host that has no application container: no DbContext, no module loader, no…"},"1338":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#the-yarp-building-blocks-cluster-profiles-ejection-trace-headers-route-policies","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"The YARP building blocks: cluster profiles, ejection, trace headers, route policies","x":"The route table itself is configuration, not code (ADR-089), and MMCA.Common.Gateway deliberately does not load it: LoadFromConfig (or any other IProxyConfigProvider) stays the…"},"1339":{"u":"/docs/onboarding/group-16-aspire-orchestration.html#how-it-all-fits-at-runtime","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"How it all fits at runtime","x":"Putting the pieces in sequence: the AppHost declares the graph, picks a broker once, and injects per-service env vars (WithSQLServerDataSource, WithSelectedBroker,…"},"1340":{"u":"/docs/onboarding/group-17-conference-domain.html","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","x":"What this chapter covers. This is the heart of the Atlanta Developers Conference application, the Conference bounded context, the largest and richest domain in MMCA.ADC. It…"},"1341":{"u":"/docs/onboarding/group-17-conference-domain.html#two-packages-one-bounded-context","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Two packages, one bounded context","x":"The Conference context spans two of the module's projects, and the split is deliberate Clean Architecture ([Rubric §3, Clean Architecture]). MMCA.ADC.Conference.Domain holds the…"},"1342":{"u":"/docs/onboarding/group-17-conference-domain.html#ten-aggregates-and-their-ownership-boundaries","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Ten aggregates and their ownership boundaries","x":"An aggregate is a root entity plus the children it exclusively owns; invariants are enforced inside the boundary, and references across aggregates are by ID, never by object…"},"1343":{"u":"/docs/onboarding/group-17-conference-domain.html#the-aggregate-shape-taught-once","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"The aggregate shape, taught once","x":"Open any of the roots and you will see the same skeleton; this repetition is the point, and it is what makes the per-type sections that follow read quickly. The shape, using…"},"1344":{"u":"/docs/onboarding/group-17-conference-domain.html#invariants-business-rules-as-testable-units","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Invariants, business rules as testable units","x":"Each aggregate has a co-located static invariant class, EventInvariants (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:14),…"},"1345":{"u":"/docs/onboarding/group-17-conference-domain.html#domain-events-and-the-outbox-spine","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Domain events and the outbox spine","x":"Every state-changing method raises a domain event through the inherited AddDomainEvent(...), and the eighteen events come in two shapes with two different base types. The nine…"},"1346":{"u":"/docs/onboarding/group-17-conference-domain.html#the-cross-aggregate-cascade-a-pure-domain-service","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"The cross-aggregate cascade: a pure domain service","x":"One business rule cannot live inside a single aggregate: deleting an Event must also delete every Session belonging to it (BR-127), every Sponsor sold against it, every Partner…"},"1347":{"u":"/docs/onboarding/group-17-conference-domain.html#read-models-and-the-ai-decision-support-feature","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Read models and the AI decision-support feature","x":"The largest cluster in Conference.Shared is the DTO layer, the wire contracts that decouple the API from the domain entities ([Rubric §9, API & Contract Design]; ADR-001 chose…"},"1348":{"u":"/docs/onboarding/group-17-conference-domain.html#authorization-vocabulary-and-current-event-selection","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Authorization vocabulary and current-event selection","x":"Two more Shared helpers deserve a mention because they encode policy the whole module relies on. ConferencePermissions…"},"1349":{"u":"/docs/onboarding/group-17-conference-domain.html#crossing-the-module-boundary-contracts-stubs-and-integration-events","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Crossing the module boundary: contracts, stubs, and integration events","x":"Conference does not live alone. Three kinds of connection point join it to other modules, and all live in Conference.Shared so neither side reaches into the other's domain…"},"1350":{"u":"/docs/onboarding/group-17-conference-domain.html#end-to-end-one-organizer-action","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"End-to-end: one organizer action","x":"To see the chapter cooperate, follow an organizer renaming a room on an event. The application handler loads the Event aggregate (with its Rooms hydrated by the navigation…"},"1351":{"u":"/docs/onboarding/group-18-conference-application.html","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","x":"What this chapter covers. This is the application layer of the Conference module, the largest single application assembly in the codebase (this group covers 358 entries in the…"},"1352":{"u":"/docs/onboarding/group-18-conference-application.html#the-vertical-slice-anatomy-of-a-use-case","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"The vertical-slice anatomy of a use case","x":"Open any feature folder under Sessions/UseCases/, Events/UseCases/, Speakers/UseCases/, Sponsors/UseCases/, Partners/UseCases/, Activities/UseCases/, Categories/UseCases/, or…"},"1353":{"u":"/docs/onboarding/group-18-conference-application.html#manual-mapping-validation-rule-fragments-and-authorization-specifications","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Manual mapping, validation rule fragments, and authorization specifications","x":"Three sibling families recur across every aggregate. DTO mappers (SessionDTOMapper, EventDTOMapper, SpeakerDTOMapper, SponsorDTOMapper, PartnerDTOMapper…"},"1354":{"u":"/docs/onboarding/group-18-conference-application.html#query-services-navigation-populators-and-the-composition-root","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Query services, navigation populators, and the composition root","x":"Read paths do not get bespoke handlers for the common cases; they go through the framework's generic IEntityQueryService , which supplies filtering, sorting, paging, and field…"},"1355":{"u":"/docs/onboarding/group-18-conference-application.html#event-driven-reactions-domain-and-integration-handlers","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Event-driven reactions: domain and integration handlers","x":"The application layer is also where the module reacts to events. The module keeps exactly one domain event handler, and it is the one with a real side effect.…"},"1356":{"u":"/docs/onboarding/group-18-conference-application.html#attendee-facing-read-models-calendar-export-and-nownext","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Attendee-facing read models: calendar export and Now/Next","x":"A small cluster of queries serves the public schedule surfaces without going through the generic query service, because their output is not a DTO list. ExportEventCalendarHandler…"},"1357":{"u":"/docs/onboarding/group-18-conference-application.html#session-materials-authorization-as-data-blob-cleanup-as-a-durable-command","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Session materials: authorization as data, blob cleanup as a durable command","x":"Speakers publish their slides and links against their own sessions, which makes the SessionAssets slice the one write surface in this module where the right to act is data rather…"},"1358":{"u":"/docs/onboarding/group-18-conference-application.html#the-sessionize-import-strategy-pattern-orchestration","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"The Sessionize import: Strategy-pattern orchestration","x":"The single most involved use case is importing a conference agenda from Sessionize.com. Sessionize returns one JSON payload covering five interdependent entity families…"},"1359":{"u":"/docs/onboarding/group-18-conference-application.html#decision-support-ai-scoring-and-content-analytics","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Decision support: AI scoring and content analytics","x":"The last cluster is session-selection decision support, analytics that help organizers triage proposals. GetSessionSelectionDashboardHandler is a composite query: it validates…"},"1360":{"u":"/docs/onboarding/group-19-conference-infrastructure.html","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","x":"What this chapter covers. This is the adapter layer of the Conference module, the place where the engine-agnostic domain meets concrete technology. Three concerns live here: (1)…"},"1361":{"u":"/docs/onboarding/group-19-conference-infrastructure.html#engine-agnostic-entities-engine-chosen-by-the-config-base-class","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"Engine-agnostic entities, engine chosen by the config base class","x":"The most important idea in this chapter is one the entities themselves never express: what storage engine each entity uses is decided here, not in the domain. A Conference domain…"},"1362":{"u":"/docs/onboarding/group-19-conference-infrastructure.html#each-config-inherits-the-cross-cutting-behavior-then-adds-entity-specifics","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"Each config inherits the cross-cutting behavior, then adds entity specifics","x":"Every configuration's Configure method begins with base.Configure(builder) (for example SessionConfiguration.cs:18, ActivityConfiguration.cs:17) and then adds its own mappings.…"},"1363":{"u":"/docs/onboarding/group-19-conference-infrastructure.html#dbsets-the-context-shape-and-how-the-configurations-are-actually-found","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"DbSets, the context shape, and how the configurations are actually found","x":"ModuleApplicationDbContext (MMCA.ADC.Conference.Infrastructure/Persistence/DbContexts/ModuleApplicationDbContext.cs:22) is the Conference module's abstract DbContext. It does one…"},"1364":{"u":"/docs/onboarding/group-19-conference-infrastructure.html#seeding-two-real-events-always-sample-data-only-in-dev-and-ci","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"Seeding: two real events always, sample data only in dev and CI","x":"ConferenceModuleDbSeeder (MMCA.ADC.Conference.Infrastructure/Persistence/DbContexts/Seeding/ConferenceModuleDbSeeder.cs:27) derives from the framework's DbSeeder and runs after…"},"1365":{"u":"/docs/onboarding/group-19-conference-infrastructure.html#the-sessionize-adapter","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"The Sessionize adapter","x":"SessionizeService (MMCA.ADC.Conference.Infrastructure/Events/Sessionize/SessionizeService.cs:12) is a deliberately thin HTTP client: the whole class is one method…"},"1366":{"u":"/docs/onboarding/group-19-conference-infrastructure.html#the-ai-scoring-adapter-and-its-response-guardrail","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"The AI scoring adapter and its response guardrail","x":"SessionScoringService (MMCA.ADC.Conference.Infrastructure/Sessions/Scoring/SessionScoringService.cs:46) is the richer of the two adapters: it scores one session proposal against…"},"1367":{"u":"/docs/onboarding/group-19-conference-infrastructure.html#the-scoring-run-is-durable-now-and-this-layer-keeps-one-adapter-inside-it","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"The scoring run is durable now, and this layer keeps one adapter inside it","x":"A multi-minute paid AI pass triggered from an HTTP POST used to be this chapter's biggest piece of machinery: a hosted BackgroundService draining an in-memory queue, plus a…"},"1368":{"u":"/docs/onboarding/group-19-conference-infrastructure.html#di-wiring-and-what-it-deliberately-no-longer-registers","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"DI wiring, and what it deliberately no longer registers","x":"DependencyInjection (MMCA.ADC.Conference.Infrastructure/DependencyInjection.cs:18) is a single extension(IServiceCollection) block (the codebase's standard DI-registration idiom,…"},"1369":{"u":"/docs/onboarding/group-19-conference-infrastructure.html#how-it-fits-together-at-runtime","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"How it fits together at runtime","x":"Three flows tie the chapter together. Persistence flow: a Conference command handler mutates an aggregate and the unit of work saves; that resolves the concrete…"},"1370":{"u":"/docs/onboarding/group-20-conference-api-grpc.html","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","x":"What this chapter covers. This is the edge of the Conference bounded context: the layer that turns the Conference domain (G17) and its CQRS slices (G18) into a running HTTP +…"},"1371":{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-controller-hierarchy-almost-everything-is-inherited","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The controller hierarchy, almost everything is inherited","x":"The Conference API exposes twenty-three controllers, and the striking thing about them is how little code each carries. They split into three structural families, all built on…"},"1372":{"u":"/docs/onboarding/group-20-conference-api-grpc.html#one-read-hook-per-controller-not-one-filter-per-action","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"One read hook per controller, not one filter per action","x":"The single most important thing to learn before editing any file here is where row scoping lives. It is not threaded through each action: the framework base declares one hook,…"},"1373":{"u":"/docs/onboarding/group-20-conference-api-grpc.html#authorization-at-the-edge-three-shapes-not-one","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"Authorization at the edge, three shapes not one","x":"Authorization is capability-based by default but not uniform, and the differences are the interesting part. Most write-bearing controllers carry a class-level…"},"1374":{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-request-records-the-inbound-write-shapes","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The request records, the inbound write shapes","x":"Several controllers declare small record class request types alongside themselves, co-located in the same file: AddRoomRequest and UpdateRoomRequest…"},"1375":{"u":"/docs/onboarding/group-20-conference-api-grpc.html#where-the-generic-shape-gives-way-filters-warnings-calendars-and-conditional-writes","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"Where the generic shape gives way: filters, warnings, calendars and conditional writes","x":"SessionsController shows best how a controller earns its overrides. Every read action is [AllowAnonymous] and [OutputCache(PolicyName = \"SessionsCache\")]…"},"1376":{"u":"/docs/onboarding/group-20-conference-api-grpc.html#two-more-deviations-versioning-and-decision-support","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"Two more deviations, versioning and decision support","x":"ServiceInfoController exists to prove the API-versioning machinery works beyond a single version ([Rubric §9, API & Contract Design]). It is a one-member shell over Common's…"},"1377":{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-module-entry-point-and-seeder-how-conference-plugs-in","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The module entry point and seeder, how Conference plugs in","x":"ConferenceModule is the Conference implementation of IModule. It is tiny by design: Register(...) calls the DependencyInjection extension's AddConferenceModule(...)…"},"1378":{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-grpc-edge-conference-as-both-server-and-client","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The gRPC edge, Conference as both server and client","x":"When Conference runs in its own process, two of its in-process collaborations must cross a network boundary, and both are handled by the G13 transport boundary (Result over the…"},"1379":{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-service-host-kestrel-first-then-caching-and-warm-up","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The service host: Kestrel first, then caching and warm-up","x":"The MMCA.ADC.Conference.Service Program.cs boots only the Conference module. Kestrel is configured before anything else, and the whole of it is one line:…"},"1380":{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-runtime-picture-one-host-two-transports","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The runtime picture, one host, two transports","x":"After module discovery (Program.cs:367-371) the host wires the Engagement gRPC client (:349), the broker (AddBrokerMessaging registering the UserRegistered integration-event…"},"1381":{"u":"/docs/onboarding/group-21-conference-ui.html","d":"21. ADC Conference - UI","k":"Onboarding Guide","x":"What this chapter covers. This is the consumer half of the \"write-once UI, render everywhere\" story (primer §2): the Blazor pages, page-local collaborators, and per-entity HTTP…"},"1382":{"u":"/docs/onboarding/group-21-conference-ui.html#the-layering-inside-the-ui-a-page-never-touches-httpclient","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"The layering inside the UI: a page never touches HttpClient","x":"Each page is a .razor + .razor.cs code-behind pair that depends on a UI service interface, never on HttpClient and never on the API's internals. The ten CRUD-shaped entities…"},"1383":{"u":"/docs/onboarding/group-21-conference-ui.html#the-list-pages-two-bases-one-recipe","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"The list pages: two bases, one recipe","x":"Twelve list screens hang off DataGridListPageBase , which supplies server-side paging against MudDataGrid , cancellation lifecycle, loading and load-failed state, filter and sort…"},"1384":{"u":"/docs/onboarding/group-21-conference-ui.html#detail-pages-form-models-and-one-declaration-of-the-rules","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Detail pages, form models, and one declaration of the rules","x":"The organizer detail pages have no ADC-local base: all nine inherit Common's DetailPageBase (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Common/DetailPageBase.cs:26)…"},"1385":{"u":"/docs/onboarding/group-21-conference-ui.html#container-and-presentational-split-and-page-local-collaborators","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Container and presentational split, and page-local collaborators","x":"The behaviour-heavy screens do not keep everything in one code-behind: the page stays the container (data fetching, filter and paging state, service calls) and hands rendering to…"},"1386":{"u":"/docs/onboarding/group-21-conference-ui.html#child-and-join-entities-a-thin-postdelete-base","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Child-and-join entities: a thin POST/DELETE base","x":"Sessions, speakers, and events own join relationships (a speaker added to a session, a category item to a speaker) that the generic CRUD base cannot model, because the write…"},"1387":{"u":"/docs/onboarding/group-21-conference-ui.html#display-enrichment-lookups-the-getall-vs-getbyid-populator-gap-worked-around-in-the-ui","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Display-enrichment lookups: the GetAll-vs-GetById populator gap, worked around in the UI","x":"Because the API's list endpoints do not always populate every cross-entity navigation, several pages need a cheap id-to-name map to render speaker names beside a session or an…"},"1388":{"u":"/docs/onboarding/group-21-conference-ui.html#three-feature-areas-that-go-beyond-crud","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Three feature areas that go beyond CRUD","x":"First, the speaker self-service dashboard. SpeakerDashboard sits behind a plain [Authorize] attribute and is gated on the speakerid JWT claim, showing the linked speaker's…"},"1389":{"u":"/docs/onboarding/group-21-conference-ui.html#session-materials-a-resource-deliberately-not-shaped-as-crud","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Session materials, a resource deliberately not shaped as CRUD","x":"Speakers publish the decks, handouts, and links that go with their own sessions, and anyone downloads them from the public session page (ADR-123, speaker session assets). This is…"},"1390":{"u":"/docs/onboarding/group-21-conference-ui.html#session-selection-decision-support-the-asynchronous-edge","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Session-selection decision support, the asynchronous edge","x":"The most behaviour-rich page is the organizer-only SessionSelectionDashboard (Pages/Sessions/Selection/SessionSelectionDashboard.razor.cs:16), which renders category…"},"1391":{"u":"/docs/onboarding/group-21-conference-ui.html#public-versus-authenticated-rendering-and-the-offline-first-path","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Public versus authenticated rendering, and the offline-first path","x":"A recurring [Rubric §11, Security] pattern: the same conference entity is exposed through two page families. No page under Pages/Public/ carries an @attribute [Authorize] at all,…"},"1392":{"u":"/docs/onboarding/group-21-conference-ui.html#sponsors-partners-and-activities-three-feature-areas-in-miniature","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Sponsors, partners, and activities, three feature areas in miniature","x":"The sponsor surface is a compact tour of every pattern above. Organizers manage the roster through SponsorList / SponsorCreate / SponsorDetail: the list is an…"},"1393":{"u":"/docs/onboarding/group-21-conference-ui.html#the-landing-page","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"The landing page","x":"ADCHome is the conference front door, shared by the web and MAUI heads; both keep their own copy of the editorial assets under wwwroot/images, so the default serves both and…"},"1394":{"u":"/docs/onboarding/group-21-conference-ui.html#routes-navigation-and-localized-strings","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Routes, navigation, and localized strings","x":"All paths are centralized in ConferenceRoutePaths, a static catalogue of literal routes and id-parameterized builder methods (EventDetails(id), PublicSessionDetails(id),…"},"1395":{"u":"/docs/onboarding/group-21-conference-ui.html#how-it-all-plugs-into-the-shell","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"How it all plugs into the shell","x":"Two registration types wire the area in. ConferenceUIModule implements Common's IUIModule (the front-end counterpart of the IModule back-end contract): it declares the module's…"},"1396":{"u":"/docs/onboarding/group-22-engagement-module.html","d":"22. ADC Engagement Module (Session Bookmarks)","k":"Onboarding Guide","x":"What this group covers. Engagement is the ADC bounded context that holds everything an attendee does at the conference beyond browsing the catalog. Four capability families live…"},"1397":{"u":"/docs/onboarding/group-23-engagement-live-layer.html","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","x":"What this chapter covers. This is the conference-day layer of the Engagement bounded context: the features that only matter while an event is actually happening in the room.…"},"1398":{"u":"/docs/onboarding/group-23-engagement-live-layer.html#the-two-aggregates-and-their-invariants","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","t":"The two aggregates and their invariants","x":"Both aggregates are sealed AuditableAggregateRootEntity subclasses that follow the framework's factory-plus-Result discipline (primer §2). LivePoll holds an EventId, an optional…"},"1399":{"u":"/docs/onboarding/group-23-engagement-live-layer.html#the-write-path-and-where-the-realtime-broadcast-actually-happens","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","t":"The write path, and where the realtime broadcast actually happens","x":"Each operation is a vertical slice under Application/{LivePollsSessionQuestions}/UseCases/{Op}/. The three lifecycle commands do not hand-roll their load-mutate-save sequence at…"},"1400":{"u":"/docs/onboarding/group-23-engagement-live-layer.html#one-websocket-one-publisher-port-and-a-cross-service-ingress","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","t":"One WebSocket, one publisher port, and a cross-service ingress","x":"The transport itself is framework-owned (ADR-039, Group 10). The single NotificationHub carries both durable notifications and channel events on one connection, and the…"},"1401":{"u":"/docs/onboarding/group-23-engagement-live-layer.html#the-read-path-and-how-the-ui-reacts","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","t":"The read path and how the UI reacts","x":"Reads do not go through the generic entity-query machinery; the live views need shaped projections. LivePollResultsBuilder…"},"1402":{"u":"/docs/onboarding/group-23-engagement-live-layer.html#authorization-feature-gating-and-the-cross-service-dependency-on-conference","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","t":"Authorization, feature gating, and the cross-service dependency on Conference","x":"Both controllers, LivePollsController (MMCA.ADC.Engagement.API/Controllers/LivePollsController.cs:42) and SessionQuestionsController…"},"1403":{"u":"/docs/onboarding/group-24-identity-module.html","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","x":"What this chapter covers. This is the Identity bounded context of MMCA.ADC, the module that owns who a person is across every ADC surface: web, WebAssembly, and MAUI. It is a…"},"1404":{"u":"/docs/onboarding/group-24-identity-module.html#projects-one-bounded-context","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Projects, one bounded context","x":"The module is split along the standard Clean Architecture layering ([Rubric §3, Clean Architecture]), each project pinned by a trivial AssemblyReference / ClassReference anchor…"},"1405":{"u":"/docs/onboarding/group-24-identity-module.html#the-user-aggregate-credentials-profile-and-cross-context-links-in-one-root","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"The User aggregate: credentials, profile, and cross-context links in one root","x":"User (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:34) is the only aggregate root in the module, and it carries more responsibility than most: it is…"},"1406":{"u":"/docs/onboarding/group-24-identity-module.html#authentication-a-thin-subclass-over-the-shared-engine","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Authentication: a thin subclass over the shared engine","x":"The login / registration / refresh / revocation workflow is not re-implemented here. It lives in AuthenticationServiceBase (G08), which owns the validate-first flow, the lockout…"},"1407":{"u":"/docs/onboarding/group-24-identity-module.html#password-recovery-the-anonymous-half-of-the-credential-lifecycle","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Password recovery: the anonymous half of the credential lifecycle","x":"PUT /Auth/password only serves a user who can already sign in. The recovery pair that serves one who cannot is a second, anonymous vertical, and it is assembled the same way: two…"},"1408":{"u":"/docs/onboarding/group-24-identity-module.html#email-confirmation-proving-the-address-is-reachable","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Email confirmation: proving the address is reachable","x":"ADC asks a new account to prove its address, and deliberately does not gate sign-in on the answer: Authentication:EmailConfirmation:RequireConfirmedEmail is false…"},"1409":{"u":"/docs/onboarding/group-24-identity-module.html#administration-accounts-roles-and-the-capability-map","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Administration: accounts, roles, and the capability map","x":"The organizer-facing administration surface is a framework surface with ADC vocabulary plugged into it. UsersAdminController (UsersAdminController.cs:25) subclasses the framework…"},"1410":{"u":"/docs/onboarding/group-24-identity-module.html#the-privacy-pair-export-and-erasure","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"The privacy pair: export and erasure","x":"Two use cases make this module the codebase's clearest [Rubric §30, Compliance / Privacy / Data Governance] story, and both are thin ADC specializations of a G14 base. The…"},"1411":{"u":"/docs/onboarding/group-24-identity-module.html#avatars-the-third-mutating-slice","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Avatars: the third mutating slice","x":"The avatar trio is a small but complete example of a file-handling slice ([Rubric §11, Security] at the content boundary, ADR-045). UsersController caps the upload in two places:…"},"1412":{"u":"/docs/onboarding/group-24-identity-module.html#persistence-seeding-and-the-disabled-stub","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Persistence, seeding, and the disabled stub","x":"ModuleApplicationDbContext (ModuleApplicationDbContext.cs:15) is the abstract, engine-agnostic context declaring the single Users set (:22); the concrete per-engine class…"},"1413":{"u":"/docs/onboarding/group-24-identity-module.html#crossing-the-service-boundary-grpc-and-integration-events","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Crossing the service boundary: gRPC and integration events","x":"Identity talks to its peers two ways, and both live in Shared and Contracts so neither side reaches into the other's domain ([Rubric §7, Microservices Readiness]). Synchronously,…"},"1414":{"u":"/docs/onboarding/group-24-identity-module.html#the-ui-edge","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"The UI edge","x":"The Blazor surface is registered as an IdentityUIModule (MMCA.ADC.Identity.UI/IdentityUIModule.cs:15), an IUIModule descriptor that contributes three NavItems as resource keys,…"},"1415":{"u":"/docs/onboarding/group-24-identity-module.html#end-to-end-one-registration","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"End-to-end: one registration","x":"To see the chapter cooperate, follow a new attendee signing up. AuthController receives the register POST, captures the client IP for BR-213 rate limiting and the user-agent for…"},"1416":{"u":"/docs/onboarding/group-25-adc-host-composition.html","d":"25. ADC Application Host, UI Shell & Cross-Module Composition","k":"Onboarding Guide","x":"What this chapter covers. Every ADC module described so far (Conference, Engagement, Identity, Notification) is consumed somewhere. This chapter is that somewhere: the client…"},"1417":{"u":"/docs/onboarding/group-26-device-capability-layer.html","d":"26. Device Capability Abstraction Layer (Native Contracts, MAUI, Browser & Fallback Adapters)","k":"Onboarding Guide","x":"What this chapter covers. One Blazor component library in MMCA.Common.UI renders on three very different heads: Blazor Server (server-side prerender plus interactive Server…"},"1418":{"u":"/docs/onboarding/group-27-common-ai-integration.html","d":"27. Common AI Integration","k":"Onboarding Guide","x":"What this chapter covers. This group is the framework's language-model boundary: the optional MMCA.Common.AI package that turns a call to an LLM provider into an ordinary,…"},"1419":{"u":"/docs/onboarding/group-27-common-ai-integration.html#the-configuration-surface-is-the-contract","d":"27. Common AI Integration","k":"Onboarding Guide","t":"The configuration surface is the contract","x":"AiSettings (MMCA.Common/Source/Core/MMCA.Common.AI/AiSettings.cs:22) binds from the Ai section (AiSettings.cs:25) and holds every knob the package has: Enabled (:39), Provider…"},"1420":{"u":"/docs/onboarding/group-27-common-ai-integration.html#the-provider-is-a-registered-factory-not-a-type-the-package-names","d":"27. Common AI Integration","k":"Onboarding Guide","t":"The provider is a registered factory, not a type the package names","x":"IAiProviderFactory (MMCA.Common/Source/Core/MMCA.Common.AI/Providers/IAiProviderFactory.cs:20) is the whole provider boundary: a Name a configuration file selects it by (:26) and…"},"1421":{"u":"/docs/onboarding/group-27-common-ai-integration.html#one-entry-point-one-pipeline-outermost-first","d":"27. Common AI Integration","k":"Onboarding Guide","t":"One entry point, one pipeline, outermost first","x":"AiServiceCollectionExtensions (MMCA.Common/Source/Core/MMCA.Common.AI/DependencyInjection.cs:77) is the only composition surface. It exposes two AddMmcaChatClient overloads…"},"1422":{"u":"/docs/onboarding/group-27-common-ai-integration.html#the-outer-layer-what-a-call-is-allowed-to-do","d":"27. Common AI Integration","k":"Onboarding Guide","t":"The outer layer: what a call is allowed to do","x":"BoundedChatClient (MMCA.Common/Source/Core/MMCA.Common.AI/Chat/BoundedChatClient.cs:65) is a DelegatingChatClient that applies five bounds (output tokens, wall clock, tool use,…"},"1423":{"u":"/docs/onboarding/group-27-common-ai-integration.html#the-middle-layer-what-the-call-may-say-and-what-it-may-answer","d":"27. Common AI Integration","k":"Onboarding Guide","t":"The middle layer: what the call may say, and what it may answer","x":"Bounds are configuration, so the framework can decide them. Content is not, so it does not. IChatGuardrail (MMCA.Common/Source/Core/MMCA.Common.AI/Chat/IChatGuardrail.cs:21) is…"},"1424":{"u":"/docs/onboarding/group-27-common-ai-integration.html#the-two-content-policies-the-framework-does-ship","d":"27. Common AI Integration","k":"Onboarding Guide","t":"The two content policies the framework does ship","x":"PiiRedactionGuardrail (MMCA.Common/Source/Core/MMCA.Common.AI/Guardrails/PiiRedactionGuardrail.cs:39) is the stated exception to \"extension points and no policy\": an email…"},"1425":{"u":"/docs/onboarding/group-27-common-ai-integration.html#the-inner-layer-what-the-call-actually-cost","d":"27. Common AI Integration","k":"Onboarding Guide","t":"The inner layer: what the call actually cost","x":"UsageRecordingChatClient (MMCA.Common/Source/Core/MMCA.Common.AI/Chat/UsageRecordingChatClient.cs:38) sits inside the guardrails and reports the provider's own numbers. On the…"},"1426":{"u":"/docs/onboarding/group-27-common-ai-integration.html#the-prompt-is-a-versioned-artifact","d":"27. Common AI Integration","k":"Onboarding Guide","t":"The prompt is a versioned artifact","x":"PromptContract (MMCA.Common/Source/Core/MMCA.Common.AI/PromptContract.cs:23) is a sealed record of the four things that decide what a model will answer: Name, Version, Model and…"},"1427":{"u":"/docs/onboarding/group-27-common-ai-integration.html#where-it-runs-today-and-how-it-is-tested","d":"27. Common AI Integration","k":"Onboarding Guide","t":"Where it runs today, and how it is tested","x":"Exactly one feature in the workspace calls a model: ADC's organizer-facing session scoring. The Conference service host reads one secret, Ai:ApiKey, and derives Ai:Enabled from…"},"1428":{"u":"/docs/onboarding/group-28-testing-infrastructure.html","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","x":"What this group covers. Everything the codebase uses to prove itself: the five reusable test-support packages that ship out of MMCA.Common/Source/Hosting (MMCA.Common.Testing,…"},"1429":{"u":"/docs/onboarding/group-28-testing-infrastructure.html#integration-tests-a-real-host-a-throwaway-database-a-per-test-reset","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Integration tests: a real host, a throwaway database, a per-test reset","x":"The integration tier boots the actual application, not a mock of it. The abstraction at its center is IIntegrationTestFixture…"},"1430":{"u":"/docs/onboarding/group-28-testing-infrastructure.html#orchestration-smoke-tests-booting-a-real-apphost","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Orchestration smoke tests: booting a real AppHost","x":"Every tier above boots hosts directly, which means none of them ever runs the file that states how a whole stack fits together. The AppHost declares the project resources, the…"},"1431":{"u":"/docs/onboarding/group-28-testing-infrastructure.html#architecture-fitness-functions-rules-that-gate-the-build","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Architecture fitness functions: rules that gate the build","x":"The layering and DDD conventions this codebase commits to are not left to code review, they are executed as tests. The reusable rule library lives in…"},"1432":{"u":"/docs/onboarding/group-28-testing-infrastructure.html#component-tests-real-mudblazor-faked-edges","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Component tests: real MudBlazor, faked edges","x":"The bUnit tier renders a single Blazor component in-process with its real dependencies but stubbed network and auth. BunitComponentTestBase…"},"1433":{"u":"/docs/onboarding/group-28-testing-infrastructure.html#end-to-end-tests-a-real-browser-accessibility-and-performance-as-gates","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"End-to-end tests: a real browser, accessibility and performance as gates","x":"The E2E tier drives a real browser through Playwright. PlaywrightFixture (MMCA.Common.Testing.E2E/Infrastructure/PlaywrightFixture.cs:6) is an xUnit collection fixture (its…"},"1434":{"u":"/docs/onboarding/group-28-testing-infrastructure.html#the-gallery-harness","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"The Gallery harness","x":"Component and E2E coverage of MMCA.Common's own UI needs a page to render, but the framework is not a runnable app. MMCA.Common.UI.Gallery is a deliberately backend-less Blazor…"},"1435":{"u":"/docs/onboarding/group-28-testing-infrastructure.html#evaluating-a-nondeterministic-dependency-the-ai-scoring-golden-corpus","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Evaluating a nondeterministic dependency: the AI-scoring golden corpus","x":"Every tier above assumes a deterministic system under test. ADC's session-scoring judge is not one: it asks a hosted model for a structured score, and the same prompt can return…"},"1436":{"u":"/docs/onboarding/group-28-testing-infrastructure.html#contract-pipeline-and-benchmark-bases","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Contract, pipeline, and benchmark bases","x":"The last family pins guarantees that live in the composition of the stack rather than in any one type, and it is the subject of ADR-058: these suites ship in MMCA.Common.Testing…"},"1437":{"u":"/docs/onboarding/group-28-testing-infrastructure.html#per-project-test-rollup","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Per-project test rollup","x":"The remaining test types in G25 are logged here by project rather than as individual sections (per the TESTS note): each project below is a Microsoft Testing Platform / xUnit v3…"},"1438":{"u":"/docs/onboarding/group-28-testing-infrastructure.html#test-suites-by-module","d":"28. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Test suites by module","x":"--- ⬅ Common AI Integration • Index • Coverage audit ➡"},"1439":{"u":"/docs/onboarding/devops-aspire.html","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","x":"This chapter teaches how the MMCA.ADC system goes from a single dotnet run on your workstation to a running stack of six .NET processes plus four containers: databases, a broker,…"},"1440":{"u":"/docs/onboarding/devops-aspire.html#the-one-command-local-run","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"The one-command local run","x":"That command brings up everything the application needs locally: four SQL Server databases, Redis, RabbitMQ with management UI, a MailDev SMTP interceptor, four extracted…"},"1441":{"u":"/docs/onboarding/devops-aspire.html#mmcaadcapphost-the-orchestration-project","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"MMCA.ADC.AppHost, the orchestration project","x":"Source file: MMCA.ADC/Source/Hosting/MMCA.ADC.AppHost/Program.cs AppHost-local helper: MMCA.ADC/Source/Hosting/MMCA.ADC.AppHost/BrokerSelection.cs Framework extension helpers:…"},"1442":{"u":"/docs/onboarding/devops-aspire.html#where-service-defaults-come-from-mmcacommonaspire-not-a-local-project","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"Where service defaults come from, MMCA.Common.Aspire, not a local project","x":"There is no MMCA.ADC.ServiceDefaults project. Source/Hosting/ contains the AppHost and the four per-service migrations assemblies, nothing else. The conventional Aspire…"},"1443":{"u":"/docs/onboarding/devops-aspire.html#mmcacommonaspire-the-framework-service-defaults-package","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"MMCA.Common.Aspire, the framework service-defaults package","x":"Source: MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs Tags: .../HealthCheckTags.cs Telemetry: .../Telemetry/OutboxPollFilterProcessor.cs Security:…"},"1444":{"u":"/docs/onboarding/devops-aspire.html#mmcacommonaspirehosting-the-apphost-extensions-package","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"MMCA.Common.Aspire.Hosting, the AppHost extensions package","x":"Source: MMCA.Common/Source/Hosting/MMCA.Common.Aspire.Hosting/ Files: Extensions.cs, H2cHealthCheckExtensions.cs, H2cEndpointHealthCheck.cs, ServiceBusEmulatorResource.cs This…"},"1445":{"u":"/docs/onboarding/devops-aspire.html#the-six-dockerfiles","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"The six Dockerfiles","x":"All six Dockerfiles share the same multi-stage structure (base, build, publish, final) and the same base images. None build the AppHost: it is a local-only orchestration…"},"1446":{"u":"/docs/onboarding/devops-aspire.html#local-to-cloud-parity","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"Local-to-cloud parity","x":"The AppHost topology maps directly to the Azure infrastructure provisioned by infra/main.bicep. The table below cross-references the local resource with its Azure equivalent: The…"},"1447":{"u":"/docs/onboarding/devops-aspire.html#the-yarp-gateways-role","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"The YARP Gateway's role","x":"The gateway (Source/Hosts/MMCA.ADC.Gateway) is a pure YARP reverse proxy. It has no DbContext, no ModuleLoader, no REST controllers, and no broker connection. Its Program.cs is…"},"1448":{"u":"/docs/onboarding/devops-aspire.html#startup-ordering-summary","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"Startup ordering summary","x":"The health-based WaitFor chain imposes this ordering. Two things shape it that are not visible at the call site: three of the four services wait on Identity without any explicit…"},"1449":{"u":"/docs/onboarding/devops-aspire.html#the-apphost-composition-smoke-test","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"The AppHost composition smoke test","x":"MMCA.ADC/Tests/Integration/MMCA.ADC.AppHost.SmokeTests/AdcAppHostFixture.cs, MMCA.ADC/Tests/Integration/MMCA.ADC.AppHost.SmokeTests/AdcAppHostSmokeTests.cs Everything above is…"},"1450":{"u":"/docs/onboarding/devops-aspire.html#rubric-category-index-for-this-chapter","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"Rubric category index for this chapter","x":"---"},"1451":{"u":"/docs/onboarding/devops-aspire.html#not-determinable-from-source","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"Not determinable from source","x":"- The specific integration events that flow over the broker (UserRegistered, SpeakerLinkedToUser, SpeakerUnlinkedFromUser) are cited from AppHost inline comments…"},"1452":{"u":"/docs/onboarding/devops-cicd.html","d":"CI/CD and Operations","k":"Onboarding Guide","x":"This chapter walks the GitHub Actions workflows that govern MMCA, from the framework's continuous integration and lockstep NuGet release in MMCA.Common, through the ADC…"},"1453":{"u":"/docs/onboarding/devops-cicd.html#mmcacommon-ciyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.Common, ci.yml","x":"File: MMCA.Common/.github/workflows/ci.yml The continuous-integration workflow for the MMCA.Common framework. Because the published packages (the authoritative id list and count…"},"1454":{"u":"/docs/onboarding/devops-cicd.html#mmcacommon-releaseyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.Common, release.yml","x":"File: MMCA.Common/.github/workflows/release.yml The lockstep NuGet release workflow. When a maintainer pushes a vX.Y.Z git tag, this workflow deterministically derives the…"},"1455":{"u":"/docs/onboarding/devops-cicd.html#mmcaadc-deployyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.ADC, deploy.yml","x":"File: MMCA.ADC/.github/workflows/deploy.yml The primary CI/CD pipeline for the Atlanta Developers Conference application. It runs on every push to main, on every pull request…"},"1456":{"u":"/docs/onboarding/devops-cicd.html#mmcaadc-e2eyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.ADC, e2e.yml","x":"File: MMCA.ADC/.github/workflows/e2e.yml The full-stack Playwright E2E test workflow. It brings up the complete Aspire stack (SQL Server + Redis + RabbitMQ + four services +…"},"1457":{"u":"/docs/onboarding/devops-cicd.html#mmcaadc-cost-guardyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.ADC, cost-guard.yml","x":"File: MMCA.ADC/.github/workflows/cost-guard.yml A read-only FinOps check that confirms the production Azure footprint is at its cost baseline. It detects a specific operational…"},"1458":{"u":"/docs/onboarding/devops-cicd.html#mmcaadc-load-testyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.ADC, load-test.yml","x":"File: MMCA.ADC/.github/workflows/load-test.yml A k6 load test targeting the output-cached Conference read endpoints through the production Gateway. It establishes a repeatable…"},"1459":{"u":"/docs/onboarding/devops-cicd.html#repository-automation-the-claude-review-pair-and-the-maui-audit","d":"CI/CD and Operations","k":"Onboarding Guide","t":"Repository automation: the Claude review pair and the MAUI audit","x":"Files: MMCA.Common/.github/workflows/claude.yml, MMCA.Common/.github/workflows/claude-code-review.yml, MMCA.ADC/.github/workflows/claude.yml,…"},"1460":{"u":"/docs/onboarding/devops-cicd.html#cross-workflow-summary","d":"CI/CD and Operations","k":"Onboarding Guide","t":"Cross-workflow summary","x":"(dr-drill.yml is the ADR-009 §29 restore drill: it PITR-restores a copy of a chosen database, times the restore for the RTO record, verifies it comes back Online, then deletes…"},"1461":{"u":"/docs/onboarding/devops-cicd.html#rubric-category-index-for-this-chapter","d":"CI/CD and Operations","k":"Onboarding Guide","t":"Rubric category index for this chapter"},"1462":{"u":"/docs/onboarding/devops-iac.html","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","x":"This chapter teaches the Azure Infrastructure-as-Code layer for the MMCA.ADC application: what resources are provisioned, why they are shaped the way they are, how secrets reach…"},"1463":{"u":"/docs/onboarding/devops-iac.html#how-the-pieces-fit-together","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"How the pieces fit together","x":"Before diving into individual files, here is the end-to-end picture: Phases 1 and 2 are their own jobs (deploy.yml:1089, deploy.yml:1143) rather than steps inside deploy, so they…"},"1464":{"u":"/docs/onboarding/devops-iac.html#azureyaml-the-azd-project-definition","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"azure.yaml, the azd project definition","x":"File: MMCA.ADC/azure.yaml azure.yaml is the Azure Developer CLI (azd) manifest for the project. It declares six deployable services and points azd at the Bicep infrastructure…"},"1465":{"u":"/docs/onboarding/devops-iac.html#infrafoundationbicep-long-lived-shared-infrastructure","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"infra/foundation.bicep, long-lived shared infrastructure","x":"File: MMCA.ADC/infra/foundation.bicep Foundation is deployed first (CI/CD chapter: deploy.yml:1115-1121) on every run. It provisions three resources: the Azure Container…"},"1466":{"u":"/docs/onboarding/devops-iac.html#deployment-parameters-assembled-at-deploy-time-not-committed","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"Deployment parameters, assembled at deploy time, not committed","x":"There is no infra/main.parameters.json file in the repository, the tracked infra/ directory holds only foundation.bicep, main.bicep, DISASTER-RECOVERY.md, OPERATIONS.md,…"},"1467":{"u":"/docs/onboarding/devops-iac.html#inframainbicep-the-full-application-infrastructure","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"infra/main.bicep, the full application infrastructure","x":"File: MMCA.ADC/infra/main.bicep main.bicep declares every application-layer Azure resource: Application Insights, five SLO scheduled query rules (one of them the AI-scoring token…"},"1468":{"u":"/docs/onboarding/devops-iac.html#deployment-model-summary","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"Deployment model summary","x":"The complete credential chain: No static credential exists at any link in this chain. The GitHub secrets AZURECLIENTID, AZURETENANTID, AZURESUBSCRIPTIONID are the OIDC…"},"1469":{"u":"/docs/onboarding/devops-iac.html#rubric-category-cross-reference","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"Rubric category cross-reference","x":"---"},"1470":{"u":"/docs/onboarding/devops-iac.html#not-determinable-from-source","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"Not determinable from source","x":"- The exact AcrPull and Key Vault Secrets User role-assignment commands used in the out-of- band bootstrap are referenced in comments (main.bicep:1045-1049, main.bicep:1063-1066)…"},"1471":{"u":"/docs/onboarding/devops-runbooks.html","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","x":"This chapter covers every operational script and runbook in MMCA.ADC: the one-time Azure bootstrap, the database-per-service cutover story (how the legacy AtlDevCon monolith DB…"},"1472":{"u":"/docs/onboarding/devops-runbooks.html#azure-setupsh-one-time-azure-bootstrap","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"azure-setup.sh, One-time Azure bootstrap","x":"File: MMCA.ADC/scripts/azure-setup.sh (175 lines) What it is. A bash script that creates every Azure identity and OIDC credential the GitHub Actions deploy pipeline needs. It is…"},"1473":{"u":"/docs/onboarding/devops-runbooks.html#the-database-per-service-cutover-and-why-its-scripts-are-gone","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"The database-per-service cutover, and why its scripts are gone","x":"Before the surviving artifacts make sense, the story behind them does. Before ADR-006. All four modules (Identity, Conference, Engagement, Notification) pointed at a single…"},"1474":{"u":"/docs/onboarding/devops-runbooks.html#infradisaster-recoverymd-dr-runbook","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"infra/DISASTER-RECOVERY.md, DR runbook","x":"File: MMCA.ADC/infra/DISASTER-RECOVERY.md (234 lines; not the Store file of the same name) What it is. The authoritative disaster-recovery runbook for the ADC production…"},"1475":{"u":"/docs/onboarding/devops-runbooks.html#dr-drillyml-and-dr-restore-drillps1-the-adr-009-restore-drill","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"dr-drill.yml and dr-restore-drill.ps1, the ADR-009 restore drill","x":"Files: MMCA.ADC/.github/workflows/dr-drill.yml (120 lines), MMCA.ADC/scripts/dr-restore-drill.ps1 (101 lines) What it is. The automation behind the drill requirement above: the…"},"1476":{"u":"/docs/onboarding/devops-runbooks.html#infraoperationsmd-day-2-alert-triage-runbook","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"infra/OPERATIONS.md, day-2 alert triage runbook","x":"File: MMCA.ADC/infra/OPERATIONS.md (207 lines) What it is. The alert-to-action companion to the provisioned observability: what to do when each alert fires, how to read the SLO…"},"1477":{"u":"/docs/onboarding/devops-runbooks.html#infrasql-managed-identitymd-staged-passwordless-sql-runbook","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"infra/SQL-MANAGED-IDENTITY.md, staged passwordless-SQL runbook","x":"File: MMCA.ADC/infra/SQL-MANAGED-IDENTITY.md (100 lines) What it is. The runbook for moving the four service apps from SQL-login (password) auth to Entra managed-identity auth…"},"1478":{"u":"/docs/onboarding/devops-runbooks.html#infrapost-cutover-atldevcon-downgrademd-archive-downgrade-and-drop-runbook","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"infra/POST-CUTOVER-atldevcon-downgrade.md, archive downgrade and drop runbook","x":"File: MMCA.ADC/infra/POST-CUTOVER-atldevcon-downgrade.md (128 lines) What it is. The record of what happened to the legacy AtlDevCon database after the database-per-service…"},"1479":{"u":"/docs/onboarding/devops-runbooks.html#play-store-captureps1-android-screenshot-capture","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"play-store-capture.ps1, Android screenshot capture","x":"File: MMCA.ADC/scripts/play-store-capture.ps1 (147 lines) What it is. A PowerShell 7 script (play-store-capture.ps1:1) that captures a screenshot from an attached Android device…"},"1480":{"u":"/docs/onboarding/devops-runbooks.html#play-store-composeps1-play-store-screenshot-compositor","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"play-store-compose.ps1, Play Store screenshot compositor","x":"File: MMCA.ADC/scripts/play-store-compose.ps1 (187 lines) What it is. A PowerShell 7 script that reads raw captures from store-assets/play-store/raw/, wraps each into a 1080x1920…"},"1481":{"u":"/docs/onboarding/devops-runbooks.html#docsmobilereleaserunbookmd-store-submission-runbook","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"Docs/MobileReleaseRunbook.md, store-submission runbook","x":"File: MMCA.ADC/Docs/MobileReleaseRunbook.md (205 lines) What it is. The manual, credential-holding steps around a store submission that code and CI cannot perform, each tagged…"},"1482":{"u":"/docs/onboarding/devops-runbooks.html#the-operational-artifacts-in-full-context","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"The operational artifacts in full context","x":"The surviving artifacts form one story that starts at bootstrap and ends at day-2 triage: The AtlDevCon database is the thread that runs through the database half of that table,…"},"1483":{"u":"/docs/onboarding/devops-runbooks.html#rubric-tag-summary","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"Rubric tag summary","x":"---"},"1484":{"u":"/docs/onboarding/devops-runbooks.html#not-determinable-from-source","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"Not determinable from source","x":"- Whether the newest weekly drill is green: the drill ledger is maintained through 2026-08-10 (DISASTER-RECOVERY.md:210-217), but dr-drill.yml runs every Monday and a run reaches…"},"1485":{"u":"/docs/onboarding/devops-testing.html","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","x":"Chapter scope note. The tier chapters (tier-00 through the sweep) document every type in the production codebase one by one. Test types are the logged exception: this chapter…"},"1486":{"u":"/docs/onboarding/devops-testing.html#1-solution-composition-and-the-test-runner","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"1. Solution composition and the test runner","x":"The two deployed apps use the same two-file pattern; MMCA.Common and MMCA.Helpdesk ship a .slnx only, because their solutions are already fast enough not to need a CI subset:…"},"1487":{"u":"/docs/onboarding/devops-testing.html#2-test-project-layout","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"2. Test project layout","x":"The inventory below is drawn from 00-inventory.md:25-135 (test-assembly counts) and the solution files above. Counts are distinct types per project as reported by the Roslyn…"},"1488":{"u":"/docs/onboarding/devops-testing.html#3-shipped-testing-infrastructure-packages","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"3. Shipped testing-infrastructure packages","x":"MMCA.Common ships five of its twenty-two packages as general testing infrastructure that downstream apps consume as NuGet references rather than writing their own harness…"},"1489":{"u":"/docs/onboarding/devops-testing.html#4-architecture-fitness-tests-executable-governance","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"4. Architecture fitness tests, executable governance","x":"[Rubric §34, Architecture Governance & Documentation]: §34 assesses whether architectural decisions are documented, enforced, and kept honest over time; fitness functions are the…"},"1490":{"u":"/docs/onboarding/devops-testing.html#5-integration-and-e2e-strategy","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"5. Integration and E2E strategy","x":"The four integration test projects (Identity, Conference, Engagement, Notification) each boot their service in-process with WebApplicationFactory . The lifecycle is not written…"},"1491":{"u":"/docs/onboarding/devops-testing.html#6-worked-examples","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"6. Worked examples","x":"Three examples tie the infrastructure above to real test code. The per-repo class is a bare subclass; the facts, the package lists and the parsing live once in the shared base:…"},"1492":{"u":"/docs/onboarding/devops-testing.html#7-the-tiers-and-the-gates-that-run-them","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"7. The tiers and the gates that run them","x":"A test tier only means something once you know what it blocks. This is the map. MMCA.Common's ui-e2e job (MMCA.Common/.github/workflows/ci.yml:248) builds the out-of-slnx gallery…"},"1493":{"u":"/docs/onboarding/devops-testing.html#quick-reference-rubric-categories-touched-in-this-chapter","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"Quick reference: rubric categories touched in this chapter","x":"---"},"1494":{"u":"/docs/onboarding/devops-testing.html#cross-links","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"Cross-links","x":"- Primer: 00-primer.md5-the-solution--test-layout , solution files, MTP runner, slnx-excluded UI projects - Primer:…"},"1495":{"u":"/docs/onboarding/99-coverage-audit.html","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","x":"This audit reconciles the written guide against the mechanically-extracted inventory, logs every deliberate exception, verifies the grouping/ordering rules, proves all 34 rubric…"},"1496":{"u":"/docs/onboarding/99-coverage-audit.html#1-coverage-reconciliation","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"1. Coverage reconciliation","x":"Cross-check result: verify.ps1 confirms 0 of the 2,624 individually-sectioned types are missing from their group chapter, every one appears as a heading or in a sibling-family…"},"1497":{"u":"/docs/onboarding/99-coverage-audit.html#2-exceptions-log-every-deliberate-omission-with-reason","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"2. Exceptions log (every deliberate omission, with reason)","x":"EF Core migrations (/Migrations/, .Migrations.SqlServer), ModelSnapshot, .Designer.cs, .g.cs, GlobalUsings.g.cs, and AssemblyInfo.cs are excluded by rule (Tools/invtool…"},"1498":{"u":"/docs/onboarding/99-coverage-audit.html#3-grouping--ordering-verification","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"3. Grouping & ordering verification","x":"- Every type in exactly one group. classify.ps1 assigns all 3,668 nodes via name-level overrides (for the grab-bag MMCA.Common.Interfaces/Services namespaces) + ordered…"},"1499":{"u":"/docs/onboarding/99-coverage-audit.html#4-rubric-coverage-matrix","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"4. Rubric coverage matrix","x":"Every one of the 34 categories is explained at least once against real code. \"First explained in\" is the earliest group chapter (by order) that tags it; many recur and several…"},"1500":{"u":"/docs/onboarding/99-coverage-audit.html#5-open-questions--not-determinable-from-source","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"5. Open questions / not determinable from source","x":"1. IDbSeeder host invocation (group-07). The seeding contract and implementations are in MMCA.Common, but the IHostedService/startup invoker that actually runs seeding at boot…"},"1501":{"u":"/docs/onboarding/99-coverage-audit.html#6-how-to-regenerate-this-audit","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"6. How to regenerate this audit","x":"Then copy the refreshed out/00-inventory.md and out/00-dependency-manifest.md into Docs/Onboarding/ (the 00-group-taxonomy.md is written there directly by classify.ps1)."},"1502":{"u":"/docs/onboarding/CONCEPT-MAPS.html","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","x":"Mermaid diagrams distilled from the Onboarding guide (primer, group taxonomy, dependency manifest, and the 28 group chapters). Each diagram captures a relationship between the…"},"1503":{"u":"/docs/onboarding/CONCEPT-MAPS.html#1-system-context-two-codebases--the-22-packages","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"1. System context, two codebases + the 22 packages","x":"MMCA.Common is a framework published as twenty-two NuGet packages in lockstep, to nuget.org and GitHub Packages from one tag (ADR-053); MMCA.ADC and MMCA.Store consume them.…"},"1504":{"u":"/docs/onboarding/CONCEPT-MAPS.html#2-clean-architecture-the-layered-dependency-rule","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"2. Clean Architecture, the layered dependency rule","x":"Source dependencies point inward toward the Domain; each layer references only layers below it. Deliberate exceptions: UI and Grpc depend on Shared only (UI for Blazor WASM…"},"1505":{"u":"/docs/onboarding/CONCEPT-MAPS.html#3-the-28-functional-groups-dependency--build-order","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"3. The 28 functional groups, dependency / build order","x":"The primary axis of the guide: every type lives in exactly one of 28 chapter groups, ordered roughly topologically. Foundational, widely-depended-on concerns first (Result →…"},"1506":{"u":"/docs/onboarding/CONCEPT-MAPS.html#4-core-framework-patterns-how-the-building-blocks-compose","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"4. Core framework patterns, how the building blocks compose","x":"The pattern-level view of the same backbone: the ideas the primer commits to and how they feed each other. Result is the pervasive currency; DDD blocks produce domain events;…"},"1507":{"u":"/docs/onboarding/CONCEPT-MAPS.html#5-request-lifecycle-the-cqrs-decorator-pipeline-adr-014","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"5. Request lifecycle, the CQRS decorator pipeline (ADR-014)","x":"Handlers are thin (one method); every cross-cutting concern is a decorator wrapping the next. Scrutor TryDecorate composes them in reverse registration order (last registered =…"},"1508":{"u":"/docs/onboarding/CONCEPT-MAPS.html#6-event-driven-integration-outbox-dual-dispatch-adr-003--010--021--100","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"6. Event-driven integration, outbox dual-dispatch (ADR-003 / 010 / 021 / 100)","x":"Domain events are captured into an OutboxMessage row in the same transaction as the data (no dual-write bug). The two event kinds then part ways: local domain events are…"},"1509":{"u":"/docs/onboarding/CONCEPT-MAPS.html#7-modular-monolith--extractable-services-adr-006--007--008--012","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"7. Modular monolith → extractable services (ADR-006 / 007 / 008 / 012)","x":"Modules implement IModule and are discovered + Kahn-ordered by ModuleLoader (ADR-059). The same module code runs as a single monolith host or as N service processes behind a YARP…"},"1510":{"u":"/docs/onboarding/CONCEPT-MAPS.html#8-persistence-database-per-service--polyglot-engines-adr-006--018--030","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"8. Persistence, database-per-service + polyglot engines (ADR-006 / 018 / 030)","x":"One sealed context class per engine (SQLServerDbContext, PostgreSQLDbContext, SqliteDbContext, CosmosDbContext) over the abstract ApplicationDbContext, one instance per database.…"},"1511":{"u":"/docs/onboarding/CONCEPT-MAPS.html#9-authentication--authorization-stack","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"9. Authentication & Authorization stack","x":"The auth concern (G08) spans token validation, session cookies, federated sign-in, password hashing, brute-force protection, per-device refresh-session rotation and revocation, a…"},"1512":{"u":"/docs/onboarding/CONCEPT-MAPS.html#10-notifications-three-channels-behind-one-send-pipeline-adr-024--044","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"10. Notifications, three channels behind one send pipeline (ADR-024 / 044)","x":"One use case (SendPushNotificationHandler) writes a durable per-user inbox (optionally scoped to an event via ScopeKey), fires a transient SignalR push, and then an OS-level…"},"1513":{"u":"/docs/onboarding/CONCEPT-MAPS.html#11-ui-write-once-render-everywhere--i18n--theming","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"11. UI, write-once render everywhere + i18n + theming","x":"A page is authored once as a Razor component in a per-module UI library; both the Blazor web host (Server + WASM) and the .NET MAUI host reference the same libraries, so it…"},"1514":{"u":"/docs/onboarding/CONCEPT-MAPS.html#12-adc-business-modules-bounded-contexts-end-to-end","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"12. ADC business modules, bounded contexts end-to-end","x":"Each ADC module is a vertical slice through all layers. Conference is large enough to split across five chapters (G17-G21); Engagement takes two (G22 the bookmark, QR badge…"},"1515":{"u":"/docs/onboarding/CONCEPT-MAPS.html#13-the-adrs-grouped-by-theme","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"13. The ADRs, grouped by theme","x":"Every accepted ADR in Website/docs-src/adr/, clustered by the concern it governs. That directory's README.md is the canonical index and owns the count and range; this map only…"},"1516":{"u":"/docs/onboarding/CONCEPT-MAPS.html#14-the-34-category-evaluation-rubric","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"14. The 34-category evaluation rubric","x":"The lens the guide tags code against ([Rubric §N]). Scored on two axes: Maturity (0-4, process) and Implementation (0-10, substance). Three parts. ---"},"1517":{"u":"/docs/onboarding/CONCEPT-MAPS.html#15-how-the-axes-fit-together-reading-map","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"15. How the axes fit together (reading map)","x":"The guide is organized on two axes at once. This ties the diagrams above back to the guide's navigation. --- - Group-to-group arrows in §3 show the dominant \"builds on\" direction…"},"1518":{"u":"/docs/governance/index.html","d":"Architecture Governance","k":"Architecture Governance","x":"The governance artifacts behind the MMCA platform: the shared 34-category evaluation rubric, and each repo's evidence-based scorecard plus its remediation backlog. Every score…"},"1519":{"u":"/docs/governance/index.html#the-rubric","d":"Architecture Governance","k":"Architecture Governance","t":"The rubric","x":"- Architecture Evaluation Criteria: the 34-category rubric (Maturity 0-4 and Implementation 0-10 per category) that all three application repos are scored against. - Rubric…"},"1520":{"u":"/docs/governance/index.html#scorecards-and-backlogs","d":"Architecture Governance","k":"Architecture Governance","t":"Scorecards and backlogs"},"1521":{"u":"/docs/governance/index.html#how-these-are-maintained","d":"Architecture Governance","k":"Architecture Governance","t":"How these are maintained","x":"Scores are re-verified from source on a cadence: each category is scored by reading the current code, config, and CI (never rolled forward), and any change lands with the…"},"1522":{"u":"/docs/governance/adc-ArchitectureScorecard.html","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","x":"Canonical, version-controlled scorecard for this repo: the single source of truth for MMCA.ADC's architecture scores, scored against the rubric at…"},"1523":{"u":"/docs/governance/adc-ArchitectureScorecard.html#at-a-glance","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"At a glance","x":"- Maturity index = Σ(maturity×weight) ÷ Σ(weight×4) = 314 ÷ 324 = 96.9% - Implementation index = Σ(impl×weight) ÷ Σ(weight×10) = 697 ÷ 810 = 86.0% - Weaker axis: Implementation,…"},"1524":{"u":"/docs/governance/adc-ArchitectureScorecard.html#top-5-risks","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 risks","x":"1. Responsive behavior holds the lowest implementation score and sits in both bands: §22 (mat 3 / impl 7, weight 2; the top row of the implementation band at implPriority 4, and…"},"1525":{"u":"/docs/governance/adc-ArchitectureScorecard.html#top-5-strengths","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 strengths","x":"1. Architecture intent is executable, not prose (§34 Governance, mat 4 / impl 9, and §3 Clean Architecture, mat 4 / impl 9): 54 architecture-test classes in…"},"1526":{"u":"/docs/governance/adc-ArchitectureScorecard.html#scorecard","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Scorecard","x":"Weighted column = Maturity·weight / Implementation·weight per row. The maturity gap is four categories at maturity 3 (§10, §12, §21, §22); every other category is at maturity 4,…"},"1527":{"u":"/docs/governance/adc-ArchitectureScorecard.html#scoring-notes","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Scoring notes","x":"- N/A (excluded from denominators): none. §16 AI-Native Application Architecture is scored because the AI session-scoring feature calls a model in production (the rubric's…"},"1528":{"u":"/docs/governance/adc-ArchitectureScorecard.html#cross-repo-comparison","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Cross-repo comparison","x":"How this repo's quality relates to the other consumers (where the framework's quality propagates, where a repo diverges or under-uses it, and where a consumer does better than…"},"1529":{"u":"/docs/governance/adc-RemediationBacklog.html","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","x":"Derived from ArchitectureScorecard.md (evidence as of 2026-10-01 at MMCA.Common. v1.217.0): Maturity 96.9% (314/324) / Implementation 86.0% (697/810), weight total 81, no N/A…"},"1530":{"u":"/docs/governance/adc-RemediationBacklog.html#open-work-at-a-glance","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"Open work at a glance"},"1531":{"u":"/docs/governance/adc-RemediationBacklog.html#open-items","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"Open items","x":"Most criteria are CI-enforced (a frozen contract snapshot that spells out generic arguments and is keyed by the [EventName] wire identity, Common…"},"1532":{"u":"/docs/governance/adc-RemediationBacklog.html#implementation-band","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"Implementation band","x":"Categories scoring implementation <= 8, ranked by implPriority (target 9). §10, §21 and §22 also hold a maturity-band row; their items are under Open items. Σ implPriority = 34…"},"1533":{"u":"/docs/governance/adc-RemediationBacklog.html#maturity-band","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"Maturity band","x":"Σ maturity priority = 10 across the 4 rows (3+3+2+2)."},"1534":{"u":"/docs/governance/adc-RemediationBacklog.html#deliberate-and-accepted","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"Deliberate and accepted","x":"Recorded decisions, not scheduled work. - Single-region deployment (no multi-region failover): accepted in infra/DISASTER-RECOVERY.md, with the single SQL server and single…"},"1535":{"u":"/docs/governance/adc-RemediationBacklog.html#resolved","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"Resolved"},"1536":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html","d":"Architecture Evaluation Criteria","k":"Architecture Governance","x":"A structured rubric for evaluating the architecture of an enterprise application. Each category defines what is being assessed, concrete criteria to check, red flags that signal…"},"1537":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#how-to-use-this-rubric","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"How to Use This Rubric","x":"Score each category 0–4. Use the same scale everywhere so totals are comparable. Alongside the maturity level, rate how well each category is actually implemented on a finer 0–10…"},"1538":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#1-solid-principles","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"1. SOLID Principles","x":"Intent: Object/module-level design discipline that keeps code flexible and decoupled. Criteria - SRP: each class/handler has one reason to change; no \"god\" services orchestrating…"},"1539":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#2-design-patterns","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"2. Design Patterns","x":"Intent: Appropriate, idiomatic use of patterns, solving real problems, not pattern theater. Criteria - Creational (Factory methods on entities, Builder, Options) used where…"},"1540":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#3-clean-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"3. Clean Architecture","x":"Intent: Dependencies point inward; business rules are independent of frameworks, UI, and data stores. Criteria - Dependency rule enforced: Domain → (nothing); Application →…"},"1541":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#4-domain-driven-design","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"4. Domain-Driven Design","x":"Intent: The model reflects the business; boundaries follow capability boundaries, not technical layers. Criteria - Ubiquitous language: type/method names match business terms…"},"1542":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#5-vertical-slice-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"5. Vertical Slice Architecture","x":"Intent: Code is organized by feature/capability, so a change touches one cohesive slice. Criteria - Features grouped by use case (command/query + handler + validator + DTO…"},"1543":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#6-cqrs--event-driven-design","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"6. CQRS & Event-Driven Design","x":"Intent: Reads and writes are separated where it pays off; integration via events is reliable. Criteria - Commands (mutate, return Result) and queries (read, side-effect-free) are…"},"1544":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#7-microservices-readiness","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"7. Microservices Readiness","x":"Intent: Whether services (or future-extractable modules) are independently deployable and own their data. Criteria - Service boundaries align with bounded contexts; one team can…"},"1545":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#8-data-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"8. Data Architecture","x":"Intent: Persistence, consistency, and migrations are deliberate and safe. Criteria - Transaction boundaries match aggregate boundaries; unit-of-work scope is clear. - Migrations…"},"1546":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#9-api--contract-design","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"9. API & Contract Design","x":"Intent: External and inter-service contracts are clear, stable, and evolvable. Criteria - Consistent resource/endpoint design (REST/minimal APIs/gRPC) with predictable shapes. -…"},"1547":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#10-messaging--integration-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"10. Messaging & Integration Architecture","x":"Intent: Integration over a broker or an edge gateway is reliable, observable and evolvable: the transport, the failure paths and the long-running processes are deliberate. (§6…"},"1548":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#11-security","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"11. Security","x":"Intent: AuthN/AuthZ, secrets, and data protection are correct by construction. Criteria - Authentication centralized; tokens validated; identity flows documented (e.g.,…"},"1549":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#12-performance--scalability","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"12. Performance & Scalability","x":"Intent: The system meets latency/throughput goals and scales horizontally. Criteria - Async I/O throughout; no sync-over-async; no blocking the request thread. - Hot-path query…"},"1550":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#13-observability--operability","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"13. Observability & Operability","x":"Intent: You can understand and operate the system in production. Criteria - Structured logging with correlation/trace IDs flowing across module/service boundaries. - Distributed…"},"1551":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#14-testability--test-strategy","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"14. Testability & Test Strategy","x":"Intent: The design supports fast, reliable, meaningful tests at the right levels. Criteria - Healthy test pyramid: many fast unit tests on domain/application, fewer integration,…"},"1552":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#15-best-practices--code-quality","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"15. Best Practices & Code Quality","x":"Intent: Day-to-day craftsmanship that keeps the codebase healthy. Criteria - Analyzers at error severity (style, security, threading, maintainability) enforced in CI;…"},"1553":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#16-ai-native-application-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"16. AI-Native Application Architecture","x":"Intent: Where a product feature calls a language model or an agent, that dependency is governed like any other external system: isolated, versioned, evaluated, observed and…"},"1554":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#17-devops--deployment","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"17. DevOps & Deployment","x":"Intent: Building, releasing, and provisioning are automated, repeatable, and safe. (The local developer experience / inner loop behind this (local orchestration, cross-repo dev,…"},"1555":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#18-ui-architecture--component-design","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"18. UI Architecture & Component Design","x":"Intent: Components are cohesive, reusable, and composed cleanly, the UI has a deliberate structure, not page-sized blobs. Criteria - Container/presentational split: smart…"},"1556":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#19-state-management--data-flow","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"19. State Management & Data Flow","x":"Intent: Client state has a clear owner and predictable flow; server state is cached and invalidated deliberately. Criteria - Single source of truth per piece of state; ownership…"},"1557":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#20-design-system-theming--ui-consistency","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"20. Design System, Theming & UI Consistency","x":"Intent: A coherent visual language enforced by a component library, not re-implemented per screen. Criteria - Component library used consistently (e.g., MudBlazor): teams build…"},"1558":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#21-accessibility-a11y","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"21. Accessibility (a11y)","x":"Intent: The UI is usable by everyone, including assistive-technology users, and ideally enforced, not aspirational. Criteria - Semantic structure: correct…"},"1559":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#22-responsive-design--cross-browserdevice","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"22. Responsive Design & Cross-Browser/Device","x":"Intent: The UI works across viewport sizes, input modes, and supported browsers. Criteria - Fluid/responsive layouts via the design system's grid/breakpoints; no fixed-width…"},"1560":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#23-front-end-performance--rendering","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"23. Front-End Performance & Rendering","x":"Intent: The UI loads and responds fast; rendering work is bounded. (Complements §12: this is the client side.) Criteria - Initial load: bundle/payload size controlled;…"},"1561":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#24-forms-validation--ux-safety","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"24. Forms, Validation & UX Safety","x":"Intent: Data entry is safe, forgiving, and consistent, users don't lose work or get confused by errors. Criteria - Validation parity: client-side validation for fast feedback…"},"1562":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#25-navigation-routing--information-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"25. Navigation, Routing & Information Architecture","x":"Intent: Users can find their way; routes are meaningful, guarded, and role-aware. Criteria - Route design: clean, bookmarkable, deep-linkable URLs; parameters typed and…"},"1563":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#26-front-end-security","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"26. Front-End Security","x":"Intent: The client doesn't become the weak link, XSS, token handling, and trust boundaries are correct. (Complements §11.) Criteria - Output encoding / XSS: no unsanitized HTML…"},"1564":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#27-internationalization--localization","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"27. Internationalization & Localization","x":"Intent: The UI can be translated and respects culture, if in scope. (Score weight 0–1 if single-locale by design.) Criteria - Externalized strings: UI text in resource files, not…"},"1565":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#28-front-end-testing--quality","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"28. Front-End Testing & Quality","x":"Intent: The UI is verified at the right levels with stable, meaningful tests. (Complements §14.) Criteria - Component tests (e.g., bUnit) for rendering logic, parameters, events,…"},"1566":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#29-resilience-reliability--business-continuity","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"29. Resilience, Reliability & Business Continuity","x":"Intent: The system survives partial failure and recovers from disaster within defined objectives. (Extends the resilience facets of §7/§12 into a first-class recovery story.)…"},"1567":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#30-compliance-privacy--data-governance","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"30. Compliance, Privacy & Data Governance","x":"Intent: Personal and regulated data is classified, governed, and handled lawfully across its lifecycle. (§11 defends against attackers; this answers to regulators.) Criteria -…"},"1568":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#31-cost-efficiency--finops","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"31. Cost Efficiency / FinOps","x":"Intent: Cloud spend is proportional to value and driven by data, not guesswork. (§17 mentions cost; this makes it a first-class axis.) Criteria - Right-sizing: compute/database…"},"1569":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#32-dependency--supply-chain-management","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"32. Dependency & Supply-Chain Management","x":"Intent: Third-party and inter-package dependencies are controlled, auditable, and evolve safely, especially critical for a framework that publishes packages. (Elevates §15's…"},"1570":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#33-developer-experience--inner-loop","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"33. Developer Experience & Inner Loop","x":"Intent: Developers build, run, test, and iterate locally with fast, low-friction feedback. (Promoted out of §17: that scores release/ops automation; this scores the inner loop.)…"},"1571":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#34-architecture-governance--documentation","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"34. Architecture Governance & Documentation","x":"Intent: Decisions are recorded, conformance is enforced, and the system is documented so it stays coherent as it evolves. (Absorbs the former §16 Maintainability & Evolvability…"},"1572":{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#appendix-quick-scan-checklist","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"Appendix: Quick-Scan Checklist","x":"A 2-minute triage before the full evaluation: any \"no\" warrants a deeper look. - [ ] Can you draw the dependency graph and is it acyclic and inward-pointing? - [ ] Is the domain…"},"1573":{"u":"/docs/governance/common-ArchitectureScorecard.html","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","x":"Canonical, version-controlled scorecard for this repo: the single source of truth for MMCA.Common's architecture scores. Scored against the rubric at…"},"1574":{"u":"/docs/governance/common-ArchitectureScorecard.html#at-a-glance","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"At a glance","x":"- Maturity index = Σ(maturity×weight) ÷ Σ(weight×4) = 317 ÷ 328 = 96.6% - Implementation index = Σ(impl×weight) ÷ Σ(weight×10) = 705 ÷ 820 = 86.0% - Weaker axis: Implementation…"},"1575":{"u":"/docs/governance/common-ArchitectureScorecard.html#top-5-risks","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 risks","x":"1. FinOps execution and governance live downstream, §31 (impl 8, maturity 2, the lowest maturity on the board; a documented accepted cap per the backlog's Deliberate and accepted…"},"1576":{"u":"/docs/governance/common-ArchitectureScorecard.html#top-5-strengths","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 strengths","x":"1. Dual-enforced Clean Architecture dependency rule (compile-time + fitness functions), §3 (impl 9): Source/Build/MMCA.Common.LayerEnforcement.targets:1-90 fails the build on…"},"1577":{"u":"/docs/governance/common-ArchitectureScorecard.html#scorecard","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Scorecard","x":"Weighted column = Maturity·weight / Implementation·weight per row."},"1578":{"u":"/docs/governance/common-ArchitectureScorecard.html#scoring-notes","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Scoring notes","x":"- N/A (excluded from denominators): none. All 34 rows carry numeric Maturity and Implementation, so Σweight is 82. §16 AI-Native Application Architecture is scored because…"},"1579":{"u":"/docs/governance/common-ArchitectureScorecard.html#cross-repo-comparison","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Cross-repo comparison","x":"How this repo's quality relates to the other consumers (where the framework's quality propagates, where a repo diverges or under-uses it, and where a consumer does better than…"},"1580":{"u":"/docs/governance/common-RemediationBacklog.html","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","x":"Derived from the scorecard: Maturity 96.6% (317/328) and Implementation 86.0% (705/820), framework v1.218.0, evidence as of 2026-10-01. Tasks are ranked on both scorecard axes,…"},"1581":{"u":"/docs/governance/common-RemediationBacklog.html#open-work-at-a-glance","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Open work at a glance"},"1582":{"u":"/docs/governance/common-RemediationBacklog.html#open-items","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Open items","x":"- Gap (maturity): SRP and DIP are enforced automatically: a ratcheted constructor-dependency ceiling is subclassed in Common…"},"1583":{"u":"/docs/governance/common-RemediationBacklog.html#implementation-band","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Implementation band","x":"Every category at implementation <= 8, ranked by implPriority = max(0, 9 − implementation) × weight. Four of these categories (1, 17, 30, 31) also sit in the maturity band; this…"},"1584":{"u":"/docs/governance/common-RemediationBacklog.html#maturity-band","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Maturity band","x":"Every category at maturity < 4, ranked by priority = (4 − maturity) × weight. Σ priority = 11 across the 4 maturity-band rows (4 + 3 + 2 + 2)."},"1585":{"u":"/docs/governance/common-RemediationBacklog.html#deliberate-and-accepted","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Deliberate and accepted","x":"Recorded decisions and deferred findings: each is real, none is scheduled work. The computed maturity priority = (4 − 2) × 2 = 4 is the highest weighted gap of any open category,…"},"1586":{"u":"/docs/governance/common-RemediationBacklog.html#resolved","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Resolved","x":"Categories at maturity 4 AND implementation = 9 (protect them, do not regress), then the closed items inside categories that are still open."},"1587":{"u":"/docs/governance/store-ArchitectureScorecard.html","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","x":"Canonical, version-controlled scorecard for this repo: the single source of truth for MMCA.Store's architecture scores. Scored against the rubric at…"},"1588":{"u":"/docs/governance/store-ArchitectureScorecard.html#at-a-glance","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"At a glance","x":"- Maturity index = Σ(maturity×weight) ÷ Σ(weight×4) = 308 ÷ 316 = 97.5% - Implementation index = Σ(impl×weight) ÷ Σ(weight×10) = 659 ÷ 790 = 83.4% - Weaker axis: Implementation…"},"1589":{"u":"/docs/governance/store-ArchitectureScorecard.html#top-5-risks","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 risks","x":"1. An internal gRPC edge returns customer PII to any caller: §11 (impl 7, weight 3): Identity maps CustomersGrpcService with .AllowAnonymous(), and it returns email and full name…"},"1590":{"u":"/docs/governance/store-ArchitectureScorecard.html#top-5-strengths","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 strengths","x":"1. Clean Architecture + DDD + CQRS depth, fitness-enforced: §3/§4 (impl 9): inherited framework substance, guarded by the shared NetArchTest suite (53 test classes,…"},"1591":{"u":"/docs/governance/store-ArchitectureScorecard.html#scorecard","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Scorecard","x":"Weighted = Maturity·weight / Implementation·weight."},"1592":{"u":"/docs/governance/store-ArchitectureScorecard.html#scoring-notes","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Scoring notes","x":"- Two axes per category: Maturity (0-4, process/governance) and Implementation (0-10, substance/execution). The indices are computed deterministically from the table: every…"},"1593":{"u":"/docs/governance/store-ArchitectureScorecard.html#cross-repo-comparison","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Cross-repo comparison","x":"How Store relates to MMCA.Common (the framework) and MMCA.ADC (the sibling consumer) is maintained once, for all three repos, in the workspace-internal…"},"1594":{"u":"/docs/governance/store-RemediationBacklog.html","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","x":"Derived from ArchitectureScorecard.md: Maturity 97.5% (308/316) / Implementation 83.4% (659/790), evidence as of 2026-10-01 at MMCA.Common. v1.217.0 (all 17 packages lockstep,…"},"1595":{"u":"/docs/governance/store-RemediationBacklog.html#open-work-at-a-glance","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"Open work at a glance"},"1596":{"u":"/docs/governance/store-RemediationBacklog.html#open-items","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"Open items","x":"- [ ] 11 Security, implementation 7: internal trust and secret handling. Four red flags hold the row at 7. The Identity customer gRPC edge is anonymous and returns email and full…"},"1597":{"u":"/docs/governance/store-RemediationBacklog.html#implementation-band","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"Implementation band","x":"21 categories, 52 gap points, the largest of the three repos. Levers are cited only where this ledger or the scorecard records one; an unnamed lever is named at the next…"},"1598":{"u":"/docs/governance/store-RemediationBacklog.html#maturity-band","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"Maturity band","x":"3 categories, 8 gap points. Ties break by priority desc, then weight desc, then category asc."},"1599":{"u":"/docs/governance/store-RemediationBacklog.html#deliberate-and-accepted","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"Deliberate and accepted","x":"- §16 AI-Native Application Architecture is N/A (rubric v2, ADR-110): no product feature calls a language model, so its weight 2 leaves both denominators until one does. - 5…"},"1600":{"u":"/docs/governance/store-RemediationBacklog.html#resolved","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"Resolved"},"1601":{"u":"/docs/guides/index.html","d":"Guides & Specifications","k":"Guides & Specifications","x":"As of: 2026-09-06. The narrative documentation for the MMCA platform: adoption guides, business specifications, workflow analyses, and per-concern reference notes. Files are…"},"1602":{"u":"/docs/guides/index.html#framework-mmcacommon","d":"Guides & Specifications","k":"Guides & Specifications","t":"Framework (MMCA.Common)","x":"- Capability Catalog: everything an adopting team gets, section by section (packages, scaffolding, the architectural core, the extraction path, data, messaging, API, identity,…"},"1603":{"u":"/docs/guides/index.html#mmcastore-e-commerce","d":"Guides & Specifications","k":"Guides & Specifications","t":"MMCA.Store (e-commerce)","x":"- Business Specification - Business Workflow Analysis - Navigation Flow - Manual Screen-Reader Pass Runbook"},"1604":{"u":"/docs/guides/index.html#mmcaadc-conference","d":"Guides & Specifications","k":"Guides & Specifications","t":"MMCA.ADC (conference)","x":"- Business Specifications - Navigation Flow - Manual Screen-Reader Pass Runbook - Integration-Test Tier Rework Plan Related reading: the Architecture Decision Records and the…"},"1605":{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","x":"As of: 2026-07-20. The automated layer (axe-core WCAG 2.1 AA scans in Tests/E2E/MMCA.ADC.E2E.Tests/AccessibilityTests.cs plus the shared Login/Register/Profile bases in…"},"1606":{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html#tools","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"Tools","x":"Run against the app started via Aspire (dotnet run --project Source/Hosting/MMCA.ADC.AppHost), reaching the UI through the Gateway. Test with the keyboard only (no mouse) for the…"},"1607":{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html#what-to-verify-on-every-flow","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"What to verify on every flow","x":"1. Landmarks and headings. The SR's landmark/heading list (NVDA Insert+F7) exposes a logical banner / navigation / main structure and a sensible h1...hN outline (one h1 per…"},"1608":{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html#flows-to-cover-mirror-the-automated-accessibilitytests-plus-the-role-journeys","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"Flows to cover (mirror the automated AccessibilityTests plus the role journeys)"},"1609":{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html#results-log","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"Results log","x":"Record one row per pass. A flow with any unresolved blocker is a FAIL (open a backlog item for it)."},"1610":{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html#after-a-pass","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"After a pass","x":"- File any defect as a remediation item; cross-link it from the §21 row's evidence. - Update the dated row above so the scorecard can cite a real, current manual pass (this is…"},"1611":{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","x":"As of: 2026-08-30. Status: complete (Phase 4 broker-transport tier landed 2026-07-06; Phase 5 residual = coverlet only). - Phase 0 ✅: Tests/WebAPI revived as MMCA.Common.API…"},"1612":{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#recommended-strategy-two-tiers","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Recommended strategy: two tiers","x":"1. Primary: per-service WebApplicationFactory : one in-process host per service (Identity / Conference / Engagement), cross-service edges mocked. AddBrokerMessaging…"},"1613":{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#three-code-facts-that-shape-the-rework-verified","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Three code facts that shape the rework (verified)","x":"- Only Conference.Service is WAF-incompatible: it ends with StartAsync() + self-HTTP/2 WarmupViaHttpAsync + WaitForShutdownAsync(). Identity/Engagement/Notification use…"},"1614":{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#databases","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Databases","x":"- SQLite in-memory for the fast bulk tier (no Docker, CI-friendly). A SQLite source with no migrations assembly has its schema created at startup by InitializeDatabasesAsync,…"},"1615":{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#project-structure","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Project structure","x":"- One WAF test project per service (MMCA.ADC.{Identity,Conference,Engagement}.IntegrationTests): can't reference two Program-bearing hosts in one project. - One…"},"1616":{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#ci","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"CI","x":"- Add the SQLite per-service tier to CI.slnf (seconds, no Docker) → restores the authz/CRUD merge gate (11) with no workflow change. - Keep the container-based MsSql + RabbitMQ…"},"1617":{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#phased-sequencing-fastest-win-first","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Phased sequencing (fastest win first)","x":"- Phase 0: re-home WebAPI.Tests middleware unit tests; drop the dead WebAPI reference; re-add to slnx+CI.slnf. ~16 tests green; removes a non-building project (16). - Phase 1:…"},"1618":{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#key-risks","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Key risks","x":"- The non-Identity JwtBearerOptions in-process override is the trickiest piece: prove it on one Conference auth test before fanning out. - SQLite vs SQL-Server fidelity (owned…"},"1619":{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#critical-files","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Critical files","x":"- Tests/Integration/MMCA.ADC.IntegrationTests/Infrastructure/TestWebApplicationFactory.cs (combined-host factory → split into per-service fixtures; its JWT config block is the…"},"1620":{"u":"/docs/guides/adc-NavigationFlow.html","d":"Navigation Flow","k":"Guides & Specifications","x":"As of: 2026-09-17. This document maps the site navigation flow for each actor in the MMCA.ADC application. Each mermaid diagram shows the pages accessible to that actor and the…"},"1621":{"u":"/docs/guides/adc-NavigationFlow.html#actors","d":"Navigation Flow","k":"Guides & Specifications","t":"Actors","x":"Roles & menu: the role vocabulary is Organizer, ContentEditor and Attendee (the default, RoleNames). Organizer holds every capability; ContentEditor holds the catalog-curation…"},"1622":{"u":"/docs/guides/adc-NavigationFlow.html#1-anonymous-user","d":"Navigation Flow","k":"Guides & Specifications","t":"1. Anonymous User","x":"Pages accessible without authentication: home, login, register, the two password-reset pages, and all public conference pages. ---"},"1623":{"u":"/docs/guides/adc-NavigationFlow.html#2-attendee-authenticated-user","d":"Navigation Flow","k":"Guides & Specifications","t":"2. Attendee (Authenticated User)","x":"Inherits all anonymous pages. Gains access to profile, feedback submission, and session bookmarking. Unauthenticated visitors are redirected to login. ---"},"1624":{"u":"/docs/guides/adc-NavigationFlow.html#3-speaker","d":"Navigation Flow","k":"Guides & Specifications","t":"3. Speaker","x":"Inherits all attendee pages. Gains access to the speaker dashboard for managing their own profile, viewing assigned sessions, and reviewing feedback ratings. ---"},"1625":{"u":"/docs/guides/adc-NavigationFlow.html#4-organizer","d":"Navigation Flow","k":"Guides & Specifications","t":"4. Organizer","x":"Authenticated users with the Organizer role. Inherits all attendee and public pages. Adds CRUD management for every conference entity (events, sessions, speakers, categories,…"},"1626":{"u":"/docs/guides/adc-NavigationFlow.html#5-functionality-flows-attendee--speaker","d":"Navigation Flow","k":"Guides & Specifications","t":"5. Functionality Flows (Attendee & Speaker)","x":"The diagrams in sections 1-4 map which pages each actor can reach. The diagrams below map how attendees and speakers accomplish each functionality, including inline actions…"},"1627":{"u":"/docs/guides/adc-NavigationFlow.html#navigation-patterns","d":"Navigation Flow","k":"Guides & Specifications","t":"Navigation Patterns","x":"- Unauthenticated users accessing protected pages are redirected to /login via the RedirectToLogin component. - Successful login/register redirects to Home (/) with a full page…"},"1628":{"u":"/docs/guides/adc-specifications.html","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","x":"As of: 2026-09-17. ---"},"1629":{"u":"/docs/guides/adc-specifications.html#1-system-overview","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"1. System Overview","x":"ADC is a conference management system for the Atlanta Developers Conference. It provides backend services to manage multi-day conference events, sessions, speakers, rooms,…"},"1630":{"u":"/docs/guides/adc-specifications.html#2-domain-model","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"2. Domain Model","x":"Relationships: - Owns many Rooms (child entities) - Owns many EventSpeakers (child join entities linking Event ↔ Speaker) - Owns many EventQuestionAnswers (child feedback…"},"1631":{"u":"/docs/guides/adc-specifications.html#3-business-rules","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"3. Business Rules","x":"Reading guide: Some rules reference other rules defined later in the document (e.g., BR-63, BR-80 are defined in Section 10). Forward references use the BR- numbering…"},"1632":{"u":"/docs/guides/adc-specifications.html#4-use-cases--business-processes","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"4. Use Cases / Business Processes","x":"See UC-30 (Registration) and UC-31 (Login) in Section 12.2 for the current email + password authentication flows. UC-34 (Request password reset) and UC-35 (Reset password) in the…"},"1633":{"u":"/docs/guides/adc-specifications.html#5-workflows--state-transitions","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"5. Workflows & State Transitions","x":"The Session.Status field is a free-text string imported from Sessionize. Default: null (for manually created sessions). Known Sessionize values: Accepted, Waitlisted, Accept…"},"1634":{"u":"/docs/guides/adc-specifications.html#6-events--side-effects","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"6. Events & Side Effects","x":"Domain events are raised for entity mutations. Not all events have registered handlers: events without handlers serve as extension points for future requirements. Note: Only…"},"1635":{"u":"/docs/guides/adc-specifications.html#7-business-constraints--invariants","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"7. Business Constraints & Invariants","x":"---"},"1636":{"u":"/docs/guides/adc-specifications.html#8-external-integrations-business-perspective","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"8. External Integrations (Business Perspective)","x":"---"},"1637":{"u":"/docs/guides/adc-specifications.html#9-glossary","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"9. Glossary","x":"---"},"1638":{"u":"/docs/guides/adc-specifications.html#ddd-structural-summary","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"DDD Structural Summary","x":"Why three bounded contexts instead of two: The original Events + Identity split grouped all conference-related entities together regardless of write profile. Separating…"},"1639":{"u":"/docs/guides/adc-specifications.html#10-specification-clarifications--addenda","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"10. Specification Clarifications & Addenda","x":"This section addresses gaps, ambiguities, and implicit design decisions identified during implementation review. New business rules are numbered BR-61+. API contract…"},"1640":{"u":"/docs/guides/adc-specifications.html#11-api-contract-specifications","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"11. API Contract Specifications","x":"This section documents API design decisions that apply across all endpoints. --- All error responses use the RFC 9457 ProblemDetails format (the successor to RFC 7807, same…"},"1641":{"u":"/docs/guides/adc-specifications.html#12-authentication--identity-architecture","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"12. Authentication & Identity Architecture","x":"This section defines the authentication mechanism for both the Web UI (Blazor) and MAUI (mobile) clients, which share a common Razor class library. It replaces the device-based…"},"1642":{"u":"/docs/guides/common-ACCESSIBILITY.html","d":"Accessibility (rubric §21)","k":"Guides & Specifications","x":"As of: 2026-07-20. The shared MMCA.Common.UI surface targets WCAG 2.1 AA. Accessibility is enforced two ways: an automated axe-core gate in CI (the bulk of coverage) and a…"},"1643":{"u":"/docs/guides/common-ACCESSIBILITY.html#automated-coverage-axe-core-wcag-21-aa","d":"Accessibility (rubric §21)","k":"Guides & Specifications","t":"Automated coverage (axe-core, WCAG 2.1 AA)","x":"The ui-e2e CI job runs Playwright + axe-core against the backend-less gallery; chromium is the blocking merge gate (firefox/webkit advisory). Scanned states: Component render is…"},"1644":{"u":"/docs/guides/common-ACCESSIBILITY.html#manual-screen-reader-pass","d":"Accessibility (rubric §21)","k":"Guides & Specifications","t":"Manual screen-reader pass","x":"Automation cannot judge reading order, focus management, or announcement quality, so the shared surface is walked manually. Checklist (re-run on any change to MainLayout, the…"},"1645":{"u":"/docs/guides/common-ACCESSIBILITY.html#known-limitations-tracked","d":"Accessibility (rubric §21)","k":"Guides & Specifications","t":"Known limitations (tracked)","x":"- ~~Dark-mode contrast (§20, not §21).~~ RESOLVED (2026-07-11). The two dark-palette WCAG AA contrast failures the prototype scan flagged (filled-primary button label ~2.65:1 on…"},"1646":{"u":"/docs/guides/common-BUILD-BY-HAND.html","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","x":"As of: 2026-09-13. This is the long-form walkthrough: every project, every file, and every load-bearing line that goes into an application on the MMCA.Common framework, in the…"},"1647":{"u":"/docs/guides/common-BUILD-BY-HAND.html#what-you-will-build","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"What you will build","x":"A modular monolith with one business module and two hosts: - Orders (your business module): an Order aggregate with OrderComment children, opened through a Result-returning…"},"1648":{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-0-prerequisites-and-decisions","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 0: Prerequisites and decisions","x":"Install: - .NET 10 SDK (the framework targets net10.0 with LangVersion: preview for C extension types). - SQL Server reachable locally (LocalDB, a container, or the one Aspire…"},"1649":{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-1-create-the-solution-and-the-build-plumbing","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 1: Create the solution and the build plumbing","x":"Scaffolded. dotnet new mmca-app writes every file in this phase. Read it to know what each one does; you do not need to type any of it. The plumbing files are the load-bearing,…"},"1650":{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-2-scaffold-the-module-project-set","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 2: Scaffold the module project set","x":"Scaffolded. dotnet new mmca-app creates this project set for your first module, and pwsh build/add-module.ps1 adds another one later: it drives dotnet new mmca-module and then…"},"1651":{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-3-the-vertical-slice-end-to-end-the-heart-of-it","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 3: The vertical slice end-to-end (the heart of it)","x":"Scaffolded. The generated module already contains this slice and six more, worked end to end. dotnet new mmca-command and dotnet new mmca-query add another one. This phase is the…"},"1652":{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-4-dbcontext-model-and-migrations","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 4: DbContext model and migrations","x":"Partly scaffolded. The migrations project and its design-time factory are generated. Running dotnet ef migrations add InitialCreate is still yours, and for a module added later…"},"1653":{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-5-compose-the-monolith-host-and-run-it","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 5: Compose the monolith host and run it","x":"Scaffolded. Both hosts, the AppHost, and the .resx pairs are generated. Read this phase before you touch any of them: the DI sequence, WaitFor(sql) rather than the database…"},"1654":{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-6-tests-and-the-architecture-fitness-map","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 6: Tests and the architecture-fitness map","x":"Scaffolded, with one deliberate gap. All three test projects and the map are generated. The IntegrationEventContractTests subclass is NOT: its frozen literal lists members…"},"1655":{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-7-upgrading-the-framework-version","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 7: Upgrading the framework version","x":"Not scaffolded. dotnet new mmca-app --framework-version picks the version you START on; moving to a later one is this phase. When a new MMCA.Common release ships, upgrade in one…"},"1656":{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-8-extract-a-module-into-its-own-service-the-payoff","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 8: Extract a module into its own service (the payoff)","x":"Not scaffolded. The generated solution carries the plumbing (the .Contracts proto convention and the .Service OpenAPI block in Directory.Build.props), but the extraction itself…"},"1657":{"u":"/docs/guides/common-BUILD-BY-HAND.html#verification-checklist","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Verification checklist","x":"1. Build green: dotnet build Contoso.Support.slnx with no warnings (TreatWarningsAsErrors + five analyzers). This is the primary automatable gate. 2. Unit + architecture tests…"},"1658":{"u":"/docs/guides/common-BUILD-BY-HAND.html#where-to-look-next","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Where to look next","x":"- Getting Started: the one-command path that writes phases 1 through 6 for you. If you are starting a new solution rather than adding the framework to an existing one, that is…"},"1659":{"u":"/docs/guides/common-CAPABILITIES.html","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","x":"As of: 2026-09-10 (framework v1.187.0). Everything an adopting team gets from the MMCA.Common framework: a .NET 10 package set for building a modular monolith on DDD, Clean…"},"1660":{"u":"/docs/guides/common-CAPABILITIES.html#package-map","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Package map","x":"Seventeen packages, one version number. The layer rule is enforced at compile time inside the framework and at test time in every consumer: API/Grpc depend on Infrastructure,…"},"1661":{"u":"/docs/guides/common-CAPABILITIES.html#getting-started-and-scaffolding","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Getting started and scaffolding","x":"From zero to a green, warning-free, tested solution before writing any business logic. - One-command solution scaffold. dotnet new install MMCA.Templates then dotnet new mmca-app…"},"1662":{"u":"/docs/guides/common-CAPABILITIES.html#architectural-core","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Architectural core","x":"The opinionated backbone: errors as values, one sealed pipeline, DDD building blocks, and module composition. - Result pattern end to end. Result / Result with nine ErrorType…"},"1663":{"u":"/docs/guides/common-CAPABILITIES.html#monolith-now-services-later","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Monolith now, services later","x":"The framework's central promise, and it is a tested property, not a slogan. - Host-only extraction. Turning a module into its own service changes hosting code only; Domain,…"},"1664":{"u":"/docs/guides/common-CAPABILITIES.html#data-and-persistence","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Data and persistence","x":"EF Core with the cross-cutting concerns every production system eventually rebuilds, already built. - Multi-database routing. Every entity resolves to a DataSourceKey (engine,…"},"1665":{"u":"/docs/guides/common-CAPABILITIES.html#messaging-and-eventing","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Messaging and eventing","x":"Reliable events with two deliberately distinct paths: in-process domain events and durable integration events. - Transactional outbox. The save interceptor writes every captured…"},"1666":{"u":"/docs/guides/common-CAPABILITIES.html#http-api-surface","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"HTTP API surface","x":"Controllers and middleware that erase most CRUD endpoint code and keep the pipeline order under test. - Five generic read endpoints for free. EntityControllerBase gives list,…"},"1667":{"u":"/docs/guides/common-CAPABILITIES.html#identity-auth-and-authorization","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Identity, auth, and authorization","x":"A full identity building-block set you compose when you want it; a fresh app runs issuer-less until then. - RS256 + JWKS + OIDC discovery across services. TokenService signs with…"},"1668":{"u":"/docs/guides/common-CAPABILITIES.html#caching-resilience-and-performance","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Caching, resilience, and performance","x":"- Declarative handler caching. A query implements IQueryCacheable, its invalidating commands implement ICacheInvalidating, and the pipeline does the rest; tenant-scoped keys are…"},"1669":{"u":"/docs/guides/common-CAPABILITIES.html#ui-blazor-web-desktop-and-mobile","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"UI: Blazor web, desktop, and mobile","x":"One shared component layer serving Blazor Server, WASM, and MAUI hybrid heads on Android, iOS, Mac Catalyst, and Windows. - Module-composed application shell. AddUIModule() scans…"},"1670":{"u":"/docs/guides/common-CAPABILITIES.html#hosting-orchestration-and-operations","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Hosting, orchestration, and operations","x":"Aspire-first local orchestration and one-line production plumbing. - AppHost DSL. Thirteen extensions: WithSQLServerDataSource, WithCosmosDataSource, WithSqliteDataSource…"},"1671":{"u":"/docs/guides/common-CAPABILITIES.html#testing-and-quality-gates","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Testing and quality gates","x":"The framework ships its own enforcement: adopters subclass, and drift fails the build. - 124 test methods across 47 abstract bases. Thirty-seven are parameterized by your…"},"1672":{"u":"/docs/guides/common-CAPABILITIES.html#compliance-and-privacy","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Compliance and privacy","x":"- PII handling. [Pii] marks personal data; the audit trail redacts marked values at capture so it never becomes a second copy erasure has to chase; PiiRedactor (Redact,…"},"1673":{"u":"/docs/guides/common-CAPABILITIES.html#governance-versioning-and-supply-chain","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Governance, versioning, and supply chain","x":"- 117 ADRs. Every framework pattern is documented as an architecture decision record with context and consequences, published publicly and indexed in one place; adopters inherit…"},"1674":{"u":"/docs/guides/common-CAPABILITIES.html#documentation-library","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Documentation library","x":"This library holds ten framework guides, the ADR index, the governance rubric with scorecards, and a chapter-per-subsystem onboarding walkthrough. - Getting Started. Nothing to a…"},"1675":{"u":"/docs/guides/common-CAPABILITIES.html#constraints-worth-knowing","d":"MMCA.Common Capability Catalog","k":"Guides & Specifications","t":"Constraints worth knowing","x":"Design choices an adopter should read before sizing a plan around them. None is a defect; each is documented in source or an ADR. - The outbox is off in in-process mode. With…"},"1676":{"u":"/docs/guides/common-COST.html","d":"Cost & FinOps Notes (rubric §31)","k":"Guides & Specifications","x":"As of: 2026-09-02. MMCA.Common is a library, so it cannot provision anything: right-sizing, scale rules, budgets, and per-service cost attribution live in the consumer apps' IaC…"},"1677":{"u":"/docs/guides/common-COST.html#what-the-framework-does-for-cost","d":"Cost & FinOps Notes (rubric §31)","k":"Guides & Specifications","t":"What the framework does for cost","x":"- Telemetry ingestion is the real line item, so high-volume / low-value spans are dropped. OutboxPollFilterProcessor (MMCA.Common.Aspire) suppresses the recurring OutboxPoll…"},"1678":{"u":"/docs/guides/common-COST.html#recommended-consumer-defaults-set-these-downstream","d":"Cost & FinOps Notes (rubric §31)","k":"Guides & Specifications","t":"Recommended consumer defaults (set these downstream)","x":"- Telemetry retention & sampling. Tune Log Analytics retention to the minimum the consumer's compliance window allows, and set Telemetry:TracesSampleRatio (the built-in…"},"1679":{"u":"/docs/guides/common-COST.html#cost-attribution--guardrail-samples-distilled-from-mmcaadc","d":"Cost & FinOps Notes (rubric §31)","k":"Guides & Specifications","t":"Cost-attribution & guardrail samples (distilled from MMCA.ADC)","x":"These belong in the consumer's IaC, not the library, but the framework documents the shape so every consumer attributes spend and guards surges the same way. The worked, deployed…"},"1680":{"u":"/docs/guides/common-COST.html#out-of-scope-for-the-framework-by-design","d":"Cost & FinOps Notes (rubric §31)","k":"Guides & Specifications","t":"Out of scope for the framework (by design)","x":"Provisioning, scale rules, budgets, per-service cost attribution, and surge/revert automation are consumer/IaC concerns and are not added to the library: see also ADR-009…"},"1681":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","x":"As of: 2026-08-06. MMCA.ECommerce is the simplest e-commerce application on the MMCA.Common framework: a Products catalog module and an Orders module with line items, behind a…"},"1682":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#before-you-start","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"Before you start","x":"- .NET 10 SDK (the framework targets net10.0 with LangVersion: preview). - Docker Desktop (Aspire provisions SQL Server as a container). - EF Core tools: dotnet tool install…"},"1683":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#2-generate-the-solution-with-the-products-module","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"2. Generate the solution with the Products module","x":"Five options do most of this guide's old work. Three remove an axis a catalog product does not have, and the code for an axis you turn off is never generated: --flat drops the…"},"1684":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#3-add-the-orders-module","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"3. Add the Orders module","x":"build/add-module.ps1 ships inside the solution you just generated. It runs dotnet new mmca-module with the shape options passed through, then performs every wire-up the template…"},"1685":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#4-reshape-products-into-a-catalog-product","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"4. Reshape Products into a catalog product","x":"The scaffolded module arrives as the template's worked example in your namespaces, already shaped by the flags in step 2: no children, no status, no requester, Name instead of…"},"1686":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#5-reshape-orders-into-an-order-with-line-items","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"5. Reshape Orders into an order with line items","x":"Orders keeps the child-collection pattern the template scaffolded, retargeted. -Child Item already did the naming (the entity is OrderItem, the slices are AddItem / EditItem /…"},"1687":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#6-point-the-ui-at-the-new-domain","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"6. Point the UI at the new domain","x":"The scaffolded Blazor host already has the load-bearing parts: the typed ECommerceApiClient calling the API server-side through Aspire service discovery (no CORS, no token), the…"},"1688":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#7-create-the-migrations","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"7. Create the migrations","x":"Neither module has a migration yet: any shape flag makes mmca-app drop the template's sample one (it described the sample shape), and -SkipMigration deferred the Orders one to…"},"1689":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#8-the-two-one-time-fixups-then-run-it","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"8. The two one-time fixups, then run it","x":"Apply the two fixups the scaffold deliberately leaves to you (they are name-dependent, so no generated value could be right). First, sort the using directives and the identifier…"},"1690":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#verification-checklist","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"Verification checklist","x":"1. Baseline green immediately after mmca-app, before any edit: 81 tests. 2. After build/add-module.ps1: still green at 99 tests, both modules' scaffolded suites running. 3. After…"},"1691":{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#where-to-look-next","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"Where to look next","x":"- MMCA.ECommerce: the finished result of this guide, build- and test-verified. - Getting started: the single-module path, the vertical-slice templates (mmca-command /…"},"1692":{"u":"/docs/guides/common-GETTING-STARTED.html","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","x":"As of: 2026-10-01 (MMCA.Templates 1.12.0, framework v1.218.0). MMCA.Common is a .NET 10 framework for DDD, Clean Architecture, and CQRS, shipped as a set of lockstep-versioned…"},"1693":{"u":"/docs/guides/common-GETTING-STARTED.html#before-you-start","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Before you start","x":"- .NET 10 SDK. The framework targets net10.0 with LangVersion: preview for C extension types. - Docker Desktop. Aspire provisions SQL Server as a container, so you do not install…"},"1694":{"u":"/docs/guides/common-GETTING-STARTED.html#1-install-the-template-pack","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"1. Install the template pack","x":"Four templates arrive: mmca-app (a whole solution), mmca-module (a business module across all five layers), and mmca-command / mmca-query (a single vertical slice)."},"1695":{"u":"/docs/guides/common-GETTING-STARTED.html#2-generate-the-solution","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"2. Generate the solution","x":"Three names, and they are independent: the solution (also your root namespace), the first module in plural PascalCase, and that module's aggregate root in singular PascalCase.…"},"1696":{"u":"/docs/guides/common-GETTING-STARTED.html#3-build-and-test-before-you-change-anything","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"3. Build and test before you change anything","x":"That is a warning-free build with TreatWarningsAsErrors and all five analyzers at error severity, and a passing test run including the architecture-fitness rules, with no…"},"1697":{"u":"/docs/guides/common-GETTING-STARTED.html#4-check-the-first-migration","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"4. Check the first migration","x":"The scaffold ships the migrations project, its design-time factory, and (in the default shape) the sample's migrations already renamed for your aggregate. Look inside…"},"1698":{"u":"/docs/guides/common-GETTING-STARTED.html#5-run-it","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"5. Run it","x":"Run this from a real, interactive terminal. Launched from a headless or background shell the Aspire AppHost stalls at control-plane init and no dashboard appears. The dashboard…"},"1699":{"u":"/docs/guides/common-GETTING-STARTED.html#6-the-one-time-fixup","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"6. The one-time fixup","x":"The scaffold deliberately does not hand this one over, because renaming invalidates it and no fixed value is right for every name you could pick. It is covered in full in the…"},"1700":{"u":"/docs/guides/common-GETTING-STARTED.html#what-you-were-handed","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"What you were handed","x":"The Order aggregate arrives fully worked: a Result-returning factory, invariants, guarded mutations raising domain events, a child entity, soft-delete cascade, the caching pair,…"},"1701":{"u":"/docs/guides/common-GETTING-STARTED.html#add-your-next-feature","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Add your next feature","x":"A vertical slice (the path every feature follows) is one command, run from the module's UseCases folder: Handlers, validators, and mappers are convention-scanned, so there is no…"},"1702":{"u":"/docs/guides/common-GETTING-STARTED.html#surface-the-slice-at-the-edge","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Surface the slice at the edge","x":"The scaffold stops at the handler, and the template's closing instructions tell you to map the command in your module's controller. Every write in the generated app follows the…"},"1703":{"u":"/docs/guides/common-GETTING-STARTED.html#then-what","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Then what","x":"- Upgrade the framework. Bump every MMCA.Common. entry in Directory.Packages.props together, in one pass. See Phase 7 and the versioning policy. - Add real authentication. Copy…"},"1704":{"u":"/docs/guides/common-GETTING-STARTED.html#verification-checklist","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Verification checklist","x":"1. dotnet new mmca-app -n produced a solution that builds and tests green before you changed anything. 2. dotnet build .slnx is warning-free (TreatWarningsAsErrors + five…"},"1705":{"u":"/docs/guides/common-GETTING-STARTED.html#where-to-look-next","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Where to look next","x":"- Templates: every parameter of all four templates, dropping the Blazor UI host, and how the pack is built. ADR-065 explains why it is derived from the reference app rather than…"},"1706":{"u":"/docs/guides/common-RESILIENCE.html","d":"Resilience & Business Continuity (rubric §29)","k":"Guides & Specifications","x":"As of: 2026-09-15. MMCA.Common is a library, so it cannot operate a deployment: restores, RTO/RPO, and SLO alerting are executed in the consumer apps' IaC (e.g. MMCA.ADC's…"},"1707":{"u":"/docs/guides/common-RESILIENCE.html#what-the-framework-provides-and-verifies-in-repo","d":"Resilience & Business Continuity (rubric §29)","k":"Guides & Specifications","t":"What the framework provides (and verifies in-repo)","x":"Failure isolation, graceful degradation, graceful startup, and the restore procedure itself are therefore demonstrated and tested centrally: the framework drills backup→restore…"},"1708":{"u":"/docs/guides/common-RESILIENCE.html#caching-is-fail-open-configurable-never-load-bearing","d":"Resilience & Business Continuity (rubric §29)","k":"Guides & Specifications","t":"Caching is fail-open (configurable, never load-bearing)","x":"The cache is an optimization, never the system of record, so nothing about it can turn a cache problem into a failed request. A miss, an unreachable cache, or a populate that…"},"1709":{"u":"/docs/guides/common-RESILIENCE.html#baseline-slo--error-budget-template-consumers-fill-in","d":"Resilience & Business Continuity (rubric §29)","k":"Guides & Specifications","t":"Baseline SLO / error-budget template (consumers fill in)","x":"Adopt and tune per app; ADC's filled-in version lives in infra/DISASTER-RECOVERY.md + the SLO metric-alerts in infra/main.bicep. Define RTO/RPO per service (ADC's worked…"},"1710":{"u":"/docs/guides/common-RESILIENCE.html#restore-drill-runbook-reference","d":"Resilience & Business Continuity (rubric §29)","k":"Guides & Specifications","t":"Restore-drill runbook (reference)","x":"The only evidence backups actually restore is a periodic drill: restore a throwaway copy, confirm it comes back Online, record the measured restore time, then delete the copy.…"},"1711":{"u":"/docs/guides/common-RESPONSIVE.html","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","x":"As of: 2026-07-20. This document is the supported-device and browser matrix for the shared MMCA.Common.UI component library. It makes the responsive contract explicit (the rubric…"},"1712":{"u":"/docs/guides/common-RESPONSIVE.html#breakpoints","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","t":"Breakpoints","x":"The framework keeps C viewport detection and CSS media queries aligned around one mobile threshold. The C 960px mobile cutoff and the CSS 1023.98px cutoff intentionally differ:…"},"1713":{"u":"/docs/guides/common-RESPONSIVE.html#supported-devices","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","t":"Supported devices"},"1714":{"u":"/docs/guides/common-RESPONSIVE.html#touch-targets","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","t":"Touch targets","x":"Interactive controls on mobile surfaces meet a 48px minimum hit area (Material Design), exceeding both WCAG 2.5.8 Target Size (Minimum, AA, 24px) and WCAG 2.5.5 Target Size…"},"1715":{"u":"/docs/guides/common-RESPONSIVE.html#grid-density","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","t":"Grid density","x":"DataGridListPageBase exposes a DenseGrid property and a ToggleDensity() method. Derived list pages bind Dense=\"@DenseGrid\" on their MudDataGrid and surface a toggle. The chosen…"},"1716":{"u":"/docs/guides/common-RESPONSIVE.html#browser-matrix","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","t":"Browser matrix","x":"The shared UI is tested against three Playwright engines in CI (.github/workflows/ci.yml, ui-e2e job): a real-browser axe (WCAG 2.1 AA) + render smoke against the backend-less…"},"1717":{"u":"/docs/guides/common-SMALL-APPS.html","d":"Small Apps: the lowest floor MMCA.Common has","k":"Guides & Specifications","x":"As of: 2026-08-30. Two mmca-app options decide the shape of the solution rather than the shape of the sample module, and together they are the smallest thing the framework…"},"1718":{"u":"/docs/guides/common-SMALL-APPS.html#why-this-shape-exists","d":"Small Apps: the lowest floor MMCA.Common has","k":"Guides & Specifications","t":"Why this shape exists","x":"The default mmca-app output is a twelve-project solution: five module layers, a REST API host, a Blazor UI host, an Aspire AppHost, a migrations project, and three test projects.…"},"1719":{"u":"/docs/guides/common-SMALL-APPS.html#the-five-minute-path","d":"Small Apps: the lowest floor MMCA.Common has","k":"Guides & Specifications","t":"The five-minute path","x":"Add --flat if the aggregate owns no child collection; the four module-shape flags all still apply (see the templates guide). Warning-free under all five analyzers with…"},"1720":{"u":"/docs/guides/common-SMALL-APPS.html#what-you-get-anyway","d":"Small Apps: the lowest floor MMCA.Common has","k":"Guides & Specifications","t":"What you get anyway","x":"Nothing in the framework is disabled by this shape. The application code is identical to the SQL-Server-plus-Aspire output: the engine is a configuration-base choice plus a…"},"1721":{"u":"/docs/guides/common-SMALL-APPS.html#what-is-deliberately-off","d":"Small Apps: the lowest floor MMCA.Common has","k":"Guides & Specifications","t":"What is deliberately off","x":"- The transactional outbox. With in-process messaging (the default, since no MessageBus:Provider is configured) the outbox resolves off: events are dispatched inside the process…"},"1722":{"u":"/docs/guides/common-SMALL-APPS.html#adding-a-second-module","d":"Small Apps: the lowest floor MMCA.Common has","k":"Guides & Specifications","t":"Adding a second module","x":"mmca-module takes --database sqlserversqlite, and a solution generated by mmca-app adds its modules through build/add-module.ps1, which reads the engine off the API host's own…"},"1723":{"u":"/docs/guides/common-SMALL-APPS.html#two-limitations-worth-knowing-before-you-start","d":"Small Apps: the lowest floor MMCA.Common has","k":"Guides & Specifications","t":"Two limitations worth knowing before you start","x":"1. Writes need the X-Tenant-Id header. The generated app enables tenancy with RequireTenant: false, so an unresolved caller reads across tenants, but a write with no tenant…"},"1724":{"u":"/docs/guides/common-SMALL-APPS.html#when-to-flip-each-switch","d":"Small Apps: the lowest floor MMCA.Common has","k":"Guides & Specifications","t":"When to flip each switch","x":"Each of these is additive: the code you have written does not change, and the change is confined to configuration or to a host project. ---"},"1725":{"u":"/docs/guides/common-SMALL-APPS.html#where-to-look-next","d":"Small Apps: the lowest floor MMCA.Common has","k":"Guides & Specifications","t":"Where to look next","x":"- Getting started: the full-shape path, in six steps, with Aspire and SQL Server. - Templates: every parameter of all four templates, including the two solution-shape options…"},"1726":{"u":"/docs/guides/common-TEMPLATES.html","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","x":"As of: 2026-09-20 (MMCA.Templates 1.11.0, framework v1.206.0). MMCA.Templates is a dotnet new pack that scaffolds solutions, modules, and vertical slices on the MMCA.Common…"},"1727":{"u":"/docs/guides/common-TEMPLATES.html#mmca-app","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","t":"mmca-app","x":"The module and aggregate names are independent, so --module Billing --aggregate Invoice is fine. Everything derived from them follows: routes, the Aspire database resource, the…"},"1728":{"u":"/docs/guides/common-TEMPLATES.html#mmca-module","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","t":"mmca-module","x":"The six shape options behave exactly as they do for mmca-app, and they are per module: a solution can hold a flat, status-less catalog module beside one whose aggregate owns a…"},"1729":{"u":"/docs/guides/common-TEMPLATES.html#buildadd-moduleps1","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","t":"build/add-module.ps1","x":"Since 1.4.0 every solution mmca-app generates ships this script, and it is the supported way to add a second module. It runs mmca-module with your shape options passed through,…"},"1730":{"u":"/docs/guides/common-TEMPLATES.html#mmca-command-and-mmca-query","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","t":"mmca-command and mmca-query","x":"Run these from the module's UseCases folder. Each creates a folder named after the slice holding its two files. --child-collection exists because both handlers load through…"},"1731":{"u":"/docs/guides/common-TEMPLATES.html#how-the-pack-is-built","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","t":"How the pack is built","x":"The template content is the MMCA.Helpdesk reference application itself, staged at pack time. There is no second copy of the solution, so the template cannot drift from the app…"},"1732":{"u":"/docs/guides/common-VERSIONING.html","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","x":"As of: 2026-09-03. MMCA.Common publishes its NuGet packages (the authoritative list and count live in FACTS.md) versioned and released together as a single unit. They share one…"},"1733":{"u":"/docs/guides/common-VERSIONING.html#semantic-versioning","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"Semantic Versioning","x":"Versions follow SemVer 2.0: MAJOR.MINOR.PATCH: - MAJOR: reserved (see \"Breaking changes within 1.x\" below). - MINOR: new capability, and the channel breaking changes currently…"},"1734":{"u":"/docs/guides/common-VERSIONING.html#what-counts-as-breaking","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"What counts as breaking","x":"A change is breaking if it is any of: - Removing or renaming a public type/member, or changing a signature. - Changing the meaning of an existing configuration key, or changing a…"},"1735":{"u":"/docs/guides/common-VERSIONING.html#breaking-changes-within-1x","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"Breaking changes within 1.x","x":"Breaking changes ship as MINOR bumps, not MAJOR ones, and the version number is therefore not a reliable breakage signal on its own. This is deliberate and follows from the…"},"1736":{"u":"/docs/guides/common-VERSIONING.html#consumer-rollout","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"Consumer rollout","x":"Per project convention, framework upgrades are swept across all consumers in one pass: there are no opt-in flags or phased rollouts for a MMCA.Common change. When a release…"},"1737":{"u":"/docs/guides/common-VERSIONING.html#deprecation","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"Deprecation","x":"There is no [Obsolete] grace period today. Because the lockstep sweep updates every first-party caller in the same change set, a superseded API is removed in the release that…"},"1738":{"u":"/docs/guides/common-VERSIONING.html#supply-chain","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"Supply chain","x":"- All package versions are centrally pinned (Directory.Packages.props). - NuGet lock files are committed for reproducible restores. - MassTransit is pinned to v8 by policy (v9…"},"1739":{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","x":"As of: 2026-09-07. The automated layer (axe-core WCAG 2.1 AA scans in Tests/E2E/MMCA.Store.E2E.Tests/Workflows/AccessibilityTests.cs plus the shared Login/Register/Profile bases…"},"1740":{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html#tools","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"Tools","x":"Run against the app started via Aspire (dotnet run --project Source/Hosting/MMCA.Store.AppHost), reaching the Web UI at https://localhost:6002. Test with the keyboard only (no…"},"1741":{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html#what-to-verify-on-every-flow","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"What to verify on every flow","x":"1. Landmarks and headings. The SR's landmark/heading list (NVDA Insert+F7) exposes a logical banner / navigation / main structure and a sensible h1...hN outline (one h1 per…"},"1742":{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html#flows-to-cover-mirror-the-automated-accessibilitytests-plus-the-role-journeys","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"Flows to cover (mirror the automated AccessibilityTests plus the role journeys)"},"1743":{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html#results-log","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"Results log","x":"Record one row per pass. A flow with any unresolved blocker is a FAIL (open a backlog item for it)."},"1744":{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html#after-a-pass","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"After a pass","x":"- File any defect as a remediation item; cross-link it from the §21 row's evidence. - Update the dated row above so the scorecard can cite a real, current manual pass (this is…"},"1745":{"u":"/docs/guides/store-BusinessWorkflows.html","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","x":"As of: 2026-09-07."},"1746":{"u":"/docs/guides/store-BusinessWorkflows.html#workflow-list-summary","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"Workflow List Summary","x":"---"},"1747":{"u":"/docs/guides/store-BusinessWorkflows.html#1-identity-module-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"1. Identity Module Workflows","x":"Entry Point: POST /auth/register, AuthController.RegisterAsync(), AllowAnonymous Execution Path: Business Steps: 1. Validate registration input (email, password, first name, last…"},"1748":{"u":"/docs/guides/store-BusinessWorkflows.html#2-catalog-module-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"2. Catalog Module Workflows","x":"Entry Point: POST /categories, Admin only, [Idempotent] Response: 201 Created with CategoryDTO Entry Point: PUT /categories/{id}/name, Admin only Entry Point: PUT…"},"1749":{"u":"/docs/guides/store-BusinessWorkflows.html#3-sales-module-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"3. Sales Module Workflows","x":"Entry Point: POST /shoppingcarts/{customerId}/shoppingcartitems, Authenticated (owner or admin via OwnerOrAdminFilter) Decision Points: - Product variant doesn't exist - NotFound…"},"1750":{"u":"/docs/guides/store-BusinessWorkflows.html#4-ui-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"4. UI Workflows","x":"The UI provides a complete shopping experience through the CartDrawer component and Blazor pages. The CartDrawer is the only cart UI: there is no dedicated cart page. It is a…"},"1751":{"u":"/docs/guides/store-BusinessWorkflows.html#5-cross-module-interactions","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"5. Cross-Module Interactions","x":"Both export edges are best-effort: an unreachable peer degrades that one section rather than failing the export, so neither carries a startup wait. Asynchronously, four…"},"1752":{"u":"/docs/guides/store-BusinessWorkflows.html#6-external-interactions","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"6. External Interactions","x":"---"},"1753":{"u":"/docs/guides/store-BusinessWorkflows.html#7-cross-cutting-concerns-participating-in-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"7. Cross-Cutting Concerns Participating in Workflows","x":"---"},"1754":{"u":"/docs/guides/store-BusinessWorkflows.html#8-end-to-end-customer-journey","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"8. End-to-End Customer Journey","x":"Alternative Flows: - Payment fails - Order status PaymentFailed - customer can retry (create new Stripe session) - Cancel order - Status Cancelled (from PendingPayment,…"},"1755":{"u":"/docs/guides/store-BusinessWorkflows.html#9-potentially-missing-or-incomplete-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"9. Potentially Missing or Incomplete Workflows","x":"--- This document is derived from source code analysis. All workflows, decisions, and behaviors described above are confirmed implementations traceable to the referenced source…"},"1756":{"u":"/docs/guides/store-NavigationFlow.html","d":"Navigation Flow","k":"Guides & Specifications","x":"As of: 2026-09-25. This document maps the site navigation flow for each actor in the MMCA.Store application. Each mermaid diagram shows the pages accessible to that actor and the…"},"1757":{"u":"/docs/guides/store-NavigationFlow.html#actors","d":"Navigation Flow","k":"Guides & Specifications","t":"Actors","x":"Roles and enforcement: Admin is the only elevated role (registration creates a Customer). The 19 admin pages carry page-level [Authorize(Roles = \"Admin\")]. The three per-module…"},"1758":{"u":"/docs/guides/store-NavigationFlow.html#1-anonymous-user","d":"Navigation Flow","k":"Guides & Specifications","t":"1. Anonymous User","x":"Pages accessible without authentication: home, login, register, the two password-reset pages, and the public catalog. Add-to-cart on the product detail page sits inside an…"},"1759":{"u":"/docs/guides/store-NavigationFlow.html#2-customer-authenticated-user","d":"Navigation Flow","k":"Guides & Specifications","t":"2. Customer (Authenticated User)","x":"Inherits all anonymous pages. Gains the profile page, the cart drawer (a layout component, not a route), checkout, and their own orders. Unauthenticated visitors deep-linking to…"},"1760":{"u":"/docs/guides/store-NavigationFlow.html#3-admin","d":"Navigation Flow","k":"Guides & Specifications","t":"3. Admin","x":"Inherits all customer pages, plus the admin CRUD surfaces for all three modules and the user and role administration pages. Every page below except the shared /orders pair (see…"},"1761":{"u":"/docs/guides/store-NavigationFlow.html#authorization-model","d":"Navigation Flow","k":"Guides & Specifications","t":"Authorization Model","x":"Three cooperating layers; the API is always the boundary: 1. Page-level route guards. The 19 admin pages carry [Authorize(Roles = \"Admin\")] and /profile / /orders carry…"},"1762":{"u":"/docs/guides/store-Specification.html","d":"MMCA Business Specification Document","k":"Guides & Specifications","x":"As of: 2026-09-07."},"1763":{"u":"/docs/guides/store-Specification.html#1-system-overview","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"1. System Overview","x":"MMCA is an e-commerce platform built with .NET 10.0 using DDD and Clean Architecture. The business logic is organized as modules (Catalog, Sales, Identity) that have been…"},"1764":{"u":"/docs/guides/store-Specification.html#2-core-business-entities","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"2. Core Business Entities","x":"Description: A classification grouping for products. Supports hierarchical (parent-child) structures for nested categorization (e.g., \"Jewelry\" \"Rings\"). Key Properties:…"},"1765":{"u":"/docs/guides/store-Specification.html#3-business-workflows","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"3. Business Workflows","x":"Trigger: A new user submits registration with first name, last name, email, and password. Steps: 1. Validate registration request (email format, password requirements) 2. Verify…"},"1766":{"u":"/docs/guides/store-Specification.html#4-order-status-state-machine","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"4. Order Status State Machine","x":"Cancellable States: PendingPayment, PaymentInitiated, PaymentFailed. Shipped is deliberately NOT cancellable, for the same reason Paid is not: the goods and the money have both…"},"1767":{"u":"/docs/guides/store-Specification.html#5-business-rules","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"5. Business Rules","x":"---"},"1768":{"u":"/docs/guides/store-Specification.html#6-use-cases","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"6. Use Cases","x":"---"},"1769":{"u":"/docs/guides/store-Specification.html#7-domain-events-and-state-changes","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"7. Domain Events and State Changes","x":"Most mutations raise a single {Entity}Changed lifecycle event carrying a DomainEntityState discriminator (Added, Updated, Deleted) rather than one event per verb (ADR-083). Those…"},"1770":{"u":"/docs/guides/store-Specification.html#8-external-integrations","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"8. External Integrations","x":"Purpose: Processes online customer payments for orders. Business Impact: Enables the system to collect payments from customers and confirm payment success or failure…"},"1771":{"u":"/docs/guides/store-Specification.html#9-authorization-model","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"9. Authorization Model","x":"Permission-based endpoints: an endpoint states the capability it needs with [HasPermission(...)] over a constant from the owning module's {Module}Permissions, and each module…"},"1772":{"u":"/docs/guides/store-Specification.html#10-cross-module-communication","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"10. Cross-Module Communication","x":"The system enforces strict module boundaries. Modules communicate only through shared interface contracts: Across process boundaries these interfaces are satisfied by gRPC…"},"1773":{"u":"/docs/guides/store-Specification.html#11-user-interface","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"11. User Interface","x":"The UI is a Blazor Server + WebAssembly hybrid (InteractiveAuto render mode) using MudBlazor component library. It supports multiple hosting targets: - Web (Server + WASM):…"},"1774":{"u":"/docs/guides/store-Specification.html#12-cross-cutting-infrastructure","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"12. Cross-Cutting Infrastructure","x":"The IdempotencyFilter (applied via [Idempotent] attribute on Create endpoints) caches the first response for a given Idempotency-Key header value for 24 hours. Duplicate requests…"},"1775":{"u":"/docs/guides/store-Specification.html#13-testing","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"13. Testing","x":"- Full customer journey: Register - Browse - Add to Cart - Checkout - Admin Pay - Deliver - Order lifecycle: all state transitions including cancellation with inventory…"},"1776":{"u":"/docs/guides/store-Specification.html#14-missing-or-unclear-business-logic","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"14. Missing or Unclear Business Logic","x":"Observation: Four handlers send email today: the password-reset request (Section 3.12), OrderPaidHandler (payment receipt, which prints a struck \"Was\" amount and the promotion…"},"1777":{"u":"/docs/guides/store-Specification.html#15-seed-data-initial-system-state","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"15. Seed Data (Initial System State)","x":"The system seeds the following data at startup: Users: - Admin: one seeded administrator account (Admin role, no Customer record; credentials are environment-specific and not…"},"1778":{"u":"/articles/index.html","d":"The MMCA.Common article series","k":"The MMCA.Common series","x":"A long-form series that turns the MMCA.Common framework's architecture decisions into teachable patterns: the Result railway, the transactional outbox, database-per-service, JWKS…"},"1779":{"u":"/articles/index.html#the-series-53-articles","d":"The MMCA.Common article series","k":"The MMCA.Common series","t":"The series (53 articles)","x":"★ = cornerstone pillar (publish first; everything links up to these). The numbering is the reading order. Each article builds on the ones before it, so reading 1 through 53 in…"},"1780":{"u":"/articles/open-sourced-enterprise-dotnet-34-categories.html","d":"I open-sourced the enterprise .NET plumbing I never want to rewrite, and graded it against 34 categories","k":"The MMCA.Common series","x":"Subtitle: A .NET 10 framework for DDD, Clean Architecture, and CQRS, built as a modular monolith that extracts to microservices without a rewrite, and scored in the open against…"},"1781":{"u":"/articles/open-sourced-enterprise-dotnet-34-categories.html#what-it-is","d":"I open-sourced the enterprise .NET plumbing I never want to rewrite, and graded it against 34 categories","k":"The MMCA.Common series","t":"What it is","x":"MMCA.Common is a set of nineteen NuGet packages that give you a DDD + Clean Architecture + CQRS foundation as a modular monolith, plus the extraction boundaries to pull a module…"},"1782":{"u":"/articles/open-sourced-enterprise-dotnet-34-categories.html#the-one-thing-that-makes-it-different","d":"I open-sourced the enterprise .NET plumbing I never want to rewrite, and graded it against 34 categories","k":"The MMCA.Common series","t":"The one thing that makes it different","x":"Plenty of repositories will sell you Clean Architecture. The difference here is that the rules are not prose in a README. They are executable, and they fail the build. The…"},"1783":{"u":"/articles/open-sourced-enterprise-dotnet-34-categories.html#modular-monolith-now-microservices-later-no-rewrite","d":"I open-sourced the enterprise .NET plumbing I never want to rewrite, and graded it against 34 categories","k":"The MMCA.Common series","t":"Modular monolith now, microservices later, no rewrite","x":"The other thesis baked into the framework is that \"monolith vs microservices\" is a false binary. You build the extraction point now and cut the service when (if) you actually…"},"1784":{"u":"/articles/open-sourced-enterprise-dotnet-34-categories.html#i-scored-it-on-two-axes-then-published-every-gap","d":"I open-sourced the enterprise .NET plumbing I never want to rewrite, and graded it against 34 categories","k":"The MMCA.Common series","t":"I scored it on two axes, then published every gap","x":"Here is the part that usually gets left out of \"look at my framework\" posts. I graded MMCA.Common against a 34-category architecture rubric (backend, front-end, and…"},"1785":{"u":"/articles/open-sourced-enterprise-dotnet-34-categories.html#it-runs-in-production-on-two-real-apps","d":"I open-sourced the enterprise .NET plumbing I never want to rewrite, and graded it against 34 categories","k":"The MMCA.Common series","t":"It runs in production, on two real apps","x":"MMCA.Common is not a toy. Two deployed apps consume the same nineteen packages: - MMCA.ADC, the Atlanta Developers Conference app, deployed to Azure and used to run a live event.…"},"1786":{"u":"/articles/open-sourced-enterprise-dotnet-34-categories.html#what-this-series-will-cover","d":"I open-sourced the enterprise .NET plumbing I never want to rewrite, and graded it against 34 categories","k":"The MMCA.Common series","t":"What this series will cover","x":"Over the coming weeks I will take one pattern per article and go deep: - The Result railway that retired exceptions-as-control-flow. - The transactional outbox, in full. -…"},"1787":{"u":"/articles/open-sourced-enterprise-dotnet-34-categories.html#try-it-or-come-argue-with-me","d":"I open-sourced the enterprise .NET plumbing I never want to rewrite, and graded it against 34 categories","k":"The MMCA.Common series","t":"Try it, or come argue with me","x":"MMCA.Common is Apache-2.0 licensed and open source. The fastest way to form an opinion is to read the scorecard (the gaps are the honest part) and skim the ADRs (the \"why\" behind…"},"1788":{"u":"/articles/modular-monolith-to-microservices.html","d":"Modular monolith to microservices, without the rewrite","k":"The MMCA.Common series","x":"Subtitle: \"Monolith or microservices\" is the wrong question. Build the extraction point now, keep it tested, and cut the service later, on a boundary you have already proven. ---…"},"1789":{"u":"/articles/modular-monolith-to-microservices.html#why-it-matters","d":"Modular monolith to microservices, without the rewrite","k":"The MMCA.Common series","t":"Why it matters","x":"The reason the \"we will split it later\" plan usually fails is that the monolith quietly grows couplings that the eventual split has to unwind. Two modules share a database, so a…"},"1790":{"u":"/articles/modular-monolith-to-microservices.html#the-mmca-answer-four-extraction-points-all-enforced-in-the-monolith","d":"Modular monolith to microservices, without the rewrite","k":"The MMCA.Common series","t":"The MMCA answer: four extraction points, all enforced in the monolith","x":"There are four places a \"split it later\" plan leaks, and the framework closes each one with a real mechanism, not a guideline. The first leak is the shared database. In…"},"1791":{"u":"/articles/modular-monolith-to-microservices.html#what-the-boundary-looks-like-in-code","d":"Modular monolith to microservices, without the rewrite","k":"The MMCA.Common series","t":"What the boundary looks like in code","x":"The whole thesis fits in one DI swap. The same handler depends on the same interface; only the registration line differs between the two topologies. The transport choice is one…"},"1792":{"u":"/articles/modular-monolith-to-microservices.html#trade-offs-honestly","d":"Modular monolith to microservices, without the rewrite","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"Reversibility is not free, and the ADRs are candid about the bill. - Distributed-systems semantics arrive the moment you split. Cross-service consistency is eventual (outbox plus…"},"1793":{"u":"/articles/modular-monolith-to-microservices.html#apply-this-even-without-mmca","d":"Modular monolith to microservices, without the rewrite","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"You do not need this framework to keep the extraction point open. You need three habits. 1. Give each module its own logical data ownership now, even in one database. No…"},"1794":{"u":"/articles/34-category-architecture-rubric.html","d":"What good architecture actually means: a 34-category rubric you can score yourself against","k":"The MMCA.Common series","x":"Subtitle: \"Clean architecture\" is not a vibe. Here is a 34-category rubric that scores any system on two axes, demands evidence for every score, and exposes the one line that…"},"1795":{"u":"/articles/34-category-architecture-rubric.html#why-good-architecture-needs-a-rubric","d":"What good architecture actually means: a 34-category rubric you can score yourself against","k":"The MMCA.Common series","t":"Why \"good architecture\" needs a rubric","x":"The problem with architectural quality is that it is multi-dimensional and most of the dimensions are invisible until they bite you. SOLID discipline is invisible until a feature…"},"1796":{"u":"/articles/34-category-architecture-rubric.html#the-structure-three-parts-34-categories","d":"What good architecture actually means: a 34-category rubric you can score yourself against","k":"The MMCA.Common series","t":"The structure: three parts, 34 categories","x":"The rubric (Architecture Evaluation Criteria) is organized into three parts that match how a real system is actually built and operated. It stands at version 2 (ADR-110), which…"},"1797":{"u":"/articles/34-category-architecture-rubric.html#the-two-axes-maturity-and-implementation-evidence-or-it-does-not-count","d":"What good architecture actually means: a 34-category rubric you can score yourself against","k":"The MMCA.Common series","t":"The two axes: maturity and implementation, evidence or it does not count","x":"Here is the part that makes the rubric honest. Each category gets two scores, not one. - Maturity (0 to 4) measures process: how consistently and how well-governed the pattern…"},"1798":{"u":"/articles/34-category-architecture-rubric.html#how-the-index-is-computed","d":"What good architecture actually means: a 34-category rubric you can score yourself against","k":"The MMCA.Common series","t":"How the index is computed","x":"The scores roll up into two indices using per-category weights (defaults provided, adjustable per engagement). The maturity index is the sum of (category maturity × weight)…"},"1799":{"u":"/articles/34-category-architecture-rubric.html#the-worked-example-mmcacommon-scored-on-both-axes","d":"What good architecture actually means: a 34-category rubric you can score yourself against","k":"The MMCA.Common series","t":"The worked example: MMCA.Common, scored on both axes","x":"When I ran MMCA.Common through the rubric (the canonical, version-controlled scorecard lives in MMCA.Common: Architecture Scorecard), it landed at a maturity index of 97.0% and…"},"1800":{"u":"/articles/34-category-architecture-rubric.html#the-one-insight-worth-the-whole-exercise","d":"What good architecture actually means: a 34-category rubric you can score yourself against","k":"The MMCA.Common series","t":"The one insight worth the whole exercise","x":"When I lined the scores up, a single pattern explained almost all of the variance between the top tier and the middle tier, and it is the pattern that has driven every re-score…"},"1801":{"u":"/articles/34-category-architecture-rubric.html#trade-offs-honestly","d":"What good architecture actually means: a 34-category rubric you can score yourself against","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"A rubric is a tool, not an oracle, and it has sharp edges worth naming. - It can be gamed. If you score yourself, you can rationalize a 3 into a 4. The evidence requirement is…"},"1802":{"u":"/articles/34-category-architecture-rubric.html#apply-this-even-without-mmca","d":"What good architecture actually means: a 34-category rubric you can score yourself against","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"You do not need this framework, or even this exact rubric, to get the benefit: 1. Pick a fixed set of categories and score every system against the same set. Comparability is…"},"1803":{"u":"/articles/result-railway-csharp.html","d":"Stop throwing exceptions for control flow: the Result railway in C","k":"The MMCA.Common series","x":"Subtitle: \"This order ID does not exist\" is not an exceptional event, it is a routine branch of normal control flow. Here is the Result railway that models it as a value, and why…"},"1804":{"u":"/articles/result-railway-csharp.html#why-exceptions-are-not-a-control-flow-mechanism","d":"Stop throwing exceptions for control flow: the Result railway in C","k":"The MMCA.Common series","t":"Why exceptions are not a control-flow mechanism","x":"Exceptions are excellent at one job: aborting a computation that has gone genuinely wrong and unwinding the stack to someone who can recover. They are a poor fit for expected…"},"1805":{"u":"/articles/result-railway-csharp.html#the-mmca-answer-three-small-types-and-a-railway","d":"Stop throwing exceptions for control flow: the Result railway in C","k":"The MMCA.Common series","t":"The MMCA answer: three small types and a railway","x":"The pattern is factored into three dependency-free pieces, all living in MMCA.Common.Shared, the innermost layer (the Blazor WebAssembly UI can reference it without dragging in…"},"1806":{"u":"/articles/result-railway-csharp.html#trade-offs-honestly","d":"Stop throwing exceptions for control flow: the Result railway in C","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The Result pattern is not free, and ADR-013 names the rough edges rather than hiding them: - More ceremony at the call site than letting an exception bubble. Combine absorbs most…"},"1807":{"u":"/articles/result-railway-csharp.html#apply-this-even-without-mmca","d":"Stop throwing exceptions for control flow: the Result railway in C","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"You do not need this framework to adopt the railway. The ideas port to any stack: 1. Make failure visible in the signature. Return a result type (Result , OneOf, ErrorOr, your…"},"1808":{"u":"/articles/kill-anemic-domain-model.html","d":"Kill the anemic domain model: rich aggregates with factory methods that return Result","k":"The MMCA.Common series","x":"Subtitle: Public setters plus logic-in-services is not a domain model, it is a database row with extra steps. Here is the three-rung entity hierarchy in MMCA.Common that makes…"},"1809":{"u":"/articles/kill-anemic-domain-model.html#why-it-matters","d":"Kill the anemic domain model: rich aggregates with factory methods that return Result","k":"The MMCA.Common series","t":"Why it matters","x":"The cost is not theoretical. When the rules live outside the object: - Invalid state is representable. You can construct an Order with a negative total and no lines, because the…"},"1810":{"u":"/articles/kill-anemic-domain-model.html#the-mmca-answer-a-three-rung-hierarchy-one-capability-per-rung","d":"Kill the anemic domain model: rich aggregates with factory methods that return Result","k":"The MMCA.Common series","t":"The MMCA answer: a three-rung hierarchy, one capability per rung","x":"MMCA.Common builds every entity on a three-class inheritance chain. Read it bottom-up; each rung adds exactly one capability. BaseEntity is almost nothing: a single required init…"},"1811":{"u":"/articles/kill-anemic-domain-model.html#trade-offs-honestly","d":"Kill the anemic domain model: rich aggregates with factory methods that return Result","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"Rich aggregates are the right default, but they are not free, and the scorecard's §4 review names the gaps: - More boilerplate per entity. A private constructor, a static…"},"1812":{"u":"/articles/kill-anemic-domain-model.html#apply-this-even-without-mmca","d":"Kill the anemic domain model: rich aggregates with factory methods that return Result","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"You do not need this framework to drop the anemic model. The moves are portable: 1. Make constructors private and add a static factory that validates and returns a result type.…"},"1813":{"u":"/articles/specification-pattern.html","d":"Specifications over LINQ spaghetti: composable, reusable query intent","k":"The MMCA.Common series","x":"Subtitle: A .Where(s = s.SpeakerId == id && !s.IsDeleted && s.IsPublished) copied into nine controllers is nine places to forget the authorization clause. Here is the…"},"1814":{"u":"/articles/specification-pattern.html#why-it-matters","d":"Specifications over LINQ spaghetti: composable, reusable query intent","k":"The MMCA.Common series","t":"Why it matters","x":"Query predicates are not throwaway plumbing. Some of them encode business rules (\"a published event is one whose status is Published and whose start date has not passed\") and…"},"1815":{"u":"/articles/specification-pattern.html#the-mmca-answer-trusted-specifications-untrusted-filters-one-pipeline","d":"Specifications over LINQ spaghetti: composable, reusable query intent","k":"The MMCA.Common series","t":"The MMCA answer: trusted specifications, untrusted filters, one pipeline","x":"MMCA.Common splits the read side into three cooperating sub-systems, and the split is the point. ISpecification exposes two faces of the same rule: - a Criteria expression tree…"},"1816":{"u":"/articles/specification-pattern.html#trade-offs-honestly","d":"Specifications over LINQ spaghetti: composable, reusable query intent","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The pattern earns its keep, but a few real, specific gaps are worth stating plainly rather than glossing: - Two query vocabularies coexist. Specifications (typed,…"},"1817":{"u":"/articles/specification-pattern.html#apply-this-even-without-mmca","d":"Specifications over LINQ spaghetti: composable, reusable query intent","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The core ideas port to any stack and any ORM: 1. Give predicates a type and a name. A PublishedEvent specification object beats a copied .Where() lambda. Named intent is…"},"1818":{"u":"/articles/cqrs-decorator-pipeline.html","d":"The CQRS decorator pipeline: logging, caching, and transactions without touching a handler","k":"The MMCA.Common series","x":"Subtitle: Cross-cutting concerns belong around a handler, not inside it. Here is a Scrutor-composed decorator pipeline where logging, caching, and transactions wrap every command…"},"1819":{"u":"/articles/cqrs-decorator-pipeline.html#why-it-matters","d":"The CQRS decorator pipeline: logging, caching, and transactions without touching a handler","k":"The MMCA.Common series","t":"Why it matters","x":"The scattered approach fails in two specific ways. First, drift. When the transaction boilerplate lives in every handler, one handler eventually forgets it, or wraps the wrong…"},"1820":{"u":"/articles/cqrs-decorator-pipeline.html#the-shape-thin-handlers-fat-pipeline","d":"The CQRS decorator pipeline: logging, caching, and transactions without touching a handler","k":"The MMCA.Common series","t":"The shape: thin handlers, fat pipeline","x":"Every write and every read in MMCA.Common is a use case behind one of two interfaces: One method each. TResult is almost always a Result or Result (the railway error type from…"},"1821":{"u":"/articles/cqrs-decorator-pipeline.html#how-the-pipeline-is-assembled-and-why-order-is-the-whole-game","d":"The CQRS decorator pipeline: logging, caching, and transactions without touching a handler","k":"The MMCA.Common series","t":"How the pipeline is assembled, and why order is the whole game","x":"The wiring uses Scrutor's TryDecorate, and there is one rule you have to internalize: TryDecorate applies decorators in reverse registration order. The last one registered…"},"1822":{"u":"/articles/cqrs-decorator-pipeline.html#transactional-behavior-in-detail","d":"The CQRS decorator pipeline: logging, caching, and transactions without touching a handler","k":"The MMCA.Common series","t":"Transactional behavior, in detail","x":"The transactional decorator wraps the handler in IUnitOfWork.ExecuteInTransactionAsync only when the command implements ITransactional. Three outcomes, three behaviors: - An…"},"1823":{"u":"/articles/cqrs-decorator-pipeline.html#opt-in-by-marker-interface-pay-only-for-what-you-use","d":"The CQRS decorator pipeline: logging, caching, and transactions without touching a handler","k":"The MMCA.Common series","t":"Opt in by marker interface: pay only for what you use","x":"The pipeline is registered for every handler, but most decorators are dormant unless the use case asks for them, through a set of tiny marker interfaces: Each decorator does an…"},"1824":{"u":"/articles/cqrs-decorator-pipeline.html#the-di-sequence-that-makes-it-work","d":"The CQRS decorator pipeline: logging, caching, and transactions without touching a handler","k":"The MMCA.Common series","t":"The DI sequence that makes it work","x":"Because TryDecorate can only wrap registrations that already exist, the decorators must be registered after every module's concrete handlers. One step of this sequence is…"},"1825":{"u":"/articles/cqrs-decorator-pipeline.html#the-same-argument-one-layer-out-the-http-pipeline","d":"The CQRS decorator pipeline: logging, caching, and transactions without touching a handler","k":"The MMCA.Common series","t":"The same argument, one layer out: the HTTP pipeline","x":"The decorator chain covers everything that happens once a command or query reaches a handler. But the request had to cross an edge to get there, and that edge is an ordered chain…"},"1826":{"u":"/articles/cqrs-decorator-pipeline.html#trade-offs-honestly","d":"The CQRS decorator pipeline: logging, caching, and transactions without touching a handler","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The pipeline is not free, and ADR-014 names the rough edges: - Registration order is the reverse of execution order. This is a genuine Scrutor foot-gun. The framework mitigates…"},"1827":{"u":"/articles/cqrs-decorator-pipeline.html#apply-this-even-without-mmca","d":"The CQRS decorator pipeline: logging, caching, and transactions without touching a handler","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The idea ports to any DI container that supports decoration (Scrutor for Microsoft.Extensions.DI, or the equivalent in your stack): 1. Define a single handler interface per…"},"1828":{"u":"/articles/validation-kit.html","d":"Compose validators, don't copy them: a reusable FluentValidation kit","k":"The MMCA.Common series","x":"Subtitle: \"Email must be valid, password must be strong, name is required.\" Every command re-declares those rules, and then they drift. Here is a validation kit where the rules…"},"1829":{"u":"/articles/validation-kit.html#reusable-rule-fragments-composed-with-include","d":"Compose validators, don't copy them: a reusable FluentValidation kit","k":"The MMCA.Common series","t":"Reusable rule fragments, composed with Include","x":"The core idea is a rule fragment: a tiny AbstractValidator that owns exactly one field's contract, generic over the parent type T and configured with a selector expression.…"},"1830":{"u":"/articles/validation-kit.html#one-validator-for-the-request-free-for-the-command","d":"Compose validators, don't copy them: a reusable FluentValidation kit","k":"The MMCA.Common series","t":"One validator for the request, free for the command","x":"Most commands in this codebase are thin wrappers that carry a request DTO, for example CreateSessionCommand(CreateSessionRequest Request), and implement ICommandWithRequest (its…"},"1831":{"u":"/articles/validation-kit.html#a-validation-failure-is-a-result-and-the-edge-maps-it-to-400","d":"Compose validators, don't copy them: a reusable FluentValidation kit","k":"The MMCA.Common series","t":"A validation failure is a Result, and the edge maps it to 400","x":"A validator that runs is useless unless its output flows into the rest of the system cleanly. FluentValidation speaks in ValidationResult and ValidationFailure. The rest of the…"},"1832":{"u":"/articles/validation-kit.html#the-two-altitude-rule-validator-versus-domain-factory","d":"Compose validators, don't copy them: a reusable FluentValidation kit","k":"The MMCA.Common series","t":"The two-altitude rule: validator versus domain factory","x":"A reasonable objection: if the validator checks the input, why does the domain factory also return a Result and re-check things? Because they check at two different altitudes,…"},"1833":{"u":"/articles/validation-kit.html#trade-offs-honestly","d":"Compose validators, don't copy them: a reusable FluentValidation kit","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- It is a FluentValidation dependency. The kit is a thin composition layer over FluentValidation 12, not a hand-rolled engine. That is a deliberate trade (a mature, well-known…"},"1834":{"u":"/articles/validation-kit.html#apply-this-even-without-mmca","d":"Compose validators, don't copy them: a reusable FluentValidation kit","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any stack with a validation library that supports composition: 1. Write each field contract once as a fragment: a small validator generic over the parent…"},"1835":{"u":"/articles/transactional-outbox-dotnet.html","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","x":"Subtitle: \"Save to the database, then publish to the broker\" is a dual write with no atomicity. Here is the at-least-once pattern that fixes it, in one SaveChanges call. ---…"},"1836":{"u":"/articles/transactional-outbox-dotnet.html#why-it-matters","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"Why it matters","x":"The failure is rare per request and catastrophic in aggregate. It does not show up in tests, because tests do not kill the broker between two awaits. It shows up in production as…"},"1837":{"u":"/articles/transactional-outbox-dotnet.html#the-pattern-one-transaction-then-a-separate-delivery","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"The pattern: one transaction, then a separate delivery","x":"The transactional outbox splits the problem into two phases that are each individually safe: 1. Record intent atomically. When you save the order, you also write the event to an…"},"1838":{"u":"/articles/transactional-outbox-dotnet.html#how-mmcacommon-does-it-you-just-record-intent","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"How MMCA.Common does it: you just record intent","x":"The reason this pattern is often skipped is that wiring it by hand is tedious. In MMCA.Common it is automatic. Your aggregate records a domain event, and the framework does the…"},"1839":{"u":"/articles/transactional-outbox-dotnet.html#the-third-participant-in-that-sequence-the-change-trail","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"The third participant in that sequence: the change trail","x":"The first step of that sequence, \"stamp audit fields\", answers who touched a row last. It cannot answer what changed, because the stamp is a single overwrite: a row edited nine…"},"1840":{"u":"/articles/transactional-outbox-dotnet.html#when-the-commit-itself-is-ambiguous","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"When the commit itself is ambiguous","x":"Everything above rests on one commit, which makes it fair to ask what happens when the commit is the thing that fails. A commit can fail after the database has applied it but…"},"1841":{"u":"/articles/transactional-outbox-dotnet.html#the-delivery-side-outboxprocessor","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"The delivery side: OutboxProcessor","x":"A background service, OutboxProcessor, drains the outbox. The details that make it production-grade: - It only touches its own outbox. Under database-per-service, every…"},"1842":{"u":"/articles/transactional-outbox-dotnet.html#the-same-code-dispatches-in-process-and-over-a-broker","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"The same code dispatches in-process and over a broker","x":"Here is where the outbox earns its place in a framework whose whole thesis is \"monolith now, microservices later.\" The event you raised does not know how it will be delivered.…"},"1843":{"u":"/articles/transactional-outbox-dotnet.html#when-the-consumer-runs-out-of-retries","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"When the consumer runs out of retries","x":"The outbox makes the publish durable. It says nothing about what happens after the broker accepts the message, and that is the other half of delivery. MassTransit applies its own…"},"1844":{"u":"/articles/transactional-outbox-dotnet.html#the-write-that-skips-the-whole-pipeline","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"The write that skips the whole pipeline","x":"Everything above depends on one assumption: that writes go through SaveChangesAsync. There is a write that does not, and it is worth knowing before you reach for it, because the…"},"1845":{"u":"/articles/transactional-outbox-dotnet.html#trade-offs-honestly","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The outbox is not free, and the rubric review of this framework names the rough edges: - At-least-once means duplicates are possible. A consumer can receive the same event twice…"},"1846":{"u":"/articles/transactional-outbox-dotnet.html#apply-this-even-without-mmca","d":"The transactional outbox in .NET 10: never lose an event again","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The idea ports to any stack: 1. Write the event row in the same transaction as the state change. If your ORM cannot do that, you do not have an outbox, you have a hopeful second…"},"1847":{"u":"/articles/database-per-service.html","d":"Database-per-service inside a monolith (and why)","k":"The MMCA.Common series","x":"Subtitle: One DbContext class. Many databases. A host with no extra configuration behaves exactly like a single-database monolith, and the same code runs against per-service…"},"1848":{"u":"/articles/database-per-service.html#why-it-matters","d":"Database-per-service inside a monolith (and why)","k":"The MMCA.Common series","t":"Why it matters","x":"Two failures hide in the shared-database monolith, and both surface at the worst possible time. The first is the coupling itself. Once a foreign key spans two modules, those…"},"1849":{"u":"/articles/database-per-service.html#the-mmca-answer-one-context-class-one-instance-per-database","d":"Database-per-service inside a monolith (and why)","k":"The MMCA.Common series","t":"The MMCA answer: one context class, one instance per database","x":"The single most counter-intuitive fact, and the one every other choice flows from: there is exactly one concrete context class per engine (SQLServerDbContext,…"},"1850":{"u":"/articles/database-per-service.html#what-it-looks-like-in-configuration","d":"Database-per-service inside a monolith (and why)","k":"The MMCA.Common series","t":"What it looks like in configuration","x":"You do not write routing code. You write configuration, and the same compiled binary becomes a monolith or a set of isolated services depending on what is in appsettings.json.…"},"1851":{"u":"/articles/database-per-service.html#the-third-axis-which-customer-owns-the-row","d":"Database-per-service inside a monolith (and why)","k":"The MMCA.Common series","t":"The third axis: which customer owns the row","x":"Source name is one axis of partitioning, and the engine is another (that is the next article). Neither of them is a tenant. ADR-073 adds the third axis, \"which customer does this…"},"1852":{"u":"/articles/database-per-service.html#trade-offs-honestly","d":"Database-per-service inside a monolith (and why)","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"ADR-006 lists the bill in plain terms, and it is a real bill. - No distributed transactions. When a save spans sources, DbContextFactory.ExecuteInTransactionAsync opens a…"},"1853":{"u":"/articles/database-per-service.html#apply-this-even-without-mmca","d":"Database-per-service inside a monolith (and why)","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The mechanism is EF-specific, but the discipline ports to any stack. 1. Decide an entity's home logically, not physically. Route by module or bounded context, with a default that…"},"1854":{"u":"/articles/polyglot-persistence.html","d":"One entity model, four databases: polyglot persistence behind a single attribute","k":"The MMCA.Common series","x":"Subtitle: A domain entity should not know which database engine stores it. Here is a design where the same entity moves between SQL Server, PostgreSQL, Cosmos DB, and SQLite by…"},"1855":{"u":"/articles/polyglot-persistence.html#two-axes-not-one","d":"One entity model, four databases: polyglot persistence behind a single attribute","k":"The MMCA.Common series","t":"Two axes, not one","x":"This is the companion to the database-per-service article (Article 10), and the two are deliberately orthogonal. It helps to name the axes. Database-per-service (ADR-006) is the…"},"1856":{"u":"/articles/polyglot-persistence.html#the-configuration-hierarchy-where-the-engine-lives","d":"One entity model, four databases: polyglot persistence behind a single attribute","k":"The MMCA.Common series","t":"The configuration hierarchy: where the engine lives","x":"Every entity in the framework gets an EntityConfiguration class. The relevant inheritance chain is short and worth holding in your head. At the bottom is…"},"1857":{"u":"/articles/polyglot-persistence.html#the-entity-move-is-one-token","d":"One entity model, four databases: polyglot persistence behind a single attribute","k":"The MMCA.Common series","t":"The entity move is one token","x":"So what does it take to move Session from SQL Server to Cosmos? You change its configuration's base class from EntityTypeConfigurationSQLServer to EntityTypeConfigurationCosmos .…"},"1858":{"u":"/articles/polyglot-persistence.html#when-a-relationship-crosses-an-engine-boundary","d":"One entity model, four databases: polyglot persistence behind a single attribute","k":"The MMCA.Common series","t":"When a relationship crosses an engine boundary","x":"A document store cannot enforce a foreign key into a SQL table. So the moment Session moves to Cosmos while the Event it references stays in SQL Server, the relationship between…"},"1859":{"u":"/articles/polyglot-persistence.html#keeping-a-cross-source-predicate-translatable","d":"One entity model, four databases: polyglot persistence behind a single attribute","k":"The MMCA.Common series","t":"Keeping a cross-source predicate translatable","x":"Degrading the relationship solves the schema. It does not solve the query. Suppose you want all sessions whose event is published. Written the obvious way, that is a navigation:…"},"1860":{"u":"/articles/polyglot-persistence.html#the-honest-adoption-note","d":"One entity model, four databases: polyglot persistence behind a single attribute","k":"The MMCA.Common series","t":"The honest adoption note","x":"Here is the part most articles would quietly omit. All of the above is shipped and tested. None of it is running in production yet. Today, every current production entity…"},"1861":{"u":"/articles/polyglot-persistence.html#trade-offs-honestly","d":"One entity model, four databases: polyglot persistence behind a single attribute","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Cosmos has no JOINs, and the framework does not pretend otherwise. The whole resolve-then-filter-by-FK machinery exists because you cannot join across containers or across…"},"1862":{"u":"/articles/polyglot-persistence.html#apply-this-even-without-mmca","d":"One entity model, four databases: polyglot persistence behind a single attribute","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any stack with EF Core or a comparable configuration-driven ORM: 1. Keep the entity engine-free. The domain class is a plain object. The persistence engine…"},"1863":{"u":"/articles/navigation-populators.html","d":"EF Core Include chains are a trap: navigation populators decouple eager loading","k":"The MMCA.Common series","x":"Subtitle: Include(x).Include(y).ThenInclude(z) couples your read model to one physical database. Here is the eager-loading boundary that batch-loads related data and survives…"},"1864":{"u":"/articles/navigation-populators.html#why-it-matters","d":"EF Core Include chains are a trap: navigation populators decouple eager loading","k":"The MMCA.Common series","t":"Why it matters","x":"The cartesian explosion is the famous failure, and EF Core has a fix for it (AsSplitQuery), but split query is a band-aid on the deeper problem: Include is a JOIN, and a JOIN…"},"1865":{"u":"/articles/navigation-populators.html#the-mmca-answer-classify-then-batch-load-through-a-populator","d":"EF Core Include chains are a trap: navigation populators decouple eager loading","k":"The MMCA.Common series","t":"The MMCA answer: classify, then batch-load through a populator","x":"MMCA.Common splits the question into three responsibilities that live in three different layers, and no EF knowledge leaks upward. The Domain layer declares only what…"},"1866":{"u":"/articles/navigation-populators.html#the-boundary-that-survives-extraction","d":"EF Core Include chains are a trap: navigation populators decouple eager loading","k":"The MMCA.Common series","t":"The boundary that survives extraction","x":"Here is why this is not over-engineering. When you split a module out and a relationship's two ends move to different physical sources, the EF model's…"},"1867":{"u":"/articles/navigation-populators.html#trade-offs-honestly","d":"EF Core Include chains are a trap: navigation populators decouple eager loading","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"ADR-002 names the rough edges rather than hiding them. - Extra abstraction for the single-database case. For a pure single-SQL-Server host where Include always works, the…"},"1868":{"u":"/articles/navigation-populators.html#apply-this-even-without-mmca","d":"EF Core Include chains are a trap: navigation populators decouple eager loading","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any stack that might outgrow one database: 1. Stop assuming a relationship is a JOIN. Treat \"can these two be loaded together in one query?\" as a runtime…"},"1869":{"u":"/articles/optimistic-concurrency-rowversion.html","d":"Optimistic concurrency you cannot opt out of: RowVersion from the database to a required If-Match","k":"The MMCA.Common series","x":"Subtitle: Two admins open the same session, both edit it, both hit Save. Without a precondition the second write silently wins and the first admin's change is gone, with no…"},"1870":{"u":"/articles/optimistic-concurrency-rowversion.html#why-it-matters-and-why-it-is-not-idempotency","d":"Optimistic concurrency you cannot opt out of: RowVersion from the database to a required If-Match","k":"The MMCA.Common series","t":"Why it matters, and why it is not idempotency","x":"The lost update is a data-integrity bug that never announces itself. It does not throw. It does not log. It leaves a perfectly valid-looking row. You find out when the organizer…"},"1871":{"u":"/articles/optimistic-concurrency-rowversion.html#the-mmca-answer-a-database-token-handed-out-as-an-etag-demanded-back-as-a-precondition","d":"Optimistic concurrency you cannot opt out of: RowVersion from the database to a required If-Match","k":"The MMCA.Common series","t":"The MMCA answer: a database token, handed out as an ETag, demanded back as a precondition","x":"The mechanism is a concurrency token that the database manages, the read model exposes, the client states back in the If-Match header, and the persistence layer plants as EF's…"},"1872":{"u":"/articles/optimistic-concurrency-rowversion.html#the-detail-that-actually-makes-it-a-conflict","d":"Optimistic concurrency you cannot opt out of: RowVersion from the database to a required If-Match","k":"The MMCA.Common series","t":"The detail that actually makes it a conflict","x":"The interesting part is not \"store a version number.\" It is where the comparison happens. A hand-rolled \"read the current token, compare it to the client's, then save if they…"},"1873":{"u":"/articles/optimistic-concurrency-rowversion.html#from-exception-to-a-status-at-the-edge","d":"Optimistic concurrency you cannot opt out of: RowVersion from the database to a required If-Match","k":"The MMCA.Common series","t":"From exception to a status at the edge","x":"DbUpdateConcurrencyException is a DbUpdateException, and the framework already has one handler for that whole family. DbUpdateExceptionHandler translates any DbUpdateException…"},"1874":{"u":"/articles/optimistic-concurrency-rowversion.html#the-transport-a-weak-etag-out-a-required-if-match-back","d":"Optimistic concurrency you cannot opt out of: RowVersion from the database to a required If-Match","k":"The MMCA.Common series","t":"The transport: a weak ETag out, a required If-Match back","x":"The same RowVersion, the same SetOriginalRowVersion extension point, the same comparison inside the UPDATE. The header is where the token lives on the wire, which means anything…"},"1875":{"u":"/articles/optimistic-concurrency-rowversion.html#the-invariant-one-source-for-the-precondition","d":"Optimistic concurrency you cannot opt out of: RowVersion from the database to a required If-Match","k":"The MMCA.Common series","t":"The invariant: one source for the precondition","x":"A convention that lives only in a code-review checklist rots. So the one-transport rule is enforced mechanically. ArchitectureRules.UpdateRequestsAreNotConcurrencyAware scans…"},"1876":{"u":"/articles/optimistic-concurrency-rowversion.html#trade-offs-honestly","d":"Optimistic concurrency you cannot opt out of: RowVersion from the database to a required If-Match","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- The precondition is mandatory, and that is a real constraint. A caller that has not read the resource cannot write it. There is no null token, no wildcard escape, no…"},"1877":{"u":"/articles/optimistic-concurrency-rowversion.html#apply-this-even-without-mmca","d":"Optimistic concurrency you cannot opt out of: RowVersion from the database to a required If-Match","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any load-modify-save stack: 1. Put a database-managed concurrency token on your rows (SQL Server rowversion, a Postgres xmin, or an application-managed…"},"1878":{"u":"/articles/self-ordering-modules.html","d":"Self-ordering modules: discovered, Kahn-ordered, and extractable","k":"The MMCA.Common series","x":"Subtitle: A module declares its name and its dependencies. The framework discovers every module, sorts them with Kahn's algorithm, and registers them in an order where a…"},"1879":{"u":"/articles/self-ordering-modules.html#why-ordering-is-a-correctness-concern-not-a-style-one","d":"Self-ordering modules: discovered, Kahn-ordered, and extractable","k":"The MMCA.Common series","t":"Why ordering is a correctness concern, not a style one","x":"In MMCA.Common the registration order is not cosmetic. The CQRS pipeline wraps command and query handlers with decorators (logging, caching, validation, transactions) using…"},"1880":{"u":"/articles/self-ordering-modules.html#a-module-declares-intent-not-position","d":"Self-ordering modules: discovered, Kahn-ordered, and extractable","k":"The MMCA.Common series","t":"A module declares intent, not position","x":"A module in MMCA.Common is an IModule. The contract is deliberately small: Three of the five members ship with default implementations, so a minimal module implements only Name…"},"1881":{"u":"/articles/self-ordering-modules.html#the-engine-moduleloader-and-kahns-algorithm","d":"Self-ordering modules: discovered, Kahn-ordered, and extractable","k":"The MMCA.Common series","t":"The engine: ModuleLoader and Kahn's algorithm","x":"ModuleLoader is the piece that turns those declarations into an order. Its DiscoverAndRegister method scans the assemblies the host names in a required moduleAssemblies…"},"1882":{"u":"/articles/self-ordering-modules.html#disabling-a-module-without-breaking-its-consumers","d":"Self-ordering modules: discovered, Kahn-ordered, and extractable","k":"The MMCA.Common series","t":"Disabling a module without breaking its consumers","x":"Here is the part that turns this from a tidy registration trick into an extraction mechanism. A host can disable a module through configuration. ModulesSettings binds the…"},"1883":{"u":"/articles/self-ordering-modules.html#convention-scanning-what-a-modules-register-actually-wires","d":"Self-ordering modules: discovered, Kahn-ordered, and extractable","k":"The MMCA.Common series","t":"Convention scanning: what a module's Register actually wires","x":"Modules do not hand-register every handler. ScanModuleApplicationServices () is the Scrutor-based convention scan that a module's Add{X}Module calls to register, by assembly,…"},"1884":{"u":"/articles/self-ordering-modules.html#each-module-is-the-unit-of-extraction","d":"Self-ordering modules: discovered, Kahn-ordered, and extractable","k":"The MMCA.Common series","t":"Each module is the unit of extraction","x":"The reason all of this is worth the machinery is that the module is also the deploy and scale boundary, and the same self-ordering ModuleLoader registration runs at both ends of…"},"1885":{"u":"/articles/self-ordering-modules.html#trade-offs-honestly","d":"Self-ordering modules: discovered, Kahn-ordered, and extractable","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Reflection at startup. Discovery calls GetTypes() on every assembly the host named. This is a startup cost, paid once, and the per-GetTypes() guard means a load-time exception…"},"1886":{"u":"/articles/self-ordering-modules.html#apply-this-even-without-mmca","d":"Self-ordering modules: discovered, Kahn-ordered, and extractable","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports cleanly to any DI container: 1. Give each module a name and a declared dependency list, not a hard-coded registration position. 2. Topologically sort before you…"},"1887":{"u":"/articles/event-schema-versioning.html","d":"Event-schema versioning: never silently reshape an event","k":"The MMCA.Common series","x":"Subtitle: An integration event is a wire contract the moment it crosses a service boundary. Reshape it in place and you break consumers that are already deployed. Here is the…"},"1888":{"u":"/articles/event-schema-versioning.html#why-it-matters","d":"Event-schema versioning: never silently reshape an event","k":"The MMCA.Common series","t":"Why it matters","x":"In a monolith, an event handler and the code that raises the event compile together. Reshape the event and the consumer fails to compile; you fix it in the same commit. The type…"},"1889":{"u":"/articles/event-schema-versioning.html#the-mmca-answer-a-version-on-every-event-and-a-rule-for-changing-it","d":"Event-schema versioning: never silently reshape an event","k":"The MMCA.Common series","t":"The MMCA answer: a version on every event, and a rule for changing it","x":"The decision has two halves: a signal that is enforced, and a discipline for breaking changes. Half one: every integration event carries an explicit SchemaVersion. The base…"},"1890":{"u":"/articles/event-schema-versioning.html#how-it-complements-the-outbox","d":"Event-schema versioning: never silently reshape an event","k":"The MMCA.Common series","t":"How it complements the outbox","x":"This sits directly on top of the transactional outbox (ADR-003). The outbox guarantees an event is delivered at least once: persisted atomically with the state change, then…"},"1891":{"u":"/articles/event-schema-versioning.html#trade-offs-honestly","d":"Event-schema versioning: never silently reshape an event","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"ADR-010 is candid that the field is a signal, not a complete mechanism. - SchemaVersion by itself does not stop a consumer breaking on a real reshape. It is a value on the wire,…"},"1892":{"u":"/articles/event-schema-versioning.html#apply-this-even-without-mmca","d":"Event-schema versioning: never silently reshape an event","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The policy ports to any event-driven system with independently deployed consumers: 1. Put an explicit version on every event, and make it un-skippable. A test that fails the…"},"1893":{"u":"/articles/jwks-cross-service-auth.html","d":"Cross-service auth without a shared secret: JWKS dual-fetch","k":"The MMCA.Common series","x":"Subtitle: When you split a monolith into services, the easy answer is to copy the JWT signing secret into every validator. That is also the answer that turns one leaked key into…"},"1894":{"u":"/articles/jwks-cross-service-auth.html#why-it-matters","d":"Cross-service auth without a shared secret: JWKS dual-fetch","k":"The MMCA.Common series","t":"Why it matters","x":"Symmetric signing couples trust to secret distribution. The more services you have, the more copies of the forge-anything key exist, and the more places it can leak: a…"},"1895":{"u":"/articles/jwks-cross-service-auth.html#the-mmca-answer-publish-the-public-half-as-a-jwks-document","d":"Cross-service auth without a shared secret: JWKS dual-fetch","k":"The MMCA.Common series","t":"The MMCA answer: publish the public half as a JWKS document","x":"The framework's token service supports two signing algorithms, selected by the concrete JwtSettings.SigningAlgorithm, which defaults to RS256: - Monolith mode: HS256 (symmetric…"},"1896":{"u":"/articles/jwks-cross-service-auth.html#the-dual-fetch-and-the-graceful-empty-set","d":"Cross-service auth without a shared secret: JWKS dual-fetch","k":"The MMCA.Common series","t":"The dual-fetch, and the graceful empty set","x":"The dual-fetch ADR-004 describes is in the key discovery itself: a validator discovers the issuer's public key from the JWKS endpoint, and the framework falls back gracefully…"},"1897":{"u":"/articles/jwks-cross-service-auth.html#jwt-algorithm-pinning-the-attack-you-would-otherwise-ship","d":"Cross-service auth without a shared secret: JWKS dual-fetch","k":"The MMCA.Common series","t":"JWT algorithm pinning: the attack you would otherwise ship","x":"Switching to asymmetric keys opens a specific, classic attack, and the framework closes it explicitly. Once the RSA public key is published (which is the entire point of JWKS),…"},"1898":{"u":"/articles/jwks-cross-service-auth.html#trade-offs-honestly","d":"Cross-service auth without a shared secret: JWKS dual-fetch","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The JWKS layer is the right model, but it is not free, and the §11 review of the framework names the rough edges. - Two round trips on successful login. The Identity service's…"},"1899":{"u":"/articles/jwks-cross-service-auth.html#apply-this-even-without-mmca","d":"Cross-service auth without a shared secret: JWKS dual-fetch","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any stack with a JWT story: 1. In a single process, symmetric (HS256) is fine. Do not add asymmetric complexity you do not need. The signer and validator are…"},"1900":{"u":"/articles/password-hashing.html","d":"Password hashing done right: PBKDF2-SHA512, 600k iterations, timing-safe","k":"The MMCA.Common series","x":"Subtitle: Storing a password is a solved problem, and almost every breach headline is someone who solved it the wrong way. Here are the four decisions that separate a hash you…"},"1901":{"u":"/articles/password-hashing.html#why-it-matters","d":"Password hashing done right: PBKDF2-SHA512, 600k iterations, timing-safe","k":"The MMCA.Common series","t":"Why it matters","x":"A leaked password table is not just a breach of your app. People reuse passwords. The credentials you fail to protect become the keys an attacker tries against their bank, their…"},"1902":{"u":"/articles/password-hashing.html#the-mmca-answer-a-deliberately-slow-kdf-salted-compared-in-constant-time","d":"Password hashing done right: PBKDF2-SHA512, 600k iterations, timing-safe","k":"The MMCA.Common series","t":"The MMCA answer: a deliberately slow KDF, salted, compared in constant time","x":"MMCA.Common ships one production password hasher, split the Clean-Architecture way: the IPasswordHasher port sits in MMCA.Common.Application and its single PasswordHasher…"},"1903":{"u":"/articles/password-hashing.html#defense-in-depth-pii-at-rest-not-just-passwords","d":"Password hashing done right: PBKDF2-SHA512, 600k iterations, timing-safe","k":"The MMCA.Common series","t":"Defense in depth: PII at rest, not just passwords","x":"Passwords are the famous case, but they are not the only personal data sitting in your database. MMCA.Common ships field-level PII encryption as a separate control, now recorded…"},"1904":{"u":"/articles/password-hashing.html#trade-offs-honestly","d":"Password hashing done right: PBKDF2-SHA512, 600k iterations, timing-safe","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"A credible security post owns its rough edges, and a few sit around this hash rather than in it. - Plain-HTTP metadata discovery is an opt-out, not a default.…"},"1905":{"u":"/articles/password-hashing.html#apply-this-even-without-mmca","d":"Password hashing done right: PBKDF2-SHA512, 600k iterations, timing-safe","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The rules port to any stack, and they are short enough to memorize: 1. Never store plaintext, and never use a fast hash (MD5, SHA-1, plain SHA-256/512) for passwords. Use a…"},"1906":{"u":"/articles/idempotency-attribute.html","d":"Idempotency in one attribute: safe retries for HTTP APIs","k":"The MMCA.Common series","x":"Subtitle: Clients retry. Resilient HTTP handlers retry harder. A POST that creates a resource must survive being sent twice. Here is the whole thing in one [Idempotent]…"},"1907":{"u":"/articles/idempotency-attribute.html#why-it-matters","d":"Idempotency in one attribute: safe retries for HTTP APIs","k":"The MMCA.Common series","t":"Why it matters","x":"The non-idempotent POST is a quiet, expensive bug. It does not throw. It does not log an error. It produces a second perfectly valid-looking row. You find out when finance…"},"1908":{"u":"/articles/idempotency-attribute.html#the-mechanism-one-attribute-one-header","d":"Idempotency in one attribute: safe retries for HTTP APIs","k":"The MMCA.Common series","t":"The mechanism: one attribute, one header","x":"You mark an action [Idempotent]: The generic create action in the framework's controller bases carries [Idempotent] by default, so most write endpoints get it for free. The…"},"1909":{"u":"/articles/idempotency-attribute.html#the-concurrency-detail-that-actually-matters","d":"Idempotency in one attribute: safe retries for HTTP APIs","k":"The MMCA.Common series","t":"The concurrency detail that actually matters","x":"The interesting part is not \"cache the response.\" It is what happens when two duplicates arrive at the same time, before the first has finished. A naive cache check would let…"},"1910":{"u":"/articles/idempotency-attribute.html#the-same-problem-shows-up-on-the-broker-and-the-inbox-is-the-matching-mechanism","d":"Idempotency in one attribute: safe retries for HTTP APIs","k":"The MMCA.Common series","t":"The same problem shows up on the broker, and the inbox is the matching mechanism","x":"HTTP retries are one source of duplicates. The other is the message broker. MMCA.Common's outbox gives at-least-once delivery: an event may be published twice (the publish…"},"1911":{"u":"/articles/idempotency-attribute.html#trade-offs-honestly","d":"Idempotency in one attribute: safe retries for HTTP APIs","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Clients must send a stable key, from a stable identity. Idempotency is a contract, not magic. If a client generates a fresh key per retry, every retry is a \"new\" request and…"},"1912":{"u":"/articles/idempotency-attribute.html#apply-this-even-without-mmca","d":"Idempotency in one attribute: safe retries for HTTP APIs","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any HTTP stack: 1. Accept an Idempotency-Key header on write endpoints and require clients to keep it stable across retries. 2. Cache the first response…"},"1913":{"u":"/articles/self-invalidating-cache.html","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","x":"Subtitle: A read-through cache is easy. Keeping it correct is the hard part. Here is a cache where a write invalidates the reads it staled automatically, because invalidation…"},"1914":{"u":"/articles/self-invalidating-cache.html#why-invalidation-belongs-in-the-pipeline-not-the-handler","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"Why invalidation belongs in the pipeline, not the handler","x":"A handler's job is one use case. The fact that succeeding at that use case happens to stale some cached reads is a cross-cutting concern, not part of the use case. Tie the two…"},"1915":{"u":"/articles/self-invalidating-cache.html#the-cache-abstraction","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"The cache abstraction","x":"The whole subsystem is small and deliberate. The Application layer depends on one port: Four cache operations, a counter primitive, and one composed read-through helper.…"},"1916":{"u":"/articles/self-invalidating-cache.html#one-contract-three-backends","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"One contract, three backends","x":"AddCaching() picks a backend by probing the container. If a real distributed cache is registered (an IDistributedCache that is not the default MemoryDistributedCache, typically…"},"1917":{"u":"/articles/self-invalidating-cache.html#the-read-path-opt-in-by-marker-interface","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"The read path: opt in by marker interface","x":"A query caches by implementing IQueryCacheable (it supplies the cache key and the retention duration). The CachingQueryDecorator does the read-through: On a hit, the decorator…"},"1918":{"u":"/articles/self-invalidating-cache.html#the-write-path-invalidate-on-success-outside-the-transaction","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"The write path: invalidate on success, outside the transaction","x":"A command invalidates by implementing ICacheInvalidating, which exposes a CachePrefix. The CachingCommandDecorator runs the inner handler first, then evicts: The decorator calls…"},"1919":{"u":"/articles/self-invalidating-cache.html#swallowing-a-failure-is-a-discipline-not-a-catch","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"Swallowing a failure is a discipline, not a catch","x":"That swallow is the right call here and a dangerous habit everywhere else, so the framework gives it a name: BestEffort.ExecuteAsync(operation, logger, action, ct), for a side…"},"1920":{"u":"/articles/self-invalidating-cache.html#the-two-key-transformations-the-pipeline-applies","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"The two key transformations the pipeline applies","x":"Everything above treats the query's CacheKey and the command's CachePrefix as literal strings. At the decorator layer there are two exceptions, and they are the parts of key…"},"1921":{"u":"/articles/self-invalidating-cache.html#what-the-cache-is-and-is-not","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"What the cache is, and is not","x":"This is a request-result read-through cache for query handlers. It is not a session store and not a write-behind buffer. The cross-source consistency mechanism in this framework…"},"1922":{"u":"/articles/self-invalidating-cache.html#the-edge-tier-caching-authenticated-reads-without-leaking-identity","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"The edge tier: caching authenticated reads without leaking identity","x":"Tier 2 sits in front of the handler instead of inside the pipeline. MMCA.Common.API always calls app.UseOutputCache() but ships no policies: each host opts in. A read-heavy…"},"1923":{"u":"/articles/self-invalidating-cache.html#trade-offs-honestly","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Cache key correctness is the consumer's responsibility. The framework gives you read-through caching and prefix invalidation. It cannot know that GetProductQuery's key and…"},"1924":{"u":"/articles/self-invalidating-cache.html#apply-this-even-without-mmca","d":"The self-invalidating cache that lives in the pipeline, not your handlers","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any stack with a decoratable handler pipeline: 1. Put the cache behind a port the business layer depends on (Get, Set, Remove, RemoveByPrefix), and choose…"},"1925":{"u":"/articles/problem-details-http-grpc.html","d":"Problem Details across HTTP and gRPC (RFC 9457)","k":"The MMCA.Common series","x":"Subtitle: One error model, two transports. How a Result failure becomes the same RFC 9457 shape whether the caller reached you over HTTP or gRPC, and the honest gaps that still…"},"1926":{"u":"/articles/problem-details-http-grpc.html#why-it-matters","d":"Problem Details across HTTP and gRPC (RFC 9457)","k":"The MMCA.Common series","t":"Why it matters","x":"A consistent error contract is not cosmetic. It is the difference between a client that can branch on \"was this a validation problem, a not-found, or a conflict?\" mechanically,…"},"1927":{"u":"/articles/problem-details-http-grpc.html#the-mmca-answer-one-mapping-table-reused-on-both-transports","d":"Problem Details across HTTP and gRPC (RFC 9457)","k":"The MMCA.Common series","t":"The MMCA answer: one mapping table, reused on both transports","x":"MMCA.Common's whole error story starts from one decision recorded across the codebase: expected failures are values, not exceptions. Handlers return Result carrying an Error…"},"1928":{"u":"/articles/problem-details-http-grpc.html#the-rest-of-the-9-contract","d":"Problem Details across HTTP and gRPC (RFC 9457)","k":"The MMCA.Common series","t":"The rest of the §9 contract","x":"The same edge standardizes the things that drift if left to each endpoint: - Pagination and filtering. The generic read controllers expose GET /paged with filter, sort, and page…"},"1929":{"u":"/articles/problem-details-http-grpc.html#trade-offs-honestly","d":"Problem Details across HTTP and gRPC (RFC 9457)","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The scorecard scores §9 (API and Contract Design) at Maturity 4 of 4 and Implementation 9 of 10 (weighted 8/18). The category sits at the maturity ceiling: the contract is…"},"1930":{"u":"/articles/problem-details-http-grpc.html#apply-this-even-without-mmca","d":"Problem Details across HTTP and gRPC (RFC 9457)","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any stack with more than one client or more than one transport: 1. Model expected failures as typed values, not exceptions or ad-hoc status codes. A small…"},"1931":{"u":"/articles/notifications-vertical-slice.html","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","x":"Subtitle: A framework is mostly horizontal layers and abstract base classes. Notifications is the one concrete bounded context MMCA.Common ships, and it is built as a textbook…"},"1932":{"u":"/articles/notifications-vertical-slice.html#horizontal-layer-cut-vs-vertical-slice","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","t":"Horizontal layer cut vs. vertical slice","x":"The horizontal instinct is to organize by technical role. All the controllers in one folder, all the handlers in another, all the EF configurations in a third. The cost shows up…"},"1933":{"u":"/articles/notifications-vertical-slice.html#sending-a-push-notification-end-to-end","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","t":"Sending a push notification, end to end","x":"The send flow is the most instructive because it touches distinct consistency concerns: a deduplication check, a durable audit record, a per-user inbox, and two best-effort…"},"1934":{"u":"/articles/notifications-vertical-slice.html#retry-safety-on-two-levels-the-idempotency-filter-and-the-dedup-key","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","t":"Retry safety on two levels: the idempotency filter and the dedup key","x":"A broadcast is exactly the operation you never want to run twice, and the two ways it gets run twice are different problems. The client retries the HTTP call; or two retries…"},"1935":{"u":"/articles/notifications-vertical-slice.html#the-push-channel-an-adapter-chosen-at-composition-not-branched-at-runtime","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","t":"The push channel: an adapter chosen at composition, not branched at runtime","x":"The real-time channel is two cooperating types. NotificationHub is an [Authorize]d SignalR Hub. For this durable-push slice it contributes a ReceiveNotification method-name…"},"1936":{"u":"/articles/notifications-vertical-slice.html#the-native-push-channel-reaching-devices-the-hub-cannot","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","t":"The native push channel: reaching devices the hub cannot","x":"The SignalR leg stops at the edge of a connected client. A phone with the app backgrounded, killed, or offline hears nothing until the next launch, and conference announcements…"},"1937":{"u":"/articles/notifications-vertical-slice.html#the-in-app-inbox-pure-cqrs-readwrite-scoped-to-the-caller","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","t":"The in-app inbox: pure CQRS read/write, scoped to the caller","x":"The inbox is the durable channel, exposed by InboxController to any authenticated user (unlike the organizer-only send and history endpoints). Four slices back it: -…"},"1938":{"u":"/articles/notifications-vertical-slice.html#the-email-channel-and-the-feature-gate-over-all-of-it","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","t":"The email channel, and the feature gate over all of it","x":"Email is the smallest channel: one port IEmailSender and one SMTP adapter SmtpEmailSender, which constructs and disposes a fresh SmtpClient per send. It is independent of the…"},"1939":{"u":"/articles/notifications-vertical-slice.html#identifier-aliases-keep-the-ids-honest","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","t":"Identifier aliases keep the IDs honest","x":"A small but consistent detail: the notification entities do not type their keys as bare int. The slice uses solution-wide identifier aliases declared once in…"},"1940":{"u":"/articles/notifications-vertical-slice.html#trade-offs-honestly","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Best-effort real-time, durable record. The deliberate swallow of delivery exceptions means a push can fail silently as far as the WebSocket is concerned. That is the right call…"},"1941":{"u":"/articles/notifications-vertical-slice.html#apply-this-even-without-mmca","d":"Notifications as a vertical slice: in-app inbox, real-time push, native push, and email","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The vertical-slice discipline ports to any stack: 1. Organize by feature, not by technical role. Keep a feature's domain, use cases, adapters, and endpoints together so the…"},"1942":{"u":"/articles/live-channel-push.html","d":"Ephemeral by design: sub-second live channels over one SignalR hub","k":"The MMCA.Common series","x":"Subtitle: Conference-day polls and Q&A need small events fanned out to whoever is watching the page right now, and persisting them is wrong. Here is how MMCA.Common adds…"},"1943":{"u":"/articles/live-channel-push.html#two-message-shapes-not-two-transports","d":"Ephemeral by design: sub-second live channels over one SignalR hub","k":"The MMCA.Common series","t":"Two message shapes, not two transports","x":"The mistake in both approaches is treating \"durable notification\" and \"live update\" as if the difference lives in the transport. It does not. Both are small messages pushed over…"},"1944":{"u":"/articles/live-channel-push.html#the-mmca-answer-one-hub-two-publisher-paths","d":"Ephemeral by design: sub-second live channels over one SignalR hub","k":"The MMCA.Common series","t":"The MMCA answer: one hub, two publisher paths","x":"The single NotificationHub gains channel semantics. A channel is just a SignalR group named by a key like event:1 or session:123, and the hub gets its first client-invokable…"},"1945":{"u":"/articles/live-channel-push.html#joining-a-channel-and-why-the-key-is-validated","d":"Ephemeral by design: sub-second live channels over one SignalR hub","k":"The MMCA.Common series","t":"Joining a channel, and why the key is validated","x":"Channel membership is a property of the one existing connection, which is the point of not adding a second hub. On the browser side, NotificationHubService carries both traffic…"},"1946":{"u":"/articles/live-channel-push.html#crossing-a-service-boundary-the-grpc-ingress","d":"Ephemeral by design: sub-second live channels over one SignalR hub","k":"The MMCA.Common series","t":"Crossing a service boundary: the gRPC ingress","x":"Here is where the publisher abstraction earns its keep. In ADC the module that raises live events (Engagement, running the poll and Q&A handlers) is not the host that maps the…"},"1947":{"u":"/articles/live-channel-push.html#the-queue-is-where-you-say-what-the-work-is-worth","d":"Ephemeral by design: sub-second live channels over one SignalR hub","k":"The MMCA.Common series","t":"The queue is where you say what the work is worth","x":"The publish queue in this article is one instance of a general contract (ADR-052): work that outlives a request goes on a bounded Channel singleton, drained by a SingleReader…"},"1948":{"u":"/articles/live-channel-push.html#the-sibling-contract-work-a-clock-owns","d":"Ephemeral by design: sub-second live channels over one SignalR hub","k":"The MMCA.Common series","t":"The sibling contract: work a clock owns","x":"The queue above and the durable row beside it answer the same question: a request started work and must not wait for it. Neither can answer the other one. Nothing starts a…"},"1949":{"u":"/articles/live-channel-push.html#trade-offs-honestly","d":"Ephemeral by design: sub-second live channels over one SignalR hub","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The ADR names the sharp edges, and they are all consequences of the ephemeral contract, not accidents: - Ephemeral means lossy. A client that connects after an event was…"},"1950":{"u":"/articles/live-channel-push.html#apply-this-even-without-mmca","d":"Ephemeral by design: sub-second live channels over one SignalR hub","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The idea ports to any real-time stack: 1. Split on the delivery contract, not the transport. \"Must survive an offline user\" and \"only matters on screen\" are different guarantees.…"},"1951":{"u":"/articles/manual-dto-mapping.html","d":"Delete AutoMapper: explicit, compile-time DTO mapping that you can actually test","k":"The MMCA.Common series","x":"Subtitle: Reflection-based mappers feel like they save you work until the day a renamed property silently maps to null in production. Here is the alternative: named mapper…"},"1952":{"u":"/articles/manual-dto-mapping.html#the-case-against-convention-based-mapping","d":"Delete AutoMapper: explicit, compile-time DTO mapping that you can actually test","k":"The MMCA.Common series","t":"The case against convention-based mapping","x":"Be precise about what goes wrong with a reflection mapper, because \"I prefer explicit code\" is not an argument. Here are four concrete failure modes, all things a type system is…"},"1953":{"u":"/articles/manual-dto-mapping.html#two-roles-two-interfaces","d":"Delete AutoMapper: explicit, compile-time DTO mapping that you can actually test","k":"The MMCA.Common series","t":"Two roles, two interfaces","x":"The first design move is to notice that \"mapping\" is actually two different jobs that deserve two different contracts. Reading data out and accepting data in are not symmetric,…"},"1954":{"u":"/articles/manual-dto-mapping.html#why-the-request-mapper-returns-a-result","d":"Delete AutoMapper: explicit, compile-time DTO mapping that you can actually test","k":"The MMCA.Common series","t":"Why the request mapper returns a Result","x":"When you read an entity out, mapping cannot really fail. The data already passed validation when it was written; you are just reshaping a valid object. So the read mapper returns…"},"1955":{"u":"/articles/manual-dto-mapping.html#mapperly-explicit-and-fast-because-a-generator-writes-the-body","d":"Delete AutoMapper: explicit, compile-time DTO mapping that you can actually test","k":"The MMCA.Common series","t":"Mapperly: explicit and fast, because a generator writes the body","x":"The AutoMapper crowd will raise one objection immediately: if you write every mapper by hand, you are back to typing dto.Name = entity.Name; forty times per entity, and that is…"},"1956":{"u":"/articles/manual-dto-mapping.html#auto-discovery-you-write-the-mapper-you-do-not-wire-it","d":"Delete AutoMapper: explicit, compile-time DTO mapping that you can actually test","k":"The MMCA.Common series","t":"Auto-discovery: you write the mapper, you do not wire it","x":"The last piece is registration, and the framework keeps it convention-driven without making it reflection-at-runtime. Mappers are plain classes that implement one of the two…"},"1957":{"u":"/articles/manual-dto-mapping.html#a-concrete-pair","d":"Delete AutoMapper: explicit, compile-time DTO mapping that you can actually test","k":"The MMCA.Common series","t":"A concrete pair","x":"Putting it together, a module holds both directions. The pair below is trimmed from the Store Catalog module's real Product mappers (the delegating sub-mappers, the two…"},"1958":{"u":"/articles/manual-dto-mapping.html#trade-offs-honestly","d":"Delete AutoMapper: explicit, compile-time DTO mapping that you can actually test","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"This is not a free win, and pretending otherwise would be exactly the kind of hand-waving the framework is built to avoid. - It is more boilerplate than AutoMapper's zero-config…"},"1959":{"u":"/articles/manual-dto-mapping.html#apply-this-even-without-mmca","d":"Delete AutoMapper: explicit, compile-time DTO mapping that you can actually test","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"You do not need this framework to adopt the pattern. The ideas port to any stack: 1. Split read mapping from write mapping. They are not symmetric. Reading reshapes valid data;…"},"1960":{"u":"/articles/permission-based-authorization.html","d":"Permission-based authorization: capabilities over role checks","k":"The MMCA.Common series","x":"Subtitle: [Authorize(Roles = \"Organizer\")] couples every endpoint to a role name. The day you need a role that can do most of what an organizer does but not all of it, you are…"},"1961":{"u":"/articles/permission-based-authorization.html#why-it-matters","d":"Permission-based authorization: capabilities over role checks","k":"The MMCA.Common series","t":"Why it matters","x":"Authorization that is coupled to role names has a fixed failure mode: reshaping who-can-do-what means touching endpoints. Every time the org chart of your application changes (a…"},"1962":{"u":"/articles/permission-based-authorization.html#the-mmca-answer-a-registry-a-builder-an-attribute-and-the-gates-that-read-them","d":"Permission-based authorization: capabilities over role checks","k":"The MMCA.Common series","t":"The MMCA answer: a registry, a builder, an attribute, and the gates that read them","x":"The capability layer is a small set of parts, and the design goal running through all of them is that the mechanism grants nothing until a host opts in. The registry is the…"},"1963":{"u":"/articles/permission-based-authorization.html#inert-until-adopted","d":"Permission-based authorization: capabilities over role checks","k":"The MMCA.Common series","t":"Inert until adopted","x":"The piece that makes this safe to add to a framework everyone consumes is that wiring it confers nothing until a host grants something. AddAuthorizationPolicies() always…"},"1964":{"u":"/articles/permission-based-authorization.html#trade-offs-honestly","d":"Permission-based authorization: capabilities over role checks","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The capability indirection earns its keep on exactly the content-editor case, but it is still RBAC, and the §11 review is explicit about the edges. - It is RBAC, not ABAC. The…"},"1965":{"u":"/articles/permission-based-authorization.html#apply-this-even-without-mmca","d":"Permission-based authorization: capabilities over role checks","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern is portable to any policy-based authorization stack: 1. Name capabilities, not roles, on the endpoint. sessions:manage says what the route does; Roles = \"Organizer\"…"},"1966":{"u":"/articles/session-cookie-auth-blazor-ssr.html","d":"Browser session-cookie auth for Blazor SSR: surviving the F5","k":"The MMCA.Common series","x":"Subtitle: Your Blazor app authenticates with a bearer token. Then a user presses F5 on an [Authorize] page and gets bounced to the login screen even though they are signed in.…"},"1967":{"u":"/articles/session-cookie-auth-blazor-ssr.html#why-it-matters","d":"Browser session-cookie auth for Blazor SSR: surviving the F5","k":"The MMCA.Common series","t":"Why it matters","x":"This is the gap between \"the user has a valid session\" and \"the server rendering the first paint can see it.\" A pure bearer-header design has nowhere to put the session that the…"},"1968":{"u":"/articles/session-cookie-auth-blazor-ssr.html#the-mmca-answer-httponly-cookies-plus-an-ssr-time-reader","d":"Browser session-cookie auth for Blazor SSR: surviving the F5","k":"The MMCA.Common series","t":"The MMCA answer: HttpOnly cookies plus an SSR-time reader","x":"The mechanism ships in MMCA.Common.API (a SessionCookies/ folder) with a MMCA.Common.UI companion, and both apps' Web UI hosts wire it. It has four moving parts. Two HttpOnly…"},"1969":{"u":"/articles/session-cookie-auth-blazor-ssr.html#the-csrf-tax-and-how-it-is-paid","d":"Browser session-cookie auth for Blazor SSR: surviving the F5","k":"The MMCA.Common series","t":"The CSRF tax, and how it is paid","x":"Cookie-based auth reintroduces a concern that a pure bearer-header design does not have: a cross-site page can cause the browser to send your cookies. The framework pays that tax…"},"1970":{"u":"/articles/session-cookie-auth-blazor-ssr.html#scaling-out-the-key-ring-has-to-be-shared","d":"Browser session-cookie auth for Blazor SSR: surviving the F5","k":"The MMCA.Common series","t":"Scaling out: the key ring has to be shared","x":"One replica is a special case. Add a second and a failure appears that has nothing to do with the design above: ASP.NET Core's DataProtection key ring lives in memory, per…"},"1971":{"u":"/articles/session-cookie-auth-blazor-ssr.html#trade-offs-honestly","d":"Browser session-cookie auth for Blazor SSR: surviving the F5","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The pattern fixes a real gap, and the §26 review names what it costs. - A non-validating auth scheme exists in your codebase. SessionCookieAuthenticationHandler trusts a cookie…"},"1972":{"u":"/articles/session-cookie-auth-blazor-ssr.html#apply-this-even-without-mmca","d":"Browser session-cookie auth for Blazor SSR: surviving the F5","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The shape ports to any server-prerendered SPA with token auth: 1. Put the session in an HttpOnly cookie, not localStorage. The server can read a cookie during render; it cannot…"},"1973":{"u":"/articles/external-oauth-login.html","d":"Google, GitHub and Apple login without leaking tokens: external OAuth behind your own JWTs","k":"The MMCA.Common series","x":"Subtitle: You want \"Sign in with Google,\" \"Sign in with GitHub,\" and the \"Sign in with Apple\" button the store guidelines ask for. So you add the provider packages, wire the…"},"1974":{"u":"/articles/external-oauth-login.html#why-it-matters","d":"Google, GitHub and Apple login without leaking tokens: external OAuth behind your own JWTs","k":"The MMCA.Common series","t":"Why it matters","x":"The invariant the rest of the system leans on is small and load-bearing: inside the app, a user is always a local User carrying the app's own JWT. Every other auth concern is…"},"1975":{"u":"/articles/external-oauth-login.html#the-mmca-answer-external-oauth-behind-a-local-jwt-exchange","d":"Google, GitHub and Apple login without leaking tokens: external OAuth behind your own JWTs","k":"The MMCA.Common series","t":"The MMCA answer: external OAuth behind a local-JWT exchange","x":"The mechanism ships in MMCA.Common.API, and adoption is one app deep (more on that below). It has three moving parts and one rule: the tokens never touch the URL. Scheme…"},"1976":{"u":"/articles/external-oauth-login.html#the-same-handshake-now-for-native-heads","d":"Google, GitHub and Apple login without leaking tokens: external OAuth behind your own JWTs","k":"The MMCA.Common series","t":"The same handshake, now for native heads","x":"A mobile head cannot intercept a redirect to a web URL, and the providers reject OAuth inside an embedded WebView, so a MAUI app has to run the provider flow in the system…"},"1977":{"u":"/articles/external-oauth-login.html#finding-the-local-user-three-ways","d":"Google, GitHub and Apple login without leaking tokens: external OAuth behind your own JWTs","k":"The MMCA.Common series","t":"Finding the local user, three ways","x":"ExternalLoginAsync is where the external identity becomes one of your users, and it resolves in a deliberate order inside a single transaction. First it looks for a user already…"},"1978":{"u":"/articles/external-oauth-login.html#trade-offs-honestly","d":"Google, GitHub and Apple login without leaking tokens: external OAuth behind your own JWTs","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The pattern buys real safety, and ADR-036 is candid about what it costs. - Opt-in per app, and easy to half-wire. The flow needs four cooperating pieces: scheme registration, the…"},"1979":{"u":"/articles/external-oauth-login.html#apply-this-even-without-mmca","d":"Google, GitHub and Apple login without leaking tokens: external OAuth behind your own JWTs","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The shape ports to any app that wants social login without a second identity system: 1. Terminate federation at the edge. Exchange the external principal for your own token the…"},"1980":{"u":"/articles/jwt-refresh-token-rotation.html","d":"Refresh tokens that rotate per device, and reuse detection that makes theft self-limiting","k":"The MMCA.Common series","x":"Subtitle: A stateless JWT you cannot revoke has to be short-lived, which forces a refresh token, which is a long-lived bearer credential that can be stolen and replayed. Here is…"},"1981":{"u":"/articles/jwt-refresh-token-rotation.html#why-it-matters","d":"Refresh tokens that rotate per device, and reuse detection that makes theft self-limiting","k":"The MMCA.Common series","t":"Why it matters","x":"The two credentials pull in opposite directions, and you cannot collapse them. The access token wants to be stateless (no lookup on the hot path) which means it cannot be revoked…"},"1982":{"u":"/articles/jwt-refresh-token-rotation.html#the-mmca-answer-one-hashed-session-per-device-rotated-on-every-use","d":"Refresh tokens that rotate per device, and reuse detection that makes theft self-limiting","k":"The MMCA.Common series","t":"The MMCA answer: one hashed session per device, rotated on every use","x":"The whole workflow lives in one place: AuthenticationServiceBase (AuthenticationServiceBase.cs:74). Both apps' Identity modules subclass it and change nothing about the token…"},"1983":{"u":"/articles/jwt-refresh-token-rotation.html#both-apps-inherit-it-the-oauth-path-opens-the-same-session","d":"Refresh tokens that rotate per device, and reuse detection that makes theft self-limiting","k":"The MMCA.Common series","t":"Both apps inherit it; the OAuth path opens the same session","x":"The rotation, reuse detection, session cap, and lifetime logic are identical in ADC and Store because neither one owns a copy. ADC's AuthenticationService…"},"1984":{"u":"/articles/jwt-refresh-token-rotation.html#trade-offs-honestly","d":"Refresh tokens that rotate per device, and reuse detection that makes theft self-limiting","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"This model is a deliberate set of choices, and ADR-097 names the edges rather than hiding them. - Every device is its own row, so sign-ins grow a table. A session is a device…"},"1985":{"u":"/articles/jwt-refresh-token-rotation.html#apply-this-even-without-mmca","d":"Refresh tokens that rotate per device, and reuse detection that makes theft self-limiting","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any stack with a JWT story: 1. Keep the access token short and stateless. Validate it by signature and expiry with no lookup, and set a lifetime measured in…"},"1986":{"u":"/articles/generic-entity-controllers.html","d":"Generic entity controllers and the dynamic query contract (ADR-034)","k":"The MMCA.Common series","x":"Subtitle: The write-once REST surface every entity inherits. How list, page, lookup, by-id, create, and delete come from two base classes, plus a bounded OData-lite query…"},"1987":{"u":"/articles/generic-entity-controllers.html#why-it-matters","d":"Generic entity controllers and the dynamic query contract (ADR-034)","k":"The MMCA.Common series","t":"Why it matters","x":"Most entities need the same six things: list them, page through them, fetch a lightweight id/name list for a dropdown, get one by id, create one, delete one. That surface is so…"},"1988":{"u":"/articles/generic-entity-controllers.html#the-mmca-answer-two-base-classes-and-an-odata-lite-contract","d":"Generic entity controllers and the dynamic query contract (ADR-034)","k":"The MMCA.Common series","t":"The MMCA answer: two base classes and an OData-lite contract","x":"The read surface is EntityControllerBase . It is an [ApiController] with [Route(\"[controller]\")] and [ApiVersion(\"1.0\")], and it exposes five GET routes that any entity inherits…"},"1989":{"u":"/articles/generic-entity-controllers.html#the-line-where-the-users-value-stops-being-code","d":"Generic entity controllers and the dynamic query contract (ADR-034)","k":"The MMCA.Common series","t":"The line where the user's value stops being code","x":"There is one more constraint under all of this, and it is the one that decides whether a user-supplied filter DSL is a good idea or a liability. When a strategy builds its…"},"1990":{"u":"/articles/generic-entity-controllers.html#trade-offs-honestly","d":"Generic entity controllers and the dynamic query contract (ADR-034)","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"A generic, dynamically queryable contract coupled to the entity model is a real trade against narrow bespoke endpoints. ADR-034 names the costs rather than hiding them. - The…"},"1991":{"u":"/articles/generic-entity-controllers.html#apply-this-even-without-mmca","d":"Generic entity controllers and the dynamic query contract (ADR-034)","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any stack where you expose more than a handful of CRUD resources: 1. Put the CRUD shape on a generic base and close the type parameters per entity. The…"},"1992":{"u":"/articles/resource-ownership-authorization.html","d":"Resource-ownership authorization: which rows you may touch, not just which actions","k":"The MMCA.Common series","x":"Subtitle: Article 24 covered what a role or permission lets you do. It said, explicitly, that \"a customer may read only their own order\" is a different concern. This is that…"},"1993":{"u":"/articles/resource-ownership-authorization.html#why-it-matters","d":"Resource-ownership authorization: which rows you may touch, not just which actions","k":"The MMCA.Common series","t":"Why it matters","x":"The failure mode here has a name, and it is not obscure. The OWASP API Security Top 10 lists Broken Object Level Authorization (the modern label for the classic Insecure Direct…"},"1994":{"u":"/articles/resource-ownership-authorization.html#the-mmca-answer-two-enforcement-points-one-bypass","d":"Resource-ownership authorization: which rows you may touch, not just which actions","k":"The MMCA.Common series","t":"The MMCA answer: two enforcement points, one bypass","x":"The first instinct is to look for a single ownership abstraction. There is not one, and ADR-033 is explicit about why: there are two genuinely different problems wearing one…"},"1995":{"u":"/articles/resource-ownership-authorization.html#how-mmcastore-wires-it","d":"Resource-ownership authorization: which rows you may touch, not just which actions","k":"The MMCA.Common series","t":"How MMCA.Store wires it","x":"This is not framework-only theory. MMCA.Store turns both points on in production. The filter guards the shopping-cart and customer-profile controllers as a class-level…"},"1996":{"u":"/articles/resource-ownership-authorization.html#trade-offs-honestly","d":"Resource-ownership authorization: which rows you may touch, not just which actions","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The §11 review is blunt about the edges of this, and they are real. - It is claim-trusting, not ABAC. Both points key entirely on the configured owner claim (customerid by…"},"1997":{"u":"/articles/resource-ownership-authorization.html#apply-this-even-without-mmca","d":"Resource-ownership authorization: which rows you may touch, not just which actions","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern carries to any web API, framework or not: 1. Treat ownership as a second axis, not a corollary of the role check. \"May read orders\" and \"may read this order\" are…"},"1998":{"u":"/articles/rate-limiting-brute-force-protection.html","d":"Defending the API edge: three controls that cover the whole surface","k":"The MMCA.Common series","x":"Subtitle: A global \"N requests per minute\" limiter feels like edge protection until you notice what it cannot do: it cannot key an anonymous login attempt to a principal, because…"},"1999":{"u":"/articles/rate-limiting-brute-force-protection.html#why-it-matters","d":"Defending the API edge: three controls that cover the whole surface","k":"The MMCA.Common series","t":"Why it matters","x":"\"Add rate limiting\" is not a decision. The load-bearing questions are who gets limited, by what partition key, and what is exempt. A naive global IP bucket gets all three wrong…"},"2000":{"u":"/articles/rate-limiting-brute-force-protection.html#the-mmca-answer-an-authenticated-only-global-limiter-plus-two-controls-for-the-surface-it-exempts","d":"Defending the API edge: three controls that cover the whole surface","k":"The MMCA.Common series","t":"The MMCA answer: an authenticated-only global limiter, plus two controls for the surface it exempts","x":"AddCommonRateLimiting (in MMCA.Common.API, Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.cs:386) installs a GlobalLimiter (:430-431) that runs on…"},"2001":{"u":"/articles/rate-limiting-brute-force-protection.html#trade-offs-honestly","d":"Defending the API edge: three controls that cover the whole surface","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"None of the three is a complete edge defence on its own, and the §11 review is explicit about the boundaries. - Per-principal keying needs an authenticated identity, full stop.…"},"2002":{"u":"/articles/rate-limiting-brute-force-protection.html#apply-this-even-without-mmca","d":"Defending the API edge: three controls that cover the whole surface","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The shape ports to any ASP.NET Core service, with or without this framework: 1. Pick the partition key before you pick the limit. \"300 a minute\" is meaningless until you answer…"},"2003":{"u":"/articles/aspire-one-command.html","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","x":"Subtitle: Four service processes, four databases, a broker, a cache, a mail interceptor, and a live telemetry dashboard, all from a single dotnet run. Here is how MMCA.Common…"},"2004":{"u":"/articles/aspire-one-command.html#prerequisites","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Prerequisites","x":"You need a few things in place before the first run: - .NET 10 SDK (the framework targets .NET 10.0 with C preview language features). - Docker (or a compatible container…"},"2005":{"u":"/articles/aspire-one-command.html#step-1-run-the-whole-stack-with-one-command","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Step 1: run the whole stack with one command","x":"From the consumer repo, the entire local topology comes up with: That single command brings up everything the application needs locally: four SQL Server databases, Redis,…"},"2006":{"u":"/articles/aspire-one-command.html#step-2-understand-what-addservicedefaults-configured","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Step 2: understand what AddServiceDefaults() configured","x":"Each service host calls one method first in its Program.cs, before anything app-specific: AddServiceDefaults() is the service-side baseline, and it is identical across both…"},"2007":{"u":"/articles/aspire-one-command.html#step-3-read-the-health-endpoints-from-mapdefaultendpoints","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Step 3: read the health endpoints from MapDefaultEndpoints()","x":"MapDefaultEndpoints() exposes the probe surface your platform reads: - /health runs all registered checks. This is the one for humans and dashboards. It is served through…"},"2008":{"u":"/articles/aspire-one-command.html#step-4-see-how-the-apphost-wires-the-distributed-app","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Step 4: see how the AppHost wires the distributed app","x":"The AppHost Program.cs does not start anything immediately. It builds a resource model: a graph of containers, databases, services, the gateway, and the UI, with their dependency…"},"2009":{"u":"/articles/aspire-one-command.html#step-5-wire-the-extraction-points-grpc-and-jwks","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Step 5: wire the extraction points (gRPC and JWKS)","x":"The same AppHost wires the extraction boundaries that make the modular monolith behave like real microservices: typed gRPC clients and cross-service auth. gRPC references are…"},"2010":{"u":"/articles/aspire-one-command.html#step-6-make-the-telemetry-coherent-and-keep-the-bill-down","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Step 6: make the telemetry coherent, and keep the bill down","x":"There are a few more pieces worth knowing, because they shape what you see in the dashboard, and what you pay for it. Start with the noise. The OutboxProcessor background service…"},"2011":{"u":"/articles/aspire-one-command.html#step-7-harden-the-response-headers-and-reuse-connections","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Step 7: harden the response headers, and reuse connections","x":"Two smaller pieces round out the baseline, and both are the kind of thing each host would otherwise hand-roll and drift on. First, security headers at the host edge. Instead of…"},"2012":{"u":"/articles/aspire-one-command.html#step-8-refuse-to-boot-on-bad-configuration","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Step 8: refuse to boot on bad configuration","x":"One command bringing the stack up is only useful if a misconfigured process says so immediately. A bad settings value can surface in two places. At first use, where a missing…"},"2013":{"u":"/articles/aspire-one-command.html#trade-offs-and-gotchas-honestly","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Trade-offs and gotchas, honestly","x":"A single-command stack is a force multiplier, but it has edges worth naming: - It wants Docker and an interactive console. No container runtime means no SQL, Redis, or broker.…"},"2014":{"u":"/articles/aspire-one-command.html#apply-this-even-without-mmca","d":"Aspire: one command brings up the whole distributed app","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports cleanly to any Aspire app: 1. Put service defaults (telemetry, discovery, resilience) in one shared method every host calls, not copy-pasted per service. Drift…"},"2015":{"u":"/articles/extract-module-to-grpc-service.html","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","x":"Subtitle: A step-by-step walkthrough of lifting a module out of the monolith into its own gRPC service without rewriting application code. Define a contract, wire a typed client,…"},"2016":{"u":"/articles/extract-module-to-grpc-service.html#step-0-the-starting-invariant","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Step 0: the starting invariant","x":"The precondition that makes everything else possible: application and domain code already talk to abstractions, and transport choices live at the edges. Concretely, that means…"},"2017":{"u":"/articles/extract-module-to-grpc-service.html#step-1-define-the-contracts-project","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Step 1: define the .Contracts project","x":"The wire surface lives in its own project whose name ends in .Contracts. That suffix is load-bearing. Directory.Build.props gives any .Contracts project special treatment: it…"},"2018":{"u":"/articles/extract-module-to-grpc-service.html#step-2-server-side-addgrpcservicedefaults","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Step 2: server side, AddGrpcServiceDefaults()","x":"In the extracted Engagement service's host, register the gRPC server defaults and map the service: AddGrpcServiceDefaults() registers GrpcResultExceptionInterceptor plus gRPC…"},"2019":{"u":"/articles/extract-module-to-grpc-service.html#step-3-client-side-addtypedgrpcclienttclientservicename","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Step 3: client side, AddTypedGrpcClient(serviceName)","x":"On the calling side (Conference's host), wire the typed gRPC client and replace the local interface registration with the adapter: AddTypedGrpcClient (serviceName) wires the…"},"2020":{"u":"/articles/extract-module-to-grpc-service.html#step-4-flip-the-message-bus-from-in-process-to-broker","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Step 4: flip the message bus from in-process to broker","x":"Synchronous calls now cross the wire. The asynchronous, fire-and-forget flows (domain and integration events) need the same treatment, and the framework makes it a configuration…"},"2021":{"u":"/articles/extract-module-to-grpc-service.html#step-5-jwks-so-the-new-service-validates-tokens","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Step 5: JWKS so the new service validates tokens","x":"The extracted Engagement service now receives forwarded JWTs from Conference and must validate them. It does so against the issuer's JWKS, the public signing keys, not a shared…"},"2022":{"u":"/articles/extract-module-to-grpc-service.html#step-6-wire-it-in-the-apphost-with-mmcacommonaspirehosting","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Step 6: wire it in the AppHost with MMCA.Common.Aspire.Hosting","x":"Local orchestration is where the topology becomes runnable. MMCA.Common.Aspire.Hosting provides AppHost extension methods for the cross-cutting infrastructure of an extracted…"},"2023":{"u":"/articles/extract-module-to-grpc-service.html#step-7-route-through-the-yarp-gateway","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Step 7: route through the YARP gateway","x":"Clients never address a service directly. A single YARP reverse-proxy gateway owns the route-to-service map and is the only entry point. It has no DbContext and no controllers;…"},"2024":{"u":"/articles/extract-module-to-grpc-service.html#step-8-what-the-edge-process-itself-owns","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Step 8: what the edge process itself owns","x":"Step 7 gets a request to the right service. The edge process also has jobs of its own, and they are jobs no service behind it can do, because only the gateway sees every request…"},"2025":{"u":"/articles/extract-module-to-grpc-service.html#a-note-you-cannot-skip-adr-012s-two-kestrel-transport-profiles","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"A note you cannot skip: ADR-012's two Kestrel transport profiles","x":"On a cleartext endpoint there is no TLS, so there is no ALPN to negotiate the protocol. Kestrel must be told up front which protocols the cleartext port speaks, and the choice…"},"2026":{"u":"/articles/extract-module-to-grpc-service.html#the-whole-point-it-stays-reversible","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"The whole point: it stays reversible","x":"Walk the steps back and you see why this is not a one-way door. Because transport lives at the edge and the core talks to abstractions, a service can be re-collapsed into a…"},"2027":{"u":"/articles/extract-module-to-grpc-service.html#trade-offs-honestly","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Operational complexity multiplies. You now run multiple deployables plus a gateway, service discovery, a broker, and per-service databases instead of one process. Aspire hides…"},"2028":{"u":"/articles/extract-module-to-grpc-service.html#apply-this-even-without-mmca","d":"Extracting a module to a gRPC service, live","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The recipe ports to any stack: 1. Make the core talk to abstractions and put a fitness test on it. A message-bus interface and plain service interfaces, with an architecture test…"},"2029":{"u":"/articles/resilience-recovery-objectives.html","d":"Retries are not a recovery plan: resilience handlers, RTO/RPO, and a restore you actually drilled","k":"The MMCA.Common series","x":"Subtitle: The last article promised the resilience handler that makes retries happen, and the recovery objectives behind it. Here is both halves: the Polly pipeline on every…"},"2030":{"u":"/articles/resilience-recovery-objectives.html#half-one-the-standard-resilience-handler-on-every-outbound-client","d":"Retries are not a recovery plan: resilience handlers, RTO/RPO, and a restore you actually drilled","k":"The MMCA.Common series","t":"Half one: the standard resilience handler on every outbound client","x":"The runtime half is the part already shown elsewhere in this series, so I will summarize and attribute rather than re-derive it. Every outbound HTTP and gRPC client in the system…"},"2031":{"u":"/articles/resilience-recovery-objectives.html#fitness-enforced-not-just-present","d":"Retries are not a recovery plan: resilience handlers, RTO/RPO, and a restore you actually drilled","k":"The MMCA.Common series","t":"Fitness-enforced, not just present","x":"A convention that is \"applied everywhere by everyone remembering to\" is the same failure mode the idempotency and caching articles kept warning about. The framing across this…"},"2032":{"u":"/articles/resilience-recovery-objectives.html#the-one-place-resilience-left-the-outbound-client-the-outbox-broker-publish","d":"Retries are not a recovery plan: resilience handlers, RTO/RPO, and a restore you actually drilled","k":"The MMCA.Common series","t":"The one place resilience left the outbound client: the outbox broker publish","x":"For most of this series, \"resilience policy\" has meant \"outbound HTTP or gRPC client.\" ADR-087 (accepted 2026-08-18) amends ADR-009 with the first exception, and it is a narrow…"},"2033":{"u":"/articles/resilience-recovery-objectives.html#half-two-declared-rto-and-rpo-per-scenario","d":"Retries are not a recovery plan: resilience handlers, RTO/RPO, and a restore you actually drilled","k":"The MMCA.Common series","t":"Half two: declared RTO and RPO per scenario","x":"Now the part that is actually missing from most \"resilience\" treatments, and the part this article exists to document. ADR-009 requires each consuming app to declare, in writing,…"},"2034":{"u":"/articles/resilience-recovery-objectives.html#the-drilled-restore-backups-you-tested-not-backups-you-assumed","d":"Retries are not a recovery plan: resilience handlers, RTO/RPO, and a restore you actually drilled","k":"The MMCA.Common series","t":"The drilled restore: backups you tested, not backups you assumed","x":"The second word in \"declared RTO/RPO and a drilled restore\" is the one that separates a posture from a wish. The backup posture in ADC's runbook is two tiers. Point-in-time…"},"2035":{"u":"/articles/resilience-recovery-objectives.html#database-per-service-multiplies-the-restore-surface","d":"Retries are not a recovery plan: resilience handlers, RTO/RPO, and a restore you actually drilled","k":"The MMCA.Common series","t":"Database-per-service multiplies the restore surface","x":"There is a structural reason recovery is harder here than in a monolith, and it is the direct cost of a decision celebrated elsewhere in this series. ADR-006…"},"2036":{"u":"/articles/resilience-recovery-objectives.html#the-dr-runbook-and-the-deploy-time-auto-revert","d":"Retries are not a recovery plan: resilience handlers, RTO/RPO, and a restore you actually drilled","k":"The MMCA.Common series","t":"The DR runbook and the deploy-time auto-revert","x":"The runbook governs four per-service databases, ADCIdentity, ADCConference, ADCEngagement and ADCNotification, and those four are the entire application data estate: one Basic…"},"2037":{"u":"/articles/resilience-recovery-objectives.html#trade-offs-honestly","d":"Retries are not a recovery plan: resilience handlers, RTO/RPO, and a restore you actually drilled","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- RTO/RPO are only as real as the last drill. ADC's objectives are declared with specific numbers, and the restore is drilled repeatedly: the drill table records six passing…"},"2038":{"u":"/articles/resilience-recovery-objectives.html#apply-this-even-without-mmca","d":"Retries are not a recovery plan: resilience handlers, RTO/RPO, and a restore you actually drilled","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The posture ports to any stack, and most of it is discipline rather than code: 1. Put the resilience handler in one shared place every client inherits (the HttpClient/gRPC…"},"2039":{"u":"/articles/architecture-fitness-functions.html","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","x":"Subtitle: The inward-dependency rule everyone agrees on is the one everyone eventually breaks. Here is how MMCA.Common enforces it twice, defines the rules once, and makes them…"},"2040":{"u":"/articles/architecture-fitness-functions.html#why-a-diagram-is-not-enforcement","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","t":"Why a diagram is not enforcement","x":"The inward-dependency rule is the load-bearing wall of Clean Architecture. The whole value proposition, business logic that is independent of frameworks, UI, and data stores,…"},"2041":{"u":"/articles/architecture-fitness-functions.html#enforced-twice-compile-time-and-runtime","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","t":"Enforced twice: compile-time and runtime","x":"MMCA.Common enforces the dependency rules through two independent gates, and the redundancy is deliberate (MMCA.Common/CLAUDE.md, Architecture Enforcement section). 1.…"},"2042":{"u":"/articles/architecture-fitness-functions.html#defined-once-identical-everywhere","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","t":"Defined once, identical everywhere","x":"Here is the part that makes the fitness functions scale across more than one repo without rotting into three slightly-different copies. The rule bodies do not live in the test…"},"2043":{"u":"/articles/architecture-fitness-functions.html#the-rules-that-matter-most","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","t":"The rules that matter most","x":"Three example rule families show what fitness functions actually assert. LayerDependencyTests. The inward-dependency rule: API reaches down to Infrastructure, Infrastructure to…"},"2044":{"u":"/articles/architecture-fitness-functions.html#freezing-the-wire-the-contract-no-single-repos-build-can-see","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","t":"Freezing the wire: the contract no single repo's build can see","x":"A .proto file is not source in the ordinary sense. It is a published contract, and what makes it dangerous is that breaking it looks exactly like editing a local file. Renumber a…"},"2045":{"u":"/articles/architecture-fitness-functions.html#the-other-kind-of-fitness-test-documentation-as-a-contract","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","t":"The other kind of fitness test: documentation as a contract","x":"Everything above tests code against code. There is a second family that tests documentation against code, and it is the one that stops a doc from rotting the moment someone ships…"},"2046":{"u":"/articles/architecture-fitness-functions.html#the-same-family-aimed-at-operations-every-alert-needs-a-runbook","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","t":"The same family, aimed at operations: every alert needs a runbook","x":"The newest member of that family points the idea at your on-call rotation. An alert with no triage steps is a page with no next step, and a runbook section for an alert that no…"},"2047":{"u":"/articles/architecture-fitness-functions.html#the-third-kind-a-fitness-function-for-cost","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","t":"The third kind: a fitness function for cost","x":"Structural rules assert shape. Documentation rules assert honesty. There is a third thing worth failing a build over, and it is the one most teams leave to a dashboard: cost.…"},"2048":{"u":"/articles/architecture-fitness-functions.html#trade-offs-honestly","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"Fitness functions are not free, and the scorecard does not pretend otherwise. - They only cover what you write a rule for. Read the scorecard category by category and the pattern…"},"2049":{"u":"/articles/architecture-fitness-functions.html#apply-this-even-without-mmca","d":"Architecture fitness functions: rules that fail the build, not a wiki page","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any stack and most of it is one afternoon of work: 1. Pick the one rule you would be most upset to see violated silently. For most teams that is the…"},"2050":{"u":"/articles/test-pyramid.html","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","x":"Subtitle: A suite you run constantly is worth more than a suite you run nervously. Here is how MMCA.Common keeps 2,254 tests fast enough to live in the inner loop, with…"},"2051":{"u":"/articles/test-pyramid.html#why-the-shape-matters-more-than-the-count","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","t":"Why the shape matters more than the count","x":"The test-count number is not the point. The shape is. The rubric (Architecture Evaluation Criteria, category 14) describes a healthy pyramid as \"many fast unit tests on domain…"},"2052":{"u":"/articles/test-pyramid.html#the-base-fast-pure-no-infrastructure","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","t":"The base: fast, pure, no infrastructure","x":"The wide base of the MMCA.Common pyramid is pure unit tests with no I/O (28. Testing & Quality Infrastructure): domain entity factories and invariants, value objects, the…"},"2053":{"u":"/articles/test-pyramid.html#the-middle-real-sqlite-two-real-databases-still-no-docker","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","t":"The middle: real SQLite, two real databases, still no Docker","x":"Above the base sit the persistence and EF-configuration tests, and this is where the \"no Docker\" claim gets interesting. Rather than mock the ORM (which proves nothing about…"},"2054":{"u":"/articles/test-pyramid.html#the-cap-and-an-orthogonal-tier","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","t":"The cap, and an orthogonal tier","x":"At the top of the pyramid are the component tests (bUnit, rendering Blazor components in isolation) and the end-to-end tests (Playwright, driving a real browser). These are…"},"2055":{"u":"/articles/test-pyramid.html#shipped-test-packages-consumers-do-not-re-invent-the-harness","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","t":"Shipped test packages: consumers do not re-invent the harness","x":"Five of the framework's nineteen published NuGet packages are test infrastructure, not test code, and this is how the pyramid stays consistent across three codebases instead of…"},"2056":{"u":"/articles/test-pyramid.html#the-apphost-tier-a-compiled-claim-about-the-composition","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","t":"The AppHost tier: a compiled claim about the composition","x":"There is one file the tiers above never execute: the AppHost. It is the only place that states how a whole stack fits together, which project resources exist, which database each…"},"2057":{"u":"/articles/test-pyramid.html#accessibility-as-a-shipped-test-contract","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","t":"Accessibility as a shipped test contract","x":"The E2E package carries one more contract, and it is the tier where a published standard turns into a build failure instead of a review comment. ADR-063 records it: WCAG 2.1 AA…"},"2058":{"u":"/articles/test-pyramid.html#runner-and-gate-xunit-v3-mtp-and-a-coverage-floor","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","t":"Runner and gate: xUnit v3, MTP, and a coverage floor","x":"A detail that trips up people coming from older .NET projects: the suite runs on the Microsoft Testing Platform (MTP), not VSTest, configured in global.json, with xUnit v3,…"},"2059":{"u":"/articles/test-pyramid.html#trade-offs-honestly","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The shape is right, but the scorecard names real gaps and one is squarely in the testing story. - The UI tier's visual check is markup-deep, not pixel-deep. The framework ships…"},"2060":{"u":"/articles/test-pyramid.html#apply-this-even-without-mmca","d":"The test pyramid, not the ice-cream cone: 2,254 fast tests, zero Docker","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The shape ports to any stack: 1. Make the base fast enough to run on every save. No Docker, no network, no shared database in the layer you run constantly. If your unit tests…"},"2061":{"u":"/articles/soft-delete-vs-erasure.html","d":"Soft-delete vs the right to erasure: the GDPR conflict and the erasure pathway","k":"The MMCA.Common series","x":"Subtitle: Soft-delete is the right default for lifecycle and audit. It is also, by itself, illegal for personal data under GDPR. This is the story of scoring that conflict 1 out…"},"2062":{"u":"/articles/soft-delete-vs-erasure.html#why-it-matters-and-why-i-am-telling-on-myself","d":"Soft-delete vs the right to erasure: the GDPR conflict and the erasure pathway","k":"The MMCA.Common series","t":"Why it matters, and why I am telling on myself","x":"When I first graded MMCA.Common against an architecture rubric and committed the scorecard to the repo, the original single-axis snapshot scored Compliance, Privacy & Data…"},"2063":{"u":"/articles/soft-delete-vs-erasure.html#the-mmca-answer-separate-the-two-concerns-give-each-a-mechanism","d":"Soft-delete vs the right to erasure: the GDPR conflict and the erasure pathway","k":"The MMCA.Common series","t":"The MMCA answer: separate the two concerns, give each a mechanism","x":"The wrong fix is to overload Delete() so it hard-deletes personal data. That would break audit, undelete, and referential integrity all at once, trading one defect for three.…"},"2064":{"u":"/articles/soft-delete-vs-erasure.html#the-delete-that-really-removes-rows-restrict-by-default","d":"Soft-delete vs the right to erasure: the GDPR conflict and the erasure pathway","k":"The MMCA.Common series","t":"The delete that really removes rows: restrict by default","x":"Soft-delete keeps the row. Anonymization keeps the row and destroys the person inside it. Physical erasure, a real DELETE that takes rows out of the table, is a third thing, and…"},"2065":{"u":"/articles/soft-delete-vs-erasure.html#the-second-hiding-place-the-outbox","d":"Soft-delete vs the right to erasure: the GDPR conflict and the erasure pathway","k":"The MMCA.Common series","t":"The second hiding place: the outbox","x":"There was a second source of retained personal data, and it is the kind of thing you only find when you go looking honestly. The transactional outbox (ADR-003) writes an event…"},"2066":{"u":"/articles/soft-delete-vs-erasure.html#the-live-session-cutting-off-a-token-mid-flight","d":"Soft-delete vs the right to erasure: the GDPR conflict and the erasure pathway","k":"The MMCA.Common series","t":"The live session: cutting off a token mid-flight","x":"Anonymizing the aggregate and committing the soft-delete shuts two doors: no future logins, and no silent re-mint of a fresh access token, because the refresh flow re-fetches the…"},"2067":{"u":"/articles/soft-delete-vs-erasure.html#the-other-right-portability-and-why-it-degrades-instead-of-failing","d":"Soft-delete vs the right to erasure: the GDPR conflict and the erasure pathway","k":"The MMCA.Common series","t":"The other right: portability, and why it degrades instead of failing","x":"Erasure has a sibling that lands in the same regulation and is usually built badly. The right to data portability means a user can ask for everything you hold about them, and…"},"2068":{"u":"/articles/soft-delete-vs-erasure.html#trade-offs-honestly","d":"Soft-delete vs the right to erasure: the GDPR conflict and the erasure pathway","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"The framework provides the mechanisms (IAnonymizable, OutboxCleanupService); the consumer owns the policy. That division is the most important caveat, and ADR-005 states it…"},"2069":{"u":"/articles/soft-delete-vs-erasure.html#apply-this-even-without-mmca","d":"Soft-delete vs the right to erasure: the GDPR conflict and the erasure pathway","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern is independent of the framework: 1. Keep soft-delete for lifecycle. It is the right tool for \"is this active?\", and audit / undelete / referential integrity all…"},"2070":{"u":"/articles/reusable-blazor-ui-framework.html","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","x":"Subtitle: This series has been backend all the way down. But the front end is where DRY usually goes to die: every list screen re-implements paging, sort, filter, loading, and…"},"2071":{"u":"/articles/reusable-blazor-ui-framework.html#the-package-that-is-allowed-to-depend-on-almost-nothing","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","t":"The package that is allowed to depend on almost nothing","x":"One structural fact sets the tone. MMCA.Common.UI is allowed to reference MMCA.Common.Shared and nothing else. Not Application, not Domain, not Infrastructure. That is a…"},"2072":{"u":"/articles/reusable-blazor-ui-framework.html#datagridlistpagebase-the-screen-you-stop-rewriting","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","t":"DataGridListPageBase: the screen you stop rewriting","x":"The centerpiece is an abstract Blazor component, DataGridListPageBase . Every server-paged list screen in every consumer app derives from it. It folds into one reusable place the…"},"2073":{"u":"/articles/reusable-blazor-ui-framework.html#the-same-page-on-a-phone-without-a-second-page","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","t":"The same page on a phone, without a second page","x":"The desktop path above is only half of what DataGridListPageBase does, because a data grid is the wrong control on a 390px screen and no amount of CSS fixes that. The base…"},"2074":{"u":"/articles/reusable-blazor-ui-framework.html#the-data-access-boundary-bind-to-an-interface-never-to-httpclient","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","t":"The data-access boundary: bind to an interface, never to HttpClient","x":"A page in this framework never injects a raw HttpClient. It injects IEntityService , a generic CRUD contract: GetAllAsync, GetPagedAsync, GetByIdAsync, AddAsync, UpdateAsync,…"},"2075":{"u":"/articles/reusable-blazor-ui-framework.html#theming-one-set-of-tokens-guarded-by-a-fitness-test","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","t":"Theming: one set of tokens, guarded by a fitness test","x":"Visual consistency lives in one static MudTheme, exposed as MMCATheme.Instance and handed to MudThemeProvider by a shared component, MmcaThemeProviders, that a root layout drops…"},"2076":{"u":"/articles/reusable-blazor-ui-framework.html#render-mode-state-persistence-no-flash-no-double-fetch","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","t":"Render-mode state persistence: no flash, no double fetch","x":"Modern Blazor's InteractiveAuto renders a page up to three times: static SSR, then interactive Server, then WASM. Naively, each transition re-runs the data fetch, and the user…"},"2077":{"u":"/articles/reusable-blazor-ui-framework.html#the-shell-ships-in-the-package-too-modules-plug-in-with-iuimodule","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","t":"The shell ships in the package too: modules plug in with IUIModule","x":"A base class removes the duplication inside a page. The next duplication up is the application shell itself: the router, the layout, the nav menu, and the handful of pages every…"},"2078":{"u":"/articles/reusable-blazor-ui-framework.html#host-polymorphic-token-refresh-one-component-set-three-platforms","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","t":"Host-polymorphic token refresh: one component set, three platforms","x":"Here is where the front-end ports pay off the most. The same Razor component set runs on Blazor Server, Blazor WebAssembly, and a .NET MAUI hybrid host (Android, iOS, macOS,…"},"2079":{"u":"/articles/reusable-blazor-ui-framework.html#trade-offs-honestly","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- This is opinionated UI infrastructure coupled to MudBlazor. DataGridListPageBase is built around MudDataGrid , GridState , and MudBlazor's viewport observer. The base is…"},"2080":{"u":"/articles/reusable-blazor-ui-framework.html#apply-this-even-without-mmca","d":"A list page in a few lines: a reusable Blazor UI framework with the same discipline as the backend","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The moves port to any component framework, not just Blazor: 1. Put a list-page base component in charge of paging, sort, filter, loading, cancellation, and teardown. A concrete…"},"2081":{"u":"/articles/i18n-and-theming.html","d":"One preference, two switches: shipping i18n and dark mode on a single cookie-and-profile pipeline","k":"The MMCA.Common series","x":"Subtitle: A user picking Spanish and a user picking dark mode are doing the same thing: stating a per-user preference that has to survive a page refresh, three render modes, and…"},"2082":{"u":"/articles/i18n-and-theming.html#why-it-matters","d":"One preference, two switches: shipping i18n and dark mode on a single cookie-and-profile pipeline","k":"The MMCA.Common series","t":"Why it matters","x":"Count the places a preference has to be true at once. The static prerender pass renders HTML on the server before any JavaScript runs. The interactive Server circuit takes over.…"},"2083":{"u":"/articles/i18n-and-theming.html#the-mmca-answer-one-preference-pipeline-used-twice","d":"One preference, two switches: shipping i18n and dark mode on a single cookie-and-profile pipeline","k":"The MMCA.Common series","t":"The MMCA answer: one preference pipeline, used twice","x":"Supported cultures are an explicit allowlist, not an open-ended capability. SupportedCultures (MMCA.Common.Shared/Globalization/SupportedCultures.cs:9) holds Default = \"en-US\"…"},"2084":{"u":"/articles/i18n-and-theming.html#the-pipeline-in-one-sketch","d":"One preference, two switches: shipping i18n and dark mode on a single cookie-and-profile pipeline","k":"The MMCA.Common series","t":"The pipeline, in one sketch","x":"This is illustrative-of-shape, condensed from the files above to put the shared pipeline in one view, not a verbatim paste: A culture choice and a theme choice enter the same way…"},"2085":{"u":"/articles/i18n-and-theming.html#trade-offs-honestly","d":"One preference, two switches: shipping i18n and dark mode on a single cookie-and-profile pipeline","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Coverage is two locales, en-US and Spanish, full stop. Spanish (es) is the one added locale (SupportedCultures.cs:18). The mechanism is built to scale (adding a locale is one…"},"2086":{"u":"/articles/i18n-and-theming.html#apply-this-even-without-mmca","d":"One preference, two switches: shipping i18n and dark mode on a single cookie-and-profile pipeline","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The moves port to any component framework with a server-prerender-then-client lifecycle: 1. Treat locale and theme as the same kind of thing. Both are small per-user preferences.…"},"2087":{"u":"/articles/build-your-first-module.html","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","x":"Subtitle: One command writes the solution. The seven steps after it are the ones worth understanding, because they are what makes the generated code safe to change. --- Most \"add…"},"2088":{"u":"/articles/build-your-first-module.html#prerequisites","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"Prerequisites","x":"- .NET 10 SDK. The framework targets net10.0 with LangVersion: preview for C extension types. - Familiarity with the layer flow: API/Grpc - Infrastructure - Application - Domain…"},"2089":{"u":"/articles/build-your-first-module.html#step-0-scaffold-the-solution","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"Step 0: scaffold the solution","x":"Three names, all independent: the solution (also your root namespace), the first module in plural PascalCase, and its aggregate root in singular PascalCase. Everything derived…"},"2090":{"u":"/articles/build-your-first-module.html#step-1-define-the-aggregate-with-a-private-constructor-and-a-result-factory","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"Step 1: define the aggregate with a private constructor and a Result factory","x":"Open the Domain layer of the module you just generated (Source/Modules/Orders/...Orders.Domain/), or start a fresh one there. An aggregate root inherits from…"},"2091":{"u":"/articles/build-your-first-module.html#step-2-raise-a-domain-event-with-adddomainevent","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"Step 2: raise a domain event with AddDomainEvent","x":"State changes that other parts of the system care about are announced as domain events. You do not publish them directly; you record them on the aggregate, and the framework…"},"2092":{"u":"/articles/build-your-first-module.html#step-3-write-the-command-and-its-handler","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"Step 3: write the command and its handler","x":"A write is a use case: a small command object handed to a handler that does exactly one thing. The handler implements ICommandHandler , which is a single method, Task…"},"2093":{"u":"/articles/build-your-first-module.html#step-4-add-a-fluentvalidation-validator","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"Step 4: add a FluentValidation validator","x":"Input validation does not belong in the handler. Write a FluentValidation validator for the command, and the pipeline runs it automatically before the handler ever executes: This…"},"2094":{"u":"/articles/build-your-first-module.html#step-5-implement-imodule","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"Step 5: implement IModule","x":"A module is the unit of cohesion above a feature slice. It implements IModule: a display Name, an optional Dependencies list of other module names, a RequiresDependencies flag,…"},"2095":{"u":"/articles/build-your-first-module.html#step-6-register-in-the-exact-di-sequence","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"Step 6: register in the exact DI sequence","x":"This is the step the most experienced developers still get wrong, because one ordering rule is load-bearing. AddApplicationDecorators() must run after every module's handler…"},"2096":{"u":"/articles/build-your-first-module.html#step-7-watch-the-decorator-pipeline-kick-in-for-free","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"Step 7: watch the decorator pipeline kick in, for free","x":"With the sequence correct, every command and query handler is now wrapped, in this execution order: You wrote a thin handler. The pipeline added the rest, driven by the marker…"},"2097":{"u":"/articles/build-your-first-module.html#the-next-slice-and-the-next-module","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"The next slice, and the next module","x":"You will repeat that shape for every feature you add, so the pack scaffolds it too, which keeps it from drifting one hand-typed slice at a time. A single vertical slice is one…"},"2098":{"u":"/articles/build-your-first-module.html#trade-offs-and-gotchas-honestly","d":"Scaffold a .NET modular monolith in one command, then build your first module","k":"The MMCA.Common series","t":"Trade-offs and gotchas, honestly","x":"The shape buys consistency, and it asks for discipline in return: - The DI order is unforgiving. Call AddApplicationDecorators() too early and handlers run undecorated with no…"},"2099":{"u":"/articles/write-your-first-fitness-test.html","d":"Write your first architecture fitness test","k":"The MMCA.Common series","x":"Subtitle: Your Clean Architecture diagram is a hope, not a guarantee, until something fails the build when it is violated. Here is how to add NetArchTest fitness functions to…"},"2100":{"u":"/articles/write-your-first-fitness-test.html#why-rules-as-code-beats-review","d":"Write your first architecture fitness test","k":"The MMCA.Common series","t":"Why rules-as-code beats review","x":"Before the steps, the thesis, because it shapes everything below. An architecture invariant is easy to state (\"Domain depends on nothing above it\") and easy to erode by accident.…"},"2101":{"u":"/articles/write-your-first-fitness-test.html#prerequisites","d":"Write your first architecture fitness test","k":"The MMCA.Common series","t":"Prerequisites","x":"- A .NET solution with a layered shape you want to protect. The MMCA layers are API/Grpc - Infrastructure - Application - Domain - Shared, but the rule library generalizes. -…"},"2102":{"u":"/articles/write-your-first-fitness-test.html#step-1-install-the-package-and-create-a-test-project","d":"Write your first architecture fitness test","k":"The MMCA.Common series","t":"Step 1: install the package and create a test project","x":"Add a dedicated architecture-test project (mirroring how the framework keeps Tests/Architecture/.Architecture.Tests), and reference the package: The package brings the…"},"2103":{"u":"/articles/write-your-first-fitness-test.html#step-2-implement-iarchitecturemap-one-anchor-type-per-layer","d":"Write your first architecture fitness test","k":"The MMCA.Common series","t":"Step 2: implement IArchitectureMap, one anchor type per layer","x":"This is the only real work, and it is small. IArchitectureMap is the single extension point every rule is parameterized by. You declare your layers (and modules, if you have…"},"2104":{"u":"/articles/write-your-first-fitness-test.html#step-3-subclass-the-test-bases-to-inherit-the-rule-library","d":"Write your first architecture fitness test","k":"The MMCA.Common series","t":"Step 3: subclass the test bases to inherit the rule library","x":"Now your test classes shrink to thin sealed subclasses that supply the map and inherit the [Fact]s. Two of the most valuable bases to start with: That is genuinely the whole test…"},"2105":{"u":"/articles/write-your-first-fitness-test.html#step-4-run-the-suite-under-microsoft-testing-platform","d":"Write your first architecture fitness test","k":"The MMCA.Common series","t":"Step 4: run the suite under Microsoft Testing Platform","x":"The fitness tests are ordinary tests in your test tier, so they run with the same command as everything else: Because they run in the normal tier, a violated invariant fails CI…"},"2106":{"u":"/articles/write-your-first-fitness-test.html#step-5-optional-add-the-compile-time-layer-guard-too","d":"Write your first architecture fitness test","k":"The MMCA.Common series","t":"Step 5 (optional): add the compile-time layer guard too","x":"The runtime suite is the broad net. For the single most common mistake, a bad project reference, you can get even faster feedback by also adding the compile-time guard, so the…"},"2107":{"u":"/articles/write-your-first-fitness-test.html#trade-offs-and-gotchas-honestly","d":"Write your first architecture fitness test","k":"The MMCA.Common series","t":"Trade-offs and gotchas, honestly","x":"ADR-015 names its own rough edges, and you should know them before you trust the green: - The tests assert structure and registration, not runtime behavior. A fitness test can…"},"2108":{"u":"/articles/write-your-first-fitness-test.html#apply-this-even-without-mmca","d":"Write your first architecture fitness test","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"If you are not on MMCA.Common, the pattern still ports to any .NET stack with NetArchTest: 1. Write each invariant as a named test, one per forbidden edge, so a failure points at…"},"2109":{"u":"/articles/two-real-apps-case-study.html","d":"Two real apps on one framework: a conference platform and a store","k":"The MMCA.Common series","x":"Subtitle: A framework's README can claim anything. The real test is whether two unrelated applications, with different domains and different deployments, can both run on it…"},"2110":{"u":"/articles/two-real-apps-case-study.html#the-two-apps","d":"Two real apps on one framework: a conference platform and a store","k":"The MMCA.Common series","t":"The two apps","x":"MMCA.ADC is the Atlanta Developers Conference application. Its domain is events: conferences, sessions, speakers, rooms, categories, and the questions attendees ask. It carries…"},"2111":{"u":"/articles/two-real-apps-case-study.html#what-they-actually-share","d":"Two real apps on one framework: a conference platform and a store","k":"The MMCA.Common series","t":"What they actually share","x":"Both apps sit at the same architecture stage, and that is deliberate. Each one has its modules extracted into separate service hosts (one process per module) behind a YARP…"},"2112":{"u":"/articles/two-real-apps-case-study.html#how-a-framework-change-ships","d":"Two real apps on one framework: a conference platform and a store","k":"The MMCA.Common series","t":"How a framework change ships","x":"The interesting operational property is not that the apps share code. It is how a change to the shared code reaches them. There is no phased rollout, no opt-in flag, no…"},"2113":{"u":"/articles/two-real-apps-case-study.html#what-each-app-proves-that-the-framework-cannot","d":"Two real apps on one framework: a conference platform and a store","k":"The MMCA.Common series","t":"What each app proves that the framework cannot","x":"A framework can demonstrate a pattern. It cannot, by itself, demonstrate that the pattern survives a real domain and a real deployment. That is the division of labor between…"},"2114":{"u":"/articles/two-real-apps-case-study.html#the-honest-note","d":"Two real apps on one framework: a conference platform and a store","k":"The MMCA.Common series","t":"The honest note","x":"Here is the caveat the framework writes about itself. It is not a preamble or a methodology section: it is written into the category rows. Because the framework is a library and…"},"2115":{"u":"/articles/two-real-apps-case-study.html#what-we-covered--next","d":"Two real apps on one framework: a conference platform and a store","k":"The MMCA.Common series","t":"What we covered / Next","x":"What we covered: two unrelated applications, a conference platform and an e-commerce store, both running on the same MMCA.Common packages at the same lockstep version and the…"},"2116":{"u":"/articles/device-capability-abstraction.html","d":"One Blazor UI, two hosts: a device-capability layer that stays resolvable everywhere","k":"The MMCA.Common series","x":"Subtitle: The same Blazor component set runs in a browser and inside a MAUI Blazor Hybrid app. But a share sheet, a fingerprint prompt, and a haptic tap only exist on the phone.…"},"2117":{"u":"/articles/device-capability-abstraction.html#the-mmca-answer-one-small-contract-per-capability-chosen-per-host","d":"One Blazor UI, two hosts: a device-capability layer that stays resolvable everywhere","k":"The MMCA.Common series","t":"The MMCA answer: one small contract per capability, chosen per host","x":"There is no god IDeviceCapabilities interface. That was a deliberate rejection: an aggregate device service forces every head to implement everything, and every new capability…"},"2118":{"u":"/articles/device-capability-abstraction.html#three-flavors-of-unavailable-all-registered-by-default","d":"One Blazor UI, two hosts: a device-capability layer that stays resolvable everywhere","k":"The MMCA.Common series","t":"Three flavors of \"unavailable,\" all registered by default","x":"Small contracts only help if every one of them always resolves. A component that injects IShareService must get something on every head, or the browser build throws at runtime…"},"2119":{"u":"/articles/device-capability-abstraction.html#heads-override-after-the-defaults-last-registration-wins","d":"One Blazor UI, two hosts: a device-capability layer that stays resolvable everywhere","k":"The MMCA.Common series","t":"Heads override after the defaults, last registration wins","x":"A default that is always inert would be useless on the platforms that can do the real thing. So heads override, and the mechanism is deliberately boring: plain Add registrations,…"},"2120":{"u":"/articles/device-capability-abstraction.html#the-ninth-package-and-why-it-lives-outside-the-solution","d":"One Blazor UI, two hosts: a device-capability layer that stays resolvable everywhere","k":"The MMCA.Common series","t":"The ninth package, and why it lives outside the solution","x":"The native adapters cannot ship in MMCA.Common.UI, because that package must stay WebAssembly-compatible. They ship in MMCA.Common.UI.Maui, the ninth of the framework's nineteen…"},"2121":{"u":"/articles/device-capability-abstraction.html#deep-links-one-funnel-no-translation-table","d":"One Blazor UI, two hosts: a device-capability layer that stays resolvable everywhere","k":"The MMCA.Common series","t":"Deep links: one funnel, no translation table","x":"The most elegant part is navigation. A notification tap, a home-screen app action, an app link, a scanned QR code: all four are native events that need to land on a Blazor route.…"},"2122":{"u":"/articles/device-capability-abstraction.html#trade-offs-honestly","d":"One Blazor UI, two hosts: a device-capability layer that stays resolvable everywhere","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Null-object defaults can mask a missing registration. A MAUI head that forgets UseMauiDeviceCapabilities does not fail fast: it silently resolves the null defaults and loses…"},"2123":{"u":"/articles/device-capability-abstraction.html#apply-this-even-without-mmca","d":"One Blazor UI, two hosts: a device-capability layer that stays resolvable everywhere","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any UI that runs on more than one host: 1. Define one small interface per capability, not an aggregate device service. Each new capability is then additive,…"},"2124":{"u":"/articles/managed-file-storage-avatars.html","d":"Managed file storage: uploads you don't have to trust","k":"The MMCA.Common series","x":"Subtitle: A user uploads a profile photo. It is attacker-controlled bytes with GPS coordinates baked into the metadata and, if you are unlucky, a payload hiding behind a valid…"},"2125":{"u":"/articles/managed-file-storage-avatars.html#why-it-matters","d":"Managed file storage: uploads you don't have to trust","k":"The MMCA.Common series","t":"Why it matters","x":"An avatar is user-generated content on an anonymous-visible surface. That combination is exactly where file-upload bugs turn into incidents. The two that bite hardest are not…"},"2126":{"u":"/articles/managed-file-storage-avatars.html#the-mmca-answer-a-storage-boundary-and-a-re-encode-that-keeps-only-pixels","d":"Managed file storage: uploads you don't have to trust","k":"The MMCA.Common series","t":"The MMCA answer: a storage boundary, and a re-encode that keeps only pixels","x":"The framework had no blob abstraction before this. Anything file-shaped would have been written directly against the Azure SDK inside a module, unusable by the next consumer and…"},"2127":{"u":"/articles/managed-file-storage-avatars.html#trade-offs-honestly","d":"Managed file storage: uploads you don't have to trust","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"ADR-045 owns its rough edges in its Consequences section, and they are real design choices rather than oversights. - The avatars container is public-read by design. Avatar URLs…"},"2128":{"u":"/articles/managed-file-storage-avatars.html#apply-this-even-without-mmca","d":"Managed file storage: uploads you don't have to trust","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any stack and any blob store. The rules are short: 1. Never trust the declared content type or the file extension. Sniff the leading magic bytes and accept…"},"2129":{"u":"/articles/http-api-versioning.html","d":"HTTP API versioning, proven not just claimed","k":"The MMCA.Common series","x":"Subtitle: Most APIs \"support versioning\" by shipping v1.0 and never a second version. Here is a header-based setup that introduces versioning without breaking a single caller,…"},"2130":{"u":"/articles/http-api-versioning.html#why-it-matters","d":"HTTP API versioning, proven not just claimed","k":"The MMCA.Common series","t":"Why it matters","x":"HTTP APIs need a versioning axis of their own: one the caller selects per request, that the service can advertise, and that it can deprecate over time. In a framework whose…"},"2131":{"u":"/articles/http-api-versioning.html#the-mmca-answer-one-registration-one-exemplar-one-fitness-contract","d":"HTTP API versioning, proven not just claimed","k":"The MMCA.Common series","t":"The MMCA answer: one registration, one exemplar, one fitness contract","x":"MMCA.Common standardizes a single header-based setup in MMCA.Common.API, adopts it in every service host through one call, and keeps it exercised by a shared contract test that…"},"2132":{"u":"/articles/http-api-versioning.html#trade-offs-honestly","d":"HTTP API versioning, proven not just claimed","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"ADR-046 is refreshingly candid about what is real today versus what the shape leaves room for. Four things are worth naming, and none of them is a bug. - The class-level version…"},"2133":{"u":"/articles/http-api-versioning.html#apply-this-even-without-mmca","d":"HTTP API versioning, proven not just claimed","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any HTTP stack that will have to evolve a response shape without a flag day: 1. Pick one version reader and put it behind one registration. Header, query, or…"},"2134":{"u":"/articles/feature-flags-cqrs-pipeline.html","d":"Feature Flags in the CQRS Pipeline: Gate Commands, Not Code","k":"The MMCA.Common series","x":"Subtitle: Decoupling release from deploy means shipping code dark and flipping a switch. Here is how MMCA.Common gates commands and queries with feature flags at the outermost…"},"2135":{"u":"/articles/feature-flags-cqrs-pipeline.html#why-it-matters","d":"Feature Flags in the CQRS Pipeline: Gate Commands, Not Code","k":"The MMCA.Common series","t":"Why it matters","x":"A feature flag is a branch in production behavior, and branches that are copy-pasted drift. If the gate lives inside the handler, then whether a feature is on is decided in as…"},"2136":{"u":"/articles/feature-flags-cqrs-pipeline.html#the-mmca-answer-an-outermost-decorator-plus-a-marker-interface","d":"Feature Flags in the CQRS Pipeline: Gate Commands, Not Code","k":"The MMCA.Common series","t":"The MMCA answer: an outermost decorator plus a marker interface","x":"The whole mechanism is two small types plus a registration. The opt-in signal is IFeatureGated (UseCases/Markers/IFeatureGated.cs:10), an interface with exactly one member,…"},"2137":{"u":"/articles/feature-flags-cqrs-pipeline.html#the-second-surface-and-why-disabled-means-404","d":"Feature Flags in the CQRS Pipeline: Gate Commands, Not Code","k":"The MMCA.Common series","t":"The second surface, and why disabled means 404","x":"A feature can be reachable from the CQRS pipeline, but it can also be reachable straight from an MVC action that never dispatches a command. So the same flag name is enforced at…"},"2138":{"u":"/articles/feature-flags-cqrs-pipeline.html#trade-offs-honestly","d":"Feature Flags in the CQRS Pipeline: Gate Commands, Not Code","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- The two surfaces must be kept in agreement by hand. Gating the controller but not the command (or the reverse) leaves a half-protected feature reachable through the entry you…"},"2139":{"u":"/articles/feature-flags-cqrs-pipeline.html#apply-this-even-without-mmca","d":"Feature Flags in the CQRS Pipeline: Gate Commands, Not Code","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any pipeline that already wraps its handlers (MediatR behaviors, an interceptor, your own decorator chain): 1. Make feature gating a marker interface with…"},"2140":{"u":"/articles/field-level-encryption-ef-core.html","d":"Field-Level Encryption in EF Core: AES-GCM for PII Columns","k":"The MMCA.Common series","x":"Subtitle: Transparent database encryption protects the file, then decrypts for anyone who can query it. When a column holds data sensitive enough that a database reader should…"},"2141":{"u":"/articles/field-level-encryption-ef-core.html#why-it-matters","d":"Field-Level Encryption in EF Core: AES-GCM for PII Columns","k":"The MMCA.Common series","t":"Why it matters","x":"The gap between \"the database is encrypted\" and \"this column is protected\" is where compliance findings live. A PII inventory (rubric §30 asks for exactly this: what is…"},"2142":{"u":"/articles/field-level-encryption-ef-core.html#the-mmca-answer-one-value-converter-applied-per-property","d":"Field-Level Encryption in EF Core: AES-GCM for PII Columns","k":"The MMCA.Common series","t":"The MMCA answer: one value converter, applied per property","x":"The mechanism is EncryptedStringConverter, a sealed ValueConverter in the MMCA.Common.Infrastructure.Persistence.Encryption namespace. You attach it to a single property in an EF…"},"2143":{"u":"/articles/field-level-encryption-ef-core.html#the-honest-part-shipped-tested-latent","d":"Field-Level Encryption in EF Core: AES-GCM for PII Columns","k":"The MMCA.Common series","t":"The honest part: shipped, tested, latent","x":"Here is what separates this from a \"look what we built\" post. This converter is not in production. Zero columns across the four repositories are encrypted with it today. The…"},"2144":{"u":"/articles/field-level-encryption-ef-core.html#column-level-crypto-vs-tde-the-trade-you-are-actually-making","d":"Field-Level Encryption in EF Core: AES-GCM for PII Columns","k":"The MMCA.Common series","t":"Column-level crypto vs TDE: the trade you are actually making","x":"TDE and field-level encryption are not competitors; they are different threat models, and the honest framing is when each earns its cost. TDE is cheap, global, and transparent to…"},"2145":{"u":"/articles/field-level-encryption-ef-core.html#trade-offs-honestly","d":"Field-Level Encryption in EF Core: AES-GCM for PII Columns","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Encrypted columns are not queryable. The random nonce makes ciphertext non-deterministic, so there is no equality filter, index seek, sort, or join on an encrypted column.…"},"2146":{"u":"/articles/field-level-encryption-ef-core.html#apply-this-even-without-mmca","d":"Field-Level Encryption in EF Core: AES-GCM for PII Columns","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any EF Core codebase, and to most ORMs with a value-conversion or type-handler hook: 1. Use an EF Core ValueConverter (or your ORM's equivalent) so…"},"2147":{"u":"/articles/security-headers-csp-blazor.html","d":"Security Headers and CSP for Blazor: One Middleware, Every Host","k":"The MMCA.Common series","x":"Subtitle: Every client-facing host must stamp the same hardened response headers, but a Blazor host needs a Content-Security-Policy no static string can express. Here is one…"},"2148":{"u":"/articles/security-headers-csp-blazor.html#why-it-matters","d":"Security Headers and CSP for Blazor: One Middleware, Every Host","k":"The MMCA.Common series","t":"Why it matters","x":"Security response headers are cheap defence-in-depth: X-Content-Type-Options: nosniff stops MIME sniffing, X-Frame-Options and frame-ancestors stop clickjacking, HSTS forces…"},"2149":{"u":"/articles/security-headers-csp-blazor.html#the-mmca-answer-one-middleware-a-pluggable-csp","d":"Security Headers and CSP for Blazor: One Middleware, Every Host","k":"The MMCA.Common series","t":"The MMCA answer: one middleware, a pluggable CSP","x":"MMCA.Common ships a single SecurityHeadersMiddleware (MMCA.Common.Aspire/Security/SecurityHeaders.cs:145) in the MMCA.Common.Aspire.Security layer, registered with…"},"2150":{"u":"/articles/security-headers-csp-blazor.html#the-blazor-policy-built-at-runtime","d":"Security Headers and CSP for Blazor: One Middleware, Every Host","k":"The MMCA.Common series","t":"The Blazor policy, built at runtime","x":"An HTML host opts into the origin-pinned policy by registering its own provider before calling AddCommonSecurityHeaders. Both apps register one shared BlazorCspPolicyProvider…"},"2151":{"u":"/articles/security-headers-csp-blazor.html#adopted-at-both-edges-of-both-apps","d":"Security Headers and CSP for Blazor: One Middleware, Every Host","k":"The MMCA.Common series","t":"Adopted at both edges of both apps","x":"This is not a library that exists and waits to be used. Store and ADC each wire it at both client-facing edges. The Gateway hosts call…"},"2152":{"u":"/articles/security-headers-csp-blazor.html#trade-offs-honestly","d":"Security Headers and CSP for Blazor: One Middleware, Every Host","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- The baseline is complete, not maximal. A default that works for a Blazor and MudBlazor host is a default that carries style-src 'unsafe-inline' (SecurityHeaders.cs:65-67), so…"},"2153":{"u":"/articles/security-headers-csp-blazor.html#apply-this-even-without-mmca","d":"Security Headers and CSP for Blazor: One Middleware, Every Host","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The shape ports to any ASP.NET Core stack, and the idea ports to any web framework: 1. Put the hardened header set in one middleware with strongly-typed, overridable settings,…"},"2154":{"u":"/articles/observability-opentelemetry.html","d":"Observability by Default: OpenTelemetry and Azure Monitor in MMCA","k":"The MMCA.Common series","x":"Subtitle: Auto-instrumentation gives you HTTP and runtime signals for free, but it is blind to the two paths that carry almost all of your framework's own work: the CQRS pipeline…"},"2155":{"u":"/articles/observability-opentelemetry.html#why-it-matters","d":"Observability by Default: OpenTelemetry and Azure Monitor in MMCA","k":"The MMCA.Common series","t":"Why it matters","x":"Observability is the category you do not miss until production, and by then retrofitting it is expensive. The rubric names the bar directly: structured logging with correlation…"},"2156":{"u":"/articles/observability-opentelemetry.html#the-mmca-answer-one-call-one-baseline-on-every-host","d":"Observability by Default: OpenTelemetry and Azure Monitor in MMCA","k":"The MMCA.Common series","t":"The MMCA answer: one call, one baseline, on every host","x":"Every framework-consuming host calls AddServiceDefaults() first in Program.cs, and that method calls ConfigureOpenTelemetry() (Extensions.cs:87, the call at :89) before anything…"},"2157":{"u":"/articles/observability-opentelemetry.html#the-one-that-pays-for-itself-filtering-idle-poll-spans","d":"Observability by Default: OpenTelemetry and Azure Monitor in MMCA","k":"The MMCA.Common series","t":"The one that pays for itself: filtering idle poll spans","x":"The OutboxProcessor polls every relational outbox table on a recurring cycle (both deployment templates push the interval to 300 seconds precisely so idle polls cost less:…"},"2158":{"u":"/articles/observability-opentelemetry.html#the-other-half-which-signal-wakes-a-human","d":"Observability by Default: OpenTelemetry and Azure Monitor in MMCA","k":"The MMCA.Common series","t":"The other half: which signal wakes a human","x":"Everything above is emission. Which of those signals pages somebody, at what threshold, at what severity, is a separate decision, and for a long time it lived in the deployment…"},"2159":{"u":"/articles/observability-opentelemetry.html#trade-offs-honestly","d":"Observability by Default: OpenTelemetry and Azure Monitor in MMCA","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- The literal names can drift. The meter, activity-source, and poll-span names are duplicated as string literals across the Aspire package (OutboxPollFilterProcessor.cs:26-29 and…"},"2160":{"u":"/articles/observability-opentelemetry.html#apply-this-even-without-mmca","d":"Observability by Default: OpenTelemetry and Azure Monitor in MMCA","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The pattern ports to any OpenTelemetry stack, with or without Aspire: 1. Put the telemetry baseline in one shared bootstrap every host calls, not in each app. One place to change…"},"2161":{"u":"/articles/saga-compensation-reconciliation.html","d":"Undo Is a Feature: Saga Compensation and the Reconciliation Backstop","k":"The MMCA.Common series","x":"Subtitle: A cancelled order has to give back stock that a transaction closed minutes ago already took, and a payment confirmation that never arrives strands the order forever.…"},"2162":{"u":"/articles/saga-compensation-reconciliation.html#why-it-matters","d":"Undo Is a Feature: Saga Compensation and the Reconciliation Backstop","k":"The MMCA.Common series","t":"Why it matters","x":"The rubric has two categories aimed straight at this. §6 (CQRS and Event-Driven Design, heading at ArchitectureEvaluationCriteria.md:229) asks for idempotent consumers (:238) and…"},"2163":{"u":"/articles/saga-compensation-reconciliation.html#the-mmca-answer-compensation-is-a-handler-not-a-branch-in-the-command","d":"Undo Is a Feature: Saga Compensation and the Reconciliation Backstop","k":"The MMCA.Common series","t":"The MMCA answer: compensation is a handler, not a branch in the command","x":"The command handler does one thing, and it does not even own the plumbing for that. CancelOrderHandler derives from MutateEntityHandlerBase (CancelOrderHandler.cs:27-31), so the…"},"2164":{"u":"/articles/saga-compensation-reconciliation.html#the-marker-idempotency-as-a-database-invariant","d":"Undo Is a Feature: Saga Compensation and the Reconciliation Backstop","k":"The MMCA.Common series","t":"The marker: idempotency as a database invariant","x":"Here is the part worth stealing even if you never touch this framework. At-least-once delivery means the compensating handler will eventually run twice. If restoring stock is not…"},"2165":{"u":"/articles/saga-compensation-reconciliation.html#redelivery-is-the-retry-mechanism-so-throwing-is-correct","d":"Undo Is a Feature: Saga Compensation and the Reconciliation Backstop","k":"The MMCA.Common series","t":"Redelivery is the retry mechanism, so throwing is correct","x":"A compensating handler needs no retry loop of its own, because it already sits on one. When in-process dispatch fails, the domain-event interceptor logs the failure and signals…"},"2166":{"u":"/articles/saga-compensation-reconciliation.html#the-inbound-edge-a-webhook-is-a-delivery-contract-not-an-api-call","d":"Undo Is a Feature: Saga Compensation and the Reconciliation Backstop","k":"The MMCA.Common series","t":"The inbound edge: a webhook is a delivery contract, not an API call","x":"Everything so far is about a message leaving the process (ADR-003, at-least-once out) or a broker redelivery arriving at a consumer (ADR-021, dedup on the way in). The Stripe…"},"2167":{"u":"/articles/saga-compensation-reconciliation.html#the-backstop-a-periodic-sweep-against-the-provider","d":"Undo Is a Feature: Saga Compensation and the Reconciliation Backstop","k":"The MMCA.Common series","t":"The backstop: a periodic sweep against the provider","x":"Compensation answers \"step two failed.\" It does not answer \"step three never reported back.\" A webhook that is never delivered, or that arrives and is rejected because the…"},"2168":{"u":"/articles/saga-compensation-reconciliation.html#trade-offs-honestly","d":"Undo Is a Feature: Saga Compensation and the Reconciliation Backstop","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Adoption is one module, and the record says so. This pattern lives in MMCA.Store's Sales module only: the two saga handlers and the one reconciliation sweep above. MMCA.ADC and…"},"2169":{"u":"/articles/saga-compensation-reconciliation.html#apply-this-even-without-mmca","d":"Undo Is a Feature: Saga Compensation and the Reconciliation Backstop","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The mechanism ports to any stack that has events, a database, and an external dependency: 1. Put the compensating action in its own handler, triggered by the event the first step…"},"2170":{"u":"/articles/governed-llm-boundary.html","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","x":"Subtitle: A chat completion is an unbounded, metered, non-deterministic call to somebody else's server, and most codebases make it by newing up a vendor client in the middle of a…"},"2171":{"u":"/articles/governed-llm-boundary.html#why-it-matters","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","t":"Why it matters","x":"Rubric §16, AI-Native Application Architecture, asks exactly one question of a product feature that calls a model: is that dependency governed like any other external system,…"},"2172":{"u":"/articles/governed-llm-boundary.html#the-mmca-answer-one-optional-package-and-the-call-is-a-pipeline","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","t":"The MMCA answer: one optional package, and the call is a pipeline","x":"MMCA.Common.AI is one of the nineteen published framework packages (MMCA.Common/FACTS.md:19, listed at :22, framework v1.205.0 at :14) and it is optional: nothing in the…"},"2173":{"u":"/articles/governed-llm-boundary.html#off-by-absence-not-off-by-flag","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","t":"Off by absence, not off by flag","x":"AiSettings (AiSettings.cs:31) binds one configuration section, Ai (:34), and carries the whole surface: Enabled (:48), Provider (:51), Model (:59), ApiKey (:70), MaxOutputTokens…"},"2174":{"u":"/articles/governed-llm-boundary.html#bounds-four-of-them-on-both-paths","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","t":"Bounds: four of them, on both paths","x":"BoundedChatClient (BoundedChatClient.cs:51) is a DelegatingChatClient and enforces four bounds, each applied to the buffered and the streaming path (the list is documented on the…"},"2175":{"u":"/articles/governed-llm-boundary.html#guardrails-the-framework-ships-the-extension-point-not-the-policy","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","t":"Guardrails: the framework ships the extension point, not the policy","x":"IChatGuardrail (IChatGuardrail.cs:20) has two methods: InspectRequestAsync (:27-30) and InspectResponseAsync (:37-40), each answering with a GuardrailVerdict. An application…"},"2176":{"u":"/articles/governed-llm-boundary.html#prompts-a-versioned-contract-the-framework-hashes","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","t":"Prompts: a versioned contract the framework hashes","x":"PromptContract (PromptContract.cs:23) is a record of four things: name, version, model and system prompt. Its Hash (:46) is the lowercase hex SHA-256 of those four joined with a…"},"2177":{"u":"/articles/governed-llm-boundary.html#metering-two-counters-one-histogram-one-name","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","t":"Metering: two counters, one histogram, one name","x":"AiUsageMeter (AiUsageMeter.cs:20) publishes mmca.ai.inputtokens (:29) and mmca.ai.outputtokens (:32) on the meter MMCA.Common.AI (:26), plus a mmca.ai.call.duration histogram in…"},"2178":{"u":"/articles/governed-llm-boundary.html#the-layering-rule-that-keeps-all-of-this-at-one-place","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","t":"The layering rule that keeps all of this at one place","x":"A governed boundary that leaks is not a boundary. There are exactly two ways this one leaks, and AiDependencyIsolationTestsBase (AiDependencyIsolationTestsBase.cs:19) is the…"},"2179":{"u":"/articles/governed-llm-boundary.html#trade-offs-honestly","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Two of the seven §16 criteria are absent from the framework on purpose. An evaluation gate in CI and retrieval as data architecture are feature-side, and ADR-120 records both…"},"2180":{"u":"/articles/governed-llm-boundary.html#apply-this-even-without-mmca","d":"The LLM Is a Dependency: A Bounded, Guarded, Metered Boundary for Chat Completions","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The shape ports to any stack with a DI container and a vendor SDK. Microsoft.Extensions.AI makes it cheap in .NET because IChatClient and DelegatingChatClient already exist, but…"},"2181":{"u":"/articles/durable-internal-commands.html","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","x":"Subtitle: A handler that wants something to happen after its transaction commits has four mechanisms to choose from, and picking the wrong one loses the work or pays for…"},"2182":{"u":"/articles/durable-internal-commands.html#why-it-matters","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"Why it matters","x":"The rubric asks for this twice, from two directions. §6 (CQRS and Event-Driven Design) wants reads and writes separated through a dispatcher with a real decorator pipeline, an…"},"2183":{"u":"/articles/durable-internal-commands.html#four-mechanisms-one-question","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"Four mechanisms, one question","x":"The framework has four ways to do work later, and they are not interchangeable. The bounded channel (ADR-121) is a Channel singleton with a SingleReader BackgroundService drain,…"},"2184":{"u":"/articles/durable-internal-commands.html#the-mmca-answer-a-deferred-execution-is-the-inline-execution","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"The MMCA answer: a deferred execution IS the inline execution","x":"Here is the decision that makes the rest of it cheap. IInternalCommand is a marker interface deriving from ICommand and nothing else (IInternalCommand.cs:38). A command that opts…"},"2185":{"u":"/articles/durable-internal-commands.html#scheduling-rides-the-callers-unit-of-work","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"Scheduling rides the caller's unit of work","x":"IInternalCommandScheduler exposes ScheduleAsync(command, runAt) and a TimeSpan delay overload, both returning Result (IInternalCommandScheduler.cs:41-57). runAt is \"the earliest…"},"2186":{"u":"/articles/durable-internal-commands.html#the-row-and-what-it-remembers","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"The row, and what it remembers","x":"InternalCommandMessage (InternalCommandMessage.cs:21) is deliberately not an auditable entity, for the same reason OutboxMessage is not: it is framework bookkeeping, not domain…"},"2187":{"u":"/articles/durable-internal-commands.html#a-rename-that-does-not-orphan-the-queue","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"A rename that does not orphan the queue","x":"The row stores the command's assembly-qualified CLR name by default, which means renaming the class, moving its namespace, or moving its assembly orphans every row already…"},"2188":{"u":"/articles/durable-internal-commands.html#the-drain-claim-execute-stamp","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"The drain: claim, execute, stamp","x":"InternalCommandProcessor is a BackgroundService (InternalCommandProcessor.cs:47). Its cycle is the outbox's idiom, borrowed rather than shared. It waits a five-second startup…"},"2189":{"u":"/articles/durable-internal-commands.html#the-worked-example-an-email-that-must-survive-a-deploy","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"The worked example: an email that must survive a deploy","x":"MMCA.Store's Sales module schedules a payment-failure notification. The command is fifteen lines and most of them are documentation: One identifier as the payload, a stable name,…"},"2190":{"u":"/articles/durable-internal-commands.html#the-expensive-case-permission-timeout-and-a-cross-replica-claim","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"The expensive case: permission, timeout and a cross-replica claim","x":"MMCA.ADC's AI session scoring is the other end of the range, and it is the case ADR-121 moved out of the in-process channel. One pass issues one paid Anthropic call per session,…"},"2191":{"u":"/articles/durable-internal-commands.html#operating-it","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"Operating it","x":"A queue with no operator surface is a queue that gets hand-edited in production SQL. IInternalCommandAdministration (IInternalCommandAdministration.cs:17) mirrors…"},"2192":{"u":"/articles/durable-internal-commands.html#trade-offs-honestly","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- One migration per relational data source. The table is in the model of every relational source (SQL Server, PostgreSQL and SQLite after ADR-113), so adopting the queue means…"},"2193":{"u":"/articles/durable-internal-commands.html#apply-this-even-without-mmca","d":"Four Ways to Do Work Later: Channels, Cron, the Outbox and Durable Internal Commands","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The mechanism ports to anything with a relational database and a background worker. Six steps, in the order that matters: 1. Write the job to a table inside the caller's…"},"2194":{"u":"/articles/identity-completions.html","d":"Finishing Identity: Second Factor, Email Confirmation and Stored Permission Grants","k":"The MMCA.Common series","x":"Subtitle: A framework that owns sign-in for two deployed apps cannot add a second factor by adding a step. Here is how a TOTP challenge, an email-confirmation gate and…"},"2195":{"u":"/articles/identity-completions.html#why-it-matters","d":"Finishing Identity: Second Factor, Email Confirmation and Stored Permission Grants","k":"The MMCA.Common series","t":"Why it matters","x":"Rubric §11 (Security) is a weight-3 category asking that authentication be centralized and identity flows documented, that authorization be enforced at the right layer rather…"},"2196":{"u":"/articles/identity-completions.html#the-mmca-answer-the-unadopted-path-is-unreachable-not-merely-disabled","d":"Finishing Identity: Second Factor, Email Confirmation and Stored Permission Grants","k":"The MMCA.Common series","t":"The MMCA answer: the unadopted path is unreachable, not merely disabled","x":"AuthenticationServiceBase gained two constructor parameters, both optional and both defaulted to null: ITwoFactorAuthenticator? twoFactor = null and IOptions ?…"},"2197":{"u":"/articles/identity-completions.html#the-step-up-assertion-is-a-claim-and-presence-is-the-whole-test","d":"Finishing Identity: Second Factor, Email Confirmation and Stored Permission Grants","k":"The MMCA.Common series","t":"The step-up assertion is a claim, and presence is the whole test","x":"A challenge that is satisfied has to be recorded somewhere the rest of the system can read. The framework stamps an amr-style claim: AuthClaimTypes.MultiFactor is the literal mfa…"},"2198":{"u":"/articles/identity-completions.html#the-framework-ships-no-user-table-on-purpose","d":"Finishing Identity: Second Factor, Email Confirmation and Stored Permission Grants","k":"The MMCA.Common series","t":"The framework ships no user table, on purpose","x":"ITwoFactorService is stateless cryptography: secrets, provisioning URIs, verification inside a skew window, recovery codes. ITwoFactorStore is the persistence, and the consumer…"},"2199":{"u":"/articles/identity-completions.html#stored-grants-operator-editable-and-structurally-unable-to-deny","d":"Finishing Identity: Second Factor, Email Confirmation and Stored Permission Grants","k":"The MMCA.Common series","t":"Stored grants: operator-editable, and structurally unable to deny","x":"Article 24 covered the compiled permission registry: a frozen role-to-permission map, consulted by the CQRS authorization decorators and the [HasPermission] policy. It has one…"},"2200":{"u":"/articles/identity-completions.html#two-refusals-that-are-worth-more-than-the-feature","d":"Finishing Identity: Second Factor, Email Confirmation and Stored Permission Grants","k":"The MMCA.Common series","t":"Two refusals that are worth more than the feature","x":"StoredPermissionRoleAdministrationService (:45) is the shipped IRoleAdministrationService: it reports each role's compiled and stored permissions and replaces the stored half.…"},"2201":{"u":"/articles/identity-completions.html#the-administration-api-is-two-bases-gated-on-capabilities","d":"Finishing Identity: Second Factor, Email Confirmation and Stored Permission Grants","k":"The MMCA.Common series","t":"The administration API is two bases, gated on capabilities","x":"RolesAdminControllerBase (:61) needs no app-supplied service, because AddStoredPermissionGrants() registers a complete one; an app implements the interface only to change that…"},"2202":{"u":"/articles/identity-completions.html#trade-offs-honestly","d":"Finishing Identity: Second Factor, Email Confirmation and Stored Permission Grants","k":"The MMCA.Common series","t":"Trade-offs, honestly","x":"- Two-factor ships unadopted, and the article will not pretend otherwise. A search across both consumers' Source/ trees this run finds no call to AddTwoFactorAuthentication and…"},"2203":{"u":"/articles/identity-completions.html#apply-this-even-without-mmca","d":"Finishing Identity: Second Factor, Email Confirmation and Stored Permission Grants","k":"The MMCA.Common series","t":"Apply this even without MMCA","x":"The identity mechanics here are standard. What is worth copying is the shape of the extension. 1. Make the unadopted path unreachable rather than disabled. An optional…"},"2204":{"u":"/articles/series-index.html","d":"The MMCA series: every pattern, one place","k":"The MMCA.Common series","x":"Subtitle: A single map of the whole series. Start at the top if MMCA.Common is new to you, or jump straight to the pattern you came for. --- MMCA.Common is an Apache-2.0 licensed…"},"2205":{"u":"/articles/series-index.html#start-here","d":"The MMCA series: every pattern, one place","k":"The MMCA.Common series","t":"Start here","x":"The thesis and the framing. Read these first if you have not met the framework. - 01 · I open-sourced the enterprise .NET plumbing I never want to rewrite, and graded it against…"},"2206":{"u":"/articles/series-index.html#core-patterns","d":"The MMCA series: every pattern, one place","k":"The MMCA.Common series","t":"Core patterns","x":"The day-one building blocks. Error handling, the domain model, query intent, the handler pipeline, the reliability backbone that ties them together, the compensating handlers…"},"2207":{"u":"/articles/series-index.html#data-and-persistence","d":"The MMCA series: every pattern, one place","k":"The MMCA.Common series","t":"Data and persistence","x":"How the data layer is built so a module owns its own database, and even its own storage engine, and can become its own service later without a rewrite. - 10 ·…"},"2208":{"u":"/articles/series-index.html#auth-the-api-edge-and-cross-cutting","d":"The MMCA series: every pattern, one place","k":"The MMCA.Common series","t":"Auth, the API edge, and cross-cutting","x":"The concerns that sit across every module: authentication between services, authorization (by role, by permission, and by row ownership), password storage, safe retries, caching,…"},"2209":{"u":"/articles/series-index.html#run-extract-and-harden","d":"The MMCA series: every pattern, one place","k":"The MMCA.Common series","t":"Run, extract, and harden","x":"Bring the whole stack up with one command, then cut a module out into its own service, make the running system survive failure, see what it is doing in production, and put…"},"2210":{"u":"/articles/series-index.html#proof-front-end-and-getting-started","d":"The MMCA series: every pattern, one place","k":"The MMCA.Common series","t":"Proof, front end, and getting started","x":"The evidence that the patterns hold (fitness functions, the test pyramid, the erasure pathway), the reusable Blazor UI framework and the internationalization and theming that…"},"2211":{"u":"/articles/series-index.html#where-to-go-next","d":"The MMCA series: every pattern, one place","k":"The MMCA.Common series","t":"Where to go next","x":"If you are evaluating the framework, read 01 then 03 then 41: the thesis, the rubric, and the proof. If you are here to solve a specific problem, the core-patterns and…"},"2212":{"u":"/","d":"Ivan Ball-llovera · Senior Software Architect","k":"Site","x":"Senior Software Architect Ivan Ball-llovera Cloud-native enterprise architecture on the Microsoft stack I design and ship production-grade .NET platforms: modular monoliths that…"},"2213":{"u":"/","d":"Ivan Ball-llovera · Senior Software Architect","k":"Site","t":"Architecture that earns its keep","x":"I am a Senior Software Architect with more than 25 years designing and delivering scalable, cloud-native systems on the Microsoft stack. My focus is Domain-Driven Design, Clean…"},"2214":{"u":"/","d":"Ivan Ball-llovera · Senior Software Architect","k":"Site","t":"The MMCA platform","x":"A production-grade .NET 10 framework and a set of reference apps that demonstrate modern enterprise architecture end to end. It is built as a modular monolith that extracts…"},"2215":{"u":"/","d":"Ivan Ball-llovera · Senior Software Architect","k":"Site","t":"Deep dives on enterprise .NET","x":"A long-form series turning the framework's decisions into teachable patterns, every claim grounded in real source. A few recent pieces: Proof & getting started · No. 53 The…"},"2216":{"u":"/","d":"Ivan Ball-llovera · Senior Software Architect","k":"Site","t":"Speaking & giving back","x":"I help run two community-driven Atlanta technology conferences and keep production-grade patterns free and in the open. See talks & community work Organizer & speaker Two Atlanta…"},"2217":{"u":"/resume.html","d":"Résumé","k":"Site","x":"Résumé Ivan Ball-llovera Senior Software Architect 25+ years designing and delivering scalable, cloud-native systems on the Microsoft stack: Domain-Driven Design, Clean…"},"2218":{"u":"/resume.html","d":"Résumé","k":"Site","t":"Professional summary","x":"Senior Software Architect with 25+ years designing and delivering scalable, cloud-native systems on the Microsoft stack. Deep expertise in Domain-Driven Design, Clean…"},"2219":{"u":"/resume.html","d":"Résumé","k":"Site","t":"Core competencies","x":"Architecture & design Domain-Driven Design, Clean Architecture, CQRS, Modular Monolith → Microservices, Event-Driven Architecture, Outbox Pattern, gRPC, API Gateway (YARP),…"},"2220":{"u":"/resume.html","d":"Résumé","k":"Site","t":"Professional experience","x":"Senior Software Engineer · Assurant June 2025 – Present · Architect-level scope: platform, security, and cross-team technical decisions Re-architected the AR.com renters quote…"},"2221":{"u":"/resume.html","d":"Résumé","k":"Site","t":"Featured project · MMCA platform","x":"Personal / open source · github.com/ivanball/MMCA.Common A production-grade .NET 10 reference platform demonstrating modern enterprise architecture end-to-end. The conference…"},"2222":{"u":"/resume.html","d":"Résumé","k":"Site","t":"Education","x":"B.S., Computer Science University of Havana (Faculty of Mathematics), Havana, Cuba (1994 – 1999)"},"2223":{"u":"/resume.html","d":"Résumé","k":"Site","t":"Languages","x":"English · Spanish (bilingual)"},"2224":{"u":"/resume.html","d":"Résumé","k":"Site","t":"Certifications","x":"✓ Azure Administrator Associate (AZ-104, 2025) ✓ Azure AI Fundamentals (AI-900, 2024) ✓ Azure Data Fundamentals (DP-900, 2021) ✓ Azure Fundamentals (AZ-900, 2021) → In progress…"},"2225":{"u":"/resume.html","d":"Résumé","k":"Site","t":"Professional development","x":"Continuously prototypes emerging technologies, with a current focus on Clean Architecture using .NET 10, Blazor, .NET MAUI, and ASP.NET Core Web API, and on AI-assisted…"},"2226":{"u":"/platform.html","d":"The MMCA Platform","k":"Site","x":"Featured work · Open source The MMCA platform A production-grade .NET 10 platform that demonstrates modern enterprise architecture end to end: an open-source framework of…"},"2227":{"u":"/platform.html","d":"The MMCA Platform","k":"Site","t":"MMCA.Common","x":"A NuGet package framework for building modular-monolith applications with DDD, Clean Architecture, and CQRS, and the extension points to extract a module into its own…"},"2228":{"u":"/platform.html","d":"The MMCA Platform","k":"Site","t":"Three reference applications","x":"The framework is consumed by real apps. Each one exercises the patterns differently, and the conference app ran a live event on Azure. Conference MMCA.ADC A production-deployed…"},"2229":{"u":"/platform.html","d":"The MMCA Platform","k":"Site","t":"From one graph, laptop to cloud","x":"Orchestrated locally with .NET Aspire, then deployed to Azure Container Apps from the same declarative model. The .NET Aspire dashboard: services, databases, and the broker as…"},"2230":{"u":"/platform.html","d":"The MMCA Platform","k":"Site","t":"Architectural styles the codebase commits to","x":"The recurring ideas every repo is built on, summarized from the onboarding primer's orientation chapter. Each is taught in full at its first concrete appearance in the reference…"},"2231":{"u":"/platform.html","d":"The MMCA Platform","k":"Site","t":"A two-axis architecture scorecard","x":"Every repo is scored against a 34-category rubric on two axes, Maturity (how complete the decision is) and Implementation (how well it is realized in code), with evidence and…"},"2232":{"u":"/platform.html","d":"The MMCA Platform","k":"Site","t":"Architecture Decision Records","x":"131 ADRs capture the context and trade-offs behind each cross-cutting pattern, so the design is teachable, not tribal knowledge. Every entry below links to the full record. 001…"},"2233":{"u":"/platform.html","d":"The MMCA Platform","k":"Site","t":"The reference library","x":"The full architecture documentation, maintained in this site's repository as its canonical home and rendered as browsable pages: every Architecture Decision Record, the…"},"2234":{"u":"/platform.html","d":"The MMCA Platform","k":"Site","t":"Use it, read it, or follow along","x":"The framework is Apache 2.0 and published to nuget.org. The fastest way in is the reference app: one module wired end to end through all five layers, with the extraction path…"},"2235":{"u":"/platform.html","d":"The MMCA Platform","k":"Site","t":"Get each deep dive by email","x":"One message per article, no digests and no other mail. Email address Subscribe"},"2236":{"u":"/writing.html","d":"Writing","k":"Site","x":"Writing Deep dives on enterprise .NET A long-form series that turns the MMCA framework's architecture decisions into teachable patterns, every claim grounded in real source. Read…"},"2237":{"u":"/writing.html","d":"Writing","k":"Site","t":"Get each deep dive by email","x":"One message per article, no digests and no other mail. Email address Subscribe"},"2238":{"u":"/speaking.html","d":"Speaking & Community","k":"Site","x":"Speaking & community Talks and giving back For more than 20 years I have been an active contributor to the Microsoft developer communities in Atlanta and South Florida: teaching,…"},"2239":{"u":"/speaking.html","d":"Speaking & Community","k":"Site","t":"Sessions and workshops","x":"Upcoming · Atlanta Developers Conference precon · October 16, 2026 Build an AI-Ready .NET App with an AI Pair: Clean Architecture, MCP, and Agentic Workflows Full-day, hands-on…"},"2240":{"u":"/speaking.html","d":"Speaking & Community","k":"Site","t":"Organizing two Atlanta conferences","x":"I help convene developers in person, giving the local community direct, no-cost access to expert content on the Microsoft platform. Lead organizer Atlanta Cloud + AI Conference…"},"2241":{"u":"/speaking.html","d":"Speaking & Community","k":"Site","t":"User groups","x":"An active participant in Atlanta's Microsoft technology user-group ecosystem, the same community network from which the conferences draw their speakers and attendees. • Atlanta…"},"2242":{"u":"/speaking.html","d":"Speaking & Community","k":"Site","t":"Open source & mentorship","x":"My MMCA framework is Apache-2.0 licensed and documented with architecture decision records, so the patterns are not just usable but teachable. I mentor developers one on one,…"},"2243":{"u":"/speaking.html","d":"Speaking & Community","k":"Site","t":"What I speak on","x":"Sessions and workshops for conferences, user groups, and teams. Clean Architecture & DDD on .NET Modular monolith → microservices The transactional outbox Database-per-service…"},"2244":{"u":"/contact.html","d":"Contact","k":"Site","x":"Contact Let's connect Happy to talk architecture, the MMCA platform, speaking at your conference or user group, or comparing notes on .NET and Azure. The fastest ways to reach…"},"2245":{"u":"/contact.html","d":"Contact","k":"Site","t":"Three places to start","x":"Open source The MMCA platform A .NET 10 framework and three reference apps, graded in the open against a 34-category rubric. See the architecture → Writing Deep dives on…"}},"dirtCount":0,"index":[["✉",{"3":{"2244":1}}],["✓",{"3":{"2224":4}}],["▸",{"3":{"2214":4}}],["↗",{"3":{"2214":1,"2226":2,"2232":1,"2234":5,"2236":1,"2242":1}}],["📚",{"3":{"1877":1,"1886":1,"1941":1,"1950":1,"1979":1,"2028":1}}],["📄",{"3":{"1793":1,"1802":1,"1846":1,"1853":1,"1877":1,"1886":1,"1899":1,"1905":1,"1950":1,"1965":1,"1972":1,"1979":1,"1985":1,"1991":1,"1997":1,"2002":1,"2028":1,"2049":1,"2060":1,"2069":1,"2086":1,"2128":1}}],["⭐",{"3":{"1787":1,"1793":1,"1802":1,"1846":1,"1853":1,"1877":1,"1886":1,"1899":1,"1905":1,"1941":1,"1950":1,"1965":1,"1972":1,"1979":1,"1985":1,"1991":1,"1997":1,"2002":1,"2028":1,"2049":1,"2060":1,"2069":1,"2086":1,"2128":1}}],["★",{"3":{"1779":8}}],["✅",{"3":{"1611":6}}],["−",{"3":{"1583":1,"1584":1,"1585":1}}],["≈",{"3":{"1537":1}}],["÷",{"3":{"1523":4,"1537":2,"1574":4,"1588":4}}],["σ",{"3":{"1523":4,"1532":1,"1533":1,"1537":4,"1574":4,"1583":1,"1584":1,"1588":4}}],["σweight",{"3":{"0":1,"1527":1,"1578":1,"1592":1}}],["≥",{"3":{"1467":1,"1709":1}}],["×4",{"3":{"1578":1,"1592":1}}],["×10",{"3":{"1527":1,"1578":1,"1592":1}}],["×",{"3":{"1456":1,"1537":5,"1583":1,"1584":1,"1585":1,"1594":2,"1678":1,"1798":2}}],["➡",{"3":{"1229":1,"1237":1,"1247":1,"1259":1,"1270":1,"1271":1,"1286":1,"1300":1,"1301":1,"1309":1,"1310":1,"1311":1,"1312":1,"1323":1,"1324":1,"1339":1,"1350":1,"1359":1,"1369":1,"1380":1,"1395":1,"1396":1,"1402":1,"1415":1,"1416":1,"1417":1,"1427":1,"1438":1}}],["⬅",{"3":{"1229":1,"1237":1,"1247":1,"1259":1,"1270":1,"1271":1,"1286":1,"1300":1,"1301":1,"1309":1,"1310":1,"1311":1,"1312":1,"1323":1,"1324":1,"1339":1,"1350":1,"1359":1,"1369":1,"1380":1,"1395":1,"1396":1,"1402":1,"1415":1,"1416":1,"1417":1,"1427":1,"1438":1}}],[">",{"0":{"1198":1},"3":{"2244":1}}],["<=",{"3":{"1532":1,"1583":1,"1588":1,"1635":1}}],["",{"1":{"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1},"3":{"2232":1}}],["enumerationconverter<",{"3":{"1237":1}}],["enumerationconverter",{"2":{"1230":1,"1235":1},"3":{"1198":1,"1199":2,"1203":1,"1207":1,"1230":1,"1235":1,"1237":3}}],["enumerationtests",{"3":{"964":3,"1191":1,"1199":1,"1207":4,"1237":1,"1438":1,"1496":1}}],["enumerationserializationtests",{"2":{"964":1},"3":{"964":3,"966":1,"1199":1,"1207":5,"1237":1}}],["enumerations",{"0":{"1235":1},"3":{"0":1,"964":1,"1049":1,"1051":1,"1230":1,"1286":4,"1311":2,"1436":1,"1438":1}}],["enumerationvalueconvertertests",{"2":{"964":1},"3":{"964":3,"1191":1,"1199":1,"1207":3,"1237":1,"1286":6,"1496":1}}],["enumerationvalueconverter",{"2":{"964":1},"3":{"0":1,"964":2,"965":1,"966":1,"1199":2,"1203":1,"1207":3,"1237":3,"1282":2,"1286":23}}],["enumerationjsonconverterfactory",{"2":{"964":1,"1230":1,"1235":1,"1237":1},"3":{"0":1,"964":1,"1050":1,"1198":1,"1199":5,"1203":1,"1207":1,"1230":1,"1235":1,"1237":6,"1300":1,"1311":2}}],["enumeration",{"2":{"963":1,"964":2,"966":1,"967":2,"968":1,"1049":1,"1051":1,"1052":1,"1235":2},"3":{"0":5,"166":1,"243":1,"441":1,"554":1,"626":1,"649":2,"651":1,"838":1,"853":1,"854":1,"855":1,"961":1,"963":8,"964":14,"965":5,"966":8,"967":4,"968":1,"990":1,"1049":1,"1050":1,"1051":1,"1052":1,"1198":1,"1199":10,"1203":1,"1207":4,"1212":2,"1230":2,"1231":1,"1234":1,"1235":17,"1237":71,"1271":1,"1282":2,"1286":22,"1294":2,"1300":16,"1301":2,"1311":8,"1323":4,"1324":9,"1359":3,"1396":1,"1415":4,"1433":1,"1436":17,"1438":4,"1496":1,"1498":1,"1526":1,"1632":1,"1641":2,"1662":3,"1767":1,"1770":1,"1915":1,"2077":1,"2127":1}}],["enumerator",{"3":{"1222":1,"1229":1,"1425":1,"2177":1}}],["enumerate",{"3":{"166":1,"248":1,"698":1,"773":1,"775":1,"1059":1,"1286":2,"1300":4,"1301":2,"1311":1,"1324":4,"1349":1,"1369":1,"1380":1,"1396":2,"1407":1,"1408":2,"1415":2,"1417":1,"1427":3,"1429":1,"1436":5,"1534":1,"1589":1,"1591":1,"1596":1,"1916":1}}],["enumerated",{"3":{"0":1,"171":1,"184":1,"186":1,"200":1,"526":1,"532":1,"618":1,"837":1,"899":1,"1018":1,"1033":1,"1036":1,"1212":1,"1237":1,"1270":1,"1300":1,"1301":1,"1309":1,"1311":1,"1323":1,"1324":3,"1339":2,"1344":1,"1359":3,"1396":1,"1436":3,"1454":1,"1492":1}}],["enumeratesourcefiles",{"3":{"1436":1}}],["enumerates",{"3":{"0":3,"184":1,"189":1,"243":2,"526":1,"585":2,"626":1,"640":1,"699":1,"765":1,"766":1,"770":1,"990":1,"1259":1,"1271":1,"1286":2,"1300":2,"1301":1,"1311":1,"1324":6,"1339":1,"1396":5,"1413":1,"1415":2,"1417":1,"1427":1,"1436":15,"1447":1,"1455":1,"1686":1,"1851":1,"1962":1,"2200":1}}],["enums",{"1":{"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1},"3":{"0":2,"782":1,"961":1,"963":2,"964":1,"1203":1,"1207":2,"1212":1,"1230":1,"1233":1,"1235":1,"1236":1,"1237":2,"1286":1,"1324":2,"1342":1,"1350":2,"1351":1,"1359":4,"1369":1,"1380":1,"1395":1,"1396":6,"1402":1,"1436":3,"1438":1,"1489":1,"1496":1,"1662":3,"2044":1,"2232":1}}],["enum",{"1":{"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1,"1172":1,"1173":1,"1174":1,"1175":1,"1176":1,"1177":1,"1178":1,"1179":1,"1180":1},"3":{"0":6,"110":1,"136":1,"142":1,"150":1,"164":1,"166":1,"454":2,"682":1,"691":2,"692":1,"717":1,"782":2,"783":2,"784":2,"961":1,"963":4,"964":6,"965":3,"966":1,"968":1,"1034":1,"1049":1,"1051":1,"1089":1,"1091":1,"1094":1,"1172":1,"1174":2,"1175":3,"1176":1,"1177":1,"1178":1,"1203":42,"1205":1,"1207":47,"1212":4,"1217":1,"1219":1,"1225":1,"1229":7,"1230":1,"1233":1,"1235":1,"1236":1,"1237":11,"1247":2,"1259":1,"1270":6,"1280":1,"1282":1,"1285":1,"1286":32,"1300":16,"1309":12,"1310":3,"1311":6,"1312":1,"1320":2,"1323":8,"1324":29,"1339":10,"1341":2,"1342":2,"1344":2,"1349":1,"1350":23,"1353":1,"1359":34,"1369":13,"1380":8,"1384":1,"1390":1,"1393":2,"1394":1,"1395":20,"1396":58,"1402":18,"1410":1,"1415":7,"1417":1,"1422":1,"1427":6,"1430":1,"1431":1,"1436":41,"1438":2,"1496":2,"1538":1,"1631":1,"1650":1,"1686":3,"1727":1,"1749":1,"1766":1,"1805":3,"1807":1,"1930":2,"1932":1,"2044":1,"2197":2,"2232":2}}],["entonces",{"3":{"1436":1}}],["entangled",{"3":{"1436":1}}],["entail",{"3":{"85":1,"264":1}}],["enterprise",{"0":{"2215":1},"1":{"1780":1,"1781":1,"1782":1,"1783":1,"1784":1,"1785":1,"1786":1,"1787":1},"3":{"696":1,"705":1,"713":1,"723":1,"739":1,"1011":1,"1213":1,"1332":1,"1339":3,"1438":2,"1443":1,"1496":1,"1536":2,"1780":2,"2205":1,"2212":1,"2214":1,"2216":1,"2220":2,"2221":1,"2226":1,"2236":1,"2239":1,"2245":1}}],["entering",{"3":{"498":1,"501":1,"1317":1,"1323":1,"1339":1,"1395":4,"1396":1,"1436":2}}],["enter",{"3":{"237":1,"507":1,"513":1,"725":1,"1247":1,"1271":4,"1286":1,"1324":5,"1339":1,"1359":11,"1380":1,"1395":1,"1417":1,"1438":2,"1453":1,"1460":1,"1467":1,"1496":1,"1669":1,"1980":1,"2036":1,"2084":1}}],["entered",{"3":{"139":1,"265":1,"333":1,"512":1,"1271":1,"1320":1,"1323":1,"1350":2,"1353":1,"1359":4,"1395":9,"1412":1,"1415":2,"1417":4,"1444":1,"1631":1,"1641":1,"1765":1}}],["enters",{"3":{"27":1,"235":1,"292":1,"427":1,"545":1,"564":1,"758":1,"1012":1,"1237":1,"1247":1,"1286":3,"1323":1,"1324":2,"1350":3,"1359":5,"1380":1,"1396":2,"1417":1,"1436":3,"1455":2,"2167":1,"2173":1}}],["entropy",{"3":{"907":1,"1300":1,"1311":1}}],["entrancy",{"3":{"1286":2,"1324":3,"1417":1}}],["entrance",{"3":{"657":2,"658":1}}],["entrant",{"3":{"0":1,"988":2,"989":1,"991":1,"1212":1,"1263":1,"1270":1,"1278":1,"1324":1,"1664":1,"1840":1}}],["entra",{"3":{"0":1,"599":2,"600":2,"1455":1,"1467":7,"1477":4}}],["entries",{"2":{"1231":1,"1233":1},"3":{"0":1,"17":4,"19":1,"122":1,"135":1,"137":1,"139":1,"142":3,"145":2,"147":1,"150":1,"166":2,"186":1,"219":1,"245":1,"330":2,"337":1,"372":1,"388":1,"390":1,"395":2,"407":1,"418":1,"423":2,"424":1,"429":1,"497":1,"511":1,"543":2,"585":1,"599":1,"611":1,"624":2,"626":2,"633":4,"669":3,"690":2,"692":2,"698":1,"715":1,"725":1,"733":2,"734":1,"735":1,"737":1,"740":1,"777":1,"819":1,"827":1,"829":1,"839":1,"841":1,"881":1,"896":1,"972":1,"996":1,"998":1,"1006":1,"1007":2,"1018":1,"1073":1,"1074":3,"1075":1,"1078":1,"1116":1,"1161":1,"1225":1,"1231":1,"1233":1,"1237":5,"1241":4,"1242":1,"1247":5,"1259":6,"1267":1,"1270":6,"1271":1,"1273":1,"1274":1,"1276":1,"1278":2,"1280":3,"1285":1,"1286":34,"1300":9,"1301":12,"1310":1,"1311":6,"1312":1,"1320":1,"1323":5,"1324":22,"1339":7,"1350":1,"1351":1,"1359":5,"1369":1,"1380":8,"1394":1,"1395":7,"1396":33,"1415":8,"1417":7,"1427":1,"1436":57,"1438":1,"1443":2,"1447":1,"1456":1,"1463":1,"1467":9,"1490":1,"1492":1,"1496":1,"1599":1,"1631":2,"1650":1,"1651":2,"1652":1,"1661":1,"1701":2,"1708":1,"1727":2,"1731":1,"1774":1,"1851":1,"1898":1,"1912":1,"1916":1,"1918":1,"1920":2,"1922":1,"1924":1,"1927":2,"1988":1,"2001":1,"2085":1,"2097":1,"2103":2,"2111":1,"2112":1,"2150":1,"2165":1}}],["entrypagesize",{"3":{"1396":1}}],["entrypoint",{"2":{"869":1},"3":{"0":1,"869":1,"870":1,"873":1,"1445":5}}],["entry",{"0":{"1377":1,"1421":1},"3":{"0":12,"27":1,"41":1,"59":2,"60":1,"71":1,"73":1,"74":1,"96":1,"121":1,"122":3,"123":1,"130":1,"135":4,"136":3,"137":3,"138":3,"139":5,"140":8,"141":4,"142":6,"145":5,"147":3,"149":1,"157":2,"166":7,"170":3,"171":1,"177":1,"186":2,"200":2,"201":5,"219":2,"243":2,"245":1,"248":4,"249":2,"251":1,"252":3,"254":5,"256":5,"257":4,"258":7,"259":4,"261":1,"265":2,"286":2,"298":1,"301":1,"303":2,"333":1,"335":2,"341":1,"349":1,"359":3,"362":1,"365":1,"366":1,"370":1,"372":1,"386":1,"395":2,"404":1,"408":1,"418":1,"423":4,"424":1,"425":5,"426":4,"427":1,"428":6,"436":1,"443":3,"461":1,"474":1,"478":2,"483":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"499":1,"507":1,"511":1,"543":2,"549":4,"551":2,"558":1,"572":1,"577":1,"585":1,"599":6,"600":1,"607":2,"609":1,"633":3,"636":1,"649":2,"657":1,"668":1,"676":1,"680":1,"698":4,"709":2,"710":1,"720":1,"725":1,"731":1,"732":1,"733":2,"735":1,"740":2,"742":1,"774":1,"776":2,"782":1,"790":1,"792":2,"794":1,"798":1,"821":1,"825":1,"826":1,"830":1,"832":1,"834":1,"838":2,"840":2,"861":2,"881":2,"899":1,"905":2,"909":1,"914":1,"937":1,"941":2,"972":2,"980":1,"988":1,"995":1,"996":2,"997":1,"1018":6,"1019":1,"1034":2,"1036":1,"1059":1,"1065":1,"1067":1,"1069":1,"1070":1,"1073":1,"1074":1,"1082":1,"1116":4,"1133":2,"1135":1,"1136":1,"1160":2,"1161":4,"1162":2,"1163":4,"1173":1,"1175":2,"1179":1,"1192":1,"1199":9,"1203":1,"1207":1,"1212":2,"1223":1,"1229":2,"1231":1,"1237":14,"1241":2,"1242":2,"1247":24,"1259":7,"1262":2,"1263":3,"1264":1,"1265":1,"1267":3,"1270":24,"1271":1,"1273":1,"1274":1,"1276":2,"1279":3,"1280":1,"1284":1,"1285":1,"1286":110,"1298":1,"1300":36,"1301":40,"1304":1,"1307":1,"1309":6,"1310":1,"1311":20,"1312":2,"1316":1,"1320":2,"1323":23,"1324":79,"1326":1,"1332":2,"1338":2,"1339":24,"1347":1,"1350":9,"1356":1,"1357":1,"1359":42,"1369":1,"1370":2,"1373":1,"1375":1,"1379":2,"1380":17,"1389":1,"1392":1,"1395":26,"1396":63,"1402":1,"1408":1,"1414":2,"1415":20,"1416":18,"1417":33,"1418":1,"1422":1,"1425":1,"1427":6,"1429":1,"1432":1,"1434":1,"1436":140,"1437":1,"1438":12,"1441":3,"1443":2,"1447":3,"1449":2,"1454":1,"1455":1,"1456":1,"1460":2,"1464":1,"1465":1,"1467":16,"1474":2,"1480":1,"1488":2,"1489":2,"1490":4,"1492":1,"1507":2,"1526":3,"1561":1,"1577":1,"1585":1,"1589":1,"1591":1,"1596":1,"1599":2,"1627":1,"1631":5,"1637":1,"1640":1,"1641":2,"1648":1,"1651":3,"1652":1,"1653":3,"1655":2,"1662":1,"1673":1,"1675":1,"1684":2,"1685":4,"1686":3,"1687":1,"1693":1,"1702":2,"1703":1,"1708":4,"1719":1,"1722":1,"1727":2,"1728":1,"1729":1,"1737":1,"1746":1,"1747":5,"1748":17,"1749":13,"1778":1,"1790":2,"1821":2,"1849":1,"1851":2,"1871":2,"1898":2,"1904":1,"1905":2,"1908":1,"1909":3,"1912":1,"1915":1,"1916":2,"1917":1,"1918":2,"1920":2,"1922":4,"1923":1,"1930":1,"1933":1,"1934":1,"1962":1,"1974":1,"1989":1,"2008":1,"2023":2,"2043":1,"2069":2,"2083":1,"2085":1,"2096":1,"2112":1,"2121":2,"2138":1,"2142":2,"2150":1,"2172":1,"2228":1,"2232":2}}],["entire",{"3":{"0":1,"47":1,"556":1,"557":1,"564":1,"640":1,"756":1,"759":1,"798":1,"906":1,"926":1,"979":1,"990":1,"1212":1,"1247":1,"1259":2,"1263":1,"1270":7,"1271":1,"1286":3,"1300":4,"1301":2,"1309":4,"1310":2,"1311":5,"1323":6,"1324":8,"1339":4,"1348":1,"1350":6,"1359":26,"1380":2,"1382":1,"1395":9,"1396":6,"1402":2,"1415":5,"1416":5,"1417":11,"1427":2,"1436":14,"1441":1,"1454":1,"1467":3,"1472":1,"1482":1,"1560":1,"1638":1,"1683":1,"1767":1,"1799":1,"1810":1,"1815":1,"1829":2,"1831":1,"1856":1,"1893":1,"1897":1,"1909":1,"1918":1,"1922":1,"1938":1,"1942":1,"2005":1,"2034":1,"2036":1,"2072":1,"2131":1,"2135":1,"2189":1}}],["entirely",{"3":{"0":3,"27":1,"32":1,"70":1,"109":1,"121":1,"126":1,"136":1,"137":1,"243":1,"244":1,"259":1,"295":1,"320":1,"349":1,"361":1,"388":1,"391":1,"427":1,"443":1,"464":1,"508":1,"527":1,"536":1,"556":1,"560":1,"564":1,"607":1,"657":1,"684":1,"724":1,"757":1,"789":1,"790":1,"799":1,"915":1,"926":1,"935":1,"988":1,"1118":1,"1170":1,"1229":2,"1237":1,"1241":1,"1245":1,"1247":4,"1256":1,"1259":4,"1266":1,"1270":7,"1271":2,"1274":1,"1279":1,"1281":1,"1286":14,"1300":13,"1301":4,"1309":3,"1311":6,"1312":2,"1314":1,"1320":2,"1323":4,"1324":13,"1331":1,"1336":1,"1339":8,"1340":1,"1342":2,"1350":2,"1359":21,"1368":1,"1369":2,"1379":1,"1380":2,"1383":1,"1392":1,"1393":1,"1395":11,"1396":13,"1400":1,"1401":1,"1402":7,"1415":9,"1417":12,"1427":2,"1430":1,"1436":23,"1438":3,"1446":1,"1447":1,"1449":1,"1454":1,"1455":3,"1456":1,"1459":1,"1465":2,"1467":3,"1478":1,"1488":1,"1490":1,"1496":2,"1504":1,"1562":1,"1577":1,"1631":1,"1651":1,"1683":1,"1720":1,"1727":1,"1765":1,"1777":1,"1781":1,"1849":1,"1858":1,"1859":1,"1872":1,"1884":1,"1916":1,"1921":1,"1922":1,"1986":1,"1988":1,"1996":1,"2000":1,"2013":1,"2029":1,"2030":1,"2031":1,"2044":1,"2047":1,"2054":1,"2076":1,"2118":1,"2121":1,"2145":1,"2161":1,"2166":1}}],["entitling",{"3":{"1764":1,"1765":1}}],["entitlements",{"3":{"418":1,"422":2,"425":1,"426":1,"1436":2,"1481":1,"1748":1,"1749":1,"1765":2,"1769":1}}],["entitlement",{"3":{"418":1,"420":1,"422":1,"1286":1,"1307":2,"1309":1,"1416":2,"1417":2,"1436":1,"1748":1,"1764":3,"1765":6,"1767":2,"1945":1}}],["entitled",{"3":{"0":1,"1247":1,"1309":2,"1323":1,"1359":7,"1396":3,"1759":1,"1764":1,"1945":1}}],["entitieswithtenantid",{"3":{"1436":1}}],["entitieswithpiiproperties",{"3":{"1436":3,"1489":1}}],["entitieswithpiiimplementanonymizable",{"2":{"725":1},"3":{"39":1,"725":1,"1237":1,"1436":1,"1496":1}}],["entities",{"0":{"1361":1,"1386":1,"1764":1},"1":{"1230":1,"1231":1,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":1},"3":{"0":1,"4":1,"5":1,"11":1,"12":1,"13":1,"21":1,"39":2,"43":1,"47":1,"132":1,"164":1,"166":1,"329":1,"331":2,"341":2,"360":1,"439":1,"469":1,"470":2,"536":1,"549":1,"642":1,"657":2,"698":1,"715":1,"799":1,"881":1,"988":1,"1018":1,"1049":1,"1082":1,"1085":1,"1117":1,"1132":1,"1140":1,"1150":2,"1168":17,"1169":1,"1170":3,"1171":1,"1202":1,"1203":4,"1207":43,"1212":3,"1229":1,"1230":2,"1231":3,"1232":1,"1236":1,"1237":28,"1244":1,"1247":7,"1249":1,"1259":3,"1269":1,"1270":1,"1277":1,"1278":3,"1279":1,"1282":2,"1286":57,"1300":1,"1309":5,"1310":12,"1311":9,"1317":1,"1323":2,"1324":2,"1340":1,"1342":2,"1343":1,"1347":1,"1350":15,"1353":1,"1359":45,"1360":1,"1361":2,"1363":1,"1369":5,"1374":1,"1380":3,"1382":1,"1395":4,"1396":7,"1402":3,"1431":3,"1436":27,"1437":4,"1441":1,"1446":1,"1467":3,"1489":3,"1496":2,"1526":2,"1539":1,"1540":1,"1541":3,"1546":2,"1577":1,"1627":1,"1629":1,"1630":12,"1631":8,"1632":11,"1633":1,"1635":2,"1636":2,"1637":1,"1638":11,"1639":17,"1640":8,"1650":1,"1651":1,"1652":1,"1686":1,"1728":1,"1729":1,"1772":1,"1810":1,"1812":1,"1840":2,"1844":1,"1849":1,"1853":1,"1856":1,"1863":1,"1864":1,"1865":1,"1867":1,"1939":1,"1987":3,"1988":1,"1991":1,"2061":1,"2104":1,"2227":2,"2230":2}}],["entitypropertysettersarenonpublic",{"3":{"1436":1}}],["entitypath",{"3":{"1247":1}}],["entityquotasapplied",{"3":{"1323":2}}],["entityqueryparameterstests",{"3":{"1199":1,"1207":3}}],["entityqueryparameters",{"2":{"333":1,"1243":1,"1244":1,"1245":1,"1815":1},"3":{"333":1,"1199":12,"1203":1,"1207":2,"1243":4,"1244":1,"1245":1,"1247":20,"1359":1,"1496":2,"1815":1}}],["entityquerypipelinetests",{"3":{"1199":1,"1207":3}}],["entityquerypipelineorderingtests",{"3":{"1199":1,"1207":3,"1436":1,"1438":2}}],["entityquerypipeline",{"2":{"330":2,"333":1,"337":1,"740":1,"1245":1,"1310":1,"1817":1,"1988":1,"1991":1},"3":{"330":9,"331":1,"332":1,"333":2,"337":3,"549":3,"552":2,"740":2,"741":1,"744":1,"1199":9,"1203":1,"1207":2,"1229":2,"1243":42,"1245":1,"1247":19,"1262":1,"1278":2,"1286":8,"1310":17,"1311":5,"1496":2,"1577":1,"1815":1,"1817":1,"1988":2,"1991":1}}],["entityqueryservicetests",{"3":{"1199":1,"1207":7,"1247":1,"1436":1,"1586":1}}],["entityqueryservicecontracttests",{"3":{"1199":1,"1207":5}}],["entityqueryserviceresolutiontests",{"3":{"1199":1,"1207":6,"1247":1,"1438":1}}],["entityqueryserviceprojectiontests",{"3":{"1199":1,"1207":6,"1247":1,"1438":1}}],["entityqueryservice",{"2":{"333":2,"337":1,"543":1,"552":1,"1269":1,"1498":1,"1817":1,"1991":1},"3":{"331":5,"332":1,"333":3,"336":3,"337":1,"543":1,"545":4,"549":2,"552":1,"1199":16,"1203":1,"1207":2,"1229":5,"1244":49,"1247":24,"1269":1,"1270":5,"1286":4,"1309":11,"1310":8,"1311":2,"1354":1,"1359":37,"1396":4,"1436":4,"1438":2,"1496":2,"1498":1,"1586":1,"1815":2,"1817":1,"1988":1,"1991":1}}],["entityname",{"3":{"1286":3,"1311":2,"1324":2,"1395":4}}],["entitytenantid",{"3":{"1286":3}}],["entitytypeof",{"3":{"1436":2}}],["entitytypename",{"3":{"1286":1}}],["entitytypeextensionstests",{"3":{"1199":1,"1207":4,"1237":2}}],["entitytypeextensions",{"2":{"980":1,"1214":1,"1230":1,"1236":1,"1237":1},"3":{"980":3,"1199":1,"1203":1,"1207":2,"1208":1,"1214":1,"1230":1,"1236":2,"1237":9,"1282":1,"1286":2,"1350":6}}],["entitytype",{"3":{"715":3,"980":1,"1208":1,"1237":2,"1270":2,"1286":15,"1323":1,"1436":1}}],["entitytypebuilder",{"2":{"658":1,"660":1,"1036":1},"3":{"658":1,"660":1,"1036":1,"1286":13,"1369":3,"1396":10,"1415":1}}],["entitytypebuilderextensions",{"2":{"655":1},"3":{"655":1,"657":2,"658":1,"1199":1,"1203":1,"1207":2,"1237":2,"1282":2,"1286":12,"1496":1}}],["entitytypeconfigurationbasetests",{"3":{"1199":1,"1207":8,"1286":1}}],["entitytypeconfigurationbase",{"2":{"1856":1},"3":{"1199":4,"1203":1,"1207":2,"1212":1,"1282":2,"1286":15,"1369":2,"1856":1}}],["entitytypeconfigurationsqlite",{"2":{"166":1,"1282":1,"1722":1,"1727":1,"1856":1},"3":{"166":1,"1199":12,"1203":1,"1207":2,"1282":2,"1286":6,"1722":1,"1727":1,"1856":1}}],["entitytypeconfigurationsqlserver",{"2":{"166":1,"470":1,"1212":1,"1282":1,"1361":1,"1412":1,"1651":1,"1849":1,"1856":1,"1857":1},"3":{"166":1,"470":1,"1199":39,"1203":1,"1207":2,"1212":1,"1282":2,"1286":12,"1323":1,"1359":1,"1361":2,"1369":25,"1396":12,"1412":1,"1415":2,"1651":1,"1849":1,"1856":1,"1857":1}}],["entitytypeconfigurationcosmos",{"2":{"166":1,"1282":1,"1856":1,"1857":1},"3":{"166":1,"1199":1,"1203":1,"1207":2,"1282":2,"1286":10,"1856":1,"1857":1}}],["entitytypeconfigurationtests",{"2":{"164":1},"3":{"164":1,"1199":1,"1207":5,"1286":2}}],["entitytypeconfiguration",{"2":{"1176":1,"1178":1,"1280":1,"1286":1,"1361":1,"1500":1,"1856":1,"1862":1},"3":{"135":1,"164":2,"166":1,"171":1,"231":1,"889":1,"1004":2,"1007":1,"1034":1,"1175":2,"1176":1,"1178":1,"1199":6,"1203":14,"1207":28,"1212":1,"1237":2,"1280":1,"1282":7,"1286":81,"1300":3,"1304":1,"1309":2,"1323":1,"1361":2,"1369":3,"1436":2,"1496":1,"1500":2,"1577":3,"1856":1,"1862":1}}],["entitytypeconfigurationpostgresql",{"2":{"166":1,"1034":1,"1036":1,"1212":1,"1282":1,"1856":1},"3":{"0":1,"166":1,"1034":3,"1036":1,"1199":4,"1203":1,"1207":2,"1212":1,"1282":2,"1286":5,"1496":1,"1856":1}}],["entityfilter",{"3":{"1286":2}}],["entityframeworkqueryableextensions",{"3":{"1286":2}}],["entityframeworkcore",{"2":{"401":1,"633":1,"1034":1,"1035":1,"1036":1,"1213":1,"1311":1,"1369":2,"1436":2,"1685":1,"1686":1,"1722":1,"1727":1,"1728":1,"1815":1,"1865":1,"2043":1,"2105":1},"3":{"0":1,"401":1,"633":1,"1034":1,"1035":1,"1036":1,"1213":1,"1286":15,"1310":2,"1311":2,"1323":1,"1339":1,"1369":20,"1436":9,"1489":2,"1685":1,"1686":2,"1722":1,"1727":1,"1728":1,"1815":1,"1865":2,"2043":1,"2105":1}}],["entityfullname",{"3":{"1286":3}}],["entityentry",{"3":{"1259":2,"1286":7}}],["entityexportauthorizationtests",{"3":{"1199":1,"1207":2,"1380":3,"1437":1,"1438":2,"1487":1}}],["entitymodel",{"2":{"1436":1},"3":{"1207":8,"1436":9,"1577":2,"1582":2}}],["entityhandlerbasetests",{"3":{"1207":18}}],["entitywithoutgeneratedid",{"3":{"1199":2,"1207":1}}],["entitywithgeneratedid",{"3":{"1199":2,"1207":1}}],["entitywithnavigation",{"3":{"1199":2,"1207":1}}],["entitybuilderbase",{"3":{"1199":9,"1207":2,"1428":1,"1429":2,"1436":8,"1488":2,"1671":1}}],["entitysettypenames",{"3":{"1436":1}}],["entityservicebasecachingtests",{"3":{"1199":1,"1207":5,"1324":1}}],["entityservicebasetests",{"2":{"883":1},"3":{"883":1,"1199":1,"1207":5,"1324":1}}],["entityservicebaseidempotencyretrytests",{"3":{"881":1,"1199":1,"1207":5,"1324":1,"1438":2}}],["entityservicebase",{"2":{"258":1,"259":1,"341":1,"833":1,"879":1,"881":1,"884":1,"1227":1,"1382":1,"1669":1,"2074":1,"2080":1},"3":{"0":2,"243":4,"245":1,"258":3,"259":2,"261":2,"341":2,"345":1,"827":2,"833":1,"879":1,"881":10,"884":2,"1199":13,"1203":1,"1207":2,"1212":1,"1227":2,"1229":1,"1300":7,"1311":4,"1324":43,"1350":1,"1380":1,"1382":1,"1395":46,"1396":1,"1438":1,"1526":1,"1577":1,"1591":1,"1653":1,"1668":1,"1669":1,"2074":2,"2080":1}}],["entitystate",{"2":{"1082":1},"3":{"1082":1,"1259":1,"1286":1}}],["entityid",{"3":{"782":1,"1249":1,"1259":2,"1265":1,"1270":5,"1350":5,"1359":24,"1399":1,"1402":4,"1415":2,"1631":1,"1638":1,"1640":1}}],["entitykey",{"3":{"715":3,"1270":2,"1286":7,"1323":1}}],["entitydatasourceregistrytests",{"3":{"1199":1,"1207":14,"1286":10}}],["entitydatasourceregistry",{"2":{"46":1,"166":1,"171":1,"1033":1,"1279":1,"1284":1,"1321":1,"1361":1,"1790":1,"1849":1,"1853":1,"1857":1},"3":{"46":1,"166":2,"171":2,"1004":1,"1033":1,"1199":9,"1203":1,"1207":3,"1212":1,"1279":3,"1284":1,"1286":64,"1300":2,"1321":1,"1323":4,"1326":1,"1339":1,"1361":1,"1438":1,"1790":1,"1849":1,"1853":1,"1857":1}}],["entitycollection",{"3":{"1309":1,"1311":2,"1359":6,"1396":2,"1415":1}}],["entityconventiontests",{"3":{"1168":2,"1199":2,"1207":4,"1436":10,"1489":1,"1526":1,"1535":1,"1577":1,"1582":1}}],["entityconventiontestsbase",{"2":{"1168":1},"3":{"0":1,"1168":4,"1199":3,"1207":2,"1212":1,"1436":8,"1489":1,"1526":1,"1582":1,"1654":1}}],["entityconfigurationinterface",{"3":{"1286":2}}],["entityconfigurationoptionstests",{"3":{"1199":1,"1207":2}}],["entityconfigurationoptions",{"3":{"1199":7,"1203":1,"1207":2,"1282":2,"1286":8}}],["entityconfiguration",{"3":{"470":1,"536":1,"657":3,"658":1,"690":3,"693":2,"889":1,"1004":2,"1026":1,"1082":2,"1116":1,"1203":30,"1207":60,"1212":1,"1237":3,"1286":16,"1350":9,"1359":13,"1361":2,"1369":44,"1396":31,"1398":2,"1401":1,"1402":1,"1412":1,"1415":3,"1436":1,"1685":1,"1686":1,"1854":1,"1856":1}}],["entitycontrollerbasetests",{"3":{"1199":1,"1207":5,"1311":1}}],["entitycontrollerbasereadspecificationtests",{"2":{"1438":1},"3":{"1199":1,"1207":9,"1311":2,"1438":2}}],["entitycontrollerbaseexporttests",{"3":{"1199":1,"1207":12,"1311":2,"1438":2}}],["entitycontrollerbaseexportcolumntests",{"3":{"1199":1,"1207":1}}],["entitycontrollerbaseetagtests",{"3":{"1199":1,"1207":8,"1311":1,"1438":2}}],["entitycontrollerbase",{"2":{"325":1,"337":1,"341":1,"345":1,"739":1,"744":1,"1049":1,"1245":1,"1702":1,"1730":1,"1874":1,"1988":1,"1991":1,"1995":1},"3":{"0":2,"318":1,"325":1,"330":15,"331":1,"332":1,"334":1,"337":2,"341":2,"345":1,"453":1,"455":1,"674":2,"739":1,"740":3,"741":10,"743":2,"744":2,"1049":2,"1055":1,"1199":31,"1203":1,"1207":2,"1212":2,"1229":5,"1245":7,"1247":1,"1300":4,"1311":44,"1323":3,"1359":8,"1371":1,"1372":4,"1380":40,"1436":1,"1438":1,"1496":3,"1575":1,"1577":3,"1582":1,"1586":1,"1651":1,"1666":1,"1702":1,"1730":1,"1874":1,"1988":1,"1991":1,"1995":6}}],["entitycsvexportertests",{"3":{"1199":1,"1207":3,"1311":2,"1438":1}}],["entitycsvexporter",{"2":{"744":1},"3":{"741":13,"743":3,"744":4,"1199":3,"1203":1,"1207":2,"1229":1,"1311":24,"1496":1}}],["entitychangedeventtests",{"3":{"782":1,"1199":1,"1207":4}}],["entitychangedevent",{"2":{"780":1,"784":1,"1249":1,"1340":1,"1345":1,"1665":1},"3":{"0":1,"780":1,"782":6,"784":1,"964":1,"1199":12,"1203":1,"1207":2,"1212":1,"1237":5,"1249":3,"1259":10,"1340":1,"1345":1,"1350":31,"1651":1,"1665":1,"1769":1}}],["entity",{"0":{"1231":1,"1243":1,"1279":1,"1282":1,"1362":1,"1857":1},"1":{"327":1,"328":1,"329":1,"330":1,"331":1,"332":1,"333":1,"334":1,"335":1,"336":1,"337":1,"779":1,"780":1,"781":1,"782":1,"783":1,"784":1,"785":1,"786":1,"878":1,"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"919":1,"920":1,"921":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"1238":1,"1239":1,"1240":1,"1241":1,"1242":1,"1243":1,"1244":1,"1245":1,"1246":1,"1247":1,"1854":1,"1855":1,"1856":1,"1857":1,"1858":1,"1859":1,"1860":1,"1861":1,"1862":1,"1986":1,"1987":1,"1988":1,"1989":1,"1990":1,"1991":1},"2":{"697":1,"715":1,"781":1,"923":1,"1048":1,"1232":1,"1273":1,"1867":1,"1955":1,"2163":1},"3":{"0":26,"3":1,"5":2,"7":1,"11":1,"12":1,"39":4,"41":2,"42":1,"81":2,"108":1,"109":1,"117":1,"122":1,"128":1,"145":1,"150":1,"164":1,"165":1,"166":6,"167":1,"168":2,"170":1,"193":1,"265":5,"268":1,"321":1,"327":1,"328":1,"329":5,"330":3,"331":2,"332":3,"333":3,"335":2,"340":1,"341":8,"343":1,"345":1,"346":1,"358":1,"359":2,"361":2,"365":2,"440":1,"455":1,"460":1,"469":4,"470":5,"471":2,"472":1,"473":2,"477":1,"536":1,"544":1,"545":5,"551":1,"642":1,"649":2,"658":1,"697":3,"698":4,"699":1,"700":2,"707":1,"713":1,"714":1,"715":5,"716":2,"717":3,"718":2,"720":1,"725":1,"740":1,"741":1,"745":1,"779":1,"781":4,"782":7,"783":3,"784":3,"798":2,"799":1,"853":1,"878":1,"879":1,"880":2,"881":5,"882":1,"883":1,"889":4,"890":1,"891":4,"899":1,"905":7,"906":1,"919":1,"920":1,"922":2,"923":1,"926":3,"932":1,"935":1,"971":1,"988":1,"1026":1,"1033":1,"1034":4,"1035":2,"1042":1,"1048":2,"1049":1,"1050":2,"1052":1,"1060":1,"1074":1,"1083":1,"1115":1,"1117":1,"1134":1,"1140":1,"1150":4,"1151":1,"1152":3,"1153":1,"1154":1,"1155":2,"1167":4,"1168":4,"1169":1,"1170":2,"1175":1,"1191":1,"1192":3,"1193":1,"1200":1,"1201":1,"1202":4,"1203":4,"1212":24,"1213":1,"1218":1,"1229":3,"1230":3,"1231":8,"1232":6,"1233":1,"1234":2,"1236":3,"1237":76,"1238":5,"1239":3,"1241":4,"1242":5,"1243":8,"1244":5,"1245":2,"1247":79,"1251":1,"1259":13,"1260":1,"1265":2,"1269":4,"1270":40,"1271":3,"1272":2,"1273":3,"1274":2,"1276":3,"1277":3,"1278":1,"1279":5,"1280":2,"1281":6,"1282":5,"1283":1,"1284":1,"1286":271,"1291":1,"1300":14,"1309":19,"1310":35,"1311":45,"1316":2,"1317":1,"1319":1,"1321":5,"1323":30,"1324":41,"1327":1,"1339":4,"1340":1,"1342":1,"1347":2,"1349":2,"1350":109,"1354":3,"1355":1,"1356":1,"1358":6,"1359":321,"1360":1,"1361":5,"1362":3,"1363":3,"1369":44,"1371":2,"1374":1,"1380":18,"1381":1,"1382":1,"1383":1,"1384":1,"1387":2,"1391":1,"1395":62,"1396":59,"1401":2,"1402":22,"1403":1,"1409":1,"1415":20,"1429":2,"1431":1,"1436":95,"1437":1,"1438":6,"1440":1,"1455":3,"1467":3,"1487":2,"1488":2,"1489":5,"1496":6,"1497":1,"1500":1,"1506":1,"1510":1,"1526":3,"1535":1,"1577":9,"1591":2,"1625":1,"1627":2,"1630":8,"1631":15,"1632":5,"1633":5,"1634":8,"1635":3,"1637":4,"1638":10,"1639":30,"1640":11,"1641":15,"1649":2,"1650":1,"1651":3,"1652":1,"1653":1,"1654":1,"1660":3,"1662":4,"1664":3,"1668":1,"1671":1,"1684":1,"1686":4,"1700":1,"1704":1,"1720":2,"1723":1,"1724":1,"1727":6,"1728":2,"1729":1,"1734":1,"1747":2,"1764":1,"1765":2,"1767":1,"1769":1,"1774":2,"1775":1,"1779":1,"1783":1,"1790":2,"1803":1,"1808":1,"1810":6,"1811":2,"1815":7,"1816":1,"1817":4,"1824":1,"1832":1,"1833":1,"1839":3,"1847":1,"1849":12,"1850":1,"1851":5,"1853":2,"1854":8,"1855":3,"1856":6,"1857":1,"1858":3,"1859":1,"1860":3,"1861":4,"1862":5,"1865":6,"1866":1,"1867":3,"1868":2,"1869":1,"1871":4,"1872":1,"1877":2,"1883":1,"1903":1,"1908":1,"1948":1,"1951":1,"1953":6,"1954":4,"1955":5,"1956":1,"1957":1,"1958":1,"1959":2,"1985":2,"1986":4,"1987":2,"1988":4,"1990":5,"1991":6,"2039":1,"2052":1,"2053":2,"2059":1,"2063":3,"2064":1,"2068":1,"2090":1,"2099":1,"2104":1,"2110":1,"2126":1,"2142":2,"2145":1,"2163":1,"2164":1,"2186":1,"2207":1,"2208":1,"2220":1,"2230":5,"2232":12}}],["endian",{"3":{"1479":1}}],["endipaddress",{"3":{"1467":1}}],["endings",{"3":{"1091":1,"1300":1,"1324":1,"1426":1,"1427":1,"1436":1,"1438":1,"2176":1,"2180":1}}],["ending",{"3":{"0":4,"53":1,"905":2,"1168":1,"1300":3,"1311":1,"1323":1,"1324":1,"1333":1,"1359":3,"1369":1,"1425":1,"1436":6,"1438":1,"1631":1,"1989":1,"2010":1,"2157":1}}],["endless",{"3":{"1395":1}}],["endlocal",{"3":{"1350":1}}],["endexclusiveutc",{"3":{"1350":1}}],["endedit",{"3":{"1324":2,"1384":1,"1395":22}}],["endedly",{"3":{"0":1,"468":1}}],["ended",{"3":{"0":1,"499":1,"797":1,"1012":1,"1049":1,"1075":1,"1212":1,"1247":1,"1300":1,"1311":1,"1324":2,"1348":1,"1350":2,"1393":2,"1395":4,"1396":1,"1402":1,"1417":3,"1492":1,"1750":1,"2029":1,"2083":1}}],["endutc",{"3":{"1350":2,"1395":1,"1396":1}}],["enddateselector",{"3":{"1359":1}}],["enddate",{"2":{"1630":1},"3":{"1348":1,"1350":8,"1353":1,"1359":13,"1369":1,"1375":1,"1395":7,"1396":2,"1630":3,"1631":5,"1635":2,"1640":1}}],["endtimeselector",{"3":{"1359":2}}],["endtime",{"3":{"1342":1,"1350":3,"1359":11,"1369":2,"1395":3}}],["endcaptureexclusion",{"2":{"1274":1},"3":{"1274":1,"1286":2}}],["endobject",{"3":{"1229":2}}],["endsatafterstartsatkey",{"3":{"1395":5}}],["endsattime",{"3":{"1395":1}}],["endsatdate",{"3":{"1395":1}}],["endsatlocal",{"3":{"1350":1,"1396":1}}],["endsat",{"3":{"1350":5,"1359":14,"1362":1,"1369":5,"1395":9,"1396":3,"1526":1,"1630":2,"1631":9,"1635":2,"1639":1,"1640":2}}],["endsatutc",{"3":{"1300":2,"1350":1}}],["endswith",{"2":{"1006":1},"3":{"1006":1,"1247":1,"1359":1}}],["ends",{"3":{"0":2,"10":2,"109":1,"110":1,"113":1,"117":1,"150":1,"166":1,"295":1,"341":2,"408":1,"413":1,"500":1,"538":1,"555":1,"675":1,"744":1,"757":1,"908":1,"1018":1,"1027":1,"1117":1,"1126":1,"1234":1,"1237":3,"1241":1,"1243":1,"1246":2,"1247":2,"1259":1,"1262":2,"1270":3,"1271":2,"1281":2,"1286":3,"1300":14,"1301":1,"1309":2,"1310":8,"1311":8,"1312":7,"1316":1,"1323":4,"1324":17,"1327":1,"1339":4,"1350":4,"1359":16,"1369":4,"1373":1,"1375":1,"1380":6,"1395":5,"1396":8,"1402":7,"1415":3,"1416":1,"1417":11,"1423":1,"1427":4,"1436":17,"1438":3,"1441":1,"1445":1,"1453":1,"1455":1,"1460":1,"1467":2,"1475":2,"1482":2,"1492":1,"1496":1,"1613":1,"1638":1,"1639":1,"1701":1,"1790":1,"1849":1,"1858":1,"1865":3,"1866":1,"1875":1,"1884":1,"1902":1,"1933":1,"1980":1,"1983":1,"1984":1,"1985":1,"1987":1,"1988":1,"2017":1,"2024":1,"2121":1,"2125":1,"2232":1}}],["endpointproperty",{"3":{"1339":1}}],["endpointprefix",{"2":{"203":1},"3":{"0":1,"15":1,"25":1,"198":2,"203":1,"1323":6,"1665":1}}],["endpointreference",{"3":{"1339":5}}],["endpointkey",{"3":{"1339":5}}],["endpointname",{"3":{"1328":1,"1339":6}}],["endpoint=sb",{"3":{"1339":1}}],["endpoint=",{"3":{"1323":1}}],["endpointmetadata",{"2":{"1300":1},"3":{"1300":1}}],["endpointfeaturestub",{"3":{"1199":2,"1207":1}}],["endpointcultureappliertests",{"3":{"1199":1,"1207":2,"1438":1}}],["endpointcultureapplier",{"3":{"265":1,"1199":4,"1203":1,"1207":2,"1324":17,"1417":1,"1438":1,"1496":1}}],["endpointdatasource",{"3":{"175":1}}],["endpointdefaults",{"3":{"91":1,"98":1,"1312":2}}],["endpointswithoutauthorizationattribute",{"3":{"1436":5}}],["endpoints",{"0":{"99":1,"2007":1},"2":{"189":1},"3":{"0":11,"31":1,"33":1,"92":2,"93":1,"95":2,"102":1,"156":1,"157":1,"162":1,"174":2,"175":1,"176":2,"177":1,"178":1,"182":1,"184":1,"185":2,"186":5,"187":1,"189":4,"190":1,"198":1,"207":1,"208":1,"235":2,"239":1,"242":1,"243":1,"279":1,"280":2,"283":1,"287":2,"318":2,"324":1,"329":2,"332":1,"350":1,"386":1,"387":3,"388":1,"390":1,"393":1,"401":1,"402":1,"406":1,"415":1,"418":3,"420":1,"423":1,"424":1,"438":1,"448":1,"508":1,"509":1,"510":1,"583":1,"585":1,"591":1,"625":1,"690":1,"725":1,"743":1,"749":2,"759":1,"790":1,"791":1,"792":1,"825":1,"826":2,"827":1,"830":1,"840":1,"854":1,"860":1,"881":1,"882":1,"903":1,"905":1,"906":1,"910":1,"917":1,"980":1,"1026":1,"1030":1,"1058":1,"1059":2,"1069":1,"1123":1,"1188":1,"1192":1,"1203":5,"1207":16,"1208":10,"1212":1,"1213":2,"1229":2,"1237":1,"1247":1,"1271":1,"1286":2,"1288":2,"1294":1,"1297":2,"1300":17,"1304":1,"1307":2,"1309":6,"1311":59,"1312":1,"1323":5,"1324":22,"1331":1,"1332":1,"1339":11,"1347":1,"1348":2,"1350":6,"1352":1,"1359":18,"1377":1,"1379":1,"1380":13,"1387":1,"1392":1,"1395":12,"1396":38,"1402":5,"1406":1,"1408":1,"1414":3,"1415":21,"1417":2,"1436":22,"1438":5,"1441":1,"1442":2,"1443":2,"1447":1,"1451":1,"1455":2,"1456":1,"1458":6,"1467":4,"1469":1,"1490":1,"1492":1,"1496":1,"1524":1,"1526":3,"1534":1,"1540":1,"1548":1,"1550":1,"1577":2,"1600":1,"1626":1,"1630":2,"1631":8,"1632":10,"1633":1,"1635":3,"1637":1,"1638":3,"1639":9,"1640":13,"1641":4,"1651":3,"1653":1,"1660":1,"1666":2,"1667":2,"1677":1,"1700":1,"1703":1,"1717":1,"1727":1,"1747":1,"1748":3,"1749":3,"1750":1,"1753":4,"1760":1,"1765":4,"1771":3,"1774":1,"1776":3,"1874":1,"1896":1,"1898":2,"1908":2,"1912":1,"1922":3,"1923":1,"1924":1,"1929":1,"1937":1,"1941":1,"1960":1,"1961":2,"1962":2,"1963":1,"1964":1,"1965":2,"1969":1,"1971":1,"1972":1,"1975":1,"1990":2,"1992":1,"1995":4,"1998":2,"1999":1,"2000":1,"2001":1,"2002":2,"2025":1,"2047":1,"2113":1,"2126":1,"2127":1,"2132":2,"2169":1,"2195":1,"2232":1}}],["endpoint",{"0":{"92":2,"95":1,"96":1},"1":{"738":1,"739":1,"740":1,"741":1,"742":1,"743":1,"744":1,"745":1},"2":{"397":1,"1333":1,"1419":1,"2010":1,"2156":1,"2159":1},"3":{"0":24,"24":1,"25":1,"27":2,"31":2,"33":2,"39":2,"42":1,"53":1,"55":2,"59":2,"62":3,"90":1,"91":9,"92":11,"93":5,"94":3,"95":12,"96":9,"97":3,"98":16,"99":6,"100":6,"101":4,"102":3,"103":3,"104":1,"105":1,"111":1,"126":1,"157":1,"159":2,"160":2,"173":2,"175":4,"177":4,"179":1,"185":2,"187":1,"188":1,"189":4,"193":1,"198":5,"203":2,"207":2,"208":1,"209":2,"216":1,"223":1,"225":2,"229":3,"235":3,"243":3,"245":1,"265":3,"281":1,"317":2,"318":2,"321":1,"324":1,"332":2,"333":1,"335":1,"342":1,"350":1,"378":1,"389":1,"390":2,"391":1,"397":1,"415":3,"418":1,"435":1,"448":1,"453":1,"459":1,"507":1,"510":1,"517":2,"520":1,"535":1,"549":1,"599":1,"640":1,"649":2,"651":1,"690":1,"709":1,"717":1,"724":1,"725":4,"726":2,"727":2,"728":1,"732":1,"735":1,"738":1,"739":2,"740":2,"741":2,"742":1,"743":2,"749":3,"751":2,"773":1,"775":1,"789":6,"790":17,"791":4,"792":7,"793":4,"794":3,"795":1,"800":1,"826":2,"827":1,"832":1,"837":1,"838":1,"853":2,"869":1,"881":4,"883":1,"907":1,"914":1,"924":1,"965":1,"990":1,"1016":1,"1018":3,"1019":2,"1020":1,"1021":2,"1051":1,"1059":2,"1060":1,"1066":1,"1067":3,"1068":2,"1069":2,"1091":3,"1099":1,"1116":1,"1124":1,"1184":1,"1185":1,"1188":1,"1192":1,"1212":4,"1218":1,"1225":1,"1229":4,"1237":1,"1241":1,"1247":4,"1259":3,"1263":2,"1264":1,"1269":1,"1270":9,"1271":2,"1278":1,"1286":10,"1288":3,"1297":5,"1298":2,"1300":70,"1301":3,"1302":1,"1304":1,"1307":3,"1309":22,"1311":74,"1312":6,"1320":2,"1322":1,"1323":27,"1324":138,"1326":4,"1328":10,"1331":2,"1332":2,"1333":1,"1334":1,"1337":3,"1339":91,"1347":3,"1350":17,"1353":1,"1359":91,"1366":1,"1369":2,"1372":1,"1373":1,"1375":1,"1379":4,"1380":21,"1382":1,"1386":1,"1387":1,"1388":1,"1389":3,"1390":2,"1395":62,"1396":103,"1400":3,"1402":26,"1406":1,"1408":1,"1409":2,"1410":1,"1413":1,"1415":59,"1416":6,"1417":12,"1419":4,"1420":2,"1427":6,"1430":3,"1434":1,"1436":72,"1437":1,"1438":19,"1440":1,"1441":12,"1442":2,"1443":4,"1444":2,"1447":3,"1449":3,"1455":2,"1456":2,"1458":1,"1460":1,"1461":1,"1467":13,"1476":1,"1477":1,"1487":3,"1488":1,"1489":2,"1496":4,"1524":1,"1526":4,"1534":4,"1546":1,"1577":1,"1582":2,"1591":1,"1612":1,"1631":3,"1632":7,"1635":1,"1638":1,"1639":7,"1640":10,"1641":17,"1653":3,"1656":2,"1664":1,"1666":5,"1670":4,"1671":2,"1673":1,"1686":1,"1698":4,"1702":1,"1703":2,"1707":1,"1720":1,"1727":1,"1747":4,"1748":5,"1749":4,"1755":2,"1758":1,"1760":1,"1765":1,"1771":1,"1775":1,"1776":3,"1805":1,"1815":2,"1823":1,"1825":2,"1876":1,"1877":1,"1896":2,"1898":3,"1909":1,"1910":1,"1912":1,"1921":1,"1922":1,"1923":2,"1925":3,"1928":2,"1930":2,"1934":1,"1946":3,"1950":1,"1960":3,"1961":3,"1963":1,"1964":1,"1965":3,"1968":1,"1969":1,"1972":2,"1978":1,"1987":1,"1990":2,"1993":2,"1994":1,"1995":1,"1998":1,"2000":4,"2001":4,"2002":2,"2005":1,"2008":1,"2009":2,"2010":1,"2025":12,"2036":1,"2056":1,"2066":2,"2068":1,"2074":1,"2126":1,"2132":4,"2133":2,"2134":1,"2135":1,"2150":1,"2152":1,"2156":1,"2159":1,"2160":1,"2161":1,"2166":11,"2167":1,"2168":2,"2169":1,"2190":1,"2199":1,"2200":1,"2201":1,"2203":2,"2232":4,"2239":1}}],["end",{"0":{"1245":2,"1304":2,"1323":2,"1350":2,"1415":2,"1433":2,"1514":2,"1560":1,"1563":1,"1565":1,"1651":2,"1754":2,"1933":2,"2210":1},"3":{"0":10,"15":1,"30":1,"59":1,"62":2,"66":2,"76":1,"92":2,"96":1,"107":2,"109":2,"115":4,"130":6,"132":1,"150":1,"155":1,"164":3,"173":1,"179":1,"180":1,"193":3,"241":4,"272":1,"298":2,"386":1,"395":6,"398":2,"402":1,"434":2,"468":4,"501":1,"516":1,"543":3,"574":1,"625":4,"626":1,"696":1,"706":1,"795":1,"797":3,"812":1,"827":1,"844":1,"846":2,"857":2,"861":1,"862":1,"867":2,"881":2,"883":2,"905":1,"920":1,"946":2,"952":2,"972":1,"1025":1,"1026":1,"1048":1,"1050":2,"1067":2,"1073":1,"1075":2,"1081":2,"1185":1,"1192":4,"1202":4,"1203":4,"1212":6,"1216":4,"1220":2,"1225":2,"1229":2,"1233":2,"1234":2,"1237":13,"1247":1,"1256":2,"1259":4,"1270":1,"1271":4,"1274":2,"1275":1,"1281":1,"1286":14,"1290":3,"1297":1,"1300":17,"1309":3,"1310":3,"1311":5,"1312":4,"1323":7,"1324":83,"1326":2,"1333":1,"1336":1,"1339":28,"1344":1,"1348":1,"1350":21,"1352":2,"1358":1,"1359":46,"1362":1,"1369":3,"1375":2,"1376":2,"1380":6,"1383":1,"1385":1,"1390":1,"1391":1,"1393":1,"1395":82,"1396":26,"1398":2,"1402":18,"1403":2,"1413":1,"1415":10,"1416":4,"1417":40,"1427":7,"1428":5,"1431":2,"1432":1,"1433":1,"1436":85,"1437":8,"1438":16,"1439":1,"1441":2,"1442":1,"1443":2,"1452":3,"1455":4,"1456":2,"1461":1,"1462":1,"1463":2,"1465":2,"1467":15,"1475":1,"1481":2,"1486":1,"1488":1,"1489":2,"1491":1,"1492":2,"1493":1,"1496":13,"1499":3,"1525":1,"1526":5,"1530":3,"1531":3,"1532":1,"1535":3,"1536":2,"1554":1,"1563":1,"1565":3,"1572":1,"1574":1,"1576":2,"1577":4,"1581":2,"1582":2,"1583":1,"1585":1,"1586":2,"1588":2,"1590":1,"1591":3,"1597":2,"1599":2,"1600":3,"1602":2,"1621":1,"1630":6,"1631":2,"1632":1,"1635":2,"1647":3,"1651":2,"1653":4,"1654":2,"1661":2,"1662":2,"1669":2,"1674":2,"1677":1,"1681":2,"1685":1,"1689":1,"1702":2,"1720":2,"1722":2,"1757":1,"1764":1,"1767":2,"1768":1,"1779":1,"1784":1,"1796":4,"1805":2,"1815":1,"1818":1,"1860":2,"1875":2,"1884":2,"1909":1,"1910":1,"1923":1,"1936":2,"1941":2,"1947":2,"1981":1,"1982":1,"2004":1,"2009":2,"2024":1,"2025":2,"2050":2,"2051":2,"2054":2,"2057":1,"2059":1,"2063":2,"2069":1,"2070":2,"2072":2,"2078":1,"2079":1,"2080":2,"2081":1,"2086":2,"2127":2,"2130":1,"2147":1,"2155":1,"2156":2,"2175":1,"2190":1,"2199":1,"2214":2,"2220":3,"2221":2,"2226":2,"2232":1,"2234":2}}],["exotic",{"3":{"1259":1,"1906":1,"1952":1,"2125":1}}],["exhibiting",{"3":{"1395":1}}],["exhibits",{"3":{"1395":1}}],["exhibitors",{"3":{"1359":1,"1364":1,"1369":1,"1380":1,"1438":1,"1629":1,"1631":1}}],["exhibitor",{"3":{"1350":1,"1359":3,"1369":1,"1395":3,"1438":1,"1631":2}}],["exhibit",{"3":{"725":1}}],["exhausting",{"3":{"1268":1,"1339":1,"2156":1}}],["exhaustively",{"3":{"1344":1,"1405":1}}],["exhaustiveness",{"3":{"257":1,"1438":1}}],["exhaustive",{"3":{"254":1,"260":1,"626":1,"1324":2,"1359":1,"1395":1,"1396":2,"1436":1,"1498":2}}],["exhaustion",{"3":{"0":1,"20":2,"1256":1,"1359":2,"1395":2,"1436":1}}],["exhaust",{"3":{"174":1,"1323":2,"1324":3,"1339":1}}],["exhausts",{"3":{"24":1,"818":1,"1237":1,"1255":1,"1256":1,"1258":1,"1259":3,"1267":1,"1317":1,"1323":2,"1332":1,"1436":2,"1438":1,"1442":1,"1447":1,"1589":1,"1591":1,"1596":1,"1917":1}}],["exhausted",{"3":{"0":1,"20":1,"395":1,"397":1,"819":1,"820":1,"1212":1,"1256":1,"1259":8,"1286":3,"1309":1,"1318":1,"1323":4,"1324":3,"1350":1,"1359":2,"1389":1,"1395":3,"1417":1,"1427":1,"1436":1,"1467":2,"1577":1,"1639":1,"1665":1,"1843":1,"1845":1,"2156":3}}],["exfiltrate",{"3":{"1300":1,"1966":1}}],["exfiltrated",{"3":{"530":1}}],["exfiltrating",{"3":{"1300":1}}],["exfiltration",{"3":{"206":1,"208":1,"219":1,"1324":1,"2150":1}}],["ex",{"3":{"265":1,"819":1,"1247":3,"1259":7,"1270":4,"1300":1,"1301":1,"1309":2,"1311":1,"1312":4,"1323":2,"1324":5,"1339":1,"1359":4,"1366":1,"1380":6,"1395":1,"1396":2,"1415":2,"1526":2,"1591":1,"1927":1,"2032":1}}],["examples",{"0":{"1491":1},"3":{"493":1,"495":2,"1270":1,"1286":1,"1309":1,"1310":1,"1324":1,"1359":1,"1436":1,"1485":2,"1491":1,"1735":1,"2109":1}}],["example",{"0":{"1799":1,"2189":1},"3":{"31":1,"53":2,"72":1,"82":1,"111":1,"119":1,"128":2,"165":1,"168":1,"185":1,"188":1,"268":2,"283":1,"318":1,"359":2,"363":1,"364":2,"370":1,"380":1,"401":1,"413":1,"415":1,"416":1,"420":1,"427":1,"448":1,"459":1,"474":1,"486":1,"490":1,"491":1,"492":1,"493":1,"543":1,"545":4,"550":1,"556":1,"566":1,"599":1,"620":1,"733":2,"780":1,"782":1,"799":1,"801":1,"837":1,"841":2,"861":1,"873":1,"876":1,"881":2,"963":1,"980":1,"1103":1,"1133":1,"1168":2,"1175":1,"1177":1,"1212":1,"1219":1,"1229":7,"1233":1,"1237":10,"1243":1,"1247":7,"1249":1,"1259":9,"1262":1,"1265":1,"1268":1,"1270":10,"1271":9,"1286":40,"1288":1,"1300":17,"1301":4,"1302":1,"1309":3,"1310":13,"1311":21,"1312":11,"1316":1,"1320":3,"1322":1,"1323":16,"1324":22,"1339":13,"1343":1,"1345":1,"1347":1,"1348":1,"1350":21,"1352":1,"1359":27,"1361":1,"1362":1,"1369":2,"1371":1,"1372":2,"1373":2,"1374":1,"1376":1,"1380":4,"1384":1,"1385":1,"1389":1,"1395":7,"1396":11,"1402":1,"1411":1,"1415":8,"1416":4,"1417":12,"1427":1,"1431":3,"1433":1,"1436":41,"1437":1,"1438":2,"1442":2,"1443":3,"1454":1,"1455":1,"1467":1,"1474":1,"1479":1,"1480":1,"1486":1,"1488":3,"1491":3,"1492":1,"1493":2,"1500":1,"1525":1,"1526":1,"1577":1,"1585":3,"1629":1,"1631":1,"1639":1,"1653":1,"1656":1,"1675":1,"1685":1,"1686":2,"1701":1,"1702":1,"1709":1,"1710":1,"1727":1,"1728":2,"1802":1,"1810":1,"1829":2,"1830":1,"1841":1,"1860":1,"1881":1,"1922":1,"1931":1,"1933":1,"1940":1,"1941":1,"1954":1,"1955":1,"1976":1,"2004":1,"2008":1,"2010":2,"2016":1,"2019":1,"2032":1,"2033":1,"2042":1,"2043":2,"2048":1,"2078":1,"2083":1,"2087":1,"2088":1,"2144":1,"2156":1}}],["exacting",{"3":{"1380":1}}],["exactness",{"3":{"1339":1}}],["exact",{"0":{"1263":1,"2095":1},"3":{"0":3,"1":1,"22":1,"24":1,"98":1,"99":2,"100":1,"102":1,"109":1,"167":1,"283":1,"341":1,"342":1,"371":1,"420":1,"440":1,"459":1,"472":1,"499":1,"530":1,"572":1,"574":1,"626":1,"627":1,"691":1,"707":1,"716":1,"725":1,"740":1,"757":1,"758":1,"765":1,"766":1,"813":1,"827":1,"838":1,"861":1,"863":1,"871":1,"890":1,"905":1,"908":1,"916":1,"980":1,"996":2,"1004":1,"1005":1,"1018":1,"1036":1,"1067":2,"1071":1,"1092":1,"1163":1,"1191":1,"1217":1,"1237":3,"1249":1,"1259":3,"1270":3,"1276":1,"1286":18,"1300":10,"1301":6,"1309":2,"1311":15,"1312":2,"1315":1,"1323":5,"1324":16,"1339":7,"1348":1,"1350":3,"1357":2,"1359":17,"1366":1,"1369":8,"1380":7,"1395":6,"1396":4,"1402":3,"1405":1,"1409":1,"1415":3,"1416":1,"1417":9,"1418":1,"1427":2,"1429":1,"1435":1,"1436":49,"1438":4,"1444":1,"1445":1,"1447":1,"1450":1,"1453":2,"1454":1,"1455":10,"1456":1,"1463":1,"1467":1,"1470":1,"1474":1,"1486":3,"1492":1,"1496":6,"1500":2,"1525":1,"1526":4,"1531":1,"1591":3,"1596":1,"1599":1,"1640":1,"1646":1,"1697":1,"1729":1,"1735":1,"1784":1,"1799":1,"1802":1,"1831":1,"1851":1,"1872":1,"1874":1,"1909":1,"1922":1,"1927":1,"1929":1,"1951":1,"1954":1,"1974":1,"1982":1,"2036":1,"2062":1,"2083":1,"2086":1,"2089":1,"2098":1,"2108":1,"2121":1,"2124":1,"2126":1,"2136":1,"2167":3,"2176":1}}],["exactlyonce",{"3":{"1492":1}}],["exactlyone",{"3":{"1415":1}}],["exactly",{"3":{"0":24,"15":1,"21":1,"23":2,"24":1,"26":2,"38":1,"39":1,"71":2,"93":1,"96":1,"99":1,"109":3,"135":6,"136":1,"139":2,"140":1,"148":2,"159":1,"160":1,"166":2,"170":2,"178":2,"180":1,"186":1,"187":1,"195":3,"197":1,"199":1,"201":1,"202":2,"214":1,"217":1,"218":1,"235":2,"243":1,"253":1,"254":1,"255":1,"257":1,"260":1,"266":1,"292":1,"303":2,"305":1,"310":1,"318":1,"335":2,"342":1,"343":1,"350":2,"351":1,"352":1,"353":1,"359":2,"361":1,"363":1,"369":1,"373":1,"374":1,"380":1,"387":1,"388":1,"390":2,"391":1,"395":1,"400":1,"401":1,"402":1,"407":1,"414":1,"415":1,"420":2,"426":1,"433":1,"443":1,"448":1,"458":2,"459":1,"464":1,"483":1,"490":2,"491":2,"492":1,"493":2,"494":1,"499":1,"507":1,"527":1,"536":2,"539":1,"545":1,"549":2,"551":1,"556":1,"563":1,"564":1,"571":1,"572":1,"574":1,"577":1,"579":1,"583":2,"585":2,"592":2,"593":1,"600":1,"608":1,"609":2,"610":1,"618":2,"619":1,"625":1,"626":1,"627":1,"633":1,"634":1,"635":1,"639":1,"640":2,"649":1,"651":1,"656":1,"657":1,"658":1,"665":1,"667":1,"674":5,"682":2,"684":1,"690":4,"692":1,"693":1,"698":3,"699":1,"702":1,"707":2,"708":1,"709":1,"714":1,"715":1,"716":1,"725":1,"727":1,"732":1,"733":4,"734":1,"735":2,"736":1,"740":1,"741":2,"742":1,"749":1,"757":3,"758":2,"761":1,"766":2,"768":1,"773":1,"775":1,"782":1,"790":2,"791":1,"799":1,"801":1,"803":1,"813":3,"814":1,"815":1,"819":2,"820":1,"826":1,"827":7,"828":1,"829":3,"832":1,"838":3,"839":1,"840":1,"846":1,"847":1,"849":1,"853":1,"861":1,"862":1,"876":1,"881":2,"882":1,"889":2,"891":1,"897":2,"898":1,"899":1,"903":1,"904":1,"905":6,"907":1,"913":2,"920":1,"923":2,"924":2,"926":3,"931":1,"932":1,"938":1,"939":1,"940":2,"944":1,"946":2,"948":2,"955":1,"956":1,"962":1,"966":1,"968":1,"971":1,"972":1,"980":1,"988":1,"990":1,"995":1,"996":1,"1005":1,"1006":1,"1013":1,"1018":4,"1020":1,"1027":2,"1034":1,"1036":1,"1041":1,"1042":2,"1043":1,"1049":1,"1050":2,"1051":2,"1053":1,"1059":1,"1061":1,"1067":1,"1069":1,"1076":1,"1082":1,"1083":1,"1084":2,"1085":1,"1090":1,"1091":2,"1092":1,"1093":2,"1107":1,"1108":1,"1116":2,"1118":1,"1124":1,"1132":1,"1133":2,"1135":1,"1140":1,"1161":1,"1175":1,"1178":1,"1184":2,"1191":2,"1200":1,"1212":4,"1220":1,"1223":1,"1228":1,"1229":14,"1230":1,"1231":2,"1232":1,"1233":1,"1234":1,"1235":1,"1237":22,"1239":1,"1241":2,"1243":2,"1244":1,"1247":26,"1250":1,"1251":2,"1253":1,"1255":1,"1259":28,"1260":1,"1262":1,"1263":2,"1264":1,"1265":1,"1266":1,"1267":1,"1269":1,"1270":48,"1271":7,"1274":3,"1275":3,"1276":2,"1277":1,"1278":2,"1279":3,"1280":2,"1281":3,"1283":3,"1284":1,"1286":110,"1289":1,"1290":2,"1292":1,"1294":2,"1295":1,"1297":2,"1300":61,"1301":17,"1307":1,"1309":27,"1310":8,"1311":61,"1312":4,"1315":1,"1316":3,"1317":2,"1318":4,"1320":2,"1322":1,"1323":66,"1324":102,"1325":1,"1328":1,"1330":1,"1331":1,"1332":1,"1334":3,"1335":1,"1336":2,"1337":1,"1339":54,"1342":6,"1345":1,"1347":1,"1348":1,"1349":1,"1350":37,"1352":2,"1353":2,"1354":1,"1355":1,"1357":2,"1358":1,"1359":144,"1362":1,"1368":2,"1369":12,"1371":1,"1372":1,"1373":1,"1374":1,"1378":1,"1380":24,"1384":1,"1389":2,"1391":2,"1395":82,"1396":90,"1399":1,"1400":1,"1402":31,"1405":1,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1415":56,"1416":13,"1417":38,"1420":1,"1421":1,"1423":1,"1424":1,"1426":1,"1427":10,"1428":2,"1430":1,"1433":1,"1435":2,"1436":183,"1438":17,"1441":3,"1442":2,"1443":2,"1447":2,"1449":1,"1453":2,"1454":1,"1455":15,"1456":3,"1459":1,"1462":1,"1463":1,"1466":1,"1467":18,"1472":1,"1473":2,"1474":1,"1476":7,"1480":1,"1482":1,"1483":1,"1484":1,"1486":3,"1488":3,"1489":2,"1490":2,"1491":2,"1492":5,"1496":6,"1498":1,"1505":1,"1526":5,"1531":1,"1577":1,"1591":1,"1631":3,"1632":1,"1635":2,"1639":1,"1641":1,"1651":1,"1652":1,"1656":1,"1664":2,"1668":2,"1683":2,"1684":1,"1685":4,"1686":5,"1689":1,"1693":1,"1697":2,"1719":1,"1728":1,"1749":3,"1764":5,"1765":2,"1767":1,"1783":1,"1789":1,"1790":1,"1796":1,"1799":1,"1800":1,"1801":1,"1805":1,"1810":1,"1814":1,"1815":3,"1816":1,"1821":1,"1822":1,"1825":2,"1829":2,"1832":2,"1839":2,"1840":1,"1842":2,"1843":1,"1847":1,"1849":3,"1852":2,"1856":1,"1864":1,"1869":1,"1871":1,"1873":1,"1874":1,"1876":1,"1881":1,"1888":1,"1891":1,"1892":1,"1900":1,"1902":1,"1904":1,"1908":1,"1909":2,"1918":1,"1919":2,"1920":1,"1922":2,"1923":3,"1927":2,"1929":1,"1931":1,"1933":1,"1934":1,"1936":1,"1937":1,"1940":1,"1942":1,"1944":1,"1946":1,"1947":1,"1948":1,"1953":1,"1958":1,"1960":1,"1961":1,"1964":1,"1965":1,"1968":1,"1970":1,"1971":1,"1974":1,"1975":1,"1976":2,"1977":1,"1978":2,"1979":3,"1980":2,"1981":1,"1982":2,"1983":1,"1993":1,"1996":1,"1998":1,"2000":2,"2001":3,"2002":1,"2003":1,"2008":1,"2010":1,"2012":1,"2024":1,"2027":1,"2029":1,"2032":1,"2036":1,"2042":2,"2043":2,"2044":2,"2047":2,"2048":1,"2057":2,"2061":1,"2064":2,"2066":1,"2070":1,"2074":1,"2075":1,"2076":1,"2081":1,"2085":1,"2088":1,"2092":1,"2098":1,"2101":1,"2105":1,"2107":2,"2109":1,"2111":1,"2114":1,"2119":1,"2125":1,"2129":1,"2131":1,"2134":1,"2136":1,"2138":1,"2140":1,"2141":1,"2142":3,"2143":1,"2144":1,"2148":2,"2149":1,"2150":1,"2151":1,"2157":1,"2158":1,"2165":1,"2166":1,"2167":3,"2171":1,"2174":1,"2176":1,"2178":1,"2179":1,"2185":1,"2186":1,"2190":1,"2192":1,"2195":1,"2199":2,"2203":1,"2230":1}}],["exif",{"3":{"439":1,"440":1,"1115":1,"1285":1,"1286":2,"1323":3,"1415":2,"1631":1,"2124":1,"2125":1,"2126":2,"2128":3}}],["exiting",{"3":{"626":1,"1460":1}}],["exits",{"3":{"0":2,"397":1,"566":2,"591":1,"626":5,"627":1,"757":1,"766":1,"1215":2,"1300":1,"1310":1,"1311":1,"1324":1,"1350":1,"1395":4,"1396":2,"1416":1,"1417":2,"1436":4,"1455":6,"1458":1,"1475":2,"1486":2,"1492":1,"1526":3,"1591":1,"1685":1,"2058":1}}],["exit",{"0":{"150":1},"1":{"1072":1,"1073":1,"1074":1,"1075":1,"1076":1,"1077":1,"1078":1,"1079":1},"3":{"0":4,"145":4,"153":1,"370":2,"373":1,"452":1,"633":1,"757":1,"766":1,"873":2,"876":1,"877":2,"980":1,"1072":1,"1073":1,"1075":2,"1077":2,"1078":1,"1079":2,"1212":1,"1270":2,"1286":2,"1293":1,"1300":2,"1311":1,"1323":1,"1324":4,"1359":1,"1380":1,"1396":3,"1415":1,"1417":9,"1445":1,"1453":2,"1455":3,"1459":1,"1475":1,"1486":1,"1496":1,"1526":3,"1577":1,"1591":1,"1673":1,"1930":1,"2058":1,"2232":1}}],["existent",{"3":{"1359":1,"1415":1,"1496":1}}],["existence",{"3":{"0":1,"230":1,"295":1,"318":5,"435":1,"459":1,"463":1,"468":1,"545":2,"550":1,"583":2,"856":1,"1003":1,"1058":1,"1170":1,"1244":1,"1247":2,"1270":3,"1276":1,"1286":16,"1300":2,"1308":1,"1309":1,"1311":2,"1322":1,"1323":3,"1324":1,"1352":1,"1359":16,"1364":1,"1369":4,"1380":2,"1395":1,"1396":3,"1402":1,"1412":1,"1415":2,"1436":3,"1438":2,"1591":1,"1600":1,"1631":1,"1666":1,"1748":1,"1759":1,"1761":1,"1765":1,"1772":1,"1995":1,"2034":1,"2066":1,"2090":1,"2137":1}}],["existed",{"3":{"22":1,"243":1,"274":1,"459":1,"477":1,"538":1,"539":1,"600":1,"698":1,"708":1,"837":1,"921":1,"945":1,"1017":1,"1018":1,"1020":1,"1066":1,"1090":1,"1118":1,"1160":1,"1183":1,"1247":3,"1249":1,"1250":1,"1253":1,"1259":4,"1270":1,"1273":1,"1286":5,"1300":1,"1301":2,"1311":2,"1318":1,"1320":1,"1323":3,"1324":6,"1330":1,"1350":4,"1355":1,"1359":8,"1365":1,"1369":2,"1395":2,"1396":3,"1402":1,"1408":1,"1415":2,"1417":1,"1427":1,"1436":10,"1465":1,"1467":1,"1496":3,"1631":1,"1641":1,"1727":1,"1748":1,"1765":1,"2048":1,"2061":1,"2167":1,"2168":1,"2186":1}}],["existinguser",{"3":{"1415":4}}],["existingdeletedbookmark",{"3":{"1396":3}}],["existinganswerids",{"3":{"1396":3}}],["existinganswer",{"3":{"1359":1}}],["existingspeaker",{"3":{"1359":1}}],["existingquestion",{"3":{"1359":1}}],["existingfilter",{"3":{"1286":3}}],["existing",{"2":{"1484":1,"1697":1},"3":{"0":21,"20":1,"24":1,"48":1,"52":1,"78":1,"79":1,"81":1,"99":1,"121":3,"126":1,"131":1,"142":1,"160":2,"200":1,"218":1,"241":1,"255":1,"265":1,"266":1,"273":1,"295":1,"309":1,"310":1,"318":1,"319":1,"350":1,"351":2,"352":2,"379":1,"380":1,"381":1,"390":1,"396":1,"401":1,"420":1,"434":1,"438":1,"443":1,"448":1,"449":2,"493":1,"518":1,"528":1,"534":1,"537":1,"545":1,"546":1,"549":2,"563":1,"575":1,"582":1,"587":1,"590":1,"599":2,"635":1,"640":1,"644":1,"648":1,"649":1,"664":1,"665":1,"690":1,"725":1,"733":3,"734":1,"736":1,"741":1,"742":1,"743":1,"756":1,"765":1,"789":1,"790":2,"793":1,"794":1,"799":2,"827":1,"846":1,"852":1,"854":2,"861":1,"889":1,"890":1,"905":1,"914":2,"920":2,"922":1,"923":1,"926":1,"938":1,"945":1,"948":1,"964":1,"1006":1,"1011":1,"1018":2,"1019":1,"1026":1,"1027":1,"1034":2,"1050":1,"1058":2,"1059":3,"1068":1,"1075":1,"1100":1,"1116":2,"1134":1,"1144":2,"1149":1,"1155":1,"1162":1,"1178":1,"1179":1,"1184":1,"1187":1,"1190":2,"1212":2,"1220":1,"1232":1,"1237":2,"1247":8,"1249":1,"1259":6,"1269":1,"1270":11,"1271":2,"1276":1,"1281":2,"1286":18,"1290":1,"1293":2,"1300":11,"1301":2,"1304":1,"1305":1,"1309":13,"1310":1,"1311":7,"1312":1,"1316":1,"1323":4,"1324":14,"1327":1,"1339":9,"1350":4,"1353":1,"1359":36,"1362":1,"1366":1,"1369":4,"1380":2,"1395":4,"1396":26,"1402":3,"1406":2,"1407":1,"1408":1,"1415":10,"1416":2,"1417":6,"1427":1,"1436":16,"1438":1,"1441":3,"1463":1,"1467":7,"1470":1,"1472":2,"1474":1,"1481":2,"1484":1,"1488":1,"1489":1,"1496":13,"1526":1,"1577":3,"1602":1,"1612":1,"1627":1,"1631":4,"1632":7,"1635":5,"1636":1,"1638":1,"1639":13,"1640":3,"1641":4,"1645":1,"1658":1,"1661":3,"1664":1,"1674":1,"1681":1,"1684":1,"1697":1,"1705":1,"1722":1,"1724":1,"1728":2,"1729":1,"1734":1,"1748":1,"1765":1,"1767":1,"1777":1,"1800":1,"1810":1,"1815":1,"1826":1,"1859":1,"1886":1,"1889":1,"1915":1,"1933":1,"1944":1,"1945":2,"1977":1,"1978":2,"1979":2,"1994":1,"2075":1,"2097":2,"2101":1,"2131":1,"2133":1,"2142":1,"2150":1,"2196":1,"2198":2,"2232":2}}],["existsasync",{"2":{"91":1,"295":1,"1364":1,"1777":1},"3":{"91":1,"295":1,"459":1,"545":2,"550":1,"1244":1,"1247":4,"1286":6,"1300":1,"1323":1,"1359":5,"1364":1,"1369":1,"1396":2,"1415":1,"1639":1,"1751":1,"1777":1,"1815":1}}],["exists",{"0":{"813":1,"1718":1},"2":{"1255":1},"3":{"0":6,"19":1,"24":1,"33":1,"45":1,"93":1,"103":1,"122":1,"135":3,"136":1,"143":1,"150":2,"157":1,"160":1,"161":1,"175":1,"179":1,"200":1,"209":1,"225":1,"243":2,"245":1,"254":1,"256":1,"265":3,"268":1,"273":1,"293":1,"301":1,"303":1,"305":1,"309":1,"318":2,"324":1,"325":1,"326":1,"336":1,"341":1,"343":2,"350":1,"361":1,"372":1,"383":1,"388":1,"390":1,"391":1,"398":1,"401":1,"413":2,"415":1,"418":2,"420":1,"423":1,"428":1,"442":1,"443":1,"448":1,"463":1,"477":1,"490":1,"491":1,"492":1,"493":2,"507":1,"517":1,"528":1,"536":1,"539":1,"543":1,"546":1,"548":1,"549":1,"551":1,"556":2,"557":1,"563":1,"572":1,"583":2,"585":1,"591":2,"592":1,"593":1,"599":3,"605":1,"607":1,"608":1,"609":3,"611":2,"618":2,"626":1,"633":2,"640":1,"645":1,"656":1,"657":1,"658":2,"665":3,"674":1,"676":4,"681":1,"689":1,"690":2,"698":2,"703":2,"707":2,"714":1,"716":2,"718":1,"720":1,"724":1,"725":1,"727":1,"736":1,"741":2,"749":1,"753":1,"759":1,"760":1,"762":1,"773":1,"790":1,"792":2,"797":1,"798":1,"804":1,"808":1,"809":2,"810":1,"811":2,"812":2,"818":1,"819":1,"820":2,"821":1,"827":2,"832":1,"834":1,"837":2,"838":1,"840":1,"862":1,"871":1,"882":1,"884":1,"890":1,"891":1,"913":2,"914":1,"916":1,"924":1,"926":2,"932":1,"945":1,"946":1,"947":1,"953":1,"956":1,"963":1,"965":1,"973":1,"974":2,"980":1,"988":1,"996":1,"997":1,"1005":1,"1014":1,"1018":1,"1019":1,"1020":1,"1029":1,"1042":2,"1043":1,"1050":2,"1051":1,"1052":1,"1067":2,"1071":1,"1077":1,"1082":1,"1092":1,"1100":1,"1118":2,"1132":1,"1134":2,"1142":1,"1168":1,"1190":1,"1191":1,"1196":1,"1200":1,"1212":1,"1229":8,"1231":1,"1232":1,"1235":1,"1237":12,"1239":2,"1240":1,"1241":2,"1242":1,"1247":12,"1251":1,"1254":1,"1255":1,"1257":1,"1259":9,"1270":15,"1271":4,"1274":1,"1276":1,"1277":2,"1278":1,"1281":1,"1286":61,"1288":1,"1289":2,"1290":1,"1293":1,"1297":2,"1300":40,"1301":8,"1304":1,"1307":1,"1309":7,"1310":2,"1311":34,"1312":1,"1313":1,"1315":1,"1320":2,"1322":1,"1323":35,"1324":63,"1332":1,"1333":2,"1334":1,"1337":2,"1339":21,"1345":1,"1348":1,"1349":1,"1350":28,"1352":3,"1357":2,"1359":100,"1368":1,"1369":8,"1371":1,"1372":2,"1375":1,"1376":1,"1379":1,"1380":10,"1383":1,"1389":1,"1395":33,"1396":46,"1401":1,"1402":14,"1405":1,"1406":1,"1410":1,"1411":1,"1414":2,"1415":40,"1416":8,"1417":37,"1418":1,"1427":6,"1431":1,"1436":106,"1438":5,"1441":1,"1443":2,"1445":3,"1447":1,"1448":1,"1449":2,"1452":1,"1455":10,"1457":2,"1458":1,"1459":1,"1465":1,"1466":1,"1467":8,"1468":1,"1470":1,"1471":1,"1472":3,"1474":2,"1475":2,"1476":1,"1478":1,"1479":1,"1482":1,"1484":2,"1486":2,"1488":3,"1489":4,"1490":1,"1491":2,"1492":2,"1496":2,"1524":2,"1526":6,"1531":5,"1532":1,"1534":3,"1539":1,"1545":1,"1547":1,"1548":1,"1577":2,"1585":1,"1591":1,"1596":3,"1626":1,"1627":1,"1631":5,"1632":9,"1633":1,"1635":1,"1638":3,"1639":6,"1641":6,"1646":1,"1651":2,"1653":1,"1654":1,"1666":1,"1668":1,"1675":1,"1683":1,"1684":1,"1686":2,"1692":1,"1701":1,"1720":2,"1724":1,"1726":1,"1730":1,"1732":1,"1737":1,"1747":1,"1748":1,"1749":6,"1755":2,"1760":1,"1765":3,"1776":3,"1790":1,"1802":1,"1810":1,"1811":1,"1815":2,"1825":1,"1831":1,"1835":1,"1837":1,"1849":1,"1851":3,"1858":1,"1861":1,"1864":2,"1874":1,"1878":1,"1881":2,"1889":1,"1902":1,"1908":1,"1909":3,"1923":1,"1926":1,"1934":1,"1945":1,"1947":1,"1948":1,"1952":1,"1953":1,"1954":3,"1955":1,"1956":1,"1970":1,"1971":1,"1975":1,"1989":1,"1994":1,"1995":4,"2000":1,"2009":1,"2014":1,"2033":1,"2037":1,"2040":1,"2041":1,"2046":1,"2057":1,"2063":1,"2074":1,"2075":2,"2078":1,"2107":1,"2121":1,"2127":1,"2131":1,"2132":1,"2135":1,"2138":1,"2151":1,"2156":1,"2165":1,"2166":3,"2167":1,"2168":3,"2174":1,"2176":1,"2183":2,"2185":1,"2188":1,"2204":1}}],["exist",{"0":{"1226":1,"1234":1},"3":{"0":11,"11":1,"16":1,"19":1,"61":1,"62":1,"68":1,"95":1,"97":1,"117":1,"122":2,"141":2,"142":1,"160":1,"219":1,"255":1,"341":1,"343":1,"395":1,"412":1,"416":1,"418":1,"422":2,"428":1,"433":1,"443":1,"468":1,"477":1,"518":1,"536":1,"543":1,"549":1,"551":1,"556":1,"560":1,"583":1,"591":1,"592":1,"601":1,"611":1,"619":1,"622":1,"625":1,"629":1,"638":1,"658":1,"666":1,"677":1,"681":1,"691":1,"725":1,"735":1,"780":1,"803":1,"819":1,"827":1,"829":2,"831":1,"846":1,"847":1,"856":1,"861":1,"876":1,"890":1,"899":1,"905":2,"914":1,"916":1,"918":1,"923":1,"924":1,"926":2,"956":1,"989":2,"1021":1,"1034":1,"1066":1,"1092":1,"1114":1,"1117":1,"1125":1,"1153":1,"1167":1,"1191":1,"1218":1,"1223":1,"1229":2,"1237":3,"1241":1,"1247":3,"1251":1,"1259":2,"1262":1,"1263":2,"1270":9,"1271":2,"1286":20,"1289":1,"1297":1,"1300":10,"1301":1,"1308":1,"1309":3,"1311":6,"1312":1,"1315":1,"1320":1,"1323":11,"1324":20,"1326":1,"1328":1,"1332":1,"1339":12,"1348":2,"1349":1,"1350":11,"1353":1,"1359":30,"1364":1,"1368":1,"1369":4,"1371":1,"1373":1,"1380":1,"1395":5,"1396":21,"1402":3,"1404":1,"1409":1,"1415":11,"1416":4,"1417":11,"1433":1,"1436":31,"1438":1,"1453":2,"1454":1,"1455":6,"1456":1,"1459":1,"1465":1,"1467":5,"1470":2,"1471":1,"1473":1,"1474":1,"1476":1,"1477":1,"1489":2,"1492":4,"1496":2,"1507":1,"1526":1,"1545":1,"1550":1,"1553":1,"1557":1,"1567":1,"1571":1,"1577":4,"1596":1,"1597":1,"1626":1,"1631":5,"1632":9,"1634":1,"1635":1,"1638":1,"1639":1,"1640":2,"1649":1,"1653":1,"1669":1,"1700":1,"1701":1,"1728":1,"1749":3,"1755":2,"1765":2,"1767":3,"1776":2,"1801":1,"1803":2,"1804":1,"1810":1,"1812":1,"1816":1,"1821":1,"1824":1,"1825":1,"1840":1,"1843":1,"1856":1,"1867":1,"1876":1,"1879":1,"1881":1,"1894":1,"1928":1,"1941":1,"1961":1,"1971":1,"2001":1,"2033":1,"2037":1,"2040":1,"2047":2,"2056":1,"2080":1,"2097":1,"2098":1,"2114":1,"2116":2,"2122":1,"2159":1,"2164":1,"2169":1,"2179":1,"2180":2,"2181":1,"2182":1}}],["exchanging",{"3":{"351":1,"1311":1,"1324":1,"1436":1,"1577":1}}],["exchangecode",{"3":{"1311":1}}],["exchanged",{"3":{"1300":1,"1324":3,"1416":1,"1582":1,"1972":1}}],["exchangeresponse",{"3":{"1199":2,"1207":1}}],["exchangeoauthcodeasync",{"2":{"420":2,"423":1,"426":1,"428":1},"3":{"420":2,"423":1,"426":1,"428":1,"1324":2,"1436":1}}],["exchangeasync",{"2":{"1535":1,"1975":1,"1976":1},"3":{"350":1,"1300":1,"1311":4,"1535":1,"1975":1,"1976":1}}],["exchanges",{"3":{"0":1,"350":1,"355":1,"419":1,"499":1,"503":1,"528":1,"1184":1,"1298":1,"1300":5,"1324":1,"1454":1,"1455":1,"1585":1,"1727":1}}],["exchange",{"0":{"1975":1},"1":{"347":1,"348":1,"349":1,"350":1,"351":1,"352":1,"353":1,"354":1,"355":1},"2":{"178":1,"818":1,"1334":1,"1843":1},"3":{"0":5,"159":1,"160":1,"161":1,"178":1,"340":1,"341":1,"342":1,"347":1,"350":6,"351":1,"352":1,"418":1,"420":1,"421":1,"422":1,"423":3,"426":3,"428":1,"528":3,"529":1,"530":2,"531":1,"640":1,"733":1,"735":1,"818":1,"872":1,"923":1,"1184":1,"1259":1,"1300":8,"1301":8,"1311":10,"1323":6,"1324":13,"1334":1,"1339":2,"1359":1,"1395":1,"1406":1,"1415":2,"1417":4,"1436":7,"1438":2,"1454":3,"1467":1,"1535":1,"1585":1,"1667":1,"1776":1,"1843":1,"1898":2,"1911":1,"1974":1,"1975":3,"1977":2,"1978":2,"1979":3,"2013":1,"2078":1,"2079":1,"2232":1}}],["excuse",{"3":{"1286":1}}],["excuses",{"3":{"318":1,"565":1,"1300":1,"1436":1,"1994":1}}],["excused",{"3":{"135":1,"139":1,"1436":2}}],["excluding",{"3":{"152":1,"619":1,"690":1,"799":1,"940":1,"1196":1,"1280":1,"1282":1,"1286":3,"1311":1,"1323":1,"1332":2,"1339":1,"1350":2,"1359":4,"1402":1,"1415":1,"1436":7,"1455":1,"1467":4,"1631":1,"1748":1,"1801":1,"2067":1}}],["excludereservedids",{"3":{"1359":1}}],["excludeitemid",{"3":{"1350":1}}],["excludefromdescription",{"3":{"1300":1,"1311":1,"1324":3,"1339":1}}],["excludessoftdeleteditems",{"2":{"1686":1},"3":{"1686":1}}],["excludesframeworkevents",{"3":{"1436":1}}],["excludesessionid",{"3":{"1359":7}}],["excludescoredsinceasync",{"3":{"1359":3}}],["excludesdomainevents",{"3":{"1286":1}}],["excludes",{"3":{"233":1,"607":1,"609":1,"819":1,"889":2,"1212":1,"1259":1,"1282":1,"1286":6,"1323":3,"1324":1,"1332":1,"1339":2,"1350":2,"1359":6,"1372":1,"1380":2,"1396":2,"1401":1,"1404":1,"1415":1,"1433":1,"1436":7,"1455":2,"1467":1,"1486":1,"1492":1,"1534":1,"1666":1,"2007":1,"2061":1}}],["exclude",{"3":{"0":1,"38":1,"141":1,"403":1,"609":1,"612":1,"1082":1,"1212":1,"1247":1,"1286":1,"1350":1,"1352":1,"1359":2,"1402":1,"1436":1,"1631":1,"1635":1,"1639":2,"1801":1,"2061":1,"2158":3,"2232":1}}],["excludedpathfragments",{"3":{"1436":3}}],["excludedpathprefixes",{"3":{"698":1,"1286":1,"1320":1}}],["excluded",{"0":{"1209":1},"3":{"0":5,"24":1,"135":2,"149":1,"474":1,"482":1,"490":1,"536":1,"573":1,"698":1,"700":1,"734":1,"799":1,"803":1,"805":1,"867":1,"871":1,"980":1,"982":1,"988":1,"996":1,"1026":1,"1074":1,"1075":1,"1078":1,"1184":1,"1196":1,"1204":1,"1209":1,"1212":2,"1215":1,"1227":1,"1231":1,"1259":4,"1270":2,"1277":1,"1286":6,"1300":2,"1311":2,"1323":2,"1324":6,"1339":3,"1350":4,"1359":13,"1369":1,"1395":2,"1396":5,"1401":1,"1402":7,"1410":1,"1415":8,"1436":13,"1438":1,"1459":1,"1467":1,"1474":2,"1486":1,"1487":1,"1494":1,"1496":3,"1497":1,"1526":1,"1527":1,"1553":1,"1577":1,"1578":1,"1582":1,"1591":1,"1592":1,"1611":1,"1617":1,"1630":1,"1631":5,"1632":1,"1633":2,"1635":5,"1637":1,"1638":2,"1639":2,"1640":4,"1664":1,"1784":1,"1851":2,"1923":1,"2067":1,"2100":1,"2158":1}}],["exclusivity",{"3":{"709":1,"1911":1}}],["exclusivemajorceiling",{"3":{"1436":1}}],["exclusively",{"3":{"193":1,"930":1,"1247":1,"1259":1,"1270":2,"1286":3,"1300":2,"1311":1,"1324":1,"1339":1,"1342":1,"1350":1,"1359":1,"1417":1,"1427":1,"1465":1,"1773":1}}],["exclusive",{"3":{"0":2,"159":1,"996":1,"997":1,"1026":1,"1074":1,"1212":1,"1269":1,"1300":1,"1316":1,"1323":3,"1348":1,"1350":4,"1395":1,"1396":2,"1402":1,"1427":1,"1436":2,"1489":1,"1630":2,"1749":1,"1764":1,"1765":1,"1767":1,"1909":1}}],["exclusionid",{"3":{"1359":1}}],["exclusiontestdbcontext",{"3":{"1199":2,"1207":1}}],["exclusionaggregate",{"3":{"1199":3,"1207":1}}],["exclusionevent",{"3":{"1199":2,"1207":1}}],["exclusions",{"3":{"876":1,"1006":1,"1353":1,"1415":1,"1436":1,"1455":1,"1486":1,"1496":2,"2043":1,"2158":1}}],["exclusion",{"1":{"993":1,"994":1,"995":1,"996":1,"997":1,"998":1,"999":1,"1000":1},"3":{"0":1,"135":1,"142":1,"159":2,"161":1,"235":1,"484":1,"486":1,"490":1,"524":1,"607":1,"696":3,"984":1,"993":1,"995":2,"996":3,"997":1,"998":2,"1000":1,"1212":1,"1237":2,"1260":1,"1269":1,"1270":4,"1274":2,"1286":11,"1300":3,"1301":2,"1311":6,"1323":1,"1339":2,"1344":2,"1350":2,"1352":1,"1359":5,"1367":1,"1395":1,"1396":1,"1402":1,"1415":1,"1417":1,"1436":5,"1438":2,"1443":1,"1474":1,"1496":1,"1534":2,"1592":1,"1635":1,"2057":1,"2120":1,"2232":1}}],["excerpt",{"3":{"1324":1}}],["excellent",{"3":{"1537":1,"1797":1,"1804":1}}],["excel",{"3":{"741":1,"1311":2}}],["exceeding",{"3":{"1427":1,"1714":1}}],["exceed",{"3":{"709":1,"828":1,"829":1,"1286":2,"1292":1,"1350":3,"1359":1,"1395":1,"1399":1,"1436":6,"1685":2,"1686":2,"1832":1,"2166":1}}],["exceeded",{"3":{"556":1,"861":1,"1300":3,"1312":2,"1324":1,"1378":1,"1380":1,"1396":1,"1467":1,"1476":1,"1640":2,"1641":2,"1927":1}}],["exceeds",{"3":{"0":1,"137":1,"369":1,"576":1,"591":1,"626":1,"861":1,"1091":1,"1237":1,"1247":1,"1300":2,"1309":2,"1350":1,"1417":1,"1422":1,"1427":1,"1436":2,"1456":1,"1467":1,"1638":1}}],["excessive",{"3":{"1639":2}}],["excessattempts",{"3":{"1300":1}}],["excess",{"3":{"282":1,"1324":2,"1339":1,"1436":1,"1640":1,"1641":1}}],["exceptargumentguards",{"3":{"1436":2}}],["excepted",{"3":{"619":1,"1488":1}}],["except",{"3":{"0":5,"31":2,"109":1,"130":2,"135":2,"138":2,"141":1,"142":1,"359":1,"388":1,"401":1,"484":1,"766":1,"881":1,"905":1,"926":1,"1034":1,"1092":1,"1247":2,"1259":1,"1270":1,"1271":1,"1278":1,"1286":3,"1300":4,"1309":1,"1311":7,"1312":1,"1318":1,"1323":1,"1324":4,"1333":1,"1339":3,"1350":1,"1359":10,"1369":2,"1392":1,"1395":7,"1396":5,"1415":2,"1427":1,"1436":11,"1438":1,"1443":1,"1455":1,"1459":1,"1490":1,"1611":1,"1627":1,"1635":1,"1760":1,"1761":1,"1821":1,"1823":1,"1856":1,"1878":1,"1898":1,"1904":1,"1919":1,"1922":2,"1948":1,"1954":1,"1955":1,"2100":1}}],["exceptionobject",{"3":{"1417":1}}],["exceptionhandler",{"3":{"1311":2,"1436":1,"1666":1}}],["exceptionhandlertests",{"3":{"1199":1,"1207":3,"1229":1}}],["exceptionhandled",{"3":{"1311":2}}],["exception",{"0":{"1218":1,"1873":1},"2":{"896":1,"899":1,"1821":1,"1919":1,"2032":1},"3":{"0":15,"17":1,"20":1,"24":1,"39":1,"53":1,"94":1,"95":1,"98":1,"107":2,"108":1,"109":12,"110":3,"111":6,"115":3,"122":1,"125":6,"135":1,"141":1,"142":1,"164":1,"175":1,"200":1,"230":1,"235":1,"265":3,"279":1,"280":2,"285":1,"287":1,"303":1,"310":1,"342":1,"370":1,"386":1,"388":2,"395":2,"397":1,"407":4,"414":1,"415":1,"420":1,"427":1,"440":1,"459":1,"465":1,"536":4,"539":1,"549":1,"583":1,"586":1,"618":3,"619":1,"620":2,"651":1,"657":2,"672":1,"674":2,"683":1,"688":1,"690":1,"691":2,"692":1,"698":1,"749":1,"757":1,"819":1,"827":1,"840":1,"881":1,"882":2,"896":2,"897":3,"899":2,"912":2,"913":1,"914":2,"917":1,"918":1,"965":1,"980":1,"988":6,"989":1,"990":1,"1002":1,"1004":1,"1005":1,"1018":2,"1022":1,"1042":1,"1054":1,"1058":1,"1107":1,"1124":2,"1126":1,"1134":1,"1201":1,"1202":1,"1203":2,"1208":1,"1212":2,"1217":1,"1226":1,"1228":1,"1229":18,"1237":3,"1241":1,"1247":12,"1252":2,"1259":19,"1263":6,"1269":1,"1270":34,"1271":1,"1273":1,"1275":1,"1278":3,"1284":1,"1286":75,"1288":1,"1293":1,"1299":1,"1300":18,"1301":1,"1304":2,"1309":8,"1311":41,"1312":22,"1318":1,"1323":36,"1324":52,"1328":2,"1339":19,"1350":5,"1352":2,"1355":3,"1357":1,"1358":1,"1359":33,"1366":1,"1369":4,"1378":1,"1379":1,"1380":2,"1390":1,"1395":24,"1396":40,"1402":6,"1413":1,"1415":19,"1416":8,"1417":31,"1423":2,"1424":1,"1427":12,"1436":70,"1438":8,"1445":1,"1447":1,"1450":1,"1455":2,"1467":1,"1485":1,"1487":1,"1488":6,"1489":2,"1495":1,"1496":12,"1498":1,"1526":1,"1591":1,"1615":1,"1631":1,"1634":1,"1638":1,"1640":2,"1651":1,"1660":1,"1666":1,"1669":1,"1685":1,"1702":1,"1729":1,"1741":1,"1753":1,"1774":2,"1804":1,"1806":6,"1807":1,"1815":1,"1821":2,"1822":4,"1825":1,"1826":1,"1827":1,"1840":5,"1873":1,"1885":1,"1886":1,"1912":1,"1918":1,"1919":1,"1922":1,"1927":1,"1933":2,"1934":2,"1951":1,"1954":3,"1959":1,"1994":1,"1998":1,"2000":2,"2032":2,"2057":3,"2060":1,"2072":1,"2090":1,"2092":1,"2096":2,"2117":1,"2120":1,"2123":1,"2126":1,"2132":1,"2159":1,"2165":3,"2169":2,"2175":2,"2188":1,"2190":1,"2191":1,"2202":1,"2232":1}}],["exceptional",{"3":{"0":2,"109":1,"111":1,"1217":1,"1218":1,"1226":2,"1229":1,"1324":1,"1415":1,"1436":3,"1803":1,"1805":1,"1806":1,"1807":1,"1959":1}}],["exceptions",{"0":{"1226":1,"1497":1,"1804":1},"1":{"106":1,"107":1,"108":1,"109":1,"110":1,"111":1,"112":1,"113":1,"1803":1,"1804":1,"1805":1,"1806":1,"1807":1},"2":{"1229":1},"3":{"0":5,"39":1,"106":1,"108":1,"109":2,"111":1,"120":3,"139":1,"265":1,"314":1,"318":1,"344":1,"549":1,"619":1,"674":2,"677":1,"683":1,"690":1,"798":1,"964":1,"988":1,"992":1,"1004":1,"1049":1,"1192":1,"1203":3,"1207":10,"1211":1,"1212":1,"1217":1,"1218":1,"1226":4,"1229":13,"1247":1,"1256":1,"1259":1,"1270":3,"1271":1,"1278":1,"1286":5,"1287":1,"1300":2,"1311":5,"1312":5,"1323":2,"1324":7,"1339":1,"1350":1,"1359":2,"1373":1,"1396":6,"1402":2,"1415":3,"1417":3,"1436":5,"1467":1,"1489":1,"1496":1,"1504":1,"1539":1,"1552":1,"1577":2,"1640":1,"1653":1,"1662":1,"1700":1,"1786":1,"1802":1,"1803":2,"1804":1,"1805":2,"1806":2,"1807":5,"1840":1,"1920":1,"1923":1,"1927":2,"1930":2,"1933":1,"1940":1,"1954":1,"1959":1,"1994":1,"2012":1,"2043":1,"2206":1,"2230":1,"2232":1}}],["exec",{"3":{"1479":2}}],["executably",{"3":{"1237":1}}],["executable",{"0":{"1489":1},"3":{"0":1,"1116":1,"1134":1,"1162":1,"1169":1,"1214":1,"1229":1,"1237":3,"1247":1,"1271":2,"1285":1,"1286":1,"1311":1,"1312":2,"1347":1,"1350":1,"1353":1,"1359":9,"1436":19,"1438":3,"1445":1,"1457":1,"1461":1,"1485":1,"1489":2,"1491":1,"1492":1,"1523":1,"1525":1,"1526":1,"1571":1,"1574":1,"1576":1,"1591":1,"1670":1,"1782":1,"1793":1,"1811":1,"1898":1,"2038":1,"2040":1,"2043":1,"2063":2,"2075":1}}],["executor",{"3":{"0":1,"881":1,"1133":1,"1135":2,"1136":1,"1137":1,"1175":1,"1176":1,"1180":1,"1286":2,"1309":4,"1310":1,"1323":1,"1324":3,"1395":4,"1396":5,"1415":2,"1417":1,"1436":2,"1653":1}}],["executing",{"3":{"0":1,"157":2,"698":1,"709":1,"995":1,"1044":1,"1212":1,"1246":1,"1247":1,"1270":1,"1276":1,"1286":3,"1300":1,"1311":2,"1323":2,"1332":1,"1359":1,"1416":1,"1436":1,"1438":1,"1577":1,"1851":1,"1909":1,"1912":1,"2192":1,"2232":1}}],["executionrejectedexception",{"3":{"1300":1}}],["executionandpublication",{"2":{"1288":1},"3":{"1288":1,"1300":1}}],["executions",{"3":{"1263":1,"1270":1,"1323":1,"1708":1,"1821":1,"1909":1}}],["executionlog",{"3":{"1199":4,"1207":1}}],["executionconfigurationbuilder",{"2":{"1067":1},"3":{"1067":1}}],["executioncontext",{"2":{"1246":1},"3":{"265":1,"1246":1,"1324":1,"1417":1}}],["execution",{"0":{"1262":1,"2184":2},"1":{"515":1,"516":1,"517":1,"518":1,"519":1,"520":1,"521":1,"522":1,"523":1,"524":1,"985":1,"986":1,"987":1,"988":1,"989":1,"990":1,"991":1,"992":1},"2":{"2191":1},"3":{"0":9,"21":1,"71":2,"117":1,"119":1,"120":1,"121":1,"122":3,"157":1,"407":1,"435":1,"515":1,"518":1,"549":1,"579":1,"707":3,"708":1,"711":1,"715":1,"819":2,"985":1,"987":2,"988":1,"991":1,"992":1,"996":1,"997":1,"1042":5,"1044":2,"1046":1,"1135":1,"1150":1,"1152":1,"1212":4,"1243":1,"1245":1,"1247":6,"1251":1,"1261":1,"1262":1,"1263":1,"1267":2,"1269":1,"1270":21,"1271":1,"1274":3,"1275":5,"1278":2,"1286":19,"1300":2,"1301":1,"1309":2,"1311":3,"1312":1,"1316":2,"1317":1,"1318":4,"1323":11,"1324":1,"1357":1,"1359":6,"1415":2,"1417":1,"1436":7,"1438":2,"1455":3,"1461":1,"1463":1,"1467":1,"1476":1,"1486":1,"1488":1,"1492":1,"1507":1,"1524":1,"1525":1,"1526":1,"1531":2,"1534":1,"1537":2,"1574":2,"1575":1,"1577":4,"1578":1,"1584":1,"1585":1,"1588":1,"1592":1,"1611":1,"1664":2,"1747":4,"1774":2,"1784":1,"1821":1,"1826":1,"1840":3,"1865":1,"1909":1,"1917":1,"1948":1,"2032":2,"2038":1,"2096":2,"2156":2,"2184":1,"2188":1,"2191":2,"2192":3,"2193":3,"2220":1,"2232":2}}],["executeandstoreasync",{"3":{"1311":1}}],["executeasync",{"2":{"255":1,"391":1,"522":1,"523":1,"524":1,"881":1,"897":1,"988":1,"1100":1,"1310":1,"1399":1,"1414":1,"1653":1,"1665":1,"1919":1,"1922":1,"1924":1,"1946":1,"2184":1},"3":{"0":1,"255":1,"391":1,"522":2,"523":2,"524":1,"536":1,"707":1,"881":1,"897":1,"988":1,"1100":4,"1212":1,"1243":2,"1244":1,"1247":12,"1259":3,"1269":1,"1270":3,"1273":1,"1286":9,"1310":1,"1311":1,"1323":5,"1324":8,"1334":1,"1339":7,"1395":9,"1396":27,"1399":2,"1402":6,"1414":1,"1415":1,"1442":1,"1653":1,"1665":1,"1919":2,"1922":1,"1924":1,"1946":1,"2167":1,"2184":1}}],["executeunderdistributedlockasync",{"3":{"1311":1}}],["executeunderprocesslockasync",{"3":{"1311":1}}],["executeupdateasync",{"2":{"20":1,"24":1,"995":1,"1844":1,"1948":1},"3":{"20":1,"24":1,"698":1,"707":2,"995":1,"1259":4,"1278":1,"1286":16,"1290":1,"1300":1,"1309":2,"1317":1,"1323":3,"1496":2,"1749":1,"1765":1,"1844":1,"1948":1}}],["executeupdate",{"2":{"1255":1,"1844":1},"3":{"0":1,"536":1,"540":1,"1252":1,"1255":1,"1259":3,"1278":1,"1286":5,"1300":1,"1309":1,"1591":1,"1664":1,"1844":2}}],["executerawlookupmethod",{"3":{"1286":1}}],["executerawlookupasync",{"3":{"1286":5}}],["executeclaimedjobasync",{"3":{"1323":3}}],["executeclaimedasync",{"3":{"1286":1}}],["executecycleasync",{"3":{"536":1,"1286":1,"1317":1,"1323":2,"2167":1}}],["executewithserversideincludesasync",{"3":{"1247":2}}],["executewithmanualnavigationasync",{"3":{"1247":2,"1310":1}}],["execute",{"0":{"2188":1},"3":{"0":1,"155":1,"156":1,"157":2,"159":1,"590":1,"707":1,"733":1,"990":1,"995":1,"996":1,"1202":1,"1203":1,"1244":1,"1247":2,"1259":1,"1269":1,"1270":2,"1284":1,"1286":3,"1301":1,"1309":1,"1311":7,"1323":3,"1324":1,"1339":1,"1359":2,"1395":2,"1417":1,"1436":4,"1445":1,"1467":1,"1490":1,"1591":1,"1666":1,"1668":1,"1765":1,"1815":1,"1909":6,"1911":2,"1912":2,"1915":1,"2056":1,"2193":2}}],["executeintransactionasync",{"2":{"21":1,"120":1,"535":1,"536":1,"539":1,"540":1,"987":2,"988":1,"1278":1,"1358":1,"1406":1,"1664":1,"1822":1,"1840":2,"1852":1,"2092":1,"2096":1},"3":{"0":1,"21":1,"120":1,"535":1,"536":1,"539":1,"540":2,"987":2,"988":3,"1212":1,"1263":1,"1270":3,"1278":1,"1286":10,"1358":1,"1359":2,"1406":1,"1415":2,"1591":1,"1664":1,"1822":1,"1840":3,"1852":1,"2092":1,"2096":1}}],["executeprojectedasync",{"2":{"549":1,"1243":1,"1244":1,"1247":1},"3":{"0":1,"549":1,"1243":1,"1244":1,"1247":4}}],["executedeletingfixture",{"3":{"1199":2,"1207":1,"1436":7}}],["executedelete",{"3":{"39":1,"1277":1,"1286":1,"1359":1,"1436":3}}],["executedeleteasync",{"2":{"39":1},"3":{"0":1,"39":1,"905":1,"1278":1,"1286":12,"1359":2,"1436":10,"1526":1}}],["executed",{"3":{"0":5,"130":2,"136":2,"137":4,"140":2,"142":1,"218":1,"626":1,"628":1,"641":1,"827":1,"1042":1,"1050":2,"1055":1,"1212":1,"1246":1,"1247":1,"1270":4,"1286":5,"1311":1,"1312":1,"1318":1,"1324":1,"1339":2,"1359":12,"1380":2,"1395":2,"1396":1,"1402":2,"1405":1,"1411":1,"1416":1,"1431":1,"1436":2,"1438":1,"1441":1,"1455":2,"1489":1,"1496":1,"1526":1,"1591":2,"1706":1,"1784":1,"1824":1,"2034":1}}],["executesql",{"2":{"1132":1},"3":{"1132":1,"1436":1}}],["executesqlinterpolated",{"2":{"1132":1},"3":{"1132":1}}],["executesqlrawasync",{"2":{"1132":1,"1133":1},"3":{"0":1,"1132":1,"1133":1,"1431":1,"1436":3}}],["executesqlraw",{"2":{"1132":1},"3":{"0":1,"1132":1,"1133":1,"1431":1,"1436":3}}],["executes",{"3":{"0":2,"135":1,"136":1,"137":2,"159":2,"265":1,"707":1,"972":1,"1042":1,"1043":1,"1100":1,"1238":1,"1247":1,"1259":2,"1270":4,"1271":2,"1275":1,"1286":3,"1300":1,"1310":2,"1311":2,"1317":1,"1323":1,"1324":2,"1326":1,"1359":2,"1369":1,"1395":1,"1415":1,"1417":3,"1430":1,"1431":1,"1436":2,"1453":1,"1489":1,"1492":1,"1524":1,"1526":1,"1531":1,"1577":1,"1815":1,"1911":2,"2093":1,"2101":1,"2178":1,"2184":1,"2188":1,"2194":1,"2203":1}}],["exe",{"3":{"1286":1,"1416":1,"1479":1,"1486":2}}],["exercisable",{"3":{"1359":1}}],["exercising",{"3":{"914":1,"1271":1,"1427":1,"1436":6,"1437":4,"1456":1,"1460":1,"1490":1,"2067":1}}],["exercised",{"3":{"136":1,"150":1,"267":1,"359":1,"361":1,"448":2,"450":1,"455":1,"572":1,"583":1,"639":1,"659":1,"667":1,"707":1,"733":1,"812":1,"917":1,"998":1,"1075":1,"1212":1,"1237":4,"1247":5,"1259":1,"1264":1,"1270":12,"1271":1,"1286":11,"1300":4,"1301":3,"1309":2,"1311":1,"1323":14,"1324":10,"1339":1,"1350":3,"1359":16,"1364":1,"1369":4,"1380":2,"1395":4,"1396":6,"1402":1,"1415":4,"1417":3,"1427":6,"1429":1,"1432":1,"1436":28,"1438":2,"1446":1,"1488":1,"1490":1,"1496":1,"1500":1,"1577":3,"1671":2,"1673":1,"1842":1,"1860":2,"1884":1,"2055":1,"2063":1,"2129":1,"2131":2,"2143":1,"2228":1}}],["exercises",{"3":{"0":3,"136":1,"170":1,"185":1,"558":1,"573":1,"635":1,"638":1,"641":1,"698":1,"716":1,"758":1,"848":1,"941":1,"954":1,"1020":1,"1270":1,"1271":1,"1286":3,"1300":1,"1309":1,"1311":2,"1323":1,"1324":1,"1339":1,"1359":6,"1369":1,"1395":1,"1396":3,"1402":1,"1427":2,"1436":25,"1438":2,"1446":1,"1456":1,"1487":1,"1490":2,"1526":1,"1535":1,"1585":1,"1600":1,"1611":1,"1663":1,"1673":1,"1702":1,"1706":1,"1929":1,"1952":1,"1960":1,"2053":1,"2073":1,"2097":1,"2113":1,"2228":1,"2232":1}}],["exercise",{"0":{"1800":1},"3":{"0":2,"62":1,"390":1,"650":1,"725":1,"776":1,"838":1,"1154":1,"1259":1,"1270":2,"1286":4,"1311":2,"1324":5,"1339":4,"1359":2,"1380":1,"1395":2,"1396":1,"1402":1,"1417":1,"1427":2,"1429":1,"1430":1,"1435":1,"1436":8,"1455":2,"1488":2,"1491":1,"1494":1,"1526":1,"1653":1,"1698":1,"1891":1,"1923":1,"2055":1,"2129":1}}],["exemplary",{"3":{"1526":3,"1537":1,"1576":1,"1577":2,"1590":1,"1591":2,"1797":1,"1799":1,"2059":1}}],["exemplar",{"0":{"2131":1},"3":{"0":1,"448":1,"449":2,"1212":1,"1323":1,"1343":1,"2131":2,"2132":1}}],["exemptprefixes",{"3":{"1324":1}}],["exemptpartitionkey",{"3":{"1324":1}}],["exemptpathprefixes",{"2":{"189":1},"3":{"189":1,"1300":3}}],["exemptablefixtureservice",{"3":{"1199":2,"1207":1,"1436":3}}],["exempting",{"3":{"1135":1,"1300":1,"1324":1,"1339":1,"1436":8,"2002":1}}],["exemption",{"3":{"0":5,"103":2,"135":6,"136":1,"139":1,"142":1,"173":4,"175":1,"179":2,"180":1,"181":1,"279":1,"480":1,"482":1,"486":1,"487":2,"488":3,"489":3,"490":3,"491":3,"492":3,"493":3,"494":1,"495":1,"507":1,"545":1,"747":1,"749":1,"753":1,"789":1,"792":1,"825":1,"826":1,"827":1,"828":1,"830":1,"832":4,"834":1,"1004":1,"1006":4,"1122":1,"1124":1,"1126":1,"1127":1,"1133":2,"1134":1,"1135":1,"1161":3,"1162":1,"1212":2,"1286":1,"1300":3,"1309":1,"1311":8,"1324":8,"1337":1,"1339":7,"1416":2,"1436":41,"1438":1,"1447":3,"1467":3,"1489":1,"1496":1,"1799":1,"1825":1,"1996":1,"1997":1,"1998":1,"2001":1,"2002":4,"2043":1}}],["exemptions",{"3":{"0":2,"90":1,"103":1,"135":1,"142":1,"545":1,"547":1,"1005":1,"1124":1,"1161":1,"1311":1,"1324":2,"1339":1,"1416":1,"1436":10,"1526":1}}],["exemptfoldersuffixes",{"2":{"1004":1,"2043":1},"3":{"1004":2,"1431":1,"1436":4,"2043":1}}],["exemptmethods",{"3":{"135":1,"139":1,"1436":2}}],["exempt",{"3":{"0":3,"174":1,"178":1,"179":3,"180":1,"189":1,"227":1,"244":1,"280":1,"484":1,"486":1,"487":2,"488":4,"489":5,"490":8,"491":7,"492":8,"493":3,"494":3,"495":2,"552":1,"575":1,"749":1,"792":1,"827":1,"830":1,"831":1,"832":1,"881":1,"1006":1,"1124":3,"1126":1,"1186":1,"1297":2,"1300":6,"1311":3,"1324":4,"1337":1,"1339":5,"1344":1,"1416":2,"1431":1,"1436":18,"1447":2,"1465":1,"1467":1,"1577":1,"1582":1,"1583":1,"1600":1,"1637":1,"1666":1,"1670":1,"1698":1,"1999":2,"2002":3,"2114":1,"2168":1}}],["exemptedoffenderfixture",{"3":{"1199":2,"1207":1,"1436":5}}],["exempted",{"3":{"0":1,"135":1,"136":1,"139":1,"179":1,"265":1,"635":1,"749":1,"1168":1,"1212":1,"1286":1,"1311":1,"1324":1,"1431":1,"1436":10,"1447":1,"1467":1,"1771":1,"2042":1}}],["exemptseverytypeunderit",{"3":{"1436":1}}],["exemptsonlythatmember",{"3":{"1436":2}}],["exempts",{"0":{"2000":1},"3":{"0":4,"175":2,"182":1,"239":1,"243":1,"287":1,"484":1,"545":2,"566":1,"792":1,"826":1,"827":1,"829":1,"832":1,"833":2,"881":1,"884":1,"1135":1,"1161":3,"1163":2,"1300":1,"1311":2,"1337":1,"1339":4,"1380":2,"1436":9,"1455":1,"1465":1,"1591":1,"1998":1,"2000":2,"2002":1,"2024":2,"2232":2}}],["extreme",{"3":{"1359":1,"1417":1,"1436":1}}],["extraforbiddendomaindependencies",{"2":{"2104":1},"3":{"1436":2,"1489":1,"2104":1}}],["extrastatefixtureid",{"3":{"1199":2,"1207":1,"1436":9}}],["extras",{"3":{"598":1,"607":2,"752":1,"1034":1,"1309":1,"1324":9,"1359":1,"1395":1,"1436":2,"1441":1,"1666":1}}],["extra",{"3":{"0":1,"13":1,"24":1,"92":1,"94":1,"95":2,"102":1,"265":1,"275":1,"330":1,"351":1,"383":1,"401":1,"405":1,"426":1,"435":1,"454":2,"471":1,"633":1,"640":1,"649":1,"665":1,"683":1,"690":1,"749":1,"768":1,"782":1,"784":1,"827":2,"871":1,"924":1,"996":1,"1027":1,"1050":1,"1186":1,"1227":1,"1228":1,"1229":3,"1247":7,"1252":1,"1254":1,"1259":8,"1265":1,"1270":9,"1276":1,"1284":1,"1286":13,"1300":7,"1309":1,"1310":1,"1311":8,"1320":1,"1323":5,"1324":8,"1326":1,"1337":1,"1338":1,"1339":6,"1345":1,"1350":3,"1359":15,"1369":1,"1374":1,"1380":1,"1385":1,"1395":12,"1396":3,"1402":2,"1415":4,"1417":3,"1436":25,"1438":1,"1441":2,"1447":1,"1467":1,"1475":1,"1476":1,"1496":1,"1627":1,"1648":1,"1682":1,"1693":1,"1702":1,"1727":1,"1755":1,"1783":1,"1808":1,"1826":1,"1839":1,"1847":1,"1851":1,"1867":1,"1898":1,"1901":1,"1908":1,"1926":1,"1949":1,"1970":1,"2107":1,"2130":1,"2150":1,"2169":1,"2193":1}}],["extracttwitterhandle",{"3":{"1359":2}}],["extractability",{"3":{"1312":1,"1436":1}}],["extractable",{"0":{"1509":1},"1":{"1878":1,"1879":1,"1880":1,"1881":1,"1882":1,"1883":1,"1884":1,"1885":1,"1886":1},"3":{"135":1,"692":1,"1192":3,"1202":1,"1203":1,"1212":1,"1257":1,"1269":1,"1350":1,"1359":3,"1369":1,"1396":2,"1415":2,"1416":1,"1436":2,"1496":2,"1526":1,"1544":2,"1576":1,"1577":1,"1590":1,"1591":1,"1662":1,"1686":1,"1878":1,"2207":1,"2214":1,"2230":1}}],["extractname",{"3":{"1311":1}}],["extractclaims",{"3":{"1311":2}}],["extractors",{"3":{"974":1}}],["extractor",{"3":{"973":1,"1496":5,"1498":1}}],["extractgridfilters",{"3":{"881":1,"1324":1}}],["extractsociallinks",{"3":{"1359":1}}],["extractsortparameters",{"3":{"881":1,"1324":1}}],["extracts",{"3":{"350":1,"1289":1,"1311":2,"1324":2,"1339":1,"1396":1,"1436":1,"1453":1,"1672":1,"1780":1,"2072":1,"2155":1,"2204":1,"2214":1,"2221":1}}],["extracting",{"1":{"2015":1,"2016":1,"2017":1,"2018":1,"2019":1,"2020":1,"2021":1,"2022":1,"2023":1,"2024":1,"2025":1,"2026":1,"2027":1,"2028":1},"3":{"59":1,"734":1,"1109":1,"1247":1,"1259":1,"1270":3,"1286":3,"1300":1,"1311":1,"1324":1,"1339":3,"1350":1,"1359":6,"1395":7,"1396":1,"1416":1,"1436":1,"1544":1,"1646":1,"1779":1,"2014":1,"2015":2,"2072":1,"2209":1}}],["extractions",{"3":{"1496":1}}],["extraction",{"0":{"1307":1,"1790":1,"1866":1,"1884":1,"2009":1},"1":{"56":1,"57":1,"58":1,"59":1,"60":1,"61":1,"62":1,"63":1,"64":1},"3":{"0":5,"31":1,"35":1,"56":1,"57":1,"59":1,"60":1,"62":3,"64":1,"86":1,"88":2,"126":1,"229":1,"325":1,"326":1,"463":1,"545":1,"548":1,"567":1,"582":3,"584":2,"587":1,"640":1,"645":1,"690":1,"707":1,"733":1,"750":1,"753":1,"773":1,"783":1,"786":1,"827":1,"831":1,"846":1,"882":2,"883":1,"935":1,"954":1,"1025":1,"1034":1,"1035":1,"1212":4,"1213":1,"1214":1,"1259":1,"1270":1,"1286":6,"1287":1,"1288":1,"1300":4,"1301":2,"1303":1,"1309":5,"1311":4,"1312":7,"1313":1,"1314":1,"1320":1,"1323":9,"1324":3,"1349":1,"1350":5,"1359":3,"1380":11,"1383":1,"1395":3,"1396":9,"1415":6,"1416":2,"1436":23,"1446":1,"1447":3,"1450":1,"1453":1,"1489":1,"1496":6,"1526":2,"1530":1,"1531":2,"1532":1,"1535":1,"1576":4,"1577":5,"1586":1,"1602":1,"1638":2,"1646":1,"1648":1,"1652":2,"1654":1,"1656":1,"1657":1,"1663":4,"1684":1,"1691":1,"1697":1,"1717":1,"1724":1,"1781":1,"1783":2,"1787":1,"1788":3,"1790":1,"1793":4,"1815":1,"1842":1,"1848":1,"1853":1,"1863":1,"1866":2,"1882":1,"1886":3,"1892":1,"1895":1,"1899":1,"1926":1,"2009":1,"2016":1,"2020":1,"2028":3,"2029":1,"2030":1,"2043":1,"2054":1,"2074":2,"2080":1,"2094":1,"2111":1,"2113":1,"2114":1,"2209":1,"2227":1,"2234":1}}],["extractdeployedregion",{"2":{"972":1,"973":1,"2043":1},"3":{"0":1,"972":2,"973":1,"1436":3,"1489":1,"2043":1}}],["extract",{"0":{"1656":1,"2209":1},"3":{"0":2,"58":1,"59":1,"62":2,"243":1,"396":1,"743":1,"745":1,"1211":1,"1286":1,"1300":2,"1309":1,"1311":3,"1380":1,"1396":1,"1534":1,"1646":2,"1647":2,"1650":1,"1656":1,"1659":1,"1663":1,"1692":1,"1703":1,"1718":1,"1724":1,"1779":1,"1842":1,"1864":1,"1884":1,"1893":1,"1930":1,"2016":1,"2169":1,"2180":1,"2193":1,"2203":1,"2212":1,"2215":1,"2226":1,"2227":1}}],["extracted",{"3":{"0":6,"20":1,"30":2,"31":2,"32":1,"46":1,"57":1,"59":2,"62":2,"63":1,"95":1,"97":1,"191":1,"323":1,"349":1,"398":1,"447":1,"448":2,"458":1,"459":1,"461":1,"564":1,"572":1,"574":1,"578":1,"582":3,"583":1,"584":2,"586":1,"639":1,"640":2,"649":1,"707":1,"708":1,"725":1,"726":1,"733":1,"749":2,"750":1,"882":1,"918":1,"933":1,"972":2,"998":1,"1192":2,"1213":2,"1229":1,"1248":1,"1252":1,"1259":7,"1270":2,"1273":1,"1286":10,"1287":1,"1297":1,"1300":10,"1301":1,"1302":1,"1307":2,"1309":7,"1310":2,"1311":16,"1312":7,"1320":1,"1323":10,"1324":2,"1339":2,"1350":3,"1359":7,"1370":2,"1380":3,"1385":1,"1391":2,"1395":9,"1396":10,"1402":1,"1404":2,"1409":1,"1413":1,"1415":11,"1431":1,"1436":18,"1438":1,"1440":1,"1444":1,"1455":1,"1489":2,"1495":1,"1496":6,"1508":1,"1576":1,"1577":1,"1600":1,"1651":1,"1656":1,"1663":2,"1763":1,"1780":1,"1781":1,"1783":1,"1790":4,"1805":1,"1838":1,"1848":1,"1868":1,"1882":1,"1884":1,"1888":1,"1892":1,"1896":1,"1897":1,"1899":2,"1916":1,"1925":1,"1927":1,"1929":1,"1948":1,"2005":1,"2018":1,"2019":1,"2020":1,"2021":2,"2022":1,"2055":1,"2066":1,"2111":1,"2131":1,"2135":1,"2155":1,"2230":1,"2232":1}}],["extern",{"3":{"1611":1}}],["externalauthbroker",{"3":{"1417":1}}],["externalauthextensionstests",{"3":{"1199":1,"1207":2}}],["externalauthextensions",{"2":{"1415":1},"3":{"350":2,"980":1,"1199":5,"1203":1,"1207":2,"1208":1,"1300":1,"1311":10,"1324":1,"1415":3,"1496":1}}],["externalizing",{"3":{"1300":1}}],["externalized",{"3":{"265":1,"1311":2,"1324":2,"1394":1,"1395":4,"1396":1,"1416":1,"1549":1,"1554":1,"1564":1,"1653":1,"2083":1}}],["externalize",{"3":{"86":1,"1653":1}}],["externalcontractfixtureservice",{"3":{"1199":1,"1207":1,"1436":2}}],["externalsessionid",{"3":{"1286":1}}],["externals",{"3":{"1193":1,"1229":7,"1237":2,"1259":3,"1286":6,"1300":3,"1301":2,"1309":13,"1311":3,"1323":20,"1324":48,"1339":7,"1359":3,"1380":9,"1395":39,"1396":105,"1402":7,"1415":26,"1416":1,"1417":12,"1436":25}}],["externalprovideralreadylinked",{"2":{"350":1,"352":1,"1977":1},"3":{"350":1,"352":1,"1415":1,"1977":1}}],["externalemailnotverified",{"2":{"350":1,"352":1,"353":1,"354":1,"1406":1,"1977":1},"3":{"0":1,"350":1,"352":2,"353":1,"354":1,"1406":1,"1415":2,"1977":1}}],["externalemailinvalid",{"2":{"350":1,"354":1,"1977":1},"3":{"0":1,"350":1,"354":1,"1415":1,"1977":1}}],["externallinkservice",{"3":{"1417":2}}],["externallinktests",{"3":{"1199":1,"1207":3}}],["externallink",{"2":{"2121":1},"3":{"0":1,"413":1,"1324":1,"1417":7,"1599":2,"2121":1}}],["externallinkrequireslocalsignin",{"2":{"350":1,"353":1,"354":1,"1406":1,"1977":1},"3":{"0":1,"350":1,"353":1,"354":1,"1406":1,"1415":1,"1977":1}}],["externalloginemailverifier",{"3":{"1415":1}}],["externalloginscheme",{"2":{"1415":1},"3":{"1311":2,"1415":1}}],["externallogincoreasync",{"2":{"354":1},"3":{"350":1,"354":1,"1415":1}}],["externalloginnotsupported",{"2":{"350":1,"352":1,"1978":1},"3":{"350":1,"352":1,"1300":1,"1978":1}}],["externalloginasync",{"2":{"350":1,"351":1,"353":1,"354":1,"1975":1,"1977":1,"1978":1,"1979":1},"3":{"350":5,"351":1,"353":1,"354":1,"1289":1,"1300":5,"1311":3,"1406":2,"1415":2,"1975":1,"1977":2,"1978":2,"1979":1}}],["externallogin",{"2":{"352":1,"1975":1,"1979":1},"3":{"0":1,"350":2,"352":1,"1311":2,"1406":1,"1415":4,"1667":1,"1975":2,"1979":1}}],["externallyfixedmethods",{"3":{"1436":2}}],["externally",{"3":{"0":4,"350":1,"831":1,"860":1,"1123":1,"1259":7,"1270":1,"1286":1,"1311":1,"1323":2,"1324":5,"1344":1,"1350":7,"1359":3,"1380":1,"1395":3,"1415":2,"1416":1,"1427":2,"1436":2,"1441":1,"1467":2,"1631":1,"1636":2,"1640":1,"1755":1,"2000":1,"2169":1}}],["external",{"0":{"1213":2,"1636":1,"1752":1,"1770":1,"1975":1},"1":{"347":1,"348":1,"349":1,"350":1,"351":1,"352":1,"353":1,"354":1,"355":1,"1973":1,"1974":1,"1975":1,"1976":1,"1977":1,"1978":1,"1979":1},"2":{"601":1,"1477":1,"1975":1},"3":{"0":3,"150":1,"219":1,"284":1,"291":1,"347":1,"348":2,"349":1,"350":15,"351":3,"352":1,"353":4,"355":2,"415":1,"419":1,"440":1,"459":1,"497":1,"499":2,"502":2,"503":1,"519":1,"535":1,"536":1,"537":1,"538":1,"599":1,"601":1,"657":2,"660":1,"706":1,"792":1,"809":1,"810":2,"812":1,"840":1,"862":1,"926":1,"946":1,"948":2,"950":1,"988":1,"996":1,"1004":2,"1073":1,"1074":1,"1090":2,"1188":1,"1191":2,"1192":3,"1202":1,"1203":1,"1210":2,"1212":1,"1237":3,"1258":1,"1259":5,"1270":1,"1271":3,"1286":4,"1289":1,"1298":1,"1300":6,"1301":1,"1309":2,"1311":9,"1323":6,"1324":11,"1339":2,"1340":1,"1342":1,"1344":1,"1347":2,"1350":7,"1352":1,"1355":1,"1358":1,"1359":47,"1369":30,"1380":2,"1391":2,"1395":9,"1396":2,"1402":1,"1405":1,"1406":2,"1412":1,"1415":30,"1416":2,"1417":21,"1418":1,"1427":5,"1436":2,"1438":4,"1454":1,"1455":2,"1467":9,"1477":2,"1487":1,"1496":3,"1534":1,"1546":1,"1553":1,"1599":2,"1629":1,"1633":1,"1639":1,"1640":1,"1641":2,"1667":3,"1669":3,"1737":1,"1749":1,"1779":1,"1902":1,"1972":1,"1973":2,"1974":2,"1975":5,"1977":6,"1978":1,"1979":5,"1983":2,"2000":1,"2067":1,"2121":1,"2122":1,"2126":1,"2161":1,"2168":2,"2169":1,"2171":1,"2208":1,"2232":1}}],["extexp0001",{"3":{"1591":1}}],["extensibility",{"2":{"960":1,"1067":1},"3":{"846":1,"850":1,"960":1,"980":1,"1067":1,"1176":1,"1178":1,"1207":1,"1208":1,"1247":6,"1270":1,"1280":1,"1316":1,"1317":1,"1323":7,"1339":1,"1415":2}}],["extensible",{"3":{"235":1,"920":1,"1270":2,"1311":1,"1350":1,"1359":1,"1369":1,"1395":1,"1666":1}}],["extensiont",{"3":{"1427":1}}],["extensionof",{"3":{"1395":2}}],["extensionmarkerattribute",{"2":{"984":1},"3":{"980":1,"984":1}}],["extensions",{"0":{"1444":1},"2":{"63":1,"171":2,"253":1,"395":1,"409":1,"451":1,"452":1,"665":1,"676":1,"732":1,"733":1,"833":1,"917":1,"1090":1,"1091":1,"1213":2,"1247":1,"1259":1,"1270":1,"1301":2,"1311":2,"1312":3,"1323":2,"1324":1,"1329":2,"1339":4,"1369":1,"1417":1,"1427":6,"1611":1,"1915":1,"1916":1,"2012":1,"2177":1,"2178":1,"2180":1},"3":{"0":3,"61":1,"63":1,"72":2,"166":2,"171":3,"235":3,"237":1,"238":2,"253":1,"255":1,"256":1,"258":2,"259":1,"261":1,"395":1,"397":25,"399":3,"400":3,"401":3,"402":11,"404":4,"407":6,"408":5,"409":3,"451":1,"452":1,"640":3,"665":2,"673":1,"674":1,"676":1,"732":1,"733":1,"819":2,"821":1,"822":1,"827":1,"833":2,"883":2,"884":1,"897":1,"899":1,"900":1,"913":2,"917":1,"920":1,"926":1,"980":6,"982":1,"1006":1,"1018":1,"1034":3,"1037":1,"1066":1,"1090":1,"1091":4,"1103":1,"1199":13,"1202":1,"1203":10,"1207":42,"1208":12,"1213":4,"1229":1,"1236":2,"1237":7,"1247":4,"1259":4,"1270":16,"1271":8,"1278":1,"1286":18,"1296":2,"1300":8,"1301":6,"1309":12,"1311":13,"1312":3,"1323":26,"1324":39,"1325":1,"1326":7,"1327":5,"1329":5,"1332":11,"1333":8,"1334":4,"1337":1,"1339":108,"1350":3,"1359":22,"1369":5,"1380":3,"1382":1,"1395":7,"1396":15,"1415":16,"1416":1,"1417":5,"1422":1,"1425":2,"1427":26,"1436":17,"1437":2,"1438":4,"1441":25,"1442":1,"1443":47,"1444":27,"1448":1,"1450":3,"1467":1,"1487":1,"1496":12,"1535":1,"1575":1,"1577":7,"1583":1,"1586":1,"1591":1,"1611":1,"1656":1,"1662":1,"1666":1,"1670":1,"1671":1,"1707":1,"1783":1,"1827":1,"1915":1,"1916":1,"2012":1,"2156":17,"2157":1,"2159":3,"2177":1,"2178":1,"2180":1}}],["extension",{"0":{"1208":1,"2175":1},"1":{"843":1,"844":1,"845":1,"846":1,"847":1,"848":1,"849":1,"850":1,"977":1,"978":1,"979":1,"980":1,"981":1,"982":1,"983":1,"984":1},"2":{"180":1,"388":1,"980":1,"982":1,"983":1,"1204":1,"1327":1,"1354":1,"1418":1,"1831":1},"3":{"0":20,"39":2,"41":1,"48":1,"68":1,"71":1,"76":1,"80":1,"95":1,"99":1,"109":3,"110":1,"133":1,"164":2,"165":1,"167":1,"180":1,"200":1,"214":1,"215":1,"243":1,"258":3,"265":1,"266":1,"295":1,"300":1,"301":1,"306":1,"341":2,"342":1,"358":1,"388":1,"411":1,"413":1,"415":1,"434":2,"438":2,"443":2,"511":1,"549":3,"574":1,"614":1,"649":2,"724":2,"725":1,"728":1,"749":1,"751":2,"766":1,"826":1,"827":2,"833":1,"843":1,"844":1,"845":1,"847":1,"859":2,"861":2,"889":1,"914":1,"922":1,"926":1,"972":1,"976":1,"977":1,"979":4,"980":20,"981":3,"982":1,"983":3,"984":2,"1004":1,"1033":1,"1034":1,"1059":2,"1067":1,"1089":3,"1116":3,"1117":1,"1119":1,"1124":2,"1125":1,"1126":2,"1204":1,"1211":1,"1212":10,"1214":7,"1224":1,"1225":1,"1229":7,"1236":2,"1237":12,"1239":1,"1243":1,"1244":1,"1245":1,"1247":10,"1258":1,"1259":7,"1262":2,"1265":2,"1270":19,"1271":15,"1280":1,"1281":2,"1282":1,"1285":1,"1286":64,"1297":1,"1300":20,"1301":3,"1304":1,"1309":15,"1310":1,"1311":54,"1312":12,"1313":1,"1314":1,"1316":2,"1323":19,"1324":57,"1326":1,"1327":1,"1334":1,"1336":1,"1339":62,"1340":1,"1347":3,"1350":6,"1354":1,"1359":18,"1368":1,"1369":4,"1377":1,"1378":1,"1379":3,"1380":9,"1382":1,"1389":2,"1392":2,"1395":27,"1396":42,"1398":1,"1400":2,"1402":5,"1415":15,"1416":3,"1417":20,"1418":1,"1420":1,"1421":2,"1423":4,"1424":1,"1427":15,"1429":1,"1430":2,"1431":1,"1432":1,"1433":2,"1435":2,"1436":59,"1438":3,"1441":7,"1442":2,"1444":1,"1456":2,"1487":1,"1488":9,"1496":3,"1500":1,"1526":1,"1575":2,"1577":5,"1582":2,"1585":3,"1586":1,"1599":1,"1619":1,"1631":2,"1634":2,"1640":1,"1648":1,"1651":2,"1658":1,"1666":1,"1693":1,"1735":1,"1769":1,"1784":1,"1799":1,"1801":1,"1810":1,"1815":1,"1823":2,"1825":2,"1831":1,"1851":1,"1860":3,"1861":1,"1868":1,"1871":1,"1874":1,"1880":1,"1891":1,"1892":2,"1903":1,"1919":1,"1926":1,"1995":1,"2022":1,"2042":1,"2063":1,"2075":1,"2077":1,"2088":1,"2103":1,"2113":1,"2122":1,"2125":1,"2126":1,"2128":1,"2148":1,"2149":1,"2152":1,"2171":1,"2172":1,"2180":2,"2203":1,"2220":1,"2227":1,"2232":3}}],["extending",{"3":{"890":1,"1040":1,"1043":1,"1286":1,"1324":1,"1339":1,"1348":1,"1349":1,"1350":1,"1395":3,"1402":3,"1467":1,"2192":1,"2232":1}}],["extend",{"3":{"0":3,"175":1,"189":1,"237":1,"572":1,"658":1,"694":1,"706":1,"749":1,"854":1,"926":1,"1229":1,"1247":2,"1259":1,"1280":1,"1286":2,"1294":1,"1300":4,"1310":1,"1311":5,"1323":2,"1324":1,"1359":2,"1395":4,"1396":3,"1402":1,"1413":1,"1436":3,"1455":1,"1488":2,"1571":1,"1595":1,"1596":1,"1597":1,"1600":1,"1641":1,"1685":1,"2043":1,"2194":1,"2232":1}}],["extends",{"3":{"0":5,"66":1,"295":1,"376":1,"532":1,"549":2,"600":1,"660":1,"686":1,"745":1,"823":1,"825":2,"852":1,"914":1,"920":1,"937":1,"976":1,"981":1,"998":1,"1032":1,"1057":1,"1089":1,"1096":1,"1114":1,"1176":1,"1182":1,"1192":1,"1212":3,"1227":1,"1237":3,"1249":1,"1259":3,"1270":1,"1271":2,"1281":1,"1286":11,"1291":2,"1300":3,"1311":7,"1322":1,"1324":3,"1339":6,"1350":1,"1359":4,"1380":2,"1395":25,"1396":4,"1406":1,"1410":1,"1412":1,"1415":4,"1417":1,"1418":1,"1431":1,"1436":3,"1443":1,"1467":2,"1488":3,"1489":2,"1566":1,"1576":1,"1591":1,"1765":1,"1805":1,"1853":1,"1905":1,"2038":1,"2049":1,"2232":7}}],["extended",{"3":{"0":3,"15":1,"328":1,"674":1,"690":1,"701":1,"834":1,"889":1,"920":1,"1016":1,"1160":1,"1169":1,"1217":1,"1229":1,"1271":2,"1286":4,"1300":1,"1311":4,"1323":1,"1324":2,"1339":2,"1359":2,"1396":1,"1415":1,"1417":4,"1436":5,"1438":1,"1496":2,"1538":1,"1855":1,"1940":1,"2012":1,"2232":1}}],["expr",{"3":{"1247":1}}],["expressiveness",{"3":{"1359":3}}],["expressible",{"3":{"699":1,"823":1,"1229":1,"1270":1,"1286":1,"1309":2,"1311":1,"1359":5,"1402":1,"1415":2,"1416":2,"1417":1}}],["expressing",{"3":{"57":1,"691":1,"926":1,"1041":1,"1275":1,"1309":1,"1311":1,"1323":1,"1324":3,"1350":2,"1359":4,"1395":1,"1402":1,"1415":1,"1844":1,"2183":1}}],["expressionboundliteraltrue",{"3":{"1436":1}}],["expression<",{"3":{"1359":5}}],["expressionsortabletemplatecolumngrid",{"3":{"1436":2}}],["expressions",{"3":{"359":1,"365":1,"434":1,"545":1,"1020":1,"1091":1,"1227":1,"1229":1,"1237":1,"1242":1,"1247":15,"1259":1,"1270":1,"1271":3,"1286":10,"1310":3,"1323":5,"1324":3,"1339":3,"1353":1,"1359":21,"1395":1,"1396":6,"1415":1,"1436":3,"2142":1}}],["expressionvisitor",{"2":{"1817":1,"1859":1},"3":{"0":1,"545":1,"549":1,"1239":1,"1247":2,"1431":1,"1436":2,"1815":1,"1817":1,"1859":1}}],["expression",{"2":{"547":1,"1238":1,"1242":1,"1817":1,"1859":2},"3":{"0":3,"6":1,"318":2,"319":1,"330":1,"332":1,"523":1,"543":2,"545":7,"546":2,"547":3,"548":1,"549":5,"556":1,"633":1,"651":1,"655":1,"657":1,"660":1,"676":1,"697":1,"698":2,"699":1,"707":4,"709":1,"726":1,"1050":1,"1091":1,"1213":1,"1214":1,"1219":1,"1229":3,"1231":1,"1237":7,"1238":1,"1239":9,"1242":3,"1247":79,"1252":1,"1259":6,"1268":1,"1269":2,"1270":9,"1271":21,"1273":1,"1286":51,"1300":16,"1301":3,"1305":1,"1309":13,"1310":17,"1311":8,"1312":1,"1323":26,"1324":21,"1339":12,"1350":14,"1353":2,"1359":139,"1369":3,"1372":1,"1380":6,"1395":16,"1396":25,"1402":10,"1415":18,"1416":3,"1417":6,"1426":1,"1427":1,"1431":1,"1436":38,"1438":1,"1453":1,"1458":1,"1465":1,"1467":1,"1475":2,"1490":1,"1577":1,"1591":1,"1599":1,"1687":1,"1689":1,"1727":2,"1815":8,"1817":4,"1829":4,"1859":3,"1865":2,"1920":1,"1948":2,"1952":1,"1953":1,"1955":1,"1988":3,"1989":1,"1990":1,"1991":1,"1994":1,"2089":1,"2176":1,"2196":1}}],["expressed",{"3":{"0":1,"59":1,"136":1,"243":1,"319":1,"464":1,"509":1,"530":1,"729":1,"781":1,"784":1,"836":1,"837":1,"838":2,"842":1,"1091":1,"1237":3,"1247":1,"1255":1,"1259":1,"1263":1,"1270":3,"1271":2,"1273":1,"1278":1,"1283":1,"1286":6,"1300":6,"1309":2,"1311":2,"1323":3,"1324":3,"1339":7,"1343":1,"1350":8,"1353":1,"1359":29,"1369":1,"1380":1,"1395":6,"1396":13,"1402":2,"1415":6,"1417":2,"1436":6,"1441":1,"1455":1,"1460":1,"1465":1,"1467":1,"1491":1,"1526":1,"1656":1,"1727":1,"1833":1}}],["expresses",{"3":{"0":1,"691":1,"708":2,"783":1,"809":1,"810":1,"954":1,"1231":1,"1237":2,"1247":1,"1271":1,"1286":2,"1300":2,"1301":1,"1309":1,"1338":1,"1359":8,"1380":3,"1395":2,"1396":1,"1417":1,"1436":1,"1455":1,"1540":1,"2042":1}}],["express",{"3":{"0":2,"187":1,"189":1,"203":1,"317":1,"523":1,"547":1,"608":1,"610":1,"675":1,"733":1,"743":1,"810":1,"819":2,"820":1,"855":1,"861":1,"862":1,"889":1,"926":1,"1102":1,"1115":1,"1116":1,"1134":1,"1229":1,"1237":1,"1247":2,"1259":1,"1270":3,"1271":1,"1276":1,"1286":8,"1300":1,"1301":1,"1311":1,"1323":6,"1324":3,"1339":2,"1342":1,"1348":1,"1350":3,"1352":1,"1353":1,"1357":1,"1359":17,"1361":1,"1362":1,"1369":1,"1373":2,"1380":3,"1395":14,"1396":3,"1402":2,"1416":1,"1431":1,"1436":6,"1441":1,"1453":3,"1467":1,"1474":1,"1490":2,"1537":1,"1816":1,"1856":1,"1898":1,"1960":1,"1965":1,"1992":1,"1997":1,"2147":1}}],["exp",{"2":{"500":1,"1980":1},"3":{"499":1,"500":1,"1300":6,"1577":1,"1980":1,"1982":1}}],["expansion",{"3":{"86":1,"265":1,"1259":2,"1286":7,"1324":1,"1436":6,"1438":2,"1454":1,"1526":3,"1534":1,"1564":1,"1577":1,"1639":1}}],["expanding",{"3":{"1277":1,"1286":1,"1475":1}}],["expandoobject",{"3":{"1242":1,"1244":1,"1247":6}}],["expandrelational",{"2":{"698":1,"703":1},"3":{"698":1,"703":1,"1286":1}}],["expands",{"3":{"442":1,"674":1,"869":1,"1259":1,"1286":2,"1300":1,"1311":1,"1436":3,"1454":1,"1847":1}}],["expandedsessions",{"3":{"1395":1}}],["expanded",{"3":{"27":1,"135":1,"468":2,"476":1,"1243":1,"1247":1,"1256":1,"1276":1,"1286":1,"1310":1,"1339":1,"1395":1,"1436":4,"1577":1,"1988":1}}],["expand",{"1":{"561":1,"562":1,"563":1,"564":1,"565":1,"566":1,"567":1,"568":1},"3":{"0":6,"15":1,"26":2,"41":1,"293":2,"296":1,"561":1,"564":6,"566":2,"756":2,"759":1,"761":1,"891":1,"892":1,"893":2,"907":2,"932":1,"1012":1,"1018":1,"1212":2,"1275":1,"1276":2,"1286":11,"1311":3,"1395":3,"1436":1,"1455":6,"1459":1,"1461":2,"1526":2,"1545":2,"1577":2,"1591":2,"1600":1,"1664":1,"1728":1,"2232":2}}],["expert",{"3":{"2224":1,"2240":1}}],["expertise",{"3":{"1369":1,"2218":1}}],["experiment",{"3":{"1300":2}}],["experiences",{"3":{"860":1,"1417":1}}],["experience",{"0":{"1570":1,"2220":1},"2":{"1327":1},"3":{"820":1,"1051":1,"1216":1,"1259":1,"1271":1,"1286":2,"1311":1,"1323":5,"1324":5,"1327":1,"1339":4,"1350":1,"1354":1,"1359":1,"1396":4,"1402":1,"1415":1,"1416":2,"1417":5,"1436":12,"1440":1,"1450":1,"1456":1,"1461":1,"1464":2,"1486":1,"1488":1,"1492":1,"1493":1,"1499":1,"1526":1,"1535":1,"1554":1,"1577":1,"1581":1,"1582":1,"1583":1,"1591":1,"1597":1,"1605":1,"1739":1,"1750":2,"1796":1,"1956":1,"2131":1,"2220":1,"2240":1}}],["experienced",{"3":{"513":1,"1190":1,"2095":1}}],["expecting",{"3":{"1270":1,"1415":1,"1438":1}}],["expects",{"3":{"185":1,"564":1,"634":1,"743":1,"827":1,"831":1,"965":1,"1259":1,"1270":2,"1271":1,"1286":3,"1300":1,"1309":1,"1311":2,"1323":2,"1324":7,"1350":1,"1359":1,"1395":2,"1396":1,"1417":1,"1436":6,"1467":1,"1639":1,"1887":1,"1888":1,"1892":1,"2167":1}}],["expect",{"3":{"77":1,"139":1,"812":1,"1036":1,"1214":1,"1237":1,"1270":1,"1286":1,"1309":1,"1311":1,"1359":1,"1395":1,"1406":1,"1577":1,"1582":1,"1685":1,"1686":1,"1719":1,"1852":1,"1887":1,"2067":1,"2106":1,"2160":1}}],["expectations",{"3":{"689":1,"1270":1,"1300":1,"1301":1,"1436":5,"1455":1,"1547":1,"2033":1}}],["expectation",{"3":{"23":1,"572":1,"585":1,"838":2,"1090":1,"1286":1,"1300":1,"1309":1,"1311":1,"1323":1,"1359":1,"1395":1,"1436":6,"1438":1,"1492":1,"1591":1,"1596":1,"1899":1}}],["expecteddisabledstubs",{"3":{"1436":1}}],["expecteddependencies",{"3":{"1436":5,"1438":1}}],["expectedrequiresdependencies",{"3":{"1436":5,"1438":1}}],["expectedname",{"3":{"1436":10,"1438":1}}],["expectedsecret",{"3":{"1339":1}}],["expectedstepnames",{"3":{"459":1,"464":1,"1311":1,"1436":3,"1488":1}}],["expectedcontract",{"2":{"635":1,"1729":1,"2107":1},"3":{"633":1,"635":1,"1436":6,"1654":1,"1727":1,"1729":1,"2107":1}}],["expectedcommanddecorators",{"2":{"121":1},"3":{"1436":1}}],["expectedquerydecorators",{"2":{"121":1},"3":{"1436":1}}],["expected",{"3":{"0":3,"68":1,"108":2,"109":2,"110":1,"111":1,"126":1,"127":1,"130":1,"132":1,"137":1,"140":1,"141":1,"142":1,"243":1,"386":1,"472":1,"530":1,"565":1,"635":1,"757":1,"765":1,"790":1,"972":1,"973":1,"996":1,"1018":1,"1020":1,"1049":1,"1163":1,"1212":3,"1215":1,"1217":2,"1218":1,"1229":4,"1259":1,"1261":1,"1262":1,"1268":1,"1270":5,"1273":1,"1275":1,"1286":4,"1287":1,"1288":1,"1300":3,"1311":4,"1312":1,"1318":1,"1323":10,"1324":7,"1339":1,"1350":3,"1359":3,"1380":1,"1395":13,"1396":8,"1402":3,"1413":1,"1415":3,"1417":3,"1436":27,"1449":1,"1453":3,"1455":8,"1459":1,"1460":1,"1461":1,"1465":2,"1486":2,"1488":3,"1489":1,"1490":1,"1492":4,"1524":5,"1525":5,"1526":1,"1575":5,"1576":5,"1577":2,"1586":1,"1589":5,"1638":1,"1720":1,"1804":2,"1806":1,"1807":3,"1820":1,"1826":1,"1877":1,"1887":1,"1897":2,"1923":1,"1927":1,"1930":1,"1945":1,"1954":2,"1959":1,"2058":1,"2104":1,"2105":1,"2107":1,"2129":1,"2156":1,"2191":1,"2227":1,"2230":1,"2232":2}}],["expensive",{"0":{"2190":1},"3":{"0":4,"81":1,"108":1,"111":1,"176":1,"234":1,"311":1,"474":1,"516":1,"518":3,"519":1,"520":1,"619":1,"627":1,"683":1,"735":1,"776":1,"800":1,"811":2,"830":1,"921":1,"953":2,"965":1,"1049":1,"1051":2,"1066":1,"1067":1,"1099":3,"1100":1,"1104":1,"1123":1,"1218":1,"1229":2,"1237":1,"1247":2,"1270":4,"1286":1,"1310":1,"1324":1,"1328":1,"1339":2,"1359":5,"1395":3,"1396":2,"1417":1,"1436":5,"1441":1,"1455":2,"1475":1,"1490":1,"1555":1,"1684":1,"1804":1,"1812":1,"1840":1,"1854":1,"1862":1,"1901":1,"1902":1,"1905":1,"1907":1,"1958":1,"1998":1,"1999":1,"2000":1,"2002":1,"2155":2,"2158":1,"2160":2,"2174":1,"2190":1}}],["expiries",{"3":{"1270":1,"1323":1}}],["expiring",{"3":{"534":1,"539":1,"1292":1,"1324":1,"1396":2,"1436":1,"1438":1}}],["expiryrule",{"3":{"1436":6}}],["expiryskew",{"3":{"1324":7,"1417":2}}],["expiryinpast",{"3":{"1300":1}}],["expiry",{"3":{"0":7,"150":1,"159":1,"201":1,"207":2,"209":1,"243":1,"255":1,"302":1,"305":2,"372":1,"458":1,"474":1,"497":2,"498":1,"499":6,"500":1,"505":1,"506":1,"507":2,"509":1,"513":1,"514":1,"529":1,"534":2,"536":3,"537":2,"538":1,"539":3,"627":1,"628":1,"708":1,"853":1,"854":1,"855":1,"905":2,"906":1,"998":1,"1160":1,"1162":1,"1212":3,"1263":1,"1267":1,"1270":1,"1288":1,"1298":2,"1300":31,"1301":3,"1311":2,"1323":5,"1324":14,"1332":1,"1396":5,"1415":3,"1417":2,"1436":12,"1438":2,"1630":1,"1635":1,"1639":2,"1641":4,"1764":1,"1765":2,"1767":3,"1922":1,"1968":3,"1971":1,"1980":1,"1981":1,"1982":3,"1984":1,"1985":1,"2002":1,"2066":2,"2069":1,"2096":1,"2138":2,"2167":3,"2193":1}}],["expiration",{"3":{"0":1,"243":3,"259":1,"388":3,"389":1,"733":4,"1300":1,"1301":24,"1311":7,"1668":1,"1708":1,"1922":2,"1923":1}}],["expireunpaidorderinternalcommand",{"2":{"539":1,"813":1,"814":1,"2189":1},"3":{"539":2,"813":1,"814":1,"2189":1}}],["expireunpaidorderinternalcommandhandler",{"2":{"539":1,"674":1},"3":{"539":2,"674":2,"813":1}}],["expireunpaidorder",{"2":{"534":1,"794":1,"2167":1},"3":{"0":1,"534":1,"794":1,"2167":1}}],["expirestrandedunpaidordersasync",{"2":{"2167":1},"3":{"2167":1}}],["expiresin",{"3":{"1641":2}}],["expiresatunixseconds",{"3":{"1300":5}}],["expiresat",{"3":{"905":3,"1300":3,"1339":2,"1436":1}}],["expires",{"3":{"0":2,"19":1,"20":1,"27":1,"392":1,"458":2,"459":2,"460":1,"461":1,"498":1,"536":1,"707":1,"788":1,"793":2,"794":1,"881":1,"905":1,"907":1,"1028":1,"1029":1,"1044":1,"1074":1,"1183":1,"1255":1,"1259":6,"1270":3,"1275":1,"1294":1,"1300":8,"1301":3,"1311":6,"1318":1,"1323":4,"1324":5,"1339":1,"1359":1,"1395":2,"1396":4,"1417":2,"1429":1,"1436":1,"1438":1,"1467":1,"1639":1,"1641":5,"1841":1,"1909":1,"1911":1,"1917":1,"1918":1,"1922":1,"1947":1,"1981":1,"2066":1,"2096":1,"2167":1,"2169":1,"2189":1,"2192":1}}],["expiredflag",{"3":{"1436":2}}],["expired",{"2":{"790":1,"1770":1},"3":{"0":2,"350":1,"499":2,"500":1,"513":1,"536":3,"608":1,"612":1,"768":1,"790":1,"813":1,"853":2,"854":1,"855":1,"856":1,"904":1,"905":5,"996":1,"997":1,"1026":1,"1042":1,"1212":1,"1255":1,"1263":1,"1270":5,"1286":2,"1287":1,"1288":1,"1289":1,"1290":3,"1294":1,"1298":1,"1300":43,"1301":2,"1311":1,"1323":7,"1324":16,"1358":1,"1359":3,"1380":1,"1396":1,"1408":1,"1415":3,"1431":1,"1436":4,"1467":1,"1641":5,"1708":2,"1747":1,"1765":3,"1770":1,"1897":1,"1916":1,"1934":1,"1948":1,"1968":2,"1972":1,"1975":1,"1982":5,"1985":4,"2158":2,"2163":1,"2167":2,"2188":1}}],["expire",{"3":{"0":1,"536":1,"540":1,"793":1,"1270":1,"1283":1,"1286":1,"1300":2,"1301":1,"1311":2,"1324":3,"1395":1,"1415":1,"1436":1,"1438":1,"1631":1,"1635":1,"1767":3,"2197":1}}],["expo",{"3":{"1340":1,"1342":1,"1350":3,"1359":3,"1395":1}}],["exponent",{"3":{"279":1,"1259":1,"1275":1,"1286":1,"1292":1}}],["exponentially",{"3":{"1841":1}}],["exponential",{"3":{"0":4,"15":1,"19":1,"23":1,"72":1,"73":1,"150":1,"175":1,"281":1,"282":1,"285":1,"640":1,"818":1,"827":1,"881":1,"1041":1,"1212":1,"1256":1,"1259":2,"1275":1,"1292":1,"1300":2,"1323":4,"1324":3,"1330":1,"1339":2,"1395":1,"1396":2,"1415":1,"1526":1,"1576":1,"1577":1,"1641":1,"1665":2,"1667":1,"1707":1,"1842":1,"1903":1,"1910":1,"2000":1,"2001":1,"2002":2,"2074":1,"2183":1,"2232":1}}],["exposing",{"3":{"41":1,"60":1,"188":1,"206":1,"225":1,"300":1,"413":1,"939":1,"1237":2,"1239":2,"1270":4,"1286":5,"1300":2,"1309":3,"1311":5,"1312":1,"1323":3,"1324":7,"1339":1,"1350":6,"1359":7,"1368":1,"1369":2,"1380":1,"1382":1,"1395":3,"1396":3,"1402":1,"1415":4,"1417":3,"1431":1,"1436":6,"1488":2,"1651":1,"1815":1,"1831":1,"1962":1,"1964":1,"2023":1}}],["exposure",{"3":{"0":1,"177":1,"459":2,"461":1,"465":1,"481":1,"498":1,"501":2,"520":1,"691":1,"735":1,"792":1,"799":2,"801":1,"802":1,"981":1,"1019":1,"1135":1,"1237":1,"1247":2,"1270":2,"1286":1,"1300":4,"1307":1,"1311":2,"1323":1,"1324":2,"1359":7,"1396":3,"1424":1,"1436":3,"1477":1,"1492":1,"1524":1,"1532":1,"1534":1,"1641":1,"1923":1,"1980":1}}],["expose",{"0":{"1291":1},"3":{"76":1,"141":1,"397":1,"507":1,"545":1,"940":1,"1168":2,"1237":1,"1247":2,"1270":1,"1280":1,"1300":3,"1310":1,"1311":8,"1312":1,"1323":2,"1324":2,"1333":1,"1339":3,"1350":1,"1359":2,"1372":1,"1380":1,"1395":3,"1396":4,"1436":12,"1442":1,"1445":2,"1488":1,"1600":1,"1667":1,"1877":1,"1928":1,"1991":1,"2193":1,"2239":1}}],["exposes",{"3":{"0":4,"24":1,"31":1,"68":1,"78":1,"81":1,"102":1,"160":1,"174":1,"243":1,"329":1,"330":1,"341":1,"459":1,"469":1,"507":1,"545":1,"547":2,"549":1,"578":1,"599":1,"618":1,"649":1,"657":1,"698":1,"725":1,"727":1,"840":1,"854":1,"869":1,"905":1,"924":1,"959":1,"988":1,"996":1,"1018":1,"1042":1,"1059":1,"1060":1,"1067":1,"1083":1,"1123":1,"1161":1,"1168":1,"1175":2,"1212":1,"1237":4,"1239":1,"1241":2,"1244":1,"1247":6,"1257":1,"1259":1,"1264":5,"1267":1,"1270":5,"1273":1,"1285":1,"1286":13,"1287":1,"1290":1,"1293":1,"1296":1,"1300":14,"1301":5,"1303":1,"1306":1,"1309":2,"1310":3,"1311":10,"1316":2,"1318":1,"1320":1,"1323":5,"1324":11,"1332":1,"1336":1,"1337":1,"1339":2,"1341":1,"1342":1,"1350":8,"1359":12,"1366":1,"1369":5,"1371":2,"1378":2,"1379":1,"1380":6,"1383":1,"1385":1,"1395":14,"1396":17,"1400":1,"1402":2,"1408":1,"1415":12,"1416":3,"1417":8,"1420":2,"1421":1,"1427":2,"1429":2,"1431":2,"1432":1,"1435":1,"1436":15,"1441":1,"1444":1,"1453":1,"1455":1,"1465":1,"1467":1,"1488":2,"1526":1,"1577":2,"1607":1,"1630":3,"1651":1,"1662":1,"1664":2,"1667":1,"1715":1,"1741":1,"1755":1,"1776":2,"1782":1,"1790":1,"1794":1,"1810":1,"1811":1,"1815":2,"1816":1,"1871":1,"1875":1,"1889":1,"1918":1,"1932":1,"1982":1,"1988":1,"2000":1,"2007":1,"2014":1,"2021":1,"2057":1,"2092":1,"2104":1,"2122":1,"2126":1,"2156":1,"2185":1}}],["exposed",{"3":{"0":2,"53":1,"92":1,"93":1,"94":1,"100":1,"130":1,"156":1,"160":1,"207":1,"243":1,"292":1,"305":1,"599":1,"743":1,"751":1,"881":1,"883":1,"924":1,"1050":1,"1100":2,"1228":1,"1237":3,"1239":1,"1240":1,"1247":2,"1259":1,"1262":1,"1270":4,"1286":3,"1290":1,"1297":1,"1300":9,"1310":1,"1311":4,"1312":4,"1323":6,"1324":4,"1330":1,"1339":4,"1343":1,"1347":1,"1350":11,"1354":1,"1359":12,"1369":2,"1380":2,"1391":1,"1395":2,"1396":4,"1398":1,"1402":3,"1407":2,"1415":1,"1416":2,"1432":1,"1436":11,"1441":1,"1467":1,"1475":1,"1548":1,"1550":1,"1591":1,"1630":2,"1632":2,"1639":3,"1641":1,"1647":1,"1670":1,"1755":1,"1765":1,"1776":1,"1790":1,"1937":1,"2075":1,"2138":1}}],["exportforbidden",{"3":{"1323":2,"1415":1}}],["exportfilenameprefix",{"3":{"741":1,"1311":1}}],["exportparameters",{"3":{"1300":1}}],["exporting",{"3":{"1264":1,"1270":1,"1300":1,"1309":1,"1359":1,"1415":3,"1416":1,"1436":1,"1455":1,"1467":2}}],["exportcalendarasync",{"3":{"1380":2}}],["exportcalendar",{"3":{"1203":5,"1207":16,"1300":6,"1350":1,"1356":3,"1359":15,"1438":1,"1496":1}}],["exportcontenttype",{"3":{"725":1,"1311":2}}],["exporttestcontroller",{"3":{"1199":2,"1207":1}}],["exporttestentity",{"3":{"1199":8,"1207":1}}],["exporttestdto",{"3":{"1199":6,"1207":1}}],["exportmoney",{"3":{"1199":3,"1207":1}}],["exportrowscoperequirederrorcode",{"3":{"1311":1}}],["exportrow",{"3":{"1199":2,"1207":1}}],["exportrowlimitheadername",{"2":{"741":1},"3":{"741":1,"1311":1}}],["exportability",{"3":{"744":1}}],["exportable",{"3":{"725":1,"743":1,"1311":1,"1356":1,"1359":5,"1487":1}}],["exportasync",{"2":{"318":1,"324":2,"743":1},"3":{"318":2,"324":2,"725":1,"741":3,"743":4,"1311":14,"1323":2,"1359":1,"1372":2,"1380":17,"1415":5,"1438":1,"1496":1,"1995":1}}],["exportuserdataregistrationtests",{"3":{"1199":1,"1207":2,"1415":2,"1438":2}}],["exportuserdatahandler",{"3":{"724":4,"1199":3,"1203":1,"1207":2,"1300":2,"1309":1,"1323":3,"1396":5,"1410":2,"1415":22,"1535":1,"1600":1}}],["exportuserdatahandlertests",{"2":{"724":1},"3":{"724":1,"1199":1,"1207":3,"1415":2,"1438":2}}],["exportuserdatahandlerbasetests",{"3":{"1199":1,"1207":7,"1323":4,"1438":2}}],["exportuserdatahandlerbase",{"2":{"723":1,"725":1,"1299":1,"1403":1,"1672":1,"2067":1},"3":{"0":1,"723":1,"725":3,"727":1,"728":1,"1199":4,"1203":1,"1207":2,"1212":1,"1270":2,"1299":1,"1300":11,"1322":5,"1323":18,"1403":1,"1410":1,"1415":11,"1438":1,"1496":2,"1672":1,"2067":1}}],["exportuserdataquery",{"2":{"723":1,"726":1},"3":{"726":1,"1199":6,"1203":1,"1207":2,"1311":1,"1323":3,"1410":2,"1415":8}}],["exportuserdata",{"2":{"1500":1},"3":{"585":1,"724":2,"725":4,"727":1,"728":1,"1203":8,"1207":26,"1300":4,"1309":1,"1322":2,"1323":17,"1396":2,"1410":3,"1415":21,"1438":1,"1500":1}}],["exporteventcalendarhandlertests",{"3":{"1199":1,"1207":2,"1359":2,"1438":1}}],["exporteventcalendarhandler",{"3":{"1199":2,"1203":1,"1207":2,"1300":5,"1356":6,"1359":6,"1438":1}}],["exporteventcalendarquery",{"3":{"1199":5,"1203":1,"1207":2,"1359":3,"1380":1,"1496":1}}],["exporter",{"2":{"397":1,"1333":1,"2010":1,"2156":1,"2159":1},"3":{"395":1,"397":2,"399":1,"741":4,"744":1,"1213":1,"1311":6,"1333":2,"1339":6,"1443":3,"1467":1,"2010":2,"2156":1,"2159":1,"2160":2}}],["exporters",{"3":{"0":1,"397":2,"399":1,"402":1,"409":1,"591":1,"608":1,"1333":4,"1339":6,"1443":5,"1492":1,"1577":3,"1923":1,"2010":2,"2014":1,"2047":1,"2157":4,"2159":1,"2160":1}}],["exported",{"0":{"1333":1},"3":{"0":3,"47":1,"396":1,"407":1,"428":1,"440":1,"556":1,"591":2,"764":1,"766":1,"899":1,"1089":1,"1213":1,"1311":3,"1323":1,"1324":1,"1339":5,"1350":2,"1359":13,"1396":6,"1402":1,"1404":1,"1410":1,"1415":6,"1416":3,"1417":2,"1435":1,"1436":3,"1441":1,"1456":1,"1467":1,"1473":1,"1475":1,"1492":2,"1631":1,"1670":1,"1765":1,"2047":1,"2215":1}}],["exportsectionunavailable",{"3":{"1323":2}}],["exportsessioncalendarhandlertests",{"3":{"1199":1,"1207":2,"1359":2}}],["exportsessioncalendarhandler",{"3":{"1199":2,"1203":1,"1207":2,"1300":5,"1356":4,"1359":4}}],["exportsessioncalendarquery",{"2":{"1375":1},"3":{"1199":4,"1203":1,"1207":2,"1359":3,"1375":1,"1380":3,"1496":1}}],["exportshapetestcontroller",{"3":{"1199":2,"1207":1}}],["exportshapetestdto",{"3":{"1199":3,"1207":1}}],["exports",{"3":{"0":2,"81":1,"324":2,"397":1,"400":1,"404":2,"408":1,"557":1,"583":1,"585":3,"591":1,"725":2,"728":1,"741":1,"743":1,"821":1,"1203":8,"1207":24,"1247":1,"1259":1,"1270":1,"1300":5,"1311":3,"1312":2,"1323":1,"1339":1,"1350":2,"1359":6,"1372":1,"1380":2,"1396":24,"1402":2,"1417":3,"1436":14,"1438":2,"1442":2,"1443":2,"1444":1,"1455":1,"1456":1,"1460":1,"1465":1,"1488":1,"1490":1,"1535":1,"1545":1,"1631":2,"1764":1,"1765":2,"1895":1,"2004":1}}],["export",{"0":{"1299":1,"1308":1,"1356":1,"1410":1},"1":{"722":1,"723":1,"724":1,"725":1,"726":1,"727":1,"728":1,"729":1,"738":1,"739":1,"740":1,"741":1,"742":1,"743":1,"744":1,"745":1,"1403":1,"1404":1,"1405":1,"1406":1,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1413":1,"1414":1,"1415":1},"2":{"690":1,"916":1,"1478":1,"1766":1},"3":{"0":14,"18":1,"39":1,"41":1,"95":3,"96":2,"104":2,"318":3,"321":2,"324":2,"325":1,"330":1,"397":1,"399":1,"408":1,"440":1,"545":1,"583":1,"585":3,"586":3,"650":2,"690":2,"692":4,"694":1,"696":2,"709":1,"722":1,"723":2,"724":6,"725":10,"727":12,"728":2,"729":1,"738":1,"740":3,"741":18,"742":4,"743":13,"744":2,"745":1,"896":2,"914":2,"916":2,"973":1,"976":1,"1018":1,"1020":2,"1026":2,"1028":1,"1044":1,"1192":4,"1201":1,"1202":2,"1203":4,"1207":10,"1212":6,"1229":1,"1256":1,"1259":1,"1270":1,"1286":1,"1299":2,"1300":21,"1307":1,"1308":1,"1309":49,"1311":59,"1312":3,"1317":2,"1322":2,"1323":32,"1324":4,"1328":1,"1333":1,"1339":8,"1350":3,"1351":1,"1356":3,"1359":18,"1371":2,"1372":7,"1373":1,"1380":76,"1396":58,"1402":2,"1403":3,"1404":2,"1405":1,"1410":2,"1415":67,"1416":2,"1417":6,"1436":9,"1437":2,"1438":17,"1441":2,"1443":7,"1446":1,"1448":1,"1450":2,"1455":2,"1467":7,"1469":1,"1470":1,"1476":1,"1478":4,"1482":2,"1484":1,"1487":4,"1496":7,"1526":3,"1534":2,"1535":1,"1545":2,"1567":1,"1575":1,"1577":3,"1582":3,"1586":1,"1590":1,"1591":3,"1631":5,"1639":1,"1660":1,"1662":1,"1666":7,"1667":1,"1668":1,"1670":2,"1672":1,"1677":1,"1720":1,"1746":2,"1747":2,"1749":1,"1751":4,"1764":1,"1765":4,"1766":1,"1772":4,"1841":1,"1898":1,"1988":5,"1991":1,"1995":10,"1997":1,"2014":1,"2067":5,"2068":2,"2126":1,"2128":1,"2156":1,"2159":1,"2160":2,"2171":1,"2192":1,"2232":4}}],["exploiting",{"3":{"2119":1}}],["exploits",{"3":{"1323":1,"1324":1,"1436":1,"1447":1}}],["explodes",{"3":{"1436":1}}],["exploding",{"3":{"1300":1}}],["explosion",{"3":{"1247":1,"1286":2,"1864":1}}],["exploring",{"3":{"2228":1}}],["explore",{"3":{"2212":1,"2244":1}}],["explorer",{"3":{"446":1,"448":4,"1050":1,"1311":4,"2131":1,"2132":1}}],["exploration",{"3":{"740":1}}],["explainable",{"3":{"1350":1,"1359":2,"1396":2}}],["explainswhatismissinganddoesnotcalltheservice",{"3":{"1436":1}}],["explains",{"3":{"1000":1,"1193":1,"1216":1,"1229":1,"1237":3,"1247":8,"1259":8,"1270":4,"1271":1,"1286":21,"1300":23,"1301":2,"1309":5,"1311":8,"1323":10,"1324":20,"1328":1,"1339":20,"1344":1,"1350":10,"1359":15,"1369":5,"1395":20,"1396":24,"1397":1,"1402":7,"1405":1,"1415":15,"1416":3,"1417":7,"1427":1,"1436":21,"1443":1,"1445":1,"1453":1,"1456":1,"1459":1,"1465":1,"1467":4,"1476":2,"1489":1,"1496":1,"1497":1,"1658":1,"1685":1,"1705":1,"1802":1,"2003":1,"2089":1,"2167":1,"2176":1,"2178":1,"2211":1}}],["explain",{"3":{"982":1,"990":1,"1237":1,"1247":5,"1259":1,"1270":2,"1271":1,"1286":6,"1300":5,"1301":1,"1311":2,"1312":2,"1313":1,"1323":4,"1324":1,"1339":1,"1350":5,"1359":4,"1380":1,"1395":3,"1396":1,"1402":1,"1413":1,"1415":1,"1436":2,"1439":1,"1443":1,"1449":1,"1455":1,"1502":1,"1515":1,"1552":1,"1639":1,"2166":1}}],["explained",{"3":{"905":1,"972":2,"1191":1,"1194":1,"1216":2,"1237":1,"1300":1,"1309":1,"1311":1,"1324":1,"1339":1,"1359":5,"1369":1,"1380":1,"1395":2,"1416":1,"1417":1,"1436":1,"1441":1,"1493":1,"1494":1,"1495":1,"1496":1,"1499":4,"1800":1}}],["explaining",{"3":{"0":1,"365":1,"373":1,"424":1,"484":1,"626":1,"700":1,"750":1,"820":1,"1190":1,"1222":1,"1237":3,"1259":1,"1271":1,"1286":1,"1300":4,"1311":1,"1323":1,"1324":2,"1339":2,"1359":2,"1362":1,"1369":1,"1395":2,"1396":3,"1436":5,"1489":1,"2143":1,"2197":1}}],["explanation",{"3":{"642":1,"1133":1,"1161":1,"1232":1,"1300":1,"1339":1,"1369":1,"1395":1,"1396":4,"1417":1,"1427":1,"1436":1,"1467":1,"1803":1}}],["explanatory",{"3":{"93":1,"616":1,"856":1,"1052":1,"1259":1,"1270":1,"1286":1,"1323":1,"1359":2,"1417":2,"1627":1}}],["explicitcapture",{"3":{"1309":2,"1427":1,"1436":2}}],["explicitsource",{"3":{"1286":2}}],["explicitkeyinsertrequested",{"3":{"1286":1}}],["explicitkeyinsert",{"3":{"1286":8}}],["explicitkeyinsertgroup",{"2":{"1280":1},"3":{"1175":2,"1199":4,"1203":1,"1207":2,"1278":1,"1280":2,"1286":12,"1496":1}}],["explicitorderupdatecommandvalidator",{"3":{"1199":2,"1207":1}}],["explicitassemblyprovider",{"2":{"1284":1},"3":{"1199":2,"1203":1,"1207":1,"1284":1,"1286":4}}],["explicitly",{"2":{"1844":1},"3":{"0":8,"15":1,"24":1,"25":1,"31":1,"39":1,"54":1,"70":1,"71":1,"73":1,"93":1,"100":1,"109":1,"135":1,"175":1,"184":1,"186":1,"193":1,"195":1,"196":1,"243":1,"317":1,"348":1,"353":1,"415":1,"556":1,"564":1,"571":1,"590":1,"595":1,"625":1,"627":1,"630":1,"657":1,"690":1,"698":1,"733":1,"741":1,"758":1,"775":1,"782":1,"819":1,"823":1,"827":1,"881":1,"897":1,"899":1,"905":1,"922":1,"931":2,"953":1,"954":1,"955":1,"963":1,"973":1,"996":1,"1012":1,"1014":1,"1051":1,"1059":1,"1118":1,"1124":1,"1133":1,"1190":1,"1193":1,"1212":3,"1229":4,"1237":4,"1241":1,"1247":7,"1257":1,"1259":10,"1270":8,"1271":4,"1275":1,"1276":2,"1286":34,"1288":1,"1297":2,"1299":1,"1300":28,"1301":6,"1302":1,"1304":1,"1309":8,"1310":2,"1311":18,"1312":3,"1323":19,"1324":22,"1330":1,"1333":1,"1337":1,"1339":8,"1343":1,"1347":1,"1349":1,"1350":8,"1352":1,"1354":1,"1359":29,"1362":3,"1369":2,"1379":1,"1380":6,"1385":1,"1386":1,"1388":1,"1395":31,"1396":25,"1402":6,"1404":1,"1409":1,"1415":25,"1416":1,"1417":11,"1422":1,"1427":1,"1431":1,"1432":1,"1436":18,"1438":2,"1441":2,"1443":2,"1444":1,"1447":2,"1453":1,"1455":4,"1458":1,"1459":1,"1460":1,"1462":1,"1467":4,"1470":1,"1474":1,"1475":1,"1476":1,"1488":4,"1489":1,"1494":1,"1496":2,"1508":1,"1554":1,"1572":1,"1577":1,"1591":2,"1631":2,"1639":1,"1667":1,"1685":1,"1686":1,"1709":1,"1724":1,"1799":1,"1827":1,"1842":1,"1844":1,"1853":1,"1861":1,"1876":1,"1881":1,"1897":1,"1898":1,"1899":1,"1916":1,"1958":1,"1968":1,"1992":1,"1997":2,"2002":1,"2032":1,"2033":1,"2041":1,"2043":1,"2044":1,"2055":1,"2063":1,"2067":1,"2086":1,"2097":1,"2131":1,"2167":1,"2169":1,"2232":1}}],["explicit",{"0":{"1955":1},"1":{"1080":1,"1081":1,"1082":1,"1083":1,"1084":1,"1085":1,"1086":1,"1087":1,"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1956":1,"1957":1,"1958":1,"1959":1},"2":{"1083":1,"1084":1,"1583":1,"2064":1},"3":{"0":14,"5":1,"7":1,"15":1,"19":1,"22":2,"24":1,"31":1,"39":1,"68":1,"72":2,"73":1,"74":1,"78":1,"86":1,"95":3,"99":1,"108":1,"115":1,"118":1,"128":1,"173":1,"186":2,"189":1,"193":1,"195":1,"198":1,"200":1,"201":1,"215":1,"219":1,"243":1,"255":1,"265":4,"266":1,"274":1,"291":1,"312":1,"318":1,"320":1,"321":1,"353":1,"358":1,"368":1,"370":1,"373":1,"386":1,"390":2,"393":1,"397":1,"398":1,"402":1,"418":1,"420":1,"427":1,"428":1,"485":1,"499":1,"518":1,"537":1,"549":3,"556":1,"557":1,"558":1,"564":1,"583":1,"584":1,"618":1,"626":1,"628":1,"633":1,"640":1,"674":1,"709":1,"715":1,"742":1,"766":1,"774":1,"799":1,"817":1,"840":1,"847":2,"881":1,"905":2,"914":2,"926":2,"931":2,"932":1,"933":1,"939":1,"941":1,"942":1,"947":1,"965":1,"971":1,"980":1,"982":1,"988":1,"996":1,"998":1,"999":1,"1012":1,"1013":1,"1035":1,"1050":3,"1052":2,"1060":1,"1066":1,"1067":1,"1080":1,"1083":3,"1084":2,"1092":1,"1108":1,"1117":1,"1133":1,"1175":3,"1212":2,"1213":1,"1219":1,"1221":1,"1229":8,"1233":2,"1236":1,"1237":10,"1239":1,"1241":1,"1247":7,"1249":2,"1253":1,"1256":1,"1257":1,"1259":14,"1263":2,"1264":1,"1269":1,"1270":12,"1271":3,"1274":1,"1275":1,"1278":3,"1280":4,"1281":2,"1286":66,"1288":1,"1300":33,"1301":8,"1309":21,"1310":2,"1311":21,"1312":11,"1316":1,"1317":1,"1318":1,"1320":2,"1322":1,"1323":25,"1324":41,"1331":3,"1333":2,"1338":2,"1339":25,"1342":1,"1343":1,"1345":1,"1350":30,"1352":1,"1353":1,"1354":1,"1358":2,"1359":70,"1364":1,"1369":15,"1373":1,"1375":1,"1376":1,"1380":13,"1383":1,"1390":1,"1392":1,"1393":1,"1394":1,"1395":29,"1396":42,"1399":1,"1401":1,"1402":23,"1411":1,"1415":36,"1416":12,"1417":19,"1427":2,"1436":47,"1438":1,"1440":1,"1441":1,"1442":1,"1443":2,"1444":1,"1445":1,"1448":1,"1451":1,"1453":1,"1454":3,"1455":2,"1458":1,"1467":4,"1469":1,"1474":1,"1475":2,"1476":1,"1486":1,"1487":1,"1489":1,"1496":1,"1510":1,"1526":4,"1534":1,"1541":1,"1543":1,"1544":1,"1545":1,"1547":1,"1553":3,"1556":1,"1566":1,"1576":1,"1577":12,"1582":1,"1583":1,"1591":1,"1631":2,"1632":1,"1639":1,"1641":1,"1651":1,"1667":1,"1673":1,"1677":1,"1686":1,"1693":1,"1711":1,"1749":1,"1765":1,"1795":1,"1830":1,"1841":1,"1842":1,"1858":1,"1889":2,"1890":1,"1892":1,"1898":3,"1903":1,"1904":1,"1910":2,"1911":1,"1948":1,"1950":1,"1951":2,"1952":2,"1955":2,"1957":1,"1958":3,"1959":3,"1962":1,"1963":1,"1964":1,"1965":2,"1982":1,"1994":1,"1995":1,"1996":1,"1997":1,"2001":1,"2019":1,"2030":3,"2033":1,"2063":1,"2064":2,"2067":1,"2083":1,"2085":1,"2086":1,"2127":1,"2138":1,"2142":1,"2152":1,"2157":1,"2160":1,"2161":1,"2162":1,"2165":1,"2169":2,"2175":1,"2179":1,"2185":1,"2186":1,"2198":1,"2208":1,"2232":3}}],["sf1nopko",{"2":{"1364":1},"3":{"1364":1,"1369":1}}],["svg",{"3":{"1324":1,"2125":1}}],["svc",{"3":{"248":1,"1301":3}}],["sdlc",{"3":{"1395":1,"2239":1}}],["sd",{"3":{"1324":3,"2076":1}}],["sdks",{"3":{"74":1,"1076":1,"1213":1,"1417":2,"1436":1,"1577":1}}],["sdk",{"2":{"877":1,"1449":1,"1460":1},"3":{"0":4,"72":5,"73":6,"74":1,"374":1,"439":1,"868":2,"869":1,"870":1,"873":1,"877":1,"980":3,"982":3,"1016":1,"1018":1,"1067":3,"1075":1,"1076":1,"1091":5,"1092":1,"1095":1,"1212":1,"1270":1,"1286":1,"1323":3,"1339":3,"1351":1,"1359":1,"1416":9,"1417":3,"1420":2,"1427":10,"1430":1,"1431":1,"1436":13,"1438":1,"1441":1,"1443":1,"1445":3,"1449":1,"1450":1,"1455":4,"1460":1,"1467":2,"1479":2,"1488":1,"1490":1,"1526":2,"1553":2,"1577":1,"1582":1,"1591":1,"1648":1,"1682":1,"1693":1,"1718":2,"1719":1,"2004":1,"2043":1,"2088":1,"2104":2,"2124":1,"2126":1,"2170":2,"2171":1,"2172":1,"2178":1,"2180":4}}],["sb",{"3":{"1323":1,"1436":3}}],["sbom",{"1":{"367":1,"368":1,"369":1,"370":1,"371":1,"372":1,"373":1,"374":1,"375":1,"376":1},"3":{"0":2,"367":1,"368":1,"369":2,"370":5,"371":4,"372":2,"373":3,"374":3,"375":4,"413":1,"415":1,"530":1,"531":1,"532":1,"590":1,"861":1,"870":1,"874":1,"1212":1,"1445":1,"1454":11,"1461":2,"1526":5,"1535":1,"1569":2,"1576":2,"1577":6,"1585":1,"1586":1,"1591":2,"1600":1,"1673":1,"1738":1,"1801":1,"2120":1,"2122":1,"2232":1}}],["s6672",{"3":{"1311":2}}],["s6932",{"3":{"790":1,"1309":1}}],["s5693",{"3":{"1380":1,"1395":1}}],["s5332",{"3":{"1309":1,"1312":1,"1323":1,"1359":1}}],["s5122",{"3":{"0":1,"774":2,"776":1,"777":1,"1311":1,"1339":1}}],["s4",{"3":{"1457":2}}],["s4275",{"3":{"1395":1}}],["s4830",{"3":{"1286":1,"1416":1}}],["s4035",{"3":{"0":1,"964":1,"1231":1,"1235":1,"1237":2,"1300":2}}],["sr",{"3":{"1359":3,"1597":1,"1607":1,"1609":1,"1741":1,"1743":1}}],["srp",{"3":{"1270":1,"1271":1,"1431":1,"1436":1,"1526":1,"1538":2,"1577":2,"1582":1,"1584":1}}],["srcset",{"3":{"1599":1}}],["src",{"3":{"0":2,"1":1,"147":2,"212":7,"213":3,"214":15,"215":3,"216":3,"219":23,"220":2,"324":1,"358":1,"361":1,"362":1,"366":1,"490":1,"491":1,"492":1,"493":1,"495":1,"582":1,"590":2,"756":2,"765":1,"773":2,"860":1,"914":1,"1003":1,"1011":1,"1012":3,"1017":1,"1090":2,"1094":1,"1095":1,"1212":4,"1216":2,"1237":2,"1271":1,"1300":8,"1311":1,"1324":53,"1336":2,"1339":16,"1350":1,"1359":11,"1380":7,"1391":1,"1395":11,"1417":4,"1428":1,"1430":2,"1436":3,"1438":1,"1441":1,"1443":2,"1445":1,"1496":1,"1502":1,"1515":1,"1525":4,"1526":9,"1531":1,"1575":1,"1577":14,"1582":2,"1586":1,"1591":5,"1596":1,"1600":3,"1793":1,"2000":2,"2001":2,"2002":1,"2075":1,"2086":1,"2117":1,"2118":1,"2120":1,"2147":3,"2149":6,"2150":17,"2151":1,"2152":2,"2153":4,"2158":1,"2172":1}}],["s2696",{"3":{"1417":1}}],["s2092",{"3":{"1300":1,"1311":1}}],["s2077",{"3":{"0":1,"1133":1,"1286":1,"1436":1}}],["s2326",{"3":{"1270":2}}],["s2245",{"3":{"1259":1,"1324":1}}],["s2221",{"3":{"1247":1,"1311":1,"1427":1}}],["s8970",{"3":{"1577":2,"1586":1,"1591":1}}],["s8",{"3":{"1201":1}}],["s1481",{"3":{"1727":1}}],["s107",{"3":{"1577":1,"1581":1,"1582":1,"1584":1}}],["s1075",{"3":{"1369":1,"1395":3,"1416":1,"1417":3}}],["s1144",{"3":{"1237":1}}],["s1200",{"3":{"1577":1,"1581":1,"1582":1,"1584":1}}],["s12",{"3":{"766":1,"769":1}}],["s1",{"3":{"766":1,"767":1,"769":1,"1591":1}}],["s+",{"3":{"609":1,"1476":1}}],["ssas",{"3":{"2219":1,"2220":1}}],["ssis",{"3":{"2219":1,"2220":2}}],["ssh",{"3":{"1526":1,"1535":1}}],["ssl",{"3":{"1416":2,"1770":1}}],["ss",{"3":{"1359":13}}],["ss9",{"3":{"448":1}}],["ssrs",{"3":{"2219":1,"2220":1}}],["ssrf",{"3":{"1358":1,"1359":1,"1369":1}}],["ssr",{"0":{"1298":1,"1968":1},"1":{"204":1,"205":1,"206":1,"207":1,"208":1,"209":1,"210":1,"1966":1,"1967":1,"1968":1,"1969":1,"1970":1,"1971":1,"1972":1},"3":{"0":6,"204":1,"206":3,"207":3,"208":2,"209":3,"210":1,"264":1,"265":3,"266":1,"269":1,"272":2,"273":2,"274":1,"275":1,"277":2,"412":1,"458":1,"462":1,"486":1,"506":2,"507":2,"555":3,"556":3,"557":1,"560":2,"649":1,"664":1,"670":1,"682":1,"881":2,"1212":2,"1298":2,"1300":8,"1311":3,"1324":37,"1383":1,"1391":1,"1393":1,"1395":6,"1396":1,"1402":3,"1416":4,"1417":5,"1436":6,"1438":2,"1447":2,"1467":1,"1488":1,"1526":2,"1534":1,"1560":1,"1577":5,"1582":1,"1591":1,"1667":1,"1759":1,"1761":1,"1779":1,"1965":1,"1966":3,"1967":3,"1968":4,"1971":4,"1972":3,"2076":3,"2085":2,"2086":2,"2119":1,"2208":1,"2232":2}}],["snyk",{"3":{"2219":1,"2220":2}}],["snippet",{"3":{"1436":2,"2022":1}}],["snippets",{"3":{"955":1}}],["sniff",{"3":{"1286":1,"1369":1,"1415":4,"2128":1}}],["sniffed",{"3":{"1174":1,"1415":1}}],["sniffer",{"3":{"1114":1,"1116":2,"1117":3,"1118":2,"1285":1,"1286":2,"1347":1,"1359":2,"1380":1,"1438":1,"2232":1}}],["sniffs",{"3":{"1116":1,"1411":1,"1415":1}}],["sniffing",{"3":{"201":1,"1259":1,"1286":2,"1415":3,"1417":1,"1538":1,"1631":1,"1640":1,"2148":1}}],["snap",{"3":{"2082":1}}],["snappy",{"3":{"1324":1}}],["snapshotname",{"3":{"1436":2}}],["snapshotoutputvariable",{"3":{"1436":2}}],["snapshotted",{"3":{"692":1,"1286":3,"1300":5,"1324":1,"1339":1,"1369":2,"1390":1,"1396":1,"1398":1,"1402":7,"1429":1,"1436":1,"1438":1,"1526":1,"1630":2,"1631":1,"1930":1}}],["snapshotting",{"3":{"691":1,"1233":1,"1286":1,"1300":1,"1311":2,"1350":1,"1402":5,"1436":1,"1630":1,"1930":1}}],["snapshots=1",{"3":{"1432":1,"1436":3,"1438":1}}],["snapshots",{"1":{"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1},"3":{"21":1,"583":1,"739":1,"741":3,"743":1,"745":1,"988":1,"1023":1,"1144":1,"1178":1,"1209":1,"1212":1,"1251":2,"1259":1,"1274":1,"1286":4,"1311":2,"1323":4,"1324":6,"1350":2,"1359":1,"1395":4,"1396":1,"1402":3,"1417":5,"1427":1,"1432":1,"1436":5,"1438":3,"1577":1,"1632":1,"1660":1,"1671":1,"1686":1,"1687":1,"2055":1,"2060":1,"2199":1,"2232":1}}],["snapshot",{"2":{"633":1,"636":1},"3":{"0":3,"184":1,"186":1,"387":1,"484":1,"486":1,"487":1,"488":1,"489":1,"490":2,"491":2,"492":2,"493":2,"494":2,"536":1,"540":1,"556":1,"564":1,"572":2,"573":2,"585":1,"633":1,"636":1,"676":1,"690":1,"725":2,"727":1,"744":1,"782":2,"783":2,"801":1,"825":1,"891":1,"914":1,"990":2,"1034":1,"1050":1,"1059":1,"1060":1,"1142":1,"1199":3,"1203":1,"1207":1,"1247":1,"1273":2,"1275":2,"1279":1,"1283":3,"1286":40,"1297":1,"1299":1,"1300":16,"1301":3,"1311":3,"1322":1,"1323":7,"1324":17,"1339":3,"1347":1,"1349":1,"1350":12,"1355":1,"1356":1,"1359":9,"1375":1,"1380":4,"1391":1,"1395":21,"1396":42,"1398":1,"1402":8,"1410":2,"1415":5,"1416":3,"1417":6,"1431":1,"1432":1,"1436":61,"1438":10,"1455":2,"1488":2,"1496":2,"1497":1,"1500":1,"1524":1,"1525":3,"1526":4,"1531":3,"1535":2,"1577":4,"1586":1,"1631":1,"1640":1,"1643":1,"1663":1,"1671":1,"1675":1,"1686":8,"1697":1,"1727":1,"1749":4,"1765":4,"1767":1,"1769":2,"1801":1,"1929":3,"1930":1,"1933":1,"2044":3,"2049":1,"2055":1,"2059":2,"2062":1,"2069":1,"2079":2,"2199":1,"2200":1}}],["snackbars",{"3":{"1324":1,"1526":3,"1591":1,"1607":1,"1644":1,"1669":1,"1741":1,"1742":1}}],["snackbaroptions",{"3":{"1324":2}}],["snackbar",{"2":{"1687":1},"3":{"109":1,"265":3,"276":1,"881":1,"905":1,"954":1,"1324":17,"1394":1,"1395":29,"1396":5,"1401":1,"1402":1,"1417":2,"1432":1,"1436":13,"1488":2,"1526":4,"1577":1,"1591":1,"1653":3,"1669":1,"1671":1,"1686":1,"1687":1,"2072":1,"2079":1}}],["snake",{"2":{"1035":1},"3":{"0":2,"1034":1,"1035":1,"1273":1,"1286":1,"1311":2,"1856":1}}],["s3330",{"3":{"1311":1}}],["s3875",{"3":{"1237":1}}],["s3011",{"3":{"1286":1}}],["s30",{"2":{"1201":1},"3":{"1201":1}}],["s3",{"3":{"0":1,"764":1,"769":1,"1591":1}}],["s0",{"3":{"0":2,"764":4,"766":5,"767":3,"769":3,"1478":4,"1483":1,"1591":1,"1600":1}}],["skills",{"3":{"2239":2}}],["skim",{"3":{"1194":1,"1248":1,"1418":1,"1441":1,"1787":1,"1812":1,"2115":1}}],["skims",{"3":{"1132":1,"1436":1}}],["skipmigration",{"3":{"1684":1,"1688":1,"1722":1,"1729":2}}],["skiperror",{"3":{"1286":1}}],["skipwhenunavailable",{"2":{"1328":1,"1449":1},"3":{"1328":1,"1339":3,"1449":1}}],["skipwhen",{"2":{"1070":2},"3":{"1067":2,"1070":2,"1328":2,"1339":2,"1430":2,"1449":2}}],["skipreason",{"2":{"1070":1,"1328":1},"3":{"1067":4,"1070":2,"1328":2,"1339":3,"1430":3,"1449":2,"2056":1}}],["skipbearer",{"2":{"514":1},"3":{"507":1,"514":1,"1324":10}}],["skippable",{"3":{"764":3,"1455":1,"1596":1,"1599":1,"1892":1}}],["skipping",{"3":{"0":1,"208":1,"397":1,"557":1,"733":1,"759":1,"887":1,"889":1,"890":2,"1067":1,"1068":1,"1117":1,"1133":1,"1168":1,"1223":1,"1231":1,"1237":2,"1241":1,"1247":1,"1271":1,"1286":12,"1300":2,"1301":2,"1311":6,"1323":1,"1324":2,"1333":1,"1339":2,"1349":1,"1350":3,"1359":9,"1395":5,"1396":1,"1402":1,"1415":3,"1417":5,"1433":1,"1436":10,"1441":1,"1443":1,"1455":1,"1467":2,"1470":1,"1489":1,"1921":1,"1985":1,"2010":1,"2076":1,"2156":1,"2160":1,"2168":1,"2232":1}}],["skippedsoftdeleted",{"2":{"1632":1},"3":{"1350":2,"1359":4,"1632":1,"1639":1}}],["skipped",{"2":{"620":1,"624":1,"863":1},"3":{"0":6,"39":1,"120":1,"135":1,"157":1,"168":1,"201":2,"214":1,"292":1,"318":1,"335":1,"341":1,"370":1,"397":1,"534":1,"536":1,"564":1,"583":1,"593":1,"620":1,"624":1,"626":5,"627":1,"628":1,"649":1,"707":1,"709":1,"717":1,"749":1,"758":1,"766":4,"849":1,"861":4,"863":2,"889":1,"891":1,"990":1,"997":1,"1004":1,"1006":1,"1092":1,"1220":1,"1229":1,"1241":2,"1242":1,"1247":4,"1259":8,"1266":1,"1269":1,"1270":6,"1276":2,"1278":2,"1279":1,"1286":10,"1288":1,"1289":1,"1300":1,"1301":3,"1311":4,"1315":1,"1323":11,"1324":4,"1334":1,"1339":1,"1347":1,"1350":3,"1359":21,"1369":1,"1372":1,"1380":2,"1395":3,"1396":7,"1402":3,"1415":4,"1425":1,"1431":2,"1436":23,"1438":1,"1443":1,"1445":3,"1449":1,"1453":3,"1455":11,"1456":2,"1459":1,"1473":1,"1475":1,"1488":1,"1489":1,"1490":2,"1492":8,"1526":5,"1531":1,"1534":1,"1551":1,"1577":2,"1585":1,"1591":6,"1596":2,"1599":1,"1631":1,"1632":2,"1636":1,"1639":2,"1641":1,"1654":1,"1729":1,"1748":1,"1765":1,"1805":1,"1822":1,"1838":1,"1858":1,"1909":1,"1910":1,"1995":1,"2043":1,"2156":1,"2167":1,"2176":1,"2180":1}}],["skipsabstractcontrollers",{"3":{"1436":1}}],["skipsave",{"3":{"0":1,"926":3,"1265":1,"1270":7,"1359":4,"1415":2,"1666":1}}],["skipscoredsinceutc",{"3":{"1359":4}}],["skipsspeakersmissingfromrepository",{"3":{"1359":1}}],["skips",{"0":{"1844":1},"3":{"0":4,"22":1,"27":1,"117":1,"135":1,"166":1,"194":1,"195":2,"243":1,"244":1,"248":1,"255":1,"259":1,"296":1,"330":1,"337":1,"451":1,"452":1,"499":1,"528":1,"536":1,"538":3,"547":1,"556":1,"557":1,"559":1,"564":1,"573":1,"583":1,"599":1,"626":1,"628":1,"657":2,"715":1,"757":1,"790":2,"795":1,"889":1,"907":1,"914":1,"931":1,"980":1,"988":1,"1018":1,"1020":1,"1107":1,"1133":1,"1241":2,"1242":1,"1245":1,"1247":8,"1252":1,"1255":1,"1259":4,"1270":2,"1271":3,"1281":2,"1286":16,"1300":4,"1301":1,"1309":2,"1311":6,"1320":1,"1323":3,"1324":5,"1328":1,"1337":1,"1339":2,"1350":4,"1358":1,"1359":19,"1369":3,"1379":1,"1380":2,"1391":1,"1393":1,"1395":5,"1396":9,"1402":5,"1414":1,"1415":5,"1417":5,"1435":1,"1436":28,"1438":6,"1445":1,"1449":1,"1454":1,"1455":5,"1456":1,"1458":1,"1459":1,"1460":1,"1467":2,"1472":1,"1486":2,"1488":1,"1490":2,"1492":1,"1496":1,"1534":1,"1596":1,"1599":2,"1632":1,"1639":1,"1651":1,"1668":1,"1677":1,"1729":1,"1765":1,"1805":1,"1816":1,"1897":1,"1918":1,"1920":1,"1922":1,"1923":1,"1947":1,"1982":1,"1988":1,"2012":1,"2057":1,"2096":2,"2105":1,"2164":1,"2165":1,"2166":2,"2168":1}}],["skip",{"0":{"2025":1},"2":{"626":2,"642":1,"768":2,"1475":2,"1534":1,"1599":1},"3":{"0":8,"20":1,"24":1,"32":1,"103":2,"179":1,"195":1,"200":1,"201":2,"210":1,"276":1,"318":1,"335":1,"341":1,"355":1,"388":1,"453":1,"459":1,"528":1,"529":1,"538":1,"549":3,"554":1,"556":1,"559":1,"567":1,"586":1,"591":3,"620":1,"626":8,"627":2,"642":1,"708":1,"741":1,"743":1,"768":2,"863":1,"869":1,"916":1,"918":1,"924":1,"1005":1,"1006":1,"1020":2,"1067":6,"1070":1,"1084":1,"1184":1,"1212":2,"1229":2,"1240":1,"1242":1,"1243":2,"1247":19,"1259":6,"1270":3,"1271":1,"1280":1,"1286":19,"1291":1,"1300":6,"1301":2,"1309":6,"1310":2,"1323":5,"1324":8,"1333":1,"1339":17,"1350":2,"1353":1,"1359":14,"1391":1,"1395":8,"1396":13,"1400":1,"1401":1,"1402":3,"1415":10,"1417":3,"1426":1,"1430":4,"1436":12,"1438":3,"1443":2,"1445":1,"1449":1,"1453":1,"1454":1,"1455":13,"1456":2,"1475":5,"1486":1,"1490":1,"1492":4,"1496":2,"1500":1,"1525":1,"1526":4,"1534":1,"1577":1,"1589":1,"1591":2,"1596":1,"1598":1,"1599":2,"1600":1,"1607":2,"1611":2,"1613":1,"1639":1,"1644":2,"1651":1,"1664":1,"1684":1,"1686":2,"1720":1,"1727":1,"1741":2,"1808":1,"1815":1,"1841":1,"1845":1,"1858":1,"1860":1,"1865":1,"1871":1,"1882":1,"1891":1,"1911":1,"1918":1,"1921":1,"1922":1,"1950":1,"1984":1,"2010":1,"2029":1,"2056":1,"2068":1,"2087":1,"2101":1,"2107":1,"2111":1,"2121":1,"2126":1,"2131":1,"2145":1,"2155":1,"2157":1,"2171":1,"2180":1,"2190":1}}],["skuexistsasync",{"3":{"1751":1}}],["skus",{"3":{"1750":1,"1767":2,"1773":1}}],["skuid",{"3":{"1198":2,"1199":10,"1207":2}}],["sku",{"2":{"767":1,"769":3,"1465":1},"3":{"583":1,"766":5,"767":1,"769":3,"889":1,"897":1,"1436":5,"1465":2,"1467":1,"1473":1,"1478":6,"1599":1,"1746":2,"1748":4,"1750":2,"1751":2,"1764":2,"1767":2,"1768":1,"1769":3,"1772":2,"1773":1}}],["sketch",{"0":{"2084":1}}],["sketched",{"3":{"264":1,"963":1,"1079":1}}],["skeletons",{"3":{"1560":1}}],["skeleton",{"3":{"980":1,"1343":1,"1359":1,"1395":2,"1396":1,"1402":1,"1436":2}}],["skew",{"3":{"0":3,"146":1,"148":1,"178":1,"507":1,"513":1,"1059":1,"1256":1,"1259":1,"1293":2,"1298":1,"1300":2,"1311":1,"1324":9,"1417":1,"1436":3,"1467":1,"1648":1,"1655":1,"1727":1,"2198":2}}],["squash",{"3":{"1453":1}}],["squatting",{"3":{"1286":1}}],["squarely",{"3":{"1285":1,"1359":1,"2059":1}}],["square",{"3":{"0":1,"440":1,"1286":2,"1323":3,"1395":1,"1496":1,"2126":1}}],["sqs",{"3":{"0":1,"1075":1}}],["sqldatabase",{"2":{"1473":1,"1478":1},"3":{"1473":1,"1478":1}}],["sqllocation",{"3":{"1466":2,"1467":2,"1474":2}}],["sqlaadadminobjectid",{"3":{"1467":2}}],["sqlaadadminlogin",{"3":{"1467":1}}],["sqladminpassword",{"2":{"1466":1},"3":{"1466":2,"1467":1,"1526":1,"1534":1}}],["sqlauditingsettings",{"2":{"719":1},"3":{"719":1}}],["sqlauditconventiontests",{"2":{"719":1},"3":{"0":1,"719":2,"1199":1,"1207":2,"1436":4,"1467":2,"1591":1}}],["sql=true",{"3":{"1455":1,"1467":1,"1477":1,"1591":1,"1596":1,"1600":1}}],["sqlbaseenvironmentvariable",{"2":{"1429":1},"3":{"1429":1,"1436":2,"1488":1,"1490":1}}],["sqlthingconfiguration",{"3":{"1199":1,"1207":1}}],["sqlthing",{"3":{"1199":2,"1207":1,"1438":1}}],["sqlthingshipped",{"3":{"1199":2,"1207":1}}],["sqlqueryable",{"3":{"1286":2}}],["sqlquery",{"2":{"1132":1,"1133":1},"3":{"1132":1,"1133":1,"1286":2,"1436":1}}],["sqlqueryraw",{"2":{"1132":1},"3":{"0":1,"1132":1,"1133":1,"1431":1,"1436":3}}],["sqlsecurityauditevents",{"2":{"719":1},"3":{"719":2,"1467":2}}],["sqlserverfqdn",{"3":{"1467":1}}],["sqlserverbaseconnectionstring",{"3":{"1436":1}}],["sqlservermodel",{"3":{"1436":2}}],["sqlservermigrationsassembly",{"2":{"166":1,"633":1,"1446":1,"1684":1,"2097":1},"3":{"166":1,"633":1,"1286":5,"1436":1,"1446":1,"1684":3,"2097":1}}],["sqlserverretryingexecutionstrategy",{"3":{"1286":1}}],["sqlserverthingconfiguration",{"3":{"1199":1,"1207":1}}],["sqlserverthing",{"3":{"1199":3,"1207":1}}],["sqlserverindexedentity",{"3":{"1199":3,"1207":1}}],["sqlserverintegrationtestfixturebase",{"2":{"572":1,"914":1,"1212":1,"1428":1,"1429":1,"1488":1,"1490":1,"1600":1,"1671":1,"2055":1},"3":{"0":1,"572":6,"914":3,"916":1,"1199":5,"1207":2,"1212":1,"1428":1,"1429":3,"1436":8,"1488":2,"1490":2,"1496":1,"1600":1,"1671":1,"2055":1}}],["sqlserverpersistencetests",{"2":{"1453":1},"3":{"1198":1,"1199":2,"1207":9,"1437":1,"1438":2,"1453":1}}],["sqlserverdatasourceengine",{"2":{"1280":1},"3":{"1175":3,"1198":1,"1199":2,"1203":1,"1207":2,"1280":6,"1282":2,"1286":24,"1359":1}}],["sqlserverdbcontexttests",{"3":{"1199":1,"1207":3}}],["sqlserverdbcontextmodelsnapshot",{"3":{"1140":1,"1209":4}}],["sqlserverdbcontext",{"2":{"47":1,"200":1,"812":1,"1192":1,"1363":1,"1412":1,"1510":1,"1614":1,"1652":1,"1697":1,"1849":1,"1853":1,"1857":1},"3":{"47":2,"71":1,"166":1,"200":1,"674":2,"812":1,"819":3,"987":1,"1034":1,"1192":1,"1198":1,"1199":4,"1202":1,"1203":2,"1207":2,"1212":2,"1272":1,"1273":5,"1284":1,"1286":23,"1363":1,"1369":7,"1396":10,"1412":1,"1415":1,"1436":5,"1438":1,"1441":1,"1453":1,"1486":1,"1487":1,"1490":1,"1510":1,"1614":1,"1651":2,"1652":1,"1664":1,"1697":1,"1727":1,"1849":2,"1853":1,"1857":1,"2032":1}}],["sqlserverauditing",{"2":{"719":1},"3":{"719":1,"1467":1}}],["sqlserveruniqueconstraintviolationdetectortests",{"2":{"1199":1,"1438":1},"3":{"1199":1,"1207":2,"1286":1,"1438":2}}],["sqlserveruniqueconstraintviolationdetector",{"2":{"1203":1,"1284":1,"1323":1},"3":{"692":1,"1199":4,"1203":1,"1207":2,"1284":2,"1286":8,"1323":1,"1396":1}}],["sqlserverorsqlite",{"2":{"636":1},"3":{"633":1,"636":1}}],["sqlserver",{"2":{"170":1,"564":2,"631":1,"1174":1,"1203":1,"1327":1,"1361":1,"1369":1,"1437":1,"1438":1,"1487":1,"1497":1,"1724":1,"1728":1,"1729":1},"3":{"166":2,"170":2,"195":3,"341":2,"345":1,"350":1,"403":1,"564":8,"631":2,"633":1,"891":4,"1018":1,"1026":1,"1034":2,"1085":1,"1140":1,"1174":1,"1175":1,"1199":8,"1203":1,"1206":1,"1207":24,"1209":138,"1212":1,"1213":3,"1259":1,"1282":1,"1286":25,"1323":1,"1327":1,"1339":6,"1359":1,"1361":1,"1369":8,"1396":7,"1415":3,"1436":12,"1437":2,"1438":4,"1441":3,"1443":1,"1444":1,"1453":2,"1455":2,"1473":1,"1486":1,"1487":1,"1490":1,"1492":1,"1496":3,"1497":1,"1500":1,"1577":1,"1591":1,"1652":1,"1661":1,"1697":1,"1722":2,"1724":1,"1727":4,"1728":4,"1729":1,"1860":1,"1862":1}}],["sqlserverconnectionstring",{"2":{"166":1,"170":1,"1326":1,"1441":2,"1473":1,"1490":1,"1722":1,"1729":1},"3":{"166":1,"170":1,"1286":6,"1326":1,"1339":3,"1436":1,"1441":3,"1473":1,"1490":1,"1722":1,"1729":1,"2008":1}}],["sqlexception",{"3":{"692":1,"1286":6,"1359":1,"1396":1,"1438":1}}],["sqlconnection",{"3":{"1488":1,"1490":1}}],["sqlconnectionstringbuilder",{"3":{"1436":1}}],["sqlcommand",{"3":{"1436":1}}],["sqlcliententraauthenticationtests",{"3":{"1199":1,"1207":2,"1496":1}}],["sqlclient",{"2":{"1477":1},"3":{"692":1,"1207":2,"1259":1,"1286":1,"1339":3,"1436":2,"1443":1,"1477":1,"1534":1,"1677":1,"2010":1,"2154":1,"2157":1}}],["sqlcmd",{"2":{"291":1,"563":1},"3":{"0":1,"291":4,"563":1,"757":1,"1212":1,"1455":4,"1467":2,"1474":1}}],["sqliteconfig",{"3":{"1286":2}}],["sqliteconnectionstring",{"2":{"633":1,"1722":1,"1729":1},"3":{"166":1,"633":1,"1286":5,"1339":2,"1441":1,"1722":1,"1729":1}}],["sqlitetestdbcontext",{"3":{"1199":2,"1207":1}}],["sqlitetestentityconfig",{"3":{"1199":2,"1207":1}}],["sqlitetestentity",{"3":{"1199":4,"1207":1}}],["sqliteindexedentity",{"3":{"1199":3,"1207":1}}],["sqlitedatasourceengine",{"2":{"1175":1,"1280":1},"3":{"1175":3,"1176":1,"1198":1,"1199":2,"1203":1,"1207":2,"1280":5,"1286":14}}],["sqlitedbcontext",{"2":{"47":1,"1510":1,"1697":1,"1719":1,"1728":1,"1731":1,"1857":1},"3":{"47":1,"166":1,"200":1,"1198":1,"1199":4,"1203":1,"1207":2,"1212":2,"1272":1,"1273":2,"1286":13,"1323":2,"1436":5,"1510":1,"1664":1,"1697":1,"1719":1,"1727":2,"1728":2,"1731":1,"1849":1,"1857":1}}],["sqlite3",{"2":{"370":1},"3":{"370":1}}],["sqlitepclraw",{"2":{"370":1},"3":{"370":3}}],["sqlitemigrationsassembly",{"2":{"171":1,"633":1,"1279":1,"1323":1,"1719":1,"1722":1,"1727":1,"1728":1,"1731":1},"3":{"166":1,"171":1,"633":1,"1279":1,"1286":4,"1323":1,"1719":1,"1722":1,"1727":1,"1728":1,"1731":1}}],["sqlite",{"0":{"2053":1},"2":{"170":1,"631":1,"932":1,"1492":1,"1602":1,"1661":1,"1674":1,"1692":1,"1697":1,"1705":1,"1718":1,"1722":2,"1726":1,"1727":2,"1728":3,"1731":2},"3":{"0":11,"10":2,"45":1,"164":1,"166":6,"170":4,"201":1,"290":1,"296":1,"343":2,"345":1,"370":1,"371":1,"564":1,"631":2,"633":8,"676":1,"698":1,"889":2,"890":1,"892":1,"906":1,"932":1,"1033":3,"1034":6,"1035":1,"1036":1,"1044":1,"1050":3,"1067":2,"1174":2,"1175":3,"1176":1,"1177":1,"1178":3,"1179":1,"1180":1,"1212":8,"1213":4,"1247":1,"1259":2,"1273":1,"1274":1,"1279":5,"1280":2,"1281":1,"1282":3,"1284":1,"1286":99,"1309":1,"1311":9,"1319":1,"1320":1,"1321":1,"1323":8,"1326":1,"1332":1,"1339":5,"1361":1,"1369":2,"1396":2,"1436":15,"1437":1,"1438":11,"1441":3,"1443":2,"1453":1,"1487":1,"1489":1,"1490":5,"1492":3,"1496":1,"1500":3,"1510":1,"1526":2,"1577":3,"1582":1,"1583":1,"1585":2,"1602":1,"1614":2,"1616":1,"1618":1,"1661":2,"1664":2,"1674":1,"1692":1,"1697":2,"1705":1,"1707":3,"1718":3,"1719":4,"1720":1,"1721":1,"1722":6,"1723":1,"1726":1,"1727":11,"1728":7,"1729":3,"1731":5,"1752":1,"1763":1,"1774":1,"1849":1,"1853":1,"1854":2,"1855":1,"1856":4,"1860":4,"1861":2,"1862":1,"1876":1,"2053":2,"2059":2,"2060":2,"2192":1,"2230":1,"2232":1}}],["sql",{"0":{"1477":2},"1":{"1130":1,"1131":1,"1132":1,"1133":1,"1134":1,"1135":1,"1136":1,"1137":1},"2":{"566":1,"599":1,"769":1,"974":1,"1455":1,"1466":2,"1468":1,"1470":4,"1472":1,"1477":4,"1482":1,"1484":1,"1647":1,"1775":1,"2036":1},"3":{"0":39,"10":2,"13":1,"21":1,"24":1,"41":1,"45":1,"46":1,"47":3,"164":3,"165":2,"166":3,"168":1,"170":8,"171":1,"201":1,"235":1,"318":1,"330":1,"331":1,"341":2,"342":1,"343":1,"387":1,"395":1,"402":1,"403":2,"405":2,"471":1,"536":1,"543":1,"544":1,"546":1,"547":1,"549":1,"566":1,"572":1,"574":1,"598":1,"599":9,"600":1,"601":2,"605":1,"609":5,"611":1,"632":1,"633":3,"640":1,"644":1,"692":1,"698":1,"699":1,"713":2,"715":1,"718":1,"764":4,"765":3,"766":9,"769":2,"800":1,"860":1,"888":1,"889":4,"890":2,"892":1,"905":1,"907":1,"913":2,"914":2,"971":2,"972":2,"973":1,"974":2,"987":1,"988":1,"1004":1,"1033":4,"1034":15,"1035":2,"1036":2,"1044":1,"1050":3,"1067":1,"1069":1,"1085":1,"1130":1,"1132":4,"1133":4,"1134":1,"1135":1,"1144":1,"1162":1,"1167":1,"1174":2,"1175":5,"1176":2,"1178":2,"1180":1,"1192":1,"1212":13,"1217":1,"1236":1,"1237":4,"1239":1,"1241":5,"1242":1,"1246":1,"1247":20,"1259":7,"1263":1,"1270":3,"1272":1,"1273":2,"1276":1,"1277":1,"1279":7,"1280":6,"1281":1,"1282":2,"1284":2,"1286":193,"1290":1,"1300":5,"1301":2,"1309":8,"1310":8,"1311":7,"1312":2,"1317":1,"1318":1,"1320":1,"1321":1,"1323":2,"1326":2,"1327":2,"1332":4,"1339":13,"1342":2,"1350":7,"1352":3,"1353":1,"1358":1,"1359":28,"1360":1,"1361":1,"1369":12,"1380":5,"1396":25,"1401":1,"1402":7,"1406":1,"1410":1,"1415":9,"1428":1,"1429":5,"1430":1,"1436":52,"1437":7,"1438":20,"1440":1,"1441":11,"1443":4,"1446":2,"1451":1,"1453":4,"1455":16,"1456":2,"1457":7,"1460":3,"1463":1,"1465":2,"1466":5,"1467":40,"1468":1,"1469":1,"1470":5,"1471":2,"1472":2,"1473":9,"1474":10,"1475":7,"1476":3,"1477":24,"1478":8,"1482":3,"1483":2,"1484":2,"1486":9,"1487":3,"1488":4,"1490":11,"1492":4,"1496":3,"1510":1,"1524":4,"1525":2,"1526":9,"1532":1,"1534":6,"1535":1,"1575":1,"1577":6,"1585":1,"1589":1,"1590":1,"1591":16,"1595":1,"1596":2,"1597":1,"1599":2,"1600":4,"1602":1,"1611":6,"1614":1,"1617":1,"1618":1,"1641":1,"1647":1,"1648":2,"1653":4,"1654":3,"1657":1,"1664":2,"1665":1,"1670":2,"1671":1,"1673":1,"1677":1,"1678":1,"1682":1,"1684":2,"1685":1,"1689":3,"1693":1,"1697":1,"1698":1,"1700":1,"1704":1,"1717":2,"1718":3,"1719":1,"1720":1,"1721":1,"1723":1,"1724":1,"1725":1,"1727":2,"1731":2,"1752":1,"1763":2,"1774":1,"1775":2,"1815":3,"1840":2,"1844":1,"1848":1,"1849":1,"1851":1,"1853":1,"1854":4,"1855":1,"1856":3,"1857":1,"1858":2,"1859":2,"1860":3,"1861":2,"1863":1,"1864":1,"1865":1,"1867":1,"1871":1,"1872":1,"1876":1,"1877":1,"1922":1,"1986":1,"1988":1,"1989":2,"1994":1,"2003":1,"2004":2,"2005":1,"2007":1,"2013":2,"2033":1,"2034":5,"2036":4,"2037":1,"2043":3,"2053":1,"2055":1,"2059":4,"2111":1,"2158":1,"2167":1,"2191":1,"2192":1,"2193":1,"2213":1,"2219":2,"2220":4,"2224":1,"2230":1,"2232":4}}],["slept",{"3":{"1436":1}}],["sleeps",{"3":{"1259":1,"1323":1,"1324":1,"1436":1,"1671":1,"1841":2,"1948":1}}],["sleep",{"2":{"760":1},"3":{"757":1,"759":1,"760":2,"998":1,"1286":1,"1323":2,"1324":1,"1417":1,"1429":1,"1436":1,"1467":1,"1526":1,"1565":1,"1577":1}}],["sleeping",{"3":{"17":1,"881":1,"1160":1,"1259":1,"1275":1,"1324":1,"1396":1,"1436":1}}],["slugged",{"3":{"1496":1}}],["slugs",{"3":{"1480":1,"1496":1}}],["slug",{"2":{"1479":1,"1480":1},"3":{"1415":2,"1479":2,"1480":3}}],["slate",{"3":{"1324":1}}],["slashes",{"3":{"1359":1,"1436":3}}],["slash",{"3":{"420":1,"427":1,"1311":1,"1324":4,"1339":2,"1384":1,"1416":1,"1417":4,"1456":1}}],["slack",{"3":{"178":1,"593":1,"626":1,"627":1,"1286":1,"1324":1,"1415":1,"1455":1,"1492":1,"2047":1,"2220":1}}],["slsa",{"3":{"374":1,"1454":1,"1577":2}}],["sldexp",{"3":{"245":1,"1301":1,"1438":1}}],["sln",{"3":{"1728":1}}],["slnf",{"2":{"1215":2,"1455":1,"1459":1,"1486":2,"1494":1,"1531":1,"1611":1,"1616":1,"1617":1,"1619":1,"1775":1},"3":{"218":2,"374":1,"575":2,"609":2,"914":1,"954":2,"960":1,"1066":1,"1192":1,"1215":3,"1339":2,"1429":1,"1436":6,"1438":3,"1455":6,"1459":1,"1460":2,"1486":19,"1487":6,"1490":1,"1492":5,"1494":1,"1524":1,"1526":34,"1531":6,"1532":1,"1534":2,"1535":2,"1591":24,"1596":3,"1597":1,"1600":2,"1611":5,"1616":1,"1617":2,"1619":1,"1757":1,"1775":1}}],["slnx",{"2":{"139":1,"140":1,"373":1,"509":1,"511":1,"514":1,"528":1,"591":1,"868":1,"906":1,"974":1,"1066":1,"1215":1,"1434":1,"1454":1,"1503":1,"1617":1,"1619":1,"1649":1,"1655":1,"1657":1,"1684":1,"1690":1,"1704":1,"2120":1},"3":{"0":2,"135":3,"137":1,"139":1,"140":1,"142":1,"373":2,"413":1,"509":2,"511":1,"514":3,"528":2,"531":1,"591":1,"618":1,"633":1,"733":1,"737":1,"868":1,"906":1,"914":1,"972":1,"974":1,"980":1,"1034":1,"1066":2,"1067":1,"1192":1,"1215":2,"1323":2,"1339":2,"1417":2,"1434":2,"1436":21,"1438":4,"1453":3,"1454":3,"1455":1,"1456":1,"1460":2,"1486":12,"1487":8,"1490":4,"1492":4,"1494":1,"1503":1,"1577":14,"1611":1,"1617":2,"1619":1,"1649":1,"1654":2,"1657":2,"1684":1,"1686":1,"1690":2,"1704":2,"1727":1,"1728":1,"1729":1,"1923":1,"2097":1,"2120":1}}],["sli",{"3":{"1707":1,"1709":1}}],["slight",{"3":{"1324":1}}],["slightly",{"3":{"178":1,"283":1,"592":1,"804":1,"933":1,"1117":1,"1237":1,"1247":1,"1286":1,"1300":1,"1311":3,"1324":2,"1343":1,"1359":1,"1380":1,"1396":1,"1492":1,"1631":1,"1960":1,"2042":1,"2047":1}}],["slid",{"3":{"1324":1}}],["slideware",{"3":{"1783":1,"2227":1}}],["slides",{"3":{"1351":1,"1357":1,"1369":1,"1395":1,"1630":1,"1982":1,"2232":1}}],["slide",{"3":{"1115":1,"1350":1,"1380":1,"1395":1,"1467":1,"2001":1,"2239":1}}],["sliding",{"3":{"0":2,"173":1,"178":1,"280":1,"497":2,"499":1,"500":1,"1212":2,"1301":1,"1311":3,"1339":1,"1666":1,"2000":1,"2002":1}}],["slidingwindowratelimiteroptions",{"3":{"1311":1}}],["slidingwindow",{"2":{"2000":1},"3":{"0":1,"178":1,"1311":4}}],["slicing",{"3":{"1005":1,"1311":1,"1415":1,"1436":2,"2114":1}}],["sliceable",{"3":{"1323":1}}],["sliced",{"3":{"1311":1,"1485":1,"2114":1}}],["slicefixtures",{"3":{"1207":11,"1431":5,"1436":25,"1496":1}}],["slicecohesion",{"3":{"1576":1}}],["slicecohesiontests",{"3":{"1199":2,"1207":4,"1436":16,"1489":2,"1496":1,"1526":1,"1534":1,"1535":1,"1577":1,"1591":2}}],["slicecohesiontestsbase",{"2":{"1003":1,"2048":1},"3":{"1003":2,"1199":3,"1207":2,"1431":3,"1436":13,"1496":1,"1526":1,"1577":2,"1591":1,"1654":1,"2048":1}}],["slicecohesionfitnesstests",{"3":{"1199":1,"1207":3,"1431":4,"1436":13,"1496":1,"1577":1,"1586":1}}],["slices",{"3":{"132":1,"1006":1,"1207":1,"1300":2,"1351":1,"1359":7,"1370":1,"1399":1,"1402":2,"1431":6,"1436":13,"1489":1,"1496":1,"1514":1,"1526":3,"1542":2,"1577":6,"1582":2,"1583":2,"1586":1,"1591":1,"1684":1,"1686":3,"1701":1,"1726":1,"1727":2,"1728":1,"1730":1,"1731":3,"1932":1,"1937":2,"2142":1}}],["slice",{"0":{"1352":1,"1411":1,"1542":1,"1651":1,"1702":1,"1932":1,"2097":1},"1":{"1931":1,"1932":1,"1933":1,"1934":1,"1935":1,"1936":1,"1937":1,"1938":1,"1939":1,"1940":1,"1941":1},"3":{"122":1,"359":1,"387":1,"632":1,"633":2,"1003":2,"1119":1,"1192":4,"1201":1,"1212":3,"1216":1,"1224":1,"1247":3,"1259":1,"1265":3,"1270":8,"1286":3,"1300":1,"1304":2,"1309":2,"1310":2,"1311":5,"1314":1,"1323":5,"1324":4,"1348":1,"1350":3,"1351":2,"1352":5,"1357":1,"1359":69,"1362":1,"1369":1,"1370":1,"1380":1,"1396":16,"1399":1,"1402":7,"1403":1,"1411":1,"1415":11,"1431":6,"1436":31,"1488":2,"1489":1,"1496":8,"1497":1,"1499":1,"1514":1,"1525":2,"1526":7,"1530":1,"1531":1,"1532":1,"1534":2,"1535":2,"1542":6,"1572":1,"1577":6,"1581":1,"1582":3,"1583":2,"1586":1,"1591":2,"1597":1,"1651":1,"1661":2,"1671":1,"1684":1,"1686":2,"1691":1,"1694":1,"1701":4,"1726":2,"1727":3,"1730":4,"1779":1,"1796":1,"1804":1,"1865":1,"1883":1,"1922":1,"1930":1,"1931":3,"1932":1,"1934":1,"1935":2,"1937":1,"1939":1,"1940":2,"1941":6,"1960":1,"2048":1,"2087":1,"2094":1,"2097":3,"2114":2,"2208":1,"2230":3}}],["slimmer",{"3":{"784":1}}],["slipping",{"3":{"1996":1}}],["slipped",{"3":{"1286":1}}],["slips",{"3":{"1323":1,"1496":1,"1909":1}}],["slip",{"3":{"157":1,"856":1,"1311":1,"1323":1,"1339":1,"1396":1,"1402":1,"1415":1,"1432":1,"1436":5,"1872":1,"2048":1}}],["slogan",{"3":{"1663":1,"2057":1}}],["sloalerts",{"2":{"612":1,"613":1,"2046":1},"3":{"609":2,"612":1,"613":1,"1436":2,"1467":1,"1476":1,"1526":1,"2046":1}}],["sloalertspecs",{"2":{"607":1,"609":1,"611":1,"612":1,"613":1,"1016":1,"1019":1,"1474":1,"2046":1},"3":{"0":1,"607":3,"609":3,"611":1,"612":1,"613":1,"1016":1,"1018":1,"1019":1,"1212":1,"1436":6,"1467":4,"1474":1,"1476":2,"1526":1,"1577":1,"1591":2,"1600":1,"2046":1,"2158":1}}],["slos",{"3":{"609":3,"914":1,"1018":1,"1436":2,"1467":3,"1566":1,"1574":1,"1575":1,"1577":1,"1578":1,"1581":1,"1582":1,"1583":1,"1707":1,"2158":1}}],["slowmo",{"3":{"1436":2,"1488":1}}],["slowing",{"3":{"1259":1,"1300":1,"1486":1}}],["slowly",{"3":{"1123":1,"1300":1,"1324":2,"1339":1,"1340":1,"1380":1,"1396":1,"1402":1,"1416":1,"2000":1,"2011":1}}],["sloworkbook",{"3":{"609":1,"1474":1,"1526":1,"1577":1}}],["slowdown",{"3":{"593":1,"1380":1,"1436":1,"2048":1}}],["slows",{"3":{"592":2,"1436":1,"1456":1,"2047":2}}],["slowbenchmark",{"2":{"591":1},"3":{"591":1}}],["slower",{"3":{"509":1,"511":1,"539":1,"717":1,"996":1,"1247":1,"1270":1,"1286":1,"1311":1,"1339":2,"1359":1,"1395":1,"1415":1,"1436":2,"1441":1,"1453":1,"1534":1,"1989":1,"2049":1}}],["slowest",{"3":{"0":1,"727":1,"1069":1,"1270":1,"1339":3,"1433":1,"1436":1,"1449":1,"1453":2,"1454":1,"1455":1,"1474":2,"1488":1,"1490":1,"2034":1}}],["slow",{"0":{"1902":1},"3":{"235":1,"236":1,"309":1,"311":1,"383":1,"459":1,"520":2,"539":1,"591":1,"709":1,"759":1,"765":1,"818":1,"821":1,"829":1,"833":1,"860":1,"996":1,"1020":1,"1042":1,"1102":1,"1259":1,"1270":1,"1286":2,"1300":1,"1301":3,"1307":1,"1309":1,"1311":2,"1323":2,"1324":5,"1332":1,"1337":1,"1339":4,"1357":1,"1359":2,"1380":1,"1390":1,"1395":5,"1396":6,"1402":1,"1415":1,"1416":1,"1417":3,"1433":1,"1436":7,"1438":1,"1441":1,"1443":1,"1446":1,"1447":1,"1449":1,"1455":1,"1459":2,"1460":1,"1475":1,"1487":1,"1488":1,"1491":1,"1549":1,"1551":1,"1570":1,"1668":1,"1671":1,"1708":1,"1889":1,"1902":1,"1922":1,"1944":1,"1946":1,"1989":1,"2007":1,"2014":1,"2032":1,"2047":1,"2050":1,"2051":1,"2053":1,"2060":1,"2188":1}}],["sloppy",{"3":{"135":1,"2067":1}}],["slots",{"3":{"319":1,"321":1,"471":1,"1012":1,"1013":1,"1247":1,"1301":3,"1311":1,"1324":1,"1359":5,"1380":1,"1402":2,"1415":1,"1453":1,"1479":2,"1480":2,"1554":1,"1555":1,"1572":1,"1577":1,"1631":1,"1994":1}}],["slot",{"3":{"0":1,"235":1,"265":1,"273":1,"299":1,"304":1,"499":1,"753":2,"774":1,"888":1,"891":1,"904":1,"1012":1,"1013":2,"1033":1,"1034":1,"1124":1,"1212":1,"1270":3,"1271":1,"1286":3,"1300":1,"1301":3,"1304":2,"1307":1,"1309":2,"1311":4,"1323":4,"1324":24,"1332":2,"1339":3,"1350":5,"1352":1,"1359":18,"1369":5,"1380":2,"1396":5,"1402":2,"1415":6,"1416":3,"1417":6,"1436":3,"1458":1,"1459":1,"1479":1,"1480":2,"1489":1,"1496":5,"1531":1,"1554":2,"1631":1,"1637":1,"1779":1,"1876":1,"1945":1,"1957":1,"2000":1,"2083":1,"2084":1,"2118":1,"2123":1,"2134":1}}],["slo",{"0":{"1709":1},"1":{"606":1,"607":1,"608":1,"609":1,"610":1,"611":1,"612":1,"613":1,"614":1},"3":{"0":4,"177":1,"403":1,"606":1,"607":3,"609":11,"612":1,"759":1,"765":1,"914":1,"971":1,"1212":1,"1431":1,"1436":6,"1455":2,"1466":1,"1467":16,"1469":3,"1474":6,"1476":6,"1482":2,"1489":2,"1526":3,"1535":1,"1550":1,"1575":2,"1577":6,"1582":1,"1583":2,"1586":2,"1591":3,"1670":1,"1671":1,"1706":3,"1709":2,"2001":1,"2046":1,"2049":1,"2158":2,"2160":1,"2232":2}}],["spun",{"3":{"2135":1}}],["spurious",{"3":{"897":1,"898":1,"996":1,"1300":1,"1309":1,"1323":1,"1324":2,"1396":2,"1415":1,"1436":1,"1472":1,"1534":1,"1651":1,"1919":1}}],["splats",{"3":{"1395":1,"1436":2}}],["splice",{"3":{"219":1,"1324":1,"2150":1}}],["spliced",{"3":{"219":1,"1286":1,"1324":2,"1467":2,"2149":1,"2150":1}}],["splitter",{"3":{"1436":1}}],["splittoplevel",{"3":{"1436":3}}],["splitting",{"3":{"135":1,"185":1,"507":1,"530":1,"592":1,"642":1,"691":1,"757":2,"758":1,"785":1,"989":1,"1237":3,"1247":2,"1261":1,"1270":2,"1271":1,"1286":5,"1300":4,"1301":1,"1309":2,"1311":3,"1323":2,"1324":8,"1339":1,"1350":2,"1359":10,"1369":1,"1380":1,"1385":1,"1395":5,"1396":3,"1402":9,"1415":3,"1417":5,"1436":7,"1455":1,"1498":1,"1560":1,"1597":1,"1749":1,"1820":1}}],["splitfullname",{"3":{"1311":1}}],["splits",{"3":{"0":1,"165":1,"180":1,"505":1,"528":1,"574":1,"649":1,"759":1,"778":1,"781":1,"845":1,"905":1,"1175":1,"1178":1,"1241":1,"1247":2,"1259":1,"1280":1,"1286":9,"1300":6,"1309":2,"1310":1,"1311":2,"1312":1,"1324":1,"1338":1,"1343":1,"1350":1,"1359":4,"1369":1,"1383":1,"1395":4,"1396":1,"1402":2,"1416":1,"1430":1,"1436":1,"1446":2,"1460":1,"1467":2,"1492":1,"1496":2,"1526":1,"1535":1,"1788":1,"1815":2,"1837":1,"1865":1,"1938":1,"1942":1,"1950":1,"1959":1,"1995":1,"1997":1,"2185":1,"2193":1,"2232":1}}],["split",{"0":{"1252":1,"1385":1},"3":{"0":8,"35":1,"47":2,"58":1,"59":1,"60":1,"62":1,"91":1,"92":1,"96":1,"97":1,"139":1,"145":1,"160":1,"161":1,"168":2,"226":1,"266":1,"320":1,"379":1,"380":1,"440":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":2,"495":1,"507":1,"509":1,"513":1,"514":1,"522":1,"526":1,"529":1,"530":1,"543":2,"545":3,"547":3,"549":2,"550":1,"554":1,"560":1,"582":1,"587":1,"616":1,"631":1,"633":2,"638":1,"652":1,"657":1,"664":1,"677":1,"682":1,"698":1,"707":1,"726":2,"731":1,"733":2,"741":1,"757":1,"758":1,"804":1,"805":1,"826":1,"827":1,"828":1,"844":1,"849":1,"862":1,"867":1,"872":1,"875":2,"923":1,"926":1,"927":1,"938":1,"941":1,"983":1,"1004":3,"1010":1,"1011":1,"1013":1,"1018":1,"1019":2,"1069":1,"1101":1,"1177":1,"1201":2,"1212":4,"1229":2,"1234":1,"1237":2,"1238":2,"1240":1,"1243":2,"1244":1,"1247":9,"1249":1,"1254":1,"1256":1,"1259":5,"1265":1,"1270":4,"1273":1,"1274":2,"1278":3,"1280":1,"1281":1,"1286":22,"1293":1,"1300":13,"1301":3,"1309":4,"1310":10,"1311":12,"1316":1,"1323":8,"1324":24,"1328":1,"1339":7,"1341":1,"1349":1,"1350":11,"1352":1,"1359":35,"1362":1,"1363":1,"1366":2,"1369":5,"1371":2,"1375":2,"1380":17,"1385":1,"1389":2,"1395":27,"1396":27,"1402":13,"1403":1,"1404":1,"1413":1,"1414":1,"1415":16,"1416":4,"1417":12,"1423":1,"1424":1,"1425":1,"1427":1,"1431":1,"1436":35,"1438":3,"1441":2,"1447":5,"1450":1,"1454":4,"1455":7,"1456":1,"1461":1,"1462":1,"1463":1,"1467":2,"1469":2,"1470":1,"1471":2,"1474":1,"1486":2,"1487":1,"1489":1,"1492":2,"1496":10,"1498":1,"1514":1,"1526":2,"1530":1,"1531":1,"1532":1,"1535":1,"1555":1,"1577":4,"1585":2,"1589":1,"1591":2,"1595":1,"1596":1,"1597":1,"1599":1,"1600":2,"1611":1,"1614":1,"1619":1,"1638":1,"1651":1,"1674":1,"1675":1,"1702":1,"1755":1,"1776":2,"1783":2,"1788":1,"1789":2,"1790":1,"1792":4,"1793":1,"1796":1,"1807":1,"1815":1,"1817":1,"1847":2,"1849":1,"1852":3,"1853":3,"1864":2,"1865":2,"1866":1,"1867":1,"1884":1,"1893":1,"1902":1,"1915":1,"1932":1,"1942":1,"1948":1,"1950":1,"1951":1,"1959":1,"1977":1,"1979":1,"1985":1,"1988":1,"1996":1,"1997":1,"2007":1,"2014":1,"2015":3,"2025":1,"2026":1,"2027":1,"2028":2,"2038":1,"2063":1,"2111":1,"2126":1,"2127":1,"2128":1,"2149":1,"2154":1,"2179":1,"2185":1,"2198":1,"2232":2}}],["spymapper",{"3":{"1199":2,"1207":1}}],["sp",{"2":{"518":1},"3":{"518":2,"1100":2,"1323":1,"1436":1,"1534":1}}],["sprint",{"3":{"1986":1,"2043":1}}],["sprinkling",{"3":{"1913":1,"2160":1}}],["sprinkled",{"3":{"1292":1,"1309":1}}],["spring",{"3":{"1348":1,"1350":2,"1355":1,"1359":4,"1393":1}}],["sprawling",{"3":{"1300":1}}],["sprawl",{"3":{"1286":1,"1526":1,"1542":1}}],["sprayed",{"3":{"1369":1,"1998":1}}],["spraying",{"3":{"280":1,"1311":1,"1406":1,"1415":1}}],["spray",{"3":{"175":1,"177":1,"1311":5,"1339":1,"1406":1,"1438":1,"1599":1,"1600":1,"1999":2,"2001":2,"2002":1}}],["spreading",{"3":{"280":1,"1396":1,"1436":2}}],["spreadsheet",{"3":{"743":2,"1286":2,"1311":3}}],["spreadsheets",{"3":{"743":1}}],["spreads",{"3":{"22":1,"23":1,"603":1,"651":1,"1286":1,"1323":1,"1350":1,"1380":2,"1417":1,"1436":2,"1526":1,"1542":1}}],["spread",{"3":{"0":2,"80":1,"408":1,"956":1,"980":1,"1174":1,"1286":2,"1301":1,"1311":1,"1323":1,"1324":1,"1347":1,"1359":10,"1380":3,"1396":1,"1415":1,"1417":1,"1436":4,"1474":1,"1638":1,"2178":1}}],["spills",{"3":{"1324":1}}],["spirit",{"3":{"491":1,"1324":1}}],["spinners",{"3":{"1607":1,"1741":1}}],["spinner",{"3":{"1324":2,"1390":1,"1395":10,"1396":1,"1561":1,"1577":1,"1643":2,"1644":1,"2070":1}}],["spinning",{"3":{"1259":2,"1323":1,"1677":1,"2003":1}}],["spine",{"0":{"1345":1},"3":{"1248":1,"1259":1,"1287":1,"1288":1,"1340":1,"1345":1,"1403":1,"1500":1}}],["spins",{"3":{"1213":1,"1570":1}}],["spin",{"3":{"19":1,"237":1,"1324":1,"1948":1,"2053":1}}],["spikes",{"3":{"875":1,"1188":1,"1488":1}}],["spike",{"3":{"0":3,"58":1,"390":1,"609":2,"611":1,"863":1,"898":1,"1077":3,"1124":1,"1339":2,"1350":1,"1436":1,"1456":1,"1465":1,"1467":2,"1670":1}}],["spof",{"3":{"2038":1}}],["spofs",{"3":{"1476":1,"2033":1}}],["spooky",{"3":{"1556":1}}],["spoof",{"3":{"1309":1}}],["spoofed",{"3":{"1300":1,"1402":2}}],["spoofable",{"3":{"177":1,"751":1,"1286":2,"1311":1}}],["spoofing",{"3":{"0":1}}],["spoken",{"3":{"1417":3}}],["sportsesports",{"3":{"1395":1}}],["sporadic",{"3":{"484":1}}],["spotting",{"3":{"2048":1}}],["spotty",{"3":{"1641":1}}],["spots",{"3":{"0":1,"422":1,"759":1,"1270":1,"1396":1,"1416":1,"1436":1,"1474":1,"1537":2,"1797":1}}],["spot",{"3":{"0":2,"135":1,"422":1,"425":1,"429":1,"759":1,"761":1,"800":1,"988":1,"1259":1,"1339":1,"1396":1,"1405":1,"1436":8,"1438":1,"1449":2,"1492":1,"1496":21,"1500":1,"2000":1,"2048":1}}],["sponsorvalidationrules",{"3":{"1207":10,"1271":4,"1350":3,"1359":37}}],["sponsorvisitlink",{"2":{"1394":1},"3":{"1394":1,"1395":2,"1396":1}}],["sponsorvisitroundtriptests",{"3":{"1199":1,"1207":3}}],["sponsorvisitresultdto",{"3":{"1199":8,"1203":1,"1207":2,"1396":15}}],["sponsorvisitrequestvalidatortests",{"3":{"1199":1,"1207":2}}],["sponsorvisitrequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1396":5}}],["sponsorvisitrequest",{"3":{"1199":7,"1203":1,"1207":2,"1396":9}}],["sponsorvisittests",{"3":{"1199":1,"1207":2,"1396":3,"1438":2}}],["sponsorvisitpage",{"3":{"1199":2,"1207":2}}],["sponsorvisits",{"2":{"690":2,"1396":1},"3":{"690":2,"1203":2,"1207":4,"1396":14}}],["sponsorvisit",{"2":{"690":1},"3":{"556":1,"690":1,"1199":5,"1203":1,"1207":3,"1395":1,"1396":38}}],["sponsorname",{"3":{"1350":2,"1380":2,"1396":2}}],["sponsornamerules",{"3":{"1199":2,"1203":1,"1207":1,"1353":1,"1359":6}}],["sponsornavigationpopulatortests",{"3":{"1199":1,"1207":2,"1359":2}}],["sponsornavigationpopulator",{"3":{"1199":2,"1203":1,"1207":2,"1310":5,"1350":1,"1354":1,"1359":7}}],["sponsorbuilder",{"3":{"1199":3,"1207":2,"1436":1}}],["sponsorboothnumberrules",{"3":{"1199":2,"1203":1,"1207":1,"1359":7}}],["sponsorupdateappliertests",{"3":{"1199":1,"1207":2}}],["sponsorupdateapplier",{"3":{"1199":2,"1203":1,"1207":2,"1270":1,"1350":1,"1354":1,"1359":8}}],["sponsorupdaterequestvalidatortests",{"3":{"1199":1,"1207":2}}],["sponsorupdaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1359":13}}],["sponsorupdaterequest",{"3":{"1199":7,"1203":1,"1207":2,"1350":1,"1359":16,"1380":1}}],["sponsorformfields",{"3":{"1350":1,"1395":5}}],["sponsorformmodel",{"3":{"1199":3,"1203":1,"1207":2,"1350":1,"1384":2,"1395":9}}],["sponsorfieldrules",{"3":{"1199":3,"1203":1,"1207":1,"1353":1,"1359":20}}],["sponsorwebsiteurlrules",{"3":{"1199":2,"1203":1,"1207":1,"1271":1,"1359":4}}],["sponsortests",{"3":{"1199":1,"1207":2,"1350":1,"1438":2}}],["sponsortwitterhandlerules",{"3":{"1199":2,"1203":1,"1207":1,"1359":4}}],["sponsortierinfo",{"3":{"1496":2}}],["sponsortiers",{"3":{"1395":2}}],["sponsortier",{"3":{"1199":25,"1203":1,"1207":2,"1342":1,"1350":10,"1359":3,"1369":5,"1392":1,"1395":11}}],["sponsorlogourlrules",{"3":{"1199":2,"1203":1,"1207":1,"1271":1,"1359":4}}],["sponsorlist",{"3":{"1199":3,"1203":1,"1207":2,"1324":2,"1350":2,"1383":2,"1392":2,"1395":20}}],["sponsorlistpage",{"3":{"1199":2,"1207":2}}],["sponsorlinkedinurlrules",{"3":{"1199":2,"1203":1,"1207":1,"1359":6}}],["sponsorliveinfo",{"3":{"1199":8,"1203":1,"1207":2,"1349":1,"1350":7,"1359":1,"1378":1,"1380":3,"1396":2}}],["sponsorinfo",{"3":{"1496":2}}],["sponsoring",{"3":{"1395":1}}],["sponsorinvariantstests",{"3":{"1199":1,"1207":2,"1350":1}}],["sponsorinvariants",{"2":{"1344":1,"1362":1},"3":{"1199":14,"1203":1,"1207":2,"1344":2,"1350":8,"1359":34,"1362":1,"1369":7}}],["sponsorid",{"2":{"803":1,"1052":1,"1055":1},"3":{"690":3,"799":2,"803":1,"1052":1,"1055":1,"1350":2,"1359":2,"1380":1,"1396":34,"1415":2,"1436":1,"1526":1,"1627":2}}],["sponsoridentifiertype",{"2":{"468":1,"799":1,"1055":1},"3":{"468":1,"799":2,"1055":1,"1350":4,"1359":5,"1395":4,"1396":11}}],["sponsoreditmodel",{"3":{"1199":2,"1203":1,"1207":2,"1395":8}}],["sponsoreventidrules",{"3":{"1199":2,"1203":1,"1207":1,"1271":1,"1359":8}}],["sponsordetails",{"2":{"1394":1},"3":{"1394":1,"1395":1}}],["sponsordetailtests",{"3":{"1199":1,"1207":2}}],["sponsordetail",{"3":{"1199":2,"1203":1,"1207":2,"1350":1,"1384":2,"1388":1,"1392":2,"1395":33,"1396":1}}],["sponsordetailpage",{"3":{"1199":3,"1207":2}}],["sponsordescriptionrules",{"3":{"1199":2,"1203":1,"1207":1,"1359":4}}],["sponsordtomappertests",{"3":{"1199":1,"1207":2,"1359":2}}],["sponsordtomapper",{"3":{"1199":5,"1203":1,"1207":2,"1311":1,"1350":4,"1353":1,"1359":7}}],["sponsordto",{"3":{"1199":20,"1203":1,"1207":2,"1347":2,"1350":13,"1359":22,"1380":1,"1382":1,"1395":27,"1496":1}}],["sponsorconfiguration",{"2":{"1361":1},"3":{"1199":1,"1203":1,"1207":2,"1350":8,"1359":5,"1361":1,"1362":3,"1369":10}}],["sponsorcreatetests",{"3":{"1199":1,"1207":2}}],["sponsorcreate",{"3":{"1199":2,"1203":1,"1207":2,"1350":3,"1392":1,"1395":25}}],["sponsorcreaterequestvalidatortests",{"3":{"1199":1,"1207":2,"1359":2}}],["sponsorcreaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1359":11}}],["sponsorcreaterequestmapper",{"3":{"1199":1,"1203":1,"1207":2,"1311":2,"1359":5}}],["sponsorcreaterequest",{"3":{"1199":8,"1203":1,"1207":2,"1270":1,"1350":1,"1359":13,"1380":1}}],["sponsorcreatemodel",{"3":{"1199":3,"1203":1,"1207":2,"1395":7}}],["sponsorcreatepage",{"3":{"1199":3,"1207":2}}],["sponsorchanged",{"3":{"782":1,"1199":3,"1203":1,"1207":2,"1345":1,"1350":10,"1359":2,"1438":1}}],["sponsorsmanage",{"3":{"1350":2,"1359":1,"1373":1,"1380":2}}],["sponsorships",{"3":{"1380":1,"1395":1}}],["sponsorshippacketurl",{"2":{"1387":1},"3":{"1350":2,"1359":6,"1369":2,"1387":1,"1395":4,"1526":1}}],["sponsorshippacketurlmaxlength",{"3":{"1350":2,"1359":2}}],["sponsorship",{"3":{"1271":1,"1340":1,"1342":3,"1350":5,"1359":10,"1364":1,"1369":4,"1380":1,"1384":1,"1392":2,"1395":12,"1436":1,"1438":1,"1455":1,"1463":1,"1467":1,"1474":1,"1629":1,"1631":2}}],["sponsorsortrules",{"3":{"1199":2,"1203":1,"1207":1,"1359":4}}],["sponsorservice",{"3":{"881":1,"1199":6,"1203":1,"1207":2,"1369":1,"1382":3,"1395":9}}],["sponsorscontrollertests",{"3":{"1199":1,"1207":2,"1438":2,"1487":1}}],["sponsorscontroller",{"3":{"743":1,"1199":4,"1203":1,"1207":2,"1350":5,"1359":20,"1371":2,"1372":3,"1373":3,"1375":3,"1380":10,"1436":1}}],["sponsorscache",{"2":{"386":1},"3":{"386":1,"1350":1,"1379":1,"1380":2}}],["sponsors",{"0":{"1392":1},"2":{"1350":1,"1395":1,"1396":1},"3":{"0":1,"186":1,"556":1,"743":1,"837":1,"838":1,"881":1,"1004":1,"1203":40,"1207":112,"1270":1,"1271":4,"1310":4,"1311":2,"1342":3,"1346":2,"1347":1,"1348":2,"1349":1,"1350":37,"1351":2,"1352":1,"1353":1,"1359":127,"1363":1,"1364":1,"1369":18,"1375":1,"1380":9,"1381":1,"1382":3,"1383":4,"1384":3,"1387":1,"1388":1,"1392":6,"1395":69,"1396":13,"1415":1,"1433":1,"1436":1,"1437":1,"1438":6,"1447":1,"1487":1,"1496":1,"1526":1,"1621":1,"1625":1,"1627":6,"1629":1,"1631":7,"2240":1}}],["sponsor",{"2":{"1346":1},"3":{"0":2,"556":1,"583":1,"688":2,"690":7,"691":3,"924":1,"1199":28,"1203":1,"1207":2,"1212":1,"1270":1,"1271":5,"1310":4,"1311":1,"1340":1,"1342":12,"1343":2,"1346":3,"1349":1,"1350":76,"1351":1,"1352":3,"1354":1,"1355":1,"1359":141,"1361":1,"1362":2,"1369":22,"1375":1,"1380":11,"1381":1,"1383":1,"1387":2,"1388":1,"1392":3,"1393":1,"1395":83,"1396":103,"1415":1,"1436":2,"1438":11,"1466":1,"1467":1,"1487":5,"1496":4,"1526":4,"1627":2,"1629":1,"1631":10,"1638":1}}],["speeding",{"3":{"1301":1}}],["speed",{"3":{"459":1,"463":1,"1301":1,"1436":3,"1488":1,"1554":1,"1560":1,"1900":1,"1923":1,"2051":1}}],["speeds",{"3":{"133":1,"457":1,"465":1,"2100":1,"2106":3}}],["speechoptions",{"3":{"1417":2}}],["speechtotextoptions",{"3":{"1417":1}}],["speechtotext",{"3":{"1402":3,"1417":2}}],["speech",{"3":{"412":1,"415":1,"1192":1,"1202":1,"1203":1,"1395":1,"1416":2,"1417":16,"1496":1,"1669":2}}],["spell",{"3":{"526":1,"607":1,"611":1,"650":1,"727":1,"1247":3,"1286":3,"1300":3,"1301":2,"1323":1,"1324":1,"1326":1,"1339":1,"1352":1,"1359":2,"1369":1,"1396":2,"1405":1,"1406":1,"1415":5,"1436":3,"1444":1,"1596":1,"1814":1}}],["spellsoutgenericarguments",{"3":{"1436":3}}],["spells",{"3":{"390":1,"674":1,"1034":1,"1237":1,"1247":3,"1259":4,"1270":1,"1286":5,"1300":4,"1309":2,"1311":3,"1315":1,"1323":6,"1324":6,"1326":1,"1339":1,"1343":1,"1350":2,"1359":4,"1369":2,"1380":2,"1395":3,"1396":4,"1402":2,"1412":1,"1415":4,"1417":2,"1436":8,"1455":1,"1467":1,"1486":1,"1524":1,"1526":1,"1531":1,"1577":1,"2004":1,"2144":1}}],["spellings",{"3":{"892":1,"1247":2,"1286":3,"1296":1,"1297":1,"1324":1,"1339":1,"1350":1,"1436":1,"1596":1}}],["spelling",{"3":{"283":2,"633":4,"889":1,"974":1,"1068":1,"1247":1,"1270":1,"1286":7,"1300":1,"1323":1,"1343":1,"1369":1,"1395":1,"1396":1,"1402":1,"1415":2,"1417":1,"1436":9,"1596":1,"1722":1,"1729":1}}],["spelled",{"3":{"122":1,"793":1,"803":1,"938":1,"980":1,"1020":1,"1230":1,"1237":1,"1259":1,"1263":1,"1270":2,"1271":1,"1286":3,"1300":4,"1301":1,"1309":1,"1311":3,"1312":1,"1323":2,"1324":3,"1326":2,"1339":2,"1359":4,"1369":1,"1379":1,"1380":1,"1395":1,"1396":5,"1402":1,"1409":1,"1415":2,"1417":4,"1436":5,"1438":2,"1492":1,"1535":1,"2030":1,"2166":1}}],["spent",{"3":{"73":1,"818":2,"819":1,"872":1,"1259":1,"1283":1,"1286":1,"1293":1,"1300":3,"1311":1,"1324":1,"1339":3,"1408":1,"1418":1,"1473":1,"1483":1,"1667":1,"1843":1,"1980":1,"1982":1,"1985":1,"2170":1}}],["spending",{"3":{"151":1,"1293":1,"1436":1,"1438":1}}],["spends",{"3":{"19":1,"122":1,"592":1,"818":1,"1263":2,"1270":2,"1311":1,"1324":2,"1339":1,"1395":2,"1396":1,"1401":1,"1402":2,"1415":1,"1436":2,"1455":2,"1492":2,"1821":1,"1908":1,"2003":1,"2047":1}}],["spend",{"3":{"0":3,"178":1,"408":1,"517":1,"538":1,"650":1,"759":1,"760":1,"765":3,"767":2,"768":1,"831":1,"1017":1,"1018":3,"1019":3,"1020":3,"1041":1,"1090":1,"1091":2,"1092":1,"1093":3,"1212":1,"1229":1,"1300":1,"1311":2,"1324":2,"1330":1,"1339":2,"1350":2,"1359":3,"1369":2,"1380":1,"1395":1,"1396":1,"1402":1,"1422":1,"1425":1,"1426":1,"1427":2,"1435":1,"1436":3,"1438":1,"1442":1,"1456":2,"1457":2,"1460":1,"1467":7,"1476":1,"1490":1,"1496":1,"1526":3,"1553":1,"1568":2,"1577":2,"1591":1,"1678":1,"1679":1,"1683":1,"1788":1,"2006":1,"2156":1,"2160":1,"2168":1,"2170":1,"2177":3,"2179":1,"2180":2,"2183":1}}],["speakasync",{"3":{"1395":1,"1417":4}}],["speakable",{"3":{"882":1}}],["speak",{"0":{"2243":1},"3":{"243":1,"286":1,"346":1,"536":1,"800":1,"1301":1,"1311":1,"1312":1,"1331":1,"1338":1,"1339":6,"1350":1,"1417":2,"1446":1,"1447":1,"1656":1,"1670":1,"1928":1,"2025":1,"2116":1,"2216":1,"2243":1}}],["speaking",{"0":{"2216":1},"1":{"2238":1,"2239":1,"2240":1,"2241":1,"2242":1,"2243":1},"3":{"103":1,"856":1,"1050":1,"1053":1,"1417":3,"2238":2,"2244":1}}],["speaks",{"3":{"31":1,"97":2,"98":1,"99":1,"265":1,"331":2,"341":1,"433":1,"692":1,"905":1,"1067":1,"1134":1,"1217":1,"1241":1,"1270":1,"1271":3,"1300":2,"1309":1,"1311":1,"1326":1,"1331":1,"1337":1,"1339":4,"1350":2,"1359":1,"1395":2,"1396":2,"1417":5,"1430":1,"1436":1,"1440":2,"1444":1,"1447":1,"1685":1,"1831":2,"1874":1,"1888":1,"1928":1,"1988":2,"2009":2,"2025":1,"2083":1,"2141":1}}],["speakerprofiletests",{"3":{"1496":1}}],["speakeroverlap",{"3":{"1350":2,"1395":1}}],["speakername",{"3":{"1350":1,"1395":1}}],["speakernavigationpopulatortests",{"3":{"1199":1,"1207":2,"1359":2}}],["speakernavigationpopulator",{"3":{"1199":2,"1203":1,"1207":2,"1310":5,"1350":2,"1354":1,"1359":10}}],["speakerresult",{"3":{"1247":1}}],["speakerrenamed",{"2":{"785":1},"3":{"785":1}}],["speakervalidationrules",{"3":{"1207":7,"1271":3,"1350":4,"1359":23}}],["speakervalidationrulestests",{"3":{"1199":1,"1207":4,"1359":6}}],["speakermanagementtests",{"3":{"1199":1,"1207":2}}],["speakertests",{"3":{"1199":1,"1207":2,"1350":1}}],["speakerbuilder",{"3":{"1199":11,"1207":2,"1436":1}}],["speakerwebsiteurlrules",{"3":{"1199":2,"1203":1,"1207":1,"1359":2}}],["speakergithuburlrules",{"3":{"1199":2,"1203":1,"1207":1,"1271":1,"1359":5}}],["speakerformfields",{"3":{"1395":2}}],["speakerformmodel",{"3":{"1199":3,"1203":1,"1207":2,"1384":2,"1395":8}}],["speakerfeedbackauthtests",{"3":{"1199":1,"1207":2,"1438":2}}],["speakerfieldrules",{"3":{"1199":3,"1203":1,"1207":1,"1353":1,"1359":20}}],["speakerfirstnamerules",{"3":{"1199":3,"1203":1,"1207":1,"1271":1,"1353":1,"1359":5}}],["speakerentityqueryservicetests",{"3":{"1199":1,"1207":2,"1359":2}}],["speakerentityqueryservice",{"2":{"1244":1},"3":{"1199":2,"1203":1,"1207":2,"1242":2,"1244":3,"1247":2,"1354":2,"1359":9}}],["speakeremailrules",{"3":{"1199":2,"1203":1,"1207":1,"1359":7}}],["speakereditmodeltests",{"3":{"1199":1,"1207":2}}],["speakereditmodel",{"3":{"1199":4,"1203":1,"1207":2,"1395":6}}],["speakerqrtests",{"3":{"1199":1,"1207":3,"1438":2}}],["speakerqr",{"3":{"1199":2,"1203":1,"1207":2,"1324":5,"1388":3,"1395":11,"1526":1}}],["speakerqrpage",{"3":{"1199":2,"1207":2}}],["speakerquestionanswernavigationpopulator",{"3":{"1496":1}}],["speakerquestionanswerid",{"3":{"1350":1}}],["speakerquestionansweridentifiertype",{"3":{"1350":3,"1359":1}}],["speakerquestionanswerscontroller",{"2":{"1632":1,"1639":1},"3":{"1632":1,"1639":2}}],["speakerquestionanswers",{"3":{"1310":1,"1350":3,"1359":4,"1369":1,"1436":1,"1630":2,"1632":2,"1639":1}}],["speakerquestionanswerconfiguration",{"2":{"1362":1},"3":{"1199":1,"1203":1,"1207":2,"1350":3,"1362":2,"1369":4}}],["speakerquestionanswerchanged",{"2":{"1345":1,"1631":1,"1632":1,"1634":1},"3":{"1199":3,"1203":1,"1207":2,"1345":1,"1350":6,"1631":1,"1632":3,"1634":2}}],["speakerquestionanswertests",{"3":{"1199":1,"1207":2,"1350":1}}],["speakerquestionanswerdtomappertests",{"3":{"1199":1,"1207":2,"1359":2}}],["speakerquestionanswerdtomapper",{"3":{"1199":7,"1203":1,"1207":2,"1350":3,"1359":7}}],["speakerquestionanswerdto",{"2":{"1347":1},"3":{"1199":3,"1203":1,"1207":2,"1347":1,"1350":5,"1359":1}}],["speakerquestionanswer",{"2":{"1342":1,"1363":1,"1638":1,"1640":1},"3":{"1198":1,"1199":10,"1203":1,"1207":2,"1342":2,"1350":12,"1359":6,"1363":1,"1369":4,"1630":1,"1631":3,"1632":1,"1634":2,"1635":3,"1637":1,"1638":5,"1639":7,"1640":5}}],["speakerlastnamerules",{"3":{"1199":3,"1203":1,"1207":1,"1359":3}}],["speakerlocalities",{"3":{"1350":3,"1395":1}}],["speakerlocality",{"3":{"1350":2,"1395":1}}],["speakerlocalityhelpertests",{"3":{"1199":1,"1207":2,"1359":5}}],["speakerlocalityhelper",{"3":{"1199":3,"1203":1,"1207":3,"1347":1,"1350":6,"1359":18,"1496":2}}],["speakerlocalitysummary",{"3":{"1199":4,"1203":1,"1207":1,"1347":1,"1350":3,"1359":2}}],["speakerlookupservicetests",{"3":{"1199":1,"1207":2}}],["speakerlookupservice",{"2":{"1387":1},"3":{"1199":2,"1203":1,"1207":2,"1324":4,"1380":1,"1382":1,"1387":2,"1395":12}}],["speakerlist",{"3":{"1199":1,"1203":1,"1207":2,"1324":2,"1350":1,"1359":1,"1383":2,"1395":13}}],["speakerlistpage",{"3":{"1199":2,"1207":2}}],["speakerlinkbrokerflowtests",{"3":{"1199":1,"1207":2}}],["speakerlinkscontrollertests",{"3":{"1199":1,"1207":2,"1487":1}}],["speakerlinkscontroller",{"3":{"1199":2,"1203":1,"1207":2,"1350":5,"1359":14,"1371":2,"1373":2,"1380":8,"1395":4,"1436":1,"1496":1,"1535":1}}],["speakerlinked",{"3":{"1438":1,"1490":1,"1611":1}}],["speakerlinkedinurlrules",{"3":{"1199":2,"1203":1,"1207":1,"1271":1,"1359":2}}],["speakerlinkedtouserhandlertests",{"3":{"1199":1,"1207":3}}],["speakerlinkedtouserhandler",{"2":{"1413":1,"1611":1},"3":{"1199":2,"1203":1,"1207":2,"1259":2,"1350":1,"1413":2,"1415":9,"1524":1,"1526":1,"1534":1,"1611":1}}],["speakerlinkedtouser",{"2":{"195":1,"1341":1,"1355":1,"1451":1,"1612":1},"3":{"195":1,"1199":10,"1203":1,"1207":2,"1237":2,"1259":1,"1341":1,"1349":4,"1350":16,"1355":2,"1359":5,"1395":1,"1413":1,"1415":6,"1436":2,"1438":1,"1451":1,"1467":1,"1534":1,"1612":1}}],["speakerinvariantstests",{"3":{"1199":1,"1207":2,"1350":1}}],["speakerinvariants",{"2":{"1344":1,"1810":1},"3":{"1199":18,"1203":1,"1207":2,"1237":1,"1286":1,"1344":3,"1350":15,"1359":42,"1369":5,"1496":1,"1810":1}}],["speakerinfo",{"3":{"1199":28,"1203":2,"1207":2,"1359":9,"1369":1,"1387":1,"1395":15}}],["speakerids",{"3":{"1350":1,"1359":4,"1380":2,"1402":5}}],["speakerid",{"2":{"1386":2,"1813":2},"3":{"1198":2,"1199":9,"1207":2,"1247":1,"1350":24,"1359":50,"1362":1,"1369":14,"1375":1,"1380":16,"1386":2,"1388":1,"1395":46,"1415":1,"1438":1,"1488":1,"1631":3,"1632":4,"1634":2,"1635":4,"1640":1,"1641":7,"1813":2}}],["speakeridentifiertype",{"2":{"470":1,"472":1,"475":1,"476":1,"477":1,"478":1,"798":1,"803":1,"804":1,"805":1,"1049":1,"1054":1},"3":{"470":2,"472":1,"475":2,"476":2,"477":1,"478":2,"798":1,"803":1,"804":1,"805":1,"1049":1,"1054":1,"1212":1,"1286":1,"1350":22,"1359":45,"1380":4,"1395":20,"1402":7,"1404":1,"1415":2}}],["speakerunlinked",{"3":{"1438":1}}],["speakerunlinkedfromuserhandlertests",{"3":{"1199":1,"1207":3}}],["speakerunlinkedfromuserhandler",{"2":{"1413":1,"1611":1},"3":{"1199":2,"1203":1,"1207":2,"1259":2,"1350":1,"1359":1,"1413":1,"1415":4,"1526":1,"1534":1,"1611":1}}],["speakerunlinkedfromuser",{"2":{"195":1,"1341":1,"1349":1,"1355":1,"1413":1,"1451":1,"1612":1},"3":{"195":1,"1199":8,"1203":1,"1207":2,"1237":2,"1259":1,"1341":1,"1349":2,"1350":14,"1355":1,"1359":9,"1395":1,"1413":1,"1415":2,"1436":1,"1438":1,"1451":1,"1467":1,"1612":1}}],["speakerusersearch",{"3":{"1199":2,"1203":1,"1207":2,"1385":2,"1395":5,"1415":2}}],["speakerupdateauthtests",{"3":{"1199":1,"1207":2}}],["speakerupdateapplier",{"3":{"926":1,"1199":3,"1203":1,"1207":2,"1270":4,"1354":2,"1359":11}}],["speakerupdaterequestvalidatortests",{"3":{"1199":1,"1207":2}}],["speakerupdaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1359":9}}],["speakerupdaterequest",{"3":{"1199":7,"1203":1,"1207":2,"1359":15,"1380":1}}],["speakerclaims",{"3":{"1406":1,"1415":1}}],["speakercount",{"3":{"1350":1}}],["speakercommandvalidatortests",{"3":{"1207":1,"1359":2}}],["speakerconfiguration",{"2":{"1361":1},"3":{"657":1,"1199":1,"1203":1,"1207":2,"1286":3,"1350":8,"1359":1,"1361":1,"1362":3,"1369":4}}],["speakercreated",{"3":{"1631":1,"1634":1}}],["speakercreaterequestvalidatortests",{"3":{"1199":1,"1207":2,"1359":2}}],["speakercreaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1359":11}}],["speakercreaterequestmappertests",{"3":{"1199":1,"1207":2,"1438":1}}],["speakercreaterequestmapper",{"3":{"1199":2,"1203":1,"1207":2,"1359":15}}],["speakercreaterequest",{"3":{"1199":9,"1203":1,"1207":2,"1270":1,"1359":34,"1380":1}}],["speakercreate",{"3":{"1199":1,"1203":1,"1207":2,"1395":20}}],["speakercreatemodel",{"3":{"1199":3,"1203":1,"1207":2,"1395":6}}],["speakercreatepage",{"3":{"1199":5,"1207":2}}],["speakercategoryitemid",{"3":{"1350":1,"1359":5}}],["speakercategoryitemidentifiertype",{"3":{"1350":4,"1359":4,"1395":4}}],["speakercategoryitemconfiguration",{"2":{"1362":1},"3":{"1199":1,"1203":1,"1207":2,"1350":1,"1359":1,"1362":2,"1369":6}}],["speakercategoryitemchanged",{"2":{"1345":1,"1631":1,"1634":1},"3":{"1199":4,"1203":1,"1207":2,"1345":1,"1350":6,"1359":2,"1631":1,"1634":1}}],["speakercategoryitemnavigationpopulatortests",{"3":{"1199":1,"1207":2,"1359":2}}],["speakercategoryitemnavigationpopulator",{"3":{"1199":2,"1203":1,"1207":2,"1310":1,"1359":9}}],["speakercategoryitemtests",{"3":{"1199":1,"1207":2,"1350":1}}],["speakercategoryitems",{"2":{"1363":1},"3":{"1310":3,"1324":1,"1350":4,"1359":12,"1363":1,"1369":2,"1380":2,"1395":1,"1436":1,"1447":1,"1630":2,"1632":3,"1635":1,"1639":2}}],["speakercategoryitemscontrollertests",{"3":{"1199":1,"1207":2}}],["speakercategoryitemscontroller",{"2":{"1371":1,"1372":1},"3":{"1199":3,"1203":1,"1207":3,"1311":2,"1350":1,"1359":23,"1371":2,"1372":3,"1373":2,"1374":1,"1380":17}}],["speakercategoryitemspaneltests",{"3":{"1199":1,"1207":3}}],["speakercategoryitemspanel",{"2":{"1385":1},"3":{"1199":2,"1203":1,"1207":2,"1385":2,"1395":12}}],["speakercategoryitemservice",{"2":{"1386":1},"3":{"1199":2,"1203":1,"1207":1,"1324":1,"1386":1,"1395":4}}],["speakercategoryitemdtomappertests",{"3":{"1199":1,"1207":2,"1359":2}}],["speakercategoryitemdtomapper",{"3":{"1199":9,"1203":1,"1207":2,"1350":3,"1359":16}}],["speakercategoryitemdto",{"3":{"1199":10,"1203":1,"1207":2,"1347":1,"1350":7,"1359":6,"1380":1,"1395":4}}],["speakercategoryitem",{"2":{"1342":1},"3":{"1198":1,"1199":16,"1203":1,"1207":2,"1237":2,"1310":1,"1342":3,"1350":8,"1359":25,"1369":3,"1380":1,"1395":1,"1630":1,"1631":1,"1632":1,"1634":2,"1635":2,"1637":1,"1638":3,"1639":5}}],["speakerchanged",{"2":{"1638":1},"3":{"782":2,"1199":5,"1203":1,"1207":2,"1259":1,"1345":3,"1350":13,"1355":1,"1359":4,"1631":1,"1634":3,"1638":3,"1639":1}}],["speakerdeleted",{"3":{"1359":1}}],["speakerdeletedhandlertests",{"3":{"1199":1,"1207":3}}],["speakerdeletedhandler",{"2":{"780":1,"782":1,"785":1},"3":{"780":1,"782":2,"784":1,"785":1,"1199":3,"1203":1,"1207":2,"1237":1,"1350":7,"1355":4,"1359":3}}],["speakerdetails",{"3":{"1395":2}}],["speakerdetailtests",{"3":{"1199":1,"1207":2,"1395":2}}],["speakerdetail",{"3":{"1199":2,"1203":1,"1207":2,"1237":1,"1350":1,"1384":2,"1395":47,"1415":2,"1496":1,"1526":1,"1531":1,"1535":1,"1627":1}}],["speakerdetailpage",{"3":{"1199":4,"1207":2}}],["speakerdetaillookupservice",{"2":{"1387":1},"3":{"1199":1,"1203":1,"1207":2,"1387":2,"1395":18}}],["speakerdetaillookups",{"2":{"1387":1},"3":{"1199":5,"1203":1,"1207":1,"1387":1,"1395":8}}],["speakerdtotests",{"3":{"1199":1,"1207":2}}],["speakerdto",{"3":{"1199":30,"1203":1,"1207":3,"1347":2,"1350":17,"1359":22,"1380":1,"1382":1,"1395":20}}],["speakerdtomappertests",{"3":{"1199":1,"1207":2,"1359":2}}],["speakerdtomapper",{"2":{"5":1,"6":1},"3":{"5":1,"6":1,"657":1,"1199":8,"1203":1,"1207":2,"1350":9,"1353":3,"1359":27}}],["speakerdashboardtests",{"3":{"1199":2,"1207":4}}],["speakerdashboardpage",{"3":{"1199":3,"1207":2}}],["speakerdashboardservicetests",{"3":{"1199":1,"1207":2,"1395":1,"1436":1}}],["speakerdashboardservice",{"2":{"1388":1},"3":{"881":1,"1199":2,"1203":1,"1207":2,"1300":2,"1350":3,"1388":3,"1395":23}}],["speakerdashboard",{"2":{"1388":1},"3":{"556":1,"1119":1,"1199":2,"1203":1,"1207":2,"1350":3,"1388":2,"1395":20,"1531":1}}],["speakerscache",{"3":{"1350":1,"1359":1,"1379":1,"1380":2}}],["speakerscontrollertests",{"3":{"1199":1,"1207":2}}],["speakerscontroller",{"3":{"550":1,"552":1,"743":1,"1199":3,"1203":1,"1207":2,"1247":4,"1311":6,"1350":7,"1359":43,"1371":2,"1372":3,"1373":7,"1375":5,"1380":19,"1395":1,"1396":2,"1436":2,"1496":1,"1526":1}}],["speakerssynced",{"3":{"1350":1,"1359":1}}],["speakersmanage",{"2":{"1373":1},"3":{"1350":3,"1359":7,"1373":3,"1380":10,"1395":1}}],["speakersyncstrategytests",{"3":{"1199":1,"1207":2,"1359":1}}],["speakersyncstrategy",{"3":{"1199":3,"1203":1,"1207":2,"1350":8,"1358":1,"1359":16,"1526":1}}],["speakersexcept",{"3":{"1395":1}}],["speakerselfservicetests",{"3":{"1199":1,"1207":2,"1526":2}}],["speakersessionassettests",{"3":{"1199":1,"1207":2}}],["speakersessionscontrollertests",{"3":{"1199":1,"1207":2,"1487":1}}],["speakersessionscontroller",{"2":{"1371":1,"1375":1},"3":{"1199":2,"1203":1,"1207":2,"1350":4,"1371":2,"1375":3,"1380":6,"1396":2,"1436":1,"1496":1}}],["speakersessionsummary",{"3":{"1199":7,"1203":1,"1207":1,"1347":1,"1350":3,"1359":3,"1395":1}}],["speakersessionoverlapdto",{"2":{"1347":1},"3":{"1199":8,"1203":1,"1207":4,"1347":1,"1350":9,"1359":3,"1380":1}}],["speakerservice",{"3":{"881":1,"1199":7,"1203":1,"1207":2,"1382":4,"1395":8}}],["speakers",{"2":{"1350":1,"1380":1,"1395":1},"3":{"0":2,"186":1,"353":2,"470":1,"550":1,"554":1,"556":2,"657":3,"743":1,"782":1,"881":2,"926":1,"1004":6,"1018":1,"1116":4,"1118":1,"1168":1,"1202":1,"1203":94,"1207":296,"1212":1,"1237":6,"1238":1,"1242":2,"1247":3,"1259":2,"1270":8,"1271":3,"1286":1,"1300":1,"1310":7,"1311":4,"1324":6,"1340":1,"1342":3,"1343":1,"1345":1,"1346":1,"1347":5,"1348":1,"1349":3,"1350":87,"1351":4,"1352":3,"1353":4,"1354":2,"1355":5,"1357":2,"1358":4,"1359":289,"1363":1,"1364":3,"1365":1,"1367":1,"1369":19,"1371":3,"1373":1,"1380":36,"1381":1,"1382":4,"1383":5,"1384":3,"1385":2,"1386":1,"1387":4,"1388":7,"1389":1,"1391":1,"1392":1,"1395":115,"1396":3,"1402":11,"1413":1,"1415":7,"1416":2,"1433":1,"1435":2,"1436":2,"1437":1,"1438":8,"1441":1,"1447":1,"1458":1,"1492":1,"1496":2,"1526":1,"1531":1,"1534":1,"1535":1,"1608":1,"1621":1,"1625":1,"1626":1,"1627":4,"1629":8,"1630":7,"1631":6,"1632":16,"1635":3,"1636":5,"1637":5,"1638":4,"1639":10,"1640":4,"1641":14,"1815":1,"1863":1,"1866":1,"2070":1,"2110":2,"2216":1,"2238":1,"2240":2,"2241":1}}],["speaker",{"0":{"1624":1,"1626":1},"1":{"1113":1,"1114":1,"1115":1,"1116":1,"1117":1,"1118":1,"1119":1,"1120":1},"2":{"1355":1,"1621":1,"1626":1,"1635":1,"1636":1,"1640":1,"1641":1,"1983":1},"3":{"0":3,"340":1,"348":1,"350":2,"351":1,"353":5,"387":2,"443":1,"470":1,"545":1,"655":1,"657":4,"888":2,"926":1,"1017":1,"1018":3,"1113":1,"1115":4,"1116":7,"1117":1,"1118":2,"1168":2,"1192":1,"1198":2,"1199":62,"1201":1,"1202":1,"1203":2,"1207":19,"1212":1,"1237":3,"1239":1,"1247":3,"1270":3,"1271":2,"1281":1,"1286":3,"1288":1,"1300":4,"1310":4,"1311":1,"1312":1,"1323":1,"1324":4,"1340":3,"1342":13,"1343":8,"1345":2,"1347":5,"1348":2,"1349":2,"1350":186,"1351":2,"1352":2,"1353":4,"1354":1,"1355":9,"1357":7,"1359":494,"1361":1,"1362":4,"1363":1,"1364":1,"1366":3,"1369":33,"1371":2,"1373":5,"1375":3,"1376":1,"1377":1,"1378":2,"1379":1,"1380":70,"1381":1,"1382":1,"1383":2,"1385":1,"1386":2,"1387":1,"1388":8,"1389":7,"1390":1,"1391":1,"1392":1,"1395":203,"1396":17,"1397":2,"1402":38,"1403":1,"1405":1,"1406":3,"1412":1,"1413":4,"1415":28,"1416":2,"1424":1,"1436":11,"1437":1,"1438":23,"1467":5,"1487":4,"1489":2,"1490":1,"1492":1,"1496":6,"1498":1,"1524":1,"1526":4,"1531":1,"1534":2,"1535":2,"1608":1,"1611":4,"1617":1,"1621":8,"1624":1,"1626":2,"1627":15,"1629":4,"1630":37,"1631":14,"1632":58,"1633":2,"1634":7,"1635":11,"1636":4,"1637":12,"1638":16,"1639":38,"1640":12,"1641":77,"1805":1,"1813":1,"1815":4,"1869":2,"1922":2,"1955":1,"1977":1,"1983":1,"2110":1,"2113":1,"2126":1,"2127":1,"2216":1,"2232":1}}],["specparser",{"3":{"2220":1}}],["speculatively",{"3":{"1455":1}}],["spectrum",{"3":{"1436":1,"1492":1,"2004":1}}],["spectestentity",{"3":{"1199":28,"1207":1}}],["spectestchild",{"3":{"1199":6,"1207":1}}],["spectestmap",{"3":{"1198":1,"1199":2,"1207":1,"1436":8}}],["specfactory",{"3":{"1300":1}}],["spec2",{"3":{"1247":3}}],["spec1",{"3":{"1247":3}}],["specs",{"3":{"319":1,"607":2,"609":3,"976":1,"1192":1,"1247":1,"1339":1,"1431":1,"1436":3,"1467":3,"1476":3,"1496":1,"1546":1,"2046":3}}],["spec",{"3":{"0":3,"318":2,"320":3,"549":1,"607":4,"609":15,"610":1,"611":1,"1212":1,"1247":6,"1286":2,"1311":1,"1336":1,"1339":2,"1359":3,"1415":1,"1417":2,"1431":1,"1436":3,"1467":6,"1476":1,"1496":1,"1500":1,"1534":1,"1577":1,"1591":1,"1638":1,"1639":10,"1815":2,"2158":2,"2239":1}}],["specimen",{"3":{"1323":1,"1340":1}}],["specifying",{"3":{"1639":1}}],["specifykind",{"3":{"1286":2,"1309":1,"1312":1,"1415":1}}],["specify",{"3":{"1237":1,"1271":1,"1359":6,"1639":2}}],["specified",{"3":{"861":1,"885":1,"1359":1,"1631":1,"1639":3,"1640":1}}],["specifies",{"3":{"859":1,"880":1,"1286":1,"1417":1,"1429":1,"1640":1}}],["specificity",{"3":{"1311":1,"1577":1}}],["specifically",{"3":{"665":1,"702":1,"1229":1,"1247":1,"1286":1,"1311":3,"1312":1,"1323":2,"1324":3,"1339":2,"1350":1,"1359":2,"1395":2,"1396":3,"1415":2,"1417":1,"1427":1,"1436":8,"1455":1,"1467":1,"1490":1,"1727":1,"1840":1,"1960":1,"2175":1}}],["specificationprefix",{"3":{"1286":3}}],["specificationtestcontext",{"3":{"1207":3}}],["specificationtestdbcontext",{"3":{"1199":9,"1207":1}}],["specificationtests",{"3":{"1199":1,"1207":5,"1247":5}}],["specificationadditionaltests",{"3":{"1199":1,"1207":5}}],["specificationhonoringqueryservice",{"3":{"1199":2,"1207":1}}],["specificationconventiontests",{"3":{"1199":1,"1207":2,"1247":1,"1436":5,"1489":2,"1526":3}}],["specificationconventiontestsbase",{"2":{"1654":1},"3":{"1199":2,"1207":2,"1239":3,"1247":1,"1436":7,"1496":1,"1654":1}}],["specificationcompositiontests",{"3":{"1199":1,"1207":7,"1247":6,"1438":2}}],["specificationcomposer",{"2":{"1239":1},"3":{"545":1,"549":1,"1199":4,"1203":1,"1207":1,"1239":1,"1247":6,"1496":1}}],["specificationentitytype",{"3":{"1436":1}}],["specificationevaluatortests",{"3":{"1199":1,"1207":8,"1247":1,"1286":1,"1438":1}}],["specificationevaluator",{"2":{"1272":1},"3":{"1199":4,"1203":1,"1207":2,"1240":8,"1244":1,"1246":1,"1247":8,"1272":1,"1278":2,"1286":26,"1438":1,"1496":1}}],["specificationextensions",{"2":{"549":1,"550":1,"1239":1},"3":{"319":1,"326":1,"549":2,"550":1,"1199":1,"1203":1,"1207":2,"1239":6,"1247":5,"1359":1,"1380":3,"1496":1}}],["specificationbenchmarks",{"3":{"591":2,"592":1,"1199":1,"1207":5,"1247":1,"1436":2,"1438":2,"1496":1,"1577":1}}],["specificationfitnesstests",{"2":{"137":1,"139":1},"3":{"137":1,"139":1,"1198":1,"1199":2,"1207":9,"1436":28,"1489":2,"1496":1,"1577":1}}],["specificationsdonotnavigatetootherentities",{"2":{"168":1,"171":1,"548":1,"1247":1,"1438":1,"1489":1,"1496":1,"1577":1,"1859":1},"3":{"168":1,"171":1,"548":1,"1239":1,"1247":2,"1359":1,"1436":5,"1438":1,"1489":1,"1496":1,"1577":1,"1859":1}}],["specifications",{"0":{"1278":1,"1353":1,"1640":1,"1815":1},"1":{"1238":1,"1239":1,"1240":1,"1241":1,"1242":1,"1243":1,"1244":1,"1245":1,"1246":1,"1247":1,"1601":1,"1602":1,"1603":1,"1604":1,"1628":1,"1629":1,"1630":1,"1631":1,"1632":1,"1633":1,"1634":1,"1635":1,"1636":1,"1637":1,"1638":1,"1639":1,"1640":1,"1641":1,"1813":1,"1814":1,"1815":1,"1816":1,"1817":1},"2":{"168":1},"3":{"132":1,"168":3,"171":2,"318":3,"322":1,"543":1,"545":8,"547":1,"549":3,"592":1,"743":1,"1192":1,"1202":4,"1203":18,"1207":112,"1237":1,"1238":2,"1239":11,"1240":1,"1241":1,"1247":46,"1259":1,"1286":4,"1350":3,"1351":1,"1353":3,"1359":16,"1380":1,"1396":2,"1431":5,"1436":23,"1437":1,"1438":6,"1487":1,"1489":2,"1496":3,"1526":2,"1577":3,"1586":1,"1591":2,"1601":2,"1604":1,"1628":1,"1639":1,"1660":1,"1662":1,"1779":1,"1812":1,"1813":1,"1815":2,"1816":2,"1817":2,"1994":2,"2206":1,"2220":1,"2227":2,"2233":2}}],["specification",{"0":{"1239":1,"1639":1},"1":{"542":1,"543":1,"544":1,"545":1,"546":1,"547":1,"548":1,"549":1,"550":1,"551":1,"552":1,"1762":1,"1763":1,"1764":1,"1765":1,"1766":1,"1767":1,"1768":1,"1769":1,"1770":1,"1771":1,"1772":1,"1773":1,"1774":1,"1775":1,"1776":1,"1777":1},"2":{"319":1,"321":1,"547":1,"550":2,"592":1,"1240":2,"1246":1,"1759":1,"1997":1},"3":{"0":7,"168":1,"318":9,"319":5,"321":2,"324":3,"325":1,"542":1,"543":5,"544":1,"545":12,"546":1,"547":4,"548":3,"549":18,"550":6,"551":4,"552":1,"591":1,"592":3,"595":1,"743":1,"798":1,"853":1,"1132":1,"1137":1,"1192":1,"1199":69,"1202":1,"1203":2,"1207":7,"1212":1,"1238":2,"1239":25,"1240":7,"1243":2,"1244":1,"1245":1,"1246":2,"1247":92,"1272":1,"1278":1,"1284":1,"1286":60,"1297":1,"1300":11,"1310":1,"1311":18,"1323":2,"1339":2,"1350":1,"1353":4,"1359":94,"1372":2,"1373":1,"1375":2,"1380":35,"1395":2,"1431":1,"1436":43,"1438":4,"1489":2,"1496":3,"1525":1,"1526":2,"1539":2,"1576":1,"1577":5,"1586":2,"1591":1,"1600":1,"1603":1,"1631":3,"1632":1,"1637":1,"1662":2,"1668":1,"1671":1,"1749":2,"1759":1,"1762":1,"1777":1,"1813":2,"1814":1,"1815":17,"1817":2,"1859":2,"1860":1,"1923":1,"1994":8,"1995":4,"1996":3,"1997":2,"2047":1,"2183":1,"2232":2}}],["specifics",{"0":{"1362":1},"3":{"164":1,"166":1,"987":1,"1350":1,"1359":1,"1369":1,"1417":1,"1467":5}}],["specific",{"3":{"0":4,"6":2,"93":1,"94":1,"109":1,"111":2,"137":1,"166":1,"175":1,"201":1,"224":1,"236":1,"265":1,"319":1,"332":1,"351":1,"381":1,"397":1,"399":1,"498":1,"499":1,"500":1,"506":1,"508":1,"530":1,"558":1,"572":1,"618":1,"627":1,"633":2,"642":1,"649":1,"650":1,"666":1,"681":1,"683":1,"697":1,"724":4,"725":1,"727":1,"728":1,"737":1,"743":1,"766":1,"782":1,"815":1,"828":1,"854":1,"863":1,"876":1,"882":1,"888":1,"890":1,"916":1,"939":1,"954":2,"959":1,"963":1,"987":1,"1016":1,"1020":1,"1041":1,"1066":1,"1067":1,"1068":1,"1089":1,"1093":1,"1096":1,"1132":1,"1144":1,"1174":1,"1175":1,"1179":1,"1190":1,"1201":1,"1211":1,"1212":1,"1214":1,"1229":4,"1234":1,"1237":1,"1246":1,"1247":3,"1258":1,"1259":2,"1265":1,"1270":11,"1271":2,"1272":1,"1273":1,"1280":1,"1284":1,"1286":29,"1289":1,"1291":2,"1296":1,"1298":1,"1300":19,"1301":1,"1302":1,"1309":11,"1311":10,"1321":1,"1322":1,"1323":18,"1324":28,"1328":1,"1338":1,"1339":9,"1344":2,"1350":7,"1352":1,"1359":29,"1366":1,"1369":2,"1371":1,"1380":14,"1387":1,"1395":21,"1396":21,"1402":6,"1406":1,"1410":1,"1415":23,"1416":8,"1417":11,"1424":1,"1427":2,"1429":2,"1432":1,"1433":1,"1436":38,"1438":6,"1440":1,"1441":1,"1443":1,"1445":1,"1447":1,"1449":2,"1451":1,"1453":1,"1455":2,"1456":1,"1457":1,"1460":1,"1467":1,"1474":1,"1475":2,"1488":4,"1490":3,"1496":2,"1525":1,"1538":1,"1554":1,"1577":1,"1591":2,"1596":1,"1630":3,"1631":1,"1632":3,"1636":1,"1637":5,"1639":5,"1640":1,"1641":6,"1645":1,"1656":1,"1658":1,"1666":1,"1667":1,"1670":1,"1685":1,"1686":1,"1727":2,"1749":1,"1764":3,"1769":1,"1771":1,"1777":1,"1816":1,"1819":1,"1852":1,"1853":1,"1882":1,"1896":1,"1897":1,"1927":1,"1929":1,"1977":1,"1983":1,"1990":1,"1992":1,"2006":2,"2030":1,"2033":1,"2037":1,"2059":2,"2067":1,"2073":1,"2088":1,"2107":1,"2123":1,"2137":1,"2145":1,"2154":1,"2156":1,"2159":1,"2168":1,"2183":1,"2189":1,"2211":1,"2232":4}}],["specially",{"3":{"1312":1}}],["specialcharacter",{"3":{"1271":1,"1300":1}}],["specialization",{"3":{"1359":8,"1395":2,"1416":1}}],["specializations",{"3":{"1354":1,"1408":1,"1410":1}}],["specialize",{"3":{"1359":1}}],["specializes",{"3":{"1339":1,"1637":1}}],["specialized",{"3":{"1286":2,"1350":2,"1359":3,"1395":2,"1396":3,"1416":1}}],["specializing",{"3":{"1229":1,"1359":1}}],["specialised",{"3":{"940":1,"1271":1,"1693":1}}],["specialist",{"3":{"620":1}}],["specialprice",{"2":{"658":1,"660":1},"3":{"658":1,"660":1}}],["special",{"3":{"0":2,"109":1,"135":2,"227":1,"389":1,"741":1,"820":1,"982":1,"1025":1,"1026":1,"1028":1,"1212":1,"1237":1,"1259":1,"1271":1,"1286":1,"1289":2,"1300":1,"1311":3,"1312":1,"1324":3,"1339":1,"1342":2,"1350":8,"1359":6,"1364":1,"1369":3,"1380":1,"1395":3,"1396":1,"1415":3,"1416":1,"1417":1,"1436":5,"1438":1,"1455":1,"1467":1,"1629":1,"1631":3,"1638":1,"1641":1,"1748":1,"1764":1,"1767":6,"1768":1,"1829":1,"1970":1,"2000":1,"2017":1}}],["spaghetti",{"1":{"1813":1,"1814":1,"1815":1,"1816":1,"1817":1},"3":{"1779":1,"1812":1,"1813":2,"2099":1,"2206":1,"2227":1}}],["spawning",{"3":{"1443":1}}],["spacing",{"3":{"890":1,"1324":1,"1557":2,"1577":1}}],["spaces",{"3":{"23":1,"972":1,"1286":1,"1324":1,"1436":3}}],["space",{"3":{"0":1,"157":1,"607":2,"609":2,"725":1,"1060":1,"1247":1,"1286":2,"1300":2,"1311":2,"1324":4,"1339":1,"1359":5,"1369":1,"1395":3,"1415":1,"1417":1,"1455":1,"1459":1,"1465":1,"1637":1,"1908":1,"1927":1}}],["sparingly",{"3":{"1270":2}}],["spared",{"3":{"1876":1}}],["spares",{"3":{"1270":2,"1380":1}}],["spare",{"3":{"790":1,"862":1,"1339":1,"1436":1}}],["sparse",{"0":{"1242":1},"3":{"0":1,"330":1,"332":1,"337":1,"591":1,"740":1,"1238":2,"1247":4,"1286":1,"1362":1,"1415":1,"1438":1,"1666":1,"1720":1,"1815":1,"1817":1,"1986":1,"1988":1,"1991":1,"2232":1}}],["spam",{"3":{"280":1,"1339":1,"1399":1,"1402":3,"1417":1,"1999":1,"2157":1}}],["spa",{"3":{"174":1,"1300":2,"1488":1,"1972":1,"1973":1,"1998":1,"2153":1}}],["spanid",{"3":{"1074":1,"1253":1,"1259":3,"1286":2,"2186":1}}],["spanish",{"3":{"0":1,"264":1,"265":2,"267":1,"856":1,"1311":3,"1324":5,"1380":1,"1389":1,"1395":2,"1396":1,"1436":11,"1438":1,"1577":3,"1582":1,"1583":1,"1591":1,"1595":1,"1596":2,"1653":1,"1669":1,"1685":2,"1784":1,"2080":1,"2081":2,"2085":4,"2086":1,"2216":1,"2223":1,"2232":1,"2242":1,"2245":1}}],["spanning",{"3":{"168":1,"760":1,"810":1,"1201":1,"1350":1,"1359":1,"1369":2,"1395":1,"1424":1,"1436":1,"1438":1,"1496":3,"1541":1,"1547":1,"1577":1,"1637":1,"2218":1,"2220":1}}],["span",{"2":{"1558":1},"3":{"47":1,"49":1,"115":1,"295":1,"395":6,"397":9,"398":1,"399":3,"400":1,"402":4,"404":1,"405":2,"406":3,"409":1,"520":1,"635":1,"821":1,"1042":1,"1091":1,"1093":1,"1102":1,"1212":1,"1229":2,"1247":2,"1251":1,"1259":3,"1273":1,"1286":10,"1301":2,"1309":1,"1311":5,"1324":4,"1333":4,"1339":39,"1350":1,"1359":7,"1415":4,"1421":1,"1425":2,"1427":1,"1436":1,"1443":7,"1450":1,"1496":3,"1526":1,"1558":1,"1565":1,"1572":1,"1575":1,"1577":4,"1583":1,"1591":2,"1631":1,"1664":1,"1819":1,"1843":1,"1909":1,"2010":3,"2154":1,"2157":10,"2159":3,"2160":1,"2179":1,"2183":1,"2192":1,"2213":1,"2215":1,"2232":1}}],["spans",{"0":{"2157":1},"3":{"0":1,"139":1,"157":1,"161":1,"230":1,"359":1,"396":1,"397":2,"398":1,"399":2,"402":2,"405":2,"466":1,"535":1,"609":1,"674":2,"676":1,"761":1,"803":1,"926":1,"946":1,"1044":1,"1089":1,"1124":1,"1201":1,"1212":1,"1270":2,"1286":6,"1300":1,"1302":1,"1320":1,"1323":1,"1339":14,"1341":1,"1346":1,"1350":2,"1359":8,"1380":1,"1395":1,"1396":1,"1417":1,"1427":1,"1431":1,"1436":2,"1438":2,"1443":6,"1454":1,"1467":3,"1496":1,"1511":1,"1550":1,"1577":1,"1668":1,"1670":1,"1677":2,"1789":1,"1841":1,"1848":1,"1852":1,"1853":1,"1858":1,"1888":1,"1892":1,"1902":1,"1909":1,"1912":1,"2000":1,"2010":3,"2012":1,"2014":2,"2142":1,"2154":2,"2156":2,"2157":4,"2159":1,"2160":2,"2161":1,"2192":1,"2220":1,"2227":1}}],["sms",{"3":{"1417":1,"2220":1}}],["sm",{"3":{"1324":1,"1591":1,"1712":1,"2073":1}}],["smuggling",{"3":{"1312":1,"1324":1}}],["smuggled",{"3":{"926":1,"1265":1,"1270":1,"1323":1,"1395":1}}],["smuggle",{"3":{"0":1,"1247":1,"1300":1,"1323":1,"1324":1,"1359":2,"1402":2,"1436":1}}],["smearing",{"3":{"1832":1,"1834":1}}],["smear",{"3":{"1278":1}}],["smeared",{"3":{"1247":1,"1324":1,"1359":1,"1396":1,"1402":1,"1931":1}}],["smell",{"3":{"70":1,"785":1,"1247":2,"1359":1,"1436":2,"1538":1,"1828":1,"1834":1}}],["smooth",{"3":{"1324":1}}],["smooths",{"3":{"1311":1}}],["smoothing",{"3":{"178":1,"1311":1}}],["smokes",{"3":{"1446":1,"1490":1}}],["smoketests",{"2":{"1069":1},"3":{"913":4,"1066":1,"1067":2,"1069":3,"1070":1,"1199":3,"1203":3,"1206":1,"1207":9,"1328":2,"1339":9,"1449":2,"1487":1}}],["smoke",{"0":{"1430":1,"1449":1},"3":{"0":8,"150":2,"373":1,"563":1,"564":1,"589":1,"591":1,"594":1,"620":1,"624":4,"626":4,"629":1,"631":2,"633":8,"635":3,"640":2,"642":1,"699":1,"755":2,"757":4,"758":2,"759":2,"762":1,"912":2,"913":2,"914":2,"916":1,"917":2,"1067":1,"1069":2,"1073":1,"1075":1,"1192":1,"1212":4,"1213":1,"1339":8,"1428":1,"1434":1,"1436":7,"1437":2,"1438":10,"1449":1,"1453":2,"1455":12,"1460":2,"1461":3,"1465":1,"1467":3,"1469":2,"1474":1,"1482":1,"1486":3,"1487":3,"1488":1,"1490":5,"1492":10,"1493":1,"1494":1,"1496":3,"1497":2,"1524":1,"1526":6,"1531":3,"1535":1,"1575":1,"1577":3,"1581":1,"1582":1,"1583":1,"1585":1,"1588":1,"1589":1,"1590":2,"1591":6,"1600":2,"1611":1,"1657":1,"1661":1,"1696":1,"1706":1,"1716":1,"1727":1,"1731":1,"1923":2,"2038":1,"2056":1,"2059":1,"2079":1,"2089":1,"2232":2}}],["smallness",{"3":{"2172":1}}],["smallestscreensize",{"3":{"1416":1}}],["smallest",{"3":{"414":1,"666":1,"699":1,"979":1,"1232":1,"1237":1,"1247":1,"1269":1,"1270":1,"1286":3,"1323":1,"1324":5,"1339":1,"1350":1,"1352":1,"1359":15,"1369":2,"1395":9,"1396":3,"1402":3,"1415":2,"1417":4,"1436":10,"1438":1,"1467":1,"1717":1,"1727":1,"1765":1,"1770":1,"1851":1,"1938":1,"2074":1}}],["smaller",{"3":{"409":1,"545":1,"547":1,"549":1,"593":1,"775":1,"827":1,"840":1,"875":1,"981":1,"1091":1,"1115":1,"1168":1,"1170":1,"1201":1,"1281":1,"1283":1,"1300":1,"1311":2,"1323":2,"1324":3,"1344":1,"1350":2,"1359":2,"1395":2,"1422":1,"1427":2,"1429":2,"1436":4,"1438":1,"1467":2,"1479":1,"1480":1,"1492":1,"1923":1,"1946":1,"2011":1,"2079":1,"2158":1,"2164":1,"2172":1,"2174":1}}],["small",{"0":{"1805":1,"2117":1},"1":{"1717":1,"1718":1,"1719":1,"1720":1,"1721":1,"1722":1,"1723":1,"1724":1,"1725":1},"3":{"6":1,"60":1,"122":1,"148":1,"165":1,"201":1,"235":1,"255":1,"273":1,"280":1,"292":1,"379":1,"398":1,"413":1,"440":1,"442":1,"507":1,"520":1,"572":1,"583":1,"593":1,"633":1,"650":1,"656":1,"681":1,"684":1,"692":1,"732":1,"742":1,"765":1,"897":1,"921":1,"930":1,"932":1,"1093":1,"1134":1,"1150":3,"1152":1,"1191":1,"1222":1,"1223":1,"1229":4,"1230":2,"1233":1,"1237":1,"1241":1,"1242":1,"1247":6,"1248":1,"1252":1,"1256":1,"1257":1,"1259":6,"1260":1,"1268":1,"1270":3,"1272":1,"1284":1,"1286":5,"1290":1,"1291":1,"1292":2,"1300":13,"1301":3,"1305":1,"1309":1,"1310":3,"1311":5,"1312":1,"1313":1,"1318":1,"1320":1,"1322":3,"1323":6,"1324":29,"1325":1,"1328":1,"1337":1,"1339":6,"1347":1,"1350":13,"1352":1,"1353":1,"1354":1,"1356":1,"1359":15,"1360":1,"1369":3,"1374":1,"1380":1,"1382":1,"1385":1,"1389":1,"1395":20,"1396":22,"1398":1,"1399":1,"1402":12,"1410":1,"1411":1,"1415":10,"1416":2,"1417":8,"1427":1,"1436":13,"1438":1,"1449":1,"1455":2,"1459":1,"1460":1,"1467":2,"1478":1,"1489":1,"1492":1,"1509":1,"1526":1,"1531":1,"1534":1,"1577":1,"1582":1,"1602":1,"1611":1,"1653":1,"1661":1,"1674":1,"1675":1,"1692":1,"1705":1,"1712":1,"1717":1,"1718":1,"1726":1,"1727":2,"1792":1,"1803":1,"1805":1,"1807":1,"1826":1,"1833":1,"1834":1,"1844":1,"1880":1,"1898":1,"1915":1,"1930":1,"1932":1,"1939":1,"1942":1,"1943":1,"1947":1,"1950":1,"1958":1,"1962":1,"1965":1,"1968":1,"1972":1,"1974":1,"1985":1,"2012":1,"2026":1,"2032":1,"2042":1,"2047":1,"2055":1,"2081":1,"2086":1,"2092":1,"2103":1,"2115":1,"2116":1,"2117":1,"2118":1,"2123":3,"2127":1,"2136":1,"2174":1,"2197":1}}],["smarttoy",{"3":{"1395":1}}],["smarter",{"3":{"1311":1}}],["smart",{"0":{"1235":1},"1":{"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1},"3":{"0":4,"15":1,"17":1,"18":1,"707":2,"711":1,"961":1,"963":2,"964":2,"965":3,"966":1,"1042":1,"1043":1,"1045":1,"1049":1,"1051":2,"1212":2,"1230":1,"1237":2,"1254":2,"1259":10,"1275":1,"1282":1,"1286":16,"1317":2,"1323":3,"1324":1,"1395":3,"1436":1,"1438":1,"1467":2,"1526":1,"1555":1,"1577":1,"1586":1,"1662":1,"1665":1,"1677":1,"1841":1,"2232":1}}],["smtppassword",{"3":{"1467":1}}],["smtpemailsendertests",{"3":{"1199":1,"1207":2}}],["smtpemailsender",{"2":{"72":1,"1512":1,"1752":1,"1938":1},"3":{"72":2,"230":4,"674":2,"1199":2,"1203":1,"1207":2,"1302":5,"1309":24,"1320":2,"1323":5,"1496":2,"1512":1,"1670":1,"1752":1,"1770":1,"1932":1,"1938":1}}],["smtpclient",{"2":{"72":1,"1938":1},"3":{"72":1,"230":1,"1302":2,"1309":4,"1323":2,"1938":1}}],["smtpsettingstests",{"3":{"1199":1,"1207":1}}],["smtpsettings",{"2":{"72":1,"1302":1},"3":{"72":2,"674":2,"1199":7,"1203":1,"1207":2,"1302":1,"1309":11,"1313":1,"1320":1,"1323":12}}],["smtptransportsecuritytests",{"3":{"1108":1,"1199":1,"1207":2,"1323":1}}],["smtptransportsecurity",{"2":{"1108":1,"1302":1},"3":{"0":1,"1108":2,"1199":3,"1203":1,"1207":2,"1212":1,"1302":1,"1309":4,"1313":1,"1320":5,"1323":5,"1496":2}}],["smtp",{"3":{"0":2,"66":2,"72":2,"74":1,"230":2,"598":1,"599":1,"600":1,"673":2,"827":1,"853":1,"856":1,"1107":1,"1108":4,"1109":2,"1112":1,"1302":2,"1303":1,"1309":9,"1320":1,"1323":9,"1326":4,"1339":3,"1408":1,"1415":2,"1440":1,"1441":3,"1446":5,"1455":1,"1466":1,"1467":9,"1470":2,"1472":5,"1474":1,"1670":1,"1752":1,"1770":1,"1938":2,"2005":1,"2012":2,"2013":1,"2165":1,"2189":2}}],["shy",{"3":{"1526":1}}],["sh",{"0":{"1472":1},"3":{"1471":1,"1472":20,"1475":1,"1482":1,"1483":2}}],["shuffling",{"3":{"1396":1}}],["shuts",{"3":{"1417":1,"2066":1}}],["shut",{"3":{"1300":1,"2007":1}}],["shutting",{"3":{"1270":1}}],["shutdowntimeoutseconds",{"3":{"1436":2,"1488":1}}],["shutdownsavetimeout",{"3":{"1259":1}}],["shutdown",{"3":{"0":5,"21":2,"27":2,"235":1,"237":1,"517":1,"518":2,"536":1,"572":4,"573":1,"574":2,"819":1,"837":1,"897":1,"898":3,"1100":2,"1192":1,"1212":2,"1247":2,"1252":1,"1255":1,"1259":7,"1270":2,"1275":1,"1286":2,"1309":1,"1311":4,"1323":6,"1324":1,"1334":1,"1337":1,"1339":9,"1359":1,"1366":1,"1396":1,"1400":1,"1402":1,"1415":1,"1436":5,"1437":1,"1438":1,"1488":2,"1525":1,"1526":1,"1550":1,"1566":1,"1591":2,"1600":1,"1671":1,"1919":1,"2055":2,"2165":1,"2232":1}}],["shrug",{"3":{"1416":1,"1436":1}}],["shrugs",{"3":{"1309":1}}],["shrunken",{"3":{"2105":1}}],["shrunk",{"3":{"1395":1}}],["shrinks",{"3":{"546":1,"783":1,"1237":1,"1350":1,"1369":1,"1395":1,"1436":3,"1480":1,"1578":1,"1815":1,"2148":1}}],["shrink",{"3":{"491":1,"651":1,"1183":1,"1300":1,"1311":1,"1353":1,"1359":1,"1390":1,"1395":2,"1402":2,"1436":5,"2042":1,"2104":1}}],["shrinking",{"3":{"461":1,"1018":1,"1020":1,"1300":1,"1380":1,"1402":1}}],["shrank",{"3":{"490":1,"1415":1,"1492":1,"1496":1}}],["shredded",{"3":{"2010":1}}],["shred",{"3":{"398":1}}],["shed",{"3":{"1126":1,"1347":1,"1350":1,"1396":2,"1436":1}}],["sheds",{"3":{"819":1,"1124":1,"1324":1,"1339":1,"1436":1,"2032":1}}],["shedding",{"3":{"575":1,"831":1,"1270":1,"1402":1,"1416":2}}],["shelf",{"3":{"627":1,"1041":1,"1101":1,"1301":1,"1915":1}}],["shelling",{"3":{"1430":1}}],["shells",{"3":{"1192":1,"1202":1,"1203":1,"1212":1,"1324":1,"1370":1,"1414":1,"1416":1,"1432":1,"1436":1,"1496":1}}],["shellpagese2etests",{"2":{"616":1},"3":{"616":1,"618":1,"1199":1,"1207":2}}],["shell",{"0":{"1395":1,"2077":1},"1":{"646":1,"647":1,"648":1,"649":1,"650":1,"651":1,"652":1,"653":1,"1416":1},"2":{"1479":1},"3":{"0":6,"415":1,"616":1,"618":3,"646":1,"648":3,"649":7,"650":3,"651":3,"869":2,"914":1,"954":1,"957":1,"974":1,"1059":1,"1192":1,"1202":1,"1203":1,"1212":3,"1324":23,"1359":1,"1376":1,"1395":5,"1396":3,"1414":1,"1415":6,"1416":13,"1417":5,"1436":3,"1438":2,"1440":1,"1452":3,"1454":1,"1461":1,"1475":3,"1479":2,"1481":1,"1490":1,"1496":1,"1513":1,"1526":1,"1562":1,"1591":1,"1597":1,"1653":1,"1660":1,"1669":1,"1682":1,"1698":1,"1761":2,"2077":6,"2080":1,"2232":3}}],["sheets",{"3":{"1324":2}}],["sheet",{"3":{"412":1,"419":1,"743":1,"1324":6,"1402":1,"1417":13,"1526":1,"2116":2}}],["shirt",{"3":{"1350":3}}],["shims",{"3":{"135":1,"151":1,"1282":1,"1286":2,"1436":1,"1496":2,"1856":2,"1862":2}}],["shim",{"3":{"0":1,"166":1,"470":1,"1004":1,"1034":1,"1175":1,"1212":1,"1286":12,"1361":1,"1369":1,"1396":4,"1436":1,"1496":1,"1577":1,"1856":1,"1968":1,"2112":1}}],["shifting",{"3":{"861":1,"1286":1,"1396":1,"1436":1,"1496":2}}],["shifted",{"3":{"251":1,"323":1,"424":1,"425":1,"426":1,"490":1,"491":1,"493":1,"543":1,"589":1,"1359":1,"1496":13}}],["shifts",{"3":{"41":1,"112":1,"693":1,"860":1,"1004":1,"1269":1,"1270":1,"1348":1,"1350":1,"1359":1,"1380":1,"1396":1,"1436":2,"1496":3}}],["shift",{"2":{"1607":1,"1741":1},"3":{"0":1,"279":1,"490":1,"709":1,"1019":1,"1074":1,"1292":1,"1300":1,"1348":1,"1359":3,"1380":1,"1396":1,"1435":1,"1443":1,"1496":1,"1607":1,"1741":1}}],["shipall",{"2":{"1809":1},"3":{"1809":1}}],["shiporderhandler",{"2":{"1875":1},"3":{"1765":1,"1875":1}}],["shiporderdialog",{"2":{"1750":1,"1760":1},"3":{"1750":1,"1760":1}}],["shipmentinvariants",{"3":{"1767":3}}],["shipments",{"3":{"1755":1,"1776":2}}],["shipmenttrackingnumber",{"2":{"1749":1},"3":{"1749":1}}],["shipment",{"2":{"1755":1},"3":{"1746":2,"1749":4,"1755":4,"1760":1,"1763":1,"1764":3,"1765":4,"1766":1,"1767":5,"1768":1,"1769":1,"1770":1,"1771":2,"1776":4,"1777":1,"2110":1}}],["shipmentline",{"3":{"1727":1}}],["shipmentlineidentifiertype",{"2":{"1727":1},"3":{"1727":1}}],["shippable",{"3":{"1270":1,"1766":1}}],["shippingaddresscity",{"3":{"1286":1}}],["shippingaddressline1",{"3":{"1286":1}}],["shippingaddress",{"3":{"1286":1}}],["shipping",{"1":{"2081":1,"2082":1,"2083":1,"2084":1,"2085":1,"2086":1},"3":{"0":2,"67":1,"216":1,"360":1,"449":1,"591":1,"619":1,"627":1,"635":2,"650":1,"657":1,"682":1,"706":1,"715":1,"726":1,"768":1,"832":1,"854":1,"932":1,"955":1,"973":1,"1051":1,"1052":1,"1092":1,"1133":1,"1212":1,"1270":2,"1286":8,"1300":2,"1309":2,"1323":2,"1324":2,"1339":1,"1350":5,"1380":1,"1402":1,"1415":2,"1416":2,"1417":1,"1427":1,"1435":1,"1436":11,"1445":1,"1455":1,"1488":1,"1750":1,"1765":1,"1767":3,"1771":1,"1776":1,"1808":1,"1809":1,"1810":1,"1815":1,"1902":1,"1931":1,"2010":1,"2055":1,"2081":1,"2129":1,"2134":1,"2210":1}}],["shippedstate",{"3":{"1767":3}}],["shippedon",{"3":{"1764":1}}],["shipped",{"0":{"1488":1,"2055":1,"2057":1,"2143":1},"1":{"569":1,"570":1,"571":1,"572":1,"573":1,"574":1,"575":1,"576":1,"577":1,"578":1,"579":1,"615":1,"616":1,"617":1,"618":1,"619":1,"620":1,"621":1,"622":1,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1,"951":1,"952":1,"953":1,"954":1,"955":1,"956":1,"957":1,"958":1,"959":1,"960":1,"1064":1,"1065":1,"1066":1,"1067":1,"1068":1,"1069":1,"1070":1,"1071":1},"2":{"135":1,"136":1,"138":1,"141":1,"142":1,"967":1,"980":1,"981":1,"983":1,"991":1,"1052":1,"1055":1,"1673":1,"1755":1,"1766":1,"1808":1,"1812":1,"2100":1},"3":{"0":28,"19":1,"22":1,"23":1,"80":1,"81":1,"91":1,"107":1,"109":1,"115":2,"121":1,"122":1,"130":1,"135":10,"136":1,"137":1,"138":2,"139":3,"140":1,"141":4,"142":7,"164":1,"166":1,"180":1,"186":1,"233":1,"235":2,"238":1,"241":1,"243":1,"245":1,"255":1,"256":1,"258":1,"265":2,"268":1,"317":1,"350":1,"358":1,"361":1,"362":1,"364":2,"370":1,"403":1,"418":2,"423":2,"443":1,"448":1,"468":1,"469":1,"482":1,"487":1,"497":3,"502":1,"527":1,"538":1,"539":1,"543":2,"545":1,"547":1,"550":1,"563":1,"569":1,"575":1,"576":1,"583":1,"599":2,"607":1,"609":1,"614":1,"615":1,"618":1,"619":1,"622":1,"627":1,"638":1,"640":1,"647":1,"649":2,"655":1,"657":5,"659":1,"660":2,"661":1,"665":1,"668":1,"688":1,"707":1,"714":1,"717":1,"723":4,"725":2,"729":1,"731":1,"732":1,"733":1,"734":1,"743":2,"745":1,"758":1,"797":1,"804":1,"809":1,"825":1,"827":1,"831":2,"845":1,"853":1,"858":1,"861":4,"873":1,"891":1,"905":2,"910":1,"921":1,"922":1,"923":1,"926":1,"951":1,"954":2,"955":1,"960":2,"962":1,"963":1,"964":5,"966":3,"967":1,"968":2,"972":2,"975":2,"980":8,"981":2,"982":3,"983":3,"988":1,"990":1,"991":1,"996":2,"1003":1,"1004":1,"1007":1,"1012":1,"1013":1,"1016":2,"1017":1,"1032":1,"1033":1,"1034":1,"1036":1,"1037":2,"1049":1,"1052":2,"1055":2,"1064":1,"1071":1,"1074":1,"1075":1,"1081":1,"1089":1,"1092":1,"1093":1,"1094":3,"1122":1,"1124":1,"1127":1,"1132":1,"1134":1,"1150":1,"1160":1,"1161":1,"1164":1,"1174":1,"1175":1,"1192":2,"1212":8,"1214":1,"1231":1,"1234":2,"1237":5,"1247":1,"1259":2,"1270":8,"1271":1,"1273":1,"1280":1,"1282":1,"1284":1,"1286":22,"1287":1,"1290":1,"1293":1,"1297":1,"1300":12,"1301":4,"1311":13,"1323":10,"1324":27,"1327":1,"1339":2,"1347":1,"1350":2,"1359":1,"1369":6,"1395":2,"1396":4,"1415":6,"1416":1,"1417":4,"1427":1,"1428":2,"1430":1,"1432":1,"1433":1,"1435":2,"1436":67,"1437":2,"1438":5,"1444":1,"1453":1,"1455":4,"1465":1,"1467":2,"1485":1,"1486":2,"1487":7,"1488":6,"1489":1,"1490":3,"1491":1,"1492":1,"1494":1,"1496":4,"1507":1,"1521":1,"1526":1,"1546":1,"1553":1,"1563":1,"1574":2,"1575":2,"1576":4,"1577":17,"1581":2,"1582":3,"1583":1,"1586":1,"1631":1,"1639":1,"1646":1,"1654":1,"1659":1,"1664":1,"1665":1,"1666":2,"1667":1,"1669":1,"1671":1,"1673":2,"1682":1,"1685":1,"1686":2,"1689":2,"1690":1,"1692":1,"1704":1,"1727":1,"1749":8,"1750":1,"1751":1,"1755":1,"1758":1,"1760":1,"1765":5,"1766":4,"1767":6,"1768":2,"1776":1,"1782":1,"1808":1,"1810":2,"1812":1,"1854":1,"1860":1,"1861":1,"1862":1,"1887":1,"1891":1,"1899":1,"1903":1,"2041":1,"2042":2,"2046":1,"2060":2,"2062":2,"2075":1,"2085":2,"2100":2,"2116":1,"2127":1,"2129":1,"2131":1,"2144":1,"2146":1,"2168":1,"2200":1,"2220":1,"2230":1,"2232":8,"2239":2}}],["ships",{"0":{"2077":1,"2112":1,"2175":1,"2198":1},"3":{"0":34,"72":1,"76":3,"78":1,"80":1,"99":1,"109":1,"134":1,"135":1,"136":3,"140":1,"145":1,"147":1,"150":1,"168":1,"174":1,"180":1,"185":1,"207":1,"212":1,"214":1,"225":1,"243":1,"255":1,"256":1,"273":1,"295":1,"302":1,"317":1,"318":1,"328":1,"358":1,"369":1,"370":1,"371":1,"380":1,"388":1,"397":2,"405":1,"413":2,"432":1,"433":1,"434":2,"436":1,"440":1,"447":1,"448":1,"454":1,"477":1,"481":1,"483":1,"497":1,"528":1,"532":1,"536":1,"543":1,"545":2,"550":1,"551":2,"564":1,"565":1,"571":1,"572":1,"574":1,"579":1,"583":1,"609":1,"610":2,"617":1,"620":1,"631":1,"633":1,"634":1,"635":1,"640":1,"648":1,"649":1,"656":1,"659":1,"667":1,"668":1,"682":2,"683":1,"684":1,"691":1,"707":1,"711":1,"715":1,"725":4,"739":1,"748":1,"774":1,"776":1,"782":2,"786":1,"808":2,"825":1,"827":1,"831":1,"832":1,"840":1,"844":1,"848":1,"855":1,"859":2,"863":1,"880":1,"905":3,"907":1,"914":1,"915":1,"921":1,"922":1,"933":1,"937":1,"939":2,"940":1,"953":1,"954":1,"960":1,"963":1,"974":1,"984":1,"996":1,"1004":2,"1017":1,"1034":1,"1037":1,"1044":2,"1049":1,"1050":1,"1053":1,"1054":1,"1055":1,"1057":3,"1058":2,"1059":7,"1061":1,"1062":2,"1066":1,"1075":1,"1077":1,"1089":2,"1091":5,"1093":2,"1096":1,"1124":2,"1139":2,"1149":2,"1160":1,"1162":1,"1178":1,"1211":1,"1212":4,"1213":1,"1237":4,"1239":1,"1248":1,"1259":1,"1269":1,"1270":4,"1271":2,"1286":12,"1289":1,"1293":2,"1297":2,"1300":9,"1301":2,"1309":3,"1311":11,"1312":3,"1313":1,"1318":1,"1320":1,"1323":8,"1324":10,"1336":1,"1339":8,"1341":1,"1350":6,"1359":1,"1369":1,"1380":3,"1395":1,"1396":3,"1408":1,"1415":9,"1416":2,"1417":20,"1420":1,"1423":1,"1427":5,"1428":1,"1431":4,"1435":1,"1436":47,"1438":2,"1442":2,"1443":4,"1445":1,"1447":2,"1453":1,"1454":1,"1455":4,"1459":2,"1461":1,"1467":6,"1471":1,"1487":2,"1488":2,"1489":6,"1491":1,"1492":1,"1496":1,"1503":2,"1524":1,"1526":5,"1531":2,"1545":1,"1553":1,"1575":2,"1577":16,"1578":2,"1582":3,"1583":1,"1585":1,"1591":3,"1595":1,"1596":1,"1626":1,"1631":1,"1641":1,"1651":1,"1652":1,"1653":2,"1654":1,"1655":1,"1661":1,"1662":1,"1663":1,"1664":1,"1666":1,"1667":1,"1668":1,"1670":1,"1671":1,"1674":1,"1675":1,"1681":1,"1683":1,"1684":1,"1686":1,"1689":1,"1697":1,"1699":1,"1701":1,"1719":1,"1727":2,"1728":1,"1729":2,"1750":1,"1764":1,"1765":1,"1767":1,"1768":1,"1781":2,"1784":2,"1790":1,"1799":1,"1800":1,"1810":1,"1815":1,"1828":1,"1829":1,"1830":1,"1840":1,"1845":2,"1883":2,"1889":1,"1891":3,"1898":1,"1902":1,"1903":1,"1921":1,"1922":2,"1930":2,"1931":1,"1933":1,"1941":1,"1948":1,"1961":1,"1968":1,"1970":1,"1975":1,"1977":1,"1994":1,"1997":1,"1998":1,"1999":1,"2001":1,"2008":1,"2010":2,"2011":2,"2017":1,"2024":1,"2034":1,"2044":1,"2045":1,"2046":1,"2055":2,"2057":1,"2059":1,"2067":1,"2068":1,"2075":1,"2077":1,"2080":1,"2081":1,"2083":2,"2087":1,"2094":1,"2097":1,"2101":1,"2107":1,"2114":2,"2115":1,"2124":1,"2127":1,"2131":2,"2133":2,"2147":1,"2148":1,"2149":2,"2150":1,"2153":1,"2154":2,"2156":2,"2168":1,"2170":1,"2175":1,"2178":2,"2180":1,"2192":2,"2202":2,"2203":1,"2232":11}}],["ship",{"0":{"576":1,"1424":1,"1897":1},"1":{"1056":1,"1057":1,"1058":1,"1059":1,"1060":1,"1061":1,"1062":1,"1063":1},"2":{"1809":1,"1812":1},"3":{"0":7,"62":1,"76":1,"78":1,"85":1,"109":1,"135":2,"147":1,"186":1,"213":1,"214":1,"235":1,"265":1,"269":1,"293":1,"299":1,"300":1,"302":1,"412":1,"415":1,"422":1,"425":1,"426":2,"448":1,"470":1,"528":1,"570":1,"572":2,"574":1,"577":1,"578":1,"579":1,"618":1,"628":2,"633":2,"634":1,"638":1,"640":1,"649":1,"658":1,"660":1,"665":1,"667":1,"672":1,"682":1,"691":1,"698":1,"708":1,"715":1,"717":1,"723":1,"733":1,"749":1,"758":1,"774":1,"798":1,"818":1,"827":1,"832":1,"836":1,"854":1,"861":1,"862":1,"914":1,"922":1,"953":1,"954":1,"955":1,"1016":1,"1049":1,"1056":1,"1059":2,"1060":2,"1067":1,"1091":1,"1096":1,"1212":2,"1237":2,"1259":1,"1270":1,"1271":3,"1282":1,"1286":4,"1300":5,"1301":1,"1308":1,"1309":1,"1311":4,"1315":1,"1323":4,"1324":5,"1334":1,"1339":3,"1359":1,"1395":1,"1396":2,"1402":2,"1415":1,"1416":2,"1417":7,"1418":2,"1419":1,"1427":4,"1428":1,"1431":1,"1432":1,"1433":2,"1436":9,"1438":2,"1454":1,"1459":1,"1465":1,"1467":1,"1486":1,"1489":2,"1492":1,"1526":1,"1531":1,"1534":1,"1575":1,"1577":1,"1583":1,"1585":1,"1591":1,"1596":1,"1627":1,"1648":1,"1654":1,"1661":1,"1662":1,"1666":1,"1667":1,"1669":1,"1673":2,"1675":1,"1699":1,"1706":1,"1727":1,"1733":1,"1735":1,"1746":2,"1749":6,"1750":1,"1755":1,"1760":1,"1761":1,"1764":1,"1765":7,"1767":1,"1768":1,"1769":2,"1771":1,"1788":1,"1809":1,"1810":2,"1812":1,"1815":2,"1843":1,"1853":1,"1880":1,"1882":1,"1889":1,"1905":1,"1927":1,"1941":1,"1959":1,"1965":1,"1970":1,"1978":1,"1999":1,"2024":1,"2028":1,"2029":1,"2038":2,"2044":1,"2049":1,"2053":1,"2060":1,"2073":1,"2085":1,"2089":1,"2101":1,"2107":1,"2116":1,"2120":2,"2122":2,"2131":2,"2133":1,"2134":1,"2138":1,"2160":1,"2173":1,"2180":1,"2195":1,"2196":1,"2200":1,"2202":1,"2203":1,"2212":1,"2213":1,"2218":1,"2220":1,"2226":1,"2232":1,"2239":1}}],["shop",{"3":{"1750":1,"1773":1}}],["shopping",{"3":{"1742":1,"1749":2,"1750":6,"1763":2,"1764":11,"1765":4,"1767":2,"1768":6,"1771":2,"1773":4,"1995":1}}],["shoppingcartinvariants",{"3":{"1767":3}}],["shoppingcartitems",{"3":{"1746":3,"1749":3,"1764":1}}],["shoppingcartitemcreaterequest",{"3":{"1270":1}}],["shoppingcartitemchanged",{"2":{"782":1,"1749":1},"3":{"782":2,"1749":2,"1769":1}}],["shoppingcartitem",{"3":{"1237":1,"1764":1}}],["shoppingcartlisttests",{"3":{"1436":2}}],["shoppingcartlist",{"3":{"1324":1,"1591":3,"1596":3,"1597":1}}],["shoppingcartcreaterequest",{"3":{"1270":1}}],["shoppingcartchanged",{"3":{"782":1,"1749":1,"1769":1}}],["shoppingcartcheckedout",{"2":{"782":1,"1749":1},"3":{"0":1,"782":3,"1259":1,"1749":1,"1769":1}}],["shoppingcart",{"2":{"1749":1,"1769":1,"2110":1},"3":{"782":1,"1324":2,"1436":2,"1591":1,"1749":1,"1764":1,"1765":1,"1767":1,"1769":4,"2110":1}}],["shoppingcartdetail",{"2":{"1591":1},"3":{"556":1,"1324":1,"1591":2}}],["shoppingcartbycustomerspecification",{"2":{"318":1,"1995":1},"3":{"318":4,"1995":2}}],["shoppingcartservice",{"3":{"881":1,"1324":1,"1591":1,"1597":1}}],["shoppingcartsmanage",{"2":{"318":1,"324":1,"1995":1},"3":{"318":1,"324":1,"1995":1}}],["shoppingcartscontroller",{"2":{"318":3,"324":1,"743":1},"3":{"318":9,"324":3,"325":1,"326":1,"743":1,"1300":7,"1311":4,"1591":1,"1599":1,"1995":11}}],["shoppingcarts",{"2":{"554":1},"3":{"317":1,"318":1,"535":1,"539":1,"554":1,"556":1,"674":1,"782":3,"813":1,"814":1,"881":2,"897":1,"900":1,"988":2,"1025":2,"1742":1,"1746":5,"1749":9,"1750":3,"1764":2,"1765":3,"1994":1}}],["shopper",{"3":{"831":1,"1028":1,"1591":1,"1596":2,"1742":2,"1764":1,"1769":1}}],["shouting",{"3":{"1324":1}}],["shouldgotoregisterpage",{"3":{"1436":1}}],["shouldnavigatetohomepage",{"3":{"1436":2}}],["shouldnotexceed",{"3":{"1436":1}}],["shouldnotexpose",{"3":{"81":1,"1436":2}}],["shouldnotinject",{"3":{"1436":2}}],["shouldnotimplement",{"3":{"1311":1,"1436":2,"1875":1}}],["shouldnotresidein",{"3":{"1436":1}}],["shouldnotreach",{"3":{"1436":6}}],["shouldnotreadtheambientclock",{"2":{"1161":1,"1436":1},"3":{"1161":1,"1436":2}}],["shouldnothave",{"3":{"1436":2}}],["shouldnot",{"3":{"1436":3,"1489":1}}],["shouldnotnavigate",{"3":{"1436":2}}],["shouldnotthrow",{"3":{"1436":2}}],["shouldnotbe",{"3":{"1436":10}}],["shouldnotdependon",{"2":{"1436":2,"1489":2},"3":{"54":1,"1436":15,"1489":6}}],["shouldupdateemail",{"3":{"1436":1}}],["shouldupdateprofileaddress",{"3":{"1436":1}}],["shouldupdateprofilename",{"3":{"1436":1}}],["shouldusereadrepositories",{"3":{"1436":4}}],["shouldpreventaccesstoprotectedpages",{"3":{"1436":1}}],["shouldexpose",{"3":{"1436":2,"1489":1}}],["shoulder",{"3":{"1300":1}}],["shouldrender",{"2":{"1555":1,"1560":1},"3":{"1555":1,"1560":1}}],["shouldredirecttologinpage",{"3":{"1436":1}}],["shouldredirecttologin",{"3":{"1436":1}}],["shouldresidein",{"3":{"1436":4}}],["shouldreside",{"3":{"1436":1}}],["shouldreturn",{"3":{"1436":2,"1489":1}}],["shouldregister",{"3":{"1436":1}}],["shouldreportpartialresults",{"3":{"1417":1}}],["shouldonlyoccurin",{"3":{"1436":3}}],["shouldinherit",{"3":{"1436":2}}],["shouldincrease",{"3":{"1436":2}}],["shouldimplement",{"3":{"1436":4,"1489":1}}],["shouldcarry",{"3":{"1436":3}}],["shouldcollectrequest",{"2":{"402":1},"3":{"402":1,"1333":1,"1339":5,"1443":2}}],["shouldcollectoutgoing",{"2":{"402":1},"3":{"402":1,"1333":1,"1339":5,"1443":1}}],["shouldhavenoaccessibilityviolations",{"3":{"1436":7}}],["shouldhave",{"2":{"1489":1},"3":{"1436":14,"1489":2}}],["shouldhandle",{"3":{"819":1,"1312":1,"1339":1}}],["shouldmatch",{"3":{"1436":5}}],["shouldlocalizeandpersist",{"3":{"1436":1}}],["shouldlockasync",{"3":{"1324":4}}],["shouldloadwithuserdata",{"3":{"1436":1}}],["shouldload",{"3":{"1310":2}}],["shouldapplyandpersist",{"3":{"1436":1}}],["shouldattempt",{"3":{"1300":2}}],["shouldaudit",{"3":{"1237":1,"1286":3}}],["shouldbypasscertificatevalidation",{"3":{"1286":2}}],["shouldbereachable",{"3":{"1436":1}}],["shouldbeforbidden",{"2":{"1627":1},"3":{"1436":1,"1627":1}}],["shouldbeaccessible",{"3":{"1436":2}}],["shouldbesealed",{"3":{"1436":4}}],["shouldbecolocatedwith",{"3":{"1436":6}}],["shouldbe",{"2":{"1168":1},"3":{"1168":1,"1237":1,"1436":26}}],["shouldsucceed",{"3":{"1436":1}}],["shouldshowerror",{"3":{"1436":4}}],["shouldshipfrom",{"2":{"81":1,"1436":1},"3":{"81":1,"1436":2}}],["shouldstayat",{"2":{"2178":1},"3":{"1436":2,"2178":1}}],["shouldstaybehind",{"2":{"1436":1,"2178":1},"3":{"1436":2,"2178":1}}],["shouldseed",{"2":{"1596":1},"3":{"1284":1,"1286":3,"1412":1,"1415":3,"1596":1}}],["shouldwritefilesink",{"2":{"401":1},"3":{"401":1,"1333":1,"1339":2}}],["shoulddeclare",{"2":{"160":1},"3":{"160":1,"1436":12}}],["should",{"3":{"0":1,"32":1,"33":1,"59":1,"62":1,"71":1,"109":1,"139":1,"155":1,"159":2,"160":2,"174":1,"175":1,"185":3,"188":1,"224":1,"230":1,"349":1,"358":1,"360":2,"361":1,"517":1,"519":1,"520":1,"537":1,"545":1,"550":1,"573":1,"625":1,"667":1,"674":1,"682":1,"709":1,"725":2,"758":1,"767":2,"801":1,"819":1,"832":2,"837":1,"861":1,"890":1,"899":1,"916":1,"940":2,"941":1,"955":1,"965":1,"980":1,"989":1,"1000":1,"1017":1,"1049":1,"1077":1,"1085":1,"1124":1,"1140":1,"1229":1,"1232":1,"1247":2,"1259":1,"1270":3,"1286":9,"1300":9,"1301":3,"1304":1,"1309":4,"1310":1,"1311":12,"1312":3,"1323":4,"1324":9,"1339":6,"1342":1,"1350":6,"1359":10,"1369":3,"1380":2,"1395":7,"1396":10,"1411":1,"1415":2,"1416":1,"1417":8,"1420":1,"1427":1,"1435":1,"1436":33,"1438":1,"1439":1,"1440":1,"1454":1,"1455":1,"1456":1,"1467":1,"1489":1,"1490":3,"1492":2,"1500":1,"1531":1,"1537":1,"1630":2,"1631":3,"1638":2,"1639":7,"1640":5,"1641":2,"1674":1,"1675":1,"1676":1,"1677":1,"1678":1,"1765":1,"1776":3,"1782":2,"1801":1,"1802":1,"1806":1,"1812":2,"1817":1,"1819":3,"1827":1,"1854":1,"1861":1,"1862":1,"1863":1,"1868":2,"1888":1,"1890":1,"1898":1,"1899":2,"1901":1,"1911":1,"1924":1,"1930":1,"1960":2,"1965":2,"1979":1,"1981":1,"1987":1,"1991":1,"1999":1,"2000":1,"2015":1,"2039":1,"2077":1,"2080":2,"2081":1,"2085":1,"2086":1,"2099":1,"2107":1,"2114":1,"2128":1,"2135":1,"2140":3,"2141":1,"2145":1,"2146":1,"2165":1,"2168":1,"2169":1,"2174":1,"2189":1,"2192":1}}],["showtargetpickers",{"3":{"1395":1}}],["showeventpicker",{"3":{"1395":1}}],["showeventpicker=",{"3":{"1395":2}}],["showempty",{"3":{"1395":1}}],["showed",{"3":{"147":1,"1070":1,"1339":1}}],["showgrid",{"3":{"1395":1}}],["showmyscheduleonlychanged",{"3":{"1395":1}}],["showmyscheduleonly",{"3":{"1395":1}}],["showmap",{"3":{"1395":3}}],["showmessageboxasync",{"3":{"1324":3}}],["showasync",{"3":{"1395":5}}],["showactionerror",{"3":{"1402":5}}],["showaction",{"3":{"1324":6,"1396":1}}],["showcancelsnackbar",{"3":{"1324":3,"1395":1}}],["showcase",{"3":{"618":1,"1434":1,"1436":1,"1438":1,"1486":1,"1487":1,"1643":2}}],["showpersistent",{"3":{"1324":8}}],["show",{"2":{"1484":1,"2034":1},"3":{"450":1,"537":1,"644":1,"681":1,"689":1,"741":1,"766":1,"1020":1,"1026":1,"1027":1,"1028":1,"1239":1,"1259":2,"1286":1,"1300":5,"1309":3,"1324":17,"1350":6,"1353":1,"1359":6,"1382":1,"1395":13,"1396":9,"1399":1,"1402":3,"1415":5,"1416":1,"1417":7,"1431":1,"1436":5,"1438":2,"1442":1,"1474":1,"1475":1,"1476":1,"1478":1,"1484":1,"1505":1,"1517":1,"1627":1,"1653":1,"1681":1,"1720":1,"1728":1,"1749":1,"1758":1,"1815":1,"1836":1,"1873":1,"1958":1,"2010":1,"2027":1,"2034":1,"2043":1,"2070":1,"2087":1,"2119":1,"2132":1,"2165":1,"2202":1}}],["shown",{"3":{"265":1,"1300":3,"1324":6,"1350":4,"1359":1,"1369":1,"1395":7,"1396":3,"1415":2,"1436":5,"1438":1,"1444":1,"1467":1,"1591":1,"1626":1,"1627":1,"1629":1,"1637":1,"1647":1,"1654":1,"1682":1,"1727":1,"1761":1,"2030":1,"2136":1}}],["showingcacheddata",{"3":{"1395":1}}],["showing",{"3":{"109":1,"265":1,"635":1,"881":1,"1300":1,"1311":2,"1324":3,"1350":1,"1359":4,"1388":1,"1391":1,"1395":8,"1396":5,"1415":2,"1416":1,"1417":2,"1436":5,"1479":1,"1750":1,"1941":1,"2003":1}}],["showstheapismessage",{"3":{"1436":1}}],["showsthesameconfirmation",{"3":{"1436":1}}],["showsclientvalidationerrors",{"3":{"1436":1}}],["showsearch",{"3":{"1324":1}}],["showsessionslink",{"3":{"1324":1}}],["shows",{"0":{"1910":1},"3":{"82":1,"109":1,"214":1,"227":1,"279":1,"536":1,"557":1,"585":1,"674":1,"684":1,"776":1,"782":1,"875":1,"890":1,"897":1,"905":2,"963":1,"981":1,"1004":1,"1018":1,"1020":1,"1029":1,"1102":2,"1229":1,"1237":2,"1259":1,"1270":1,"1280":1,"1286":7,"1289":1,"1300":4,"1301":1,"1307":1,"1309":7,"1311":3,"1313":1,"1323":6,"1324":38,"1339":13,"1340":1,"1344":1,"1350":7,"1351":1,"1359":10,"1362":1,"1369":1,"1375":1,"1380":3,"1384":1,"1385":1,"1387":1,"1388":1,"1391":3,"1395":40,"1396":26,"1397":1,"1398":1,"1402":13,"1403":1,"1414":1,"1415":2,"1416":2,"1417":7,"1427":2,"1431":1,"1433":1,"1436":9,"1438":1,"1440":1,"1456":1,"1467":1,"1470":1,"1474":1,"1484":2,"1492":1,"1577":1,"1620":1,"1627":1,"1631":2,"1646":1,"1657":2,"1661":2,"1670":1,"1673":1,"1684":1,"1685":1,"1689":1,"1702":1,"1704":1,"1729":1,"1730":1,"1748":1,"1750":2,"1756":1,"1768":2,"1773":2,"1836":1,"1913":1,"1919":1,"1923":1,"1932":1,"1943":1,"1949":1,"1950":1,"1971":1,"2005":1,"2010":1,"2022":1,"2081":1,"2086":1,"2099":1,"2116":1,"2117":1,"2135":1}}],["shotgun",{"3":{"1011":1,"1012":1,"1496":1,"1571":1}}],["shot",{"3":{"0":1,"422":1,"1161":1,"1259":1,"1286":3,"1297":1,"1309":1,"1324":4,"1334":1,"1339":1,"1359":1,"1389":1,"1393":1,"1395":6,"1414":1,"1415":1,"1417":2,"1436":2,"1438":1,"1765":1}}],["shortfall",{"3":{"1350":1,"1417":1}}],["shortcuts",{"3":{"1416":7}}],["shortcut",{"3":{"517":1,"923":1,"940":1,"1247":1,"1286":1,"1311":1,"1396":2,"1416":7,"1436":2,"1441":2,"1488":2,"1491":1,"1759":1,"2040":1}}],["shorthand",{"3":{"255":1,"674":1,"952":1,"954":1,"959":1,"1258":1,"1259":2,"1286":1,"1436":1,"1496":1}}],["shorter",{"3":{"139":1,"142":1,"359":1,"386":1,"387":1,"500":1,"733":1,"741":1,"827":2,"907":1,"975":1,"1244":1,"1247":1,"1270":2,"1286":2,"1288":1,"1300":1,"1301":2,"1309":1,"1311":2,"1323":1,"1324":1,"1350":1,"1359":1,"1415":1,"1436":3,"1438":1,"1465":1,"1821":1,"1824":1,"1900":1}}],["shortest",{"3":{"59":1,"135":1,"1265":1,"1359":2,"1436":6,"1759":1}}],["shorten",{"3":{"938":1,"1259":1,"1323":1,"1396":1}}],["shortened",{"3":{"461":1,"1187":1,"1396":1}}],["shortening",{"3":{"22":1,"459":1}}],["shortens",{"3":{"19":1,"526":1,"675":1,"1339":1,"1415":1}}],["short",{"3":{"0":3,"1":1,"39":1,"70":1,"71":1,"109":2,"110":1,"113":1,"117":1,"121":1,"122":1,"124":1,"125":1,"126":1,"223":1,"233":1,"235":1,"243":2,"244":1,"283":1,"284":1,"300":1,"318":1,"319":1,"324":1,"350":3,"351":1,"352":1,"359":2,"360":1,"361":2,"386":1,"390":2,"392":1,"459":2,"498":2,"500":2,"501":1,"508":1,"528":1,"549":1,"591":3,"593":1,"633":1,"640":1,"684":1,"690":1,"691":1,"734":1,"741":1,"743":1,"744":1,"790":2,"819":1,"820":2,"853":1,"855":1,"920":1,"921":1,"946":1,"948":1,"964":1,"973":1,"979":1,"1006":1,"1042":1,"1061":1,"1168":1,"1183":1,"1212":1,"1220":2,"1224":1,"1229":5,"1231":1,"1237":6,"1239":1,"1245":1,"1247":3,"1250":1,"1255":1,"1256":1,"1259":10,"1263":2,"1265":1,"1268":5,"1270":32,"1271":3,"1286":19,"1288":1,"1300":26,"1301":5,"1304":1,"1309":9,"1310":1,"1311":20,"1320":1,"1323":8,"1324":13,"1328":1,"1337":1,"1339":19,"1346":1,"1347":1,"1350":9,"1359":51,"1371":1,"1375":2,"1380":7,"1387":1,"1389":1,"1391":1,"1395":25,"1396":19,"1402":14,"1406":1,"1410":1,"1415":13,"1416":5,"1417":5,"1427":2,"1430":1,"1436":11,"1438":2,"1441":1,"1443":1,"1447":1,"1453":1,"1454":3,"1455":2,"1460":1,"1461":1,"1462":1,"1467":1,"1489":2,"1492":3,"1496":3,"1523":1,"1526":1,"1554":1,"1577":9,"1585":2,"1592":2,"1612":1,"1630":1,"1640":2,"1641":1,"1666":1,"1701":1,"1707":1,"1726":1,"1732":1,"1800":1,"1805":2,"1821":1,"1828":1,"1831":1,"1834":1,"1856":1,"1874":1,"1905":1,"1916":1,"1919":1,"1921":1,"1922":1,"1923":1,"1933":2,"1962":1,"1972":1,"1975":1,"1979":1,"1980":3,"1981":1,"1984":1,"1985":1,"1994":1,"2000":1,"2001":1,"2007":1,"2008":1,"2030":1,"2047":2,"2048":1,"2096":2,"2128":1,"2136":1,"2137":1,"2142":2,"2143":1,"2156":1,"2160":1,"2188":1,"2203":1,"2232":2}}],["shas",{"3":{"1483":1}}],["sha1",{"3":{"1300":1}}],["shadowed",{"3":{"1324":1}}],["shadow",{"3":{"1035":1,"1059":1,"1237":1,"1276":1,"1286":5,"1301":3,"1350":2,"1369":1,"1395":6,"1436":1,"1467":1,"1480":2,"2201":1}}],["shadowing",{"3":{"964":1,"1237":1}}],["shadows",{"3":{"840":1,"1311":1,"1443":1}}],["sha2",{"3":{"905":1,"1300":1}}],["sha256",{"2":{"418":2,"423":1,"424":1,"868":1,"870":1,"873":1,"1481":1},"3":{"30":1,"418":2,"423":2,"424":1,"868":1,"870":1,"873":1,"1300":8,"1309":1,"1311":2,"1427":1,"1445":1,"1481":1,"1895":2}}],["shaky",{"3":{"682":1,"685":1,"1417":2}}],["shaping",{"3":{"346":1,"572":1,"591":1,"595":1,"744":1,"827":2,"1238":1,"1242":3,"1244":2,"1247":9,"1270":3,"1272":1,"1309":4,"1311":2,"1339":2,"1350":4,"1356":1,"1359":7,"1380":1,"1395":1,"1436":3,"1438":1,"1488":1,"1727":2,"1815":1,"2055":1}}],["shapecollectiondata",{"2":{"1242":1},"3":{"741":1,"1242":1,"1247":4}}],["shaperow",{"2":{"744":1},"3":{"741":1,"744":1,"1311":1}}],["shaper",{"3":{"741":2,"742":1,"1436":1,"1438":1}}],["shapedaccessorcache",{"3":{"1247":4}}],["shapedata",{"2":{"741":1},"3":{"741":1,"1242":1,"1247":3,"1311":1}}],["shaped",{"0":{"1389":1},"3":{"175":1,"249":1,"253":1,"337":1,"341":1,"388":3,"439":1,"443":2,"573":1,"585":1,"608":1,"626":1,"633":1,"719":1,"741":3,"743":1,"785":1,"789":1,"825":1,"830":1,"880":1,"890":1,"921":1,"946":1,"948":1,"964":1,"1004":1,"1018":1,"1117":1,"1124":1,"1150":1,"1152":1,"1153":1,"1177":1,"1217":1,"1229":4,"1247":5,"1259":1,"1270":1,"1271":2,"1285":1,"1286":8,"1300":9,"1311":11,"1320":1,"1323":2,"1324":9,"1326":1,"1339":1,"1347":1,"1350":8,"1352":1,"1353":1,"1359":12,"1369":4,"1380":1,"1382":1,"1395":8,"1396":8,"1401":1,"1402":1,"1410":1,"1415":5,"1416":1,"1417":7,"1427":1,"1436":10,"1438":1,"1462":1,"1476":1,"1496":1,"1526":1,"1543":1,"1576":1,"1577":3,"1585":1,"1669":1,"1685":1,"1702":2,"1722":1,"1730":1,"1731":1,"1874":1,"1883":1,"1911":1,"1916":1,"1923":1,"1940":1,"1958":1,"2001":1,"2002":1,"2074":1,"2117":1,"2126":1,"2150":1}}],["shapes",{"0":{"1373":1,"1374":1,"1943":1},"3":{"0":2,"109":2,"157":1,"166":1,"170":1,"301":1,"317":1,"318":1,"395":1,"518":1,"522":1,"535":1,"549":1,"557":1,"566":1,"583":1,"591":1,"633":3,"640":1,"657":1,"733":1,"734":1,"743":1,"781":1,"782":1,"789":1,"810":1,"812":1,"826":1,"834":1,"845":1,"853":1,"920":1,"926":2,"980":2,"983":1,"984":1,"998":1,"1050":2,"1060":1,"1068":1,"1100":1,"1110":1,"1118":1,"1171":1,"1202":1,"1203":1,"1212":1,"1227":2,"1228":1,"1229":1,"1237":2,"1242":1,"1245":1,"1247":1,"1249":1,"1260":1,"1263":1,"1265":1,"1270":6,"1271":2,"1273":1,"1279":1,"1286":11,"1293":1,"1297":1,"1300":14,"1301":1,"1309":4,"1311":14,"1312":8,"1315":1,"1317":1,"1320":1,"1323":7,"1324":7,"1328":1,"1334":1,"1339":5,"1340":1,"1342":1,"1345":1,"1347":1,"1350":1,"1359":20,"1366":1,"1369":1,"1373":2,"1374":1,"1380":1,"1387":1,"1393":1,"1395":7,"1396":19,"1399":1,"1402":4,"1410":1,"1415":6,"1416":1,"1431":1,"1432":1,"1436":43,"1438":2,"1444":1,"1496":2,"1526":1,"1535":1,"1546":2,"1600":1,"1652":1,"1665":1,"1666":1,"1720":1,"1731":1,"1927":1,"1949":1,"1952":1,"1955":2,"1958":1,"1991":1,"1994":1,"1995":1,"2018":1,"2100":1,"2131":1}}],["shape",{"0":{"1261":1,"1295":1,"1331":1,"1343":1,"1363":1,"1375":1,"1613":1,"1718":1,"1820":1,"2051":1},"2":{"1704":1},"3":{"0":21,"24":1,"31":1,"42":1,"53":1,"59":1,"62":2,"68":1,"71":1,"72":1,"76":1,"77":2,"78":1,"80":1,"109":4,"110":1,"111":1,"122":1,"125":1,"126":1,"127":1,"130":1,"134":1,"136":2,"138":1,"139":2,"159":1,"164":1,"166":3,"180":1,"184":1,"186":1,"195":2,"201":1,"218":1,"226":1,"227":1,"230":1,"234":1,"243":1,"245":1,"247":1,"248":2,"255":2,"256":1,"258":1,"274":1,"280":1,"282":1,"300":1,"306":1,"309":1,"322":1,"325":2,"329":1,"331":1,"332":1,"333":1,"335":1,"341":1,"350":2,"352":1,"353":1,"372":1,"379":1,"380":1,"384":1,"400":1,"401":1,"404":1,"407":1,"412":1,"418":2,"423":1,"427":2,"428":1,"429":1,"430":1,"447":2,"448":5,"449":1,"451":1,"455":1,"459":1,"497":1,"498":1,"502":1,"509":1,"517":2,"519":1,"522":1,"523":1,"536":1,"539":1,"541":1,"544":1,"545":2,"549":6,"556":1,"563":2,"564":4,"572":2,"574":1,"582":2,"609":1,"625":1,"626":1,"630":1,"633":5,"635":2,"638":1,"655":1,"657":1,"661":1,"668":1,"674":2,"675":1,"676":1,"681":1,"689":1,"691":1,"698":2,"699":1,"700":1,"707":2,"714":1,"725":2,"726":1,"727":1,"731":1,"732":2,"733":2,"734":1,"740":1,"743":1,"749":3,"750":1,"760":2,"774":1,"776":1,"780":1,"782":5,"783":2,"784":3,"786":1,"790":3,"797":1,"798":1,"799":1,"801":2,"804":1,"808":1,"810":2,"812":1,"818":1,"821":1,"827":2,"829":1,"831":1,"832":1,"837":2,"838":1,"840":3,"845":3,"846":2,"847":1,"848":2,"854":1,"862":1,"868":1,"881":1,"882":2,"889":1,"891":1,"897":1,"898":1,"899":1,"901":1,"905":2,"907":1,"909":1,"913":1,"914":1,"921":1,"924":1,"926":5,"930":1,"935":1,"939":1,"941":1,"945":1,"946":4,"947":1,"948":2,"950":1,"957":1,"963":1,"964":1,"965":1,"966":1,"968":1,"971":2,"976":1,"979":2,"980":5,"981":3,"982":1,"1003":1,"1004":3,"1005":1,"1006":1,"1008":1,"1018":4,"1020":1,"1025":1,"1033":1,"1034":1,"1035":2,"1037":1,"1042":1,"1043":1,"1048":1,"1049":1,"1050":2,"1058":1,"1059":3,"1066":1,"1069":1,"1082":1,"1090":1,"1091":1,"1092":1,"1095":1,"1101":1,"1102":1,"1104":1,"1108":1,"1115":1,"1124":1,"1126":2,"1132":2,"1143":1,"1152":1,"1162":1,"1167":1,"1168":1,"1169":1,"1174":1,"1184":1,"1193":1,"1212":2,"1218":1,"1223":1,"1226":1,"1229":22,"1233":1,"1234":3,"1235":2,"1237":16,"1238":2,"1239":2,"1240":1,"1242":1,"1244":3,"1247":31,"1258":1,"1259":9,"1260":1,"1262":1,"1263":3,"1269":2,"1270":38,"1271":10,"1273":1,"1275":1,"1276":1,"1278":1,"1286":74,"1288":1,"1289":3,"1292":2,"1294":1,"1296":1,"1299":1,"1300":110,"1301":12,"1307":1,"1309":31,"1310":5,"1311":42,"1312":9,"1313":1,"1317":1,"1320":3,"1323":60,"1324":117,"1330":1,"1333":1,"1334":1,"1337":1,"1339":40,"1343":2,"1348":1,"1350":79,"1352":3,"1353":4,"1358":1,"1359":226,"1362":1,"1366":1,"1368":1,"1369":30,"1370":1,"1372":1,"1373":3,"1376":3,"1378":1,"1380":40,"1382":1,"1383":1,"1389":1,"1392":2,"1395":84,"1396":136,"1397":1,"1399":1,"1400":1,"1401":1,"1402":45,"1404":1,"1405":2,"1409":1,"1415":54,"1416":15,"1417":48,"1418":2,"1421":1,"1427":6,"1429":1,"1431":5,"1433":1,"1435":1,"1436":246,"1438":9,"1440":1,"1441":3,"1443":2,"1444":1,"1447":1,"1448":1,"1455":4,"1456":1,"1459":3,"1465":1,"1466":1,"1467":5,"1470":1,"1474":1,"1476":1,"1478":1,"1486":1,"1488":1,"1489":3,"1491":1,"1492":4,"1496":2,"1497":1,"1515":1,"1526":1,"1534":1,"1545":1,"1547":1,"1577":2,"1585":1,"1591":3,"1596":2,"1611":1,"1627":1,"1631":3,"1641":7,"1646":1,"1651":1,"1654":2,"1661":2,"1662":1,"1665":1,"1668":1,"1673":1,"1679":1,"1684":2,"1685":2,"1686":3,"1688":2,"1692":2,"1697":3,"1699":1,"1701":3,"1704":2,"1705":1,"1717":5,"1718":3,"1719":3,"1720":2,"1723":2,"1724":1,"1725":2,"1727":14,"1728":2,"1729":2,"1730":1,"1731":1,"1765":1,"1805":6,"1806":1,"1810":2,"1815":4,"1816":1,"1817":1,"1825":1,"1832":1,"1833":1,"1843":1,"1861":2,"1863":1,"1867":1,"1869":1,"1875":1,"1887":3,"1888":2,"1889":3,"1890":1,"1892":1,"1901":1,"1902":2,"1905":1,"1909":2,"1915":2,"1919":2,"1920":2,"1922":1,"1923":1,"1924":1,"1925":1,"1926":1,"1927":2,"1928":1,"1929":1,"1930":1,"1933":1,"1934":1,"1942":1,"1945":1,"1947":1,"1950":1,"1951":1,"1953":1,"1955":1,"1957":1,"1970":1,"1972":1,"1977":1,"1978":2,"1979":2,"1986":1,"1987":2,"1988":1,"1989":1,"1990":1,"1991":2,"1993":1,"2001":3,"2002":1,"2008":2,"2010":1,"2013":1,"2024":1,"2043":1,"2044":1,"2046":1,"2047":1,"2050":1,"2051":1,"2055":1,"2057":1,"2059":2,"2060":2,"2067":1,"2074":1,"2082":1,"2084":1,"2088":1,"2089":4,"2093":1,"2096":1,"2097":1,"2098":2,"2101":1,"2106":1,"2107":3,"2110":1,"2113":1,"2119":1,"2123":1,"2125":1,"2129":1,"2130":2,"2131":4,"2132":3,"2133":2,"2134":1,"2136":1,"2142":1,"2150":1,"2153":1,"2156":2,"2163":1,"2166":1,"2167":2,"2180":1,"2186":1,"2198":1,"2203":1,"2230":1,"2232":1}}],["shallowly",{"3":{"1459":1}}],["shallow",{"3":{"237":1,"566":1,"1237":1,"1359":1,"1436":1,"1438":1,"1453":1,"1577":1,"1957":1}}],["shards",{"3":{"2220":1}}],["sharply",{"3":{"1311":1,"1417":1}}],["sharpened",{"3":{"804":1}}],["sharpens",{"3":{"803":1,"1996":1}}],["sharper",{"3":{"168":1,"549":1,"1267":1,"1281":1,"1286":1,"1324":3,"1436":7,"2025":1}}],["sharpest",{"3":{"48":1,"160":1,"585":1,"1301":1,"1323":1,"1324":1,"1328":1,"1359":1,"1395":1,"1396":1,"1415":2,"1436":6,"1441":1,"1455":1,"2156":1}}],["sharp",{"3":{"102":1,"549":1,"735":1,"1312":1,"1396":1,"1436":2,"1787":1,"1792":1,"1793":1,"1801":1,"1807":1,"1812":1,"1817":1,"1827":1,"1834":1,"1846":1,"1853":1,"1862":1,"1868":1,"1877":1,"1886":1,"1892":1,"1899":1,"1905":1,"1912":1,"1921":1,"1922":3,"1924":1,"1930":1,"1941":1,"1949":1,"1950":1,"1959":1,"1965":1,"1972":1,"1979":1,"1985":1,"1991":1,"1997":1,"2002":1,"2014":1,"2022":1,"2028":1,"2030":1,"2048":1,"2049":1,"2058":1,"2060":1,"2069":1,"2080":1,"2086":1,"2098":1,"2108":1,"2123":1,"2128":1,"2133":1,"2139":1,"2146":1,"2153":1,"2160":1,"2169":1,"2180":1,"2193":1,"2203":1,"2211":1}}],["sharing",{"3":{"24":1,"25":1,"30":1,"58":1,"175":1,"228":1,"246":1,"248":1,"503":1,"597":1,"629":1,"665":1,"682":2,"715":1,"827":1,"846":1,"897":1,"964":1,"996":1,"1052":1,"1055":1,"1203":1,"1207":8,"1229":2,"1237":3,"1242":1,"1247":1,"1259":2,"1267":1,"1270":5,"1279":1,"1286":6,"1298":1,"1300":7,"1301":6,"1309":1,"1310":1,"1311":2,"1312":1,"1323":5,"1324":10,"1339":2,"1342":1,"1350":1,"1356":1,"1359":8,"1369":2,"1380":1,"1395":3,"1396":6,"1402":3,"1406":2,"1415":4,"1417":7,"1436":5,"1437":1,"1438":3,"1543":1,"1722":1,"1765":1,"1849":1,"1920":2,"1994":1,"1997":1,"2053":1,"2124":1}}],["sharefile",{"3":{"1417":2}}],["sharefilerequest",{"3":{"1417":2}}],["sharefileasync",{"3":{"1324":1,"1417":6,"2117":1}}],["sharetextrequest",{"3":{"1417":2}}],["sharetitle",{"3":{"1324":1}}],["sharelinkasync",{"3":{"1417":5,"2117":2}}],["sharelink",{"3":{"1417":4}}],["sharepagebutton",{"3":{"1324":7}}],["sharepagebuttontests",{"3":{"1199":2,"1207":4}}],["shareable",{"3":{"691":1,"1324":4,"1395":1,"1396":3,"1715":1}}],["sharescheduleasync",{"3":{"1395":1}}],["sharesthegatewayliftstrigger",{"3":{"1339":1}}],["shares",{"3":{"0":1,"74":1,"174":1,"181":2,"243":1,"265":1,"269":1,"277":1,"309":1,"665":1,"881":1,"998":1,"1000":1,"1133":1,"1229":2,"1239":1,"1255":1,"1259":2,"1267":1,"1270":1,"1271":2,"1273":1,"1277":1,"1278":1,"1286":13,"1290":1,"1300":2,"1309":1,"1310":2,"1311":10,"1323":3,"1324":10,"1339":5,"1348":1,"1350":3,"1359":8,"1380":4,"1391":1,"1395":1,"1396":3,"1402":1,"1415":1,"1416":1,"1417":3,"1427":1,"1436":9,"1438":1,"1442":1,"1475":1,"1488":1,"1539":1,"1591":1,"1698":1,"1849":1,"1851":1,"1894":1,"1998":1,"2000":1,"2071":1,"2177":1,"2232":1}}],["share",{"0":{"2111":1},"2":{"412":1,"2116":1,"2119":1},"3":{"0":8,"10":1,"15":1,"25":1,"27":1,"32":1,"46":1,"111":1,"116":1,"147":1,"157":1,"166":2,"169":1,"235":1,"248":1,"253":1,"335":1,"395":2,"412":2,"419":1,"420":1,"507":1,"633":2,"665":2,"667":1,"714":1,"733":1,"778":1,"799":1,"803":1,"827":2,"854":1,"881":1,"883":1,"891":1,"905":2,"909":1,"922":1,"926":1,"964":1,"998":1,"1018":1,"1036":1,"1040":1,"1043":1,"1044":1,"1045":1,"1066":1,"1096":1,"1116":1,"1185":1,"1192":1,"1193":1,"1196":1,"1202":1,"1203":1,"1237":1,"1239":1,"1241":1,"1242":1,"1243":3,"1247":13,"1259":1,"1270":7,"1271":3,"1284":1,"1286":27,"1297":1,"1300":12,"1301":10,"1309":6,"1310":3,"1311":8,"1312":1,"1317":1,"1320":2,"1322":1,"1323":9,"1324":31,"1325":1,"1332":1,"1337":1,"1339":12,"1346":1,"1350":5,"1351":1,"1353":1,"1358":1,"1359":38,"1366":1,"1369":3,"1380":6,"1385":2,"1387":1,"1395":30,"1396":17,"1397":1,"1402":3,"1415":2,"1416":2,"1417":51,"1425":1,"1427":2,"1435":1,"1436":21,"1438":10,"1445":1,"1453":1,"1454":1,"1467":6,"1486":1,"1489":1,"1496":5,"1591":2,"1597":1,"1630":2,"1631":2,"1639":5,"1640":1,"1641":3,"1664":1,"1669":2,"1731":1,"1732":1,"1789":1,"1805":1,"1847":1,"1859":1,"1862":1,"1865":1,"1868":1,"1894":1,"1898":1,"1900":1,"1908":2,"1909":1,"1916":1,"1932":1,"1972":1,"2021":1,"2024":1,"2056":1,"2081":1,"2086":1,"2112":1,"2115":1,"2116":4,"2117":1,"2118":2,"2119":2,"2121":1,"2122":2,"2192":1,"2232":1}}],["shared→masstransit",{"3":{"1576":1}}],["shared→domain→application→infra→api",{"3":{"1576":1}}],["shareddtos",{"3":{"1436":1}}],["sharedintegrationeventsnamespace",{"3":{"1436":1}}],["sharedadchome",{"3":{"1416":1}}],["sharedaccesskey=sas",{"3":{"1339":1}}],["sharedaccesskeyname=rootmanagesharedaccesskey",{"3":{"1339":1}}],["sharedassemblies",{"2":{"81":1,"1436":1},"3":{"80":1,"81":1,"1436":2}}],["sharedretrypolicy",{"2":{"1395":1},"3":{"1324":3,"1395":1}}],["sharedresource",{"3":{"265":1,"268":1,"905":1,"1199":15,"1203":1,"1207":2,"1324":29,"1436":5,"1496":1,"1577":2}}],["sharedownloadedfileasync",{"3":{"1324":1}}],["sharedkeywords",{"3":{"1350":2}}],["sharedkey",{"3":{"1300":1}}],["sharedlayertests",{"3":{"1199":1,"1207":2,"1436":4,"1489":1}}],["sharedlayertestsbase",{"3":{"1199":2,"1207":2,"1436":5,"1654":1}}],["sharedhttptestdoublestests",{"3":{"1199":1,"1207":2,"1436":3}}],["sharedcode",{"3":{"1436":2}}],["sharedcodes",{"3":{"1436":2}}],["sharedcodeerrors",{"3":{"1199":2,"1207":1,"1436":5}}],["sharedcategoryitems",{"3":{"1350":2}}],["sharedcachestripewebhooksecretstore",{"2":{"793":1},"3":{"792":1,"793":2,"794":1,"1589":1,"1591":1,"1596":1,"1597":1}}],["sharedstorereadoptions",{"2":{"733":1,"736":1},"3":{"733":1,"736":1,"1301":4}}],["shared",{"0":{"1284":1,"1289":1,"1300":1,"1322":1,"1336":1,"1406":1,"1465":1,"1970":1},"1":{"646":1,"647":1,"648":1,"649":1,"650":1,"651":1,"652":1,"653":1,"662":1,"663":1,"664":1,"665":1,"666":1,"667":1,"668":1,"669":1,"670":1,"695":1,"696":1,"697":1,"698":1,"699":1,"700":1,"701":1,"702":1,"703":1,"746":1,"747":1,"748":1,"749":1,"750":1,"751":1,"752":1,"753":1,"1893":1,"1894":1,"1895":1,"1896":1,"1897":1,"1898":1,"1899":1},"2":{"107":1,"109":1,"186":1,"190":1,"471":1,"472":1,"473":1,"576":1,"578":1,"938":1,"963":1,"966":1,"1051":1,"1217":1,"1222":1,"1229":3,"1230":1,"1237":2,"1271":1,"1287":2,"1309":2,"1325":1,"1340":1,"1341":1,"1344":1,"1359":1,"1396":4,"1402":2,"1404":1,"1415":1,"1504":1,"1650":2,"1653":1,"1660":1,"1687":1,"1699":2,"1702":2,"1727":2,"1781":1,"1805":1,"1829":1,"1962":1,"2071":1,"2078":1,"2157":1},"3":{"0":62,"15":1,"23":1,"26":2,"27":5,"31":2,"32":1,"33":1,"39":1,"42":1,"43":1,"45":1,"46":1,"47":1,"48":2,"53":1,"54":2,"58":1,"59":2,"61":1,"62":1,"68":1,"71":1,"74":1,"76":1,"78":2,"80":10,"81":4,"93":1,"95":2,"96":1,"98":2,"100":1,"107":1,"109":14,"110":2,"111":1,"113":1,"115":1,"121":1,"126":2,"130":2,"131":1,"132":1,"134":1,"135":1,"136":1,"139":1,"140":1,"150":1,"152":1,"155":1,"157":2,"160":2,"161":3,"166":1,"171":1,"173":2,"175":4,"176":1,"179":2,"182":1,"184":1,"186":13,"190":2,"196":1,"200":1,"202":2,"208":1,"214":2,"215":1,"216":2,"217":1,"220":1,"235":1,"237":1,"238":1,"241":1,"242":1,"243":3,"245":1,"246":2,"255":1,"256":1,"265":7,"266":2,"273":4,"283":2,"303":2,"305":5,"310":2,"311":1,"314":1,"318":2,"320":1,"325":1,"330":1,"340":1,"341":4,"346":1,"348":1,"350":1,"353":1,"360":1,"361":1,"368":1,"370":2,"382":2,"386":2,"387":1,"388":3,"391":1,"392":1,"397":2,"411":1,"412":2,"413":5,"418":3,"419":1,"420":5,"426":2,"427":1,"428":2,"430":1,"447":1,"448":2,"450":2,"457":1,"459":5,"461":2,"462":1,"463":1,"464":1,"466":1,"469":2,"470":9,"471":1,"472":2,"473":1,"475":10,"476":11,"477":13,"478":1,"482":1,"484":1,"490":1,"491":1,"492":1,"493":1,"499":2,"500":1,"505":1,"507":5,"508":1,"509":1,"510":1,"511":2,"516":1,"518":1,"523":2,"528":1,"534":1,"536":3,"538":1,"540":1,"549":2,"556":8,"558":2,"564":1,"570":1,"571":1,"572":3,"574":1,"576":6,"577":1,"578":1,"579":2,"583":6,"585":5,"587":1,"590":1,"591":2,"593":1,"594":1,"599":4,"601":4,"602":3,"603":1,"604":1,"605":1,"609":1,"610":1,"614":1,"617":2,"618":2,"624":1,"626":1,"633":1,"638":2,"640":2,"642":1,"643":4,"644":1,"646":1,"647":1,"649":2,"656":1,"657":5,"662":1,"665":1,"666":3,"667":3,"672":1,"674":4,"682":1,"690":10,"693":1,"695":1,"696":3,"698":4,"700":1,"702":2,"703":1,"707":1,"709":1,"718":1,"719":1,"725":4,"728":1,"729":1,"733":1,"741":1,"743":1,"746":1,"750":1,"753":1,"756":1,"757":1,"759":1,"760":2,"774":1,"776":1,"778":1,"782":4,"788":1,"790":5,"792":3,"793":1,"794":4,"798":3,"799":1,"803":1,"804":2,"805":1,"806":1,"810":1,"819":3,"821":1,"826":1,"827":5,"829":1,"831":2,"832":1,"833":1,"834":1,"836":1,"837":1,"838":4,"840":1,"841":1,"848":1,"860":1,"861":2,"868":1,"869":1,"872":1,"877":1,"881":6,"883":2,"884":1,"890":1,"891":1,"893":1,"903":1,"905":6,"906":1,"909":1,"910":1,"912":1,"914":1,"918":2,"920":1,"921":1,"922":1,"926":1,"938":1,"939":1,"946":1,"954":1,"956":1,"960":1,"962":1,"963":5,"964":3,"966":2,"967":1,"972":2,"973":2,"980":1,"982":1,"995":1,"996":1,"999":2,"1000":1,"1004":8,"1006":1,"1025":1,"1026":4,"1035":2,"1040":1,"1042":2,"1049":2,"1050":10,"1051":1,"1052":4,"1054":2,"1055":5,"1057":1,"1058":2,"1059":14,"1066":1,"1067":2,"1074":2,"1078":1,"1087":1,"1094":1,"1096":1,"1100":1,"1116":6,"1119":1,"1124":3,"1126":1,"1137":1,"1147":1,"1150":1,"1164":1,"1168":3,"1171":1,"1183":1,"1191":1,"1192":9,"1193":1,"1198":12,"1199":330,"1201":1,"1202":2,"1203":258,"1206":9,"1207":996,"1208":2,"1211":4,"1212":19,"1214":1,"1217":1,"1219":3,"1220":1,"1222":2,"1223":2,"1224":1,"1225":1,"1226":2,"1227":2,"1228":1,"1229":68,"1230":2,"1234":12,"1235":2,"1236":2,"1237":74,"1239":1,"1240":1,"1241":4,"1242":1,"1243":1,"1246":2,"1247":25,"1251":1,"1253":2,"1254":3,"1257":1,"1259":20,"1260":1,"1263":1,"1264":1,"1265":2,"1267":1,"1270":36,"1271":29,"1275":1,"1276":5,"1278":1,"1282":1,"1284":1,"1286":108,"1287":4,"1289":6,"1290":1,"1291":6,"1293":2,"1295":1,"1296":3,"1297":9,"1298":1,"1299":2,"1300":400,"1301":28,"1303":4,"1304":3,"1305":2,"1308":2,"1309":59,"1310":1,"1311":107,"1312":38,"1313":2,"1318":1,"1320":2,"1322":2,"1323":55,"1324":173,"1325":2,"1326":1,"1328":3,"1330":6,"1333":1,"1334":1,"1336":5,"1337":3,"1338":6,"1339":74,"1340":1,"1341":10,"1342":2,"1344":4,"1347":13,"1348":8,"1349":9,"1350":272,"1351":1,"1352":1,"1353":4,"1355":1,"1358":2,"1359":371,"1364":1,"1368":1,"1369":18,"1371":1,"1372":1,"1373":3,"1375":1,"1376":1,"1377":2,"1378":1,"1379":5,"1380":33,"1383":1,"1384":3,"1385":1,"1386":1,"1388":2,"1389":3,"1391":2,"1393":1,"1395":200,"1396":291,"1398":2,"1399":3,"1401":3,"1402":146,"1403":1,"1404":7,"1405":4,"1406":2,"1409":1,"1410":3,"1411":1,"1412":1,"1413":3,"1414":5,"1415":192,"1416":63,"1417":114,"1423":1,"1427":3,"1430":1,"1431":5,"1432":1,"1433":3,"1434":2,"1436":363,"1437":15,"1438":40,"1441":2,"1442":2,"1443":3,"1445":2,"1447":9,"1449":2,"1450":3,"1453":1,"1454":3,"1455":5,"1456":1,"1459":1,"1462":1,"1463":1,"1464":1,"1465":1,"1467":23,"1469":1,"1472":2,"1473":3,"1475":2,"1476":1,"1477":1,"1478":2,"1481":1,"1486":3,"1487":9,"1488":9,"1489":18,"1490":3,"1491":6,"1492":4,"1496":66,"1497":5,"1500":1,"1503":1,"1504":4,"1513":1,"1518":1,"1523":1,"1524":1,"1525":1,"1526":37,"1528":1,"1531":1,"1532":1,"1534":2,"1535":4,"1540":1,"1542":3,"1544":2,"1551":1,"1555":1,"1556":2,"1557":2,"1565":2,"1566":1,"1568":1,"1570":1,"1572":1,"1576":1,"1577":27,"1579":1,"1582":1,"1585":1,"1586":2,"1589":2,"1590":1,"1591":18,"1595":1,"1596":4,"1597":1,"1599":3,"1600":3,"1605":1,"1627":2,"1630":1,"1631":2,"1639":1,"1640":1,"1641":3,"1642":1,"1643":1,"1644":2,"1648":1,"1649":1,"1650":3,"1651":2,"1653":4,"1656":2,"1660":4,"1662":3,"1663":3,"1664":1,"1665":1,"1667":1,"1668":2,"1669":8,"1670":1,"1671":3,"1672":1,"1673":1,"1675":1,"1677":1,"1685":4,"1686":4,"1687":1,"1693":1,"1699":2,"1701":1,"1702":5,"1707":1,"1711":1,"1714":2,"1716":1,"1721":1,"1724":1,"1727":2,"1731":1,"1739":1,"1741":1,"1748":1,"1760":2,"1761":1,"1764":6,"1765":1,"1769":1,"1772":1,"1781":3,"1783":1,"1786":1,"1790":4,"1805":3,"1806":1,"1807":1,"1815":1,"1821":1,"1827":4,"1829":1,"1833":1,"1835":1,"1841":1,"1842":1,"1848":3,"1849":1,"1851":3,"1852":1,"1853":2,"1856":1,"1869":1,"1875":1,"1884":1,"1891":1,"1892":2,"1893":3,"1895":3,"1896":2,"1898":2,"1899":1,"1916":2,"1917":2,"1920":1,"1922":3,"1923":3,"1929":1,"1939":1,"1946":1,"1949":3,"1962":2,"1963":1,"1971":1,"1972":2,"1976":1,"1977":1,"1983":1,"1989":1,"1991":1,"1996":1,"1997":1,"1998":1,"1999":1,"2000":2,"2001":5,"2002":4,"2009":1,"2011":1,"2014":2,"2015":1,"2016":1,"2017":1,"2021":1,"2024":2,"2028":2,"2034":1,"2036":1,"2038":1,"2042":1,"2043":4,"2047":1,"2049":1,"2052":1,"2055":2,"2060":2,"2064":1,"2066":1,"2067":1,"2068":1,"2071":1,"2075":1,"2078":2,"2079":2,"2080":1,"2083":2,"2084":1,"2085":1,"2088":1,"2101":1,"2104":3,"2107":2,"2111":3,"2112":1,"2114":1,"2116":2,"2118":2,"2119":2,"2120":1,"2121":3,"2122":1,"2123":3,"2128":1,"2130":1,"2131":3,"2132":2,"2143":1,"2150":3,"2151":1,"2152":2,"2154":1,"2155":1,"2157":1,"2160":2,"2166":1,"2167":1,"2168":1,"2169":1,"2175":1,"2178":2,"2186":1,"2188":1,"2194":1,"2195":1,"2198":1,"2203":2,"2208":1,"2214":1,"2215":1,"2221":1,"2227":5,"2230":3,"2232":13}}],["sha",{"2":{"1589":1,"1597":1},"3":{"0":5,"157":2,"161":1,"225":1,"231":1,"368":1,"373":3,"374":2,"418":1,"424":1,"428":1,"429":1,"629":1,"854":1,"868":1,"870":1,"873":1,"905":1,"1018":3,"1091":1,"1212":2,"1286":1,"1290":1,"1294":1,"1295":1,"1300":8,"1309":1,"1311":4,"1418":1,"1426":1,"1427":1,"1436":1,"1438":1,"1445":4,"1452":2,"1455":8,"1456":2,"1459":4,"1461":3,"1465":1,"1466":1,"1467":1,"1469":1,"1475":3,"1526":1,"1589":1,"1591":1,"1596":1,"1597":1,"1641":2,"1666":1,"1667":1,"1765":1,"1767":1,"1900":1,"1902":2,"1905":2,"1908":2,"1982":1,"2176":1,"2180":1}}],["sha512",{"1":{"307":1,"308":1,"309":1,"310":1,"311":1,"312":1,"313":1,"314":1,"1900":1,"1901":1,"1902":1,"1903":1,"1904":1,"1905":1},"2":{"310":1,"946":1},"3":{"0":3,"307":1,"308":1,"309":1,"310":3,"311":1,"314":1,"945":2,"946":4,"1212":2,"1292":1,"1300":4,"1324":1,"1436":1,"1576":1,"1577":1,"1667":1,"1747":1,"1763":1,"1764":1,"1765":4,"1899":1,"1900":1,"1902":3,"1904":1,"1905":3,"2208":1}}],["symptoms",{"3":{"770":1,"2081":1}}],["symptom",{"3":{"234":1,"310":1,"664":1,"666":1,"821":1,"956":1,"963":1,"1339":5,"1396":2,"1430":1,"1436":3,"1651":1,"1970":1,"1989":1}}],["symmetricsecuritykey",{"3":{"1300":1,"1311":1}}],["symmetrically",{"3":{"1259":1,"1286":1,"1300":1,"1323":1,"1324":1,"1436":1}}],["symmetrical",{"3":{"930":1}}],["symmetric",{"3":{"24":1,"30":2,"31":2,"32":1,"33":1,"698":1,"764":2,"766":1,"819":1,"1212":1,"1237":1,"1267":1,"1270":1,"1286":1,"1288":1,"1300":3,"1312":3,"1324":3,"1350":1,"1415":1,"1441":1,"1450":1,"1843":1,"1893":2,"1894":1,"1895":1,"1898":1,"1899":2,"1920":1,"1953":1,"1959":1,"2009":1,"2021":1,"2028":1,"2032":1,"2230":1}}],["symmetry",{"3":{"0":1,"870":1,"1035":1,"1270":3,"1286":1,"1311":2,"1312":1,"1339":1,"1359":1,"1395":2,"1396":2,"1415":3,"1927":1,"2183":1}}],["symbolic",{"3":{"1467":1}}],["symbolically",{"3":{"1311":1}}],["symbols",{"3":{"636":1,"954":2,"1034":1,"1037":1,"1324":1,"1417":1,"1436":2,"1673":1}}],["symbol",{"3":{"0":1,"336":1,"619":1,"633":4,"634":1,"1311":2,"1324":11,"1396":1,"1417":1,"1432":1,"1488":1,"1492":1,"1591":1,"1600":1}}],["synonym",{"3":{"472":1}}],["synonyms",{"3":{"0":1,"798":1}}],["syncquestionanswers",{"3":{"1350":1,"1359":1}}],["synccategoryitems",{"3":{"1350":1,"1359":2}}],["synctrackedkey",{"3":{"1286":1}}],["syncing",{"3":{"1259":1,"1350":1,"1417":2}}],["syncstrategy",{"3":{"1526":1,"1535":1}}],["syncsessionquestionanswers",{"3":{"1359":1}}],["syncsessionchildren",{"3":{"1359":1}}],["syncscopedreadcontroller",{"3":{"1199":2,"1207":1}}],["syncs",{"3":{"988":1,"1252":1,"1259":1,"1359":3,"1417":1,"1632":2,"2220":1}}],["syncregistrationsasync",{"2":{"707":1,"710":1},"3":{"707":1,"710":1,"1323":1}}],["syncfusion",{"3":{"650":1}}],["syncasync",{"2":{"507":1},"3":{"507":1,"1324":7,"1359":3}}],["synchronizes",{"3":{"1632":1}}],["synchronize",{"2":{"1460":1},"3":{"1359":1,"1459":2,"1460":1}}],["synchronized",{"3":{"23":1,"643":1,"1324":1}}],["synchronization",{"3":{"481":2,"482":1,"485":1,"1229":1,"1286":1,"1310":1,"1312":1,"1324":2,"1380":1,"1395":2,"2232":1}}],["synchronizationcontext",{"2":{"481":1},"3":{"481":1,"1359":1}}],["synchronously",{"3":{"97":1,"601":1,"1259":4,"1323":1,"1324":1,"1335":1,"1339":2,"1350":1,"1395":6,"1402":1,"1413":1,"1427":1,"1467":1,"1792":1,"2016":1,"2111":1}}],["synchronous",{"1":{"50":1,"51":1,"52":1,"53":1,"54":1,"55":1},"3":{"0":3,"50":1,"52":1,"59":1,"68":1,"109":1,"184":1,"444":1,"603":1,"727":1,"743":3,"861":1,"933":1,"1059":2,"1060":1,"1202":1,"1203":1,"1212":1,"1213":1,"1220":1,"1229":3,"1247":2,"1259":10,"1270":1,"1274":1,"1283":1,"1286":16,"1297":1,"1300":5,"1301":1,"1309":1,"1311":8,"1312":4,"1318":1,"1323":2,"1324":10,"1339":1,"1349":2,"1350":2,"1359":10,"1372":2,"1380":4,"1395":6,"1396":3,"1402":4,"1417":13,"1431":1,"1436":10,"1496":1,"1526":1,"1543":1,"1544":1,"1549":1,"1656":2,"1789":1,"1790":2,"1793":1,"1805":1,"2016":1,"2020":1,"2030":1,"2044":1,"2190":1,"2199":1,"2203":1,"2232":1}}],["synced",{"3":{"0":1,"1212":1,"1347":1,"1359":7,"1369":1,"1632":2,"1636":1}}],["sync",{"3":{"0":1,"209":1,"243":1,"273":1,"274":1,"399":1,"434":1,"483":1,"507":1,"509":1,"649":1,"650":1,"1117":1,"1212":1,"1220":1,"1229":1,"1237":1,"1270":2,"1286":6,"1300":1,"1311":2,"1323":1,"1324":8,"1339":4,"1342":1,"1347":2,"1350":16,"1358":1,"1359":16,"1362":1,"1369":4,"1395":2,"1396":4,"1402":1,"1416":1,"1417":1,"1427":1,"1436":3,"1438":3,"1480":1,"1496":2,"1526":2,"1535":1,"1544":1,"1549":1,"1560":1,"1577":2,"1629":2,"1630":1,"1631":1,"1635":1,"1636":1,"1638":1,"1639":1,"1660":1,"1669":1,"1673":1,"1833":1,"1971":2,"2083":1,"2136":1,"2157":1,"2159":1,"2220":1}}],["syntactically",{"3":{"1323":1,"1324":1,"1395":1}}],["syntactic",{"3":{"1191":1,"1204":1,"1500":1}}],["syntax",{"2":{"2044":1},"3":{"0":1,"136":2,"332":1,"981":1,"1184":1,"1214":2,"1237":1,"1247":3,"1259":1,"1262":1,"1264":1,"1270":1,"1271":1,"1286":7,"1300":3,"1309":5,"1311":2,"1316":1,"1323":3,"1324":8,"1339":1,"1350":1,"1395":1,"1417":4,"1436":5,"1438":1,"1455":1,"1486":1,"1493":1,"1640":1,"1928":1,"1990":1,"2044":1}}],["synthesis",{"3":{"1417":2}}],["synthesize",{"3":{"1417":1}}],["synthesizer",{"3":{"1417":2}}],["synthesizes",{"3":{"1300":1,"1396":1,"1488":1}}],["synthesized",{"3":{"1300":2,"1311":1,"1312":1,"1324":1,"1436":1}}],["synthesizing",{"3":{"109":1,"1300":1,"1312":2,"1438":1}}],["synthetictrafficheadername",{"3":{"827":1,"1339":3}}],["synthetictrafficsecret",{"2":{"827":1,"1467":1,"2024":1},"3":{"827":2,"1339":6,"1458":1,"1467":2,"2024":1}}],["synthetic",{"2":{"1458":2,"1468":1,"1470":1},"3":{"0":1,"173":1,"179":1,"572":1,"597":1,"598":1,"599":2,"825":2,"827":6,"1244":1,"1247":1,"1337":2,"1339":12,"1350":1,"1369":1,"1395":2,"1436":1,"1455":3,"1458":5,"1461":1,"1466":1,"1467":5,"1468":1,"1470":1,"1474":1,"1476":1,"1488":1,"1535":1,"1591":1,"1670":1}}],["systematically",{"3":{"1526":1,"1531":1}}],["systematic",{"3":{"1436":1,"1496":1,"1530":1,"1532":1}}],["systempreferred",{"3":{"1417":1}}],["systemprefersdark",{"2":{"273":1},"3":{"273":1,"1324":1,"2083":1}}],["systemprompt",{"2":{"1426":1},"3":{"1018":1,"1369":2,"1426":1,"1427":2}}],["systemnethttpclient",{"2":{"73":1},"3":{"73":1}}],["systems",{"3":{"0":1,"61":1,"689":1,"812":1,"1011":1,"1116":1,"1238":1,"1359":2,"1380":1,"1395":1,"1426":1,"1776":1,"1789":1,"1792":1,"1802":1,"1815":1,"1835":1,"1846":1,"1847":1,"1853":1,"1912":1,"2027":1,"2108":1,"2160":1,"2162":1,"2169":1,"2171":1,"2193":1,"2213":1,"2217":1,"2218":1,"2220":6,"2238":1}}],["system",{"0":{"1503":1,"1557":1,"1629":1,"1763":1,"1777":1},"1":{"1313":1,"1314":1,"1315":1,"1316":1,"1317":1,"1318":1,"1319":1,"1320":1,"1321":1,"1322":1,"1323":1},"2":{"72":1,"397":1,"471":1,"475":1,"476":1,"477":1,"591":1,"665":1,"681":1,"798":1,"800":1,"803":1,"804":1,"805":1,"960":1,"966":1,"1049":1,"1054":1,"1163":1,"1196":1,"1229":5,"1270":1,"1311":2,"1324":3,"1332":1,"1344":1,"1369":1,"1400":1,"1415":1,"1447":1,"1480":2,"1687":1,"2156":1,"2186":1},"3":{"0":4,"58":1,"72":1,"78":2,"79":1,"81":1,"125":1,"244":1,"259":1,"266":1,"274":1,"340":1,"349":2,"350":2,"351":1,"361":1,"397":3,"400":1,"413":1,"419":1,"427":1,"470":2,"471":1,"475":1,"476":1,"477":1,"517":1,"520":1,"536":1,"539":1,"591":1,"593":1,"607":2,"611":1,"612":1,"665":1,"681":1,"691":2,"696":1,"698":4,"699":1,"700":2,"702":3,"706":1,"717":1,"741":1,"765":1,"792":1,"798":1,"799":2,"800":1,"801":1,"803":1,"804":1,"805":3,"809":1,"810":1,"853":1,"899":1,"947":1,"948":1,"954":2,"960":1,"963":1,"964":1,"966":1,"980":3,"1013":1,"1018":5,"1035":1,"1049":1,"1050":1,"1054":1,"1058":1,"1082":1,"1090":1,"1091":4,"1099":1,"1115":1,"1134":1,"1140":1,"1142":1,"1145":1,"1154":1,"1155":1,"1157":1,"1163":1,"1185":1,"1192":2,"1196":1,"1202":2,"1203":2,"1212":3,"1213":3,"1216":2,"1221":1,"1223":1,"1227":1,"1229":17,"1231":1,"1235":1,"1237":34,"1241":1,"1247":59,"1249":1,"1253":2,"1259":19,"1264":1,"1270":25,"1271":5,"1274":1,"1275":2,"1286":67,"1287":1,"1293":1,"1294":1,"1300":81,"1301":11,"1302":1,"1309":24,"1310":7,"1311":49,"1312":8,"1313":2,"1318":2,"1323":41,"1324":94,"1325":1,"1332":1,"1333":2,"1339":24,"1340":1,"1342":1,"1344":2,"1350":14,"1359":71,"1366":3,"1369":13,"1370":1,"1380":7,"1381":1,"1395":39,"1396":62,"1400":1,"1402":6,"1403":1,"1414":1,"1415":29,"1416":8,"1417":68,"1422":1,"1424":1,"1426":1,"1427":20,"1431":1,"1435":3,"1436":124,"1438":3,"1439":1,"1441":3,"1443":6,"1447":1,"1449":1,"1453":1,"1455":3,"1457":1,"1458":3,"1465":2,"1467":6,"1474":1,"1476":1,"1480":3,"1488":2,"1492":2,"1496":6,"1499":1,"1523":1,"1526":1,"1534":1,"1535":1,"1537":1,"1549":1,"1550":1,"1553":1,"1559":1,"1565":1,"1566":1,"1570":1,"1571":1,"1572":2,"1574":1,"1577":2,"1581":1,"1582":1,"1583":1,"1588":1,"1591":2,"1595":1,"1596":1,"1597":1,"1629":1,"1630":4,"1631":2,"1632":31,"1633":1,"1636":1,"1638":3,"1639":3,"1641":31,"1660":1,"1662":1,"1664":1,"1666":1,"1669":1,"1670":1,"1687":1,"1708":1,"1748":1,"1752":1,"1763":1,"1765":3,"1769":1,"1770":2,"1772":1,"1776":1,"1777":1,"1788":1,"1794":1,"1795":1,"1796":3,"1799":1,"1801":1,"1802":1,"1812":1,"1813":1,"1831":1,"1835":1,"1836":2,"1844":1,"1851":1,"1869":2,"1888":1,"1889":3,"1892":1,"1893":2,"1899":1,"1907":2,"1916":1,"1921":1,"1922":1,"1932":1,"1951":1,"1952":1,"1974":3,"1976":2,"1977":1,"1979":2,"1989":1,"2003":1,"2007":1,"2024":1,"2030":1,"2037":1,"2038":1,"2064":1,"2067":2,"2079":3,"2091":1,"2113":1,"2121":1,"2155":2,"2156":4,"2168":1,"2171":1,"2176":1,"2186":2,"2193":1,"2195":1,"2196":1,"2197":1,"2203":1,"2209":1,"2220":2,"2227":1}}],["swipe",{"3":{"1324":1,"1416":1,"1417":2,"1669":1}}],["switchboard",{"3":{"2114":1}}],["switchboards",{"3":{"1542":1}}],["switchgeneration",{"3":{"1396":2}}],["switchable",{"3":{"572":1,"578":1,"1417":1,"1648":1}}],["switching",{"3":{"265":1,"267":1,"415":1,"454":1,"733":1,"799":1,"882":1,"1247":1,"1276":1,"1286":3,"1300":2,"1311":2,"1323":1,"1324":1,"1369":1,"1390":1,"1395":2,"1416":1,"1417":2,"1427":1,"1436":2,"1662":1,"1854":1,"1897":1}}],["switchexpressionexception",{"3":{"1436":3}}],["switchexpressionfixture",{"3":{"1199":1,"1207":1,"1436":4}}],["switched",{"3":{"0":2,"235":1,"556":1,"667":1,"914":1,"930":1,"941":1,"1232":1,"1259":1,"1270":1,"1286":4,"1300":1,"1309":1,"1311":4,"1312":1,"1313":1,"1323":2,"1324":3,"1332":1,"1339":2,"1347":1,"1350":1,"1359":3,"1380":1,"1395":5,"1396":11,"1415":2,"1417":2,"1419":1,"1436":3,"1438":2,"1450":1,"1467":1,"1572":1,"1631":1,"1668":1,"1720":1,"1845":1,"1938":1,"1970":1}}],["switchers",{"3":{"1415":1}}],["switcher",{"3":{"0":1,"265":5,"266":1,"273":1,"1300":1,"1311":5,"1323":1,"1324":5,"1415":1,"1417":4,"1436":1,"1438":1,"1577":1,"1644":1,"1653":4,"1669":1,"2083":2,"2086":1}}],["switches",{"1":{"2081":1,"2082":1,"2083":1,"2084":1,"2085":1,"2086":1},"3":{"0":2,"109":1,"171":1,"179":1,"265":1,"432":1,"452":2,"609":1,"674":1,"684":1,"939":1,"1175":1,"1184":1,"1270":2,"1277":1,"1286":7,"1300":1,"1301":1,"1306":2,"1309":2,"1310":2,"1311":1,"1320":2,"1323":3,"1324":7,"1326":1,"1333":1,"1339":3,"1350":1,"1355":1,"1359":3,"1395":4,"1396":6,"1402":1,"1416":2,"1427":1,"1436":4,"1441":2,"1456":1,"1467":4,"1481":1,"1496":1,"1675":1,"1701":1,"1921":1,"2081":1,"2210":1}}],["switch",{"0":{"1288":1,"1724":1},"2":{"966":1,"1538":1,"1862":1},"3":{"0":8,"31":1,"32":1,"59":1,"142":1,"150":2,"164":1,"166":2,"171":1,"195":1,"243":2,"265":4,"267":1,"290":1,"295":3,"296":1,"299":1,"301":1,"302":1,"361":1,"440":1,"572":1,"576":1,"600":1,"602":1,"618":1,"644":1,"666":1,"674":1,"691":1,"819":1,"838":1,"879":1,"916":1,"963":1,"964":1,"965":1,"966":1,"1034":4,"1035":1,"1036":1,"1051":1,"1075":2,"1091":1,"1107":2,"1108":1,"1212":1,"1220":1,"1229":1,"1235":1,"1237":2,"1241":1,"1243":1,"1247":4,"1259":2,"1260":1,"1264":1,"1270":4,"1273":2,"1280":1,"1286":20,"1288":1,"1297":2,"1300":6,"1303":1,"1311":15,"1312":2,"1313":1,"1323":6,"1324":25,"1336":1,"1339":9,"1347":1,"1350":7,"1359":2,"1363":1,"1380":1,"1395":24,"1396":39,"1399":1,"1402":6,"1415":3,"1416":3,"1417":22,"1421":2,"1427":3,"1431":1,"1436":22,"1438":5,"1440":1,"1443":1,"1444":1,"1446":2,"1447":1,"1450":1,"1467":9,"1475":1,"1479":1,"1488":2,"1496":3,"1507":1,"1526":1,"1535":1,"1538":2,"1591":2,"1602":1,"1607":1,"1608":1,"1631":1,"1653":1,"1661":2,"1674":1,"1686":1,"1692":1,"1712":1,"1717":1,"1724":2,"1741":1,"1742":1,"1795":1,"1805":1,"1842":1,"1856":1,"1862":1,"1894":1,"1970":1,"1972":1,"2011":1,"2013":1,"2020":2,"2073":1,"2083":1,"2086":2,"2111":1,"2126":1,"2134":1,"2135":1,"2138":1,"2139":1,"2145":1,"2179":1,"2180":1,"2197":1}}],["swagger",{"3":{"1534":1}}],["swamping",{"3":{"714":1}}],["swallowing",{"0":{"1919":1},"3":{"230":1,"537":1,"897":1,"898":2,"901":1,"989":1,"1237":1,"1252":1,"1259":2,"1311":2,"1323":1,"1324":4,"1339":1,"1355":1,"1359":1,"1396":2,"1413":1,"1415":1,"1417":4,"1436":1,"1455":1,"1459":1,"1496":1,"1946":1,"2165":1}}],["swallow",{"3":{"0":1,"111":1,"255":1,"258":1,"517":1,"534":2,"536":1,"538":1,"896":1,"897":5,"898":2,"899":2,"901":1,"1247":6,"1259":5,"1270":1,"1301":1,"1309":1,"1311":2,"1323":1,"1324":2,"1339":1,"1359":2,"1389":1,"1395":3,"1396":6,"1402":4,"1415":2,"1417":5,"1438":2,"1665":1,"1919":2,"1933":1,"1934":1,"1940":1,"1946":1,"1949":1,"1950":1}}],["swallowed",{"3":{"0":1,"157":1,"400":1,"461":1,"854":1,"896":2,"897":3,"898":1,"901":1,"988":1,"1212":1,"1247":8,"1286":1,"1300":1,"1301":2,"1307":1,"1309":1,"1311":4,"1323":2,"1324":10,"1355":1,"1358":1,"1359":1,"1369":1,"1383":2,"1395":11,"1396":7,"1399":1,"1402":5,"1408":1,"1415":1,"1417":7,"1433":1,"1436":2,"1438":3,"1443":1,"1455":1,"1665":1,"1909":2,"1918":1,"1919":1,"2006":1,"2117":1,"2156":1}}],["swallows",{"3":{"0":2,"125":1,"195":1,"230":1,"759":1,"897":1,"988":1,"1259":1,"1263":1,"1270":1,"1275":1,"1286":2,"1300":2,"1309":1,"1311":2,"1324":6,"1359":1,"1395":1,"1396":3,"1402":3,"1415":1,"1416":1,"1417":6,"1431":1,"1436":3,"1438":1,"1665":1,"1933":1,"1949":2}}],["swapping",{"3":{"0":2,"54":1,"472":1,"478":1,"798":1,"799":1,"1212":1,"1259":1,"1286":2,"1300":1,"1301":1,"1312":1,"1323":1,"1324":4,"1359":2,"1361":1,"1369":1,"1378":1,"1396":3,"1415":1,"1420":1,"1436":2,"1438":1,"1793":1,"1825":1,"1902":1,"2017":1,"2073":1,"2079":1,"2150":1}}],["swappable",{"1":{"240":1,"241":1,"242":1,"243":1,"244":1,"245":1,"246":1,"247":1,"248":1,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"260":1,"261":1},"3":{"0":1,"240":1,"1212":1,"1248":1,"1259":1,"1270":1,"1286":4,"1309":1,"1324":3,"1359":3,"1396":3,"1417":4,"1427":1,"1436":2,"1668":1,"2232":1}}],["swapped",{"3":{"0":3,"244":1,"380":1,"499":1,"636":1,"800":1,"803":1,"1259":3,"1300":1,"1311":1,"1324":1,"1350":1,"1359":2,"1396":1,"1402":1,"1436":4,"1496":1,"1684":1,"1736":1,"1902":2,"1982":1,"2026":1,"2178":1}}],["swaps",{"3":{"0":4,"178":1,"225":1,"434":1,"440":1,"633":1,"832":2,"881":1,"1018":1,"1212":2,"1239":1,"1286":1,"1300":1,"1307":1,"1311":3,"1316":1,"1323":1,"1324":2,"1339":1,"1359":2,"1378":1,"1380":1,"1383":1,"1395":4,"1396":5,"1413":1,"1415":2,"1417":2,"1431":1,"1436":1,"1438":1,"1440":1,"1467":1,"1477":1,"1488":1,"1492":1,"1526":1,"1936":1,"1948":1,"1972":1,"1975":1,"1979":1,"2013":1,"2075":1,"2119":1,"2123":1,"2126":1}}],["swap",{"3":{"0":3,"31":1,"68":1,"150":1,"241":1,"243":2,"244":1,"247":1,"253":1,"257":1,"259":2,"261":1,"265":1,"350":1,"454":1,"632":1,"651":1,"664":1,"732":1,"801":1,"1018":2,"1019":1,"1075":1,"1076":1,"1077":1,"1079":1,"1090":1,"1091":1,"1092":2,"1142":1,"1212":2,"1247":1,"1257":1,"1259":1,"1286":2,"1288":1,"1301":1,"1309":2,"1311":1,"1312":1,"1323":5,"1324":2,"1339":1,"1369":1,"1380":1,"1396":7,"1413":1,"1415":1,"1417":1,"1427":3,"1435":1,"1436":5,"1438":1,"1442":1,"1474":1,"1492":1,"1534":1,"1553":1,"1571":1,"1577":4,"1586":1,"1724":1,"1727":1,"1765":1,"1790":1,"1791":1,"1803":1,"1847":1,"1849":1,"1853":1,"1916":2,"1922":1,"1933":1,"1979":1,"2028":1,"2033":1,"2060":1,"2075":1,"2078":1,"2111":1,"2128":1,"2150":2,"2177":1,"2232":1}}],["swelling",{"3":{"1395":1}}],["sweepdeadlettersasync",{"3":{"1436":1}}],["sweepharness",{"3":{"1199":2,"1207":1}}],["sweeper",{"3":{"853":1,"854":1,"1259":2,"1293":1,"1294":1,"1300":3,"1324":1,"1415":2,"1641":1,"1667":1,"1765":1}}],["sweeping",{"3":{"148":1,"284":1,"1300":1,"1324":1,"1359":1,"1406":1,"1415":1,"1961":1,"2196":1}}],["sweeps",{"3":{"0":2,"24":1,"145":1,"635":1,"788":1,"793":1,"839":1,"937":1,"1212":2,"1259":2,"1276":2,"1278":1,"1284":1,"1286":6,"1300":1,"1313":1,"1317":1,"1323":1,"1359":1,"1438":3,"1490":1,"1492":1,"1526":1,"1585":1,"1661":1,"1664":2,"1731":2,"1764":1,"1845":1}}],["sweep",{"0":{"539":1,"2167":1},"3":{"0":9,"24":1,"27":1,"123":1,"135":1,"146":1,"147":3,"148":1,"149":1,"151":1,"187":2,"245":2,"246":1,"254":1,"257":1,"258":1,"260":1,"267":1,"369":1,"465":1,"497":1,"534":6,"536":7,"537":2,"538":3,"539":9,"540":2,"541":4,"574":1,"579":1,"634":1,"696":1,"707":2,"713":1,"715":2,"720":1,"725":1,"733":1,"737":1,"739":1,"741":2,"743":2,"744":1,"760":1,"780":1,"782":1,"789":1,"792":1,"794":1,"798":1,"799":1,"802":1,"809":6,"810":5,"812":2,"813":3,"814":1,"815":2,"891":1,"903":2,"905":9,"906":1,"907":2,"909":1,"941":2,"942":1,"955":1,"1003":1,"1004":1,"1016":1,"1018":1,"1022":1,"1036":1,"1041":1,"1042":1,"1044":1,"1074":1,"1075":1,"1084":1,"1118":1,"1212":1,"1256":2,"1259":8,"1264":1,"1270":2,"1273":1,"1275":2,"1276":2,"1286":34,"1290":4,"1300":8,"1311":2,"1316":1,"1317":1,"1318":1,"1323":8,"1324":2,"1359":3,"1367":2,"1368":1,"1369":1,"1395":2,"1396":4,"1436":7,"1438":5,"1459":1,"1460":2,"1461":1,"1467":1,"1475":5,"1483":1,"1485":1,"1489":1,"1490":1,"1492":1,"1496":33,"1500":1,"1526":1,"1576":2,"1577":3,"1585":1,"1591":2,"1600":3,"1648":1,"1654":1,"1664":1,"1672":1,"1693":1,"1737":1,"1765":3,"1844":2,"1845":1,"1948":1,"1959":1,"1960":1,"1965":2,"1982":1,"1984":2,"2055":1,"2112":1,"2115":1,"2161":1,"2166":2,"2167":9,"2168":8,"2169":2,"2183":1,"2190":1,"2215":1,"2232":1}}],["swept",{"3":{"0":2,"78":1,"249":1,"633":1,"905":1,"1004":1,"1077":1,"1256":1,"1277":1,"1286":2,"1290":1,"1300":2,"1333":1,"1359":2,"1436":1,"1441":1,"1455":1,"1465":1,"1531":1,"1569":1,"1735":1,"1736":1,"1785":1,"2112":1}}],["se",{"3":{"1686":1}}],["seq",{"2":{"760":1},"3":{"760":1}}],["sequencing",{"0":{"1617":1},"3":{"127":1,"151":1,"383":1,"518":1,"1100":1,"1359":1,"1395":1,"1467":1,"1944":1}}],["sequenceequal",{"2":{"1902":1},"3":{"1286":1,"1300":1,"1436":1,"1902":1}}],["sequenced",{"3":{"1191":1,"1323":1,"1436":2}}],["sequences",{"3":{"0":1,"126":1,"127":1,"1262":1,"1323":1,"1396":1,"1423":1,"1436":2,"1826":2}}],["sequence",{"0":{"1824":1,"1839":1,"2095":1},"3":{"0":3,"57":1,"115":1,"117":2,"119":1,"121":2,"122":1,"127":1,"256":1,"279":1,"281":1,"283":1,"459":1,"523":1,"560":1,"572":3,"633":1,"651":1,"698":1,"715":1,"733":1,"749":1,"750":1,"751":1,"838":1,"1069":1,"1100":2,"1145":1,"1212":1,"1232":1,"1237":5,"1259":4,"1262":4,"1270":7,"1286":5,"1289":1,"1300":4,"1301":1,"1309":2,"1311":7,"1316":2,"1323":8,"1324":4,"1339":2,"1343":1,"1350":1,"1359":22,"1395":8,"1396":2,"1399":1,"1402":3,"1415":3,"1416":4,"1417":1,"1427":1,"1436":17,"1438":2,"1441":1,"1452":1,"1455":1,"1463":1,"1467":1,"1473":1,"1474":1,"1478":1,"1488":1,"1489":1,"1577":1,"1591":1,"1631":1,"1653":2,"1656":1,"1658":1,"1685":1,"1727":1,"1779":1,"1824":3,"1825":1,"1826":3,"1838":1,"1839":1,"1878":1,"1881":1,"1883":1,"1886":1,"1905":1,"1915":1,"1941":1,"2091":1,"2095":1,"2096":1,"2098":2,"2107":1,"2196":1}}],["sequentially",{"3":{"1":1,"122":1,"538":1,"727":1,"759":1,"914":1,"916":1,"1263":1,"1271":1,"1278":1,"1310":1,"1322":1,"1339":1,"1359":3,"1415":2,"1429":1,"1436":2,"1488":1,"1852":1,"2168":1}}],["sequential",{"3":{"0":1,"122":1,"286":1,"470":1,"477":1,"537":1,"690":1,"798":1,"987":1,"988":1,"992":1,"1116":1,"1233":1,"1237":2,"1263":1,"1270":2,"1271":1,"1286":4,"1292":1,"1300":1,"1322":1,"1342":1,"1350":1,"1359":8,"1369":1,"1395":2,"1396":5,"1402":1,"1415":4,"1436":2,"1438":1,"1455":2,"1577":1,"1631":2,"1664":1,"1840":1,"1845":1,"2001":1,"2164":1,"2169":1}}],["segoe",{"3":{"1324":2,"2075":1}}],["segregation",{"3":{"1212":1,"1237":2,"1259":1,"1260":1,"1270":3,"1278":1,"1286":3,"1291":1,"1300":7,"1309":2,"1311":3,"1323":1,"1324":2,"1359":5,"1380":1,"1395":4,"1396":1,"1415":1,"1417":1,"1427":1}}],["segregated",{"3":{"545":1,"1272":1,"1278":1,"1311":1,"1359":1}}],["segmentversionedprobecontroller",{"3":{"1199":3,"1207":1}}],["segmented",{"3":{"178":1}}],["segment",{"3":{"0":1,"123":1,"135":1,"139":1,"178":1,"246":1,"333":1,"446":1,"447":1,"450":1,"470":1,"477":1,"549":1,"607":1,"635":1,"741":1,"798":1,"827":1,"1004":3,"1006":2,"1050":2,"1115":1,"1116":2,"1118":1,"1124":1,"1126":2,"1228":1,"1229":4,"1241":3,"1247":13,"1267":1,"1270":3,"1285":1,"1286":14,"1300":8,"1301":1,"1311":9,"1321":1,"1323":2,"1324":10,"1339":6,"1342":1,"1350":1,"1359":2,"1365":1,"1369":3,"1396":1,"1415":5,"1436":21,"1467":1,"1477":1,"1666":1,"1730":1,"1810":1,"1856":1,"1920":2,"2043":3,"2044":1,"2129":1,"2130":1,"2132":1,"2133":1,"2149":1}}],["segments",{"3":{"0":1,"333":1,"549":1,"599":1,"1228":1,"1229":3,"1241":2,"1247":4,"1270":2,"1286":1,"1300":1,"1311":4,"1324":4,"1337":1,"1339":2,"1436":2,"1467":2}}],["segmentsperwindow",{"2":{"178":1},"3":{"0":1,"178":1,"1311":5}}],["sesion",{"3":{"265":1}}],["session→400",{"3":{"1611":1}}],["sessionpresent",{"3":{"1396":1}}],["sessionprefix",{"3":{"1309":3}}],["sessionvalidationservice",{"3":{"1396":1}}],["sessionvalidationrules",{"3":{"1207":9,"1271":2,"1359":28,"1496":1}}],["sessionvalidationrulestests",{"3":{"1199":1,"1207":4,"1359":4}}],["sessionqa",{"2":{"1630":1,"1632":1},"3":{"1396":3,"1402":2,"1630":1,"1632":1}}],["sessionquestionid",{"3":{"1396":3,"1402":5,"1630":1}}],["sessionquestionidentifiertype",{"3":{"1396":4,"1402":16,"1415":1}}],["sessionquestioninvariants",{"2":{"1398":1},"3":{"1199":9,"1203":1,"1207":2,"1396":3,"1398":2,"1399":2,"1402":17}}],["sessionquestionlifecycletests",{"3":{"1199":1,"1207":2}}],["sessionquestionconfiguration",{"3":{"1199":2,"1203":1,"1207":2,"1396":5,"1402":3}}],["sessionquestionchannel",{"2":{"1402":1},"3":{"1199":10,"1203":1,"1207":2,"1399":2,"1402":29}}],["sessionquestionchanged",{"2":{"782":1,"1398":1},"3":{"690":1,"782":1,"1199":5,"1203":1,"1207":2,"1396":4,"1398":1,"1402":11}}],["sessionquestiontests",{"3":{"1199":1,"1207":2}}],["sessionquestionviewbuilder",{"2":{"1401":1,"1534":1},"3":{"1199":6,"1203":1,"1207":2,"1286":1,"1396":2,"1401":2,"1402":11,"1436":1,"1534":1}}],["sessionquestion",{"2":{"1397":1,"1398":1},"3":{"1199":24,"1203":1,"1207":2,"1350":1,"1396":11,"1397":2,"1398":6,"1402":46,"1415":1,"1436":2,"1438":1,"1487":1,"1496":2,"1630":1}}],["sessionquestiondto",{"2":{"1398":1},"3":{"1199":20,"1203":1,"1207":2,"1396":4,"1398":2,"1402":39}}],["sessionquestiondismissedpayload",{"3":{"1199":2,"1203":1,"1207":2,"1402":5}}],["sessionquestionpendingcountchangedpayload",{"2":{"1402":1},"3":{"1199":4,"1203":1,"1207":2,"1402":9}}],["sessionquestionapprovedpayload",{"2":{"1399":1},"3":{"1199":4,"1203":1,"1207":2,"1399":1,"1402":10}}],["sessionquestionanswerid",{"3":{"1350":1,"1359":8}}],["sessionquestionansweridentifiertype",{"3":{"1350":3,"1359":4,"1395":3,"1396":1}}],["sessionquestionanswerconfiguration",{"2":{"1362":1},"3":{"1199":1,"1203":1,"1207":2,"1359":1,"1362":4,"1369":5,"1396":1}}],["sessionquestionanswerchanged",{"2":{"1345":1,"1631":1,"1634":1},"3":{"1199":3,"1203":1,"1207":2,"1345":1,"1350":5,"1359":2,"1631":1,"1634":1}}],["sessionquestionanswertests",{"3":{"1199":1,"1207":2}}],["sessionquestionanswernavigationpopulatortests",{"3":{"1199":1,"1207":2,"1359":2}}],["sessionquestionanswernavigationpopulator",{"3":{"1199":2,"1203":1,"1207":2,"1359":5}}],["sessionquestionanswerrules",{"3":{"1199":4,"1203":1,"1207":2,"1350":4,"1352":1,"1359":38}}],["sessionquestionanswerdtomappertests",{"3":{"1199":1,"1207":2,"1359":2}}],["sessionquestionanswerdtomapper",{"3":{"1199":11,"1203":1,"1207":2,"1359":8}}],["sessionquestionanswerdto",{"2":{"1347":1},"3":{"1199":16,"1203":1,"1207":2,"1347":1,"1350":9,"1359":3,"1380":1,"1395":4,"1396":5}}],["sessionquestionansweredpayload",{"3":{"1199":2,"1203":1,"1207":2,"1402":7}}],["sessionquestionanswer",{"2":{"1342":1,"1638":1,"1639":1,"1640":1},"3":{"1198":1,"1199":21,"1203":1,"1207":2,"1342":2,"1350":11,"1359":20,"1369":3,"1380":1,"1630":1,"1631":5,"1632":1,"1634":2,"1635":3,"1637":1,"1638":7,"1639":5,"1640":6}}],["sessionquestionanswers",{"2":{"1115":1,"1343":1,"1363":1},"3":{"1115":1,"1343":1,"1350":5,"1359":18,"1363":1,"1369":2,"1380":3,"1395":3,"1396":2,"1447":1,"1630":2,"1631":1,"1632":1,"1635":1,"1638":1,"1639":4}}],["sessionquestionanswerscontrollertests",{"3":{"1199":1,"1207":2}}],["sessionquestionanswerscontroller",{"2":{"325":1,"1371":1,"1373":1,"1639":1},"3":{"325":3,"326":1,"743":1,"1199":3,"1203":1,"1207":6,"1239":1,"1247":3,"1300":2,"1311":4,"1350":1,"1359":32,"1371":2,"1372":4,"1373":2,"1374":3,"1380":30,"1396":2,"1496":1,"1639":1,"1995":1}}],["sessionquestionupvoteidentifiertype",{"3":{"1402":1}}],["sessionquestionupvoteinvariants",{"3":{"1199":2,"1203":1,"1207":2,"1402":4}}],["sessionquestionupvotes",{"3":{"1396":1,"1630":1}}],["sessionquestionupvoteconfiguration",{"3":{"1199":2,"1203":1,"1207":2,"1286":1,"1396":4,"1402":1}}],["sessionquestionupvotechangedpayload",{"3":{"1199":4,"1203":1,"1207":2,"1396":2,"1402":7}}],["sessionquestionupvotechangedhandlertests",{"3":{"1199":1,"1207":3,"1438":1}}],["sessionquestionupvotechangedhandler",{"2":{"522":1,"1100":1,"1399":1},"3":{"522":2,"897":2,"900":1,"1100":2,"1199":2,"1203":1,"1207":2,"1247":1,"1396":3,"1399":7,"1402":11,"1496":2}}],["sessionquestionupvotechanged",{"2":{"383":1,"522":1,"1100":1,"1399":1},"3":{"383":1,"1199":5,"1203":1,"1207":2,"1399":1,"1402":10}}],["sessionquestionupvotetests",{"3":{"1199":1,"1207":2}}],["sessionquestionupvote",{"2":{"1398":1,"1402":1},"3":{"1199":17,"1203":1,"1207":2,"1396":6,"1398":1,"1402":27,"1496":1,"1630":1}}],["sessionquestionuiservicetests",{"3":{"1199":1,"1207":2}}],["sessionquestionuiservice",{"2":{"1192":1},"3":{"881":1,"1192":1,"1199":2,"1203":1,"1207":2,"1300":6,"1396":7,"1402":1}}],["sessionquestionscontrollertests",{"3":{"1199":1,"1207":3}}],["sessionquestionscontroller",{"2":{"1874":1},"3":{"1199":3,"1203":1,"1207":2,"1311":2,"1380":1,"1396":3,"1402":30,"1496":1,"1526":1,"1874":1}}],["sessionquestionsubmittedpointshandlertests",{"3":{"782":1,"1199":1,"1207":3,"1396":1}}],["sessionquestionsubmittedpointshandler",{"3":{"690":1,"692":1,"782":1,"1199":2,"1203":1,"1207":2,"1237":1,"1396":10,"1402":2}}],["sessionquestions",{"2":{"1402":2,"2110":1},"3":{"493":1,"494":1,"495":1,"522":3,"523":2,"524":1,"897":1,"996":1,"1100":3,"1203":34,"1207":102,"1247":4,"1270":1,"1286":1,"1309":1,"1359":1,"1396":19,"1397":1,"1398":3,"1399":3,"1401":3,"1402":92,"1436":3,"1438":1,"1447":1,"1496":1,"1632":3,"2110":1}}],["sessionmatchesscoretier",{"3":{"1395":1}}],["sessionmatchesstatus",{"3":{"1395":2}}],["sessionmatchesfilters",{"3":{"1395":1}}],["sessionmappingonlydbcontext",{"3":{"1199":1,"1207":1}}],["sessionget",{"3":{"1324":1}}],["sessionnotinevent",{"3":{"1402":1}}],["sessionnotfound",{"3":{"905":1,"1300":2,"1359":1,"1395":1}}],["sessionnavigationpopulatortests",{"3":{"1199":1,"1207":2,"1359":2}}],["sessionnavigationpopulator",{"3":{"1199":2,"1203":1,"1207":2,"1310":2,"1354":1,"1359":11}}],["sessionbstatus",{"3":{"1350":1}}],["sessionbtitle",{"3":{"1350":1}}],["sessionbid",{"3":{"1350":1}}],["sessionbuilder",{"3":{"1199":29,"1207":2,"1436":1}}],["sessionbookmarkcount",{"3":{"1396":1}}],["sessionbookmarks",{"3":{"1396":5}}],["sessionbookmarksgrpcservicetests",{"3":{"1199":1,"1207":2,"1438":1}}],["sessionbookmarksgrpcservice",{"2":{"1378":1},"3":{"1199":2,"1203":1,"1207":2,"1312":6,"1350":1,"1359":2,"1378":4,"1380":6,"1396":1}}],["sessionbookmarkvalidationserviceclient",{"3":{"1380":1}}],["sessionbookmarkvalidationservicebase",{"3":{"1380":1}}],["sessionbookmarkvalidationservicetests",{"3":{"1199":1,"1207":2,"1359":2}}],["sessionbookmarkvalidationservice",{"3":{"1199":6,"1203":1,"1207":2,"1350":2,"1355":4,"1359":9,"1380":3}}],["sessionbookmarkvalidationservicegrpcadaptertests",{"2":{"1199":1},"3":{"1199":1,"1207":2,"1438":1}}],["sessionbookmarkvalidationservicegrpcadapter",{"2":{"53":1,"1203":1,"1312":1,"1378":1,"1927":1},"3":{"53":1,"1199":2,"1203":1,"1207":2,"1312":4,"1350":1,"1378":4,"1380":8,"1927":1}}],["sessionbookmarkuiservicetests",{"3":{"1199":1,"1207":2,"1396":2,"1417":1}}],["sessionbookmarkuiservice",{"3":{"1199":2,"1203":1,"1207":2,"1396":14,"1417":1}}],["sessionbookmarkbuttontests",{"3":{"1199":1,"1207":2}}],["sessionbookmarkbutton",{"2":{"1391":1},"3":{"1199":2,"1203":1,"1207":2,"1391":3,"1395":6,"1396":4,"1496":1}}],["sessiontitlerequiredkey",{"3":{"1395":1}}],["sessiontitlerules",{"3":{"1199":3,"1203":1,"1207":1,"1271":1,"1353":1,"1359":5}}],["sessiontitle",{"3":{"1350":5,"1380":1,"1395":3,"1396":3}}],["sessiontests",{"3":{"1199":1,"1207":2}}],["sessiontokenresult",{"2":{"1298":1},"3":{"1199":9,"1203":1,"1207":1,"1298":1,"1300":10}}],["sessiontokenresponse",{"2":{"1298":1,"1968":1},"3":{"1199":5,"1203":1,"1207":1,"1298":1,"1300":6,"1968":1}}],["sessionevent",{"3":{"1369":1}}],["sessioneventidrules",{"3":{"1199":3,"1203":1,"1207":1,"1271":3,"1359":8,"1496":1}}],["sessionentityqueryservicetests",{"3":{"1199":1,"1207":2,"1359":2}}],["sessionentityqueryservice",{"3":{"1199":2,"1203":1,"1207":2,"1354":2,"1359":8}}],["sessioneditmodeltests",{"3":{"1199":1,"1207":2}}],["sessioneditmodel",{"3":{"1199":2,"1203":1,"1207":2,"1395":6}}],["sessionhandoff",{"2":{"1324":1},"3":{"1324":1}}],["sessionhandoffendpoints",{"3":{"1199":2,"1203":1,"1207":3,"1300":4,"1324":18,"1496":1}}],["sessionhandoffservicestests",{"3":{"1199":1,"1207":2,"1324":2,"1438":1}}],["sessionhandoffprotector",{"3":{"1184":1,"1199":7,"1203":1,"1207":3,"1324":17}}],["sessionformfields",{"3":{"1395":2}}],["sessionformmodel",{"2":{"1526":1},"3":{"1199":4,"1203":1,"1207":2,"1350":2,"1384":2,"1395":14,"1526":2,"1531":1,"1532":1,"1535":1}}],["sessionfieldrules",{"3":{"1199":3,"1203":1,"1207":1,"1353":1,"1359":21}}],["sessionfeedbackorganizer",{"2":{"1395":1},"3":{"1395":2}}],["sessionfeedbacktests",{"3":{"1199":1,"1207":2,"1396":1,"1438":1}}],["sessionfeedbackservicetests",{"3":{"1199":1,"1207":2,"1396":1}}],["sessionfeedbackservice",{"3":{"1199":2,"1203":1,"1207":2,"1396":6}}],["sessionfeedbacksubmittedpointshandlertests",{"3":{"1199":1,"1207":2,"1396":1}}],["sessionfeedbacksubmittedpointshandler",{"3":{"1199":2,"1203":1,"1207":2,"1259":1,"1350":2,"1359":2,"1396":8}}],["sessionfeedbacksubmitted",{"2":{"195":1,"1341":1,"1349":1,"1639":1},"3":{"195":1,"690":1,"1199":6,"1203":1,"1207":2,"1237":2,"1341":1,"1349":5,"1350":6,"1359":4,"1396":3,"1436":1,"1467":1,"1631":1,"1639":1}}],["sessionfeedbackdto",{"3":{"1199":10,"1203":1,"1207":4,"1347":2,"1350":12,"1359":3,"1380":1,"1395":3}}],["sessionfeedbackpage",{"3":{"1199":2,"1207":2}}],["sessionfeedback",{"2":{"692":1},"3":{"690":1,"692":1,"1199":5,"1203":1,"1207":2,"1344":1,"1350":1,"1395":2,"1396":38,"1526":1,"1531":1,"1534":1}}],["sessiondurationsorttests",{"3":{"1199":1,"1207":2}}],["sessiondtomappertests",{"3":{"1199":1,"1207":2,"1359":2}}],["sessiondtomapper",{"3":{"1199":8,"1203":1,"1207":2,"1350":4,"1353":2,"1359":21}}],["sessiondtotests",{"3":{"1199":1,"1207":2}}],["sessiondto",{"3":{"1199":47,"1203":1,"1207":2,"1311":2,"1347":2,"1350":40,"1359":23,"1380":2,"1382":1,"1395":21,"1396":7,"1496":2,"1875":1}}],["sessiondescriptionrules",{"3":{"1199":2,"1203":1,"1207":1,"1359":3}}],["sessiondetails",{"3":{"1395":2,"1396":1}}],["sessiondetailstaleloadtests",{"3":{"1199":1,"1207":2}}],["sessiondetailscheduletests",{"3":{"1199":1,"1207":2,"1438":1}}],["sessiondetailroomcachetests",{"3":{"1199":1,"1207":2,"1438":2}}],["sessiondetailcategorychiptests",{"3":{"1199":1,"1207":2}}],["sessiondetailpage",{"3":{"1199":10,"1207":2}}],["sessiondetail",{"3":{"1119":1,"1199":5,"1203":1,"1207":2,"1237":1,"1384":2,"1395":51,"1526":3,"1531":1,"1535":1}}],["sessiondeleted",{"2":{"781":1,"783":1},"3":{"781":1,"783":1}}],["sessionupdaterequestvalidatortests",{"3":{"1199":1,"1207":2,"1359":2}}],["sessionupdaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1359":13}}],["sessionupdaterequest",{"2":{"1875":1},"3":{"1199":5,"1203":1,"1207":2,"1359":10,"1380":1,"1875":1}}],["sessiononlycontextbase",{"3":{"1199":3,"1207":1}}],["sessionlookup",{"3":{"1396":1,"1402":1}}],["sessionlookups",{"3":{"1199":1,"1203":1,"1207":2,"1385":2,"1387":1,"1395":26}}],["sessionlookupservicetests",{"3":{"1199":1,"1207":2,"1396":2}}],["sessionlookupservice",{"2":{"1192":1},"3":{"1192":1,"1199":2,"1203":1,"1207":2,"1324":2,"1396":14,"1496":1}}],["sessionlevel",{"3":{"1350":2,"1359":1,"1395":1}}],["sessionlisturi",{"3":{"1380":2}}],["sessionlisteventfiltertests",{"3":{"1199":1,"1207":2,"1436":1}}],["sessionlistcurrenteventclocktests",{"3":{"1199":1,"1207":3,"1438":1}}],["sessionlist",{"3":{"1199":3,"1203":1,"1207":2,"1324":4,"1350":2,"1383":2,"1385":1,"1395":22}}],["sessionlistpage",{"3":{"1199":3,"1207":2,"1436":1}}],["sessionlivemoderationpaneltests",{"3":{"1199":1,"1207":2}}],["sessionlivemoderationpanel",{"2":{"1401":1},"3":{"1199":3,"1203":1,"1207":3,"1396":4,"1401":1,"1402":16,"1531":1}}],["sessionliveurlrules",{"3":{"1199":2,"1203":1,"1207":1,"1359":5}}],["sessionliveuiservice",{"2":{"1192":1,"1401":1},"3":{"1192":1,"1199":1,"1203":1,"1207":2,"1396":6,"1401":2,"1402":2}}],["sessionlivequestionpanel",{"2":{"1401":1},"3":{"1199":3,"1203":1,"1207":2,"1396":2,"1401":1,"1402":10}}],["sessionlivequestionpaneltests",{"3":{"954":1,"960":1,"1199":1,"1207":2}}],["sessionlivepollpaneltests",{"3":{"1199":1,"1207":2}}],["sessionlivepollpanel",{"2":{"1401":1},"3":{"1199":3,"1203":1,"1207":2,"1395":1,"1396":1,"1401":1,"1402":7}}],["sessionliveinfo",{"3":{"1199":23,"1203":1,"1207":2,"1349":1,"1350":9,"1359":1,"1378":1,"1380":3,"1402":10,"1496":1}}],["sessionlive",{"2":{"1397":1},"3":{"556":3,"881":1,"954":1,"960":1,"1192":1,"1199":2,"1203":17,"1207":46,"1300":5,"1395":1,"1396":44,"1397":4,"1400":5,"1401":9,"1402":36,"1436":1,"1496":3,"1526":1,"1531":1,"1534":1}}],["sessionroomfiltertests",{"3":{"1199":1,"1207":2,"1247":1,"1438":2}}],["sessionroomschedulingtests",{"3":{"1199":1,"1207":2,"1359":2}}],["sessionroomscheduling",{"2":{"1359":1,"1534":1},"3":{"545":1,"551":1,"1199":4,"1203":1,"1207":2,"1352":7,"1359":15,"1496":2,"1534":1}}],["sessionretrievalfailed",{"2":{"1600":1},"3":{"1600":1}}],["sessionresult",{"3":{"1395":1,"1396":1}}],["sessionresourcelinksrules",{"3":{"1199":2,"1203":1,"1207":1,"1359":3}}],["sessionrecordingurlrules",{"3":{"1199":2,"1203":1,"1207":1,"1359":3}}],["sessionremindercoordinatortests",{"3":{"1199":1,"1207":2,"1396":1,"1417":1}}],["sessionremindercoordinator",{"3":{"1199":4,"1203":1,"1207":2,"1396":10,"1417":3}}],["sessionreminderplannertests",{"3":{"1199":1,"1207":2}}],["sessionreminderplanner",{"3":{"1199":4,"1203":1,"1207":3,"1396":16}}],["sessionreminder",{"3":{"1199":3,"1203":1,"1207":1,"1396":5}}],["sessionrefreshoutcome",{"3":{"1184":1,"1185":1,"1199":6,"1203":1,"1207":3,"1300":8,"1324":1,"1496":1}}],["sessionrefreshstatus",{"3":{"1184":1,"1199":5,"1203":1,"1207":1,"1300":6,"1324":1}}],["sessioncreationfailed",{"2":{"1600":1},"3":{"1600":1,"1770":1}}],["sessioncreaterequestvalidatortests",{"3":{"1199":1,"1207":2,"1359":4}}],["sessioncreaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1359":13}}],["sessioncreaterequestmappertests",{"3":{"1199":1,"1207":2,"1359":2}}],["sessioncreaterequestmapper",{"3":{"1199":2,"1203":1,"1207":2,"1359":7}}],["sessioncreaterequest",{"3":{"1199":9,"1203":1,"1207":2,"1270":1,"1359":12,"1374":1,"1380":1}}],["sessioncreatetests",{"3":{"1199":1,"1207":2}}],["sessioncreatemodeltests",{"3":{"1199":1,"1207":2}}],["sessioncreatemodel",{"3":{"1199":3,"1203":1,"1207":2,"1395":6}}],["sessioncreatepage",{"3":{"1199":12,"1207":2}}],["sessioncreated",{"2":{"781":1,"783":1},"3":{"781":1,"783":1,"1259":1,"1631":1,"1632":2,"1634":3}}],["sessioncreatedhandler",{"2":{"780":1},"3":{"780":1}}],["sessioncreate",{"3":{"128":1,"1199":2,"1203":1,"1207":2,"1395":25,"1526":2,"1532":1,"1535":2}}],["sessioncleanuptestcontext",{"3":{"1199":2,"1207":1}}],["sessionclaimstokentests",{"3":{"1199":1,"1207":2,"1300":1,"1438":1}}],["sessionclaimstoken",{"3":{"1184":2,"1185":1,"1199":5,"1203":1,"1207":2,"1300":6,"1324":2,"1496":1}}],["sessioncount",{"3":{"1350":1}}],["sessioncommandvalidatortests",{"3":{"1207":4,"1359":4}}],["sessionconfiguration",{"2":{"1361":1},"3":{"1082":1,"1199":1,"1203":1,"1207":2,"1350":1,"1359":2,"1361":2,"1362":7,"1369":6,"1496":1}}],["sessioncookieauthenticationextensions",{"2":{"1298":1},"3":{"1199":1,"1203":1,"1207":1,"1298":1,"1300":2}}],["sessioncookieauthenticationhandlertests",{"3":{"1199":1,"1207":2,"1300":1}}],["sessioncookieauthenticationhandler",{"2":{"207":1,"209":1,"1287":1,"1298":1,"1300":1,"1968":1,"1971":1,"1972":1},"3":{"0":1,"207":1,"209":1,"1199":3,"1203":1,"1207":3,"1208":1,"1287":1,"1298":7,"1300":14,"1416":1,"1968":1,"1971":1,"1972":1}}],["sessioncookierequest",{"2":{"1298":1},"3":{"1199":4,"1203":1,"1207":1,"1298":1,"1300":4}}],["sessioncookieendpointstests",{"3":{"1199":1,"1207":3,"1300":1}}],["sessioncookieendpoints",{"2":{"1298":1,"1968":1},"3":{"980":1,"1198":1,"1199":17,"1203":1,"1207":3,"1208":1,"1287":1,"1298":8,"1300":20,"1324":3,"1968":1}}],["sessioncookie",{"3":{"207":1,"1300":2,"1968":1}}],["sessioncookiesync",{"3":{"1324":4}}],["sessioncookiestore",{"3":{"1199":1,"1203":1,"1207":1,"1300":4,"1311":2,"1496":1}}],["sessioncookiesamesite",{"3":{"1184":2,"1300":1,"1324":2}}],["sessioncookiesettings",{"2":{"1184":1},"3":{"1184":3,"1199":8,"1203":1,"1207":2,"1300":10,"1311":2,"1324":3,"1496":1}}],["sessioncookies",{"3":{"207":1,"980":1,"1108":1,"1183":2,"1184":3,"1203":18,"1207":64,"1208":3,"1298":6,"1300":68,"1311":1,"1324":1,"1438":2,"1496":1,"1582":1,"1968":1}}],["sessioncookiejartests",{"3":{"1199":1,"1207":2,"1300":1}}],["sessioncookiejar",{"2":{"207":1,"1968":1},"3":{"207":1,"1108":1,"1198":1,"1199":5,"1203":1,"1207":2,"1298":4,"1300":15,"1534":1,"1968":1}}],["sessioncategories",{"3":{"1350":2,"1395":1}}],["sessioncategoryitemid",{"3":{"1350":1,"1359":5}}],["sessioncategoryitemidentifiertype",{"3":{"1350":3,"1359":4,"1395":3}}],["sessioncategoryitemconfiguration",{"2":{"1362":1},"3":{"1199":1,"1203":1,"1207":2,"1362":2,"1369":6}}],["sessioncategoryitemchanged",{"2":{"1345":1,"1631":1,"1634":1},"3":{"1199":4,"1203":1,"1207":2,"1345":1,"1350":6,"1359":1,"1631":1,"1634":1}}],["sessioncategoryitemtests",{"3":{"1199":1,"1207":2}}],["sessioncategoryitemnavigationpopulatortests",{"3":{"1199":1,"1207":2,"1359":2}}],["sessioncategoryitemnavigationpopulator",{"3":{"1199":2,"1203":1,"1207":2,"1359":10}}],["sessioncategoryitemdtomappertests",{"3":{"1199":1,"1207":2,"1359":2}}],["sessioncategoryitemdtomapper",{"3":{"1199":9,"1203":1,"1207":2,"1359":9}}],["sessioncategoryitemdto",{"3":{"1199":10,"1203":1,"1207":2,"1347":1,"1350":10,"1359":2,"1380":1,"1395":4}}],["sessioncategoryitem",{"2":{"1342":1},"3":{"1198":1,"1199":17,"1203":1,"1207":2,"1237":2,"1342":3,"1350":12,"1359":16,"1369":3,"1380":1,"1395":2,"1630":1,"1631":1,"1632":2,"1634":2,"1635":3,"1637":1,"1638":3,"1639":3}}],["sessioncategoryitemscontrollertests",{"3":{"1199":1,"1207":2}}],["sessioncategoryitemscontroller",{"2":{"1371":1,"1372":1},"3":{"1199":3,"1203":1,"1207":3,"1350":2,"1359":22,"1371":2,"1372":2,"1373":2,"1374":1,"1380":17}}],["sessioncategoryitemservice",{"2":{"1386":1},"3":{"1199":2,"1203":1,"1207":1,"1324":1,"1386":1,"1395":4}}],["sessioncategoryitems",{"2":{"1115":1,"1363":1},"3":{"1115":1,"1324":1,"1350":3,"1359":14,"1363":1,"1369":2,"1380":2,"1395":1,"1447":1,"1630":2,"1631":1,"1632":2,"1635":2,"1638":1,"1639":2}}],["sessioncalendarcontroller",{"2":{"1371":1,"1373":1,"1375":1},"3":{"1199":1,"1203":1,"1207":2,"1371":2,"1373":2,"1375":3,"1380":6,"1436":1,"1496":1,"1535":1}}],["sessioncapexceeded",{"3":{"905":2,"1290":1,"1300":1,"1982":1}}],["sessioncancelled",{"2":{"785":1},"3":{"785":1}}],["sessionchanged",{"2":{"781":1,"783":1},"3":{"781":1,"782":3,"783":1,"1199":3,"1203":1,"1207":2,"1259":1,"1345":3,"1350":12,"1359":1,"1631":1,"1634":1}}],["sessioncheckin",{"2":{"691":1,"692":1},"3":{"690":1,"691":1,"692":1,"1396":5,"1415":1}}],["sessionissuer",{"3":{"1300":8}}],["sessionincludechildrenregressiontests",{"3":{"1199":1,"1207":2,"1526":3}}],["sessioninvariantstests",{"3":{"1199":1,"1207":2}}],["sessioninvariants",{"2":{"1344":1,"1362":1,"1364":1,"1395":1},"3":{"1199":24,"1203":1,"1207":2,"1237":1,"1344":10,"1350":28,"1352":1,"1355":1,"1359":55,"1362":1,"1364":1,"1369":8,"1395":2,"1396":2}}],["sessioninfo",{"3":{"1199":14,"1203":1,"1207":1,"1396":12,"1402":10}}],["sessionizeid",{"3":{"1639":1}}],["sessionizeintegration",{"2":{"1352":1,"2136":1},"3":{"1347":1,"1350":4,"1352":1,"1359":2,"2136":2}}],["sessionizemodels",{"3":{"1207":9,"1358":1,"1359":22}}],["sessionizerefreshtests",{"3":{"1199":1,"1207":2,"1359":1}}],["sessionizerefreshoutcome",{"2":{"1382":1,"1387":1},"3":{"1199":4,"1203":1,"1207":2,"1382":2,"1387":1,"1395":10,"1526":1}}],["sessionizeresponse",{"2":{"1365":1},"3":{"1199":13,"1203":1,"1207":1,"1358":1,"1359":10,"1365":1,"1369":3}}],["sessionizeroom",{"3":{"1199":5,"1203":1,"1207":1,"1358":1,"1359":3}}],["sessionizeservicetests",{"3":{"1199":1,"1207":2,"1438":1}}],["sessionizeservice",{"2":{"1365":1},"3":{"1199":3,"1203":1,"1207":2,"1350":1,"1359":4,"1365":8,"1369":5,"1496":1}}],["sessionizesession",{"3":{"1199":5,"1203":1,"1207":1,"1358":1,"1359":4}}],["sessionizesynccontext",{"3":{"1199":13,"1203":1,"1207":2,"1358":1,"1359":14}}],["sessionizesyncwarnings",{"3":{"1199":6,"1203":1,"1207":2,"1358":1,"1359":12,"1496":1}}],["sessionizesyncresult",{"3":{"1199":8,"1203":1,"1207":1,"1350":1,"1358":1,"1359":5}}],["sessionizespeaker",{"3":{"1199":5,"1203":1,"1207":1,"1358":1,"1359":5}}],["sessionizecodemaxlength",{"3":{"1350":2,"1359":1}}],["sessionizecode",{"2":{"1636":1},"3":{"1350":5,"1353":1,"1359":14,"1362":1,"1369":8,"1395":9,"1630":2,"1631":1,"1632":5,"1636":1}}],["sessionizecodeattribute",{"2":{"1384":1},"3":{"1199":1,"1203":1,"1207":2,"1384":3,"1395":4,"1526":1}}],["sessionizecodeformattests",{"3":{"1199":1,"1207":2,"1350":1,"1437":1}}],["sessionizecodeformat",{"2":{"1353":2,"1365":1,"1631":1},"3":{"1199":8,"1203":1,"1207":2,"1347":7,"1350":10,"1353":2,"1359":12,"1365":1,"1369":2,"1384":1,"1395":3,"1496":2,"1526":1,"1631":1}}],["sessionizecategory",{"3":{"1199":5,"1203":1,"1207":1,"1358":1,"1359":4}}],["sessionizecategoryitem",{"3":{"1199":4,"1203":1,"1207":1,"1358":1,"1359":13}}],["sessionizequestionanswer",{"3":{"1199":6,"1203":1,"1207":1,"1358":1,"1359":6}}],["sessionizequestion",{"3":{"1199":4,"1203":1,"1207":1,"1358":1,"1359":3}}],["sessionizelink",{"3":{"1199":3,"1203":1,"1207":1,"1358":1,"1359":5}}],["sessionize",{"0":{"1358":1,"1365":1},"2":{"1207":1,"1359":1},"3":{"0":1,"344":1,"353":1,"470":1,"477":1,"798":1,"988":1,"1004":1,"1115":2,"1116":2,"1117":1,"1192":1,"1202":1,"1203":12,"1207":22,"1212":1,"1286":3,"1342":9,"1343":3,"1344":3,"1347":6,"1350":72,"1351":1,"1352":4,"1353":2,"1355":1,"1358":7,"1359":148,"1360":1,"1362":1,"1364":4,"1365":3,"1368":1,"1369":25,"1371":1,"1375":2,"1380":10,"1382":1,"1395":28,"1436":3,"1437":2,"1438":2,"1487":2,"1489":1,"1496":1,"1534":1,"1629":4,"1630":27,"1631":30,"1632":18,"1633":3,"1635":1,"1636":6,"1637":3,"1638":5,"1639":48,"1640":3,"1641":14,"2136":1}}],["sessionid=",{"3":{"1395":3,"1396":4,"1402":2,"1631":1,"1640":1}}],["sessionids=",{"3":{"1395":1}}],["sessionids",{"3":{"1359":6,"1380":2,"1395":3,"1396":6}}],["sessionidentifiertype",{"2":{"472":1,"799":1,"1055":1},"3":{"472":1,"799":2,"1055":1,"1259":1,"1350":23,"1359":41,"1380":6,"1395":19,"1396":36,"1402":26,"1415":2}}],["sessionid",{"2":{"1052":1,"1055":1,"1357":1},"3":{"0":2,"690":3,"799":1,"905":3,"996":1,"1052":1,"1055":1,"1116":2,"1212":1,"1259":1,"1300":19,"1311":2,"1324":4,"1342":2,"1345":1,"1350":28,"1357":1,"1359":80,"1362":4,"1369":19,"1380":30,"1395":52,"1396":117,"1398":2,"1401":1,"1402":54,"1415":4,"1436":2,"1438":1,"1467":1,"1526":1,"1535":1,"1627":1,"1630":2,"1631":6,"1632":7,"1634":3,"1635":4,"1638":1,"1639":1,"1640":2,"1641":2}}],["sessionastatus",{"3":{"1350":1}}],["sessionassetmalwarescanning",{"3":{"1467":1}}],["sessionassetrequests",{"3":{"1207":2,"1389":2,"1395":6}}],["sessionassetvalidationrules",{"3":{"1207":6,"1286":2,"1350":3,"1353":1,"1359":7}}],["sessionassettests",{"3":{"1199":1,"1207":2,"1350":2}}],["sessionassettitlerules",{"3":{"1199":2,"1203":1,"1207":1,"1359":5}}],["sessionassetbuilder",{"3":{"1199":2,"1207":2}}],["sessionassetfixtures",{"3":{"1199":7,"1207":2}}],["sessionassetfieldrules",{"3":{"1199":4,"1203":1,"1207":1,"1353":1,"1359":11}}],["sessionassetfilenamerules",{"3":{"1199":2,"1203":1,"1207":1,"1359":3}}],["sessionasseturlrules",{"3":{"1199":2,"1203":1,"1207":1,"1359":5}}],["sessionassetupdaterequestvalidatortests",{"3":{"1199":1,"1207":2}}],["sessionassetupdaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1350":1,"1359":6}}],["sessionassetupdaterequest",{"2":{"1389":1},"3":{"1199":12,"1203":2,"1207":3,"1359":10,"1380":1,"1389":1,"1395":4}}],["sessionassetdisplay",{"3":{"1199":3,"1203":1,"1207":2,"1389":3,"1395":6,"1496":1}}],["sessionassetdto",{"3":{"1199":19,"1203":1,"1207":2,"1347":3,"1350":23,"1359":5,"1380":1,"1395":7}}],["sessionassetdtomappertests",{"3":{"1199":1,"1207":2,"1359":2}}],["sessionassetdtomapper",{"2":{"3":1},"3":{"3":1,"1199":10,"1203":1,"1207":2,"1350":1,"1357":1,"1359":14}}],["sessionassetlinkrequestvalidatortests",{"3":{"1199":1,"1207":2}}],["sessionassetlinkrequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1359":7,"1395":2}}],["sessionassetlinkrequest",{"2":{"1389":1},"3":{"1199":13,"1203":2,"1207":3,"1359":12,"1380":1,"1389":1,"1395":8}}],["sessionassetlimits",{"2":{"1118":1,"1357":1,"1373":1,"1389":1},"3":{"1116":3,"1118":1,"1199":14,"1203":1,"1207":2,"1286":2,"1347":8,"1350":13,"1357":1,"1359":9,"1373":1,"1380":4,"1389":3,"1395":7}}],["sessionassetid",{"3":{"1350":2}}],["sessionassetidentifiertype",{"2":{"468":1,"470":1,"472":1,"477":1,"478":1,"798":1,"805":1,"1049":1,"1054":1},"3":{"468":1,"470":1,"472":1,"477":3,"478":1,"798":1,"805":1,"1049":1,"1054":1,"1212":1,"1350":3,"1359":4}}],["sessionassetinvariantstests",{"3":{"1199":1,"1207":2,"1350":2}}],["sessionassetinvariants",{"2":{"1344":1,"1369":1},"3":{"1116":2,"1199":11,"1203":1,"1207":2,"1344":7,"1350":6,"1359":9,"1369":5,"1496":1}}],["sessionassetkind",{"2":{"1342":1},"3":{"1116":1,"1199":14,"1203":1,"1207":2,"1342":3,"1350":12,"1395":3}}],["sessionassetcomposer",{"3":{"1199":2,"1203":1,"1207":2,"1389":2,"1395":5,"1496":1}}],["sessionassetconfiguration",{"2":{"1361":1},"3":{"1116":1,"1199":1,"1203":1,"1207":2,"1350":2,"1361":1,"1362":6,"1369":5,"1496":2}}],["sessionassetchanged",{"2":{"780":1,"1345":1},"3":{"780":1,"782":1,"1199":3,"1203":1,"1207":2,"1345":3,"1350":7,"1496":1}}],["sessionassetstests",{"3":{"1199":1,"1207":2}}],["sessionassetsortorderrules",{"3":{"1199":2,"1203":1,"1207":1,"1359":7}}],["sessionassetsessionidrules",{"3":{"1199":3,"1203":1,"1207":1,"1359":6}}],["sessionassetservicetests",{"3":{"1199":1,"1207":2}}],["sessionassetservice",{"3":{"881":1,"1199":4,"1203":1,"1207":2,"1350":2,"1389":2,"1395":9,"1496":2}}],["sessionassetspaneltests",{"3":{"1199":1,"1207":2}}],["sessionassetspanelpage",{"3":{"1199":2,"1207":2}}],["sessionassetspanel",{"3":{"1116":1,"1199":2,"1203":1,"1207":2,"1350":4,"1389":4,"1395":18,"1496":2,"1526":1,"1531":1}}],["sessionassetscontrollertests",{"3":{"1199":1,"1207":2,"1380":1,"1487":1}}],["sessionassetscontroller",{"2":{"1373":1},"3":{"1116":4,"1199":3,"1203":1,"1207":2,"1350":2,"1359":13,"1371":2,"1373":4,"1380":3,"1436":2,"1496":2}}],["sessionassetsdownloadlisttests",{"3":{"1199":1,"1207":2}}],["sessionassetsdownloadlist",{"2":{"1116":1,"1389":1},"3":{"1116":1,"1199":2,"1203":1,"1207":2,"1350":1,"1389":3,"1395":11,"1496":1}}],["sessionassetsmanage",{"2":{"184":1},"3":{"184":1,"1350":5,"1373":2,"1380":2}}],["sessionassets",{"2":{"1004":1,"1207":1,"1357":1,"1380":1,"1395":1},"3":{"0":1,"536":2,"881":1,"1004":1,"1116":12,"1203":45,"1207":128,"1264":1,"1286":6,"1318":1,"1323":3,"1342":2,"1344":1,"1345":1,"1347":2,"1350":17,"1353":1,"1357":4,"1359":73,"1363":1,"1369":6,"1371":1,"1373":1,"1380":5,"1389":8,"1395":33,"1438":1,"1447":2,"1487":1,"1630":1,"1631":5,"1640":5}}],["sessionassetaccessservicetests",{"3":{"1199":1,"1207":2,"1359":2}}],["sessionassetaccessservice",{"2":{"552":1,"1116":1},"3":{"0":1,"552":1,"1116":4,"1199":3,"1203":1,"1207":2,"1350":3,"1357":3,"1359":9,"1496":1}}],["sessionasset",{"2":{"1114":1,"1116":1,"1346":1,"1347":1,"1357":1,"1389":4},"3":{"0":1,"1114":1,"1116":4,"1199":24,"1203":1,"1207":2,"1212":1,"1323":1,"1340":1,"1342":9,"1343":7,"1344":3,"1345":1,"1346":2,"1347":1,"1350":23,"1352":1,"1357":1,"1359":45,"1369":4,"1389":4,"1395":2,"1436":1,"1496":2}}],["sessionatitle",{"3":{"1350":1}}],["sessionattendance",{"3":{"1396":3}}],["sessionattendancerow",{"3":{"1199":2,"1203":1,"1207":1,"1396":4}}],["sessionattendancedto",{"3":{"1199":6,"1203":1,"1207":2,"1396":10}}],["sessionaid",{"3":{"1350":1}}],["sessionaiscoreconfiguration",{"3":{"1199":1,"1203":1,"1207":2,"1350":1,"1359":1,"1362":4,"1369":4,"1496":1}}],["sessionaiscoretests",{"3":{"1199":1,"1207":2}}],["sessionaiscoredto",{"3":{"1199":9,"1203":1,"1207":3,"1347":2,"1350":8,"1359":1,"1395":3,"1496":2}}],["sessionaiscoreidentifiertype",{"2":{"468":1,"477":1},"3":{"468":1,"477":1,"1350":1}}],["sessionaiscore",{"2":{"1018":2},"3":{"0":1,"1018":3,"1199":7,"1203":1,"1207":2,"1342":8,"1347":1,"1350":10,"1359":11,"1363":1,"1369":9,"1496":2}}],["sessionawareauthenticationservice",{"3":{"1199":2,"1207":1}}],["sessionaccessibilityinforules",{"3":{"1199":2,"1203":1,"1207":1,"1359":7}}],["sessionsnext",{"3":{"1396":1}}],["sessionsnow",{"3":{"1396":1}}],["sessionsnavrequiredrole",{"2":{"186":1,"190":1},"3":{"186":1,"190":1,"905":1,"909":1,"1324":1,"1627":1,"1761":1}}],["sessionsfailed",{"3":{"1350":1}}],["sessionsscored",{"3":{"1350":1,"1390":1,"1395":5}}],["sessionssynced",{"3":{"1350":1,"1359":1}}],["sessionsmanage",{"3":{"1350":2,"1359":8,"1373":1,"1380":4}}],["sessionsyncstrategytests",{"3":{"1199":1,"1207":2,"1359":1}}],["sessionsyncstrategy",{"3":{"1199":3,"1203":1,"1207":2,"1358":6,"1359":9,"1496":1}}],["sessionscachetag",{"3":{"1369":2,"1396":2}}],["sessionscache",{"3":{"1350":1,"1359":1,"1373":2,"1375":1,"1379":1,"1380":6}}],["sessionschedulepagerequest",{"2":{"1391":1},"3":{"1199":5,"1203":1,"1207":1,"1391":1,"1395":7}}],["sessionscorestamp",{"3":{"1496":2}}],["sessionscored",{"3":{"1259":1}}],["sessionscoreresponseguardrailtests",{"2":{"1435":1},"3":{"1199":1,"1207":3,"1369":2,"1435":2,"1437":1,"1438":1,"1487":1}}],["sessionscoreresponseguardrail",{"2":{"1018":1,"1021":1,"1094":1,"1366":1},"3":{"1018":3,"1021":3,"1094":1,"1199":5,"1203":1,"1207":2,"1366":4,"1369":11,"1427":2,"1496":1,"1526":2,"1535":1}}],["sessionscoringcandidate",{"3":{"1496":2}}],["sessionscoringcontract",{"2":{"1366":1,"1368":1,"1369":1,"1492":1},"3":{"1366":1,"1368":1,"1369":3,"1492":1}}],["sessionscoringworkitem",{"3":{"1496":2}}],["sessionscoringenqueueresult",{"3":{"1496":2}}],["sessionscoringsweepjob",{"3":{"1270":1,"1496":2}}],["sessionscoringservicetests",{"2":{"1435":1},"3":{"1199":1,"1207":3,"1369":1,"1435":2,"1437":1,"1438":1}}],["sessionscoringservice",{"2":{"1016":1,"1018":1,"1021":1,"1094":1,"1095":1,"1366":2,"1369":1,"1492":1},"3":{"1016":1,"1017":1,"1018":11,"1021":2,"1090":1,"1093":1,"1094":2,"1095":2,"1199":9,"1203":1,"1207":2,"1347":1,"1350":1,"1359":9,"1366":11,"1369":19,"1427":3,"1435":1,"1438":2,"1492":1,"1496":3,"1526":8}}],["sessionscoringinput",{"3":{"1199":10,"1203":1,"1207":1,"1359":7,"1369":1}}],["sessionscoringresult",{"3":{"1199":6,"1203":1,"1207":1,"1359":5,"1366":1,"1369":4}}],["sessionscoringrunnertests",{"3":{"1199":1,"1207":2,"1359":2,"1438":2}}],["sessionscoringrunner",{"3":{"1082":1,"1086":1,"1199":2,"1203":1,"1207":2,"1286":1,"1350":2,"1359":23,"1369":2,"1436":2,"1438":1,"1496":3,"1526":1}}],["sessionscoring",{"2":{"1018":1,"1021":1,"1099":1,"1103":1},"3":{"1018":1,"1021":1,"1099":1,"1103":1,"1347":1,"1350":4,"1359":2,"1380":2,"1496":1}}],["sessionscoringpromptcontractpintests",{"2":{"1018":1,"1426":1,"1428":1,"1435":1},"3":{"1018":1,"1199":1,"1207":1,"1426":1,"1428":1,"1435":1}}],["sessionscoringprocessortests",{"3":{"1496":1}}],["sessionscoringprocessor",{"2":{"491":1,"492":1,"493":1,"518":1},"3":{"491":2,"492":3,"493":2,"518":1,"1442":2,"1496":4}}],["sessionscoringqueue",{"2":{"518":1},"3":{"0":2,"518":1,"1496":4}}],["sessionscontrollertests",{"3":{"1199":1,"1207":2}}],["sessionscontroller",{"2":{"543":1,"552":1},"3":{"543":2,"545":2,"546":1,"550":1,"552":1,"743":2,"1199":3,"1203":1,"1207":2,"1247":7,"1311":8,"1350":2,"1359":45,"1371":5,"1372":5,"1373":4,"1374":1,"1375":9,"1380":10,"1395":2,"1436":4,"1496":1,"1526":1,"1534":1}}],["sessionsimilaritycalculatortests",{"3":{"1199":1,"1207":2,"1359":2}}],["sessionsimilaritycalculator",{"3":{"1199":3,"1203":1,"1207":2,"1359":7}}],["sessionspeakerids",{"3":{"1359":1}}],["sessionspeakerid",{"3":{"1350":1,"1359":5}}],["sessionspeakeridentifiertype",{"3":{"1350":3,"1359":4,"1395":3}}],["sessionspeakerconfiguration",{"3":{"1199":1,"1203":1,"1207":2,"1362":2,"1369":8}}],["sessionspeakerchanged",{"2":{"1345":1,"1634":1},"3":{"1199":4,"1203":1,"1207":2,"1345":1,"1350":6,"1359":2,"1631":1,"1634":1}}],["sessionspeakertests",{"3":{"1199":1,"1207":2}}],["sessionspeakernavigationpopulatortests",{"3":{"1199":1,"1207":2,"1359":2}}],["sessionspeakernavigationpopulator",{"3":{"1199":2,"1203":1,"1207":2,"1359":5}}],["sessionspeakerdtomappertests",{"3":{"1199":1,"1207":2,"1359":2}}],["sessionspeakerdtomapper",{"3":{"1199":9,"1203":1,"1207":2,"1359":8}}],["sessionspeakerdto",{"3":{"1199":9,"1203":1,"1207":2,"1347":1,"1350":9,"1359":2,"1380":1,"1395":3}}],["sessionspeaker",{"3":{"1198":1,"1199":24,"1203":1,"1207":2,"1237":4,"1324":1,"1342":3,"1350":8,"1353":1,"1359":38,"1364":2,"1369":5,"1380":1,"1395":5,"1438":1,"1630":1,"1631":2,"1632":2,"1634":2,"1635":3,"1637":1,"1638":3,"1639":4,"1640":1}}],["sessionspeakerscontrollertests",{"3":{"1199":1,"1207":2}}],["sessionspeakerscontroller",{"2":{"1371":1,"1372":1},"3":{"1199":3,"1203":1,"1207":3,"1311":2,"1350":2,"1359":23,"1371":2,"1372":2,"1373":2,"1374":1,"1380":17}}],["sessionspeakerservice",{"2":{"1386":1},"3":{"1199":1,"1203":1,"1207":1,"1324":1,"1386":1,"1395":3}}],["sessionspeakers",{"2":{"1115":1,"1357":1,"1363":1},"3":{"1115":1,"1116":1,"1324":1,"1350":4,"1357":1,"1359":21,"1363":1,"1369":4,"1380":2,"1395":6,"1447":1,"1526":1,"1630":2,"1631":1,"1632":2,"1635":3,"1638":1,"1639":3}}],["sessionspagee2etests",{"3":{"618":2,"906":1,"909":1,"1199":1,"1207":2,"1324":1,"1436":5,"1438":1}}],["sessionstatusrules",{"3":{"1199":2,"1203":1,"1207":1,"1359":3}}],["sessionstatusdisplay",{"3":{"1199":1,"1203":1,"1207":2,"1385":2,"1395":6,"1496":1}}],["sessionstatuses",{"3":{"1199":23,"1203":1,"1207":2,"1344":7,"1350":10,"1353":1,"1356":1,"1359":40,"1395":6,"1396":2,"1496":2,"1535":1}}],["sessionstampingtokenservice",{"2":{"1287":1,"1290":1},"3":{"905":1,"1199":2,"1203":1,"1207":1,"1287":1,"1290":1,"1300":6,"1496":1}}],["sessionstests",{"3":{"906":1,"1199":1,"1207":2,"1324":1,"1436":1,"1438":2}}],["sessionstorage",{"2":{"2076":1},"3":{"881":1,"1324":7,"1577":1,"1715":1,"2076":1}}],["sessionset",{"3":{"1324":1}}],["sessionservice",{"3":{"881":1,"1199":8,"1203":1,"1207":2,"1324":1,"1382":3,"1395":7,"1396":1}}],["sessionselectiontests",{"3":{"1199":1,"1207":2}}],["sessionselectionstaleresponsetests",{"3":{"1199":1,"1207":2,"1395":2,"1438":2}}],["sessionselectionspeakeroverlaptests",{"3":{"1199":1,"1207":2,"1395":1}}],["sessionselectionspeakeroverlap",{"2":{"1385":1},"3":{"1199":2,"1203":1,"1207":2,"1385":3,"1395":4}}],["sessionselectionservicetests",{"3":{"1199":1,"1207":2}}],["sessionselectionservice",{"2":{"1390":1},"3":{"881":1,"1199":2,"1203":1,"1207":2,"1359":4,"1382":1,"1390":4,"1395":9}}],["sessionselectionfilters",{"2":{"1385":1},"3":{"1199":2,"1203":1,"1207":2,"1385":2,"1395":7}}],["sessionselectionfilteroptions",{"2":{"1385":1},"3":{"1199":2,"1203":1,"1207":2,"1385":2,"1395":4,"1496":1}}],["sessionselectionaiscorestests",{"3":{"1199":1,"1207":2,"1395":2,"1438":1}}],["sessionselectionaiscores",{"2":{"1385":1},"3":{"1199":2,"1203":1,"1207":2,"1385":3,"1395":8}}],["sessionselectiondashboard",{"2":{"1390":1,"1535":1},"3":{"1199":3,"1203":1,"1207":2,"1350":2,"1359":1,"1390":2,"1395":30,"1535":1}}],["sessionselectiondashboardtests",{"3":{"1199":2,"1207":4,"1395":2}}],["sessionselectiondashboarddto",{"2":{"1347":1,"1390":1},"3":{"1199":16,"1203":1,"1207":3,"1347":5,"1350":8,"1359":2,"1380":1,"1390":1,"1395":10}}],["sessionselectiondisplay",{"2":{"1385":1},"3":{"1199":3,"1203":1,"1207":2,"1385":2,"1395":15}}],["sessionselectioncontrollertests",{"3":{"1199":1,"1207":3}}],["sessionselectioncontroller",{"2":{"1371":1,"1376":1,"1390":1},"3":{"1018":1,"1021":1,"1099":1,"1103":1,"1199":2,"1203":1,"1207":2,"1311":1,"1323":2,"1347":3,"1350":6,"1359":19,"1367":1,"1369":1,"1371":2,"1373":1,"1376":2,"1379":1,"1380":10,"1390":2,"1395":3,"1496":2,"1526":1,"2190":1}}],["sessionselectionmanage",{"2":{"1018":1,"1264":1,"1376":1,"1380":1},"3":{"1018":1,"1264":1,"1350":1,"1359":4,"1373":1,"1376":1,"1380":2,"1526":1}}],["sessionselection",{"3":{"837":1,"839":1,"841":1,"1018":1,"1359":9,"1395":6,"1438":1,"1447":1,"1496":1,"1632":2,"1640":5}}],["sessions",{"0":{"1283":1,"1290":1,"2239":1},"1":{"456":1,"457":1,"458":1,"459":1,"460":1,"461":1,"462":1,"463":1,"464":1,"465":1,"466":1,"902":1,"903":1,"904":1,"905":1,"906":1,"907":1,"908":1,"909":1,"910":1},"2":{"1350":1,"1380":1,"1395":2},"3":{"0":9,"128":1,"186":2,"187":1,"325":1,"353":1,"456":1,"492":1,"493":2,"494":1,"495":1,"497":4,"501":1,"517":1,"543":2,"545":3,"550":1,"554":1,"556":1,"583":1,"616":2,"618":3,"666":1,"690":1,"743":3,"782":2,"827":1,"852":1,"857":1,"862":1,"881":2,"902":1,"903":1,"905":12,"906":1,"907":4,"908":1,"909":2,"910":4,"926":1,"995":1,"1004":2,"1017":1,"1018":6,"1082":3,"1085":1,"1090":1,"1099":2,"1103":1,"1116":3,"1192":1,"1199":14,"1202":2,"1203":179,"1207":548,"1212":1,"1237":5,"1239":2,"1247":10,"1264":2,"1270":7,"1271":2,"1283":2,"1286":16,"1287":2,"1290":6,"1293":1,"1297":2,"1300":56,"1301":1,"1310":1,"1311":6,"1312":1,"1323":5,"1324":56,"1340":1,"1342":3,"1344":3,"1345":1,"1346":1,"1347":10,"1348":2,"1349":5,"1350":137,"1351":2,"1352":9,"1353":4,"1354":1,"1355":3,"1356":4,"1357":1,"1358":2,"1359":582,"1361":1,"1362":2,"1363":1,"1364":2,"1365":1,"1366":3,"1367":4,"1369":43,"1371":3,"1373":2,"1375":5,"1380":69,"1381":2,"1382":3,"1383":2,"1384":3,"1385":13,"1386":1,"1388":3,"1389":2,"1390":12,"1391":12,"1395":189,"1396":41,"1402":6,"1405":1,"1409":2,"1415":20,"1416":2,"1417":1,"1427":3,"1433":1,"1436":31,"1437":1,"1438":21,"1441":1,"1442":2,"1447":1,"1458":1,"1467":3,"1479":1,"1489":1,"1496":9,"1526":8,"1531":2,"1535":2,"1577":1,"1582":1,"1583":1,"1586":1,"1591":1,"1600":1,"1608":2,"1621":1,"1624":1,"1625":1,"1627":11,"1629":9,"1630":11,"1631":16,"1632":29,"1633":3,"1635":10,"1636":2,"1637":8,"1638":10,"1639":12,"1640":9,"1641":6,"1667":6,"1669":1,"1672":1,"1752":1,"1761":3,"1763":1,"1813":1,"1815":3,"1859":1,"1922":1,"1947":1,"1961":1,"1962":1,"1965":1,"1982":1,"1985":2,"1995":1,"2012":1,"2063":1,"2070":1,"2110":2,"2190":1,"2216":1,"2232":1,"2238":1,"2240":1,"2243":1}}],["session",{"0":{"1298":1,"1357":1,"1389":1,"1390":1,"1982":1,"1983":1,"2066":1},"1":{"204":1,"205":1,"206":1,"207":1,"208":1,"209":1,"210":1,"1015":1,"1016":1,"1017":1,"1018":1,"1019":1,"1020":1,"1021":1,"1022":1,"1113":1,"1114":1,"1115":1,"1116":1,"1117":1,"1118":1,"1119":1,"1120":1,"1396":1,"1397":1,"1398":1,"1399":1,"1400":1,"1401":1,"1402":1,"1966":1,"1967":1,"1968":1,"1969":1,"1970":1,"1971":1,"1972":1},"2":{"164":1,"780":1,"781":1,"790":2,"1115":2,"1116":1,"1117":1,"1188":1,"1342":1,"1343":1,"1346":1,"1630":1,"1631":1,"1633":1,"1635":1,"1638":1,"1640":1,"1765":1,"1770":2,"1854":1,"1857":1,"1858":1,"1859":1,"1860":1},"3":{"0":42,"57":1,"59":1,"160":1,"164":1,"165":1,"168":1,"170":1,"185":2,"186":1,"204":1,"207":6,"208":2,"209":2,"210":1,"221":1,"243":2,"261":1,"281":1,"285":1,"287":1,"340":1,"348":1,"350":2,"351":1,"355":1,"379":1,"380":2,"383":1,"384":1,"387":2,"390":1,"443":1,"458":1,"462":1,"497":2,"498":1,"499":4,"500":2,"501":4,"503":1,"506":1,"507":3,"508":2,"509":1,"510":2,"514":1,"516":1,"517":2,"519":1,"524":1,"534":3,"536":11,"540":3,"545":2,"551":1,"555":1,"560":1,"583":3,"664":2,"665":1,"670":1,"689":4,"690":11,"691":1,"692":1,"693":1,"748":1,"756":1,"780":1,"781":2,"782":2,"788":1,"790":4,"793":5,"794":1,"854":1,"857":1,"861":1,"864":1,"897":4,"903":4,"904":2,"905":18,"906":5,"907":1,"908":2,"909":1,"910":1,"926":2,"954":2,"995":1,"996":2,"1012":1,"1015":1,"1016":2,"1017":4,"1018":2,"1020":1,"1059":1,"1082":2,"1089":2,"1090":3,"1091":1,"1099":2,"1100":1,"1101":1,"1107":1,"1108":1,"1113":1,"1115":4,"1116":21,"1117":7,"1118":4,"1124":1,"1125":1,"1129":1,"1150":2,"1182":1,"1183":1,"1184":9,"1185":3,"1187":3,"1188":3,"1189":1,"1192":6,"1198":1,"1199":131,"1201":1,"1202":6,"1203":7,"1207":2,"1212":9,"1237":6,"1238":1,"1239":6,"1247":11,"1270":5,"1271":2,"1273":1,"1280":1,"1283":4,"1284":2,"1286":29,"1287":4,"1289":5,"1290":10,"1298":1,"1300":165,"1301":1,"1302":1,"1303":1,"1305":2,"1307":2,"1309":10,"1311":13,"1312":2,"1316":1,"1318":1,"1322":1,"1323":14,"1324":119,"1339":2,"1340":4,"1342":30,"1343":7,"1344":6,"1346":7,"1347":4,"1348":6,"1349":9,"1350":251,"1351":4,"1352":8,"1353":3,"1354":1,"1355":8,"1356":1,"1357":14,"1358":1,"1359":760,"1360":2,"1361":2,"1362":5,"1363":1,"1364":4,"1366":7,"1367":1,"1369":70,"1371":2,"1373":9,"1374":2,"1375":5,"1376":2,"1377":2,"1378":3,"1380":70,"1381":2,"1385":4,"1386":1,"1387":2,"1388":4,"1389":12,"1390":6,"1391":5,"1395":233,"1396":362,"1397":5,"1399":7,"1400":2,"1401":6,"1402":177,"1405":3,"1406":6,"1408":1,"1410":1,"1415":46,"1416":6,"1417":12,"1427":2,"1428":1,"1433":1,"1435":3,"1436":45,"1437":6,"1438":49,"1441":1,"1442":1,"1447":4,"1453":2,"1455":2,"1461":1,"1463":1,"1467":12,"1475":1,"1479":1,"1485":1,"1487":11,"1488":3,"1489":1,"1490":2,"1492":1,"1496":27,"1498":1,"1500":1,"1511":2,"1513":1,"1524":1,"1526":7,"1527":1,"1531":2,"1534":7,"1535":1,"1549":1,"1556":1,"1563":1,"1582":1,"1585":2,"1586":1,"1591":1,"1608":1,"1611":2,"1621":2,"1623":1,"1625":1,"1626":1,"1627":7,"1629":7,"1630":35,"1631":63,"1632":55,"1633":4,"1634":8,"1635":20,"1636":1,"1637":10,"1638":36,"1639":46,"1640":31,"1641":7,"1660":1,"1664":1,"1667":6,"1669":2,"1672":1,"1746":1,"1749":4,"1750":1,"1754":1,"1759":1,"1761":1,"1764":2,"1765":6,"1768":4,"1769":1,"1770":3,"1779":1,"1790":1,"1804":1,"1813":1,"1815":3,"1832":1,"1854":1,"1857":2,"1858":1,"1859":3,"1860":1,"1869":3,"1870":1,"1921":1,"1922":1,"1935":1,"1941":1,"1944":1,"1945":1,"1960":2,"1965":1,"1966":1,"1967":2,"1968":4,"1971":3,"1972":4,"1973":3,"1974":1,"1975":1,"1977":1,"1979":1,"1980":1,"1981":2,"1982":11,"1983":3,"1984":4,"1985":5,"1995":1,"2002":1,"2076":1,"2078":1,"2079":1,"2110":4,"2126":1,"2127":1,"2158":1,"2163":3,"2167":4,"2179":1,"2180":1,"2190":4,"2208":2,"2232":6,"2239":1}}],["semicolons",{"3":{"1286":1}}],["semicolon",{"3":{"219":1,"1270":4,"1286":2,"1300":1,"1311":1,"1324":4,"1339":1,"1415":2,"1436":4,"2150":1}}],["semanticproperties",{"3":{"1417":2}}],["semantically",{"3":{"1286":2,"1359":1,"1380":1,"1436":1,"1639":1}}],["semantic",{"0":{"1733":1},"3":{"684":1,"1196":1,"1237":1,"1249":1,"1259":1,"1270":1,"1271":1,"1300":1,"1324":1,"1359":1,"1396":1,"1417":2,"1425":1,"1500":1,"1526":2,"1534":1,"1558":1,"1569":1,"1577":2,"1585":1,"1591":1,"2177":1}}],["semanticscreenreader",{"3":{"1417":5}}],["semantics",{"3":{"0":5,"24":1,"40":1,"61":1,"115":1,"119":1,"120":1,"121":1,"202":1,"242":1,"247":1,"265":1,"310":1,"389":1,"434":1,"513":1,"618":1,"641":1,"658":1,"698":1,"733":1,"734":1,"735":1,"840":1,"842":1,"855":1,"906":1,"923":1,"946":1,"996":1,"1012":1,"1020":1,"1042":1,"1043":1,"1050":1,"1076":1,"1150":1,"1152":2,"1212":1,"1229":1,"1237":4,"1247":3,"1259":5,"1260":2,"1263":1,"1270":1,"1286":6,"1300":7,"1309":4,"1311":5,"1323":2,"1324":16,"1339":2,"1350":2,"1359":14,"1369":1,"1379":1,"1380":3,"1395":6,"1396":14,"1402":2,"1415":7,"1417":6,"1436":11,"1438":2,"1446":1,"1455":2,"1467":1,"1488":1,"1492":1,"1547":1,"1558":1,"1577":2,"1586":1,"1591":1,"1631":3,"1638":2,"1640":1,"1664":1,"1759":1,"1789":1,"1792":1,"1821":1,"1825":1,"1826":2,"1847":1,"1944":1,"1991":1,"2027":1,"2059":1,"2065":1,"2182":1,"2188":1,"2192":1,"2203":1}}],["semaphores",{"3":{"1270":2,"1300":2}}],["semaphoreslim",{"2":{"436":1,"995":1,"1254":1,"1774":1,"1909":1},"3":{"157":1,"434":1,"436":1,"995":1,"1254":1,"1259":6,"1270":3,"1275":1,"1286":5,"1300":8,"1324":3,"1332":1,"1339":3,"1417":3,"1774":1,"1909":1}}],["semaphorefullexception",{"3":{"1259":1,"1275":1,"1286":1}}],["semaphore",{"3":{"155":1,"157":3,"159":1,"830":1,"996":1,"1259":4,"1267":1,"1270":6,"1275":1,"1286":9,"1298":1,"1300":8,"1301":1,"1311":3,"1323":2,"1324":1,"1332":1,"1339":4,"1417":5,"1577":1,"1585":1,"1909":2}}],["semver",{"3":{"147":1,"1576":1,"1577":3,"1673":1,"1674":1,"1733":1}}],["separable",{"3":{"126":1,"1300":1,"1396":1,"1436":1}}],["separators",{"3":{"741":1,"1116":1,"1286":2,"1300":3,"1323":1,"1324":1,"1396":1}}],["separator",{"3":{"599":1,"741":2,"743":1,"1228":1,"1229":1,"1247":3,"1286":1,"1296":1,"1301":1,"1309":1,"1324":4,"1335":1,"1339":1,"1395":4,"1442":1,"1467":1,"1486":1,"1685":1}}],["separation",{"3":{"0":1,"642":1,"868":1,"999":2,"1050":1,"1230":1,"1270":1,"1286":2,"1300":3,"1309":1,"1323":2,"1324":2,"1359":1,"1381":1,"1395":2,"1396":2,"1402":2,"1417":2,"1436":1,"1467":1,"1526":1,"1545":1,"1555":1,"1577":1,"1638":1,"1639":1,"1815":1,"1817":1}}],["separating",{"3":{"0":1,"1185":1,"1229":1,"1259":1,"1286":1,"1300":4,"1302":1,"1309":1,"1311":2,"1324":4,"1350":1,"1359":4,"1369":1,"1380":1,"1395":3,"1402":3,"1417":1,"1427":1,"1436":1,"1478":1,"1489":1,"1577":1,"1638":2,"1953":1,"2007":1,"2069":1}}],["separates",{"3":{"395":1,"418":1,"426":1,"862":1,"905":1,"998":2,"1237":1,"1247":1,"1263":1,"1270":1,"1286":1,"1300":2,"1311":1,"1323":1,"1324":2,"1359":1,"1396":1,"1402":1,"1415":1,"1416":1,"1436":3,"1455":1,"1462":1,"1794":1,"1982":1,"2034":1,"2063":1,"2143":1}}],["separately",{"3":{"224":1,"235":1,"265":1,"275":1,"296":1,"322":1,"518":2,"549":1,"626":1,"665":1,"676":1,"684":1,"707":1,"715":1,"724":1,"785":1,"789":1,"838":2,"881":1,"883":1,"1049":1,"1061":1,"1100":1,"1169":1,"1231":1,"1237":2,"1243":1,"1247":1,"1259":3,"1270":1,"1271":3,"1286":4,"1300":3,"1301":2,"1305":1,"1309":1,"1310":1,"1311":1,"1316":1,"1323":1,"1324":5,"1334":1,"1339":4,"1350":5,"1359":15,"1380":1,"1395":2,"1396":4,"1402":2,"1415":4,"1417":3,"1427":1,"1429":1,"1436":14,"1438":1,"1441":1,"1442":1,"1443":1,"1467":4,"1476":2,"1487":1,"1489":1,"1554":1,"1639":1,"1640":1,"1675":1,"1815":1,"1837":1,"1887":1,"1938":1,"2001":1,"2002":1,"2063":1,"2161":1,"2193":1}}],["separated",{"3":{"26":1,"330":1,"633":2,"757":1,"974":1,"1012":1,"1212":1,"1247":4,"1253":1,"1259":1,"1260":1,"1270":1,"1271":1,"1286":2,"1300":4,"1310":1,"1311":3,"1318":1,"1323":1,"1324":3,"1339":2,"1396":2,"1402":1,"1417":2,"1436":1,"1455":1,"1526":2,"1543":1,"1545":1,"1630":1,"1727":1,"1988":1,"2182":1}}],["separate",{"0":{"1837":1,"2063":1},"3":{"0":9,"24":1,"30":1,"35":1,"39":1,"52":1,"70":1,"97":1,"109":1,"117":1,"136":1,"165":2,"168":3,"186":1,"188":1,"201":1,"237":2,"255":1,"291":1,"317":1,"318":1,"319":1,"320":1,"361":1,"363":1,"390":1,"391":1,"403":1,"509":1,"511":1,"532":1,"551":1,"558":1,"572":2,"579":1,"602":1,"634":1,"640":1,"651":1,"665":1,"676":1,"690":1,"698":1,"707":1,"731":1,"742":1,"751":1,"782":1,"784":1,"790":1,"829":2,"838":1,"862":1,"869":1,"881":1,"882":1,"905":1,"971":1,"980":1,"1006":1,"1018":1,"1019":2,"1025":2,"1042":1,"1043":2,"1045":1,"1049":1,"1050":1,"1059":2,"1067":1,"1068":1,"1116":3,"1123":1,"1124":1,"1125":2,"1170":1,"1174":1,"1178":1,"1193":1,"1212":4,"1237":5,"1247":7,"1248":1,"1259":3,"1262":1,"1267":1,"1270":9,"1271":4,"1273":1,"1279":1,"1286":16,"1293":1,"1300":15,"1301":4,"1302":1,"1309":10,"1310":1,"1311":7,"1312":1,"1313":1,"1315":1,"1323":15,"1324":28,"1325":1,"1339":11,"1342":1,"1346":1,"1349":1,"1350":19,"1352":2,"1355":1,"1359":40,"1369":3,"1374":1,"1380":1,"1384":1,"1393":1,"1395":20,"1396":31,"1398":1,"1401":2,"1402":17,"1415":19,"1416":3,"1417":11,"1427":5,"1433":1,"1436":29,"1437":1,"1438":1,"1441":2,"1442":1,"1444":1,"1447":1,"1449":1,"1455":1,"1456":1,"1457":1,"1460":1,"1465":1,"1467":6,"1473":1,"1478":1,"1490":2,"1492":4,"1512":1,"1517":1,"1545":1,"1599":1,"1605":1,"1616":1,"1617":1,"1626":1,"1627":1,"1630":3,"1631":2,"1632":1,"1634":1,"1638":1,"1639":1,"1640":1,"1652":1,"1662":2,"1665":1,"1693":1,"1696":1,"1731":3,"1739":1,"1748":1,"1749":1,"1755":1,"1765":1,"1767":1,"1768":1,"1790":1,"1796":1,"1802":1,"1805":1,"1810":1,"1821":1,"1842":1,"1846":1,"1847":1,"1848":1,"1849":2,"1851":1,"1855":1,"1883":1,"1900":1,"1903":1,"1919":1,"1964":1,"1972":1,"1995":1,"1997":1,"2000":1,"2004":1,"2034":1,"2041":1,"2063":2,"2067":1,"2068":1,"2069":1,"2073":1,"2074":1,"2088":1,"2107":1,"2109":1,"2111":1,"2122":1,"2123":1,"2133":1,"2144":1,"2151":1,"2156":1,"2158":1,"2163":1,"2185":1,"2193":1,"2202":1,"2230":1}}],["seat",{"3":{"1631":1,"1632":1}}],["seating",{"3":{"1630":2}}],["seats",{"3":{"1395":1}}],["season",{"3":{"1324":1}}],["seam",{"3":{"1323":1,"1324":1,"1359":1}}],["seamless",{"3":{"1300":1}}],["searchandwaitforrowasync",{"2":{"1433":1},"3":{"1433":1,"1436":1,"1488":1}}],["searchasync",{"3":{"1396":8}}],["searchability",{"3":{"1286":1}}],["searchable",{"3":{"363":1,"611":1,"1286":1,"1350":1}}],["searchfunc",{"3":{"1395":1}}],["searchfilterkey",{"3":{"1324":3,"1415":1}}],["searchusersasync",{"3":{"1395":2}}],["searchstringchanged",{"3":{"1395":1}}],["searchstring",{"3":{"1324":2,"1359":1,"1395":2}}],["searchvalues",{"3":{"1309":1,"1311":4,"1324":3}}],["searchtermfrom",{"3":{"1324":1}}],["searchterm",{"3":{"1300":1,"1311":1,"1324":2}}],["searching",{"3":{"1286":1,"1395":1}}],["searchedlocation",{"3":{"1324":1}}],["searched",{"3":{"488":1,"490":1,"1161":1,"1395":1,"1436":1}}],["searches",{"3":{"350":1,"1150":1,"1259":1,"1359":2,"1436":2,"1456":1,"1749":1}}],["search",{"3":{"0":1,"135":1,"243":1,"255":1,"259":1,"491":1,"492":1,"493":1,"511":1,"527":1,"528":1,"610":1,"684":1,"685":1,"690":2,"751":1,"819":1,"822":1,"837":1,"861":1,"863":1,"899":1,"946":1,"956":1,"963":1,"980":1,"1093":1,"1229":2,"1270":16,"1271":1,"1286":3,"1300":3,"1301":2,"1311":3,"1324":14,"1350":1,"1359":2,"1369":2,"1383":1,"1385":1,"1391":2,"1395":51,"1396":17,"1414":1,"1415":8,"1417":4,"1427":1,"1436":9,"1438":3,"1474":1,"1490":1,"1526":1,"1545":1,"1553":1,"1577":1,"1591":1,"1599":1,"1600":1,"1627":2,"1631":1,"1632":1,"1681":1,"1685":1,"1750":4,"1755":4,"1773":1,"1786":1,"1881":1,"1902":1,"1922":1,"1989":1,"2048":1,"2145":1,"2158":1,"2202":1,"2204":1,"2220":2}}],["seal",{"3":{"1323":5,"1438":1,"1824":1,"1826":1}}],["sealpipeline",{"3":{"1262":1,"1316":1,"1323":2}}],["sealing",{"3":{"0":1,"125":1,"126":1,"1168":1,"1169":1,"1170":1,"1259":1,"1270":1,"1312":1,"1323":1,"1324":1,"1415":1,"1436":5,"1671":1,"1827":1,"1883":1}}],["seals",{"0":{"1316":1},"3":{"0":1,"117":1,"122":1,"1289":1,"1311":1,"1323":1,"1324":1,"1383":1,"1396":1,"1415":1,"1436":1,"1662":1,"1824":1,"1883":1}}],["sealed",{"3":{"0":10,"45":1,"47":2,"80":1,"115":2,"166":1,"214":1,"255":1,"281":1,"310":2,"314":1,"318":1,"341":1,"350":1,"359":1,"448":2,"450":1,"455":1,"492":1,"498":1,"499":1,"511":1,"577":1,"631":2,"635":1,"657":2,"725":1,"733":1,"782":1,"854":1,"910":1,"922":1,"925":1,"926":1,"962":1,"963":3,"964":2,"967":1,"968":1,"988":1,"996":1,"1026":1,"1032":1,"1033":2,"1034":1,"1038":1,"1051":1,"1117":1,"1119":1,"1167":1,"1168":4,"1170":1,"1173":1,"1175":1,"1180":1,"1212":5,"1219":1,"1229":11,"1235":1,"1237":23,"1241":1,"1243":1,"1247":35,"1259":20,"1260":1,"1262":1,"1265":2,"1270":32,"1271":8,"1272":1,"1273":1,"1280":1,"1283":1,"1286":119,"1289":1,"1300":75,"1301":10,"1309":43,"1310":10,"1311":54,"1312":6,"1318":1,"1323":50,"1324":108,"1339":56,"1350":84,"1359":352,"1366":1,"1369":17,"1377":1,"1380":35,"1384":1,"1395":65,"1396":143,"1398":1,"1402":57,"1415":96,"1416":7,"1417":86,"1423":1,"1426":1,"1427":4,"1431":2,"1432":1,"1436":260,"1489":1,"1490":1,"1510":1,"1525":1,"1526":8,"1577":2,"1591":6,"1651":1,"1654":1,"1662":2,"1664":1,"1675":1,"1720":1,"1807":1,"1849":1,"1857":1,"1874":1,"1891":1,"1896":1,"1978":1,"1994":1,"2000":1,"2042":1,"2101":1,"2104":2,"2131":3,"2132":1,"2142":1,"2199":1,"2232":1}}],["senior",{"1":{"2212":1,"2213":1,"2214":1,"2215":1,"2216":1},"3":{"1794":1,"2099":1,"2109":1,"2212":1,"2213":1,"2217":1,"2218":1,"2220":2}}],["sensibly",{"3":{"1417":1,"1562":1}}],["sensible",{"3":{"709":1,"1339":1,"1395":1,"1436":2,"1488":1,"1555":1,"1561":1,"1607":1,"1642":1,"1678":1,"1741":1,"1927":1}}],["sensitivity",{"3":{"610":1,"1196":1,"1431":1,"1436":3,"1467":1,"1638":1,"2158":1}}],["sensitivedatadiscovery",{"3":{"1467":1}}],["sensitivedatalogginggatetests",{"3":{"1108":1,"1199":1,"1207":3,"1286":1}}],["sensitivedatalogginggate",{"2":{"1108":1},"3":{"0":1,"1108":2,"1199":2,"1203":1,"1207":2,"1212":1,"1273":3,"1286":6,"1323":1,"1496":2}}],["sensitively",{"3":{"1350":1,"1359":1}}],["sensitive",{"3":{"0":1,"122":1,"209":1,"265":1,"295":1,"313":1,"358":2,"360":1,"362":1,"585":1,"916":1,"950":1,"1108":1,"1110":1,"1247":1,"1284":1,"1286":1,"1300":3,"1310":1,"1311":1,"1312":1,"1323":1,"1324":3,"1339":1,"1350":1,"1359":6,"1380":1,"1395":1,"1396":3,"1415":3,"1416":1,"1417":6,"1423":1,"1436":3,"1453":2,"1467":1,"1563":1,"1567":1,"1631":1,"1971":1,"1973":1,"2139":1,"2140":2,"2141":1,"2144":1,"2145":1,"2149":1}}],["sense",{"3":{"396":1,"988":1,"1247":1,"1259":1,"1286":1,"1301":1,"1359":4,"1369":1,"1396":1,"1417":1,"1467":1,"1473":1,"2044":1,"2155":1}}],["sentto",{"3":{"1324":1,"1436":1}}],["senton",{"3":{"1309":6,"1415":2,"1436":2}}],["sentrequest",{"3":{"1199":3,"1207":1}}],["sentbyuserid",{"2":{"231":1},"3":{"225":1,"231":1,"1286":1,"1309":7,"1436":1,"1933":1}}],["sent",{"3":{"225":2,"227":1,"231":1,"434":1,"741":1,"759":1,"791":1,"837":1,"857":1,"1018":1,"1091":1,"1183":1,"1184":1,"1185":1,"1187":2,"1270":1,"1278":1,"1286":3,"1300":4,"1303":1,"1308":2,"1309":19,"1311":2,"1323":5,"1324":18,"1337":1,"1339":4,"1359":4,"1369":1,"1380":1,"1395":8,"1396":9,"1415":4,"1423":1,"1427":1,"1435":1,"1436":10,"1438":1,"1640":1,"1641":2,"1770":1,"1776":1,"1906":1,"1933":1,"1969":1,"2024":1,"2129":1,"2189":1}}],["sentences",{"3":{"265":1,"534":1,"543":1,"626":1,"1193":1,"1237":1,"1286":1,"1300":1,"1395":2,"1396":1,"1435":1,"1496":2,"1564":1,"1653":1,"1737":1}}],["sentence",{"3":{"0":1,"138":1,"139":1,"142":1,"245":1,"265":2,"373":1,"522":1,"523":1,"582":2,"611":1,"624":1,"626":2,"628":1,"798":1,"822":1,"864":1,"971":2,"972":2,"973":4,"974":2,"1007":1,"1026":1,"1067":2,"1068":1,"1119":1,"1237":1,"1270":1,"1286":3,"1311":3,"1323":3,"1324":9,"1339":2,"1359":4,"1369":1,"1389":1,"1395":12,"1396":9,"1415":2,"1427":1,"1430":1,"1436":7,"1478":1,"1496":3,"1526":2,"1577":2,"1586":1,"1591":2,"1653":1,"1750":1,"1900":1,"2033":1,"2039":2,"2043":2,"2056":1,"2171":1,"2178":1,"2226":1}}],["sentinels",{"3":{"1324":1}}],["sentinelref",{"3":{"1324":2}}],["sentinel",{"3":{"0":1,"26":1,"265":4,"268":1,"657":5,"658":3,"1234":1,"1237":4,"1244":1,"1247":1,"1253":1,"1259":1,"1274":1,"1275":1,"1282":1,"1286":7,"1300":4,"1309":2,"1311":1,"1324":11,"1350":2,"1352":1,"1359":3,"1369":1,"1383":1,"1390":3,"1395":19,"1396":3,"1436":6,"1438":4,"1577":1,"1591":1,"1611":1,"1653":1,"1669":1}}],["sendnotfoundprobeasync",{"3":{"1436":1}}],["sendnotificationasync",{"3":{"1324":1}}],["sendvalidationerrorprobeasync",{"3":{"1436":1}}],["sendforwardedasync",{"3":{"1436":1}}],["sendgetrequestasync",{"3":{"1395":3}}],["sendgrid",{"3":{"1309":1}}],["sendprobeasync",{"3":{"1339":2}}],["sendpushnotificationrequestvalidatortests",{"3":{"1199":1,"1207":2}}],["sendpushnotificationrequestvalidator",{"2":{"1305":1},"3":{"1199":2,"1203":1,"1207":2,"1305":2,"1309":13}}],["sendpushnotificationrequest",{"2":{"1303":1,"1305":1},"3":{"1199":14,"1203":1,"1207":2,"1303":1,"1305":2,"1309":13,"1324":11,"1436":1}}],["sendpushnotificationcommand",{"2":{"435":1,"436":1,"1304":1,"1933":1},"3":{"435":1,"436":1,"1199":5,"1203":1,"1207":2,"1270":2,"1304":3,"1309":13,"1933":1}}],["sendpushnotificationhandlertests",{"3":{"1199":1,"1207":3}}],["sendpushnotificationhandler",{"2":{"225":1,"229":1,"230":1,"231":1,"434":1,"435":1,"1304":1,"1512":1,"1933":1},"3":{"0":1,"225":7,"229":1,"230":1,"231":2,"434":1,"435":3,"436":1,"1199":2,"1203":1,"1207":2,"1286":2,"1302":1,"1304":3,"1309":54,"1323":2,"1512":1,"1933":1}}],["sendresult",{"3":{"1324":3}}],["sendreadwithauthrefreshasync",{"3":{"1324":3}}],["sendrequestasync",{"2":{"2074":1},"3":{"0":1,"881":2,"1324":8,"1395":12,"2074":1}}],["sendtouserasync",{"3":{"1309":3}}],["sendtousersasync",{"2":{"434":1,"1933":2},"3":{"434":1,"1286":1,"1309":5,"1323":2,"1933":2}}],["sendmailasync",{"3":{"230":1,"1309":1}}],["sendasync",{"3":{"230":1,"1309":8,"1311":3,"1323":1,"1324":15,"1339":1,"1436":7,"1488":1}}],["sendemailasync",{"2":{"2181":1},"3":{"2181":1}}],["sendemailconfirmationasync",{"3":{"1415":2,"1436":1}}],["sendemailconfirmationcommand",{"2":{"1408":1},"3":{"1199":7,"1203":1,"1207":2,"1300":1,"1323":2,"1408":2,"1415":12,"1436":1}}],["sendemailconfirmationrequestvalidator",{"2":{"1295":1},"3":{"1199":1,"1203":1,"1207":1,"1295":1,"1300":3}}],["sendemailconfirmationrequest",{"2":{"1295":1},"3":{"1199":11,"1203":1,"1207":1,"1295":1,"1300":3,"1323":1,"1324":1,"1415":3}}],["sendemailconfirmation",{"2":{"1408":1},"3":{"674":1,"1203":2,"1207":6,"1323":2,"1408":2,"1415":4}}],["sendemailconfirmationhandlertests",{"3":{"1199":1,"1207":2}}],["sendemailconfirmationhandler",{"2":{"674":1,"1408":1},"3":{"674":4,"1199":2,"1203":1,"1207":2,"1300":2,"1323":1,"1408":2,"1415":5,"1496":1}}],["sendemailconfirmationhandlerbase",{"2":{"674":1,"1322":1,"1408":1,"2198":1},"3":{"230":1,"674":2,"1199":3,"1203":1,"1207":2,"1300":3,"1322":3,"1323":4,"1408":1,"1415":2,"1496":1,"1577":1,"1582":1,"2198":1}}],["senderscopeddedupkey",{"3":{"1309":2}}],["senders",{"3":{"1309":3,"1323":1}}],["sender",{"3":{"0":1,"223":1,"225":4,"230":3,"231":3,"380":1,"433":1,"436":1,"1192":1,"1286":1,"1300":2,"1302":1,"1303":3,"1307":1,"1309":24,"1320":1,"1323":8,"1436":1,"1467":1,"1496":1,"1512":1,"1935":1,"1938":1,"1940":1,"1941":1,"1944":1}}],["sendordershippedemailinternalcommand",{"2":{"1264":1},"3":{"1264":2}}],["sendordershippedemailinternalcommandhandler",{"2":{"230":1},"3":{"230":1}}],["sendorderpaymentfailedemail",{"3":{"2187":1}}],["sendorderpaymentfailedemailinternalcommand",{"2":{"2165":1},"3":{"2165":1,"2189":2}}],["sendorderpaymentfailedemailinternalcommandhandler",{"2":{"230":1},"3":{"230":1,"2189":1}}],["sendorderpaidemailinternalcommand",{"2":{"1026":1},"3":{"1026":1}}],["sendorderpaidemailinternalcommandhandler",{"2":{"230":1,"1026":1},"3":{"230":2,"1026":3}}],["sending",{"0":{"1933":1},"3":{"180":1,"792":1,"1019":1,"1186":1,"1202":1,"1203":1,"1271":1,"1286":2,"1304":2,"1309":8,"1323":4,"1324":4,"1339":1,"1359":3,"1384":1,"1395":1,"1396":3,"1402":3,"1415":3,"1424":1,"1427":1,"1508":1,"1769":1,"1770":1,"1933":2}}],["sendshttp2withpriorknowledge",{"3":{"1339":1}}],["sendshttp11",{"3":{"1339":1}}],["sends",{"3":{"0":2,"97":1,"111":1,"157":2,"160":1,"180":1,"206":1,"227":1,"230":1,"382":1,"434":2,"435":2,"448":2,"449":1,"507":1,"649":1,"773":1,"826":1,"827":1,"842":1,"881":2,"1050":1,"1091":1,"1237":1,"1241":1,"1247":1,"1286":3,"1300":4,"1304":1,"1309":11,"1311":2,"1312":1,"1323":5,"1324":23,"1328":1,"1339":4,"1347":1,"1350":3,"1359":13,"1369":1,"1380":2,"1389":1,"1395":18,"1396":9,"1400":1,"1402":1,"1415":4,"1416":1,"1422":1,"1436":6,"1438":2,"1441":1,"1455":1,"1458":2,"1467":3,"1481":1,"1600":1,"1627":1,"1641":2,"1666":1,"1719":1,"1765":1,"1767":1,"1770":1,"1835":1,"1908":1,"1933":1,"1936":1,"1944":1,"1949":1,"2003":1,"2010":1,"2131":2,"2133":2,"2147":2,"2181":1,"2189":1}}],["send",{"0":{"1304":1,"1512":1},"2":{"641":1,"642":1},"3":{"0":6,"31":1,"72":2,"109":1,"175":1,"202":1,"225":6,"227":2,"230":10,"231":2,"236":1,"336":1,"380":1,"435":1,"436":1,"640":1,"641":1,"642":1,"649":1,"675":1,"740":1,"789":1,"795":1,"848":1,"854":2,"856":2,"881":2,"883":1,"896":1,"926":1,"1018":1,"1041":1,"1203":3,"1207":6,"1247":1,"1249":1,"1270":1,"1286":8,"1294":1,"1300":10,"1302":5,"1303":1,"1304":3,"1305":2,"1307":1,"1309":51,"1311":8,"1323":16,"1324":54,"1339":4,"1350":1,"1359":3,"1369":1,"1395":2,"1396":6,"1400":1,"1402":1,"1408":3,"1415":9,"1427":1,"1434":1,"1436":10,"1438":5,"1467":3,"1512":2,"1534":1,"1577":1,"1591":1,"1595":1,"1596":2,"1641":4,"1666":1,"1667":1,"1669":1,"1719":1,"1723":1,"1749":1,"1761":1,"1765":1,"1767":1,"1770":2,"1776":1,"1911":1,"1933":7,"1934":5,"1935":2,"1936":2,"1937":1,"1938":3,"1940":4,"1941":1,"1950":1,"1969":1,"2148":1,"2166":1,"2196":1}}],["sev",{"2":{"2046":1},"3":{"0":1,"609":15,"1436":2,"1467":3,"1469":1,"1474":4,"1476":8,"2046":1}}],["seventy",{"3":{"2044":1}}],["seventeen",{"3":{"135":1,"141":2,"468":1,"475":2,"616":1,"618":2,"782":1,"803":1,"1212":1,"1350":1,"1369":13,"1395":3,"1417":2,"1467":1,"1660":1,"1666":1,"2111":1,"2112":1}}],["seventh",{"3":{"0":1,"1055":1,"1359":1,"1396":1,"1436":5,"1526":1,"1728":1}}],["seven",{"3":{"0":14,"76":1,"80":1,"115":2,"117":1,"121":2,"122":1,"126":1,"135":1,"150":1,"184":1,"195":1,"203":1,"241":3,"243":1,"245":3,"250":1,"251":2,"252":1,"254":5,"256":2,"257":2,"258":1,"259":3,"260":3,"261":1,"400":2,"401":2,"404":1,"418":1,"423":1,"426":1,"454":1,"501":3,"502":1,"554":1,"556":1,"572":2,"574":1,"577":1,"578":3,"579":1,"589":1,"599":1,"604":1,"616":1,"626":1,"631":1,"632":2,"633":3,"635":2,"638":1,"640":1,"657":1,"659":3,"672":3,"674":3,"677":1,"690":1,"715":1,"749":1,"750":1,"767":1,"774":1,"782":2,"801":1,"881":3,"897":1,"900":1,"914":1,"968":1,"1018":1,"1020":1,"1050":1,"1076":1,"1090":1,"1176":1,"1212":4,"1220":1,"1237":1,"1241":1,"1247":9,"1260":1,"1262":1,"1270":7,"1273":1,"1286":2,"1300":3,"1301":2,"1309":1,"1311":1,"1312":2,"1323":5,"1324":7,"1339":7,"1347":1,"1350":17,"1359":33,"1369":4,"1371":1,"1380":2,"1382":1,"1395":6,"1396":8,"1402":2,"1415":11,"1417":1,"1428":2,"1431":1,"1436":34,"1438":2,"1443":1,"1450":1,"1455":1,"1459":1,"1467":2,"1476":1,"1486":1,"1492":1,"1496":3,"1524":1,"1526":6,"1531":1,"1532":2,"1577":3,"1591":3,"1600":2,"1627":1,"1650":1,"1651":1,"1662":1,"1667":1,"1670":1,"1671":2,"1686":1,"1728":1,"1729":1,"1805":1,"1821":1,"1826":1,"1839":1,"1874":1,"1916":1,"1923":1,"1963":1,"1980":1,"1982":1,"1984":2,"2044":1,"2055":1,"2060":1,"2065":1,"2087":1,"2097":2,"2098":2,"2131":1,"2167":2,"2171":2,"2179":1}}],["sever",{"3":{"1436":2}}],["severities",{"3":{"610":1,"614":1,"649":1,"1324":3,"1467":1,"1474":1,"1476":1,"1483":1,"2079":1,"2158":1}}],["severityfor",{"3":{"1396":2}}],["severity",{"0":{"1222":1},"2":{"135":1,"490":1,"491":1,"492":1,"493":1,"1526":1},"3":{"0":6,"107":2,"110":1,"111":1,"113":1,"135":2,"265":1,"407":1,"486":1,"490":2,"491":2,"492":2,"493":2,"607":2,"608":1,"609":23,"610":1,"611":6,"612":2,"633":2,"896":2,"898":2,"980":1,"981":2,"1018":1,"1198":2,"1199":9,"1207":2,"1212":1,"1213":1,"1214":2,"1225":1,"1229":2,"1237":1,"1271":1,"1311":2,"1324":24,"1339":1,"1359":1,"1395":3,"1396":5,"1431":1,"1436":6,"1438":1,"1453":2,"1467":12,"1476":2,"1489":1,"1526":1,"1552":1,"1577":1,"1581":1,"1582":1,"1584":1,"1591":1,"1649":1,"1654":1,"1661":1,"1662":1,"1673":1,"1683":1,"1685":1,"1696":1,"1727":2,"1903":1,"1905":1,"2046":2,"2049":1,"2085":1,"2086":1,"2158":12,"2220":1,"2232":1}}],["several",{"3":{"0":1,"27":1,"109":3,"131":1,"165":1,"166":1,"185":1,"228":1,"268":1,"396":1,"471":1,"508":1,"509":1,"544":1,"549":1,"551":1,"600":2,"601":1,"632":1,"677":1,"714":1,"765":1,"809":1,"810":1,"822":1,"839":1,"900":1,"974":1,"988":1,"1055":1,"1067":1,"1103":1,"1104":1,"1107":1,"1220":1,"1229":4,"1239":1,"1246":1,"1247":1,"1259":1,"1263":1,"1269":2,"1270":9,"1271":1,"1280":1,"1286":15,"1289":1,"1300":3,"1301":3,"1302":1,"1309":1,"1310":1,"1311":8,"1319":1,"1320":1,"1323":5,"1324":11,"1332":1,"1333":1,"1339":3,"1350":9,"1351":1,"1352":1,"1353":1,"1359":10,"1362":1,"1374":1,"1380":2,"1387":1,"1395":4,"1396":10,"1405":1,"1409":1,"1416":1,"1417":6,"1420":1,"1429":1,"1436":17,"1442":1,"1475":1,"1488":2,"1489":1,"1490":1,"1496":3,"1499":1,"1500":2,"1537":1,"1577":1,"1582":1,"1671":1,"1675":1,"1692":1,"1697":1,"1699":1,"1708":1,"1726":1,"1727":1,"1767":1,"1805":1,"1863":1,"1942":1,"2041":1,"2087":1}}],["severe",{"3":{"0":1,"109":2,"1224":1,"1229":2,"1311":5,"1312":2,"1324":2,"1702":1,"1805":1,"1806":1,"1927":2}}],["ser",{"3":{"1685":1,"1686":2}}],["serious",{"3":{"2061":1}}],["seriously",{"3":{"1396":1}}],["serif",{"3":{"1324":1,"2075":1}}],["serially",{"3":{"1455":1}}],["serial",{"3":{"1300":1,"1436":1}}],["serializable",{"3":{"1286":1,"1300":1,"1324":1,"1350":1,"1359":2,"1417":1,"1534":1}}],["serializationerror",{"3":{"1286":1}}],["serialization",{"2":{"1229":2,"1369":1},"3":{"0":1,"245":1,"253":1,"291":1,"380":1,"520":1,"593":1,"655":1,"657":2,"658":1,"659":1,"733":1,"964":1,"1043":1,"1102":1,"1150":1,"1154":1,"1203":3,"1207":24,"1212":2,"1223":1,"1229":8,"1233":1,"1234":1,"1235":1,"1237":6,"1259":3,"1270":1,"1300":3,"1301":3,"1311":3,"1323":3,"1324":2,"1341":1,"1350":1,"1359":9,"1369":1,"1380":2,"1402":2,"1415":3,"1417":2,"1436":3,"1438":1,"1496":1,"1540":1,"1916":1,"1920":1,"1944":1,"2040":1,"2232":1}}],["serializing",{"3":{"434":1,"1251":1,"1270":1,"1300":1,"1301":1,"1311":3,"1323":1,"1359":1,"1396":2,"1402":1,"1417":2,"1475":1,"1916":1}}],["serializetoutf8bytes",{"3":{"1301":1,"1311":1,"1415":1}}],["serialize",{"3":{"1229":1,"1244":1,"1247":2,"1270":2,"1286":1,"1300":5,"1301":2,"1311":3,"1324":2,"1350":2,"1359":1,"1395":1,"1399":1,"1402":2,"1415":3,"1417":3,"1427":2,"1838":1,"1909":2,"1912":1,"1950":1}}],["serializes",{"3":{"24":2,"77":1,"157":1,"195":1,"243":1,"301":1,"306":1,"341":1,"423":1,"424":1,"436":1,"519":1,"541":1,"657":1,"660":1,"725":1,"964":1,"995":1,"996":2,"999":1,"1018":1,"1050":2,"1142":1,"1150":1,"1229":3,"1233":3,"1234":1,"1237":5,"1259":2,"1270":1,"1286":4,"1300":8,"1301":2,"1311":12,"1323":3,"1324":5,"1345":1,"1350":1,"1359":1,"1396":2,"1399":1,"1402":4,"1415":4,"1417":3,"1436":2,"1455":1,"1496":1,"1638":1,"1806":1,"1874":1,"1887":2,"1909":1,"1912":1,"1927":1,"2076":1,"2091":1,"2169":1}}],["serialized",{"3":{"0":3,"17":1,"38":1,"78":1,"157":2,"159":3,"330":1,"380":1,"447":1,"472":1,"536":1,"558":1,"781":1,"783":1,"852":1,"857":1,"965":1,"966":1,"1034":1,"1043":1,"1074":1,"1140":1,"1175":1,"1212":1,"1223":1,"1229":4,"1237":3,"1245":1,"1259":6,"1274":1,"1275":1,"1286":2,"1289":1,"1298":1,"1300":6,"1309":3,"1311":4,"1312":1,"1323":2,"1324":7,"1350":4,"1359":1,"1380":1,"1396":9,"1400":1,"1402":5,"1406":1,"1415":4,"1417":2,"1436":6,"1546":1,"1630":1,"1631":1,"1639":1,"1640":1,"1805":1,"1845":1,"1889":1,"1908":2,"1944":1,"1946":1,"1950":1,"1968":1,"1972":1,"1988":1,"2065":1,"2130":1,"2184":1}}],["serializeroptions",{"3":{"1259":1,"1286":1}}],["serializer",{"3":{"0":1,"79":1,"82":1,"381":1,"470":1,"965":1,"1212":1,"1286":1,"1300":2,"1311":2,"1324":1,"1359":1,"1396":1,"1400":1,"1427":1,"1438":1,"1944":1,"2232":1}}],["series",{"0":{"1779":1,"1786":1},"1":{"1778":1,"1779":1,"2204":1,"2205":1,"2206":1,"2207":1,"2208":1,"2209":1,"2210":1,"2211":1},"3":{"0":1,"24":1,"126":1,"527":1,"863":1,"1247":1,"1259":1,"1270":1,"1286":1,"1311":1,"1359":1,"1395":2,"1425":3,"1427":4,"1778":2,"1779":2,"1780":1,"1783":1,"1790":1,"1793":1,"1802":1,"1807":1,"1812":1,"1817":1,"1821":1,"1827":1,"1834":1,"1840":1,"1846":1,"1853":1,"1862":1,"1868":1,"1877":2,"1886":2,"1892":1,"1899":1,"1905":1,"1912":1,"1924":1,"1930":1,"1935":1,"1941":2,"1942":1,"1947":1,"1950":2,"1957":1,"1959":1,"1965":1,"1972":1,"1979":2,"1985":1,"1991":1,"1997":1,"2002":1,"2014":1,"2028":2,"2030":1,"2031":1,"2032":1,"2035":1,"2038":2,"2049":1,"2060":1,"2069":1,"2070":2,"2077":1,"2080":1,"2086":1,"2098":1,"2108":2,"2115":1,"2123":2,"2128":1,"2133":1,"2139":2,"2146":2,"2153":1,"2160":1,"2169":1,"2177":1,"2180":1,"2183":1,"2193":1,"2194":1,"2203":4,"2204":3,"2211":1,"2215":3,"2232":1,"2236":2,"2245":2}}],["serilogbootstraptests",{"3":{"1199":1,"1207":2,"1437":1,"1487":1}}],["seriloghostextensionstests",{"3":{"401":1,"1199":1,"1207":3,"1339":2,"1438":1}}],["seriloghostextensions",{"3":{"401":3,"980":1,"1199":2,"1203":1,"1207":2,"1208":1,"1311":1,"1333":2,"1339":3,"1442":2}}],["serilogloggerprovider",{"2":{"401":1},"3":{"401":1,"1339":1}}],["serilog",{"3":{"0":3,"395":2,"401":4,"914":1,"1213":1,"1311":1,"1323":1,"1325":1,"1333":1,"1339":6,"1379":1,"1437":1,"1438":1,"1442":2,"1445":1,"1456":1,"1467":2,"1489":1,"1490":1,"1577":1,"1660":1,"1670":2,"2043":1,"2063":1}}],["serving",{"3":{"0":2,"31":1,"62":1,"91":2,"94":1,"99":1,"104":1,"234":1,"235":2,"390":1,"421":1,"563":1,"609":1,"699":1,"733":1,"756":1,"757":4,"758":2,"765":1,"875":1,"971":1,"1124":1,"1270":1,"1278":1,"1286":3,"1300":2,"1301":1,"1307":1,"1311":10,"1312":1,"1324":3,"1335":1,"1339":4,"1350":1,"1359":7,"1369":1,"1372":1,"1379":1,"1380":1,"1395":1,"1396":8,"1415":2,"1436":5,"1455":6,"1461":1,"1467":7,"1474":1,"1476":1,"1478":1,"1577":1,"1669":1,"1708":1,"1920":1,"1923":1,"2001":1,"2036":2,"2043":1,"2124":1,"2125":1,"2147":1,"2151":1,"2156":1}}],["servicetestfixture",{"2":{"1617":1},"3":{"1617":1}}],["servicetype",{"3":{"1323":2,"1339":1,"1436":1}}],["servicelifetime",{"3":{"1436":1}}],["servicehost",{"3":{"1431":1,"1436":3}}],["serviceprovider",{"3":{"1259":6,"1286":5,"1301":2,"1315":2,"1323":2,"1324":2,"1369":3,"1380":1,"1395":2,"1396":2,"1402":1,"1427":4,"1436":2}}],["serviced",{"3":{"1571":1}}],["servicedatabaseltr",{"2":{"1474":1,"2034":1},"3":{"1474":1,"2034":1}}],["servicedatabasenames",{"2":{"1473":1},"3":{"1467":1,"1473":1}}],["servicedatabases",{"3":{"1436":1}}],["servicedatasources",{"3":{"1328":1,"1339":2}}],["servicediscovery",{"2":{"1213":1},"3":{"1213":2,"1324":1}}],["servicedescriptor",{"2":{"122":1},"3":{"122":1,"1286":2,"1309":1,"1311":2,"1312":1,"1323":7,"1359":1,"1369":1,"1380":2,"1396":2,"1415":2,"1427":1,"1436":2}}],["servicedefaultsretrytests",{"3":{"1199":1,"1207":2,"1438":2}}],["servicedefaults",{"2":{"61":1,"1439":1,"1442":1},"3":{"0":1,"61":1,"1192":2,"1202":1,"1203":1,"1325":1,"1339":3,"1436":1,"1439":1,"1442":3,"1496":2,"1591":1,"2156":1}}],["servicemodel",{"3":{"1415":1}}],["servicemodels",{"3":{"1199":2,"1207":1,"1436":6,"1487":1,"1489":1}}],["servicemocks",{"3":{"1199":4,"1207":3}}],["servicecollectiondescriptorextensions",{"2":{"1309":1,"1350":1,"1380":1,"1415":1},"3":{"1309":1,"1311":1,"1350":1,"1380":1,"1396":1,"1415":1}}],["servicecollection",{"3":{"572":1,"1262":1,"1436":9}}],["servicecontractinterfaceof",{"3":{"1436":1}}],["servicecontractimplementations",{"3":{"1436":2}}],["servicecontractimplementationsarenotpublic",{"2":{"1312":1},"3":{"1312":1,"1436":1}}],["servicecontractpuritytests",{"3":{"1199":2,"1207":4,"1312":1,"1436":16,"1489":3,"1577":1,"1591":1}}],["servicecontractpuritytestsbase",{"2":{"51":1,"1585":1,"1663":1},"3":{"51":1,"54":1,"1199":3,"1207":2,"1312":5,"1350":1,"1415":1,"1436":13,"1577":1,"1585":1,"1663":1}}],["servicecontractattributefullname",{"3":{"1312":1,"1436":1}}],["servicecontractattribute",{"2":{"1349":1,"1801":1},"3":{"1199":1,"1203":1,"1207":2,"1312":9,"1349":1,"1350":3,"1396":4,"1415":3,"1436":6,"1577":1,"1801":1}}],["servicecontractsdonotdependonserviceinternals",{"2":{"1929":1},"3":{"54":1,"1312":1,"1436":4,"1929":1}}],["servicecontracts",{"3":{"54":1,"1436":3}}],["servicecontract",{"2":{"51":1,"54":1,"1025":1,"1030":1,"1663":1,"1929":1,"1930":1},"3":{"51":1,"54":1,"1025":1,"1030":1,"1202":1,"1203":1,"1309":2,"1312":5,"1349":1,"1350":2,"1359":1,"1396":3,"1404":1,"1415":2,"1436":11,"1577":1,"1585":1,"1586":1,"1662":1,"1663":1,"1929":2,"1930":1}}],["servicefilter",{"2":{"320":1,"1996":1},"3":{"318":2,"320":1,"324":1,"1300":1,"1396":3,"1994":1,"1995":2,"1996":1}}],["servicefilterattribute",{"2":{"157":1,"343":1,"1908":1},"3":{"157":1,"341":1,"343":1,"1311":4,"1908":1}}],["serviceinternalnamespaces",{"3":{"1436":1}}],["serviceinternals",{"3":{"54":1,"1436":3}}],["serviceinforesponse",{"3":{"448":1,"1199":2,"1203":1,"1207":1,"1311":6,"1380":1,"1496":1,"2131":1}}],["serviceinfo",{"2":{"449":1,"450":1,"2132":1},"3":{"448":2,"449":1,"450":1,"572":1,"1212":1,"1311":2,"1376":1,"1380":3,"1436":5,"1488":2,"1496":1,"1497":1,"1591":1,"2055":1,"2131":1,"2132":2}}],["serviceinfov2response",{"2":{"448":1,"2131":1},"3":{"448":1,"1199":2,"1203":1,"1207":1,"1311":6,"1380":1,"1496":1,"2131":1}}],["serviceinfoversioningcontracttestsbase",{"2":{"448":1,"455":1,"572":1,"1428":1,"1488":1,"2055":1,"2131":1,"2133":1},"3":{"0":1,"448":4,"455":1,"572":3,"1199":2,"1207":2,"1311":2,"1428":1,"1436":7,"1488":2,"2055":1,"2131":4,"2133":1}}],["serviceinfocontroller",{"2":{"448":1,"1376":1,"2131":1},"3":{"448":8,"1199":1,"1203":1,"1207":2,"1311":3,"1371":2,"1376":4,"1380":6,"1436":3,"1489":1,"1496":1,"1526":1,"1535":1,"1591":2,"1600":2,"2131":5}}],["serviceinfocontrollerbase",{"2":{"446":1,"448":1,"449":1,"1371":1,"1376":1,"2131":1,"2133":1},"3":{"0":1,"446":1,"448":8,"449":1,"1199":2,"1203":1,"1207":4,"1212":1,"1311":24,"1371":1,"1376":1,"1380":8,"1436":1,"1496":1,"2131":7,"2133":1}}],["servicenames",{"3":{"827":1,"1339":3}}],["servicename",{"0":{"2019":1},"2":{"53":1,"2009":1,"2019":1,"2028":1,"2030":1},"3":{"53":1,"1309":5,"1311":2,"1312":7,"1316":1,"1323":1,"1339":5,"1376":1,"1378":2,"1380":7,"1396":4,"1415":5,"1441":4,"1467":1,"1656":1,"2009":1,"2019":2,"2028":1,"2030":1}}],["serviceuri",{"2":{"440":1},"3":{"0":1,"440":1,"1323":7,"1467":2,"2126":2}}],["servicebusadministrationclient",{"3":{"1323":2}}],["servicebusclient",{"3":{"1323":2}}],["servicebusroundtripsmoketests",{"3":{"1199":1,"1207":2,"1436":2,"1438":1}}],["servicebusemulatorimagetag",{"3":{"1339":1}}],["servicebusemulatorimage",{"3":{"1339":1}}],["servicebusemulatorimageregistry",{"3":{"1339":1}}],["servicebusemulatorbrokertests",{"3":{"1199":1,"1207":2,"1339":3,"1438":2}}],["servicebusemulatorcollection",{"3":{"1199":2,"1207":1}}],["servicebusemulator",{"2":{"1203":1,"1437":1,"1438":1,"1486":1,"1487":1,"1490":2},"3":{"1199":3,"1203":1,"1206":1,"1207":9,"1436":4,"1437":1,"1438":2,"1446":1,"1486":1,"1487":1,"1490":2,"1496":1,"1599":1}}],["servicebusemulatorfixture",{"3":{"1199":3,"1207":3,"1436":2,"1438":2}}],["servicebusemulatorfixturebasetests",{"3":{"1199":1,"1207":4,"1436":1,"1438":1,"1487":1}}],["servicebusemulatorfixturebase",{"2":{"145":1,"638":1,"640":1,"1077":1,"1671":1},"3":{"145":1,"150":3,"638":1,"640":2,"642":1,"1074":1,"1077":1,"1199":4,"1207":2,"1323":2,"1436":4,"1438":2,"1488":1,"1496":1,"1671":1}}],["servicebusemulatorresource",{"2":{"640":1,"1444":2},"3":{"640":1,"1199":3,"1203":1,"1207":2,"1327":5,"1339":6,"1441":2,"1444":4,"1451":1,"1496":1}}],["servicebusemulatorsupporttests",{"3":{"1199":1,"1207":2,"1323":2,"1438":2}}],["servicebusemulatorsupport",{"2":{"638":1,"640":1,"1320":1},"3":{"150":3,"638":1,"640":2,"1074":1,"1199":2,"1203":1,"1207":2,"1320":3,"1323":10,"1496":1}}],["servicebus",{"2":{"145":1,"147":1,"149":1,"150":1,"152":1,"810":1,"1074":1,"1075":2,"1077":1,"1078":1},"3":{"0":2,"145":4,"147":2,"149":1,"150":2,"152":1,"624":2,"626":1,"640":4,"810":1,"1074":1,"1075":2,"1077":1,"1078":1,"1213":1,"1323":2,"1339":1,"1436":5,"1438":1,"1441":1,"1444":1,"1455":1,"1460":1,"1489":1,"1490":1,"1526":2,"1600":1}}],["serviceexceptionhelper",{"3":{"0":1,"109":1,"265":1}}],["services",{"0":{"1354":1,"1509":1,"1663":1},"1":{"56":1,"57":1,"58":1,"59":1,"60":1,"61":1,"62":1,"63":1,"64":1},"2":{"92":1,"95":1,"96":1,"104":1,"121":1,"180":1,"265":1,"300":1,"412":1,"454":2,"846":1,"954":3,"979":8,"980":1,"981":1,"1004":1,"1207":2,"1247":2,"1249":1,"1269":1,"1307":1,"1309":1,"1310":1,"1369":1,"1377":1,"1395":1,"1396":3,"1416":2,"1417":6,"1441":1,"1447":1,"1455":1,"1486":1,"1491":1,"1653":2,"1684":1,"1702":1,"1728":1,"1810":1,"1882":1,"1935":1,"2009":1,"2019":1,"2022":1,"2026":1,"2094":1,"2097":1},"3":{"0":14,"20":1,"24":1,"30":1,"31":4,"46":4,"49":3,"53":2,"55":2,"56":1,"59":1,"60":2,"61":1,"62":1,"63":2,"73":1,"92":1,"93":3,"94":2,"95":8,"96":5,"97":1,"98":5,"99":1,"102":1,"103":1,"104":1,"105":1,"107":1,"109":1,"117":2,"121":1,"142":1,"157":2,"175":2,"180":1,"181":1,"186":2,"195":4,"203":1,"235":6,"241":2,"242":1,"243":14,"245":9,"247":1,"248":1,"254":2,"258":2,"259":5,"260":4,"261":2,"264":1,"265":4,"281":1,"283":2,"295":7,"300":2,"303":1,"310":1,"312":1,"313":1,"318":1,"330":5,"331":1,"333":4,"336":1,"337":3,"341":1,"349":2,"353":1,"387":2,"388":1,"390":5,"391":2,"392":3,"396":1,"400":1,"401":2,"412":1,"413":4,"416":2,"418":1,"420":1,"423":1,"426":2,"427":1,"428":5,"429":1,"430":2,"434":1,"436":1,"447":1,"448":5,"449":1,"451":1,"452":2,"454":4,"459":3,"461":1,"482":1,"486":2,"487":1,"489":1,"490":1,"491":2,"492":2,"493":1,"505":1,"507":12,"508":1,"509":2,"511":1,"512":1,"513":3,"514":3,"518":1,"536":1,"538":2,"539":1,"541":1,"545":1,"549":1,"552":1,"556":1,"558":1,"572":4,"574":1,"577":1,"578":1,"582":1,"583":9,"584":1,"585":7,"586":1,"600":2,"602":1,"640":2,"642":1,"644":1,"649":2,"657":2,"665":2,"668":1,"669":1,"674":7,"682":3,"690":2,"698":1,"707":1,"715":2,"723":1,"725":2,"727":1,"733":3,"736":1,"740":1,"741":1,"744":1,"749":6,"751":1,"773":1,"774":3,"775":1,"790":1,"810":1,"826":2,"827":5,"831":1,"833":1,"837":1,"838":2,"846":2,"854":2,"868":3,"869":2,"870":2,"873":3,"876":8,"877":3,"881":27,"897":2,"905":1,"913":2,"914":1,"916":1,"930":2,"931":1,"950":1,"953":1,"954":8,"979":10,"980":5,"981":1,"983":1,"998":4,"999":2,"1004":4,"1006":1,"1025":1,"1026":2,"1028":1,"1029":1,"1050":4,"1059":5,"1069":2,"1082":1,"1086":1,"1090":1,"1092":1,"1095":1,"1100":1,"1103":1,"1104":1,"1116":1,"1140":1,"1161":1,"1178":1,"1183":2,"1184":3,"1192":7,"1199":17,"1201":1,"1202":4,"1203":293,"1206":1,"1207":1067,"1208":61,"1212":5,"1213":2,"1227":1,"1229":3,"1237":2,"1241":4,"1242":2,"1243":5,"1244":1,"1246":2,"1247":92,"1248":1,"1249":1,"1252":1,"1253":1,"1256":3,"1257":1,"1259":10,"1262":3,"1269":1,"1270":12,"1271":1,"1275":3,"1276":1,"1278":1,"1281":1,"1285":1,"1286":34,"1287":1,"1300":51,"1301":22,"1304":1,"1305":1,"1307":9,"1309":55,"1310":32,"1311":78,"1312":36,"1314":3,"1316":2,"1320":1,"1323":44,"1324":368,"1325":6,"1326":2,"1328":3,"1331":6,"1332":11,"1333":1,"1334":4,"1335":5,"1336":1,"1337":1,"1338":1,"1339":77,"1340":1,"1341":1,"1345":1,"1349":1,"1350":18,"1351":1,"1352":1,"1353":1,"1354":1,"1355":2,"1359":95,"1360":1,"1368":1,"1369":9,"1377":1,"1378":7,"1379":3,"1380":52,"1381":3,"1382":25,"1385":1,"1386":7,"1387":12,"1388":8,"1389":3,"1390":3,"1391":3,"1392":2,"1393":2,"1395":209,"1396":239,"1401":7,"1402":32,"1404":2,"1406":3,"1409":1,"1413":1,"1414":2,"1415":82,"1416":36,"1417":365,"1421":1,"1427":6,"1428":1,"1429":1,"1431":1,"1432":4,"1434":1,"1435":3,"1436":77,"1437":7,"1438":49,"1440":1,"1441":12,"1442":2,"1443":5,"1444":2,"1445":3,"1446":3,"1447":7,"1448":1,"1449":2,"1455":5,"1456":4,"1460":1,"1461":1,"1464":10,"1467":32,"1469":1,"1473":1,"1474":1,"1477":1,"1486":1,"1487":6,"1488":5,"1489":2,"1490":4,"1491":2,"1496":30,"1498":2,"1508":1,"1509":2,"1523":1,"1526":9,"1531":2,"1534":2,"1535":3,"1538":1,"1541":1,"1542":2,"1544":4,"1547":1,"1548":1,"1549":1,"1550":1,"1555":1,"1556":2,"1570":1,"1577":9,"1582":3,"1586":2,"1588":1,"1589":2,"1591":13,"1596":5,"1599":1,"1600":1,"1611":2,"1613":1,"1619":1,"1629":1,"1638":1,"1641":1,"1651":1,"1652":1,"1653":5,"1656":5,"1663":1,"1667":1,"1669":2,"1677":1,"1678":1,"1684":1,"1702":2,"1708":1,"1724":1,"1728":1,"1761":1,"1765":3,"1770":2,"1790":2,"1792":2,"1808":2,"1810":1,"1824":1,"1835":1,"1848":1,"1850":1,"1852":2,"1864":1,"1878":1,"1882":1,"1883":3,"1887":1,"1893":1,"1894":2,"1895":2,"1897":1,"1898":1,"1899":1,"1908":3,"1916":1,"1920":1,"1923":1,"1926":1,"1935":1,"1948":2,"1970":1,"2004":1,"2008":1,"2009":2,"2010":1,"2015":1,"2019":1,"2022":1,"2023":1,"2024":2,"2025":1,"2026":1,"2028":1,"2055":3,"2067":1,"2082":2,"2083":3,"2086":1,"2094":2,"2097":1,"2110":1,"2111":1,"2113":1,"2117":1,"2118":2,"2119":1,"2121":3,"2123":1,"2127":1,"2130":2,"2131":2,"2136":1,"2154":1,"2155":1,"2156":3,"2164":1,"2168":1,"2177":1,"2202":1,"2208":1,"2229":1,"2230":1,"2232":2}}],["service",{"0":{"95":1,"1225":1,"1244":1,"1327":1,"1329":1,"1346":1,"1379":1,"1400":1,"1402":1,"1413":1,"1442":1,"1443":1,"1473":1,"1510":1,"1656":1,"1946":1,"2021":1,"2035":1},"1":{"28":1,"29":1,"30":1,"31":1,"32":1,"33":1,"34":1,"35":1,"44":1,"45":1,"46":1,"47":1,"48":1,"49":1,"50":1,"51":1,"52":1,"53":1,"54":1,"55":1,"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"294":1,"295":1,"296":1,"746":1,"747":1,"748":1,"749":1,"750":1,"751":1,"752":1,"753":1,"771":1,"772":1,"773":1,"774":1,"775":1,"776":1,"777":1,"778":1,"1238":1,"1239":1,"1240":1,"1241":1,"1242":1,"1243":1,"1244":1,"1245":1,"1246":1,"1247":1,"1312":1,"1325":1,"1326":1,"1327":1,"1328":1,"1329":1,"1330":1,"1331":1,"1332":1,"1333":1,"1334":1,"1335":1,"1336":1,"1337":1,"1338":1,"1339":1,"1370":1,"1371":1,"1372":1,"1373":1,"1374":1,"1375":1,"1376":1,"1377":1,"1378":1,"1379":1,"1380":1,"1847":1,"1848":1,"1849":1,"1850":1,"1851":1,"1852":1,"1853":1,"1893":1,"1894":1,"1895":1,"1896":1,"1897":1,"1898":1,"1899":1,"2015":1,"2016":1,"2017":1,"2018":1,"2019":1,"2020":1,"2021":1,"2022":1,"2023":1,"2024":1,"2025":1,"2026":1,"2027":1,"2028":1},"2":{"62":1,"229":1,"493":3,"1363":1,"1370":1,"1379":1,"1404":1,"1445":4,"1469":1,"1525":1,"1613":1,"1703":1},"3":{"0":82,"10":1,"15":1,"28":1,"30":7,"31":6,"32":3,"40":1,"44":1,"46":1,"47":5,"48":1,"49":1,"50":1,"52":1,"53":3,"54":1,"55":2,"57":2,"59":14,"60":4,"61":5,"62":6,"63":3,"64":3,"67":1,"68":2,"77":2,"81":1,"91":4,"92":6,"93":6,"94":3,"95":8,"96":5,"97":3,"98":5,"99":2,"100":5,"102":2,"103":3,"105":2,"107":1,"109":3,"110":1,"111":1,"115":1,"117":9,"122":2,"126":2,"127":1,"136":2,"145":2,"146":1,"147":1,"150":7,"152":2,"153":1,"155":1,"158":2,"159":2,"165":2,"167":1,"169":1,"174":2,"175":3,"176":1,"177":3,"181":1,"186":1,"191":2,"195":23,"196":2,"197":2,"200":5,"201":4,"202":1,"203":1,"216":1,"217":1,"219":1,"220":1,"224":1,"229":4,"234":1,"235":6,"236":1,"237":1,"241":4,"243":14,"245":13,"249":1,"255":7,"257":1,"258":1,"259":3,"260":5,"279":1,"280":1,"281":1,"283":3,"285":1,"288":1,"290":2,"291":7,"292":3,"293":2,"294":2,"295":8,"296":1,"300":1,"302":2,"313":1,"329":1,"330":1,"343":1,"349":2,"350":3,"352":1,"361":1,"380":1,"381":1,"382":1,"386":3,"387":4,"388":1,"389":2,"390":10,"391":3,"392":3,"395":3,"396":1,"397":2,"398":2,"399":1,"400":3,"401":6,"403":1,"407":1,"412":1,"413":2,"415":1,"418":2,"420":1,"426":3,"428":3,"429":2,"430":1,"433":1,"435":1,"446":2,"447":2,"448":6,"449":1,"450":1,"451":1,"452":8,"453":1,"454":1,"458":3,"459":3,"461":3,"471":1,"473":1,"493":4,"497":1,"498":1,"499":2,"500":1,"506":1,"513":3,"517":1,"518":1,"521":2,"524":1,"535":1,"536":3,"539":1,"541":1,"544":1,"545":1,"547":1,"548":1,"556":1,"558":2,"563":1,"564":1,"568":3,"572":5,"573":1,"574":2,"582":3,"583":18,"584":3,"585":5,"586":1,"587":1,"597":2,"598":3,"599":8,"600":1,"601":1,"602":8,"605":2,"609":2,"612":1,"624":1,"625":2,"626":9,"628":1,"633":1,"638":7,"639":2,"640":20,"641":4,"642":8,"643":10,"644":3,"645":1,"657":1,"659":1,"664":3,"665":5,"667":1,"668":2,"669":1,"674":5,"675":1,"690":3,"691":1,"692":4,"693":1,"696":1,"697":1,"699":1,"702":2,"706":3,"707":1,"708":1,"709":1,"711":1,"715":2,"725":4,"726":1,"727":1,"731":2,"732":1,"733":6,"741":1,"744":2,"746":1,"749":11,"750":3,"751":3,"756":1,"757":3,"758":4,"759":1,"762":2,"764":1,"766":2,"768":3,"771":1,"773":6,"774":21,"775":2,"776":5,"777":1,"778":3,"783":1,"786":1,"789":1,"790":1,"791":1,"792":1,"793":3,"794":1,"799":1,"809":1,"810":3,"815":2,"818":1,"819":4,"820":1,"821":2,"822":1,"823":1,"825":2,"826":10,"827":8,"828":2,"829":2,"834":2,"836":1,"837":2,"838":3,"839":1,"842":1,"846":1,"848":1,"854":4,"857":1,"860":1,"868":3,"869":12,"870":3,"872":2,"873":3,"875":1,"876":8,"877":2,"881":1,"883":2,"896":1,"897":2,"905":1,"906":1,"907":1,"913":4,"914":10,"915":3,"918":1,"922":1,"930":1,"931":2,"933":1,"935":1,"939":1,"950":1,"952":1,"954":4,"956":1,"960":1,"980":2,"987":3,"990":1,"992":1,"994":1,"995":1,"996":1,"998":3,"999":3,"1000":1,"1004":1,"1011":2,"1012":2,"1013":1,"1016":2,"1017":3,"1018":8,"1021":1,"1024":1,"1026":1,"1027":2,"1028":2,"1029":1,"1030":2,"1034":2,"1043":1,"1058":1,"1059":5,"1061":1,"1063":1,"1066":2,"1067":7,"1068":1,"1069":1,"1070":1,"1073":1,"1074":3,"1075":8,"1076":1,"1083":1,"1085":1,"1090":1,"1093":3,"1094":3,"1095":1,"1100":1,"1107":1,"1108":1,"1116":4,"1117":1,"1118":1,"1119":1,"1124":1,"1127":1,"1135":1,"1140":1,"1150":1,"1155":2,"1175":2,"1176":1,"1185":1,"1187":2,"1191":2,"1192":15,"1198":2,"1199":12,"1202":10,"1203":22,"1206":4,"1207":36,"1208":4,"1212":28,"1213":6,"1218":1,"1225":2,"1229":1,"1237":8,"1238":2,"1239":1,"1241":1,"1242":1,"1243":1,"1244":2,"1245":3,"1246":1,"1247":16,"1248":2,"1249":1,"1251":1,"1252":2,"1253":1,"1254":1,"1257":2,"1258":2,"1259":55,"1262":3,"1263":1,"1265":2,"1269":2,"1270":23,"1271":2,"1272":1,"1273":1,"1275":3,"1276":2,"1277":2,"1280":1,"1286":78,"1287":1,"1288":6,"1289":1,"1290":5,"1292":1,"1294":1,"1297":1,"1300":68,"1301":34,"1302":1,"1303":2,"1304":2,"1306":1,"1307":9,"1308":1,"1309":77,"1310":9,"1311":149,"1312":60,"1313":3,"1314":3,"1315":2,"1316":2,"1318":3,"1320":3,"1321":1,"1323":100,"1324":111,"1325":12,"1326":10,"1327":4,"1328":8,"1329":2,"1330":1,"1331":6,"1332":13,"1333":7,"1334":3,"1335":5,"1336":2,"1337":5,"1338":2,"1339":193,"1340":2,"1341":4,"1344":1,"1346":3,"1347":1,"1348":2,"1349":3,"1350":52,"1354":4,"1355":1,"1356":1,"1359":97,"1361":1,"1363":2,"1366":2,"1368":2,"1369":13,"1370":5,"1373":1,"1377":1,"1378":7,"1379":5,"1380":107,"1381":1,"1382":2,"1383":1,"1385":1,"1386":1,"1388":2,"1389":2,"1390":2,"1391":3,"1392":1,"1395":167,"1396":232,"1398":1,"1400":4,"1401":2,"1402":49,"1403":2,"1404":2,"1406":7,"1409":6,"1410":1,"1412":2,"1413":6,"1414":1,"1415":134,"1416":9,"1417":44,"1425":1,"1427":8,"1428":1,"1429":3,"1430":3,"1431":4,"1432":2,"1435":1,"1436":181,"1437":7,"1438":49,"1439":4,"1440":2,"1441":43,"1442":14,"1443":7,"1444":8,"1445":12,"1446":12,"1447":14,"1449":6,"1450":1,"1451":1,"1452":1,"1455":32,"1456":11,"1459":1,"1460":15,"1461":1,"1462":1,"1463":1,"1464":10,"1465":1,"1467":91,"1469":9,"1471":3,"1473":16,"1474":6,"1475":3,"1476":7,"1477":4,"1478":2,"1481":1,"1482":4,"1483":1,"1484":1,"1486":7,"1487":13,"1488":10,"1489":12,"1490":24,"1491":2,"1492":12,"1493":1,"1496":34,"1497":1,"1498":3,"1500":1,"1509":3,"1510":1,"1523":1,"1524":2,"1525":3,"1526":46,"1531":6,"1532":8,"1534":14,"1535":11,"1544":5,"1545":2,"1546":1,"1547":2,"1548":2,"1550":1,"1553":1,"1556":1,"1566":2,"1568":2,"1572":4,"1575":3,"1576":3,"1577":8,"1581":1,"1582":1,"1583":1,"1585":2,"1586":1,"1588":1,"1589":4,"1590":4,"1591":46,"1595":4,"1596":13,"1597":6,"1599":5,"1600":6,"1611":11,"1612":3,"1613":2,"1614":1,"1615":1,"1616":1,"1617":1,"1618":1,"1619":2,"1626":2,"1630":3,"1631":6,"1632":6,"1635":2,"1636":1,"1637":2,"1638":4,"1639":1,"1640":4,"1641":2,"1646":2,"1647":2,"1650":1,"1651":2,"1652":1,"1653":2,"1654":3,"1656":12,"1660":5,"1662":2,"1663":9,"1664":1,"1665":2,"1666":3,"1667":1,"1668":1,"1670":8,"1671":4,"1675":2,"1676":1,"1677":2,"1678":1,"1680":1,"1686":1,"1687":1,"1697":1,"1699":1,"1702":1,"1703":2,"1707":2,"1709":2,"1715":1,"1718":2,"1721":1,"1724":3,"1727":1,"1749":1,"1750":1,"1752":3,"1761":2,"1763":2,"1765":3,"1769":2,"1770":1,"1772":1,"1775":2,"1778":1,"1779":3,"1781":1,"1783":5,"1786":2,"1788":4,"1790":10,"1792":4,"1793":4,"1799":1,"1803":1,"1805":1,"1808":2,"1810":1,"1817":1,"1818":1,"1823":1,"1824":2,"1825":1,"1826":2,"1827":1,"1839":3,"1841":4,"1842":3,"1843":2,"1845":2,"1846":1,"1847":3,"1848":3,"1852":3,"1853":4,"1855":2,"1863":1,"1864":1,"1865":1,"1875":2,"1882":1,"1883":1,"1884":3,"1886":3,"1887":2,"1888":1,"1892":3,"1893":7,"1894":2,"1895":3,"1896":3,"1897":2,"1898":1,"1899":3,"1903":1,"1916":2,"1919":1,"1920":1,"1921":1,"1922":7,"1923":5,"1924":3,"1927":3,"1928":1,"1929":2,"1940":1,"1944":1,"1946":4,"1948":4,"1949":1,"1950":1,"1951":1,"1968":1,"1970":2,"1974":1,"1975":1,"1978":3,"1980":1,"1988":2,"1994":1,"1999":1,"2000":4,"2001":3,"2002":2,"2003":2,"2004":4,"2006":5,"2008":7,"2009":4,"2010":2,"2012":1,"2013":3,"2014":6,"2015":2,"2018":4,"2019":1,"2020":2,"2021":3,"2022":3,"2023":4,"2024":11,"2025":4,"2026":1,"2027":4,"2028":4,"2029":1,"2030":4,"2033":2,"2034":3,"2035":6,"2036":5,"2037":4,"2038":3,"2043":1,"2049":1,"2053":2,"2055":4,"2060":1,"2063":2,"2066":2,"2067":3,"2074":1,"2076":1,"2080":1,"2082":2,"2085":1,"2087":1,"2107":1,"2111":5,"2113":4,"2115":1,"2117":1,"2118":1,"2119":1,"2123":1,"2127":4,"2128":1,"2130":3,"2131":7,"2132":1,"2135":1,"2138":2,"2145":1,"2151":1,"2154":1,"2155":1,"2156":5,"2160":1,"2162":1,"2164":3,"2166":2,"2167":2,"2168":1,"2175":2,"2179":3,"2180":1,"2193":1,"2194":1,"2201":1,"2202":3,"2207":2,"2208":1,"2209":2,"2213":1,"2214":2,"2219":2,"2221":2,"2226":1,"2227":3,"2228":1,"2230":8,"2232":11,"2243":2}}],["servefromcacheasync",{"3":{"1311":1}}],["serve",{"3":{"0":2,"53":1,"55":1,"91":2,"92":2,"97":2,"98":2,"100":1,"101":1,"213":1,"234":1,"235":1,"236":1,"237":1,"243":1,"264":1,"265":1,"266":1,"388":1,"441":1,"451":1,"506":1,"556":1,"571":1,"733":1,"741":1,"751":1,"774":1,"819":1,"825":1,"829":1,"945":1,"1043":1,"1191":1,"1200":1,"1237":1,"1239":1,"1246":1,"1247":4,"1259":1,"1267":1,"1270":4,"1276":1,"1278":1,"1286":17,"1300":4,"1301":2,"1309":1,"1310":2,"1311":8,"1312":1,"1317":1,"1323":2,"1324":9,"1332":2,"1337":1,"1339":8,"1350":1,"1357":1,"1359":12,"1362":1,"1369":3,"1380":8,"1395":6,"1396":13,"1402":1,"1415":2,"1416":2,"1417":3,"1436":3,"1441":1,"1443":3,"1467":2,"1475":1,"1476":3,"1488":1,"1534":1,"1631":1,"1634":1,"1667":1,"1708":1,"1821":1,"1849":1,"1899":1,"1920":1,"1922":1,"1946":1,"2014":1,"2025":3,"2127":1,"2128":1,"2147":1,"2153":1}}],["served",{"3":{"0":7,"37":1,"42":1,"164":1,"170":2,"175":1,"176":1,"177":1,"233":1,"235":2,"242":1,"243":2,"341":2,"388":1,"391":1,"418":4,"420":1,"423":2,"424":2,"426":1,"428":1,"429":1,"433":1,"447":1,"450":1,"461":1,"573":1,"577":1,"707":1,"733":1,"735":1,"740":1,"749":1,"761":1,"826":2,"827":1,"905":1,"947":1,"1099":1,"1116":1,"1124":1,"1185":1,"1212":1,"1221":1,"1246":1,"1247":1,"1263":1,"1264":1,"1267":2,"1270":5,"1286":5,"1288":1,"1297":1,"1300":4,"1301":1,"1306":2,"1309":6,"1311":9,"1323":3,"1324":14,"1339":1,"1347":1,"1350":6,"1358":1,"1359":5,"1379":3,"1380":5,"1395":7,"1396":8,"1400":1,"1402":1,"1406":1,"1415":5,"1416":2,"1427":1,"1436":3,"1438":2,"1446":2,"1447":2,"1453":2,"1454":2,"1455":19,"1456":2,"1457":1,"1458":2,"1459":3,"1466":1,"1467":3,"1473":1,"1481":2,"1488":1,"1526":2,"1591":3,"1631":1,"1640":1,"1641":1,"1651":1,"1708":1,"1748":1,"1765":1,"1821":1,"1825":1,"1849":1,"1917":1,"1920":1,"1922":3,"1926":1,"1936":1,"2007":1,"2024":2,"2055":1,"2075":1,"2125":1,"2130":1,"2133":1,"2140":1,"2150":1,"2202":1,"2220":1,"2232":1}}],["serves",{"3":{"0":4,"63":1,"91":2,"93":1,"95":1,"98":3,"102":2,"110":1,"113":2,"170":1,"213":1,"216":2,"229":1,"265":1,"291":1,"391":1,"418":1,"448":2,"509":1,"536":1,"546":1,"571":1,"574":1,"577":1,"584":1,"633":1,"650":1,"665":1,"676":1,"697":1,"698":2,"725":1,"756":1,"758":1,"767":1,"832":1,"869":1,"877":1,"881":1,"882":1,"889":1,"926":2,"947":1,"954":1,"972":1,"1034":1,"1059":3,"1067":2,"1118":1,"1150":1,"1184":1,"1212":2,"1237":1,"1247":4,"1249":1,"1259":3,"1263":1,"1270":5,"1277":1,"1281":1,"1286":15,"1287":1,"1300":4,"1309":6,"1311":19,"1312":1,"1323":2,"1324":20,"1325":1,"1331":1,"1332":3,"1339":9,"1350":2,"1356":1,"1359":9,"1362":2,"1366":1,"1369":4,"1371":1,"1376":1,"1378":1,"1380":3,"1391":1,"1393":1,"1395":9,"1396":14,"1400":1,"1402":2,"1407":2,"1408":1,"1409":2,"1413":1,"1415":6,"1416":2,"1417":1,"1431":2,"1433":1,"1436":22,"1438":1,"1441":2,"1444":1,"1447":3,"1448":1,"1449":1,"1453":1,"1455":1,"1456":1,"1457":1,"1467":2,"1475":1,"1481":1,"1491":1,"1513":1,"1591":1,"1596":1,"1627":2,"1649":1,"1666":1,"1668":1,"1720":1,"1750":1,"1760":1,"1761":1,"1790":1,"1851":2,"1895":1,"1922":1,"1923":1,"1929":1,"1989":1,"1999":1,"2017":1,"2021":1,"2025":4,"2073":1,"2077":1,"2121":1,"2126":1,"2131":1,"2149":1,"2150":1,"2152":2,"2163":1,"2201":1,"2232":2}}],["serverless",{"3":{"1568":1,"1678":1}}],["server=true",{"3":{"1456":1,"1534":1}}],["serververdict",{"3":{"1436":1}}],["serveresponseasync",{"3":{"1311":1}}],["servercallcontext",{"3":{"1309":2,"1380":2,"1396":2,"1415":1,"1438":1}}],["servertokenstorageservicetests",{"3":{"1199":1,"1207":3,"1324":2,"1438":1}}],["servertokenstorageservice",{"2":{"486":1,"487":1,"489":1,"490":1,"491":1,"492":1,"507":1},"3":{"486":2,"487":2,"489":2,"490":2,"491":2,"492":3,"507":8,"514":1,"1199":4,"1203":1,"1207":2,"1300":2,"1324":18,"1416":1,"1417":1,"1438":1,"1487":1,"1496":2,"1590":1,"1591":2,"1600":1}}],["serverstreamingserverhandler",{"3":{"1312":1}}],["servers",{"2":{"1929":1},"3":{"91":1,"1192":1,"1202":1,"1203":1,"1301":3,"1359":1,"1370":1,"1436":1,"1437":1,"1474":1,"1929":1,"2220":1}}],["serverdata=",{"3":{"1395":1}}],["serverdata",{"2":{"2072":1,"2076":1},"3":{"0":1,"556":1,"881":1,"1324":5,"1383":1,"1395":6,"1526":1,"2072":1,"2074":1,"2076":1}}],["server",{"0":{"1378":1,"2018":1},"1":{"1121":1,"1122":1,"1123":1,"1124":1,"1125":1,"1126":1,"1127":1,"1128":1,"1129":1},"2":{"402":1,"409":1,"451":1,"452":1,"1698":1,"2159":1},"3":{"0":49,"10":2,"13":1,"45":1,"47":1,"53":2,"86":1,"91":2,"92":2,"97":2,"100":1,"103":1,"109":5,"117":1,"128":1,"158":1,"164":3,"165":2,"166":6,"170":9,"171":1,"174":2,"175":3,"176":1,"179":3,"180":1,"201":1,"206":3,"207":2,"208":2,"217":1,"228":1,"237":1,"241":1,"243":1,"258":1,"264":1,"265":7,"273":2,"330":2,"333":1,"337":1,"341":4,"342":4,"343":2,"350":1,"351":1,"363":1,"384":2,"387":1,"389":1,"397":2,"402":2,"409":1,"412":1,"413":1,"415":3,"419":1,"434":1,"436":1,"440":1,"443":1,"451":1,"452":1,"470":1,"499":1,"501":1,"506":3,"507":5,"508":2,"547":1,"549":1,"555":2,"556":5,"557":2,"558":3,"560":1,"572":1,"574":1,"609":1,"632":1,"633":5,"640":1,"644":1,"648":2,"650":1,"653":1,"664":1,"682":1,"684":1,"689":1,"690":3,"691":1,"692":1,"708":1,"719":4,"735":1,"741":1,"749":1,"766":1,"826":1,"827":3,"828":1,"830":2,"833":2,"855":2,"857":1,"860":2,"863":1,"865":2,"879":1,"880":2,"881":6,"882":3,"883":1,"884":1,"885":4,"889":2,"890":1,"892":1,"905":3,"909":1,"913":1,"914":1,"926":1,"971":1,"972":1,"974":1,"987":1,"988":1,"1004":1,"1028":1,"1033":4,"1034":18,"1035":2,"1036":3,"1044":1,"1050":2,"1052":1,"1067":1,"1069":1,"1083":1,"1085":1,"1091":1,"1116":2,"1117":1,"1121":1,"1123":3,"1124":1,"1129":1,"1132":1,"1133":1,"1134":1,"1135":1,"1144":1,"1174":2,"1175":2,"1176":1,"1182":1,"1184":9,"1185":2,"1187":2,"1192":1,"1212":12,"1213":3,"1217":1,"1219":2,"1225":1,"1227":1,"1229":7,"1236":1,"1237":3,"1239":1,"1241":5,"1242":6,"1243":2,"1245":2,"1247":24,"1259":2,"1265":2,"1270":6,"1271":3,"1273":2,"1274":1,"1277":1,"1278":1,"1279":7,"1280":5,"1281":1,"1282":3,"1284":3,"1286":141,"1287":2,"1289":1,"1290":1,"1298":5,"1300":49,"1301":17,"1306":1,"1309":30,"1310":4,"1311":39,"1312":28,"1317":1,"1320":1,"1321":1,"1323":8,"1324":253,"1326":2,"1327":2,"1332":2,"1333":3,"1334":2,"1339":43,"1342":3,"1347":1,"1350":21,"1352":1,"1355":1,"1357":1,"1358":1,"1359":54,"1360":1,"1361":1,"1369":7,"1378":2,"1379":1,"1380":13,"1381":1,"1382":1,"1383":2,"1384":3,"1385":1,"1387":1,"1388":2,"1389":1,"1390":1,"1391":1,"1392":3,"1393":1,"1395":182,"1396":170,"1399":1,"1401":1,"1402":22,"1408":1,"1410":1,"1414":3,"1415":31,"1416":21,"1417":88,"1419":1,"1427":1,"1428":1,"1429":3,"1431":1,"1432":1,"1433":1,"1436":79,"1437":8,"1438":27,"1440":1,"1441":12,"1443":10,"1446":3,"1447":2,"1451":2,"1453":8,"1455":3,"1456":2,"1457":2,"1460":2,"1467":42,"1473":3,"1474":3,"1475":5,"1476":2,"1477":1,"1478":2,"1486":4,"1487":4,"1488":7,"1489":2,"1490":15,"1492":4,"1496":5,"1510":1,"1513":1,"1526":16,"1531":1,"1532":2,"1534":3,"1535":2,"1554":1,"1556":4,"1560":2,"1561":3,"1563":1,"1564":1,"1572":3,"1577":9,"1582":3,"1583":1,"1585":1,"1586":1,"1591":9,"1595":1,"1596":2,"1597":1,"1599":8,"1600":1,"1602":1,"1611":2,"1618":1,"1626":1,"1627":2,"1630":2,"1631":5,"1632":2,"1635":1,"1638":1,"1639":3,"1640":5,"1641":5,"1647":2,"1648":2,"1653":7,"1654":1,"1656":1,"1660":2,"1664":2,"1666":1,"1667":2,"1668":2,"1669":4,"1671":2,"1673":2,"1675":1,"1677":3,"1681":1,"1682":1,"1685":1,"1687":1,"1692":1,"1693":1,"1697":1,"1698":1,"1702":3,"1709":1,"1717":2,"1718":3,"1719":1,"1720":2,"1721":2,"1723":1,"1724":2,"1725":1,"1727":2,"1731":2,"1748":1,"1749":1,"1750":1,"1752":1,"1757":1,"1759":2,"1763":3,"1765":1,"1767":1,"1773":2,"1774":1,"1775":1,"1790":2,"1805":2,"1814":1,"1815":2,"1817":2,"1840":2,"1851":1,"1853":1,"1854":3,"1855":1,"1856":4,"1857":1,"1858":1,"1859":2,"1860":2,"1861":4,"1863":1,"1867":1,"1871":2,"1872":2,"1874":1,"1876":2,"1877":1,"1883":1,"1903":1,"1906":4,"1916":1,"1921":1,"1927":1,"1928":1,"1936":1,"1945":1,"1956":1,"1966":4,"1967":2,"1968":3,"1972":7,"1975":3,"1979":2,"1980":2,"1981":1,"1984":1,"1985":2,"1988":2,"1989":1,"1998":1,"1999":1,"2000":5,"2001":1,"2002":1,"2003":1,"2004":2,"2005":1,"2007":1,"2017":1,"2018":2,"2024":1,"2025":1,"2028":2,"2033":1,"2034":1,"2036":2,"2037":1,"2043":2,"2053":1,"2055":1,"2059":3,"2069":1,"2070":3,"2071":1,"2072":3,"2074":5,"2076":1,"2077":1,"2078":2,"2080":3,"2081":2,"2082":3,"2083":2,"2085":3,"2086":4,"2089":1,"2118":2,"2119":1,"2126":1,"2158":1,"2159":1,"2167":1,"2170":1,"2192":1,"2219":1,"2220":2,"2224":1,"2230":1,"2232":5}}],["selenium",{"3":{"2219":1,"2220":1}}],["selecteventasync",{"3":{"1359":2}}],["selectedrole",{"3":{"1415":1}}],["selectedroomidchanged",{"3":{"1395":1}}],["selectedroomid",{"3":{"1395":4}}],["selectedvalue",{"3":{"1396":1}}],["selectedsessionid",{"3":{"1396":1}}],["selectedstatus",{"3":{"1395":1}}],["selectedeventname",{"3":{"1395":1}}],["selectedeventidchanged",{"3":{"1395":1}}],["selectedeventid",{"3":{"1395":4}}],["selected",{"3":{"0":3,"31":1,"166":1,"170":1,"243":1,"253":1,"447":1,"448":1,"455":1,"674":1,"689":1,"774":1,"775":1,"778":1,"827":1,"840":1,"905":1,"945":1,"1016":1,"1091":1,"1133":1,"1192":1,"1202":1,"1203":1,"1212":1,"1257":1,"1286":7,"1291":1,"1300":2,"1301":2,"1310":1,"1311":5,"1323":2,"1324":2,"1339":3,"1350":4,"1356":1,"1359":3,"1376":1,"1380":1,"1389":1,"1395":15,"1396":3,"1402":1,"1415":2,"1416":1,"1417":6,"1418":1,"1427":2,"1433":2,"1436":4,"1467":1,"1496":1,"1577":1,"1626":1,"1627":1,"1630":1,"1635":1,"1653":1,"1656":1,"1663":1,"1671":1,"1842":1,"1895":1,"1898":1,"2012":1,"2047":1,"2130":1,"2131":1,"2133":1,"2232":2}}],["selectcurrentornext",{"3":{"1350":7,"1359":1,"1395":10,"1396":1}}],["selectcleartexturl",{"3":{"1339":2}}],["selectmany",{"3":{"1286":1,"1311":1,"1324":1,"1436":1}}],["selectable",{"3":{"178":1,"1342":1,"1350":3,"1475":1,"1591":1,"1667":1,"2002":1}}],["selective",{"3":{"780":1,"784":1,"1350":2}}],["selecting",{"3":{"0":1,"359":1,"846":1,"1247":2,"1284":1,"1286":3,"1300":1,"1311":1,"1350":1,"1359":2,"1395":1,"1396":1,"1436":2,"1492":1,"1687":1,"1748":1}}],["selections",{"3":{"1395":1}}],["selectionservice",{"3":{"1395":1}}],["selection",{"0":{"1348":1,"1390":1},"1":{"637":1,"638":1,"639":1,"640":1,"641":1,"642":1,"643":1,"644":1,"645":1},"2":{"1395":1},"3":{"0":1,"27":1,"86":2,"107":1,"185":1,"199":1,"225":1,"305":1,"312":1,"359":1,"365":1,"412":1,"449":1,"591":1,"637":1,"642":2,"643":1,"774":2,"823":1,"881":1,"913":1,"917":1,"945":1,"947":2,"1017":1,"1111":1,"1202":2,"1203":14,"1207":34,"1212":2,"1229":1,"1238":1,"1243":2,"1247":2,"1257":1,"1278":1,"1286":9,"1292":1,"1300":1,"1301":1,"1311":4,"1323":2,"1324":3,"1326":1,"1339":6,"1340":1,"1347":1,"1348":1,"1350":9,"1359":9,"1369":2,"1377":1,"1380":2,"1381":1,"1382":1,"1385":9,"1390":9,"1393":1,"1395":66,"1396":4,"1405":1,"1415":2,"1417":7,"1427":3,"1429":1,"1436":2,"1437":2,"1438":8,"1446":2,"1455":1,"1460":1,"1467":1,"1469":1,"1492":1,"1493":1,"1496":1,"1524":2,"1526":1,"1531":1,"1532":1,"1564":1,"1577":1,"1621":2,"1625":1,"1627":2,"1632":2,"1640":1,"1815":1,"1960":1,"2047":1,"2142":1,"2232":1}}],["selectors",{"3":{"1278":1,"1286":1,"1310":3,"1359":18,"1416":2,"1436":4,"1526":1,"1531":1,"1565":2,"1577":1,"1640":1,"1865":1,"1868":1}}],["selectorderedcandidates",{"2":{"1255":1},"3":{"1237":1,"1255":1,"1259":1}}],["selector",{"2":{"1833":1},"3":{"0":4,"328":1,"333":2,"640":2,"692":1,"755":1,"1192":1,"1229":1,"1239":1,"1247":6,"1271":25,"1286":12,"1300":1,"1310":7,"1311":3,"1323":1,"1324":3,"1339":1,"1348":1,"1353":3,"1359":129,"1395":7,"1396":4,"1415":1,"1416":1,"1436":17,"1487":1,"1526":1,"1534":1,"1577":1,"1750":1,"1773":1,"1829":2,"1830":1,"1833":3,"1834":1,"1859":1,"2232":1}}],["select",{"2":{"1238":1,"2077":1,"2140":1,"2193":1},"3":{"0":2,"235":1,"311":1,"330":1,"402":1,"536":1,"549":2,"550":1,"618":2,"620":1,"640":1,"649":1,"1238":1,"1242":1,"1243":1,"1247":4,"1269":1,"1270":2,"1271":2,"1286":14,"1287":1,"1309":3,"1311":3,"1323":1,"1324":2,"1326":1,"1332":1,"1333":1,"1339":4,"1350":4,"1359":14,"1362":1,"1369":1,"1380":4,"1395":2,"1396":12,"1402":4,"1415":10,"1417":2,"1433":1,"1436":5,"1438":1,"1441":1,"1443":1,"1444":1,"1446":1,"1467":1,"1488":2,"1577":1,"1677":1,"1750":1,"1815":1,"1988":1,"2008":1,"2010":1,"2022":1,"2057":1,"2077":1,"2140":1,"2142":1,"2193":1}}],["selectsortkey",{"3":{"1415":3}}],["selects",{"3":{"0":1,"98":1,"201":1,"280":1,"330":1,"332":1,"447":1,"448":1,"499":1,"539":1,"592":1,"599":1,"638":1,"644":1,"757":1,"905":1,"946":1,"1091":1,"1108":1,"1112":1,"1222":1,"1229":1,"1237":1,"1240":1,"1243":2,"1247":4,"1256":1,"1259":2,"1269":2,"1284":1,"1286":6,"1288":1,"1300":3,"1311":1,"1312":2,"1319":1,"1323":2,"1327":1,"1336":1,"1339":4,"1359":5,"1392":1,"1395":1,"1396":4,"1402":2,"1416":1,"1420":1,"1427":1,"1436":11,"1438":2,"1441":1,"1455":3,"1456":1,"1467":3,"1486":1,"1526":1,"1653":1,"1669":1,"1765":1,"1790":1,"1842":1,"1902":1,"1916":1,"1924":1,"1988":1,"2020":1,"2130":1}}],["selling",{"3":{"1395":1,"2110":1}}],["sell",{"3":{"1395":1,"1782":1}}],["sells",{"3":{"689":1,"698":1,"1025":1,"1035":1,"1395":2}}],["selfreference",{"3":{"1417":3}}],["selfhealasync",{"3":{"1301":2}}],["selfhttpwarmup",{"3":{"1396":1,"1415":1}}],["selfhttpwarmuptask",{"2":{"1191":1,"1498":1},"3":{"235":2,"1191":3,"1198":2,"1199":4,"1203":2,"1207":4,"1334":1,"1339":4,"1396":9,"1413":2,"1415":14,"1496":5,"1498":3}}],["selfhttpwarmuptaskbasetests",{"3":{"1199":1,"1207":8,"1339":3,"1438":1}}],["selfhttpwarmuptaskbase",{"2":{"233":1,"235":1,"238":1,"1413":1,"1668":1},"3":{"0":1,"233":1,"235":3,"238":1,"1199":6,"1203":1,"1207":2,"1334":2,"1339":5,"1379":1,"1380":3,"1396":2,"1413":1,"1415":1,"1438":1,"1443":7,"1496":1,"1668":1}}],["selfhttpoutputcache",{"3":{"1380":1}}],["selfhttpoutputcachewarmuptasktests",{"2":{"1379":1},"3":{"1199":1,"1207":2,"1379":1,"1380":2,"1438":1}}],["selfhttpoutputcachewarmuptask",{"2":{"1379":1},"3":{"235":4,"1199":2,"1203":1,"1207":2,"1324":2,"1334":1,"1339":4,"1379":3,"1380":8,"1395":5}}],["selfonlydeletefixture",{"3":{"1199":2,"1207":1,"1436":5}}],["selfmappingupcaster",{"3":{"1199":2,"1207":1}}],["selfcheckinoutcome",{"3":{"1199":5,"1203":1,"1207":2,"1396":20}}],["self",{"1":{"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"294":1,"295":1,"296":1,"787":1,"788":1,"789":1,"790":1,"791":1,"792":1,"793":1,"794":1,"795":1,"1878":1,"1879":1,"1880":1,"1881":1,"1882":1,"1883":1,"1884":1,"1885":1,"1886":1,"1913":1,"1914":1,"1915":1,"1916":1,"1917":1,"1918":1,"1919":1,"1920":1,"1921":1,"1922":1,"1923":1,"1924":1,"1980":1,"1981":1,"1982":1,"1983":1,"1984":1,"1985":1},"3":{"0":8,"55":1,"109":1,"165":1,"195":1,"196":1,"212":3,"214":11,"215":1,"216":1,"219":10,"235":3,"236":1,"243":1,"245":1,"282":1,"288":1,"332":1,"333":1,"341":1,"359":1,"360":1,"389":1,"484":1,"486":1,"500":2,"563":1,"568":1,"684":1,"688":2,"689":1,"690":2,"691":3,"692":2,"707":1,"733":1,"757":1,"758":1,"787":1,"790":2,"791":1,"794":1,"821":1,"828":1,"846":1,"853":1,"856":1,"910":1,"964":2,"1020":1,"1050":1,"1052":1,"1100":1,"1161":1,"1192":1,"1212":2,"1235":1,"1237":3,"1247":3,"1270":4,"1271":1,"1286":4,"1288":1,"1292":1,"1300":9,"1309":5,"1310":2,"1311":10,"1312":1,"1317":1,"1323":10,"1324":25,"1328":1,"1332":3,"1336":2,"1339":26,"1350":13,"1359":13,"1373":2,"1375":2,"1378":1,"1380":10,"1388":2,"1395":9,"1396":52,"1402":8,"1406":1,"1407":1,"1411":1,"1415":14,"1416":2,"1417":1,"1431":1,"1434":1,"1436":40,"1438":3,"1441":1,"1443":3,"1455":3,"1459":1,"1460":1,"1467":3,"1469":1,"1486":2,"1492":1,"1493":1,"1496":3,"1510":1,"1526":1,"1534":1,"1552":1,"1575":2,"1577":11,"1582":1,"1586":1,"1591":2,"1600":1,"1611":1,"1613":1,"1630":2,"1632":3,"1641":2,"1665":1,"1749":1,"1764":1,"1779":3,"1792":1,"1810":1,"1845":1,"1877":1,"1878":1,"1884":2,"1912":1,"1913":1,"1923":1,"1924":1,"1979":1,"1980":1,"1990":1,"2001":1,"2002":1,"2007":1,"2009":1,"2022":1,"2029":1,"2030":1,"2075":2,"2079":1,"2127":1,"2142":1,"2146":3,"2149":5,"2150":12,"2152":1,"2169":1,"2207":1,"2208":2,"2232":1}}],["seeing",{"3":{"1247":1,"1301":3,"1311":1,"1350":1,"1359":3,"1369":1,"1381":1,"1395":1,"1396":3,"1415":2,"1436":3}}],["seekable",{"3":{"1286":1,"1323":1}}],["seeks",{"3":{"1278":1,"1286":2,"1402":1}}],["seeking",{"3":{"1228":1,"1229":1}}],["seeked",{"3":{"359":1}}],["seek",{"3":{"200":1,"361":1,"1228":1,"1229":4,"1259":3,"1278":1,"1286":10,"1311":1,"1396":6,"1438":2,"1577":1,"2144":1,"2145":1}}],["seenrequest",{"3":{"1199":2,"1207":1}}],["seen",{"3":{"100":1,"188":1,"341":2,"344":1,"527":1,"1093":1,"1229":2,"1259":2,"1270":1,"1276":1,"1286":1,"1300":4,"1301":1,"1309":2,"1311":1,"1312":1,"1323":2,"1324":4,"1350":3,"1359":9,"1380":1,"1396":3,"1402":1,"1415":1,"1416":1,"1423":1,"1427":2,"1436":2,"1467":1,"1685":1,"1708":1,"1808":1,"1851":1,"1927":1,"1964":1,"2163":1}}],["sees",{"3":{"0":5,"17":1,"39":1,"41":1,"47":1,"53":1,"78":1,"109":1,"125":2,"126":1,"160":1,"175":1,"177":1,"217":1,"224":1,"244":1,"255":1,"265":1,"318":1,"351":1,"371":1,"382":1,"384":1,"390":1,"471":1,"544":1,"564":1,"583":1,"584":1,"592":1,"609":1,"611":1,"620":1,"639":1,"675":1,"692":1,"698":1,"699":1,"700":1,"714":3,"715":1,"717":2,"725":1,"789":1,"795":1,"798":1,"821":1,"825":1,"826":1,"827":1,"828":1,"829":1,"831":1,"881":1,"926":2,"933":1,"966":1,"982":1,"988":1,"989":1,"1018":2,"1019":1,"1020":1,"1033":1,"1050":2,"1059":1,"1061":1,"1066":1,"1092":1,"1116":2,"1117":1,"1123":1,"1125":2,"1141":1,"1162":1,"1169":1,"1170":1,"1177":1,"1184":1,"1186":1,"1188":1,"1212":2,"1232":1,"1237":4,"1247":7,"1257":1,"1259":6,"1266":1,"1270":6,"1271":5,"1276":1,"1280":1,"1286":20,"1300":6,"1301":3,"1302":1,"1305":2,"1309":9,"1311":9,"1312":1,"1315":1,"1323":7,"1324":19,"1332":1,"1333":1,"1336":1,"1338":2,"1339":6,"1350":8,"1359":27,"1366":1,"1369":1,"1371":1,"1375":1,"1379":1,"1380":6,"1395":19,"1396":12,"1402":9,"1408":1,"1410":1,"1415":9,"1416":1,"1417":6,"1420":1,"1424":1,"1427":3,"1436":28,"1438":2,"1441":1,"1442":1,"1443":1,"1455":1,"1467":2,"1473":1,"1475":1,"1485":1,"1489":2,"1496":1,"1596":1,"1641":1,"1672":1,"1677":1,"1708":2,"1749":2,"1759":1,"1765":1,"1831":2,"1840":1,"1841":1,"1851":1,"1871":1,"1876":1,"1881":1,"1889":1,"1906":1,"1927":1,"1930":1,"1933":1,"1940":1,"1949":1,"1966":1,"1968":1,"1974":1,"1994":1,"1999":1,"2000":1,"2001":1,"2024":2,"2041":1,"2047":1,"2048":1,"2056":1,"2076":1,"2116":1,"2123":1,"2141":1,"2144":1,"2165":1,"2167":1,"2179":1}}],["seeddarkthemeasync",{"3":{"1436":2}}],["seeddevelopersconferenceeventasync",{"3":{"1369":1}}],["seedlive",{"3":{"1436":1}}],["seedpartnersasync",{"3":{"1369":3}}],["seedquestionsasync",{"3":{"1369":1}}],["seedcloudaiconferenceeventasync",{"3":{"1369":1}}],["seedable",{"3":{"1436":1}}],["seedactivitiesasync",{"3":{"1369":1}}],["seedaccountasync",{"3":{"1286":1}}],["seedaccount",{"3":{"1199":5,"1203":1,"1207":2,"1284":2,"1286":6,"1415":1,"1496":1}}],["seedasync",{"2":{"1315":1,"1881":1},"3":{"1286":6,"1315":2,"1323":3,"1369":3,"1380":2,"1415":4,"1881":1}}],["seedallasync",{"2":{"295":2},"3":{"295":2,"1286":1,"1315":1,"1323":2,"1369":1,"1881":1}}],["seededday",{"3":{"1395":1}}],["seededids",{"3":{"1199":2,"1207":1}}],["seeded",{"3":{"207":1,"295":2,"310":1,"499":1,"618":1,"657":1,"747":1,"749":1,"774":1,"1282":1,"1286":2,"1297":1,"1298":1,"1300":2,"1311":3,"1342":1,"1350":3,"1364":3,"1369":7,"1380":1,"1395":10,"1396":1,"1402":2,"1415":4,"1429":1,"1433":1,"1436":12,"1438":4,"1488":3,"1577":1,"1595":1,"1596":2,"1630":1,"1643":1,"1777":2,"1825":1,"1968":1,"2057":1}}],["seedermocks",{"3":{"1199":6,"1207":3}}],["seeders",{"3":{"295":2,"314":1,"583":2,"1272":1,"1276":1,"1284":1,"1286":4,"1300":1,"1311":2,"1323":4,"1777":1,"1851":1}}],["seeder",{"0":{"1377":1},"3":{"0":2,"295":7,"585":1,"698":1,"1237":2,"1246":1,"1247":1,"1253":1,"1270":1,"1286":20,"1300":1,"1311":2,"1315":1,"1323":9,"1350":3,"1359":2,"1360":1,"1364":1,"1369":9,"1380":6,"1412":1,"1415":12,"1437":1,"1438":2}}],["seeding",{"0":{"1284":1,"1364":1,"1412":1},"3":{"0":1,"295":6,"296":1,"310":4,"314":2,"470":1,"583":1,"698":1,"700":1,"946":2,"1192":1,"1202":1,"1203":7,"1207":32,"1284":6,"1286":28,"1300":1,"1311":2,"1315":1,"1323":5,"1350":1,"1359":2,"1364":2,"1369":12,"1377":2,"1380":5,"1412":3,"1415":13,"1436":2,"1438":1,"1441":2,"1444":1,"1487":1,"1500":3,"1595":1,"1596":2,"1600":1,"1618":1,"1631":1,"1638":1,"1641":1,"1662":1,"1664":2,"1777":1,"1881":1,"2013":1}}],["seed",{"0":{"1777":1},"3":{"0":2,"80":1,"146":1,"207":1,"295":6,"314":1,"369":1,"545":1,"551":1,"556":1,"562":1,"564":1,"566":1,"572":3,"583":1,"585":1,"616":1,"618":2,"620":1,"631":11,"632":1,"633":6,"634":2,"635":7,"636":2,"649":1,"651":1,"657":1,"658":1,"665":1,"666":1,"674":1,"698":1,"700":1,"782":2,"861":1,"980":1,"1004":1,"1034":1,"1036":1,"1037":1,"1190":1,"1212":1,"1229":1,"1234":1,"1237":1,"1270":1,"1284":3,"1286":18,"1300":6,"1301":1,"1311":1,"1315":1,"1324":4,"1335":1,"1336":1,"1339":5,"1364":1,"1369":5,"1380":3,"1395":1,"1396":3,"1415":3,"1436":8,"1486":1,"1488":1,"1489":2,"1492":6,"1577":1,"1596":2,"1597":1,"1600":1,"1618":1,"1619":1,"1653":1,"1661":1,"1664":1,"1707":1,"1731":3,"1751":1,"1881":2,"1970":1,"2057":1,"2077":2,"2087":1,"2101":1,"2228":2,"2232":1,"2234":1,"2239":2}}],["seedsignedincookieasync",{"3":{"1436":1}}],["seedsponsorsasync",{"3":{"1369":1}}],["seedspeakersasync",{"3":{"1369":1}}],["seedsampleeventlinksasync",{"3":{"1369":1}}],["seedsessionsasync",{"3":{"1369":2}}],["seeds",{"3":{"0":1,"207":1,"210":1,"295":1,"296":1,"459":1,"464":1,"499":1,"507":1,"583":1,"698":1,"749":2,"1229":1,"1237":1,"1241":1,"1247":1,"1278":1,"1300":2,"1301":3,"1309":2,"1311":6,"1323":2,"1324":6,"1339":1,"1347":1,"1350":6,"1359":1,"1364":2,"1369":1,"1380":1,"1395":11,"1396":1,"1402":3,"1412":1,"1415":3,"1436":7,"1441":3,"1449":1,"1492":1,"1652":1,"1664":1,"1707":1,"1777":1,"2118":1,"2123":2}}],["see",{"0":{"2008":1,"2044":1},"3":{"0":9,"15":6,"17":2,"19":2,"21":1,"22":1,"24":1,"39":1,"47":1,"57":2,"62":1,"66":3,"72":1,"76":2,"91":4,"92":2,"93":2,"95":1,"97":1,"98":1,"101":1,"110":1,"115":4,"117":1,"122":2,"130":8,"132":1,"133":1,"135":3,"136":1,"139":1,"145":1,"146":1,"147":1,"155":3,"164":2,"166":1,"173":1,"175":2,"189":1,"193":4,"194":1,"202":1,"223":2,"228":1,"233":6,"241":5,"245":1,"248":2,"259":1,"272":1,"279":1,"298":2,"301":1,"302":1,"303":1,"316":1,"346":1,"359":1,"368":1,"372":1,"376":1,"386":4,"388":1,"391":1,"395":7,"397":1,"400":2,"413":1,"418":4,"423":2,"424":1,"425":2,"428":2,"432":1,"438":1,"443":1,"446":3,"448":1,"449":1,"451":1,"463":1,"468":4,"472":1,"497":2,"499":1,"505":1,"516":1,"526":2,"528":1,"530":2,"534":2,"537":1,"543":3,"546":1,"547":1,"564":1,"570":2,"593":1,"599":1,"612":1,"617":1,"624":1,"633":1,"638":1,"655":1,"665":1,"676":1,"680":1,"696":2,"698":1,"699":1,"713":1,"718":1,"725":1,"731":1,"733":1,"734":1,"759":1,"764":1,"790":1,"791":1,"797":3,"798":1,"808":1,"809":1,"821":1,"825":1,"826":1,"827":1,"829":2,"836":1,"840":1,"844":1,"852":1,"867":2,"868":1,"869":1,"870":3,"873":1,"875":1,"879":1,"891":1,"899":1,"905":1,"920":1,"922":2,"926":3,"952":3,"957":1,"970":1,"971":1,"974":1,"986":1,"994":1,"995":1,"1016":1,"1018":1,"1019":1,"1020":1,"1028":1,"1032":1,"1033":1,"1040":1,"1048":1,"1050":1,"1057":1,"1067":1,"1068":1,"1074":1,"1101":1,"1108":2,"1114":1,"1116":3,"1122":2,"1135":1,"1140":1,"1162":1,"1191":1,"1193":1,"1212":2,"1213":4,"1214":2,"1218":1,"1223":1,"1225":1,"1228":1,"1229":7,"1230":1,"1231":1,"1232":1,"1236":1,"1237":12,"1246":1,"1247":11,"1251":2,"1253":1,"1259":17,"1263":1,"1265":3,"1267":1,"1269":1,"1270":30,"1271":7,"1281":1,"1283":1,"1286":56,"1289":2,"1291":2,"1300":40,"1301":10,"1302":1,"1306":1,"1309":27,"1310":3,"1311":47,"1312":12,"1316":1,"1319":1,"1321":1,"1323":28,"1324":55,"1339":25,"1341":1,"1342":1,"1343":1,"1348":1,"1350":28,"1357":1,"1359":103,"1367":1,"1369":12,"1373":1,"1380":31,"1389":1,"1392":1,"1395":46,"1396":66,"1399":2,"1402":35,"1409":1,"1415":21,"1416":2,"1417":12,"1423":1,"1427":26,"1428":1,"1429":1,"1431":2,"1433":1,"1436":128,"1438":4,"1441":1,"1442":1,"1443":3,"1444":2,"1447":1,"1453":2,"1454":1,"1455":6,"1459":2,"1460":1,"1463":2,"1465":2,"1467":11,"1471":1,"1474":2,"1476":2,"1486":1,"1487":3,"1488":3,"1489":1,"1492":3,"1496":8,"1497":1,"1498":1,"1500":2,"1502":1,"1526":1,"1527":1,"1530":1,"1531":1,"1545":1,"1547":1,"1551":1,"1561":1,"1562":1,"1574":2,"1576":1,"1577":1,"1581":1,"1583":2,"1584":1,"1588":2,"1591":1,"1596":1,"1597":1,"1621":1,"1627":1,"1630":7,"1631":13,"1632":8,"1634":2,"1635":3,"1637":1,"1638":4,"1639":8,"1640":6,"1641":1,"1646":1,"1648":3,"1649":2,"1650":2,"1651":7,"1652":1,"1653":3,"1654":1,"1655":1,"1656":5,"1670":1,"1677":1,"1680":1,"1689":1,"1693":1,"1701":1,"1703":3,"1707":1,"1718":1,"1719":1,"1726":1,"1727":1,"1733":1,"1749":2,"1755":1,"1757":1,"1760":1,"1764":1,"1765":2,"1768":4,"1803":1,"1804":1,"1813":1,"1816":1,"1817":1,"1819":1,"1825":1,"1832":1,"1839":1,"1840":1,"1841":1,"1852":1,"1861":1,"1877":1,"1912":2,"1915":1,"1922":2,"1924":1,"1937":1,"1957":1,"1967":1,"1979":1,"1994":1,"1997":2,"2002":2,"2010":1,"2026":1,"2028":1,"2040":1,"2049":1,"2100":1,"2106":1,"2119":1,"2129":1,"2131":1,"2133":1,"2136":1,"2140":1,"2142":1,"2146":1,"2157":1,"2160":1,"2171":1,"2172":1,"2174":1,"2209":1,"2214":1,"2216":1,"2231":1,"2232":2,"2242":1,"2244":1,"2245":2}}],["setviewportsize",{"3":{"1591":1,"1596":1}}],["setvalues",{"3":{"1286":1}}],["setvalidator",{"2":{"1830":1,"1833":1},"3":{"1271":7,"1402":4,"1415":3,"1830":1,"1833":1}}],["setvariantdiscount",{"3":{"674":1}}],["setvariantdiscounthandler",{"3":{"674":2}}],["sethostconnectionstring",{"3":{"1436":1,"1488":1}}],["setnameddatasource",{"3":{"1436":2}}],["setbearertoken",{"3":{"1429":1,"1436":2,"1488":2,"1491":1}}],["setb",{"3":{"1359":1}}],["setbrowsercultureasync",{"2":{"265":1,"1526":1,"1653":1,"2083":1},"3":{"265":1,"1311":1,"1324":4,"1526":1,"1653":1,"2083":1}}],["setenvironmentvariable",{"3":{"1436":2,"1488":1}}],["seteventquestionanswers",{"3":{"1350":1,"1359":2}}],["seteventspeakers",{"3":{"1350":1,"1359":2}}],["setevent",{"3":{"1350":7,"1359":8}}],["seterror",{"3":{"1324":1}}],["setcategory",{"3":{"1350":1,"1359":1}}],["setcategoryitems",{"3":{"1350":2,"1359":1}}],["setcookie",{"3":{"1324":1}}],["setcorrelationid",{"3":{"1311":4}}],["setconcurrencyetag",{"3":{"341":1,"922":1,"1300":1,"1311":6,"1666":1,"1874":1}}],["setx",{"3":{"1343":1}}],["setxxx",{"3":{"1168":2}}],["setdiscount",{"3":{"1767":1}}],["setdiscountonallvariants",{"2":{"1026":1,"1767":1},"3":{"1026":1,"1767":2}}],["setdescription",{"3":{"1417":1}}],["setdarkmodeasync",{"3":{"1324":4,"2083":1}}],["setinventory",{"2":{"1769":1},"3":{"1746":2,"1749":3,"1769":1}}],["setinventoryitemcommand",{"3":{"1270":1}}],["setitems",{"2":{"1170":1},"3":{"1168":2,"1170":1,"1231":1,"1237":2,"1343":1,"1350":4,"1359":2,"1402":2,"1438":1,"1496":1,"1662":1,"1810":1}}],["setobserved",{"3":{"1417":2}}],["setoptions",{"3":{"1402":4}}],["setoldestdueage",{"3":{"1286":1}}],["setoldestpendingage",{"3":{"1259":2,"2156":1}}],["setoriginalrowversion",{"2":{"341":1,"342":1,"1871":1,"1872":1,"1874":1,"1876":1,"1877":1},"3":{"0":1,"341":3,"342":1,"926":1,"1237":2,"1270":2,"1286":8,"1311":2,"1359":4,"1871":1,"1872":1,"1874":1,"1876":1,"1877":1}}],["setprincipal",{"3":{"1436":2}}],["setproperty",{"3":{"1286":2}}],["setpropertycalls",{"3":{"1286":1}}],["setproperties",{"3":{"1286":1}}],["setproductdiscount",{"3":{"674":1}}],["setproductdiscounthandler",{"3":{"674":2}}],["setpublishedasync",{"3":{"1395":1}}],["setpermissionasync",{"3":{"1436":3}}],["setpermissionsasync",{"3":{"1311":2}}],["setpermissions",{"3":{"1300":1}}],["setpendingdepth",{"3":{"1259":3,"1286":2}}],["setresponse",{"3":{"1432":1,"1436":3}}],["setrendererinfo",{"2":{"955":1,"956":1},"3":{"0":1,"954":3,"955":1,"956":1,"1432":2,"1436":3}}],["setrooms",{"3":{"1350":1,"1359":2}}],["setroom",{"3":{"1350":1,"1359":1}}],["setrowsperpageasync",{"3":{"1324":1}}],["setroleasync",{"3":{"1324":7,"1415":1}}],["setroles",{"3":{"1300":1}}],["setrolesasync",{"3":{"1300":1,"1311":2,"1324":7,"1415":2}}],["setrolepermissionsrequestvalidator",{"2":{"1297":1},"3":{"1199":1,"1203":1,"1207":1,"1297":1,"1300":5}}],["setrolepermissionsrequest",{"3":{"1199":6,"1203":1,"1207":1,"1297":1,"1300":3,"1324":2}}],["setlivepoll",{"3":{"1402":4}}],["setlockedasync",{"3":{"1300":3,"1311":1,"1415":1}}],["setloadfailed",{"2":{"111":1,"112":1},"3":{"109":1,"111":1,"112":1,"1324":1}}],["setleaderboardparticipationasync",{"3":{"1396":7}}],["setleaderboardparticipation",{"3":{"1203":1,"1207":2,"1271":1,"1286":1,"1396":4}}],["setleaderboardparticipationrequest",{"3":{"1199":5,"1203":1,"1207":2,"1396":10,"1436":1}}],["setleaderboardparticipationhandlertests",{"3":{"1199":1,"1207":3,"1396":1}}],["setleaderboardparticipationhandler",{"2":{"692":1,"693":1},"3":{"690":1,"692":1,"693":1,"1199":2,"1203":1,"1207":2,"1271":2,"1286":3,"1396":15,"1496":1}}],["setauthenticationstate",{"3":{"1436":3}}],["setactionsasync",{"3":{"1416":2}}],["setavatarasync",{"3":{"1415":2}}],["setavatarurl",{"3":{"1415":3}}],["seta",{"3":{"1359":1}}],["setapplicationname",{"3":{"665":1,"1339":1}}],["setasync",{"3":{"243":1,"733":2,"1270":2,"1300":1,"1301":17,"1311":2,"1324":2,"1416":1,"1417":12}}],["setunreadcount",{"3":{"1324":4}}],["setuserrolesrequestvalidator",{"2":{"1297":1},"3":{"1199":1,"1203":1,"1207":1,"1297":1,"1300":4}}],["setuserrolesrequest",{"3":{"1199":6,"1203":1,"1207":1,"1297":1,"1300":2,"1311":1,"1324":3,"1496":1}}],["setuser",{"3":{"954":1,"1432":1,"1436":3,"1488":1}}],["setuseravatarcommandmarkertests",{"3":{"1199":1,"1207":2}}],["setuseravatarcommandvalidator",{"3":{"1199":1,"1203":1,"1207":2,"1415":8}}],["setuseravatarcommand",{"2":{"1264":1},"3":{"1199":7,"1203":1,"1207":2,"1264":2,"1411":2,"1415":9}}],["setuseravatarhandlertests",{"3":{"1199":1,"1207":2,"1415":1}}],["setuseravatarhandler",{"2":{"1526":1,"2127":1},"3":{"444":2,"926":1,"1199":3,"1203":1,"1207":2,"1270":3,"1286":9,"1323":4,"1411":2,"1415":19,"1496":1,"1526":1,"2127":2}}],["setuseravatar",{"3":{"444":2,"926":1,"1203":3,"1207":10,"1264":1,"1270":1,"1286":1,"1411":1,"1415":8,"1436":1,"1496":2}}],["setupdefaultroles",{"3":{"1436":1}}],["setupdefaultclient",{"3":{"1436":1}}],["setuproles",{"3":{"1436":1}}],["setups",{"3":{"1369":1}}],["setuppublicroomfilter",{"3":{"1359":1}}],["setup",{"0":{"1472":1},"3":{"0":1,"61":1,"140":1,"373":1,"402":1,"448":1,"619":1,"749":1,"953":1,"954":1,"955":2,"1247":2,"1286":1,"1300":2,"1309":1,"1311":3,"1324":2,"1396":1,"1406":1,"1415":1,"1417":3,"1427":1,"1429":1,"1431":1,"1432":1,"1436":15,"1453":3,"1454":1,"1455":3,"1456":1,"1471":1,"1472":20,"1475":1,"1482":1,"1483":2,"1488":2,"1565":1,"1570":1,"1577":1,"1686":1,"1751":1,"2129":1,"2131":1,"2139":1,"2142":1,"2154":1}}],["settag",{"3":{"1427":1}}],["settable",{"3":{"382":1,"881":1,"954":1,"1259":1,"1270":1,"1286":2,"1300":1,"1311":1,"1323":1,"1324":3,"1339":1,"1359":3,"1380":1,"1396":1,"1402":2,"1436":8,"1891":1,"1949":1}}],["settling",{"3":{"831":1}}],["settleasync",{"3":{"1436":1}}],["settleselector",{"3":{"1436":2}}],["settles",{"3":{"350":1,"639":1,"904":1,"1323":4,"1339":1,"1355":1,"1359":1,"1395":1,"1398":1,"1415":2,"1433":1,"1436":4,"1488":2,"1686":1}}],["settled",{"3":{"175":1,"325":1,"732":1,"906":1,"1300":2,"1311":1,"1337":1,"1339":4,"1359":1,"1395":2,"1402":2,"1427":1,"1433":1,"1436":1,"1453":1,"1455":1,"1488":1,"1828":1,"1926":1,"2143":1}}],["settle",{"3":{"160":1,"716":1,"1004":1,"1190":1,"1247":1,"1436":6,"1484":1,"1686":1,"1924":1}}],["settextasync",{"3":{"1417":5}}],["settenant",{"2":{"698":1},"3":{"698":4,"1270":3,"1286":3,"1311":2,"1317":1,"1323":3}}],["setter",{"3":{"265":1,"341":1,"342":1,"674":1,"675":1,"698":1,"883":1,"963":1,"1168":4,"1169":2,"1170":3,"1237":1,"1247":1,"1270":1,"1274":1,"1286":2,"1311":2,"1323":4,"1324":8,"1342":4,"1343":2,"1350":14,"1359":4,"1395":1,"1396":4,"1402":3,"1405":1,"1415":3,"1417":1,"1431":1,"1436":13,"1809":1,"1844":1,"1871":1,"2072":1,"2087":1,"2164":1}}],["setters",{"3":{"0":2,"265":1,"962":1,"1167":1,"1168":3,"1212":1,"1229":1,"1231":1,"1237":4,"1242":1,"1274":1,"1278":1,"1286":2,"1309":2,"1324":2,"1350":5,"1359":3,"1396":2,"1402":3,"1436":10,"1488":1,"1489":1,"1526":2,"1535":1,"1541":1,"1591":1,"1808":2,"1810":2,"1811":1,"1812":1,"1988":1,"2090":1}}],["settokensasync",{"2":{"423":1,"426":1,"428":1,"430":1,"505":1,"512":1,"514":1},"3":{"423":1,"426":1,"428":1,"430":1,"505":1,"507":1,"511":1,"512":3,"514":1,"1324":8,"1417":2,"1436":2}}],["setting",{"3":{"0":1,"20":1,"24":2,"32":1,"73":1,"100":1,"142":1,"145":1,"178":2,"186":1,"193":1,"195":2,"200":1,"201":2,"274":1,"290":1,"291":1,"296":1,"337":1,"403":1,"418":1,"422":1,"423":1,"499":1,"599":1,"640":1,"673":1,"719":2,"740":1,"741":3,"750":1,"776":1,"819":2,"820":1,"821":1,"827":1,"828":1,"831":1,"832":1,"837":1,"838":2,"882":1,"905":1,"907":1,"922":1,"931":3,"933":1,"935":1,"1107":1,"1108":2,"1112":1,"1118":1,"1175":1,"1178":1,"1229":1,"1232":1,"1237":2,"1256":2,"1259":5,"1267":1,"1270":2,"1273":2,"1280":1,"1286":20,"1292":1,"1300":6,"1301":6,"1309":3,"1311":4,"1312":2,"1323":8,"1324":9,"1326":1,"1335":1,"1339":10,"1350":2,"1359":1,"1395":3,"1396":9,"1402":1,"1416":1,"1417":7,"1422":1,"1427":2,"1436":8,"1438":1,"1440":1,"1441":2,"1442":1,"1444":1,"1453":1,"1455":2,"1465":4,"1466":1,"1467":12,"1470":1,"1490":1,"1492":1,"1526":1,"1591":1,"1627":1,"1701":1,"1721":1,"1767":1,"1769":1,"1843":2,"1845":1,"1871":1,"1904":1,"1929":1,"2000":1,"2001":1,"2011":1,"2020":1,"2057":1,"2074":1,"2131":1,"2149":2,"2203":1,"2232":1}}],["settingstests",{"3":{"1207":6,"1323":2}}],["settings",{"0":{"1320":1},"2":{"130":1,"201":1,"819":1,"1994":2},"3":{"0":11,"22":1,"24":2,"59":1,"73":1,"74":1,"130":1,"135":3,"139":3,"178":1,"186":2,"190":1,"193":1,"200":1,"201":1,"217":1,"243":1,"281":1,"318":2,"330":1,"337":1,"403":1,"530":1,"539":1,"583":2,"586":1,"633":1,"640":1,"641":1,"649":2,"650":1,"672":2,"673":1,"674":5,"675":2,"676":2,"696":1,"701":1,"707":3,"710":1,"713":1,"715":3,"727":1,"733":1,"741":2,"742":1,"743":1,"819":2,"827":6,"829":2,"831":1,"832":2,"834":1,"838":1,"840":1,"905":2,"907":1,"909":1,"914":1,"932":1,"946":1,"954":1,"980":1,"1004":1,"1034":1,"1042":1,"1045":1,"1107":1,"1112":1,"1116":1,"1119":1,"1127":1,"1175":3,"1177":3,"1178":2,"1179":1,"1184":4,"1189":1,"1192":5,"1202":2,"1203":13,"1207":48,"1212":2,"1259":7,"1267":1,"1270":4,"1272":1,"1273":1,"1276":2,"1279":1,"1284":1,"1286":64,"1287":1,"1288":3,"1290":1,"1294":1,"1295":1,"1300":33,"1301":16,"1302":1,"1309":6,"1311":38,"1313":2,"1316":1,"1320":9,"1323":50,"1324":45,"1326":1,"1327":1,"1335":1,"1337":1,"1338":2,"1339":61,"1359":11,"1366":1,"1379":1,"1380":1,"1396":16,"1415":2,"1416":7,"1417":19,"1418":1,"1419":1,"1427":17,"1429":3,"1436":15,"1437":1,"1438":7,"1442":1,"1443":2,"1444":1,"1455":1,"1456":1,"1466":1,"1467":2,"1474":1,"1476":1,"1484":1,"1487":2,"1488":2,"1496":13,"1526":1,"1577":2,"1582":1,"1626":2,"1627":1,"1661":1,"1666":1,"1670":2,"1675":1,"1708":1,"1734":1,"1748":1,"1765":1,"1825":1,"1842":1,"1881":1,"1983":1,"1994":2,"2000":1,"2002":1,"2012":5,"2014":1,"2024":2,"2027":1,"2028":1,"2153":1,"2167":1,"2173":1,"2180":1,"2188":1,"2196":1,"2198":1,"2202":1,"2203":1,"2232":1}}],["setsharedenvironment",{"3":{"1436":1}}],["setsessioncategoryitems",{"3":{"1359":1}}],["setsessionquestionanswers",{"3":{"1359":1}}],["setsessionspeakers",{"3":{"1359":1}}],["setsession",{"3":{"1350":4,"1359":4}}],["setscrollposition",{"3":{"1324":1}}],["setspeaker",{"3":{"1350":3,"1359":1}}],["setspeakerquestionanswers",{"3":{"1350":1,"1359":2}}],["setspeakercategoryitems",{"3":{"1310":1,"1350":1,"1359":2}}],["setsproperty",{"2":{"1844":1},"3":{"1286":4,"1844":1}}],["setsvaluegeneratednever",{"3":{"1286":1}}],["setstatus",{"3":{"1395":1,"1402":1}}],["setstablenameandkey",{"3":{"1286":1}}],["setstoredpermissionsasync",{"2":{"2200":1,"2203":1},"3":{"1059":1,"1300":6,"1311":1,"1324":4,"2200":1,"2203":1}}],["setsrequiredmembers",{"3":{"1229":3}}],["sets",{"3":{"0":4,"17":1,"24":1,"31":2,"38":1,"43":1,"59":1,"72":1,"74":1,"94":1,"178":1,"189":1,"202":1,"243":1,"263":1,"265":3,"296":1,"305":1,"311":1,"341":1,"342":1,"370":1,"397":4,"399":1,"446":1,"448":2,"458":1,"499":1,"507":2,"528":1,"557":1,"562":1,"564":1,"578":1,"601":1,"611":1,"617":1,"626":2,"640":4,"650":1,"657":1,"660":1,"698":1,"702":1,"705":1,"707":1,"715":1,"733":1,"736":1,"740":2,"741":1,"755":1,"757":1,"764":2,"767":1,"810":1,"819":1,"826":1,"827":2,"854":1,"863":1,"881":2,"888":1,"905":1,"907":1,"916":1,"926":1,"931":1,"933":2,"954":1,"963":1,"979":1,"1004":2,"1018":2,"1042":1,"1083":1,"1124":1,"1134":1,"1140":1,"1160":1,"1162":1,"1184":2,"1212":2,"1213":1,"1229":2,"1231":1,"1237":2,"1239":1,"1242":1,"1247":4,"1257":1,"1259":7,"1271":6,"1274":1,"1275":1,"1286":24,"1288":1,"1290":1,"1295":1,"1300":13,"1301":5,"1309":6,"1311":34,"1312":2,"1323":15,"1324":51,"1326":1,"1327":1,"1336":1,"1339":24,"1349":1,"1350":10,"1352":1,"1359":55,"1364":1,"1369":8,"1379":1,"1380":3,"1395":23,"1396":24,"1402":8,"1412":1,"1415":15,"1416":6,"1417":10,"1427":1,"1429":1,"1430":1,"1431":1,"1432":1,"1436":33,"1441":6,"1443":6,"1444":1,"1445":1,"1447":1,"1449":1,"1453":2,"1454":2,"1455":3,"1459":2,"1464":2,"1467":3,"1475":1,"1476":2,"1484":1,"1488":3,"1489":1,"1490":1,"1491":1,"1492":1,"1496":3,"1524":1,"1526":2,"1531":1,"1535":1,"1549":1,"1560":1,"1577":1,"1591":1,"1596":2,"1611":1,"1627":1,"1630":1,"1640":1,"1641":5,"1649":1,"1668":1,"1678":1,"1708":1,"1748":1,"1749":2,"1754":1,"1765":2,"1810":1,"1811":1,"1830":1,"1861":1,"1871":1,"1898":1,"1910":1,"1933":1,"1970":1,"1993":1,"2001":1,"2008":2,"2009":1,"2010":2,"2071":1,"2077":1,"2083":2,"2126":1,"2131":2,"2138":1,"2156":1,"2159":1,"2164":1,"2167":1,"2186":1,"2193":1}}],["set",{"0":{"1235":1,"1650":1,"1678":1,"1896":1,"2075":1,"2078":1},"2":{"764":1,"1135":1,"1136":1,"1912":1,"1924":1},"3":{"0":24,"17":1,"19":2,"24":2,"25":1,"26":2,"31":2,"32":1,"33":3,"39":3,"41":1,"45":1,"68":2,"79":1,"81":1,"82":1,"90":1,"94":1,"95":2,"98":3,"109":1,"121":1,"122":1,"135":1,"139":1,"142":2,"145":5,"146":1,"147":2,"149":2,"157":1,"164":1,"166":1,"167":1,"173":1,"175":1,"177":1,"178":1,"180":1,"181":2,"186":4,"189":1,"195":2,"200":1,"202":1,"215":1,"243":2,"245":1,"248":2,"259":1,"261":1,"265":8,"273":1,"291":2,"317":1,"318":1,"330":2,"332":1,"335":4,"337":2,"349":1,"350":1,"351":1,"352":1,"370":2,"386":3,"388":1,"392":1,"397":2,"402":1,"411":1,"412":1,"415":1,"424":1,"441":1,"448":2,"452":2,"461":1,"493":1,"494":1,"498":1,"499":3,"500":1,"507":2,"509":1,"514":1,"526":2,"528":2,"532":1,"549":2,"551":1,"554":2,"556":4,"564":1,"566":1,"567":1,"571":1,"572":1,"574":1,"576":1,"578":1,"584":2,"585":2,"592":1,"595":1,"598":1,"599":4,"601":1,"602":1,"612":1,"616":1,"618":2,"619":1,"632":1,"633":2,"640":1,"641":1,"642":1,"644":1,"650":1,"652":1,"655":1,"656":1,"657":1,"659":1,"668":2,"674":2,"689":1,"690":1,"697":1,"698":2,"702":1,"703":1,"707":2,"711":1,"715":1,"725":1,"727":1,"733":4,"749":3,"757":1,"764":2,"766":3,"780":2,"790":2,"809":1,"827":2,"832":1,"833":2,"837":3,"857":1,"859":1,"861":3,"863":1,"864":1,"869":1,"873":1,"875":2,"876":1,"881":4,"884":2,"897":3,"900":2,"903":1,"905":1,"907":2,"914":1,"924":1,"926":3,"929":1,"933":1,"937":1,"938":1,"939":2,"940":3,"941":1,"942":1,"948":1,"953":3,"963":4,"964":5,"965":4,"966":3,"973":1,"974":1,"980":1,"981":1,"988":1,"996":1,"998":2,"999":1,"1006":1,"1020":1,"1033":1,"1034":1,"1036":1,"1042":2,"1043":2,"1050":1,"1058":1,"1059":4,"1060":1,"1067":3,"1079":1,"1082":1,"1090":2,"1095":1,"1107":1,"1108":1,"1116":2,"1124":3,"1133":2,"1135":1,"1136":1,"1150":1,"1163":1,"1168":1,"1175":1,"1184":1,"1196":1,"1212":6,"1214":1,"1229":7,"1234":1,"1235":1,"1237":17,"1241":7,"1242":5,"1243":3,"1247":37,"1252":1,"1254":1,"1256":1,"1259":23,"1261":1,"1264":2,"1265":2,"1266":1,"1270":21,"1271":8,"1273":3,"1274":1,"1275":2,"1276":1,"1278":1,"1279":2,"1280":1,"1283":2,"1286":112,"1287":1,"1288":1,"1290":1,"1293":2,"1296":1,"1297":2,"1298":1,"1300":94,"1301":12,"1302":1,"1304":1,"1309":20,"1310":6,"1311":58,"1312":6,"1316":1,"1317":1,"1318":2,"1320":2,"1322":2,"1323":49,"1324":108,"1325":1,"1326":2,"1328":1,"1338":1,"1339":34,"1342":1,"1343":1,"1348":1,"1349":1,"1350":37,"1353":5,"1355":1,"1357":1,"1358":2,"1359":172,"1362":2,"1364":2,"1369":10,"1377":1,"1380":7,"1384":2,"1385":1,"1387":1,"1388":1,"1390":1,"1391":1,"1395":64,"1396":73,"1401":3,"1402":40,"1405":2,"1408":2,"1409":2,"1412":2,"1413":2,"1414":1,"1415":58,"1416":4,"1417":19,"1421":1,"1422":1,"1427":9,"1430":1,"1431":2,"1432":1,"1433":1,"1436":143,"1437":1,"1438":12,"1440":1,"1441":3,"1442":1,"1443":1,"1444":1,"1445":1,"1446":1,"1450":1,"1453":3,"1454":2,"1455":8,"1456":2,"1458":1,"1459":6,"1460":3,"1461":1,"1465":2,"1467":18,"1470":3,"1472":1,"1475":2,"1477":5,"1479":1,"1480":1,"1481":1,"1487":1,"1488":3,"1489":2,"1490":2,"1492":1,"1496":9,"1504":1,"1509":1,"1525":1,"1526":8,"1534":5,"1535":1,"1560":1,"1571":1,"1575":1,"1577":8,"1591":4,"1596":4,"1599":1,"1611":1,"1613":1,"1630":8,"1631":12,"1632":3,"1633":1,"1635":1,"1637":1,"1638":3,"1639":1,"1640":2,"1641":5,"1646":1,"1648":2,"1650":3,"1651":1,"1653":1,"1655":1,"1659":1,"1662":1,"1664":1,"1666":1,"1667":3,"1668":3,"1670":1,"1671":1,"1673":1,"1674":1,"1676":1,"1677":5,"1678":1,"1679":1,"1684":1,"1686":2,"1692":1,"1693":1,"1695":2,"1699":1,"1703":1,"1724":1,"1727":2,"1735":1,"1736":1,"1737":1,"1742":2,"1746":3,"1748":3,"1749":3,"1765":3,"1767":1,"1768":3,"1780":1,"1781":1,"1802":2,"1803":1,"1808":2,"1810":2,"1820":1,"1823":1,"1828":1,"1841":1,"1844":4,"1845":1,"1850":1,"1861":1,"1863":1,"1877":1,"1895":4,"1896":2,"1898":1,"1899":3,"1909":1,"1912":2,"1916":1,"1920":1,"1922":1,"1924":1,"1932":1,"1933":1,"1955":1,"1962":3,"1963":1,"1975":4,"1977":1,"1979":1,"1983":1,"1984":1,"1985":1,"1988":3,"1990":1,"1994":2,"2002":1,"2005":1,"2008":1,"2010":2,"2012":1,"2027":2,"2029":1,"2042":1,"2045":1,"2057":1,"2065":2,"2073":1,"2078":1,"2079":2,"2080":1,"2083":3,"2085":2,"2090":1,"2098":1,"2106":1,"2107":3,"2116":1,"2119":1,"2123":1,"2127":1,"2129":1,"2147":1,"2149":2,"2153":2,"2156":5,"2157":1,"2160":2,"2164":1,"2166":2,"2174":1,"2180":1,"2186":1,"2188":1,"2196":1,"2197":1,"2199":2,"2200":1,"2214":1,"2227":1,"2232":1}}],["securely",{"3":{"1764":1}}],["secure",{"2":{"601":1,"1107":1,"1108":1,"1111":1,"1468":1},"3":{"0":1,"29":1,"215":1,"506":1,"507":1,"508":1,"601":1,"1107":1,"1108":2,"1109":1,"1111":1,"1183":1,"1298":1,"1300":5,"1311":4,"1320":2,"1323":2,"1324":1,"1339":1,"1416":2,"1417":7,"1438":1,"1455":1,"1466":1,"1467":4,"1468":1,"1469":1,"1586":1,"1641":3,"1660":1,"1669":1,"1900":1}}],["securestorage",{"2":{"509":1,"511":1,"513":1},"3":{"0":2,"507":3,"508":1,"509":2,"510":1,"511":3,"513":1,"1184":1,"1187":1,"1212":1,"1324":7,"1416":2,"1417":5,"1639":1,"1641":6,"2232":1}}],["securitytokenexception",{"3":{"1300":1}}],["securitykey",{"3":{"1300":2}}],["securityalgorithms",{"3":{"1300":3,"1436":1}}],["securityheadersextensions",{"3":{"1199":1,"1203":1,"1207":1,"1336":1,"1339":4,"1443":1}}],["securityheaderstestsbase",{"2":{"218":1},"3":{"218":2,"572":5,"1199":3,"1207":2,"1339":1,"1428":1,"1436":10,"1438":1,"1488":3,"1493":1,"1496":1,"2055":1}}],["securityheaderstests",{"2":{"2151":1},"3":{"217":1,"218":2,"572":4,"575":1,"1199":2,"1207":4,"1436":4,"1437":1,"1438":2,"1487":2,"1488":1,"1535":1,"2151":1}}],["securityheaders",{"3":{"214":2,"216":3,"217":1,"219":1,"220":1,"674":1,"980":1,"1108":1,"1207":7,"1208":2,"1336":2,"1339":28,"1443":6,"1450":1,"1496":1,"1526":1,"1577":1,"1585":1,"1586":1,"2011":1,"2149":8,"2152":3}}],["securityheadersmiddleware",{"2":{"214":1,"217":1,"2011":1,"2149":1,"2153":1},"3":{"214":1,"217":1,"1199":3,"1203":1,"1207":1,"1324":3,"1336":1,"1339":12,"1416":1,"1436":1,"1438":1,"1443":1,"1496":2,"2011":1,"2149":1,"2153":1}}],["securityheadersmiddlewaretests",{"2":{"214":1,"2151":1},"3":{"214":1,"1199":1,"1207":4,"1339":5,"1577":1,"1586":1,"2151":1}}],["securityheaderssettings",{"2":{"0":1,"214":2,"216":1,"217":1,"1339":1,"1577":1,"2149":1},"3":{"0":1,"214":2,"216":1,"217":1,"674":1,"1199":5,"1203":1,"1207":1,"1336":1,"1339":7,"1577":1,"2149":2}}],["security",{"0":{"1336":1,"1548":1,"1563":1},"1":{"211":1,"212":1,"213":1,"214":1,"215":1,"216":1,"217":1,"218":1,"219":1,"220":1,"221":1,"2147":1,"2148":1,"2149":1,"2150":1,"2151":1,"2152":1,"2153":1},"2":{"214":1,"665":1,"1335":1,"1423":1,"1674":1,"2149":1},"3":{"0":4,"23":1,"25":1,"59":1,"103":1,"177":1,"179":1,"207":2,"211":1,"213":2,"214":4,"216":2,"217":3,"218":1,"219":7,"220":1,"221":1,"228":1,"246":1,"283":1,"284":1,"311":1,"312":1,"313":2,"333":1,"344":1,"353":1,"358":2,"362":2,"369":2,"373":1,"375":1,"376":3,"395":1,"403":1,"419":1,"440":1,"509":1,"563":1,"572":4,"573":1,"574":2,"575":1,"602":1,"607":1,"612":2,"617":1,"665":1,"674":1,"676":1,"684":1,"718":1,"751":2,"752":1,"773":1,"778":1,"793":1,"810":1,"825":1,"826":2,"827":2,"830":1,"837":1,"854":1,"873":1,"946":2,"948":1,"950":2,"980":1,"1011":1,"1075":1,"1077":1,"1108":3,"1115":1,"1117":3,"1186":1,"1189":1,"1192":2,"1202":2,"1203":13,"1207":44,"1208":2,"1212":2,"1216":2,"1228":1,"1229":2,"1236":1,"1237":5,"1239":1,"1241":3,"1247":14,"1253":1,"1259":1,"1263":1,"1270":13,"1271":6,"1273":1,"1276":1,"1283":1,"1284":1,"1285":1,"1286":36,"1287":2,"1290":2,"1292":1,"1297":1,"1300":118,"1301":6,"1304":1,"1305":1,"1306":1,"1307":1,"1308":1,"1309":24,"1311":39,"1312":3,"1318":1,"1320":2,"1322":1,"1323":39,"1324":89,"1325":1,"1335":1,"1336":5,"1337":1,"1338":2,"1339":69,"1347":2,"1348":1,"1350":12,"1353":2,"1355":2,"1357":1,"1359":82,"1365":1,"1366":1,"1369":3,"1372":1,"1373":1,"1380":20,"1384":1,"1388":1,"1389":1,"1391":2,"1395":49,"1396":65,"1402":39,"1403":1,"1406":1,"1407":1,"1409":1,"1411":1,"1415":60,"1416":10,"1417":50,"1419":1,"1421":1,"1422":1,"1423":1,"1427":9,"1429":1,"1436":60,"1437":4,"1438":4,"1441":1,"1442":1,"1443":5,"1450":1,"1454":2,"1455":3,"1456":1,"1459":1,"1461":1,"1465":6,"1466":1,"1467":8,"1469":1,"1472":2,"1474":1,"1475":1,"1477":1,"1483":1,"1487":2,"1488":5,"1492":2,"1493":1,"1496":5,"1499":2,"1525":1,"1526":7,"1530":2,"1531":1,"1532":1,"1534":1,"1535":2,"1548":1,"1552":1,"1554":2,"1562":1,"1563":2,"1575":3,"1576":1,"1577":9,"1581":2,"1582":2,"1583":1,"1585":3,"1586":6,"1588":2,"1590":1,"1591":3,"1595":3,"1596":3,"1597":1,"1600":1,"1627":1,"1639":1,"1641":1,"1660":1,"1667":1,"1670":2,"1671":1,"1673":1,"1674":1,"1675":1,"1769":1,"1779":2,"1796":2,"1801":1,"1814":1,"1815":1,"1817":1,"1829":1,"1899":1,"1903":2,"1904":1,"1905":2,"1908":1,"1933":1,"1937":1,"1940":1,"1965":2,"1966":1,"1968":1,"1972":1,"1979":1,"1983":1,"1985":1,"1993":1,"1997":1,"2000":1,"2001":2,"2002":2,"2011":4,"2045":1,"2055":2,"2075":1,"2078":1,"2126":3,"2128":2,"2143":1,"2146":2,"2147":3,"2148":3,"2149":4,"2150":3,"2153":5,"2160":1,"2195":1,"2199":1,"2203":1,"2208":1,"2219":2,"2220":3,"2232":2}}],["sec",{"3":{"0":2,"25":1,"42":2,"103":1,"123":2,"179":2,"189":2,"198":1,"207":1,"209":1,"217":2,"246":3,"284":3,"333":5,"344":1,"353":2,"373":7,"384":3,"403":6,"427":2,"442":1,"602":4,"612":2,"643":3,"718":3,"747":1,"749":1,"793":4,"830":4,"831":1,"873":7,"876":1,"908":1,"920":1,"921":1,"1184":1,"1247":19,"1270":3,"1286":3,"1298":1,"1300":4,"1301":3,"1309":3,"1311":5,"1323":11,"1324":3,"1339":6,"1355":1,"1359":2,"1402":1,"1415":3,"1416":3,"1417":6,"1436":5,"1438":1,"1447":1,"1453":1,"1467":4,"1496":1,"1534":1,"1969":2,"1971":1,"1972":1,"2000":2}}],["sectionheading",{"2":{"1687":1}}],["sectioned",{"3":{"1201":1,"1203":2,"1496":40,"1497":2,"1500":1}}],["sectionname",{"2":{"728":1,"1447":1,"2000":1},"3":{"674":2,"725":1,"728":1,"1259":3,"1286":7,"1300":9,"1301":4,"1309":1,"1311":4,"1320":2,"1323":17,"1324":11,"1339":5,"1396":2,"1415":7,"1427":3,"1447":1}}],["sections",{"0":{"1209":1},"3":{"0":3,"27":3,"74":2,"104":1,"128":1,"203":1,"231":1,"256":1,"259":1,"337":1,"375":2,"393":1,"409":1,"418":1,"429":1,"463":1,"540":1,"552":2,"604":1,"607":1,"609":3,"611":2,"612":1,"613":1,"649":1,"672":2,"673":1,"674":8,"675":1,"676":2,"677":1,"725":8,"726":1,"727":3,"728":1,"731":1,"794":1,"832":1,"833":2,"877":1,"909":1,"927":1,"960":1,"976":1,"980":1,"995":1,"1021":1,"1068":1,"1095":1,"1124":1,"1125":1,"1128":1,"1191":1,"1194":1,"1210":1,"1211":1,"1212":1,"1213":1,"1229":1,"1279":1,"1286":4,"1300":3,"1310":1,"1311":4,"1313":4,"1316":1,"1320":3,"1322":1,"1323":8,"1324":11,"1339":2,"1340":1,"1343":2,"1359":1,"1395":7,"1401":1,"1402":2,"1403":1,"1404":1,"1406":1,"1415":9,"1427":1,"1436":4,"1437":1,"1438":2,"1451":1,"1452":1,"1467":1,"1476":4,"1481":1,"1487":1,"1489":1,"1496":77,"1497":4,"1498":2,"1500":1,"1523":1,"1526":1,"1577":1,"1626":1,"2012":4,"2046":1,"2060":1,"2067":1,"2232":1}}],["section",{"0":{"136":1,"1193":1},"2":{"1418":1,"1423":1,"1426":1},"3":{"0":21,"59":1,"72":1,"76":1,"94":1,"95":1,"96":1,"97":1,"109":1,"130":3,"135":1,"137":2,"139":2,"145":1,"159":1,"161":1,"166":3,"173":1,"178":3,"180":3,"214":2,"215":1,"216":1,"219":1,"225":2,"243":1,"255":1,"281":1,"286":1,"298":1,"300":1,"350":1,"375":1,"395":4,"397":1,"400":1,"407":1,"408":1,"409":1,"418":1,"423":3,"426":1,"428":2,"429":2,"432":1,"434":2,"440":1,"475":1,"476":1,"477":1,"482":1,"484":1,"490":1,"511":1,"526":1,"543":1,"583":1,"584":1,"585":4,"586":1,"590":1,"607":6,"608":1,"609":6,"610":1,"611":2,"617":1,"633":1,"640":1,"651":1,"652":1,"673":1,"674":13,"675":1,"676":5,"678":1,"690":2,"696":1,"698":1,"702":1,"707":3,"715":2,"723":1,"725":8,"726":2,"727":5,"728":2,"729":1,"731":1,"733":2,"798":1,"800":1,"819":1,"821":1,"827":5,"829":1,"832":3,"836":1,"837":1,"838":2,"839":1,"841":1,"845":1,"849":1,"854":1,"860":2,"865":1,"896":1,"901":1,"915":3,"931":1,"933":1,"971":1,"972":1,"976":1,"996":5,"997":1,"998":2,"1003":1,"1006":1,"1007":2,"1010":5,"1012":2,"1014":2,"1017":1,"1018":1,"1022":1,"1034":2,"1058":1,"1059":1,"1062":1,"1073":1,"1078":1,"1079":2,"1089":1,"1090":2,"1091":2,"1094":2,"1095":1,"1096":1,"1124":3,"1184":1,"1188":1,"1193":3,"1194":1,"1212":8,"1214":1,"1216":1,"1229":1,"1237":2,"1247":6,"1259":37,"1260":1,"1264":1,"1270":6,"1271":1,"1273":2,"1275":1,"1276":1,"1277":1,"1278":1,"1279":5,"1280":1,"1286":43,"1289":1,"1294":1,"1297":1,"1299":3,"1300":28,"1301":18,"1307":1,"1309":16,"1310":3,"1311":21,"1312":2,"1316":1,"1320":12,"1322":1,"1323":89,"1324":65,"1326":2,"1332":2,"1336":2,"1337":1,"1338":4,"1339":38,"1350":6,"1352":1,"1355":1,"1359":4,"1366":1,"1368":1,"1369":11,"1389":1,"1392":1,"1395":31,"1396":38,"1402":8,"1410":2,"1414":3,"1415":31,"1416":3,"1417":2,"1418":4,"1419":4,"1420":1,"1421":3,"1422":4,"1423":2,"1424":1,"1425":1,"1426":1,"1427":13,"1428":1,"1431":2,"1435":1,"1436":36,"1438":10,"1440":1,"1441":1,"1442":2,"1443":2,"1444":3,"1447":2,"1452":1,"1460":2,"1463":1,"1465":1,"1467":15,"1470":1,"1471":1,"1472":1,"1473":1,"1474":2,"1476":5,"1478":1,"1481":4,"1485":1,"1486":3,"1487":8,"1488":8,"1489":2,"1490":1,"1494":2,"1496":51,"1497":3,"1498":1,"1500":2,"1526":3,"1532":1,"1575":2,"1577":2,"1589":1,"1591":3,"1596":2,"1600":1,"1602":2,"1625":1,"1627":2,"1630":4,"1631":9,"1632":7,"1635":1,"1638":3,"1639":4,"1640":1,"1641":4,"1651":1,"1652":1,"1656":4,"1666":1,"1667":1,"1668":1,"1669":1,"1670":2,"1672":3,"1673":1,"1675":1,"1683":1,"1684":2,"1708":3,"1722":1,"1726":1,"1727":1,"1729":1,"1749":1,"1750":1,"1751":4,"1758":2,"1759":1,"1760":1,"1765":6,"1770":1,"1772":3,"1773":1,"1776":2,"1849":1,"1882":1,"1895":1,"1902":1,"1908":1,"1909":1,"1916":1,"1919":1,"1921":1,"1933":1,"1936":1,"1940":1,"1975":1,"1992":1,"2000":1,"2001":2,"2002":1,"2011":2,"2012":4,"2013":1,"2014":2,"2024":1,"2027":1,"2041":1,"2046":3,"2049":3,"2055":1,"2060":1,"2063":1,"2067":7,"2094":1,"2114":1,"2126":2,"2127":1,"2136":1,"2138":1,"2149":1,"2150":1,"2152":1,"2158":2,"2167":1,"2173":2,"2180":1,"2186":1,"2195":1,"2232":6}}],["secretbytelength",{"3":{"1300":2}}],["secretkey",{"2":{"792":1,"2166":1},"3":{"790":1,"792":1,"1770":1,"2166":1}}],["secretmissing",{"3":{"790":1}}],["secretforkey",{"3":{"31":1,"674":1,"1300":3}}],["secretref",{"3":{"0":1,"599":2,"640":1,"1212":1,"1467":11,"1496":1,"1577":1}}],["secrets",{"0":{"1335":1},"2":{"598":1,"601":1,"869":1,"875":1,"1339":1,"1455":1},"3":{"0":5,"349":1,"351":1,"359":1,"598":2,"599":6,"600":2,"601":4,"603":1,"758":1,"766":1,"869":2,"875":1,"1012":1,"1059":1,"1066":2,"1212":2,"1286":3,"1300":4,"1311":3,"1323":2,"1324":2,"1326":1,"1335":2,"1339":12,"1415":6,"1417":7,"1419":1,"1427":1,"1436":1,"1441":1,"1442":1,"1445":1,"1454":1,"1455":1,"1456":1,"1459":1,"1460":1,"1462":2,"1466":2,"1467":24,"1468":5,"1469":2,"1470":1,"1472":3,"1474":5,"1475":1,"1477":1,"1484":1,"1526":2,"1535":1,"1548":2,"1554":3,"1563":3,"1570":1,"1572":2,"1577":3,"1586":1,"1591":2,"1663":1,"1670":1,"1770":1,"1945":1,"1974":1,"1975":1,"1979":1,"2068":1,"2128":1,"2142":2,"2146":1,"2198":1,"2230":1}}],["secret",{"1":{"596":1,"597":1,"598":1,"599":1,"600":1,"601":1,"602":1,"603":1,"604":1,"605":1,"1893":1,"1894":1,"1895":1,"1896":1,"1897":1,"1898":1,"1899":1},"2":{"1458":1,"1468":1,"1470":1,"1472":1},"3":{"0":10,"30":4,"31":3,"33":1,"53":1,"72":1,"179":1,"180":1,"217":1,"350":1,"441":1,"500":1,"529":2,"530":2,"535":1,"596":1,"597":2,"598":3,"599":12,"601":5,"604":1,"605":3,"640":2,"644":1,"664":2,"670":1,"673":1,"678":1,"788":1,"789":2,"790":11,"791":3,"792":9,"793":4,"794":6,"827":10,"869":3,"871":2,"872":1,"1017":4,"1020":2,"1212":2,"1288":2,"1293":9,"1300":35,"1311":8,"1312":2,"1322":3,"1323":18,"1324":13,"1335":2,"1337":3,"1339":33,"1396":1,"1406":1,"1416":2,"1417":1,"1419":1,"1423":1,"1427":3,"1441":1,"1442":2,"1445":4,"1447":2,"1450":2,"1454":3,"1455":13,"1458":4,"1461":1,"1466":4,"1467":35,"1468":3,"1469":2,"1470":4,"1472":3,"1473":1,"1481":2,"1486":2,"1492":1,"1526":1,"1554":1,"1585":1,"1586":1,"1589":2,"1591":2,"1595":1,"1596":3,"1597":1,"1667":2,"1670":4,"1770":1,"1786":1,"1790":1,"1892":1,"1893":7,"1894":1,"1895":3,"1897":1,"1898":1,"1899":2,"1901":1,"1902":2,"1904":1,"1905":3,"1973":1,"1975":2,"1980":2,"1981":1,"1985":1,"2009":1,"2012":1,"2021":2,"2024":3,"2028":1,"2111":1,"2125":1,"2127":1,"2142":1,"2149":1,"2161":1,"2166":7,"2167":1,"2169":1,"2175":1,"2198":2,"2208":1,"2230":1,"2232":3}}],["secondfactor",{"3":{"1300":1}}],["secondentityfullname",{"3":{"1286":2}}],["secondjob",{"3":{"1199":2,"1207":1}}],["secondtestrequestvalidator",{"3":{"1199":2,"1207":1}}],["secondarysynced",{"3":{"1359":1}}],["secondarycontrasttext",{"3":{"1324":1}}],["secondarylight",{"3":{"1324":1}}],["secondarydark",{"3":{"1324":1}}],["secondary",{"2":{"2075":1},"3":{"618":1,"1191":1,"1286":1,"1300":1,"1324":12,"1350":1,"1358":1,"1359":1,"1380":1,"1395":1,"1492":1,"1526":5,"1535":1,"1577":1,"1591":1,"1612":1,"1638":1,"2075":2}}],["seconds",{"3":{"0":1,"19":2,"22":2,"23":1,"24":1,"72":2,"73":2,"121":1,"178":1,"233":1,"235":1,"243":3,"245":1,"254":1,"257":1,"379":2,"387":1,"390":3,"392":2,"460":2,"461":2,"463":1,"565":1,"707":1,"709":1,"733":1,"735":1,"818":1,"819":2,"821":2,"827":1,"830":1,"831":1,"833":1,"837":2,"838":2,"869":1,"914":1,"1028":2,"1029":1,"1044":1,"1061":1,"1089":1,"1091":1,"1101":1,"1124":2,"1184":1,"1256":1,"1259":6,"1263":1,"1264":3,"1270":4,"1273":1,"1286":4,"1293":1,"1300":11,"1301":7,"1309":4,"1311":6,"1312":4,"1317":1,"1320":1,"1323":8,"1324":15,"1328":1,"1337":1,"1339":9,"1359":2,"1378":1,"1380":4,"1390":1,"1395":2,"1396":3,"1402":5,"1415":1,"1416":2,"1417":5,"1419":1,"1425":1,"1427":2,"1430":1,"1433":1,"1436":8,"1441":3,"1443":2,"1447":3,"1451":1,"1455":2,"1456":1,"1467":11,"1490":1,"1492":1,"1616":1,"1639":1,"1668":1,"1748":1,"1765":1,"1841":1,"1843":1,"1906":1,"1916":1,"1918":1,"1922":1,"1923":1,"1924":1,"1948":1,"2010":1,"2024":1,"2051":1,"2066":1,"2134":1,"2156":4,"2157":1,"2158":1,"2173":1,"2177":1,"2192":1,"2198":1,"2202":1}}],["second",{"0":{"95":1,"1293":1,"1722":1,"2065":1,"2073":1,"2137":1},"1":{"816":1,"817":1,"818":1,"819":1,"820":1,"821":1,"822":1,"823":1,"1942":1,"1943":1,"1944":1,"1945":1,"1946":1,"1947":1,"1948":1,"1949":1,"1950":1,"2194":1,"2195":1,"2196":1,"2197":1,"2198":1,"2199":1,"2200":1,"2201":1,"2202":1,"2203":1},"3":{"0":34,"15":1,"24":2,"27":1,"31":1,"38":1,"55":1,"70":3,"71":4,"74":1,"91":1,"92":1,"95":1,"100":2,"113":1,"115":1,"122":1,"126":3,"130":1,"136":1,"137":1,"139":2,"150":1,"152":1,"156":1,"157":1,"165":1,"173":1,"178":3,"189":1,"197":1,"199":1,"200":1,"201":2,"214":1,"219":1,"231":1,"233":1,"234":1,"235":4,"237":2,"243":3,"252":1,"254":2,"258":1,"265":2,"279":1,"280":1,"285":1,"292":1,"296":1,"305":1,"324":1,"325":1,"340":1,"341":3,"342":1,"350":2,"351":1,"352":2,"358":1,"374":1,"379":2,"386":1,"387":2,"388":1,"390":1,"391":1,"395":3,"397":1,"404":3,"409":1,"411":1,"412":1,"415":1,"428":1,"436":1,"448":2,"450":2,"457":1,"459":7,"460":1,"461":2,"463":2,"465":5,"466":1,"477":1,"501":2,"505":1,"507":1,"508":1,"513":1,"517":1,"519":1,"524":1,"528":1,"530":1,"531":1,"534":1,"536":1,"538":2,"539":1,"540":1,"543":1,"546":1,"549":1,"551":1,"556":3,"558":1,"564":2,"599":1,"600":1,"609":1,"617":1,"619":1,"624":1,"628":2,"629":1,"631":1,"633":3,"635":2,"638":1,"640":4,"642":1,"649":1,"655":1,"656":1,"657":1,"660":1,"665":2,"673":1,"674":1,"675":2,"676":2,"681":1,"689":1,"690":2,"691":4,"692":1,"697":1,"698":2,"699":1,"700":1,"702":1,"707":3,"709":1,"715":1,"720":1,"728":1,"732":1,"733":4,"734":2,"735":2,"736":2,"737":2,"740":1,"741":3,"742":1,"749":2,"757":3,"758":1,"759":2,"761":1,"784":1,"789":1,"790":1,"792":1,"794":1,"810":1,"811":3,"813":1,"816":1,"818":1,"819":4,"820":1,"821":2,"825":1,"827":8,"828":1,"829":5,"831":1,"832":2,"834":1,"837":2,"849":1,"853":1,"854":2,"855":1,"859":1,"861":1,"881":2,"882":2,"889":1,"891":1,"897":1,"904":1,"905":1,"906":1,"907":1,"914":1,"916":1,"917":1,"926":2,"931":1,"934":1,"946":1,"947":2,"974":1,"988":3,"990":1,"994":1,"995":2,"996":6,"999":1,"1003":1,"1018":1,"1019":1,"1020":2,"1025":1,"1028":1,"1042":2,"1043":2,"1045":1,"1050":1,"1051":2,"1058":1,"1059":1,"1060":1,"1074":1,"1075":1,"1079":1,"1089":2,"1090":2,"1091":1,"1092":1,"1093":1,"1107":1,"1116":3,"1128":1,"1134":1,"1142":1,"1154":2,"1178":1,"1184":1,"1185":1,"1212":5,"1221":1,"1229":4,"1231":1,"1234":1,"1236":2,"1237":17,"1243":1,"1244":2,"1247":12,"1248":1,"1249":2,"1251":1,"1252":1,"1254":2,"1255":1,"1256":1,"1257":1,"1259":15,"1262":1,"1263":3,"1264":3,"1265":3,"1267":1,"1269":1,"1270":36,"1271":4,"1273":3,"1274":3,"1275":5,"1277":1,"1278":3,"1283":1,"1286":72,"1287":2,"1289":3,"1293":5,"1297":1,"1298":2,"1300":67,"1301":17,"1302":1,"1305":1,"1307":4,"1309":26,"1310":4,"1311":31,"1312":10,"1315":1,"1317":2,"1318":1,"1322":1,"1323":38,"1324":78,"1326":1,"1327":2,"1328":1,"1330":9,"1331":1,"1332":1,"1333":2,"1334":2,"1335":1,"1337":1,"1338":7,"1339":55,"1347":1,"1350":22,"1352":1,"1355":2,"1356":1,"1358":2,"1359":103,"1365":1,"1366":1,"1368":1,"1369":11,"1372":2,"1375":2,"1376":1,"1378":2,"1379":2,"1380":16,"1385":1,"1388":1,"1389":2,"1390":3,"1392":2,"1393":2,"1395":43,"1396":79,"1397":1,"1398":1,"1399":1,"1402":15,"1406":1,"1407":1,"1413":1,"1415":39,"1416":13,"1417":17,"1424":1,"1427":4,"1429":1,"1430":3,"1431":1,"1433":2,"1436":96,"1438":10,"1440":1,"1441":5,"1442":5,"1443":2,"1445":1,"1446":2,"1447":4,"1453":2,"1454":1,"1455":10,"1456":1,"1458":1,"1459":2,"1460":1,"1465":5,"1466":1,"1467":17,"1469":1,"1473":1,"1474":1,"1476":3,"1478":2,"1481":1,"1488":3,"1489":2,"1490":1,"1492":5,"1496":8,"1509":1,"1526":3,"1534":2,"1545":1,"1547":1,"1577":4,"1585":1,"1591":3,"1630":2,"1631":3,"1632":1,"1639":2,"1651":1,"1654":1,"1666":1,"1667":1,"1668":2,"1672":1,"1684":2,"1686":2,"1689":1,"1701":2,"1719":1,"1721":2,"1722":2,"1724":1,"1727":4,"1729":2,"1731":4,"1748":1,"1755":1,"1765":2,"1767":1,"1776":1,"1779":2,"1781":1,"1790":1,"1813":1,"1819":1,"1821":3,"1825":1,"1829":1,"1839":1,"1840":4,"1841":1,"1843":2,"1846":1,"1848":1,"1851":1,"1852":1,"1853":1,"1863":1,"1869":1,"1871":1,"1875":1,"1876":2,"1877":2,"1898":3,"1899":1,"1900":2,"1902":1,"1907":1,"1909":3,"1911":1,"1915":1,"1916":2,"1918":2,"1920":2,"1921":1,"1922":2,"1923":2,"1925":1,"1933":3,"1934":1,"1937":1,"1942":6,"1943":1,"1944":1,"1945":2,"1946":1,"1947":1,"1948":2,"1950":3,"1959":1,"1960":1,"1962":1,"1964":1,"1967":3,"1970":3,"1973":1,"1974":1,"1977":1,"1978":1,"1979":3,"1980":1,"1986":1,"1995":1,"1997":1,"2000":2,"2006":3,"2007":1,"2011":1,"2012":2,"2013":1,"2024":4,"2029":1,"2030":4,"2032":4,"2034":1,"2037":1,"2045":2,"2048":1,"2049":1,"2052":1,"2057":1,"2063":1,"2065":1,"2069":2,"2070":1,"2072":1,"2073":1,"2081":1,"2082":1,"2089":1,"2097":1,"2100":1,"2113":1,"2114":1,"2121":1,"2126":1,"2129":4,"2131":1,"2132":2,"2133":1,"2134":1,"2136":1,"2137":1,"2140":2,"2146":1,"2147":1,"2156":2,"2157":3,"2158":1,"2161":1,"2164":2,"2166":1,"2167":2,"2168":2,"2169":1,"2172":1,"2174":1,"2178":1,"2179":1,"2180":1,"2181":2,"2184":1,"2187":1,"2188":1,"2192":1,"2193":2,"2194":4,"2195":1,"2196":2,"2197":3,"2198":1,"2203":2,"2208":3,"2211":1,"2232":5,"2239":1}}],["school",{"3":{"1395":1}}],["schedulable",{"3":{"0":1,"1044":1,"1323":1,"1534":1,"1575":1,"1582":1,"1585":1,"2186":1,"2192":1}}],["scheduling",{"0":{"2185":1},"3":{"0":1,"230":1,"255":1,"444":1,"517":1,"529":1,"536":1,"539":3,"706":1,"707":6,"708":1,"709":2,"710":1,"995":1,"1036":1,"1041":1,"1042":2,"1044":2,"1191":1,"1203":6,"1207":44,"1259":1,"1264":1,"1275":1,"1284":1,"1286":14,"1317":3,"1318":1,"1320":1,"1323":27,"1350":1,"1357":1,"1359":16,"1380":3,"1396":1,"1410":1,"1411":1,"1415":12,"1417":11,"1436":1,"1438":6,"1442":1,"1487":1,"1496":1,"1527":1,"1532":1,"1574":1,"1578":1,"1594":1,"1630":2,"1637":1,"1638":1,"1923":1,"1950":2,"2165":2,"2189":1,"2192":2}}],["scheduleavatarblobdeletionasync",{"3":{"1415":5}}],["scheduleasync",{"2":{"1099":1,"1103":1,"2185":1,"2193":1},"3":{"1042":1,"1099":1,"1103":1,"1286":5,"1323":5,"1359":2,"1376":1,"1380":1,"1396":3,"1415":4,"1417":5,"2185":1,"2189":1,"2193":1}}],["scheduleuploadedblobcleanupasync",{"3":{"1415":1}}],["scheduleerror",{"3":{"1395":7}}],["schedulecachekey",{"3":{"1395":1}}],["scheduleblobcleanupasync",{"3":{"1359":1}}],["scheduleorphancleanupasync",{"3":{"1359":2}}],["schedule",{"2":{"1456":1},"3":{"0":1,"19":1,"23":1,"224":1,"230":1,"404":1,"444":2,"500":1,"511":1,"536":1,"539":2,"591":1,"617":1,"625":1,"627":1,"628":1,"641":1,"689":1,"706":1,"707":7,"708":3,"709":3,"727":1,"860":1,"900":1,"954":2,"1018":2,"1019":1,"1040":2,"1045":1,"1099":1,"1100":1,"1103":1,"1116":2,"1118":1,"1247":1,"1259":2,"1260":1,"1269":1,"1270":5,"1286":3,"1317":1,"1323":25,"1324":3,"1340":1,"1343":1,"1349":1,"1350":12,"1351":1,"1352":1,"1356":1,"1357":1,"1359":31,"1369":6,"1376":2,"1380":11,"1385":1,"1391":5,"1395":59,"1396":21,"1411":1,"1415":3,"1416":2,"1417":12,"1427":1,"1436":2,"1438":8,"1443":1,"1455":1,"1456":7,"1458":1,"1459":1,"1475":1,"1490":3,"1492":2,"1524":1,"1526":1,"1534":2,"1599":3,"1626":3,"1629":4,"1630":2,"1631":4,"1632":6,"1636":1,"1637":4,"1638":1,"1639":1,"1640":4,"1841":1,"1895":1,"1948":6,"2006":1,"2047":1,"2127":1,"2156":1,"2186":2,"2190":1,"2192":1,"2232":2}}],["schedules",{"0":{"1254":1},"3":{"0":2,"440":1,"444":3,"459":1,"466":1,"536":1,"640":1,"644":1,"707":1,"810":1,"814":1,"819":1,"849":1,"897":1,"901":1,"907":1,"1016":1,"1018":1,"1020":1,"1042":1,"1044":1,"1116":1,"1118":1,"1212":1,"1286":1,"1300":1,"1301":1,"1323":7,"1339":1,"1347":1,"1357":1,"1359":9,"1367":1,"1369":2,"1376":1,"1380":4,"1390":1,"1395":4,"1396":5,"1408":1,"1410":1,"1411":2,"1415":14,"1417":5,"1436":2,"1631":1,"1639":1,"1640":1,"1843":1,"1918":1,"2165":1,"2185":1,"2189":2,"2190":1,"2192":1,"2193":2}}],["scheduledon",{"2":{"1045":1},"3":{"1042":1,"1045":1,"1286":4,"1323":2,"2186":1}}],["scheduledjobs",{"3":{"1273":1,"1286":3,"1317":1,"1323":4,"1436":1,"1438":1}}],["scheduledjobrunnertests",{"3":{"1199":1,"1207":2,"1323":2,"1438":1}}],["scheduledjobrunner",{"2":{"995":1,"1043":1},"3":{"707":4,"710":1,"995":2,"996":1,"1043":1,"1199":5,"1203":1,"1207":3,"1269":1,"1270":2,"1286":4,"1313":1,"1317":10,"1323":42,"1438":1,"1442":2,"1496":3}}],["scheduledjoboverridesettings",{"2":{"707":1,"1320":1},"3":{"707":1,"1199":3,"1203":1,"1207":1,"1270":1,"1320":1,"1323":2}}],["scheduledjobentry",{"2":{"995":1,"1036":1,"1041":1},"3":{"0":1,"707":3,"710":1,"995":1,"1041":2,"1199":7,"1203":1,"1207":2,"1212":1,"1270":1,"1273":1,"1286":4,"1317":3,"1323":8}}],["scheduledqueryalertspecs",{"2":{"609":1,"1476":1,"2158":1},"3":{"609":1,"1467":1,"1476":2,"2158":1}}],["scheduledqueryrules",{"2":{"1474":1,"2158":1},"3":{"0":1,"609":1,"1467":1,"1474":1,"1577":1,"2158":1}}],["scheduled",{"3":{"0":4,"24":1,"71":1,"166":1,"229":1,"403":1,"405":1,"440":1,"444":1,"470":1,"534":1,"536":1,"538":1,"539":1,"541":1,"607":1,"609":4,"625":1,"626":1,"709":1,"715":1,"720":1,"767":1,"808":1,"810":1,"813":2,"818":2,"827":1,"897":1,"898":1,"988":1,"1018":2,"1019":1,"1020":1,"1026":1,"1042":1,"1043":1,"1044":3,"1099":1,"1100":1,"1116":2,"1212":4,"1259":2,"1263":1,"1270":5,"1275":1,"1277":1,"1278":1,"1286":17,"1318":1,"1323":17,"1327":1,"1337":1,"1339":5,"1346":1,"1350":3,"1352":1,"1357":2,"1359":14,"1369":1,"1380":7,"1395":1,"1396":14,"1415":9,"1416":2,"1417":5,"1436":4,"1438":1,"1440":1,"1455":4,"1456":2,"1457":1,"1458":1,"1460":5,"1465":5,"1467":9,"1469":1,"1474":3,"1475":6,"1476":1,"1492":1,"1525":2,"1526":5,"1530":1,"1531":3,"1532":2,"1534":6,"1568":1,"1575":3,"1577":1,"1578":1,"1581":2,"1582":1,"1583":2,"1584":1,"1585":7,"1591":3,"1599":2,"1630":3,"1665":1,"1670":1,"1678":1,"1679":1,"1710":1,"1720":1,"1823":1,"1832":1,"1839":1,"2034":1,"2158":2,"2161":1,"2165":1,"2167":2,"2168":2,"2187":1,"2191":1,"2192":4,"2193":1,"2232":2}}],["scheduleretryasync",{"3":{"1286":1}}],["schedulertableenabled",{"3":{"1286":2}}],["schedulertestharness",{"3":{"1199":3,"1207":5,"1323":2}}],["schedulertestcontext",{"3":{"1199":2,"1207":1}}],["schedulermodelgatetests",{"3":{"1191":1,"1199":1,"1207":4,"1286":2,"1323":2,"1438":2,"1496":1}}],["schedulermetricstests",{"3":{"1199":1,"1207":2,"1323":2,"1438":1}}],["schedulermetrics",{"2":{"711":1,"2159":1},"3":{"707":2,"711":1,"1199":3,"1203":1,"1207":2,"1317":5,"1323":12,"1438":1,"1496":1,"2156":1,"2159":1}}],["schedulersettings",{"2":{"707":1,"711":1},"3":{"674":1,"707":4,"711":1,"1199":7,"1203":1,"1207":3,"1270":1,"1286":3,"1313":1,"1317":2,"1320":5,"1323":17}}],["schedulers",{"3":{"0":1,"827":1,"1043":1}}],["scheduler",{"1":{"704":1,"705":1,"706":1,"707":1,"708":1,"709":1,"710":1,"711":1},"2":{"400":1,"408":1,"1317":1,"2006":1,"2013":1,"2160":1},"3":{"0":9,"170":2,"258":1,"400":2,"408":1,"536":2,"539":1,"540":1,"696":1,"704":1,"705":1,"707":9,"708":1,"709":1,"710":1,"711":1,"713":1,"715":2,"716":2,"720":3,"721":1,"897":1,"905":1,"910":2,"994":1,"996":1,"999":1,"1000":1,"1034":1,"1041":1,"1042":1,"1043":1,"1046":1,"1085":1,"1212":1,"1213":1,"1237":1,"1269":1,"1270":8,"1273":1,"1275":1,"1277":2,"1284":1,"1286":23,"1300":1,"1317":5,"1318":2,"1320":1,"1323":37,"1333":3,"1339":1,"1359":3,"1379":1,"1380":1,"1396":2,"1415":1,"1436":1,"1438":3,"1442":2,"1443":2,"1446":1,"1467":6,"1469":1,"1496":2,"1577":1,"1585":1,"1660":1,"1664":4,"1670":1,"1675":1,"1948":1,"1950":1,"2006":3,"2012":1,"2013":1,"2156":7,"2160":1,"2183":1,"2192":1,"2227":1,"2232":1}}],["schemas",{"3":{"0":1,"109":1,"656":1,"1034":1,"1035":1,"1082":1,"1085":1,"1093":1,"1286":1,"1300":2,"1422":1,"1543":1,"2174":1}}],["schemaversionrule",{"3":{"1436":1}}],["schemaversion",{"2":{"78":1,"81":1,"131":1,"256":1,"447":1,"455":1,"844":1,"845":1,"847":1,"1508":1,"1576":1,"1577":1,"1889":1,"1891":1,"1892":1,"2130":1},"3":{"0":2,"78":5,"79":1,"80":2,"81":1,"82":1,"131":1,"255":1,"256":1,"447":1,"455":1,"782":1,"844":1,"845":1,"846":1,"847":1,"1212":1,"1249":3,"1259":7,"1270":2,"1350":1,"1436":19,"1489":1,"1508":1,"1526":3,"1576":1,"1577":2,"1665":1,"1889":5,"1891":3,"1892":1,"2130":1,"2232":2}}],["schema",{"1":{"75":1,"76":1,"77":1,"78":1,"79":1,"80":1,"81":1,"82":1,"561":1,"562":1,"563":1,"564":1,"565":1,"566":1,"567":1,"568":1,"695":1,"696":1,"697":1,"698":1,"699":1,"700":1,"701":1,"702":1,"703":1,"1887":1,"1888":1,"1889":1,"1890":1,"1891":1,"1892":1},"3":{"0":15,"26":1,"46":1,"48":1,"75":1,"109":1,"166":1,"200":1,"282":1,"292":2,"295":1,"309":1,"342":1,"343":1,"376":1,"452":1,"453":1,"455":1,"470":1,"471":2,"561":1,"563":1,"564":2,"565":2,"566":1,"568":3,"572":1,"573":1,"574":1,"633":1,"657":1,"658":1,"695":1,"698":1,"700":1,"702":1,"703":1,"707":1,"708":1,"719":1,"756":2,"757":2,"759":2,"761":1,"762":1,"786":1,"799":1,"854":1,"855":1,"891":3,"892":1,"893":1,"931":1,"932":1,"933":1,"935":1,"964":1,"965":1,"1004":1,"1012":1,"1018":5,"1026":1,"1034":4,"1042":1,"1043":1,"1050":2,"1051":1,"1082":1,"1083":1,"1085":2,"1090":2,"1093":1,"1133":2,"1144":1,"1154":1,"1175":1,"1212":8,"1213":1,"1237":3,"1259":2,"1270":1,"1273":1,"1276":1,"1280":2,"1281":1,"1282":3,"1286":60,"1294":1,"1300":7,"1309":2,"1311":30,"1312":2,"1323":10,"1327":1,"1339":1,"1350":5,"1359":7,"1362":2,"1364":1,"1366":4,"1369":21,"1380":4,"1395":1,"1396":12,"1402":1,"1405":1,"1412":1,"1415":5,"1417":1,"1427":1,"1436":4,"1438":5,"1441":1,"1455":8,"1461":2,"1467":5,"1473":2,"1490":2,"1492":1,"1496":1,"1526":3,"1544":1,"1545":4,"1546":1,"1547":1,"1551":1,"1577":5,"1590":1,"1591":1,"1614":1,"1641":1,"1664":4,"1665":1,"1667":1,"1684":1,"1688":1,"1697":1,"1719":1,"1721":1,"1765":1,"1779":1,"1810":1,"1848":1,"1851":3,"1854":1,"1856":6,"1859":1,"1873":2,"1876":1,"1886":1,"1887":2,"1888":1,"1890":1,"1892":2,"1927":1,"1948":1,"1991":1,"2013":1,"2043":1,"2055":1,"2112":1,"2133":1,"2179":1,"2192":1,"2207":1,"2232":5}}],["schemename",{"2":{"1300":1},"3":{"1300":4,"1436":3}}],["schemes",{"3":{"350":2,"352":1,"420":1,"422":1,"1237":1,"1271":1,"1300":1,"1311":4,"1324":3,"1350":1,"1359":2,"1417":2,"1975":1,"1976":1,"1978":2}}],["scheme",{"3":{"0":3,"59":1,"92":2,"96":1,"207":3,"208":2,"209":2,"265":1,"273":2,"309":1,"310":1,"311":1,"350":3,"352":1,"355":1,"360":1,"361":2,"418":4,"420":5,"421":2,"422":3,"427":2,"556":1,"557":1,"618":1,"665":1,"668":1,"749":2,"751":1,"827":2,"907":1,"945":1,"948":1,"1124":1,"1184":1,"1186":1,"1212":2,"1237":3,"1271":7,"1286":4,"1287":1,"1298":1,"1300":13,"1311":27,"1312":1,"1323":2,"1324":17,"1339":2,"1347":1,"1350":2,"1353":1,"1359":38,"1395":4,"1406":1,"1415":4,"1416":11,"1417":15,"1434":1,"1436":8,"1441":1,"1447":1,"1467":1,"1490":1,"1534":1,"1577":1,"1599":1,"1611":1,"1647":1,"1650":1,"1698":1,"1721":1,"1825":3,"1900":1,"1903":1,"1904":1,"1965":1,"1968":3,"1971":1,"1975":5,"1976":3,"1978":1,"2025":1,"2083":1,"2086":1,"2122":1,"2142":1,"2144":1,"2232":2}}],["sc",{"3":{"1359":2}}],["science",{"3":{"1913":1,"2222":1}}],["sci",{"3":{"1350":4,"1359":9}}],["scuffed",{"3":{"1324":1}}],["sccs",{"3":{"1201":1,"1496":3,"1498":1,"1517":1}}],["scc",{"0":{"1198":1},"3":{"1196":1,"1237":1,"1436":1,"1496":5,"1498":1}}],["scenarios",{"3":{"1286":1,"1359":1,"1474":1,"1492":1,"1639":1,"1775":1,"2033":1,"2037":1}}],["scenario",{"0":{"2033":1},"3":{"68":2,"627":1,"1229":1,"1271":1,"1286":1,"1323":2,"1324":1,"1350":1,"1395":2,"1436":3,"1438":1,"1467":1,"1472":1,"1474":2,"1492":3,"1534":1,"1591":1,"1709":1,"2029":1,"2033":3,"2034":1,"2037":1,"2038":2}}],["scorable",{"3":{"1350":1,"2190":1}}],["scoretier",{"3":{"1395":1}}],["scoretieroptions",{"3":{"1395":2}}],["scorematchesfilter",{"3":{"1395":3}}],["scorepollsession",{"2":{"1390":1},"3":{"1199":2,"1203":1,"1207":3,"1390":5,"1395":21}}],["scorepollsignal",{"2":{"1390":1},"3":{"1199":4,"1203":1,"1207":1,"1390":1,"1395":6,"1496":1}}],["scorepollhost",{"3":{"1199":3,"1203":1,"1207":1,"1390":1,"1395":6,"1496":1}}],["scorepolltrackertests",{"3":{"1199":1,"1207":2}}],["scorepolltracker",{"2":{"1390":1},"3":{"1199":3,"1203":1,"1207":3,"1390":2,"1395":17,"1496":2}}],["scorerequested",{"3":{"1395":2}}],["scorer",{"3":{"756":1,"1016":1,"1018":4,"1021":2,"1095":2,"1359":4,"1360":1,"1366":5,"1368":1,"1369":9,"1435":1,"1437":1,"1455":2,"1461":1,"1463":1,"1485":1,"1487":1,"1492":2,"2239":1}}],["scoreeventsessionscommand",{"3":{"1496":1}}],["scoreeventsessionshandler",{"3":{"1286":1,"1350":1,"1496":3}}],["scoreeventsessionsresultdto",{"2":{"1347":1,"1390":1},"3":{"1199":9,"1203":1,"1207":1,"1347":1,"1350":3,"1359":2,"1390":1,"1395":3}}],["scoreeventsessionsinternalcommandmarkertests",{"2":{"1438":1},"3":{"1199":1,"1207":2,"1438":2}}],["scoreeventsessionsinternalcommand",{"2":{"1018":1,"1264":1,"1347":1,"1367":1,"1376":1,"1390":1},"3":{"1018":1,"1199":6,"1203":1,"1207":2,"1264":2,"1286":1,"1347":2,"1350":3,"1359":8,"1367":1,"1369":1,"1376":1,"1380":3,"1390":2,"1438":1,"1496":2,"2190":2}}],["scoreeventsessionsinternalcommandhandlertests",{"2":{"1438":1},"3":{"1199":1,"1207":2,"1438":2}}],["scoreeventsessionsinternalcommandhandler",{"2":{"994":1,"996":1,"1018":1,"1270":1,"1367":1,"1369":1,"1496":1,"2190":1},"3":{"493":1,"494":1,"495":1,"994":1,"995":1,"996":2,"998":1,"1018":2,"1099":1,"1103":1,"1199":2,"1203":1,"1207":2,"1270":3,"1350":2,"1359":14,"1367":2,"1369":3,"1438":2,"1496":2,"2190":1}}],["scoreeventsessions",{"3":{"493":1,"494":1,"495":1,"995":1,"1018":2,"1082":1,"1099":1,"1203":9,"1207":18,"1264":1,"1270":3,"1286":1,"1347":1,"1350":1,"1359":24,"1367":1,"1369":2,"1390":1,"1438":1,"1467":1,"1496":3,"1526":1}}],["score",{"1":{"1794":1,"1795":1,"1796":1,"1797":1,"1798":1,"1799":1,"1800":1,"1801":1,"1802":1},"3":{"0":3,"520":1,"1010":2,"1012":6,"1014":2,"1017":2,"1018":9,"1019":2,"1020":2,"1082":1,"1094":1,"1193":1,"1212":1,"1216":1,"1342":3,"1347":1,"1350":15,"1359":40,"1362":2,"1366":3,"1369":16,"1376":1,"1385":2,"1395":39,"1396":1,"1435":5,"1436":1,"1438":3,"1455":1,"1487":1,"1496":1,"1518":1,"1524":1,"1525":1,"1526":4,"1527":2,"1530":3,"1531":3,"1534":1,"1535":1,"1537":8,"1553":1,"1564":1,"1575":1,"1578":2,"1580":1,"1582":2,"1585":3,"1591":1,"1597":1,"1599":1,"1630":1,"1632":2,"1640":1,"1784":1,"1794":3,"1797":1,"1799":4,"1800":1,"1801":3,"1802":6,"2050":1,"2060":1,"2114":1,"2171":1,"2190":3,"2205":1,"2239":2}}],["scorecards",{"0":{"1520":1},"3":{"1011":1,"1012":1,"1216":1,"1521":1,"1604":1,"1674":2,"2231":1,"2233":1}}],["scorecard",{"0":{"1526":1,"1577":1,"1591":1,"2231":1},"1":{"1522":1,"1523":1,"1524":1,"1525":1,"1526":1,"1527":1,"1528":1,"1573":1,"1574":1,"1575":1,"1576":1,"1577":1,"1578":1,"1579":1,"1587":1,"1588":1,"1589":1,"1590":1,"1591":1,"1592":1,"1593":1},"3":{"0":2,"1006":2,"1007":1,"1010":1,"1011":4,"1012":3,"1013":2,"1014":2,"1021":1,"1089":1,"1340":1,"1342":2,"1477":1,"1496":1,"1518":1,"1520":4,"1522":2,"1526":1,"1528":1,"1529":1,"1536":2,"1537":1,"1553":1,"1571":1,"1573":2,"1577":1,"1579":1,"1580":3,"1587":2,"1591":1,"1594":2,"1597":1,"1605":1,"1610":1,"1673":1,"1739":1,"1744":1,"1778":1,"1787":2,"1794":1,"1796":1,"1799":3,"1801":2,"1802":4,"1805":1,"1811":2,"1888":1,"1905":2,"1910":2,"1916":1,"1929":1,"1930":3,"2034":1,"2043":2,"2048":2,"2059":2,"2060":1,"2062":3,"2069":2,"2079":1,"2085":1,"2109":1,"2115":3,"2133":2,"2153":2,"2160":2,"2169":2,"2180":2,"2193":2,"2203":2,"2211":2}}],["scoredshown",{"3":{"1395":1}}],["scoredthisrun",{"3":{"1395":14}}],["scoredon",{"3":{"1350":2,"1359":1,"1390":1,"1395":3}}],["scored",{"0":{"1784":1,"1799":1},"3":{"0":1,"1011":5,"1012":5,"1013":2,"1014":2,"1017":1,"1018":1,"1022":1,"1090":2,"1216":1,"1347":2,"1350":4,"1359":17,"1376":1,"1380":3,"1390":2,"1395":6,"1426":1,"1428":1,"1438":2,"1467":1,"1516":1,"1519":1,"1521":1,"1522":1,"1526":3,"1527":2,"1534":1,"1536":2,"1547":1,"1553":1,"1554":1,"1571":1,"1573":1,"1577":3,"1578":2,"1585":3,"1587":1,"1591":1,"1592":1,"1599":1,"1645":1,"1780":1,"1794":2,"1799":2,"1801":3,"1802":1,"1903":1,"1916":1,"2040":1,"2062":2,"2069":2,"2085":1,"2171":1,"2190":1,"2214":1,"2231":1}}],["scoresessionsasync",{"2":{"1390":1},"3":{"1380":4,"1390":1,"1395":7}}],["scoresessionasync",{"3":{"1018":1,"1359":5,"1369":1}}],["scoreschema",{"3":{"1366":2,"1369":2}}],["scores",{"3":{"0":2,"85":1,"768":1,"1010":3,"1012":5,"1014":1,"1017":1,"1018":7,"1019":2,"1020":1,"1090":1,"1094":1,"1259":28,"1286":1,"1323":7,"1342":2,"1347":2,"1350":10,"1359":21,"1362":1,"1366":4,"1367":1,"1369":9,"1380":1,"1390":2,"1395":10,"1396":1,"1435":2,"1436":2,"1437":1,"1438":2,"1442":1,"1487":1,"1492":1,"1496":1,"1521":1,"1522":1,"1526":1,"1536":1,"1537":1,"1547":2,"1570":2,"1573":1,"1587":1,"1591":1,"1592":3,"1594":1,"1632":1,"1784":4,"1794":1,"1797":1,"1798":1,"1799":4,"1800":1,"1910":1,"1929":1,"2053":1,"2059":1,"2062":3,"2114":1,"2171":1,"2182":1,"2232":1}}],["scoringinstruments",{"3":{"1496":2}}],["scoring=true",{"3":{"1492":1}}],["scoringnoscores",{"3":{"1395":1}}],["scoringcomplete",{"3":{"1395":2}}],["scoringtimedout",{"3":{"1395":2}}],["scoringpartial",{"3":{"1395":2}}],["scoringpollfailed",{"3":{"1395":2}}],["scoringpercent",{"3":{"1395":1}}],["scoring",{"0":{"1359":1,"1366":1,"1367":1,"1435":1,"1527":1,"1578":1,"1592":1},"1":{"1015":1,"1016":1,"1017":1,"1018":1,"1019":1,"1020":1,"1021":1,"1022":1},"2":{"1203":1,"1369":1,"1379":1,"1427":1,"1435":1,"1437":1,"1438":1,"1442":1,"1455":1,"1486":1,"1487":1,"1492":1,"1496":1},"3":{"0":8,"492":1,"493":1,"516":1,"517":2,"520":2,"607":2,"609":2,"994":1,"995":1,"996":2,"998":1,"999":1,"1004":1,"1012":1,"1015":1,"1016":3,"1017":3,"1018":11,"1019":1,"1020":3,"1022":3,"1089":4,"1090":4,"1093":1,"1096":1,"1099":2,"1103":2,"1192":1,"1193":1,"1199":10,"1203":5,"1206":1,"1207":38,"1212":1,"1270":2,"1342":1,"1347":4,"1350":14,"1354":1,"1359":47,"1360":2,"1366":10,"1367":3,"1368":1,"1369":36,"1376":1,"1379":1,"1380":5,"1390":7,"1395":29,"1396":1,"1426":1,"1427":12,"1428":1,"1435":10,"1436":5,"1437":8,"1438":12,"1442":7,"1455":8,"1463":1,"1467":9,"1469":1,"1470":2,"1486":2,"1487":3,"1492":8,"1493":1,"1496":20,"1526":5,"1527":1,"1529":1,"1532":1,"1534":1,"1535":2,"1537":1,"1553":1,"1594":1,"1632":2,"1640":1,"1784":2,"1794":1,"1795":1,"1802":1,"1947":1,"2060":1,"2061":1,"2062":1,"2179":1,"2180":1,"2182":1,"2190":1,"2232":4}}],["scoping",{"0":{"1267":1},"3":{"0":1,"159":1,"318":3,"320":1,"321":2,"324":1,"335":1,"544":1,"545":1,"619":1,"739":1,"743":3,"826":1,"906":1,"964":1,"1239":1,"1247":3,"1270":11,"1286":2,"1297":1,"1300":7,"1309":1,"1311":12,"1323":2,"1324":6,"1350":1,"1353":2,"1359":5,"1372":2,"1380":27,"1388":1,"1395":14,"1396":9,"1402":3,"1415":1,"1416":1,"1417":1,"1436":9,"1438":3,"1455":1,"1467":1,"1496":2,"1526":2,"1591":1,"1595":1,"1596":1,"1599":1,"1631":2,"1639":4,"1666":1,"1749":1,"1759":1,"1815":2,"1920":6,"1995":2,"1996":1}}],["scoper",{"3":{"1997":1}}],["scopers",{"3":{"1260":1}}],["scopeunresolvedforpublicreader",{"3":{"1395":4}}],["scopecaption",{"3":{"1324":1}}],["scopecapturinglogger",{"3":{"1199":3,"1207":2}}],["scope=",{"3":{"1324":1}}],["scopequeryasync",{"3":{"1324":1}}],["scopeprovider",{"3":{"1324":4}}],["scopeable",{"3":{"1324":1,"1465":1}}],["scopefactory",{"3":{"1259":5,"1286":1,"1359":4,"1402":1}}],["scopekeyregex",{"3":{"1309":1}}],["scopekeymaxlength",{"2":{"1306":1},"3":{"1286":2,"1306":1,"1309":7}}],["scopekey",{"2":{"231":1,"1512":1,"1941":1},"3":{"225":1,"231":1,"1286":1,"1309":31,"1324":4,"1396":1,"1415":4,"1512":1,"1933":2,"1941":1}}],["scopedeventids",{"3":{"1359":1}}],["scopedexporttestcontroller",{"3":{"1199":2,"1207":1}}],["scopedkey",{"3":{"1324":4}}],["scopedintegrationeventhandlerbasetests",{"3":{"1199":1,"1207":6,"1259":1,"1438":2}}],["scopedintegrationeventhandlerbase",{"2":{"1252":1,"1413":1,"1665":1},"3":{"1199":15,"1203":1,"1207":2,"1252":3,"1259":24,"1355":2,"1359":11,"1396":12,"1402":4,"1413":1,"1415":7,"1496":1,"1665":1}}],["scopedprobe",{"3":{"1199":3,"1207":1}}],["scopeduseroverride",{"2":{"1045":1,"2186":1},"3":{"0":1,"1042":2,"1045":1,"1199":5,"1203":1,"1207":2,"1313":1,"1318":6,"1323":12,"1496":2,"2186":1}}],["scoped",{"0":{"1937":1},"1":{"479":1,"480":1,"481":1,"482":1,"483":1,"484":1,"485":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"495":1},"3":{"0":19,"24":1,"26":1,"39":1,"71":1,"85":1,"86":1,"109":1,"115":1,"122":1,"149":1,"157":1,"173":1,"195":1,"200":1,"201":2,"214":1,"219":1,"223":1,"225":1,"231":1,"241":1,"243":1,"246":2,"283":1,"292":1,"303":1,"317":3,"318":2,"320":1,"324":2,"325":2,"326":1,"397":1,"415":1,"434":1,"443":1,"479":1,"482":1,"483":1,"490":1,"491":1,"492":1,"493":1,"494":1,"511":1,"513":1,"518":1,"524":1,"530":2,"545":1,"549":1,"554":1,"564":1,"601":1,"602":1,"609":2,"620":1,"633":1,"638":1,"647":1,"651":1,"665":2,"666":2,"669":1,"670":1,"698":4,"699":1,"700":2,"702":1,"703":1,"706":1,"707":3,"708":2,"719":1,"724":1,"743":1,"747":1,"749":1,"751":1,"758":1,"766":1,"791":1,"794":1,"819":1,"827":2,"832":1,"863":1,"881":1,"905":2,"910":1,"916":1,"924":1,"926":1,"941":1,"988":1,"996":3,"1012":3,"1019":1,"1020":1,"1035":1,"1042":1,"1052":1,"1073":1,"1099":1,"1100":1,"1116":2,"1124":1,"1133":2,"1147":1,"1153":1,"1168":2,"1183":1,"1212":4,"1214":2,"1237":6,"1247":7,"1252":1,"1253":3,"1256":1,"1258":1,"1259":15,"1262":2,"1263":2,"1264":1,"1265":1,"1267":3,"1269":3,"1270":41,"1271":2,"1273":1,"1275":3,"1276":1,"1277":2,"1278":1,"1286":55,"1290":1,"1294":1,"1296":1,"1300":34,"1301":4,"1306":2,"1307":1,"1308":1,"1309":25,"1310":1,"1311":44,"1312":1,"1313":2,"1316":3,"1317":3,"1318":1,"1322":1,"1323":44,"1324":70,"1337":1,"1339":16,"1346":1,"1350":5,"1353":3,"1355":2,"1359":41,"1366":1,"1368":2,"1369":10,"1372":1,"1378":1,"1380":30,"1383":1,"1384":1,"1385":1,"1395":59,"1396":85,"1400":1,"1401":1,"1402":25,"1413":1,"1415":19,"1416":4,"1417":39,"1427":1,"1431":1,"1436":50,"1438":6,"1446":1,"1453":1,"1454":1,"1455":9,"1458":1,"1460":1,"1461":2,"1467":14,"1469":1,"1474":2,"1481":1,"1486":1,"1487":2,"1489":1,"1492":3,"1496":2,"1512":1,"1525":2,"1526":8,"1531":1,"1534":3,"1548":1,"1552":1,"1555":1,"1556":3,"1577":7,"1582":1,"1591":3,"1596":2,"1599":1,"1600":1,"1629":2,"1630":4,"1631":5,"1632":4,"1635":2,"1636":1,"1638":1,"1639":2,"1640":1,"1664":1,"1666":1,"1668":2,"1669":1,"1673":1,"1714":1,"1749":1,"1757":1,"1815":3,"1825":1,"1849":1,"1851":2,"1883":3,"1908":1,"1920":3,"1921":1,"1924":2,"1937":1,"1940":3,"1941":1,"1948":2,"1956":2,"1959":1,"1994":2,"1995":4,"1997":2,"2000":1,"2001":1,"2010":1,"2024":1,"2033":1,"2057":2,"2074":1,"2095":2,"2118":1,"2122":1,"2126":1,"2150":1,"2186":1,"2232":1}}],["scopes",{"3":{"0":6,"143":1,"179":1,"200":1,"201":1,"225":1,"318":1,"325":2,"378":1,"384":1,"397":1,"544":2,"545":1,"548":1,"607":1,"743":1,"782":1,"826":1,"905":2,"953":1,"1057":1,"1212":1,"1239":1,"1259":2,"1267":1,"1270":3,"1286":3,"1300":1,"1301":1,"1309":1,"1311":5,"1323":2,"1324":2,"1333":1,"1339":2,"1350":4,"1359":6,"1380":1,"1395":3,"1396":17,"1402":3,"1415":4,"1417":1,"1436":15,"1438":1,"1457":1,"1483":1,"1577":2,"1761":1,"1813":1,"1815":1,"1817":2,"1933":1,"1975":1,"1995":2,"2006":1,"2163":1,"2232":2}}],["scope",{"0":{"1305":1,"1680":1},"2":{"1054":1,"1920":2},"3":{"0":15,"15":1,"24":2,"26":6,"27":7,"71":1,"81":1,"84":1,"123":4,"125":1,"135":3,"136":2,"141":1,"160":2,"178":3,"180":1,"193":1,"201":1,"202":6,"223":1,"225":1,"230":1,"243":1,"246":2,"264":1,"312":1,"319":2,"320":1,"321":1,"343":1,"350":2,"359":2,"361":1,"365":2,"368":1,"369":1,"384":1,"388":1,"397":1,"405":2,"438":2,"459":1,"461":2,"501":1,"507":1,"522":1,"526":1,"528":1,"529":1,"531":1,"534":1,"536":4,"545":1,"546":1,"550":1,"556":1,"579":1,"620":1,"674":1,"688":1,"690":10,"691":3,"692":1,"696":3,"698":12,"699":1,"700":2,"702":5,"703":1,"707":3,"719":1,"731":2,"743":2,"744":1,"749":1,"799":1,"803":1,"820":1,"827":1,"848":4,"889":1,"898":1,"899":1,"905":1,"910":1,"914":1,"920":1,"926":3,"966":1,"988":5,"990":1,"994":1,"996":1,"1010":1,"1012":2,"1013":1,"1018":2,"1042":3,"1051":1,"1054":1,"1067":1,"1074":1,"1093":1,"1100":1,"1108":1,"1114":1,"1120":1,"1124":1,"1133":1,"1157":1,"1161":1,"1168":3,"1177":1,"1192":1,"1195":1,"1204":2,"1211":1,"1212":2,"1214":1,"1236":1,"1237":1,"1238":1,"1239":1,"1240":1,"1245":1,"1246":7,"1247":14,"1252":1,"1253":3,"1254":1,"1256":1,"1258":1,"1259":38,"1260":1,"1263":3,"1265":1,"1267":1,"1269":1,"1270":43,"1273":1,"1275":3,"1276":6,"1277":1,"1278":4,"1286":92,"1300":12,"1303":1,"1304":1,"1305":3,"1306":2,"1308":2,"1309":58,"1311":28,"1317":1,"1318":3,"1323":40,"1324":51,"1339":4,"1342":1,"1350":5,"1352":1,"1353":3,"1355":2,"1357":2,"1359":65,"1369":1,"1372":3,"1376":1,"1380":16,"1383":2,"1387":2,"1391":2,"1395":43,"1396":159,"1399":1,"1401":4,"1402":42,"1407":1,"1411":2,"1413":1,"1415":25,"1416":1,"1417":15,"1428":1,"1431":1,"1433":1,"1436":59,"1438":14,"1454":1,"1455":1,"1459":2,"1463":1,"1467":1,"1470":1,"1472":1,"1475":1,"1485":1,"1487":1,"1488":1,"1489":1,"1496":9,"1498":1,"1500":2,"1534":1,"1545":1,"1548":1,"1553":1,"1564":1,"1585":1,"1596":1,"1600":1,"1605":1,"1630":1,"1631":9,"1632":3,"1636":1,"1639":6,"1641":2,"1654":1,"1665":1,"1667":1,"1669":1,"1685":1,"1707":1,"1739":1,"1748":1,"1765":1,"1776":1,"1815":2,"1818":1,"1821":2,"1824":1,"1849":1,"1851":3,"1891":1,"1910":2,"1920":4,"1924":2,"1933":3,"1935":1,"1937":6,"1940":2,"1941":1,"1948":1,"1953":1,"1975":2,"1994":1,"1996":1,"1997":3,"2000":2,"2010":1,"2032":1,"2036":1,"2047":1,"2048":1,"2066":1,"2074":2,"2085":3,"2107":1,"2126":1,"2135":1,"2136":1,"2142":2,"2156":1,"2163":3,"2164":1,"2165":1,"2167":2,"2169":3,"2179":1,"2184":1,"2185":1,"2188":1,"2189":1,"2220":1,"2232":2}}],["scrypt",{"3":{"1905":1}}],["scroller",{"3":{"1526":1}}],["scrolledid",{"3":{"1324":3}}],["scrolled",{"3":{"1324":2,"2073":1}}],["scrollports",{"3":{"1438":1}}],["scrollposition",{"3":{"1324":1}}],["scrollwidth",{"3":{"1436":1}}],["scrollbar",{"3":{"1436":1}}],["scrollbehavior",{"3":{"1324":2}}],["scrollintoviewasync",{"3":{"1324":1}}],["scrollingelement",{"3":{"1324":1}}],["scrolling",{"3":{"1228":1,"1229":1,"1324":1,"1436":1,"1559":1}}],["scrollmanager",{"3":{"1324":2}}],["scrolltop",{"3":{"1324":1}}],["scrolltrackerid",{"3":{"1324":1}}],["scrollcontainerselector",{"3":{"1324":1}}],["scrolls",{"3":{"1247":1,"1324":5,"1436":1,"1438":1}}],["scroll",{"3":{"881":2,"883":1,"1286":1,"1324":33,"1383":1,"1395":12,"1396":2,"1438":3,"1526":1,"1643":1,"1669":3,"2073":2,"2076":1,"2079":2}}],["scratch",{"3":{"799":1,"1396":1,"1402":1,"1415":1,"1436":2,"1455":1,"1466":1,"1780":1}}],["scraper",{"3":{"1998":1}}],["scrapers",{"3":{"174":1}}],["scraped",{"3":{"1459":1}}],["scrape",{"3":{"1415":1,"1893":1}}],["scraping",{"3":{"725":1,"1317":1}}],["scramble",{"3":{"150":1}}],["screenreader",{"3":{"1524":1,"1526":2,"1531":1,"1533":1,"1589":2,"1591":2,"1596":1,"1598":1}}],["screencap",{"2":{"1479":1},"3":{"1479":3}}],["screenlayout",{"3":{"1416":1}}],["screening",{"3":{"1359":1}}],["screensize",{"3":{"1416":1}}],["screens",{"3":{"1059":1,"1300":1,"1324":4,"1350":3,"1380":2,"1381":1,"1383":1,"1385":1,"1395":3,"1396":2,"1409":1,"1415":3,"1417":3,"1436":1,"1438":1,"1559":1,"2070":1,"2079":1}}],["screenshots",{"3":{"1417":1,"1436":2,"1479":4,"1480":5,"1669":1}}],["screenshotformat",{"3":{"1417":1}}],["screenshotted",{"3":{"690":1,"1396":5,"1631":1}}],["screenshot",{"0":{"1479":1,"1480":1},"3":{"0":1,"691":1,"1202":1,"1203":1,"1391":1,"1396":2,"1417":7,"1436":1,"1479":1,"1480":1,"1496":1}}],["screened",{"3":{"676":1,"1359":1,"1415":1}}],["screen",{"0":{"1644":1,"2072":1},"1":{"1605":1,"1606":1,"1607":1,"1608":1,"1609":1,"1610":1,"1739":1,"1740":1,"1741":1,"1742":1,"1743":1,"1744":1},"3":{"24":1,"109":1,"380":1,"620":1,"622":1,"683":1,"691":1,"692":1,"906":1,"1059":2,"1259":2,"1270":2,"1286":3,"1300":8,"1311":1,"1318":1,"1323":1,"1324":21,"1339":1,"1347":1,"1350":2,"1359":8,"1375":1,"1380":3,"1388":1,"1391":1,"1395":26,"1396":23,"1397":1,"1402":2,"1409":1,"1414":3,"1415":6,"1416":10,"1417":27,"1436":13,"1438":1,"1479":1,"1523":1,"1524":3,"1526":1,"1530":1,"1531":2,"1532":1,"1533":1,"1557":1,"1558":1,"1577":1,"1589":1,"1591":1,"1595":1,"1596":2,"1603":1,"1604":1,"1605":2,"1606":1,"1610":1,"1626":1,"1640":1,"1642":1,"1668":1,"1669":2,"1674":1,"1685":1,"1712":1,"1739":2,"1740":1,"1744":1,"1760":1,"1773":1,"1942":2,"1943":1,"1950":1,"1962":1,"1966":1,"2045":1,"2070":4,"2071":1,"2072":2,"2073":1,"2080":4,"2116":1,"2121":1,"2191":1,"2200":1,"2202":1,"2203":1}}],["scrum",{"3":{"2219":1,"2220":1}}],["scrutiny",{"3":{"1436":1}}],["scrutor",{"0":{"1262":1},"3":{"0":2,"5":1,"117":2,"119":1,"121":2,"122":2,"572":1,"649":1,"781":1,"922":1,"1042":1,"1212":1,"1213":1,"1259":2,"1262":2,"1265":1,"1268":1,"1270":12,"1275":1,"1309":1,"1311":3,"1313":1,"1316":3,"1318":1,"1319":1,"1323":15,"1324":8,"1354":1,"1359":10,"1380":2,"1395":9,"1404":1,"1415":9,"1436":5,"1438":2,"1489":1,"1507":1,"1577":3,"1651":1,"1730":1,"1818":1,"1821":1,"1826":1,"1827":1,"1879":1,"1883":1,"1956":1,"2043":1,"2095":2,"2184":1,"2227":1,"2232":1}}],["scrubemailaddressesfromproductreviewernames",{"3":{"403":1}}],["scrubs",{"3":{"403":1,"1323":1,"1324":2,"1496":1,"1627":1,"1641":1,"2063":1}}],["scrubbed",{"3":{"0":1,"395":1,"1359":1}}],["scripting",{"3":{"1467":1}}],["scriptedhandler",{"3":{"1199":4,"1207":2}}],["scripted",{"3":{"861":2,"862":1,"1003":1,"1004":1,"1126":1,"1436":2,"1727":1,"2005":1}}],["scripts",{"0":{"1473":1},"1":{"1471":1,"1472":1,"1473":1,"1474":1,"1475":1,"1476":1,"1477":1,"1478":1,"1479":1,"1480":1,"1481":1,"1482":1,"1483":1,"1484":1},"3":{"0":1,"208":1,"387":1,"390":1,"484":1,"490":1,"491":1,"492":1,"493":1,"633":1,"1004":3,"1093":1,"1192":1,"1324":2,"1339":1,"1422":1,"1436":2,"1471":6,"1472":1,"1473":6,"1474":1,"1475":1,"1479":2,"1480":1,"1482":3,"1563":1,"1570":1,"1706":1,"1710":1,"1727":1,"1876":1,"1922":1,"2174":1}}],["script",{"1":{"1181":1,"1182":1,"1183":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1},"2":{"483":1},"3":{"0":3,"157":1,"208":1,"212":2,"213":1,"214":5,"215":1,"216":1,"219":6,"273":1,"275":1,"286":1,"461":1,"483":1,"484":1,"543":1,"545":1,"633":2,"634":1,"659":1,"861":2,"869":1,"966":1,"996":2,"998":1,"1004":1,"1082":1,"1084":1,"1181":1,"1183":4,"1184":1,"1185":3,"1186":2,"1187":3,"1212":3,"1271":1,"1286":2,"1300":5,"1311":2,"1316":1,"1323":5,"1324":20,"1336":2,"1339":9,"1350":1,"1359":1,"1415":2,"1417":1,"1433":2,"1436":6,"1438":1,"1443":1,"1452":1,"1455":1,"1458":5,"1460":1,"1471":1,"1472":5,"1473":1,"1475":14,"1479":3,"1480":4,"1492":1,"1496":1,"1525":1,"1526":3,"1531":1,"1577":1,"1591":2,"1600":1,"1661":2,"1665":1,"1673":2,"1674":1,"1681":1,"1682":1,"1684":5,"1685":1,"1701":2,"1710":1,"1722":2,"1727":1,"1728":1,"1729":5,"1731":1,"1966":1,"1968":1,"1972":1,"2034":2,"2085":1,"2097":2,"2124":1,"2135":1,"2147":2,"2148":1,"2149":3,"2150":3,"2152":1,"2153":1,"2232":1}}],["scars",{"3":{"2072":1}}],["scarier",{"3":{"2036":1}}],["scar",{"3":{"1359":1,"1455":1}}],["scarcest",{"3":{"871":1}}],["scatter",{"3":{"1396":1,"1817":1,"1826":1,"1913":1,"1923":1}}],["scattering",{"3":{"1247":1,"1300":1,"1324":1,"1359":1,"1380":1,"1396":1,"1924":1,"1964":1,"2230":1}}],["scattered",{"3":{"118":1,"187":1,"360":1,"1201":1,"1212":1,"1237":1,"1247":2,"1260":1,"1270":1,"1286":3,"1301":3,"1311":1,"1314":1,"1323":2,"1324":2,"1348":1,"1350":6,"1352":1,"1395":2,"1396":1,"1402":2,"1415":1,"1417":2,"1436":2,"1557":1,"1813":1,"1819":2,"1822":1,"1827":1,"1882":1,"1930":1,"2136":1}}],["scatters",{"3":{"116":1}}],["scaffold",{"0":{"1650":1,"2089":1},"1":{"2087":1,"2088":1,"2089":1,"2090":1,"2091":1,"2092":1,"2093":1,"2094":1,"2095":1,"2096":1,"2097":1,"2098":1},"2":{"1689":1,"1699":1},"3":{"631":1,"633":1,"634":1,"635":1,"907":1,"923":1,"939":1,"942":1,"1034":1,"1133":1,"1286":4,"1436":3,"1488":3,"1602":2,"1646":2,"1649":1,"1656":1,"1661":2,"1674":1,"1681":1,"1683":1,"1684":4,"1685":4,"1686":6,"1687":1,"1689":3,"1692":1,"1693":1,"1697":1,"1699":2,"1701":3,"1702":1,"1705":2,"1719":3,"1724":1,"1727":2,"1729":1,"1730":1,"2004":1,"2087":1,"2089":1,"2098":2,"2210":1}}],["scaffolds",{"3":{"564":1,"698":1,"891":1,"1034":2,"1037":1,"1190":1,"1284":1,"1286":1,"1681":1,"1726":1,"1731":1,"2097":1,"2211":1}}],["scaffolded",{"3":{"41":1,"551":1,"749":1,"891":1,"921":1,"1270":2,"1286":4,"1396":1,"1436":5,"1497":1,"1646":1,"1649":1,"1650":1,"1651":1,"1652":1,"1653":1,"1654":1,"1655":1,"1656":1,"1664":1,"1675":1,"1684":1,"1685":4,"1686":4,"1687":1,"1690":1,"1697":1,"1699":1,"1701":4,"1720":1,"1727":1,"1729":1,"1730":1,"2004":1,"2101":1}}],["scaffolding",{"0":{"1661":1},"1":{"631":1,"632":1,"633":1,"634":1,"635":1,"636":1,"1726":1,"1727":1,"1728":1,"1729":1,"1730":1,"1731":1},"3":{"0":1,"534":1,"631":1,"633":1,"1212":1,"1286":1,"1428":1,"1429":1,"1436":3,"1438":2,"1442":1,"1488":1,"1602":1,"1699":1,"1726":1,"1727":2,"2062":1,"2098":1,"2232":1}}],["scalable",{"3":{"1237":1,"2213":1,"2217":1,"2218":1,"2220":1}}],["scalability",{"0":{"1549":1},"3":{"1216":1,"1223":1,"1228":1,"1229":8,"1231":1,"1233":1,"1237":3,"1241":1,"1242":1,"1243":1,"1247":16,"1252":1,"1259":8,"1260":1,"1263":1,"1269":1,"1270":18,"1271":1,"1274":1,"1278":1,"1286":33,"1287":1,"1300":10,"1301":10,"1306":1,"1307":1,"1309":12,"1310":4,"1311":21,"1313":1,"1316":1,"1323":9,"1324":9,"1337":1,"1339":10,"1343":1,"1347":1,"1350":13,"1355":1,"1358":1,"1359":61,"1367":1,"1369":5,"1375":1,"1380":16,"1388":1,"1395":19,"1396":43,"1402":16,"1406":1,"1413":1,"1415":11,"1417":10,"1422":1,"1427":1,"1436":13,"1438":1,"1450":1,"1455":1,"1456":1,"1458":2,"1461":1,"1496":1,"1499":1,"1526":1,"1530":1,"1531":1,"1533":1,"1535":1,"1577":1,"1581":1,"1582":1,"1583":1,"1591":1,"1595":1,"1596":1,"1597":1,"1598":1,"1796":1,"1800":1}}],["scalars",{"3":{"1234":1,"1237":1,"1286":2,"1350":9,"1359":1,"1369":6,"1396":3,"1415":1,"1436":1,"1686":2,"2186":1,"2192":1}}],["scalaronlyspec",{"3":{"1199":2,"1207":1,"1436":8}}],["scalaronlyqueryspec",{"3":{"1199":2,"1207":1,"1436":6}}],["scalar",{"3":{"0":1,"47":1,"49":1,"166":1,"799":3,"801":1,"802":1,"1050":2,"1053":1,"1132":1,"1133":1,"1236":1,"1237":1,"1241":1,"1247":3,"1281":2,"1286":13,"1300":1,"1311":6,"1312":3,"1323":2,"1342":5,"1350":22,"1353":1,"1359":22,"1363":1,"1369":10,"1395":1,"1396":27,"1401":1,"1402":2,"1410":1,"1415":11,"1436":9,"1473":1,"1489":1,"1496":1,"1534":1,"1577":1,"1656":1,"1663":1,"1666":3,"1727":1,"1749":1,"1810":1,"1849":1,"1853":1,"1858":2,"1859":1,"1862":1,"1866":1,"1929":1,"2035":1}}],["scaling",{"0":{"1970":1},"3":{"48":1,"58":2,"387":1,"389":1,"706":1,"740":1,"742":1,"759":1,"768":1,"1300":2,"1323":1,"1480":1,"1568":1,"1724":1,"1948":1,"1971":1}}],["scales",{"3":{"133":1,"282":1,"760":1,"765":1,"781":1,"799":1,"1125":1,"1270":1,"1300":1,"1301":1,"1309":1,"1339":1,"1359":3,"1395":1,"1396":1,"1526":1,"1549":1,"1671":1,"1896":1,"2100":1,"2202":1}}],["scaled",{"1":{"662":1,"663":1,"664":1,"665":1,"666":1,"667":1,"668":1,"669":1,"670":1},"3":{"0":1,"24":1,"58":1,"234":1,"242":1,"245":2,"283":1,"662":1,"664":2,"667":2,"732":1,"766":1,"768":1,"769":1,"793":1,"1212":1,"1233":1,"1237":1,"1301":2,"1309":1,"1317":1,"1336":1,"1339":2,"1442":1,"1447":1,"1467":1,"1473":1,"1480":1,"1524":1,"1531":1,"1568":1,"1591":1,"1600":1,"1848":1,"1855":1,"1916":1,"1923":1,"1924":1,"1971":1,"1972":1,"2001":1,"2103":1,"2188":1,"2232":2}}],["scale",{"0":{"1255":1},"2":{"766":1},"3":{"0":5,"15":1,"20":1,"48":1,"58":1,"60":1,"62":2,"68":1,"100":1,"141":1,"176":1,"225":1,"234":1,"243":1,"283":1,"285":1,"291":1,"390":2,"435":1,"458":1,"480":1,"483":1,"486":1,"487":1,"488":1,"489":1,"490":2,"491":1,"492":1,"493":1,"494":2,"495":1,"517":1,"538":1,"700":1,"765":2,"766":2,"768":1,"869":1,"871":1,"882":1,"995":1,"1011":1,"1101":1,"1152":1,"1154":1,"1156":1,"1213":1,"1229":1,"1237":1,"1255":1,"1256":1,"1259":8,"1270":1,"1275":1,"1286":1,"1300":1,"1309":2,"1312":1,"1323":3,"1324":6,"1336":1,"1339":1,"1350":2,"1351":1,"1359":4,"1380":1,"1396":10,"1398":1,"1402":1,"1415":1,"1457":1,"1462":1,"1465":1,"1467":9,"1474":1,"1525":1,"1526":4,"1531":1,"1532":1,"1534":1,"1536":1,"1537":3,"1549":2,"1554":3,"1565":1,"1568":3,"1572":1,"1575":1,"1577":6,"1585":1,"1591":2,"1599":1,"1638":2,"1665":1,"1676":1,"1678":2,"1679":2,"1680":1,"1795":1,"1797":1,"1841":1,"1884":1,"1900":1,"1923":1,"1935":1,"1940":1,"1942":1,"1972":1,"2001":1,"2002":1,"2010":1,"2014":1,"2042":1,"2066":1,"2075":1,"2085":1,"2157":1,"2228":1}}],["scanfile",{"3":{"1436":2}}],["scanfailingassembly",{"3":{"1199":2,"1207":1,"1438":1}}],["scanpage",{"3":{"1417":1}}],["scanoutcome",{"3":{"1199":2,"1203":1,"1207":1,"1396":4}}],["scanoutcomekind",{"3":{"1199":3,"1203":1,"1207":1,"1396":5}}],["scanonmainthreadasync",{"2":{"682":1},"3":{"682":1,"1417":1}}],["scangridasync",{"2":{"616":1,"620":1,"621":1,"2057":1},"3":{"616":1,"618":2,"620":1,"621":1,"1433":1,"1436":4,"1487":1,"1488":2,"1591":1,"2057":1}}],["scananddeleteasync",{"2":{"251":1},"3":{"251":1,"1301":2}}],["scanasync",{"2":{"621":1},"3":{"0":1,"618":1,"621":1,"682":1,"1396":1,"1417":4,"1433":1,"1436":4,"1487":1,"1488":2,"1591":1,"2057":1}}],["scanmodules",{"3":{"1316":1,"1323":1,"1662":1}}],["scanmodule",{"3":{"121":1,"1316":1,"1323":2,"1436":1,"1662":1}}],["scanmoduleapplicationservices",{"2":{"117":1,"122":1,"127":1,"922":1,"1262":1,"1316":1,"1354":1,"1404":1,"1651":1,"1662":1,"1830":1,"1883":1,"1886":1,"1956":1,"2095":1},"3":{"117":1,"122":1,"127":1,"922":1,"1259":1,"1262":2,"1270":5,"1271":2,"1316":2,"1323":15,"1350":1,"1354":1,"1359":15,"1396":8,"1402":2,"1404":1,"1415":12,"1651":1,"1662":1,"1830":1,"1883":2,"1886":1,"1956":1,"2095":2,"2098":1}}],["scannableassemblylocations",{"3":{"1436":5}}],["scannable",{"3":{"1301":1,"1395":1,"1396":2,"1436":2}}],["scanners",{"3":{"1311":1}}],["scanner",{"2":{"685":1},"3":{"684":1,"685":2,"690":2,"691":1,"694":1,"734":1,"872":1,"1271":2,"1300":1,"1301":3,"1311":1,"1323":9,"1350":1,"1396":20,"1415":4,"1416":4,"1417":7,"1436":5,"1627":1}}],["scanneravailable",{"2":{"684":1,"685":1},"3":{"684":1,"685":1,"690":1,"1396":1}}],["scannedpages",{"3":{"1436":3}}],["scannedpasswordhasherset",{"3":{"1436":1}}],["scanneduifiles",{"3":{"1436":1}}],["scannedcontrollers",{"3":{"1436":2}}],["scannedservices",{"3":{"1436":2}}],["scannedsourcedirectories",{"3":{"1436":5}}],["scannedhandlers",{"3":{"1436":3}}],["scannedendpointset",{"3":{"1436":4}}],["scannedassemblies",{"3":{"1286":3}}],["scanned",{"3":{"0":2,"27":1,"160":1,"200":1,"545":1,"564":1,"566":1,"618":1,"622":1,"628":1,"691":1,"692":1,"781":1,"799":1,"870":1,"873":1,"910":1,"1118":1,"1135":1,"1163":1,"1204":1,"1271":3,"1286":3,"1300":2,"1301":4,"1311":1,"1323":1,"1324":1,"1349":1,"1388":1,"1396":24,"1414":1,"1415":1,"1417":2,"1434":2,"1436":50,"1438":1,"1443":1,"1455":1,"1459":2,"1467":1,"1475":1,"1489":1,"1496":3,"1526":2,"1591":2,"1626":1,"1627":2,"1643":2,"1661":1,"1701":1,"1720":1,"1730":1,"1815":1,"1883":1,"1956":1,"2097":1,"2121":1}}],["scanning",{"0":{"1883":1},"1":{"679":1,"680":1,"681":1,"682":1,"683":1,"684":1,"685":1,"686":1},"3":{"0":5,"5":1,"117":1,"413":1,"565":2,"618":1,"619":1,"679":1,"681":1,"682":2,"683":1,"689":2,"690":1,"691":2,"801":1,"966":1,"1055":1,"1114":1,"1116":2,"1118":3,"1119":1,"1161":1,"1175":1,"1192":1,"1212":2,"1213":2,"1259":2,"1262":1,"1270":2,"1271":1,"1286":4,"1300":3,"1301":2,"1309":1,"1311":7,"1312":1,"1319":1,"1323":8,"1324":1,"1339":2,"1350":3,"1359":20,"1369":6,"1380":2,"1395":3,"1396":32,"1402":5,"1404":1,"1415":13,"1416":2,"1417":12,"1427":1,"1433":1,"1436":19,"1437":1,"1438":1,"1455":1,"1467":3,"1469":1,"1470":1,"1488":1,"1496":1,"1548":1,"1627":1,"1631":2,"1651":1,"1669":1,"1956":1,"1959":1,"2057":1,"2093":1,"2095":1,"2232":1}}],["scansource",{"3":{"1436":2}}],["scans",{"3":{"0":5,"54":1,"122":1,"253":1,"265":1,"268":1,"341":1,"413":2,"545":4,"583":1,"585":1,"616":3,"618":10,"620":1,"621":1,"682":1,"689":2,"690":1,"691":1,"733":1,"870":3,"873":1,"953":1,"1050":1,"1108":1,"1133":1,"1170":1,"1229":1,"1237":2,"1250":1,"1256":1,"1259":2,"1270":3,"1275":1,"1279":1,"1282":1,"1286":7,"1300":4,"1301":2,"1311":2,"1312":1,"1316":2,"1319":1,"1323":6,"1324":5,"1339":1,"1350":3,"1359":5,"1395":3,"1396":18,"1415":2,"1416":1,"1417":8,"1421":1,"1431":1,"1436":67,"1438":2,"1467":2,"1485":1,"1487":1,"1490":1,"1492":1,"1526":2,"1531":1,"1532":1,"1577":1,"1591":2,"1596":2,"1600":1,"1605":1,"1631":2,"1654":1,"1669":1,"1739":1,"1741":1,"1810":1,"1824":1,"1875":1,"1881":1,"1883":1,"1898":1,"1929":1,"1956":1,"2057":1,"2220":1}}],["scan",{"0":{"1572":1},"3":{"0":17,"24":2,"109":2,"117":1,"121":1,"245":1,"248":2,"251":1,"257":1,"483":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":2,"492":2,"493":2,"494":3,"495":3,"537":1,"539":1,"546":2,"549":1,"552":1,"572":1,"583":4,"585":1,"587":1,"616":4,"618":8,"619":1,"620":3,"622":1,"626":3,"628":1,"629":2,"649":1,"682":5,"683":1,"684":6,"685":2,"686":1,"688":2,"690":6,"691":2,"692":1,"726":1,"733":3,"734":1,"735":1,"799":1,"810":1,"849":1,"867":3,"870":1,"871":1,"872":1,"874":1,"876":1,"881":2,"906":1,"922":4,"924":1,"926":3,"946":1,"1133":5,"1135":2,"1161":2,"1162":1,"1163":3,"1196":1,"1212":2,"1228":1,"1229":2,"1236":1,"1237":3,"1242":1,"1259":6,"1262":3,"1263":2,"1265":2,"1269":2,"1270":5,"1271":7,"1286":23,"1300":21,"1301":13,"1308":1,"1309":2,"1311":7,"1315":4,"1316":1,"1318":1,"1319":1,"1323":28,"1324":14,"1339":1,"1350":4,"1354":4,"1359":122,"1369":6,"1377":1,"1380":1,"1395":30,"1396":119,"1402":3,"1414":1,"1415":34,"1416":2,"1417":33,"1421":1,"1431":2,"1433":3,"1434":1,"1436":195,"1438":10,"1442":1,"1445":2,"1450":1,"1459":1,"1487":5,"1488":3,"1489":2,"1491":1,"1492":2,"1496":7,"1526":3,"1531":1,"1577":2,"1589":1,"1591":5,"1596":2,"1627":2,"1629":1,"1631":2,"1643":1,"1645":2,"1651":2,"1653":1,"1666":1,"1673":1,"1677":1,"1749":1,"1815":1,"1816":1,"1824":2,"1830":1,"1849":1,"1881":2,"1883":2,"1898":3,"1916":2,"1920":1,"1923":1,"1956":2,"1959":1,"1991":1,"2041":1,"2057":3,"2079":1,"2095":1,"2103":2,"2163":2,"2232":1}}],["sil",{"3":{"2075":1,"2150":1}}],["silver",{"3":{"1350":1,"1364":1,"1369":1,"1631":1}}],["silo",{"3":{"1149":1,"1152":1,"1154":1,"1155":1,"1157":2,"2232":1}}],["silenced",{"3":{"982":1,"1309":1,"1417":1,"1436":2}}],["silencesonlythattype",{"3":{"1436":7}}],["silencestherule",{"3":{"1436":5}}],["silences",{"3":{"27":1,"372":1,"1359":1,"1436":9}}],["silence",{"3":{"0":2,"122":1,"200":1,"255":1,"370":1,"399":1,"401":1,"591":1,"821":1,"863":1,"1013":1,"1311":1,"1339":1,"1395":1,"1417":1,"1436":5,"1467":1,"1492":1,"1670":1,"1923":1,"2148":1,"2159":1,"2168":1}}],["silently",{"1":{"1887":1,"1888":1,"1889":1,"1890":1,"1891":1,"1892":1},"3":{"0":15,"6":1,"20":1,"67":1,"68":1,"77":1,"98":1,"109":2,"110":1,"119":1,"121":1,"122":1,"132":1,"135":3,"142":1,"160":1,"170":1,"175":2,"178":1,"180":1,"188":1,"214":1,"216":1,"220":1,"248":1,"302":1,"310":1,"318":2,"335":2,"340":1,"344":1,"350":1,"359":1,"360":1,"361":1,"363":2,"364":1,"370":1,"371":1,"373":1,"387":2,"391":1,"397":2,"399":1,"403":1,"408":1,"413":1,"415":1,"427":1,"428":1,"447":1,"459":1,"461":2,"474":1,"478":1,"484":1,"499":1,"501":2,"509":1,"518":1,"530":1,"534":1,"549":1,"558":1,"573":1,"574":1,"576":2,"583":1,"585":1,"592":1,"608":1,"610":1,"611":1,"625":1,"628":1,"632":1,"634":1,"635":1,"658":1,"667":1,"676":1,"698":2,"699":1,"700":1,"706":1,"709":1,"717":1,"735":1,"741":1,"749":1,"750":1,"758":1,"765":1,"768":1,"775":1,"784":1,"789":1,"819":1,"821":1,"827":1,"837":1,"838":1,"840":1,"847":1,"856":1,"863":1,"889":1,"890":1,"899":2,"907":1,"930":1,"931":1,"953":1,"954":1,"964":1,"972":1,"988":1,"990":1,"991":1,"998":1,"1005":1,"1017":1,"1018":1,"1028":1,"1042":1,"1050":2,"1051":1,"1059":1,"1060":1,"1067":1,"1085":1,"1100":1,"1102":1,"1117":1,"1126":1,"1132":1,"1154":1,"1215":1,"1222":1,"1228":1,"1229":3,"1237":4,"1241":4,"1247":10,"1252":1,"1257":2,"1259":6,"1262":2,"1263":1,"1270":6,"1271":2,"1273":2,"1276":1,"1278":2,"1279":1,"1281":2,"1284":1,"1286":46,"1291":1,"1300":34,"1301":9,"1306":1,"1309":5,"1310":1,"1311":24,"1312":4,"1315":2,"1322":1,"1323":22,"1324":34,"1326":1,"1332":1,"1333":2,"1334":1,"1335":1,"1336":1,"1339":20,"1343":1,"1344":2,"1347":1,"1349":1,"1350":15,"1352":1,"1355":1,"1359":42,"1366":1,"1368":1,"1369":5,"1380":7,"1383":1,"1385":1,"1387":1,"1390":1,"1395":32,"1396":26,"1402":11,"1406":1,"1409":1,"1414":1,"1415":20,"1416":7,"1417":19,"1421":1,"1423":1,"1426":1,"1427":9,"1428":1,"1429":1,"1430":1,"1431":2,"1432":1,"1433":2,"1436":79,"1438":15,"1442":2,"1443":2,"1444":2,"1445":2,"1447":2,"1449":1,"1453":3,"1454":2,"1455":3,"1460":1,"1467":7,"1469":1,"1473":1,"1475":1,"1486":2,"1488":4,"1489":3,"1490":1,"1491":1,"1492":2,"1526":1,"1546":1,"1547":1,"1571":1,"1585":1,"1607":1,"1639":3,"1640":1,"1641":2,"1645":1,"1651":1,"1653":2,"1654":2,"1675":1,"1677":2,"1684":1,"1685":3,"1686":1,"1700":2,"1701":1,"1708":1,"1728":1,"1730":1,"1741":1,"1748":1,"1749":1,"1764":1,"1765":2,"1767":2,"1785":1,"1802":1,"1821":1,"1823":1,"1825":1,"1826":1,"1830":1,"1840":1,"1843":1,"1844":1,"1849":1,"1851":2,"1869":1,"1870":1,"1877":1,"1879":1,"1881":1,"1886":1,"1887":2,"1890":1,"1892":1,"1902":1,"1920":1,"1923":2,"1940":1,"1947":1,"1948":1,"1951":1,"1952":1,"1959":1,"1964":1,"1977":1,"1984":1,"1988":1,"1994":1,"1996":1,"2001":2,"2002":1,"2010":1,"2013":1,"2043":1,"2044":1,"2045":2,"2047":1,"2049":1,"2056":1,"2058":1,"2061":1,"2067":1,"2075":1,"2095":1,"2097":2,"2098":2,"2105":1,"2108":1,"2121":1,"2122":1,"2125":1,"2129":1,"2138":1,"2142":1,"2144":1,"2145":1,"2149":1,"2152":1,"2156":2,"2159":1,"2164":1,"2166":1,"2174":1,"2175":1,"2182":1,"2183":1,"2188":1,"2197":2,"2200":1,"2207":1,"2220":1,"2232":2}}],["silent",{"3":{"0":5,"70":1,"78":1,"122":1,"136":1,"155":1,"193":1,"201":1,"227":1,"230":1,"235":1,"340":1,"342":2,"361":1,"383":1,"401":1,"465":1,"500":1,"518":1,"519":1,"520":1,"538":1,"573":1,"583":1,"585":1,"609":1,"617":1,"642":1,"649":1,"666":1,"692":1,"726":1,"734":1,"740":1,"748":1,"757":1,"765":1,"782":1,"784":1,"790":1,"818":1,"829":1,"831":1,"838":1,"922":1,"924":1,"931":1,"971":1,"990":1,"997":1,"1036":1,"1094":1,"1102":1,"1108":1,"1191":1,"1224":1,"1229":2,"1237":2,"1247":3,"1249":1,"1259":3,"1269":1,"1270":5,"1273":1,"1276":1,"1286":13,"1300":3,"1309":1,"1310":1,"1311":10,"1316":1,"1320":1,"1323":8,"1324":3,"1327":1,"1338":1,"1339":11,"1350":2,"1353":1,"1359":23,"1379":1,"1389":1,"1395":4,"1396":9,"1402":4,"1415":5,"1417":10,"1420":1,"1427":2,"1433":2,"1436":45,"1438":1,"1444":2,"1455":1,"1460":1,"1466":1,"1467":1,"1472":1,"1476":1,"1486":1,"1488":4,"1489":1,"1492":1,"1496":1,"1553":1,"1561":1,"1585":2,"1599":2,"1639":2,"1654":1,"1667":1,"1671":1,"1684":1,"1699":1,"1812":1,"1843":1,"1856":1,"1862":1,"1868":1,"1876":1,"1889":1,"1945":1,"1949":1,"1952":1,"1958":2,"1985":1,"1986":1,"1994":1,"2000":1,"2066":1,"2067":1,"2107":1,"2112":1,"2142":1,"2159":1,"2169":1,"2187":1,"2192":1}}],["sigsegv",{"3":{"1417":1}}],["sigstore",{"3":{"374":1}}],["sighted",{"3":{"1396":1,"1417":6}}],["sight",{"3":{"1359":1,"1380":1,"1396":1,"1417":1,"2050":1}}],["sighting",{"3":{"1237":1,"1350":1}}],["sig",{"3":{"1300":1}}],["sigma",{"3":{"1012":2}}],["signer",{"3":{"1893":1,"1899":1}}],["signedout",{"3":{"905":1,"1290":1,"1982":1}}],["signed",{"3":{"0":5,"68":1,"110":1,"186":1,"188":1,"265":3,"285":1,"351":1,"372":1,"374":1,"384":2,"434":1,"499":2,"501":1,"616":2,"618":1,"689":1,"691":1,"759":1,"790":1,"792":1,"827":1,"853":2,"855":1,"904":4,"905":5,"906":3,"907":3,"1067":1,"1184":1,"1212":2,"1228":1,"1229":1,"1276":1,"1286":5,"1289":2,"1290":1,"1291":1,"1300":26,"1307":1,"1311":4,"1323":1,"1324":30,"1339":2,"1373":1,"1395":5,"1396":5,"1407":1,"1414":2,"1415":12,"1416":2,"1417":4,"1429":1,"1434":1,"1436":14,"1438":1,"1454":1,"1479":1,"1576":1,"1577":2,"1586":1,"1591":1,"1596":1,"1627":1,"1668":1,"1672":2,"1703":1,"1750":1,"1761":2,"1773":1,"1897":1,"1922":1,"1945":1,"1966":1,"1973":2,"1977":1,"1980":1,"1982":6,"1983":1,"1984":2,"1985":1,"2033":1,"2083":1,"2111":1,"2124":1,"2166":1,"2232":1}}],["signoutlocallyasync",{"3":{"1324":4}}],["signouteverywhereasync",{"3":{"1300":3}}],["signoutasync",{"3":{"1300":3,"1324":1,"1436":5,"1671":1}}],["signup",{"3":{"283":1,"1999":1,"2001":1}}],["signups",{"3":{"282":1}}],["significance",{"3":{"1631":3}}],["significant",{"3":{"46":1,"1537":1,"1552":1,"1571":2}}],["signinwithbrokerasync",{"3":{"1417":1}}],["signinasync",{"3":{"1417":5}}],["signinscheme",{"3":{"1311":1}}],["signingkey",{"3":{"1339":2,"1442":2}}],["signingcredentials",{"3":{"1300":2,"1436":1}}],["signingalgorithm=rs256",{"3":{"1436":1}}],["signingalgorithm",{"2":{"31":1,"1288":1,"1895":1},"3":{"31":2,"1212":1,"1288":1,"1300":4,"1467":2,"1641":1,"1895":1}}],["signing",{"0":{"1288":1},"2":{"31":1,"418":1,"1481":1},"3":{"0":4,"30":1,"31":3,"32":1,"349":1,"353":1,"418":2,"420":1,"423":2,"424":1,"428":1,"499":1,"501":1,"514":1,"598":1,"599":2,"674":1,"788":1,"789":2,"790":2,"791":1,"792":1,"793":2,"794":1,"904":1,"905":1,"906":1,"963":1,"1212":1,"1290":2,"1300":15,"1311":4,"1323":1,"1324":2,"1335":1,"1339":1,"1415":2,"1416":1,"1429":1,"1433":1,"1436":3,"1442":1,"1455":1,"1456":1,"1466":1,"1467":2,"1472":2,"1481":5,"1488":1,"1490":1,"1576":1,"1589":2,"1591":1,"1595":1,"1596":2,"1597":1,"1790":1,"1893":2,"1894":1,"1895":1,"1899":1,"1982":1,"1984":1,"2021":2,"2166":4,"2167":1,"2230":1,"2232":1}}],["signs",{"3":{"0":2,"30":1,"31":1,"350":3,"353":1,"599":2,"905":3,"907":2,"909":1,"1212":1,"1288":2,"1290":1,"1300":9,"1311":3,"1324":8,"1326":1,"1328":1,"1348":1,"1350":1,"1359":1,"1380":1,"1396":2,"1406":2,"1415":2,"1436":2,"1441":1,"1449":1,"1455":1,"1466":1,"1467":1,"1481":1,"1667":1,"1893":1,"1894":1,"1895":2,"1975":2,"1978":1,"2055":1}}],["sign",{"0":{"1293":1},"3":{"0":8,"186":1,"243":2,"258":2,"261":1,"265":1,"281":1,"349":1,"350":4,"352":1,"353":2,"433":2,"434":3,"498":1,"512":1,"513":1,"618":1,"648":1,"650":1,"664":1,"665":1,"666":1,"674":1,"743":1,"853":1,"903":1,"905":8,"906":3,"907":1,"909":1,"1057":1,"1058":2,"1059":5,"1060":1,"1061":1,"1063":1,"1184":1,"1185":1,"1188":1,"1237":1,"1283":1,"1286":2,"1288":1,"1290":2,"1293":4,"1295":1,"1300":39,"1311":5,"1323":7,"1324":51,"1335":1,"1336":1,"1339":4,"1359":1,"1406":1,"1407":1,"1408":1,"1409":1,"1415":8,"1417":16,"1436":33,"1438":3,"1442":1,"1455":1,"1456":1,"1466":1,"1488":3,"1511":1,"1577":1,"1627":1,"1630":1,"1631":1,"1637":1,"1641":3,"1666":1,"1668":1,"1670":1,"1671":1,"1742":1,"1765":2,"1897":1,"1936":2,"1970":1,"1972":2,"1973":4,"1975":1,"1976":1,"1977":3,"1978":1,"1979":2,"1980":1,"1982":3,"1983":2,"1984":1,"1985":1,"2012":1,"2077":1,"2144":1,"2194":3,"2195":1,"2196":1,"2198":1,"2202":1,"2203":3,"2220":1,"2232":1}}],["signalling",{"3":{"1283":1,"1286":3}}],["signalled",{"3":{"743":1,"782":1,"1259":1,"1275":1,"1311":1,"1359":1,"1415":2,"1436":1,"1446":1,"1467":1,"1651":1,"1686":1,"2185":1,"2232":1}}],["signalingentry",{"3":{"1199":2,"1207":1}}],["signals",{"3":{"0":2,"18":1,"19":1,"396":1,"406":1,"608":1,"609":1,"767":1,"827":1,"1040":2,"1042":1,"1045":1,"1247":1,"1254":2,"1257":1,"1259":7,"1270":1,"1274":2,"1286":12,"1311":1,"1324":1,"1339":2,"1350":1,"1359":2,"1396":1,"1402":1,"1415":1,"1417":2,"1427":1,"1433":1,"1436":5,"1455":1,"1467":1,"1474":1,"1488":1,"1526":1,"1568":1,"1577":1,"2118":1,"2154":2,"2156":1,"2158":2,"2159":1,"2165":2}}],["signal",{"0":{"2158":1},"3":{"0":6,"17":1,"18":1,"19":1,"20":1,"22":1,"72":1,"77":1,"79":1,"80":1,"108":1,"125":1,"147":1,"159":1,"216":1,"237":1,"255":1,"343":1,"350":1,"376":1,"395":1,"399":1,"400":1,"402":1,"403":1,"406":1,"407":2,"518":1,"527":1,"530":1,"549":1,"585":1,"607":1,"612":2,"627":2,"628":1,"702":1,"726":1,"727":1,"735":1,"740":1,"741":1,"742":1,"758":1,"789":1,"830":1,"840":1,"844":1,"845":2,"905":2,"906":1,"914":1,"916":1,"948":1,"990":1,"1018":1,"1044":3,"1045":1,"1068":2,"1069":1,"1074":1,"1100":1,"1126":1,"1188":1,"1247":2,"1254":2,"1256":1,"1257":1,"1259":28,"1270":5,"1274":1,"1275":4,"1286":20,"1290":1,"1300":11,"1301":1,"1309":1,"1311":4,"1317":1,"1322":1,"1323":3,"1324":8,"1339":6,"1347":1,"1350":1,"1355":1,"1359":13,"1369":2,"1380":2,"1390":1,"1395":8,"1396":11,"1402":6,"1415":1,"1417":10,"1426":1,"1433":1,"1436":13,"1438":1,"1441":2,"1443":4,"1447":1,"1450":1,"1455":2,"1456":4,"1459":1,"1465":1,"1467":11,"1476":2,"1486":2,"1488":2,"1491":1,"1492":2,"1534":1,"1536":1,"1541":1,"1630":1,"1641":1,"1665":1,"1666":1,"1675":1,"1677":2,"1724":1,"1735":1,"1769":2,"1841":1,"1888":1,"1889":1,"1891":1,"1919":1,"1923":1,"1982":1,"1985":1,"1988":1,"2036":1,"2055":1,"2056":1,"2136":1,"2152":1,"2155":3,"2157":1,"2158":1,"2160":1,"2192":3,"2232":1}}],["signalrconfigured",{"2":{"1935":1},"3":{"1935":1}}],["signalrextensions",{"3":{"225":1,"749":1,"1150":1,"1199":1,"1203":1,"1207":2,"1208":1,"1309":1,"1311":7}}],["signalrlivechannelpublishertests",{"3":{"1199":1,"1207":2}}],["signalrlivechannelpublisher",{"2":{"225":1,"380":1,"1303":1,"1307":1,"1400":1,"1944":1,"1946":1},"3":{"225":2,"380":1,"1150":1,"1199":3,"1203":1,"1207":2,"1303":1,"1307":4,"1309":13,"1323":1,"1400":1,"1496":1,"1944":1,"1946":2}}],["signalrpushnotificationsendertests",{"3":{"1199":1,"1207":2}}],["signalrpushnotificationsenderadditionaltests",{"3":{"1199":1,"1207":2}}],["signalrpushnotificationsender",{"2":{"225":1,"1303":1,"1307":1,"1932":1,"1935":1},"3":{"225":2,"1199":4,"1203":1,"1207":2,"1300":3,"1303":1,"1307":2,"1309":16,"1323":1,"1932":1,"1935":2}}],["signalr",{"0":{"1307":1},"1":{"222":1,"223":1,"224":1,"225":1,"226":1,"227":1,"228":1,"229":1,"230":1,"231":1,"1942":1,"1943":1,"1944":1,"1945":1,"1946":1,"1947":1,"1948":1,"1949":1,"1950":1},"3":{"0":8,"25":1,"27":1,"31":1,"55":1,"61":1,"92":2,"96":1,"98":1,"100":1,"101":1,"135":1,"216":1,"222":1,"225":7,"226":1,"227":1,"228":1,"229":2,"230":2,"379":1,"380":2,"397":1,"433":1,"434":2,"507":1,"508":1,"513":1,"555":1,"556":1,"608":1,"749":1,"759":1,"774":3,"827":1,"832":1,"837":1,"838":2,"1124":1,"1149":1,"1150":1,"1192":1,"1202":2,"1203":2,"1212":1,"1213":2,"1247":1,"1285":1,"1286":4,"1296":1,"1300":8,"1302":4,"1303":2,"1304":1,"1307":1,"1308":1,"1309":43,"1311":4,"1312":4,"1320":3,"1323":8,"1324":20,"1331":1,"1338":1,"1339":5,"1397":1,"1400":1,"1402":10,"1417":4,"1433":2,"1436":10,"1437":1,"1438":8,"1441":3,"1443":1,"1447":4,"1467":10,"1474":1,"1488":1,"1489":1,"1496":1,"1512":2,"1599":1,"1656":1,"1658":1,"1666":1,"1667":1,"1669":1,"1705":1,"1779":1,"1932":1,"1933":6,"1935":5,"1936":2,"1940":2,"1941":2,"1942":3,"1944":1,"1945":1,"1946":1,"1950":3,"2024":1,"2025":1,"2110":1,"2150":2,"2152":1,"2158":1,"2208":1,"2232":1}}],["signatureverification",{"3":{"1770":1}}],["signatureverificationfailed",{"2":{"790":1},"3":{"790":1}}],["signaturemissing",{"3":{"790":1}}],["signatures",{"3":{"0":2,"31":1,"135":2,"265":1,"471":1,"676":1,"798":1,"799":1,"1115":1,"1300":3,"1301":1,"1324":2,"1349":1,"1395":1,"1396":2,"1416":1,"1431":1,"1436":3,"1455":1,"1489":1,"1496":1,"1585":1,"1671":1,"2083":1,"2232":1}}],["signature",{"3":{"0":4,"29":1,"31":1,"34":1,"108":1,"110":1,"135":5,"207":1,"208":1,"210":1,"372":1,"458":2,"498":1,"499":1,"509":1,"757":1,"790":7,"792":1,"793":2,"799":2,"803":1,"879":1,"903":1,"905":1,"907":1,"908":1,"1034":1,"1049":1,"1059":1,"1116":1,"1133":1,"1134":1,"1184":1,"1212":1,"1218":1,"1229":1,"1241":1,"1246":1,"1247":4,"1259":1,"1270":6,"1286":20,"1289":1,"1290":1,"1300":22,"1301":2,"1309":2,"1310":4,"1311":3,"1323":4,"1324":14,"1339":2,"1359":21,"1389":1,"1395":6,"1396":11,"1415":4,"1417":5,"1431":1,"1436":14,"1438":2,"1441":1,"1489":1,"1534":1,"1585":1,"1685":1,"1702":2,"1734":1,"1749":1,"1765":1,"1770":2,"1803":2,"1804":1,"1807":2,"1810":2,"1897":1,"1953":1,"1954":1,"1968":2,"1972":1,"1980":1,"1985":1,"1995":1,"2066":1,"2126":2,"2166":5,"2168":1,"2169":2,"2197":1,"2232":2}}],["sizebytes",{"3":{"1350":5,"1369":1,"1395":2}}],["size=10",{"3":{"1270":1}}],["sizes",{"3":{"237":1,"312":1,"359":1,"1018":1,"1286":2,"1300":1,"1309":1,"1324":2,"1359":1,"1369":1,"1396":2,"1398":1,"1415":1,"1436":1,"1467":1,"1559":2,"1661":1,"1678":1,"1711":1,"1748":1,"1777":1,"1825":1,"2142":1}}],["sized",{"3":{"236":1,"719":1,"996":1,"1020":1,"1150":1,"1291":1,"1300":2,"1311":2,"1324":1,"1350":4,"1359":2,"1369":3,"1380":1,"1395":3,"1396":2,"1402":2,"1415":1,"1436":1,"1438":1,"1467":5,"1478":1,"1486":1,"1523":1,"1525":1,"1526":3,"1549":1,"1554":1,"1555":1,"1560":1,"1572":2,"1591":2,"1670":1,"1909":1,"2033":1,"2037":1}}],["size",{"0":{"1198":1},"2":{"1986":1},"3":{"137":1,"140":1,"311":1,"330":3,"331":1,"335":2,"337":1,"360":2,"434":1,"438":1,"558":2,"609":1,"673":1,"692":1,"740":1,"741":2,"766":1,"788":1,"872":1,"881":1,"915":1,"1116":2,"1118":1,"1187":1,"1198":1,"1201":1,"1228":1,"1229":2,"1242":1,"1243":2,"1244":2,"1245":1,"1247":13,"1259":1,"1286":7,"1300":5,"1309":5,"1311":7,"1320":1,"1323":11,"1324":23,"1344":1,"1350":11,"1359":11,"1380":1,"1387":2,"1389":2,"1395":33,"1396":16,"1398":1,"1402":1,"1406":1,"1415":9,"1417":1,"1422":2,"1436":1,"1438":3,"1467":1,"1479":2,"1496":1,"1526":3,"1529":1,"1560":1,"1577":1,"1589":1,"1596":1,"1599":2,"1631":3,"1639":1,"1640":2,"1662":1,"1669":1,"1673":1,"1678":2,"1714":2,"1764":1,"1765":1,"1777":3,"1797":1,"1817":1,"1845":1,"1911":1,"1986":2,"1987":1,"1988":2,"1991":1,"2050":1,"2072":1,"2126":2,"2128":2,"2142":1,"2143":2,"2159":1,"2167":3,"2174":1}}],["sizing",{"3":{"0":1,"682":1,"867":1,"875":1,"1300":1,"1309":1,"1324":1,"1350":1,"1369":2,"1390":1,"1396":3,"1455":1,"1465":1,"1467":3,"1469":2,"1526":1,"1534":1,"1568":1,"1574":1,"1575":3,"1577":2,"1585":1,"1602":1,"1675":1,"1676":1,"1799":1}}],["simulated",{"3":{"1436":3,"1671":1,"1707":2}}],["simulate",{"3":{"1324":1,"1417":1,"1436":1,"1707":1}}],["simultaneous",{"3":{"0":1,"159":1,"856":1,"1124":1,"1311":2,"1323":1,"1324":2,"1339":1,"1357":1,"1359":2,"1396":1,"1416":1,"1667":1,"1982":1,"2000":1,"2169":1}}],["simultaneously",{"3":{"0":1,"40":1,"501":1,"690":1,"907":1,"1323":2,"1339":2,"1380":1,"1396":4,"1436":2,"1443":2,"1638":1,"1670":1}}],["similarityscore",{"3":{"1350":1}}],["similarity",{"3":{"1350":4,"1359":7,"1376":2,"1380":1,"1390":1,"1395":2,"1632":1,"1640":1}}],["similarly",{"3":{"1350":1,"1638":1,"1639":1}}],["similarsessionpair",{"3":{"1199":4,"1203":1,"1207":1,"1347":1,"1350":2,"1359":2}}],["similar",{"3":{"158":1,"725":1,"827":1,"973":1,"1229":1,"1350":5,"1369":1,"1373":1,"1415":1,"1467":1,"1640":1,"1834":1}}],["simplify",{"3":{"1631":1}}],["simplifying",{"3":{"1395":1,"1641":1}}],["simplification",{"3":{"1339":1,"1402":1,"1417":1}}],["simplifies",{"3":{"310":1,"1417":1,"1638":2}}],["simplename",{"3":{"1436":3}}],["simpletypename",{"3":{"1436":1}}],["simplest",{"3":{"1286":2,"1323":1,"1353":1,"1359":5,"1380":1,"1383":1,"1395":2,"1396":1,"1436":5,"1445":1,"1617":1,"1681":1}}],["simple",{"3":{"136":1,"160":1,"161":1,"196":1,"341":1,"372":1,"699":1,"1074":1,"1117":1,"1196":2,"1247":1,"1270":1,"1286":1,"1300":1,"1309":1,"1311":3,"1323":6,"1324":4,"1339":1,"1341":1,"1357":1,"1359":3,"1369":1,"1380":1,"1395":4,"1396":7,"1402":1,"1415":4,"1416":1,"1436":9,"1638":2,"1641":1,"1853":1,"1875":1,"1958":1,"2079":1,"2238":1}}],["simpler",{"3":{"48":1,"811":1,"871":1,"1034":1,"1323":1,"1324":4,"1350":2,"1359":1,"1395":1,"1396":2,"1436":1,"1650":1,"1658":1,"1703":1,"1705":1,"1848":1,"2063":1}}],["simply",{"3":{"22":1,"24":1,"79":1,"117":1,"135":1,"137":1,"213":1,"224":1,"225":1,"235":1,"295":1,"342":1,"343":1,"399":1,"415":1,"464":1,"539":1,"585":1,"599":1,"676":1,"681":1,"709":1,"717":1,"751":1,"840":1,"907":1,"933":1,"963":1,"1042":1,"1059":1,"1084":1,"1232":1,"1234":1,"1237":2,"1239":1,"1241":1,"1243":1,"1247":5,"1251":1,"1254":1,"1256":1,"1259":5,"1270":8,"1271":1,"1276":1,"1286":12,"1297":1,"1300":10,"1301":6,"1302":1,"1309":8,"1310":1,"1311":14,"1316":1,"1323":7,"1324":15,"1339":6,"1343":1,"1350":6,"1353":1,"1359":19,"1369":1,"1375":1,"1380":3,"1389":1,"1395":15,"1396":18,"1401":1,"1402":4,"1415":8,"1416":5,"1417":22,"1427":1,"1431":1,"1436":18,"1438":2,"1449":1,"1455":1,"1467":2,"1488":1,"1640":1,"1641":1,"1810":1,"1841":1,"1851":1,"1863":1,"1870":1,"1880":1,"1885":1,"1889":1,"1916":1,"1946":1,"1948":1,"1977":1,"2001":1,"2006":1,"2010":1,"2066":1,"2092":1,"2097":1,"2117":1,"2139":1,"2156":1,"2188":1,"2196":1}}],["sid",{"2":{"903":1,"906":1,"909":1},"3":{"0":4,"903":1,"905":5,"906":2,"907":3,"909":1,"1059":1,"1289":1,"1290":3,"1296":2,"1300":23,"1311":3,"1324":1,"1438":2,"1667":1}}],["sideways",{"3":{"1436":2,"2128":1}}],["sidekick",{"3":{"1313":1}}],["sided",{"3":{"1300":1,"1339":1,"1369":1,"1436":2}}],["sidebar",{"3":{"881":1,"1324":11,"1409":1,"1414":2,"1415":3,"1438":1,"1712":1,"1713":1,"1750":1,"1773":1}}],["sides",{"3":{"122":1,"180":1,"243":1,"286":1,"556":1,"631":1,"640":1,"720":1,"827":1,"975":1,"1050":1,"1212":1,"1236":1,"1237":3,"1241":1,"1247":2,"1270":4,"1271":1,"1276":1,"1277":1,"1286":5,"1289":1,"1300":5,"1309":2,"1311":2,"1312":2,"1323":1,"1324":5,"1339":1,"1350":1,"1359":5,"1380":1,"1396":3,"1415":2,"1436":13,"1488":1,"1577":1,"1641":1,"1651":1,"1839":1,"1979":1,"2044":1,"2096":1,"2136":1,"2157":2}}],["sidesteps",{"3":{"92":1,"1270":1,"1286":2,"1300":1,"1323":1,"1359":1,"1396":2,"1402":1,"1415":1,"1436":1,"1467":1,"1872":1}}],["side",{"0":{"141":1,"926":1,"1227":1,"1265":1,"1306":1,"1634":1,"1841":1,"2018":1,"2019":1},"1":{"192":1,"193":1,"194":1,"195":1,"196":1,"197":1,"198":1,"199":1,"200":1,"201":1,"202":1,"203":1,"504":1,"505":1,"506":1,"507":1,"508":1,"509":1,"510":1,"511":1,"512":1,"513":1,"514":1,"878":1,"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"894":1,"895":1,"896":1,"897":1,"898":1,"899":1,"900":1,"901":1,"919":1,"920":1,"921":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1},"3":{"0":27,"15":1,"21":1,"24":3,"31":2,"39":1,"53":2,"66":1,"72":1,"73":2,"99":1,"100":1,"103":1,"109":4,"113":2,"117":1,"121":1,"122":3,"124":1,"126":1,"128":1,"135":1,"139":2,"141":2,"145":2,"147":2,"150":3,"166":2,"168":2,"175":1,"180":1,"192":1,"193":1,"194":1,"199":1,"201":1,"207":1,"208":1,"236":1,"239":1,"245":1,"248":1,"255":2,"258":1,"264":1,"265":3,"267":1,"273":2,"295":1,"298":1,"330":2,"335":1,"337":1,"342":1,"343":1,"346":1,"350":3,"351":1,"353":3,"360":1,"363":1,"379":1,"387":1,"388":1,"389":1,"391":1,"399":1,"400":1,"401":3,"408":1,"415":1,"418":1,"419":1,"420":1,"433":1,"434":1,"436":1,"451":1,"470":1,"490":1,"499":1,"501":2,"504":1,"507":3,"508":1,"509":1,"518":1,"536":1,"537":1,"545":1,"549":1,"551":1,"557":1,"560":1,"591":2,"599":2,"601":1,"612":1,"629":1,"633":2,"635":1,"640":1,"645":1,"648":1,"649":2,"650":2,"653":1,"657":1,"665":2,"668":2,"674":1,"684":1,"690":3,"692":3,"693":2,"696":1,"697":1,"698":2,"701":1,"702":2,"725":1,"741":1,"756":1,"758":1,"770":1,"785":1,"786":1,"790":2,"795":1,"818":2,"827":6,"829":2,"830":1,"833":1,"836":2,"839":2,"844":1,"845":1,"846":1,"848":2,"860":3,"861":1,"865":3,"870":1,"878":1,"880":1,"881":6,"883":2,"885":2,"894":1,"897":7,"898":2,"899":1,"901":1,"904":1,"905":1,"919":1,"920":1,"921":1,"922":2,"923":1,"924":1,"925":1,"926":2,"930":1,"935":1,"984":1,"995":1,"1012":3,"1017":1,"1018":3,"1020":2,"1021":2,"1028":1,"1041":1,"1044":2,"1066":1,"1067":1,"1068":1,"1085":1,"1093":1,"1094":2,"1100":1,"1115":1,"1118":1,"1126":1,"1160":1,"1182":1,"1184":4,"1185":1,"1187":2,"1202":1,"1203":1,"1212":14,"1219":1,"1220":2,"1222":2,"1225":2,"1227":2,"1228":1,"1229":9,"1231":1,"1233":1,"1237":11,"1238":2,"1240":1,"1242":2,"1243":2,"1245":2,"1247":28,"1248":1,"1249":2,"1252":3,"1253":1,"1258":3,"1259":24,"1260":4,"1261":1,"1262":1,"1263":6,"1265":2,"1268":1,"1269":3,"1270":71,"1271":7,"1274":1,"1278":3,"1282":1,"1284":3,"1285":3,"1286":45,"1287":1,"1292":1,"1293":1,"1298":1,"1300":62,"1301":7,"1304":1,"1305":2,"1306":2,"1308":2,"1309":29,"1310":6,"1311":38,"1312":22,"1313":1,"1317":2,"1318":4,"1322":1,"1323":36,"1324":135,"1325":6,"1326":1,"1330":2,"1337":3,"1339":37,"1342":2,"1344":1,"1349":1,"1350":47,"1352":1,"1353":1,"1355":5,"1357":2,"1359":136,"1362":1,"1366":2,"1368":2,"1369":12,"1370":1,"1374":2,"1375":1,"1378":1,"1380":21,"1383":2,"1385":1,"1387":1,"1388":3,"1389":1,"1390":1,"1391":1,"1392":4,"1393":1,"1395":85,"1396":127,"1399":2,"1400":1,"1401":1,"1402":38,"1405":1,"1406":1,"1413":1,"1414":1,"1415":50,"1416":19,"1417":35,"1422":1,"1424":2,"1427":6,"1429":1,"1430":1,"1433":2,"1435":5,"1436":88,"1437":1,"1438":17,"1441":4,"1443":3,"1444":1,"1445":1,"1447":5,"1449":1,"1451":1,"1455":3,"1456":1,"1461":2,"1467":8,"1475":2,"1476":3,"1487":1,"1488":4,"1489":1,"1492":4,"1494":1,"1496":7,"1526":13,"1531":1,"1532":1,"1534":4,"1535":2,"1543":1,"1545":1,"1547":2,"1553":1,"1560":3,"1561":2,"1563":1,"1566":1,"1572":4,"1575":3,"1576":1,"1577":9,"1582":2,"1585":2,"1586":1,"1589":1,"1591":5,"1596":4,"1597":1,"1598":1,"1599":3,"1627":5,"1630":1,"1631":2,"1632":8,"1634":1,"1635":1,"1638":1,"1639":3,"1640":2,"1641":4,"1647":1,"1651":4,"1653":1,"1661":2,"1665":3,"1666":3,"1667":3,"1668":3,"1669":2,"1670":2,"1675":2,"1677":1,"1685":1,"1687":1,"1702":1,"1720":1,"1721":1,"1726":2,"1748":1,"1749":1,"1750":2,"1757":1,"1759":2,"1761":1,"1765":1,"1767":2,"1773":1,"1781":1,"1800":1,"1805":5,"1808":1,"1810":1,"1814":1,"1815":2,"1820":1,"1821":2,"1843":2,"1845":1,"1853":1,"1854":1,"1856":2,"1883":4,"1902":2,"1903":1,"1906":1,"1910":2,"1911":2,"1912":1,"1913":1,"1916":2,"1919":3,"1920":1,"1924":1,"1927":2,"1928":1,"1929":1,"1935":1,"1936":1,"1942":1,"1944":1,"1945":1,"1946":1,"1952":2,"1953":2,"1956":4,"1957":2,"1959":1,"1966":2,"1968":3,"1970":1,"1972":2,"1973":1,"1975":2,"1977":1,"1979":2,"1982":2,"1983":1,"1984":1,"1985":2,"1988":2,"2002":1,"2004":2,"2006":3,"2018":1,"2019":1,"2028":2,"2030":3,"2036":1,"2056":1,"2064":1,"2070":1,"2072":2,"2073":1,"2074":1,"2077":2,"2078":2,"2079":1,"2083":1,"2085":1,"2086":1,"2127":1,"2136":1,"2138":1,"2141":1,"2156":2,"2159":2,"2162":1,"2165":3,"2166":1,"2167":1,"2171":1,"2174":1,"2179":1,"2182":1,"2187":1,"2189":1,"2192":2,"2193":1,"2198":1,"2230":1,"2232":5}}],["siblings",{"3":{"509":1,"511":1,"703":1,"773":1,"914":1,"980":1,"1212":1,"1234":1,"1247":2,"1258":1,"1271":1,"1281":1,"1282":1,"1286":3,"1300":2,"1301":1,"1311":3,"1324":5,"1326":2,"1330":1,"1336":1,"1339":5,"1350":11,"1353":3,"1359":28,"1369":1,"1371":1,"1380":2,"1382":1,"1395":11,"1396":9,"1401":1,"1402":4,"1404":1,"1411":1,"1415":4,"1416":1,"1417":30,"1431":2,"1436":13,"1441":3,"1467":1,"1489":1,"1496":1,"1577":1,"1591":2,"2165":1}}],["sibling",{"0":{"1948":1},"3":{"0":4,"194":1,"247":1,"255":1,"256":1,"258":1,"265":4,"291":1,"406":1,"443":1,"538":1,"568":1,"614":1,"630":1,"645":1,"657":1,"694":1,"711":1,"725":1,"741":1,"750":1,"753":1,"765":1,"770":1,"782":1,"852":1,"854":2,"865":1,"881":1,"905":1,"922":1,"926":1,"935":1,"963":1,"973":1,"975":2,"976":2,"1120":1,"1133":1,"1136":1,"1184":1,"1193":1,"1212":2,"1223":1,"1228":1,"1229":2,"1237":5,"1247":1,"1252":2,"1256":1,"1259":6,"1260":1,"1267":1,"1270":11,"1271":2,"1278":1,"1282":1,"1285":1,"1286":16,"1295":1,"1296":1,"1300":11,"1309":4,"1310":4,"1311":17,"1316":1,"1323":10,"1324":27,"1339":13,"1343":1,"1344":1,"1350":13,"1352":1,"1353":1,"1356":1,"1359":55,"1369":4,"1380":14,"1395":13,"1396":19,"1402":21,"1407":2,"1415":18,"1416":3,"1417":8,"1427":1,"1431":1,"1436":55,"1438":2,"1456":1,"1475":1,"1486":1,"1489":1,"1491":1,"1492":1,"1496":8,"1497":2,"1526":2,"1577":2,"1593":1,"1653":2,"1654":1,"1749":1,"1950":1,"2043":2,"2067":1,"2085":1,"2092":1,"2151":2,"2183":1}}],["situations",{"3":{"1417":1,"1982":1,"2167":1}}],["situation",{"3":{"1300":1,"1324":1,"1380":1,"1396":2,"1415":1,"1436":1,"1908":1}}],["sitting",{"3":{"178":1,"198":1,"235":1,"257":1,"486":1,"536":1,"545":1,"572":1,"826":1,"1004":1,"1050":1,"1117":1,"1259":1,"1270":1,"1300":3,"1302":1,"1323":2,"1324":4,"1339":1,"1359":1,"1380":3,"1395":1,"1396":3,"1415":1,"1416":2,"1417":1,"1436":8,"1443":2,"1463":1,"1465":1,"1467":2,"1749":1,"1765":1,"1843":1,"1902":1,"1903":1,"1966":1,"1973":1,"1980":1,"2054":1,"2062":1,"2065":1,"2067":1,"2075":1,"2149":1,"2170":1}}],["sitemap",{"3":{"609":1,"1300":1,"1467":3,"2158":3}}],["sites",{"3":{"0":9,"93":1,"94":1,"95":1,"104":1,"111":1,"128":3,"223":1,"230":2,"254":1,"257":1,"260":1,"261":1,"281":1,"310":1,"314":1,"323":1,"326":1,"330":1,"418":1,"429":1,"453":1,"454":1,"483":2,"484":2,"486":2,"487":2,"488":2,"489":2,"490":2,"491":2,"492":2,"493":2,"494":3,"516":1,"522":3,"523":2,"545":2,"549":1,"550":2,"552":3,"647":1,"668":1,"669":1,"688":1,"693":1,"696":4,"698":1,"700":1,"703":1,"752":1,"782":1,"819":1,"837":1,"884":1,"897":2,"899":1,"900":2,"994":2,"996":1,"999":2,"1036":1,"1100":1,"1127":1,"1132":1,"1134":1,"1175":2,"1176":1,"1212":1,"1237":3,"1240":1,"1247":5,"1250":1,"1262":1,"1267":1,"1270":2,"1271":4,"1276":1,"1286":20,"1300":19,"1301":6,"1310":1,"1311":6,"1312":1,"1318":2,"1323":5,"1324":12,"1339":1,"1350":7,"1359":8,"1369":1,"1387":1,"1395":4,"1396":13,"1402":1,"1415":1,"1417":4,"1427":1,"1436":22,"1438":1,"1442":1,"1496":1,"1510":1,"1535":1,"1810":1,"1815":1,"1851":1,"1882":1,"1995":3,"2063":1,"2156":1,"2157":1}}],["site",{"3":{"0":8,"67":1,"109":3,"111":1,"113":1,"118":1,"121":1,"184":1,"207":2,"209":1,"217":1,"230":2,"279":2,"285":1,"303":1,"308":1,"310":3,"318":2,"325":1,"336":1,"353":1,"386":1,"387":1,"395":3,"397":1,"419":1,"420":1,"424":1,"446":3,"472":1,"480":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"495":1,"516":1,"517":2,"523":1,"524":2,"545":3,"549":1,"650":1,"655":1,"657":2,"663":1,"665":2,"667":1,"669":1,"674":2,"696":1,"700":1,"733":1,"777":1,"782":1,"799":2,"800":1,"803":1,"819":2,"827":2,"832":1,"838":1,"846":1,"861":2,"889":1,"896":1,"900":1,"926":1,"934":1,"946":1,"948":1,"950":1,"952":1,"955":1,"964":2,"980":1,"981":1,"990":1,"1050":1,"1051":1,"1052":1,"1100":1,"1102":1,"1116":1,"1123":1,"1124":1,"1126":1,"1133":2,"1136":1,"1176":1,"1179":1,"1184":1,"1187":1,"1212":1,"1221":1,"1229":7,"1236":1,"1237":7,"1241":1,"1246":1,"1247":10,"1259":3,"1270":10,"1271":5,"1280":1,"1286":37,"1297":1,"1298":2,"1300":23,"1301":4,"1309":8,"1310":5,"1311":14,"1312":3,"1323":14,"1324":40,"1332":1,"1339":12,"1344":1,"1350":9,"1359":30,"1366":1,"1369":4,"1380":1,"1392":1,"1395":15,"1396":28,"1402":2,"1415":11,"1416":9,"1417":12,"1427":4,"1436":49,"1438":3,"1444":1,"1447":3,"1448":1,"1467":3,"1534":2,"1566":1,"1620":1,"1659":1,"1666":1,"1667":1,"1673":1,"1675":1,"1701":1,"1756":1,"1793":1,"1802":1,"1806":1,"1807":1,"1810":1,"1813":1,"1815":2,"1817":1,"1821":1,"1826":1,"1827":2,"1843":1,"1846":1,"1853":1,"1868":1,"1877":1,"1886":2,"1892":2,"1899":1,"1905":1,"1915":2,"1919":1,"1930":1,"1939":1,"1950":1,"1965":1,"1969":5,"1971":1,"1972":2,"1975":1,"1979":1,"1985":1,"1991":1,"1997":1,"2000":1,"2002":1,"2012":1,"2024":1,"2030":2,"2063":1,"2069":1,"2086":1,"2108":1,"2115":1,"2121":1,"2124":1,"2128":1,"2133":1,"2149":1,"2150":1,"2153":1,"2160":1,"2169":1,"2175":1,"2179":1,"2180":1,"2189":1,"2193":1,"2203":1,"2211":1,"2233":1}}],["sit",{"0":{"1303":1},"3":{"0":6,"25":1,"27":1,"64":1,"71":1,"80":1,"95":1,"147":1,"180":1,"182":1,"201":1,"242":1,"281":1,"287":1,"344":1,"423":1,"455":1,"459":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":2,"494":1,"495":2,"509":1,"511":1,"566":1,"572":1,"599":1,"609":1,"626":1,"627":1,"632":1,"633":1,"649":1,"674":2,"676":2,"751":1,"766":1,"798":1,"814":1,"815":1,"823":1,"832":1,"834":1,"862":1,"926":1,"954":1,"968":1,"980":1,"982":1,"992":1,"1051":1,"1055":1,"1067":1,"1117":1,"1144":1,"1168":1,"1192":1,"1211":1,"1212":2,"1213":1,"1220":1,"1229":1,"1230":1,"1237":1,"1249":1,"1258":1,"1259":1,"1260":1,"1269":1,"1270":3,"1271":3,"1275":1,"1278":1,"1284":1,"1285":1,"1286":6,"1300":4,"1311":7,"1316":1,"1317":2,"1322":1,"1323":4,"1324":11,"1330":1,"1332":1,"1339":8,"1347":1,"1350":4,"1354":1,"1359":16,"1371":1,"1372":1,"1378":1,"1379":1,"1380":2,"1389":1,"1391":1,"1395":5,"1396":9,"1402":2,"1403":1,"1414":1,"1415":6,"1416":1,"1417":3,"1421":2,"1431":1,"1436":20,"1438":2,"1441":1,"1445":1,"1447":2,"1453":1,"1459":1,"1467":2,"1489":3,"1490":2,"1496":2,"1498":2,"1523":2,"1531":1,"1551":1,"1574":3,"1577":10,"1581":1,"1582":3,"1583":1,"1584":1,"1591":6,"1596":2,"1597":1,"1599":2,"1627":2,"1630":1,"1640":1,"1767":2,"1781":2,"1784":1,"1810":1,"1851":1,"1855":1,"1856":1,"1864":1,"1883":1,"1898":1,"1904":1,"1927":1,"1956":1,"1975":1,"2000":1,"2010":1,"2053":1,"2111":1,"2172":1,"2196":1,"2198":1,"2203":1,"2208":1,"2232":1}}],["sits",{"0":{"1237":1,"1286":1,"1309":1},"3":{"0":5,"109":1,"122":3,"123":1,"135":1,"141":1,"149":1,"150":1,"152":1,"162":1,"178":1,"182":1,"218":1,"230":1,"237":1,"255":2,"259":1,"279":1,"287":1,"291":1,"295":1,"310":1,"317":1,"346":1,"349":1,"355":1,"366":1,"373":1,"380":1,"395":1,"407":1,"411":1,"416":1,"423":2,"427":1,"428":1,"457":1,"459":1,"462":1,"464":1,"491":1,"492":3,"493":3,"494":1,"495":1,"524":1,"536":1,"538":1,"544":1,"560":1,"564":1,"567":1,"575":1,"577":1,"591":1,"609":2,"619":1,"631":1,"633":2,"638":1,"665":2,"682":1,"692":1,"701":1,"706":1,"724":1,"736":1,"748":1,"749":3,"766":1,"768":1,"784":1,"803":1,"821":1,"823":1,"827":1,"856":1,"897":1,"905":1,"914":1,"953":1,"957":1,"963":2,"966":1,"967":1,"979":2,"980":1,"1004":1,"1014":1,"1018":1,"1034":1,"1052":1,"1054":1,"1059":1,"1066":1,"1077":1,"1093":1,"1124":1,"1125":1,"1137":1,"1167":1,"1212":3,"1229":1,"1230":1,"1233":1,"1237":2,"1247":1,"1259":31,"1263":7,"1265":1,"1270":15,"1271":5,"1278":1,"1285":2,"1286":11,"1297":1,"1299":1,"1300":9,"1301":2,"1304":1,"1309":3,"1310":1,"1311":10,"1316":3,"1323":19,"1324":16,"1328":1,"1330":1,"1336":1,"1337":1,"1339":7,"1343":1,"1347":1,"1348":2,"1350":4,"1351":1,"1359":12,"1369":2,"1371":1,"1372":1,"1373":1,"1375":1,"1380":1,"1388":1,"1393":1,"1395":10,"1396":23,"1402":4,"1404":1,"1410":1,"1415":6,"1416":4,"1417":8,"1421":1,"1425":1,"1429":1,"1433":1,"1435":1,"1436":18,"1438":2,"1443":1,"1447":1,"1453":1,"1455":1,"1456":1,"1460":1,"1467":3,"1473":1,"1474":1,"1475":2,"1479":1,"1486":1,"1490":1,"1492":5,"1503":1,"1504":2,"1507":1,"1523":1,"1524":2,"1526":2,"1527":1,"1534":2,"1544":1,"1554":1,"1574":1,"1577":2,"1582":2,"1588":1,"1591":3,"1592":1,"1631":2,"1648":1,"1653":1,"1662":1,"1695":1,"1749":1,"1757":1,"1758":1,"1766":1,"1784":1,"1799":1,"1800":1,"1804":1,"1815":1,"1821":7,"1825":1,"1828":1,"1831":1,"1839":1,"1856":1,"1881":1,"1889":1,"1890":1,"1902":2,"1916":3,"1918":1,"1921":1,"1922":3,"1929":1,"1938":1,"1944":1,"1947":1,"1953":1,"1972":1,"1988":1,"2000":3,"2007":1,"2032":1,"2046":1,"2048":1,"2050":1,"2064":1,"2078":1,"2091":1,"2096":2,"2100":1,"2126":2,"2136":1,"2149":1,"2156":1,"2158":1,"2161":1,"2165":1,"2168":1,"2171":1,"2177":1,"2179":1,"2195":1,"2199":1,"2232":1}}],["sixlaborslicensekey",{"3":{"1436":1}}],["sixlabors",{"2":{"145":1,"147":1},"3":{"145":2,"147":1,"1323":1,"1431":1,"1436":3,"1489":1}}],["sixty",{"3":{"1359":1,"1395":1,"1415":1,"1985":1}}],["sixteen",{"3":{"130":1,"138":2,"139":1,"141":2,"468":1,"554":1,"672":1,"1217":1,"1229":1,"1263":1,"1286":1,"1324":2,"1339":1,"1359":2,"1369":2,"1372":1,"1417":1,"1436":1,"1438":1,"1455":1,"1486":1,"1496":1}}],["sixth",{"3":{"19":1,"142":1,"258":1,"259":1,"261":1,"350":1,"688":1,"761":1,"776":1,"806":1,"1004":1,"1270":1,"1359":2,"1415":2,"1428":1,"1431":1,"1433":1,"1467":1,"1488":1,"1977":1}}],["six",{"0":{"140":1,"1445":1},"1":{"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1},"3":{"0":19,"62":1,"63":1,"115":1,"122":1,"126":1,"130":1,"136":1,"140":1,"142":1,"145":2,"147":1,"184":1,"186":1,"251":1,"252":1,"335":1,"413":1,"429":1,"440":1,"466":2,"468":1,"470":1,"476":2,"478":1,"526":1,"531":1,"545":1,"554":1,"572":3,"589":1,"593":1,"599":1,"603":1,"607":5,"609":3,"625":1,"632":1,"657":3,"658":1,"660":1,"672":1,"674":2,"696":3,"707":2,"757":2,"760":1,"798":1,"800":2,"804":2,"832":1,"867":1,"868":2,"869":1,"875":1,"881":1,"897":2,"905":1,"936":1,"938":4,"939":2,"940":4,"946":1,"954":2,"955":1,"980":1,"981":1,"982":2,"986":1,"991":1,"998":1,"1004":1,"1017":1,"1018":3,"1019":2,"1020":1,"1033":1,"1048":1,"1051":1,"1060":1,"1068":1,"1116":1,"1191":1,"1211":2,"1212":1,"1234":1,"1237":4,"1244":2,"1247":10,"1259":1,"1260":2,"1268":1,"1270":4,"1271":15,"1286":10,"1293":1,"1300":7,"1309":4,"1311":2,"1312":1,"1322":1,"1323":6,"1324":14,"1339":5,"1342":1,"1343":1,"1346":1,"1350":12,"1359":53,"1362":1,"1364":1,"1366":2,"1369":13,"1375":1,"1377":1,"1380":1,"1383":2,"1395":16,"1396":16,"1402":4,"1415":6,"1416":1,"1417":1,"1433":1,"1436":84,"1439":1,"1441":2,"1442":1,"1443":1,"1445":9,"1449":1,"1450":5,"1454":1,"1455":10,"1461":1,"1464":1,"1465":5,"1466":1,"1467":27,"1469":2,"1472":1,"1474":3,"1488":2,"1489":3,"1492":1,"1496":10,"1497":1,"1503":1,"1525":2,"1526":4,"1535":2,"1577":2,"1591":1,"1602":1,"1630":1,"1631":1,"1651":1,"1660":1,"1661":2,"1662":1,"1666":1,"1674":1,"1684":1,"1686":2,"1692":1,"1693":3,"1717":1,"1725":1,"1726":1,"1727":2,"1728":2,"1781":1,"1794":1,"1801":1,"1809":1,"1810":3,"1826":1,"1829":2,"1881":1,"1902":1,"1913":1,"1948":2,"1951":1,"1958":1,"1987":1,"2034":1,"2037":2,"2038":1,"2044":1,"2055":1,"2075":1,"2110":1,"2127":1,"2128":1,"2138":1,"2147":1,"2158":1,"2167":1,"2193":1,"2198":1,"2200":1,"2226":1,"2227":2,"2232":2,"2234":1}}],["sin",{"3":{"2047":1}}],["singular",{"3":{"872":1,"1324":1,"1359":3,"1436":1,"1577":1,"1695":1,"1727":3,"1728":3,"1729":1,"2089":1}}],["singly",{"3":{"386":1,"1402":1}}],["singles",{"3":{"1496":1}}],["singleserviceprovider",{"3":{"1199":2,"1207":1}}],["singleframesettlescript",{"3":{"1436":1}}],["singlecapture",{"3":{"1436":1}}],["singlecontextfactory",{"3":{"1199":2,"1207":1}}],["singletop",{"3":{"1416":1}}],["singletonstateregistration",{"3":{"1436":1}}],["singletonscan",{"3":{"1436":1}}],["singletons",{"3":{"522":1,"536":1,"651":1,"884":1,"1100":1,"1219":1,"1229":4,"1237":2,"1252":1,"1259":2,"1262":1,"1270":1,"1286":1,"1300":1,"1316":4,"1323":3,"1324":1,"1377":1,"1380":1,"1395":1,"1396":2,"1399":1,"1415":1,"1417":4,"1436":1,"1438":1,"1555":1,"1883":1,"2163":2}}],["singleton",{"3":{"0":6,"200":1,"201":1,"235":1,"303":1,"310":2,"413":1,"511":1,"518":2,"649":1,"698":1,"699":1,"700":1,"703":1,"790":1,"831":1,"832":1,"846":1,"850":1,"881":3,"946":1,"954":1,"980":1,"1050":1,"1089":1,"1091":2,"1100":3,"1124":2,"1133":2,"1212":2,"1229":1,"1237":2,"1247":1,"1253":1,"1259":8,"1262":1,"1267":1,"1269":1,"1270":3,"1277":2,"1278":1,"1279":2,"1283":1,"1286":48,"1300":21,"1301":2,"1309":2,"1310":1,"1311":28,"1315":1,"1316":2,"1320":1,"1323":22,"1324":25,"1332":2,"1339":12,"1350":3,"1355":2,"1359":8,"1368":1,"1369":2,"1395":4,"1396":18,"1402":2,"1413":1,"1415":5,"1416":6,"1417":64,"1424":1,"1427":10,"1436":19,"1438":2,"1443":2,"1488":1,"1526":1,"1539":1,"1556":1,"1577":1,"1591":1,"1669":1,"1750":1,"1773":1,"1810":1,"1849":3,"1851":1,"1881":1,"1920":1,"1947":1,"2000":1,"2095":1,"2118":1,"2121":2,"2166":1,"2183":1}}],["singlewriter",{"3":{"1396":1}}],["singleordefaultasync",{"3":{"1286":1}}],["singlereader",{"2":{"1947":1,"2183":1},"3":{"0":2,"518":1,"1100":1,"1212":2,"1396":3,"1400":1,"1947":1,"2183":1}}],["single",{"0":{"94":1,"170":1,"1294":1,"2044":1},"1":{"83":1,"84":1,"85":1,"86":1,"87":1,"88":1,"496":1,"497":1,"498":1,"499":1,"500":1,"501":1,"502":1,"503":1,"1854":1,"1855":1,"1856":1,"1857":1,"1858":1,"1859":1,"1860":1,"1861":1,"1862":1,"2081":1,"2082":1,"2083":1,"2084":1,"2085":1,"2086":1},"3":{"0":39,"11":1,"15":1,"23":1,"24":1,"26":1,"30":1,"31":2,"32":1,"45":1,"46":2,"47":1,"48":1,"57":1,"58":1,"59":2,"60":1,"62":3,"67":1,"68":3,"69":1,"73":1,"83":1,"85":2,"86":2,"87":1,"91":1,"92":1,"99":1,"100":1,"115":1,"117":1,"121":1,"125":2,"128":1,"132":1,"135":2,"145":1,"147":1,"149":1,"157":3,"159":1,"160":1,"166":3,"174":1,"175":1,"176":2,"179":1,"180":1,"186":1,"194":1,"195":1,"196":2,"201":1,"202":1,"214":1,"216":1,"217":1,"220":1,"230":1,"233":1,"234":2,"235":3,"242":2,"243":2,"245":1,"246":1,"248":1,"249":1,"255":1,"259":1,"263":1,"264":1,"265":2,"266":1,"273":2,"281":1,"290":1,"310":2,"311":2,"312":1,"317":1,"318":3,"319":1,"320":1,"333":1,"340":1,"341":1,"350":3,"351":1,"352":1,"359":5,"360":1,"361":1,"365":1,"369":1,"370":2,"380":1,"382":3,"386":1,"388":1,"389":1,"390":2,"396":1,"397":1,"398":1,"405":1,"407":1,"412":1,"413":1,"418":1,"419":1,"420":2,"421":1,"422":1,"427":1,"428":2,"429":1,"446":2,"447":1,"448":2,"449":1,"459":1,"469":1,"475":1,"476":1,"491":1,"492":2,"496":1,"497":1,"499":2,"501":1,"503":1,"505":1,"506":2,"507":3,"508":1,"509":1,"510":1,"511":1,"518":1,"519":1,"526":1,"528":1,"536":1,"538":1,"539":1,"540":1,"545":4,"546":1,"549":1,"556":2,"564":1,"573":1,"582":2,"583":3,"584":1,"589":1,"608":1,"609":1,"618":1,"626":2,"627":1,"629":1,"633":1,"638":1,"649":1,"657":1,"659":1,"664":1,"665":3,"666":1,"674":3,"676":1,"681":1,"688":1,"690":3,"691":3,"693":1,"698":3,"702":1,"707":1,"709":1,"714":1,"731":1,"733":1,"735":2,"736":1,"741":1,"748":1,"756":1,"757":2,"758":1,"759":1,"766":1,"775":1,"781":1,"782":2,"790":2,"792":1,"795":1,"799":2,"808":1,"810":3,"819":1,"820":1,"821":1,"825":1,"826":1,"827":5,"830":2,"836":1,"837":1,"838":1,"853":1,"854":1,"855":2,"861":1,"862":1,"863":1,"872":1,"881":1,"889":1,"891":1,"893":1,"898":1,"904":2,"905":1,"906":1,"907":1,"910":1,"912":1,"922":2,"930":1,"941":1,"945":1,"946":3,"947":1,"954":1,"955":1,"971":2,"972":2,"973":1,"974":2,"976":1,"980":2,"981":1,"982":1,"988":2,"990":1,"996":4,"997":1,"1004":1,"1012":3,"1018":2,"1020":1,"1021":1,"1026":1,"1033":1,"1034":2,"1041":2,"1044":1,"1058":1,"1066":1,"1067":1,"1069":1,"1083":1,"1087":1,"1099":1,"1100":1,"1101":1,"1116":1,"1123":1,"1124":5,"1126":1,"1150":5,"1152":1,"1157":1,"1161":1,"1162":1,"1183":1,"1185":1,"1191":1,"1192":1,"1202":1,"1203":1,"1212":13,"1213":1,"1218":1,"1220":1,"1221":1,"1225":1,"1227":1,"1228":1,"1229":6,"1231":1,"1232":1,"1234":1,"1236":2,"1237":24,"1239":1,"1241":2,"1243":1,"1244":1,"1246":1,"1247":38,"1248":1,"1250":1,"1251":1,"1252":2,"1253":1,"1254":1,"1256":1,"1257":1,"1258":1,"1259":37,"1260":1,"1265":1,"1266":1,"1267":1,"1269":3,"1270":46,"1271":17,"1272":1,"1273":1,"1276":1,"1277":1,"1278":1,"1279":1,"1280":1,"1281":1,"1282":1,"1284":1,"1285":1,"1286":121,"1287":1,"1288":5,"1289":1,"1290":1,"1291":2,"1292":1,"1293":1,"1294":1,"1296":1,"1297":1,"1298":2,"1299":1,"1300":97,"1301":30,"1303":1,"1304":1,"1306":1,"1307":1,"1309":44,"1310":22,"1311":70,"1312":7,"1313":1,"1317":1,"1318":2,"1320":4,"1323":73,"1324":143,"1325":1,"1326":3,"1327":1,"1329":2,"1331":1,"1332":1,"1338":1,"1339":62,"1340":1,"1346":2,"1347":1,"1350":63,"1351":1,"1352":4,"1354":1,"1355":2,"1357":1,"1358":2,"1359":205,"1362":1,"1363":1,"1366":2,"1368":2,"1369":18,"1370":1,"1371":1,"1372":1,"1374":1,"1376":2,"1378":1,"1380":32,"1384":1,"1387":2,"1389":2,"1390":1,"1391":1,"1393":1,"1394":1,"1395":91,"1396":106,"1397":1,"1398":1,"1400":2,"1401":1,"1402":56,"1403":2,"1406":1,"1407":2,"1408":1,"1412":1,"1414":2,"1415":51,"1416":20,"1417":67,"1418":2,"1427":8,"1429":2,"1430":1,"1431":3,"1432":1,"1436":198,"1437":1,"1438":11,"1439":1,"1440":2,"1441":5,"1442":2,"1443":4,"1444":3,"1446":1,"1447":5,"1449":1,"1450":1,"1453":3,"1454":3,"1455":5,"1456":2,"1459":2,"1460":1,"1464":1,"1465":3,"1467":16,"1469":1,"1472":1,"1473":4,"1474":7,"1475":1,"1487":2,"1488":3,"1489":2,"1490":4,"1492":1,"1496":3,"1498":1,"1508":1,"1509":3,"1510":1,"1517":1,"1522":2,"1526":10,"1527":1,"1529":1,"1534":7,"1535":1,"1538":1,"1541":1,"1545":1,"1548":1,"1553":1,"1554":1,"1556":1,"1564":1,"1566":2,"1573":1,"1574":1,"1577":6,"1578":1,"1585":2,"1586":1,"1587":1,"1591":8,"1592":1,"1594":1,"1595":1,"1596":2,"1597":2,"1599":3,"1600":2,"1611":3,"1627":1,"1629":1,"1630":2,"1631":5,"1632":4,"1634":1,"1638":3,"1639":3,"1640":2,"1641":6,"1649":1,"1651":1,"1652":2,"1656":1,"1661":1,"1664":1,"1667":2,"1670":1,"1673":1,"1675":2,"1677":2,"1685":4,"1686":2,"1689":1,"1691":1,"1692":1,"1694":1,"1699":1,"1708":1,"1709":2,"1727":2,"1732":1,"1748":4,"1749":4,"1764":1,"1765":2,"1767":2,"1768":2,"1769":1,"1773":1,"1775":1,"1776":1,"1783":1,"1784":1,"1788":1,"1790":3,"1799":1,"1800":1,"1802":2,"1803":1,"1804":1,"1805":3,"1806":1,"1807":1,"1810":2,"1815":3,"1818":1,"1819":1,"1823":1,"1825":1,"1827":2,"1828":1,"1829":1,"1830":1,"1831":1,"1839":1,"1840":1,"1847":3,"1849":5,"1851":1,"1852":1,"1853":2,"1854":2,"1856":2,"1858":1,"1859":1,"1862":1,"1863":1,"1867":2,"1869":1,"1871":1,"1875":1,"1884":2,"1893":1,"1897":1,"1899":1,"1900":1,"1902":1,"1905":1,"1906":1,"1908":1,"1909":2,"1915":2,"1916":2,"1918":2,"1920":2,"1922":2,"1923":2,"1927":2,"1930":2,"1933":3,"1935":1,"1939":1,"1941":1,"1942":1,"1944":2,"1945":1,"1946":1,"1949":3,"1952":1,"1953":1,"1959":1,"1961":1,"1962":3,"1965":1,"1969":1,"1970":2,"1972":1,"1973":1,"1975":1,"1976":1,"1977":2,"1978":1,"1979":3,"1983":1,"1985":1,"1986":1,"1987":1,"1988":1,"1989":1,"1991":1,"1994":5,"1997":2,"1998":2,"1999":1,"2000":1,"2002":1,"2003":1,"2005":2,"2007":1,"2008":2,"2010":1,"2013":1,"2023":1,"2024":2,"2025":2,"2028":1,"2029":1,"2030":1,"2033":2,"2034":2,"2035":1,"2036":1,"2037":4,"2038":2,"2043":2,"2044":1,"2050":1,"2053":1,"2055":1,"2057":1,"2059":1,"2062":1,"2066":2,"2069":1,"2072":1,"2073":1,"2075":2,"2081":2,"2083":3,"2085":1,"2086":2,"2089":1,"2090":1,"2092":2,"2094":1,"2097":1,"2103":1,"2106":1,"2112":1,"2116":1,"2119":1,"2121":1,"2123":1,"2126":1,"2128":1,"2129":2,"2131":1,"2133":2,"2135":1,"2141":1,"2142":3,"2143":1,"2147":2,"2149":2,"2154":1,"2156":1,"2158":2,"2165":1,"2166":2,"2167":1,"2169":1,"2170":1,"2182":1,"2183":2,"2185":1,"2192":1,"2193":1,"2197":1,"2198":1,"2203":1,"2204":1,"2207":1,"2210":1,"2227":2,"2228":1,"2230":2,"2231":1,"2232":6}}],["sinks",{"3":{"401":1,"665":1,"1339":2,"1396":1}}],["sink",{"3":{"0":1,"395":1,"401":3,"914":1,"1271":1,"1286":1,"1311":1,"1326":1,"1333":1,"1339":5,"1359":2,"1416":1,"1417":2,"1436":2,"1445":1,"1467":2,"1670":1}}],["since",{"3":{"0":30,"18":1,"57":1,"91":1,"94":1,"98":1,"122":1,"126":1,"130":1,"135":2,"137":1,"138":3,"139":1,"140":2,"141":1,"142":1,"150":1,"157":1,"160":1,"166":2,"171":1,"175":1,"178":1,"202":1,"228":1,"229":1,"237":2,"243":1,"245":1,"251":1,"252":2,"254":3,"255":1,"256":2,"257":1,"258":1,"259":1,"265":2,"280":1,"291":1,"308":1,"314":1,"323":1,"324":2,"340":1,"349":1,"360":1,"365":1,"386":1,"390":1,"402":1,"413":1,"423":1,"427":1,"446":1,"448":2,"450":1,"465":1,"469":1,"490":2,"492":1,"493":1,"494":1,"499":1,"501":1,"511":1,"518":1,"526":1,"527":1,"543":1,"545":1,"550":1,"572":3,"600":1,"604":1,"626":1,"631":2,"633":1,"634":1,"636":1,"638":2,"640":1,"664":1,"665":1,"675":1,"677":1,"681":1,"696":1,"702":1,"703":1,"708":1,"711":1,"716":1,"720":2,"728":1,"789":1,"799":1,"800":1,"803":1,"805":2,"809":2,"826":1,"853":1,"857":1,"870":1,"875":1,"889":1,"898":1,"905":1,"907":1,"947":1,"948":1,"959":1,"988":1,"991":2,"1004":1,"1006":1,"1012":2,"1014":1,"1017":1,"1033":1,"1034":1,"1043":1,"1049":1,"1050":2,"1052":1,"1059":1,"1069":1,"1075":2,"1083":1,"1124":1,"1156":1,"1177":1,"1192":1,"1212":4,"1229":5,"1237":3,"1244":1,"1247":8,"1252":1,"1254":2,"1256":2,"1258":1,"1259":3,"1262":1,"1263":2,"1268":1,"1270":8,"1271":1,"1275":2,"1283":1,"1285":1,"1286":37,"1290":2,"1300":20,"1301":7,"1304":1,"1308":1,"1309":6,"1310":1,"1311":27,"1312":1,"1317":1,"1322":1,"1323":14,"1324":38,"1331":1,"1333":1,"1337":1,"1339":16,"1344":1,"1350":9,"1351":1,"1359":27,"1369":3,"1375":1,"1379":1,"1380":10,"1393":1,"1395":17,"1396":29,"1398":1,"1400":1,"1401":1,"1402":7,"1406":1,"1408":1,"1415":16,"1416":5,"1417":6,"1424":1,"1425":1,"1427":5,"1429":1,"1431":1,"1436":72,"1438":20,"1441":3,"1442":2,"1443":1,"1445":2,"1449":1,"1453":1,"1454":1,"1455":6,"1457":2,"1459":1,"1460":1,"1467":9,"1482":1,"1486":1,"1490":2,"1496":21,"1497":2,"1498":1,"1500":1,"1526":2,"1534":1,"1548":1,"1577":1,"1591":1,"1631":1,"1635":1,"1654":1,"1656":1,"1670":1,"1684":1,"1701":1,"1721":1,"1722":1,"1727":2,"1729":2,"1732":1,"1750":2,"1800":1,"1839":1,"1840":1,"1843":1,"1844":2,"1845":1,"1851":1,"1872":1,"1889":1,"1947":1,"1988":3,"2067":1,"2142":1,"2151":1}}],["sacrificing",{"3":{"2218":1}}],["sacrificed",{"3":{"1286":1}}],["saas",{"3":{"1940":1}}],["sa1203",{"3":{"1395":1}}],["sa1211",{"2":{"1689":1,"1699":1,"2089":1},"3":{"0":1,"490":1,"491":1,"492":1,"493":1,"495":1,"633":2,"1686":1,"1689":1,"1699":1,"1727":1,"2089":1}}],["sa1210",{"2":{"635":1,"1689":1,"2089":1},"3":{"0":1,"490":1,"491":1,"492":1,"493":1,"495":1,"633":2,"635":1,"1689":1,"1727":1,"2089":1}}],["sake",{"3":{"1339":1,"2156":1}}],["sa",{"3":{"1339":2,"1441":1}}],["sails",{"3":{"1998":1}}],["sail",{"3":{"1324":1}}],["said",{"3":{"0":1,"22":1,"27":1,"43":1,"62":1,"130":1,"138":1,"160":1,"200":1,"220":1,"256":1,"306":1,"353":1,"374":1,"408":1,"425":2,"426":1,"443":1,"624":1,"642":1,"683":1,"720":1,"761":1,"794":1,"837":1,"958":1,"1017":2,"1052":1,"1054":1,"1055":1,"1062":1,"1091":1,"1259":1,"1270":1,"1300":1,"1323":2,"1324":3,"1359":2,"1396":3,"1417":1,"1427":1,"1431":1,"1496":1,"1992":2}}],["safari",{"3":{"1324":5,"1417":1,"1436":2,"1606":1,"1716":1,"1740":1}}],["safer",{"3":{"584":1,"1286":1,"1301":1,"1339":1,"1436":1,"1998":1}}],["safedomaineventhandlertests",{"3":{"536":1,"1199":1,"1207":5,"1259":2,"1438":1,"2165":2}}],["safedomaineventhandler",{"2":{"534":1,"1252":1,"1665":1,"2165":1},"3":{"534":1,"536":2,"537":1,"1199":2,"1203":1,"1207":2,"1252":5,"1259":25,"1396":1,"1438":1,"1496":2,"1665":1,"2165":3}}],["safely",{"3":{"350":1,"508":1,"829":2,"883":1,"1237":1,"1259":1,"1271":1,"1286":4,"1300":3,"1311":1,"1312":1,"1324":3,"1358":1,"1359":1,"1395":1,"1396":3,"1402":2,"1415":1,"1417":3,"1427":1,"1436":1,"1438":1,"1472":1,"1563":1,"1569":1,"1646":1,"1665":1,"1840":1,"1977":1,"2185":1}}],["safeguards",{"3":{"1922":2}}],["safeguard",{"3":{"182":1,"1359":1,"1436":3,"1489":1,"1526":1}}],["safety",{"0":{"1254":1,"1561":1,"1934":1},"3":{"0":1,"6":1,"19":1,"158":1,"160":1,"209":1,"255":1,"330":1,"335":1,"382":1,"435":1,"472":1,"501":1,"522":1,"523":1,"524":1,"564":1,"800":1,"801":1,"803":1,"882":1,"1068":1,"1116":1,"1132":1,"1135":1,"1216":1,"1229":1,"1237":2,"1243":1,"1247":1,"1255":1,"1257":1,"1259":7,"1263":2,"1270":5,"1271":6,"1273":1,"1286":3,"1300":4,"1302":1,"1309":4,"1311":4,"1323":2,"1324":30,"1339":2,"1350":3,"1353":1,"1359":54,"1380":1,"1384":2,"1395":45,"1396":16,"1402":10,"1414":1,"1415":8,"1417":3,"1427":2,"1436":26,"1438":3,"1455":3,"1458":1,"1467":1,"1499":1,"1508":1,"1526":2,"1530":1,"1532":1,"1535":1,"1577":1,"1586":1,"1591":1,"1595":1,"1597":1,"1652":1,"1671":1,"1796":1,"1805":1,"1817":1,"1888":1,"1941":1,"1949":2,"1952":1,"1959":1,"1971":1,"1978":1,"1987":2,"2201":1,"2202":1}}],["safe",{"1":{"1900":1,"1901":1,"1902":1,"1903":1,"1904":1,"1905":1,"1906":1,"1907":1,"1908":1,"1909":1,"1910":1,"1911":1,"1912":1},"3":{"0":8,"15":1,"20":1,"78":1,"79":1,"109":1,"111":1,"122":1,"145":1,"147":2,"158":1,"187":1,"189":1,"208":1,"245":1,"265":2,"291":1,"302":1,"309":1,"310":1,"335":1,"397":1,"398":1,"402":1,"413":2,"416":1,"443":1,"460":1,"506":1,"507":1,"530":1,"550":1,"556":1,"564":1,"565":1,"607":2,"609":1,"610":1,"681":1,"691":1,"694":1,"700":1,"706":1,"711":1,"743":1,"751":1,"758":1,"767":1,"776":1,"827":1,"832":1,"838":1,"848":1,"861":1,"869":1,"890":1,"926":1,"940":1,"942":1,"946":1,"965":1,"989":1,"1059":1,"1084":1,"1109":1,"1115":2,"1116":1,"1124":1,"1132":1,"1147":1,"1184":1,"1212":2,"1229":3,"1237":4,"1238":1,"1247":3,"1252":1,"1259":15,"1263":1,"1269":3,"1270":12,"1271":2,"1275":1,"1278":1,"1283":1,"1285":1,"1286":30,"1298":1,"1299":1,"1300":26,"1301":4,"1304":1,"1309":12,"1310":2,"1311":29,"1312":3,"1320":3,"1322":1,"1323":19,"1324":39,"1333":1,"1334":1,"1337":1,"1339":20,"1344":1,"1346":1,"1349":1,"1350":7,"1355":1,"1358":1,"1359":26,"1364":1,"1369":6,"1380":4,"1395":12,"1396":26,"1402":11,"1415":17,"1416":1,"1417":28,"1421":1,"1427":1,"1429":2,"1436":27,"1453":3,"1455":7,"1457":1,"1458":2,"1463":1,"1467":2,"1472":1,"1475":2,"1477":1,"1478":2,"1484":1,"1488":1,"1490":1,"1492":2,"1496":1,"1545":1,"1554":1,"1561":1,"1577":1,"1590":1,"1591":1,"1611":1,"1626":1,"1631":1,"1660":1,"1661":1,"1668":1,"1672":2,"1686":1,"1708":1,"1729":1,"1734":1,"1748":1,"1790":1,"1835":1,"1837":1,"1842":1,"1844":1,"1873":1,"1889":2,"1899":1,"1900":1,"1902":1,"1905":1,"1906":1,"1907":2,"1910":1,"1911":1,"1912":3,"1920":1,"1923":1,"1933":1,"1935":1,"1948":1,"1950":1,"1958":1,"1963":1,"1968":1,"1972":2,"2000":1,"2011":1,"2014":1,"2020":1,"2021":1,"2029":1,"2037":1,"2048":1,"2055":1,"2063":1,"2069":1,"2087":1,"2118":1,"2119":1,"2123":2,"2126":1,"2138":1,"2148":1,"2156":1,"2162":1,"2188":1,"2196":1,"2199":1,"2208":3,"2232":1}}],["saturation",{"3":{"1324":1}}],["saturate",{"3":{"1311":1}}],["saturated",{"3":{"1124":1,"1256":1,"1259":2,"1286":2,"1339":2,"1380":1,"1444":1}}],["satellites",{"3":{"1436":2}}],["satellite",{"3":{"265":2,"269":1,"1324":1,"1380":2,"1402":1,"1436":1}}],["sat",{"3":{"72":1,"179":1,"390":1,"424":1,"689":1,"1174":1,"1324":5,"1460":1,"1467":2,"1475":1,"1500":1}}],["satisfaction",{"3":{"1396":1}}],["satisfiable",{"3":{"593":1,"1309":2,"1323":1,"1415":1}}],["satisfiesloadbearinginvariants",{"3":{"1436":2}}],["satisfies",{"3":{"160":1,"470":1,"799":1,"1018":1,"1049":1,"1089":1,"1091":1,"1229":1,"1237":1,"1247":1,"1270":1,"1286":6,"1300":4,"1309":1,"1310":2,"1311":2,"1323":1,"1324":1,"1359":13,"1368":1,"1369":2,"1380":1,"1395":4,"1396":6,"1402":2,"1410":1,"1415":4,"1417":4,"1436":14,"1482":1,"1671":1,"1810":1,"1845":1,"2126":1,"2230":1}}],["satisfied",{"3":{"0":1,"59":2,"130":1,"135":1,"140":1,"166":1,"584":1,"905":1,"926":1,"1050":1,"1059":1,"1237":1,"1247":3,"1265":1,"1270":2,"1286":1,"1293":1,"1300":5,"1309":2,"1315":1,"1320":1,"1323":6,"1324":2,"1350":3,"1359":8,"1380":1,"1396":4,"1415":7,"1416":3,"1417":2,"1431":2,"1436":7,"1478":1,"1488":1,"1534":1,"1577":1,"1662":1,"1772":1,"1790":1,"1863":1,"1882":1,"2168":1,"2197":2}}],["satisfy",{"3":{"109":1,"518":1,"528":1,"1006":1,"1017":1,"1100":1,"1124":1,"1145":1,"1220":1,"1229":1,"1237":3,"1247":1,"1286":3,"1300":4,"1301":2,"1309":1,"1310":1,"1311":1,"1312":2,"1323":6,"1324":7,"1337":1,"1338":1,"1339":3,"1350":3,"1359":8,"1378":1,"1380":2,"1388":1,"1395":1,"1396":2,"1402":1,"1415":4,"1416":1,"1417":1,"1424":1,"1427":1,"1436":14,"1447":1,"1454":1,"1467":1,"1480":1,"1488":1,"1492":1,"1591":1,"1790":1,"1793":1,"1805":1,"1864":1,"1916":1,"1967":1,"2017":1,"2069":1}}],["satisfying",{"3":{"40":1,"803":1,"1300":2,"1310":1,"1350":1,"1396":1,"1416":1,"1417":1,"1436":1,"1438":1}}],["sawerrors",{"3":{"1229":1}}],["sawvalue",{"3":{"1229":2}}],["saw",{"3":{"26":1,"72":1,"236":1,"340":1,"873":1,"876":1,"881":1,"1017":1,"1052":1,"1055":1,"1169":1,"1259":1,"1286":1,"1300":3,"1311":3,"1323":1,"1324":2,"1350":2,"1359":2,"1395":1,"1396":3,"1436":2,"1869":1,"1870":1}}],["sandboxing",{"3":{"2239":1}}],["sandboxed",{"3":{"1526":1}}],["sandbox",{"3":{"1429":1}}],["sandwich",{"3":{"1415":1}}],["santiago",{"3":{"1350":1}}],["sans",{"3":{"1324":1,"2075":1}}],["sane",{"3":{"1228":1,"1229":1,"1300":1,"1324":1,"1339":1,"1396":1,"1676":1}}],["sanctions",{"3":{"913":1,"1396":1,"1591":1}}],["sanctioned",{"3":{"0":1,"260":1,"371":1,"392":1,"642":1,"912":1,"914":1,"996":1,"1229":1,"1237":1,"1247":1,"1286":5,"1380":1,"1416":2,"1417":1,"1436":3,"1455":1,"1806":1,"2232":1}}],["sanitization",{"3":{"1286":1,"1323":1,"1563":1}}],["sanitizes",{"3":{"1286":1,"1320":1,"1324":2,"1359":1}}],["sanitize",{"3":{"1270":2,"1323":1,"1324":4}}],["sanitized",{"3":{"1116":2,"1286":1,"1300":2,"1323":1,"1324":3,"1344":1,"1350":2,"1357":1,"1359":2,"1526":1,"1563":1,"1572":1,"1577":1,"1631":1,"1669":1}}],["sanitizer",{"3":{"1114":1,"1324":2,"2232":1}}],["sanitizefilename",{"2":{"443":1,"1116":1},"3":{"0":1,"443":1,"1116":1,"1286":1}}],["sanity",{"3":{"846":1,"1436":2,"1479":1,"1489":1}}],["saying",{"3":{"201":1,"350":1,"541":1,"576":1,"673":1,"1140":1,"1161":1,"1259":1,"1270":1,"1286":3,"1309":1,"1323":1,"1324":2,"1339":3,"1359":3,"1389":1,"1395":1,"1396":5,"1415":1,"1427":1,"1436":3,"1442":1,"1596":1,"1646":1,"1685":1,"1860":1,"1887":1,"1908":1,"1909":1,"1961":1,"1970":1,"1977":1,"2000":1,"2162":1,"2164":1}}],["say",{"0":{"1423":1,"1947":1},"3":{"0":1,"73":1,"109":1,"122":1,"123":1,"136":1,"171":1,"185":1,"220":1,"231":1,"260":1,"261":1,"284":1,"300":1,"353":1,"390":1,"444":1,"477":1,"564":1,"578":1,"590":1,"610":1,"619":1,"626":2,"627":1,"638":1,"660":1,"676":1,"684":2,"689":1,"702":1,"703":1,"741":1,"743":1,"756":1,"776":1,"790":1,"804":1,"864":1,"871":1,"899":1,"904":1,"905":1,"907":1,"921":1,"960":1,"988":1,"1062":1,"1067":1,"1079":1,"1082":1,"1085":1,"1107":1,"1216":1,"1231":1,"1233":1,"1237":2,"1247":1,"1253":1,"1259":2,"1270":1,"1271":1,"1280":1,"1286":11,"1293":1,"1297":1,"1300":8,"1309":1,"1311":10,"1316":1,"1323":9,"1324":8,"1339":1,"1350":2,"1359":17,"1366":1,"1369":2,"1395":5,"1396":10,"1402":2,"1415":9,"1417":2,"1418":1,"1423":1,"1431":1,"1436":16,"1438":1,"1443":1,"1474":1,"1486":1,"1488":1,"1489":1,"1553":1,"1591":2,"1596":1,"1638":1,"1651":1,"1685":1,"1686":1,"1689":1,"1698":1,"1702":1,"1748":1,"1749":1,"1765":1,"1799":1,"1803":1,"1810":1,"1823":1,"1842":1,"1848":1,"1853":1,"1854":1,"1862":1,"1902":1,"1904":1,"1908":1,"1909":1,"1911":1,"1929":1,"1995":1,"2000":1,"2007":1,"2012":1,"2013":1,"2044":1,"2057":1,"2064":1,"2086":1,"2087":1,"2156":1,"2158":1,"2163":1,"2175":1,"2180":1,"2203":1}}],["saysnothingaboutthecanonicaldeclaration",{"3":{"1436":3}}],["says",{"3":{"0":4,"13":1,"27":2,"74":1,"77":1,"135":2,"137":1,"138":1,"139":1,"142":1,"149":1,"170":1,"171":1,"187":1,"189":1,"200":3,"201":2,"254":1,"256":2,"259":1,"265":1,"317":1,"342":1,"343":1,"353":1,"391":1,"459":1,"461":1,"478":1,"490":1,"535":1,"537":1,"539":1,"540":1,"541":1,"549":1,"550":1,"556":1,"563":1,"564":1,"601":1,"611":1,"624":1,"625":1,"633":3,"636":1,"657":1,"659":1,"660":2,"665":2,"674":1,"690":1,"692":1,"703":1,"725":1,"740":1,"742":1,"743":1,"749":2,"751":1,"761":1,"765":1,"781":1,"784":1,"793":1,"798":1,"827":1,"832":1,"833":2,"861":1,"876":1,"880":3,"890":2,"891":1,"893":1,"897":2,"904":1,"907":1,"927":1,"932":1,"946":1,"955":1,"958":1,"964":1,"966":1,"967":1,"979":1,"988":1,"996":1,"997":1,"1014":1,"1018":3,"1026":1,"1037":1,"1050":2,"1051":1,"1058":1,"1067":1,"1074":1,"1079":1,"1085":1,"1091":1,"1093":1,"1099":1,"1119":1,"1124":1,"1133":1,"1167":3,"1176":2,"1190":1,"1212":1,"1227":1,"1229":4,"1237":6,"1241":1,"1247":6,"1248":1,"1259":12,"1262":1,"1266":1,"1269":1,"1270":17,"1271":1,"1276":1,"1277":1,"1286":31,"1290":1,"1291":1,"1297":1,"1300":26,"1301":2,"1305":1,"1309":4,"1310":2,"1311":26,"1315":1,"1316":1,"1318":1,"1320":1,"1323":19,"1324":30,"1337":1,"1338":1,"1339":20,"1342":2,"1346":1,"1347":1,"1348":1,"1350":10,"1354":1,"1355":1,"1356":1,"1357":1,"1359":57,"1367":1,"1369":3,"1380":4,"1393":1,"1395":22,"1396":47,"1400":1,"1402":11,"1410":1,"1413":1,"1414":1,"1415":26,"1416":1,"1417":9,"1419":1,"1430":2,"1433":1,"1436":45,"1438":2,"1441":1,"1442":1,"1445":2,"1453":3,"1455":4,"1459":1,"1460":1,"1462":1,"1467":8,"1470":1,"1473":1,"1474":2,"1476":2,"1481":1,"1488":1,"1489":2,"1490":1,"1492":2,"1496":2,"1500":1,"1553":1,"1596":1,"1639":1,"1646":1,"1661":1,"1685":3,"1702":1,"1721":1,"1723":1,"1727":1,"1729":1,"1765":1,"1769":1,"1794":1,"1799":1,"1815":2,"1831":1,"1835":1,"1836":1,"1843":1,"1880":2,"1887":1,"1892":1,"1906":1,"1915":1,"1944":1,"1945":1,"1960":1,"1963":1,"1965":2,"1982":1,"1992":2,"1994":2,"2000":2,"2001":1,"2002":1,"2012":1,"2029":1,"2030":1,"2033":1,"2039":1,"2045":1,"2047":1,"2056":1,"2105":1,"2124":2,"2126":1,"2131":1,"2151":1,"2156":1,"2158":2,"2162":1,"2164":1,"2167":1,"2168":2,"2171":1,"2172":1,"2173":1,"2175":1,"2177":1,"2179":1,"2196":1}}],["sast",{"3":{"2219":1,"2220":1}}],["sas",{"3":{"0":2,"441":1,"597":1,"599":1,"602":1,"638":2,"643":2,"1446":1,"1467":7,"1589":1,"1591":1,"1595":1,"1596":2,"1597":1,"2127":1}}],["savings",{"3":{"1026":1,"1455":1,"1459":1,"1749":2,"1750":1,"1764":2,"1767":2,"1769":1}}],["savingchangesasync",{"3":{"715":1,"1286":5}}],["savingchanges",{"2":{"21":1,"1251":1},"3":{"21":1,"715":1,"1251":1,"1274":3,"1286":11}}],["saving",{"3":{"0":1,"43":1,"197":1,"342":1,"391":1,"534":1,"709":1,"827":1,"1026":2,"1229":1,"1237":1,"1269":1,"1270":4,"1286":10,"1300":1,"1323":3,"1324":1,"1339":1,"1359":2,"1395":1,"1396":1,"1399":1,"1401":1,"1402":19,"1417":1,"1438":1,"1484":1,"1526":1,"1749":1,"2024":1,"2164":1,"2220":1}}],["savebutton",{"3":{"1436":1}}],["savebehindaninterface",{"3":{"1436":2}}],["saveaddressbutton",{"3":{"1436":1}}],["saveasync",{"3":{"1324":4}}],["savenamebutton",{"3":{"1436":1}}],["saveupvoteasync",{"3":{"1402":2}}],["saver",{"3":{"1396":4,"1417":5}}],["saveresult",{"3":{"1324":2,"1395":27}}],["savepasswordasync",{"3":{"1415":1}}],["savepagefilters",{"3":{"1383":2,"1395":10}}],["saveprofileasync",{"3":{"1395":1}}],["savetitleasync",{"3":{"1395":2}}],["savetoken",{"3":{"1312":1}}],["savetokens",{"3":{"1311":2}}],["savefilters",{"3":{"1324":3,"1383":2,"1395":8,"1396":1}}],["saveexplicitkeygroupsasync",{"3":{"1286":3}}],["saveerror",{"3":{"265":1,"1324":1}}],["savewithexplicitkeyinsertasync",{"2":{"1278":1},"3":{"1278":1,"1286":2}}],["savecount",{"3":{"1436":3}}],["savechangedetectiontests",{"3":{"1199":1,"1207":5,"1286":2,"1438":2}}],["savechangesinterceptor",{"2":{"1839":1},"3":{"0":1,"715":1,"1212":3,"1286":2,"1839":1}}],["savechanges",{"0":{"1274":1},"2":{"124":1,"365":1,"809":1,"1701":1,"1839":1,"1844":1,"1846":1,"2091":1,"2092":1,"2161":1,"2164":1,"2169":1},"3":{"0":1,"124":1,"359":1,"365":1,"540":1,"715":1,"809":1,"1212":1,"1213":1,"1237":2,"1252":1,"1259":6,"1273":1,"1286":11,"1369":2,"1396":1,"1436":5,"1438":1,"1453":1,"1487":1,"1490":1,"1498":1,"1585":1,"1671":1,"1701":1,"1835":1,"1838":1,"1839":1,"1844":1,"1846":1,"2091":1,"2092":1,"2142":1,"2161":1,"2164":1,"2169":2}}],["savechangesasync",{"2":{"17":1,"27":1,"340":1,"444":1,"465":1,"466":1,"518":1,"522":1,"523":2,"524":1,"539":1,"714":1,"781":1,"1355":1,"1358":1,"1363":1,"1823":1,"1838":2,"1844":1,"1869":1,"1910":1,"1934":1,"2111":1,"2164":1},"3":{"0":1,"17":1,"27":1,"201":1,"340":1,"444":1,"459":1,"465":1,"466":1,"499":1,"518":1,"522":2,"523":4,"524":1,"539":1,"540":1,"714":1,"781":1,"897":2,"905":1,"1100":2,"1212":1,"1237":3,"1258":1,"1259":13,"1270":3,"1272":1,"1273":1,"1286":23,"1300":2,"1309":3,"1323":10,"1345":1,"1349":1,"1350":12,"1355":1,"1358":1,"1359":43,"1363":1,"1369":1,"1395":19,"1396":5,"1402":7,"1410":1,"1415":10,"1436":21,"1488":2,"1489":1,"1526":2,"1651":1,"1749":1,"1765":1,"1810":1,"1823":1,"1838":2,"1844":1,"1869":1,"1910":1,"1934":1,"2111":1,"2164":1}}],["savecurrentstate",{"3":{"881":1,"1324":3}}],["savedsortdescending",{"3":{"1324":1}}],["savedsortcolumn",{"3":{"1324":1}}],["savedpagesize",{"3":{"1324":2}}],["savedpage",{"3":{"1324":1}}],["savedtoken",{"3":{"1312":1}}],["savedchangesasync",{"3":{"1274":1,"1286":3}}],["savedchanges",{"3":{"1252":1,"1274":2,"1286":7}}],["saved",{"3":{"0":2,"120":2,"536":1,"538":1,"540":1,"609":1,"627":1,"725":1,"914":2,"926":1,"991":1,"1024":1,"1026":2,"1093":2,"1247":1,"1259":3,"1270":2,"1274":1,"1275":2,"1278":1,"1286":12,"1300":2,"1306":1,"1309":2,"1311":2,"1312":4,"1318":1,"1322":1,"1323":5,"1324":2,"1350":1,"1355":1,"1359":9,"1380":4,"1383":4,"1391":1,"1395":15,"1396":13,"1399":1,"1402":3,"1406":1,"1415":6,"1436":2,"1438":1,"1456":1,"1467":2,"1479":1,"1480":1,"1591":1,"1749":2,"1750":1,"1765":1,"1768":1,"1776":2,"1835":1,"1933":2,"1951":1,"2158":1,"2185":1}}],["save",{"2":{"268":1},"3":{"0":6,"17":2,"18":1,"20":2,"21":2,"26":2,"27":2,"39":1,"109":2,"120":1,"166":1,"195":1,"201":4,"225":1,"265":1,"268":1,"340":2,"342":1,"344":1,"388":1,"391":1,"435":1,"436":1,"516":2,"518":4,"522":2,"523":5,"524":3,"534":1,"536":1,"538":4,"539":4,"698":1,"715":1,"716":1,"717":1,"857":1,"897":1,"905":2,"908":2,"920":1,"921":1,"923":2,"926":6,"931":1,"988":2,"991":1,"1033":1,"1035":1,"1100":6,"1102":1,"1116":1,"1140":1,"1168":1,"1175":1,"1212":2,"1231":2,"1232":2,"1233":2,"1237":10,"1251":4,"1252":6,"1253":3,"1257":2,"1258":1,"1259":32,"1260":1,"1265":7,"1270":31,"1272":1,"1273":3,"1274":3,"1276":3,"1277":3,"1278":2,"1280":1,"1286":146,"1289":2,"1290":3,"1293":1,"1294":1,"1300":11,"1304":4,"1309":7,"1311":4,"1316":1,"1318":2,"1322":1,"1323":32,"1324":8,"1344":1,"1345":1,"1349":1,"1350":10,"1352":1,"1355":1,"1357":4,"1358":3,"1359":119,"1369":4,"1379":1,"1390":1,"1395":46,"1396":19,"1398":1,"1399":3,"1402":17,"1406":2,"1411":2,"1415":31,"1417":3,"1432":1,"1436":43,"1438":10,"1453":1,"1456":1,"1508":1,"1543":1,"1638":2,"1664":2,"1665":1,"1666":2,"1672":1,"1685":2,"1720":1,"1742":1,"1749":1,"1765":1,"1818":1,"1835":2,"1837":1,"1846":1,"1849":1,"1852":1,"1857":1,"1869":3,"1871":1,"1872":2,"1877":5,"1906":1,"1933":5,"1934":1,"1940":1,"1946":1,"1951":1,"1973":1,"2060":1,"2063":1,"2083":1,"2091":1,"2161":2,"2163":3,"2165":3,"2167":2,"2168":3,"2182":1,"2185":1,"2230":2,"2232":1}}],["savestate",{"3":{"1324":1}}],["savestagedasync",{"3":{"201":1,"1259":1}}],["saveskipped",{"3":{"926":1,"1270":3}}],["saves",{"3":{"0":2,"24":1,"27":2,"39":1,"195":1,"201":1,"499":1,"518":2,"522":3,"523":1,"524":1,"536":2,"540":2,"792":1,"897":1,"908":1,"921":1,"986":1,"988":3,"1042":1,"1045":1,"1061":1,"1100":1,"1115":1,"1143":1,"1237":2,"1258":1,"1259":4,"1260":1,"1265":2,"1270":3,"1286":18,"1289":1,"1300":7,"1309":3,"1318":1,"1323":4,"1324":3,"1346":2,"1352":1,"1355":1,"1359":17,"1369":3,"1373":1,"1395":4,"1396":7,"1402":1,"1411":1,"1414":1,"1415":7,"1431":1,"1436":12,"1455":1,"1472":1,"1476":1,"1479":1,"1638":1,"1869":2,"1933":2,"1982":1,"2063":1,"2091":1,"2092":1,"2161":1}}],["sagastatemachineinstance",{"2":{"809":1,"813":1},"3":{"809":1,"813":1}}],["sagas",{"3":{"0":3,"536":1,"926":1,"1011":1,"1012":1,"1577":1}}],["saga",{"1":{"533":1,"534":1,"535":1,"536":1,"537":1,"538":1,"539":1,"540":1,"541":1,"2161":1,"2162":1,"2163":1,"2164":1,"2165":1,"2166":1,"2167":1,"2168":1,"2169":1},"3":{"0":4,"230":1,"533":1,"534":1,"536":9,"537":2,"539":1,"674":1,"809":2,"810":3,"811":2,"812":3,"813":2,"815":2,"896":1,"987":1,"1041":1,"1044":1,"1212":2,"1309":1,"1359":1,"1436":1,"1526":2,"1547":1,"1572":1,"1591":5,"1600":2,"1665":1,"1755":2,"1765":1,"1779":2,"2113":1,"2160":2,"2161":2,"2163":2,"2167":1,"2168":4,"2169":1,"2180":1,"2189":1,"2206":1,"2215":2,"2232":2}}],["salient",{"3":{"1795":1}}],["salvages",{"3":{"1300":1}}],["salvaged",{"3":{"1018":1}}],["saleable",{"3":{"1764":1}}],["sale",{"3":{"539":1,"1025":1,"1027":1,"1028":3,"1742":1,"1750":2,"1769":1,"1773":2}}],["salesrouteauthorizationtests",{"3":{"1436":1,"1591":1}}],["salesintegrationtestfixture",{"3":{"1436":1}}],["salescrossservicefactory",{"3":{"1436":1}}],["salestestwebapplicationfactory",{"3":{"1436":1}}],["salesfakeusecase",{"3":{"1199":2,"1207":2}}],["salesfakeorder",{"3":{"1199":2,"1207":2}}],["salesfakeaggregate",{"3":{"1199":2,"1207":2}}],["salesfeatures",{"2":{"2136":1},"3":{"300":1,"1270":1,"2136":2}}],["salesuimodule",{"3":{"649":1,"1324":4}}],["salesuserdataexportsection",{"2":{"583":1},"3":{"585":1,"725":1,"1323":4}}],["salesmoduletests",{"3":{"1436":2}}],["salesmoduleseeder",{"3":{"1323":2}}],["salesmoduledbseeder",{"3":{"1286":2}}],["salesmodule",{"3":{"539":1,"540":2,"583":2,"584":1,"674":1,"676":1,"1323":1}}],["salesapp",{"2":{"538":1},"3":{"538":1}}],["salespermissiongrants",{"3":{"186":1}}],["salespermissions",{"2":{"318":1,"324":1,"1264":1,"1995":1},"3":{"186":1,"318":1,"324":1,"1264":1,"1995":1}}],["salesservice",{"2":{"96":1},"3":{"96":1}}],["sales",{"0":{"96":1,"1749":1},"1":{"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1},"2":{"62":1,"96":2,"493":1,"534":1,"794":1,"1588":1,"1748":1,"1751":1,"1766":1,"2167":1},"3":{"0":11,"24":1,"62":2,"72":6,"73":4,"80":3,"91":8,"94":1,"95":4,"96":12,"97":2,"98":1,"99":1,"100":1,"101":3,"102":1,"104":3,"117":2,"128":2,"165":1,"186":2,"195":4,"230":7,"235":2,"238":1,"243":3,"245":2,"250":1,"251":1,"252":1,"254":1,"257":1,"260":4,"261":1,"291":1,"295":1,"318":6,"324":1,"401":1,"448":1,"452":3,"453":1,"454":1,"459":1,"475":4,"476":4,"477":4,"493":1,"534":1,"535":2,"536":3,"539":4,"540":1,"545":3,"554":1,"556":2,"564":3,"572":2,"583":11,"584":1,"585":4,"586":1,"599":2,"602":1,"640":2,"643":1,"649":3,"657":2,"674":7,"676":2,"715":1,"725":1,"728":1,"743":2,"749":2,"752":1,"757":1,"774":3,"780":2,"782":9,"789":5,"790":20,"792":1,"793":13,"794":9,"795":1,"798":1,"804":1,"809":3,"813":4,"837":1,"838":4,"841":1,"868":1,"869":1,"870":1,"873":2,"876":3,"877":1,"881":3,"897":2,"900":2,"914":1,"926":6,"954":4,"988":4,"1023":1,"1025":9,"1026":11,"1027":2,"1029":2,"1054":1,"1083":1,"1085":1,"1161":1,"1207":6,"1212":3,"1233":1,"1237":10,"1259":3,"1264":4,"1265":1,"1270":14,"1286":18,"1300":21,"1301":1,"1309":4,"1310":4,"1311":23,"1312":4,"1323":10,"1324":16,"1339":13,"1436":30,"1486":1,"1496":2,"1541":1,"1588":2,"1589":1,"1591":21,"1596":3,"1600":6,"1631":1,"1742":1,"1746":17,"1748":1,"1749":4,"1751":14,"1752":1,"1757":1,"1761":1,"1763":1,"1764":16,"1765":40,"1766":1,"1769":9,"1770":3,"1771":1,"1772":5,"1775":2,"1785":1,"1839":1,"1874":1,"1875":1,"1963":1,"1995":1,"1997":1,"2025":1,"2087":1,"2110":1,"2111":1,"2113":1,"2131":1,"2136":1,"2138":1,"2161":1,"2166":2,"2167":3,"2168":1,"2187":1,"2189":1,"2220":1,"2228":1,"2232":1}}],["salted",{"0":{"1902":1},"3":{"309":1,"905":1,"1300":1}}],["saltsize",{"2":{"284":1,"944":1,"948":1},"3":{"284":1,"310":1,"944":1,"946":3,"948":1,"1300":5}}],["salt",{"3":{"0":7,"284":2,"309":1,"310":6,"311":2,"312":5,"314":2,"350":1,"352":1,"906":1,"944":1,"945":4,"946":10,"947":1,"948":3,"1212":3,"1286":1,"1291":1,"1292":2,"1300":29,"1323":3,"1410":1,"1415":8,"1436":2,"1577":1,"1630":1,"1641":4,"1667":1,"1673":1,"1747":1,"1765":2,"1900":2,"1902":7,"1905":3,"1977":2,"1978":1,"2067":1}}],["sample=",{"3":{"1436":1}}],["sampleid",{"3":{"1436":1}}],["sampleitem",{"3":{"1199":4,"1207":1,"1496":1}}],["samplev1tov2upcaster",{"3":{"1199":2,"1207":1}}],["samplemodel",{"3":{"1199":2,"1207":1}}],["samplegriddata",{"3":{"1199":1,"1207":1,"1436":4}}],["samplegridrow",{"3":{"1199":2,"1207":3,"1436":9}}],["samplegatewayhardeningtests",{"3":{"1199":1,"1207":1,"1436":1}}],["samplegatewayentrypoint",{"3":{"1199":2,"1207":1}}],["sampleapphostcollection",{"2":{"1430":1},"3":{"1199":2,"1207":2,"1430":2,"1487":1}}],["sampleapphostfixture",{"3":{"1069":1,"1199":3,"1207":2,"1430":2,"1487":1}}],["sampleapphosttests",{"3":{"1067":1,"1199":1,"1207":2,"1430":2,"1487":1}}],["sampler",{"3":{"398":1,"402":1,"1339":2,"1575":1,"1577":2,"1585":1,"1678":1}}],["sampleservice",{"3":{"1199":2,"1207":1}}],["samples",{"0":{"1679":1},"3":{"397":1,"599":2,"601":1,"781":1,"963":1,"1300":1,"1350":1,"1431":1,"1436":8,"1438":1,"1443":1,"1467":3,"1470":1,"1496":3,"1575":2,"1577":10,"1581":1,"1582":2,"1583":1,"1585":1,"1586":3,"1648":1,"1670":1,"1671":1,"1677":1,"2109":1,"2156":1}}],["sample",{"0":{"1364":1},"2":{"1453":1},"3":{"0":5,"76":1,"80":2,"181":1,"295":1,"397":1,"399":1,"405":1,"543":1,"550":1,"554":1,"556":1,"597":1,"599":2,"604":2,"718":1,"749":1,"780":1,"782":1,"861":8,"862":2,"863":2,"905":1,"913":1,"917":1,"964":1,"1050":1,"1067":8,"1069":2,"1090":1,"1229":1,"1237":2,"1264":1,"1270":2,"1286":1,"1311":1,"1323":1,"1350":3,"1359":1,"1364":6,"1369":11,"1380":5,"1415":1,"1431":1,"1436":31,"1437":1,"1438":1,"1441":1,"1443":1,"1446":1,"1453":3,"1467":5,"1486":2,"1487":1,"1488":1,"1490":1,"1496":1,"1526":1,"1575":3,"1576":1,"1577":10,"1581":1,"1582":3,"1583":2,"1586":1,"1602":1,"1654":1,"1661":1,"1673":1,"1674":2,"1677":1,"1683":1,"1685":2,"1686":2,"1687":1,"1688":2,"1689":3,"1690":1,"1697":2,"1717":1,"1718":1,"1719":1,"1727":6,"1728":1,"1729":1,"1800":1,"1815":1,"1891":1,"2063":1,"2089":1,"2110":1,"2156":1,"2160":2}}],["sampledeploymentobservabilitytests",{"2":{"1431":1},"3":{"1199":1,"1207":2,"1431":4,"1436":7,"1467":1,"1496":1,"1577":2,"1583":1,"1586":2}}],["sampled",{"3":{"0":1,"135":1,"397":1,"398":2,"399":1,"861":1,"1333":2,"1339":3,"1443":1,"1446":1,"1467":3,"1496":2,"1677":1,"2010":2,"2156":1,"2157":1,"2159":1}}],["samplingduration",{"3":{"819":1,"1339":3}}],["sampling",{"2":{"1330":1},"3":{"0":4,"68":1,"70":1,"71":1,"100":1,"395":1,"397":1,"398":2,"399":4,"402":1,"406":1,"914":1,"916":1,"918":1,"1212":1,"1259":1,"1312":1,"1330":2,"1333":2,"1339":7,"1438":1,"1443":3,"1467":3,"1469":1,"1568":1,"1591":1,"1677":1,"1678":1,"2006":1,"2010":1,"2030":1,"2032":1,"2037":1,"2156":1,"2159":2,"2160":1,"2232":1}}],["sameevent",{"3":{"1396":1}}],["samesponsor",{"3":{"1396":1}}],["samesession",{"3":{"1396":1}}],["samesitemode",{"3":{"1300":4,"1324":2}}],["samesite",{"2":{"1187":1,"1969":1},"3":{"1184":3,"1185":1,"1187":1,"1300":21,"1311":1,"1324":3,"1667":1,"1969":1}}],["samesite=none",{"3":{"1311":1}}],["samesite=strict",{"3":{"0":1,"1324":1,"1641":2}}],["samesite=lax",{"3":{"0":1,"207":3,"209":1,"350":1,"1187":1,"1298":2,"1311":1,"1324":2,"1534":2,"1968":1,"1969":1,"1971":1,"1972":1,"1975":1,"2083":1}}],["sameoriginproxyendpoint",{"3":{"1324":1}}],["sameoriginproxyclienttests",{"3":{"1199":1,"1207":3,"1324":4,"1438":1}}],["sameoriginproxyinvoker",{"3":{"1199":4,"1203":1,"1207":2,"1324":7,"1496":1}}],["sameoriginproxytransformer",{"3":{"1184":2,"1199":2,"1203":1,"1207":3,"1300":3,"1324":13}}],["sameoriginproxytokenrefreshertests",{"3":{"1199":1,"1207":2,"1324":2}}],["sameoriginproxytokenrefresher",{"2":{"507":1,"509":1,"1582":1},"3":{"0":1,"507":5,"509":1,"1199":3,"1203":1,"1207":2,"1324":8,"1416":1,"1582":2}}],["sameoriginproxyheaders",{"3":{"1184":2,"1199":6,"1203":1,"1207":2,"1324":12,"1582":1}}],["sameoriginproxy",{"2":{"1324":1},"3":{"1184":8,"1203":15,"1207":54,"1208":2,"1300":11,"1324":47,"1437":1,"1438":2,"1496":1,"1582":1}}],["sameoriginproxyrequesthandler",{"2":{"1184":1},"3":{"1184":2,"1199":1,"1203":1,"1207":2,"1324":13}}],["sameoriginapiproxymarker",{"3":{"1199":4,"1203":1,"1207":1,"1324":5}}],["sameoriginapiproxyhubtests",{"3":{"1184":2,"1199":1,"1207":2,"1438":1}}],["sameoriginapiproxycsrftests",{"3":{"1184":2,"1199":1,"1207":2,"1438":1}}],["sameoriginapiproxyendpoint",{"3":{"1184":5,"1185":1,"1186":3,"1187":1,"1199":4,"1203":1,"1207":2,"1300":7,"1324":20,"1496":2,"1582":1}}],["sameoriginapiproxyendpointextensions",{"3":{"1184":1,"1199":1,"1203":1,"1207":2,"1208":1,"1324":13}}],["sameoriginapiproxy",{"3":{"1184":1,"1324":2,"1582":1}}],["sameoriginapiproxysettingsvalidator",{"3":{"1184":1,"1185":1,"1199":2,"1203":1,"1207":2,"1324":6}}],["sameoriginapiproxysettings",{"3":{"1184":3,"1199":7,"1203":1,"1207":2,"1300":2,"1324":11}}],["sameoriginapiproxyserviceextensions",{"3":{"1184":3,"1186":1,"1199":1,"1203":1,"1207":3,"1208":1,"1300":2,"1324":21,"1582":1}}],["sameoriginapiproxytokentests",{"2":{"1184":1},"3":{"1184":2,"1199":1,"1207":2,"1437":1,"1438":1}}],["sameoriginapiproxyoptintests",{"2":{"1184":1},"3":{"1184":2,"1199":1,"1207":2,"1324":1,"1438":1}}],["sameoriginapiproxyorigintests",{"2":{"1184":1},"3":{"1184":2,"1199":1,"1207":2,"1438":1}}],["sameoriginapiproxyauthflowtests",{"2":{"1184":1},"3":{"1184":2,"1199":1,"1207":2,"1324":1,"1438":1}}],["sameoriginapiproxyrefreshoutcometests",{"2":{"1184":1},"3":{"1184":2,"1199":1,"1207":2,"1438":1}}],["sameoriginapiendpoint",{"2":{"1184":1},"3":{"1184":4,"1324":16,"1438":1}}],["sameness",{"3":{"284":1}}],["same",{"0":{"1225":1,"1228":1,"1293":1,"1295":1,"1317":1,"1825":1,"1842":1,"1910":1,"1976":1,"1983":1,"2046":1,"2073":1},"1":{"712":1,"713":1,"714":1,"715":1,"716":1,"717":1,"718":1,"719":1,"720":1,"721":1,"1181":1,"1182":1,"1183":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1,"2070":1,"2071":1,"2072":1,"2073":1,"2074":1,"2075":1,"2076":1,"2077":1,"2078":1,"2079":1,"2080":1},"3":{"0":71,"17":1,"18":1,"20":2,"21":2,"23":1,"24":6,"26":8,"30":2,"31":1,"32":1,"39":1,"41":1,"42":2,"46":1,"53":2,"57":1,"59":2,"61":1,"62":1,"68":1,"69":1,"70":1,"71":1,"72":1,"76":1,"78":1,"79":1,"87":1,"91":1,"92":2,"93":2,"94":1,"95":3,"96":3,"99":3,"100":2,"103":1,"109":9,"110":4,"117":1,"118":1,"120":2,"122":4,"125":3,"126":2,"130":1,"132":1,"133":1,"135":5,"136":5,"139":1,"142":1,"145":4,"146":1,"147":4,"148":1,"150":2,"151":1,"156":1,"157":6,"158":1,"159":1,"160":2,"162":1,"164":1,"165":2,"166":1,"167":1,"173":1,"175":3,"178":1,"180":3,"185":1,"186":2,"188":1,"193":2,"194":1,"195":2,"196":1,"197":3,"200":2,"201":5,"202":10,"207":1,"213":1,"217":1,"218":1,"220":1,"221":1,"225":3,"226":2,"227":1,"230":4,"231":1,"235":3,"236":1,"237":1,"239":1,"242":1,"243":7,"245":3,"247":1,"248":1,"251":1,"255":4,"257":1,"265":9,"266":1,"272":1,"273":2,"274":1,"275":2,"279":1,"282":1,"283":3,"285":2,"290":1,"291":1,"295":6,"300":1,"302":1,"309":1,"310":3,"313":2,"314":1,"318":5,"320":1,"321":1,"325":2,"329":1,"330":1,"331":1,"337":1,"340":6,"341":6,"342":2,"343":1,"344":2,"346":4,"348":1,"349":3,"350":8,"351":1,"352":2,"353":1,"358":1,"359":3,"361":3,"362":1,"365":1,"370":3,"371":1,"373":1,"375":1,"376":1,"380":2,"381":1,"383":1,"386":1,"388":3,"390":1,"391":1,"392":1,"395":1,"396":1,"397":6,"401":4,"402":1,"403":1,"404":3,"406":2,"407":1,"408":1,"409":1,"412":1,"413":3,"414":1,"415":3,"420":1,"422":1,"423":1,"424":2,"427":1,"434":1,"435":1,"440":1,"443":3,"444":1,"446":1,"448":5,"449":1,"450":2,"452":1,"455":1,"458":1,"459":3,"460":1,"461":2,"462":1,"463":2,"464":1,"470":3,"472":1,"473":2,"475":2,"476":1,"477":2,"480":1,"481":1,"482":1,"485":1,"486":1,"487":1,"488":2,"489":2,"490":1,"491":1,"492":2,"493":3,"494":4,"499":4,"500":1,"503":2,"506":2,"507":5,"508":2,"509":2,"513":1,"514":1,"517":3,"519":1,"520":1,"524":2,"526":1,"528":4,"529":2,"530":2,"532":2,"536":8,"537":2,"538":3,"539":7,"543":3,"544":1,"545":4,"546":1,"549":5,"550":1,"551":1,"555":1,"556":8,"557":2,"558":2,"560":2,"564":3,"565":1,"566":2,"572":1,"573":1,"574":2,"575":1,"577":2,"579":1,"582":2,"583":3,"584":2,"585":2,"587":1,"591":5,"592":7,"599":9,"600":1,"601":1,"602":1,"603":1,"604":1,"605":3,"607":1,"609":9,"610":2,"611":4,"613":1,"614":1,"617":2,"618":1,"619":1,"622":1,"624":3,"626":10,"627":1,"628":1,"629":3,"633":3,"635":2,"638":2,"639":1,"640":8,"641":3,"642":4,"644":4,"648":2,"649":2,"650":4,"651":1,"657":6,"658":5,"659":2,"665":1,"667":2,"668":2,"674":13,"675":1,"676":1,"681":1,"682":2,"683":1,"684":1,"686":1,"689":1,"690":6,"691":2,"692":1,"694":1,"698":6,"699":1,"702":2,"707":7,"709":1,"711":1,"712":1,"714":2,"715":3,"716":4,"718":1,"721":1,"724":3,"725":6,"726":4,"729":1,"732":2,"733":2,"734":1,"740":5,"741":7,"742":1,"743":3,"747":1,"748":1,"749":4,"750":3,"751":2,"756":3,"757":1,"758":1,"759":1,"760":1,"761":1,"766":4,"767":1,"770":1,"773":1,"774":3,"776":1,"778":3,"782":7,"783":3,"784":1,"785":1,"786":1,"789":1,"790":3,"794":1,"795":1,"798":1,"799":2,"801":1,"803":2,"804":3,"805":6,"806":1,"809":1,"810":2,"819":1,"821":1,"825":3,"827":12,"829":6,"830":1,"831":4,"832":5,"833":1,"837":3,"838":8,"839":1,"840":1,"846":2,"854":1,"856":1,"859":1,"861":6,"862":1,"863":3,"865":3,"868":3,"869":1,"870":2,"871":1,"872":2,"873":1,"875":1,"876":3,"877":1,"880":1,"881":10,"882":1,"883":1,"885":1,"889":8,"890":4,"891":3,"897":2,"899":1,"901":1,"903":1,"904":2,"905":8,"906":1,"907":3,"910":3,"913":1,"914":3,"916":9,"921":1,"922":3,"923":5,"924":1,"926":5,"930":2,"931":1,"933":4,"935":1,"937":3,"939":2,"941":1,"942":1,"946":3,"948":2,"950":2,"953":1,"954":1,"955":2,"957":1,"958":1,"963":4,"964":1,"965":2,"966":1,"967":1,"968":1,"971":2,"972":3,"976":1,"979":4,"980":3,"981":1,"982":5,"988":3,"992":1,"995":5,"996":3,"997":2,"998":4,"1000":1,"1004":2,"1012":2,"1016":1,"1018":10,"1019":2,"1020":1,"1022":1,"1026":3,"1027":1,"1028":2,"1033":1,"1034":8,"1035":3,"1041":2,"1042":7,"1044":4,"1049":1,"1050":7,"1051":1,"1052":2,"1055":1,"1058":2,"1059":5,"1061":4,"1066":2,"1067":1,"1068":4,"1069":2,"1074":3,"1075":4,"1076":1,"1079":1,"1084":1,"1090":1,"1091":3,"1093":1,"1100":1,"1108":2,"1116":3,"1117":1,"1118":2,"1123":1,"1124":8,"1126":3,"1127":1,"1133":3,"1134":1,"1137":1,"1142":2,"1145":1,"1147":1,"1150":1,"1162":1,"1167":2,"1168":1,"1175":2,"1176":1,"1178":1,"1179":1,"1181":1,"1182":1,"1183":1,"1184":5,"1185":1,"1186":1,"1187":1,"1193":1,"1211":3,"1212":15,"1214":1,"1216":1,"1217":1,"1218":2,"1219":1,"1220":2,"1222":1,"1223":2,"1225":3,"1226":1,"1227":1,"1228":1,"1229":26,"1231":6,"1232":1,"1233":4,"1234":1,"1235":3,"1236":1,"1237":55,"1239":3,"1241":3,"1242":3,"1243":1,"1244":5,"1247":53,"1248":1,"1249":1,"1251":2,"1252":3,"1253":7,"1254":1,"1255":2,"1256":5,"1257":1,"1258":5,"1259":68,"1260":1,"1261":2,"1262":1,"1263":5,"1265":7,"1267":6,"1270":103,"1271":35,"1272":1,"1273":3,"1274":6,"1275":9,"1276":2,"1277":2,"1278":1,"1279":1,"1280":1,"1281":4,"1283":3,"1284":2,"1285":1,"1286":274,"1288":3,"1289":7,"1290":2,"1292":1,"1293":1,"1294":4,"1295":3,"1296":1,"1297":4,"1298":2,"1300":210,"1301":48,"1302":3,"1303":1,"1304":3,"1305":2,"1306":2,"1307":2,"1308":1,"1309":57,"1310":14,"1311":159,"1312":15,"1313":2,"1316":1,"1317":3,"1318":6,"1320":7,"1322":1,"1323":153,"1324":302,"1326":2,"1327":1,"1328":4,"1330":2,"1331":1,"1332":3,"1333":1,"1334":2,"1335":1,"1336":1,"1337":6,"1338":2,"1339":124,"1340":1,"1341":1,"1342":2,"1343":3,"1344":1,"1345":1,"1347":4,"1348":2,"1349":1,"1350":117,"1352":6,"1353":7,"1355":5,"1356":6,"1357":2,"1358":2,"1359":435,"1361":1,"1362":2,"1367":1,"1369":37,"1370":1,"1371":2,"1372":3,"1373":1,"1374":1,"1376":2,"1377":2,"1378":3,"1379":2,"1380":107,"1381":1,"1383":1,"1384":5,"1385":2,"1387":2,"1388":1,"1389":1,"1390":1,"1391":4,"1392":5,"1393":1,"1395":246,"1396":299,"1397":1,"1398":3,"1399":2,"1400":3,"1401":3,"1402":98,"1404":2,"1405":1,"1406":5,"1407":4,"1408":3,"1409":2,"1410":2,"1411":2,"1413":1,"1415":148,"1416":48,"1417":97,"1418":1,"1419":1,"1421":1,"1422":1,"1423":3,"1425":5,"1426":1,"1427":27,"1428":1,"1429":2,"1430":1,"1431":2,"1432":2,"1433":5,"1435":10,"1436":456,"1437":1,"1438":39,"1440":5,"1441":12,"1442":7,"1443":8,"1444":4,"1445":9,"1446":5,"1447":4,"1450":1,"1453":11,"1454":17,"1455":28,"1456":6,"1457":3,"1458":1,"1459":9,"1460":4,"1461":2,"1462":1,"1463":2,"1465":9,"1466":1,"1467":55,"1469":1,"1470":2,"1471":1,"1473":4,"1474":6,"1475":9,"1476":3,"1477":1,"1478":3,"1481":2,"1486":4,"1487":1,"1488":10,"1489":13,"1490":7,"1491":2,"1492":20,"1493":1,"1496":29,"1498":1,"1500":1,"1504":1,"1506":1,"1507":2,"1508":2,"1509":1,"1512":1,"1513":2,"1526":11,"1530":1,"1531":3,"1534":1,"1537":1,"1545":1,"1554":1,"1557":2,"1565":1,"1570":2,"1577":10,"1581":1,"1582":4,"1585":1,"1589":1,"1591":13,"1596":3,"1598":1,"1599":3,"1600":2,"1602":1,"1608":1,"1611":3,"1627":5,"1630":2,"1631":12,"1632":8,"1635":2,"1637":1,"1638":5,"1639":11,"1640":8,"1641":14,"1646":1,"1648":2,"1651":6,"1652":3,"1653":5,"1654":1,"1656":1,"1661":1,"1662":3,"1663":1,"1664":2,"1665":1,"1667":1,"1668":2,"1669":1,"1670":3,"1671":3,"1673":2,"1674":1,"1677":3,"1679":1,"1681":1,"1684":1,"1685":1,"1686":4,"1689":1,"1692":1,"1693":2,"1695":1,"1697":1,"1699":2,"1701":3,"1702":4,"1705":3,"1707":2,"1708":3,"1717":5,"1719":1,"1722":1,"1727":6,"1729":1,"1730":1,"1732":1,"1735":2,"1736":1,"1737":1,"1742":1,"1748":4,"1749":7,"1750":1,"1751":2,"1756":1,"1759":1,"1761":1,"1765":13,"1766":1,"1767":8,"1769":2,"1772":1,"1776":1,"1780":2,"1782":2,"1783":2,"1784":1,"1785":2,"1790":2,"1791":2,"1795":2,"1796":1,"1800":1,"1802":1,"1805":7,"1806":1,"1810":8,"1815":3,"1820":1,"1821":3,"1823":1,"1825":2,"1827":2,"1829":1,"1833":2,"1834":3,"1837":1,"1838":1,"1839":3,"1840":1,"1841":1,"1842":4,"1844":1,"1845":2,"1846":2,"1847":1,"1848":1,"1849":3,"1850":1,"1851":1,"1853":4,"1854":1,"1858":1,"1861":2,"1862":1,"1866":1,"1868":1,"1869":4,"1870":4,"1872":2,"1873":1,"1874":4,"1875":3,"1876":3,"1877":2,"1878":1,"1883":2,"1884":3,"1888":1,"1889":2,"1891":1,"1893":3,"1897":1,"1899":3,"1902":2,"1903":1,"1905":1,"1906":4,"1907":1,"1908":5,"1909":7,"1910":3,"1911":2,"1912":5,"1914":1,"1915":1,"1916":4,"1918":2,"1919":1,"1920":3,"1922":1,"1923":5,"1924":2,"1925":1,"1926":2,"1927":7,"1928":3,"1929":2,"1930":2,"1934":4,"1935":2,"1936":1,"1941":1,"1942":1,"1943":2,"1944":2,"1945":2,"1947":2,"1948":8,"1951":2,"1952":1,"1953":1,"1954":1,"1955":2,"1956":1,"1960":1,"1962":4,"1963":1,"1964":1,"1968":1,"1972":1,"1974":2,"1975":1,"1976":2,"1977":1,"1978":1,"1980":1,"1981":2,"1982":3,"1983":4,"1984":2,"1987":1,"1988":4,"1989":2,"1992":1,"1993":1,"1994":4,"1995":11,"1996":1,"1997":1,"1999":1,"2000":2,"2001":4,"2002":1,"2007":1,"2008":2,"2009":1,"2010":2,"2011":1,"2012":2,"2013":1,"2014":1,"2017":1,"2018":1,"2019":1,"2020":3,"2023":1,"2024":1,"2025":1,"2026":2,"2028":2,"2029":2,"2030":2,"2031":1,"2032":2,"2034":1,"2036":1,"2038":1,"2041":1,"2042":2,"2043":3,"2044":5,"2045":1,"2046":3,"2047":6,"2048":1,"2049":2,"2053":1,"2055":2,"2056":1,"2057":1,"2061":3,"2062":1,"2063":1,"2065":1,"2066":1,"2067":2,"2069":1,"2070":3,"2071":1,"2073":1,"2074":1,"2075":4,"2077":2,"2078":2,"2080":2,"2081":7,"2082":2,"2083":5,"2084":4,"2086":3,"2089":2,"2091":1,"2092":1,"2100":2,"2101":1,"2103":1,"2105":1,"2107":1,"2110":1,"2111":9,"2112":2,"2113":3,"2115":5,"2116":2,"2117":1,"2118":2,"2120":2,"2122":2,"2123":1,"2124":1,"2125":1,"2126":3,"2131":5,"2132":2,"2134":3,"2135":1,"2137":1,"2138":1,"2139":1,"2141":1,"2142":2,"2144":3,"2146":1,"2147":1,"2149":1,"2150":1,"2151":2,"2154":3,"2155":3,"2156":5,"2157":5,"2158":8,"2159":1,"2160":1,"2161":1,"2164":3,"2165":1,"2166":2,"2167":6,"2168":3,"2169":3,"2170":1,"2172":2,"2176":1,"2177":3,"2181":1,"2182":1,"2183":1,"2184":1,"2185":1,"2186":3,"2188":1,"2189":2,"2190":4,"2192":5,"2193":3,"2196":2,"2198":1,"2199":1,"2202":6,"2210":1,"2229":1,"2230":3,"2232":8,"2240":2,"2241":1}}],["s",{"0":{"93":1,"95":1,"96":1,"138":1,"139":1,"1259":1,"1291":1,"1323":1,"1447":1,"1881":1,"1883":1,"1989":1,"2025":1,"2044":1,"2185":1},"1":{"1039":1,"1040":1,"1041":1,"1042":1,"1043":1,"1044":1,"1045":1,"1046":1},"2":{"1239":1,"1353":1,"1813":2},"3":{"0":269,"6":1,"7":2,"10":2,"13":1,"15":1,"20":2,"21":5,"22":1,"24":3,"25":1,"26":2,"27":6,"30":1,"31":7,"37":1,"38":2,"39":6,"40":2,"41":1,"42":4,"43":2,"46":2,"47":1,"49":1,"51":1,"52":2,"53":10,"54":2,"57":3,"59":5,"60":1,"62":5,"66":4,"68":1,"71":6,"72":6,"73":6,"74":1,"76":1,"77":1,"78":2,"79":1,"80":8,"81":4,"82":2,"91":7,"92":4,"93":6,"94":4,"95":9,"96":6,"97":3,"98":3,"99":6,"100":9,"101":3,"102":2,"103":4,"104":2,"109":9,"111":2,"115":3,"117":2,"121":2,"122":5,"123":2,"125":2,"126":2,"127":3,"128":1,"130":6,"131":1,"132":1,"134":1,"135":15,"136":8,"137":4,"138":2,"139":5,"140":3,"141":3,"142":5,"145":7,"147":5,"149":2,"150":2,"152":1,"157":3,"159":2,"160":5,"161":2,"162":1,"164":1,"165":1,"166":6,"168":1,"169":1,"170":2,"173":1,"174":2,"175":7,"177":1,"178":1,"179":4,"180":6,"184":3,"186":20,"187":2,"189":1,"190":1,"193":5,"194":1,"195":4,"196":1,"199":1,"200":2,"201":6,"202":2,"208":1,"209":2,"212":2,"215":1,"217":3,"218":4,"219":4,"225":6,"226":3,"228":3,"230":7,"231":2,"233":1,"234":1,"235":7,"237":5,"241":10,"242":1,"243":9,"245":4,"246":3,"247":5,"248":1,"249":2,"250":2,"251":3,"252":2,"253":1,"254":5,"255":10,"256":7,"257":5,"258":4,"259":9,"260":2,"261":4,"264":1,"265":15,"267":2,"268":1,"273":3,"274":1,"275":3,"276":1,"279":3,"280":1,"282":3,"284":2,"286":1,"290":4,"291":3,"292":2,"293":1,"295":3,"296":1,"298":2,"300":1,"301":1,"302":1,"303":1,"305":4,"306":1,"309":1,"310":6,"311":1,"313":1,"314":3,"317":2,"318":17,"320":3,"322":1,"324":3,"325":4,"326":6,"330":1,"332":1,"333":4,"340":1,"341":13,"342":2,"343":2,"344":2,"345":1,"348":5,"349":3,"350":15,"351":3,"352":6,"353":8,"354":1,"355":1,"359":3,"361":3,"364":1,"365":2,"366":1,"369":1,"370":1,"373":7,"374":1,"375":1,"382":1,"383":2,"386":8,"387":5,"388":2,"389":2,"390":7,"391":5,"392":8,"393":2,"396":1,"397":5,"400":5,"401":3,"402":7,"404":2,"407":4,"408":1,"409":2,"411":1,"412":2,"413":3,"414":2,"415":2,"418":4,"420":6,"421":1,"422":2,"423":1,"426":2,"427":2,"428":3,"429":3,"430":2,"434":3,"435":2,"441":2,"447":1,"448":11,"450":2,"451":3,"452":4,"453":1,"454":4,"455":1,"458":3,"459":12,"460":2,"461":2,"462":1,"463":5,"464":2,"465":7,"466":3,"468":7,"469":2,"472":1,"473":1,"474":2,"475":2,"476":4,"477":4,"478":1,"481":3,"483":1,"484":1,"487":1,"488":1,"489":2,"490":5,"491":1,"492":1,"493":1,"494":1,"497":8,"498":1,"499":9,"500":1,"501":2,"502":3,"503":1,"506":2,"507":3,"509":4,"510":1,"511":1,"512":1,"513":1,"517":3,"518":1,"522":1,"524":2,"527":1,"528":3,"530":5,"532":1,"534":5,"535":1,"536":10,"537":3,"538":2,"539":4,"540":4,"541":1,"543":5,"545":12,"548":1,"549":8,"550":4,"551":6,"554":1,"556":14,"557":3,"558":2,"562":2,"564":7,"565":1,"566":3,"568":1,"572":15,"573":3,"575":3,"576":1,"577":3,"578":3,"579":1,"582":1,"583":12,"584":2,"585":6,"586":1,"587":3,"590":1,"591":5,"592":1,"593":1,"598":3,"599":5,"601":3,"602":3,"603":3,"604":1,"607":19,"609":22,"610":2,"611":3,"612":2,"613":1,"614":2,"616":1,"617":4,"618":11,"619":3,"620":1,"622":1,"624":2,"626":10,"627":1,"629":4,"631":3,"632":1,"633":20,"634":2,"635":8,"636":3,"638":2,"640":13,"642":3,"643":1,"644":4,"649":14,"650":1,"651":8,"655":5,"656":1,"657":7,"658":4,"659":1,"660":1,"663":1,"664":1,"665":4,"666":1,"667":1,"668":1,"674":17,"675":1,"676":1,"677":4,"681":3,"682":7,"683":5,"684":6,"685":1,"688":1,"689":3,"690":4,"691":1,"692":1,"693":1,"694":1,"696":1,"697":1,"698":8,"699":2,"700":6,"702":2,"703":1,"706":1,"707":2,"708":3,"709":3,"711":1,"714":4,"715":7,"716":4,"717":5,"718":3,"719":1,"720":2,"724":1,"725":11,"726":4,"727":6,"729":1,"731":3,"732":2,"733":11,"734":3,"735":6,"736":2,"737":1,"739":2,"740":2,"741":6,"742":1,"743":4,"744":1,"745":1,"749":7,"750":2,"751":4,"755":2,"757":5,"758":1,"759":1,"760":3,"761":1,"764":8,"766":13,"767":2,"768":1,"769":3,"774":2,"775":1,"776":2,"780":6,"781":1,"782":9,"783":1,"784":3,"785":1,"789":1,"790":5,"791":3,"792":2,"793":1,"798":2,"799":4,"800":1,"802":1,"803":7,"804":1,"805":1,"808":2,"809":5,"810":8,"811":2,"812":3,"813":3,"814":1,"815":1,"817":3,"818":3,"819":4,"820":1,"821":1,"822":1,"825":8,"826":7,"827":28,"828":1,"829":7,"830":2,"831":5,"832":9,"833":6,"834":1,"836":1,"837":6,"838":20,"839":3,"840":7,"841":2,"842":1,"844":1,"845":1,"846":4,"847":3,"848":3,"849":3,"850":1,"853":3,"854":6,"855":1,"857":3,"860":2,"861":6,"862":1,"863":7,"864":4,"867":3,"868":2,"870":2,"873":4,"876":9,"877":2,"879":1,"880":1,"881":16,"882":1,"884":3,"889":4,"891":5,"892":3,"895":1,"896":1,"897":14,"898":1,"900":2,"901":1,"903":1,"904":5,"905":26,"906":7,"907":7,"908":1,"909":1,"910":2,"912":2,"913":4,"914":9,"916":6,"917":2,"918":3,"920":3,"921":4,"922":10,"923":2,"924":5,"925":1,"926":10,"927":1,"931":1,"932":1,"934":1,"944":1,"945":1,"946":3,"947":1,"949":1,"952":1,"953":1,"954":14,"956":4,"958":2,"959":2,"960":3,"962":1,"964":6,"965":3,"966":1,"971":4,"972":9,"973":4,"974":3,"979":4,"980":2,"981":1,"982":1,"983":2,"986":3,"987":1,"988":13,"990":3,"991":1,"992":1,"994":4,"995":4,"996":8,"997":1,"998":5,"999":2,"1003":1,"1004":6,"1005":2,"1006":2,"1007":1,"1010":1,"1011":1,"1012":4,"1013":1,"1014":2,"1016":1,"1017":2,"1018":22,"1019":4,"1020":1,"1021":2,"1022":1,"1024":2,"1025":1,"1026":9,"1028":5,"1029":1,"1030":1,"1033":4,"1034":9,"1035":2,"1036":3,"1037":2,"1038":1,"1039":1,"1040":1,"1042":12,"1043":3,"1044":5,"1045":1,"1048":3,"1049":5,"1050":8,"1051":5,"1052":5,"1054":3,"1055":3,"1057":1,"1058":7,"1059":9,"1060":2,"1061":3,"1063":1,"1066":3,"1067":15,"1068":2,"1069":9,"1070":3,"1073":3,"1074":9,"1075":5,"1076":1,"1077":1,"1078":1,"1079":1,"1081":1,"1082":4,"1083":1,"1085":2,"1086":2,"1089":3,"1090":2,"1091":14,"1092":3,"1093":8,"1094":2,"1099":3,"1100":1,"1103":2,"1104":1,"1108":1,"1114":1,"1115":1,"1116":18,"1117":5,"1118":3,"1119":2,"1122":1,"1123":3,"1124":17,"1125":5,"1126":2,"1127":3,"1128":1,"1133":7,"1135":3,"1140":2,"1142":2,"1144":2,"1147":1,"1149":1,"1150":5,"1151":1,"1155":1,"1156":1,"1157":1,"1160":2,"1161":4,"1162":1,"1168":3,"1170":1,"1175":5,"1176":4,"1178":2,"1182":1,"1184":11,"1186":1,"1187":1,"1191":1,"1192":2,"1193":3,"1194":2,"1196":3,"1201":1,"1202":2,"1203":2,"1211":1,"1212":41,"1213":3,"1214":6,"1215":1,"1216":3,"1218":1,"1219":1,"1221":1,"1223":2,"1224":1,"1226":1,"1227":1,"1228":3,"1229":56,"1230":4,"1231":4,"1232":2,"1233":4,"1235":2,"1236":5,"1237":145,"1238":2,"1239":10,"1240":3,"1241":5,"1242":2,"1243":2,"1244":5,"1246":3,"1247":217,"1248":1,"1249":5,"1251":4,"1252":2,"1253":7,"1254":1,"1256":2,"1257":1,"1258":8,"1259":216,"1260":3,"1262":8,"1263":9,"1264":11,"1265":18,"1266":3,"1267":7,"1268":3,"1269":4,"1270":355,"1271":91,"1272":1,"1273":11,"1274":4,"1275":7,"1276":13,"1277":5,"1278":17,"1279":5,"1280":2,"1281":8,"1282":5,"1283":6,"1284":2,"1285":3,"1286":977,"1287":2,"1288":1,"1289":13,"1290":19,"1291":1,"1292":2,"1293":5,"1294":4,"1295":4,"1296":3,"1297":10,"1298":5,"1300":773,"1301":79,"1302":3,"1303":1,"1304":8,"1305":2,"1306":1,"1307":8,"1308":3,"1309":297,"1310":70,"1311":527,"1312":71,"1314":5,"1315":9,"1316":12,"1317":3,"1318":8,"1319":4,"1320":6,"1321":1,"1322":6,"1323":478,"1324":1026,"1325":1,"1326":8,"1327":5,"1328":7,"1329":1,"1330":4,"1331":3,"1332":5,"1333":10,"1334":4,"1335":1,"1336":5,"1337":4,"1338":8,"1339":433,"1340":3,"1341":5,"1342":10,"1343":2,"1344":3,"1345":2,"1346":3,"1347":6,"1348":9,"1349":5,"1350":480,"1351":4,"1352":14,"1353":12,"1354":6,"1355":13,"1356":3,"1357":14,"1358":6,"1359":1774,"1360":5,"1362":9,"1363":5,"1364":5,"1366":13,"1367":5,"1368":7,"1369":147,"1371":5,"1372":5,"1373":4,"1374":3,"1375":6,"1376":3,"1377":7,"1378":12,"1379":9,"1380":336,"1381":3,"1382":5,"1383":7,"1384":14,"1385":6,"1386":2,"1387":7,"1388":5,"1389":13,"1390":3,"1391":7,"1392":4,"1393":1,"1394":1,"1395":955,"1396":1023,"1397":2,"1398":5,"1399":7,"1400":5,"1401":7,"1402":471,"1404":1,"1405":4,"1406":5,"1407":3,"1409":1,"1410":5,"1411":3,"1412":4,"1413":1,"1414":4,"1415":645,"1416":134,"1417":394,"1418":3,"1420":3,"1421":4,"1422":6,"1423":6,"1424":8,"1425":3,"1426":4,"1427":165,"1428":1,"1429":7,"1430":3,"1431":20,"1432":3,"1433":6,"1434":6,"1435":14,"1436":1906,"1437":28,"1438":92,"1439":1,"1441":31,"1442":7,"1443":38,"1444":4,"1445":8,"1446":8,"1447":23,"1449":4,"1450":6,"1451":5,"1452":2,"1453":10,"1454":9,"1455":44,"1456":17,"1457":4,"1458":4,"1459":14,"1460":11,"1461":8,"1463":1,"1465":10,"1466":4,"1467":90,"1469":1,"1470":4,"1471":3,"1472":2,"1473":6,"1474":5,"1475":18,"1476":21,"1477":1,"1478":1,"1479":2,"1480":1,"1481":3,"1483":1,"1484":2,"1486":10,"1487":5,"1488":30,"1489":30,"1490":16,"1491":9,"1492":34,"1494":1,"1496":105,"1497":2,"1498":3,"1500":5,"1507":1,"1509":1,"1512":2,"1513":3,"1515":1,"1517":1,"1518":1,"1522":1,"1523":4,"1524":5,"1525":3,"1526":42,"1527":2,"1528":2,"1529":1,"1531":9,"1532":2,"1534":14,"1536":1,"1537":1,"1541":1,"1542":1,"1543":1,"1546":2,"1547":1,"1553":3,"1558":1,"1559":1,"1565":1,"1567":2,"1569":2,"1571":1,"1573":1,"1575":6,"1576":1,"1577":41,"1578":2,"1579":2,"1582":10,"1583":1,"1585":8,"1586":2,"1587":1,"1588":1,"1589":5,"1591":42,"1592":1,"1595":3,"1596":21,"1597":3,"1598":2,"1599":8,"1600":7,"1607":2,"1610":1,"1611":2,"1615":1,"1621":2,"1625":1,"1626":2,"1627":6,"1629":1,"1630":23,"1631":31,"1632":16,"1635":5,"1636":1,"1637":3,"1638":6,"1639":20,"1640":10,"1641":22,"1644":1,"1645":1,"1650":3,"1651":13,"1652":1,"1653":10,"1654":1,"1656":7,"1658":1,"1661":3,"1662":3,"1663":7,"1664":3,"1665":4,"1666":6,"1667":3,"1668":3,"1669":5,"1670":1,"1671":4,"1674":2,"1675":2,"1676":1,"1677":4,"1678":2,"1679":2,"1680":2,"1683":3,"1684":6,"1685":17,"1686":9,"1687":2,"1688":3,"1689":2,"1690":1,"1693":1,"1695":1,"1697":2,"1699":1,"1700":1,"1701":6,"1702":6,"1703":3,"1706":1,"1707":5,"1708":4,"1709":4,"1710":1,"1718":2,"1719":5,"1720":2,"1722":2,"1723":1,"1724":4,"1726":2,"1727":20,"1728":7,"1729":7,"1730":5,"1731":4,"1741":3,"1742":1,"1744":1,"1748":8,"1749":8,"1750":1,"1751":2,"1756":1,"1759":3,"1760":1,"1761":2,"1764":8,"1765":22,"1767":8,"1768":1,"1769":10,"1770":3,"1771":2,"1772":1,"1773":1,"1778":1,"1779":3,"1781":1,"1782":1,"1784":1,"1789":1,"1790":4,"1792":1,"1795":1,"1797":1,"1799":2,"1800":1,"1802":2,"1805":1,"1810":6,"1811":2,"1813":5,"1815":11,"1817":2,"1821":4,"1823":2,"1824":4,"1825":3,"1826":1,"1827":1,"1829":3,"1830":2,"1831":2,"1832":2,"1833":1,"1834":2,"1839":5,"1840":6,"1841":2,"1843":5,"1844":2,"1848":2,"1849":7,"1851":8,"1852":2,"1853":1,"1855":2,"1856":5,"1857":2,"1859":3,"1860":1,"1861":2,"1862":1,"1865":3,"1866":4,"1869":2,"1871":4,"1872":5,"1874":3,"1875":5,"1876":6,"1877":4,"1878":1,"1879":2,"1880":1,"1881":5,"1882":4,"1883":1,"1884":3,"1885":1,"1886":1,"1889":2,"1891":5,"1892":1,"1894":1,"1895":4,"1896":6,"1897":4,"1898":6,"1899":5,"1903":1,"1905":1,"1906":1,"1908":4,"1909":4,"1910":4,"1911":2,"1912":3,"1914":1,"1915":1,"1919":1,"1920":11,"1921":1,"1922":17,"1923":13,"1924":1,"1925":1,"1927":7,"1928":1,"1929":5,"1930":1,"1934":5,"1935":2,"1936":1,"1937":3,"1938":1,"1940":1,"1941":1,"1944":2,"1945":2,"1946":5,"1947":5,"1948":1,"1949":3,"1950":4,"1952":1,"1953":1,"1954":3,"1955":1,"1956":2,"1957":1,"1958":3,"1961":1,"1962":4,"1963":3,"1964":2,"1965":1,"1966":1,"1967":1,"1968":2,"1970":3,"1971":1,"1972":2,"1973":3,"1974":3,"1975":1,"1976":2,"1977":4,"1978":8,"1979":1,"1981":1,"1982":6,"1983":5,"1985":3,"1987":1,"1989":3,"1990":1,"1991":2,"1993":3,"1994":6,"1995":9,"1996":2,"1997":3,"1998":1,"2000":14,"2001":4,"2002":3,"2004":1,"2006":7,"2008":5,"2009":4,"2010":8,"2012":4,"2013":1,"2014":1,"2015":1,"2018":1,"2019":4,"2021":2,"2023":2,"2024":8,"2025":4,"2027":2,"2028":1,"2030":1,"2031":1,"2032":3,"2033":1,"2034":3,"2035":1,"2036":1,"2037":1,"2038":2,"2041":2,"2042":5,"2043":9,"2044":3,"2046":4,"2047":3,"2048":1,"2050":1,"2052":1,"2053":2,"2055":7,"2056":3,"2057":4,"2059":2,"2060":5,"2061":1,"2062":2,"2063":5,"2064":1,"2065":1,"2066":3,"2068":1,"2069":1,"2070":1,"2072":3,"2073":1,"2074":3,"2075":4,"2076":4,"2077":5,"2078":2,"2079":2,"2080":1,"2082":2,"2083":3,"2085":4,"2086":1,"2087":1,"2088":1,"2089":3,"2090":1,"2091":1,"2093":1,"2094":2,"2095":7,"2096":1,"2097":6,"2099":1,"2101":2,"2103":3,"2104":3,"2105":4,"2106":2,"2107":2,"2108":1,"2109":3,"2112":1,"2113":3,"2114":1,"2115":2,"2118":3,"2120":2,"2121":1,"2122":1,"2126":4,"2127":2,"2128":2,"2129":1,"2131":14,"2132":3,"2133":1,"2136":2,"2137":2,"2138":6,"2141":1,"2142":3,"2143":2,"2144":1,"2146":1,"2148":1,"2150":6,"2151":2,"2153":2,"2154":2,"2156":5,"2157":2,"2158":8,"2160":3,"2161":3,"2163":3,"2164":2,"2165":3,"2166":1,"2167":9,"2168":7,"2169":3,"2170":2,"2171":1,"2172":2,"2174":2,"2176":2,"2177":2,"2178":4,"2179":3,"2180":6,"2185":6,"2186":1,"2187":1,"2188":3,"2189":2,"2190":4,"2192":7,"2193":6,"2194":2,"2195":3,"2197":2,"2198":2,"2200":1,"2202":5,"2203":2,"2215":1,"2216":1,"2220":3,"2222":1,"2228":1,"2230":1,"2232":17,"2233":1,"2236":1,"2239":2,"2241":1,"2244":1}}],["sunday",{"3":{"1460":1,"1492":1,"1526":1}}],["sundays",{"3":{"1459":1,"1591":1}}],["suffers",{"3":{"1324":1}}],["suffer",{"3":{"1243":1,"1247":1,"1395":1}}],["suffixing",{"3":{"1324":1}}],["suffixes",{"3":{"1324":1,"1436":7}}],["suffixed",{"3":{"1270":2,"1300":1,"1309":1,"1333":1,"1359":1,"1429":1,"1436":2,"1496":1,"2128":1}}],["suffixredactor",{"3":{"1199":2,"1207":1}}],["suffix",{"2":{"1006":1},"3":{"123":1,"246":1,"343":2,"441":2,"443":2,"444":1,"576":1,"607":1,"609":1,"611":1,"629":1,"868":1,"870":1,"873":1,"975":1,"1006":2,"1267":2,"1270":1,"1300":2,"1309":2,"1311":2,"1312":1,"1324":4,"1339":1,"1350":1,"1395":3,"1396":1,"1411":1,"1415":4,"1416":1,"1436":18,"1444":1,"1465":1,"1467":3,"1475":2,"1476":3,"1535":1,"1591":1,"1651":2,"1685":1,"1686":1,"1733":1,"1920":2,"1923":1,"1924":2,"2017":1,"2127":2,"2158":2,"2160":1}}],["sufficiency",{"3":{"1300":1,"1749":1,"1765":2}}],["sufficient",{"3":{"100":1,"810":1,"813":1,"815":1,"856":1,"1300":1,"1309":1,"1311":1,"1323":1,"1324":1,"1350":1,"1380":2,"1395":3,"1402":1,"1415":2,"1417":1,"1457":1,"1472":1,"1490":1,"1496":1,"1631":1,"1639":1,"1749":1,"1765":1,"1767":2,"1891":1}}],["sufficed",{"3":{"1436":1}}],["suffices",{"3":{"30":1,"1273":1,"1286":1,"1359":3}}],["suffice",{"3":{"0":1,"1212":1}}],["sustain",{"3":{"1458":1}}],["sustained",{"3":{"0":1,"70":1,"403":1,"612":1,"625":1,"1102":1,"1311":2,"1324":2,"1396":1,"1400":1,"1534":1,"1949":1,"2166":1}}],["suspend",{"3":{"1396":1}}],["suspected",{"3":{"1323":1}}],["suspicious",{"3":{"801":1,"1415":1}}],["suggest",{"3":{"1341":1,"1436":1}}],["suggested",{"0":{"1194":1},"3":{"1075":1,"1286":1,"1709":1}}],["suggests",{"3":{"640":1,"768":1,"981":1,"987":1,"1252":1,"1270":1,"1467":1,"1727":1}}],["suggestions",{"3":{"1311":1,"1699":1,"1727":1}}],["suggestion",{"2":{"2089":1},"3":{"633":1,"1092":1,"1419":1,"1427":1,"1577":1,"1581":1,"1582":1,"1584":1,"1824":1,"1826":1,"2057":1,"2058":1,"2089":1,"2197":1}}],["sugar",{"3":{"0":1,"359":1,"655":1,"657":2,"660":1,"1168":1,"1259":1,"1286":2,"1761":1,"2142":1}}],["sums",{"3":{"1018":1,"1259":2,"1286":2,"1396":4}}],["sumselector",{"3":{"551":1,"1286":2}}],["summing",{"3":{"1286":2,"1396":1}}],["summer",{"3":{"1028":1,"1467":1,"1478":1}}],["summed",{"3":{"688":1,"1259":3,"1286":2,"1311":1,"1396":7,"1402":2}}],["summarizer",{"3":{"1435":1}}],["summarizes",{"3":{"1369":1,"1686":1}}],["summarize",{"3":{"1359":1,"2030":1}}],["summarized",{"3":{"369":1,"2230":1}}],["summaries",{"3":{"454":3,"1212":1,"1311":3,"1324":2,"1461":1,"1492":1,"1674":1,"1685":2}}],["summarypath",{"3":{"1475":1}}],["summary",{"0":{"1448":1,"1460":1,"1468":1,"1483":1,"1638":1,"1746":1,"2218":1},"3":{"0":2,"313":1,"376":1,"626":1,"627":1,"628":1,"642":1,"751":1,"757":1,"759":1,"766":3,"905":1,"946":1,"950":1,"972":1,"1006":2,"1237":2,"1259":1,"1263":1,"1270":2,"1277":1,"1286":3,"1300":12,"1309":1,"1311":3,"1312":2,"1323":3,"1324":10,"1339":2,"1350":7,"1359":31,"1380":1,"1395":13,"1396":10,"1402":6,"1407":1,"1414":1,"1415":4,"1416":11,"1417":7,"1431":1,"1433":1,"1436":19,"1438":4,"1452":1,"1455":5,"1457":1,"1459":1,"1461":1,"1474":1,"1475":4,"1488":2,"1489":1,"1492":2,"1526":3,"1561":1,"1607":1,"1608":1,"1632":1,"1639":3,"1669":1,"1671":1,"1685":1,"1686":1,"1701":1,"1729":1,"1741":1,"1742":1,"1748":2,"1750":1,"1760":1,"1765":2,"1767":2,"1769":1,"1957":1,"1958":1,"1995":4}}],["sumbyasync",{"3":{"543":1,"545":1,"551":1,"690":1,"692":1,"1286":9,"1396":4,"1815":1}}],["sum",{"3":{"0":1,"551":1,"592":1,"690":1,"692":2,"1011":1,"1012":3,"1259":1,"1270":1,"1286":4,"1339":1,"1359":1,"1366":1,"1395":1,"1396":11,"1402":1,"1455":1,"1467":2,"1487":1,"1492":1,"1496":8,"1498":1,"1686":1,"1764":2,"1798":2,"2047":1}}],["suiting",{"3":{"1339":1}}],["suitable",{"3":{"1323":2}}],["suit",{"3":{"165":1,"1309":1}}],["suits",{"3":{"0":1,"165":1,"650":1,"1270":1,"1300":1}}],["suite",{"0":{"2105":1},"2":{"2047":1},"3":{"0":4,"59":1,"132":1,"133":1,"135":3,"136":1,"137":4,"140":2,"143":1,"162":1,"218":1,"361":2,"546":1,"555":1,"556":1,"558":2,"571":1,"572":8,"573":1,"574":3,"575":2,"577":1,"591":6,"593":1,"616":2,"618":2,"621":1,"777":1,"838":1,"840":1,"859":1,"860":1,"861":2,"862":2,"863":1,"865":2,"906":1,"972":1,"973":1,"974":1,"1012":1,"1017":1,"1018":4,"1022":1,"1094":1,"1117":1,"1168":2,"1192":1,"1212":3,"1213":1,"1241":1,"1247":2,"1271":1,"1286":8,"1300":1,"1323":4,"1324":6,"1326":2,"1339":4,"1359":3,"1366":2,"1368":1,"1369":5,"1395":1,"1396":2,"1400":1,"1402":1,"1409":1,"1414":1,"1415":4,"1426":1,"1427":1,"1428":1,"1433":2,"1434":1,"1435":1,"1436":126,"1437":5,"1438":20,"1441":5,"1444":3,"1449":1,"1455":1,"1456":12,"1460":1,"1461":2,"1486":5,"1487":6,"1488":4,"1489":2,"1490":5,"1492":7,"1496":1,"1497":1,"1524":1,"1526":3,"1534":2,"1535":1,"1553":1,"1572":1,"1589":2,"1590":1,"1591":4,"1596":2,"1612":1,"1685":2,"1686":3,"1689":1,"1739":1,"1785":1,"1898":1,"1961":1,"2016":1,"2041":1,"2047":3,"2048":1,"2050":4,"2051":1,"2055":1,"2058":3,"2059":2,"2060":2,"2075":1,"2079":3,"2101":1,"2105":2,"2106":2,"2107":2,"2108":3,"2112":1,"2171":1,"2180":1,"2220":1,"2232":1}}],["suites",{"0":{"1438":1},"1":{"569":1,"570":1,"571":1,"572":1,"573":1,"574":1,"575":1,"576":1,"577":1,"578":1,"579":1},"3":{"0":2,"124":1,"143":1,"569":1,"572":3,"573":1,"574":6,"576":1,"577":1,"578":1,"622":1,"650":1,"773":1,"837":1,"841":1,"863":1,"953":2,"1212":1,"1247":1,"1270":1,"1286":1,"1323":1,"1359":1,"1395":1,"1415":1,"1428":2,"1429":1,"1432":1,"1433":1,"1435":2,"1436":27,"1438":2,"1460":1,"1485":2,"1487":3,"1488":2,"1489":1,"1492":1,"1494":1,"1496":7,"1500":1,"1526":1,"1660":2,"1661":1,"1668":1,"1671":3,"1684":1,"1685":1,"1690":1,"1898":1,"1923":1,"2041":1,"2055":2,"2232":1}}],["succeeding",{"3":{"350":1,"881":1,"906":1,"1286":1,"1300":2,"1324":2,"1395":1,"1436":1,"1767":1,"1914":1,"1977":1}}],["succeeded",{"3":{"243":1,"538":1,"707":1,"741":1,"791":1,"881":1,"896":1,"1229":1,"1247":3,"1259":1,"1265":2,"1270":2,"1278":1,"1300":1,"1304":1,"1311":4,"1323":5,"1324":6,"1343":1,"1350":4,"1359":6,"1380":1,"1389":1,"1395":4,"1396":2,"1402":2,"1410":1,"1415":4,"1417":1,"1433":1,"1436":3,"1438":2,"1467":1,"1488":1,"1490":1,"1492":1,"1525":1,"1591":2,"1599":1,"1840":1,"1845":1,"1852":1,"1908":1,"1910":1,"1922":1,"2156":1,"2164":1,"2168":1}}],["succeed",{"3":{"22":1,"109":1,"195":1,"196":1,"342":1,"415":1,"537":1,"854":1,"857":1,"996":1,"1258":1,"1259":2,"1265":1,"1271":1,"1286":2,"1300":5,"1323":3,"1324":2,"1359":3,"1415":1,"1417":1,"1436":3,"1454":1,"1455":1,"1668":1,"1765":1,"1906":1,"2037":1,"2122":1,"2126":2}}],["succeeds",{"3":{"0":4,"31":1,"53":1,"186":1,"234":1,"239":1,"255":1,"318":1,"511":1,"518":1,"549":1,"848":1,"856":1,"988":1,"1018":1,"1212":1,"1237":1,"1247":1,"1252":1,"1255":1,"1286":2,"1294":1,"1300":11,"1311":2,"1322":1,"1323":6,"1324":3,"1334":1,"1339":4,"1350":2,"1352":1,"1359":3,"1367":1,"1380":1,"1395":4,"1396":6,"1402":1,"1410":1,"1415":10,"1417":2,"1435":1,"1436":4,"1438":2,"1443":1,"1456":1,"1472":1,"1488":1,"1492":1,"1631":2,"1649":1,"1657":2,"1701":1,"1748":2,"1749":1,"1755":1,"1776":1,"1835":1,"1840":1,"1845":1,"1882":1,"1923":1,"1944":2,"1947":1,"1962":1,"1992":1,"2007":1,"2165":1}}],["successalert",{"3":{"1436":2}}],["successoutcome",{"3":{"1427":2}}],["successorevent",{"3":{"1199":3,"1207":1}}],["successors",{"3":{"905":1,"1255":1,"1286":1,"1300":1,"1436":3}}],["successor",{"3":{"0":4,"499":1,"846":1,"903":1,"905":3,"906":2,"1252":1,"1258":1,"1259":2,"1270":2,"1286":4,"1290":1,"1300":11,"1436":9,"1438":4,"1496":1,"1640":1,"1667":1,"1926":1,"1982":4,"1984":1,"1985":1}}],["successive",{"3":{"1396":1}}],["successcontrasttext",{"3":{"1324":1}}],["successfully",{"3":{"195":1,"265":1,"527":1,"539":1,"585":1,"818":1,"1259":3,"1270":1,"1300":2,"1324":4,"1359":3,"1395":1,"1436":2,"1455":1,"1639":1,"1641":2,"1653":1,"1677":1,"1843":1}}],["successful",{"2":{"1467":1},"3":{"0":5,"20":1,"21":1,"24":1,"109":1,"120":1,"234":1,"243":1,"258":1,"265":1,"281":1,"312":1,"498":1,"499":2,"500":1,"591":2,"626":1,"725":1,"727":1,"732":1,"733":1,"756":1,"757":1,"758":1,"854":1,"857":1,"881":1,"908":1,"922":1,"988":2,"991":1,"1020":1,"1140":1,"1183":1,"1184":1,"1212":2,"1217":1,"1220":2,"1227":1,"1229":2,"1233":1,"1237":2,"1247":1,"1259":3,"1263":1,"1265":1,"1270":7,"1274":1,"1286":5,"1288":1,"1289":1,"1294":1,"1300":14,"1301":1,"1309":2,"1311":5,"1318":1,"1323":5,"1324":12,"1350":1,"1358":1,"1359":26,"1369":1,"1380":3,"1391":1,"1395":21,"1396":6,"1398":1,"1402":4,"1406":1,"1407":1,"1415":8,"1417":2,"1436":2,"1438":2,"1455":2,"1456":1,"1460":1,"1463":1,"1467":1,"1474":2,"1475":2,"1488":2,"1490":1,"1526":2,"1577":1,"1589":1,"1591":3,"1596":2,"1598":1,"1627":2,"1641":3,"1651":2,"1709":2,"1767":1,"1774":1,"1895":1,"1898":1,"1911":1,"1968":1,"1982":1,"2034":1,"2037":1}}],["successes",{"3":{"0":1,"243":1,"258":1,"1286":1,"1324":1,"1359":1,"1396":2,"1402":2,"1438":1,"1577":1}}],["success",{"0":{"1918":1},"2":{"620":1,"855":1,"1099":1,"1221":1,"2190":1},"3":{"0":5,"109":4,"157":1,"178":1,"196":1,"225":1,"226":1,"230":1,"265":1,"312":1,"350":1,"423":1,"434":1,"435":1,"461":1,"537":1,"539":1,"583":1,"585":1,"620":1,"626":6,"640":1,"737":1,"756":2,"758":2,"766":1,"790":3,"854":2,"855":1,"861":1,"881":1,"897":1,"905":4,"922":1,"926":4,"988":1,"989":1,"996":2,"1018":2,"1075":1,"1089":1,"1099":1,"1103":1,"1168":1,"1212":2,"1217":1,"1219":2,"1220":6,"1221":5,"1223":3,"1227":1,"1229":31,"1236":1,"1237":7,"1247":1,"1252":1,"1256":2,"1259":5,"1264":1,"1265":3,"1270":18,"1271":2,"1286":13,"1289":1,"1290":1,"1292":1,"1294":1,"1300":42,"1304":1,"1309":14,"1311":22,"1318":1,"1322":1,"1323":42,"1324":61,"1339":7,"1346":1,"1350":25,"1358":2,"1359":97,"1365":1,"1366":1,"1369":3,"1379":1,"1380":13,"1389":1,"1390":1,"1395":77,"1396":52,"1398":1,"1402":24,"1407":1,"1408":1,"1415":38,"1416":2,"1417":15,"1425":2,"1427":2,"1433":5,"1436":30,"1438":5,"1445":3,"1455":18,"1456":1,"1460":1,"1467":2,"1473":1,"1474":1,"1475":4,"1488":4,"1492":9,"1496":1,"1526":7,"1561":1,"1577":2,"1589":1,"1591":6,"1596":2,"1641":1,"1651":3,"1653":1,"1686":2,"1702":2,"1709":1,"1749":1,"1770":1,"1805":10,"1821":1,"1822":2,"1896":1,"1898":1,"1903":1,"1911":1,"1912":1,"1923":1,"1924":2,"1930":1,"1933":3,"1940":1,"1975":1,"1976":1,"1982":1,"2001":1,"2045":1,"2046":1,"2063":1,"2067":1,"2092":1,"2096":2,"2156":1,"2163":1,"2165":1,"2169":2,"2177":1,"2189":1,"2190":2,"2196":1}}],["such",{"3":{"0":4,"57":1,"81":2,"145":1,"157":1,"175":1,"185":1,"193":1,"215":1,"234":1,"235":1,"249":1,"290":1,"296":1,"341":1,"352":1,"363":1,"369":1,"390":1,"391":1,"427":1,"428":1,"491":1,"509":1,"536":1,"538":1,"549":1,"605":1,"657":1,"698":1,"707":1,"720":1,"743":3,"767":1,"776":1,"782":1,"799":1,"801":1,"803":1,"868":1,"883":2,"889":1,"930":2,"948":1,"966":1,"980":2,"995":1,"1034":1,"1042":2,"1108":1,"1115":1,"1168":1,"1184":1,"1188":1,"1233":1,"1237":4,"1244":1,"1247":3,"1249":1,"1252":1,"1256":1,"1259":5,"1270":2,"1271":2,"1275":1,"1286":23,"1297":1,"1300":21,"1301":2,"1309":4,"1311":15,"1312":2,"1323":10,"1324":20,"1339":5,"1342":1,"1347":1,"1348":1,"1350":7,"1353":1,"1359":28,"1372":1,"1380":5,"1384":1,"1395":11,"1396":10,"1402":6,"1406":1,"1415":8,"1416":2,"1417":8,"1427":1,"1428":1,"1431":4,"1436":28,"1453":1,"1455":1,"1500":1,"1534":1,"1569":1,"1585":1,"1653":1,"1808":1,"1883":1,"1908":1,"1909":1,"1922":1,"1948":1,"2012":1,"2107":1,"2164":1,"2168":1,"2188":2,"2199":1}}],["superar",{"3":{"1685":2,"1686":2}}],["supervisedusercircle",{"3":{"1415":1}}],["supertype",{"3":{"1339":1}}],["superficial",{"3":{"1140":1}}],["supersession",{"3":{"1324":5,"1436":1,"1478":1}}],["supersets",{"3":{"1247":1}}],["superset",{"3":{"448":1,"564":1,"1270":1,"1309":1,"1311":4,"1436":1,"2131":1}}],["supersede",{"3":{"607":1,"610":1,"1395":3,"1438":1,"1456":1,"1892":1}}],["supersedes",{"1":{"262":1,"263":1,"264":1,"265":1,"266":1,"267":1,"268":1,"269":1},"3":{"0":3,"15":1,"22":1,"45":1,"142":1,"145":2,"262":1,"263":1,"313":1,"326":1,"390":1,"724":1,"739":1,"743":1,"792":1,"903":1,"944":1,"1098":1,"1120":1,"1212":4,"1286":1,"1287":1,"1300":3,"1324":3,"1395":2,"1436":1,"1438":1,"1453":1,"1455":1,"1459":1,"1496":1,"1527":1,"1578":1,"1591":1,"1592":1,"1639":1,"1737":1,"1784":1,"1799":1,"1947":1,"2081":1,"2085":1,"2232":4}}],["superseded",{"0":{"137":1},"3":{"0":8,"1":1,"27":1,"84":1,"91":2,"92":1,"109":1,"117":1,"122":1,"130":1,"136":3,"137":2,"142":1,"195":2,"197":1,"200":1,"264":1,"269":1,"308":1,"372":1,"395":3,"408":2,"409":1,"438":1,"443":1,"463":2,"465":1,"475":2,"476":1,"477":1,"490":1,"492":1,"493":1,"497":2,"516":1,"522":2,"523":1,"543":6,"547":3,"549":1,"607":1,"609":2,"611":1,"833":1,"867":1,"950":1,"1016":1,"1096":1,"1104":1,"1114":1,"1212":4,"1259":1,"1301":1,"1309":1,"1311":1,"1324":6,"1394":1,"1395":12,"1415":1,"1436":3,"1465":1,"1467":1,"1473":1,"1478":1,"1496":6,"1515":1,"1585":1,"1591":1,"1600":1,"1611":1,"1631":1,"1671":1,"1737":1,"2126":1,"2158":1,"2232":1}}],["superseding",{"3":{"0":2,"1":1,"130":1,"495":1,"1287":1,"1527":1,"2085":1}}],["suppose",{"3":{"1859":1}}],["supposedly",{"3":{"2081":1}}],["supposed",{"3":{"141":1,"673":1,"765":1,"789":1,"1019":1,"1323":2,"1359":1,"1427":1,"1455":1,"1803":1,"1906":1,"1952":1,"2042":1,"2050":1,"2099":1,"2130":1,"2135":1,"2147":1,"2154":1}}],["supportapiclient",{"3":{"1653":1,"1702":1}}],["supportemail",{"3":{"1359":1}}],["supporter",{"3":{"1350":1}}],["supportedversions",{"3":{"1311":1,"1436":2}}],["supportedincludes",{"3":{"1310":6,"1865":1}}],["supportedfk",{"3":{"1199":2,"1207":1}}],["supportedchild",{"3":{"1199":2,"1207":1}}],["supportedculturestests",{"3":{"1199":1,"1207":2,"1438":2}}],["supportedcultures",{"2":{"265":1,"268":1,"2083":1,"2086":1},"3":{"265":8,"268":1,"1199":15,"1203":1,"1207":2,"1237":2,"1311":15,"1324":11,"1415":7,"1417":5,"1436":12,"1438":1,"1496":3,"1577":1,"1653":1,"2083":4,"2085":1,"2086":1}}],["supportedoperators",{"2":{"330":1,"331":1},"3":{"330":1,"331":1,"1241":2,"1247":10,"1988":1}}],["supported",{"0":{"1713":1},"2":{"31":3},"3":{"0":1,"31":3,"148":1,"170":1,"216":1,"265":1,"330":1,"350":1,"351":1,"352":1,"448":3,"501":1,"549":1,"550":1,"572":1,"682":1,"741":1,"1033":1,"1050":2,"1108":1,"1132":2,"1212":2,"1213":1,"1229":1,"1237":1,"1241":1,"1243":2,"1244":1,"1247":8,"1259":1,"1271":1,"1277":1,"1278":1,"1286":13,"1300":5,"1301":1,"1310":4,"1311":17,"1323":1,"1324":4,"1336":1,"1339":6,"1359":4,"1376":1,"1380":3,"1395":1,"1404":1,"1405":1,"1416":1,"1417":9,"1436":9,"1438":1,"1443":1,"1444":1,"1467":1,"1487":1,"1488":1,"1500":1,"1534":1,"1559":3,"1567":1,"1577":1,"1591":1,"1627":1,"1632":1,"1640":3,"1641":1,"1667":1,"1670":1,"1674":1,"1711":1,"1727":1,"1729":1,"1734":1,"1761":1,"1860":1,"1867":1,"1868":1,"1902":1,"1977":1,"1978":1,"1979":1,"1988":1,"2055":1,"2083":1,"2131":3,"2133":1,"2142":1,"2149":1,"2232":1}}],["supporting",{"0":{"1268":1},"3":{"659":1,"683":1,"845":1,"1247":2,"1272":1,"1284":1,"1286":2,"1287":1,"1311":1,"1312":1,"1324":1,"1346":1,"1350":1,"1415":1,"1432":1,"1436":1,"1488":1,"1763":1,"1774":1}}],["supportshttpstrafficonly",{"3":{"1467":1}}],["supportsrelationaloperator",{"3":{"1286":1}}],["supportstransactions",{"2":{"1840":1},"3":{"988":1,"1286":1,"1840":1}}],["supportsoutbox",{"2":{"720":1,"1174":1,"1175":1,"1178":1,"1861":1},"3":{"166":1,"715":1,"720":1,"1174":1,"1175":1,"1178":1,"1496":1,"1861":1}}],["supports",{"3":{"0":1,"10":1,"57":1,"449":1,"527":1,"528":1,"618":1,"640":1,"642":1,"845":1,"846":1,"891":1,"1019":1,"1048":1,"1050":1,"1237":1,"1241":1,"1247":2,"1270":1,"1286":2,"1300":2,"1323":1,"1326":1,"1339":1,"1340":1,"1342":1,"1350":2,"1359":5,"1395":1,"1396":3,"1417":2,"1436":1,"1456":1,"1467":1,"1488":1,"1534":1,"1551":1,"1629":1,"1631":1,"1639":1,"1675":1,"1764":1,"1773":1,"1799":1,"1815":1,"1827":1,"1834":1,"1895":1,"2129":1}}],["supportsifmatchattributetests",{"3":{"1199":1,"1207":2,"1438":2}}],["supportsifmatchattribute",{"2":{"341":1,"922":1,"1375":1,"1651":1,"1874":1},"3":{"341":4,"343":1,"345":1,"922":1,"1199":22,"1203":1,"1207":2,"1286":1,"1300":3,"1311":17,"1347":1,"1350":2,"1359":17,"1375":1,"1380":16,"1402":9,"1496":1,"1651":2,"1874":1}}],["supportsifmatch",{"2":{"343":1,"346":1,"1635":1},"3":{"0":2,"341":1,"343":2,"346":1,"922":1,"1212":1,"1286":1,"1311":4,"1350":1,"1359":2,"1373":1,"1375":1,"1380":5,"1402":5,"1438":2,"1635":1,"1651":1,"1664":1,"1666":1,"1874":1}}],["support",{"0":{"1347":1,"1359":1,"1376":1,"1390":1},"1":{"1711":1,"1712":1,"1713":1,"1714":1,"1715":1,"1716":1},"2":{"1646":1,"1649":1,"1650":5,"1657":1,"1697":2,"1698":1,"1699":1,"1727":2,"1728":2,"2089":1},"3":{"0":4,"12":1,"24":1,"109":1,"150":1,"166":2,"243":1,"363":1,"447":1,"461":1,"552":1,"556":1,"633":1,"650":1,"690":1,"743":1,"800":1,"812":1,"845":1,"848":1,"853":1,"904":1,"931":1,"980":2,"1019":1,"1052":1,"1073":1,"1074":2,"1075":3,"1077":2,"1175":2,"1192":1,"1202":1,"1203":1,"1207":66,"1208":2,"1211":1,"1232":1,"1237":2,"1247":2,"1257":2,"1259":2,"1270":3,"1279":1,"1286":19,"1300":4,"1311":3,"1324":2,"1340":1,"1342":1,"1350":5,"1351":1,"1359":16,"1376":1,"1380":3,"1395":12,"1396":1,"1405":1,"1415":2,"1416":1,"1417":5,"1428":2,"1429":3,"1430":1,"1436":59,"1438":6,"1467":1,"1487":1,"1488":3,"1490":2,"1492":3,"1496":2,"1558":1,"1567":1,"1577":2,"1582":1,"1602":1,"1629":3,"1630":2,"1631":2,"1635":1,"1638":1,"1639":1,"1640":2,"1641":2,"1646":1,"1649":1,"1650":5,"1653":3,"1654":2,"1656":5,"1657":2,"1665":1,"1673":1,"1674":1,"1685":2,"1697":3,"1698":2,"1699":1,"1711":1,"1716":1,"1727":4,"1728":2,"1774":1,"1873":1,"1898":1,"1951":1,"1974":1,"2073":1,"2089":1,"2128":1,"2129":1,"2131":1,"2133":2,"2141":1,"2200":1,"2220":1,"2234":1}}],["suppresswhenunderprobe",{"3":{"1339":2}}],["suppressnextlocationchanged",{"3":{"1324":2}}],["suppressfinalize",{"3":{"1324":2,"1396":3}}],["suppressible",{"3":{"1286":2}}],["suppressing",{"3":{"1286":2,"1339":1,"1436":1,"1438":1,"1443":2,"1450":1,"1639":1}}],["suppression",{"3":{"0":2,"258":1,"370":1,"371":1,"619":1,"726":1,"774":2,"941":1,"980":1,"982":2,"1108":1,"1111":1,"1133":1,"1237":5,"1259":5,"1266":1,"1270":2,"1271":1,"1273":1,"1286":5,"1300":6,"1309":2,"1311":5,"1323":5,"1324":6,"1339":16,"1350":1,"1359":2,"1395":4,"1396":2,"1415":2,"1416":3,"1417":2,"1427":1,"1436":7,"1441":2,"1453":1,"1487":1,"1488":1,"1490":1,"1496":1,"1586":1,"1591":1,"2057":1}}],["suppressions",{"3":{"0":2,"371":1,"776":1,"980":1,"981":2,"982":2,"1237":2,"1311":1,"1324":1,"1417":1,"1488":1,"1552":1,"1577":1,"1591":1,"1600":1,"2079":1}}],["suppressmessageattribute",{"3":{"1237":1,"1324":1}}],["suppressmessage",{"2":{"1270":1,"1324":1,"1415":1},"3":{"142":1,"980":1,"1229":1,"1247":1,"1270":5,"1286":2,"1300":4,"1311":3,"1312":1,"1323":2,"1324":6,"1369":1,"1380":1,"1396":1,"1415":1,"1427":2,"1436":4,"1496":1}}],["suppress",{"3":{"135":1,"231":1,"790":1,"1108":1,"1273":1,"1286":1,"1309":1,"1461":1,"1526":1,"1591":2,"1600":1,"2043":1,"2160":1,"2166":1}}],["suppresses",{"3":{"18":1,"216":1,"225":1,"733":1,"819":1,"1034":1,"1256":1,"1259":1,"1271":1,"1286":3,"1309":1,"1324":2,"1339":2,"1359":1,"1396":1,"1402":1,"1415":1,"1436":2,"1467":1,"1596":1,"1677":1,"2157":1}}],["suppressed",{"3":{"0":4,"130":1,"135":1,"265":1,"370":2,"454":1,"674":1,"692":1,"726":1,"790":1,"939":1,"941":1,"980":2,"983":1,"1044":1,"1212":1,"1241":1,"1259":2,"1270":1,"1273":1,"1286":2,"1309":1,"1312":2,"1323":1,"1324":3,"1339":2,"1387":1,"1395":3,"1415":1,"1416":2,"1417":2,"1427":1,"1453":2,"1496":1,"1526":1,"1569":1,"1577":1,"1591":2,"1841":1,"1897":1,"1934":1,"2157":1,"2192":1}}],["supplements",{"3":{"1395":1}}],["supplementary",{"3":{"1389":1,"1630":1}}],["supplemental",{"3":{"1286":1}}],["supplementation",{"3":{"1286":1}}],["supplied",{"1":{"154":1,"155":1,"156":1,"157":1,"158":1,"159":1,"160":1,"161":1,"162":1},"3":{"0":2,"31":1,"103":1,"109":1,"111":1,"154":1,"157":2,"175":1,"186":2,"190":1,"231":1,"265":1,"318":2,"330":1,"332":1,"350":2,"359":1,"378":1,"381":1,"397":1,"409":1,"423":1,"427":1,"443":2,"549":1,"599":1,"649":1,"674":1,"733":1,"795":1,"861":1,"905":1,"1059":2,"1091":1,"1115":2,"1116":1,"1132":1,"1184":1,"1228":1,"1229":4,"1237":2,"1239":1,"1242":2,"1247":15,"1270":3,"1271":12,"1282":1,"1285":1,"1286":19,"1289":1,"1300":20,"1301":4,"1306":2,"1309":7,"1310":1,"1311":18,"1323":9,"1324":16,"1334":1,"1339":9,"1343":3,"1349":1,"1350":15,"1352":1,"1355":1,"1359":61,"1369":2,"1378":1,"1380":6,"1395":10,"1396":17,"1402":7,"1415":14,"1416":2,"1417":5,"1421":1,"1423":1,"1424":1,"1425":1,"1427":2,"1429":2,"1432":1,"1436":24,"1438":4,"1467":2,"1526":2,"1534":1,"1631":1,"1663":1,"1748":1,"1764":1,"1765":2,"1767":1,"1805":1,"1830":1,"1831":1,"1909":1,"1912":1,"1937":1,"1950":1,"1977":1,"1988":1,"1989":1,"1990":1,"1994":1,"2000":1,"2002":1,"2131":1,"2136":1,"2196":1,"2201":1}}],["supplies",{"3":{"0":3,"5":1,"25":1,"31":1,"67":1,"79":1,"132":1,"150":1,"157":1,"166":1,"186":2,"230":1,"235":1,"265":1,"266":1,"269":1,"310":2,"318":1,"350":1,"398":1,"413":1,"415":1,"448":2,"460":1,"461":1,"507":1,"544":1,"572":4,"583":1,"603":1,"609":1,"610":1,"633":1,"639":1,"648":1,"653":1,"674":1,"683":1,"724":1,"725":2,"731":1,"799":1,"827":1,"836":1,"846":1,"881":1,"922":1,"923":1,"946":1,"972":1,"1018":3,"1050":3,"1059":1,"1074":1,"1083":2,"1108":1,"1161":3,"1184":1,"1237":4,"1247":4,"1257":1,"1258":1,"1259":5,"1265":1,"1269":1,"1270":12,"1271":3,"1286":13,"1289":1,"1300":12,"1301":3,"1302":1,"1303":1,"1304":1,"1305":2,"1309":7,"1310":1,"1311":15,"1323":11,"1324":21,"1326":1,"1328":1,"1333":1,"1336":1,"1338":1,"1339":6,"1350":3,"1352":1,"1354":1,"1355":1,"1359":54,"1360":1,"1369":4,"1380":4,"1383":1,"1395":25,"1396":10,"1402":2,"1406":2,"1407":1,"1409":1,"1410":1,"1412":1,"1414":1,"1415":22,"1416":5,"1417":6,"1427":3,"1431":1,"1435":1,"1436":99,"1438":2,"1467":1,"1488":3,"1489":2,"1490":1,"1526":1,"1651":1,"1654":1,"1666":2,"1685":1,"1686":1,"1708":1,"1765":1,"1810":3,"1815":1,"1817":1,"1856":1,"1889":1,"1891":1,"1897":1,"1908":1,"1917":1,"1932":1,"1937":1,"1940":1,"1945":1,"1994":1,"1995":1,"1997":1,"2010":1,"2042":1,"2043":3,"2044":1,"2055":3,"2072":1,"2085":1,"2111":1,"2114":1,"2131":2,"2138":1,"2142":1}}],["supplyparameterfromqueryattribute",{"3":{"1436":1}}],["supplyparameterfromquery",{"3":{"1324":3,"1395":1,"1436":3,"1526":1}}],["supplying",{"3":{"136":1,"341":1,"470":1,"640":1,"854":1,"1004":1,"1214":1,"1259":1,"1286":2,"1297":1,"1309":2,"1311":6,"1323":2,"1324":3,"1338":1,"1350":1,"1359":12,"1386":1,"1395":3,"1396":2,"1415":2,"1417":2,"1431":2,"1436":13,"1496":1,"1765":1,"1957":1,"2096":1}}],["supply",{"0":{"1569":1,"1673":1,"1738":1},"1":{"367":1,"368":1,"369":1,"370":1,"371":1,"372":1,"373":1,"374":1,"375":1,"376":1},"3":{"0":3,"117":1,"216":1,"314":1,"367":1,"369":3,"370":1,"371":1,"373":1,"376":2,"499":1,"529":1,"532":1,"548":1,"583":1,"590":1,"591":1,"592":1,"595":1,"599":1,"603":1,"625":1,"626":1,"756":1,"765":1,"870":1,"874":1,"922":1,"1017":1,"1058":1,"1070":1,"1110":1,"1162":1,"1168":1,"1212":1,"1214":1,"1216":1,"1247":1,"1249":1,"1259":1,"1265":2,"1270":1,"1286":6,"1300":5,"1301":1,"1311":7,"1323":4,"1324":10,"1326":1,"1339":6,"1342":1,"1350":1,"1359":5,"1362":1,"1369":2,"1371":2,"1380":6,"1395":4,"1396":1,"1402":2,"1415":3,"1417":7,"1430":1,"1431":1,"1433":1,"1436":19,"1443":1,"1445":2,"1450":1,"1453":2,"1454":3,"1455":2,"1459":7,"1460":1,"1461":3,"1463":1,"1465":1,"1467":1,"1475":1,"1483":1,"1486":3,"1488":1,"1489":1,"1491":1,"1493":1,"1499":1,"1526":11,"1534":1,"1535":1,"1576":1,"1577":2,"1586":1,"1591":6,"1600":1,"1649":1,"1651":1,"1673":1,"1723":1,"1782":1,"1796":1,"1799":1,"1851":1,"1875":1,"1891":1,"1896":1,"1983":1,"2042":1,"2101":1,"2104":1,"2114":1,"2150":1,"2152":1,"2232":1}}],["subrequests",{"3":{"1969":1}}],["subdirectories",{"3":{"1464":1}}],["subdomains",{"3":{"1339":1}}],["subdomain",{"3":{"698":1,"1184":1}}],["suburbs",{"3":{"1350":1,"1359":2}}],["subgroup",{"3":{"1347":1}}],["subverted",{"3":{"1286":1}}],["subagent",{"3":{"1011":1}}],["subfolders",{"3":{"607":1,"693":1}}],["submissions",{"3":{"1347":1,"1350":5,"1359":3,"1396":1,"1402":3,"1467":2,"1635":3,"1637":1,"1638":3}}],["submission",{"0":{"1481":1},"3":{"173":1,"179":1,"1018":1,"1324":1,"1350":2,"1359":2,"1360":1,"1366":2,"1369":1,"1380":3,"1395":1,"1396":3,"1399":1,"1401":1,"1402":6,"1415":1,"1436":1,"1447":1,"1467":1,"1471":1,"1481":3,"1482":1,"1483":1,"1623":1,"1629":1,"1630":2,"1631":3,"1632":4,"1638":3,"1639":4,"1640":6,"1764":1,"1765":1,"1767":1,"1999":1,"2001":1,"2200":1}}],["submitbutton",{"3":{"1436":2}}],["submitbuttontext",{"3":{"1396":1}}],["submitanswerasync",{"3":{"1396":6}}],["submitanswersasync",{"3":{"1396":11}}],["submitasync",{"3":{"1396":2,"1402":1}}],["submitfailed",{"3":{"1396":1}}],["submitfeedback",{"3":{"1396":2}}],["submitfeedbackasync",{"3":{"1396":3}}],["submitter",{"3":{"1398":1,"1402":1,"1632":1}}],["submittedquestions",{"3":{"1396":1,"1415":3}}],["submittedonutc",{"3":{"1350":2,"1396":2}}],["submitted",{"3":{"280":1,"282":1,"285":1,"690":1,"1018":2,"1238":1,"1247":1,"1300":2,"1323":5,"1324":3,"1349":1,"1350":11,"1359":13,"1366":1,"1369":2,"1380":3,"1395":3,"1396":12,"1398":1,"1399":1,"1402":5,"1410":1,"1415":4,"1436":1,"1438":1,"1455":1,"1534":1,"1629":1,"1630":1,"1631":1,"1632":3,"1638":1,"1639":2,"1640":5,"1765":1,"1999":1,"2000":2,"2002":2}}],["submittingwithoutatoken",{"3":{"1436":1}}],["submitting",{"3":{"1300":2,"1307":1,"1359":8,"1380":1,"1396":3,"1402":3,"1561":1,"1630":1,"1631":1,"1638":2,"1639":1,"1640":1,"1946":1,"1998":1}}],["submitreviewhandler",{"3":{"552":1,"1767":2}}],["submitquestionasync",{"3":{"1402":1}}],["submitquestionrequest",{"3":{"1199":8,"1203":1,"1207":2,"1396":3,"1402":8}}],["submitquestioncommandvalidatortests",{"3":{"1199":1,"1207":2}}],["submitquestioncommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1402":10}}],["submitquestioncommand",{"2":{"523":1},"3":{"1199":5,"1203":1,"1207":2,"1402":9}}],["submitquestionhandlertests",{"3":{"1199":1,"1207":3,"1436":1}}],["submitquestionhandler",{"2":{"522":1,"523":1,"524":1,"899":1,"900":1,"996":1,"1102":1,"1534":1},"3":{"0":2,"493":1,"494":1,"495":1,"522":2,"523":3,"524":3,"897":2,"899":2,"900":1,"996":2,"1100":2,"1102":2,"1199":1,"1203":1,"1207":2,"1247":2,"1350":4,"1359":2,"1396":5,"1399":8,"1402":20,"1436":2,"1496":1,"1534":1}}],["submit",{"3":{"160":1,"493":1,"494":1,"495":1,"522":1,"523":1,"524":1,"552":1,"897":3,"899":1,"900":1,"994":2,"996":4,"1100":1,"1203":3,"1207":6,"1247":2,"1265":1,"1270":1,"1300":1,"1324":13,"1359":14,"1369":1,"1373":1,"1380":7,"1395":10,"1396":33,"1397":1,"1399":1,"1402":34,"1415":2,"1436":15,"1467":1,"1488":6,"1526":3,"1534":1,"1561":3,"1591":1,"1607":1,"1608":1,"1626":1,"1627":1,"1632":4,"1638":1,"1639":1,"1640":1,"1741":1,"1742":1,"1746":1,"1748":1,"1765":2}}],["submitsitasthestoredset",{"3":{"1436":1}}],["submitscanasync",{"3":{"1396":1}}],["submits",{"3":{"156":1,"1349":1,"1350":3,"1352":1,"1369":1,"1380":1,"1395":1,"1396":2,"1399":1,"1402":2,"1432":1,"1436":6,"1488":1,"1632":3,"1765":2}}],["subtitled",{"3":{"1395":2}}],["subtitles",{"3":{"1395":1,"1480":1}}],["subtitle",{"3":{"1395":1,"1480":2,"1780":1,"1788":1,"1794":1,"1803":1,"1808":1,"1813":1,"1818":1,"1828":1,"1835":1,"1847":1,"1854":1,"1863":1,"1869":1,"1878":1,"1887":1,"1893":1,"1900":1,"1906":1,"1913":1,"1925":1,"1931":1,"1942":1,"1951":1,"1960":1,"1966":1,"1973":1,"1980":1,"1986":1,"1992":1,"1998":1,"2003":1,"2015":1,"2029":1,"2039":1,"2050":1,"2061":1,"2070":1,"2081":1,"2087":1,"2099":1,"2109":1,"2116":1,"2124":1,"2129":1,"2134":1,"2140":1,"2147":1,"2154":1,"2161":1,"2170":1,"2181":1,"2194":1,"2204":1}}],["subtitle2",{"3":{"1324":1}}],["subtitle1",{"3":{"1324":1}}],["subtag",{"3":{"1311":2}}],["subtrees",{"3":{"1487":3}}],["subtree",{"3":{"1286":1,"1333":1,"1339":2,"1436":1,"1443":1,"1487":2}}],["subtract",{"3":{"1395":1}}],["subtracted",{"3":{"1369":1}}],["subtracts",{"3":{"742":1,"1286":1,"1300":1,"1323":1,"1436":2,"1749":1}}],["subtractive",{"3":{"1300":1}}],["subtraction",{"3":{"741":1,"1019":1,"1359":1,"1362":1,"1369":1}}],["subtracting",{"3":{"137":1}}],["subtype",{"3":{"1259":1}}],["subtypes",{"3":{"1229":3,"1807":1}}],["subtly",{"3":{"274":1,"840":1,"1051":1,"1359":3,"1436":2,"1479":1,"1828":1,"2082":1}}],["subtlest",{"3":{"1291":1,"1300":1,"1339":1,"1417":1}}],["subtleties",{"3":{"1286":1,"1436":1}}],["subtlety",{"3":{"97":1,"1252":1,"1300":1,"1320":1,"1328":1,"1339":1,"1348":1,"1395":1,"1405":1,"1415":1,"1416":1,"1436":1,"1881":1}}],["subtle",{"3":{"574":1,"800":1,"862":1,"1229":1,"1247":3,"1258":1,"1259":2,"1262":1,"1270":1,"1274":1,"1286":1,"1300":1,"1309":3,"1311":2,"1323":3,"1324":4,"1332":1,"1350":3,"1359":1,"1380":1,"1395":1,"1396":3,"1415":1,"1436":5,"1454":1,"1815":1,"1818":1,"1822":1,"1862":1,"1881":1,"1886":1,"2041":1,"2074":1,"2118":1,"2136":1,"2239":1}}],["subtler",{"3":{"133":1,"610":1,"1237":1,"1332":1,"1417":1,"2100":1,"2106":1}}],["subquery",{"3":{"24":1,"1237":1,"1259":1,"1286":1,"1309":3,"1401":1,"1402":2}}],["sub",{"1":{"1942":1,"1943":1,"1944":1,"1945":1,"1946":1,"1947":1,"1948":1,"1949":1,"1950":1},"2":{"298":1,"910":1},"3":{"0":6,"68":1,"157":1,"161":1,"175":1,"259":1,"298":1,"303":3,"318":2,"324":1,"379":1,"413":1,"416":1,"493":1,"497":1,"545":1,"709":1,"905":3,"910":1,"954":1,"1004":1,"1006":1,"1011":1,"1018":4,"1019":1,"1042":1,"1238":1,"1269":1,"1270":1,"1286":2,"1288":2,"1289":1,"1296":2,"1300":19,"1311":2,"1323":1,"1324":2,"1332":1,"1339":3,"1350":3,"1359":2,"1362":1,"1366":1,"1369":3,"1371":1,"1372":1,"1380":3,"1395":2,"1396":3,"1415":2,"1432":1,"1436":12,"1474":1,"1488":1,"1492":1,"1526":3,"1529":1,"1530":1,"1531":1,"1532":1,"1535":1,"1582":1,"1585":1,"1594":1,"1600":1,"1747":1,"1765":1,"1771":1,"1779":1,"1815":1,"1942":1,"1957":1,"1982":1,"1995":1,"2033":1,"2037":1,"2043":1,"2138":1,"2186":1,"2208":1}}],["subclassable",{"3":{"622":1,"1286":1}}],["subclasses",{"3":{"0":8,"27":2,"76":1,"124":1,"130":1,"137":1,"139":1,"140":1,"147":1,"160":1,"281":1,"283":1,"305":1,"314":1,"465":1,"523":1,"549":1,"572":3,"573":1,"575":1,"577":1,"578":5,"609":1,"618":2,"631":2,"632":1,"635":1,"707":1,"723":1,"725":3,"741":1,"744":1,"749":1,"751":1,"809":1,"846":1,"881":1,"884":1,"905":1,"912":1,"964":1,"973":1,"976":1,"1004":1,"1018":2,"1074":2,"1091":1,"1136":1,"1161":1,"1162":1,"1163":1,"1168":1,"1212":1,"1214":1,"1231":1,"1237":3,"1247":6,"1252":1,"1259":5,"1262":1,"1265":1,"1270":7,"1271":1,"1286":5,"1300":7,"1311":5,"1312":1,"1322":1,"1323":4,"1324":6,"1354":1,"1359":8,"1384":1,"1395":4,"1398":1,"1402":1,"1406":1,"1409":1,"1415":4,"1418":1,"1427":2,"1429":1,"1431":2,"1436":54,"1438":2,"1476":1,"1487":3,"1488":2,"1489":8,"1496":2,"1526":3,"1577":5,"1582":2,"1654":1,"1666":1,"1826":1,"1857":1,"1896":1,"1983":1,"2000":1,"2042":1,"2055":3,"2057":1,"2101":1,"2104":1,"2105":1,"2107":1,"2111":1,"2138":1,"2232":1}}],["subclassed",{"3":{"0":3,"51":1,"80":1,"130":1,"265":1,"302":1,"572":4,"573":1,"574":1,"578":2,"638":1,"723":1,"972":1,"1050":1,"1054":1,"1161":1,"1168":1,"1247":1,"1259":1,"1270":3,"1286":1,"1300":1,"1310":1,"1311":3,"1323":9,"1359":1,"1395":4,"1396":1,"1430":1,"1436":71,"1488":3,"1489":2,"1526":1,"1576":1,"1577":2,"1582":1,"1583":1,"1586":2,"2042":1,"2055":1,"2101":1,"2131":1,"2178":1}}],["subclassing",{"3":{"0":2,"534":1,"545":1,"611":1,"619":1,"1075":1,"1247":2,"1270":1,"1271":2,"1310":1,"1311":3,"1323":3,"1359":3,"1402":3,"1416":1,"1436":2,"1488":3,"1489":1,"1577":1,"1654":1,"1667":1,"1815":1,"1891":1,"2108":1}}],["subclass",{"0":{"1406":1,"2104":1},"3":{"0":10,"27":1,"47":1,"54":1,"80":1,"109":1,"128":1,"134":1,"135":3,"136":2,"139":1,"142":1,"147":1,"218":1,"233":1,"235":2,"281":1,"283":1,"310":4,"341":1,"350":1,"351":1,"352":1,"446":1,"448":4,"449":1,"450":2,"455":1,"459":2,"461":1,"463":1,"465":1,"497":2,"498":1,"499":1,"500":1,"518":1,"523":2,"536":3,"540":1,"545":1,"551":1,"572":8,"573":1,"574":2,"575":1,"577":1,"585":2,"607":1,"609":3,"610":1,"611":1,"614":1,"616":1,"618":1,"631":2,"635":1,"640":1,"706":1,"720":1,"723":1,"724":1,"725":8,"726":1,"727":1,"749":2,"813":1,"814":1,"855":1,"881":3,"884":1,"905":2,"907":1,"909":1,"910":1,"914":2,"920":1,"922":1,"924":1,"926":3,"954":3,"956":1,"957":1,"960":2,"972":1,"973":1,"974":1,"1004":1,"1005":2,"1048":1,"1050":1,"1054":1,"1058":1,"1059":2,"1065":1,"1067":1,"1069":1,"1081":1,"1083":2,"1091":2,"1100":1,"1133":2,"1135":2,"1137":1,"1161":3,"1168":2,"1169":1,"1171":1,"1226":1,"1229":2,"1237":2,"1244":2,"1247":6,"1252":1,"1259":10,"1265":1,"1270":21,"1271":1,"1272":1,"1282":1,"1286":13,"1289":2,"1290":1,"1300":11,"1310":6,"1311":20,"1312":1,"1318":2,"1322":1,"1323":31,"1324":5,"1334":1,"1339":5,"1353":1,"1359":49,"1369":2,"1380":6,"1384":1,"1395":10,"1396":3,"1402":3,"1406":3,"1407":2,"1410":1,"1411":1,"1412":1,"1413":2,"1414":1,"1415":22,"1416":2,"1417":2,"1427":2,"1428":1,"1429":3,"1431":2,"1432":1,"1433":1,"1435":1,"1436":263,"1438":9,"1443":1,"1467":1,"1488":4,"1489":7,"1490":1,"1491":2,"1496":1,"1525":1,"1526":7,"1532":1,"1577":1,"1582":1,"1585":1,"1591":5,"1653":1,"1654":2,"1660":1,"1671":2,"1689":1,"1699":1,"1727":1,"1782":1,"1815":2,"1816":1,"1849":1,"1865":1,"1875":1,"1898":1,"1977":1,"1978":2,"1982":1,"1983":1,"1990":1,"2000":1,"2043":3,"2044":3,"2046":2,"2055":1,"2056":1,"2101":1,"2102":1,"2103":1,"2104":2,"2107":1,"2131":7,"2132":2,"2133":1,"2165":2,"2167":1,"2180":1,"2196":1,"2197":1,"2201":1}}],["subjective",{"3":{"1801":1}}],["subjects",{"3":{"1436":7,"1455":1,"2063":1}}],["subjectsnapshot",{"3":{"1199":2,"1207":1}}],["subjectkey",{"3":{"0":1,"690":2,"1212":1,"1396":22,"1415":2,"1631":2}}],["subject",{"0":{"1299":1},"1":{"722":1,"723":1,"724":1,"725":1,"726":1,"727":1,"728":1,"729":1},"2":{"31":1,"303":1,"1299":1,"2138":1},"3":{"0":5,"31":1,"38":1,"39":3,"41":2,"42":2,"95":1,"96":1,"136":1,"137":1,"157":2,"159":1,"161":1,"173":2,"175":1,"181":1,"230":1,"303":1,"358":1,"583":1,"585":1,"625":1,"690":2,"691":3,"692":1,"693":1,"698":1,"700":1,"716":1,"722":1,"724":4,"725":6,"727":3,"745":1,"765":1,"766":1,"868":1,"896":2,"905":1,"976":1,"1026":1,"1028":1,"1044":1,"1199":6,"1207":1,"1212":1,"1229":1,"1236":3,"1237":8,"1270":2,"1277":1,"1283":1,"1286":3,"1299":4,"1300":29,"1308":1,"1309":21,"1311":7,"1312":2,"1320":1,"1322":2,"1323":20,"1324":1,"1350":5,"1359":6,"1395":3,"1396":51,"1402":2,"1403":1,"1410":4,"1415":51,"1417":1,"1431":1,"1435":1,"1436":33,"1438":11,"1455":1,"1457":1,"1460":1,"1461":1,"1467":3,"1472":5,"1487":1,"1489":5,"1492":1,"1567":2,"1591":1,"1597":1,"1631":6,"1639":5,"1640":1,"1641":1,"1666":1,"1747":1,"1751":1,"1764":1,"1765":4,"1772":1,"1908":2,"1920":1,"1964":1,"1974":1,"2000":1,"2044":1,"2062":1,"2068":1,"2138":1,"2141":1,"2156":1,"2158":1,"2165":1,"2193":1,"2232":2,"2239":1}}],["subsuming",{"3":{"1436":1}}],["subsumes",{"3":{"1436":1,"1731":1}}],["subsequent",{"3":{"388":1,"390":1,"1247":2,"1259":1,"1286":2,"1311":2,"1324":1,"1359":1,"1456":1,"1475":1,"1491":1,"1630":1,"1635":3,"1639":1,"1641":1,"1908":1}}],["subsequently",{"3":{"91":1,"1324":1}}],["subsections",{"3":{"1467":1}}],["subsection",{"3":{"258":1,"1496":2}}],["subsets",{"3":{"0":1,"1242":1,"1247":1,"1324":1,"1348":1,"1350":2,"1436":2}}],["subset",{"3":{"0":1,"137":1,"140":1,"184":1,"186":1,"187":2,"741":1,"751":1,"954":1,"1215":1,"1242":1,"1244":1,"1245":1,"1247":6,"1259":1,"1271":1,"1286":3,"1300":6,"1332":1,"1350":1,"1373":1,"1377":2,"1380":8,"1396":4,"1405":1,"1409":1,"1415":6,"1417":1,"1486":1,"1496":4,"1526":1,"1560":1,"1614":1,"1617":1,"1618":1,"1621":2,"1627":1,"1961":1,"1963":1,"2075":1,"2111":1,"2150":2}}],["substantial",{"3":{"1436":1}}],["substantive",{"3":{"0":1,"77":1,"250":1,"251":1,"252":1,"257":1,"543":4,"1270":1,"1324":1,"1402":1,"1888":1}}],["substance",{"3":{"200":1,"256":1,"258":1,"464":1,"524":1,"703":1,"797":1,"875":1,"958":1,"1216":1,"1286":1,"1324":1,"1359":1,"1436":1,"1516":1,"1523":1,"1526":7,"1537":1,"1577":2,"1588":1,"1590":1,"1591":2,"1592":4,"1784":2,"1797":1,"1799":1}}],["substitutability",{"3":{"1237":1,"1270":1,"1286":2,"1300":1,"1359":1,"1417":3,"1427":1}}],["substitutable",{"3":{"641":1,"1309":1,"1395":1,"1417":1,"1436":1,"1538":1,"1842":1}}],["substituting",{"3":{"448":1,"545":1,"685":1,"1300":1,"1311":1,"1323":2,"1324":1,"1375":1,"1380":2,"1395":1,"1396":1,"1416":1,"1436":2,"1815":2}}],["substitutionpriority",{"2":{"1279":1},"3":{"1175":1,"1279":1,"1286":8}}],["substitutions",{"3":{"633":1}}],["substitution",{"0":{"170":1},"3":{"170":1,"543":1,"550":1,"1175":3,"1239":1,"1247":5,"1280":1,"1286":9,"1300":1,"1311":2,"1323":1,"1339":2,"1380":1,"1396":2,"1436":1,"1465":1,"1475":1,"1577":2,"1685":1,"1686":1,"1815":2,"1817":2,"1897":1,"1899":1,"1916":1,"2149":1,"2153":1}}],["substituteengine",{"3":{"1286":1}}],["substituteapiversioninurl",{"2":{"448":1},"3":{"448":1,"1311":1}}],["substituted",{"3":{"214":1,"359":1,"365":1,"369":1,"370":1,"838":1,"1237":1,"1247":1,"1259":1,"1286":2,"1324":1,"1336":1,"1339":1,"1350":1,"1359":3,"1375":1,"1380":3,"1395":1,"1396":1,"1436":4,"2142":1}}],["substitutes",{"3":{"170":1,"212":1,"954":1,"1279":1,"1280":2,"1286":9,"1300":1,"1309":2,"1312":1,"1323":1,"1324":4,"1339":1,"1359":2,"1380":1,"1396":1,"1417":3,"1436":3,"1438":2}}],["substitute",{"3":{"170":3,"546":1,"1034":1,"1177":1,"1259":1,"1286":8,"1311":2,"1324":5,"1359":1,"1362":1,"1368":1,"1373":1,"1395":1,"1396":2,"1417":1,"1481":1,"1646":1,"1897":1,"1992":1}}],["substituteunconfiguredengine",{"2":{"170":1,"1279":1},"3":{"170":1,"1279":1,"1286":1}}],["substrings",{"3":{"1311":1,"1395":1,"1436":2}}],["substring",{"3":{"0":1,"585":1,"633":2,"1270":1,"1286":1,"1339":1,"1359":2,"1395":1,"1415":1,"1436":5,"1455":1,"1686":1,"1727":2,"1731":1}}],["substrates",{"3":{"1438":1}}],["substrate",{"1":{"240":1,"241":1,"242":1,"243":1,"244":1,"245":1,"246":1,"247":1,"248":1,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"260":1,"261":1,"730":1,"731":1,"732":1,"733":1,"734":1,"735":1,"736":1,"737":1},"3":{"0":5,"124":1,"162":1,"239":1,"240":1,"241":2,"242":1,"243":3,"244":1,"247":3,"248":1,"253":3,"254":1,"259":3,"260":1,"282":1,"287":1,"730":1,"731":3,"733":5,"734":1,"735":2,"736":1,"737":2,"853":1,"1044":1,"1212":2,"1300":3,"1301":10,"1339":1,"1415":1,"1428":1,"1577":1,"1641":1,"1651":1,"1668":1,"1708":1,"1916":2,"1921":1,"2114":1,"2232":2}}],["subscribing",{"3":{"408":1,"784":2,"1323":1,"1324":2,"1339":1,"1350":4,"1359":1,"1396":4,"1417":2,"2179":1}}],["subscribers",{"3":{"1270":1,"1309":1,"1324":3,"1396":1,"1415":1,"1417":2,"1750":1}}],["subscriber",{"3":{"198":1,"382":1,"781":1,"783":1,"784":1,"1026":1,"1324":11,"1349":1,"1350":15,"1359":3,"1396":11,"1399":1,"1402":10,"1415":5,"1417":2,"1436":1,"1949":3}}],["subscribes",{"3":{"72":1,"273":1,"384":1,"391":1,"397":1,"404":1,"782":1,"790":1,"899":1,"1259":1,"1324":11,"1339":3,"1349":1,"1350":19,"1396":7,"1402":2,"1415":3,"1417":12,"1427":1,"1438":1,"1526":1,"1922":1,"2006":1,"2166":1}}],["subscribed",{"0":{"408":1},"3":{"0":1,"72":1,"255":1,"395":5,"400":1,"404":1,"407":1,"1259":1,"1302":1,"1309":4,"1324":3,"1339":2,"1402":1,"1417":1,"2006":1,"2156":2}}],["subscribe",{"3":{"0":1,"66":1,"273":1,"384":1,"404":1,"406":1,"819":1,"897":1,"1309":1,"1324":3,"1341":1,"1350":2,"1396":3,"1402":1,"1417":5,"1435":1,"1945":1,"2156":2,"2215":1,"2235":1,"2236":1,"2237":1}}],["subscription",{"2":{"1468":1,"1472":1},"3":{"0":1,"258":2,"259":1,"261":1,"276":1,"378":1,"384":1,"395":2,"400":1,"407":2,"408":3,"640":2,"641":1,"766":1,"767":1,"783":1,"784":1,"822":1,"900":1,"971":1,"973":2,"1323":1,"1324":12,"1327":1,"1339":2,"1396":5,"1400":2,"1401":1,"1402":9,"1415":2,"1417":7,"1436":4,"1438":2,"1440":1,"1443":1,"1455":2,"1463":1,"1465":1,"1466":1,"1467":6,"1468":1,"1470":2,"1472":2,"1474":1,"1476":1,"1484":2,"1950":1,"2043":1,"2073":1,"2156":2,"2159":1,"2180":1}}],["subscriptions",{"3":{"0":1,"380":1,"399":1,"1192":1,"1324":2,"1339":1,"1417":1,"1467":3,"1945":1}}],["subsystems",{"3":{"0":1,"1108":1,"1192":1,"1300":3,"1309":1,"1396":1}}],["subsystem",{"3":{"0":1,"165":1,"235":2,"237":1,"707":1,"1110":1,"1192":1,"1202":1,"1203":1,"1300":1,"1302":1,"1309":5,"1310":2,"1334":2,"1339":3,"1402":1,"1496":2,"1674":1,"1915":1}}],["surname",{"3":{"1311":1}}],["surround",{"3":{"2038":1}}],["surrounds",{"3":{"1904":1}}],["surrounding",{"3":{"139":1,"426":1,"490":1,"1300":2,"1311":1,"1323":1,"1324":2,"1350":1,"1359":4,"1380":1,"1395":1,"1396":6,"1415":2,"1416":1,"1417":1,"1436":2,"1438":1,"1728":1,"2059":1}}],["surrogate",{"3":{"1259":2,"1300":1,"1396":1,"1414":1,"1415":2}}],["surplus",{"3":{"1241":1,"1254":1,"1259":4,"1307":1,"1311":1}}],["surprisingly",{"3":{"1380":1}}],["surprising",{"3":{"692":1,"743":1,"1237":1,"1281":1,"1286":1,"1339":1,"1396":1,"1415":1,"1436":1,"1639":1,"2099":1}}],["surprises",{"3":{"1286":1,"1363":1,"1369":1,"1486":1,"1568":1,"1662":1}}],["surprise",{"3":{"399":1,"698":1,"741":1,"1093":1,"1229":1,"1286":3,"1311":2,"1339":2,"1350":2,"1395":1,"1406":1,"1415":2,"1455":1,"1679":1,"1686":1,"1881":1,"1885":1,"1955":1,"2046":1,"2065":1,"2159":1}}],["surprised",{"3":{"243":1,"1925":1,"1986":1}}],["surgically",{"3":{"1359":1}}],["surgical",{"3":{"1231":1,"1237":1}}],["surges",{"3":{"1457":1,"1526":1,"1679":1}}],["surgery",{"3":{"1011":1,"1012":1,"1436":1,"1496":1,"1571":1}}],["surge",{"3":{"0":1,"765":2,"766":3,"767":4,"769":1,"1212":1,"1457":5,"1461":2,"1467":4,"1469":1,"1476":1,"1525":1,"1526":4,"1530":1,"1531":2,"1532":1,"1534":1,"1591":3,"1597":1,"1678":2,"1679":1,"1680":1,"2232":1}}],["sure",{"3":{"215":1,"1300":1,"1324":1,"1436":1,"1877":1,"1930":1,"2105":1,"2150":1,"2153":1}}],["surfacing",{"3":{"39":1,"99":1,"501":1,"1259":1,"1263":1,"1271":1,"1286":1,"1300":1,"1311":2,"1312":1,"1323":1,"1324":3,"1339":1,"1350":2,"1383":1,"1395":3,"1396":2,"1402":4,"1417":1,"1427":2,"1436":1,"1984":1,"2072":1,"2220":1,"2232":1}}],["surfaced",{"3":{"39":2,"78":1,"265":1,"690":1,"729":1,"789":1,"846":1,"926":1,"1026":1,"1036":1,"1084":1,"1099":1,"1237":2,"1242":1,"1270":1,"1286":1,"1300":2,"1309":1,"1312":1,"1323":1,"1324":3,"1339":2,"1350":6,"1358":1,"1359":7,"1395":4,"1396":5,"1402":1,"1415":2,"1427":1,"1431":1,"1436":4,"1488":1,"1496":3,"1532":1,"1535":1,"1577":1,"1621":1,"1627":1,"1643":1,"1916":1,"1944":1,"1955":1,"2166":1,"2220":1}}],["surfaces",{"0":{"926":1},"3":{"0":5,"41":1,"43":1,"95":1,"109":1,"127":1,"189":1,"201":1,"215":1,"237":1,"245":1,"274":1,"300":2,"301":1,"303":1,"306":2,"340":1,"388":1,"390":1,"401":1,"441":1,"500":1,"514":1,"545":1,"550":1,"585":1,"612":1,"628":1,"648":1,"649":1,"650":1,"651":1,"688":1,"690":1,"732":1,"790":1,"802":1,"803":1,"861":1,"883":1,"913":1,"926":1,"966":1,"990":1,"996":1,"1004":1,"1043":1,"1051":1,"1212":2,"1247":2,"1259":2,"1263":1,"1270":6,"1271":1,"1276":1,"1278":1,"1286":9,"1287":1,"1297":1,"1300":8,"1301":2,"1307":1,"1309":1,"1310":1,"1311":7,"1323":1,"1324":18,"1339":6,"1347":1,"1350":2,"1352":1,"1355":1,"1356":1,"1359":23,"1379":1,"1380":1,"1388":1,"1394":1,"1395":15,"1396":33,"1397":1,"1401":1,"1402":16,"1415":4,"1416":4,"1417":6,"1433":2,"1434":1,"1436":18,"1438":7,"1444":1,"1447":1,"1454":1,"1456":1,"1467":2,"1487":1,"1488":1,"1489":1,"1496":1,"1526":6,"1531":2,"1532":2,"1534":1,"1535":1,"1576":1,"1577":1,"1582":1,"1591":2,"1596":1,"1627":2,"1636":1,"1651":1,"1698":1,"1714":2,"1760":1,"1869":1,"1923":1,"2074":1,"2075":1,"2077":1,"2083":2,"2113":1,"2127":1,"2137":1,"2138":1,"2153":1,"2232":3}}],["surface",{"0":{"1419":1,"1666":1,"1702":1,"2000":1,"2035":1,"2137":1},"1":{"297":1,"298":1,"299":1,"300":1,"301":1,"302":1,"303":1,"304":1,"305":1,"306":1,"977":1,"978":1,"979":1,"980":1,"981":1,"982":1,"983":1,"984":1,"1998":1,"1999":1,"2000":1,"2001":1,"2002":1},"3":{"0":16,"6":1,"24":1,"37":1,"42":1,"53":1,"54":2,"55":1,"58":2,"100":1,"103":1,"107":1,"109":1,"111":1,"121":1,"124":1,"130":1,"135":10,"138":1,"139":1,"141":1,"143":1,"145":2,"150":1,"162":1,"165":1,"166":1,"173":2,"177":2,"186":1,"189":1,"209":2,"210":1,"237":2,"256":1,"265":2,"275":1,"280":2,"287":1,"297":1,"299":1,"301":1,"305":1,"306":1,"314":1,"321":1,"329":1,"330":1,"331":1,"332":4,"334":1,"341":1,"349":1,"351":1,"355":1,"363":1,"384":1,"391":1,"398":1,"403":1,"415":2,"421":1,"435":1,"447":1,"450":1,"471":1,"474":1,"506":2,"507":1,"508":3,"513":1,"527":1,"543":1,"545":1,"547":1,"548":1,"549":2,"550":2,"551":2,"572":3,"583":2,"591":1,"609":1,"611":1,"617":1,"618":1,"649":1,"650":2,"674":2,"675":2,"676":1,"681":1,"682":2,"684":1,"690":2,"691":5,"692":1,"694":1,"700":3,"707":2,"715":2,"717":1,"723":1,"725":2,"727":1,"740":1,"741":1,"743":1,"745":1,"751":1,"773":1,"774":1,"775":2,"783":2,"784":1,"792":1,"795":1,"797":4,"799":1,"803":1,"804":1,"805":2,"806":1,"827":2,"832":1,"840":1,"848":1,"880":1,"881":1,"882":1,"885":1,"914":1,"920":3,"921":1,"942":1,"963":1,"964":3,"966":1,"968":1,"971":1,"976":1,"977":1,"979":2,"980":7,"981":1,"982":1,"983":1,"984":2,"988":1,"1008":1,"1020":1,"1025":1,"1043":1,"1044":1,"1048":1,"1049":2,"1051":1,"1052":3,"1053":1,"1054":1,"1055":2,"1059":3,"1073":1,"1074":1,"1076":1,"1091":1,"1118":1,"1132":1,"1133":2,"1134":1,"1187":1,"1192":1,"1202":1,"1203":1,"1212":4,"1213":1,"1217":1,"1220":1,"1227":1,"1229":6,"1237":3,"1238":1,"1247":7,"1248":1,"1249":1,"1256":1,"1259":4,"1263":1,"1270":9,"1271":1,"1275":1,"1276":2,"1277":1,"1278":1,"1280":1,"1285":1,"1286":33,"1290":1,"1291":1,"1297":2,"1299":2,"1300":34,"1301":3,"1309":15,"1310":1,"1311":43,"1312":8,"1316":1,"1318":3,"1320":1,"1323":16,"1324":60,"1332":2,"1336":1,"1338":1,"1339":8,"1348":2,"1350":18,"1354":1,"1356":2,"1357":1,"1358":1,"1359":49,"1366":1,"1367":1,"1368":1,"1369":8,"1370":2,"1371":2,"1373":2,"1375":1,"1380":11,"1381":2,"1392":1,"1395":41,"1396":65,"1401":2,"1402":22,"1403":2,"1406":2,"1409":3,"1414":2,"1415":41,"1416":9,"1417":17,"1421":1,"1429":1,"1431":3,"1436":73,"1437":1,"1438":11,"1443":1,"1445":1,"1449":1,"1450":1,"1453":1,"1454":1,"1455":2,"1465":1,"1467":2,"1476":2,"1487":2,"1488":2,"1492":1,"1496":4,"1524":1,"1526":2,"1531":1,"1534":1,"1570":1,"1577":7,"1585":4,"1591":2,"1597":1,"1599":2,"1626":1,"1627":1,"1630":1,"1631":2,"1640":1,"1642":1,"1644":2,"1662":2,"1665":1,"1673":2,"1686":1,"1715":1,"1716":1,"1720":1,"1748":1,"1761":1,"1771":1,"1781":1,"1792":1,"1815":1,"1848":1,"1852":1,"1868":1,"1876":1,"1929":5,"1930":2,"1932":1,"1936":1,"1938":1,"1940":1,"1941":1,"1958":1,"1971":2,"1985":1,"1986":1,"1987":3,"1988":2,"1989":1,"1990":3,"1997":1,"1998":1,"2000":1,"2001":1,"2002":3,"2007":2,"2012":1,"2017":1,"2035":1,"2037":1,"2038":2,"2041":1,"2044":1,"2051":2,"2055":1,"2078":1,"2079":1,"2080":1,"2085":2,"2100":1,"2106":1,"2114":3,"2122":2,"2125":1,"2129":1,"2132":1,"2137":1,"2159":1,"2168":1,"2169":1,"2172":1,"2173":1,"2191":1,"2193":2,"2200":1,"2201":1,"2203":2,"2208":2,"2210":1,"2232":6}}],["surveys",{"3":{"1074":1,"1629":1,"1639":1}}],["survey",{"3":{"1033":1,"1350":1,"1359":3,"1629":1,"1630":4,"1637":5,"1639":5,"1640":1}}],["surveyed",{"3":{"0":1,"1049":1,"1051":1}}],["survival",{"3":{"1359":1,"2168":1}}],["survivable",{"3":{"563":1,"698":1,"759":1,"1324":1,"1359":1,"1417":3,"2193":1}}],["survivor",{"3":{"1359":1,"1436":2}}],["survivors",{"3":{"1286":1,"1415":1}}],["surviving",{"1":{"1966":1,"1967":1,"1968":1,"1969":1,"1970":1,"1971":1,"1972":1},"3":{"0":1,"67":1,"122":1,"201":1,"497":1,"734":1,"856":1,"863":1,"1247":1,"1259":1,"1262":1,"1271":1,"1285":1,"1286":2,"1311":1,"1323":1,"1324":3,"1339":2,"1350":2,"1359":5,"1395":2,"1410":1,"1415":1,"1427":1,"1436":4,"1438":1,"1473":1,"1482":1,"1966":1,"2208":1}}],["survived",{"3":{"266":1,"839":1,"840":1,"908":1,"1359":2,"1415":1,"1989":1}}],["survive",{"0":{"1223":1,"2189":1},"3":{"0":2,"39":2,"49":1,"131":1,"166":1,"242":1,"265":1,"380":1,"390":1,"440":1,"490":1,"520":2,"610":1,"626":1,"708":1,"711":1,"846":1,"932":1,"933":1,"997":1,"1018":1,"1020":1,"1043":1,"1074":1,"1102":2,"1115":1,"1212":1,"1217":1,"1229":1,"1237":3,"1259":4,"1274":1,"1286":7,"1287":1,"1294":1,"1300":4,"1310":1,"1311":1,"1312":1,"1323":4,"1324":13,"1339":3,"1342":1,"1349":1,"1350":4,"1359":6,"1369":2,"1376":1,"1380":2,"1395":4,"1396":8,"1400":1,"1402":1,"1410":1,"1413":1,"1415":4,"1416":1,"1417":7,"1418":1,"1426":1,"1427":1,"1436":2,"1438":3,"1441":1,"1476":1,"1591":1,"1596":1,"1597":1,"1662":1,"1663":1,"1684":1,"1686":1,"1689":1,"1724":1,"1729":1,"1787":1,"1794":1,"1851":1,"1866":1,"1889":1,"1906":1,"1930":1,"1945":1,"1950":1,"1965":1,"2045":1,"2063":1,"2081":1,"2100":1,"2109":1,"2126":1,"2128":1,"2162":1,"2176":1,"2183":1,"2185":1,"2200":1,"2209":1}}],["survivesblockcommentsineveryposition",{"3":{"142":1,"1436":1}}],["survives",{"0":{"1307":1,"1866":1},"3":{"0":4,"24":1,"31":1,"91":1,"96":1,"101":1,"248":1,"458":1,"472":1,"497":1,"506":1,"524":1,"592":1,"633":1,"635":1,"638":1,"690":1,"699":1,"715":1,"716":1,"732":1,"783":1,"855":1,"862":1,"881":1,"897":1,"905":1,"988":2,"992":1,"996":2,"1018":1,"1019":1,"1054":1,"1093":1,"1115":1,"1116":1,"1212":3,"1237":1,"1239":1,"1247":4,"1250":1,"1251":1,"1259":4,"1270":5,"1274":1,"1286":10,"1287":1,"1288":1,"1300":7,"1301":2,"1304":1,"1309":3,"1311":5,"1312":1,"1318":1,"1323":8,"1324":16,"1333":1,"1336":1,"1337":1,"1339":2,"1350":1,"1357":1,"1359":14,"1369":1,"1379":1,"1380":2,"1395":9,"1396":9,"1402":1,"1415":8,"1416":3,"1417":5,"1427":1,"1436":11,"1441":1,"1453":1,"1454":1,"1486":1,"1492":2,"1566":1,"1651":1,"1664":1,"1699":1,"1715":1,"1727":1,"1790":1,"1792":1,"1815":1,"1840":1,"1852":1,"1858":1,"1863":1,"1923":1,"1933":1,"1943":1,"1965":1,"1972":1,"2010":1,"2024":1,"2047":1,"2076":1,"2089":1,"2113":1,"2121":1,"2157":1,"2160":1,"2165":1}}],["sovereignty",{"3":{"1567":1}}],["sonnet",{"3":{"1496":2}}],["sonaranalyzer",{"3":{"1213":1,"1395":2,"1417":1,"1453":1,"1903":1}}],["sonar",{"3":{"1133":1,"1237":2,"1266":1,"1270":1,"1300":2,"1309":1,"1312":1,"1369":1,"1380":1,"1395":1,"1436":1,"1577":1,"1673":1}}],["soak",{"3":{"1456":1}}],["sothecontractisnotvacuous",{"2":{"1436":1},"3":{"1436":1}}],["soi",{"3":{"1286":1,"2126":1}}],["sock",{"3":{"1430":1}}],["sockets",{"3":{"1300":1,"1339":2,"1436":1}}],["socketshttphandler",{"2":{"53":1,"100":1,"1329":1,"1450":1,"1663":1,"1677":1,"2006":1,"2011":1,"2014":1,"2019":1,"2030":1},"3":{"53":1,"100":1,"1312":4,"1324":2,"1329":1,"1339":11,"1380":1,"1416":3,"1441":1,"1443":2,"1450":1,"1663":1,"1677":1,"2006":1,"2011":1,"2014":1,"2019":1,"2030":1}}],["socket",{"3":{"0":2,"94":1,"95":1,"100":1,"109":1,"110":1,"235":1,"881":1,"1067":1,"1225":1,"1323":1,"1324":2,"1329":1,"1334":1,"1339":14,"1395":2,"1396":3,"1415":1,"1430":1,"1436":1,"1443":2,"1577":1,"1611":1,"1653":1,"1702":1}}],["socialsecuritynumber",{"2":{"364":1},"3":{"359":1,"364":1}}],["social",{"3":{"349":1,"657":1,"691":1,"1020":1,"1300":2,"1311":1,"1324":3,"1342":1,"1350":6,"1351":1,"1359":11,"1364":1,"1369":4,"1380":4,"1383":1,"1392":1,"1395":7,"1415":2,"1416":4,"1467":1,"1629":2,"1630":2,"1631":4,"1632":1,"1636":2,"1639":1,"1641":2,"1658":1,"1705":1,"1973":1,"1974":1,"1977":1,"1979":3,"2110":2}}],["southcentralus",{"3":{"1436":1}}],["southeast",{"3":{"975":1}}],["south",{"3":{"972":1,"1339":1,"1436":3,"1467":1,"2238":1}}],["sound",{"3":{"209":1,"659":1,"811":1,"1051":1,"1286":1,"1301":1,"1323":1,"1455":2,"1549":1,"1870":1,"1923":1,"1971":1,"1997":1}}],["sounds",{"3":{"136":1,"1150":1,"1286":1,"1339":1,"1799":1,"1951":1,"2129":1}}],["sourcing",{"1":{"1138":1,"1139":1,"1140":1,"1141":1,"1142":1,"1143":1,"1144":1,"1145":1,"1146":1,"1147":1},"3":{"0":1,"638":1,"1138":1,"1139":1,"1140":1,"1141":2,"1142":1,"1144":1,"1145":1,"1212":1,"1259":1,"1323":1,"1324":1,"1359":3,"1396":1,"1402":1,"1431":1,"1436":1,"1496":1,"1577":1,"1585":1,"2232":1}}],["source=",{"3":{"1339":1,"1441":1}}],["sourcecollectinghostapplicationbuilder",{"3":{"1199":2,"1207":1}}],["sourcecollectingconfigurationmanager",{"3":{"1199":2,"1207":1}}],["sourcetype",{"3":{"846":1,"1247":1,"1269":1,"1270":2,"1436":1}}],["sourcedatabase",{"2":{"1475":1},"3":{"1474":1,"1475":3}}],["sourcedir",{"3":{"1436":2}}],["sourced",{"1":{"1780":1,"1781":1,"1782":1,"1783":1,"1784":1,"1785":1,"1786":1,"1787":1},"3":{"0":1,"373":1,"602":1,"631":1,"643":1,"1083":1,"1085":1,"1116":1,"1142":1,"1145":1,"1286":6,"1300":2,"1312":1,"1344":1,"1350":4,"1359":8,"1362":1,"1369":4,"1376":1,"1378":1,"1380":2,"1396":3,"1415":1,"1436":1,"1489":1,"1600":1,"1630":2,"1779":1,"1780":2,"2030":1,"2205":1}}],["sourcename",{"2":{"634":1},"3":{"0":1,"634":1,"698":1,"1286":4,"1323":1,"1492":1}}],["sourcesbrandcolorfromtoken",{"3":{"1436":2}}],["sources",{"3":{"0":3,"26":1,"46":1,"47":1,"166":1,"169":1,"186":1,"265":1,"369":1,"370":1,"399":1,"407":1,"413":1,"640":1,"676":1,"715":1,"717":1,"827":1,"838":1,"905":1,"926":1,"988":2,"989":1,"1014":1,"1069":1,"1144":2,"1175":1,"1214":1,"1247":4,"1276":3,"1279":4,"1281":3,"1286":31,"1300":2,"1301":1,"1310":4,"1311":12,"1323":4,"1324":1,"1333":1,"1335":2,"1338":1,"1339":7,"1353":1,"1359":8,"1375":1,"1395":4,"1402":2,"1415":2,"1417":2,"1429":1,"1436":7,"1437":1,"1438":1,"1442":1,"1453":2,"1454":1,"1488":1,"1489":2,"1526":1,"1534":1,"1548":1,"1569":1,"1630":2,"1631":1,"1639":1,"1660":1,"1665":1,"1670":1,"1675":1,"1774":2,"1790":1,"1833":1,"1840":1,"1841":1,"1845":1,"1849":2,"1852":3,"1853":1,"1858":1,"1859":1,"1861":1,"1863":1,"1866":1,"1868":1,"1962":1,"1965":1,"2006":1,"2022":1,"2024":1,"2153":1,"2156":2,"2160":1,"2188":1,"2220":1,"2232":1}}],["source",{"0":{"1330":1,"1335":1,"1451":1,"1470":1,"1484":1,"1500":1,"1859":1,"1875":1,"2242":1},"1":{"1172":1,"1173":1,"1174":1,"1175":1,"1176":1,"1177":1,"1178":1,"1179":1,"1180":1},"2":{"138":1,"468":1,"469":1,"474":1,"477":1,"805":1,"809":1,"813":1,"1150":1,"1475":2},"3":{"0":53,"1":1,"3":1,"5":1,"10":1,"12":1,"15":5,"17":2,"24":13,"25":4,"26":10,"27":6,"31":2,"34":1,"38":1,"39":4,"42":2,"43":2,"47":4,"53":1,"57":1,"59":6,"60":1,"61":1,"62":1,"63":3,"68":3,"71":2,"72":6,"73":1,"74":4,"79":1,"80":6,"81":3,"82":3,"90":1,"93":3,"94":2,"95":8,"96":5,"98":5,"99":1,"100":2,"103":3,"109":16,"111":1,"112":1,"117":6,"121":3,"122":3,"123":4,"125":5,"126":5,"128":4,"130":1,"132":1,"135":5,"136":1,"138":2,"141":3,"142":4,"145":2,"146":1,"147":5,"150":5,"151":1,"152":1,"157":2,"160":3,"161":3,"162":1,"164":1,"166":17,"168":2,"169":1,"170":3,"171":4,"175":3,"178":3,"179":6,"180":7,"181":2,"184":1,"186":5,"189":4,"190":5,"193":1,"195":9,"196":2,"197":1,"198":3,"200":2,"201":3,"202":7,"203":2,"216":2,"217":2,"218":1,"219":5,"220":5,"225":1,"228":4,"230":9,"231":4,"235":15,"237":2,"238":1,"243":8,"245":9,"246":7,"250":1,"251":1,"252":2,"254":1,"255":5,"256":1,"257":2,"259":3,"260":4,"261":1,"265":11,"266":1,"273":2,"277":1,"280":1,"282":1,"283":2,"284":2,"285":3,"290":7,"295":12,"296":3,"303":4,"305":7,"306":4,"310":3,"314":4,"318":9,"324":1,"325":3,"328":2,"330":9,"331":2,"333":5,"336":4,"337":4,"341":17,"342":2,"343":3,"344":6,"345":2,"350":5,"352":4,"353":7,"359":1,"369":1,"370":4,"371":1,"372":1,"373":1,"379":1,"384":7,"387":2,"388":3,"390":6,"391":7,"392":4,"393":1,"395":4,"397":11,"399":1,"400":3,"401":7,"402":3,"403":1,"404":5,"406":1,"407":5,"408":4,"409":3,"412":1,"413":11,"415":3,"416":2,"418":11,"419":1,"420":3,"422":1,"423":7,"424":3,"425":2,"426":8,"427":2,"428":10,"429":5,"430":4,"433":2,"434":1,"435":1,"440":1,"442":1,"443":2,"444":3,"448":11,"450":1,"451":1,"452":2,"453":1,"454":1,"459":13,"461":1,"463":6,"464":4,"465":5,"466":2,"468":3,"469":4,"470":14,"474":1,"475":10,"476":13,"477":13,"478":1,"482":2,"483":3,"484":3,"486":11,"487":7,"488":5,"489":7,"490":7,"491":8,"492":9,"493":12,"494":8,"495":5,"497":13,"498":1,"499":6,"502":4,"507":20,"509":1,"511":6,"512":1,"513":5,"514":2,"522":2,"523":3,"524":2,"527":1,"528":1,"530":1,"535":1,"536":4,"537":1,"539":3,"544":2,"545":26,"547":1,"549":7,"550":3,"551":2,"552":4,"554":1,"556":25,"559":1,"564":7,"572":4,"576":3,"577":4,"583":33,"585":12,"586":5,"587":2,"592":1,"597":1,"599":6,"601":2,"603":3,"609":2,"616":2,"618":2,"621":1,"626":1,"629":2,"632":3,"633":3,"634":1,"636":1,"638":2,"640":14,"642":3,"644":3,"649":13,"651":1,"656":2,"657":17,"665":7,"668":4,"669":3,"672":4,"674":45,"675":3,"676":15,"677":2,"680":1,"682":1,"685":1,"690":1,"696":3,"697":2,"698":24,"699":3,"700":2,"701":4,"702":10,"703":9,"706":1,"707":9,"708":1,"709":2,"711":2,"715":6,"717":1,"720":3,"721":3,"724":5,"725":11,"727":3,"728":2,"731":1,"732":1,"733":8,"736":4,"740":5,"741":10,"742":1,"743":2,"744":2,"749":11,"751":2,"774":12,"777":1,"780":3,"782":19,"784":2,"789":4,"790":8,"792":3,"793":6,"794":4,"798":9,"799":8,"803":3,"804":5,"805":4,"806":1,"809":3,"813":4,"818":1,"819":16,"822":2,"825":1,"826":7,"827":18,"830":7,"831":3,"832":6,"833":14,"836":1,"837":5,"838":11,"839":2,"840":1,"841":2,"842":1,"846":9,"849":1,"850":1,"854":7,"857":4,"861":2,"863":1,"868":3,"869":6,"872":5,"873":3,"876":17,"877":4,"880":1,"881":14,"883":1,"884":1,"888":2,"889":1,"891":4,"892":1,"897":10,"900":2,"904":1,"905":13,"908":3,"909":1,"910":1,"912":1,"913":6,"914":2,"915":1,"916":1,"921":3,"922":9,"923":1,"924":3,"926":10,"929":1,"930":1,"931":7,"935":1,"939":1,"946":10,"949":1,"954":4,"959":1,"960":2,"963":4,"964":4,"966":1,"971":1,"972":6,"975":2,"979":5,"980":7,"982":2,"987":3,"988":17,"989":1,"990":4,"992":1,"995":4,"996":5,"998":1,"999":2,"1000":1,"1003":1,"1004":6,"1006":1,"1012":1,"1017":1,"1018":2,"1021":1,"1025":5,"1026":14,"1028":1,"1029":1,"1033":1,"1034":13,"1035":1,"1036":1,"1041":6,"1042":21,"1043":2,"1044":6,"1045":2,"1049":4,"1050":17,"1051":1,"1052":2,"1054":2,"1055":3,"1058":4,"1059":27,"1061":2,"1062":1,"1066":4,"1067":4,"1070":1,"1073":1,"1074":7,"1075":1,"1078":1,"1082":6,"1083":7,"1085":1,"1086":3,"1089":1,"1090":2,"1091":9,"1094":1,"1095":1,"1099":2,"1100":6,"1103":3,"1108":6,"1111":1,"1116":17,"1119":1,"1123":3,"1124":2,"1132":1,"1133":7,"1134":1,"1135":4,"1140":10,"1142":1,"1144":1,"1150":14,"1160":2,"1161":3,"1163":1,"1168":16,"1172":1,"1174":1,"1175":27,"1177":1,"1183":4,"1184":18,"1186":2,"1190":4,"1191":1,"1192":6,"1193":1,"1196":1,"1202":3,"1203":3,"1204":3,"1211":2,"1212":28,"1213":2,"1214":2,"1219":4,"1220":1,"1222":3,"1223":2,"1224":1,"1225":6,"1226":2,"1227":3,"1228":3,"1229":59,"1231":6,"1232":5,"1233":8,"1234":13,"1235":2,"1236":8,"1237":164,"1238":1,"1239":18,"1240":4,"1241":5,"1242":4,"1243":10,"1244":5,"1245":2,"1246":3,"1247":152,"1251":1,"1252":1,"1254":2,"1256":1,"1257":1,"1259":120,"1261":2,"1262":6,"1263":6,"1264":9,"1265":3,"1267":5,"1268":1,"1269":5,"1270":270,"1271":103,"1272":1,"1273":13,"1274":2,"1275":7,"1276":15,"1277":7,"1278":22,"1279":14,"1280":9,"1281":6,"1282":1,"1283":5,"1284":5,"1285":7,"1286":877,"1288":8,"1289":10,"1290":11,"1291":12,"1292":8,"1293":9,"1294":9,"1295":6,"1296":5,"1297":30,"1298":7,"1299":2,"1300":1020,"1301":82,"1302":6,"1303":8,"1304":11,"1305":7,"1306":9,"1307":16,"1308":8,"1309":208,"1310":78,"1311":456,"1312":85,"1313":1,"1314":3,"1315":4,"1316":8,"1317":11,"1318":10,"1319":1,"1320":28,"1321":3,"1322":14,"1323":388,"1324":595,"1325":9,"1326":11,"1327":4,"1328":4,"1329":1,"1330":3,"1331":5,"1332":19,"1333":7,"1334":10,"1335":7,"1336":8,"1337":6,"1338":10,"1339":367,"1341":3,"1342":12,"1344":7,"1345":5,"1346":1,"1347":17,"1348":5,"1349":10,"1350":359,"1353":2,"1354":1,"1359":930,"1361":1,"1362":2,"1363":3,"1364":1,"1366":1,"1368":1,"1369":84,"1371":6,"1372":2,"1373":5,"1374":1,"1375":1,"1376":1,"1377":7,"1378":8,"1379":4,"1380":199,"1381":1,"1384":3,"1387":1,"1389":1,"1390":2,"1395":373,"1396":591,"1402":129,"1403":1,"1404":4,"1405":1,"1406":5,"1407":2,"1409":1,"1410":1,"1412":1,"1413":1,"1414":1,"1415":377,"1416":32,"1417":148,"1419":2,"1420":6,"1421":1,"1422":5,"1423":5,"1424":6,"1425":6,"1426":1,"1427":58,"1428":1,"1429":3,"1430":1,"1431":4,"1433":1,"1435":1,"1436":742,"1437":1,"1438":16,"1440":3,"1441":9,"1442":2,"1443":6,"1444":6,"1445":9,"1447":2,"1449":5,"1451":1,"1453":5,"1454":4,"1455":12,"1459":2,"1461":1,"1462":1,"1464":16,"1466":1,"1467":17,"1469":2,"1470":2,"1473":1,"1474":3,"1475":3,"1476":1,"1477":1,"1478":2,"1482":1,"1484":2,"1485":2,"1486":5,"1487":2,"1488":27,"1489":8,"1490":3,"1491":1,"1492":12,"1494":1,"1495":1,"1496":108,"1497":1,"1500":5,"1504":2,"1509":1,"1510":2,"1512":1,"1521":1,"1522":1,"1526":22,"1531":6,"1534":3,"1535":3,"1545":1,"1547":1,"1548":1,"1553":2,"1554":1,"1556":1,"1569":1,"1570":1,"1572":1,"1573":1,"1576":1,"1577":43,"1578":1,"1581":1,"1582":18,"1583":6,"1585":2,"1586":7,"1587":1,"1591":7,"1594":1,"1596":1,"1606":1,"1614":1,"1619":3,"1630":1,"1631":3,"1632":16,"1636":1,"1637":2,"1638":1,"1639":4,"1640":1,"1641":2,"1646":1,"1648":4,"1649":1,"1650":4,"1651":4,"1652":5,"1653":7,"1654":1,"1655":1,"1656":6,"1659":1,"1661":2,"1664":12,"1665":1,"1671":2,"1673":6,"1675":5,"1684":1,"1685":5,"1686":5,"1687":1,"1695":2,"1697":1,"1708":1,"1710":1,"1712":1,"1718":1,"1719":3,"1722":3,"1727":4,"1728":6,"1729":3,"1731":2,"1734":1,"1736":1,"1740":1,"1755":2,"1764":27,"1765":31,"1770":4,"1773":3,"1777":2,"1778":1,"1783":1,"1787":2,"1790":3,"1792":1,"1793":1,"1802":1,"1805":2,"1807":1,"1810":1,"1812":1,"1815":3,"1817":1,"1827":1,"1831":5,"1834":2,"1838":1,"1840":4,"1841":1,"1845":3,"1846":1,"1849":9,"1851":8,"1852":8,"1853":3,"1857":1,"1859":1,"1860":2,"1861":2,"1862":2,"1865":5,"1866":3,"1867":2,"1868":2,"1875":1,"1877":3,"1886":1,"1889":1,"1891":1,"1892":1,"1895":1,"1899":1,"1902":1,"1905":1,"1910":1,"1912":1,"1921":1,"1924":1,"1927":2,"1930":1,"1933":1,"1941":1,"1942":1,"1948":1,"1950":1,"1951":1,"1955":1,"1958":1,"1959":4,"1962":1,"1965":1,"1972":1,"1979":1,"1983":1,"1985":1,"1987":1,"1988":3,"1991":1,"1994":6,"1995":1,"1997":1,"1999":1,"2000":8,"2001":2,"2002":4,"2005":1,"2008":2,"2013":2,"2014":1,"2024":2,"2028":1,"2030":1,"2033":1,"2036":1,"2038":1,"2041":2,"2043":2,"2044":4,"2046":1,"2049":1,"2053":6,"2060":1,"2064":2,"2065":2,"2066":5,"2069":1,"2070":1,"2072":3,"2074":1,"2075":2,"2076":1,"2079":2,"2080":5,"2082":1,"2083":3,"2085":1,"2086":3,"2090":1,"2097":1,"2098":1,"2106":1,"2108":1,"2111":1,"2115":2,"2117":1,"2123":2,"2126":1,"2127":1,"2128":1,"2133":1,"2136":1,"2139":1,"2142":1,"2146":1,"2147":1,"2153":1,"2156":4,"2157":5,"2159":1,"2160":2,"2169":1,"2172":1,"2179":1,"2180":1,"2188":1,"2189":1,"2191":1,"2192":4,"2193":1,"2199":1,"2202":3,"2203":1,"2211":1,"2212":1,"2215":1,"2216":1,"2221":1,"2226":2,"2230":3,"2232":6,"2234":1,"2236":1,"2239":1,"2245":2}}],["someday",{"3":{"1436":1}}],["somefixture",{"3":{"1436":3}}],["somehandler",{"3":{"1396":1}}],["somehow",{"3":{"1335":1}}],["somerandomtype",{"3":{"1380":1}}],["somerandomhandler",{"3":{"1323":2}}],["someincidentalclass",{"3":{"1311":1}}],["someintegrationevent",{"2":{"1252":1},"3":{"1252":1}}],["someid",{"3":{"1237":1}}],["sometimes",{"3":{"1345":1,"1359":1,"1436":1,"2099":1}}],["sometime",{"3":{"1077":1}}],["somethingbroke",{"3":{"1436":4}}],["something",{"3":{"1":1,"79":1,"81":1,"103":1,"108":1,"110":1,"135":1,"138":1,"139":1,"140":1,"147":1,"150":1,"175":1,"258":1,"290":1,"449":1,"539":1,"626":1,"627":1,"681":1,"683":1,"734":1,"740":1,"751":1,"758":1,"781":1,"793":1,"799":1,"812":1,"817":1,"860":2,"905":1,"931":1,"989":2,"1019":1,"1059":1,"1066":1,"1067":1,"1068":2,"1074":1,"1102":1,"1140":1,"1220":1,"1237":2,"1247":1,"1248":1,"1259":4,"1265":1,"1270":2,"1281":1,"1286":12,"1292":1,"1293":1,"1300":7,"1302":1,"1309":4,"1311":4,"1320":1,"1323":9,"1324":16,"1339":3,"1344":1,"1350":4,"1352":1,"1359":17,"1368":1,"1369":1,"1380":1,"1389":1,"1395":5,"1396":12,"1402":3,"1409":1,"1415":5,"1416":1,"1417":9,"1427":2,"1435":1,"1436":20,"1443":2,"1451":1,"1455":1,"1459":1,"1460":1,"1467":1,"1478":1,"1486":1,"1492":3,"1512":1,"1637":1,"1651":1,"1653":1,"1684":1,"1686":2,"1693":1,"1787":1,"1794":1,"1804":1,"1815":2,"1817":2,"1836":1,"1842":1,"1847":1,"1865":1,"1867":1,"1881":2,"1890":1,"1900":1,"1913":1,"1933":1,"1943":2,"1949":1,"1952":1,"1963":1,"1965":1,"1982":1,"2002":2,"2016":1,"2034":1,"2043":1,"2049":1,"2056":1,"2064":1,"2094":1,"2098":1,"2099":1,"2116":1,"2118":1,"2124":2,"2126":1,"2128":1,"2132":1,"2138":3,"2157":1,"2158":1,"2181":1,"2182":1,"2183":1,"2203":1,"2232":1}}],["somebody",{"3":{"896":1,"1082":1,"1083":1,"1134":1,"1229":1,"1286":1,"1306":1,"1323":2,"1324":1,"1359":1,"1396":2,"1415":2,"1422":1,"1427":1,"1436":2,"1465":1,"1475":1,"1951":1,"2039":1,"2043":1,"2046":1,"2047":1,"2048":1,"2064":1,"2153":1,"2158":1,"2170":2,"2174":1,"2180":2,"2194":1,"2195":1,"2196":1,"2202":1,"2203":1}}],["someone",{"3":{"160":1,"215":1,"255":1,"318":1,"342":1,"343":1,"372":1,"373":1,"387":1,"407":1,"435":1,"538":1,"574":1,"592":1,"610":1,"627":1,"634":1,"692":1,"715":1,"717":1,"759":1,"765":1,"767":2,"812":1,"820":1,"821":1,"828":1,"855":1,"890":1,"965":1,"974":1,"1052":1,"1085":1,"1102":1,"1160":1,"1237":2,"1247":2,"1263":1,"1270":1,"1286":4,"1300":4,"1309":3,"1311":2,"1323":5,"1324":7,"1332":1,"1339":2,"1350":1,"1359":7,"1369":1,"1395":4,"1396":11,"1402":1,"1415":8,"1417":2,"1436":14,"1467":1,"1472":1,"1654":1,"1718":1,"1788":1,"1794":2,"1797":1,"1800":2,"1804":1,"1809":1,"1813":1,"1815":1,"1817":1,"1818":1,"1828":1,"1872":1,"1878":1,"1900":2,"1901":1,"1914":1,"1947":2,"1960":2,"1966":1,"1976":1,"1993":1,"1995":1,"1997":1,"2029":1,"2033":1,"2045":2,"2048":1,"2067":1,"2073":1,"2089":1,"2099":1,"2118":1,"2129":1,"2144":1,"2147":1,"2148":1,"2154":1}}],["someerror",{"3":{"109":1,"1220":1,"1229":1}}],["some",{"3":{"6":1,"52":1,"134":1,"188":1,"318":1,"358":1,"412":1,"517":1,"537":1,"574":1,"585":1,"676":1,"725":1,"757":1,"759":2,"800":1,"848":1,"891":1,"974":1,"995":1,"1052":1,"1135":1,"1143":1,"1153":1,"1215":1,"1216":1,"1247":1,"1271":3,"1286":4,"1300":3,"1310":1,"1311":1,"1312":1,"1320":1,"1323":2,"1324":6,"1339":2,"1350":2,"1359":6,"1369":1,"1380":4,"1395":1,"1396":3,"1415":3,"1416":2,"1417":3,"1436":10,"1438":1,"1453":2,"1454":1,"1455":1,"1467":1,"1476":1,"1498":1,"1526":1,"1531":1,"1537":2,"1627":1,"1631":1,"1654":1,"1708":1,"1776":1,"1801":1,"1814":2,"1816":1,"1817":1,"1829":1,"1914":1,"1986":1,"2059":1,"2073":1,"2079":1,"2114":1,"2122":1,"2136":1,"2140":1,"2202":1}}],["somewhere",{"3":{"0":1,"138":1,"150":1,"157":1,"255":1,"544":1,"609":1,"718":1,"809":1,"921":1,"989":1,"995":1,"1027":1,"1109":1,"1235":1,"1237":1,"1271":1,"1286":2,"1311":1,"1324":3,"1350":1,"1359":2,"1395":1,"1396":5,"1416":2,"1417":1,"1423":1,"1432":1,"1436":6,"1467":1,"1488":1,"1531":1,"1575":1,"1818":1,"1909":1,"1922":1,"1954":1,"1967":2,"1972":1,"1987":1,"2044":1,"2116":1,"2158":1,"2165":1,"2175":1,"2182":1,"2185":1,"2197":1}}],["soonest",{"3":{"1348":2,"1350":1}}],["sooner",{"3":{"19":1,"640":1,"1436":1}}],["soon",{"3":{"0":1,"234":1,"827":1,"1116":1,"1278":1,"1323":3,"1324":2,"1339":1,"1359":1,"1396":1,"1433":1,"1436":2,"1553":1,"1631":1,"1902":1,"2157":1,"2185":1,"2189":1}}],["solo",{"3":{"1686":1}}],["sold",{"3":{"1340":1,"1342":1,"1346":1,"1350":4,"1359":2,"1380":1,"1395":5}}],["solvable",{"3":{"1638":1}}],["solving",{"3":{"1324":1,"1358":1,"1443":1,"1539":1}}],["solve",{"3":{"390":1,"811":1,"948":1,"990":1,"1043":1,"1218":1,"1229":4,"1247":1,"1286":1,"1310":1,"1350":1,"1395":1,"1396":1,"1415":2,"1417":1,"1488":1,"1859":1,"1942":1,"2181":1,"2211":1}}],["solves",{"3":{"272":1,"555":1,"971":2,"1237":1,"1247":1,"1259":1,"1270":2,"1298":1,"1300":1,"1324":2,"1359":3,"1395":1,"1396":1,"1417":1,"1436":1,"1443":1,"1447":1,"1818":1,"1859":1,"1910":1,"2076":1,"2124":1}}],["solved",{"3":{"26":1,"601":1,"708":1,"827":1,"990":1,"1115":1,"1311":1,"1312":1,"1323":1,"1339":1,"1380":1,"1395":1,"1396":4,"1415":1,"1417":1,"1436":1,"1900":4,"1996":1,"2086":1,"2162":1}}],["solid",{"0":{"1538":1},"3":{"1216":1,"1229":1,"1234":1,"1237":4,"1241":1,"1244":1,"1247":4,"1259":4,"1261":1,"1264":1,"1270":16,"1271":5,"1278":1,"1286":14,"1291":1,"1300":20,"1301":1,"1309":9,"1310":2,"1311":15,"1316":1,"1322":1,"1323":12,"1324":15,"1339":1,"1343":1,"1350":5,"1353":1,"1359":60,"1369":2,"1371":1,"1380":6,"1384":1,"1395":17,"1396":19,"1402":8,"1405":1,"1415":8,"1416":2,"1417":16,"1427":5,"1431":1,"1436":12,"1499":1,"1526":1,"1535":1,"1577":1,"1581":1,"1582":1,"1583":1,"1584":1,"1588":1,"1591":1,"1597":1,"1784":1,"1795":1,"1796":1,"1799":1,"1971":1,"2090":1}}],["solutions",{"3":{"556":1,"633":1,"1485":1,"1486":1,"1654":1,"1696":1,"1726":1,"1731":1,"2089":1,"2220":1,"2224":1}}],["solutionfilename",{"2":{"136":1},"3":{"136":1,"1431":1,"1436":5}}],["solution",{"0":{"1215":1,"1486":1,"1649":1,"1683":1,"1695":1,"2089":1,"2120":1},"1":{"1485":1,"1486":1,"1487":1,"1488":1,"1489":1,"1490":1,"1491":1,"1492":1,"1493":1,"1494":1},"3":{"0":4,"132":1,"135":1,"137":2,"140":1,"142":1,"370":2,"470":2,"509":1,"609":1,"631":1,"632":2,"633":7,"634":1,"635":2,"733":1,"863":1,"870":1,"883":1,"905":1,"906":2,"938":1,"939":2,"942":1,"979":1,"980":1,"1034":1,"1067":2,"1190":1,"1192":1,"1212":1,"1215":3,"1237":1,"1247":1,"1259":1,"1271":1,"1286":2,"1296":1,"1300":5,"1309":3,"1311":1,"1323":4,"1350":2,"1359":1,"1395":1,"1396":12,"1402":2,"1428":2,"1436":12,"1441":1,"1445":1,"1449":1,"1453":5,"1454":6,"1455":3,"1485":1,"1486":8,"1487":12,"1488":2,"1490":3,"1492":5,"1494":2,"1524":1,"1526":2,"1531":1,"1577":2,"1591":1,"1596":1,"1599":1,"1602":2,"1646":3,"1647":1,"1649":2,"1650":1,"1654":1,"1656":1,"1657":1,"1658":1,"1661":8,"1662":1,"1674":1,"1675":1,"1681":1,"1684":4,"1685":4,"1686":4,"1690":1,"1692":2,"1694":1,"1695":1,"1700":1,"1701":2,"1703":1,"1704":2,"1705":2,"1707":1,"1717":1,"1718":3,"1722":3,"1725":2,"1726":4,"1727":6,"1728":8,"1729":10,"1730":1,"1731":4,"1781":2,"1810":1,"1829":1,"1939":1,"2044":1,"2056":1,"2087":2,"2089":1,"2097":5,"2098":4,"2099":2,"2100":1,"2101":3,"2103":1,"2105":1,"2211":1,"2220":1,"2224":1,"2239":1}}],["solely",{"3":{"77":1,"149":1,"776":1,"1323":2,"1324":1,"1325":1,"1359":3,"1369":1,"1396":1,"1415":1,"1486":1,"1488":1,"1641":1,"1887":1}}],["sole",{"1":{"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"294":1,"295":1,"296":1},"3":{"0":6,"20":1,"288":1,"291":3,"295":1,"296":2,"370":1,"563":1,"620":1,"756":2,"757":1,"762":1,"827":1,"833":1,"1212":1,"1229":1,"1237":1,"1247":1,"1270":2,"1286":2,"1311":2,"1324":2,"1350":1,"1359":5,"1396":7,"1415":1,"1416":1,"1417":1,"1436":5,"1447":1,"1455":1,"1467":2,"1469":1,"1473":1,"1476":1,"1477":1,"1488":2,"1496":3,"1534":1,"1652":1,"1664":1,"1750":1,"2232":2}}],["sortability",{"3":{"1395":1}}],["sortabletemplatecolumngrid",{"3":{"1436":2}}],["sortabletemplatecolumn",{"3":{"1436":1}}],["sortablegridcolumnsusepropertycolumn",{"3":{"1436":3}}],["sortable=",{"3":{"1395":1,"1436":5}}],["sortablecolumns",{"3":{"1436":2}}],["sortablecolumnconventiontests",{"3":{"1199":1,"1207":2,"1436":9,"1489":1}}],["sortablecolumnconventiontestsbase",{"3":{"1199":2,"1207":2,"1436":8}}],["sortablecolumnfitnesstests",{"3":{"1199":1,"1207":2,"1436":3,"1489":1}}],["sortable",{"3":{"332":1,"1247":1,"1286":1,"1311":1,"1324":2,"1350":1,"1359":3,"1383":1,"1395":10,"1396":1,"1415":4,"1436":14,"1489":1,"1629":1,"1640":2,"1671":1,"1748":1,"1765":1,"1988":1,"1990":1}}],["sortorder",{"3":{"1342":1,"1350":10,"1359":18,"1362":2,"1369":5,"1395":9}}],["sortby",{"3":{"1324":1}}],["sortproperty",{"3":{"1286":4}}],["sort=",{"3":{"1238":1,"1815":1}}],["sorttestentity",{"3":{"1199":2,"1207":1}}],["sortdescending",{"3":{"1324":1}}],["sortdefinition",{"3":{"881":1,"1324":1}}],["sortdirection=asc",{"3":{"1324":1,"1380":1,"1395":3}}],["sortdirection",{"3":{"330":1,"740":1,"741":1,"881":1,"1247":2,"1311":1,"1324":2,"1395":2,"1396":1,"1415":4,"1640":1,"1988":1}}],["sorts",{"3":{"583":1,"633":1,"741":1,"1242":1,"1247":3,"1286":2,"1310":1,"1311":1,"1313":1,"1323":2,"1342":1,"1350":4,"1359":7,"1362":1,"1369":1,"1395":4,"1396":1,"1402":1,"1415":2,"1436":5,"1599":1,"1699":2,"1727":2,"1815":1,"1878":1,"1879":1,"1886":1,"2089":1}}],["sortkey",{"3":{"549":1,"1278":1,"1286":3,"1577":1}}],["sortvalue",{"3":{"549":1,"1228":1,"1229":3,"1286":1}}],["sorted",{"3":{"359":1,"549":1,"1059":1,"1200":1,"1238":1,"1242":1,"1247":3,"1270":1,"1286":3,"1300":8,"1315":1,"1323":1,"1324":2,"1350":4,"1354":1,"1359":2,"1369":1,"1371":1,"1395":9,"1396":2,"1402":2,"1427":1,"1436":7,"1492":1,"1496":1,"1498":1,"1877":1,"1988":1}}],["sortcolumn=id",{"3":{"1324":1,"1380":1,"1395":3}}],["sortcolumn",{"2":{"42":1},"3":{"42":1,"330":1,"549":1,"740":1,"741":1,"881":1,"1228":1,"1229":2,"1247":4,"1286":1,"1311":1,"1324":3,"1369":1,"1395":2,"1396":1,"1415":4,"1640":2,"1765":1,"1988":1}}],["sorting",{"0":{"1242":1},"3":{"0":1,"319":1,"329":1,"333":1,"363":1,"545":1,"549":2,"591":1,"742":1,"1050":2,"1202":1,"1203":1,"1238":2,"1243":2,"1245":1,"1247":7,"1284":1,"1300":1,"1311":2,"1324":1,"1350":3,"1354":1,"1359":7,"1362":1,"1369":2,"1380":2,"1395":4,"1396":5,"1402":1,"1415":4,"1436":4,"1438":1,"1526":2,"1546":1,"1557":1,"1560":1,"1577":1,"1630":1,"1631":1,"1632":2,"1639":1,"1640":3,"1666":1,"1669":1,"1748":2,"1815":1,"1816":1,"1817":1,"1987":1,"1988":1,"1997":1}}],["sort",{"2":{"1353":1},"3":{"0":5,"122":1,"243":1,"328":1,"329":1,"330":1,"331":1,"361":1,"547":1,"549":8,"583":4,"585":2,"595":1,"633":1,"881":4,"882":1,"1050":2,"1053":1,"1192":1,"1212":1,"1228":2,"1229":9,"1242":4,"1243":6,"1244":3,"1245":1,"1247":23,"1259":1,"1263":1,"1270":1,"1278":2,"1286":24,"1309":1,"1315":1,"1323":5,"1324":22,"1342":1,"1344":1,"1350":31,"1353":1,"1354":1,"1357":2,"1359":120,"1362":2,"1369":15,"1380":15,"1383":1,"1392":4,"1395":46,"1396":8,"1402":14,"1406":3,"1415":11,"1436":5,"1438":6,"1496":1,"1498":1,"1526":2,"1600":1,"1607":1,"1630":5,"1631":3,"1632":8,"1639":5,"1640":9,"1669":1,"1675":1,"1686":1,"1689":2,"1699":1,"1715":1,"1720":1,"1727":2,"1741":1,"1750":1,"1751":1,"1769":1,"1773":1,"1815":2,"1881":2,"1886":2,"1923":1,"1928":1,"1988":2,"1991":3,"2070":3,"2072":2,"2076":1,"2079":1,"2080":1,"2089":2,"2144":1,"2145":1,"2232":1}}],["softer",{"3":{"1396":2}}],["softens",{"3":{"803":1}}],["softdeletingfixture",{"3":{"1199":2,"1207":1,"1436":5}}],["softdeletabletestentity",{"3":{"1199":3,"1207":1}}],["softdeletableentity",{"3":{"1199":3,"1207":1}}],["softdeletepredicate",{"3":{"1286":1}}],["softdeletefidelitytests",{"2":{"1535":1},"3":{"1199":1,"1207":2,"1535":1}}],["softdeletefilteronly",{"3":{"698":1}}],["softdeletefiltername",{"2":{"697":1},"3":{"697":1,"698":1,"1286":1}}],["softdeletefiltersql",{"2":{"889":2,"892":3,"1034":1,"1176":1},"3":{"0":2,"889":7,"891":1,"892":6,"1034":3,"1175":1,"1176":1,"1198":1,"1199":4,"1203":1,"1207":2,"1280":1,"1281":2,"1286":27,"1369":1,"1415":2,"1496":1}}],["softdeletequeryfiltertests",{"3":{"1199":1,"1207":5}}],["softdeleteenforcementtests",{"3":{"1199":2,"1207":4,"1286":4,"1436":16,"1489":2,"1577":1}}],["softdeleteenforcementtestsbase",{"3":{"39":2,"1199":3,"1207":2,"1436":14}}],["softdeleteenforcementfitnesstests",{"3":{"1199":1,"1207":3,"1436":22,"1489":1}}],["softdeletetestdbcontext",{"3":{"1199":6,"1207":2}}],["softdeleted",{"3":{"1286":1,"1396":1,"1402":1}}],["softdeletedmarkerfailed",{"2":{"459":1,"465":1},"3":{"459":1,"465":1,"1323":2,"1415":1}}],["softdeleteduserfilter",{"2":{"464":1,"1311":1,"2066":1},"3":{"459":2,"464":2,"466":1,"698":1,"749":2,"1311":3,"1436":1,"1666":1,"2066":1}}],["softdeleteduservalidatortests",{"3":{"1199":2,"1207":4,"1323":1,"1438":2}}],["softdeleteduservalidator",{"2":{"460":1,"463":1},"3":{"0":1,"459":4,"460":1,"463":3,"464":1,"465":1,"1199":4,"1203":1,"1207":2,"1300":1,"1322":3,"1323":8,"1404":1,"1415":3,"1496":2}}],["softdeletedusercachetests",{"3":{"1199":1,"1207":2,"1438":1}}],["softdeletedusercache",{"2":{"459":3,"461":2,"463":1,"2066":1},"3":{"0":1,"459":7,"461":4,"463":3,"464":1,"465":1,"1199":7,"1203":1,"1207":2,"1300":10,"1301":3,"1311":6,"1323":2,"1403":1,"1438":1,"2066":2}}],["softdeletedusermiddlewaretests",{"2":{"459":1,"466":1},"3":{"459":3,"463":1,"464":1,"466":2,"1199":1,"1207":2}}],["softdeletedusermiddleware",{"2":{"463":1,"464":1,"748":1,"752":1,"1923":1,"2066":1,"2069":1},"3":{"0":1,"459":7,"461":1,"463":4,"464":1,"465":1,"748":1,"749":4,"752":1,"1199":3,"1203":1,"1207":2,"1212":1,"1300":5,"1301":2,"1311":10,"1323":2,"1415":2,"1923":1,"2066":5,"2069":1}}],["softdeleteuniqueindexconventiontests",{"3":{"890":1,"891":1,"1199":1,"1207":8,"1286":1,"1438":1}}],["softdeleteuniqueindexconvention",{"2":{"889":1,"1212":1,"1281":1,"1362":1,"1412":1},"3":{"0":1,"888":1,"889":8,"890":1,"891":2,"892":1,"1198":1,"1199":2,"1203":1,"1207":2,"1212":1,"1280":1,"1281":3,"1286":32,"1362":2,"1369":9,"1396":7,"1412":1,"1415":4,"1496":2,"1577":1}}],["softdelete",{"3":{"0":2,"39":3,"549":2,"551":1,"699":1,"701":1,"1207":1,"1212":2,"1232":1,"1237":1,"1240":1,"1247":1,"1273":1,"1276":1,"1286":6,"1436":13,"1438":1,"1489":1,"1664":1,"1851":2}}],["softwarefactory",{"3":{"1395":1}}],["software",{"1":{"2212":1,"2213":1,"2214":1,"2215":1,"2216":1},"3":{"370":1,"1395":2,"1454":2,"1673":1,"1787":1,"1793":1,"1802":1,"1807":1,"1812":1,"1817":1,"1827":1,"1834":1,"1846":1,"1853":1,"1862":1,"1868":1,"1877":1,"1886":1,"1892":1,"1899":1,"1905":1,"1924":1,"1930":1,"1941":1,"1950":1,"1959":1,"1965":1,"1979":1,"1985":1,"1991":1,"1997":1,"2002":1,"2028":1,"2038":1,"2049":1,"2060":1,"2069":1,"2080":1,"2086":1,"2098":1,"2108":1,"2115":1,"2123":1,"2128":1,"2133":1,"2139":1,"2169":1,"2180":1,"2193":1,"2203":1,"2211":1,"2212":1,"2213":1,"2217":1,"2218":1,"2220":8}}],["soft",{"1":{"36":1,"37":1,"38":1,"39":1,"40":1,"41":1,"42":1,"43":1,"456":1,"457":1,"458":1,"459":1,"460":1,"461":1,"462":1,"463":1,"464":1,"465":1,"466":1,"886":1,"887":1,"888":1,"889":1,"890":1,"891":1,"892":1,"893":1,"2061":1,"2062":1,"2063":1,"2064":1,"2065":1,"2066":1,"2067":1,"2068":1,"2069":1},"3":{"0":14,"36":1,"38":3,"39":2,"40":1,"41":2,"313":1,"318":1,"326":1,"346":1,"358":1,"361":1,"362":2,"366":1,"456":1,"458":3,"459":3,"461":3,"462":2,"503":1,"538":1,"545":1,"549":1,"550":1,"551":1,"690":2,"698":4,"699":2,"700":1,"707":2,"710":1,"714":1,"715":1,"716":3,"721":2,"733":1,"734":1,"735":1,"748":1,"749":2,"781":1,"782":1,"886":1,"888":3,"889":7,"890":7,"891":4,"893":1,"905":1,"910":1,"926":1,"950":1,"1033":2,"1034":1,"1059":1,"1061":1,"1063":1,"1082":2,"1084":1,"1086":1,"1087":1,"1116":1,"1118":1,"1120":1,"1141":1,"1142":1,"1146":1,"1147":1,"1168":4,"1174":2,"1175":4,"1180":1,"1192":2,"1201":2,"1212":6,"1213":1,"1230":2,"1231":10,"1232":2,"1236":2,"1237":29,"1240":1,"1244":1,"1247":9,"1270":3,"1273":1,"1274":1,"1275":1,"1276":2,"1278":2,"1280":1,"1281":6,"1286":80,"1287":1,"1289":1,"1290":1,"1300":16,"1301":6,"1304":1,"1306":1,"1309":3,"1311":8,"1323":14,"1324":1,"1342":2,"1343":4,"1346":1,"1347":1,"1350":46,"1352":2,"1358":1,"1359":80,"1361":1,"1362":4,"1363":1,"1369":26,"1380":3,"1395":6,"1396":82,"1398":2,"1399":1,"1402":43,"1405":3,"1406":1,"1412":2,"1415":31,"1417":3,"1431":2,"1436":35,"1438":12,"1453":1,"1480":1,"1487":2,"1489":2,"1490":2,"1496":3,"1526":2,"1534":1,"1535":1,"1545":2,"1567":3,"1572":1,"1577":4,"1586":2,"1591":2,"1612":1,"1614":1,"1618":2,"1630":3,"1631":11,"1632":29,"1633":4,"1634":7,"1635":13,"1636":1,"1637":1,"1638":14,"1639":51,"1640":6,"1641":2,"1651":2,"1653":1,"1657":1,"1662":1,"1664":1,"1667":1,"1671":2,"1672":3,"1675":1,"1685":2,"1686":1,"1698":1,"1700":1,"1704":1,"1717":1,"1719":1,"1720":1,"1727":1,"1747":2,"1748":6,"1749":1,"1765":4,"1769":7,"1779":1,"1784":1,"1795":1,"1796":1,"1799":1,"1810":2,"1812":1,"1815":1,"1825":1,"1839":2,"1851":3,"1923":1,"1964":1,"2034":1,"2060":1,"2061":4,"2062":1,"2063":2,"2064":2,"2066":4,"2068":1,"2069":6,"2090":1,"2098":1,"2164":2,"2167":1,"2168":1,"2186":1,"2202":1,"2210":1,"2221":1,"2230":2,"2232":6}}],["so",{"0":{"96":1,"2021":1,"2165":1},"3":{"0":224,"1":2,"5":1,"15":3,"18":1,"19":2,"20":1,"21":2,"22":1,"23":2,"24":15,"26":4,"27":4,"30":1,"31":10,"32":2,"33":2,"34":1,"39":11,"41":5,"42":1,"43":1,"45":1,"46":2,"47":2,"53":4,"54":2,"59":4,"62":2,"63":1,"68":1,"71":2,"72":3,"73":2,"76":2,"78":3,"79":1,"80":3,"81":4,"88":1,"91":5,"92":1,"93":4,"94":2,"95":4,"97":2,"98":9,"99":2,"100":5,"101":1,"107":2,"109":23,"110":4,"111":3,"117":3,"118":2,"119":1,"120":2,"121":5,"122":7,"123":1,"124":1,"125":2,"126":4,"127":2,"128":2,"130":2,"132":2,"135":21,"136":2,"137":2,"139":4,"141":2,"142":2,"145":6,"147":7,"149":1,"150":5,"151":1,"152":1,"155":1,"157":6,"158":2,"159":4,"160":7,"161":2,"164":2,"165":1,"166":3,"167":1,"168":1,"170":2,"171":1,"173":1,"174":2,"175":3,"177":3,"178":4,"179":2,"180":4,"181":1,"184":2,"185":1,"186":9,"187":2,"189":2,"191":1,"193":2,"195":10,"196":2,"197":2,"198":1,"200":8,"201":8,"202":6,"203":3,"206":1,"207":1,"208":2,"213":2,"214":4,"215":2,"216":3,"217":2,"218":1,"219":3,"220":4,"223":2,"225":8,"227":3,"228":2,"230":5,"231":2,"233":3,"234":3,"235":13,"236":3,"237":6,"241":3,"242":1,"243":12,"244":3,"245":4,"246":1,"248":3,"249":2,"250":1,"253":1,"254":3,"255":9,"256":1,"257":1,"258":1,"259":4,"260":2,"261":2,"263":1,"264":1,"265":31,"266":2,"267":3,"268":1,"272":1,"273":5,"274":3,"275":2,"276":2,"279":2,"280":3,"281":3,"282":2,"283":3,"284":1,"285":3,"286":3,"290":2,"291":4,"292":2,"293":1,"295":5,"298":1,"300":2,"301":2,"302":2,"303":3,"304":1,"305":3,"306":2,"309":2,"310":4,"311":1,"312":2,"314":2,"317":1,"318":13,"319":2,"320":3,"322":1,"324":2,"325":4,"326":3,"330":5,"331":1,"332":1,"333":2,"335":5,"337":1,"339":1,"341":13,"342":4,"343":5,"344":2,"349":1,"350":15,"351":2,"352":4,"353":4,"358":5,"359":5,"360":3,"361":4,"365":4,"370":8,"371":2,"373":3,"374":1,"375":1,"380":1,"383":4,"384":2,"388":5,"390":6,"391":2,"392":1,"393":1,"395":4,"396":2,"397":13,"398":2,"399":2,"400":2,"401":5,"402":2,"403":1,"404":2,"405":2,"407":6,"408":1,"409":2,"413":7,"415":2,"418":2,"419":1,"420":3,"423":1,"424":2,"425":1,"426":2,"427":2,"428":1,"434":2,"435":3,"436":1,"440":1,"441":1,"442":1,"443":3,"444":4,"446":1,"448":5,"449":3,"450":3,"451":3,"452":1,"454":1,"457":1,"458":2,"459":13,"460":4,"461":3,"463":2,"464":3,"465":4,"466":1,"468":3,"469":1,"470":2,"471":1,"477":1,"478":2,"481":1,"482":1,"483":2,"484":3,"486":3,"487":2,"488":2,"489":2,"490":4,"491":4,"492":5,"493":7,"494":5,"495":3,"497":2,"498":2,"499":5,"501":5,"502":1,"505":1,"506":1,"507":7,"508":3,"509":1,"511":6,"512":1,"513":2,"514":2,"517":2,"518":6,"519":1,"520":1,"522":3,"523":3,"524":4,"526":3,"527":3,"528":5,"529":3,"530":5,"531":1,"532":1,"534":10,"536":8,"537":4,"538":6,"539":5,"540":4,"543":3,"544":1,"545":7,"546":2,"547":6,"549":13,"550":4,"551":8,"552":2,"555":1,"556":9,"557":4,"558":6,"559":1,"562":1,"564":7,"565":3,"566":3,"567":1,"572":8,"573":2,"574":2,"575":3,"576":2,"578":1,"583":8,"584":1,"585":10,"586":1,"589":1,"590":2,"591":3,"592":3,"593":5,"594":1,"597":1,"599":12,"600":1,"601":4,"602":1,"603":1,"605":1,"607":11,"609":14,"610":4,"611":5,"612":3,"613":1,"616":1,"617":1,"618":10,"619":2,"620":4,"624":7,"626":18,"627":2,"628":4,"629":3,"631":2,"632":2,"633":10,"634":2,"635":5,"636":2,"638":8,"640":12,"641":3,"642":6,"644":2,"648":1,"649":9,"650":3,"651":4,"655":1,"656":2,"657":12,"658":5,"659":3,"660":6,"664":1,"665":8,"666":1,"667":4,"668":2,"672":1,"674":11,"675":1,"676":4,"677":1,"680":1,"681":2,"682":5,"683":1,"684":8,"690":7,"691":8,"692":11,"693":1,"696":2,"698":16,"699":2,"700":4,"702":3,"703":1,"706":2,"707":6,"708":1,"709":4,"714":2,"715":5,"716":5,"717":5,"718":2,"719":2,"720":1,"723":1,"725":11,"726":1,"727":5,"728":1,"731":1,"732":1,"733":8,"734":1,"735":3,"736":1,"739":1,"740":2,"741":11,"742":1,"743":4,"747":1,"748":1,"749":8,"750":1,"751":3,"755":1,"756":1,"757":7,"758":5,"759":7,"760":2,"761":1,"762":1,"764":4,"766":6,"767":2,"768":4,"769":1,"773":1,"774":5,"775":4,"776":5,"778":1,"780":1,"781":1,"782":3,"783":1,"784":4,"785":1,"789":4,"790":9,"791":2,"792":6,"793":4,"797":1,"798":2,"799":5,"801":2,"804":1,"805":2,"809":1,"810":4,"812":2,"813":2,"814":2,"819":11,"820":1,"821":5,"825":2,"826":7,"827":19,"828":2,"829":8,"830":1,"831":4,"832":4,"833":4,"836":1,"837":4,"838":10,"839":1,"840":1,"844":1,"845":1,"846":7,"848":3,"849":1,"853":3,"854":6,"856":4,"857":4,"861":10,"862":3,"863":9,"867":1,"868":1,"869":3,"870":2,"871":1,"872":2,"873":3,"874":1,"875":4,"876":5,"877":1,"881":14,"882":3,"883":3,"884":2,"888":1,"889":5,"890":2,"891":3,"896":1,"897":10,"898":1,"899":3,"903":1,"904":2,"905":22,"906":2,"907":7,"908":2,"909":1,"912":1,"914":5,"916":4,"917":1,"920":2,"921":2,"922":8,"923":1,"924":2,"926":11,"927":1,"929":1,"930":2,"931":4,"932":4,"938":1,"939":2,"941":2,"945":1,"946":4,"948":5,"952":1,"953":1,"954":11,"955":3,"956":4,"958":2,"959":1,"960":2,"963":3,"964":8,"965":1,"966":2,"967":1,"971":1,"972":6,"973":3,"974":5,"975":1,"979":1,"980":3,"981":1,"982":3,"983":2,"986":1,"987":1,"988":12,"989":2,"990":4,"991":1,"995":2,"996":10,"997":1,"998":4,"999":2,"1003":1,"1004":4,"1005":3,"1006":3,"1012":4,"1013":1,"1014":2,"1016":2,"1017":3,"1018":26,"1019":8,"1020":7,"1021":2,"1024":1,"1026":4,"1028":5,"1029":1,"1030":1,"1033":2,"1034":5,"1036":3,"1041":3,"1042":8,"1043":2,"1044":4,"1045":2,"1049":1,"1050":11,"1051":1,"1052":4,"1054":2,"1055":3,"1058":1,"1059":13,"1060":1,"1061":4,"1062":3,"1065":1,"1066":5,"1067":14,"1068":3,"1069":3,"1074":3,"1075":4,"1076":1,"1077":2,"1079":1,"1081":1,"1082":2,"1083":5,"1085":5,"1089":2,"1090":3,"1091":17,"1092":4,"1093":6,"1100":6,"1107":1,"1108":5,"1109":2,"1115":3,"1116":15,"1117":4,"1118":7,"1119":1,"1123":2,"1124":14,"1125":3,"1126":2,"1127":2,"1128":1,"1132":4,"1133":12,"1134":3,"1135":5,"1136":1,"1144":1,"1145":1,"1150":2,"1154":3,"1155":1,"1156":1,"1160":2,"1161":4,"1162":1,"1163":1,"1168":6,"1169":1,"1170":3,"1174":1,"1175":6,"1176":1,"1177":3,"1178":2,"1183":1,"1184":7,"1185":1,"1186":2,"1187":2,"1190":1,"1191":4,"1193":1,"1196":2,"1210":1,"1212":34,"1213":2,"1214":2,"1215":2,"1216":2,"1218":1,"1219":1,"1220":4,"1221":2,"1222":2,"1223":3,"1224":2,"1225":3,"1226":1,"1227":3,"1228":4,"1229":54,"1230":1,"1231":6,"1232":5,"1233":6,"1234":8,"1235":2,"1236":4,"1237":102,"1239":10,"1240":4,"1241":15,"1242":6,"1243":7,"1244":6,"1246":3,"1247":186,"1248":1,"1249":4,"1250":1,"1251":3,"1252":9,"1253":7,"1254":2,"1255":4,"1256":8,"1257":6,"1258":7,"1259":183,"1260":1,"1261":2,"1262":3,"1263":20,"1264":6,"1265":11,"1266":1,"1267":11,"1269":13,"1270":230,"1271":55,"1273":11,"1274":11,"1275":13,"1276":12,"1277":3,"1278":9,"1279":4,"1280":3,"1281":12,"1282":3,"1283":7,"1284":10,"1285":3,"1286":606,"1287":2,"1288":8,"1289":12,"1290":9,"1291":2,"1292":7,"1293":11,"1294":9,"1295":1,"1296":2,"1297":7,"1298":6,"1299":2,"1300":451,"1301":114,"1302":2,"1303":2,"1304":7,"1305":3,"1306":3,"1307":9,"1308":4,"1309":176,"1310":40,"1311":440,"1312":61,"1313":1,"1314":3,"1315":7,"1316":13,"1317":10,"1318":11,"1320":12,"1321":2,"1322":10,"1323":392,"1324":746,"1325":2,"1326":7,"1327":4,"1328":7,"1329":1,"1330":3,"1331":1,"1332":10,"1333":12,"1334":3,"1335":4,"1336":6,"1337":10,"1338":10,"1339":339,"1341":2,"1342":9,"1343":3,"1344":4,"1345":2,"1346":2,"1347":8,"1348":7,"1349":7,"1350":297,"1352":13,"1353":10,"1354":4,"1355":16,"1356":4,"1357":9,"1358":9,"1359":1013,"1360":1,"1362":7,"1364":5,"1365":2,"1366":10,"1367":2,"1368":5,"1369":93,"1371":3,"1372":5,"1373":5,"1374":5,"1375":5,"1376":3,"1377":3,"1378":5,"1379":11,"1380":184,"1381":1,"1382":3,"1383":6,"1384":4,"1385":4,"1386":2,"1387":2,"1388":4,"1389":14,"1390":5,"1391":9,"1392":4,"1393":5,"1394":2,"1395":601,"1396":706,"1397":2,"1398":2,"1399":11,"1400":4,"1401":7,"1402":235,"1403":1,"1404":1,"1405":7,"1406":14,"1407":3,"1408":6,"1409":10,"1410":9,"1411":7,"1412":5,"1413":7,"1414":5,"1415":369,"1416":87,"1417":340,"1419":4,"1420":3,"1421":5,"1422":7,"1423":11,"1424":6,"1425":7,"1426":3,"1427":73,"1428":3,"1429":16,"1430":10,"1431":25,"1432":11,"1433":10,"1434":6,"1435":13,"1436":1119,"1437":1,"1438":100,"1440":2,"1441":35,"1442":13,"1443":24,"1444":9,"1445":13,"1446":2,"1447":23,"1449":8,"1450":1,"1452":2,"1453":17,"1454":11,"1455":71,"1456":19,"1457":4,"1458":5,"1459":11,"1460":9,"1461":2,"1462":1,"1463":2,"1465":12,"1467":115,"1469":1,"1470":1,"1471":1,"1472":4,"1473":4,"1474":9,"1475":25,"1476":17,"1477":6,"1478":1,"1480":2,"1481":6,"1483":1,"1484":2,"1486":9,"1487":1,"1488":35,"1489":30,"1490":22,"1491":5,"1492":28,"1496":50,"1497":1,"1498":3,"1500":4,"1504":1,"1507":2,"1508":3,"1509":1,"1510":1,"1512":1,"1513":2,"1517":2,"1524":3,"1525":1,"1526":36,"1527":2,"1531":9,"1532":1,"1533":1,"1534":11,"1536":2,"1537":1,"1541":1,"1542":1,"1544":1,"1545":1,"1553":3,"1557":1,"1567":1,"1569":2,"1570":1,"1571":1,"1575":2,"1576":2,"1577":25,"1578":3,"1582":6,"1583":1,"1584":2,"1585":6,"1589":3,"1591":22,"1592":1,"1596":10,"1597":1,"1598":1,"1599":6,"1600":2,"1610":1,"1611":4,"1612":1,"1613":2,"1626":1,"1627":8,"1630":4,"1631":22,"1635":2,"1638":4,"1639":4,"1640":2,"1641":24,"1643":2,"1644":1,"1645":1,"1646":2,"1647":1,"1648":3,"1649":2,"1650":1,"1651":8,"1652":4,"1653":10,"1654":5,"1655":1,"1656":3,"1657":1,"1660":2,"1661":6,"1662":7,"1663":4,"1664":6,"1665":6,"1666":4,"1667":6,"1668":6,"1669":9,"1670":9,"1671":7,"1672":4,"1673":5,"1675":3,"1676":1,"1677":10,"1678":1,"1679":3,"1681":1,"1682":2,"1683":1,"1684":6,"1685":14,"1686":14,"1687":1,"1688":1,"1689":4,"1692":1,"1693":4,"1697":1,"1698":3,"1699":3,"1700":1,"1701":7,"1702":6,"1705":1,"1706":3,"1707":3,"1708":3,"1711":1,"1714":1,"1715":1,"1718":1,"1719":4,"1720":2,"1721":3,"1722":2,"1723":1,"1724":1,"1725":1,"1727":19,"1728":4,"1729":4,"1730":4,"1731":2,"1732":2,"1735":1,"1736":1,"1744":1,"1747":1,"1748":10,"1749":10,"1750":3,"1751":1,"1757":1,"1758":1,"1759":2,"1760":1,"1761":6,"1764":6,"1765":17,"1766":1,"1767":5,"1769":2,"1770":1,"1772":1,"1773":2,"1779":1,"1780":1,"1781":3,"1782":1,"1783":1,"1784":1,"1785":1,"1788":2,"1789":4,"1790":3,"1792":1,"1794":1,"1796":1,"1799":2,"1800":1,"1805":7,"1806":2,"1810":12,"1811":1,"1814":2,"1815":15,"1816":1,"1817":2,"1819":1,"1821":10,"1822":2,"1823":3,"1824":2,"1825":10,"1826":2,"1827":1,"1828":2,"1829":2,"1830":1,"1831":4,"1832":2,"1833":1,"1834":2,"1838":3,"1839":2,"1840":9,"1841":3,"1842":4,"1843":2,"1844":3,"1845":3,"1848":2,"1849":5,"1851":7,"1852":1,"1853":1,"1855":1,"1856":5,"1857":1,"1858":4,"1859":3,"1860":2,"1861":2,"1862":1,"1863":1,"1865":4,"1867":1,"1868":2,"1869":1,"1871":6,"1873":2,"1874":4,"1875":3,"1876":4,"1877":5,"1879":1,"1880":1,"1881":3,"1882":2,"1883":1,"1886":4,"1889":6,"1891":3,"1892":1,"1893":2,"1895":2,"1896":3,"1897":1,"1898":4,"1899":2,"1900":1,"1902":8,"1903":2,"1904":1,"1905":3,"1906":1,"1908":6,"1909":10,"1910":4,"1911":4,"1912":3,"1913":2,"1915":3,"1916":10,"1917":3,"1918":3,"1919":4,"1920":9,"1921":1,"1922":12,"1923":14,"1924":3,"1926":1,"1927":5,"1928":1,"1929":5,"1930":1,"1932":1,"1933":5,"1934":2,"1935":3,"1936":3,"1937":3,"1938":2,"1940":2,"1941":3,"1942":1,"1944":8,"1945":7,"1946":4,"1947":3,"1948":10,"1949":2,"1950":4,"1951":1,"1952":1,"1953":3,"1954":2,"1955":1,"1956":1,"1959":3,"1960":1,"1962":8,"1963":4,"1964":1,"1965":2,"1966":1,"1967":1,"1968":3,"1969":1,"1970":4,"1971":2,"1972":2,"1973":2,"1974":1,"1975":4,"1976":4,"1977":5,"1978":6,"1979":1,"1980":1,"1981":2,"1982":5,"1983":1,"1984":3,"1985":3,"1986":2,"1987":1,"1988":7,"1989":3,"1990":1,"1992":1,"1993":2,"1994":6,"1995":12,"1996":3,"1997":3,"1998":3,"1999":1,"2000":15,"2001":8,"2002":2,"2004":1,"2006":2,"2007":4,"2008":3,"2009":2,"2010":12,"2011":2,"2012":6,"2013":4,"2014":2,"2016":1,"2019":3,"2021":2,"2022":1,"2023":1,"2024":7,"2025":5,"2027":2,"2028":4,"2030":2,"2031":1,"2032":3,"2033":2,"2034":5,"2035":1,"2036":1,"2038":1,"2041":2,"2042":4,"2043":4,"2044":2,"2045":1,"2046":1,"2047":6,"2048":2,"2049":1,"2052":3,"2055":5,"2056":5,"2057":4,"2058":2,"2059":1,"2060":1,"2061":2,"2063":8,"2064":1,"2065":1,"2066":4,"2067":4,"2069":2,"2070":2,"2071":1,"2072":1,"2073":1,"2074":2,"2075":5,"2076":4,"2077":3,"2078":3,"2079":3,"2080":5,"2081":3,"2082":1,"2083":8,"2085":6,"2086":2,"2087":2,"2089":3,"2090":2,"2091":1,"2093":1,"2094":3,"2095":1,"2096":4,"2097":7,"2098":1,"2099":1,"2100":1,"2101":1,"2104":3,"2105":3,"2106":2,"2107":2,"2108":4,"2111":3,"2112":1,"2113":3,"2114":1,"2116":2,"2117":5,"2118":6,"2119":1,"2120":1,"2121":4,"2123":5,"2126":8,"2127":1,"2131":9,"2132":3,"2133":3,"2134":3,"2135":1,"2136":4,"2137":1,"2138":6,"2139":5,"2140":1,"2141":1,"2142":3,"2143":2,"2144":2,"2145":3,"2146":4,"2147":1,"2149":3,"2150":7,"2152":2,"2153":3,"2154":1,"2155":2,"2156":14,"2157":4,"2158":9,"2159":3,"2160":2,"2161":1,"2163":7,"2164":4,"2165":8,"2166":7,"2167":10,"2168":8,"2169":2,"2170":1,"2172":3,"2173":1,"2174":3,"2175":2,"2176":3,"2177":1,"2179":5,"2180":3,"2181":4,"2183":2,"2184":1,"2185":2,"2186":3,"2187":1,"2188":2,"2189":2,"2190":3,"2192":5,"2193":4,"2194":1,"2195":2,"2196":1,"2197":3,"2198":2,"2199":5,"2200":1,"2201":1,"2202":2,"2203":4,"2204":1,"2207":1,"2211":1,"2216":1,"2227":1,"2230":4,"2232":13,"2242":1}}],["stderr",{"3":{"790":1,"792":2,"794":1,"1456":1,"1465":1,"1467":2,"1589":1,"1591":1,"1596":1,"1597":1,"2166":1}}],["stdout",{"3":{"0":1,"401":2,"914":1,"916":1,"1339":1,"1442":1,"1456":1,"1465":1,"1467":3,"1476":1}}],["stylistic",{"3":{"979":1,"1229":1,"1311":1,"1359":1,"1417":1,"1436":1,"1460":1}}],["styling",{"3":{"682":1,"905":1,"1324":1,"1436":1,"1557":1,"1561":1}}],["stylefunc",{"3":{"1436":1}}],["stylefunc=",{"3":{"1436":1,"1526":1}}],["style=",{"3":{"1436":3,"1526":2,"1534":2,"1591":1,"1600":1}}],["styled",{"3":{"1396":1,"1415":1,"1417":1}}],["stylecop",{"3":{"1213":1,"1453":1,"1577":1,"1673":1}}],["stylesheets",{"3":{"1431":1,"1436":3}}],["stylesheet",{"3":{"1324":1,"1395":1,"1417":1,"1436":8,"1489":1,"1496":1,"1526":2,"1534":1,"1591":3,"1596":3,"1597":1,"2075":2,"2150":1}}],["styles",{"0":{"1212":1,"2230":1},"3":{"0":1,"214":1,"216":2,"469":1,"889":1,"981":1,"1052":1,"1229":1,"1286":1,"1324":3,"1339":1,"1351":1,"1359":2,"1396":2,"1436":3,"1439":1,"1443":1,"1526":2,"1534":1,"1535":1,"1557":2,"2149":1,"2152":1,"2230":1}}],["style",{"0":{"1214":1,"1879":1},"2":{"1004":1,"1214":1},"3":{"0":5,"207":1,"212":1,"213":1,"214":3,"215":1,"216":2,"219":1,"546":1,"725":1,"748":1,"855":1,"980":1,"1004":1,"1034":1,"1059":1,"1134":1,"1137":1,"1160":1,"1162":1,"1164":1,"1169":1,"1171":1,"1210":1,"1212":1,"1214":2,"1229":2,"1247":1,"1265":1,"1270":2,"1273":1,"1286":4,"1297":1,"1300":8,"1309":2,"1310":1,"1323":2,"1324":6,"1336":1,"1339":7,"1343":1,"1350":2,"1359":10,"1369":1,"1395":4,"1396":3,"1402":1,"1417":2,"1436":19,"1437":2,"1438":20,"1443":1,"1497":1,"1524":2,"1525":2,"1526":2,"1530":1,"1532":1,"1534":1,"1552":1,"1554":1,"1574":1,"1577":6,"1581":1,"1582":2,"1583":1,"1591":1,"1600":2,"1685":1,"1807":1,"1826":1,"1856":1,"1988":1,"2060":1,"2086":1,"2147":2,"2149":3,"2150":2,"2152":2,"2153":1,"2186":1,"2197":1}}],["stickiness",{"3":{"2138":2}}],["stick",{"3":{"1436":1}}],["sticks",{"3":{"1380":1,"1433":1}}],["stickysidebare2etests",{"3":{"1199":1,"1207":2,"1436":1,"1438":2}}],["sticky",{"3":{"664":1,"666":1,"669":1,"1311":3,"1438":2,"1467":4}}],["stitches",{"3":{"398":1,"400":1,"1309":1,"1380":1,"1396":1,"1653":1}}],["stillcarriestheauthippolicy",{"3":{"2000":1}}],["stillcoversframeworkevents",{"3":{"1436":1}}],["stillfails",{"3":{"1436":1}}],["stillassertsthetwoscanscannotoverlap",{"3":{"1436":1}}],["stillreported",{"3":{"1436":5}}],["stillreportsunhealthyratherthanthrowing",{"3":{"1339":1}}],["still",{"0":{"2053":1},"1":{"1047":1,"1048":1,"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1},"3":{"0":27,"13":1,"22":1,"23":1,"24":5,"26":2,"27":3,"39":1,"41":2,"42":1,"46":1,"47":1,"59":1,"62":1,"70":1,"71":2,"73":2,"80":1,"81":1,"91":4,"92":3,"93":1,"95":1,"96":1,"98":1,"104":1,"109":3,"111":3,"117":1,"120":1,"121":2,"123":1,"125":3,"126":2,"127":1,"130":1,"135":2,"137":1,"139":6,"140":1,"141":1,"142":4,"145":1,"147":2,"152":2,"155":1,"157":1,"159":1,"160":1,"166":1,"170":1,"175":1,"177":1,"178":5,"179":3,"186":1,"188":1,"190":2,"195":2,"198":1,"199":1,"200":4,"201":5,"202":1,"203":1,"207":1,"214":1,"215":2,"216":2,"220":1,"225":2,"231":1,"235":5,"236":1,"237":4,"241":1,"243":2,"245":1,"246":1,"248":2,"250":1,"253":1,"254":2,"255":2,"256":3,"257":4,"258":2,"259":6,"260":1,"261":1,"265":4,"267":1,"273":1,"275":1,"280":1,"283":1,"286":1,"293":1,"294":1,"296":2,"300":1,"302":1,"303":3,"310":1,"318":2,"320":2,"330":1,"335":1,"337":1,"342":1,"343":2,"350":1,"352":2,"358":1,"359":3,"361":5,"364":1,"366":2,"383":1,"384":1,"387":2,"390":3,"392":1,"397":2,"407":4,"408":2,"409":1,"413":2,"422":2,"423":1,"424":2,"425":1,"426":4,"428":2,"429":2,"442":1,"444":1,"447":1,"448":1,"449":1,"451":1,"454":1,"458":1,"459":3,"461":1,"462":1,"464":1,"465":1,"466":3,"478":1,"480":1,"487":2,"488":2,"489":3,"490":3,"491":7,"492":8,"493":6,"494":3,"495":2,"497":2,"499":1,"507":1,"511":1,"513":1,"518":1,"522":2,"523":3,"524":3,"528":1,"529":2,"536":3,"538":3,"539":4,"540":2,"544":1,"545":1,"546":1,"549":7,"550":4,"551":3,"552":2,"558":1,"564":2,"565":1,"572":3,"573":1,"577":2,"578":1,"583":3,"584":1,"585":2,"590":1,"592":1,"593":1,"599":1,"601":3,"602":1,"607":4,"609":2,"610":1,"618":1,"620":1,"624":3,"625":1,"626":3,"627":1,"628":1,"633":1,"634":1,"636":2,"640":1,"642":4,"651":3,"656":1,"657":3,"658":1,"661":1,"667":1,"668":1,"674":1,"681":1,"684":2,"688":2,"691":1,"692":1,"696":1,"698":3,"700":1,"702":2,"703":4,"707":1,"709":2,"715":2,"717":1,"718":1,"724":3,"732":1,"733":2,"735":1,"741":3,"743":2,"749":1,"751":1,"756":1,"757":1,"759":1,"761":1,"766":1,"767":1,"774":1,"776":1,"782":1,"789":1,"790":4,"792":1,"798":1,"800":2,"801":2,"803":3,"804":2,"805":2,"809":3,"812":1,"813":6,"814":1,"819":1,"825":1,"826":1,"827":3,"829":3,"830":1,"831":2,"832":2,"833":1,"837":1,"839":2,"840":3,"848":1,"849":3,"856":1,"861":1,"862":1,"867":1,"872":2,"876":1,"877":2,"881":1,"882":1,"888":2,"890":1,"891":1,"897":2,"898":3,"899":1,"905":2,"906":1,"907":6,"914":1,"920":2,"921":2,"924":1,"926":4,"931":1,"932":1,"939":1,"941":1,"942":1,"945":1,"946":2,"949":1,"956":2,"959":2,"965":1,"967":1,"971":1,"972":1,"974":1,"975":1,"981":1,"983":2,"988":3,"996":2,"998":1,"999":1,"1006":1,"1014":1,"1018":5,"1020":2,"1026":2,"1027":1,"1028":1,"1043":1,"1045":1,"1047":1,"1049":2,"1051":4,"1052":1,"1054":5,"1055":3,"1058":1,"1059":3,"1066":1,"1069":1,"1070":1,"1073":1,"1078":1,"1079":1,"1086":1,"1089":1,"1092":2,"1093":2,"1100":1,"1116":1,"1118":1,"1123":1,"1135":1,"1136":1,"1156":1,"1168":1,"1170":2,"1175":2,"1177":1,"1183":2,"1185":1,"1186":2,"1196":1,"1212":2,"1222":1,"1225":1,"1226":1,"1228":1,"1229":5,"1231":3,"1233":1,"1236":1,"1237":8,"1241":2,"1242":1,"1244":1,"1247":23,"1249":1,"1251":1,"1252":1,"1254":1,"1259":29,"1262":2,"1263":3,"1264":1,"1265":1,"1267":2,"1269":1,"1270":31,"1271":5,"1274":2,"1276":1,"1278":2,"1280":1,"1281":3,"1284":1,"1285":1,"1286":80,"1288":1,"1289":2,"1290":1,"1292":2,"1294":1,"1297":1,"1298":3,"1300":74,"1301":24,"1302":2,"1304":1,"1305":1,"1308":1,"1309":21,"1310":3,"1311":64,"1312":7,"1314":2,"1315":1,"1316":2,"1317":3,"1323":42,"1324":85,"1328":1,"1332":1,"1334":3,"1336":1,"1337":1,"1338":2,"1339":36,"1344":1,"1346":1,"1350":16,"1352":2,"1353":1,"1354":3,"1355":1,"1357":2,"1359":87,"1362":1,"1364":1,"1366":3,"1369":11,"1377":1,"1379":2,"1380":9,"1383":1,"1389":1,"1390":1,"1391":1,"1395":74,"1396":64,"1397":2,"1398":2,"1399":2,"1400":2,"1401":2,"1402":31,"1410":2,"1412":1,"1415":43,"1416":6,"1417":42,"1421":2,"1423":1,"1427":4,"1429":1,"1430":1,"1431":1,"1435":4,"1436":149,"1438":18,"1441":1,"1443":5,"1445":5,"1446":1,"1447":3,"1449":1,"1451":1,"1453":4,"1455":17,"1456":7,"1457":1,"1459":2,"1463":2,"1465":1,"1466":1,"1467":29,"1468":2,"1469":1,"1472":1,"1474":3,"1475":2,"1476":4,"1477":1,"1478":3,"1481":3,"1484":1,"1486":2,"1488":7,"1489":1,"1490":3,"1492":6,"1496":6,"1497":1,"1515":1,"1517":1,"1524":1,"1525":1,"1562":1,"1586":2,"1596":1,"1611":1,"1627":1,"1631":2,"1632":2,"1638":1,"1640":1,"1641":5,"1651":2,"1652":1,"1653":2,"1656":1,"1662":1,"1663":1,"1670":1,"1677":3,"1684":3,"1685":8,"1686":2,"1690":1,"1693":1,"1702":2,"1708":2,"1713":1,"1719":1,"1720":1,"1727":4,"1731":1,"1737":1,"1748":3,"1749":2,"1759":1,"1765":4,"1766":1,"1767":1,"1770":1,"1776":1,"1789":1,"1790":1,"1799":1,"1800":1,"1807":1,"1810":2,"1815":1,"1817":1,"1822":1,"1825":3,"1838":1,"1840":2,"1845":1,"1848":1,"1849":1,"1851":2,"1858":1,"1866":1,"1867":1,"1881":1,"1882":2,"1886":1,"1887":1,"1888":1,"1891":4,"1892":1,"1894":1,"1896":2,"1898":1,"1903":1,"1908":2,"1909":2,"1910":1,"1911":2,"1913":1,"1915":1,"1916":1,"1917":1,"1918":2,"1919":2,"1920":2,"1922":1,"1923":5,"1924":1,"1925":2,"1927":2,"1928":1,"1933":1,"1934":1,"1935":1,"1940":3,"1944":2,"1946":1,"1949":3,"1951":1,"1953":1,"1961":1,"1962":1,"1963":1,"1964":1,"1965":1,"1966":1,"1968":3,"1970":1,"1976":1,"1978":3,"1979":1,"1982":1,"1984":1,"1986":1,"1988":2,"1994":3,"1995":1,"1996":1,"1997":2,"1998":1,"2000":2,"2001":1,"2002":1,"2007":2,"2008":1,"2010":1,"2013":1,"2016":1,"2025":2,"2034":1,"2036":1,"2043":1,"2045":2,"2047":1,"2051":1,"2055":1,"2056":1,"2057":1,"2058":1,"2063":3,"2065":1,"2066":1,"2068":1,"2078":1,"2079":3,"2083":1,"2085":1,"2086":1,"2090":1,"2093":1,"2095":1,"2096":1,"2097":1,"2108":1,"2114":2,"2122":1,"2123":1,"2124":2,"2126":1,"2127":1,"2130":1,"2131":1,"2135":1,"2138":2,"2140":1,"2142":2,"2145":5,"2148":1,"2150":2,"2152":1,"2153":2,"2156":2,"2158":1,"2161":1,"2163":1,"2164":1,"2166":3,"2167":3,"2168":1,"2169":2,"2173":1,"2176":1,"2179":2,"2180":1,"2181":1,"2191":1,"2203":2,"2232":4}}],["steals",{"3":{"2146":1}}],["stealing",{"3":{"2047":1,"2073":1,"2150":1,"2158":1,"2164":1,"2191":1,"2197":1}}],["stealable",{"3":{"1985":1}}],["steal",{"3":{"1300":1,"1324":1,"1454":1,"1981":1}}],["steady",{"3":{"390":1,"397":1,"399":1,"996":1,"1259":2,"1301":3,"1323":3,"1459":1,"1677":2,"2154":1,"2157":1,"2159":1}}],["steers",{"3":{"1323":1,"1417":3,"1436":1}}],["steering",{"3":{"1300":1,"1415":1,"1737":1}}],["steer",{"3":{"1270":1,"1324":2}}],["steered",{"3":{"659":1}}],["stem",{"3":{"741":1,"1286":2,"1393":1,"1395":1}}],["stepnames",{"3":{"1311":2,"1436":2}}],["stepupquery",{"3":{"1199":2,"1207":1}}],["stepupcommand",{"3":{"1199":2,"1207":1}}],["steppable",{"3":{"6":1,"1359":1}}],["stepped",{"3":{"0":1,"859":1,"1270":1}}],["steps",{"3":{"0":5,"110":1,"168":1,"243":1,"256":1,"296":1,"360":1,"368":1,"459":2,"464":1,"528":2,"536":1,"562":1,"566":1,"593":1,"599":1,"602":1,"604":1,"609":2,"611":1,"633":1,"698":1,"747":1,"749":3,"790":1,"809":1,"810":2,"873":1,"881":1,"914":2,"923":1,"927":1,"1018":1,"1094":1,"1212":1,"1229":1,"1247":2,"1248":1,"1254":1,"1259":1,"1265":1,"1270":1,"1286":1,"1289":1,"1300":5,"1301":2,"1310":1,"1311":18,"1312":1,"1315":1,"1316":1,"1323":2,"1324":2,"1336":1,"1359":18,"1369":1,"1380":1,"1395":3,"1396":1,"1402":1,"1415":1,"1417":3,"1436":7,"1453":1,"1454":4,"1455":7,"1456":1,"1461":1,"1463":1,"1467":2,"1478":1,"1481":1,"1488":1,"1490":2,"1526":1,"1577":2,"1585":1,"1602":1,"1641":1,"1653":1,"1659":1,"1666":2,"1674":1,"1675":1,"1692":1,"1717":1,"1719":1,"1725":1,"1727":1,"1729":1,"1747":3,"1749":1,"1765":10,"1808":1,"1815":1,"1825":3,"1835":1,"1846":1,"1898":1,"1923":1,"2026":1,"2035":1,"2046":2,"2087":2,"2100":1,"2101":3,"2105":1,"2163":1,"2168":1,"2169":1,"2193":2,"2202":1,"2226":1}}],["step",{"0":{"1293":1,"2005":1,"2006":1,"2007":1,"2008":1,"2009":1,"2010":1,"2011":1,"2012":1,"2016":1,"2017":1,"2018":1,"2019":1,"2020":1,"2021":1,"2022":1,"2023":1,"2024":1,"2089":1,"2090":1,"2091":1,"2092":1,"2093":1,"2094":1,"2095":1,"2096":1,"2102":1,"2103":1,"2104":1,"2105":1,"2106":1,"2197":1},"2":{"451":1,"483":1},"3":{"0":16,"24":1,"57":1,"59":2,"62":1,"63":1,"109":2,"115":1,"117":1,"126":3,"128":2,"132":1,"135":2,"137":2,"140":3,"142":2,"147":1,"149":1,"217":1,"220":1,"243":1,"256":2,"258":2,"265":1,"291":2,"292":1,"295":1,"343":1,"365":1,"370":4,"371":1,"373":3,"375":1,"435":1,"451":2,"452":1,"453":1,"457":2,"459":4,"462":2,"464":6,"466":1,"483":1,"529":3,"530":1,"534":1,"535":4,"536":5,"563":1,"564":7,"566":4,"567":1,"584":1,"587":1,"600":1,"608":1,"624":1,"626":8,"627":1,"628":1,"629":2,"632":1,"633":1,"635":1,"640":1,"656":1,"659":1,"665":1,"666":1,"674":3,"675":1,"684":1,"696":1,"698":5,"700":1,"703":1,"725":1,"733":1,"735":1,"741":1,"747":1,"749":13,"751":6,"756":2,"757":9,"758":1,"766":5,"767":2,"769":1,"774":5,"776":1,"777":2,"791":1,"809":7,"810":2,"812":2,"813":1,"829":1,"839":1,"847":1,"861":2,"864":1,"867":1,"870":2,"873":1,"876":2,"877":1,"882":1,"907":2,"917":1,"920":1,"927":1,"941":1,"991":1,"1018":3,"1020":2,"1043":1,"1058":1,"1059":2,"1060":2,"1067":1,"1069":2,"1116":1,"1117":1,"1124":1,"1156":1,"1170":1,"1184":1,"1187":1,"1212":2,"1220":1,"1229":9,"1233":2,"1237":1,"1241":1,"1244":1,"1247":3,"1252":2,"1253":1,"1258":1,"1259":6,"1262":1,"1263":2,"1264":1,"1265":3,"1270":15,"1271":1,"1284":1,"1286":28,"1293":2,"1300":28,"1301":6,"1309":3,"1311":46,"1312":1,"1315":2,"1318":1,"1320":1,"1322":2,"1323":15,"1324":10,"1325":1,"1338":1,"1339":2,"1346":1,"1350":3,"1359":66,"1364":1,"1366":1,"1367":1,"1369":1,"1375":1,"1380":2,"1395":15,"1396":12,"1399":1,"1400":1,"1402":1,"1415":18,"1416":1,"1417":6,"1427":1,"1433":1,"1436":25,"1438":2,"1445":6,"1447":1,"1449":1,"1452":2,"1453":21,"1454":15,"1455":43,"1456":15,"1457":2,"1458":6,"1459":6,"1460":3,"1461":2,"1465":7,"1466":2,"1467":8,"1469":2,"1471":2,"1472":1,"1473":2,"1475":13,"1476":2,"1477":1,"1478":1,"1481":1,"1482":1,"1486":4,"1488":5,"1489":1,"1490":6,"1492":13,"1496":1,"1526":2,"1531":1,"1545":1,"1570":1,"1572":1,"1577":8,"1582":1,"1591":5,"1600":1,"1611":1,"1631":1,"1641":3,"1646":1,"1651":1,"1656":1,"1658":1,"1661":3,"1663":2,"1667":1,"1670":1,"1673":2,"1681":1,"1682":2,"1683":1,"1684":3,"1685":2,"1686":3,"1696":1,"1697":1,"1705":1,"1719":1,"1722":2,"1726":1,"1728":1,"1729":3,"1774":2,"1784":1,"1805":2,"1815":1,"1821":2,"1824":1,"1825":6,"1826":1,"1835":2,"1839":1,"1845":1,"1846":1,"1881":2,"1898":2,"1909":1,"1910":1,"1923":1,"1940":1,"1982":1,"2003":2,"2006":4,"2013":1,"2015":5,"2022":1,"2023":1,"2024":3,"2034":1,"2041":1,"2045":1,"2046":1,"2066":2,"2075":1,"2087":2,"2093":1,"2095":3,"2096":1,"2097":2,"2099":1,"2100":1,"2101":1,"2105":2,"2106":1,"2136":1,"2142":1,"2147":1,"2161":1,"2162":3,"2163":3,"2164":2,"2165":1,"2167":2,"2168":1,"2169":5,"2187":1,"2189":1,"2194":2,"2195":1,"2196":1,"2197":2,"2198":2,"2203":4,"2206":1,"2232":3,"2234":1}}],["stumbles",{"3":{"1912":1}}],["student",{"3":{"1395":1}}],["study",{"3":{"1324":2,"1332":1,"1395":1,"1396":1,"1416":1,"1779":1,"2109":1,"2234":1}}],["studying",{"3":{"1286":1,"1300":1,"1309":1,"1324":2,"1350":1,"1359":2,"1380":1,"1390":1,"1396":1,"1399":1}}],["studio",{"3":{"214":1,"219":1,"665":1,"1074":1,"1324":1,"1335":1,"1339":2,"2150":1}}],["studied",{"3":{"0":1,"1033":1,"1324":1}}],["stuffing",{"3":{"174":1,"280":1,"286":1,"403":1,"612":1,"1292":1,"1300":1,"1326":1,"1339":1,"1444":1,"1998":2,"1999":1,"2001":1}}],["stubviewport",{"3":{"1436":4}}],["stubregistrations",{"3":{"1323":1}}],["stubrefresher",{"3":{"1199":3,"1207":2}}],["stubpushnotificationuiservice",{"2":{"1434":1},"3":{"1199":2,"1207":2,"1434":2,"1436":4}}],["stubparent",{"3":{"1199":2,"1207":1}}],["stubnotificationinboxuiservice",{"2":{"1434":1},"3":{"1199":2,"1207":2,"1434":2,"1436":5}}],["stubentity",{"3":{"1199":2,"1207":1}}],["stuberrorlocalizer",{"3":{"1199":2,"1207":1}}],["stubuimodule",{"3":{"1199":2,"1207":1,"1324":1}}],["stubmap",{"3":{"1199":2,"1207":1,"1436":3}}],["stubmetricsbuilder",{"3":{"1199":2,"1207":1}}],["stubfeaturemanager",{"3":{"1199":2,"1207":1}}],["stubguardrail",{"3":{"1199":4,"1207":2,"1427":1,"1435":2,"1487":1}}],["stubtwofactorservice",{"3":{"1199":2,"1207":1}}],["stubtenantcontext",{"3":{"1199":2,"1207":1}}],["stubtoolpolicy",{"3":{"1199":3,"1207":3,"1427":1}}],["stubtool",{"3":{"1199":2,"1207":1}}],["stubtokenstorageservicetests",{"3":{"1199":1,"1207":2,"1436":1}}],["stubtokenstorageservice",{"2":{"1432":1},"3":{"954":1,"1199":9,"1207":2,"1324":1,"1428":1,"1432":2,"1436":7,"1488":1}}],["stublocalizer",{"3":{"1199":8,"1207":5}}],["stubloggingbuilder",{"3":{"1199":2,"1207":1}}],["stubwebhostenvironment",{"3":{"1199":2,"1207":1}}],["stubcurrentuserservice",{"3":{"1199":3,"1207":1}}],["stubchild",{"3":{"1199":2,"1207":1}}],["stubchanneljoinauthorizer",{"3":{"1199":2,"1207":1}}],["stubchatclient",{"3":{"1199":17,"1207":6,"1427":1,"1487":1}}],["stubcspprovider",{"3":{"1199":2,"1207":1}}],["stubclock",{"3":{"1199":2,"1207":1}}],["stubhandler",{"3":{"1199":6,"1207":3}}],["stubhttpmessagehandler",{"3":{"1199":17,"1207":2}}],["stubhttpclientfactory",{"3":{"1199":16,"1207":3}}],["stubhostenvironment",{"3":{"1199":10,"1207":5}}],["stubbed",{"3":{"583":1,"957":1,"1396":1,"1415":1,"1432":1,"1436":2,"1438":2}}],["stubsharedlocalizer",{"3":{"1199":2,"1207":1}}],["stubscopeprovider",{"3":{"1199":3,"1207":2}}],["stubs",{"0":{"1349":1},"3":{"53":3,"59":1,"63":1,"135":1,"413":1,"582":1,"583":9,"584":1,"585":2,"586":2,"587":2,"649":1,"1192":1,"1207":16,"1212":1,"1286":1,"1300":1,"1309":1,"1323":3,"1324":3,"1341":2,"1380":4,"1396":3,"1434":9,"1436":38,"1496":2,"1656":1,"1671":1,"1790":2,"1884":1,"1885":1,"1886":1,"2028":1}}],["stub",{"0":{"1412":1},"3":{"0":1,"53":1,"63":1,"583":9,"585":4,"649":1,"650":1,"954":1,"1212":1,"1300":3,"1309":6,"1311":2,"1312":2,"1314":3,"1315":2,"1323":9,"1324":11,"1349":2,"1350":23,"1359":1,"1378":2,"1380":3,"1396":13,"1398":1,"1402":4,"1412":1,"1413":1,"1415":13,"1417":1,"1427":1,"1431":1,"1432":1,"1435":2,"1436":52,"1437":2,"1489":1,"1490":1,"1496":1,"1577":1,"1651":1,"1653":1,"1662":1,"1751":1,"1772":1,"1882":3,"1884":1,"1885":1,"1886":1,"2017":1,"2019":1,"2053":1,"2094":2,"2113":1,"2118":1,"2123":1,"2232":1}}],["stuckageminutes",{"2":{"793":1},"3":{"0":1,"539":1,"540":1,"793":1}}],["stuck",{"3":{"0":2,"24":2,"174":1,"234":1,"235":1,"237":1,"536":1,"537":1,"538":2,"540":1,"792":1,"809":1,"811":1,"997":1,"1259":2,"1324":4,"1339":1,"1395":2,"1396":1,"1436":1,"1476":1,"1854":1,"1998":1,"2072":1,"2156":1,"2167":3,"2168":2}}],["stryker",{"3":{"1577":1}}],["stroke",{"3":{"1359":2}}],["strongpasswordrules",{"2":{"1829":1,"1834":1},"3":{"1199":6,"1203":1,"1207":1,"1271":10,"1289":1,"1294":1,"1300":9,"1323":1,"1324":1,"1415":5,"1436":6,"1496":1,"1829":2,"1834":1}}],["strongest",{"3":{"947":1,"1229":1,"1286":1,"1323":1,"1324":1,"1359":2,"1380":2,"1454":1,"1523":1,"1574":1,"1588":1}}],["stronger",{"3":{"0":2,"48":1,"537":1,"694":1,"785":1,"801":1,"804":1,"819":1,"840":1,"995":1,"1043":1,"1237":1,"1286":1,"1300":1,"1323":1,"1359":2,"1396":1,"1417":1,"1436":2,"1474":1,"1848":1,"2164":1,"2169":1}}],["strong",{"3":{"54":1,"727":1,"1019":1,"1237":1,"1271":2,"1300":6,"1311":1,"1324":1,"1350":1,"1395":1,"1415":3,"1438":1,"1526":5,"1527":1,"1537":2,"1541":2,"1544":1,"1575":1,"1577":9,"1582":1,"1591":3,"1592":1,"1638":1,"1666":1,"1797":1,"1812":1,"1828":1,"1834":2,"1902":1,"2107":1,"2144":1}}],["stronglytypedidapitests",{"3":{"1199":1,"1207":7,"1300":2}}],["stronglytypedidconverter",{"3":{"1199":2,"1203":1,"1207":1,"1300":6}}],["stronglytypedidconventiontests",{"3":{"1050":1,"1199":1,"1207":2,"1436":10}}],["stronglytypedidregistry",{"2":{"1281":1},"3":{"1050":2,"1199":4,"1203":1,"1207":2,"1281":1,"1286":3,"1300":11,"1810":1}}],["stronglytypedidvaluecomparer<",{"3":{"1286":2}}],["stronglytypedidvaluecomparer",{"2":{"1281":1,"1810":1},"3":{"1050":1,"1199":3,"1203":1,"1207":1,"1281":1,"1286":1,"1810":1}}],["stronglytypedidvalueconverter",{"2":{"1281":1,"1810":1},"3":{"798":1,"806":1,"1050":2,"1199":3,"1203":1,"1207":4,"1281":3,"1286":14,"1496":1,"1810":1}}],["stronglytypedidvalueparserdelegate",{"3":{"1199":2,"1203":1,"1207":1,"1300":3}}],["stronglytypedidvalueparser",{"3":{"1050":1,"1199":2,"1203":1,"1207":1,"1300":7}}],["stronglytypedidpersistencetests",{"2":{"1050":1},"3":{"1050":1,"1199":1,"1207":2,"1286":3}}],["stronglytypedidparametertransformer",{"2":{"1050":1},"3":{"1050":1,"1199":2,"1203":1,"1207":1,"1311":6,"1496":1}}],["stronglytypedidtests",{"3":{"1050":3,"1199":2,"1207":4,"1300":4,"1436":3}}],["stronglytypedidtestsbase",{"2":{"130":1,"1054":1},"3":{"0":1,"130":1,"1050":2,"1054":1,"1199":3,"1207":2,"1431":3,"1436":11}}],["stronglytypedidtypeconverter",{"2":{"1810":1},"3":{"1050":2,"1052":1,"1055":2,"1199":3,"1203":1,"1207":3,"1300":15,"1810":1}}],["stronglytypedidtypeconvertertests",{"2":{"1050":1},"3":{"1050":1,"1052":1,"1055":1,"1199":1,"1207":3,"1300":4}}],["stronglytypedidtypeconverters",{"2":{"1050":1,"1052":1,"1810":1},"3":{"1050":1,"1052":1,"1199":4,"1203":1,"1207":1,"1300":4,"1810":1}}],["stronglytypedidmappertests",{"3":{"1199":1,"1207":8,"1300":2}}],["stronglytypedidmappings",{"3":{"798":1,"806":1,"1050":3,"1199":2,"1203":1,"1207":2,"1300":8,"1810":1}}],["stronglytypedidmodelconfiguration",{"2":{"1050":1,"1281":1,"1810":1},"3":{"1050":3,"1199":1,"1203":1,"1207":2,"1281":2,"1286":19,"1300":2,"1496":1,"1810":1}}],["stronglytypedidjsonconverterfactory",{"2":{"967":1,"1050":1,"1810":1},"3":{"967":1,"1050":2,"1199":2,"1203":1,"1207":3,"1300":12,"1311":3,"1810":1}}],["stronglytypedid",{"2":{"798":1,"1241":1},"3":{"798":2,"799":1,"1050":3,"1198":1,"1199":11,"1203":1,"1207":4,"1241":1,"1247":3,"1300":32,"1311":4,"1496":2}}],["stronglytypedidfitnesstests",{"3":{"1198":1,"1199":2,"1207":3,"1436":13}}],["stronglytypedidfixtures",{"3":{"477":2,"1207":15,"1300":3,"1436":22}}],["stronglytypedidfilterstrategytests",{"3":{"1199":1,"1207":5,"1247":2}}],["stronglytypedidfilterstrategy",{"2":{"330":1,"337":1,"1241":2,"1247":1},"3":{"330":1,"337":1,"1050":2,"1199":2,"1203":1,"1207":4,"1241":11,"1247":8,"1300":1,"1496":3}}],["stronglytypedidschematransformer",{"3":{"1050":2,"1199":3,"1203":1,"1207":3,"1300":2,"1311":14,"1496":2}}],["stronglytypedidserializationtests",{"2":{"1050":1},"3":{"1050":1,"1199":1,"1207":3,"1300":4}}],["stronglytypedids",{"2":{"468":1},"3":{"468":1,"798":1,"801":1,"804":1,"1050":1,"1207":1,"1436":9,"1489":1}}],["stronglytypedidsarereadonlyrecordstructs",{"2":{"0":1,"130":1,"798":1,"801":1,"804":1,"1050":1},"3":{"0":1,"130":1,"798":1,"801":1,"804":1,"1050":1,"1436":6}}],["strongly",{"1":{"467":1,"468":1,"469":1,"470":1,"471":1,"472":1,"473":1,"474":1,"475":1,"476":1,"477":1,"478":1,"1047":1,"1048":1,"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1},"3":{"0":4,"135":2,"139":1,"330":1,"337":1,"454":1,"467":1,"469":1,"656":1,"962":1,"1047":1,"1049":1,"1050":1,"1212":2,"1231":1,"1236":1,"1237":6,"1241":1,"1247":4,"1270":1,"1286":13,"1300":14,"1309":1,"1311":10,"1320":1,"1323":1,"1336":1,"1339":2,"1340":1,"1350":2,"1359":3,"1362":1,"1395":4,"1396":1,"1403":1,"1417":1,"1431":1,"1436":23,"1441":1,"1496":2,"1554":1,"1577":1,"1581":1,"1641":1,"1810":1,"1812":1,"2153":1,"2232":3}}],["struggling",{"3":{"1339":2,"2037":1}}],["strung",{"3":{"1300":1}}],["struck",{"3":{"1026":2,"1027":1,"1028":1,"1030":1,"1496":4,"1515":1,"1749":2,"1750":3,"1764":1,"1765":1,"1767":1,"1768":1,"1769":1,"1773":2,"1776":1}}],["structurally",{"0":{"2199":1},"3":{"0":1,"827":1,"946":1,"973":1,"1100":1,"1217":1,"1226":1,"1229":3,"1231":1,"1236":1,"1237":4,"1247":1,"1270":2,"1271":1,"1286":5,"1300":3,"1310":3,"1311":2,"1324":2,"1339":3,"1350":7,"1359":18,"1369":2,"1380":4,"1395":13,"1396":4,"1402":3,"1415":4,"1417":2,"1436":10,"1453":1,"1459":1,"1465":1,"1489":2,"1576":1,"1577":1,"1707":1,"1927":1,"1992":1,"1997":1,"2002":2,"2038":1,"2116":1,"2123":1,"2154":1,"2160":1}}],["structural",{"0":{"1638":1},"3":{"0":3,"135":4,"140":1,"518":1,"523":1,"572":2,"573":1,"579":1,"595":1,"622":1,"657":1,"733":1,"831":1,"865":1,"882":1,"926":1,"957":1,"997":1,"1022":1,"1050":1,"1212":1,"1234":1,"1237":3,"1247":2,"1249":1,"1259":2,"1265":1,"1270":1,"1286":9,"1300":3,"1301":2,"1310":1,"1311":1,"1323":4,"1324":2,"1332":1,"1339":5,"1350":8,"1359":9,"1371":2,"1380":4,"1395":5,"1396":12,"1401":1,"1402":10,"1415":5,"1428":1,"1431":1,"1436":22,"1455":1,"1467":2,"1488":1,"1489":1,"1494":1,"1496":1,"1526":1,"1539":1,"1574":1,"1575":1,"1577":2,"1578":1,"1638":3,"1639":2,"1654":2,"1799":1,"1806":1,"1810":2,"1832":3,"1833":3,"1834":1,"1849":1,"1858":1,"1922":1,"1950":1,"2031":2,"2035":1,"2047":1,"2054":1,"2071":1,"2079":1,"2093":1,"2098":1,"2232":1}}],["structures",{"3":{"1301":2,"1764":1}}],["structure",{"0":{"1615":1,"1796":1},"3":{"0":1,"1":1,"58":1,"130":1,"134":1,"264":1,"571":1,"590":1,"595":1,"617":2,"865":1,"953":1,"955":1,"1004":1,"1237":1,"1270":1,"1271":1,"1300":6,"1301":1,"1311":1,"1324":5,"1339":1,"1350":1,"1359":3,"1377":1,"1380":1,"1395":2,"1396":2,"1402":1,"1405":1,"1415":4,"1428":1,"1436":4,"1445":3,"1455":1,"1486":1,"1494":1,"1555":1,"1558":1,"1607":1,"1621":1,"1640":1,"1654":1,"1741":1,"1750":1,"1773":1,"1775":1,"1794":1,"1802":1,"1932":1,"1991":1,"2041":1,"2042":1,"2055":2,"2059":1,"2107":1}}],["structured",{"3":{"0":1,"53":1,"265":1,"372":1,"1018":1,"1116":1,"1213":1,"1217":1,"1229":1,"1237":3,"1247":2,"1263":1,"1270":5,"1311":3,"1312":7,"1315":1,"1317":1,"1323":2,"1324":1,"1339":1,"1340":1,"1342":1,"1350":1,"1359":11,"1369":2,"1376":1,"1378":1,"1380":4,"1396":1,"1402":1,"1417":1,"1435":2,"1436":2,"1438":1,"1465":2,"1467":1,"1473":1,"1526":1,"1536":1,"1550":1,"1577":3,"1630":1,"1665":1,"1821":1,"1881":1,"1885":1,"1927":1,"1928":1,"1991":1,"2018":1,"2063":1,"2074":1,"2155":1,"2156":1}}],["struct",{"2":{"469":1,"1051":1},"3":{"0":6,"468":4,"469":4,"470":4,"471":1,"472":1,"473":1,"474":1,"477":3,"656":2,"661":1,"797":3,"798":2,"799":3,"802":1,"803":1,"804":1,"1048":1,"1049":1,"1050":6,"1051":2,"1052":1,"1203":27,"1205":2,"1207":48,"1212":1,"1229":2,"1237":1,"1241":1,"1242":1,"1247":8,"1253":1,"1259":12,"1271":1,"1275":1,"1277":1,"1279":1,"1286":20,"1287":1,"1289":1,"1300":48,"1310":4,"1311":2,"1312":1,"1359":1,"1396":6,"1415":1,"1423":2,"1427":3,"1436":32,"1810":5,"1812":1,"2175":1}}],["structs",{"1":{"467":1,"468":1,"469":1,"470":1,"471":1,"472":1,"473":1,"474":1,"475":1,"476":1,"477":1,"478":1,"796":1,"797":1,"798":1,"799":1,"800":1,"801":1,"802":1,"803":1,"804":1,"805":1,"806":1},"3":{"0":2,"467":1,"469":1,"470":1,"472":1,"477":1,"660":1,"661":1,"796":1,"799":2,"1049":1,"1212":2,"1231":1,"1286":1,"1300":2,"2232":2}}],["street",{"3":{"1359":1,"1391":1,"1417":5}}],["streak",{"3":{"1069":1,"1453":1,"1490":1}}],["streamboundedasync",{"3":{"1427":2}}],["streamrendering",{"3":{"1324":1}}],["streamwriter",{"2":{"744":1},"3":{"741":1,"744":1,"1311":3}}],["streamedguardrailtests",{"3":{"1199":1,"1207":3,"1435":2}}],["streamed",{"3":{"741":2,"745":1,"1089":2,"1091":3,"1093":2,"1324":1,"1366":1,"1371":1,"1415":1,"1418":1,"1423":3,"1424":1,"1427":9,"1435":2,"1437":1,"1487":1,"1577":2,"1585":1,"1599":1,"1988":2,"1991":1,"2175":2,"2179":1}}],["stream",{"3":{"443":1,"741":1,"742":1,"743":2,"914":2,"1091":1,"1093":1,"1116":1,"1139":1,"1141":1,"1142":1,"1143":1,"1144":1,"1147":1,"1212":1,"1229":1,"1233":1,"1237":1,"1286":8,"1301":2,"1311":7,"1323":2,"1324":6,"1339":1,"1350":3,"1357":2,"1359":2,"1369":1,"1373":1,"1380":1,"1389":1,"1395":4,"1396":1,"1414":1,"1415":10,"1416":2,"1417":16,"1422":2,"1423":1,"1425":2,"1427":5,"1436":6,"1443":2,"1446":1,"1447":1,"1451":1,"1456":1,"1467":5,"1476":1,"1479":2,"1483":1,"1629":1,"1630":1,"1677":1,"1908":1,"2010":1,"2126":1,"2157":1,"2174":2,"2177":1,"2232":1}}],["streamprefix",{"3":{"142":1}}],["streams",{"3":{"0":1,"318":1,"407":1,"743":1,"1117":1,"1212":1,"1286":2,"1311":3,"1324":1,"1339":1,"1359":2,"1375":1,"1380":1,"1389":1,"1415":1,"1417":1,"1465":1,"1467":1,"1986":1,"1995":1,"2160":1,"2192":1,"2232":1}}],["streaming",{"3":{"0":1,"136":2,"142":1,"741":5,"1091":2,"1093":1,"1311":1,"1312":8,"1372":1,"1380":1,"1395":1,"1417":2,"1422":3,"1423":2,"1424":1,"1425":3,"1427":8,"1436":4,"1526":1,"1549":1,"1927":1,"2044":3,"2174":3,"2175":2,"2177":1,"2179":2}}],["stresses",{"3":{"1286":1,"1324":1,"1436":1}}],["stress",{"3":{"966":1,"1052":1,"1549":1,"1577":1,"1582":1,"2114":1}}],["stretching",{"3":{"1467":1,"2002":1}}],["stretches",{"3":{"1334":1,"1443":1}}],["stretched",{"3":{"309":1,"914":1}}],["stretch",{"3":{"234":1,"491":1,"1339":1,"1443":1,"1654":1}}],["strengths",{"0":{"1525":1,"1576":1,"1590":1},"3":{"1324":1,"1527":1}}],["strengthen",{"3":{"1577":1}}],["strengthened",{"3":{"1292":1,"1300":1}}],["strengthens",{"3":{"831":1,"917":1}}],["strength",{"3":{"103":1,"214":1,"215":1,"341":1,"472":1,"801":1,"1300":3,"1311":1,"1323":2,"1324":1,"1336":1,"1339":3,"1348":1,"1350":2,"1415":5,"1443":1,"1474":1,"1577":1,"1641":7,"1765":1,"1767":3,"1825":1,"1874":1,"1903":1,"1910":1,"2148":1,"2149":1,"2171":1}}],["strike",{"3":{"1773":1}}],["strikethrough",{"3":{"1639":1,"1742":1}}],["striking",{"3":{"1371":1}}],["stride",{"3":{"1534":1,"1577":1}}],["stripper",{"3":{"1436":1}}],["stripped",{"3":{"166":1,"889":1,"1091":2,"1184":1,"1323":1,"1324":2,"1359":2,"1366":1,"1369":1,"1395":1,"1421":1,"1422":1,"1427":2,"1435":1,"1436":5,"1447":1,"1577":1,"1631":1,"2174":1}}],["stripping",{"3":{"1286":1,"1300":1,"1323":1,"1324":2,"1339":2,"1436":1,"2179":1}}],["striping",{"3":{"157":1,"1267":1,"1270":2,"1298":1,"1300":2,"1301":2,"1311":1,"1909":1}}],["strips",{"3":{"0":1,"169":1,"972":1,"1184":1,"1286":5,"1300":4,"1309":1,"1323":2,"1324":3,"1359":1,"1395":2,"1396":2,"1401":1,"1402":1,"1415":1,"1417":1,"1427":1,"1436":7,"1445":1,"1454":1,"1526":1,"2126":1,"2128":1,"2232":1}}],["strip",{"3":{"0":1,"440":1,"1115":1,"1212":1,"1286":2,"1300":1,"1322":1,"1323":1,"1324":2,"1350":1,"1352":1,"1353":1,"1359":2,"1369":2,"1380":1,"1392":2,"1395":3,"1396":1,"1402":1,"1436":1,"1438":1,"1496":1,"1534":1,"1663":1,"2126":1,"2128":2}}],["stripepaymentintentid",{"2":{"1749":1,"1754":1},"3":{"1749":1,"1754":1,"1764":1}}],["stripepaymentservice",{"2":{"72":1,"73":1,"794":1,"1752":1},"3":{"72":2,"73":2,"674":1,"790":3,"791":2,"792":1,"794":1,"1591":1,"1600":1,"1752":1,"1765":1,"1770":1,"2166":1}}],["stripeuri",{"3":{"1600":1}}],["stripesessionid",{"3":{"1286":1,"1764":1}}],["stripesettings",{"2":{"73":1,"1770":1},"3":{"72":1,"73":2,"1770":1}}],["stripes",{"3":{"996":1,"1267":1,"1270":3,"1300":2,"1301":3,"1323":2,"1438":1,"1909":1}}],["stripewebhooksecretprovider",{"2":{"790":1},"3":{"790":2,"2166":1}}],["stripewebhookregistrationservicetests",{"3":{"790":1,"2167":1}}],["stripewebhookregistrationservice",{"2":{"790":1,"792":1,"794":1,"2166":1},"3":{"674":1,"789":1,"790":3,"791":1,"792":4,"794":2,"1589":1,"1591":2,"1596":1,"1597":1,"2166":2}}],["striped",{"3":{"155":1,"157":2,"159":1,"995":1,"996":1,"1183":1,"1267":1,"1270":4,"1300":2,"1311":1,"1323":1,"1438":1,"1496":1,"1577":2,"1585":1,"1668":1,"1909":1,"1912":1}}],["stripeclient",{"2":{"73":1},"3":{"73":1}}],["stripeclientfactorytests",{"3":{"73":2}}],["stripeclientfactory",{"2":{"73":1},"3":{"72":1,"73":2,"674":1}}],["stripeexception",{"2":{"72":1},"3":{"72":1}}],["stripe",{"1":{"787":1,"788":1,"789":1,"790":1,"791":1,"792":1,"793":1,"794":1,"795":1},"2":{"1600":3},"3":{"0":13,"66":1,"72":5,"73":4,"96":2,"101":1,"157":1,"534":1,"535":2,"536":3,"537":1,"538":2,"539":1,"598":1,"599":1,"674":1,"698":1,"701":1,"733":2,"757":1,"759":1,"761":1,"787":1,"789":3,"790":8,"791":1,"792":5,"793":2,"794":2,"795":1,"827":2,"837":1,"838":1,"840":1,"841":1,"996":2,"1026":1,"1212":3,"1270":13,"1298":1,"1300":13,"1301":18,"1311":4,"1339":1,"1436":4,"1489":1,"1588":1,"1589":1,"1591":6,"1595":1,"1596":1,"1597":1,"1600":7,"1673":1,"1746":2,"1749":7,"1750":2,"1752":1,"1754":2,"1755":2,"1759":1,"1763":2,"1765":10,"1768":1,"1769":1,"1770":7,"1773":2,"1776":1,"1785":1,"1909":2,"1910":1,"1911":1,"1915":1,"1917":1,"1920":1,"2012":1,"2024":1,"2104":1,"2110":1,"2113":1,"2136":1,"2161":2,"2166":8,"2167":6,"2168":2,"2169":2,"2221":1,"2228":1,"2232":1}}],["strict=true",{"3":{"1591":1}}],["stricttransportsecurity",{"3":{"1339":1}}],["strictconformanttests",{"3":{"1198":1,"1199":2,"1207":1,"1436":6}}],["strictdriftedtests",{"3":{"1198":1,"1199":2,"1207":1,"1436":8}}],["strictness",{"3":{"461":1,"583":1,"584":2,"618":1,"1323":1}}],["strictest",{"3":{"215":1,"2150":1,"2153":1}}],["stricter",{"3":{"135":1,"177":1,"214":1,"217":1,"243":1,"1093":1,"1223":1,"1241":1,"1247":1,"1300":1,"1309":1,"1320":1,"1323":1,"1324":3,"1359":2,"1395":1,"1436":6,"1443":1,"1496":1,"1500":1,"1898":1,"2001":1,"2153":1}}],["strictly",{"3":{"0":3,"93":1,"95":1,"537":1,"591":1,"666":1,"846":1,"890":1,"914":1,"916":1,"1229":1,"1259":1,"1270":1,"1286":3,"1300":2,"1308":1,"1311":1,"1324":3,"1339":2,"1359":4,"1395":1,"1396":1,"1402":3,"1417":1,"1429":1,"1436":6,"1438":1,"1442":1,"1488":1,"1526":1,"1577":1,"1631":1,"1635":1,"1767":1,"1781":1,"1867":1,"2047":1,"2164":1}}],["strict",{"3":{"0":1,"58":1,"109":1,"187":1,"213":1,"214":1,"215":1,"217":2,"218":1,"219":1,"236":1,"388":1,"583":3,"584":1,"616":1,"618":7,"621":2,"953":1,"1184":4,"1191":1,"1200":1,"1229":2,"1237":1,"1247":3,"1262":1,"1270":2,"1286":2,"1300":5,"1309":2,"1311":1,"1324":4,"1334":1,"1339":3,"1359":3,"1395":1,"1396":1,"1398":1,"1402":1,"1405":1,"1409":1,"1415":4,"1433":1,"1436":25,"1443":1,"1455":3,"1480":1,"1486":1,"1488":1,"1496":1,"1498":1,"1517":1,"1534":2,"1585":1,"1621":1,"1638":1,"1640":1,"1671":1,"1772":1,"1922":1,"1961":1,"1967":1,"1977":1,"2057":1,"2147":2,"2148":1,"2149":1,"2151":1,"2153":1,"2227":1}}],["stringcontent",{"3":{"1436":2}}],["stringcomparemethod",{"3":{"1286":1}}],["stringcomparer",{"3":{"1237":1,"1247":4,"1286":4,"1300":4,"1301":1,"1309":2,"1310":1,"1311":1,"1323":3,"1324":8,"1339":5,"1350":2,"1359":2,"1395":3,"1396":4,"1402":1,"1417":2}}],["stringcomparison",{"3":{"1271":1,"1286":1,"1300":5,"1301":1,"1311":2,"1323":1,"1324":5,"1339":2,"1350":3,"1359":3,"1395":3,"1396":4,"1415":1,"1436":1}}],["stringwithqualityheadervalue",{"3":{"1324":1}}],["stringvalues",{"3":{"1311":1,"1324":1}}],["stringbuilder",{"3":{"1286":2,"1300":1,"1312":1,"1324":2,"1427":1}}],["stringfilterstrategytests",{"3":{"1199":1,"1207":3,"1247":1}}],["stringfilterstrategy",{"3":{"1199":3,"1203":1,"1207":2,"1241":4,"1247":10,"1359":1,"1496":3,"1815":1}}],["stringlocalizer",{"3":{"1324":1}}],["stringlocalizerpluralextensionstests",{"3":{"1199":1,"1207":3,"1586":1}}],["stringlocalizerpluralextensions",{"3":{"1199":1,"1203":1,"1207":2,"1208":1,"1324":5,"1436":2,"1496":1,"1577":2,"1586":1,"1591":1,"1596":1}}],["stringlength",{"3":{"827":1,"1300":4,"1309":4,"1323":3,"1324":5,"1339":4}}],["stringly",{"3":{"188":1,"1324":1,"1359":1,"1964":1}}],["stringifies",{"3":{"1247":1}}],["stringified",{"3":{"741":1,"1309":1,"1311":1,"1378":1}}],["stringidentity",{"3":{"1199":2,"1207":1}}],["stringincrementasync",{"2":{"178":1},"3":{"178":1,"1311":1}}],["stringstrategy",{"3":{"1247":3}}],["stringsplitoptions",{"3":{"1247":3}}],["stringsetasync",{"3":{"996":1,"1323":1}}],["strings",{"0":{"1394":1},"3":{"0":2,"86":2,"88":2,"164":1,"166":1,"188":1,"202":1,"230":1,"265":6,"286":1,"380":1,"448":1,"585":1,"599":2,"643":1,"673":1,"698":1,"700":1,"717":1,"974":1,"980":1,"983":1,"1059":1,"1175":1,"1229":1,"1232":1,"1237":3,"1247":2,"1259":1,"1270":5,"1271":3,"1278":1,"1282":1,"1286":16,"1300":18,"1309":10,"1311":9,"1312":3,"1323":3,"1324":28,"1335":1,"1339":7,"1350":14,"1359":19,"1369":1,"1380":1,"1394":2,"1395":11,"1396":14,"1402":4,"1409":2,"1415":14,"1416":4,"1417":12,"1427":2,"1436":24,"1442":1,"1455":1,"1467":10,"1470":1,"1473":2,"1474":1,"1477":2,"1489":1,"1490":1,"1496":1,"1524":1,"1526":1,"1531":1,"1548":1,"1553":1,"1554":1,"1564":2,"1591":2,"1596":1,"1653":1,"1666":1,"1669":2,"1670":1,"1671":1,"1684":1,"1686":1,"1695":1,"1727":2,"1790":1,"1920":1,"1928":1,"1964":1,"2012":1,"2081":1,"2083":2,"2131":1,"2136":1,"2139":1,"2171":1}}],["string",{"0":{"1305":1},"2":{"360":1,"397":1,"656":1,"658":1,"680":1,"684":1,"701":1,"776":1,"914":1,"959":1,"1132":1,"1333":1,"1356":1,"1443":1,"1446":1,"1467":1,"1541":1,"1687":1,"2010":1,"2078":1,"2146":1,"2156":1,"2159":1},"3":{"0":14,"24":1,"31":1,"33":1,"77":1,"86":1,"109":1,"111":1,"121":2,"159":1,"160":1,"164":1,"166":1,"168":1,"170":4,"171":1,"186":1,"190":1,"214":1,"216":1,"219":1,"225":1,"227":1,"229":1,"241":1,"243":2,"245":2,"249":1,"253":1,"255":1,"259":2,"260":2,"261":1,"265":11,"266":1,"286":1,"305":1,"314":1,"330":1,"335":1,"350":1,"358":1,"359":5,"360":1,"381":1,"386":1,"388":1,"390":1,"392":1,"397":1,"422":1,"425":1,"426":1,"429":1,"499":1,"547":1,"558":1,"572":1,"598":3,"599":8,"600":1,"601":2,"602":1,"604":1,"605":2,"609":1,"626":1,"633":1,"640":5,"643":1,"644":1,"656":1,"657":7,"658":1,"659":2,"680":5,"684":1,"691":1,"692":1,"698":7,"699":2,"701":1,"707":1,"708":2,"710":1,"715":1,"732":1,"733":1,"736":1,"740":1,"759":2,"827":1,"839":3,"881":3,"883":1,"888":1,"899":1,"904":1,"905":1,"906":1,"914":5,"922":1,"926":2,"959":2,"963":2,"964":2,"972":2,"974":1,"996":2,"1005":1,"1017":1,"1018":4,"1027":1,"1028":1,"1034":2,"1036":1,"1050":3,"1067":1,"1089":1,"1091":3,"1094":1,"1095":2,"1132":1,"1133":1,"1135":2,"1150":1,"1175":2,"1176":1,"1177":1,"1192":1,"1208":1,"1212":2,"1229":11,"1232":2,"1234":3,"1235":1,"1236":3,"1237":60,"1238":2,"1240":1,"1241":12,"1242":1,"1244":1,"1246":1,"1247":74,"1250":2,"1259":8,"1264":2,"1265":1,"1269":1,"1270":41,"1271":26,"1273":1,"1278":5,"1279":5,"1280":2,"1284":1,"1286":216,"1290":1,"1299":2,"1300":206,"1301":25,"1307":1,"1309":68,"1310":2,"1311":103,"1312":25,"1320":4,"1323":102,"1324":201,"1326":3,"1332":4,"1333":1,"1339":85,"1341":1,"1344":2,"1348":1,"1350":105,"1352":1,"1353":2,"1359":180,"1366":3,"1369":15,"1379":2,"1380":28,"1382":1,"1385":1,"1388":1,"1389":1,"1394":1,"1395":101,"1396":104,"1400":1,"1402":31,"1405":1,"1408":1,"1415":108,"1416":17,"1417":112,"1419":1,"1422":1,"1427":18,"1429":5,"1430":1,"1431":2,"1435":1,"1436":216,"1438":8,"1441":3,"1442":2,"1443":3,"1444":3,"1446":2,"1449":1,"1451":1,"1453":2,"1454":3,"1455":1,"1456":1,"1459":1,"1460":1,"1465":2,"1466":1,"1467":18,"1472":1,"1477":1,"1481":2,"1486":1,"1488":4,"1489":1,"1490":2,"1491":1,"1492":1,"1496":5,"1526":4,"1531":1,"1532":1,"1535":1,"1541":1,"1564":1,"1577":2,"1582":1,"1583":1,"1586":2,"1591":1,"1630":2,"1631":1,"1633":1,"1640":4,"1641":21,"1651":1,"1653":4,"1662":1,"1664":1,"1667":1,"1668":1,"1669":3,"1670":1,"1684":3,"1685":2,"1686":3,"1687":2,"1700":1,"1720":1,"1724":1,"1727":1,"1730":1,"1758":1,"1764":1,"1815":1,"1816":2,"1831":1,"1832":1,"1847":1,"1849":4,"1851":2,"1853":1,"1887":1,"1912":1,"1916":1,"1921":1,"1922":2,"1923":2,"1935":1,"1937":1,"1940":1,"1944":2,"1948":1,"1949":1,"1950":1,"1951":1,"1962":3,"1973":1,"1982":1,"1988":1,"1989":2,"1991":1,"2000":1,"2001":1,"2003":1,"2008":3,"2010":1,"2013":1,"2056":1,"2070":1,"2072":1,"2074":1,"2076":1,"2083":2,"2085":1,"2086":1,"2097":1,"2108":1,"2126":1,"2127":1,"2129":1,"2130":1,"2136":1,"2142":4,"2143":1,"2146":2,"2147":4,"2149":2,"2153":1,"2156":1,"2159":2,"2160":1,"2193":1,"2201":1,"2230":1,"2232":2}}],["straddle",{"3":{"1536":1}}],["straddles",{"3":{"1324":1,"1356":1,"1359":1}}],["stratification",{"3":{"1487":1}}],["strategic",{"3":{"1577":1,"1581":1,"1582":1,"1583":1,"1811":2,"2114":1}}],["strategies",{"3":{"0":1,"290":1,"295":1,"335":2,"1176":1,"1212":1,"1238":1,"1241":2,"1247":23,"1284":1,"1286":8,"1339":1,"1359":17,"1369":1,"1436":2,"1438":3,"1485":1,"1496":1,"1497":1,"1535":1,"1538":1,"1664":1,"1666":1,"1988":1,"2232":1}}],["strategy",{"0":{"1241":1,"1358":1,"1490":1,"1551":1,"1612":1},"1":{"394":1,"395":1,"396":1,"397":1,"398":1,"399":1,"400":1,"401":1,"402":1,"403":1,"404":1,"405":1,"406":1,"407":1,"408":1,"409":1,"445":1,"446":1,"447":1,"448":1,"449":1,"450":1,"451":1,"452":1,"453":1,"454":1,"455":1,"553":1,"554":1,"555":1,"556":1,"557":1,"558":1,"559":1,"560":1,"1072":1,"1073":1,"1074":1,"1075":1,"1076":1,"1077":1,"1078":1,"1079":1,"1172":1,"1173":1,"1174":1,"1175":1,"1176":1,"1177":1,"1178":1,"1179":1,"1180":1},"2":{"72":1,"407":2,"609":1,"988":1,"2156":1,"2158":1},"3":{"0":12,"17":1,"21":1,"27":1,"71":6,"72":2,"120":1,"153":1,"239":1,"290":3,"295":1,"296":3,"328":1,"330":3,"335":1,"394":1,"407":3,"435":1,"445":1,"459":1,"553":1,"573":1,"609":1,"653":1,"749":1,"819":7,"897":1,"948":1,"987":1,"988":5,"989":1,"991":1,"992":1,"1050":3,"1051":3,"1072":1,"1123":1,"1129":1,"1172":1,"1175":1,"1179":1,"1192":1,"1212":5,"1216":1,"1237":8,"1238":1,"1241":10,"1247":34,"1251":1,"1256":1,"1259":1,"1270":5,"1273":1,"1274":2,"1278":3,"1281":1,"1286":51,"1301":3,"1309":3,"1310":7,"1311":26,"1314":1,"1321":1,"1323":3,"1324":4,"1339":5,"1350":1,"1358":4,"1359":29,"1361":1,"1364":1,"1369":2,"1380":2,"1395":8,"1396":3,"1415":2,"1416":1,"1417":3,"1418":1,"1429":1,"1436":9,"1438":3,"1449":1,"1450":1,"1453":1,"1455":3,"1461":1,"1467":3,"1487":1,"1488":1,"1490":1,"1491":1,"1492":1,"1493":3,"1496":3,"1499":1,"1510":1,"1526":3,"1535":1,"1539":1,"1543":1,"1545":1,"1546":1,"1554":1,"1556":1,"1566":1,"1571":1,"1576":1,"1577":4,"1586":2,"1591":3,"1597":1,"1600":1,"1637":1,"1639":1,"1652":1,"1664":1,"1708":1,"1719":1,"1752":1,"1763":1,"1774":2,"1796":1,"1825":1,"1840":5,"1854":1,"1882":1,"1988":3,"1989":2,"1990":1,"1991":3,"2000":1,"2032":5,"2038":1,"2055":1,"2133":1,"2156":2,"2158":1,"2160":1,"2232":4}}],["stray",{"3":{"1091":1,"1300":1,"1395":2,"1396":1,"1425":1,"1436":2,"1496":3,"2150":1}}],["straightforward",{"3":{"1265":1,"1323":1,"1347":1}}],["straight",{"3":{"0":2,"102":1,"109":1,"121":1,"135":1,"150":1,"295":1,"423":1,"424":1,"459":1,"522":1,"523":1,"534":1,"545":1,"599":1,"674":1,"700":1,"749":1,"782":1,"790":1,"793":1,"799":1,"827":1,"881":1,"905":1,"987":1,"1018":1,"1042":1,"1077":1,"1100":1,"1116":1,"1183":1,"1187":1,"1194":1,"1237":1,"1244":1,"1247":2,"1252":1,"1256":1,"1257":1,"1259":5,"1263":1,"1264":1,"1270":11,"1271":1,"1278":2,"1286":23,"1289":1,"1300":11,"1301":2,"1307":1,"1309":5,"1311":11,"1318":1,"1323":4,"1324":22,"1339":6,"1347":1,"1350":6,"1359":41,"1369":4,"1375":1,"1380":7,"1383":1,"1389":1,"1392":1,"1395":20,"1396":18,"1402":1,"1406":1,"1415":14,"1416":3,"1417":5,"1436":11,"1438":2,"1467":2,"1479":1,"1585":1,"1651":1,"1669":1,"1730":1,"1773":1,"1823":1,"1840":2,"1844":1,"1851":1,"1859":1,"1902":1,"1917":1,"1922":1,"1945":1,"1955":2,"1957":1,"1975":1,"1986":1,"1991":1,"1994":1,"1998":1,"2047":1,"2066":1,"2069":1,"2096":1,"2124":1,"2137":1,"2157":1,"2162":1,"2165":1,"2186":1,"2204":1}}],["strangers",{"3":{"1396":1}}],["stranger",{"3":{"1017":1,"1415":1,"1454":1}}],["strangler",{"3":{"0":2,"53":1,"57":1,"59":1,"1012":1,"1312":1,"1436":1,"1526":1,"1532":1,"1544":1,"1576":1,"1577":1,"1586":1,"1591":1,"1663":1}}],["stranding",{"3":{"897":1,"1311":1,"1431":1}}],["strands",{"3":{"539":1,"1270":1,"1309":1,"1324":1,"1436":1,"2161":1,"2167":1}}],["strand",{"3":{"350":1,"1255":1,"1318":1,"1324":4,"1406":1,"1415":1,"1918":1,"1977":1}}],["strandedfixturehandler",{"3":{"1207":1,"1431":1,"1436":3}}],["strandedfixturehandlerbase",{"3":{"1199":2,"1207":1,"1431":1,"1436":7}}],["stranded",{"3":{"0":1,"534":1,"788":1,"793":1,"794":1,"1003":1,"1207":2,"1323":1,"1431":2,"1436":9,"1496":1,"1526":1,"1569":1,"1577":2,"2048":1,"2169":1}}],["stolen",{"3":{"500":1,"907":2,"908":1,"1300":4,"1322":2,"1323":1,"1406":1,"1415":1,"1436":1,"1901":1,"1905":2,"1980":1,"1981":1,"2140":2,"2144":1}}],["stood",{"3":{"111":1,"136":1,"285":1,"497":1,"1312":1,"1350":1,"1396":1,"1436":1,"1474":1,"2013":1,"2232":1}}],["stock",{"3":{"0":3,"99":2,"534":3,"535":3,"536":6,"538":4,"539":4,"540":2,"556":1,"793":2,"809":1,"1066":1,"1067":2,"1286":1,"1324":4,"1328":2,"1339":7,"1441":5,"1444":1,"1490":1,"1492":1,"1591":2,"1742":2,"1749":1,"1750":1,"1751":1,"1755":1,"1763":1,"1764":3,"1765":1,"1767":2,"1768":1,"1769":2,"1776":2,"2022":1,"2110":1,"2113":1,"2161":4,"2163":1,"2164":2,"2167":3,"2168":3,"2215":1}}],["storable",{"3":{"1369":2}}],["storagedelete",{"3":{"1467":1}}],["storagewrite",{"3":{"1467":1}}],["storageread",{"3":{"1467":2}}],["storageremove",{"3":{"1417":3}}],["storageaccesskey",{"3":{"1467":1}}],["storageallowsharedkeyaccess",{"3":{"1467":1}}],["storagev2",{"3":{"1467":1}}],["storageclearprefix",{"3":{"1417":1}}],["storageset",{"3":{"1417":3}}],["storageget",{"3":{"1417":1}}],["storagekey",{"3":{"1324":2}}],["storagenotconfigured",{"2":{"1116":1,"1389":1},"3":{"1116":1,"1323":1,"1359":1,"1389":1}}],["storage",{"0":{"1285":1,"2126":1},"1":{"163":1,"164":1,"165":1,"166":1,"167":1,"168":1,"169":1,"170":1,"171":1,"437":1,"438":1,"439":1,"440":1,"441":1,"442":1,"443":1,"444":1,"2124":1,"2125":1,"2126":1,"2127":1,"2128":1},"2":{"1484":1,"2127":1},"3":{"0":11,"163":1,"165":2,"166":1,"182":1,"207":1,"243":1,"245":3,"248":1,"251":1,"254":1,"257":1,"258":1,"259":2,"353":1,"359":1,"360":1,"361":1,"365":1,"387":1,"388":2,"403":1,"437":1,"438":2,"439":2,"441":2,"442":1,"443":5,"444":2,"459":1,"497":2,"505":2,"506":3,"507":8,"508":1,"509":4,"510":1,"511":2,"513":6,"514":3,"536":1,"555":1,"560":1,"598":2,"599":1,"602":2,"641":1,"663":1,"664":1,"665":6,"666":1,"670":1,"707":2,"708":1,"717":1,"732":1,"737":1,"853":1,"855":1,"903":1,"906":1,"972":1,"974":2,"1043":3,"1115":2,"1116":11,"1117":2,"1118":1,"1119":2,"1120":1,"1182":1,"1183":1,"1192":2,"1203":11,"1207":32,"1212":3,"1237":5,"1247":2,"1250":1,"1259":3,"1271":1,"1272":1,"1285":8,"1286":44,"1287":1,"1300":5,"1301":2,"1309":2,"1310":4,"1311":3,"1318":2,"1323":38,"1324":57,"1339":2,"1350":6,"1357":3,"1359":25,"1361":1,"1369":4,"1373":1,"1375":1,"1380":5,"1395":5,"1396":7,"1407":1,"1410":1,"1411":1,"1415":19,"1416":2,"1417":37,"1436":14,"1437":1,"1438":3,"1445":1,"1446":1,"1455":3,"1465":6,"1467":13,"1470":1,"1472":1,"1473":1,"1474":2,"1478":2,"1481":2,"1484":2,"1487":1,"1490":1,"1496":3,"1526":1,"1563":1,"1567":1,"1577":2,"1599":1,"1631":2,"1635":1,"1638":1,"1639":2,"1640":2,"1641":5,"1660":1,"1668":2,"1669":3,"1670":2,"1677":1,"1748":1,"1765":3,"1779":2,"1854":1,"1855":1,"1862":2,"1863":1,"1864":1,"1900":1,"1901":1,"1904":1,"1922":1,"1933":1,"1948":2,"1968":1,"1970":2,"1980":1,"2012":1,"2034":2,"2036":2,"2074":1,"2098":1,"2123":2,"2124":3,"2126":7,"2127":3,"2128":3,"2133":1,"2142":1,"2143":3,"2144":1,"2145":1,"2207":1,"2208":1,"2210":2,"2232":3}}],["stories",{"3":{"506":1,"508":1,"555":1,"560":1,"2131":1}}],["storing",{"3":{"206":1,"208":1,"995":1,"1092":1,"1259":3,"1270":1,"1286":5,"1300":2,"1309":1,"1311":2,"1323":1,"1324":5,"1339":1,"1350":4,"1359":1,"1362":1,"1369":4,"1395":4,"1396":2,"1417":3,"1436":3,"1631":1,"1900":1,"1979":1,"2124":1,"2125":1}}],["story",{"3":{"24":1,"71":1,"148":1,"168":1,"236":1,"349":1,"350":1,"365":1,"379":1,"413":1,"507":1,"633":1,"707":1,"916":1,"990":1,"1018":1,"1020":1,"1035":1,"1044":1,"1074":1,"1084":1,"1212":1,"1228":1,"1230":1,"1234":1,"1236":1,"1237":2,"1241":1,"1247":2,"1259":1,"1263":2,"1270":2,"1271":1,"1275":1,"1286":9,"1290":1,"1291":1,"1292":1,"1300":5,"1301":1,"1303":1,"1309":3,"1311":3,"1312":2,"1323":4,"1324":4,"1336":1,"1348":1,"1350":4,"1353":1,"1359":3,"1366":1,"1369":3,"1375":1,"1376":1,"1380":3,"1381":1,"1395":2,"1396":7,"1400":1,"1410":1,"1415":2,"1416":2,"1417":6,"1427":1,"1428":1,"1436":4,"1455":2,"1471":1,"1473":1,"1476":1,"1482":1,"1488":1,"1496":1,"1547":1,"1549":1,"1566":1,"1577":1,"1591":1,"1699":1,"1706":1,"1723":1,"1727":1,"1833":1,"1853":1,"1894":1,"1899":2,"1927":1,"1929":1,"1968":1,"1978":1,"1985":2,"1989":1,"2029":1,"2034":1,"2059":1,"2061":1,"2062":1,"2064":2,"2089":1,"2090":1,"2113":1,"2121":1,"2129":2,"2146":1,"2155":1,"2169":1,"2182":1,"2194":1,"2208":1,"2232":1}}],["storming",{"3":{"1438":1}}],["storms",{"3":{"174":1,"1547":1,"1566":1,"2014":1,"2162":1,"2182":1}}],["storm",{"3":{"0":3,"72":1,"407":1,"612":1,"792":1,"827":2,"883":1,"1259":1,"1330":1,"1339":3,"1402":1,"1436":1,"1438":1,"1443":1,"1465":2,"1668":1,"2007":1,"2073":1,"2166":1,"2232":1}}],["storehome",{"3":{"1591":6,"1595":1,"1596":3,"1597":2,"1600":2}}],["storehomepagecontent",{"3":{"1324":1}}],["storeharness",{"3":{"1199":2,"1207":1}}],["storegenerated",{"3":{"1175":1,"1280":1,"1286":3}}],["storeaccounts",{"3":{"1286":1}}],["storeapphostfixture",{"3":{"914":1,"1069":1}}],["storearchitecturemap",{"2":{"132":1,"1161":1},"3":{"132":1,"1161":1,"1431":1,"1436":3,"1489":2,"1590":1,"1591":4,"1600":1,"2042":1}}],["storetimezonesettings",{"2":{"675":1,"676":1},"3":{"675":1,"676":1}}],["storefronthostapplicationfactory",{"2":{"572":1},"3":{"572":1}}],["storefront",{"3":{"0":2,"217":1,"825":1,"830":1,"831":1,"832":1,"1027":1,"1028":2,"1124":1,"1416":1,"1436":2,"1600":2,"1750":1,"1764":2,"1765":1,"1767":1}}],["stores",{"3":{"0":6,"24":1,"26":1,"231":1,"243":1,"245":2,"256":1,"261":1,"286":1,"309":1,"420":1,"696":1,"702":1,"715":1,"727":1,"732":1,"848":1,"849":1,"854":1,"883":1,"904":1,"945":1,"947":1,"1012":1,"1025":1,"1026":1,"1034":1,"1035":1,"1068":1,"1116":1,"1229":1,"1237":2,"1247":2,"1259":4,"1263":1,"1270":5,"1273":1,"1275":2,"1285":1,"1286":13,"1293":2,"1300":7,"1301":8,"1309":1,"1311":9,"1312":1,"1318":1,"1322":1,"1323":7,"1324":13,"1339":6,"1350":2,"1357":1,"1359":5,"1369":2,"1395":12,"1396":4,"1402":1,"1406":1,"1409":1,"1412":1,"1415":10,"1416":1,"1417":11,"1433":1,"1436":8,"1438":1,"1441":1,"1454":1,"1456":1,"1465":1,"1467":1,"1488":1,"1496":1,"1540":1,"1553":1,"1631":3,"1641":1,"1660":1,"1667":1,"1854":1,"1902":1,"1909":1,"1911":1,"1917":1,"2040":1,"2143":1,"2149":1,"2187":1,"2232":1}}],["storedat",{"3":{"1324":1}}],["storedpermission",{"3":{"1436":1}}],["storedpermissions",{"3":{"1300":2,"1324":1}}],["storedpermissionroleadministrationservicetests",{"2":{"1199":1},"3":{"1199":1,"1207":4}}],["storedpermissionroleadministrationservice",{"2":{"1287":1,"1297":1,"2200":1},"3":{"1059":1,"1199":2,"1203":1,"1207":2,"1287":1,"1297":2,"1300":22,"1323":1,"1496":1,"2200":1}}],["storedhashes",{"3":{"1300":1}}],["storedstate",{"3":{"1199":2,"1207":1}}],["stored",{"0":{"1250":1,"2199":1},"1":{"2194":1,"2195":1,"2196":1,"2197":1,"2198":1,"2199":1,"2200":1,"2201":1,"2202":1,"2203":1},"2":{"1902":1},"3":{"0":12,"24":3,"27":1,"41":1,"126":1,"157":5,"160":1,"184":1,"186":2,"224":1,"225":1,"231":1,"243":1,"246":1,"248":1,"265":1,"284":1,"309":2,"310":6,"311":1,"314":1,"353":1,"358":1,"359":6,"360":1,"363":1,"364":1,"365":3,"387":1,"388":1,"395":1,"397":1,"399":1,"403":1,"423":1,"443":2,"499":11,"500":1,"501":2,"507":2,"511":1,"513":1,"528":1,"529":2,"530":1,"539":1,"574":1,"591":1,"605":1,"666":1,"672":1,"692":1,"707":1,"717":1,"740":1,"767":2,"790":1,"813":1,"849":1,"852":1,"854":1,"880":1,"881":2,"885":1,"904":1,"944":2,"945":4,"946":2,"948":5,"965":1,"971":1,"995":1,"996":1,"1017":1,"1018":3,"1026":3,"1034":1,"1035":1,"1042":2,"1044":1,"1050":1,"1057":1,"1059":6,"1060":2,"1061":4,"1063":1,"1114":1,"1115":2,"1116":3,"1118":2,"1135":1,"1140":1,"1183":1,"1212":7,"1229":2,"1234":2,"1237":4,"1247":2,"1250":2,"1251":1,"1256":2,"1259":11,"1270":7,"1271":2,"1283":2,"1284":2,"1285":1,"1286":37,"1289":1,"1291":2,"1292":2,"1293":1,"1294":1,"1295":1,"1297":3,"1300":80,"1301":15,"1309":4,"1311":31,"1312":1,"1321":1,"1323":15,"1324":49,"1339":5,"1342":2,"1344":1,"1347":3,"1350":16,"1352":1,"1356":1,"1357":1,"1358":1,"1359":43,"1362":3,"1369":11,"1380":7,"1382":1,"1384":1,"1389":2,"1391":1,"1395":19,"1396":13,"1402":3,"1405":1,"1409":5,"1411":1,"1414":1,"1415":26,"1417":18,"1432":1,"1435":1,"1436":12,"1438":2,"1443":1,"1454":2,"1455":2,"1462":1,"1465":2,"1467":4,"1492":1,"1496":3,"1563":1,"1567":1,"1577":2,"1585":1,"1630":6,"1631":8,"1633":1,"1638":1,"1639":4,"1640":3,"1641":20,"1664":1,"1686":2,"1732":1,"1747":2,"1749":2,"1764":3,"1765":3,"1767":2,"1779":1,"1849":1,"1854":1,"1861":1,"1900":1,"1902":4,"1908":3,"1909":4,"1922":1,"1933":1,"1964":1,"1975":1,"1979":1,"1980":1,"1982":1,"2043":1,"2047":1,"2078":1,"2083":2,"2125":1,"2128":1,"2141":2,"2142":4,"2144":1,"2157":1,"2186":1,"2192":1,"2193":1,"2194":1,"2199":3,"2200":3,"2202":3,"2203":2,"2208":2,"2211":1,"2232":4}}],["store",{"0":{"91":1,"96":1,"1479":1,"1480":2,"1481":1,"1603":1,"1995":1},"1":{"1587":1,"1588":1,"1589":1,"1590":1,"1591":1,"1592":1,"1593":1,"1594":1,"1595":1,"1596":1,"1597":1,"1598":1,"1599":1,"1600":1,"1681":1,"1682":1,"1683":1,"1684":1,"1685":1,"1686":1,"1687":1,"1688":1,"1689":1,"1690":1,"1691":1,"2109":1,"2110":1,"2111":1,"2112":1,"2113":1,"2114":1,"2115":1},"2":{"62":3,"80":1,"395":1,"401":1,"493":3,"564":1,"642":1,"1195":1,"1215":1,"1471":1,"1486":1,"1490":2,"1503":1,"1588":3,"1591":1,"1775":3,"2105":1,"2111":1,"2202":1},"3":{"0":117,"3":5,"7":2,"24":1,"27":3,"37":1,"39":1,"42":7,"59":5,"62":6,"63":1,"66":1,"68":1,"72":9,"73":5,"76":1,"80":8,"85":1,"86":1,"91":6,"93":5,"94":12,"95":5,"96":12,"97":2,"98":3,"99":2,"100":1,"101":2,"102":1,"103":3,"104":3,"109":3,"115":1,"117":3,"121":3,"127":3,"128":7,"132":1,"135":1,"145":6,"146":1,"147":5,"149":3,"150":1,"151":1,"152":5,"155":1,"157":4,"159":1,"161":1,"165":2,"166":2,"167":1,"170":1,"175":3,"180":11,"184":2,"186":5,"193":1,"195":17,"196":2,"200":1,"201":1,"202":1,"203":2,"212":1,"214":1,"217":6,"218":7,"223":1,"229":1,"230":11,"235":7,"238":1,"241":1,"242":1,"243":27,"245":10,"249":3,"250":4,"251":2,"252":3,"253":1,"254":2,"256":1,"257":2,"258":1,"259":3,"260":10,"261":8,"279":1,"281":2,"283":3,"285":1,"291":5,"293":1,"295":6,"300":1,"310":4,"314":2,"318":13,"320":1,"322":1,"324":3,"325":5,"326":3,"333":2,"341":5,"345":2,"350":1,"351":1,"352":1,"353":1,"359":3,"368":1,"373":3,"375":3,"386":4,"387":3,"388":1,"390":14,"391":13,"392":8,"393":7,"395":4,"397":1,"401":11,"403":13,"405":1,"409":1,"414":1,"418":1,"421":1,"422":2,"433":1,"435":2,"439":1,"446":2,"448":12,"452":19,"453":1,"454":4,"458":2,"459":5,"461":1,"462":1,"463":6,"464":2,"465":5,"466":1,"468":1,"470":7,"475":10,"476":11,"477":11,"478":2,"481":1,"482":1,"483":2,"486":1,"487":1,"488":2,"489":2,"490":2,"491":2,"492":3,"493":7,"494":4,"495":4,"497":12,"499":11,"500":1,"502":1,"507":10,"511":4,"513":6,"514":2,"527":1,"534":2,"535":2,"536":3,"537":2,"538":1,"539":6,"540":1,"543":5,"545":22,"547":1,"550":1,"551":1,"552":7,"554":2,"556":25,"562":1,"563":5,"564":20,"566":6,"567":8,"572":29,"574":1,"575":3,"576":1,"577":4,"578":8,"582":1,"583":30,"585":13,"586":7,"589":1,"591":7,"593":1,"594":3,"597":1,"598":1,"599":35,"600":5,"601":5,"602":3,"603":5,"604":3,"607":14,"609":52,"611":11,"612":6,"613":6,"616":4,"618":7,"620":3,"621":4,"624":8,"625":7,"626":37,"627":2,"628":6,"629":5,"638":4,"640":25,"641":1,"642":8,"643":5,"644":10,"649":10,"651":5,"655":5,"657":14,"658":1,"660":2,"663":1,"665":12,"666":1,"667":2,"668":9,"669":7,"674":17,"675":3,"676":5,"677":3,"681":2,"683":1,"689":1,"690":1,"691":2,"698":2,"702":2,"707":4,"710":1,"711":1,"713":1,"714":1,"715":4,"718":6,"719":4,"720":2,"723":1,"724":4,"725":20,"726":2,"727":2,"728":7,"733":5,"737":2,"739":1,"741":2,"743":6,"744":2,"745":1,"749":14,"751":4,"752":2,"755":1,"756":4,"757":24,"758":1,"759":6,"760":5,"761":6,"764":5,"765":4,"766":35,"767":2,"768":6,"769":5,"774":17,"780":4,"782":15,"789":6,"790":21,"792":4,"793":16,"794":9,"798":2,"799":1,"803":1,"804":5,"805":2,"806":1,"808":2,"809":5,"810":2,"813":5,"825":4,"826":8,"827":25,"829":1,"830":10,"831":7,"832":14,"833":7,"836":1,"837":13,"838":25,"839":6,"840":3,"841":6,"842":1,"846":1,"853":4,"854":5,"855":2,"856":2,"857":1,"859":1,"861":15,"863":9,"864":5,"867":1,"868":7,"869":14,"870":8,"872":2,"873":12,"875":2,"876":23,"877":7,"879":1,"881":7,"884":3,"889":3,"891":4,"892":1,"896":1,"897":10,"900":6,"903":2,"905":6,"906":1,"907":1,"908":1,"913":6,"914":7,"916":5,"917":2,"924":7,"926":7,"927":4,"930":1,"933":1,"939":1,"946":3,"949":1,"954":16,"956":1,"958":1,"959":2,"960":1,"964":1,"971":3,"972":4,"973":1,"974":3,"979":1,"980":3,"981":1,"983":1,"986":2,"988":7,"990":3,"991":3,"994":1,"996":2,"999":1,"1004":5,"1006":1,"1010":1,"1012":6,"1014":2,"1020":1,"1025":11,"1026":28,"1027":1,"1028":2,"1029":2,"1036":1,"1044":1,"1050":5,"1051":2,"1054":4,"1058":4,"1063":1,"1065":2,"1067":3,"1069":3,"1073":1,"1074":3,"1078":2,"1081":1,"1082":7,"1083":3,"1085":2,"1086":3,"1090":1,"1093":1,"1094":1,"1117":1,"1123":2,"1124":13,"1127":3,"1128":1,"1129":1,"1133":4,"1139":1,"1141":1,"1142":2,"1144":1,"1154":2,"1156":1,"1161":4,"1162":1,"1163":1,"1168":11,"1184":1,"1187":3,"1195":1,"1207":1,"1208":1,"1212":9,"1214":1,"1215":1,"1216":1,"1229":4,"1231":1,"1233":1,"1237":43,"1246":2,"1247":15,"1253":1,"1257":1,"1259":23,"1262":5,"1263":1,"1264":8,"1265":1,"1266":1,"1269":2,"1270":75,"1271":29,"1273":1,"1275":1,"1277":1,"1280":2,"1282":1,"1283":1,"1285":1,"1286":95,"1290":5,"1293":1,"1294":1,"1300":138,"1301":36,"1309":9,"1310":6,"1311":117,"1312":13,"1315":1,"1316":3,"1318":2,"1322":2,"1323":118,"1324":141,"1326":2,"1337":1,"1339":139,"1350":1,"1357":2,"1359":13,"1369":2,"1379":2,"1380":7,"1395":9,"1396":6,"1402":2,"1405":2,"1406":1,"1409":1,"1415":19,"1416":1,"1417":69,"1427":1,"1428":1,"1431":1,"1436":323,"1438":5,"1441":1,"1442":1,"1444":6,"1447":1,"1453":2,"1454":1,"1455":10,"1459":1,"1460":1,"1462":1,"1465":3,"1467":5,"1471":8,"1474":1,"1475":2,"1479":21,"1480":30,"1481":3,"1482":2,"1483":3,"1485":1,"1486":10,"1487":2,"1488":4,"1489":5,"1490":8,"1492":10,"1496":7,"1500":2,"1503":1,"1512":1,"1520":1,"1526":2,"1531":1,"1545":1,"1556":1,"1566":1,"1573":1,"1577":2,"1582":4,"1583":1,"1585":2,"1587":2,"1588":4,"1589":2,"1591":80,"1593":1,"1594":1,"1596":20,"1597":3,"1598":1,"1599":12,"1600":18,"1601":1,"1602":1,"1627":1,"1631":1,"1635":1,"1636":1,"1638":1,"1641":2,"1645":1,"1646":1,"1650":2,"1653":1,"1654":1,"1658":2,"1659":1,"1660":1,"1665":1,"1666":1,"1667":1,"1668":2,"1669":2,"1672":1,"1673":1,"1675":1,"1681":1,"1703":2,"1705":3,"1708":1,"1721":1,"1724":1,"1736":1,"1739":1,"1740":1,"1747":1,"1756":1,"1757":1,"1760":2,"1761":6,"1764":14,"1765":35,"1770":1,"1773":3,"1775":3,"1784":1,"1785":1,"1810":2,"1815":3,"1828":1,"1839":2,"1851":1,"1854":1,"1856":1,"1858":1,"1872":1,"1874":1,"1875":2,"1876":1,"1891":1,"1896":2,"1898":1,"1900":1,"1905":1,"1909":2,"1910":1,"1912":3,"1915":1,"1916":1,"1921":1,"1922":6,"1923":5,"1934":1,"1947":1,"1948":2,"1957":1,"1963":1,"1967":1,"1970":2,"1972":2,"1973":1,"1978":2,"1979":1,"1982":2,"1983":5,"1985":3,"1994":1,"1995":4,"1996":1,"1997":1,"2000":9,"2004":1,"2012":1,"2024":2,"2025":3,"2042":1,"2043":1,"2044":5,"2046":1,"2055":3,"2057":1,"2066":1,"2069":1,"2083":1,"2101":1,"2104":1,"2105":1,"2109":1,"2110":1,"2111":6,"2112":2,"2113":5,"2114":1,"2115":1,"2123":1,"2124":1,"2125":1,"2126":1,"2127":1,"2128":3,"2131":7,"2136":1,"2138":2,"2142":1,"2146":1,"2149":1,"2151":4,"2156":2,"2157":1,"2158":5,"2161":1,"2164":1,"2166":2,"2168":1,"2169":1,"2179":2,"2185":1,"2187":1,"2189":1,"2192":1,"2193":2,"2200":1,"2202":5,"2210":1,"2228":1,"2231":1,"2232":3}}],["stoptracingasync",{"3":{"1436":1}}],["stoplistenasync",{"3":{"1417":1}}],["stopdetecting",{"3":{"1417":1}}],["stopwords",{"3":{"1359":1}}],["stopwatch",{"2":{"1163":1,"1821":1},"3":{"1163":1,"1263":3,"1270":6,"1311":2,"1323":5,"1339":3,"1425":1,"1427":2,"1436":3,"1475":1,"1818":1,"1821":2}}],["stopasync",{"3":{"572":2,"1069":1,"1259":2,"1286":1,"1323":1,"1324":1,"1395":1,"1417":7,"1429":1,"1436":6,"1488":2,"2055":2}}],["stop",{"0":{"2072":1},"1":{"1803":1,"1804":1,"1805":1,"1806":1,"1807":1},"3":{"80":1,"81":2,"115":1,"156":2,"237":1,"283":1,"341":1,"433":1,"472":1,"557":1,"571":1,"595":1,"674":1,"751":1,"791":1,"827":1,"870":1,"897":2,"898":1,"910":1,"932":1,"948":1,"1006":1,"1124":1,"1133":1,"1134":1,"1212":1,"1237":2,"1240":1,"1247":1,"1251":1,"1259":3,"1270":1,"1286":3,"1300":3,"1311":6,"1323":5,"1324":5,"1326":1,"1328":1,"1339":4,"1350":2,"1357":1,"1359":7,"1366":1,"1369":1,"1395":6,"1396":9,"1415":5,"1417":4,"1436":13,"1438":3,"1443":1,"1454":1,"1456":2,"1467":2,"1489":1,"1490":1,"1526":1,"1589":1,"1595":1,"1596":1,"1606":1,"1654":1,"1671":1,"1684":1,"1727":1,"1729":1,"1740":1,"1749":1,"1765":1,"1796":1,"1802":1,"1803":1,"1812":1,"1859":1,"1868":1,"1891":1,"1893":1,"1924":1,"1980":1,"2000":1,"2001":3,"2016":1,"2049":1,"2050":1,"2066":1,"2080":1,"2085":1,"2097":1,"2148":1,"2150":1,"2169":1,"2180":1,"2206":1}}],["stopped",{"3":{"0":1,"67":1,"109":1,"170":1,"248":1,"318":1,"413":1,"497":1,"499":1,"527":1,"563":1,"741":1,"757":1,"832":1,"840":1,"876":1,"905":2,"932":1,"1005":2,"1019":1,"1247":2,"1259":1,"1270":2,"1286":3,"1309":1,"1311":1,"1312":1,"1322":1,"1323":4,"1324":5,"1328":1,"1332":1,"1339":4,"1359":2,"1395":1,"1396":2,"1399":1,"1402":2,"1415":1,"1417":4,"1435":1,"1436":13,"1438":2,"1449":1,"1451":1,"1455":1,"1467":2,"1478":1,"1489":2,"1496":1,"1699":1,"1727":1,"1765":1,"1826":1,"1919":2,"1946":1,"1992":1,"2039":1,"2045":1,"2097":1}}],["stoppingtoken",{"2":{"237":1},"3":{"237":1,"518":1,"1100":1,"1286":1,"1323":2,"1339":4,"1396":3}}],["stopping",{"3":{"0":3,"135":1,"235":1,"255":1,"626":1,"898":1,"1091":1,"1187":1,"1271":1,"1312":1,"1323":3,"1339":1,"1380":1,"1395":4,"1396":1,"1417":2,"1427":3,"1431":1,"1435":1,"1436":3,"1492":1,"1665":1,"1807":1,"1919":1,"1922":1}}],["stopsgracefully",{"3":{"1436":1}}],["stopsthewalk",{"3":{"1436":2}}],["stops",{"0":{"1989":1},"3":{"0":13,"1":1,"19":1,"24":2,"27":1,"57":1,"72":1,"81":1,"109":1,"111":1,"122":1,"137":1,"148":1,"160":1,"176":1,"216":1,"235":1,"241":1,"243":1,"265":1,"290":1,"361":1,"369":1,"370":1,"371":1,"391":1,"399":1,"401":1,"403":1,"408":1,"472":1,"528":1,"535":1,"539":1,"549":2,"593":1,"608":1,"611":1,"612":1,"614":1,"626":1,"632":1,"633":1,"648":1,"657":1,"698":1,"700":2,"707":1,"714":1,"741":1,"750":1,"782":1,"789":1,"800":1,"809":1,"810":1,"821":1,"827":1,"829":1,"838":1,"863":1,"869":1,"874":1,"889":1,"898":1,"899":1,"912":1,"921":2,"923":1,"924":1,"926":2,"948":1,"963":1,"973":1,"1020":1,"1022":1,"1033":1,"1042":1,"1049":1,"1051":1,"1067":1,"1093":1,"1153":1,"1163":1,"1212":2,"1229":2,"1233":1,"1237":1,"1247":2,"1256":2,"1259":8,"1270":7,"1274":1,"1277":1,"1281":1,"1286":16,"1300":13,"1301":1,"1309":2,"1311":8,"1312":1,"1322":1,"1323":9,"1324":12,"1327":1,"1339":12,"1350":3,"1359":15,"1369":3,"1380":1,"1395":20,"1396":14,"1400":2,"1401":1,"1402":1,"1405":1,"1415":8,"1416":2,"1417":5,"1423":1,"1425":1,"1427":4,"1431":1,"1436":25,"1438":4,"1441":3,"1443":1,"1449":1,"1455":1,"1465":1,"1467":4,"1472":1,"1475":1,"1488":1,"1489":2,"1490":1,"1492":1,"1494":2,"1531":1,"1545":1,"1654":1,"1665":1,"1681":1,"1685":1,"1692":1,"1702":1,"1717":1,"1722":1,"1729":1,"1769":1,"1824":1,"1849":1,"1851":1,"1876":1,"1902":2,"1936":1,"1951":1,"1964":1,"1988":1,"1990":1,"1994":1,"2001":1,"2029":1,"2041":2,"2045":1,"2048":1,"2056":1,"2066":1,"2067":1,"2069":1,"2075":1,"2082":1,"2097":2,"2116":1,"2142":1,"2146":1,"2148":2,"2150":1,"2157":1,"2159":1,"2162":1,"2164":1,"2166":1,"2168":1,"2170":1,"2175":1,"2180":1,"2193":1}}],["stakeholders",{"3":{"1541":1}}],["stake",{"3":{"1396":1,"1436":2,"1438":1,"2195":1}}],["stakes",{"3":{"530":1,"1286":1,"1324":1,"1350":1,"2148":1}}],["stash",{"3":{"1311":3,"1324":1,"1352":1}}],["stashes",{"3":{"350":1,"419":1,"1273":1,"1298":1,"1300":3,"1311":3,"1339":1,"1359":1,"1415":1,"1443":1,"1825":1,"1975":2}}],["stashed",{"3":{"214":1,"350":1,"420":1,"1300":2,"1311":3,"1336":1,"1976":1,"2149":1}}],["staffs",{"3":{"1350":1}}],["staff",{"3":{"689":1}}],["stably",{"3":{"1324":1,"1395":2}}],["stablepolls",{"3":{"1395":3}}],["stablepollsforcompletion",{"3":{"1395":1}}],["stable",{"3":{"0":3,"24":1,"264":1,"265":4,"266":1,"303":1,"341":1,"434":2,"449":1,"549":1,"593":1,"635":1,"742":1,"750":1,"751":1,"880":1,"891":1,"964":1,"1012":1,"1133":1,"1178":1,"1212":1,"1229":3,"1233":1,"1237":5,"1247":2,"1259":4,"1269":1,"1270":4,"1271":3,"1276":1,"1286":7,"1300":7,"1301":1,"1308":1,"1309":5,"1310":1,"1311":13,"1318":1,"1323":14,"1324":4,"1339":1,"1348":1,"1350":16,"1353":2,"1359":46,"1369":2,"1380":4,"1387":1,"1390":2,"1395":7,"1396":28,"1402":16,"1406":1,"1410":1,"1415":9,"1416":1,"1417":7,"1427":1,"1435":1,"1436":10,"1438":3,"1441":1,"1465":1,"1496":1,"1519":1,"1536":1,"1546":1,"1560":1,"1565":1,"1577":1,"1585":1,"1638":1,"1639":1,"1641":3,"1664":1,"1665":1,"1796":1,"1805":1,"1896":1,"1899":1,"1911":2,"1912":1,"1958":1,"2083":1,"2086":2,"2132":1,"2138":2,"2158":1,"2180":1,"2187":1,"2189":1,"2193":1}}],["stability",{"3":{"592":1,"1134":1,"1359":2,"1369":1,"1395":3,"1396":1,"1417":1,"1436":2,"1492":1,"1565":1}}],["starred",{"3":{"1396":1}}],["starborder",{"3":{"1324":1}}],["starhalf",{"3":{"1324":1}}],["stars",{"3":{"1324":4,"1385":1,"1391":1,"1395":3,"1396":1,"1748":1,"1765":1,"1767":1}}],["starving",{"3":{"1259":1}}],["starve",{"3":{"1124":1,"1128":1,"1254":1,"1259":1,"1275":1,"1324":1,"1447":1}}],["starved",{"3":{"556":1}}],["star",{"3":{"391":1,"1324":11,"1391":2,"1395":12,"1396":16,"1417":1,"1748":1,"1750":1,"1758":1,"1763":1,"1764":3,"1765":1,"1767":1,"1768":1,"1773":1,"1787":1,"1793":1,"1807":1,"1812":1,"1817":1,"1827":1,"1834":1,"1846":1,"1853":1,"1862":1,"1868":1,"1877":1,"1886":1,"1892":1,"1899":1,"1905":1,"1912":1,"1922":2,"1924":1,"1930":1,"1941":1,"1950":1,"1959":1,"1965":1,"1972":1,"1979":1,"1985":1,"1991":1,"1997":1,"2002":1,"2014":1,"2028":1,"2038":1,"2049":1,"2060":1,"2069":1,"2080":1,"2086":1,"2098":1,"2108":1,"2115":1,"2123":1,"2128":1,"2133":1,"2139":1,"2146":1,"2153":1,"2160":1,"2169":1,"2180":1,"2193":1,"2203":1,"2211":1}}],["startipaddress",{"3":{"1467":1}}],["startindex",{"3":{"1324":1}}],["starting",{"0":{"2016":1},"3":{"59":1,"256":1,"529":1,"674":6,"749":1,"1067":1,"1124":1,"1229":1,"1247":2,"1263":1,"1270":1,"1286":1,"1293":1,"1300":1,"1301":4,"1311":1,"1320":1,"1323":1,"1324":7,"1326":1,"1327":1,"1339":4,"1342":1,"1350":3,"1359":3,"1380":1,"1390":1,"1395":7,"1396":2,"1402":1,"1415":2,"1417":1,"1436":5,"1438":1,"1441":3,"1449":1,"1488":1,"1492":1,"1537":1,"1646":1,"1658":1,"1727":1,"1781":1,"1801":1,"1821":1,"1825":1,"2000":1,"2028":1,"2087":1,"2098":1,"2156":1}}],["startlocal",{"3":{"1350":1}}],["startutc",{"3":{"1350":2,"1395":1,"1396":1}}],["startuptext",{"3":{"1416":3}}],["startupresources",{"3":{"1416":2}}],["startupbudget",{"3":{"1069":1}}],["startupdelay",{"3":{"905":1,"1259":2,"1275":1,"1286":5,"1317":1,"1323":3}}],["startup",{"0":{"1328":1,"1448":1},"1":{"232":1,"233":1,"234":1,"235":1,"236":1,"237":1,"238":1,"239":1,"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"294":1,"295":1,"296":1,"787":1,"788":1,"789":1,"790":1,"791":1,"792":1,"793":1,"794":1,"795":1},"3":{"0":16,"15":1,"24":1,"31":3,"33":1,"34":1,"55":1,"61":1,"72":2,"91":1,"92":1,"93":1,"94":2,"99":2,"103":2,"122":2,"146":1,"147":1,"148":1,"150":1,"170":4,"175":1,"178":3,"179":2,"181":1,"193":2,"199":1,"200":1,"201":1,"212":1,"219":1,"220":2,"232":1,"235":1,"237":1,"243":3,"245":1,"255":1,"256":4,"258":1,"259":1,"265":4,"288":1,"290":2,"291":3,"292":1,"293":1,"295":3,"296":2,"310":1,"350":1,"387":1,"401":1,"406":1,"418":2,"423":1,"424":1,"448":4,"453":1,"459":4,"464":3,"501":1,"521":1,"536":1,"563":1,"564":1,"567":1,"568":1,"583":6,"585":1,"587":1,"599":3,"601":2,"603":2,"633":1,"640":2,"665":1,"666":1,"667":2,"674":5,"675":1,"678":1,"684":1,"698":2,"700":1,"702":1,"709":1,"711":1,"727":1,"732":1,"747":1,"749":7,"751":3,"756":3,"757":2,"762":1,"774":2,"776":3,"787":1,"790":2,"792":3,"794":1,"795":1,"821":1,"827":3,"830":1,"838":1,"846":1,"875":1,"905":1,"907":1,"913":1,"918":1,"929":1,"931":3,"932":2,"933":3,"934":1,"980":8,"1006":2,"1011":1,"1012":1,"1018":1,"1034":1,"1050":1,"1066":4,"1067":2,"1068":4,"1069":1,"1071":1,"1074":1,"1089":2,"1091":2,"1093":1,"1104":1,"1107":1,"1108":7,"1109":1,"1112":1,"1124":3,"1125":1,"1150":3,"1174":1,"1176":1,"1177":1,"1180":1,"1186":1,"1189":1,"1192":1,"1202":1,"1203":20,"1207":88,"1208":15,"1212":3,"1213":1,"1222":1,"1229":2,"1237":1,"1247":5,"1248":1,"1254":1,"1257":2,"1259":10,"1270":1,"1272":1,"1275":1,"1279":1,"1280":1,"1283":1,"1286":46,"1288":3,"1297":1,"1300":19,"1301":1,"1309":8,"1311":121,"1312":2,"1315":5,"1316":2,"1317":2,"1320":3,"1323":38,"1324":32,"1325":1,"1328":5,"1332":2,"1333":1,"1334":1,"1335":4,"1336":1,"1338":1,"1339":43,"1359":2,"1364":1,"1369":6,"1378":1,"1379":1,"1380":7,"1395":4,"1396":10,"1406":1,"1413":1,"1415":17,"1416":6,"1417":16,"1418":1,"1419":1,"1420":1,"1427":11,"1430":2,"1436":18,"1438":22,"1439":1,"1441":10,"1442":3,"1443":3,"1444":1,"1446":1,"1447":1,"1450":1,"1455":6,"1456":3,"1460":1,"1461":1,"1467":19,"1469":2,"1476":1,"1477":1,"1491":1,"1496":6,"1500":1,"1526":3,"1550":1,"1554":1,"1566":1,"1577":6,"1586":2,"1614":1,"1619":1,"1641":1,"1652":1,"1653":1,"1663":1,"1664":3,"1665":3,"1667":1,"1670":4,"1684":2,"1689":1,"1700":1,"1706":1,"1707":2,"1718":1,"1719":3,"1721":1,"1722":1,"1724":2,"1727":1,"1728":1,"1748":1,"1751":1,"1764":1,"1765":1,"1767":1,"1772":1,"1777":1,"1790":1,"1792":1,"1824":1,"1825":1,"1826":1,"1827":1,"1842":1,"1849":1,"1856":1,"1857":1,"1862":1,"1881":3,"1882":1,"1885":5,"1886":1,"1889":1,"1898":1,"1904":1,"1908":1,"1910":1,"1922":1,"1923":1,"1956":2,"1959":1,"1964":1,"1970":1,"1971":1,"1975":2,"1982":2,"1984":1,"1994":1,"2000":2,"2001":1,"2004":1,"2006":1,"2007":1,"2009":1,"2012":1,"2014":1,"2022":1,"2027":2,"2030":2,"2056":3,"2066":1,"2083":1,"2127":1,"2131":1,"2132":2,"2150":1,"2167":1,"2169":1,"2188":1,"2232":5}}],["startdatefunc",{"3":{"1359":2}}],["startdateselector",{"3":{"1359":2}}],["startdate",{"2":{"1630":1},"3":{"1348":1,"1350":8,"1353":1,"1359":9,"1369":3,"1375":1,"1395":9,"1396":2,"1630":4,"1631":3,"1635":2,"1640":2}}],["starttimefunc",{"3":{"1359":3}}],["starttimeselector",{"3":{"1359":3}}],["starttimestamp",{"3":{"1270":1}}],["starttime",{"3":{"1342":1,"1350":4,"1359":4,"1362":1,"1369":4,"1395":6}}],["startaddingitem",{"3":{"1395":2}}],["startactivity",{"3":{"1339":1,"2157":1}}],["startasync",{"2":{"135":1,"1613":1,"1619":1},"3":{"135":1,"200":1,"1069":1,"1259":3,"1286":1,"1323":1,"1324":6,"1436":4,"1611":1,"1613":1,"1619":1}}],["startcoreasync",{"3":{"1324":3}}],["starter",{"3":{"2239":1}}],["startenrollmentasync",{"3":{"1293":1,"1300":2}}],["startedhost",{"3":{"1436":3,"1488":1}}],["starteditingitem",{"3":{"1395":2}}],["starteditingprofile",{"3":{"1395":1}}],["startediting",{"3":{"1395":22}}],["startedat",{"3":{"1323":2,"1324":1}}],["started",{"0":{"1661":1,"2210":1},"1":{"1692":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1698":1,"1699":1,"1700":1,"1701":1,"1702":1,"1703":1,"1704":1,"1705":1},"3":{"0":2,"24":1,"99":1,"170":1,"234":1,"348":1,"353":1,"397":4,"451":1,"490":1,"491":1,"492":1,"493":1,"495":1,"507":1,"513":1,"517":1,"527":1,"528":1,"572":1,"632":3,"640":1,"673":1,"706":2,"711":1,"799":1,"868":1,"869":1,"889":1,"938":1,"1005":1,"1050":1,"1067":2,"1190":1,"1194":1,"1251":1,"1259":1,"1270":2,"1286":1,"1300":3,"1311":2,"1324":14,"1328":1,"1333":1,"1339":7,"1359":1,"1395":9,"1396":3,"1409":1,"1417":4,"1427":1,"1429":1,"1430":2,"1436":8,"1438":1,"1441":2,"1444":1,"1467":3,"1488":1,"1489":1,"1526":1,"1535":1,"1577":7,"1600":1,"1602":1,"1606":1,"1646":1,"1658":1,"1661":1,"1669":1,"1673":1,"1674":1,"1681":1,"1689":2,"1691":1,"1692":1,"1717":1,"1718":1,"1725":1,"1726":1,"1730":1,"1740":1,"1779":1,"1948":1,"1950":2,"1975":1,"1976":1,"2012":1,"2041":1,"2055":1,"2056":2,"2098":2,"2157":1,"2215":1,"2226":1,"2228":1,"2232":1,"2234":2,"2236":1}}],["startobject",{"3":{"1229":2}}],["startoutboxactivity",{"3":{"397":1,"1259":1}}],["startscanningasync",{"3":{"1396":1}}],["startsattime",{"3":{"1395":1}}],["startsatdate",{"3":{"1395":1}}],["startsatlocal",{"3":{"1350":1,"1396":1,"1416":2}}],["startsat",{"2":{"1342":1},"3":{"1342":1,"1350":5,"1352":2,"1359":19,"1362":1,"1369":5,"1380":2,"1395":13,"1396":4,"1526":1,"1534":1,"1630":2,"1631":9,"1635":2,"1639":1,"1640":3,"1814":1}}],["startsatutc",{"3":{"1300":2,"1350":1,"1359":1,"1396":4}}],["startsync",{"3":{"1324":2}}],["startswithscheme",{"3":{"1417":2}}],["startswithsegments",{"3":{"1300":1,"1311":1,"1339":3}}],["startswith",{"3":{"1247":1,"1301":1,"1339":1,"1467":1,"1640":1}}],["starts",{"3":{"0":5,"104":1,"148":1,"195":1,"234":1,"235":2,"238":1,"241":1,"350":2,"395":1,"400":1,"402":1,"409":1,"413":1,"452":1,"518":1,"528":1,"545":1,"551":1,"556":1,"632":1,"640":1,"641":1,"675":1,"705":1,"733":1,"759":1,"762":1,"766":1,"776":1,"782":1,"792":1,"810":1,"827":1,"861":1,"875":1,"907":1,"914":1,"932":1,"965":1,"988":1,"1017":1,"1018":2,"1019":2,"1069":2,"1074":1,"1100":1,"1168":1,"1212":1,"1241":2,"1247":1,"1249":1,"1259":2,"1270":3,"1273":1,"1285":1,"1286":5,"1290":2,"1293":1,"1298":1,"1300":8,"1301":2,"1310":1,"1311":3,"1318":1,"1323":4,"1324":8,"1327":1,"1328":2,"1334":1,"1339":14,"1350":1,"1359":9,"1366":1,"1369":2,"1376":1,"1380":2,"1383":1,"1387":1,"1390":1,"1395":17,"1396":11,"1402":2,"1414":1,"1415":3,"1416":3,"1417":11,"1422":1,"1427":3,"1430":1,"1436":23,"1438":2,"1441":1,"1443":1,"1444":1,"1445":1,"1449":1,"1450":1,"1453":1,"1455":1,"1475":1,"1476":1,"1482":1,"1488":1,"1489":1,"1490":4,"1491":3,"1630":1,"1632":1,"1640":1,"1648":1,"1651":2,"1721":1,"1821":1,"1840":2,"1866":1,"1927":1,"1929":1,"1945":1,"1948":1,"1951":1,"1968":1,"1977":1,"2004":1,"2040":1,"2056":1,"2067":1,"2069":1,"2156":1,"2227":1}}],["start",{"0":{"1334":1,"1682":1,"1693":1,"1723":1,"2205":1,"2245":1},"1":{"232":1,"233":1,"234":1,"235":1,"236":1,"237":1,"238":1,"239":1},"2":{"1606":1,"1740":1},"3":{"0":3,"1":1,"24":1,"33":1,"99":1,"101":1,"135":1,"170":1,"195":1,"232":1,"236":3,"239":1,"256":1,"395":1,"427":1,"452":1,"486":1,"517":1,"539":1,"673":1,"675":2,"676":1,"698":2,"707":1,"709":2,"768":1,"795":1,"808":1,"818":1,"819":1,"820":1,"837":1,"846":1,"867":1,"869":1,"872":1,"875":1,"905":1,"918":1,"932":1,"1025":1,"1026":1,"1070":1,"1091":2,"1093":1,"1096":1,"1125":1,"1133":1,"1144":1,"1184":1,"1194":1,"1210":1,"1212":2,"1234":2,"1237":9,"1238":1,"1247":2,"1269":1,"1270":6,"1286":2,"1300":8,"1311":11,"1317":2,"1320":1,"1323":16,"1324":30,"1327":1,"1328":4,"1333":1,"1339":15,"1344":1,"1348":1,"1350":14,"1352":1,"1356":2,"1359":33,"1364":2,"1369":4,"1379":1,"1380":6,"1392":1,"1395":31,"1396":20,"1402":4,"1415":4,"1416":13,"1417":13,"1430":1,"1436":13,"1438":3,"1441":1,"1443":5,"1444":2,"1449":3,"1453":1,"1454":1,"1456":5,"1467":4,"1475":1,"1477":1,"1488":1,"1489":1,"1490":3,"1496":2,"1577":2,"1606":2,"1630":3,"1631":2,"1635":2,"1650":1,"1651":1,"1654":2,"1655":1,"1669":1,"1671":1,"1675":1,"1707":1,"1718":1,"1724":2,"1740":2,"1751":1,"1764":1,"1765":1,"1767":2,"1768":1,"1772":1,"1814":1,"1818":1,"1843":1,"1860":1,"1913":1,"1947":1,"1958":1,"1960":1,"1962":1,"2003":1,"2007":1,"2008":1,"2010":1,"2012":2,"2013":1,"2015":1,"2055":1,"2065":1,"2090":1,"2097":1,"2104":1,"2121":1,"2157":1,"2178":1,"2197":1,"2204":2,"2211":1,"2233":1,"2236":1}}],["staging",{"3":{"201":1,"543":1,"545":1,"600":1,"631":2,"633":4,"635":1,"636":2,"1034":1,"1258":1,"1259":7,"1286":3,"1290":1,"1311":1,"1324":1,"1441":1,"1458":1,"1467":1,"1477":1,"1685":1,"1686":1,"2107":1}}],["stagefileasync",{"3":{"1324":1}}],["stage",{"2":{"636":1},"3":{"0":5,"157":1,"160":1,"161":1,"490":1,"491":1,"492":1,"493":1,"495":1,"543":2,"545":1,"552":1,"599":1,"600":1,"631":1,"633":6,"635":1,"636":2,"757":1,"790":1,"867":2,"868":2,"869":8,"871":3,"873":2,"876":1,"877":1,"1034":2,"1037":1,"1212":3,"1237":1,"1247":3,"1259":3,"1270":5,"1271":4,"1286":1,"1300":4,"1311":4,"1323":1,"1324":1,"1339":1,"1359":7,"1395":3,"1402":2,"1415":6,"1427":1,"1436":6,"1438":1,"1445":12,"1450":1,"1455":3,"1467":2,"1477":5,"1482":1,"1496":1,"1591":1,"1764":1,"1908":2,"2111":1,"2115":1}}],["stages",{"3":{"0":1,"24":1,"201":1,"599":1,"1074":1,"1258":1,"1259":7,"1270":1,"1286":3,"1300":4,"1311":2,"1323":1,"1324":2,"1396":1,"1402":1,"1436":4,"1445":1,"1455":1,"1467":1,"1477":3,"1577":1,"1908":1,"1910":1}}],["staged",{"0":{"1477":1},"3":{"0":4,"21":1,"24":1,"193":1,"195":1,"201":3,"202":1,"384":1,"543":2,"598":1,"599":3,"601":1,"631":2,"633":3,"782":1,"905":1,"1212":2,"1258":1,"1259":11,"1286":3,"1300":1,"1323":2,"1395":1,"1417":1,"1436":12,"1441":1,"1466":1,"1467":3,"1469":1,"1471":1,"1477":2,"1483":1,"1490":1,"1500":2,"1526":1,"1577":1,"1646":1,"1691":1,"1701":1,"1705":1,"1725":1,"1731":2,"1781":1,"1910":1,"2089":1,"2232":1}}],["stanza",{"3":{"1464":1}}],["stance",{"3":{"39":1,"45":1,"1176":1,"1286":1,"1300":1,"1324":1,"1380":1,"1396":2,"1416":1,"1436":5,"1596":1,"1598":1}}],["standout",{"3":{"2053":1}}],["standalone",{"3":{"350":1,"396":1,"609":1,"631":1,"1271":1,"1309":2,"1323":1,"1324":1,"1341":1,"1350":3,"1359":3,"1380":1,"1395":3,"1396":1,"1402":2,"1415":3,"1432":1,"1436":3,"1441":2,"1443":1,"1467":2,"1489":1,"1611":1,"1630":2,"1632":1,"1635":1,"1638":5,"1639":2,"1648":1}}],["standards",{"3":{"1311":1,"2213":1,"2218":1}}],["standardized",{"3":{"608":1,"826":1,"1218":1,"1229":2,"1259":1,"1271":1,"1311":1,"1526":1,"1546":1}}],["standardizes",{"3":{"340":1,"536":1,"901":1,"1436":1,"1665":1,"1928":1,"2131":1}}],["standardize",{"3":{"300":1,"397":1,"448":1,"1237":1}}],["standard",{"0":{"2030":1},"3":{"0":6,"53":1,"62":1,"66":1,"67":1,"68":1,"70":1,"72":2,"74":1,"100":2,"109":1,"200":1,"265":2,"341":1,"350":1,"407":1,"481":1,"483":1,"497":1,"640":3,"665":1,"743":1,"764":1,"766":4,"768":1,"769":2,"799":1,"819":1,"822":1,"833":1,"846":1,"880":1,"882":1,"883":1,"938":1,"939":1,"1043":1,"1125":1,"1126":1,"1211":1,"1212":2,"1229":4,"1270":10,"1286":6,"1290":1,"1296":1,"1300":13,"1309":3,"1311":11,"1312":4,"1316":1,"1323":3,"1324":5,"1329":1,"1339":17,"1350":4,"1358":1,"1359":8,"1365":1,"1368":2,"1369":1,"1379":1,"1380":2,"1395":12,"1396":7,"1402":1,"1404":1,"1405":1,"1410":1,"1415":5,"1417":2,"1421":1,"1425":1,"1427":2,"1431":1,"1435":1,"1436":3,"1438":1,"1443":2,"1446":1,"1455":2,"1456":1,"1457":1,"1459":1,"1467":8,"1474":1,"1476":3,"1488":1,"1489":1,"1503":1,"1526":3,"1534":1,"1537":1,"1591":3,"1600":2,"1631":2,"1632":5,"1639":2,"1640":3,"1641":1,"1647":1,"1653":1,"1656":1,"1663":1,"1668":1,"1670":1,"1693":1,"1707":1,"1720":1,"1781":1,"1895":1,"1899":1,"1909":1,"1926":1,"1930":1,"1975":1,"1980":1,"1982":1,"2011":1,"2013":1,"2029":1,"2031":1,"2038":1,"2057":2,"2156":1,"2177":1,"2185":1,"2203":1,"2227":1,"2232":1}}],["stand",{"3":{"73":1,"95":1,"111":1,"126":1,"258":1,"476":1,"490":1,"491":1,"492":1,"493":1,"495":1,"601":1,"1016":1,"1074":1,"1077":1,"1128":1,"1154":1,"1211":1,"1270":1,"1284":1,"1286":2,"1312":1,"1339":2,"1350":2,"1359":2,"1369":1,"1380":2,"1396":1,"1402":1,"1415":1,"1417":3,"1428":1,"1436":10,"1438":1,"1443":1,"1467":1,"1503":1,"1577":1,"1602":1,"1663":2,"1806":1,"1929":1,"1942":1,"1995":1,"2008":1}}],["standing",{"3":{"0":2,"140":1,"177":1,"474":1,"490":1,"572":1,"591":1,"745":1,"768":1,"801":1,"811":1,"812":1,"840":1,"867":1,"912":1,"1054":1,"1090":1,"1094":1,"1247":1,"1300":3,"1301":2,"1323":2,"1339":1,"1359":3,"1392":1,"1395":1,"1396":5,"1415":1,"1427":2,"1436":5,"1460":2,"1461":1,"1472":1,"1474":1,"1490":1,"1492":1,"1496":3,"1526":2,"1692":1,"1726":1,"1809":1,"1896":1,"1929":1,"1950":1,"2113":1,"2131":1}}],["standstill",{"3":{"1124":1,"1311":2,"1339":2}}],["stands",{"3":{"0":4,"20":1,"54":1,"178":1,"257":1,"259":1,"508":1,"551":1,"620":1,"640":1,"649":1,"790":1,"797":1,"803":1,"804":1,"808":1,"813":1,"814":1,"827":1,"840":1,"848":1,"877":1,"1055":1,"1075":1,"1116":1,"1142":1,"1177":1,"1286":2,"1300":3,"1309":1,"1323":2,"1324":1,"1350":1,"1359":3,"1380":1,"1395":1,"1396":2,"1415":1,"1436":7,"1496":1,"1526":1,"1534":1,"1577":1,"1591":1,"1671":1,"1786":1,"1796":1,"1933":1,"2001":1,"2048":1,"2050":1,"2094":1,"2168":1,"2204":1}}],["stalling",{"3":{"1309":1,"1312":1,"1323":1,"1413":1}}],["stall",{"3":{"1301":1,"1324":1,"1396":3,"1402":1,"1415":1,"1451":1,"1727":1,"1944":1,"2005":1,"2013":1}}],["stalled",{"3":{"759":1,"1323":1,"1396":1,"1488":1,"2193":1}}],["stalls",{"3":{"0":1,"709":1,"914":1,"1256":1,"1427":1,"1440":1,"1456":1,"1467":1,"1490":1,"1577":1,"1582":1,"1653":1,"1689":1,"1698":1,"2174":1}}],["stales",{"3":{"1922":2}}],["stalerowversionconflicttests",{"3":{"1591":1,"1600":1}}],["staler",{"3":{"1395":1}}],["staled",{"3":{"1301":1,"1912":1,"1913":1,"1918":1,"1924":1}}],["staleundecoratedallowlisttests",{"3":{"1198":1,"1199":2,"1207":1,"1436":5}}],["staleallowlisttests",{"3":{"1198":1,"1199":2,"1207":1,"1436":7,"1496":1}}],["staleness",{"3":{"189":1,"243":1,"245":2,"305":1,"390":2,"391":1,"441":1,"444":1,"674":1,"735":1,"736":1,"924":1,"1060":1,"1102":1,"1270":4,"1283":1,"1300":1,"1301":5,"1311":2,"1324":14,"1350":1,"1359":1,"1395":1,"1402":2,"1436":2,"1475":1,"1496":1,"1534":1,"1556":1,"1577":2,"1586":1,"1591":2,"1597":1,"1651":1,"1668":1,"1923":1,"2127":1,"2128":1,"2203":1}}],["stale",{"3":{"0":5,"70":1,"104":1,"128":1,"130":1,"138":2,"140":1,"141":1,"171":1,"231":1,"235":1,"243":1,"255":2,"279":1,"324":1,"340":1,"341":2,"343":1,"344":1,"345":1,"373":1,"388":1,"426":1,"512":2,"572":2,"608":1,"610":1,"626":1,"628":2,"703":1,"716":1,"720":1,"733":2,"735":1,"751":1,"759":1,"790":3,"792":1,"793":1,"810":1,"841":1,"848":1,"873":1,"876":1,"877":1,"892":1,"905":1,"906":1,"927":1,"971":1,"972":2,"986":1,"991":1,"1028":1,"1042":1,"1045":1,"1065":1,"1101":1,"1175":1,"1184":1,"1212":2,"1229":1,"1237":3,"1249":1,"1259":3,"1265":1,"1270":7,"1271":1,"1286":7,"1300":10,"1301":6,"1306":1,"1309":2,"1311":6,"1323":1,"1324":29,"1332":1,"1339":4,"1347":1,"1350":3,"1352":1,"1359":17,"1373":1,"1375":1,"1379":1,"1380":4,"1393":1,"1395":20,"1396":25,"1399":1,"1402":3,"1406":1,"1411":1,"1415":5,"1416":1,"1417":9,"1436":23,"1438":3,"1453":2,"1460":1,"1475":3,"1488":2,"1489":1,"1490":1,"1492":1,"1496":21,"1525":1,"1526":5,"1534":2,"1546":1,"1549":1,"1556":2,"1561":1,"1571":2,"1577":1,"1591":5,"1596":1,"1597":1,"1600":1,"1635":1,"1639":1,"1648":1,"1651":2,"1668":1,"1685":1,"1689":1,"1701":1,"1702":1,"1708":3,"1720":1,"1730":1,"1748":3,"1749":1,"1765":1,"1822":1,"1869":1,"1870":1,"1871":1,"1876":2,"1877":2,"1898":2,"1912":1,"1913":1,"1914":1,"1918":2,"1920":1,"1923":2,"1924":1,"1948":1,"2013":1,"2034":1,"2037":1,"2043":2,"2045":2,"2046":1,"2055":1,"2097":1,"2127":1,"2163":1,"2167":3,"2188":1,"2193":1,"2199":1,"2202":1,"2232":1}}],["stamptimeout",{"3":{"1323":4}}],["stamptestdbcontext",{"3":{"1199":2,"1207":1}}],["stamper",{"3":{"1323":1}}],["stampeding",{"3":{"1396":1,"1402":1}}],["stampedentity",{"3":{"1199":3,"1207":1}}],["stampedetestquery",{"3":{"1199":2,"1207":1}}],["stampede",{"3":{"0":1,"248":1,"508":1,"733":3,"737":1,"1192":1,"1267":3,"1270":10,"1301":9,"1323":1,"1496":1,"1668":1,"1708":1,"1917":2,"1920":1}}],["stamped",{"3":{"0":3,"26":1,"39":1,"214":1,"221":1,"345":1,"397":1,"434":1,"435":1,"500":1,"501":1,"507":1,"714":1,"715":1,"720":1,"905":1,"907":1,"1044":1,"1083":1,"1084":1,"1140":1,"1168":1,"1212":3,"1231":1,"1232":1,"1237":3,"1247":4,"1253":2,"1259":7,"1265":1,"1269":1,"1270":3,"1275":1,"1280":1,"1281":1,"1286":19,"1290":1,"1300":5,"1309":3,"1312":2,"1318":1,"1323":7,"1324":4,"1339":6,"1357":1,"1358":1,"1359":19,"1380":2,"1390":1,"1395":7,"1396":6,"1402":2,"1415":8,"1418":1,"1425":1,"1427":5,"1436":5,"1438":5,"1496":1,"1545":1,"1577":1,"1591":1,"1653":1,"1657":1,"1664":1,"1665":1,"1672":1,"1685":1,"1698":1,"1704":1,"1719":1,"1720":1,"1749":1,"1767":1,"1810":1,"1839":1,"1933":1,"1936":1,"1982":2,"1984":1,"2090":1,"2186":1,"2192":1,"2197":1,"2203":1,"2230":1}}],["stampauditfields",{"3":{"1286":2}}],["stampasync",{"3":{"1042":1,"1436":5,"2188":1}}],["stamprowversion",{"3":{"1274":1,"1286":2}}],["stamporverifyinsert",{"3":{"698":1,"1286":2}}],["stampingtransformer",{"3":{"1199":2,"1207":1}}],["stamping",{"3":{"0":1,"21":1,"26":1,"39":1,"179":1,"202":1,"350":1,"698":1,"716":2,"826":1,"840":1,"847":1,"923":1,"926":1,"1083":1,"1163":1,"1232":1,"1237":4,"1244":1,"1247":3,"1259":3,"1265":1,"1270":1,"1272":1,"1274":1,"1281":1,"1286":12,"1300":4,"1304":1,"1309":1,"1311":3,"1323":2,"1324":3,"1339":4,"1342":1,"1359":7,"1369":2,"1396":2,"1398":1,"1402":3,"1412":1,"1415":2,"1438":1,"1526":1,"1577":2,"1591":1,"1663":1,"1664":2,"1717":1,"1839":1,"1844":1,"1871":1,"1977":1,"2000":1,"2052":1,"2074":1,"2149":1,"2221":1}}],["stamp",{"0":{"2188":1},"3":{"0":4,"213":1,"214":1,"341":1,"343":1,"365":1,"397":1,"716":1,"926":2,"988":1,"996":1,"1034":1,"1035":1,"1083":1,"1084":1,"1091":1,"1160":1,"1163":1,"1174":1,"1175":1,"1231":1,"1237":1,"1247":1,"1249":2,"1253":1,"1259":8,"1265":1,"1270":7,"1286":21,"1300":7,"1309":1,"1311":2,"1318":2,"1323":9,"1324":8,"1336":1,"1339":5,"1342":1,"1350":1,"1352":2,"1356":1,"1358":5,"1359":33,"1380":1,"1382":1,"1395":4,"1396":2,"1402":2,"1415":2,"1427":4,"1431":2,"1436":14,"1438":2,"1496":1,"1524":1,"1526":1,"1531":1,"1534":3,"1577":1,"1585":1,"1679":1,"1765":1,"1838":1,"1839":3,"1948":1,"2010":1,"2064":1,"2086":1,"2142":1,"2147":1,"2153":1,"2163":1,"2176":1,"2180":1,"2191":1,"2193":2,"2203":1}}],["stampsoftdeletetransition",{"2":{"1274":1},"3":{"1274":1,"1286":2}}],["stamps",{"3":{"0":8,"27":2,"37":1,"39":1,"41":2,"109":1,"195":1,"202":1,"214":1,"246":1,"339":1,"341":1,"343":1,"359":1,"365":1,"402":1,"499":1,"574":1,"698":2,"702":2,"707":1,"714":1,"715":3,"716":2,"740":1,"827":2,"833":1,"846":1,"849":1,"861":1,"905":1,"909":1,"921":1,"995":1,"1018":1,"1033":1,"1034":1,"1042":1,"1059":2,"1081":1,"1083":3,"1118":1,"1184":1,"1212":5,"1231":1,"1232":2,"1237":7,"1247":1,"1249":1,"1252":1,"1253":1,"1255":2,"1259":5,"1270":4,"1271":2,"1273":1,"1274":3,"1275":1,"1276":1,"1278":2,"1281":1,"1286":34,"1290":1,"1300":11,"1309":2,"1310":1,"1311":3,"1312":2,"1317":1,"1323":5,"1324":14,"1326":1,"1333":1,"1336":1,"1338":1,"1339":11,"1350":3,"1359":13,"1363":1,"1366":1,"1369":1,"1373":1,"1380":1,"1389":1,"1395":1,"1396":6,"1402":3,"1410":1,"1415":4,"1417":1,"1426":1,"1427":2,"1433":1,"1436":8,"1438":1,"1441":2,"1443":2,"1447":1,"1496":2,"1585":1,"1664":1,"1670":1,"1839":1,"1844":1,"1851":1,"1876":1,"1889":1,"1948":1,"2083":1,"2091":1,"2143":1,"2148":1,"2149":2,"2153":1,"2166":1,"2186":1,"2188":1,"2197":1,"2200":1,"2208":1,"2232":1}}],["stacked",{"3":{"1304":1,"1402":1,"1524":1,"1713":1}}],["stackexchangeredis",{"2":{"1339":1},"3":{"1339":1}}],["stackexchangerediscache",{"2":{"245":1,"249":1,"253":1,"286":1,"732":1},"3":{"245":1,"249":1,"253":1,"286":1,"732":1,"1301":2}}],["stackexchange",{"2":{"234":1,"245":1,"1301":1,"1915":1,"2043":1},"3":{"234":2,"245":1,"259":2,"1213":1,"1270":3,"1286":1,"1301":6,"1311":4,"1323":3,"1332":1,"1339":6,"1442":2,"1443":1,"1489":1,"1577":1,"1915":1,"2043":1}}],["stackalloc",{"3":{"1286":1}}],["stacks",{"3":{"125":1,"881":1,"1263":1,"1270":1,"1311":1,"1368":1,"1369":1,"1417":1,"1585":1,"2220":1}}],["stacking",{"3":{"0":1,"1028":1,"1324":1,"1339":1,"1396":1,"1415":1,"1416":1,"1443":1,"1459":1}}],["stack",{"0":{"1213":1,"1511":1,"2005":1},"1":{"1210":1,"1211":1,"1212":1,"1213":1,"1214":1,"1215":1,"1216":1},"3":{"0":7,"6":1,"73":1,"115":1,"125":5,"150":1,"340":1,"387":1,"401":1,"407":1,"483":1,"618":1,"619":1,"625":1,"640":1,"641":2,"644":1,"827":1,"837":1,"860":1,"883":1,"913":1,"953":1,"1066":1,"1067":2,"1069":1,"1070":1,"1154":1,"1192":1,"1210":1,"1212":1,"1218":1,"1226":1,"1229":3,"1230":1,"1236":1,"1237":1,"1247":1,"1252":1,"1259":5,"1261":1,"1263":2,"1270":5,"1286":6,"1300":6,"1309":2,"1311":3,"1313":1,"1316":1,"1318":2,"1323":5,"1324":13,"1328":1,"1339":13,"1350":2,"1359":3,"1395":1,"1396":5,"1402":3,"1415":2,"1416":4,"1417":11,"1430":4,"1436":10,"1437":1,"1438":3,"1439":1,"1440":2,"1441":3,"1443":1,"1444":1,"1446":1,"1447":1,"1449":3,"1450":1,"1456":7,"1464":1,"1467":2,"1486":1,"1487":3,"1488":1,"1489":2,"1490":4,"1492":5,"1496":2,"1536":1,"1582":1,"1591":1,"1599":1,"1640":1,"1647":1,"1653":1,"1663":1,"1669":2,"1673":1,"1763":1,"1781":3,"1800":1,"1804":2,"1806":1,"1807":2,"1817":1,"1827":1,"1834":1,"1846":1,"1853":1,"1862":1,"1868":1,"1874":1,"1877":1,"1899":1,"1905":1,"1906":1,"1912":1,"1924":1,"1925":1,"1930":1,"1941":1,"1950":1,"1952":1,"1958":1,"1959":1,"1965":1,"1985":1,"1991":1,"1993":1,"2002":1,"2003":1,"2012":1,"2013":1,"2014":1,"2028":1,"2038":1,"2049":1,"2053":1,"2056":2,"2057":1,"2060":2,"2075":2,"2076":1,"2108":1,"2128":1,"2133":1,"2150":1,"2153":1,"2157":1,"2160":1,"2165":1,"2169":1,"2180":1,"2209":1,"2212":2,"2213":1,"2217":1,"2218":1}}],["stat",{"3":{"1396":1,"1455":1,"1526":1}}],["stats",{"3":{"1396":12,"1487":1}}],["statistics",{"3":{"1339":1,"1524":1,"1526":1,"1531":1}}],["stating",{"0":{"1201":1},"3":{"128":1,"219":1,"463":1,"819":1,"828":1,"972":1,"1012":1,"1018":1,"1082":1,"1134":1,"1248":1,"1270":2,"1286":4,"1300":2,"1311":3,"1323":1,"1324":2,"1339":3,"1350":2,"1359":6,"1362":1,"1369":1,"1395":1,"1396":4,"1417":4,"1436":10,"1459":1,"1526":1,"1810":1,"1815":1,"1816":1,"1844":1,"1881":1,"1948":1,"1956":1,"2081":1,"2137":1}}],["staticwebassets",{"3":{"1436":2}}],["statically",{"3":{"1237":1,"1339":1,"1359":1,"1396":1,"1416":1,"1436":7,"2070":1}}],["staticcsppolicyprovider",{"2":{"214":1,"2011":1,"2149":1},"3":{"214":1,"1199":2,"1203":1,"1207":1,"1336":1,"1339":8,"2011":1,"2149":1}}],["statics",{"3":{"145":1,"150":1,"413":1,"1237":1,"1270":1,"1286":1,"1311":1,"1319":1,"1323":2,"1324":3,"1415":1,"1417":1,"1436":3}}],["static",{"2":{"883":1,"982":1,"1051":1,"1162":1,"1168":1,"1196":1,"1246":1,"1319":1,"1346":1},"3":{"0":14,"10":1,"15":1,"27":1,"59":2,"122":1,"135":3,"137":1,"140":1,"178":1,"189":2,"212":1,"213":2,"214":1,"215":1,"216":2,"219":1,"220":1,"258":1,"305":1,"318":1,"343":1,"350":1,"397":1,"413":1,"549":1,"555":1,"556":2,"640":2,"642":1,"657":3,"698":1,"741":2,"744":1,"782":1,"819":2,"826":1,"881":3,"883":2,"884":1,"897":1,"963":2,"964":3,"972":2,"975":1,"979":1,"980":8,"981":1,"982":1,"1026":1,"1050":6,"1051":1,"1052":1,"1109":1,"1117":1,"1124":1,"1126":1,"1162":1,"1163":1,"1167":3,"1168":7,"1171":1,"1175":2,"1176":1,"1177":1,"1178":1,"1196":2,"1212":5,"1214":2,"1219":1,"1221":1,"1228":1,"1229":9,"1234":2,"1235":2,"1237":33,"1239":3,"1241":3,"1243":1,"1246":2,"1247":43,"1259":21,"1266":1,"1268":3,"1270":39,"1271":11,"1274":1,"1275":1,"1280":2,"1286":107,"1297":1,"1300":75,"1301":14,"1307":1,"1309":35,"1310":7,"1311":59,"1312":6,"1316":1,"1319":3,"1320":1,"1323":75,"1324":106,"1326":1,"1330":1,"1336":1,"1338":1,"1339":82,"1340":1,"1341":1,"1343":1,"1344":1,"1346":1,"1350":52,"1352":1,"1353":2,"1354":1,"1359":72,"1366":2,"1368":1,"1369":11,"1380":17,"1383":1,"1393":2,"1394":1,"1395":53,"1396":82,"1402":17,"1405":2,"1415":46,"1416":13,"1417":26,"1422":2,"1423":1,"1426":1,"1427":15,"1433":1,"1434":1,"1436":138,"1438":2,"1441":1,"1443":2,"1447":1,"1453":1,"1455":2,"1460":1,"1465":1,"1468":1,"1469":1,"1488":3,"1489":3,"1496":4,"1510":1,"1525":1,"1526":3,"1556":2,"1572":1,"1577":1,"1591":2,"1611":1,"1631":1,"1651":1,"1653":1,"1667":1,"1671":1,"1698":2,"1702":1,"1805":1,"1810":5,"1811":2,"1812":1,"1831":1,"1856":1,"1868":1,"1956":1,"1959":1,"1975":1,"1980":1,"1989":1,"1994":1,"2023":1,"2036":1,"2075":1,"2076":1,"2081":1,"2082":1,"2083":1,"2090":1,"2119":1,"2126":1,"2147":1,"2148":1,"2149":2,"2150":2,"2151":2,"2152":1,"2153":4,"2175":1,"2219":1}}],["statequery",{"3":{"1417":1}}],["statehaschanged",{"2":{"1556":1},"3":{"1324":3,"1395":7,"1396":4,"1401":1,"1402":1,"1526":1,"1556":2,"1577":1,"1591":2}}],["state=",{"3":{"1311":1,"1417":2}}],["statetable",{"3":{"1286":1}}],["staterules",{"3":{"1199":3,"1203":1,"1207":1,"1271":8}}],["statemaxlength",{"3":{"1237":1,"1271":2}}],["statemanagementconventiontests",{"2":{"1525":1,"1591":1},"3":{"881":4,"884":1,"1199":2,"1207":4,"1324":1,"1436":17,"1489":2,"1525":1,"1526":2,"1535":1,"1591":2,"1600":1}}],["statemanagementconventiontestsbase",{"2":{"1526":1},"3":{"881":1,"884":1,"1199":3,"1207":2,"1436":17,"1526":2}}],["statements",{"3":{"27":1,"63":1,"82":1,"161":1,"171":1,"181":1,"268":1,"285":1,"345":1,"375":1,"409":1,"486":1,"502":1,"514":1,"526":1,"531":1,"534":1,"540":2,"578":1,"586":1,"604":1,"607":1,"660":1,"713":1,"718":1,"744":1,"761":1,"777":1,"814":1,"825":1,"833":2,"841":1,"857":1,"877":1,"900":1,"917":1,"927":1,"999":1,"1029":1,"1045":1,"1055":1,"1074":1,"1103":1,"1271":1,"1286":2,"1300":1,"1311":1,"1350":1,"1352":1,"1359":9,"1396":6,"1467":1,"1538":1}}],["statement",{"0":{"1246":1},"3":{"0":2,"135":1,"136":1,"140":1,"150":1,"200":1,"220":1,"341":2,"395":1,"423":1,"438":1,"443":1,"478":1,"488":1,"489":1,"518":1,"522":1,"523":1,"524":3,"563":1,"566":1,"587":1,"590":1,"607":2,"633":1,"677":1,"707":1,"716":1,"719":4,"827":1,"838":1,"973":1,"974":1,"996":1,"1005":1,"1007":1,"1049":1,"1100":1,"1114":1,"1120":1,"1123":1,"1132":2,"1133":7,"1134":2,"1135":2,"1141":1,"1175":2,"1190":1,"1212":1,"1235":1,"1237":1,"1242":1,"1243":1,"1246":1,"1247":2,"1252":1,"1259":3,"1270":3,"1271":2,"1275":1,"1278":1,"1280":1,"1286":25,"1300":3,"1301":1,"1309":3,"1310":1,"1323":3,"1324":5,"1339":3,"1348":1,"1350":5,"1359":11,"1369":1,"1396":6,"1402":1,"1415":1,"1416":1,"1417":1,"1423":1,"1431":1,"1436":15,"1438":1,"1447":1,"1451":1,"1489":4,"1526":1,"1689":1,"1699":1,"1727":1,"1749":1,"1795":1,"1865":1,"1871":1,"1872":1,"1876":1,"2031":1,"2033":1,"2043":1,"2089":1,"2126":1,"2203":1}}],["statecontainer",{"3":{"881":1,"884":1,"1436":1,"1526":1}}],["stateful",{"3":{"178":1,"458":1,"881":1,"1020":1,"1186":1,"1187":1,"1300":1,"1324":1,"1396":3,"1415":1,"1417":2,"1436":5,"1467":3,"1489":1,"1526":1,"1566":1,"1833":1}}],["stated",{"3":{"0":2,"24":1,"27":1,"67":1,"100":1,"109":1,"111":1,"115":1,"125":1,"126":1,"135":3,"160":1,"193":2,"200":1,"202":1,"223":1,"243":1,"342":1,"463":1,"477":1,"518":1,"522":1,"536":1,"543":1,"545":1,"556":1,"559":1,"579":1,"602":2,"628":1,"631":2,"632":1,"658":1,"703":1,"723":1,"725":1,"751":1,"766":1,"769":1,"792":1,"797":1,"799":2,"809":1,"819":1,"826":1,"827":3,"838":4,"861":1,"868":1,"869":1,"876":2,"883":1,"888":1,"906":1,"914":1,"931":1,"946":2,"963":1,"968":1,"972":1,"979":2,"988":1,"996":1,"1012":2,"1019":1,"1021":1,"1075":1,"1083":1,"1100":2,"1108":1,"1116":1,"1124":1,"1133":1,"1134":1,"1168":1,"1193":1,"1212":1,"1225":1,"1228":1,"1229":1,"1236":1,"1237":3,"1247":3,"1259":1,"1270":8,"1273":1,"1286":19,"1291":1,"1292":1,"1293":1,"1294":1,"1298":1,"1300":11,"1304":1,"1309":8,"1311":11,"1312":3,"1318":1,"1323":10,"1324":10,"1330":2,"1332":1,"1333":1,"1336":1,"1337":1,"1339":8,"1350":5,"1359":28,"1369":1,"1380":3,"1384":1,"1389":1,"1392":1,"1395":5,"1396":27,"1399":1,"1402":9,"1405":1,"1415":7,"1416":1,"1417":3,"1424":1,"1425":1,"1436":25,"1441":2,"1445":1,"1455":1,"1459":1,"1467":3,"1469":1,"1470":1,"1474":1,"1486":1,"1489":1,"1496":3,"1526":1,"1531":1,"1541":1,"1545":2,"1547":1,"1548":1,"1558":1,"1572":1,"1577":1,"1591":2,"1666":1,"1673":1,"1685":1,"1709":1,"1789":1,"1799":1,"1874":3,"1877":1,"1958":1,"2030":1,"2059":1,"2168":1,"2174":1,"2182":1,"2232":1}}],["statelessness",{"3":{"1323":1,"1427":1}}],["stateless",{"3":{"0":5,"310":1,"359":1,"365":1,"458":2,"460":4,"462":1,"498":2,"499":2,"500":1,"501":2,"503":2,"910":1,"946":1,"1059":1,"1176":1,"1184":1,"1187":1,"1212":3,"1259":1,"1272":1,"1280":1,"1286":6,"1293":2,"1300":7,"1309":1,"1311":2,"1323":3,"1339":2,"1350":1,"1359":4,"1395":4,"1396":1,"1402":1,"1417":6,"1441":1,"1539":1,"1549":1,"1577":1,"1591":1,"1980":2,"1981":1,"1982":1,"1984":1,"1985":3,"2066":2,"2142":1,"2198":1,"2232":2}}],["state",{"0":{"100":1,"720":1,"813":1,"1556":1,"1633":1,"1766":1,"1769":1,"1777":1,"2076":1},"1":{"779":1,"780":1,"781":1,"782":1,"783":1,"784":1,"785":1,"786":1},"2":{"426":1,"784":1,"1976":1,"2032":2},"3":{"0":23,"17":1,"27":3,"39":1,"42":1,"74":1,"93":1,"109":1,"120":1,"131":1,"137":1,"141":1,"152":1,"159":1,"177":1,"178":1,"181":1,"186":1,"188":1,"195":1,"201":1,"208":1,"227":1,"228":1,"243":2,"249":1,"252":1,"254":1,"256":1,"257":1,"258":1,"259":1,"265":1,"266":1,"267":1,"272":1,"279":1,"281":1,"283":1,"284":1,"285":1,"286":1,"302":1,"324":1,"341":1,"342":1,"345":1,"348":1,"350":1,"353":1,"363":1,"375":1,"382":2,"384":2,"389":1,"390":2,"397":1,"399":1,"413":1,"423":1,"426":1,"429":1,"434":1,"497":3,"499":2,"501":1,"506":1,"507":4,"508":1,"513":1,"518":1,"524":1,"534":1,"536":1,"537":5,"540":2,"547":1,"549":1,"550":1,"552":1,"554":1,"557":1,"566":1,"572":1,"578":1,"599":1,"609":1,"610":1,"611":2,"613":1,"618":3,"622":1,"651":1,"664":3,"667":1,"670":1,"698":2,"699":1,"700":1,"707":1,"709":1,"736":3,"758":1,"761":1,"768":1,"779":1,"782":9,"783":1,"784":5,"785":1,"793":1,"794":1,"808":2,"809":6,"810":10,"811":4,"812":2,"813":6,"814":1,"815":2,"819":2,"821":1,"833":1,"836":1,"838":1,"841":1,"855":3,"856":1,"868":1,"871":1,"876":2,"879":1,"881":7,"882":3,"883":1,"884":1,"896":1,"905":1,"909":1,"923":1,"926":7,"927":1,"946":1,"954":3,"959":1,"960":1,"962":1,"963":1,"972":1,"976":1,"986":1,"988":2,"989":3,"994":1,"996":2,"1014":1,"1021":2,"1030":1,"1050":2,"1052":1,"1065":1,"1067":2,"1068":1,"1081":1,"1089":2,"1095":1,"1100":1,"1116":1,"1117":1,"1119":1,"1123":1,"1124":2,"1125":2,"1139":1,"1140":5,"1141":1,"1142":1,"1145":1,"1147":1,"1150":5,"1151":1,"1152":4,"1154":2,"1155":2,"1161":3,"1162":2,"1167":1,"1169":3,"1171":1,"1175":1,"1184":1,"1199":64,"1207":1,"1212":7,"1216":1,"1226":1,"1227":1,"1229":5,"1230":1,"1231":2,"1232":1,"1233":1,"1237":14,"1240":1,"1246":1,"1247":9,"1249":1,"1252":1,"1259":22,"1261":1,"1263":2,"1264":1,"1265":4,"1267":1,"1269":1,"1270":25,"1271":10,"1274":4,"1275":2,"1278":2,"1286":60,"1290":1,"1293":3,"1300":39,"1301":10,"1303":1,"1309":20,"1311":24,"1317":1,"1318":1,"1322":1,"1323":39,"1324":206,"1327":1,"1334":2,"1339":18,"1342":1,"1343":2,"1345":2,"1347":1,"1350":57,"1352":1,"1354":1,"1355":1,"1357":1,"1359":71,"1366":1,"1369":4,"1378":1,"1380":17,"1383":4,"1384":2,"1385":1,"1390":2,"1391":5,"1395":204,"1396":121,"1397":3,"1398":1,"1402":82,"1405":2,"1409":3,"1411":1,"1414":2,"1415":41,"1416":6,"1417":87,"1427":3,"1431":2,"1432":2,"1433":1,"1434":1,"1436":116,"1438":13,"1441":2,"1447":2,"1451":1,"1453":1,"1454":2,"1455":4,"1459":1,"1460":1,"1467":1,"1470":1,"1472":1,"1473":1,"1474":1,"1475":2,"1478":1,"1481":1,"1484":1,"1486":1,"1488":2,"1489":1,"1490":1,"1491":3,"1496":4,"1499":1,"1525":3,"1526":8,"1535":2,"1541":1,"1542":1,"1547":1,"1549":2,"1555":1,"1556":10,"1561":3,"1562":3,"1563":1,"1572":2,"1577":8,"1586":2,"1588":1,"1591":9,"1597":1,"1599":1,"1600":3,"1607":2,"1608":3,"1631":3,"1632":2,"1633":3,"1638":1,"1641":1,"1643":5,"1645":1,"1651":3,"1652":1,"1654":1,"1660":2,"1665":1,"1669":1,"1671":2,"1677":1,"1686":1,"1701":1,"1709":1,"1715":2,"1741":2,"1742":1,"1747":5,"1748":6,"1749":7,"1750":3,"1755":1,"1764":3,"1765":5,"1769":9,"1773":3,"1775":1,"1776":2,"1796":1,"1805":1,"1808":1,"1809":2,"1810":1,"1820":1,"1822":2,"1832":1,"1840":3,"1844":1,"1846":1,"1849":1,"1851":1,"1852":1,"1862":1,"1871":1,"1876":1,"1877":1,"1883":1,"1890":1,"1916":1,"1918":3,"1920":1,"1937":1,"1940":1,"1948":2,"1949":3,"1950":2,"1971":2,"1972":1,"1975":2,"1976":1,"1979":1,"1984":1,"1995":1,"2000":2,"2001":1,"2002":3,"2013":1,"2032":2,"2060":1,"2063":1,"2065":1,"2070":2,"2075":1,"2076":3,"2078":1,"2079":1,"2080":2,"2081":1,"2082":1,"2086":1,"2090":1,"2091":3,"2100":1,"2118":2,"2119":1,"2123":1,"2154":1,"2156":1,"2159":1,"2163":1,"2167":4,"2168":6,"2173":1,"2182":1,"2185":1,"2186":1,"2194":1,"2230":1,"2232":5}}],["stateservice",{"3":{"881":1,"884":1,"1436":1,"1526":1,"1591":1}}],["states",{"3":{"0":6,"68":1,"85":1,"91":1,"98":1,"115":1,"130":1,"136":1,"138":1,"141":1,"156":1,"171":1,"195":1,"200":2,"268":1,"301":1,"318":2,"322":1,"341":1,"353":1,"378":1,"392":1,"393":1,"459":1,"465":1,"490":1,"524":1,"529":1,"544":1,"549":1,"555":3,"556":1,"572":1,"582":1,"584":1,"591":1,"609":1,"612":1,"618":4,"627":1,"630":1,"642":1,"643":1,"644":1,"649":1,"650":1,"756":1,"766":1,"782":1,"789":1,"790":1,"794":1,"797":1,"809":1,"827":1,"833":1,"836":1,"838":3,"849":1,"867":1,"869":1,"881":1,"905":1,"914":1,"922":2,"941":1,"946":1,"963":2,"964":1,"967":1,"972":1,"1003":1,"1004":1,"1017":1,"1018":1,"1066":1,"1067":1,"1089":1,"1091":1,"1108":1,"1128":1,"1143":1,"1160":1,"1167":1,"1212":1,"1229":6,"1237":13,"1247":15,"1259":15,"1270":20,"1271":4,"1273":1,"1275":2,"1277":1,"1278":1,"1280":1,"1284":1,"1286":56,"1297":1,"1300":54,"1301":5,"1309":16,"1310":1,"1311":28,"1312":2,"1323":19,"1324":62,"1333":1,"1338":1,"1339":45,"1343":1,"1350":23,"1359":111,"1362":3,"1365":1,"1369":3,"1375":1,"1380":12,"1389":1,"1395":55,"1396":82,"1400":1,"1402":31,"1407":1,"1414":1,"1415":39,"1416":5,"1417":12,"1423":1,"1430":1,"1436":81,"1438":3,"1440":1,"1447":2,"1449":1,"1453":1,"1454":2,"1455":9,"1457":1,"1459":1,"1465":2,"1467":11,"1473":1,"1475":1,"1483":1,"1486":1,"1488":1,"1489":2,"1491":2,"1492":2,"1496":1,"1525":1,"1526":1,"1545":1,"1557":1,"1558":1,"1560":1,"1561":2,"1562":1,"1565":2,"1577":5,"1585":2,"1591":1,"1596":2,"1633":1,"1634":1,"1639":3,"1643":2,"1645":2,"1651":1,"1653":2,"1656":2,"1669":1,"1686":1,"1708":1,"1720":1,"1749":1,"1766":6,"1767":1,"1769":1,"1771":1,"1799":1,"1869":1,"1871":2,"1874":1,"1876":1,"1877":1,"1917":1,"1960":1,"1961":1,"1964":1,"1970":1,"2024":1,"2028":1,"2043":2,"2056":1,"2068":1,"2112":1,"2165":1,"2168":1,"2183":1,"2189":1,"2198":1,"2232":2}}],["status202accepted",{"3":{"1380":1}}],["status201created",{"3":{"1311":1}}],["statusmaxlength",{"3":{"1350":2,"1359":2,"1369":1}}],["statusineligible",{"3":{"1344":2,"1350":1}}],["status500internalservererror",{"3":{"1311":1}}],["status429toomanyrequests",{"3":{"1339":1}}],["status400badrequest",{"3":{"1311":3}}],["status409conflict",{"3":{"1311":1}}],["status499clientclosedrequest",{"3":{"1311":2}}],["statuserrorcodeprefix",{"3":{"1300":1}}],["statuses",{"3":{"318":1,"326":1,"1311":1,"1350":1,"1359":7,"1395":3,"1396":1,"1402":1,"1630":2,"1631":1,"1632":1,"1633":2,"1635":1,"1639":1}}],["statuscriteria",{"2":{"1239":1,"1359":1},"3":{"1239":1,"1247":1,"1359":7}}],["statuscodes",{"3":{"1311":8,"1339":1,"1380":1}}],["statuscodetoerrortype",{"3":{"1300":1}}],["statuscoderesult",{"2":{"159":1,"1909":1,"1911":1},"3":{"157":1,"159":1,"1311":6,"1909":1,"1911":1}}],["statuscode",{"2":{"100":1,"1225":2,"2018":1},"3":{"100":1,"1225":2,"1270":1,"1309":1,"1311":10,"1312":14,"1378":1,"1380":1,"1436":6,"1438":1,"1927":2,"2018":1}}],["statusbucket",{"2":{"1351":1},"3":{"1199":3,"1203":2,"1207":2,"1351":1,"1359":11,"1496":1}}],["status=success",{"3":{"626":1,"1475":1}}],["status",{"0":{"3":1,"9":1,"15":1,"29":1,"37":1,"45":1,"51":1,"57":1,"66":1,"76":1,"84":1,"90":1,"107":1,"115":1,"130":1,"145":1,"155":1,"164":1,"173":1,"184":1,"193":1,"205":1,"212":1,"223":1,"233":1,"241":1,"263":1,"271":1,"279":1,"289":1,"298":1,"308":1,"316":1,"328":1,"339":1,"348":1,"357":1,"368":1,"378":1,"386":1,"395":1,"411":1,"418":1,"432":1,"438":1,"446":1,"457":1,"468":1,"480":1,"497":1,"505":1,"516":1,"526":1,"534":1,"543":1,"554":1,"562":1,"570":1,"581":1,"589":1,"597":1,"607":1,"616":1,"624":1,"638":1,"647":1,"655":1,"663":1,"672":1,"680":1,"688":1,"696":1,"705":1,"713":1,"723":1,"731":1,"739":1,"747":1,"755":1,"764":1,"772":1,"780":1,"788":1,"797":1,"808":1,"817":1,"825":1,"836":1,"844":1,"852":1,"859":1,"867":1,"879":1,"887":1,"895":1,"903":1,"912":1,"920":1,"929":1,"937":1,"944":1,"952":1,"962":1,"970":1,"978":1,"986":1,"994":1,"1002":1,"1010":1,"1016":1,"1024":1,"1032":1,"1040":1,"1048":1,"1057":1,"1065":1,"1073":1,"1081":1,"1089":1,"1098":1,"1106":1,"1114":1,"1122":1,"1131":1,"1139":1,"1149":1,"1159":1,"1166":1,"1173":1,"1182":1,"1766":1,"1873":1},"2":{"537":1,"574":1,"809":1,"813":1,"1594":1,"1633":1,"1749":1,"1808":1,"1812":1,"1926":1,"2167":1,"2168":1},"3":{"0":5,"1":1,"27":1,"42":1,"74":1,"100":2,"107":1,"109":8,"110":2,"111":3,"112":1,"138":1,"139":1,"140":1,"142":1,"150":1,"157":4,"203":1,"225":1,"230":2,"235":1,"261":1,"301":1,"340":1,"341":3,"343":1,"392":1,"403":1,"423":2,"424":1,"434":1,"435":1,"451":1,"458":1,"459":4,"460":1,"461":1,"497":2,"499":1,"502":1,"536":4,"537":1,"538":1,"556":1,"574":1,"591":1,"609":1,"631":1,"633":2,"660":1,"731":1,"741":1,"757":3,"759":1,"761":1,"766":1,"789":3,"790":2,"791":3,"794":1,"802":1,"809":3,"811":1,"812":1,"813":1,"856":1,"882":1,"909":2,"921":1,"1019":1,"1021":2,"1062":1,"1116":1,"1124":1,"1140":1,"1143":1,"1150":1,"1212":2,"1217":2,"1219":1,"1222":1,"1224":1,"1225":1,"1229":13,"1237":1,"1239":1,"1247":3,"1260":1,"1263":1,"1266":1,"1270":3,"1286":4,"1300":18,"1304":2,"1309":27,"1311":43,"1312":15,"1324":14,"1328":1,"1332":1,"1337":2,"1339":19,"1341":1,"1344":7,"1349":1,"1350":43,"1353":4,"1356":1,"1358":2,"1359":66,"1365":1,"1369":4,"1380":3,"1385":3,"1389":2,"1390":1,"1395":38,"1396":28,"1398":2,"1401":1,"1402":49,"1415":4,"1416":1,"1417":9,"1436":17,"1438":5,"1449":1,"1453":1,"1455":5,"1465":1,"1467":5,"1474":4,"1475":1,"1478":1,"1496":2,"1526":3,"1531":1,"1535":1,"1546":1,"1577":5,"1586":1,"1591":1,"1594":1,"1600":1,"1611":1,"1630":7,"1631":6,"1632":4,"1633":4,"1635":5,"1637":1,"1638":1,"1639":10,"1640":6,"1641":1,"1643":1,"1644":1,"1650":1,"1651":3,"1660":1,"1661":1,"1662":1,"1666":1,"1669":1,"1683":3,"1684":1,"1685":1,"1686":13,"1687":1,"1697":1,"1701":1,"1702":1,"1710":1,"1716":1,"1727":10,"1728":3,"1729":2,"1742":1,"1748":3,"1749":10,"1750":4,"1753":1,"1754":2,"1760":1,"1764":5,"1765":14,"1768":3,"1770":1,"1776":1,"1805":5,"1806":2,"1808":1,"1810":1,"1812":1,"1814":2,"1815":1,"1831":1,"1873":1,"1874":1,"1876":2,"1877":1,"1908":2,"1909":3,"1912":1,"1923":1,"1925":2,"1926":1,"1927":7,"1930":6,"1933":4,"1940":1,"1941":1,"1957":1,"1959":1,"2018":1,"2066":2,"2085":1,"2089":1,"2127":1,"2137":1,"2158":1,"2163":1,"2166":4,"2167":3,"2168":2,"2169":3,"2232":3}}],["staywithinthelinecap",{"3":{"1436":3}}],["stayed",{"3":{"148":1,"265":1,"592":1,"766":1,"832":1,"871":1,"1300":1,"1311":1,"1339":2,"1369":1,"1387":1,"1436":1,"1449":1,"1474":1,"1476":1,"1491":1,"2156":1}}],["staying",{"3":{"131":1,"212":1,"499":1,"906":1,"909":1,"1020":1,"1128":1,"1237":1,"1247":1,"1271":1,"1286":2,"1297":1,"1300":2,"1311":3,"1312":1,"1324":1,"1339":1,"1359":1,"1395":1,"1396":3,"1436":5,"1438":2,"1441":1,"1477":1,"1667":1,"2001":1}}],["stay",{"2":{"2043":1},"3":{"0":21,"27":2,"58":1,"62":1,"68":1,"72":1,"78":1,"91":1,"93":1,"96":1,"100":3,"111":1,"117":1,"122":1,"128":1,"138":1,"140":1,"141":1,"142":2,"149":1,"150":1,"186":2,"197":1,"201":1,"209":1,"219":1,"224":1,"265":2,"273":1,"274":1,"286":1,"351":1,"358":1,"362":1,"363":1,"369":1,"395":1,"398":1,"399":1,"401":1,"407":1,"412":1,"413":2,"442":1,"448":1,"460":2,"468":1,"482":2,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"498":2,"500":1,"509":1,"511":1,"518":1,"524":1,"528":1,"544":1,"547":1,"568":1,"590":1,"591":1,"609":3,"611":1,"618":1,"625":1,"649":1,"656":1,"657":1,"681":1,"683":1,"724":1,"735":1,"742":1,"785":1,"790":2,"799":1,"821":2,"827":1,"829":1,"832":1,"833":3,"838":1,"846":1,"863":1,"876":1,"877":1,"922":1,"923":1,"926":2,"939":1,"960":1,"964":2,"980":1,"996":1,"1004":1,"1018":1,"1026":1,"1036":1,"1045":1,"1049":1,"1059":4,"1074":1,"1075":2,"1091":1,"1096":1,"1109":1,"1123":1,"1141":1,"1168":1,"1175":1,"1177":1,"1184":1,"1201":2,"1210":1,"1212":5,"1217":1,"1220":1,"1229":4,"1233":1,"1235":1,"1237":8,"1239":2,"1241":1,"1247":2,"1252":1,"1257":1,"1259":5,"1267":1,"1269":1,"1270":10,"1271":2,"1280":1,"1281":1,"1286":24,"1291":1,"1295":1,"1300":13,"1301":4,"1309":6,"1310":1,"1311":12,"1317":1,"1322":1,"1323":8,"1324":24,"1338":1,"1339":9,"1347":1,"1350":6,"1353":1,"1355":1,"1359":47,"1362":1,"1369":4,"1377":1,"1380":9,"1393":1,"1395":16,"1396":23,"1401":1,"1402":4,"1409":1,"1415":5,"1416":8,"1417":8,"1423":1,"1424":1,"1433":1,"1435":1,"1436":69,"1438":5,"1442":1,"1447":1,"1455":1,"1456":1,"1467":2,"1474":1,"1476":1,"1489":4,"1490":1,"1492":2,"1496":1,"1508":1,"1509":1,"1515":1,"1526":3,"1527":1,"1534":1,"1536":1,"1560":1,"1569":1,"1572":1,"1577":2,"1582":1,"1583":1,"1591":2,"1592":1,"1599":1,"1627":1,"1654":1,"1656":1,"1663":1,"1665":2,"1668":1,"1672":1,"1677":1,"1686":1,"1693":1,"1703":1,"1722":1,"1758":1,"1765":1,"1805":1,"1810":1,"1815":2,"1842":1,"1843":1,"1886":1,"1891":1,"1898":2,"1916":1,"1920":1,"1933":1,"1967":1,"1971":1,"1977":1,"1991":1,"2000":1,"2001":1,"2010":1,"2028":1,"2042":1,"2043":1,"2047":1,"2058":1,"2083":3,"2094":1,"2104":1,"2120":1,"2131":1,"2135":1,"2139":1,"2141":1,"2150":2,"2158":1,"2159":1,"2167":1,"2169":1,"2179":1,"2202":1,"2232":3}}],["staysinsidetheenginestrategies",{"3":{"1436":2}}],["stays",{"0":{"2026":1},"1":{"2116":1,"2117":1,"2118":1,"2119":1,"2120":1,"2121":1,"2122":1,"2123":1},"3":{"0":26,"15":1,"20":1,"31":2,"38":1,"39":2,"54":2,"59":3,"62":1,"72":1,"91":1,"92":2,"96":1,"100":1,"109":1,"124":1,"134":1,"135":1,"139":1,"140":1,"141":1,"152":1,"158":1,"173":1,"178":2,"182":1,"184":1,"188":1,"193":1,"195":2,"201":1,"207":1,"219":1,"225":1,"229":1,"230":1,"233":1,"235":3,"237":1,"244":1,"250":1,"258":1,"260":2,"265":2,"295":1,"312":1,"313":1,"314":1,"317":1,"324":1,"342":3,"345":1,"350":1,"357":1,"359":3,"361":1,"362":1,"365":1,"380":1,"384":1,"392":1,"396":1,"398":1,"402":1,"404":1,"408":1,"413":2,"414":1,"425":1,"434":1,"448":1,"449":1,"451":1,"458":2,"460":1,"486":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"499":1,"501":1,"508":1,"528":1,"530":1,"538":1,"539":3,"545":1,"547":1,"549":1,"556":1,"591":1,"592":1,"607":1,"609":1,"617":1,"627":1,"633":1,"640":2,"644":1,"657":1,"668":1,"682":1,"683":1,"684":1,"692":1,"698":1,"711":1,"719":1,"725":1,"733":1,"741":1,"749":1,"751":1,"758":1,"774":1,"790":1,"793":1,"797":1,"799":1,"803":1,"810":1,"827":3,"830":1,"834":1,"836":1,"837":1,"838":1,"849":1,"853":1,"857":1,"863":1,"870":1,"873":1,"876":1,"897":1,"905":1,"914":6,"917":1,"926":2,"929":1,"946":1,"950":1,"954":1,"957":1,"964":2,"966":1,"973":1,"990":1,"1013":1,"1018":2,"1026":1,"1034":2,"1037":1,"1048":2,"1050":1,"1059":1,"1060":1,"1075":1,"1093":3,"1094":3,"1096":1,"1100":1,"1116":1,"1133":1,"1137":1,"1149":1,"1151":3,"1163":1,"1175":1,"1182":1,"1184":1,"1185":1,"1186":1,"1187":2,"1188":1,"1193":1,"1212":10,"1215":1,"1220":1,"1229":2,"1231":1,"1234":1,"1237":9,"1239":1,"1240":1,"1243":1,"1247":8,"1252":1,"1254":1,"1256":1,"1259":12,"1263":1,"1264":1,"1265":1,"1266":1,"1270":10,"1271":3,"1274":1,"1275":2,"1276":3,"1281":1,"1286":39,"1288":1,"1289":1,"1293":2,"1300":24,"1301":7,"1304":2,"1306":1,"1309":12,"1310":2,"1311":23,"1312":2,"1316":1,"1323":26,"1324":50,"1328":1,"1329":1,"1332":1,"1333":2,"1338":1,"1339":26,"1350":13,"1352":1,"1353":1,"1355":1,"1358":1,"1359":70,"1360":1,"1366":1,"1368":1,"1369":11,"1379":1,"1380":13,"1385":1,"1391":1,"1395":41,"1396":51,"1399":1,"1402":20,"1406":2,"1407":1,"1408":2,"1409":1,"1410":3,"1415":31,"1416":13,"1417":28,"1420":1,"1423":1,"1427":4,"1431":2,"1433":1,"1434":1,"1436":84,"1438":6,"1441":1,"1442":1,"1443":2,"1447":6,"1454":2,"1455":3,"1456":1,"1458":1,"1460":1,"1467":10,"1474":1,"1486":2,"1489":3,"1490":2,"1492":1,"1496":10,"1534":2,"1568":1,"1571":1,"1577":5,"1585":5,"1586":1,"1592":1,"1599":2,"1627":2,"1629":1,"1630":1,"1631":1,"1641":4,"1651":1,"1653":1,"1660":1,"1661":2,"1662":1,"1667":1,"1668":1,"1669":1,"1677":1,"1684":2,"1685":2,"1686":2,"1721":1,"1727":3,"1729":1,"1764":1,"1765":1,"1767":1,"1770":1,"1781":1,"1785":1,"1792":1,"1799":1,"1805":2,"1806":1,"1810":2,"1815":1,"1818":1,"1823":2,"1830":1,"1837":1,"1838":1,"1842":1,"1858":3,"1859":1,"1865":1,"1871":1,"1874":1,"1881":1,"1891":1,"1896":1,"1902":2,"1908":1,"1915":1,"1920":1,"1929":1,"1933":1,"1934":1,"1940":1,"1946":1,"1959":1,"1963":1,"1967":1,"1968":1,"1972":1,"1978":1,"1979":1,"1982":1,"1984":1,"1985":3,"1988":1,"2000":1,"2001":1,"2002":1,"2006":1,"2015":1,"2025":1,"2028":1,"2044":1,"2048":1,"2055":3,"2061":2,"2063":1,"2064":2,"2089":1,"2096":1,"2099":1,"2104":1,"2107":2,"2112":1,"2116":1,"2120":1,"2122":2,"2142":1,"2145":1,"2151":1,"2155":1,"2156":1,"2168":1,"2175":1,"2199":2,"2210":1,"2225":1,"2232":3}}],["mcp",{"3":{"2239":2}}],["mcpd",{"3":{"2224":1}}],["mcts",{"3":{"2224":1}}],["mct001",{"3":{"1417":2}}],["mcsa",{"3":{"2224":2}}],["mcr",{"3":{"640":1,"868":2,"1339":1,"1441":1,"1445":2,"1465":1}}],["mvp",{"3":{"1395":3}}],["mvc",{"2":{"300":1,"2137":1},"3":{"0":4,"157":1,"299":1,"300":1,"301":1,"448":1,"583":1,"657":1,"725":1,"726":1,"827":1,"962":1,"1049":1,"1050":3,"1051":2,"1052":1,"1213":2,"1235":2,"1237":5,"1300":7,"1308":1,"1309":10,"1311":40,"1323":1,"1379":1,"1380":2,"1396":7,"1402":2,"1415":3,"1436":13,"1438":1,"1447":1,"1666":1,"1810":3,"2131":2,"2137":2,"2220":1,"2224":1}}],["m119",{"3":{"1496":1}}],["m135",{"3":{"1496":1}}],["m136",{"3":{"1496":1}}],["m137",{"3":{"1496":1}}],["m139",{"3":{"1350":2,"1496":1}}],["m140",{"3":{"1496":1}}],["m148",{"3":{"1415":1,"1496":1}}],["m146",{"3":{"1398":1,"1496":1}}],["m143",{"3":{"1350":1,"1496":1}}],["m162",{"3":{"1395":1}}],["m161",{"3":{"1350":1,"1496":1}}],["m8",{"3":{"1323":1}}],["mgmt",{"3":{"1192":1}}],["m48",{"3":{"1438":1}}],["m4",{"3":{"1012":2,"1014":2,"1089":1,"1090":1,"1094":1,"1095":1,"1526":5,"1531":9,"1534":1,"1577":2,"1582":1,"1588":1,"1591":4,"1592":1,"1595":11,"1600":42}}],["m3",{"3":{"1012":2,"1014":1,"1526":2,"1531":5,"1577":2,"1589":2,"1591":1,"1592":1,"1595":4,"1596":1,"1600":11}}],["m2",{"3":{"1012":1,"1090":1}}],["mm",{"3":{"305":1,"1018":1,"1300":2,"1350":2,"1359":1,"1369":2,"1416":2,"1436":6,"1492":1,"1609":1,"1710":1,"1743":1,"2138":1}}],["mmcacommondomain",{"3":{"1436":2}}],["mmcaclientconfigbootstraptests",{"3":{"1199":1,"1207":3,"1324":2}}],["mmcaclientconfigbootstrap",{"3":{"1184":1,"1199":3,"1203":1,"1207":2,"1324":5,"1416":1,"1496":1}}],["mmcaculturebootstrap",{"2":{"265":1,"1526":1,"1653":1,"2083":1,"2086":1},"3":{"265":2,"1199":1,"1203":1,"1207":2,"1311":3,"1324":17,"1416":1,"1417":1,"1496":1,"1526":1,"1653":1,"2083":2,"2086":1}}],["mmcastore",{"2":{"764":1,"1471":1},"3":{"764":1,"766":2,"1471":1,"1591":1,"1600":2}}],["mmca0002",{"2":{"576":1},"3":{"576":1,"1489":1}}],["mmca0001",{"2":{"576":1},"3":{"576":1,"1489":1}}],["mmcalayerenforcement",{"2":{"576":1}}],["mmcaadcuiweb",{"3":{"1456":1,"1490":1}}],["mmcaadc",{"3":{"1456":2,"1490":1}}],["mmcaauthhandoff",{"3":{"1324":3}}],["mmcaauthcookie",{"3":{"1324":6}}],["mmcaauthsession",{"2":{"507":1},"3":{"507":1,"1324":2}}],["mmcaapplicationpipelinebuilder",{"2":{"1262":1,"1313":1,"1316":1,"1662":1},"3":{"122":1,"1199":1,"1203":1,"1207":2,"1262":1,"1313":1,"1316":3,"1323":7,"1662":1}}],["mmcaopenapidesigntime",{"2":{"452":1},"3":{"452":1,"1311":1}}],["mmcagatewaysection",{"3":{"1326":1,"1339":1,"1444":1}}],["mmcagatewaytests",{"3":{"180":1,"827":1,"833":1}}],["mmcagateway",{"2":{"836":1,"841":1},"3":{"0":2,"827":1,"833":1,"836":1,"838":3,"841":1,"1338":2,"1339":12,"1436":1,"1438":2,"1447":4,"1656":1}}],["mmcagatewayhardeningtestsbasetests",{"3":{"1199":1,"1207":4,"1436":1,"1438":1,"1487":1}}],["mmcagatewayhardeningtestsbase",{"2":{"572":1,"578":1,"2055":1},"3":{"0":1,"572":8,"578":3,"1199":4,"1207":2,"1436":5,"1438":2,"1488":4,"1493":1,"1496":1,"2055":1}}],["mmcagenerateopenapidocument",{"2":{"451":1,"452":1,"1455":1},"3":{"0":1,"451":1,"452":1,"1311":1,"1455":1}}],["mmcathemeprovidersprerendertests",{"3":{"1199":1,"1207":2,"1324":1}}],["mmcathemeproviderstests",{"3":{"1199":2,"1207":2,"1324":1}}],["mmcathemeproviders",{"2":{"273":1,"275":1,"276":1,"2075":1,"2083":1},"3":{"0":2,"273":5,"275":1,"276":2,"1324":9,"1433":1,"1436":2,"1526":3,"2075":2,"2083":2}}],["mmcatheme",{"2":{"272":1,"273":1,"621":1,"2075":2,"2080":1,"2083":1,"2086":1},"3":{"0":2,"272":2,"273":2,"618":1,"621":1,"622":1,"1199":3,"1203":1,"1207":2,"1324":13,"1436":1,"1496":1,"1526":3,"1577":2,"1586":1,"2075":4,"2080":1,"2083":3,"2086":1}}],["mmca",{"0":{"95":1,"465":1,"1441":1,"1442":1,"1443":1,"1444":1,"1453":1,"1454":1,"1455":1,"1456":1,"1457":1,"1458":1,"1602":1,"1603":1,"1604":1,"1679":1,"1727":1,"1728":1,"1730":2,"1790":1,"1793":1,"1799":1,"1802":1,"1805":1,"1807":1,"1810":1,"1812":1,"1815":1,"1817":1,"1827":1,"1834":1,"1838":1,"1846":1,"1849":1,"1853":1,"1862":1,"1865":1,"1868":1,"1871":1,"1877":1,"1886":1,"1889":1,"1892":1,"1895":1,"1899":1,"1902":1,"1905":1,"1912":1,"1924":1,"1927":1,"1930":1,"1941":1,"1944":1,"1950":1,"1959":1,"1962":1,"1965":1,"1968":1,"1972":1,"1975":1,"1979":1,"1982":1,"1985":1,"1988":1,"1991":1,"1994":1,"1995":1,"1997":1,"2000":1,"2002":1,"2014":1,"2022":1,"2028":1,"2038":1,"2049":1,"2060":1,"2063":1,"2069":1,"2080":1,"2083":1,"2086":1,"2108":1,"2117":1,"2123":1,"2126":1,"2128":1,"2131":1,"2133":1,"2136":1,"2139":1,"2142":1,"2146":1,"2149":1,"2153":1,"2156":1,"2160":1,"2163":1,"2169":1,"2172":1,"2180":1,"2184":1,"2193":1,"2196":1,"2203":1,"2214":1,"2221":1,"2227":1},"1":{"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1,"1088":1,"1089":1,"1090":1,"1091":1,"1092":1,"1093":1,"1094":1,"1095":1,"1096":1,"1190":2,"1191":2,"1192":2,"1193":2,"1194":2,"1195":2,"1502":1,"1503":1,"1504":1,"1505":1,"1506":1,"1507":1,"1508":1,"1509":1,"1510":1,"1511":1,"1512":1,"1513":1,"1514":1,"1515":1,"1516":1,"1517":1,"1522":1,"1523":1,"1524":1,"1525":1,"1526":1,"1527":1,"1528":1,"1529":1,"1530":1,"1531":1,"1532":1,"1533":1,"1534":1,"1535":1,"1573":1,"1574":1,"1575":1,"1576":1,"1577":1,"1578":1,"1579":1,"1580":1,"1581":1,"1582":1,"1583":1,"1584":1,"1585":1,"1586":1,"1587":1,"1588":1,"1589":1,"1590":1,"1591":1,"1592":1,"1593":1,"1594":1,"1595":1,"1596":1,"1597":1,"1598":1,"1599":1,"1600":1,"1646":1,"1647":1,"1648":1,"1649":1,"1650":1,"1651":1,"1652":1,"1653":1,"1654":1,"1655":1,"1656":1,"1657":1,"1658":1,"1659":1,"1660":1,"1661":1,"1662":1,"1663":1,"1664":1,"1665":1,"1666":1,"1667":1,"1668":1,"1669":1,"1670":1,"1671":1,"1672":1,"1673":1,"1674":1,"1675":1,"1681":1,"1682":1,"1683":1,"1684":1,"1685":1,"1686":1,"1687":1,"1688":1,"1689":1,"1690":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1698":1,"1699":1,"1700":1,"1701":1,"1702":1,"1703":1,"1704":1,"1705":1,"1717":1,"1718":1,"1719":1,"1720":1,"1721":1,"1722":1,"1723":1,"1724":1,"1725":1,"1726":1,"1727":1,"1728":1,"1729":1,"1730":1,"1731":1,"1745":1,"1746":1,"1747":1,"1748":1,"1749":1,"1750":1,"1751":1,"1752":1,"1753":1,"1754":1,"1755":1,"1762":1,"1763":1,"1764":1,"1765":1,"1766":1,"1767":1,"1768":1,"1769":1,"1770":1,"1771":1,"1772":1,"1773":1,"1774":1,"1775":1,"1776":1,"1777":1,"1778":1,"1779":1,"2154":1,"2155":1,"2156":1,"2157":1,"2158":1,"2159":1,"2160":1,"2204":1,"2205":1,"2206":1,"2207":1,"2208":1,"2209":1,"2210":1,"2211":1,"2226":1,"2227":1,"2228":1,"2229":1,"2230":1,"2231":1,"2232":1,"2233":1,"2234":1,"2235":1},"2":{"24":1,"39":3,"53":1,"58":1,"59":2,"61":2,"62":3,"67":1,"68":1,"70":1,"78":1,"80":3,"85":1,"95":1,"99":1,"100":1,"107":2,"109":1,"111":1,"117":1,"124":1,"126":1,"130":1,"132":2,"135":2,"137":1,"138":1,"139":3,"140":1,"141":6,"142":6,"145":2,"147":1,"150":1,"152":2,"160":1,"180":1,"186":1,"190":1,"207":4,"214":2,"216":1,"225":2,"229":1,"241":2,"247":1,"250":1,"252":1,"255":2,"258":1,"259":2,"261":1,"265":1,"273":1,"281":2,"310":1,"359":2,"370":1,"373":1,"388":1,"395":6,"400":7,"401":2,"404":2,"406":1,"408":1,"412":1,"436":1,"460":1,"481":1,"482":3,"486":3,"487":3,"488":3,"489":3,"490":3,"491":3,"492":3,"493":6,"494":3,"495":3,"509":6,"511":3,"514":1,"526":2,"527":2,"528":3,"529":1,"530":1,"558":1,"564":2,"572":2,"576":1,"577":1,"578":2,"591":1,"599":1,"617":1,"618":1,"632":1,"633":1,"634":3,"635":1,"636":1,"638":3,"648":1,"649":1,"667":1,"681":1,"683":1,"684":1,"686":2,"731":1,"733":1,"780":1,"789":1,"821":1,"822":3,"823":3,"825":2,"826":3,"832":1,"836":1,"838":1,"840":1,"861":1,"880":1,"897":2,"899":1,"906":1,"912":1,"913":1,"914":2,"917":1,"936":1,"938":2,"939":7,"941":2,"942":1,"953":1,"954":1,"957":1,"963":1,"966":1,"982":1,"984":1,"1004":2,"1012":1,"1016":1,"1036":3,"1037":1,"1044":1,"1051":1,"1057":1,"1058":1,"1062":1,"1066":1,"1067":2,"1068":3,"1069":3,"1074":1,"1089":4,"1090":1,"1091":1,"1093":1,"1124":2,"1161":2,"1191":2,"1194":2,"1195":1,"1200":2,"1201":2,"1203":4,"1207":10,"1211":1,"1213":5,"1214":1,"1215":4,"1217":1,"1222":1,"1229":3,"1230":2,"1236":1,"1237":3,"1247":3,"1256":1,"1259":6,"1264":1,"1268":1,"1270":6,"1271":2,"1275":1,"1286":5,"1287":2,"1300":2,"1301":3,"1309":4,"1310":2,"1317":1,"1321":1,"1323":5,"1324":1,"1325":6,"1326":1,"1327":1,"1328":2,"1329":1,"1330":1,"1333":1,"1336":1,"1337":1,"1339":1,"1340":1,"1341":3,"1350":6,"1351":1,"1359":5,"1363":1,"1366":1,"1369":2,"1370":3,"1378":1,"1379":2,"1380":7,"1381":1,"1386":1,"1395":6,"1396":11,"1402":6,"1404":6,"1414":1,"1415":4,"1416":3,"1417":9,"1418":4,"1422":2,"1425":4,"1426":3,"1427":1,"1428":1,"1430":3,"1434":3,"1435":1,"1436":2,"1437":12,"1438":3,"1439":1,"1442":1,"1445":8,"1449":1,"1451":1,"1452":1,"1453":3,"1454":2,"1455":2,"1456":1,"1459":1,"1460":1,"1471":2,"1476":1,"1486":10,"1487":9,"1488":1,"1490":6,"1492":3,"1494":1,"1496":4,"1497":2,"1500":1,"1503":10,"1509":2,"1512":1,"1513":2,"1523":1,"1524":1,"1525":1,"1526":2,"1527":2,"1531":2,"1534":1,"1535":1,"1576":1,"1577":1,"1578":1,"1585":3,"1591":1,"1599":1,"1602":1,"1605":1,"1611":2,"1615":4,"1619":2,"1639":1,"1642":1,"1643":2,"1648":1,"1649":1,"1650":5,"1653":2,"1654":1,"1656":2,"1660":12,"1661":2,"1675":1,"1677":1,"1681":1,"1682":1,"1684":1,"1687":1,"1690":1,"1693":2,"1702":4,"1711":1,"1726":1,"1730":1,"1732":1,"1739":1,"1741":1,"1761":2,"1775":2,"1781":1,"1782":2,"1787":1,"1790":1,"1793":1,"1805":1,"1807":1,"1812":1,"1815":1,"1817":1,"1827":1,"1828":1,"1829":1,"1834":1,"1843":1,"1846":1,"1853":1,"1862":1,"1865":1,"1868":1,"1877":1,"1886":1,"1892":1,"1895":1,"1896":1,"1899":1,"1902":2,"1905":1,"1912":1,"1919":1,"1922":1,"1924":1,"1930":1,"1935":1,"1941":1,"1950":1,"1953":1,"1959":1,"1962":1,"1965":1,"1972":1,"1975":2,"1979":1,"1985":1,"1991":1,"1997":1,"2002":1,"2004":3,"2006":9,"2008":1,"2013":8,"2014":3,"2016":1,"2022":1,"2024":1,"2028":2,"2030":1,"2038":1,"2042":1,"2044":1,"2046":1,"2049":1,"2055":2,"2056":1,"2057":1,"2059":2,"2060":1,"2063":1,"2070":1,"2071":2,"2077":1,"2080":1,"2086":1,"2100":2,"2101":1,"2108":1,"2111":1,"2115":1,"2116":1,"2120":3,"2123":1,"2128":1,"2131":1,"2133":1,"2139":1,"2142":1,"2146":1,"2149":1,"2150":1,"2151":1,"2152":1,"2153":1,"2156":1,"2157":3,"2160":9,"2169":1,"2172":1,"2176":3,"2177":4,"2178":1,"2180":2,"2191":1,"2193":2,"2203":1,"2211":1},"3":{"0":139,"5":1,"11":1,"15":4,"17":4,"18":1,"19":3,"22":2,"23":1,"24":11,"25":8,"26":19,"27":24,"31":5,"34":2,"39":9,"42":4,"43":4,"46":1,"53":3,"54":2,"58":1,"59":17,"60":2,"61":4,"62":11,"63":7,"66":1,"67":1,"68":8,"70":5,"71":4,"72":14,"73":5,"74":14,"76":2,"78":1,"79":2,"80":14,"81":9,"82":4,"85":2,"93":8,"94":6,"95":17,"96":11,"97":2,"98":10,"99":3,"100":5,"103":6,"104":2,"107":2,"109":46,"111":6,"117":14,"121":15,"122":5,"123":8,"124":1,"125":10,"126":7,"127":4,"128":17,"130":6,"132":2,"135":26,"136":6,"137":5,"138":5,"139":6,"140":3,"141":7,"142":14,"145":3,"146":3,"147":19,"149":6,"150":13,"151":2,"152":9,"157":5,"160":8,"161":6,"164":1,"166":10,"168":1,"170":3,"171":3,"173":1,"175":7,"178":6,"179":12,"180":19,"181":4,"184":1,"186":27,"189":10,"190":11,"193":3,"195":21,"196":4,"198":6,"200":4,"201":3,"202":18,"203":4,"207":5,"208":1,"214":4,"216":5,"217":6,"218":12,"219":8,"220":10,"225":9,"228":8,"229":1,"230":18,"231":8,"235":24,"237":2,"238":1,"241":2,"243":44,"245":22,"246":14,"247":1,"250":2,"251":1,"252":3,"254":1,"255":11,"256":8,"257":1,"258":8,"259":15,"260":9,"261":5,"265":22,"268":2,"272":1,"273":4,"275":1,"281":3,"283":4,"284":4,"285":6,"290":2,"291":4,"295":24,"296":2,"300":4,"301":1,"303":8,"305":14,"306":8,"310":12,"312":1,"314":4,"318":20,"324":1,"325":10,"326":2,"328":1,"330":8,"331":1,"333":10,"336":1,"337":4,"341":37,"342":2,"343":6,"344":12,"345":4,"350":11,"352":8,"353":14,"359":9,"365":2,"366":1,"368":1,"369":2,"370":5,"373":6,"374":4,"375":5,"384":14,"387":4,"388":7,"390":12,"391":14,"392":8,"395":8,"397":13,"400":16,"401":19,"402":4,"403":9,"404":7,"406":1,"407":12,"408":8,"409":8,"412":4,"413":18,"415":3,"416":2,"418":27,"419":1,"420":3,"422":4,"423":11,"424":7,"425":7,"426":19,"427":4,"428":18,"429":12,"430":7,"434":3,"436":3,"442":2,"443":4,"444":6,"446":2,"448":24,"450":3,"451":3,"452":17,"453":2,"454":1,"457":1,"459":27,"460":1,"463":15,"464":10,"465":10,"466":5,"469":6,"470":31,"472":1,"475":28,"476":34,"477":34,"478":6,"481":2,"482":6,"483":2,"484":2,"486":27,"487":16,"488":10,"489":17,"490":24,"491":22,"492":22,"493":27,"494":12,"495":15,"497":20,"498":1,"499":10,"502":5,"507":44,"509":12,"511":15,"512":2,"513":10,"514":6,"522":4,"523":6,"524":4,"526":4,"527":7,"528":7,"529":1,"530":3,"531":4,"532":1,"535":2,"536":23,"538":3,"539":7,"540":3,"543":6,"544":2,"545":57,"547":5,"549":6,"550":7,"551":7,"552":16,"556":73,"558":6,"559":2,"562":3,"563":10,"564":45,"566":11,"567":3,"570":2,"572":83,"575":6,"576":12,"577":16,"578":12,"579":1,"582":1,"583":77,"585":26,"586":12,"587":2,"589":2,"590":1,"591":20,"592":2,"593":6,"594":5,"597":1,"599":69,"600":8,"601":14,"602":3,"603":8,"604":3,"607":1,"608":1,"609":78,"611":11,"612":3,"613":7,"616":2,"617":1,"618":21,"620":1,"621":1,"624":10,"625":13,"626":75,"627":3,"628":10,"629":12,"631":4,"632":5,"633":23,"634":8,"635":8,"636":4,"638":4,"640":46,"642":12,"643":3,"644":8,"648":1,"649":43,"651":5,"652":2,"656":2,"657":28,"659":1,"664":2,"665":24,"667":1,"668":11,"669":10,"674":92,"675":6,"676":22,"677":1,"681":3,"682":6,"683":1,"684":1,"685":1,"686":2,"688":1,"689":2,"690":5,"691":2,"692":2,"693":1,"696":3,"697":3,"698":33,"700":3,"702":19,"703":13,"705":1,"706":2,"707":8,"713":1,"715":10,"718":2,"719":5,"720":8,"723":1,"724":8,"725":31,"727":7,"728":5,"731":1,"732":2,"733":16,"736":8,"739":1,"740":4,"741":10,"742":1,"743":2,"744":4,"749":38,"751":8,"756":6,"757":35,"758":3,"759":8,"760":4,"761":5,"765":5,"766":51,"768":8,"769":3,"774":41,"776":7,"780":2,"782":48,"784":4,"789":7,"790":21,"792":7,"793":12,"794":8,"798":20,"799":14,"803":6,"804":7,"805":4,"806":2,"808":1,"809":7,"810":1,"813":7,"814":2,"818":2,"819":34,"821":1,"822":8,"823":3,"825":9,"826":20,"827":58,"830":14,"831":10,"832":26,"833":32,"836":1,"837":16,"838":29,"839":6,"840":1,"841":6,"846":5,"849":5,"850":2,"853":2,"854":15,"855":2,"857":12,"860":1,"861":18,"863":3,"864":1,"865":1,"868":13,"869":31,"870":8,"872":4,"873":8,"875":4,"876":28,"877":13,"880":3,"881":43,"883":4,"884":3,"888":4,"889":2,"890":2,"891":7,"892":2,"897":23,"899":1,"900":4,"904":2,"905":22,"906":14,"907":1,"908":6,"912":1,"913":27,"914":16,"915":4,"916":1,"917":6,"921":2,"922":9,"923":1,"924":7,"926":20,"927":1,"930":1,"931":7,"932":1,"936":1,"938":2,"939":12,"941":2,"942":1,"945":1,"946":21,"950":1,"953":1,"954":59,"955":1,"956":10,"957":1,"958":9,"959":10,"960":15,"963":9,"964":18,"966":3,"971":11,"972":21,"973":1,"974":5,"975":7,"979":15,"980":21,"982":3,"983":1,"984":1,"986":2,"987":4,"988":15,"990":3,"991":2,"994":2,"995":9,"996":13,"998":3,"999":7,"1003":5,"1004":36,"1006":6,"1007":4,"1010":5,"1012":14,"1013":1,"1016":3,"1017":7,"1018":24,"1021":2,"1022":1,"1025":11,"1026":28,"1028":2,"1029":2,"1034":26,"1036":3,"1037":6,"1041":8,"1042":38,"1043":3,"1044":8,"1045":5,"1049":10,"1050":49,"1051":1,"1052":6,"1054":8,"1055":6,"1057":2,"1058":10,"1059":57,"1062":4,"1065":2,"1066":12,"1067":25,"1068":5,"1069":13,"1070":5,"1073":4,"1074":24,"1075":6,"1078":6,"1082":12,"1083":13,"1085":5,"1086":4,"1088":1,"1089":11,"1090":7,"1091":48,"1093":5,"1094":6,"1095":5,"1099":4,"1100":13,"1103":6,"1108":15,"1116":39,"1117":1,"1119":5,"1122":1,"1123":11,"1124":46,"1125":9,"1126":7,"1127":4,"1128":4,"1129":2,"1132":4,"1133":26,"1135":2,"1136":4,"1137":1,"1140":18,"1142":3,"1150":26,"1154":2,"1156":2,"1160":7,"1161":17,"1163":1,"1164":1,"1168":45,"1170":1,"1171":1,"1173":1,"1174":4,"1175":54,"1180":1,"1182":3,"1183":8,"1184":39,"1185":3,"1186":4,"1187":2,"1189":1,"1190":4,"1191":9,"1192":1,"1194":2,"1195":3,"1199":5234,"1200":3,"1201":3,"1203":2371,"1204":6,"1206":110,"1207":16206,"1208":236,"1209":138,"1211":13,"1212":40,"1213":9,"1214":11,"1215":7,"1217":1,"1219":6,"1220":2,"1222":7,"1223":4,"1224":2,"1225":8,"1226":4,"1227":6,"1228":4,"1229":129,"1230":4,"1231":12,"1232":10,"1233":14,"1234":24,"1235":4,"1236":19,"1237":372,"1238":1,"1239":26,"1240":8,"1241":12,"1242":6,"1243":10,"1244":5,"1245":2,"1246":6,"1247":348,"1249":8,"1250":1,"1251":3,"1252":4,"1253":1,"1254":4,"1255":1,"1256":3,"1257":10,"1258":5,"1259":289,"1260":1,"1261":4,"1262":21,"1263":4,"1264":25,"1265":8,"1266":5,"1267":10,"1268":4,"1269":4,"1270":558,"1271":215,"1273":10,"1274":4,"1275":5,"1276":16,"1277":8,"1278":18,"1279":18,"1280":2,"1281":7,"1282":2,"1283":4,"1284":4,"1285":14,"1286":1670,"1287":2,"1288":16,"1289":19,"1290":22,"1291":24,"1292":12,"1293":18,"1294":18,"1295":12,"1296":10,"1297":60,"1298":14,"1299":4,"1300":2345,"1301":163,"1302":14,"1303":17,"1304":20,"1305":12,"1306":16,"1307":32,"1308":16,"1309":511,"1310":140,"1311":989,"1312":164,"1313":1,"1314":6,"1315":10,"1316":24,"1317":23,"1318":19,"1319":6,"1320":57,"1321":5,"1322":29,"1323":907,"1324":1729,"1325":28,"1326":16,"1327":9,"1328":11,"1329":3,"1330":11,"1331":10,"1332":34,"1333":23,"1334":20,"1335":12,"1336":15,"1337":14,"1338":22,"1339":723,"1340":2,"1341":21,"1342":24,"1344":15,"1345":10,"1346":2,"1347":32,"1348":10,"1349":20,"1350":826,"1351":1,"1352":19,"1353":21,"1354":10,"1355":7,"1356":4,"1357":3,"1358":3,"1359":3249,"1361":4,"1362":4,"1363":6,"1364":5,"1365":2,"1366":7,"1367":3,"1368":4,"1369":215,"1370":4,"1371":14,"1372":4,"1373":8,"1374":2,"1376":2,"1377":14,"1378":17,"1379":6,"1380":489,"1381":3,"1384":2,"1386":1,"1387":2,"1389":2,"1390":4,"1395":1050,"1396":1492,"1397":3,"1398":3,"1399":2,"1400":4,"1401":5,"1402":473,"1403":4,"1404":24,"1405":3,"1406":15,"1407":3,"1408":1,"1409":3,"1410":6,"1411":1,"1412":5,"1413":8,"1414":14,"1415":950,"1416":282,"1417":897,"1418":5,"1419":4,"1420":12,"1421":2,"1422":8,"1423":10,"1424":10,"1425":11,"1426":7,"1427":194,"1428":13,"1429":10,"1430":19,"1431":45,"1432":9,"1433":23,"1434":13,"1435":23,"1436":4107,"1437":139,"1438":207,"1439":3,"1440":3,"1441":23,"1442":20,"1443":32,"1444":7,"1445":21,"1446":1,"1447":14,"1449":11,"1450":1,"1451":4,"1452":4,"1453":15,"1454":12,"1455":34,"1456":4,"1457":2,"1458":1,"1459":32,"1460":16,"1461":3,"1462":3,"1464":14,"1465":1,"1467":66,"1470":1,"1471":4,"1472":3,"1473":2,"1474":2,"1475":8,"1476":4,"1477":1,"1478":2,"1479":1,"1480":1,"1481":9,"1484":1,"1485":7,"1486":68,"1487":84,"1488":93,"1489":54,"1490":34,"1491":5,"1492":59,"1494":1,"1496":377,"1497":2,"1498":3,"1500":4,"1502":1,"1503":13,"1504":1,"1509":4,"1512":1,"1513":2,"1518":1,"1520":3,"1521":1,"1522":4,"1523":3,"1524":4,"1525":6,"1526":114,"1527":4,"1529":2,"1531":18,"1532":2,"1534":10,"1535":14,"1569":1,"1570":1,"1573":2,"1574":1,"1576":2,"1577":85,"1578":2,"1580":1,"1582":24,"1583":5,"1584":1,"1585":9,"1586":11,"1587":4,"1588":3,"1589":1,"1591":79,"1592":1,"1593":2,"1594":3,"1596":8,"1597":1,"1599":2,"1600":8,"1601":1,"1602":7,"1605":2,"1606":1,"1611":17,"1615":4,"1619":9,"1620":1,"1627":11,"1631":1,"1639":1,"1642":1,"1643":2,"1646":8,"1648":11,"1649":9,"1650":12,"1651":4,"1652":2,"1653":18,"1654":3,"1655":4,"1656":4,"1658":8,"1659":5,"1660":17,"1661":9,"1664":1,"1673":3,"1674":2,"1675":6,"1676":2,"1677":4,"1678":3,"1679":2,"1681":5,"1682":3,"1684":2,"1685":12,"1686":10,"1687":2,"1688":1,"1689":1,"1690":3,"1691":4,"1692":3,"1693":4,"1694":4,"1695":3,"1699":1,"1701":2,"1702":6,"1703":3,"1704":1,"1705":4,"1706":2,"1707":1,"1708":1,"1710":1,"1711":1,"1712":1,"1713":1,"1714":1,"1717":2,"1718":1,"1722":2,"1724":2,"1725":1,"1726":10,"1727":13,"1728":3,"1729":2,"1730":2,"1731":2,"1732":2,"1736":5,"1739":2,"1740":1,"1741":1,"1745":1,"1748":1,"1756":2,"1757":1,"1761":5,"1762":1,"1763":1,"1764":17,"1765":33,"1770":2,"1773":3,"1775":2,"1778":2,"1780":1,"1781":2,"1782":3,"1784":3,"1785":3,"1787":4,"1788":1,"1790":3,"1793":4,"1794":1,"1799":2,"1801":1,"1802":4,"1803":2,"1805":2,"1806":1,"1807":3,"1808":1,"1810":4,"1812":3,"1815":8,"1816":1,"1817":3,"1820":1,"1826":1,"1827":4,"1828":2,"1829":1,"1834":4,"1838":2,"1843":1,"1846":4,"1847":1,"1851":1,"1853":3,"1854":1,"1860":1,"1862":4,"1865":2,"1868":4,"1877":4,"1879":1,"1880":1,"1884":2,"1886":3,"1889":1,"1891":1,"1892":4,"1894":1,"1895":3,"1896":1,"1897":1,"1899":3,"1902":4,"1903":1,"1905":4,"1906":1,"1910":1,"1912":3,"1913":1,"1919":1,"1922":2,"1924":4,"1927":2,"1929":2,"1930":5,"1931":2,"1935":1,"1939":1,"1941":4,"1942":1,"1947":3,"1950":3,"1951":1,"1953":1,"1955":1,"1959":3,"1961":1,"1962":1,"1963":2,"1965":4,"1968":5,"1970":2,"1972":4,"1975":2,"1978":1,"1979":4,"1981":1,"1985":4,"1991":4,"1994":8,"1995":8,"1997":5,"1999":1,"2000":19,"2001":1,"2002":4,"2003":2,"2004":7,"2006":13,"2008":1,"2013":9,"2014":6,"2015":1,"2016":1,"2022":1,"2024":3,"2028":4,"2030":1,"2038":3,"2039":2,"2041":5,"2042":5,"2043":3,"2044":18,"2046":11,"2047":3,"2049":4,"2050":2,"2052":1,"2055":7,"2056":1,"2057":3,"2059":2,"2060":4,"2061":1,"2062":1,"2063":1,"2064":3,"2066":5,"2067":1,"2069":2,"2070":1,"2071":2,"2075":2,"2077":2,"2080":3,"2081":1,"2083":14,"2085":7,"2086":4,"2087":1,"2089":1,"2097":2,"2098":5,"2099":1,"2100":3,"2101":8,"2105":1,"2106":1,"2107":1,"2108":5,"2109":1,"2110":2,"2111":6,"2112":2,"2113":1,"2114":2,"2115":4,"2116":1,"2117":1,"2119":2,"2120":5,"2122":3,"2123":6,"2124":1,"2127":5,"2128":3,"2131":10,"2133":4,"2134":2,"2136":1,"2137":1,"2138":1,"2139":3,"2140":1,"2142":1,"2146":4,"2149":3,"2150":6,"2151":1,"2152":1,"2153":7,"2154":2,"2156":12,"2157":5,"2158":9,"2160":13,"2161":1,"2163":1,"2164":1,"2166":1,"2168":3,"2169":6,"2171":1,"2172":2,"2176":3,"2177":4,"2178":3,"2179":5,"2180":6,"2182":1,"2189":1,"2190":1,"2191":1,"2193":5,"2195":1,"2202":2,"2203":5,"2204":4,"2209":1,"2210":1,"2211":3,"2221":1,"2226":2,"2227":23,"2228":3,"2229":1,"2231":3,"2232":12,"2234":5,"2236":1,"2240":1,"2242":1,"2244":2,"2245":1}}],["mx",{"3":{"265":1,"1311":1,"1417":1,"1438":1}}],["mtp",{"0":{"2058":1},"3":{"1192":1,"1213":2,"1215":2,"1456":1,"1486":6,"1493":1,"1494":1,"1611":1,"1654":1,"1685":1,"1686":1,"2058":4,"2060":1}}],["mt",{"2":{"146":1,"147":1},"3":{"146":1,"147":1,"150":1,"1436":1,"1491":1}}],["mfa",{"2":{"909":1,"1063":1,"2197":1,"2203":1},"3":{"0":1,"126":1,"905":1,"909":1,"1059":1,"1063":1,"1270":5,"1289":2,"1293":1,"1300":18,"1437":2,"2197":4,"2203":1}}],["m",{"2":{"2077":1},"3":{"0":1,"69":1,"137":1,"138":1,"649":2,"758":1,"980":2,"1259":2,"1270":1,"1324":6,"1436":2,"1524":2,"1526":2,"1529":1,"1530":1,"1531":8,"1532":4,"1534":1,"1535":1,"1577":1,"1581":5,"1585":4,"1586":1,"1591":1,"1595":1,"1596":3,"1600":1,"1727":1,"1730":1,"2077":2,"2220":1}}],["myprofile",{"3":{"1415":1}}],["mypointstests",{"3":{"1199":1,"1207":2,"1396":2,"1438":2}}],["mypoints",{"3":{"1199":3,"1203":1,"1207":2,"1396":26}}],["mypointsdto",{"3":{"1199":7,"1203":1,"1207":2,"1396":12}}],["mypointspage",{"3":{"1199":3,"1207":2}}],["myvotebypoll",{"3":{"1402":1}}],["myvoteoptionid",{"3":{"1396":1,"1399":1,"1402":9}}],["mymoderatedquestions",{"3":{"1402":1}}],["myupvote",{"3":{"1401":1,"1402":9}}],["mysql",{"3":{"2219":1,"2220":1}}],["mysteriously",{"3":{"2126":1}}],["mystery",{"3":{"1315":1}}],["myself",{"0":{"2062":1},"3":{"1436":2}}],["myscheduleroute",{"3":{"1416":2}}],["myschedulesessionids",{"3":{"1395":4}}],["myschedule",{"3":{"1395":1}}],["myapp",{"3":{"1311":1}}],["myrolenames",{"3":{"1300":1}}],["myordersquery",{"3":{"1199":2,"1207":1}}],["mybadgetests",{"3":{"1199":1,"1207":2,"1396":1,"1438":2}}],["mybadge",{"3":{"1199":2,"1203":1,"1207":2,"1324":1,"1396":23,"1438":1}}],["mybadgedto",{"3":{"1199":7,"1203":1,"1207":2,"1396":14}}],["mybadgepage",{"3":{"1199":3,"1207":2}}],["my",{"3":{"0":1,"317":1,"649":1,"690":3,"694":1,"905":1,"907":1,"1194":1,"1247":1,"1267":2,"1290":1,"1300":2,"1309":3,"1311":4,"1324":2,"1359":3,"1385":1,"1391":3,"1395":21,"1396":24,"1402":3,"1414":1,"1415":4,"1416":2,"1417":1,"1436":2,"1438":1,"1441":1,"1487":1,"1608":1,"1627":3,"1631":2,"1667":2,"1728":1,"1750":3,"1759":1,"1768":1,"1773":3,"1784":1,"1794":1,"1801":1,"1880":1,"1920":2,"2003":1,"2213":1,"2216":1,"2238":1,"2242":1,"2244":1}}],["msg",{"3":{"1455":1}}],["mssqllocaldb",{"3":{"1490":1}}],["mssql",{"3":{"1213":1,"1339":1,"1436":1,"1441":1,"1614":1,"1616":1,"1617":1,"1618":1,"1775":1}}],["ms",{"3":{"0":3,"556":1,"861":7,"862":1,"881":2,"1212":1,"1237":1,"1270":1,"1271":1,"1300":1,"1309":1,"1323":2,"1324":2,"1347":1,"1395":1,"1417":1,"1429":1,"1433":6,"1436":22,"1453":1,"1474":1,"1488":3,"1492":2,"1577":1,"1707":4}}],["msbuild",{"3":{"0":2,"133":1,"135":1,"138":1,"147":1,"266":1,"570":1,"869":1,"1196":1,"1416":1,"1431":2,"1436":2,"1455":1,"1459":2,"1489":1,"1491":1,"1504":1,"1577":2,"1591":1,"1650":1,"1671":1,"1782":1,"2041":2,"2048":1,"2049":1,"2100":2,"2106":2,"2108":1,"2230":1}}],["mp",{"3":{"0":1,"1324":2}}],["mb",{"3":{"0":3,"440":1,"442":1,"875":3,"1116":1,"1117":1,"1118":1,"1212":1,"1323":4,"1347":1,"1350":1,"1373":1,"1380":2,"1389":3,"1395":2,"1411":1,"1414":1,"1415":5,"1417":1,"1443":2,"1447":4,"1451":2,"1456":2,"1465":1,"1467":8,"1478":1,"1490":1,"1631":2,"1640":2,"1748":2,"1765":2,"2126":3,"2128":1,"2166":1}}],["muscle",{"3":{"1144":1}}],["mustcascadesoftdelete",{"3":{"1436":2}}],["mustquote",{"3":{"1311":2}}],["mustnotexceed",{"2":{"1074":1,"1489":2,"1491":1},"3":{"1074":1,"1436":4,"1489":2,"1491":1}}],["must",{"0":{"1291":1,"2189":1},"3":{"0":21,"4":1,"16":1,"17":1,"18":2,"19":1,"24":3,"25":1,"30":1,"37":1,"38":1,"39":1,"41":1,"42":1,"55":1,"62":2,"68":2,"70":1,"92":2,"95":1,"97":2,"98":3,"100":3,"102":2,"108":1,"109":1,"111":2,"117":1,"119":1,"120":1,"126":1,"134":1,"135":1,"149":1,"157":1,"160":1,"168":2,"176":1,"177":3,"178":1,"188":1,"194":1,"195":2,"196":1,"197":1,"198":1,"199":1,"201":1,"209":1,"213":2,"216":1,"217":1,"219":1,"224":1,"235":1,"243":1,"253":1,"255":1,"265":1,"272":1,"279":1,"280":1,"290":1,"302":3,"309":1,"317":2,"318":1,"320":1,"332":1,"333":1,"336":1,"343":1,"348":1,"349":1,"352":1,"358":2,"359":1,"361":2,"362":1,"363":1,"366":1,"380":1,"382":1,"388":1,"390":3,"401":1,"407":1,"412":1,"415":1,"419":1,"424":1,"428":1,"433":1,"434":1,"435":1,"441":1,"448":1,"450":1,"459":1,"465":1,"482":1,"490":1,"498":2,"501":1,"506":2,"509":1,"511":1,"517":3,"518":2,"520":1,"521":1,"527":1,"536":1,"538":2,"541":1,"544":1,"545":1,"549":3,"556":1,"564":1,"572":1,"583":1,"587":1,"590":2,"591":2,"601":1,"626":1,"632":2,"633":1,"645":1,"657":1,"659":1,"667":2,"674":1,"676":1,"681":1,"683":1,"690":1,"698":1,"707":3,"709":3,"716":1,"718":1,"724":1,"757":3,"759":1,"761":1,"762":2,"766":1,"769":1,"774":1,"775":1,"783":2,"790":1,"792":1,"799":1,"809":2,"813":1,"820":1,"821":1,"823":1,"827":2,"829":1,"832":1,"838":1,"840":1,"846":2,"855":1,"856":1,"861":1,"889":1,"897":2,"898":1,"905":1,"914":1,"916":1,"922":2,"926":2,"932":1,"946":1,"948":1,"964":1,"972":2,"976":1,"987":1,"989":1,"990":1,"996":1,"997":1,"1014":1,"1017":1,"1018":2,"1019":1,"1026":1,"1036":1,"1041":2,"1042":1,"1043":1,"1044":1,"1066":1,"1067":1,"1068":1,"1074":1,"1079":1,"1083":2,"1091":4,"1099":2,"1100":3,"1102":2,"1104":1,"1116":4,"1117":1,"1124":1,"1126":1,"1133":1,"1135":1,"1145":1,"1155":1,"1168":3,"1170":1,"1175":1,"1184":1,"1185":2,"1186":4,"1187":1,"1188":1,"1191":1,"1200":1,"1211":1,"1212":5,"1213":1,"1214":2,"1215":1,"1219":1,"1222":1,"1223":1,"1225":1,"1228":1,"1229":7,"1231":3,"1233":2,"1236":2,"1237":17,"1238":2,"1239":1,"1243":1,"1247":24,"1252":1,"1254":1,"1255":1,"1259":26,"1262":2,"1263":2,"1264":6,"1265":3,"1269":4,"1270":38,"1271":15,"1273":3,"1274":1,"1275":2,"1278":1,"1279":1,"1284":1,"1286":87,"1290":1,"1296":1,"1297":1,"1300":71,"1301":14,"1309":17,"1310":10,"1311":44,"1312":8,"1316":2,"1318":3,"1323":47,"1324":80,"1326":1,"1328":2,"1332":2,"1333":2,"1336":1,"1337":4,"1338":3,"1339":51,"1342":1,"1344":3,"1346":1,"1347":1,"1350":28,"1352":3,"1353":2,"1354":1,"1356":1,"1358":1,"1359":138,"1366":4,"1368":1,"1369":4,"1372":1,"1373":1,"1377":1,"1378":3,"1379":3,"1380":19,"1383":1,"1390":1,"1391":1,"1393":1,"1394":1,"1395":44,"1396":65,"1397":1,"1399":1,"1402":29,"1410":1,"1411":1,"1413":1,"1415":38,"1416":16,"1417":45,"1422":1,"1423":3,"1424":1,"1425":1,"1427":7,"1429":3,"1431":1,"1435":2,"1436":306,"1438":47,"1441":2,"1443":4,"1444":1,"1445":1,"1447":3,"1453":5,"1454":4,"1455":13,"1457":3,"1460":1,"1465":2,"1467":13,"1472":1,"1473":1,"1474":2,"1475":1,"1476":3,"1477":1,"1478":1,"1479":1,"1480":1,"1481":5,"1486":3,"1487":1,"1488":4,"1489":10,"1490":1,"1491":1,"1492":1,"1496":2,"1534":1,"1542":1,"1544":1,"1567":1,"1569":1,"1570":1,"1575":1,"1577":3,"1582":2,"1585":2,"1589":1,"1591":2,"1596":2,"1611":1,"1613":1,"1630":7,"1631":17,"1632":9,"1635":12,"1638":1,"1639":5,"1640":5,"1641":8,"1648":1,"1651":1,"1653":3,"1656":1,"1657":1,"1665":1,"1675":1,"1685":1,"1686":4,"1700":1,"1724":3,"1728":1,"1748":1,"1749":2,"1764":6,"1765":10,"1767":25,"1781":1,"1782":1,"1790":1,"1815":1,"1821":1,"1823":1,"1824":2,"1826":1,"1827":1,"1828":2,"1833":1,"1845":1,"1853":1,"1876":1,"1883":1,"1885":1,"1898":4,"1906":1,"1909":1,"1910":1,"1911":1,"1915":1,"1916":1,"1918":2,"1919":2,"1922":1,"1923":2,"1924":2,"1933":1,"1936":1,"1942":1,"1943":1,"1946":1,"1947":1,"1948":2,"1949":1,"1950":3,"1960":1,"1967":2,"1971":1,"1974":1,"1981":1,"1994":1,"1996":1,"1997":1,"2000":1,"2001":4,"2002":1,"2007":1,"2013":1,"2021":1,"2022":1,"2025":4,"2027":2,"2039":1,"2042":1,"2043":2,"2047":1,"2048":1,"2049":1,"2058":2,"2068":2,"2085":1,"2093":1,"2095":2,"2104":2,"2105":1,"2107":1,"2118":1,"2120":1,"2122":1,"2127":1,"2132":1,"2138":1,"2141":1,"2142":3,"2143":1,"2144":1,"2147":1,"2149":1,"2150":1,"2157":1,"2158":2,"2167":3,"2168":2,"2169":1,"2177":1,"2178":1,"2183":3,"2185":1,"2192":1,"2193":1,"2197":1}}],["much",{"3":{"235":1,"384":1,"490":1,"491":1,"492":1,"493":1,"494":1,"625":1,"749":1,"815":1,"819":1,"869":1,"897":1,"1033":1,"1044":1,"1243":1,"1247":1,"1259":1,"1270":2,"1286":4,"1290":1,"1295":1,"1323":1,"1324":7,"1339":4,"1348":1,"1350":1,"1359":2,"1368":1,"1378":1,"1395":2,"1396":8,"1402":1,"1415":1,"1417":2,"1429":1,"1431":1,"1436":4,"1488":2,"1692":1,"1726":1,"1784":1,"1818":1,"1946":1,"1966":1,"1999":1,"2002":1,"2029":1,"2064":1,"2086":1,"2087":1,"2158":1,"2191":1,"2192":1,"2193":1}}],["mute",{"3":{"1436":2}}],["muted",{"3":{"590":1,"2075":1}}],["mutually",{"3":{"667":1,"1191":1,"1196":1,"1300":1,"1323":1,"1395":1,"1396":1,"1427":1,"1749":1,"1765":1}}],["mutual",{"1":{"993":1,"994":1,"995":1,"996":1,"997":1,"998":1,"999":1,"1000":1},"3":{"0":1,"55":1,"135":1,"159":2,"161":1,"993":1,"996":1,"1212":1,"1237":8,"1260":1,"1269":1,"1270":2,"1286":1,"1300":1,"1301":2,"1312":1,"1350":1,"1436":1,"1438":1,"2030":1,"2232":1}}],["mutant",{"3":{"2059":1}}],["mutability",{"3":{"1339":1,"1396":2}}],["mutablepropertyviolations",{"3":{"1436":1}}],["mutableoptions",{"3":{"1199":2,"1207":1}}],["mutablefixtureid",{"3":{"1199":2,"1207":1,"1436":9}}],["mutabletenantcontext",{"3":{"1199":2,"1207":1}}],["mutableauthenticationstateprovider",{"2":{"954":1,"1488":1,"1491":1},"3":{"954":1,"1199":2,"1207":1,"1436":3,"1488":1,"1491":1}}],["mutable",{"3":{"0":1,"340":1,"341":1,"342":1,"343":1,"373":1,"629":1,"881":1,"884":1,"954":1,"962":1,"963":1,"1050":1,"1170":1,"1237":1,"1281":1,"1286":4,"1300":7,"1301":1,"1310":3,"1311":1,"1323":3,"1324":8,"1339":8,"1350":6,"1359":5,"1380":1,"1395":5,"1396":3,"1402":5,"1417":1,"1427":1,"1432":1,"1436":21,"1452":1,"1459":1,"1475":1,"1488":2,"1489":1,"1491":2,"1525":1,"1526":2,"1542":1,"1556":2,"1572":1,"1577":1,"1591":1,"1630":1,"1632":2,"1639":1,"1660":1,"1671":1,"1686":1,"1869":1,"1875":1,"1877":1,"2052":1}}],["mutator",{"3":{"1291":1,"1300":2,"1310":1,"1343":1,"1345":1,"1359":14,"1396":3,"1402":1}}],["mutators",{"3":{"0":1,"109":1,"690":1,"782":1,"1309":1,"1310":4,"1311":2,"1344":1,"1350":1,"1359":3,"1396":2,"1631":1}}],["mutating",{"0":{"1411":1},"3":{"80":1,"258":1,"318":1,"359":1,"388":2,"390":1,"727":1,"905":1,"1247":1,"1270":2,"1283":1,"1286":4,"1293":1,"1300":1,"1301":1,"1309":1,"1311":3,"1323":1,"1324":2,"1339":1,"1359":4,"1375":1,"1380":3,"1395":2,"1396":1,"1402":1,"1415":8,"1429":1,"1436":1,"1460":2,"1475":2,"1556":1,"1664":1,"1701":1,"1923":1,"1995":1,"2142":1}}],["mutationcontexttests",{"3":{"926":1,"1199":1,"1207":1,"1270":1}}],["mutationcontexthandlertests",{"2":{"926":1},"3":{"926":1,"1199":1,"1207":1}}],["mutationcontext",{"3":{"0":1,"926":4,"1199":15,"1203":1,"1207":2,"1260":1,"1265":5,"1270":16,"1352":1,"1359":9,"1411":1,"1415":5,"1666":1}}],["mutation",{"3":{"0":1,"120":1,"241":1,"242":1,"243":1,"244":1,"255":2,"318":3,"322":1,"341":1,"344":1,"386":1,"387":1,"390":3,"715":1,"721":1,"792":1,"905":1,"920":1,"921":2,"922":3,"926":10,"988":1,"1153":1,"1167":1,"1168":1,"1169":1,"1229":1,"1237":3,"1259":2,"1263":1,"1265":6,"1270":28,"1278":1,"1286":14,"1290":1,"1300":5,"1301":5,"1311":7,"1323":3,"1324":3,"1339":2,"1340":1,"1350":10,"1352":2,"1354":1,"1359":51,"1369":1,"1379":1,"1380":14,"1395":9,"1396":7,"1402":9,"1411":1,"1415":11,"1417":2,"1427":1,"1431":1,"1436":7,"1438":1,"1556":1,"1576":2,"1577":2,"1591":1,"1632":1,"1651":1,"1685":1,"1686":1,"1701":1,"1748":2,"1749":1,"1750":1,"1753":1,"1761":1,"1765":2,"1821":1,"1918":2,"1922":1,"1923":2,"1941":1,"1995":1,"2059":2,"2079":1,"2163":2}}],["mutations",{"3":{"0":2,"24":1,"193":1,"201":2,"320":1,"926":1,"1155":1,"1258":1,"1259":4,"1270":3,"1286":1,"1306":1,"1323":1,"1324":2,"1346":1,"1350":2,"1359":4,"1380":9,"1395":2,"1396":2,"1402":2,"1436":1,"1455":2,"1465":1,"1599":1,"1631":1,"1634":2,"1638":1,"1651":1,"1685":1,"1686":2,"1700":1,"1727":1,"1748":1,"1769":1,"1910":1,"1996":1}}],["mutatefirstmultimemberevent",{"3":{"1436":1}}],["mutatetrackedasync",{"3":{"1396":3}}],["mutatechildasync",{"3":{"1395":2}}],["mutatecoreasync",{"2":{"523":1,"921":1},"3":{"0":1,"523":1,"921":1,"926":1,"1270":4,"1359":5,"1402":1,"1415":1}}],["mutateattemptscopetests",{"2":{"926":1},"3":{"926":1,"1199":1,"1207":1}}],["mutateasync",{"3":{"0":1,"922":1,"926":6,"1265":1,"1270":12,"1352":1,"1359":35,"1389":1,"1395":6,"1399":1,"1402":6}}],["mutated",{"3":{"914":1,"922":1,"926":2,"1091":1,"1150":1,"1155":1,"1229":1,"1237":1,"1265":1,"1270":6,"1286":4,"1300":2,"1309":1,"1311":1,"1323":2,"1339":2,"1350":9,"1359":9,"1395":4,"1396":5,"1415":2,"1417":1,"1436":4,"1453":1,"1556":1,"1647":1,"2174":1}}],["mutateentityhandlercore",{"2":{"523":2,"926":1,"1100":1,"1265":1},"3":{"523":2,"926":1,"1100":1,"1199":3,"1203":1,"1207":1,"1212":1,"1260":1,"1265":1,"1270":9,"1359":6,"1402":1,"1415":1,"1496":2,"1666":1}}],["mutateentityhandlerbasetests",{"3":{"1199":1,"1207":1,"1270":3}}],["mutateentityhandlerbase",{"2":{"344":1,"518":1,"523":1,"922":1,"926":1,"1100":1,"1265":1,"1399":1,"1871":1,"2163":1},"3":{"0":3,"341":1,"344":2,"345":1,"444":1,"518":1,"523":3,"921":2,"922":2,"923":1,"926":4,"927":1,"1100":2,"1199":25,"1203":1,"1207":6,"1260":1,"1265":17,"1270":17,"1311":2,"1352":1,"1355":1,"1359":91,"1399":1,"1402":16,"1415":5,"1496":2,"1871":1,"2163":1}}],["mutateentitypayloadhandlerbase",{"3":{"0":1,"926":1,"1199":6,"1203":1,"1207":1,"1260":1,"1265":1,"1270":3,"1352":1,"1359":3,"1411":1,"1415":2}}],["mutate",{"3":{"0":3,"827":1,"920":3,"921":1,"926":8,"927":1,"1068":1,"1153":1,"1212":1,"1237":1,"1247":3,"1260":1,"1261":1,"1265":3,"1270":9,"1286":6,"1300":1,"1301":1,"1309":1,"1324":1,"1342":1,"1343":1,"1350":2,"1359":25,"1369":1,"1380":2,"1395":5,"1396":1,"1399":1,"1402":3,"1415":5,"1423":1,"1436":5,"1543":1,"1651":2,"1666":1,"1808":1,"1820":1,"1891":1,"1937":1,"2230":1}}],["mutates",{"3":{"0":1,"17":1,"341":1,"766":1,"767":1,"1212":1,"1237":1,"1247":1,"1259":2,"1270":1,"1286":4,"1309":1,"1311":3,"1323":2,"1324":1,"1350":4,"1352":1,"1359":9,"1369":1,"1380":2,"1395":2,"1396":1,"1402":5,"1415":1,"1436":6,"1455":1,"1457":1,"1458":1,"1460":1,"1591":1,"1632":1,"1911":1,"2091":2}}],["mudnumericfield",{"3":{"1702":1}}],["mudnavgroup",{"3":{"1324":1}}],["mudpagination",{"3":{"1591":1}}],["mudpopoverprovider",{"3":{"1436":2,"1653":1}}],["mudprogresscircular",{"3":{"1324":1}}],["mudproviderhandles",{"3":{"1199":3,"1207":1,"1432":1,"1436":3,"1488":1}}],["mudmenu",{"3":{"1436":1}}],["mudmessagebox",{"3":{"1415":2,"1436":1,"1488":1,"1526":2}}],["mudrating",{"3":{"1395":2,"1396":3}}],["mudcheckbox",{"3":{"1436":2}}],["mudchip",{"3":{"1395":2}}],["mudcard",{"3":{"1395":2}}],["muddialogprovider",{"3":{"1436":2,"1653":1}}],["muddivider",{"3":{"1395":1}}],["muddatagridrowselector",{"3":{"1436":1}}],["muddatagrid",{"2":{"1715":1,"2072":1,"2079":1},"3":{"0":1,"881":1,"883":1,"1324":14,"1383":1,"1395":6,"1396":1,"1415":2,"1433":1,"1436":6,"1526":1,"1715":1,"1741":1,"1750":7,"2072":2,"2079":1}}],["mudimage",{"3":{"1395":2}}],["muditem",{"3":{"1395":1}}],["mudicon",{"3":{"1324":1}}],["mudiconbutton",{"3":{"1324":2}}],["mudbutton",{"3":{"1324":1,"1436":3}}],["mudblazor",{"0":{"1432":1},"1":{"1324":1},"2":{"265":1,"1698":1},"3":{"0":4,"213":1,"214":1,"215":1,"216":2,"263":1,"265":8,"267":3,"268":1,"556":2,"558":1,"618":2,"620":1,"647":1,"649":3,"650":1,"651":2,"940":1,"954":5,"956":2,"960":1,"1125":1,"1192":1,"1202":1,"1203":1,"1212":1,"1213":1,"1323":1,"1324":129,"1336":1,"1339":4,"1383":1,"1395":51,"1396":11,"1402":2,"1415":4,"1416":5,"1428":1,"1432":3,"1433":2,"1436":25,"1438":3,"1443":1,"1488":7,"1489":1,"1491":2,"1496":3,"1526":2,"1534":1,"1536":1,"1557":1,"1577":8,"1586":1,"1591":4,"1600":1,"1647":1,"1653":2,"1660":1,"1669":3,"1670":1,"1671":1,"1681":1,"1687":1,"1698":1,"1712":2,"1741":1,"1763":1,"1773":1,"2043":1,"2055":1,"2057":1,"2072":3,"2073":1,"2075":2,"2079":4,"2085":3,"2089":1,"2147":1,"2149":1,"2150":3,"2152":1,"2221":1,"2227":1}}],["mudlink",{"3":{"1324":1,"1395":5,"1417":1,"1436":1}}],["mudlocalizer",{"2":{"267":1},"3":{"265":1,"267":1,"1324":22,"2085":1}}],["mudswitch",{"3":{"1396":2}}],["mudsimpletable",{"2":{"1531":1},"3":{"1395":2,"1396":2,"1526":1,"1531":1}}],["mudsnackbarprovider",{"3":{"1324":1,"1436":2,"1653":1}}],["mudselect",{"3":{"618":1,"1395":1,"1396":1,"1488":1}}],["mudavatar",{"3":{"1395":2,"1415":1,"1491":1}}],["mudautocomplete",{"3":{"1395":2}}],["mudappdialogservice",{"2":{"649":1},"3":{"649":1,"954":1,"1199":2,"1203":1,"1207":2,"1324":22}}],["mudappbar",{"3":{"273":1}}],["mudalert",{"3":{"109":1,"1324":1,"1395":4,"1396":2,"1436":2}}],["mudform",{"2":{"128":1,"2045":1},"3":{"109":1,"117":1,"128":1,"1324":9,"1384":1,"1395":25,"1396":5,"1415":1,"1436":3,"1489":1,"1526":1,"1535":1,"1591":1,"2045":1}}],["mudtext",{"3":{"1324":1}}],["mudtextfield",{"3":{"1324":1,"1395":2,"1396":2,"1402":2}}],["mudtoastservicetests",{"3":{"1199":1,"1207":2,"1324":3}}],["mudtoastservice",{"3":{"649":1,"954":1,"1199":3,"1203":1,"1207":2,"1324":18,"1496":1}}],["mudtable",{"2":{"554":1},"3":{"554":1,"556":1,"1324":1}}],["mudtablepager",{"2":{"651":1,"2057":1},"3":{"0":1,"618":2,"651":1,"1436":2,"1488":2,"2057":1}}],["mudtranslations",{"2":{"267":1,"268":1,"2085":1},"3":{"265":2,"267":1,"268":2,"1199":2,"1203":1,"1207":2,"1324":10,"1436":2,"1496":1,"1577":2,"2085":3}}],["mudtheme",{"2":{"2075":1},"3":{"0":1,"273":2,"1324":6,"2075":2,"2083":1}}],["mudthemeprovider",{"2":{"272":1,"273":1,"2075":1,"2086":1},"3":{"0":1,"272":1,"273":1,"1212":1,"1324":4,"1436":1,"1577":1,"1653":1,"2075":1,"2086":1,"2232":1}}],["mud",{"3":{"0":2,"273":1,"275":1,"954":1,"1324":6,"1436":7,"1653":1,"2075":1}}],["multidimensional",{"3":{"2220":1}}],["multidevicerefreshsessionsandauditdeletioncolumns",{"3":{"1209":2}}],["multithreaded",{"3":{"2220":1}}],["multiline",{"3":{"1396":1,"1436":1,"1455":1}}],["multifactor",{"2":{"2197":1},"3":{"1300":7,"2197":1}}],["multifactormethodrecoverycode",{"3":{"1300":1}}],["multifactormethodtotp",{"3":{"1300":1}}],["multifactormethodfor",{"3":{"1300":2}}],["multifactormethod",{"3":{"1300":6}}],["multifactorrequired",{"2":{"1263":1},"3":{"126":1,"1263":1,"1270":1,"1821":1}}],["multihandlereventhandler2",{"3":{"1199":2,"1207":1}}],["multihandlereventhandler1",{"3":{"1199":2,"1207":1}}],["multihandlerevent",{"3":{"1199":4,"1207":1}}],["multipartformdatacontent",{"3":{"1395":2,"1415":2}}],["multipart",{"3":{"1116":1,"1347":1,"1350":2,"1389":1,"1395":4,"1411":1,"1415":3,"1447":1,"1631":2,"1640":1,"1748":1,"1765":1}}],["multiplying",{"3":{"1247":1,"1323":1,"1396":1}}],["multiply",{"3":{"657":1,"685":1,"716":1,"819":1,"1229":1,"1237":1,"1242":1,"1247":4,"1286":2,"1339":2,"1359":1,"1449":1,"1863":1,"1925":1,"2032":1}}],["multiplicity",{"3":{"1396":1}}],["multiplication",{"3":{"0":1,"178":1,"827":1,"1339":1}}],["multipliers",{"3":{"1339":1}}],["multiplier",{"3":{"714":1,"742":1,"1339":2,"2013":1}}],["multiplies",{"0":{"2035":1},"3":{"509":1,"682":1,"700":1,"740":1,"783":1,"1232":1,"1237":1,"1286":1,"1300":1,"1337":1,"1402":1,"1417":1,"2027":1,"2035":1,"2037":1,"2038":1}}],["multiplied",{"3":{"19":1,"23":1,"1126":1,"1256":1,"1259":2,"1275":1,"1339":2,"1395":1,"1416":1,"1841":1,"2027":1,"2038":1}}],["multiplexed",{"3":{"1329":1,"1339":1,"1443":1}}],["multiplexer",{"3":{"0":2,"155":1,"159":1,"178":1,"243":2,"245":1,"257":1,"260":1,"261":1,"733":1,"996":1,"1301":11,"1323":1,"1332":1,"1339":8,"1438":1,"1443":1,"1577":1,"1666":1,"1668":1,"1909":1,"1911":1,"1923":3}}],["multiple",{"1":{"163":1,"164":1,"165":1,"166":1,"167":1,"168":1,"169":1,"170":1,"171":1},"3":{"0":1,"10":1,"46":1,"135":1,"163":1,"839":1,"1085":1,"1237":1,"1247":1,"1259":1,"1270":2,"1286":4,"1300":3,"1311":3,"1312":1,"1323":3,"1324":2,"1347":1,"1350":1,"1359":3,"1369":1,"1395":2,"1397":1,"1417":1,"1429":1,"1436":5,"1464":1,"1488":1,"1537":1,"1544":1,"1555":1,"1556":1,"1630":2,"1639":3,"1668":1,"1767":1,"1773":1,"1792":1,"1940":1,"2027":1,"2220":1}}],["multisourceorderconfiguration",{"3":{"1199":1,"1207":1}}],["multisourceorder",{"3":{"1199":3,"1207":1}}],["multisourcecustomerconfiguration",{"3":{"1199":1,"1207":1}}],["multisourcecustomer",{"3":{"1199":4,"1207":1}}],["multisourcetestevent",{"3":{"1199":1,"1207":1}}],["multisourcesqliteintegrationtests",{"2":{"164":1,"2053":1,"2060":1},"3":{"164":1,"1198":1,"1199":2,"1207":8,"1286":1,"1496":1,"2053":1,"2060":1}}],["multisessionspeaker",{"3":{"1199":9,"1203":1,"1207":1,"1347":1,"1350":3,"1359":3,"1395":1}}],["multiselect",{"3":{"757":1,"1455":1}}],["multicast",{"3":{"0":1,"380":1,"382":1,"1324":2,"1400":2,"1402":1,"1945":1,"1949":1}}],["multi",{"1":{"262":1,"263":1,"264":1,"265":1,"266":1,"267":1,"268":1,"269":1,"695":1,"696":1,"697":1,"698":1,"699":1,"700":1,"701":1,"702":1,"703":1,"902":1,"903":1,"904":1,"905":1,"906":1,"907":1,"908":1,"909":1,"910":1},"3":{"0":9,"12":1,"30":1,"68":2,"84":1,"86":1,"149":1,"158":1,"166":1,"227":1,"243":1,"245":1,"258":1,"262":1,"264":1,"283":1,"340":1,"386":1,"390":1,"392":1,"413":1,"442":1,"462":1,"491":1,"501":1,"517":1,"535":1,"536":1,"555":1,"657":1,"659":1,"660":1,"670":1,"695":1,"706":1,"708":1,"748":1,"750":1,"809":2,"810":2,"827":1,"897":1,"902":1,"926":1,"988":1,"990":1,"997":1,"998":1,"1012":1,"1013":1,"1067":1,"1212":6,"1217":1,"1237":1,"1247":2,"1259":1,"1267":1,"1270":6,"1271":1,"1276":1,"1283":1,"1286":24,"1287":1,"1296":1,"1300":17,"1301":4,"1311":7,"1316":1,"1323":6,"1324":9,"1332":1,"1333":1,"1336":1,"1339":5,"1350":6,"1359":5,"1367":1,"1380":5,"1395":8,"1415":2,"1416":2,"1417":1,"1427":1,"1436":14,"1437":1,"1438":3,"1441":1,"1445":1,"1449":1,"1454":1,"1455":1,"1459":1,"1466":1,"1474":1,"1489":1,"1496":4,"1499":1,"1526":3,"1529":1,"1534":1,"1541":2,"1547":1,"1548":1,"1566":1,"1570":1,"1572":1,"1577":3,"1599":1,"1629":2,"1631":2,"1638":1,"1653":1,"1661":1,"1664":2,"1667":1,"1673":1,"1675":2,"1677":1,"1686":1,"1698":1,"1713":1,"1720":1,"1727":2,"1774":1,"1780":1,"1795":1,"1799":1,"1853":1,"1869":1,"1920":1,"1940":1,"1948":1,"1949":1,"1950":1,"1971":1,"1985":2,"2001":1,"2008":1,"2033":1,"2034":1,"2037":2,"2053":1,"2081":1,"2085":1,"2086":1,"2120":1,"2162":1,"2220":4,"2221":1,"2227":2,"2230":1,"2232":4,"2240":1}}],["mobility",{"3":{"1630":1}}],["mobileviewport",{"3":{"1436":1}}],["mobilepage",{"3":{"1324":1}}],["mobilepagesize",{"3":{"881":1,"1324":1,"1395":2}}],["mobilelist",{"3":{"1324":2}}],["mobiletoprowe2etests",{"3":{"1199":1,"1207":2,"1436":4}}],["mobiletotalitems",{"3":{"881":1,"1324":1,"1395":1}}],["mobilecardlist",{"2":{"2059":1},"3":{"1436":2,"2059":1}}],["mobilecardlisttests",{"3":{"1199":1,"1207":2}}],["mobilecurrentpage",{"3":{"881":1,"1324":3,"1395":1}}],["mobilehostparitytests",{"3":{"1199":1,"1207":2,"1436":3}}],["mobileitems",{"3":{"881":1,"1324":1,"1395":1}}],["mobileinfinitescrolllisttests",{"3":{"1199":1,"1207":2,"1324":1}}],["mobileinfinitescrolllist",{"2":{"112":1,"1383":1,"2059":1,"2073":1},"3":{"0":1,"109":2,"111":1,"112":2,"1199":18,"1203":1,"1207":2,"1324":12,"1383":1,"1395":12,"1396":1,"1436":1,"1526":1,"1577":1,"1591":1,"1600":1,"2059":1,"2073":1}}],["mobilenavkeyboarde2etests",{"2":{"616":1},"3":{"616":1,"618":1,"1199":1,"1207":2}}],["mobilereleaserunbook",{"0":{"1481":1},"3":{"422":1,"423":1,"424":1,"425":1,"426":2,"1416":1,"1470":1,"1481":16,"1482":1,"1483":1,"1526":1}}],["mobileredirectscheme",{"3":{"415":1,"1416":1,"1417":4}}],["mobile",{"0":{"1669":1},"1":{"417":1,"418":1,"419":1,"420":1,"421":1,"422":1,"423":1,"424":1,"425":1,"426":1,"427":1,"428":1,"429":1,"430":1},"3":{"0":2,"340":1,"387":1,"417":1,"419":1,"618":1,"649":1,"652":1,"881":3,"905":1,"907":1,"1124":1,"1212":1,"1300":3,"1311":3,"1323":1,"1324":29,"1350":1,"1380":1,"1383":2,"1385":1,"1395":39,"1396":9,"1402":2,"1411":1,"1414":1,"1415":5,"1416":2,"1417":5,"1436":8,"1438":5,"1459":1,"1471":1,"1472":1,"1481":1,"1487":1,"1490":1,"1496":1,"1524":2,"1526":5,"1531":3,"1534":2,"1559":2,"1577":2,"1591":2,"1595":1,"1596":1,"1597":1,"1599":1,"1629":1,"1640":1,"1641":4,"1667":1,"1669":3,"1673":1,"1675":1,"1712":2,"1713":1,"1714":4,"1874":1,"1976":2,"2072":1,"2073":4,"2079":3,"2116":1,"2121":1,"2232":1}}],["mouse",{"3":{"1606":1,"1740":1}}],["mount=type=secret",{"3":{"1445":1}}],["mounted",{"3":{"869":1,"1300":3,"1309":1,"1311":1,"1323":2,"1324":4,"1380":4,"1395":3,"1396":5,"1402":2,"1415":5,"1455":1,"1458":1,"2149":1}}],["mounts",{"3":{"217":1,"723":1,"871":1,"1311":1,"1324":2,"1380":1,"1395":2,"1396":1,"1416":1,"1432":1,"1666":1}}],["mount",{"3":{"0":1,"871":1,"1300":1,"1396":1,"1445":1,"1666":1,"2153":1}}],["mo",{"3":{"1436":2,"1534":1,"1671":1}}],["motion",{"3":{"1433":1,"1436":1}}],["motive",{"3":{"1270":1,"1300":2,"1395":1,"1402":1}}],["motivation",{"3":{"1323":2,"1339":1,"1359":1,"1395":1,"1415":1,"1436":1}}],["motivations",{"3":{"1237":1}}],["motivating",{"3":{"1286":1,"1324":2,"1396":1}}],["motivate",{"3":{"1259":1}}],["motivates",{"3":{"485":1,"1296":1,"1309":1,"1359":1}}],["motivated",{"3":{"0":1,"105":1,"318":1,"686":1,"829":1,"836":1,"1396":1,"1413":1,"1436":1,"1438":1,"1467":3,"1492":1,"2027":1}}],["motor",{"3":{"1417":2}}],["moot",{"3":{"915":1,"1395":1}}],["mojibake",{"3":{"741":1,"1311":1}}],["mornings",{"3":{"1457":1}}],["morning",{"3":{"689":1,"1342":1,"1350":1,"1359":2,"1364":1,"1369":1}}],["more",{"0":{"1335":1,"1376":1,"2051":1,"2200":1},"3":{"0":11,"7":1,"27":1,"33":1,"49":1,"81":1,"95":1,"96":1,"109":2,"111":2,"126":1,"130":1,"135":1,"137":1,"141":1,"145":1,"185":1,"194":1,"195":1,"243":1,"245":1,"259":1,"264":1,"340":1,"350":1,"361":2,"365":2,"382":1,"390":1,"391":1,"392":1,"413":1,"423":1,"461":1,"463":1,"470":1,"497":1,"518":1,"530":2,"549":2,"551":2,"566":1,"572":1,"590":1,"592":1,"597":1,"600":1,"601":1,"609":3,"633":1,"635":1,"673":1,"674":3,"690":1,"706":1,"708":1,"723":1,"725":1,"743":1,"758":1,"767":1,"782":1,"800":1,"827":1,"829":1,"833":1,"838":1,"849":1,"854":1,"856":2,"862":1,"871":2,"881":1,"905":1,"907":1,"920":1,"924":1,"926":2,"937":1,"941":3,"979":1,"980":1,"981":1,"982":2,"987":2,"995":1,"1004":1,"1006":1,"1013":1,"1019":2,"1020":1,"1034":1,"1036":1,"1051":2,"1061":1,"1067":1,"1069":2,"1085":1,"1089":1,"1091":1,"1100":1,"1101":1,"1116":2,"1134":1,"1142":1,"1143":1,"1145":1,"1154":1,"1156":2,"1163":1,"1168":1,"1170":1,"1219":1,"1229":6,"1237":3,"1243":1,"1247":1,"1252":1,"1254":1,"1255":1,"1256":1,"1258":1,"1259":7,"1265":1,"1268":1,"1270":11,"1273":1,"1275":2,"1277":1,"1278":1,"1282":1,"1284":1,"1285":3,"1286":22,"1294":1,"1300":13,"1301":2,"1309":2,"1311":13,"1314":1,"1317":1,"1320":1,"1322":1,"1323":9,"1324":25,"1326":1,"1328":1,"1339":10,"1347":1,"1348":1,"1350":7,"1352":2,"1359":33,"1364":1,"1369":2,"1375":1,"1379":1,"1380":5,"1395":18,"1396":21,"1401":1,"1402":2,"1405":1,"1407":1,"1409":2,"1415":11,"1416":1,"1417":5,"1421":1,"1427":3,"1431":2,"1436":36,"1438":3,"1441":3,"1442":3,"1443":1,"1444":1,"1455":2,"1456":2,"1457":1,"1460":1,"1465":2,"1467":8,"1475":1,"1478":1,"1488":2,"1489":2,"1492":3,"1496":4,"1503":1,"1508":1,"1534":1,"1631":1,"1637":3,"1638":1,"1640":1,"1651":1,"1654":1,"1662":1,"1673":1,"1683":1,"1693":1,"1695":1,"1724":1,"1727":1,"1764":6,"1765":1,"1778":1,"1781":1,"1793":1,"1794":1,"1799":1,"1802":1,"1805":2,"1806":1,"1810":1,"1811":2,"1826":1,"1828":1,"1833":1,"1836":1,"1845":1,"1851":2,"1852":1,"1870":1,"1874":1,"1880":1,"1892":1,"1894":3,"1902":1,"1907":1,"1909":1,"1915":1,"1923":1,"1925":1,"1929":1,"1930":2,"1942":1,"1945":1,"1949":1,"1958":1,"1962":1,"1965":1,"1971":2,"1972":1,"1973":1,"1975":1,"1986":1,"1989":1,"1991":1,"1995":2,"2001":1,"2010":1,"2035":1,"2042":1,"2043":1,"2046":1,"2047":2,"2049":1,"2050":1,"2055":2,"2057":2,"2060":3,"2062":1,"2063":1,"2067":1,"2073":2,"2085":1,"2086":1,"2104":1,"2121":1,"2123":1,"2126":1,"2131":2,"2135":1,"2136":1,"2145":1,"2155":1,"2156":3,"2158":1,"2168":2,"2180":1,"2183":1,"2187":1,"2192":1,"2193":1,"2197":1,"2199":1,"2201":1,"2202":1,"2213":1,"2238":1}}],["mockable",{"3":{"1286":2,"1324":3,"1415":1}}],["mocks",{"3":{"1199":28,"1207":17,"1286":2,"1323":2,"1359":2,"1395":1,"1396":1,"1436":2,"1438":2,"1491":1,"1685":1}}],["mocking",{"3":{"956":1,"1213":1,"1286":1,"1323":1,"1359":1,"1436":2,"1440":1,"1685":1,"2060":1}}],["mocked",{"3":{"914":1,"998":1,"1286":1,"1323":2,"1324":1,"1359":1,"1396":1,"1436":7,"1438":5,"1487":1,"1490":1,"1612":1,"1654":1,"1671":1,"1685":2,"2052":1}}],["mock",{"3":{"414":1,"1271":1,"1286":5,"1300":3,"1323":1,"1324":5,"1359":1,"1395":1,"1396":5,"1429":1,"1436":19,"1438":4,"1453":4,"1488":1,"1490":1,"1538":1,"1551":1,"1577":1,"1617":2,"1686":1,"2053":1}}],["moq",{"3":{"0":1,"954":1,"956":2,"960":1,"1213":1,"1369":2,"1436":5,"1488":1,"1686":2,"2058":1,"2219":1}}],["momentarily",{"3":{"1402":1,"1454":1}}],["moments",{"3":{"135":1,"1324":1}}],["moment",{"3":{"0":1,"20":1,"24":1,"54":1,"160":1,"351":1,"358":1,"499":2,"527":1,"539":1,"557":1,"565":1,"628":1,"692":1,"714":1,"741":1,"765":1,"767":1,"768":1,"798":1,"800":1,"828":1,"840":1,"889":1,"891":1,"971":1,"1090":1,"1153":1,"1160":1,"1212":1,"1223":1,"1237":1,"1247":2,"1249":1,"1254":1,"1256":1,"1259":4,"1265":1,"1270":2,"1271":2,"1275":1,"1286":11,"1300":5,"1301":1,"1309":1,"1310":1,"1311":5,"1312":2,"1318":1,"1323":6,"1324":6,"1330":1,"1339":4,"1350":2,"1358":3,"1359":11,"1369":1,"1390":1,"1393":1,"1395":2,"1396":7,"1402":2,"1405":1,"1415":5,"1416":1,"1417":6,"1436":9,"1438":2,"1465":2,"1467":1,"1489":2,"1534":1,"1577":1,"1585":1,"1653":1,"1654":1,"1708":1,"1792":1,"1805":1,"1836":1,"1858":1,"1863":1,"1887":1,"1888":1,"1899":1,"1913":1,"1918":1,"1929":1,"1950":1,"1960":1,"1972":1,"1974":1,"1978":1,"1979":1,"1985":1,"1988":1,"1998":1,"2006":1,"2040":1,"2045":1,"2063":1,"2081":1,"2116":1,"2126":3,"2127":1,"2129":1,"2135":1,"2140":1,"2146":1,"2150":1,"2166":1,"2167":1,"2171":1,"2194":2}}],["mon",{"3":{"1456":1,"1460":2,"1490":1}}],["monetary",{"3":{"1237":1}}],["moneytestdbcontext",{"3":{"1199":2,"1207":1}}],["moneytests",{"3":{"1199":1,"1207":2}}],["moneytowire",{"3":{"657":1}}],["moneyserializationtests",{"3":{"1199":1,"1207":2,"1438":2}}],["moneyextensionstests",{"3":{"1199":1,"1207":3,"1324":1}}],["moneyextensions",{"3":{"980":1,"1199":1,"1203":1,"1207":2,"1208":2,"1237":1,"1324":4,"1496":1,"1591":2,"1596":1,"1600":1}}],["moneyfromwire",{"3":{"657":2}}],["moneyv1",{"3":{"0":1,"657":1}}],["money",{"2":{"655":1,"658":1,"659":1,"660":3,"1025":1},"3":{"0":3,"535":1,"536":1,"655":2,"657":29,"658":5,"659":5,"660":7,"689":1,"767":1,"768":1,"793":1,"1020":1,"1025":2,"1026":1,"1168":1,"1199":13,"1203":1,"1207":2,"1208":1,"1212":1,"1230":1,"1234":13,"1237":41,"1247":1,"1282":2,"1286":11,"1311":1,"1312":3,"1324":13,"1395":1,"1422":1,"1435":1,"1438":3,"1455":4,"1492":1,"1496":2,"1541":1,"1577":1,"1585":2,"1591":3,"1596":1,"1600":3,"1662":2,"1749":1,"1764":6,"1765":2,"1766":1,"1770":1,"1810":4,"2161":1,"2163":1,"2169":1}}],["monadic",{"3":{"1220":1,"1229":1,"1805":1}}],["months",{"3":{"888":1,"1019":1,"1436":2,"1661":1,"1794":1,"1809":1,"1913":1,"1948":1,"1951":1,"2039":1,"2043":1,"2100":1,"2147":1,"2200":1}}],["month",{"3":{"827":1,"1020":2,"1118":1,"1270":2,"1395":1,"1457":1,"1465":1,"1467":8,"1474":1,"1476":1,"1709":1,"1958":1,"1973":1,"2043":1,"2057":1,"2158":1}}],["monthlybudgetamount",{"3":{"1467":1,"1591":1}}],["monthly",{"3":{"0":1,"591":1,"609":1,"625":1,"626":1,"627":1,"628":1,"765":2,"827":1,"870":1,"1020":1,"1116":1,"1436":1,"1446":1,"1455":2,"1456":1,"1458":1,"1460":2,"1467":8,"1469":2,"1474":2,"1475":1,"1484":1,"1524":1,"1525":1,"1526":1,"1531":1,"1535":1,"1589":2,"1591":1,"1595":1,"1596":3,"1598":1,"1678":1,"1709":1,"2034":2,"2047":1,"2211":1}}],["mondays",{"3":{"1475":1,"2034":1}}],["monday",{"3":{"625":3,"766":1,"1438":1,"1455":1,"1456":1,"1457":3,"1460":3,"1461":1,"1475":1,"1484":1,"1490":1,"1492":1,"1534":1,"1591":2,"1599":1}}],["monitors",{"3":{"1417":1}}],["monitored",{"3":{"1324":1,"1465":1,"1467":2}}],["monitoringviewresourceregex",{"3":{"1436":2}}],["monitoringworkbookordashboard",{"3":{"1436":2,"1467":1}}],["monitoring",{"3":{"109":1,"827":1,"1311":1,"1417":1,"1436":2,"1467":2,"1474":1,"1927":1,"2220":1}}],["monitor",{"1":{"2154":1,"2155":1,"2156":1,"2157":1,"2158":1,"2159":1,"2160":1},"3":{"0":2,"395":1,"397":2,"401":1,"402":1,"790":1,"914":1,"997":1,"1068":1,"1213":1,"1333":4,"1339":11,"1379":1,"1436":1,"1438":1,"1442":2,"1443":3,"1447":1,"1450":1,"1467":7,"1474":2,"1526":1,"1577":1,"1668":1,"1670":1,"1779":1,"2010":3,"2014":1,"2153":2,"2154":3,"2156":2,"2157":1,"2160":2,"2169":1,"2209":1,"2215":1}}],["monotonic",{"3":{"1390":1,"1395":1,"1396":1,"1417":1,"1438":1}}],["monotonically",{"3":{"690":1,"1286":1,"1324":1,"1396":2,"1465":1}}],["monopolizing",{"3":{"176":1}}],["mono",{"3":{"0":1,"27":1,"39":1,"109":1,"1161":1,"1162":1,"1436":12}}],["monolithic",{"3":{"1359":1,"1396":1}}],["monoliths",{"3":{"1211":1,"2212":1}}],["monolith",{"0":{"1509":1,"1653":1,"1663":1,"1783":1,"1790":1},"1":{"56":1,"57":1,"58":1,"59":1,"60":1,"61":1,"62":1,"63":1,"64":1,"1788":1,"1789":1,"1790":1,"1791":1,"1792":1,"1793":1,"1847":1,"1848":1,"1849":1,"1850":1,"1851":1,"1852":1,"1853":1,"2087":1,"2088":1,"2089":1,"2090":1,"2091":1,"2092":1,"2093":1,"2094":1,"2095":1,"2096":1,"2097":1,"2098":1},"3":{"0":10,"20":1,"30":2,"31":3,"32":1,"47":1,"56":1,"58":2,"59":2,"60":1,"61":1,"62":2,"105":1,"196":1,"201":1,"224":1,"242":1,"243":2,"247":1,"282":1,"329":1,"396":1,"405":1,"448":1,"582":3,"583":1,"587":1,"639":1,"640":1,"664":1,"674":1,"734":1,"781":1,"783":1,"845":1,"931":1,"933":2,"1212":4,"1248":1,"1252":1,"1257":3,"1259":16,"1270":2,"1273":1,"1279":1,"1281":2,"1286":15,"1287":1,"1288":1,"1300":7,"1301":3,"1307":1,"1309":1,"1310":1,"1311":6,"1312":4,"1313":1,"1320":2,"1323":10,"1339":1,"1350":2,"1359":4,"1396":4,"1402":2,"1415":2,"1429":1,"1436":5,"1447":1,"1471":1,"1489":1,"1508":1,"1509":1,"1534":1,"1536":1,"1544":2,"1577":3,"1641":1,"1646":2,"1647":2,"1648":1,"1652":4,"1653":2,"1656":4,"1658":1,"1659":1,"1663":3,"1670":1,"1675":1,"1683":1,"1692":1,"1697":1,"1779":2,"1780":2,"1781":1,"1783":3,"1786":1,"1787":1,"1788":4,"1789":1,"1790":5,"1792":1,"1793":3,"1815":1,"1842":3,"1846":1,"1847":5,"1848":1,"1849":4,"1850":1,"1853":2,"1858":1,"1864":1,"1878":1,"1884":5,"1886":1,"1888":2,"1893":2,"1895":1,"1896":2,"1899":1,"1916":1,"1924":1,"1926":1,"1986":1,"1991":1,"2004":1,"2008":1,"2009":1,"2014":1,"2015":2,"2016":1,"2017":1,"2020":2,"2028":1,"2035":1,"2043":1,"2087":1,"2111":1,"2130":1,"2131":1,"2135":1,"2154":1,"2155":1,"2169":1,"2180":1,"2193":1,"2203":1,"2204":1,"2205":1,"2207":1,"2210":1,"2213":2,"2214":2,"2218":1,"2219":1,"2221":1,"2226":1,"2227":1,"2228":1,"2230":2,"2232":1,"2239":2,"2243":1}}],["movable",{"3":{"373":1,"1247":1,"1436":1}}],["moving",{"3":{"0":4,"33":1,"59":1,"164":1,"166":1,"286":1,"291":1,"295":1,"374":1,"591":1,"609":1,"610":1,"627":1,"631":1,"633":1,"635":1,"691":1,"838":1,"856":1,"891":1,"982":1,"1003":1,"1019":1,"1034":1,"1051":1,"1212":1,"1233":1,"1237":2,"1259":2,"1263":1,"1270":2,"1271":1,"1282":1,"1286":2,"1293":1,"1300":2,"1301":1,"1309":1,"1311":3,"1323":6,"1342":3,"1350":4,"1353":1,"1359":14,"1369":2,"1380":1,"1392":1,"1395":12,"1396":2,"1402":3,"1415":3,"1417":1,"1428":1,"1436":6,"1438":1,"1447":1,"1459":1,"1465":1,"1477":2,"1492":2,"1534":1,"1577":1,"1585":1,"1631":2,"1638":1,"1655":1,"1727":1,"1735":1,"1765":1,"1801":1,"1854":1,"1885":1,"1904":1,"1968":1,"1975":1,"1990":1,"2046":1,"2047":1,"2097":1,"2107":1,"2187":2,"2198":1}}],["movenext",{"3":{"1436":2}}],["movenextasync",{"3":{"1427":1,"1436":2}}],["movement",{"3":{"1145":1,"1496":8,"1575":2,"1582":1,"1585":1,"1769":1}}],["move",{"0":{"139":1,"140":1,"142":1,"1857":1},"3":{"0":10,"24":3,"46":1,"59":1,"102":1,"130":2,"135":1,"136":1,"138":1,"140":1,"141":1,"145":1,"150":2,"165":1,"177":1,"242":1,"254":1,"390":2,"407":1,"440":1,"488":1,"492":1,"493":1,"494":2,"545":1,"590":1,"635":1,"640":1,"676":1,"688":1,"690":1,"691":1,"698":1,"725":2,"726":1,"727":1,"741":1,"765":1,"767":1,"790":1,"791":1,"812":1,"819":1,"827":1,"838":1,"842":1,"849":3,"882":1,"899":1,"921":1,"926":1,"932":1,"954":1,"971":1,"980":1,"1004":3,"1005":1,"1006":3,"1012":3,"1014":1,"1019":1,"1041":1,"1053":1,"1075":1,"1077":1,"1085":1,"1134":1,"1137":1,"1176":1,"1212":1,"1214":1,"1229":1,"1233":1,"1237":1,"1249":1,"1250":2,"1259":4,"1270":2,"1271":1,"1275":1,"1286":5,"1297":1,"1300":6,"1301":1,"1309":2,"1310":1,"1311":5,"1312":1,"1323":7,"1324":13,"1339":5,"1343":1,"1350":2,"1353":1,"1359":8,"1369":3,"1380":1,"1385":1,"1391":1,"1392":1,"1395":8,"1396":11,"1399":1,"1402":7,"1415":6,"1416":1,"1417":2,"1436":22,"1438":1,"1441":1,"1442":1,"1455":1,"1461":1,"1467":2,"1482":1,"1489":3,"1492":2,"1496":8,"1500":1,"1524":1,"1525":1,"1577":2,"1578":1,"1607":1,"1618":1,"1663":1,"1665":1,"1673":1,"1675":1,"1686":1,"1687":1,"1697":1,"1701":1,"1735":1,"1741":1,"1749":1,"1765":2,"1802":1,"1817":1,"1824":1,"1826":1,"1857":1,"1860":1,"1861":1,"1862":1,"1866":1,"1893":1,"1914":1,"1923":1,"1924":1,"1950":1,"1953":1,"1955":1,"2001":1,"2011":1,"2032":1,"2047":1,"2070":2,"2075":1,"2106":1,"2124":1,"2128":1,"2152":1,"2167":1,"2187":1,"2194":1,"2232":1}}],["moves",{"0":{"539":1},"3":{"0":7,"25":1,"57":1,"126":2,"141":1,"147":1,"150":1,"170":1,"198":1,"203":1,"216":1,"228":1,"253":1,"255":2,"258":1,"311":1,"386":1,"387":1,"392":1,"407":1,"459":1,"464":1,"490":1,"535":1,"536":1,"540":2,"591":1,"592":1,"638":1,"651":1,"691":1,"727":1,"743":1,"781":1,"799":1,"803":1,"804":1,"805":1,"812":1,"818":2,"831":1,"840":2,"891":2,"916":1,"955":1,"959":1,"971":1,"974":1,"990":1,"1004":1,"1006":2,"1012":3,"1018":3,"1069":1,"1077":1,"1083":1,"1089":1,"1092":1,"1093":1,"1125":1,"1126":1,"1129":1,"1136":1,"1153":1,"1184":1,"1212":1,"1229":1,"1259":4,"1263":1,"1270":1,"1271":1,"1286":4,"1300":2,"1309":2,"1311":4,"1323":2,"1324":13,"1326":1,"1339":3,"1350":2,"1352":1,"1355":1,"1359":5,"1369":1,"1380":1,"1390":1,"1395":3,"1396":7,"1402":2,"1413":1,"1415":4,"1417":1,"1422":1,"1426":1,"1435":1,"1436":13,"1438":1,"1441":1,"1445":1,"1455":4,"1460":1,"1467":2,"1475":1,"1476":3,"1496":4,"1575":1,"1582":1,"1607":2,"1644":1,"1648":1,"1660":1,"1666":1,"1686":1,"1695":1,"1701":1,"1727":1,"1741":2,"1742":1,"1748":1,"1765":1,"1799":1,"1812":1,"1823":1,"1843":2,"1854":1,"1856":1,"1858":1,"1884":1,"1896":1,"1961":1,"2043":1,"2047":1,"2058":1,"2080":1,"2086":1,"2141":1,"2144":1,"2146":1,"2152":1,"2160":1,"2161":1,"2176":1,"2177":2,"2232":2}}],["moved",{"0":{"95":1},"3":{"0":5,"43":1,"57":1,"59":1,"82":1,"91":1,"93":1,"99":1,"104":3,"111":1,"125":1,"126":1,"130":3,"137":1,"138":5,"139":7,"140":1,"142":2,"161":1,"167":1,"168":1,"171":1,"179":1,"180":1,"201":1,"223":1,"238":1,"241":1,"243":1,"248":1,"250":1,"251":2,"252":2,"254":3,"255":2,"256":3,"257":1,"258":1,"259":1,"261":2,"276":1,"296":1,"322":1,"323":1,"364":1,"365":1,"374":1,"383":1,"395":1,"407":1,"418":2,"424":1,"425":2,"426":1,"428":3,"429":4,"430":1,"446":1,"453":1,"457":2,"463":2,"464":1,"465":1,"468":1,"475":1,"476":1,"478":1,"487":1,"488":1,"489":1,"490":2,"491":1,"492":2,"493":3,"495":2,"497":1,"516":2,"522":1,"524":1,"528":1,"534":2,"536":1,"539":1,"540":2,"543":1,"547":1,"549":1,"551":1,"552":2,"559":1,"607":2,"609":1,"611":1,"618":1,"631":1,"632":1,"638":1,"647":1,"652":1,"677":1,"688":1,"693":1,"696":2,"714":1,"720":1,"747":2,"821":1,"822":1,"825":2,"832":2,"845":1,"867":1,"875":1,"877":1,"909":1,"926":1,"929":1,"934":1,"967":1,"975":1,"983":2,"994":1,"1016":2,"1021":2,"1037":1,"1040":1,"1049":2,"1054":1,"1078":1,"1099":1,"1127":1,"1133":1,"1237":1,"1247":1,"1259":2,"1270":1,"1271":1,"1286":3,"1300":1,"1311":1,"1323":4,"1324":6,"1339":2,"1342":1,"1350":6,"1359":2,"1366":1,"1367":1,"1369":2,"1371":1,"1380":2,"1395":7,"1396":6,"1402":2,"1409":1,"1415":4,"1416":1,"1417":1,"1431":1,"1436":27,"1438":1,"1445":1,"1447":2,"1453":1,"1455":1,"1467":1,"1474":1,"1475":1,"1476":2,"1489":2,"1490":1,"1496":61,"1500":1,"1524":1,"1526":1,"1531":2,"1535":1,"1544":1,"1589":1,"1591":1,"1596":1,"1611":1,"1639":1,"1729":1,"1764":1,"1766":1,"1816":1,"1888":1,"1889":1,"1996":1,"2048":1,"2051":1,"2166":1,"2169":1,"2171":1,"2190":1,"2192":1,"2232":1}}],["mostsevere",{"2":{"109":1,"1222":1,"1225":1},"3":{"109":2,"1222":1,"1225":1,"1229":2,"1311":1,"1312":2}}],["mostly",{"3":{"41":1,"135":1,"267":1,"409":1,"734":1,"1011":1,"1264":1,"1270":1,"1311":1,"1312":1,"1350":1,"1359":1,"1380":1,"1396":1,"1436":4,"1438":1,"1496":2,"1551":1,"1577":1,"1582":1,"1583":1,"1585":2,"1931":1,"2051":1,"2074":1}}],["most",{"0":{"2043":1},"3":{"0":5,"24":2,"69":1,"109":3,"110":1,"111":1,"132":1,"150":1,"157":1,"178":1,"235":1,"243":1,"255":1,"312":1,"329":1,"349":1,"351":1,"352":1,"390":1,"402":1,"459":2,"460":1,"463":1,"470":1,"472":1,"487":1,"500":1,"518":1,"556":1,"584":1,"590":1,"599":1,"625":1,"628":3,"641":1,"650":1,"692":1,"725":1,"742":1,"781":1,"811":1,"819":2,"821":1,"826":1,"830":1,"839":1,"840":1,"842":1,"854":1,"941":1,"947":1,"971":1,"973":1,"984":1,"998":1,"1004":1,"1020":2,"1033":1,"1051":1,"1058":1,"1067":1,"1075":1,"1085":1,"1115":1,"1116":1,"1125":1,"1126":1,"1140":2,"1154":1,"1191":1,"1200":1,"1212":3,"1216":1,"1218":1,"1224":1,"1226":1,"1229":4,"1237":5,"1247":2,"1251":1,"1252":1,"1254":2,"1255":1,"1259":6,"1264":1,"1265":1,"1267":1,"1270":7,"1271":3,"1274":1,"1280":1,"1283":1,"1286":19,"1290":1,"1291":1,"1300":8,"1301":4,"1309":2,"1310":2,"1311":15,"1312":2,"1316":1,"1322":1,"1323":8,"1324":14,"1333":1,"1337":1,"1338":1,"1339":11,"1340":2,"1343":1,"1347":2,"1348":1,"1350":7,"1352":1,"1358":1,"1359":32,"1361":1,"1363":1,"1366":1,"1369":4,"1372":1,"1373":1,"1376":1,"1379":1,"1380":4,"1390":1,"1395":13,"1396":21,"1397":1,"1399":1,"1401":1,"1402":12,"1403":1,"1405":1,"1414":1,"1415":6,"1417":19,"1426":1,"1428":1,"1431":1,"1436":22,"1438":3,"1441":1,"1442":1,"1443":1,"1445":1,"1447":1,"1453":1,"1455":8,"1458":1,"1460":1,"1465":1,"1467":2,"1475":2,"1489":4,"1492":2,"1496":2,"1525":1,"1526":2,"1527":1,"1531":1,"1537":3,"1574":2,"1578":1,"1585":1,"1625":1,"1627":1,"1630":2,"1631":2,"1632":1,"1638":1,"1639":1,"1641":4,"1656":1,"1666":2,"1677":1,"1683":1,"1702":1,"1765":3,"1769":1,"1782":1,"1795":1,"1796":1,"1797":1,"1799":1,"1802":4,"1803":1,"1805":2,"1806":2,"1813":2,"1823":1,"1830":1,"1840":1,"1843":1,"1847":1,"1849":1,"1858":1,"1860":1,"1878":1,"1900":2,"1908":1,"1917":1,"1922":1,"1927":4,"1931":1,"1933":1,"1959":1,"1960":1,"1964":1,"1973":2,"1974":1,"1978":1,"1986":1,"1987":1,"1989":1,"1998":2,"2001":1,"2002":1,"2010":1,"2015":2,"2029":2,"2032":1,"2033":1,"2036":1,"2038":1,"2047":2,"2049":4,"2050":1,"2066":1,"2068":1,"2069":2,"2078":1,"2079":1,"2081":1,"2087":1,"2095":1,"2100":1,"2104":1,"2106":1,"2108":1,"2111":1,"2114":1,"2121":2,"2124":1,"2129":1,"2131":1,"2135":1,"2146":1,"2155":2,"2170":2,"2171":1,"2189":1,"2195":1,"2220":1,"2230":1}}],["modreq",{"3":{"1168":1}}],["modificar",{"3":{"1686":1}}],["modification",{"3":{"564":1,"566":1,"1244":1,"1270":4,"1286":5,"1323":1,"1324":1,"1359":2,"1415":1,"1630":1,"1749":1,"1823":1}}],["modifier",{"3":{"1259":1,"1270":1,"1286":1,"1359":1,"1369":1,"1380":1,"1395":1,"1431":1,"1436":4,"1630":1}}],["modifiers",{"3":{"1214":1}}],["modified",{"2":{"2167":1},"3":{"344":1,"698":1,"714":1,"715":2,"716":1,"1274":1,"1277":1,"1286":17,"1323":1,"1436":1,"1438":1,"1496":2,"1545":1,"1632":1,"1633":1,"1636":1,"1755":1,"1839":1,"1851":1,"1871":1,"1876":1,"2167":1}}],["modifying",{"3":{"1286":1,"1359":1,"1458":1}}],["modify",{"3":{"0":1,"245":2,"248":1,"249":1,"253":1,"254":1,"256":1,"257":1,"279":1,"285":1,"286":1,"340":1,"732":1,"733":1,"854":1,"856":1,"857":1,"1286":1,"1292":1,"1300":4,"1301":6,"1395":1,"1438":3,"1542":1,"1631":1,"1638":1,"1869":1,"1877":2,"1915":1,"2001":1}}],["modalities",{"3":{"1395":1}}],["modally",{"3":{"682":1,"1417":4}}],["modal",{"3":{"0":1,"682":1,"684":1,"1324":1,"1417":8}}],["mode=complete",{"3":{"1472":1}}],["mode=form",{"3":{"1311":1,"1975":1}}],["mode=max",{"3":{"868":1,"1455":4,"1465":2}}],["moderating",{"3":{"1395":1,"1402":3,"1946":1}}],["moderationqueue",{"3":{"1402":2}}],["moderationaction",{"3":{"1199":6,"1203":1,"1207":2,"1402":12}}],["moderation",{"3":{"523":1,"897":2,"900":1,"1342":1,"1349":2,"1350":10,"1353":1,"1355":1,"1359":11,"1378":2,"1380":2,"1381":1,"1388":1,"1395":11,"1396":16,"1398":1,"1399":2,"1401":2,"1402":75,"1438":3,"1626":1,"1627":2,"1629":1,"1630":1,"1631":3,"1632":5,"1635":1,"1748":2,"1750":1,"1760":1,"1763":1,"1765":3,"1771":1}}],["moderateasync",{"3":{"1402":3}}],["moderatequestioncommand",{"3":{"1199":5,"1203":1,"1207":2,"1402":7,"1436":1}}],["moderatequestionhandlertests",{"3":{"1199":1,"1207":3}}],["moderatequestionhandler",{"2":{"522":1,"523":1,"1100":1,"1399":1},"3":{"522":2,"523":2,"897":2,"1100":2,"1199":2,"1203":1,"1207":2,"1247":1,"1270":2,"1350":2,"1396":4,"1399":10,"1402":8,"1436":1}}],["moderated",{"3":{"1192":1,"1202":1,"1203":1,"1350":1,"1402":2}}],["moderate",{"3":{"522":1,"523":1,"897":1,"1100":1,"1203":2,"1207":4,"1247":1,"1270":2,"1350":1,"1395":3,"1396":2,"1399":1,"1402":9,"1626":1,"1632":1,"1748":2,"1760":1,"1765":3,"1768":2,"1771":1}}],["moderators",{"3":{"1396":4,"1399":1,"1402":11,"1748":1,"1765":1}}],["moderator",{"3":{"391":1,"1350":1,"1396":3,"1397":1,"1402":21,"1438":1,"1630":1,"1632":2,"1764":1,"1765":3,"1768":1}}],["modernizing",{"3":{"2213":1,"2218":1,"2220":1}}],["modernize",{"3":{"1396":1}}],["modernization",{"3":{"1012":1,"1526":1,"1531":1,"1532":1,"1544":1,"1576":1,"1577":1,"2220":1}}],["modern",{"3":{"309":1,"743":1,"799":1,"1049":1,"1237":1,"1286":1,"1292":1,"1417":1,"1436":3,"1993":1,"2070":1,"2076":1,"2214":1,"2220":1,"2221":1,"2226":1,"2239":1,"2240":1}}],["modest",{"3":{"1474":1,"2033":2,"2060":1}}],["modes",{"1":{"504":1,"505":1,"506":1,"507":1,"508":1,"509":1,"510":1,"511":1,"512":1,"513":1,"514":1},"3":{"0":1,"16":1,"100":1,"101":1,"185":1,"504":1,"520":1,"536":1,"555":1,"626":2,"666":1,"880":1,"885":1,"1152":1,"1189":1,"1212":1,"1229":1,"1247":2,"1259":2,"1270":1,"1286":4,"1311":3,"1324":5,"1339":1,"1350":1,"1359":3,"1395":2,"1396":1,"1415":2,"1417":2,"1424":1,"1427":1,"1436":7,"1453":2,"1467":1,"1490":2,"1492":1,"1559":1,"1577":1,"1648":1,"1664":1,"1684":1,"1721":1,"1890":1,"1899":1,"1952":1,"2079":1,"2081":1,"2232":1}}],["model=",{"3":{"1596":1}}],["modeltype",{"3":{"1311":2}}],["modeltestcontext",{"3":{"1199":2,"1207":1}}],["modelmetadata",{"3":{"1311":2}}],["modelconfigurationbuilder",{"3":{"1286":1}}],["modelidvalue",{"3":{"1018":2,"1369":1,"1526":1}}],["modelid",{"2":{"1090":1,"1425":1},"3":{"1018":2,"1090":1,"1212":1,"1350":1,"1359":3,"1366":1,"1425":2,"1427":5,"1496":1}}],["modeling",{"3":{"168":1,"1229":1,"1286":1,"1309":1,"1324":1,"1350":5,"1359":3,"1380":2,"1395":2,"1402":1,"1415":1,"1417":1,"1436":1,"1473":1,"1804":1,"1811":1}}],["modelused",{"2":{"1018":1},"3":{"1018":1,"1350":9,"1359":2,"1362":1,"1369":2}}],["modelling",{"3":{"683":1,"1041":1,"1115":1,"1162":1,"1218":1,"1249":1,"1259":1,"1300":1,"1311":1,"1323":1,"1324":1,"1339":1,"1350":3,"1359":3,"1362":1,"1369":1,"1395":2,"1402":3,"1415":2,"1436":1,"1776":1,"2183":1}}],["modelled",{"3":{"0":1,"691":1,"1108":1,"1217":1,"1247":1,"1270":1,"1286":2,"1311":1,"1323":1,"1324":1,"1350":2,"1359":3,"1396":1,"1415":2,"1436":3,"1631":2}}],["modelbindingresult",{"3":{"1311":1}}],["modelbinding",{"3":{"1311":1}}],["modelbinders",{"3":{"330":1,"880":1,"883":1,"1203":1,"1207":4,"1241":1,"1247":1,"1311":3}}],["modelbinder",{"3":{"330":1,"741":1,"1311":1,"1380":1,"1988":1}}],["modelbuilderextensions",{"3":{"1199":2,"1203":1,"1207":2,"1208":1,"1282":3,"1286":14}}],["modelbuilderextensionstests",{"3":{"1198":1,"1199":2,"1207":6,"1286":2}}],["modelbuilder",{"2":{"697":1},"3":{"200":1,"697":1,"715":1,"1208":2,"1282":1,"1286":13,"1396":1}}],["modelvalidationtests",{"3":{"1199":1,"1207":7}}],["modelvalidation",{"2":{"117":1,"128":2,"1596":1},"3":{"117":2,"128":4,"1199":24,"1203":1,"1207":2,"1324":18,"1384":1,"1395":26,"1396":6,"1436":1,"1496":1,"1591":1,"1596":2,"1600":1}}],["modeled",{"3":{"109":1,"1286":3,"1300":3,"1309":3,"1342":2,"1350":5,"1359":2,"1380":3,"1395":2,"1396":4,"1402":2,"1417":1,"1531":1,"1532":1,"2239":1}}],["model",{"0":{"1228":1,"1281":1,"1468":1,"1630":1,"1652":1,"1761":1,"1771":1},"1":{"163":1,"164":1,"165":1,"166":1,"167":1,"168":1,"169":1,"170":1,"171":1,"1088":1,"1089":1,"1090":1,"1091":1,"1092":1,"1093":1,"1094":1,"1095":1,"1096":1,"1148":1,"1149":1,"1150":1,"1151":1,"1152":1,"1153":1,"1154":1,"1155":1,"1156":1,"1157":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":1,"1346":1,"1347":1,"1348":1,"1349":1,"1350":1,"1808":1,"1809":1,"1810":1,"1811":1,"1812":1,"1854":1,"1855":1,"1856":1,"1857":1,"1858":1,"1859":1,"1860":1,"1861":1,"1862":1},"2":{"1083":1,"1594":1},"3":{"0":36,"25":1,"26":1,"38":1,"39":1,"52":1,"53":1,"81":1,"109":2,"117":1,"121":1,"124":1,"126":1,"128":1,"163":1,"166":3,"167":1,"168":1,"185":1,"186":2,"187":1,"188":1,"193":1,"200":2,"201":1,"223":1,"226":1,"227":1,"228":1,"230":4,"274":1,"277":1,"284":1,"291":2,"293":1,"296":1,"313":1,"317":1,"318":3,"319":1,"320":1,"329":1,"332":2,"341":1,"346":1,"350":1,"358":1,"362":1,"384":1,"397":1,"408":1,"434":1,"458":1,"462":1,"470":1,"497":2,"498":1,"501":1,"507":1,"560":1,"563":2,"564":2,"566":1,"567":1,"572":1,"582":1,"587":2,"599":1,"600":1,"605":1,"612":1,"641":1,"650":1,"651":1,"656":1,"657":2,"670":1,"686":1,"690":1,"697":1,"698":5,"699":5,"707":3,"708":1,"720":1,"733":1,"737":1,"748":1,"755":1,"756":2,"758":1,"761":1,"762":1,"783":1,"790":1,"797":1,"798":1,"811":1,"821":1,"834":1,"842":1,"857":1,"889":5,"890":1,"891":4,"903":2,"904":1,"905":2,"906":1,"907":3,"910":1,"913":2,"914":5,"915":3,"918":1,"929":2,"931":1,"932":1,"934":1,"946":1,"950":1,"974":1,"1011":2,"1012":6,"1013":1,"1016":2,"1017":7,"1018":19,"1019":8,"1020":5,"1025":1,"1026":2,"1027":1,"1033":2,"1034":2,"1035":1,"1036":1,"1042":1,"1044":1,"1049":1,"1050":3,"1051":3,"1052":2,"1058":1,"1059":2,"1060":1,"1062":1,"1068":1,"1069":2,"1076":1,"1083":10,"1084":2,"1085":3,"1088":1,"1089":2,"1090":9,"1091":12,"1092":5,"1093":5,"1094":3,"1118":1,"1133":1,"1137":1,"1139":1,"1140":2,"1142":1,"1148":1,"1149":1,"1150":1,"1151":1,"1152":1,"1155":1,"1160":1,"1168":1,"1170":1,"1174":1,"1176":2,"1177":1,"1178":2,"1180":1,"1192":3,"1202":2,"1203":2,"1212":11,"1213":1,"1220":1,"1230":2,"1235":1,"1237":11,"1241":2,"1247":5,"1249":2,"1259":5,"1263":1,"1269":1,"1270":11,"1271":3,"1272":1,"1273":5,"1276":1,"1277":1,"1278":1,"1279":2,"1280":2,"1281":11,"1282":1,"1283":1,"1284":1,"1286":207,"1287":2,"1290":3,"1296":1,"1297":2,"1300":37,"1302":1,"1309":10,"1310":4,"1311":18,"1312":4,"1320":1,"1323":29,"1324":108,"1326":1,"1328":1,"1339":6,"1340":2,"1342":3,"1347":2,"1348":2,"1350":54,"1359":44,"1362":3,"1363":1,"1365":1,"1366":5,"1368":1,"1369":30,"1371":1,"1373":2,"1374":1,"1380":5,"1381":1,"1384":4,"1386":1,"1395":200,"1396":54,"1401":1,"1402":16,"1409":1,"1410":1,"1412":1,"1415":10,"1416":3,"1417":9,"1418":3,"1419":2,"1420":6,"1422":6,"1423":1,"1424":3,"1425":2,"1426":3,"1427":41,"1428":2,"1429":2,"1431":5,"1435":8,"1436":118,"1438":12,"1441":1,"1442":2,"1443":1,"1446":1,"1453":3,"1455":14,"1456":1,"1461":1,"1462":4,"1467":8,"1469":2,"1476":1,"1488":3,"1489":2,"1490":2,"1492":7,"1496":10,"1503":1,"1508":1,"1511":1,"1524":2,"1526":15,"1527":1,"1530":1,"1531":1,"1532":2,"1534":8,"1535":1,"1537":2,"1541":4,"1543":1,"1544":2,"1545":2,"1548":3,"1553":11,"1554":1,"1556":1,"1572":2,"1575":3,"1576":1,"1577":8,"1578":1,"1581":1,"1582":3,"1583":1,"1585":5,"1591":5,"1592":1,"1594":1,"1596":2,"1599":4,"1600":1,"1614":1,"1621":1,"1627":1,"1630":2,"1631":2,"1635":1,"1636":1,"1638":3,"1639":3,"1640":1,"1641":4,"1651":1,"1653":1,"1663":1,"1664":1,"1686":2,"1697":3,"1704":1,"1721":1,"1724":1,"1727":1,"1735":1,"1757":1,"1776":1,"1779":2,"1781":1,"1784":1,"1790":1,"1795":1,"1799":1,"1805":1,"1807":1,"1808":3,"1809":1,"1810":1,"1812":2,"1849":10,"1851":4,"1853":1,"1854":1,"1858":7,"1859":1,"1861":1,"1862":1,"1863":3,"1864":1,"1866":2,"1868":1,"1871":1,"1872":1,"1874":1,"1875":1,"1894":1,"1898":1,"1901":2,"1908":1,"1920":1,"1923":1,"1925":1,"1927":1,"1928":1,"1930":2,"1948":1,"1961":1,"1964":1,"1978":1,"1980":1,"1982":1,"1984":1,"1990":3,"1991":1,"1993":1,"1994":2,"1996":1,"2008":1,"2018":2,"2053":2,"2055":1,"2061":2,"2064":1,"2068":1,"2077":1,"2078":1,"2083":1,"2086":1,"2114":1,"2125":2,"2140":1,"2141":1,"2146":1,"2154":1,"2156":3,"2166":1,"2170":2,"2171":4,"2172":2,"2173":2,"2174":2,"2175":1,"2176":2,"2177":3,"2178":2,"2179":2,"2180":7,"2192":1,"2195":1,"2198":1,"2199":1,"2206":2,"2207":1,"2209":1,"2220":2,"2227":1,"2229":1,"2230":2,"2232":9,"2239":2}}],["modelsnapshot",{"3":{"1497":1}}],["modelstatedictionary",{"3":{"1311":1}}],["models",{"0":{"1347":1,"1356":1,"1384":1},"3":{"0":1,"95":1,"115":1,"117":1,"128":1,"165":1,"224":1,"242":1,"255":1,"659":1,"731":1,"810":1,"932":1,"1028":1,"1083":2,"1085":2,"1234":1,"1237":1,"1286":4,"1311":2,"1324":7,"1339":1,"1340":2,"1347":1,"1350":6,"1359":2,"1380":7,"1395":7,"1396":3,"1402":6,"1415":1,"1436":20,"1488":1,"1496":1,"1543":1,"1567":1,"1577":2,"1600":1,"1803":1,"2144":1}}],["mode",{"0":{"1270":1,"2076":1},"1":{"270":1,"271":1,"272":1,"273":1,"274":1,"275":1,"276":1,"277":1,"553":1,"554":1,"555":1,"556":1,"557":1,"558":1,"559":1,"560":1,"928":1,"929":1,"930":1,"931":1,"932":1,"933":1,"934":1,"935":1,"2081":1,"2082":1,"2083":1,"2084":1,"2085":1,"2086":1},"3":{"0":17,"18":1,"20":1,"23":1,"31":1,"100":1,"101":1,"135":1,"146":1,"147":1,"168":1,"216":1,"226":1,"234":1,"235":1,"236":1,"243":2,"245":5,"255":1,"259":1,"265":1,"270":1,"272":1,"273":1,"283":1,"361":1,"368":2,"373":3,"401":1,"483":1,"507":2,"508":1,"516":1,"518":1,"519":1,"520":1,"526":1,"527":1,"529":1,"530":2,"531":2,"532":1,"536":1,"537":1,"553":1,"555":4,"556":12,"557":2,"558":6,"560":2,"592":1,"609":1,"610":1,"618":2,"622":1,"626":2,"633":6,"648":1,"653":1,"684":1,"699":1,"700":1,"702":1,"716":1,"718":1,"740":1,"758":1,"765":1,"767":1,"809":1,"811":1,"825":1,"845":1,"856":2,"860":1,"863":1,"865":2,"867":3,"868":1,"873":2,"876":4,"877":1,"881":1,"883":1,"885":1,"923":1,"928":1,"940":1,"954":1,"955":1,"956":1,"982":1,"997":1,"1018":2,"1034":1,"1055":1,"1067":1,"1074":2,"1091":2,"1099":1,"1100":2,"1101":2,"1125":1,"1212":7,"1228":1,"1229":4,"1237":1,"1240":1,"1247":1,"1257":3,"1259":21,"1270":1,"1271":1,"1286":14,"1288":1,"1300":11,"1301":2,"1311":9,"1323":6,"1324":41,"1326":1,"1332":1,"1334":1,"1336":1,"1339":19,"1350":1,"1357":1,"1359":9,"1362":1,"1369":2,"1384":1,"1389":1,"1391":1,"1395":21,"1396":17,"1402":3,"1415":2,"1416":2,"1417":6,"1427":9,"1432":1,"1433":1,"1436":45,"1438":3,"1441":3,"1442":1,"1443":1,"1445":6,"1449":2,"1450":1,"1451":1,"1453":4,"1454":1,"1455":4,"1456":1,"1459":1,"1460":2,"1461":1,"1463":3,"1467":9,"1469":1,"1472":1,"1473":1,"1475":1,"1476":1,"1478":2,"1486":2,"1487":1,"1488":2,"1490":2,"1491":1,"1492":9,"1496":1,"1524":1,"1526":3,"1531":2,"1534":1,"1535":1,"1566":1,"1577":12,"1582":1,"1585":1,"1589":1,"1591":5,"1596":1,"1600":1,"1608":1,"1641":1,"1643":2,"1644":1,"1645":2,"1648":1,"1649":1,"1655":1,"1660":1,"1661":2,"1669":1,"1670":1,"1673":1,"1675":4,"1696":1,"1731":5,"1742":1,"1750":1,"1773":2,"1790":1,"1825":1,"1842":2,"1851":1,"1895":2,"1896":1,"1914":1,"1922":1,"1923":2,"1948":1,"1951":1,"1959":1,"1961":1,"1993":1,"1996":1,"2001":1,"2020":1,"2027":1,"2031":1,"2036":1,"2043":1,"2048":1,"2050":1,"2056":1,"2070":1,"2073":1,"2076":1,"2079":2,"2080":3,"2081":3,"2086":2,"2089":1,"2142":1,"2145":1,"2146":1,"2148":1,"2150":1,"2169":1,"2174":1,"2183":1,"2204":1,"2210":1,"2232":4}}],["modulo",{"3":{"1286":1,"1436":1,"1475":1}}],["modularity",{"3":{"1436":3}}],["modular",{"0":{"1509":1,"1783":1},"1":{"56":1,"57":1,"58":1,"59":1,"60":1,"61":1,"62":1,"63":1,"64":1,"1788":1,"1789":1,"1790":1,"1791":1,"1792":1,"1793":1,"2087":1,"2088":1,"2089":1,"2090":1,"2091":1,"2092":1,"2093":1,"2094":1,"2095":1,"2096":1,"2097":1,"2098":1},"3":{"0":1,"30":1,"56":1,"58":3,"62":1,"329":1,"396":1,"405":1,"582":1,"781":1,"905":1,"1211":1,"1212":1,"1259":2,"1270":1,"1290":1,"1300":5,"1310":1,"1312":2,"1323":3,"1324":1,"1359":1,"1395":1,"1396":2,"1402":2,"1436":3,"1455":1,"1489":1,"1534":1,"1536":1,"1544":1,"1646":1,"1647":1,"1659":1,"1675":1,"1692":1,"1779":1,"1780":2,"1781":1,"1787":1,"1788":1,"1793":1,"1864":1,"1878":1,"1886":1,"1986":1,"2009":1,"2087":1,"2155":1,"2204":1,"2205":1,"2210":1,"2212":1,"2213":2,"2214":2,"2218":1,"2219":1,"2221":1,"2227":1,"2230":1,"2239":2,"2243":1}}],["moduledefinition",{"3":{"1436":3}}],["moduledomains",{"3":{"1436":2}}],["moduledomain",{"3":{"1431":1,"1436":4}}],["modulebearingmap",{"3":{"1436":2}}],["modulelayerisolated",{"3":{"1436":1}}],["modulelayer",{"3":{"1436":1}}],["modulelessmap",{"3":{"1436":1}}],["moduleless",{"3":{"1436":1}}],["moduleloaderlogger",{"3":{"1311":1}}],["moduleloadertests",{"3":{"583":7,"586":1,"1199":1,"1207":16,"1315":1,"1323":2,"1438":2}}],["moduleloader",{"0":{"1881":1},"2":{"58":1,"59":1,"60":1,"63":1,"117":1,"295":2,"582":1,"587":1,"633":1,"636":1,"1378":1,"1413":1,"1509":1,"1700":1,"1701":1,"1884":1,"1886":1,"2019":1,"2026":1,"2094":1,"2097":1},"3":{"58":1,"59":3,"60":1,"63":3,"117":2,"122":1,"295":3,"582":1,"583":18,"584":5,"585":6,"586":1,"587":1,"633":1,"636":1,"674":1,"980":1,"1199":8,"1202":1,"1203":2,"1207":2,"1286":2,"1309":4,"1311":21,"1313":1,"1315":28,"1323":20,"1350":2,"1359":1,"1369":3,"1377":1,"1378":1,"1380":7,"1396":6,"1413":1,"1415":4,"1431":1,"1436":5,"1438":1,"1447":1,"1496":2,"1509":1,"1534":1,"1577":1,"1653":2,"1662":1,"1684":1,"1700":1,"1701":1,"1728":1,"1790":1,"1881":1,"1884":2,"1886":1,"2019":1,"2026":1,"2094":1,"2097":1}}],["moduletests",{"3":{"1436":2,"1489":1}}],["modulerequiredlayeroverrides",{"3":{"1436":3,"1489":1}}],["moduleinfrastructures",{"3":{"1436":1}}],["moduleinternallayersareisolated",{"3":{"1436":3}}],["moduleinternallayers",{"3":{"1436":2}}],["moduleinitializer",{"3":{"1433":1,"1436":2,"1488":1}}],["moduleisolationtests",{"3":{"1199":1,"1207":2,"1436":4,"1489":3,"1591":1}}],["moduleisolationtestsbasetests",{"3":{"1199":1,"1207":3,"1436":6,"1489":1}}],["moduleisolationtestsbase",{"3":{"587":1,"1199":2,"1207":2,"1431":1,"1436":8,"1489":1,"1654":1}}],["moduleof",{"3":{"1431":1,"1436":2}}],["modulepath",{"3":{"1324":4,"1417":1}}],["moduleprobemap",{"3":{"1199":2,"1207":1,"1436":6}}],["moduleenabled",{"2":{"1314":1,"1882":1},"3":{"1314":1,"1882":1}}],["moduleapis",{"3":{"1436":1}}],["moduleapplications",{"3":{"1436":2}}],["moduleapplication",{"3":{"1431":1,"1436":13}}],["moduleapplicationdbcontext",{"2":{"1363":1,"1404":1,"1412":1,"1500":1,"1685":1,"1686":1},"3":{"1199":3,"1203":3,"1207":6,"1363":5,"1369":17,"1396":8,"1404":2,"1412":2,"1415":6,"1496":2,"1500":1,"1651":1,"1685":1,"1686":1}}],["moduleassembly",{"2":{"1830":1},"3":{"1270":2,"1271":1,"1359":4,"1415":2,"1830":1}}],["moduleassemblies",{"2":{"1881":1},"3":{"0":1,"583":1,"1311":3,"1323":2,"1881":1}}],["modulehost",{"3":{"1311":10,"1312":1,"1323":3,"1380":1}}],["modulehostcontext",{"2":{"1881":1},"3":{"1199":4,"1203":1,"1207":2,"1311":16,"1315":3,"1323":6,"1881":1}}],["modulehostextensionstests",{"3":{"1199":1,"1207":2,"1311":5,"1438":2}}],["modulehostextensions",{"3":{"583":1,"674":1,"677":1,"980":2,"1199":1,"1203":1,"1207":2,"1208":1,"1311":9,"1315":3,"1323":6,"1339":1}}],["moduleconformancetestsbasetests",{"3":{"1199":1,"1207":9,"1436":32,"1489":4}}],["moduleconformancetestsbase",{"2":{"585":1,"1497":1},"3":{"585":2,"1199":6,"1207":2,"1323":2,"1428":1,"1431":3,"1436":15,"1438":1,"1489":2,"1496":2,"1497":1}}],["modulecontrollerfeatureprovidertests",{"3":{"1199":1,"1207":2}}],["modulecontrollerfeatureprovider",{"2":{"583":1},"3":{"583":2,"585":3,"1199":3,"1203":1,"1207":2,"1311":10,"1323":4}}],["modulecode",{"2":{"1133":1},"3":{"0":1,"1133":1,"1436":2}}],["modulename",{"2":{"1773":1},"3":{"405":1,"583":2,"585":1,"1270":5,"1286":2,"1300":1,"1311":3,"1315":2,"1323":5,"1324":2,"1380":1,"1415":1,"1436":2,"1773":1,"1856":1}}],["modulenames",{"2":{"80":1,"82":1},"3":{"80":1,"82":1,"1436":12}}],["modulenameconventionstests",{"3":{"1004":1,"1199":1,"1207":2,"1286":1,"1300":3,"1438":2}}],["modulenameconventions",{"3":{"0":1,"1004":2,"1199":5,"1203":1,"1207":2,"1263":1,"1270":3,"1286":8,"1300":10,"1662":1,"2043":1}}],["moduleshared",{"3":{"1436":6}}],["modulesdeclareeveryexpectedlayer",{"2":{"1489":1},"3":{"1436":2,"1489":2}}],["modulesdeclarelayers",{"3":{"1436":12}}],["modulessectionname",{"3":{"1324":1}}],["modulessettingstests",{"3":{"1199":1,"1207":2}}],["modulessettings",{"2":{"585":1,"1315":1,"1882":1,"2012":1,"2094":1},"3":{"0":1,"583":5,"584":1,"585":4,"586":1,"674":1,"979":1,"1199":10,"1203":1,"1207":2,"1311":18,"1313":1,"1315":2,"1320":4,"1323":9,"1436":4,"1662":1,"1882":1,"2012":1,"2094":1,"2098":1}}],["modulesbyname",{"3":{"1323":1}}],["modulescanning",{"2":{"115":1,"1262":1},"3":{"115":1,"122":1,"536":1,"552":1,"922":1,"927":1,"980":1,"1207":1,"1208":1,"1262":7,"1263":2,"1270":8,"1271":4,"1300":1,"1311":3,"1316":7,"1323":6,"1359":8,"1396":6,"1415":13}}],["modulesettings",{"2":{"585":1,"674":1,"1882":2},"3":{"0":1,"583":4,"584":1,"674":2,"677":1,"1199":5,"1203":1,"1207":2,"1313":1,"1315":1,"1320":4,"1323":5,"1882":2}}],["modules",{"0":{"1514":1,"2077":1},"1":{"1878":1,"1879":1,"1880":1,"1881":1,"1882":1,"1883":1,"1884":1,"1885":1,"1886":1},"2":{"576":1,"584":1,"586":1,"1004":1,"1639":1,"1884":1},"3":{"0":3,"42":2,"46":1,"52":1,"53":1,"54":1,"59":6,"60":1,"63":2,"72":3,"73":2,"80":6,"97":1,"109":1,"128":3,"132":1,"165":1,"184":1,"186":5,"230":6,"265":1,"295":4,"311":1,"318":1,"325":2,"344":1,"350":2,"352":3,"353":4,"360":1,"384":2,"388":2,"390":1,"391":2,"396":1,"401":1,"444":1,"448":2,"459":2,"463":2,"465":2,"466":1,"470":8,"475":8,"476":10,"477":8,"478":1,"491":1,"492":1,"493":2,"494":2,"495":2,"497":6,"499":2,"502":1,"522":1,"523":1,"524":1,"535":1,"536":1,"539":1,"545":15,"550":1,"551":1,"552":2,"556":8,"559":1,"572":1,"576":1,"577":2,"583":27,"584":1,"585":11,"586":5,"618":1,"633":1,"648":1,"649":4,"650":1,"651":1,"653":1,"657":6,"674":7,"675":2,"676":3,"690":1,"707":1,"724":3,"725":5,"728":2,"741":1,"743":2,"782":14,"784":2,"789":2,"790":7,"793":6,"794":4,"798":1,"799":1,"803":1,"804":1,"805":1,"809":1,"813":2,"846":1,"881":4,"889":1,"897":4,"900":1,"905":1,"924":4,"926":6,"946":2,"954":5,"956":1,"958":1,"960":1,"963":1,"980":2,"988":4,"995":1,"996":1,"1004":3,"1017":1,"1025":5,"1026":11,"1049":1,"1052":1,"1054":1,"1055":1,"1058":1,"1059":1,"1082":4,"1086":1,"1090":1,"1099":2,"1100":4,"1103":1,"1116":12,"1133":2,"1140":1,"1142":1,"1150":4,"1152":1,"1168":7,"1191":1,"1195":1,"1200":1,"1201":1,"1203":3,"1207":37,"1211":1,"1212":2,"1213":1,"1237":38,"1239":5,"1242":1,"1247":20,"1259":8,"1264":8,"1265":2,"1270":43,"1271":35,"1284":1,"1286":73,"1300":98,"1301":3,"1304":4,"1305":1,"1308":5,"1309":31,"1310":18,"1311":64,"1312":15,"1313":3,"1314":3,"1315":10,"1317":1,"1318":3,"1320":1,"1323":102,"1324":72,"1339":1,"1341":3,"1342":12,"1344":7,"1345":5,"1346":1,"1347":16,"1348":5,"1349":12,"1350":254,"1355":1,"1359":546,"1369":60,"1371":6,"1373":4,"1374":1,"1376":1,"1377":6,"1380":112,"1381":1,"1389":1,"1390":2,"1395":272,"1396":377,"1402":63,"1403":1,"1404":3,"1405":1,"1406":1,"1415":216,"1416":16,"1417":12,"1426":1,"1427":6,"1431":1,"1435":2,"1436":130,"1437":20,"1438":26,"1455":5,"1467":1,"1473":1,"1487":1,"1489":6,"1491":1,"1492":2,"1496":10,"1505":1,"1509":2,"1526":9,"1534":3,"1535":1,"1538":1,"1540":1,"1541":1,"1544":2,"1571":3,"1577":2,"1588":1,"1590":1,"1591":6,"1626":1,"1638":1,"1639":2,"1649":1,"1650":3,"1651":1,"1653":1,"1654":2,"1656":1,"1658":1,"1659":1,"1662":1,"1666":1,"1667":1,"1675":1,"1681":1,"1684":3,"1685":6,"1686":7,"1690":1,"1700":1,"1705":1,"1718":1,"1722":1,"1726":1,"1728":4,"1729":2,"1731":1,"1760":1,"1763":1,"1764":14,"1765":31,"1770":1,"1772":1,"1773":2,"1777":1,"1779":1,"1781":1,"1785":1,"1789":1,"1790":2,"1793":2,"1811":1,"1848":4,"1864":1,"1875":1,"1877":2,"1878":2,"1881":5,"1882":3,"1883":2,"1884":1,"1885":1,"1886":3,"1891":1,"1896":1,"1962":2,"1982":1,"2015":1,"2017":1,"2027":1,"2067":1,"2083":1,"2087":1,"2090":1,"2094":3,"2097":2,"2103":1,"2110":2,"2111":1,"2202":1,"2207":1,"2214":2,"2230":1,"2232":1}}],["module",{"0":{"1265":1,"1308":1,"1314":1,"1349":1,"1377":1,"1438":1,"1650":1,"1656":1,"1683":1,"1684":1,"1722":1,"1728":1,"1729":1,"1747":1,"1748":1,"1749":1,"1751":1,"1772":1,"1880":1,"1882":1,"1883":1,"1884":1,"2097":1},"1":{"56":1,"57":1,"58":1,"59":1,"60":1,"61":1,"62":1,"63":1,"64":1,"580":1,"581":1,"582":1,"583":1,"584":1,"585":1,"586":1,"587":1,"1313":1,"1314":1,"1315":1,"1316":1,"1317":1,"1318":1,"1319":1,"1320":1,"1321":1,"1322":1,"1323":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":1,"1346":1,"1347":1,"1348":1,"1349":1,"1350":1,"1396":1,"1403":1,"1404":1,"1405":1,"1406":1,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1413":1,"1414":1,"1415":1,"1416":1,"1681":1,"1682":1,"1683":1,"1684":1,"1685":1,"1686":1,"1687":1,"1688":1,"1689":1,"1690":1,"1691":1,"2015":1,"2016":1,"2017":1,"2018":1,"2019":1,"2020":1,"2021":1,"2022":1,"2023":1,"2024":1,"2025":1,"2026":1,"2027":1,"2028":1,"2087":1,"2088":1,"2089":1,"2090":1,"2091":1,"2092":1,"2093":1,"2094":1,"2095":1,"2096":1,"2097":1,"2098":1},"2":{"1731":1,"1771":1,"1882":1,"1884":1,"2103":1},"3":{"0":52,"24":1,"30":1,"47":3,"51":1,"53":6,"56":1,"57":1,"58":4,"59":12,"60":6,"62":3,"63":4,"80":2,"81":4,"82":1,"109":2,"117":3,"121":2,"122":2,"127":2,"128":2,"131":2,"132":1,"143":1,"166":2,"168":1,"184":1,"186":16,"187":7,"265":2,"295":4,"300":1,"318":2,"324":1,"325":1,"328":1,"329":1,"341":2,"350":2,"401":1,"405":4,"439":1,"459":2,"460":1,"470":2,"471":3,"472":1,"473":2,"474":1,"490":1,"493":1,"498":1,"536":3,"544":1,"545":3,"547":2,"556":1,"558":1,"562":1,"564":1,"572":4,"576":1,"580":1,"582":6,"583":30,"584":13,"585":20,"586":1,"587":7,"631":3,"632":2,"633":18,"635":8,"636":3,"639":1,"645":1,"648":4,"649":12,"650":6,"651":3,"673":1,"674":2,"688":1,"690":2,"692":2,"697":1,"698":1,"700":1,"707":2,"709":1,"725":3,"727":1,"750":1,"773":1,"778":1,"781":1,"782":3,"783":1,"784":1,"792":1,"798":2,"799":6,"801":1,"802":1,"803":1,"805":1,"809":1,"846":2,"880":1,"889":2,"891":1,"893":1,"905":2,"906":1,"912":1,"920":2,"921":2,"922":7,"923":2,"924":4,"926":3,"933":1,"956":1,"963":1,"979":1,"980":2,"1004":8,"1007":1,"1008":1,"1016":1,"1018":5,"1020":1,"1021":2,"1022":1,"1026":2,"1027":1,"1034":1,"1048":1,"1049":2,"1050":1,"1052":1,"1053":1,"1058":1,"1059":4,"1077":1,"1083":1,"1084":1,"1090":2,"1094":3,"1095":2,"1116":1,"1117":2,"1118":1,"1132":1,"1133":8,"1134":2,"1135":3,"1140":1,"1143":1,"1145":5,"1147":1,"1149":1,"1150":3,"1151":1,"1152":2,"1155":5,"1168":7,"1170":1,"1192":7,"1194":1,"1195":1,"1201":4,"1202":11,"1203":11,"1211":3,"1212":16,"1213":1,"1229":3,"1234":1,"1237":7,"1241":1,"1244":1,"1247":6,"1248":1,"1249":5,"1252":2,"1257":1,"1259":29,"1260":1,"1261":1,"1262":4,"1263":6,"1265":7,"1269":2,"1270":64,"1271":37,"1273":4,"1274":1,"1276":1,"1279":2,"1281":2,"1282":2,"1283":4,"1284":1,"1286":87,"1289":1,"1297":5,"1300":68,"1301":3,"1304":3,"1308":4,"1309":67,"1310":17,"1311":90,"1312":13,"1313":5,"1314":8,"1315":11,"1316":4,"1320":3,"1321":2,"1322":1,"1323":108,"1324":119,"1328":1,"1333":1,"1337":1,"1339":9,"1340":4,"1341":6,"1342":1,"1344":1,"1345":1,"1347":1,"1348":3,"1349":5,"1350":79,"1351":2,"1352":2,"1353":2,"1354":7,"1355":7,"1357":1,"1359":299,"1360":3,"1362":2,"1363":5,"1366":1,"1367":2,"1368":6,"1369":39,"1370":3,"1377":5,"1378":1,"1379":2,"1380":43,"1381":2,"1384":1,"1386":2,"1389":1,"1391":1,"1392":1,"1394":1,"1395":55,"1396":258,"1398":1,"1401":1,"1402":40,"1403":3,"1404":6,"1405":2,"1409":6,"1410":1,"1412":1,"1414":2,"1415":141,"1416":31,"1417":25,"1427":2,"1429":3,"1431":4,"1436":417,"1437":11,"1438":23,"1441":1,"1445":3,"1446":1,"1455":7,"1458":1,"1467":1,"1486":1,"1487":13,"1488":1,"1489":29,"1490":2,"1496":29,"1509":2,"1512":1,"1513":2,"1514":1,"1526":3,"1534":2,"1538":1,"1541":1,"1544":2,"1545":1,"1550":1,"1551":1,"1571":1,"1572":1,"1576":1,"1577":5,"1582":4,"1591":7,"1596":1,"1599":1,"1602":2,"1618":3,"1621":1,"1627":2,"1631":1,"1638":3,"1639":4,"1646":5,"1647":5,"1649":4,"1650":10,"1651":16,"1652":1,"1653":6,"1654":6,"1656":3,"1658":2,"1660":2,"1661":9,"1662":7,"1663":7,"1664":2,"1665":2,"1666":6,"1667":1,"1669":4,"1670":4,"1671":7,"1672":1,"1673":3,"1674":2,"1675":1,"1681":7,"1682":1,"1683":2,"1684":18,"1685":2,"1686":11,"1687":2,"1688":3,"1689":1,"1690":3,"1691":2,"1692":1,"1694":2,"1695":3,"1697":1,"1698":1,"1700":4,"1701":6,"1702":1,"1703":3,"1705":1,"1717":2,"1718":2,"1719":2,"1720":1,"1721":1,"1722":6,"1724":4,"1726":6,"1727":25,"1728":20,"1729":10,"1730":5,"1731":9,"1746":1,"1748":1,"1749":2,"1751":1,"1757":1,"1760":1,"1761":1,"1763":1,"1764":1,"1765":7,"1769":1,"1771":3,"1772":7,"1773":1,"1775":3,"1779":2,"1781":1,"1783":2,"1788":1,"1789":2,"1790":5,"1793":3,"1815":3,"1816":1,"1817":1,"1823":1,"1824":5,"1830":3,"1838":1,"1842":3,"1849":6,"1853":1,"1855":2,"1856":4,"1864":1,"1865":1,"1866":1,"1868":1,"1875":3,"1878":5,"1879":3,"1880":5,"1881":9,"1882":11,"1883":3,"1884":7,"1885":4,"1886":5,"1888":1,"1889":1,"1891":2,"1893":1,"1899":1,"1916":1,"1925":1,"1930":1,"1933":1,"1946":2,"1950":1,"1956":4,"1957":4,"1962":4,"1963":7,"1965":2,"1978":1,"1981":1,"2012":2,"2014":1,"2015":4,"2016":3,"2019":2,"2026":2,"2030":1,"2042":2,"2043":5,"2066":1,"2074":1,"2077":10,"2080":1,"2083":2,"2086":1,"2087":4,"2088":2,"2089":3,"2090":1,"2094":9,"2095":5,"2096":1,"2097":11,"2098":5,"2103":3,"2104":1,"2110":1,"2111":2,"2113":1,"2119":1,"2126":1,"2130":1,"2134":1,"2136":1,"2138":1,"2139":1,"2143":1,"2156":1,"2161":1,"2166":1,"2167":1,"2168":2,"2178":1,"2179":1,"2188":1,"2189":1,"2207":1,"2208":1,"2209":2,"2210":2,"2220":2,"2227":3,"2228":1,"2230":3,"2232":8,"2234":1}}],["miami",{"3":{"2220":1}}],["mientras",{"3":{"1686":1}}],["mi",{"3":{"1526":1,"1590":2,"1591":2,"1600":1}}],["mildly",{"3":{"1396":1}}],["miles",{"3":{"1395":1}}],["million",{"3":{"1228":1,"1229":1,"1467":1}}],["millions",{"3":{"1150":1}}],["millisecond",{"3":{"930":1,"1399":1,"1402":2,"1436":1,"1946":1}}],["milliseconds",{"3":{"235":1,"397":1,"1268":1,"1270":1,"1323":1,"1339":3,"1436":2,"1443":1,"1638":1,"2050":1,"2052":1,"2156":1}}],["might",{"3":{"1247":1,"1286":1,"1323":2,"1359":2,"1366":1,"1396":2,"1415":2,"1417":1,"1424":1,"1436":2,"1803":1,"1810":1,"1868":2,"1886":1,"1913":1,"1930":1,"1954":3}}],["migrating",{"3":{"294":1,"1035":1,"1048":1,"1051":1,"1116":1,"1323":1,"1416":1,"1436":1,"1444":1}}],["migrationid",{"3":{"1323":3}}],["migrationattribute",{"3":{"1323":1}}],["migrationapplyprooftests",{"3":{"1199":1,"1207":4,"1323":4,"1438":2,"1577":1,"1586":1}}],["migrationproof",{"3":{"1323":3}}],["migrationpolicy",{"3":{"1175":2,"1199":8,"1203":1,"1207":2,"1278":1,"1279":1,"1280":2,"1286":16,"1311":1,"1496":1}}],["migrationbuilder",{"2":{"566":1},"3":{"566":1,"1323":3}}],["migrationless",{"3":{"296":1,"1311":5}}],["migrationsassemblyprefix",{"3":{"1436":1}}],["migrationsassemblysettingname",{"3":{"1286":3}}],["migrationsassembly",{"3":{"1286":12}}],["migrationsfixture",{"2":{"1453":1},"3":{"564":1,"1199":1,"1203":1,"1206":1,"1207":3,"1319":1,"1323":6,"1487":1,"1490":1}}],["migrations",{"0":{"1652":1,"1688":1},"1":{"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"294":1,"295":1,"296":1},"2":{"291":1,"563":1,"564":2,"1037":1,"1044":1,"1180":1,"1369":1,"1497":1,"1614":1,"1652":1,"1657":1,"1688":2,"1697":3,"1719":1,"1722":2,"1727":1,"1728":3,"1729":2,"1731":1,"2097":1,"2192":1},"3":{"0":13,"49":1,"164":1,"166":1,"168":1,"169":1,"195":6,"200":2,"288":1,"290":7,"291":3,"295":2,"341":5,"343":1,"345":2,"350":2,"403":2,"563":2,"564":19,"568":2,"632":1,"633":5,"698":2,"699":1,"700":1,"701":1,"757":1,"872":1,"891":9,"905":3,"906":1,"918":1,"966":1,"1004":1,"1018":2,"1026":2,"1034":6,"1037":1,"1044":1,"1052":1,"1067":1,"1081":1,"1082":1,"1085":4,"1087":1,"1135":1,"1140":2,"1143":1,"1175":5,"1177":1,"1179":1,"1180":1,"1209":273,"1212":1,"1213":1,"1273":4,"1276":1,"1279":5,"1280":3,"1283":2,"1284":4,"1286":75,"1290":1,"1300":1,"1311":15,"1317":1,"1319":1,"1320":1,"1323":13,"1326":1,"1359":1,"1361":1,"1369":9,"1396":11,"1415":8,"1431":1,"1436":17,"1438":3,"1441":2,"1442":1,"1445":1,"1455":13,"1461":1,"1467":6,"1469":1,"1473":2,"1476":1,"1486":1,"1487":1,"1490":2,"1492":2,"1496":3,"1497":3,"1510":2,"1526":1,"1545":4,"1572":1,"1577":5,"1590":1,"1591":3,"1614":2,"1647":1,"1652":9,"1657":3,"1661":2,"1664":4,"1673":1,"1681":1,"1683":1,"1684":2,"1688":2,"1689":1,"1690":1,"1697":12,"1701":1,"1704":2,"1718":1,"1719":7,"1722":3,"1724":1,"1726":1,"1727":11,"1728":11,"1729":3,"1731":1,"1851":1,"1852":1,"2043":2,"2089":1,"2097":5,"2192":1,"2232":1}}],["migration",{"0":{"1697":1},"2":{"1697":1},"3":{"0":23,"15":2,"26":2,"41":2,"68":1,"78":1,"81":1,"167":1,"168":2,"195":4,"197":2,"200":3,"273":1,"275":2,"277":1,"290":1,"291":6,"292":3,"293":8,"294":1,"296":1,"308":1,"311":1,"348":2,"350":4,"352":1,"360":1,"403":1,"461":1,"474":2,"548":1,"562":4,"563":5,"564":10,"565":5,"566":6,"568":1,"598":1,"600":1,"609":1,"625":1,"630":1,"633":5,"635":1,"657":2,"691":1,"697":1,"698":1,"699":1,"700":1,"702":1,"707":1,"711":1,"715":2,"733":1,"756":3,"757":2,"759":3,"762":2,"797":4,"798":1,"799":4,"800":2,"801":1,"802":2,"803":1,"804":1,"805":1,"806":1,"810":1,"811":1,"812":1,"846":1,"848":2,"853":1,"855":1,"891":5,"893":1,"905":2,"907":3,"924":1,"929":1,"931":2,"932":1,"939":1,"947":1,"948":2,"965":1,"972":1,"973":1,"1004":1,"1016":1,"1026":1,"1034":2,"1035":1,"1037":1,"1042":1,"1044":1,"1046":1,"1048":2,"1049":2,"1050":1,"1051":4,"1052":1,"1054":1,"1059":1,"1060":1,"1061":1,"1082":1,"1085":5,"1089":1,"1145":1,"1174":1,"1175":1,"1177":1,"1178":2,"1179":1,"1212":4,"1259":4,"1273":3,"1281":1,"1284":1,"1286":24,"1290":1,"1293":1,"1300":7,"1309":2,"1311":8,"1316":1,"1319":3,"1323":24,"1324":1,"1326":1,"1339":1,"1350":1,"1369":8,"1402":1,"1408":1,"1415":5,"1429":1,"1436":5,"1438":1,"1455":21,"1460":1,"1461":2,"1467":8,"1469":1,"1471":1,"1474":1,"1475":1,"1477":1,"1478":1,"1490":1,"1492":1,"1496":2,"1497":1,"1508":1,"1526":2,"1545":1,"1577":1,"1586":1,"1591":3,"1600":1,"1614":1,"1641":3,"1650":1,"1651":1,"1652":3,"1661":2,"1663":1,"1664":2,"1684":1,"1688":3,"1689":1,"1697":3,"1701":1,"1709":1,"1718":1,"1719":3,"1721":1,"1722":1,"1724":1,"1727":3,"1728":3,"1729":2,"1731":1,"1765":1,"1784":2,"1787":1,"1788":1,"1810":1,"1842":1,"1848":1,"1851":2,"1852":1,"1853":1,"1854":1,"1875":1,"1876":1,"1889":2,"1891":1,"1948":1,"1978":1,"2008":1,"2013":1,"2033":1,"2038":1,"2083":2,"2085":1,"2097":1,"2188":1,"2192":2,"2202":1,"2220":3,"2232":4}}],["migrateasync",{"2":{"291":1},"3":{"291":1,"1278":1,"1286":4,"1311":3,"1323":2,"1577":1,"1664":1}}],["migrated",{"3":{"151":1,"200":1,"290":1,"563":1,"573":1,"633":1,"800":1,"1034":1,"1280":1,"1286":11,"1311":5,"1323":1,"1369":1,"1415":1,"1432":1,"1436":2,"1473":1,"1488":1,"1496":2,"1638":1,"1674":1,"1684":2,"1692":1,"1719":1,"1727":1,"1728":1,"1889":1,"2036":1,"2097":1}}],["migrates",{"3":{"0":3,"79":1,"294":1,"296":1,"468":1,"564":1,"698":1,"703":1,"1034":2,"1036":1,"1050":1,"1212":2,"1286":9,"1300":1,"1311":2,"1339":1,"1436":1,"1449":1,"1455":1,"1467":1,"1475":1,"1491":1,"1722":1,"1729":1}}],["migrate",{"2":{"291":1,"563":1,"1364":1,"1614":1,"1652":1,"1719":1},"3":{"0":2,"49":1,"102":1,"290":3,"291":4,"293":1,"295":1,"296":2,"311":1,"365":1,"563":1,"567":1,"846":1,"1212":1,"1286":4,"1311":6,"1320":1,"1323":4,"1364":1,"1417":1,"1429":1,"1436":1,"1438":2,"1455":2,"1467":2,"1473":2,"1490":1,"1570":1,"1614":1,"1652":1,"1653":1,"1665":1,"1684":1,"1719":2,"1792":1,"1905":1,"2055":1,"2232":1}}],["migrator",{"1":{"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"294":1,"295":1,"296":1},"3":{"0":2,"20":1,"288":1,"291":3,"292":1,"295":1,"296":2,"563":1,"756":2,"757":1,"762":1,"1180":1,"1192":1,"1212":1,"1286":1,"1324":1,"1396":2,"1455":1,"1467":1,"1469":1,"1473":1,"1476":1,"1477":1,"1496":2,"1652":1,"1664":1,"2232":2}}],["mime",{"3":{"1116":1,"1286":4,"1324":2,"1417":3,"2148":1}}],["mib",{"3":{"875":1,"1125":1,"1324":2,"1467":1}}],["microphone",{"3":{"1339":1,"1417":6}}],["microphone=",{"3":{"214":1,"2149":1}}],["micro",{"3":{"1135":1,"1286":1,"1359":1,"1417":2}}],["microbenchmark",{"3":{"860":1}}],["microsecond",{"3":{"820":1,"2032":1}}],["microseconds",{"3":{"71":1,"819":1,"827":1,"1126":1,"1270":1,"1324":1,"1337":1,"1339":1,"1948":1,"2024":1}}],["microserviceextraction",{"3":{"1576":1,"1577":1}}],["microserviceextractiontests",{"2":{"54":1,"59":1,"150":1,"1525":1,"1535":1,"1591":1,"1790":1,"1793":1,"1842":1,"2016":1,"2028":1,"2043":1,"2049":1},"3":{"54":1,"59":1,"62":1,"150":1,"1199":2,"1207":4,"1257":1,"1259":3,"1436":12,"1489":7,"1494":1,"1525":1,"1526":2,"1535":1,"1576":1,"1577":1,"1586":1,"1591":2,"1790":1,"1793":1,"1842":1,"2016":1,"2028":1,"2043":1,"2049":1}}],["microserviceextractiontestsbase",{"2":{"1074":1,"1077":1,"1654":1,"1663":1,"2042":1,"2104":1},"3":{"0":1,"1074":2,"1077":1,"1199":3,"1207":2,"1312":1,"1431":1,"1436":9,"1489":1,"1576":1,"1577":1,"1654":1,"1663":1,"2042":1,"2104":1}}],["microservice",{"3":{"846":1,"1034":1,"1211":1,"1214":1,"1248":1,"1259":1,"1286":2,"1311":2,"1312":2,"1315":1,"1323":3,"1380":1,"1440":1,"1488":1,"1489":1,"1526":1,"1646":1,"1647":1,"1692":1,"1781":1,"1815":1,"1847":1,"1882":1,"1895":1,"2005":1,"2017":1,"2054":1,"2227":1}}],["microservices",{"0":{"1544":1,"1783":1},"1":{"1788":1,"1789":1,"1790":1,"1791":1,"1792":1,"1793":1},"3":{"46":1,"60":1,"1216":1,"1229":1,"1237":1,"1247":2,"1257":1,"1259":8,"1270":3,"1272":1,"1273":1,"1281":1,"1286":14,"1287":1,"1290":1,"1297":1,"1300":14,"1301":3,"1303":1,"1307":1,"1309":11,"1310":3,"1311":13,"1312":7,"1313":1,"1323":14,"1324":5,"1326":1,"1338":1,"1339":12,"1349":1,"1350":15,"1354":1,"1355":1,"1359":15,"1363":1,"1369":3,"1370":1,"1378":1,"1380":7,"1391":1,"1395":4,"1396":36,"1402":14,"1410":1,"1413":1,"1415":16,"1436":50,"1441":1,"1444":1,"1446":1,"1447":1,"1450":1,"1467":1,"1469":1,"1499":1,"1526":1,"1530":1,"1532":1,"1536":1,"1545":1,"1576":1,"1577":1,"1586":1,"1590":1,"1591":1,"1595":1,"1596":1,"1597":1,"1659":1,"1675":1,"1779":1,"1780":1,"1783":1,"1784":1,"1787":2,"1788":3,"1792":1,"1793":2,"1796":1,"1799":1,"1842":1,"1846":1,"1847":1,"1868":1,"1886":1,"1892":1,"1895":1,"1899":1,"2009":1,"2014":1,"2026":1,"2028":2,"2115":1,"2169":1,"2193":1,"2204":1,"2205":1,"2211":1,"2212":1,"2213":3,"2214":1,"2217":1,"2218":2,"2219":1,"2221":1,"2228":2,"2243":1}}],["microsoft",{"0":{"2105":1},"2":{"0":1,"135":1,"137":1,"138":1,"253":1,"300":2,"401":2,"409":1,"451":1,"452":1,"676":1,"732":1,"733":1,"1090":1,"1091":2,"1213":2,"1247":1,"1259":1,"1270":2,"1300":1,"1301":2,"1311":6,"1312":5,"1323":2,"1324":5,"1339":3,"1369":3,"1395":1,"1416":1,"1417":1,"1427":6,"1477":1,"1492":1,"1611":1,"1685":1,"1686":1,"1722":1,"1727":1,"1728":1,"1815":1,"1865":1,"1915":1,"1916":1,"2012":1,"2043":2,"2099":1,"2100":1,"2105":1,"2120":1,"2137":1,"2177":1,"2178":1,"2180":1},"3":{"0":6,"135":2,"137":1,"138":1,"253":1,"300":2,"397":1,"401":2,"409":1,"413":1,"451":1,"452":1,"609":2,"640":1,"673":1,"676":1,"732":1,"733":1,"868":2,"870":1,"954":1,"980":1,"1018":1,"1036":1,"1090":3,"1091":5,"1116":1,"1154":1,"1212":1,"1213":11,"1215":1,"1244":1,"1247":3,"1259":4,"1270":12,"1286":26,"1300":19,"1301":6,"1309":18,"1310":2,"1311":40,"1312":5,"1323":25,"1324":61,"1339":21,"1359":20,"1369":26,"1380":4,"1395":7,"1396":11,"1402":1,"1415":14,"1416":9,"1417":7,"1422":1,"1425":2,"1427":25,"1432":1,"1436":71,"1437":1,"1438":1,"1441":1,"1443":1,"1445":2,"1446":1,"1453":1,"1455":1,"1459":1,"1465":2,"1466":1,"1467":13,"1472":1,"1477":1,"1481":1,"1485":1,"1486":1,"1488":1,"1489":4,"1492":1,"1577":4,"1586":1,"1611":1,"1654":1,"1671":1,"1685":2,"1686":2,"1722":1,"1727":1,"1728":1,"1815":1,"1827":1,"1865":2,"1915":1,"1916":1,"2012":1,"2043":2,"2058":1,"2099":1,"2100":1,"2101":1,"2105":2,"2108":1,"2120":1,"2137":1,"2177":1,"2178":1,"2180":1,"2212":2,"2213":1,"2217":1,"2218":1,"2220":1,"2224":1,"2238":2,"2240":2,"2241":2}}],["mixes",{"3":{"649":1,"1176":1,"1286":1,"1310":1,"1396":1}}],["mixedentity",{"3":{"1199":2,"1207":1}}],["mixed",{"0":{"92":1,"96":1},"3":{"0":3,"62":1,"91":2,"92":1,"93":1,"95":4,"96":2,"98":2,"99":2,"100":1,"101":1,"102":3,"104":1,"105":1,"229":1,"267":1,"692":1,"790":1,"795":1,"837":1,"838":1,"869":1,"981":1,"1034":1,"1178":1,"1279":1,"1286":5,"1307":1,"1309":2,"1312":2,"1324":2,"1328":2,"1337":1,"1339":7,"1359":1,"1396":1,"1400":1,"1415":1,"1438":1,"1441":1,"1442":1,"1449":1,"1467":2,"1552":1,"1557":1,"1670":1,"1946":1,"1950":1,"2025":3}}],["mix",{"3":{"392":1,"1018":1,"1020":1,"1229":1,"1297":1,"1359":2,"1395":1,"1415":1}}],["mixing",{"3":{"0":1,"95":1,"941":1,"990":1,"1035":1,"1237":1,"1301":1,"1311":1,"1324":1,"1415":1,"1436":1,"1438":1,"1555":1,"1630":1}}],["mitigates",{"3":{"237":1,"1826":1}}],["mitigated",{"3":{"13":1,"61":1,"88":1,"119":1,"188":1,"209":1,"812":1,"947":1,"1055":1,"1474":1,"1526":1,"1867":1,"1964":1}}],["mitigations",{"3":{"692":1,"1534":1,"1548":1}}],["mitigation",{"1":{"232":1,"233":1,"234":1,"235":1,"236":1,"237":1,"238":1,"239":1},"3":{"70":1,"121":1,"188":1,"232":1,"312":1,"318":1,"684":1,"725":1,"743":1,"883":1,"947":1,"1402":1,"1417":1,"1436":1,"1459":1,"1474":1,"1478":1,"1630":1,"1885":1,"1964":1,"2033":2,"2037":1,"2038":1,"2107":1}}],["mit",{"3":{"0":1,"650":1,"682":2,"683":1,"707":1,"708":1,"1059":1,"1323":1,"1661":1,"1948":1,"2239":1}}],["misclassified",{"3":{"1436":1}}],["misconstructed",{"3":{"1427":1}}],["misconfigurations",{"3":{"1438":1}}],["misconfiguration",{"3":{"0":1,"178":1,"214":1,"215":1,"401":1,"501":1,"674":1,"846":1,"1060":1,"1259":1,"1269":1,"1272":1,"1286":7,"1300":2,"1320":2,"1323":4,"1324":3,"1339":3,"1396":1,"1415":1,"1427":1,"1436":1,"1721":1,"1984":1,"2001":1}}],["misconfigured",{"3":{"0":1,"399":1,"415":1,"535":1,"700":1,"792":1,"1212":1,"1259":2,"1264":1,"1270":2,"1300":1,"1311":1,"1317":1,"1324":4,"1332":1,"1339":1,"1396":1,"1417":1,"1419":1,"1427":1,"1430":1,"1443":1,"1670":2,"1823":1,"1894":1,"2012":1,"2014":2,"2159":1,"2161":1}}],["miscounts",{"3":{"607":1,"1436":1}}],["mishandled",{"3":{"1416":1}}],["miswiring",{"3":{"1339":1}}],["misled",{"3":{"1436":1}}],["misleads",{"3":{"1436":1}}],["mislead",{"3":{"1369":1,"1415":1,"1436":2,"1474":1}}],["misleading",{"3":{"1324":1,"1359":1,"1415":1,"1417":1,"1467":1,"1927":1,"2048":1}}],["mislabeled",{"3":{"1311":1,"1359":1}}],["misbehaves",{"3":{"1327":1,"1339":1,"2134":1}}],["misbehaved",{"3":{"1312":1}}],["misbehaving",{"3":{"827":1,"848":1,"1339":1}}],["misalignment",{"3":{"1496":1}}],["misaligned",{"3":{"1359":1,"1631":1}}],["misattributed",{"3":{"1436":1}}],["misapplication",{"3":{"1311":1}}],["misauthenticates",{"3":{"312":1}}],["misrouting",{"3":{"1311":1,"1438":1}}],["misrouted",{"3":{"1300":1,"1438":1}}],["misreported",{"3":{"1323":1,"1396":1,"1436":1}}],["misreports",{"3":{"1110":1,"1324":1,"1339":1,"1481":1}}],["misregistration",{"3":{"1270":1}}],["misreadable",{"3":{"1467":1}}],["misreads",{"3":{"827":1,"1300":1,"1339":1}}],["misread",{"3":{"136":1,"1311":1,"1312":1,"1323":1,"1362":1,"2044":1}}],["misplaced",{"3":{"1436":1}}],["misplacedtokenfixtureservice",{"3":{"1199":2,"1207":1,"1436":2}}],["mispositioned",{"3":{"135":1}}],["misfires",{"3":{"1561":1}}],["misfire",{"3":{"1043":1,"1436":1,"2203":1}}],["mistype",{"3":{"1396":1}}],["mistyped",{"3":{"838":1,"1286":1,"1338":1,"1339":4,"1359":3,"1427":1,"1438":1}}],["mistakes",{"3":{"1311":1}}],["mistaken",{"3":{"109":1,"235":1,"1339":2,"1396":1,"1417":1,"1423":1,"1433":1,"1436":6,"1459":1,"1910":1,"1929":1}}],["mistake",{"3":{"0":1,"122":1,"132":1,"140":1,"215":1,"216":1,"290":1,"391":1,"465":1,"676":1,"727":1,"776":2,"801":1,"828":2,"1041":1,"1066":1,"1132":1,"1241":1,"1247":3,"1259":1,"1270":1,"1286":2,"1300":1,"1311":4,"1317":1,"1323":1,"1328":1,"1333":1,"1359":1,"1369":1,"1436":2,"1686":2,"1927":1,"1930":1,"1939":1,"1943":1,"2043":1,"2056":1,"2100":1,"2106":1,"2107":1,"2108":1,"2152":1,"2153":1,"2183":2}}],["misdiagnose",{"3":{"829":1}}],["misordered",{"3":{"751":1}}],["misordering",{"3":{"749":1,"1825":1}}],["misused",{"3":{"1396":1,"1436":1}}],["misuse",{"3":{"735":1,"1396":2,"1436":1,"1605":1,"1739":1}}],["misnamedtokenfixtureservice",{"3":{"1199":2,"1207":1,"1436":2}}],["misnamed",{"3":{"135":1,"1436":1}}],["mis",{"3":{"111":1,"324":1,"733":1,"1228":1,"1229":1,"1286":2,"1300":1,"1311":1,"1323":2,"1395":1,"1396":2,"1436":4,"1952":1}}],["misshapen",{"3":{"1324":1}}],["misspelled",{"3":{"840":1,"1311":1,"1339":1,"1427":1,"1652":1}}],["miss",{"3":{"157":1,"159":1,"166":1,"171":1,"343":1,"399":1,"404":1,"459":1,"463":1,"538":1,"549":1,"733":2,"734":1,"849":1,"964":1,"995":1,"1000":1,"1006":1,"1107":1,"1118":1,"1162":1,"1237":2,"1241":1,"1242":1,"1247":4,"1263":1,"1267":2,"1268":1,"1270":16,"1278":1,"1279":1,"1283":1,"1286":21,"1300":12,"1301":27,"1309":1,"1311":14,"1323":3,"1324":4,"1339":2,"1350":1,"1355":1,"1359":6,"1369":1,"1388":1,"1395":12,"1396":11,"1415":2,"1417":4,"1427":1,"1436":11,"1438":4,"1443":1,"1444":1,"1538":2,"1600":2,"1651":1,"1653":1,"1655":1,"1668":1,"1684":1,"1708":1,"1810":1,"1849":1,"1909":2,"1911":1,"1917":2,"1923":2,"1945":1,"1989":1,"1998":1,"2006":1,"2048":1,"2135":1,"2155":1,"2156":1,"2159":1}}],["misses",{"3":{"24":1,"159":1,"243":1,"255":1,"618":1,"762":1,"980":1,"1241":1,"1247":4,"1267":1,"1270":3,"1286":1,"1311":1,"1323":1,"1324":2,"1339":1,"1359":2,"1396":4,"1397":1,"1415":1,"1417":1,"1436":6,"1496":1,"1641":1,"1668":1,"1911":1,"1989":1}}],["missed",{"3":{"0":3,"235":1,"301":1,"520":1,"558":1,"627":1,"658":1,"707":2,"890":1,"1036":1,"1041":1,"1099":1,"1101":1,"1174":1,"1176":1,"1212":1,"1269":1,"1270":2,"1271":1,"1286":1,"1300":2,"1301":1,"1309":1,"1311":1,"1323":2,"1324":2,"1334":1,"1339":1,"1359":1,"1395":1,"1396":4,"1399":1,"1424":1,"1427":1,"1436":5,"1438":2,"1459":1,"1467":1,"1474":1,"1577":1,"1585":1,"1708":1,"1709":1,"1829":1,"1918":1,"1933":2,"1948":2,"1961":1,"2138":1,"2183":2,"2192":1,"2193":1,"2232":1}}],["missingsubscription",{"2":{"1472":1},"3":{"1472":1}}],["missingroot",{"3":{"1436":1}}],["missingrequired",{"2":{"1687":1},"3":{"1396":5,"1687":2}}],["missingrecordingharness",{"3":{"1199":2,"1207":1}}],["missingcode",{"3":{"1324":1}}],["missingaccesstokencode",{"3":{"1324":2}}],["missingaccesstoken",{"3":{"1324":2}}],["missingoverridefixture",{"3":{"1199":2,"1207":1,"1436":10}}],["missingtokenfixtureservice",{"3":{"1199":2,"1207":1,"1436":3}}],["missing",{"0":{"1755":1,"1776":1},"3":{"0":8,"67":1,"79":1,"102":1,"108":1,"136":1,"155":1,"159":1,"160":1,"188":1,"214":1,"230":1,"265":1,"283":1,"302":1,"305":1,"318":7,"320":2,"322":1,"323":1,"324":1,"326":2,"342":1,"350":3,"370":1,"371":1,"375":1,"383":1,"411":1,"415":1,"448":2,"484":1,"534":1,"538":2,"549":1,"554":1,"572":1,"573":1,"576":1,"583":1,"584":1,"585":1,"591":1,"599":1,"601":1,"602":1,"609":1,"610":1,"617":1,"626":2,"632":1,"633":2,"642":1,"648":1,"667":1,"673":1,"676":1,"684":1,"700":1,"707":1,"725":1,"757":1,"765":1,"780":1,"790":2,"791":1,"795":1,"819":1,"820":1,"828":1,"838":1,"842":1,"844":1,"871":1,"879":2,"881":1,"888":2,"915":1,"916":1,"921":1,"926":1,"948":1,"972":1,"974":1,"1018":1,"1042":1,"1049":1,"1058":1,"1067":1,"1084":1,"1092":1,"1093":1,"1109":1,"1163":1,"1184":1,"1188":1,"1212":1,"1246":1,"1250":1,"1259":2,"1265":1,"1266":1,"1270":8,"1271":2,"1282":1,"1286":12,"1297":1,"1300":23,"1301":3,"1309":2,"1311":20,"1312":3,"1320":1,"1323":13,"1324":35,"1336":1,"1339":6,"1343":1,"1349":1,"1350":9,"1352":1,"1359":51,"1369":5,"1372":1,"1380":9,"1384":1,"1395":17,"1396":19,"1402":9,"1406":1,"1411":1,"1415":11,"1416":1,"1417":10,"1421":1,"1422":1,"1427":4,"1430":1,"1431":1,"1432":1,"1436":60,"1438":3,"1440":1,"1442":2,"1443":1,"1449":2,"1454":1,"1455":3,"1459":2,"1466":1,"1467":6,"1472":1,"1480":1,"1489":1,"1491":1,"1492":3,"1496":30,"1524":2,"1525":1,"1526":3,"1531":2,"1558":1,"1577":1,"1591":1,"1600":1,"1605":1,"1631":1,"1640":1,"1641":3,"1668":1,"1684":1,"1685":1,"1700":2,"1702":1,"1719":1,"1727":2,"1728":1,"1729":2,"1731":1,"1739":1,"1748":1,"1749":2,"1765":1,"1803":1,"1839":1,"1843":1,"1844":1,"1862":1,"1881":1,"1887":1,"1889":1,"1913":1,"1924":1,"1944":1,"1948":1,"1951":1,"1959":1,"1964":2,"1968":1,"1975":2,"1986":1,"1987":2,"1993":1,"1994":3,"1995":3,"1996":2,"1997":2,"2001":4,"2012":1,"2033":1,"2044":1,"2045":1,"2056":2,"2067":1,"2081":1,"2097":1,"2100":1,"2107":2,"2122":1,"2123":1,"2131":1,"2138":1,"2149":1,"2167":1,"2168":2,"2169":2,"2173":1,"2180":1,"2188":1,"2189":1,"2194":1,"2195":1,"2232":3}}],["mismatched",{"3":{"511":1,"854":1,"1270":1,"1286":4,"1294":1,"1300":6,"1309":1,"1311":1,"1323":1,"1324":1,"1359":1,"1417":1,"1436":4,"1641":1,"1765":1}}],["mismatches",{"2":{"1997":1},"3":{"0":2,"501":1,"1270":2,"1415":1,"1496":4,"1635":1,"1670":1,"1761":1,"1997":1,"2232":1}}],["mismatch",{"3":{"0":1,"91":1,"245":1,"251":1,"254":1,"257":1,"259":1,"264":1,"318":1,"319":1,"322":2,"323":1,"350":1,"452":1,"499":2,"500":2,"609":1,"658":1,"659":1,"732":1,"907":1,"926":1,"1069":1,"1212":1,"1234":1,"1237":1,"1270":10,"1286":4,"1300":4,"1301":1,"1311":4,"1323":4,"1324":5,"1339":3,"1350":2,"1359":7,"1369":1,"1396":2,"1402":1,"1415":2,"1416":1,"1417":1,"1427":1,"1436":7,"1454":1,"1467":1,"1490":1,"1496":1,"1577":2,"1585":1,"1747":1,"1764":1,"1902":1,"1922":1,"1955":1,"1995":1,"1997":1,"2074":1,"2232":1}}],["midpointrounding",{"3":{"1359":1,"1369":1}}],["midsavecontextcreatingdbcontext",{"2":{"1498":1},"3":{"1198":1,"1199":3,"1207":1,"1496":1,"1498":1}}],["midnight",{"3":{"403":1,"612":1,"1348":1,"1350":1,"1355":2,"1393":1,"1395":6,"1438":1,"1631":1}}],["middle",{"0":{"139":1,"1423":1,"2053":1},"3":{"27":1,"130":1,"135":1,"139":1,"602":1,"1092":2,"1124":1,"1271":1,"1286":1,"1300":1,"1323":1,"1324":6,"1350":1,"1369":1,"1395":1,"1396":3,"1417":2,"1436":7,"1685":1,"1794":1,"1800":1,"1818":1,"1887":1,"1950":1,"2059":1,"2060":1,"2073":1,"2170":1,"2236":1}}],["middlewares",{"3":{"829":1,"1311":2}}],["middlewarepipelinestep",{"3":{"749":1,"1199":3,"1203":1,"1207":2,"1311":9}}],["middlewarepipelinestepnames",{"2":{"243":1,"256":1,"258":1,"259":1,"696":1,"749":1,"751":1,"1311":2},"3":{"243":2,"256":3,"258":2,"259":1,"261":1,"459":1,"696":1,"698":2,"749":2,"751":1,"1199":4,"1203":1,"1207":2,"1311":13,"1436":4}}],["middlewarepipelineordertests",{"2":{"572":1},"3":{"572":2,"749":4,"1199":2,"1207":4,"1311":1,"1436":10,"1438":1,"1487":1,"1488":3,"1489":3}}],["middlewarepipelineordertestsbase",{"2":{"572":1,"696":1,"698":1,"747":1,"749":1,"751":1,"1428":1,"1488":1,"2055":1},"3":{"0":1,"459":2,"464":2,"572":2,"696":1,"698":1,"747":1,"749":2,"751":1,"1199":3,"1207":2,"1311":8,"1428":1,"1436":15,"1438":1,"1488":5,"1489":1,"2055":1}}],["middlewarepipelinebuildertests",{"3":{"776":1,"777":1,"1199":1,"1207":2,"1311":1,"1438":1}}],["middlewarepipelinebuilder",{"2":{"103":1,"179":1,"259":1,"457":1,"459":2,"464":2,"465":1,"466":1,"696":1,"698":1,"747":3,"749":3,"1496":1,"1666":1},"3":{"0":1,"103":2,"179":2,"243":1,"256":1,"258":1,"259":1,"261":1,"457":1,"459":5,"464":6,"465":1,"466":1,"696":1,"698":5,"747":3,"749":14,"751":2,"774":2,"777":1,"1108":1,"1111":1,"1124":1,"1150":1,"1156":1,"1198":1,"1199":5,"1203":1,"1207":2,"1301":2,"1311":31,"1436":7,"1438":1,"1496":3,"1666":1,"2066":1}}],["middleware",{"0":{"2149":1},"1":{"211":1,"212":1,"213":1,"214":1,"215":1,"216":1,"217":1,"218":1,"219":1,"220":1,"221":1,"746":1,"747":1,"748":1,"749":1,"750":1,"751":1,"752":1,"753":1,"1311":1,"2147":1,"2148":1,"2149":1,"2150":1,"2151":1,"2152":1,"2153":1},"3":{"0":10,"31":2,"33":1,"57":1,"59":3,"86":1,"109":7,"118":1,"125":2,"135":1,"174":1,"175":1,"182":1,"211":1,"212":1,"214":3,"215":1,"217":1,"219":1,"234":1,"235":1,"236":1,"237":2,"243":1,"256":1,"265":1,"280":1,"282":1,"301":1,"306":1,"342":1,"397":2,"407":2,"409":2,"415":2,"459":11,"460":4,"462":3,"463":6,"464":4,"465":1,"466":2,"503":1,"571":1,"573":1,"696":2,"698":2,"700":1,"702":1,"703":1,"741":1,"746":1,"748":2,"749":5,"750":1,"753":1,"773":1,"774":1,"775":1,"778":2,"825":2,"826":2,"827":6,"833":1,"910":1,"1125":1,"1126":1,"1129":1,"1192":2,"1202":2,"1203":12,"1207":36,"1212":9,"1222":1,"1229":5,"1270":2,"1286":5,"1296":1,"1297":1,"1300":23,"1301":2,"1304":1,"1309":1,"1310":1,"1311":75,"1312":2,"1323":6,"1324":14,"1334":1,"1336":3,"1337":1,"1338":1,"1339":38,"1359":1,"1370":1,"1379":1,"1380":1,"1396":3,"1415":3,"1416":2,"1429":1,"1436":19,"1438":6,"1441":1,"1443":2,"1447":3,"1486":1,"1487":2,"1488":3,"1496":4,"1526":1,"1540":1,"1577":2,"1611":1,"1615":1,"1617":1,"1659":1,"1660":1,"1666":2,"1670":1,"1671":1,"1753":1,"1774":1,"1825":1,"1827":3,"1873":1,"1920":1,"1934":1,"1968":2,"1972":2,"1973":1,"1975":1,"1998":1,"2000":3,"2024":2,"2066":3,"2083":1,"2146":1,"2147":2,"2149":2,"2151":1,"2153":4,"2160":1,"2208":1,"2227":1,"2230":2,"2232":5}}],["mid",{"0":{"2066":1},"3":{"0":3,"20":1,"26":1,"194":1,"517":1,"674":1,"691":1,"707":1,"741":1,"743":1,"759":1,"767":1,"881":1,"954":1,"996":1,"998":1,"1042":1,"1101":1,"1247":1,"1251":1,"1255":2,"1259":3,"1267":1,"1270":4,"1276":1,"1286":6,"1300":1,"1311":6,"1323":9,"1324":8,"1339":1,"1359":3,"1369":1,"1380":2,"1390":1,"1395":8,"1396":16,"1400":1,"1402":1,"1415":2,"1417":3,"1432":1,"1436":6,"1438":1,"1455":1,"1456":1,"1460":1,"1475":3,"1488":2,"1496":3,"1684":1,"1749":1,"1765":1,"1766":1,"1835":1,"1841":1,"1889":1,"1909":1,"1911":1,"1948":1,"2035":1,"2066":1,"2069":1,"2158":1,"2188":1,"2232":1}}],["minoptions",{"3":{"1398":1,"1402":10}}],["minority",{"3":{"689":1,"741":1,"1380":1}}],["minor",{"2":{"1733":2},"3":{"147":1,"1004":1,"1075":1,"1324":1,"1496":1,"1517":1,"1526":3,"1532":1,"1537":1,"1576":1,"1577":1,"1591":2,"1599":1,"1673":1,"1675":1,"1733":3,"1735":1}}],["mind",{"3":{"1323":1,"1355":1,"1359":1,"1398":1}}],["mincount",{"3":{"1237":1}}],["minvalue",{"2":{"1352":1},"3":{"1300":1,"1311":2,"1350":2,"1352":1,"1359":1,"1369":1,"1395":2,"1465":1}}],["minvaluespec",{"3":{"1199":2,"1207":1,"1496":1}}],["minverskip=true",{"3":{"1454":1}}],["minver",{"3":{"0":1,"147":1,"939":1,"1212":1,"1213":1,"1453":1,"1454":3,"1492":1,"1577":1,"1733":1,"2112":2}}],["mini",{"3":{"2239":1}}],["miniprofiler",{"3":{"1262":2,"1270":6,"1278":1,"1284":1,"1286":15,"1311":4,"1316":1,"1323":1,"1438":1,"1664":1,"1753":1,"1774":2}}],["miniprofilerextensions",{"3":{"980":1,"1199":1,"1203":1,"1207":2,"1208":1,"1270":1,"1311":4,"1323":2}}],["miniature",{"0":{"1392":1},"3":{"1217":1,"1230":1,"1291":1,"1300":2,"1301":1,"1309":2,"1350":1,"1359":1,"1395":1,"1417":1,"1431":1,"1436":1,"2090":1}}],["minimisation",{"3":{"1396":1}}],["minimizing",{"3":{"1396":1,"1445":1,"1458":1}}],["minimizes",{"3":{"1395":1}}],["minimized",{"3":{"1359":1}}],["minimization",{"3":{"1237":1,"1359":2,"1396":1,"1415":2,"1417":1}}],["minimaltlsversion",{"3":{"1467":1}}],["minimality",{"3":{"1324":1}}],["minimalism",{"3":{"1300":1}}],["minimal",{"3":{"0":1,"31":1,"59":1,"448":2,"632":1,"674":1,"1004":1,"1050":1,"1213":1,"1229":1,"1237":1,"1247":1,"1259":2,"1284":1,"1286":6,"1289":1,"1300":8,"1309":1,"1311":8,"1313":1,"1323":4,"1324":6,"1339":1,"1350":2,"1376":1,"1380":2,"1395":2,"1396":2,"1402":3,"1415":2,"1416":3,"1417":1,"1429":1,"1430":1,"1436":21,"1438":1,"1441":1,"1443":1,"1445":1,"1454":1,"1489":1,"1492":1,"1542":1,"1546":1,"1555":1,"1563":1,"1638":2,"1658":1,"1810":1,"1880":1,"1898":1,"2055":1,"2094":1,"2131":2,"2221":1,"2228":1,"2232":1}}],["minimumtlsversion",{"3":{"1467":1}}],["minimumthroughput",{"2":{"100":1},"3":{"100":1,"819":1,"1312":2,"1339":2}}],["minimumroutes",{"3":{"1436":2}}],["minimumrazorfiles",{"3":{"1436":1}}],["minimumcreateforms=4",{"3":{"1591":1,"1600":1}}],["minimumcreateforms",{"3":{"1436":2,"1489":1,"1596":1}}],["minimumcodebehindfiles",{"3":{"1436":3,"1591":1}}],["minimumcommonpackagecount",{"3":{"1436":2}}],["minimumcommands",{"3":{"1436":2,"1489":2}}],["minimumcontainerapps",{"3":{"1436":1}}],["minimumbaseresources=25",{"3":{"1591":1}}],["minimumbaseresources",{"3":{"1436":4,"1489":1}}],["minimumscannedfiles=40",{"3":{"1591":1}}],["minimumscannedfiles=60",{"3":{"1526":1}}],["minimumscannedfiles",{"3":{"1436":5,"1489":1}}],["minimumscannedtypes",{"3":{"1436":7}}],["minimumsimilarity",{"2":{"1376":1},"3":{"1359":6,"1376":1,"1380":1}}],["minimumgovernedpages",{"3":{"1431":1,"1436":1,"1526":1}}],["minimumlevel",{"3":{"1339":1}}],["minimumlength",{"3":{"827":1,"1271":2,"1300":2,"1339":2,"1436":1}}],["minimumerrorcodes",{"2":{"1271":1},"3":{"1271":1,"1436":2}}],["minimumwait",{"3":{"1259":1,"1286":2,"1323":2}}],["minimumpathcount=10",{"3":{"1526":1,"1531":1}}],["minimumpathcountbecause",{"3":{"1436":1}}],["minimumpathcount",{"2":{"574":1},"3":{"574":1,"1436":3,"1488":1}}],["minimum",{"3":{"0":1,"24":1,"71":1,"74":1,"101":1,"130":1,"132":1,"137":1,"140":1,"142":1,"215":1,"401":1,"530":1,"610":1,"635":1,"821":1,"1018":2,"1020":1,"1215":1,"1259":2,"1271":2,"1300":1,"1309":1,"1310":1,"1323":1,"1330":2,"1333":1,"1339":3,"1359":6,"1396":2,"1402":2,"1415":4,"1416":1,"1431":1,"1436":8,"1441":1,"1449":1,"1453":3,"1454":1,"1455":6,"1460":1,"1461":1,"1465":1,"1467":1,"1477":1,"1486":2,"1490":1,"1492":4,"1526":1,"1577":2,"1586":1,"1591":1,"1602":1,"1640":1,"1641":2,"1678":1,"1705":1,"1714":3,"2030":1,"2032":1,"2045":1,"2047":1,"2058":1,"2105":1,"2107":1}}],["minimumalertspecs",{"2":{"607":1,"1476":1,"2046":1},"3":{"0":2,"607":2,"609":2,"1018":1,"1436":7,"1476":1,"1526":1,"2046":2}}],["minlength",{"3":{"657":1,"1237":2,"1300":1,"1467":1,"1472":1,"1474":1}}],["minratio",{"3":{"591":1}}],["minreplicas",{"2":{"291":1,"293":1,"995":1,"1154":1,"1156":1,"1255":1,"1469":1,"1841":1,"2033":1},"3":{"0":1,"20":1,"291":2,"293":1,"390":2,"995":1,"1154":1,"1156":1,"1255":1,"1259":2,"1455":1,"1467":4,"1469":2,"1474":1,"1526":2,"1534":1,"1841":1,"2033":1}}],["mine=true",{"3":{"1391":1,"1395":1,"1416":2}}],["mine",{"3":{"320":1,"1237":1,"1395":1,"1396":1,"1402":1,"1748":1,"1759":1,"1765":1,"1996":1}}],["minus",{"3":{"22":1,"707":1,"743":1,"1019":1,"1262":1,"1271":1,"1286":2,"1309":2,"1311":1,"1324":1,"1339":1,"1359":2,"1366":1,"1380":4,"1396":3,"1417":1,"1435":1,"1436":3,"1438":1,"1455":1,"1480":1,"1507":1,"1662":1,"1765":1,"1827":1}}],["minutesuntilstart",{"3":{"1396":2}}],["minutes",{"2":{"539":1,"1475":1},"3":{"0":3,"18":1,"19":2,"22":1,"237":1,"386":1,"387":3,"392":2,"499":1,"517":1,"530":1,"535":1,"538":1,"539":6,"540":1,"609":2,"611":1,"619":1,"625":2,"627":3,"640":2,"675":1,"708":1,"757":1,"759":5,"760":2,"813":1,"818":1,"819":1,"905":1,"996":1,"1018":1,"1019":1,"1029":1,"1067":1,"1069":1,"1099":2,"1212":1,"1264":1,"1288":1,"1289":1,"1294":1,"1295":1,"1300":6,"1305":1,"1311":3,"1312":2,"1323":3,"1324":4,"1328":2,"1330":1,"1339":3,"1350":2,"1358":1,"1359":9,"1376":1,"1380":1,"1387":1,"1390":1,"1395":6,"1396":5,"1397":1,"1416":1,"1417":2,"1430":1,"1436":2,"1449":1,"1453":5,"1454":2,"1455":9,"1456":5,"1460":2,"1465":1,"1466":1,"1467":14,"1473":1,"1474":6,"1475":8,"1476":6,"1478":1,"1486":1,"1490":2,"1492":1,"1496":1,"1577":3,"1582":2,"1630":1,"1631":1,"1632":2,"1638":1,"1639":1,"1641":2,"1767":1,"1843":2,"1922":1,"1923":1,"1942":1,"1980":1,"1982":1,"1985":1,"2012":1,"2033":2,"2034":3,"2036":1,"2038":1,"2050":1,"2066":1,"2158":2,"2161":2,"2167":1,"2168":1}}],["minute",{"0":{"1719":1},"3":{"0":8,"175":2,"178":2,"179":1,"260":1,"280":1,"282":1,"350":1,"390":1,"391":1,"392":2,"422":1,"459":1,"517":1,"536":2,"591":1,"593":1,"607":1,"609":1,"626":1,"627":1,"640":1,"709":1,"759":1,"766":1,"809":2,"819":1,"854":2,"862":1,"914":1,"916":1,"1018":2,"1029":1,"1066":1,"1124":2,"1184":1,"1212":2,"1264":2,"1270":3,"1286":1,"1288":1,"1292":1,"1300":5,"1311":13,"1323":2,"1324":5,"1330":2,"1339":4,"1342":2,"1350":1,"1357":1,"1358":2,"1359":11,"1362":1,"1367":3,"1368":1,"1369":1,"1379":1,"1380":9,"1390":1,"1395":3,"1396":3,"1436":1,"1438":4,"1443":1,"1447":1,"1449":2,"1455":4,"1456":4,"1458":1,"1459":2,"1460":2,"1463":1,"1467":16,"1469":2,"1474":1,"1475":4,"1476":2,"1492":1,"1496":1,"1525":1,"1526":2,"1531":2,"1534":1,"1535":1,"1572":1,"1591":1,"1599":1,"1631":1,"1639":2,"1641":1,"1667":1,"1807":1,"1827":1,"1843":1,"1846":1,"1853":1,"1868":1,"1892":1,"1899":1,"1922":1,"1950":1,"1958":1,"1965":1,"1972":1,"1975":3,"1979":1,"1980":1,"1985":1,"1991":1,"1997":1,"1998":3,"2000":1,"2001":2,"2002":1,"2037":2,"2038":1,"2048":1,"2069":1,"2087":1,"2108":1,"2128":1,"2133":1,"2139":1,"2153":1,"2158":3,"2160":1,"2167":2,"2169":1,"2180":1,"2190":2,"2193":1,"2203":1}}],["minter",{"3":{"1396":1}}],["minted",{"3":{"0":7,"207":1,"208":1,"209":1,"350":1,"443":1,"470":1,"664":1,"690":1,"788":1,"790":2,"793":2,"794":3,"805":1,"826":1,"827":1,"829":1,"880":1,"881":2,"882":1,"905":1,"1067":1,"1116":2,"1212":3,"1259":1,"1264":1,"1270":2,"1293":2,"1300":16,"1309":1,"1311":7,"1323":1,"1324":3,"1336":1,"1339":2,"1342":1,"1348":1,"1350":1,"1357":1,"1380":2,"1395":4,"1396":10,"1404":1,"1409":1,"1415":2,"1417":1,"1436":1,"1438":1,"1442":1,"1467":2,"1490":1,"1631":2,"1641":1,"1667":1,"1765":1,"1893":1,"1968":2,"1970":2,"1971":1,"1973":1,"1982":1,"2166":1,"2197":1,"2198":1,"2232":1}}],["mintaccesstoken",{"3":{"1300":6}}],["mintable",{"3":{"0":1}}],["mintforsession",{"3":{"1290":1,"1300":4}}],["mint",{"3":{"30":1,"32":1,"177":1,"499":1,"500":1,"664":2,"665":2,"670":2,"792":3,"828":1,"881":1,"883":1,"904":1,"905":2,"906":1,"926":1,"1212":1,"1278":1,"1286":2,"1292":1,"1293":1,"1300":18,"1311":4,"1323":3,"1324":3,"1326":1,"1336":1,"1339":1,"1369":1,"1380":1,"1395":2,"1396":1,"1415":2,"1417":1,"1436":3,"1438":1,"1441":1,"1454":1,"1455":1,"1467":3,"1472":1,"1582":1,"1585":1,"1667":1,"1765":1,"1767":1,"1894":1,"1899":1,"1911":1,"1970":1,"1980":1,"2063":1,"2066":1,"2197":1,"2232":1}}],["mints",{"3":{"0":1,"32":1,"186":1,"349":1,"350":2,"353":1,"458":1,"499":1,"602":1,"665":1,"667":1,"668":2,"792":1,"827":1,"829":1,"880":1,"885":1,"905":3,"907":1,"1059":1,"1069":1,"1249":1,"1259":2,"1270":1,"1288":1,"1289":2,"1290":2,"1294":1,"1300":24,"1301":1,"1309":2,"1311":3,"1322":1,"1323":1,"1324":4,"1328":1,"1337":1,"1339":2,"1343":1,"1380":3,"1395":3,"1396":8,"1407":1,"1414":1,"1415":3,"1417":1,"1429":1,"1430":1,"1436":1,"1438":2,"1449":1,"1460":1,"1467":1,"1472":1,"1475":1,"1488":1,"1490":1,"1589":1,"1596":1,"1627":1,"1641":1,"1671":1,"1732":1,"1893":2,"1899":1,"1972":1,"1975":2,"1982":1,"1985":1,"2024":1,"2056":1,"2202":1}}],["minting",{"3":{"0":2,"126":1,"295":1,"333":1,"351":1,"400":1,"827":1,"881":1,"908":1,"1030":1,"1116":1,"1259":1,"1263":1,"1270":1,"1286":1,"1287":1,"1290":1,"1295":1,"1300":5,"1304":1,"1309":2,"1324":1,"1337":1,"1339":1,"1348":1,"1350":3,"1377":2,"1380":1,"1395":1,"1396":8,"1402":1,"1406":1,"1409":2,"1415":3,"1417":2,"1432":1,"1445":1,"1447":1,"1449":1,"1455":1,"1491":1,"1972":1,"2024":1,"2166":1}}],["min",{"3":{"0":3,"24":1,"68":2,"177":1,"281":1,"330":1,"609":2,"612":1,"733":1,"1237":1,"1244":1,"1259":1,"1286":1,"1300":1,"1311":3,"1324":3,"1339":2,"1396":1,"1415":1,"1422":1,"1443":2,"1455":2,"1467":4,"1474":14,"1525":1,"1526":1,"1534":1,"1535":1,"1576":1,"1577":2,"1590":1,"1591":2,"1596":1,"1599":1,"1600":2,"1632":1,"1709":2,"1710":1,"1712":1,"1747":1,"1753":1,"1765":1,"1988":1,"2000":1}}],["mirrors",{"3":{"0":4,"26":1,"199":1,"247":1,"265":1,"268":1,"269":1,"321":1,"324":1,"351":1,"355":1,"362":1,"388":1,"471":1,"556":1,"599":1,"626":1,"644":1,"653":1,"698":2,"707":1,"725":1,"766":1,"832":1,"857":1,"881":1,"1042":1,"1059":2,"1063":1,"1067":1,"1068":1,"1108":1,"1212":3,"1229":2,"1237":3,"1242":1,"1247":1,"1259":2,"1270":3,"1271":1,"1286":6,"1295":1,"1300":2,"1301":2,"1303":1,"1309":2,"1310":2,"1311":7,"1312":3,"1322":1,"1323":3,"1324":17,"1339":4,"1350":10,"1353":1,"1359":11,"1369":4,"1372":1,"1374":1,"1379":2,"1380":3,"1384":1,"1395":7,"1396":10,"1402":7,"1407":1,"1412":1,"1414":1,"1415":5,"1416":1,"1417":2,"1430":1,"1436":17,"1438":1,"1441":1,"1444":1,"1455":2,"1490":1,"1496":1,"1526":1,"1561":1,"1570":1,"1591":2,"1599":1,"1653":1,"1662":1,"1666":1,"1825":1,"1922":1,"1927":1,"1968":1,"1995":1,"2018":1,"2075":1,"2076":1,"2187":1,"2191":1}}],["mirror",{"0":{"1608":1,"1742":1},"3":{"0":3,"122":1,"162":1,"181":1,"217":1,"219":1,"241":1,"346":1,"407":1,"448":1,"528":1,"576":1,"707":1,"729":1,"803":1,"860":1,"931":1,"1034":1,"1042":1,"1212":1,"1225":1,"1227":1,"1228":1,"1229":3,"1237":3,"1253":2,"1258":1,"1270":1,"1283":1,"1286":2,"1300":3,"1301":1,"1309":4,"1310":1,"1311":2,"1324":15,"1339":3,"1340":1,"1350":4,"1359":13,"1380":3,"1384":1,"1395":7,"1396":10,"1398":1,"1401":1,"1402":3,"1415":2,"1416":5,"1432":1,"1436":3,"1444":1,"1531":1,"1535":1,"1648":1,"1649":1,"1667":1,"1669":1,"1685":1,"1687":2,"1702":1,"1835":1,"1851":1,"1922":1,"1930":1,"1953":1,"2131":1,"2156":1,"2165":1,"2188":1}}],["mirroring",{"3":{"0":1,"53":1,"145":1,"243":1,"246":1,"698":1,"715":1,"799":1,"854":1,"1034":1,"1116":1,"1124":1,"1229":2,"1244":1,"1247":1,"1259":1,"1286":3,"1300":1,"1309":2,"1311":1,"1312":1,"1323":1,"1324":4,"1350":2,"1359":2,"1380":3,"1395":3,"1396":2,"1416":1,"1417":3,"1427":1,"1436":6,"1486":1,"1596":1,"1631":1,"1645":1,"1825":1,"1995":1,"2102":1}}],["mirrored",{"3":{"0":2,"109":1,"147":1,"374":1,"618":1,"649":1,"1067":1,"1068":1,"1211":1,"1291":1,"1312":1,"1324":3,"1330":1,"1339":3,"1344":1,"1350":1,"1396":4,"1417":2,"1436":1,"1443":1,"1478":1,"1488":1,"1526":1,"1532":1,"1591":1,"1669":1,"1673":1,"2056":1,"2079":1}}],["mezzanine",{"3":{"1359":1,"1630":1}}],["meziantou",{"2":{"138":1},"3":{"138":1,"265":1,"1213":1,"1453":1,"1577":1,"1673":1,"1903":1}}],["mega",{"3":{"1323":1}}],["megabytes",{"3":{"1323":1,"1395":3}}],["megabyte",{"3":{"390":1,"1117":1,"1125":1,"1324":1,"1359":1}}],["me",{"0":{"1787":1},"3":{"1228":2,"1229":2,"1270":1,"1286":2,"1300":1,"1309":1,"1311":1,"1312":1,"1324":1,"1359":4,"1378":1,"1395":1,"1396":12,"1402":1,"1415":9,"1417":1,"1436":1,"1631":2,"1640":3,"1787":1,"1859":1,"1880":1,"2098":1,"2166":1,"2169":1,"2211":1,"2243":1,"2244":1}}],["mesh",{"3":{"702":1,"1312":2,"1396":1}}],["messagingentitynotfoundexception",{"3":{"1323":1}}],["messagingexception",{"3":{"1323":2}}],["messaging",{"0":{"1547":1,"1665":1},"1":{"928":1,"929":1,"930":1,"931":1,"932":1,"933":1,"934":1,"935":1},"2":{"130":1,"150":1,"152":1,"203":1,"917":1,"929":1,"1074":1,"1075":1,"1077":1,"1078":2,"1259":1,"1323":1},"3":{"0":4,"15":1,"25":1,"26":2,"27":5,"68":1,"70":1,"74":1,"130":1,"135":1,"139":2,"145":1,"150":15,"152":2,"166":1,"171":1,"193":2,"196":2,"198":1,"200":1,"201":2,"202":6,"203":5,"255":1,"256":1,"259":1,"391":1,"638":1,"640":12,"641":2,"642":2,"644":1,"674":1,"702":4,"818":1,"819":5,"821":1,"822":2,"846":1,"849":1,"915":2,"917":1,"928":1,"929":1,"930":1,"931":4,"933":1,"934":2,"980":3,"982":2,"983":1,"1006":1,"1012":1,"1013":1,"1074":9,"1075":3,"1077":1,"1078":2,"1175":2,"1203":16,"1207":141,"1208":2,"1212":3,"1213":1,"1216":1,"1237":2,"1247":2,"1257":5,"1258":2,"1259":84,"1270":3,"1286":4,"1300":8,"1309":1,"1311":4,"1316":2,"1317":4,"1318":3,"1320":5,"1323":71,"1339":3,"1359":1,"1396":1,"1415":2,"1417":1,"1436":6,"1438":8,"1441":1,"1443":1,"1451":1,"1496":4,"1499":1,"1519":1,"1524":1,"1526":3,"1527":1,"1530":1,"1531":3,"1532":1,"1533":1,"1536":1,"1539":1,"1551":1,"1577":7,"1578":1,"1582":1,"1583":1,"1585":1,"1586":1,"1588":1,"1589":1,"1591":2,"1592":1,"1595":1,"1596":2,"1597":1,"1598":1,"1602":1,"1656":1,"1660":1,"1662":1,"1673":1,"1675":1,"1707":2,"1718":1,"1721":1,"1784":1,"1796":1,"1924":1,"1936":1,"2008":1,"2182":1,"2212":1,"2213":1,"2218":1,"2232":1}}],["messagepack",{"3":{"370":1,"1577":1}}],["messageheaders",{"2":{"202":1},"3":{"202":2,"702":1,"1199":5,"1203":1,"1207":2,"1259":5,"1300":6,"1323":2,"1496":1}}],["messageid",{"2":{"77":1,"195":1,"197":1,"200":1,"202":1,"203":2,"786":1,"847":1,"1273":1,"1508":1,"1845":1,"1888":1,"1889":1},"3":{"0":2,"77":1,"195":7,"197":2,"200":2,"201":2,"202":1,"203":2,"782":1,"786":1,"846":2,"847":1,"1074":1,"1212":2,"1247":4,"1249":3,"1258":1,"1259":26,"1269":1,"1270":2,"1273":1,"1286":1,"1309":1,"1323":2,"1350":3,"1427":1,"1436":2,"1438":2,"1508":1,"1577":2,"1845":1,"1888":1,"1889":1,"1910":4}}],["messages=",{"3":{"1436":1}}],["messages",{"3":{"0":1,"21":1,"24":1,"25":1,"27":1,"53":1,"109":1,"117":1,"195":1,"198":1,"225":1,"255":1,"265":1,"397":1,"433":1,"538":1,"629":2,"640":1,"656":1,"790":1,"810":1,"819":1,"1012":1,"1018":1,"1026":1,"1082":1,"1091":2,"1107":1,"1150":1,"1229":1,"1237":1,"1247":2,"1255":1,"1259":13,"1270":1,"1271":4,"1273":1,"1286":5,"1296":1,"1300":5,"1301":1,"1302":1,"1309":8,"1311":3,"1315":1,"1323":11,"1324":14,"1333":1,"1339":2,"1359":18,"1377":1,"1380":4,"1394":1,"1395":9,"1396":11,"1402":3,"1415":7,"1420":1,"1423":3,"1424":1,"1427":9,"1435":2,"1436":17,"1437":1,"1467":3,"1492":2,"1577":1,"1641":1,"1653":1,"1677":1,"1686":1,"1702":1,"1727":1,"1817":1,"1831":1,"1841":1,"1845":1,"1846":1,"1936":1,"1943":1,"2044":1,"2081":1,"2156":1,"2172":1}}],["message",{"0":{"1943":1,"2020":1},"1":{"816":1,"817":1,"818":1,"819":1,"820":1,"821":1,"822":1,"823":1,"1072":1,"1073":1,"1074":1,"1075":1,"1076":1,"1077":1,"1078":1,"1079":1},"2":{"301":1,"818":1,"1843":1},"3":{"0":10,"17":1,"19":4,"22":2,"24":4,"26":1,"27":1,"71":1,"72":1,"77":1,"109":11,"111":2,"113":1,"125":1,"136":1,"170":2,"193":2,"195":2,"196":1,"197":1,"198":1,"200":1,"201":4,"202":2,"219":1,"255":1,"265":9,"266":1,"267":1,"284":1,"290":1,"300":1,"301":1,"306":1,"342":1,"352":1,"353":1,"354":1,"391":1,"397":5,"399":2,"434":1,"536":2,"537":2,"538":2,"539":1,"546":1,"568":1,"574":1,"583":1,"624":1,"626":3,"628":1,"629":1,"640":2,"657":1,"659":1,"673":1,"674":1,"684":1,"688":1,"690":2,"691":1,"692":2,"696":1,"702":2,"766":3,"768":1,"789":3,"790":2,"816":1,"818":7,"819":7,"820":2,"821":2,"823":1,"838":1,"845":1,"846":1,"862":1,"875":1,"882":1,"905":1,"931":1,"963":1,"972":1,"973":1,"988":1,"989":1,"996":1,"1004":1,"1012":2,"1017":1,"1018":3,"1019":2,"1043":1,"1067":1,"1072":1,"1074":1,"1075":2,"1091":3,"1092":1,"1093":1,"1100":1,"1108":1,"1126":1,"1133":1,"1134":1,"1140":1,"1161":1,"1162":1,"1169":1,"1175":1,"1192":1,"1202":1,"1203":1,"1212":3,"1219":1,"1225":1,"1229":9,"1230":1,"1237":9,"1247":10,"1248":1,"1249":1,"1253":2,"1256":2,"1258":3,"1259":82,"1264":1,"1269":1,"1270":23,"1271":27,"1274":1,"1279":2,"1284":1,"1286":46,"1297":2,"1299":1,"1300":31,"1301":2,"1302":3,"1306":1,"1309":11,"1311":43,"1312":25,"1315":1,"1316":1,"1317":3,"1318":3,"1320":1,"1323":93,"1324":95,"1333":1,"1338":1,"1339":12,"1350":15,"1352":1,"1353":1,"1355":1,"1359":148,"1366":3,"1369":1,"1380":10,"1384":1,"1395":45,"1396":59,"1402":39,"1406":1,"1413":1,"1414":1,"1415":28,"1417":12,"1420":1,"1422":1,"1423":2,"1424":1,"1427":22,"1429":1,"1430":1,"1435":1,"1436":176,"1438":14,"1441":1,"1443":1,"1455":1,"1467":6,"1488":4,"1489":2,"1492":1,"1496":1,"1524":1,"1526":3,"1531":1,"1543":1,"1547":4,"1575":1,"1577":3,"1585":1,"1591":3,"1596":1,"1639":1,"1640":3,"1641":2,"1645":1,"1653":1,"1665":1,"1669":1,"1677":4,"1685":1,"1686":2,"1702":1,"1752":1,"1769":1,"1776":1,"1790":1,"1802":1,"1805":1,"1815":1,"1824":2,"1831":2,"1840":1,"1841":2,"1843":9,"1852":1,"1873":2,"1876":1,"1887":1,"1889":2,"1891":1,"1910":3,"1912":1,"1922":5,"1924":1,"1927":2,"1942":2,"1977":1,"1978":1,"1985":1,"1994":1,"2003":1,"2006":1,"2010":1,"2012":2,"2015":1,"2028":1,"2032":2,"2044":1,"2047":1,"2069":1,"2074":1,"2083":3,"2086":2,"2092":1,"2099":1,"2126":1,"2128":1,"2150":1,"2154":1,"2157":2,"2164":1,"2165":1,"2166":2,"2172":1,"2174":1,"2179":1,"2180":1,"2181":2,"2182":2,"2200":1,"2227":2,"2232":3,"2235":1,"2237":1}}],["messagebusbackpressuretests",{"2":{"1591":1},"3":{"1591":2,"1596":1}}],["messagebusprovider",{"2":{"150":1,"200":1,"201":1,"931":1,"1075":1,"1446":1,"1842":2},"3":{"0":2,"150":1,"200":1,"201":2,"640":1,"931":1,"964":1,"1075":1,"1199":4,"1203":1,"1207":1,"1212":1,"1320":1,"1323":4,"1326":1,"1339":3,"1446":1,"1842":2}}],["messagebus",{"2":{"640":2,"642":1,"644":1,"915":1,"933":1,"1441":1,"2013":1},"3":{"0":7,"15":3,"24":1,"25":1,"27":1,"193":2,"195":1,"198":2,"200":1,"203":1,"640":9,"642":1,"644":1,"819":1,"914":1,"915":1,"930":1,"931":3,"932":1,"933":2,"1212":2,"1257":2,"1259":10,"1286":1,"1323":6,"1326":1,"1327":1,"1339":4,"1429":1,"1436":2,"1441":4,"1446":3,"1451":1,"1467":4,"1508":1,"1577":1,"1582":1,"1591":1,"1612":1,"1653":1,"1663":1,"1665":1,"1675":1,"1721":1,"1724":2,"1842":1,"1843":1,"1845":1,"1910":1,"1911":1,"2008":2,"2013":1,"2022":1}}],["messagebussettingstests",{"3":{"1199":1,"1207":1}}],["messagebussettings",{"2":{"0":1,"145":1,"152":1,"201":1,"638":1,"644":1,"822":1,"929":1,"931":1,"1257":1,"1707":2,"1790":1,"1842":2,"1910":2,"2020":1,"2026":1},"3":{"0":1,"15":1,"24":1,"25":1,"27":2,"145":2,"150":1,"152":1,"193":1,"196":1,"198":1,"200":2,"201":2,"203":1,"638":1,"640":4,"644":1,"674":1,"818":1,"819":2,"822":1,"915":1,"929":1,"930":1,"931":2,"1199":14,"1203":1,"1207":3,"1257":3,"1259":20,"1286":2,"1313":1,"1316":1,"1320":2,"1323":24,"1326":1,"1339":3,"1436":2,"1496":1,"1534":1,"1577":1,"1582":1,"1583":1,"1586":1,"1591":1,"1707":3,"1790":1,"1842":2,"1910":2,"2020":1,"2026":1}}],["mediocre",{"3":{"1216":1,"1537":1,"1797":1}}],["medium",{"3":{"682":1,"1324":3,"1395":1,"1396":1,"1417":2,"2220":1,"2234":1,"2236":2}}],["median",{"3":{"1436":1,"1577":1,"1707":1}}],["mediapicker",{"3":{"439":1,"1415":1,"1417":6}}],["media",{"2":{"1712":1},"3":{"416":1,"682":4,"684":1,"691":1,"740":1,"741":1,"743":1,"1202":1,"1203":15,"1207":28,"1300":1,"1311":1,"1324":1,"1350":2,"1415":1,"1417":57,"1491":1,"1496":1,"1585":1,"1669":2,"1712":2,"1714":1,"2073":1,"2118":1}}],["mediate",{"3":{"1991":1}}],["mediated",{"3":{"0":1,"332":1}}],["mediators",{"3":{"1556":1}}],["mediator",{"3":{"1270":2,"1539":2,"1543":1}}],["mediatr",{"3":{"0":1,"1075":1,"2139":1}}],["meetups",{"3":{"1380":1}}],["meetup",{"3":{"1342":1,"1350":1}}],["meetcustomrule",{"3":{"1312":1,"1436":2}}],["meeting",{"3":{"734":1,"808":1,"1283":1,"1311":1,"1417":1,"1436":1,"1491":1,"1526":1,"1888":1}}],["meet",{"3":{"92":1,"255":1,"813":1,"1191":1,"1194":1,"1200":1,"1214":1,"1241":1,"1247":1,"1276":1,"1286":2,"1300":3,"1301":4,"1311":1,"1322":1,"1324":2,"1350":1,"1359":4,"1366":1,"1396":3,"1478":1,"1577":2,"1641":3,"1668":1,"1714":1,"1831":1,"1923":1,"1995":1,"2141":1}}],["meetsminimumsizeonmobileviewport",{"3":{"1714":1}}],["meets",{"3":{"53":1,"135":1,"136":1,"568":1,"811":1,"1231":1,"1247":1,"1271":1,"1286":1,"1300":1,"1311":1,"1326":1,"1346":1,"1359":2,"1360":1,"1396":2,"1415":1,"1417":3,"1436":5,"1476":1,"1488":1,"1526":1,"1549":1,"1558":1,"1577":1,"1641":2,"1833":1,"2136":1}}],["mermaid",{"3":{"1502":1,"1620":1,"1756":1}}],["merchandising",{"3":{"1025":1}}],["mere",{"3":{"681":1,"1270":1,"1297":1}}],["merely",{"0":{"2196":1},"3":{"24":1,"38":1,"39":1,"96":1,"135":2,"137":1,"218":1,"324":1,"353":1,"358":1,"359":1,"360":1,"371":1,"497":1,"540":1,"575":1,"690":1,"757":1,"800":2,"827":2,"904":1,"946":1,"1060":1,"1237":2,"1255":1,"1270":1,"1271":1,"1286":6,"1295":1,"1300":2,"1306":1,"1309":2,"1311":1,"1313":1,"1320":1,"1323":2,"1324":1,"1327":1,"1328":1,"1339":5,"1350":2,"1359":10,"1380":2,"1395":3,"1396":10,"1402":2,"1415":4,"1417":1,"1428":1,"1436":17,"1438":1,"1444":1,"1455":1,"1457":1,"1460":1,"1467":1,"1473":1,"1486":1,"1526":1,"1685":1,"1825":1,"1920":1,"1923":1,"2045":1,"2048":1,"2061":1,"2132":1,"2137":1,"2140":1,"2142":2}}],["merging",{"3":{"627":1,"1018":1,"1359":3,"1453":1,"1591":1,"1767":1}}],["merges",{"3":{"0":1,"418":1,"618":1,"838":2,"1091":1,"1271":1,"1286":1,"1300":1,"1309":1,"1311":1,"1324":1,"1359":6,"1396":1,"1415":1,"1436":1,"1453":1,"1455":2,"1492":1,"1572":1,"1577":1,"1582":1,"1591":1,"1905":1}}],["merge",{"3":{"0":3,"112":1,"135":1,"265":1,"564":2,"590":1,"591":1,"595":1,"618":2,"625":1,"626":3,"628":2,"629":1,"630":1,"642":2,"838":1,"863":2,"1212":2,"1247":3,"1286":1,"1300":2,"1310":1,"1311":2,"1323":4,"1339":2,"1359":1,"1415":4,"1436":2,"1453":3,"1454":1,"1455":5,"1456":1,"1459":1,"1478":1,"1485":1,"1486":4,"1490":1,"1492":16,"1496":2,"1526":6,"1531":1,"1535":1,"1554":1,"1565":1,"1570":1,"1572":1,"1577":12,"1581":3,"1582":3,"1583":1,"1586":1,"1589":3,"1590":1,"1591":12,"1595":1,"1596":5,"1598":1,"1605":1,"1616":1,"1643":1,"1662":1,"1673":1,"1716":3,"1765":1,"1800":2,"1811":1,"1876":1,"1902":1,"1923":2,"2057":1,"2073":1,"2079":1,"2110":1,"2134":1,"2232":1}}],["merged",{"3":{"0":2,"94":1,"109":1,"365":1,"368":1,"373":1,"424":1,"425":1,"526":1,"530":1,"543":1,"550":2,"572":1,"607":1,"832":2,"833":1,"1012":2,"1016":1,"1081":1,"1085":2,"1229":1,"1237":1,"1247":2,"1256":1,"1259":2,"1300":1,"1323":1,"1339":3,"1359":3,"1396":1,"1415":2,"1429":1,"1438":1,"1447":1,"1453":1,"1454":3,"1459":1,"1481":1,"1492":2,"1496":1,"2058":1}}],["merits",{"3":{"486":1,"801":1,"1018":1,"1054":1}}],["mechanic",{"3":{"715":1,"716":1,"1286":4,"1301":1,"1395":1,"1396":4,"1417":1,"1839":1}}],["mechanics",{"3":{"1":1,"147":1,"272":1,"329":1,"369":1,"506":1,"715":1,"861":1,"862":1,"1043":1,"1045":1,"1229":1,"1237":2,"1247":1,"1259":5,"1270":1,"1275":1,"1286":12,"1300":5,"1311":3,"1324":5,"1339":4,"1350":5,"1359":4,"1380":1,"1395":9,"1415":1,"1417":1,"1433":1,"1435":1,"1436":11,"1475":1,"1577":1,"1861":1,"2047":1,"2203":1}}],["mechanically",{"3":{"342":1,"801":1,"1191":2,"1192":1,"1204":1,"1270":1,"1286":1,"1359":2,"1369":1,"1417":1,"1436":6,"1480":1,"1495":1,"1496":8,"1500":1,"1875":1,"1926":1}}],["mechanical",{"3":{"0":1,"88":1,"135":1,"143":1,"267":1,"483":1,"486":1,"674":1,"980":1,"1069":1,"1094":1,"1270":2,"1286":1,"1309":1,"1311":1,"1324":1,"1350":2,"1359":4,"1369":1,"1396":1,"1431":1,"1436":7,"1455":1,"1459":2,"1460":1,"1496":9,"1585":1,"1653":1,"1673":1,"1675":1,"1735":1}}],["mechanisms",{"0":{"2183":1},"3":{"19":1,"67":1,"282":1,"319":1,"340":1,"358":1,"557":1,"812":1,"1144":1,"1236":2,"1237":1,"1286":2,"1300":1,"1304":1,"1310":1,"1311":1,"1323":1,"1324":4,"1359":4,"1379":1,"1395":10,"1396":3,"1417":1,"1436":4,"1497":1,"1517":1,"1706":1,"1708":1,"1784":1,"1816":1,"1858":1,"1995":1,"2068":2,"2069":1,"2141":1,"2167":2,"2181":1,"2193":1}}],["mechanism",{"0":{"1804":1,"1908":1,"1910":1,"2063":1,"2165":1},"3":{"0":10,"3":1,"24":1,"39":1,"68":1,"80":1,"86":1,"109":1,"142":1,"169":1,"186":1,"207":1,"235":1,"259":1,"265":2,"266":2,"292":1,"295":1,"317":2,"320":1,"340":1,"358":1,"361":2,"362":1,"366":1,"372":1,"389":1,"391":1,"413":1,"465":1,"508":1,"517":1,"522":1,"536":2,"537":1,"538":1,"539":1,"582":1,"598":1,"599":1,"605":1,"608":1,"624":1,"658":1,"678":1,"689":2,"696":1,"699":1,"715":1,"723":1,"764":4,"769":1,"808":1,"809":1,"815":1,"820":1,"827":2,"829":1,"844":1,"846":1,"847":1,"848":2,"849":2,"850":1,"871":1,"905":1,"931":1,"958":1,"959":1,"960":1,"981":1,"988":1,"1012":1,"1075":1,"1099":1,"1101":1,"1139":1,"1147":1,"1192":1,"1193":1,"1212":2,"1213":1,"1218":1,"1229":5,"1237":6,"1247":3,"1249":1,"1253":1,"1258":1,"1259":9,"1270":11,"1271":3,"1286":16,"1290":1,"1297":1,"1300":5,"1309":1,"1310":1,"1311":5,"1318":1,"1323":8,"1324":17,"1339":11,"1350":5,"1355":1,"1359":21,"1377":1,"1380":4,"1395":10,"1396":6,"1402":5,"1413":1,"1415":6,"1416":1,"1417":9,"1418":1,"1436":25,"1438":1,"1446":1,"1454":1,"1456":1,"1457":1,"1459":1,"1474":3,"1477":1,"1479":1,"1486":1,"1488":1,"1489":1,"1490":1,"1496":2,"1510":1,"1526":1,"1564":1,"1566":1,"1567":2,"1575":2,"1577":2,"1578":1,"1582":1,"1586":1,"1591":5,"1634":1,"1639":2,"1641":2,"1677":1,"1707":1,"1764":1,"1790":1,"1840":1,"1842":1,"1844":1,"1845":1,"1846":1,"1851":1,"1853":1,"1871":1,"1876":1,"1882":1,"1891":1,"1921":1,"1922":1,"1947":1,"1948":1,"1950":1,"1962":1,"1963":1,"1964":1,"1965":1,"1968":1,"1975":1,"1992":1,"1994":1,"1996":2,"1997":1,"2000":1,"2002":1,"2020":1,"2031":1,"2043":1,"2046":1,"2061":2,"2062":1,"2063":1,"2068":3,"2069":1,"2080":1,"2082":1,"2085":1,"2086":1,"2113":1,"2119":1,"2120":1,"2122":1,"2130":1,"2133":1,"2134":1,"2136":1,"2142":1,"2143":1,"2145":1,"2164":3,"2168":2,"2169":1,"2185":1,"2193":1,"2230":1,"2232":1}}],["mentor",{"3":{"2216":1,"2242":1}}],["mentorship",{"0":{"2242":1},"3":{"2216":1,"2219":1}}],["mentoring",{"3":{"2213":1,"2218":1,"2238":1,"2245":1}}],["mentioning",{"3":{"1415":1}}],["mentioned",{"3":{"370":1,"1006":1,"1632":1}}],["mention",{"3":{"256":1,"423":1,"638":1,"988":1,"1286":1,"1348":1,"1436":1,"1459":1,"1460":1,"1685":2}}],["mentions",{"3":{"0":1,"230":1,"418":1,"425":1,"426":1,"435":1,"497":1,"555":1,"593":1,"609":1,"700":1,"782":1,"1270":4,"1311":3,"1359":1,"1369":1,"1396":1,"1415":2,"1436":3,"1472":1,"1496":1,"1568":1,"1639":2}}],["mental",{"3":{"167":1,"906":1,"932":1,"1220":1,"1323":1,"1359":1,"1395":1,"1416":1,"1805":1}}],["menuitem",{"3":{"1436":1}}],["menus",{"3":{"265":1,"1324":3,"1436":1,"1526":1,"1559":1,"1591":1,"1607":1,"1653":1,"1741":1,"2085":1}}],["menu",{"3":{"0":1,"618":1,"647":1,"648":1,"649":1,"651":1,"1212":1,"1324":19,"1395":2,"1396":4,"1415":6,"1436":2,"1438":1,"1526":1,"1562":1,"1621":1,"1625":1,"1627":4,"1653":3,"1761":2,"2077":4,"2083":1}}],["measurably",{"3":{"1289":1,"1483":1}}],["measurable",{"3":{"255":1,"405":1,"799":1,"1270":1,"1271":1,"1395":1,"1402":1,"1427":1,"1802":2}}],["measuring",{"3":{"236":1,"474":1,"592":1,"1286":1,"1339":1,"1436":1,"1455":1,"1458":1,"1492":2,"2047":1}}],["measureasync",{"3":{"1436":1}}],["measureandassertasync",{"2":{"861":1},"3":{"861":1}}],["measurewebvitalsasync",{"2":{"861":1},"3":{"861":1,"1436":6}}],["measurewebvitalswithinteractionasync",{"2":{"859":1,"861":1,"864":1},"3":{"859":1,"861":2,"864":1,"1436":3}}],["measure",{"3":{"72":1,"407":1,"591":1,"617":1,"631":1,"865":1,"1271":1,"1286":1,"1324":1,"1339":1,"1359":1,"1395":1,"1416":1,"1417":1,"1436":1,"1458":1,"1467":1,"1488":1,"1491":1,"1492":2,"1521":1,"1531":1,"1537":2,"1710":1,"1797":1,"1902":1,"2193":1}}],["measuresarevokedrowfromitsrevocationnotitsexpiry",{"3":{"906":1}}],["measures",{"3":{"0":2,"140":1,"558":1,"591":2,"611":1,"797":1,"821":1,"859":1,"860":2,"861":3,"1259":1,"1263":1,"1270":1,"1323":2,"1324":2,"1339":1,"1395":2,"1425":1,"1436":6,"1438":1,"1455":5,"1461":1,"1467":2,"1492":3,"1496":1,"1526":1,"1591":1,"1670":1,"1797":2,"1821":1,"2024":1,"2046":1,"2087":1}}],["measurements",{"3":{"480":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"495":1,"591":1,"631":5,"636":1,"915":1,"1270":1,"1311":1,"1323":1,"1436":3,"1456":1,"1467":1,"1484":1,"1492":2,"1794":1}}],["measurement",{"3":{"0":1,"397":1,"487":2,"488":1,"489":1,"490":2,"491":1,"492":1,"493":1,"494":1,"591":3,"592":3,"799":1,"859":1,"860":1,"861":5,"863":1,"869":1,"875":1,"980":1,"1018":1,"1199":4,"1207":1,"1259":2,"1270":1,"1339":1,"1369":1,"1395":1,"1427":3,"1436":10,"1438":1,"1445":1,"1451":1,"1453":1,"1455":2,"1458":1,"1465":1,"1467":3,"1475":2,"1478":1,"1492":3,"1531":1,"1802":1,"2047":2,"2048":1,"2232":2}}],["measuredconstructors",{"3":{"1436":4}}],["measured",{"3":{"0":7,"137":1,"237":1,"397":1,"480":1,"483":1,"486":2,"487":2,"488":2,"489":2,"490":2,"491":2,"492":2,"493":2,"494":2,"590":1,"591":3,"592":1,"597":1,"604":1,"616":2,"620":1,"621":1,"632":1,"683":1,"797":2,"801":1,"803":1,"804":1,"805":1,"806":1,"827":1,"828":1,"860":2,"861":5,"862":3,"863":3,"869":1,"871":1,"914":2,"915":1,"947":1,"978":1,"980":1,"983":1,"986":1,"1011":1,"1012":1,"1048":1,"1054":1,"1101":1,"1125":1,"1199":2,"1207":1,"1212":1,"1270":2,"1286":1,"1300":4,"1323":2,"1324":2,"1339":2,"1344":1,"1358":1,"1395":2,"1396":1,"1402":1,"1416":1,"1425":1,"1436":21,"1438":2,"1443":1,"1447":1,"1455":4,"1458":1,"1460":1,"1461":1,"1465":3,"1467":4,"1474":2,"1475":2,"1476":1,"1483":1,"1484":1,"1492":6,"1496":4,"1500":1,"1523":1,"1525":1,"1526":4,"1529":1,"1534":2,"1549":1,"1560":1,"1566":1,"1568":1,"1571":1,"1575":3,"1576":1,"1577":8,"1581":1,"1582":1,"1583":1,"1591":4,"1596":1,"1670":1,"1678":2,"1707":10,"1709":1,"1710":1,"1923":2,"1985":1,"2024":1,"2034":3,"2037":1,"2043":1,"2047":2,"2058":1,"2059":1,"2079":1,"2156":1,"2177":1,"2232":3}}],["meanings",{"3":{"1300":1,"1323":1,"1339":1,"1630":1}}],["meaningless",{"3":{"691":1,"784":1,"1247":1,"1311":1,"1323":4,"1359":1,"1395":2,"1396":1,"1415":1,"1417":1,"1434":1,"1436":1,"1631":2,"1638":1,"1639":2,"2002":1}}],["meaningfulness",{"3":{"1487":1}}],["meaningfully",{"3":{"1323":1,"1359":1,"1417":1,"1436":1,"1492":1,"1950":1}}],["meaningful",{"3":{"305":1,"390":1,"499":1,"775":1,"918":1,"932":1,"1140":1,"1212":1,"1237":1,"1240":1,"1247":3,"1259":1,"1265":1,"1267":1,"1270":2,"1286":3,"1300":1,"1310":1,"1311":1,"1312":1,"1323":1,"1324":3,"1339":1,"1350":4,"1359":8,"1369":1,"1395":6,"1396":4,"1402":5,"1415":2,"1420":1,"1427":1,"1435":1,"1436":7,"1453":1,"1458":1,"1467":1,"1537":1,"1541":1,"1551":2,"1562":1,"1565":1,"1605":1,"1607":1,"1630":1,"1637":1,"1642":1,"1739":1,"1741":1,"1909":1,"2073":1,"2230":1,"2232":1}}],["meaning",{"3":{"284":1,"698":2,"758":1,"827":2,"1090":1,"1222":1,"1237":2,"1241":1,"1247":1,"1253":1,"1259":1,"1264":2,"1271":1,"1286":5,"1300":6,"1301":4,"1311":2,"1323":2,"1324":9,"1339":5,"1350":4,"1359":9,"1369":3,"1395":9,"1396":6,"1402":4,"1415":1,"1417":5,"1436":6,"1442":1,"1459":1,"1465":1,"1467":3,"1476":1,"1537":2,"1630":17,"1633":2,"1638":1,"1639":2,"1640":1,"1727":1,"1728":1,"1729":1,"1730":1,"1734":1,"1769":4,"1797":1,"1810":1,"1933":1,"2171":1,"2185":1}}],["meanwhile",{"3":{"261":1,"826":1,"1350":1,"1380":1,"1395":1}}],["mean",{"3":{"60":1,"110":1,"135":1,"198":1,"218":1,"310":1,"402":1,"414":1,"509":1,"591":2,"593":1,"620":2,"626":1,"697":1,"699":1,"757":1,"768":1,"776":1,"810":1,"819":1,"821":1,"829":1,"856":1,"861":1,"862":1,"863":1,"923":1,"981":2,"1006":1,"1018":1,"1043":1,"1067":1,"1091":1,"1092":1,"1116":1,"1188":1,"1259":3,"1270":1,"1275":1,"1280":1,"1286":6,"1300":4,"1301":1,"1309":4,"1310":2,"1311":2,"1323":4,"1324":2,"1333":1,"1339":6,"1350":6,"1359":6,"1395":4,"1396":8,"1402":1,"1415":4,"1417":5,"1422":1,"1427":1,"1436":5,"1438":1,"1443":1,"1455":2,"1467":2,"1472":1,"1478":1,"1479":1,"1489":1,"1492":2,"1630":1,"1638":1,"1862":1,"1863":1,"1922":1,"1933":1,"1979":1,"2000":1,"2001":1,"2015":1,"2028":1,"2032":1,"2037":1,"2048":1,"2057":1,"2079":1,"2098":1,"2127":1}}],["meantime",{"3":{"635":1,"988":1,"1117":1}}],["meant",{"3":{"0":2,"81":1,"110":1,"246":1,"302":1,"318":1,"342":1,"403":1,"460":1,"539":1,"540":1,"609":1,"648":1,"650":1,"683":1,"697":1,"698":1,"709":1,"765":1,"939":1,"945":1,"1074":1,"1169":1,"1174":1,"1229":2,"1239":1,"1247":4,"1259":2,"1270":1,"1271":2,"1286":3,"1300":8,"1301":1,"1311":2,"1323":2,"1324":4,"1350":1,"1355":1,"1359":3,"1379":2,"1380":1,"1395":1,"1396":4,"1402":2,"1413":1,"1415":1,"1427":1,"1436":16,"1455":1,"1475":1,"1677":1,"1698":1,"1726":1,"1815":1,"1872":1,"1874":1,"2002":1,"2032":1,"2138":1}}],["means",{"0":{"719":1,"2137":1},"1":{"1794":1,"1795":1,"1796":1,"1797":1,"1798":1,"1799":1,"1800":1,"1801":1,"1802":1},"3":{"0":9,"30":1,"32":1,"58":1,"79":1,"80":1,"119":1,"122":1,"132":1,"134":1,"135":1,"136":1,"149":1,"159":1,"160":2,"170":1,"175":1,"185":1,"187":1,"189":1,"194":1,"196":1,"201":1,"216":1,"226":2,"236":1,"237":1,"243":2,"244":1,"255":1,"266":1,"274":1,"282":1,"283":1,"292":1,"311":1,"318":1,"319":1,"320":1,"332":1,"341":1,"342":1,"343":1,"349":1,"351":2,"352":1,"359":1,"361":2,"363":1,"371":1,"372":1,"382":1,"384":1,"390":1,"399":1,"413":1,"441":2,"448":1,"449":3,"461":1,"500":2,"501":1,"508":1,"519":1,"520":1,"538":2,"546":1,"547":1,"549":1,"564":2,"573":1,"575":1,"583":2,"591":1,"598":1,"601":2,"602":1,"617":1,"619":5,"642":1,"650":1,"651":1,"665":1,"666":1,"667":1,"676":1,"682":1,"684":1,"691":1,"692":1,"698":2,"700":1,"708":1,"713":1,"716":1,"717":1,"718":1,"726":1,"727":1,"733":2,"741":3,"743":1,"750":2,"756":1,"766":1,"767":2,"768":1,"781":1,"782":1,"784":1,"792":2,"799":1,"800":1,"801":1,"810":1,"819":1,"821":1,"827":1,"838":1,"845":1,"855":2,"863":1,"872":1,"883":1,"905":1,"906":1,"916":1,"932":2,"945":1,"955":1,"965":2,"988":1,"989":1,"996":3,"997":1,"999":1,"1020":1,"1026":1,"1034":2,"1035":1,"1041":1,"1044":1,"1060":2,"1066":1,"1076":1,"1083":1,"1091":1,"1092":3,"1093":1,"1101":1,"1102":1,"1107":1,"1108":1,"1109":1,"1144":1,"1177":1,"1212":2,"1229":10,"1231":1,"1237":13,"1240":1,"1243":1,"1247":12,"1253":1,"1254":1,"1259":26,"1264":1,"1270":25,"1271":1,"1276":1,"1285":1,"1286":81,"1291":1,"1300":53,"1301":12,"1305":2,"1309":20,"1310":8,"1311":47,"1312":3,"1323":49,"1324":64,"1328":1,"1332":2,"1339":32,"1344":2,"1350":32,"1352":1,"1354":1,"1359":99,"1366":1,"1368":1,"1369":9,"1372":2,"1373":1,"1380":7,"1395":50,"1396":69,"1402":35,"1406":1,"1407":1,"1408":1,"1411":1,"1415":32,"1416":3,"1417":49,"1427":8,"1430":2,"1431":1,"1433":1,"1436":56,"1438":3,"1440":1,"1441":7,"1442":1,"1443":2,"1445":1,"1446":1,"1453":2,"1454":2,"1455":6,"1456":1,"1458":1,"1460":1,"1464":1,"1467":7,"1468":1,"1473":1,"1475":1,"1476":1,"1488":4,"1489":1,"1490":1,"1492":4,"1526":2,"1537":2,"1542":1,"1597":1,"1599":1,"1631":2,"1639":1,"1640":1,"1641":1,"1652":1,"1653":1,"1668":1,"1685":1,"1692":1,"1702":1,"1723":1,"1731":1,"1735":1,"1748":1,"1755":1,"1767":1,"1792":1,"1793":1,"1794":1,"1797":1,"1798":2,"1810":1,"1812":1,"1815":1,"1817":1,"1822":1,"1823":1,"1826":1,"1830":2,"1833":1,"1843":1,"1844":1,"1845":2,"1851":2,"1854":1,"1871":1,"1874":1,"1876":3,"1885":2,"1889":1,"1890":1,"1891":2,"1900":1,"1902":3,"1905":1,"1908":2,"1909":1,"1911":1,"1912":2,"1916":1,"1920":1,"1932":1,"1933":1,"1940":2,"1948":1,"1949":2,"1958":1,"1961":1,"1965":1,"1981":3,"1982":1,"1990":1,"1991":1,"1996":1,"1998":1,"2001":1,"2002":1,"2013":2,"2015":1,"2016":2,"2033":1,"2041":1,"2042":1,"2043":1,"2044":1,"2045":1,"2046":1,"2047":2,"2051":2,"2053":1,"2056":2,"2059":1,"2067":1,"2070":3,"2077":1,"2078":1,"2085":1,"2117":1,"2126":1,"2127":1,"2131":2,"2134":1,"2136":1,"2138":1,"2142":1,"2150":1,"2156":1,"2160":1,"2164":1,"2166":1,"2168":2,"2169":1,"2174":1,"2175":1,"2177":1,"2179":1,"2183":2,"2184":3,"2187":1,"2192":2,"2193":1,"2196":1,"2197":1,"2199":1,"2202":1,"2205":1,"2230":1,"2232":3}}],["memoizing",{"3":{"1259":1,"1286":1,"1311":1}}],["memoization",{"3":{"1247":1,"1395":1,"1556":1}}],["memoize",{"3":{"1247":1,"1395":1}}],["memoizes",{"3":{"244":1,"330":1,"337":1,"1050":1,"1247":2,"1286":2,"1323":1,"1359":1,"1387":1,"1395":2}}],["memoized",{"3":{"242":1,"682":1,"1241":2,"1247":2,"1300":1,"1310":1,"1395":1}}],["memorize",{"3":{"1270":1,"1311":1,"1905":1}}],["memorizing",{"3":{"1231":1,"1263":1,"1311":1,"1372":1,"1396":2}}],["memoryallocator",{"3":{"1438":1}}],["memorymarshal",{"3":{"1359":1}}],["memorystream",{"3":{"1323":1,"1324":1,"1359":1,"1380":1,"1415":3}}],["memorycache",{"3":{"1467":1}}],["memorycacheentryoptions",{"3":{"1301":3}}],["memorycacheservicetests",{"3":{"1199":1,"1207":2,"1301":5}}],["memorycacheservice",{"2":{"248":1,"252":1,"253":1,"731":1,"1916":1},"3":{"0":1,"243":2,"248":2,"252":1,"253":1,"731":1,"1199":4,"1203":1,"1207":2,"1300":6,"1301":57,"1323":2,"1496":2,"1577":1,"1916":2}}],["memorydiagnoser",{"2":{"591":1,"592":1,"2047":1},"3":{"591":2,"592":1,"1436":1,"1492":1,"2047":1}}],["memorydistributedcache",{"2":{"252":1,"1916":1},"3":{"243":1,"252":1,"1301":2,"1323":1,"1916":1}}],["memory",{"2":{"875":1},"3":{"0":6,"16":1,"17":1,"39":1,"43":1,"157":1,"158":1,"162":1,"178":2,"207":1,"209":1,"235":2,"241":1,"242":1,"243":5,"244":1,"245":3,"249":1,"259":1,"261":1,"283":1,"386":1,"390":2,"461":1,"507":5,"508":1,"514":1,"536":1,"540":1,"545":2,"546":1,"549":1,"551":1,"664":1,"665":1,"667":1,"688":1,"692":1,"699":1,"732":1,"733":1,"767":1,"792":1,"793":1,"794":1,"818":1,"827":1,"831":1,"856":2,"875":2,"881":2,"903":1,"905":1,"914":1,"916":1,"1050":1,"1052":1,"1085":1,"1117":1,"1123":1,"1125":1,"1126":1,"1150":2,"1152":2,"1154":1,"1155":1,"1183":1,"1212":1,"1229":1,"1237":4,"1239":3,"1243":1,"1247":11,"1250":1,"1259":1,"1270":1,"1278":1,"1282":1,"1286":21,"1290":2,"1297":1,"1298":3,"1300":11,"1301":7,"1309":2,"1310":2,"1311":15,"1323":3,"1324":46,"1332":1,"1334":1,"1336":1,"1337":1,"1339":12,"1346":1,"1350":6,"1354":1,"1357":1,"1359":45,"1362":1,"1367":1,"1369":5,"1379":1,"1380":1,"1383":1,"1391":1,"1395":13,"1396":17,"1401":1,"1402":3,"1415":2,"1417":5,"1427":1,"1429":1,"1431":1,"1436":25,"1438":9,"1442":3,"1443":2,"1457":1,"1465":1,"1467":6,"1473":1,"1476":1,"1526":2,"1535":1,"1549":1,"1560":1,"1562":1,"1577":3,"1582":1,"1586":1,"1590":1,"1591":1,"1611":1,"1614":1,"1641":4,"1651":1,"1667":1,"1668":1,"1675":1,"1715":1,"1721":1,"1814":2,"1815":2,"1817":1,"1824":1,"1856":1,"1868":1,"1885":1,"1904":1,"1909":1,"1916":1,"1923":4,"1924":1,"1929":1,"1950":1,"1966":1,"1968":2,"1970":2,"1971":1,"1982":1,"1986":1,"1988":1,"2000":1,"2001":2,"2007":1,"2027":1,"2052":1,"2074":1,"2076":2,"2079":1,"2081":1,"2108":1,"2128":1,"2167":2,"2180":1,"2199":1,"2232":2}}],["memberallowlistentry",{"3":{"1436":2}}],["membername",{"3":{"1324":3,"1427":1,"1436":2}}],["memberexpression",{"3":{"1247":1,"1286":3,"1310":1,"1324":2,"1436":1}}],["memberpath",{"3":{"1247":2,"1310":1,"1395":1}}],["memberless",{"3":{"657":1}}],["memberinit",{"3":{"330":1,"1242":1,"1247":3,"1286":1,"1311":2,"1988":1}}],["member",{"2":{"1161":1},"3":{"0":13,"24":1,"27":1,"121":2,"124":1,"126":1,"135":4,"142":1,"164":1,"186":1,"245":1,"248":1,"249":1,"255":1,"261":1,"335":1,"350":1,"443":1,"463":1,"501":1,"502":1,"545":1,"546":2,"549":1,"550":2,"583":1,"609":1,"633":2,"692":1,"698":2,"725":1,"726":1,"733":2,"734":1,"735":1,"741":2,"745":1,"749":1,"782":3,"783":2,"784":2,"854":1,"859":1,"861":2,"922":1,"963":3,"964":12,"965":2,"966":3,"967":1,"979":3,"980":6,"981":3,"982":3,"984":2,"1034":1,"1038":1,"1042":1,"1050":3,"1052":1,"1059":2,"1060":2,"1091":1,"1119":1,"1133":1,"1135":1,"1161":14,"1162":1,"1163":2,"1168":1,"1176":1,"1193":1,"1212":1,"1219":3,"1225":2,"1229":13,"1230":1,"1232":1,"1235":2,"1236":1,"1237":21,"1239":1,"1241":2,"1247":19,"1249":1,"1259":6,"1263":1,"1266":1,"1270":39,"1271":7,"1276":2,"1278":1,"1279":1,"1280":1,"1286":99,"1293":1,"1294":1,"1295":3,"1297":1,"1300":29,"1301":12,"1309":23,"1310":5,"1311":21,"1312":1,"1315":1,"1318":2,"1323":23,"1324":48,"1327":1,"1339":6,"1348":1,"1350":21,"1352":1,"1353":1,"1354":2,"1357":1,"1359":145,"1369":8,"1376":1,"1380":7,"1395":25,"1396":60,"1402":13,"1405":1,"1407":1,"1408":1,"1411":1,"1415":29,"1416":2,"1417":22,"1423":2,"1427":3,"1429":1,"1431":1,"1436":210,"1438":2,"1467":1,"1477":1,"1478":1,"1489":2,"1496":2,"1517":1,"1526":1,"1577":3,"1673":1,"1686":1,"1699":1,"1727":5,"1734":2,"1769":1,"1815":3,"1887":1,"1915":3,"1953":1,"1963":1,"1977":1,"1989":1,"2001":1,"2046":1,"2063":1,"2077":1,"2100":1,"2107":3,"2126":1,"2136":1,"2186":1}}],["membersof",{"3":{"1436":3}}],["memberslazy",{"3":{"1237":1}}],["membershipservice",{"3":{"1199":2,"1207":1}}],["membershipdto",{"3":{"1199":3,"1207":1}}],["membership",{"0":{"1203":1},"3":{"0":1,"284":1,"380":2,"381":1,"1152":1,"1154":1,"1200":1,"1247":6,"1259":1,"1286":2,"1296":1,"1300":5,"1302":1,"1307":2,"1309":4,"1310":1,"1311":1,"1312":1,"1324":9,"1350":1,"1359":3,"1380":1,"1396":5,"1400":1,"1402":1,"1436":5,"1496":5,"1638":1,"1945":4}}],["members",{"0":{"1300":1},"1":{"977":1,"978":1,"979":1,"980":1,"981":1,"982":1,"983":1,"984":1},"3":{"0":8,"39":2,"109":1,"112":1,"135":6,"136":1,"142":1,"166":1,"184":1,"201":1,"235":1,"258":1,"261":1,"276":1,"326":1,"486":1,"497":1,"499":1,"543":1,"545":1,"549":7,"550":4,"551":3,"552":1,"583":5,"633":1,"649":1,"650":1,"657":1,"674":2,"675":1,"677":1,"682":1,"733":1,"749":1,"782":2,"784":1,"819":1,"846":1,"849":1,"881":1,"884":1,"922":1,"963":1,"964":5,"966":2,"975":1,"977":1,"980":4,"982":1,"1018":1,"1027":1,"1034":1,"1036":1,"1045":1,"1050":2,"1133":2,"1135":1,"1168":3,"1175":2,"1193":1,"1198":1,"1212":4,"1214":3,"1220":1,"1229":3,"1232":1,"1235":2,"1236":1,"1237":19,"1239":2,"1247":11,"1249":1,"1259":9,"1262":1,"1269":1,"1270":12,"1271":1,"1280":1,"1281":2,"1286":67,"1288":1,"1296":1,"1299":1,"1300":39,"1301":7,"1304":1,"1309":16,"1310":5,"1311":19,"1312":3,"1314":1,"1323":28,"1324":74,"1334":1,"1339":10,"1347":2,"1349":1,"1350":46,"1359":107,"1380":7,"1395":37,"1396":55,"1402":40,"1405":1,"1409":1,"1412":1,"1414":1,"1415":35,"1416":5,"1417":23,"1427":6,"1431":2,"1436":142,"1438":3,"1488":3,"1489":1,"1496":47,"1498":2,"1538":1,"1575":1,"1577":1,"1648":1,"1654":1,"1766":1,"1805":1,"1810":1,"1815":1,"1849":1,"1858":1,"1871":1,"1880":1,"1926":1,"1948":1,"2094":1,"2107":1,"2167":1,"2232":1}}],["met",{"3":{"1017":1,"1145":1,"1152":1,"1153":1,"1155":1,"1248":1,"1323":3,"1359":1,"1396":1,"1415":1,"1418":1,"1436":1,"1445":1,"1526":6,"1527":1,"1531":1,"1537":5,"1576":1,"1577":10,"1578":1,"1582":2,"1591":1,"1797":1,"2014":1,"2205":1}}],["metastasizes",{"3":{"2116":1}}],["meta",{"3":{"1436":24,"1486":1,"1496":1}}],["metacharacters",{"3":{"1452":1}}],["metacharacter",{"3":{"1324":1}}],["metaphor",{"3":{"1229":1}}],["metapackages",{"3":{"940":1}}],["metapackage",{"1":{"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1},"3":{"0":3,"135":2,"139":1,"141":3,"142":1,"145":1,"526":1,"936":1,"937":1,"939":4,"940":3,"941":3,"942":1,"953":2,"957":1,"966":1,"984":1,"1096":1,"1211":1,"1212":1,"1454":1,"1503":1,"1527":1,"1577":1,"1660":1,"1661":1,"1693":1,"1781":1,"2172":1,"2227":2,"2232":2,"2234":1}}],["metadatanames",{"3":{"1311":1}}],["metadata",{"0":{"1236":1},"1":{"1310":1},"2":{"1369":1},"3":{"0":5,"13":1,"31":2,"90":1,"91":1,"93":1,"98":2,"99":1,"103":1,"173":2,"175":3,"179":1,"189":1,"318":1,"330":1,"435":1,"439":1,"440":1,"526":1,"528":1,"611":1,"749":2,"939":1,"981":1,"1089":1,"1093":1,"1107":1,"1115":1,"1133":1,"1136":1,"1192":1,"1202":1,"1203":1,"1208":1,"1212":1,"1229":4,"1230":1,"1236":1,"1237":2,"1238":1,"1244":2,"1245":1,"1247":4,"1259":1,"1262":1,"1271":1,"1286":20,"1287":1,"1297":3,"1300":16,"1309":9,"1310":15,"1311":23,"1312":10,"1323":10,"1324":1,"1339":1,"1340":1,"1350":4,"1359":10,"1369":16,"1380":1,"1396":5,"1405":1,"1409":1,"1415":10,"1416":2,"1417":1,"1427":1,"1434":1,"1436":20,"1438":1,"1441":2,"1443":1,"1447":2,"1450":1,"1454":1,"1467":4,"1496":1,"1629":1,"1630":2,"1631":2,"1639":1,"1640":1,"1641":15,"1663":1,"1664":1,"1667":1,"1668":1,"1703":1,"1805":2,"1815":1,"1817":1,"1825":1,"1867":1,"1898":2,"1904":1,"1905":1,"1925":1,"1927":2,"1928":1,"1963":1,"1988":1,"1994":1,"2000":2,"2009":1,"2018":1,"2019":1,"2124":2,"2125":1,"2126":2,"2128":2,"2232":1}}],["methodreference",{"3":{"1436":1}}],["methoddefinition",{"3":{"1436":1}}],["methodinfo",{"3":{"1270":1,"1286":3}}],["methodology",{"3":{"491":1,"797":1,"1496":2,"2114":1,"2232":1}}],["methodname",{"3":{"135":1,"1286":3,"1436":4}}],["methods",{"1":{"1808":1,"1809":1,"1810":1,"1811":1,"1812":1},"3":{"0":11,"68":1,"71":1,"109":1,"130":2,"135":5,"136":3,"137":2,"139":1,"142":1,"281":1,"380":1,"415":1,"499":1,"537":1,"547":1,"549":1,"578":1,"674":2,"749":1,"773":1,"774":2,"775":1,"776":1,"790":1,"854":1,"860":1,"914":1,"921":1,"922":2,"923":1,"924":1,"926":1,"963":1,"964":3,"966":1,"979":3,"980":1,"982":1,"1004":1,"1050":1,"1075":1,"1161":1,"1167":1,"1168":2,"1169":1,"1186":1,"1193":1,"1212":2,"1214":1,"1219":1,"1220":1,"1221":2,"1223":1,"1229":9,"1236":1,"1237":9,"1240":1,"1247":8,"1259":5,"1265":2,"1269":1,"1270":5,"1286":26,"1290":1,"1294":1,"1297":1,"1300":22,"1301":3,"1309":15,"1310":5,"1311":12,"1312":3,"1320":1,"1323":22,"1324":28,"1339":14,"1342":1,"1343":5,"1344":1,"1350":18,"1353":1,"1358":1,"1359":36,"1366":1,"1368":2,"1369":2,"1378":2,"1380":18,"1394":1,"1395":20,"1396":30,"1402":9,"1405":4,"1415":9,"1416":1,"1417":23,"1420":1,"1427":6,"1431":1,"1436":39,"1437":1,"1442":1,"1443":1,"1444":1,"1485":1,"1487":2,"1488":2,"1489":3,"1496":3,"1525":2,"1526":1,"1539":1,"1541":1,"1576":1,"1577":3,"1585":2,"1639":1,"1647":1,"1651":2,"1654":1,"1659":1,"1662":1,"1671":1,"1686":1,"1751":1,"1782":1,"1805":2,"1807":2,"1808":1,"1810":1,"1812":1,"1815":1,"1817":1,"1935":1,"1944":1,"2008":1,"2022":1,"2088":1,"2090":1,"2101":1,"2117":1,"2118":1,"2122":1,"2167":2,"2175":1,"2178":1,"2191":1,"2206":1,"2220":1,"2227":1,"2230":1}}],["method",{"2":{"160":1,"1994":1},"3":{"0":10,"5":3,"24":1,"27":1,"39":1,"93":1,"94":1,"95":2,"96":1,"98":2,"108":1,"109":1,"110":1,"113":1,"130":2,"135":11,"137":1,"139":2,"140":2,"157":1,"160":1,"225":1,"252":1,"254":1,"256":1,"295":1,"312":1,"318":1,"325":1,"336":1,"341":1,"395":1,"397":1,"407":1,"409":1,"459":1,"487":1,"499":1,"511":1,"512":1,"546":2,"548":1,"549":1,"592":1,"631":1,"632":1,"657":1,"665":1,"672":1,"674":6,"676":1,"677":1,"682":1,"715":1,"741":2,"748":1,"749":2,"750":1,"751":4,"776":1,"826":1,"827":1,"846":1,"849":1,"850":1,"855":1,"905":2,"921":1,"979":5,"980":7,"981":2,"982":3,"987":1,"988":1,"996":1,"1004":1,"1018":1,"1019":1,"1021":1,"1026":1,"1027":1,"1030":1,"1036":1,"1043":1,"1059":1,"1093":1,"1100":1,"1133":1,"1135":1,"1161":6,"1162":1,"1168":1,"1169":1,"1170":1,"1184":3,"1212":2,"1214":1,"1215":1,"1218":4,"1224":2,"1229":8,"1231":1,"1233":2,"1234":1,"1237":24,"1238":1,"1241":1,"1242":1,"1244":2,"1247":22,"1259":19,"1261":1,"1265":1,"1269":3,"1270":35,"1271":8,"1278":1,"1286":66,"1288":1,"1291":2,"1293":2,"1300":59,"1301":3,"1309":32,"1310":7,"1311":52,"1312":15,"1314":2,"1315":2,"1316":2,"1318":1,"1323":53,"1324":74,"1326":1,"1329":1,"1333":1,"1335":1,"1336":2,"1339":44,"1344":1,"1345":1,"1346":1,"1348":1,"1350":30,"1353":3,"1355":1,"1359":142,"1362":1,"1365":2,"1368":3,"1369":7,"1372":1,"1378":3,"1380":19,"1382":1,"1383":1,"1389":1,"1395":67,"1396":87,"1402":21,"1405":1,"1415":49,"1417":25,"1418":1,"1421":1,"1427":8,"1429":1,"1431":4,"1436":133,"1438":4,"1441":4,"1442":1,"1443":1,"1444":2,"1453":1,"1467":2,"1486":1,"1488":2,"1489":2,"1492":1,"1493":1,"1496":4,"1507":1,"1539":1,"1541":1,"1641":1,"1653":1,"1654":2,"1666":1,"1685":2,"1686":1,"1701":6,"1702":3,"1715":1,"1730":4,"1747":1,"1755":1,"1769":1,"1776":1,"1783":1,"1789":1,"1790":1,"1803":3,"1804":1,"1806":1,"1807":2,"1809":2,"1810":3,"1812":2,"1815":1,"1817":1,"1818":1,"1820":1,"1821":1,"1824":1,"1825":3,"1826":1,"1831":1,"1832":1,"1840":1,"1842":1,"1856":1,"1859":1,"1880":1,"1881":2,"1884":1,"1899":1,"1908":1,"1912":1,"1935":2,"1937":1,"1944":5,"1950":1,"1953":1,"1954":1,"1955":1,"1956":1,"1962":1,"1970":1,"1982":1,"1983":1,"1994":1,"1995":2,"2000":2,"2006":1,"2014":1,"2015":1,"2020":1,"2030":1,"2032":1,"2043":1,"2047":1,"2058":2,"2066":1,"2072":2,"2078":1,"2080":1,"2083":1,"2091":1,"2092":2,"2094":1,"2097":2,"2126":1,"2134":1,"2139":1,"2156":2,"2166":1,"2170":1,"2172":1,"2184":1,"2191":1,"2197":4,"2200":1,"2203":1,"2232":1}}],["metername",{"3":{"1247":2,"1259":2,"1270":1,"1286":2,"1311":2,"1323":3,"1339":1,"1427":4}}],["metering",{"0":{"2177":1},"3":{"1016":1,"1020":1,"1022":1,"1192":1,"1311":3,"1418":1,"1421":1,"1496":1,"2176":1,"2209":1,"2227":1}}],["meterlistener",{"3":{"897":1}}],["meterprovider",{"3":{"397":1,"1333":1,"1339":1,"1443":1,"2156":1}}],["meters",{"3":{"0":2,"395":2,"397":1,"400":2,"407":1,"408":1,"792":1,"818":1,"819":1,"822":1,"899":1,"1018":1,"1186":1,"1212":1,"1270":1,"1311":1,"1323":1,"1333":3,"1339":7,"1427":1,"1442":2,"1443":3,"1450":1,"1467":2,"1577":1,"1670":2,"1843":1,"2006":1,"2010":1,"2013":1,"2156":2,"2160":2}}],["metered",{"1":{"2170":1,"2171":1,"2172":1,"2173":1,"2174":1,"2175":1,"2176":1,"2177":1,"2178":1,"2179":1,"2180":1},"3":{"0":3,"173":2,"175":1,"177":1,"179":1,"279":1,"280":1,"287":1,"1016":1,"1018":1,"1091":2,"1202":1,"1203":1,"1212":1,"1311":4,"1324":1,"1350":2,"1358":2,"1359":5,"1369":2,"1395":1,"1425":1,"1668":1,"1779":1,"1998":1,"2000":1,"2169":2,"2170":2,"2193":1,"2209":1}}],["meter",{"0":{"407":1,"408":1},"3":{"0":14,"24":2,"66":1,"72":3,"121":1,"124":1,"126":1,"241":1,"255":4,"258":3,"259":1,"261":1,"395":12,"397":7,"399":2,"400":3,"404":5,"406":2,"407":3,"408":4,"409":2,"707":2,"731":1,"819":3,"821":3,"822":2,"823":1,"827":1,"829":1,"897":3,"899":2,"900":1,"901":1,"914":1,"916":1,"917":1,"1016":1,"1018":3,"1020":1,"1044":1,"1089":2,"1090":3,"1091":3,"1092":2,"1093":1,"1094":1,"1096":1,"1192":1,"1212":2,"1247":12,"1256":1,"1259":9,"1268":2,"1270":7,"1275":1,"1286":3,"1311":15,"1317":2,"1323":13,"1333":5,"1339":12,"1366":1,"1369":1,"1379":1,"1396":5,"1400":1,"1402":2,"1418":2,"1421":1,"1425":6,"1427":13,"1435":2,"1438":1,"1442":3,"1443":3,"1451":1,"1465":3,"1467":8,"1496":2,"1577":1,"1586":1,"1665":1,"1668":1,"1843":1,"1919":1,"1922":1,"1946":1,"1949":1,"2002":1,"2006":1,"2010":1,"2013":2,"2156":14,"2157":1,"2159":2,"2160":4,"2177":6,"2179":1,"2180":1,"2191":1,"2193":2}}],["metricalerts",{"3":{"609":1,"1467":1}}],["metricmeasurecolumn",{"2":{"609":1,"2158":1},"3":{"609":2,"1467":2,"2158":1}}],["metricstreamconfiguration",{"2":{"1333":1,"1443":1},"3":{"1333":1,"1339":1,"1443":1,"1577":1}}],["metricsinstrumentationtoggletests",{"3":{"1199":1,"1207":2,"1339":1,"1438":1}}],["metrics",{"2":{"407":1},"3":{"0":2,"15":1,"125":1,"395":4,"397":7,"398":1,"399":2,"401":1,"402":4,"404":1,"407":3,"409":5,"707":1,"709":1,"711":1,"859":1,"861":2,"897":1,"914":1,"916":1,"917":1,"1020":1,"1043":1,"1044":1,"1213":1,"1247":5,"1259":2,"1268":1,"1270":5,"1286":1,"1311":2,"1323":3,"1333":4,"1339":13,"1421":1,"1425":1,"1427":4,"1436":4,"1438":3,"1440":1,"1443":8,"1450":1,"1451":1,"1465":3,"1467":6,"1487":1,"1496":10,"1550":1,"1577":2,"1591":1,"1632":1,"1641":1,"1664":1,"1677":4,"1841":1,"1923":1,"2003":1,"2005":1,"2006":2,"2010":1,"2153":1,"2154":2,"2155":1,"2156":6,"2157":1,"2159":3,"2160":3,"2215":1,"2232":1}}],["metric",{"2":{"916":1},"3":{"0":7,"20":1,"343":1,"395":2,"397":2,"398":2,"527":1,"530":2,"607":2,"608":2,"609":4,"610":2,"611":1,"613":1,"727":1,"818":1,"860":1,"861":3,"862":2,"863":1,"897":2,"898":1,"899":1,"914":1,"916":3,"918":1,"1247":5,"1259":1,"1268":1,"1270":7,"1275":1,"1309":1,"1311":2,"1317":1,"1323":4,"1333":1,"1339":9,"1396":3,"1402":2,"1415":1,"1436":4,"1438":1,"1443":4,"1446":2,"1450":1,"1467":12,"1469":2,"1474":3,"1476":1,"1577":2,"1586":1,"1589":1,"1591":2,"1596":1,"1598":1,"1665":1,"1677":1,"1706":1,"1709":1,"1843":1,"1909":2,"1911":1,"1912":1,"1919":2,"2010":1,"2014":1,"2051":1,"2156":2,"2158":5,"2160":2}}],["ma0176",{"3":{"1436":1}}],["ma0136",{"3":{"1436":1}}],["ma0004",{"2":{"481":1,"482":1},"3":{"481":1,"482":2,"490":1,"491":1,"492":1}}],["ma0076",{"2":{"269":1},"3":{"0":1,"265":1,"269":1,"485":1,"1577":1,"2085":1}}],["magic",{"3":{"1270":1,"1285":1,"1286":2,"1300":2,"1309":2,"1311":4,"1323":4,"1324":1,"1350":1,"1353":1,"1359":1,"1395":2,"1396":1,"1402":2,"1411":1,"1415":7,"1436":1,"1438":1,"1467":1,"1526":1,"1554":1,"1557":1,"1631":1,"1641":2,"1859":1,"1911":1,"1958":1,"2126":1,"2128":2,"2136":1,"2139":1,"2220":1}}],["magazine",{"3":{"1074":1}}],["magnitude",{"3":{"592":1,"860":1,"1436":1,"1707":1,"2001":1}}],["macintosh",{"3":{"1324":2}}],["macos",{"3":{"1212":1,"1381":1,"1416":3,"1417":2,"1459":3,"1513":1,"1606":1,"1740":1,"1773":1,"2078":1,"2230":1}}],["mac",{"3":{"682":1,"684":1,"1286":1,"1324":1,"1417":4,"1660":1,"1669":1}}],["maccatalyst",{"3":{"412":1,"413":2,"434":1,"436":1,"1207":2,"1416":7,"1417":3,"1454":1,"1459":1,"2120":1}}],["machinery",{"1":{"1039":1,"1040":1,"1041":1,"1042":1,"1043":1,"1044":1,"1045":1,"1046":1},"3":{"0":2,"46":1,"164":1,"167":1,"225":1,"230":1,"255":1,"269":1,"272":1,"274":1,"447":1,"448":1,"455":1,"469":1,"470":1,"477":1,"536":1,"548":1,"556":1,"572":1,"676":1,"678":1,"697":1,"733":1,"744":1,"802":1,"845":1,"921":1,"1039":1,"1040":1,"1041":1,"1043":1,"1046":1,"1076":1,"1144":1,"1201":1,"1212":3,"1217":1,"1227":1,"1247":3,"1248":1,"1249":2,"1252":1,"1259":3,"1270":2,"1275":1,"1281":1,"1286":2,"1287":1,"1300":1,"1301":1,"1302":1,"1309":3,"1310":2,"1311":4,"1317":1,"1318":1,"1319":1,"1323":1,"1324":4,"1328":1,"1333":1,"1340":1,"1343":1,"1359":11,"1363":1,"1367":1,"1370":1,"1376":1,"1379":1,"1380":3,"1395":3,"1396":4,"1401":1,"1402":2,"1403":1,"1415":2,"1428":1,"1436":14,"1439":1,"1488":1,"1571":1,"1577":1,"1800":1,"1826":1,"1853":1,"1854":1,"1861":1,"1884":1,"1895":1,"1952":1,"2055":1,"2079":2,"2081":1,"2086":1,"2129":1,"2131":1,"2132":1,"2135":1,"2181":1,"2182":2,"2193":1,"2232":2}}],["machines",{"3":{"0":1,"27":1,"926":1,"1161":1,"1402":1,"1436":3}}],["machine",{"0":{"813":1,"1766":1},"3":{"0":3,"264":1,"265":5,"295":1,"450":1,"485":1,"537":2,"576":1,"584":1,"592":2,"593":1,"620":1,"641":2,"665":2,"725":1,"741":1,"782":1,"784":1,"791":1,"808":2,"809":1,"810":6,"811":2,"812":1,"813":1,"815":1,"914":1,"926":1,"1067":2,"1068":1,"1116":1,"1117":1,"1161":2,"1162":1,"1212":2,"1219":1,"1229":1,"1237":1,"1247":1,"1249":1,"1259":2,"1263":1,"1270":2,"1271":4,"1296":1,"1300":5,"1309":1,"1311":12,"1323":2,"1324":3,"1335":1,"1336":1,"1339":9,"1350":2,"1359":19,"1380":1,"1389":1,"1390":1,"1395":4,"1396":3,"1402":7,"1415":6,"1416":1,"1417":1,"1430":1,"1436":35,"1438":2,"1441":1,"1442":1,"1453":2,"1454":2,"1455":1,"1481":1,"1488":1,"1490":1,"1492":2,"1496":1,"1525":1,"1526":6,"1577":4,"1591":6,"1600":1,"1633":2,"1666":1,"1668":1,"1689":1,"1749":2,"1764":1,"1765":1,"1805":1,"1811":1,"1926":1,"1970":1,"2003":1,"2047":2,"2083":2,"2086":1,"2091":1,"2106":1,"2132":1,"2168":2,"2181":1,"2220":1,"2232":2}}],["mailaddress",{"2":{"1344":1},"3":{"1344":1,"1350":2,"1415":2,"1639":1,"1640":1}}],["mailing",{"3":{"1311":1,"1764":2}}],["mailmessage",{"3":{"1309":2,"1323":1}}],["mails",{"3":{"1294":1,"1407":1}}],["mailto",{"3":{"1271":1,"1417":2}}],["mailed",{"3":{"1184":1,"1300":1,"1324":1}}],["mailbox",{"3":{"1150":1,"1436":1}}],["maildevsmtpport",{"3":{"1339":1,"1441":1}}],["maildevhttpport",{"3":{"1339":1,"1441":1}}],["maildev",{"3":{"1069":1,"1302":1,"1320":1,"1326":1,"1339":6,"1440":1,"1441":4,"1444":2,"1446":2,"1490":1,"1526":1,"1591":1,"1660":1,"1938":1,"2005":2,"2013":1}}],["mail",{"2":{"72":1,"1344":1},"3":{"72":3,"230":3,"673":1,"674":2,"675":1,"926":1,"1059":1,"1108":2,"1203":4,"1207":12,"1295":1,"1300":1,"1302":2,"1309":12,"1311":1,"1320":2,"1323":7,"1339":2,"1344":1,"1350":2,"1408":1,"1415":3,"1436":1,"1438":1,"1439":1,"1467":1,"1512":2,"1932":1,"2003":1,"2004":1,"2012":1,"2013":1,"2235":1,"2237":1}}],["mainly",{"3":{"2109":1}}],["mainlauncher",{"3":{"428":1,"1416":3}}],["mainlayout",{"2":{"275":1,"276":1,"1607":1,"1644":2,"1741":1,"2077":1},"3":{"0":2,"273":6,"275":2,"276":2,"649":3,"652":1,"1324":11,"1396":1,"1526":3,"1577":4,"1582":1,"1607":1,"1627":1,"1644":5,"1653":1,"1687":1,"1741":1,"2077":3,"2083":4}}],["mainapplication",{"3":{"1199":1,"1203":1,"1207":2,"1212":1,"1416":13}}],["mainactivity",{"2":{"423":1},"3":{"418":3,"422":2,"423":2,"425":2,"426":2,"427":1,"428":1,"429":1,"1199":2,"1203":1,"1207":2,"1416":26,"1417":2,"1436":5,"1496":2}}],["mainpage",{"3":{"1199":2,"1203":1,"1207":2,"1212":1,"1416":20,"1417":3}}],["mainpagebase",{"3":{"1199":2,"1203":1,"1207":2,"1324":3,"1416":9,"1417":27,"1496":1}}],["mainthread",{"3":{"1417":6}}],["maintenance",{"3":{"0":1,"336":1,"360":1,"399":1,"423":1,"529":1,"809":1,"814":1,"1075":1,"1247":1,"1286":2,"1300":1,"1350":1,"1369":1,"1395":1,"1436":2,"1459":1,"1465":1,"1844":1,"1848":1,"2142":1}}],["maintainable",{"3":{"1436":1}}],["maintainability",{"3":{"0":1,"1011":1,"1496":1,"1526":1,"1534":1,"1536":1,"1552":1,"1553":1,"1571":1,"1591":1,"2042":1}}],["maintains",{"3":{"1286":2,"1301":1,"1323":1,"1369":1,"1436":1,"1916":1}}],["maintaining",{"3":{"486":1,"634":1,"1309":1,"1311":1,"1359":1,"1436":2,"1479":1,"1639":1}}],["maintainers",{"3":{"1436":1}}],["maintainer",{"3":{"150":1,"590":1,"1436":1,"1454":1,"1459":1,"2047":1}}],["maintained",{"0":{"1521":1},"3":{"0":1,"58":1,"135":1,"342":1,"584":1,"609":1,"620":1,"634":1,"642":1,"683":1,"759":1,"801":1,"839":1,"974":2,"1237":1,"1286":1,"1324":1,"1339":1,"1350":2,"1359":6,"1380":1,"1396":2,"1416":1,"1436":1,"1467":1,"1474":1,"1479":1,"1484":1,"1490":1,"1526":1,"1528":1,"1534":1,"1546":1,"1571":1,"1577":1,"1579":1,"1593":1,"1599":1,"1600":1,"1658":1,"1705":1,"1885":1,"2098":1,"2158":1,"2233":1}}],["maintain",{"3":{"136":1,"167":1,"311":1,"1324":1,"1395":1,"1415":1,"1492":1,"1638":1,"1877":1,"1885":1,"1942":1,"1994":1,"2048":1}}],["main",{"0":{"1467":1},"2":{"63":1,"66":1,"73":1,"296":1,"365":1,"373":1,"374":1,"375":1,"566":1,"593":1,"597":1,"601":1,"604":1,"607":2,"613":1,"629":1,"644":1,"719":1,"756":1,"761":1,"766":1,"794":1,"877":1,"920":1,"1016":1,"1128":1,"1458":1,"1463":1,"1464":1,"1476":1,"1478":1,"1583":1,"1589":1,"1597":1,"1607":1,"1644":1,"1741":1},"3":{"0":5,"15":1,"27":1,"47":1,"59":1,"62":1,"63":2,"66":1,"72":1,"73":1,"92":1,"93":6,"94":3,"95":1,"96":3,"104":3,"128":1,"135":1,"162":1,"180":2,"261":1,"265":1,"291":2,"296":1,"365":1,"368":1,"373":1,"374":2,"375":1,"397":2,"403":5,"409":2,"495":2,"526":1,"528":1,"530":1,"538":1,"543":1,"550":1,"564":1,"566":2,"572":1,"591":2,"593":1,"597":1,"599":57,"600":8,"601":11,"602":3,"603":2,"604":3,"607":6,"608":1,"609":44,"611":3,"612":2,"613":5,"629":2,"633":1,"640":12,"642":4,"643":3,"644":2,"648":1,"664":2,"665":5,"668":4,"669":4,"682":1,"684":1,"719":3,"720":2,"756":2,"757":6,"761":3,"765":5,"766":4,"768":1,"774":2,"790":2,"794":1,"827":3,"832":2,"837":2,"854":1,"869":3,"875":3,"877":3,"914":2,"915":1,"916":2,"917":2,"920":1,"992":1,"995":1,"998":1,"1016":1,"1017":2,"1018":1,"1020":2,"1043":1,"1044":1,"1045":1,"1116":2,"1119":1,"1125":1,"1128":1,"1154":2,"1156":2,"1212":1,"1229":2,"1300":1,"1309":1,"1324":5,"1331":1,"1339":3,"1350":3,"1359":4,"1380":1,"1395":1,"1396":5,"1416":10,"1417":7,"1431":2,"1436":19,"1438":1,"1441":7,"1442":5,"1443":13,"1446":27,"1447":3,"1450":4,"1451":2,"1453":5,"1454":5,"1455":18,"1456":1,"1457":1,"1458":1,"1460":3,"1462":1,"1463":5,"1464":1,"1465":8,"1466":8,"1467":312,"1470":9,"1472":5,"1473":10,"1474":19,"1475":3,"1476":26,"1477":5,"1478":3,"1480":1,"1481":8,"1482":3,"1483":1,"1484":6,"1486":1,"1489":3,"1492":7,"1496":24,"1524":2,"1525":2,"1526":17,"1531":2,"1532":2,"1534":3,"1535":4,"1554":1,"1577":10,"1582":4,"1583":2,"1585":1,"1586":3,"1589":3,"1590":1,"1591":21,"1596":5,"1597":4,"1600":2,"1607":2,"1611":1,"1616":1,"1632":18,"1639":1,"1641":6,"1644":2,"1665":1,"1675":1,"1676":1,"1679":1,"1683":1,"1706":1,"1709":1,"1716":1,"1727":2,"1728":1,"1729":1,"1731":1,"1741":2,"2034":1,"2046":1,"2077":1,"2100":1,"2110":1,"2157":2,"2158":8,"2168":1}}],["masquerading",{"3":{"1543":1,"2048":1}}],["massive",{"3":{"1549":1}}],["mass",{"3":{"666":1}}],["masstransitpackageids",{"2":{"1075":1},"3":{"1075":1,"1436":1,"1491":1}}],["masstransitstatemachine",{"2":{"809":1,"810":1,"813":1},"3":{"0":1,"809":1,"810":1,"813":1}}],["masstransit",{"1":{"144":1,"145":1,"146":1,"147":1,"148":1,"149":1,"150":1,"151":1,"152":1,"153":1,"1072":1,"1073":1,"1074":1,"1075":1,"1076":1,"1077":1,"1078":1,"1079":1},"2":{"54":1,"145":1,"146":1,"147":3,"149":1,"150":1,"152":2,"810":3,"1073":1,"1074":2,"1075":1,"1078":1,"1489":1,"1738":1,"2016":1},"3":{"0":14,"20":1,"54":1,"59":2,"69":1,"77":1,"78":1,"79":2,"131":2,"133":1,"143":1,"144":1,"145":13,"146":1,"147":8,"148":1,"149":3,"150":7,"152":5,"153":1,"194":1,"195":1,"201":1,"255":1,"369":1,"371":1,"376":1,"639":1,"640":5,"641":2,"645":1,"683":1,"810":6,"812":1,"814":1,"815":1,"818":3,"819":1,"820":1,"845":1,"846":1,"1013":1,"1072":1,"1073":2,"1074":12,"1075":6,"1077":1,"1078":2,"1212":3,"1213":1,"1214":2,"1257":4,"1258":3,"1259":26,"1311":5,"1312":1,"1317":1,"1323":30,"1327":3,"1339":5,"1350":1,"1355":2,"1359":1,"1396":1,"1413":1,"1415":2,"1431":1,"1436":29,"1437":1,"1438":3,"1441":1,"1444":3,"1446":3,"1454":2,"1455":1,"1467":8,"1487":1,"1489":8,"1490":1,"1491":3,"1508":1,"1523":1,"1526":2,"1576":2,"1577":6,"1585":1,"1586":1,"1588":1,"1590":1,"1591":2,"1599":1,"1611":1,"1649":1,"1654":1,"1655":1,"1656":1,"1660":1,"1663":1,"1665":1,"1671":1,"1673":1,"1738":1,"1752":1,"1790":2,"1801":2,"1842":2,"1843":3,"1889":2,"1910":1,"2013":1,"2016":1,"2020":1,"2042":1,"2043":1,"2048":2,"2104":1,"2111":1,"2112":1,"2156":1,"2168":2,"2232":4}}],["masked",{"3":{"1237":3,"1415":1,"1436":1,"1466":1}}],["masking",{"3":{"1230":1,"1237":2,"1300":1,"1396":1,"1436":3,"1575":1}}],["mask",{"3":{"415":1,"1237":1,"1311":4,"1406":1,"1436":2,"1459":1,"1496":1,"1549":1,"1675":1,"2122":1}}],["maskseverypiimember",{"3":{"1436":1}}],["masks",{"3":{"39":1,"227":1,"684":1,"1237":1,"1422":1,"1438":1,"1453":1,"1467":1,"1561":1,"1577":1,"2063":1}}],["masterclass",{"3":{"1455":1}}],["master",{"3":{"403":1,"719":1,"766":1,"1324":1,"1395":1,"1415":1,"1417":1,"1436":1,"1467":4,"1496":1,"1629":1,"1632":1,"1637":1,"1638":3}}],["malicious",{"3":{"1311":1,"1415":1,"1452":1,"1459":1,"1467":1}}],["malwarescanning",{"3":{"1116":1,"1119":1}}],["malware",{"3":{"0":1,"1114":1,"1116":1,"1118":1,"1119":1,"1469":1,"1470":1}}],["malformedresponsereason",{"3":{"1369":1}}],["malformedresponse",{"3":{"1300":1,"1378":1,"1380":4}}],["malformedresponsecode",{"3":{"1300":2}}],["malformedifmatchresult",{"3":{"1311":1}}],["malformedifmatch",{"2":{"341":1},"3":{"0":1,"341":1,"1311":1}}],["malformed",{"3":{"0":3,"122":1,"202":1,"255":1,"341":1,"361":1,"657":1,"707":1,"840":1,"854":1,"907":1,"948":1,"1016":1,"1021":1,"1228":1,"1229":1,"1236":1,"1237":2,"1247":4,"1263":2,"1270":1,"1286":6,"1292":1,"1294":1,"1300":13,"1309":1,"1311":5,"1312":2,"1318":1,"1323":8,"1324":7,"1339":2,"1350":4,"1359":12,"1365":1,"1369":3,"1380":2,"1395":1,"1396":4,"1402":2,"1415":2,"1436":3,"1438":6,"1496":1,"1641":3,"1651":1,"1720":1,"1767":1,"1821":1,"1874":1,"1922":1,"1954":2,"1959":2,"2093":1}}],["mariner",{"3":{"2220":1}}],["mariadb",{"3":{"2220":1}}],["martin",{"3":{"1808":1}}],["marten",{"3":{"0":1,"1144":2}}],["marshalling",{"3":{"1416":1}}],["marshalled",{"3":{"1395":1,"1417":1,"1556":1,"1577":1}}],["marshals",{"3":{"682":1,"1324":1,"1396":1,"1417":3}}],["marries",{"3":{"1359":1}}],["margin",{"3":{"1126":1,"1324":4,"1359":1,"1417":1,"1436":2,"1438":1,"1447":2,"1712":1}}],["marginal",{"3":{"0":2,"237":1,"627":1,"862":1,"863":1}}],["march",{"3":{"625":1}}],["markquestionansweredasync",{"3":{"1402":1}}],["markready",{"3":{"1339":3}}],["markreadasync",{"3":{"1309":1,"1324":4,"1436":1}}],["markread",{"3":{"1203":2,"1207":4,"1306":1,"1309":5,"1577":1}}],["marknotificationreadhandlertests",{"3":{"1199":1,"1207":4}}],["marknotificationreadhandler",{"2":{"1306":1,"1937":1},"3":{"1199":4,"1203":1,"1207":2,"1286":1,"1306":3,"1309":6,"1937":1}}],["marknotificationreadcommand",{"2":{"1306":1,"1939":1},"3":{"1199":7,"1203":1,"1207":2,"1306":1,"1309":10,"1939":1}}],["markanswered",{"3":{"1398":1,"1402":3}}],["markansweredasync",{"3":{"1396":2,"1402":1}}],["markallreadasync",{"3":{"1309":1,"1324":3,"1436":1}}],["markallread",{"3":{"1203":2,"1207":4,"1306":1,"1309":4,"1496":1,"1577":1}}],["markallnotificationsreadhandlertests",{"3":{"1199":1,"1207":5,"1438":2,"1496":1}}],["markallnotificationsreadhandlertrackingtests",{"3":{"1199":1,"1207":4}}],["markallnotificationsreadhandler",{"2":{"1306":1,"1937":1},"3":{"1199":5,"1203":1,"1207":2,"1286":1,"1306":2,"1309":4,"1496":2,"1937":1}}],["markallnotificationsreadcommand",{"2":{"1306":1},"3":{"1199":8,"1203":1,"1207":2,"1306":1,"1309":6}}],["markasdelivered",{"3":{"1769":2}}],["markascheckedout",{"2":{"1769":1},"3":{"1769":1}}],["markascancelled",{"2":{"2163":2},"3":{"536":1,"539":1,"1769":1,"2163":2,"2167":1}}],["markassent",{"2":{"1933":1},"3":{"1304":1,"1309":3,"1933":1}}],["markaspaymentfailed",{"2":{"2167":1},"3":{"536":1,"1769":1,"2167":1}}],["markaspaidmanually",{"3":{"1765":1,"1769":1}}],["markaspaid",{"3":{"536":1,"1769":1,"2167":1}}],["markasfailed",{"2":{"896":1,"1933":1},"3":{"225":1,"896":1,"1304":1,"1309":3,"1933":2}}],["markasread",{"2":{"1937":1},"3":{"225":1,"1306":2,"1309":9,"1496":1,"1577":1,"1937":1}}],["markdirty",{"3":{"1324":1,"1395":5,"1396":1}}],["markdown",{"3":{"564":1,"567":1,"611":1,"1286":1,"1436":3,"1453":1,"1457":1,"1475":1}}],["markdeletedasync",{"2":{"459":1,"461":1},"3":{"459":2,"461":1,"1300":2,"1323":1}}],["markinventoryrestored",{"2":{"538":1,"2164":1},"3":{"536":1,"538":1,"2164":1}}],["markings",{"3":{"1672":1}}],["marking",{"3":{"41":1,"196":2,"973":1,"1212":1,"1232":3,"1237":7,"1256":1,"1259":1,"1264":1,"1286":3,"1300":2,"1309":1,"1312":1,"1359":1,"1402":1,"1415":6,"1436":5,"1575":1,"1577":1,"1582":1,"1586":1,"1632":1,"2067":1}}],["markprocessedasync",{"2":{"195":1,"201":1},"3":{"195":1,"201":1,"1258":1,"1259":8,"1286":1,"1586":1}}],["mark",{"3":{"18":2,"20":1,"54":1,"290":1,"536":1,"538":1,"757":1,"761":1,"829":1,"1252":2,"1254":2,"1259":3,"1274":1,"1286":2,"1289":1,"1300":3,"1306":2,"1309":10,"1311":1,"1312":1,"1323":1,"1324":16,"1359":1,"1396":1,"1397":1,"1402":7,"1415":1,"1416":1,"1431":1,"1436":14,"1443":1,"1462":1,"1489":1,"1526":4,"1553":1,"1591":1,"1651":1,"1653":1,"1689":1,"1765":3,"1768":2,"1838":1,"1845":1,"1908":1,"1910":1,"1937":2,"2007":1,"2165":2,"2167":1,"2168":1}}],["markupstring",{"2":{"1563":1},"3":{"1526":1,"1563":1,"1577":1}}],["markupsnapshotresult",{"3":{"1199":2,"1207":1,"1432":1,"1436":2,"1496":1}}],["markupsnapshot",{"3":{"954":2,"1199":5,"1207":3,"1428":1,"1432":9,"1436":8,"1488":1,"1496":1,"1577":3,"1586":1}}],["markuproots",{"3":{"1436":3}}],["markup",{"3":{"0":2,"86":1,"265":2,"273":1,"618":3,"620":1,"648":1,"953":2,"954":3,"957":1,"1207":1,"1324":38,"1350":2,"1359":2,"1384":1,"1391":1,"1394":1,"1395":68,"1396":38,"1402":12,"1415":7,"1417":6,"1431":1,"1432":1,"1434":1,"1436":58,"1438":6,"1488":3,"1489":1,"1497":1,"1498":1,"1525":2,"1526":2,"1531":2,"1534":2,"1535":1,"1555":2,"1563":1,"1564":1,"1577":5,"1586":1,"1591":1,"1597":1,"1600":1,"1643":1,"1660":1,"1671":1,"1702":1,"2057":2,"2059":3,"2060":1,"2072":1,"2077":1,"2079":2}}],["marketing",{"3":{"1359":1,"1395":1,"1416":1,"2240":1}}],["markers",{"0":{"1232":1,"1236":1,"1266":1},"2":{"1270":1},"3":{"0":2,"121":2,"123":1,"125":1,"126":1,"160":1,"246":1,"247":1,"522":1,"523":1,"564":1,"741":1,"744":1,"923":1,"974":1,"1059":1,"1089":1,"1091":1,"1095":1,"1100":1,"1192":1,"1201":1,"1202":2,"1203":10,"1207":16,"1212":1,"1230":2,"1232":1,"1249":1,"1260":2,"1264":11,"1270":21,"1286":3,"1321":1,"1323":4,"1324":3,"1359":15,"1395":2,"1396":1,"1403":2,"1415":8,"1422":1,"1424":3,"1436":3,"1438":1,"1526":1,"1577":1,"1591":1,"1660":1,"1662":2,"1667":1,"1826":1,"2098":1,"2136":1,"2190":1}}],["marker",{"0":{"1264":1,"1823":1,"1917":1,"2136":1,"2164":1},"3":{"0":9,"51":1,"115":1,"117":1,"121":5,"122":3,"123":1,"126":2,"135":1,"242":1,"246":1,"265":2,"293":1,"295":1,"312":1,"402":2,"457":1,"459":11,"461":3,"463":1,"465":5,"466":3,"491":1,"492":1,"493":1,"522":2,"523":1,"534":2,"536":9,"537":2,"538":7,"539":1,"540":2,"541":1,"564":3,"565":1,"566":1,"567":1,"583":1,"618":1,"633":1,"640":1,"700":1,"713":1,"715":3,"716":1,"717":1,"741":1,"742":1,"766":1,"791":1,"792":1,"794":1,"809":1,"815":1,"891":1,"892":1,"893":1,"907":1,"910":1,"923":1,"927":1,"945":1,"947":1,"948":2,"972":7,"973":3,"974":1,"982":1,"988":1,"1042":1,"1059":2,"1091":2,"1093":1,"1095":1,"1100":1,"1192":1,"1202":1,"1203":1,"1212":3,"1232":3,"1236":1,"1237":16,"1239":1,"1247":2,"1249":2,"1259":6,"1260":2,"1262":1,"1264":5,"1266":3,"1267":3,"1269":1,"1270":64,"1273":1,"1277":1,"1282":2,"1283":1,"1286":20,"1297":1,"1300":22,"1301":2,"1309":9,"1310":2,"1311":20,"1312":12,"1313":1,"1316":2,"1318":1,"1323":40,"1324":28,"1327":1,"1333":2,"1339":14,"1340":2,"1350":11,"1354":1,"1359":41,"1369":3,"1380":6,"1394":1,"1395":17,"1396":34,"1401":1,"1402":6,"1405":1,"1410":2,"1415":38,"1416":2,"1424":2,"1427":7,"1435":1,"1436":63,"1438":6,"1441":1,"1444":1,"1455":1,"1478":1,"1489":1,"1496":4,"1507":1,"1526":1,"1531":1,"1532":1,"1577":2,"1582":1,"1591":2,"1596":1,"1653":1,"1662":1,"1666":1,"1667":2,"1672":1,"1749":1,"1753":2,"1764":1,"1765":4,"1774":1,"1823":2,"1827":1,"1839":1,"1851":1,"1902":1,"1908":1,"1914":1,"1920":1,"1923":1,"1929":2,"1933":1,"1988":2,"2063":1,"2092":3,"2095":1,"2096":1,"2098":2,"2134":1,"2139":2,"2161":1,"2164":11,"2168":6,"2169":5,"2184":1,"2189":2,"2194":1,"2199":1,"2203":1,"2232":3}}],["markerduration",{"2":{"459":1,"461":1,"463":1,"2066":1},"3":{"0":1,"459":2,"461":1,"463":1,"1300":3,"1311":2,"2066":1}}],["marked",{"3":{"0":2,"18":1,"20":1,"39":2,"41":1,"51":1,"54":2,"126":2,"160":2,"189":1,"484":1,"490":1,"534":1,"537":1,"538":1,"543":2,"690":1,"714":1,"905":1,"910":1,"980":1,"1003":1,"1203":1,"1212":1,"1229":1,"1237":6,"1252":3,"1259":2,"1264":1,"1270":1,"1277":1,"1286":5,"1300":8,"1309":1,"1311":2,"1312":4,"1323":1,"1342":1,"1347":1,"1349":1,"1350":5,"1357":1,"1359":10,"1380":12,"1402":6,"1405":1,"1415":3,"1422":1,"1436":23,"1438":3,"1441":1,"1447":1,"1459":1,"1467":2,"1489":1,"1496":2,"1508":1,"1577":5,"1640":1,"1672":1,"1699":1,"1727":1,"1737":1,"1749":3,"1764":2,"1767":2,"1821":1,"1839":1,"1840":1,"1930":1,"1975":1,"2063":1,"2089":1,"2098":1,"2165":1,"2197":1}}],["markstale",{"3":{"1324":2,"1577":2}}],["marksnothingcurrent",{"3":{"907":1}}],["marks",{"3":{"0":4,"157":1,"160":1,"344":1,"528":1,"538":1,"540":1,"543":1,"657":1,"674":1,"873":1,"876":1,"907":1,"1012":1,"1091":1,"1233":1,"1236":1,"1237":4,"1257":1,"1259":5,"1265":1,"1270":1,"1274":1,"1286":8,"1297":1,"1300":11,"1309":7,"1310":1,"1311":5,"1312":4,"1322":1,"1323":1,"1324":2,"1339":2,"1350":6,"1359":3,"1369":1,"1395":2,"1396":3,"1402":3,"1405":1,"1415":9,"1416":2,"1417":1,"1427":2,"1436":14,"1441":1,"1512":1,"1577":1,"1582":1,"1586":1,"1630":1,"1632":1,"1639":1,"1662":2,"1666":1,"1672":1,"1687":1,"1765":1,"1784":1,"1846":1,"1871":1,"1876":1,"1929":2,"1937":2,"2012":1,"2013":1,"2044":1,"2091":1,"2165":1}}],["making",{"0":{"1223":1,"2144":1},"3":{"0":3,"159":1,"178":1,"235":1,"237":1,"264":1,"286":1,"340":1,"436":1,"520":1,"551":1,"619":1,"651":1,"666":2,"683":1,"691":1,"698":1,"700":1,"709":1,"716":1,"724":1,"742":1,"767":2,"805":1,"819":1,"820":1,"827":2,"855":1,"890":1,"907":1,"965":1,"996":1,"1018":1,"1027":1,"1051":1,"1076":1,"1117":1,"1212":1,"1229":2,"1237":4,"1247":3,"1259":6,"1270":7,"1271":2,"1286":8,"1293":1,"1300":6,"1301":1,"1309":1,"1310":2,"1311":6,"1323":4,"1324":11,"1332":1,"1339":6,"1350":9,"1359":15,"1369":1,"1395":8,"1396":12,"1402":5,"1417":6,"1436":9,"1438":1,"1454":1,"1455":5,"1467":1,"1492":1,"1638":1,"1641":1,"1734":1,"1776":1,"1806":1,"1901":2,"1956":1,"2007":1,"2045":1,"2070":1,"2081":1,"2155":1,"2162":1}}],["makegenericmethod",{"3":{"1247":1,"1270":1,"1286":5}}],["makegenerictype",{"3":{"1229":1,"1237":1,"1247":1,"1259":1,"1286":5,"1300":2,"1323":1}}],["makes",{"0":{"1782":1,"1824":1,"1872":1},"1":{"1980":1,"1981":1,"1982":1,"1983":1,"1984":1,"1985":1},"3":{"0":20,"24":2,"26":1,"72":2,"100":1,"109":1,"110":3,"111":1,"116":1,"125":1,"136":1,"137":1,"147":1,"150":1,"151":1,"158":1,"160":1,"162":1,"168":1,"177":2,"182":1,"186":2,"187":1,"188":1,"189":1,"194":1,"195":1,"215":1,"217":1,"218":1,"219":1,"230":1,"236":1,"243":1,"244":1,"245":1,"255":2,"264":1,"265":3,"266":3,"273":1,"283":1,"284":1,"309":1,"311":1,"341":1,"342":2,"361":3,"374":1,"388":2,"397":1,"398":1,"407":1,"449":1,"458":1,"460":1,"465":1,"477":1,"484":1,"498":1,"500":1,"511":1,"530":2,"543":1,"546":2,"551":1,"556":1,"557":1,"558":1,"560":1,"563":2,"572":1,"573":1,"582":1,"584":2,"591":1,"592":3,"599":1,"600":1,"601":1,"603":1,"607":2,"609":1,"610":1,"619":1,"641":2,"649":1,"650":1,"656":2,"658":1,"667":1,"670":1,"672":1,"674":1,"675":1,"690":1,"691":4,"692":1,"694":1,"697":1,"698":2,"708":1,"716":3,"717":1,"718":1,"721":1,"725":2,"733":3,"734":1,"735":1,"742":2,"748":1,"750":1,"751":2,"766":1,"767":1,"773":1,"789":1,"791":1,"799":1,"800":1,"802":1,"812":1,"819":1,"820":1,"823":1,"827":3,"828":1,"838":1,"839":1,"840":1,"847":1,"856":2,"860":1,"862":3,"871":1,"873":1,"874":1,"881":1,"882":2,"883":2,"888":1,"890":2,"891":1,"896":3,"898":1,"901":1,"906":1,"918":1,"923":1,"924":1,"932":2,"938":1,"940":2,"942":1,"946":1,"948":2,"954":1,"965":1,"968":1,"972":1,"988":2,"989":1,"990":2,"992":1,"996":1,"997":1,"1005":1,"1006":1,"1012":1,"1018":1,"1019":1,"1020":2,"1025":1,"1027":1,"1033":1,"1036":1,"1044":1,"1051":3,"1052":2,"1060":2,"1067":2,"1076":1,"1084":2,"1087":1,"1091":1,"1092":3,"1093":1,"1109":1,"1116":1,"1123":1,"1124":1,"1129":2,"1132":1,"1133":1,"1135":1,"1137":1,"1147":1,"1150":1,"1162":1,"1167":1,"1169":1,"1212":1,"1223":1,"1229":11,"1231":1,"1237":10,"1247":11,"1250":1,"1252":1,"1253":1,"1257":1,"1259":25,"1265":2,"1266":1,"1269":1,"1270":28,"1271":4,"1275":1,"1276":1,"1278":2,"1283":1,"1284":1,"1286":77,"1290":1,"1291":1,"1292":1,"1294":1,"1300":51,"1301":7,"1309":18,"1310":5,"1311":42,"1312":4,"1314":1,"1316":1,"1317":1,"1320":1,"1323":34,"1324":74,"1325":1,"1326":1,"1327":1,"1328":2,"1330":1,"1333":3,"1334":1,"1336":1,"1337":2,"1338":1,"1339":60,"1343":1,"1344":1,"1346":1,"1350":17,"1353":1,"1355":1,"1356":1,"1357":1,"1359":121,"1362":1,"1364":1,"1366":1,"1369":16,"1376":1,"1380":7,"1392":1,"1393":1,"1395":51,"1396":98,"1397":1,"1401":1,"1402":33,"1405":1,"1406":1,"1412":1,"1415":42,"1416":10,"1417":35,"1421":1,"1422":2,"1424":2,"1425":1,"1427":2,"1430":1,"1431":1,"1435":2,"1436":150,"1438":7,"1441":2,"1443":2,"1447":2,"1453":3,"1454":1,"1455":7,"1456":2,"1457":1,"1458":1,"1460":1,"1465":1,"1467":8,"1472":1,"1474":2,"1475":1,"1476":2,"1477":1,"1480":1,"1488":5,"1489":1,"1490":1,"1491":1,"1492":3,"1526":2,"1534":2,"1536":1,"1568":1,"1577":1,"1633":1,"1638":1,"1649":1,"1652":1,"1658":1,"1661":1,"1662":2,"1667":1,"1677":1,"1684":1,"1685":1,"1686":3,"1688":1,"1697":1,"1701":1,"1708":1,"1711":1,"1720":1,"1729":1,"1731":2,"1737":1,"1749":2,"1755":1,"1765":1,"1779":1,"1790":2,"1795":2,"1797":1,"1808":1,"1810":1,"1811":1,"1812":1,"1813":1,"1814":1,"1815":1,"1819":1,"1825":1,"1839":1,"1840":2,"1841":1,"1842":1,"1843":1,"1844":2,"1846":1,"1849":2,"1853":1,"1854":1,"1876":1,"1879":1,"1883":1,"1884":2,"1895":1,"1899":1,"1902":1,"1905":1,"1907":1,"1908":1,"1916":1,"1922":1,"1923":2,"1924":1,"1939":1,"1943":1,"1944":2,"1946":2,"1947":2,"1950":1,"1958":1,"1963":1,"1970":1,"1971":1,"1980":1,"1982":1,"1983":1,"1989":1,"1993":1,"1996":1,"1997":1,"1998":1,"2000":1,"2001":1,"2010":1,"2016":1,"2020":2,"2021":1,"2022":1,"2024":2,"2029":1,"2030":1,"2031":1,"2034":1,"2039":1,"2040":1,"2042":1,"2043":1,"2044":1,"2045":1,"2057":1,"2061":2,"2064":1,"2066":1,"2070":1,"2073":1,"2083":1,"2087":1,"2098":1,"2112":1,"2132":1,"2133":1,"2137":1,"2141":1,"2142":1,"2144":2,"2145":4,"2146":1,"2156":2,"2157":1,"2160":1,"2163":1,"2164":1,"2165":2,"2166":1,"2167":1,"2169":2,"2170":2,"2177":1,"2180":2,"2184":1,"2185":1,"2186":1,"2188":1,"2192":1,"2193":1,"2202":2,"2203":1,"2208":1}}],["make",{"0":{"2010":1},"3":{"0":4,"1":1,"6":1,"20":1,"39":1,"41":1,"88":1,"109":3,"160":1,"195":1,"219":1,"257":1,"295":1,"305":1,"342":1,"361":1,"373":1,"381":1,"383":1,"390":1,"395":1,"396":1,"403":1,"469":1,"500":1,"518":1,"537":1,"583":1,"584":1,"585":1,"593":1,"609":1,"618":1,"626":1,"650":1,"656":1,"657":1,"675":1,"683":1,"698":1,"709":1,"726":1,"729":1,"740":1,"741":1,"749":1,"750":1,"776":1,"810":1,"815":1,"820":1,"827":1,"829":1,"838":2,"848":1,"861":1,"862":1,"872":1,"889":1,"898":1,"899":1,"903":1,"906":1,"907":1,"914":1,"915":1,"926":1,"931":1,"932":1,"945":1,"988":1,"989":1,"997":1,"1018":1,"1020":1,"1034":2,"1035":2,"1042":1,"1052":1,"1059":1,"1060":1,"1067":1,"1083":1,"1091":1,"1092":1,"1100":1,"1108":1,"1123":1,"1139":1,"1149":1,"1187":1,"1212":1,"1219":1,"1221":1,"1229":3,"1237":2,"1242":1,"1247":3,"1249":1,"1258":2,"1259":1,"1265":1,"1268":1,"1269":3,"1270":4,"1271":3,"1276":1,"1281":1,"1286":23,"1300":14,"1301":1,"1309":2,"1311":13,"1313":1,"1315":1,"1319":1,"1320":1,"1323":8,"1324":13,"1330":1,"1334":1,"1337":1,"1339":7,"1344":1,"1350":7,"1357":1,"1358":1,"1359":30,"1366":1,"1369":2,"1373":1,"1379":1,"1380":1,"1395":14,"1396":23,"1402":5,"1403":1,"1409":1,"1410":1,"1415":2,"1416":2,"1417":8,"1427":1,"1431":1,"1436":39,"1438":1,"1441":2,"1453":1,"1455":5,"1465":1,"1467":4,"1471":1,"1473":1,"1488":1,"1489":1,"1492":1,"1585":1,"1595":1,"1596":1,"1639":2,"1651":1,"1656":1,"1678":1,"1686":1,"1702":1,"1708":1,"1720":1,"1728":1,"1729":1,"1749":1,"1765":2,"1788":2,"1793":1,"1803":1,"1807":2,"1812":1,"1827":2,"1828":1,"1829":1,"1832":1,"1841":1,"1846":2,"1853":2,"1859":1,"1862":2,"1863":1,"1865":1,"1868":1,"1871":1,"1877":2,"1886":2,"1890":1,"1892":2,"1901":1,"1905":1,"1907":1,"1911":1,"1912":3,"1930":2,"1941":1,"1947":1,"1948":1,"1954":1,"1958":1,"1959":1,"1964":1,"1965":1,"1972":1,"1979":1,"1980":1,"1981":1,"1985":1,"1995":3,"1996":1,"1997":1,"1998":1,"2004":1,"2009":1,"2028":1,"2038":1,"2040":1,"2043":1,"2049":1,"2060":1,"2067":1,"2068":1,"2069":1,"2080":1,"2097":1,"2105":1,"2108":1,"2111":1,"2113":1,"2125":1,"2136":1,"2139":1,"2142":1,"2145":1,"2153":1,"2155":1,"2160":3,"2161":1,"2166":2,"2167":1,"2169":1,"2170":1,"2179":2,"2180":3,"2192":2,"2193":3,"2197":1,"2200":1,"2203":4,"2209":1,"2211":1,"2232":1}}],["mat",{"3":{"1524":8,"1525":10,"1589":6,"1598":1}}],["matrices",{"3":{"1456":1,"1526":1,"1535":1,"1611":1}}],["matrix",{"0":{"1499":1,"1716":1},"2":{"1456":1},"3":{"137":1,"146":1,"148":1,"265":1,"618":1,"619":1,"684":1,"756":1,"863":2,"868":1,"1192":1,"1212":1,"1216":1,"1247":1,"1300":1,"1324":2,"1402":1,"1417":2,"1433":2,"1436":3,"1438":1,"1445":1,"1450":1,"1453":1,"1455":5,"1456":10,"1460":1,"1461":1,"1465":1,"1490":4,"1492":3,"1496":1,"1526":2,"1533":1,"1559":1,"1577":7,"1591":4,"1596":1,"1599":1,"1611":1,"1632":2,"1639":1,"1674":1,"1711":1,"2073":1,"2076":1,"2112":1}}],["mates",{"3":{"1271":1}}],["materialised",{"3":{"1339":2,"1396":3}}],["materializing",{"3":{"1237":1,"1242":1,"1243":1,"1245":1,"1259":1,"1269":1,"1270":1,"1286":5,"1309":2,"1311":1,"1323":1,"1324":1,"1353":1,"1359":5,"1395":1,"1396":2,"1402":2,"1415":2,"1436":1,"1988":1,"2167":1}}],["materializers",{"3":{"1286":3}}],["materializer",{"3":{"657":1,"1237":1,"1438":1}}],["materialize",{"3":{"31":1,"549":1,"609":1,"659":1,"741":1,"1243":2,"1247":1,"1269":1,"1270":3,"1271":1,"1278":1,"1286":4,"1300":2,"1301":1,"1309":2,"1311":2,"1323":1,"1359":4,"1395":1,"1396":1,"1402":1,"1427":1,"1436":2,"1438":2,"1667":1,"1883":1,"1956":1,"1965":1}}],["materializes",{"3":{"0":1,"166":1,"186":1,"551":1,"819":1,"964":1,"966":1,"1212":1,"1231":1,"1237":1,"1239":1,"1243":1,"1247":3,"1259":1,"1263":1,"1270":1,"1271":2,"1286":13,"1297":1,"1300":5,"1309":4,"1310":2,"1311":2,"1324":4,"1350":1,"1359":8,"1380":1,"1395":3,"1396":5,"1402":3,"1415":3,"1417":1,"1423":1,"1427":3,"1436":5,"1467":1,"1810":1,"1815":1,"1844":1,"1859":1,"1861":1,"1895":1,"1962":1,"2090":1}}],["materialized",{"3":{"0":1,"47":1,"71":1,"122":1,"188":1,"335":1,"609":2,"658":1,"799":1,"988":1,"1091":1,"1229":1,"1239":1,"1243":4,"1244":1,"1247":11,"1259":2,"1270":4,"1286":9,"1288":1,"1300":3,"1309":2,"1310":4,"1311":1,"1323":1,"1324":1,"1359":25,"1396":9,"1402":1,"1406":1,"1415":2,"1423":3,"1427":1,"1431":1,"1436":2,"1467":1,"1474":1,"1476":1,"1964":1,"1988":1,"2158":2,"2232":1}}],["materialization",{"3":{"0":1,"330":1,"359":1,"365":1,"657":1,"990":1,"1226":1,"1229":2,"1244":1,"1247":5,"1270":2,"1278":1,"1284":1,"1286":10,"1300":1,"1309":6,"1310":1,"1323":1,"1343":1,"1359":2,"1362":1,"1369":1,"1395":1,"1396":6,"1402":3,"1436":1,"1805":1,"1806":1,"1811":1,"1815":1,"2142":1}}],["materials",{"0":{"1357":1,"1389":1},"1":{"1113":1,"1114":1,"1115":1,"1116":1,"1117":1,"1118":1,"1119":1,"1120":1},"3":{"0":2,"370":1,"371":1,"372":1,"374":1,"443":1,"1113":1,"1116":5,"1117":3,"1212":1,"1348":1,"1350":2,"1357":2,"1359":4,"1373":1,"1377":1,"1380":2,"1381":1,"1389":1,"1395":10,"1436":1,"1454":1,"1630":1,"1631":5,"1636":1,"1640":2,"2126":1,"2232":1}}],["material",{"3":{"0":1,"30":1,"31":1,"284":1,"359":3,"361":1,"599":1,"650":1,"673":1,"674":1,"792":1,"827":1,"906":1,"907":1,"944":1,"946":3,"948":2,"950":1,"1060":1,"1063":1,"1066":1,"1115":1,"1117":1,"1118":1,"1212":1,"1241":1,"1285":1,"1286":2,"1287":2,"1288":1,"1292":1,"1294":1,"1300":20,"1311":1,"1323":2,"1324":6,"1339":1,"1342":2,"1350":3,"1351":1,"1352":1,"1357":1,"1359":2,"1373":2,"1380":1,"1389":1,"1395":15,"1409":1,"1410":1,"1415":6,"1417":1,"1429":1,"1430":1,"1436":4,"1438":3,"1442":1,"1447":1,"1456":1,"1467":2,"1640":1,"1645":1,"1667":1,"1714":1,"1902":4,"1905":1,"1973":1,"1982":1,"2012":1,"2024":1,"2056":2,"2142":2,"2145":1,"2166":1,"2198":1,"2203":1,"2230":1,"2232":1,"2234":1}}],["materially",{"3":{"0":1,"259":1,"474":1,"549":1,"799":1,"987":1,"1324":1,"1436":1,"1467":1,"1534":1}}],["maturity×weight",{"3":{"1523":1,"1574":1,"1588":1}}],["maturity",{"0":{"1533":1,"1584":1,"1598":1,"1797":1},"2":{"1529":1},"3":{"1216":1,"1516":1,"1519":1,"1523":5,"1524":6,"1526":27,"1529":3,"1530":4,"1531":10,"1532":2,"1533":2,"1534":1,"1535":7,"1536":1,"1537":9,"1554":1,"1565":1,"1574":4,"1575":10,"1576":1,"1577":20,"1578":3,"1580":4,"1581":4,"1582":4,"1583":4,"1584":4,"1585":4,"1586":1,"1588":3,"1589":1,"1591":21,"1592":3,"1594":3,"1595":3,"1596":3,"1597":3,"1599":1,"1673":1,"1784":6,"1794":1,"1797":1,"1798":3,"1799":7,"1800":1,"1802":2,"1903":2,"1929":2,"2034":1,"2041":1,"2043":2,"2048":1,"2050":1,"2053":1,"2059":2,"2062":1,"2069":1,"2114":1,"2171":1,"2182":2,"2195":1,"2231":4}}],["mature",{"3":{"1216":1,"1537":1,"1578":1,"1784":1,"1797":1,"1833":1}}],["mathematics",{"3":{"2222":1}}],["math",{"3":{"281":1,"330":1,"337":1,"1229":1,"1244":3,"1247":7,"1259":1,"1286":1,"1300":3,"1311":3,"1324":9,"1348":1,"1350":6,"1359":2,"1369":1,"1395":1,"1396":15,"1401":1,"1402":1,"1415":4,"1417":4,"1422":1,"1435":1,"1438":1,"1455":2,"1988":1,"2000":1}}],["mattering",{"3":{"1323":1,"2097":1}}],["matters",{"0":{"1789":1,"1809":1,"1814":1,"1819":1,"1836":1,"1848":1,"1864":1,"1870":1,"1888":1,"1894":1,"1901":1,"1907":1,"1909":1,"1926":1,"1961":1,"1967":1,"1974":1,"1981":1,"1987":1,"1993":1,"1999":1,"2051":1,"2062":1,"2082":1,"2125":1,"2130":1,"2135":1,"2141":1,"2148":1,"2155":1,"2162":1,"2171":1,"2182":1,"2195":1},"3":{"160":1,"198":1,"216":1,"264":1,"380":1,"387":1,"390":1,"423":1,"551":1,"592":1,"702":1,"827":1,"845":1,"847":1,"875":1,"906":1,"941":2,"1186":1,"1223":1,"1229":1,"1237":3,"1238":1,"1247":3,"1259":2,"1270":4,"1271":1,"1280":1,"1286":17,"1300":7,"1309":5,"1311":6,"1315":1,"1323":7,"1324":11,"1339":13,"1345":1,"1347":1,"1359":18,"1366":1,"1369":1,"1372":1,"1379":1,"1380":1,"1395":11,"1396":13,"1398":1,"1402":2,"1415":7,"1417":6,"1421":1,"1427":1,"1436":18,"1438":2,"1441":1,"1445":1,"1447":1,"1453":3,"1454":1,"1455":1,"1456":1,"1467":2,"1475":2,"1488":1,"1490":1,"1555":1,"1649":1,"1684":1,"1782":1,"1813":1,"1817":1,"1840":1,"1844":1,"1878":1,"1886":1,"1889":1,"1898":1,"1902":1,"1934":1,"1937":1,"1943":1,"1950":1,"1962":1,"2007":1,"2010":1,"2029":1,"2036":1,"2039":1,"2049":1,"2057":1,"2060":1,"2073":1,"2075":1,"2095":1,"2098":1,"2099":1,"2118":1,"2128":1,"2155":1,"2156":1,"2193":1,"2201":1}}],["matter",{"0":{"2043":1},"3":{"122":1,"160":1,"219":1,"265":1,"783":1,"998":1,"1004":1,"1059":1,"1192":2,"1229":1,"1259":1,"1270":1,"1271":1,"1286":3,"1296":1,"1300":5,"1301":3,"1311":3,"1323":3,"1324":2,"1339":3,"1350":1,"1359":10,"1369":1,"1395":1,"1396":3,"1397":1,"1402":1,"1410":1,"1415":1,"1417":4,"1431":2,"1436":4,"1438":3,"1463":1,"1475":2,"1489":1,"1490":1,"1496":2,"1638":1,"1831":1,"1874":1,"1883":1,"1908":1,"1918":1,"1922":1,"2024":1,"2045":1,"2049":1,"2093":1,"2109":1,"2150":1,"2154":1,"2158":1,"2187":1,"2200":1}}],["matchtimeout",{"3":{"1427":2}}],["matchtimeoutmilliseconds",{"3":{"1237":2,"1309":1}}],["matchlocaleasync",{"3":{"1417":2}}],["matchmedia",{"2":{"273":1},"3":{"273":1,"2083":1}}],["matchingmethods",{"3":{"1436":1}}],["matching",{"0":{"1910":1},"3":{"0":8,"31":1,"80":1,"97":1,"126":1,"136":1,"158":1,"166":1,"180":1,"226":1,"243":2,"265":2,"272":1,"295":1,"318":1,"351":1,"352":1,"353":1,"365":1,"403":1,"420":1,"448":1,"470":1,"473":1,"491":1,"492":1,"493":2,"494":2,"495":1,"528":1,"536":2,"540":1,"543":1,"551":2,"554":1,"556":1,"575":1,"599":1,"603":1,"607":1,"609":2,"612":1,"643":1,"657":2,"692":3,"697":1,"698":1,"716":1,"749":1,"760":1,"764":1,"790":1,"827":1,"838":1,"839":1,"840":1,"842":1,"847":1,"882":1,"925":1,"939":1,"946":1,"948":1,"964":1,"970":1,"972":1,"975":1,"1059":1,"1089":1,"1133":1,"1191":1,"1196":1,"1212":1,"1229":4,"1230":1,"1234":1,"1237":5,"1239":1,"1247":7,"1258":1,"1259":3,"1270":9,"1271":3,"1276":1,"1278":1,"1286":30,"1288":1,"1289":1,"1291":1,"1293":3,"1300":18,"1301":3,"1309":8,"1310":3,"1311":21,"1312":4,"1322":1,"1323":8,"1324":22,"1327":1,"1337":1,"1339":15,"1344":2,"1350":16,"1352":1,"1353":1,"1359":38,"1366":1,"1369":3,"1374":1,"1380":3,"1395":15,"1396":17,"1399":1,"1402":7,"1405":1,"1415":4,"1417":12,"1427":10,"1430":1,"1436":69,"1438":1,"1443":2,"1445":1,"1446":1,"1447":2,"1454":1,"1455":2,"1464":1,"1467":1,"1476":1,"1488":2,"1489":1,"1492":1,"1496":2,"1526":1,"1572":2,"1577":2,"1586":1,"1589":2,"1591":2,"1599":1,"1630":2,"1631":1,"1632":2,"1639":1,"1641":8,"1662":1,"1670":1,"1821":1,"1851":1,"1859":1,"1861":1,"1894":1,"1895":1,"1902":1,"1906":1,"1916":1,"1975":1,"1978":1,"1979":1,"1994":1,"2025":1,"2027":1,"2046":1,"2063":1,"2131":1,"2157":1,"2158":1,"2159":1,"2164":1,"2232":1}}],["matchers",{"3":{"1427":1}}],["matcher",{"3":{"1237":1,"1309":2,"1311":1,"1324":1,"1339":1,"1347":1,"1350":1,"1436":2,"1438":1}}],["matcheswholeregiontokensonly",{"3":{"1436":2}}],["matchestherouteattributereality",{"3":{"1436":1}}],["matchesthecurrentcontract",{"2":{"1427":1},"3":{"1427":1}}],["matchesserverstrongpasswordrules",{"3":{"1436":2}}],["matchessignature",{"3":{"1286":1}}],["matchesdeployedregion",{"3":{"972":1,"1436":3,"2043":1}}],["matches",{"0":{"720":1},"3":{"0":5,"39":1,"40":1,"74":1,"91":1,"96":1,"109":1,"139":1,"142":1,"179":1,"217":1,"218":1,"258":1,"265":1,"268":1,"318":1,"324":1,"335":1,"341":1,"345":1,"352":1,"372":1,"398":1,"402":1,"415":1,"500":1,"545":1,"551":2,"564":1,"611":4,"650":1,"655":1,"666":1,"688":1,"734":1,"740":1,"749":1,"769":1,"790":1,"801":1,"827":1,"847":1,"857":1,"871":1,"881":1,"904":1,"946":1,"955":1,"972":1,"973":1,"974":1,"975":2,"1004":1,"1006":2,"1018":1,"1040":1,"1091":1,"1118":1,"1135":1,"1177":1,"1194":1,"1196":1,"1202":1,"1212":1,"1223":1,"1229":1,"1234":1,"1237":3,"1240":1,"1241":1,"1247":8,"1259":3,"1262":1,"1270":6,"1271":5,"1275":1,"1284":2,"1286":26,"1289":1,"1292":1,"1297":1,"1300":19,"1301":3,"1306":1,"1309":6,"1310":4,"1311":18,"1312":1,"1317":1,"1323":8,"1324":29,"1333":1,"1337":1,"1339":8,"1347":1,"1350":4,"1352":1,"1353":2,"1358":1,"1359":41,"1364":1,"1369":1,"1380":4,"1385":1,"1395":11,"1396":13,"1402":3,"1406":1,"1413":1,"1415":14,"1416":2,"1417":9,"1420":1,"1421":1,"1424":1,"1427":8,"1432":1,"1435":1,"1436":41,"1438":2,"1440":1,"1441":2,"1443":2,"1453":2,"1455":1,"1456":1,"1467":1,"1475":3,"1476":2,"1480":2,"1481":1,"1488":1,"1489":1,"1496":1,"1524":1,"1525":1,"1526":2,"1531":1,"1567":1,"1571":1,"1575":1,"1577":6,"1582":1,"1585":3,"1586":1,"1589":2,"1591":3,"1596":2,"1597":1,"1631":1,"1632":1,"1639":2,"1641":2,"1651":1,"1654":1,"1686":2,"1730":1,"1749":1,"1750":1,"1765":1,"1767":2,"1771":1,"1829":1,"1851":1,"1872":1,"1904":1,"1920":1,"1923":1,"1928":1,"1948":1,"1952":1,"1978":1,"1995":1,"1996":2,"2000":1,"2043":1,"2045":2,"2048":1,"2083":1,"2097":1,"2157":1,"2166":1,"2187":1,"2199":1,"2232":1}}],["matchedhash",{"3":{"1300":1}}],["matchedstep",{"3":{"1300":4}}],["matched",{"3":{"0":2,"136":1,"142":1,"160":1,"186":1,"249":1,"265":2,"310":1,"353":1,"372":1,"538":2,"557":1,"564":1,"585":1,"586":1,"592":1,"649":1,"827":1,"837":1,"840":2,"877":1,"905":1,"946":1,"999":1,"1029":1,"1045":1,"1067":1,"1089":1,"1091":2,"1117":1,"1135":1,"1161":2,"1163":1,"1196":1,"1231":1,"1237":1,"1247":2,"1270":2,"1286":9,"1300":14,"1301":1,"1310":1,"1311":8,"1312":1,"1315":1,"1323":2,"1324":4,"1338":1,"1339":8,"1350":1,"1355":1,"1359":5,"1395":3,"1396":2,"1402":1,"1415":1,"1416":1,"1417":3,"1419":1,"1421":1,"1424":1,"1427":4,"1436":38,"1443":1,"1447":1,"1455":1,"1458":1,"1492":1,"1526":1,"1568":1,"1577":1,"1631":5,"1636":1,"1639":2,"1640":1,"1641":2,"1653":1,"1684":1,"1700":1,"1902":1,"1951":1,"2044":1,"2139":1,"2149":1,"2157":1,"2168":1}}],["matchasync",{"2":{"1220":1},"3":{"0":1,"109":2,"1220":2,"1229":3,"1805":2}}],["match",{"0":{"1874":1},"1":{"1869":1,"1870":1,"1871":1,"1872":1,"1873":1,"1874":1,"1875":1,"1876":1,"1877":1},"2":{"1004":1,"1807":1},"3":{"0":8,"93":1,"98":1,"102":1,"109":2,"152":1,"162":1,"173":1,"268":1,"279":1,"300":1,"314":1,"339":1,"340":1,"341":3,"342":3,"343":2,"344":1,"348":1,"350":2,"353":2,"363":1,"402":1,"420":1,"489":1,"499":1,"507":1,"511":1,"540":1,"547":1,"564":1,"572":1,"607":1,"609":2,"610":1,"611":2,"635":1,"658":1,"744":1,"766":1,"785":1,"789":1,"809":1,"813":1,"829":1,"833":1,"879":1,"881":2,"885":1,"907":1,"909":1,"916":1,"922":2,"925":1,"944":1,"965":1,"972":4,"973":1,"974":2,"975":1,"1004":3,"1006":1,"1021":1,"1026":1,"1040":1,"1042":1,"1091":3,"1095":1,"1119":1,"1120":1,"1133":1,"1135":1,"1150":1,"1212":1,"1220":2,"1229":5,"1234":1,"1237":6,"1247":5,"1259":3,"1264":1,"1265":2,"1270":8,"1271":2,"1273":1,"1286":24,"1289":1,"1293":2,"1295":1,"1300":26,"1301":6,"1305":1,"1307":1,"1309":4,"1310":3,"1311":27,"1323":9,"1324":28,"1339":14,"1347":3,"1349":1,"1350":15,"1355":4,"1359":55,"1369":5,"1373":1,"1375":1,"1380":9,"1382":1,"1389":2,"1395":18,"1396":17,"1402":18,"1406":3,"1413":2,"1414":1,"1415":13,"1416":3,"1417":5,"1424":2,"1427":7,"1432":3,"1436":65,"1438":2,"1443":4,"1451":1,"1455":2,"1456":1,"1457":1,"1459":1,"1461":1,"1467":1,"1475":1,"1476":3,"1481":1,"1489":3,"1492":3,"1496":9,"1517":1,"1526":4,"1531":1,"1541":1,"1545":1,"1562":1,"1577":1,"1591":1,"1596":1,"1600":2,"1611":2,"1627":1,"1631":3,"1632":5,"1635":3,"1638":1,"1640":1,"1641":7,"1651":3,"1654":1,"1662":1,"1664":1,"1689":1,"1720":1,"1728":1,"1748":4,"1749":4,"1765":5,"1779":1,"1796":1,"1805":3,"1807":1,"1810":1,"1816":1,"1832":1,"1869":2,"1871":1,"1872":1,"1874":3,"1876":2,"1877":3,"1882":1,"1923":1,"1926":1,"1945":1,"1973":2,"1975":1,"1976":1,"1977":1,"1978":1,"1979":2,"1995":1,"2000":1,"2003":1,"2033":1,"2107":1,"2113":1,"2126":1,"2136":1,"2144":1,"2160":1,"2164":1,"2166":1,"2188":1,"2198":1,"2207":1,"2232":1}}],["maxhandlerconstructordependencies",{"2":{"1431":1},"3":{"1431":1,"1436":9}}],["maxopenquestionsperuserpersession",{"3":{"1402":4}}],["maxoptions",{"3":{"1398":1,"1402":8}}],["maxoutcomes",{"3":{"1396":2}}],["maxoutputtokens",{"2":{"1419":1,"1422":1,"2173":1,"2174":1},"3":{"1018":1,"1090":1,"1091":3,"1094":1,"1366":1,"1369":3,"1419":1,"1422":1,"1427":5,"2173":1,"2174":1}}],["maxsizebytes",{"2":{"1478":1},"3":{"1478":1}}],["maxserviceconstructorparameters",{"3":{"1436":2}}],["maxsponsors",{"3":{"1395":1}}],["maxspeakersessions",{"3":{"1395":4}}],["maxsuggestions",{"3":{"1395":1}}],["maxsavepasses",{"3":{"1278":1,"1286":3}}],["maxpolls",{"3":{"1395":5}}],["maxpairs",{"3":{"1359":2}}],["maxpages",{"3":{"1324":2,"1395":1}}],["maxpagesize=500",{"3":{"1577":1}}],["maxpagesize",{"2":{"331":1,"337":1,"741":1,"743":1,"1228":1,"1245":1,"1988":1,"1991":1,"2201":1},"3":{"0":1,"330":2,"331":1,"337":2,"674":1,"741":1,"743":1,"1228":2,"1229":3,"1242":1,"1245":1,"1247":3,"1259":1,"1286":1,"1309":4,"1311":8,"1320":1,"1323":4,"1324":3,"1380":2,"1395":1,"1396":4,"1415":2,"1748":2,"1988":3,"1991":1,"2201":1}}],["maxpayloadbytes",{"3":{"1309":1}}],["maxmanualidattempts",{"3":{"1359":3,"1395":1}}],["maxlineoctets",{"3":{"1300":2}}],["maxlockoutseconds",{"2":{"1292":1},"3":{"1292":2,"1300":2}}],["maxlookupselectorcacheentries",{"3":{"1286":2}}],["maxlengtherror",{"3":{"1396":2}}],["maxlengthattribute",{"3":{"1324":1,"1395":1}}],["maxlength",{"2":{"657":1,"1353":1,"1833":1},"3":{"657":2,"1237":6,"1271":10,"1286":5,"1309":5,"1311":2,"1324":10,"1350":10,"1353":1,"1359":24,"1395":15,"1396":9,"1631":1,"1727":1,"1810":2,"1829":1,"1833":1}}],["maxrosterpages",{"3":{"1396":2}}],["maxroster",{"3":{"1395":2}}],["maxroleslength",{"3":{"1286":4,"1318":1,"1323":2}}],["maxrenderedrows",{"3":{"1436":1}}],["maxrendereditems",{"2":{"2079":1},"3":{"1324":2,"2079":1}}],["maxreturnedownquestions",{"3":{"1402":1}}],["maxreturnedquestions",{"3":{"1402":2}}],["maxretryattempts",{"3":{"1339":5,"1443":1}}],["maxretrydelay",{"2":{"987":1},"3":{"987":1,"1286":2}}],["maxretrycount",{"2":{"987":1},"3":{"987":1,"1286":2}}],["maxretrybackoffseconds",{"2":{"2188":1},"3":{"0":1,"1042":2,"1286":1,"2188":2}}],["maxretries",{"2":{"19":1,"409":1,"2032":1},"3":{"19":3,"24":1,"27":1,"397":2,"409":1,"819":1,"849":1,"1256":1,"1259":11,"1286":1,"1324":1,"1339":1,"1359":1,"2032":1}}],["maxrecentcount",{"3":{"1396":3}}],["maxrequestsperemail",{"2":{"1641":1},"3":{"1300":5,"1641":2,"1767":1}}],["maxrequestspertcpconnection",{"3":{"1286":1}}],["maxrequestbytes",{"2":{"1118":1,"1373":1},"3":{"1118":1,"1350":2,"1373":1,"1380":1}}],["maxregistrationsperipperhour",{"2":{"281":1,"1326":1,"1339":1,"1444":1},"3":{"1300":1,"1326":1,"1339":1,"1444":1,"1496":1}}],["maxreplicas",{"2":{"538":1,"664":1,"764":1,"765":1,"766":2,"767":1,"768":1,"995":1,"1154":1,"1156":1,"1469":1,"2168":1},"3":{"0":1,"390":2,"534":4,"538":1,"540":1,"664":1,"665":1,"764":1,"765":1,"766":2,"767":1,"768":1,"995":1,"1154":1,"1156":1,"1467":6,"1469":1,"1526":4,"1535":1,"2168":1}}],["maxtimeout",{"3":{"1427":3}}],["maxtagsperexpression",{"3":{"1323":2}}],["maxtcpconnectionsperendpoint",{"3":{"1286":1}}],["maxtenantidlength",{"3":{"1286":2}}],["maxkeylength",{"3":{"1286":1}}],["maxintervalseconds",{"3":{"1707":1}}],["maxinlinecodelines",{"3":{"1436":4,"1526":1}}],["maxinspectedentries",{"3":{"1286":1}}],["maximumlength",{"2":{"1828":1,"1833":1},"3":{"1271":16,"1300":6,"1309":3,"1324":4,"1359":38,"1402":4,"1436":3,"1828":1,"1829":1,"1833":1}}],["maximumlengthvalidator",{"3":{"1271":2,"1395":1}}],["maximum",{"3":{"71":1,"529":1,"819":1,"862":1,"1271":1,"1275":1,"1286":1,"1300":1,"1324":5,"1339":1,"1359":11,"1395":7,"1396":1,"1402":1,"1415":2,"1436":1,"1457":1,"1630":1,"1631":2,"1640":3,"1641":1,"1767":1,"1840":1,"1908":1,"2032":1}}],["maximal",{"3":{"216":1,"1324":1,"2152":1}}],["maxima",{"3":{"0":1,"861":2,"862":1,"1436":1,"1596":1}}],["maxeventnamelength",{"3":{"1309":1}}],["maxentitytypelength",{"3":{"1286":1}}],["maxerrorlength",{"3":{"1259":1,"1286":1,"1323":1}}],["maxexportrows",{"2":{"743":1,"1988":1,"1991":1},"3":{"0":1,"674":1,"741":1,"743":1,"1212":1,"1229":1,"1311":4,"1320":1,"1323":4,"1988":2,"1991":1}}],["maxfilemegabytes",{"3":{"1395":3}}],["maxfilebytes",{"3":{"1350":8,"1359":2,"1380":1,"1395":5}}],["maxfilters",{"3":{"1241":1,"1247":2,"1311":4}}],["maxframes",{"3":{"1323":3}}],["maxfailedattempts",{"2":{"286":1,"1292":1,"2001":1},"3":{"175":1,"281":1,"286":1,"1292":1,"1300":4,"2000":1,"2001":1}}],["maxdirectfiles",{"2":{"1004":1},"3":{"1004":2,"1431":1,"1436":3,"2043":1}}],["maxdecodeddimension",{"2":{"442":1},"3":{"0":1,"442":1,"1323":3,"2126":1}}],["maxdecodedpixels",{"2":{"442":1},"3":{"0":1,"442":1,"1323":3,"2126":1}}],["maxvalidationattempts",{"2":{"1641":1},"3":{"854":1,"1294":1,"1300":5,"1641":2,"1767":1}}],["maxvalue",{"2":{"335":1,"1419":1},"3":{"335":1,"827":1,"1124":1,"1242":1,"1247":2,"1286":1,"1309":4,"1311":1,"1323":3,"1324":4,"1339":3,"1350":2,"1396":3,"1415":4,"1419":1,"1427":1,"1436":5,"1465":1,"2000":1}}],["maxavatarbytes",{"3":{"1415":7}}],["maxavatarrequestbytes",{"2":{"1411":1},"3":{"1411":1,"1415":4}}],["maxactivities",{"3":{"1395":1}}],["maxactivecircuits",{"2":{"830":1,"831":1,"1128":1},"3":{"830":1,"831":1,"832":1,"1124":3,"1128":1,"1212":1,"1324":5,"1416":1,"2000":1}}],["maxactivesessionsperuser",{"2":{"909":1,"1290":1},"3":{"0":1,"905":2,"909":1,"1290":1,"1300":2,"1982":2}}],["maxassetspersession",{"2":{"1357":1,"1389":1},"3":{"1350":2,"1357":1,"1359":2,"1380":1,"1389":1,"1395":1}}],["maxage",{"3":{"1300":2,"1324":3}}],["maxattempts",{"3":{"444":1,"1042":1,"1286":2,"1323":1,"1436":1,"2188":1}}],["maxcalldepth",{"3":{"1436":4}}],["maxcacheentries",{"2":{"337":1},"3":{"330":1,"333":1,"337":1,"1242":1,"1247":2}}],["maxchannelkeylength",{"3":{"1309":1}}],["maxcodebehindlines",{"2":{"1531":1},"3":{"1436":4,"1526":1,"1531":1}}],["maxcodelength",{"3":{"1300":3}}],["maxconstructordependencies",{"3":{"1431":1,"1436":7}}],["maxconsecutivefailures",{"3":{"1395":1}}],["maxcontrollerconstructordependencies",{"2":{"1431":1},"3":{"1431":1,"1436":7}}],["maxconnectionsperuser",{"2":{"1307":1,"1309":1,"1945":1},"3":{"0":1,"228":1,"384":1,"1307":1,"1309":3,"1323":3,"1945":1}}],["maxcorrelationidlength",{"3":{"1286":2}}],["maxcount",{"3":{"1237":1}}],["max",{"2":{"766":1,"768":1,"1457":1,"1529":1},"3":{"214":1,"217":1,"572":1,"626":1,"690":1,"698":1,"766":1,"768":2,"905":1,"1237":4,"1244":1,"1247":4,"1271":5,"1286":2,"1298":1,"1300":1,"1303":2,"1309":2,"1311":1,"1324":7,"1339":2,"1344":1,"1350":11,"1353":2,"1359":32,"1369":8,"1395":3,"1396":3,"1402":1,"1405":1,"1415":5,"1436":3,"1456":1,"1457":1,"1467":2,"1488":1,"1526":1,"1577":1,"1583":1,"1630":1,"1631":4,"1640":1,"1641":2,"1683":1,"1713":1,"1727":2,"1748":3,"1764":14,"1767":22,"1829":2,"1832":1,"1833":1,"1911":1,"1934":1,"1986":1,"2055":1,"2149":2}}],["maxby",{"3":{"109":1,"1222":1,"1229":1}}],["maxnetworkretries",{"2":{"73":2},"3":{"73":2}}],["maxnavigationdepth",{"2":{"333":1,"1242":1},"3":{"0":1,"333":1,"1241":1,"1242":1,"1247":6}}],["maxunboundedresultlimit",{"2":{"330":2,"331":1,"332":1,"333":1,"335":1,"552":1,"740":1,"741":1,"745":1,"1243":1,"1244":1,"1815":1,"1988":2,"1990":1,"1991":1},"3":{"0":1,"330":3,"331":1,"332":1,"333":1,"335":1,"337":1,"552":1,"740":1,"741":1,"745":1,"1229":2,"1243":1,"1244":2,"1247":6,"1278":1,"1286":3,"1310":1,"1311":3,"1577":1,"1815":1,"1988":2,"1990":1,"1991":1}}],["mayor",{"3":{"1685":1,"1686":2}}],["maybenullwhen",{"3":{"1270":1}}],["maybe",{"3":{"1237":1,"1286":1,"1808":1,"1818":1,"1973":1,"2003":1}}],["may",{"0":{"1423":2},"1":{"1992":1,"1993":1,"1994":1,"1995":1,"1996":1,"1997":1},"3":{"0":7,"18":1,"24":1,"27":1,"38":1,"71":1,"76":1,"77":1,"80":1,"81":3,"122":1,"126":1,"185":1,"186":1,"187":1,"188":1,"219":1,"225":1,"235":2,"243":2,"255":1,"266":1,"267":1,"317":3,"318":1,"325":1,"335":1,"342":1,"353":1,"384":1,"390":1,"433":1,"441":1,"530":1,"536":1,"556":2,"563":1,"564":1,"568":1,"620":1,"626":1,"628":1,"658":4,"674":1,"683":1,"727":1,"782":1,"793":1,"819":1,"863":1,"881":1,"891":1,"896":1,"916":1,"924":1,"989":2,"1014":1,"1020":1,"1042":1,"1059":1,"1075":1,"1090":1,"1091":1,"1102":1,"1107":1,"1112":1,"1116":1,"1168":1,"1187":1,"1193":1,"1211":1,"1220":1,"1228":1,"1229":2,"1230":1,"1237":5,"1241":1,"1242":1,"1244":1,"1247":15,"1254":1,"1255":1,"1256":2,"1259":7,"1263":3,"1264":2,"1265":1,"1269":4,"1270":9,"1273":1,"1275":1,"1278":1,"1279":1,"1284":1,"1285":1,"1286":30,"1287":1,"1290":1,"1296":1,"1297":1,"1300":18,"1301":1,"1305":1,"1307":1,"1309":11,"1311":16,"1312":2,"1320":2,"1323":16,"1324":26,"1330":1,"1339":12,"1342":1,"1348":3,"1350":14,"1353":1,"1355":1,"1357":3,"1359":62,"1369":1,"1373":2,"1375":1,"1380":8,"1389":1,"1395":5,"1396":16,"1399":1,"1402":8,"1403":1,"1405":1,"1406":1,"1409":1,"1415":18,"1417":6,"1418":3,"1427":4,"1431":1,"1436":28,"1438":4,"1455":1,"1456":1,"1467":3,"1488":1,"1489":2,"1492":1,"1496":1,"1553":1,"1585":3,"1596":1,"1626":1,"1630":4,"1631":7,"1632":1,"1635":2,"1639":1,"1640":1,"1641":1,"1708":1,"1755":2,"1764":1,"1765":1,"1767":1,"1776":5,"1792":1,"1805":1,"1816":1,"1837":1,"1840":4,"1910":1,"1923":1,"1960":2,"1964":1,"1973":1,"1991":2,"1992":5,"1994":1,"1995":1,"1997":5,"2032":1,"2077":1,"2098":1,"2107":1,"2127":1,"2142":1,"2167":1,"2170":1,"2171":1,"2185":1,"2186":1,"2188":1,"2198":1,"2199":1,"2208":1,"2220":3,"2232":1,"2240":1}}],["mangled",{"3":{"1380":1}}],["mangling",{"3":{"743":1}}],["manipulation",{"3":{"1438":1}}],["manipulations",{"3":{"1350":1}}],["manipulate",{"3":{"1371":1}}],["manifestation",{"3":{"1326":1,"1339":1}}],["manifests",{"2":{"1461":1},"3":{"422":1,"1416":1,"1436":1,"1455":3,"1461":1,"1465":3}}],["manifest",{"0":{"1199":1},"1":{"1196":1,"1197":1,"1198":1,"1199":1},"3":{"418":2,"423":1,"426":1,"1191":2,"1192":1,"1196":1,"1200":1,"1201":1,"1300":1,"1309":2,"1324":4,"1369":3,"1416":7,"1417":7,"1431":1,"1434":1,"1436":6,"1445":1,"1455":2,"1464":2,"1465":1,"1476":1,"1479":1,"1481":1,"1496":2,"1498":1,"1500":1,"1501":1,"1502":2,"1517":1,"1599":1,"2046":1}}],["manufacturing",{"3":{"2136":1}}],["manufacture",{"3":{"1436":2,"2139":1}}],["manufacturer",{"3":{"1300":1,"1630":1,"1764":1}}],["manufactures",{"3":{"1229":1,"1268":1}}],["manufactured",{"3":{"295":1,"1271":1}}],["manualidrangeexhausted",{"3":{"1359":2,"1395":2}}],["manualidrangeend",{"3":{"1350":2,"1359":2,"1395":2}}],["manualidrangestart",{"2":{"1364":1},"3":{"1350":3,"1352":1,"1359":2,"1364":2,"1369":2,"1395":2}}],["manualinstructions",{"2":{"633":1},"3":{"633":1}}],["manualcheckinasync",{"3":{"1396":4}}],["manualcheckin",{"3":{"1203":2,"1207":4,"1271":1,"1396":6}}],["manualcheckinhandlertests",{"3":{"1199":1,"1207":3}}],["manualcheckinhandler",{"3":{"1199":2,"1203":1,"1207":2,"1271":2,"1350":2,"1396":13}}],["manualcheckinrequestvalidatortests",{"3":{"1199":1,"1207":2}}],["manualcheckinrequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1396":6}}],["manualcheckinrequest",{"3":{"1199":10,"1203":1,"1207":2,"1396":10}}],["manualclock",{"3":{"1199":4,"1207":2}}],["manuals",{"3":{"1117":1}}],["manually",{"3":{"435":1,"767":1,"1247":2,"1309":1,"1350":3,"1355":1,"1396":2,"1417":1,"1457":1,"1467":1,"1627":1,"1630":3,"1631":1,"1633":1,"1637":1,"1638":1,"1639":4,"1641":2,"1644":1,"1765":1,"1768":1}}],["manual",{"0":{"1353":1,"1644":1},"1":{"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"1605":1,"1606":1,"1607":1,"1608":1,"1609":1,"1610":1,"1739":1,"1740":1,"1741":1,"1742":1,"1743":1,"1744":1},"3":{"0":2,"2":1,"4":1,"10":1,"13":1,"19":1,"20":1,"41":1,"293":1,"330":1,"331":1,"334":1,"435":1,"444":2,"512":1,"528":2,"530":1,"536":1,"549":1,"577":1,"620":1,"622":1,"626":1,"635":2,"659":1,"684":1,"685":2,"690":2,"757":1,"759":3,"765":1,"791":1,"793":1,"799":1,"889":1,"890":1,"966":1,"990":1,"1110":1,"1118":1,"1168":1,"1192":1,"1202":1,"1203":1,"1212":3,"1213":1,"1238":1,"1243":1,"1247":3,"1259":2,"1265":1,"1270":1,"1273":1,"1286":13,"1300":5,"1309":1,"1310":7,"1311":5,"1323":1,"1324":7,"1344":1,"1347":2,"1349":1,"1350":10,"1352":1,"1359":17,"1369":1,"1380":4,"1395":3,"1396":27,"1402":1,"1415":2,"1417":1,"1427":1,"1432":1,"1436":5,"1438":4,"1454":1,"1455":2,"1456":2,"1457":1,"1467":2,"1470":1,"1473":1,"1474":1,"1475":3,"1476":1,"1480":1,"1481":3,"1483":1,"1487":1,"1490":1,"1492":3,"1496":1,"1523":1,"1524":2,"1525":1,"1526":5,"1530":1,"1531":3,"1532":1,"1533":1,"1546":1,"1554":1,"1558":1,"1564":1,"1565":1,"1570":2,"1571":1,"1577":4,"1582":1,"1589":1,"1591":2,"1595":1,"1596":2,"1598":1,"1603":1,"1604":1,"1605":4,"1610":2,"1611":1,"1627":1,"1630":1,"1631":3,"1632":1,"1636":2,"1639":6,"1641":10,"1642":1,"1645":1,"1651":1,"1653":1,"1662":1,"1674":1,"1684":1,"1701":1,"1710":1,"1728":1,"1729":1,"1739":4,"1744":2,"1746":1,"1749":3,"1754":2,"1765":3,"1766":1,"1767":2,"1771":1,"1779":1,"1865":1,"1867":1,"1988":1,"1991":1,"2034":1,"2036":1,"2147":1,"2163":1,"2232":2}}],["managing",{"3":{"188":1,"1380":1,"1395":2,"1415":1,"1624":1,"1629":1,"1637":1}}],["managepollrows",{"3":{"1402":1}}],["managepolls",{"3":{"1402":4}}],["managepackageversionscentrally",{"2":{"1214":1},"3":{"1214":1}}],["manageable",{"3":{"1286":1,"1638":1}}],["managers",{"3":{"1380":1,"1395":1,"1577":1}}],["manager",{"1":{"807":1,"808":1,"809":1,"810":1,"811":1,"812":1,"813":1,"814":1,"815":1},"3":{"0":1,"807":1,"810":2,"812":1,"815":1,"1212":1,"1300":4,"1307":1,"1309":2,"1311":1,"1324":1,"1380":1,"1395":1,"1396":1,"1416":1,"1436":1,"1539":1,"1547":1,"1577":1,"1768":1,"1935":1,"2232":1}}],["manageroles",{"2":{"184":1,"1059":1,"2200":1,"2201":1},"3":{"0":1,"184":1,"186":1,"1059":2,"1300":14,"1311":2,"1324":5,"1415":4,"2200":1,"2201":1}}],["managed",{"0":{"1477":1},"1":{"437":1,"438":1,"439":1,"440":1,"441":1,"442":1,"443":1,"444":1,"596":1,"597":1,"598":1,"599":1,"600":1,"601":1,"602":1,"603":1,"604":1,"605":1,"2124":1,"2125":1,"2126":1,"2127":1,"2128":1},"2":{"599":1,"1455":1,"1466":1,"1470":1,"1477":2,"1484":1},"3":{"0":7,"234":1,"245":1,"259":1,"341":2,"342":1,"343":1,"437":1,"440":1,"443":2,"591":1,"596":1,"598":1,"599":4,"601":1,"639":1,"640":1,"641":1,"664":1,"665":1,"666":1,"670":1,"681":1,"682":1,"683":1,"684":1,"860":1,"1022":1,"1035":1,"1038":1,"1114":1,"1115":1,"1150":1,"1192":1,"1212":4,"1213":1,"1237":1,"1286":4,"1302":1,"1323":2,"1332":1,"1335":1,"1339":7,"1350":3,"1380":1,"1395":1,"1396":1,"1415":3,"1416":7,"1417":7,"1436":1,"1438":1,"1441":1,"1442":2,"1443":1,"1445":1,"1446":1,"1455":6,"1459":1,"1463":1,"1466":3,"1467":12,"1470":2,"1471":2,"1472":3,"1474":1,"1476":1,"1477":19,"1478":1,"1482":1,"1483":2,"1484":2,"1496":2,"1524":2,"1526":5,"1534":2,"1535":1,"1548":1,"1554":1,"1575":1,"1577":2,"1588":1,"1589":1,"1591":8,"1596":2,"1597":1,"1600":1,"1629":3,"1630":2,"1631":2,"1632":1,"1635":1,"1636":1,"1637":1,"1638":4,"1668":1,"1670":2,"1748":1,"1765":1,"1779":2,"1861":2,"1868":1,"1872":1,"1876":1,"1877":4,"1904":1,"2047":1,"2123":1,"2124":1,"2126":1,"2128":2,"2133":1,"2142":1,"2146":1,"2210":2,"2213":1,"2219":1,"2221":1,"2232":2}}],["manages",{"3":{"0":1,"1074":1,"1286":1,"1307":1,"1309":1,"1324":1,"1373":1,"1396":1,"1402":2,"1472":1,"1637":1,"1750":1,"1765":1,"1773":1,"1871":1,"1936":1}}],["managementrouteauthorizationtests",{"2":{"1535":1},"3":{"1199":1,"1207":2,"1436":1,"1526":2,"1535":2}}],["management",{"0":{"1556":1,"1569":1},"1":{"596":1,"597":1,"598":1,"599":1,"600":1,"601":1,"602":1,"603":1,"604":1,"605":1},"3":{"0":2,"32":1,"39":1,"186":1,"225":1,"301":1,"324":1,"359":1,"361":1,"369":1,"379":1,"381":1,"596":1,"640":2,"641":1,"881":1,"938":1,"954":1,"1026":1,"1192":1,"1202":1,"1203":1,"1212":1,"1213":1,"1214":1,"1216":2,"1259":2,"1300":11,"1308":1,"1309":1,"1311":7,"1323":9,"1324":34,"1326":1,"1327":2,"1339":5,"1350":5,"1359":2,"1380":6,"1383":1,"1389":1,"1390":1,"1395":50,"1396":14,"1402":8,"1414":1,"1415":6,"1417":10,"1429":1,"1431":1,"1436":12,"1437":1,"1438":9,"1440":1,"1441":2,"1444":3,"1446":1,"1453":1,"1455":1,"1467":2,"1487":2,"1496":1,"1499":1,"1525":2,"1526":2,"1534":1,"1535":2,"1552":1,"1569":1,"1577":2,"1586":1,"1588":1,"1591":1,"1597":1,"1600":1,"1611":2,"1621":1,"1625":2,"1626":1,"1627":7,"1629":6,"1630":1,"1632":5,"1636":1,"1637":2,"1638":4,"1639":2,"1641":2,"1644":1,"1649":1,"1666":1,"1667":2,"1671":1,"1685":1,"1686":1,"1747":1,"1748":4,"1749":1,"1750":2,"1763":3,"1767":1,"1771":3,"1773":1,"1796":2,"1802":1,"1942":1,"1963":1,"1995":1,"2005":2,"2055":1,"2063":1,"2135":1,"2145":1,"2220":1,"2232":2}}],["manageusers",{"2":{"184":1,"1059":1,"2201":1},"3":{"0":1,"184":1,"186":1,"1059":1,"1300":6,"1311":1,"1415":3,"2201":1}}],["manage",{"2":{"186":1,"638":1,"642":1,"1304":1},"3":{"0":6,"152":1,"185":1,"186":6,"187":1,"209":1,"378":1,"384":2,"638":1,"640":3,"642":2,"690":1,"694":1,"1026":1,"1078":1,"1116":3,"1120":1,"1297":4,"1300":6,"1303":1,"1304":3,"1309":13,"1324":5,"1347":1,"1348":7,"1350":11,"1357":2,"1359":1,"1373":1,"1377":1,"1380":5,"1389":1,"1392":1,"1395":1,"1396":16,"1402":29,"1409":3,"1414":2,"1415":5,"1417":1,"1438":1,"1455":1,"1467":5,"1496":1,"1550":1,"1589":1,"1591":1,"1596":1,"1597":1,"1621":1,"1626":2,"1627":5,"1629":1,"1631":5,"1632":5,"1638":1,"1640":2,"1651":1,"1653":2,"1687":1,"1748":3,"1749":3,"1760":1,"1763":2,"1767":1,"1768":8,"1771":1,"1960":1,"1961":1,"1962":1,"1963":4,"1965":1,"1971":1,"2072":1}}],["many",{"3":{"71":1,"135":1,"175":2,"186":1,"227":1,"258":1,"282":1,"310":1,"317":1,"319":1,"329":1,"333":1,"361":1,"472":1,"598":1,"600":1,"697":1,"740":1,"760":1,"819":1,"827":1,"829":1,"853":1,"881":2,"946":1,"1110":1,"1124":1,"1126":1,"1150":1,"1153":1,"1200":1,"1229":1,"1237":2,"1247":5,"1259":2,"1270":1,"1271":1,"1276":1,"1279":1,"1286":9,"1300":10,"1301":4,"1304":1,"1305":1,"1309":4,"1310":12,"1311":5,"1312":2,"1323":5,"1324":13,"1339":5,"1350":29,"1354":1,"1359":17,"1380":3,"1387":1,"1395":14,"1396":25,"1402":5,"1406":1,"1410":1,"1415":4,"1417":3,"1427":1,"1428":1,"1431":2,"1436":7,"1438":2,"1456":1,"1459":1,"1465":1,"1473":1,"1476":1,"1496":2,"1499":1,"1500":1,"1541":1,"1551":1,"1555":1,"1565":1,"1571":1,"1577":3,"1630":23,"1638":3,"1640":2,"1641":1,"1749":1,"1780":1,"1790":1,"1808":1,"1815":1,"1847":1,"1853":1,"1894":1,"1901":1,"1902":1,"1982":1,"1984":1,"1994":1,"1997":1,"2000":1,"2001":2,"2024":1,"2032":1,"2037":1,"2051":1,"2135":1,"2145":1,"2167":1,"2226":1}}],["mandating",{"3":{"1436":1}}],["mandated",{"3":{"1474":1}}],["mandates",{"3":{"120":1,"988":1,"1237":1,"1323":1,"1380":2,"1840":1}}],["mandate",{"3":{"0":1,"68":1}}],["mandatory",{"3":{"0":1,"674":1,"707":1,"1058":1,"1060":2,"1270":3,"1286":1,"1293":1,"1300":2,"1311":3,"1323":1,"1324":1,"1350":1,"1359":9,"1380":1,"1395":1,"1396":4,"1402":3,"1415":1,"1436":5,"1455":1,"1461":1,"1491":1,"1526":1,"1600":1,"1635":1,"1654":1,"1748":1,"1749":4,"1765":5,"1802":1,"1876":1,"2195":1,"2203":1}}],["major≥9",{"3":{"1576":1,"1577":1}}],["majorversion3",{"2":{"1489":1,"1491":1},"3":{"1436":2,"1489":1,"1491":1}}],["majorversion8",{"2":{"1074":1,"1489":1},"3":{"1074":1,"1436":2,"1489":1}}],["majority",{"3":{"1229":1,"1286":1,"1380":1,"1415":1,"1443":1,"1896":1,"1898":1}}],["majors",{"3":{"145":1,"413":2,"1431":1}}],["major",{"2":{"1733":1},"3":{"0":4,"147":6,"149":1,"150":2,"152":2,"1036":1,"1074":1,"1075":2,"1212":1,"1213":1,"1323":1,"1431":2,"1436":3,"1454":1,"1489":1,"1526":1,"1537":1,"1552":1,"1569":1,"1576":1,"1577":2,"1586":1,"1733":2,"1735":2,"1763":1,"2042":1,"2220":1}}],["maui=",{"3":{"1416":1}}],["mauiuiapplicationdelegate",{"3":{"1416":1}}],["mauiuimodule",{"2":{"651":1},"3":{"649":2,"651":2,"1324":2}}],["mauiwinuiapplication",{"3":{"1416":5}}],["mauimediapickerservice",{"3":{"1199":1,"1203":1,"1207":2,"1417":12}}],["mauimapnavigationservice",{"3":{"1199":1,"1203":1,"1207":2,"1417":9}}],["mauilocalnotificationservice",{"3":{"1199":1,"1203":1,"1207":2,"1417":17}}],["mauilocalcachestore",{"3":{"1199":1,"1203":1,"1207":2,"1417":14}}],["mauigeolocationservice",{"2":{"2119":1},"3":{"1199":1,"1203":1,"1207":2,"1417":11,"2119":1}}],["mauigeocodingservice",{"3":{"1199":1,"1203":1,"1207":2,"1417":10}}],["mauitexttospeechservice",{"3":{"1199":1,"1203":1,"1207":2,"1417":13}}],["mauitokenstorageservice",{"2":{"507":1,"510":1,"513":1},"3":{"0":1,"507":5,"510":1,"513":8,"1199":1,"1203":1,"1207":2,"1324":3,"1416":4,"1417":10,"1496":1}}],["mauispeechtotextservice",{"3":{"1199":1,"1203":1,"1207":2,"1417":10}}],["mauishareservice",{"2":{"2119":1},"3":{"1199":1,"1203":1,"1207":2,"1417":8,"2119":2}}],["mauiscreenshotservice",{"3":{"1199":1,"1203":1,"1207":2,"1417":7}}],["mauisecuretokenstore",{"2":{"510":1,"511":1,"513":1},"3":{"507":5,"509":1,"510":1,"511":7,"512":1,"513":2,"1199":1,"1203":1,"1207":2,"1324":1,"1417":11}}],["mauihapticfeedbackservice",{"2":{"2119":1},"3":{"1199":1,"1203":1,"1207":2,"1417":9,"2119":1}}],["mauiformfactor",{"3":{"1199":1,"1203":1,"1207":2,"1417":17}}],["mauifirebasemessagingservice",{"2":{"434":1,"436":1},"3":{"434":2,"436":2}}],["mauidevicepreferences",{"3":{"1199":1,"1203":1,"1207":2,"1417":16}}],["mauicommunitytoolkit",{"3":{"1417":1}}],["mauicontext",{"3":{"1416":3}}],["mauiconnectivitystatusservice",{"3":{"1199":1,"1203":1,"1207":2,"1417":11}}],["mauiclipboardservice",{"2":{"2119":1},"3":{"1199":1,"1203":1,"1207":2,"1417":7,"2119":1}}],["mauiculturestore",{"3":{"265":1,"268":1,"415":1,"1199":3,"1203":1,"1207":2,"1311":2,"1417":14}}],["mauicultureinitializer",{"3":{"265":1,"1199":2,"1203":1,"1207":2,"1324":1,"1416":1,"1417":12}}],["mauicultureapplier",{"3":{"265":1,"415":1,"1199":2,"1203":1,"1207":2,"1311":2,"1324":3,"1417":13}}],["mauibiometricauthenticator",{"3":{"1199":1,"1203":1,"1207":2,"1417":9}}],["mauibatterystatusservice",{"3":{"1199":1,"1203":1,"1207":2,"1417":11}}],["mauibarcodescannerservice",{"2":{"680":1,"682":1},"3":{"680":1,"682":5,"684":1,"1199":2,"1203":1,"1207":2,"1417":30}}],["mauibacknavigationbridgetests",{"3":{"1199":1,"1207":2,"1438":1}}],["mauibacknavigationbridge",{"2":{"1417":1},"3":{"413":1,"1199":3,"1203":1,"1207":3,"1324":14,"1416":1,"1417":4}}],["mauiapp",{"3":{"1416":3}}],["mauiapplication",{"3":{"1416":2}}],["mauiappcompatactivity",{"3":{"1416":1}}],["mauiappbuilder",{"3":{"265":1,"682":1,"980":1,"1208":1,"1417":9}}],["mauiaccessibilityannouncer",{"3":{"1199":1,"1203":1,"1207":2,"1417":9}}],["mauipubliclinkbuilder",{"3":{"1199":1,"1203":1,"1207":2,"1324":4,"1417":8,"1496":1}}],["mauipushregistrationservice",{"2":{"434":1,"436":1},"3":{"434":2,"436":2,"1199":1,"1203":1,"1207":2,"1417":21,"1496":1}}],["mauiprogram",{"2":{"683":1},"3":{"412":1,"413":1,"507":2,"511":4,"513":2,"649":2,"683":1,"1199":4,"1203":1,"1207":2,"1212":2,"1324":11,"1395":2,"1415":1,"1416":77,"1417":20,"1496":2}}],["mauiexternallinkservice",{"3":{"1199":1,"1203":1,"1207":2,"1417":8}}],["mauiexternalauthbroker",{"2":{"415":1,"418":1,"1976":1,"2122":1},"3":{"0":1,"415":2,"418":2,"420":1,"423":1,"428":1,"1199":1,"1203":1,"1207":2,"1324":1,"1417":13,"1496":1,"1976":1,"2122":1}}],["mauierrorhandlinginitializer",{"2":{"413":1},"3":{"413":1,"1199":2,"1203":1,"1207":2,"1417":29}}],["maui",{"0":{"1459":1},"1":{"410":1,"411":1,"412":1,"413":1,"414":1,"415":1,"416":1,"1417":1},"2":{"130":1,"138":1,"141":1,"142":1,"436":1,"481":1,"482":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"495":1,"509":1,"511":1,"528":1,"682":1,"686":1,"939":1,"942":1,"982":1,"984":1,"1416":2,"1417":4,"1454":1,"1503":1,"1504":1,"1585":1,"1599":1,"1660":1,"1675":1,"1732":1,"1781":1,"2100":1,"2120":2,"2123":1},"3":{"0":19,"59":1,"60":1,"130":2,"135":4,"138":1,"141":1,"142":1,"243":1,"265":4,"370":1,"373":2,"389":1,"410":1,"412":5,"413":15,"414":2,"415":6,"418":3,"419":2,"420":2,"422":1,"423":2,"426":1,"428":2,"429":1,"430":1,"432":1,"434":2,"436":2,"439":1,"481":2,"482":3,"483":1,"485":1,"486":4,"487":1,"488":1,"489":1,"490":3,"491":1,"492":1,"493":2,"494":1,"495":2,"505":1,"506":3,"507":11,"508":2,"509":10,"510":2,"511":12,"512":1,"513":6,"514":2,"528":4,"530":1,"531":2,"532":1,"555":1,"560":1,"649":1,"650":1,"653":1,"681":1,"682":5,"683":2,"684":2,"685":1,"686":1,"773":1,"854":1,"880":1,"939":2,"942":2,"980":8,"982":4,"983":2,"984":2,"1004":1,"1184":1,"1185":1,"1187":2,"1192":3,"1199":35,"1202":2,"1203":37,"1206":1,"1207":105,"1208":6,"1211":2,"1212":12,"1289":1,"1294":1,"1298":1,"1300":1,"1311":5,"1323":2,"1324":67,"1328":1,"1339":1,"1359":1,"1381":1,"1384":1,"1388":1,"1391":1,"1393":1,"1395":13,"1396":1,"1402":1,"1403":1,"1405":1,"1414":1,"1415":15,"1416":109,"1417":426,"1427":1,"1436":6,"1438":2,"1441":1,"1447":1,"1449":1,"1452":1,"1453":1,"1454":13,"1455":2,"1456":2,"1459":23,"1460":2,"1461":4,"1486":4,"1488":1,"1489":3,"1496":15,"1503":2,"1504":2,"1513":2,"1524":4,"1526":9,"1530":1,"1531":8,"1532":2,"1577":1,"1582":1,"1583":1,"1585":3,"1591":11,"1595":1,"1596":5,"1597":2,"1599":4,"1600":2,"1626":2,"1627":2,"1629":1,"1630":7,"1639":1,"1641":25,"1660":2,"1669":6,"1673":1,"1675":2,"1732":2,"1773":1,"1781":3,"1976":2,"2077":1,"2078":2,"2080":1,"2100":2,"2111":1,"2115":1,"2116":4,"2118":1,"2119":4,"2120":7,"2122":6,"2123":4,"2219":1,"2221":1,"2225":1,"2227":2,"2228":1,"2230":1,"2232":4}}],["made",{"3":{"0":7,"24":2,"26":1,"27":1,"110":1,"125":1,"135":1,"136":2,"149":1,"151":1,"157":1,"168":1,"170":1,"173":1,"201":1,"243":1,"255":1,"265":1,"303":1,"365":1,"372":1,"448":1,"454":1,"526":1,"537":1,"539":1,"550":1,"603":1,"609":1,"626":1,"706":1,"716":2,"724":1,"726":1,"734":1,"749":1,"799":1,"812":1,"819":1,"826":2,"827":3,"829":1,"832":1,"834":1,"837":2,"840":1,"860":1,"868":1,"896":1,"906":1,"923":1,"938":1,"954":1,"964":2,"965":2,"971":1,"1033":1,"1036":1,"1043":1,"1045":1,"1051":2,"1055":1,"1061":2,"1074":1,"1082":1,"1090":1,"1092":1,"1101":1,"1212":1,"1229":2,"1237":1,"1239":1,"1241":1,"1247":1,"1252":1,"1253":1,"1255":1,"1259":8,"1270":3,"1271":3,"1273":1,"1275":1,"1286":8,"1300":12,"1301":4,"1309":3,"1310":1,"1311":10,"1312":2,"1313":1,"1318":1,"1320":1,"1323":8,"1324":19,"1331":1,"1332":1,"1339":5,"1350":1,"1352":1,"1353":2,"1359":18,"1369":2,"1380":2,"1388":1,"1395":11,"1396":7,"1402":3,"1415":6,"1417":1,"1422":1,"1426":1,"1427":2,"1428":1,"1432":1,"1436":14,"1443":1,"1447":1,"1455":6,"1457":1,"1461":1,"1467":2,"1473":1,"1478":1,"1489":2,"1496":3,"1631":1,"1641":2,"1686":1,"1700":1,"1727":2,"1735":1,"1822":1,"1823":1,"1825":1,"1835":1,"1854":1,"1863":1,"1908":1,"1927":1,"1941":1,"1963":1,"2029":1,"2045":1,"2048":1,"2158":1,"2174":1,"2176":1,"2179":1,"2198":1,"2232":1}}],["mapget",{"3":{"1324":1}}],["mapgrpcservice",{"2":{"103":1,"179":1},"3":{"103":1,"179":1,"1311":2,"1312":1,"1380":4,"1415":1,"2000":1}}],["maphub",{"3":{"1311":1}}],["maphealthchecks",{"3":{"235":1,"1332":1,"1339":4}}],["mapformat",{"3":{"1311":5}}],["mapforwarder",{"2":{"57":1,"59":1,"836":1,"837":1,"840":1,"1509":1},"3":{"0":2,"57":1,"59":1,"836":1,"837":2,"838":1,"840":1,"1212":1,"1436":1,"1447":1,"1509":1,"2024":1}}],["maptype",{"3":{"1311":5}}],["maptoapiversion",{"3":{"448":2,"1311":4,"1380":1,"2131":2}}],["maptodtogenerated",{"3":{"1350":1,"1359":3}}],["maptodto",{"2":{"5":1,"6":1,"1953":1,"1956":1},"3":{"5":4,"6":1,"1270":1,"1309":3,"1311":3,"1350":3,"1359":51,"1396":5,"1402":5,"1415":3,"1953":2,"1956":1}}],["maptodtos",{"2":{"5":1,"7":1,"331":1,"1953":1},"3":{"5":2,"7":1,"331":1,"1244":1,"1309":2,"1311":2,"1359":20,"1396":3,"1402":1,"1415":1,"1953":1}}],["mapopenapi",{"2":{"2132":1},"3":{"454":1,"1311":2,"1591":1,"2132":1}}],["mapoidcdiscoveryendpoint",{"2":{"31":1,"751":1,"1667":1},"3":{"31":1,"751":1,"1311":1,"1667":1}}],["mapcontrollers",{"3":{"1311":1}}],["mapcommonscalarui",{"2":{"1929":1},"3":{"1311":2,"1496":1,"1577":2,"1929":1}}],["mapcommonsameoriginapiproxy",{"2":{"1184":1},"3":{"0":1,"1184":1,"1212":1,"1324":5}}],["mapcommonopenapiauthorizationtests",{"3":{"1199":1,"1207":2,"1311":2,"1438":1}}],["mapcommonopenapi",{"2":{"446":1,"453":1,"454":2,"1929":1,"2132":1},"3":{"446":1,"448":1,"450":1,"453":1,"454":3,"1311":4,"1438":1,"1577":3,"1929":2,"2132":1}}],["mapchild",{"3":{"1265":1,"1270":3,"1359":9}}],["mapclientconfigendpoint",{"2":{"674":1},"3":{"674":1,"1324":8,"1526":1}}],["mapcultureendpoint",{"2":{"415":1},"3":{"265":1,"415":1,"1311":4,"1324":1,"1591":1,"1653":1,"1666":1}}],["mapcachedhealthchecks",{"2":{"233":1,"235":1,"238":1,"1332":1,"2007":1},"3":{"233":1,"235":1,"238":1,"1332":1,"1339":7,"2007":1}}],["mapnotificationhub",{"3":{"225":2,"1307":1,"1309":1,"1311":2,"1666":1}}],["mapasync",{"3":{"109":1,"1220":1,"1229":2,"1805":1}}],["mapappassociationendpoints",{"2":{"418":1,"423":1,"424":1,"425":1,"426":1,"428":1,"1481":1,"1666":1},"3":{"0":1,"418":1,"423":1,"424":1,"425":1,"426":1,"428":1,"429":1,"1311":1,"1481":1,"1666":1}}],["mapjwksendpoint",{"3":{"31":1,"827":1,"1311":1,"1667":1}}],["mapdefaultendpoints",{"0":{"2007":1},"2":{"235":1,"464":1,"913":1,"1331":1,"1332":1,"1677":1,"1720":1,"1727":1,"2007":1,"2014":1},"3":{"0":1,"235":1,"464":1,"913":1,"1212":1,"1331":1,"1332":1,"1338":1,"1339":10,"1442":1,"1443":2,"1447":1,"1653":1,"1670":1,"1677":1,"1720":1,"1727":1,"2007":1,"2014":1}}],["mapregistry",{"3":{"1199":2,"1207":1}}],["mapreverseproxy",{"2":{"59":1,"836":1,"838":1,"841":1,"1509":1},"3":{"0":1,"59":1,"827":1,"836":1,"838":1,"841":1,"1338":1,"1339":1,"1436":1,"1447":1,"1509":1}}],["maprazorcomponents",{"2":{"647":1,"1434":1},"3":{"0":1,"556":1,"647":1,"649":1,"1311":1,"1324":1,"1416":1,"1434":1,"1436":2}}],["mapssearchurl",{"3":{"1417":1}}],["mapspeakersessions",{"3":{"1359":1}}],["mapscalarapireference",{"3":{"1311":1}}],["mapsearchurl",{"3":{"1395":2}}],["mapsessionizequestiontype",{"3":{"1359":2}}],["mapsessioncookieendpoints",{"2":{"1667":1},"3":{"1300":2,"1667":1}}],["mapsentity",{"3":{"1286":1}}],["mapstaticassets",{"3":{"1436":1}}],["mapstatustostring",{"3":{"1270":1,"1309":3}}],["mapstoaprovisionedalert",{"2":{"609":1},"3":{"609":2,"1436":2}}],["mapster",{"3":{"3":1,"4":1}}],["maps",{"0":{"1831":1},"1":{"1502":1,"1503":1,"1504":1,"1505":1,"1506":1,"1507":1,"1508":1,"1509":1,"1510":1,"1511":1,"1512":1,"1513":1,"1514":1,"1515":1,"1516":1,"1517":1},"3":{"0":7,"5":1,"53":1,"60":1,"78":1,"95":1,"107":1,"109":7,"170":2,"186":3,"225":1,"235":2,"265":1,"282":1,"318":2,"324":1,"330":1,"341":1,"365":1,"405":1,"449":1,"454":2,"548":1,"585":1,"626":1,"655":1,"657":5,"660":2,"674":1,"707":1,"710":1,"713":1,"733":1,"751":1,"827":1,"838":1,"845":1,"854":1,"869":1,"905":2,"921":1,"1020":1,"1033":1,"1034":1,"1050":3,"1059":1,"1061":1,"1161":1,"1180":1,"1184":1,"1191":1,"1194":1,"1212":1,"1217":1,"1219":1,"1229":3,"1237":5,"1245":1,"1247":6,"1263":2,"1265":2,"1269":1,"1270":5,"1271":3,"1273":2,"1277":1,"1279":1,"1280":1,"1281":1,"1283":3,"1284":1,"1286":42,"1296":1,"1297":2,"1300":13,"1306":1,"1307":2,"1309":9,"1311":24,"1312":7,"1316":1,"1323":6,"1324":25,"1331":1,"1332":1,"1335":1,"1339":13,"1350":9,"1352":1,"1354":4,"1359":36,"1362":2,"1369":4,"1372":1,"1374":2,"1380":13,"1390":1,"1391":5,"1395":24,"1396":29,"1400":1,"1402":8,"1405":1,"1406":1,"1408":1,"1412":1,"1413":1,"1415":13,"1416":2,"1417":26,"1427":1,"1435":1,"1436":35,"1438":2,"1441":1,"1442":1,"1443":1,"1444":1,"1446":1,"1466":1,"1467":1,"1489":1,"1496":14,"1502":1,"1509":1,"1582":1,"1589":1,"1591":2,"1596":1,"1600":1,"1620":1,"1631":1,"1632":1,"1636":2,"1651":1,"1658":1,"1662":1,"1663":2,"1665":1,"1666":1,"1667":1,"1670":1,"1677":1,"1681":1,"1685":1,"1700":1,"1702":3,"1729":1,"1749":1,"1753":1,"1756":1,"1770":1,"1790":2,"1805":1,"1807":1,"1821":1,"1834":1,"1849":1,"1861":1,"1871":1,"1889":2,"1930":1,"1933":1,"1946":2,"1951":1,"1952":1,"1953":2,"1954":2,"1959":2,"1961":2,"1962":1,"1963":2,"1965":1,"1988":1,"1995":1,"2042":1,"2072":1,"2087":1,"2115":1,"2150":2,"2167":1,"2199":1,"2203":1,"2232":1,"2234":1}}],["map",{"0":{"1409":1,"1517":1,"1654":1,"1660":1},"2":{"80":1,"82":1,"973":1,"1224":1,"1951":1},"3":{"0":7,"26":1,"41":1,"54":1,"57":2,"59":1,"80":3,"81":1,"82":2,"109":1,"111":1,"135":4,"166":2,"184":3,"186":3,"305":1,"333":2,"341":1,"350":1,"359":1,"380":3,"413":1,"546":1,"549":1,"583":1,"585":1,"599":1,"631":2,"632":1,"633":1,"635":1,"659":1,"698":2,"749":1,"751":1,"826":1,"827":1,"833":1,"836":3,"838":1,"840":2,"842":3,"846":2,"905":1,"926":1,"964":1,"972":3,"973":1,"1004":2,"1011":2,"1054":1,"1058":1,"1059":1,"1133":2,"1161":6,"1162":1,"1168":1,"1188":1,"1190":1,"1196":1,"1212":2,"1220":1,"1224":1,"1225":1,"1229":5,"1236":1,"1237":3,"1239":1,"1241":3,"1242":7,"1243":1,"1247":17,"1265":3,"1269":1,"1270":11,"1279":1,"1282":1,"1286":15,"1291":1,"1297":1,"1300":13,"1301":1,"1304":3,"1309":4,"1311":10,"1312":4,"1320":1,"1321":1,"1323":13,"1324":8,"1339":3,"1341":1,"1348":4,"1350":4,"1351":1,"1352":1,"1354":1,"1357":1,"1359":50,"1362":1,"1364":1,"1369":22,"1377":1,"1378":1,"1380":8,"1385":3,"1387":2,"1391":1,"1395":25,"1396":42,"1400":2,"1402":2,"1404":1,"1406":1,"1409":7,"1414":1,"1415":12,"1416":1,"1417":7,"1428":2,"1431":5,"1436":516,"1438":4,"1445":1,"1455":5,"1467":2,"1469":1,"1487":1,"1489":11,"1492":1,"1493":1,"1496":6,"1509":1,"1515":2,"1525":1,"1526":6,"1531":1,"1535":1,"1571":1,"1572":1,"1577":4,"1582":1,"1585":1,"1586":1,"1600":1,"1621":1,"1626":3,"1627":1,"1629":1,"1630":1,"1636":1,"1649":1,"1650":1,"1654":5,"1660":1,"1661":2,"1662":2,"1669":1,"1671":1,"1673":2,"1675":1,"1700":1,"1701":2,"1702":2,"1728":3,"1729":1,"1730":1,"1735":2,"1760":1,"1765":1,"1782":1,"1790":1,"1805":1,"1807":1,"1815":2,"1817":1,"1834":1,"1840":1,"1849":1,"1876":1,"1883":1,"1891":1,"1894":1,"1929":2,"1930":1,"1935":1,"1944":1,"1946":1,"1951":2,"1952":3,"1953":1,"1956":4,"1958":2,"1959":2,"1962":2,"1964":1,"1965":1,"1990":1,"1991":2,"2018":1,"2023":1,"2042":2,"2043":1,"2049":1,"2097":3,"2098":1,"2101":1,"2103":1,"2104":4,"2111":1,"2120":1,"2129":1,"2142":1,"2178":1,"2197":2,"2199":1,"2204":2,"2236":1}}],["mapproperty",{"2":{"1957":1},"3":{"1309":2,"1359":4,"1957":1}}],["mappedadminport",{"3":{"1436":1}}],["mappedentityqueryservice",{"3":{"1199":2,"1207":1}}],["mappedorder",{"3":{"1199":4,"1207":1}}],["mappedorderid",{"3":{"1199":5,"1207":1}}],["mappeddto",{"3":{"1199":2,"1207":1}}],["mapped",{"3":{"0":5,"4":1,"15":1,"31":1,"59":1,"96":1,"110":1,"111":1,"225":1,"303":1,"335":2,"341":1,"343":1,"345":1,"415":2,"448":3,"453":1,"454":1,"545":2,"549":1,"583":1,"585":2,"640":1,"657":4,"682":1,"698":1,"702":1,"703":1,"749":1,"751":1,"929":1,"932":1,"935":2,"1011":1,"1042":1,"1046":1,"1089":1,"1094":1,"1132":1,"1133":2,"1140":1,"1150":1,"1212":1,"1218":1,"1234":1,"1237":1,"1247":4,"1253":1,"1269":1,"1270":4,"1271":2,"1273":3,"1277":1,"1278":1,"1283":1,"1286":18,"1288":2,"1297":1,"1300":12,"1304":1,"1307":2,"1309":10,"1311":12,"1312":2,"1316":1,"1318":1,"1320":1,"1323":9,"1324":5,"1332":1,"1339":7,"1350":19,"1359":18,"1362":1,"1363":1,"1369":4,"1380":3,"1395":3,"1396":16,"1402":5,"1405":1,"1415":11,"1417":2,"1436":12,"1447":1,"1455":2,"1467":1,"1477":2,"1486":1,"1489":3,"1496":5,"1577":2,"1600":1,"1632":1,"1685":1,"1720":1,"1804":1,"1815":1,"1842":1,"1889":1,"2000":1,"2025":1,"2131":2,"2138":1}}],["mapperignoretarget",{"3":{"1359":2}}],["mapper",{"0":{"1954":1,"1956":1},"2":{"1957":1,"1958":1},"3":{"3":1,"5":2,"6":2,"7":2,"333":1,"423":1,"424":1,"549":2,"657":1,"798":1,"806":1,"1026":1,"1050":1,"1201":1,"1212":1,"1213":1,"1220":1,"1229":1,"1241":1,"1243":1,"1244":2,"1247":3,"1260":1,"1265":2,"1269":2,"1270":14,"1283":1,"1300":5,"1309":18,"1311":13,"1324":3,"1350":11,"1352":1,"1353":4,"1354":2,"1359":163,"1369":1,"1375":1,"1380":3,"1396":13,"1401":1,"1402":8,"1415":8,"1436":2,"1438":2,"1481":1,"1487":1,"1526":1,"1532":1,"1546":1,"1651":1,"1653":1,"1727":1,"1805":1,"1810":1,"1831":1,"1849":1,"1951":4,"1952":1,"1953":3,"1954":6,"1955":7,"1956":2,"1957":3,"1958":7,"1959":4,"2230":1}}],["mappers",{"3":{"0":1,"3":4,"5":2,"6":1,"7":1,"117":1,"473":1,"548":1,"1262":2,"1269":2,"1270":4,"1311":4,"1316":2,"1323":2,"1350":1,"1351":1,"1353":2,"1359":23,"1396":6,"1404":1,"1415":3,"1437":2,"1438":3,"1496":1,"1526":1,"1650":1,"1651":1,"1661":1,"1662":3,"1686":1,"1701":1,"1775":1,"1857":1,"1883":3,"1951":1,"1953":1,"1956":3,"1957":2,"2052":1,"2095":1,"2232":1}}],["mapperly",{"0":{"1955":1},"3":{"0":3,"3":1,"5":2,"548":1,"549":1,"1050":4,"1051":1,"1212":1,"1213":2,"1270":3,"1300":2,"1306":1,"1309":11,"1347":1,"1350":7,"1353":1,"1357":1,"1359":48,"1396":11,"1401":1,"1402":6,"1404":1,"1415":7,"1438":1,"1650":1,"1651":1,"1662":1,"1810":1,"1955":4,"1956":1,"1957":1,"1958":1,"1959":2}}],["mappings",{"3":{"104":1,"658":1,"1309":1,"1362":1,"1395":1,"1774":1}}],["mapping",{"0":{"1281":1,"1353":1,"1927":1,"1952":1},"1":{"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"1311":1,"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1956":1,"1957":1,"1958":1,"1959":1},"2":{"1247":1,"1270":1},"3":{"0":7,"2":1,"3":1,"4":2,"6":5,"7":1,"26":1,"53":1,"92":1,"104":1,"109":1,"111":1,"151":1,"166":1,"185":1,"186":1,"188":1,"238":1,"265":1,"331":1,"334":1,"360":1,"369":1,"370":2,"371":1,"372":1,"420":1,"454":1,"470":1,"545":1,"549":2,"559":1,"572":1,"583":1,"657":1,"658":2,"659":1,"690":1,"707":1,"734":1,"741":1,"742":1,"749":1,"782":1,"790":1,"791":2,"799":2,"806":1,"833":1,"838":1,"846":1,"872":1,"874":1,"905":2,"906":2,"910":1,"922":1,"925":1,"964":1,"1011":1,"1018":1,"1026":1,"1034":3,"1042":1,"1049":2,"1050":2,"1051":4,"1052":1,"1069":1,"1175":3,"1192":3,"1202":4,"1203":10,"1207":26,"1212":1,"1213":3,"1224":1,"1225":2,"1229":3,"1237":2,"1238":1,"1244":2,"1245":1,"1247":10,"1265":1,"1269":2,"1270":12,"1271":4,"1273":2,"1280":1,"1281":1,"1282":4,"1283":2,"1286":56,"1290":1,"1300":22,"1304":1,"1307":1,"1309":10,"1310":1,"1311":45,"1312":6,"1317":1,"1323":3,"1324":12,"1332":2,"1339":3,"1347":1,"1350":4,"1353":3,"1359":64,"1360":1,"1365":1,"1366":1,"1369":17,"1370":1,"1375":1,"1378":1,"1380":19,"1395":15,"1396":44,"1401":1,"1402":11,"1404":1,"1409":1,"1411":1,"1414":1,"1415":16,"1416":3,"1417":3,"1436":14,"1437":2,"1438":3,"1441":1,"1443":1,"1450":1,"1453":1,"1455":1,"1461":1,"1487":1,"1488":2,"1489":1,"1496":3,"1498":1,"1526":1,"1546":1,"1571":1,"1577":1,"1585":1,"1600":1,"1651":1,"1662":2,"1666":1,"1673":1,"1686":1,"1779":1,"1805":2,"1807":1,"1810":3,"1825":1,"1831":1,"1854":1,"1856":3,"1862":1,"1923":1,"1924":2,"1925":1,"1927":1,"1929":1,"1930":2,"1935":1,"1950":1,"1951":4,"1952":3,"1953":2,"1954":2,"1955":4,"1956":2,"1957":1,"1958":2,"1959":9,"1960":1,"1962":2,"1964":1,"1965":1,"1988":1,"1991":1,"2018":1,"2025":1,"2055":1,"2060":1,"2146":1,"2166":1,"2167":1,"2192":1,"2208":2,"2232":1}}],["md5",{"3":{"1905":1}}],["md",{"0":{"1474":1,"1476":1,"1477":1,"1478":1,"1481":1},"2":{"137":1,"138":1,"140":1,"146":1,"147":1,"164":1,"375":1,"607":1,"609":1,"636":2,"971":1,"972":1,"974":1,"975":1,"1003":2,"1004":1,"1058":1,"1190":1,"1451":1,"1457":1,"1458":1,"1465":1,"1467":1,"1476":1,"1483":1,"1515":1,"1522":2,"1529":1,"1571":1,"1573":2,"1574":1,"1575":2,"1577":1,"1587":2,"1589":1,"1594":1,"1595":1,"1605":1,"1658":2,"1673":2,"1674":5,"1675":1,"1697":1,"1756":1,"2045":1,"2049":2,"2060":1,"2112":1,"2195":1},"3":{"0":12,"1":5,"27":1,"68":1,"130":4,"135":6,"136":3,"137":8,"138":4,"139":4,"140":5,"141":2,"142":4,"146":1,"147":5,"152":1,"164":1,"313":1,"317":1,"321":1,"324":1,"326":2,"358":2,"361":1,"362":2,"365":1,"366":2,"369":4,"371":1,"375":2,"376":3,"398":1,"406":1,"422":1,"423":1,"424":1,"425":1,"426":2,"435":1,"443":1,"469":1,"483":1,"490":1,"491":1,"492":1,"493":1,"495":1,"497":1,"502":1,"526":2,"527":1,"528":2,"531":1,"532":1,"550":1,"555":6,"556":1,"557":1,"558":2,"563":2,"564":7,"571":2,"577":1,"579":1,"582":1,"590":2,"591":3,"593":1,"594":3,"598":2,"599":1,"601":2,"605":1,"607":5,"608":1,"609":16,"611":6,"612":1,"614":1,"617":1,"618":1,"620":1,"621":1,"622":1,"626":5,"632":2,"633":5,"634":2,"636":4,"657":1,"660":1,"748":3,"756":4,"761":1,"765":1,"766":2,"773":2,"825":1,"827":1,"832":1,"857":1,"860":1,"861":1,"864":2,"892":1,"893":1,"896":5,"897":1,"899":1,"904":1,"914":2,"917":1,"937":1,"946":1,"950":1,"953":2,"954":1,"955":1,"959":1,"960":1,"971":4,"972":5,"974":1,"975":2,"987":2,"990":1,"991":1,"1003":5,"1004":8,"1006":2,"1007":5,"1011":1,"1012":3,"1017":1,"1018":5,"1058":2,"1062":1,"1069":1,"1089":1,"1090":2,"1091":1,"1092":1,"1093":3,"1094":2,"1095":5,"1116":1,"1119":1,"1124":2,"1127":1,"1128":1,"1132":2,"1136":1,"1137":1,"1160":2,"1164":1,"1171":1,"1180":1,"1182":1,"1186":3,"1188":1,"1189":1,"1190":2,"1191":4,"1192":11,"1193":2,"1200":3,"1201":1,"1202":28,"1203":28,"1211":2,"1212":5,"1214":1,"1216":5,"1237":4,"1270":3,"1286":8,"1300":17,"1301":7,"1304":2,"1308":1,"1309":4,"1311":1,"1323":1,"1324":22,"1339":3,"1347":1,"1350":4,"1355":1,"1359":22,"1380":20,"1395":11,"1396":11,"1402":2,"1403":1,"1405":1,"1410":2,"1411":1,"1415":6,"1416":2,"1417":4,"1427":1,"1428":2,"1430":2,"1431":4,"1436":48,"1438":1,"1440":1,"1451":2,"1453":7,"1454":3,"1455":1,"1457":2,"1458":1,"1459":3,"1462":1,"1465":1,"1466":4,"1467":22,"1470":6,"1471":4,"1472":1,"1473":1,"1474":37,"1475":1,"1476":32,"1477":12,"1478":5,"1479":1,"1480":2,"1481":17,"1482":9,"1483":10,"1484":4,"1485":2,"1486":3,"1487":2,"1488":1,"1489":6,"1492":6,"1494":4,"1496":48,"1497":2,"1498":1,"1501":3,"1502":5,"1503":1,"1515":1,"1517":5,"1521":1,"1522":4,"1524":3,"1525":4,"1526":50,"1527":1,"1528":1,"1529":1,"1531":5,"1532":1,"1533":1,"1534":3,"1535":5,"1571":1,"1573":3,"1574":1,"1575":4,"1576":3,"1577":72,"1578":1,"1579":1,"1582":11,"1583":1,"1585":6,"1586":13,"1587":4,"1589":6,"1590":1,"1591":32,"1593":1,"1594":3,"1595":1,"1596":9,"1597":3,"1598":3,"1599":1,"1600":4,"1605":1,"1617":1,"1631":1,"1646":1,"1648":1,"1649":2,"1654":1,"1658":3,"1673":2,"1674":5,"1675":2,"1692":1,"1697":1,"1706":1,"1709":1,"1732":1,"1734":1,"1735":1,"1756":1,"1765":1,"1838":1,"1895":1,"1994":1,"2000":3,"2001":3,"2033":1,"2034":1,"2041":1,"2042":1,"2043":1,"2045":1,"2046":2,"2049":2,"2060":1,"2112":2,"2117":1,"2118":1,"2120":1,"2126":1,"2151":1,"2155":1,"2158":1,"2162":1,"2171":2,"2172":2,"2178":1,"2179":3,"2182":2,"2195":3,"2239":2}}],["cmd",{"2":{"1606":1,"1740":1},"3":{"1606":1,"1740":1}}],["cms",{"3":{"1395":1}}],["ck",{"2":{"1591":1},"3":{"1591":1}}],["c4adff2",{"2":{"1761":1},"3":{"1591":1,"1761":1}}],["c4",{"3":{"1571":1}}],["cwv",{"3":{"1526":1,"1531":1,"1600":3}}],["c64b9fe2",{"3":{"1496":1}}],["c6369020",{"3":{"1472":1}}],["c911480",{"3":{"1496":2}}],["cyan",{"3":{"1480":3}}],["cyclic",{"3":{"1259":1,"1286":1}}],["cyclomatic",{"3":{"1237":1,"1247":1,"1312":1,"1359":1}}],["cyclonedx",{"2":{"373":1},"3":{"0":1,"370":4,"371":1,"372":1,"373":1,"374":1,"375":2,"870":1,"1445":1,"1454":4,"1455":1,"1526":1,"1576":1,"1577":5,"1673":1,"1738":1}}],["cyclefixtures",{"2":{"1436":1},"3":{"1207":8,"1436":15}}],["cycletestmap",{"3":{"1198":1,"1199":2,"1207":1,"1436":5,"1496":1}}],["cycles",{"0":{"1198":1},"2":{"135":1},"3":{"0":1,"19":1,"130":1,"135":3,"136":1,"139":1,"627":1,"1011":1,"1191":1,"1192":1,"1196":1,"1201":3,"1207":1,"1259":1,"1270":1,"1275":2,"1286":1,"1300":1,"1323":1,"1324":1,"1339":1,"1431":1,"1436":12,"1438":1,"1489":1,"1496":24,"1498":5,"1517":2,"1638":1,"1639":1,"1665":1,"1804":1,"1867":1,"1885":1,"1886":1}}],["cycle",{"0":{"139":1},"3":{"0":3,"17":1,"19":1,"22":1,"24":5,"26":2,"27":3,"99":1,"125":1,"130":1,"135":5,"139":1,"140":1,"534":1,"536":6,"539":2,"556":1,"583":2,"707":5,"846":1,"861":1,"876":1,"1012":1,"1014":1,"1017":1,"1036":2,"1085":1,"1089":2,"1090":1,"1201":2,"1212":1,"1229":1,"1237":8,"1247":4,"1251":1,"1253":2,"1254":4,"1255":2,"1256":1,"1259":27,"1269":1,"1270":2,"1275":6,"1286":17,"1309":1,"1315":1,"1317":4,"1323":27,"1324":5,"1328":2,"1330":1,"1339":6,"1350":2,"1359":2,"1369":1,"1395":3,"1409":1,"1415":1,"1416":2,"1417":1,"1431":1,"1436":35,"1438":2,"1441":2,"1443":1,"1448":1,"1449":1,"1450":1,"1453":1,"1456":1,"1467":2,"1486":1,"1490":1,"1491":1,"1496":9,"1498":2,"1517":2,"1571":1,"1664":1,"1706":1,"1881":1,"1885":1,"1886":1,"1889":1,"1948":1,"2010":1,"2156":1,"2157":1,"2165":1,"2167":8,"2188":3,"2193":1}}],["cn=user",{"3":{"1416":1}}],["c50d86f",{"3":{"1496":1}}],["c5",{"3":{"1355":1,"1359":1,"1641":1}}],["cbc",{"3":{"1286":1}}],["c2y6ydjf5",{"3":{"1286":1}}],["cjk",{"3":{"1271":1,"1289":1,"1300":1,"1324":1,"1436":4,"1438":1}}],["cpm",{"3":{"1214":1,"1591":2}}],["cpu",{"2":{"875":1},"3":{"31":1,"234":1,"361":1,"593":1,"869":1,"871":1,"875":5,"1300":1,"1334":1,"1339":4,"1443":2,"1467":9,"1534":1,"2000":1,"2007":1,"2011":1,"2145":1}}],["cdc",{"3":{"1526":1,"1545":1}}],["cd0026df",{"2":{"1204":1},"3":{"1204":1,"1496":1}}],["cd",{"1":{"1452":1,"1453":1,"1454":1,"1455":1,"1456":1,"1457":1,"1458":1,"1459":1,"1460":1,"1461":1},"3":{"1192":1,"1452":1,"1453":1,"1455":1,"1462":2,"1463":1,"1465":1,"1471":1,"1474":1,"1475":1,"1476":1,"1482":1,"1486":1,"1526":2,"1545":1,"1554":1,"1575":1,"1577":2,"1582":1,"1584":1,"1586":2,"1670":1,"1674":1,"2109":1,"2213":1,"2218":1,"2219":1,"2220":2}}],["cdn",{"3":{"441":1,"1116":1,"1311":2,"1415":1,"1467":1,"1481":1,"1666":1,"1973":1,"2075":1,"2127":1,"2150":4}}],["cf69cb8e",{"3":{"1496":3}}],["cfg",{"2":{"819":1},"3":{"640":2,"819":1,"1323":7}}],["cfbundleurltypes",{"2":{"420":1},"3":{"420":1}}],["ctrl",{"2":{"1606":1,"1740":1},"3":{"1606":1,"1740":1}}],["cts",{"3":{"1324":30,"1395":16,"1396":14,"1402":5,"1415":3,"1417":4,"1526":2,"1577":4}}],["ctor",{"3":{"1286":2,"1311":1,"1324":2,"1436":7,"1526":2,"1591":1}}],["ctorprobequeryhandler",{"3":{"1199":2,"1207":1}}],["ctorprobequery",{"3":{"1199":3,"1207":1}}],["ctorprobecommandhandler",{"3":{"1199":2,"1207":1}}],["ctorprobecommand",{"3":{"1199":3,"1207":1}}],["cte",{"3":{"1132":1}}],["ct",{"3":{"109":2,"434":1,"690":1,"819":1,"1212":1,"1259":3,"1270":3,"1286":6,"1300":6,"1323":9,"1324":28,"1359":2,"1380":4,"1395":60,"1396":23,"1436":9,"1919":1,"1944":1,"2032":1}}],["c→a",{"3":{"78":1,"1591":1,"1594":1,"1596":4,"1597":2,"1599":1}}],["c+",{"3":{"0":1,"1183":2,"1187":2}}],["c",{"0":{"1214":1},"1":{"977":1,"978":1,"979":1,"980":1,"981":1,"982":1,"983":1,"984":1,"1803":1,"1804":1,"1805":1,"1806":1,"1807":1},"3":{"0":3,"53":1,"142":1,"145":1,"147":1,"265":1,"274":1,"418":1,"543":1,"549":1,"692":1,"839":1,"840":1,"963":1,"964":2,"977":1,"979":2,"980":1,"1004":1,"1010":1,"1011":1,"1013":1,"1019":1,"1066":1,"1183":1,"1193":1,"1196":1,"1212":2,"1214":3,"1216":1,"1229":3,"1236":1,"1237":5,"1247":2,"1259":1,"1262":1,"1270":2,"1271":8,"1286":6,"1300":9,"1309":3,"1311":7,"1312":7,"1316":1,"1323":6,"1324":25,"1326":1,"1339":6,"1342":1,"1344":1,"1349":1,"1350":6,"1354":1,"1359":12,"1378":1,"1379":1,"1380":7,"1395":8,"1396":31,"1401":2,"1402":5,"1413":1,"1415":10,"1416":3,"1417":9,"1425":1,"1427":5,"1430":1,"1433":1,"1436":18,"1441":1,"1455":1,"1479":1,"1488":2,"1491":1,"1492":1,"1496":2,"1497":1,"1526":2,"1536":1,"1565":1,"1577":1,"1582":1,"1585":4,"1586":7,"1632":1,"1639":1,"1641":1,"1648":2,"1651":1,"1661":1,"1692":1,"1693":1,"1712":3,"1726":1,"1727":1,"1728":1,"1761":1,"1765":1,"1779":1,"1787":1,"1790":1,"1793":1,"1796":1,"1803":1,"1807":1,"1812":1,"1817":1,"1827":1,"1830":3,"1831":1,"1834":1,"1846":1,"1853":1,"1862":1,"1868":1,"1877":1,"1886":1,"1892":1,"1899":1,"1905":1,"1912":1,"1924":1,"1927":1,"1930":1,"1941":1,"1950":1,"1953":1,"1955":1,"1959":1,"1965":1,"1972":1,"1979":1,"1985":1,"1991":1,"1997":1,"2002":1,"2004":1,"2014":1,"2016":1,"2017":2,"2018":1,"2028":1,"2049":1,"2060":1,"2069":1,"2075":2,"2080":1,"2086":1,"2088":1,"2098":1,"2108":1,"2123":1,"2128":1,"2133":1,"2139":1,"2146":1,"2153":1,"2160":1,"2169":1,"2177":1,"2180":1,"2193":1,"2203":1,"2206":1,"2211":1,"2219":1,"2220":2,"2232":1}}],["cve",{"3":{"0":1,"370":1,"1445":1}}],["cs0103",{"2":{"1648":1},"3":{"1648":1}}],["cs0618",{"3":{"1417":1,"1436":1}}],["cs1591",{"3":{"1577":2,"1586":3,"1591":1}}],["cs8509",{"3":{"1436":1}}],["cs9113",{"3":{"1436":3}}],["cs9107",{"3":{"1259":1,"1286":3}}],["cswinrt1028",{"3":{"1417":1}}],["cswinrt1030",{"3":{"1417":2}}],["cswinrt",{"3":{"1417":2}}],["css3",{"3":{"2220":1}}],["css",{"2":{"2075":1,"2079":1},"3":{"954":1,"1300":1,"1324":13,"1395":1,"1432":1,"1436":17,"1438":1,"1489":2,"1524":2,"1526":12,"1531":2,"1532":1,"1534":2,"1535":1,"1557":1,"1565":1,"1577":11,"1582":6,"1583":2,"1586":1,"1591":7,"1595":1,"1596":2,"1597":2,"1600":4,"1712":3,"1714":1,"2073":1,"2075":9,"2079":1,"2150":2}}],["csrfheadervalue",{"3":{"1324":2}}],["csrfheadername",{"3":{"1324":1}}],["csrf",{"0":{"1969":1},"3":{"0":2,"207":1,"209":2,"1184":7,"1185":3,"1186":1,"1187":2,"1300":2,"1324":25,"1417":1,"1438":2,"1534":1,"1582":1,"1971":1,"1972":2}}],["cs",{"2":{"43":2,"63":1,"93":1,"95":1,"112":1,"115":3,"142":1,"145":4,"152":3,"171":2,"181":2,"203":1,"220":2,"254":1,"256":1,"258":1,"259":4,"337":5,"345":3,"354":2,"364":1,"378":1,"395":1,"409":1,"418":1,"424":1,"425":1,"428":2,"452":1,"453":1,"464":1,"465":1,"466":1,"514":2,"543":4,"545":1,"547":1,"552":7,"558":1,"559":1,"572":1,"597":1,"604":1,"618":1,"621":1,"638":1,"644":2,"649":1,"651":1,"652":1,"693":5,"720":2,"728":2,"736":1,"744":1,"747":3,"750":1,"752":1,"822":3,"832":2,"833":3,"841":1,"846":1,"849":1,"850":1,"883":1,"892":5,"916":1,"917":2,"929":1,"949":4,"960":1,"967":1,"975":2,"991":1,"999":1,"1029":4,"1050":3,"1062":1,"1074":6,"1078":2,"1128":3,"1133":1,"1135":1,"1214":1,"1262":2,"1300":1,"1311":2,"1329":2,"1444":2,"1445":1,"1453":2,"1497":2,"1532":1,"1535":4,"1577":1,"1582":2,"1586":1,"1591":2,"1596":1,"1597":1,"1619":1,"1684":1,"1685":1,"1686":1,"1689":1,"1767":8,"1829":1,"1878":1,"1885":1,"1895":2,"1902":1,"2011":1,"2030":1,"2148":1,"2159":8,"2188":1,"2190":1},"3":{"0":6,"15":2,"17":2,"19":5,"22":2,"23":1,"24":24,"25":5,"26":13,"27":23,"31":5,"34":1,"39":13,"42":2,"43":4,"53":1,"59":5,"60":1,"61":1,"62":1,"63":4,"68":4,"70":2,"71":4,"72":7,"73":4,"74":8,"79":1,"80":10,"81":6,"82":2,"93":6,"94":2,"95":15,"96":6,"98":5,"99":1,"100":2,"103":3,"104":2,"109":38,"110":3,"111":7,"112":4,"115":3,"117":12,"121":15,"122":15,"123":4,"125":7,"126":16,"127":2,"128":9,"135":9,"136":3,"138":1,"139":10,"142":6,"145":7,"147":3,"150":25,"152":4,"157":11,"159":2,"160":9,"161":12,"164":1,"166":14,"168":1,"170":5,"171":10,"175":2,"178":9,"179":7,"180":5,"181":4,"186":35,"188":1,"189":5,"190":6,"193":2,"195":9,"196":2,"198":3,"200":8,"201":9,"202":11,"203":6,"216":4,"217":3,"218":4,"219":7,"220":10,"225":11,"228":4,"230":12,"231":4,"235":25,"237":5,"238":3,"243":37,"245":15,"246":8,"250":6,"251":12,"252":15,"254":19,"255":7,"256":17,"257":16,"258":16,"259":15,"260":11,"261":19,"265":12,"268":7,"272":1,"281":1,"283":2,"284":2,"285":3,"290":1,"295":12,"296":1,"300":4,"301":1,"303":4,"305":8,"306":5,"310":30,"312":5,"314":8,"318":67,"319":4,"320":2,"323":7,"324":8,"325":8,"326":13,"328":1,"330":37,"331":8,"332":7,"333":5,"334":2,"336":3,"337":12,"341":23,"342":1,"343":5,"344":6,"345":9,"350":21,"352":7,"353":7,"354":2,"359":62,"360":8,"361":9,"364":1,"365":3,"378":1,"384":8,"387":2,"388":4,"390":10,"391":8,"392":4,"393":5,"395":2,"397":66,"399":6,"400":5,"401":15,"402":16,"403":1,"404":7,"407":12,"408":7,"409":6,"412":1,"413":12,"415":4,"416":1,"418":11,"419":1,"420":2,"422":2,"423":6,"424":3,"425":4,"426":6,"427":2,"428":9,"429":2,"430":2,"434":7,"435":3,"436":8,"442":1,"443":5,"444":9,"448":45,"450":3,"452":1,"453":7,"454":3,"455":1,"459":35,"461":5,"463":15,"464":17,"465":14,"466":8,"469":4,"470":17,"472":1,"474":1,"475":10,"476":14,"477":14,"478":2,"482":2,"486":6,"487":6,"488":5,"489":6,"490":6,"491":7,"492":8,"493":8,"494":6,"495":5,"497":12,"498":1,"499":45,"500":1,"501":5,"502":4,"507":63,"508":1,"509":1,"511":13,"512":1,"513":15,"514":8,"522":8,"523":8,"524":2,"535":1,"536":73,"537":1,"538":15,"539":14,"540":13,"543":11,"544":2,"545":44,"546":4,"547":6,"549":25,"550":9,"551":3,"552":21,"556":47,"558":4,"559":6,"564":8,"572":81,"574":2,"575":4,"577":10,"578":9,"583":60,"584":12,"585":28,"586":6,"587":1,"591":14,"592":3,"597":1,"599":4,"601":2,"603":3,"604":1,"609":7,"618":35,"620":3,"621":4,"622":1,"633":4,"635":1,"636":3,"638":1,"640":24,"642":5,"644":5,"649":28,"651":5,"652":4,"656":1,"657":72,"658":4,"659":6,"660":10,"665":8,"668":4,"669":3,"674":82,"675":3,"676":14,"677":11,"682":10,"684":2,"685":2,"690":40,"691":4,"692":3,"693":9,"697":1,"698":26,"700":4,"702":9,"703":7,"706":1,"707":15,"710":8,"715":7,"718":1,"719":1,"720":11,"724":8,"725":21,"727":3,"728":6,"732":1,"733":16,"736":12,"740":7,"741":31,"743":14,"744":6,"747":3,"749":48,"750":1,"751":12,"752":2,"774":28,"776":7,"777":6,"782":51,"783":1,"784":4,"789":5,"790":20,"791":4,"792":10,"793":6,"794":4,"798":12,"799":5,"801":3,"803":5,"804":3,"805":2,"806":4,"809":6,"813":6,"814":8,"818":1,"819":21,"821":3,"822":7,"826":5,"827":47,"829":6,"830":9,"831":15,"832":21,"833":15,"837":6,"838":10,"839":2,"841":4,"846":5,"847":1,"849":4,"850":4,"854":5,"857":4,"861":11,"862":4,"863":4,"864":3,"880":1,"881":60,"883":8,"884":4,"888":3,"889":20,"890":5,"891":8,"892":10,"897":34,"899":3,"900":8,"904":1,"905":55,"906":18,"907":15,"908":5,"909":11,"910":2,"913":5,"914":15,"915":2,"916":3,"917":2,"921":3,"922":11,"923":5,"924":4,"926":29,"927":8,"929":1,"930":1,"931":9,"934":3,"946":19,"948":5,"949":5,"954":21,"955":2,"956":1,"959":1,"960":7,"963":6,"964":12,"965":3,"966":7,"967":5,"971":2,"972":6,"973":1,"974":5,"975":4,"979":8,"980":71,"981":1,"982":10,"983":7,"987":6,"988":36,"990":3,"991":4,"995":4,"996":18,"997":2,"998":9,"999":4,"1003":1,"1004":20,"1005":2,"1006":4,"1007":3,"1017":2,"1018":38,"1020":3,"1021":9,"1025":5,"1026":29,"1028":2,"1029":9,"1034":19,"1037":5,"1041":4,"1042":20,"1043":1,"1044":4,"1045":6,"1049":5,"1050":25,"1052":3,"1054":2,"1055":9,"1058":4,"1059":56,"1061":1,"1062":3,"1066":4,"1067":20,"1069":5,"1070":3,"1074":22,"1075":1,"1078":3,"1082":11,"1083":7,"1085":2,"1086":9,"1090":3,"1091":64,"1092":5,"1093":13,"1094":5,"1095":7,"1099":2,"1100":17,"1102":2,"1103":5,"1108":15,"1111":6,"1116":37,"1117":2,"1119":4,"1123":8,"1124":37,"1125":7,"1126":6,"1127":2,"1128":6,"1129":2,"1132":1,"1133":17,"1135":3,"1136":3,"1140":11,"1142":1,"1146":2,"1150":13,"1156":3,"1160":2,"1161":8,"1162":4,"1163":4,"1168":32,"1169":1,"1170":3,"1174":1,"1175":39,"1176":6,"1177":7,"1178":3,"1179":2,"1183":4,"1184":36,"1185":6,"1186":7,"1187":1,"1188":1,"1190":1,"1191":6,"1204":1,"1207":5402,"1208":118,"1209":139,"1212":6,"1214":2,"1219":13,"1220":24,"1221":11,"1222":9,"1223":15,"1224":6,"1225":11,"1226":7,"1227":13,"1228":21,"1229":244,"1231":31,"1232":18,"1233":18,"1234":43,"1235":14,"1236":27,"1237":526,"1239":59,"1240":36,"1241":96,"1242":48,"1243":54,"1244":56,"1245":6,"1246":16,"1247":330,"1249":15,"1250":5,"1251":6,"1252":13,"1253":16,"1254":17,"1255":10,"1256":37,"1257":17,"1258":19,"1259":576,"1261":4,"1262":41,"1263":84,"1264":34,"1265":60,"1266":17,"1267":28,"1268":18,"1269":48,"1270":588,"1271":181,"1273":20,"1274":4,"1275":20,"1276":11,"1277":8,"1278":29,"1279":14,"1280":33,"1281":16,"1282":29,"1283":9,"1284":11,"1285":9,"1286":2431,"1288":35,"1289":49,"1290":61,"1291":19,"1292":21,"1293":52,"1294":47,"1295":30,"1296":18,"1297":100,"1298":38,"1299":9,"1300":1398,"1301":413,"1302":8,"1303":11,"1304":19,"1305":8,"1306":11,"1307":26,"1308":11,"1309":440,"1310":118,"1311":973,"1312":127,"1314":12,"1315":35,"1316":51,"1317":39,"1318":45,"1319":6,"1320":64,"1321":3,"1322":41,"1323":863,"1324":1359,"1325":7,"1326":14,"1327":12,"1328":6,"1329":5,"1330":5,"1331":5,"1332":34,"1333":20,"1334":14,"1335":6,"1336":7,"1337":6,"1338":13,"1339":404,"1341":3,"1342":78,"1343":42,"1344":46,"1345":8,"1346":6,"1347":47,"1348":19,"1349":25,"1350":995,"1352":49,"1353":38,"1354":15,"1355":17,"1356":12,"1357":14,"1358":11,"1359":3331,"1361":3,"1362":46,"1363":5,"1364":14,"1365":8,"1366":16,"1367":6,"1368":7,"1369":111,"1371":27,"1372":40,"1373":45,"1374":16,"1375":38,"1376":4,"1377":15,"1378":19,"1379":9,"1380":378,"1382":25,"1383":27,"1384":24,"1385":16,"1386":6,"1387":13,"1388":12,"1389":14,"1390":11,"1391":22,"1392":21,"1393":10,"1394":6,"1395":998,"1396":1539,"1397":5,"1398":26,"1399":44,"1400":13,"1401":22,"1402":504,"1403":1,"1404":22,"1405":13,"1406":32,"1407":6,"1408":8,"1409":11,"1410":13,"1411":7,"1412":7,"1413":12,"1414":13,"1415":821,"1416":211,"1417":1129,"1419":7,"1420":7,"1421":4,"1422":11,"1423":7,"1424":6,"1425":9,"1426":3,"1427":285,"1429":28,"1430":23,"1431":90,"1432":43,"1433":50,"1434":18,"1435":37,"1436":2193,"1437":40,"1438":400,"1440":2,"1441":95,"1442":39,"1443":111,"1444":39,"1445":1,"1446":2,"1447":39,"1448":4,"1449":25,"1450":8,"1451":3,"1453":2,"1455":4,"1467":18,"1476":11,"1481":1,"1487":1,"1488":69,"1489":36,"1490":6,"1491":3,"1492":27,"1496":341,"1497":4,"1498":2,"1524":6,"1525":8,"1526":227,"1531":20,"1532":16,"1534":10,"1535":55,"1575":3,"1576":11,"1577":247,"1582":29,"1583":10,"1584":1,"1585":7,"1586":66,"1589":5,"1590":5,"1591":148,"1596":22,"1597":9,"1599":6,"1600":57,"1605":1,"1611":3,"1619":6,"1627":2,"1651":2,"1653":5,"1654":1,"1656":2,"1684":2,"1685":25,"1686":35,"1689":1,"1697":1,"1707":5,"1708":1,"1712":1,"1727":1,"1728":2,"1739":1,"1749":1,"1757":1,"1760":2,"1761":2,"1764":19,"1765":30,"1767":83,"1770":2,"1815":1,"1829":2,"1878":1,"1885":1,"1895":2,"1896":1,"1902":1,"1939":1,"1953":1,"1982":22,"1983":3,"1984":3,"1994":41,"1995":36,"2000":38,"2001":12,"2006":1,"2008":1,"2011":1,"2030":1,"2032":2,"2044":7,"2046":2,"2064":3,"2066":8,"2083":12,"2085":8,"2097":2,"2117":9,"2118":8,"2119":4,"2121":3,"2122":3,"2126":19,"2127":6,"2131":23,"2132":3,"2136":15,"2137":3,"2138":11,"2143":1,"2148":1,"2149":7,"2150":3,"2151":7,"2152":3,"2156":33,"2157":13,"2159":14,"2161":1,"2163":9,"2164":6,"2165":5,"2166":5,"2167":15,"2168":5,"2172":2,"2173":2,"2174":5,"2175":9,"2176":1,"2177":3,"2178":1,"2179":8,"2184":3,"2185":2,"2186":4,"2187":2,"2188":2,"2189":3,"2190":4,"2191":2,"2192":1,"2196":1,"2197":4,"2198":3,"2199":6,"2201":2,"2202":10}}],["csvcontenttype",{"3":{"1311":1}}],["csvwritertests",{"3":{"1199":1,"1207":2,"1438":1}}],["csvwriter",{"2":{"741":1},"3":{"741":5,"743":1,"1199":3,"1203":1,"1207":2,"1311":25,"1438":1,"1496":1}}],["csvs",{"3":{"725":2}}],["csvhelper",{"3":{"0":1,"741":1,"1311":1}}],["csv",{"1":{"738":1,"739":1,"740":1,"741":1,"742":1,"743":1,"744":1,"745":1},"3":{"0":3,"318":3,"321":1,"324":1,"330":1,"696":1,"738":1,"740":5,"741":9,"742":2,"743":4,"744":1,"745":1,"1212":1,"1229":1,"1311":22,"1323":1,"1359":3,"1371":1,"1372":2,"1380":15,"1438":2,"1487":1,"1496":1,"1498":1,"1526":2,"1575":1,"1577":1,"1582":1,"1586":1,"1660":1,"1666":4,"1720":1,"1988":3,"1991":1,"1995":2,"1997":1,"2232":1}}],["cspoptions",{"3":{"1324":2}}],["cspnonce",{"2":{"2149":1},"3":{"214":1,"1199":3,"1203":1,"1207":1,"1336":1,"1339":6,"1443":1,"1526":1,"1577":2,"1670":1,"2149":2}}],["csppolicy",{"2":{"2011":1},"3":{"214":1,"1199":7,"1203":1,"1207":1,"1324":3,"1336":1,"1339":11,"2011":1,"2149":1}}],["csproj",{"2":{"152":1,"872":1,"1215":1,"1728":1,"2041":1},"3":{"0":1,"135":1,"147":1,"152":2,"265":1,"370":1,"375":1,"422":1,"426":1,"429":1,"430":1,"486":4,"487":1,"489":1,"490":1,"491":1,"492":1,"493":1,"526":1,"528":1,"572":2,"576":1,"591":2,"593":1,"609":2,"613":1,"665":1,"669":1,"826":2,"827":1,"861":1,"872":1,"876":4,"913":4,"914":1,"917":1,"939":1,"954":11,"956":4,"958":2,"960":2,"971":1,"972":1,"979":1,"1059":1,"1067":3,"1090":1,"1091":6,"1094":2,"1212":1,"1214":1,"1215":1,"1247":1,"1323":2,"1324":3,"1325":2,"1339":5,"1341":5,"1344":1,"1350":1,"1359":4,"1380":1,"1395":2,"1396":3,"1415":2,"1416":5,"1417":1,"1434":1,"1435":1,"1436":36,"1437":1,"1438":2,"1441":8,"1445":4,"1454":2,"1476":1,"1481":9,"1486":2,"1488":2,"1489":3,"1492":1,"1494":1,"1524":1,"1526":6,"1531":1,"1535":1,"1577":1,"1586":1,"1591":2,"1600":1,"1649":2,"1685":1,"1686":1,"1727":1,"1728":5,"2041":1,"2046":1,"2179":2}}],["csprng",{"3":{"0":1,"1212":1,"1300":5}}],["csp",{"0":{"2149":1},"1":{"211":1,"212":1,"213":1,"214":1,"215":1,"216":1,"217":1,"218":1,"219":1,"220":1,"221":1,"2147":1,"2148":1,"2149":1,"2150":1,"2151":1,"2152":1,"2153":1},"3":{"0":4,"211":1,"213":1,"214":2,"215":1,"216":2,"672":1,"676":1,"1108":1,"1109":1,"1111":1,"1112":1,"1183":1,"1189":1,"1212":2,"1324":11,"1336":4,"1339":25,"1391":1,"1395":3,"1437":1,"1441":1,"1450":1,"1496":3,"1525":1,"1526":4,"1535":1,"1577":3,"1585":1,"1586":1,"1590":1,"1591":2,"1660":2,"1670":1,"1779":2,"2011":3,"2012":1,"2146":1,"2147":4,"2148":2,"2149":3,"2150":2,"2152":2,"2153":6,"2160":1,"2208":1,"2232":2}}],["csharp",{"2":{"136":1,"1214":2,"2044":1},"3":{"0":1,"136":2,"1213":1,"1214":2,"1436":3,"2044":2}}],["celebrated",{"3":{"2035":1}}],["celebration",{"3":{"1396":1}}],["cellclass=",{"3":{"1395":1,"1591":1}}],["celltemplate",{"3":{"1395":1}}],["cellstyle=",{"3":{"1526":1,"1534":1,"1591":1,"1600":1}}],["cells",{"3":{"1311":2,"1402":5,"1436":2}}],["cell",{"3":{"743":2,"1311":4,"1395":3,"1396":1,"1402":1,"1436":2,"1488":2,"1496":2,"1500":1,"1526":1,"1534":1,"1591":2,"1988":1}}],["cede",{"3":{"1079":1}}],["ceding",{"3":{"1078":1}}],["census",{"3":{"474":1,"475":1,"476":1,"797":4,"803":1,"804":1,"805":1,"806":1,"1048":1,"1049":1,"1051":1,"1054":1,"2232":3}}],["cents",{"3":{"1765":1,"1770":1}}],["centric",{"3":{"1359":1,"1436":1,"1540":1,"1577":1}}],["centre",{"3":{"1286":1,"1679":1}}],["centralising",{"3":{"1339":2}}],["centralised",{"3":{"1270":1,"1301":1,"1323":1,"1339":2}}],["centralizing",{"3":{"132":1,"215":1,"750":1,"1229":1,"1237":2,"1247":1,"1270":2,"1286":5,"1300":2,"1309":1,"1311":2,"1323":1,"1344":1,"1350":1,"1359":2,"1380":1,"1395":1,"1396":1,"1402":3,"1405":1,"1415":1,"1417":1,"1436":3,"2023":1,"2152":1}}],["centralize",{"3":{"1336":1,"1862":1,"2153":1}}],["centralizes",{"3":{"60":1,"1286":1,"1339":1,"1396":1,"1417":1,"1431":1,"1436":2,"2023":1,"2147":1}}],["centralized",{"1":{"211":1,"212":1,"213":1,"214":1,"215":1,"216":1,"217":1,"218":1,"219":1,"220":1,"221":1},"3":{"0":2,"211":1,"283":1,"1117":1,"1237":2,"1270":1,"1286":1,"1292":1,"1300":2,"1301":1,"1309":1,"1311":3,"1323":1,"1324":5,"1339":2,"1350":1,"1359":3,"1380":1,"1394":1,"1395":2,"1396":4,"1402":1,"1415":1,"1433":1,"1526":2,"1548":1,"1557":1,"1575":1,"1576":1,"1577":4,"1591":1,"1805":1,"2153":1,"2195":1}}],["centralus",{"2":{"972":1},"3":{"972":1,"1436":3}}],["centrally",{"3":{"31":1,"665":1,"847":1,"1212":1,"1231":2,"1237":2,"1270":1,"1274":1,"1286":2,"1323":1,"1324":3,"1348":1,"1350":2,"1359":1,"1369":1,"1395":1,"1402":1,"1526":1,"1545":1,"1577":2,"1706":1,"1707":2,"1738":1,"1750":1,"1773":1,"1810":1,"2230":1}}],["central",{"3":{"0":2,"186":1,"201":1,"301":1,"465":1,"669":1,"715":1,"800":1,"938":1,"954":2,"956":1,"958":2,"959":2,"960":1,"972":1,"1213":1,"1214":1,"1229":1,"1247":1,"1260":1,"1286":1,"1300":1,"1311":1,"1323":3,"1324":2,"1339":1,"1350":1,"1359":1,"1369":1,"1395":2,"1402":1,"1415":1,"1416":2,"1430":1,"1436":7,"1449":1,"1460":1,"1465":1,"1467":2,"1489":1,"1490":1,"1526":1,"1535":1,"1552":1,"1569":2,"1577":1,"1591":1,"1649":1,"1663":1,"1685":1,"1686":1,"1706":1,"1707":1,"1852":1,"2043":2}}],["centers",{"3":{"2220":1}}],["centered",{"3":{"1480":1,"1669":1}}],["centerpiece",{"3":{"1324":1,"1396":1,"2072":1}}],["center",{"3":{"440":1,"1286":2,"1324":1,"1395":1,"1429":1,"1436":1,"1526":1,"1534":1,"1577":1,"1789":1,"2126":1,"2128":1}}],["ceil",{"2":{"435":1},"3":{"435":1}}],["ceilings",{"3":{"0":3,"591":3,"592":1,"593":2,"595":1,"859":1,"861":1,"862":1,"1125":2,"1126":1,"1212":1,"1323":1,"1359":2,"1416":2,"1431":1,"1436":19,"1438":1,"1492":1,"1534":1,"1535":1,"1577":3,"1588":1,"1591":1,"1597":1,"1599":2,"1668":1,"1673":1,"2047":1,"2048":1,"2232":1}}],["ceiling",{"3":{"0":15,"22":1,"48":1,"150":1,"177":1,"178":1,"233":1,"235":2,"237":3,"330":2,"337":1,"438":1,"442":1,"549":1,"575":1,"591":3,"593":1,"607":3,"609":6,"651":1,"733":3,"736":1,"741":4,"742":1,"743":1,"745":1,"760":1,"764":4,"765":1,"766":1,"768":1,"769":1,"792":1,"827":2,"829":1,"831":2,"832":4,"833":1,"860":1,"861":4,"862":2,"863":1,"997":1,"1016":3,"1018":5,"1019":2,"1020":1,"1042":2,"1067":1,"1074":2,"1075":1,"1076":1,"1090":2,"1092":2,"1094":2,"1095":1,"1096":1,"1099":1,"1100":1,"1122":1,"1124":5,"1125":1,"1126":2,"1128":1,"1129":1,"1186":1,"1212":2,"1228":1,"1229":4,"1238":1,"1243":3,"1244":1,"1247":15,"1270":1,"1271":4,"1275":1,"1286":5,"1289":2,"1297":1,"1300":6,"1301":6,"1306":1,"1309":4,"1310":1,"1311":9,"1312":1,"1323":6,"1324":30,"1334":1,"1339":12,"1350":3,"1359":15,"1366":2,"1368":1,"1369":2,"1371":1,"1380":7,"1395":4,"1396":11,"1415":7,"1416":6,"1417":1,"1418":2,"1420":1,"1422":1,"1427":4,"1436":68,"1438":1,"1447":1,"1455":1,"1463":1,"1465":5,"1467":12,"1469":1,"1470":1,"1489":5,"1492":3,"1496":4,"1526":6,"1534":3,"1577":6,"1582":2,"1583":1,"1584":1,"1586":1,"1591":2,"1597":1,"1599":1,"1664":1,"1666":2,"1668":1,"1670":1,"1671":1,"1707":4,"1708":2,"1817":1,"1923":1,"1929":1,"1987":1,"1988":2,"1991":2,"2000":6,"2001":2,"2002":2,"2027":1,"2047":1,"2048":1,"2126":2,"2145":1,"2154":1,"2172":1,"2174":1,"2180":3,"2182":1,"2188":2,"2190":1,"2232":1}}],["cero",{"3":{"1685":1,"1686":2}}],["ceremony",{"3":{"110":1,"111":1,"683":1,"938":1,"1109":1,"1220":1,"1237":1,"1259":1,"1286":1,"1309":1,"1323":2,"1324":1,"1340":1,"1342":1,"1350":2,"1359":4,"1392":1,"1395":1,"1396":7,"1415":5,"1417":3,"1436":1,"1637":1,"1806":1,"1808":1,"1811":1}}],["certification",{"3":{"2224":1}}],["certifications",{"0":{"2224":1},"3":{"2224":1}}],["certificates",{"3":{"1187":1,"1467":1}}],["certificate",{"3":{"0":1,"418":2,"420":1,"423":1,"424":1,"698":1,"1066":2,"1067":3,"1068":2,"1069":1,"1286":3,"1311":3,"1328":2,"1339":3,"1416":2,"1430":2,"1449":2,"1453":2,"1456":5,"1460":1,"1481":1,"1487":1,"1488":2,"1490":4,"1492":1}}],["certs",{"3":{"1067":1,"1069":1,"1430":1,"1456":1}}],["cert",{"2":{"418":1,"423":1,"424":1,"1481":1},"3":{"418":1,"423":1,"424":1,"1416":3,"1481":1}}],["certainly",{"3":{"1436":2,"1888":1}}],["certainty",{"3":{"1424":1,"1427":1}}],["certain",{"3":{"10":1,"1286":1,"1359":2,"1453":1,"1490":1,"2059":1}}],["cecil",{"3":{"0":1,"27":1,"39":1,"109":1,"1161":1,"1162":1,"1436":18}}],["cqrscontractinspectortests",{"3":{"1199":1,"1207":12,"1266":1,"1270":8,"1438":2}}],["cqrscontractinspector",{"2":{"1266":1,"1662":1},"3":{"1199":2,"1203":1,"1207":4,"1260":1,"1266":12,"1270":18,"1496":1,"1662":1}}],["cqrscontractmismatch",{"2":{"1266":1},"3":{"1199":3,"1203":1,"1207":1,"1260":1,"1266":1,"1270":6}}],["cqrscontractmismatchkind",{"2":{"1260":1,"1266":1},"3":{"1199":4,"1203":1,"1207":1,"1260":1,"1266":1,"1270":7}}],["cqrsmetricstests",{"3":{"1199":1,"1207":5,"1438":1}}],["cqrsmetricsprobequery",{"3":{"1199":2,"1207":1}}],["cqrsmetricsprobecommand",{"3":{"1199":2,"1207":1}}],["cqrsmetrics",{"2":{"126":1,"397":1,"405":1,"1268":1,"2159":1},"3":{"121":2,"126":2,"397":4,"399":1,"405":1,"1199":7,"1203":1,"1207":2,"1260":1,"1263":4,"1268":9,"1270":30,"1301":1,"1438":1,"1496":1,"2156":2,"2159":1}}],["cqrs",{"0":{"1507":1,"1543":1,"1937":1},"1":{"114":1,"115":1,"116":1,"117":1,"118":1,"119":1,"120":1,"121":1,"122":1,"123":1,"124":1,"125":1,"126":1,"127":1,"128":1,"1260":1,"1261":1,"1262":1,"1263":1,"1264":1,"1265":1,"1266":1,"1267":1,"1268":1,"1269":1,"1270":1,"1818":1,"1819":1,"1820":1,"1821":1,"1822":1,"1823":1,"1824":1,"1825":1,"1826":1,"1827":1,"2134":1,"2135":1,"2136":1,"2137":1,"2138":1,"2139":1},"2":{"121":1,"124":1,"126":2,"400":1,"822":1,"823":1,"1268":3,"2006":1,"2013":1,"2156":1,"2160":1},"3":{"0":8,"17":2,"27":4,"77":1,"114":1,"117":1,"121":4,"124":1,"126":2,"127":2,"128":3,"135":3,"139":3,"142":2,"160":2,"186":1,"246":1,"258":1,"299":1,"300":2,"301":1,"306":1,"395":6,"396":1,"397":6,"398":2,"400":1,"404":1,"405":1,"406":1,"407":1,"408":1,"409":1,"545":4,"552":1,"572":2,"578":1,"608":1,"748":1,"819":1,"822":1,"823":1,"827":1,"897":2,"1003":1,"1004":1,"1006":2,"1042":1,"1058":1,"1059":1,"1075":1,"1132":1,"1133":3,"1191":1,"1192":2,"1194":1,"1200":1,"1202":3,"1203":3,"1207":101,"1211":1,"1212":4,"1213":1,"1216":1,"1218":1,"1223":1,"1229":2,"1231":1,"1237":4,"1238":1,"1245":1,"1246":1,"1247":5,"1249":1,"1259":23,"1260":3,"1268":3,"1270":37,"1271":4,"1286":15,"1297":1,"1300":12,"1301":5,"1302":1,"1303":1,"1304":1,"1309":22,"1311":20,"1315":1,"1316":2,"1323":16,"1324":6,"1333":1,"1339":2,"1345":1,"1350":32,"1351":1,"1352":1,"1355":1,"1359":53,"1370":2,"1371":1,"1380":8,"1390":1,"1395":2,"1396":33,"1402":32,"1403":1,"1409":1,"1413":1,"1415":30,"1417":1,"1431":17,"1436":246,"1437":2,"1438":1,"1443":1,"1455":1,"1487":1,"1488":1,"1489":2,"1496":6,"1499":1,"1505":1,"1526":2,"1534":1,"1535":2,"1536":1,"1543":1,"1577":9,"1582":2,"1583":2,"1584":1,"1586":2,"1590":1,"1591":4,"1597":1,"1646":1,"1659":1,"1660":1,"1662":1,"1666":1,"1670":1,"1675":1,"1692":1,"1717":1,"1720":1,"1779":2,"1780":2,"1781":1,"1784":1,"1786":1,"1796":1,"1803":1,"1817":1,"1818":2,"1827":3,"1828":1,"1840":1,"1879":1,"1888":1,"1914":1,"1932":1,"1941":1,"1947":1,"1962":1,"1965":1,"2006":1,"2010":1,"2013":1,"2052":1,"2098":1,"2111":1,"2115":1,"2133":2,"2134":1,"2137":1,"2139":1,"2146":1,"2154":2,"2156":10,"2160":2,"2162":1,"2181":1,"2182":1,"2199":1,"2204":1,"2206":2,"2213":2,"2215":1,"2217":1,"2218":1,"2219":1,"2221":1,"2227":3,"2230":1,"2232":3,"2239":2}}],["clutter",{"3":{"2010":1}}],["clustertraceheadername",{"3":{"1436":1}}],["clustertraceechoheader",{"3":{"1436":1}}],["clusterheader",{"3":{"1436":1}}],["clusterheadername",{"3":{"1339":1}}],["clusterconfig",{"3":{"1339":4}}],["clustered",{"3":{"1332":1,"1339":3,"1443":1,"1496":4,"1515":1,"1799":1}}],["clusterprofile",{"3":{"1199":2,"1207":1}}],["clusterid",{"3":{"838":1,"1338":1,"1339":5}}],["clustering",{"3":{"641":1,"1043":1,"1154":1,"1467":2}}],["clusterrequestoverrides",{"2":{"838":1,"1338":1,"1656":1},"3":{"838":2,"1338":1,"1339":3,"1447":1,"1656":1}}],["clusterrequestdefaults",{"2":{"836":1,"838":1,"1338":1,"1656":1},"3":{"0":1,"836":1,"838":3,"1338":1,"1339":5,"1438":1,"1447":1,"1656":1}}],["clusters",{"3":{"91":1,"827":1,"838":7,"839":1,"841":1,"1287":2,"1300":1,"1338":1,"1339":4,"1436":1,"1438":4,"1447":8,"1496":1,"1656":1,"1779":1}}],["cluster",{"0":{"1338":1},"2":{"234":1},"3":{"0":14,"57":1,"59":1,"91":2,"93":2,"98":4,"100":4,"102":1,"234":2,"235":3,"236":1,"245":1,"259":2,"520":1,"572":4,"578":3,"790":3,"827":10,"829":2,"833":2,"836":2,"838":21,"839":3,"840":2,"841":2,"842":1,"1152":1,"1212":1,"1237":2,"1247":1,"1259":1,"1267":1,"1270":3,"1286":1,"1290":1,"1298":2,"1301":4,"1312":1,"1323":3,"1325":2,"1332":1,"1338":10,"1339":61,"1347":1,"1350":1,"1356":2,"1359":1,"1436":12,"1438":11,"1442":1,"1443":1,"1446":1,"1447":17,"1455":1,"1488":1,"1496":3,"1656":7,"1660":1,"1663":2,"1765":1,"1917":1,"2024":1,"2055":1,"2220":1}}],["cleverness",{"3":{"1909":1}}],["clever",{"3":{"1790":1}}],["cleavestack",{"3":{"2226":1,"2234":2}}],["clearlinkedspeakerasync",{"2":{"1350":1},"3":{"1350":1}}],["clearlocalcacheasync",{"3":{"1324":3}}],["clearly",{"3":{"1259":1,"1280":1,"1286":1,"1324":1,"1339":1,"1417":1,"1436":1,"1727":1,"2128":1}}],["clearmobileitems",{"3":{"1324":1}}],["clearallpools",{"3":{"1490":1}}],["clearauthentication",{"3":{"1429":1,"1436":1,"1488":1}}],["clearasync",{"3":{"1324":9,"1417":9}}],["clearable",{"3":{"248":1,"1301":1}}],["cleardraft",{"3":{"1389":1,"1395":4}}],["cleardirty",{"3":{"1324":1}}],["cleardomainevents",{"2":{"1231":1},"3":{"1231":1,"1237":4,"1286":4,"1810":1}}],["clearest",{"3":{"799":1,"1231":1,"1270":3,"1278":1,"1309":1,"1310":1,"1311":1,"1324":4,"1329":1,"1339":2,"1348":1,"1350":1,"1359":5,"1373":1,"1389":1,"1395":9,"1396":3,"1402":1,"1410":1,"1417":2,"1431":1,"1455":1,"2043":1}}],["cleared",{"3":{"0":2,"21":1,"24":1,"26":1,"219":2,"512":1,"749":1,"751":1,"783":1,"989":1,"1014":1,"1066":1,"1124":1,"1127":1,"1185":1,"1259":1,"1263":1,"1274":2,"1286":4,"1298":1,"1300":1,"1311":3,"1318":1,"1323":4,"1324":9,"1338":1,"1339":2,"1350":3,"1355":1,"1359":4,"1389":1,"1390":1,"1395":20,"1396":3,"1402":2,"1415":7,"1417":1,"1427":1,"1433":1,"1438":1,"1635":1,"1638":2,"1639":2,"1641":1,"1673":1,"1748":2,"1749":1,"1754":1,"1765":1,"1840":1,"1923":1}}],["cleartokensasync",{"2":{"511":1},"3":{"511":1,"1324":6,"1417":2,"1436":2}}],["cleartextport",{"3":{"95":1,"1331":1,"1339":3,"1442":1}}],["cleartext",{"3":{"31":1,"33":1,"53":2,"55":1,"60":1,"61":1,"91":5,"92":3,"94":1,"96":1,"97":7,"98":6,"99":3,"100":3,"101":1,"102":1,"103":2,"235":2,"749":2,"1067":3,"1309":1,"1311":5,"1312":6,"1323":3,"1326":1,"1328":2,"1331":1,"1334":2,"1337":1,"1339":17,"1379":3,"1380":1,"1396":3,"1415":3,"1430":2,"1438":1,"1441":5,"1447":4,"1449":1,"1467":5,"1476":1,"1488":1,"1496":1,"1656":2,"1663":1,"1670":1,"1825":1,"1898":2,"1904":1,"1946":1,"2009":1,"2019":1,"2023":1,"2025":11}}],["clears→nuget",{"3":{"1526":1}}],["clears",{"3":{"0":1,"21":1,"39":1,"123":1,"175":1,"207":1,"220":1,"246":1,"350":1,"355":1,"370":1,"391":1,"397":1,"402":1,"404":1,"556":1,"790":1,"854":1,"857":1,"881":1,"905":1,"997":1,"1091":1,"1184":2,"1229":1,"1231":1,"1237":2,"1259":3,"1267":1,"1273":1,"1286":10,"1289":1,"1293":1,"1294":1,"1300":9,"1301":1,"1309":2,"1311":3,"1323":6,"1324":37,"1333":1,"1339":4,"1349":1,"1350":3,"1359":9,"1373":1,"1380":4,"1390":1,"1395":46,"1396":11,"1402":4,"1407":1,"1410":1,"1415":7,"1417":8,"1433":1,"1436":15,"1438":1,"1443":1,"1488":1,"1526":1,"1563":1,"1611":1,"1634":1,"1639":2,"1641":7,"1747":1,"1748":1,"1751":1,"1753":1,"1764":1,"1765":2,"1767":5,"1769":1,"1776":1,"1903":1,"1918":1,"1920":1,"1922":2,"1923":1,"1924":1,"2010":1,"2079":1,"2157":1,"2160":1,"2191":1,"2197":2}}],["clearing",{"3":{"0":1,"279":1,"500":1,"512":1,"861":1,"1026":1,"1233":1,"1237":1,"1252":1,"1259":1,"1270":1,"1274":2,"1275":1,"1286":3,"1300":2,"1311":3,"1323":1,"1324":7,"1338":1,"1339":4,"1343":1,"1345":1,"1359":3,"1395":3,"1396":2,"1399":1,"1402":1,"1415":3,"1417":1,"1436":3,"1443":1,"1488":1,"1748":2,"1764":1,"1767":1,"1769":1,"1776":1,"2157":1}}],["clear",{"2":{"1840":1},"3":{"0":1,"21":1,"170":1,"207":1,"212":1,"219":1,"243":3,"258":2,"259":1,"261":1,"395":1,"440":1,"499":1,"507":1,"618":1,"707":1,"715":1,"716":1,"733":1,"766":1,"861":1,"972":1,"988":1,"1027":1,"1028":1,"1237":6,"1247":1,"1282":1,"1286":9,"1300":8,"1301":2,"1309":3,"1311":1,"1318":2,"1323":11,"1324":29,"1333":1,"1339":4,"1343":1,"1350":1,"1355":1,"1359":6,"1380":5,"1383":1,"1390":1,"1395":26,"1396":4,"1413":1,"1415":3,"1417":7,"1436":19,"1438":2,"1475":1,"1480":1,"1488":1,"1496":1,"1541":1,"1545":1,"1546":1,"1556":1,"1572":1,"1577":3,"1638":1,"1641":1,"1667":1,"1668":1,"1746":3,"1748":1,"1749":2,"1750":3,"1765":1,"1768":2,"1769":1,"1773":1,"1775":1,"1840":1,"1841":1,"1918":1,"1924":1,"2057":1,"2079":1,"2085":1,"2126":1,"2128":1,"2132":1,"2140":1,"2146":1}}],["cleanest",{"3":{"1323":1,"1324":1,"1350":1,"1369":1,"1416":1,"1417":1,"1800":1,"2032":1,"2034":1,"2078":1}}],["cleaned",{"3":{"19":1,"707":1,"1274":1,"1323":1,"1359":2,"2065":1}}],["cleans",{"3":{"1082":1,"1323":1,"1415":1,"1460":1}}],["cleanly",{"3":{"187":1,"234":1,"266":1,"310":1,"414":1,"641":1,"799":1,"1247":1,"1270":2,"1273":1,"1285":1,"1286":2,"1309":2,"1311":1,"1323":4,"1324":4,"1350":1,"1359":1,"1369":1,"1395":1,"1396":1,"1402":2,"1415":1,"1417":6,"1436":5,"1441":1,"1460":1,"1467":1,"1481":1,"1526":1,"1555":1,"1831":1,"1886":1,"1929":1,"1960":1,"2014":1,"2061":1,"2212":1,"2214":1,"2221":1}}],["cleanuptestcontext",{"3":{"1199":2,"1207":1}}],["cleanupintervalhours",{"2":{"1256":1,"1290":1,"2188":1},"3":{"905":1,"1042":1,"1256":1,"1259":2,"1286":3,"1290":1,"1300":1,"1845":1,"2188":1}}],["cleanup",{"0":{"1357":1},"3":{"0":1,"21":1,"24":1,"39":1,"40":1,"546":1,"699":1,"790":1,"905":1,"1082":1,"1086":2,"1116":1,"1118":1,"1134":1,"1247":1,"1259":4,"1270":3,"1276":1,"1286":13,"1303":1,"1311":2,"1323":3,"1324":2,"1345":1,"1350":6,"1355":1,"1357":2,"1359":13,"1396":1,"1411":1,"1415":13,"1417":1,"1436":4,"1467":1,"1475":6,"1488":1,"1496":3,"1634":3,"1639":1}}],["clean",{"0":{"1504":1,"1540":1},"2":{"1360":1},"3":{"0":1,"54":1,"58":1,"131":1,"135":2,"136":1,"248":1,"282":1,"373":1,"507":1,"512":1,"529":1,"546":1,"757":1,"759":1,"988":1,"1211":3,"1212":1,"1216":1,"1217":1,"1221":1,"1229":3,"1230":1,"1237":3,"1241":1,"1243":1,"1245":1,"1247":4,"1249":1,"1259":3,"1270":5,"1271":1,"1272":1,"1286":20,"1289":1,"1300":6,"1301":8,"1303":2,"1309":10,"1310":5,"1311":13,"1312":1,"1313":1,"1314":1,"1323":9,"1324":11,"1339":2,"1341":2,"1342":1,"1345":1,"1346":1,"1349":1,"1350":7,"1352":2,"1354":1,"1359":41,"1360":2,"1369":7,"1380":6,"1382":1,"1395":4,"1396":21,"1402":1,"1404":2,"1415":15,"1416":3,"1417":9,"1427":1,"1436":55,"1438":3,"1445":4,"1455":5,"1459":1,"1467":2,"1476":1,"1488":1,"1489":3,"1493":1,"1496":24,"1499":1,"1522":1,"1523":1,"1525":1,"1526":4,"1535":1,"1536":1,"1554":1,"1562":1,"1576":1,"1577":4,"1586":1,"1587":1,"1588":1,"1590":1,"1591":1,"1594":1,"1600":1,"1611":1,"1638":1,"1639":1,"1646":1,"1659":1,"1692":1,"1710":1,"1763":1,"1780":2,"1781":1,"1782":1,"1784":1,"1794":2,"1796":1,"1797":1,"1799":1,"1805":1,"1815":1,"1831":1,"1882":1,"1902":1,"1915":1,"1923":1,"1931":1,"1960":1,"1966":1,"2002":1,"2008":1,"2039":1,"2040":2,"2044":2,"2049":1,"2052":1,"2055":1,"2071":1,"2076":1,"2099":1,"2108":1,"2109":1,"2116":1,"2126":3,"2204":1,"2213":2,"2216":1,"2217":1,"2218":1,"2219":1,"2220":1,"2221":1,"2225":1,"2227":1,"2230":1,"2239":3,"2243":1,"2245":1}}],["cldr",{"3":{"1324":2,"1577":1}}],["cl",{"2":{"609":1,"1465":1,"1476":2},"3":{"609":1,"1465":1,"1467":2,"1476":2}}],["clrtype",{"2":{"697":1},"3":{"697":1,"1286":3}}],["clr",{"0":{"1241":1},"3":{"0":1,"24":2,"330":1,"471":1,"633":1,"636":1,"798":1,"799":1,"803":2,"805":1,"848":1,"849":1,"963":2,"964":2,"965":2,"1049":1,"1233":1,"1235":2,"1237":7,"1241":2,"1247":5,"1250":2,"1259":9,"1269":1,"1270":1,"1276":1,"1277":1,"1278":1,"1281":2,"1282":1,"1286":33,"1300":3,"1310":5,"1318":1,"1323":5,"1339":1,"1350":1,"1369":4,"1380":1,"1395":1,"1396":1,"1415":2,"1417":2,"1433":1,"1436":8,"1586":1,"1815":1,"1849":1,"1858":1,"1988":1,"1991":1,"2187":1,"2193":1}}],["cls=",{"3":{"1436":1}}],["cls",{"3":{"0":3,"860":1,"861":6,"863":2,"1212":1,"1433":3,"1436":9,"1526":1,"1560":1,"1582":1,"1591":1,"1596":1,"1671":1,"2079":1}}],["cliff",{"3":{"1892":1}}],["clipped",{"3":{"1653":1}}],["clip",{"3":{"1417":1,"1427":1}}],["clips",{"3":{"1286":1,"1669":1}}],["clipboard",{"2":{"412":1,"2116":1},"3":{"412":2,"1192":1,"1202":1,"1203":1,"1395":1,"1417":22,"1496":1,"1669":1,"2116":2}}],["climbs",{"3":{"1436":1}}],["climb",{"3":{"801":1}}],["climbing",{"3":{"799":1,"801":1,"1049":1,"1465":1}}],["clickjacks",{"3":{"2148":1}}],["clickjacking",{"3":{"1324":1,"1339":1,"2148":1}}],["clickandverifyasync",{"2":{"1671":1},"3":{"1433":1,"1436":1,"1488":1,"1671":1}}],["clickandwaitforurlasync",{"2":{"1433":1},"3":{"1433":1,"1436":1,"1488":1}}],["clickablecard",{"3":{"1324":1,"1436":1}}],["clickablecardtests",{"3":{"1199":1,"1207":2}}],["clickable",{"3":{"1324":1,"1359":1,"1436":2}}],["clickbuttonbytext",{"3":{"1432":1,"1436":2}}],["clicked",{"3":{"608":1,"1290":1,"1395":1,"1396":1,"1415":1,"1436":1,"2034":1,"2158":1}}],["clicks",{"3":{"517":1,"519":1,"743":1,"954":1,"1311":1,"1383":1,"1395":2,"1417":1,"1436":22,"1488":4,"1973":1}}],["clicking",{"3":{"156":1,"905":1,"1300":1,"1324":1,"1389":1,"1402":1,"1436":3,"1463":1,"1488":1}}],["click",{"3":{"0":2,"413":1,"519":1,"520":1,"743":1,"766":1,"861":1,"862":1,"905":1,"907":1,"1020":1,"1300":2,"1324":13,"1339":1,"1359":1,"1392":1,"1395":7,"1396":1,"1402":2,"1415":2,"1417":11,"1433":2,"1436":18,"1456":1,"1474":1,"1488":3,"1554":1,"1558":2,"1565":1,"1627":1,"1629":1,"1966":1,"2117":1,"2121":1}}],["cli",{"3":{"0":1,"665":1,"764":1,"766":1,"790":1,"1335":1,"1339":2,"1441":1,"1455":1,"1457":1,"1458":1,"1464":1,"1465":1,"1472":2,"1474":1,"1475":1,"1682":1,"2034":1,"2166":1}}],["cliente",{"3":{"1686":2}}],["clienteventformatvalidationtests",{"3":{"1199":1,"1207":2}}],["clientwidth",{"3":{"1436":1}}],["clientverdict",{"3":{"1436":1}}],["clientpasswordcomplexity",{"3":{"1436":2}}],["clientmodel",{"3":{"1427":1}}],["clientoptions",{"2":{"1420":1},"3":{"1420":1,"1427":2}}],["clienttoken",{"3":{"1359":1}}],["clientname",{"3":{"1339":1}}],["clientnameprefix",{"3":{"1339":1}}],["clientuseragent",{"3":{"1311":2,"1415":1}}],["clienturlvalidationtests",{"3":{"1199":1,"1207":2,"1438":2}}],["clientfactory",{"3":{"1213":1,"1312":2,"1436":5}}],["clientinterceptorcontext",{"3":{"1312":1}}],["clientip",{"3":{"1338":1,"1339":6,"1436":1,"1447":1}}],["clientipaddress",{"3":{"1311":2,"1415":1}}],["clientippartition",{"3":{"827":1,"1324":1,"1337":1,"1339":2}}],["clientid",{"2":{"351":1,"352":1},"3":{"0":1,"350":2,"351":1,"352":1,"1311":6,"1324":3,"1472":2,"1667":1,"1975":4,"1978":1}}],["clientconfigpath",{"3":{"1324":2}}],["clientconfigendpointtests",{"3":{"1199":2,"1207":4,"1324":4,"1437":1,"1487":2}}],["clientconfigendpointextensions",{"2":{"1324":1},"3":{"674":1,"677":1,"1184":1,"1198":1,"1199":3,"1203":1,"1207":2,"1208":1,"1324":17,"1496":1}}],["clientconfigbuilder",{"3":{"1198":1,"1199":2,"1203":1,"1207":2,"1324":11,"1496":1}}],["clientconfig",{"3":{"674":1,"1184":1,"1203":2,"1207":6,"1208":1,"1324":14,"1487":1}}],["clientcascade",{"2":{"1083":1},"3":{"0":1,"1083":1}}],["clientkey",{"2":{"231":1},"3":{"225":1,"231":1,"1247":1,"1309":1}}],["client",{"0":{"1378":1,"2019":1,"2030":1,"2032":1},"1":{"154":1,"155":1,"156":1,"157":1,"158":1,"159":1,"160":1,"161":1,"162":1,"504":1,"505":1,"506":1,"507":1,"508":1,"509":1,"510":1,"511":1,"512":1,"513":1,"514":1,"878":1,"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1},"2":{"407":1,"599":1,"601":1,"603":1,"667":1,"669":1,"1075":1,"1425":1,"1443":1,"1445":1,"1468":1,"1472":2,"1531":1,"1677":1,"2177":1},"3":{"0":40,"53":5,"59":2,"60":1,"66":1,"67":1,"68":1,"70":2,"71":2,"72":5,"73":4,"74":2,"86":1,"92":1,"95":1,"96":1,"97":3,"100":2,"103":1,"104":1,"109":8,"110":1,"111":3,"113":2,"122":1,"131":1,"134":1,"135":2,"136":1,"142":2,"154":1,"156":2,"157":8,"158":1,"159":1,"160":4,"166":2,"168":2,"175":4,"177":1,"179":2,"180":4,"182":1,"194":1,"206":2,"213":1,"225":2,"229":1,"230":1,"231":3,"233":1,"234":2,"235":3,"237":2,"241":2,"243":2,"245":4,"247":1,"258":3,"259":2,"261":3,"264":1,"265":5,"267":2,"272":1,"279":1,"280":3,"285":1,"332":1,"333":2,"335":1,"340":2,"341":6,"342":4,"343":3,"348":1,"350":1,"353":1,"379":2,"380":1,"381":2,"382":1,"383":1,"384":2,"387":1,"389":1,"398":1,"402":1,"404":1,"406":1,"407":3,"408":1,"418":1,"420":2,"423":1,"425":1,"430":1,"433":2,"434":4,"435":1,"436":1,"439":1,"440":1,"443":1,"447":2,"449":3,"455":1,"498":1,"499":2,"500":2,"501":2,"504":1,"506":1,"507":6,"508":1,"509":1,"510":1,"536":1,"545":1,"551":1,"554":1,"555":4,"556":7,"557":2,"558":1,"559":1,"560":1,"571":1,"572":5,"583":2,"591":1,"598":1,"599":5,"601":1,"603":1,"608":1,"612":1,"638":1,"640":2,"649":1,"650":1,"665":3,"667":1,"669":1,"674":2,"677":1,"684":2,"740":1,"741":1,"742":1,"743":2,"749":2,"750":1,"773":1,"774":1,"789":1,"792":1,"795":1,"817":1,"822":1,"826":3,"827":10,"829":1,"833":2,"837":1,"860":3,"861":1,"865":2,"876":5,"878":1,"879":1,"880":6,"881":13,"882":6,"883":2,"884":1,"885":4,"897":1,"904":1,"905":2,"907":2,"910":2,"914":1,"922":1,"954":1,"988":1,"996":2,"998":1,"1016":1,"1017":1,"1018":5,"1020":1,"1034":2,"1035":2,"1050":1,"1052":1,"1059":1,"1067":2,"1075":1,"1082":1,"1085":1,"1089":1,"1090":1,"1091":6,"1092":3,"1093":3,"1112":1,"1116":1,"1124":4,"1125":1,"1126":2,"1182":1,"1183":2,"1184":5,"1185":1,"1186":1,"1187":1,"1189":1,"1192":2,"1199":1,"1202":1,"1203":2,"1206":1,"1207":3,"1212":12,"1213":1,"1217":1,"1218":1,"1225":3,"1227":1,"1228":1,"1229":9,"1237":2,"1239":1,"1241":5,"1242":2,"1243":1,"1244":1,"1247":29,"1259":1,"1268":1,"1270":3,"1271":4,"1285":2,"1286":24,"1287":1,"1289":1,"1290":2,"1294":1,"1295":1,"1298":1,"1300":53,"1301":4,"1302":2,"1305":1,"1306":1,"1308":2,"1309":53,"1311":98,"1312":36,"1314":2,"1315":1,"1316":2,"1323":34,"1324":261,"1327":1,"1328":2,"1332":2,"1333":1,"1334":1,"1335":1,"1336":3,"1337":1,"1338":3,"1339":69,"1347":3,"1349":1,"1350":28,"1352":3,"1353":1,"1355":2,"1357":1,"1358":3,"1359":77,"1360":1,"1365":2,"1366":5,"1369":13,"1370":1,"1371":1,"1378":3,"1380":37,"1384":4,"1387":1,"1388":1,"1389":2,"1395":100,"1396":110,"1397":1,"1402":48,"1404":1,"1406":2,"1408":1,"1411":1,"1413":1,"1414":2,"1415":58,"1416":40,"1417":34,"1420":2,"1421":5,"1422":2,"1423":1,"1425":4,"1427":34,"1429":2,"1432":2,"1433":1,"1435":4,"1436":84,"1437":5,"1438":28,"1441":6,"1443":3,"1444":1,"1445":2,"1447":8,"1455":4,"1456":1,"1460":1,"1461":1,"1467":16,"1468":1,"1472":5,"1477":1,"1487":1,"1488":5,"1489":1,"1490":1,"1491":3,"1492":2,"1496":7,"1503":1,"1526":13,"1531":3,"1532":3,"1534":2,"1545":1,"1554":1,"1556":2,"1560":3,"1561":2,"1563":5,"1564":1,"1572":3,"1577":4,"1585":1,"1586":2,"1589":1,"1591":4,"1595":1,"1596":5,"1597":2,"1626":1,"1627":2,"1631":4,"1638":1,"1639":5,"1640":4,"1641":6,"1651":1,"1653":5,"1656":2,"1662":2,"1663":4,"1666":1,"1667":2,"1668":3,"1669":1,"1670":1,"1671":1,"1677":1,"1685":3,"1702":5,"1707":1,"1720":1,"1749":2,"1765":1,"1773":2,"1790":4,"1805":2,"1815":1,"1831":2,"1840":1,"1856":1,"1868":1,"1870":1,"1871":2,"1872":3,"1873":2,"1874":2,"1875":1,"1876":4,"1877":2,"1882":2,"1884":1,"1905":1,"1906":4,"1908":7,"1909":2,"1911":4,"1912":3,"1918":1,"1921":1,"1925":2,"1926":2,"1927":3,"1930":1,"1934":1,"1935":1,"1936":2,"1937":1,"1942":2,"1944":3,"1945":2,"1946":1,"1949":1,"1950":2,"1966":3,"1968":3,"1972":4,"1973":1,"1975":4,"1976":1,"1979":2,"1980":2,"1981":1,"1982":2,"1984":1,"1985":1,"1986":1,"1987":1,"1988":1,"1990":1,"1998":1,"2000":2,"2001":1,"2002":2,"2009":1,"2010":1,"2015":1,"2017":2,"2018":1,"2019":3,"2022":1,"2023":1,"2024":4,"2028":2,"2029":2,"2030":4,"2031":3,"2032":1,"2038":3,"2043":1,"2044":1,"2055":4,"2074":1,"2077":1,"2078":2,"2079":1,"2080":2,"2081":1,"2082":1,"2083":2,"2086":1,"2094":1,"2104":2,"2107":1,"2124":2,"2126":4,"2129":3,"2130":2,"2131":2,"2132":1,"2133":3,"2147":1,"2151":1,"2156":2,"2158":1,"2163":1,"2166":1,"2167":1,"2170":2,"2173":1,"2174":1,"2175":1,"2177":1,"2180":1,"2232":5,"2239":1}}],["clientstreamingserverhandler",{"3":{"1312":1}}],["clientstate",{"3":{"1311":4}}],["clientstateitemkey",{"3":{"1311":2}}],["clientstamped",{"3":{"1175":2,"1274":1,"1280":1,"1286":5}}],["clientsecret",{"3":{"350":1,"1311":1,"1975":1}}],["clientsetnull",{"2":{"1082":1,"1085":1},"3":{"0":1,"1082":1,"1085":1}}],["clients",{"0":{"100":1},"2":{"1330":1},"3":{"0":5,"53":2,"57":1,"59":2,"60":1,"63":1,"66":1,"74":1,"97":1,"98":3,"135":1,"157":1,"174":1,"225":2,"314":1,"380":1,"383":1,"387":1,"560":1,"583":1,"586":1,"587":1,"638":2,"640":1,"645":1,"657":1,"773":1,"823":1,"881":1,"882":1,"1077":1,"1091":2,"1184":1,"1192":2,"1202":1,"1203":1,"1212":3,"1213":1,"1247":1,"1286":4,"1289":1,"1296":1,"1300":7,"1301":1,"1302":1,"1307":5,"1309":13,"1311":13,"1312":1,"1316":1,"1320":1,"1323":14,"1324":8,"1326":1,"1329":3,"1330":1,"1339":9,"1350":1,"1353":1,"1359":4,"1379":1,"1380":4,"1395":4,"1396":15,"1399":1,"1402":4,"1406":1,"1410":1,"1414":1,"1415":9,"1418":1,"1427":1,"1436":8,"1438":2,"1441":4,"1443":1,"1447":4,"1450":1,"1467":1,"1490":1,"1496":1,"1509":1,"1538":1,"1555":1,"1591":1,"1630":2,"1631":2,"1638":1,"1639":4,"1640":3,"1641":11,"1660":2,"1668":1,"1669":2,"1671":1,"1772":1,"1790":2,"1906":1,"1907":1,"1911":1,"1912":2,"1932":1,"1935":3,"1944":1,"1946":1,"1950":1,"2009":1,"2019":1,"2023":2,"2025":2,"2028":1,"2031":1,"2037":1,"2038":1,"2083":1,"2111":1,"2170":1,"2214":1,"2221":2,"2227":1,"2232":1}}],["clobbering",{"3":{"1324":1,"1870":1}}],["clobbers",{"3":{"1324":1,"2072":1}}],["clobber",{"3":{"1323":2,"1359":1,"1415":1,"1429":1,"1430":1,"1436":2,"1490":1}}],["cloning",{"3":{"820":1,"1286":1}}],["cloned",{"3":{"1091":1,"1436":1}}],["clones",{"3":{"698":1,"1286":5,"1427":1,"1453":1,"1851":1}}],["clone",{"3":{"468":1,"657":1,"1286":4,"1359":1,"1422":1,"1464":1,"2003":1,"2004":1,"2174":1}}],["clothes",{"3":{"341":1}}],["cloud",{"0":{"1446":1,"2229":1},"3":{"0":1,"175":1,"177":1,"219":1,"397":1,"405":1,"641":1,"749":1,"972":1,"1011":1,"1043":1,"1124":1,"1213":1,"1309":1,"1311":2,"1338":1,"1339":2,"1350":1,"1364":3,"1369":4,"1439":1,"1442":1,"1450":1,"1457":1,"1467":2,"1554":1,"1568":1,"1570":1,"1575":1,"1577":1,"1670":1,"1707":3,"1936":1,"2010":2,"2013":1,"2027":1,"2128":1,"2154":2,"2156":1,"2160":1,"2185":2,"2193":1,"2212":1,"2213":3,"2214":1,"2216":1,"2217":1,"2218":2,"2219":1,"2220":2,"2221":1,"2228":1,"2238":1,"2239":2,"2240":2}}],["closures",{"3":{"1309":2,"1436":1}}],["closure",{"3":{"122":1,"135":2,"465":1,"466":1,"1067":1,"1222":1,"1229":2,"1247":3,"1259":1,"1270":1,"1286":2,"1301":1,"1311":1,"1323":2,"1324":1,"1339":2,"1359":1,"1395":1,"1417":1,"1436":8}}],["closing",{"3":{"0":3,"122":1,"217":1,"252":1,"254":1,"265":1,"390":1,"392":1,"458":1,"492":1,"523":1,"603":1,"607":1,"664":1,"699":1,"764":1,"856":1,"872":1,"897":1,"920":2,"1033":1,"1052":1,"1108":1,"1229":1,"1239":1,"1247":2,"1258":1,"1270":2,"1286":1,"1300":3,"1311":2,"1323":5,"1324":4,"1340":1,"1342":1,"1350":2,"1359":7,"1380":1,"1392":1,"1395":4,"1402":5,"1415":3,"1417":1,"1427":1,"1436":9,"1450":1,"1455":1,"1467":1,"1496":1,"1524":1,"1577":2,"1637":1,"1641":1,"1702":1,"1845":1,"1851":1,"1923":1,"1946":1,"2034":1,"2079":1,"2085":1,"2163":1}}],["closepollasync",{"3":{"1402":2}}],["closeasync",{"3":{"1396":2,"1402":1}}],["closely",{"3":{"1270":1,"1300":1,"1311":2,"1320":1,"1339":1,"1350":1,"1355":1,"1359":3,"1395":1,"1396":1,"1406":1,"1417":2,"1421":1,"1436":2,"1441":1,"1455":1,"1456":1}}],["closelivepollcommand",{"3":{"1199":6,"1203":1,"1207":2,"1402":11}}],["closelivepollhandlertests",{"3":{"1199":1,"1207":3}}],["closelivepollhandler",{"2":{"522":1,"1399":1},"3":{"522":2,"523":2,"1100":2,"1199":2,"1203":1,"1207":2,"1270":2,"1350":2,"1396":4,"1399":6,"1402":17}}],["closer",{"3":{"915":1,"1780":1}}],["close",{"3":{"0":5,"21":1,"26":1,"99":1,"122":1,"197":1,"265":1,"273":1,"320":1,"329":1,"335":1,"342":1,"350":1,"390":1,"491":1,"507":1,"522":1,"523":1,"558":1,"642":1,"832":1,"837":1,"840":1,"848":1,"916":1,"921":1,"926":1,"988":2,"996":1,"1061":1,"1075":1,"1100":1,"1108":1,"1123":1,"1124":2,"1125":1,"1128":1,"1203":2,"1207":4,"1247":1,"1259":1,"1262":1,"1265":1,"1270":9,"1271":1,"1277":1,"1284":1,"1286":3,"1292":1,"1300":5,"1301":1,"1309":1,"1311":1,"1312":2,"1323":3,"1324":17,"1338":1,"1339":1,"1350":1,"1355":1,"1359":9,"1379":1,"1380":1,"1383":1,"1395":7,"1396":9,"1398":1,"1399":1,"1401":1,"1402":38,"1415":4,"1416":1,"1427":1,"1436":12,"1438":1,"1441":1,"1448":1,"1449":1,"1459":2,"1467":1,"1488":2,"1489":1,"1490":1,"1496":1,"1526":1,"1534":2,"1576":1,"1582":1,"1607":1,"1632":2,"1741":1,"1872":1,"1902":1,"1934":1,"1977":1,"1978":1,"1987":1,"1991":1,"1998":1,"2000":3,"2055":1,"2085":1,"2202":1}}],["closedrawer",{"3":{"1591":1}}],["closedgenericarguments",{"3":{"1436":1}}],["closedhandlertype",{"3":{"1259":3}}],["closed",{"0":{"870":1,"1235":1},"1":{"1105":1,"1106":1,"1107":1,"1108":1,"1109":1,"1110":1,"1111":1,"1112":1},"2":{"384":1},"3":{"0":18,"24":1,"76":1,"80":2,"109":1,"122":1,"135":1,"195":1,"201":1,"212":1,"214":1,"215":1,"216":1,"224":1,"233":2,"235":2,"237":2,"267":1,"275":1,"298":1,"305":1,"316":1,"324":1,"325":2,"326":1,"341":1,"368":1,"373":1,"374":1,"384":1,"414":1,"421":1,"423":1,"424":1,"459":2,"461":1,"463":1,"501":1,"512":1,"528":1,"530":1,"531":1,"535":1,"545":1,"549":1,"562":1,"566":1,"567":1,"572":2,"574":2,"591":1,"599":1,"624":1,"626":1,"628":1,"629":1,"657":1,"659":1,"698":1,"699":1,"700":1,"743":1,"759":1,"768":1,"776":2,"784":1,"833":1,"834":1,"840":1,"867":3,"868":1,"871":1,"872":2,"873":1,"906":1,"912":1,"922":4,"926":1,"963":1,"964":3,"988":1,"1018":1,"1042":1,"1050":2,"1052":1,"1059":1,"1091":2,"1105":1,"1108":3,"1169":1,"1176":1,"1177":1,"1178":1,"1212":1,"1223":1,"1229":7,"1234":1,"1235":2,"1237":14,"1240":1,"1241":3,"1247":13,"1259":5,"1263":2,"1264":2,"1265":2,"1267":2,"1268":1,"1270":44,"1271":5,"1273":1,"1275":1,"1278":1,"1280":1,"1286":21,"1297":1,"1300":26,"1301":1,"1307":1,"1309":8,"1310":2,"1311":29,"1312":1,"1316":1,"1318":1,"1323":21,"1324":22,"1334":1,"1339":4,"1350":14,"1352":2,"1353":1,"1354":2,"1358":1,"1359":119,"1366":1,"1369":1,"1372":1,"1380":25,"1383":1,"1384":1,"1391":1,"1395":27,"1396":20,"1398":1,"1399":1,"1401":1,"1402":32,"1403":1,"1404":1,"1406":1,"1415":24,"1417":4,"1421":1,"1422":1,"1423":2,"1427":7,"1436":33,"1438":6,"1442":1,"1447":1,"1453":1,"1454":1,"1455":2,"1459":3,"1467":1,"1489":1,"1496":5,"1526":4,"1531":1,"1575":2,"1577":1,"1582":1,"1586":2,"1590":1,"1591":4,"1597":1,"1600":1,"1630":1,"1632":2,"1653":1,"1670":1,"1686":2,"1701":1,"1702":2,"1707":1,"1771":1,"1815":1,"1823":2,"1830":1,"1851":2,"1912":1,"1927":1,"1962":1,"1963":1,"1984":1,"1985":1,"1989":1,"1995":1,"1996":1,"1997":1,"2000":1,"2038":1,"2046":1,"2047":1,"2136":1,"2140":1,"2150":1,"2152":1,"2153":2,"2161":2,"2172":1,"2175":1,"2179":1,"2180":2,"2184":1,"2190":1,"2193":1,"2201":2,"2203":1,"2232":1}}],["closesentinel",{"3":{"1324":1}}],["closest",{"3":{"1263":1,"1270":3,"1311":1,"1323":1,"1821":1}}],["closes",{"3":{"0":6,"40":1,"42":1,"69":1,"110":1,"121":1,"197":1,"201":1,"208":1,"230":1,"265":1,"284":2,"305":1,"318":1,"331":1,"350":1,"360":1,"365":1,"381":1,"387":1,"418":1,"459":1,"474":1,"538":1,"539":1,"599":1,"609":2,"612":1,"702":2,"741":1,"755":1,"757":1,"798":1,"827":2,"829":2,"832":1,"839":1,"893":1,"916":1,"1018":1,"1028":1,"1053":1,"1124":2,"1125":1,"1128":1,"1229":2,"1237":2,"1241":1,"1247":7,"1253":1,"1259":8,"1267":1,"1270":5,"1271":1,"1278":1,"1281":1,"1286":10,"1300":10,"1301":1,"1307":1,"1309":1,"1311":5,"1316":2,"1323":9,"1324":6,"1327":1,"1334":1,"1337":1,"1339":9,"1350":3,"1357":1,"1359":23,"1380":3,"1389":1,"1395":7,"1396":7,"1399":1,"1402":7,"1415":7,"1416":2,"1417":4,"1420":1,"1430":2,"1435":1,"1436":31,"1438":4,"1445":1,"1446":1,"1453":1,"1456":1,"1459":1,"1465":1,"1467":4,"1474":1,"1476":1,"1488":1,"1492":2,"1582":1,"1600":1,"1632":1,"1641":1,"1651":1,"1790":1,"1843":1,"1844":1,"1871":1,"1886":1,"1897":1,"1899":1,"1909":1,"1920":1,"1922":1,"1929":1,"1930":1,"1945":1,"1950":1,"1968":1,"1972":2,"1979":1,"2000":1,"2002":2,"2024":1,"2027":1,"2036":1,"2056":1,"2065":1,"2069":1,"2143":1,"2146":1,"2178":1}}],["clockreadfixtures",{"3":{"1207":21,"1436":30}}],["clockreadtests",{"3":{"1161":3,"1199":2,"1207":5,"1431":1,"1436":29,"1577":1,"1591":1,"1599":1}}],["clockreadtestsbase",{"3":{"0":1,"1161":4,"1162":1,"1199":3,"1207":2,"1212":1,"1431":3,"1436":13}}],["clockreadsin",{"3":{"1436":1}}],["clockreads",{"3":{"1160":1,"1161":3,"1162":2,"1163":2,"1207":1,"1431":1,"1436":5}}],["clocks",{"3":{"1028":1,"1324":1,"1396":1}}],["clock",{"0":{"1948":1},"1":{"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1},"3":{"0":4,"19":1,"39":1,"62":1,"178":1,"255":1,"305":1,"386":2,"387":2,"390":1,"392":2,"396":1,"590":1,"640":1,"711":1,"724":1,"726":1,"727":1,"860":1,"905":1,"954":1,"1028":1,"1029":1,"1090":1,"1117":1,"1158":1,"1160":2,"1161":3,"1163":4,"1212":2,"1237":1,"1249":1,"1254":1,"1256":3,"1259":10,"1269":1,"1286":7,"1293":1,"1300":26,"1306":1,"1309":7,"1311":5,"1317":1,"1323":7,"1324":13,"1332":1,"1339":2,"1342":1,"1347":1,"1348":1,"1350":11,"1356":2,"1359":15,"1364":1,"1369":2,"1379":1,"1380":1,"1383":2,"1393":1,"1395":23,"1396":23,"1402":4,"1415":1,"1417":3,"1418":1,"1422":1,"1425":1,"1427":2,"1431":2,"1436":42,"1438":9,"1467":1,"1486":1,"1487":1,"1492":2,"1496":2,"1535":1,"1577":2,"1582":1,"1585":1,"1586":2,"1599":1,"1600":2,"1640":1,"1664":1,"1671":1,"1731":1,"1750":1,"1922":1,"1937":1,"1948":1,"1950":2,"2047":1,"2066":1,"2154":1,"2174":1,"2180":1,"2183":1,"2232":1}}],["clarify",{"3":{"1636":1,"1755":1,"1776":3}}],["clarification",{"3":{"1639":1,"1641":2}}],["clarifications",{"0":{"1639":1},"3":{"337":1,"1639":2}}],["clarifies",{"3":{"1639":18,"1640":1}}],["clarified",{"3":{"3":1,"45":1}}],["clarity",{"3":{"1369":1,"1417":1}}],["clash",{"3":{"1237":1,"1350":1}}],["class=",{"3":{"1416":1}}],["classname",{"3":{"1286":6}}],["classreference",{"2":{"1319":1,"1354":1},"3":{"1199":27,"1203":16,"1207":16,"1311":5,"1313":1,"1319":2,"1323":16,"1341":1,"1350":6,"1354":1,"1359":5,"1369":4,"1377":1,"1380":3,"1396":17,"1404":1,"1415":23,"1496":1}}],["classifiable",{"3":{"1229":1}}],["classifications",{"3":{"1243":1,"1247":2,"1662":1}}],["classification",{"0":{"1219":1},"3":{"688":1,"1091":1,"1217":1,"1219":2,"1221":1,"1225":1,"1229":4,"1230":1,"1247":7,"1270":2,"1271":4,"1286":6,"1310":7,"1350":1,"1359":8,"1369":1,"1395":1,"1396":2,"1433":1,"1436":2,"1488":1,"1517":1,"1630":3,"1637":3,"1669":1,"1764":1,"1769":1,"1805":2,"1867":1,"2062":1,"2063":1}}],["classifiers",{"3":{"1368":1}}],["classifier",{"3":{"593":1,"1019":1,"1243":1,"1286":3,"1323":2,"1339":1,"1402":1,"1423":1,"1436":7,"1453":2,"1455":5,"1488":1,"1492":1,"1496":18,"1531":1,"1630":1}}],["classifies",{"0":{"1222":1},"3":{"100":1,"564":1,"626":1,"893":1,"988":1,"1229":3,"1245":1,"1247":2,"1284":2,"1286":9,"1300":1,"1310":1,"1311":3,"1324":1,"1339":1,"1359":3,"1395":1,"1396":2,"1425":1,"1436":3,"1453":1,"1455":1,"1840":1,"1868":1}}],["classified",{"3":{"10":2,"109":1,"110":1,"539":1,"1006":1,"1091":1,"1184":1,"1229":1,"1260":1,"1266":1,"1270":1,"1286":1,"1289":1,"1300":2,"1304":1,"1309":1,"1310":1,"1324":1,"1359":3,"1391":1,"1395":1,"1443":1,"1567":1}}],["classifystatus",{"3":{"1359":11}}],["classifyfailure",{"3":{"1300":3}}],["classifyentries",{"3":{"1286":4}}],["classifynavigationproperty",{"3":{"1247":1,"1310":1}}],["classifying",{"3":{"109":1,"110":1,"1247":1,"1286":5,"1310":1,"1350":1,"1359":2,"1393":1,"1396":2,"1436":1,"1445":1,"2069":1}}],["classify",{"0":{"1865":1},"2":{"1495":1,"1501":1},"3":{"0":1,"107":1,"620":1,"692":1,"1191":1,"1222":2,"1229":1,"1247":1,"1286":5,"1300":1,"1324":1,"1339":1,"1350":2,"1359":1,"1395":1,"1433":1,"1436":4,"1467":1,"1488":1,"1495":1,"1496":24,"1498":1,"1501":1,"1524":1,"1526":1,"1807":1,"1865":1,"1940":1}}],["classic",{"3":{"0":3,"31":1,"208":1,"481":1,"801":1,"828":1,"963":1,"979":1,"980":7,"981":1,"982":1,"1212":2,"1229":1,"1237":2,"1247":1,"1270":1,"1300":3,"1311":4,"1323":1,"1324":5,"1339":1,"1350":1,"1359":1,"1380":1,"1395":2,"1396":1,"1415":1,"1416":1,"1417":5,"1427":1,"1436":7,"1441":1,"1467":2,"1488":1,"1585":1,"1652":1,"1664":1,"1796":1,"1897":1,"1993":1,"2007":1,"2045":1}}],["classes",{"0":{"1988":1},"1":{"1056":1,"1057":1,"1058":1,"1059":1,"1060":1,"1061":1,"1062":1,"1063":1},"3":{"0":10,"4":1,"7":1,"47":1,"48":1,"130":1,"132":1,"135":1,"136":1,"137":1,"140":1,"160":1,"314":1,"511":1,"513":1,"591":1,"616":5,"618":2,"657":1,"672":1,"674":4,"676":1,"677":1,"728":1,"794":1,"926":1,"954":2,"960":1,"980":1,"983":1,"1056":1,"1117":1,"1168":2,"1175":1,"1192":2,"1202":1,"1203":1,"1211":1,"1212":4,"1214":1,"1229":1,"1234":1,"1237":5,"1247":1,"1259":1,"1270":1,"1271":6,"1279":1,"1286":12,"1300":4,"1309":2,"1310":2,"1311":1,"1312":3,"1316":1,"1321":1,"1323":11,"1324":7,"1330":1,"1339":4,"1340":1,"1350":5,"1352":1,"1359":9,"1360":1,"1363":1,"1369":3,"1385":1,"1395":2,"1396":5,"1399":1,"1416":2,"1417":5,"1428":3,"1431":1,"1433":1,"1436":50,"1437":1,"1438":3,"1443":1,"1450":1,"1487":1,"1488":5,"1489":7,"1496":18,"1497":4,"1498":2,"1500":1,"1523":1,"1525":1,"1526":3,"1531":1,"1534":1,"1535":1,"1538":1,"1539":1,"1551":1,"1577":3,"1582":1,"1583":1,"1588":1,"1590":1,"1591":5,"1600":2,"1653":1,"1654":1,"1662":2,"1666":1,"1685":1,"1782":1,"1808":1,"1810":1,"1811":1,"1850":1,"1854":1,"1931":2,"1951":2,"1956":1,"1957":1,"1958":1,"1959":1,"1986":1,"1991":1,"2042":2,"2055":1,"2060":1,"2070":1,"2080":1,"2088":1,"2101":1,"2102":1,"2104":1,"2114":1,"2126":1,"2138":2,"2178":1,"2203":1,"2232":1}}],["class",{"0":{"1273":1,"1361":1,"1849":1},"1":{"1031":1,"1032":1,"1033":1,"1034":1,"1035":1,"1036":1,"1037":1,"1038":1},"2":{"1319":1,"1619":1,"1994":1},"3":{"0":11,"5":1,"6":1,"7":1,"11":1,"24":1,"45":2,"47":3,"53":1,"138":1,"150":1,"164":1,"167":1,"189":1,"214":1,"216":1,"230":1,"233":3,"235":3,"236":1,"237":1,"243":1,"253":1,"258":3,"264":1,"265":1,"283":2,"305":2,"318":3,"322":1,"326":1,"340":1,"343":1,"350":1,"364":1,"412":1,"428":1,"446":1,"448":2,"450":1,"455":1,"459":2,"463":3,"464":1,"492":1,"497":1,"511":1,"516":1,"523":1,"524":1,"534":1,"536":2,"537":2,"543":1,"545":2,"554":1,"555":1,"557":1,"573":1,"610":1,"616":1,"618":5,"619":1,"621":1,"633":2,"635":1,"639":1,"640":2,"657":2,"659":3,"660":1,"672":1,"674":5,"675":3,"676":1,"677":1,"681":1,"682":1,"715":1,"720":1,"724":2,"725":2,"726":2,"733":2,"741":1,"776":1,"793":2,"794":1,"797":1,"798":1,"801":1,"805":1,"811":1,"819":1,"821":1,"826":1,"827":1,"831":1,"832":2,"847":1,"855":1,"861":1,"862":1,"880":1,"882":1,"889":1,"893":1,"905":1,"910":1,"923":1,"926":1,"955":2,"963":2,"974":1,"979":1,"980":2,"989":2,"990":1,"1006":1,"1031":1,"1032":1,"1033":2,"1034":3,"1035":2,"1048":1,"1051":1,"1061":2,"1067":1,"1075":1,"1083":1,"1085":1,"1087":1,"1102":1,"1133":1,"1137":1,"1146":1,"1156":1,"1167":3,"1168":3,"1170":2,"1171":1,"1173":1,"1175":2,"1176":2,"1177":2,"1179":1,"1185":1,"1187":1,"1191":1,"1201":1,"1203":1589,"1204":1,"1205":1,"1207":4315,"1212":7,"1214":2,"1215":1,"1221":1,"1229":14,"1231":1,"1233":1,"1234":1,"1236":1,"1237":54,"1239":3,"1240":1,"1241":1,"1244":1,"1247":85,"1249":1,"1257":1,"1258":1,"1259":68,"1262":1,"1265":1,"1269":1,"1270":76,"1271":32,"1273":4,"1275":1,"1276":1,"1277":1,"1279":1,"1280":3,"1286":321,"1288":1,"1291":2,"1295":1,"1297":1,"1300":180,"1301":26,"1304":1,"1305":1,"1309":93,"1310":14,"1311":163,"1312":25,"1316":1,"1317":1,"1319":2,"1320":2,"1321":2,"1322":1,"1323":186,"1324":327,"1326":1,"1329":1,"1330":1,"1339":156,"1344":2,"1346":1,"1350":123,"1352":2,"1353":1,"1354":1,"1359":582,"1361":3,"1363":2,"1365":1,"1366":2,"1369":86,"1371":1,"1372":2,"1373":5,"1374":1,"1375":1,"1376":1,"1377":1,"1380":114,"1381":1,"1383":1,"1385":1,"1392":1,"1395":272,"1396":340,"1402":108,"1404":1,"1405":2,"1406":4,"1407":1,"1410":1,"1412":1,"1414":1,"1415":208,"1416":45,"1417":165,"1427":30,"1429":2,"1432":2,"1433":1,"1436":848,"1437":4,"1438":5,"1441":3,"1443":3,"1444":1,"1453":3,"1454":1,"1467":4,"1486":1,"1488":10,"1489":9,"1491":3,"1496":9,"1498":1,"1510":2,"1526":3,"1534":2,"1538":1,"1566":1,"1568":1,"1571":1,"1577":3,"1591":1,"1597":1,"1613":1,"1619":1,"1641":2,"1651":2,"1654":3,"1664":2,"1665":1,"1668":1,"1672":1,"1675":1,"1685":2,"1686":1,"1697":1,"1701":1,"1713":1,"1714":1,"1719":1,"1734":1,"1776":1,"1789":1,"1794":1,"1796":1,"1805":3,"1808":1,"1810":2,"1811":2,"1813":1,"1826":1,"1831":1,"1834":2,"1847":1,"1849":5,"1853":1,"1854":2,"1856":5,"1857":4,"1861":1,"1862":2,"1865":1,"1868":1,"1889":1,"1896":1,"1909":2,"1941":1,"1944":1,"1952":1,"1955":3,"1956":2,"1958":3,"1962":1,"1987":1,"1994":1,"1995":5,"1996":1,"1997":1,"2002":1,"2012":1,"2017":1,"2042":1,"2043":1,"2054":1,"2058":2,"2066":1,"2070":1,"2073":1,"2074":1,"2077":1,"2087":1,"2099":1,"2104":1,"2105":1,"2107":1,"2116":1,"2122":1,"2131":2,"2132":1,"2137":1,"2139":1,"2150":1,"2156":1,"2163":3,"2165":5,"2167":2,"2169":1,"2178":1,"2187":1,"2194":1,"2198":1,"2199":1,"2215":1,"2232":4}}],["clamppagesize",{"3":{"1324":1}}],["clamping",{"3":{"740":1,"1017":1,"1396":4,"1417":2,"1427":2,"2174":1}}],["clamp",{"2":{"1242":1},"3":{"330":1,"331":1,"337":1,"744":1,"1019":1,"1229":1,"1242":1,"1243":1,"1244":2,"1247":11,"1292":1,"1300":1,"1306":1,"1309":2,"1311":4,"1369":3,"1387":1,"1395":2,"1396":7,"1415":5,"1427":1,"1438":1,"1496":1,"1577":3,"1586":1,"1986":1,"1988":1,"1991":2,"2180":1}}],["clamped",{"3":{"279":1,"337":1,"682":1,"741":1,"1018":2,"1090":1,"1094":1,"1228":1,"1229":3,"1243":1,"1247":2,"1256":1,"1259":2,"1275":1,"1292":1,"1309":2,"1311":4,"1324":2,"1366":1,"1396":12,"1415":2,"1417":1,"1418":1,"1435":1,"1438":3,"1817":1,"2174":1}}],["clamps",{"3":{"0":1,"330":3,"337":1,"549":1,"741":1,"744":1,"1018":1,"1091":1,"1092":1,"1094":1,"1242":1,"1245":1,"1247":1,"1286":1,"1300":1,"1306":1,"1311":4,"1324":1,"1366":1,"1369":1,"1380":2,"1395":3,"1396":2,"1406":1,"1409":1,"1415":1,"1422":1,"1427":3,"1988":1,"2201":1}}],["claude",{"0":{"1459":1},"2":{"636":1,"1003":1,"1190":1,"1451":1,"1459":1,"1460":1,"1571":1,"1658":1,"2049":1,"2060":1},"3":{"1":1,"469":1,"550":1,"599":3,"626":2,"636":1,"766":1,"1003":1,"1004":2,"1018":1,"1190":1,"1192":1,"1211":1,"1270":2,"1286":3,"1300":6,"1311":1,"1323":1,"1339":3,"1366":1,"1369":1,"1380":1,"1415":1,"1436":15,"1440":1,"1451":2,"1452":1,"1453":2,"1454":1,"1459":32,"1460":4,"1461":2,"1467":2,"1470":1,"1496":10,"1526":1,"1571":1,"1576":1,"1577":5,"1582":1,"1591":3,"1617":1,"1649":1,"1658":2,"1838":1,"1895":1,"2041":1,"2049":1,"2060":1,"2213":1,"2219":1,"2220":1,"2225":1,"2239":4,"2243":1}}],["clauses",{"3":{"699":1,"1229":1,"1238":1,"1245":1,"1247":2,"1270":1,"1286":3,"1324":2,"1339":2,"1359":3,"1395":1,"1415":2,"1425":1,"1436":1,"1815":2}}],["clause",{"3":{"0":3,"109":1,"110":1,"145":1,"341":1,"342":1,"343":1,"697":1,"703":1,"887":1,"889":2,"890":3,"892":1,"988":2,"1073":1,"1075":1,"1132":1,"1220":1,"1229":1,"1237":2,"1241":2,"1247":5,"1263":1,"1271":3,"1274":1,"1281":1,"1286":14,"1300":1,"1309":1,"1311":2,"1323":2,"1324":2,"1339":2,"1350":2,"1359":11,"1369":1,"1396":1,"1402":1,"1412":1,"1415":7,"1416":5,"1427":1,"1436":4,"1456":1,"1662":1,"1813":2,"1817":1,"1844":1,"1859":1,"1869":1,"1872":1,"1877":1,"1987":1,"1991":1,"2232":2}}],["claimvalue",{"3":{"1415":1}}],["claimbaseduseridprovidertests",{"3":{"1199":1,"1207":4}}],["claimbaseduseridprovider",{"2":{"1296":1,"1935":1},"3":{"1199":3,"1203":1,"1207":2,"1287":1,"1296":3,"1300":9,"1309":1,"1935":1}}],["claimdueasync",{"2":{"2188":1},"3":{"1042":1,"2188":1}}],["claimwait",{"2":{"1099":1},"3":{"996":1,"1099":1,"1270":1,"1359":1,"1402":2}}],["claimtoken",{"3":{"1286":1}}],["claimtimetolive",{"2":{"1099":1},"3":{"996":1,"1099":1,"1270":1,"1359":1,"1402":2}}],["claimtype",{"3":{"318":1,"698":1,"1286":2,"1300":4,"1311":1,"1323":1,"1396":1,"1994":1}}],["claimtypes",{"2":{"157":1,"161":1,"246":1,"303":2,"324":1,"954":1,"1296":1,"1372":1,"1995":1,"1996":1,"2138":1},"3":{"157":1,"161":1,"186":1,"246":1,"303":2,"318":2,"324":2,"954":1,"1296":1,"1300":13,"1311":5,"1323":1,"1372":1,"1380":5,"1396":5,"1436":7,"1962":1,"1995":1,"1996":1,"2138":1}}],["claiming",{"0":{"1255":1},"3":{"862":1,"1018":1,"1049":1,"1247":1,"1259":2,"1270":1,"1286":1,"1311":1,"1323":3,"1324":1,"1359":1,"1402":1,"1436":2,"1801":1,"1841":1,"1891":1}}],["claimants",{"3":{"1436":1}}],["claimant",{"3":{"708":1,"1436":3,"2188":1,"2193":1}}],["claimable",{"3":{"353":1,"995":1,"1259":2,"1323":1,"1406":1,"1415":1,"1977":1}}],["claimeligibleasync",{"3":{"707":1,"1259":1}}],["claimeduntil",{"3":{"1286":4,"2186":1}}],["claimedby",{"3":{"1286":3,"2186":1}}],["claimed",{"1":{"2129":1,"2130":1,"2131":1,"2132":1,"2133":1},"3":{"82":1,"137":1,"164":1,"477":1,"499":1,"660":1,"690":1,"696":1,"707":2,"709":1,"876":1,"927":1,"995":1,"1237":1,"1247":1,"1259":5,"1279":1,"1286":6,"1290":1,"1300":1,"1311":1,"1323":3,"1324":1,"1339":3,"1359":1,"1380":1,"1415":1,"1436":3,"1489":1,"1496":3,"1779":1,"1948":1,"2118":1,"2123":1,"2128":1,"2129":1,"2139":1,"2181":1,"2210":1}}],["claimkey",{"2":{"524":1},"3":{"524":1,"996":1,"1359":1,"1402":1}}],["claimsidentity",{"3":{"1300":2,"1323":2,"1324":2,"1436":6}}],["claimsonlysessioncookieendpointstests",{"3":{"1199":1,"1207":3,"1300":2,"1438":1}}],["claimsonlybrowsertokens",{"2":{"1184":1},"3":{"1184":1,"1300":4,"1324":1}}],["claimsprincipal",{"2":{"1296":1,"2186":1},"3":{"507":1,"1042":1,"1286":2,"1296":1,"1300":19,"1309":2,"1311":1,"1323":3,"1324":1,"1396":1,"1415":1,"1432":1,"1436":9,"2186":1}}],["claimsprincipalextensionstests",{"3":{"906":1,"1199":1,"1207":2}}],["claimsprincipalextensions",{"2":{"126":1,"186":1,"246":1,"905":1,"1059":2,"1270":1,"1289":1,"1293":1,"1296":1,"1662":1},"3":{"0":1,"126":3,"157":1,"161":1,"186":2,"246":2,"303":1,"649":1,"905":4,"907":1,"1059":4,"1199":1,"1203":1,"1207":2,"1270":2,"1287":1,"1289":1,"1293":3,"1296":5,"1297":2,"1300":35,"1311":4,"1395":3,"1436":1,"1662":1,"2138":1}}],["claims",{"0":{"1296":1},"3":{"0":5,"15":1,"20":2,"24":1,"43":1,"93":1,"109":1,"181":1,"186":2,"191":1,"207":1,"208":1,"209":2,"259":1,"265":1,"303":1,"344":1,"350":2,"499":2,"507":1,"531":1,"538":1,"571":1,"592":1,"611":1,"649":1,"690":1,"691":1,"696":1,"697":1,"707":2,"720":1,"727":1,"749":1,"857":1,"905":1,"972":1,"973":1,"996":1,"1018":1,"1042":1,"1059":2,"1184":3,"1185":1,"1186":1,"1192":1,"1202":1,"1203":1,"1255":1,"1259":4,"1270":2,"1275":1,"1286":3,"1287":1,"1288":2,"1289":2,"1296":2,"1300":61,"1309":2,"1311":13,"1317":1,"1323":7,"1324":12,"1339":5,"1355":1,"1359":4,"1369":1,"1377":1,"1380":2,"1396":9,"1402":9,"1405":1,"1406":1,"1415":10,"1416":1,"1436":22,"1438":4,"1449":2,"1488":1,"1489":1,"1492":1,"1496":3,"1526":1,"1577":1,"1627":1,"1631":1,"1632":3,"1638":1,"1639":1,"1641":6,"1653":1,"1665":1,"1747":1,"1765":1,"1771":1,"1801":1,"1825":1,"1841":1,"1862":1,"1897":3,"1947":1,"1948":1,"1963":1,"1968":2,"1971":2,"1972":2,"1975":2,"1982":1,"2045":1,"2046":1,"2053":1,"2078":1,"2168":1,"2188":1,"2194":1}}],["claim",{"0":{"2056":1,"2188":1,"2190":1,"2197":1},"1":{"704":1,"705":1,"706":1,"707":1,"708":1,"709":1,"710":1,"711":1,"969":1,"970":1,"971":1,"972":1,"973":1,"974":1,"975":1,"976":1},"2":{"494":1,"495":1,"1997":1},"3":{"0":28,"15":1,"20":1,"22":2,"24":3,"26":1,"31":1,"46":1,"62":1,"72":1,"103":1,"115":1,"121":1,"126":4,"145":1,"157":1,"161":1,"173":3,"175":1,"186":7,"191":1,"197":1,"246":1,"250":1,"251":1,"252":1,"254":2,"256":1,"257":1,"258":1,"259":1,"279":2,"298":2,"303":3,"318":18,"320":6,"321":1,"322":2,"323":1,"324":3,"325":1,"326":4,"344":2,"349":1,"350":4,"351":1,"352":2,"365":1,"372":1,"373":1,"400":1,"403":1,"426":1,"447":1,"449":1,"459":1,"463":1,"464":1,"465":1,"477":1,"489":1,"490":2,"491":1,"492":1,"493":1,"494":1,"495":1,"497":3,"498":1,"499":2,"500":1,"518":1,"520":1,"524":4,"526":1,"538":1,"543":2,"550":1,"554":1,"574":1,"582":1,"592":1,"638":1,"650":1,"690":1,"696":1,"698":5,"700":1,"703":1,"704":1,"707":4,"708":1,"709":1,"710":1,"711":1,"743":1,"748":1,"749":1,"758":1,"764":2,"799":2,"800":2,"811":1,"833":1,"862":1,"903":3,"905":9,"906":3,"907":3,"909":1,"959":1,"969":1,"971":3,"972":4,"973":1,"976":1,"994":1,"995":5,"996":3,"999":1,"1000":2,"1018":3,"1019":1,"1021":1,"1022":1,"1041":1,"1042":2,"1043":1,"1046":1,"1057":1,"1058":1,"1059":6,"1060":1,"1061":1,"1063":1,"1066":1,"1067":1,"1074":1,"1075":1,"1085":1,"1099":3,"1100":3,"1103":2,"1104":1,"1108":1,"1116":1,"1118":1,"1212":4,"1213":1,"1232":1,"1233":1,"1237":5,"1247":1,"1255":3,"1256":1,"1259":18,"1263":1,"1269":1,"1270":17,"1275":2,"1276":2,"1281":1,"1286":27,"1288":3,"1289":2,"1290":3,"1293":1,"1296":6,"1297":5,"1300":135,"1304":1,"1309":2,"1311":27,"1317":2,"1318":1,"1323":29,"1324":10,"1339":3,"1348":2,"1349":1,"1350":3,"1357":1,"1358":2,"1359":35,"1367":2,"1369":1,"1372":4,"1373":2,"1375":1,"1376":1,"1380":27,"1388":2,"1389":2,"1395":20,"1396":21,"1402":28,"1405":1,"1406":4,"1409":2,"1410":1,"1413":1,"1414":1,"1415":36,"1416":1,"1430":1,"1432":1,"1436":32,"1438":16,"1441":1,"1449":2,"1450":1,"1473":1,"1474":3,"1488":1,"1491":2,"1496":6,"1525":1,"1526":1,"1534":1,"1577":2,"1582":1,"1583":1,"1621":3,"1626":1,"1627":3,"1631":2,"1632":2,"1638":1,"1639":3,"1640":1,"1641":12,"1660":1,"1664":2,"1665":1,"1667":2,"1668":1,"1719":2,"1723":2,"1724":2,"1757":1,"1761":1,"1771":1,"1778":1,"1797":2,"1821":1,"1825":1,"1841":1,"1848":1,"1849":1,"1851":4,"1896":1,"1908":1,"1933":1,"1947":1,"1948":2,"1950":2,"1962":4,"1965":2,"1974":1,"1977":2,"1978":1,"1979":1,"1982":2,"1983":1,"1984":1,"1985":1,"1994":7,"1995":8,"1996":10,"1997":6,"2000":1,"2002":1,"2043":3,"2048":1,"2049":1,"2053":1,"2060":1,"2077":1,"2085":1,"2109":1,"2110":1,"2122":1,"2125":1,"2129":1,"2138":2,"2143":1,"2168":1,"2183":1,"2186":3,"2189":1,"2190":1,"2193":3,"2194":1,"2197":7,"2202":2,"2203":2,"2215":1,"2220":1,"2232":3,"2236":1}}],["cuba",{"3":{"2220":1,"2222":1}}],["cu27",{"3":{"1453":1}}],["cumulative",{"3":{"1443":1,"1467":1}}],["cultural",{"3":{"1436":1}}],["culture+theme",{"3":{"1438":1}}],["cultureandtheme",{"3":{"1436":1}}],["cultureinvariant",{"3":{"1350":1,"1427":1,"1436":1}}],["cultureinfo",{"2":{"265":1,"1236":1,"1324":1},"3":{"265":3,"1050":1,"1236":1,"1237":3,"1241":1,"1247":5,"1270":1,"1271":6,"1286":3,"1300":11,"1309":15,"1311":4,"1312":3,"1323":6,"1324":38,"1339":2,"1350":5,"1359":13,"1380":2,"1395":31,"1396":14,"1402":4,"1415":5,"1416":2,"1417":16,"1427":1,"1436":3,"1591":1}}],["culture=",{"3":{"1311":1,"1436":1}}],["cultureendpointtests",{"3":{"1199":1,"1207":2}}],["culturedelegatinghandler",{"2":{"2083":1,"2086":1},"3":{"265":2,"881":1,"1199":2,"1203":1,"1207":2,"1324":11,"1496":1,"2083":2,"2086":1}}],["cultureswitch",{"3":{"1436":1}}],["cultureswitchertests",{"3":{"1199":1,"1207":3,"1438":2}}],["cultureswitcher",{"2":{"273":1,"276":1},"3":{"1324":5,"1577":1,"2083":2}}],["culturescope",{"3":{"1199":2,"1207":1}}],["cultures",{"3":{"265":1,"1108":1,"1300":1,"1311":3,"1324":4,"1417":1,"1436":4,"1487":1,"1564":1,"1591":2,"1666":1,"2083":1}}],["culture",{"3":{"0":6,"86":3,"87":1,"263":1,"264":1,"265":41,"267":1,"268":1,"269":1,"273":1,"415":3,"459":1,"485":1,"555":1,"556":1,"558":1,"560":2,"684":1,"686":1,"861":1,"1050":1,"1108":2,"1111":1,"1192":2,"1203":4,"1207":8,"1230":1,"1236":3,"1237":4,"1241":1,"1247":3,"1271":5,"1286":2,"1291":1,"1296":3,"1300":24,"1305":1,"1309":4,"1311":43,"1312":5,"1318":1,"1323":12,"1324":149,"1339":1,"1344":1,"1350":2,"1359":12,"1379":1,"1380":2,"1394":2,"1395":18,"1396":18,"1402":3,"1405":1,"1415":23,"1416":6,"1417":53,"1436":29,"1438":9,"1496":4,"1513":1,"1526":2,"1534":1,"1564":4,"1577":5,"1591":3,"1596":1,"1600":1,"1607":1,"1608":1,"1644":1,"1653":5,"1660":1,"1662":1,"1666":2,"1669":3,"1687":1,"1741":1,"1742":1,"1747":2,"1994":1,"2048":2,"2081":1,"2082":3,"2083":11,"2084":1,"2085":5,"2086":6,"2232":1}}],["culprit",{"3":{"1324":1,"1456":1}}],["cues",{"3":{"1417":1}}],["cue",{"3":{"353":1,"1324":1,"1417":3,"1641":2}}],["curriculum",{"3":{"1779":1,"1786":1,"2204":1,"2236":1}}],["currencies",{"3":{"1237":1,"1324":1}}],["currencymismatch",{"3":{"1234":1,"1237":2,"1311":1}}],["currencytests",{"3":{"1199":1,"1207":2}}],["currencyjsonconvertertests",{"3":{"1199":2,"1207":4}}],["currencyjsonconverter",{"2":{"659":1,"660":1,"964":1,"1230":1,"1235":1},"3":{"657":1,"659":1,"660":1,"964":1,"1198":1,"1199":4,"1203":2,"1207":3,"1230":1,"1234":1,"1235":1,"1237":9,"1311":11}}],["currency",{"2":{"655":1,"658":1,"659":1,"660":2},"3":{"0":2,"86":1,"124":1,"125":1,"536":1,"655":1,"656":1,"657":25,"658":5,"659":7,"660":8,"790":1,"1011":1,"1026":1,"1198":1,"1199":16,"1203":1,"1207":3,"1230":2,"1234":13,"1237":75,"1270":1,"1282":1,"1286":12,"1300":1,"1311":17,"1323":1,"1324":19,"1438":1,"1498":1,"1506":1,"1564":1,"1585":1,"1591":2,"1600":3,"1662":2,"1685":1,"1687":1,"1764":8,"1765":1,"1767":4,"1770":1,"1810":1}}],["currentisdirty",{"3":{"1577":1}}],["currentdevicesessionid",{"3":{"1436":1}}],["currentdomain",{"3":{"1286":1}}],["currentfixtimeout",{"3":{"1417":2}}],["currentformatversion",{"3":{"1300":1,"1323":1,"1415":1}}],["currentgeneration",{"3":{"1395":3}}],["currentcount",{"3":{"1395":2}}],["currentculture",{"3":{"265":5,"1300":1,"1324":11,"1395":4,"1396":5,"1402":1,"1417":1,"1591":2,"1600":1}}],["currenteditcontext",{"3":{"1324":1}}],["currenteventdefaultstests",{"3":{"1199":1,"1207":2}}],["currenteventdefaults",{"2":{"1348":1,"1391":1},"3":{"1199":5,"1203":1,"1207":2,"1348":2,"1350":7,"1391":1,"1395":6,"1496":1}}],["currenteventselectortests",{"3":{"1199":1,"1207":3,"1437":1}}],["currenteventselector",{"2":{"1348":1,"1383":1,"1392":1,"1393":2,"1401":1},"3":{"1199":16,"1203":1,"1207":2,"1348":6,"1350":28,"1355":1,"1356":1,"1359":7,"1383":1,"1392":2,"1393":2,"1395":28,"1396":4,"1401":1,"1496":1}}],["currenteventnotificationscopeprovidertests",{"2":{"1487":1},"3":{"1199":1,"1207":2,"1396":1,"1438":1,"1487":1}}],["currenteventnotificationscopeprovider",{"2":{"1305":1},"3":{"1199":3,"1203":1,"1207":2,"1305":2,"1309":2,"1324":2,"1396":7,"1438":1}}],["currentpasswordfield",{"3":{"1436":1}}],["currentpassword",{"3":{"1300":2,"1324":1,"1415":3,"1436":1,"1641":1}}],["currentpagestate",{"3":{"1324":2}}],["currentpage",{"3":{"1227":1,"1229":2,"1324":9,"1417":1,"1526":1,"1631":1,"1805":1}}],["currentmultifactormethod",{"3":{"1300":2}}],["currentsessionid",{"3":{"1300":7}}],["currentsaveuserid",{"3":{"1273":1,"1274":1,"1286":5}}],["currenttransaction",{"3":{"1286":3}}],["currenttype",{"3":{"1247":1}}],["currenttenantid",{"2":{"1851":1},"3":{"698":3,"1237":2,"1276":3,"1286":10,"1851":2}}],["currentvalues",{"3":{"1286":1}}],["currentvalue",{"3":{"698":1,"715":1,"1231":1,"1237":1,"1274":1,"1286":3}}],["currentuiculture",{"3":{"265":7,"1324":6,"1416":2,"1417":5}}],["currentuserrole",{"3":{"725":1,"1322":1,"1323":5,"1415":10}}],["currentuserid",{"2":{"727":1},"3":{"725":1,"727":1,"1311":1,"1322":1,"1323":3,"1324":2,"1415":6}}],["currentuserservicetests",{"3":{"1199":1,"1207":4}}],["currentuserserviceadditionaltests",{"3":{"1199":1,"1207":2}}],["currentuserserviceextensionstests",{"3":{"1199":1,"1207":2,"1271":2}}],["currentuserserviceextensions",{"2":{"1373":1},"3":{"388":2,"980":1,"1199":3,"1203":2,"1207":4,"1208":2,"1271":11,"1350":2,"1373":3,"1380":19,"1396":8,"1496":1}}],["currentuserservice",{"2":{"303":1,"1208":1,"1296":1,"1308":1,"1359":1,"1380":1},"3":{"303":1,"318":1,"1199":8,"1203":1,"1207":2,"1208":2,"1259":2,"1271":3,"1286":6,"1287":1,"1296":6,"1300":23,"1308":1,"1309":2,"1311":10,"1318":1,"1323":1,"1359":15,"1372":1,"1380":16,"1396":7,"1402":1,"1415":3}}],["currentuser",{"3":{"121":1,"126":1,"1270":6,"1415":2}}],["currentusertargetingcontextaccessortests",{"3":{"1199":1,"1207":2,"1438":2}}],["currentusertargetingcontextaccessor",{"2":{"303":1,"2138":1},"3":{"0":1,"303":2,"1199":3,"1203":1,"1207":2,"1300":2,"1311":7,"2138":3}}],["currently",{"3":{"54":1,"72":1,"85":1,"135":1,"273":1,"306":1,"370":1,"422":1,"547":1,"678":1,"751":2,"800":1,"811":1,"839":1,"996":1,"1018":1,"1229":1,"1247":1,"1259":1,"1270":2,"1271":1,"1286":7,"1300":4,"1301":1,"1305":1,"1309":4,"1311":2,"1323":3,"1324":9,"1339":1,"1350":1,"1359":5,"1369":2,"1378":1,"1395":1,"1396":4,"1415":4,"1417":2,"1436":7,"1480":1,"1576":1,"1632":1,"1634":1,"1639":2,"1733":1,"1764":1,"1896":1,"1929":1,"2036":1,"2085":1,"2132":1,"2218":1}}],["currentoutboxorigin",{"2":{"26":1,"1253":1},"3":{"26":1,"1253":1,"1259":3,"1286":4}}],["current",{"0":{"720":1,"1348":1},"2":{"452":1,"453":1,"682":1,"1246":1,"1416":1,"1425":1},"3":{"0":5,"25":1,"27":2,"29":1,"39":1,"57":1,"63":1,"74":1,"86":1,"87":1,"100":1,"112":1,"115":1,"137":2,"140":1,"142":1,"145":4,"152":2,"157":1,"164":1,"173":1,"181":1,"198":1,"202":1,"220":1,"227":1,"250":1,"251":1,"252":2,"254":1,"256":1,"257":2,"258":1,"259":1,"263":1,"273":1,"281":2,"285":3,"308":1,"309":1,"310":2,"311":1,"312":4,"328":1,"336":2,"337":1,"341":1,"342":1,"345":1,"353":2,"354":1,"359":5,"360":2,"361":1,"364":1,"365":5,"366":1,"375":1,"393":1,"395":8,"397":1,"407":1,"429":1,"434":1,"448":1,"451":1,"452":1,"453":1,"463":1,"464":2,"465":1,"466":1,"474":1,"476":1,"483":1,"497":4,"499":1,"502":1,"507":1,"512":1,"514":1,"520":1,"526":2,"534":1,"540":1,"552":2,"554":3,"559":1,"562":1,"567":1,"574":1,"583":1,"609":1,"613":1,"618":1,"621":1,"624":1,"629":1,"635":1,"649":1,"669":1,"682":1,"684":2,"696":2,"703":1,"707":2,"710":1,"714":1,"716":1,"720":1,"761":1,"777":1,"794":2,"810":1,"814":1,"825":4,"827":1,"833":2,"841":1,"849":1,"850":1,"873":1,"876":1,"877":1,"895":1,"905":3,"906":2,"907":1,"909":2,"910":1,"912":2,"914":1,"927":1,"945":1,"946":1,"949":1,"960":1,"994":1,"996":1,"1018":2,"1021":3,"1062":1,"1065":1,"1067":1,"1069":1,"1085":1,"1086":1,"1095":1,"1111":1,"1116":2,"1117":1,"1119":2,"1127":1,"1128":1,"1139":1,"1140":3,"1141":1,"1145":1,"1167":1,"1171":1,"1178":1,"1192":2,"1202":1,"1203":1,"1212":1,"1220":1,"1229":3,"1232":1,"1237":5,"1246":2,"1247":2,"1251":1,"1259":4,"1260":1,"1262":1,"1264":1,"1270":8,"1271":2,"1273":1,"1275":1,"1276":2,"1282":1,"1284":2,"1286":43,"1287":1,"1298":1,"1300":31,"1301":1,"1309":5,"1311":23,"1312":2,"1318":1,"1323":13,"1324":43,"1332":1,"1333":1,"1337":1,"1339":19,"1342":1,"1349":1,"1350":5,"1356":2,"1357":2,"1359":16,"1364":1,"1380":1,"1391":1,"1392":2,"1395":50,"1396":22,"1401":1,"1402":6,"1415":16,"1416":12,"1417":31,"1425":2,"1427":7,"1431":1,"1435":1,"1436":21,"1437":1,"1438":5,"1447":1,"1451":1,"1453":1,"1455":2,"1461":1,"1465":1,"1467":4,"1473":1,"1476":2,"1478":1,"1479":1,"1481":1,"1484":2,"1487":3,"1488":1,"1492":1,"1496":33,"1500":1,"1521":1,"1526":2,"1531":1,"1561":1,"1570":1,"1571":2,"1572":1,"1577":3,"1589":1,"1591":3,"1600":1,"1610":1,"1627":1,"1630":4,"1631":2,"1632":4,"1638":1,"1639":5,"1640":2,"1641":7,"1648":2,"1661":1,"1664":1,"1686":2,"1716":1,"1744":1,"1747":1,"1748":1,"1749":1,"1764":1,"1765":3,"1767":1,"1805":1,"1813":1,"1839":1,"1844":1,"1860":1,"1871":1,"1872":2,"1904":1,"1905":1,"1933":1,"1936":1,"1948":1,"1968":1,"1988":1,"2010":1,"2019":1,"2024":1,"2046":1,"2060":1,"2072":1,"2077":1,"2083":1,"2096":1,"2101":1,"2131":1,"2133":1,"2142":5,"2146":1,"2163":1,"2225":1}}],["curiously",{"3":{"1237":1,"1286":1,"1300":1,"1436":1}}],["curl",{"3":{"387":1,"426":1,"757":1,"1455":1,"1467":1,"1874":1}}],["curating",{"3":{"1395":1}}],["curation",{"3":{"184":1,"186":1,"1348":1,"1350":1,"1359":3,"1377":1,"1380":6,"1621":2,"1627":1}}],["curates",{"3":{"1340":1,"1380":1,"1405":1,"1415":2}}],["curated",{"3":{"265":1,"1324":1,"1350":1,"1630":1}}],["curate",{"3":{"185":1,"1347":1,"1415":1,"1960":1}}],["curve",{"3":{"22":1,"23":2,"27":1,"709":1,"1300":1,"1833":1,"1958":1}}],["cursor",{"3":{"0":1,"549":3,"1228":5,"1229":21,"1272":1,"1278":1,"1286":16,"1438":4,"1664":1}}],["custommetrics",{"3":{"1467":1}}],["customdomainexception",{"3":{"1436":2}}],["customdeleteorderhandler",{"3":{"1199":2,"1207":1}}],["customincreaseorderhandler",{"3":{"1199":2,"1207":1}}],["customize",{"3":{"1416":1}}],["customizes",{"3":{"1355":1,"1436":1,"1438":1}}],["customized",{"3":{"749":1,"1436":1,"1438":1,"1825":1}}],["customizing",{"3":{"1311":2}}],["customization",{"3":{"471":1,"749":1,"751":1,"1311":4,"1436":3,"1438":1}}],["customloggingintegrationeventhandler",{"3":{"1199":2,"1207":1}}],["customschemacontext",{"3":{"1199":2,"1207":1}}],["customexceptionthrowingfixture",{"3":{"1199":2,"1207":1,"1436":2}}],["customerinvariants",{"2":{"1810":1},"3":{"1767":2,"1810":1}}],["customeridentifiertype",{"3":{"1771":1,"1810":1}}],["customerid",{"2":{"42":1,"1995":1},"3":{"42":1,"96":1,"318":1,"782":1,"1198":2,"1199":10,"1207":2,"1600":1,"1747":1,"1749":2,"1764":5,"1765":2,"1769":2,"1810":2,"1995":4}}],["customername",{"2":{"1686":1},"3":{"1684":1,"1686":14,"1727":4}}],["customerdetail",{"3":{"1591":2,"1596":1,"1600":1}}],["customerbuilder",{"3":{"1436":1}}],["customerlookupservice",{"3":{"1324":1}}],["customerlist",{"3":{"1324":1,"1591":2}}],["customervalidationrules",{"3":{"1271":1}}],["customeremailrules",{"3":{"1271":1,"1596":1}}],["customererasedhandlertests",{"3":{"1591":1}}],["customererasedhandler",{"2":{"391":1,"1748":1,"1922":1},"3":{"391":3,"897":1,"1748":1,"1765":1,"1769":1,"1922":1}}],["customererased",{"2":{"76":1,"391":1,"1597":1,"1748":1,"1751":1,"1769":1},"3":{"76":1,"80":2,"195":2,"391":1,"1591":3,"1596":1,"1597":1,"1748":1,"1751":1,"1765":1,"1769":2,"1922":1}}],["customerrenamedv3",{"3":{"1199":4,"1207":1}}],["customerrenamedv2",{"3":{"1199":6,"1207":1}}],["customerrenamedv1",{"3":{"1199":7,"1207":1}}],["customercreate",{"3":{"1596":2}}],["customercreaterequestvalidator",{"3":{"1271":2}}],["customercreaterequest",{"3":{"1270":1}}],["customerchangeaddressrequestvalidator",{"2":{"1271":1},"3":{"1271":3}}],["customerchanged",{"2":{"1747":1},"3":{"782":1,"1747":5,"1769":1}}],["customerconfiguration",{"3":{"657":2,"660":2,"1237":5,"1286":2,"1767":1,"1810":1}}],["customercontactlookupguard",{"2":{"103":1,"1596":1},"3":{"103":1,"1596":1}}],["customerservice",{"3":{"545":1,"550":1,"881":1,"1324":1}}],["customerscontroller",{"2":{"318":2,"324":3,"326":1,"1874":1},"3":{"318":8,"324":4,"326":2,"1300":4,"1311":4,"1874":1,"1995":6}}],["customersmanage",{"2":{"318":1,"324":1,"326":1,"1995":1},"3":{"318":4,"324":1,"326":1,"1311":1,"1995":1}}],["customersgrpcservice",{"2":{"103":1,"1589":1,"1596":1},"3":{"103":2,"1589":1,"1591":1,"1596":1}}],["customers",{"2":{"1757":1},"3":{"80":1,"317":1,"318":1,"320":1,"324":1,"545":1,"583":1,"657":1,"782":1,"1237":2,"1271":5,"1312":1,"1436":1,"1591":1,"1742":2,"1746":5,"1747":13,"1749":1,"1750":6,"1753":1,"1757":1,"1761":3,"1763":1,"1764":2,"1765":1,"1768":2,"1770":1,"1773":1,"1776":2,"1994":1,"1996":1}}],["customer",{"0":{"1754":1,"1759":1,"1851":1},"2":{"320":1,"655":1,"660":1,"924":1,"1747":3,"1757":2,"1761":1,"1771":1,"1983":1,"1992":1,"1996":1,"2113":1},"3":{"0":3,"42":2,"103":1,"188":1,"230":1,"317":3,"318":10,"320":2,"324":1,"326":1,"459":1,"536":2,"539":1,"544":1,"545":1,"655":1,"657":5,"660":1,"697":2,"699":1,"743":2,"782":1,"793":1,"809":1,"897":2,"900":1,"924":1,"990":1,"1026":1,"1237":8,"1259":1,"1270":3,"1271":2,"1286":3,"1288":1,"1297":1,"1300":7,"1310":1,"1323":4,"1339":1,"1395":1,"1427":1,"1436":5,"1488":2,"1577":1,"1589":2,"1591":3,"1595":1,"1596":4,"1597":1,"1600":1,"1686":7,"1687":1,"1701":1,"1727":1,"1746":5,"1747":17,"1748":5,"1749":4,"1750":9,"1751":3,"1754":1,"1755":1,"1757":5,"1759":1,"1760":3,"1761":3,"1763":1,"1764":24,"1765":29,"1767":15,"1768":21,"1769":11,"1770":3,"1771":8,"1772":1,"1773":2,"1775":2,"1776":5,"1777":4,"1851":2,"1875":1,"1906":1,"1907":1,"1964":1,"1983":1,"1992":6,"1993":2,"1994":2,"1995":10,"1996":3,"1997":1,"2110":1,"2113":2,"2161":1,"2163":1,"2165":2,"2181":2,"2189":1,"2220":1}}],["custom",{"3":{"0":4,"135":1,"216":3,"220":1,"273":1,"275":1,"335":1,"350":1,"355":1,"396":1,"397":1,"398":1,"399":1,"418":3,"420":2,"422":3,"481":1,"640":1,"645":1,"672":3,"676":1,"698":1,"735":1,"905":1,"1075":1,"1076":1,"1077":1,"1185":1,"1212":2,"1241":3,"1247":4,"1269":1,"1270":1,"1286":2,"1288":1,"1296":1,"1298":1,"1300":2,"1309":2,"1310":1,"1311":7,"1323":6,"1324":11,"1336":1,"1339":7,"1350":4,"1359":4,"1380":2,"1415":1,"1416":4,"1417":4,"1436":21,"1488":1,"1526":1,"1557":1,"1558":1,"1641":1,"1965":1,"1976":2,"2075":2,"2078":1,"2152":1,"2156":1,"2232":1}}],["cut",{"0":{"1932":1},"3":{"396":1,"397":2,"530":1,"610":1,"881":1,"914":1,"1044":1,"1161":1,"1254":1,"1259":2,"1277":1,"1311":1,"1324":3,"1352":1,"1359":1,"1367":1,"1395":1,"1396":2,"1417":1,"1436":7,"1454":2,"1456":1,"1467":4,"1491":1,"1496":1,"1526":1,"1534":2,"1653":1,"1661":1,"1677":2,"1695":1,"1727":1,"1736":1,"1783":1,"1788":1,"1841":1,"1941":1,"2010":1,"2063":1,"2158":1,"2192":1,"2209":1}}],["cutoffs",{"3":{"1160":1}}],["cutoff",{"3":{"24":1,"536":2,"905":1,"1162":1,"1254":1,"1256":1,"1259":5,"1286":2,"1712":2,"1713":1,"2167":4}}],["cutover",{"0":{"1473":1,"1478":1},"3":{"0":3,"25":2,"27":1,"57":1,"193":1,"198":1,"422":1,"425":1,"426":1,"429":1,"1192":2,"1300":1,"1416":2,"1436":1,"1466":1,"1467":4,"1470":1,"1471":4,"1473":5,"1474":1,"1478":8,"1482":3,"1483":4,"1484":3,"1496":1,"1534":2,"1577":2}}],["cuts",{"3":{"18":1,"409":1,"1259":1,"1286":1,"1311":2,"1324":1,"1351":1,"1467":1,"1831":1,"2158":1}}],["cutting",{"0":{"1362":1,"1753":1,"1774":1,"2066":1,"2208":1},"3":{"0":5,"57":1,"116":2,"117":1,"119":1,"122":1,"265":1,"396":1,"826":1,"834":1,"906":1,"914":1,"1011":1,"1191":1,"1192":1,"1200":1,"1202":1,"1203":1,"1210":1,"1212":2,"1216":1,"1231":1,"1232":1,"1259":1,"1260":1,"1261":2,"1267":1,"1270":4,"1271":3,"1273":1,"1281":1,"1286":10,"1300":1,"1309":3,"1311":5,"1312":1,"1313":1,"1316":1,"1323":2,"1324":8,"1326":1,"1338":1,"1339":2,"1350":1,"1359":4,"1395":2,"1396":2,"1415":4,"1428":1,"1431":1,"1436":7,"1437":3,"1450":1,"1496":1,"1502":1,"1507":1,"1509":1,"1526":2,"1534":1,"1536":2,"1542":1,"1547":1,"1565":1,"1571":1,"1577":4,"1591":1,"1599":1,"1630":1,"1656":1,"1662":1,"1664":2,"1780":1,"1796":1,"1810":1,"1818":2,"1819":1,"1820":1,"1827":3,"1834":1,"1856":1,"1879":1,"1914":1,"1916":1,"1937":1,"1941":1,"2008":1,"2022":1,"2069":1,"2070":1,"2074":1,"2092":1,"2096":1,"2134":1,"2139":3,"2148":1,"2211":1,"2220":2,"2230":1,"2232":1}}],["ccpa",{"3":{"0":1,"38":1,"1212":2,"1230":1,"1237":1,"1299":1,"1311":1,"1323":2,"1436":1,"1489":1,"1499":1,"1567":1,"1747":1,"1765":1,"2061":1,"2062":1,"2069":1,"2221":1,"2230":1,"2232":1}}],["camps",{"3":{"2225":1}}],["camp",{"3":{"1788":1}}],["camel",{"3":{"1686":2,"1727":2}}],["camelcasing",{"3":{"1311":1}}],["camelcased",{"3":{"1311":1,"1324":1}}],["camelcasename",{"3":{"1247":3}}],["camelcase",{"2":{"741":1,"2088":1},"3":{"0":1,"741":2,"1214":1,"1242":2,"1244":1,"1247":6,"1300":1,"1311":6,"1324":3,"1396":1,"1416":1,"1436":1,"2088":1}}],["came",{"3":{"26":1,"284":1,"291":1,"610":1,"633":1,"732":1,"743":1,"905":1,"941":1,"1081":1,"1083":1,"1091":1,"1115":1,"1247":2,"1253":1,"1259":4,"1286":3,"1300":1,"1301":2,"1311":2,"1312":1,"1323":1,"1324":7,"1339":1,"1350":1,"1359":2,"1369":2,"1395":1,"1396":1,"1402":1,"1417":2,"1436":3,"1438":2,"1467":1,"1474":1,"1476":1,"1728":1,"1764":1,"1790":1,"1926":1,"2097":1,"2186":1,"2204":1}}],["camerabarcodereaderview",{"3":{"1417":2}}],["cameras",{"3":{"1396":1,"1417":1}}],["camera=",{"3":{"214":1,"2149":1}}],["cameradescription",{"2":{"684":1},"3":{"0":1,"682":1,"684":1,"1416":1,"1417":3}}],["camera",{"2":{"681":1},"3":{"0":2,"413":2,"426":1,"439":1,"681":4,"682":4,"683":3,"684":1,"689":1,"690":1,"1212":1,"1323":1,"1339":1,"1395":1,"1396":12,"1414":1,"1415":1,"1416":3,"1417":58,"1438":2,"1627":1,"2126":1,"2232":2}}],["ca5392",{"2":{"1524":1},"3":{"1524":1,"1526":2,"1531":1}}],["ca5394",{"3":{"1259":1,"1324":1}}],["ca5359",{"3":{"1416":1}}],["ca5404",{"3":{"1300":1}}],["ca1822",{"3":{"1416":1}}],["ca1859",{"3":{"1300":1}}],["ca1819",{"3":{"1237":2,"1300":1,"1311":1,"1350":2,"1415":1}}],["ca1721",{"3":{"1300":1}}],["ca1708",{"3":{"0":1,"258":1,"980":1,"981":1,"982":1,"983":1,"1311":3,"1312":1,"1324":4,"1339":11,"1436":2,"1488":1}}],["ca1068",{"3":{"1436":1}}],["ca1008",{"3":{"1350":1,"1369":1,"1396":1}}],["ca1000",{"3":{"1237":1}}],["ca1055",{"3":{"1324":1}}],["ca1056",{"3":{"1300":1,"1324":1}}],["ca1054",{"3":{"1237":1,"1324":2,"1359":1,"1415":1}}],["ca1032",{"3":{"1312":4}}],["ca1033",{"3":{"1270":1}}],["ca1031",{"3":{"692":1,"1247":1,"1270":1,"1309":3,"1311":1,"1324":2,"1339":2,"1395":1,"1396":2,"1416":1,"1417":5,"1427":1,"1934":1}}],["ca1308",{"3":{"972":1,"1237":1,"1300":3,"1323":1,"1359":1,"1395":1,"1436":1}}],["ca2100",{"3":{"1436":2,"1490":1}}],["ca2000",{"3":{"1311":1,"1323":1,"1427":2}}],["ca2007",{"1":{"479":1,"480":1,"481":1,"482":1,"483":1,"484":1,"485":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"495":1},"2":{"481":1,"482":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1},"3":{"0":2,"479":1,"481":1,"482":3,"483":2,"486":3,"487":3,"488":2,"489":3,"490":7,"491":7,"492":7,"493":6,"494":1,"1212":1,"2232":1}}],["ca2227",{"3":{"674":1,"1323":1}}],["cautious",{"3":{"1359":1,"1415":1}}],["cautionary",{"3":{"2025":1}}],["caution",{"3":{"543":2,"550":1,"1496":1}}],["caught",{"3":{"21":1,"39":2,"110":1,"178":1,"235":3,"237":1,"345":1,"361":1,"427":1,"459":2,"558":1,"618":2,"675":2,"725":1,"759":1,"776":1,"819":1,"837":1,"854":1,"863":1,"883":1,"896":1,"901":1,"916":1,"941":2,"966":1,"1034":1,"1036":1,"1067":1,"1168":1,"1170":1,"1229":2,"1236":1,"1247":1,"1269":1,"1270":4,"1286":4,"1300":1,"1301":2,"1311":3,"1312":2,"1323":4,"1324":13,"1339":5,"1358":1,"1359":7,"1366":1,"1369":1,"1380":2,"1395":6,"1396":5,"1402":1,"1415":2,"1416":1,"1417":4,"1427":2,"1436":17,"1453":1,"1459":1,"1465":1,"1496":3,"1534":1,"1546":1,"1673":1,"1686":1,"1800":1,"1801":1,"1810":1,"1844":1,"1852":1,"1885":1,"1909":1,"1933":1,"1940":1,"2032":1,"2085":1}}],["causes",{"3":{"1222":1,"1324":1,"1339":1,"1359":1,"1415":1,"2002":1,"2014":1}}],["caused",{"3":{"451":1,"556":1,"789":1,"790":1,"981":1,"1006":1,"1066":1,"1300":1,"1311":2,"1323":1,"1324":1,"1359":4,"1396":3,"1402":1,"1425":1,"1427":1,"1436":1,"1473":1,"1534":1,"1665":1,"2177":1}}],["cause",{"3":{"0":1,"156":1,"343":2,"915":1,"948":1,"1065":1,"1286":2,"1300":1,"1309":1,"1311":2,"1323":2,"1339":1,"1395":2,"1415":1,"1417":1,"1436":2,"1438":4,"1467":2,"1476":1,"1490":1,"1686":1,"1876":1,"1913":1,"1934":1,"1969":1,"1989":1}}],["caveats",{"3":{"1191":1,"1193":1,"1229":7,"1237":18,"1247":14,"1259":13,"1270":39,"1271":6,"1286":51,"1300":49,"1301":8,"1309":7,"1310":5,"1311":33,"1312":4,"1323":49,"1324":58,"1339":53,"1350":35,"1359":177,"1369":7,"1380":9,"1395":44,"1396":90,"1402":16,"1415":41,"1416":7,"1417":27,"1427":3,"1436":94,"1577":1,"1591":1,"2133":1}}],["caveat",{"3":{"0":1,"135":1,"159":1,"188":1,"197":1,"245":3,"283":1,"320":1,"352":1,"361":1,"446":1,"450":2,"461":1,"558":1,"574":1,"601":1,"605":1,"607":1,"611":2,"642":1,"667":2,"751":1,"821":1,"829":1,"863":3,"865":1,"998":1,"1010":2,"1231":1,"1236":1,"1259":1,"1270":2,"1282":1,"1286":2,"1296":1,"1301":1,"1310":1,"1318":1,"1323":1,"1324":1,"1326":1,"1334":1,"1339":3,"1359":4,"1369":1,"1395":3,"1396":1,"1402":1,"1422":1,"1423":1,"1436":1,"1440":1,"1468":1,"1496":3,"1591":2,"1599":2,"1891":1,"1923":1,"1964":1,"1996":1,"2063":1,"2068":1,"2077":1,"2114":1,"2131":1,"2132":2,"2138":1}}],["cash",{"3":{"1765":1}}],["cashed",{"3":{"1436":1,"1474":1}}],["casting",{"3":{"1286":1,"1300":1,"1396":3,"1399":1,"1401":1,"1402":4,"1417":1}}],["cast",{"3":{"1237":3,"1247":3,"1259":1,"1268":1,"1270":1,"1272":1,"1286":3,"1300":1,"1301":1,"1309":1,"1312":1,"1313":1,"1323":1,"1324":5,"1339":1,"1380":1,"1395":1,"1396":4,"1397":1,"1401":1,"1402":13,"1415":2,"1436":4}}],["casts",{"3":{"1229":1,"1237":1,"1259":1,"1270":1,"1286":1,"1323":1,"1324":1,"1380":1,"1402":2,"1436":1,"1632":1}}],["castvoteasync",{"3":{"1396":4,"1402":1}}],["castvoterequest",{"3":{"1199":4,"1203":1,"1207":2,"1396":2,"1402":6}}],["castvotecommandvalidatortests",{"3":{"1199":1,"1207":2}}],["castvotecommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1402":5}}],["castvotecommand",{"3":{"1199":8,"1203":1,"1207":2,"1396":2,"1402":8}}],["castvote",{"3":{"1150":1,"1203":3,"1207":6,"1398":1,"1402":11,"1496":2}}],["castvotehandlertests",{"3":{"1199":1,"1207":3,"1436":2}}],["castvotehandler",{"2":{"383":1},"3":{"383":1,"1150":1,"1156":1,"1199":2,"1203":1,"1207":2,"1396":1,"1398":3,"1399":2,"1401":1,"1402":19,"1496":1}}],["casualty",{"3":{"751":1}}],["casings",{"3":{"1247":1}}],["casing",{"3":{"135":1,"572":1,"574":1,"585":1,"964":1,"1089":1,"1094":1,"1247":3,"1286":1,"1300":1,"1323":4,"1324":1,"1350":1,"1359":3,"1395":2,"1396":1,"1405":1,"1410":1,"1415":6,"1416":1,"1436":1,"1438":2,"1453":1,"1455":1,"1631":1}}],["cascadingforeignkeysareexplicitlyoptedin",{"3":{"1436":4}}],["cascadingparameterattribute",{"3":{"1436":1}}],["cascadingparameter",{"3":{"1324":2,"1396":2,"1436":1}}],["cascadingauthenticationstate",{"2":{"1641":1,"2078":1},"3":{"1324":1,"1396":2,"1436":1,"1641":1,"2078":1}}],["cascadingchild",{"3":{"1199":3,"1207":1}}],["cascadingdeletes",{"2":{"1083":1},"3":{"1083":1,"1436":3}}],["cascading",{"3":{"0":1,"39":1,"556":1,"954":1,"1081":1,"1116":1,"1168":1,"1281":1,"1286":2,"1301":1,"1324":2,"1359":1,"1395":6,"1396":2,"1402":3,"1432":1,"1436":12,"1488":2,"1491":2,"1556":1,"1639":1,"1662":1,"1776":1,"2064":1,"2232":1}}],["cascaded",{"3":{"1323":1,"1350":1,"1359":2,"1391":1,"1395":3,"1396":1,"1438":1,"1492":1}}],["cascadedelete",{"2":{"1116":1,"1346":1},"3":{"1116":1,"1346":1,"1350":4,"1359":2}}],["cascadefixtures",{"3":{"1207":21,"1436":43}}],["cascadechildfixture",{"3":{"1199":7,"1207":1,"1436":12}}],["cascadesoftdelete",{"3":{"1207":1,"1436":9,"1489":1,"1577":1}}],["cascadesoftdeleteconventiontests",{"3":{"1199":2,"1207":4,"1436":19,"1489":2,"1577":1}}],["cascadesoftdeleteconventiontestsbase",{"3":{"1199":3,"1207":2,"1436":11,"1586":2}}],["cascadesoftdeletefitnesstests",{"3":{"1199":1,"1207":3,"1436":31,"1489":1}}],["cascades",{"3":{"0":1,"1082":1,"1084":1,"1087":1,"1231":1,"1237":2,"1270":1,"1281":1,"1286":1,"1323":1,"1346":2,"1350":2,"1359":2,"1380":1,"1395":1,"1398":1,"1402":1,"1431":1,"1432":1,"1436":10,"1455":1,"1489":1,"1490":1,"1496":1,"1544":1,"1635":1,"1638":2,"2055":1,"2064":1}}],["cascade",{"0":{"1346":1},"2":{"1082":1,"1083":1,"1281":1,"2064":1},"3":{"0":7,"27":1,"37":1,"39":2,"43":3,"459":1,"926":1,"1082":4,"1083":6,"1084":3,"1085":3,"1117":1,"1120":2,"1170":1,"1212":1,"1237":2,"1265":1,"1270":3,"1281":3,"1286":9,"1340":1,"1342":1,"1343":2,"1346":3,"1350":31,"1352":2,"1354":1,"1359":31,"1362":2,"1369":4,"1402":5,"1415":2,"1431":1,"1436":43,"1438":3,"1496":1,"1566":1,"1577":4,"1586":2,"1630":1,"1631":1,"1632":2,"1635":5,"1638":23,"1639":21,"1664":1,"1671":1,"1686":1,"1700":1,"1727":1,"1755":1,"1776":1,"2064":3}}],["caseoptedindefaultsource",{"3":{"1199":2,"1207":1}}],["casenosettings",{"3":{"1199":2,"1207":1}}],["casenooptin",{"3":{"1199":2,"1207":1}}],["casenamedsource",{"3":{"1199":3,"1207":2}}],["casemappingshape",{"3":{"1199":3,"1207":2}}],["caseenableddefaultsource",{"3":{"1199":2,"1207":1}}],["casewrongsource",{"3":{"1199":2,"1207":1}}],["caseidentity",{"3":{"1199":2,"1207":1}}],["caseconference",{"3":{"1199":2,"1207":1}}],["casedefaultsettings",{"3":{"1199":2,"1207":1}}],["cased",{"3":{"1091":1,"1247":2,"1323":1,"1324":1,"1339":1,"1350":1,"1359":2,"1395":1,"1425":1,"1427":1,"1436":1,"1488":1}}],["case",{"0":{"1246":1,"1322":1,"1352":1,"1952":1,"2190":1},"2":{"1035":1},"3":{"0":10,"32":1,"42":1,"109":2,"116":2,"117":1,"121":1,"126":3,"133":1,"135":2,"136":1,"150":1,"156":1,"157":1,"158":1,"160":1,"175":1,"186":1,"189":1,"196":1,"197":1,"201":1,"225":1,"227":1,"245":2,"246":1,"249":1,"301":1,"318":2,"333":1,"340":1,"358":1,"379":1,"386":1,"389":1,"390":1,"391":1,"396":1,"402":1,"435":1,"460":1,"461":1,"473":1,"517":2,"519":1,"534":1,"539":1,"540":1,"544":1,"564":1,"582":1,"583":1,"585":4,"586":1,"601":1,"609":2,"633":3,"650":1,"667":1,"683":1,"690":2,"709":1,"737":1,"741":1,"749":1,"765":1,"766":1,"782":1,"789":1,"791":1,"799":1,"802":1,"810":1,"811":1,"820":1,"821":1,"827":1,"831":1,"861":1,"863":1,"864":2,"875":1,"881":2,"883":2,"889":1,"897":2,"898":1,"905":2,"907":1,"926":2,"933":1,"938":1,"939":1,"940":1,"964":1,"965":1,"972":1,"974":1,"980":1,"982":1,"988":1,"1003":1,"1004":1,"1018":11,"1019":2,"1020":6,"1034":1,"1035":1,"1045":1,"1049":1,"1050":1,"1060":1,"1091":4,"1099":1,"1101":1,"1108":2,"1109":2,"1112":1,"1116":1,"1123":1,"1155":1,"1177":1,"1212":1,"1220":1,"1221":1,"1223":2,"1229":4,"1234":2,"1235":1,"1237":10,"1241":1,"1242":1,"1246":1,"1247":22,"1250":1,"1259":11,"1260":3,"1261":1,"1263":5,"1264":4,"1269":1,"1270":16,"1271":9,"1273":1,"1276":1,"1278":1,"1281":2,"1286":44,"1290":1,"1293":1,"1295":1,"1296":1,"1297":2,"1300":46,"1301":7,"1306":1,"1309":7,"1310":2,"1311":20,"1312":7,"1313":1,"1315":2,"1318":1,"1323":25,"1324":45,"1331":1,"1332":2,"1334":1,"1337":1,"1339":28,"1342":1,"1344":1,"1350":16,"1352":1,"1353":1,"1355":1,"1358":1,"1359":85,"1362":1,"1365":1,"1369":5,"1372":1,"1378":1,"1380":6,"1383":2,"1392":1,"1395":29,"1396":28,"1398":1,"1402":16,"1403":1,"1404":1,"1405":1,"1415":33,"1416":6,"1417":10,"1420":1,"1427":18,"1431":2,"1432":1,"1433":1,"1435":10,"1436":87,"1438":18,"1443":1,"1453":2,"1455":8,"1456":1,"1458":1,"1459":1,"1467":2,"1470":1,"1474":1,"1480":1,"1486":1,"1487":1,"1488":1,"1489":2,"1492":14,"1496":7,"1512":1,"1526":6,"1531":1,"1534":1,"1535":1,"1540":1,"1542":1,"1568":1,"1577":4,"1582":1,"1583":1,"1600":1,"1617":1,"1630":2,"1631":6,"1632":2,"1635":2,"1638":1,"1639":2,"1640":2,"1641":5,"1649":1,"1651":3,"1667":1,"1678":1,"1686":3,"1697":1,"1708":1,"1727":4,"1730":1,"1731":4,"1749":1,"1755":1,"1768":3,"1776":1,"1779":1,"1805":1,"1810":1,"1815":1,"1817":1,"1818":2,"1820":1,"1823":2,"1825":1,"1827":1,"1830":1,"1840":1,"1849":1,"1853":3,"1856":1,"1858":1,"1867":2,"1874":1,"1881":1,"1903":1,"1909":3,"1914":3,"1922":1,"1923":2,"1941":1,"1962":1,"1964":1,"1970":1,"1977":1,"1988":1,"1993":1,"1994":2,"2000":1,"2002":1,"2025":1,"2030":1,"2034":1,"2043":3,"2070":1,"2083":1,"2087":1,"2092":1,"2096":1,"2105":1,"2106":1,"2108":2,"2109":1,"2117":1,"2118":1,"2123":1,"2134":2,"2135":1,"2136":1,"2139":1,"2141":2,"2149":1,"2154":1,"2158":1,"2160":1,"2163":1,"2165":1,"2167":2,"2183":1,"2189":1,"2190":1,"2193":1,"2197":1,"2232":1,"2234":1}}],["casesettingsonly",{"3":{"1199":2,"1207":1}}],["cases",{"0":{"1632":1,"1768":1},"1":{"1351":1,"1352":1,"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1,"1359":1},"3":{"0":1,"33":1,"126":1,"135":1,"136":1,"138":1,"142":1,"160":1,"314":1,"324":1,"335":1,"350":1,"365":1,"466":1,"539":1,"656":1,"790":1,"791":1,"794":1,"845":1,"972":1,"988":1,"998":1,"1018":3,"1020":1,"1050":1,"1091":1,"1116":1,"1192":2,"1201":1,"1202":3,"1203":3,"1217":1,"1219":1,"1229":4,"1237":1,"1247":3,"1260":2,"1263":1,"1270":1,"1271":1,"1286":9,"1300":8,"1303":1,"1306":1,"1309":3,"1311":3,"1322":1,"1323":4,"1324":4,"1339":10,"1350":3,"1351":2,"1354":1,"1359":18,"1365":1,"1369":4,"1380":1,"1395":4,"1396":7,"1402":3,"1403":2,"1410":1,"1415":9,"1416":1,"1417":2,"1427":3,"1428":1,"1431":2,"1435":2,"1436":22,"1438":8,"1444":1,"1454":1,"1487":1,"1492":1,"1496":6,"1500":1,"1526":1,"1553":1,"1577":1,"1611":3,"1632":2,"1641":1,"1646":1,"1649":1,"1650":1,"1660":1,"1667":2,"1729":1,"1749":1,"1821":1,"1856":1,"1909":1,"1932":1,"1941":1,"1955":1,"1962":1,"1982":1,"2114":1,"2136":1,"2138":1,"2143":1,"2167":1}}],["caracteres",{"3":{"1685":2,"1686":2}}],["car",{"3":{"1416":1}}],["carve",{"3":{"200":1,"265":2,"317":1,"322":1,"324":1,"326":1,"562":1,"783":1,"862":1,"1286":2,"1375":1,"1380":2,"1402":1,"1431":1,"1433":2,"1436":1,"1465":1,"1467":1,"1496":1,"1577":1,"1630":1,"1635":1}}],["carved",{"3":{"200":1,"1323":1,"1441":1}}],["career",{"3":{"1395":1,"2220":1}}],["careless",{"3":{"1107":1,"1135":1,"1905":1}}],["carelessly",{"3":{"829":1}}],["careful",{"3":{"839":1,"1259":1,"1300":1,"1323":1,"1324":1,"1359":1,"1396":2,"1402":1,"1425":1,"1431":1,"1436":2,"1942":1,"2000":1,"2072":1,"2167":1}}],["carefully",{"3":{"365":1,"1234":1,"1237":1,"1259":1,"1270":1,"1301":1,"1323":1,"1324":1,"1332":1,"1341":1,"1344":1,"1350":2,"1359":2,"1396":2,"1415":2,"1436":1,"1467":1,"1486":1,"1803":1}}],["cares",{"3":{"784":1,"827":1,"1041":1,"1296":1,"1339":4,"1350":1,"1359":2,"1395":1,"1415":1,"1417":1,"1427":1,"1429":1,"1436":2,"2091":1,"2183":1}}],["care",{"3":{"55":1,"740":1,"842":1,"1194":1,"1229":1,"1255":1,"1259":1,"1273":1,"1312":1,"1323":1,"1324":1,"1359":1,"1396":1,"1436":2,"1453":1,"1782":1,"1783":1,"1792":1,"1882":1,"1905":1,"2091":1,"2107":1}}],["carte",{"3":{"1436":1}}],["cartesian",{"3":{"1286":3,"1863":1,"1864":1}}],["cartdrawer",{"3":{"1324":1,"1591":7,"1596":3,"1597":1,"1600":2,"1750":3,"1773":1}}],["cartbutton",{"3":{"1324":1,"1591":1,"1596":1,"1597":1}}],["carts",{"3":{"1297":1,"1742":1,"1750":2,"1761":1,"1763":1,"1768":2,"1773":2,"2110":1}}],["cartstateservice",{"2":{"879":1,"883":1,"884":1,"1599":1},"3":{"0":1,"879":1,"881":2,"883":1,"884":2,"1324":1,"1591":5,"1599":1,"1600":3}}],["cartid",{"3":{"1237":1}}],["cart",{"3":{"0":1,"318":1,"320":1,"535":1,"881":1,"990":1,"1025":1,"1026":2,"1027":3,"1028":1,"1237":1,"1270":1,"1300":1,"1311":1,"1324":3,"1436":2,"1577":1,"1591":5,"1596":1,"1599":2,"1600":3,"1712":1,"1713":2,"1714":1,"1741":1,"1742":2,"1746":5,"1748":1,"1749":18,"1750":20,"1751":1,"1753":1,"1754":2,"1755":2,"1757":1,"1758":1,"1759":1,"1761":1,"1763":1,"1764":13,"1765":17,"1767":8,"1768":12,"1769":2,"1771":2,"1773":10,"1775":2,"1776":10,"1995":5,"1996":1,"2077":1,"2161":1}}],["cardinalities",{"3":{"1640":1}}],["cardinality",{"3":{"0":2,"407":1,"897":2,"898":1,"1150":1,"1152":1,"1212":1,"1247":3,"1301":1,"1311":2,"1396":3,"1402":3,"1919":1}}],["cardclass",{"3":{"1324":1}}],["cardcontent",{"3":{"1324":1}}],["cardelevation",{"3":{"1324":1}}],["cardelementid",{"3":{"1324":1}}],["cardformatting",{"3":{"1324":1,"1591":2}}],["cardtemplate",{"3":{"1324":1}}],["card",{"3":{"539":1,"684":1,"685":1,"690":1,"881":2,"953":1,"1264":1,"1270":1,"1324":12,"1383":1,"1385":1,"1395":33,"1396":9,"1402":13,"1415":3,"1416":3,"1436":3,"1438":2,"1490":1,"1524":2,"1526":2,"1531":1,"1591":2,"1599":2,"1627":2,"1643":1,"1669":1,"1712":1,"1713":2,"1750":4,"1773":4,"2079":1}}],["cardsperpage",{"3":{"1395":1}}],["cardstyle",{"3":{"1324":1}}],["cardselector",{"3":{"1324":2}}],["cards",{"3":{"0":1,"1324":2,"1383":2,"1395":11,"1396":1,"1402":3,"1414":1,"1415":1,"1577":1,"1714":1,"1742":1,"1758":1,"1773":1}}],["carriage",{"3":{"885":1,"947":1}}],["carriertrackingurls",{"3":{"1767":1}}],["carriers",{"3":{"1286":1,"1300":1,"1313":1}}],["carrier",{"0":{"1219":1},"3":{"0":1,"340":1,"342":1,"672":1,"905":1,"1042":1,"1124":1,"1219":1,"1229":1,"1247":3,"1286":8,"1288":1,"1300":7,"1309":1,"1312":1,"1318":2,"1323":1,"1324":2,"1339":1,"1350":3,"1359":3,"1402":2,"1417":3,"1749":3,"1750":1,"1763":1,"1764":3,"1765":7,"1766":1,"1767":2,"1768":2,"1769":2,"1770":1,"1776":2,"1805":1,"2186":1}}],["carried",{"3":{"0":3,"77":1,"94":1,"99":2,"135":1,"201":2,"258":1,"265":1,"318":1,"400":1,"407":1,"447":1,"448":1,"455":1,"463":1,"464":2,"465":2,"492":1,"529":1,"536":1,"607":1,"626":1,"702":2,"707":1,"719":1,"743":1,"747":1,"800":1,"827":1,"839":1,"853":1,"867":1,"872":1,"921":1,"973":1,"980":1,"1006":2,"1010":2,"1012":1,"1014":3,"1024":1,"1054":1,"1065":1,"1075":1,"1099":2,"1212":1,"1229":2,"1247":2,"1263":1,"1264":1,"1270":1,"1286":8,"1300":16,"1311":6,"1312":1,"1321":1,"1323":8,"1324":8,"1339":4,"1344":1,"1349":1,"1350":7,"1359":14,"1369":3,"1380":4,"1395":9,"1396":16,"1402":3,"1413":1,"1415":7,"1417":4,"1427":2,"1432":1,"1436":5,"1455":1,"1467":3,"1478":1,"1489":1,"1496":1,"1526":1,"1531":1,"1578":1,"1591":1,"1634":1,"1737":1,"1764":1,"1805":1,"1825":1,"1854":1,"1983":1,"1994":1,"2043":1,"2050":1,"2126":1,"2130":1,"2131":1,"2167":1,"2176":1,"2203":1}}],["carrieshardenedsecurityheaders",{"3":{"1436":1}}],["carriesanactivehealthcheckprobingalive",{"3":{"1436":1}}],["carriesaservicetag",{"3":{"1436":1}}],["carriesnoinlinestyles",{"3":{"1436":2}}],["carriesservicecontractattribute",{"3":{"1312":1,"1436":3}}],["carries",{"0":{"95":1},"3":{"0":35,"1":1,"23":1,"24":1,"26":2,"39":2,"54":1,"55":1,"57":1,"61":1,"68":1,"72":1,"73":1,"78":1,"91":2,"93":1,"95":2,"109":2,"111":1,"115":1,"122":1,"123":1,"125":1,"126":2,"132":1,"135":1,"140":2,"141":1,"142":1,"149":1,"157":1,"165":1,"166":1,"168":1,"171":1,"173":1,"175":1,"180":2,"184":1,"186":3,"188":1,"189":1,"195":3,"197":1,"200":1,"212":1,"214":2,"215":1,"216":1,"217":1,"219":1,"220":1,"225":1,"229":2,"243":1,"255":2,"258":1,"259":1,"265":2,"275":1,"282":1,"284":1,"293":1,"295":1,"300":1,"302":1,"305":1,"308":1,"318":3,"324":1,"325":2,"326":1,"333":1,"341":5,"343":1,"346":2,"348":1,"349":1,"350":4,"353":3,"359":1,"360":1,"365":1,"372":1,"373":1,"374":1,"380":1,"384":1,"388":1,"392":1,"395":2,"399":1,"400":1,"401":1,"402":2,"403":1,"404":2,"405":1,"406":1,"407":1,"408":1,"418":3,"422":1,"423":2,"426":2,"439":1,"448":2,"451":1,"452":1,"458":1,"459":1,"461":1,"462":1,"470":1,"471":1,"474":1,"475":1,"476":1,"477":1,"484":1,"487":1,"488":2,"489":2,"490":2,"491":1,"492":2,"493":3,"494":4,"495":2,"499":1,"528":2,"536":2,"539":1,"543":1,"545":4,"546":1,"547":2,"549":2,"551":1,"556":1,"558":1,"564":3,"565":1,"572":4,"575":1,"578":1,"591":1,"599":5,"601":1,"604":1,"605":1,"607":2,"609":4,"618":2,"624":1,"625":1,"626":4,"631":1,"633":4,"634":1,"638":1,"640":3,"642":2,"649":1,"651":2,"656":1,"657":4,"659":1,"664":1,"668":1,"676":1,"689":1,"690":3,"691":2,"692":3,"698":3,"700":1,"702":1,"707":2,"709":1,"710":1,"713":1,"714":2,"720":1,"725":4,"733":1,"737":2,"741":1,"749":1,"757":2,"764":1,"766":4,"781":1,"782":7,"783":1,"784":2,"788":1,"790":4,"792":1,"793":1,"794":1,"799":2,"803":1,"804":2,"805":1,"806":1,"815":1,"818":1,"819":1,"825":1,"826":1,"827":2,"830":1,"833":2,"838":1,"848":1,"849":2,"850":1,"852":1,"854":1,"857":1,"859":1,"861":2,"876":2,"881":3,"889":1,"899":1,"903":1,"905":6,"907":1,"916":1,"920":1,"922":3,"924":1,"926":2,"931":1,"939":2,"944":1,"946":1,"948":2,"954":2,"959":1,"960":1,"962":1,"964":2,"971":1,"972":3,"988":1,"996":1,"1004":6,"1008":1,"1011":1,"1012":2,"1014":1,"1017":1,"1018":7,"1019":1,"1021":1,"1026":1,"1029":1,"1034":1,"1041":2,"1055":1,"1058":1,"1059":4,"1062":2,"1065":1,"1067":4,"1068":1,"1069":1,"1073":1,"1074":1,"1083":2,"1091":1,"1094":1,"1096":1,"1116":3,"1118":1,"1123":1,"1124":2,"1125":1,"1133":1,"1135":1,"1140":2,"1150":1,"1157":1,"1162":1,"1168":1,"1173":1,"1177":1,"1184":1,"1211":1,"1212":7,"1213":1,"1217":1,"1220":2,"1225":1,"1228":2,"1229":12,"1231":1,"1233":1,"1237":18,"1238":1,"1240":1,"1241":2,"1242":1,"1247":7,"1249":1,"1253":4,"1259":15,"1262":1,"1263":1,"1264":2,"1265":3,"1266":1,"1270":22,"1271":7,"1272":1,"1273":2,"1275":3,"1276":3,"1277":1,"1278":3,"1279":1,"1281":1,"1284":1,"1285":3,"1286":60,"1288":1,"1289":2,"1290":2,"1291":1,"1292":1,"1294":1,"1296":1,"1297":3,"1299":1,"1300":68,"1301":5,"1303":1,"1304":2,"1305":2,"1307":1,"1308":1,"1309":20,"1310":2,"1311":56,"1312":7,"1316":1,"1320":3,"1322":1,"1323":49,"1324":78,"1325":1,"1328":2,"1330":1,"1333":2,"1337":1,"1338":1,"1339":43,"1342":5,"1343":1,"1345":2,"1347":3,"1348":1,"1349":1,"1350":73,"1352":4,"1353":2,"1354":1,"1357":2,"1358":1,"1359":157,"1366":4,"1367":1,"1369":11,"1371":1,"1373":4,"1374":1,"1376":2,"1377":1,"1379":1,"1380":52,"1384":1,"1386":1,"1389":3,"1391":2,"1392":1,"1393":1,"1394":1,"1395":55,"1396":133,"1397":2,"1398":1,"1399":3,"1400":1,"1402":45,"1404":1,"1405":2,"1406":4,"1407":4,"1408":4,"1410":2,"1411":2,"1413":1,"1414":2,"1415":52,"1416":8,"1417":21,"1421":1,"1422":1,"1423":1,"1425":2,"1427":7,"1428":1,"1430":1,"1431":2,"1432":1,"1436":122,"1438":19,"1439":1,"1441":5,"1442":1,"1443":1,"1444":2,"1445":1,"1447":4,"1449":3,"1453":2,"1454":1,"1455":9,"1456":3,"1458":1,"1459":3,"1460":2,"1465":2,"1467":20,"1470":1,"1473":1,"1474":3,"1475":2,"1476":1,"1482":1,"1488":3,"1489":3,"1490":2,"1491":1,"1492":5,"1496":3,"1504":2,"1508":1,"1512":1,"1513":1,"1517":1,"1522":1,"1525":1,"1526":4,"1531":1,"1534":2,"1535":1,"1547":1,"1548":1,"1577":12,"1582":2,"1585":1,"1589":2,"1591":10,"1596":5,"1597":1,"1599":4,"1600":1,"1627":1,"1631":5,"1634":1,"1638":1,"1640":1,"1641":3,"1643":1,"1651":1,"1653":2,"1654":1,"1656":2,"1660":1,"1665":1,"1666":1,"1670":1,"1673":1,"1677":1,"1683":1,"1684":1,"1685":1,"1686":2,"1693":1,"1701":1,"1702":1,"1703":2,"1712":1,"1719":1,"1722":1,"1727":1,"1728":1,"1748":5,"1749":2,"1750":1,"1751":1,"1755":1,"1758":2,"1760":3,"1764":2,"1767":1,"1769":7,"1771":2,"1772":1,"1773":2,"1776":1,"1799":3,"1805":3,"1810":1,"1815":3,"1821":3,"1825":2,"1831":1,"1839":1,"1840":2,"1849":2,"1851":1,"1854":1,"1856":1,"1870":1,"1871":1,"1874":1,"1876":1,"1877":1,"1882":1,"1888":1,"1889":1,"1902":1,"1908":2,"1909":1,"1916":1,"1918":1,"1922":1,"1925":1,"1927":2,"1933":2,"1934":1,"1939":1,"1940":1,"1943":1,"1945":1,"1947":1,"1950":1,"1962":2,"1964":1,"1965":1,"1966":1,"1968":2,"1973":1,"1975":3,"1977":1,"1982":1,"1992":1,"1994":1,"1995":1,"1997":1,"2013":1,"2032":1,"2034":1,"2046":1,"2055":1,"2056":1,"2057":2,"2059":1,"2063":2,"2064":1,"2073":1,"2074":1,"2079":2,"2082":1,"2087":1,"2089":1,"2097":1,"2105":1,"2107":2,"2110":2,"2124":1,"2126":1,"2130":1,"2131":4,"2132":1,"2137":1,"2138":2,"2150":1,"2151":1,"2152":1,"2156":4,"2158":1,"2164":1,"2165":1,"2166":1,"2173":1,"2177":2,"2180":1,"2183":2,"2186":1,"2194":1,"2197":2,"2227":1,"2230":1,"2232":7}}],["carrynomutablestaticstate",{"3":{"1436":3}}],["carrytheservicetagoftheowningapp",{"3":{"1436":1}}],["carrythetightratelimiterpolicy",{"2":{"180":1},"3":{"180":1}}],["carry",{"0":{"1235":1},"3":{"0":13,"1":2,"15":1,"24":1,"71":1,"81":1,"91":1,"98":1,"99":1,"109":1,"125":1,"126":1,"135":3,"136":1,"138":2,"139":1,"142":1,"147":1,"155":1,"161":1,"170":2,"176":1,"177":2,"179":1,"186":2,"195":1,"200":1,"207":2,"217":1,"225":1,"241":1,"259":1,"260":1,"261":1,"265":3,"279":1,"291":1,"300":1,"318":4,"324":2,"341":2,"343":2,"345":1,"350":1,"352":1,"355":1,"359":1,"361":1,"387":1,"390":1,"396":1,"401":2,"403":2,"404":1,"407":1,"419":1,"438":1,"443":1,"446":1,"450":1,"451":1,"461":1,"470":1,"490":1,"497":1,"505":1,"526":1,"530":1,"536":1,"543":1,"549":2,"558":1,"564":1,"572":1,"578":2,"583":1,"591":1,"594":1,"599":2,"603":1,"607":2,"609":3,"611":1,"618":2,"626":1,"629":1,"634":1,"635":1,"636":1,"640":1,"649":1,"657":1,"658":1,"659":1,"661":1,"668":1,"689":1,"690":2,"698":2,"718":1,"719":1,"724":1,"739":2,"741":1,"742":1,"743":1,"755":1,"764":1,"766":1,"774":1,"782":1,"783":1,"799":3,"801":1,"802":1,"826":1,"828":1,"831":1,"832":1,"838":3,"869":1,"873":1,"881":1,"884":1,"891":1,"905":1,"907":2,"926":1,"963":1,"964":1,"972":1,"982":1,"996":1,"1011":1,"1018":1,"1020":1,"1026":1,"1034":1,"1042":1,"1043":1,"1044":1,"1050":1,"1083":1,"1085":2,"1091":1,"1110":1,"1116":4,"1125":1,"1126":1,"1127":1,"1141":1,"1155":1,"1175":1,"1184":1,"1185":3,"1212":3,"1228":1,"1229":4,"1231":1,"1234":1,"1236":1,"1237":11,"1247":4,"1252":1,"1253":1,"1255":1,"1259":6,"1264":1,"1265":1,"1266":1,"1270":13,"1271":4,"1275":1,"1276":1,"1285":1,"1286":35,"1290":1,"1297":1,"1300":36,"1301":3,"1309":6,"1311":25,"1312":5,"1317":1,"1318":2,"1319":1,"1323":9,"1324":34,"1328":2,"1337":1,"1339":17,"1342":1,"1344":2,"1345":3,"1347":1,"1350":18,"1353":1,"1359":66,"1360":1,"1362":2,"1366":1,"1369":8,"1371":1,"1373":2,"1377":1,"1380":16,"1382":1,"1384":1,"1388":2,"1389":1,"1390":1,"1392":2,"1395":33,"1396":29,"1399":1,"1401":1,"1402":16,"1405":1,"1415":24,"1416":2,"1417":4,"1423":1,"1427":1,"1428":1,"1431":1,"1434":1,"1436":51,"1438":7,"1443":3,"1444":1,"1445":1,"1447":4,"1448":1,"1453":2,"1454":1,"1455":2,"1456":1,"1458":1,"1465":2,"1467":14,"1469":1,"1473":1,"1474":1,"1476":3,"1481":1,"1485":1,"1489":3,"1491":1,"1496":3,"1526":4,"1534":1,"1536":1,"1543":1,"1575":1,"1576":1,"1577":3,"1578":1,"1582":1,"1585":1,"1586":1,"1591":2,"1596":1,"1621":1,"1626":1,"1627":2,"1630":1,"1631":1,"1632":2,"1635":1,"1641":3,"1654":1,"1662":1,"1665":1,"1666":1,"1672":1,"1686":1,"1688":1,"1723":1,"1727":1,"1748":2,"1757":1,"1758":1,"1759":1,"1760":1,"1761":2,"1764":1,"1765":4,"1767":2,"1771":1,"1830":1,"1839":1,"1851":1,"1876":1,"1883":1,"1902":1,"1903":1,"1908":1,"1911":1,"1938":1,"1942":1,"1946":1,"1948":1,"1949":1,"1950":2,"1963":3,"1964":1,"1969":1,"1974":1,"1978":1,"1993":1,"1995":3,"1996":1,"2001":1,"2018":1,"2031":1,"2046":1,"2047":1,"2056":2,"2070":1,"2075":1,"2079":1,"2086":1,"2098":1,"2126":1,"2131":2,"2142":1,"2145":1,"2154":2,"2156":1,"2160":1,"2164":1,"2189":1,"2191":1,"2192":3,"2198":1,"2203":1,"2232":1}}],["carrying",{"3":{"0":4,"20":1,"53":1,"81":1,"109":2,"110":1,"111":1,"117":1,"122":1,"128":1,"147":1,"160":1,"180":1,"189":1,"193":1,"202":1,"212":1,"217":1,"225":2,"231":1,"243":2,"244":1,"247":1,"265":1,"305":1,"318":1,"328":1,"344":1,"349":1,"350":4,"365":1,"370":1,"387":1,"413":1,"420":1,"443":2,"448":1,"499":1,"538":1,"557":1,"609":1,"618":1,"626":1,"632":1,"688":1,"690":2,"715":2,"725":3,"740":1,"741":2,"782":1,"790":1,"804":1,"805":1,"810":1,"813":1,"827":1,"860":1,"863":1,"876":1,"881":1,"897":1,"914":1,"920":2,"921":1,"926":1,"945":1,"966":1,"971":1,"980":2,"987":1,"988":2,"996":1,"1004":1,"1026":2,"1034":1,"1042":1,"1049":1,"1050":1,"1051":1,"1054":1,"1067":1,"1119":1,"1168":1,"1175":1,"1212":2,"1219":1,"1223":1,"1224":1,"1225":1,"1229":9,"1237":8,"1239":1,"1240":1,"1241":1,"1243":1,"1245":1,"1247":4,"1249":1,"1256":2,"1259":6,"1263":3,"1265":1,"1266":1,"1270":16,"1271":3,"1273":1,"1275":1,"1277":1,"1280":1,"1284":1,"1286":26,"1294":1,"1297":2,"1300":24,"1301":2,"1304":1,"1305":1,"1309":11,"1310":2,"1311":21,"1312":5,"1317":1,"1318":1,"1320":1,"1323":26,"1324":30,"1332":1,"1335":1,"1336":1,"1339":11,"1347":1,"1349":1,"1350":8,"1352":1,"1353":1,"1358":1,"1359":37,"1361":1,"1364":1,"1369":3,"1373":1,"1374":2,"1378":1,"1379":1,"1380":13,"1384":2,"1389":1,"1391":1,"1395":27,"1396":48,"1398":1,"1402":21,"1405":1,"1406":1,"1409":1,"1410":1,"1415":27,"1416":6,"1417":10,"1423":2,"1426":1,"1427":1,"1430":2,"1434":1,"1436":55,"1438":8,"1446":1,"1452":1,"1453":1,"1458":1,"1465":1,"1467":3,"1488":2,"1489":2,"1492":1,"1534":1,"1577":2,"1627":1,"1631":1,"1641":1,"1651":1,"1666":2,"1667":1,"1685":2,"1702":1,"1723":1,"1749":2,"1750":1,"1765":1,"1769":3,"1790":1,"1792":1,"1805":2,"1806":1,"1815":1,"1821":1,"1840":1,"1865":1,"1871":1,"1908":1,"1909":1,"1922":2,"1924":1,"1927":3,"1929":1,"1933":2,"1937":1,"1974":1,"1975":1,"1976":2,"1977":2,"1978":1,"1988":1,"1994":1,"1995":1,"2018":1,"2036":2,"2055":1,"2057":1,"2083":1,"2110":1,"2126":1,"2136":1,"2149":1,"2158":1,"2175":1,"2186":1,"2230":1}}],["calcifies",{"3":{"1788":2}}],["calculation",{"3":{"1798":1,"2075":1}}],["calculations",{"3":{"1229":1}}],["calculated",{"3":{"1631":1,"1764":1}}],["calculateduration",{"2":{"1342":1},"3":{"1342":1,"1350":1}}],["calculatesimilarity",{"3":{"1359":2}}],["calculatejaccardindex",{"3":{"1359":1}}],["calmly",{"3":{"1396":1}}],["calm",{"3":{"1396":2}}],["calscale",{"3":{"1300":1}}],["calibrate",{"3":{"1229":1}}],["calibrated",{"3":{"0":1,"861":3,"1436":2,"1596":1}}],["calibration",{"3":{"862":1,"1263":1,"1339":1,"1369":1,"1577":2,"1578":1,"1592":1}}],["calibrating",{"3":{"862":1,"1237":1}}],["calendarexportmappertests",{"3":{"1199":1,"1207":2,"1359":2}}],["calendarexportmapper",{"2":{"1356":1},"3":{"1199":6,"1203":1,"1207":2,"1300":5,"1350":3,"1356":4,"1359":16,"1396":1,"1526":1}}],["calendars",{"0":{"1375":1},"3":{"1043":2,"1203":2,"1207":6,"1300":10,"1359":4,"1496":1}}],["calendar",{"0":{"1356":1},"3":{"706":1,"1192":2,"1300":13,"1324":4,"1350":1,"1351":1,"1356":1,"1359":18,"1375":3,"1380":7,"1396":1,"1436":4,"1438":1,"1455":2,"1496":2,"1535":1,"1631":1,"1640":1,"1660":1,"1948":2}}],["callout",{"3":{"1496":1}}],["callvirt",{"3":{"1436":3}}],["callcount",{"3":{"1427":2}}],["calldurationhistogramname",{"3":{"1427":1}}],["calldeadline",{"3":{"1309":1,"1312":1,"1380":3,"1396":3,"1415":2}}],["callrefreshasync",{"3":{"1300":1}}],["callgraphindex",{"3":{"1199":2,"1207":2,"1436":12,"1489":1,"1671":1}}],["callable",{"3":{"265":1,"497":1,"1271":1,"1286":3,"1359":3,"1395":3,"1396":1,"1417":1,"1436":1,"1548":1}}],["callee",{"3":{"39":1,"1161":1,"1324":1,"1436":7}}],["callees",{"3":{"39":1,"1436":2}}],["called",{"3":{"13":1,"39":2,"81":1,"92":1,"109":2,"117":1,"122":1,"201":1,"203":1,"243":1,"259":1,"260":1,"265":1,"268":1,"284":1,"325":2,"326":2,"336":1,"341":1,"344":1,"350":1,"397":2,"400":1,"407":1,"413":1,"423":3,"426":2,"428":2,"497":1,"499":2,"591":1,"652":1,"674":1,"697":1,"698":2,"717":1,"741":1,"743":1,"822":1,"837":1,"875":1,"881":1,"889":2,"905":1,"908":1,"926":2,"931":2,"947":1,"1006":1,"1108":1,"1170":1,"1175":1,"1191":1,"1229":4,"1237":9,"1247":9,"1254":1,"1259":9,"1262":1,"1263":1,"1266":1,"1270":13,"1273":1,"1274":1,"1278":2,"1281":1,"1286":29,"1297":1,"1300":17,"1301":3,"1307":1,"1309":15,"1310":2,"1311":24,"1312":5,"1315":2,"1316":1,"1318":1,"1323":23,"1324":27,"1333":1,"1335":1,"1339":6,"1350":9,"1352":1,"1359":30,"1369":6,"1378":1,"1380":2,"1386":1,"1389":1,"1395":14,"1396":29,"1402":5,"1409":1,"1415":11,"1416":3,"1417":16,"1427":5,"1436":22,"1442":4,"1443":2,"1457":1,"1460":1,"1467":1,"1473":1,"1474":1,"1488":2,"1496":2,"1526":1,"1556":1,"1600":2,"1662":1,"1780":1,"1825":1,"1909":1,"1925":1,"1936":2,"1948":1,"1954":1,"1982":2,"1994":1,"1995":1,"2043":1,"2105":1,"2119":1,"2156":2,"2164":1,"2167":2}}],["callerfilepathattribute",{"3":{"1436":1}}],["callerfilepath",{"3":{"1432":1,"1436":3}}],["calleruserid",{"3":{"1402":4}}],["callerhasprivilegedrole",{"3":{"1323":2}}],["callermembername",{"3":{"1312":2,"1380":1}}],["callerisorganizer",{"2":{"1354":1},"3":{"1270":3,"1354":1,"1359":6,"1373":1,"1380":1,"1402":15}}],["callerspeakerid",{"3":{"1402":15}}],["callerscopedquerycachetests",{"3":{"1199":1,"1207":5,"1264":1,"1270":2}}],["callers",{"3":{"0":6,"33":1,"109":1,"157":1,"175":1,"230":1,"243":1,"244":1,"258":1,"265":1,"320":1,"335":1,"388":1,"435":1,"449":1,"481":1,"497":1,"502":1,"507":1,"513":1,"543":1,"545":1,"550":2,"552":1,"659":1,"674":1,"827":1,"889":1,"899":1,"980":1,"988":1,"995":1,"996":1,"998":1,"1153":1,"1155":2,"1176":1,"1212":2,"1220":1,"1229":1,"1236":1,"1237":2,"1239":1,"1241":1,"1242":1,"1243":1,"1247":10,"1259":2,"1263":1,"1267":1,"1270":12,"1271":2,"1278":1,"1286":22,"1300":18,"1301":9,"1302":1,"1309":7,"1311":15,"1312":2,"1316":1,"1323":7,"1324":31,"1337":1,"1339":4,"1350":13,"1359":23,"1369":2,"1373":1,"1375":1,"1380":4,"1395":7,"1396":14,"1402":2,"1415":8,"1416":1,"1417":20,"1430":1,"1436":4,"1438":3,"1441":1,"1442":1,"1443":1,"1467":1,"1496":1,"1596":1,"1630":1,"1631":2,"1635":3,"1639":1,"1640":2,"1701":1,"1735":1,"1749":1,"1790":1,"1807":1,"1815":2,"1908":1,"1912":1,"1915":1,"1922":1,"1937":1,"1987":1,"1997":1,"2024":1,"2126":1,"2133":1,"2232":2}}],["caller",{"0":{"1937":1,"2185":1},"2":{"1844":1},"3":{"0":25,"31":1,"39":2,"42":2,"43":1,"53":1,"72":1,"90":1,"95":1,"96":1,"103":2,"107":1,"108":2,"109":5,"110":3,"111":1,"115":1,"122":3,"123":2,"125":2,"126":2,"135":1,"147":1,"157":5,"158":1,"159":2,"173":2,"175":3,"177":2,"178":1,"179":2,"180":3,"184":1,"186":2,"189":1,"201":1,"225":3,"228":2,"230":2,"231":1,"235":2,"241":2,"243":3,"246":5,"259":1,"284":1,"317":2,"318":12,"324":2,"330":1,"333":3,"340":1,"341":2,"342":3,"344":2,"350":1,"359":1,"365":1,"383":1,"384":2,"387":2,"388":3,"389":1,"400":1,"435":1,"447":2,"448":2,"449":2,"450":1,"459":2,"461":1,"465":1,"481":1,"490":1,"507":2,"517":1,"518":2,"519":1,"538":1,"544":1,"549":7,"550":1,"551":2,"552":1,"572":1,"597":1,"598":1,"599":3,"600":1,"619":1,"649":1,"656":1,"657":1,"658":1,"660":1,"674":2,"682":1,"683":1,"684":3,"690":2,"715":1,"717":2,"725":2,"727":3,"733":4,"735":2,"740":3,"741":1,"743":1,"749":1,"751":1,"766":1,"769":1,"789":1,"790":2,"791":2,"792":2,"793":1,"825":1,"826":2,"827":12,"829":1,"833":1,"854":1,"861":3,"864":1,"881":5,"896":1,"897":12,"899":1,"905":12,"906":2,"908":1,"921":2,"922":2,"924":1,"926":3,"948":1,"980":1,"986":1,"987":1,"988":5,"989":1,"990":2,"992":2,"996":2,"997":2,"998":1,"1018":1,"1019":1,"1021":1,"1026":1,"1042":3,"1043":1,"1050":1,"1059":2,"1091":3,"1092":2,"1093":2,"1100":2,"1101":2,"1116":2,"1123":1,"1124":1,"1126":2,"1132":1,"1133":3,"1140":1,"1150":1,"1161":2,"1168":1,"1169":2,"1176":1,"1212":2,"1219":1,"1227":1,"1228":3,"1229":16,"1231":1,"1232":1,"1237":20,"1238":2,"1241":4,"1242":3,"1243":1,"1244":1,"1247":44,"1251":1,"1253":1,"1259":19,"1262":1,"1263":6,"1264":8,"1265":5,"1267":6,"1269":1,"1270":100,"1271":20,"1273":1,"1275":1,"1276":4,"1278":1,"1283":1,"1285":2,"1286":139,"1287":2,"1289":1,"1290":3,"1293":2,"1296":2,"1297":4,"1299":1,"1300":139,"1301":22,"1302":1,"1304":2,"1305":1,"1306":2,"1307":2,"1308":2,"1309":49,"1310":7,"1311":90,"1312":14,"1318":1,"1322":1,"1323":81,"1324":127,"1326":1,"1332":1,"1337":1,"1339":44,"1343":1,"1346":3,"1348":2,"1350":45,"1352":1,"1353":2,"1356":1,"1357":6,"1358":2,"1359":256,"1365":1,"1366":2,"1368":2,"1369":6,"1372":1,"1373":4,"1375":3,"1376":1,"1378":2,"1379":2,"1380":65,"1389":3,"1395":40,"1396":196,"1398":1,"1399":3,"1401":5,"1402":128,"1407":1,"1408":1,"1410":2,"1411":2,"1414":1,"1415":64,"1416":3,"1417":57,"1419":1,"1422":5,"1423":10,"1424":1,"1425":1,"1426":1,"1427":27,"1432":2,"1435":1,"1436":73,"1437":1,"1438":17,"1447":1,"1455":2,"1456":2,"1467":5,"1487":1,"1488":2,"1496":1,"1507":1,"1508":1,"1548":1,"1553":2,"1577":2,"1589":1,"1591":1,"1596":1,"1627":1,"1631":4,"1632":8,"1639":1,"1640":4,"1641":2,"1653":2,"1656":1,"1663":1,"1665":1,"1666":1,"1671":1,"1672":2,"1686":2,"1698":1,"1703":1,"1708":2,"1723":2,"1737":1,"1748":2,"1759":1,"1764":2,"1765":7,"1767":2,"1771":1,"1803":3,"1804":1,"1805":3,"1806":1,"1807":2,"1810":1,"1813":1,"1815":3,"1817":1,"1821":6,"1823":1,"1824":1,"1825":1,"1831":2,"1839":1,"1840":1,"1844":3,"1852":1,"1857":1,"1871":1,"1873":1,"1874":1,"1875":1,"1876":2,"1877":3,"1883":1,"1898":1,"1908":2,"1909":5,"1911":2,"1912":1,"1919":4,"1920":6,"1922":3,"1923":3,"1924":3,"1925":2,"1926":1,"1927":1,"1930":1,"1933":2,"1934":2,"1937":2,"1938":1,"1940":1,"1941":2,"1944":1,"1945":2,"1947":2,"1950":2,"1962":1,"1963":1,"1982":4,"1988":1,"1991":1,"1992":1,"1993":3,"1994":10,"1995":6,"1996":1,"1997":2,"2000":3,"2002":2,"2016":1,"2019":1,"2024":6,"2038":1,"2055":1,"2057":1,"2066":1,"2078":1,"2090":1,"2096":1,"2117":2,"2129":2,"2130":2,"2131":2,"2132":1,"2133":1,"2137":1,"2139":1,"2143":1,"2165":1,"2166":3,"2168":1,"2169":1,"2171":1,"2174":4,"2175":2,"2179":2,"2180":2,"2185":2,"2190":1,"2193":2,"2196":2,"2197":1}}],["callbackpath",{"3":{"1311":1}}],["callbackscheme",{"3":{"1416":2}}],["callbacks",{"3":{"180":1,"248":1,"413":1,"744":1,"1259":1,"1323":1,"1324":4,"1385":1,"1387":1,"1390":1,"1395":2,"1402":8,"1416":5,"1417":5,"1447":1,"1555":1,"1560":1,"1973":1}}],["callback",{"1":{"417":1,"418":1,"419":1,"420":1,"421":1,"422":1,"423":1,"424":1,"425":1,"426":1,"427":1,"428":1,"429":1,"430":1},"3":{"0":6,"243":1,"248":3,"265":1,"348":1,"350":4,"351":1,"355":1,"383":1,"397":1,"413":2,"417":1,"418":2,"420":3,"422":1,"427":2,"428":1,"463":1,"518":1,"556":3,"733":1,"831":2,"832":3,"1100":1,"1128":1,"1212":1,"1223":1,"1229":5,"1243":1,"1247":3,"1259":1,"1286":1,"1290":1,"1300":10,"1301":15,"1309":1,"1310":3,"1311":14,"1316":1,"1323":9,"1324":32,"1333":1,"1339":9,"1380":1,"1391":1,"1395":26,"1396":8,"1400":1,"1402":3,"1406":1,"1415":5,"1416":9,"1417":33,"1436":2,"1443":2,"1496":2,"1599":1,"1662":1,"1667":1,"1669":1,"1868":1,"1883":1,"1898":1,"1916":1,"1944":1,"1945":1,"1947":1,"1973":2,"1975":5,"1976":1,"1979":1,"2074":1,"2121":2,"2122":1,"2232":1}}],["calling",{"3":{"0":5,"91":1,"115":1,"214":1,"224":1,"237":2,"243":1,"265":2,"283":1,"310":1,"380":1,"409":1,"418":1,"459":1,"638":1,"657":1,"676":1,"696":1,"700":1,"733":2,"735":1,"741":1,"751":1,"766":2,"840":1,"880":1,"881":1,"906":1,"912":1,"922":1,"924":1,"959":1,"960":1,"979":1,"1012":1,"1016":1,"1017":1,"1018":1,"1020":1,"1042":1,"1090":2,"1134":1,"1184":1,"1212":1,"1229":2,"1232":1,"1234":1,"1237":5,"1241":1,"1251":1,"1259":4,"1263":1,"1270":13,"1271":3,"1275":1,"1286":23,"1291":1,"1300":11,"1301":2,"1304":1,"1309":9,"1311":13,"1312":2,"1315":1,"1316":1,"1317":1,"1323":11,"1324":21,"1336":1,"1339":12,"1350":7,"1353":1,"1359":32,"1365":1,"1366":1,"1369":4,"1376":1,"1378":1,"1380":8,"1395":11,"1396":12,"1402":11,"1415":5,"1416":2,"1417":13,"1425":1,"1427":5,"1432":1,"1436":20,"1438":2,"1467":1,"1476":1,"1488":2,"1526":2,"1540":1,"1553":1,"1577":1,"1578":1,"1641":1,"1687":1,"1790":1,"1805":1,"1821":1,"1844":1,"1882":1,"1914":1,"1944":1,"1983":1,"2019":1,"2027":1,"2074":1,"2085":1,"2113":1,"2150":2,"2170":1,"2171":1,"2185":2,"2232":1}}],["calls",{"1":{"50":1,"51":1,"52":1,"53":1,"54":1,"55":1},"3":{"0":17,"26":1,"27":2,"35":1,"39":2,"41":1,"50":1,"52":2,"57":1,"59":2,"64":1,"74":1,"96":1,"105":1,"120":1,"127":1,"135":1,"175":1,"179":1,"180":1,"185":1,"186":1,"195":2,"200":1,"201":1,"202":1,"206":1,"207":1,"209":1,"214":1,"215":1,"220":1,"225":1,"226":1,"230":1,"243":3,"245":1,"255":2,"256":3,"257":1,"258":2,"259":3,"260":5,"261":3,"279":1,"280":1,"281":2,"283":1,"284":1,"285":1,"303":1,"308":1,"310":1,"318":1,"326":1,"340":1,"341":2,"343":1,"350":4,"354":1,"359":1,"362":1,"370":2,"374":1,"387":1,"390":1,"391":1,"395":2,"397":1,"401":1,"402":3,"405":1,"413":1,"418":1,"420":2,"423":2,"425":1,"426":1,"428":2,"429":1,"430":1,"434":1,"435":1,"453":1,"459":2,"461":1,"464":1,"492":1,"493":1,"494":1,"497":2,"499":2,"502":1,"506":1,"507":1,"517":1,"518":1,"519":1,"522":2,"523":3,"524":1,"534":1,"536":2,"539":3,"545":1,"546":1,"550":2,"551":1,"552":1,"572":1,"583":4,"585":1,"599":2,"603":1,"609":1,"626":1,"640":2,"642":1,"644":1,"649":2,"651":1,"665":2,"667":1,"674":3,"677":2,"682":1,"684":1,"690":1,"696":1,"698":2,"705":1,"707":1,"708":1,"715":3,"724":1,"725":1,"733":4,"749":5,"750":1,"774":2,"819":3,"826":1,"827":2,"829":1,"832":1,"836":1,"837":2,"838":2,"839":2,"857":2,"861":1,"880":1,"881":1,"883":2,"884":1,"885":1,"905":1,"913":1,"922":2,"923":1,"926":1,"946":1,"954":2,"955":1,"956":1,"964":1,"972":1,"979":2,"987":2,"988":2,"996":1,"1011":1,"1012":3,"1013":1,"1017":2,"1018":5,"1020":1,"1022":1,"1034":1,"1042":2,"1050":2,"1052":1,"1055":1,"1059":3,"1062":1,"1090":3,"1091":1,"1093":1,"1094":2,"1095":1,"1100":1,"1101":1,"1108":1,"1133":2,"1161":2,"1162":1,"1168":2,"1179":1,"1183":2,"1185":1,"1202":2,"1203":2,"1212":6,"1213":1,"1222":1,"1229":5,"1231":1,"1233":2,"1235":2,"1236":1,"1237":17,"1240":1,"1244":1,"1245":1,"1247":20,"1249":1,"1251":1,"1256":1,"1257":1,"1258":1,"1259":31,"1262":2,"1263":2,"1265":4,"1270":34,"1271":15,"1274":2,"1275":2,"1276":1,"1281":1,"1282":1,"1283":1,"1286":99,"1287":1,"1289":1,"1292":2,"1297":2,"1300":61,"1301":13,"1308":1,"1309":38,"1310":4,"1311":72,"1312":16,"1315":1,"1316":3,"1317":1,"1323":52,"1324":135,"1326":2,"1329":1,"1330":1,"1332":1,"1333":4,"1336":2,"1339":48,"1340":1,"1344":1,"1346":1,"1347":1,"1349":2,"1350":42,"1352":2,"1353":1,"1354":2,"1355":1,"1359":153,"1360":1,"1362":1,"1365":2,"1366":1,"1368":1,"1369":15,"1372":2,"1376":1,"1377":2,"1378":3,"1380":21,"1382":1,"1383":1,"1384":1,"1385":1,"1388":1,"1395":89,"1396":92,"1399":1,"1400":1,"1401":2,"1402":42,"1405":1,"1409":1,"1414":1,"1415":38,"1416":12,"1417":72,"1421":2,"1423":2,"1424":1,"1426":1,"1427":20,"1429":1,"1431":1,"1432":1,"1436":119,"1438":5,"1441":6,"1442":2,"1443":7,"1444":3,"1447":3,"1450":1,"1454":1,"1455":3,"1456":1,"1457":1,"1466":1,"1467":9,"1475":1,"1476":1,"1488":4,"1489":2,"1490":2,"1491":5,"1492":4,"1496":1,"1508":1,"1526":8,"1527":1,"1534":1,"1537":1,"1544":1,"1548":1,"1549":1,"1553":4,"1568":1,"1572":2,"1577":2,"1582":2,"1591":1,"1592":1,"1599":1,"1600":1,"1605":1,"1632":2,"1639":1,"1640":1,"1641":1,"1647":1,"1651":2,"1653":6,"1656":2,"1663":1,"1677":1,"1685":1,"1701":2,"1719":1,"1730":2,"1739":1,"1781":1,"1789":1,"1790":1,"1793":1,"1799":1,"1810":1,"1817":1,"1825":1,"1829":1,"1830":1,"1831":2,"1839":1,"1840":2,"1865":1,"1869":1,"1871":1,"1874":1,"1881":1,"1882":1,"1883":1,"1884":2,"1885":1,"1908":1,"1910":3,"1913":1,"1916":1,"1918":1,"1922":1,"1927":3,"1929":1,"1933":4,"1935":2,"1938":2,"1940":2,"1944":1,"1946":1,"1960":1,"1963":1,"1968":2,"1970":2,"1971":1,"1975":2,"1978":1,"2000":2,"2001":2,"2006":2,"2010":1,"2014":1,"2015":2,"2016":2,"2017":1,"2018":2,"2020":1,"2022":3,"2024":2,"2028":1,"2029":1,"2030":2,"2037":1,"2048":1,"2055":2,"2063":2,"2074":1,"2078":1,"2083":1,"2091":1,"2092":1,"2096":1,"2097":1,"2117":1,"2118":2,"2119":3,"2126":1,"2127":2,"2142":2,"2143":1,"2149":1,"2152":1,"2153":1,"2156":4,"2160":1,"2164":1,"2171":3,"2172":2,"2178":1,"2179":2,"2186":1,"2189":1,"2199":1,"2202":2,"2232":2}}],["call",{"0":{"1422":1,"1423":1,"1425":1,"2156":1,"2166":1,"2172":1},"2":{"629":1,"766":1,"767":1,"1089":1,"1425":1,"1456":1,"1457":1,"1460":1,"2177":1},"3":{"0":39,"17":1,"24":1,"26":1,"27":1,"39":1,"41":1,"53":1,"68":1,"71":3,"73":1,"74":2,"93":1,"94":2,"95":5,"97":1,"98":2,"99":1,"100":3,"104":1,"109":5,"110":1,"111":5,"113":1,"117":1,"121":3,"122":1,"126":1,"127":2,"128":3,"135":3,"156":1,"180":1,"188":2,"200":1,"202":1,"207":1,"208":1,"209":2,"210":1,"216":1,"223":1,"230":7,"231":1,"237":1,"241":1,"243":5,"245":4,"246":1,"250":1,"253":1,"254":1,"255":2,"256":2,"257":1,"258":1,"259":3,"260":3,"261":2,"265":1,"273":1,"279":4,"281":1,"283":1,"285":1,"295":2,"303":1,"308":1,"310":5,"314":2,"320":1,"323":1,"325":2,"330":1,"336":1,"353":1,"361":1,"387":1,"390":2,"391":1,"392":1,"395":2,"397":1,"398":1,"401":3,"407":2,"408":2,"413":1,"418":2,"420":1,"424":1,"426":1,"427":1,"428":1,"429":1,"430":1,"434":3,"438":1,"444":2,"446":3,"448":5,"449":1,"453":1,"454":2,"459":5,"460":1,"463":1,"465":1,"466":2,"472":1,"481":2,"483":1,"489":1,"499":2,"502":1,"507":1,"508":1,"509":1,"511":1,"513":1,"514":1,"517":2,"518":1,"522":3,"523":2,"524":3,"536":2,"538":3,"539":1,"544":1,"545":3,"548":1,"549":4,"550":2,"552":2,"556":4,"559":1,"567":1,"583":1,"584":3,"585":1,"591":1,"592":2,"593":1,"594":1,"599":1,"603":2,"618":2,"626":1,"628":1,"629":2,"632":1,"633":2,"641":1,"642":1,"647":2,"649":3,"650":1,"655":1,"656":1,"657":1,"658":1,"661":1,"663":2,"665":6,"667":1,"668":1,"669":2,"672":2,"674":7,"676":1,"682":1,"690":3,"696":5,"698":2,"700":2,"703":1,"707":2,"715":2,"716":1,"724":2,"725":1,"731":1,"733":7,"735":1,"741":1,"748":3,"749":7,"750":1,"751":2,"752":2,"757":1,"759":1,"766":4,"767":1,"768":1,"774":4,"777":1,"782":1,"784":2,"789":1,"790":1,"791":1,"797":1,"798":1,"799":3,"800":1,"802":1,"803":2,"810":1,"814":2,"819":6,"821":2,"826":2,"827":5,"829":1,"831":1,"832":1,"837":1,"838":1,"846":2,"848":1,"850":2,"854":1,"861":4,"881":6,"882":2,"883":1,"889":2,"890":1,"896":1,"897":9,"899":2,"900":2,"905":1,"913":1,"920":1,"922":2,"924":2,"926":3,"930":1,"934":2,"946":3,"948":1,"949":1,"950":1,"952":1,"954":5,"955":2,"957":1,"964":2,"965":2,"966":1,"972":1,"980":1,"988":4,"989":1,"990":1,"994":2,"995":1,"996":3,"999":1,"1012":1,"1016":1,"1017":2,"1018":8,"1019":3,"1020":2,"1021":1,"1028":2,"1034":1,"1035":1,"1050":2,"1051":4,"1052":1,"1059":1,"1067":1,"1089":2,"1090":1,"1091":7,"1092":5,"1093":5,"1094":1,"1095":1,"1099":1,"1100":1,"1102":2,"1103":1,"1126":1,"1127":1,"1132":2,"1133":3,"1134":3,"1136":1,"1150":1,"1156":1,"1168":3,"1170":1,"1175":3,"1176":3,"1182":1,"1183":1,"1184":1,"1185":1,"1186":3,"1188":1,"1212":6,"1217":1,"1220":2,"1221":2,"1226":1,"1229":10,"1234":1,"1236":2,"1237":20,"1239":1,"1240":1,"1241":1,"1243":1,"1244":2,"1246":1,"1247":53,"1249":1,"1252":2,"1253":2,"1259":22,"1261":1,"1262":6,"1263":5,"1265":2,"1267":1,"1269":1,"1270":41,"1271":14,"1273":3,"1275":1,"1276":1,"1278":3,"1279":1,"1280":1,"1283":1,"1286":134,"1287":1,"1289":1,"1290":1,"1291":1,"1293":2,"1297":1,"1298":2,"1300":79,"1301":21,"1302":2,"1303":1,"1304":1,"1309":27,"1310":10,"1311":85,"1312":32,"1313":1,"1315":2,"1316":4,"1318":4,"1322":1,"1323":84,"1324":197,"1326":1,"1327":1,"1328":1,"1329":1,"1331":3,"1332":2,"1333":3,"1335":1,"1337":2,"1338":3,"1339":99,"1342":1,"1343":1,"1347":2,"1350":61,"1352":1,"1354":1,"1358":4,"1359":214,"1362":3,"1365":2,"1366":9,"1367":1,"1368":4,"1369":25,"1372":1,"1377":1,"1378":2,"1380":41,"1382":2,"1383":1,"1384":2,"1385":1,"1387":2,"1388":2,"1390":3,"1391":1,"1392":2,"1393":1,"1395":134,"1396":155,"1398":2,"1401":1,"1402":39,"1408":1,"1409":1,"1410":2,"1413":2,"1414":1,"1415":79,"1416":18,"1417":81,"1418":3,"1420":2,"1421":3,"1422":2,"1423":4,"1425":4,"1427":61,"1430":2,"1431":1,"1432":1,"1435":1,"1436":164,"1438":16,"1441":7,"1442":8,"1443":5,"1444":4,"1445":1,"1446":2,"1447":3,"1448":1,"1449":1,"1450":1,"1453":1,"1456":3,"1457":1,"1460":2,"1466":1,"1467":5,"1470":1,"1472":1,"1475":4,"1476":1,"1477":1,"1479":1,"1482":2,"1486":1,"1487":1,"1488":4,"1489":5,"1490":4,"1491":1,"1492":1,"1496":4,"1510":1,"1526":9,"1530":1,"1531":2,"1532":1,"1534":1,"1544":1,"1553":3,"1555":1,"1566":2,"1572":1,"1581":1,"1582":2,"1591":2,"1596":1,"1599":1,"1600":1,"1627":3,"1630":1,"1631":1,"1632":4,"1636":1,"1637":1,"1639":1,"1651":1,"1653":4,"1659":1,"1663":1,"1664":1,"1665":1,"1667":1,"1668":3,"1669":1,"1670":2,"1671":1,"1673":1,"1675":1,"1684":4,"1685":2,"1686":3,"1701":1,"1719":2,"1720":1,"1721":1,"1724":1,"1734":1,"1749":1,"1765":3,"1783":1,"1789":2,"1790":5,"1792":1,"1803":1,"1805":1,"1806":5,"1808":1,"1810":4,"1811":1,"1813":1,"1815":5,"1817":2,"1818":1,"1821":1,"1824":2,"1825":2,"1835":1,"1839":2,"1840":1,"1842":1,"1843":1,"1848":1,"1851":1,"1865":3,"1867":1,"1881":2,"1882":1,"1883":3,"1886":2,"1895":1,"1898":1,"1902":1,"1908":2,"1915":3,"1916":3,"1919":2,"1920":1,"1923":2,"1924":2,"1927":1,"1932":1,"1933":1,"1934":2,"1936":1,"1937":1,"1939":1,"1940":2,"1946":1,"1951":1,"1954":1,"1955":1,"1960":1,"1962":1,"1963":2,"1964":1,"1966":1,"1968":1,"1970":3,"1971":3,"1972":1,"1973":1,"1979":1,"1989":1,"1992":1,"1994":1,"1995":3,"2000":3,"2006":2,"2008":1,"2009":1,"2010":1,"2011":1,"2012":1,"2013":1,"2016":1,"2017":2,"2018":1,"2019":3,"2020":1,"2024":3,"2029":1,"2030":4,"2032":3,"2033":1,"2035":1,"2038":1,"2046":1,"2047":1,"2057":1,"2063":2,"2070":1,"2071":1,"2074":2,"2076":1,"2079":2,"2080":1,"2082":1,"2083":2,"2086":1,"2087":1,"2093":1,"2095":1,"2096":1,"2098":1,"2113":1,"2119":2,"2131":3,"2133":2,"2135":1,"2136":2,"2145":1,"2150":2,"2151":1,"2152":1,"2154":2,"2156":5,"2157":1,"2160":3,"2163":1,"2164":1,"2165":2,"2166":1,"2167":3,"2168":2,"2169":1,"2170":3,"2171":2,"2172":2,"2174":5,"2175":2,"2176":1,"2177":2,"2179":5,"2180":6,"2186":1,"2189":2,"2190":1,"2193":1,"2198":1,"2199":1,"2202":3,"2209":1,"2230":1,"2232":2}}],["cachingtestentity",{"3":{"1199":2,"1207":1}}],["cachingdecoratortenantscopingtests",{"3":{"1199":1,"1207":2,"1438":2}}],["cachingdecoratorconstructorselectiontests",{"3":{"1199":1,"1207":6,"1438":2}}],["cachingcommanddecoratortests",{"3":{"1199":1,"1207":6}}],["cachingcommanddecorator",{"2":{"1260":1,"1753":1,"1774":1,"1918":1},"3":{"698":1,"1199":6,"1203":1,"1207":2,"1260":1,"1263":9,"1267":2,"1270":30,"1301":8,"1323":1,"1359":11,"1436":1,"1753":1,"1774":1,"1918":1}}],["cachingquerydecoratortests",{"3":{"1199":1,"1207":13,"1270":1}}],["cachingquerydecorator",{"2":{"123":1,"246":1,"1223":1,"1267":1,"1917":1,"1923":1},"3":{"123":2,"246":2,"674":2,"698":1,"733":1,"1199":6,"1203":1,"1207":3,"1223":2,"1229":2,"1260":1,"1263":6,"1267":11,"1268":1,"1270":56,"1300":6,"1301":16,"1323":5,"1436":1,"1467":1,"1496":2,"1917":1,"1923":1}}],["caching",{"0":{"1379":1,"1668":1,"1708":1,"1922":1},"1":{"240":1,"241":1,"242":1,"243":1,"244":1,"245":1,"246":1,"247":1,"248":1,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"260":1,"261":1,"385":1,"386":1,"387":1,"388":1,"389":1,"390":1,"391":1,"392":1,"393":1,"1301":1,"1818":1,"1819":1,"1820":1,"1821":1,"1822":1,"1823":1,"1824":1,"1825":1,"1826":1,"1827":1},"2":{"253":1,"259":1,"732":1,"733":1,"736":1,"999":1,"1301":2,"1339":1,"1915":1,"1916":1},"3":{"0":13,"25":1,"115":1,"117":2,"118":1,"121":3,"122":5,"123":1,"124":1,"126":2,"157":1,"160":1,"162":1,"186":1,"190":1,"235":1,"239":1,"240":1,"242":3,"243":16,"244":1,"245":5,"246":2,"247":2,"252":1,"253":1,"255":4,"256":1,"258":2,"259":3,"260":3,"261":3,"285":2,"300":1,"343":1,"385":1,"387":1,"389":1,"390":2,"391":1,"392":1,"400":1,"460":1,"674":4,"677":1,"701":1,"702":1,"732":3,"733":8,"736":4,"737":1,"740":1,"789":1,"885":1,"897":4,"924":1,"980":2,"996":6,"999":2,"1011":1,"1149":1,"1150":2,"1151":1,"1157":1,"1192":3,"1202":3,"1203":19,"1207":69,"1208":4,"1212":7,"1223":1,"1229":1,"1237":2,"1241":1,"1247":8,"1259":6,"1261":2,"1263":2,"1264":1,"1267":1,"1268":1,"1270":36,"1271":1,"1278":1,"1286":14,"1296":1,"1300":8,"1301":105,"1309":2,"1310":1,"1311":34,"1313":1,"1316":7,"1320":5,"1323":23,"1324":25,"1332":2,"1339":9,"1352":2,"1359":15,"1375":2,"1379":1,"1380":4,"1395":8,"1396":5,"1409":1,"1415":6,"1417":2,"1421":2,"1427":2,"1432":1,"1436":4,"1437":1,"1438":6,"1441":1,"1442":1,"1445":1,"1487":1,"1496":11,"1507":4,"1526":3,"1549":2,"1556":1,"1560":1,"1577":5,"1600":1,"1602":1,"1651":5,"1654":2,"1660":1,"1662":3,"1668":2,"1685":2,"1686":1,"1700":2,"1708":1,"1720":1,"1721":2,"1727":1,"1730":1,"1748":1,"1786":1,"1817":1,"1818":3,"1820":1,"1821":3,"1823":1,"1824":1,"1827":1,"1851":2,"1879":1,"1914":1,"1915":1,"1916":1,"1918":1,"1919":1,"1922":1,"1923":2,"1924":2,"2031":1,"2092":1,"2095":1,"2096":3,"2097":1,"2111":2,"2134":1,"2135":1,"2136":2,"2139":1,"2156":1,"2190":1,"2206":1,"2208":1,"2227":2,"2228":1,"2230":1,"2232":2}}],["cacheexpireson",{"3":{"1396":1}}],["cacheexpirationhours",{"2":{"157":1},"3":{"157":1,"1311":3}}],["cachettl",{"3":{"1395":2}}],["cachetag",{"3":{"1311":1}}],["cachecontrol",{"3":{"1286":4,"1323":1,"1339":1}}],["cacheinvalidating",{"3":{"1270":4}}],["cacheinvalidatingtestcommand",{"3":{"1199":3,"1207":1}}],["cachereachable",{"3":{"1311":1}}],["cachereadfailurequery",{"3":{"1199":2,"1207":1}}],["cachereadfailuremetricquery",{"3":{"1199":2,"1207":1}}],["cachereadcanceledquery",{"3":{"1199":2,"1207":1}}],["cacherequestasync",{"2":{"261":1},"3":{"243":1,"261":1,"1311":1}}],["cachepipelinetestcommand",{"3":{"1199":2,"1207":1}}],["cacheprobeentity",{"3":{"1199":2,"1207":1}}],["cacheprefix",{"2":{"924":1,"925":1,"1918":1,"1920":1,"2092":1},"3":{"0":1,"117":1,"922":1,"924":1,"925":1,"926":1,"1264":1,"1265":1,"1270":15,"1301":1,"1311":1,"1323":1,"1359":51,"1380":1,"1415":6,"1651":1,"1685":1,"1686":1,"1918":2,"1920":1,"2092":1}}],["cachemissmetricquery",{"3":{"1199":2,"1207":1}}],["cachehitmetricquery",{"3":{"1199":2,"1207":1}}],["cacheability",{"3":{"389":1}}],["cacheabletestquery",{"3":{"1199":3,"1207":1}}],["cacheable",{"3":{"123":2,"243":1,"246":2,"633":1,"1270":5,"1301":4,"1311":3,"1700":1,"1726":1,"1727":1,"1730":1,"1920":2,"1923":1,"2097":1}}],["cachevarybyrules",{"2":{"243":1,"246":1,"247":1,"740":2,"1922":1},"3":{"243":1,"246":1,"247":1,"388":1,"740":2,"1311":2,"1324":1,"1922":1}}],["cacheoptionstests",{"3":{"1199":1,"1207":2,"1301":1}}],["cacheoptions",{"2":{"243":2,"252":1,"254":1,"736":1,"1708":2,"1916":1},"3":{"243":4,"252":2,"254":4,"256":2,"257":2,"259":1,"261":1,"736":1,"1199":7,"1203":1,"1207":2,"1301":26,"1708":2,"1916":1}}],["cachekeys",{"3":{"1701":1,"1727":1,"1730":1,"2097":1}}],["cachekeynamespace",{"2":{"996":1,"999":1},"3":{"733":2,"996":1,"999":1,"1199":8,"1203":1,"1207":1,"1301":20,"1323":6,"1496":2}}],["cachekeylocks",{"3":{"733":1,"1199":2,"1203":1,"1207":1,"1260":1,"1267":1,"1270":2,"1300":1,"1301":4,"1496":2}}],["cachekey",{"2":{"123":1,"248":1,"1730":1,"1920":1,"2097":1},"3":{"117":1,"123":2,"246":1,"248":1,"1264":1,"1270":12,"1286":2,"1324":6,"1359":1,"1496":1,"1651":1,"1685":1,"1701":1,"1730":1,"1920":2,"2096":1,"2097":1}}],["cachekeyprefixoptions",{"3":{"674":1,"1199":4,"1203":1,"1207":1,"1301":9,"1323":1,"1496":2}}],["cachekeyprefix",{"3":{"25":1,"246":1,"996":1,"998":1,"999":1,"1207":2,"1301":35,"1311":1,"1320":1,"1323":1,"1496":2}}],["cachesettingstests",{"3":{"1199":1,"1207":2,"1301":1,"1323":2}}],["cachesettings",{"2":{"731":2,"1708":1,"1916":1},"3":{"674":1,"731":1,"733":3,"1192":1,"1199":8,"1203":1,"1207":2,"1301":33,"1320":2,"1323":5,"1496":1,"1708":2,"1916":1}}],["cacheservicegetorcreatetests",{"3":{"1199":1,"1207":3,"1301":1}}],["cacheservice",{"3":{"459":1,"465":1,"1300":1,"1323":1,"1415":1}}],["cacheseconds",{"2":{"1061":1,"2199":1,"2202":1},"3":{"0":3,"235":1,"830":1,"833":1,"1059":1,"1061":2,"1286":3,"1297":1,"1300":1,"1332":1,"1339":2,"1447":1,"2199":1,"2202":2}}],["caches",{"3":{"33":1,"162":1,"235":1,"243":2,"245":1,"390":1,"391":1,"441":1,"459":1,"507":1,"545":2,"592":1,"733":1,"735":1,"827":1,"830":1,"868":1,"964":1,"1052":1,"1055":1,"1229":1,"1237":3,"1239":1,"1241":1,"1242":2,"1247":8,"1259":3,"1270":5,"1278":1,"1279":1,"1286":15,"1288":1,"1300":12,"1301":3,"1305":1,"1310":1,"1323":4,"1324":4,"1334":1,"1339":1,"1359":4,"1385":1,"1387":2,"1395":4,"1396":3,"1411":1,"1415":1,"1417":1,"1421":1,"1436":2,"1438":1,"1453":1,"1455":1,"1456":1,"1567":1,"1666":1,"1672":1,"1748":1,"1753":1,"1774":2,"1815":2,"1849":1,"1912":1,"1914":1,"1915":1,"1916":1,"1917":1,"1922":2,"1923":3,"1924":1,"1934":1,"2024":1,"2069":1,"2127":1,"2172":1}}],["cachedirectory",{"3":{"1417":1}}],["cacheduntil",{"3":{"1395":3}}],["cacheduration",{"2":{"463":1,"2096":1},"3":{"117":1,"463":1,"1264":1,"1270":8,"1301":2,"1359":2,"1396":1,"1496":1,"1651":1,"2096":1}}],["cachedresult",{"3":{"1311":1}}],["cachedroles",{"3":{"1286":2}}],["cachedkeyset",{"3":{"1300":1}}],["cachedsuccess",{"3":{"1229":1}}],["cachedoublecheckmetricquery",{"3":{"1199":2,"1207":1}}],["cachedpage",{"3":{"1199":2,"1203":1,"1207":1,"1324":6}}],["cachedcompile",{"2":{"591":1},"3":{"591":2}}],["cachedhealthreportprovidertests",{"3":{"1199":1,"1207":4,"1339":2}}],["cachedhealthreportprovider",{"2":{"233":1,"235":1,"830":1,"833":1,"1447":1},"3":{"0":1,"233":1,"235":3,"830":3,"833":1,"1199":3,"1203":1,"1207":3,"1332":3,"1339":20,"1447":1,"1496":2}}],["cached",{"3":{"0":9,"21":1,"24":1,"155":1,"157":3,"159":2,"160":2,"174":1,"176":1,"184":1,"201":1,"235":1,"243":2,"245":1,"248":1,"255":2,"330":1,"387":1,"388":2,"390":1,"391":3,"392":1,"459":5,"460":1,"461":1,"463":1,"545":1,"697":1,"698":2,"733":2,"741":2,"743":1,"825":1,"830":1,"905":1,"922":1,"924":1,"1028":2,"1061":1,"1091":3,"1116":1,"1134":1,"1212":2,"1221":1,"1229":2,"1230":1,"1236":1,"1237":6,"1238":1,"1242":3,"1243":1,"1244":1,"1247":15,"1249":1,"1250":2,"1252":1,"1259":8,"1263":1,"1270":18,"1278":4,"1286":24,"1294":1,"1295":1,"1298":1,"1300":21,"1301":17,"1307":1,"1309":3,"1310":5,"1311":24,"1322":1,"1323":3,"1324":23,"1332":1,"1339":8,"1350":3,"1352":1,"1359":31,"1369":1,"1373":2,"1376":2,"1379":2,"1380":10,"1391":4,"1395":28,"1396":11,"1402":1,"1406":1,"1407":1,"1415":6,"1416":3,"1417":6,"1426":2,"1427":2,"1436":6,"1438":4,"1447":1,"1456":1,"1458":3,"1507":1,"1535":1,"1556":2,"1589":1,"1591":2,"1596":1,"1631":3,"1638":1,"1640":4,"1651":1,"1662":2,"1668":1,"1748":3,"1758":1,"1765":3,"1774":1,"1815":1,"1821":1,"1823":1,"1851":1,"1865":1,"1867":2,"1895":2,"1908":3,"1909":2,"1911":2,"1913":1,"1914":1,"1915":1,"1916":1,"1917":2,"1918":1,"1920":1,"1922":4,"1923":2,"1924":1,"1945":1,"1972":1,"1988":1,"1999":1,"2000":1,"2047":1,"2066":1,"2096":1,"2136":1,"2149":1,"2150":1,"2156":1,"2176":2,"2202":1,"2232":2}}],["cache",{"0":{"1294":1,"1915":1,"1921":1},"1":{"240":1,"241":1,"242":1,"243":1,"244":1,"245":1,"246":1,"247":1,"248":1,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"260":1,"261":1,"851":1,"852":1,"853":1,"854":1,"855":1,"856":1,"857":1,"1913":1,"1914":1,"1915":1,"1916":1,"1917":1,"1918":1,"1919":1,"1920":1,"1921":1,"1922":1,"1923":1,"1924":1},"2":{"255":1,"258":1,"400":1,"731":1,"897":1,"899":1,"1708":1,"1919":1,"1922":1},"3":{"0":47,"15":1,"25":4,"116":1,"117":2,"120":2,"122":1,"140":1,"155":1,"157":8,"158":1,"159":4,"161":3,"175":1,"176":1,"177":1,"181":1,"186":1,"190":1,"195":1,"212":1,"217":4,"233":1,"234":1,"235":7,"236":1,"237":3,"240":1,"241":6,"242":3,"243":17,"244":2,"245":3,"246":4,"247":1,"248":4,"249":2,"253":1,"255":6,"256":1,"258":3,"259":4,"282":1,"283":3,"285":1,"328":1,"330":1,"333":2,"337":2,"343":1,"350":1,"351":1,"373":2,"382":1,"386":3,"387":4,"388":3,"389":1,"390":4,"391":1,"392":1,"400":3,"441":1,"443":1,"444":1,"459":13,"460":2,"461":7,"463":6,"464":2,"465":3,"549":1,"550":1,"592":6,"593":1,"595":1,"657":1,"665":1,"666":2,"672":2,"674":3,"698":4,"699":3,"700":1,"731":5,"732":5,"733":5,"734":4,"735":3,"736":1,"737":3,"740":5,"742":2,"743":1,"744":1,"745":2,"749":1,"753":1,"757":1,"788":1,"792":2,"793":1,"794":1,"826":4,"828":1,"851":1,"852":1,"853":1,"854":3,"855":2,"856":2,"869":3,"871":1,"872":2,"879":1,"881":4,"885":1,"896":3,"897":7,"899":2,"922":1,"923":2,"924":1,"995":2,"996":6,"997":2,"998":4,"999":2,"1000":1,"1012":1,"1028":2,"1029":1,"1042":1,"1058":1,"1059":2,"1091":1,"1093":2,"1103":1,"1116":4,"1150":1,"1156":1,"1192":2,"1202":2,"1203":2,"1212":6,"1213":1,"1223":2,"1229":7,"1235":1,"1237":9,"1239":1,"1241":3,"1242":3,"1244":1,"1247":29,"1249":1,"1259":10,"1260":1,"1263":7,"1264":1,"1265":1,"1267":9,"1268":2,"1270":87,"1273":1,"1275":1,"1278":1,"1285":1,"1286":32,"1287":1,"1288":1,"1292":1,"1294":3,"1298":1,"1300":62,"1301":101,"1304":1,"1309":5,"1310":6,"1311":72,"1316":1,"1320":3,"1323":41,"1324":52,"1332":6,"1337":1,"1339":42,"1348":3,"1350":18,"1352":1,"1359":128,"1360":1,"1367":1,"1368":1,"1369":5,"1373":3,"1375":3,"1379":8,"1380":50,"1387":1,"1388":3,"1391":1,"1395":53,"1396":24,"1397":1,"1400":1,"1402":2,"1406":1,"1407":1,"1409":1,"1415":21,"1416":1,"1417":29,"1421":2,"1427":3,"1429":1,"1436":9,"1437":3,"1438":27,"1439":1,"1441":1,"1442":5,"1443":5,"1445":2,"1446":1,"1447":1,"1453":5,"1454":1,"1455":12,"1456":5,"1461":1,"1465":8,"1467":3,"1469":2,"1487":3,"1492":3,"1496":15,"1507":1,"1511":1,"1526":3,"1534":1,"1535":1,"1545":1,"1549":1,"1577":7,"1591":6,"1600":4,"1630":1,"1631":1,"1635":1,"1637":1,"1638":1,"1640":2,"1641":4,"1651":7,"1660":1,"1662":2,"1664":2,"1667":4,"1668":9,"1669":1,"1670":1,"1672":2,"1675":1,"1695":1,"1702":1,"1708":10,"1727":1,"1748":5,"1753":2,"1765":5,"1774":1,"1779":1,"1818":1,"1819":1,"1821":7,"1822":3,"1827":1,"1849":1,"1851":4,"1908":4,"1909":14,"1911":3,"1912":6,"1913":5,"1914":2,"1915":1,"1916":5,"1917":4,"1918":6,"1919":1,"1920":7,"1921":5,"1922":12,"1923":15,"1924":9,"1934":2,"1949":1,"1975":2,"1979":1,"1988":2,"1989":5,"2000":1,"2001":6,"2002":2,"2003":1,"2006":2,"2007":3,"2012":3,"2024":2,"2047":3,"2066":3,"2069":1,"2076":1,"2079":1,"2089":1,"2096":3,"2126":1,"2127":3,"2128":1,"2135":1,"2136":1,"2149":4,"2150":1,"2153":1,"2156":8,"2160":1,"2179":2,"2180":1,"2184":1,"2199":3,"2203":3,"2208":1,"2232":9}}],["cat",{"3":{"2220":1}}],["categorized",{"3":{"1631":1}}],["categorization",{"3":{"1395":1,"1764":1}}],["categoriessynced",{"3":{"1350":1,"1359":1}}],["categoriesmanage",{"3":{"1350":2,"1359":1,"1373":1,"1380":7}}],["categoriescontroller",{"2":{"1874":1,"1986":1},"3":{"897":1,"900":1,"1311":4,"1591":1,"1874":1,"1986":1}}],["categoriescache",{"2":{"392":1,"393":1},"3":{"175":1,"392":1,"393":1,"1350":1,"1379":1,"1380":2}}],["categories",{"0":{"1493":1,"1796":1},"1":{"1780":1,"1781":1,"1782":1,"1783":1,"1784":1,"1785":1,"1786":1,"1787":1},"2":{"1359":1,"1380":1},"3":{"0":2,"109":1,"110":1,"111":1,"186":1,"545":1,"556":1,"782":1,"881":2,"897":1,"1004":1,"1011":2,"1012":5,"1193":1,"1202":1,"1203":53,"1207":160,"1212":1,"1216":4,"1224":1,"1229":7,"1237":2,"1247":1,"1270":7,"1301":1,"1310":3,"1311":1,"1318":1,"1322":1,"1324":4,"1342":2,"1344":1,"1347":1,"1350":31,"1352":1,"1353":1,"1354":1,"1358":2,"1359":175,"1363":1,"1367":1,"1369":6,"1380":24,"1381":1,"1382":3,"1383":1,"1384":4,"1385":1,"1387":2,"1395":54,"1415":2,"1416":1,"1417":1,"1436":6,"1437":1,"1438":3,"1447":1,"1452":1,"1458":1,"1465":1,"1467":2,"1471":1,"1495":1,"1499":2,"1502":1,"1519":2,"1523":2,"1526":1,"1532":1,"1536":2,"1554":1,"1565":1,"1574":4,"1578":1,"1580":2,"1583":1,"1586":2,"1588":3,"1591":1,"1592":1,"1594":1,"1597":1,"1598":1,"1621":1,"1625":1,"1629":4,"1630":1,"1631":3,"1632":5,"1636":2,"1637":1,"1638":2,"1639":4,"1640":1,"1673":2,"1677":1,"1742":1,"1746":4,"1748":9,"1750":6,"1763":1,"1764":1,"1768":3,"1771":1,"1773":1,"1777":1,"1779":1,"1780":1,"1784":3,"1794":1,"1796":4,"1799":1,"1801":1,"1802":1,"1807":1,"2109":1,"2110":2,"2114":1,"2115":1,"2141":1,"2162":1,"2205":1}}],["categorydto",{"3":{"1748":1}}],["categorydetail",{"3":{"1591":1,"1600":1}}],["categorydistribution",{"3":{"1350":2,"1395":1}}],["categorydistributiondto",{"3":{"1199":10,"1203":1,"1207":4,"1347":1,"1350":10,"1359":4,"1380":1}}],["categorybuilder",{"3":{"1436":1}}],["categoryweight",{"3":{"1359":2}}],["categorygroups",{"3":{"1359":1}}],["categorygroupdistribution",{"2":{"1347":1},"3":{"1199":6,"1203":1,"1207":1,"1347":1,"1350":4,"1359":1}}],["categorytitlerequiredkey",{"3":{"1395":1}}],["categorytitles",{"3":{"1395":4}}],["categorytitle",{"3":{"1350":1,"1395":8}}],["categorytests",{"3":{"1199":1,"1207":2}}],["categorylisttests",{"3":{"1591":2,"1600":1}}],["categorylistpage",{"3":{"1436":1}}],["categorylist",{"3":{"1324":1,"1591":2}}],["categoryentityqueryservice",{"3":{"1247":2}}],["categoryname",{"2":{"1990":1},"3":{"1247":3,"1990":1}}],["categorycreated",{"3":{"1631":1,"1634":1}}],["categorycreate",{"3":{"1596":1}}],["categorycreaterequest",{"3":{"1270":1}}],["categoryconfiguration",{"3":{"1286":1,"1369":1,"1767":1}}],["categorycommandvalidatortests",{"3":{"1207":2,"1359":1}}],["categorychanged",{"2":{"1634":1},"3":{"782":2,"1199":4,"1203":1,"1207":2,"1259":2,"1345":1,"1350":10,"1359":4,"1385":1,"1395":1,"1631":1,"1634":1,"1748":1,"1769":1}}],["categoryscore",{"3":{"1359":1}}],["categorysyncstrategytests",{"3":{"1199":1,"1207":2,"1359":1}}],["categorysyncstrategy",{"3":{"1199":3,"1203":1,"1207":2,"1358":1,"1359":11}}],["categoryservice",{"3":{"881":1,"1324":1,"1395":1}}],["category=aieval",{"3":{"1018":1,"1435":1,"1438":1,"1455":1,"1492":1}}],["categoryinvariantstests",{"3":{"1199":1,"1207":2}}],["categoryinvariants",{"2":{"1344":1,"1350":1,"1359":1,"1369":1},"3":{"1198":1,"1199":14,"1203":1,"1207":2,"1344":5,"1350":16,"1359":25,"1369":5,"1767":2}}],["categoryid",{"2":{"891":1,"1776":1},"3":{"891":1,"1271":1,"1350":9,"1359":21,"1362":1,"1369":3,"1380":9,"1395":4,"1634":1,"1640":1,"1755":1,"1764":1,"1776":1}}],["categoryitemid",{"3":{"1350":13,"1359":24,"1369":6,"1380":10,"1395":8,"1631":2,"1635":2}}],["categoryitemidentifiertype",{"3":{"1350":9,"1359":14,"1380":4,"1395":12}}],["categoryiteminfo",{"3":{"1199":16,"1203":1,"1207":1,"1387":1,"1395":14}}],["categoryitemcount",{"2":{"1354":1},"3":{"1350":3,"1354":1,"1359":5}}],["categoryitemconfiguration",{"3":{"1199":2,"1203":1,"1207":2,"1350":1,"1359":2,"1362":2,"1369":10}}],["categoryitemchanged",{"2":{"1345":1,"1634":1},"3":{"1199":2,"1203":1,"1207":2,"1345":1,"1350":10,"1359":5,"1631":1,"1634":1}}],["categoryitemnamemaxlength",{"2":{"1369":1},"3":{"1350":3,"1359":5,"1369":2}}],["categoryitemnames",{"3":{"1350":2,"1395":1}}],["categoryitemname",{"3":{"1350":1,"1395":1}}],["categoryitemnamerules",{"3":{"1199":4,"1203":1,"1207":1,"1353":1,"1359":13}}],["categoryitemnavigationpopulatortests",{"3":{"1199":1,"1207":2,"1359":2}}],["categoryitemnavigationpopulator",{"3":{"1199":3,"1203":1,"1207":2,"1310":3,"1359":4}}],["categoryitemlookupservice",{"2":{"1387":1},"3":{"1199":3,"1203":1,"1207":2,"1300":2,"1324":4,"1380":1,"1387":2,"1395":12}}],["categoryitemdtomappertests",{"3":{"1199":1,"1207":2}}],["categoryitemdtomapper",{"3":{"1199":9,"1203":1,"1207":2,"1350":4,"1353":2,"1359":11}}],["categoryitemdto",{"3":{"1199":13,"1203":1,"1207":2,"1347":1,"1350":15,"1359":9,"1380":1,"1384":1,"1395":13}}],["categoryitemdistribution",{"2":{"1347":1},"3":{"1199":6,"1203":1,"1207":1,"1347":1,"1350":7,"1359":3}}],["categoryitemsexcept",{"3":{"1395":1}}],["categoryitemservice",{"2":{"1382":1},"3":{"881":1,"1199":2,"1203":1,"1207":2,"1382":3,"1395":7}}],["categoryitemssynced",{"3":{"1350":1,"1359":1}}],["categoryitems",{"2":{"1354":1},"3":{"1310":2,"1350":12,"1354":1,"1359":25,"1363":1,"1369":2,"1380":3,"1395":8,"1447":1,"1630":1,"1632":4,"1636":1,"1639":8,"1640":1}}],["categoryitemscontrollertests",{"3":{"1199":1,"1207":2}}],["categoryitemscontroller",{"2":{"1371":1,"1372":1},"3":{"1199":2,"1203":1,"1207":4,"1311":4,"1359":24,"1371":2,"1372":2,"1373":1,"1374":1,"1380":26,"1436":1}}],["categoryitemsortrules",{"3":{"1199":4,"1203":1,"1207":1,"1359":12}}],["categoryitem",{"2":{"891":1},"3":{"891":1,"1198":1,"1199":16,"1203":1,"1207":2,"1237":1,"1340":1,"1342":2,"1343":1,"1344":1,"1350":43,"1359":41,"1369":9,"1380":6,"1395":1,"1630":6,"1631":2,"1632":2,"1634":2,"1635":3,"1638":5,"1639":6,"1640":3}}],["categoryassignparentupdatehandler",{"2":{"543":1,"551":1,"924":1,"927":1},"3":{"543":1,"545":1,"550":1,"551":1,"924":1,"927":1,"1270":2}}],["category",{"0":{"1216":1,"1450":1,"1461":1,"1469":1,"1516":1},"1":{"1009":1,"1010":1,"1011":1,"1012":1,"1013":1,"1014":1,"1794":1,"1795":1,"1796":1,"1797":1,"1798":1,"1799":1,"1800":1,"1801":1,"1802":1},"2":{"11":1,"804":1,"924":1,"1594":1,"1990":1,"2110":1},"3":{"0":7,"11":1,"109":4,"110":3,"111":1,"168":1,"188":1,"390":1,"403":1,"550":1,"804":1,"819":1,"860":1,"882":1,"924":2,"963":1,"972":1,"1009":1,"1010":1,"1011":5,"1012":4,"1013":2,"1014":1,"1017":1,"1022":1,"1028":1,"1090":2,"1190":1,"1192":2,"1193":2,"1194":1,"1198":1,"1199":52,"1201":1,"1202":1,"1203":2,"1207":3,"1216":3,"1222":1,"1229":4,"1237":6,"1241":1,"1247":10,"1259":1,"1270":2,"1286":3,"1301":1,"1310":5,"1311":1,"1312":1,"1315":1,"1317":1,"1320":1,"1323":2,"1324":7,"1339":4,"1340":1,"1342":3,"1343":4,"1344":1,"1347":2,"1348":1,"1350":124,"1351":2,"1352":1,"1353":1,"1354":1,"1359":264,"1362":3,"1369":14,"1371":2,"1375":1,"1376":1,"1380":17,"1382":1,"1385":1,"1386":1,"1387":2,"1390":1,"1395":98,"1396":1,"1416":1,"1417":3,"1418":1,"1427":2,"1431":2,"1436":13,"1437":1,"1438":5,"1440":1,"1450":1,"1456":1,"1457":1,"1458":1,"1459":1,"1461":1,"1467":5,"1469":1,"1487":1,"1492":1,"1493":1,"1496":5,"1497":1,"1498":1,"1499":2,"1518":2,"1519":2,"1521":1,"1523":1,"1526":7,"1527":1,"1529":2,"1530":1,"1531":1,"1532":3,"1533":1,"1535":3,"1536":2,"1537":10,"1553":1,"1575":2,"1577":3,"1578":1,"1580":1,"1581":1,"1582":1,"1583":3,"1584":3,"1585":1,"1586":1,"1588":1,"1591":5,"1592":2,"1594":1,"1595":1,"1597":2,"1598":2,"1600":2,"1611":1,"1629":1,"1630":11,"1631":3,"1632":31,"1634":5,"1635":2,"1636":3,"1637":4,"1638":5,"1639":16,"1640":7,"1673":2,"1674":1,"1702":1,"1742":1,"1746":6,"1748":8,"1750":6,"1755":3,"1764":9,"1767":5,"1768":1,"1769":4,"1773":2,"1776":4,"1777":1,"1779":1,"1780":2,"1784":8,"1787":1,"1793":1,"1794":2,"1796":5,"1797":4,"1798":2,"1799":6,"1800":3,"1801":1,"1802":5,"1827":1,"1845":1,"1903":1,"1925":1,"1929":1,"1930":1,"1957":1,"1990":1,"2040":2,"2042":1,"2043":2,"2048":3,"2050":1,"2051":1,"2053":1,"2055":1,"2059":2,"2060":1,"2062":4,"2069":1,"2085":1,"2110":1,"2114":1,"2115":1,"2133":1,"2153":1,"2155":2,"2160":1,"2162":1,"2169":1,"2171":1,"2180":1,"2182":1,"2193":1,"2195":1,"2203":1,"2204":1,"2205":1,"2211":1,"2214":1,"2221":1,"2231":2,"2232":2,"2245":1}}],["catalyst",{"3":{"682":1,"684":1,"1416":2,"1417":4,"1660":1,"1669":1}}],["catalogroutepaths",{"3":{"1591":1}}],["catalogrouteauthorizationtests",{"3":{"1436":1,"1591":1}}],["catalogintegrationtestfixture",{"3":{"1436":1}}],["catalogcrossservicefactory",{"3":{"1436":1}}],["catalogcache",{"2":{"392":1,"393":1},"3":{"392":1,"393":3}}],["catalogtestwebapplicationfactory",{"3":{"1436":1}}],["cataloged",{"3":{"1428":1,"1436":1}}],["catalogued",{"3":{"1301":1,"1347":1,"1350":1,"1403":1,"1496":1}}],["catalogue",{"3":{"974":1,"1237":2,"1311":2,"1324":2,"1344":2,"1348":1,"1369":1,"1394":1,"1395":4,"1436":1}}],["cataloguimodule",{"3":{"649":1,"1324":2,"1591":1}}],["cataloguserdataexportsection",{"2":{"583":1,"728":1},"3":{"725":1,"728":2}}],["catalogmoduletests",{"3":{"1436":2}}],["catalogmoduleseeder",{"3":{"1323":2}}],["catalogmoduledbseeder",{"3":{"1286":2}}],["catalogmodule",{"2":{"586":1},"3":{"583":2,"584":1,"586":2,"675":1,"676":1,"1323":1}}],["catalogbrowse",{"2":{"557":1,"558":1},"3":{"556":6,"557":1,"558":1,"1324":1,"1591":9,"1596":1,"1597":1,"1600":1}}],["catalogproductdetail",{"3":{"556":1,"1324":1,"1591":1,"1596":1,"1597":1}}],["catalogpermissiongrants",{"3":{"186":1}}],["catalogpermissions",{"2":{"1264":1},"3":{"186":1,"1026":1,"1264":1,"1767":1}}],["catalogs",{"3":{"387":1,"1436":1,"1755":1}}],["catalogfeatures",{"2":{"1748":1,"1765":1},"3":{"300":1,"1270":1,"1748":1,"1765":1}}],["catalog",{"0":{"1685":1,"1748":1},"1":{"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1,"1659":1,"1660":1,"1661":1,"1662":1,"1663":1,"1664":1,"1665":1,"1666":1,"1667":1,"1668":1,"1669":1,"1670":1,"1671":1,"1672":1,"1673":1,"1674":1,"1675":1},"2":{"62":1,"493":1,"1588":1,"1751":2,"1769":2,"2201":1},"3":{"0":3,"42":4,"62":1,"80":5,"91":4,"93":1,"94":2,"95":1,"97":2,"99":1,"101":2,"104":1,"109":2,"117":2,"128":4,"136":1,"142":1,"184":1,"185":1,"186":2,"195":5,"235":2,"238":1,"243":8,"245":2,"249":1,"250":2,"251":2,"252":2,"254":2,"257":2,"259":1,"260":6,"261":3,"291":1,"295":1,"325":1,"326":1,"341":1,"345":1,"387":2,"390":4,"391":8,"392":4,"393":1,"401":2,"403":1,"446":1,"448":6,"452":3,"453":1,"454":1,"459":1,"468":1,"470":4,"475":4,"476":7,"477":4,"493":1,"545":9,"552":2,"556":6,"564":1,"572":4,"583":13,"585":4,"586":4,"599":2,"602":1,"640":2,"643":1,"649":3,"650":1,"657":5,"658":1,"674":4,"675":2,"676":2,"715":1,"725":1,"728":3,"733":1,"743":1,"749":2,"752":1,"757":2,"759":1,"774":2,"780":1,"782":9,"790":1,"798":2,"804":4,"837":2,"838":2,"861":1,"864":1,"869":1,"876":1,"881":1,"889":2,"891":1,"897":10,"900":2,"914":1,"924":5,"927":3,"954":7,"990":1,"1023":1,"1024":1,"1025":7,"1026":24,"1028":2,"1029":3,"1030":1,"1054":1,"1059":7,"1082":4,"1085":1,"1086":2,"1168":6,"1207":1,"1212":2,"1237":7,"1247":4,"1262":1,"1265":1,"1270":14,"1271":5,"1286":14,"1297":2,"1300":25,"1301":6,"1307":1,"1311":34,"1312":4,"1323":18,"1324":31,"1339":15,"1348":2,"1350":12,"1359":1,"1369":2,"1380":14,"1393":1,"1395":10,"1396":30,"1401":1,"1402":2,"1405":1,"1414":1,"1415":6,"1431":1,"1436":53,"1438":1,"1467":1,"1486":1,"1488":1,"1489":1,"1496":2,"1500":1,"1541":1,"1588":2,"1591":24,"1596":6,"1597":1,"1599":1,"1600":10,"1602":1,"1621":2,"1627":1,"1659":1,"1671":1,"1681":1,"1683":1,"1685":5,"1686":1,"1687":1,"1727":1,"1728":1,"1742":3,"1746":26,"1748":9,"1749":4,"1750":6,"1751":15,"1755":1,"1757":2,"1758":1,"1760":1,"1761":1,"1763":4,"1764":15,"1765":32,"1767":5,"1768":3,"1769":12,"1771":3,"1772":6,"1773":7,"1775":2,"1777":1,"1785":1,"1875":1,"1893":1,"1918":2,"1922":4,"1923":2,"1957":1,"1960":1,"1963":2,"2055":1,"2087":1,"2110":2,"2111":1,"2113":1,"2131":6,"2138":1,"2156":1,"2200":3,"2201":3,"2203":1,"2228":1,"2232":1}}],["catastrophic",{"3":{"0":1,"791":1,"861":1,"1237":1,"1309":1,"1323":1,"1577":1,"1706":1,"1707":1,"1836":1,"1849":1,"2079":1,"2166":1}}],["catchable",{"3":{"1259":1,"1436":2}}],["catchesadomainreachinganothermodulesapplication",{"3":{"1436":1}}],["catches",{"3":{"18":1,"125":1,"127":1,"132":1,"133":1,"135":1,"148":1,"225":1,"255":1,"345":1,"465":1,"486":1,"490":1,"491":1,"492":1,"493":1,"494":1,"518":1,"566":1,"573":1,"593":1,"609":1,"698":1,"709":1,"718":1,"767":2,"863":1,"897":1,"956":1,"982":1,"988":1,"990":1,"1018":1,"1019":2,"1021":1,"1102":1,"1169":1,"1237":1,"1254":1,"1259":2,"1270":1,"1286":5,"1300":3,"1311":2,"1312":4,"1315":1,"1323":4,"1324":9,"1339":3,"1359":3,"1380":1,"1396":6,"1402":2,"1410":1,"1415":3,"1416":2,"1417":6,"1423":1,"1436":18,"1438":2,"1455":3,"1456":1,"1458":1,"1475":1,"1476":1,"1488":2,"1489":1,"1492":1,"1496":2,"1524":1,"1526":1,"1535":1,"1605":3,"1654":1,"1739":3,"1805":1,"1806":1,"1923":1,"1927":2,"1929":2,"1930":1,"1961":1,"1999":1,"2018":1,"2036":1,"2041":1,"2048":1,"2100":2,"2106":2,"2108":1,"2126":1,"2169":1,"2175":1}}],["catching",{"3":{"0":1,"590":1,"881":1,"1247":1,"1270":1,"1286":3,"1300":1,"1304":1,"1323":2,"1324":9,"1395":2,"1396":5,"1415":2,"1416":1,"1417":5,"1427":1,"1431":1,"1436":9,"1438":1,"1455":2,"1492":2,"1663":1,"1669":1,"1702":1,"2002":1,"2047":1,"2048":1,"2100":1,"2117":1,"2165":1}}],["catch",{"0":{"1919":1},"2":{"21":1,"111":1,"512":1,"537":1,"684":1,"896":1,"899":1,"1218":1,"1803":1,"1804":1,"1919":1,"2169":1},"3":{"0":7,"21":1,"107":1,"108":1,"109":6,"110":2,"111":2,"135":1,"180":3,"231":1,"258":1,"295":1,"303":1,"310":1,"344":1,"413":1,"426":1,"434":1,"435":1,"436":1,"466":1,"512":1,"517":1,"523":1,"529":1,"536":2,"537":1,"559":1,"565":1,"574":1,"619":1,"684":1,"692":2,"700":1,"707":1,"709":1,"724":1,"726":1,"727":1,"765":1,"790":1,"801":1,"810":1,"819":1,"827":2,"840":3,"842":1,"861":1,"896":1,"897":2,"899":1,"914":1,"972":1,"988":1,"1018":1,"1019":2,"1093":1,"1218":1,"1225":1,"1229":4,"1233":1,"1247":6,"1254":1,"1259":7,"1263":2,"1270":5,"1276":1,"1286":13,"1289":2,"1300":8,"1301":4,"1302":1,"1304":1,"1309":9,"1311":10,"1312":3,"1323":16,"1324":29,"1334":1,"1337":1,"1339":7,"1350":1,"1352":1,"1357":1,"1358":1,"1359":16,"1366":2,"1378":2,"1380":25,"1395":14,"1396":23,"1399":1,"1402":7,"1415":9,"1416":2,"1417":20,"1425":2,"1427":3,"1435":2,"1436":36,"1438":4,"1447":3,"1453":1,"1455":1,"1466":1,"1467":1,"1487":1,"1488":1,"1489":3,"1496":7,"1524":1,"1547":1,"1577":1,"1662":1,"1670":1,"1671":1,"1765":1,"1774":1,"1803":1,"1804":3,"1805":1,"1806":1,"1812":1,"1835":1,"1852":1,"1858":1,"1881":1,"1919":1,"1920":1,"1927":3,"1933":2,"1934":2,"1940":1,"1946":2,"1952":1,"1954":1,"1959":1,"1970":1,"1980":1,"1994":1,"1998":1,"2032":1,"2041":1,"2043":1,"2049":1,"2054":1,"2059":1,"2067":3,"2070":1,"2075":1,"2078":1,"2079":1,"2099":1,"2165":1,"2167":2,"2169":1,"2177":1,"2232":1}}],["cantidad",{"3":{"1686":1}}],["canvas",{"3":{"1480":3}}],["canevaluatepolicy",{"3":{"1417":1}}],["canmoderate",{"3":{"1402":7}}],["canmanage=",{"3":{"1395":2}}],["canmanage",{"3":{"1380":2,"1389":1,"1395":2}}],["canreview",{"3":{"1748":1,"1765":1}}],["canrendereventpicker",{"3":{"1395":2}}],["canreadsessionassetsasync",{"3":{"1359":6}}],["canreadtoken",{"3":{"1300":1,"1324":1}}],["canbookmark",{"3":{"1395":1}}],["cansubmitlink",{"3":{"1395":2}}],["canserve",{"3":{"1324":4,"1395":2}}],["canned",{"3":{"1359":1,"1432":2,"1434":1,"1436":14,"1671":1}}],["cannot",{"0":{"1234":1,"1936":1,"2025":1,"2113":1},"1":{"1869":1,"1870":1,"1871":1,"1872":1,"1873":1,"1874":1,"1875":1,"1876":1,"1877":1},"3":{"0":35,"17":1,"19":2,"24":2,"26":1,"31":3,"32":1,"33":1,"38":1,"39":1,"41":1,"68":2,"78":1,"81":1,"91":1,"92":2,"93":1,"94":1,"98":1,"100":1,"103":1,"109":1,"110":2,"119":1,"120":1,"122":1,"123":1,"130":1,"133":1,"134":1,"135":3,"136":1,"137":3,"147":1,"149":1,"150":1,"157":2,"158":1,"160":1,"168":1,"187":1,"189":1,"201":1,"203":1,"206":1,"207":1,"212":1,"213":2,"214":1,"215":3,"216":1,"224":1,"225":1,"227":1,"233":1,"234":1,"235":4,"236":1,"243":1,"248":2,"249":1,"253":1,"255":3,"265":4,"267":1,"279":1,"280":1,"284":1,"285":1,"309":2,"310":1,"317":2,"318":3,"319":1,"324":1,"326":1,"330":2,"331":2,"341":2,"342":1,"343":2,"350":3,"353":1,"359":2,"363":1,"365":1,"370":1,"371":1,"380":1,"384":1,"387":1,"390":1,"397":3,"399":1,"408":1,"412":2,"413":1,"420":1,"422":1,"423":1,"433":1,"435":1,"449":2,"451":1,"459":1,"461":1,"465":1,"498":1,"500":1,"506":1,"509":3,"511":1,"513":1,"517":3,"518":2,"519":2,"526":1,"528":1,"529":5,"530":3,"535":1,"536":2,"537":3,"545":2,"546":2,"547":2,"549":1,"551":1,"556":2,"557":1,"558":1,"564":1,"565":1,"568":1,"571":1,"574":1,"576":2,"584":1,"585":1,"591":1,"593":1,"601":2,"608":1,"609":2,"610":1,"617":1,"619":1,"620":2,"625":3,"628":1,"633":2,"635":2,"640":1,"649":1,"651":1,"658":1,"660":1,"664":1,"674":1,"676":1,"681":1,"689":2,"690":1,"691":2,"692":1,"698":4,"699":1,"709":1,"714":2,"717":1,"718":3,"725":1,"726":2,"733":3,"734":1,"741":3,"742":3,"743":1,"757":2,"758":1,"762":1,"767":1,"773":1,"775":1,"784":1,"789":1,"790":1,"792":1,"793":1,"794":1,"800":1,"810":2,"818":1,"819":1,"821":1,"827":6,"828":1,"832":1,"840":1,"848":1,"853":1,"854":5,"855":2,"857":1,"861":2,"862":1,"870":1,"871":1,"872":1,"881":3,"883":1,"888":1,"889":3,"896":1,"903":1,"904":2,"905":5,"906":1,"908":1,"914":1,"921":1,"922":3,"924":1,"926":4,"931":1,"939":1,"940":1,"955":1,"956":1,"957":1,"966":1,"972":1,"974":1,"988":2,"996":5,"997":2,"998":1,"1004":1,"1017":1,"1018":3,"1019":2,"1020":2,"1026":2,"1028":2,"1029":1,"1033":1,"1034":1,"1042":2,"1043":2,"1044":1,"1050":1,"1052":2,"1059":3,"1060":1,"1067":2,"1074":1,"1083":1,"1085":2,"1091":2,"1092":2,"1093":1,"1100":1,"1101":2,"1102":1,"1110":1,"1115":1,"1116":4,"1124":2,"1125":1,"1126":1,"1132":2,"1133":2,"1134":1,"1135":1,"1145":1,"1155":1,"1160":1,"1162":1,"1175":1,"1176":1,"1177":1,"1184":1,"1185":4,"1191":1,"1212":8,"1221":1,"1223":1,"1224":1,"1226":1,"1228":1,"1229":10,"1232":1,"1233":1,"1234":2,"1235":1,"1237":11,"1239":3,"1240":1,"1241":6,"1243":4,"1247":33,"1248":1,"1252":1,"1253":2,"1254":2,"1256":1,"1257":1,"1259":15,"1262":1,"1263":1,"1265":3,"1267":1,"1270":29,"1271":13,"1273":2,"1274":2,"1275":2,"1276":2,"1277":2,"1278":3,"1279":1,"1281":2,"1283":1,"1284":1,"1286":81,"1288":2,"1289":2,"1290":2,"1292":2,"1293":2,"1294":2,"1297":3,"1298":1,"1300":74,"1301":19,"1302":1,"1305":3,"1307":2,"1308":1,"1309":28,"1310":7,"1311":61,"1312":7,"1316":1,"1317":1,"1318":1,"1320":4,"1322":3,"1323":63,"1324":105,"1326":2,"1327":1,"1328":1,"1331":1,"1332":4,"1336":1,"1337":3,"1339":58,"1344":1,"1346":1,"1348":1,"1349":1,"1350":41,"1352":3,"1353":1,"1354":1,"1355":1,"1357":2,"1358":2,"1359":221,"1362":3,"1366":5,"1367":2,"1368":1,"1369":14,"1371":1,"1372":2,"1373":1,"1374":1,"1375":1,"1378":2,"1379":1,"1380":32,"1384":1,"1385":1,"1386":1,"1389":2,"1391":1,"1392":1,"1393":1,"1395":58,"1396":103,"1398":1,"1399":5,"1401":3,"1402":44,"1405":2,"1406":4,"1407":2,"1408":1,"1409":1,"1414":2,"1415":63,"1416":9,"1417":30,"1419":1,"1421":1,"1423":2,"1426":1,"1427":12,"1428":2,"1429":1,"1430":3,"1431":5,"1432":1,"1433":1,"1435":3,"1436":160,"1438":23,"1441":6,"1442":3,"1443":3,"1444":2,"1445":2,"1446":1,"1447":1,"1449":2,"1452":1,"1453":11,"1454":5,"1455":17,"1456":1,"1458":1,"1459":7,"1460":1,"1465":4,"1467":15,"1469":1,"1472":1,"1474":3,"1475":3,"1476":1,"1477":2,"1481":2,"1482":1,"1483":1,"1484":1,"1486":3,"1487":1,"1488":5,"1489":4,"1490":3,"1491":1,"1492":7,"1496":2,"1498":1,"1500":1,"1513":1,"1524":1,"1526":8,"1531":3,"1534":3,"1537":1,"1553":1,"1575":2,"1577":4,"1582":1,"1584":1,"1585":2,"1589":1,"1591":2,"1596":1,"1605":2,"1611":2,"1630":2,"1631":8,"1632":3,"1635":3,"1638":1,"1639":10,"1640":3,"1641":9,"1642":1,"1644":1,"1648":1,"1651":2,"1653":1,"1654":1,"1661":2,"1666":2,"1667":4,"1668":1,"1669":2,"1670":3,"1671":3,"1673":1,"1676":1,"1677":2,"1679":1,"1681":1,"1685":6,"1686":9,"1692":1,"1701":2,"1702":1,"1706":1,"1707":1,"1722":2,"1723":1,"1725":1,"1726":1,"1727":2,"1728":1,"1729":1,"1730":1,"1731":1,"1732":1,"1737":1,"1739":2,"1748":2,"1749":2,"1755":2,"1764":2,"1765":8,"1767":11,"1772":1,"1788":1,"1793":1,"1795":1,"1797":1,"1803":2,"1805":3,"1806":1,"1809":1,"1810":3,"1812":2,"1814":3,"1815":4,"1816":1,"1817":1,"1832":3,"1834":1,"1835":1,"1838":1,"1839":1,"1842":1,"1846":1,"1849":1,"1851":2,"1858":2,"1859":2,"1861":1,"1862":1,"1863":1,"1864":1,"1868":1,"1869":1,"1874":1,"1876":2,"1877":1,"1885":1,"1888":2,"1889":1,"1894":2,"1897":1,"1898":2,"1901":1,"1902":2,"1906":1,"1908":1,"1912":1,"1916":1,"1918":1,"1920":2,"1922":1,"1923":1,"1924":1,"1925":1,"1927":2,"1933":2,"1937":2,"1940":1,"1941":1,"1942":1,"1944":2,"1945":2,"1946":1,"1948":1,"1950":3,"1952":1,"1953":1,"1954":1,"1957":1,"1960":1,"1965":1,"1966":1,"1968":1,"1970":1,"1972":2,"1974":1,"1976":3,"1977":1,"1978":1,"1980":3,"1981":3,"1982":2,"1985":1,"1986":1,"1987":2,"1988":2,"1992":2,"1994":4,"1995":1,"1996":1,"1997":2,"1998":2,"1999":1,"2000":2,"2001":2,"2002":4,"2014":1,"2016":1,"2019":1,"2024":1,"2025":3,"2027":1,"2028":1,"2030":1,"2037":1,"2040":1,"2041":2,"2043":2,"2045":2,"2049":2,"2056":2,"2061":1,"2063":1,"2068":2,"2071":2,"2074":1,"2082":1,"2083":2,"2085":1,"2089":1,"2090":1,"2097":2,"2098":1,"2100":1,"2104":1,"2106":1,"2107":2,"2109":1,"2113":1,"2115":1,"2116":1,"2120":3,"2122":1,"2123":1,"2127":1,"2129":1,"2131":1,"2133":2,"2136":1,"2141":1,"2142":2,"2144":1,"2146":2,"2147":4,"2150":1,"2152":1,"2153":4,"2154":1,"2156":1,"2158":1,"2160":2,"2161":1,"2164":2,"2165":2,"2166":2,"2167":2,"2168":1,"2169":4,"2172":1,"2174":1,"2176":2,"2178":1,"2180":2,"2181":1,"2183":1,"2185":1,"2188":1,"2192":1,"2193":2,"2194":1,"2195":2,"2196":1,"2198":2,"2199":1,"2201":1,"2203":3,"2207":1,"2230":1,"2232":4}}],["cangobackasync",{"3":{"1324":1}}],["canaries",{"3":{"1453":1}}],["canary",{"3":{"0":4,"135":1,"265":1,"530":2,"564":4,"624":2,"626":2,"631":1,"633":5,"757":1,"1012":1,"1032":2,"1034":4,"1036":1,"1037":3,"1436":2,"1453":2,"1454":2,"1455":1,"1460":1,"1461":3,"1486":1,"1492":3,"1493":1,"1554":1,"1574":1,"1577":2,"1664":1,"1673":1,"2105":1}}],["canauthenticate",{"3":{"1417":1}}],["canacceptupvote",{"3":{"1398":1,"1402":2}}],["canacceptvote",{"3":{"1398":1,"1402":6}}],["canadminister",{"3":{"1324":1,"1415":1}}],["canjoinasync",{"3":{"1286":1,"1309":2}}],["candid",{"3":{"1270":1,"1369":1,"1436":1,"1443":1,"1792":1,"1891":1,"1978":1,"2059":1,"2132":1}}],["candidate",{"3":{"0":2,"24":1,"145":1,"150":2,"256":1,"325":1,"326":1,"907":1,"1075":2,"1076":1,"1077":1,"1196":1,"1237":3,"1259":3,"1270":2,"1275":1,"1286":2,"1289":2,"1300":7,"1301":2,"1303":1,"1309":1,"1311":1,"1324":5,"1339":2,"1350":2,"1359":4,"1395":2,"1415":2,"1436":1,"1438":1,"1496":1,"1638":1,"2049":1}}],["candidates",{"3":{"0":2,"145":2,"150":2,"153":1,"1074":1,"1075":1,"1077":1,"1079":2,"1259":1,"1311":1,"1324":1,"1339":2,"1355":1,"1396":1,"1402":1,"1415":1,"1436":4,"1673":1,"2188":1}}],["canproject",{"3":{"1244":1,"1247":2,"1270":1,"1309":2}}],["canparse",{"3":{"1241":1,"1247":12}}],["canparsevalues",{"3":{"1300":5}}],["canparsevalue",{"2":{"335":1},"3":{"335":1,"1241":2,"1247":15,"1496":1}}],["canwrite",{"3":{"1242":1,"1247":2}}],["canclose",{"3":{"1436":1}}],["cancheckin",{"3":{"1396":2}}],["canconvertto",{"3":{"1300":1}}],["canconvertfrom",{"3":{"1300":1}}],["canconvert",{"2":{"1223":1},"3":{"1223":1,"1229":1,"1237":2,"1300":2}}],["cancelordercommand",{"2":{"2163":1},"3":{"1591":1}}],["cancelorderhandler",{"2":{"534":1,"793":1,"1875":1,"2163":1},"3":{"534":3,"536":8,"540":2,"793":2,"1591":1,"1749":1,"1765":1,"1767":1,"1875":1,"2163":2}}],["cancelall",{"3":{"1417":1}}],["cancelallasync",{"3":{"1417":3}}],["cancelasync",{"3":{"1417":5}}],["cancelable",{"3":{"1359":1}}],["cancelanddisposecurrent",{"3":{"1324":2}}],["cancelafter",{"3":{"1263":1,"1324":1}}],["canceledoutcome",{"3":{"1427":2}}],["cancelediting",{"3":{"1395":11}}],["canceled",{"3":{"1089":1,"1311":1,"1324":1,"1425":1,"1436":2,"2177":1}}],["cancels",{"3":{"237":1,"518":1,"556":1,"592":1,"813":1,"926":1,"1100":1,"1212":1,"1247":1,"1263":1,"1270":2,"1300":1,"1316":1,"1323":2,"1324":12,"1339":2,"1380":1,"1395":7,"1396":5,"1402":9,"1415":3,"1417":8,"1436":2,"1456":1,"1492":1,"1668":1,"1765":1,"1821":1,"2047":1,"2161":1}}],["canceltext",{"2":{"684":1},"3":{"0":1,"682":1,"684":1,"1324":3,"1416":1,"1417":3}}],["canceller",{"3":{"1339":1}}],["cancelled",{"2":{"793":1,"1754":1},"3":{"0":4,"109":1,"135":1,"440":1,"534":1,"536":5,"538":2,"539":2,"682":3,"790":1,"793":4,"897":1,"1091":1,"1212":1,"1259":2,"1263":1,"1270":4,"1286":1,"1289":1,"1300":1,"1309":1,"1311":1,"1323":1,"1324":6,"1339":3,"1357":1,"1359":3,"1395":10,"1396":7,"1399":1,"1402":2,"1411":1,"1415":5,"1417":15,"1422":1,"1431":1,"1436":3,"1438":1,"1455":3,"1456":1,"1460":1,"1475":2,"1483":1,"1526":1,"1686":1,"1749":1,"1754":1,"1755":1,"1765":3,"1766":1,"1767":1,"1923":1,"2096":1,"2126":1,"2161":1,"2163":2,"2164":2,"2167":3,"2168":1,"2174":1,"2232":1}}],["cancelloading",{"3":{"1324":1}}],["cancellingsection",{"3":{"1199":2,"1207":1,"1323":1}}],["cancelling",{"3":{"0":1,"535":1,"788":1,"793":2,"813":1,"1259":1,"1270":1,"1324":6,"1339":1,"1395":3,"1396":7,"1427":1,"1456":1,"1475":1,"1492":1,"1767":1}}],["cancellable",{"3":{"0":1,"135":1,"683":1,"1270":1,"1300":1,"1395":1,"1396":2,"1415":1,"1417":1,"1436":2,"1455":1,"1765":1,"1766":2,"1767":1,"1768":1,"1918":1}}],["cancellations",{"3":{"1311":1}}],["cancellationfixtures",{"3":{"1207":12,"1436":10}}],["cancellationtestmap",{"3":{"1198":1,"1199":2,"1207":1,"1436":5,"1496":1}}],["cancellationtokenfixtures",{"3":{"1207":6,"1436":8}}],["cancellationtokenfitnesstests",{"3":{"1198":1,"1199":2,"1207":3,"1436":10,"1489":1,"1496":1}}],["cancellationtokenconventiontests",{"3":{"135":1,"139":1,"142":2,"1199":1,"1207":2,"1309":1,"1436":11,"1489":1}}],["cancellationtokenconventiontestsbase",{"2":{"1431":1},"3":{"135":2,"139":1,"1199":2,"1207":2,"1431":3,"1436":8,"1496":1}}],["cancellationtokenexemptmethods",{"3":{"135":1,"1436":4}}],["cancellationtokens",{"2":{"135":1},"3":{"135":3,"139":1,"1207":1,"1431":1,"1436":7,"1489":1}}],["cancellationtokensource",{"2":{"121":1,"743":1,"1384":1,"2070":1,"2072":1},"3":{"743":1,"1263":1,"1270":1,"1323":1,"1324":15,"1384":1,"1395":24,"1396":4,"1402":6,"1415":3,"1417":2,"1427":1,"1436":1,"2070":1,"2072":1}}],["cancellationtoken",{"2":{"130":1,"143":1,"225":1,"743":1,"879":1,"897":1,"996":1,"1261":2,"1289":1,"1324":1,"1918":1,"1919":1,"2092":1},"3":{"0":5,"130":1,"135":2,"143":1,"225":1,"235":1,"459":1,"682":3,"707":1,"725":1,"733":1,"743":1,"879":1,"881":2,"897":5,"922":1,"996":1,"1247":10,"1259":14,"1261":2,"1263":1,"1269":1,"1270":30,"1286":74,"1289":1,"1300":54,"1301":14,"1304":1,"1309":30,"1310":4,"1311":29,"1315":1,"1323":41,"1324":63,"1339":15,"1350":4,"1359":45,"1369":2,"1380":11,"1395":41,"1396":45,"1402":8,"1415":15,"1417":126,"1431":2,"1436":58,"1918":1,"1919":2,"2092":1,"2232":1}}],["cancellation",{"3":{"0":4,"21":2,"27":2,"109":2,"135":1,"178":1,"459":1,"465":1,"518":1,"534":1,"536":2,"538":1,"559":1,"683":1,"881":2,"882":2,"896":2,"897":5,"898":2,"988":1,"1100":1,"1212":1,"1247":4,"1255":1,"1259":6,"1263":1,"1270":14,"1286":10,"1301":2,"1309":3,"1311":4,"1323":7,"1324":23,"1334":1,"1337":1,"1339":3,"1357":1,"1358":1,"1359":5,"1366":1,"1369":2,"1380":2,"1383":1,"1391":1,"1395":23,"1396":17,"1399":2,"1402":4,"1411":1,"1415":2,"1417":16,"1422":1,"1423":1,"1427":2,"1431":2,"1436":8,"1438":8,"1475":1,"1496":2,"1526":1,"1535":1,"1577":2,"1591":1,"1640":1,"1653":1,"1665":1,"1666":1,"1671":1,"1702":1,"1749":3,"1755":4,"1765":2,"1767":2,"1775":1,"1776":2,"1806":1,"1917":2,"1919":3,"2067":1,"2070":1,"2072":2,"2080":1,"2096":1,"2113":1,"2117":1,"2163":3,"2165":1,"2168":1,"2180":1}}],["cancel",{"2":{"268":1},"3":{"0":3,"265":1,"268":1,"517":1,"519":1,"536":2,"556":1,"682":3,"684":2,"793":4,"809":1,"1101":1,"1212":1,"1270":1,"1324":9,"1357":1,"1359":1,"1395":18,"1396":6,"1411":1,"1415":2,"1417":17,"1430":1,"1436":2,"1445":1,"1453":2,"1455":5,"1456":2,"1459":1,"1460":3,"1475":1,"1591":1,"1627":1,"1742":1,"1746":2,"1749":2,"1754":1,"1755":1,"1765":1,"1768":4,"2076":1,"2079":1,"2163":1,"2167":1,"2232":1}}],["canonicalization",{"3":{"1324":1}}],["canonicalizing",{"3":{"1286":1}}],["canonicalizes",{"3":{"1300":1,"1324":1}}],["canonicalize",{"3":{"220":1,"1324":1}}],["canonicalized",{"3":{"219":1,"1324":1,"2150":1}}],["canonically",{"3":{"333":1}}],["canonical",{"3":{"0":1,"98":1,"160":1,"175":1,"202":1,"265":1,"309":1,"324":1,"325":1,"333":1,"366":1,"498":1,"529":1,"782":1,"872":1,"912":1,"944":1,"946":3,"950":1,"963":1,"964":1,"1018":2,"1019":1,"1020":1,"1116":2,"1192":1,"1212":2,"1221":1,"1227":1,"1229":2,"1234":1,"1237":1,"1247":3,"1270":1,"1271":2,"1278":1,"1279":2,"1286":6,"1300":6,"1309":2,"1311":3,"1323":1,"1324":3,"1339":2,"1350":6,"1352":1,"1359":4,"1380":2,"1396":2,"1411":1,"1415":6,"1435":2,"1436":9,"1438":1,"1443":1,"1492":1,"1496":1,"1502":1,"1515":1,"1522":1,"1526":3,"1573":1,"1577":3,"1587":1,"1591":1,"1630":1,"1631":1,"1641":4,"1799":1,"1805":1,"1849":1,"1905":1,"1955":1,"2042":1,"2112":1,"2232":1,"2233":1}}],["can",{"0":{"1235":1,"2044":1},"1":{"1794":1,"1795":1,"1796":1,"1797":1,"1798":1,"1799":1,"1800":1,"1801":1,"1802":1,"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1956":1,"1957":1,"1958":1,"1959":1},"3":{"0":25,"1":1,"5":1,"10":1,"24":1,"26":2,"27":1,"30":1,"31":1,"32":2,"39":1,"42":2,"48":1,"60":1,"68":1,"69":2,"70":1,"72":1,"77":1,"95":1,"97":1,"100":1,"101":1,"103":1,"109":3,"110":1,"111":2,"117":2,"119":1,"121":1,"122":2,"134":1,"135":2,"136":2,"137":1,"140":1,"145":1,"146":1,"150":1,"156":1,"157":2,"159":1,"166":1,"167":1,"170":1,"174":1,"175":1,"177":3,"178":2,"179":1,"184":1,"185":1,"189":2,"194":1,"199":1,"201":1,"202":1,"208":1,"210":1,"214":1,"215":1,"217":1,"231":1,"234":2,"235":1,"236":2,"237":4,"243":1,"245":3,"255":1,"256":1,"259":1,"264":1,"265":3,"266":1,"275":1,"280":1,"283":2,"286":2,"293":1,"299":1,"303":2,"305":2,"310":1,"317":1,"318":1,"319":1,"320":1,"333":1,"340":2,"341":4,"343":1,"344":1,"352":1,"353":1,"358":3,"359":1,"360":1,"365":1,"373":1,"378":1,"380":2,"384":1,"386":1,"390":3,"391":1,"396":1,"397":2,"399":1,"415":2,"420":1,"421":1,"422":1,"427":3,"428":1,"435":2,"441":1,"442":1,"447":1,"449":1,"459":1,"461":2,"474":1,"478":1,"502":1,"506":1,"508":1,"509":1,"511":1,"513":1,"518":1,"519":1,"520":2,"528":1,"529":1,"530":4,"536":1,"538":1,"539":3,"540":1,"544":1,"545":1,"546":1,"549":5,"556":1,"558":2,"564":1,"565":1,"571":1,"572":2,"573":1,"583":2,"592":1,"598":1,"601":1,"610":2,"611":1,"617":3,"619":1,"620":2,"626":4,"627":1,"628":1,"632":1,"633":2,"635":1,"638":1,"640":3,"642":2,"649":1,"651":1,"657":2,"658":3,"665":1,"667":1,"673":3,"675":2,"676":2,"681":1,"682":1,"684":1,"690":4,"691":1,"692":4,"697":3,"698":2,"707":1,"709":2,"714":1,"716":2,"717":1,"718":2,"721":1,"725":3,"726":2,"727":3,"733":2,"735":2,"740":2,"741":1,"742":1,"743":2,"749":2,"750":1,"751":3,"757":3,"758":1,"759":1,"767":1,"768":3,"774":2,"775":1,"783":1,"784":2,"790":1,"791":3,"792":2,"799":2,"800":1,"810":1,"811":1,"815":1,"819":2,"821":2,"826":2,"827":5,"828":2,"829":2,"832":1,"839":1,"840":1,"853":1,"855":1,"856":1,"862":2,"863":1,"876":1,"881":1,"882":1,"891":1,"896":1,"897":1,"898":1,"903":1,"904":2,"905":5,"906":2,"907":2,"909":1,"916":1,"922":2,"924":1,"926":1,"930":1,"933":1,"940":1,"941":1,"952":1,"953":1,"954":1,"956":3,"960":1,"964":1,"965":1,"966":1,"971":2,"972":1,"973":1,"974":1,"982":1,"987":1,"988":2,"989":2,"990":2,"995":2,"996":3,"998":1,"1005":1,"1006":3,"1013":1,"1017":1,"1018":4,"1019":3,"1020":1,"1026":1,"1028":2,"1033":1,"1034":2,"1042":2,"1044":1,"1050":1,"1051":2,"1052":3,"1058":2,"1059":6,"1066":1,"1067":1,"1068":1,"1069":1,"1074":1,"1083":2,"1084":1,"1091":1,"1092":1,"1093":1,"1100":2,"1101":1,"1102":1,"1115":1,"1116":5,"1117":1,"1118":2,"1126":1,"1134":2,"1147":1,"1160":2,"1162":2,"1169":1,"1174":1,"1175":1,"1176":1,"1178":1,"1183":2,"1185":2,"1186":1,"1190":1,"1191":1,"1200":1,"1210":1,"1212":6,"1216":1,"1217":2,"1218":1,"1219":1,"1220":1,"1222":1,"1225":2,"1229":18,"1231":2,"1236":1,"1237":38,"1239":1,"1240":2,"1241":2,"1242":2,"1243":1,"1244":1,"1247":57,"1248":1,"1253":1,"1254":3,"1255":1,"1256":2,"1259":30,"1262":3,"1263":3,"1265":1,"1267":1,"1269":1,"1270":68,"1271":13,"1273":2,"1274":1,"1276":2,"1278":6,"1279":1,"1280":1,"1281":3,"1283":1,"1284":1,"1285":3,"1286":177,"1288":3,"1289":1,"1290":1,"1292":2,"1293":4,"1294":1,"1296":1,"1297":3,"1299":1,"1300":118,"1301":27,"1306":1,"1307":3,"1308":2,"1309":45,"1310":14,"1311":116,"1312":12,"1313":1,"1315":2,"1316":1,"1317":4,"1318":1,"1320":1,"1322":1,"1323":107,"1324":193,"1326":1,"1328":2,"1330":2,"1331":1,"1333":2,"1334":1,"1336":2,"1338":1,"1339":78,"1341":2,"1342":4,"1343":3,"1344":2,"1345":1,"1347":2,"1348":1,"1349":1,"1350":98,"1352":3,"1353":4,"1354":1,"1355":2,"1356":1,"1357":2,"1359":245,"1362":2,"1363":1,"1366":4,"1368":3,"1369":19,"1371":2,"1372":1,"1373":4,"1374":1,"1375":4,"1376":1,"1378":1,"1379":2,"1380":36,"1382":1,"1383":2,"1384":1,"1386":1,"1388":1,"1389":4,"1390":2,"1391":1,"1395":123,"1396":163,"1398":3,"1399":1,"1400":2,"1401":1,"1402":56,"1404":1,"1406":1,"1407":2,"1408":1,"1409":3,"1411":2,"1413":1,"1415":87,"1416":20,"1417":105,"1418":1,"1420":1,"1422":4,"1423":1,"1425":1,"1427":18,"1429":4,"1430":4,"1431":3,"1433":1,"1434":1,"1435":3,"1436":218,"1438":18,"1441":4,"1442":1,"1443":5,"1445":1,"1446":2,"1447":2,"1449":1,"1451":1,"1452":2,"1453":5,"1454":5,"1455":28,"1456":6,"1458":3,"1459":4,"1461":1,"1463":1,"1464":1,"1465":2,"1467":25,"1474":1,"1475":3,"1476":1,"1478":1,"1479":2,"1481":1,"1484":2,"1486":1,"1488":8,"1489":1,"1490":5,"1492":9,"1498":2,"1500":2,"1504":1,"1507":1,"1513":1,"1526":2,"1531":1,"1534":3,"1537":2,"1541":1,"1544":1,"1547":1,"1550":2,"1553":1,"1562":2,"1564":1,"1567":1,"1570":1,"1572":4,"1577":1,"1589":2,"1591":3,"1596":1,"1599":1,"1607":1,"1610":1,"1613":1,"1615":1,"1626":3,"1627":2,"1630":3,"1631":19,"1632":7,"1635":5,"1638":4,"1639":5,"1641":18,"1645":1,"1650":1,"1651":1,"1652":1,"1653":2,"1661":1,"1663":1,"1664":1,"1665":1,"1667":1,"1668":2,"1669":1,"1670":1,"1671":1,"1673":1,"1674":1,"1675":1,"1676":1,"1677":1,"1679":1,"1684":1,"1685":2,"1686":4,"1687":1,"1689":1,"1693":1,"1699":1,"1701":2,"1702":2,"1706":1,"1708":1,"1720":1,"1727":3,"1728":2,"1729":2,"1731":2,"1741":1,"1744":1,"1749":1,"1754":1,"1755":1,"1758":1,"1761":2,"1764":1,"1765":7,"1767":12,"1769":1,"1770":1,"1773":1,"1776":2,"1786":1,"1788":1,"1794":1,"1796":1,"1797":3,"1799":1,"1800":1,"1801":2,"1802":1,"1803":2,"1804":2,"1805":3,"1806":1,"1807":2,"1808":2,"1809":2,"1810":1,"1811":1,"1812":2,"1814":1,"1815":4,"1817":5,"1819":1,"1821":1,"1824":1,"1825":3,"1826":2,"1832":1,"1833":1,"1836":1,"1837":1,"1838":1,"1839":1,"1840":2,"1841":2,"1844":2,"1845":2,"1847":1,"1848":2,"1852":2,"1855":2,"1856":1,"1861":1,"1863":1,"1864":2,"1865":2,"1868":1,"1871":2,"1872":1,"1873":1,"1874":1,"1875":1,"1877":2,"1882":2,"1885":1,"1890":2,"1892":4,"1893":4,"1894":1,"1897":1,"1898":1,"1899":1,"1900":1,"1902":3,"1904":1,"1905":1,"1908":3,"1909":4,"1910":1,"1911":4,"1912":1,"1916":2,"1918":2,"1919":1,"1920":1,"1922":2,"1923":3,"1924":1,"1926":1,"1927":1,"1929":1,"1933":1,"1934":1,"1938":1,"1940":1,"1942":1,"1944":2,"1945":1,"1946":4,"1948":4,"1949":2,"1950":3,"1951":1,"1952":1,"1954":3,"1955":1,"1957":1,"1960":5,"1961":1,"1962":2,"1963":2,"1964":1,"1965":2,"1966":2,"1967":4,"1968":1,"1969":1,"1970":1,"1971":1,"1972":2,"1975":1,"1977":1,"1978":1,"1979":2,"1980":4,"1982":1,"1993":2,"1994":1,"1995":3,"1996":1,"1997":1,"1998":1,"1999":1,"2001":7,"2002":3,"2004":1,"2005":2,"2008":1,"2009":1,"2010":3,"2011":1,"2012":3,"2013":2,"2021":1,"2024":4,"2025":1,"2026":2,"2027":1,"2028":1,"2029":2,"2030":1,"2032":1,"2033":1,"2034":2,"2035":1,"2037":2,"2038":1,"2041":1,"2043":3,"2044":2,"2045":1,"2047":1,"2048":1,"2049":1,"2050":1,"2052":1,"2055":1,"2056":4,"2057":1,"2061":1,"2063":1,"2065":1,"2066":1,"2067":2,"2068":2,"2069":1,"2076":1,"2082":1,"2083":1,"2086":2,"2087":2,"2091":1,"2094":1,"2095":2,"2098":1,"2104":1,"2105":1,"2106":1,"2107":2,"2109":3,"2113":2,"2114":1,"2119":1,"2121":1,"2122":2,"2124":1,"2126":2,"2127":3,"2128":1,"2130":2,"2131":1,"2132":1,"2133":2,"2135":2,"2137":2,"2140":3,"2141":4,"2146":3,"2147":1,"2149":1,"2150":1,"2153":3,"2156":3,"2158":1,"2159":2,"2161":1,"2163":2,"2164":2,"2166":1,"2167":2,"2168":2,"2169":2,"2170":3,"2171":1,"2178":1,"2180":3,"2184":1,"2187":1,"2188":2,"2191":1,"2192":2,"2193":2,"2194":1,"2195":1,"2196":1,"2197":1,"2198":1,"2199":3,"2200":2,"2201":1,"2202":2,"2203":4,"2205":1,"2206":1,"2207":1,"2208":1,"2230":1,"2232":4}}],["cadences",{"3":{"767":1,"1467":1,"1469":1,"1484":1,"1888":1}}],["cadence",{"3":{"0":3,"22":3,"62":2,"143":1,"607":2,"609":1,"626":1,"627":1,"634":1,"642":1,"706":1,"1018":1,"1036":1,"1259":4,"1300":1,"1301":1,"1323":4,"1339":2,"1350":2,"1390":2,"1395":4,"1443":1,"1450":1,"1455":2,"1459":1,"1467":8,"1469":2,"1474":3,"1475":1,"1476":3,"1483":1,"1492":1,"1521":1,"1589":2,"1591":1,"1592":1,"1595":1,"1596":1,"1598":1,"1661":1,"1724":1,"1841":2,"1845":1,"2037":1}}],["capgbpermonth",{"3":{"1467":1}}],["capgbpermonth=",{"3":{"1116":1,"1119":1}}],["capital",{"3":{"1379":1}}],["capitalized",{"3":{"1311":1,"1380":1}}],["capitalization",{"3":{"1300":1,"1311":1,"1667":1}}],["capitals",{"3":{"1286":1,"1324":1}}],["captioning",{"3":{"1630":1,"1631":1,"1637":1}}],["captions",{"3":{"1480":1}}],["caption",{"3":{"1324":9,"1396":3,"1436":3,"1480":5}}],["captive",{"3":{"1259":1,"1359":1,"1417":2}}],["capturingmessagebus",{"3":{"1199":2,"1207":1}}],["capturinghandler",{"3":{"1199":6,"1207":4}}],["capturinghttpmessagehandlertests",{"3":{"1199":1,"1207":2,"1436":3}}],["capturinghttpmessagehandler",{"2":{"1432":1},"3":{"954":1,"1199":27,"1207":4,"1428":1,"1432":8,"1436":12,"1488":1}}],["capturinglogger",{"3":{"1199":4,"1207":2}}],["capturing",{"0":{"1251":1},"3":{"481":1,"483":1,"716":2,"1237":1,"1251":1,"1259":1,"1274":1,"1286":6,"1300":1,"1310":1,"1311":1,"1323":1,"1324":1,"1338":1,"1339":2,"1359":1,"1417":4,"1436":4,"1456":1,"1577":1,"1660":1,"1671":1,"1764":1}}],["captureasync",{"3":{"1436":3}}],["captureavatarasync",{"3":{"1415":1}}],["capturetofileasync",{"3":{"1417":3}}],["capturephotoasync",{"3":{"1417":4}}],["capturejsruntime",{"3":{"1417":1}}],["captureunauthorized",{"3":{"1324":1}}],["captureexclusiontable",{"3":{"1286":1}}],["captureeventsandpersisttooutbox",{"3":{"1286":4}}],["captureentry",{"3":{"1286":4}}],["capturemodifiedproperties",{"3":{"1286":3}}],["capturechanges",{"3":{"1286":4}}],["capturecontext",{"2":{"1277":1},"3":{"1199":2,"1203":1,"1207":1,"1277":1,"1286":3}}],["captureheaders",{"3":{"1259":2,"1436":2}}],["captures",{"3":{"15":1,"142":1,"202":1,"261":1,"350":1,"373":1,"396":1,"420":1,"423":1,"481":1,"572":1,"676":1,"702":1,"707":1,"715":1,"717":1,"931":1,"1042":1,"1091":1,"1233":1,"1237":1,"1247":2,"1259":4,"1273":1,"1286":8,"1300":3,"1309":2,"1311":2,"1318":1,"1323":3,"1324":4,"1339":3,"1340":1,"1345":1,"1349":1,"1350":3,"1359":8,"1363":1,"1369":2,"1380":1,"1395":3,"1396":10,"1402":1,"1406":1,"1415":3,"1417":10,"1427":1,"1429":1,"1436":8,"1441":1,"1479":2,"1480":2,"1488":1,"1502":1,"1525":1,"1526":1,"1665":1,"1839":1,"1840":1,"1881":1,"1908":1,"1948":1,"1976":1,"2055":1,"2091":1}}],["capturedstate",{"3":{"1199":3,"1203":1,"1207":1,"1237":1,"1274":1,"1286":6}}],["capturedrequest",{"3":{"1199":4,"1207":2,"1432":1,"1436":5}}],["capturedmeasurement",{"3":{"1199":2,"1207":1}}],["capturedcounter",{"3":{"1199":2,"1207":1}}],["captured",{"0":{"1253":1},"3":{"0":1,"21":1,"26":2,"27":1,"109":1,"120":1,"202":1,"243":1,"330":1,"359":1,"397":1,"399":1,"403":1,"483":1,"498":1,"500":2,"501":1,"682":1,"698":1,"716":1,"720":2,"781":1,"786":1,"860":1,"1020":1,"1043":1,"1212":2,"1231":1,"1232":1,"1233":1,"1237":7,"1246":1,"1247":1,"1251":1,"1252":1,"1253":1,"1259":10,"1270":3,"1274":4,"1279":1,"1286":29,"1300":3,"1301":1,"1309":3,"1310":2,"1311":7,"1318":1,"1323":10,"1324":4,"1326":1,"1327":2,"1328":1,"1339":9,"1350":10,"1355":1,"1358":1,"1359":14,"1380":1,"1395":18,"1396":5,"1398":1,"1402":4,"1406":1,"1410":1,"1415":3,"1416":1,"1417":6,"1436":6,"1438":2,"1455":1,"1467":1,"1479":2,"1480":1,"1496":1,"1508":1,"1567":1,"1571":1,"1631":1,"1641":1,"1665":1,"1677":1,"1686":1,"1825":1,"1843":1,"1844":1,"1894":1,"1980":2,"1981":1,"2111":1,"2192":1}}],["capture",{"0":{"1479":2},"3":{"0":6,"21":8,"202":1,"439":1,"440":1,"481":1,"490":1,"698":1,"713":1,"714":2,"715":2,"716":1,"717":2,"720":1,"721":1,"749":1,"751":2,"905":1,"1036":1,"1212":1,"1213":1,"1218":1,"1229":2,"1233":1,"1237":5,"1249":2,"1251":4,"1253":3,"1255":1,"1259":14,"1270":1,"1272":1,"1274":4,"1275":2,"1277":1,"1280":1,"1286":39,"1300":2,"1309":2,"1311":3,"1312":1,"1318":2,"1323":1,"1324":3,"1339":1,"1350":1,"1359":1,"1395":2,"1396":1,"1402":1,"1415":2,"1417":19,"1433":2,"1436":16,"1438":1,"1455":1,"1471":1,"1479":15,"1480":4,"1482":1,"1483":1,"1488":3,"1537":2,"1552":1,"1575":1,"1577":1,"1584":1,"1664":1,"1672":1,"1767":1,"1797":1,"1804":1,"1825":2,"1838":1,"1839":1,"2053":1,"2126":1,"2232":2}}],["capacities",{"3":{"1359":1}}],["capacity",{"3":{"413":1,"519":1,"591":1,"628":1,"630":1,"768":2,"827":2,"1100":1,"1101":1,"1124":1,"1125":1,"1270":1,"1301":1,"1311":3,"1324":3,"1337":1,"1339":2,"1350":4,"1359":18,"1369":1,"1380":4,"1395":13,"1396":4,"1400":1,"1416":1,"1417":3,"1455":3,"1458":3,"1459":1,"1461":1,"1467":4,"1484":1,"1523":2,"1524":2,"1525":1,"1526":1,"1530":1,"1531":1,"1533":1,"1534":1,"1549":1,"1577":2,"1582":1,"1589":1,"1596":1,"1629":1,"1630":2,"1631":2,"1637":1,"1670":1,"1708":1,"1841":1,"1947":1,"2121":2}}],["capable",{"3":{"92":1,"96":1,"100":1,"810":1,"838":1,"841":1,"988":2,"1243":1,"1247":1,"1286":1,"1301":3,"1339":1,"1359":1,"1369":1,"1395":1,"1396":1,"1417":2,"1447":1,"2073":1}}],["capabilitiesjsmodule",{"2":{"2119":1},"3":{"1199":11,"1203":1,"1207":2,"1324":5,"1417":21,"2119":1}}],["capabilities",{"0":{"2201":1},"1":{"1960":1,"1961":1,"1962":1,"1963":1,"1964":1,"1965":1},"2":{"1175":1,"1416":3,"1417":9},"3":{"0":3,"185":1,"186":6,"187":2,"190":1,"245":1,"266":1,"283":1,"304":1,"324":1,"412":1,"413":7,"414":2,"415":2,"416":2,"418":1,"420":1,"423":1,"427":1,"428":2,"430":1,"434":1,"436":1,"461":1,"556":1,"682":5,"684":1,"689":1,"820":1,"980":1,"1034":1,"1059":1,"1060":1,"1107":1,"1175":3,"1176":1,"1200":1,"1203":89,"1207":204,"1208":1,"1238":1,"1259":3,"1270":1,"1280":1,"1286":34,"1297":4,"1300":7,"1309":1,"1311":1,"1317":1,"1323":1,"1324":9,"1350":3,"1377":1,"1380":1,"1395":1,"1396":8,"1409":2,"1415":8,"1416":5,"1417":426,"1436":1,"1438":1,"1496":7,"1500":1,"1510":1,"1577":1,"1599":1,"1621":1,"1626":1,"1627":1,"1629":1,"1630":1,"1632":1,"1638":1,"1639":1,"1662":1,"1667":1,"1960":2,"1961":1,"1963":1,"1965":2,"2117":1,"2118":3,"2119":1,"2121":3,"2122":1,"2123":2,"2194":1,"2195":1,"2201":1,"2203":1,"2208":1}}],["capabilityfallbacktests",{"3":{"1199":1,"1207":2,"1417":32,"1438":2}}],["capability",{"0":{"1231":1,"1409":1,"1810":1,"2117":1},"1":{"410":1,"411":1,"412":1,"413":1,"414":1,"415":1,"416":1,"679":1,"680":1,"681":1,"682":1,"683":1,"684":1,"685":1,"686":1,"1047":1,"1048":1,"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1,"1417":1,"1659":1,"1660":1,"1661":1,"1662":1,"1663":1,"1664":1,"1665":1,"1666":1,"1667":1,"1668":1,"1669":1,"1670":1,"1671":1,"1672":1,"1673":1,"1674":1,"1675":1,"2116":1,"2117":1,"2118":1,"2119":1,"2120":1,"2121":1,"2122":1,"2123":1},"2":{"1760":1},"3":{"0":14,"39":1,"115":1,"126":3,"184":2,"185":4,"186":4,"188":2,"191":1,"243":2,"258":1,"300":1,"301":1,"317":2,"318":1,"319":1,"324":2,"358":1,"410":1,"411":1,"413":5,"414":2,"415":2,"426":1,"434":1,"439":1,"440":1,"468":1,"482":1,"510":1,"549":1,"558":1,"649":1,"650":1,"653":1,"656":1,"661":1,"665":1,"679":1,"681":4,"682":2,"683":2,"686":2,"689":1,"691":1,"694":2,"696":1,"698":1,"705":1,"706":1,"708":1,"713":1,"717":1,"723":1,"725":1,"739":1,"741":1,"749":1,"797":1,"798":2,"802":1,"804":2,"806":1,"820":2,"954":1,"957":1,"964":1,"965":3,"966":2,"968":1,"979":1,"1003":1,"1004":1,"1034":1,"1047":1,"1048":1,"1049":2,"1050":1,"1051":1,"1052":3,"1053":1,"1054":1,"1055":1,"1060":1,"1115":1,"1116":2,"1118":1,"1120":1,"1132":1,"1176":1,"1178":1,"1191":1,"1192":4,"1194":2,"1195":1,"1200":1,"1201":2,"1202":3,"1203":3,"1212":5,"1217":1,"1228":1,"1229":2,"1230":1,"1231":1,"1232":1,"1237":5,"1247":2,"1259":1,"1263":4,"1264":1,"1270":12,"1271":1,"1281":1,"1286":19,"1287":1,"1297":3,"1300":23,"1301":4,"1303":2,"1304":2,"1308":1,"1309":19,"1310":3,"1311":7,"1315":1,"1322":1,"1323":11,"1324":14,"1339":1,"1347":1,"1348":8,"1350":20,"1357":2,"1359":11,"1367":1,"1372":3,"1373":8,"1377":2,"1380":32,"1381":1,"1388":1,"1391":1,"1395":10,"1396":44,"1402":6,"1404":1,"1405":1,"1406":1,"1409":5,"1414":5,"1415":22,"1416":10,"1417":104,"1422":1,"1427":2,"1431":1,"1436":11,"1438":4,"1481":2,"1491":2,"1496":14,"1505":1,"1513":1,"1514":1,"1517":1,"1526":4,"1531":1,"1534":1,"1541":1,"1542":1,"1575":1,"1577":2,"1582":2,"1583":1,"1585":1,"1599":3,"1602":1,"1621":2,"1626":3,"1627":4,"1631":1,"1632":1,"1640":3,"1653":1,"1659":2,"1660":2,"1669":1,"1673":1,"1675":1,"1733":1,"1760":2,"1761":1,"1770":1,"1771":1,"1779":2,"1810":3,"1821":4,"1823":2,"1860":1,"1868":1,"1903":1,"1904":1,"1921":1,"1932":1,"1948":1,"1959":1,"1960":3,"1961":4,"1962":6,"1963":1,"1964":4,"1965":3,"1978":1,"1995":3,"2063":2,"2083":1,"2115":2,"2116":3,"2117":4,"2118":2,"2120":1,"2122":1,"2123":6,"2126":2,"2127":1,"2137":1,"2138":2,"2139":1,"2143":1,"2198":1,"2199":1,"2202":1,"2203":1,"2210":2,"2232":6}}],["capping",{"3":{"176":1,"332":1,"1242":1,"1247":1,"1286":1,"1311":1,"1350":1,"1402":1,"1597":1}}],["capped",{"3":{"0":5,"19":1,"22":1,"23":1,"328":1,"330":1,"333":2,"335":1,"378":1,"384":1,"707":1,"854":1,"1026":1,"1042":2,"1095":1,"1118":1,"1212":1,"1237":1,"1238":1,"1243":2,"1247":3,"1256":1,"1259":6,"1275":2,"1278":2,"1286":7,"1291":1,"1292":1,"1294":1,"1300":6,"1309":1,"1311":1,"1317":1,"1323":2,"1324":2,"1339":1,"1344":1,"1357":1,"1359":2,"1369":2,"1371":1,"1395":15,"1396":3,"1402":2,"1415":1,"1427":1,"1436":2,"1443":1,"1467":1,"1578":1,"1589":1,"1631":2,"1632":1,"1640":1,"1641":3,"1665":1,"1667":1,"1668":1,"1713":1,"1748":3,"1765":3,"1784":1,"1800":1,"1815":2,"1829":2,"1841":1,"1923":1,"1937":1,"1948":1,"1988":2,"1998":1,"2000":1,"2048":1,"2178":1,"2188":1}}],["capseconds",{"3":{"1286":1}}],["caps",{"3":{"0":2,"27":1,"175":2,"280":2,"282":3,"330":1,"333":1,"334":1,"361":1,"552":1,"669":1,"691":1,"692":1,"740":1,"830":1,"832":1,"853":1,"907":1,"1042":1,"1118":1,"1212":2,"1237":1,"1241":2,"1247":3,"1259":2,"1286":5,"1294":1,"1300":2,"1305":1,"1307":1,"1309":3,"1310":1,"1311":1,"1323":3,"1324":3,"1344":2,"1350":20,"1359":5,"1384":2,"1388":1,"1395":9,"1396":1,"1398":2,"1401":1,"1402":4,"1411":1,"1415":1,"1417":1,"1436":7,"1442":1,"1447":1,"1457":3,"1467":3,"1526":1,"1534":1,"1577":2,"1591":1,"1599":3,"1600":1,"1671":1,"1945":1,"1988":1,"1990":1,"1999":1,"2000":1,"2002":3,"2145":1,"2170":1,"2232":1}}],["cap",{"0":{"2054":1},"3":{"0":16,"19":4,"23":3,"26":1,"27":1,"57":1,"59":2,"174":1,"175":2,"176":3,"177":2,"178":3,"179":1,"180":1,"182":1,"223":1,"225":1,"228":1,"280":1,"282":1,"283":1,"287":1,"335":2,"350":1,"395":2,"403":2,"434":1,"438":1,"442":1,"500":2,"501":2,"524":2,"607":2,"609":2,"611":1,"612":3,"710":1,"719":2,"741":8,"743":2,"744":1,"749":1,"767":1,"825":1,"826":2,"827":2,"828":1,"831":1,"855":1,"856":1,"905":6,"906":4,"907":2,"909":1,"996":3,"1005":1,"1006":2,"1059":1,"1116":3,"1119":2,"1124":1,"1212":2,"1237":2,"1242":2,"1243":2,"1247":11,"1254":1,"1256":1,"1259":9,"1273":1,"1275":2,"1283":1,"1285":1,"1286":3,"1290":1,"1294":1,"1295":1,"1300":25,"1307":1,"1309":7,"1311":10,"1323":15,"1324":13,"1326":1,"1339":1,"1344":1,"1347":2,"1350":17,"1357":4,"1359":12,"1371":1,"1373":1,"1380":1,"1383":1,"1384":1,"1389":4,"1390":2,"1392":1,"1395":31,"1396":12,"1399":2,"1401":1,"1402":18,"1406":2,"1409":1,"1414":1,"1415":9,"1416":1,"1417":1,"1427":1,"1436":14,"1438":3,"1447":2,"1456":3,"1460":2,"1465":7,"1467":21,"1469":1,"1473":1,"1478":2,"1488":1,"1489":1,"1496":1,"1525":1,"1526":4,"1531":3,"1532":2,"1534":2,"1575":1,"1581":1,"1583":1,"1584":1,"1586":1,"1591":6,"1596":2,"1597":2,"1599":1,"1631":1,"1632":33,"1635":1,"1640":4,"1662":1,"1667":1,"1767":1,"1817":1,"1825":1,"1829":1,"1977":1,"1982":4,"1983":2,"1984":4,"1985":1,"1988":1,"1991":1,"1998":2,"1999":2,"2000":2,"2001":3,"2002":5,"2014":1,"2024":2,"2060":1,"2073":1,"2079":1,"2126":1,"2166":1,"2169":1,"2198":1,"2232":1}}],["chronological",{"3":{"1309":1,"1383":1,"1395":2,"1396":1,"1632":2,"1640":4}}],["chronologically",{"3":{"741":1,"1359":1,"1380":1,"1392":1,"1395":2}}],["chrometextmuted",{"3":{"1324":2}}],["chrometext",{"3":{"1324":1}}],["chromebackground",{"3":{"1324":3}}],["chrome",{"3":{"265":4,"267":1,"275":1,"860":1,"861":1,"954":1,"1324":30,"1395":4,"1396":2,"1402":1,"1416":3,"1436":6,"1438":1,"1526":1,"1559":1,"1577":1,"1591":1,"1606":1,"1609":1,"1644":1,"1653":1,"1669":1,"1687":2,"1716":1,"1740":1,"1743":1,"2079":3,"2085":1}}],["chromium",{"3":{"0":4,"265":2,"558":1,"591":1,"618":2,"619":2,"625":3,"626":2,"756":1,"861":1,"863":3,"1212":3,"1324":2,"1414":1,"1433":3,"1436":7,"1438":2,"1455":1,"1456":7,"1460":1,"1461":2,"1463":2,"1486":1,"1488":4,"1490":2,"1492":5,"1524":1,"1526":8,"1531":3,"1532":1,"1534":3,"1535":1,"1577":6,"1586":1,"1591":10,"1596":5,"1599":3,"1600":1,"1643":1,"1716":2,"1739":1,"1775":1,"2057":2,"2073":1,"2079":2}}],["cheat",{"3":{"691":1,"1369":1}}],["cheaper",{"3":{"627":1,"675":1,"741":1,"947":1,"964":1,"965":1,"981":1,"1019":1,"1041":1,"1051":1,"1092":1,"1124":1,"1144":1,"1229":1,"1286":4,"1300":2,"1311":1,"1323":1,"1324":3,"1329":1,"1339":2,"1359":6,"1395":4,"1396":3,"1402":1,"1415":1,"1417":1,"1436":3,"1443":1,"1491":1,"1631":1,"1678":1,"1708":1,"1951":1,"2000":1,"2049":1,"2150":1}}],["cheapest",{"3":{"110":1,"150":1,"235":1,"242":1,"266":1,"319":1,"742":1,"915":1,"973":1,"1019":1,"1234":1,"1237":2,"1247":3,"1259":1,"1270":1,"1286":1,"1300":1,"1309":1,"1311":2,"1324":1,"1359":3,"1369":1,"1392":1,"1395":2,"1396":3,"1417":3,"1436":7,"1467":1,"1488":1,"1531":1,"1802":1,"1810":1,"2054":1,"2139":1}}],["cheaply",{"3":{"244":1,"460":1,"1052":1,"1142":1,"1277":1,"1286":1,"1289":1,"1300":2,"1309":1,"1311":1,"1359":3,"1395":1,"1396":1,"1402":1,"1415":1,"1436":6,"1605":1,"1739":1,"2024":1,"2093":1,"2126":1}}],["cheap",{"3":{"0":2,"18":1,"110":1,"111":1,"165":1,"174":1,"236":1,"237":1,"295":1,"309":1,"529":1,"546":1,"565":2,"619":1,"641":1,"684":1,"734":1,"751":1,"758":1,"767":1,"776":1,"783":1,"811":1,"813":1,"826":2,"855":1,"871":1,"932":1,"953":1,"962":1,"965":1,"989":1,"1005":2,"1049":1,"1075":1,"1099":1,"1123":1,"1154":1,"1212":1,"1229":1,"1237":1,"1242":1,"1247":3,"1259":2,"1270":1,"1271":1,"1286":9,"1292":1,"1300":3,"1301":2,"1309":1,"1311":5,"1323":1,"1324":6,"1339":2,"1350":1,"1359":12,"1369":3,"1376":1,"1380":5,"1387":1,"1395":9,"1396":12,"1402":3,"1415":1,"1417":4,"1427":4,"1436":5,"1438":2,"1449":1,"1455":4,"1456":2,"1467":1,"1475":1,"1492":1,"1531":1,"1534":1,"1589":1,"1641":1,"1677":2,"1678":1,"1789":1,"1792":1,"1810":1,"1832":2,"1902":1,"1934":1,"1958":1,"1999":1,"2000":2,"2098":1,"2144":1,"2148":1,"2158":1,"2179":1,"2180":1,"2184":1,"2197":1}}],["checkpoint",{"3":{"1429":2,"1649":1,"1654":1}}],["checkpreconditions",{"3":{"1396":4}}],["checknotlocked",{"3":{"1415":2}}],["checknameprefix",{"3":{"1339":1}}],["checkname",{"2":{"1220":1},"3":{"1220":1,"1339":1}}],["checkkey",{"3":{"1339":2,"1441":1}}],["checkhealthasync",{"3":{"1339":9}}],["checkboxes",{"3":{"1324":2,"1436":1}}],["checkbox",{"3":{"1324":6,"1396":1,"1436":4,"1496":1,"1626":1,"2140":1}}],["checkdate",{"2":{"1220":1},"3":{"1220":1}}],["checkownershipasync",{"3":{"1300":1}}],["checkownership",{"2":{"725":1,"1322":1,"1667":1},"3":{"725":1,"1322":1,"1323":4,"1415":2,"1667":1}}],["checkoutoutcome",{"3":{"1600":1}}],["checkoutandpayasync",{"3":{"1600":1}}],["checkoutcommand",{"3":{"988":1,"1270":2,"1749":2}}],["checkoutpreflight",{"2":{"1025":1,"1029":1},"3":{"897":1,"900":1,"1025":2,"1026":2,"1029":2,"1600":1}}],["checkouts",{"3":{"566":1,"567":1}}],["checkoutdomainservice",{"2":{"1025":1},"3":{"0":1,"1025":2,"1765":1,"1767":1}}],["checkoutdeadlinescheduler",{"2":{"534":1,"539":1,"540":1,"814":1},"3":{"0":1,"534":1,"539":5,"540":3,"674":2,"677":1,"814":2,"1600":1}}],["checkouthandler",{"2":{"535":1,"539":1,"677":1,"813":1,"814":1,"986":1,"990":1,"991":1,"1025":1,"1600":1},"3":{"0":2,"534":3,"535":2,"539":3,"540":1,"677":1,"813":2,"814":2,"986":1,"988":4,"990":2,"991":1,"1025":2,"1026":1,"1029":1,"1591":2,"1600":3,"1765":1,"1767":1,"2161":1}}],["checkout",{"1":{"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1},"2":{"790":2,"1765":1,"1770":2},"3":{"0":6,"534":3,"535":3,"536":2,"539":6,"562":1,"566":2,"567":1,"674":1,"782":1,"790":2,"793":3,"794":1,"811":1,"813":2,"814":1,"881":2,"897":5,"900":3,"988":2,"1023":1,"1025":3,"1026":2,"1027":3,"1028":2,"1029":1,"1212":1,"1426":2,"1436":2,"1453":1,"1454":4,"1455":2,"1459":3,"1475":1,"1483":1,"1500":1,"1588":1,"1591":3,"1600":5,"1742":1,"1746":4,"1749":6,"1750":4,"1751":1,"1752":1,"1753":1,"1755":3,"1757":1,"1759":1,"1763":2,"1765":14,"1767":4,"1768":3,"1769":2,"1770":3,"1773":2,"1775":2,"1776":3,"2161":3,"2163":1,"2167":3,"2232":1}}],["checklist",{"0":{"1572":1,"1657":1,"1690":1,"1704":1},"3":{"620":1,"622":1,"1396":1,"1402":1,"1436":1,"1472":4,"1481":1,"1496":1,"1577":1,"1585":1,"1644":1,"1875":1,"2038":1}}],["checklockoutasync",{"3":{"281":2,"1300":3,"1323":1,"2000":1}}],["checkregistrationratelimitasync",{"2":{"281":1,"2000":1},"3":{"281":1,"1300":4,"2000":1}}],["checkable",{"3":{"72":1,"620":1,"1084":1,"1270":3,"1286":1,"1300":3,"1324":1,"1359":2,"1415":1,"1436":1,"1488":1,"1883":1}}],["checkinbycredentialasync",{"3":{"1396":2}}],["checkinidentifiertype",{"3":{"1396":1}}],["checkininvariants",{"2":{"690":1},"3":{"690":1,"1199":2,"1203":1,"1207":2,"1396":8}}],["checkintests",{"3":{"1199":1,"1207":2,"1396":1,"1438":1}}],["checkinasync",{"3":{"1396":2}}],["checkinauthorizationtests",{"3":{"1199":1,"1207":2,"1438":2}}],["checkinattendee",{"3":{"1203":2,"1207":4,"1271":2,"1396":9}}],["checkinattendeehandlertests",{"3":{"1199":1,"1207":3,"1438":1}}],["checkinattendeehandler",{"3":{"1199":2,"1203":1,"1207":2,"1271":3,"1350":2,"1396":21,"1438":1}}],["checkinattendeerequestvalidatortests",{"3":{"1199":1,"1207":2}}],["checkinattendeerequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1396":7}}],["checkinattendeerequest",{"3":{"1199":11,"1203":1,"1207":2,"1396":12}}],["checkinandpointstests",{"3":{"1199":1,"1207":2,"1438":2}}],["checkindtomapper",{"3":{"1199":1,"1203":1,"1207":2,"1396":7}}],["checkindto",{"3":{"1199":2,"1203":1,"1207":2,"1396":11}}],["checkinerrorcodes",{"3":{"1199":5,"1203":1,"1207":2,"1396":16}}],["checkinresultdto",{"3":{"1199":10,"1203":1,"1207":2,"1396":20}}],["checkinrow",{"3":{"1199":2,"1207":1}}],["checkinconfiguration",{"3":{"690":1,"691":1,"1199":1,"1203":1,"1207":2,"1286":1,"1396":24}}],["checkinprocessor",{"2":{"690":1},"3":{"690":2,"1199":5,"1203":1,"1207":3,"1350":5,"1359":2,"1396":73,"1436":1}}],["checkinmanage",{"2":{"690":1},"3":{"690":1,"1396":12}}],["checkinservicetests",{"3":{"1199":1,"1207":3,"1396":1,"1438":1}}],["checkinservice",{"3":{"1199":8,"1203":1,"1207":2,"1396":35}}],["checkinsettingstests",{"3":{"1199":1,"1207":2,"1396":1}}],["checkinsettings",{"2":{"690":1},"3":{"674":1,"690":1,"1199":4,"1203":1,"1207":2,"1350":2,"1396":9}}],["checkinstate",{"3":{"1199":2,"1203":1,"1207":1,"1396":8}}],["checkinscontrollertests",{"3":{"1199":1,"1207":3,"1396":2,"1438":2}}],["checkinscontroller",{"2":{"693":1},"3":{"690":3,"693":1,"1199":2,"1203":1,"1207":2,"1396":82}}],["checkinscopenamestests",{"3":{"1199":1,"1207":2,"1396":1}}],["checkinscopenames",{"3":{"1199":11,"1203":1,"1207":2,"1396":11}}],["checkinscope",{"2":{"688":1,"799":1,"803":1,"1052":1,"1054":1},"3":{"688":1,"690":2,"799":1,"803":1,"1052":1,"1054":1,"1199":34,"1203":1,"1207":2,"1396":37,"1415":1}}],["checkinscanroundtriptests",{"3":{"1199":1,"1207":2,"1438":2}}],["checkinscantests",{"3":{"1199":1,"1207":2,"1396":1,"1438":2}}],["checkinscanpage",{"3":{"1199":3,"1207":2}}],["checkinscan",{"2":{"693":1},"3":{"684":3,"685":3,"690":5,"693":1,"1199":2,"1203":1,"1207":4,"1396":56,"1417":1}}],["checkins",{"2":{"554":1,"1207":1,"1396":5},"3":{"0":1,"554":1,"556":3,"674":1,"690":11,"693":3,"799":1,"837":1,"838":1,"881":1,"1052":1,"1055":1,"1203":51,"1207":178,"1237":2,"1271":10,"1286":4,"1324":2,"1350":1,"1359":3,"1396":178,"1415":3,"1436":3,"1438":6,"1447":1,"1487":1,"1496":1,"1526":1}}],["checking",{"3":{"0":1,"390":1,"427":1,"460":1,"507":2,"579":1,"675":1,"827":2,"973":1,"1133":1,"1168":1,"1229":2,"1259":1,"1263":1,"1270":1,"1286":5,"1300":3,"1309":1,"1324":9,"1339":5,"1350":1,"1359":4,"1380":2,"1395":3,"1396":3,"1415":1,"1416":1,"1417":3,"1431":1,"1436":9,"1438":2,"1455":1,"1489":1,"1496":1,"1630":1,"1641":1,"1669":1,"1812":1,"1908":1,"1947":1,"1952":1,"1969":1,"1993":1,"2045":1}}],["checkin",{"2":{"474":1,"797":1,"801":2,"803":1,"1048":1,"1054":1,"1055":1,"1627":1},"3":{"0":2,"186":1,"474":1,"690":7,"691":1,"694":1,"797":1,"799":3,"801":4,"803":2,"1048":1,"1052":2,"1054":1,"1055":2,"1199":16,"1203":1,"1207":2,"1212":1,"1237":2,"1396":84,"1438":5,"1487":1,"1526":1,"1627":2,"1631":2,"2232":1}}],["checkemailconfirmed",{"2":{"2196":1},"3":{"1300":3,"2196":2}}],["checker",{"3":{"0":1,"1496":1,"2047":3}}],["checkedout",{"3":{"1749":2,"1750":1,"1764":1,"1765":1}}],["checkedin",{"3":{"1396":2}}],["checkedinon",{"3":{"1396":17,"1415":2}}],["checkedinbyuserid",{"2":{"799":1},"3":{"690":1,"799":1,"1396":12,"1631":1}}],["checked",{"3":{"0":2,"109":2,"135":1,"139":1,"160":1,"186":1,"209":1,"243":2,"250":2,"252":2,"254":1,"256":2,"257":2,"258":1,"259":1,"260":1,"261":2,"284":1,"318":1,"323":1,"335":3,"342":1,"350":2,"390":1,"397":1,"418":2,"423":1,"424":1,"426":1,"438":1,"442":1,"463":1,"464":1,"465":1,"489":1,"490":1,"507":1,"572":1,"576":1,"584":1,"585":1,"591":1,"611":1,"617":1,"628":1,"633":1,"642":1,"657":1,"660":1,"674":1,"692":1,"715":1,"718":1,"759":1,"766":1,"798":1,"811":1,"827":1,"838":1,"846":1,"867":1,"945":1,"959":1,"975":1,"1034":1,"1037":1,"1059":1,"1067":1,"1074":1,"1091":1,"1118":1,"1124":1,"1161":1,"1168":1,"1212":1,"1229":1,"1237":1,"1247":3,"1259":2,"1263":1,"1270":8,"1273":1,"1277":2,"1281":1,"1286":10,"1297":1,"1298":1,"1300":13,"1301":2,"1306":1,"1309":2,"1311":8,"1317":1,"1323":7,"1324":7,"1339":3,"1344":1,"1350":7,"1359":26,"1369":1,"1373":1,"1380":5,"1384":1,"1389":1,"1390":2,"1395":4,"1396":12,"1402":6,"1415":11,"1417":2,"1419":1,"1427":4,"1431":2,"1436":23,"1445":1,"1453":2,"1455":1,"1457":1,"1459":1,"1466":1,"1467":1,"1475":1,"1481":1,"1487":1,"1496":4,"1563":1,"1571":1,"1575":1,"1577":1,"1600":1,"1627":1,"1631":1,"1662":1,"1699":1,"1721":1,"1722":1,"1727":3,"1729":1,"1748":2,"1749":1,"1750":1,"1754":1,"1755":1,"1759":1,"1765":1,"1767":1,"1773":2,"1776":1,"1823":1,"1929":1,"1951":1,"1955":1,"1957":1,"1958":1,"1962":1,"1971":1,"1980":1,"1982":1,"2041":1,"2055":1,"2089":1,"2096":1,"2232":1}}],["checkstatusasync",{"3":{"1417":1}}],["checks",{"0":{"1332":1},"1":{"1960":1,"1961":1,"1962":1,"1963":1,"1964":1,"1965":1},"3":{"0":6,"12":1,"70":1,"74":1,"79":1,"100":1,"126":1,"132":1,"134":1,"135":1,"158":1,"166":1,"170":1,"187":1,"191":1,"201":1,"207":1,"233":2,"235":3,"236":1,"237":2,"243":1,"259":1,"282":1,"283":1,"287":1,"300":1,"349":1,"359":1,"402":2,"435":1,"453":1,"507":1,"536":1,"545":1,"558":1,"564":1,"566":1,"571":1,"572":1,"574":2,"577":1,"583":2,"591":1,"599":3,"600":1,"611":2,"626":1,"629":1,"651":1,"657":2,"659":1,"690":1,"700":1,"736":1,"744":1,"749":1,"757":3,"784":1,"826":1,"827":2,"829":1,"833":2,"834":1,"910":1,"913":1,"924":1,"956":1,"960":1,"990":1,"996":1,"1004":1,"1005":1,"1050":1,"1059":1,"1066":2,"1068":1,"1077":1,"1078":1,"1108":1,"1109":1,"1110":1,"1160":1,"1168":1,"1220":2,"1228":1,"1229":8,"1234":5,"1237":12,"1239":1,"1241":6,"1242":1,"1247":19,"1259":1,"1261":1,"1263":1,"1264":1,"1267":1,"1270":9,"1271":1,"1276":2,"1286":21,"1288":1,"1289":2,"1298":2,"1300":30,"1301":4,"1305":1,"1309":6,"1310":2,"1311":22,"1312":3,"1314":1,"1315":1,"1323":13,"1324":26,"1325":1,"1328":2,"1330":1,"1331":1,"1332":4,"1333":1,"1337":1,"1338":3,"1339":39,"1343":1,"1344":2,"1347":1,"1348":3,"1349":1,"1350":25,"1352":2,"1355":1,"1357":1,"1359":72,"1365":1,"1369":6,"1379":2,"1380":5,"1395":18,"1396":22,"1398":1,"1399":1,"1402":23,"1415":12,"1416":2,"1417":7,"1427":5,"1430":3,"1431":1,"1434":1,"1436":53,"1438":5,"1441":1,"1442":1,"1443":9,"1447":2,"1450":1,"1453":2,"1454":3,"1455":3,"1459":1,"1466":1,"1467":3,"1472":2,"1486":2,"1488":1,"1489":1,"1490":1,"1492":4,"1496":15,"1497":1,"1526":1,"1531":2,"1548":1,"1550":2,"1551":2,"1558":1,"1563":1,"1565":1,"1571":1,"1572":1,"1574":1,"1577":10,"1582":1,"1591":1,"1596":2,"1608":1,"1611":1,"1627":2,"1632":1,"1641":4,"1660":1,"1661":1,"1668":1,"1670":2,"1673":1,"1677":1,"1684":1,"1742":1,"1759":1,"1761":1,"1764":1,"1765":2,"1800":1,"1802":1,"1805":2,"1811":1,"1815":2,"1821":1,"1824":1,"1832":2,"1834":1,"1845":1,"1865":1,"1882":1,"1897":1,"1902":1,"1909":1,"1917":1,"1923":1,"1933":1,"1960":3,"1965":1,"1968":1,"1969":1,"1974":3,"1996":1,"1997":1,"2004":1,"2007":2,"2010":1,"2012":1,"2030":1,"2040":1,"2042":2,"2047":1,"2055":1,"2056":2,"2057":1,"2093":1,"2098":1,"2099":1,"2107":1,"2122":1,"2134":1,"2135":1,"2142":1,"2155":1,"2164":1,"2167":1,"2175":1,"2200":1,"2203":1,"2208":1,"2227":1}}],["check",{"0":{"1697":1},"1":{"687":1,"688":1,"689":1,"690":1,"691":1,"692":1,"693":1,"694":1},"3":{"0":24,"11":1,"24":1,"59":1,"80":1,"93":2,"94":1,"99":3,"109":1,"110":1,"115":1,"121":1,"122":2,"126":3,"133":1,"135":3,"157":2,"164":1,"166":1,"171":2,"175":1,"186":1,"188":1,"189":1,"195":2,"196":1,"200":1,"201":3,"202":2,"209":1,"225":1,"231":1,"233":1,"234":3,"235":7,"237":3,"239":1,"241":1,"245":3,"246":1,"247":1,"257":1,"259":1,"265":2,"268":1,"279":2,"284":2,"285":1,"290":1,"295":1,"296":1,"302":1,"305":1,"313":1,"316":1,"317":2,"318":6,"320":1,"322":2,"326":2,"340":1,"341":3,"342":2,"350":1,"352":2,"360":1,"370":1,"371":1,"373":1,"384":2,"386":1,"388":1,"390":1,"391":2,"402":1,"406":1,"413":1,"423":1,"427":2,"429":1,"448":3,"453":1,"458":1,"459":4,"460":1,"461":1,"464":1,"490":1,"499":1,"505":1,"507":1,"513":2,"529":1,"536":3,"551":1,"556":1,"564":1,"565":2,"566":1,"572":5,"574":2,"583":1,"584":1,"585":2,"591":1,"592":2,"594":3,"611":2,"617":1,"618":2,"619":1,"625":1,"626":1,"633":2,"636":3,"638":1,"642":1,"681":2,"683":2,"684":1,"685":1,"687":1,"688":1,"689":4,"690":5,"691":6,"692":2,"694":2,"701":1,"709":1,"724":1,"725":1,"729":2,"733":2,"743":1,"749":3,"756":1,"757":1,"758":1,"764":1,"765":1,"766":2,"767":2,"768":2,"769":1,"790":1,"799":3,"801":2,"827":6,"829":2,"834":1,"838":2,"839":1,"840":1,"847":1,"869":1,"876":1,"891":1,"905":3,"923":1,"924":1,"946":2,"948":1,"950":1,"962":1,"971":1,"974":1,"997":1,"1018":2,"1033":1,"1034":3,"1036":1,"1037":2,"1042":1,"1050":1,"1067":1,"1074":1,"1077":1,"1092":1,"1093":1,"1100":1,"1110":2,"1111":1,"1117":2,"1118":1,"1155":1,"1163":2,"1170":1,"1175":1,"1190":1,"1192":2,"1211":1,"1212":4,"1213":1,"1229":3,"1231":1,"1237":20,"1241":1,"1244":1,"1247":15,"1255":1,"1259":6,"1263":4,"1264":2,"1265":1,"1267":1,"1270":46,"1271":12,"1275":1,"1280":1,"1281":2,"1286":63,"1288":1,"1289":6,"1290":1,"1293":2,"1295":1,"1296":1,"1297":3,"1300":69,"1301":8,"1304":1,"1307":2,"1308":1,"1309":12,"1310":3,"1311":31,"1312":2,"1316":1,"1317":1,"1322":1,"1323":33,"1324":50,"1325":1,"1326":1,"1327":1,"1328":3,"1329":1,"1332":9,"1333":1,"1337":4,"1339":109,"1343":1,"1344":2,"1347":1,"1348":1,"1349":2,"1350":52,"1352":1,"1353":3,"1355":1,"1357":2,"1358":3,"1359":187,"1364":2,"1366":1,"1369":18,"1372":4,"1373":1,"1375":1,"1380":21,"1384":1,"1387":1,"1389":3,"1391":1,"1395":38,"1396":223,"1399":1,"1400":1,"1402":31,"1405":1,"1406":4,"1410":1,"1411":1,"1412":1,"1414":1,"1415":39,"1416":13,"1417":20,"1421":1,"1422":1,"1427":8,"1430":2,"1431":1,"1433":2,"1436":95,"1437":4,"1438":30,"1441":6,"1442":2,"1443":11,"1444":1,"1446":1,"1447":1,"1449":3,"1453":3,"1454":2,"1455":10,"1457":6,"1460":2,"1467":9,"1475":5,"1476":4,"1479":1,"1484":2,"1485":1,"1487":7,"1488":2,"1489":2,"1490":1,"1491":1,"1492":4,"1496":27,"1500":3,"1514":1,"1524":3,"1525":1,"1526":7,"1530":1,"1531":3,"1533":1,"1534":5,"1536":1,"1547":1,"1576":1,"1577":8,"1582":1,"1583":1,"1586":5,"1590":1,"1591":9,"1595":1,"1596":4,"1598":1,"1599":1,"1600":8,"1614":1,"1625":1,"1627":8,"1629":2,"1630":1,"1631":13,"1632":1,"1638":1,"1639":6,"1644":1,"1651":2,"1652":1,"1660":1,"1662":2,"1663":1,"1666":1,"1668":2,"1670":2,"1677":1,"1679":1,"1687":1,"1719":1,"1731":1,"1747":2,"1749":1,"1755":2,"1765":7,"1767":1,"1772":1,"1776":4,"1777":1,"1782":1,"1800":3,"1801":1,"1802":1,"1805":1,"1808":1,"1809":2,"1811":1,"1818":1,"1821":6,"1823":2,"1825":1,"1826":2,"1832":4,"1833":1,"1842":1,"1871":1,"1872":1,"1875":1,"1877":1,"1881":1,"1886":1,"1888":1,"1889":1,"1893":1,"1899":1,"1902":1,"1909":2,"1912":2,"1915":1,"1917":1,"1922":1,"1923":2,"1929":1,"1930":2,"1933":1,"1934":2,"1940":2,"1941":2,"1945":1,"1954":2,"1959":1,"1968":1,"1972":1,"1977":1,"1978":2,"1982":1,"1984":1,"1985":1,"1992":5,"1993":1,"1994":1,"1995":3,"1996":3,"1997":3,"2007":1,"2008":1,"2010":1,"2024":3,"2034":2,"2039":2,"2041":1,"2042":1,"2043":1,"2048":2,"2049":2,"2055":4,"2056":1,"2057":1,"2059":1,"2066":2,"2079":1,"2099":1,"2100":1,"2106":1,"2108":2,"2116":1,"2119":1,"2122":1,"2123":1,"2126":2,"2128":3,"2131":2,"2134":2,"2135":1,"2136":2,"2138":2,"2139":2,"2142":2,"2158":1,"2159":1,"2160":2,"2164":1,"2167":1,"2169":5,"2172":1,"2173":1,"2174":1,"2177":1,"2180":1,"2184":1,"2193":1,"2197":1,"2200":1,"2203":2,"2228":1,"2232":2}}],["chips",{"3":{"1324":2,"1385":1,"1395":5,"1496":1,"1742":1,"1750":1}}],["chip",{"3":{"0":1,"618":1,"905":1,"1324":2,"1385":1,"1395":19,"1402":1,"1436":3,"1526":1,"1742":1,"1750":2,"1773":1}}],["childitem",{"3":{"1728":1}}],["childid",{"3":{"1237":1,"1395":2}}],["childrules",{"3":{"1359":2}}],["childresult",{"3":{"1350":4}}],["children",{"3":{"0":2,"39":4,"341":1,"397":1,"402":1,"922":1,"926":1,"1168":1,"1212":1,"1231":1,"1237":6,"1247":2,"1270":1,"1281":1,"1286":2,"1310":4,"1324":1,"1333":2,"1339":5,"1342":2,"1343":4,"1346":1,"1350":41,"1352":1,"1359":39,"1369":1,"1380":1,"1382":1,"1385":3,"1387":1,"1395":21,"1398":1,"1401":1,"1402":5,"1436":26,"1443":2,"1489":1,"1556":1,"1575":1,"1630":2,"1632":5,"1635":2,"1638":8,"1639":8,"1640":3,"1647":1,"1651":2,"1664":1,"1666":1,"1677":1,"1685":3,"1720":1,"1748":2,"1749":2,"2010":2,"2061":1,"2064":2,"2157":1}}],["childforeignkeyselector",{"3":{"1310":3,"1359":12,"1402":2}}],["childfk",{"3":{"1310":1,"1359":1,"1865":1}}],["childless",{"3":{"1237":1,"1350":4,"1354":1}}],["childlessfixture",{"3":{"1199":2,"1207":1,"1436":6}}],["childd",{"3":{"1199":3,"1207":1}}],["childcollectiontypenames",{"3":{"1436":1}}],["childcollection",{"3":{"1247":1,"1310":3}}],["childc",{"3":{"1199":2,"1207":1}}],["childb",{"3":{"1199":2,"1207":1}}],["childa",{"3":{"1199":2,"1207":1}}],["childmodel",{"3":{"1199":3,"1207":1}}],["childentityhandlerbase",{"3":{"1207":2,"1265":6,"1270":6,"1359":43}}],["childentityhandlerbasetests",{"3":{"1199":1,"1207":1,"1270":2}}],["childentitydeletepathtests",{"3":{"1199":1,"1207":1}}],["childentitydeletepath",{"2":{"1386":1},"3":{"1199":6,"1203":1,"1207":1,"1324":1,"1386":1,"1395":12}}],["childentityservices",{"3":{"881":1,"1207":5,"1324":2,"1386":5,"1395":39}}],["childentityservicebasetests",{"3":{"1199":1,"1207":5,"1324":1}}],["childentityservicebase",{"2":{"879":1,"883":2,"1386":1},"3":{"0":2,"879":2,"881":5,"883":3,"1199":6,"1203":1,"1207":2,"1300":2,"1324":12,"1386":1,"1395":14,"1496":1}}],["childentity",{"3":{"341":1,"1199":4,"1207":1,"1286":1}}],["childnavigationdescriptortests",{"3":{"1199":1,"1207":4}}],["childnavigationdescriptor",{"2":{"11":1,"1865":1},"3":{"11":1,"1199":7,"1203":1,"1207":2,"1310":17,"1359":22,"1402":3,"1865":2}}],["child",{"0":{"1386":1},"3":{"0":3,"10":1,"11":1,"39":3,"43":2,"164":1,"339":1,"341":5,"344":1,"397":1,"599":1,"633":2,"782":1,"921":2,"926":3,"1115":1,"1116":1,"1117":1,"1140":1,"1167":1,"1168":8,"1170":2,"1191":1,"1212":2,"1231":10,"1232":2,"1237":32,"1243":4,"1244":1,"1247":9,"1259":2,"1260":1,"1265":9,"1270":21,"1271":2,"1278":1,"1286":18,"1310":23,"1311":6,"1323":1,"1324":5,"1339":8,"1340":1,"1342":2,"1343":5,"1345":3,"1346":1,"1347":1,"1350":127,"1353":2,"1354":4,"1359":268,"1369":6,"1371":2,"1374":2,"1380":24,"1386":1,"1393":1,"1395":49,"1396":8,"1401":1,"1402":26,"1416":1,"1436":41,"1438":1,"1465":2,"1467":1,"1496":1,"1526":1,"1577":3,"1586":1,"1591":1,"1630":10,"1631":10,"1632":10,"1633":1,"1634":1,"1635":5,"1637":2,"1638":10,"1639":5,"1640":3,"1650":1,"1651":1,"1661":1,"1662":1,"1666":2,"1668":1,"1683":1,"1684":2,"1686":7,"1700":1,"1701":2,"1704":1,"1719":1,"1727":14,"1728":4,"1729":5,"1730":3,"1731":2,"1764":1,"1810":3,"1863":1,"1865":1,"1871":3,"1876":4,"1877":1,"1988":2,"2010":1,"2154":1,"2157":2}}],["chunking",{"3":{"1320":1,"1323":2}}],["chunks",{"3":{"1307":1,"1309":1,"1323":1}}],["chunk",{"3":{"435":1,"1286":1,"1323":5,"1383":2,"1395":1,"1479":1}}],["chunked",{"3":{"0":1,"434":1,"1323":1,"1935":1}}],["churns",{"3":{"1436":1,"1438":1,"1748":1,"1989":1}}],["churning",{"3":{"1402":1,"1436":1}}],["churn",{"3":{"0":1,"87":1,"785":1,"798":1,"800":1,"891":1,"1237":1,"1259":1,"1286":1,"1324":1,"1338":1,"1339":3,"1402":1,"1436":7,"1492":1,"1496":1,"1677":1}}],["chaos",{"3":{"1566":1,"1577":1}}],["chasing",{"3":{"1440":1,"1446":1}}],["chased",{"3":{"1551":1}}],["chases",{"3":{"1436":1,"2153":1}}],["chase",{"3":{"1277":1,"1286":1,"1350":1,"1415":1,"1672":1,"1839":1}}],["chapter",{"0":{"1450":1,"1461":1,"1493":1},"3":{"1191":1,"1192":5,"1193":1,"1194":1,"1200":2,"1201":2,"1202":1,"1203":1,"1210":1,"1212":3,"1214":1,"1217":4,"1223":2,"1225":1,"1227":1,"1229":20,"1238":1,"1240":1,"1248":2,"1251":1,"1271":2,"1272":1,"1286":2,"1287":1,"1300":2,"1301":1,"1302":1,"1309":3,"1310":4,"1311":1,"1312":3,"1313":4,"1316":4,"1320":1,"1321":1,"1322":1,"1323":3,"1324":10,"1325":1,"1339":6,"1340":3,"1345":1,"1346":1,"1347":1,"1350":7,"1351":2,"1359":8,"1360":1,"1361":2,"1367":1,"1369":11,"1370":2,"1380":1,"1381":1,"1395":4,"1396":14,"1397":3,"1401":1,"1402":3,"1403":3,"1415":7,"1416":6,"1417":7,"1418":2,"1428":1,"1436":9,"1438":1,"1439":1,"1451":3,"1452":1,"1455":4,"1460":1,"1462":3,"1463":1,"1465":1,"1470":1,"1471":2,"1473":1,"1474":2,"1475":1,"1476":1,"1482":2,"1485":4,"1494":3,"1496":80,"1497":1,"1498":2,"1499":1,"1500":1,"1503":1,"1505":1,"1517":7,"1674":1,"1812":2,"1815":1,"1817":2,"1834":1,"1862":1,"1912":1,"1924":1,"1959":1,"2014":1,"2030":2,"2038":1,"2080":1,"2123":1,"2146":1,"2230":2}}],["chapters",{"0":{"1192":1},"3":{"329":1,"751":1,"1011":1,"1012":1,"1014":1,"1191":1,"1192":2,"1194":1,"1200":1,"1201":1,"1210":2,"1212":1,"1216":1,"1271":1,"1320":1,"1339":1,"1452":1,"1471":1,"1485":2,"1496":26,"1498":1,"1499":2,"1502":2,"1514":1,"1517":2,"1674":2}}],["chatrole",{"3":{"1427":2}}],["chatresponseupdate",{"3":{"1427":2}}],["chatresponseformat",{"2":{"1018":1,"1366":1},"3":{"1018":1,"1366":1}}],["chatresponse",{"2":{"1016":1,"1019":1,"1092":1},"3":{"1016":1,"1018":2,"1019":1,"1091":1,"1092":1,"1369":2,"1427":7,"1435":1,"1492":1}}],["chatclientbuilder",{"2":{"1421":1,"1427":1},"3":{"1421":1,"1427":3}}],["chatclient",{"3":{"1369":2}}],["chatclientmetadata",{"2":{"1091":1,"1425":1},"3":{"1091":1,"1425":1,"1427":3}}],["chatoptions",{"2":{"1419":1,"1424":1,"2174":1},"3":{"1369":4,"1419":1,"1424":1,"1427":17,"2174":1}}],["chatmessage",{"3":{"1369":2,"1427":4}}],["chatfinishreason",{"2":{"1366":1},"3":{"1366":1,"1369":2}}],["chatty",{"3":{"1402":1,"1544":1,"1568":1}}],["chattoolpolicy",{"2":{"1095":1},"3":{"1091":1,"1092":1,"1095":2,"1199":4,"1203":1,"1207":2,"1422":2,"1427":14,"1496":2}}],["chatter",{"3":{"18":1,"1259":2,"1339":2,"1402":1,"1443":1,"1456":1,"1467":2,"1490":1,"1677":1,"2010":1,"2159":1}}],["chatguardrailexception",{"2":{"1021":1,"1089":1,"1366":1,"2175":1},"3":{"1018":1,"1021":1,"1089":1,"1091":1,"1199":5,"1203":1,"1207":2,"1366":1,"1369":2,"1423":4,"1427":8,"1496":1,"2175":3}}],["chat",{"1":{"2170":1,"2171":1,"2172":1,"2173":1,"2174":1,"2175":1,"2176":1,"2177":1,"2178":1,"2179":1,"2180":1},"3":{"404":1,"406":1,"408":1,"1018":1,"1089":1,"1091":6,"1112":1,"1118":1,"1192":2,"1202":1,"1203":9,"1207":16,"1324":1,"1359":3,"1360":1,"1366":3,"1369":5,"1396":1,"1422":2,"1423":5,"1425":2,"1427":31,"1437":3,"1496":1,"1526":3,"1577":1,"1585":1,"1779":1,"2169":2,"2170":2,"2174":1,"2193":1,"2209":1}}],["challengeprovider",{"3":{"1311":1}}],["challenges",{"3":{"1311":1,"1487":1,"1898":1}}],["challengesecondfactorcountingfailuresasync",{"3":{"1300":2}}],["challengesecondfactorasync",{"2":{"1059":1,"2196":1},"3":{"1059":1,"1300":2,"2196":2}}],["challengeasync",{"2":{"2196":1},"3":{"1300":4,"1323":1,"2196":1}}],["challenged",{"3":{"1300":1}}],["challenge",{"3":{"0":1,"189":1,"350":1,"352":1,"353":1,"420":1,"1059":2,"1188":1,"1212":1,"1289":1,"1293":2,"1298":1,"1300":18,"1311":4,"1323":9,"1324":5,"1396":1,"1406":1,"1415":1,"1417":1,"1436":3,"1438":1,"1489":1,"1490":1,"1975":2,"1976":2,"1978":1,"2194":2,"2197":2,"2202":1}}],["chartable",{"3":{"2156":1}}],["charting",{"3":{"1270":1}}],["chart",{"3":{"1247":1,"1402":1,"1961":1,"1965":1,"2159":1}}],["charters",{"3":{"1505":1,"1517":1}}],["charter",{"3":{"1202":1,"1324":1,"1339":1,"1496":1}}],["charset",{"3":{"1347":2,"1359":4,"1365":1,"1369":1,"1384":2}}],["charset=utf",{"3":{"1311":2}}],["chars",{"3":{"690":1,"1303":1,"1350":1,"1359":3,"1630":1,"1640":1,"1764":9}}],["charges",{"3":{"1027":1,"1821":1}}],["charge",{"3":{"201":1,"1026":2,"1027":1,"1029":1,"1243":1,"1247":1,"1263":1,"1417":1,"1436":1,"1460":1,"1764":1,"1907":1,"2080":1}}],["charged",{"3":{"0":1,"122":1,"1024":1,"1028":2,"1270":1,"1749":2,"1750":1,"1764":1,"1767":1,"1776":1,"1906":1}}],["characters",{"3":{"231":1,"397":1,"409":1,"674":1,"676":1,"698":1,"827":2,"1018":1,"1026":2,"1091":1,"1234":1,"1237":3,"1247":1,"1259":1,"1271":6,"1276":1,"1286":4,"1288":1,"1290":1,"1300":7,"1309":2,"1311":2,"1320":1,"1324":7,"1337":1,"1339":1,"1344":1,"1347":1,"1350":2,"1359":33,"1369":2,"1395":1,"1396":2,"1402":6,"1415":1,"1417":1,"1422":1,"1427":2,"1436":5,"1496":1,"1630":3,"1631":3,"1632":2,"1641":1,"1685":2,"1686":2,"1764":1,"1765":1,"1767":11,"1829":1,"1933":1,"1934":1,"1982":1,"2012":1,"2174":2,"2179":1}}],["character",{"3":{"26":1,"373":1,"427":1,"657":1,"698":1,"710":1,"741":1,"832":1,"905":1,"931":2,"972":1,"1018":1,"1228":1,"1229":1,"1232":1,"1237":2,"1241":2,"1259":1,"1270":1,"1271":2,"1282":1,"1286":1,"1289":2,"1300":7,"1309":4,"1311":2,"1312":3,"1323":1,"1324":12,"1339":4,"1344":2,"1347":1,"1350":5,"1359":13,"1362":3,"1395":7,"1396":4,"1415":2,"1416":1,"1417":7,"1426":1,"1427":1,"1433":2,"1436":9,"1438":2,"1496":1,"1641":1,"1829":1,"1842":2,"1851":1,"2127":1,"2150":1}}],["char",{"3":{"0":1,"905":1,"1212":1,"1247":1,"1286":1,"1300":1,"1324":4,"1350":4,"1488":1}}],["chain+cost",{"3":{"1526":1}}],["chainable",{"3":{"1271":1,"1324":1}}],["chaining",{"3":{"0":1,"749":1,"846":1,"1247":1,"1259":1,"1286":4,"1300":2,"1309":5,"1311":4,"1312":2,"1323":1,"1324":2,"1350":2,"1359":2,"1369":1,"1380":2,"1395":2,"1396":5,"1415":4,"1417":4,"1427":2,"1436":4,"1891":1}}],["chained",{"3":{"0":3,"258":1,"261":1,"827":2,"828":1,"831":1,"832":1,"905":1,"1124":1,"1128":1,"1212":1,"1300":2,"1324":3,"1337":1,"1339":3,"1359":2,"1369":1,"1436":2,"1438":1,"1467":2,"1488":1,"1489":1,"1670":1,"1684":1,"2024":1}}],["chains",{"1":{"1863":1,"1864":1,"1865":1,"1866":1,"1867":1,"1868":1},"3":{"0":2,"117":1,"121":2,"125":1,"195":1,"448":1,"672":4,"674":2,"752":1,"826":1,"827":1,"846":1,"872":1,"1212":1,"1224":1,"1247":2,"1260":1,"1270":5,"1271":1,"1286":4,"1300":5,"1311":4,"1323":1,"1324":3,"1339":3,"1359":16,"1377":1,"1380":1,"1396":2,"1402":2,"1416":1,"1419":1,"1436":3,"1441":1,"1443":1,"1444":1,"1445":1,"1543":1,"1545":1,"1786":1,"1821":1,"1829":2,"1833":1,"1862":1,"1863":1,"1864":1,"1868":1,"1889":1,"1891":1,"1892":1,"2008":1,"2022":1,"2207":1}}],["chain",{"0":{"1231":1,"1232":1,"1569":1,"1673":1,"1738":1},"1":{"367":1,"368":1,"369":1,"370":1,"371":1,"372":1,"373":1,"374":1,"375":1,"376":1},"3":{"0":10,"99":1,"107":1,"109":2,"110":2,"111":1,"115":4,"121":3,"122":2,"126":2,"127":1,"128":1,"259":1,"350":1,"367":1,"369":3,"370":1,"371":1,"373":2,"376":2,"388":1,"389":1,"395":1,"402":1,"404":2,"407":1,"408":1,"409":1,"499":1,"500":2,"501":2,"529":1,"532":1,"549":1,"574":1,"590":1,"591":1,"592":1,"595":1,"625":1,"626":1,"642":1,"665":1,"672":4,"674":6,"675":4,"676":7,"677":2,"692":2,"698":2,"701":1,"707":1,"711":1,"715":1,"721":1,"748":1,"749":2,"751":1,"756":1,"765":1,"819":1,"821":1,"823":1,"826":1,"852":1,"854":2,"870":1,"874":1,"905":1,"906":1,"908":1,"915":1,"923":1,"925":1,"964":1,"988":1,"1046":1,"1058":2,"1075":1,"1212":3,"1214":1,"1216":1,"1220":2,"1229":7,"1230":2,"1231":1,"1232":1,"1235":1,"1237":3,"1240":1,"1241":1,"1247":17,"1259":1,"1260":1,"1262":2,"1269":2,"1270":17,"1271":14,"1278":2,"1283":1,"1284":1,"1286":23,"1290":2,"1300":9,"1301":1,"1309":1,"1310":1,"1311":25,"1322":1,"1323":10,"1324":9,"1326":1,"1327":1,"1329":1,"1333":2,"1339":15,"1350":9,"1353":1,"1359":28,"1369":4,"1380":1,"1395":3,"1396":3,"1402":5,"1403":1,"1406":1,"1407":1,"1408":1,"1415":8,"1416":5,"1417":7,"1421":1,"1427":3,"1431":2,"1436":57,"1438":5,"1441":1,"1443":2,"1445":2,"1448":1,"1450":1,"1453":2,"1454":3,"1455":4,"1456":1,"1459":7,"1460":1,"1461":3,"1463":1,"1465":1,"1468":2,"1475":1,"1483":1,"1486":3,"1487":1,"1489":2,"1491":1,"1492":2,"1493":1,"1496":1,"1499":1,"1503":1,"1507":1,"1524":1,"1526":12,"1530":1,"1531":1,"1533":1,"1534":1,"1535":1,"1576":1,"1577":2,"1586":1,"1591":9,"1599":1,"1600":1,"1651":1,"1667":1,"1673":1,"1753":1,"1796":1,"1799":1,"1805":1,"1806":1,"1810":1,"1815":1,"1821":2,"1825":4,"1826":2,"1827":1,"1828":2,"1829":1,"1834":1,"1840":1,"1856":1,"1864":2,"1868":1,"1922":1,"1927":1,"1930":1,"1977":1,"1983":1,"1984":1,"2008":1,"2010":1,"2012":4,"2013":2,"2024":1,"2074":1,"2090":1,"2139":1,"2156":1,"2157":2,"2184":1,"2190":2,"2193":1,"2203":2,"2227":1,"2232":3}}],["chances",{"3":{"882":1,"938":1,"1359":1}}],["chance",{"3":{"234":1,"235":1,"411":1,"413":1,"673":1,"750":1,"1090":1,"1265":1,"1270":1,"1286":2,"1312":1,"1339":1,"1359":1,"1380":1,"1396":1,"1413":1,"1415":2,"1416":2,"1417":6,"1669":1,"2170":1}}],["channelreader",{"3":{"1396":2}}],["channelreferencecountertests",{"3":{"1199":1,"1207":1,"1324":1}}],["channelreferencecounter",{"3":{"1199":3,"1203":1,"1207":2,"1324":9,"1496":1}}],["channeljoinrefused",{"3":{"1309":1}}],["channelsync",{"3":{"1324":2}}],["channelsubscriptions",{"3":{"1324":2}}],["channelsubscription",{"3":{"1198":1,"1199":2,"1203":1,"1207":1,"1324":7,"1496":2}}],["channels",{"0":{"1512":1},"1":{"1942":1,"1943":1,"1944":1,"1945":1,"1946":1,"1947":1,"1948":1,"1949":1,"1950":1,"2181":1,"2182":1,"2183":1,"2184":1,"2185":1,"2186":1,"2187":1,"2188":1,"2189":1,"2190":1,"2191":1,"2192":1,"2193":1},"2":{"1400":1},"3":{"109":1,"110":1,"111":1,"225":3,"384":1,"433":1,"434":1,"1286":2,"1300":1,"1302":1,"1304":1,"1309":7,"1311":1,"1320":1,"1323":1,"1324":3,"1396":4,"1400":1,"1417":3,"1436":1,"1496":1,"1556":1,"1669":1,"1677":1,"1779":2,"1806":1,"1942":2,"1947":2,"1950":1,"2165":1,"2180":2,"2181":1,"2203":1,"2206":1,"2208":1}}],["channelkeyregexcache",{"3":{"1309":1}}],["channelkeymatchtimeout",{"3":{"1309":2}}],["channelkey",{"3":{"0":1,"380":1,"1286":4,"1309":17,"1324":8,"1396":10,"1402":3,"1944":1}}],["channelkeypattern",{"2":{"0":1,"380":1,"1305":1,"1309":1,"1402":1,"1945":1},"3":{"0":1,"380":1,"1305":1,"1307":1,"1309":2,"1323":2,"1402":1,"1945":1}}],["channel",{"0":{"1305":1,"1935":1,"1936":1,"1938":1,"1945":1},"1":{"222":1,"223":1,"224":1,"225":1,"226":1,"227":1,"228":1,"229":1,"230":1,"231":1,"377":1,"378":1,"379":1,"380":1,"381":1,"382":1,"383":1,"384":1,"431":1,"432":1,"433":1,"434":1,"435":1,"436":1,"1097":1,"1098":1,"1099":1,"1100":1,"1101":1,"1102":1,"1103":1,"1104":1},"2":{"1100":1,"1947":1,"2183":1},"3":{"0":18,"25":2,"27":1,"92":1,"95":1,"105":1,"110":1,"222":1,"223":2,"225":5,"226":1,"228":4,"229":1,"230":3,"255":1,"265":2,"273":1,"343":2,"377":1,"378":2,"380":3,"381":2,"382":2,"383":1,"384":6,"431":1,"434":1,"517":2,"518":2,"519":1,"521":1,"522":1,"608":1,"610":1,"640":1,"856":1,"857":1,"882":1,"896":1,"897":3,"914":2,"915":1,"916":1,"926":1,"930":1,"1066":1,"1097":1,"1099":2,"1100":5,"1101":2,"1102":1,"1104":1,"1112":1,"1150":1,"1157":1,"1192":6,"1202":2,"1203":2,"1212":5,"1226":1,"1229":1,"1259":1,"1260":1,"1265":1,"1270":6,"1285":1,"1286":9,"1292":1,"1300":3,"1302":3,"1307":5,"1308":2,"1309":40,"1312":1,"1320":6,"1323":21,"1324":40,"1328":1,"1339":1,"1359":8,"1380":7,"1395":1,"1396":52,"1397":2,"1399":8,"1400":4,"1401":3,"1402":84,"1415":6,"1417":11,"1429":1,"1436":7,"1437":1,"1438":4,"1441":1,"1445":1,"1448":1,"1460":1,"1466":1,"1467":2,"1488":1,"1490":1,"1496":6,"1512":1,"1599":2,"1644":1,"1677":1,"1733":1,"1902":1,"1922":1,"1935":2,"1936":2,"1937":1,"1938":2,"1940":1,"1941":4,"1943":1,"1944":5,"1945":6,"1947":5,"1948":1,"1949":2,"1950":4,"2025":1,"2079":1,"2083":2,"2158":1,"2183":5,"2190":3,"2192":1,"2193":2,"2232":6}}],["changequantity",{"3":{"1686":1}}],["changeorderitemquantityrequest",{"3":{"1686":1}}],["changeorderstatusrequest",{"2":{"1686":1,"1702":1},"3":{"1686":2,"1702":1}}],["changeoption",{"3":{"1402":4}}],["changeitemquantityasync",{"3":{"1686":1}}],["changeitemquantityhandler",{"3":{"1686":1}}],["changeitemquantitycommand",{"2":{"1686":1},"3":{"1686":2}}],["changeitemquantity",{"2":{"1684":1},"3":{"1684":1,"1686":4}}],["changename",{"2":{"1747":1},"3":{"1436":1,"1747":1,"1769":1}}],["changerole",{"3":{"1409":1,"1415":2}}],["changeroleasync",{"3":{"1324":1,"1415":1}}],["changeable",{"3":{"1359":1}}],["changeaddress",{"2":{"1747":1},"3":{"1271":2,"1436":1,"1747":1,"1769":1}}],["changetracking",{"3":{"1286":3}}],["changetracker",{"2":{"1231":1,"1233":1,"1278":1,"1840":1},"3":{"715":1,"988":1,"1231":1,"1233":1,"1237":2,"1273":1,"1278":1,"1286":10,"1840":1}}],["changevariantskuhandler",{"3":{"1286":1}}],["changevariantskucommand",{"3":{"1270":1}}],["changevariantprice",{"3":{"1286":1}}],["changevariantpricehandler",{"3":{"1286":2}}],["changevariantpricecommand",{"3":{"1270":1}}],["changeemail",{"2":{"1747":1},"3":{"1237":1,"1436":1,"1747":1,"1769":1}}],["changepreferencesasync",{"3":{"1311":1}}],["changepreferences",{"3":{"1203":4,"1207":10,"1291":2,"1300":4,"1322":1,"1323":9,"1415":9,"1436":1,"1496":2}}],["changepreferenceshandlertests",{"3":{"1199":1,"1207":2}}],["changepreferenceshandler",{"3":{"1199":4,"1203":1,"1207":2,"1323":3,"1406":2,"1415":9}}],["changepreferenceshandlerbasetests",{"3":{"1199":1,"1207":4,"1323":1}}],["changepreferenceshandlerbase",{"2":{"1291":1,"1322":1},"3":{"1199":3,"1203":1,"1207":2,"1291":3,"1300":6,"1322":2,"1323":8,"1415":6}}],["changepreferencesrequest",{"2":{"1291":1},"3":{"1199":12,"1203":1,"1207":2,"1291":2,"1300":6,"1311":1,"1323":3,"1415":4,"1496":1}}],["changepreferencescommandvalidator",{"3":{"1199":1,"1203":1,"1207":2,"1270":1,"1415":5}}],["changepreferencescommand",{"3":{"572":1,"1199":8,"1203":1,"1207":2,"1300":2,"1323":6,"1406":3,"1415":11,"1436":3,"1489":1}}],["changeprice",{"2":{"1028":1},"3":{"1028":1}}],["changepasswordfailed",{"3":{"1526":1}}],["changepasswordbutton",{"3":{"1436":1}}],["changepasswordasync",{"3":{"1311":1,"1324":3,"1436":1}}],["changepasswordcommand",{"3":{"1199":5,"1203":1,"1207":2,"1270":2,"1300":2,"1311":2,"1323":6,"1406":2,"1415":12}}],["changepasswordrequestvalidatortests",{"3":{"1199":1,"1207":2}}],["changepasswordrequestvalidator",{"2":{"1404":1},"3":{"1199":2,"1203":1,"1207":2,"1271":2,"1300":2,"1404":1,"1415":9,"1591":1}}],["changepasswordrequest",{"2":{"1289":1},"3":{"1199":15,"1203":1,"1207":2,"1289":1,"1300":4,"1311":1,"1323":1,"1324":2,"1415":5,"1591":1}}],["changepassword",{"2":{"314":1,"1291":1,"1500":1},"3":{"285":1,"310":3,"314":2,"353":1,"908":1,"926":1,"946":1,"1059":1,"1062":1,"1203":3,"1207":8,"1291":2,"1294":1,"1300":8,"1311":1,"1322":1,"1323":14,"1405":1,"1415":18,"1436":2,"1496":1,"1500":1,"1591":1,"1769":1}}],["changepasswordhandlertests",{"3":{"1199":1,"1207":2,"1436":1}}],["changepasswordhandler",{"2":{"310":1,"314":1},"3":{"281":1,"310":3,"314":4,"926":1,"1199":3,"1203":1,"1207":2,"1300":2,"1323":4,"1406":3,"1415":16}}],["changepasswordhandlerbasetests",{"3":{"1199":1,"1207":4,"1323":1,"1438":2}}],["changepasswordhandlerbase",{"2":{"281":1,"285":1,"310":1,"314":1,"353":1,"908":1,"946":1,"949":1,"1062":1,"1291":1,"1403":1},"3":{"281":2,"285":2,"310":4,"314":3,"353":2,"908":3,"909":1,"946":2,"949":1,"1059":2,"1062":2,"1199":3,"1203":1,"1207":2,"1291":2,"1300":8,"1322":5,"1323":13,"1403":1,"1415":14,"1496":1,"1577":1}}],["changelog",{"2":{"1674":1},"3":{"27":1,"135":2,"137":2,"138":2,"139":2,"140":2,"142":2,"286":1,"326":1,"365":2,"366":2,"449":1,"497":2,"502":1,"657":1,"660":1,"698":1,"825":1,"832":1,"857":1,"990":1,"991":2,"1003":2,"1004":3,"1007":2,"1091":1,"1095":3,"1119":1,"1124":2,"1127":1,"1128":2,"1132":2,"1136":2,"1160":2,"1173":1,"1324":1,"1577":7,"1585":2,"1586":2,"1673":3,"1674":1,"1675":1,"1734":1,"1735":1,"1737":1,"2065":1,"2131":1,"2232":1}}],["changedby",{"3":{"715":1,"1286":4,"1300":2,"1323":1}}],["changedon",{"2":{"715":1,"720":1,"1273":1},"3":{"715":3,"720":1,"1270":1,"1273":1,"1286":10,"1323":1}}],["changed",{"0":{"1277":1},"2":{"781":1,"923":1},"3":{"0":4,"1":1,"24":1,"25":1,"26":2,"27":1,"34":1,"43":1,"51":1,"82":1,"103":1,"104":1,"112":1,"115":1,"120":1,"123":1,"125":1,"126":1,"127":1,"130":1,"135":1,"136":1,"138":1,"139":4,"140":1,"141":1,"142":2,"149":1,"152":1,"164":1,"171":1,"178":1,"181":1,"189":1,"190":1,"200":1,"202":1,"203":1,"220":1,"238":1,"249":1,"250":1,"251":1,"252":1,"254":1,"256":1,"257":1,"258":2,"259":3,"260":1,"261":1,"265":1,"268":1,"276":1,"284":1,"296":1,"303":1,"314":1,"322":1,"323":1,"324":1,"336":1,"337":1,"339":1,"345":1,"354":1,"363":1,"366":1,"375":1,"393":1,"407":1,"416":1,"423":1,"424":1,"425":2,"426":1,"428":1,"429":1,"430":1,"451":1,"453":1,"454":2,"463":1,"465":1,"474":2,"475":1,"476":1,"477":1,"478":1,"495":1,"502":1,"514":1,"522":2,"523":1,"524":1,"531":1,"538":1,"545":1,"549":1,"550":1,"551":2,"552":1,"559":1,"564":1,"567":1,"573":1,"577":1,"578":1,"594":1,"602":1,"604":1,"607":7,"613":1,"616":4,"617":1,"621":1,"626":1,"629":1,"636":1,"652":1,"660":1,"669":1,"677":1,"685":1,"688":1,"691":1,"693":2,"710":1,"714":1,"715":2,"716":1,"717":3,"720":1,"728":1,"744":1,"752":1,"777":1,"781":1,"782":1,"783":1,"797":1,"800":1,"803":1,"804":1,"805":1,"806":1,"813":1,"814":1,"822":1,"832":1,"833":2,"836":1,"838":1,"840":2,"841":1,"849":1,"850":1,"857":1,"864":1,"877":1,"881":1,"891":2,"892":1,"900":1,"908":1,"909":1,"917":1,"921":1,"923":1,"927":1,"933":1,"934":1,"949":1,"960":1,"967":1,"980":1,"983":1,"1007":1,"1016":1,"1019":1,"1028":1,"1042":1,"1052":1,"1054":1,"1058":1,"1070":1,"1078":1,"1086":1,"1093":1,"1095":1,"1103":1,"1111":1,"1118":1,"1119":1,"1128":1,"1136":1,"1142":1,"1146":1,"1156":1,"1180":1,"1232":1,"1237":5,"1242":1,"1247":1,"1257":1,"1259":1,"1265":1,"1270":4,"1271":1,"1277":1,"1279":1,"1281":1,"1286":8,"1300":1,"1301":2,"1309":2,"1311":2,"1323":14,"1324":10,"1339":5,"1343":1,"1350":9,"1359":2,"1369":1,"1380":3,"1395":11,"1396":9,"1399":1,"1401":3,"1402":18,"1405":1,"1407":1,"1409":1,"1415":5,"1436":13,"1438":2,"1445":1,"1447":1,"1453":3,"1455":3,"1467":3,"1474":1,"1476":1,"1485":1,"1492":2,"1496":35,"1500":1,"1526":1,"1531":1,"1547":1,"1554":1,"1631":4,"1632":1,"1634":2,"1635":1,"1639":6,"1641":3,"1656":1,"1672":1,"1686":1,"1704":1,"1727":1,"1734":1,"1735":1,"1748":2,"1767":1,"1769":2,"1776":1,"1839":3,"1850":1,"1871":1,"1876":1,"1877":1,"1887":1,"1890":1,"1911":1,"1912":1,"1918":1,"2027":1,"2107":1,"2150":1,"2167":1,"2192":1,"2197":1,"2200":1,"2232":1}}],["changeshoppingcartitemquantity",{"2":{"1769":1},"3":{"1769":1}}],["changestatusasync",{"3":{"1702":2}}],["changestatus",{"2":{"1701":1},"3":{"1686":3,"1701":1,"1702":1,"1727":1}}],["changes+supply",{"3":{"1526":1}}],["changes",{"0":{"1735":1,"1769":1},"2":{"562":1,"564":1,"593":1,"604":1,"624":1,"626":1,"756":1,"1455":1,"1492":1,"1596":1},"3":{"0":4,"16":1,"17":1,"18":1,"20":1,"24":3,"71":1,"73":1,"74":1,"78":2,"79":1,"95":1,"136":1,"145":2,"147":3,"150":1,"160":1,"193":1,"201":1,"217":1,"219":1,"245":1,"246":2,"291":1,"305":1,"311":1,"312":1,"351":1,"353":1,"360":1,"390":2,"392":1,"413":1,"422":1,"434":1,"441":1,"499":1,"536":1,"539":1,"549":1,"562":1,"563":1,"564":4,"568":1,"572":1,"576":1,"593":1,"599":1,"600":1,"602":1,"604":2,"624":1,"626":3,"629":1,"698":1,"699":1,"717":1,"723":1,"726":1,"733":1,"740":1,"741":1,"750":1,"756":1,"767":1,"791":1,"793":1,"797":1,"799":1,"819":1,"825":6,"827":2,"830":1,"833":1,"838":1,"840":1,"844":1,"854":1,"903":1,"905":1,"907":1,"920":1,"921":1,"930":1,"954":2,"982":1,"1006":1,"1012":2,"1016":1,"1018":2,"1020":1,"1021":1,"1030":1,"1034":2,"1048":1,"1077":1,"1095":1,"1116":1,"1145":1,"1184":1,"1212":4,"1213":1,"1231":2,"1233":1,"1237":7,"1247":2,"1249":1,"1250":1,"1257":1,"1259":9,"1270":2,"1273":2,"1274":1,"1277":1,"1278":1,"1281":1,"1286":23,"1300":7,"1301":2,"1309":9,"1310":1,"1311":11,"1312":1,"1318":1,"1322":1,"1323":7,"1324":23,"1339":4,"1345":1,"1350":8,"1359":15,"1369":1,"1373":1,"1379":1,"1380":4,"1385":1,"1389":1,"1395":15,"1396":17,"1397":1,"1398":1,"1402":6,"1405":1,"1413":1,"1414":1,"1415":14,"1416":3,"1417":6,"1427":5,"1432":1,"1436":24,"1438":5,"1440":1,"1442":2,"1443":2,"1445":1,"1446":1,"1447":2,"1453":2,"1455":11,"1458":2,"1461":1,"1467":7,"1476":1,"1477":1,"1478":1,"1483":1,"1486":1,"1488":2,"1492":1,"1496":6,"1526":2,"1534":1,"1541":1,"1546":2,"1547":1,"1548":1,"1553":1,"1556":1,"1561":1,"1569":1,"1572":1,"1586":1,"1591":2,"1596":2,"1605":1,"1607":1,"1608":1,"1614":1,"1631":6,"1632":2,"1634":2,"1635":1,"1638":1,"1639":2,"1640":1,"1651":1,"1663":1,"1669":1,"1673":1,"1675":1,"1685":6,"1686":7,"1701":1,"1704":1,"1727":2,"1733":2,"1734":1,"1735":1,"1739":1,"1741":1,"1747":3,"1748":1,"1749":3,"1765":5,"1769":2,"1776":1,"1842":1,"1851":2,"1853":1,"1863":2,"1866":1,"1874":1,"1884":1,"1889":3,"1892":1,"1896":1,"1899":1,"1913":1,"1916":1,"1920":1,"1961":2,"1965":1,"1974":1,"1980":2,"2013":2,"2017":1,"2045":1,"2057":1,"2063":1,"2073":1,"2078":1,"2085":1,"2087":1,"2090":1,"2091":1,"2112":1,"2118":1,"2126":1,"2127":1,"2144":1,"2150":1,"2160":1,"2187":2,"2198":1,"2220":2,"2230":2,"2232":2}}],["change",{"0":{"1696":1,"1839":1,"2112":1},"1":{"712":1,"713":1,"714":1,"715":1,"716":1,"717":1,"718":1,"719":1,"720":1,"721":1,"1732":1,"1733":1,"1734":1,"1735":1,"1736":1,"1737":1,"1738":1},"2":{"1467":1},"3":{"0":28,"21":2,"26":1,"27":4,"30":1,"32":1,"41":1,"46":1,"54":1,"58":1,"59":2,"60":4,"63":1,"70":1,"71":1,"78":1,"79":1,"81":2,"94":1,"99":1,"111":1,"118":1,"119":1,"121":1,"131":1,"133":1,"134":1,"135":6,"136":3,"141":1,"142":1,"143":1,"147":3,"149":2,"150":1,"151":3,"158":1,"160":1,"164":1,"166":1,"167":1,"170":1,"186":2,"187":2,"200":1,"224":1,"241":1,"243":2,"253":1,"254":1,"255":2,"265":3,"273":1,"279":1,"285":1,"301":2,"303":2,"310":1,"311":1,"312":1,"314":1,"330":1,"332":2,"333":1,"340":1,"348":1,"353":2,"359":1,"364":1,"365":5,"370":1,"390":2,"391":3,"393":1,"397":2,"414":2,"422":1,"425":1,"429":1,"433":1,"447":1,"449":1,"451":1,"454":1,"465":1,"472":1,"474":1,"486":1,"490":1,"497":3,"499":1,"509":1,"526":1,"528":2,"547":1,"549":1,"550":1,"551":1,"563":1,"564":2,"566":1,"568":1,"583":1,"584":2,"591":2,"592":1,"609":1,"610":3,"618":1,"619":1,"620":2,"624":1,"625":1,"628":1,"629":1,"640":2,"641":1,"644":1,"650":1,"651":1,"657":1,"659":1,"665":1,"668":1,"691":1,"692":1,"703":1,"706":1,"712":1,"714":2,"715":4,"716":2,"719":1,"726":1,"727":1,"731":1,"733":1,"740":1,"742":1,"747":1,"749":1,"750":1,"751":2,"760":1,"765":1,"766":1,"767":1,"776":2,"784":1,"793":1,"799":3,"800":2,"803":2,"804":1,"818":1,"819":1,"825":1,"829":3,"832":1,"833":1,"836":1,"840":1,"845":1,"853":1,"854":2,"856":1,"857":1,"860":2,"861":1,"862":1,"863":1,"869":1,"870":1,"881":1,"883":3,"891":1,"903":1,"905":2,"907":1,"908":1,"916":1,"921":1,"924":1,"926":2,"931":2,"932":1,"939":1,"942":1,"946":1,"947":1,"948":1,"955":1,"958":1,"964":2,"965":1,"966":1,"972":1,"980":1,"981":2,"982":1,"986":1,"987":1,"988":4,"989":1,"990":1,"991":1,"1003":1,"1004":2,"1008":1,"1011":1,"1012":1,"1014":1,"1016":1,"1017":1,"1018":8,"1019":4,"1020":2,"1027":1,"1034":2,"1035":1,"1041":1,"1042":3,"1043":3,"1044":1,"1049":1,"1050":2,"1051":2,"1052":1,"1058":1,"1059":4,"1060":1,"1061":2,"1077":1,"1082":1,"1084":2,"1085":2,"1086":1,"1091":2,"1094":2,"1096":1,"1119":1,"1126":2,"1129":1,"1142":2,"1147":1,"1169":1,"1175":1,"1188":1,"1196":1,"1202":1,"1203":1,"1212":7,"1213":1,"1214":1,"1229":6,"1230":2,"1232":1,"1233":1,"1236":1,"1237":24,"1241":1,"1246":1,"1247":12,"1249":2,"1251":2,"1252":1,"1255":1,"1259":15,"1260":1,"1261":1,"1264":1,"1267":1,"1269":2,"1270":17,"1271":5,"1272":2,"1273":2,"1274":1,"1275":1,"1277":3,"1278":3,"1281":1,"1282":2,"1283":1,"1286":77,"1289":2,"1293":1,"1294":2,"1296":1,"1300":35,"1301":4,"1304":1,"1306":2,"1309":15,"1310":1,"1311":29,"1312":2,"1318":2,"1321":1,"1322":1,"1323":43,"1324":45,"1326":1,"1333":1,"1338":1,"1339":21,"1340":2,"1342":1,"1343":1,"1345":3,"1347":1,"1348":1,"1350":41,"1352":2,"1355":1,"1358":2,"1359":93,"1361":1,"1362":1,"1366":1,"1369":10,"1370":1,"1371":1,"1378":1,"1380":9,"1389":1,"1394":1,"1395":38,"1396":40,"1402":25,"1403":3,"1405":2,"1406":2,"1407":2,"1408":1,"1410":1,"1414":3,"1415":57,"1416":3,"1417":20,"1421":1,"1426":1,"1427":6,"1432":1,"1435":5,"1436":71,"1438":11,"1441":2,"1442":1,"1445":2,"1453":5,"1455":19,"1459":1,"1461":2,"1463":1,"1465":1,"1467":17,"1474":5,"1475":1,"1476":5,"1477":1,"1478":3,"1481":1,"1488":4,"1492":8,"1496":45,"1521":1,"1524":2,"1526":1,"1531":4,"1534":1,"1535":1,"1538":1,"1542":1,"1545":1,"1546":2,"1547":1,"1553":5,"1569":1,"1570":2,"1571":3,"1577":6,"1585":2,"1591":1,"1592":1,"1596":1,"1599":3,"1602":1,"1608":2,"1616":1,"1626":1,"1630":2,"1631":1,"1632":3,"1633":1,"1634":2,"1638":2,"1639":4,"1641":10,"1643":1,"1644":1,"1646":1,"1651":2,"1653":1,"1662":1,"1664":2,"1667":2,"1668":1,"1673":1,"1674":1,"1675":1,"1677":1,"1692":1,"1696":1,"1697":1,"1701":1,"1703":1,"1708":1,"1710":1,"1719":4,"1724":3,"1727":1,"1732":1,"1734":1,"1735":3,"1736":3,"1737":2,"1742":2,"1746":10,"1747":6,"1748":6,"1749":1,"1750":1,"1760":1,"1765":2,"1767":2,"1768":6,"1775":1,"1776":3,"1788":1,"1790":4,"1793":1,"1796":1,"1805":1,"1807":1,"1809":1,"1810":2,"1812":1,"1821":1,"1826":1,"1839":4,"1840":1,"1844":2,"1846":1,"1847":1,"1849":3,"1851":1,"1852":1,"1853":2,"1854":2,"1855":1,"1857":6,"1861":1,"1869":2,"1889":3,"1890":1,"1892":3,"1896":1,"1899":1,"1902":1,"1913":1,"1916":1,"1918":1,"1923":1,"1926":1,"1929":2,"1930":1,"1932":2,"1936":1,"1938":1,"1939":1,"1941":1,"1965":2,"1982":1,"1989":1,"1990":2,"1991":1,"1995":2,"2008":2,"2010":2,"2013":1,"2015":1,"2017":1,"2020":2,"2028":1,"2034":1,"2044":3,"2045":1,"2047":1,"2051":1,"2063":1,"2065":2,"2068":1,"2078":1,"2079":1,"2083":1,"2087":2,"2089":2,"2098":1,"2100":1,"2106":1,"2107":3,"2111":1,"2112":2,"2115":1,"2117":1,"2123":1,"2130":1,"2131":1,"2136":2,"2142":1,"2143":2,"2146":1,"2150":1,"2154":1,"2156":1,"2160":3,"2175":1,"2183":2,"2188":1,"2192":4,"2193":1,"2194":1,"2195":1,"2199":2,"2201":1,"2202":1,"2203":1,"2230":1,"2232":2}}],["changing",{"0":{"1889":1},"3":{"0":4,"40":1,"136":1,"160":1,"194":1,"208":1,"311":1,"365":1,"448":1,"476":1,"478":1,"497":1,"547":1,"692":1,"701":1,"732":1,"735":1,"741":1,"768":1,"819":1,"829":1,"905":1,"907":2,"948":1,"980":1,"1014":1,"1018":1,"1020":1,"1059":1,"1071":1,"1092":1,"1095":1,"1173":1,"1182":1,"1229":3,"1237":1,"1286":3,"1290":1,"1300":6,"1309":2,"1311":6,"1312":1,"1323":4,"1324":5,"1339":1,"1345":1,"1350":1,"1359":6,"1395":5,"1396":2,"1402":2,"1415":3,"1417":1,"1420":1,"1427":1,"1436":5,"1474":1,"1631":1,"1639":4,"1683":1,"1708":1,"1734":3,"1767":1,"1802":1,"1805":1,"1854":1,"1856":1,"1875":2,"1889":1,"1902":1,"1923":1,"1961":1,"1965":1,"1972":1,"2026":1,"2027":1,"2032":1,"2044":1,"2065":1,"2194":1,"2199":1,"2202":1}}],["chore",{"3":{"832":1,"1237":1,"1436":1,"1496":12,"1951":1}}],["choreography",{"3":{"537":1,"541":1,"809":4,"811":1,"815":1,"2168":1}}],["choreographed",{"1":{"533":1,"534":1,"535":1,"536":1,"537":1,"538":1,"539":1,"540":1,"541":1},"3":{"533":1,"536":1,"815":1,"1147":1,"1577":1,"2163":1,"2169":1}}],["chokepoint",{"3":{"1286":1,"1359":2,"2074":1}}],["choke",{"3":{"826":1,"829":1}}],["choosable",{"3":{"422":1}}],["choosing",{"3":{"70":1,"248":1,"295":1,"427":1,"520":1,"855":1,"896":1,"926":1,"964":1,"998":1,"1051":1,"1074":1,"1102":1,"1259":1,"1270":3,"1286":3,"1300":1,"1301":3,"1311":2,"1321":1,"1323":2,"1324":3,"1359":4,"1369":1,"1380":2,"1395":2,"1396":2,"1415":1,"1436":2,"1852":1,"1985":1,"2096":1,"2192":1}}],["chooses",{"3":{"234":1,"305":1,"1184":1,"1247":1,"1270":1,"1300":2,"1309":1,"1311":1,"1321":1,"1323":1,"1324":1,"1395":3,"1396":4,"1402":1,"1415":1,"1417":1,"1427":1,"1436":3,"1903":1,"2063":1}}],["choose",{"3":{"0":2,"157":1,"226":1,"481":1,"557":1,"640":1,"966":1,"996":1,"999":1,"1160":1,"1185":1,"1232":1,"1237":1,"1240":1,"1247":1,"1278":1,"1323":1,"1324":3,"1359":5,"1380":1,"1383":1,"1396":3,"1415":1,"1417":1,"1457":1,"1648":1,"1899":1,"1924":1,"1941":1,"1952":1,"2002":1,"2123":1,"2146":1,"2160":1,"2168":1,"2181":1}}],["chose",{"3":{"1":1,"91":1,"101":1,"305":1,"329":1,"469":1,"727":1,"743":1,"827":1,"853":1,"933":1,"963":1,"1004":1,"1049":1,"1082":1,"1085":2,"1093":1,"1109":1,"1213":1,"1286":2,"1311":3,"1323":4,"1324":3,"1338":1,"1339":3,"1347":1,"1359":2,"1380":1,"1395":2,"1396":5,"1415":1,"1417":2,"1423":1,"1427":1,"1436":2,"1898":1,"2025":1,"2043":2,"2064":2,"2175":1}}],["chosen",{"0":{"1361":1,"1935":1,"2117":1},"3":{"0":4,"1":1,"27":1,"102":1,"243":1,"255":1,"265":1,"353":1,"447":1,"450":1,"458":1,"461":1,"501":1,"546":1,"556":1,"584":1,"591":1,"657":1,"684":1,"732":1,"811":1,"821":1,"862":1,"889":1,"1013":1,"1084":1,"1092":1,"1144":1,"1212":2,"1229":1,"1235":1,"1244":1,"1247":1,"1269":2,"1270":2,"1286":10,"1300":6,"1301":2,"1302":1,"1309":1,"1311":7,"1323":3,"1324":11,"1326":1,"1338":1,"1339":5,"1350":2,"1359":11,"1369":3,"1373":1,"1380":1,"1395":14,"1396":6,"1402":5,"1415":4,"1417":1,"1422":1,"1429":1,"1435":1,"1436":5,"1438":1,"1441":1,"1455":1,"1460":1,"1474":2,"1475":1,"1492":1,"1496":1,"1513":1,"1545":1,"1556":1,"1567":1,"1577":2,"1630":1,"1653":1,"1666":1,"1715":1,"1842":1,"1889":1,"1915":1,"1944":1,"1984":1,"2024":1,"2064":1,"2078":1,"2083":2,"2115":1,"2116":1,"2130":1,"2131":1,"2132":1,"2160":1,"2177":1,"2232":3}}],["choices",{"3":{"0":2,"97":1,"449":1,"633":1,"880":2,"905":1,"953":1,"1212":1,"1229":1,"1257":1,"1259":1,"1266":1,"1270":1,"1271":1,"1286":4,"1300":4,"1309":3,"1310":1,"1311":2,"1312":1,"1323":1,"1324":4,"1337":1,"1339":1,"1350":3,"1369":3,"1373":1,"1375":1,"1380":1,"1395":3,"1396":2,"1415":1,"1416":1,"1417":1,"1436":7,"1467":1,"1492":1,"1630":1,"1675":1,"1783":1,"1789":1,"1829":1,"1967":1,"1984":1,"2016":1,"2028":1,"2043":1,"2052":1,"2075":1,"2127":1,"2130":1,"2131":1}}],["choice",{"3":{"0":7,"20":1,"31":1,"32":1,"54":1,"62":1,"68":1,"87":1,"97":1,"98":1,"101":1,"157":1,"165":2,"168":1,"170":1,"175":1,"178":1,"201":1,"236":1,"265":2,"273":1,"274":1,"291":1,"309":1,"469":1,"508":1,"551":1,"555":1,"558":1,"560":1,"633":1,"640":2,"642":1,"647":1,"666":1,"676":1,"686":1,"690":1,"792":1,"808":1,"810":1,"811":1,"833":1,"853":1,"874":1,"876":1,"913":1,"914":1,"947":1,"953":1,"954":1,"955":1,"963":1,"968":1,"980":1,"981":1,"1032":1,"1034":3,"1037":1,"1049":1,"1052":1,"1073":1,"1077":1,"1082":1,"1125":1,"1162":1,"1185":1,"1212":4,"1229":3,"1231":1,"1237":3,"1247":4,"1248":1,"1249":1,"1259":6,"1270":6,"1271":1,"1283":1,"1286":22,"1288":1,"1300":12,"1301":1,"1304":1,"1309":1,"1310":1,"1311":8,"1312":2,"1314":1,"1315":1,"1316":1,"1323":14,"1324":26,"1327":2,"1339":17,"1350":8,"1353":1,"1355":1,"1359":24,"1362":1,"1369":3,"1379":1,"1380":4,"1391":1,"1395":18,"1396":23,"1397":1,"1402":4,"1415":10,"1416":2,"1417":15,"1423":1,"1427":2,"1436":27,"1439":1,"1441":5,"1445":1,"1453":1,"1455":1,"1459":1,"1460":1,"1465":1,"1467":2,"1471":1,"1472":1,"1475":2,"1496":1,"1534":1,"1547":2,"1577":2,"1596":1,"1597":1,"1599":4,"1632":1,"1640":1,"1665":1,"1669":2,"1678":1,"1720":1,"1724":1,"1747":1,"1791":1,"1805":1,"1810":1,"1826":1,"1849":1,"1854":2,"1856":1,"1861":2,"1862":2,"1866":1,"1884":1,"1909":1,"1923":1,"1935":1,"1941":1,"1994":1,"1998":1,"2025":1,"2027":1,"2028":1,"2047":1,"2067":1,"2081":3,"2083":1,"2084":2,"2086":1,"2117":1,"2150":1,"2165":1,"2182":1,"2230":1,"2232":1}}],["cipher",{"3":{"2146":2}}],["ciphertext",{"2":{"365":1,"2146":1},"3":{"358":1,"359":8,"360":2,"361":4,"363":2,"365":2,"1284":2,"1286":11,"1324":2,"1675":1,"2139":1,"2140":2,"2142":7,"2143":2,"2144":2,"2145":1,"2146":5}}],["cil",{"3":{"1436":1}}],["circle",{"3":{"1395":1,"1417":2}}],["circularity",{"3":{"1436":2}}],["circular",{"3":{"0":1,"583":1,"768":1,"1286":1,"1311":1,"1415":1,"1436":2,"1441":1,"1540":1,"1577":1,"1641":1,"1881":1,"1885":1,"2009":1}}],["circuitbreakersamplingduration",{"3":{"1339":3}}],["circuitbreaker",{"3":{"819":1,"1359":1}}],["circuitopencounter",{"2":{"1256":1},"3":{"1256":1,"1259":2,"1323":2}}],["circuitopen",{"3":{"819":1}}],["circuitoptions",{"2":{"831":1,"1123":1,"1125":1},"3":{"0":1,"831":1,"832":1,"1123":2,"1125":1,"1324":6,"1416":2,"2000":1}}],["circuiting",{"3":{"284":1,"946":1,"1229":1,"1231":1,"1237":1,"1247":1,"1268":1,"1270":3,"1286":1,"1300":2,"1311":1,"1320":1,"1323":1,"1324":1,"1350":2,"1359":2,"1380":2,"1387":1,"1395":3,"1396":1,"1402":2,"1415":2,"1436":1,"1577":1,"1666":1,"1707":1}}],["circuited",{"3":{"71":1,"819":1,"820":1,"1270":1,"1311":1,"1339":1,"1402":1}}],["circuithandler",{"2":{"1124":1,"1125":1,"2000":1},"3":{"0":1,"1124":2,"1125":1,"1324":8,"1416":1,"2000":2}}],["circuits",{"3":{"0":1,"70":1,"109":1,"117":1,"121":1,"126":1,"175":1,"179":1,"300":1,"318":1,"459":1,"556":1,"640":1,"690":1,"790":2,"820":1,"821":1,"830":1,"832":1,"948":1,"964":1,"1123":3,"1124":2,"1125":1,"1126":1,"1128":3,"1212":1,"1224":1,"1229":2,"1237":3,"1239":1,"1245":1,"1247":2,"1259":2,"1268":1,"1270":8,"1271":1,"1286":5,"1300":7,"1309":7,"1311":9,"1323":2,"1324":21,"1339":3,"1346":1,"1347":1,"1350":3,"1359":27,"1380":1,"1389":1,"1391":1,"1395":9,"1396":9,"1402":7,"1410":1,"1415":6,"1416":4,"1417":4,"1427":1,"1436":2,"1441":1,"1455":1,"1461":1,"1467":1,"1534":1,"1577":1,"1599":1,"1612":1,"1805":1,"1821":1,"1831":1,"1933":1,"1962":1,"1994":1,"2000":2,"2001":1,"2008":1,"2032":1,"2096":2,"2137":1,"2203":1}}],["circuit",{"0":{"100":1,"1128":2},"2":{"819":1,"2032":1,"2156":1},"3":{"0":11,"27":2,"66":1,"67":2,"70":1,"71":1,"72":2,"73":1,"100":1,"109":1,"110":1,"113":1,"122":1,"124":1,"125":1,"175":1,"180":1,"189":1,"223":1,"241":1,"243":1,"258":1,"264":1,"265":1,"272":1,"359":1,"407":2,"413":1,"486":1,"506":3,"507":4,"549":1,"555":2,"557":2,"558":1,"575":1,"607":1,"609":3,"817":1,"819":6,"821":1,"822":1,"825":1,"828":1,"830":1,"831":3,"832":4,"833":1,"879":1,"881":3,"884":1,"920":1,"1020":1,"1074":1,"1079":1,"1122":3,"1123":2,"1124":15,"1125":4,"1126":1,"1128":5,"1129":1,"1168":1,"1184":3,"1192":2,"1212":2,"1213":1,"1220":2,"1229":2,"1237":1,"1256":2,"1259":7,"1263":2,"1265":1,"1268":3,"1270":17,"1271":1,"1286":7,"1300":1,"1301":1,"1304":1,"1307":1,"1309":2,"1310":1,"1311":5,"1312":5,"1317":1,"1323":5,"1324":131,"1330":2,"1339":18,"1350":2,"1359":17,"1365":1,"1380":2,"1395":12,"1396":17,"1401":1,"1402":7,"1415":4,"1416":9,"1417":32,"1433":1,"1436":20,"1437":2,"1438":7,"1441":3,"1443":2,"1447":1,"1453":1,"1455":1,"1456":2,"1465":1,"1467":10,"1489":1,"1490":1,"1496":1,"1525":1,"1526":5,"1534":1,"1544":1,"1560":2,"1566":2,"1577":6,"1585":1,"1599":1,"1653":1,"1665":1,"1668":1,"1698":1,"1701":1,"1707":1,"1708":1,"1805":1,"1874":1,"1921":1,"1933":1,"1946":1,"2000":7,"2002":2,"2006":3,"2010":1,"2019":1,"2029":1,"2030":4,"2032":2,"2037":1,"2038":2,"2070":1,"2074":2,"2076":2,"2081":2,"2082":1,"2118":2,"2136":1,"2142":1,"2156":4,"2158":1,"2160":1,"2232":1}}],["cicd",{"3":{"1192":1,"1212":16,"1462":1,"1494":1,"1496":9,"1499":3}}],["citizen",{"3":{"1300":1}}],["citing",{"3":{"0":1,"137":2,"428":1,"990":1,"1259":1,"1300":1,"1324":2,"1359":1}}],["citymaxlength",{"3":{"1237":1,"1271":1}}],["city",{"3":{"1237":1,"1271":2,"1286":1,"1324":4,"1436":2,"1764":1,"1829":1}}],["cityrules",{"3":{"1199":3,"1203":1,"1207":1,"1271":7}}],["citable",{"3":{"861":2,"1433":1,"1436":3,"1526":1,"1534":2}}],["citations",{"0":{"138":1,"139":1,"140":1,"142":1},"3":{"0":1,"27":1,"34":1,"43":1,"74":1,"104":1,"112":1,"115":2,"128":1,"130":2,"137":1,"138":2,"139":2,"142":2,"145":2,"152":1,"171":1,"184":1,"190":1,"203":1,"231":1,"238":1,"249":1,"268":1,"296":1,"322":1,"328":2,"337":2,"345":1,"354":1,"365":1,"366":1,"375":1,"393":1,"395":8,"409":3,"429":1,"446":2,"453":1,"468":3,"476":1,"478":1,"490":1,"493":1,"495":1,"497":1,"514":1,"534":3,"540":1,"551":1,"554":3,"559":1,"578":1,"586":1,"589":1,"594":1,"597":2,"621":1,"624":1,"636":1,"638":3,"644":1,"647":1,"652":1,"669":1,"672":4,"680":2,"685":1,"688":1,"696":2,"710":1,"713":1,"723":1,"728":1,"744":1,"752":1,"764":3,"769":1,"777":1,"780":2,"794":1,"797":1,"803":2,"822":1,"825":3,"841":1,"844":1,"849":1,"850":1,"864":1,"867":1,"875":1,"892":1,"900":1,"927":1,"929":1,"934":2,"949":1,"975":1,"983":1,"1003":1,"1006":1,"1007":1,"1029":1,"1037":1,"1045":1,"1055":1,"1062":1,"1078":1,"1086":1,"1095":1,"1103":1,"1111":1,"1128":1,"1136":1,"1146":1,"1156":1,"1396":2,"1452":1,"1496":17,"1500":1,"1536":1,"1796":1}}],["citation",{"0":{"137":1},"3":{"0":2,"82":2,"137":1,"138":1,"139":1,"141":1,"142":1,"145":2,"252":1,"256":1,"258":2,"336":1,"364":1,"378":1,"395":3,"409":1,"425":1,"426":1,"428":2,"429":1,"474":1,"478":1,"492":1,"534":4,"540":1,"543":1,"552":1,"559":1,"567":1,"597":1,"604":1,"607":5,"613":1,"624":2,"629":1,"677":1,"693":1,"755":1,"761":1,"780":1,"806":1,"825":1,"833":1,"867":1,"877":1,"892":1,"909":1,"912":1,"917":1,"958":1,"960":1,"1006":1,"1013":1,"1021":1,"1119":1,"1381":2,"1396":1,"1462":1,"1496":25,"1497":1,"1499":1,"1500":1,"2232":1}}],["cites",{"3":{"252":1,"392":1,"492":1,"534":1,"549":1,"594":1,"782":1,"865":1,"893":1,"968":1,"986":1,"987":1,"1006":1,"1062":1,"1070":1,"1193":1,"1237":2,"1270":1,"1300":2,"1312":1,"1324":1,"1339":2,"1359":6,"1369":1,"1396":5,"1415":1,"1436":6,"1455":1,"1471":1,"1496":14,"1518":1,"2000":1}}],["cited",{"3":{"29":1,"139":1,"140":2,"250":1,"258":1,"259":2,"261":1,"276":2,"423":1,"430":1,"463":1,"490":1,"493":1,"547":1,"551":1,"584":1,"586":1,"587":1,"607":1,"720":1,"780":1,"825":1,"867":1,"875":1,"877":1,"916":1,"1006":1,"1011":1,"1065":1,"1074":1,"1190":1,"1193":1,"1236":1,"1237":1,"1270":2,"1286":1,"1300":3,"1301":1,"1311":3,"1323":1,"1324":1,"1339":1,"1350":2,"1359":1,"1380":1,"1395":2,"1396":2,"1417":5,"1436":1,"1451":2,"1462":1,"1471":1,"1485":1,"1496":9,"1497":1,"1580":1,"1597":1,"1673":1,"2050":1}}],["cite",{"3":{"0":1,"95":1,"121":1,"136":1,"139":1,"286":1,"395":1,"407":1,"493":1,"556":1,"660":1,"748":1,"869":1,"1289":1,"1300":1,"1339":1,"1436":1,"1451":1,"1496":9,"1610":1,"1744":1}}],["ci",{"0":{"1364":1,"1453":1,"1616":1},"1":{"561":1,"562":1,"563":1,"564":1,"565":1,"566":1,"567":1,"568":1,"588":1,"589":1,"590":1,"591":1,"592":1,"593":1,"594":1,"595":1,"615":1,"616":1,"617":1,"618":1,"619":1,"620":1,"621":1,"622":1,"1452":1,"1453":1,"1454":1,"1455":1,"1456":1,"1457":1,"1458":1,"1459":1,"1460":1,"1461":1},"2":{"374":1,"514":1,"589":1,"594":1,"604":1,"624":1,"1215":2,"1459":1,"1531":1,"1616":1,"1617":1,"1619":1},"3":{"0":17,"130":3,"132":3,"135":4,"136":1,"137":3,"138":1,"140":6,"142":4,"148":1,"218":3,"263":1,"265":1,"291":1,"368":1,"369":2,"370":7,"371":2,"373":10,"374":1,"375":1,"401":1,"412":1,"413":1,"414":1,"446":1,"450":2,"451":2,"483":1,"486":1,"509":4,"511":1,"514":1,"526":1,"527":1,"530":2,"531":2,"555":1,"556":1,"557":1,"558":1,"561":1,"562":1,"564":3,"572":1,"573":1,"574":1,"575":2,"576":1,"577":1,"588":1,"589":2,"590":2,"591":8,"592":1,"593":5,"594":1,"599":3,"601":1,"604":3,"609":4,"615":1,"618":1,"619":1,"621":1,"624":3,"625":1,"626":7,"630":1,"631":2,"633":5,"634":2,"635":3,"675":1,"700":1,"766":2,"767":1,"768":1,"862":1,"863":3,"864":1,"865":1,"868":1,"869":2,"871":2,"873":1,"876":1,"893":1,"906":1,"913":1,"914":3,"917":1,"941":1,"954":3,"960":1,"982":2,"983":1,"1012":2,"1017":2,"1018":1,"1020":1,"1034":8,"1037":2,"1066":2,"1067":3,"1069":1,"1070":1,"1074":1,"1091":1,"1192":1,"1212":2,"1214":1,"1215":6,"1216":1,"1237":1,"1273":1,"1286":2,"1311":2,"1324":1,"1326":3,"1339":8,"1359":2,"1364":2,"1369":4,"1380":3,"1415":2,"1426":1,"1427":4,"1428":2,"1429":2,"1431":2,"1433":4,"1434":4,"1435":1,"1436":82,"1438":17,"1440":1,"1441":4,"1442":1,"1444":1,"1445":2,"1450":1,"1452":2,"1453":56,"1454":8,"1455":12,"1456":5,"1457":1,"1459":6,"1460":2,"1461":6,"1462":2,"1463":2,"1464":1,"1465":1,"1467":3,"1468":1,"1471":1,"1474":1,"1475":1,"1476":1,"1481":2,"1482":1,"1485":2,"1486":42,"1487":8,"1488":3,"1489":10,"1490":15,"1491":1,"1492":44,"1493":2,"1494":3,"1496":6,"1503":1,"1521":1,"1523":1,"1524":5,"1525":4,"1526":60,"1530":1,"1531":10,"1532":2,"1534":8,"1535":7,"1537":2,"1545":1,"1548":1,"1551":2,"1552":1,"1553":1,"1554":2,"1558":2,"1565":1,"1569":1,"1570":2,"1572":1,"1574":1,"1576":4,"1577":65,"1582":7,"1583":2,"1584":1,"1585":3,"1586":9,"1589":1,"1590":1,"1591":40,"1592":3,"1595":1,"1596":6,"1597":1,"1599":1,"1600":2,"1605":2,"1611":6,"1614":1,"1616":1,"1617":3,"1619":2,"1642":1,"1643":1,"1654":1,"1659":1,"1661":1,"1664":1,"1669":1,"1670":1,"1671":1,"1673":1,"1674":1,"1675":1,"1696":1,"1706":1,"1707":2,"1716":4,"1731":2,"1738":1,"1739":2,"1757":2,"1761":1,"1781":1,"1784":1,"1785":1,"1793":1,"1797":1,"1800":2,"1923":2,"1929":1,"1930":1,"2003":1,"2047":2,"2051":1,"2058":1,"2059":3,"2060":1,"2079":1,"2097":1,"2101":1,"2105":3,"2108":1,"2109":1,"2120":2,"2122":1,"2123":2,"2132":2,"2133":1,"2171":1,"2176":1,"2179":1,"2213":1,"2218":1,"2219":1,"2220":2,"2232":2,"2239":2}}],["crtp",{"3":{"1436":2}}],["cry",{"3":{"1300":1}}],["cryptoprovidercache",{"3":{"1300":1,"1323":1}}],["crypto",{"0":{"2144":1},"3":{"311":1,"360":1,"602":1,"665":1,"667":1,"1213":1,"1300":2,"1336":1,"1339":3,"1436":2,"1442":1,"1467":3,"1496":1,"1905":1,"1970":1,"2142":1,"2146":1}}],["cryptographic",{"3":{"372":1,"1259":1,"1300":4,"1323":1,"1339":1,"1415":1,"1436":1,"1438":1,"1630":2,"1641":3}}],["cryptographicexception",{"2":{"359":1,"361":1,"2142":1},"3":{"359":4,"361":1,"1286":2,"1324":1,"2142":3}}],["cryptographically",{"3":{"209":2,"359":1,"1300":1,"1324":1,"1641":1,"1902":1,"1905":1,"1971":2,"2142":1}}],["cryptographicoperationstype",{"3":{"1436":1}}],["cryptographicoperations",{"2":{"179":1,"310":1,"827":1,"854":1,"946":2,"1292":1,"1293":1,"1294":1,"1337":1,"1902":1,"1905":1},"3":{"0":1,"179":1,"310":1,"827":1,"854":1,"946":2,"1292":1,"1293":1,"1294":1,"1300":8,"1337":1,"1339":3,"1436":3,"1902":1,"1905":1}}],["cryptography",{"2":{"665":1},"3":{"0":1,"664":1,"665":1,"1059":1,"1286":1,"1293":1,"1300":5,"1311":1,"1324":1,"1339":3,"1395":1,"1427":1,"1436":1,"1442":1,"1467":1,"1905":1,"2194":1,"2198":1}}],["cruiser",{"3":{"2049":1}}],["crumb",{"3":{"1324":2,"1395":5}}],["crucial",{"3":{"1310":1}}],["crucially",{"3":{"1223":1,"1251":1,"1259":1,"1270":1,"1281":1,"1300":1,"1323":1,"1339":2,"1359":1,"1402":1,"1415":1,"1417":1,"1865":1}}],["crux",{"3":{"1286":1,"1314":1,"1882":1,"1968":1}}],["crutch",{"3":{"1229":1}}],["crud",{"0":{"1388":1,"1389":1},"1":{"779":1,"780":1,"781":1,"782":1,"783":1,"784":1,"785":1,"786":1},"2":{"115":1,"1262":1,"1270":1},"3":{"0":1,"115":1,"122":2,"328":1,"341":1,"344":1,"444":1,"490":1,"523":1,"779":1,"782":2,"783":1,"784":1,"785":1,"881":4,"883":1,"921":1,"922":5,"924":1,"926":4,"927":1,"963":1,"980":1,"1100":1,"1203":14,"1207":87,"1208":1,"1212":1,"1233":1,"1249":1,"1259":3,"1262":2,"1263":1,"1265":8,"1270":45,"1271":3,"1286":1,"1300":1,"1311":5,"1323":5,"1324":14,"1350":10,"1352":1,"1354":1,"1359":80,"1371":3,"1373":1,"1380":14,"1382":2,"1386":1,"1389":1,"1392":1,"1395":68,"1396":3,"1402":4,"1415":6,"1436":1,"1438":2,"1526":1,"1540":1,"1577":5,"1591":1,"1611":2,"1612":1,"1616":1,"1617":1,"1621":1,"1625":1,"1626":1,"1627":1,"1629":1,"1631":1,"1632":12,"1638":2,"1639":3,"1651":1,"1666":1,"1742":1,"1747":1,"1750":1,"1757":1,"1760":1,"1824":1,"1874":1,"1985":1,"1991":3,"2074":1,"2232":1}}],["crudentitycontrollerbasetests",{"3":{"1199":1,"1207":5,"1311":2}}],["crudentitycontrollerbase",{"2":{"328":1,"925":1,"1666":1,"1874":1},"3":{"0":2,"328":1,"341":1,"922":2,"925":1,"1199":2,"1203":1,"1207":2,"1212":2,"1270":6,"1300":1,"1311":14,"1436":2,"1496":1,"1666":1,"1874":1}}],["crlf",{"3":{"741":1,"1300":4,"1311":2,"1436":1,"1438":1,"1479":1,"1496":1}}],["crisp",{"3":{"1816":1}}],["crios",{"3":{"1324":1}}],["cries",{"3":{"590":1,"1436":1}}],["criterion",{"3":{"265":1,"371":1,"1011":1,"1012":1,"1018":1,"1019":1,"1020":1,"1093":1,"1247":2,"1359":2,"1366":1,"1369":1,"1436":1,"1491":1,"1496":1,"1524":1,"1526":15,"1527":1,"1531":3,"1532":2,"1537":1,"1576":1,"1577":12,"1578":1,"1582":2,"1585":1,"1591":2,"1797":1,"2142":1}}],["criteria",{"1":{"1536":1,"1537":1,"1538":1,"1539":1,"1540":1,"1541":1,"1542":1,"1543":1,"1544":1,"1545":1,"1546":1,"1547":1,"1548":1,"1549":1,"1550":1,"1551":1,"1552":1,"1553":1,"1554":1,"1555":1,"1556":1,"1557":1,"1558":1,"1559":1,"1560":1,"1561":1,"1562":1,"1563":1,"1564":1,"1565":1,"1566":1,"1567":1,"1568":1,"1569":1,"1570":1,"1571":1,"1572":1},"2":{"319":1,"546":1,"547":1,"550":1},"3":{"0":4,"168":1,"318":1,"319":1,"376":1,"544":1,"545":4,"546":1,"547":1,"548":1,"549":3,"550":1,"591":1,"1010":1,"1011":2,"1012":9,"1013":3,"1014":3,"1017":3,"1018":1,"1019":1,"1090":1,"1212":1,"1216":2,"1239":11,"1240":2,"1243":3,"1245":1,"1247":49,"1278":1,"1286":5,"1310":1,"1311":2,"1342":1,"1350":3,"1353":1,"1359":43,"1366":1,"1369":2,"1372":1,"1380":2,"1431":1,"1436":11,"1438":4,"1467":1,"1519":2,"1526":5,"1531":2,"1534":1,"1536":5,"1537":6,"1538":1,"1539":1,"1540":1,"1541":1,"1542":1,"1543":1,"1544":1,"1545":1,"1546":1,"1547":1,"1548":1,"1549":1,"1550":1,"1551":1,"1552":1,"1553":2,"1554":1,"1555":1,"1556":1,"1557":1,"1558":1,"1559":1,"1560":1,"1561":1,"1562":1,"1563":1,"1564":1,"1565":1,"1566":1,"1567":1,"1568":1,"1569":1,"1570":1,"1571":1,"1575":1,"1577":9,"1581":1,"1582":2,"1584":1,"1585":2,"1591":2,"1637":1,"1662":1,"1796":1,"1797":1,"1802":1,"1815":9,"1817":1,"1859":1,"1994":2,"2040":1,"2051":1,"2171":2,"2179":1}}],["critically",{"3":{"235":1,"1237":1,"1270":1,"1300":1,"1342":1,"1436":3,"1452":1,"1805":1,"1895":1}}],["critical",{"0":{"1619":1},"2":{"791":1,"792":1,"794":1,"2166":1,"2168":1},"3":{"0":1,"767":1,"790":6,"791":1,"792":3,"794":1,"812":1,"870":1,"995":1,"1259":1,"1286":1,"1300":3,"1303":1,"1311":1,"1395":11,"1415":1,"1417":5,"1445":1,"1449":1,"1455":3,"1456":1,"1460":1,"1463":2,"1466":1,"1492":1,"1559":1,"1565":1,"1569":1,"1639":1,"1649":1,"1814":1,"2166":3,"2168":1}}],["crack",{"3":{"1900":1,"1902":1,"1905":1}}],["cracking",{"3":{"311":1,"945":1,"1300":2,"1436":1}}],["craft",{"3":{"1324":1,"1897":1,"2069":1}}],["craftsmanship",{"3":{"1301":1,"1552":1}}],["crafted",{"3":{"1270":1,"1324":1,"1359":4,"1416":1,"1436":3}}],["crawler",{"3":{"607":1,"609":1,"1467":2,"2158":3}}],["crashing",{"3":{"1237":1,"1259":1,"1286":1,"1300":1,"1380":1,"1396":1,"1401":1,"1402":2,"1416":1}}],["crashed",{"3":{"1125":1,"1323":2,"1324":1,"1475":1}}],["crashes",{"3":{"0":1,"16":1,"18":1,"146":1,"147":1,"150":1,"1074":1,"1213":1,"1286":1,"1324":1,"1436":1,"1491":1,"2128":1}}],["crash",{"3":{"0":4,"19":1,"24":1,"148":1,"195":1,"197":1,"199":1,"201":3,"226":1,"413":2,"520":1,"524":1,"537":1,"539":1,"601":1,"603":1,"676":1,"692":2,"714":1,"716":1,"756":1,"897":1,"931":2,"933":1,"996":1,"1016":1,"1018":1,"1022":1,"1041":1,"1074":1,"1076":1,"1102":1,"1116":1,"1254":1,"1257":1,"1258":1,"1259":12,"1269":1,"1270":1,"1286":3,"1318":1,"1323":5,"1324":2,"1339":4,"1349":1,"1357":1,"1359":6,"1367":1,"1368":1,"1369":2,"1396":4,"1408":1,"1415":6,"1416":1,"1417":16,"1436":1,"1455":1,"1491":2,"1576":1,"1577":1,"1631":1,"1660":1,"1669":1,"1721":1,"1724":1,"1839":1,"1842":1,"1845":1,"1947":1,"2013":1,"2126":1,"2164":1,"2190":1}}],["crowd",{"3":{"390":1,"1324":2,"1395":1,"1955":1}}],["cronformatexception",{"3":{"1323":1}}],["cronexpression",{"3":{"707":3,"1269":1,"1270":2,"1286":1,"1323":3,"1442":1}}],["cronschedule",{"3":{"1323":1}}],["crons",{"3":{"625":1,"626":2,"1438":1,"1456":1,"1490":1,"1492":2,"1591":1,"1599":1}}],["cronosnextoccurrencetests",{"3":{"1199":1,"1207":2,"1438":2}}],["cronos",{"3":{"0":1,"707":2,"708":1,"709":1,"1059":1,"1212":1,"1213":1,"1270":2,"1323":3,"1948":1}}],["cron",{"1":{"704":1,"705":1,"706":1,"707":1,"708":1,"709":1,"710":1,"711":1,"2181":1,"2182":1,"2183":1,"2184":1,"2185":1,"2186":1,"2187":1,"2188":1,"2189":1,"2190":1,"2191":1,"2192":1,"2193":1},"2":{"707":1},"3":{"0":5,"627":2,"628":1,"640":1,"704":1,"707":6,"708":1,"709":3,"766":1,"767":1,"768":1,"1000":1,"1041":1,"1043":1,"1046":1,"1212":1,"1213":1,"1260":1,"1269":2,"1270":5,"1323":16,"1367":1,"1438":2,"1456":1,"1457":1,"1459":1,"1460":1,"1465":1,"1474":1,"1475":1,"1482":1,"1492":2,"1496":1,"1524":1,"1526":5,"1531":1,"1577":2,"1582":1,"1589":1,"1590":1,"1591":4,"1596":1,"1664":3,"1779":1,"1947":2,"1948":3,"1950":3,"2034":1,"2037":1,"2165":1,"2180":2,"2181":1,"2183":1,"2192":1,"2203":1,"2206":1,"2232":1}}],["cropped",{"3":{"1480":1}}],["cropping",{"3":{"1480":1}}],["crops",{"3":{"1323":1,"2126":1}}],["crop",{"3":{"0":1,"440":1,"1212":1,"1286":1,"1323":1,"1415":1,"1496":1,"2128":1}}],["crossrepocomparison",{"3":{"1522":1,"1528":1,"1573":1,"1579":1,"1587":1,"1593":1}}],["crosslayerviolation",{"3":{"1436":2}}],["crossoriginrejection",{"3":{"1324":2}}],["crosstenantwritedetail",{"3":{"1311":1}}],["crosstenantwritetitle",{"3":{"1311":1}}],["crosstenantwriteexception",{"2":{"107":1,"109":1,"698":2,"1276":1,"1851":1},"3":{"0":1,"107":1,"109":2,"698":2,"1199":5,"1203":1,"1207":2,"1237":1,"1276":2,"1286":6,"1311":5,"1851":1}}],["crossservicespeakerlinktests",{"2":{"1535":1},"3":{"1199":1,"1207":2,"1438":1,"1535":1,"1611":1}}],["crossservicesmoketests",{"3":{"1199":1,"1207":2}}],["crossserviceuserregisteredtests",{"2":{"1535":1},"3":{"1199":1,"1207":2,"1438":1,"1535":1,"1611":1}}],["crossservicetestbase",{"3":{"1199":6,"1207":2,"1436":2}}],["crossservicecollection",{"3":{"1199":2,"1207":2}}],["crossservicedatasource",{"2":{"1429":1},"3":{"1199":4,"1207":1,"1429":1,"1436":2,"1488":1}}],["crossservice",{"2":{"1207":1,"1437":1,"1486":1,"1487":1,"1490":2,"1526":1,"1534":1,"1591":1,"1615":1},"3":{"914":3,"1199":13,"1203":1,"1206":1,"1207":53,"1436":11,"1437":2,"1438":3,"1467":1,"1486":1,"1487":1,"1488":1,"1490":2,"1496":1,"1526":3,"1534":1,"1591":3,"1611":3,"1615":1}}],["crossservicefixture",{"2":{"914":1},"3":{"914":3,"1199":8,"1207":2,"1436":4,"1437":2,"1488":2}}],["crossservicefixturebasetests",{"3":{"1199":1,"1207":3,"1436":1,"1438":1,"1487":1}}],["crossservicefixturebase",{"2":{"914":1,"1429":1,"1671":1},"3":{"640":1,"914":4,"916":1,"1066":1,"1199":5,"1207":3,"1212":1,"1428":1,"1429":7,"1436":21,"1438":2,"1488":2,"1496":1,"1671":1}}],["crosssourcespecificationtests",{"3":{"1199":1,"1207":4,"1496":1}}],["crosssourcespecification",{"2":{"168":1,"545":1,"549":1,"1239":1,"1500":1,"1577":1,"1662":1,"1859":2,"1861":1,"1862":1},"3":{"168":2,"545":2,"547":1,"549":1,"1199":4,"1203":1,"1207":2,"1212":1,"1239":11,"1247":9,"1353":2,"1359":11,"1436":2,"1438":1,"1496":3,"1500":1,"1577":4,"1662":1,"1859":2,"1861":1,"1862":1}}],["crossevent",{"3":{"1359":2}}],["crossentitynavigationfinder",{"3":{"1199":2,"1207":1,"1428":1,"1431":1,"1436":4}}],["crossed",{"3":{"255":1,"1323":1,"1396":1,"1415":1,"1436":1,"1467":1,"1973":1,"2010":1}}],["crosses",{"0":{"1225":1,"1858":1},"3":{"0":2,"109":2,"249":1,"265":1,"396":1,"551":1,"645":1,"656":1,"657":1,"658":2,"696":1,"786":1,"789":1,"930":1,"1018":1,"1212":1,"1237":2,"1243":1,"1259":2,"1300":2,"1307":1,"1309":1,"1311":3,"1324":2,"1332":1,"1350":4,"1353":1,"1357":1,"1359":4,"1379":1,"1380":3,"1395":2,"1396":9,"1400":1,"1402":2,"1415":2,"1416":2,"1417":2,"1427":1,"1436":1,"1438":1,"1634":1,"1638":2,"1840":1,"1862":1,"1887":1,"1892":1,"1916":1,"1919":1,"1922":1,"1927":1,"1944":1,"1953":1,"2073":1,"2126":1,"2155":1,"2230":1}}],["crossdatasourcedegradeconventiontests",{"2":{"164":1},"3":{"164":1,"1199":1,"1207":7,"1286":1,"1438":1}}],["crossdatasourcedegradeconvention",{"2":{"46":1,"47":1,"166":1,"1281":1,"1310":1,"1473":1,"1482":1,"1577":1,"1849":1,"1853":1,"1858":1,"1862":1,"1866":1},"3":{"46":1,"47":1,"166":1,"1198":1,"1199":2,"1203":1,"1207":2,"1247":1,"1281":3,"1286":30,"1310":4,"1396":1,"1436":1,"1473":1,"1482":1,"1577":2,"1849":1,"1853":1,"1858":1,"1862":1,"1866":1}}],["crossings",{"3":{"1324":1}}],["crossing",{"0":{"1349":1,"1413":1,"1946":1},"3":{"0":2,"255":1,"390":1,"427":1,"826":1,"1293":1,"1323":2,"1324":1,"1339":1,"1350":3,"1359":3,"1396":1,"1402":1,"1416":1,"1436":2,"1631":1,"1922":1,"2073":1}}],["cross",{"0":{"1346":1,"1362":1,"1400":1,"1402":1,"1405":1,"1460":1,"1469":1,"1494":1,"1528":1,"1559":1,"1579":1,"1593":1,"1751":1,"1753":1,"1772":1,"1774":1,"1859":1,"2190":1,"2208":1},"1":{"8":1,"9":1,"10":1,"11":1,"12":1,"13":1,"28":1,"29":1,"30":1,"31":1,"32":1,"33":1,"34":1,"35":1,"50":1,"51":1,"52":1,"53":1,"54":1,"55":1,"771":1,"772":1,"773":1,"774":1,"775":1,"776":1,"777":1,"778":1,"993":1,"994":1,"995":1,"996":1,"997":1,"998":1,"999":1,"1000":1,"1416":1,"1711":1,"1712":1,"1713":1,"1714":1,"1715":1,"1716":1,"1893":1,"1894":1,"1895":1,"1896":1,"1897":1,"1898":1,"1899":1},"2":{"1188":1},"3":{"0":28,"8":1,"10":3,"28":1,"31":1,"47":2,"49":1,"50":1,"52":1,"54":1,"57":1,"59":2,"61":3,"62":3,"63":1,"64":2,"68":1,"77":1,"81":1,"91":1,"97":1,"99":1,"105":2,"116":2,"117":1,"119":1,"122":2,"135":1,"136":1,"150":2,"151":1,"152":1,"157":2,"159":1,"162":1,"166":3,"168":2,"169":2,"171":1,"207":1,"214":2,"215":1,"216":1,"217":1,"245":2,"253":1,"255":1,"264":1,"265":3,"266":1,"273":1,"313":1,"331":1,"336":1,"343":1,"381":1,"386":2,"387":1,"391":1,"396":1,"398":1,"399":1,"400":1,"405":1,"446":1,"459":1,"470":1,"474":1,"506":2,"507":1,"509":1,"517":1,"521":2,"524":2,"528":1,"531":1,"535":1,"536":1,"541":1,"545":1,"547":1,"549":3,"554":1,"578":1,"583":2,"584":3,"585":1,"587":1,"607":1,"611":1,"618":1,"619":2,"624":2,"625":2,"626":11,"627":1,"628":4,"629":2,"640":5,"642":1,"659":1,"661":1,"665":1,"667":1,"692":1,"698":1,"699":1,"717":1,"731":1,"733":1,"747":1,"756":2,"764":1,"771":1,"774":4,"776":1,"777":1,"781":1,"782":1,"799":4,"801":1,"802":1,"809":2,"810":1,"826":1,"834":1,"861":1,"895":1,"896":1,"897":1,"900":1,"906":1,"907":1,"914":5,"915":2,"930":1,"931":1,"950":1,"964":1,"980":1,"987":1,"990":1,"992":1,"993":1,"996":1,"1011":1,"1018":1,"1022":1,"1024":1,"1026":1,"1027":1,"1028":1,"1030":1,"1043":1,"1049":1,"1050":1,"1053":1,"1061":1,"1066":2,"1067":1,"1068":1,"1069":1,"1070":1,"1075":3,"1083":1,"1086":1,"1104":1,"1108":1,"1140":1,"1147":1,"1150":1,"1183":1,"1184":1,"1185":1,"1187":1,"1188":1,"1190":1,"1191":2,"1192":10,"1193":2,"1200":3,"1201":3,"1202":6,"1203":6,"1210":2,"1212":20,"1213":1,"1216":3,"1231":1,"1232":2,"1237":3,"1239":3,"1243":1,"1244":2,"1245":2,"1247":11,"1252":1,"1257":1,"1258":1,"1259":15,"1260":1,"1261":2,"1265":1,"1267":1,"1270":13,"1271":3,"1273":1,"1276":1,"1278":1,"1281":3,"1286":38,"1287":1,"1288":1,"1298":1,"1300":16,"1301":6,"1308":2,"1309":21,"1310":15,"1311":23,"1312":6,"1313":2,"1314":1,"1316":2,"1320":1,"1323":19,"1324":36,"1326":4,"1328":3,"1338":1,"1339":21,"1340":4,"1341":2,"1342":1,"1343":1,"1345":3,"1349":1,"1350":37,"1352":1,"1353":1,"1354":2,"1355":4,"1359":80,"1363":2,"1367":1,"1369":4,"1375":1,"1378":1,"1379":1,"1380":16,"1381":1,"1387":1,"1391":1,"1395":29,"1396":72,"1398":1,"1401":1,"1402":34,"1403":1,"1404":3,"1405":2,"1406":1,"1410":2,"1413":2,"1414":1,"1415":39,"1416":11,"1417":27,"1427":3,"1428":2,"1429":1,"1430":1,"1431":2,"1433":2,"1436":154,"1437":4,"1438":26,"1439":1,"1440":1,"1441":2,"1442":1,"1443":1,"1444":1,"1446":2,"1449":4,"1452":3,"1453":1,"1454":1,"1455":12,"1456":4,"1459":1,"1460":15,"1461":1,"1462":1,"1463":4,"1465":2,"1467":5,"1471":1,"1473":2,"1474":1,"1475":4,"1476":2,"1482":4,"1485":2,"1486":4,"1487":2,"1488":5,"1489":4,"1490":16,"1492":13,"1493":1,"1496":32,"1497":1,"1498":4,"1499":1,"1500":3,"1502":1,"1507":1,"1509":1,"1510":2,"1522":1,"1523":1,"1524":4,"1525":1,"1526":23,"1528":1,"1530":1,"1531":6,"1532":1,"1533":2,"1534":11,"1535":5,"1536":2,"1542":1,"1545":1,"1547":1,"1554":2,"1556":1,"1565":1,"1570":2,"1571":1,"1572":2,"1573":1,"1574":1,"1576":1,"1577":15,"1579":1,"1585":1,"1586":1,"1587":1,"1591":19,"1594":1,"1595":1,"1596":1,"1597":1,"1599":4,"1600":4,"1602":1,"1610":1,"1611":6,"1612":2,"1617":1,"1618":3,"1627":1,"1630":2,"1631":2,"1634":3,"1635":4,"1638":9,"1639":6,"1641":1,"1651":1,"1653":1,"1654":1,"1656":4,"1660":1,"1661":1,"1662":4,"1663":3,"1664":5,"1665":2,"1667":1,"1668":1,"1670":1,"1671":1,"1674":1,"1675":1,"1682":1,"1699":1,"1711":1,"1722":1,"1729":1,"1732":1,"1744":1,"1748":1,"1749":1,"1751":1,"1752":1,"1764":1,"1765":5,"1769":2,"1775":1,"1779":1,"1780":1,"1783":1,"1786":1,"1790":1,"1792":3,"1793":1,"1796":2,"1810":1,"1818":2,"1819":1,"1820":1,"1823":1,"1825":1,"1827":3,"1833":1,"1834":1,"1845":1,"1849":4,"1851":1,"1852":1,"1853":3,"1856":1,"1858":1,"1860":2,"1861":2,"1862":3,"1863":1,"1865":3,"1866":2,"1867":2,"1868":2,"1876":1,"1879":1,"1882":1,"1884":1,"1885":1,"1886":1,"1892":1,"1893":1,"1896":1,"1897":1,"1899":1,"1911":1,"1914":1,"1916":1,"1919":1,"1921":1,"1923":1,"1937":2,"1941":1,"1946":1,"1950":1,"1968":1,"1969":3,"1974":1,"1975":1,"1988":1,"1999":1,"2008":1,"2009":1,"2016":1,"2020":1,"2022":1,"2027":3,"2029":1,"2035":1,"2053":2,"2056":1,"2067":1,"2070":1,"2074":1,"2082":1,"2083":2,"2086":3,"2092":1,"2094":1,"2096":1,"2098":1,"2107":1,"2113":2,"2134":1,"2139":3,"2146":2,"2148":1,"2149":1,"2150":2,"2151":1,"2152":1,"2156":1,"2160":1,"2162":1,"2168":1,"2169":1,"2185":1,"2194":1,"2202":1,"2208":1,"2211":1,"2213":1,"2218":1,"2219":1,"2220":2,"2227":1,"2228":1,"2230":5,"2232":10,"2243":1}}],["cref",{"3":{"1416":2,"1496":1,"1526":1}}],["creep",{"3":{"698":1,"2048":1}}],["creds",{"3":{"1526":2}}],["credible",{"3":{"1144":1,"1904":1,"2043":1}}],["credibilityexperience",{"3":{"1369":1}}],["credibilityexperiencescore",{"3":{"1350":1,"1359":1,"1369":1}}],["credibility",{"3":{"1018":1,"1019":1,"1020":1,"1350":1,"1359":2,"1366":2}}],["credit",{"3":{"1490":1}}],["crediting",{"3":{"1286":1}}],["credits",{"3":{"777":1,"1591":2,"1805":1,"1910":1}}],["credited",{"3":{"428":1,"703":1,"1910":1}}],["credentialroutes",{"2":{"180":1},"3":{"180":1}}],["credentials",{"0":{"1405":1},"3":{"0":2,"313":1,"349":1,"351":1,"362":1,"432":1,"433":1,"434":2,"436":1,"498":1,"507":1,"527":1,"598":2,"601":2,"643":1,"665":1,"774":3,"775":3,"776":1,"905":1,"926":1,"945":2,"947":1,"950":1,"972":1,"1284":1,"1286":2,"1288":1,"1300":16,"1309":2,"1311":8,"1312":1,"1323":6,"1324":12,"1336":2,"1339":2,"1396":3,"1406":2,"1410":1,"1412":1,"1415":10,"1417":23,"1429":1,"1433":1,"1436":6,"1441":1,"1443":1,"1454":2,"1455":1,"1459":2,"1464":1,"1465":1,"1467":3,"1469":1,"1472":4,"1475":1,"1481":1,"1482":1,"1488":3,"1490":1,"1526":1,"1554":1,"1577":1,"1582":1,"1589":1,"1596":2,"1639":1,"1641":8,"1648":1,"1667":1,"1669":1,"1671":1,"1673":1,"1682":1,"1693":1,"1764":2,"1769":1,"1777":1,"1896":1,"1898":1,"1901":1,"1936":1,"1940":1,"1981":1,"2063":1,"2067":1,"2088":1,"2122":1,"2224":1}}],["credentialed",{"3":{"0":1,"432":1,"436":1,"1187":1,"1339":2,"1416":1,"1417":12,"1898":1}}],["credential",{"0":{"1407":1},"3":{"0":8,"173":1,"174":1,"176":1,"177":1,"179":1,"180":2,"189":1,"212":1,"217":1,"279":1,"280":1,"281":2,"284":4,"286":1,"309":2,"313":2,"314":3,"348":1,"349":1,"350":1,"352":1,"353":2,"355":1,"358":1,"362":1,"403":1,"435":1,"458":1,"498":1,"499":2,"526":1,"528":1,"529":1,"530":2,"532":1,"572":1,"597":1,"598":1,"599":2,"600":1,"602":2,"605":1,"612":1,"632":1,"634":1,"640":1,"641":1,"642":2,"643":3,"667":1,"670":1,"684":1,"688":1,"690":6,"691":1,"692":1,"718":2,"766":1,"768":1,"792":1,"853":1,"855":1,"869":1,"871":1,"872":2,"874":1,"904":1,"906":2,"908":1,"938":1,"945":3,"946":1,"947":1,"948":4,"950":3,"1017":2,"1018":1,"1020":3,"1022":1,"1057":1,"1058":1,"1059":1,"1063":1,"1118":1,"1183":1,"1212":1,"1241":1,"1264":1,"1270":1,"1283":1,"1286":4,"1289":1,"1290":1,"1291":2,"1292":2,"1293":1,"1294":1,"1300":48,"1311":2,"1312":1,"1322":1,"1323":12,"1324":10,"1326":1,"1335":1,"1339":14,"1359":1,"1366":1,"1368":1,"1369":1,"1395":1,"1396":89,"1403":2,"1404":1,"1405":2,"1406":2,"1407":3,"1409":2,"1415":22,"1417":8,"1427":1,"1436":22,"1438":9,"1441":1,"1444":1,"1445":1,"1446":1,"1447":1,"1455":8,"1457":2,"1458":1,"1460":1,"1461":1,"1462":2,"1465":5,"1466":1,"1467":14,"1468":2,"1472":2,"1474":1,"1475":1,"1477":2,"1481":2,"1492":2,"1577":1,"1595":1,"1596":1,"1631":3,"1641":4,"1650":1,"1661":1,"1670":1,"1672":1,"1703":1,"1732":2,"1765":1,"1898":2,"1899":1,"1902":3,"1905":1,"1967":1,"1971":1,"1973":1,"1978":3,"1979":3,"1980":3,"1982":1,"1998":2,"1999":2,"2000":1,"2001":4,"2002":2,"2055":1,"2066":1,"2117":1,"2141":1,"2153":1,"2166":1,"2173":1}}],["credentialpathprefixes",{"2":{"0":1,"217":1,"1339":1,"2149":1},"3":{"0":1,"217":1,"1339":6,"2149":1}}],["cream",{"1":{"2050":1,"2051":1,"2052":1,"2053":1,"2054":1,"2055":1,"2056":1,"2057":1,"2058":1,"2059":1,"2060":1},"3":{"2050":2,"2060":1,"2210":1}}],["creada",{"3":{"265":1}}],["creado",{"3":{"265":2}}],["creator",{"3":{"1362":1,"1630":1}}],["creatable",{"3":{"1311":2}}],["creational",{"3":{"1526":1,"1539":1}}],["creations",{"3":{"27":1,"1436":1}}],["creation",{"3":{"17":1,"351":1,"352":1,"524":1,"698":3,"782":1,"784":2,"790":2,"792":1,"794":1,"906":1,"988":1,"1174":1,"1175":2,"1213":1,"1229":1,"1234":1,"1249":1,"1254":1,"1259":2,"1271":1,"1278":1,"1286":9,"1300":3,"1309":3,"1311":4,"1339":1,"1350":7,"1357":1,"1359":9,"1364":1,"1369":1,"1380":1,"1395":12,"1396":4,"1402":2,"1415":6,"1417":4,"1429":1,"1436":1,"1467":1,"1472":3,"1481":1,"1490":1,"1496":2,"1630":1,"1631":5,"1632":2,"1634":1,"1635":1,"1639":4,"1640":1,"1651":1,"1685":1,"1686":1,"1719":1,"1727":3,"1728":1,"1729":1,"1750":1,"1751":1,"1765":1,"1767":1,"1770":1,"1773":1,"1775":1,"1841":1,"1978":1,"1979":2,"2166":1}}],["creating",{"3":{"0":1,"7":1,"291":1,"632":1,"711":1,"715":1,"781":1,"1270":1,"1281":1,"1286":4,"1300":1,"1309":3,"1311":3,"1323":1,"1324":1,"1350":3,"1359":10,"1380":1,"1396":1,"1402":1,"1417":2,"1436":5,"1467":1,"1472":1,"1474":1,"1490":2,"1629":1,"1631":2,"1632":1,"1639":3,"1664":1,"1684":1,"1722":1,"1729":1,"1749":1,"1988":1,"1999":1}}],["create→",{"3":{"1577":1}}],["create→result",{"3":{"1526":1}}],["createwindow",{"3":{"1416":3,"1417":1}}],["createmodel",{"3":{"1496":2}}],["createmodule",{"3":{"1436":1}}],["createmauiapp",{"3":{"1416":15,"1417":2}}],["createmigrationprooftable",{"2":{"564":1,"1319":1},"3":{"564":2,"1199":3,"1203":1,"1207":2,"1319":3,"1323":5}}],["createorderhandler",{"3":{"1686":1}}],["createorreactivate",{"3":{"1396":5}}],["createorupdateinstallationasync",{"3":{"1323":1}}],["createroomasync",{"3":{"1395":2}}],["createrequest",{"3":{"1497":1}}],["createrequestclientasync",{"2":{"881":1},"3":{"881":2,"1324":1,"1395":1}}],["createresult",{"3":{"1395":4}}],["createresetpasswordcommand",{"3":{"1311":1,"1415":2}}],["createreadonly",{"3":{"1286":7}}],["createrefreshusermissingerror",{"2":{"497":1,"499":1},"3":{"497":1,"499":1,"1300":1}}],["createx",{"3":{"1359":1}}],["createxhandler",{"2":{"1354":1},"3":{"1354":1,"1359":1}}],["createnewcategory",{"3":{"1359":1}}],["createnewanswerasync",{"3":{"1350":1,"1359":2}}],["createbatchasync",{"2":{"1380":1},"3":{"1380":8}}],["createbuilder",{"3":{"1339":1,"1436":1}}],["createbookmarkasync",{"3":{"1396":2}}],["createbookmarkhandlertests",{"3":{"1199":1,"1207":3}}],["createbookmarkhandler",{"3":{"1199":2,"1203":1,"1207":2,"1286":3,"1350":1,"1359":3,"1380":1,"1396":16,"1436":1}}],["createbookmarkrequestvalidatortests",{"3":{"1199":1,"1207":2}}],["createbookmarkrequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1396":8}}],["createbookmarkrequest",{"3":{"1199":12,"1203":1,"1207":2,"1396":12}}],["createbootstraploggerfactory",{"2":{"401":1,"1333":1},"3":{"401":2,"1311":1,"1333":1,"1339":2}}],["createbounded",{"2":{"1100":1},"3":{"1100":1,"1396":1}}],["createifnotexists",{"3":{"1323":1}}],["createinheritedasync",{"3":{"1436":2}}],["createinventoryitemhandler",{"3":{"897":2}}],["createinstance",{"2":{"583":1,"585":1,"1315":1},"3":{"583":1,"585":1,"1229":1,"1237":1,"1247":1,"1286":4,"1300":2,"1315":1,"1323":1}}],["createindex",{"2":{"564":1},"3":{"564":1}}],["createloggerconfiguration",{"2":{"1333":1},"3":{"1333":1,"1339":2}}],["createlogger",{"3":{"1311":1,"1339":2,"1417":1}}],["createlinkedtimeout",{"2":{"1422":1},"3":{"1422":1,"1427":1}}],["createlinkedtokensource",{"2":{"121":1}}],["createlink",{"2":{"1357":1},"3":{"1343":1,"1350":3,"1357":1,"1359":2}}],["createlimitedpartition",{"3":{"1311":9,"2000":1}}],["createlivepollhandlertests",{"3":{"1199":1,"1207":3}}],["createlivepollhandler",{"3":{"1199":2,"1203":1,"1207":2,"1350":7,"1399":2,"1402":11,"1496":1}}],["createlivepollcommandvalidatortests",{"3":{"1199":1,"1207":2}}],["createlivepollcommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1402":6}}],["createlivepollcommand",{"3":{"1199":8,"1203":1,"1207":2,"1402":9}}],["createlivepollrequestvalidatortests",{"3":{"1199":1,"1207":2}}],["createlivepollrequestvalidator",{"3":{"1199":3,"1203":1,"1207":2,"1402":8}}],["createlivepollrequest",{"3":{"1199":14,"1203":1,"1207":2,"1396":2,"1402":17}}],["createhost",{"3":{"1436":2,"1488":1}}],["createhostasync",{"3":{"1300":1}}],["createhandler",{"3":{"1311":3,"1324":1,"1359":1,"1380":1,"1396":1}}],["createfile",{"3":{"1343":1,"1350":3,"1359":2}}],["createforwardedheadersoptions",{"2":{"1338":1},"3":{"1338":1,"1339":3}}],["createforgotpasswordcommand",{"3":{"1311":1,"1415":2}}],["createfactory",{"3":{"1286":2,"1436":2,"1488":1}}],["createfailure",{"3":{"1270":24,"2136":1}}],["createfetchcts",{"2":{"559":1},"3":{"556":1,"559":1,"881":1,"1324":3}}],["createquestionasync",{"3":{"1395":1}}],["createquestioncommand",{"3":{"1359":1}}],["createquestionhandlertests",{"3":{"1199":1,"1207":2,"1359":2}}],["createquestionhandler",{"2":{"1639":1},"3":{"1199":2,"1203":1,"1207":2,"1270":2,"1352":1,"1359":23,"1395":2,"1639":1}}],["createquery",{"3":{"725":3,"1311":3,"1415":2}}],["createproducthandlertests",{"3":{"1685":1}}],["createproducthandler",{"3":{"1685":1}}],["createproviderchatclient",{"2":{"1091":1,"1421":1,"2172":1},"3":{"1091":1,"1421":1,"1427":2,"2172":1}}],["createpage",{"3":{"1496":1}}],["createpartnerasync",{"3":{"1395":2}}],["createpartnerhandlertests",{"3":{"1199":1,"1207":2,"1359":4,"1496":1}}],["createpartnerhandler",{"3":{"1199":2,"1203":1,"1207":2,"1350":2,"1352":3,"1359":18}}],["createpostgresql",{"2":{"909":1,"1034":1},"3":{"905":1,"909":1,"1034":1,"1286":2}}],["createthingasync",{"3":{"1436":3}}],["createticketcommandvalidator",{"3":{"1199":1,"1207":1,"1436":6}}],["createticketcommand",{"3":{"1199":5,"1207":1,"1436":10}}],["createtickethandler",{"3":{"782":1,"1199":1,"1207":1,"1436":5}}],["createtenantscope",{"2":{"703":1},"3":{"698":1,"703":1,"1259":5,"1286":10,"1311":1}}],["createtable",{"2":{"564":1},"3":{"564":1,"1323":1}}],["createversion7",{"3":{"690":1,"1396":2}}],["createcategoryasync",{"3":{"1395":2}}],["createcouponcommand",{"2":{"2096":1},"3":{"2096":1}}],["createcore",{"3":{"1350":7}}],["createcoreasync",{"3":{"0":1,"926":1,"1265":1,"1270":3,"1359":15}}],["createcontainers",{"3":{"1436":1}}],["createcontext",{"3":{"1270":3}}],["createconferencecategorycommand",{"3":{"1359":1}}],["createconferencecategoryhandlertests",{"3":{"1199":1,"1207":2,"1359":2,"1436":1}}],["createconferencecategoryhandler",{"3":{"1199":2,"1203":1,"1207":2,"1270":2,"1352":1,"1359":23}}],["createconverter",{"2":{"1223":1,"1237":1},"3":{"1223":1,"1229":2,"1237":4,"1300":3}}],["createchangepasswordcommand",{"2":{"1406":1},"3":{"1311":2,"1406":1,"1415":2}}],["createchangepreferencescommand",{"2":{"1406":1},"3":{"1300":1,"1311":1,"1406":1,"1415":2}}],["createchained",{"2":{"827":1,"1124":1},"3":{"827":1,"1124":1,"1324":1,"1339":1}}],["createcheckoutsessionhandler",{"3":{"1765":1}}],["createcheckoutsessioncommand",{"2":{"1264":1},"3":{"1264":2}}],["createcheckoutsessioncommandvalidator",{"2":{"674":1},"3":{"674":2,"1591":1}}],["createcheckoutsession",{"3":{"674":1,"1264":1,"1765":1}}],["createclientwithtoken",{"3":{"881":1,"1324":4}}],["createclient",{"3":{"572":1,"1309":2,"1429":1,"1436":7,"1488":2,"1611":1}}],["createaccountlink",{"3":{"1436":1}}],["createaccesstokenforsession",{"2":{"905":1},"3":{"905":1,"1300":6}}],["createaccesstoken",{"2":{"907":1,"2197":1},"3":{"0":1,"905":1,"907":1,"1059":1,"1289":2,"1290":1,"1300":6,"1406":1,"1415":1,"2197":1}}],["createactivityasync",{"3":{"1395":2}}],["createactivityhandlertests",{"3":{"1199":1,"1207":2,"1359":2,"1438":1}}],["createactivityhandler",{"3":{"1199":3,"1203":1,"1207":2,"1270":2,"1352":2,"1359":15}}],["createauthenticatedclientasync",{"2":{"881":1},"3":{"881":1,"1324":6,"1395":6,"1396":3,"1415":1}}],["createasyncscope",{"3":{"1259":2,"1359":4,"1402":1}}],["createasync",{"2":{"318":1,"324":1,"913":1,"1067":1,"1069":1},"3":{"318":1,"324":1,"913":1,"1067":1,"1069":1,"1311":6,"1359":2,"1380":29,"1396":8,"1402":2,"1490":1,"1995":1}}],["createunsafe",{"3":{"1237":1}}],["createunderclaimasync",{"2":{"524":1,"1102":1},"3":{"0":1,"524":2,"1100":2,"1102":1,"1402":4}}],["createusingazureservicebus",{"2":{"640":1},"3":{"640":1}}],["createuser",{"2":{"314":1},"3":{"314":1,"1286":2,"1289":1,"1300":6,"1406":1,"1415":3}}],["createsinglekeyring",{"3":{"1286":1}}],["createsessionpollasync",{"3":{"1402":1}}],["createsessionasync",{"3":{"1395":3}}],["createsessionrequest",{"2":{"1830":1},"3":{"1271":1,"1830":1}}],["createsessioncommand",{"2":{"1830":1},"3":{"1271":1,"1359":1,"1830":1}}],["createsessionhandlertests",{"3":{"1199":1,"1207":2,"1359":2}}],["createsessionhandler",{"3":{"1199":2,"1203":1,"1207":2,"1286":3,"1352":8,"1359":23,"1395":3,"1436":2}}],["createsponsorasync",{"3":{"1395":3}}],["createsponsorhandlertests",{"3":{"1199":1,"1207":2,"1359":2,"1438":1}}],["createsponsorhandler",{"3":{"1199":2,"1203":1,"1207":2,"1270":2,"1350":1,"1352":1,"1359":13}}],["createspeakerasync",{"3":{"1395":3}}],["createspeakercommand",{"3":{"1359":1}}],["createspeakerhandlertests",{"3":{"1199":1,"1207":2,"1359":2}}],["createspeakerhandler",{"3":{"1199":2,"1203":1,"1207":2,"1270":2,"1352":1,"1359":20}}],["createsqlite",{"2":{"909":1},"3":{"905":1,"909":1,"1286":2}}],["createsqlserver",{"2":{"909":1},"3":{"905":1,"909":1,"1286":2,"1436":1}}],["creates",{"0":{"1314":1},"3":{"25":1,"27":1,"42":1,"157":1,"195":1,"197":1,"200":1,"225":1,"230":1,"350":1,"352":1,"524":1,"572":1,"599":1,"610":1,"665":1,"698":1,"705":1,"792":1,"881":1,"926":1,"1034":1,"1212":1,"1223":1,"1259":1,"1270":2,"1286":10,"1300":8,"1307":1,"1309":2,"1311":7,"1323":4,"1324":19,"1329":1,"1339":7,"1352":2,"1359":16,"1373":1,"1374":2,"1389":1,"1395":10,"1396":4,"1402":7,"1415":1,"1416":4,"1417":9,"1427":2,"1436":12,"1438":3,"1441":2,"1467":5,"1472":3,"1475":1,"1488":3,"1491":1,"1630":1,"1632":3,"1635":1,"1638":1,"1639":1,"1640":1,"1641":1,"1650":1,"1653":2,"1671":1,"1684":2,"1688":1,"1692":1,"1700":1,"1722":1,"1726":1,"1730":1,"1747":4,"1748":1,"1749":3,"1750":1,"1751":2,"1752":1,"1757":1,"1764":1,"1765":2,"1767":1,"1769":2,"1770":1,"1849":1,"1906":1,"1909":1,"1933":2,"1977":1,"1978":1,"2048":1,"2055":1,"2113":1,"2158":1,"2166":1}}],["createeventasync",{"3":{"1395":2}}],["createeventcommand",{"3":{"1359":1}}],["createeventhandlertests",{"3":{"1199":1,"1207":2,"1359":1,"1436":1}}],["createeventhandler",{"3":{"1199":2,"1203":1,"1207":2,"1270":2,"1352":1,"1359":10}}],["createexecutionstrategy",{"2":{"1273":1},"3":{"1273":1,"1286":2}}],["createexternal",{"2":{"354":1,"1977":1},"3":{"0":1,"350":2,"354":1,"1405":1,"1415":4,"1977":1}}],["createentityasync",{"2":{"1956":1},"3":{"1270":1,"1311":2,"1359":12,"1956":1}}],["createentityhandlerbasetests",{"3":{"1199":1,"1207":1,"1270":1}}],["createentityhandlerbase",{"2":{"922":1,"1265":1,"1269":1,"1666":1},"3":{"922":1,"926":1,"1199":13,"1203":1,"1207":2,"1260":1,"1265":4,"1269":2,"1270":8,"1311":7,"1350":1,"1352":1,"1359":74,"1666":1}}],["createentityhandler",{"2":{"922":1,"1265":1},"3":{"0":1,"922":2,"1199":3,"1203":1,"1207":2,"1260":1,"1265":3,"1270":7,"1311":1,"1323":2,"1577":1}}],["createdroom",{"3":{"1395":1}}],["createdactivity",{"3":{"1395":1}}],["createdat",{"3":{"905":3,"1286":1,"1300":3,"1436":2}}],["createdatabasesasync",{"3":{"1436":1}}],["createdataction",{"3":{"157":1}}],["createdataprotectionkeyvaultkey",{"2":{"602":1,"604":1,"668":1},"3":{"0":1,"602":1,"604":1,"668":1,"1467":1}}],["createdatroute",{"3":{"157":1,"330":1,"1311":2,"1380":10,"1988":1}}],["createdsession",{"3":{"1395":2}}],["createdcount",{"3":{"1359":1}}],["createdevent",{"3":{"1395":1}}],["createdelegate",{"3":{"1286":2,"1300":1}}],["createdefault",{"2":{"459":1,"464":1,"466":2,"698":1,"747":1,"749":1},"3":{"459":1,"464":1,"466":2,"698":1,"747":1,"749":1,"774":1,"1311":6,"1436":5}}],["createdbcontext",{"2":{"1278":1},"3":{"1278":1,"1286":7}}],["createdby",{"2":{"1635":1},"3":{"470":1,"545":1,"715":1,"1237":4,"1239":1,"1247":6,"1286":2,"1355":1,"1359":11,"1369":4,"1380":2,"1438":1,"1630":1,"1631":6,"1632":4,"1635":4,"1638":1,"1639":1,"1640":2,"1815":1}}],["createdtime",{"2":{"757":1},"3":{"757":1,"1455":1,"1467":1}}],["createdon",{"2":{"1140":1},"3":{"39":2,"539":1,"714":1,"1140":1,"1212":1,"1231":2,"1237":3,"1274":2,"1286":4,"1309":6,"1323":2,"1359":4,"1369":1,"1396":15,"1402":4,"1415":13,"1436":3,"1600":2,"1630":1,"1631":2,"1640":5,"1664":1,"1810":1,"2090":1,"2167":1,"2186":1}}],["created",{"2":{"781":1,"1683":1},"3":{"0":2,"157":1,"178":1,"265":2,"290":3,"296":2,"350":2,"353":1,"408":1,"440":1,"539":1,"599":2,"600":1,"610":1,"633":1,"640":1,"665":1,"668":1,"670":1,"690":1,"693":1,"707":1,"716":1,"721":1,"781":1,"782":1,"784":1,"789":1,"790":6,"791":2,"881":1,"905":1,"988":1,"991":1,"1033":1,"1034":1,"1096":1,"1116":1,"1212":1,"1229":2,"1247":3,"1249":1,"1259":4,"1270":3,"1274":1,"1275":1,"1278":3,"1280":1,"1281":1,"1286":19,"1290":1,"1300":5,"1301":1,"1309":10,"1311":9,"1316":1,"1323":6,"1324":14,"1339":7,"1342":2,"1344":1,"1350":30,"1352":1,"1359":37,"1364":1,"1369":5,"1371":1,"1380":4,"1395":25,"1396":22,"1399":1,"1402":14,"1415":6,"1416":4,"1417":4,"1425":2,"1427":1,"1429":1,"1436":11,"1438":3,"1453":1,"1455":2,"1465":2,"1467":6,"1472":1,"1474":2,"1475":1,"1479":1,"1490":1,"1496":4,"1538":1,"1545":1,"1577":1,"1596":1,"1614":1,"1630":5,"1631":5,"1632":2,"1633":2,"1634":8,"1635":1,"1636":1,"1637":1,"1638":1,"1639":5,"1641":1,"1646":1,"1652":2,"1653":2,"1683":1,"1684":1,"1689":1,"1690":1,"1719":2,"1727":1,"1728":1,"1731":1,"1747":2,"1748":2,"1764":2,"1765":2,"1767":2,"1875":1,"1895":1,"1906":1,"1908":1,"1933":1,"1954":1,"2074":1,"2126":1,"2166":2,"2167":2,"2177":1,"2232":2}}],["create",{"0":{"1649":1,"1688":1,"2102":1},"1":{"919":1,"920":1,"921":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1},"2":{"220":1,"231":1,"354":1,"601":1,"661":1,"698":1,"700":1,"703":1,"741":1,"747":1,"749":1,"801":2,"965":1,"968":1,"1051":1,"1067":1,"1124":1,"1135":1,"1167":1,"1169":1,"1170":1,"1398":1,"1472":1,"1811":1,"1812":1,"1954":1,"2048":1,"2090":1,"2093":1,"2104":1},"3":{"0":8,"5":1,"73":1,"200":2,"220":1,"231":1,"290":1,"318":1,"328":1,"329":1,"330":3,"334":1,"348":1,"350":4,"354":1,"527":1,"564":1,"601":1,"609":1,"641":1,"642":1,"657":15,"659":1,"660":1,"661":1,"690":4,"691":1,"693":1,"698":1,"700":1,"703":1,"732":1,"734":2,"741":1,"747":1,"749":2,"782":1,"799":1,"801":2,"803":1,"839":1,"882":1,"888":2,"891":1,"897":4,"905":1,"907":1,"919":1,"920":1,"921":1,"922":4,"924":1,"925":2,"926":1,"965":1,"968":1,"1033":1,"1050":1,"1051":1,"1067":1,"1085":1,"1091":1,"1124":1,"1135":1,"1167":1,"1168":7,"1169":1,"1170":2,"1184":1,"1203":38,"1207":86,"1212":4,"1229":1,"1234":2,"1237":22,"1259":1,"1260":3,"1265":4,"1270":41,"1271":10,"1286":42,"1290":1,"1300":14,"1301":8,"1304":1,"1305":1,"1309":15,"1311":45,"1312":1,"1323":9,"1324":24,"1327":1,"1339":5,"1342":2,"1343":2,"1349":2,"1350":81,"1351":1,"1352":11,"1353":4,"1354":3,"1357":1,"1358":2,"1359":384,"1369":8,"1371":2,"1373":1,"1374":1,"1375":3,"1378":1,"1380":40,"1384":3,"1392":3,"1395":146,"1396":74,"1398":3,"1399":1,"1402":61,"1405":1,"1406":1,"1412":1,"1415":22,"1417":1,"1420":1,"1421":1,"1427":9,"1429":1,"1436":37,"1438":8,"1441":2,"1467":6,"1472":6,"1474":1,"1476":1,"1477":2,"1481":2,"1482":1,"1487":2,"1488":1,"1489":5,"1490":2,"1496":4,"1526":7,"1534":2,"1535":2,"1591":2,"1596":2,"1600":1,"1608":1,"1627":3,"1630":3,"1631":4,"1632":9,"1638":3,"1639":9,"1640":2,"1641":2,"1646":1,"1651":4,"1652":1,"1653":2,"1654":1,"1656":1,"1664":1,"1666":1,"1671":1,"1685":13,"1686":12,"1687":2,"1689":1,"1697":1,"1698":1,"1719":1,"1720":1,"1725":1,"1727":2,"1728":2,"1742":3,"1746":4,"1747":3,"1748":2,"1749":2,"1750":8,"1753":1,"1754":1,"1765":3,"1768":4,"1769":9,"1774":1,"1810":4,"1811":2,"1812":1,"1908":1,"1922":1,"1933":1,"1953":2,"1954":1,"1956":2,"1977":2,"1979":1,"1986":1,"1987":1,"1988":2,"1991":1,"2045":1,"2048":1,"2090":1,"2093":1,"2104":1,"2128":1,"2163":1,"2232":2}}],["cobertura",{"2":{"1455":1},"3":{"1455":2,"1492":1,"1591":1}}],["coffee",{"3":{"1340":1,"1342":1,"1350":3,"1359":1,"1364":1,"1369":2,"1392":1,"1395":1}}],["covariant",{"3":{"1269":1,"1270":1,"1311":2,"1323":1}}],["coverlet",{"3":{"1213":1,"1611":2,"1617":1}}],["covering",{"3":{"138":1,"160":1,"268":1,"494":1,"609":1,"618":1,"635":1,"790":1,"980":1,"996":1,"1083":1,"1247":1,"1280":1,"1286":9,"1309":1,"1311":1,"1312":1,"1323":1,"1324":6,"1339":1,"1350":3,"1358":1,"1359":12,"1380":1,"1395":2,"1396":4,"1416":2,"1417":1,"1433":1,"1436":13,"1437":2,"1438":2,"1449":1,"1453":1,"1455":1,"1456":1,"1467":1,"1489":1,"1491":1,"1496":2,"1526":2,"1577":1,"1591":1,"1654":1,"1671":1,"1684":1,"1686":1,"1748":1,"1923":1,"2034":1,"2037":1,"2038":1,"2043":1,"2079":1,"2158":1,"2190":1,"2232":1,"2240":1}}],["cover",{"0":{"1608":1,"1742":1,"1786":1},"1":{"1998":1,"1999":1,"2000":1,"2001":1,"2002":1},"3":{"23":1,"52":1,"72":1,"118":1,"135":2,"157":1,"170":1,"265":1,"333":1,"388":1,"398":1,"401":1,"461":1,"466":1,"528":1,"530":1,"531":1,"563":1,"578":1,"582":1,"618":1,"651":1,"682":1,"690":1,"700":1,"718":1,"719":1,"751":1,"768":1,"789":1,"810":1,"881":1,"954":1,"966":1,"1018":1,"1026":1,"1075":1,"1119":1,"1187":1,"1219":1,"1235":1,"1242":1,"1247":1,"1259":1,"1265":1,"1286":5,"1300":2,"1301":2,"1309":1,"1311":1,"1320":1,"1322":1,"1324":5,"1339":2,"1359":3,"1380":2,"1396":5,"1417":1,"1425":1,"1428":1,"1436":19,"1438":8,"1445":1,"1456":1,"1459":1,"1465":1,"1483":1,"1487":1,"1488":1,"1534":1,"1551":1,"1577":1,"1582":2,"1583":1,"1597":1,"1632":1,"1646":1,"1666":1,"1667":1,"1669":1,"1776":1,"1998":2,"1999":1,"2001":1,"2002":1,"2006":2,"2025":1,"2034":1,"2048":1,"2059":1,"2076":1,"2079":1,"2104":1,"2156":1,"2208":1,"2236":1}}],["covered",{"0":{"2115":1},"3":{"0":1,"100":1,"164":1,"177":1,"186":1,"215":1,"236":1,"249":1,"265":1,"295":1,"358":1,"419":1,"601":1,"611":1,"620":1,"632":1,"657":1,"702":1,"718":1,"743":1,"768":1,"790":1,"832":1,"889":1,"907":1,"998":1,"1011":1,"1017":1,"1050":2,"1091":1,"1104":1,"1124":1,"1212":1,"1214":1,"1237":7,"1247":4,"1259":1,"1263":1,"1270":4,"1271":6,"1284":1,"1286":33,"1290":1,"1300":9,"1301":3,"1309":6,"1310":1,"1311":10,"1312":1,"1317":1,"1323":18,"1324":33,"1332":1,"1339":38,"1350":2,"1359":92,"1380":3,"1395":7,"1396":43,"1402":2,"1415":10,"1417":8,"1436":29,"1438":6,"1441":1,"1443":1,"1444":3,"1449":1,"1452":1,"1453":1,"1454":1,"1455":3,"1460":1,"1462":1,"1467":2,"1482":1,"1488":2,"1489":1,"1490":1,"1496":4,"1497":1,"1526":3,"1534":1,"1561":1,"1577":3,"1585":2,"1591":3,"1596":1,"1611":1,"1627":1,"1645":1,"1664":1,"1666":1,"1699":1,"1700":1,"1707":2,"1715":1,"1728":1,"1755":1,"1793":1,"1802":1,"1807":1,"1810":1,"1812":1,"1817":1,"1827":1,"1828":1,"1831":1,"1834":1,"1846":1,"1853":1,"1862":1,"1868":1,"1877":1,"1886":1,"1890":1,"1892":1,"1899":1,"1905":1,"1912":1,"1914":1,"1924":1,"1930":1,"1941":1,"1950":1,"1959":1,"1965":1,"1972":1,"1979":1,"1985":1,"1991":1,"1992":1,"1997":1,"2000":1,"2001":1,"2002":2,"2006":1,"2014":1,"2028":1,"2030":1,"2038":1,"2041":1,"2049":1,"2060":1,"2069":1,"2080":1,"2086":1,"2097":1,"2098":1,"2108":1,"2115":1,"2123":1,"2128":1,"2133":1,"2139":1,"2146":1,"2153":1,"2160":1,"2167":1,"2169":1,"2180":1,"2193":1,"2194":1,"2199":1,"2203":1,"2239":1}}],["covers",{"0":{"141":1},"3":{"0":7,"35":1,"41":1,"130":1,"135":1,"142":1,"160":1,"173":1,"178":1,"179":1,"182":1,"189":1,"226":1,"233":1,"235":1,"241":1,"265":1,"318":2,"328":1,"343":1,"358":1,"359":2,"361":1,"365":1,"369":1,"373":1,"402":1,"418":1,"427":1,"434":1,"440":1,"459":1,"461":1,"482":1,"486":1,"506":1,"518":1,"528":2,"529":1,"535":1,"539":1,"555":1,"572":2,"590":1,"591":1,"607":1,"609":2,"618":1,"620":1,"625":1,"626":2,"648":1,"665":1,"674":1,"675":1,"690":1,"701":1,"762":1,"766":1,"794":1,"802":1,"809":1,"818":1,"832":3,"838":1,"844":1,"861":1,"880":1,"897":1,"926":1,"946":1,"964":4,"1016":1,"1018":1,"1019":1,"1020":1,"1074":1,"1116":3,"1123":1,"1132":1,"1135":1,"1161":1,"1163":1,"1170":1,"1175":1,"1185":1,"1195":1,"1212":1,"1217":1,"1229":3,"1230":1,"1238":1,"1247":6,"1248":1,"1258":1,"1259":4,"1260":1,"1263":1,"1270":5,"1271":1,"1272":1,"1281":1,"1284":1,"1286":21,"1287":1,"1300":12,"1301":2,"1302":2,"1309":3,"1311":26,"1313":1,"1316":1,"1320":1,"1323":10,"1324":35,"1325":1,"1333":1,"1339":8,"1340":1,"1350":3,"1351":2,"1359":23,"1360":1,"1365":1,"1369":4,"1370":1,"1380":1,"1381":1,"1395":10,"1396":56,"1397":1,"1402":10,"1403":1,"1411":1,"1415":17,"1416":3,"1417":8,"1418":1,"1422":1,"1428":1,"1435":1,"1436":98,"1437":1,"1438":65,"1447":1,"1449":1,"1450":1,"1453":1,"1454":1,"1455":5,"1465":1,"1467":8,"1471":1,"1474":1,"1475":1,"1476":2,"1481":1,"1485":1,"1487":1,"1489":1,"1490":2,"1492":3,"1496":1,"1524":1,"1526":12,"1532":1,"1533":1,"1535":1,"1577":4,"1583":1,"1585":2,"1591":5,"1596":1,"1612":1,"1626":1,"1627":1,"1632":1,"1639":4,"1645":1,"1653":1,"1664":1,"1670":1,"1685":1,"1686":2,"1764":1,"1810":1,"1821":1,"1825":1,"1843":1,"1855":1,"1891":1,"1910":1,"1916":1,"1923":1,"1929":1,"2001":2,"2002":1,"2034":1,"2091":1,"2098":1,"2118":1,"2127":1,"2141":1,"2142":1,"2146":1,"2158":1,"2167":1,"2168":1,"2174":1,"2183":2,"2189":1}}],["coverage",{"0":{"138":1,"1496":1,"1499":1,"1643":1,"2058":1},"1":{"1495":1,"1496":1,"1497":1,"1498":1,"1499":1,"1500":1,"1501":1},"2":{"1455":1,"1461":1},"3":{"0":7,"67":1,"71":1,"74":1,"115":1,"122":1,"127":2,"130":1,"132":1,"138":2,"265":5,"267":3,"269":1,"305":1,"312":1,"361":1,"365":1,"373":1,"547":1,"551":1,"566":1,"572":1,"574":1,"593":1,"607":4,"608":1,"609":1,"619":1,"620":1,"717":1,"729":1,"782":1,"837":1,"840":1,"863":2,"874":1,"891":1,"892":2,"914":2,"918":1,"982":1,"990":1,"1020":1,"1037":1,"1066":1,"1076":1,"1085":1,"1192":2,"1194":1,"1212":2,"1213":1,"1216":2,"1237":1,"1270":1,"1271":1,"1281":1,"1286":3,"1300":9,"1311":15,"1339":1,"1350":2,"1369":2,"1395":2,"1396":1,"1415":1,"1417":1,"1434":1,"1436":63,"1438":4,"1453":4,"1455":20,"1456":2,"1459":2,"1460":1,"1461":2,"1463":1,"1467":2,"1469":1,"1486":5,"1487":1,"1488":2,"1489":2,"1490":2,"1492":10,"1493":1,"1495":1,"1496":13,"1497":1,"1500":1,"1525":1,"1526":3,"1534":3,"1535":1,"1551":1,"1565":2,"1576":2,"1577":8,"1581":1,"1582":1,"1586":1,"1591":6,"1595":1,"1596":1,"1597":1,"1600":1,"1611":3,"1642":1,"1643":1,"1661":1,"1671":1,"1673":1,"1799":1,"1891":1,"1964":1,"2034":1,"2048":1,"2050":2,"2058":4,"2059":2,"2060":2,"2085":2,"2158":1,"2220":2}}],["coercing",{"3":{"1237":1}}],["coerced",{"3":{"1237":1,"1286":1,"1300":1,"1311":1}}],["coerces",{"3":{"1236":1,"1237":1,"1259":2}}],["coexisting",{"3":{"2079":1}}],["coexisted",{"3":{"1453":1}}],["coexists",{"3":{"1402":1}}],["coexist",{"3":{"0":1,"59":1,"111":1,"293":1,"448":2,"1052":1,"1229":1,"1300":1,"1304":1,"1323":1,"1359":1,"1396":1,"1416":1,"1436":1,"1545":1,"1806":1,"1816":1,"2129":1,"2131":2,"2133":1,"2139":1}}],["coined",{"3":{"1415":2}}],["coining",{"3":{"1409":1,"1415":1}}],["coincides",{"3":{"1380":1}}],["coincide",{"3":{"1324":1}}],["coincidentally",{"3":{"1300":1,"1311":1}}],["coin",{"3":{"1126":1,"1907":1,"2029":1}}],["cohesion",{"3":{"1191":1,"1200":1,"1314":1,"1323":1,"1359":3,"1396":2,"1415":1,"1436":12,"1497":1,"1517":1,"1525":1,"1526":1,"1534":1,"1535":1,"1571":1,"1577":4,"1582":1,"1586":1,"1591":1,"1671":1,"1932":1,"1941":1,"2048":1,"2094":1}}],["cohesive",{"3":{"1003":1,"1201":1,"1212":1,"1300":2,"1323":1,"1352":1,"1395":1,"1396":1,"1416":1,"1436":5,"1496":1,"1526":2,"1534":1,"1542":2,"1555":1,"1572":1,"1591":1,"1941":1,"2230":1}}],["coherence",{"3":{"302":1,"896":1,"1311":6,"1324":1,"1923":1,"2138":1,"2156":1}}],["coherently",{"3":{"1359":1,"1417":1}}],["coherent",{"0":{"2010":1},"3":{"0":1,"98":1,"369":1,"398":1,"1270":2,"1301":1,"1324":7,"1339":1,"1359":1,"1381":1,"1395":1,"1415":1,"1416":2,"1417":6,"1436":2,"1454":1,"1557":1,"1571":1,"1916":1,"1924":1,"2025":1,"2112":1,"2232":1}}],["codified",{"3":{"1247":1,"1339":1}}],["coding",{"3":{"243":1,"254":1,"1242":1,"1247":1,"1300":1,"1311":2,"1324":1,"1341":1,"1350":2,"1359":2,"1380":1,"1395":2,"1396":1,"1415":3,"1417":1,"1436":4,"1475":1,"1570":1,"1916":1}}],["codeowners",{"3":{"1673":1}}],["codebehinds",{"3":{"1436":3}}],["codebases",{"0":{"1503":1},"3":{"1211":1,"1212":1,"1300":1,"1359":1,"1500":1,"1782":1,"1878":1,"2039":1,"2042":1,"2055":1,"2170":1}}],["codebase",{"0":{"1212":1,"2230":1},"3":{"131":1,"151":1,"469":1,"725":1,"800":1,"801":1,"804":1,"805":1,"1052":1,"1124":1,"1140":1,"1190":1,"1210":1,"1212":1,"1214":3,"1216":1,"1217":1,"1218":1,"1221":1,"1229":7,"1231":1,"1236":1,"1237":1,"1247":3,"1248":1,"1259":1,"1270":6,"1271":3,"1272":1,"1281":1,"1286":7,"1300":13,"1301":3,"1302":1,"1309":2,"1310":2,"1311":2,"1312":1,"1323":4,"1324":5,"1329":1,"1339":8,"1340":1,"1350":2,"1351":1,"1359":18,"1363":1,"1368":1,"1369":6,"1380":7,"1395":3,"1396":7,"1402":4,"1410":1,"1415":2,"1416":4,"1417":4,"1427":1,"1428":1,"1431":2,"1436":14,"1485":1,"1489":1,"1491":1,"1492":1,"1536":1,"1552":1,"1661":1,"1674":1,"1786":1,"1794":1,"1805":1,"1828":1,"1830":1,"1831":1,"1848":1,"1854":1,"1927":1,"1960":1,"1971":1,"2016":1,"2049":1,"2050":1,"2075":1,"2087":1,"2099":1,"2139":1,"2146":1,"2230":1,"2233":1}}],["codereusedacrossbranchesofonetype",{"2":{"1436":1},"3":{"1436":2}}],["codepath",{"3":{"1395":1}}],["codecoverage",{"2":{"1611":1},"3":{"1611":1}}],["codec",{"3":{"1228":1,"1312":2,"1395":3,"1438":1}}],["code=true",{"3":{"1453":1}}],["code=false",{"3":{"1453":1}}],["code=",{"3":{"423":1,"1416":1,"1417":1,"1975":1}}],["codesignprovision",{"3":{"1481":1}}],["codesignentitlements",{"2":{"1481":1},"3":{"1481":1}}],["codes",{"3":{"0":4,"201":1,"265":1,"266":1,"341":1,"343":1,"419":2,"538":1,"576":1,"609":1,"759":1,"790":1,"794":1,"1028":1,"1058":1,"1059":1,"1212":1,"1217":1,"1229":2,"1235":1,"1237":3,"1259":1,"1263":1,"1270":2,"1271":9,"1286":2,"1293":6,"1300":24,"1309":1,"1311":10,"1312":1,"1323":9,"1324":6,"1337":1,"1339":2,"1344":1,"1350":4,"1359":31,"1375":1,"1380":4,"1395":3,"1396":16,"1402":1,"1404":1,"1415":6,"1436":28,"1438":1,"1472":1,"1489":1,"1496":1,"1546":1,"1577":2,"1582":2,"1583":1,"1591":1,"1596":1,"1639":1,"1641":6,"1666":1,"1683":1,"1685":1,"1686":3,"1727":1,"1748":1,"1753":1,"1925":1,"1930":1,"1977":1,"2076":1,"2118":1,"2158":1,"2164":1,"2166":1,"2167":1,"2198":3,"2202":1,"2232":1}}],["coded",{"1":{"787":1,"788":1,"789":1,"790":1,"791":1,"792":1,"793":1,"794":1,"795":1},"3":{"0":1,"32":1,"88":1,"109":2,"117":1,"173":1,"265":3,"447":1,"787":1,"790":1,"794":1,"827":1,"861":1,"1270":1,"1271":2,"1286":3,"1300":6,"1301":6,"1309":2,"1311":6,"1312":3,"1323":2,"1324":9,"1339":4,"1350":2,"1359":21,"1369":2,"1395":2,"1396":3,"1402":3,"1427":1,"1436":9,"1438":1,"1465":1,"1480":1,"1488":1,"1489":2,"1526":2,"1547":1,"1564":3,"1577":2,"1591":2,"1596":1,"1597":2,"1653":3,"1654":1,"1666":1,"1702":1,"1821":1,"1886":1,"1927":1,"2083":1,"2130":1,"2169":1,"2203":1}}],["code",{"0":{"720":1,"1214":1,"1552":1,"1613":1,"1791":1,"1842":1,"1989":1,"2100":1},"1":{"606":1,"607":1,"608":1,"609":1,"610":1,"611":1,"612":1,"613":1,"614":1,"1462":1,"1463":1,"1464":1,"1465":1,"1466":1,"1467":1,"1468":1,"1469":1,"1470":1,"2134":1,"2135":1,"2136":1,"2137":1,"2138":1,"2139":1},"2":{"107":1,"264":1,"266":1,"269":1,"276":1,"301":1,"562":1,"564":1,"567":1,"604":1,"626":1,"660":1,"1459":1,"1976":1,"2086":1},"3":{"0":32,"1":1,"6":1,"21":1,"24":1,"27":2,"32":1,"51":1,"52":1,"53":1,"57":2,"59":2,"60":1,"63":2,"74":1,"93":1,"107":2,"109":11,"110":1,"111":1,"118":1,"122":1,"125":1,"128":2,"135":4,"136":3,"145":1,"149":1,"151":1,"157":3,"160":1,"165":1,"167":2,"171":2,"177":1,"180":2,"186":1,"188":1,"190":1,"217":1,"220":1,"224":1,"225":1,"226":1,"230":1,"242":1,"243":3,"244":1,"248":1,"249":2,"258":2,"264":1,"265":6,"266":2,"267":1,"268":1,"269":1,"276":1,"285":1,"290":1,"292":1,"299":1,"300":1,"301":2,"305":1,"306":2,"310":3,"316":1,"317":1,"322":1,"325":1,"326":1,"328":1,"335":1,"341":2,"342":1,"343":2,"350":8,"351":3,"352":2,"353":1,"354":1,"359":2,"360":1,"365":1,"370":1,"373":1,"381":1,"383":2,"391":1,"397":1,"401":1,"409":1,"412":1,"415":1,"418":1,"419":1,"420":5,"421":2,"422":1,"423":2,"424":1,"425":1,"426":1,"428":2,"433":1,"434":1,"435":1,"448":1,"451":1,"452":1,"459":2,"465":1,"472":1,"481":1,"482":3,"483":2,"486":1,"488":1,"489":2,"490":5,"491":2,"492":2,"493":3,"494":1,"495":1,"497":3,"502":2,"506":1,"507":1,"508":1,"511":1,"517":1,"518":1,"530":1,"536":1,"537":1,"540":1,"543":2,"545":4,"546":2,"549":1,"550":1,"555":1,"557":1,"560":1,"562":1,"563":1,"564":2,"565":1,"566":1,"567":1,"573":1,"582":2,"583":2,"584":2,"585":1,"586":1,"590":1,"591":1,"592":2,"593":1,"599":2,"604":2,"606":1,"609":1,"617":2,"618":1,"626":5,"633":2,"635":1,"638":2,"639":1,"640":5,"642":2,"649":1,"657":8,"658":1,"659":1,"660":2,"673":1,"674":4,"676":1,"681":2,"682":1,"683":2,"684":1,"689":2,"691":3,"692":2,"698":1,"700":2,"708":2,"713":1,"716":1,"725":1,"732":1,"733":2,"735":1,"736":1,"741":4,"742":1,"744":1,"749":2,"751":1,"757":4,"758":1,"759":1,"765":1,"766":2,"789":3,"790":4,"791":1,"794":1,"799":1,"800":1,"811":1,"812":1,"819":1,"825":1,"827":2,"829":1,"831":1,"832":2,"833":2,"836":1,"837":2,"839":2,"840":3,"845":1,"846":1,"856":1,"873":2,"876":1,"877":2,"880":1,"882":1,"884":1,"897":2,"905":2,"909":1,"914":1,"916":1,"932":1,"933":1,"934":1,"939":2,"940":1,"946":2,"948":2,"953":1,"963":1,"964":4,"971":1,"973":1,"976":2,"979":1,"980":1,"988":3,"990":2,"999":1,"1003":1,"1004":5,"1005":2,"1014":1,"1018":3,"1019":6,"1021":1,"1029":1,"1033":1,"1040":1,"1043":1,"1045":1,"1051":1,"1058":2,"1059":4,"1060":3,"1066":1,"1068":1,"1074":1,"1075":1,"1082":1,"1091":3,"1092":2,"1093":1,"1095":1,"1100":1,"1108":1,"1124":1,"1126":1,"1132":1,"1133":1,"1134":1,"1135":1,"1140":1,"1150":1,"1161":4,"1163":1,"1186":1,"1190":5,"1192":1,"1193":2,"1212":25,"1214":2,"1216":3,"1217":1,"1219":2,"1220":1,"1221":2,"1225":1,"1229":21,"1231":1,"1234":3,"1235":1,"1237":45,"1241":1,"1247":19,"1248":1,"1257":2,"1258":1,"1259":23,"1263":4,"1265":2,"1270":30,"1271":53,"1275":1,"1282":1,"1286":82,"1289":3,"1293":7,"1296":1,"1300":165,"1301":24,"1302":1,"1303":1,"1305":2,"1308":1,"1309":26,"1310":10,"1311":126,"1312":26,"1313":2,"1314":1,"1315":1,"1320":1,"1322":4,"1323":97,"1324":93,"1325":3,"1329":1,"1333":1,"1338":2,"1339":36,"1342":2,"1343":1,"1344":3,"1345":1,"1347":4,"1348":1,"1349":1,"1350":41,"1352":2,"1353":5,"1355":2,"1358":3,"1359":306,"1364":2,"1365":7,"1369":19,"1370":2,"1371":2,"1375":1,"1377":2,"1379":1,"1380":38,"1382":3,"1384":2,"1385":2,"1388":2,"1389":1,"1394":1,"1395":192,"1396":169,"1397":1,"1399":2,"1402":70,"1403":1,"1406":1,"1407":1,"1409":1,"1412":1,"1413":1,"1414":1,"1415":87,"1416":13,"1417":64,"1424":1,"1427":7,"1429":1,"1431":7,"1436":223,"1437":1,"1438":7,"1441":3,"1443":1,"1445":2,"1446":2,"1447":1,"1449":2,"1450":1,"1451":1,"1452":2,"1453":10,"1455":30,"1459":20,"1460":2,"1461":3,"1462":2,"1463":4,"1467":21,"1474":3,"1475":4,"1476":1,"1477":1,"1481":1,"1482":1,"1486":3,"1487":1,"1488":2,"1489":9,"1491":2,"1492":17,"1496":34,"1497":1,"1499":2,"1500":1,"1509":3,"1515":1,"1516":1,"1518":1,"1521":1,"1524":3,"1525":1,"1526":10,"1530":2,"1531":5,"1532":3,"1533":1,"1534":3,"1535":5,"1537":1,"1538":2,"1539":1,"1542":1,"1547":1,"1548":1,"1552":2,"1553":1,"1554":1,"1555":1,"1560":1,"1571":3,"1572":1,"1574":1,"1577":11,"1581":1,"1582":1,"1586":2,"1589":1,"1591":11,"1595":1,"1596":3,"1597":2,"1598":1,"1600":4,"1602":1,"1626":2,"1627":1,"1630":2,"1631":3,"1640":1,"1641":2,"1646":1,"1651":2,"1653":3,"1656":3,"1658":1,"1661":2,"1662":1,"1663":2,"1666":3,"1669":1,"1671":3,"1673":2,"1676":1,"1681":2,"1683":1,"1685":4,"1686":5,"1688":1,"1700":1,"1702":1,"1703":1,"1705":1,"1708":1,"1720":1,"1724":3,"1725":1,"1726":1,"1727":2,"1755":1,"1764":1,"1770":1,"1777":1,"1781":1,"1782":1,"1783":2,"1784":2,"1789":1,"1790":5,"1793":2,"1794":1,"1796":1,"1800":1,"1802":1,"1805":5,"1806":1,"1810":2,"1811":1,"1815":4,"1817":2,"1819":2,"1822":1,"1826":1,"1827":1,"1831":3,"1842":3,"1847":2,"1849":3,"1850":1,"1853":4,"1858":1,"1864":1,"1865":1,"1866":1,"1868":2,"1871":1,"1875":1,"1877":1,"1878":2,"1881":1,"1884":1,"1888":1,"1893":1,"1896":1,"1898":1,"1899":2,"1900":1,"1902":1,"1904":1,"1908":2,"1909":3,"1911":1,"1915":2,"1916":2,"1920":1,"1923":2,"1924":1,"1927":2,"1938":1,"1944":3,"1948":1,"1950":1,"1952":2,"1955":3,"1956":1,"1957":1,"1958":1,"1959":2,"1962":1,"1964":1,"1972":1,"1973":1,"1975":8,"1976":6,"1977":1,"1978":2,"1979":4,"2000":2,"2001":2,"2002":1,"2003":1,"2010":2,"2013":3,"2014":1,"2015":2,"2016":2,"2017":1,"2020":2,"2024":1,"2026":2,"2028":1,"2034":1,"2036":3,"2038":2,"2040":1,"2041":1,"2043":6,"2044":2,"2045":3,"2046":1,"2047":1,"2051":1,"2055":1,"2058":2,"2063":1,"2070":1,"2075":1,"2078":2,"2083":8,"2086":3,"2087":2,"2088":1,"2096":1,"2097":3,"2098":1,"2099":2,"2100":1,"2108":2,"2111":2,"2112":2,"2116":1,"2121":2,"2123":1,"2124":1,"2129":1,"2133":3,"2134":2,"2135":2,"2136":1,"2137":1,"2142":2,"2146":3,"2149":1,"2154":1,"2156":1,"2158":2,"2160":2,"2166":2,"2169":4,"2171":1,"2178":1,"2179":1,"2180":3,"2184":2,"2196":3,"2197":2,"2199":2,"2200":2,"2202":2,"2203":2,"2206":1,"2213":1,"2219":2,"2220":4,"2221":1,"2225":3,"2227":1,"2230":2,"2231":1,"2232":6,"2234":2,"2239":2,"2243":1}}],["codeanalysistreatwarningsaserrors",{"2":{"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"981":1},"3":{"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"981":1}}],["codeanalysis",{"2":{"0":1,"135":1,"137":1,"138":1,"1229":1,"1270":1,"1324":1,"1415":1,"2100":1},"3":{"0":1,"135":2,"137":1,"138":1,"1229":1,"1270":2,"1300":3,"1324":4,"1396":1,"1415":1,"1436":1,"2100":1}}],["coached",{"3":{"2220":1}}],["coalesced",{"3":{"1286":1}}],["coalesces",{"3":{"1100":1,"1286":2,"1359":1,"1436":1}}],["coalesce",{"3":{"518":1,"1300":2,"1323":1,"1324":1,"1380":1,"1396":2,"1415":1}}],["coalescing",{"3":{"0":1,"519":1,"1229":1,"1237":1,"1286":1,"1311":1,"1323":2,"1324":3,"1350":1,"1415":2,"1436":1}}],["coarseness",{"3":{"2002":1}}],["coarselocationonly",{"3":{"1417":2}}],["coarse",{"2":{"426":1},"3":{"177":1,"178":2,"185":1,"283":1,"426":1,"572":1,"574":1,"872":1,"1177":1,"1300":1,"1350":2,"1396":1,"1402":1,"1415":1,"1417":7,"1431":1,"1436":3,"1537":1,"1591":1,"1596":1,"1597":1,"1876":1,"2001":3}}],["coarser",{"3":{"60":1,"784":1,"1311":1,"1417":1}}],["copilot",{"3":{"2213":1,"2219":1,"2220":2,"2224":1,"2225":1}}],["copied",{"3":{"0":1,"24":2,"27":1,"110":1,"216":1,"359":1,"450":1,"638":1,"640":1,"658":2,"698":1,"715":1,"750":1,"791":1,"836":1,"955":1,"972":3,"974":1,"1018":2,"1067":1,"1107":1,"1167":1,"1212":1,"1255":1,"1259":4,"1271":1,"1278":1,"1286":4,"1300":1,"1309":1,"1311":1,"1322":1,"1324":4,"1339":5,"1350":4,"1359":5,"1369":1,"1395":3,"1398":1,"1402":2,"1415":3,"1416":2,"1417":4,"1427":1,"1430":1,"1435":2,"1436":2,"1438":1,"1445":1,"1455":1,"1467":1,"1473":1,"1528":1,"1579":1,"1591":1,"1630":1,"1656":1,"1781":1,"1813":1,"1817":1,"1828":1,"1991":1,"2011":1,"2043":2,"2088":1,"2129":1,"2132":1,"2142":1,"2148":1}}],["copies",{"3":{"0":7,"94":2,"95":1,"145":1,"219":1,"325":1,"418":1,"497":1,"511":3,"600":1,"624":1,"633":1,"707":1,"708":1,"721":1,"729":1,"831":2,"832":2,"872":2,"880":1,"882":1,"884":1,"935":1,"954":1,"962":1,"1024":1,"1025":1,"1030":1,"1053":1,"1091":1,"1142":1,"1152":1,"1168":1,"1219":1,"1229":1,"1233":1,"1237":3,"1247":2,"1259":4,"1270":3,"1271":2,"1286":11,"1300":4,"1301":2,"1310":1,"1311":4,"1312":2,"1322":1,"1323":11,"1324":9,"1336":1,"1339":8,"1359":16,"1380":2,"1384":1,"1395":26,"1396":15,"1402":1,"1415":13,"1417":7,"1425":1,"1426":1,"1427":4,"1436":6,"1438":2,"1443":1,"1445":6,"1459":1,"1460":1,"1471":1,"1475":3,"1483":1,"1496":8,"1686":1,"1894":1,"1923":1,"1948":1,"2019":1,"2042":3,"2070":2,"2124":1,"2148":1,"2176":1,"2232":1}}],["cope",{"3":{"1359":3}}],["copes",{"3":{"1324":1,"1359":1}}],["copytext",{"3":{"1417":3}}],["copywifiasync",{"3":{"1395":1}}],["copyonly",{"2":{"635":1,"1729":1},"3":{"635":1,"1729":1}}],["copying",{"3":{"132":1,"698":1,"758":1,"906":1,"1170":1,"1229":2,"1251":1,"1286":3,"1300":4,"1311":1,"1323":2,"1324":1,"1339":2,"1350":2,"1359":5,"1395":1,"1396":1,"1399":1,"1402":1,"1413":1,"1415":2,"1435":1,"1436":3,"1445":2,"1476":1,"1577":1,"1781":1,"2032":1,"2190":1,"2203":1}}],["copy",{"1":{"1828":1,"1829":1,"1830":1,"1831":1,"1832":1,"1833":1,"1834":1},"2":{"563":1,"759":1,"868":1},"3":{"0":10,"122":1,"123":1,"215":1,"219":1,"220":1,"246":1,"290":1,"296":1,"318":1,"320":1,"325":1,"359":1,"365":1,"374":1,"390":1,"451":1,"460":1,"511":1,"556":1,"563":1,"566":1,"598":1,"601":2,"610":1,"611":1,"632":3,"633":2,"634":1,"640":1,"650":1,"674":2,"690":1,"707":1,"724":2,"725":1,"733":4,"735":2,"750":1,"757":1,"759":1,"782":1,"784":1,"790":1,"792":1,"813":1,"821":1,"826":1,"827":3,"829":2,"832":2,"834":1,"847":1,"854":1,"855":1,"856":1,"868":3,"869":1,"872":2,"926":1,"973":2,"1019":1,"1058":1,"1065":1,"1066":1,"1068":2,"1091":2,"1107":1,"1122":1,"1124":1,"1126":1,"1167":2,"1184":2,"1185":1,"1212":2,"1229":4,"1231":1,"1236":1,"1237":3,"1239":1,"1247":4,"1254":1,"1259":3,"1267":1,"1270":3,"1271":7,"1272":1,"1274":1,"1275":1,"1277":1,"1286":19,"1300":20,"1301":11,"1302":1,"1309":4,"1311":15,"1323":13,"1324":25,"1329":1,"1337":1,"1338":1,"1339":11,"1350":5,"1355":1,"1356":1,"1359":30,"1373":1,"1380":3,"1384":1,"1393":1,"1395":29,"1396":35,"1402":4,"1415":16,"1416":2,"1417":24,"1426":2,"1427":2,"1431":1,"1436":19,"1438":3,"1442":1,"1443":2,"1445":3,"1453":2,"1455":4,"1459":2,"1460":4,"1461":1,"1467":2,"1468":2,"1473":2,"1474":5,"1475":8,"1476":1,"1478":2,"1482":2,"1484":1,"1488":1,"1489":1,"1492":3,"1496":4,"1501":1,"1526":1,"1555":1,"1627":1,"1631":1,"1641":1,"1649":2,"1650":1,"1653":1,"1654":1,"1658":1,"1664":1,"1672":1,"1673":1,"1684":1,"1685":1,"1703":1,"1705":1,"1708":1,"1710":3,"1724":1,"1728":1,"1730":1,"1731":1,"1751":1,"1761":1,"1764":1,"1765":1,"1769":1,"1779":1,"1782":1,"1810":1,"1813":1,"1818":1,"1821":1,"1827":1,"1828":2,"1833":2,"1834":3,"1839":1,"1851":1,"1877":1,"1893":2,"1896":1,"1920":1,"1922":1,"1923":1,"1924":1,"1983":1,"2000":1,"2006":1,"2010":1,"2014":1,"2024":1,"2027":1,"2034":3,"2056":1,"2060":1,"2070":1,"2072":1,"2080":1,"2097":1,"2101":1,"2111":1,"2116":1,"2117":2,"2119":1,"2134":1,"2135":1,"2139":1,"2143":1,"2147":2,"2148":1,"2150":1,"2152":1,"2153":1,"2156":1,"2166":1,"2176":2,"2206":1,"2232":2,"2236":1}}],["co",{"3":{"0":1,"60":1,"265":1,"266":1,"1004":1,"1116":1,"1237":1,"1247":2,"1270":2,"1271":1,"1286":1,"1309":3,"1311":1,"1323":1,"1324":4,"1331":1,"1339":2,"1342":1,"1344":1,"1350":2,"1352":1,"1357":1,"1359":3,"1370":1,"1374":1,"1377":1,"1378":2,"1380":1,"1394":1,"1395":7,"1396":4,"1402":2,"1405":1,"1415":1,"1416":4,"1431":1,"1436":12,"1441":1,"1577":3,"1591":1,"1648":1,"1651":1,"1653":1,"1655":1,"1856":1,"1865":1,"1867":1,"2043":1,"2083":1,"2113":1}}],["cooperation",{"3":{"1286":1}}],["cooperative",{"3":{"1270":1}}],["cooperating",{"3":{"352":1,"1323":1,"1334":1,"1761":1,"1815":1,"1849":1,"1935":1,"1978":1}}],["cooperate",{"3":{"1238":1,"1259":1,"1350":1,"1415":1}}],["cooldown",{"3":{"344":1,"1639":2}}],["coordinating",{"3":{"1286":1,"2044":1}}],["coordination",{"3":{"0":1,"170":1,"810":1,"811":1,"1286":2,"1309":1,"1324":2,"1339":1,"1359":1,"1849":1}}],["coordinate",{"3":{"1259":1,"1359":1,"1417":4,"1436":2,"2052":1,"2107":1}}],["coordinates",{"3":{"255":1,"439":1,"809":1,"810":1,"883":1,"1286":5,"1323":2,"1324":1,"1340":1,"1350":1,"1417":8,"1459":1,"1467":1,"1472":1,"1923":1,"2124":2,"2125":1}}],["coordinated",{"3":{"30":1,"1011":1,"1272":1,"1300":1,"1301":1,"1309":1,"1323":1,"1324":2,"1436":2,"1526":1,"1569":1,"1572":1,"1576":1,"1577":1,"1894":1,"2238":1,"2240":1}}],["coordinator",{"3":{"0":3,"808":1,"809":3,"810":7,"811":1,"812":1,"813":1,"814":1,"815":2,"1147":1,"1212":1,"1278":2,"1280":1,"1286":2,"1346":1,"1396":11,"1438":1,"1849":1,"2168":2}}],["cookiename",{"3":{"1436":1}}],["cookieoptions",{"3":{"1300":1}}],["cookietokenreadertests",{"3":{"1199":1,"1207":2,"1300":1}}],["cookietokenreader",{"2":{"1298":1,"1968":1},"3":{"1199":9,"1203":1,"1207":2,"1298":5,"1300":9,"1311":3,"1324":5,"1968":1}}],["cookierequestcultureprovider",{"3":{"265":1,"1417":1}}],["cookiesessionrefreshmiddlewaretests",{"3":{"1199":1,"1207":3,"1300":2}}],["cookiesessionrefreshmiddlewareextensions",{"2":{"1298":1},"3":{"1199":2,"1203":1,"1207":1,"1298":1,"1300":2}}],["cookiesessionrefreshmiddleware",{"2":{"1298":1,"1968":1},"3":{"1199":3,"1203":1,"1207":3,"1208":1,"1298":4,"1300":8,"1582":1,"1968":1}}],["cookiesessionrefreshertests",{"3":{"1199":1,"1207":5,"1300":1}}],["cookiesessionrefresher",{"2":{"207":1,"1298":1,"1968":1},"3":{"207":1,"1183":1,"1184":1,"1185":1,"1199":4,"1203":1,"1207":5,"1287":1,"1298":17,"1300":26,"1311":3,"1582":1,"1968":1}}],["cookies",{"0":{"1298":1,"1968":1},"3":{"0":4,"207":3,"208":1,"209":1,"349":1,"351":1,"355":1,"507":1,"558":1,"664":1,"667":1,"670":1,"1108":1,"1109":1,"1183":1,"1184":5,"1185":1,"1212":2,"1298":2,"1300":31,"1311":1,"1324":35,"1336":2,"1339":4,"1436":2,"1438":3,"1442":1,"1467":1,"1496":1,"1511":1,"1526":1,"1577":1,"1660":1,"1968":4,"1969":2,"1970":1,"1971":1,"1972":2,"1974":1,"2232":2}}],["cookie",{"1":{"204":1,"205":1,"206":1,"207":1,"208":1,"209":1,"210":1,"1966":1,"1967":1,"1968":1,"1969":1,"1970":1,"1971":1,"1972":1,"2081":1,"2082":1,"2083":1,"2084":1,"2085":1,"2086":1},"3":{"0":11,"204":1,"207":6,"208":3,"209":5,"221":1,"265":11,"266":2,"269":2,"272":1,"273":9,"274":1,"277":2,"287":1,"350":4,"351":1,"352":1,"388":1,"458":2,"462":1,"506":2,"507":11,"508":4,"509":1,"510":1,"514":3,"555":2,"556":2,"557":1,"558":1,"560":1,"616":1,"618":1,"664":3,"665":3,"668":1,"774":1,"827":1,"1107":1,"1108":2,"1111":1,"1182":1,"1183":4,"1184":9,"1185":3,"1186":1,"1187":4,"1188":2,"1189":1,"1192":1,"1202":1,"1203":1,"1212":3,"1287":2,"1298":6,"1300":66,"1311":19,"1323":1,"1324":114,"1336":1,"1339":3,"1395":3,"1406":1,"1415":4,"1416":4,"1417":3,"1433":1,"1434":1,"1436":25,"1438":6,"1442":2,"1467":3,"1487":1,"1488":1,"1496":4,"1513":2,"1525":1,"1526":2,"1531":1,"1534":4,"1535":1,"1577":3,"1582":1,"1586":1,"1590":1,"1591":3,"1639":1,"1641":11,"1643":1,"1660":1,"1667":3,"1668":1,"1669":3,"1759":1,"1761":2,"1779":1,"1922":1,"1965":1,"1966":2,"1967":2,"1968":7,"1969":2,"1971":5,"1972":7,"1975":6,"1977":1,"1979":1,"2080":1,"2081":3,"2082":3,"2083":9,"2084":1,"2085":4,"2086":5,"2208":1,"2210":1,"2232":4}}],["cosmetic",{"3":{"202":1,"311":1,"926":1,"1123":1,"1241":1,"1247":1,"1271":1,"1286":3,"1310":1,"1323":1,"1324":1,"1327":1,"1339":1,"1347":1,"1359":3,"1384":1,"1391":1,"1395":2,"1396":2,"1415":1,"1417":1,"1436":2,"1496":1,"1879":1,"1926":1,"2120":1}}],["cosmosindexedentity",{"3":{"1199":3,"1207":1}}],["cosmosintidvaluegeneratortests",{"3":{"1199":1,"1207":2,"1286":2}}],["cosmosintidvaluegenerator",{"2":{"166":1,"1664":1,"1856":1},"3":{"166":2,"1199":3,"1203":1,"1207":2,"1282":2,"1286":14,"1664":1,"1856":1}}],["cosmosdatasourceengine",{"2":{"1280":1,"1286":1},"3":{"1175":2,"1177":1,"1198":1,"1199":2,"1203":1,"1207":2,"1280":5,"1282":2,"1286":18,"1300":1}}],["cosmosdatabasenameof",{"3":{"1286":1}}],["cosmosdatabasename",{"3":{"166":1,"1286":12,"1339":1,"1441":1}}],["cosmosdb",{"2":{"170":1,"889":1,"1174":1,"1856":1,"1857":1},"3":{"166":1,"170":1,"889":1,"1034":1,"1174":1,"1213":1,"1286":12,"1323":1,"1436":1,"1856":1,"1857":1}}],["cosmosdbcontexttests",{"3":{"1199":1,"1207":2,"1286":2,"1438":2}}],["cosmosdbcontext",{"2":{"26":1,"47":1,"200":1,"343":1,"698":1,"701":1,"720":1,"1510":1,"1857":1,"1861":1},"3":{"26":1,"47":1,"166":3,"171":1,"200":2,"343":2,"345":1,"698":1,"701":1,"715":2,"720":1,"988":1,"1042":1,"1198":1,"1199":3,"1203":1,"1207":2,"1212":2,"1272":1,"1273":2,"1274":1,"1286":14,"1339":1,"1510":1,"1664":1,"1849":1,"1857":1,"1861":1}}],["cosmosconnectionstring",{"3":{"166":1,"1286":4,"1441":1}}],["cosmosconfigurationportabilitytests",{"2":{"164":1},"3":{"164":1,"1198":1,"1199":2,"1207":7,"1286":4,"1496":3}}],["cosmos",{"3":{"0":12,"10":3,"26":1,"45":1,"164":1,"166":7,"168":4,"169":1,"170":1,"171":2,"195":1,"200":2,"290":1,"296":1,"343":2,"345":1,"715":1,"717":1,"721":1,"889":1,"890":1,"891":2,"905":1,"931":1,"988":2,"990":2,"1004":1,"1033":2,"1036":1,"1042":1,"1045":1,"1050":1,"1083":2,"1085":1,"1133":1,"1134":1,"1135":1,"1144":1,"1175":3,"1176":1,"1177":2,"1179":1,"1212":9,"1213":2,"1237":1,"1239":1,"1247":6,"1252":1,"1259":4,"1273":1,"1274":1,"1276":1,"1279":3,"1280":2,"1281":5,"1282":4,"1286":97,"1300":1,"1310":1,"1311":4,"1317":1,"1320":1,"1321":1,"1323":4,"1326":1,"1339":2,"1359":1,"1361":1,"1369":2,"1380":1,"1396":2,"1415":1,"1436":5,"1438":3,"1441":4,"1489":1,"1496":1,"1500":3,"1510":1,"1526":2,"1577":1,"1585":2,"1752":1,"1763":1,"1774":1,"1815":1,"1840":1,"1849":1,"1853":1,"1854":4,"1855":1,"1856":3,"1857":1,"1858":5,"1859":3,"1860":4,"1861":5,"1862":1,"1864":1,"1876":1,"2064":1,"2213":1,"2219":2,"2220":1,"2230":1,"2232":1}}],["costcenter",{"3":{"1577":1,"1591":1}}],["costly",{"3":{"1436":1}}],["costtagconventiontests",{"3":{"1199":1,"1207":2,"1436":4,"1467":2,"1525":1,"1526":2,"1535":1,"1591":1}}],["costing",{"3":{"765":1,"856":1,"1300":1,"1323":1,"1324":1,"1396":1,"1438":1}}],["costs",{"0":{"1333":1},"3":{"0":23,"1":1,"24":2,"26":1,"41":1,"92":1,"95":1,"108":1,"111":1,"135":2,"136":1,"160":1,"195":1,"196":1,"233":1,"234":1,"237":2,"255":1,"267":2,"350":1,"360":2,"400":1,"401":1,"402":1,"435":2,"442":1,"448":1,"459":3,"461":1,"483":1,"529":1,"549":1,"551":1,"565":1,"619":1,"628":1,"635":1,"640":1,"659":1,"681":1,"683":1,"690":1,"691":1,"715":1,"726":2,"733":1,"735":1,"747":1,"751":2,"759":1,"767":2,"768":1,"775":1,"791":1,"792":1,"797":2,"827":2,"830":1,"853":1,"856":1,"872":1,"904":1,"906":2,"923":1,"931":1,"932":2,"946":1,"965":2,"982":1,"998":1,"1005":1,"1019":2,"1020":1,"1027":1,"1051":1,"1099":1,"1109":1,"1117":1,"1143":1,"1153":1,"1228":2,"1229":2,"1246":1,"1247":6,"1259":5,"1270":4,"1271":1,"1278":1,"1286":13,"1294":1,"1300":13,"1301":1,"1307":2,"1309":1,"1310":3,"1311":5,"1323":8,"1324":11,"1332":1,"1339":11,"1359":21,"1369":1,"1380":3,"1385":1,"1395":13,"1396":12,"1402":3,"1415":6,"1417":4,"1422":1,"1435":2,"1436":5,"1438":1,"1440":1,"1443":1,"1455":7,"1457":2,"1465":1,"1467":4,"1474":1,"1490":1,"1492":1,"1526":1,"1534":1,"1553":1,"1631":1,"1651":1,"1652":1,"1663":1,"1672":1,"1717":1,"1721":1,"1724":1,"1750":1,"1765":1,"1773":1,"1790":1,"1792":1,"1811":1,"1839":1,"1842":1,"1843":1,"1849":1,"1851":1,"1858":1,"1876":1,"1902":1,"1923":2,"1942":1,"1971":1,"1972":2,"1977":1,"1978":1,"1979":1,"1984":1,"1989":1,"1990":1,"2000":1,"2007":1,"2013":1,"2044":1,"2066":1,"2087":1,"2150":1,"2156":1,"2158":1,"2166":1,"2169":1,"2171":1,"2174":1,"2181":1,"2193":1,"2194":1,"2203":1,"2232":1,"2239":1}}],["cost",{"0":{"1228":1,"1425":1,"1457":1,"1568":1,"1677":1,"1679":1,"2047":1},"1":{"763":1,"764":1,"765":1,"766":1,"767":1,"768":1,"769":1,"770":1,"1676":1,"1677":1,"1678":1,"1679":1,"1680":1},"2":{"1467":1},"3":{"0":14,"1":1,"20":1,"26":1,"48":1,"58":1,"68":1,"86":1,"102":2,"111":1,"125":2,"137":2,"138":1,"139":1,"140":1,"150":1,"151":1,"160":1,"167":1,"168":2,"178":1,"200":1,"216":1,"235":1,"237":1,"245":2,"246":1,"267":2,"274":1,"279":1,"283":1,"284":1,"291":1,"305":1,"312":1,"331":1,"351":1,"353":1,"360":1,"372":1,"395":6,"396":2,"397":5,"398":2,"399":4,"400":1,"402":1,"403":2,"404":3,"405":1,"406":2,"407":1,"408":1,"409":2,"425":1,"426":1,"460":1,"469":1,"471":1,"472":1,"481":1,"511":1,"535":1,"541":1,"549":1,"551":1,"556":1,"566":1,"573":1,"590":1,"592":3,"593":2,"595":4,"601":1,"608":1,"609":1,"614":1,"619":1,"620":1,"625":3,"626":1,"627":2,"628":1,"635":1,"641":1,"650":1,"656":1,"658":1,"676":1,"684":1,"690":1,"691":1,"707":1,"708":2,"716":2,"725":1,"727":1,"732":1,"733":3,"742":1,"756":1,"763":1,"764":3,"765":2,"766":38,"768":11,"769":4,"798":1,"799":2,"800":1,"801":2,"811":1,"812":2,"820":2,"827":2,"828":1,"829":1,"834":1,"848":1,"855":1,"861":1,"862":1,"863":1,"865":2,"871":1,"882":1,"883":1,"891":1,"899":1,"907":1,"915":2,"917":1,"918":1,"926":1,"931":1,"933":1,"940":1,"948":2,"968":1,"971":1,"973":1,"974":1,"979":1,"981":1,"982":1,"989":1,"998":1,"1008":1,"1011":2,"1012":1,"1013":1,"1017":1,"1018":2,"1019":2,"1020":1,"1025":1,"1028":1,"1036":1,"1043":1,"1044":1,"1051":1,"1061":1,"1068":1,"1069":1,"1074":1,"1075":2,"1076":1,"1077":2,"1085":1,"1093":1,"1101":2,"1116":1,"1117":1,"1119":2,"1126":2,"1147":1,"1152":2,"1153":1,"1154":2,"1162":1,"1170":1,"1178":1,"1192":1,"1212":3,"1216":1,"1232":2,"1237":3,"1247":8,"1254":1,"1255":1,"1256":1,"1259":19,"1263":1,"1270":9,"1275":1,"1283":1,"1286":20,"1300":11,"1301":3,"1309":3,"1310":3,"1311":11,"1312":1,"1323":14,"1324":16,"1327":1,"1329":1,"1333":3,"1334":1,"1337":2,"1338":1,"1339":32,"1342":1,"1349":1,"1350":9,"1359":20,"1366":1,"1367":1,"1369":1,"1376":1,"1380":5,"1392":1,"1395":11,"1396":14,"1401":1,"1402":9,"1413":1,"1415":6,"1417":2,"1418":1,"1421":2,"1422":1,"1425":2,"1427":8,"1430":1,"1435":1,"1436":9,"1438":5,"1442":1,"1443":5,"1446":1,"1447":4,"1450":1,"1451":1,"1452":1,"1454":1,"1455":12,"1456":1,"1457":17,"1459":3,"1460":5,"1461":5,"1463":1,"1465":5,"1467":31,"1469":2,"1475":3,"1476":7,"1478":3,"1483":1,"1486":2,"1489":2,"1490":1,"1492":2,"1496":7,"1499":1,"1523":1,"1525":3,"1526":15,"1530":1,"1531":2,"1532":2,"1534":4,"1535":2,"1550":2,"1553":2,"1554":1,"1568":5,"1571":1,"1572":1,"1574":1,"1575":8,"1577":17,"1578":1,"1581":1,"1583":1,"1584":2,"1585":9,"1590":1,"1591":8,"1597":2,"1599":4,"1600":3,"1602":1,"1638":1,"1641":2,"1660":1,"1668":2,"1670":2,"1674":2,"1676":5,"1677":2,"1678":1,"1679":5,"1680":1,"1697":1,"1708":1,"1796":1,"1799":2,"1804":1,"1806":1,"1809":1,"1821":1,"1823":1,"1826":1,"1839":2,"1864":1,"1867":1,"1876":1,"1885":3,"1896":1,"1909":2,"1910":1,"1923":1,"1925":1,"1932":1,"1933":1,"1940":2,"1958":2,"1984":1,"1987":1,"1989":1,"2001":3,"2006":1,"2011":1,"2014":1,"2024":1,"2032":1,"2035":1,"2046":1,"2047":1,"2048":2,"2049":1,"2054":1,"2055":1,"2056":1,"2057":1,"2059":2,"2060":1,"2067":2,"2075":1,"2086":1,"2087":1,"2112":1,"2114":2,"2126":1,"2127":1,"2135":1,"2144":1,"2148":2,"2152":1,"2154":1,"2155":1,"2156":1,"2157":1,"2159":4,"2160":1,"2162":2,"2170":2,"2171":1,"2172":1,"2174":1,"2177":1,"2179":1,"2189":1,"2192":1,"2203":1,"2220":1,"2232":2,"2240":1}}],["col",{"3":{"1175":5,"1526":1}}],["colocating",{"3":{"1350":1}}],["colon",{"3":{"564":1,"1324":2,"1339":2,"1359":1,"1384":1,"1395":1,"1442":1}}],["coloured",{"3":{"1436":1}}],["colour",{"3":{"0":1,"756":1,"905":1,"910":1,"1324":1,"1396":1,"1436":1}}],["colored",{"3":{"2075":1}}],["colors",{"3":{"1324":6,"1385":1,"1395":2,"1416":1,"1480":2,"1577":1,"1582":1,"2075":2}}],["color",{"3":{"0":1,"273":2,"1311":1,"1324":16,"1385":3,"1395":11,"1436":1,"1489":1,"1558":2,"1591":1,"1600":1,"1644":1,"1669":1,"1671":1,"2075":3,"2079":1,"2081":1,"2083":1,"2086":1,"2107":1}}],["colder",{"3":{"1417":3}}],["cold",{"0":{"1334":1},"1":{"232":1,"233":1,"234":1,"235":1,"236":1,"237":1,"238":1,"239":1},"3":{"0":2,"25":1,"31":1,"33":1,"100":1,"232":1,"234":1,"236":4,"239":1,"246":1,"390":1,"413":1,"556":2,"559":1,"717":1,"733":1,"735":1,"762":1,"867":1,"869":1,"875":1,"881":1,"1059":1,"1067":1,"1069":1,"1270":1,"1286":1,"1287":1,"1288":1,"1298":2,"1300":2,"1301":1,"1304":1,"1309":1,"1324":6,"1339":8,"1359":1,"1379":1,"1380":3,"1393":1,"1395":2,"1396":3,"1415":3,"1416":8,"1417":11,"1430":1,"1436":2,"1438":1,"1441":1,"1443":3,"1445":1,"1447":1,"1449":2,"1450":1,"1456":2,"1467":4,"1490":3,"1534":1,"1577":1,"1668":1,"1669":1,"1707":1,"1800":1,"1934":1,"2007":3,"2121":1,"2199":1,"2203":2}}],["colleague",{"3":{"1116":1,"1342":1,"1350":1,"1357":1,"1359":1}}],["collectively",{"3":{"1461":1}}],["collecting",{"3":{"1247":1,"1253":1,"1271":1,"1324":1,"1339":2,"1359":1,"1435":1,"1436":8,"1455":1}}],["collectionincludecache",{"3":{"1286":1}}],["collectiondefinition",{"3":{"640":1,"1339":2,"1436":4,"1438":1,"1488":1}}],["collectionresulttests",{"3":{"1199":1,"1207":2}}],["collectionresult",{"2":{"1217":1,"1227":1,"1228":1,"1805":1},"3":{"549":1,"1199":34,"1203":1,"1207":1,"1217":1,"1227":1,"1228":1,"1229":10,"1311":4,"1324":3,"1371":1,"1380":3,"1382":1,"1395":2,"1805":1,"1988":1}}],["collections",{"2":{"1229":1},"3":{"0":1,"545":1,"551":1,"926":1,"1050":1,"1115":1,"1117":1,"1212":1,"1229":2,"1237":4,"1243":2,"1247":19,"1259":2,"1265":2,"1270":2,"1279":1,"1286":10,"1300":4,"1309":1,"1310":5,"1311":2,"1312":1,"1323":1,"1324":5,"1339":2,"1343":5,"1346":1,"1350":20,"1352":1,"1353":1,"1359":36,"1395":15,"1396":3,"1402":4,"1415":4,"1436":3,"1526":1,"1639":2,"1863":1,"1865":1,"2233":1}}],["collection",{"2":{"1953":1},"3":{"0":3,"10":1,"11":1,"81":1,"122":1,"265":1,"317":1,"318":4,"319":1,"320":1,"321":1,"324":1,"544":1,"545":1,"548":1,"551":1,"572":1,"583":2,"598":1,"651":1,"676":1,"741":1,"743":2,"921":1,"926":2,"1067":2,"1107":1,"1108":1,"1116":1,"1117":1,"1168":2,"1212":1,"1217":1,"1221":2,"1229":6,"1230":1,"1231":4,"1234":1,"1237":14,"1240":1,"1243":2,"1244":2,"1247":6,"1259":1,"1260":1,"1262":1,"1265":4,"1270":9,"1271":1,"1276":2,"1278":1,"1282":1,"1286":26,"1297":1,"1300":10,"1309":9,"1310":17,"1311":25,"1312":1,"1316":1,"1320":2,"1323":13,"1324":13,"1328":3,"1337":1,"1339":22,"1342":2,"1343":2,"1350":61,"1353":1,"1355":1,"1359":128,"1362":1,"1369":10,"1380":10,"1395":18,"1396":30,"1402":15,"1415":13,"1417":6,"1421":1,"1428":1,"1429":2,"1430":3,"1433":1,"1436":75,"1437":1,"1438":6,"1449":1,"1453":2,"1455":2,"1465":1,"1467":1,"1486":1,"1488":6,"1491":1,"1496":1,"1577":2,"1591":1,"1611":1,"1629":1,"1631":2,"1632":1,"1635":1,"1638":2,"1651":3,"1662":2,"1666":1,"1677":1,"1683":1,"1684":1,"1686":4,"1701":3,"1719":1,"1727":2,"1728":2,"1729":1,"1730":3,"1747":1,"1748":2,"1749":1,"1755":1,"1761":1,"1764":2,"1765":2,"1776":1,"1805":1,"1810":1,"1815":1,"1824":1,"1826":2,"1830":2,"1856":1,"1865":1,"1883":2,"1922":1,"1953":2,"1975":1,"1988":3,"1994":3,"1995":2,"1997":2,"2055":1,"2056":3,"2090":1,"2091":1,"2232":1,"2233":1}}],["collectasync",{"3":{"861":1,"1433":1,"1436":3}}],["collects",{"3":{"760":1,"861":1,"1091":1,"1161":1,"1229":1,"1247":1,"1259":3,"1286":4,"1300":2,"1301":1,"1310":2,"1311":1,"1323":1,"1324":1,"1339":1,"1350":1,"1359":5,"1369":1,"1395":6,"1396":2,"1402":1,"1415":1,"1427":1,"1431":1,"1435":1,"1436":9,"1438":1,"1454":1,"1455":1,"1456":1,"1488":2,"1492":2,"1639":2,"1844":1,"1865":1,"2043":1,"2058":1,"2168":1}}],["collect",{"3":{"539":1,"1270":1,"1310":1,"1339":2,"1359":1,"1397":1,"1415":1,"1431":1,"1436":4,"1455":1,"1456":2,"1489":1,"1490":1,"1770":1,"1868":1,"1975":1,"2149":1}}],["collected",{"3":{"352":1,"538":1,"725":1,"792":1,"972":1,"1218":1,"1270":1,"1271":1,"1286":3,"1300":2,"1315":1,"1323":3,"1324":1,"1339":3,"1350":2,"1359":2,"1395":1,"1396":1,"1436":8,"1456":1,"1567":1,"1769":1,"1804":1,"1978":1,"2141":1}}],["collector",{"3":{"0":1,"861":3,"1436":6,"1488":1,"1497":1,"1671":1}}],["colliding",{"3":{"312":1,"610":1,"1286":1,"1311":2,"1312":1,"1323":1,"1324":2,"1339":1,"1350":1,"1359":4,"1395":2,"1402":1,"1436":4}}],["collided",{"3":{"1401":1}}],["collides",{"3":{"295":1,"1339":1,"1396":2,"1436":2,"1441":1,"1496":1}}],["collide",{"3":{"99":1,"246":1,"996":1,"998":1,"1286":1,"1297":1,"1300":1,"1309":1,"1311":1,"1323":4,"1324":1,"1331":1,"1339":1,"1350":3,"1352":1,"1359":4,"1362":1,"1364":1,"1369":3,"1380":2,"1395":2,"1396":4,"1402":3,"1415":1,"1429":1,"1436":4,"1438":2,"1474":1,"1478":1,"1639":1,"1684":1,"1731":1,"1920":1,"1977":1,"2097":1}}],["collisions",{"3":{"1267":1,"1270":1,"1300":1,"1301":2,"1359":1,"1395":1,"1488":1,"1639":2}}],["collision",{"3":{"0":1,"157":1,"340":1,"342":1,"733":1,"891":1,"905":1,"999":1,"1191":1,"1270":2,"1283":1,"1286":5,"1300":2,"1323":1,"1350":1,"1359":10,"1369":1,"1395":2,"1396":1,"1402":1,"1416":1,"1417":1,"1436":16,"1475":1,"1478":1,"1496":2,"1869":1,"1876":1,"1908":1}}],["collateral",{"3":{"2024":1}}],["collation",{"3":{"1350":1,"1359":5}}],["collaborate",{"3":{"1359":1,"1396":1,"1415":1,"1436":1}}],["collaborates",{"3":{"1355":1}}],["collaborations",{"3":{"1378":1}}],["collaboration",{"3":{"1309":1,"1436":1,"2219":1}}],["collaborating",{"3":{"1286":1,"1523":1,"1588":1}}],["collaborators",{"0":{"1385":1},"3":{"1057":1,"1270":1,"1286":2,"1300":1,"1311":1,"1323":4,"1324":1,"1339":1,"1359":7,"1381":1,"1385":1,"1395":5,"1396":1,"1402":1,"1436":12,"1438":2,"1489":1}}],["collaborator",{"3":{"0":1,"534":1,"539":1,"1025":1,"1060":1,"1286":2,"1293":1,"1323":1,"1359":1,"1395":4,"1396":4,"1415":1,"1436":16,"2196":1,"2202":1,"2203":1}}],["collapsible",{"3":{"649":1,"1324":2,"1395":1}}],["collapsing",{"3":{"0":1,"63":1,"283":1,"386":1,"675":1,"783":1,"964":1,"1124":1,"1220":1,"1229":1,"1247":1,"1259":1,"1270":1,"1279":2,"1286":4,"1300":7,"1311":3,"1323":1,"1324":7,"1337":1,"1339":3,"1359":7,"1380":1,"1395":3,"1396":1,"1402":2,"1416":1,"1417":3,"1436":4,"1534":1,"1670":1,"1805":1,"2024":1,"2167":1}}],["collapsed",{"3":{"60":1,"791":1,"882":1,"1204":1,"1247":1,"1286":5,"1295":1,"1298":1,"1300":1,"1311":1,"1324":1,"1350":1,"1359":1,"1395":3,"1396":1,"1436":1,"1496":1,"1534":1,"2026":1}}],["collapse",{"3":{"0":2,"111":1,"137":1,"164":1,"166":3,"170":1,"179":1,"333":1,"507":1,"680":1,"693":1,"881":1,"945":1,"996":2,"1247":1,"1259":1,"1270":2,"1279":3,"1285":1,"1286":16,"1292":1,"1300":9,"1301":1,"1322":1,"1323":1,"1324":9,"1339":4,"1359":2,"1395":5,"1396":6,"1415":1,"1417":7,"1429":1,"1436":8,"1438":1,"1443":1,"1447":2,"1467":2,"1489":1,"1490":1,"1531":1,"1534":2,"1652":2,"1664":2,"1712":1,"1790":1,"1849":3,"1852":1,"1853":1,"1981":1,"2024":1,"2053":1}}],["collapses",{"3":{"0":2,"47":1,"459":1,"490":1,"513":1,"592":1,"690":1,"790":1,"827":1,"854":1,"905":1,"996":1,"1116":1,"1212":1,"1225":1,"1237":1,"1247":1,"1259":1,"1270":3,"1271":1,"1281":1,"1285":1,"1286":11,"1294":2,"1300":3,"1309":2,"1311":5,"1322":1,"1323":3,"1324":4,"1326":1,"1338":1,"1339":5,"1350":3,"1359":7,"1395":6,"1396":11,"1415":4,"1417":2,"1436":4,"1438":1,"1441":1,"1455":1,"1534":1,"1631":1,"1641":2,"1652":1,"1662":1,"1667":1,"1684":1,"1765":1,"1790":1,"1847":1,"1849":3,"1858":1,"1870":1,"1909":1,"1922":1,"2029":1,"2040":1,"2047":1,"2048":1,"2166":1}}],["columnprefix",{"3":{"1286":3}}],["columnname",{"3":{"1286":2}}],["columnwidth",{"3":{"1199":5,"1203":1,"1207":2,"1259":2,"1277":2,"1286":6,"1496":1}}],["column",{"0":{"2144":1},"3":{"0":11,"26":2,"275":1,"311":1,"330":1,"333":2,"337":2,"341":2,"343":1,"358":1,"359":4,"360":3,"361":4,"363":2,"364":1,"365":1,"499":1,"501":1,"549":3,"564":1,"566":2,"655":1,"656":1,"657":7,"659":1,"690":1,"691":1,"696":1,"697":1,"698":2,"703":4,"717":1,"721":1,"741":2,"743":1,"744":1,"766":1,"809":2,"811":1,"812":1,"881":2,"889":5,"890":1,"892":1,"903":1,"904":1,"905":1,"906":1,"907":1,"910":2,"964":4,"965":1,"966":1,"1018":3,"1020":2,"1033":1,"1035":1,"1050":5,"1051":2,"1052":3,"1053":1,"1058":1,"1085":1,"1135":1,"1175":4,"1228":1,"1229":3,"1234":1,"1237":8,"1239":1,"1241":2,"1242":4,"1243":1,"1244":3,"1247":25,"1259":3,"1260":1,"1270":1,"1271":3,"1276":3,"1277":1,"1278":1,"1280":2,"1281":4,"1282":1,"1283":1,"1284":2,"1286":121,"1290":2,"1291":1,"1297":1,"1300":8,"1309":8,"1310":1,"1311":13,"1318":1,"1323":7,"1324":10,"1342":3,"1344":1,"1350":25,"1354":1,"1359":66,"1362":7,"1369":34,"1375":1,"1380":16,"1383":2,"1392":1,"1395":28,"1396":27,"1398":1,"1402":2,"1405":1,"1408":1,"1414":1,"1415":29,"1436":15,"1438":4,"1445":1,"1455":2,"1460":1,"1489":1,"1492":1,"1496":3,"1526":1,"1577":2,"1591":3,"1600":1,"1630":2,"1631":1,"1639":4,"1640":3,"1641":1,"1663":1,"1672":1,"1675":1,"1683":1,"1686":1,"1713":1,"1723":1,"1727":4,"1749":1,"1750":2,"1767":1,"1769":1,"1810":1,"1815":1,"1849":1,"1851":2,"1856":1,"1858":1,"1862":1,"1875":1,"1876":1,"1877":1,"1903":1,"1933":1,"1934":1,"1940":1,"1986":1,"1990":1,"1991":1,"2070":2,"2081":2,"2083":1,"2084":1,"2085":2,"2086":1,"2140":2,"2141":3,"2142":3,"2143":3,"2144":4,"2145":4,"2146":6,"2186":1,"2194":1,"2232":2}}],["columns",{"1":{"2140":1,"2141":1,"2142":1,"2143":1,"2144":1,"2145":1,"2146":1},"3":{"0":6,"26":3,"41":1,"166":1,"313":1,"328":1,"330":1,"341":1,"348":1,"350":3,"352":1,"358":3,"359":1,"360":1,"361":1,"549":1,"564":3,"657":2,"658":1,"660":1,"715":1,"717":1,"741":3,"742":2,"799":1,"802":1,"853":2,"891":1,"905":1,"907":1,"950":1,"1024":2,"1026":3,"1030":1,"1043":1,"1060":1,"1061":1,"1074":1,"1075":1,"1116":1,"1133":1,"1175":1,"1212":2,"1234":1,"1237":3,"1239":1,"1241":2,"1242":1,"1243":1,"1244":1,"1245":1,"1247":7,"1251":1,"1253":2,"1259":6,"1269":1,"1270":2,"1277":1,"1280":1,"1281":2,"1286":26,"1300":4,"1309":5,"1311":7,"1312":2,"1323":3,"1324":2,"1350":7,"1359":8,"1362":1,"1363":1,"1369":14,"1380":1,"1395":7,"1396":16,"1401":1,"1402":1,"1406":1,"1407":1,"1412":1,"1415":20,"1436":2,"1438":2,"1467":1,"1496":1,"1524":1,"1526":1,"1531":1,"1591":1,"1607":1,"1641":1,"1656":1,"1664":2,"1666":1,"1671":1,"1675":1,"1685":2,"1686":1,"1741":1,"1810":1,"1815":1,"1856":1,"1866":1,"1903":1,"1977":1,"1978":2,"1988":1,"2072":1,"2080":1,"2085":1,"2139":1,"2140":2,"2141":1,"2143":1,"2145":4,"2146":2,"2153":1,"2186":1,"2192":1,"2198":1,"2208":2,"2232":1}}],["courtesy",{"3":{"1389":1,"1396":1}}],["courses",{"3":{"2225":1}}],["course",{"3":{"1323":1}}],["cousin",{"3":{"1230":1,"1270":1,"1359":1}}],["coupon",{"2":{"2087":1,"2090":1,"2093":1},"3":{"0":1,"1028":1,"2087":1,"2090":1,"2093":1}}],["couple",{"3":{"92":1,"1211":1,"1300":2,"1312":1,"1323":1,"1359":2,"1396":1,"1436":3,"1689":1,"1781":1,"1868":1,"1903":1,"1965":1,"2016":1}}],["couples",{"3":{"0":1,"116":1,"185":1,"544":1,"1300":1,"1436":1,"1540":1,"1546":1,"1863":1,"1894":1,"1960":1,"1991":1}}],["coupled",{"3":{"0":2,"265":1,"329":1,"545":1,"547":1,"585":1,"1069":1,"1077":1,"1271":1,"1289":1,"1300":1,"1324":1,"1339":1,"1359":2,"1436":7,"1489":1,"1815":1,"1863":1,"1932":1,"1961":1,"1990":1,"2027":1,"2028":1,"2079":1,"2232":1}}],["couplings",{"3":{"1789":1}}],["coupling",{"3":{"0":1,"33":1,"52":1,"102":1,"135":1,"159":1,"226":1,"255":1,"458":1,"547":1,"549":1,"628":1,"827":1,"924":1,"1011":1,"1012":1,"1202":1,"1203":1,"1229":1,"1236":1,"1237":2,"1259":3,"1271":1,"1300":1,"1309":1,"1311":3,"1313":1,"1323":2,"1336":1,"1339":2,"1350":4,"1359":3,"1391":1,"1396":3,"1415":1,"1416":4,"1431":1,"1436":17,"1442":1,"1455":1,"1496":1,"1526":1,"1534":1,"1542":2,"1545":1,"1571":3,"1591":1,"1638":1,"1661":1,"1727":1,"1792":1,"1800":1,"1816":1,"1829":1,"1847":1,"1848":1,"1854":1,"1864":1,"1951":1,"1952":2}}],["could",{"3":{"0":4,"19":1,"20":1,"21":2,"22":1,"72":1,"109":1,"111":1,"120":1,"135":1,"142":1,"170":1,"188":1,"213":1,"228":1,"235":1,"245":1,"246":1,"248":1,"254":1,"256":1,"265":2,"266":1,"318":1,"335":1,"391":1,"419":1,"448":1,"451":1,"490":1,"528":1,"529":1,"536":3,"537":1,"538":1,"539":1,"558":1,"612":1,"627":2,"664":1,"674":1,"696":1,"725":2,"749":1,"760":1,"800":1,"801":2,"826":2,"827":1,"833":1,"838":1,"853":1,"889":1,"897":1,"898":1,"905":3,"907":1,"915":1,"922":1,"926":1,"939":1,"945":2,"947":1,"956":1,"971":1,"979":1,"988":1,"1017":1,"1018":1,"1019":2,"1026":1,"1059":1,"1067":1,"1100":1,"1116":1,"1128":1,"1174":1,"1219":1,"1222":1,"1223":1,"1229":3,"1233":1,"1237":5,"1242":1,"1247":3,"1252":1,"1253":1,"1259":3,"1265":1,"1270":2,"1271":1,"1274":1,"1286":15,"1292":1,"1299":1,"1300":21,"1301":3,"1304":1,"1309":5,"1311":13,"1312":2,"1323":16,"1324":20,"1332":1,"1339":8,"1350":2,"1357":1,"1359":31,"1369":2,"1380":3,"1384":1,"1390":1,"1395":12,"1396":31,"1402":6,"1409":1,"1415":18,"1417":5,"1436":26,"1438":2,"1440":1,"1446":1,"1447":1,"1454":3,"1455":1,"1467":4,"1473":1,"1474":1,"1475":1,"1488":2,"1492":1,"1495":1,"1496":1,"1546":1,"1596":1,"1638":2,"1639":3,"1641":1,"1685":1,"1689":1,"1699":1,"1727":1,"1755":1,"1765":1,"1780":1,"1806":1,"1807":2,"1822":1,"1824":1,"1881":1,"1898":1,"1918":1,"1924":1,"1970":1,"1995":1,"2012":1,"2062":1,"2065":1,"2073":1,"2085":1,"2089":1,"2124":1,"2129":2,"2131":1,"2161":1,"2163":1,"2164":1,"2166":1,"2167":1,"2168":1,"2169":1,"2200":1}}],["county",{"3":{"2220":2}}],["countfollowedbyplural",{"3":{"1436":1}}],["countoccurrences",{"3":{"1436":1}}],["countinvalid",{"3":{"1402":1}}],["countingmessagebus",{"3":{"1199":2,"1207":3,"1436":1,"1487":1}}],["countingqueryableexecutor",{"3":{"1199":3,"1207":1}}],["countingsweep",{"3":{"1199":2,"1207":1,"1323":1}}],["countingallocator",{"3":{"1199":2,"1207":1}}],["countingfailurehandler",{"3":{"1199":2,"1207":1}}],["countingcheck",{"3":{"1199":2,"1207":1}}],["counting",{"3":{"0":3,"27":1,"125":1,"280":1,"284":1,"476":1,"477":1,"593":1,"631":1,"782":1,"799":2,"803":2,"804":1,"805":1,"1004":1,"1005":1,"1018":1,"1240":1,"1245":1,"1247":3,"1256":1,"1259":1,"1270":3,"1278":1,"1286":2,"1300":2,"1307":1,"1309":2,"1311":2,"1323":1,"1324":3,"1339":1,"1350":3,"1354":1,"1355":1,"1359":5,"1395":1,"1396":6,"1402":1,"1415":1,"1436":6,"1438":2,"1447":1,"2028":1,"2043":1,"2048":1,"2106":1}}],["countcategoryitems",{"3":{"1359":4}}],["countries",{"3":{"1324":1}}],["countrymaxlength",{"3":{"1237":1,"1271":1}}],["country",{"3":{"1237":1,"1271":4,"1286":1,"1324":1,"1427":1,"1764":1}}],["countryrules",{"3":{"1199":3,"1203":1,"1207":1,"1271":5,"1829":1}}],["countunpaginatedasync",{"2":{"1243":1},"3":{"1243":1,"1247":2}}],["countable",{"3":{"898":1,"1396":3,"1399":1}}],["countasync",{"2":{"546":1},"3":{"0":1,"545":1,"546":1,"549":1,"550":1,"552":1,"1247":1,"1286":16,"1309":3,"1396":5,"1402":2,"1415":1,"1436":3,"1815":5}}],["countdown",{"3":{"556":1,"1350":1,"1393":2,"1395":9,"1416":1,"1438":1,"1737":1}}],["countbyasync",{"3":{"543":1,"545":1,"551":1,"1286":10,"1359":2,"1396":3,"1815":1}}],["countpendingasync",{"2":{"24":1,"409":1,"2157":1,"2191":1},"3":{"24":1,"397":1,"409":1,"1259":3,"1286":4,"1323":1,"2157":1,"2191":1}}],["counterexample",{"3":{"1436":1}}],["countermeasure",{"3":{"1300":1}}],["counterweights",{"3":{"1118":1}}],["counterweight",{"3":{"530":1,"974":1,"1359":1,"1396":1,"1467":1,"2181":1}}],["counterparts",{"3":{"982":1,"1229":1,"1301":1,"1312":1,"1320":1,"1339":1,"1350":1,"1359":2,"1392":1,"1402":1,"1415":1,"1496":1}}],["counterpart",{"3":{"0":2,"143":1,"230":1,"265":1,"313":1,"362":2,"514":1,"543":1,"550":1,"591":1,"595":1,"707":1,"748":1,"749":1,"766":1,"842":1,"950":1,"980":1,"1212":1,"1229":2,"1237":2,"1259":3,"1269":1,"1270":3,"1271":1,"1275":1,"1286":13,"1300":3,"1306":1,"1309":5,"1311":4,"1312":2,"1323":3,"1324":9,"1339":3,"1342":1,"1350":7,"1352":1,"1359":14,"1380":1,"1395":8,"1396":5,"1402":5,"1415":2,"1416":1,"1417":6,"1431":1,"1433":1,"1436":24,"1444":1,"1455":1,"1488":2,"1489":1,"1554":1,"1910":1,"2048":1,"2077":1}}],["counters",{"0":{"2177":1},"3":{"0":3,"124":1,"173":1,"177":1,"178":1,"245":1,"248":1,"255":2,"279":1,"283":2,"285":1,"286":2,"287":1,"379":1,"400":3,"404":1,"408":1,"609":1,"698":1,"700":1,"707":1,"732":1,"734":1,"737":1,"855":1,"899":1,"1016":1,"1018":2,"1020":1,"1089":1,"1090":1,"1093":1,"1268":2,"1270":6,"1286":3,"1289":1,"1292":1,"1294":1,"1300":5,"1301":5,"1311":3,"1317":1,"1323":1,"1350":1,"1359":3,"1395":1,"1396":1,"1418":1,"1425":2,"1427":3,"1435":1,"1438":4,"1442":2,"1443":2,"1467":1,"1577":1,"1641":3,"1665":1,"1666":1,"1667":1,"1765":1,"1767":1,"1915":1,"1920":1,"1935":1,"1941":1,"2001":2,"2006":1,"2154":1,"2156":5,"2157":1,"2160":3,"2180":1,"2191":1}}],["counter",{"3":{"0":8,"121":1,"126":1,"157":1,"241":1,"242":1,"245":5,"247":1,"248":2,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":2,"257":1,"279":4,"283":1,"284":1,"285":1,"395":2,"397":2,"398":1,"399":2,"406":1,"407":1,"518":1,"608":1,"707":2,"731":1,"732":2,"733":2,"734":1,"735":1,"736":2,"818":1,"819":2,"823":1,"827":1,"849":1,"854":2,"896":1,"897":2,"898":1,"899":1,"996":1,"997":1,"1074":1,"1090":1,"1092":1,"1093":1,"1100":1,"1101":1,"1102":1,"1126":1,"1150":1,"1212":2,"1247":11,"1256":3,"1259":3,"1267":1,"1270":6,"1282":1,"1286":4,"1292":2,"1294":3,"1295":1,"1300":18,"1301":17,"1309":1,"1311":26,"1317":1,"1323":20,"1324":24,"1337":1,"1339":4,"1344":1,"1350":5,"1359":6,"1395":18,"1396":3,"1402":5,"1415":4,"1416":1,"1427":2,"1429":1,"1436":4,"1438":4,"1442":2,"1453":1,"1488":1,"1490":1,"1496":1,"1577":1,"1641":5,"1765":1,"1767":1,"1843":2,"1849":1,"1903":1,"1915":2,"1916":1,"1922":1,"1947":1,"1999":1,"2000":1,"2001":6,"2002":3,"2006":2,"2010":1,"2055":1,"2142":1,"2156":7,"2159":1,"2180":1}}],["counted",{"3":{"0":5,"121":1,"126":1,"130":1,"135":1,"138":2,"139":1,"175":1,"255":1,"279":1,"383":1,"408":1,"423":1,"474":1,"484":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"556":1,"575":1,"577":1,"655":1,"672":3,"798":1,"799":2,"801":1,"803":1,"805":1,"832":1,"916":1,"996":1,"1091":2,"1093":1,"1247":1,"1256":1,"1259":1,"1263":2,"1267":1,"1270":2,"1286":1,"1300":3,"1311":7,"1323":1,"1324":4,"1339":1,"1350":1,"1357":1,"1359":13,"1396":7,"1400":1,"1402":4,"1416":1,"1422":1,"1436":2,"1438":1,"1487":1,"1489":1,"1496":3,"1585":1,"1639":1,"1669":1,"1764":1,"1817":1,"1909":1,"1911":1,"1919":1,"1922":2,"1923":1,"1949":2,"1983":1,"2000":2,"2174":1,"2179":1}}],["countstheliteralcatalog",{"3":{"1436":2}}],["countsthedatacarryingcommands",{"3":{"1436":3}}],["countsbyquestion",{"3":{"1402":1}}],["countsbypolloption",{"3":{"1402":1}}],["countscoredsince",{"3":{"1395":5}}],["countsessionspercategoryitem",{"3":{"1359":4}}],["counts",{"0":{"137":1,"140":1,"1205":1,"1206":1,"1734":1},"3":{"0":6,"24":1,"73":2,"74":1,"125":1,"130":3,"135":4,"136":1,"137":2,"140":1,"157":1,"184":1,"228":1,"235":1,"255":2,"280":1,"281":1,"319":1,"386":1,"390":1,"395":1,"397":1,"399":1,"400":2,"474":2,"475":1,"477":2,"480":1,"483":1,"486":1,"487":2,"488":1,"489":1,"490":1,"491":3,"492":1,"493":1,"494":1,"495":3,"524":1,"526":1,"530":1,"538":2,"545":1,"577":1,"585":1,"597":1,"604":2,"607":2,"609":1,"616":3,"621":1,"629":1,"634":1,"649":1,"677":1,"706":1,"733":1,"741":1,"780":3,"819":1,"827":1,"832":1,"836":1,"883":1,"888":1,"889":1,"896":1,"972":1,"974":1,"978":1,"983":1,"986":1,"1004":3,"1006":1,"1091":1,"1093":1,"1124":1,"1150":1,"1152":1,"1168":1,"1186":1,"1212":1,"1237":1,"1247":2,"1256":1,"1259":5,"1270":1,"1271":2,"1273":1,"1275":1,"1286":9,"1289":2,"1300":4,"1301":3,"1304":1,"1309":3,"1311":7,"1323":6,"1324":15,"1333":1,"1334":1,"1339":8,"1347":4,"1349":1,"1350":18,"1355":1,"1359":34,"1366":1,"1379":1,"1380":6,"1388":2,"1390":3,"1391":1,"1395":12,"1396":17,"1399":1,"1401":1,"1402":22,"1415":5,"1416":2,"1417":2,"1423":1,"1427":1,"1430":1,"1435":1,"1436":37,"1438":2,"1453":1,"1455":3,"1467":2,"1474":1,"1476":1,"1484":1,"1485":1,"1487":3,"1490":1,"1492":1,"1494":1,"1495":1,"1496":18,"1498":1,"1535":1,"1573":1,"1577":1,"1582":2,"1591":1,"1599":1,"1632":3,"1637":1,"1639":1,"1641":3,"1654":1,"1659":1,"1665":2,"1668":1,"1669":1,"1673":2,"1674":1,"1675":1,"1815":1,"1904":1,"1917":1,"1922":1,"1923":1,"1937":1,"1949":1,"1997":1,"2000":3,"2002":2,"2024":1,"2034":1,"2159":1,"2170":1,"2172":1,"2174":1,"2175":1,"2179":1,"2192":1,"2232":2}}],["count",{"0":{"1128":1,"1797":1,"2051":1},"2":{"20":1,"80":1,"82":1,"121":1,"126":1,"397":1,"819":1,"821":1,"1256":1,"1268":2,"1310":1,"1354":1,"1665":1,"1843":1,"2032":1,"2156":2,"2159":1,"2191":2},"3":{"0":25,"3":1,"20":1,"24":1,"71":1,"80":1,"82":1,"100":1,"121":1,"125":1,"126":1,"130":2,"135":1,"136":2,"137":2,"138":1,"140":1,"141":1,"142":3,"146":1,"147":1,"152":1,"160":1,"178":1,"195":1,"203":1,"225":1,"254":1,"255":1,"258":1,"260":2,"310":1,"311":1,"335":1,"379":1,"390":2,"392":2,"393":1,"395":2,"397":3,"408":1,"409":1,"418":1,"423":1,"426":3,"452":2,"453":1,"468":1,"475":1,"476":1,"492":2,"493":2,"494":2,"495":1,"522":2,"523":2,"524":4,"526":1,"527":1,"528":1,"549":2,"550":2,"554":3,"572":2,"589":1,"592":1,"607":1,"608":1,"609":6,"610":1,"611":2,"616":3,"624":1,"631":3,"634":1,"635":3,"640":1,"655":1,"672":1,"696":1,"699":1,"700":2,"759":1,"768":1,"775":1,"797":3,"799":4,"800":1,"801":1,"803":1,"804":1,"805":2,"819":5,"820":1,"821":1,"860":1,"862":1,"883":2,"884":2,"896":1,"897":1,"899":1,"905":2,"906":1,"914":1,"937":1,"946":2,"948":1,"952":2,"959":1,"972":1,"975":1,"986":2,"991":1,"996":2,"999":1,"1004":2,"1005":1,"1018":1,"1020":2,"1042":1,"1048":1,"1049":1,"1054":1,"1055":1,"1069":1,"1073":1,"1078":1,"1100":2,"1116":1,"1118":1,"1122":1,"1124":4,"1125":3,"1126":1,"1136":1,"1178":1,"1192":2,"1196":1,"1202":1,"1205":1,"1211":1,"1212":2,"1221":1,"1228":3,"1229":11,"1237":4,"1240":2,"1243":4,"1247":13,"1253":1,"1256":3,"1259":10,"1264":1,"1268":2,"1270":10,"1271":1,"1275":1,"1278":1,"1286":30,"1300":13,"1301":3,"1303":1,"1306":3,"1309":30,"1310":3,"1311":11,"1315":1,"1317":1,"1318":1,"1323":15,"1324":63,"1330":1,"1337":2,"1339":9,"1347":1,"1350":13,"1354":1,"1355":1,"1357":1,"1358":1,"1359":62,"1369":1,"1371":1,"1375":1,"1380":4,"1389":1,"1395":55,"1396":83,"1399":4,"1401":4,"1402":78,"1406":1,"1415":6,"1416":2,"1422":1,"1425":1,"1427":4,"1431":2,"1435":1,"1436":98,"1438":6,"1442":1,"1443":1,"1446":1,"1453":8,"1454":2,"1455":2,"1456":1,"1458":2,"1461":1,"1467":2,"1473":1,"1474":2,"1475":2,"1476":2,"1478":1,"1486":1,"1487":1,"1488":1,"1489":4,"1496":20,"1500":1,"1503":1,"1515":1,"1521":1,"1524":1,"1525":1,"1526":5,"1531":3,"1534":2,"1535":1,"1547":1,"1577":6,"1582":1,"1591":2,"1595":1,"1596":2,"1597":1,"1600":1,"1607":1,"1611":1,"1612":1,"1617":1,"1632":8,"1641":3,"1646":1,"1649":1,"1665":3,"1673":1,"1675":2,"1692":1,"1709":1,"1729":1,"1732":1,"1741":1,"1750":3,"1764":1,"1765":1,"1767":2,"1773":2,"1780":1,"1796":1,"1818":1,"1841":1,"1843":1,"1874":1,"1879":1,"1881":1,"1885":1,"1902":3,"1904":1,"1905":1,"1912":1,"1919":2,"1937":1,"1940":1,"1942":1,"1946":1,"1950":1,"1982":1,"1986":1,"1988":1,"1998":1,"1999":1,"2000":5,"2002":1,"2027":2,"2032":3,"2042":1,"2043":3,"2045":1,"2046":2,"2047":1,"2051":1,"2052":1,"2055":1,"2060":1,"2070":1,"2082":1,"2106":1,"2112":1,"2113":1,"2116":1,"2156":8,"2157":1,"2158":1,"2159":1,"2174":1,"2177":1,"2180":2,"2182":1,"2191":4,"2193":1,"2232":3}}],["comfort",{"3":{"1800":1}}],["comfortable",{"3":{"1153":1,"1324":2,"1559":1,"1591":1,"1596":1,"1597":1}}],["comfortably",{"3":{"907":1,"996":1,"1270":1,"1275":1,"1323":1,"1324":1,"1396":1,"1402":1,"1436":1,"1467":1}}],["coming",{"3":{"0":1,"24":1,"1040":1,"1259":1,"1286":2,"1307":1,"1395":1,"1417":1,"1786":1,"2058":1}}],["com",{"2":{"1481":1},"3":{"0":2,"528":1,"640":1,"855":2,"868":2,"1292":2,"1300":7,"1324":3,"1339":1,"1347":1,"1351":1,"1358":1,"1359":6,"1365":1,"1369":2,"1395":6,"1415":1,"1417":2,"1436":7,"1441":1,"1445":2,"1465":1,"1472":1,"1481":2,"1631":2,"1787":1,"1793":1,"1802":1,"1807":1,"1812":1,"1817":1,"1827":1,"1834":1,"1846":1,"1853":1,"1862":1,"1868":1,"1877":1,"1886":1,"1892":1,"1899":1,"1905":1,"1912":1,"1924":1,"1930":1,"1941":1,"1950":1,"1959":1,"1965":1,"1972":1,"1979":1,"1985":1,"1991":1,"1997":1,"2002":1,"2014":1,"2028":1,"2038":1,"2049":1,"2060":1,"2069":1,"2080":1,"2086":1,"2098":1,"2108":1,"2115":1,"2123":1,"2128":1,"2133":1,"2139":1,"2146":1,"2153":1,"2160":1,"2169":1,"2180":1,"2193":1,"2203":1,"2211":1,"2220":2,"2221":1,"2240":2,"2244":1}}],["combobox",{"3":{"0":1,"618":2,"620":2,"651":1,"1436":4,"1488":3,"1591":1,"1741":1,"2057":1}}],["combinable",{"3":{"1286":1}}],["combinations",{"3":{"932":1,"946":1,"1286":1,"1324":1,"1641":1}}],["combination",{"3":{"0":1,"92":1,"148":1,"556":1,"684":1,"872":1,"931":1,"1091":1,"1092":1,"1141":1,"1212":1,"1237":1,"1247":2,"1286":1,"1300":1,"1311":1,"1324":1,"1343":1,"1350":1,"1359":3,"1395":1,"1396":3,"1415":1,"1417":1,"1436":2,"1438":1,"1491":1,"1529":1,"1631":1,"1842":1,"1844":1,"1947":1,"2125":1,"2176":1}}],["combinatorial",{"3":{"146":1}}],["combinators",{"3":{"0":1,"109":1,"110":1,"111":1,"547":2,"549":2,"550":1,"1219":1,"1220":1,"1229":8,"1239":1,"1247":10,"1805":1,"1807":1,"1815":1,"1817":1}}],["combinator",{"3":{"0":1,"107":1,"109":2,"545":1,"1217":1,"1220":2,"1229":5,"1247":8,"1662":1,"1805":1,"1807":1,"1815":1}}],["combiningacute",{"3":{"1324":1}}],["combining",{"3":{"1220":1,"1229":1,"1231":2,"1237":1,"1247":1,"1286":2,"1324":2,"1336":1,"1339":2,"1343":1,"1350":2,"1359":1,"1415":1,"1417":1,"1436":2,"1814":1}}],["combines",{"3":{"964":1,"1270":2,"1278":1,"1300":1,"1309":2,"1339":1,"1350":5,"1359":12,"1395":1,"1396":1,"1398":1,"1402":4,"1415":3,"1417":2,"1436":1,"1488":1}}],["combine",{"2":{"43":1,"110":1,"1169":1,"1170":1,"1220":1,"1222":1,"1224":1,"1343":1,"1701":1,"1807":1},"3":{"0":3,"39":3,"43":2,"109":1,"110":1,"545":1,"549":1,"657":1,"1168":6,"1169":2,"1170":2,"1220":2,"1222":1,"1224":1,"1229":5,"1234":1,"1237":8,"1239":1,"1244":1,"1247":7,"1271":1,"1309":4,"1311":2,"1312":2,"1324":2,"1342":1,"1343":3,"1344":1,"1350":35,"1359":4,"1395":8,"1396":10,"1402":5,"1405":1,"1415":4,"1427":1,"1436":5,"1526":1,"1651":1,"1662":2,"1685":3,"1701":1,"1805":1,"1806":1,"1807":2,"1810":1,"1815":1,"1817":1,"1927":1,"1962":1}}],["combined",{"3":{"0":1,"43":1,"57":1,"59":4,"60":1,"62":1,"63":1,"166":1,"195":1,"350":1,"483":1,"487":1,"488":1,"489":1,"490":1,"491":2,"492":2,"493":2,"494":2,"495":2,"547":1,"562":1,"582":1,"584":1,"743":1,"790":1,"889":1,"890":1,"1239":1,"1247":1,"1270":3,"1286":4,"1289":1,"1324":2,"1339":1,"1344":1,"1350":6,"1359":8,"1395":3,"1396":2,"1402":7,"1415":1,"1436":4,"1438":1,"1441":1,"1446":1,"1455":1,"1456":1,"1467":1,"1486":2,"1487":1,"1496":2,"1526":1,"1619":1,"1651":1,"1673":1,"1765":1,"1775":1,"1817":1,"1859":1,"1884":1,"2026":1}}],["computation",{"3":{"681":1,"1026":1,"1027":1,"1350":1,"1359":3,"1395":3,"1396":2,"1436":1,"1500":1,"1804":1}}],["computing",{"3":{"629":1,"1019":1,"1259":1,"1267":1,"1286":2,"1300":2,"1324":1,"1339":2,"1350":1,"1359":3,"1369":1,"1402":4,"1417":1,"1436":2}}],["computer",{"3":{"1913":1,"2222":1}}],["computerequestbodyhashasync",{"3":{"1311":2}}],["computeretrybackoffseconds",{"2":{"19":2,"1042":2,"1259":1,"1275":1,"2188":1},"3":{"19":2,"1042":2,"1259":3,"1275":1,"1286":2,"2188":1}}],["computeancestors",{"3":{"1436":1}}],["computeoptions",{"3":{"1395":3}}],["computelivewindowutc",{"3":{"1359":1}}],["computelegacyhash",{"2":{"945":1},"3":{"0":1,"310":1,"945":1,"946":1}}],["computecategorydistribution",{"3":{"1359":1}}],["computevirtualwindow",{"3":{"1324":1}}],["computewaittime",{"2":{"1042":1,"1254":1},"3":{"1042":1,"1254":1,"1259":3,"1275":1,"1286":7,"1323":1}}],["compute",{"3":{"707":1,"973":1,"1267":1,"1279":1,"1311":2,"1315":1,"1323":2,"1324":1,"1350":2,"1352":1,"1355":1,"1359":5,"1395":1,"1396":4,"1402":2,"1417":3,"1436":2,"1446":1,"1467":4,"1568":1,"1677":2,"1678":2,"1879":1,"1881":1,"1920":2,"2155":1,"2180":1}}],["computespeakerlocality",{"3":{"1359":1}}],["computespeakeroverlap",{"3":{"1359":2}}],["computes",{"3":{"583":1,"626":1,"1091":1,"1212":1,"1229":1,"1242":1,"1247":3,"1259":1,"1263":1,"1267":1,"1270":4,"1286":9,"1300":3,"1310":1,"1311":1,"1323":2,"1324":7,"1339":5,"1350":1,"1352":1,"1359":13,"1373":1,"1380":1,"1395":3,"1396":5,"1401":2,"1402":7,"1417":4,"1431":1,"1436":12,"1496":1,"1920":1,"2149":2,"2167":1}}],["computepbkdf2hash",{"3":{"310":1,"946":1,"1300":1}}],["computedhash",{"3":{"1300":1}}],["computed",{"0":{"1798":1},"2":{"1902":1},"3":{"0":3,"19":1,"157":1,"159":1,"219":1,"335":1,"633":1,"650":1,"905":1,"926":1,"990":1,"1018":1,"1026":2,"1091":1,"1191":1,"1192":1,"1193":1,"1200":1,"1227":1,"1229":2,"1237":2,"1247":5,"1254":1,"1259":3,"1267":1,"1269":1,"1270":8,"1286":12,"1300":9,"1309":1,"1310":4,"1311":4,"1323":3,"1324":14,"1331":1,"1332":1,"1339":4,"1342":3,"1350":12,"1352":1,"1354":2,"1359":23,"1362":1,"1369":8,"1383":2,"1391":1,"1395":22,"1396":18,"1402":11,"1409":1,"1412":1,"1415":11,"1417":1,"1426":1,"1427":1,"1436":7,"1438":1,"1467":1,"1480":1,"1496":1,"1577":1,"1585":1,"1592":1,"1630":6,"1631":1,"1639":2,"1640":3,"1641":3,"1686":3,"1749":2,"1764":5,"1765":1,"1767":2,"1805":1,"1840":1,"1867":1,"1902":1,"1920":1,"1948":1,"1988":1,"2176":1,"2180":1}}],["complains",{"3":{"1907":1,"1923":1}}],["complaint",{"3":{"1323":1}}],["comply",{"3":{"1436":1}}],["compliment",{"3":{"2200":1}}],["complication",{"3":{"1359":1}}],["compliance",{"0":{"1567":1,"1672":1},"3":{"716":1,"971":1,"972":1,"973":1,"976":1,"1168":1,"1207":2,"1216":1,"1232":1,"1236":1,"1237":6,"1253":1,"1259":2,"1269":1,"1270":3,"1276":1,"1283":1,"1286":14,"1299":1,"1300":4,"1308":1,"1309":5,"1311":2,"1317":1,"1323":8,"1324":2,"1350":1,"1353":1,"1355":1,"1359":4,"1380":2,"1395":2,"1396":32,"1402":3,"1403":1,"1405":1,"1410":2,"1415":25,"1417":5,"1436":24,"1438":2,"1454":1,"1461":1,"1479":1,"1480":3,"1483":1,"1489":1,"1493":1,"1499":1,"1526":1,"1534":1,"1535":1,"1569":1,"1575":1,"1577":2,"1581":1,"1582":1,"1583":1,"1584":1,"1591":1,"1597":1,"1672":1,"1678":1,"1784":1,"1796":1,"1799":1,"1801":1,"1845":2,"1846":1,"2043":1,"2062":1,"2067":1,"2141":1}}],["compliantstringfixtureid",{"3":{"1199":2,"1207":1,"1436":5}}],["compliantfixtureid",{"3":{"1199":2,"1207":1,"1436":8}}],["compliantfixtureservice",{"3":{"1199":2,"1207":1,"1436":3}}],["compliant",{"3":{"38":1,"41":1,"1050":1,"1168":1,"1300":1,"1403":1,"1436":17,"1480":1,"2061":1,"2068":1}}],["complexities",{"3":{"1639":1}}],["complexity",{"3":{"61":1,"68":1,"87":1,"1214":1,"1237":1,"1247":1,"1254":1,"1271":3,"1287":1,"1294":1,"1300":5,"1301":1,"1312":1,"1323":2,"1324":5,"1359":1,"1395":1,"1415":2,"1639":1,"1641":2,"1767":1,"1792":1,"1832":1,"1899":1,"1940":1,"2027":2,"2037":1}}],["complex",{"3":{"1052":1,"1055":1,"1286":1,"1300":1,"1359":2,"2079":1}}],["complementary",{"3":{"541":1,"624":1,"626":2,"1247":1,"1300":1,"1339":1,"1359":1,"1396":1,"1436":3,"1463":1,"1486":2,"1492":1,"1493":1,"1605":1,"1739":1,"1977":1,"1998":1,"2041":1,"2076":1,"2164":1}}],["complementing",{"3":{"503":1,"1311":1,"1402":1}}],["complements",{"0":{"1890":1},"3":{"122":1,"282":1,"321":1,"370":1,"579":1,"758":1,"766":1,"770":1,"1309":1,"1311":1,"1312":1,"1324":1,"1396":1,"1436":5,"1438":1,"1455":2,"1457":1,"1463":1,"1560":1,"1563":1,"1565":1,"1611":1,"1642":1,"1706":1,"1892":1,"2093":1}}],["complement",{"3":{"0":1,"626":1,"860":1,"1259":1,"1339":1,"1395":1,"1396":1,"1436":2,"1455":3,"1461":1,"1486":1,"1525":1,"1526":1,"1531":1,"1591":1,"1596":1,"1600":1,"1605":1,"1739":1,"1832":1}}],["completing",{"3":{"920":1,"1311":1,"1324":2,"1359":2,"1395":4,"1396":1,"1411":1,"1417":2,"1467":1,"1474":1,"2232":1}}],["completions",{"1":{"1056":1,"1057":1,"1058":1,"1059":1,"1060":1,"1061":1,"1062":1,"1063":1,"2170":1,"2171":1,"2172":1,"2173":1,"2174":1,"2175":1,"2176":1,"2177":1,"2178":1,"2179":1,"2180":1},"3":{"0":1,"1056":1,"1059":1,"1062":1,"1212":1,"1270":1,"1275":1,"1287":1,"1300":7,"1323":2,"1415":1,"1779":2,"2169":2,"2170":1,"2193":1,"2203":1,"2209":1,"2232":1}}],["completion",{"3":{"0":4,"189":1,"348":1,"350":1,"353":3,"419":2,"420":2,"422":1,"423":2,"426":1,"428":1,"430":1,"1091":1,"1107":1,"1108":1,"1112":1,"1270":6,"1286":1,"1300":3,"1311":2,"1323":3,"1324":12,"1339":5,"1380":1,"1390":1,"1395":4,"1415":2,"1416":2,"1417":16,"1421":1,"1427":1,"1436":2,"1667":2,"1669":1,"1975":1,"1976":2,"2170":1,"2172":1,"2232":1}}],["completable",{"3":{"681":1,"684":1}}],["completeenrollmentasync",{"2":{"1293":1},"3":{"1293":1,"1300":2,"1323":1}}],["completely",{"3":{"922":1,"1265":1,"1286":2,"1300":1,"1311":1,"1316":1,"1323":2,"1324":1,"1467":1,"1815":1,"1824":1,"1835":1,"1999":2,"2158":1}}],["complete",{"0":{"926":1},"2":{"161":1},"3":{"0":6,"39":1,"109":1,"160":1,"161":1,"164":1,"168":1,"181":1,"212":1,"214":1,"215":1,"216":1,"217":1,"265":1,"272":1,"273":1,"360":1,"361":1,"420":3,"423":1,"428":1,"434":1,"440":1,"537":1,"574":1,"577":1,"585":2,"597":1,"632":1,"649":1,"675":1,"682":1,"741":1,"743":1,"774":1,"811":1,"916":1,"920":1,"963":1,"965":1,"1049":1,"1108":1,"1212":1,"1219":1,"1229":1,"1247":1,"1259":2,"1265":1,"1286":2,"1300":9,"1309":2,"1311":8,"1322":1,"1323":8,"1324":10,"1326":1,"1330":1,"1336":2,"1339":7,"1340":1,"1350":3,"1352":1,"1357":1,"1359":10,"1369":6,"1395":6,"1396":4,"1406":1,"1409":1,"1410":1,"1411":1,"1415":9,"1416":2,"1417":9,"1427":1,"1432":1,"1436":8,"1442":1,"1443":1,"1444":1,"1453":1,"1456":1,"1468":1,"1472":1,"1477":1,"1485":1,"1488":1,"1489":1,"1496":1,"1497":1,"1526":1,"1531":1,"1532":2,"1575":1,"1577":3,"1582":1,"1591":1,"1599":2,"1611":1,"1632":1,"1653":1,"1654":1,"1658":1,"1661":1,"1670":1,"1687":1,"1705":1,"1750":1,"1765":1,"1849":1,"1891":1,"1898":1,"1931":1,"1932":1,"1936":1,"1941":1,"1964":1,"1976":2,"2001":1,"2034":1,"2083":2,"2085":1,"2126":1,"2143":1,"2149":2,"2152":1,"2153":1,"2165":1,"2200":1,"2201":1,"2208":1,"2231":1,"2233":1}}],["completes",{"3":{"0":2,"235":2,"539":1,"844":1,"897":1,"925":1,"1018":1,"1212":1,"1259":2,"1286":2,"1300":3,"1311":2,"1318":1,"1323":1,"1324":5,"1334":1,"1339":4,"1350":1,"1357":1,"1359":9,"1376":1,"1380":1,"1395":2,"1402":1,"1415":1,"1417":4,"1436":4,"1438":1,"1443":1,"1453":1,"1467":1,"1707":1,"1755":2,"1784":1,"2067":1,"2232":1}}],["completeness",{"3":{"0":2,"265":2,"399":1,"574":1,"725":1,"727":1,"838":2,"840":2,"1311":2,"1369":3,"1396":1,"1415":1,"1436":11,"1438":2,"1592":1,"1630":1,"1638":1,"1653":1,"1669":1,"1671":2,"2086":1,"2159":1}}],["completeasync",{"2":{"202":1,"350":1,"355":1,"419":2,"423":1,"1910":1,"1975":2,"1976":1},"3":{"0":2,"24":1,"195":1,"201":4,"202":1,"350":3,"355":1,"418":1,"419":2,"420":1,"423":1,"1258":2,"1259":9,"1286":1,"1311":2,"1323":1,"1415":1,"1416":1,"1910":1,"1975":2,"1976":1}}],["completedstable",{"3":{"1395":3}}],["completedall",{"3":{"1395":3}}],["completedtask",{"3":{"1259":3,"1270":1,"1286":3,"1309":2,"1310":3,"1311":3,"1323":5,"1324":2,"1339":1,"1395":1,"1417":16,"1436":6,"1865":1}}],["completed",{"2":{"719":1,"790":1,"1765":1,"1770":1,"1821":1},"3":{"0":2,"237":1,"352":1,"397":1,"539":1,"626":3,"628":2,"719":1,"790":2,"792":1,"794":1,"795":1,"809":1,"821":1,"823":1,"988":1,"1042":1,"1048":1,"1086":1,"1212":1,"1251":1,"1259":1,"1263":2,"1270":4,"1275":1,"1286":5,"1300":1,"1310":1,"1311":1,"1318":1,"1323":8,"1324":8,"1339":1,"1350":2,"1359":8,"1380":1,"1395":2,"1396":4,"1415":3,"1417":14,"1423":1,"1427":5,"1436":2,"1455":1,"1467":4,"1470":1,"1471":1,"1474":1,"1492":1,"1591":2,"1640":1,"1765":1,"1769":1,"1770":1,"1821":1,"1978":1,"2220":1}}],["comprehensive",{"3":{"2041":1,"2060":1}}],["compressible",{"3":{"442":1}}],["compressionlevel",{"3":{"1311":2}}],["compression",{"3":{"0":1,"749":1,"1311":7,"1379":1,"1436":1}}],["compressed",{"3":{"0":1,"438":1,"442":1,"591":1,"1323":3,"1560":1,"2126":1}}],["compromises",{"3":{"1311":2}}],["compromise",{"3":{"500":1,"904":2,"906":1,"1270":2,"1300":1,"1301":1,"1323":1,"1350":1,"1436":1,"1639":1}}],["compromised",{"3":{"32":1,"358":1,"602":1,"642":1,"904":1,"1212":1,"1288":1,"1300":2,"1406":1,"1415":1,"1453":1,"1467":2}}],["compilable",{"3":{"1417":1,"1436":1}}],["compilation",{"3":{"6":1,"576":1,"1213":1,"1247":1,"1310":1,"1324":1,"1359":4,"1396":1,"1417":1,"1673":1,"1955":1,"2041":1}}],["compiling",{"3":{"0":1,"908":1,"921":1,"979":1,"1059":1,"1091":1,"1134":1,"1247":1,"1268":1,"1270":2,"1290":1,"1301":1,"1305":1,"1309":2,"1311":1,"1324":3,"1353":1,"1359":2,"1417":1,"1427":1,"1436":6,"1453":1,"1727":1,"2094":1,"2126":1,"2196":1}}],["compiles",{"3":{"26":1,"53":1,"160":1,"373":1,"634":1,"697":1,"799":1,"801":1,"872":1,"920":1,"938":2,"980":1,"1066":1,"1132":2,"1237":1,"1239":1,"1247":5,"1259":3,"1270":5,"1301":1,"1310":1,"1312":1,"1324":1,"1350":1,"1359":19,"1369":1,"1395":4,"1396":1,"1415":2,"1416":4,"1417":5,"1427":3,"1430":1,"1431":1,"1436":15,"1611":4,"1656":1,"1663":1,"1685":1,"1730":1,"1736":1,"1737":1,"1790":1,"1800":1,"1810":1,"1815":1,"1825":2,"1830":1,"1863":1,"1952":1,"2017":1,"2044":1,"2056":1,"2060":1,"2097":1,"2200":1}}],["compiledpermission",{"3":{"1436":1}}],["compiledpermissions",{"3":{"1300":2}}],["compiledexpressioncache",{"3":{"1310":1}}],["compiled",{"0":{"2056":1},"3":{"0":6,"132":1,"184":2,"186":1,"330":1,"333":1,"359":1,"365":1,"422":1,"426":1,"454":1,"545":1,"546":1,"576":1,"591":1,"592":1,"595":1,"674":2,"699":1,"707":1,"848":1,"869":1,"946":1,"979":1,"980":1,"982":1,"983":1,"1057":1,"1058":1,"1059":6,"1060":1,"1061":1,"1063":1,"1067":1,"1161":2,"1213":1,"1214":1,"1237":1,"1238":1,"1241":1,"1242":3,"1247":18,"1252":1,"1259":2,"1276":1,"1278":3,"1283":1,"1286":18,"1297":3,"1300":37,"1304":1,"1305":1,"1309":5,"1310":7,"1311":5,"1319":1,"1323":8,"1324":18,"1341":1,"1348":1,"1350":1,"1353":1,"1359":12,"1380":3,"1395":2,"1396":6,"1409":2,"1414":1,"1415":4,"1416":2,"1417":2,"1427":1,"1428":1,"1431":1,"1432":1,"1436":68,"1438":2,"1445":1,"1455":1,"1466":1,"1486":2,"1489":2,"1491":1,"1504":1,"1531":1,"1577":4,"1626":2,"1665":1,"1670":1,"1671":1,"1675":1,"1782":1,"1850":1,"1851":2,"1865":1,"1902":1,"1923":1,"1945":1,"1956":1,"1962":1,"1964":1,"1988":1,"1989":1,"2028":1,"2041":2,"2042":1,"2047":1,"2054":1,"2100":1,"2142":1,"2194":1,"2199":4,"2200":3,"2203":3}}],["compilergeneratedattribute",{"3":{"1436":2}}],["compilerswitchexpressionthrow",{"3":{"1436":2}}],["compilerservices",{"3":{"980":1,"1312":1,"1436":3}}],["compiler",{"3":{"0":5,"81":1,"135":2,"160":2,"459":1,"465":1,"472":1,"547":1,"620":1,"633":1,"657":1,"784":1,"798":1,"799":1,"800":1,"821":1,"839":1,"840":2,"955":1,"956":1,"971":1,"980":3,"981":2,"982":1,"1004":1,"1005":1,"1036":2,"1050":1,"1052":1,"1161":1,"1162":1,"1174":1,"1212":1,"1218":1,"1229":1,"1234":1,"1237":1,"1247":1,"1259":1,"1270":8,"1286":7,"1300":7,"1310":1,"1311":5,"1312":1,"1323":2,"1324":6,"1339":5,"1359":16,"1369":1,"1395":1,"1396":5,"1402":2,"1415":3,"1416":1,"1417":2,"1436":58,"1449":1,"1450":1,"1488":1,"1500":1,"1737":1,"1803":1,"1804":1,"1810":2,"1833":1,"1888":1,"1892":1,"1919":1,"1951":2,"1955":1,"1957":1,"1959":1,"1996":1}}],["compile",{"0":{"576":1,"2041":1,"2106":1},"1":{"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1956":1,"1957":1,"1958":1,"1959":1},"3":{"0":7,"3":1,"5":1,"6":1,"130":1,"132":1,"133":1,"135":2,"136":1,"265":1,"310":1,"312":1,"412":1,"413":1,"422":1,"425":1,"461":1,"469":1,"472":2,"570":1,"590":1,"592":1,"599":1,"604":1,"609":1,"633":1,"650":1,"698":3,"798":1,"801":1,"838":1,"839":1,"846":1,"848":1,"869":1,"871":1,"882":1,"883":2,"923":1,"926":2,"933":1,"939":1,"948":1,"966":1,"1004":1,"1033":1,"1050":2,"1052":1,"1067":1,"1091":1,"1093":1,"1133":1,"1135":1,"1212":5,"1213":2,"1214":2,"1229":1,"1232":1,"1237":2,"1242":1,"1247":4,"1259":4,"1270":3,"1271":1,"1286":10,"1291":1,"1300":16,"1301":2,"1309":3,"1310":2,"1311":8,"1323":7,"1324":11,"1339":5,"1343":1,"1350":4,"1353":3,"1359":46,"1380":3,"1381":1,"1395":5,"1396":13,"1401":1,"1402":3,"1405":1,"1415":12,"1416":3,"1417":4,"1424":1,"1427":2,"1431":3,"1436":50,"1438":1,"1453":1,"1455":1,"1467":2,"1489":5,"1491":2,"1494":1,"1504":1,"1575":1,"1576":1,"1577":12,"1581":1,"1582":3,"1583":1,"1584":1,"1585":2,"1586":1,"1591":1,"1611":1,"1650":1,"1651":1,"1660":1,"1662":1,"1671":1,"1685":1,"1686":1,"1701":1,"1722":1,"1727":2,"1728":2,"1729":1,"1730":1,"1782":1,"1784":1,"1800":1,"1810":1,"1851":1,"1865":1,"1888":2,"1949":1,"1950":1,"1951":2,"1952":2,"1953":1,"1955":3,"1958":1,"1959":3,"2041":2,"2044":2,"2049":1,"2071":1,"2077":1,"2085":1,"2097":1,"2100":2,"2106":2,"2108":2,"2116":2,"2120":1,"2158":1,"2208":1,"2214":1,"2230":1,"2232":1}}],["company",{"3":{"1359":1,"2220":1}}],["companions",{"3":{"1247":1,"1286":1,"1324":1,"1436":1}}],["companion",{"0":{"1195":1},"3":{"39":1,"66":1,"72":1,"73":1,"78":1,"170":1,"207":1,"406":1,"424":1,"622":1,"659":1,"718":1,"782":1,"825":1,"1147":1,"1190":1,"1237":1,"1241":1,"1247":2,"1259":2,"1270":2,"1285":1,"1286":1,"1300":2,"1311":3,"1323":12,"1324":1,"1350":4,"1359":2,"1380":1,"1391":1,"1395":1,"1396":4,"1415":2,"1416":1,"1427":1,"1428":1,"1436":11,"1438":1,"1459":1,"1474":1,"1476":1,"1479":1,"1481":1,"1486":1,"1487":1,"1488":1,"1492":1,"1577":1,"1602":1,"1626":1,"1658":1,"1674":1,"1756":1,"1855":1,"1968":1,"2076":1,"2228":1,"2234":1}}],["compacthtml",{"3":{"1436":2}}],["compact",{"3":{"881":1,"1223":1,"1229":2,"1233":1,"1286":1,"1289":1,"1292":1,"1300":4,"1311":2,"1324":4,"1328":1,"1344":1,"1350":1,"1359":3,"1384":1,"1392":1,"1395":2,"1396":3,"1402":4,"1403":1,"1416":2,"1433":1,"1436":1,"1559":1,"1591":1,"1596":1,"1597":1}}],["compacted",{"3":{"618":1,"1488":1,"2057":1}}],["compatibility",{"1":{"307":1,"308":1,"309":1,"310":1,"311":1,"312":1,"313":1,"314":1},"3":{"79":1,"99":1,"146":1,"148":1,"151":1,"307":1,"365":1,"556":1,"558":1,"565":1,"566":1,"799":1,"950":1,"965":1,"1033":1,"1211":1,"1229":1,"1247":1,"1259":4,"1286":2,"1300":3,"1323":1,"1324":2,"1339":1,"1350":2,"1359":2,"1436":3,"1455":1,"1504":1,"1546":1,"1732":1,"1748":1,"1889":2,"1892":1,"2112":1}}],["compatible",{"3":{"0":1,"78":1,"136":1,"149":1,"215":1,"412":1,"435":1,"511":1,"568":1,"626":1,"680":1,"681":1,"689":1,"734":1,"757":1,"762":1,"954":1,"1061":1,"1091":1,"1213":2,"1259":1,"1286":3,"1300":1,"1311":1,"1324":1,"1339":1,"1359":1,"1395":1,"1419":1,"1427":1,"1432":1,"1455":1,"1464":1,"1467":1,"1488":1,"1534":1,"1544":1,"1577":2,"1733":1,"1889":1,"2004":1,"2120":1,"2202":2}}],["comparability",{"3":{"1802":1}}],["comparable",{"3":{"735":1,"766":1,"1012":1,"1014":1,"1286":1,"1350":2,"1359":2,"1396":1,"1417":1,"1436":1,"1536":2,"1537":1,"1592":1,"1794":1,"1795":1,"1862":1}}],["comparator",{"3":{"1402":1}}],["comparatively",{"3":{"108":1,"1804":1}}],["comparing",{"3":{"619":1,"964":1,"972":1,"1018":1,"1019":1,"1033":1,"1229":1,"1286":4,"1290":1,"1300":3,"1301":1,"1311":1,"1323":3,"1324":3,"1350":1,"1359":7,"1366":1,"1373":1,"1375":1,"1395":3,"1396":2,"1402":1,"1415":1,"1436":6,"1453":1,"1490":1,"1492":1,"1639":1,"1798":1,"1995":1,"2043":1,"2047":1,"2244":1}}],["comparisons",{"3":{"1241":2,"1247":7,"1273":1,"1286":4,"1359":10,"1396":2,"1496":1,"1630":1,"1631":1,"1635":1,"1637":1,"1639":1}}],["comparison",{"0":{"1528":1,"1579":1,"1593":1},"3":{"0":3,"150":1,"178":1,"179":1,"243":1,"261":1,"279":1,"309":2,"310":1,"311":1,"322":1,"342":1,"627":1,"766":1,"827":2,"838":1,"854":1,"889":1,"946":3,"954":1,"970":1,"972":3,"973":1,"975":1,"997":1,"1020":2,"1033":1,"1038":1,"1074":1,"1161":1,"1184":1,"1229":1,"1237":4,"1247":5,"1259":1,"1270":1,"1271":4,"1281":1,"1286":11,"1294":1,"1295":1,"1300":19,"1301":1,"1311":3,"1323":1,"1324":8,"1337":1,"1339":8,"1350":4,"1352":2,"1355":1,"1359":28,"1369":1,"1395":2,"1396":7,"1402":2,"1415":8,"1417":1,"1427":1,"1436":21,"1438":2,"1522":1,"1587":1,"1630":1,"1631":1,"1673":1,"1765":1,"1872":2,"1874":1,"1877":3,"1899":1,"1900":1,"1902":4,"1905":1,"1996":1,"2073":1,"2107":1,"2232":1}}],["comparemembers",{"3":{"1436":1}}],["comparebyscorethenindex",{"3":{"1359":1}}],["compareexchange",{"2":{"1337":1},"3":{"1337":1,"1339":2}}],["compareto",{"3":{"1286":2}}],["comparers",{"3":{"1300":1}}],["comparertype",{"3":{"1050":1,"1286":2}}],["comparer",{"3":{"585":1,"1050":1,"1212":1,"1222":1,"1229":1,"1237":2,"1247":1,"1286":8,"1300":7,"1324":3,"1339":1,"1359":6,"1395":1,"1396":2,"1431":1,"1436":4,"1438":2,"1496":1}}],["compared",{"0":{"1902":1},"3":{"0":1,"186":1,"499":1,"591":1,"592":1,"628":1,"649":1,"838":1,"946":2,"948":1,"974":1,"1247":3,"1270":2,"1279":1,"1286":3,"1300":8,"1301":1,"1323":2,"1324":2,"1339":1,"1350":1,"1359":4,"1369":1,"1395":1,"1396":4,"1398":1,"1415":1,"1417":1,"1436":7,"1438":1,"1492":1,"1496":1,"1631":1,"1641":1,"1699":1,"1767":1,"1962":2,"2047":1}}],["compares",{"3":{"0":2,"136":1,"318":1,"320":1,"341":1,"350":1,"363":1,"572":1,"608":1,"633":1,"715":1,"727":1,"827":1,"872":1,"889":1,"964":1,"1050":1,"1186":1,"1234":1,"1237":2,"1241":1,"1247":2,"1266":1,"1270":1,"1271":1,"1286":9,"1289":1,"1293":1,"1297":1,"1300":13,"1323":4,"1324":8,"1337":1,"1339":2,"1350":4,"1353":1,"1359":12,"1369":1,"1380":2,"1395":7,"1396":3,"1415":7,"1432":1,"1435":1,"1436":16,"1438":3,"1455":1,"1456":1,"1467":1,"1488":1,"1490":1,"1727":1,"1839":1,"1902":3,"1994":1,"1996":1,"2024":1,"2046":1,"2047":1,"2059":1,"2107":1,"2144":1}}],["compare",{"2":{"549":1},"3":{"0":3,"157":1,"310":1,"318":1,"319":1,"342":2,"343":1,"344":1,"484":1,"490":1,"491":1,"492":1,"493":1,"549":1,"633":1,"838":1,"857":1,"862":1,"973":1,"996":2,"1212":1,"1229":2,"1237":7,"1270":4,"1286":9,"1292":1,"1297":1,"1300":6,"1309":1,"1310":1,"1311":2,"1316":1,"1323":5,"1324":11,"1339":1,"1350":3,"1359":15,"1369":2,"1380":2,"1395":4,"1396":5,"1402":1,"1405":1,"1415":3,"1417":1,"1436":20,"1438":1,"1455":1,"1537":1,"1577":2,"1668":1,"1749":1,"1803":1,"1810":1,"1871":1,"1872":2,"1877":2,"1905":2,"1908":1,"1909":1,"1912":2,"1994":3,"2184":1}}],["compensated",{"3":{"1359":1}}],["compensate",{"3":{"536":1,"1324":1,"2167":1,"2169":1}}],["compensates",{"3":{"536":1,"1416":1,"1436":1,"1749":1}}],["compensating",{"3":{"0":4,"49":1,"166":1,"293":1,"534":2,"536":7,"538":2,"776":1,"809":3,"810":2,"1212":2,"1281":2,"1286":6,"1309":1,"1359":1,"1369":1,"1396":2,"1415":1,"1436":2,"1477":1,"1526":1,"1534":2,"1596":1,"1749":2,"1765":1,"1792":1,"1849":1,"1852":1,"1858":2,"1866":1,"2113":1,"2163":4,"2164":1,"2165":1,"2168":3,"2169":1,"2189":1,"2206":1,"2232":2}}],["compensations",{"3":{"541":1}}],["compensation",{"0":{"2163":1},"1":{"533":1,"534":1,"535":1,"536":1,"537":1,"538":1,"539":1,"540":1,"541":1,"2161":1,"2162":1,"2163":1,"2164":1,"2165":1,"2166":1,"2167":1,"2168":1,"2169":1},"3":{"0":4,"533":1,"534":2,"536":4,"537":1,"538":3,"539":1,"541":2,"793":1,"809":1,"810":1,"813":2,"815":1,"896":2,"901":1,"926":1,"987":2,"992":1,"1012":1,"1093":1,"1147":1,"1212":2,"1286":1,"1359":1,"1526":1,"1547":2,"1572":1,"1577":1,"1591":2,"1665":1,"1749":1,"1755":1,"1779":2,"2160":2,"2161":2,"2163":2,"2167":3,"2168":2,"2169":3,"2180":1,"2206":1,"2215":1,"2232":1}}],["competition",{"3":{"1396":2}}],["competitors",{"3":{"1286":1,"2144":1}}],["competing",{"3":{"0":1,"341":1,"342":2,"1286":2,"1323":2,"1417":1,"1436":2,"1453":1,"1455":1,"1651":1,"1875":1,"1877":1,"2164":1}}],["competencies",{"0":{"2219":1}}],["competent",{"3":{"708":1}}],["competes",{"3":{"905":1,"1323":1,"1369":1}}],["compete",{"3":{"517":1,"1258":2,"1259":2,"1270":1,"1317":1,"1323":1,"1359":3,"1459":1}}],["compound",{"3":{"1259":1,"1339":1,"1687":1}}],["compounds",{"3":{"1259":1,"1324":1}}],["compounding",{"3":{"872":1}}],["composability",{"3":{"546":1,"1154":1,"1359":1}}],["composable",{"1":{"1813":1,"1814":1,"1815":1,"1816":1,"1817":1},"3":{"0":1,"110":1,"544":1,"1202":1,"1203":1,"1229":2,"1247":2,"1265":1,"1270":1,"1271":1,"1286":1,"1309":1,"1350":1,"1359":4,"1396":1,"1416":1,"1427":1,"1662":1,"1812":1,"1813":2,"1814":1,"1817":1,"1832":1,"1997":2,"2206":1}}],["composing",{"3":{"43":1,"545":1,"550":1,"552":1,"749":1,"826":1,"1018":1,"1227":1,"1239":1,"1247":3,"1271":1,"1286":1,"1299":1,"1300":2,"1301":1,"1316":1,"1323":1,"1324":8,"1339":1,"1347":1,"1350":1,"1359":9,"1395":1,"1396":1,"1415":2,"1427":1,"1436":2,"1479":1,"1480":1,"1805":1,"1815":1,"1833":1,"1991":1}}],["compositor",{"0":{"1480":1}}],["compositeformat",{"3":{"1369":1}}],["compositekeything",{"3":{"1199":3,"1207":1}}],["composites",{"3":{"0":1,"545":1,"547":1,"549":1,"1234":1,"1247":6,"1395":1}}],["composite",{"3":{"0":2,"265":1,"268":1,"368":1,"370":2,"374":1,"375":1,"545":2,"546":1,"549":1,"550":2,"551":1,"564":1,"591":1,"594":1,"599":1,"624":1,"626":2,"629":1,"644":1,"1247":4,"1269":1,"1270":1,"1271":6,"1278":1,"1286":6,"1300":1,"1324":1,"1347":2,"1350":7,"1359":21,"1362":4,"1369":11,"1376":1,"1380":1,"1387":1,"1395":13,"1396":6,"1412":1,"1415":2,"1417":1,"1453":1,"1454":2,"1455":1,"1467":1,"1475":1,"1492":1,"1531":1,"1577":1,"1582":1,"1640":1,"1666":1,"1829":1,"2136":1,"2232":1}}],["compositional",{"3":{"1286":1}}],["compositiontestentity",{"3":{"1199":4,"1207":1}}],["composition",{"0":{"1316":1,"1322":1,"1354":1,"1449":1,"1486":1,"1935":1,"2056":1},"1":{"580":1,"581":1,"582":1,"583":1,"584":1,"585":1,"586":1,"587":1,"646":1,"647":1,"648":1,"649":1,"650":1,"651":1,"652":1,"653":1,"1313":1,"1314":1,"1315":1,"1316":1,"1317":1,"1318":1,"1319":1,"1320":1,"1321":1,"1322":1,"1323":1,"1416":1,"1485":1,"1486":1,"1487":1,"1488":1,"1489":1,"1490":1,"1491":1,"1492":1,"1493":1,"1494":1},"3":{"0":9,"115":2,"122":1,"123":1,"135":1,"265":1,"326":1,"543":4,"545":1,"547":1,"549":3,"550":3,"580":1,"582":2,"583":2,"584":2,"585":1,"587":1,"591":1,"646":1,"650":1,"651":1,"680":1,"682":1,"749":1,"825":1,"826":1,"847":1,"856":1,"881":1,"890":1,"913":1,"914":2,"980":1,"984":1,"1016":2,"1018":3,"1021":1,"1066":1,"1192":7,"1202":7,"1203":7,"1212":3,"1229":3,"1247":12,"1260":1,"1269":2,"1270":4,"1271":5,"1276":1,"1285":1,"1286":19,"1300":1,"1301":3,"1302":1,"1307":1,"1309":15,"1310":2,"1311":12,"1312":2,"1313":4,"1314":1,"1317":1,"1320":4,"1321":1,"1322":2,"1323":21,"1324":17,"1328":1,"1329":1,"1339":6,"1340":1,"1348":1,"1350":2,"1352":1,"1353":1,"1354":1,"1359":43,"1368":1,"1369":5,"1370":1,"1379":1,"1380":10,"1381":1,"1392":1,"1395":7,"1396":14,"1400":1,"1402":3,"1403":1,"1413":1,"1415":11,"1416":17,"1417":21,"1418":1,"1421":1,"1427":2,"1428":1,"1436":21,"1438":4,"1449":2,"1485":1,"1487":1,"1488":2,"1490":1,"1492":2,"1496":11,"1513":1,"1526":3,"1555":2,"1577":3,"1586":1,"1641":1,"1650":1,"1660":1,"1662":4,"1790":1,"1791":1,"1815":1,"1829":1,"1833":1,"1834":2,"1884":1,"1886":1,"1915":1,"1923":1,"1924":1,"1935":1,"1941":2,"1944":1,"1948":1,"1956":1,"2032":1,"2056":1,"2060":1,"2074":1,"2077":1,"2115":1,"2119":1,"2123":2,"2172":1,"2227":1,"2232":3}}],["composeconnectionstring",{"3":{"1436":4}}],["composeconfirmationlink",{"3":{"1323":1}}],["composemessage",{"3":{"1286":1}}],["composer",{"3":{"1247":2,"1380":2,"1389":1,"1395":14,"1436":2}}],["composers",{"3":{"1247":2,"1395":1,"1438":1}}],["composeresetlink",{"2":{"857":1},"3":{"854":1,"857":1,"1323":3}}],["composebody",{"3":{"854":1,"1323":2}}],["composeadminconnectionstring",{"2":{"640":1},"3":{"640":1,"1436":1}}],["composedpipeline",{"2":{"121":1,"127":1},"3":{"121":1,"127":1,"1436":2}}],["composed",{"0":{"1317":1,"1829":1},"3":{"0":5,"109":1,"117":1,"265":1,"319":1,"443":1,"544":1,"545":1,"546":1,"547":1,"549":3,"573":1,"599":1,"657":1,"698":1,"699":1,"854":1,"1091":1,"1094":1,"1150":1,"1168":1,"1237":1,"1239":3,"1247":8,"1262":1,"1269":1,"1270":4,"1271":3,"1272":1,"1276":1,"1286":3,"1294":1,"1300":2,"1306":1,"1309":2,"1311":1,"1323":3,"1324":7,"1325":1,"1339":3,"1350":3,"1353":2,"1359":24,"1369":2,"1380":5,"1381":1,"1394":1,"1395":11,"1396":4,"1402":2,"1416":3,"1417":3,"1425":1,"1427":1,"1435":1,"1436":5,"1438":2,"1455":2,"1467":2,"1479":1,"1480":2,"1486":1,"1496":1,"1509":1,"1526":1,"1555":1,"1577":1,"1631":1,"1651":1,"1669":1,"1685":1,"1701":1,"1737":1,"1814":1,"1815":4,"1816":1,"1817":1,"1818":1,"1821":1,"1827":1,"1828":1,"1851":1,"1883":1,"1915":1}}],["composesubject",{"3":{"854":1,"1323":2}}],["composes",{"3":{"0":5,"39":1,"103":1,"109":2,"115":1,"188":1,"246":1,"265":1,"317":1,"319":1,"331":2,"351":1,"545":2,"571":1,"648":1,"657":1,"701":2,"702":1,"827":2,"896":1,"913":1,"917":1,"1018":2,"1089":1,"1154":1,"1168":1,"1212":3,"1220":1,"1229":3,"1232":1,"1237":1,"1238":1,"1246":1,"1247":3,"1267":1,"1270":3,"1271":3,"1276":1,"1284":1,"1286":16,"1300":3,"1309":5,"1311":2,"1312":1,"1313":2,"1318":1,"1323":1,"1324":16,"1337":1,"1338":1,"1339":1,"1349":1,"1350":8,"1353":1,"1359":20,"1360":1,"1366":1,"1368":1,"1369":4,"1380":2,"1389":1,"1395":18,"1396":6,"1412":1,"1415":4,"1416":3,"1417":4,"1418":1,"1427":4,"1435":1,"1436":7,"1438":1,"1467":1,"1492":2,"1507":1,"1526":2,"1577":1,"1632":1,"1653":1,"1663":1,"1672":1,"1702":1,"1805":2,"1815":2,"1824":1,"1825":1,"1851":1,"1883":1,"1920":1,"1964":1,"1992":1,"2180":2,"2230":1,"2232":1}}],["compose",{"0":{"1480":1,"1506":1,"1653":1},"1":{"1828":1,"1829":1,"1830":1,"1831":1,"1832":1,"1833":1,"1834":1},"3":{"0":2,"109":1,"117":1,"187":1,"319":1,"545":1,"618":1,"649":1,"698":1,"749":2,"819":1,"1018":1,"1091":1,"1202":1,"1203":1,"1212":1,"1232":1,"1237":3,"1238":1,"1247":2,"1259":1,"1262":1,"1269":1,"1270":3,"1271":1,"1278":1,"1286":7,"1300":3,"1309":2,"1311":2,"1313":1,"1323":5,"1324":7,"1339":2,"1350":1,"1353":1,"1358":1,"1359":5,"1368":1,"1369":1,"1380":1,"1391":1,"1395":2,"1396":2,"1402":4,"1415":1,"1416":1,"1417":1,"1422":1,"1427":2,"1436":7,"1440":1,"1450":1,"1471":1,"1479":2,"1480":18,"1482":1,"1483":1,"1555":1,"1653":1,"1662":1,"1667":1,"1718":1,"1727":1,"1779":1,"1815":1,"1817":2,"1824":1,"1828":2,"1834":2,"1855":1,"1994":1,"1997":1,"2005":1,"2038":1,"2080":1,"2170":1,"2206":1,"2227":1}}],["componentparameters",{"3":{"1436":1}}],["componentization",{"3":{"1417":1}}],["componenttype",{"3":{"1324":1,"1395":1,"1416":4}}],["componentbase",{"3":{"1324":11,"1383":1,"1395":2,"1396":2,"1415":4}}],["componentmodel",{"3":{"1259":1,"1286":4,"1300":11,"1309":2,"1311":2,"1323":4,"1324":12,"1339":3,"1395":10,"1396":2,"1427":2,"1436":1}}],["componentspage",{"3":{"1643":1,"1714":1}}],["componentspagee2etests",{"3":{"618":1,"1199":1,"1207":2,"1436":2,"1577":2,"1643":1,"1714":1}}],["componentsgallery",{"3":{"1324":1,"1436":1}}],["componentssnapshottests",{"3":{"1199":3,"1207":6,"1417":1,"1436":3,"1438":4,"1525":1,"1526":2,"1531":1,"1535":1}}],["components",{"1":{"1324":1},"2":{"650":1,"1324":2,"1395":1,"1416":1},"3":{"0":3,"88":1,"109":2,"135":1,"265":1,"273":1,"275":1,"370":1,"412":1,"413":4,"415":1,"426":1,"490":1,"556":7,"560":1,"618":1,"648":1,"649":2,"650":2,"651":2,"653":1,"682":2,"861":2,"863":1,"864":1,"931":1,"954":1,"1004":1,"1018":1,"1059":5,"1062":1,"1119":1,"1184":1,"1202":1,"1203":9,"1207":94,"1212":1,"1213":1,"1229":2,"1279":1,"1286":2,"1300":1,"1311":3,"1323":1,"1324":99,"1339":1,"1381":1,"1389":2,"1395":32,"1396":15,"1402":2,"1414":1,"1415":5,"1416":16,"1417":61,"1426":1,"1428":1,"1432":3,"1433":1,"1434":2,"1436":42,"1437":4,"1438":16,"1453":1,"1454":1,"1486":1,"1487":2,"1488":3,"1491":1,"1496":1,"1525":1,"1526":4,"1530":1,"1531":2,"1532":1,"1535":1,"1555":7,"1556":2,"1557":1,"1564":1,"1572":2,"1577":10,"1586":4,"1591":1,"1597":1,"1641":2,"1643":2,"1644":3,"1653":2,"1669":2,"1687":3,"1760":1,"1898":1,"2043":1,"2054":1,"2073":1,"2075":2,"2077":2,"2078":1,"2079":1,"2080":1,"2116":1,"2118":1,"2119":1,"2121":2,"2122":1,"2176":1,"2202":1,"2227":1}}],["component",{"0":{"1432":1,"1555":1,"2078":1},"1":{"951":1,"952":1,"953":1,"954":1,"955":1,"956":1,"957":1,"958":1,"959":1,"960":1},"2":{"1381":1},"3":{"0":12,"88":1,"109":4,"135":4,"139":2,"165":1,"189":1,"201":1,"265":2,"266":2,"267":1,"273":5,"275":1,"374":1,"375":1,"397":1,"407":1,"412":1,"413":1,"423":1,"427":1,"482":3,"484":1,"511":1,"556":3,"577":1,"647":1,"649":7,"650":1,"651":1,"682":3,"683":3,"684":1,"689":1,"694":1,"827":2,"881":1,"910":1,"915":1,"931":1,"951":1,"952":1,"953":4,"954":6,"955":2,"956":3,"957":1,"960":2,"996":1,"999":1,"1004":2,"1057":1,"1059":2,"1201":1,"1202":1,"1203":1,"1212":4,"1213":2,"1216":1,"1229":1,"1237":1,"1280":1,"1286":3,"1300":2,"1309":1,"1310":1,"1311":9,"1323":3,"1324":180,"1333":1,"1339":4,"1350":2,"1359":3,"1381":2,"1383":1,"1384":1,"1385":1,"1387":1,"1389":3,"1390":1,"1391":1,"1393":1,"1395":118,"1396":42,"1402":16,"1409":1,"1414":2,"1415":7,"1416":22,"1417":102,"1426":1,"1427":1,"1428":2,"1429":1,"1431":1,"1432":3,"1434":1,"1436":85,"1437":5,"1438":9,"1443":1,"1447":1,"1450":1,"1455":3,"1461":1,"1465":1,"1467":4,"1474":2,"1476":1,"1487":1,"1488":4,"1491":5,"1496":3,"1497":1,"1499":1,"1513":1,"1526":5,"1530":1,"1531":2,"1532":1,"1536":1,"1544":1,"1555":2,"1556":2,"1557":3,"1565":2,"1572":2,"1577":3,"1586":1,"1591":1,"1627":1,"1643":1,"1645":1,"1653":2,"1660":2,"1669":6,"1671":2,"1702":2,"1711":1,"1714":1,"1750":1,"1759":2,"1773":1,"1796":1,"1865":1,"1868":1,"2043":2,"2054":1,"2055":1,"2059":1,"2072":2,"2075":1,"2077":2,"2078":2,"2079":2,"2080":3,"2083":4,"2085":1,"2086":1,"2116":4,"2117":2,"2118":2,"2119":2,"2121":2,"2123":3,"2147":1,"2156":2,"2202":1,"2230":1,"2232":2}}],["comes",{"3":{"0":3,"101":1,"195":2,"229":1,"318":1,"360":1,"402":1,"413":1,"446":1,"572":1,"599":1,"601":1,"635":1,"651":1,"665":1,"673":1,"678":1,"690":1,"692":1,"697":1,"716":1,"726":1,"741":2,"766":1,"768":1,"818":1,"905":1,"915":1,"939":1,"953":1,"972":1,"982":1,"988":1,"996":1,"1018":1,"1042":1,"1050":1,"1115":1,"1184":1,"1225":1,"1228":1,"1229":1,"1237":2,"1244":2,"1247":2,"1254":1,"1255":1,"1259":3,"1270":3,"1271":1,"1275":2,"1286":20,"1294":1,"1300":9,"1301":4,"1309":7,"1311":10,"1312":1,"1323":5,"1324":13,"1332":1,"1336":1,"1339":3,"1350":5,"1359":37,"1364":1,"1369":3,"1373":1,"1380":3,"1388":1,"1395":28,"1396":22,"1402":6,"1415":9,"1417":3,"1419":1,"1422":1,"1425":1,"1435":1,"1436":5,"1438":2,"1441":1,"1447":1,"1451":1,"1455":1,"1460":1,"1466":1,"1467":1,"1474":1,"1477":1,"1484":1,"1491":1,"1492":1,"1498":1,"1591":1,"1627":1,"1653":1,"1702":2,"1710":1,"1727":1,"1825":1,"1837":1,"1923":1,"1927":1,"1948":1,"1951":1,"1954":1,"1982":1,"1997":1,"2005":1,"2011":1,"2015":1,"2018":1,"2019":1,"2034":1,"2074":1,"2096":2,"2111":1,"2112":1,"2140":1,"2146":1,"2161":2,"2165":1,"2168":1,"2184":1}}],["come",{"0":{"140":1,"1442":1,"1787":1},"3":{"0":2,"95":1,"135":1,"219":1,"265":1,"273":1,"282":1,"329":1,"360":1,"372":1,"397":1,"400":1,"401":1,"402":1,"529":1,"544":1,"583":1,"617":1,"626":1,"633":1,"640":1,"649":1,"657":1,"703":1,"726":1,"741":2,"774":1,"818":1,"827":1,"889":1,"905":1,"907":1,"913":1,"914":1,"954":1,"1069":1,"1090":1,"1092":1,"1099":1,"1124":2,"1186":1,"1194":1,"1195":2,"1211":1,"1217":1,"1231":1,"1237":1,"1241":1,"1242":1,"1247":4,"1256":1,"1259":4,"1262":1,"1271":1,"1286":17,"1289":1,"1300":2,"1306":1,"1309":3,"1311":3,"1312":1,"1315":1,"1316":1,"1323":2,"1324":18,"1328":1,"1331":1,"1332":1,"1339":12,"1343":1,"1344":1,"1345":1,"1350":9,"1354":1,"1359":6,"1369":2,"1384":1,"1388":1,"1395":13,"1396":9,"1399":1,"1402":7,"1409":1,"1410":1,"1414":2,"1415":5,"1416":2,"1417":5,"1425":1,"1427":2,"1433":1,"1436":12,"1438":3,"1441":1,"1442":2,"1443":2,"1445":1,"1449":1,"1454":1,"1455":1,"1467":1,"1485":1,"1489":1,"1492":1,"1503":1,"1510":1,"1627":1,"1631":2,"1646":1,"1651":1,"1677":1,"1702":1,"1730":1,"1731":1,"1760":1,"1761":1,"1765":1,"1826":1,"1843":1,"1876":1,"1881":1,"1982":1,"1985":1,"1986":1,"1987":1,"2002":1,"2053":1,"2076":1,"2081":1,"2091":1,"2112":1,"2128":1,"2142":1}}],["commodity",{"3":{"1436":1,"1900":1}}],["commons",{"3":{"1436":2}}],["commontags",{"2":{"1469":1},"3":{"1436":1,"1465":1,"1467":2,"1469":1,"1526":1,"1535":1,"1591":1}}],["commonest",{"3":{"1270":1}}],["commonauditdeletioncolumnsandoutboxordering",{"3":{"1209":6}}],["commonarchitecturemap",{"2":{"80":1,"132":1,"1161":1,"1891":1,"2103":1},"3":{"80":2,"132":1,"1161":1,"1199":36,"1207":2,"1323":2,"1324":2,"1436":84,"1487":1,"1489":4,"1577":1,"1891":1,"2103":1}}],["commonv1141pushnotificationdedupindexsoftdeletefilter",{"3":{"1209":2,"1286":1}}],["commonv1120outboxleaseandsoftdeleteindexfilters",{"3":{"564":1,"891":4,"1209":8}}],["commonvalidationrules",{"3":{"1207":12,"1271":37,"1289":1,"1300":5,"1359":67,"1436":2,"1496":2,"1586":1,"1829":1}}],["commonvalidationrulestests",{"3":{"1199":1,"1207":8,"1271":4}}],["commoninvariantstests",{"3":{"1199":1,"1207":3,"1237":1}}],["commoninvariants",{"2":{"1271":1,"1350":2,"1359":1,"1402":1,"1405":1,"2085":1},"3":{"1199":26,"1203":1,"1207":2,"1230":1,"1234":3,"1237":28,"1271":9,"1309":6,"1311":2,"1344":1,"1350":42,"1359":15,"1396":11,"1402":18,"1405":1,"1415":7,"1436":1,"1662":1,"1685":1,"2085":2}}],["commonly",{"3":{"122":1,"1263":1,"1270":1,"1271":1,"1282":1,"1291":1,"1295":1,"2015":1}}],["commonforwardedheaderstests",{"3":{"1199":1,"1207":2}}],["commonforwardedheadersextensions",{"3":{"220":1,"749":2,"1124":1,"1186":1,"1199":1,"1203":1,"1207":2,"1208":1,"1311":7,"1496":1}}],["commonforwardedheaders",{"2":{"220":1,"747":1,"749":1,"1124":1},"3":{"0":1,"220":2,"747":1,"749":4,"751":2,"1124":3,"1199":4,"1203":1,"1207":2,"1311":16,"1496":1}}],["common",{"0":{"95":1,"465":1,"1442":1,"1443":1,"1444":1,"1453":1,"1454":1,"1602":1,"1799":1,"1838":1,"2022":1,"2227":1},"1":{"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1,"1088":1,"1089":1,"1090":1,"1091":1,"1092":1,"1093":1,"1094":1,"1095":1,"1096":1,"1190":1,"1191":1,"1192":1,"1193":1,"1194":1,"1195":1,"1324":1,"1418":1,"1419":1,"1420":1,"1421":1,"1422":1,"1423":1,"1424":1,"1425":1,"1426":1,"1427":1,"1573":1,"1574":1,"1575":1,"1576":1,"1577":1,"1578":1,"1579":1,"1580":1,"1581":1,"1582":1,"1583":1,"1584":1,"1585":1,"1586":1,"1646":1,"1647":1,"1648":1,"1649":1,"1650":1,"1651":1,"1652":1,"1653":1,"1654":1,"1655":1,"1656":1,"1657":1,"1658":1,"1659":1,"1660":1,"1661":1,"1662":1,"1663":1,"1664":1,"1665":1,"1666":1,"1667":1,"1668":1,"1669":1,"1670":1,"1671":1,"1672":1,"1673":1,"1674":1,"1675":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1698":1,"1699":1,"1700":1,"1701":1,"1702":1,"1703":1,"1704":1,"1705":1,"1717":1,"1718":1,"1719":1,"1720":1,"1721":1,"1722":1,"1723":1,"1724":1,"1725":1,"1778":1,"1779":1},"2":{"24":1,"39":3,"53":1,"61":2,"67":1,"68":1,"70":1,"78":1,"85":1,"99":1,"100":1,"107":2,"109":1,"111":1,"117":1,"124":1,"126":1,"130":1,"132":2,"135":2,"137":1,"138":1,"139":3,"140":1,"141":6,"142":6,"145":2,"147":1,"150":1,"152":2,"160":1,"180":1,"186":1,"190":1,"207":2,"214":2,"216":1,"225":2,"241":2,"247":1,"250":1,"252":1,"255":2,"258":1,"259":2,"261":1,"265":1,"268":3,"273":1,"281":2,"310":1,"359":2,"370":1,"373":1,"388":1,"395":4,"400":7,"404":2,"406":1,"408":1,"412":1,"436":1,"460":1,"481":1,"482":3,"486":3,"487":3,"488":3,"489":3,"490":3,"491":3,"492":3,"493":3,"494":3,"495":3,"509":6,"511":3,"514":1,"526":1,"527":2,"528":3,"558":1,"572":2,"576":1,"577":1,"578":2,"591":1,"617":1,"618":1,"634":1,"635":1,"638":3,"648":1,"649":2,"681":1,"683":1,"684":1,"686":2,"731":1,"733":1,"789":1,"821":1,"822":3,"823":3,"825":2,"826":3,"832":1,"836":1,"838":1,"840":1,"861":1,"880":1,"897":2,"899":1,"906":1,"912":1,"914":1,"917":1,"936":1,"938":2,"939":6,"941":2,"942":1,"953":1,"954":1,"957":1,"963":1,"966":1,"982":1,"984":1,"1004":2,"1012":1,"1016":1,"1036":3,"1044":1,"1051":1,"1057":1,"1058":1,"1062":1,"1066":1,"1067":2,"1068":3,"1069":1,"1074":1,"1089":3,"1090":1,"1091":1,"1093":1,"1124":2,"1161":2,"1191":1,"1194":1,"1200":1,"1201":1,"1203":2,"1211":1,"1213":5,"1214":1,"1215":2,"1217":1,"1222":1,"1229":3,"1230":2,"1236":1,"1237":3,"1247":3,"1256":1,"1259":6,"1264":1,"1268":1,"1270":6,"1271":1,"1275":1,"1286":5,"1287":2,"1300":2,"1301":3,"1309":2,"1310":2,"1317":1,"1321":1,"1323":5,"1324":1,"1325":5,"1326":1,"1327":1,"1328":1,"1329":1,"1330":1,"1333":1,"1336":1,"1337":1,"1339":1,"1341":1,"1351":1,"1386":1,"1414":1,"1416":3,"1417":9,"1418":4,"1425":1,"1428":1,"1430":3,"1434":3,"1436":2,"1437":4,"1438":1,"1439":1,"1445":1,"1451":1,"1452":1,"1453":3,"1454":2,"1456":1,"1460":1,"1476":1,"1480":1,"1487":3,"1488":1,"1490":2,"1496":3,"1497":2,"1503":8,"1509":1,"1513":2,"1523":1,"1525":1,"1526":1,"1527":2,"1535":1,"1576":1,"1577":1,"1578":1,"1585":3,"1599":1,"1605":1,"1615":2,"1642":1,"1643":1,"1648":1,"1650":5,"1653":2,"1654":1,"1656":2,"1660":12,"1661":1,"1675":1,"1677":1,"1693":1,"1702":5,"1711":1,"1732":1,"1739":1,"1741":1,"1761":1,"1781":1,"1782":2,"1790":1,"1793":1,"1805":1,"1807":1,"1812":1,"1815":1,"1817":1,"1827":1,"1828":1,"1829":1,"1834":1,"1843":1,"1846":1,"1853":1,"1862":1,"1865":1,"1868":1,"1877":1,"1886":1,"1892":1,"1895":1,"1896":1,"1899":1,"1902":2,"1905":1,"1912":1,"1919":1,"1922":1,"1924":1,"1930":1,"1935":1,"1941":1,"1950":1,"1953":1,"1959":1,"1962":1,"1965":1,"1972":1,"1975":1,"1979":1,"1985":1,"1991":1,"1997":1,"2002":1,"2004":2,"2006":9,"2008":1,"2013":8,"2014":3,"2016":1,"2022":1,"2024":1,"2028":2,"2030":1,"2038":1,"2042":1,"2044":1,"2046":1,"2049":1,"2055":2,"2057":1,"2059":2,"2060":1,"2063":1,"2070":1,"2071":2,"2077":1,"2080":1,"2086":1,"2100":2,"2101":1,"2108":1,"2111":1,"2115":1,"2116":1,"2120":3,"2123":1,"2128":1,"2131":1,"2133":1,"2139":1,"2142":1,"2146":1,"2149":1,"2150":1,"2151":1,"2152":1,"2153":1,"2156":1,"2157":3,"2160":9,"2169":1,"2172":1,"2177":1,"2178":1,"2180":2,"2191":1,"2193":2,"2203":1,"2211":1},"3":{"0":96,"4":1,"5":1,"11":1,"15":4,"17":4,"18":1,"19":3,"22":2,"23":1,"24":11,"25":9,"26":18,"27":17,"31":5,"34":2,"39":9,"43":4,"46":1,"53":3,"54":2,"59":2,"61":4,"63":2,"67":1,"68":7,"69":1,"70":5,"71":4,"72":6,"74":14,"76":1,"78":2,"79":2,"80":6,"81":10,"82":4,"85":1,"95":3,"98":2,"99":3,"100":5,"103":5,"107":2,"108":1,"109":42,"110":1,"111":5,"115":1,"117":7,"121":10,"122":5,"123":10,"124":1,"125":10,"126":7,"128":4,"130":6,"132":4,"133":1,"134":1,"135":25,"136":7,"137":6,"138":5,"139":6,"140":4,"141":7,"142":14,"145":5,"146":3,"147":23,"149":2,"150":11,"152":5,"157":5,"160":8,"161":6,"164":2,"166":10,"168":1,"170":3,"171":3,"173":1,"175":5,"178":6,"179":11,"180":5,"181":2,"186":18,"189":11,"190":12,"193":2,"196":5,"197":1,"198":7,"200":4,"201":3,"202":14,"203":2,"207":2,"208":1,"214":4,"216":5,"217":2,"218":2,"219":6,"220":8,"225":9,"228":8,"230":8,"231":8,"235":12,"237":2,"238":1,"241":2,"243":28,"245":8,"246":17,"247":1,"250":2,"251":1,"252":3,"254":1,"255":11,"256":8,"257":2,"258":8,"259":13,"260":3,"261":5,"265":30,"268":5,"272":1,"273":3,"275":1,"281":3,"284":7,"285":6,"286":1,"290":2,"295":6,"296":2,"300":4,"301":1,"303":8,"305":14,"306":8,"310":6,"312":1,"314":6,"318":9,"325":3,"326":2,"328":1,"330":8,"331":1,"333":12,"336":1,"337":4,"341":29,"342":2,"343":6,"344":11,"345":2,"350":2,"352":2,"353":8,"359":6,"365":2,"366":1,"368":1,"369":2,"370":4,"373":10,"374":2,"375":2,"384":11,"388":1,"390":2,"391":6,"392":2,"395":6,"397":11,"400":16,"401":5,"402":3,"404":7,"406":1,"407":12,"408":8,"409":6,"412":2,"413":14,"414":1,"415":3,"416":2,"418":6,"419":1,"420":3,"423":4,"424":1,"426":4,"427":3,"428":7,"430":4,"434":3,"436":3,"442":3,"443":4,"444":4,"448":8,"450":3,"453":1,"454":2,"457":1,"459":16,"460":2,"463":6,"464":6,"465":4,"466":1,"468":3,"469":9,"470":12,"475":6,"476":8,"477":13,"481":2,"482":6,"484":2,"486":25,"487":14,"488":8,"489":15,"490":19,"491":15,"492":15,"493":15,"494":4,"495":9,"497":13,"498":1,"499":4,"502":3,"507":30,"509":12,"511":9,"512":2,"513":6,"514":5,"522":2,"523":4,"524":2,"526":3,"527":4,"528":6,"530":1,"531":3,"532":1,"534":1,"536":17,"538":2,"539":3,"540":4,"543":1,"544":2,"545":13,"547":1,"549":6,"550":2,"552":3,"556":16,"558":2,"562":1,"564":4,"566":1,"570":2,"572":13,"576":9,"577":8,"578":2,"579":1,"583":20,"585":2,"586":6,"587":2,"590":1,"591":13,"592":2,"593":3,"594":1,"597":1,"599":6,"601":3,"603":2,"604":1,"609":5,"617":2,"618":12,"620":1,"621":1,"622":1,"624":1,"626":18,"628":2,"629":5,"632":2,"633":2,"634":5,"635":2,"638":4,"640":15,"642":2,"644":2,"648":1,"649":27,"650":1,"652":2,"656":2,"657":9,"665":9,"668":2,"669":1,"674":68,"675":4,"676":18,"677":1,"681":2,"682":6,"683":2,"684":1,"685":1,"686":2,"688":1,"689":1,"690":1,"691":1,"692":2,"693":1,"696":2,"697":3,"698":30,"700":3,"702":18,"703":13,"705":1,"706":2,"707":8,"713":1,"715":10,"720":6,"723":1,"724":4,"725":8,"727":4,"731":1,"732":2,"733":11,"736":9,"739":1,"740":4,"741":9,"742":1,"744":1,"747":1,"749":6,"766":2,"774":6,"776":7,"782":14,"789":3,"790":1,"792":3,"798":12,"799":9,"803":4,"804":5,"805":3,"806":3,"809":3,"810":1,"813":2,"814":2,"818":2,"819":34,"821":1,"822":8,"823":3,"825":5,"826":6,"827":20,"830":5,"831":2,"832":9,"833":14,"836":1,"837":2,"838":5,"840":1,"846":5,"849":5,"850":2,"854":4,"857":7,"860":1,"861":10,"863":3,"864":1,"865":1,"880":3,"881":36,"883":5,"884":4,"888":4,"890":2,"892":2,"897":15,"899":1,"900":2,"904":2,"905":23,"906":14,"907":2,"908":7,"912":2,"913":14,"914":5,"915":2,"917":4,"920":1,"921":3,"922":9,"923":1,"926":5,"930":1,"931":7,"936":1,"938":3,"939":10,"940":1,"941":2,"942":1,"945":1,"946":18,"949":1,"950":1,"953":2,"954":25,"955":1,"956":5,"957":1,"958":4,"959":6,"960":6,"963":7,"964":14,"966":3,"971":2,"972":12,"975":7,"979":9,"980":11,"982":3,"983":1,"984":1,"987":4,"988":4,"990":2,"991":1,"995":6,"996":9,"999":3,"1003":5,"1004":19,"1006":5,"1007":3,"1010":2,"1012":7,"1014":1,"1016":3,"1018":6,"1034":21,"1036":3,"1037":2,"1041":8,"1042":38,"1043":2,"1044":7,"1045":4,"1049":7,"1050":38,"1051":1,"1052":4,"1054":4,"1055":4,"1057":2,"1058":8,"1059":58,"1062":4,"1066":9,"1067":20,"1068":5,"1069":6,"1070":3,"1073":2,"1074":19,"1075":4,"1077":1,"1078":2,"1082":4,"1083":6,"1085":3,"1086":2,"1088":1,"1089":10,"1090":1,"1091":38,"1093":3,"1094":2,"1095":2,"1100":4,"1103":4,"1108":15,"1116":6,"1117":1,"1119":4,"1122":2,"1123":7,"1124":27,"1125":7,"1126":6,"1127":3,"1128":4,"1132":4,"1133":12,"1135":1,"1136":1,"1137":1,"1140":14,"1150":16,"1160":7,"1161":7,"1163":1,"1164":1,"1168":22,"1170":1,"1171":1,"1173":1,"1174":4,"1175":54,"1176":1,"1180":1,"1182":3,"1183":8,"1184":40,"1185":3,"1186":4,"1187":2,"1189":1,"1190":1,"1191":5,"1192":7,"1194":1,"1195":4,"1199":3206,"1200":2,"1201":6,"1202":6,"1203":1243,"1204":3,"1206":45,"1207":10164,"1208":192,"1211":11,"1212":19,"1213":8,"1214":8,"1215":4,"1216":1,"1217":1,"1219":6,"1220":3,"1221":1,"1222":7,"1223":4,"1224":2,"1225":7,"1226":4,"1227":6,"1228":4,"1229":131,"1230":3,"1231":12,"1232":10,"1233":14,"1234":24,"1235":4,"1236":19,"1237":281,"1238":1,"1239":17,"1240":8,"1241":12,"1242":4,"1243":10,"1244":4,"1245":2,"1246":6,"1247":318,"1249":9,"1250":2,"1251":3,"1252":4,"1253":1,"1254":4,"1255":1,"1256":3,"1257":10,"1258":5,"1259":281,"1261":4,"1262":12,"1263":4,"1264":5,"1265":4,"1266":2,"1267":10,"1268":4,"1269":4,"1270":427,"1271":150,"1273":10,"1274":4,"1275":5,"1276":16,"1277":8,"1278":18,"1279":18,"1280":3,"1281":6,"1282":6,"1283":4,"1284":4,"1285":14,"1286":1513,"1287":2,"1288":16,"1289":19,"1290":22,"1291":24,"1292":12,"1293":18,"1294":18,"1295":12,"1296":10,"1297":60,"1298":12,"1299":4,"1300":2115,"1301":142,"1302":13,"1303":17,"1304":10,"1305":10,"1306":16,"1307":18,"1308":10,"1309":362,"1310":104,"1311":829,"1312":84,"1314":2,"1315":10,"1316":22,"1317":23,"1318":15,"1319":7,"1320":57,"1321":5,"1322":29,"1323":730,"1324":1580,"1325":14,"1326":5,"1327":5,"1328":5,"1329":3,"1330":11,"1331":2,"1332":15,"1333":22,"1334":14,"1335":2,"1336":7,"1337":15,"1338":16,"1339":483,"1341":4,"1342":1,"1348":1,"1350":79,"1351":1,"1352":1,"1353":2,"1354":1,"1359":505,"1361":2,"1362":4,"1363":4,"1366":2,"1368":1,"1369":23,"1371":2,"1372":6,"1376":1,"1378":3,"1379":2,"1380":62,"1381":1,"1382":1,"1383":5,"1384":7,"1386":7,"1387":3,"1388":2,"1389":1,"1392":1,"1395":163,"1396":152,"1399":1,"1400":1,"1402":39,"1404":2,"1414":5,"1415":184,"1416":39,"1417":792,"1418":6,"1419":4,"1420":12,"1421":2,"1422":6,"1423":11,"1424":10,"1425":8,"1426":2,"1427":165,"1428":11,"1429":9,"1430":18,"1431":37,"1432":9,"1433":21,"1434":13,"1435":14,"1436":3274,"1437":49,"1438":95,"1439":2,"1441":21,"1442":11,"1443":38,"1444":4,"1445":3,"1447":10,"1448":1,"1449":6,"1450":1,"1451":2,"1452":2,"1453":14,"1454":12,"1455":11,"1456":3,"1459":22,"1460":7,"1461":3,"1467":35,"1470":1,"1474":1,"1475":4,"1476":1,"1480":2,"1481":1,"1484":1,"1485":2,"1486":26,"1487":40,"1488":74,"1489":43,"1490":14,"1491":3,"1492":38,"1496":343,"1497":2,"1498":2,"1500":2,"1503":12,"1504":1,"1505":2,"1509":1,"1513":2,"1514":1,"1517":1,"1520":1,"1521":1,"1522":2,"1523":2,"1524":2,"1525":3,"1526":69,"1527":4,"1529":1,"1531":15,"1532":1,"1534":5,"1535":8,"1544":1,"1550":1,"1555":1,"1569":1,"1570":1,"1573":3,"1574":1,"1575":1,"1576":2,"1577":108,"1578":2,"1580":1,"1581":1,"1582":35,"1583":8,"1584":1,"1585":8,"1586":14,"1587":2,"1588":1,"1589":3,"1591":40,"1592":1,"1593":1,"1594":2,"1595":3,"1596":15,"1597":3,"1598":1,"1599":4,"1600":8,"1601":1,"1605":1,"1611":1,"1615":2,"1619":4,"1627":5,"1631":1,"1641":3,"1642":1,"1643":1,"1646":3,"1648":11,"1649":3,"1650":6,"1653":16,"1654":2,"1655":3,"1656":2,"1658":1,"1659":2,"1660":17,"1661":1,"1675":3,"1676":1,"1677":2,"1681":1,"1682":1,"1692":2,"1693":3,"1695":2,"1699":1,"1702":7,"1703":1,"1706":1,"1707":1,"1708":1,"1711":1,"1712":2,"1717":1,"1726":1,"1727":4,"1731":1,"1732":2,"1736":1,"1739":1,"1741":1,"1761":4,"1764":3,"1770":2,"1775":1,"1778":2,"1780":1,"1781":2,"1782":3,"1784":1,"1785":1,"1787":2,"1788":1,"1790":3,"1793":4,"1794":1,"1799":2,"1801":1,"1802":4,"1803":2,"1805":3,"1806":1,"1807":3,"1808":1,"1810":4,"1812":3,"1815":4,"1817":3,"1820":1,"1826":1,"1827":4,"1828":2,"1829":1,"1834":4,"1838":2,"1843":1,"1846":4,"1847":1,"1853":3,"1854":1,"1860":1,"1861":1,"1862":4,"1865":2,"1868":4,"1877":4,"1879":1,"1880":1,"1886":3,"1889":1,"1891":4,"1892":4,"1894":1,"1895":3,"1896":1,"1897":1,"1899":3,"1902":4,"1903":1,"1905":4,"1906":1,"1909":2,"1910":1,"1912":3,"1913":1,"1919":1,"1922":2,"1923":1,"1924":4,"1927":2,"1929":1,"1930":5,"1931":2,"1935":1,"1939":1,"1941":4,"1942":1,"1947":1,"1950":3,"1951":1,"1953":1,"1955":2,"1959":3,"1961":1,"1962":1,"1963":1,"1965":4,"1968":2,"1972":4,"1975":1,"1979":4,"1981":1,"1985":4,"1991":4,"1993":1,"1994":7,"1995":1,"1997":4,"1999":1,"2000":9,"2001":1,"2002":4,"2003":2,"2004":3,"2006":12,"2008":1,"2013":9,"2014":6,"2015":2,"2016":1,"2022":1,"2024":3,"2028":4,"2030":1,"2036":1,"2038":3,"2039":2,"2041":5,"2042":6,"2043":1,"2044":10,"2046":3,"2047":2,"2049":4,"2050":2,"2052":1,"2055":6,"2057":2,"2059":2,"2060":4,"2061":1,"2062":1,"2063":1,"2064":2,"2066":4,"2069":2,"2070":1,"2071":2,"2075":1,"2077":1,"2080":3,"2081":1,"2083":14,"2085":6,"2086":3,"2087":1,"2089":1,"2098":4,"2099":1,"2100":4,"2101":3,"2105":1,"2106":3,"2107":1,"2108":7,"2109":1,"2111":4,"2112":3,"2113":1,"2115":4,"2116":1,"2117":1,"2119":2,"2120":5,"2122":3,"2123":6,"2124":1,"2127":1,"2128":3,"2131":4,"2133":4,"2134":2,"2136":1,"2137":1,"2138":2,"2139":3,"2140":1,"2142":1,"2146":4,"2149":3,"2150":5,"2151":1,"2152":1,"2153":5,"2154":1,"2156":10,"2157":3,"2160":13,"2163":1,"2169":4,"2171":2,"2172":3,"2177":1,"2178":3,"2179":1,"2180":7,"2182":2,"2191":1,"2193":5,"2195":2,"2203":4,"2204":3,"2211":3,"2221":1,"2227":23,"2229":1,"2231":1,"2232":6,"2234":3}}],["communities",{"3":{"2238":1}}],["communitytoolkit",{"3":{"1416":2,"1417":2}}],["community",{"1":{"2238":1,"2239":1,"2240":1,"2241":1,"2242":1,"2243":1},"2":{"1364":1},"3":{"0":2,"150":2,"1035":1,"1075":1,"1077":1,"1079":1,"1340":1,"1342":3,"1350":11,"1364":2,"1369":7,"1384":1,"1392":1,"1395":4,"1417":1,"1629":1,"1631":5,"2215":1,"2216":2,"2225":1,"2238":2,"2239":1,"2240":3,"2241":1,"2245":1}}],["communicate",{"3":{"1359":1,"1772":1}}],["communicates",{"3":{"1237":1}}],["communication",{"0":{"1772":1},"3":{"59":1,"1350":1,"1359":2,"1436":1,"1489":1,"1544":1,"1556":1,"1770":1}}],["commas",{"3":{"1436":4}}],["comma",{"3":{"26":1,"219":1,"330":1,"633":1,"972":1,"974":1,"1241":1,"1247":7,"1253":1,"1259":1,"1300":3,"1311":1,"1312":2,"1318":1,"1323":2,"1324":2,"1380":2,"1436":6,"1496":1,"1988":1}}],["command+handler",{"3":{"1591":1}}],["command+handler+request+validator+mapper",{"3":{"1591":1}}],["command+validator+handler+dto",{"3":{"1526":1}}],["commandorrequestsuffix",{"3":{"1436":1}}],["commandwithitsownvalidator",{"3":{"1436":2}}],["commandwithnovalidation",{"3":{"1436":2}}],["commandcoveredthroughtherequestbridge",{"3":{"1436":3}}],["commandpipeline",{"3":{"1436":2}}],["commandinventory",{"3":{"1436":5}}],["commandflags",{"3":{"1323":1}}],["commandtype",{"3":{"1286":4,"1323":2,"2186":1}}],["commandtypecache",{"3":{"1286":2}}],["commandtimeout",{"3":{"1286":3}}],["commandtimeoutseconds",{"2":{"71":1,"2032":1},"3":{"71":1,"819":1,"1286":4,"1320":1,"1664":1,"2032":1}}],["commandhandlers",{"3":{"1436":1}}],["commandhandlersreturnresult",{"3":{"1436":1}}],["commandhandleraskingforthewriterepository",{"3":{"1436":2}}],["commandhandler",{"3":{"1270":1}}],["commandvalidators",{"3":{"1207":1,"1415":1,"1436":12,"1489":1}}],["commandvalidatorfixtures",{"3":{"1207":48,"1436":52}}],["commandvalidatortests",{"3":{"1207":4,"1359":7}}],["commandvalidatorcoveragefitnesstests",{"2":{"1489":1},"3":{"1199":1,"1207":3,"1436":14,"1489":1}}],["commandvalidatorcoveragetests",{"3":{"128":2,"1199":1,"1207":2,"1300":2,"1359":1,"1415":6,"1436":6,"1489":2}}],["commandvalidatorcoveragetestsbase",{"2":{"117":1,"128":1},"3":{"117":2,"128":2,"1199":2,"1207":2,"1436":11}}],["commanddecoratorpipelinetests",{"3":{"1199":1,"1207":6,"1270":1}}],["commandduration",{"2":{"397":1},"3":{"397":1,"1270":3}}],["commandname",{"3":{"405":1,"1270":5,"1311":1}}],["commandrequestvalidatortests",{"3":{"1199":1,"1207":7,"1271":1}}],["commandrequestvalidator",{"2":{"117":1,"128":1,"922":1,"1666":1,"1830":1,"1834":1},"3":{"0":2,"117":1,"128":1,"922":2,"1199":6,"1203":1,"1207":2,"1263":1,"1270":5,"1271":10,"1300":4,"1316":1,"1322":1,"1323":4,"1359":6,"1415":10,"1436":4,"1666":1,"1830":2,"1834":1}}],["commandsandqueries",{"3":{"1436":2}}],["commandshavevalidators",{"3":{"1436":2}}],["commands",{"1":{"919":1,"920":1,"921":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"1039":1,"1040":1,"1041":1,"1042":1,"1043":1,"1044":1,"1045":1,"1046":1,"1260":1,"1261":1,"1262":1,"1263":1,"1264":1,"1265":1,"1266":1,"1267":1,"1268":1,"1269":1,"1270":1,"2134":1,"2135":1,"2136":1,"2137":1,"2138":1,"2139":1,"2181":1,"2182":1,"2183":1,"2184":1,"2185":1,"2186":1,"2187":1,"2188":1,"2189":1,"2190":1,"2191":1,"2192":1,"2193":1},"2":{"2191":4},"3":{"0":7,"115":1,"116":1,"117":1,"121":2,"122":2,"126":2,"223":1,"236":1,"388":1,"422":1,"426":1,"516":1,"518":2,"522":2,"523":1,"599":1,"672":1,"750":1,"753":1,"919":1,"926":1,"988":2,"991":1,"1012":1,"1014":1,"1028":1,"1039":1,"1043":1,"1044":1,"1082":1,"1085":1,"1100":1,"1192":2,"1193":1,"1202":2,"1203":2,"1212":4,"1229":1,"1259":1,"1260":1,"1261":1,"1263":2,"1264":2,"1265":1,"1267":1,"1270":25,"1271":7,"1286":14,"1301":1,"1309":1,"1311":4,"1322":1,"1323":4,"1324":1,"1339":6,"1350":5,"1352":3,"1357":1,"1359":16,"1369":1,"1371":1,"1378":1,"1380":4,"1399":1,"1402":5,"1415":5,"1436":30,"1438":1,"1446":1,"1465":1,"1467":1,"1470":3,"1489":1,"1496":2,"1497":1,"1506":1,"1507":1,"1526":1,"1543":2,"1570":2,"1577":1,"1651":1,"1660":1,"1662":2,"1666":2,"1668":1,"1671":1,"1682":1,"1686":1,"1700":1,"1721":1,"1727":1,"1730":1,"1753":1,"1774":1,"1779":2,"1820":1,"1821":1,"1828":1,"1830":2,"1918":1,"1946":1,"1947":1,"2012":1,"2036":1,"2060":1,"2088":1,"2089":1,"2096":3,"2097":1,"2111":1,"2133":2,"2134":2,"2136":1,"2138":1,"2139":1,"2146":1,"2165":1,"2180":2,"2181":1,"2187":1,"2191":4,"2192":2,"2193":1,"2203":1,"2206":2,"2230":1,"2232":2}}],["command",{"0":{"1275":1,"1357":1,"1440":1,"1730":1,"1830":1,"2005":1,"2092":1,"2163":1},"1":{"2003":1,"2004":1,"2005":1,"2006":1,"2007":1,"2008":1,"2009":1,"2010":1,"2011":1,"2012":1,"2013":1,"2014":1,"2087":1,"2088":1,"2089":1,"2090":1,"2091":1,"2092":1,"2093":1,"2094":1,"2095":1,"2096":1,"2097":1,"2098":1},"2":{"1370":1,"1823":1,"1871":1,"2185":1},"3":{"0":24,"24":1,"26":1,"27":2,"71":1,"92":1,"109":1,"113":1,"115":1,"117":3,"121":2,"122":7,"125":1,"126":1,"127":1,"128":1,"157":1,"225":1,"229":1,"230":2,"234":2,"235":3,"236":1,"245":1,"259":1,"299":2,"300":1,"341":1,"373":2,"383":2,"396":1,"397":4,"404":2,"405":4,"406":1,"408":1,"434":1,"438":1,"441":1,"444":1,"459":2,"466":1,"486":1,"490":2,"491":1,"492":1,"493":1,"516":1,"518":4,"522":3,"523":3,"528":1,"534":1,"536":4,"537":1,"539":2,"540":1,"541":2,"633":3,"690":1,"714":1,"735":1,"790":1,"794":2,"808":1,"809":1,"810":1,"813":1,"815":1,"854":1,"869":2,"876":2,"896":1,"897":2,"898":2,"900":1,"920":6,"921":2,"922":10,"923":5,"925":2,"926":20,"927":1,"986":1,"987":2,"988":2,"991":1,"994":1,"995":1,"1003":1,"1010":1,"1011":2,"1013":2,"1016":1,"1018":2,"1019":1,"1022":2,"1024":1,"1030":1,"1034":1,"1042":6,"1043":1,"1044":4,"1046":1,"1068":1,"1086":1,"1099":1,"1100":5,"1101":1,"1102":2,"1104":1,"1116":2,"1132":2,"1133":2,"1150":1,"1175":1,"1177":1,"1202":1,"1203":1,"1212":4,"1213":2,"1217":1,"1218":1,"1224":1,"1233":2,"1237":1,"1238":1,"1246":1,"1247":7,"1252":2,"1259":7,"1260":4,"1262":1,"1263":13,"1264":4,"1265":10,"1266":1,"1267":4,"1268":2,"1270":240,"1271":25,"1273":2,"1275":7,"1276":1,"1277":1,"1278":1,"1286":76,"1289":1,"1297":1,"1300":20,"1301":8,"1304":3,"1306":1,"1307":1,"1309":32,"1311":22,"1312":1,"1313":2,"1316":5,"1318":10,"1322":4,"1323":95,"1332":1,"1339":9,"1346":1,"1347":3,"1349":1,"1350":18,"1352":4,"1354":2,"1357":7,"1358":1,"1359":419,"1367":2,"1369":14,"1370":1,"1373":1,"1374":4,"1375":1,"1376":1,"1380":43,"1390":2,"1395":7,"1396":53,"1399":3,"1402":75,"1403":1,"1406":1,"1407":3,"1410":1,"1411":2,"1415":109,"1431":4,"1436":146,"1437":5,"1438":4,"1440":3,"1442":1,"1443":2,"1445":1,"1446":1,"1450":1,"1453":1,"1454":1,"1455":2,"1458":3,"1464":1,"1465":2,"1467":3,"1475":2,"1478":2,"1483":1,"1487":1,"1488":1,"1489":3,"1492":1,"1496":10,"1508":1,"1526":2,"1534":1,"1542":1,"1547":1,"1548":1,"1570":2,"1572":1,"1577":6,"1585":1,"1591":3,"1602":1,"1631":1,"1646":1,"1650":1,"1651":11,"1654":1,"1658":2,"1661":4,"1662":2,"1666":1,"1668":1,"1671":1,"1683":1,"1685":7,"1686":2,"1689":1,"1691":1,"1692":2,"1694":1,"1697":2,"1701":5,"1702":4,"1719":1,"1726":2,"1727":7,"1729":2,"1730":3,"1765":1,"1774":3,"1779":1,"1781":1,"1803":1,"1817":1,"1818":2,"1821":5,"1822":2,"1823":5,"1825":1,"1826":1,"1827":3,"1828":3,"1830":5,"1831":2,"1833":1,"1834":2,"1840":1,"1857":1,"1871":1,"1875":1,"1879":1,"1883":3,"1913":2,"1914":1,"1918":6,"1920":5,"1923":2,"1933":2,"1934":1,"1944":3,"1946":2,"1947":1,"1948":1,"1950":2,"1962":1,"2002":1,"2003":2,"2004":1,"2005":1,"2006":2,"2012":1,"2013":2,"2030":1,"2036":1,"2048":1,"2087":2,"2092":2,"2093":1,"2095":1,"2096":5,"2097":2,"2098":1,"2105":1,"2111":1,"2133":1,"2136":4,"2137":1,"2138":1,"2139":1,"2154":1,"2156":2,"2157":2,"2163":2,"2167":1,"2168":1,"2169":3,"2181":1,"2183":3,"2184":3,"2185":2,"2186":2,"2187":2,"2188":2,"2189":4,"2190":3,"2191":1,"2192":6,"2193":2,"2206":1,"2209":2,"2210":1,"2211":1,"2227":1,"2230":1,"2232":2}}],["commerce",{"0":{"1603":1},"3":{"1520":1,"1588":1,"1601":1,"1661":1,"1673":2,"1681":1,"1763":2,"1785":1,"2004":1,"2110":1,"2115":1,"2221":1,"2228":2,"2231":1}}],["commercially",{"3":{"1380":1,"1454":1}}],["commercial",{"3":{"0":5,"145":1,"146":1,"147":1,"150":1,"650":1,"683":1,"810":1,"1074":1,"1075":2,"1077":1,"1079":1,"1213":1,"1259":1,"1323":1,"1342":2,"1350":1,"1369":1,"1380":1,"1384":1,"1392":1,"1395":3,"1431":1,"1436":3,"1489":1,"1491":1,"1569":1,"1576":1,"1577":1,"1631":1,"1649":1,"1671":1,"1673":1,"1738":1,"2168":1,"2226":1,"2234":1}}],["commentid",{"3":{"1651":1}}],["commenting",{"3":{"627":1,"1459":1}}],["commentary",{"3":{"1270":1}}],["commentedoutgrid",{"3":{"1436":1}}],["commented",{"3":{"0":1,"142":1,"1212":2,"1259":3,"1286":1,"1300":2,"1309":1,"1323":1,"1324":1,"1339":2,"1359":1,"1369":2,"1396":1,"1417":2,"1436":11,"2230":1,"2232":1}}],["comment",{"2":{"429":1,"1459":3},"3":{"0":3,"93":1,"95":2,"104":2,"109":1,"137":1,"138":1,"140":1,"142":1,"147":2,"148":1,"149":1,"152":1,"245":1,"250":1,"251":1,"256":4,"257":1,"258":1,"259":1,"275":1,"276":1,"291":1,"322":1,"370":1,"371":1,"372":1,"373":2,"374":1,"390":1,"393":2,"422":1,"424":1,"425":2,"426":2,"428":3,"429":3,"448":3,"453":1,"454":1,"459":1,"464":1,"468":1,"470":1,"476":1,"478":1,"490":1,"491":1,"492":2,"493":1,"512":1,"528":1,"530":1,"531":1,"540":1,"543":1,"547":1,"556":1,"559":1,"564":1,"576":1,"593":1,"626":1,"627":1,"629":1,"636":1,"638":2,"640":1,"669":1,"674":2,"676":1,"682":1,"685":1,"703":1,"741":1,"743":1,"749":6,"750":1,"751":1,"757":1,"765":1,"766":1,"769":1,"774":1,"777":1,"819":1,"833":3,"837":4,"839":4,"841":2,"847":1,"870":1,"871":1,"876":4,"877":1,"954":1,"982":1,"1019":1,"1074":4,"1075":1,"1076":1,"1116":2,"1124":1,"1133":1,"1135":1,"1161":1,"1162":1,"1168":1,"1212":1,"1213":1,"1219":1,"1222":1,"1227":1,"1228":1,"1229":21,"1231":1,"1232":1,"1233":1,"1235":1,"1237":30,"1239":1,"1243":1,"1246":2,"1247":45,"1259":60,"1270":48,"1271":9,"1273":3,"1275":1,"1277":1,"1278":2,"1280":1,"1283":3,"1284":2,"1286":176,"1300":189,"1301":5,"1309":51,"1310":4,"1311":85,"1312":8,"1316":2,"1317":1,"1323":54,"1324":265,"1326":2,"1327":1,"1328":1,"1329":1,"1330":2,"1332":3,"1333":2,"1339":170,"1342":3,"1343":2,"1346":1,"1347":1,"1350":88,"1356":1,"1359":167,"1362":1,"1364":2,"1365":1,"1366":1,"1367":1,"1368":1,"1369":33,"1380":49,"1389":1,"1395":176,"1396":225,"1401":1,"1402":95,"1405":1,"1414":1,"1415":115,"1416":25,"1417":51,"1419":1,"1422":1,"1423":1,"1427":5,"1436":177,"1438":1,"1441":10,"1442":3,"1443":6,"1444":2,"1445":10,"1447":3,"1451":4,"1452":1,"1453":12,"1454":3,"1455":35,"1456":3,"1457":2,"1458":2,"1459":11,"1460":4,"1463":1,"1465":3,"1467":37,"1472":2,"1473":3,"1475":6,"1478":1,"1479":1,"1480":1,"1483":1,"1486":2,"1488":5,"1489":6,"1490":2,"1491":1,"1492":6,"1496":12,"1526":2,"1577":2,"1585":1,"1589":2,"1591":4,"1596":4,"1630":2,"1651":2,"1653":1,"1685":5,"1686":6,"1727":1,"1728":1,"1738":1,"1782":1,"1790":1,"1801":2,"1815":1,"1816":1,"1821":1,"1877":1,"1886":1,"1889":1,"1896":1,"1902":1,"1945":1,"1995":1,"2000":2,"2004":1,"2024":1,"2039":1,"2044":1,"2048":3,"2049":2,"2057":1,"2075":2,"2085":1,"2098":1,"2099":1,"2104":1,"2112":1,"2117":1,"2122":1,"2126":2,"2131":1,"2156":1,"2157":1,"2158":1,"2159":1,"2163":1,"2164":2,"2166":1,"2167":2,"2168":1,"2172":1,"2175":1,"2178":1,"2196":1,"2197":1}}],["comments",{"2":{"1730":1},"3":{"0":1,"119":1,"121":1,"147":1,"220":1,"256":1,"276":1,"286":1,"290":1,"296":1,"390":2,"393":1,"468":1,"526":1,"543":1,"545":1,"547":1,"556":1,"616":1,"626":4,"629":1,"642":1,"700":1,"750":1,"751":1,"833":1,"868":1,"869":1,"875":1,"982":1,"986":1,"989":1,"1129":1,"1219":1,"1226":1,"1229":1,"1237":2,"1259":2,"1262":1,"1270":1,"1286":9,"1300":10,"1301":2,"1309":3,"1311":5,"1324":9,"1338":1,"1339":9,"1344":1,"1350":6,"1359":9,"1380":6,"1395":5,"1396":11,"1402":6,"1415":3,"1416":2,"1417":1,"1427":1,"1436":10,"1438":1,"1443":1,"1451":2,"1467":1,"1470":3,"1475":1,"1476":1,"1486":1,"1496":3,"1507":1,"1552":1,"1577":2,"1585":1,"1629":1,"1651":2,"1653":1,"1676":1,"1686":1,"1701":3,"1730":1,"1816":1,"1826":2,"1827":1,"1896":1,"1995":1,"2013":1,"2049":1,"2156":1}}],["commitfailingdbcontext",{"3":{"1198":1,"1199":3,"1207":1,"1496":1}}],["commitments",{"3":{"1396":1}}],["commitment",{"3":{"168":1,"971":1,"1212":1,"1237":1,"1369":1,"1396":2,"2033":1,"2043":1}}],["committransaction",{"2":{"1822":1},"3":{"1286":4,"1822":1}}],["committable",{"3":{"1277":1}}],["committee",{"3":{"1017":1,"1366":1,"1369":1,"1395":1,"2240":1}}],["committedsources",{"2":{"1840":1,"1852":1},"3":{"988":1,"1286":1,"1840":1,"1852":1}}],["committed",{"0":{"451":1,"577":1,"1466":1},"1":{"588":1,"589":1,"590":1,"591":1,"592":1,"593":1,"594":1,"595":1},"3":{"0":8,"24":1,"120":2,"130":1,"135":2,"201":2,"225":1,"255":1,"369":1,"370":3,"371":1,"373":1,"390":1,"444":1,"446":1,"451":4,"452":2,"453":1,"454":2,"459":1,"461":1,"465":1,"535":1,"536":3,"537":1,"570":1,"572":2,"573":1,"577":2,"588":1,"591":4,"592":1,"692":1,"715":1,"765":1,"770":1,"782":1,"793":1,"809":1,"860":1,"865":1,"869":1,"876":2,"896":1,"897":3,"972":1,"973":1,"974":1,"976":1,"988":3,"989":1,"990":1,"1044":2,"1067":2,"1142":1,"1212":2,"1229":1,"1247":3,"1251":1,"1258":1,"1259":12,"1263":3,"1270":6,"1275":1,"1278":2,"1286":13,"1301":1,"1304":1,"1309":1,"1311":4,"1318":1,"1323":5,"1324":1,"1357":1,"1358":2,"1359":4,"1395":2,"1396":2,"1399":1,"1402":7,"1415":5,"1428":1,"1429":2,"1431":2,"1432":1,"1435":1,"1436":29,"1438":7,"1445":1,"1449":1,"1453":5,"1454":2,"1455":5,"1456":1,"1459":1,"1460":1,"1461":1,"1479":1,"1486":1,"1488":4,"1490":2,"1492":4,"1496":1,"1524":1,"1525":1,"1526":6,"1531":5,"1532":2,"1534":1,"1535":1,"1548":1,"1576":1,"1577":11,"1586":1,"1591":4,"1663":1,"1664":1,"1668":1,"1673":2,"1685":2,"1738":1,"1749":1,"1765":2,"1769":1,"1770":1,"1784":1,"1794":1,"1800":1,"1801":1,"1821":1,"1822":2,"1837":1,"1839":1,"1840":2,"1918":5,"1923":2,"1929":1,"1930":2,"1933":1,"1940":1,"1946":1,"1948":1,"2044":3,"2047":3,"2049":2,"2055":1,"2059":1,"2062":1,"2079":1,"2100":1,"2161":2,"2164":2,"2167":2,"2169":1,"2192":2,"2211":1,"2215":1,"2232":3}}],["committing",{"3":{"0":1,"986":1,"988":1,"1212":1,"1259":1,"1278":1,"1286":3,"1300":2,"1359":2,"1402":3,"1410":1,"1455":1,"1459":1,"1526":1,"1845":1,"1929":1,"2044":1,"2066":1}}],["commit",{"0":{"452":1,"1840":1},"1":{"985":1,"986":1,"987":1,"988":1,"989":1,"990":1,"991":1,"992":1},"3":{"0":23,"15":1,"20":2,"27":2,"91":1,"94":1,"115":1,"120":2,"147":1,"195":1,"226":1,"350":1,"368":1,"373":2,"374":1,"383":3,"424":2,"425":2,"426":1,"444":2,"459":2,"463":1,"465":2,"466":1,"516":1,"518":4,"521":3,"522":3,"523":2,"524":2,"530":1,"534":1,"535":1,"536":3,"537":1,"538":2,"539":1,"540":1,"541":1,"563":2,"577":1,"620":1,"628":1,"629":1,"633":1,"642":1,"692":1,"714":2,"715":1,"716":1,"758":1,"759":1,"781":1,"782":1,"809":1,"863":2,"868":1,"870":2,"875":1,"897":5,"898":1,"900":2,"931":1,"933":1,"985":1,"986":2,"987":2,"988":26,"989":7,"990":6,"991":4,"992":3,"1019":1,"1042":2,"1043":1,"1044":1,"1100":3,"1104":3,"1116":4,"1118":2,"1185":1,"1212":2,"1233":1,"1252":2,"1254":1,"1259":7,"1262":1,"1263":2,"1265":1,"1270":12,"1273":1,"1274":1,"1278":7,"1286":45,"1289":2,"1290":1,"1300":4,"1301":1,"1309":2,"1311":1,"1318":3,"1323":6,"1352":1,"1355":1,"1357":2,"1358":1,"1359":36,"1369":1,"1395":1,"1396":7,"1399":1,"1402":20,"1406":1,"1410":2,"1415":27,"1436":10,"1438":3,"1452":1,"1454":5,"1455":5,"1459":2,"1473":6,"1475":1,"1478":4,"1483":1,"1492":1,"1496":45,"1508":1,"1524":1,"1526":1,"1534":1,"1577":1,"1591":6,"1600":3,"1627":2,"1631":1,"1651":6,"1653":1,"1655":1,"1662":1,"1664":2,"1665":2,"1667":1,"1672":1,"1673":1,"1685":1,"1686":1,"1699":1,"1721":1,"1727":2,"1733":1,"1749":2,"1761":2,"1765":3,"1769":1,"1774":1,"1801":1,"1819":1,"1822":1,"1827":1,"1837":1,"1838":2,"1839":1,"1840":12,"1845":1,"1847":1,"1852":4,"1853":1,"1861":1,"1888":1,"1941":1,"1944":3,"1950":2,"1958":1,"1977":1,"1986":1,"2057":1,"2096":1,"2113":1,"2160":1,"2161":2,"2162":1,"2163":2,"2164":1,"2168":2,"2169":3,"2185":1,"2192":2,"2193":1,"2232":2}}],["commits",{"0":{"1212":1,"2230":1},"3":{"0":3,"24":1,"94":1,"193":1,"195":1,"201":1,"352":1,"424":1,"444":1,"446":1,"452":2,"466":1,"517":1,"535":1,"536":2,"538":1,"539":3,"562":1,"564":2,"744":1,"793":1,"897":1,"900":1,"988":2,"1041":2,"1116":1,"1142":1,"1174":1,"1182":1,"1210":1,"1214":1,"1252":1,"1258":1,"1259":4,"1263":1,"1270":1,"1272":1,"1275":1,"1278":2,"1286":9,"1300":3,"1311":1,"1318":1,"1323":5,"1324":2,"1350":1,"1352":1,"1358":1,"1359":11,"1396":2,"1402":6,"1405":1,"1410":2,"1415":8,"1431":1,"1436":2,"1438":2,"1453":1,"1473":1,"1478":1,"1490":1,"1492":1,"1496":11,"1506":1,"1526":1,"1651":1,"1655":1,"1673":1,"1765":1,"1800":1,"1819":1,"1822":1,"1835":1,"1838":1,"1839":1,"1840":2,"1852":1,"1910":1,"1924":1,"1933":2,"1940":1,"1950":1,"1978":1,"2063":1,"2091":1,"2161":1,"2163":2,"2164":1,"2168":1,"2169":2,"2181":2,"2183":1}}],["corner",{"3":{"1847":1}}],["cornerstone",{"3":{"1779":1}}],["corporate",{"3":{"1324":1}}],["corpus",{"0":{"1435":1},"3":{"309":1,"945":2,"1016":1,"1018":5,"1019":5,"1020":2,"1091":3,"1092":2,"1093":1,"1300":1,"1427":10,"1428":1,"1435":7,"1437":1,"1438":3,"1492":5,"1496":4,"1577":1}}],["corollary",{"3":{"1237":1,"1997":1}}],["corspolicyallowall",{"2":{"774":1,"1108":1},"3":{"774":1,"1108":1,"1311":1}}],["corspolicyallowspecificorigins",{"2":{"774":1,"1108":1},"3":{"774":1,"1108":1,"1311":1}}],["cors",{"0":{"1336":1},"2":{"774":1,"776":1,"777":1},"3":{"0":5,"59":1,"60":1,"572":1,"749":1,"773":2,"774":6,"775":1,"776":3,"777":1,"778":4,"826":3,"834":1,"1107":1,"1108":1,"1109":1,"1111":1,"1112":1,"1184":2,"1185":1,"1187":2,"1189":1,"1212":1,"1311":10,"1324":7,"1336":4,"1338":4,"1339":9,"1379":1,"1429":1,"1436":3,"1438":1,"1443":1,"1447":2,"1467":2,"1488":1,"1653":1,"1666":1,"1670":1,"1671":1,"1673":1,"1687":1,"1702":1,"1898":2,"1904":1,"2023":2,"2055":1,"2151":1,"2232":1}}],["corrupting",{"3":{"1309":1,"1479":1}}],["corruption",{"3":{"0":1,"51":1,"53":2,"68":1,"1012":1,"1312":3,"1358":1,"1359":1,"1467":1,"1474":2,"1526":1,"1532":1,"1544":1,"1577":1,"1586":1,"1591":1,"1630":1,"1639":1,"1663":1,"1709":1,"2033":1,"2164":1}}],["corrupted",{"3":{"360":1,"692":1,"1226":1,"1229":1,"1396":1,"1412":1,"1415":1,"1438":1,"1467":1,"2142":1}}],["corrupt",{"3":{"312":1,"743":1,"947":1,"1223":1,"1229":2,"1237":1,"1300":1,"1311":1,"1312":2,"1324":2,"1395":2,"1402":1,"1417":5}}],["correspond",{"3":{"1369":1,"1396":1}}],["correspondence",{"3":{"1359":1}}],["corresponds",{"3":{"1339":1,"1396":1,"1641":2}}],["corresponding",{"3":{"838":1,"1237":1,"1247":1,"1270":1,"1271":1,"1279":1,"1286":1,"1300":1,"1323":1,"1324":3,"1359":1,"1395":1,"1417":1,"1436":4,"1466":1,"1517":1,"1627":1,"1641":1,"1654":1,"1765":1}}],["correspondingly",{"3":{"0":1,"549":1,"551":1,"1324":1,"2041":1}}],["correctfollowing",{"3":{"1526":1}}],["correcting",{"3":{"1396":1,"1455":1,"1460":1,"1496":1,"1776":1}}],["correction",{"3":{"22":1,"115":1,"137":2,"251":1,"254":1,"256":1,"260":1,"261":1,"323":1,"364":1,"365":1,"423":1,"424":1,"428":2,"490":1,"492":1,"493":1,"567":1,"616":1,"682":1,"684":1,"876":1,"1156":1,"1270":1,"1271":1,"1300":1,"1324":2,"1339":1,"1359":1,"1396":1,"1438":1,"1455":1,"1496":2,"1534":1,"1631":1,"1639":1,"1749":1,"1765":2,"1767":2,"1769":2,"1770":1,"1776":1}}],["corrections",{"3":{"21":1,"248":1,"259":1,"335":1,"383":1,"497":2,"764":1,"833":1,"857":1,"1040":1,"1496":6,"2232":1}}],["correctable",{"3":{"1323":1,"1766":1}}],["correctamente",{"3":{"265":1}}],["corrects",{"3":{"137":1,"138":1,"141":1,"171":1,"275":1,"616":1,"909":1,"1324":2,"1359":1,"1396":1,"1415":1,"1436":1,"1765":1}}],["correct",{"0":{"2165":1},"3":{"0":1,"20":1,"110":1,"119":1,"134":1,"136":1,"137":1,"157":1,"159":1,"196":1,"224":1,"235":1,"242":1,"243":1,"244":1,"251":1,"252":2,"254":2,"266":2,"281":1,"319":1,"320":1,"343":1,"360":1,"363":1,"391":1,"395":2,"490":1,"492":1,"493":1,"509":1,"534":1,"537":1,"539":1,"611":1,"633":1,"664":1,"665":1,"692":1,"699":1,"716":1,"735":1,"742":2,"743":1,"748":1,"768":1,"801":1,"809":1,"811":1,"818":1,"820":1,"821":1,"826":1,"827":1,"828":2,"829":1,"830":1,"837":1,"876":1,"890":1,"924":1,"930":1,"932":1,"933":1,"946":2,"948":1,"955":1,"965":2,"972":1,"989":2,"996":1,"998":1,"1019":1,"1093":1,"1100":1,"1118":1,"1135":1,"1160":1,"1169":1,"1212":1,"1219":1,"1229":2,"1237":1,"1247":6,"1252":1,"1259":5,"1260":1,"1270":6,"1286":23,"1300":10,"1301":1,"1309":2,"1311":6,"1312":1,"1323":8,"1324":12,"1339":6,"1350":3,"1359":16,"1369":1,"1380":1,"1395":5,"1396":10,"1402":3,"1415":8,"1417":15,"1436":20,"1441":1,"1442":1,"1443":1,"1455":5,"1467":2,"1474":1,"1477":1,"1479":1,"1489":3,"1496":3,"1498":3,"1548":1,"1558":1,"1563":1,"1577":2,"1585":1,"1591":1,"1596":1,"1631":1,"1637":1,"1638":2,"1639":1,"1640":1,"1651":1,"1654":1,"1666":1,"1685":1,"1686":2,"1708":1,"1746":1,"1749":1,"1750":1,"1765":1,"1767":1,"1768":1,"1785":1,"1805":1,"1825":1,"1826":1,"1833":1,"1838":1,"1843":2,"1844":2,"1845":1,"1852":1,"1870":1,"1884":1,"1885":1,"1894":1,"1895":1,"1898":1,"1904":2,"1910":2,"1911":1,"1913":1,"1914":1,"1927":1,"1942":1,"1947":1,"1949":1,"1996":1,"2026":1,"2034":2,"2049":1,"2050":1,"2061":2,"2066":1,"2068":1,"2074":1,"2096":1,"2107":1,"2113":1,"2118":1,"2127":1,"2142":1,"2152":1,"2165":1,"2168":1,"2169":1,"2192":1}}],["correctly",{"3":{"0":2,"160":1,"235":1,"507":1,"550":1,"571":1,"573":2,"790":1,"849":1,"904":1,"1246":1,"1247":1,"1275":1,"1286":4,"1297":1,"1300":3,"1301":1,"1311":3,"1323":4,"1324":1,"1339":2,"1353":1,"1359":6,"1369":1,"1395":2,"1396":3,"1416":1,"1417":2,"1427":1,"1429":1,"1436":15,"1437":1,"1438":1,"1457":1,"1466":1,"1496":1,"1556":2,"1564":1,"1638":1,"1671":1,"1685":1,"1686":1,"1708":1,"1815":1,"1909":1,"1923":1,"1940":1,"2055":1,"2070":1,"2085":1}}],["correctness",{"0":{"1879":1},"3":{"0":2,"20":1,"227":2,"244":1,"291":1,"295":1,"312":1,"320":1,"483":1,"538":1,"549":2,"566":1,"633":1,"642":1,"666":1,"698":1,"708":1,"733":1,"743":1,"827":1,"855":1,"996":2,"997":1,"1154":1,"1247":2,"1259":2,"1263":1,"1270":5,"1286":5,"1300":4,"1301":5,"1309":1,"1310":1,"1311":1,"1323":2,"1324":6,"1339":3,"1350":1,"1359":8,"1380":2,"1395":4,"1396":6,"1402":2,"1410":1,"1415":4,"1417":2,"1427":1,"1436":2,"1437":1,"1476":1,"1492":1,"1534":1,"1556":2,"1707":1,"1708":2,"1819":1,"1821":1,"1826":1,"1841":1,"1876":1,"1878":1,"1881":1,"1886":2,"1915":1,"1916":1,"1921":2,"1923":1,"1924":1,"1940":1,"1947":1,"1970":1,"1996":1,"2141":1,"2168":1}}],["corrected",{"3":{"0":2,"27":1,"43":1,"57":1,"63":1,"74":1,"82":1,"94":1,"130":3,"137":1,"140":1,"145":2,"161":1,"173":1,"181":1,"203":1,"220":1,"231":1,"256":2,"258":1,"263":1,"268":1,"273":1,"279":2,"285":1,"298":1,"345":1,"348":2,"365":1,"375":1,"407":1,"418":4,"426":1,"444":1,"446":2,"464":1,"465":1,"466":1,"468":3,"492":1,"502":1,"514":1,"526":1,"531":2,"534":5,"540":1,"549":1,"554":1,"578":1,"586":1,"589":1,"604":1,"607":2,"624":2,"638":1,"660":1,"672":1,"680":1,"685":1,"688":1,"693":1,"696":2,"713":1,"723":2,"736":1,"744":1,"761":1,"764":1,"777":1,"780":1,"797":1,"803":1,"806":1,"822":1,"833":1,"841":1,"857":1,"864":1,"877":1,"892":1,"900":1,"909":1,"917":1,"927":1,"960":1,"962":1,"986":1,"1010":1,"1029":1,"1037":1,"1048":1,"1055":1,"1070":1,"1103":1,"1324":3,"1438":1,"1455":1,"1496":22,"1500":2,"1767":1,"1769":1,"2085":1,"2232":2}}],["correlatable",{"3":{"1350":1}}],["correlating",{"3":{"1339":2,"1345":1,"1641":1}}],["correlationheader",{"3":{"1436":1}}],["correlationcontexttests",{"3":{"1199":1,"1207":2}}],["correlationcontext",{"3":{"1199":7,"1203":1,"1207":2,"1259":1,"1270":1,"1286":3,"1311":10,"1323":1}}],["correlation",{"0":{"1337":1},"3":{"0":9,"26":3,"59":1,"125":1,"193":1,"202":3,"395":2,"397":3,"398":2,"400":2,"407":1,"409":1,"572":1,"608":1,"664":1,"707":1,"714":1,"716":1,"749":1,"808":1,"810":1,"813":1,"815":1,"825":1,"826":5,"827":3,"829":1,"834":1,"899":1,"916":1,"1042":1,"1202":1,"1203":1,"1212":2,"1251":1,"1253":3,"1259":5,"1263":1,"1270":5,"1275":1,"1277":2,"1286":12,"1300":4,"1311":24,"1318":1,"1323":6,"1325":1,"1337":1,"1338":1,"1339":9,"1350":1,"1396":1,"1402":3,"1436":6,"1438":3,"1447":1,"1487":1,"1488":1,"1544":1,"1550":2,"1572":1,"1577":1,"1591":1,"1660":1,"1670":3,"1671":1,"1821":1,"2010":1,"2024":2,"2028":1,"2055":1,"2155":1,"2168":1,"2193":1,"2215":1,"2232":2}}],["correlationidmiddlewaretests",{"3":{"1199":1,"1207":2}}],["correlationidmiddleware",{"2":{"400":1,"409":1,"826":1,"2010":1,"2014":1,"2024":1},"3":{"397":8,"400":1,"409":2,"698":1,"826":2,"1199":3,"1203":1,"1207":2,"1286":1,"1311":13,"1337":1,"1339":2,"1438":1,"2010":1,"2014":1,"2024":1}}],["correlationid",{"2":{"15":1,"26":1,"810":1,"2186":1},"3":{"0":1,"15":1,"26":1,"715":1,"810":1,"1259":6,"1263":1,"1270":7,"1286":8,"1300":1,"1311":8,"1323":3,"1436":1,"1666":1,"2186":1}}],["correlate",{"3":{"813":1,"1323":1,"1396":3,"1402":4,"2193":1}}],["correlates",{"3":{"0":1,"856":1,"1286":1,"1665":1}}],["correlated",{"3":{"0":1,"24":1,"1255":1,"1259":1,"1270":4,"1309":1,"1401":1,"1402":2}}],["corelayers",{"2":{"1489":1},"3":{"1436":2,"1489":1}}],["cores",{"3":{"875":1,"1467":2}}],["corepublicresources",{"2":{"574":1},"3":{"574":1,"1436":3,"1488":1}}],["core",{"0":{"1506":1,"1643":1,"1662":1,"1764":1,"2206":1,"2219":1},"1":{"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1,"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1,"1272":1,"1273":1,"1274":1,"1275":1,"1276":1,"1277":1,"1278":1,"1279":1,"1280":1,"1281":1,"1282":1,"1283":1,"1284":1,"1285":1,"1286":1,"1863":1,"1864":1,"1865":1,"1866":1,"1867":1,"1868":1,"2140":1,"2141":1,"2142":1,"2143":1,"2144":1,"2145":1,"2146":1},"2":{"145":1,"147":1,"149":1,"152":1,"810":1,"960":1,"1067":1,"1074":1,"1075":1,"1225":1,"1426":1,"2176":1},"3":{"0":8,"10":1,"15":3,"24":5,"25":4,"26":9,"27":4,"31":1,"38":1,"39":4,"43":2,"54":1,"59":2,"62":1,"63":1,"68":2,"70":1,"71":3,"72":2,"74":5,"79":1,"80":1,"81":2,"82":1,"100":1,"109":8,"110":1,"111":2,"117":1,"121":3,"122":2,"123":4,"125":3,"126":3,"142":1,"145":4,"147":3,"149":1,"150":3,"152":1,"157":1,"161":1,"164":1,"166":8,"170":3,"171":2,"174":1,"185":1,"186":3,"190":1,"193":1,"196":2,"198":3,"200":2,"201":1,"202":7,"203":1,"207":1,"228":4,"230":4,"231":4,"235":1,"243":1,"246":6,"255":2,"265":2,"284":2,"285":3,"290":1,"291":1,"292":1,"295":2,"303":2,"305":5,"306":1,"310":3,"312":1,"314":2,"318":2,"328":1,"330":7,"331":1,"333":5,"336":1,"337":3,"341":8,"343":2,"344":6,"352":1,"353":2,"358":1,"359":5,"365":1,"370":1,"384":5,"387":2,"391":2,"395":2,"396":1,"397":10,"398":1,"400":1,"404":2,"405":1,"407":2,"408":1,"409":2,"411":1,"415":1,"442":1,"443":2,"444":2,"448":1,"451":1,"459":5,"463":2,"465":2,"469":4,"470":6,"471":1,"475":2,"476":3,"477":4,"481":1,"482":1,"497":6,"498":1,"499":4,"502":2,"522":1,"523":2,"524":1,"536":4,"539":1,"544":3,"545":9,"549":3,"550":1,"551":1,"552":1,"556":1,"558":1,"562":1,"563":1,"564":2,"572":1,"576":1,"583":6,"586":3,"591":1,"592":1,"593":1,"640":4,"644":1,"656":1,"657":5,"664":1,"665":1,"674":16,"676":7,"692":1,"697":1,"698":13,"700":1,"702":9,"703":6,"706":1,"707":3,"715":5,"720":3,"724":2,"725":2,"727":1,"732":1,"733":4,"736":3,"740":1,"741":4,"748":1,"782":3,"798":5,"799":4,"800":1,"804":1,"809":1,"810":2,"813":1,"818":1,"819":12,"822":1,"826":1,"831":1,"833":1,"837":1,"840":1,"846":4,"849":1,"850":1,"853":1,"854":3,"857":3,"858":1,"860":2,"861":2,"862":1,"881":2,"888":2,"890":1,"892":1,"897":2,"904":1,"905":4,"906":4,"908":3,"914":1,"915":1,"916":1,"917":1,"921":1,"922":8,"923":1,"926":5,"930":1,"931":7,"936":1,"939":1,"946":7,"953":1,"960":1,"962":1,"963":4,"964":5,"979":2,"987":3,"988":1,"995":3,"996":2,"999":1,"1004":2,"1018":2,"1034":8,"1036":1,"1037":1,"1041":4,"1042":19,"1043":1,"1044":2,"1045":2,"1049":4,"1050":13,"1052":2,"1055":2,"1058":3,"1059":20,"1067":1,"1074":4,"1075":2,"1081":1,"1082":3,"1083":2,"1086":1,"1091":7,"1100":2,"1103":1,"1107":1,"1108":3,"1116":2,"1119":1,"1124":1,"1132":1,"1133":5,"1140":7,"1150":5,"1160":1,"1168":4,"1174":1,"1175":26,"1192":1,"1202":3,"1203":3,"1211":1,"1212":5,"1213":4,"1217":2,"1219":3,"1220":1,"1222":1,"1223":2,"1225":3,"1226":3,"1227":2,"1228":2,"1229":36,"1230":1,"1231":7,"1232":5,"1233":8,"1234":12,"1235":1,"1236":7,"1237":95,"1239":7,"1240":4,"1241":7,"1242":2,"1243":6,"1244":2,"1246":4,"1247":119,"1249":1,"1251":1,"1259":63,"1260":1,"1261":2,"1262":4,"1263":2,"1264":2,"1265":3,"1266":1,"1267":5,"1268":1,"1269":1,"1270":141,"1271":56,"1272":2,"1273":5,"1274":2,"1275":2,"1276":8,"1277":4,"1278":10,"1279":9,"1280":1,"1281":4,"1282":1,"1283":2,"1284":2,"1285":7,"1286":625,"1288":5,"1289":9,"1290":10,"1291":12,"1292":6,"1293":9,"1294":8,"1295":6,"1296":5,"1297":17,"1299":2,"1300":603,"1301":51,"1302":6,"1303":8,"1304":4,"1305":5,"1306":7,"1307":9,"1308":2,"1309":122,"1310":46,"1311":89,"1312":14,"1314":1,"1315":3,"1316":9,"1317":9,"1318":7,"1319":2,"1320":24,"1321":2,"1322":14,"1323":234,"1324":29,"1330":3,"1332":1,"1333":3,"1336":1,"1337":1,"1338":1,"1339":45,"1341":1,"1343":2,"1350":31,"1351":1,"1352":1,"1359":223,"1360":1,"1361":1,"1362":2,"1363":1,"1369":12,"1380":14,"1395":5,"1396":84,"1402":17,"1414":1,"1415":75,"1416":5,"1417":3,"1418":1,"1419":2,"1420":6,"1421":1,"1422":3,"1423":5,"1424":5,"1425":3,"1426":2,"1427":39,"1430":2,"1431":1,"1433":3,"1435":4,"1436":86,"1437":8,"1438":13,"1441":2,"1442":1,"1443":7,"1447":1,"1451":1,"1453":1,"1454":1,"1456":2,"1461":1,"1465":1,"1467":6,"1486":5,"1487":2,"1488":4,"1489":3,"1490":2,"1496":25,"1497":1,"1526":3,"1531":2,"1534":1,"1535":1,"1559":1,"1560":1,"1576":2,"1577":13,"1582":3,"1583":1,"1585":2,"1586":2,"1591":1,"1596":1,"1599":1,"1602":1,"1605":1,"1629":1,"1630":1,"1640":1,"1641":1,"1642":1,"1643":1,"1646":1,"1648":1,"1653":1,"1660":9,"1661":1,"1664":1,"1668":1,"1671":2,"1677":1,"1682":1,"1692":1,"1693":2,"1707":1,"1708":1,"1739":1,"1752":1,"1779":2,"1782":1,"1790":1,"1793":1,"1805":2,"1806":1,"1807":1,"1810":3,"1811":1,"1813":1,"1815":6,"1817":2,"1825":1,"1829":1,"1853":1,"1862":2,"1863":2,"1864":1,"1868":1,"1895":1,"1896":1,"1903":1,"1922":1,"1929":1,"1934":1,"1940":1,"1970":1,"1988":1,"1989":1,"1994":2,"1998":1,"2000":5,"2002":1,"2006":2,"2010":1,"2016":2,"2026":1,"2028":2,"2032":1,"2039":1,"2040":1,"2043":2,"2050":1,"2053":1,"2055":2,"2059":1,"2061":1,"2064":2,"2066":3,"2071":1,"2079":1,"2099":1,"2104":2,"2107":1,"2139":1,"2140":3,"2142":1,"2146":3,"2153":2,"2154":1,"2156":2,"2160":1,"2176":1,"2207":1,"2208":1,"2211":1,"2215":1,"2219":3,"2220":1,"2225":1,"2227":4,"2232":4,"2234":1,"2236":1}}],["cone",{"1":{"2050":1,"2051":1,"2052":1,"2053":1,"2054":1,"2055":1,"2056":1,"2057":1,"2058":1,"2059":1,"2060":1},"3":{"2050":2,"2060":1,"2210":1}}],["conviction",{"3":{"2039":1}}],["convey",{"3":{"1396":1}}],["conveyed",{"3":{"1359":1,"1558":1}}],["conveys",{"3":{"332":1,"1990":1}}],["convene",{"3":{"2240":1}}],["convenient",{"3":{"1051":1,"1286":1,"1324":1,"1339":1,"1380":2,"1395":1,"1396":1,"1415":1,"1417":1,"1436":3,"1801":1,"1955":1,"1984":1,"2039":1,"2040":1}}],["conveniences",{"3":{"1270":1,"1395":1,"1436":1,"1805":1}}],["convenience",{"3":{"0":1,"318":1,"501":1,"782":1,"826":1,"1144":1,"1229":1,"1237":1,"1244":2,"1247":1,"1270":2,"1278":1,"1286":4,"1300":1,"1301":1,"1309":1,"1311":2,"1315":1,"1323":1,"1324":7,"1350":1,"1359":1,"1369":1,"1395":1,"1396":3,"1402":3,"1415":1,"1416":1,"1417":2,"1436":9,"1627":1,"1640":2,"1641":1,"1885":1,"1915":1,"1952":1,"1956":1,"1959":2,"1989":1,"1994":1,"2013":1,"2178":1,"2179":1}}],["conventiontests",{"3":{"1436":1}}],["conventionforeignkeysrestrictdeletes",{"3":{"1436":4}}],["conventiondeletes",{"2":{"1083":1},"3":{"1083":1,"1436":3}}],["conventionally",{"3":{"1237":1}}],["conventional",{"3":{"318":1,"657":1,"853":1,"1226":1,"1270":2,"1286":1,"1300":2,"1311":1,"1323":4,"1324":1,"1339":2,"1341":1,"1350":1,"1359":2,"1362":1,"1369":2,"1395":1,"1396":3,"1415":1,"1423":1,"1436":3,"1442":1,"2074":1,"2232":1}}],["conventionsource",{"3":{"1286":2}}],["conventions",{"0":{"1214":1,"1281":1},"1":{"1210":1,"1211":1,"1212":1,"1213":1,"1214":1,"1215":1,"1216":1},"3":{"0":1,"40":1,"164":1,"169":1,"707":1,"711":1,"801":1,"888":1,"889":1,"890":1,"891":1,"1004":2,"1020":1,"1035":1,"1050":2,"1051":1,"1083":2,"1086":1,"1090":1,"1174":1,"1176":1,"1178":1,"1192":2,"1202":1,"1203":6,"1207":46,"1210":1,"1212":1,"1229":1,"1247":2,"1259":3,"1270":1,"1271":1,"1272":1,"1273":1,"1280":2,"1281":7,"1282":1,"1286":37,"1300":9,"1310":1,"1311":4,"1324":1,"1350":1,"1362":2,"1369":1,"1380":7,"1396":7,"1403":1,"1415":3,"1427":2,"1431":2,"1436":9,"1437":6,"1438":3,"1452":1,"1488":1,"1489":2,"1496":1,"1526":1,"1537":2,"1546":1,"1552":2,"1557":1,"1571":2,"1574":1,"1577":1,"1591":1,"1654":1,"1671":3,"1687":1,"1690":1,"1701":1,"1702":1,"1775":1,"1797":1,"1811":1,"1952":1,"1958":1,"2063":1,"2064":1,"2088":2,"2098":1,"2104":3,"2211":1}}],["convention",{"0":{"1883":1,"1952":1},"1":{"89":1,"90":1,"91":1,"92":1,"93":1,"94":1,"95":1,"96":1,"97":1,"98":1,"99":1,"100":1,"101":1,"102":1,"103":1,"104":1,"105":1,"1001":1,"1002":1,"1003":1,"1004":1,"1005":1,"1006":1,"1007":1,"1008":1},"2":{"1083":1,"2064":1},"3":{"0":22,"3":1,"4":1,"5":1,"6":1,"47":1,"53":2,"76":2,"79":1,"80":2,"89":1,"107":1,"117":1,"122":1,"134":1,"139":1,"140":1,"141":1,"142":1,"147":1,"162":1,"167":1,"169":1,"175":1,"216":1,"279":1,"283":1,"299":1,"301":1,"302":1,"305":1,"342":1,"343":1,"360":1,"381":1,"382":1,"388":1,"455":1,"469":1,"472":1,"473":1,"482":1,"483":1,"550":1,"564":1,"565":2,"583":1,"585":1,"620":1,"676":1,"713":1,"718":1,"726":1,"741":1,"743":1,"782":1,"784":1,"785":2,"801":2,"802":1,"845":1,"887":1,"888":1,"889":11,"890":3,"891":2,"892":1,"893":2,"899":1,"916":1,"922":1,"924":2,"947":1,"948":1,"956":1,"989":1,"1001":1,"1003":1,"1004":2,"1005":1,"1008":1,"1018":1,"1019":2,"1034":4,"1035":1,"1050":3,"1051":4,"1052":1,"1083":8,"1084":2,"1085":1,"1087":1,"1091":1,"1134":1,"1137":1,"1160":2,"1162":1,"1164":1,"1168":2,"1171":1,"1192":2,"1210":1,"1212":1,"1214":1,"1229":1,"1237":4,"1239":1,"1247":8,"1249":2,"1255":1,"1259":7,"1270":14,"1271":9,"1281":14,"1286":68,"1300":38,"1301":1,"1309":3,"1310":1,"1311":23,"1312":2,"1315":1,"1316":1,"1323":19,"1324":18,"1335":1,"1339":2,"1350":12,"1353":1,"1354":1,"1359":83,"1362":4,"1369":12,"1380":5,"1395":11,"1396":45,"1400":1,"1402":14,"1415":31,"1416":3,"1417":3,"1418":1,"1425":1,"1427":1,"1431":3,"1436":90,"1437":1,"1438":3,"1455":2,"1461":1,"1467":3,"1473":1,"1488":1,"1489":2,"1492":1,"1493":1,"1496":3,"1497":1,"1500":1,"1526":3,"1539":1,"1541":1,"1545":1,"1575":2,"1577":6,"1584":1,"1585":1,"1586":2,"1591":2,"1596":1,"1630":1,"1631":1,"1632":1,"1649":2,"1651":4,"1656":3,"1661":1,"1662":1,"1664":1,"1672":1,"1673":1,"1685":1,"1686":1,"1701":1,"1703":1,"1720":1,"1730":1,"1736":1,"1790":1,"1792":1,"1793":1,"1794":1,"1800":1,"1802":2,"1810":1,"1811":1,"1830":3,"1849":2,"1856":1,"1858":3,"1860":1,"1875":1,"1877":1,"1882":1,"1883":1,"1886":1,"1889":1,"1891":1,"1902":1,"1949":1,"1951":2,"1952":3,"1954":1,"1956":1,"1958":2,"1959":2,"2000":2,"2013":1,"2031":1,"2039":2,"2043":3,"2045":1,"2049":1,"2053":1,"2063":1,"2064":2,"2093":1,"2095":1,"2097":1,"2100":1,"2107":1,"2137":1,"2138":1,"2163":1,"2167":1,"2168":1,"2177":1,"2232":3}}],["conversely",{"3":{"1396":1,"1550":1}}],["conversation",{"3":{"1359":1,"1423":1,"1427":1}}],["conversion",{"3":{"0":1,"109":1,"111":1,"360":1,"545":1,"547":1,"550":1,"657":1,"698":1,"1237":5,"1269":1,"1270":2,"1271":1,"1281":1,"1286":4,"1300":6,"1311":1,"1324":1,"1339":1,"1350":4,"1356":1,"1359":15,"1369":9,"1395":13,"1396":4,"1415":3,"1417":1,"1427":1,"1438":2,"1577":1,"1639":1,"2146":1}}],["conversions",{"3":{"0":1,"110":1,"657":1,"798":1,"806":1,"964":2,"1034":1,"1050":2,"1191":1,"1203":11,"1207":56,"1220":1,"1229":1,"1237":4,"1273":1,"1277":1,"1281":1,"1282":4,"1286":32,"1362":1,"1369":1,"1395":1,"1496":3,"1662":1}}],["converge",{"3":{"539":1,"838":1,"840":1,"881":1,"1311":1,"1324":3,"1369":1,"1396":2,"1402":2,"1411":1,"1415":2,"1441":1,"2166":1,"2167":1}}],["convergence",{"3":{"260":1,"840":1,"1415":1,"1465":1}}],["converges",{"3":{"109":1,"1417":1,"1436":1,"1765":3,"1806":1,"2061":1}}],["converged",{"0":{"91":1},"3":{"0":2,"97":1,"101":1,"148":1,"755":1,"1090":1,"1599":1,"2025":1}}],["converttimefromutc",{"2":{"1639":1},"3":{"1639":1}}],["converttimetoutc",{"2":{"1348":1},"3":{"1348":1,"1350":2,"1395":1}}],["convertto",{"3":{"1300":1}}],["convertfrom",{"3":{"1300":2}}],["convertfromrsasecuritykey",{"2":{"1300":1},"3":{"1300":1}}],["convertfrominvariantstring",{"3":{"1286":1}}],["convertname",{"3":{"1247":1}}],["convertible",{"3":{"1237":1}}],["converting",{"3":{"0":1,"110":1,"826":1,"964":1,"1237":2,"1270":3,"1311":3,"1323":1,"1339":1,"1350":2,"1359":1,"1378":1,"1396":3,"1427":1,"1436":1,"1770":1}}],["converted",{"3":{"881":1,"964":1,"1050":1,"1234":1,"1235":1,"1244":1,"1270":1,"1286":3,"1289":1,"1300":1,"1312":1,"1323":2,"1324":1,"1348":1,"1350":2,"1359":4,"1380":1,"1395":3,"1396":2,"1402":1,"1415":4,"1639":1,"2067":1,"2185":1}}],["convertertype",{"3":{"1050":1,"1286":2,"1300":1}}],["converters",{"2":{"964":1,"1235":1},"3":{"0":3,"472":1,"656":1,"658":1,"659":3,"800":1,"963":1,"964":4,"1050":2,"1051":1,"1192":1,"1202":1,"1203":1,"1212":1,"1223":1,"1229":7,"1235":1,"1237":2,"1272":1,"1282":1,"1286":2,"1311":6,"1431":1,"1436":1,"1496":1,"1662":3}}],["converter",{"0":{"2142":1},"1":{"356":1,"357":1,"358":1,"359":1,"360":1,"361":1,"362":1,"363":1,"364":1,"365":1,"366":1},"3":{"0":2,"356":1,"357":1,"358":2,"359":10,"361":4,"362":2,"363":2,"364":1,"365":8,"366":1,"470":1,"471":1,"605":1,"655":1,"656":1,"657":4,"659":1,"660":1,"798":2,"800":1,"806":1,"963":2,"964":7,"965":3,"967":1,"1034":1,"1035":1,"1050":4,"1051":2,"1052":2,"1055":2,"1192":1,"1212":1,"1223":3,"1229":7,"1237":10,"1241":1,"1247":1,"1270":1,"1272":1,"1281":1,"1284":2,"1286":45,"1300":10,"1309":3,"1311":10,"1350":1,"1369":7,"1396":1,"1415":6,"1436":5,"1496":1,"1498":1,"1577":1,"1903":2,"2063":2,"2140":2,"2142":8,"2143":4,"2144":2,"2145":1,"2232":1}}],["convert",{"3":{"461":1,"905":2,"1077":1,"1300":9,"1309":1,"1311":1,"1324":2,"1332":1,"1339":1,"1359":1,"1395":2,"1427":1,"1436":2,"1525":1,"1576":1,"1585":1,"1768":1,"1802":1}}],["converts",{"3":{"0":3,"87":1,"109":4,"122":1,"235":1,"295":1,"610":1,"627":1,"675":1,"726":1,"758":1,"767":1,"820":1,"881":1,"947":1,"964":1,"1034":1,"1050":2,"1052":1,"1055":1,"1143":1,"1225":1,"1226":1,"1229":1,"1237":1,"1244":1,"1247":6,"1270":3,"1271":2,"1300":3,"1309":2,"1311":4,"1315":1,"1323":1,"1324":7,"1332":1,"1339":4,"1350":3,"1358":1,"1359":8,"1369":2,"1380":1,"1395":10,"1396":8,"1415":7,"1417":1,"1436":2,"1443":1,"1475":1,"1489":2,"1702":2,"1765":1,"1831":1,"2012":1,"2032":1,"2038":2,"2203":1}}],["condensed",{"3":{"2000":1,"2008":1,"2084":1,"2156":1}}],["conduct",{"3":{"1673":1}}],["conditioned",{"3":{"631":1,"633":2,"1286":1,"1535":1,"1591":1,"1874":1}}],["conditions",{"3":{"140":1,"188":1,"341":1,"549":1,"626":1,"766":1,"1073":1,"1247":1,"1270":3,"1274":1,"1310":1,"1311":3,"1324":2,"1359":4,"1369":1,"1395":1,"1417":1,"1455":1,"1463":1,"1918":1,"1964":1,"2196":1,"2203":1}}],["condition",{"3":{"1":1,"135":1,"138":2,"141":1,"195":1,"200":1,"201":1,"319":1,"342":1,"343":1,"474":2,"529":1,"567":1,"624":1,"626":4,"628":1,"629":1,"633":2,"684":1,"757":1,"764":2,"766":2,"797":1,"798":1,"800":1,"808":1,"809":1,"813":1,"831":1,"863":1,"922":1,"931":1,"997":1,"998":1,"1067":1,"1139":1,"1149":1,"1220":1,"1247":3,"1270":1,"1286":4,"1300":1,"1309":1,"1311":3,"1323":1,"1324":3,"1328":1,"1339":1,"1358":1,"1359":10,"1395":4,"1396":4,"1415":1,"1416":1,"1417":2,"1429":1,"1430":1,"1433":1,"1436":9,"1441":1,"1443":1,"1444":2,"1449":1,"1453":1,"1454":1,"1455":7,"1459":1,"1467":1,"1475":1,"1486":1,"1492":5,"1524":1,"1525":1,"1526":3,"1531":1,"1591":5,"1596":1,"1599":1,"1600":1,"1640":1,"1653":1,"1687":1,"1876":1}}],["conditionality",{"3":{"1531":1}}],["conditionalweaktable",{"2":{"1274":1},"3":{"1274":1,"1286":9,"1323":1}}],["conditionalwrite",{"3":{"1270":1}}],["conditionalwriteroottouchtests",{"3":{"1199":1,"1207":2}}],["conditionalwriteconventiontests",{"3":{"1199":2,"1207":4,"1311":2,"1380":3,"1437":2,"1438":4}}],["conditionals",{"3":{"1247":1,"1286":1,"1324":1,"1350":1,"1396":1,"1761":1}}],["conditionally",{"3":{"245":1,"674":1,"996":1,"1176":1,"1180":1,"1259":1,"1278":1,"1286":3,"1311":2,"1324":3,"1339":1,"1359":2,"1364":1,"1395":1,"1396":2,"1416":1,"1455":1,"1466":1,"1467":1,"1526":1,"1577":1,"1773":1,"1814":1,"1958":1,"2012":1}}],["conditional",{"0":{"1375":1},"3":{"0":8,"6":2,"109":1,"259":1,"260":1,"261":1,"339":1,"340":1,"341":1,"342":2,"343":2,"344":1,"401":1,"413":1,"535":1,"556":1,"599":3,"609":1,"624":3,"626":1,"703":1,"749":1,"764":1,"809":1,"879":1,"905":2,"920":1,"921":1,"922":2,"995":1,"996":2,"998":1,"1108":1,"1110":1,"1212":2,"1237":3,"1255":1,"1259":1,"1265":1,"1270":7,"1275":1,"1278":1,"1286":13,"1290":1,"1300":11,"1306":1,"1309":3,"1310":1,"1311":14,"1316":1,"1323":4,"1324":8,"1333":1,"1339":3,"1350":2,"1359":48,"1373":1,"1375":1,"1380":12,"1389":1,"1395":15,"1396":12,"1402":6,"1415":2,"1416":2,"1417":3,"1419":1,"1421":1,"1427":2,"1436":4,"1437":2,"1438":2,"1443":1,"1455":3,"1467":8,"1469":1,"1526":2,"1531":2,"1532":1,"1565":1,"1577":2,"1590":1,"1591":1,"1600":1,"1631":1,"1640":1,"1651":1,"1685":1,"1702":1,"1727":1,"1748":2,"1749":1,"1765":3,"1825":1,"1856":1,"1871":2,"1874":2,"1876":2,"1916":1,"1923":1,"1952":2,"1955":1,"1958":1,"2123":1,"2158":1,"2161":1,"2185":1,"2232":1}}],["conn",{"3":{"1467":1,"1496":1,"1577":1}}],["connectasync",{"3":{"1339":1}}],["connecttimeout",{"3":{"1324":1,"1339":1}}],["connecting",{"3":{"1190":1,"1324":1,"1438":1,"1455":1}}],["connectivitychangedeventargs",{"3":{"1417":1}}],["connectivitychanged",{"3":{"1417":7}}],["connectivity",{"3":{"291":1,"412":1,"413":1,"1192":1,"1202":1,"1203":1,"1324":1,"1337":1,"1395":1,"1417":15,"1455":1,"1496":1,"1669":1,"2118":1}}],["connectionfactory",{"3":{"1324":2}}],["connectionid",{"3":{"1309":1}}],["connectionidentityincludesdatabasename",{"2":{"1279":1},"3":{"1279":1,"1286":3}}],["connectionmode",{"3":{"1286":2}}],["connectionmultiplexer",{"2":{"1301":1},"3":{"733":1,"1301":4,"1339":2}}],["connectionname",{"3":{"243":1,"1301":1,"1332":2,"1339":5}}],["connectionsperuser",{"3":{"1309":1,"1323":1}}],["connectionstringexpression",{"2":{"1327":1},"3":{"1327":1,"1339":2}}],["connectionstringfor",{"3":{"1286":7}}],["connectionstringrequired",{"2":{"1175":1,"1280":1},"3":{"1175":1,"1280":1,"1286":4}}],["connectionstringsettingstests",{"3":{"1199":1,"1207":1}}],["connectionstringsettings",{"2":{"1175":1,"1272":1,"1279":1,"1320":1},"3":{"674":1,"1175":3,"1177":1,"1199":27,"1203":1,"1207":2,"1272":1,"1279":2,"1286":29,"1320":4,"1323":3}}],["connectionstringsettingsvalidatortests",{"3":{"1199":1,"1207":2,"1286":1,"1438":2}}],["connectionstringsettingsvalidator",{"2":{"170":1,"676":1,"1279":1,"1320":1},"3":{"170":2,"676":2,"1199":3,"1203":1,"1207":2,"1279":2,"1286":31,"1320":4,"1323":2}}],["connectionstrings",{"2":{"640":1,"1332":1,"1441":1,"1446":1,"1652":1,"1722":1,"1729":1},"3":{"166":1,"640":3,"676":1,"1034":1,"1279":3,"1286":18,"1323":6,"1332":1,"1335":2,"1339":4,"1436":2,"1441":1,"1443":1,"1446":1,"1467":2,"1652":1,"1722":1,"1729":1}}],["connectionstring",{"2":{"440":1,"601":1,"604":1,"640":1,"644":1},"3":{"0":1,"170":1,"434":1,"440":1,"601":1,"604":1,"640":3,"644":1,"1067":1,"1286":10,"1323":13,"1339":3,"1429":1,"1436":2,"1441":1,"1467":2,"2126":1}}],["connections",{"0":{"2011":1},"2":{"1443":1,"1677":1},"3":{"0":3,"174":1,"223":1,"378":1,"382":1,"384":1,"608":1,"818":1,"1035":1,"1124":1,"1273":1,"1286":3,"1296":1,"1300":1,"1302":2,"1307":2,"1309":5,"1323":3,"1324":4,"1329":2,"1339":2,"1436":3,"1443":3,"1467":4,"1474":1,"1490":1,"1653":1,"1667":1,"1677":1,"1840":1,"1945":2,"1949":1,"2011":1,"2014":1,"2158":1}}],["connectionaborted",{"2":{"135":1},"3":{"0":1,"135":2,"1309":3,"1436":3}}],["connection",{"2":{"175":1,"397":1,"914":1,"1333":1,"1337":1,"1443":1,"1446":1,"1467":1,"2010":1,"2156":1,"2159":1},"3":{"0":11,"71":1,"72":1,"100":2,"109":1,"155":1,"159":1,"164":2,"166":2,"168":1,"170":4,"171":1,"175":1,"179":1,"225":1,"227":1,"228":3,"234":2,"235":4,"236":1,"237":2,"241":1,"243":2,"245":1,"259":2,"260":2,"261":1,"358":1,"379":2,"380":2,"381":2,"384":2,"390":1,"392":1,"397":2,"572":1,"598":3,"599":7,"601":2,"602":1,"604":1,"605":1,"608":2,"610":1,"633":1,"640":5,"642":1,"643":2,"644":1,"673":1,"698":3,"699":1,"700":2,"733":1,"736":1,"742":1,"749":1,"819":2,"820":1,"827":1,"830":1,"832":1,"881":1,"905":1,"914":5,"988":2,"996":2,"998":1,"1034":3,"1036":1,"1043":1,"1066":1,"1067":1,"1123":2,"1133":1,"1150":1,"1175":4,"1176":1,"1192":1,"1212":3,"1225":1,"1259":2,"1273":1,"1276":1,"1278":2,"1279":7,"1280":4,"1284":1,"1286":112,"1299":1,"1300":11,"1301":3,"1307":5,"1309":19,"1311":17,"1312":5,"1320":3,"1323":30,"1324":41,"1326":3,"1328":2,"1329":2,"1330":1,"1332":4,"1333":1,"1334":3,"1335":1,"1337":1,"1338":1,"1339":63,"1349":1,"1378":1,"1379":1,"1380":4,"1395":2,"1396":6,"1400":2,"1402":1,"1415":2,"1416":2,"1417":3,"1429":6,"1430":4,"1436":33,"1438":6,"1441":3,"1442":3,"1443":7,"1444":3,"1446":2,"1447":4,"1449":1,"1451":1,"1455":1,"1460":1,"1467":26,"1473":2,"1474":2,"1477":3,"1481":2,"1486":1,"1488":4,"1490":1,"1492":1,"1496":2,"1526":1,"1548":1,"1591":1,"1652":3,"1653":2,"1663":1,"1664":3,"1668":2,"1670":2,"1671":1,"1677":3,"1684":4,"1688":1,"1697":1,"1702":1,"1719":2,"1720":1,"1724":1,"1727":1,"1790":1,"1806":1,"1847":1,"1849":4,"1851":1,"1853":1,"1906":1,"1916":1,"1923":1,"1927":1,"1935":6,"1940":1,"1942":2,"1943":1,"1944":2,"1945":5,"1949":1,"1950":1,"2000":2,"2002":1,"2003":1,"2007":1,"2008":3,"2010":1,"2011":1,"2012":1,"2032":1,"2053":1,"2056":1,"2118":1,"2127":1,"2140":1,"2141":1,"2156":2,"2158":2,"2159":1,"2160":1,"2193":1,"2230":1,"2232":2}}],["connect",{"3":{"212":1,"213":1,"214":3,"215":1,"216":2,"219":4,"225":1,"1193":1,"1300":1,"1311":2,"1312":2,"1324":14,"1339":8,"1340":1,"1342":1,"1350":3,"1359":1,"1364":1,"1369":1,"1392":1,"1395":1,"1438":3,"1441":1,"1446":1,"1452":1,"1467":2,"1477":1,"1525":1,"1526":2,"1636":1,"2147":1,"2148":1,"2150":4,"2152":1,"2153":2,"2244":1}}],["connects",{"3":{"0":1,"272":1,"382":1,"1184":1,"1324":1,"1359":1,"1397":1,"1436":3,"1467":2,"1473":1,"1489":1,"1596":1,"1949":1,"2047":1}}],["connected",{"3":{"0":3,"135":2,"139":1,"193":1,"200":2,"224":1,"226":1,"227":1,"272":1,"433":1,"1123":1,"1237":1,"1257":1,"1286":1,"1302":1,"1304":1,"1307":1,"1309":7,"1311":1,"1324":2,"1395":2,"1396":1,"1402":2,"1415":1,"1436":9,"1438":2,"1932":1,"1936":1,"1940":1,"1946":1,"2039":1}}],["conjure",{"3":{"1415":1}}],["conjunction",{"3":{"1324":1}}],["conjunct",{"3":{"1237":1}}],["conjoined",{"3":{"1237":1}}],["concise",{"3":{"1311":1}}],["concretions",{"3":{"1309":1,"1538":1}}],["concretion",{"3":{"1309":2}}],["concretes",{"3":{"1436":1}}],["concreteenginebranching",{"3":{"1436":3}}],["concreteclasses",{"3":{"1436":11}}],["concretely",{"3":{"1271":1,"1286":1,"1312":1,"1324":1,"1359":5,"1396":1,"1417":1,"1436":1,"1790":1,"2016":1}}],["concretedomainexception",{"3":{"1199":2,"1207":1}}],["concrete",{"0":{"1957":1},"3":{"0":3,"5":1,"7":1,"47":1,"76":2,"78":1,"80":3,"81":1,"117":1,"136":1,"151":1,"160":1,"166":1,"185":1,"189":1,"243":1,"255":1,"318":1,"329":1,"331":1,"405":1,"449":1,"468":1,"469":1,"470":2,"474":1,"477":1,"518":1,"543":1,"545":1,"583":1,"657":1,"674":2,"675":1,"714":1,"725":3,"726":1,"782":1,"799":1,"848":1,"914":1,"921":1,"922":2,"926":1,"963":2,"964":3,"965":2,"966":1,"1058":1,"1060":2,"1100":2,"1168":4,"1169":1,"1170":1,"1212":9,"1217":1,"1223":1,"1226":1,"1229":6,"1230":1,"1231":2,"1234":1,"1235":1,"1236":1,"1237":11,"1239":2,"1240":1,"1244":1,"1247":12,"1249":1,"1257":1,"1259":14,"1261":1,"1262":1,"1265":1,"1266":1,"1270":7,"1271":1,"1278":1,"1282":2,"1286":40,"1287":1,"1288":1,"1289":2,"1300":20,"1301":1,"1302":1,"1303":1,"1309":11,"1310":9,"1311":21,"1312":2,"1315":2,"1316":1,"1320":1,"1322":1,"1323":27,"1324":9,"1331":1,"1332":1,"1333":1,"1334":1,"1339":3,"1350":8,"1352":1,"1354":1,"1359":86,"1360":1,"1363":2,"1366":1,"1369":8,"1370":1,"1371":1,"1380":7,"1381":1,"1383":1,"1395":28,"1396":20,"1402":1,"1412":1,"1415":17,"1417":6,"1421":1,"1427":3,"1428":2,"1429":1,"1436":81,"1438":1,"1455":2,"1465":1,"1488":2,"1489":2,"1490":1,"1491":1,"1496":2,"1498":1,"1526":1,"1536":1,"1538":1,"1577":2,"1585":2,"1634":1,"1646":1,"1651":3,"1656":1,"1666":1,"1697":1,"1700":2,"1719":1,"1797":1,"1804":1,"1806":1,"1810":1,"1815":1,"1820":1,"1824":1,"1829":1,"1848":1,"1849":1,"1856":1,"1871":1,"1879":1,"1881":1,"1883":1,"1889":1,"1891":3,"1895":1,"1915":1,"1922":1,"1929":3,"1930":3,"1931":2,"1941":1,"1952":1,"1953":1,"1987":1,"1988":1,"2070":1,"2072":1,"2074":1,"2078":1,"2079":1,"2080":2,"2171":1,"2230":1}}],["concat",{"3":{"1270":2,"1300":1,"1301":2,"1323":1,"1395":2,"1467":1,"1496":3}}],["concatenating",{"3":{"1132":1,"1324":1,"1350":1}}],["concatenation",{"3":{"86":1,"265":2,"1018":1,"1247":2,"1286":1,"1311":1,"1324":1,"1359":1,"1436":4,"1526":1,"1564":1}}],["concatenates",{"3":{"219":1,"359":1,"651":1,"1242":1,"1286":1,"1324":1,"1339":1,"1359":1,"1395":2,"1396":1,"1422":1,"1427":1}}],["concatenated",{"3":{"0":2,"230":1,"665":1,"1018":1,"1133":2,"1135":2,"1212":1,"1241":1,"1247":1,"1286":2,"1300":2,"1324":3,"1359":1,"1395":1,"1396":1,"1416":1,"1431":1,"1436":4,"1553":1,"1564":1,"1815":1,"1989":1}}],["concluding",{"3":{"486":1,"1467":1,"1476":1}}],["concluded",{"3":{"626":2,"640":1,"1075":1,"1438":1,"1455":2,"1475":1,"1492":1,"1526":2}}],["concludes",{"3":{"529":1,"1395":1,"1445":1,"1455":1,"1456":1,"1630":1}}],["conclude",{"3":{"230":1,"626":2,"820":1,"1286":1,"1455":2,"1492":1,"1526":1,"2032":1}}],["conclusions",{"3":{"624":1,"640":1}}],["conclusion",{"3":{"0":2,"122":1,"230":1,"241":1,"256":1,"624":3,"626":2,"627":1,"640":1,"803":1,"804":1,"1051":1,"1090":1,"1263":1,"1270":2,"1300":1,"1309":1,"1359":2,"1455":4,"1456":1,"1492":2,"1821":1,"1952":1,"2170":1,"2189":1}}],["concentrating",{"3":{"1286":1,"1300":1,"1323":1,"1369":1}}],["concentrate",{"3":{"1638":1}}],["concentrates",{"3":{"799":1,"802":1,"1053":1,"1110":1,"1416":2}}],["concentrated",{"3":{"799":1,"1286":1}}],["concert",{"3":{"1270":1}}],["concerned",{"3":{"1300":2,"1940":1}}],["concern",{"0":{"1879":1},"3":{"40":1,"49":1,"70":1,"73":1,"117":1,"119":1,"134":1,"139":1,"156":1,"162":2,"176":1,"177":1,"188":1,"223":1,"302":1,"317":1,"340":1,"342":1,"346":1,"351":1,"413":1,"416":1,"447":1,"648":1,"673":1,"683":1,"692":2,"698":1,"699":1,"714":1,"717":1,"827":2,"846":1,"1091":1,"1191":1,"1192":1,"1194":1,"1200":1,"1201":1,"1229":1,"1259":2,"1260":1,"1264":1,"1267":1,"1270":8,"1271":1,"1282":1,"1286":7,"1300":7,"1309":3,"1311":9,"1312":2,"1316":1,"1323":7,"1324":8,"1339":3,"1353":1,"1359":18,"1360":1,"1369":2,"1380":9,"1387":1,"1395":12,"1396":7,"1402":5,"1415":7,"1417":6,"1422":1,"1423":1,"1427":1,"1436":16,"1490":2,"1496":3,"1507":2,"1511":1,"1515":1,"1554":1,"1577":3,"1601":1,"1638":3,"1640":1,"1641":1,"1753":1,"1815":1,"1819":1,"1821":1,"1823":1,"1826":2,"1827":4,"1831":1,"1834":1,"1852":1,"1871":1,"1886":1,"1914":1,"1915":1,"1927":1,"1928":1,"1964":1,"1969":1,"1974":1,"1992":3,"2001":1,"2070":1,"2072":1,"2107":1,"2130":1,"2134":2,"2138":1,"2139":2,"2148":1,"2175":1,"2180":2}}],["concerns",{"0":{"1753":1,"2063":1},"3":{"0":1,"39":1,"61":1,"116":3,"117":2,"194":1,"255":1,"611":1,"673":1,"826":2,"1011":1,"1013":1,"1076":1,"1191":1,"1195":1,"1200":1,"1212":1,"1231":1,"1232":1,"1237":2,"1259":1,"1260":1,"1261":1,"1270":3,"1286":6,"1300":2,"1301":1,"1311":11,"1312":2,"1313":1,"1323":1,"1324":6,"1325":1,"1339":2,"1347":1,"1359":8,"1360":1,"1380":2,"1395":2,"1396":8,"1415":2,"1416":1,"1417":8,"1436":3,"1490":1,"1505":1,"1526":1,"1534":1,"1536":2,"1538":1,"1540":1,"1542":1,"1547":1,"1551":1,"1555":1,"1565":1,"1575":1,"1599":1,"1638":2,"1641":1,"1664":1,"1680":1,"1780":1,"1796":3,"1807":1,"1810":1,"1818":2,"1819":1,"1820":1,"1826":1,"1827":2,"1916":1,"1933":1,"2023":1,"2063":1,"2069":1,"2072":1,"2074":1,"2096":1,"2139":1,"2208":1,"2230":1}}],["concession",{"3":{"557":1,"1436":1}}],["conceptual",{"3":{"1259":1,"1339":1,"1638":1}}],["conceptually",{"3":{"1201":1,"1259":1,"1300":1,"1323":1,"1339":2,"1436":1}}],["concepts",{"1":{"1210":1,"1211":1,"1212":1,"1213":1,"1214":1,"1215":1,"1216":1},"3":{"1192":1,"1210":1,"1212":1,"1213":1,"1230":1,"1234":1,"1247":1,"1286":2,"1300":1,"1309":1,"1359":1,"1369":1,"1395":1,"1502":1,"1541":1,"1867":1,"1958":1,"2230":1}}],["concept",{"1":{"1502":1,"1503":1,"1504":1,"1505":1,"1506":1,"1507":1,"1508":1,"1509":1,"1510":1,"1511":1,"1512":1,"1513":1,"1514":1,"1515":1,"1516":1,"1517":1},"3":{"0":1,"319":1,"690":1,"1004":1,"1074":1,"1190":1,"1193":1,"1217":1,"1229":18,"1237":40,"1247":43,"1259":45,"1270":61,"1271":11,"1286":191,"1300":176,"1301":13,"1309":61,"1310":12,"1311":104,"1312":7,"1323":104,"1324":184,"1339":75,"1350":118,"1359":398,"1369":43,"1380":65,"1395":171,"1396":240,"1402":107,"1415":134,"1416":21,"1417":104,"1427":26,"1436":443,"1496":12,"1502":1,"1718":1,"1727":1,"1776":1,"1814":1}}],["concurrencyconflictdetector",{"2":{"1359":1},"3":{"1359":2}}],["concurrencyconventiontests",{"3":{"341":2,"1199":2,"1207":4,"1436":38,"1489":2,"1526":1,"1577":1,"1591":1}}],["concurrencyconventiontestsbase",{"2":{"341":1,"1651":1,"1654":1,"1875":1},"3":{"341":2,"1199":3,"1207":2,"1311":1,"1436":10,"1651":1,"1654":1,"1875":1}}],["concurrencylimiter",{"3":{"1324":1}}],["concurrencylimiteroptions",{"3":{"1324":1,"1339":1}}],["concurrencytokenrequest",{"3":{"1496":2}}],["concurrencytrackingtokenstorage",{"3":{"1199":2,"1207":1}}],["concurrencytagof",{"3":{"341":1,"881":1,"1311":1,"1324":2}}],["concurrencypartitionkey",{"3":{"1324":1}}],["concurrencypartition",{"3":{"827":1,"1324":1,"1337":1,"1339":2}}],["concurrencyetagtests",{"3":{"1199":1,"1207":2,"1300":2,"1438":1}}],["concurrencyetag",{"2":{"341":1,"881":1},"3":{"341":5,"881":1,"1199":17,"1203":1,"1207":2,"1300":20,"1311":8,"1324":2,"1350":2,"1382":1,"1389":1,"1395":7,"1396":5,"1438":1,"1496":3,"1662":1}}],["concurrency",{"0":{"1909":1},"1":{"338":1,"339":1,"340":1,"341":1,"342":1,"343":1,"344":1,"345":1,"346":1,"1869":1,"1870":1,"1871":1,"1872":1,"1873":1,"1874":1,"1875":1,"1876":1,"1877":1},"2":{"341":3},"3":{"0":10,"109":1,"136":1,"195":1,"201":1,"338":1,"339":1,"340":2,"341":5,"342":1,"343":5,"345":2,"346":1,"536":2,"538":1,"539":1,"544":1,"548":1,"792":1,"827":4,"828":2,"830":1,"831":1,"832":1,"833":1,"856":1,"881":1,"885":1,"905":1,"920":1,"921":2,"923":1,"926":1,"995":1,"996":2,"998":1,"1030":1,"1035":2,"1122":1,"1124":6,"1128":3,"1140":1,"1150":1,"1151":1,"1152":1,"1153":1,"1154":1,"1155":2,"1186":1,"1188":1,"1192":1,"1203":7,"1207":22,"1212":4,"1230":1,"1231":3,"1237":6,"1265":3,"1270":12,"1273":1,"1275":2,"1278":1,"1280":1,"1281":1,"1286":38,"1290":1,"1300":22,"1301":4,"1311":28,"1316":2,"1317":1,"1323":14,"1324":15,"1339":5,"1350":22,"1352":1,"1358":2,"1359":59,"1361":1,"1362":1,"1368":1,"1369":5,"1380":4,"1395":33,"1396":10,"1399":1,"1401":1,"1402":18,"1416":1,"1417":1,"1431":1,"1436":8,"1438":8,"1447":1,"1453":1,"1455":2,"1456":1,"1459":1,"1460":1,"1461":2,"1475":2,"1489":1,"1492":1,"1496":5,"1525":1,"1526":3,"1534":1,"1535":1,"1545":2,"1577":3,"1586":1,"1591":5,"1596":1,"1614":1,"1618":1,"1631":1,"1635":2,"1638":2,"1639":2,"1651":2,"1664":3,"1670":1,"1671":1,"1685":2,"1707":1,"1720":1,"1765":1,"1779":1,"1805":1,"1810":1,"1823":1,"1861":2,"1868":1,"1869":3,"1870":2,"1871":2,"1873":1,"1876":2,"1877":3,"1927":1,"2000":2,"2002":2,"2024":2,"2059":1,"2104":1,"2164":1,"2167":1,"2168":1,"2169":1,"2186":2,"2193":1,"2207":1,"2232":1}}],["concurrentrequests",{"3":{"1467":1}}],["concurrentmessagelimit",{"2":{"1582":1,"1707":1},"3":{"1323":5,"1577":1,"1582":1,"1591":1,"1707":1}}],["concurrentdictionary",{"2":{"996":1,"1236":1,"1909":1,"1916":1},"3":{"243":1,"996":1,"1236":1,"1237":1,"1241":1,"1247":8,"1259":7,"1270":2,"1286":11,"1300":4,"1301":5,"1309":3,"1310":3,"1316":1,"1323":5,"1324":1,"1417":3,"1496":1,"1909":1,"1916":1}}],["concurrently",{"3":{"0":1,"380":1,"832":1,"868":1,"1124":1,"1270":4,"1286":1,"1311":3,"1324":2,"1339":1,"1359":3,"1369":1,"1395":1,"1417":2,"1436":2,"1455":3,"1909":2,"1945":1}}],["concurrent",{"3":{"0":3,"58":1,"62":1,"178":1,"195":1,"201":1,"223":1,"225":1,"245":1,"286":1,"340":1,"344":1,"390":2,"436":1,"501":1,"507":2,"508":1,"513":1,"517":1,"536":2,"540":1,"541":1,"690":1,"721":1,"830":1,"854":1,"857":1,"996":1,"1125":1,"1155":1,"1237":1,"1247":4,"1258":1,"1259":6,"1270":5,"1274":1,"1286":14,"1289":1,"1298":1,"1300":17,"1301":4,"1304":1,"1307":1,"1309":3,"1310":2,"1311":3,"1323":4,"1324":14,"1326":1,"1339":2,"1350":2,"1352":4,"1358":1,"1359":23,"1369":2,"1382":1,"1395":8,"1396":14,"1399":1,"1402":6,"1415":1,"1416":1,"1417":3,"1427":1,"1436":3,"1438":2,"1443":1,"1458":1,"1467":2,"1474":1,"1492":1,"1529":1,"1534":1,"1577":2,"1630":1,"1638":2,"1639":2,"1651":1,"1708":1,"1748":1,"1749":1,"1765":2,"1774":1,"1868":1,"1877":1,"1909":1,"1934":2,"2001":1,"2164":2,"2167":1,"2169":2}}],["confuse",{"3":{"1300":1,"1301":1,"1350":1}}],["confused",{"3":{"1286":1,"1436":3,"1561":1,"1950":1}}],["confusing",{"3":{"692":1,"933":1,"1300":1,"1415":2,"1427":1,"1436":2,"1489":1,"1492":1}}],["confusion",{"3":{"370":1,"1300":1,"1311":1,"1537":1,"1576":1,"1577":1,"1640":1,"1667":1,"1673":1,"1897":1,"1899":1,"2125":1,"2128":1,"2202":1}}],["conforminggrid",{"3":{"1436":3}}],["conformingmarkup",{"3":{"1436":1}}],["conforming",{"3":{"1436":3,"1664":1}}],["conform",{"3":{"1436":1,"1639":1}}],["conformanttests",{"3":{"1198":1,"1199":4,"1207":2,"1436":19,"1496":1}}],["conformant",{"3":{"674":1,"785":1,"1436":2}}],["conformance",{"1":{"569":1,"570":1,"571":1,"572":1,"573":1,"574":1,"575":1,"576":1,"577":1,"578":1,"579":1},"2":{"572":1,"579":1},"3":{"0":2,"115":1,"121":1,"124":1,"126":1,"143":1,"217":1,"218":2,"448":1,"450":1,"459":1,"464":1,"569":1,"571":1,"572":8,"573":1,"577":1,"579":2,"617":2,"618":1,"619":2,"622":1,"749":1,"773":1,"776":1,"778":1,"842":1,"953":2,"955":1,"957":1,"1207":34,"1212":1,"1262":2,"1311":5,"1324":1,"1428":2,"1436":75,"1438":1,"1485":1,"1487":1,"1488":5,"1489":2,"1492":3,"1494":1,"1524":1,"1531":1,"1558":1,"1571":1,"1591":1,"1660":1,"1671":2,"1825":1,"1826":1,"1827":1,"2040":1,"2041":1,"2055":2,"2060":1,"2131":1,"2132":1,"2232":2}}],["confounds",{"3":{"610":1}}],["confound",{"3":{"609":1,"1436":1,"1467":1,"1474":1}}],["confers",{"3":{"1300":1,"1963":2,"1965":2}}],["confer",{"3":{"186":1,"1270":1,"1300":1,"1961":1}}],["conferencedbcontext",{"2":{"1849":1},"3":{"1849":1}}],["conference→engagement",{"3":{"1534":1,"1611":1,"1612":1}}],["conference→engagement→notification→conference",{"3":{"1526":1}}],["conference↔engagement",{"3":{"1526":1}}],["conferenceimage",{"3":{"1467":1}}],["conferenceintegrationtestbase",{"3":{"1199":32,"1207":2,"1300":1}}],["conferenceintegrationtestcollection",{"3":{"1199":5,"1207":2}}],["conferenceintegrationtestfixture",{"3":{"1199":37,"1207":2,"1436":1}}],["conferenceevent",{"3":{"1359":2}}],["conferenceentityconfigurationtests",{"3":{"1199":1,"1207":4,"1437":1,"1438":1}}],["conferenceerrorresourcestests",{"3":{"1199":1,"1207":2,"1380":2,"1437":1,"1438":1}}],["conferenceerrorresources",{"2":{"1377":1},"3":{"1199":2,"1203":1,"1207":2,"1311":1,"1377":3,"1380":7,"1496":1}}],["conferencecachetag",{"3":{"1380":1}}],["conferencecache",{"2":{"1376":1},"3":{"1359":1,"1376":1,"1379":1,"1380":3}}],["conferencecategories",{"3":{"1359":4,"1380":3,"1395":9,"1447":1,"1627":1,"1632":2,"1640":2}}],["conferencecategoriescontrollertests",{"3":{"1199":1,"1207":2}}],["conferencecategoriescontroller",{"2":{"1371":1,"1372":1},"3":{"1199":3,"1203":1,"1207":2,"1311":2,"1350":3,"1359":21,"1371":2,"1372":2,"1373":1,"1375":1,"1380":8}}],["conferencecategoryformfields",{"3":{"1395":4}}],["conferencecategoryformmodel",{"2":{"1384":1},"3":{"1199":3,"1203":1,"1207":2,"1384":2,"1395":8}}],["conferencecategory",{"3":{"1380":2,"1395":40,"1496":1}}],["conferencecategoryidentifiertype",{"3":{"1350":6,"1359":17,"1380":2,"1395":7}}],["conferencecategoryitemspanel",{"2":{"1385":1},"3":{"1199":1,"1203":1,"1207":2,"1385":2,"1395":20}}],["conferencecategoryitemeditmodel",{"2":{"1384":1},"3":{"1199":2,"1203":1,"1207":2,"1384":2,"1395":7}}],["conferencecategoryvalidationrules",{"3":{"1207":3,"1350":2,"1359":17}}],["conferencecategoryvalidationrulestests",{"3":{"1199":1,"1207":6}}],["conferencecategoryconfiguration",{"2":{"1362":1},"3":{"1199":2,"1203":1,"1207":2,"1350":4,"1362":2,"1369":3}}],["conferencecategorycreaterequestvalidatortests",{"3":{"1199":1,"1207":2,"1359":2}}],["conferencecategorycreaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1359":12}}],["conferencecategorycreaterequestmapper",{"3":{"1199":1,"1203":1,"1207":2,"1359":11}}],["conferencecategorycreaterequest",{"3":{"1199":9,"1203":1,"1207":2,"1270":1,"1359":18,"1380":2}}],["conferencecategorycreate",{"3":{"1199":1,"1203":1,"1207":2,"1395":20}}],["conferencecategorycreatemodel",{"3":{"1199":2,"1203":1,"1207":2,"1395":7}}],["conferencecategorycreatepage",{"3":{"1199":5,"1207":2}}],["conferencecategorynavigationpopulatortests",{"3":{"1199":1,"1207":2,"1359":2}}],["conferencecategorynavigationpopulator",{"3":{"1199":3,"1203":1,"1207":2,"1310":1,"1354":1,"1359":8}}],["conferencecategoryentityqueryservicetests",{"3":{"1199":1,"1207":2,"1359":2}}],["conferencecategoryentityqueryservice",{"3":{"1199":3,"1203":1,"1207":2,"1350":2,"1354":2,"1359":11}}],["conferencecategoryeditmodel",{"3":{"1199":2,"1203":1,"1207":2,"1395":5}}],["conferencecategoryupdateappliertests",{"3":{"1199":1,"1207":2,"1359":2}}],["conferencecategoryupdateapplier",{"3":{"1199":2,"1203":1,"1207":2,"1270":1,"1354":1,"1359":5}}],["conferencecategoryupdaterequestvalidatortests",{"3":{"1199":1,"1207":2,"1359":2}}],["conferencecategoryupdaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1359":6}}],["conferencecategoryupdaterequest",{"3":{"1199":9,"1203":1,"1207":2,"1359":12,"1380":1}}],["conferencecategorytitlerules",{"3":{"1199":4,"1203":1,"1207":1,"1359":9}}],["conferencecategorylist",{"2":{"1383":1},"3":{"1199":1,"1203":1,"1207":2,"1324":2,"1350":1,"1383":2,"1395":16}}],["conferencecategorylistpage",{"3":{"1199":3,"1207":2}}],["conferencecategorydetails",{"2":{"1395":1},"3":{"1395":1}}],["conferencecategorydetail",{"2":{"1384":1},"3":{"1199":1,"1203":1,"1207":2,"1384":2,"1395":22}}],["conferencecategorydetailpage",{"3":{"1199":4,"1207":2}}],["conferencecategorydtomappertests",{"3":{"1199":1,"1207":2}}],["conferencecategorydtomapper",{"3":{"1199":7,"1203":1,"1207":2,"1350":2,"1359":8}}],["conferencecategorydtotests",{"3":{"1199":1,"1207":2}}],["conferencecategorydto",{"3":{"1199":21,"1203":1,"1207":2,"1347":2,"1350":11,"1359":13,"1380":1,"1395":19}}],["conferencecategoryservice",{"2":{"1382":1},"3":{"881":1,"1199":1,"1203":1,"1207":2,"1382":3,"1395":6}}],["conferencecrudregistrationtests",{"3":{"1199":1,"1207":2,"1270":2,"1359":2,"1438":2}}],["conferencecrossservicefactory",{"3":{"1199":2,"1207":2,"1436":2}}],["conferencetestwebapplicationfactory",{"3":{"1199":2,"1207":2,"1436":2}}],["conferencetestdbcontext",{"3":{"1199":2,"1207":1}}],["conferencetrackinfo",{"2":{"1393":1},"3":{"1199":2,"1203":1,"1207":1,"1393":1,"1395":4,"1496":2}}],["conferenceaiguardrailsregistrationtests",{"2":{"1435":1,"1487":1,"1526":1},"3":{"1199":1,"1207":3,"1369":1,"1435":2,"1437":1,"1487":1,"1526":2,"1535":1}}],["conferenceroutepaths",{"2":{"1394":1,"1395":2},"3":{"1199":39,"1203":1,"1207":2,"1324":1,"1394":4,"1395":71,"1396":5,"1416":1}}],["conferencereadaudience",{"2":{"243":1,"388":1,"1307":1,"1348":1,"1373":1,"1379":1,"1391":1,"1922":1},"3":{"243":1,"388":2,"1199":6,"1203":1,"1207":2,"1307":1,"1309":2,"1348":4,"1350":7,"1373":2,"1379":1,"1380":7,"1391":1,"1395":6,"1496":2,"1922":1}}],["conferences",{"0":{"2240":1},"3":{"683":1,"1395":2,"2110":1,"2212":1,"2216":2,"2240":1,"2241":1,"2243":1,"2245":2}}],["conferenceuimodule",{"3":{"649":1,"1199":2,"1203":1,"1207":2,"1324":2,"1395":18,"1526":3}}],["conferenceuihostapplicationfactory",{"2":{"218":1},"3":{"218":2,"572":1,"575":1,"1199":7,"1207":2,"1437":2,"1487":1}}],["conferencepubliccache",{"2":{"386":1,"392":1},"3":{"386":1,"392":1,"1350":1,"1379":1,"1380":1,"1496":1}}],["conferencepermissiongrantstests",{"3":{"1199":1,"1207":2,"1437":1,"1487":1,"1496":1}}],["conferencepermissiongrants",{"2":{"1348":1,"1373":1,"1377":2},"3":{"186":1,"1116":2,"1119":1,"1199":3,"1203":1,"1207":2,"1348":5,"1350":6,"1373":1,"1377":5,"1380":5,"1415":1,"1496":2,"1526":1}}],["conferencepermissions",{"2":{"188":1,"1018":1,"1192":1,"1264":1,"1348":1,"1372":1,"1373":2,"1376":1,"1377":1,"1380":1,"1964":1},"3":{"186":1,"188":1,"1018":1,"1116":1,"1192":1,"1199":30,"1203":1,"1207":2,"1264":1,"1347":2,"1348":7,"1350":16,"1359":8,"1372":1,"1373":6,"1376":1,"1377":2,"1380":46,"1395":1,"1496":3,"1964":1}}],["conferencefeatures",{"2":{"300":1,"1018":1,"1021":1,"1099":1,"1103":1,"2136":1},"3":{"300":1,"1018":1,"1021":1,"1099":1,"1103":1,"1199":5,"1203":1,"1207":2,"1270":1,"1300":1,"1347":7,"1350":10,"1359":5,"1380":2,"1396":1,"1496":2,"1535":1,"2136":2}}],["conferencemodule",{"2":{"1314":1,"1349":1,"1370":1,"1880":1,"1884":1},"3":{"583":3,"585":1,"1199":3,"1203":1,"1207":2,"1314":4,"1323":3,"1349":2,"1350":7,"1359":1,"1369":1,"1370":1,"1377":4,"1380":11,"1436":2,"1880":1,"1884":1}}],["conferencemoduleseeder",{"2":{"1364":1,"1370":1,"1377":1},"3":{"295":1,"1199":1,"1203":1,"1207":2,"1323":2,"1364":3,"1369":4,"1370":1,"1377":3,"1380":8}}],["conferencemoduledbseedertests",{"3":{"1199":1,"1207":3,"1369":6,"1438":1}}],["conferencemoduledbseeder",{"2":{"295":1,"1364":1,"1377":1},"3":{"295":2,"1199":3,"1203":1,"1207":2,"1286":2,"1350":4,"1359":1,"1364":12,"1369":9,"1377":1,"1380":3,"1496":1}}],["conference",{"0":{"1377":1,"1378":1,"1402":1,"1604":1},"1":{"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":1,"1346":1,"1347":1,"1348":1,"1349":1,"1350":1,"1351":1,"1352":1,"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1,"1359":1,"1360":1,"1361":1,"1362":1,"1363":1,"1364":1,"1365":1,"1366":1,"1367":1,"1368":1,"1369":1,"1370":1,"1371":1,"1372":1,"1373":1,"1374":1,"1375":1,"1376":1,"1377":1,"1378":1,"1379":1,"1380":1,"1381":1,"1382":1,"1383":1,"1384":1,"1385":1,"1386":1,"1387":1,"1388":1,"1389":1,"1390":1,"1391":1,"1392":1,"1393":1,"1394":1,"1395":1,"1628":1,"1629":1,"1630":1,"1631":1,"1632":1,"1633":1,"1634":1,"1635":1,"1636":1,"1637":1,"1638":1,"1639":1,"1640":1,"1641":1,"2109":1,"2110":1,"2111":1,"2112":1,"2113":1,"2114":1,"2115":1},"2":{"47":1,"59":1,"536":1,"564":1,"1116":1,"1203":1,"1207":8,"1271":1,"1340":1,"1341":3,"1344":1,"1347":1,"1349":1,"1350":6,"1359":4,"1363":1,"1367":1,"1369":2,"1370":3,"1378":1,"1379":2,"1380":7,"1381":1,"1395":6,"1427":1,"1435":1,"1437":2,"1438":1,"1445":1,"1464":1,"1473":1,"1474":1,"1486":1,"1487":2,"1496":1,"1525":1,"1613":1,"1615":1,"1631":1,"2030":1,"2034":1,"2035":1,"2036":1,"2038":1,"2111":1},"3":{"0":11,"31":1,"47":1,"52":2,"55":1,"58":4,"59":2,"60":1,"61":1,"62":2,"63":1,"68":1,"77":2,"80":1,"85":1,"86":1,"93":4,"95":1,"97":1,"99":1,"104":1,"117":2,"128":3,"164":1,"165":1,"175":2,"181":2,"184":1,"186":6,"187":1,"195":4,"203":2,"235":2,"238":1,"241":1,"243":10,"245":2,"249":1,"250":2,"251":2,"252":2,"254":2,"257":2,"259":2,"260":6,"261":3,"291":1,"295":5,"325":6,"341":1,"344":2,"350":2,"353":3,"379":1,"387":2,"388":5,"390":6,"392":3,"393":1,"401":2,"433":1,"434":1,"435":1,"446":1,"448":6,"452":1,"453":1,"454":1,"468":3,"470":6,"472":1,"475":8,"476":4,"477":6,"478":6,"487":1,"491":2,"492":3,"493":3,"494":2,"495":2,"520":1,"536":3,"545":13,"550":3,"551":1,"552":1,"554":1,"556":4,"559":2,"564":1,"572":6,"578":1,"583":13,"585":4,"599":2,"602":1,"640":2,"643":1,"649":3,"657":10,"665":1,"674":2,"681":1,"689":1,"690":5,"691":2,"692":1,"715":1,"733":1,"743":3,"744":2,"749":2,"752":1,"757":2,"765":1,"766":1,"774":2,"780":3,"782":8,"798":5,"803":5,"805":5,"827":1,"831":2,"832":1,"837":4,"838":2,"839":1,"841":1,"868":2,"869":7,"870":2,"872":1,"875":3,"876":1,"877":2,"881":5,"891":1,"914":2,"920":1,"924":3,"926":3,"927":1,"954":7,"956":3,"958":3,"960":1,"980":2,"988":1,"991":1,"995":3,"998":2,"999":1,"1004":7,"1017":5,"1018":16,"1021":2,"1022":1,"1049":4,"1054":5,"1082":4,"1085":1,"1090":5,"1093":2,"1094":5,"1095":2,"1099":4,"1102":1,"1103":2,"1115":1,"1116":33,"1117":1,"1118":2,"1119":1,"1124":2,"1125":1,"1140":1,"1150":2,"1168":2,"1192":10,"1194":2,"1199":1121,"1201":2,"1202":15,"1203":736,"1206":16,"1207":3461,"1208":13,"1209":48,"1211":3,"1212":8,"1237":26,"1239":10,"1242":2,"1247":25,"1259":2,"1262":1,"1264":4,"1270":47,"1271":29,"1284":1,"1286":49,"1300":28,"1301":14,"1304":1,"1305":1,"1307":2,"1309":2,"1310":31,"1311":57,"1312":27,"1314":7,"1318":2,"1323":30,"1324":50,"1325":1,"1326":2,"1328":3,"1331":1,"1332":4,"1333":1,"1334":2,"1335":1,"1339":21,"1340":5,"1341":17,"1342":25,"1344":14,"1345":12,"1346":2,"1347":36,"1348":18,"1349":35,"1350":865,"1351":7,"1352":19,"1353":21,"1354":11,"1355":11,"1356":5,"1357":4,"1358":5,"1359":2879,"1360":5,"1361":3,"1362":2,"1363":5,"1364":9,"1365":2,"1366":5,"1367":6,"1368":4,"1369":234,"1370":9,"1371":14,"1372":2,"1373":11,"1374":2,"1375":4,"1376":4,"1377":16,"1378":19,"1379":9,"1380":522,"1381":8,"1382":2,"1383":2,"1384":1,"1386":1,"1389":2,"1390":4,"1391":2,"1392":1,"1393":2,"1394":2,"1395":1049,"1396":252,"1397":1,"1401":4,"1402":76,"1405":1,"1406":4,"1413":2,"1415":39,"1416":28,"1417":10,"1426":2,"1427":23,"1435":12,"1436":73,"1437":49,"1438":59,"1441":9,"1442":15,"1443":1,"1445":2,"1446":3,"1447":8,"1448":1,"1449":2,"1455":5,"1456":3,"1457":5,"1458":10,"1460":1,"1462":1,"1464":3,"1465":2,"1467":40,"1473":3,"1474":7,"1476":2,"1486":4,"1487":15,"1488":3,"1489":4,"1490":3,"1492":6,"1496":87,"1500":2,"1514":2,"1517":1,"1520":1,"1523":3,"1525":4,"1526":58,"1529":1,"1530":1,"1531":4,"1532":2,"1534":5,"1535":7,"1582":1,"1596":1,"1601":1,"1611":11,"1612":1,"1613":2,"1615":1,"1617":1,"1618":1,"1619":1,"1621":2,"1622":1,"1625":2,"1626":3,"1627":12,"1628":1,"1629":8,"1630":10,"1631":10,"1632":6,"1634":22,"1635":1,"1636":1,"1637":5,"1638":51,"1639":3,"1640":4,"1641":4,"1650":1,"1656":1,"1673":1,"1678":2,"1785":1,"1855":1,"1859":1,"1860":1,"1865":1,"1869":1,"1874":2,"1880":2,"1882":1,"1884":1,"1922":6,"1936":1,"1942":2,"1961":1,"1962":1,"1963":2,"1995":3,"2004":1,"2006":1,"2008":2,"2009":1,"2016":1,"2019":1,"2021":1,"2022":2,"2024":1,"2030":3,"2033":1,"2034":2,"2035":1,"2036":2,"2038":1,"2055":1,"2087":1,"2109":1,"2110":3,"2111":1,"2113":2,"2115":1,"2123":1,"2127":2,"2131":7,"2136":1,"2138":1,"2156":1,"2179":4,"2210":1,"2214":3,"2216":2,"2221":4,"2228":6,"2231":1,"2239":4,"2240":5,"2244":1}}],["conflate",{"3":{"1218":1,"1641":1,"1896":1}}],["conflated",{"3":{"486":1,"1013":1}}],["conflates",{"3":{"108":1,"176":1,"829":1,"1229":1,"1311":1,"1804":1,"1999":1}}],["conflating",{"3":{"40":1,"1380":1,"1396":1,"1436":1,"1815":1,"1817":1,"1860":1,"2014":1,"2068":1,"2130":1}}],["conflictkindinflight",{"3":{"1311":1}}],["conflictkindbodymismatch",{"3":{"1311":1}}],["conflictkindtag",{"3":{"1311":1}}],["conflicts",{"3":{"38":1,"1286":4,"1639":1,"2069":1}}],["conflicting",{"3":{"0":1,"340":1,"633":1,"888":1,"1279":1,"1286":6,"1311":1,"1611":1,"1651":1,"1684":1,"1876":1,"2097":1}}],["conflict",{"0":{"1872":1},"1":{"2061":1,"2062":1,"2063":1,"2064":1,"2065":1,"2066":1,"2067":1,"2068":1,"2069":1},"2":{"1806":1,"1873":1},"3":{"0":2,"108":1,"109":4,"157":1,"339":1,"342":3,"343":3,"344":1,"348":1,"350":1,"539":2,"996":1,"998":1,"1019":1,"1147":1,"1219":1,"1222":1,"1229":4,"1237":2,"1286":11,"1289":2,"1300":4,"1311":19,"1312":1,"1323":1,"1347":1,"1350":3,"1358":1,"1359":12,"1368":1,"1380":1,"1395":3,"1396":11,"1399":1,"1402":3,"1406":1,"1415":3,"1427":1,"1436":6,"1438":2,"1443":1,"1455":1,"1467":1,"1487":1,"1488":1,"1567":1,"1632":1,"1639":1,"1640":2,"1641":2,"1651":2,"1662":1,"1664":1,"1747":1,"1774":1,"1784":1,"1799":1,"1805":2,"1806":2,"1873":4,"1874":2,"1876":3,"1877":2,"1909":1,"1926":2,"1934":1,"1954":1,"2006":1,"2060":1,"2061":3,"2062":1,"2156":1,"2163":1,"2210":1}}],["confining",{"3":{"1369":1}}],["confines",{"3":{"1431":1}}],["confine",{"3":{"1311":1}}],["confined",{"3":{"0":1,"493":1,"495":1,"557":1,"799":1,"979":1,"980":1,"1126":1,"1324":1,"1339":1,"1369":1,"1396":1,"1417":1,"1436":4,"1724":1,"2232":1}}],["confident",{"3":{"1496":2,"1794":1}}],["confidential",{"3":{"360":1,"1323":1,"2142":1}}],["confidentiality",{"3":{"358":1,"359":1,"360":2,"363":1,"1286":1,"1903":1,"2141":1,"2142":2,"2146":1}}],["confidence",{"3":{"593":1,"2029":1,"2043":1,"2050":1}}],["confirmnewpasswordfield",{"3":{"1436":1}}],["confirmdeleteasync",{"3":{"1433":1,"1436":1,"1488":1}}],["confirmpasswordfield",{"3":{"1436":1}}],["confirmpassword",{"3":{"1324":6}}],["confirmtext",{"3":{"1324":2}}],["confirmtwofactorenrollmenthandlerbase",{"2":{"1300":1,"1322":1,"2198":1},"3":{"1199":2,"1203":1,"1207":2,"1300":4,"1322":3,"1323":5,"1496":1,"2198":1}}],["confirmsonceandreportssuccess",{"3":{"1436":1}}],["confirms",{"3":{"790":1,"1300":1,"1324":3,"1359":4,"1369":1,"1395":4,"1396":2,"1401":1,"1402":2,"1415":1,"1417":1,"1436":6,"1455":1,"1457":1,"1496":2,"1499":1,"1765":1}}],["confirming",{"3":{"676":1,"1259":1,"1295":1,"1300":1,"1323":1,"1324":2,"1380":1,"1396":1,"1415":2}}],["confirmableauthenticationservice",{"3":{"1199":2,"1207":1}}],["confirmableauthuser",{"3":{"1199":3,"1207":1}}],["confirmableuser",{"3":{"1199":4,"1207":1}}],["confirmable",{"3":{"907":1,"1092":1,"1553":1,"2179":1}}],["confirmasync",{"3":{"649":1,"1324":5,"1415":1}}],["confirmationalert",{"3":{"1436":2}}],["confirmations",{"3":{"1436":1,"1742":1}}],["confirmationservice",{"2":{"1324":1},"3":{"1324":3}}],["confirmationstate",{"3":{"1199":2,"1203":1,"1207":1,"1324":4,"1496":1}}],["confirmationthrottled",{"3":{"1300":1}}],["confirmationurl",{"3":{"1300":1,"1761":1}}],["confirmation",{"0":{"1295":1,"1408":1},"1":{"2194":1,"2195":1,"2196":1,"2197":1,"2198":1,"2199":1,"2200":1,"2201":1,"2202":1,"2203":1},"2":{"1761":1},"3":{"0":5,"173":1,"175":2,"176":1,"177":1,"230":1,"348":1,"350":2,"535":2,"618":1,"650":2,"672":1,"733":1,"735":1,"907":1,"1057":1,"1058":2,"1059":6,"1060":1,"1062":1,"1063":1,"1092":1,"1192":1,"1212":1,"1287":2,"1289":1,"1295":2,"1300":26,"1301":5,"1322":1,"1323":7,"1324":25,"1350":3,"1395":10,"1396":3,"1402":2,"1403":1,"1405":1,"1414":1,"1415":24,"1417":1,"1422":1,"1427":7,"1433":1,"1436":12,"1438":1,"1467":1,"1487":1,"1496":7,"1553":1,"1570":1,"1577":1,"1627":3,"1633":1,"1641":1,"1643":1,"1653":1,"1669":1,"1749":1,"1755":1,"1761":3,"1763":1,"1764":1,"1765":1,"1767":1,"1769":1,"1771":1,"1776":2,"1779":1,"1835":1,"1964":1,"1977":1,"2001":1,"2012":1,"2161":3,"2167":1,"2169":1,"2193":1,"2194":3,"2198":2,"2202":3,"2203":1,"2208":2,"2211":1}}],["confirmemailasync",{"3":{"1324":3,"1415":1,"1436":1}}],["confirmemailhandlertests",{"3":{"1199":1,"1207":2,"1300":1,"1415":2}}],["confirmemailhandler",{"2":{"1408":1},"3":{"1199":2,"1203":1,"1207":2,"1300":1,"1323":1,"1324":1,"1408":2,"1415":5,"1496":1}}],["confirmemailhandlerbase",{"2":{"2198":1},"3":{"1199":3,"1203":1,"1207":2,"1300":6,"1322":4,"1323":5,"1415":3,"1496":1,"2198":1}}],["confirmemailpagetests",{"3":{"1199":1,"1207":2,"1436":1}}],["confirmemailpagetestsbase",{"2":{"577":1,"960":1,"1432":1},"3":{"0":1,"577":2,"960":1,"1199":3,"1207":2,"1324":2,"1432":3,"1436":2}}],["confirmemailcommand",{"2":{"1408":1},"3":{"1199":5,"1203":1,"1207":2,"1300":2,"1324":1,"1408":2,"1415":8,"1436":1}}],["confirmemailrequestvalidator",{"2":{"1295":1},"3":{"1199":1,"1203":1,"1207":1,"1295":1,"1300":3}}],["confirmemailrequest",{"3":{"1199":10,"1203":1,"1207":1,"1295":1,"1300":2,"1323":1,"1324":1,"1415":3}}],["confirmemail",{"2":{"1062":1},"3":{"577":1,"1059":1,"1062":2,"1199":2,"1203":3,"1207":9,"1295":1,"1300":3,"1323":2,"1324":12,"1408":2,"1414":1,"1415":8,"1436":2,"1496":2,"1526":2,"1591":1,"1627":1,"1761":1}}],["confirmemailtests",{"3":{"577":2,"578":2,"1199":1,"1207":2,"1436":1}}],["confirmedtoolspropertykey",{"3":{"1427":1}}],["confirmed",{"2":{"1422":1},"3":{"193":1,"254":1,"350":2,"353":1,"490":1,"799":1,"905":1,"909":1,"947":1,"1012":1,"1059":1,"1091":1,"1196":1,"1295":1,"1300":6,"1322":1,"1323":7,"1324":9,"1395":2,"1396":4,"1405":1,"1406":1,"1408":1,"1415":10,"1422":2,"1427":6,"1436":2,"1477":1,"1496":17,"1526":1,"1561":1,"1630":2,"1631":1,"1633":1,"1667":1,"1675":1,"1755":1,"1764":1,"1772":1,"1777":1,"1846":1,"1973":1,"1977":1,"2169":1,"2198":2}}],["confirm",{"3":{"0":1,"140":1,"175":1,"570":1,"577":1,"794":1,"907":1,"1059":1,"1062":1,"1116":1,"1235":1,"1286":1,"1300":2,"1311":1,"1322":1,"1323":7,"1324":14,"1350":3,"1357":1,"1359":3,"1372":1,"1380":5,"1383":1,"1395":15,"1396":4,"1402":1,"1414":2,"1415":8,"1417":2,"1432":2,"1436":10,"1467":1,"1474":1,"1478":2,"1488":1,"1489":1,"1526":2,"1532":1,"1586":1,"1591":2,"1596":1,"1600":1,"1607":1,"1608":1,"1627":1,"1631":1,"1653":1,"1657":1,"1697":1,"1698":1,"1710":1,"1741":1,"1742":2,"1755":1,"1761":1,"1768":1,"1770":1,"1776":2,"1995":1,"2169":1,"2196":1}}],["configfilter",{"3":{"1496":1}}],["configkey",{"3":{"1339":4,"1415":3}}],["configs",{"3":{"1192":1,"1361":1,"1362":1,"1496":1,"1500":1,"1853":1}}],["config",{"0":{"1361":1,"1362":1},"1":{"297":1,"298":1,"299":1,"300":1,"301":1,"302":1,"303":1,"304":1,"305":1,"306":1},"2":{"369":1,"370":1,"372":1,"527":1,"632":1,"764":1,"869":1,"938":1,"1649":1},"3":{"0":14,"34":1,"60":1,"93":1,"95":1,"117":1,"164":1,"189":1,"225":1,"297":1,"300":2,"348":1,"350":1,"369":1,"370":5,"372":2,"418":2,"419":1,"420":1,"422":1,"428":1,"429":1,"484":1,"490":1,"491":1,"492":1,"493":1,"527":1,"556":1,"557":1,"619":1,"632":1,"633":5,"635":1,"674":1,"677":1,"749":1,"764":1,"766":1,"826":1,"827":1,"829":2,"840":2,"869":2,"914":1,"938":1,"1012":1,"1034":1,"1037":1,"1059":3,"1075":1,"1184":1,"1192":5,"1212":7,"1241":2,"1247":5,"1270":2,"1300":12,"1307":1,"1309":1,"1311":7,"1314":1,"1315":1,"1320":2,"1323":2,"1324":17,"1336":2,"1338":2,"1339":15,"1349":1,"1350":1,"1362":2,"1369":1,"1380":2,"1415":1,"1416":8,"1427":1,"1429":1,"1431":1,"1436":11,"1437":1,"1438":8,"1441":1,"1447":3,"1451":1,"1455":3,"1467":1,"1487":2,"1488":2,"1496":5,"1500":1,"1510":1,"1521":1,"1526":5,"1534":1,"1548":2,"1553":1,"1554":3,"1563":1,"1569":1,"1572":1,"1576":1,"1577":4,"1583":1,"1590":1,"1591":4,"1611":1,"1614":1,"1619":1,"1649":1,"1653":2,"1661":1,"1665":1,"1666":1,"1790":1,"1821":1,"1842":1,"1849":1,"1850":1,"1853":1,"1854":1,"1857":1,"1882":2,"1893":1,"1894":1,"1896":1,"1898":1,"1899":2,"1904":2,"1958":1,"1965":1,"1975":1,"1976":2,"1979":1,"1989":2,"2001":1,"2002":1,"2008":1,"2011":2,"2023":1,"2094":1,"2098":1,"2136":1,"2138":1,"2139":2,"2142":1,"2220":1,"2232":2}}],["configuring",{"3":{"402":1,"1286":1,"1300":1,"1301":1,"1311":1,"1320":1,"1324":1,"1339":1,"1467":1}}],["configurability",{"3":{"776":1}}],["configurablewarmuptask",{"3":{"1199":2,"1207":1}}],["configurable",{"0":{"1708":1},"3":{"0":4,"157":1,"189":1,"279":1,"312":1,"318":1,"381":1,"425":1,"461":1,"834":1,"883":1,"905":1,"1286":3,"1293":1,"1297":1,"1300":4,"1301":1,"1311":3,"1323":1,"1324":1,"1339":4,"1350":2,"1359":1,"1424":1,"1427":1,"1436":1,"1496":1,"1577":1,"1669":1,"1670":1,"1671":1,"1708":1,"1752":1,"1767":2,"1945":1,"1950":1,"1994":1,"2198":1}}],["configurator",{"3":{"572":1,"1259":1,"1311":1,"1323":2,"1339":5}}],["configurationchanges",{"3":{"1416":3}}],["configurationkey",{"3":{"1323":2}}],["configurationassemblies",{"3":{"1286":2}}],["configurationbuilder",{"3":{"1050":1,"1286":2,"1436":1}}],["configurationoauthuisettings",{"2":{"350":1},"3":{"350":1,"1199":2,"1203":1,"1207":2,"1311":1,"1324":8,"1416":2,"1496":1}}],["configurationmanager",{"2":{"237":1,"1335":1},"3":{"237":1,"665":1,"1335":1,"1339":2,"1429":1,"1436":1,"1438":1,"1442":1,"1443":1,"1611":1,"1613":1}}],["configurationsource",{"3":{"1286":3}}],["configurations",{"0":{"1363":1},"3":{"10":1,"97":1,"135":1,"633":1,"657":1,"889":1,"891":1,"1034":1,"1192":1,"1202":1,"1203":1,"1213":1,"1234":1,"1237":5,"1247":1,"1271":2,"1273":1,"1279":1,"1281":1,"1282":2,"1286":34,"1308":1,"1310":1,"1316":1,"1319":1,"1323":2,"1350":1,"1360":1,"1363":2,"1369":8,"1396":6,"1401":1,"1409":1,"1415":1,"1436":4,"1438":2,"1467":1,"1496":1,"1641":1,"1650":1,"1651":2,"1652":1,"1722":1,"1727":2,"1728":1,"1774":1,"1849":1,"1932":1,"2053":1}}],["configuration",{"0":{"1282":1,"1320":1,"1335":2,"1419":1,"1850":1,"1856":1,"2012":1},"1":{"671":1,"672":1,"673":1,"674":1,"675":1,"676":1,"677":1,"678":1,"835":1,"836":1,"837":1,"838":1,"839":1,"840":1,"841":1,"842":1,"1313":1,"1314":1,"1315":1,"1316":1,"1317":1,"1318":1,"1319":1,"1320":1,"1321":1,"1322":1,"1323":1},"2":{"261":1,"440":1,"696":1,"705":1,"713":1,"776":1,"832":1,"914":1,"979":1,"1061":1,"1311":1,"1324":1,"1335":1,"1339":1,"1417":1,"1773":1,"1839":1,"1936":1,"2094":1,"2127":1,"2151":1},"3":{"0":19,"25":1,"30":1,"31":7,"32":1,"46":1,"47":2,"57":3,"59":3,"60":1,"63":1,"72":1,"73":1,"95":4,"96":1,"98":1,"140":1,"150":1,"164":2,"166":6,"167":3,"170":1,"182":1,"186":1,"198":1,"200":2,"213":1,"214":2,"216":1,"219":1,"225":2,"228":1,"231":1,"235":1,"243":3,"261":1,"280":1,"290":1,"295":1,"300":1,"301":1,"302":1,"318":1,"330":2,"358":1,"359":3,"361":2,"365":2,"373":1,"384":1,"395":2,"401":1,"407":1,"409":1,"413":1,"415":1,"421":1,"422":1,"425":1,"432":1,"434":2,"440":1,"451":1,"470":1,"501":1,"529":1,"530":1,"536":2,"539":1,"556":1,"583":5,"584":1,"585":3,"586":1,"597":1,"598":1,"599":7,"601":3,"603":1,"633":1,"640":1,"641":1,"642":1,"644":1,"650":1,"651":1,"657":2,"658":1,"659":1,"663":1,"665":3,"666":1,"667":1,"671":1,"673":2,"674":6,"676":3,"678":1,"690":1,"691":1,"696":1,"698":3,"700":1,"705":1,"707":3,"708":2,"713":1,"715":1,"733":1,"750":1,"765":1,"767":1,"770":1,"774":2,"776":2,"778":1,"790":1,"791":1,"792":2,"795":2,"810":1,"819":1,"823":1,"827":9,"829":5,"831":1,"832":7,"833":2,"835":1,"836":2,"838":9,"839":4,"840":4,"842":3,"846":1,"854":1,"863":1,"876":1,"883":1,"889":3,"890":4,"891":3,"905":1,"914":2,"916":2,"929":2,"931":2,"933":1,"934":1,"945":1,"947":1,"948":1,"954":1,"973":1,"976":1,"979":5,"998":1,"999":1,"1004":1,"1018":4,"1033":2,"1034":4,"1040":1,"1043":1,"1059":1,"1060":1,"1061":2,"1068":1,"1075":1,"1078":1,"1083":2,"1084":1,"1085":1,"1089":1,"1091":7,"1092":2,"1094":3,"1108":1,"1109":1,"1124":2,"1126":1,"1127":1,"1149":1,"1175":4,"1178":1,"1184":1,"1192":1,"1202":1,"1203":19,"1207":129,"1208":2,"1212":11,"1232":3,"1237":12,"1243":1,"1247":5,"1257":1,"1259":13,"1264":1,"1270":8,"1271":4,"1272":2,"1273":3,"1276":2,"1279":6,"1280":1,"1281":3,"1282":5,"1283":1,"1284":1,"1286":214,"1288":2,"1294":1,"1297":1,"1300":28,"1301":15,"1302":1,"1304":2,"1307":2,"1309":21,"1310":2,"1311":47,"1312":1,"1313":2,"1314":1,"1315":4,"1316":1,"1317":3,"1320":7,"1321":2,"1323":81,"1324":84,"1325":1,"1326":2,"1331":1,"1333":2,"1334":1,"1335":4,"1337":1,"1338":6,"1339":123,"1344":1,"1350":16,"1359":23,"1361":2,"1362":2,"1363":2,"1366":2,"1369":33,"1379":2,"1380":4,"1395":3,"1396":54,"1402":4,"1403":1,"1404":1,"1406":2,"1407":1,"1412":1,"1415":30,"1416":16,"1417":18,"1419":1,"1420":3,"1421":1,"1423":1,"1424":2,"1427":10,"1429":3,"1431":1,"1433":1,"1436":41,"1437":3,"1438":17,"1441":2,"1442":9,"1443":2,"1444":1,"1446":1,"1447":5,"1450":1,"1455":1,"1457":1,"1467":15,"1468":1,"1469":1,"1470":1,"1472":1,"1477":1,"1481":1,"1484":1,"1487":3,"1488":4,"1489":2,"1492":1,"1496":5,"1509":1,"1526":5,"1547":1,"1554":2,"1577":7,"1626":1,"1629":1,"1631":3,"1639":3,"1641":1,"1651":1,"1652":2,"1656":2,"1660":1,"1661":1,"1663":1,"1664":3,"1667":3,"1668":1,"1670":3,"1671":1,"1675":1,"1720":1,"1721":1,"1724":3,"1727":4,"1728":1,"1734":1,"1764":1,"1770":2,"1773":1,"1783":1,"1788":1,"1790":1,"1825":1,"1830":1,"1839":1,"1847":1,"1849":5,"1850":1,"1851":1,"1854":2,"1856":3,"1857":2,"1858":1,"1860":1,"1862":2,"1872":1,"1881":1,"1882":3,"1902":1,"1903":1,"1916":1,"1922":1,"1935":1,"1936":3,"1941":1,"1944":1,"1952":2,"1970":1,"1997":1,"2000":3,"2001":1,"2008":1,"2010":1,"2012":1,"2013":1,"2014":2,"2020":1,"2024":4,"2026":1,"2027":2,"2028":2,"2048":1,"2053":1,"2064":2,"2094":1,"2126":2,"2127":1,"2136":1,"2142":2,"2143":1,"2145":1,"2149":2,"2150":1,"2151":2,"2152":1,"2160":1,"2166":1,"2167":2,"2172":1,"2173":1,"2174":1,"2180":1,"2196":1,"2199":1,"2203":1,"2230":2,"2232":4}}],["configuretestservices",{"3":{"1436":1}}],["configuretestenvironment",{"2":{"1429":1},"3":{"1429":1,"1436":2,"1488":1,"1490":1}}],["configuretestfeatureflags",{"2":{"1429":1,"1671":1},"3":{"1429":1,"1436":2,"1488":1,"1671":1}}],["configurerouteasync",{"3":{"1339":2}}],["configurerefreshsessions",{"2":{"1283":1},"3":{"1283":1,"1286":3}}],["configurereceiveendpoint",{"3":{"640":1,"1436":1}}],["configureclusterasync",{"3":{"1339":2}}],["configureconnection",{"3":{"1324":3}}],["configureconsumers",{"3":{"1259":1}}],["configureconcurrencytokens",{"2":{"341":1,"343":1,"1872":1,"2164":1},"3":{"341":1,"343":1,"1286":1,"1526":1,"1577":1,"1872":1,"2164":1}}],["configureconventions",{"2":{"0":1,"798":1,"806":1,"889":1,"1034":1,"1050":1,"1052":1,"1083":1,"1086":1,"1212":1},"3":{"0":2,"798":1,"806":1,"889":1,"1034":1,"1050":1,"1052":1,"1083":1,"1086":1,"1212":1,"1281":2,"1286":11,"1810":1}}],["configurekestrel",{"3":{"1339":3}}],["configureprimaryhttpmessagehandler",{"3":{"1339":2}}],["configurepermissiongrants",{"2":{"1286":1},"3":{"1286":4}}],["configurefallback",{"3":{"1300":3}}],["configurewarnings",{"3":{"1286":2}}],["configureinprocesstokenvalidation",{"2":{"1429":1,"1488":1},"3":{"1429":1,"1436":3,"1438":1,"1488":1}}],["configureinternalcommands",{"2":{"1042":1,"1045":1},"3":{"1042":1,"1045":1,"1286":1}}],["configureinbox",{"3":{"200":1,"1259":1,"1286":1}}],["configureoptions",{"3":{"1300":1}}],["configureopentelemetry",{"2":{"401":1,"1089":1,"1329":1,"1677":1,"2160":1},"3":{"397":1,"401":1,"1089":1,"1259":1,"1311":1,"1323":1,"1329":1,"1333":1,"1339":6,"1438":1,"1443":2,"1467":1,"1577":1,"1677":3,"2156":2,"2160":1}}],["configureoutbox",{"2":{"931":1,"934":1},"3":{"931":1,"934":1,"1286":1}}],["configureessentials",{"3":{"1416":2}}],["configureemulatorhost",{"3":{"640":1,"1323":3}}],["configureendpoints",{"2":{"642":1},"3":{"640":2,"642":1,"1323":2}}],["configureendpointswithhealthprobe",{"2":{"98":1,"1379":1,"1413":1,"1441":1,"1670":1},"3":{"0":1,"95":1,"96":1,"98":2,"1331":1,"1339":4,"1379":1,"1396":1,"1413":1,"1441":1,"1442":1,"1670":1}}],["configureendpointdefaults",{"2":{"95":1,"98":1},"3":{"95":1,"98":2,"1339":1}}],["configureapplicationpartmanager",{"3":{"1311":1}}],["configureappconfiguration",{"2":{"914":1},"3":{"914":1,"1436":1,"1490":1}}],["configureall",{"3":{"1311":5,"1324":2}}],["configureaudittrail",{"2":{"720":1},"3":{"715":2,"720":1,"1286":4}}],["configureaspnetcoremetrics",{"2":{"397":1,"409":1},"3":{"397":1,"409":1,"1443":1}}],["configureawait",{"1":{"479":1,"480":1,"481":1,"482":1,"483":1,"484":1,"485":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"495":1},"2":{"481":1,"482":1,"483":1,"484":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"495":1},"3":{"0":3,"479":1,"481":3,"482":2,"483":1,"484":2,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":2,"493":2,"494":2,"495":1,"1212":1,"1229":1,"1247":1,"1259":1,"1286":1,"1300":1,"1309":1,"1310":1,"1312":1,"1323":4,"1324":2,"1339":1,"1359":6,"1369":1,"1380":3,"1402":1,"1415":1,"1417":1,"1436":4,"2232":2}}],["configuremetrics",{"2":{"395":1,"407":1,"409":1},"3":{"395":1,"407":1,"409":1,"1333":1,"1339":3,"2156":1}}],["configurehttp2withhealthprobe",{"3":{"93":1,"94":1}}],["configurehttpclientdefaults",{"2":{"67":1,"100":1,"883":1,"1329":1,"1707":1},"3":{"67":1,"100":1,"883":1,"1312":2,"1329":1,"1339":7,"1441":1,"1443":1,"1707":1}}],["configuresharedenvironment",{"3":{"1436":1}}],["configuresensitivedatalogging",{"3":{"1286":4}}],["configureservices",{"2":{"1488":1},"3":{"572":1,"1436":5,"1488":1,"1490":1}}],["configurescheduler",{"2":{"710":1,"1036":1},"3":{"707":1,"710":1,"1036":1,"1286":1,"1323":1}}],["configures",{"3":{"70":1,"93":1,"164":1,"170":8,"186":1,"214":2,"318":1,"324":1,"341":1,"352":1,"446":1,"448":1,"454":1,"640":2,"674":1,"766":1,"819":1,"832":1,"918":1,"1034":1,"1036":1,"1273":1,"1286":16,"1300":7,"1301":3,"1309":3,"1311":6,"1323":6,"1324":6,"1339":2,"1369":1,"1396":2,"1415":1,"1417":2,"1427":1,"1436":6,"1443":2,"1490":1,"1526":1,"1577":1,"1582":1,"1670":1,"1671":1,"1708":1,"1721":1,"1851":1,"1995":1,"2006":1,"2014":1,"2030":1,"2055":1,"2122":1,"2150":1}}],["configure",{"2":{"1824":1},"3":{"0":2,"164":1,"170":1,"214":2,"215":1,"318":1,"401":1,"459":1,"464":2,"640":1,"674":1,"698":1,"733":3,"747":1,"749":4,"751":3,"914":1,"916":1,"997":1,"1188":1,"1279":1,"1286":29,"1300":7,"1301":2,"1309":2,"1311":15,"1316":1,"1323":4,"1324":5,"1333":1,"1339":16,"1362":2,"1369":12,"1395":1,"1396":15,"1415":1,"1416":1,"1417":1,"1424":1,"1429":2,"1436":9,"1438":1,"1443":2,"1488":2,"1490":1,"1496":1,"1685":1,"1724":1,"1824":1,"1825":1,"1845":1,"1856":1,"1978":1,"1995":1,"2011":1,"2113":1,"2149":2,"2151":1,"2152":1}}],["configuredasyncdisposable",{"3":{"1436":1}}],["configuredatagridlistpagehost",{"2":{"954":1,"959":1,"960":1,"1432":1},"3":{"0":1,"954":1,"959":1,"960":1,"1432":1,"1436":3,"1488":1}}],["configuredpatternoptions",{"2":{"1427":1},"3":{"1427":2}}],["configuredprovider",{"2":{"1094":1},"3":{"1094":1,"1427":1}}],["configuredvalue",{"3":{"1415":1}}],["configuredengines",{"3":{"1286":3}}],["configured",{"0":{"2006":1},"3":{"0":10,"31":2,"73":1,"95":1,"100":1,"101":1,"135":1,"139":1,"157":1,"158":1,"159":1,"170":3,"175":2,"177":1,"178":1,"179":2,"186":1,"200":1,"214":1,"225":2,"235":1,"241":1,"243":2,"245":1,"249":1,"261":1,"265":1,"279":1,"280":1,"283":1,"285":1,"300":1,"303":1,"320":3,"330":1,"337":1,"343":2,"346":1,"349":1,"350":2,"351":1,"355":1,"381":1,"398":1,"406":1,"413":1,"448":1,"464":1,"499":1,"501":3,"524":1,"530":1,"536":2,"538":1,"575":1,"601":1,"640":1,"641":1,"665":2,"668":1,"674":1,"675":1,"676":3,"690":1,"698":1,"740":1,"741":2,"742":1,"776":1,"789":2,"790":5,"792":2,"794":1,"818":1,"819":1,"821":1,"827":6,"829":1,"838":1,"841":1,"849":1,"869":1,"906":1,"915":1,"930":1,"931":1,"996":1,"1017":1,"1018":2,"1034":1,"1042":1,"1059":1,"1083":3,"1089":2,"1091":4,"1092":2,"1108":2,"1116":2,"1126":1,"1150":1,"1184":1,"1188":1,"1212":1,"1237":2,"1247":1,"1251":1,"1258":1,"1259":7,"1269":1,"1270":4,"1271":2,"1276":1,"1279":2,"1280":1,"1281":2,"1283":1,"1285":1,"1286":56,"1288":3,"1293":1,"1294":1,"1297":1,"1300":34,"1301":13,"1302":2,"1307":1,"1309":10,"1310":1,"1311":34,"1313":1,"1320":4,"1323":19,"1324":26,"1333":1,"1336":1,"1337":4,"1339":21,"1350":4,"1357":1,"1358":1,"1359":11,"1365":1,"1366":1,"1368":1,"1369":7,"1379":2,"1380":2,"1395":4,"1396":36,"1400":1,"1402":2,"1406":1,"1413":1,"1415":9,"1417":7,"1418":1,"1424":1,"1425":2,"1427":10,"1433":1,"1436":15,"1438":8,"1441":1,"1445":1,"1447":1,"1455":2,"1456":1,"1458":1,"1459":1,"1465":1,"1467":7,"1488":4,"1489":1,"1492":1,"1526":1,"1531":1,"1548":1,"1563":1,"1577":3,"1591":1,"1631":3,"1632":1,"1635":1,"1637":1,"1639":1,"1641":3,"1653":1,"1663":1,"1664":3,"1667":1,"1670":2,"1672":1,"1675":3,"1684":1,"1700":1,"1708":1,"1719":1,"1721":1,"1723":1,"1727":1,"1728":1,"1765":1,"1770":1,"1801":1,"1829":1,"1842":1,"1847":1,"1853":1,"1872":1,"1896":1,"1921":1,"1923":1,"1928":1,"1933":1,"1944":2,"1974":2,"1975":1,"1976":1,"1979":1,"1982":1,"1984":1,"1994":1,"1996":3,"2001":1,"2012":2,"2024":1,"2027":2,"2028":1,"2030":1,"2032":1,"2055":1,"2058":1,"2064":2,"2129":1,"2132":1,"2138":1,"2164":1,"2166":4,"2167":1,"2168":1,"2172":1,"2174":1,"2199":1,"2232":1}}],["configurebrokertransport",{"2":{"150":1,"638":1,"819":1,"822":1,"1075":1,"1707":1},"3":{"0":2,"150":1,"638":1,"640":2,"819":1,"822":1,"1075":1,"1323":6,"1707":2}}],["conspicuously",{"3":{"1396":1}}],["conspicuous",{"3":{"1286":1}}],["consolidating",{"3":{"1415":1,"2072":1}}],["consolidation",{"3":{"1286":1,"1311":1,"1395":1,"1496":4,"1600":1}}],["consolidated",{"3":{"1259":1,"1271":1,"1311":1,"1486":1,"1487":1,"1496":3,"1526":1}}],["consolidates",{"3":{"782":1,"1311":1,"1350":2,"1676":1}}],["consolidate",{"3":{"435":1,"782":1}}],["consoles",{"3":{"1481":1}}],["console",{"2":{"1550":1},"3":{"214":1,"401":1,"481":1,"776":1,"914":2,"1041":1,"1044":1,"1323":2,"1324":2,"1333":1,"1339":2,"1395":7,"1446":1,"1447":1,"1451":1,"1467":1,"1475":1,"1480":4,"1550":1,"2005":2,"2013":1,"2147":1,"2150":1}}],["consciously",{"3":{"1436":2,"1521":1}}],["conscious",{"3":{"87":1,"1252":1,"1300":1,"1526":1,"1566":1,"1571":1,"1709":1}}],["conserving",{"3":{"1417":2}}],["conserve",{"3":{"1417":1}}],["conservative",{"3":{"529":1,"1237":1,"1286":3,"1301":2,"1350":1,"1417":3,"1455":1,"1916":1}}],["consented",{"3":{"1396":1}}],["consent",{"3":{"1396":2,"1417":1,"1567":1,"1574":1,"1575":3,"1577":2,"1581":1,"1582":2,"1584":1,"1591":1,"1597":1}}],["consensus",{"3":{"0":1,"996":1,"1270":1,"1909":1}}],["consequently",{"3":{"1301":1,"1369":1,"1396":1}}],["consequentialpropertykey",{"3":{"1427":1}}],["consequentially",{"3":{"1229":1}}],["consequential",{"2":{"1422":1},"3":{"1012":1,"1091":1,"1092":1,"1095":2,"1396":1,"1422":2,"1427":6,"1496":1,"1526":1,"1553":1,"1577":1}}],["consequences",{"0":{"435":1,"441":1,"1094":1,"1179":1,"1188":1},"3":{"0":2,"101":1,"123":1,"178":1,"436":1,"444":1,"527":1,"555":1,"629":1,"891":1,"1034":1,"1089":2,"1259":1,"1286":2,"1309":1,"1323":2,"1350":1,"1359":3,"1372":1,"1380":1,"1395":1,"1415":2,"1417":1,"1436":1,"1482":1,"1486":1,"1571":1,"1673":1,"1810":1,"1918":1,"1948":1,"1949":1,"2127":1}}],["consequence",{"3":{"0":3,"23":1,"24":1,"27":1,"96":1,"122":1,"200":1,"201":1,"353":1,"359":1,"390":1,"415":1,"543":3,"549":1,"611":2,"643":1,"659":1,"706":1,"790":1,"793":1,"829":2,"875":1,"914":1,"931":1,"941":1,"988":1,"1050":1,"1059":1,"1082":1,"1229":1,"1231":1,"1247":1,"1252":1,"1259":3,"1270":2,"1271":1,"1286":6,"1297":1,"1300":8,"1301":1,"1309":2,"1311":6,"1323":5,"1324":4,"1339":4,"1350":4,"1355":1,"1358":1,"1359":8,"1369":3,"1380":3,"1395":6,"1396":12,"1415":7,"1416":1,"1417":1,"1436":9,"1438":1,"1459":1,"1467":3,"1472":1,"1475":2,"1651":1,"1686":2,"1849":1,"1861":1,"1886":1,"1910":1,"1947":1,"1982":1,"2035":1,"2042":1,"2158":1,"2163":1,"2164":1,"2165":1,"2166":1}}],["consecutivefailures",{"2":{"1338":1},"3":{"827":1,"1338":1,"1339":1,"1395":1}}],["consecutive",{"3":{"281":1,"459":1,"609":1,"799":1,"803":1,"1003":1,"1006":1,"1007":1,"1048":1,"1054":2,"1242":1,"1247":1,"1285":1,"1286":1,"1292":1,"1307":1,"1324":1,"1390":2,"1395":5,"1415":1,"1438":1,"1456":1,"1474":1,"1492":1,"1641":1,"1716":1,"2000":1}}],["consultant",{"3":{"1395":1}}],["consulted",{"3":{"443":1,"1058":1,"1116":1,"1184":1,"1247":1,"1285":1,"1286":2,"1300":2,"1311":2,"1323":1,"1324":2,"1331":1,"1339":2,"1359":3,"1391":1,"1395":2,"1415":1,"1427":1,"1432":1,"1994":1,"2199":1}}],["consults",{"3":{"249":1,"295":1,"365":1,"384":1,"420":1,"819":1,"827":1,"885":1,"1117":1,"1189":1,"1237":1,"1242":1,"1247":1,"1286":3,"1307":1,"1311":4,"1323":1,"1324":2,"1359":2,"1406":1,"1436":1,"1670":1,"1843":1,"1945":1,"1976":1,"2142":1}}],["consult",{"3":{"0":1,"245":1,"318":1,"819":1,"846":1,"1025":1,"1212":1,"1236":1,"1286":2,"1301":1,"1309":1,"1350":1,"1359":1,"1417":1,"1427":1,"1474":2,"1476":1,"1478":1,"1997":1}}],["consulting",{"3":{"0":1,"1686":1}}],["consumable",{"3":{"955":1,"1350":1,"1438":1}}],["consumption",{"3":{"31":1,"195":1,"234":1,"373":1,"530":1,"543":2,"576":1,"674":1,"692":1,"941":1,"954":1,"1300":3,"1301":2,"1323":1,"1339":3,"1396":2,"1443":2,"1450":1,"1453":1,"1454":1,"1467":3,"1488":1,"1489":1,"1577":1,"1665":1,"1675":1,"1677":1,"1678":1,"2011":1,"2012":1}}],["consuming",{"0":{"1258":1},"3":{"0":2,"26":1,"53":2,"68":1,"72":1,"81":1,"195":2,"197":1,"200":1,"201":1,"202":1,"203":1,"273":1,"309":1,"370":1,"382":1,"390":1,"470":1,"498":1,"518":1,"557":1,"571":1,"572":1,"585":1,"617":1,"702":1,"921":1,"945":1,"956":2,"958":1,"960":1,"1024":1,"1067":1,"1100":1,"1229":1,"1231":1,"1270":5,"1286":2,"1300":8,"1304":2,"1308":1,"1309":10,"1311":2,"1312":3,"1323":5,"1324":9,"1326":2,"1327":1,"1339":7,"1350":1,"1359":1,"1395":4,"1396":3,"1415":3,"1417":4,"1427":1,"1430":1,"1432":1,"1436":3,"1444":1,"1474":1,"1500":1,"1526":1,"1574":1,"1577":2,"1588":1,"1651":1,"1663":1,"1736":1,"1810":1,"1898":1,"1933":1,"1949":1,"1953":1,"1958":1,"1981":1,"2009":1,"2012":1,"2033":1,"2034":1,"2055":1,"2083":1,"2111":1,"2156":1,"2158":1,"2188":1}}],["consumecontext",{"3":{"150":1,"1259":1,"1323":1}}],["consumesthetokenbeforesaving",{"3":{"1323":1}}],["consumes",{"3":{"0":1,"59":1,"124":1,"195":6,"203":1,"375":1,"391":1,"483":1,"507":1,"508":1,"543":1,"585":2,"587":1,"665":1,"694":1,"751":1,"880":1,"990":1,"1200":1,"1236":1,"1247":1,"1259":5,"1266":1,"1270":1,"1286":3,"1294":1,"1300":4,"1309":2,"1310":2,"1311":4,"1312":1,"1322":1,"1323":1,"1324":5,"1332":1,"1339":3,"1341":1,"1349":1,"1350":1,"1359":10,"1369":1,"1378":1,"1396":13,"1402":1,"1415":2,"1416":1,"1417":8,"1427":1,"1436":4,"1438":3,"1441":1,"1451":1,"1455":2,"1460":1,"1467":2,"1472":1,"1475":1,"1488":2,"1489":2,"1496":1,"1526":2,"1531":1,"1599":1,"1641":1,"1748":1,"1765":1,"1843":1,"1874":1,"1922":1,"1963":1,"2004":1,"2121":1}}],["consumed",{"3":{"0":3,"146":1,"177":1,"202":2,"223":1,"265":1,"368":1,"374":1,"383":1,"481":1,"545":1,"549":1,"692":1,"733":1,"829":1,"856":1,"1124":1,"1144":1,"1212":1,"1237":5,"1240":1,"1247":6,"1249":1,"1259":10,"1260":1,"1270":7,"1286":24,"1291":1,"1300":36,"1301":6,"1309":9,"1310":3,"1311":9,"1320":1,"1323":11,"1324":31,"1334":1,"1339":17,"1344":1,"1345":1,"1350":22,"1359":22,"1380":27,"1395":14,"1396":39,"1402":14,"1415":12,"1416":4,"1417":38,"1427":4,"1432":1,"1436":14,"1437":1,"1438":1,"1443":1,"1453":1,"1455":2,"1456":1,"1467":1,"1488":2,"1492":1,"1496":1,"1525":1,"1526":3,"1527":1,"1535":1,"1555":1,"1591":2,"1638":2,"1641":2,"1748":1,"2055":1,"2066":1,"2101":1,"2221":1,"2228":1}}],["consume",{"3":{"0":5,"21":1,"109":1,"145":1,"150":2,"194":2,"195":2,"196":1,"199":1,"201":1,"202":1,"247":1,"369":1,"391":1,"535":1,"548":1,"599":1,"626":1,"656":1,"815":1,"818":2,"819":1,"823":1,"832":1,"1042":1,"1044":1,"1074":1,"1076":1,"1091":1,"1122":1,"1140":1,"1147":1,"1225":1,"1229":1,"1237":1,"1247":1,"1259":6,"1269":1,"1270":2,"1271":3,"1286":1,"1300":9,"1309":1,"1312":1,"1318":1,"1323":7,"1324":6,"1325":1,"1326":1,"1339":2,"1350":3,"1359":4,"1389":1,"1395":2,"1396":2,"1402":1,"1408":1,"1413":1,"1415":2,"1417":4,"1428":1,"1431":1,"1433":1,"1435":1,"1436":5,"1438":3,"1444":1,"1467":1,"1488":1,"1503":1,"1577":1,"1582":1,"1591":1,"1638":1,"1648":1,"1649":1,"1669":1,"1724":1,"1765":1,"1784":1,"1785":1,"1843":2,"1887":1,"1910":1,"2042":1,"2109":1,"2156":1,"2162":1,"2188":1,"2192":1}}],["consumermodule",{"3":{"1323":1}}],["consumerecoverycodeasync",{"3":{"1300":2}}],["consumeroriginrestore",{"2":{"150":1,"202":1,"1074":1,"1253":1},"3":{"145":1,"150":3,"202":4,"702":1,"1074":1,"1199":3,"1203":1,"1207":2,"1253":1,"1259":2,"1300":2,"1313":1,"1318":4,"1323":9,"1496":2}}],["consumers",{"0":{"452":1,"1709":1,"1882":1,"2055":1},"2":{"1259":1,"1323":1},"3":{"0":23,"27":1,"32":1,"41":2,"62":1,"68":1,"69":1,"71":1,"76":1,"77":1,"78":4,"79":1,"91":1,"109":1,"110":1,"118":1,"119":1,"122":1,"127":1,"133":1,"135":2,"136":1,"143":1,"145":2,"146":1,"147":1,"148":1,"150":8,"151":1,"156":1,"160":1,"162":1,"166":1,"201":1,"202":6,"255":4,"256":1,"259":1,"264":1,"312":1,"341":1,"342":1,"359":1,"369":1,"370":1,"375":1,"390":1,"391":1,"435":1,"446":1,"447":1,"451":1,"452":1,"470":1,"490":1,"491":1,"527":1,"530":1,"543":1,"549":1,"550":1,"552":1,"570":1,"572":1,"573":1,"574":2,"576":1,"577":2,"579":1,"591":1,"593":1,"598":1,"600":1,"607":2,"609":3,"638":4,"640":2,"665":2,"668":1,"682":1,"692":1,"698":2,"700":1,"702":2,"715":1,"723":1,"724":1,"725":1,"731":1,"733":1,"736":1,"739":2,"742":1,"743":2,"799":1,"800":1,"819":2,"820":1,"821":1,"825":3,"827":3,"829":1,"832":2,"833":1,"836":1,"845":1,"846":1,"849":1,"854":1,"856":1,"867":2,"870":1,"873":1,"876":1,"891":2,"912":1,"914":1,"918":1,"922":1,"948":1,"956":1,"958":1,"963":1,"980":1,"981":1,"998":1,"1004":3,"1007":1,"1008":1,"1044":1,"1058":1,"1059":4,"1061":1,"1074":3,"1085":1,"1092":1,"1140":1,"1176":1,"1177":1,"1178":1,"1192":1,"1193":1,"1203":6,"1207":58,"1208":1,"1212":3,"1229":1,"1234":1,"1237":5,"1247":4,"1257":1,"1258":4,"1259":27,"1270":6,"1271":2,"1280":1,"1284":1,"1285":1,"1286":17,"1299":1,"1300":8,"1301":1,"1309":3,"1310":3,"1311":8,"1312":2,"1313":1,"1317":4,"1318":3,"1320":1,"1323":29,"1324":15,"1339":6,"1350":7,"1359":4,"1369":2,"1380":6,"1395":2,"1396":17,"1402":5,"1413":1,"1415":8,"1417":4,"1427":1,"1431":1,"1436":32,"1438":6,"1441":1,"1443":1,"1447":1,"1453":2,"1454":1,"1467":2,"1469":1,"1488":1,"1489":2,"1491":2,"1492":1,"1496":2,"1498":1,"1508":1,"1526":1,"1528":1,"1543":2,"1546":1,"1547":1,"1551":1,"1569":3,"1570":1,"1572":1,"1575":2,"1577":5,"1579":1,"1581":1,"1582":2,"1583":1,"1585":7,"1589":1,"1591":1,"1596":1,"1599":2,"1631":1,"1632":2,"1639":1,"1641":1,"1660":1,"1673":2,"1674":1,"1676":1,"1677":1,"1686":1,"1699":1,"1706":1,"1711":1,"1736":1,"1769":2,"1770":1,"1805":1,"1846":1,"1875":1,"1886":1,"1887":2,"1889":3,"1890":2,"1891":1,"1892":3,"1911":1,"1912":2,"1920":1,"1974":1,"2004":1,"2027":1,"2042":1,"2047":1,"2055":2,"2060":1,"2065":1,"2068":1,"2112":1,"2113":1,"2114":1,"2115":1,"2130":1,"2156":1,"2162":2,"2182":2,"2202":4,"2203":1,"2232":1}}],["consumer",{"0":{"451":1,"1258":1,"1678":1,"1736":1,"1843":1},"1":{"192":1,"193":1,"194":1,"195":1,"196":1,"197":1,"198":1,"199":1,"200":1,"201":1,"202":1,"203":1},"3":{"0":48,"15":2,"17":1,"24":2,"25":2,"26":1,"27":2,"38":1,"39":2,"41":3,"59":1,"61":1,"62":2,"66":1,"67":1,"68":1,"70":1,"72":2,"73":1,"77":1,"78":1,"80":4,"81":1,"91":2,"95":1,"97":1,"98":1,"100":1,"101":1,"109":1,"111":2,"116":1,"117":1,"124":1,"127":1,"132":1,"134":1,"135":4,"136":5,"137":2,"145":2,"147":1,"149":1,"150":1,"151":2,"152":1,"160":2,"175":1,"189":1,"192":1,"193":2,"194":3,"195":4,"196":1,"197":1,"198":4,"199":2,"201":1,"202":3,"203":3,"225":1,"226":1,"241":1,"247":1,"255":8,"256":1,"258":1,"265":1,"273":1,"279":1,"283":1,"305":1,"311":1,"340":1,"344":1,"346":1,"359":1,"361":1,"369":1,"373":1,"374":2,"376":1,"391":1,"401":1,"412":1,"418":1,"432":1,"438":1,"439":1,"440":1,"441":1,"443":1,"448":2,"450":1,"451":3,"455":1,"457":1,"465":1,"474":1,"480":1,"481":1,"483":2,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":2,"495":1,"527":1,"528":1,"530":1,"543":2,"545":2,"549":1,"550":1,"551":3,"564":4,"572":3,"573":1,"574":1,"576":1,"577":2,"578":1,"583":1,"594":2,"598":1,"599":2,"605":1,"607":3,"609":6,"610":2,"611":2,"614":1,"616":2,"618":2,"619":1,"620":1,"624":1,"626":2,"629":1,"640":1,"641":1,"645":1,"659":1,"660":1,"672":1,"674":4,"675":1,"676":1,"684":2,"689":1,"690":2,"691":3,"696":2,"697":1,"698":2,"700":1,"707":2,"708":1,"714":2,"715":2,"716":2,"717":2,"721":1,"723":1,"724":1,"725":3,"727":3,"729":1,"733":1,"737":1,"740":1,"741":3,"742":2,"743":2,"747":1,"749":1,"751":2,"759":1,"780":2,"782":2,"783":1,"786":1,"789":1,"799":1,"802":1,"818":1,"819":7,"820":2,"821":2,"825":2,"827":6,"829":2,"832":1,"833":3,"836":3,"839":1,"840":1,"844":1,"845":2,"846":2,"847":1,"848":4,"849":1,"853":3,"854":2,"855":1,"861":1,"862":1,"865":1,"881":1,"889":2,"891":2,"903":1,"905":5,"907":6,"908":1,"910":2,"913":1,"914":2,"920":1,"923":1,"924":1,"926":3,"927":1,"930":1,"931":1,"935":1,"937":1,"939":1,"940":1,"941":3,"942":1,"954":2,"957":1,"958":1,"959":1,"963":1,"964":1,"965":1,"966":1,"972":1,"973":1,"974":1,"976":1,"979":1,"980":1,"982":1,"984":1,"986":1,"988":2,"994":1,"996":3,"999":1,"1000":1,"1003":1,"1004":2,"1006":1,"1008":1,"1012":2,"1016":1,"1033":2,"1034":2,"1035":1,"1036":1,"1037":1,"1043":1,"1044":1,"1049":1,"1050":6,"1051":2,"1052":1,"1058":2,"1059":6,"1060":1,"1061":4,"1065":2,"1067":3,"1068":4,"1069":3,"1070":2,"1074":3,"1075":5,"1076":1,"1077":3,"1078":1,"1089":2,"1091":1,"1093":3,"1096":1,"1117":2,"1160":1,"1162":1,"1177":2,"1178":1,"1179":1,"1185":1,"1188":1,"1195":1,"1202":1,"1203":1,"1211":1,"1212":10,"1228":1,"1229":3,"1233":1,"1237":5,"1241":1,"1247":10,"1248":1,"1249":2,"1256":1,"1258":6,"1259":81,"1261":1,"1262":1,"1269":3,"1270":15,"1271":8,"1273":2,"1274":1,"1280":1,"1286":43,"1290":1,"1291":1,"1300":25,"1301":1,"1309":10,"1310":1,"1311":30,"1312":10,"1315":1,"1317":3,"1318":2,"1320":1,"1323":60,"1324":56,"1326":1,"1339":31,"1345":1,"1348":1,"1349":1,"1350":28,"1359":19,"1369":1,"1378":1,"1379":1,"1380":9,"1381":2,"1386":1,"1395":6,"1396":48,"1398":1,"1402":10,"1406":1,"1412":1,"1413":1,"1415":17,"1416":1,"1417":14,"1418":1,"1421":1,"1427":6,"1428":2,"1429":2,"1430":1,"1431":4,"1432":1,"1433":1,"1436":179,"1437":4,"1438":12,"1441":3,"1442":1,"1443":5,"1451":1,"1453":3,"1454":4,"1455":2,"1460":1,"1461":1,"1465":1,"1467":3,"1476":3,"1487":2,"1488":4,"1489":2,"1490":1,"1492":7,"1493":1,"1496":8,"1503":1,"1526":2,"1528":1,"1543":1,"1546":2,"1547":5,"1551":1,"1572":1,"1574":3,"1575":10,"1576":2,"1577":28,"1578":1,"1579":1,"1581":3,"1582":7,"1583":1,"1584":2,"1585":10,"1586":2,"1589":2,"1591":4,"1593":1,"1596":3,"1597":1,"1599":1,"1611":2,"1631":1,"1632":11,"1645":2,"1656":1,"1659":1,"1660":1,"1662":1,"1664":2,"1665":4,"1666":2,"1671":2,"1673":4,"1674":1,"1675":1,"1676":1,"1677":1,"1678":1,"1679":2,"1680":1,"1685":1,"1686":2,"1706":1,"1707":5,"1708":1,"1721":1,"1727":2,"1732":1,"1734":1,"1735":1,"1749":1,"1751":3,"1764":1,"1765":1,"1769":7,"1772":1,"1782":1,"1784":1,"1799":1,"1800":1,"1810":3,"1815":2,"1826":1,"1829":1,"1843":5,"1845":2,"1851":1,"1856":2,"1865":1,"1870":1,"1886":1,"1887":2,"1888":4,"1889":7,"1890":3,"1891":6,"1892":1,"1896":1,"1899":1,"1903":1,"1906":1,"1910":5,"1911":2,"1912":1,"1919":1,"1921":1,"1922":2,"1923":3,"1926":1,"1927":1,"1929":2,"1930":1,"1948":1,"1949":1,"1970":1,"2005":1,"2006":3,"2009":1,"2017":1,"2025":2,"2042":1,"2044":2,"2046":2,"2056":1,"2057":1,"2059":1,"2060":1,"2063":2,"2067":1,"2068":3,"2069":1,"2072":1,"2074":2,"2077":1,"2083":1,"2085":2,"2087":1,"2107":3,"2109":1,"2112":3,"2114":1,"2124":1,"2126":2,"2127":2,"2131":1,"2133":1,"2150":1,"2156":2,"2166":1,"2173":1,"2179":2,"2182":1,"2194":1,"2196":1,"2197":1,"2198":2,"2201":2,"2202":1,"2203":1,"2232":5}}],["consts",{"3":{"1395":2,"1396":2}}],["const",{"2":{"2075":1},"3":{"256":1,"258":1,"305":1,"310":1,"897":1,"946":1,"1229":1,"1237":1,"1242":1,"1243":1,"1247":4,"1270":6,"1271":1,"1286":8,"1300":30,"1301":3,"1304":1,"1309":12,"1311":9,"1312":2,"1323":3,"1324":13,"1339":22,"1344":2,"1348":1,"1350":16,"1359":11,"1369":1,"1384":1,"1395":3,"1396":16,"1402":12,"1405":1,"1415":14,"1416":1,"1417":2,"1427":2,"1436":12,"1577":1,"1653":1,"2075":1}}],["constantexpression",{"3":{"1241":1,"1247":1,"1286":1,"1989":1}}],["constantly",{"3":{"656":1,"661":1,"1324":1,"1570":1,"2050":1,"2060":2}}],["constants",{"3":{"0":1,"186":2,"188":1,"202":1,"300":1,"310":1,"318":1,"359":1,"657":1,"731":1,"749":1,"859":1,"861":2,"883":1,"905":1,"946":2,"1067":1,"1068":1,"1184":1,"1212":1,"1214":1,"1229":4,"1234":2,"1237":10,"1247":2,"1270":1,"1271":6,"1286":13,"1290":1,"1300":20,"1309":10,"1311":14,"1323":7,"1324":36,"1325":1,"1326":2,"1330":1,"1332":2,"1333":1,"1339":22,"1341":1,"1344":2,"1350":66,"1359":14,"1362":1,"1369":7,"1373":1,"1380":2,"1384":1,"1390":1,"1395":18,"1396":23,"1402":12,"1404":1,"1405":1,"1412":1,"1414":1,"1415":22,"1416":7,"1417":4,"1427":1,"1430":1,"1436":40,"1438":2,"1444":1,"1496":1,"1526":2,"1577":1,"1653":1,"1677":1,"1685":1,"1810":2,"1829":1,"1833":1,"1834":1,"1902":1,"1944":2,"1964":1,"1968":1,"1989":1,"2056":1,"2136":1,"2138":1,"2139":1,"2142":1}}],["constant",{"0":{"1902":1},"3":{"0":3,"24":1,"100":1,"179":1,"186":3,"213":1,"214":1,"217":1,"280":1,"305":3,"310":1,"312":1,"318":1,"391":1,"413":1,"418":1,"422":1,"423":2,"425":1,"457":1,"459":1,"463":1,"609":1,"619":2,"620":1,"697":1,"698":3,"699":1,"702":1,"740":1,"751":1,"790":1,"793":1,"827":4,"856":1,"880":1,"881":2,"885":1,"897":2,"905":2,"945":1,"946":3,"948":2,"980":1,"1017":1,"1018":4,"1067":1,"1090":1,"1094":1,"1095":1,"1100":1,"1134":1,"1161":1,"1175":1,"1212":2,"1232":1,"1233":1,"1237":6,"1247":5,"1255":1,"1259":4,"1267":1,"1270":1,"1271":15,"1276":2,"1286":18,"1288":1,"1290":1,"1292":2,"1294":1,"1295":2,"1297":1,"1300":28,"1303":1,"1305":1,"1309":11,"1311":19,"1312":2,"1323":17,"1324":16,"1326":2,"1328":1,"1330":1,"1333":3,"1336":1,"1337":2,"1339":14,"1344":4,"1348":1,"1350":27,"1353":1,"1359":73,"1366":3,"1367":1,"1369":7,"1380":1,"1384":1,"1389":1,"1393":1,"1395":28,"1396":23,"1398":1,"1399":1,"1402":19,"1405":1,"1415":9,"1416":5,"1417":29,"1423":1,"1427":1,"1431":1,"1436":35,"1441":1,"1443":2,"1488":1,"1526":1,"1641":2,"1651":1,"1667":1,"1668":1,"1670":1,"1683":1,"1712":1,"1727":2,"1765":2,"1767":1,"1771":1,"1833":1,"1851":2,"1902":3,"1905":2,"1916":2,"1919":1,"1935":1,"1958":1,"1982":1,"2024":1,"2056":1,"2057":1,"2073":1,"2075":1,"2118":1,"2123":1,"2138":4,"2147":1,"2148":1,"2149":1,"2153":1,"2193":1,"2239":1}}],["constrain",{"3":{"95":1,"725":1,"774":1,"1025":1,"1286":1,"1311":1,"1339":1,"1350":1,"1359":6,"1415":1,"2141":1}}],["constrains",{"3":{"71":1,"95":1,"216":1,"372":1,"427":1,"469":1,"470":1,"568":1,"630":1,"675":1,"732":1,"801":1,"804":1,"819":1,"889":1,"1049":1,"1232":1,"1237":2,"1270":1,"1271":1,"1281":1,"1286":2,"1291":1,"1300":2,"1309":3,"1311":1,"1312":1,"1323":1,"1339":1,"1359":11,"1375":1,"1395":1,"1396":3,"1415":5,"1436":1,"1475":1,"1810":1,"2032":1,"2152":1}}],["constrained",{"3":{"0":1,"782":1,"1018":2,"1050":1,"1090":1,"1212":1,"1231":2,"1237":3,"1247":4,"1259":7,"1270":9,"1271":5,"1286":11,"1297":1,"1300":4,"1310":1,"1311":5,"1323":10,"1324":4,"1350":2,"1353":2,"1359":22,"1366":1,"1369":1,"1395":3,"1402":1,"1417":1,"1436":5,"1496":1,"1526":1,"1953":1}}],["constraining",{"3":{"0":1,"1093":1,"1237":1,"1270":2,"1286":1,"1323":1,"1359":1,"2179":1}}],["constraints",{"0":{"1635":1,"1675":1},"3":{"0":1,"47":1,"61":1,"62":1,"92":1,"166":1,"168":1,"412":1,"657":1,"681":1,"725":1,"732":1,"848":1,"921":1,"1074":1,"1083":1,"1085":1,"1196":1,"1237":1,"1247":4,"1259":1,"1270":7,"1281":1,"1286":12,"1309":1,"1310":1,"1311":9,"1323":11,"1324":2,"1339":1,"1350":1,"1359":15,"1369":2,"1396":2,"1402":1,"1415":1,"1416":2,"1417":6,"1431":1,"1436":4,"1473":1,"1569":1,"1602":1,"1632":8,"1635":2,"1639":1,"1640":2,"1641":1,"1652":1,"1953":1,"1967":1,"2064":1}}],["constraint",{"3":{"0":4,"62":1,"92":1,"101":1,"109":1,"119":1,"145":1,"150":1,"159":1,"345":1,"363":1,"364":1,"411":1,"415":1,"464":1,"476":1,"641":1,"657":1,"691":1,"692":1,"723":1,"759":1,"792":1,"808":1,"810":1,"811":1,"814":1,"818":1,"825":1,"826":2,"827":1,"869":1,"915":1,"955":1,"1036":1,"1082":2,"1137":1,"1237":5,"1239":1,"1247":16,"1259":7,"1260":1,"1269":1,"1270":24,"1271":1,"1284":1,"1286":52,"1291":2,"1300":23,"1309":2,"1310":6,"1311":17,"1319":1,"1322":1,"1323":22,"1324":13,"1339":6,"1344":1,"1350":5,"1352":1,"1353":1,"1357":1,"1359":69,"1368":1,"1369":3,"1395":3,"1396":22,"1402":4,"1415":9,"1417":4,"1436":13,"1438":1,"1465":1,"1467":5,"1478":1,"1488":1,"1489":1,"1534":1,"1585":3,"1591":1,"1631":2,"1635":3,"1638":2,"1639":2,"1641":1,"1663":2,"1675":1,"1724":1,"1748":1,"1760":1,"1765":1,"1792":1,"1810":1,"1811":1,"1815":1,"1824":2,"1826":1,"1830":1,"1849":1,"1853":3,"1858":2,"1862":1,"1866":1,"1873":1,"1876":2,"1911":1,"1916":1,"1919":1,"1927":1,"1949":1,"1958":1,"1988":1,"1989":1,"2024":1,"2025":1,"2069":1,"2071":2,"2095":1,"2112":1,"2116":1,"2150":1,"2192":1}}],["constructevent",{"2":{"1765":1,"1770":1},"3":{"1765":1,"1770":1}}],["constructedexceptiontype",{"3":{"1436":2}}],["constructed",{"3":{"0":2,"72":1,"73":1,"109":1,"359":1,"365":1,"549":1,"572":1,"585":1,"657":1,"665":1,"674":1,"698":1,"733":1,"741":1,"784":1,"861":1,"922":2,"926":1,"1016":1,"1107":1,"1110":1,"1212":1,"1229":2,"1234":1,"1237":4,"1244":1,"1246":1,"1247":7,"1259":2,"1262":1,"1270":3,"1271":1,"1273":1,"1274":1,"1286":27,"1300":14,"1301":2,"1309":5,"1310":6,"1311":7,"1323":11,"1324":12,"1339":9,"1343":1,"1350":5,"1359":77,"1364":1,"1368":2,"1380":1,"1395":8,"1396":15,"1402":10,"1415":12,"1416":1,"1417":7,"1429":1,"1432":1,"1436":42,"1438":1,"1488":2,"1541":1,"1810":1,"1812":1,"1826":1,"1865":1,"2055":1,"2230":1}}],["construct",{"3":{"725":1,"726":1,"1018":1,"1060":1,"1229":1,"1237":1,"1247":1,"1270":1,"1271":2,"1286":5,"1300":2,"1309":2,"1311":1,"1323":1,"1324":3,"1339":2,"1350":6,"1359":6,"1396":2,"1415":1,"1427":1,"1436":13,"1441":1,"1631":1,"1651":1,"1809":1,"2170":1}}],["constructible",{"3":{"1286":2,"1323":1}}],["constructing",{"3":{"361":1,"435":1,"782":1,"1247":1,"1259":3,"1270":1,"1286":3,"1309":2,"1311":6,"1343":1,"1359":2,"1405":1,"1415":1,"1427":1,"1436":4,"2142":1}}],["constructions",{"3":{"837":1,"1359":1}}],["construction",{"0":{"1221":1},"3":{"0":9,"20":1,"24":1,"60":1,"71":1,"79":1,"121":1,"123":1,"219":1,"234":1,"248":1,"255":1,"291":1,"295":1,"359":4,"361":1,"545":1,"546":1,"547":1,"549":2,"552":1,"574":2,"640":1,"655":1,"657":1,"667":1,"683":1,"690":1,"698":2,"715":1,"743":1,"750":1,"829":2,"834":1,"846":2,"881":1,"891":1,"926":1,"1018":1,"1020":1,"1034":1,"1051":1,"1085":1,"1090":1,"1091":2,"1124":1,"1160":1,"1163":1,"1168":3,"1169":1,"1212":2,"1220":1,"1221":1,"1223":1,"1228":1,"1229":5,"1231":1,"1233":1,"1237":10,"1247":5,"1249":1,"1253":1,"1255":1,"1258":1,"1259":10,"1267":1,"1270":5,"1271":1,"1274":1,"1278":1,"1279":1,"1284":1,"1286":32,"1300":10,"1301":6,"1302":1,"1309":3,"1310":1,"1311":17,"1320":1,"1323":15,"1324":11,"1339":2,"1347":1,"1350":8,"1359":22,"1369":1,"1380":3,"1395":7,"1396":17,"1402":5,"1415":5,"1417":9,"1420":1,"1421":1,"1423":1,"1427":2,"1436":17,"1455":1,"1474":1,"1488":1,"1489":1,"1496":1,"1498":1,"1539":1,"1548":1,"1577":1,"1591":1,"1665":1,"1685":1,"1764":1,"1765":1,"1767":1,"1809":1,"1810":1,"1811":1,"1812":1,"1825":1,"1829":1,"1833":1,"1839":1,"1889":1,"1920":1,"1923":1,"1924":1,"2000":1,"2024":1,"2052":1,"2073":1,"2090":1,"2126":1,"2142":2,"2143":1,"2145":1,"2150":2,"2173":1,"2175":1,"2199":1}}],["constructs",{"3":{"166":1,"230":1,"556":1,"703":1,"861":1,"885":1,"922":1,"1016":1,"1018":1,"1035":1,"1090":1,"1229":1,"1247":1,"1259":3,"1265":1,"1270":4,"1271":3,"1286":10,"1297":1,"1300":1,"1301":1,"1309":2,"1311":10,"1315":2,"1323":3,"1324":3,"1339":9,"1350":7,"1359":13,"1369":3,"1377":1,"1380":4,"1395":2,"1396":2,"1402":7,"1415":5,"1416":1,"1417":4,"1427":2,"1436":20,"1438":1,"1491":1,"1591":1,"1685":1,"1938":1,"2172":1}}],["constructorarity",{"3":{"1436":1}}],["constructorinfo",{"3":{"1436":1}}],["constructordependencies",{"3":{"1207":2,"1436":3,"1577":1,"1582":1,"1583":1,"1584":1,"1586":1}}],["constructordependencyfixtures",{"3":{"1207":18,"1436":28}}],["constructordependencycounttests",{"2":{"1591":1},"3":{"1199":1,"1207":2,"1359":1,"1371":1,"1380":3,"1415":1,"1436":10,"1489":1,"1496":2,"1526":1,"1534":2,"1535":1,"1591":2,"1597":1,"1599":1}}],["constructordependencycounttestsbasetests",{"2":{"1431":1},"3":{"1199":1,"1207":7,"1431":3,"1436":38,"1577":1}}],["constructordependencycounttestsbase",{"2":{"1431":1,"1489":1,"1654":1},"3":{"1199":6,"1207":2,"1431":3,"1436":32,"1489":1,"1496":1,"1654":1}}],["constructors",{"3":{"497":1,"499":1,"674":1,"964":1,"1221":1,"1223":1,"1226":2,"1229":13,"1247":2,"1271":2,"1286":3,"1309":5,"1311":1,"1312":6,"1339":1,"1350":10,"1359":1,"1396":4,"1402":2,"1415":1,"1423":1,"1427":3,"1436":15,"1438":1,"1526":1,"1538":1,"1586":1,"1651":1,"1805":1,"1811":1,"1812":1,"2104":1,"2142":1}}],["constructor",{"0":{"2090":1},"3":{"0":4,"122":1,"135":1,"160":1,"168":1,"230":1,"243":1,"245":1,"258":1,"281":2,"310":1,"314":1,"318":1,"323":1,"330":1,"359":3,"365":1,"388":1,"389":1,"435":1,"459":3,"465":2,"466":1,"474":1,"497":1,"499":1,"524":1,"545":2,"549":1,"578":1,"585":1,"633":3,"640":1,"642":1,"657":4,"673":1,"674":4,"676":1,"680":1,"692":1,"731":2,"733":2,"799":1,"801":2,"803":2,"827":1,"837":1,"838":1,"846":1,"854":1,"857":1,"881":1,"908":1,"909":1,"954":1,"964":1,"972":1,"994":1,"996":3,"999":1,"1054":1,"1059":3,"1061":1,"1089":1,"1090":1,"1091":1,"1094":1,"1167":1,"1168":4,"1169":1,"1193":1,"1212":1,"1221":1,"1227":3,"1228":1,"1229":17,"1231":1,"1234":2,"1237":12,"1240":1,"1244":2,"1247":24,"1252":1,"1259":19,"1261":1,"1267":1,"1268":1,"1269":2,"1270":24,"1271":13,"1273":1,"1278":1,"1286":104,"1288":1,"1289":2,"1293":1,"1300":76,"1301":12,"1307":1,"1309":36,"1310":8,"1311":27,"1312":6,"1316":1,"1317":1,"1320":1,"1323":47,"1324":59,"1336":2,"1339":28,"1342":1,"1343":2,"1350":34,"1353":1,"1359":219,"1363":1,"1369":9,"1371":2,"1378":1,"1380":16,"1395":26,"1396":44,"1399":1,"1402":25,"1405":2,"1406":1,"1407":1,"1415":48,"1416":8,"1417":37,"1423":1,"1426":1,"1427":15,"1436":111,"1438":5,"1488":3,"1489":1,"1496":4,"1525":1,"1526":1,"1534":1,"1535":1,"1577":2,"1582":1,"1583":1,"1584":1,"1586":1,"1588":1,"1591":2,"1597":1,"1599":1,"1600":1,"1668":1,"1671":1,"1673":1,"1685":2,"1689":1,"1699":1,"1702":1,"1727":1,"1810":3,"1811":4,"1812":1,"1820":1,"1829":1,"1830":1,"1945":1,"1983":1,"1994":1,"2000":3,"2024":1,"2087":1,"2089":3,"2090":2,"2098":1,"2142":1,"2149":2,"2176":1,"2194":1,"2196":1,"2202":3,"2203":1,"2232":1}}],["consists",{"3":{"1359":1}}],["consistency",{"0":{"1557":1},"3":{"0":1,"47":1,"49":1,"60":1,"61":1,"68":1,"166":1,"168":1,"169":1,"171":1,"273":1,"350":1,"535":1,"536":1,"784":1,"809":1,"971":1,"981":1,"1147":1,"1212":1,"1216":1,"1231":1,"1237":3,"1256":1,"1259":1,"1270":1,"1278":1,"1286":3,"1300":1,"1301":1,"1309":2,"1323":1,"1324":5,"1350":9,"1359":4,"1395":1,"1396":3,"1402":3,"1413":1,"1415":3,"1431":2,"1436":3,"1473":1,"1499":1,"1510":1,"1526":1,"1535":1,"1537":1,"1543":1,"1544":1,"1545":2,"1557":1,"1571":1,"1577":2,"1581":1,"1582":1,"1583":1,"1591":1,"1595":1,"1597":1,"1637":1,"1638":6,"1639":2,"1656":1,"1664":1,"1792":1,"1796":1,"1798":1,"1810":1,"1845":1,"1846":1,"1847":1,"1849":1,"1852":2,"1853":2,"1861":1,"1866":1,"1921":1,"1933":1,"2027":1,"2035":1,"2075":1,"2079":1,"2098":1,"2113":1,"2162":1,"2168":1,"2169":1,"2230":1,"2232":1}}],["consistently",{"3":{"102":1,"264":1,"578":1,"1216":1,"1270":2,"1271":1,"1300":2,"1311":1,"1323":2,"1324":3,"1359":7,"1395":2,"1486":1,"1526":2,"1537":1,"1539":1,"1545":1,"1552":1,"1557":1,"1572":1,"1577":1,"1591":1,"1631":1,"1797":1,"1802":1}}],["consistent",{"3":{"0":1,"7":1,"121":1,"136":1,"159":1,"195":1,"200":1,"244":1,"251":1,"252":1,"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"298":1,"302":1,"303":3,"332":1,"471":1,"484":1,"491":1,"492":1,"493":1,"509":1,"564":1,"644":1,"1118":1,"1167":1,"1218":1,"1226":1,"1229":5,"1237":4,"1247":2,"1259":1,"1271":1,"1286":2,"1300":6,"1309":3,"1311":7,"1312":2,"1323":3,"1324":5,"1339":1,"1340":1,"1349":1,"1350":5,"1355":1,"1359":15,"1369":1,"1370":1,"1380":1,"1395":5,"1396":4,"1402":4,"1403":1,"1413":1,"1415":3,"1417":3,"1429":1,"1433":1,"1436":10,"1441":1,"1488":1,"1489":1,"1492":1,"1526":3,"1537":1,"1546":2,"1552":1,"1561":2,"1562":1,"1564":1,"1570":1,"1577":2,"1591":1,"1630":1,"1635":1,"1639":2,"1641":1,"1671":1,"1679":1,"1797":1,"1805":1,"1831":1,"1832":1,"1926":1,"1939":1,"1990":1,"2055":1,"2081":1,"2138":1}}],["consideration",{"3":{"1417":1,"1630":1,"1631":1,"1639":1,"1640":1}}],["considerations",{"3":{"209":1}}],["consider",{"3":{"1359":1,"1435":1,"1755":1,"1776":1,"2098":1}}],["considers",{"3":{"675":1,"1259":1,"1286":1,"1311":1,"1324":1,"1339":1,"1639":1}}],["considered",{"3":{"0":1,"1":2,"387":1,"469":1,"470":1,"656":1,"733":1,"797":1,"800":1,"819":1,"1051":1,"1060":1,"1068":1,"1226":1,"1286":1,"1339":1,"1359":1,"1380":2,"1395":1,"1396":2,"1415":1,"1568":1,"1631":1}}],["contorted",{"3":{"1359":1}}],["contoso",{"2":{"1646":1,"1649":1,"1650":5,"1657":1,"1697":2,"1699":1,"1727":2,"1728":2,"2089":1},"3":{"556":1,"633":1,"1646":1,"1649":1,"1650":5,"1653":3,"1654":2,"1656":3,"1657":2,"1697":3,"1699":1,"1719":1,"1727":4,"1728":2,"2089":1}}],["contactfragments",{"3":{"1436":1}}],["contacts",{"3":{"1359":1,"1395":2}}],["contact",{"1":{"2244":1,"2245":1},"2":{"1237":1,"1271":1},"3":{"103":1,"390":1,"657":1,"1016":1,"1018":3,"1019":1,"1091":1,"1093":1,"1203":6,"1207":20,"1229":2,"1234":6,"1237":12,"1271":8,"1286":2,"1311":1,"1323":1,"1342":1,"1350":3,"1359":8,"1366":1,"1369":1,"1380":1,"1395":1,"1417":1,"1423":1,"1427":1,"1435":2,"1436":1,"1453":1,"1492":1,"1496":2,"1526":2,"1577":1,"1639":2,"1751":1,"1770":1,"1772":1,"1829":1,"1873":1,"2109":1,"2158":1,"2217":1,"2244":1}}],["contain",{"2":{"1730":1},"3":{"38":1,"258":1,"359":1,"401":1,"523":1,"568":1,"584":1,"1215":1,"1228":1,"1229":2,"1256":1,"1259":3,"1275":1,"1286":3,"1300":1,"1323":2,"1324":4,"1339":2,"1359":6,"1380":1,"1395":1,"1396":1,"1402":1,"1415":2,"1436":21,"1438":1,"1442":1,"1455":2,"1479":1,"1489":2,"1639":1,"1701":1,"1730":1,"1765":1,"1767":2,"1771":1,"1845":1,"2058":1,"2065":1}}],["containing",{"3":{"24":1,"177":1,"609":1,"633":1,"725":1,"980":1,"1228":1,"1237":2,"1255":1,"1270":1,"1298":1,"1300":2,"1309":2,"1311":1,"1324":4,"1339":2,"1359":6,"1395":1,"1396":1,"1415":1,"1417":1,"1436":16,"1458":1,"1488":1,"1639":2,"1641":3}}],["containment",{"3":{"0":1,"970":1,"975":1,"1018":1,"1020":1,"1036":1,"1247":1,"1324":1,"1359":4,"1369":1,"1396":1,"1671":1,"2232":1}}],["containsingle",{"3":{"1436":1}}],["containsanyexcept",{"3":{"1309":1}}],["containspredicate",{"3":{"1286":4,"1415":1}}],["containsregionclaim",{"2":{"972":1,"975":1},"3":{"972":2,"975":1,"1436":7}}],["contains",{"2":{"1239":1,"1859":1},"3":{"0":1,"94":1,"126":1,"135":1,"293":1,"459":1,"524":1,"583":1,"609":1,"741":1,"827":1,"926":1,"980":1,"981":1,"1018":1,"1085":1,"1234":2,"1237":4,"1239":2,"1241":3,"1247":20,"1259":2,"1265":1,"1270":3,"1286":11,"1300":5,"1310":2,"1311":3,"1323":4,"1324":3,"1339":3,"1350":3,"1359":44,"1369":2,"1380":1,"1395":18,"1396":7,"1402":3,"1415":5,"1417":1,"1436":50,"1438":1,"1442":1,"1447":1,"1454":2,"1455":1,"1459":1,"1466":1,"1467":2,"1471":1,"1486":1,"1526":1,"1596":1,"1598":1,"1599":1,"1600":1,"1640":3,"1641":1,"1651":1,"1735":1,"1736":1,"1749":1,"1755":1,"1761":1,"1764":4,"1829":1,"1849":1,"1859":2}}],["contained",{"3":{"0":1,"165":1,"196":1,"341":1,"389":1,"826":1,"837":1,"853":1,"945":1,"1033":1,"1145":1,"1155":1,"1237":2,"1271":1,"1300":1,"1301":1,"1309":2,"1310":2,"1311":1,"1323":1,"1324":2,"1339":1,"1350":5,"1359":5,"1366":1,"1380":1,"1395":2,"1396":2,"1402":5,"1407":1,"1415":1,"1416":1,"1436":3,"1486":1,"1496":1}}],["containerization",{"3":{"1554":1}}],["containerized",{"3":{"1311":1,"1339":1}}],["containerregistry",{"3":{"1465":1}}],["containerregistryloginevents",{"2":{"1465":1},"3":{"1465":1}}],["containerregistryrepositoryevents",{"2":{"1465":1},"3":{"1465":1}}],["containerresource",{"3":{"1339":1}}],["containerlifetime",{"2":{"1441":1},"3":{"1339":1,"1441":1}}],["containername",{"2":{"440":1},"3":{"440":1,"1323":2,"1467":1}}],["containerstarttimeout",{"3":{"640":1,"1436":2}}],["containers",{"1":{"1439":1,"1440":1,"1441":1,"1442":1,"1443":1,"1444":1,"1445":1,"1446":1,"1447":1,"1448":1,"1449":1,"1450":1,"1451":1},"3":{"0":2,"10":1,"62":1,"599":1,"869":1,"914":1,"915":1,"1069":1,"1325":1,"1332":1,"1359":1,"1436":2,"1439":2,"1441":2,"1445":1,"1449":1,"1450":1,"1462":1,"1467":5,"1468":1,"1487":1,"1490":2,"1861":1,"2004":1,"2008":1,"2013":1,"2213":1}}],["containerappconsolelogs",{"2":{"1476":1},"3":{"1467":1,"1476":1,"1591":1}}],["containerapps",{"3":{"1436":1,"1467":1}}],["containerappsystemlogs",{"2":{"609":1,"1465":1,"1476":1},"3":{"609":1,"1465":1,"1467":1,"1476":1}}],["containerappdeclaration",{"3":{"1436":1}}],["containerapp",{"2":{"563":1,"757":1,"1464":1},"3":{"0":1,"563":1,"757":1,"1212":1,"1464":7,"1474":1,"1476":1}}],["container",{"0":{"1385":1},"1":{"8":1,"9":1,"10":1,"11":1,"12":1,"13":1,"866":1,"867":1,"868":1,"869":1,"870":1,"871":1,"872":1,"873":1,"874":1,"875":1,"876":1,"877":1},"3":{"0":19,"8":1,"10":2,"31":1,"47":1,"61":1,"63":1,"91":2,"92":2,"93":1,"94":1,"95":1,"97":1,"98":2,"100":1,"102":1,"137":1,"159":1,"166":3,"168":1,"175":1,"202":1,"217":1,"234":2,"235":1,"242":1,"245":1,"291":3,"292":1,"374":1,"401":4,"402":1,"405":1,"413":1,"422":1,"440":2,"441":1,"443":1,"444":1,"451":1,"517":1,"527":1,"545":2,"550":1,"563":1,"564":1,"582":1,"583":1,"584":1,"585":1,"598":1,"599":2,"603":1,"626":1,"633":2,"639":1,"640":8,"641":1,"642":2,"643":1,"665":5,"666":2,"667":1,"669":2,"670":1,"733":2,"751":1,"756":2,"757":1,"758":1,"759":1,"764":1,"765":1,"766":3,"774":2,"783":1,"784":1,"790":1,"791":1,"792":1,"818":1,"820":1,"826":1,"827":5,"833":1,"837":1,"838":1,"866":1,"867":1,"868":1,"869":2,"870":1,"871":1,"872":1,"875":3,"913":2,"914":1,"915":1,"916":1,"924":1,"931":1,"971":1,"980":2,"995":1,"1004":1,"1017":3,"1022":1,"1034":1,"1037":1,"1052":1,"1067":3,"1069":1,"1101":1,"1107":1,"1116":6,"1117":1,"1118":3,"1123":2,"1124":2,"1125":4,"1154":1,"1156":1,"1175":1,"1176":2,"1178":2,"1192":1,"1202":1,"1203":1,"1212":5,"1244":1,"1247":1,"1259":4,"1261":1,"1269":1,"1270":2,"1271":3,"1280":2,"1282":1,"1284":1,"1285":1,"1286":34,"1300":3,"1301":9,"1307":1,"1309":10,"1310":1,"1311":11,"1312":2,"1315":2,"1316":1,"1322":2,"1323":22,"1324":19,"1325":2,"1326":2,"1327":2,"1331":1,"1332":2,"1333":1,"1337":1,"1339":39,"1340":1,"1342":1,"1350":4,"1357":3,"1359":8,"1369":1,"1378":1,"1380":1,"1385":1,"1395":14,"1396":6,"1401":1,"1402":15,"1415":5,"1416":4,"1417":11,"1421":1,"1427":3,"1429":1,"1430":1,"1436":38,"1438":8,"1440":2,"1441":6,"1442":2,"1443":5,"1444":4,"1445":3,"1446":7,"1447":15,"1449":1,"1450":1,"1455":13,"1457":4,"1458":1,"1463":1,"1464":1,"1465":6,"1467":55,"1469":2,"1472":2,"1473":3,"1474":3,"1476":2,"1477":2,"1478":1,"1481":1,"1484":1,"1486":1,"1487":2,"1488":2,"1489":2,"1490":1,"1496":2,"1524":1,"1525":1,"1526":4,"1534":2,"1535":1,"1555":1,"1577":2,"1589":2,"1591":4,"1595":1,"1597":1,"1611":2,"1612":1,"1616":1,"1617":1,"1631":2,"1639":1,"1641":1,"1648":2,"1653":1,"1654":1,"1670":4,"1677":2,"1682":1,"1689":2,"1692":1,"1693":1,"1718":4,"1724":1,"1752":1,"1763":1,"1774":1,"1792":1,"1810":1,"1820":1,"1827":1,"1843":1,"1854":1,"1856":2,"1864":1,"1868":1,"1879":1,"1882":1,"1883":1,"1886":1,"1916":1,"1923":1,"1938":1,"1956":1,"1970":3,"2000":3,"2004":1,"2006":1,"2008":1,"2010":1,"2011":1,"2013":3,"2019":1,"2024":1,"2025":3,"2033":1,"2037":1,"2043":1,"2053":2,"2056":1,"2060":2,"2110":2,"2113":1,"2126":5,"2127":1,"2128":2,"2157":1,"2173":1,"2175":1,"2180":1,"2184":1,"2190":1,"2193":1,"2213":1,"2219":1,"2221":1,"2229":1,"2232":5,"2239":1,"2243":1}}],["contrived",{"3":{"2113":1}}],["contributions",{"3":{"1324":2,"1672":1}}],["contribution",{"3":{"650":1,"1270":1,"1297":1,"1300":1,"1309":1,"1324":1,"1359":3,"1369":1,"1380":1,"1415":5,"1416":1,"1436":9,"2077":1}}],["contributing",{"3":{"135":1,"483":1,"563":3,"564":7,"591":3,"593":1,"594":1,"618":1,"621":1,"626":5,"724":1,"756":3,"766":3,"1091":2,"1300":2,"1323":3,"1339":1,"1350":1,"1357":1,"1380":1,"1395":1,"1416":1,"1436":2,"1459":2,"1486":3,"1492":4,"1496":1,"1513":1,"1526":6,"1577":7,"1582":2,"1586":1,"1591":3,"1600":1}}],["contributed",{"3":{"186":1,"1300":3,"1311":1,"1324":3,"1396":3,"1442":1,"1962":1,"2077":1}}],["contribute",{"3":{"166":1,"648":1,"650":1,"1178":1,"1286":2,"1300":1,"1323":1,"1324":1,"1339":2,"1359":1,"1380":1,"1396":4,"1415":1,"1416":1,"1436":1,"1443":2,"1467":1,"1487":1,"1627":1,"1884":1}}],["contributes",{"3":{"166":2,"171":1,"187":1,"552":1,"585":1,"649":2,"651":1,"725":1,"728":1,"846":1,"1212":1,"1254":1,"1259":2,"1271":3,"1275":1,"1286":5,"1297":1,"1300":1,"1311":1,"1312":6,"1314":1,"1315":1,"1316":1,"1317":1,"1323":4,"1324":9,"1332":1,"1339":1,"1359":15,"1369":1,"1379":2,"1395":6,"1396":4,"1402":1,"1404":1,"1410":1,"1412":1,"1414":1,"1415":9,"1416":3,"1436":9,"1438":2,"1442":1,"1443":1,"1467":1,"1761":1,"1881":1,"1882":1,"1935":1,"2044":1,"2077":1,"2163":1,"2232":1}}],["contributors",{"3":{"756":1,"1299":1,"1300":2,"1317":1,"1323":1,"1415":1,"1577":1,"1582":1,"2040":1,"2067":1}}],["contributor",{"2":{"2127":1},"3":{"0":1,"441":1,"599":1,"628":1,"665":2,"1116":1,"1119":1,"1299":2,"1300":4,"1323":4,"1339":1,"1415":2,"1416":1,"1467":4,"1472":2,"1474":1,"1475":1,"1477":1,"1571":1,"2041":1,"2048":1,"2049":1,"2060":1,"2127":1,"2197":1,"2238":1}}],["contravariance",{"3":{"1259":1,"1270":2,"1310":2}}],["contravariant",{"3":{"1259":2,"1261":1,"1270":4,"1310":1}}],["contradict",{"3":{"1395":1,"1571":1,"1585":1,"1765":1}}],["contradicts",{"3":{"1270":1}}],["contradictory",{"3":{"790":1,"1436":1}}],["contradicted",{"3":{"761":1,"972":1,"1436":1,"1489":1}}],["contradicting",{"3":{"364":1,"972":1}}],["contradiction",{"3":{"256":1,"828":1,"888":1,"1417":1,"1427":1,"1436":1}}],["contrasted",{"3":{"1395":1}}],["contrasting",{"3":{"1324":1,"1395":2,"1396":2}}],["contrasts",{"3":{"455":1,"967":1,"1247":1,"1259":1,"1427":1}}],["contrast",{"3":{"0":1,"24":1,"93":1,"473":1,"617":1,"618":1,"619":1,"621":1,"622":1,"1237":3,"1259":1,"1270":1,"1286":2,"1300":2,"1309":5,"1312":1,"1323":2,"1324":13,"1339":1,"1350":4,"1359":14,"1369":3,"1374":1,"1380":1,"1395":4,"1396":15,"1402":1,"1415":4,"1416":1,"1417":2,"1427":1,"1434":1,"1436":8,"1526":2,"1558":2,"1577":3,"1586":1,"1591":1,"1605":1,"1644":1,"1645":2,"1739":1,"2075":1,"2079":1,"2202":1}}],["contracted",{"3":{"1324":1}}],["contracttype",{"3":{"1247":1}}],["contractnames",{"3":{"1247":2}}],["contractually",{"3":{"1324":1,"1417":1,"1436":1}}],["contractual",{"3":{"1237":1,"1338":1,"1359":1,"1380":1,"1567":1,"1929":1}}],["contractimplementationtests",{"3":{"1199":2,"1207":4,"1436":17,"1489":3}}],["contractimplementationtestsbase",{"2":{"1663":1},"3":{"1199":3,"1207":2,"1312":3,"1436":10,"1663":1}}],["contract",{"0":{"451":1,"1266":1,"1270":1,"1314":1,"1419":1,"1436":1,"1546":1,"1640":1,"1916":1,"1928":1,"1948":1,"1988":1,"2044":1,"2045":1,"2057":1,"2117":1,"2131":1,"2166":1,"2176":1},"1":{"327":1,"328":1,"329":1,"330":1,"331":1,"332":1,"333":1,"334":1,"335":1,"336":1,"337":1,"542":1,"543":1,"544":1,"545":1,"546":1,"547":1,"548":1,"549":1,"550":1,"551":1,"552":1,"561":1,"562":1,"563":1,"564":1,"565":1,"566":1,"567":1,"568":1,"580":1,"581":1,"582":1,"583":1,"584":1,"585":1,"586":1,"587":1,"615":1,"616":1,"617":1,"618":1,"619":1,"620":1,"621":1,"622":1,"671":1,"672":1,"673":1,"674":1,"675":1,"676":1,"677":1,"678":1,"722":1,"723":1,"724":1,"725":1,"726":1,"727":1,"728":1,"729":1,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1,"878":1,"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"894":1,"895":1,"896":1,"897":1,"898":1,"899":1,"900":1,"901":1,"985":1,"986":1,"987":1,"988":1,"989":1,"990":1,"991":1,"992":1,"1165":1,"1166":1,"1167":1,"1168":1,"1169":1,"1170":1,"1171":1,"1311":1,"1986":1,"1987":1,"1988":1,"1989":1,"1990":1,"1991":1},"2":{"633":1,"636":1,"1266":1,"1370":1},"3":{"0":57,"24":5,"26":1,"37":1,"53":3,"54":3,"67":1,"76":1,"79":1,"81":6,"91":1,"107":1,"109":1,"110":1,"113":1,"122":1,"130":1,"135":3,"136":6,"143":1,"160":1,"178":1,"182":1,"193":1,"201":2,"202":3,"226":1,"230":1,"247":1,"255":4,"256":2,"265":1,"293":2,"296":1,"327":1,"328":1,"329":2,"330":1,"331":3,"332":2,"333":3,"335":1,"342":1,"376":1,"381":1,"384":1,"388":1,"411":1,"413":5,"415":1,"416":1,"420":2,"440":1,"443":1,"448":5,"449":1,"450":4,"452":1,"454":1,"469":1,"470":2,"473":1,"477":1,"542":1,"544":3,"545":1,"548":3,"549":4,"550":1,"551":1,"561":1,"564":7,"566":1,"568":1,"571":2,"572":22,"573":1,"574":3,"577":2,"579":2,"580":1,"582":1,"583":2,"584":2,"586":1,"595":1,"608":1,"615":1,"617":1,"618":1,"619":2,"620":1,"624":1,"626":1,"629":1,"631":3,"633":6,"634":1,"635":1,"636":3,"640":2,"648":2,"649":3,"650":2,"651":1,"653":1,"657":2,"658":1,"659":1,"671":1,"674":3,"675":1,"678":3,"682":3,"683":1,"684":1,"686":1,"699":1,"701":1,"707":2,"722":1,"725":4,"726":3,"729":1,"733":1,"735":1,"739":1,"741":3,"742":2,"743":2,"744":1,"745":3,"751":2,"756":2,"759":1,"761":1,"766":1,"776":1,"778":1,"780":1,"781":1,"782":3,"783":1,"784":1,"789":1,"790":2,"792":1,"793":1,"794":1,"795":1,"798":1,"800":1,"817":1,"823":1,"827":3,"834":1,"845":1,"846":4,"847":1,"849":1,"853":1,"854":1,"855":1,"856":1,"858":1,"862":1,"878":1,"879":1,"880":3,"881":3,"884":1,"885":1,"891":3,"892":1,"893":2,"894":1,"896":1,"897":5,"901":1,"904":1,"905":5,"907":3,"914":1,"924":1,"926":1,"931":1,"932":2,"935":1,"939":1,"963":1,"964":2,"965":1,"966":1,"973":1,"980":2,"984":1,"985":1,"987":2,"988":2,"990":2,"992":1,"996":4,"997":1,"998":2,"999":1,"1003":1,"1008":1,"1012":6,"1016":1,"1017":1,"1018":10,"1019":2,"1020":2,"1022":2,"1030":3,"1042":2,"1044":1,"1050":3,"1051":1,"1053":2,"1058":1,"1059":2,"1060":2,"1063":1,"1077":2,"1079":1,"1089":1,"1090":2,"1091":3,"1092":1,"1093":1,"1095":1,"1096":1,"1120":1,"1124":1,"1126":1,"1132":1,"1133":1,"1134":2,"1135":1,"1145":1,"1150":1,"1152":1,"1154":1,"1155":1,"1165":1,"1167":2,"1168":1,"1169":1,"1170":1,"1171":3,"1174":1,"1175":1,"1177":1,"1192":6,"1202":2,"1203":2,"1207":18,"1212":17,"1216":1,"1218":1,"1222":1,"1225":1,"1226":1,"1228":1,"1229":20,"1231":2,"1233":2,"1235":2,"1236":1,"1237":41,"1238":1,"1241":8,"1242":7,"1243":2,"1244":3,"1245":2,"1247":53,"1249":6,"1252":2,"1254":1,"1258":2,"1259":38,"1260":1,"1266":1,"1269":2,"1270":56,"1271":13,"1272":1,"1274":1,"1275":1,"1276":2,"1278":3,"1280":1,"1285":1,"1286":79,"1290":1,"1291":2,"1293":1,"1295":1,"1300":112,"1301":7,"1304":2,"1305":1,"1307":1,"1309":52,"1310":4,"1311":113,"1312":24,"1313":2,"1314":1,"1316":1,"1317":3,"1318":3,"1322":1,"1323":83,"1324":175,"1326":1,"1332":1,"1339":32,"1347":2,"1348":2,"1349":1,"1350":94,"1353":2,"1359":238,"1362":1,"1366":7,"1369":10,"1370":4,"1371":1,"1374":1,"1375":2,"1376":1,"1378":3,"1380":73,"1382":5,"1389":1,"1392":1,"1393":1,"1395":117,"1396":205,"1399":1,"1401":2,"1402":59,"1403":1,"1404":2,"1405":1,"1406":1,"1409":2,"1415":66,"1416":13,"1417":139,"1418":3,"1419":1,"1420":1,"1425":1,"1427":12,"1428":1,"1429":1,"1431":10,"1433":1,"1435":4,"1436":337,"1437":3,"1438":30,"1441":1,"1449":2,"1450":1,"1451":1,"1455":10,"1459":2,"1461":3,"1463":1,"1467":1,"1487":3,"1488":11,"1489":2,"1492":4,"1493":1,"1494":1,"1496":13,"1497":2,"1499":1,"1500":1,"1503":1,"1513":1,"1524":4,"1525":1,"1526":15,"1530":2,"1531":6,"1532":2,"1534":1,"1535":3,"1542":1,"1544":1,"1545":3,"1546":2,"1547":2,"1551":2,"1577":15,"1582":1,"1585":1,"1586":3,"1591":8,"1596":1,"1599":1,"1600":4,"1632":1,"1638":1,"1639":1,"1640":1,"1641":1,"1651":1,"1654":3,"1657":1,"1661":1,"1662":2,"1663":1,"1664":2,"1665":2,"1666":1,"1668":1,"1669":2,"1670":1,"1671":3,"1675":1,"1686":1,"1689":1,"1690":1,"1699":1,"1701":1,"1704":1,"1711":1,"1720":1,"1727":1,"1729":1,"1734":1,"1749":1,"1765":1,"1766":1,"1769":3,"1779":1,"1796":1,"1805":2,"1806":1,"1810":2,"1815":3,"1817":1,"1829":2,"1834":1,"1840":2,"1871":2,"1875":1,"1880":1,"1883":1,"1885":1,"1887":2,"1888":2,"1889":5,"1890":1,"1891":1,"1892":1,"1906":2,"1908":2,"1909":1,"1911":2,"1915":1,"1916":1,"1920":1,"1922":1,"1923":1,"1924":1,"1925":2,"1926":2,"1929":7,"1930":5,"1943":1,"1944":2,"1947":2,"1949":2,"1950":3,"1953":1,"1955":1,"1982":1,"1986":2,"1987":1,"1988":2,"1990":3,"1991":3,"2000":2,"2012":1,"2014":1,"2015":1,"2017":1,"2028":1,"2044":6,"2048":1,"2049":1,"2055":4,"2057":3,"2063":1,"2071":1,"2074":1,"2077":1,"2080":1,"2085":1,"2089":1,"2098":1,"2101":1,"2107":2,"2112":1,"2114":1,"2117":2,"2118":2,"2122":2,"2123":6,"2126":4,"2128":2,"2129":1,"2131":4,"2132":3,"2139":1,"2166":2,"2167":1,"2183":1,"2184":1,"2185":1,"2189":1,"2192":1,"2193":1,"2199":1,"2202":1,"2203":1,"2208":1,"2220":1,"2230":1,"2232":20}}],["contractshapefixtures",{"3":{"1207":24,"1436":35}}],["contracts",{"0":{"1269":1,"1285":1,"1349":1,"2017":1},"1":{"50":1,"51":1,"52":1,"53":1,"54":1,"55":1,"1312":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":1,"1346":1,"1347":1,"1348":1,"1349":1,"1350":1,"1370":1,"1371":1,"1372":1,"1373":1,"1374":1,"1375":1,"1376":1,"1377":1,"1378":1,"1379":1,"1380":1,"1417":1},"2":{"53":1,"95":1,"1270":1,"1370":1,"1378":1,"1404":1,"1500":1,"1649":1,"1703":1,"1792":1,"1793":1,"2017":1,"2028":1},"3":{"0":9,"50":1,"51":1,"53":2,"54":1,"59":1,"80":2,"81":3,"82":1,"95":2,"96":1,"109":6,"136":4,"139":3,"142":1,"150":1,"339":1,"411":1,"413":2,"414":1,"434":1,"493":1,"571":1,"583":2,"587":1,"614":1,"617":1,"649":2,"657":2,"782":3,"846":1,"849":1,"914":1,"953":2,"955":2,"1006":2,"1024":2,"1026":4,"1027":1,"1059":2,"1067":1,"1074":1,"1091":3,"1192":11,"1199":11,"1202":12,"1203":28,"1206":4,"1207":102,"1208":8,"1212":5,"1227":1,"1229":2,"1230":1,"1247":2,"1248":1,"1259":4,"1260":3,"1261":2,"1269":2,"1270":20,"1271":5,"1272":1,"1285":1,"1286":5,"1287":3,"1291":2,"1293":2,"1300":3,"1303":1,"1304":1,"1307":2,"1309":33,"1311":8,"1312":38,"1320":1,"1322":2,"1323":4,"1324":5,"1339":1,"1340":2,"1341":1,"1347":3,"1350":22,"1352":1,"1353":1,"1355":1,"1359":23,"1369":3,"1370":3,"1378":6,"1380":23,"1395":11,"1396":51,"1400":1,"1402":9,"1404":1,"1409":1,"1413":4,"1415":19,"1416":3,"1417":18,"1418":1,"1424":1,"1427":7,"1431":11,"1436":197,"1437":2,"1438":4,"1441":1,"1446":1,"1450":1,"1455":5,"1476":1,"1487":1,"1488":1,"1489":6,"1492":2,"1493":1,"1496":17,"1497":1,"1498":1,"1500":3,"1524":1,"1526":4,"1531":1,"1544":3,"1546":3,"1555":1,"1576":2,"1577":7,"1585":2,"1586":1,"1591":3,"1599":1,"1649":1,"1656":5,"1659":1,"1660":2,"1663":2,"1665":1,"1669":1,"1671":1,"1685":2,"1686":1,"1687":1,"1703":1,"1769":1,"1772":1,"1783":1,"1790":3,"1792":1,"1793":1,"1888":1,"1929":1,"1930":1,"1943":1,"1953":1,"1956":1,"1959":1,"1963":1,"2017":2,"2028":1,"2030":1,"2041":1,"2044":8,"2055":1,"2056":1,"2060":1,"2067":1,"2111":1,"2115":1,"2116":2,"2117":1,"2118":3,"2122":1,"2208":1,"2214":1,"2221":1,"2227":3,"2232":3}}],["controlling",{"3":{"1300":4,"1359":1}}],["controllable",{"3":{"1259":1,"1286":1}}],["controlled",{"3":{"439":1,"1107":1,"1212":1,"1247":3,"1286":2,"1311":2,"1324":1,"1359":5,"1395":1,"1402":2,"1417":3,"1436":4,"1438":1,"1443":1,"1522":1,"1553":1,"1560":1,"1569":1,"1573":1,"1577":1,"1587":1,"1637":1,"1799":1,"1897":1,"2034":1,"2124":2}}],["controllerparameterdescriptor",{"3":{"1311":1}}],["controllerbase",{"3":{"1311":1,"1436":21}}],["controllerfeature",{"3":{"1311":1}}],["controllerconventiontests",{"3":{"1199":1,"1207":2,"1436":5,"1489":1}}],["controllerconventiontestsbase",{"2":{"1654":1},"3":{"1199":2,"1207":2,"1436":5,"1654":1}}],["controllermocks",{"3":{"1199":7,"1207":6}}],["controller",{"0":{"1371":1,"1372":1},"2":{"450":1,"455":1,"727":1},"3":{"0":7,"42":1,"121":1,"124":1,"160":1,"173":1,"186":1,"189":1,"258":1,"299":1,"300":1,"301":1,"302":1,"318":5,"320":3,"321":1,"325":3,"326":1,"329":2,"330":4,"331":1,"332":1,"341":1,"346":1,"350":1,"351":1,"352":2,"391":2,"448":1,"450":1,"454":1,"455":3,"549":1,"571":2,"572":1,"583":1,"585":2,"690":2,"715":1,"723":2,"725":8,"726":1,"727":1,"729":1,"739":2,"740":1,"741":4,"743":5,"744":2,"751":1,"790":1,"838":1,"854":2,"880":1,"900":1,"920":2,"921":2,"922":3,"924":1,"938":1,"1018":1,"1049":1,"1059":2,"1060":1,"1116":2,"1117":1,"1118":2,"1192":1,"1202":1,"1203":1,"1212":1,"1217":1,"1218":1,"1224":2,"1227":1,"1229":4,"1237":1,"1238":1,"1239":1,"1245":2,"1247":6,"1265":1,"1270":6,"1271":2,"1275":1,"1286":1,"1289":1,"1292":1,"1297":2,"1300":24,"1301":1,"1304":1,"1306":1,"1309":18,"1311":110,"1323":4,"1324":2,"1339":1,"1347":2,"1350":12,"1352":1,"1353":1,"1357":1,"1359":121,"1369":1,"1370":1,"1371":5,"1372":3,"1373":3,"1374":3,"1375":2,"1376":3,"1379":1,"1380":122,"1388":1,"1389":1,"1395":8,"1396":42,"1402":19,"1407":2,"1408":1,"1409":2,"1414":1,"1415":47,"1431":1,"1436":87,"1437":3,"1438":11,"1443":1,"1447":1,"1487":3,"1488":2,"1496":6,"1526":3,"1534":1,"1576":1,"1577":2,"1591":2,"1627":1,"1631":3,"1632":3,"1639":2,"1640":2,"1647":2,"1650":3,"1651":4,"1654":1,"1660":1,"1666":1,"1667":2,"1671":1,"1698":1,"1700":1,"1702":3,"1703":1,"1721":1,"1724":1,"1727":4,"1730":1,"1747":2,"1748":1,"1749":3,"1765":2,"1771":1,"1803":1,"1805":2,"1807":1,"1814":2,"1815":1,"1908":1,"1927":3,"1928":1,"1934":1,"1936":1,"1937":1,"1960":1,"1963":1,"1965":1,"1975":1,"1978":2,"1985":1,"1986":1,"1987":1,"1988":2,"1990":1,"1991":1,"1994":2,"1995":2,"1996":2,"1997":1,"2000":1,"2055":1,"2068":1,"2069":1,"2087":1,"2104":1,"2124":2,"2125":1,"2131":1,"2137":2,"2138":1,"2167":1,"2202":1,"2203":1,"2232":1}}],["controllersinheritapicontrollerbase",{"3":{"1436":1}}],["controllersaresealed",{"3":{"1436":1}}],["controllersdonotdependonentityframeworkcore",{"3":{"1436":1}}],["controllersdonotdependoninfrastructure",{"3":{"1436":1}}],["controllersexemptfromapicontrollerbase",{"3":{"1436":2}}],["controllers",{"1":{"327":1,"328":1,"329":1,"330":1,"331":1,"332":1,"333":1,"334":1,"335":1,"336":1,"337":1,"1986":1,"1987":1,"1988":1,"1989":1,"1990":1,"1991":1},"2":{"751":1,"1207":1,"1380":7,"1436":1},"3":{"0":5,"42":1,"59":1,"109":1,"132":1,"136":3,"137":1,"155":1,"160":3,"161":2,"186":1,"187":1,"243":2,"300":2,"306":1,"318":7,"320":1,"324":1,"325":5,"326":1,"327":1,"330":2,"334":1,"337":1,"341":2,"384":1,"391":1,"419":1,"423":1,"426":1,"428":1,"447":1,"448":5,"473":1,"545":3,"550":1,"585":2,"674":1,"690":2,"692":1,"725":4,"733":1,"740":1,"741":3,"743":9,"749":3,"751":4,"773":1,"775":1,"789":1,"790":1,"793":1,"826":2,"837":1,"839":1,"841":1,"854":1,"897":2,"900":1,"905":1,"906":1,"909":1,"921":1,"922":1,"923":2,"1004":3,"1018":1,"1026":2,"1028":1,"1049":1,"1059":4,"1060":1,"1099":1,"1103":1,"1116":1,"1192":1,"1202":1,"1203":74,"1207":376,"1212":3,"1213":2,"1224":1,"1227":1,"1229":5,"1236":1,"1239":2,"1244":1,"1245":1,"1247":11,"1270":6,"1286":1,"1290":1,"1294":1,"1297":1,"1300":63,"1301":2,"1302":1,"1303":1,"1304":1,"1306":1,"1308":3,"1309":28,"1311":127,"1323":15,"1339":2,"1347":2,"1348":1,"1350":11,"1351":1,"1359":173,"1367":1,"1369":1,"1370":2,"1371":10,"1372":13,"1373":4,"1374":3,"1375":1,"1376":1,"1380":141,"1386":1,"1390":1,"1395":10,"1396":52,"1402":31,"1403":1,"1404":1,"1406":1,"1408":1,"1410":1,"1415":55,"1416":1,"1431":3,"1436":69,"1437":2,"1438":21,"1447":1,"1467":2,"1487":2,"1489":2,"1496":11,"1526":3,"1535":3,"1540":1,"1577":3,"1582":2,"1586":1,"1591":5,"1597":1,"1599":2,"1600":1,"1635":1,"1656":1,"1666":4,"1673":1,"1685":1,"1686":1,"1753":1,"1765":4,"1779":1,"1812":1,"1813":2,"1815":2,"1825":1,"1874":3,"1898":1,"1922":1,"1928":1,"1929":2,"1932":2,"1938":2,"1941":1,"1961":1,"1985":1,"1986":2,"1991":1,"1995":11,"1997":1,"2000":3,"2001":1,"2023":1,"2024":1,"2110":1,"2131":2,"2132":1,"2137":1,"2208":1,"2227":1,"2232":1}}],["controls",{"0":{"2000":1},"1":{"1998":1,"1999":1,"2000":1,"2001":1,"2002":1},"2":{"682":1,"2120":1},"3":{"0":1,"227":1,"273":1,"369":1,"370":2,"373":1,"413":1,"592":1,"682":1,"685":1,"825":1,"830":1,"1126":1,"1229":1,"1286":1,"1300":1,"1311":4,"1323":2,"1324":3,"1339":1,"1359":4,"1389":1,"1395":4,"1396":1,"1402":2,"1415":2,"1417":3,"1436":8,"1438":1,"1467":2,"1477":1,"1526":1,"1534":2,"1557":1,"1572":1,"1577":1,"1607":1,"1626":1,"1631":3,"1637":1,"1677":1,"1714":1,"1741":1,"1750":1,"1768":1,"1773":1,"1998":2,"1999":1,"2000":1,"2001":1,"2002":2,"2043":1,"2083":1,"2109":1,"2120":1,"2196":1,"2208":1}}],["control",{"0":{"1804":1},"1":{"106":1,"107":1,"108":1,"109":1,"110":1,"111":1,"112":1,"113":1,"1803":1,"1804":1,"1805":1,"1806":1,"1807":1},"3":{"0":4,"106":1,"174":1,"175":1,"176":1,"177":3,"178":1,"179":1,"185":1,"212":1,"217":1,"221":1,"293":1,"296":1,"343":1,"358":2,"362":1,"376":1,"403":1,"427":2,"443":1,"595":1,"627":1,"699":1,"734":1,"743":1,"745":1,"756":1,"758":2,"765":1,"767":2,"768":1,"770":1,"776":2,"789":1,"876":2,"947":1,"1019":1,"1067":1,"1116":3,"1185":1,"1186":1,"1187":1,"1218":1,"1220":1,"1226":1,"1229":4,"1237":6,"1247":1,"1259":2,"1270":2,"1271":1,"1275":1,"1284":1,"1285":1,"1286":2,"1300":5,"1301":1,"1308":1,"1309":1,"1311":3,"1322":1,"1323":4,"1324":13,"1326":1,"1339":9,"1347":2,"1350":1,"1359":11,"1369":1,"1384":1,"1389":1,"1395":16,"1396":8,"1402":1,"1415":3,"1416":5,"1417":13,"1423":1,"1436":38,"1440":1,"1443":1,"1444":1,"1455":2,"1462":1,"1467":5,"1475":1,"1488":1,"1490":1,"1496":1,"1539":1,"1548":1,"1550":1,"1555":1,"1568":1,"1591":1,"1596":1,"1607":1,"1629":1,"1631":2,"1641":1,"1644":1,"1653":2,"1662":1,"1664":1,"1665":1,"1672":1,"1689":1,"1698":1,"1714":1,"1741":1,"1786":1,"1802":1,"1803":2,"1805":1,"1807":1,"1816":1,"1870":1,"1903":1,"1969":1,"1973":1,"1999":5,"2000":1,"2001":4,"2002":3,"2005":3,"2013":1,"2073":2,"2084":1,"2126":2,"2140":1,"2141":3,"2143":1,"2144":1,"2145":1,"2149":1,"2155":1,"2166":1,"2186":1,"2203":1,"2206":1,"2228":1}}],["contested",{"3":{"714":1,"1436":3}}],["contending",{"3":{"1948":1}}],["contends",{"3":{"1415":1}}],["contenders",{"3":{"1247":1}}],["contended",{"3":{"857":1,"1300":2,"1436":1,"1488":2}}],["contend",{"3":{"519":1,"708":1,"1359":1,"1402":2,"1630":1,"1638":1}}],["contentful",{"3":{"1395":1}}],["contentfilter",{"2":{"1366":1},"3":{"1018":1,"1366":1,"1369":1}}],["contentpage",{"3":{"1324":2,"1417":5}}],["contentpolicyregistrationtests",{"3":{"1199":1,"1207":2,"1577":1}}],["contentpolicyinjectionmode",{"3":{"1091":2,"1199":5,"1203":1,"1207":2,"1424":2,"1427":8}}],["contentpolicy",{"3":{"1021":1,"1089":1,"1091":1,"1093":1,"1366":1,"1424":1,"1427":3,"1526":2,"1577":1}}],["contentpolicyguardrailtests",{"2":{"1435":1},"3":{"1093":1,"1199":1,"1207":2,"1427":1,"1435":2,"1437":1,"1577":1}}],["contentpolicyguardrail",{"2":{"1021":1,"1089":1,"1094":1,"1095":1,"1366":1,"1368":1,"1427":1,"1586":1},"3":{"1018":2,"1021":1,"1089":1,"1091":6,"1093":2,"1094":1,"1095":1,"1198":1,"1199":6,"1203":1,"1207":2,"1366":1,"1368":1,"1424":3,"1427":24,"1496":1,"1526":2,"1577":2,"1585":2,"1586":1}}],["contentpolicysettings",{"2":{"676":1,"677":1,"1424":1,"1427":1},"3":{"674":1,"676":2,"677":1,"1091":5,"1093":1,"1198":1,"1199":5,"1203":1,"1207":2,"1424":3,"1427":17,"1526":1,"1577":1,"1585":1}}],["contentresult",{"3":{"1311":1}}],["contenttypemaxlength",{"2":{"1369":1},"3":{"1350":2,"1369":1}}],["contenttype=",{"3":{"1324":1}}],["contenttype",{"3":{"1286":2,"1323":6,"1324":2,"1350":3,"1369":2,"1415":1,"1417":6,"1436":1}}],["contentdisposition",{"3":{"1286":1,"1323":1}}],["contentsimilaritydto",{"3":{"1199":4,"1203":1,"1207":3,"1347":2,"1350":6,"1359":2,"1380":1}}],["contents",{"2":{"626":1},"3":{"626":1,"715":1,"766":2,"874":1,"1003":1,"1019":1,"1192":3,"1237":2,"1279":1,"1300":1,"1324":4,"1359":1,"1415":1,"1436":1,"1454":2,"1459":1,"1471":1,"1473":1,"1475":2,"1492":1,"1496":1,"1768":1}}],["contentsecuritypolicyreportonly",{"3":{"1339":1}}],["contentsecuritypolicy",{"2":{"214":1,"216":1,"1577":1,"2149":1},"3":{"214":1,"216":1,"1339":3,"1577":2,"2149":1}}],["contention",{"3":{"558":1,"863":2,"996":1,"1150":1,"1153":1,"1278":1,"1286":2,"1359":2,"1402":2,"1436":1,"1456":1,"1545":1,"1630":1,"1635":1,"1638":7,"1688":1,"1909":1}}],["contentmanagement",{"2":{"184":1,"1963":1},"3":{"184":1,"186":1,"1116":1,"1348":2,"1350":4,"1377":1,"1380":7,"1631":2,"1963":1}}],["contenteditors",{"3":{"1359":2,"1627":1}}],["contenteditor",{"2":{"187":1,"386":1,"388":1,"1348":1,"1621":1,"1626":1,"1630":1,"1633":1,"1635":1,"1637":1,"1638":1,"1960":1},"3":{"0":1,"186":1,"187":1,"386":1,"388":1,"1116":1,"1300":1,"1307":1,"1348":2,"1350":4,"1359":5,"1373":2,"1377":1,"1380":7,"1395":2,"1396":2,"1405":1,"1409":1,"1415":9,"1496":2,"1526":1,"1621":4,"1626":1,"1627":2,"1630":4,"1631":6,"1632":1,"1633":1,"1635":2,"1637":2,"1638":5,"1640":2,"1922":1,"1960":2,"1963":1}}],["content",{"0":{"1359":1,"1424":1},"1":{"738":1,"739":1,"740":1,"741":1,"742":1,"743":1,"744":1,"745":1},"2":{"443":1,"1285":1},"3":{"0":7,"90":1,"103":2,"139":1,"142":1,"157":1,"173":1,"175":1,"179":1,"185":1,"213":3,"214":3,"219":1,"384":1,"440":2,"443":3,"454":1,"478":1,"497":1,"529":1,"572":1,"574":1,"575":1,"633":1,"651":1,"664":1,"688":1,"693":1,"720":1,"725":3,"738":1,"740":2,"741":3,"747":1,"749":2,"757":1,"764":1,"774":1,"809":1,"813":1,"832":1,"869":1,"909":1,"1010":1,"1011":1,"1012":1,"1016":1,"1089":3,"1091":3,"1093":3,"1095":2,"1114":1,"1115":1,"1116":6,"1118":2,"1186":2,"1192":1,"1212":1,"1237":2,"1259":1,"1270":1,"1283":1,"1285":5,"1286":28,"1300":3,"1301":1,"1306":1,"1309":9,"1310":1,"1311":19,"1323":12,"1324":28,"1336":3,"1339":9,"1347":1,"1348":1,"1350":19,"1357":1,"1359":40,"1366":1,"1369":1,"1376":2,"1380":14,"1390":1,"1393":1,"1394":1,"1395":26,"1396":9,"1399":1,"1402":17,"1411":2,"1415":28,"1416":11,"1417":6,"1418":2,"1423":2,"1424":4,"1427":18,"1432":2,"1436":35,"1437":1,"1438":4,"1443":2,"1455":2,"1458":1,"1467":1,"1487":1,"1488":1,"1489":1,"1496":5,"1526":2,"1531":1,"1532":1,"1553":3,"1559":2,"1563":3,"1572":1,"1577":2,"1586":1,"1600":1,"1607":1,"1630":2,"1631":4,"1632":1,"1637":2,"1638":4,"1639":2,"1640":3,"1641":2,"1644":1,"1646":1,"1702":1,"1705":1,"1713":2,"1731":1,"1741":1,"1749":1,"1764":1,"1765":1,"1781":1,"1825":1,"1871":1,"1909":1,"1922":1,"1960":1,"1961":1,"1964":1,"1965":1,"2000":3,"2011":3,"2055":1,"2075":2,"2125":3,"2126":5,"2128":2,"2147":2,"2148":2,"2149":3,"2150":1,"2153":1,"2175":1,"2179":1,"2180":1,"2232":3,"2240":1}}],["contextless",{"3":{"2074":1}}],["contextual",{"3":{"1395":1}}],["contextkey",{"3":{"1286":1}}],["contexts",{"0":{"1514":1},"3":{"0":1,"166":1,"591":2,"593":1,"618":1,"703":1,"988":1,"1035":2,"1175":1,"1226":1,"1237":1,"1259":1,"1278":1,"1283":1,"1286":15,"1324":1,"1359":2,"1396":1,"1415":2,"1417":1,"1436":2,"1443":1,"1453":1,"1486":1,"1487":1,"1490":1,"1492":1,"1496":1,"1526":1,"1541":1,"1544":1,"1545":1,"1577":6,"1582":2,"1583":1,"1584":1,"1638":2,"1639":1,"1660":1,"1664":1,"1697":1,"1700":1,"1719":1,"1800":1,"1811":1,"1849":1,"2010":1,"2110":1,"2113":1,"2115":1,"2239":1}}],["context",{"0":{"4":1,"10":1,"16":1,"30":1,"38":1,"46":1,"52":1,"58":1,"67":1,"77":1,"85":1,"97":1,"108":1,"116":1,"131":1,"146":1,"156":1,"165":1,"174":1,"185":1,"194":1,"206":1,"213":1,"224":1,"234":1,"242":1,"264":1,"272":1,"280":1,"290":1,"299":1,"309":1,"317":1,"329":1,"340":1,"349":1,"358":1,"369":1,"379":1,"387":1,"396":1,"412":1,"419":1,"433":1,"439":1,"447":1,"458":1,"469":1,"481":1,"498":1,"506":1,"517":1,"527":1,"535":1,"544":1,"555":1,"563":1,"571":1,"582":1,"590":1,"598":1,"608":1,"617":1,"625":1,"632":1,"639":1,"648":1,"656":1,"664":1,"673":1,"681":1,"689":1,"697":1,"706":1,"714":1,"724":1,"732":1,"740":1,"748":1,"756":1,"765":1,"773":1,"781":1,"789":1,"798":1,"809":1,"818":1,"826":1,"837":1,"845":1,"853":1,"860":1,"868":1,"880":1,"888":1,"896":1,"904":1,"913":1,"921":1,"930":1,"938":1,"945":1,"953":1,"963":1,"971":1,"979":1,"987":1,"995":1,"1003":1,"1011":1,"1017":1,"1025":1,"1033":1,"1041":1,"1049":1,"1058":1,"1066":1,"1074":1,"1082":1,"1090":1,"1099":1,"1107":1,"1115":1,"1123":1,"1132":1,"1140":1,"1150":1,"1160":1,"1167":1,"1174":1,"1183":1,"1195":1,"1273":1,"1318":1,"1341":1,"1363":1,"1404":1,"1405":1,"1482":1,"1503":1,"1849":1},"2":{"135":1,"459":1,"740":1,"1175":1,"1233":1,"1300":1,"1464":1},"3":{"0":24,"1":1,"15":2,"26":2,"27":3,"45":3,"47":2,"62":1,"135":2,"145":1,"150":1,"160":1,"164":1,"165":1,"166":2,"167":1,"193":1,"200":1,"201":1,"202":5,"229":1,"233":1,"265":1,"279":1,"296":1,"298":1,"302":2,"303":4,"340":1,"346":1,"359":3,"365":2,"366":1,"375":1,"392":1,"393":1,"395":1,"397":1,"400":1,"423":1,"459":1,"468":1,"470":1,"477":1,"481":5,"482":2,"483":3,"485":1,"490":2,"526":1,"539":1,"545":1,"555":1,"564":1,"578":1,"582":1,"587":1,"591":1,"593":1,"647":1,"660":1,"669":1,"696":1,"697":1,"698":12,"699":2,"700":2,"702":7,"707":2,"715":1,"716":2,"720":1,"731":1,"740":1,"748":1,"761":2,"794":1,"797":1,"804":1,"806":1,"825":1,"826":1,"827":1,"833":1,"836":1,"875":1,"877":2,"889":3,"893":1,"898":1,"905":5,"906":1,"910":2,"920":1,"926":11,"931":1,"962":1,"967":3,"988":9,"990":2,"991":1,"1007":1,"1019":1,"1021":1,"1032":1,"1033":2,"1034":4,"1035":1,"1036":3,"1038":2,"1040":1,"1042":6,"1050":4,"1053":1,"1054":1,"1059":2,"1061":1,"1062":1,"1083":3,"1084":2,"1086":1,"1087":1,"1089":1,"1093":1,"1103":1,"1107":1,"1110":1,"1128":1,"1133":1,"1137":2,"1150":1,"1173":1,"1174":3,"1175":5,"1177":1,"1180":1,"1192":2,"1201":1,"1202":4,"1203":11,"1207":32,"1212":4,"1219":1,"1229":5,"1233":1,"1237":4,"1247":1,"1249":1,"1252":2,"1253":3,"1254":1,"1256":1,"1257":2,"1258":1,"1259":50,"1270":33,"1271":1,"1273":9,"1274":8,"1275":2,"1276":4,"1277":1,"1278":12,"1280":1,"1281":2,"1283":1,"1284":1,"1286":275,"1296":2,"1297":1,"1300":49,"1309":17,"1310":2,"1311":33,"1312":8,"1313":1,"1316":1,"1317":1,"1318":5,"1319":1,"1323":35,"1324":23,"1326":1,"1333":1,"1336":1,"1339":31,"1340":1,"1341":1,"1343":1,"1345":2,"1350":14,"1357":1,"1359":64,"1363":4,"1369":11,"1370":1,"1378":1,"1380":11,"1395":3,"1396":24,"1397":1,"1402":5,"1403":1,"1404":2,"1405":1,"1406":2,"1410":1,"1412":1,"1415":31,"1417":7,"1427":1,"1433":1,"1436":38,"1437":1,"1438":7,"1441":1,"1453":2,"1455":1,"1456":1,"1457":1,"1464":2,"1471":1,"1488":1,"1489":1,"1496":3,"1510":1,"1526":2,"1534":2,"1541":1,"1543":1,"1556":1,"1561":1,"1571":1,"1575":1,"1577":6,"1581":1,"1586":1,"1607":1,"1629":1,"1630":8,"1631":3,"1632":2,"1634":7,"1635":2,"1638":11,"1639":9,"1641":1,"1651":4,"1652":3,"1664":2,"1665":2,"1666":1,"1670":1,"1673":1,"1697":3,"1700":1,"1719":2,"1720":1,"1727":1,"1728":2,"1741":1,"1751":1,"1790":1,"1800":1,"1801":1,"1805":1,"1810":2,"1832":1,"1840":2,"1849":7,"1851":6,"1853":1,"1869":1,"1923":1,"1930":1,"1931":2,"1941":1,"2051":2,"2053":1,"2113":1,"2119":1,"2138":1,"2142":3,"2157":1,"2165":1,"2169":1,"2179":1,"2185":2,"2186":1,"2193":1,"2199":2,"2232":5,"2239":1}}],["contiguous",{"3":{"1312":2}}],["contiguously",{"3":{"0":1}}],["continuing",{"3":{"1324":1,"1339":2,"1396":1,"1447":1}}],["continuity",{"0":{"1566":1},"1":{"1706":1,"1707":1,"1708":1,"1709":1,"1710":1},"2":{"1330":1,"1367":1},"3":{"399":1,"1216":1,"1233":1,"1237":1,"1247":2,"1258":1,"1259":15,"1263":1,"1270":7,"1286":18,"1300":5,"1301":1,"1304":1,"1307":1,"1309":10,"1311":10,"1312":7,"1323":15,"1324":24,"1330":2,"1334":1,"1338":1,"1339":25,"1350":2,"1352":1,"1357":1,"1359":17,"1365":1,"1367":1,"1369":2,"1380":7,"1395":25,"1396":35,"1402":8,"1415":15,"1416":2,"1417":20,"1427":1,"1435":1,"1436":11,"1441":2,"1450":1,"1455":4,"1458":1,"1461":1,"1467":2,"1469":1,"1474":1,"1475":1,"1483":1,"1499":1,"1526":1,"1535":1,"1575":1,"1577":1,"1581":1,"1582":1,"1583":1,"1591":1,"1600":1,"1602":1,"1674":1,"1706":1,"1796":1,"2029":1,"2162":1}}],["continuation",{"3":{"1300":1,"1312":6,"1417":1,"1436":1,"2186":1}}],["continuations",{"3":{"482":1,"1805":1}}],["continuous",{"3":{"68":1,"1300":1,"1417":1,"1452":1,"1453":1,"1455":1,"1709":1}}],["continuously",{"3":{"0":1,"62":1,"390":1,"501":1,"1153":1,"1286":1,"1324":1,"1436":1,"1475":1,"1984":1,"2225":1}}],["continued",{"3":{"1417":1,"1455":1}}],["continueuseractivity",{"3":{"1416":2}}],["continue",{"2":{"889":1},"3":{"0":2,"375":1,"491":1,"583":1,"618":1,"624":1,"626":3,"633":1,"638":1,"640":1,"642":1,"757":1,"873":1,"876":2,"877":2,"889":1,"913":1,"914":1,"917":1,"1034":1,"1037":1,"1069":2,"1075":1,"1259":3,"1270":1,"1286":3,"1300":1,"1311":2,"1328":1,"1334":1,"1339":6,"1359":1,"1369":4,"1390":1,"1395":5,"1396":1,"1438":1,"1441":1,"1445":2,"1446":1,"1449":1,"1450":1,"1453":1,"1455":5,"1456":1,"1459":1,"1460":1,"1465":1,"1467":1,"1490":4,"1492":4,"1526":5,"1534":1,"1577":7,"1591":1,"1599":1,"1600":1,"1750":1,"1773":1,"2194":1}}],["continues",{"3":{"0":1,"47":1,"141":1,"516":1,"897":1,"1259":2,"1286":1,"1300":4,"1311":2,"1323":4,"1339":1,"1359":1,"1396":1,"1445":1}}],["dhl",{"3":{"1749":1,"1765":1}}],["d1cae3ed",{"3":{"1591":1}}],["d5297706",{"3":{"1496":1}}],["d5fd0e9",{"2":{"424":1},"3":{"424":1}}],["d4824b36",{"3":{"1496":1}}],["d4",{"3":{"1436":1}}],["dwarf",{"3":{"1467":1}}],["dwarfs",{"3":{"1292":1}}],["dwelling",{"3":{"1436":1}}],["dp",{"3":{"2224":1}}],["dpi",{"3":{"1416":1}}],["dpapi",{"3":{"1416":1,"1417":2}}],["dcp",{"3":{"1311":3,"1490":1}}],["d841f798",{"3":{"1496":1}}],["d8",{"3":{"1286":1,"2126":1}}],["dst",{"3":{"1300":1,"1350":2,"1356":1,"1359":3,"1369":1,"1396":2}}],["dsl",{"3":{"1241":1,"1660":1,"1670":1,"1989":2}}],["dsar",{"1":{"722":1,"723":1,"724":1,"725":1,"726":1,"727":1,"728":1,"729":1},"3":{"0":1,"696":1,"722":1,"727":1,"1212":2,"1300":1,"1311":2,"1438":2,"1575":1,"1577":2,"1582":1,"1672":1,"2232":1}}],["dtend",{"3":{"1300":1}}],["dtstart",{"3":{"1300":1}}],["dtstamp",{"2":{"1356":1},"3":{"1300":4,"1356":1}}],["dt",{"3":{"1247":2}}],["dtu",{"3":{"767":1,"1446":1,"1457":1,"1467":1,"1473":1,"1478":1,"1483":1,"1484":1}}],["dtoorlookupsuffix",{"3":{"1436":1}}],["dtoprojector",{"3":{"1247":1,"1270":1}}],["dtomapper",{"2":{"331":1},"3":{"331":1,"1193":1,"1244":1,"1247":2,"1270":1,"1309":1,"1311":1,"1359":16,"1396":1,"1402":4,"1497":1,"1651":1}}],["dtotoentitypropertymap",{"2":{"331":1,"332":1,"333":1,"335":1,"1244":1,"1988":1,"1990":1},"3":{"331":1,"332":1,"333":1,"335":1,"1244":1,"1247":14,"1359":7,"1988":1,"1990":1}}],["dtosandrequests",{"3":{"1436":1}}],["dtos",{"2":{"1207":1,"1359":2,"1396":1,"1402":1},"3":{"4":1,"331":1,"340":1,"341":2,"549":1,"558":1,"657":2,"674":1,"799":1,"1026":1,"1049":1,"1168":1,"1192":2,"1202":2,"1203":30,"1207":98,"1244":1,"1245":1,"1247":3,"1270":5,"1271":1,"1286":2,"1289":1,"1300":8,"1309":13,"1311":15,"1312":2,"1323":1,"1324":2,"1341":2,"1347":1,"1350":35,"1353":4,"1359":67,"1374":2,"1380":3,"1395":4,"1396":18,"1401":1,"1402":13,"1404":2,"1415":6,"1431":1,"1436":15,"1438":3,"1455":1,"1489":2,"1496":2,"1526":2,"1532":1,"1542":1,"1546":1,"1630":2,"1631":1,"1639":1,"1650":1,"1671":1,"1857":1,"1871":1,"1929":1,"1988":1,"2071":1,"2078":1,"2142":1,"2227":1,"2232":1}}],["dto",{"1":{"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"1311":1,"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1956":1,"1957":1,"1958":1,"1959":1},"2":{"1955":1},"3":{"0":8,"2":1,"3":3,"5":1,"7":1,"42":1,"330":1,"331":3,"332":1,"333":2,"334":1,"335":1,"341":2,"342":1,"343":1,"360":1,"473":1,"543":1,"545":1,"549":1,"657":1,"690":1,"725":1,"741":1,"742":1,"799":1,"881":5,"882":1,"883":3,"922":1,"923":1,"926":9,"927":1,"1026":1,"1028":1,"1049":1,"1050":3,"1059":2,"1060":1,"1133":1,"1168":4,"1170":1,"1192":1,"1202":2,"1203":2,"1212":3,"1213":1,"1227":1,"1229":2,"1237":1,"1238":1,"1241":4,"1242":2,"1243":1,"1244":4,"1247":25,"1260":3,"1262":2,"1263":1,"1265":5,"1266":1,"1269":1,"1270":35,"1271":6,"1286":7,"1297":1,"1300":20,"1304":1,"1309":20,"1310":1,"1311":50,"1312":2,"1316":1,"1323":12,"1324":28,"1340":1,"1344":3,"1347":1,"1348":1,"1350":91,"1352":1,"1353":1,"1354":2,"1356":1,"1359":168,"1370":1,"1375":1,"1380":4,"1382":3,"1384":2,"1389":1,"1395":70,"1396":49,"1398":2,"1401":2,"1402":33,"1409":1,"1410":1,"1415":36,"1416":3,"1436":13,"1437":4,"1438":6,"1487":2,"1496":7,"1526":3,"1542":1,"1577":3,"1591":1,"1639":1,"1651":1,"1662":2,"1666":1,"1683":1,"1684":1,"1686":1,"1702":2,"1720":1,"1727":4,"1728":1,"1749":1,"1750":1,"1779":1,"1810":2,"1830":1,"1868":1,"1874":2,"1883":2,"1933":2,"1950":1,"1951":2,"1952":1,"1953":6,"1954":1,"1955":3,"1956":2,"1957":2,"1959":1,"1988":2,"1990":2,"1991":4,"2052":1,"2080":1,"2095":1,"2208":2,"2230":1,"2232":1}}],["dml",{"0":{"719":1},"3":{"719":1,"1436":1,"1467":1,"1496":1}}],["dd0b6a9",{"3":{"1496":1}}],["ddl",{"3":{"1467":1,"1477":1,"1719":1,"1727":1}}],["dd",{"3":{"305":1,"1018":1,"1300":2,"1350":2,"1359":1,"1369":2,"1436":6,"1492":1,"1609":1,"1710":1,"1743":1,"2138":1}}],["ddd",{"0":{"1638":1},"3":{"0":1,"469":1,"781":1,"1049":1,"1117":1,"1201":1,"1202":1,"1203":1,"1211":1,"1212":1,"1230":1,"1231":2,"1237":10,"1247":3,"1259":1,"1270":4,"1271":1,"1286":4,"1300":5,"1309":5,"1310":1,"1311":2,"1323":3,"1340":1,"1350":9,"1359":28,"1369":6,"1380":3,"1396":9,"1402":13,"1415":5,"1431":1,"1436":29,"1489":3,"1506":1,"1523":1,"1536":1,"1577":2,"1581":1,"1582":1,"1583":1,"1588":1,"1590":1,"1637":1,"1638":1,"1639":2,"1640":1,"1646":1,"1659":1,"1662":2,"1692":1,"1763":1,"1780":1,"1781":1,"1810":1,"1811":2,"2048":1,"2090":1,"2114":1,"2115":1,"2204":1,"2213":1,"2216":1,"2221":1,"2227":1,"2243":1,"2245":1}}],["d32",{"3":{"91":1}}],["dlls",{"3":{"1436":1}}],["dll",{"2":{"1445":6},"3":{"0":1,"869":1,"939":1,"1436":1,"1445":7}}],["d",{"3":{"0":1,"67":1,"91":1,"373":1,"585":1,"1126":1,"1237":1,"1300":3,"1307":1,"1323":1,"1324":4,"1350":1,"1359":30,"1375":1,"1377":1,"1380":1,"1396":1,"1417":1,"1427":1,"1436":9,"1454":1,"1526":1,"1566":1,"1577":1,"1591":1,"1611":1,"1632":1,"1715":1,"1765":1,"1935":1}}],["dynamiccode",{"3":{"1436":2}}],["dynamiccomponent",{"2":{"1324":1},"3":{"649":1,"1324":1}}],["dynamicerrors",{"3":{"1199":2,"1207":1,"1436":7}}],["dynamicqueryconfig",{"2":{"1247":1,"1989":1},"3":{"1199":9,"1203":1,"1207":2,"1241":4,"1247":19,"1496":2,"1989":1}}],["dynamically",{"3":{"329":1,"545":1,"1018":1,"1247":2,"1324":2,"1436":1,"1441":1,"1455":1,"1458":1,"1486":1,"1489":1,"1750":1,"1773":1,"1990":1}}],["dynamic",{"0":{"1241":1},"1":{"327":1,"328":1,"329":1,"330":1,"331":1,"332":1,"333":1,"334":1,"335":1,"336":1,"337":1,"1986":1,"1987":1,"1988":1,"1989":1,"1990":1,"1991":1},"3":{"0":1,"95":1,"215":1,"235":1,"327":1,"329":1,"330":2,"331":2,"332":1,"335":1,"545":1,"548":1,"549":1,"591":1,"593":1,"740":1,"766":1,"880":1,"881":1,"1192":1,"1202":1,"1203":1,"1212":1,"1213":2,"1238":3,"1241":4,"1242":1,"1243":2,"1245":1,"1247":39,"1259":1,"1286":1,"1310":1,"1311":3,"1324":3,"1331":1,"1336":1,"1339":10,"1342":1,"1350":1,"1359":3,"1362":1,"1369":1,"1371":1,"1380":1,"1395":3,"1396":8,"1402":1,"1407":1,"1416":1,"1436":6,"1437":1,"1438":4,"1441":3,"1444":1,"1496":3,"1585":1,"1666":1,"1720":1,"1779":1,"1815":3,"1816":2,"1817":1,"1928":1,"1986":2,"1988":2,"1989":1,"1990":1,"1991":2,"2011":1,"2153":3,"2208":1,"2220":1}}],["dade",{"3":{"2220":1}}],["dast",{"3":{"2219":1}}],["dashed",{"3":{"1758":1}}],["dashes",{"3":{"1286":1}}],["dash",{"3":{"1335":1,"1339":1,"1395":2,"1438":1,"1467":2}}],["dashboard",{"2":{"1449":1,"1460":1},"3":{"0":3,"126":1,"390":1,"397":2,"407":1,"556":1,"618":1,"707":1,"709":1,"792":1,"861":1,"898":1,"913":3,"914":2,"915":2,"916":1,"917":1,"918":1,"1017":2,"1018":2,"1020":1,"1043":2,"1067":1,"1091":1,"1092":2,"1116":1,"1212":1,"1213":1,"1259":1,"1270":3,"1311":1,"1312":1,"1323":1,"1333":1,"1339":3,"1340":1,"1347":4,"1350":20,"1359":22,"1366":1,"1376":1,"1380":9,"1385":3,"1388":1,"1389":1,"1395":49,"1396":13,"1402":1,"1425":2,"1427":3,"1435":1,"1436":1,"1438":5,"1439":1,"1440":2,"1441":1,"1443":2,"1446":1,"1449":1,"1460":1,"1476":6,"1483":1,"1488":1,"1490":2,"1524":1,"1526":1,"1532":1,"1608":1,"1621":1,"1624":1,"1625":1,"1626":2,"1627":4,"1632":3,"1640":2,"1653":4,"1657":2,"1689":1,"1698":2,"1700":2,"1704":1,"1724":1,"1755":1,"1911":1,"1948":1,"2003":3,"2005":3,"2010":6,"2014":2,"2047":1,"2154":2,"2156":2,"2157":1,"2158":1,"2160":1,"2177":2,"2229":1}}],["dashboards",{"1":{"911":1,"912":1,"913":1,"914":1,"915":1,"916":1,"917":1,"918":1},"3":{"0":1,"402":1,"409":1,"911":1,"1212":1,"1301":1,"1317":1,"1323":1,"1324":1,"1332":1,"1333":1,"1339":1,"1436":3,"1438":2,"1443":2,"1467":1,"1482":1,"1487":1,"1550":1,"1570":1,"1574":1,"1577":2,"1578":1,"1582":1,"1583":1,"1677":1,"2007":1,"2010":1,"2232":1}}],["damaging",{"3":{"1900":1}}],["damage",{"3":{"237":1,"293":1,"818":1,"1324":2,"1339":1,"1359":1,"1396":1,"1980":1}}],["dangling",{"3":{"1323":1,"1359":2,"1383":1,"1395":3,"1415":1,"1639":1,"1852":1}}],["danger",{"3":{"1286":1}}],["dangerously",{"3":{"1301":1}}],["dangerousacceptanyservercertificatevalidator",{"3":{"1286":1}}],["dangerous",{"3":{"932":1,"1107":1,"1132":1,"1133":1,"1300":1,"1359":3,"1465":1,"1467":1,"1473":1,"1489":1,"1840":1,"1919":1,"2044":1,"2194":1}}],["dance",{"3":{"1109":1,"1286":2,"1311":1,"1324":1,"1343":1,"1352":1,"1396":3,"1398":1,"1402":3,"1415":1,"1973":1,"1979":1,"2127":1}}],["daemon",{"3":{"625":1,"642":1,"1339":1,"1430":1,"1436":5,"1438":1,"1449":1,"1455":1,"1460":1,"1486":2,"1490":2}}],["dailyquotagb",{"2":{"719":1,"1465":1,"1469":1},"3":{"719":1,"1465":3,"1467":2,"1469":1}}],["daily",{"3":{"0":1,"395":1,"401":1,"403":2,"709":1,"719":2,"1270":1,"1286":2,"1333":1,"1339":1,"1442":1,"1443":1,"1445":1,"1451":1,"1455":3,"1461":1,"1465":6,"1467":6,"1469":1,"1496":1,"1591":1,"1860":1,"1929":1}}],["dateless",{"3":{"1436":1}}],["dateformat",{"3":{"1395":1}}],["dateonly",{"3":{"1234":1,"1237":6,"1300":3,"1350":4,"1359":8,"1369":1,"1395":4,"1436":11,"1630":3,"1631":1,"1640":4}}],["dateoccurred",{"2":{"77":1,"1160":1,"1162":1,"1163":1,"1585":1,"1888":1,"1889":1},"3":{"0":2,"77":1,"782":1,"846":1,"1160":1,"1161":1,"1162":1,"1163":1,"1212":2,"1247":3,"1249":2,"1259":8,"1269":1,"1270":2,"1350":2,"1396":2,"1436":6,"1438":1,"1496":1,"1577":1,"1585":1,"1888":1,"1889":1}}],["dates",{"3":{"86":2,"265":1,"490":1,"493":1,"656":1,"1073":1,"1074":2,"1237":1,"1308":1,"1309":3,"1350":1,"1359":5,"1369":3,"1387":1,"1395":6,"1396":5,"1415":1,"1438":1,"1564":1,"1591":1,"1630":1,"1631":1,"1764":1,"1776":1}}],["datetimetoday",{"3":{"1436":2}}],["datetimestyles",{"3":{"1247":2,"1300":1,"1309":3,"1312":1,"1396":2}}],["datetimekind",{"3":{"1237":1,"1247":1,"1286":6,"1309":2,"1312":3,"1359":1,"1369":2,"1396":4,"1415":2,"1436":1,"1438":4}}],["datetimefilterstrategytests",{"3":{"1199":1,"1207":3}}],["datetimefilterstrategy",{"2":{"1241":1,"1815":1},"3":{"1199":3,"1203":1,"1207":2,"1241":5,"1247":9,"1815":1}}],["datetimeoffset",{"2":{"1161":1},"3":{"1161":2,"1212":1,"1286":3,"1300":6,"1311":1,"1323":4,"1324":2,"1339":3,"1350":4,"1359":4,"1380":2,"1395":2,"1396":18,"1415":3,"1417":4,"1431":1,"1436":12,"1631":1,"1639":1}}],["datetime",{"2":{"708":1,"1033":1,"1160":2,"2052":1},"3":{"0":1,"330":1,"708":1,"1033":1,"1034":2,"1160":3,"1161":5,"1212":1,"1237":6,"1247":7,"1259":6,"1273":1,"1286":17,"1300":11,"1309":8,"1311":1,"1312":6,"1323":12,"1324":4,"1342":2,"1350":21,"1359":12,"1362":1,"1369":1,"1378":1,"1380":4,"1395":11,"1396":21,"1402":3,"1415":13,"1416":1,"1431":2,"1436":42,"1438":1,"1453":1,"1490":1,"1496":4,"1526":1,"1577":2,"1591":1,"1631":2,"1635":2,"1639":2,"1640":4,"1641":1,"1937":1,"1988":1,"2052":1}}],["datetimerangetests",{"3":{"1199":1,"1207":2,"1237":1}}],["datetimerange",{"2":{"659":1,"660":1},"3":{"0":1,"657":9,"659":3,"660":1,"1199":3,"1203":1,"1207":2,"1230":1,"1234":4,"1237":10,"1630":1,"1631":1,"1635":1,"1662":1}}],["datediff",{"3":{"1362":1,"1369":2}}],["dated",{"3":{"0":1,"104":1,"138":2,"139":3,"140":1,"141":1,"142":1,"371":1,"409":1,"429":1,"486":1,"767":1,"869":1,"1018":1,"1021":1,"1042":1,"1045":1,"1075":1,"1077":1,"1090":1,"1212":1,"1324":1,"1339":1,"1350":1,"1359":1,"1366":1,"1369":1,"1417":2,"1436":2,"1438":1,"1477":1,"1492":1,"1496":1,"1526":2,"1530":1,"1531":1,"1533":1,"1535":1,"1577":1,"1589":1,"1591":3,"1595":1,"1596":2,"1597":1,"1598":1,"1605":1,"1610":1,"1672":1,"1739":1,"1744":1,"2185":1}}],["daterangetests",{"3":{"1199":1,"1207":2}}],["daterange",{"2":{"659":1,"660":1},"3":{"0":1,"657":9,"659":3,"660":1,"1199":3,"1203":1,"1207":2,"1230":1,"1234":7,"1237":15,"1311":1,"1350":1,"1359":1,"1438":1,"1496":1,"1577":1,"1630":1,"1631":2,"1635":2,"1662":1,"1810":1}}],["date",{"3":{"0":2,"1":3,"136":2,"138":1,"139":1,"252":1,"254":1,"259":1,"298":2,"305":7,"349":1,"492":1,"540":2,"624":1,"627":1,"717":1,"825":1,"867":1,"1018":1,"1220":1,"1237":1,"1247":1,"1286":2,"1300":12,"1309":3,"1311":1,"1323":1,"1347":1,"1350":7,"1352":3,"1359":20,"1362":1,"1364":1,"1369":4,"1380":1,"1393":3,"1395":33,"1396":9,"1415":3,"1431":2,"1436":24,"1453":1,"1455":2,"1467":1,"1474":1,"1475":1,"1477":1,"1496":1,"1526":1,"1564":1,"1591":1,"1609":1,"1630":1,"1631":8,"1635":3,"1639":1,"1710":1,"1743":1,"1764":2,"1765":1,"1767":1,"1769":2,"1814":1,"2003":1,"2085":1,"2138":4,"2232":1}}],["datacimex",{"3":{"2220":1}}],["datacontractattribute",{"3":{"1300":1}}],["datacontractserializer",{"2":{"657":1},"3":{"657":1}}],["datacontract",{"2":{"659":1,"963":1},"3":{"0":2,"657":1,"659":1,"725":1,"963":1,"964":2,"1227":1,"1229":7,"1234":1,"1237":5,"1300":1,"1311":1,"1805":1}}],["dataresidency",{"3":{"1489":1}}],["dataresidencytests",{"2":{"972":1,"1525":1},"3":{"971":1,"972":5,"974":2,"1199":1,"1207":2,"1436":9,"1489":1,"1493":1,"1525":1,"1526":2,"1535":1,"1591":1}}],["dataresidencytestsbasetests",{"3":{"972":2,"975":1,"1199":1,"1207":3,"1436":9,"1577":1,"1582":1}}],["dataresidencytestsbase",{"2":{"972":1,"975":1,"1575":1,"2042":1,"2043":1},"3":{"0":1,"972":2,"973":1,"974":2,"975":2,"1199":3,"1207":2,"1212":1,"1431":1,"1436":16,"1496":2,"1575":1,"1577":1,"1582":1,"1654":1,"2042":1,"2043":1}}],["dataannotation",{"3":{"1395":1}}],["dataannotationsmodelvalidator",{"2":{"1384":1},"3":{"1199":27,"1203":1,"1207":2,"1324":24,"1384":1,"1395":19,"1396":4}}],["dataannotations",{"3":{"117":1,"128":2,"819":1,"1259":1,"1271":1,"1286":3,"1300":9,"1309":3,"1311":2,"1320":1,"1323":6,"1324":24,"1339":3,"1384":1,"1395":29,"1396":3,"1427":2,"1436":2,"1591":2,"1596":2,"1597":1,"1600":1}}],["datagrid",{"3":{"1324":3,"1395":1,"1396":2,"1524":2,"1526":1,"1530":1,"1531":2}}],["datagridlistpagebasetests",{"2":{"2079":1},"3":{"1199":1,"1207":5,"1324":1,"1436":1,"2079":1}}],["datagridlistpagebase",{"0":{"2072":1},"2":{"151":1,"556":1,"558":1,"559":1,"879":1,"884":1,"885":1,"1383":1,"1586":1,"1597":1,"1669":1,"1715":1,"2072":1,"2073":1,"2076":1,"2079":1,"2080":1},"3":{"0":2,"151":1,"556":14,"558":2,"559":4,"879":1,"881":4,"883":1,"884":2,"885":1,"1199":12,"1203":1,"1207":3,"1212":1,"1324":50,"1383":1,"1395":18,"1396":2,"1415":2,"1436":4,"1524":1,"1526":7,"1531":1,"1532":1,"1577":6,"1586":3,"1591":1,"1596":1,"1597":1,"1669":1,"1715":1,"2072":1,"2073":1,"2076":1,"2079":2,"2080":1}}],["dataintegritytests",{"3":{"1199":1,"1207":2,"1438":1}}],["datapoints",{"3":{"914":1,"1443":1,"1467":2}}],["dataprotectionkek",{"3":{"1467":1}}],["dataprotectionkeyscontainer",{"3":{"1467":1}}],["dataprotectionkeyvaultkeyuri",{"2":{"602":1,"604":1,"668":1},"3":{"0":1,"602":1,"604":1,"668":1,"1467":1}}],["dataprotectionstorage",{"3":{"665":1}}],["dataprotectionstorageready",{"2":{"604":1,"663":1,"665":1,"668":1,"1970":1},"3":{"0":1,"604":1,"663":1,"665":2,"668":1,"1970":1}}],["dataprotectionextensionstests",{"3":{"665":1,"1199":1,"1207":2,"1339":1,"1438":2}}],["dataprotectionextensions",{"3":{"665":4,"668":1,"1199":1,"1203":1,"1207":2,"1336":2,"1339":5,"1442":3,"1467":2}}],["dataprotection",{"1":{"662":1,"663":1,"664":1,"665":1,"666":1,"667":1,"668":1,"669":1,"670":1},"2":{"665":2,"668":1,"1339":1},"3":{"0":3,"425":1,"662":1,"664":2,"665":14,"667":2,"668":3,"669":1,"1207":3,"1212":1,"1324":1,"1325":1,"1336":3,"1339":8,"1438":2,"1442":5,"1445":1,"1467":21,"1469":1,"1496":2,"1970":4,"1972":1,"2232":2}}],["datamatrix",{"3":{"682":1,"685":1,"1417":1}}],["datamemberattribute",{"3":{"1300":1}}],["datamember",{"2":{"659":1},"3":{"0":1,"657":1,"659":1,"964":2,"1227":1,"1229":7,"1234":1,"1237":7,"1300":2,"1805":1}}],["datahost",{"3":{"423":1}}],["datasets",{"3":{"1560":1}}],["datasetrowcount",{"3":{"1436":1}}],["datasubject",{"2":{"1436":1},"3":{"1436":1}}],["datasubjectsample",{"3":{"1199":2,"1207":1,"1436":3,"1496":1,"1577":1}}],["datastorageregion",{"3":{"972":1,"1436":3,"2043":1}}],["datascheme",{"3":{"423":1,"1416":1}}],["datasource=",{"3":{"1436":2}}],["datasource=name",{"3":{"1286":1}}],["datasourcemodelcachekeyfactory",{"2":{"1201":1,"1273":1,"1849":1,"1853":1},"3":{"1198":1,"1199":3,"1201":1,"1203":1,"1207":2,"1273":2,"1286":6,"1396":1,"1498":1,"1849":1,"1853":1}}],["datasourcebranchingfitnesstests",{"2":{"1177":1,"1179":1,"1212":1},"3":{"1175":1,"1177":1,"1179":1,"1199":1,"1207":3,"1212":1,"1286":11,"1436":6,"1496":1}}],["datasourceenginecapabilities",{"2":{"1175":1,"1274":1,"1280":1},"3":{"1175":2,"1176":1,"1177":1,"1178":2,"1199":7,"1203":1,"1207":2,"1274":1,"1280":4,"1286":18,"1496":1}}],["datasourceengines",{"2":{"1179":1,"1510":1,"1586":1},"3":{"0":1,"1175":3,"1176":2,"1177":1,"1178":1,"1179":2,"1198":1,"1199":13,"1203":1,"1207":2,"1212":1,"1272":1,"1273":1,"1280":2,"1286":62,"1436":1,"1496":1,"1510":1,"1577":1,"1586":1}}],["datasourceentrysettings",{"2":{"1279":1,"1323":1},"3":{"166":2,"171":1,"1175":2,"1199":22,"1203":1,"1207":2,"1279":2,"1286":21,"1320":2,"1323":4,"1436":1}}],["datasourcessettings",{"2":{"1272":1},"3":{"674":1,"1199":17,"1203":1,"1207":2,"1272":1,"1279":2,"1286":19,"1320":2,"1323":3}}],["datasourceservicetests",{"3":{"1199":1,"1207":3,"1286":1}}],["datasourceserviceadditionaltests",{"3":{"1199":1,"1207":4,"1286":1}}],["datasourceservice",{"3":{"166":1,"1175":1,"1199":4,"1203":1,"1207":2,"1247":1,"1279":3,"1280":1,"1286":15,"1323":3}}],["datasources",{"2":{"170":1,"1441":1,"1473":1,"1490":1,"1614":1,"1652":1,"1701":1,"1722":1,"1729":1,"1731":1,"2097":1},"3":{"47":1,"166":6,"170":5,"633":1,"676":2,"698":4,"703":1,"1004":1,"1034":2,"1067":1,"1175":13,"1203":26,"1207":148,"1276":2,"1279":12,"1280":1,"1286":127,"1300":2,"1311":2,"1320":6,"1323":5,"1326":1,"1328":1,"1332":1,"1339":7,"1359":1,"1429":1,"1436":8,"1438":2,"1441":5,"1443":1,"1446":1,"1473":1,"1490":2,"1496":1,"1577":1,"1586":1,"1614":1,"1652":3,"1684":2,"1701":1,"1719":1,"1722":1,"1727":1,"1728":1,"1729":1,"1731":1,"1790":1,"1849":2,"2008":1,"2097":1}}],["datasourceresolvertests",{"2":{"164":1},"3":{"164":1,"170":2,"1199":1,"1207":2,"1286":12}}],["datasourceresolver",{"2":{"46":1,"47":1,"991":1,"1033":1,"1321":1,"1790":1,"1849":1,"1853":1},"3":{"46":1,"47":1,"166":4,"170":2,"171":1,"991":1,"1033":1,"1175":2,"1199":14,"1203":1,"1207":3,"1279":3,"1284":1,"1286":121,"1321":1,"1323":2,"1326":1,"1339":1,"1436":3,"1438":1,"1534":1,"1790":1,"1849":2,"1853":1}}],["datasourcename",{"2":{"910":1,"2199":1},"3":{"0":1,"905":4,"910":1,"1059":1,"1061":1,"1273":1,"1283":2,"1286":11,"1290":1,"1297":1,"1300":3,"1323":1,"1436":1,"2199":1}}],["datasourcekey",{"2":{"165":1,"169":1,"697":1,"699":1,"701":1},"3":{"0":1,"47":1,"165":1,"166":2,"169":1,"697":1,"698":1,"699":1,"701":1,"1199":95,"1203":1,"1207":2,"1212":1,"1259":2,"1278":2,"1279":3,"1286":77,"1311":1,"1323":1,"1436":6,"1664":1,"1790":1,"1849":3,"1851":1}}],["datasource",{"2":{"889":1,"1174":1,"1179":1,"1321":1,"1361":1,"1510":1,"1856":2,"1857":1},"3":{"0":4,"24":3,"166":3,"170":3,"195":1,"201":1,"889":1,"905":1,"1034":2,"1036":1,"1174":1,"1175":3,"1179":1,"1199":104,"1203":1,"1207":1,"1212":3,"1259":11,"1270":1,"1277":1,"1279":1,"1280":3,"1282":1,"1284":1,"1286":91,"1311":1,"1317":1,"1321":1,"1323":17,"1339":2,"1361":1,"1369":3,"1396":2,"1436":14,"1441":1,"1510":1,"1664":1,"1774":1,"1856":3,"1857":1}}],["dataexport",{"2":{"723":1,"725":1,"727":1,"1299":1,"1410":1,"1672":1},"3":{"0":1,"723":1,"725":1,"727":1,"1203":8,"1207":16,"1299":1,"1300":4,"1311":4,"1410":2,"1415":24,"1577":1,"1672":1}}],["dataexportcontrollerbasetests",{"3":{"1199":1,"1207":6,"1311":2,"1438":2}}],["dataexportcontrollerbase",{"2":{"723":1,"725":1,"728":1,"1060":1,"1299":1,"1672":1},"3":{"0":1,"723":2,"725":4,"728":1,"1060":1,"1199":4,"1203":1,"1207":2,"1299":1,"1300":7,"1311":9,"1323":4,"1410":1,"1415":13,"1496":1,"1577":1,"1672":1}}],["data",{"0":{"1299":1,"1357":1,"1364":1,"1545":1,"1556":1,"1567":1,"1664":1,"1777":1,"2074":1,"2207":1},"1":{"542":1,"543":1,"544":1,"545":1,"546":1,"547":1,"548":1,"549":1,"550":1,"551":1,"552":1,"722":1,"723":1,"724":1,"725":1,"726":1,"727":1,"728":1,"729":1,"878":1,"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"969":1,"970":1,"971":1,"972":1,"973":1,"974":1,"975":1,"976":1,"1172":1,"1173":1,"1174":1,"1175":1,"1176":1,"1177":1,"1178":1,"1179":1,"1180":1},"2":{"1477":1,"1492":1},"3":{"0":25,"12":1,"24":2,"38":7,"39":9,"40":1,"41":5,"42":1,"46":2,"47":1,"48":1,"68":1,"69":1,"80":1,"95":2,"96":2,"117":1,"120":1,"122":1,"128":1,"135":1,"165":1,"167":2,"184":1,"188":1,"195":1,"208":1,"214":1,"219":1,"235":1,"245":1,"248":1,"267":1,"273":1,"275":1,"290":1,"295":4,"310":1,"314":1,"317":2,"318":1,"358":5,"359":2,"360":2,"362":1,"365":2,"388":1,"398":1,"399":1,"402":1,"403":2,"409":1,"428":1,"435":1,"440":1,"441":2,"459":1,"464":1,"537":1,"539":1,"542":1,"545":2,"549":2,"550":1,"555":1,"556":1,"557":1,"572":1,"583":1,"585":1,"587":1,"591":1,"597":1,"598":1,"602":1,"604":1,"605":1,"609":2,"610":1,"612":1,"618":1,"650":1,"659":1,"664":1,"665":3,"666":2,"670":1,"674":1,"676":2,"682":1,"684":1,"689":1,"690":2,"697":3,"698":1,"699":2,"700":3,"707":1,"714":3,"715":3,"716":2,"722":1,"724":6,"725":10,"726":2,"727":4,"728":1,"742":1,"743":1,"745":1,"747":1,"749":1,"757":1,"758":1,"759":1,"798":1,"810":1,"827":2,"838":1,"839":2,"853":1,"855":1,"878":1,"880":1,"881":2,"884":1,"885":1,"896":2,"905":2,"906":1,"907":1,"920":1,"945":1,"947":1,"948":1,"963":1,"964":2,"966":1,"969":1,"971":2,"972":1,"974":1,"987":2,"988":1,"992":1,"1004":1,"1011":1,"1012":1,"1018":1,"1019":1,"1020":1,"1026":1,"1028":1,"1044":2,"1059":2,"1060":1,"1069":1,"1082":2,"1107":1,"1108":1,"1110":1,"1116":3,"1118":1,"1132":1,"1142":2,"1143":1,"1172":1,"1184":2,"1185":2,"1192":4,"1201":1,"1202":3,"1203":3,"1207":8,"1212":10,"1216":3,"1218":1,"1229":8,"1231":1,"1232":2,"1233":2,"1236":8,"1237":45,"1238":1,"1239":2,"1240":1,"1242":1,"1243":3,"1247":18,"1251":2,"1256":2,"1257":1,"1259":30,"1263":2,"1264":1,"1267":1,"1269":1,"1270":28,"1271":3,"1272":4,"1273":1,"1275":1,"1276":1,"1277":4,"1283":4,"1284":3,"1285":1,"1286":201,"1290":2,"1293":1,"1297":1,"1299":2,"1300":81,"1301":10,"1304":1,"1306":1,"1307":2,"1308":3,"1309":43,"1310":15,"1311":46,"1312":2,"1313":1,"1315":1,"1316":1,"1317":2,"1318":1,"1320":2,"1322":1,"1323":77,"1324":90,"1326":2,"1328":2,"1339":28,"1343":2,"1344":2,"1347":1,"1350":59,"1352":1,"1353":5,"1355":2,"1356":1,"1357":2,"1358":1,"1359":157,"1361":1,"1363":1,"1364":1,"1366":1,"1369":36,"1372":1,"1373":1,"1375":1,"1378":1,"1380":24,"1381":1,"1383":1,"1385":2,"1390":1,"1391":1,"1392":1,"1393":1,"1395":95,"1396":153,"1399":1,"1400":2,"1401":1,"1402":50,"1403":2,"1410":5,"1412":1,"1415":105,"1416":4,"1417":13,"1420":1,"1423":2,"1427":3,"1429":2,"1431":2,"1433":4,"1436":161,"1437":1,"1438":21,"1441":3,"1443":1,"1444":1,"1449":3,"1454":1,"1455":4,"1456":2,"1461":1,"1462":1,"1465":2,"1467":26,"1469":1,"1473":5,"1474":4,"1475":1,"1476":2,"1477":4,"1478":8,"1479":1,"1481":1,"1482":2,"1483":1,"1484":1,"1487":3,"1488":6,"1489":7,"1490":1,"1492":2,"1493":1,"1496":6,"1497":1,"1499":3,"1508":1,"1524":2,"1525":2,"1526":6,"1530":1,"1531":1,"1534":3,"1535":2,"1540":1,"1544":2,"1545":1,"1548":2,"1549":2,"1553":1,"1555":4,"1556":2,"1559":2,"1560":1,"1561":2,"1563":1,"1567":5,"1568":1,"1572":4,"1575":1,"1577":6,"1581":1,"1582":2,"1584":1,"1585":1,"1586":3,"1590":1,"1591":5,"1597":2,"1600":2,"1602":1,"1607":2,"1621":1,"1629":5,"1630":8,"1631":8,"1632":12,"1634":1,"1635":1,"1636":3,"1637":2,"1638":4,"1639":12,"1640":2,"1641":4,"1649":1,"1654":1,"1660":2,"1662":1,"1663":1,"1664":1,"1665":2,"1666":1,"1667":1,"1670":1,"1671":1,"1672":3,"1677":1,"1685":1,"1686":1,"1702":1,"1707":3,"1709":1,"1713":1,"1714":1,"1718":1,"1722":1,"1727":1,"1729":1,"1734":1,"1741":2,"1746":1,"1747":4,"1748":1,"1751":2,"1753":1,"1757":1,"1759":1,"1764":2,"1765":6,"1772":1,"1774":2,"1776":1,"1777":2,"1779":1,"1783":1,"1784":1,"1790":4,"1792":1,"1793":2,"1795":2,"1796":3,"1799":1,"1804":1,"1805":1,"1807":1,"1808":1,"1810":1,"1812":1,"1813":1,"1815":1,"1817":1,"1825":1,"1832":2,"1836":1,"1838":2,"1839":3,"1848":1,"1849":1,"1852":1,"1853":1,"1855":2,"1857":1,"1859":1,"1861":1,"1862":1,"1863":2,"1865":1,"1868":2,"1870":1,"1873":1,"1902":1,"1903":1,"1920":2,"1922":2,"1923":3,"1924":1,"1942":1,"1948":1,"1950":1,"1951":1,"1953":2,"1954":1,"1957":1,"1959":1,"1988":1,"1993":1,"1994":1,"1997":2,"2010":1,"2012":2,"2013":1,"2022":1,"2029":1,"2033":1,"2034":2,"2036":1,"2038":1,"2040":1,"2043":1,"2047":1,"2053":3,"2055":1,"2057":1,"2059":1,"2061":4,"2062":2,"2063":8,"2064":1,"2065":4,"2067":4,"2068":6,"2069":4,"2070":1,"2073":2,"2076":1,"2080":1,"2085":1,"2091":2,"2098":1,"2111":1,"2114":1,"2125":2,"2126":4,"2127":2,"2128":2,"2140":4,"2141":6,"2142":3,"2143":1,"2146":3,"2150":1,"2154":1,"2156":1,"2158":1,"2159":2,"2160":3,"2164":1,"2171":1,"2175":1,"2179":1,"2191":1,"2192":2,"2199":1,"2200":2,"2203":1,"2207":1,"2214":1,"2219":3,"2220":4,"2224":1,"2230":1,"2232":9,"2236":1,"2241":1}}],["databasecreated",{"3":{"1436":1}}],["databaseauditingpolicy",{"3":{"1436":1}}],["databasefacade",{"3":{"1311":1}}],["databaserestoredrilltests",{"2":{"1707":1},"3":{"1199":1,"1207":3,"1438":1,"1577":2,"1586":1,"1707":3}}],["databaseinitializationextensionstests",{"2":{"1496":1},"3":{"1198":1,"1199":2,"1207":7,"1286":1,"1311":2,"1323":3,"1496":2}}],["databaseinitializationextensions",{"2":{"290":1,"1034":1},"3":{"290":2,"295":1,"296":1,"453":1,"583":1,"1034":2,"1199":1,"1203":1,"1207":2,"1208":2,"1286":15,"1311":5,"1323":8,"1369":2,"1415":1,"1577":1}}],["databaseinitstrategy",{"2":{"290":1,"291":2,"295":1,"296":1,"563":1,"1467":1,"1614":1,"1652":1,"1664":1,"1719":1},"3":{"290":1,"291":2,"295":1,"296":1,"563":1,"698":1,"1311":1,"1320":1,"1323":3,"1467":1,"1490":1,"1614":1,"1652":1,"1664":1,"1719":1}}],["databaseinitstrategy=migrate",{"3":{"0":1,"572":1,"1212":1,"1455":1,"1476":1}}],["databasenameprefix",{"3":{"1436":1,"1488":1,"1490":1}}],["databasename",{"2":{"1446":1},"3":{"195":1,"201":1,"1259":8,"1286":4,"1436":2,"1441":1,"1446":1,"1684":1,"1728":1}}],["databases",{"0":{"1614":1,"2053":1},"1":{"1854":1,"1855":1,"1856":1,"1857":1,"1858":1,"1859":1,"1860":1,"1861":1,"1862":1},"3":{"0":3,"10":1,"47":2,"49":1,"61":1,"67":1,"165":1,"166":2,"295":1,"390":1,"439":1,"628":1,"676":1,"717":1,"719":1,"762":1,"766":2,"913":1,"971":1,"1025":1,"1042":1,"1059":1,"1061":1,"1212":1,"1259":4,"1273":1,"1275":1,"1276":1,"1278":1,"1279":2,"1286":33,"1310":1,"1311":1,"1320":1,"1323":3,"1325":1,"1326":1,"1332":1,"1350":1,"1359":7,"1369":2,"1380":2,"1396":4,"1402":1,"1415":2,"1429":1,"1430":1,"1436":12,"1439":1,"1440":1,"1441":2,"1446":2,"1449":1,"1455":2,"1460":3,"1461":1,"1467":11,"1469":5,"1471":3,"1473":4,"1474":4,"1475":6,"1477":2,"1478":3,"1482":3,"1496":1,"1525":2,"1526":3,"1535":1,"1589":1,"1591":2,"1596":1,"1600":1,"1652":1,"1664":1,"1670":1,"1678":1,"1684":1,"1688":1,"1689":2,"1698":1,"1710":1,"1724":1,"1783":1,"1792":1,"1847":4,"1849":3,"1852":1,"1853":2,"1854":1,"1855":1,"1858":1,"1860":1,"1862":1,"1864":1,"2003":1,"2004":1,"2005":1,"2008":1,"2014":1,"2027":1,"2034":3,"2036":1,"2037":2,"2038":2,"2053":2,"2060":1,"2067":1,"2109":1,"2192":1,"2199":1,"2207":1,"2229":1}}],["database",{"0":{"1273":1,"1279":1,"1283":1,"1429":1,"1473":1,"1510":1,"1849":1,"1871":1,"2035":1,"2164":1},"1":{"44":1,"45":1,"46":1,"47":1,"48":1,"49":1,"1847":1,"1848":1,"1849":1,"1850":1,"1851":1,"1852":1,"1853":1,"1869":1,"1870":1,"1871":1,"1872":1,"1873":1,"1874":1,"1875":1,"1876":1,"1877":1},"2":{"291":1,"1133":1,"1273":1,"1461":1,"1467":3,"1475":3},"3":{"0":27,"10":3,"17":1,"24":1,"38":1,"41":1,"44":1,"45":4,"46":2,"47":7,"48":1,"49":1,"58":1,"59":1,"61":1,"64":1,"66":2,"67":1,"71":3,"73":1,"77":1,"94":1,"109":1,"135":1,"153":1,"158":1,"165":2,"166":10,"167":1,"169":1,"170":2,"171":1,"174":1,"176":1,"195":3,"196":2,"197":1,"199":1,"201":2,"202":2,"218":1,"255":1,"290":2,"291":3,"292":2,"293":2,"294":2,"295":2,"296":1,"330":1,"331":1,"341":3,"342":2,"343":2,"350":2,"358":4,"359":1,"390":1,"391":1,"403":1,"451":1,"458":2,"459":4,"460":1,"461":2,"471":1,"473":1,"500":1,"501":1,"535":3,"536":1,"537":4,"538":1,"541":1,"544":1,"545":1,"546":2,"548":1,"549":1,"551":2,"563":1,"565":1,"568":2,"572":3,"573":1,"574":1,"575":1,"582":1,"583":1,"587":1,"598":1,"599":3,"600":1,"601":1,"605":1,"625":1,"628":1,"631":2,"632":1,"633":6,"645":1,"676":1,"688":1,"690":2,"691":1,"692":1,"697":2,"698":4,"700":1,"702":5,"703":1,"707":3,"708":1,"709":2,"711":1,"713":1,"714":3,"715":3,"716":1,"717":1,"718":2,"719":4,"735":2,"742":1,"743":1,"756":1,"758":1,"759":1,"765":1,"766":2,"767":1,"769":1,"782":1,"793":1,"799":1,"805":1,"809":1,"810":1,"815":1,"817":1,"819":5,"820":2,"821":2,"823":1,"853":1,"856":1,"888":1,"889":1,"890":1,"891":2,"893":1,"897":1,"904":1,"905":3,"906":2,"907":1,"913":1,"918":2,"922":1,"930":1,"932":1,"933":1,"972":1,"974":1,"987":3,"988":1,"990":1,"992":2,"995":2,"996":1,"1000":2,"1018":1,"1030":1,"1032":1,"1033":3,"1034":5,"1036":1,"1037":1,"1038":1,"1043":2,"1050":1,"1052":1,"1059":1,"1061":1,"1066":1,"1067":2,"1068":1,"1074":1,"1082":2,"1083":3,"1085":3,"1116":1,"1120":1,"1133":1,"1135":1,"1137":1,"1140":1,"1145":1,"1149":1,"1150":5,"1151":1,"1152":1,"1153":1,"1173":1,"1192":1,"1202":3,"1203":3,"1212":6,"1213":2,"1228":1,"1229":2,"1230":1,"1231":2,"1236":2,"1237":11,"1238":2,"1239":2,"1242":1,"1243":2,"1244":1,"1247":16,"1248":1,"1251":2,"1253":3,"1254":4,"1256":1,"1259":33,"1263":2,"1269":1,"1270":17,"1271":3,"1272":3,"1273":5,"1274":1,"1275":2,"1276":4,"1277":2,"1278":5,"1279":10,"1280":1,"1281":5,"1282":3,"1283":5,"1284":2,"1286":292,"1290":3,"1292":1,"1297":2,"1300":29,"1301":2,"1303":1,"1309":15,"1310":9,"1311":32,"1313":1,"1316":1,"1320":7,"1321":2,"1323":47,"1324":1,"1325":1,"1326":3,"1328":1,"1332":5,"1333":1,"1339":27,"1342":7,"1343":2,"1344":1,"1350":52,"1354":2,"1355":3,"1357":2,"1359":112,"1360":1,"1361":2,"1362":2,"1363":3,"1364":1,"1365":1,"1367":1,"1369":31,"1379":1,"1380":4,"1395":8,"1396":89,"1399":1,"1401":2,"1402":22,"1404":1,"1405":1,"1406":1,"1409":1,"1410":1,"1412":1,"1413":2,"1415":46,"1417":1,"1428":1,"1429":10,"1431":1,"1436":70,"1437":2,"1438":16,"1441":13,"1443":5,"1446":3,"1449":3,"1451":1,"1453":4,"1455":4,"1457":1,"1460":6,"1461":1,"1462":1,"1463":1,"1467":45,"1469":2,"1471":3,"1473":10,"1474":12,"1475":18,"1476":4,"1477":4,"1478":11,"1482":7,"1483":3,"1484":1,"1486":1,"1487":1,"1488":7,"1490":8,"1491":2,"1492":5,"1510":1,"1523":1,"1524":1,"1534":5,"1541":1,"1544":2,"1545":2,"1547":1,"1568":1,"1572":1,"1576":1,"1577":1,"1588":1,"1590":1,"1591":4,"1596":2,"1597":1,"1600":1,"1602":1,"1630":3,"1631":6,"1637":2,"1638":3,"1639":6,"1640":1,"1641":4,"1650":1,"1651":4,"1652":4,"1653":7,"1654":2,"1656":2,"1661":5,"1662":4,"1663":3,"1664":5,"1667":3,"1668":1,"1670":2,"1671":3,"1674":1,"1675":5,"1678":1,"1683":1,"1684":10,"1685":3,"1686":3,"1688":2,"1689":1,"1692":2,"1695":1,"1696":1,"1697":5,"1698":1,"1700":2,"1701":2,"1704":2,"1705":1,"1706":1,"1707":1,"1708":1,"1717":1,"1718":4,"1719":2,"1721":3,"1722":3,"1724":3,"1726":2,"1727":12,"1728":6,"1729":5,"1731":2,"1748":2,"1749":1,"1764":1,"1765":1,"1767":1,"1774":2,"1778":1,"1779":1,"1780":1,"1781":1,"1783":2,"1785":1,"1786":1,"1789":1,"1790":4,"1792":1,"1793":3,"1796":1,"1800":1,"1806":1,"1808":1,"1810":1,"1814":1,"1815":3,"1817":2,"1821":3,"1831":1,"1832":2,"1835":2,"1837":1,"1839":1,"1840":2,"1841":2,"1846":1,"1847":6,"1848":3,"1849":11,"1851":3,"1852":1,"1853":11,"1854":1,"1855":5,"1858":3,"1860":1,"1862":1,"1863":2,"1864":3,"1866":1,"1867":1,"1868":4,"1869":2,"1871":2,"1872":1,"1873":1,"1875":2,"1876":2,"1877":3,"1886":1,"1887":1,"1898":1,"1900":1,"1901":1,"1903":2,"1913":1,"1920":1,"1921":1,"1922":2,"1923":2,"1933":2,"1934":1,"1937":1,"1940":1,"1941":1,"1946":2,"1948":3,"1951":1,"1954":2,"1965":1,"1980":2,"1982":1,"1984":1,"1985":1,"1987":1,"1988":2,"1991":1,"1999":1,"2008":4,"2027":1,"2032":3,"2034":5,"2035":6,"2036":3,"2037":4,"2038":2,"2047":1,"2049":1,"2050":2,"2052":1,"2053":3,"2054":1,"2055":4,"2056":1,"2060":2,"2061":1,"2064":2,"2071":1,"2089":1,"2097":3,"2111":2,"2113":2,"2115":1,"2128":1,"2135":1,"2140":6,"2141":4,"2144":2,"2146":4,"2161":3,"2162":2,"2164":2,"2168":1,"2169":4,"2170":1,"2180":1,"2185":1,"2191":1,"2193":3,"2207":3,"2214":1,"2220":3,"2221":1,"2230":3,"2232":4,"2243":1}}],["darkened",{"3":{"2075":1}}],["darkens",{"3":{"1324":2}}],["darkerror",{"3":{"1324":3}}],["darkactiondefault",{"3":{"1324":1}}],["darkbackground",{"3":{"1324":2}}],["darkwarning",{"3":{"1324":1}}],["darksurface",{"3":{"1324":2}}],["darksuccess",{"3":{"1324":1}}],["darksecondarylighten",{"3":{"1324":1}}],["darksecondarydarken",{"3":{"1324":1}}],["darkinfo",{"3":{"1324":2}}],["darktextsecondary",{"3":{"1324":1}}],["darktertiary",{"3":{"1324":1}}],["darktheme",{"3":{"1237":1,"1415":2}}],["darkprimarylighten",{"3":{"1324":1}}],["darkmodee2etests",{"2":{"1643":1,"1645":1},"3":{"618":1,"1199":1,"1207":2,"1324":2,"1436":1,"1577":4,"1586":2,"1643":1,"1645":1}}],["dark",{"1":{"270":1,"271":1,"272":1,"273":1,"274":1,"275":1,"276":1,"277":1,"2081":1,"2082":1,"2083":1,"2084":1,"2085":1,"2086":1},"3":{"0":3,"270":1,"272":1,"273":2,"274":1,"275":1,"299":1,"396":1,"555":2,"556":1,"558":2,"618":4,"622":3,"1192":1,"1212":1,"1234":1,"1237":2,"1300":1,"1311":1,"1324":27,"1333":1,"1339":3,"1402":2,"1405":1,"1415":5,"1416":2,"1436":6,"1438":2,"1480":1,"1487":1,"1496":2,"1499":1,"1513":1,"1526":2,"1557":1,"1577":12,"1586":2,"1591":2,"1596":2,"1608":1,"1643":5,"1645":6,"1653":1,"1669":1,"1742":1,"2075":2,"2079":4,"2080":1,"2081":4,"2083":2,"2086":3,"2134":2,"2160":1,"2210":1,"2232":2}}],["dayoffset",{"3":{"1369":2}}],["daylight",{"3":{"709":1,"1269":1,"1270":1,"1438":1}}],["days",{"2":{"629":1},"3":{"0":5,"38":1,"41":1,"93":1,"489":1,"499":2,"501":2,"502":1,"513":1,"591":2,"626":2,"627":1,"628":1,"629":2,"642":1,"706":1,"767":1,"805":2,"905":1,"1020":2,"1150":1,"1212":1,"1259":1,"1288":1,"1300":5,"1323":2,"1324":1,"1410":1,"1417":1,"1427":2,"1436":1,"1445":1,"1453":1,"1455":6,"1459":1,"1460":1,"1461":2,"1465":5,"1467":7,"1474":3,"1475":3,"1476":4,"1478":1,"1490":1,"1525":1,"1526":2,"1534":1,"1589":1,"1591":2,"1596":2,"1599":2,"1631":1,"1637":1,"1638":1,"1641":3,"1747":1,"1765":1,"1767":1,"1845":1,"1982":1,"2061":1,"2063":1,"2065":1}}],["day",{"0":{"1476":1},"1":{"270":1,"271":1,"272":1,"273":1,"274":1,"275":1,"276":1,"277":1},"3":{"0":5,"41":1,"130":1,"186":1,"243":2,"265":1,"270":1,"273":1,"275":1,"311":1,"379":1,"387":1,"390":1,"423":1,"424":1,"491":2,"492":1,"501":1,"520":1,"529":1,"566":1,"594":1,"609":2,"626":1,"628":2,"689":1,"690":1,"706":2,"707":1,"708":1,"717":1,"726":1,"757":1,"765":1,"766":2,"767":1,"781":1,"785":1,"792":1,"831":1,"832":1,"848":1,"907":2,"916":1,"1003":1,"1013":2,"1018":1,"1020":1,"1082":1,"1085":1,"1102":1,"1124":1,"1125":1,"1134":1,"1143":1,"1150":1,"1154":1,"1162":1,"1183":1,"1192":1,"1212":1,"1237":4,"1247":1,"1270":2,"1288":1,"1295":1,"1298":1,"1300":5,"1301":1,"1309":2,"1323":1,"1324":3,"1339":1,"1348":1,"1349":1,"1350":3,"1356":1,"1359":5,"1369":2,"1378":1,"1379":1,"1380":2,"1393":1,"1395":12,"1396":23,"1397":1,"1401":1,"1402":9,"1416":3,"1435":1,"1436":5,"1438":6,"1455":6,"1456":1,"1457":3,"1458":3,"1459":1,"1462":1,"1465":12,"1467":15,"1469":1,"1471":1,"1474":2,"1475":1,"1476":2,"1482":2,"1487":1,"1490":1,"1492":3,"1496":14,"1499":1,"1513":1,"1514":1,"1525":3,"1526":6,"1529":2,"1530":1,"1531":2,"1532":1,"1534":1,"1552":2,"1577":1,"1585":1,"1589":1,"1591":5,"1626":2,"1627":1,"1629":3,"1630":5,"1631":1,"1641":1,"1663":1,"1675":1,"1678":1,"1695":1,"1698":1,"1764":1,"1765":1,"1767":2,"1783":1,"1788":2,"1789":1,"1790":1,"1795":1,"1800":1,"1826":1,"1847":2,"1853":1,"1854":1,"1862":1,"1863":1,"1864":1,"1870":1,"1894":1,"1900":2,"1905":1,"1922":1,"1925":1,"1942":1,"1948":2,"1951":1,"1958":2,"1960":1,"1963":1,"1980":1,"1982":1,"1984":2,"1986":1,"2003":1,"2015":2,"2016":1,"2028":1,"2034":1,"2056":1,"2068":1,"2075":1,"2080":1,"2081":1,"2086":1,"2087":1,"2097":1,"2098":1,"2099":1,"2110":1,"2133":3,"2138":1,"2146":1,"2180":2,"2193":1,"2206":1,"2220":1,"2228":1,"2232":1,"2238":1,"2239":2,"2240":2}}],["dns",{"3":{"0":1,"100":1,"109":1,"234":1,"235":1,"881":1,"1187":1,"1225":1,"1312":2,"1324":1,"1329":1,"1334":1,"1339":7,"1395":2,"1396":2,"1415":1,"1443":2,"1447":1,"1467":4,"1632":1,"1640":1,"1653":1,"1702":1}}],["duties",{"3":{"1366":2}}],["duty",{"3":{"757":1,"1270":1,"1300":1,"1402":1,"2063":1}}],["duplexstreamingserverhandler",{"3":{"1312":1}}],["duplex",{"3":{"1312":3}}],["duplicating",{"3":{"186":1,"273":1,"282":1,"351":1,"423":1,"767":1,"914":1,"1229":1,"1259":2,"1270":2,"1286":3,"1309":1,"1311":3,"1323":2,"1324":3,"1337":1,"1350":1,"1359":4,"1380":1,"1395":4,"1396":2,"1402":2,"1415":1,"1416":1,"1436":3,"1557":1,"1828":1,"2083":1}}],["duplication",{"3":{"133":1,"135":1,"255":1,"325":1,"405":1,"651":2,"681":1,"724":1,"819":1,"837":1,"1011":1,"1043":1,"1061":1,"1220":1,"1247":1,"1259":1,"1270":1,"1286":1,"1300":3,"1301":1,"1309":1,"1311":3,"1323":1,"1324":6,"1339":6,"1359":11,"1369":1,"1395":5,"1396":2,"1402":1,"1415":4,"1427":1,"1436":2,"1496":1,"1526":2,"1917":1,"2042":1,"2075":1,"2077":2,"2080":1,"2098":1,"2132":1,"2192":1,"2198":1}}],["duplicate→409",{"3":{"1611":1}}],["duplicatecode",{"3":{"1436":3}}],["duplicateticketerrors",{"3":{"1199":2,"1207":1,"1436":11}}],["duplicated",{"3":{"0":1,"61":1,"219":1,"255":1,"399":1,"400":1,"404":1,"463":1,"490":1,"534":1,"538":1,"558":1,"609":1,"819":1,"829":1,"834":1,"837":1,"839":1,"848":1,"899":1,"996":1,"1011":1,"1043":1,"1059":1,"1118":1,"1224":1,"1229":1,"1247":2,"1259":2,"1268":1,"1270":6,"1271":2,"1300":2,"1301":1,"1309":1,"1311":2,"1312":1,"1323":2,"1324":6,"1339":5,"1350":4,"1359":15,"1380":1,"1389":1,"1395":6,"1396":5,"1402":3,"1415":4,"1436":3,"1443":1,"1446":1,"1453":1,"1467":1,"1496":4,"1542":1,"1551":1,"1565":2,"1582":1,"1632":1,"1830":1,"1929":1,"2013":1,"2055":1,"2073":2,"2150":1,"2157":1,"2159":2,"2168":1}}],["duplicates",{"3":{"0":2,"159":3,"162":1,"549":1,"649":1,"811":1,"846":1,"891":2,"915":2,"988":1,"989":1,"995":1,"1140":1,"1237":1,"1240":1,"1258":1,"1259":2,"1270":1,"1271":2,"1286":2,"1300":3,"1311":5,"1323":3,"1324":2,"1338":1,"1339":2,"1350":3,"1359":3,"1369":1,"1394":1,"1395":5,"1396":1,"1402":2,"1415":1,"1417":1,"1436":1,"1454":1,"1496":6,"1526":1,"1577":1,"1631":5,"1639":1,"1765":1,"1840":1,"1845":1,"1870":1,"1909":6,"1910":1,"1911":2,"1912":4}}],["duplicate",{"3":{"0":8,"18":1,"24":3,"156":1,"157":2,"159":1,"195":3,"197":1,"198":1,"201":5,"295":1,"330":1,"465":2,"508":1,"518":1,"528":1,"529":1,"557":1,"558":1,"610":1,"688":1,"691":1,"692":2,"749":1,"790":1,"821":1,"834":1,"871":1,"890":1,"897":1,"905":1,"966":1,"988":1,"996":5,"998":1,"1018":1,"1020":1,"1022":1,"1043":1,"1099":1,"1100":1,"1142":1,"1147":1,"1212":2,"1229":1,"1231":1,"1237":1,"1247":6,"1251":1,"1254":1,"1258":2,"1259":14,"1265":1,"1269":1,"1270":8,"1271":1,"1284":1,"1286":19,"1288":1,"1289":1,"1290":1,"1297":1,"1300":11,"1309":5,"1310":1,"1311":14,"1312":2,"1317":1,"1323":10,"1324":14,"1328":1,"1339":8,"1343":2,"1347":1,"1350":15,"1359":47,"1367":1,"1369":2,"1376":1,"1380":7,"1389":1,"1395":7,"1396":26,"1402":6,"1415":2,"1417":2,"1431":1,"1436":17,"1438":5,"1441":1,"1454":1,"1467":2,"1472":1,"1473":1,"1488":1,"1496":16,"1535":1,"1547":2,"1566":1,"1577":1,"1631":3,"1632":2,"1640":1,"1661":1,"1665":1,"1666":1,"1749":1,"1765":2,"1767":1,"1769":1,"1774":2,"1832":1,"1840":2,"1845":1,"1846":1,"1889":1,"1890":1,"1905":1,"1907":1,"1909":4,"1910":1,"1911":2,"1912":3,"1947":1,"1988":1,"1991":1,"2000":1,"2162":1,"2165":1,"2166":1,"2182":1}}],["dumb",{"3":{"1350":1,"1395":1,"1555":1,"1577":1,"1586":1}}],["dumber",{"3":{"1324":1}}],["dummybearerauthority",{"3":{"1436":1}}],["dummy",{"3":{"946":1,"949":1,"1436":1,"1490":1}}],["dumped",{"3":{"1894":1}}],["dumping",{"3":{"1311":1,"1324":1,"1416":1,"1540":1}}],["dump",{"3":{"854":1,"904":1,"1244":1,"1294":1,"1300":2,"1324":1,"1396":1,"1577":1,"1985":1}}],["dueon",{"3":{"1323":1}}],["due",{"2":{"2191":1},"3":{"0":2,"539":2,"707":3,"709":1,"813":1,"1040":2,"1042":3,"1045":2,"1275":4,"1286":13,"1317":1,"1323":15,"1396":1,"1436":1,"1467":1,"1631":1,"1639":1,"1948":1,"2156":2,"2185":1,"2188":2,"2191":2,"2193":1}}],["durably",{"3":{"1251":1,"1402":3,"1415":1,"1436":1}}],["durable",{"0":{"1357":1,"1367":1},"1":{"222":1,"223":1,"224":1,"225":1,"226":1,"227":1,"228":1,"229":1,"230":1,"231":1,"2181":1,"2182":1,"2183":1,"2184":1,"2185":1,"2186":1,"2187":1,"2188":1,"2189":1,"2190":1,"2191":1,"2192":1,"2193":1},"3":{"0":12,"222":1,"223":1,"225":4,"229":1,"230":3,"379":1,"380":1,"382":1,"404":1,"406":1,"422":1,"433":1,"438":1,"440":1,"459":1,"466":1,"516":1,"517":1,"518":1,"521":1,"522":1,"523":1,"524":1,"536":1,"541":1,"706":2,"711":1,"809":1,"810":2,"813":1,"815":1,"856":1,"897":2,"898":1,"901":1,"933":1,"987":1,"988":3,"989":2,"990":1,"995":2,"996":2,"1000":1,"1018":1,"1022":1,"1024":1,"1030":1,"1044":2,"1074":1,"1075":2,"1076":1,"1099":1,"1100":2,"1102":1,"1104":1,"1116":1,"1120":1,"1142":1,"1152":1,"1154":1,"1192":1,"1212":4,"1247":1,"1259":1,"1272":1,"1274":1,"1278":1,"1286":13,"1300":2,"1302":1,"1304":1,"1309":10,"1323":7,"1324":5,"1350":1,"1352":1,"1357":2,"1359":7,"1360":1,"1369":1,"1376":1,"1380":8,"1390":1,"1396":1,"1397":2,"1398":1,"1400":1,"1402":3,"1408":1,"1410":1,"1411":2,"1415":11,"1417":2,"1438":2,"1455":1,"1476":1,"1496":2,"1512":2,"1631":1,"1665":1,"1675":1,"1735":1,"1779":2,"1789":1,"1790":1,"1800":1,"1837":1,"1840":5,"1842":1,"1843":1,"1861":2,"1910":2,"1912":1,"1932":1,"1933":3,"1935":2,"1937":1,"1940":2,"1941":2,"1942":3,"1943":3,"1944":1,"1947":3,"1948":2,"1949":4,"1950":7,"1981":1,"2006":1,"2020":1,"2081":1,"2091":1,"2127":1,"2156":1,"2165":3,"2168":1,"2169":1,"2180":2,"2181":2,"2183":1,"2190":1,"2192":1,"2193":2,"2203":1,"2206":2,"2232":3}}],["durability",{"3":{"0":1,"230":2,"444":1,"711":1,"933":1,"987":1,"1018":1,"1140":1,"1259":2,"1270":1,"1286":1,"1302":1,"1359":4,"1367":2,"1369":1,"1380":1,"1396":1,"1417":1,"1474":2,"1845":1,"1933":1,"2091":1}}],["durationmeasurement",{"3":{"1199":2,"1207":1,"1435":1}}],["durationms",{"3":{"609":1,"1467":1}}],["durationthreshold",{"3":{"861":1}}],["durations",{"3":{"731":1,"733":1,"1259":1,"1359":1}}],["durationhistogram",{"3":{"707":1,"1286":1,"1323":1}}],["duration",{"2":{"402":1,"407":3,"409":1,"1089":1,"1425":2,"1640":1,"2159":1,"2177":2,"2191":1},"3":{"0":6,"72":2,"100":1,"115":1,"125":2,"243":1,"395":4,"397":4,"398":1,"402":1,"404":1,"405":1,"407":5,"408":2,"409":1,"608":2,"609":1,"707":1,"709":2,"743":1,"856":1,"996":1,"1089":1,"1091":1,"1234":2,"1237":1,"1259":3,"1263":2,"1267":1,"1268":1,"1270":18,"1275":1,"1286":4,"1290":1,"1300":2,"1301":2,"1311":3,"1317":1,"1323":7,"1324":2,"1330":1,"1333":1,"1339":10,"1342":2,"1344":2,"1350":5,"1359":3,"1362":1,"1369":5,"1395":3,"1415":1,"1417":9,"1425":4,"1427":10,"1436":1,"1441":1,"1443":2,"1460":1,"1466":1,"1467":5,"1474":2,"1475":1,"1526":1,"1577":1,"1630":1,"1631":3,"1632":1,"1635":2,"1638":1,"1640":1,"1641":1,"1651":1,"1664":1,"1677":1,"1709":1,"1821":1,"1841":1,"1909":1,"1917":1,"1920":1,"1948":1,"2006":2,"2010":1,"2066":1,"2096":1,"2156":8,"2158":2,"2159":1,"2160":1,"2177":4,"2191":1,"2215":1,"2232":1}}],["during",{"3":{"0":2,"17":1,"19":1,"21":1,"71":1,"120":1,"159":1,"207":2,"235":1,"261":1,"267":1,"273":1,"293":1,"332":1,"383":1,"506":1,"507":1,"555":1,"556":1,"558":1,"560":1,"599":1,"649":1,"664":1,"733":2,"743":1,"749":1,"762":1,"768":1,"811":1,"819":1,"820":1,"827":1,"848":1,"881":1,"915":1,"954":1,"1152":1,"1154":1,"1233":1,"1237":5,"1259":3,"1265":1,"1270":2,"1271":1,"1274":1,"1286":12,"1288":2,"1298":1,"1300":8,"1309":1,"1311":2,"1312":2,"1323":4,"1324":32,"1338":1,"1339":8,"1347":1,"1350":7,"1359":8,"1367":1,"1380":1,"1391":1,"1393":1,"1395":7,"1396":10,"1402":9,"1408":1,"1415":5,"1416":4,"1417":13,"1427":2,"1432":1,"1433":1,"1436":9,"1438":2,"1441":2,"1443":1,"1455":1,"1456":1,"1467":1,"1468":1,"1475":2,"1482":1,"1488":2,"1534":1,"1582":1,"1630":6,"1631":6,"1632":3,"1633":1,"1634":1,"1636":1,"1637":1,"1638":7,"1639":7,"1641":1,"1700":1,"1708":1,"1755":1,"1912":1,"1968":1,"1972":1,"2009":1,"2016":1,"2030":1,"2076":1,"2085":1,"2119":1,"2135":1,"2165":1,"2232":1}}],["duality",{"3":{"1359":1}}],["dual",{"0":{"1508":1,"1896":1},"1":{"14":1,"15":1,"16":1,"17":1,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1,"24":1,"25":1,"26":1,"27":1,"297":1,"298":1,"299":1,"300":1,"301":1,"302":1,"303":1,"304":1,"305":1,"306":1,"525":1,"526":1,"527":1,"528":1,"529":1,"530":1,"531":1,"532":1,"1248":1,"1249":1,"1250":1,"1251":1,"1252":1,"1253":1,"1254":1,"1255":1,"1256":1,"1257":1,"1258":1,"1259":1,"1893":1,"1894":1,"1895":1,"1896":1,"1897":1,"1898":1,"1899":1},"3":{"0":3,"14":1,"17":1,"20":1,"21":1,"22":1,"24":1,"26":1,"27":1,"64":1,"143":1,"209":1,"229":1,"297":1,"397":1,"498":1,"525":1,"532":1,"680":1,"874":1,"935":1,"942":1,"945":2,"984":1,"1004":1,"1008":1,"1043":1,"1192":2,"1202":2,"1203":2,"1212":3,"1213":1,"1237":3,"1247":3,"1248":1,"1259":5,"1270":3,"1286":1,"1287":1,"1289":1,"1300":3,"1311":2,"1312":1,"1324":1,"1345":1,"1350":1,"1359":1,"1369":1,"1395":1,"1396":1,"1402":1,"1415":2,"1436":4,"1443":2,"1450":1,"1455":1,"1467":2,"1496":2,"1508":1,"1543":1,"1548":1,"1571":2,"1574":1,"1576":1,"1577":2,"1585":1,"1586":1,"1591":1,"1835":2,"1838":1,"1842":1,"1846":2,"1892":1,"1893":1,"1896":4,"1898":1,"1899":2,"1972":1,"2014":1,"2181":1,"2208":1,"2221":1,"2230":1,"2232":2}}],["dbs",{"3":{"1473":4,"1496":1,"1526":1,"1535":1,"1566":1,"1570":1,"1590":1,"1591":3,"1596":1,"1599":1,"1678":1}}],["dbseedertests",{"3":{"1199":1,"1207":3,"1286":2}}],["dbseeder",{"2":{"295":1,"1364":1},"3":{"295":2,"1199":4,"1203":1,"1207":2,"1284":2,"1286":9,"1364":1,"1369":4,"1415":2}}],["dbsets",{"0":{"1363":1}}],["dbsetremovingfixture",{"3":{"1199":2,"1207":1,"1436":10}}],["dbset",{"2":{"544":1,"1360":1,"1700":1,"1719":1},"3":{"39":1,"544":1,"545":1,"1259":1,"1278":1,"1286":10,"1360":1,"1363":4,"1369":16,"1396":6,"1415":1,"1436":17,"1496":1,"1651":1,"1685":1,"1686":2,"1697":1,"1700":1,"1719":1,"1844":1}}],["dbname",{"3":{"1436":1}}],["dbconnectionstringbuilder",{"3":{"1286":1}}],["dbcontextattribute",{"3":{"1323":1}}],["dbcontextarg",{"3":{"1286":2}}],["dbcontextoptionsbuilder",{"3":{"1436":3}}],["dbcontextoptions",{"2":{"1369":1},"3":{"1286":3,"1369":4,"1396":2,"1415":1}}],["dbcontexts",{"2":{"1286":1,"1500":1},"3":{"26":2,"71":1,"166":2,"171":1,"200":2,"203":1,"295":2,"310":3,"314":1,"341":1,"343":1,"470":1,"536":2,"540":1,"674":1,"697":1,"698":5,"700":1,"702":2,"703":4,"707":2,"710":1,"715":2,"798":1,"819":2,"888":1,"889":1,"905":2,"931":1,"946":1,"987":2,"988":2,"1034":6,"1042":4,"1050":1,"1059":1,"1061":1,"1083":1,"1133":1,"1140":3,"1146":1,"1150":1,"1175":4,"1203":26,"1207":164,"1208":1,"1212":1,"1232":1,"1237":3,"1251":1,"1259":2,"1263":1,"1273":6,"1278":2,"1282":1,"1284":2,"1286":112,"1300":6,"1311":1,"1318":1,"1323":1,"1350":1,"1359":2,"1363":2,"1364":1,"1369":11,"1396":8,"1404":1,"1412":1,"1415":7,"1438":5,"1496":3,"1500":1,"1577":2,"2064":1}}],["dbcontextfactorytests",{"3":{"1199":1,"1207":2}}],["dbcontextfactorytenanttests",{"3":{"1199":1,"1207":3,"1323":1}}],["dbcontextfactorytransactiontests",{"3":{"988":1,"1199":1,"1207":6,"1286":1,"1438":1}}],["dbcontextfactorysaveintegritytests",{"3":{"1199":1,"1207":6,"1286":2,"1438":2}}],["dbcontextfactorymigrationtargettests",{"3":{"1199":1,"1207":4,"1286":2,"1323":2,"1438":2}}],["dbcontextfactoryadditionaltests",{"3":{"1199":1,"1207":4}}],["dbcontextfactorycommitambiguitytests",{"3":{"988":1,"1199":1,"1207":8,"1286":4,"1438":2,"1496":1}}],["dbcontextfactory",{"2":{"46":1,"71":1,"120":1,"170":1,"700":1,"987":1,"991":1,"1133":1,"1135":1,"1253":1,"1321":1,"1840":1,"1851":1,"1852":1},"3":{"0":1,"26":1,"46":1,"71":1,"120":1,"170":1,"536":1,"538":1,"540":2,"698":3,"700":2,"702":1,"703":1,"819":2,"822":1,"987":2,"988":7,"990":2,"991":2,"1133":2,"1135":1,"1136":1,"1175":3,"1199":13,"1203":1,"1207":2,"1252":1,"1253":2,"1259":2,"1263":6,"1270":9,"1274":2,"1278":6,"1280":2,"1286":125,"1300":4,"1318":1,"1321":1,"1323":6,"1359":4,"1436":4,"1437":1,"1438":3,"1453":1,"1486":1,"1487":1,"1490":1,"1496":3,"1577":2,"1840":2,"1849":1,"1851":1,"1852":1}}],["dbcontext",{"0":{"1652":1},"2":{"21":1,"39":1,"365":1,"875":1,"1033":1,"1083":1,"1251":1,"1319":1,"1360":1,"1540":1,"1847":1,"1857":1},"3":{"0":1,"21":1,"39":1,"47":1,"48":1,"59":1,"122":1,"359":1,"365":1,"574":1,"707":1,"875":1,"1033":1,"1083":1,"1192":1,"1201":1,"1202":1,"1203":1,"1212":1,"1213":1,"1237":1,"1251":1,"1259":1,"1263":1,"1265":1,"1270":3,"1271":1,"1273":1,"1278":1,"1286":42,"1290":1,"1300":1,"1310":1,"1319":1,"1322":1,"1323":6,"1337":1,"1339":1,"1350":1,"1352":1,"1359":5,"1360":1,"1363":1,"1369":1,"1396":6,"1415":3,"1436":11,"1437":1,"1438":1,"1447":1,"1467":2,"1497":1,"1540":1,"1650":1,"1651":1,"1653":1,"1656":1,"1658":1,"1700":1,"1847":1,"1857":1,"2023":1,"2099":1,"2142":1}}],["dba",{"3":{"0":1,"1082":1,"1246":1,"2141":1,"2220":1}}],["dbo",{"2":{"719":1,"1446":1,"1469":1,"1473":1},"3":{"0":2,"719":3,"1259":2,"1286":16,"1300":1,"1323":1,"1369":1,"1415":1,"1436":4,"1438":2,"1446":1,"1467":3,"1469":1,"1473":2,"1577":1,"1684":1}}],["dbupdateconcurrencyexception",{"2":{"341":1,"343":1,"344":1,"345":1,"388":1,"393":1,"536":1,"1379":1,"1872":1,"1873":1,"1874":1,"2164":1,"2167":1},"3":{"341":2,"343":1,"344":1,"345":1,"388":1,"393":1,"536":2,"1237":2,"1286":7,"1311":3,"1359":2,"1379":1,"1872":1,"1873":1,"1874":1,"2164":1,"2167":1}}],["dbupdateexception",{"2":{"201":1,"342":1,"343":1,"345":1,"1311":1,"1873":1,"1876":1,"1934":1},"3":{"109":1,"195":1,"201":1,"342":1,"343":1,"345":1,"1259":3,"1286":4,"1289":1,"1300":1,"1309":1,"1311":5,"1359":2,"1369":2,"1396":1,"1873":2,"1876":1,"1934":1}}],["dbupdateexceptionhandler",{"2":{"125":1,"342":1,"343":1,"345":1,"1873":1,"1927":1},"3":{"0":1,"109":2,"125":2,"342":2,"343":2,"345":1,"407":1,"1199":4,"1203":1,"1207":2,"1311":13,"1774":1,"1873":1,"1927":1}}],["dbupdate",{"2":{"1806":1},"3":{"0":1,"1311":2,"1753":1,"1806":1}}],["db",{"1":{"695":1,"696":1,"697":1,"698":1,"699":1,"700":1,"701":1,"702":1,"703":1},"2":{"1477":1,"1589":1,"1597":1,"1719":1},"3":{"0":9,"10":2,"12":1,"18":1,"48":1,"68":1,"93":1,"94":1,"109":1,"117":1,"164":1,"265":2,"273":1,"318":1,"350":1,"545":1,"590":1,"593":1,"695":1,"698":1,"699":1,"700":1,"1033":1,"1133":1,"1134":1,"1175":1,"1212":8,"1237":1,"1247":3,"1274":1,"1279":1,"1286":8,"1323":2,"1326":1,"1339":1,"1350":1,"1359":1,"1380":2,"1415":2,"1436":1,"1438":4,"1441":1,"1446":1,"1455":1,"1456":1,"1467":6,"1469":1,"1471":1,"1473":1,"1474":6,"1475":3,"1476":1,"1477":3,"1478":5,"1482":1,"1490":2,"1491":1,"1494":1,"1525":1,"1526":3,"1534":3,"1543":1,"1544":1,"1549":1,"1577":1,"1589":1,"1591":1,"1596":1,"1597":1,"1599":2,"1600":2,"1611":2,"1614":1,"1618":1,"1651":1,"1652":1,"1653":2,"1654":1,"1657":2,"1666":1,"1677":1,"1700":1,"1707":1,"1709":1,"1710":1,"1719":1,"1752":1,"1753":1,"1763":1,"1774":1,"1775":1,"1815":1,"1854":2,"1855":1,"1856":1,"2008":1,"2033":1,"2034":3,"2036":1,"2096":1,"2213":1,"2219":2,"2220":1,"2230":1,"2232":2}}],["diluted",{"3":{"2058":1}}],["dilutes",{"3":{"1492":1}}],["dichotomy",{"3":{"1955":1}}],["dictating",{"3":{"1417":1}}],["dictations",{"3":{"1417":1}}],["dictation",{"3":{"1402":10,"1417":5}}],["dictated",{"3":{"1323":2,"1416":1}}],["dictates",{"3":{"1300":1,"1339":1}}],["dictionaries",{"3":{"1286":1,"1300":1,"1311":1,"1324":1,"1385":1,"1395":5,"1415":1,"1436":2}}],["dictionary",{"2":{"1882":1},"3":{"0":2,"109":2,"111":1,"157":1,"201":2,"243":1,"248":2,"330":1,"341":1,"359":1,"365":1,"583":2,"585":1,"674":1,"741":1,"881":1,"990":1,"1050":4,"1175":1,"1178":2,"1229":1,"1231":1,"1237":4,"1241":3,"1242":1,"1243":1,"1247":9,"1259":2,"1270":11,"1279":2,"1280":1,"1286":30,"1300":14,"1307":1,"1309":6,"1310":2,"1311":10,"1315":1,"1320":3,"1323":12,"1324":30,"1339":6,"1350":1,"1359":33,"1380":6,"1387":1,"1395":58,"1396":23,"1402":4,"1415":4,"1417":4,"1436":11,"1438":1,"1632":2,"1640":1,"1651":1,"1805":1,"1810":1,"1874":1,"1882":1,"1912":1,"1927":1,"1988":2,"1991":1,"2076":1,"2142":1,"2143":1}}],["dip",{"3":{"1311":3,"1323":3,"1396":1,"1436":2,"1526":1,"1538":2,"1577":2,"1582":1,"1584":1}}],["dirties",{"3":{"1455":2,"1651":1}}],["dirtied",{"3":{"1286":1}}],["dirtying",{"3":{"1455":1}}],["dirty",{"3":{"1265":1,"1270":1,"1286":1,"1324":5,"1384":2,"1395":13,"1396":1,"1436":1,"1445":1,"1450":1,"1455":4,"1461":1,"1489":1,"1496":1,"1526":1,"1561":3,"1572":1,"1573":1,"1577":1,"1586":1,"1591":2,"1596":1,"1871":1,"2045":1}}],["dir",{"2":{"861":1,"863":1,"1456":1,"1492":1},"3":{"861":2,"863":1,"1433":1,"1436":2,"1456":1,"1492":1,"1591":1}}],["directreadsofeitherclocktype",{"3":{"1436":2}}],["directed",{"3":{"1431":1,"1436":3,"1441":1,"1489":1,"1769":1,"2009":1}}],["directsave",{"3":{"1436":1}}],["directsavinghandler",{"3":{"1199":2,"1207":1,"1436":5}}],["directs",{"3":{"31":1,"359":1,"545":1,"605":1,"782":1,"1300":1}}],["directories",{"3":{"0":2,"141":1,"1005":1,"1133":1,"1135":1,"1352":1,"1436":4}}],["directory",{"2":{"53":1,"141":1,"145":1,"146":1,"147":1,"149":1,"152":1,"369":1,"370":2,"371":1,"373":1,"452":1,"468":1,"470":1,"478":1,"526":1,"528":1,"631":1,"632":3,"707":1,"733":1,"869":1,"939":1,"979":1,"980":1,"981":1,"983":1,"984":1,"1069":1,"1074":1,"1076":1,"1214":1,"1445":1,"1453":1,"1454":2,"1455":1,"1459":1,"1460":1,"1464":1,"1477":1,"1491":1,"1648":1,"1649":2,"1655":1,"1656":1,"1684":1,"1685":1,"1700":1,"1703":1,"1728":1,"1738":1,"2017":1,"2041":1,"2042":1,"2087":1,"2097":1,"2112":1},"3":{"0":6,"53":1,"135":5,"138":2,"139":1,"140":1,"141":1,"142":3,"145":2,"146":1,"147":7,"149":1,"150":2,"152":5,"268":1,"369":1,"370":8,"371":2,"373":1,"375":1,"413":1,"451":1,"452":7,"468":2,"470":4,"478":3,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"526":2,"528":1,"545":1,"564":1,"578":3,"591":1,"599":2,"618":1,"621":1,"631":1,"632":5,"665":3,"669":1,"682":1,"685":1,"707":1,"733":1,"810":1,"814":1,"825":4,"861":3,"864":1,"869":1,"881":1,"913":3,"939":1,"954":3,"956":4,"958":4,"959":3,"972":3,"979":5,"980":7,"981":3,"982":1,"983":1,"984":1,"990":1,"1004":1,"1069":2,"1070":1,"1074":4,"1075":1,"1076":1,"1078":4,"1091":3,"1212":1,"1213":4,"1214":2,"1270":1,"1286":1,"1309":1,"1311":1,"1312":3,"1324":2,"1350":2,"1359":2,"1380":1,"1395":2,"1396":1,"1417":11,"1427":2,"1431":1,"1433":1,"1436":43,"1445":1,"1453":3,"1454":7,"1455":4,"1456":4,"1458":1,"1459":3,"1460":1,"1464":3,"1466":1,"1467":2,"1477":2,"1479":2,"1486":1,"1488":1,"1489":2,"1490":2,"1491":1,"1496":9,"1515":1,"1523":1,"1526":5,"1527":1,"1535":3,"1576":2,"1577":6,"1586":3,"1591":8,"1596":1,"1600":3,"1648":1,"1649":3,"1655":1,"1656":3,"1684":1,"1685":1,"1686":1,"1700":1,"1703":1,"1726":1,"1728":1,"1738":1,"1810":1,"2017":1,"2041":1,"2042":1,"2047":1,"2053":1,"2087":1,"2097":1,"2112":1,"2220":1}}],["directionalprefixes",{"3":{"1436":1}}],["directional",{"3":{"91":1,"97":1,"98":1,"100":1,"972":1,"974":1,"975":3,"1436":5,"1620":1,"1756":1,"2025":2}}],["direction",{"0":{"1322":1},"3":{"0":2,"141":1,"193":2,"335":1,"401":1,"402":1,"404":1,"486":1,"520":1,"566":1,"691":2,"743":1,"776":2,"813":1,"931":1,"932":1,"1004":1,"1059":1,"1091":2,"1093":1,"1123":1,"1124":1,"1212":1,"1225":1,"1229":1,"1243":1,"1244":1,"1247":3,"1250":1,"1265":1,"1270":2,"1283":1,"1286":8,"1300":2,"1301":4,"1309":1,"1310":2,"1311":2,"1312":1,"1323":1,"1324":9,"1339":1,"1341":2,"1350":2,"1355":2,"1359":34,"1369":1,"1384":1,"1395":5,"1396":7,"1402":2,"1415":5,"1416":3,"1417":2,"1422":1,"1436":17,"1443":1,"1448":1,"1467":1,"1476":3,"1489":1,"1505":1,"1517":1,"1526":1,"1534":1,"1592":1,"1842":1,"1933":1,"2000":1,"2001":1,"2022":1,"2043":1,"2046":1,"2148":2,"2181":1,"2192":1,"2199":1,"2203":1}}],["directions",{"3":{"0":2,"24":1,"170":1,"180":1,"201":1,"235":1,"359":2,"608":1,"627":1,"718":1,"766":1,"838":1,"861":1,"890":1,"931":1,"946":1,"972":1,"997":1,"1091":1,"1134":1,"1211":1,"1212":1,"1233":1,"1257":1,"1286":2,"1300":1,"1311":1,"1312":1,"1323":2,"1324":4,"1350":1,"1359":3,"1391":1,"1392":1,"1395":4,"1396":3,"1402":1,"1415":2,"1417":2,"1421":1,"1431":2,"1435":1,"1436":10,"1438":2,"1453":1,"1455":4,"1465":1,"1467":1,"1476":1,"1481":1,"1489":1,"1492":2,"1577":1,"1663":1,"1670":1,"1799":1,"1854":1,"1910":1,"1957":2,"1981":1,"2000":1,"2024":1,"2044":2,"2045":2,"2046":1,"2049":1,"2142":2,"2182":1}}],["directives",{"3":{"212":1,"214":2,"219":1,"1059":1,"1062":1,"1286":5,"1311":1,"1323":1,"1324":4,"1336":1,"1339":2,"1350":1,"1359":1,"1396":2,"1415":3,"1416":1,"1443":1,"1577":1,"1591":1,"1653":1,"1689":1,"2149":2,"2150":2}}],["directive",{"3":{"0":1,"214":2,"215":1,"219":2,"676":1,"938":1,"1057":1,"1059":1,"1324":13,"1339":2,"1395":5,"1396":1,"1402":1,"1415":2,"1577":1,"1699":1,"1727":1,"2150":3,"2153":2,"2232":1}}],["directapitokenrefreshertests",{"3":{"1199":1,"1207":3,"1324":4}}],["directapitokenrefresher",{"2":{"507":1,"510":1,"511":1,"513":1,"514":1},"3":{"0":1,"507":8,"510":1,"511":1,"513":3,"514":3,"1199":4,"1203":1,"1207":2,"1300":1,"1324":13,"1416":3}}],["directly",{"3":{"0":6,"15":1,"31":2,"59":2,"63":1,"81":1,"94":1,"96":1,"97":1,"98":1,"100":1,"117":1,"166":1,"186":1,"206":1,"230":1,"235":1,"237":2,"241":1,"247":1,"280":1,"281":1,"304":1,"326":1,"341":1,"370":1,"390":1,"418":1,"428":1,"439":1,"497":1,"507":1,"523":1,"535":1,"536":1,"556":1,"566":1,"572":1,"576":1,"583":1,"599":1,"618":1,"649":1,"651":1,"672":1,"674":4,"698":1,"700":1,"703":1,"707":1,"733":1,"751":2,"758":1,"767":1,"782":2,"831":1,"861":1,"881":5,"905":1,"912":1,"921":1,"924":1,"953":1,"954":3,"956":1,"960":1,"964":1,"979":1,"987":1,"988":1,"1004":2,"1019":1,"1054":1,"1066":1,"1068":1,"1100":1,"1107":1,"1110":1,"1116":1,"1129":1,"1134":1,"1168":1,"1170":1,"1184":1,"1211":1,"1212":1,"1214":2,"1221":1,"1229":4,"1237":5,"1246":1,"1247":13,"1248":1,"1259":17,"1263":1,"1269":1,"1270":11,"1271":5,"1273":1,"1274":1,"1278":1,"1279":2,"1286":68,"1293":1,"1297":1,"1300":39,"1301":6,"1309":10,"1310":2,"1311":22,"1312":4,"1320":2,"1323":26,"1324":37,"1325":1,"1330":1,"1339":22,"1350":27,"1351":1,"1353":1,"1355":1,"1356":1,"1359":87,"1362":1,"1369":11,"1374":1,"1380":12,"1383":1,"1384":1,"1395":23,"1396":26,"1399":1,"1400":1,"1402":6,"1408":1,"1415":20,"1416":5,"1417":20,"1427":2,"1430":1,"1432":1,"1434":1,"1436":112,"1437":1,"1438":7,"1441":3,"1443":1,"1445":2,"1446":2,"1447":1,"1449":1,"1453":1,"1454":2,"1455":3,"1456":1,"1457":3,"1460":3,"1464":1,"1465":1,"1466":1,"1467":3,"1472":1,"1473":1,"1475":1,"1479":1,"1480":1,"1486":2,"1488":1,"1490":1,"1491":2,"1492":1,"1496":4,"1501":1,"1526":1,"1527":1,"1534":1,"1540":1,"1541":1,"1546":1,"1547":1,"1578":1,"1592":1,"1596":1,"1631":2,"1641":1,"1648":1,"1651":1,"1653":1,"1656":1,"1666":1,"1671":1,"1685":1,"1686":1,"1698":1,"1701":1,"1702":1,"1730":1,"1749":2,"1765":1,"1766":1,"1772":1,"1783":1,"1789":1,"1790":2,"1804":1,"1808":1,"1815":1,"1816":1,"1821":1,"1842":1,"1844":1,"1857":1,"1863":1,"1874":1,"1890":1,"1915":1,"1920":1,"1923":1,"1946":1,"1954":1,"1962":1,"1992":1,"1995":1,"2000":1,"2001":1,"2023":1,"2025":1,"2030":1,"2041":1,"2043":1,"2056":1,"2069":1,"2071":1,"2074":1,"2076":1,"2087":1,"2091":1,"2126":1,"2155":1,"2156":1,"2165":1,"2167":1,"2232":1,"2239":1}}],["direct",{"3":{"0":9,"27":1,"33":1,"39":1,"41":1,"91":2,"93":3,"98":2,"100":1,"104":1,"137":1,"140":1,"150":1,"230":2,"245":1,"260":1,"330":1,"370":1,"506":1,"507":1,"509":1,"665":1,"698":1,"700":1,"884":1,"913":1,"931":1,"954":1,"956":1,"958":2,"981":1,"986":1,"988":2,"1004":2,"1005":1,"1036":1,"1125":1,"1161":1,"1212":1,"1229":3,"1237":1,"1243":1,"1247":1,"1259":3,"1268":1,"1271":3,"1273":1,"1286":9,"1300":2,"1301":2,"1309":4,"1311":2,"1323":3,"1324":9,"1339":3,"1344":1,"1349":1,"1350":3,"1353":1,"1355":1,"1359":16,"1364":1,"1369":3,"1380":1,"1396":3,"1415":2,"1417":1,"1431":1,"1436":38,"1438":1,"1441":1,"1453":1,"1467":1,"1488":1,"1489":2,"1491":1,"1496":1,"1504":1,"1544":1,"1555":1,"1567":1,"1577":1,"1611":1,"1632":1,"1634":2,"1638":1,"1639":1,"1662":1,"1671":2,"1750":1,"1773":1,"1789":1,"1790":1,"1910":1,"1920":1,"1988":1,"1993":1,"2015":1,"2032":1,"2035":1,"2041":2,"2048":1,"2061":1,"2064":1,"2079":1,"2136":1,"2240":1}}],["dim",{"3":{"691":1}}],["dimensional",{"3":{"1417":2,"1795":1}}],["dimensions",{"3":{"442":1,"766":1,"768":1,"1270":1,"1323":3,"1359":2,"1369":1,"1425":2,"1427":4,"1474":1,"1599":1,"1795":2,"2048":1,"2128":1,"2177":3,"2180":1}}],["dimension",{"3":{"438":1,"442":1,"740":1,"1093":1,"1270":3,"1286":1,"1323":1,"1350":1,"1359":2,"1396":1,"1436":1,"1467":2,"1479":1,"1637":1,"1638":1,"1987":1}}],["dietary",{"3":{"1350":3}}],["die",{"3":{"1270":1,"1311":1,"1323":1,"1455":1,"2003":1,"2070":1}}],["died",{"3":{"565":1,"1300":1,"1323":2,"1402":1,"1410":1,"1415":1,"1436":2,"1441":1,"1729":1,"1909":1}}],["dies",{"3":{"20":1,"707":1,"855":1,"996":2,"1044":1,"1115":1,"1255":1,"1259":1,"1270":2,"1275":1,"1301":1,"1318":1,"1323":5,"1359":4,"1369":1,"1396":1,"1417":1,"1670":1,"1765":1,"1841":1,"2045":1,"2181":1,"2189":1,"2192":1}}],["div",{"2":{"1558":1,"1643":1},"3":{"1324":1,"1396":1,"1417":1,"1558":2}}],["dives",{"0":{"2215":1},"3":{"2204":1,"2226":1,"2236":1,"2245":1}}],["diverted",{"3":{"1438":1,"1496":1}}],["diversity3",{"3":{"1395":1}}],["diverging",{"3":{"785":1,"1311":1,"1395":1}}],["diverged",{"3":{"837":1,"1324":1,"1339":1,"1459":1,"2070":1,"2080":1}}],["divergent",{"3":{"511":1,"642":1,"1286":1,"1301":1,"1402":1,"1416":1,"1569":2}}],["divergence",{"3":{"0":1,"216":1,"290":1,"342":1,"484":1,"549":1,"585":1,"831":1,"840":2,"1042":2,"1044":1,"1270":2,"1286":1,"1323":3,"1324":1,"1327":1,"1339":2,"1359":1,"1396":1,"1415":1,"1436":1,"1438":1,"2152":1,"2188":1,"2192":1}}],["divergences",{"3":{"0":1,"912":1,"1415":1}}],["diverges",{"3":{"325":1,"665":1,"707":1,"766":1,"1116":1,"1323":1,"1324":1,"1436":2,"1455":2,"1528":1,"1570":1,"1579":1}}],["diverge",{"3":{"0":1,"290":1,"609":1,"640":1,"698":1,"766":1,"1034":1,"1058":1,"1270":2,"1271":1,"1286":1,"1324":3,"1350":1,"1359":7,"1379":1,"1395":2,"1396":2,"1415":1,"1436":1,"1440":1,"1454":1,"1467":1,"1838":1,"1923":1,"2043":1,"2113":1,"2193":1}}],["dive",{"0":{"2235":1,"2237":1},"3":{"1194":1,"1359":1,"1779":45,"1783":1,"1790":1,"1821":1,"1828":1,"1914":1,"1920":1,"1935":1,"2030":2,"2160":1,"2232":1}}],["diving",{"3":{"1463":1}}],["dividing",{"3":{"1350":1,"1359":1,"1800":2}}],["divide",{"3":{"1396":1,"1436":1,"1467":1}}],["dividers",{"3":{"1324":1}}],["divider",{"3":{"1324":5}}],["divided",{"3":{"1300":1,"1311":1,"1359":1,"1798":1}}],["divides",{"3":{"591":1,"1311":2,"1359":1,"2047":1}}],["division",{"3":{"423":1,"426":1,"628":1,"1090":1,"1229":1,"1257":1,"1259":2,"1286":3,"1300":2,"1323":1,"1324":1,"1339":1,"1345":1,"1359":1,"1369":1,"1380":1,"1395":4,"1396":1,"1402":2,"1406":1,"1415":2,"1436":1,"1934":1,"2068":1,"2113":1}}],["diamond",{"3":{"1247":1,"1270":1}}],["dialling",{"3":{"1402":1}}],["dialing",{"3":{"1400":1}}],["dialed",{"3":{"1309":1}}],["dialects",{"3":{"1176":1,"1396":1,"1925":1}}],["dialect",{"3":{"1175":4,"1176":2,"1178":1,"1280":3,"1286":9,"1359":1,"1928":2,"1986":1}}],["dials",{"3":{"1301":1,"1339":1,"1444":1}}],["dial",{"3":{"1286":1,"1301":1}}],["dialogs",{"3":{"649":1,"650":1,"1213":1,"1324":5,"1415":1,"1559":1,"1607":2,"1669":1,"1741":2}}],["dialog",{"2":{"1687":1},"3":{"265":1,"650":1,"907":1,"954":1,"955":1,"1324":15,"1395":9,"1396":1,"1402":2,"1417":2,"1432":2,"1436":9,"1488":1,"1496":1,"1558":1,"1607":1,"1608":1,"1669":2,"1671":1,"1687":2,"1741":1,"1742":2,"1750":1,"1760":2}}],["diagram",{"0":{"2040":1},"3":{"122":1,"1262":1,"1270":1,"1324":1,"1496":3,"1502":1,"1526":1,"1591":2,"1620":1,"1627":1,"1756":1,"1761":1,"1793":2,"1821":1,"2039":2,"2049":1,"2099":5,"2105":1,"2108":1,"2172":1}}],["diagrams",{"3":{"121":1,"1502":2,"1517":2,"1562":1,"1571":1,"1626":2,"1860":1,"2109":1}}],["diagnosability",{"3":{"1436":1}}],["diagnosable",{"3":{"0":1,"862":1,"1085":1,"1297":1,"1311":1,"1436":1,"1456":2}}],["diagnosing",{"3":{"1339":1}}],["diagnosis",{"3":{"20":1,"1259":1,"1286":1,"1324":1,"1339":1,"1415":1}}],["diagnosed",{"3":{"401":1,"1324":1,"1339":1,"1436":2}}],["diagnose",{"3":{"24":1,"390":1,"1324":1,"1467":1,"1665":1,"2056":1}}],["diagnostically",{"3":{"1311":1}}],["diagnostics",{"2":{"1229":1,"1270":1,"1324":1,"1339":1,"1415":1},"3":{"39":1,"135":2,"341":1,"395":1,"403":1,"483":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"633":1,"861":1,"863":1,"1229":1,"1247":1,"1259":6,"1270":7,"1286":4,"1300":4,"1311":6,"1323":4,"1324":4,"1333":1,"1339":11,"1359":2,"1396":2,"1402":1,"1415":1,"1417":2,"1427":5,"1436":5,"1456":2,"1490":1,"1640":1,"1670":1,"1958":1}}],["diagnostic",{"2":{"135":1,"490":1,"491":1,"492":1,"493":1,"1526":1},"3":{"0":1,"109":1,"135":2,"265":1,"403":2,"405":1,"406":1,"490":2,"491":2,"492":2,"493":2,"574":1,"633":1,"719":2,"1126":1,"1247":1,"1270":1,"1286":3,"1300":1,"1311":3,"1324":2,"1339":1,"1359":6,"1396":2,"1402":1,"1415":4,"1436":5,"1459":1,"1465":1,"1467":6,"1489":1,"1591":1,"1955":1,"2102":1}}],["digits",{"3":{"972":1,"1018":1,"1237":1,"1286":1,"1293":1,"1300":3,"1305":1,"1309":1,"1311":1,"1324":1,"1347":1,"1350":1,"1353":1,"1359":2,"1369":1,"1395":3,"1396":3,"1417":1,"1631":1,"2198":1}}],["digital",{"3":{"424":1,"426":1,"1311":4}}],["digit",{"3":{"235":1,"972":1,"974":1,"975":1,"1020":1,"1271":2,"1289":1,"1300":7,"1323":1,"1324":7,"1339":1,"1359":2,"1417":1,"1427":2,"1436":9,"1438":2,"1443":1,"1465":1,"1641":1,"1829":1}}],["digests",{"3":{"0":1,"877":1,"906":1,"1300":6,"1982":1,"2235":1,"2237":1}}],["digest",{"3":{"0":5,"373":1,"867":1,"868":1,"870":2,"872":1,"873":2,"876":1,"877":1,"905":4,"906":1,"907":1,"945":1,"946":6,"948":1,"1212":3,"1283":1,"1286":4,"1290":1,"1300":15,"1436":4,"1438":2,"1445":8,"1450":1,"1455":1,"1458":2,"1667":2,"1672":1,"1900":1,"1902":4,"1980":1,"1982":4,"1985":1}}],["didactic",{"3":{"635":1}}],["didn",{"3":{"60":1,"61":1,"1556":2}}],["did",{"3":{"0":1,"21":1,"24":2,"42":1,"96":1,"115":1,"126":1,"141":1,"149":1,"157":1,"158":1,"160":1,"175":1,"201":1,"202":1,"203":1,"230":1,"242":1,"243":1,"245":1,"249":2,"256":2,"258":1,"259":1,"260":1,"283":2,"322":1,"333":1,"335":1,"341":1,"342":1,"343":1,"350":1,"353":2,"363":1,"365":2,"374":1,"390":1,"402":1,"433":1,"475":1,"476":1,"477":1,"478":1,"486":2,"487":1,"488":2,"489":1,"490":2,"491":1,"492":1,"511":1,"517":1,"524":1,"536":1,"539":1,"549":1,"550":1,"551":1,"583":1,"586":1,"626":1,"628":1,"635":1,"638":1,"642":1,"648":1,"675":1,"681":1,"689":1,"700":1,"702":1,"709":2,"724":1,"725":1,"726":1,"733":1,"735":1,"740":1,"743":1,"789":1,"798":1,"812":1,"832":1,"867":1,"877":1,"889":1,"891":1,"905":1,"907":1,"908":1,"926":3,"938":1,"988":3,"1006":1,"1051":3,"1066":1,"1067":1,"1069":1,"1091":1,"1092":1,"1124":1,"1142":1,"1212":1,"1232":1,"1237":1,"1241":1,"1244":1,"1247":3,"1250":1,"1258":1,"1259":6,"1265":1,"1266":1,"1270":4,"1275":1,"1277":1,"1286":18,"1293":1,"1300":4,"1301":3,"1309":5,"1311":7,"1312":1,"1315":1,"1318":2,"1323":7,"1324":22,"1328":2,"1339":7,"1344":1,"1350":4,"1358":1,"1359":6,"1366":1,"1367":1,"1369":2,"1391":1,"1395":11,"1396":12,"1402":2,"1408":1,"1415":9,"1417":12,"1418":2,"1425":1,"1427":3,"1430":1,"1436":5,"1438":2,"1442":1,"1443":2,"1449":2,"1451":1,"1455":6,"1467":4,"1474":1,"1475":1,"1476":1,"1478":1,"1490":1,"1492":2,"1496":3,"1526":1,"1684":1,"1686":1,"1697":1,"1728":1,"1730":1,"1780":2,"1801":1,"1809":1,"1840":1,"1845":1,"1863":1,"1869":1,"1876":1,"1892":1,"1899":1,"1900":1,"1906":1,"1910":1,"1918":2,"1922":1,"1933":1,"1940":1,"1956":1,"1959":1,"1965":1,"1997":1,"2001":1,"2012":1,"2037":1,"2062":1,"2081":1,"2095":1,"2098":1,"2124":2,"2148":1,"2156":1,"2160":1,"2166":1,"2170":1,"2177":1,"2180":2,"2186":1,"2188":1,"2197":2,"2199":1}}],["di",{"0":{"1368":1,"1824":1,"2095":1},"1":{"977":1,"978":1,"979":1,"980":1,"981":1,"982":1,"983":1,"984":1},"3":{"0":6,"27":1,"119":1,"155":1,"157":2,"200":1,"241":1,"258":1,"341":1,"343":1,"359":1,"395":1,"401":3,"435":1,"483":1,"536":1,"550":1,"583":1,"632":1,"649":1,"651":1,"725":1,"797":1,"798":1,"802":1,"827":1,"920":1,"926":1,"977":1,"979":1,"980":3,"983":2,"997":1,"1042":1,"1051":1,"1059":2,"1175":1,"1176":1,"1178":2,"1192":3,"1202":2,"1203":2,"1212":2,"1213":1,"1214":1,"1237":1,"1244":1,"1247":3,"1254":1,"1257":1,"1259":10,"1265":2,"1269":1,"1270":16,"1271":1,"1273":1,"1274":1,"1275":2,"1280":2,"1283":1,"1284":1,"1286":36,"1294":1,"1300":13,"1301":3,"1302":1,"1309":21,"1310":4,"1311":28,"1312":2,"1313":1,"1314":1,"1315":1,"1316":1,"1323":13,"1324":31,"1332":1,"1333":1,"1339":9,"1350":5,"1352":1,"1355":2,"1357":1,"1359":29,"1360":1,"1368":1,"1369":4,"1370":1,"1380":8,"1395":7,"1396":22,"1399":1,"1402":9,"1411":1,"1413":1,"1415":21,"1416":7,"1417":19,"1418":1,"1421":1,"1422":1,"1427":6,"1429":1,"1431":1,"1436":24,"1437":1,"1438":7,"1488":1,"1489":1,"1496":2,"1513":1,"1526":1,"1538":1,"1540":1,"1577":1,"1582":1,"1585":2,"1591":1,"1638":1,"1650":3,"1651":3,"1653":2,"1656":1,"1658":1,"1661":1,"1665":1,"1668":1,"1701":1,"1730":1,"1748":1,"1765":1,"1791":1,"1826":2,"1827":2,"1830":1,"1874":1,"1881":1,"1886":1,"1908":1,"1909":1,"1915":1,"1948":1,"1956":1,"2016":1,"2074":1,"2085":1,"2095":1,"2098":3,"2115":1,"2142":1,"2163":2,"2169":1,"2180":1,"2188":1,"2198":1,"2202":2,"2232":2}}],["diffing",{"3":{"1436":1,"2079":1}}],["diffed",{"3":{"721":1,"767":1,"1311":1,"1415":1,"1930":1}}],["differed",{"3":{"724":1,"1438":1}}],["differences",{"3":{"0":2,"219":1,"454":1,"624":1,"629":1,"636":1,"769":1,"868":1,"1034":1,"1126":1,"1270":1,"1286":3,"1309":1,"1310":1,"1323":1,"1324":1,"1350":1,"1373":1,"1380":1,"1395":5,"1396":1,"1399":1,"1417":1,"1436":2,"1438":1,"1467":2}}],["difference",{"3":{"0":2,"24":1,"175":1,"218":1,"245":1,"266":1,"388":1,"425":1,"497":1,"499":1,"522":1,"539":1,"573":1,"577":1,"584":1,"592":1,"599":1,"603":1,"626":1,"633":2,"639":1,"641":2,"716":1,"723":1,"726":1,"800":1,"820":2,"825":1,"827":1,"831":1,"838":1,"839":1,"840":1,"996":1,"1034":1,"1040":1,"1051":1,"1052":1,"1099":1,"1132":1,"1150":1,"1212":1,"1229":1,"1237":2,"1247":3,"1257":1,"1259":1,"1263":1,"1270":3,"1271":1,"1286":6,"1300":4,"1301":1,"1307":1,"1309":1,"1311":2,"1323":2,"1324":8,"1339":3,"1342":1,"1350":7,"1359":31,"1362":1,"1369":2,"1380":4,"1383":1,"1395":6,"1396":12,"1402":3,"1415":6,"1416":2,"1417":4,"1436":19,"1455":1,"1459":1,"1467":1,"1489":1,"1497":1,"1537":1,"1631":1,"1685":1,"1727":1,"1728":1,"1782":1,"1817":1,"1824":1,"1861":1,"1879":1,"1926":1,"1930":1,"1943":2,"2007":1,"2039":1,"2080":1,"2105":1,"2110":1,"2119":1,"2133":1,"2174":1,"2184":1,"2187":1,"2196":1,"2200":2}}],["differentcurrencies",{"2":{"1585":1},"3":{"1585":2}}],["differentiation",{"3":{"1396":1}}],["differently",{"3":{"153":1,"160":1,"303":1,"341":1,"447":1,"520":1,"574":1,"603":1,"651":1,"819":1,"828":1,"829":1,"840":1,"854":1,"906":1,"924":1,"933":1,"996":1,"1060":1,"1117":1,"1124":1,"1174":1,"1176":1,"1247":1,"1259":1,"1286":4,"1300":8,"1301":3,"1311":1,"1323":4,"1324":6,"1339":2,"1343":1,"1350":2,"1359":5,"1369":1,"1380":1,"1396":1,"1402":1,"1415":1,"1435":1,"1436":7,"1455":1,"1727":1,"1730":1,"1748":1,"1819":1,"1874":1,"1909":1,"2068":1,"2130":1,"2198":1,"2228":1}}],["different",{"0":{"719":1,"1228":1,"1295":1,"1782":1},"3":{"0":11,"10":1,"24":1,"26":1,"68":1,"77":1,"91":1,"95":1,"97":1,"135":2,"136":1,"137":1,"147":1,"156":1,"157":1,"159":3,"161":1,"166":2,"177":1,"186":1,"195":1,"201":1,"202":1,"216":1,"217":1,"225":1,"227":1,"236":1,"242":3,"244":3,"248":1,"255":3,"256":1,"266":1,"274":1,"283":1,"299":1,"301":1,"317":2,"318":2,"320":1,"340":1,"350":1,"353":1,"359":2,"388":1,"390":1,"401":1,"447":2,"457":1,"465":1,"474":1,"483":1,"500":1,"506":2,"509":1,"528":1,"530":1,"537":1,"556":1,"574":1,"577":1,"608":1,"609":2,"617":2,"619":1,"626":1,"634":2,"639":1,"640":1,"650":1,"651":1,"666":2,"684":1,"689":1,"690":1,"692":1,"697":1,"698":1,"699":1,"702":1,"707":1,"713":1,"714":1,"715":1,"718":1,"726":2,"732":2,"733":1,"740":1,"741":2,"749":1,"758":1,"773":1,"774":1,"790":1,"791":1,"799":1,"809":1,"820":3,"821":1,"827":4,"829":4,"830":1,"832":2,"856":1,"869":1,"888":1,"890":1,"891":1,"899":1,"956":1,"957":1,"974":1,"980":2,"989":1,"995":2,"997":1,"1011":1,"1014":1,"1018":3,"1021":1,"1028":1,"1033":1,"1035":1,"1043":4,"1049":1,"1067":1,"1079":1,"1091":2,"1092":1,"1099":1,"1116":1,"1124":1,"1125":1,"1126":2,"1134":1,"1144":1,"1183":1,"1212":1,"1229":1,"1232":1,"1236":1,"1237":3,"1239":1,"1240":1,"1242":1,"1247":9,"1259":9,"1261":1,"1267":3,"1270":9,"1271":2,"1273":1,"1276":1,"1279":1,"1281":1,"1284":1,"1286":35,"1289":1,"1290":1,"1297":1,"1300":17,"1301":7,"1302":1,"1306":1,"1307":1,"1309":4,"1310":7,"1311":13,"1312":1,"1323":22,"1324":33,"1327":2,"1339":20,"1343":2,"1345":1,"1350":11,"1351":1,"1353":1,"1355":1,"1359":73,"1362":1,"1368":1,"1369":5,"1372":1,"1375":1,"1379":1,"1380":12,"1389":2,"1391":1,"1395":22,"1396":41,"1402":5,"1406":1,"1409":1,"1415":21,"1416":4,"1417":14,"1419":1,"1423":2,"1427":4,"1430":1,"1431":1,"1435":3,"1436":64,"1438":2,"1441":1,"1442":1,"1443":1,"1446":1,"1447":1,"1455":4,"1458":1,"1459":1,"1460":1,"1462":1,"1467":2,"1471":1,"1472":1,"1475":2,"1485":1,"1486":1,"1488":3,"1489":2,"1492":3,"1496":6,"1498":2,"1536":1,"1537":1,"1570":1,"1627":1,"1630":3,"1638":1,"1639":5,"1641":4,"1701":1,"1719":1,"1781":1,"1794":1,"1797":1,"1804":2,"1816":2,"1820":1,"1822":1,"1825":1,"1828":1,"1832":1,"1839":1,"1849":4,"1851":1,"1853":1,"1858":1,"1860":1,"1862":2,"1863":1,"1864":1,"1865":1,"1866":1,"1870":1,"1887":1,"1888":3,"1890":2,"1896":1,"1902":1,"1908":4,"1909":2,"1911":3,"1912":1,"1916":1,"1922":2,"1923":2,"1925":2,"1934":2,"1950":1,"1952":1,"1953":2,"1959":2,"1960":1,"1962":1,"1963":1,"1964":1,"1977":1,"1982":1,"1986":2,"1992":2,"1994":2,"1995":2,"1997":1,"1999":3,"2000":2,"2001":2,"2024":1,"2033":1,"2036":1,"2037":1,"2041":2,"2042":1,"2043":2,"2047":1,"2063":1,"2069":2,"2070":1,"2073":1,"2077":1,"2082":2,"2086":1,"2109":3,"2110":1,"2113":4,"2114":2,"2130":2,"2133":1,"2134":1,"2142":2,"2144":1,"2152":1,"2156":1,"2164":1,"2167":1,"2192":4,"2193":3,"2199":1}}],["differ",{"3":{"0":2,"32":1,"97":1,"202":1,"244":1,"282":1,"318":1,"359":1,"491":1,"558":2,"626":1,"633":1,"640":1,"641":1,"642":1,"667":1,"689":1,"691":1,"764":1,"766":1,"827":1,"828":1,"829":1,"831":1,"877":1,"889":1,"973":1,"980":1,"1035":1,"1042":1,"1058":1,"1116":1,"1124":1,"1127":1,"1132":1,"1175":1,"1212":1,"1229":1,"1247":3,"1249":1,"1263":1,"1270":2,"1271":1,"1280":1,"1286":5,"1300":1,"1301":1,"1309":2,"1311":4,"1323":6,"1324":4,"1339":5,"1350":1,"1359":6,"1369":1,"1392":1,"1395":7,"1396":5,"1402":1,"1406":1,"1415":2,"1416":1,"1417":5,"1436":20,"1447":1,"1466":1,"1467":3,"1489":1,"1496":2,"1667":1,"1686":1,"1712":1,"1861":1,"1947":1,"1999":1,"2078":1,"2188":1,"2230":1}}],["differs",{"3":{"0":1,"27":1,"30":1,"62":1,"91":1,"93":1,"126":1,"195":1,"452":1,"472":1,"585":1,"618":1,"640":1,"668":2,"698":1,"766":1,"783":1,"819":1,"828":2,"831":1,"916":1,"982":1,"1035":1,"1037":1,"1212":1,"1247":2,"1249":1,"1259":1,"1262":1,"1270":1,"1271":1,"1280":1,"1286":9,"1295":1,"1300":2,"1309":1,"1310":2,"1311":4,"1320":1,"1323":2,"1324":9,"1338":1,"1339":2,"1350":2,"1359":7,"1369":1,"1372":1,"1380":3,"1395":9,"1396":19,"1399":1,"1402":1,"1415":7,"1416":2,"1417":2,"1427":2,"1429":1,"1436":16,"1441":1,"1442":1,"1447":1,"1455":1,"1456":1,"1459":1,"1467":1,"1475":1,"1485":1,"1486":1,"1490":1,"1641":1,"1656":1,"1769":3,"1791":1,"1826":1,"1861":1,"1922":1,"1934":1,"1976":1,"1977":1,"1996":1,"2032":1,"2057":1,"2076":1,"2078":2,"2126":1}}],["differing",{"3":{"0":1,"1034":1,"1212":1,"1237":1,"1300":1,"1311":1,"1323":3,"1324":2,"1350":2,"1359":1,"1395":2,"1396":1,"1415":1,"1436":5,"1902":1}}],["diffable",{"3":{"610":1,"708":1}}],["diffs",{"3":{"0":1,"1212":2,"1237":1,"1274":1,"1286":2,"1300":1,"1431":1,"1436":1,"1438":2,"1455":1,"1492":1,"1534":1,"1577":1,"1663":1,"1929":2,"1930":2,"2044":1,"2045":1,"2059":1,"2079":1,"2232":1}}],["diff",{"2":{"452":1,"566":1},"3":{"0":6,"135":1,"160":1,"451":2,"452":1,"562":1,"564":2,"565":1,"566":7,"567":1,"572":1,"573":1,"592":1,"608":1,"619":1,"714":2,"715":2,"716":1,"750":1,"766":2,"838":1,"840":2,"863":1,"964":1,"972":1,"981":2,"982":2,"1005":1,"1019":2,"1020":1,"1177":1,"1178":1,"1270":1,"1286":5,"1300":2,"1323":1,"1359":1,"1380":1,"1396":1,"1427":1,"1436":16,"1445":1,"1453":2,"1455":12,"1459":1,"1463":3,"1486":2,"1492":3,"1496":5,"1524":3,"1526":1,"1530":1,"1531":4,"1532":1,"1546":1,"1577":2,"1586":1,"1591":1,"1661":1,"1673":1,"1684":1,"1729":1,"1825":1,"1839":1,"1876":1,"2043":1,"2047":1,"2158":1,"2195":1,"2200":1}}],["disguise",{"3":{"2128":1}}],["disguised",{"3":{"1395":1}}],["dissolves",{"3":{"1955":1}}],["dissolved",{"3":{"130":1,"135":1,"139":1,"1004":1,"1436":1,"1496":1}}],["dismissquestionasync",{"3":{"1402":1}}],["dismissasync",{"3":{"1396":2,"1402":1}}],["dismissals",{"3":{"1417":2}}],["dismissal",{"3":{"1324":1,"1399":1,"1402":3,"1417":1}}],["dismiss",{"3":{"1396":2,"1397":1,"1398":1,"1402":15,"1415":1,"1526":1}}],["dismisses",{"3":{"1324":1,"1632":1}}],["dismissed",{"3":{"1324":4,"1395":1,"1396":1,"1398":1,"1399":1,"1401":1,"1402":17,"1417":2,"1630":1,"1632":1}}],["dismissing",{"3":{"649":1,"1324":2,"1402":1,"1632":1}}],["disjunction",{"3":{"1324":1}}],["disjunctive",{"3":{"1247":1}}],["disjoint",{"3":{"0":2,"241":1,"247":1,"253":1,"731":1,"733":3,"734":1,"736":1,"737":1,"827":1,"1212":1,"1286":1,"1300":1,"1301":3,"1311":1,"1324":1,"1359":1,"1379":1,"1436":1,"1438":1,"1668":1,"1916":1,"2232":1}}],["disqualifies",{"3":{"1244":1}}],["disqualify",{"3":{"335":1,"336":1,"1244":2,"1247":3,"1270":1,"1309":1,"1310":1}}],["disqualifying",{"3":{"335":1,"1244":1,"1247":1}}],["disregard",{"3":{"1091":1,"1427":1}}],["disincentive",{"3":{"692":1}}],["disk",{"3":{"401":1,"451":1,"1124":1,"1163":1,"1300":1,"1324":1,"1339":1,"1369":1,"1416":2,"1417":1,"1427":1,"1435":1,"1436":12,"1453":1,"1459":1,"1468":1,"1486":1,"1496":2,"2140":2,"2144":1,"2146":1}}],["disabilities",{"3":{"1629":1}}],["disabling",{"0":{"1882":1},"3":{"0":1,"557":1,"1259":1,"1286":1,"1300":5,"1311":2,"1323":2,"1339":2,"1396":1,"1433":1,"1436":1,"1465":1,"1467":1,"1490":1,"1724":1}}],["disableforunsafehttpmethods",{"3":{"1339":2}}],["disablefor",{"3":{"1339":3}}],["disablement",{"3":{"1323":1,"2166":1}}],["disableantiforgery",{"3":{"1300":1,"1324":2}}],["disableasync",{"3":{"1293":1,"1300":1,"1323":2}}],["disableaspnetcoremetrics=true",{"3":{"1443":1,"1446":1}}],["disableaspnetcoremetrics",{"2":{"397":1},"3":{"0":1,"395":1,"397":2,"409":1,"1443":1,"1467":1,"1496":1}}],["disabletwofactorhandlerbase",{"2":{"1322":1,"2198":1},"3":{"1199":2,"1203":1,"1207":2,"1300":2,"1322":3,"1323":4,"1496":1,"2198":1}}],["disablelocalcacheread",{"3":{"733":2,"1301":1}}],["disablelocalcachewrite",{"2":{"733":1},"3":{"733":2,"1301":1}}],["disableunderlyingdata",{"2":{"733":1,"734":1,"1301":1},"3":{"733":1,"734":1,"1301":1}}],["disablehealthchecks",{"2":{"245":1,"1332":1},"3":{"245":1,"1332":1,"1339":4,"1442":1}}],["disablehttpclientmetrics=true",{"3":{"1443":1,"1446":1,"1677":1}}],["disablehttpclientmetrics",{"2":{"1476":1,"1668":1},"3":{"0":1,"395":1,"397":1,"407":1,"1333":1,"1339":1,"1443":1,"1467":1,"1476":1,"1577":1,"1668":1,"2010":1,"2156":1,"2160":1}}],["disable",{"2":{"1552":1,"1845":1},"3":{"0":2,"19":1,"39":1,"207":1,"259":1,"388":1,"600":1,"774":1,"789":1,"907":1,"1059":1,"1060":1,"1212":1,"1237":4,"1286":3,"1300":11,"1301":1,"1309":3,"1311":6,"1323":4,"1324":2,"1339":7,"1359":1,"1379":1,"1402":1,"1415":1,"1417":3,"1436":2,"1455":1,"1466":1,"1467":1,"1496":1,"1524":1,"1552":1,"1596":1,"1845":1,"1882":1,"1886":1,"1969":1,"2065":1,"2094":1,"2108":1,"2166":1,"2169":1,"2199":1,"2232":1}}],["disablescrolltracking",{"3":{"1324":1}}],["disables",{"3":{"0":1,"60":1,"236":1,"481":1,"583":1,"618":1,"620":1,"690":1,"827":1,"905":1,"917":1,"1109":1,"1211":1,"1256":1,"1259":1,"1300":1,"1301":1,"1309":1,"1311":1,"1314":1,"1323":3,"1324":4,"1339":3,"1395":1,"1402":4,"1415":1,"1436":1,"1454":1,"1455":1,"1467":3,"1488":1,"1591":1,"1631":1,"1639":1,"1878":1,"1885":1,"2057":1,"2203":1}}],["disableruntimemetrics=true",{"3":{"1443":1,"1446":1,"1677":1}}],["disableruntimemetrics",{"2":{"1476":1,"1668":1},"3":{"0":1,"395":1,"397":1,"1333":1,"1339":1,"1443":1,"1467":1,"1476":1,"1577":1,"1668":1,"2010":1,"2156":1,"2160":1}}],["disabledstubhook",{"3":{"1436":1}}],["disabledsessionbookmarkvalidationservice",{"2":{"583":1,"1314":1,"1349":1,"1377":1},"3":{"583":2,"585":1,"1199":2,"1203":1,"1207":2,"1314":1,"1349":3,"1350":11,"1377":1,"1380":3,"1415":1}}],["disabled=",{"3":{"1324":1}}],["disabledmodulenames",{"2":{"1315":1,"1882":1},"3":{"1315":1,"1323":2,"1882":1}}],["disabledfakeexportservice",{"3":{"1199":3,"1207":1,"1436":6}}],["disabledfeaturehandlertests",{"3":{"1199":1,"1207":2}}],["disabledfeaturehandler",{"2":{"306":1,"1928":1,"2137":1,"2139":1},"3":{"0":1,"300":2,"306":2,"1199":4,"1203":1,"1207":2,"1311":14,"1396":2,"1928":1,"2137":3,"2139":1}}],["disabledusernotificationexportservice",{"2":{"1308":1},"3":{"585":1,"1199":3,"1203":1,"1207":2,"1308":1,"1309":9,"1415":1,"1438":1}}],["disableduserengagementexportservice",{"3":{"585":1,"1199":2,"1203":1,"1207":2,"1396":9}}],["disabledusersalesexportservice",{"2":{"583":1},"3":{"583":2,"585":1}}],["disabledattendeequeryservicetests",{"3":{"1199":1,"1207":2,"1487":1}}],["disabledattendeequeryservice",{"2":{"586":1,"1412":1},"3":{"585":1,"586":1,"1199":3,"1203":1,"1207":2,"1350":1,"1412":2,"1415":9}}],["disabledbookmarkcountservicetests",{"3":{"1199":1,"1207":2,"1487":1}}],["disabledbookmarkcountservice",{"3":{"585":1,"1199":3,"1203":1,"1207":2,"1323":1,"1350":1,"1359":3,"1396":11,"1415":1}}],["disabledcustomerservice",{"3":{"583":2}}],["disabledproductvariantservice",{"2":{"583":1,"1751":1,"1772":1},"3":{"583":2,"1751":1,"1772":1}}],["disabledeventlivevalidationservicetests",{"3":{"1199":1,"1207":2,"1437":1}}],["disabledeventlivevalidationservice",{"2":{"583":1,"1314":1,"1349":1,"1377":1},"3":{"583":2,"585":1,"1199":3,"1203":1,"1207":2,"1314":1,"1349":3,"1350":19,"1359":1,"1377":1,"1380":3,"1496":1}}],["disabled",{"0":{"1412":1,"2137":1,"2196":1},"2":{"300":1,"306":1},"3":{"0":5,"15":1,"31":1,"53":3,"59":2,"63":2,"124":1,"299":1,"300":3,"301":1,"302":1,"304":2,"306":3,"397":1,"482":1,"556":1,"565":1,"582":2,"583":10,"584":1,"585":11,"587":2,"607":1,"725":1,"729":1,"789":1,"790":1,"792":1,"827":1,"914":1,"931":2,"932":1,"1018":2,"1060":1,"1093":1,"1212":2,"1257":1,"1258":1,"1259":3,"1263":3,"1270":11,"1284":1,"1286":8,"1288":1,"1300":2,"1308":1,"1309":11,"1311":22,"1312":2,"1313":1,"1314":3,"1315":3,"1318":1,"1320":2,"1323":20,"1324":6,"1339":5,"1341":2,"1349":2,"1350":12,"1359":3,"1366":1,"1369":3,"1378":1,"1380":5,"1391":1,"1395":10,"1396":23,"1398":1,"1402":5,"1412":1,"1413":1,"1415":5,"1417":1,"1420":1,"1421":1,"1427":2,"1431":1,"1436":13,"1437":1,"1438":1,"1455":1,"1459":1,"1465":1,"1467":2,"1469":1,"1470":1,"1472":1,"1474":1,"1487":3,"1488":1,"1489":1,"1504":1,"1508":1,"1526":2,"1535":1,"1551":1,"1552":1,"1561":2,"1577":1,"1626":2,"1653":1,"1661":1,"1662":2,"1666":3,"1671":1,"1720":1,"1751":1,"1770":1,"1772":1,"1790":2,"1821":1,"1842":1,"1882":6,"1884":1,"1886":1,"1896":1,"1928":2,"2019":2,"2051":1,"2094":1,"2113":1,"2134":1,"2135":1,"2136":3,"2137":2,"2138":1,"2139":4,"2156":1,"2166":1,"2173":1,"2180":2,"2196":1,"2203":1,"2232":2}}],["disallowed",{"3":{"1359":2,"1423":1,"2175":1}}],["disambiguated",{"3":{"1416":1}}],["disambiguates",{"3":{"1396":1}}],["disambiguate",{"3":{"1339":1,"1415":3,"1436":1}}],["disambiguation",{"3":{"1339":2,"1436":2,"1496":1}}],["disarmed",{"3":{"1300":1}}],["disarm",{"3":{"1300":2}}],["disarms",{"3":{"819":1,"905":1,"1300":2,"1843":1}}],["disappearance",{"3":{"1417":1}}],["disappears",{"3":{"818":1,"840":1,"1247":1,"1286":1,"1324":1,"1339":1,"1350":1,"1395":1,"1436":4,"1442":1,"1467":1,"1727":1,"1858":1,"1902":1,"1994":1,"2076":1}}],["disappear",{"3":{"303":1,"1247":1,"1309":1,"1367":1,"1433":1,"1436":1,"1438":1,"2193":1}}],["disappearing",{"3":{"137":1,"1417":1,"1436":1}}],["disappeared",{"3":{"135":1,"454":1,"1436":1,"1474":1,"1673":1}}],["disaster",{"0":{"1474":1},"3":{"68":1,"601":1,"602":1,"608":1,"609":1,"614":1,"971":1,"972":1,"1192":1,"1436":1,"1438":1,"1466":1,"1467":4,"1470":1,"1471":3,"1472":3,"1474":35,"1475":1,"1476":2,"1478":1,"1482":3,"1483":3,"1484":2,"1496":1,"1525":2,"1526":1,"1534":2,"1535":1,"1566":2,"1590":1,"1591":2,"1599":1,"1706":1,"1707":1,"1709":1,"1852":1,"2033":1,"2034":1,"2043":1}}],["disagreeing",{"3":{"940":1,"1270":1,"1309":1,"1324":2,"1348":1,"1916":1,"2011":1}}],["disagrees",{"3":{"890":1,"905":1,"1093":1,"1269":1,"1286":2,"1300":1,"1309":1,"1396":1,"1436":1,"1453":1,"1496":1,"1561":1,"2046":1}}],["disagree",{"3":{"0":1,"136":1,"530":1,"556":1,"617":1,"742":1,"811":1,"827":1,"889":1,"905":1,"926":1,"1026":1,"1034":1,"1079":1,"1142":1,"1178":1,"1190":1,"1241":1,"1247":2,"1251":1,"1270":2,"1271":3,"1281":1,"1286":7,"1288":1,"1300":10,"1309":1,"1311":2,"1312":1,"1322":1,"1323":2,"1324":4,"1350":2,"1352":1,"1359":13,"1366":1,"1369":1,"1372":1,"1395":3,"1396":5,"1402":6,"1409":1,"1415":5,"1417":1,"1436":7,"1438":1,"1488":1,"1648":1,"1695":1,"1722":1,"1729":1,"1767":1,"1924":1,"1962":1,"2024":1,"2073":1}}],["disagreed",{"3":{"0":1,"335":1,"837":1,"1323":1,"1339":1,"1359":1,"1415":1}}],["disagreement",{"3":{"0":1,"691":1,"1266":1,"1270":4,"1300":1,"1436":1,"1722":1,"1729":1}}],["disclaims",{"3":{"1591":1}}],["discloses",{"3":{"1247":1,"1641":1,"1765":1}}],["disclose",{"3":{"856":1,"1300":1}}],["disclosed",{"3":{"854":1,"1396":1}}],["disclosure",{"3":{"388":1,"1191":1,"1200":1,"1270":1,"1311":1,"1359":2,"1396":5,"1922":1}}],["discharges",{"3":{"1395":1}}],["discharge",{"3":{"1323":1}}],["discharged",{"3":{"0":1,"1010":2,"1395":1}}],["discussion",{"3":{"1350":1,"1359":1,"1467":1}}],["discuss",{"3":{"1311":1}}],["discussed",{"3":{"256":1,"1395":1}}],["discrete",{"3":{"1380":1}}],["discrepancy",{"3":{"1190":1}}],["discriminating",{"3":{"1436":2}}],["discrimination",{"3":{"100":1,"1436":13,"1631":1}}],["discriminate",{"3":{"1359":1}}],["discriminates",{"3":{"1270":2,"1436":4}}],["discriminated",{"3":{"920":1,"926":1,"1265":1,"1270":4,"1323":1,"1396":1,"1415":1,"1959":1}}],["discriminators",{"3":{"1350":1,"1369":1}}],["discriminator",{"1":{"779":1,"780":1,"781":1,"782":1,"783":1,"784":1,"785":1,"786":1},"3":{"0":3,"665":4,"667":2,"669":1,"697":1,"733":1,"779":1,"780":1,"782":10,"783":1,"784":1,"786":1,"923":1,"926":3,"963":1,"964":2,"968":1,"1212":2,"1233":1,"1237":2,"1270":1,"1286":2,"1301":1,"1309":1,"1336":1,"1339":1,"1350":7,"1355":1,"1359":5,"1369":1,"1395":1,"1396":16,"1398":1,"1402":2,"1769":1,"1970":1,"1972":1,"2232":2}}],["disciplines",{"3":{"1396":1}}],["disciplined",{"3":{"537":1,"1286":1,"1417":1,"1526":1,"2161":1}}],["discipline",{"0":{"1221":1,"1919":1},"1":{"2070":1,"2071":1,"2072":1,"2073":1,"2074":1,"2075":1,"2076":1,"2077":1,"2078":1,"2079":1,"2080":1},"3":{"0":1,"41":1,"69":1,"79":1,"80":1,"133":1,"135":1,"136":1,"148":1,"162":1,"196":1,"342":1,"346":1,"370":1,"376":1,"398":2,"414":1,"415":1,"449":1,"472":1,"537":1,"549":1,"565":1,"568":1,"592":1,"610":1,"620":1,"622":1,"658":1,"676":1,"700":1,"702":1,"729":1,"750":1,"801":1,"839":1,"842":1,"844":1,"845":2,"847":1,"863":1,"865":1,"882":1,"890":1,"955":1,"990":1,"1012":1,"1044":1,"1076":1,"1223":1,"1229":4,"1237":2,"1254":1,"1259":1,"1270":1,"1286":6,"1300":4,"1303":1,"1311":7,"1317":1,"1320":2,"1323":3,"1324":3,"1339":1,"1346":1,"1350":2,"1352":1,"1359":13,"1369":5,"1374":1,"1384":1,"1386":1,"1395":4,"1396":12,"1398":1,"1402":2,"1415":4,"1417":5,"1436":40,"1455":2,"1467":2,"1526":1,"1538":1,"1555":1,"1575":1,"1576":1,"1577":2,"1671":1,"1795":1,"1805":1,"1833":1,"1834":1,"1851":1,"1853":1,"1859":1,"1875":1,"1885":1,"1889":2,"1891":3,"1910":1,"1924":2,"1935":1,"1936":1,"1941":1,"1944":1,"1949":1,"1954":1,"1972":1,"2034":1,"2038":1,"2044":1,"2069":1,"2070":2,"2098":1,"2107":1,"2120":1,"2131":1,"2156":1,"2159":1,"2164":1,"2190":1,"2192":1,"2210":1,"2212":1}}],["discourage",{"3":{"1570":1}}],["discouraging",{"3":{"530":1}}],["discounted",{"3":{"1028":1,"1742":1,"1748":1,"1749":1,"1750":2,"1764":2,"1765":1,"1767":1,"1768":1,"1773":1,"1776":1}}],["discount",{"3":{"0":2,"387":1,"390":2,"392":2,"1026":8,"1027":2,"1028":5,"1029":1,"1030":3,"1160":1,"1212":1,"1746":6,"1748":11,"1750":5,"1764":4,"1765":1,"1767":14,"1768":1,"1769":2,"1773":1,"1832":1}}],["discounting",{"3":{"0":1,"1026":1,"1767":1}}],["discounts",{"3":{"0":1,"1768":1}}],["disconnection",{"3":{"1640":1}}],["disconnects",{"3":{"1324":2}}],["disconnect",{"3":{"109":1,"612":1,"1311":1,"1324":3,"1438":1,"1669":1,"1927":1}}],["disconnectedcircuit",{"3":{"1416":1}}],["disconnectedcircuitretentionperiod",{"2":{"1123":1,"2000":1},"3":{"1123":1,"1324":1,"2000":1}}],["disconnectedcircuitretentionseconds",{"2":{"830":1,"831":1,"832":1},"3":{"830":1,"831":1,"832":1,"1324":2}}],["disconnectedcircuitmaxretained",{"2":{"830":1,"831":1,"832":1,"1123":1,"1124":1,"2000":1},"3":{"830":1,"831":1,"832":1,"1123":1,"1124":1,"1324":4,"2000":1}}],["disconnected",{"3":{"0":2,"507":1,"608":1,"831":1,"1124":1,"1247":1,"1286":1,"1311":3,"1324":13,"1416":1,"1417":1,"1438":1,"1640":1,"2158":1}}],["discoverrunbookalertheadings",{"3":{"1436":1}}],["discoverroutedpages",{"3":{"1436":1}}],["discoverdocumentedroutes",{"3":{"1436":1}}],["discovermembers",{"3":{"964":1,"1237":2}}],["discoveralertspecs",{"3":{"1436":1}}],["discoverability",{"3":{"227":1,"1270":1}}],["discoverable",{"3":{"86":1,"450":1,"555":1,"609":1,"1259":1,"1286":4,"1300":1,"1311":1,"1323":2,"1324":1,"1339":1,"1350":1,"1359":2,"1396":1,"1415":3,"1436":2,"1460":1,"1539":1,"1564":1,"1570":1,"1571":1,"2132":1}}],["discoverandregister",{"2":{"117":1,"633":1,"636":1,"1315":1,"1378":1,"1413":1,"1881":1,"1883":1},"3":{"117":2,"583":2,"633":1,"636":1,"1309":1,"1311":3,"1315":1,"1323":4,"1378":1,"1380":2,"1396":2,"1413":1,"1415":1,"1881":3,"1883":1}}],["discovering",{"3":{"69":1,"908":1,"965":1,"1051":1,"1270":1,"1323":1,"1324":1,"1330":1,"1359":2,"1436":1,"1440":1,"2046":1,"2203":1}}],["discovers",{"3":{"63":1,"135":1,"295":1,"584":1,"586":1,"609":1,"649":1,"905":1,"1212":1,"1259":1,"1286":3,"1300":1,"1309":1,"1311":3,"1313":1,"1323":2,"1324":1,"1339":1,"1359":2,"1377":1,"1380":1,"1396":3,"1415":5,"1416":1,"1434":1,"1436":7,"1453":1,"1458":1,"1476":1,"1486":1,"1492":1,"1662":1,"1671":1,"1684":2,"1728":1,"1729":1,"1810":1,"1878":1,"1886":1,"1896":1,"1934":1,"2094":1,"2097":1,"2105":1}}],["discovered",{"1":{"1878":1,"1879":1,"1880":1,"1881":1,"1882":1,"1883":1,"1884":1,"1885":1,"1886":1},"3":{"0":2,"53":1,"59":1,"63":1,"446":1,"454":1,"583":2,"609":1,"619":1,"648":1,"673":2,"792":1,"840":1,"964":1,"1051":1,"1082":1,"1084":1,"1135":1,"1212":1,"1235":1,"1237":2,"1259":3,"1270":3,"1286":7,"1309":4,"1311":2,"1315":1,"1323":6,"1324":2,"1339":1,"1359":16,"1369":2,"1380":1,"1396":8,"1402":5,"1415":9,"1416":1,"1417":1,"1427":1,"1431":1,"1436":30,"1438":1,"1441":2,"1467":2,"1476":2,"1486":1,"1509":1,"1544":1,"1550":1,"1651":2,"1653":1,"1790":1,"1877":1,"1878":1,"1883":1,"1951":1,"2045":2,"2046":1,"2058":1,"2077":1,"2093":1,"2132":1,"2203":1,"2207":1,"2214":1,"2232":1}}],["discover",{"3":{"0":1,"57":1,"62":1,"401":1,"536":1,"583":1,"610":1,"1052":1,"1067":1,"1092":1,"1237":1,"1259":1,"1275":1,"1286":1,"1309":1,"1310":1,"1311":1,"1313":1,"1323":3,"1324":2,"1339":1,"1354":1,"1369":2,"1380":1,"1395":3,"1438":1,"1458":1,"1467":1,"1626":1,"1789":1,"1830":1,"1896":1,"1899":1,"2027":1,"2107":1,"2129":1,"2135":1,"2158":1,"2163":1,"2168":1}}],["discovery",{"0":{"1315":1,"1956":1},"1":{"28":1,"29":1,"30":1,"31":1,"32":1,"33":1,"34":1,"35":1},"3":{"0":9,"28":1,"30":1,"31":8,"32":3,"33":5,"35":2,"53":1,"55":1,"61":1,"91":1,"92":3,"93":1,"95":1,"96":1,"97":1,"98":5,"102":1,"105":1,"132":1,"175":1,"182":1,"234":1,"235":3,"237":1,"239":1,"401":1,"448":1,"527":1,"583":3,"584":2,"585":1,"609":1,"628":1,"650":1,"749":2,"751":1,"758":1,"827":5,"834":1,"837":1,"838":2,"868":1,"913":2,"963":1,"966":1,"967":1,"1018":1,"1052":1,"1066":1,"1184":1,"1202":1,"1203":1,"1212":7,"1215":1,"1281":1,"1282":1,"1286":17,"1288":2,"1300":1,"1309":3,"1310":2,"1311":35,"1312":7,"1315":4,"1316":1,"1323":9,"1324":12,"1325":1,"1328":1,"1329":1,"1333":1,"1334":2,"1337":1,"1339":20,"1350":2,"1359":6,"1369":3,"1378":1,"1379":1,"1380":10,"1396":3,"1415":5,"1416":1,"1436":17,"1437":1,"1438":4,"1439":1,"1441":11,"1442":1,"1444":1,"1446":2,"1447":3,"1450":2,"1454":1,"1455":2,"1460":1,"1467":7,"1469":1,"1475":1,"1486":2,"1488":1,"1489":4,"1492":1,"1526":1,"1547":1,"1576":1,"1577":3,"1641":1,"1647":1,"1653":1,"1656":1,"1660":2,"1663":3,"1667":2,"1670":2,"1671":1,"1677":1,"1687":1,"1702":1,"1707":1,"1718":1,"1724":2,"1727":1,"1772":2,"1773":1,"1785":1,"1790":1,"1792":1,"1825":1,"1881":3,"1885":1,"1895":1,"1896":2,"1897":1,"1898":2,"1899":5,"1904":1,"1956":1,"1959":1,"2000":1,"2002":1,"2004":2,"2006":1,"2007":1,"2009":2,"2014":3,"2019":2,"2021":1,"2022":2,"2023":2,"2024":2,"2025":1,"2027":1,"2030":1,"2046":1,"2056":1,"2058":1,"2077":1,"2108":1,"2111":1,"2131":1,"2132":3,"2133":2,"2230":1,"2232":1}}],["discardunstartedconnectionasync",{"3":{"1324":1}}],["discardabandonedcapture",{"2":{"715":1,"1274":1},"3":{"715":1,"1274":1,"1286":3}}],["discarding",{"3":{"518":1,"707":1,"1270":1,"1278":1,"1286":1,"1300":1,"1311":1,"1324":3,"1417":4,"1436":2,"1947":1}}],["discards",{"3":{"21":1,"201":1,"255":1,"265":1,"520":1,"538":1,"540":1,"690":1,"1259":2,"1271":1,"1286":3,"1309":1,"1323":3,"1324":9,"1346":1,"1359":1,"1390":1,"1396":4,"1402":3,"1415":1,"1417":1,"1438":1,"1476":1,"1481":1,"1667":1}}],["discarded",{"3":{"0":2,"21":1,"534":1,"539":1,"640":1,"716":1,"854":1,"863":1,"991":1,"1100":1,"1229":1,"1259":1,"1286":2,"1294":1,"1300":4,"1309":1,"1311":2,"1323":1,"1324":7,"1350":1,"1359":7,"1395":1,"1396":3,"1402":1,"1415":1,"1416":1,"1417":3,"1436":1,"1492":1,"1641":2,"1767":1,"1822":1,"1947":1,"1949":1,"2183":1}}],["discard",{"3":{"0":1,"715":1,"721":1,"988":1,"1101":1,"1233":1,"1237":1,"1259":1,"1270":1,"1278":1,"1311":1,"1324":4,"1359":2,"1369":1,"1395":2,"1396":6,"1400":1,"1402":2,"1416":1,"1417":1,"1436":6,"1438":1,"1526":1,"1531":1,"1547":1,"1589":1,"1595":1,"1596":2,"1598":1,"1686":1,"1851":1}}],["distant",{"3":{"1311":1,"1359":1}}],["distancekmto",{"3":{"1395":1,"1417":2}}],["distance",{"3":{"632":1,"1379":1,"1395":4,"1417":6,"1436":1,"1438":1,"1556":1,"1922":1}}],["distorts",{"3":{"1286":1}}],["disturbs",{"3":{"1380":3,"1765":1}}],["disturbed",{"3":{"1323":1}}],["disturbing",{"3":{"1311":2,"1359":2,"1395":1,"1402":1,"1436":1}}],["disturb",{"3":{"1079":1,"1311":1}}],["distilled",{"0":{"1679":1},"3":{"601":1,"1470":1,"1502":1,"1679":1}}],["distinguishable",{"3":{"790":1,"907":1,"926":1,"1093":1,"1259":1,"1270":4,"1309":1,"1311":2,"1323":2,"1324":4,"1359":5,"1395":2,"1396":7,"1415":1,"1436":1,"1496":1,"2179":1}}],["distinguishing",{"3":{"309":1,"1259":1,"1310":1,"1311":1,"1324":1,"1339":1,"1350":2,"1359":4,"1380":1,"1395":2,"1396":4,"1436":2}}],["distinguish",{"3":{"158":1,"343":1,"612":1,"1286":1,"1300":4,"1301":1,"1311":2,"1320":1,"1323":3,"1324":3,"1359":5,"1395":4,"1396":4,"1402":1,"1415":1,"1417":1,"1436":8,"1438":1,"1498":1,"1630":1,"1982":1}}],["distinguished",{"3":{"111":1,"1270":1,"1342":1,"1350":1,"1359":1,"1396":3,"1556":1,"1637":1}}],["distinguishes",{"3":{"10":1,"536":1,"549":1,"676":1,"700":1,"758":1,"803":1,"805":1,"881":1,"922":1,"1237":1,"1259":1,"1265":1,"1286":2,"1300":2,"1323":1,"1324":2,"1359":3,"1380":1,"1383":1,"1395":9,"1396":4,"1402":1,"1415":2,"1436":5,"1672":1,"1821":1}}],["distincterrorcodecount",{"3":{"1436":3}}],["distinctcodecount",{"3":{"1436":2}}],["distincts",{"3":{"1359":1}}],["distinctness",{"3":{"1286":1}}],["distinctly",{"3":{"1286":1,"1323":3,"1324":1,"1369":1,"1395":1,"1396":1,"1939":1}}],["distinctby",{"3":{"1271":1,"1395":2,"1830":1}}],["distinctive",{"3":{"1247":1,"1350":2,"1395":1,"1396":1,"1436":2}}],["distinctions",{"3":{"1396":4}}],["distinction",{"3":{"0":2,"136":1,"187":1,"691":1,"837":1,"855":1,"882":1,"906":1,"1225":1,"1247":1,"1249":1,"1259":3,"1270":1,"1286":5,"1300":4,"1309":3,"1323":1,"1324":2,"1339":2,"1348":1,"1350":4,"1359":3,"1380":2,"1395":2,"1396":4,"1402":1,"1415":2,"1417":4,"1422":1,"1428":1,"1430":1,"1436":8,"1456":2,"1460":1,"1476":1,"1489":1,"1577":1,"1651":1,"1956":1,"1982":1,"2044":1}}],["distinct",{"0":{"1206":1},"2":{"647":1},"3":{"0":7,"19":1,"92":1,"135":1,"157":1,"162":2,"166":2,"174":2,"225":1,"229":1,"242":1,"248":1,"284":1,"312":1,"340":1,"346":2,"358":1,"359":1,"470":1,"472":1,"587":1,"607":1,"610":1,"612":1,"618":1,"619":1,"647":1,"651":1,"690":1,"699":1,"741":1,"828":1,"1132":1,"1134":1,"1191":2,"1192":1,"1196":1,"1197":1,"1200":1,"1202":1,"1204":1,"1206":1,"1231":1,"1237":3,"1241":2,"1242":1,"1247":3,"1259":2,"1261":1,"1271":2,"1279":1,"1286":16,"1300":6,"1301":2,"1302":1,"1309":5,"1310":5,"1311":6,"1323":6,"1324":12,"1325":1,"1332":1,"1339":3,"1347":1,"1350":5,"1351":1,"1359":29,"1369":2,"1380":3,"1395":9,"1396":24,"1397":1,"1402":4,"1406":1,"1415":3,"1416":2,"1417":9,"1431":1,"1436":29,"1438":2,"1443":1,"1467":2,"1487":1,"1496":32,"1499":1,"1531":1,"1543":1,"1631":2,"1652":1,"1665":1,"1670":1,"1685":1,"1702":1,"1707":1,"1747":1,"1765":1,"1865":1,"1870":1,"1900":1,"1933":1,"1989":2,"2046":1,"2141":1,"2196":1}}],["distrust",{"3":{"940":1}}],["distro",{"3":{"395":1,"397":1,"402":1,"1333":2,"1339":5,"1438":1,"1443":4,"1446":1,"1467":3,"1668":1,"2010":2,"2154":1,"2156":1,"2157":1,"2160":2}}],["distributing",{"3":{"2028":1}}],["distributions",{"3":{"1350":2}}],["distribution",{"0":{"1197":1},"3":{"33":1,"530":1,"598":1,"1300":1,"1311":1,"1350":4,"1359":12,"1376":1,"1380":1,"1390":1,"1395":2,"1416":1,"1425":1,"1427":2,"1535":1,"1585":1,"1632":1,"1640":1,"1894":1}}],["distributedapplicationbuilder",{"3":{"1438":1}}],["distributedapplication",{"2":{"1430":1,"2056":1},"3":{"1339":1,"1430":1,"1489":1,"2056":1}}],["distributedapplicationtestingbuilder",{"2":{"912":1,"913":2,"914":1,"1066":1,"1067":1,"1069":1,"1212":1,"1612":1},"3":{"0":2,"912":1,"913":2,"914":1,"1066":1,"1067":1,"1069":1,"1212":1,"1612":1}}],["distributedcontextpropagator",{"3":{"1324":1}}],["distributedcacheentryoptions",{"2":{"243":1,"254":1},"3":{"243":1,"254":1,"1301":5}}],["distributedcache",{"3":{"0":1,"1091":1}}],["distributedcacheservicetests",{"3":{"1199":1,"1207":3,"1301":2,"1496":1}}],["distributedcacheserviceredistests",{"2":{"1301":1},"3":{"1199":1,"1207":2,"1301":3,"1438":2,"1496":1}}],["distributedcacheservice",{"2":{"245":1,"248":1,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"256":1,"257":1,"259":1,"261":1,"285":1,"286":1,"731":1,"733":1,"736":1,"1442":1,"1453":1,"1486":1,"1916":1,"1923":1},"3":{"0":1,"243":2,"245":4,"248":2,"249":1,"250":1,"251":3,"252":2,"253":1,"254":3,"256":4,"257":2,"259":2,"261":1,"285":1,"286":1,"731":1,"732":1,"733":3,"736":1,"1199":8,"1203":1,"1207":2,"1229":4,"1300":1,"1301":63,"1323":2,"1339":2,"1438":2,"1442":1,"1453":1,"1467":1,"1486":1,"1487":1,"1496":1,"1577":2,"1916":2,"1923":1}}],["distributedlock",{"3":{"1300":1,"1577":1}}],["distributed",{"0":{"2008":1},"1":{"2003":1,"2004":1,"2005":1,"2006":1,"2007":1,"2008":1,"2009":1,"2010":1,"2011":1,"2012":1,"2013":1,"2014":1},"2":{"182":1,"2001":1},"3":{"0":5,"25":1,"61":1,"135":1,"157":2,"158":1,"159":1,"162":1,"173":1,"177":1,"178":5,"182":2,"235":2,"241":1,"243":5,"244":2,"245":4,"246":1,"253":1,"259":1,"261":1,"282":1,"283":3,"398":2,"461":1,"516":1,"517":1,"520":1,"535":1,"538":1,"732":1,"788":1,"792":1,"794":1,"852":1,"995":1,"1000":1,"1011":1,"1012":1,"1091":1,"1202":1,"1203":1,"1212":1,"1213":2,"1223":1,"1229":3,"1257":1,"1259":4,"1270":2,"1286":1,"1294":1,"1300":8,"1301":26,"1311":20,"1312":2,"1320":1,"1323":4,"1325":1,"1332":2,"1339":6,"1358":1,"1359":4,"1369":2,"1379":1,"1421":1,"1427":3,"1438":2,"1440":1,"1441":1,"1442":2,"1443":1,"1454":1,"1465":1,"1467":3,"1496":2,"1526":1,"1534":2,"1544":2,"1547":1,"1550":1,"1577":2,"1586":1,"1639":1,"1656":1,"1666":2,"1668":2,"1675":1,"1789":1,"1792":1,"1845":1,"1846":1,"1847":1,"1849":2,"1852":1,"1853":1,"1894":1,"1909":1,"1910":1,"1912":2,"1915":1,"1916":6,"1917":1,"1920":2,"1923":3,"1934":1,"1947":1,"2001":5,"2002":1,"2003":3,"2005":1,"2007":1,"2010":1,"2014":1,"2019":1,"2027":1,"2067":1,"2155":1,"2160":1,"2168":1,"2169":1,"2193":1,"2209":1}}],["disprove",{"3":{"2239":1}}],["dispensation",{"3":{"1534":1}}],["disputed",{"3":{"1396":1}}],["displacing",{"3":{"1311":1,"1323":1,"1975":1}}],["displace",{"3":{"1359":1}}],["displaces",{"3":{"1311":1,"1417":1}}],["displaced",{"3":{"624":2,"1184":1,"1396":1,"1475":1,"1922":1}}],["displaying",{"3":{"1324":1,"1436":1}}],["displaynameunavailable",{"3":{"1396":1}}],["displaynamemaxlength",{"3":{"1396":5}}],["displayname",{"3":{"690":1,"1300":1,"1339":1,"1359":1,"1395":1,"1396":25,"1410":1,"1415":1,"1436":1}}],["displaystatus",{"3":{"1324":1}}],["displays",{"3":{"690":1,"1350":2,"1437":1,"1631":1,"1639":1,"1666":1}}],["displayed",{"3":{"135":1,"265":1,"1324":3,"1350":1,"1359":2,"1395":5,"1402":1,"1629":1,"1630":7,"1631":5,"1637":1}}],["display",{"0":{"1387":1},"1":{"679":1,"680":1,"681":1,"682":1,"683":1,"684":1,"685":1,"686":1},"3":{"0":2,"135":1,"265":2,"402":1,"679":1,"682":2,"683":1,"688":1,"690":2,"691":1,"897":1,"963":1,"964":1,"990":1,"1027":1,"1028":1,"1212":1,"1235":1,"1237":2,"1247":1,"1278":2,"1286":3,"1293":1,"1295":1,"1300":3,"1311":6,"1314":1,"1323":2,"1324":12,"1333":1,"1339":7,"1342":1,"1344":2,"1350":36,"1353":1,"1359":30,"1369":2,"1380":1,"1387":1,"1395":52,"1396":35,"1402":11,"1415":3,"1416":1,"1417":2,"1427":1,"1436":3,"1438":2,"1443":1,"1488":1,"1577":1,"1591":1,"1630":11,"1631":6,"1633":1,"1637":2,"1639":3,"1640":4,"1641":3,"1656":1,"1669":1,"1748":2,"1750":3,"1764":2,"1765":2,"1776":1,"2075":1,"2094":1,"2150":2,"2232":1}}],["disposables",{"3":{"1324":1,"1396":1}}],["disposable",{"3":{"1247":2,"1273":1,"1286":1,"1300":2,"1323":2,"1324":3,"1339":2,"1396":8,"1400":1,"1415":2,"1417":2,"1436":2}}],["disposal",{"3":{"572":1,"881":1,"916":1,"996":1,"998":1,"1270":2,"1286":7,"1323":2,"1324":22,"1389":1,"1395":32,"1396":10,"1402":11,"1415":1,"1417":3,"1436":4,"1438":2,"1488":2,"1490":2,"1849":1,"1897":1,"2055":1,"2072":1}}],["disposing",{"3":{"996":1,"1270":2,"1286":1,"1300":1,"1323":3,"1324":9,"1339":1,"1395":4,"1396":5,"1417":2,"1425":1,"1427":2,"1436":1,"1668":1,"2177":1}}],["dispositiontype",{"3":{"1286":1}}],["disposition",{"3":{"0":1,"443":1,"725":3,"741":2,"1116":2,"1285":1,"1286":7,"1311":2,"1350":1,"1357":1,"1417":1,"1631":1,"2126":1}}],["disposehostsasync",{"3":{"1436":1}}],["disposehandler",{"3":{"1324":1,"1339":2,"1436":3}}],["disposetrackingresponse",{"3":{"1199":2,"1207":1}}],["disposes",{"3":{"401":1,"1259":2,"1286":3,"1300":1,"1302":1,"1311":1,"1323":1,"1324":18,"1339":3,"1395":6,"1396":4,"1402":10,"1415":4,"1417":8,"1427":2,"1429":1,"1436":8,"1438":1,"1488":3,"1491":1,"1938":1,"2056":1,"2070":1}}],["disposed",{"3":{"109":1,"524":1,"1259":2,"1270":4,"1273":1,"1274":1,"1286":4,"1288":1,"1300":2,"1309":1,"1323":3,"1324":42,"1339":3,"1359":1,"1395":16,"1396":9,"1415":1,"1417":1,"1425":1,"1427":1,"1432":1,"1436":3,"1526":1,"1577":3,"1702":1,"2072":2,"2079":1,"2177":1}}],["disposeasync",{"2":{"1491":2},"3":{"0":1,"135":1,"1067":1,"1286":3,"1323":2,"1324":8,"1339":1,"1396":4,"1402":5,"1417":2,"1429":2,"1436":9,"1488":3,"1491":2}}],["dispose",{"3":{"0":1,"135":1,"401":1,"996":1,"1246":1,"1247":2,"1259":3,"1286":8,"1300":5,"1311":1,"1323":2,"1324":41,"1339":5,"1384":1,"1395":24,"1396":15,"1415":1,"1417":14,"1427":2,"1432":1,"1436":6,"1442":1,"1577":2,"1591":2,"1611":1,"2070":1}}],["dispatchchanneleventasync",{"3":{"1324":3}}],["dispatchcache",{"3":{"1259":1,"1323":1}}],["dispatchable",{"3":{"1359":4}}],["dispatchandfinalizeasync",{"2":{"1274":1},"3":{"1274":1,"1286":3}}],["dispatchasync",{"3":{"1259":6,"1286":2,"1436":2}}],["dispatchtohandlersasync",{"3":{"1259":1}}],["dispatchmessagesasync",{"2":{"2165":1},"3":{"1259":2,"2165":1}}],["dispatchfailed",{"2":{"897":1},"3":{"897":1,"1247":5}}],["dispatchlaghistogram",{"3":{"707":1,"1259":3}}],["dispatching",{"3":{"18":1,"20":1,"58":1,"230":1,"767":1,"881":1,"1252":1,"1255":1,"1257":1,"1259":7,"1274":1,"1278":1,"1286":2,"1323":1,"1345":1,"1350":1,"1359":1,"1375":1,"1380":4,"1396":1,"1402":1,"1436":1,"1438":1,"1489":1,"1577":1,"1841":2}}],["dispatched",{"3":{"15":1,"16":1,"17":1,"18":3,"19":1,"20":1,"21":1,"26":1,"230":1,"291":1,"642":1,"692":1,"702":1,"781":1,"786":1,"1140":1,"1237":2,"1249":1,"1251":1,"1252":3,"1256":1,"1259":16,"1271":1,"1286":5,"1289":1,"1300":1,"1309":4,"1350":15,"1352":1,"1359":13,"1395":1,"1396":7,"1402":21,"1415":9,"1508":2,"1577":1,"1631":1,"1634":1,"1637":1,"1638":1,"1639":1,"1651":1,"1677":1,"1719":1,"1721":1,"1789":1,"1838":1,"1861":1}}],["dispatches",{"0":{"1842":1},"2":{"1370":1},"3":{"0":1,"15":1,"24":1,"166":1,"225":2,"230":1,"265":1,"536":1,"538":1,"846":1,"930":1,"931":1,"1011":1,"1042":1,"1233":1,"1237":2,"1247":1,"1252":1,"1257":2,"1259":20,"1286":7,"1311":8,"1312":1,"1317":1,"1323":1,"1324":3,"1344":1,"1350":1,"1359":6,"1370":1,"1375":1,"1380":33,"1395":4,"1396":9,"1402":2,"1408":1,"1415":5,"1416":1,"1431":1,"1436":4,"1508":1,"1783":1,"1815":1,"1856":1,"2091":1,"2137":1,"2156":1,"2163":1,"2165":3,"2188":1}}],["dispatcher",{"3":{"0":1,"81":1,"135":1,"418":1,"420":1,"428":1,"538":1,"784":1,"1042":1,"1140":1,"1192":1,"1202":1,"1203":1,"1237":1,"1241":1,"1247":2,"1248":1,"1252":1,"1256":1,"1257":1,"1259":19,"1262":1,"1270":1,"1274":1,"1286":6,"1309":1,"1311":3,"1396":3,"1402":2,"1415":1,"1416":9,"1417":11,"1436":3,"1487":2,"1526":1,"1539":1,"1543":1,"1577":1,"1665":1,"1669":1,"1889":1,"2121":1,"2165":1,"2168":1,"2182":1,"2186":1}}],["dispatch",{"0":{"1247":1,"1252":1,"1508":1},"1":{"14":1,"15":1,"16":1,"17":1,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1,"24":1,"25":1,"26":1,"27":1,"1248":1,"1249":1,"1250":1,"1251":1,"1252":1,"1253":1,"1254":1,"1255":1,"1256":1,"1257":1,"1258":1,"1259":1},"2":{"24":1,"400":1,"626":1,"766":1,"768":1,"897":1,"899":1,"901":1,"1399":1,"1456":1,"1457":1,"1458":1,"1474":1,"1475":2,"1589":1,"1599":1,"1919":1,"1946":1,"2034":1,"2156":1},"3":{"0":14,"14":1,"15":2,"16":2,"17":3,"18":3,"19":1,"20":6,"21":4,"22":1,"24":3,"25":1,"26":2,"27":3,"64":1,"71":1,"113":1,"115":1,"120":3,"168":1,"171":1,"196":1,"200":1,"229":1,"314":1,"383":1,"400":1,"518":1,"626":5,"639":1,"692":1,"707":1,"766":1,"768":1,"819":1,"845":1,"846":1,"863":1,"881":5,"897":2,"899":1,"901":1,"931":2,"933":2,"935":1,"987":1,"988":3,"989":1,"992":1,"1010":1,"1011":1,"1013":2,"1075":1,"1076":1,"1192":2,"1202":1,"1203":1,"1212":3,"1230":1,"1233":3,"1237":7,"1241":2,"1247":6,"1248":2,"1249":1,"1251":2,"1254":1,"1255":1,"1257":2,"1259":44,"1263":1,"1270":7,"1274":5,"1278":1,"1286":24,"1300":3,"1311":5,"1323":6,"1324":6,"1345":1,"1350":3,"1359":4,"1369":3,"1375":1,"1380":4,"1395":5,"1396":18,"1399":2,"1402":7,"1406":1,"1412":1,"1415":8,"1417":5,"1436":9,"1438":2,"1443":1,"1446":1,"1452":1,"1455":8,"1456":6,"1457":1,"1458":1,"1460":10,"1473":1,"1474":1,"1475":10,"1483":1,"1490":2,"1492":3,"1496":2,"1498":1,"1508":1,"1524":1,"1526":4,"1531":1,"1534":1,"1538":1,"1543":1,"1571":1,"1577":2,"1582":1,"1583":1,"1589":1,"1591":4,"1596":2,"1599":1,"1600":2,"1611":2,"1634":1,"1638":1,"1662":1,"1665":3,"1721":1,"1749":1,"1838":3,"1840":2,"1842":1,"1846":3,"1919":1,"1944":1,"1946":1,"1950":1,"2006":1,"2026":1,"2032":1,"2034":1,"2074":1,"2156":3,"2159":1,"2165":1,"2221":1,"2232":2}}],["douglasville",{"3":{"2244":1}}],["douglas",{"3":{"2220":1}}],["doubly",{"3":{"1161":1,"1417":1,"1455":1,"1526":1,"1591":1}}],["doubling",{"3":{"178":1,"390":1,"741":1,"1270":1,"1311":1,"1324":3,"1350":1,"1396":1,"1417":1,"1496":1,"1985":1}}],["doublebooked",{"3":{"1359":1,"1436":1}}],["doublebookederror",{"3":{"1359":2}}],["doubled",{"0":{"141":1},"3":{"0":1,"125":1,"551":1,"556":1,"559":2,"982":1,"1214":1,"1311":1,"1359":1,"1489":1,"1494":1,"2043":1}}],["doubles",{"3":{"0":1,"195":1,"282":1,"782":2,"838":1,"848":1,"875":1,"954":1,"955":1,"1060":1,"1237":1,"1269":2,"1270":3,"1286":1,"1300":3,"1311":2,"1318":1,"1323":2,"1324":3,"1339":1,"1350":1,"1359":3,"1369":1,"1395":2,"1396":1,"1400":1,"1402":1,"1403":1,"1436":5,"1437":2,"1459":1,"1467":1,"1488":2,"1496":2,"1651":1,"1654":1,"1660":1,"1671":1,"1673":1,"1932":1,"1942":1,"1965":1,"2055":1}}],["double",{"0":{"2076":1},"3":{"0":8,"20":1,"122":1,"156":2,"157":2,"160":1,"195":1,"201":1,"379":1,"493":1,"494":1,"495":1,"497":2,"501":1,"502":1,"517":1,"536":2,"538":1,"556":2,"557":2,"558":1,"647":1,"690":1,"692":1,"733":1,"743":2,"827":1,"829":1,"905":1,"953":1,"954":3,"955":1,"956":1,"960":1,"997":1,"1042":1,"1174":1,"1175":1,"1212":1,"1231":1,"1237":2,"1247":1,"1255":1,"1259":2,"1265":1,"1267":1,"1270":11,"1271":1,"1286":14,"1288":1,"1296":1,"1298":1,"1300":12,"1301":4,"1309":1,"1311":13,"1312":2,"1323":10,"1324":14,"1335":1,"1339":5,"1350":4,"1352":1,"1359":16,"1369":3,"1380":2,"1395":1,"1396":5,"1402":3,"1412":1,"1415":2,"1417":8,"1427":1,"1429":1,"1432":2,"1436":22,"1438":3,"1467":1,"1488":2,"1494":1,"1496":1,"1526":1,"1534":3,"1561":2,"1591":1,"1667":1,"1668":1,"1671":1,"1749":1,"1810":2,"1841":1,"1906":1,"1909":1,"1911":1,"1912":1,"1915":1,"1917":1,"1984":1,"2061":1,"2165":1,"2167":1,"2232":1}}],["dodge",{"3":{"1801":1,"2186":1}}],["dollar",{"3":{"1534":1}}],["dollars",{"3":{"1324":1,"1457":1}}],["dogfooding",{"3":{"1487":1,"1488":1}}],["dogfoods",{"3":{"572":1,"707":1,"715":1}}],["doasync",{"3":{"1436":1}}],["doomed",{"3":{"1286":1,"1324":2,"1396":1,"1417":1,"1438":1}}],["doors",{"3":{"1350":1,"1359":1,"2066":1}}],["door",{"3":{"0":1,"443":1,"544":1,"689":4,"690":1,"691":2,"827":1,"1115":1,"1123":1,"1125":1,"1132":1,"1133":1,"1134":1,"1212":1,"1237":1,"1247":1,"1300":1,"1311":1,"1339":1,"1350":1,"1359":16,"1393":1,"1395":1,"1396":27,"1402":1,"1415":1,"1416":1,"1467":1,"1627":2,"1629":1,"1631":2,"1701":1,"1780":1,"1783":1,"2000":1,"2026":1,"2066":1,"2069":1,"2090":1,"2139":1}}],["dormant",{"3":{"691":1,"1087":1,"1264":1,"1359":1,"1396":6,"1401":1,"1402":1,"1417":2,"1524":1,"1526":2,"1534":1,"1823":1,"1860":1}}],["dozens",{"3":{"1005":1,"1124":1,"1286":1,"1313":1,"1352":1,"1359":1,"1395":2,"1396":1,"1436":1,"1958":1,"1986":1}}],["dozen",{"3":{"527":1,"673":1,"1035":1,"1314":1,"1324":1,"1339":1,"1353":1,"1395":3,"1396":3,"1401":2,"1402":2,"1436":2,"1828":1,"1880":1,"2059":1,"2202":1}}],["dominant",{"3":{"1229":1,"1361":1,"1456":1,"1490":1,"1496":3,"1505":1,"1517":1}}],["dominates",{"3":{"1247":1,"1496":1,"1677":1}}],["dominated",{"3":{"1067":1,"1287":1,"1395":1,"1455":2,"1492":2}}],["dominate",{"3":{"396":1,"1259":1,"1286":1,"1339":1,"1443":1,"1467":1,"1677":1,"1841":1,"2010":1,"2052":1,"2157":1}}],["dom",{"3":{"506":2,"507":1,"508":1,"1187":1,"1324":8,"1395":1,"1416":1,"1417":1,"1432":1,"1436":6,"1456":1,"1488":1,"1531":1,"2073":1,"2079":1}}],["domainpurity",{"3":{"1576":1,"1577":1}}],["domainpuritytests",{"2":{"1525":1,"2049":1},"3":{"1199":2,"1207":4,"1436":13,"1489":5,"1493":1,"1525":1,"1526":1,"1576":1,"1577":1,"1591":1,"1600":1,"2043":1,"2049":1}}],["domainpuritytestsbase",{"2":{"2042":1,"2104":1,"2108":1},"3":{"1199":3,"1207":2,"1431":1,"1436":9,"1489":1,"1654":1,"2042":1,"2104":1,"2108":1}}],["domainorinfrastructure",{"3":{"1436":1}}],["domainfactories",{"3":{"1436":2,"1489":1}}],["domainfactoriesreturnresult",{"2":{"657":1,"1586":1},"3":{"657":1,"1431":1,"1436":1,"1577":1,"1586":1}}],["domainassemblylocations",{"3":{"1436":1}}],["domainaggregaterootshavenopublicconstructors",{"2":{"1577":1},"3":{"1436":1,"1577":1}}],["domainandapplicationdonotreadtheclock",{"2":{"1161":1},"3":{"1161":1,"1436":6}}],["domainandapplication",{"2":{"1161":1,"1436":1},"3":{"1161":1,"1436":2}}],["domaindoesnotdependonapplication",{"3":{"1436":1}}],["domainindex",{"3":{"1300":1}}],["domaininvariantviolationexception",{"2":{"109":1,"881":1,"1217":1,"1226":1,"1229":1,"1806":1},"3":{"0":2,"109":1,"881":1,"1199":4,"1203":1,"1207":2,"1217":1,"1226":3,"1229":7,"1300":1,"1324":1,"1806":1}}],["domainhelpertests",{"3":{"1199":1,"1207":2,"1237":2}}],["domainhelper",{"3":{"980":1,"982":1,"1199":1,"1203":1,"1207":2,"1208":1,"1214":1,"1230":2,"1236":7,"1237":7,"1395":11,"1396":3}}],["domainthrowfixtures",{"3":{"1207":24,"1436":27}}],["domainthrowfitnesstests",{"3":{"1199":1,"1207":3,"1436":27,"1489":1}}],["domainthrowtests",{"3":{"1199":1,"1207":2,"1436":6,"1489":1}}],["domainthrowtestsbase",{"3":{"1199":2,"1207":2,"1436":7}}],["domainthrows",{"3":{"980":1,"982":1,"1207":1,"1436":10,"1489":1}}],["domainthrowsonlyargumentguards",{"2":{"980":1,"983":1},"3":{"980":1,"983":1,"1436":7}}],["domaintypes",{"3":{"81":1,"1436":2}}],["domains",{"3":{"418":1,"420":1,"422":1,"1168":3,"1416":1,"1436":4,"1481":2,"1599":1,"1629":1,"2109":2,"2110":1,"2114":1}}],["domainentityproperties",{"3":{"1436":1}}],["domainentitystate",{"2":{"963":1,"968":1,"1233":1,"1249":1,"1340":1,"1355":1,"1398":1,"1685":1},"3":{"0":2,"690":1,"782":6,"963":2,"964":2,"968":1,"1199":75,"1203":1,"1207":2,"1212":1,"1230":1,"1233":2,"1237":3,"1249":1,"1259":6,"1300":1,"1340":1,"1345":1,"1350":38,"1355":1,"1359":23,"1396":18,"1398":1,"1402":15,"1631":3,"1633":1,"1634":15,"1639":1,"1665":1,"1685":1,"1769":1}}],["domainentities",{"3":{"1436":2}}],["domainexposesaggregateroots",{"3":{"1431":1,"1577":1}}],["domainexceptiontests",{"3":{"1199":1,"1207":3,"1229":1}}],["domainexception",{"2":{"1217":1,"1226":1,"1640":1},"3":{"109":1,"1199":6,"1203":1,"1207":2,"1217":1,"1226":6,"1229":12,"1311":4,"1436":1,"1640":2}}],["domainexceptionhandler",{"2":{"1226":1,"1927":1},"3":{"109":2,"1199":4,"1203":1,"1207":2,"1226":1,"1229":6,"1311":9,"1774":1,"1927":1}}],["domaineventcaptureexclusiontests",{"3":{"1199":1,"1207":6,"1286":2,"1438":2}}],["domaineventoccurrencestamp",{"2":{"1161":1},"3":{"1161":1}}],["domaineventsavefixtures",{"3":{"1207":27,"1436":34}}],["domaineventsavechangesinterceptoroutboxdisabledtests",{"3":{"1199":1,"1207":2,"1286":2}}],["domaineventsavechangesinterceptoroutboxroutingtests",{"3":{"1199":2,"1207":9,"1237":1,"1286":2}}],["domaineventsavechangesinterceptortests",{"3":{"1199":1,"1207":6,"1286":2}}],["domaineventsavechangesinterceptor",{"2":{"20":1,"120":1,"540":1,"715":1,"931":1,"1201":1,"1233":1,"1251":1,"1274":1,"1286":1},"3":{"0":1,"20":1,"26":1,"120":1,"166":1,"536":1,"538":1,"540":1,"715":3,"931":2,"988":2,"1175":1,"1198":1,"1199":59,"1201":1,"1203":1,"1207":5,"1233":6,"1237":6,"1251":4,"1252":3,"1253":1,"1254":1,"1259":17,"1270":1,"1274":3,"1280":1,"1286":32,"1323":1,"1350":2,"1436":7,"1496":4,"1498":1,"2165":1,"2168":1}}],["domainevents",{"2":{"1161":1,"1207":2,"1350":6,"1396":1,"1402":1,"1415":1},"3":{"81":1,"255":1,"536":2,"782":19,"784":2,"1026":1,"1160":1,"1161":1,"1203":33,"1207":98,"1231":1,"1237":5,"1247":1,"1249":4,"1252":2,"1259":20,"1270":1,"1282":1,"1286":6,"1309":3,"1342":1,"1345":5,"1350":45,"1359":2,"1369":1,"1396":10,"1402":9,"1415":5,"1436":8,"1438":2,"1496":1,"1577":2,"1686":1,"1769":1,"1810":1,"1856":1}}],["domainevent",{"3":{"26":2,"782":1,"1259":2,"1286":1,"1396":3,"2189":1}}],["domaineventhandlers",{"3":{"230":1,"390":1,"522":2,"545":2,"690":1,"782":5,"897":3,"1026":1,"1100":2,"1203":5,"1207":28,"1237":2,"1247":4,"1259":2,"1309":1,"1350":2,"1355":1,"1359":2,"1396":8,"1399":2,"1402":5,"1436":3,"1438":2,"1496":2,"1526":1,"1651":1,"1765":3}}],["domaineventhandlersavefitnesstests",{"3":{"1199":1,"1207":3,"1436":20,"1489":1}}],["domaineventhandlersavetests",{"3":{"27":2,"1199":1,"1207":2,"1436":6,"1489":1}}],["domaineventhandlersavetestsbase",{"3":{"17":2,"27":1,"1199":2,"1207":2,"1436":9}}],["domaineventhandlersaves",{"3":{"17":1,"27":1,"1207":1,"1436":13,"1489":1}}],["domaineventhandlersdonotsave",{"2":{"17":1,"27":1},"3":{"17":1,"27":1,"1436":7}}],["domaineventdispatchertests",{"3":{"1199":1,"1207":7}}],["domaineventdispatcheradditionaltests",{"3":{"1199":1,"1207":13}}],["domaineventdispatcher",{"2":{"17":1,"846":1,"848":1,"1252":1,"1269":1,"1651":1},"3":{"0":1,"17":1,"538":1,"846":1,"848":1,"1140":1,"1199":9,"1203":1,"1207":2,"1247":2,"1252":4,"1259":36,"1269":1,"1270":3,"1323":3,"1350":4,"1359":1,"1396":1,"1436":2,"1438":1,"1577":1,"1651":1,"2168":1}}],["domain",{"0":{"1233":1,"1345":1,"1346":1,"1355":1,"1541":1,"1630":1,"1687":1,"1769":1,"1832":1,"2091":1},"1":{"654":1,"655":1,"656":1,"657":1,"658":1,"659":1,"660":1,"661":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1,"1230":1,"1231":1,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":1,"1248":1,"1249":1,"1250":1,"1251":1,"1252":1,"1253":1,"1254":1,"1255":1,"1256":1,"1257":1,"1258":1,"1259":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":1,"1346":1,"1347":1,"1348":1,"1349":1,"1350":1,"1808":1,"1809":1,"1810":1,"1811":1,"1812":1},"2":{"39":3,"146":1,"1006":1,"1161":1,"1207":2,"1230":1,"1236":1,"1237":1,"1321":1,"1341":3,"1350":6,"1396":2,"1402":2,"1404":1,"1415":1,"1436":1,"1650":2,"1660":1,"1781":1,"1931":1,"2063":1},"3":{"0":23,"4":1,"15":1,"16":1,"17":2,"18":1,"19":1,"20":1,"24":2,"26":1,"27":2,"39":11,"40":1,"43":3,"52":1,"53":1,"54":2,"58":2,"59":2,"60":2,"62":1,"76":1,"80":3,"81":7,"108":1,"109":4,"110":2,"115":1,"117":1,"120":1,"124":1,"128":1,"131":2,"139":1,"146":1,"150":1,"156":2,"165":1,"167":1,"168":1,"185":1,"194":1,"224":1,"225":3,"226":1,"230":1,"255":1,"265":2,"318":1,"332":1,"341":5,"342":2,"350":1,"353":1,"359":1,"360":1,"370":1,"383":1,"391":1,"422":2,"469":2,"470":4,"473":1,"475":2,"476":3,"477":2,"497":5,"499":3,"502":2,"518":2,"522":2,"536":7,"537":1,"545":3,"546":1,"549":5,"556":1,"558":1,"572":4,"576":1,"584":1,"591":1,"592":1,"593":1,"634":1,"640":1,"654":1,"655":1,"656":1,"657":11,"658":1,"659":1,"690":5,"692":1,"698":3,"707":1,"715":2,"718":1,"781":1,"782":24,"783":1,"784":2,"785":1,"791":1,"798":2,"799":2,"802":1,"809":1,"810":1,"846":2,"881":1,"882":1,"883":1,"888":1,"897":1,"900":1,"904":1,"905":1,"906":1,"921":1,"922":1,"926":1,"930":1,"933":1,"939":1,"954":1,"963":3,"966":1,"971":1,"980":4,"982":2,"1004":8,"1006":1,"1018":1,"1024":1,"1025":2,"1026":4,"1027":2,"1029":1,"1033":1,"1034":1,"1037":1,"1041":1,"1049":1,"1050":5,"1051":1,"1052":1,"1054":1,"1055":1,"1058":1,"1059":4,"1074":2,"1078":1,"1082":1,"1083":4,"1085":1,"1091":3,"1093":1,"1100":1,"1104":1,"1116":4,"1140":4,"1141":2,"1142":1,"1147":1,"1150":1,"1158":1,"1160":3,"1161":14,"1162":1,"1168":26,"1169":1,"1170":2,"1171":1,"1183":1,"1185":1,"1187":2,"1191":1,"1192":5,"1194":2,"1198":18,"1199":252,"1200":1,"1201":4,"1202":8,"1203":151,"1206":8,"1207":883,"1208":2,"1211":5,"1212":15,"1213":1,"1214":2,"1216":1,"1217":1,"1220":1,"1224":1,"1226":1,"1229":11,"1230":6,"1231":10,"1232":4,"1233":5,"1234":3,"1235":1,"1236":6,"1237":160,"1238":2,"1239":9,"1240":1,"1247":69,"1248":2,"1249":14,"1252":2,"1257":2,"1259":63,"1262":2,"1263":1,"1265":3,"1270":19,"1271":25,"1272":2,"1274":2,"1275":1,"1278":1,"1280":1,"1281":1,"1285":1,"1286":97,"1290":1,"1291":5,"1293":1,"1295":1,"1297":1,"1300":71,"1302":1,"1303":6,"1304":1,"1305":2,"1306":2,"1309":63,"1310":25,"1311":41,"1312":8,"1313":1,"1316":1,"1319":2,"1321":1,"1323":38,"1324":12,"1339":1,"1340":6,"1341":13,"1342":12,"1343":3,"1344":16,"1345":9,"1346":4,"1347":1,"1349":2,"1350":365,"1351":3,"1352":3,"1353":2,"1354":1,"1355":4,"1356":1,"1358":2,"1359":504,"1360":4,"1361":3,"1362":1,"1363":1,"1369":34,"1370":1,"1375":1,"1377":1,"1378":1,"1379":1,"1380":28,"1395":18,"1396":176,"1397":2,"1398":3,"1399":3,"1402":113,"1403":1,"1404":7,"1405":6,"1409":1,"1410":1,"1412":1,"1413":1,"1415":119,"1416":1,"1417":7,"1427":1,"1429":2,"1431":14,"1436":452,"1437":9,"1438":17,"1455":2,"1486":1,"1487":7,"1488":1,"1489":28,"1492":1,"1493":1,"1496":39,"1498":1,"1499":1,"1503":1,"1504":2,"1505":1,"1506":1,"1508":2,"1526":17,"1535":4,"1538":1,"1539":2,"1540":5,"1541":2,"1544":1,"1546":2,"1551":2,"1553":1,"1572":1,"1575":1,"1576":2,"1577":25,"1581":1,"1582":5,"1583":1,"1585":3,"1586":2,"1591":5,"1600":1,"1631":8,"1632":9,"1633":1,"1634":6,"1637":2,"1638":11,"1639":9,"1640":1,"1641":3,"1647":1,"1650":5,"1651":5,"1653":2,"1654":4,"1656":1,"1660":3,"1661":1,"1662":5,"1663":2,"1665":2,"1666":1,"1671":3,"1681":2,"1683":1,"1685":8,"1686":8,"1687":2,"1689":1,"1693":1,"1700":1,"1701":3,"1719":1,"1724":1,"1727":4,"1730":2,"1747":4,"1748":2,"1749":2,"1751":1,"1753":1,"1755":1,"1763":1,"1764":13,"1765":12,"1769":4,"1770":1,"1772":1,"1775":2,"1776":2,"1779":1,"1781":2,"1782":1,"1783":1,"1789":1,"1790":2,"1796":1,"1800":1,"1803":1,"1805":4,"1806":2,"1807":1,"1808":4,"1809":1,"1810":4,"1812":5,"1815":3,"1822":2,"1829":1,"1831":3,"1832":5,"1833":4,"1834":3,"1838":5,"1839":1,"1842":1,"1844":1,"1851":1,"1853":1,"1854":3,"1856":1,"1861":1,"1862":1,"1864":1,"1865":2,"1871":2,"1877":1,"1883":1,"1884":1,"1891":1,"1922":1,"1929":1,"1931":2,"1932":2,"1933":1,"1934":1,"1937":2,"1941":1,"1944":2,"1946":1,"1950":2,"1951":1,"1953":2,"1954":2,"1990":1,"1994":1,"2016":2,"2020":1,"2028":1,"2039":2,"2040":2,"2042":1,"2043":7,"2051":1,"2052":2,"2054":1,"2063":3,"2064":2,"2071":2,"2074":1,"2083":3,"2085":3,"2086":1,"2087":1,"2088":1,"2090":3,"2091":3,"2093":1,"2095":1,"2097":1,"2098":3,"2099":2,"2100":1,"2101":1,"2104":6,"2105":2,"2109":1,"2110":4,"2111":2,"2113":2,"2114":2,"2125":1,"2139":1,"2142":1,"2163":3,"2164":2,"2165":2,"2169":1,"2170":1,"2171":1,"2178":2,"2180":1,"2183":1,"2186":1,"2189":1,"2206":2,"2213":1,"2214":1,"2217":1,"2218":1,"2219":1,"2227":6,"2230":5,"2232":4,"2239":2}}],["dosomething",{"3":{"1415":1}}],["dossier",{"3":{"1300":1}}],["dos",{"3":{"283":3,"1339":1,"2002":1}}],["dots",{"3":{"1116":1,"1285":1,"1286":1,"1311":2}}],["dot",{"3":{"564":1,"1241":1,"1247":1,"1286":1,"1300":1,"1350":1,"1436":2}}],["dotted",{"3":{"265":1,"333":1,"1126":1,"1241":3,"1247":11,"1270":1,"1300":1,"1311":1,"1324":6,"1359":4,"1436":1,"1666":1}}],["dotnetref",{"3":{"1324":2}}],["dotnetobjectreference",{"3":{"1324":7,"1417":3}}],["dotnet",{"2":{"132":1,"135":1,"291":1,"371":1,"372":1,"373":1,"374":1,"451":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"495":1,"527":1,"563":1,"634":1,"635":1,"675":1,"869":1,"906":1,"930":1,"932":1,"938":1,"1004":1,"1044":1,"1066":1,"1190":1,"1325":1,"1439":1,"1526":1,"1606":1,"1646":1,"1648":1,"1652":1,"1655":1,"1657":1,"1661":1,"1681":1,"1682":1,"1688":1,"1690":1,"1693":1,"1704":1,"1717":1,"1718":1,"1719":1,"1726":1,"1729":1,"1730":1,"1740":1,"1781":1,"1787":1,"1793":1,"1807":1,"1812":1,"1827":1,"1846":1,"1853":1,"1862":1,"1868":1,"1877":1,"1886":1,"1892":1,"1899":1,"1905":1,"1912":1,"1924":1,"1930":1,"1941":1,"1950":1,"1959":1,"1965":1,"1972":1,"1979":1,"1985":1,"1997":1,"2002":1,"2003":1,"2004":1,"2028":1,"2038":1,"2060":1,"2080":1,"2087":1,"2089":1,"2098":1,"2101":1,"2107":1,"2108":1,"2115":1,"2123":1,"2128":1,"2133":1,"2139":1,"2146":1,"2153":1,"2169":1,"2192":1,"2193":1,"2203":1,"2210":1,"2211":1},"3":{"0":7,"132":1,"135":2,"140":1,"291":2,"370":2,"371":1,"372":1,"373":4,"374":1,"397":1,"451":1,"483":1,"486":1,"487":1,"488":1,"489":1,"490":3,"491":3,"492":3,"493":4,"495":1,"527":1,"528":2,"563":1,"564":1,"599":1,"626":1,"632":1,"633":6,"634":1,"635":2,"675":1,"696":1,"698":1,"707":1,"715":1,"782":1,"861":1,"868":2,"869":2,"906":1,"914":2,"930":1,"932":1,"938":1,"982":1,"1004":1,"1044":1,"1066":1,"1067":1,"1069":1,"1190":1,"1212":2,"1214":1,"1273":1,"1284":3,"1286":18,"1300":1,"1311":1,"1323":3,"1325":1,"1326":1,"1328":1,"1339":1,"1430":1,"1436":5,"1439":1,"1441":1,"1445":12,"1449":1,"1453":7,"1454":5,"1455":6,"1456":1,"1459":4,"1461":1,"1464":2,"1467":1,"1486":4,"1487":1,"1490":3,"1492":3,"1526":2,"1577":1,"1591":1,"1606":1,"1646":1,"1648":3,"1649":2,"1650":2,"1651":2,"1652":2,"1654":2,"1655":2,"1657":4,"1661":3,"1664":1,"1677":1,"1681":1,"1682":3,"1684":1,"1686":1,"1688":1,"1690":2,"1693":2,"1697":1,"1701":2,"1704":4,"1717":1,"1718":4,"1719":3,"1726":2,"1727":3,"1728":2,"1729":2,"1730":1,"1740":1,"1781":1,"1787":2,"1793":1,"1807":1,"1812":1,"1817":1,"1827":1,"1834":1,"1846":1,"1853":1,"1862":1,"1868":1,"1877":1,"1886":1,"1892":1,"1899":1,"1905":1,"1912":1,"1924":1,"1930":1,"1941":1,"1950":1,"1959":1,"1965":1,"1972":1,"1979":1,"1985":1,"1991":1,"1997":1,"2002":1,"2003":1,"2004":2,"2014":2,"2028":1,"2038":1,"2058":1,"2060":1,"2080":1,"2086":1,"2087":1,"2089":1,"2097":2,"2098":2,"2101":3,"2107":1,"2108":1,"2115":1,"2123":1,"2128":1,"2133":1,"2139":1,"2146":1,"2153":1,"2156":1,"2160":1,"2169":1,"2180":1,"2192":1,"2193":1,"2203":1,"2210":1,"2211":1,"2232":1}}],["doing",{"3":{"72":1,"225":1,"236":1,"237":1,"373":1,"539":2,"549":1,"592":1,"633":1,"698":2,"757":1,"831":1,"867":1,"898":2,"1067":1,"1233":1,"1259":2,"1262":1,"1270":1,"1286":7,"1290":1,"1300":3,"1301":1,"1309":2,"1311":2,"1312":1,"1323":4,"1324":6,"1339":4,"1348":1,"1350":3,"1352":1,"1356":1,"1359":7,"1369":1,"1395":3,"1396":6,"1402":1,"1415":1,"1417":7,"1427":1,"1436":5,"1441":1,"1442":1,"1444":1,"1455":1,"1470":1,"1832":1,"2072":1,"2081":1,"2097":1,"2130":1,"2209":1}}],["donor",{"3":{"1496":1}}],["donotcoverthatpair",{"3":{"1436":1}}],["donotexceedconstructordependencyceiling",{"3":{"1436":21}}],["done",{"1":{"1900":1,"1901":1,"1902":1,"1903":1,"1904":1,"1905":1},"3":{"401":1,"435":1,"526":1,"709":1,"856":1,"857":1,"1124":1,"1237":1,"1239":1,"1270":3,"1286":2,"1304":1,"1309":1,"1310":1,"1311":1,"1318":1,"1324":6,"1339":1,"1359":1,"1395":3,"1402":1,"1417":1,"1436":1,"1496":1,"1529":1,"1531":1,"1575":2,"1589":1,"1596":1,"1611":1,"1641":1,"1651":1,"1664":1,"1701":1,"1728":1,"1779":1,"1840":1,"1879":1,"1899":1,"1900":1,"1935":1,"1937":1,"2000":1,"2097":1,"2101":1,"2129":1,"2194":1,"2208":1}}],["don",{"1":{"1828":1,"1829":1,"1830":1,"1831":1,"1832":1,"1833":1,"1834":1,"2124":1,"2125":1,"2126":1,"2127":1,"2128":1},"3":{"47":1,"1311":1,"1467":1,"1474":1,"1561":1,"1569":1,"1591":1,"1638":1,"1651":1,"1654":1,"1779":2,"1828":1,"1972":1,"2080":1,"2123":1,"2124":1,"2133":1,"2206":1,"2210":1}}],["docresource",{"3":{"1436":1}}],["doc",{"3":{"33":1,"68":2,"98":1,"135":1,"138":1,"139":3,"142":1,"251":1,"256":3,"257":1,"258":1,"322":1,"359":1,"364":1,"448":1,"453":1,"468":2,"476":1,"492":1,"543":1,"625":1,"682":1,"700":1,"751":1,"782":4,"784":1,"801":1,"819":1,"827":1,"837":1,"971":1,"976":1,"988":1,"990":1,"1003":1,"1074":1,"1190":1,"1219":2,"1226":1,"1228":1,"1229":15,"1231":1,"1232":1,"1233":1,"1235":1,"1237":30,"1239":1,"1247":47,"1259":70,"1262":1,"1263":1,"1264":1,"1269":1,"1270":59,"1271":12,"1273":1,"1274":1,"1275":1,"1277":1,"1278":3,"1280":1,"1283":3,"1284":2,"1286":152,"1300":127,"1301":5,"1309":52,"1310":3,"1311":95,"1312":8,"1315":1,"1316":1,"1317":1,"1323":69,"1324":184,"1326":1,"1327":1,"1330":2,"1332":2,"1333":1,"1339":148,"1342":2,"1347":1,"1350":100,"1359":101,"1369":3,"1380":63,"1391":1,"1395":159,"1396":171,"1402":74,"1415":103,"1416":3,"1417":55,"1419":1,"1422":1,"1423":1,"1427":6,"1431":1,"1436":311,"1443":1,"1451":1,"1488":3,"1489":3,"1490":1,"1496":8,"1526":4,"1534":2,"1585":1,"1586":2,"1591":2,"1596":1,"1597":1,"1600":1,"1685":1,"1686":5,"1815":1,"1821":1,"1896":2,"1994":1,"1995":1,"2000":1,"2004":1,"2039":1,"2041":1,"2043":2,"2044":1,"2045":5,"2048":1,"2066":1,"2085":1,"2104":1,"2112":1,"2117":1,"2122":1,"2129":1,"2149":1,"2159":1}}],["docs",{"0":{"1481":1},"3":{"1":2,"70":1,"99":1,"135":1,"140":1,"142":1,"147":2,"324":1,"358":2,"361":1,"362":1,"366":1,"370":1,"422":1,"423":1,"424":1,"425":1,"426":2,"490":1,"491":1,"492":1,"493":1,"495":1,"564":1,"582":1,"590":2,"599":1,"626":2,"756":2,"765":1,"766":1,"773":2,"860":1,"914":1,"1003":1,"1011":1,"1012":3,"1017":1,"1090":2,"1094":1,"1095":1,"1190":1,"1212":2,"1216":2,"1237":2,"1270":3,"1300":9,"1309":1,"1311":3,"1323":3,"1324":23,"1350":5,"1359":13,"1380":7,"1395":9,"1396":9,"1402":3,"1416":1,"1417":5,"1428":1,"1430":2,"1436":10,"1453":6,"1455":4,"1459":1,"1460":1,"1461":1,"1470":1,"1481":2,"1482":1,"1483":1,"1496":2,"1501":1,"1502":1,"1515":1,"1522":1,"1525":1,"1526":8,"1528":1,"1531":1,"1534":1,"1535":1,"1571":3,"1572":1,"1573":1,"1575":1,"1576":2,"1577":15,"1579":1,"1581":1,"1582":3,"1583":1,"1586":2,"1587":1,"1591":2,"1593":1,"1594":1,"1596":1,"1600":1,"1663":1,"1793":2,"1802":1,"1807":1,"1815":1,"1827":2,"1846":1,"1853":1,"1868":1,"1877":1,"1886":1,"1892":2,"1899":1,"1905":1,"1909":2,"1923":1,"1930":1,"1950":1,"1965":1,"1972":1,"1979":1,"1985":1,"1991":1,"1997":1,"2000":2,"2001":2,"2002":2,"2069":1,"2086":2,"2098":2,"2108":1,"2112":1,"2115":1,"2117":1,"2118":1,"2120":1,"2128":1,"2133":1,"2151":1,"2153":1,"2158":1,"2160":1,"2169":1,"2172":1,"2180":1,"2193":1,"2203":1,"2211":1,"2234":1}}],["docx",{"3":{"0":1,"443":1,"1116":2,"1285":1,"1347":1,"1350":1,"1395":1,"1631":1}}],["documentlanguagetests",{"3":{"1199":1,"1207":2,"1438":2}}],["documentformats",{"2":{"1285":1},"3":{"1199":5,"1203":1,"1207":1,"1285":1,"1286":5,"1359":2}}],["documentname",{"3":{"448":1,"1311":2,"2131":1}}],["documentingly",{"3":{"1350":1}}],["documenting",{"3":{"101":1,"251":1,"254":1,"257":1,"332":1,"557":1,"699":1,"805":1,"1247":1,"1286":2,"1300":1,"1311":2,"1323":2,"1324":1,"1339":3,"1380":1,"1396":1,"1496":1,"1552":1,"1591":1,"1659":1,"1990":1}}],["documentation",{"0":{"1571":1,"1674":1,"2045":1},"3":{"69":1,"135":1,"200":3,"230":1,"359":1,"364":2,"365":2,"425":1,"426":2,"459":1,"465":1,"466":1,"472":1,"529":2,"545":1,"547":1,"549":1,"593":1,"627":1,"862":1,"982":1,"986":1,"1006":1,"1011":1,"1012":1,"1067":1,"1070":1,"1092":1,"1108":2,"1124":2,"1133":1,"1161":1,"1216":1,"1221":1,"1229":1,"1237":3,"1247":1,"1270":3,"1271":1,"1286":1,"1300":4,"1309":3,"1311":6,"1312":2,"1322":1,"1323":1,"1324":3,"1339":5,"1350":3,"1352":1,"1359":9,"1369":2,"1395":2,"1396":5,"1415":2,"1428":1,"1436":49,"1447":1,"1453":1,"1457":1,"1459":1,"1486":1,"1489":1,"1492":1,"1493":1,"1496":1,"1499":1,"1571":1,"1582":1,"1585":1,"1591":1,"1592":1,"1601":1,"1686":1,"1796":1,"1941":1,"2014":1,"2045":1,"2047":1,"2056":1,"2142":1,"2144":1,"2163":1,"2165":3,"2167":2,"2174":1,"2175":1,"2177":1,"2178":1,"2180":1,"2189":2,"2196":1,"2202":1,"2233":1}}],["documentcontentsniffertests",{"3":{"1199":1,"1207":2,"1286":1}}],["documentcontentsniffer",{"2":{"443":1,"1116":1,"1285":1,"1357":1},"3":{"0":1,"443":2,"1116":2,"1199":4,"1203":1,"1207":3,"1212":1,"1285":2,"1286":20,"1347":1,"1350":1,"1357":1,"1359":5,"1496":2}}],["documents",{"0":{"452":1,"577":1},"2":{"452":1,"453":1},"3":{"0":4,"68":1,"121":1,"135":1,"189":1,"256":1,"290":1,"365":1,"418":1,"443":1,"446":1,"448":1,"451":2,"452":2,"453":1,"454":3,"570":1,"572":1,"573":1,"631":1,"635":1,"651":1,"744":1,"749":1,"756":1,"827":1,"946":1,"973":1,"979":1,"990":1,"996":1,"1016":1,"1050":1,"1114":1,"1126":1,"1133":1,"1229":3,"1247":2,"1259":2,"1270":3,"1271":1,"1278":1,"1286":9,"1300":9,"1310":1,"1311":13,"1323":6,"1324":9,"1339":6,"1352":1,"1359":14,"1369":1,"1380":2,"1395":6,"1396":12,"1399":1,"1402":2,"1406":1,"1410":1,"1415":4,"1416":2,"1417":8,"1427":2,"1436":11,"1453":1,"1455":4,"1458":1,"1467":2,"1473":1,"1474":2,"1496":2,"1500":1,"1524":1,"1526":2,"1531":2,"1575":1,"1577":2,"1586":1,"1596":1,"1640":1,"1651":1,"1653":1,"1670":1,"1679":1,"1684":1,"1686":1,"1698":1,"1701":1,"1861":1,"1883":1,"2095":1,"2132":1,"2133":1,"2232":1,"2233":1}}],["document",{"0":{"454":1,"1895":1},"1":{"1762":1,"1763":1,"1764":1,"1765":1,"1766":1,"1767":1,"1768":1,"1769":1,"1770":1,"1771":1,"1772":1,"1773":1,"1774":1,"1775":1,"1776":1,"1777":1},"2":{"450":1},"3":{"0":2,"31":3,"32":1,"102":1,"165":1,"166":2,"234":1,"239":1,"371":1,"395":2,"415":1,"418":1,"423":1,"424":1,"438":1,"443":2,"446":5,"448":4,"450":4,"451":4,"452":2,"453":1,"454":5,"472":1,"487":1,"490":1,"564":1,"570":1,"571":2,"572":2,"573":2,"577":3,"585":1,"625":1,"626":1,"724":1,"725":3,"726":2,"727":1,"743":1,"882":1,"905":1,"910":1,"974":1,"976":1,"1010":1,"1011":1,"1013":1,"1021":1,"1050":3,"1114":1,"1115":1,"1116":1,"1117":1,"1120":1,"1212":1,"1213":1,"1270":1,"1273":1,"1285":3,"1286":14,"1288":3,"1299":1,"1300":16,"1309":2,"1311":47,"1322":1,"1323":12,"1324":20,"1339":2,"1356":3,"1359":16,"1369":1,"1380":2,"1396":20,"1399":1,"1404":1,"1406":1,"1410":2,"1415":20,"1417":7,"1433":2,"1436":25,"1438":3,"1439":1,"1441":1,"1447":1,"1455":1,"1467":2,"1470":1,"1474":1,"1475":1,"1478":1,"1481":2,"1485":1,"1488":4,"1496":1,"1521":1,"1524":4,"1526":2,"1530":1,"1531":5,"1532":1,"1545":1,"1576":1,"1577":2,"1585":1,"1599":1,"1620":1,"1631":2,"1640":1,"1666":2,"1669":1,"1672":1,"1676":1,"1711":1,"1717":1,"1755":1,"1756":1,"1762":1,"1765":1,"1772":2,"1825":1,"1845":1,"1854":1,"1858":1,"1862":1,"1896":2,"1899":2,"1929":3,"1930":1,"2007":1,"2033":1,"2055":1,"2099":1,"2122":1,"2131":1,"2132":3,"2220":1,"2232":1,"2233":1}}],["documented",{"3":{"0":10,"37":1,"53":1,"107":1,"118":1,"122":1,"136":1,"178":1,"189":1,"200":2,"216":2,"237":1,"245":1,"248":1,"253":1,"291":1,"329":1,"333":1,"348":2,"359":1,"363":1,"415":1,"441":1,"450":1,"465":1,"484":1,"526":1,"527":1,"528":1,"545":1,"564":1,"571":1,"583":1,"599":1,"601":1,"602":1,"617":1,"620":1,"631":3,"635":1,"640":2,"657":1,"659":1,"674":1,"682":1,"690":1,"698":1,"732":1,"748":1,"749":1,"750":1,"808":1,"861":1,"897":2,"901":1,"907":2,"926":1,"930":1,"931":1,"932":1,"964":2,"966":2,"972":1,"973":1,"980":2,"981":1,"986":1,"988":1,"991":1,"1004":1,"1005":1,"1006":1,"1018":1,"1050":1,"1091":1,"1108":1,"1125":1,"1134":1,"1139":1,"1140":1,"1149":1,"1161":1,"1162":1,"1169":1,"1175":1,"1211":1,"1212":2,"1229":2,"1231":1,"1235":1,"1237":9,"1242":1,"1244":2,"1247":2,"1254":1,"1255":1,"1259":10,"1269":1,"1270":18,"1271":3,"1276":1,"1284":1,"1285":1,"1286":37,"1292":1,"1296":1,"1300":27,"1301":2,"1309":8,"1311":10,"1312":5,"1313":2,"1316":1,"1317":1,"1321":2,"1323":23,"1324":32,"1329":1,"1331":1,"1332":1,"1339":11,"1343":1,"1348":1,"1350":9,"1355":1,"1357":1,"1359":44,"1364":1,"1366":1,"1369":1,"1380":3,"1384":1,"1395":16,"1396":23,"1402":12,"1412":1,"1415":27,"1416":4,"1417":22,"1419":1,"1431":1,"1433":2,"1436":34,"1438":1,"1441":1,"1442":2,"1443":1,"1445":1,"1450":1,"1453":2,"1454":1,"1455":1,"1467":4,"1470":1,"1472":1,"1473":1,"1474":2,"1475":1,"1481":1,"1483":1,"1488":3,"1489":1,"1491":1,"1496":12,"1500":1,"1524":1,"1526":13,"1531":1,"1532":1,"1534":3,"1535":1,"1537":1,"1543":1,"1545":1,"1546":2,"1547":1,"1548":1,"1562":2,"1566":4,"1570":2,"1571":3,"1575":1,"1576":1,"1577":21,"1581":1,"1582":1,"1583":1,"1584":1,"1585":8,"1591":5,"1599":1,"1600":1,"1632":1,"1638":1,"1639":1,"1640":1,"1641":1,"1642":1,"1647":1,"1656":1,"1665":1,"1670":1,"1671":1,"1673":2,"1675":1,"1686":1,"1709":1,"1797":1,"1801":1,"1815":1,"1821":1,"1826":1,"1842":1,"1910":1,"2001":1,"2030":1,"2034":2,"2036":1,"2045":1,"2055":1,"2061":1,"2074":1,"2088":1,"2098":1,"2119":1,"2127":1,"2149":1,"2159":1,"2162":1,"2164":1,"2172":1,"2174":1,"2182":1,"2195":1,"2199":1,"2216":1,"2221":1,"2229":1,"2232":4,"2242":1}}],["dockeravailabilitytests",{"3":{"1199":1,"1207":2,"1487":1}}],["dockeravailability",{"3":{"1067":1,"1199":3,"1207":2,"1428":1,"1430":4,"1488":2}}],["dockerfile",{"3":{"867":1,"868":6,"869":19,"870":4,"872":3,"873":6,"876":14,"877":6,"1445":37,"1450":6,"1455":2,"1464":8,"1496":2}}],["dockerfiles",{"0":{"1445":1},"3":{"0":1,"868":1,"869":1,"871":1,"877":3,"1212":1,"1445":3,"1450":2,"1455":1,"1464":1}}],["docker",{"0":{"2053":1},"1":{"2050":1,"2051":1,"2052":1,"2053":1,"2054":1,"2055":1,"2056":1,"2057":1,"2058":1,"2059":1,"2060":1},"2":{"373":1,"869":1,"871":1,"1069":1},"3":{"0":3,"149":1,"152":1,"373":1,"625":1,"627":1,"642":1,"868":1,"869":1,"871":1,"873":1,"914":1,"915":1,"935":1,"1034":1,"1067":1,"1069":3,"1074":1,"1078":1,"1328":2,"1339":5,"1430":5,"1436":6,"1437":1,"1438":6,"1439":1,"1440":1,"1445":2,"1449":2,"1453":1,"1455":6,"1458":1,"1460":1,"1464":2,"1465":1,"1486":3,"1487":2,"1488":1,"1490":5,"1492":5,"1591":1,"1599":1,"1602":1,"1611":3,"1614":1,"1616":1,"1648":1,"1661":1,"1682":1,"1689":1,"1692":1,"1693":1,"1717":1,"1718":3,"1785":1,"2004":1,"2005":1,"2013":1,"2049":1,"2050":4,"2053":1,"2059":1,"2060":2,"2210":1,"2213":1,"2218":1,"2219":1}}],["downscale",{"3":{"1457":1}}],["downside",{"3":{"1441":1}}],["downstreamcheckprefix",{"3":{"1436":2}}],["downstreamreadinesscache",{"2":{"833":1},"3":{"830":1,"833":1}}],["downstreamservices",{"3":{"1436":1}}],["downstreamservicehealthcheck",{"2":{"402":1,"1443":1},"3":{"402":1,"827":1,"1199":3,"1203":1,"1207":2,"1333":1,"1337":2,"1339":12,"1443":1,"1496":2}}],["downstreams",{"3":{"99":2,"827":1,"1339":3,"1436":2,"2024":1}}],["downstreamprobeversion",{"2":{"99":2,"1447":1},"3":{"99":2,"1199":4,"1203":1,"1207":1,"1337":1,"1339":7,"1447":1}}],["downstream",{"0":{"99":1,"1337":1,"1678":1},"3":{"0":5,"25":1,"35":1,"53":1,"59":1,"97":1,"99":5,"100":1,"101":1,"146":1,"174":1,"349":1,"351":1,"369":1,"402":1,"459":1,"529":1,"572":2,"757":2,"761":2,"783":1,"826":2,"827":10,"828":2,"829":3,"830":1,"833":1,"834":1,"1066":1,"1068":1,"1212":2,"1247":2,"1252":1,"1259":1,"1263":1,"1270":1,"1271":2,"1283":1,"1284":1,"1286":6,"1296":1,"1300":4,"1301":1,"1309":4,"1311":19,"1312":6,"1323":8,"1324":6,"1325":1,"1328":3,"1329":1,"1332":2,"1337":8,"1338":2,"1339":47,"1344":1,"1350":6,"1359":14,"1395":1,"1396":7,"1402":8,"1409":1,"1415":4,"1416":1,"1417":3,"1427":4,"1429":1,"1430":1,"1433":2,"1436":21,"1438":4,"1441":2,"1442":1,"1443":1,"1447":12,"1450":1,"1453":2,"1455":1,"1467":6,"1472":1,"1476":1,"1487":1,"1488":8,"1544":1,"1575":3,"1577":2,"1578":1,"1582":1,"1583":1,"1585":1,"1670":1,"1671":1,"1677":1,"1784":1,"1811":1,"1812":1,"1821":1,"1836":1,"1852":1,"1895":1,"1954":1,"1974":2,"1977":1,"1979":2,"2007":1,"2010":1,"2019":1,"2024":2,"2028":1,"2055":1,"2056":1,"2066":1,"2114":2,"2115":2,"2127":1,"2136":1,"2172":1}}],["downward",{"3":{"1311":1,"1436":1}}],["downcasts",{"3":{"1270":1}}],["downgrading",{"3":{"641":1}}],["downgrades",{"3":{"1447":1}}],["downgraded",{"3":{"342":1,"1229":1,"1271":1,"1311":1,"1312":2,"1334":1,"1438":1,"1473":1,"1478":2,"1927":1}}],["downgrade",{"0":{"1478":2},"3":{"0":1,"98":1,"109":1,"235":1,"768":1,"790":1,"1067":1,"1192":1,"1222":1,"1229":1,"1300":1,"1327":1,"1339":3,"1380":1,"1436":2,"1443":1,"1444":1,"1466":1,"1467":3,"1470":1,"1473":2,"1478":7,"1482":3,"1483":2,"1484":2,"1591":1}}],["down",{"0":{"2010":1},"2":{"565":1},"3":{"0":7,"1":1,"58":1,"81":1,"100":1,"159":1,"234":1,"235":1,"256":1,"257":1,"293":1,"310":2,"351":1,"364":1,"413":1,"424":1,"475":1,"476":1,"490":4,"492":1,"494":1,"517":1,"543":1,"545":1,"546":1,"556":1,"563":1,"564":4,"565":1,"633":1,"640":1,"666":1,"684":1,"692":1,"700":1,"702":1,"707":1,"726":1,"749":1,"756":2,"757":1,"765":1,"766":1,"767":1,"821":1,"827":1,"853":1,"854":1,"859":1,"861":2,"868":1,"871":1,"881":1,"884":1,"912":1,"953":1,"963":1,"1003":1,"1004":1,"1018":2,"1019":1,"1050":1,"1082":1,"1084":1,"1091":1,"1101":1,"1118":1,"1133":1,"1152":1,"1186":1,"1229":3,"1235":1,"1238":1,"1247":5,"1259":3,"1270":5,"1286":7,"1300":5,"1307":1,"1309":5,"1310":1,"1311":2,"1321":1,"1323":6,"1324":25,"1333":1,"1334":1,"1339":12,"1350":5,"1352":1,"1358":1,"1359":16,"1369":4,"1373":2,"1380":1,"1395":22,"1396":15,"1402":6,"1415":3,"1416":5,"1417":12,"1422":1,"1427":2,"1436":27,"1438":3,"1441":1,"1443":1,"1445":1,"1447":4,"1455":6,"1467":4,"1476":2,"1478":1,"1480":1,"1524":1,"1531":1,"1537":1,"1556":1,"1566":1,"1686":1,"1726":1,"1731":1,"1748":1,"1749":1,"1765":1,"1784":1,"1797":1,"1799":3,"1801":1,"1810":1,"1825":2,"1827":2,"1837":1,"1864":2,"1909":1,"1911":1,"1912":3,"1944":1,"1946":2,"1948":1,"1972":1,"2000":1,"2007":1,"2028":1,"2032":1,"2034":1,"2037":1,"2038":2,"2040":1,"2043":1,"2044":1,"2056":1,"2057":1,"2062":1,"2064":1,"2067":1,"2069":1,"2070":1,"2089":1,"2118":1,"2165":1,"2169":2,"2174":1,"2189":1,"2192":1,"2198":1}}],["downloading",{"3":{"1359":1}}],["downloadable",{"3":{"1309":1}}],["downloaded",{"3":{"0":1,"555":1,"556":1,"743":1,"1116":1,"1118":1,"1342":1,"1350":1,"1416":1,"1454":1,"1456":1,"1467":1,"2067":1}}],["downloads",{"3":{"0":2,"527":1,"557":1,"1116":1,"1117":1,"1182":1,"1184":1,"1286":2,"1359":1,"1389":1,"1416":1,"1631":1,"2082":1,"2150":1}}],["download",{"3":{"0":1,"527":1,"530":1,"556":1,"725":2,"1116":1,"1117":1,"1184":1,"1285":1,"1286":3,"1300":1,"1311":3,"1323":1,"1324":10,"1344":1,"1350":2,"1357":1,"1359":2,"1380":1,"1395":2,"1410":1,"1415":3,"1438":2,"1455":1,"1467":1,"1669":1,"1672":1,"2217":1}}],["downtime",{"3":{"0":1,"361":1,"365":1,"1284":1,"1545":1,"1664":1,"1672":1,"2145":1,"2146":1}}],["do",{"0":{"1303":1,"1422":1,"1956":1,"2055":1},"1":{"2181":1,"2182":1,"2183":1,"2184":1,"2185":1,"2186":1,"2187":1,"2188":1,"2189":1,"2190":1,"2191":1,"2192":1,"2193":1},"3":{"0":3,"15":1,"17":1,"18":1,"24":1,"27":1,"47":1,"62":1,"63":1,"91":1,"97":1,"100":2,"102":1,"108":1,"109":2,"110":1,"115":1,"122":1,"123":1,"133":2,"146":1,"147":2,"156":1,"158":1,"170":1,"175":1,"176":1,"185":1,"187":1,"224":1,"228":1,"234":1,"235":1,"236":1,"243":1,"245":2,"255":1,"256":1,"265":2,"283":3,"306":1,"317":1,"319":1,"335":1,"345":1,"351":1,"353":1,"401":1,"408":1,"449":1,"450":1,"451":2,"459":3,"461":1,"466":1,"473":1,"481":2,"482":1,"497":1,"502":1,"518":2,"539":1,"541":1,"549":1,"557":1,"566":1,"573":1,"592":1,"611":1,"622":1,"625":2,"626":1,"642":1,"658":1,"665":2,"676":1,"681":1,"683":1,"684":1,"689":2,"692":1,"698":1,"725":2,"735":1,"743":2,"748":1,"749":1,"751":2,"757":1,"782":2,"783":1,"789":1,"790":1,"800":1,"802":1,"803":1,"811":1,"819":1,"821":1,"827":2,"829":1,"838":1,"861":1,"868":1,"869":2,"873":1,"875":1,"877":1,"889":1,"896":1,"897":2,"905":2,"907":1,"912":1,"914":1,"923":1,"926":2,"966":1,"971":1,"982":1,"987":1,"988":1,"989":1,"995":1,"996":1,"1019":1,"1020":1,"1026":1,"1034":1,"1041":2,"1049":1,"1053":1,"1090":1,"1091":2,"1116":1,"1117":1,"1120":1,"1141":1,"1151":2,"1168":1,"1170":1,"1174":1,"1184":1,"1229":2,"1235":1,"1237":4,"1239":2,"1240":1,"1241":1,"1244":2,"1247":9,"1248":2,"1249":1,"1253":2,"1256":1,"1258":1,"1259":8,"1269":1,"1270":14,"1271":3,"1276":1,"1278":2,"1286":38,"1287":1,"1290":1,"1297":1,"1300":15,"1301":8,"1303":1,"1309":6,"1310":5,"1311":23,"1312":1,"1313":1,"1317":1,"1318":1,"1323":20,"1324":22,"1332":2,"1339":15,"1342":1,"1345":1,"1348":1,"1350":21,"1352":2,"1353":1,"1354":1,"1357":1,"1358":1,"1359":37,"1369":3,"1373":1,"1377":1,"1380":10,"1385":1,"1387":1,"1395":15,"1396":31,"1399":1,"1401":2,"1402":8,"1403":1,"1415":10,"1416":4,"1417":34,"1418":2,"1423":1,"1427":3,"1431":1,"1432":1,"1435":1,"1436":63,"1438":3,"1442":2,"1443":2,"1444":1,"1445":1,"1453":2,"1454":1,"1455":2,"1467":3,"1470":1,"1471":1,"1472":2,"1473":1,"1474":1,"1475":3,"1476":2,"1488":2,"1489":2,"1490":3,"1491":2,"1492":1,"1496":1,"1503":1,"1513":1,"1526":1,"1532":1,"1534":3,"1539":1,"1553":2,"1572":5,"1586":1,"1596":2,"1600":1,"1630":1,"1631":1,"1632":1,"1635":1,"1638":2,"1641":2,"1646":1,"1649":1,"1651":1,"1675":1,"1676":1,"1682":1,"1683":1,"1684":1,"1685":1,"1692":1,"1693":1,"1701":1,"1706":1,"1723":1,"1724":1,"1727":1,"1728":2,"1729":1,"1730":1,"1735":1,"1741":1,"1748":1,"1749":1,"1759":1,"1767":2,"1769":1,"1779":1,"1782":1,"1788":1,"1793":2,"1794":1,"1797":1,"1799":1,"1800":1,"1802":2,"1804":1,"1805":1,"1807":2,"1810":1,"1812":1,"1815":2,"1817":1,"1819":2,"1825":1,"1826":2,"1836":2,"1845":1,"1846":2,"1847":1,"1849":1,"1850":1,"1851":1,"1857":3,"1860":1,"1862":1,"1867":2,"1868":1,"1871":1,"1876":2,"1877":2,"1881":1,"1883":1,"1888":1,"1891":1,"1898":3,"1899":3,"1902":1,"1904":1,"1909":1,"1912":1,"1915":1,"1920":1,"1923":1,"1924":1,"1927":1,"1930":1,"1932":1,"1939":1,"1946":2,"1947":1,"1950":3,"1952":1,"1955":1,"1956":1,"1959":2,"1960":3,"1961":1,"1965":1,"1966":1,"1970":1,"1973":1,"1975":1,"1981":2,"1982":1,"1986":1,"1991":1,"1992":1,"1993":2,"1994":2,"1996":1,"1997":2,"1998":2,"2001":2,"2002":2,"2011":1,"2024":2,"2032":1,"2040":2,"2042":2,"2043":1,"2047":2,"2048":1,"2060":1,"2063":2,"2064":1,"2069":2,"2077":1,"2080":1,"2085":1,"2086":1,"2091":1,"2093":1,"2094":1,"2097":2,"2100":2,"2101":1,"2107":3,"2109":1,"2116":1,"2118":2,"2119":1,"2128":1,"2131":1,"2134":1,"2135":1,"2136":1,"2139":1,"2142":1,"2153":1,"2154":1,"2155":2,"2156":1,"2161":4,"2165":2,"2166":1,"2167":1,"2170":1,"2171":2,"2180":4,"2181":3,"2182":1,"2183":3,"2195":2,"2196":1,"2199":1,"2202":1,"2203":3,"2206":1}}],["doesn",{"3":{"1557":1,"1563":1,"1566":1,"1749":1,"1755":1,"1776":1}}],["doesnotcalltheservice",{"3":{"1436":1}}],["doesnotcalltheserviceandshowstheform",{"3":{"1436":1}}],["doesnotleakpseudosentinel",{"3":{"1436":1}}],["doesnotflag",{"3":{"1436":1}}],["doesnotreport",{"3":{"1436":6}}],["doesnotshareitsduplicateguardacrossbuilders",{"3":{"1339":1}}],["doesnotthrowandcommitsonce",{"3":{"988":1}}],["doesnotuserawqueryablesurfaces",{"2":{"0":1,"700":1,"1815":1},"3":{"0":1,"545":1,"700":1,"1436":2,"1815":1}}],["does",{"0":{"1265":1,"1424":1,"1677":1,"1797":1,"1838":1,"2187":1},"3":{"0":27,"15":1,"17":1,"19":1,"20":1,"21":1,"23":1,"24":1,"26":2,"27":1,"31":1,"32":2,"39":4,"41":1,"49":1,"62":1,"68":1,"71":1,"72":1,"80":1,"81":2,"91":1,"95":1,"96":1,"109":7,"111":2,"117":1,"121":1,"122":2,"124":1,"126":2,"128":1,"131":1,"135":3,"136":2,"137":1,"147":1,"148":1,"150":1,"158":1,"160":2,"161":1,"164":1,"170":1,"175":1,"178":1,"180":1,"181":1,"185":1,"186":1,"187":1,"188":1,"193":1,"195":2,"197":1,"200":2,"202":2,"206":1,"207":1,"208":1,"209":1,"216":2,"225":1,"234":1,"236":1,"237":4,"242":1,"243":2,"245":3,"253":1,"255":2,"256":2,"260":1,"265":6,"266":1,"273":1,"275":1,"280":1,"282":1,"283":3,"291":2,"299":1,"300":1,"304":1,"305":1,"306":2,"310":2,"311":1,"312":1,"318":6,"320":1,"325":1,"330":1,"333":1,"341":4,"342":1,"343":4,"344":1,"350":4,"353":1,"365":2,"366":1,"369":2,"370":1,"371":1,"372":1,"380":3,"384":1,"388":3,"389":1,"391":1,"396":1,"397":3,"400":1,"404":2,"405":1,"416":1,"420":1,"422":1,"428":1,"442":2,"443":1,"444":1,"448":3,"450":1,"459":1,"460":1,"463":1,"470":1,"490":2,"491":1,"492":1,"493":1,"499":2,"509":1,"517":2,"520":2,"523":1,"526":1,"527":1,"528":1,"535":1,"536":2,"538":1,"539":2,"543":1,"544":1,"547":1,"549":1,"550":1,"551":1,"552":1,"556":2,"558":3,"563":1,"564":4,"572":1,"573":2,"574":1,"582":1,"583":2,"585":3,"591":1,"592":2,"593":4,"598":1,"599":2,"600":2,"601":1,"602":1,"607":3,"608":1,"609":6,"610":1,"611":3,"618":1,"620":2,"624":1,"626":4,"628":2,"633":2,"635":3,"640":2,"642":2,"649":3,"651":1,"657":2,"660":1,"664":1,"665":1,"666":1,"668":2,"673":1,"674":3,"676":2,"678":2,"682":3,"684":1,"685":1,"689":2,"691":1,"692":1,"693":1,"697":2,"698":3,"699":1,"700":1,"705":1,"707":2,"708":2,"709":1,"711":1,"714":2,"715":2,"716":1,"717":1,"718":1,"723":1,"724":1,"725":5,"726":3,"733":2,"735":1,"736":1,"740":2,"741":1,"742":1,"743":3,"748":2,"749":3,"751":1,"757":1,"759":1,"764":2,"765":2,"766":4,"767":1,"768":2,"770":1,"774":1,"775":1,"776":1,"781":1,"782":2,"784":1,"789":4,"790":3,"792":1,"793":2,"794":1,"795":2,"799":1,"800":2,"809":1,"810":3,"811":1,"812":1,"813":1,"815":1,"818":1,"819":3,"820":1,"821":1,"825":2,"826":1,"827":8,"829":2,"830":1,"831":1,"833":1,"834":1,"836":2,"837":1,"838":1,"840":1,"842":1,"849":2,"853":2,"854":1,"856":2,"860":1,"863":2,"869":2,"870":1,"873":2,"875":1,"876":2,"881":2,"883":2,"885":1,"888":2,"889":1,"890":2,"891":2,"896":1,"897":1,"899":2,"905":5,"907":1,"910":1,"913":1,"914":1,"915":1,"923":2,"924":1,"926":4,"931":1,"932":1,"937":1,"946":2,"947":1,"953":1,"954":1,"956":2,"958":1,"960":1,"963":2,"965":2,"966":1,"971":2,"972":4,"973":1,"979":1,"980":1,"982":3,"990":2,"992":2,"995":5,"996":3,"1006":1,"1013":1,"1018":3,"1020":2,"1025":1,"1026":1,"1028":1,"1034":2,"1035":2,"1036":1,"1041":1,"1042":2,"1050":4,"1052":4,"1053":1,"1059":1,"1061":1,"1062":1,"1066":1,"1067":1,"1069":2,"1070":1,"1074":2,"1075":1,"1076":1,"1077":1,"1079":2,"1082":1,"1084":1,"1090":4,"1091":6,"1092":1,"1093":1,"1100":2,"1102":2,"1107":1,"1108":2,"1110":1,"1115":1,"1116":1,"1117":1,"1119":1,"1123":1,"1124":4,"1125":1,"1132":1,"1133":2,"1134":2,"1135":2,"1142":1,"1152":1,"1157":1,"1160":1,"1161":2,"1163":1,"1168":3,"1182":1,"1183":2,"1184":1,"1186":1,"1188":1,"1193":1,"1212":4,"1216":1,"1217":1,"1218":1,"1220":1,"1223":1,"1225":1,"1226":1,"1228":2,"1229":16,"1231":1,"1232":1,"1233":1,"1235":6,"1237":26,"1240":1,"1241":5,"1242":2,"1243":1,"1244":1,"1247":32,"1248":2,"1251":1,"1252":2,"1256":1,"1258":1,"1259":23,"1260":1,"1261":2,"1263":3,"1264":4,"1265":2,"1266":1,"1267":2,"1270":49,"1271":12,"1273":3,"1274":1,"1275":3,"1278":2,"1279":2,"1281":1,"1283":1,"1286":123,"1287":1,"1288":1,"1289":4,"1290":3,"1291":1,"1293":3,"1294":2,"1295":2,"1296":1,"1297":1,"1300":86,"1301":13,"1304":2,"1307":4,"1308":2,"1309":23,"1310":8,"1311":67,"1312":9,"1313":2,"1315":4,"1317":2,"1318":3,"1320":6,"1322":1,"1323":69,"1324":125,"1327":1,"1328":2,"1330":1,"1332":1,"1333":1,"1335":1,"1336":1,"1337":1,"1338":1,"1339":54,"1340":1,"1341":1,"1342":1,"1343":1,"1344":3,"1345":1,"1346":1,"1348":2,"1349":2,"1350":50,"1352":4,"1353":2,"1355":2,"1357":1,"1358":3,"1359":220,"1362":3,"1363":2,"1365":2,"1366":2,"1369":27,"1372":1,"1373":1,"1375":1,"1376":1,"1378":2,"1380":27,"1389":3,"1390":2,"1395":107,"1396":122,"1398":1,"1400":7,"1402":41,"1405":5,"1406":2,"1408":2,"1409":2,"1410":3,"1413":1,"1415":70,"1416":12,"1417":47,"1418":2,"1423":2,"1424":2,"1425":1,"1426":1,"1427":17,"1428":1,"1429":1,"1431":4,"1432":1,"1433":3,"1435":5,"1436":207,"1438":13,"1441":5,"1442":2,"1443":3,"1445":3,"1446":2,"1447":4,"1449":1,"1453":5,"1454":2,"1455":26,"1456":4,"1457":2,"1458":1,"1459":2,"1460":5,"1461":1,"1465":2,"1467":29,"1471":1,"1473":1,"1475":9,"1476":5,"1478":2,"1480":2,"1481":4,"1483":2,"1488":6,"1489":10,"1490":8,"1491":1,"1492":10,"1496":3,"1524":1,"1525":1,"1526":5,"1528":1,"1531":2,"1532":1,"1534":5,"1553":1,"1572":3,"1577":2,"1579":1,"1582":1,"1585":2,"1591":2,"1596":1,"1599":4,"1608":1,"1627":1,"1630":1,"1631":6,"1632":1,"1635":2,"1636":1,"1638":10,"1639":13,"1640":3,"1641":3,"1646":1,"1649":1,"1651":4,"1653":5,"1655":1,"1664":1,"1668":1,"1670":1,"1675":1,"1677":4,"1683":1,"1684":4,"1685":2,"1686":2,"1688":1,"1691":1,"1699":1,"1700":1,"1701":2,"1702":1,"1703":1,"1709":1,"1717":1,"1723":3,"1724":1,"1725":1,"1726":1,"1727":6,"1728":2,"1729":1,"1730":1,"1742":1,"1748":1,"1749":1,"1750":1,"1751":1,"1760":1,"1761":1,"1764":1,"1765":5,"1769":1,"1776":1,"1783":1,"1790":4,"1792":1,"1793":2,"1799":1,"1801":1,"1802":1,"1803":4,"1804":1,"1805":3,"1810":4,"1813":1,"1815":1,"1820":2,"1821":1,"1823":1,"1824":2,"1825":2,"1828":1,"1832":4,"1833":1,"1836":1,"1837":2,"1838":1,"1839":1,"1841":2,"1842":1,"1843":2,"1844":2,"1845":1,"1847":1,"1849":3,"1851":1,"1852":2,"1854":1,"1856":1,"1857":2,"1859":4,"1861":1,"1864":1,"1865":1,"1866":1,"1867":1,"1868":1,"1869":1,"1870":2,"1871":1,"1874":1,"1875":1,"1876":2,"1879":2,"1880":1,"1881":2,"1882":2,"1883":3,"1887":1,"1890":1,"1891":2,"1896":2,"1900":1,"1901":1,"1905":1,"1907":2,"1908":2,"1911":1,"1912":1,"1913":1,"1914":1,"1915":1,"1916":1,"1917":2,"1919":2,"1922":6,"1923":3,"1926":1,"1927":2,"1928":1,"1930":1,"1931":1,"1933":3,"1935":2,"1936":1,"1938":1,"1940":2,"1941":1,"1943":1,"1945":1,"1946":2,"1952":1,"1955":1,"1958":2,"1960":1,"1961":1,"1962":2,"1964":1,"1965":2,"1966":1,"1968":1,"1969":1,"1971":1,"1976":2,"1977":1,"1978":1,"1979":1,"1982":1,"1984":2,"1988":2,"1994":1,"1995":4,"1996":3,"1998":1,"1999":1,"2000":4,"2001":3,"2002":1,"2003":1,"2007":2,"2008":2,"2010":1,"2011":1,"2016":2,"2020":2,"2021":1,"2024":3,"2025":1,"2028":1,"2030":1,"2032":3,"2035":1,"2036":1,"2037":1,"2038":1,"2041":2,"2043":1,"2044":3,"2046":2,"2047":3,"2048":1,"2053":1,"2055":1,"2057":1,"2061":2,"2063":1,"2066":2,"2068":1,"2070":4,"2072":4,"2073":1,"2076":1,"2077":1,"2079":1,"2080":1,"2081":1,"2082":1,"2083":1,"2085":3,"2092":3,"2093":1,"2098":2,"2099":2,"2100":1,"2105":1,"2106":1,"2108":1,"2109":1,"2111":1,"2113":1,"2114":2,"2118":1,"2122":2,"2126":5,"2127":1,"2131":1,"2132":3,"2134":1,"2136":1,"2139":1,"2142":1,"2143":1,"2148":2,"2150":3,"2152":1,"2154":2,"2155":1,"2156":2,"2160":1,"2162":1,"2163":3,"2164":1,"2166":2,"2167":1,"2168":2,"2171":1,"2172":1,"2174":1,"2179":1,"2180":1,"2181":2,"2183":3,"2185":1,"2186":1,"2192":2,"2193":1,"2195":1,"2197":1,"2232":1}}],["drudgery",{"3":{"1359":1}}],["dry",{"3":{"1229":1,"1237":1,"1271":1,"1300":1,"1323":1,"1350":1,"1436":1,"1496":2,"2070":1}}],["drown",{"3":{"1527":1}}],["drove",{"3":{"1021":1,"1347":1,"1350":1,"1368":1,"1369":1,"1492":1}}],["droplink",{"3":{"1359":1}}],["dropdatabaseasync",{"3":{"1436":1}}],["dropdeferred",{"2":{"1286":1},"3":{"1274":1,"1286":4,"1840":1}}],["dropdowns",{"3":{"1244":1,"1247":2,"1286":1,"1311":1,"1324":2,"1344":1,"1350":1,"1371":1,"1395":1,"1640":1,"1748":2,"2074":1}}],["dropdown",{"3":{"329":1,"330":1,"1311":4,"1380":1,"1395":5,"1632":1,"1773":1,"1987":1,"1988":1}}],["dropforeignkey",{"2":{"566":1},"3":{"566":1}}],["dropindex",{"2":{"293":1},"3":{"0":2,"293":1,"564":4,"1455":2}}],["droptable",{"2":{"293":1},"3":{"0":1,"293":1,"564":2,"1455":2}}],["dropcolumn",{"2":{"293":1,"563":1},"3":{"0":1,"293":1,"563":1,"564":2,"1455":2}}],["dropschema",{"2":{"566":1},"3":{"566":1}}],["drops",{"3":{"0":5,"20":1,"120":1,"132":1,"150":1,"243":2,"261":1,"383":2,"395":1,"397":3,"398":1,"399":1,"409":1,"505":1,"511":2,"512":1,"543":2,"549":1,"564":1,"565":1,"572":1,"583":1,"586":1,"591":1,"609":1,"633":2,"640":1,"649":1,"674":1,"675":1,"698":1,"700":1,"709":1,"775":1,"831":1,"867":1,"870":1,"881":1,"891":2,"897":1,"917":1,"930":1,"988":2,"1042":1,"1102":1,"1124":2,"1184":1,"1191":1,"1212":1,"1215":1,"1229":1,"1240":1,"1247":4,"1265":1,"1271":1,"1276":1,"1278":1,"1281":2,"1286":10,"1300":1,"1309":1,"1310":1,"1311":5,"1312":3,"1315":1,"1323":4,"1324":15,"1333":1,"1339":1,"1350":5,"1359":16,"1362":1,"1369":1,"1375":1,"1380":5,"1395":8,"1396":10,"1415":3,"1416":1,"1417":5,"1429":1,"1436":22,"1438":3,"1443":3,"1445":1,"1454":1,"1455":1,"1460":1,"1466":1,"1467":5,"1474":1,"1478":1,"1488":1,"1491":1,"1492":1,"1496":1,"1545":1,"1591":1,"1600":1,"1666":1,"1683":3,"1685":1,"1727":3,"1748":1,"1767":1,"1785":1,"1841":1,"1851":1,"1859":1,"1866":1,"1876":1,"1918":1,"1922":2,"1944":1,"1948":1,"1950":1,"2047":1,"2055":1,"2058":1,"2075":1,"2086":1,"2121":1,"2156":1,"2157":1,"2160":1,"2188":1}}],["dropoldest",{"2":{"383":1,"518":1,"520":1,"1099":1,"1100":1,"1101":1,"1102":1,"1947":1,"2183":1},"3":{"0":4,"383":1,"518":2,"520":1,"1099":1,"1100":3,"1101":1,"1102":1,"1212":2,"1396":7,"1400":2,"1944":1,"1947":2,"2183":2}}],["drop",{"0":{"1478":1},"2":{"1333":1,"1443":1},"3":{"0":5,"383":1,"395":1,"397":1,"551":1,"564":1,"565":2,"566":3,"573":1,"617":1,"698":1,"732":1,"741":1,"869":1,"871":1,"890":1,"893":1,"897":1,"905":1,"907":2,"910":1,"916":1,"932":1,"1019":1,"1085":1,"1100":1,"1101":1,"1124":1,"1247":2,"1257":1,"1259":2,"1270":2,"1274":1,"1286":8,"1290":1,"1300":2,"1301":2,"1311":3,"1312":1,"1323":1,"1324":10,"1333":3,"1337":1,"1339":7,"1359":3,"1369":1,"1380":2,"1386":1,"1395":9,"1396":5,"1400":1,"1402":2,"1415":1,"1417":4,"1428":1,"1436":10,"1438":1,"1443":3,"1445":1,"1455":1,"1456":1,"1463":1,"1467":7,"1470":1,"1471":1,"1473":1,"1478":4,"1480":1,"1482":2,"1483":2,"1484":1,"1490":2,"1496":5,"1524":1,"1534":2,"1545":1,"1577":1,"1591":1,"1596":2,"1611":1,"1617":1,"1653":2,"1668":1,"1669":1,"1677":1,"1684":1,"1687":1,"1688":1,"1689":1,"1702":1,"1727":1,"1765":1,"1812":1,"1853":1,"1862":1,"1922":1,"1924":1,"1940":1,"1944":2,"1947":1,"1950":2,"1958":1,"1959":1,"1973":1,"2010":2,"2024":1,"2026":1,"2045":1,"2142":1,"2156":4,"2160":4,"2193":1,"2197":1,"2232":1}}],["droppable",{"3":{"2002":1}}],["dropprimarykey",{"2":{"566":1},"3":{"566":1}}],["dropping",{"3":{"0":3,"24":1,"26":1,"175":1,"196":1,"402":1,"512":1,"545":1,"549":1,"607":1,"633":1,"698":1,"749":1,"838":1,"869":1,"988":1,"989":1,"1012":1,"1028":1,"1241":2,"1247":2,"1257":1,"1259":2,"1270":1,"1286":3,"1300":1,"1311":3,"1323":1,"1324":2,"1339":2,"1355":1,"1359":7,"1380":1,"1395":1,"1396":4,"1416":2,"1417":2,"1427":2,"1438":1,"1443":1,"1467":3,"1473":1,"1489":2,"1490":1,"1526":1,"1705":1,"1727":1,"1815":1,"1947":1,"2001":1,"2187":1}}],["droppedcount",{"2":{"383":1,"1949":1},"3":{"383":1,"1100":1,"1396":4,"1944":1,"1949":1}}],["dropped",{"3":{"0":5,"1":2,"17":1,"19":1,"26":1,"45":1,"47":1,"72":1,"109":1,"110":1,"175":1,"265":1,"279":1,"312":1,"324":1,"335":1,"353":1,"383":1,"386":1,"397":2,"398":1,"399":1,"407":2,"409":1,"427":1,"428":1,"468":1,"518":2,"526":1,"534":1,"535":1,"538":1,"592":1,"607":1,"633":1,"638":1,"664":1,"703":1,"764":2,"766":2,"856":1,"881":1,"914":1,"916":1,"931":1,"988":2,"991":1,"1116":1,"1117":1,"1119":1,"1123":1,"1139":1,"1144":1,"1149":1,"1163":1,"1191":1,"1196":2,"1202":1,"1212":1,"1241":2,"1247":2,"1252":1,"1263":2,"1270":2,"1286":6,"1300":2,"1301":3,"1309":1,"1311":7,"1323":2,"1324":11,"1339":5,"1359":11,"1379":1,"1380":1,"1395":7,"1396":11,"1399":1,"1402":2,"1415":4,"1416":4,"1417":4,"1427":2,"1436":6,"1438":4,"1441":1,"1443":1,"1459":1,"1467":4,"1469":1,"1473":1,"1475":1,"1478":1,"1488":2,"1492":1,"1496":26,"1500":1,"1547":1,"1639":1,"1664":1,"1677":2,"1697":1,"1702":1,"1806":1,"1813":1,"1814":1,"1840":3,"1922":1,"1929":1,"1944":1,"1988":1,"2000":2,"2002":1,"2010":1,"2089":1,"2156":2,"2164":1,"2168":1,"2182":1}}],["dressed",{"3":{"1019":1}}],["drew",{"3":{"571":1}}],["drama",{"3":{"2161":1,"2166":1}}],["dragging",{"3":{"1286":1,"1301":1,"1324":1,"1359":1,"1395":1,"1396":1,"1404":1,"1436":1,"1455":1,"1805":1,"2165":1}}],["dragged",{"3":{"608":1,"1217":1,"1467":1,"2158":1}}],["drags",{"3":{"673":1,"1067":1,"1286":2,"1311":1,"1325":1,"1350":1,"1436":1,"1438":1,"1459":1,"1585":1,"1863":1,"2004":1,"2073":1,"2172":1}}],["drag",{"3":{"640":1,"1061":1,"1286":1,"1300":1,"1306":1,"1309":1,"1436":3,"1459":1,"2071":1}}],["drafts",{"3":{"464":1}}],["draft",{"2":{"384":1},"3":{"384":1,"1350":1,"1380":2,"1384":1,"1389":2,"1395":5,"1396":3,"1398":1,"1399":1,"1402":20,"1436":1,"1630":1,"1632":2}}],["draw",{"3":{"799":1,"1286":2,"1300":3,"1301":1,"1309":1,"1324":1,"1350":2,"1395":2,"1396":2,"1402":1,"1417":3,"1572":1,"1788":1,"1793":1,"2002":1,"2146":1,"2203":1,"2241":1}}],["drawing",{"2":{"681":1,"1480":2},"3":{"681":1,"1415":1,"1436":1,"1480":3}}],["drawericon",{"3":{"1324":1}}],["drawertext",{"3":{"1324":1}}],["drawerbackground",{"3":{"1324":1}}],["drawers",{"3":{"650":1,"1324":1}}],["drawer",{"3":{"648":1,"649":1,"1324":3,"1577":1,"1591":1,"1600":2,"1669":1,"1712":1,"1713":2,"1714":1,"1741":1,"1742":2,"1750":2,"1759":1,"1768":1,"1773":1,"2077":1}}],["draws",{"3":{"310":1,"359":1,"499":1,"946":1,"1059":2,"1259":3,"1262":1,"1270":1,"1286":6,"1300":3,"1309":1,"1311":2,"1323":2,"1324":3,"1339":2,"1350":1,"1359":3,"1369":2,"1380":1,"1395":1,"1396":2,"1415":2,"1417":2,"1433":1,"1436":1,"1455":1,"1982":1,"2055":1,"2142":1,"2201":1}}],["drawn",{"3":{"24":1,"136":1,"1262":1,"1286":2,"1300":1,"1311":1,"1323":1,"1324":1,"1359":1,"1369":1,"1396":1,"1417":1,"1487":1,"1494":1,"1517":1,"1631":1,"1793":1,"1999":1,"2044":1,"2099":1,"2136":1,"2239":1}}],["drainallasync",{"3":{"1259":2,"1275":1,"1286":2}}],["drainable",{"3":{"1237":1}}],["drainers",{"3":{"1436":1}}],["drainer",{"3":{"1248":1}}],["drained",{"3":{"0":1,"78":1,"702":1,"845":1,"848":1,"849":1,"1068":1,"1237":1,"1254":1,"1259":4,"1286":2,"1323":2,"1380":1,"1396":1,"1402":1,"1415":1,"1436":1,"1478":1,"1808":1,"1842":1,"1889":1,"1890":1,"1891":1,"1947":1,"2020":1}}],["draining",{"3":{"256":1,"573":1,"847":2,"1257":2,"1259":2,"1275":1,"1286":1,"1323":1,"1367":1,"1417":1,"1438":1,"1665":1,"1891":1,"1945":1}}],["drain",{"0":{"2188":1},"1":{"515":1,"516":1,"517":1,"518":1,"519":1,"520":1,"521":1,"522":1,"523":1,"524":1,"1097":1,"1098":1,"1099":1,"1100":1,"1101":1,"1102":1,"1103":1,"1104":1},"3":{"0":6,"15":1,"20":1,"24":1,"78":1,"383":1,"515":1,"516":1,"517":1,"518":6,"520":1,"541":1,"573":1,"706":2,"709":1,"897":1,"1016":1,"1018":1,"1022":1,"1043":1,"1068":1,"1097":1,"1100":4,"1101":1,"1102":1,"1150":1,"1152":1,"1157":1,"1212":2,"1233":1,"1237":1,"1259":7,"1275":4,"1286":4,"1307":2,"1309":4,"1318":1,"1323":1,"1339":2,"1367":1,"1368":1,"1369":1,"1396":20,"1400":1,"1402":9,"1417":1,"1436":4,"1438":1,"1467":2,"1492":3,"1496":1,"1525":1,"1577":1,"1665":1,"1675":1,"1839":1,"1846":1,"1889":1,"1892":1,"1944":2,"1946":1,"1947":2,"1949":2,"1950":2,"2154":1,"2156":2,"2160":2,"2183":1,"2187":1,"2193":1,"2232":2}}],["drains10000messages",{"3":{"1492":1}}],["drainsource",{"3":{"1286":1}}],["drains",{"3":{"0":2,"25":1,"27":1,"47":1,"124":1,"193":1,"198":1,"255":1,"413":1,"518":1,"571":1,"583":1,"673":1,"698":1,"701":1,"703":1,"1034":1,"1037":1,"1041":1,"1100":1,"1140":1,"1212":1,"1237":1,"1254":2,"1257":1,"1258":1,"1259":8,"1273":1,"1275":3,"1286":8,"1318":1,"1323":4,"1324":1,"1396":2,"1399":1,"1402":1,"1415":1,"1416":1,"1417":6,"1436":2,"1438":2,"1526":1,"1577":1,"1665":1,"1790":1,"1812":1,"1837":1,"1841":2,"1851":2,"2012":1,"2121":1,"2157":1,"2160":1,"2183":1}}],["dr",{"0":{"1474":1,"1475":2,"2036":1},"3":{"0":3,"68":2,"69":1,"70":1,"87":1,"625":4,"626":5,"628":4,"756":1,"971":1,"972":1,"973":1,"974":1,"1212":3,"1452":1,"1455":2,"1460":15,"1461":2,"1463":1,"1467":1,"1471":3,"1473":1,"1474":7,"1475":63,"1476":1,"1482":4,"1483":8,"1484":2,"1486":2,"1496":6,"1525":3,"1526":10,"1534":1,"1535":2,"1588":1,"1590":3,"1591":9,"1596":1,"1600":3,"1706":2,"1710":2,"2034":3,"2037":1,"2038":1,"2232":1}}],["drilling",{"3":{"1526":1,"1555":1}}],["drills",{"3":{"1474":1,"1707":1,"2038":1}}],["drillresult",{"3":{"1199":2,"1207":1}}],["drill",{"0":{"1475":3,"1710":1},"2":{"1475":3},"3":{"0":2,"68":1,"69":1,"70":1,"625":7,"626":3,"627":1,"628":3,"630":1,"1212":1,"1438":1,"1452":1,"1455":1,"1460":23,"1461":2,"1467":1,"1471":4,"1474":16,"1475":91,"1482":6,"1483":11,"1484":4,"1496":7,"1525":3,"1526":8,"1535":2,"1575":1,"1577":3,"1582":1,"1586":1,"1590":2,"1591":7,"1600":2,"1670":1,"1674":1,"1706":4,"1707":5,"1709":4,"1710":5,"1800":1,"2029":1,"2034":10,"2035":1,"2037":4,"2038":4,"2146":1,"2232":1}}],["drilled",{"0":{"2034":1},"1":{"2029":1,"2030":1,"2031":1,"2032":1,"2033":1,"2034":1,"2035":1,"2036":1,"2037":1,"2038":1},"3":{"0":1,"68":2,"69":1,"71":1,"294":1,"568":1,"608":1,"614":1,"625":1,"630":1,"758":1,"762":1,"1212":1,"1467":1,"1474":1,"1475":1,"1525":1,"1526":1,"1535":1,"1588":1,"1590":1,"1591":1,"1600":1,"1784":1,"2028":1,"2029":3,"2034":2,"2037":2,"2038":1,"2209":1,"2232":1}}],["driving",{"3":{"0":2,"39":1,"572":1,"650":1,"1018":1,"1229":1,"1259":1,"1286":2,"1300":1,"1311":1,"1324":3,"1339":1,"1402":1,"1417":2,"1431":1,"1436":9,"1437":1,"1438":1,"1577":1,"1999":1,"2054":1}}],["drive",{"3":{"864":1,"953":1,"1162":1,"1229":3,"1230":1,"1248":1,"1256":1,"1259":4,"1286":4,"1317":1,"1320":1,"1323":1,"1324":7,"1335":1,"1339":1,"1342":1,"1350":1,"1359":4,"1369":1,"1396":3,"1402":1,"1415":1,"1417":1,"1428":1,"1436":7,"1438":1,"1487":1,"1489":2,"1513":1,"1526":1,"1566":1,"1591":1,"1868":1,"1948":1,"2000":1,"2079":1,"2169":1,"2239":1}}],["driven",{"0":{"1355":1,"1508":1,"1541":1,"1543":1},"1":{"297":1,"298":1,"299":1,"300":1,"301":1,"302":1,"303":1,"304":1,"305":1,"306":1},"3":{"0":5,"96":1,"117":2,"126":1,"136":1,"142":1,"157":1,"165":1,"200":1,"297":1,"359":1,"365":1,"402":1,"507":1,"549":1,"564":1,"572":1,"574":1,"599":1,"690":1,"706":2,"759":1,"838":1,"856":1,"881":2,"916":1,"923":1,"927":1,"947":1,"1012":1,"1050":1,"1124":2,"1194":1,"1202":1,"1203":1,"1212":3,"1213":1,"1216":2,"1225":1,"1230":1,"1231":2,"1234":1,"1236":1,"1237":11,"1238":1,"1239":1,"1245":1,"1247":5,"1249":1,"1259":28,"1260":1,"1269":2,"1270":10,"1271":2,"1281":1,"1286":23,"1300":2,"1304":1,"1309":17,"1311":7,"1312":3,"1316":1,"1317":1,"1323":12,"1324":21,"1326":1,"1333":1,"1339":2,"1340":1,"1343":1,"1345":1,"1346":1,"1350":52,"1352":1,"1355":1,"1359":85,"1369":1,"1370":1,"1380":9,"1383":1,"1384":1,"1390":1,"1395":7,"1396":38,"1402":25,"1403":1,"1405":1,"1413":1,"1415":26,"1416":1,"1417":10,"1421":1,"1427":1,"1435":1,"1436":63,"1437":1,"1438":8,"1441":2,"1443":2,"1446":2,"1450":2,"1454":1,"1455":1,"1463":1,"1467":1,"1476":1,"1487":1,"1488":3,"1489":3,"1493":1,"1496":5,"1499":2,"1512":1,"1525":1,"1526":3,"1535":2,"1546":1,"1547":1,"1551":1,"1557":1,"1568":1,"1577":6,"1581":1,"1582":1,"1583":1,"1585":1,"1586":1,"1591":3,"1597":1,"1600":1,"1621":1,"1627":1,"1638":1,"1652":1,"1664":1,"1668":1,"1752":1,"1796":2,"1800":1,"1812":1,"1815":2,"1821":1,"1862":1,"1888":1,"1892":2,"1929":3,"1933":1,"1948":1,"1956":1,"2013":1,"2045":1,"2072":1,"2079":1,"2096":1,"2098":1,"2114":1,"2142":1,"2162":1,"2167":1,"2182":1,"2212":1,"2213":3,"2216":1,"2217":1,"2218":2,"2219":2,"2230":2,"2238":1,"2239":2,"2240":2}}],["drives",{"3":{"0":1,"70":1,"142":1,"166":1,"176":1,"239":1,"330":1,"536":2,"572":2,"625":1,"627":1,"657":1,"832":1,"838":1,"859":1,"861":3,"863":1,"864":1,"907":2,"925":1,"954":1,"1217":1,"1229":3,"1274":1,"1286":7,"1300":2,"1310":2,"1311":7,"1315":1,"1323":4,"1324":12,"1337":1,"1339":6,"1342":1,"1350":8,"1359":9,"1369":2,"1380":5,"1395":4,"1396":9,"1402":1,"1406":2,"1415":2,"1416":2,"1417":4,"1425":1,"1427":1,"1428":1,"1433":2,"1435":1,"1436":28,"1438":12,"1456":1,"1487":1,"1488":2,"1489":1,"1490":1,"1492":1,"1513":1,"1611":2,"1650":1,"1664":1,"1726":1,"1732":1,"1765":1,"1805":1,"1865":1,"1881":1,"1988":1,"2024":1,"2044":1,"2055":2,"2075":1,"2177":1}}],["driver",{"3":{"0":1,"96":1,"388":1,"1436":4,"1455":2,"1496":1}}],["drifting",{"3":{"148":1,"841":1,"1247":1,"1259":1,"1271":1,"1300":1,"1301":1,"1309":2,"1311":2,"1324":2,"1332":1,"1339":2,"1359":5,"1369":1,"1380":1,"1395":1,"1402":2,"1415":1,"1417":1,"1431":1,"1436":9,"1441":1,"1443":1,"1475":1,"2042":1,"2080":1,"2097":1}}],["drift",{"3":{"0":5,"26":1,"57":1,"61":1,"70":1,"94":1,"100":1,"126":1,"130":2,"136":1,"137":1,"138":1,"215":1,"242":1,"256":1,"265":1,"291":1,"293":1,"296":2,"331":1,"341":1,"372":1,"373":1,"426":1,"449":1,"484":1,"493":1,"511":1,"526":1,"529":1,"530":1,"545":1,"558":1,"567":1,"608":1,"619":1,"634":2,"658":1,"691":1,"734":2,"764":2,"766":8,"767":3,"768":2,"783":1,"784":1,"821":1,"831":1,"832":1,"836":1,"838":2,"839":1,"872":1,"883":1,"940":1,"973":1,"1006":1,"1014":1,"1018":3,"1019":2,"1020":1,"1101":1,"1124":1,"1212":2,"1224":1,"1227":1,"1229":3,"1237":1,"1240":1,"1253":1,"1262":1,"1270":3,"1271":2,"1278":1,"1286":6,"1300":12,"1301":2,"1305":1,"1309":4,"1311":9,"1312":1,"1320":1,"1323":3,"1324":12,"1330":2,"1336":1,"1339":9,"1350":9,"1356":1,"1358":1,"1359":39,"1366":1,"1369":2,"1372":1,"1380":2,"1384":1,"1395":10,"1396":17,"1398":1,"1402":9,"1405":1,"1409":1,"1415":4,"1416":2,"1427":2,"1431":2,"1436":38,"1438":2,"1441":1,"1442":1,"1443":1,"1449":1,"1453":2,"1454":3,"1455":6,"1457":4,"1461":2,"1467":3,"1469":2,"1476":2,"1479":1,"1489":3,"1490":1,"1492":1,"1496":31,"1500":1,"1524":1,"1525":1,"1526":12,"1528":1,"1531":1,"1535":1,"1545":1,"1546":1,"1553":1,"1554":2,"1562":1,"1576":1,"1577":8,"1579":1,"1582":1,"1585":1,"1586":1,"1591":7,"1592":1,"1600":1,"1614":1,"1654":1,"1666":1,"1671":2,"1673":1,"1677":1,"1686":1,"1731":1,"1805":1,"1809":1,"1818":1,"1819":1,"1827":1,"1828":1,"1830":1,"1834":1,"1862":1,"1928":1,"1929":1,"1962":1,"1987":2,"1991":1,"2006":1,"2011":1,"2014":1,"2024":1,"2042":2,"2043":1,"2049":1,"2073":1,"2075":2,"2083":1,"2130":1,"2131":1,"2135":1,"2153":1,"2158":1,"2159":1,"2176":1,"2193":1}}],["driftedmarkedcommandhandler",{"3":{"1199":2,"1207":1}}],["driftedmarkedcommand",{"3":{"1199":3,"1207":1,"1270":1}}],["driftedtests",{"3":{"1198":1,"1199":4,"1207":2,"1436":24,"1496":2}}],["drifted",{"3":{"0":2,"136":2,"137":1,"140":1,"142":1,"161":1,"213":1,"251":1,"252":2,"254":2,"257":1,"258":1,"259":1,"291":1,"373":1,"466":1,"609":1,"644":1,"648":1,"744":1,"857":1,"876":2,"952":1,"959":1,"1020":1,"1322":1,"1323":1,"1324":3,"1330":1,"1339":1,"1350":1,"1380":1,"1384":1,"1396":1,"1431":1,"1436":20,"1460":1,"1474":1,"1476":1,"1487":1,"1489":2,"1490":1,"1496":9,"1500":1,"2044":2,"2046":1,"2148":1}}],["drifts",{"3":{"0":1,"185":1,"329":1,"517":1,"634":1,"706":1,"791":1,"973":1,"1006":1,"1237":1,"1396":1,"1415":1,"1436":6,"1453":1,"1662":1,"1673":1,"1701":1,"1730":1,"1834":1,"1948":1,"1960":2,"1986":1,"1991":1,"2050":1,"2097":1,"2147":1,"2155":1}}],["degenerate",{"3":{"1247":1,"1324":1}}],["degrees",{"3":{"1315":1,"1417":2}}],["degree",{"3":{"583":2,"1315":2,"1323":2,"1417":1,"1881":3}}],["degradable",{"3":{"1311":2,"1670":1}}],["degradationwarned",{"3":{"1323":2}}],["degradation",{"3":{"68":1,"71":1,"182":1,"235":2,"413":1,"682":1,"725":1,"727":1,"898":1,"901":1,"996":2,"1259":1,"1270":1,"1286":1,"1300":4,"1301":1,"1309":3,"1311":3,"1316":1,"1323":6,"1324":5,"1332":2,"1339":3,"1349":1,"1350":4,"1358":1,"1359":4,"1369":2,"1395":2,"1396":5,"1415":1,"1416":1,"1417":17,"1438":4,"1443":3,"1450":1,"1467":1,"1524":2,"1526":2,"1544":1,"1559":1,"1566":1,"1577":1,"1638":1,"1707":2,"1910":1,"1912":1,"2007":1,"2067":1,"2162":1}}],["degrading",{"3":{"212":1,"601":1,"684":1,"700":1,"1237":1,"1284":1,"1286":1,"1300":2,"1311":1,"1324":2,"1339":1,"1350":1,"1359":2,"1380":2,"1391":1,"1396":1,"1401":1,"1402":1,"1415":1,"1417":1,"1455":1,"1467":1,"1851":1,"1859":1,"1927":1,"1933":1,"2197":1}}],["degradeforeignkey",{"3":{"1286":2}}],["degradetestcontext",{"3":{"1199":2,"1207":1}}],["degradecustomer",{"3":{"1198":1,"1199":4,"1207":1}}],["degradeorder",{"3":{"1198":1,"1199":4,"1207":1}}],["degraded",{"2":{"157":1,"159":1,"161":1,"1909":1,"1911":1},"3":{"157":2,"159":1,"161":1,"440":1,"583":2,"585":1,"603":1,"676":1,"726":1,"821":1,"932":1,"997":1,"1247":1,"1270":1,"1286":1,"1300":1,"1301":2,"1304":1,"1309":2,"1311":9,"1323":7,"1337":3,"1339":5,"1396":1,"1415":1,"1417":10,"1436":2,"1443":1,"1577":1,"1627":1,"1666":1,"1858":1,"1859":1,"1909":2,"1911":1,"1934":1,"2006":1,"2013":1,"2126":1,"2156":1}}],["degrade",{"3":{"49":1,"166":1,"167":1,"168":1,"169":1,"413":1,"585":1,"665":1,"724":1,"997":1,"1083":1,"1086":1,"1212":2,"1228":1,"1247":1,"1270":3,"1286":4,"1301":1,"1309":1,"1311":1,"1323":3,"1324":3,"1339":1,"1356":1,"1359":6,"1377":1,"1380":1,"1395":3,"1396":7,"1415":3,"1417":6,"1436":1,"1438":4,"1467":2,"1500":1,"1510":1,"1531":1,"1663":1,"1708":1,"1849":1,"1860":1,"1862":1,"1864":1,"1868":1,"1970":1,"2053":1,"2079":1,"2119":1,"2126":1,"2230":1}}],["degradesgracefully",{"3":{"1707":1}}],["degradestotheplaininbox",{"3":{"1436":1}}],["degrades",{"0":{"2067":1},"3":{"0":2,"178":1,"235":1,"255":1,"266":1,"350":1,"391":1,"461":2,"507":1,"511":1,"584":1,"792":1,"854":1,"896":1,"905":1,"995":1,"1212":1,"1229":1,"1236":1,"1237":1,"1247":2,"1250":1,"1259":2,"1264":1,"1266":1,"1270":2,"1283":1,"1285":1,"1286":5,"1290":1,"1299":1,"1300":9,"1301":5,"1309":1,"1311":4,"1312":2,"1322":1,"1323":8,"1324":5,"1328":1,"1332":1,"1339":5,"1350":1,"1359":10,"1378":1,"1380":4,"1395":14,"1396":7,"1401":1,"1402":3,"1410":1,"1415":6,"1416":2,"1417":14,"1438":2,"1443":2,"1444":1,"1467":1,"1476":1,"1534":1,"1641":1,"1666":1,"1668":1,"1685":1,"1708":1,"1729":1,"1751":1,"1772":1,"1858":1,"1917":1,"1922":1,"1923":1,"1924":1,"1952":1,"1977":1,"1982":1,"1991":1,"2001":1,"2007":1,"2067":1,"2083":1,"2086":1,"2118":1,"2119":1,"2123":1,"2232":1}}],["dequeued",{"3":{"1323":1}}],["deque",{"2":{"1453":1},"3":{"1213":1,"1436":2,"1488":1}}],["dereferenced",{"3":{"1359":1}}],["dereference",{"3":{"1300":1,"1324":1,"1372":1,"1396":1,"1467":1}}],["dereferences",{"3":{"448":1,"1311":1,"1359":3,"1436":2,"2131":1}}],["dereferencing",{"3":{"1300":1,"1436":1}}],["deregistration",{"3":{"1247":1}}],["derivable",{"3":{"1396":1,"1436":1,"1638":1}}],["derivatives",{"3":{"1599":1}}],["derivative",{"3":{"741":1,"743":1,"1050":1,"1051":1,"1235":2,"1237":2,"1395":2}}],["derivations",{"3":{"0":1,"963":1,"964":1,"1227":1,"1286":1,"1359":1,"1805":1}}],["derivation",{"3":{"0":2,"279":1,"309":2,"539":1,"946":1,"963":1,"1004":1,"1006":1,"1018":1,"1282":1,"1289":1,"1300":10,"1311":1,"1395":2,"1396":3,"1436":5,"1453":1,"1454":1,"1856":1,"1902":2}}],["deriving",{"3":{"0":1,"32":1,"202":1,"218":1,"634":1,"723":1,"725":1,"739":1,"742":1,"782":2,"854":1,"883":1,"932":1,"946":1,"963":3,"966":1,"1026":1,"1042":1,"1092":1,"1237":1,"1239":1,"1259":1,"1270":2,"1279":1,"1286":13,"1300":4,"1309":3,"1311":2,"1323":1,"1324":2,"1350":3,"1359":11,"1369":2,"1380":1,"1382":1,"1395":5,"1396":2,"1402":2,"1415":3,"1416":1,"1436":20,"1441":2,"1467":1,"2055":1,"2080":1,"2176":1,"2184":1}}],["derivequestionentity",{"3":{"1359":1}}],["derive",{"3":{"0":1,"111":1,"305":1,"459":1,"465":1,"540":1,"651":1,"657":1,"780":2,"782":1,"784":1,"881":3,"954":1,"956":1,"960":1,"963":1,"1034":1,"1058":1,"1212":3,"1229":2,"1235":1,"1237":2,"1247":2,"1252":1,"1259":5,"1270":1,"1271":1,"1282":1,"1286":2,"1300":6,"1301":1,"1309":2,"1311":2,"1313":1,"1317":1,"1323":6,"1324":5,"1339":2,"1342":1,"1345":2,"1350":5,"1353":1,"1359":4,"1361":1,"1369":1,"1371":2,"1380":2,"1383":1,"1395":6,"1396":7,"1399":1,"1402":4,"1415":1,"1417":1,"1436":6,"1454":2,"1585":1,"1663":1,"1769":2,"1829":1,"1856":1,"1857":1,"1886":1,"1925":1,"2030":1,"2198":1}}],["derivedoverride",{"3":{"2000":1}}],["derivedatasourcekey",{"3":{"1286":2}}],["derivedupdatecommandtests",{"2":{"926":1},"3":{"926":1,"1199":1,"1207":1}}],["derived",{"1":{"631":1,"632":1,"633":1,"634":1,"635":1,"636":1},"3":{"0":5,"135":1,"147":1,"175":1,"186":1,"208":1,"235":1,"273":1,"328":1,"350":1,"507":1,"539":1,"585":1,"609":2,"631":1,"633":1,"780":2,"922":1,"923":1,"926":2,"927":1,"946":2,"954":1,"964":1,"1004":1,"1017":1,"1018":1,"1026":1,"1034":1,"1037":1,"1116":1,"1125":1,"1161":1,"1212":1,"1221":1,"1229":5,"1235":1,"1237":13,"1240":1,"1242":1,"1244":1,"1247":3,"1256":1,"1259":5,"1263":1,"1265":5,"1270":17,"1271":1,"1286":12,"1291":1,"1300":21,"1309":1,"1310":3,"1311":12,"1312":1,"1317":1,"1321":1,"1323":9,"1324":13,"1326":1,"1339":9,"1342":1,"1347":1,"1348":1,"1350":5,"1354":2,"1359":21,"1362":1,"1369":6,"1372":1,"1380":1,"1383":1,"1385":1,"1395":28,"1396":15,"1402":6,"1415":5,"1416":1,"1417":3,"1427":3,"1431":2,"1436":45,"1438":4,"1441":2,"1443":1,"1454":1,"1455":1,"1465":1,"1467":2,"1470":1,"1476":1,"1488":1,"1492":1,"1496":5,"1529":1,"1538":1,"1571":1,"1580":1,"1594":1,"1630":4,"1631":2,"1635":1,"1640":1,"1651":1,"1658":1,"1666":2,"1667":1,"1684":1,"1695":1,"1705":1,"1715":1,"1727":2,"1755":1,"1764":5,"1765":1,"1767":2,"1777":1,"1805":1,"1810":1,"1849":2,"1856":1,"1880":1,"1962":1,"1965":2,"1982":1,"2000":3,"2043":1,"2046":1,"2075":1,"2083":1,"2089":1,"2110":1,"2232":1}}],["derivesfrombaseentity",{"3":{"1436":2}}],["derives",{"3":{"0":6,"25":1,"81":1,"109":1,"166":1,"231":1,"310":1,"370":1,"459":1,"499":1,"501":1,"534":1,"536":2,"545":1,"549":1,"575":1,"585":1,"618":1,"741":1,"751":1,"879":1,"884":1,"905":1,"909":1,"922":1,"925":1,"926":2,"944":1,"946":1,"954":1,"964":1,"972":1,"974":1,"1004":1,"1036":1,"1050":1,"1140":1,"1212":1,"1213":1,"1229":1,"1237":3,"1259":4,"1270":2,"1271":2,"1276":1,"1286":17,"1288":2,"1292":1,"1297":1,"1300":9,"1309":4,"1310":1,"1311":10,"1318":1,"1323":5,"1324":6,"1328":1,"1339":2,"1342":1,"1350":6,"1352":2,"1355":2,"1357":2,"1359":29,"1364":1,"1369":2,"1371":1,"1379":1,"1380":5,"1388":1,"1389":1,"1395":8,"1396":4,"1402":4,"1415":2,"1416":1,"1417":1,"1427":1,"1431":2,"1436":28,"1449":1,"1454":2,"1488":2,"1492":1,"1810":1,"1902":2,"1908":1,"1927":1,"2043":1,"2072":1,"2074":1,"2163":1,"2165":1,"2167":1}}],["deal",{"3":{"1350":1,"1931":1}}],["deactivation",{"3":{"497":2,"499":1,"1755":2,"1776":1}}],["deactivating",{"3":{"458":1}}],["deactivate",{"2":{"497":1,"1755":1,"1776":1},"3":{"497":1,"499":1,"1324":1,"1436":2,"1755":1,"1769":1,"1776":1}}],["deactivated",{"3":{"284":1,"460":1,"499":1,"1300":2,"1417":1,"1455":1,"1461":1,"1747":2,"1983":1,"2066":2}}],["death",{"3":{"444":1}}],["deadletterasync",{"3":{"1286":1}}],["deadletteredon",{"2":{"2186":1},"3":{"1286":5,"1323":2,"2186":1}}],["deadlettered",{"2":{"1273":1},"3":{"1273":1}}],["deadlettercounter",{"3":{"707":1,"1259":2,"1286":1}}],["deadletterretentiondays",{"2":{"818":1,"1256":1},"3":{"0":2,"20":1,"24":1,"818":1,"1256":1,"1259":1,"1286":1,"1467":1,"1496":1,"1577":1,"1845":1}}],["deadlocking",{"3":{"1493":1}}],["deadlock",{"0":{"1328":1},"3":{"55":1,"481":1,"1066":1,"1068":1,"1286":1,"1301":1,"1312":1,"1328":2,"1339":1,"1378":1,"1441":3,"1492":1,"1549":1,"1670":1,"1684":1,"1792":1,"2009":1,"2030":1,"2056":1}}],["deadlocks",{"3":{"0":1,"483":1,"1212":1,"1300":1,"1430":1,"1653":1,"1700":1,"2022":1}}],["deadlineexceeded",{"3":{"100":1,"1396":1}}],["deadlinescheduler",{"2":{"814":1},"3":{"814":1}}],["deadlines",{"3":{"0":1,"810":1,"1160":1,"1212":1,"1380":1,"2168":1,"2169":1}}],["deadline",{"0":{"539":1,"813":1},"3":{"0":5,"237":1,"534":2,"536":2,"538":2,"539":8,"541":2,"724":1,"725":1,"808":1,"809":2,"810":3,"812":1,"813":3,"815":2,"1067":1,"1153":1,"1212":2,"1286":1,"1300":2,"1307":1,"1309":2,"1312":3,"1323":2,"1359":1,"1378":2,"1380":10,"1396":8,"1413":1,"1415":4,"1430":1,"1433":1,"1436":4,"1438":1,"1449":1,"1467":1,"1476":1,"1481":1,"1496":1,"1600":1,"1780":1,"1927":1,"1946":1,"2161":1,"2165":1,"2167":4,"2168":3,"2169":1,"2232":1}}],["dead",{"0":{"1256":1},"2":{"20":1,"397":1,"1256":1,"2191":1},"3":{"0":11,"15":4,"17":1,"19":2,"20":3,"21":1,"22":2,"23":1,"24":7,"26":1,"27":3,"71":1,"122":1,"125":1,"195":1,"223":1,"229":1,"230":3,"305":1,"310":1,"395":4,"396":1,"397":3,"398":1,"399":2,"404":2,"406":1,"409":1,"413":2,"444":1,"464":2,"465":1,"497":1,"536":1,"537":1,"539":1,"607":1,"608":1,"609":4,"611":2,"627":1,"708":1,"709":1,"751":1,"756":1,"791":1,"810":2,"815":1,"817":1,"818":2,"819":1,"820":1,"823":1,"848":1,"849":2,"896":1,"907":1,"935":1,"963":1,"1011":1,"1012":1,"1041":1,"1042":3,"1044":1,"1046":1,"1075":1,"1076":1,"1099":1,"1100":1,"1144":1,"1212":2,"1233":2,"1237":4,"1247":1,"1255":1,"1256":6,"1258":1,"1259":36,"1270":1,"1271":3,"1275":3,"1286":16,"1300":6,"1301":2,"1309":2,"1311":7,"1317":1,"1318":2,"1323":15,"1324":10,"1330":1,"1339":2,"1355":1,"1359":7,"1391":2,"1392":1,"1395":9,"1396":9,"1402":1,"1415":3,"1417":10,"1436":11,"1438":5,"1443":1,"1456":1,"1465":1,"1467":4,"1469":1,"1476":3,"1482":1,"1489":1,"1496":5,"1524":1,"1526":5,"1531":2,"1547":3,"1552":1,"1572":1,"1577":2,"1589":1,"1591":4,"1596":3,"1599":1,"1615":1,"1617":1,"1661":1,"1665":4,"1677":1,"1841":1,"1843":1,"1845":1,"1910":1,"1922":1,"1923":2,"1944":1,"1950":1,"1984":1,"2006":1,"2007":1,"2032":2,"2121":1,"2154":1,"2156":4,"2158":1,"2159":1,"2165":2,"2167":1,"2169":1,"2182":4,"2183":1,"2187":1,"2190":1,"2191":3,"2193":4}}],["de",{"3":{"219":1,"649":1,"651":1,"1059":1,"1140":1,"1270":1,"1271":2,"1286":2,"1300":3,"1311":3,"1323":2,"1324":4,"1339":2,"1359":5,"1369":1,"1395":4,"1415":2,"1433":1,"1436":3,"1496":1,"1526":1,"1686":4,"1765":1,"1830":1,"2150":1}}],["detour",{"3":{"1323":2}}],["determinable",{"0":{"1451":1,"1470":1,"1484":1,"1500":1},"3":{"530":1,"599":1,"629":1,"665":1,"1190":1,"1237":1,"1247":1,"1259":2,"1286":8,"1300":2,"1311":5,"1312":1,"1323":10,"1324":5,"1339":8,"1359":14,"1369":2,"1380":4,"1395":7,"1396":7,"1402":2,"1415":9,"1416":5,"1417":9,"1436":3,"1458":1,"1467":1,"1470":2,"1496":1}}],["determinism",{"3":{"360":1,"364":1,"1286":3,"1300":2,"1359":1,"1436":1,"1438":1,"1465":1,"2144":1}}],["deterministically",{"3":{"543":1,"549":1,"905":1,"954":1,"1256":1,"1259":3,"1263":1,"1270":1,"1300":1,"1301":1,"1306":1,"1312":1,"1323":1,"1324":1,"1364":1,"1383":1,"1395":1,"1396":2,"1417":2,"1427":1,"1436":3,"1438":1,"1454":1,"1488":1,"1592":1}}],["deterministic",{"3":{"0":1,"23":1,"157":1,"295":1,"359":2,"360":1,"361":2,"363":2,"545":1,"549":1,"551":1,"591":1,"592":2,"721":1,"742":1,"905":1,"907":1,"1019":2,"1163":1,"1227":1,"1229":2,"1284":2,"1286":10,"1290":1,"1300":6,"1309":1,"1311":3,"1323":1,"1324":2,"1350":2,"1355":1,"1359":6,"1369":1,"1380":1,"1392":2,"1395":6,"1396":3,"1402":2,"1415":1,"1417":1,"1427":2,"1428":1,"1432":1,"1435":3,"1436":13,"1438":3,"1455":4,"1465":1,"1479":2,"1488":1,"1492":3,"1496":1,"1526":1,"1551":1,"1577":2,"1590":1,"1591":1,"1675":1,"1805":1,"1982":1,"2052":1,"2060":1,"2142":2,"2144":1,"2145":1,"2146":1,"2170":1}}],["determine",{"3":{"1395":1,"1415":1,"1454":1,"1631":1,"1776":1}}],["determined",{"3":{"283":1,"990":1,"1286":1,"1324":1,"1492":1,"1495":1,"1631":1,"1641":1,"2001":1}}],["determines",{"3":{"20":1,"162":1,"389":1,"1286":2,"1395":1,"1630":4,"1639":1,"2002":1,"2051":1}}],["detectability",{"3":{"1981":1}}],["detectable",{"3":{"498":1,"906":1,"907":1,"1270":1,"1286":1,"1300":1,"1359":1,"1366":1,"1369":2,"1436":1,"1500":1}}],["detectchanges",{"3":{"1273":1,"1286":2,"1585":1}}],["detectchangesscope",{"3":{"1199":2,"1203":1,"1207":1,"1273":1,"1286":3,"1496":1}}],["detectchangesonce",{"3":{"715":1,"1273":1,"1286":3}}],["detect",{"2":{"443":1},"3":{"443":1,"1116":1,"1259":1,"1270":2,"1273":1,"1286":7,"1300":1,"1324":1,"1339":1,"1350":1,"1359":5,"1396":1,"1417":1,"1436":1,"1467":1,"1877":1}}],["detector",{"3":{"305":1,"339":1,"344":1,"1020":1,"1283":1,"1286":3,"1309":1,"1323":1,"1359":1,"1368":2,"1396":5,"1402":1,"1424":1,"1436":21,"1438":1,"1467":2,"1526":1,"1531":1,"1532":1,"1577":1,"1707":1,"1985":2}}],["detects",{"3":{"291":1,"948":1,"974":1,"1237":1,"1274":1,"1286":3,"1300":2,"1324":2,"1332":1,"1339":4,"1352":1,"1359":1,"1380":1,"1395":1,"1396":1,"1417":1,"1430":2,"1431":1,"1436":5,"1457":2,"1661":1,"1728":1,"1729":1,"1876":1,"1903":1}}],["detected",{"3":{"0":1,"259":1,"382":1,"741":1,"792":1,"1067":1,"1168":1,"1286":2,"1311":2,"1324":1,"1339":3,"1350":1,"1366":1,"1417":1,"1436":2,"1443":1,"1455":1,"1571":1,"1729":1}}],["detecting",{"3":{"0":1,"593":2,"733":1,"1068":1,"1284":1,"1359":1,"1436":1,"1591":1,"1729":1}}],["detectiontestdbcontext",{"3":{"1199":2,"1207":1}}],["detection",{"1":{"496":1,"497":1,"498":1,"499":1,"500":1,"501":1,"502":1,"503":1,"1980":1,"1981":1,"1982":1,"1983":1,"1984":1,"1985":1},"3":{"0":3,"403":1,"496":1,"497":1,"498":1,"499":1,"500":2,"501":2,"506":1,"510":1,"608":1,"609":1,"612":1,"614":1,"692":1,"715":2,"733":2,"759":1,"762":1,"801":1,"804":1,"903":1,"904":1,"905":3,"907":3,"910":1,"1058":1,"1212":1,"1237":1,"1247":1,"1259":2,"1270":2,"1273":1,"1283":1,"1286":15,"1287":1,"1290":2,"1300":17,"1323":4,"1324":6,"1338":1,"1339":3,"1359":1,"1396":1,"1406":1,"1415":3,"1417":13,"1433":1,"1436":9,"1437":1,"1438":3,"1455":1,"1461":1,"1467":3,"1469":1,"1476":1,"1486":1,"1496":2,"1526":1,"1531":1,"1532":1,"1585":1,"1639":1,"1641":3,"1660":1,"1712":1,"1722":1,"1729":1,"1765":1,"1779":1,"1979":1,"1980":1,"1981":1,"1982":1,"1983":1,"1984":1,"1985":1,"2146":1,"2194":1,"2208":1,"2220":1,"2232":1}}],["detachobserverasync",{"3":{"1324":1}}],["detaching",{"3":{"1324":1,"1359":1,"1395":1,"1436":1}}],["detached",{"3":{"235":1,"237":1,"898":1,"1251":1,"1259":1,"1286":4,"1339":2,"1350":1,"1359":1,"1454":1}}],["detaches",{"3":{"0":1,"201":2,"1259":1,"1274":1,"1286":1,"1324":1,"1359":3}}],["detach",{"3":{"24":1,"1286":1,"1324":3,"1359":3}}],["detailpage",{"3":{"1496":1}}],["detailpagebasetests",{"3":{"1199":1,"1207":3,"1324":1}}],["detailpagebase",{"3":{"1192":1,"1199":2,"1203":1,"1207":2,"1324":4,"1384":3,"1395":22,"1496":4}}],["detailhref",{"3":{"1324":1}}],["detailed",{"3":{"1312":2,"1313":1,"1324":1,"1340":1,"1441":1,"1630":1,"1641":1,"1663":1,"1764":1}}],["detail",{"0":{"1384":1,"1822":1,"1872":1,"1909":1},"2":{"1926":1},"3":{"0":1,"1":1,"62":1,"72":1,"109":4,"261":1,"342":1,"556":2,"557":1,"559":1,"699":1,"725":1,"748":1,"806":1,"861":2,"905":1,"988":1,"1016":1,"1018":2,"1026":1,"1082":1,"1093":1,"1116":1,"1190":1,"1229":2,"1237":3,"1246":1,"1247":9,"1252":1,"1259":4,"1270":5,"1271":1,"1275":1,"1286":15,"1290":1,"1300":10,"1301":1,"1309":5,"1311":27,"1312":9,"1314":1,"1323":6,"1324":30,"1333":1,"1339":7,"1345":1,"1350":8,"1359":21,"1363":1,"1366":1,"1369":3,"1380":7,"1384":4,"1385":3,"1386":1,"1387":2,"1388":5,"1389":2,"1391":1,"1392":2,"1395":89,"1396":18,"1399":1,"1402":6,"1410":2,"1415":14,"1416":1,"1417":7,"1436":18,"1438":2,"1443":1,"1453":1,"1455":1,"1467":1,"1476":2,"1479":1,"1481":1,"1487":1,"1496":3,"1524":1,"1526":2,"1531":1,"1535":2,"1591":2,"1600":2,"1627":12,"1631":1,"1632":2,"1635":2,"1636":2,"1639":2,"1640":2,"1641":1,"1686":2,"1700":1,"1713":1,"1723":1,"1748":1,"1750":7,"1758":2,"1759":1,"1760":2,"1768":2,"1773":2,"1806":1,"1830":1,"1851":1,"1873":1,"1905":1,"1909":1,"1926":1,"1939":1,"1988":1,"2031":1,"2042":1,"2043":1,"2045":1,"2055":1,"2058":1,"2094":1,"2126":1,"2136":1,"2150":1,"2174":1,"2193":1}}],["detailsurl",{"3":{"1395":4}}],["details",{"1":{"1925":1,"1926":1,"1927":1,"1928":1,"1929":1,"1930":1},"3":{"0":2,"175":1,"341":2,"343":1,"409":1,"453":1,"497":1,"572":4,"574":4,"578":2,"657":1,"741":1,"819":1,"1018":1,"1019":1,"1091":1,"1212":1,"1221":1,"1224":1,"1226":1,"1227":1,"1229":6,"1247":1,"1251":1,"1259":2,"1263":1,"1270":1,"1271":1,"1276":1,"1286":5,"1300":3,"1301":1,"1311":16,"1312":1,"1323":1,"1324":6,"1328":1,"1339":2,"1340":1,"1347":1,"1349":1,"1350":2,"1355":1,"1359":9,"1369":1,"1380":2,"1383":1,"1395":12,"1396":8,"1402":8,"1406":1,"1415":2,"1421":1,"1423":1,"1427":1,"1435":2,"1436":4,"1438":2,"1442":1,"1443":1,"1449":1,"1454":1,"1455":2,"1459":1,"1487":1,"1488":1,"1489":2,"1492":1,"1526":2,"1546":1,"1551":1,"1591":2,"1630":3,"1631":3,"1640":1,"1651":1,"1653":1,"1685":1,"1751":1,"1765":1,"1768":4,"1770":2,"1772":1,"1773":1,"1779":1,"1839":1,"1841":1,"1859":1,"1873":1,"1908":1,"1919":1,"1924":1,"1925":1,"1926":1,"1927":3,"1930":1,"1988":1,"2019":1,"2055":1,"2067":1,"2072":1,"2074":1,"2075":1,"2131":1,"2175":1,"2187":1,"2197":1,"2208":1}}],["deeply",{"3":{"1395":1}}],["deeplinkroute",{"3":{"1396":2,"1417":5}}],["deeplinkrouteshapetests",{"3":{"1199":1,"1207":2,"1417":1}}],["deeplinkrouteeventargs",{"3":{"1199":4,"1203":1,"1207":2,"1417":12}}],["deeplinkrouteguard",{"2":{"418":1,"428":1},"3":{"418":1,"428":2}}],["deeplinkdispatchertests",{"3":{"1199":1,"1207":2,"1417":1}}],["deeplinkdispatcher",{"2":{"418":1,"427":1,"428":1,"430":1,"1416":1,"2118":1,"2121":1},"3":{"0":1,"418":1,"427":2,"428":2,"430":2,"1199":7,"1203":1,"1207":2,"1416":8,"1417":17,"1496":2,"2118":1,"2121":2}}],["deeplinklistenertests",{"3":{"1199":1,"1207":2,"1417":1}}],["deeplinklistener",{"2":{"2121":1},"3":{"0":1,"413":1,"1416":5,"1417":10,"1599":2,"2121":1}}],["deepens",{"3":{"2114":1}}],["deepen",{"3":{"1436":1}}],["deepest",{"3":{"1247":1,"1359":1,"2135":1}}],["deeper",{"3":{"237":1,"1247":1,"1311":1,"1334":1,"1359":3,"1381":1,"1402":1,"1416":1,"1427":1,"1436":4,"1475":1,"1572":1,"1577":1,"1821":1,"1864":1,"1960":1,"2034":1}}],["deep",{"0":{"2121":1,"2215":1,"2235":1,"2237":1},"1":{"417":1,"418":1,"419":1,"420":1,"421":1,"422":1,"423":1,"424":1,"425":1,"426":1,"427":1,"428":1,"429":1,"430":1},"3":{"0":3,"1":1,"121":1,"122":1,"206":1,"208":1,"353":1,"399":1,"413":1,"417":1,"418":1,"424":1,"549":1,"616":1,"618":1,"649":1,"690":1,"854":1,"872":1,"883":1,"1184":1,"1185":1,"1187":1,"1192":2,"1194":1,"1202":1,"1203":1,"1212":1,"1228":1,"1229":4,"1247":1,"1286":3,"1294":1,"1298":1,"1300":2,"1309":1,"1311":2,"1323":3,"1324":19,"1350":4,"1359":1,"1391":1,"1395":3,"1396":14,"1402":1,"1416":14,"1417":22,"1436":4,"1445":1,"1492":1,"1496":3,"1526":1,"1555":2,"1562":1,"1577":2,"1585":1,"1591":1,"1599":2,"1626":1,"1627":1,"1641":2,"1645":1,"1664":1,"1666":1,"1669":3,"1759":1,"1760":1,"1761":1,"1765":1,"1779":45,"1783":1,"1786":1,"1790":1,"1821":2,"1826":1,"1828":1,"1914":1,"1920":1,"1935":1,"1966":1,"1975":1,"1976":1,"1978":1,"2030":2,"2041":1,"2059":2,"2060":2,"2076":1,"2080":1,"2121":2,"2159":1,"2160":1,"2204":1,"2218":1,"2226":1,"2232":2,"2236":1,"2245":1}}],["debe",{"3":{"1685":1,"1686":2}}],["debounce",{"3":{"1591":1}}],["debounceinterval=",{"3":{"1396":1}}],["debounces",{"3":{"1395":1}}],["debounced",{"3":{"881":1,"1324":2,"1385":1,"1395":2,"1526":1,"1560":1,"2072":1}}],["debuggable",{"3":{"1286":1,"1417":1}}],["debuggability",{"3":{"6":1,"1952":1}}],["debugger",{"3":{"1259":1,"1339":1,"1415":1}}],["debugging",{"3":{"399":1,"402":1,"407":1,"641":1,"1259":1,"1311":1,"1333":1,"1339":2,"1415":1,"1430":1,"1436":1,"1438":1,"1443":1,"1488":1,"1641":1,"1913":1,"2159":2}}],["debug",{"3":{"0":1,"401":1,"1109":1,"1256":1,"1259":4,"1263":1,"1270":6,"1271":1,"1286":2,"1311":4,"1323":4,"1324":2,"1333":1,"1339":1,"1396":7,"1402":2,"1416":9,"1436":1,"1444":1,"1481":1,"1575":1,"1577":3,"1582":1,"1585":1,"1648":3,"1655":1,"1677":2,"1894":1,"1917":1,"1952":1}}],["debt",{"3":{"0":2,"298":1,"302":2,"607":1,"674":1,"1011":1,"1012":1,"1014":1,"1300":4,"1350":1,"1396":1,"1436":6,"1496":1,"1522":1,"1526":1,"1529":1,"1534":1,"1571":1,"1591":1,"2138":2}}],["density",{"0":{"1715":1},"3":{"881":1,"1324":13,"1416":2,"1524":4,"1526":3,"1530":1,"1531":3,"1532":1,"1533":1,"1557":1,"1559":1,"1577":1,"1591":1,"1596":3,"1597":2,"1669":1,"1715":1,"2079":1}}],["dense=",{"3":{"1524":1,"1526":2,"1531":1,"1532":1,"1591":1,"1596":1,"1597":1,"1715":1}}],["densegrid",{"2":{"1524":1,"1531":1,"1532":1,"1595":1,"1597":1,"1715":1},"3":{"1324":4,"1524":2,"1526":2,"1531":1,"1532":1,"1577":3,"1595":1,"1596":1,"1597":1,"1715":2}}],["densest",{"3":{"1286":1,"1324":1}}],["denser",{"3":{"691":1,"1324":1}}],["dense",{"2":{"1524":1},"3":{"650":1,"1237":1,"1256":1,"1324":4,"1380":2,"1396":1,"1524":1,"1559":1}}],["denormalize",{"3":{"1396":1,"1769":1}}],["denormalized",{"3":{"1030":1,"1117":1,"1342":1,"1346":1,"1350":2,"1359":2,"1380":1,"1395":5,"1398":1,"1402":3,"1415":2,"1630":2,"1750":1,"1751":2,"1769":2,"1773":1}}],["denormalization",{"3":{"1395":1,"1396":1,"1635":1,"1686":1}}],["denormalizationbackfillservice",{"3":{"674":1}}],["denormalizing",{"3":{"1350":1,"1395":1,"1402":1,"1638":1}}],["denominators",{"3":{"1012":4,"1527":3,"1537":1,"1553":1,"1578":3,"1591":1,"1592":4,"1594":1,"1599":1,"2171":1}}],["denominator",{"3":{"0":1,"1014":1,"1324":1,"1436":1,"1536":1}}],["denies",{"3":{"126":2,"188":1,"318":2,"1059":1,"1264":2,"1270":5,"1297":1,"1300":6,"1339":1,"1359":3,"1417":3,"1436":1,"1667":2,"1821":1,"1964":1,"1994":2,"1997":1,"2197":2}}],["denied",{"2":{"121":1,"126":1,"1268":1,"1422":1,"2156":1},"3":{"0":3,"121":1,"126":1,"317":1,"318":2,"440":1,"682":2,"729":1,"960":1,"1091":1,"1212":1,"1263":1,"1268":1,"1270":7,"1271":5,"1275":1,"1283":1,"1297":2,"1300":4,"1316":1,"1323":1,"1359":1,"1380":1,"1395":1,"1396":6,"1417":12,"1422":2,"1427":1,"1436":2,"1507":1,"1535":1,"1570":1,"1611":1,"1662":1,"1721":1,"1821":3,"1994":2,"2096":1,"2126":1,"2156":1}}],["denials",{"3":{"121":1,"126":3,"1263":1,"1270":1,"1997":1}}],["denial",{"3":{"0":1,"121":1,"126":2,"318":1,"793":1,"952":1,"954":1,"956":1,"960":1,"1263":1,"1270":7,"1300":4,"1323":1,"1359":1,"1396":1,"1402":1,"1417":10,"1436":2,"1577":1,"1821":1,"1995":1,"2001":1,"2096":1,"2117":1}}],["denying",{"3":{"1268":1,"1270":3,"1299":1,"1300":3,"1323":1,"1339":1,"1599":1,"2156":1}}],["denylist",{"3":{"0":1,"972":3,"973":2,"974":1,"975":1,"1212":1,"1324":2}}],["deny",{"0":{"2199":1},"2":{"2147":1},"3":{"0":3,"126":1,"184":1,"214":1,"318":3,"322":1,"324":1,"326":1,"458":1,"954":1,"956":1,"960":1,"1059":2,"1060":2,"1263":1,"1270":1,"1271":1,"1297":1,"1300":7,"1311":1,"1323":1,"1336":1,"1339":1,"1344":1,"1348":1,"1357":1,"1359":2,"1379":1,"1380":1,"1409":1,"1415":1,"1432":1,"1436":4,"1443":1,"1467":1,"1526":1,"1631":1,"1994":1,"1995":1,"1996":1,"2147":1,"2149":1,"2199":1,"2203":1,"2239":1}}],["demos",{"3":{"2109":1}}],["demonstrably",{"3":{"1765":1}}],["demonstrable",{"3":{"1286":1}}],["demonstrating",{"3":{"2221":1}}],["demonstration",{"3":{"1270":1,"1271":1,"1339":1,"1352":1,"1359":1}}],["demonstrator",{"3":{"1534":1}}],["demonstrates",{"3":{"551":1,"620":1,"698":1,"1058":1,"1300":1,"1311":1,"1324":1,"1359":4,"1376":1,"1436":3,"1487":1,"1721":1,"2057":1,"2226":1}}],["demonstrated",{"3":{"0":2,"941":1,"966":1,"1090":1,"1402":1,"1577":1,"1591":2,"1706":1,"1707":1,"2109":1}}],["demonstrate",{"3":{"0":1,"62":1,"449":1,"651":1,"1339":1,"1488":1,"1575":1,"1576":1,"1884":1,"2113":2,"2129":1,"2133":1,"2214":1}}],["demote",{"3":{"1300":1,"1311":1,"1339":1}}],["demoted",{"3":{"619":1,"743":1,"1289":1,"1324":2,"2057":1}}],["demotion",{"3":{"1270":1}}],["demoting",{"3":{"1270":1,"1414":1}}],["demo",{"3":{"698":1,"1437":1,"1698":1,"1777":1,"1789":1,"1796":1,"1958":1,"1966":1,"1973":1}}],["demanded",{"0":{"1871":1},"3":{"1237":1,"1300":1,"1415":1}}],["demands",{"3":{"147":2,"150":1,"265":1,"626":1,"674":2,"709":1,"1229":1,"1259":1,"1300":3,"1301":1,"1311":1,"1322":1,"1324":2,"1327":1,"1350":1,"1359":2,"1415":2,"1416":1,"1426":1,"1436":5,"1454":1,"1455":2,"1459":1,"1526":1,"1675":1,"1794":1,"2012":2,"2035":1}}],["demand",{"0":{"1327":1},"3":{"0":2,"86":1,"126":1,"186":1,"471":1,"499":1,"507":1,"591":1,"1060":1,"1125":1,"1229":1,"1237":1,"1247":2,"1263":1,"1270":1,"1286":3,"1297":3,"1300":5,"1311":3,"1318":1,"1323":1,"1324":6,"1327":1,"1342":1,"1350":1,"1359":1,"1395":2,"1396":1,"1415":1,"1436":4,"1438":1,"1440":1,"1455":2,"1459":2,"1460":1,"1475":1,"1482":1,"1492":1,"1577":1,"1589":1,"1802":1,"1821":1,"1877":1,"1962":1,"1965":2,"2013":1,"2034":1,"2047":1,"2141":1,"2162":1,"2180":2,"2199":1,"2232":1}}],["deflated",{"3":{"1492":1}}],["deflate",{"3":{"1455":1}}],["deflating",{"3":{"1455":1}}],["defining",{"3":{"1300":8,"1309":2,"1324":1,"1339":2,"1359":2,"1396":1,"1415":1,"1436":18,"1577":1,"1639":1,"1916":1,"1949":1,"2028":1,"2159":1}}],["definingprojectname",{"3":{"1067":1}}],["definitive",{"3":{"1359":1}}],["definitions",{"3":{"53":1,"54":1,"471":1,"881":1,"1266":1,"1281":1,"1286":2,"1301":1,"1312":1,"1324":1,"1350":1,"1359":6,"1380":1,"1396":2,"1436":4,"1447":1,"1494":1,"1632":1,"1858":1,"1962":1,"1994":1}}],["definition",{"0":{"1464":1},"2":{"1730":1},"3":{"33":1,"428":1,"619":1,"767":1,"810":1,"1102":1,"1117":1,"1160":1,"1229":1,"1231":1,"1237":1,"1239":1,"1247":1,"1259":1,"1270":3,"1271":1,"1286":3,"1300":5,"1309":1,"1311":2,"1312":1,"1324":10,"1339":4,"1347":1,"1350":8,"1359":27,"1369":4,"1380":1,"1396":4,"1415":5,"1417":1,"1433":1,"1436":15,"1467":2,"1586":1,"1631":1,"1637":1,"1638":1,"1641":1,"1701":1,"1730":1,"1802":1,"1962":2,"1980":1}}],["definite",{"3":{"1350":2,"1427":1}}],["definitely",{"3":{"1270":1,"1350":2,"1888":1}}],["definelayers",{"2":{"2103":1},"3":{"1431":1,"1436":24,"2103":1}}],["define",{"0":{"2017":1,"2090":1},"3":{"342":1,"449":1,"988":1,"1229":3,"1237":1,"1247":1,"1286":1,"1300":1,"1311":2,"1323":1,"1324":1,"1333":1,"1350":2,"1359":1,"1369":1,"1436":2,"1443":1,"1576":1,"1638":1,"1639":2,"1656":1,"1709":1,"1827":1,"1955":1,"1997":1,"2013":1,"2015":1,"2049":1,"2123":1,"2156":1}}],["defined",{"0":{"2042":1},"3":{"0":2,"11":1,"54":1,"215":1,"272":1,"331":1,"350":1,"352":1,"395":2,"404":1,"426":1,"428":1,"608":1,"749":1,"790":1,"850":1,"860":1,"861":2,"862":1,"881":1,"991":1,"1124":1,"1237":1,"1240":1,"1257":1,"1259":2,"1271":2,"1276":1,"1286":5,"1289":1,"1300":5,"1301":2,"1309":4,"1311":5,"1312":1,"1320":1,"1323":1,"1324":5,"1336":1,"1339":5,"1350":1,"1352":1,"1359":23,"1395":7,"1396":2,"1402":3,"1409":1,"1415":3,"1417":3,"1436":2,"1457":1,"1458":2,"1479":1,"1480":1,"1488":1,"1526":2,"1542":1,"1547":1,"1556":1,"1559":1,"1566":3,"1567":1,"1572":1,"1577":1,"1582":1,"1591":2,"1631":3,"1634":1,"1706":1,"1714":1,"1764":1,"1790":1,"1915":1,"1929":2,"2016":1,"2055":1,"2073":1,"2111":1,"2147":1,"2153":1,"2182":1,"2220":1}}],["definescope",{"3":{"1270":2}}],["defines",{"3":{"0":1,"125":1,"175":1,"184":3,"186":5,"256":1,"342":1,"350":1,"406":1,"407":1,"452":1,"536":1,"558":1,"564":1,"571":1,"614":1,"707":1,"870":1,"897":1,"1016":1,"1018":1,"1212":1,"1234":1,"1236":1,"1237":2,"1286":2,"1300":5,"1301":1,"1303":1,"1309":3,"1312":1,"1323":1,"1324":5,"1359":2,"1369":1,"1380":1,"1395":2,"1396":2,"1409":1,"1415":5,"1416":1,"1417":1,"1436":6,"1443":1,"1474":1,"1489":1,"1536":1,"1540":1,"1577":1,"1638":1,"1639":2,"1641":1,"1810":1,"1932":1,"1963":4,"1978":1,"2039":1,"2042":1,"2075":1,"2083":1,"2117":1,"2156":1,"2165":1,"2230":1}}],["defeated",{"3":{"716":1,"1359":1,"1436":1}}],["defeating",{"0":{"1334":1},"3":{"157":1,"591":1,"593":1,"1270":1,"1415":1,"1492":1}}],["defeat",{"3":{"151":1,"528":1,"741":1,"941":1,"1093":1,"1300":1,"1311":2,"1312":1,"1323":1,"1324":1,"1417":1,"1436":1,"1441":1,"1667":1,"1975":1,"2019":1}}],["defeats",{"3":{"100":1,"235":1,"360":1,"998":1,"1300":1,"1311":1,"1312":1,"1339":1,"1423":1,"1427":1,"1431":1,"1436":1,"1576":1,"1909":1,"2030":1,"2128":1,"2175":1,"2179":1}}],["defer",{"3":{"459":1,"614":1,"810":1,"1286":1,"1323":1,"1339":2,"1402":1,"1416":1,"1434":1,"1436":1,"1498":1,"2193":1}}],["defersessionpersist",{"3":{"1324":1}}],["defers",{"3":{"20":1,"609":1,"675":1,"815":1,"821":1,"918":1,"1116":1,"1271":2,"1286":1,"1300":2,"1311":2,"1339":1,"1356":1,"1359":1,"1395":1,"1408":1,"1415":6,"1416":1,"1417":1,"1422":1,"1436":1,"1476":1,"1582":1,"1722":1,"1950":1,"1959":1,"2012":1}}],["deferring",{"3":{"735":1,"801":1,"989":1,"1286":1,"1311":2,"1323":1,"1324":3,"1339":1,"1415":1,"1416":1,"1427":1}}],["deferrals",{"3":{"1286":1,"1571":1,"1594":1}}],["deferral",{"3":{"0":7,"468":1,"469":1,"473":1,"474":2,"518":1,"521":1,"522":1,"726":1,"797":3,"798":2,"799":2,"800":1,"808":2,"810":1,"811":1,"812":2,"813":1,"827":1,"914":1,"917":1,"1048":1,"1051":1,"1053":1,"1054":1,"1100":1,"1104":1,"1212":1,"1324":2,"1396":2,"1577":1,"2168":2,"2232":1}}],["deferredtable",{"3":{"1286":2}}],["deferreddispatch",{"2":{"1233":1,"1498":1},"3":{"1198":1,"1199":2,"1203":1,"1207":1,"1233":1,"1274":1,"1286":5,"1496":2,"1498":2}}],["deferred",{"0":{"1275":1,"1318":1,"2184":1},"1":{"796":1,"797":1,"798":1,"799":1,"800":1,"801":1,"802":1,"803":1,"804":1,"805":1,"806":1,"807":1,"808":1,"809":1,"810":1,"811":1,"812":1,"813":1,"814":1,"815":1},"3":{"0":16,"20":1,"26":1,"68":1,"100":1,"111":1,"120":2,"125":1,"383":1,"468":2,"470":1,"474":1,"518":1,"541":1,"601":1,"680":2,"692":1,"698":1,"725":1,"727":1,"729":1,"733":1,"741":1,"796":1,"799":1,"804":1,"807":1,"827":1,"881":1,"914":1,"988":5,"992":1,"1042":2,"1044":1,"1046":1,"1048":1,"1049":1,"1051":1,"1117":3,"1192":1,"1212":3,"1217":1,"1229":1,"1233":1,"1237":1,"1252":1,"1263":2,"1264":2,"1270":5,"1272":1,"1275":3,"1278":2,"1286":20,"1297":1,"1300":2,"1311":1,"1313":1,"1318":3,"1323":7,"1324":2,"1339":1,"1350":1,"1357":1,"1359":7,"1395":1,"1396":3,"1399":1,"1402":2,"1415":1,"1416":3,"1417":2,"1433":1,"1436":2,"1467":1,"1508":1,"1524":2,"1526":3,"1529":1,"1530":1,"1532":1,"1534":2,"1577":3,"1578":1,"1582":2,"1585":7,"1599":3,"1611":3,"1612":1,"1631":1,"1640":2,"1651":1,"1688":1,"1822":1,"1840":2,"1944":1,"2015":1,"2184":2,"2186":1,"2193":4,"2232":2}}],["defensiveness",{"3":{"1396":1}}],["defensive",{"3":{"359":1,"365":1,"1247":1,"1286":6,"1297":1,"1300":3,"1301":1,"1311":6,"1324":3,"1339":1,"1350":2,"1359":5,"1415":1,"1417":1,"1443":1,"1881":1,"2126":1,"2143":1}}],["defensively",{"3":{"359":1,"1286":1,"1324":1,"1395":2,"1396":1,"1416":2,"1427":1,"1436":1,"1911":1,"1925":1,"1986":1}}],["defensible",{"3":{"0":1,"691":1,"699":1,"821":1,"862":1,"898":1,"1220":1,"1396":1,"1436":1,"1526":1,"1531":1,"1912":1,"1972":1}}],["defenses",{"3":{"1300":1,"1324":1,"1436":1,"1553":1,"1576":1,"1825":1}}],["defense",{"0":{"1903":1},"3":{"207":1,"370":1,"390":1,"973":1,"1185":1,"1247":4,"1263":1,"1270":2,"1285":1,"1286":1,"1300":5,"1309":1,"1311":4,"1324":6,"1336":1,"1369":1,"1396":1,"1402":1,"1415":3,"1427":1,"1436":1,"1548":1,"1577":1,"1627":1,"1667":1,"1673":1,"1757":1,"1823":1,"1969":1,"2040":1,"2073":1,"2126":1,"2201":1}}],["defences",{"3":{"280":1,"1339":1}}],["defence",{"2":{"1365":1},"3":{"0":1,"128":1,"443":1,"725":1,"757":1,"758":1,"1019":1,"1020":1,"1115":1,"1116":1,"1118":1,"1124":2,"1300":1,"1311":1,"1324":1,"1339":3,"1365":1,"1369":1,"1395":1,"1415":2,"1455":1,"1467":2,"1475":1,"1492":1,"2001":1,"2148":1}}],["defending",{"1":{"1998":1,"1999":1,"2000":1,"2001":1,"2002":1},"3":{"1229":1,"1339":1,"1415":1,"1779":1,"1997":1,"1998":1,"2208":2}}],["defend",{"3":{"1125":1,"1286":1,"1309":1,"1396":1,"1436":1,"1438":3,"1900":1}}],["defended",{"3":{"659":1,"868":1,"1224":1,"1300":1,"1395":1,"1402":1,"1415":1,"1436":6,"1526":1,"1826":1}}],["defenderforstoragesettings",{"2":{"1470":1},"3":{"1116":1,"1119":1,"1467":1,"1470":1}}],["defender",{"3":{"0":1,"403":1,"1116":2,"1118":1,"1467":1}}],["defends",{"3":{"0":1,"1124":1,"1249":1,"1339":1,"1359":1,"1436":3,"1567":1,"2141":1}}],["defect",{"3":{"0":1,"46":1,"135":1,"249":1,"390":2,"415":1,"474":1,"549":2,"619":1,"799":1,"800":2,"801":2,"803":1,"856":1,"890":1,"955":1,"966":1,"1004":1,"1049":1,"1050":1,"1247":1,"1270":2,"1286":3,"1300":1,"1301":1,"1311":2,"1324":6,"1339":1,"1350":1,"1356":1,"1359":8,"1395":3,"1396":1,"1402":1,"1417":1,"1436":25,"1438":2,"1467":1,"1473":1,"1535":1,"1586":1,"1591":1,"1597":1,"1599":3,"1610":1,"1643":2,"1671":1,"1675":1,"1744":1,"1799":1,"1841":1,"1909":1,"2063":1,"2183":1}}],["defects",{"3":{"0":1,"157":1,"517":1,"1300":2,"1311":1,"1324":1,"1402":1,"1909":1,"2051":2}}],["defaultkeyid",{"3":{"1436":1,"1488":1}}],["defaultkeyversion",{"3":{"1286":1}}],["defaultinterval",{"3":{"1436":1}}],["defaulting",{"3":{"0":5,"178":1,"226":1,"229":1,"243":1,"255":1,"274":1,"333":1,"351":1,"421":1,"539":1,"549":2,"640":1,"819":1,"820":1,"922":1,"973":1,"1116":1,"1184":1,"1229":1,"1237":1,"1241":1,"1243":1,"1247":4,"1249":1,"1257":1,"1259":4,"1267":1,"1270":2,"1271":3,"1273":1,"1277":2,"1279":1,"1286":14,"1300":11,"1301":3,"1309":5,"1311":5,"1317":1,"1323":14,"1324":17,"1326":1,"1332":1,"1336":1,"1338":1,"1339":6,"1350":10,"1359":10,"1366":1,"1376":1,"1380":2,"1387":1,"1392":1,"1395":8,"1396":9,"1402":1,"1412":1,"1415":2,"1419":3,"1423":1,"1424":1,"1427":1,"1433":1,"1436":19,"1441":1,"1455":1,"1465":1,"1467":3,"1470":1,"1474":1,"1476":1,"1484":1,"1488":1,"1684":1,"1695":1,"1922":1,"1970":1,"2173":2,"2232":1}}],["defaultissuer",{"3":{"1436":1,"1488":1}}],["defaultleadminutes",{"3":{"1396":2}}],["defaultlocalizationinterceptor",{"3":{"265":1,"1324":2}}],["defaultversionpolicy",{"3":{"1339":1}}],["defaultrouteheadername",{"3":{"1339":1}}],["defaultredactionplaceholder",{"3":{"1427":1}}],["defaultrequestversion",{"3":{"1339":1}}],["defaultrequestheaders",{"3":{"1324":4}}],["defaultretryafter",{"3":{"1324":1}}],["defaultretrydelay",{"3":{"1324":1}}],["defaultresolutionorder",{"3":{"1286":1}}],["defaultculture",{"3":{"1436":1}}],["defaultclusterheadername",{"3":{"1339":1}}],["defaultcleartextport",{"3":{"95":2,"1339":2}}],["defaultconnectionname",{"3":{"1339":3}}],["defaultbaseaddress",{"3":{"1436":3}}],["defaultbaseurl",{"3":{"1436":1,"1488":1}}],["defaultbackoff",{"3":{"1324":1,"1396":1}}],["defaultbrokerresourcename",{"2":{"1326":1},"3":{"1326":1,"1339":1,"1441":1}}],["defaultborderradius",{"3":{"1324":1}}],["defaultjwkspath",{"3":{"1311":2}}],["defaulthttpcontext",{"3":{"1311":1}}],["defaultheaders",{"3":{"1311":3}}],["defaultmaxoutputtokens",{"3":{"1427":1}}],["defaultmaxexportrows",{"3":{"741":1,"1311":1,"1323":1}}],["defaultmaildevresourcename",{"3":{"1339":1}}],["defaultmessagetimetolive",{"3":{"1323":1,"1436":1}}],["defaultmessage",{"3":{"1286":2}}],["defaultpassword",{"3":{"1436":3}}],["defaultprivatekeypem",{"3":{"1429":1,"1436":1,"1488":1}}],["defaultprobepath",{"2":{"99":1},"3":{"99":1,"1339":3,"1441":1}}],["defaultprotocols",{"3":{"95":1,"1331":1,"1339":4}}],["defaultpublickeypem",{"3":{"1429":1,"1436":1,"1488":1}}],["defaultport",{"3":{"1339":2}}],["defaultpopulatelocktimeout",{"3":{"1270":1,"1323":3}}],["defaultwidth",{"3":{"1270":1,"1300":2}}],["defaultname",{"3":{"1259":2,"1286":8,"1323":1}}],["defaultdocumentname",{"3":{"1311":1}}],["defaultdatasourceresolver",{"3":{"1199":12,"1207":1}}],["defaultduration",{"2":{"243":1,"254":1,"257":1,"736":1,"1708":1,"1916":2},"3":{"243":2,"254":3,"257":2,"733":1,"736":1,"1301":20,"1496":1,"1708":2,"1916":2}}],["defaultoidcdiscoverypath",{"3":{"1311":1}}],["defaultoauthuisettings",{"3":{"1199":2,"1203":1,"1207":2,"1324":7,"1416":1}}],["defaultoptions",{"2":{"1091":1},"3":{"1091":1,"1427":1}}],["default=warning",{"3":{"790":1,"1442":1}}],["defaulttitlefor",{"3":{"1395":1}}],["defaulttimeout",{"3":{"1324":3,"1427":1,"1436":4,"1488":1}}],["defaulttooauthauthentication",{"3":{"1467":1}}],["defaulttoautonegotiation",{"3":{"1339":2}}],["defaulttokenissuingpaths",{"3":{"1324":1}}],["defaultthreadcurrentuiculture",{"3":{"1324":1,"1417":1}}],["defaultthreadcurrentculture",{"3":{"1324":1,"1417":1}}],["defaultthreadcurrent",{"2":{"265":1},"3":{"265":2,"1324":1}}],["defaultttl",{"3":{"243":1,"1324":4}}],["defaultemail",{"3":{"1436":3}}],["defaultemulatorimage",{"3":{"640":1,"1436":1}}],["defaultendpointname",{"3":{"1339":2}}],["defaultenginedbcontextfactories",{"3":{"1496":1}}],["defaultengine",{"3":{"1286":2}}],["defaultentityconfigurationassemblyprovider",{"2":{"1203":1,"1282":1,"1323":1},"3":{"1199":3,"1203":1,"1207":2,"1282":3,"1286":11,"1323":1,"1436":1}}],["defaultentityconfigurationassemblyprovidertests",{"2":{"1199":1},"3":{"1199":1,"1207":2,"1286":1}}],["defaultexcludedpathprefixes",{"3":{"1286":1}}],["defaultexporttestcontroller",{"3":{"1199":2,"1207":1}}],["defaultexpiration",{"2":{"243":1,"254":1,"257":1},"3":{"243":1,"254":3,"257":1,"1301":4,"1311":3}}],["defaultexemptpathprefixes",{"2":{"189":1},"3":{"189":1,"1300":3}}],["defaulted",{"3":{"0":1,"583":1,"649":1,"650":1,"768":1,"827":1,"837":1,"861":1,"862":1,"1018":1,"1034":1,"1212":1,"1247":1,"1253":1,"1258":1,"1259":7,"1265":2,"1270":4,"1284":1,"1286":7,"1300":1,"1301":1,"1307":1,"1309":4,"1311":3,"1323":1,"1324":9,"1339":1,"1344":1,"1349":1,"1350":7,"1359":12,"1395":5,"1396":19,"1402":2,"1415":3,"1417":2,"1436":6,"1918":1,"1945":1,"2196":1,"2203":1}}],["defaultaction",{"3":{"1467":1}}],["defaultauthorizationservice",{"2":{"954":1,"956":1,"960":1,"1432":1},"3":{"954":1,"956":1,"960":1,"1432":1,"1436":5}}],["defaultauthorizationpolicyprovider",{"2":{"186":1,"1962":1},"3":{"186":1,"1300":2,"1962":1}}],["defaultapiversion",{"2":{"446":1},"3":{"446":1,"448":1,"1311":1,"2131":1}}],["defaultazurecredential",{"2":{"441":1,"598":1,"599":1,"664":1,"665":1,"670":1,"1335":1,"1336":1,"1442":1,"1970":1,"2126":1,"2127":1},"3":{"0":2,"440":1,"441":1,"598":1,"599":1,"664":1,"665":1,"670":1,"1212":1,"1323":4,"1335":1,"1336":1,"1339":6,"1442":1,"1467":6,"1577":1,"1970":1,"2126":1,"2127":1}}],["defaultstothederivedtypesassembly",{"3":{"1436":1}}],["defaultsettings",{"3":{"1577":1}}],["defaultservicebusemulatorresourcename",{"3":{"1339":1}}],["defaultseed",{"3":{"1199":2,"1203":1,"1207":1,"1279":1,"1286":3}}],["defaultsmtpport",{"3":{"1323":2}}],["defaultsortcolumn",{"3":{"1380":1}}],["defaultsort",{"3":{"1247":1}}],["defaultsspecification",{"3":{"1199":2,"1207":1}}],["defaultsqlserverdbcontextfactory",{"2":{"703":1},"3":{"703":1}}],["defaults",{"0":{"1442":1,"1443":1,"1678":1,"2119":1},"1":{"1325":1,"1326":1,"1327":1,"1328":1,"1329":1,"1330":1,"1331":1,"1332":1,"1333":1,"1334":1,"1335":1,"1336":1,"1337":1,"1338":1,"1339":1},"2":{"150":1},"3":{"0":15,"19":2,"22":1,"23":1,"25":1,"27":1,"31":3,"66":1,"68":1,"72":1,"73":1,"74":2,"95":3,"98":2,"100":3,"102":1,"145":1,"146":1,"147":1,"150":1,"168":1,"177":1,"189":1,"193":1,"198":1,"202":1,"217":1,"219":1,"225":2,"228":1,"230":1,"235":1,"243":2,"246":1,"255":1,"258":1,"263":1,"265":4,"314":1,"318":2,"325":1,"333":1,"335":1,"350":1,"384":1,"395":4,"397":3,"400":1,"402":1,"404":1,"406":2,"413":2,"414":1,"416":1,"434":2,"482":1,"536":1,"538":1,"539":1,"583":3,"585":2,"599":1,"600":1,"609":2,"622":1,"635":1,"640":2,"657":1,"668":1,"674":3,"690":1,"699":1,"720":1,"733":2,"736":1,"749":1,"809":1,"819":3,"821":1,"827":5,"832":2,"833":1,"837":1,"861":5,"862":1,"863":1,"897":1,"905":2,"913":2,"931":1,"954":1,"956":1,"957":1,"994":1,"998":1,"1004":1,"1018":3,"1026":1,"1042":1,"1045":1,"1050":1,"1059":1,"1067":1,"1082":1,"1089":1,"1091":1,"1092":1,"1093":1,"1094":1,"1119":1,"1124":3,"1135":1,"1160":1,"1175":1,"1192":1,"1202":1,"1203":1,"1212":3,"1237":1,"1241":2,"1244":3,"1247":7,"1249":3,"1257":1,"1258":1,"1259":11,"1270":4,"1271":1,"1275":1,"1276":2,"1277":1,"1284":1,"1285":1,"1286":24,"1288":1,"1293":1,"1297":2,"1300":38,"1301":11,"1302":1,"1309":5,"1311":26,"1312":3,"1320":2,"1323":22,"1324":42,"1325":2,"1326":1,"1330":2,"1333":1,"1335":1,"1337":1,"1338":3,"1339":82,"1350":8,"1355":1,"1359":28,"1364":1,"1368":1,"1369":4,"1372":1,"1380":2,"1384":1,"1393":1,"1395":17,"1396":13,"1402":2,"1415":10,"1416":5,"1417":24,"1420":1,"1427":6,"1429":1,"1433":1,"1435":1,"1436":50,"1437":1,"1438":10,"1439":2,"1441":1,"1442":1,"1443":6,"1444":1,"1446":1,"1447":5,"1455":2,"1458":1,"1466":1,"1467":6,"1470":3,"1475":1,"1477":1,"1481":3,"1487":1,"1488":2,"1490":1,"1491":1,"1496":5,"1537":1,"1555":1,"1557":1,"1561":1,"1577":3,"1582":1,"1591":1,"1596":1,"1631":2,"1640":1,"1656":1,"1660":4,"1662":1,"1663":3,"1670":3,"1674":1,"1676":1,"1707":1,"1734":1,"1798":1,"1801":1,"1825":1,"1842":1,"1843":1,"1895":1,"1908":1,"1916":2,"1921":1,"1923":1,"1936":1,"1970":1,"1989":1,"1994":2,"1995":1,"2000":1,"2001":2,"2011":1,"2014":3,"2018":1,"2030":2,"2031":1,"2038":1,"2043":1,"2046":1,"2062":1,"2075":1,"2083":1,"2119":1,"2122":3,"2123":2,"2131":1,"2149":1,"2155":1,"2156":1,"2159":1,"2160":1,"2167":1,"2168":1,"2188":1,"2198":2,"2199":1,"2227":1}}],["default",{"0":{"1327":1,"2064":1,"2118":1},"1":{"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1,"1047":1,"1048":1,"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1,"1080":1,"1081":1,"1082":1,"1083":1,"1084":1,"1085":1,"1086":1,"1087":1,"2154":1,"2155":1,"2156":1,"2157":1,"2158":1,"2159":1,"2160":1},"2":{"511":1,"513":1,"916":1,"1041":1,"1261":1,"1386":1,"1423":1,"1467":1,"1652":1,"2175":1},"3":{"0":76,"5":1,"7":2,"15":2,"17":3,"19":3,"20":1,"24":1,"25":1,"26":1,"27":1,"29":1,"31":4,"32":2,"38":1,"39":3,"41":1,"47":2,"55":1,"62":1,"67":1,"68":1,"71":1,"72":2,"73":2,"78":4,"79":2,"91":2,"92":4,"93":3,"94":1,"95":1,"96":5,"97":1,"98":7,"99":2,"100":1,"101":2,"102":2,"109":1,"121":1,"135":1,"147":1,"150":1,"152":1,"157":1,"158":1,"159":1,"165":1,"166":6,"170":2,"173":1,"175":7,"177":1,"178":5,"179":3,"184":1,"185":1,"186":2,"189":2,"193":1,"195":3,"200":4,"201":3,"203":1,"212":1,"214":6,"215":3,"216":4,"219":5,"220":2,"225":3,"226":1,"227":2,"228":2,"229":1,"230":2,"231":1,"233":1,"235":2,"236":1,"241":2,"243":5,"244":1,"245":2,"248":1,"249":1,"252":1,"254":2,"255":3,"258":1,"265":4,"273":2,"279":2,"280":3,"281":2,"283":1,"290":1,"302":1,"310":1,"318":8,"320":1,"322":1,"324":1,"326":1,"329":1,"330":1,"332":1,"335":2,"340":1,"344":1,"350":7,"351":1,"352":1,"353":1,"360":1,"369":1,"380":2,"381":1,"384":1,"386":2,"387":4,"388":3,"389":1,"395":2,"397":3,"398":1,"402":2,"407":1,"413":1,"414":1,"415":1,"420":2,"422":1,"432":1,"434":2,"436":2,"440":2,"443":1,"444":1,"446":1,"447":1,"448":3,"449":2,"458":1,"468":1,"499":4,"501":3,"502":1,"507":1,"511":1,"513":1,"520":1,"527":1,"549":1,"556":2,"572":1,"573":1,"578":1,"583":3,"584":1,"599":4,"602":1,"609":3,"626":2,"633":2,"635":1,"640":2,"642":1,"644":1,"649":1,"650":1,"656":1,"657":2,"660":1,"661":1,"664":1,"665":6,"666":1,"667":4,"668":6,"674":3,"682":4,"690":1,"691":1,"698":5,"699":2,"700":1,"707":6,"708":1,"709":4,"711":1,"713":1,"715":1,"731":2,"733":7,"734":1,"736":1,"737":1,"740":1,"741":4,"743":4,"749":2,"774":3,"775":2,"781":1,"782":1,"784":1,"790":1,"791":1,"793":1,"798":1,"804":1,"819":2,"820":2,"821":1,"826":2,"827":8,"829":1,"830":2,"831":1,"832":1,"837":1,"838":2,"841":1,"846":2,"854":2,"857":2,"861":2,"868":1,"881":3,"890":1,"897":2,"900":1,"905":8,"907":1,"912":1,"913":1,"914":1,"915":2,"916":1,"922":1,"924":1,"926":2,"932":1,"933":1,"935":2,"946":1,"953":1,"954":5,"956":2,"960":1,"961":1,"962":1,"964":4,"965":3,"966":1,"968":1,"971":1,"972":3,"974":1,"988":3,"991":1,"998":1,"999":2,"1004":1,"1006":1,"1012":2,"1018":3,"1020":1,"1033":1,"1034":1,"1035":1,"1036":1,"1040":1,"1041":1,"1044":1,"1047":1,"1049":3,"1050":5,"1052":1,"1053":1,"1054":1,"1055":1,"1059":2,"1061":1,"1067":1,"1069":1,"1080":1,"1081":1,"1082":2,"1083":2,"1084":1,"1089":3,"1090":1,"1091":5,"1092":1,"1094":1,"1108":1,"1109":1,"1114":1,"1116":4,"1117":1,"1118":1,"1119":2,"1122":1,"1124":7,"1127":1,"1133":2,"1134":1,"1135":4,"1161":1,"1168":1,"1175":5,"1177":1,"1179":1,"1184":2,"1188":1,"1212":9,"1223":1,"1229":7,"1231":2,"1232":1,"1235":1,"1236":2,"1237":24,"1241":2,"1242":1,"1244":2,"1247":21,"1249":1,"1253":1,"1254":2,"1255":1,"1256":4,"1257":1,"1258":2,"1259":56,"1261":1,"1263":1,"1264":1,"1267":2,"1269":2,"1270":50,"1271":10,"1273":2,"1274":1,"1275":1,"1276":3,"1277":1,"1279":9,"1280":1,"1281":3,"1285":1,"1286":210,"1287":1,"1288":7,"1289":2,"1290":4,"1292":3,"1294":3,"1295":1,"1296":1,"1297":4,"1300":118,"1301":83,"1302":4,"1304":2,"1305":1,"1307":3,"1308":1,"1309":64,"1310":2,"1311":105,"1312":2,"1313":1,"1314":1,"1317":1,"1318":2,"1320":15,"1321":1,"1322":1,"1323":102,"1324":153,"1326":4,"1328":3,"1331":3,"1333":3,"1334":2,"1335":2,"1336":4,"1338":5,"1339":89,"1342":1,"1343":2,"1344":2,"1349":3,"1350":37,"1353":1,"1354":1,"1355":1,"1359":149,"1365":1,"1369":18,"1372":1,"1373":1,"1378":2,"1379":5,"1380":18,"1383":2,"1386":1,"1389":2,"1392":3,"1393":1,"1395":89,"1396":54,"1398":1,"1399":1,"1400":2,"1402":40,"1405":2,"1406":1,"1407":1,"1408":1,"1415":27,"1416":14,"1417":270,"1421":2,"1423":3,"1424":1,"1427":14,"1430":2,"1431":3,"1432":2,"1433":3,"1436":222,"1438":13,"1440":1,"1441":12,"1442":4,"1443":9,"1444":3,"1446":5,"1447":3,"1449":1,"1450":1,"1453":5,"1454":1,"1455":10,"1456":2,"1458":1,"1465":2,"1466":3,"1467":33,"1470":1,"1472":1,"1474":2,"1475":6,"1476":1,"1477":3,"1481":2,"1484":1,"1488":8,"1489":2,"1492":1,"1496":8,"1526":7,"1527":1,"1534":1,"1537":1,"1538":1,"1539":1,"1540":1,"1541":1,"1542":1,"1543":1,"1544":1,"1545":1,"1546":1,"1547":1,"1548":1,"1549":1,"1550":1,"1551":1,"1552":1,"1553":1,"1554":1,"1555":1,"1556":1,"1557":1,"1558":1,"1559":1,"1560":1,"1561":1,"1562":1,"1563":2,"1564":1,"1565":1,"1566":1,"1567":2,"1568":1,"1569":1,"1570":1,"1571":1,"1574":1,"1575":1,"1577":22,"1578":1,"1581":1,"1582":1,"1583":3,"1585":3,"1586":2,"1591":1,"1592":2,"1596":1,"1600":1,"1621":2,"1627":1,"1630":8,"1631":4,"1632":1,"1633":1,"1637":2,"1638":1,"1639":3,"1640":6,"1641":4,"1652":2,"1653":1,"1662":1,"1663":1,"1664":1,"1667":2,"1668":3,"1669":1,"1670":2,"1677":7,"1684":4,"1685":1,"1697":1,"1704":1,"1707":1,"1708":3,"1718":2,"1719":1,"1721":1,"1727":2,"1728":1,"1731":2,"1734":2,"1757":1,"1767":3,"1770":2,"1774":1,"1779":1,"1790":1,"1810":3,"1811":1,"1826":1,"1841":4,"1842":1,"1843":3,"1844":1,"1845":3,"1849":3,"1851":1,"1853":1,"1856":2,"1862":1,"1869":2,"1880":1,"1881":1,"1882":1,"1889":6,"1892":1,"1896":1,"1898":1,"1904":1,"1905":1,"1906":1,"1908":1,"1910":2,"1911":1,"1912":1,"1915":2,"1916":2,"1917":1,"1920":2,"1921":1,"1922":7,"1925":1,"1933":2,"1934":1,"1935":1,"1938":1,"1941":1,"1942":1,"1944":1,"1945":3,"1946":1,"1948":2,"1950":1,"1952":1,"1953":2,"1962":1,"1965":2,"1970":2,"1971":1,"1975":2,"1976":1,"1977":2,"1978":2,"1979":2,"1982":5,"1984":2,"1987":1,"1989":1,"1990":2,"1994":5,"1995":1,"1996":3,"1997":1,"2000":8,"2001":2,"2002":1,"2006":1,"2008":1,"2009":1,"2010":2,"2011":1,"2012":1,"2013":1,"2024":1,"2025":2,"2030":1,"2031":1,"2043":1,"2046":2,"2056":1,"2061":1,"2063":3,"2064":3,"2065":1,"2068":1,"2069":1,"2075":2,"2076":1,"2077":1,"2082":1,"2083":4,"2086":1,"2094":1,"2098":1,"2118":6,"2119":2,"2121":1,"2123":3,"2126":4,"2127":1,"2128":2,"2129":2,"2130":2,"2131":3,"2133":3,"2138":1,"2142":1,"2149":6,"2150":4,"2151":1,"2152":3,"2153":4,"2154":2,"2155":1,"2156":2,"2157":1,"2158":1,"2160":1,"2166":1,"2169":2,"2175":1,"2180":2,"2187":1,"2192":1,"2196":1,"2197":1,"2199":2,"2202":1,"2203":1,"2209":1,"2215":1,"2232":6}}],["deducted",{"3":{"1526":1,"1577":1}}],["deduction",{"3":{"1524":1,"1526":2,"1591":2,"1599":1,"1610":1,"1744":1}}],["deductions",{"3":{"1524":1,"1526":1}}],["dedupable",{"3":{"881":1}}],["dedupkeyindex",{"3":{"1286":1}}],["dedupkeymaxlength",{"3":{"1286":2,"1309":1}}],["dedupkey",{"2":{"231":1,"890":1,"892":1,"1934":1,"1941":1},"3":{"225":2,"231":1,"889":2,"890":1,"892":1,"1175":1,"1286":8,"1304":4,"1309":16,"1933":3,"1934":2,"1941":1}}],["dedupes",{"3":{"1237":1,"1324":1}}],["dedupe",{"3":{"220":1,"1237":1,"1396":1}}],["deduped",{"3":{"197":1,"789":1,"1396":2}}],["deduping",{"3":{"194":2,"1259":1,"1438":1}}],["deduplicate",{"3":{"538":1,"1311":2,"1396":1,"1721":1,"1933":1,"1941":1,"2168":1}}],["deduplicates",{"3":{"199":1,"517":1,"518":1,"645":1,"1286":1,"1300":2,"1301":1,"1311":1,"1324":1,"1339":1,"1376":1,"1415":1}}],["deduplicated",{"3":{"0":1,"109":1,"157":1,"160":1,"166":1,"225":1,"827":1,"1100":1,"1276":1,"1286":1,"1300":2,"1311":3,"1380":1,"1395":1,"1416":1,"1496":2,"1910":1}}],["deduplication",{"3":{"157":1,"158":1,"231":1,"846":1,"1018":1,"1247":2,"1249":2,"1259":7,"1269":1,"1270":3,"1303":1,"1309":5,"1311":6,"1317":1,"1324":3,"1339":1,"1350":1,"1380":1,"1436":1,"1443":1,"1496":1,"1889":1,"1909":1,"1933":1,"1940":1,"1941":1}}],["deduplicating",{"3":{"0":2,"109":1,"156":1,"1100":1,"1324":1,"1332":1,"1380":1}}],["dedup",{"0":{"1934":1},"3":{"0":8,"24":1,"194":1,"195":2,"196":2,"197":4,"200":1,"201":4,"202":2,"223":2,"516":1,"518":2,"519":1,"520":1,"524":2,"541":1,"694":1,"702":1,"786":1,"795":1,"815":1,"817":1,"823":1,"847":1,"889":2,"892":1,"930":1,"1074":1,"1099":1,"1104":1,"1212":1,"1237":1,"1247":1,"1249":1,"1258":2,"1259":14,"1286":3,"1304":4,"1309":12,"1323":6,"1324":1,"1350":1,"1359":1,"1380":1,"1396":1,"1438":1,"1496":1,"1508":1,"1526":1,"1577":2,"1586":1,"1665":1,"1910":2,"1911":1,"1933":4,"1934":1,"1940":1,"2166":1}}],["dedups",{"3":{"0":3,"195":2,"255":1,"340":2,"346":2,"538":1,"541":1,"846":1,"882":1,"1212":2,"1259":2,"1270":1,"1395":1,"1496":1,"1526":1,"1665":1,"1845":1,"1870":2,"1910":1,"1922":1,"2164":2,"2232":2}}],["dedicated",{"1":{"738":1,"739":1,"740":1,"741":1,"742":1,"743":1,"744":1,"745":1},"3":{"0":9,"51":1,"91":3,"92":2,"93":3,"94":3,"96":1,"98":1,"99":1,"100":1,"186":1,"195":1,"229":2,"343":1,"350":1,"395":1,"403":1,"407":1,"413":2,"536":1,"591":1,"593":1,"612":1,"618":1,"638":1,"640":2,"649":1,"663":1,"665":1,"666":1,"669":1,"698":1,"738":1,"741":1,"825":1,"846":1,"847":1,"939":1,"1069":1,"1212":4,"1215":1,"1241":1,"1247":2,"1258":1,"1270":1,"1271":2,"1275":1,"1286":8,"1307":1,"1309":6,"1311":3,"1312":1,"1323":3,"1324":4,"1328":1,"1331":1,"1339":8,"1350":3,"1359":6,"1376":1,"1379":1,"1380":4,"1395":5,"1396":4,"1400":1,"1402":2,"1415":5,"1417":4,"1427":1,"1431":1,"1436":3,"1438":2,"1441":1,"1442":1,"1446":1,"1449":1,"1467":4,"1486":1,"1487":1,"1492":1,"1503":1,"1509":1,"1526":3,"1534":2,"1577":1,"1585":2,"1599":1,"1639":1,"1641":2,"1660":1,"1661":1,"1666":1,"1670":1,"1732":1,"1750":1,"1773":1,"1842":1,"1851":1,"1929":2,"1930":2,"1946":1,"1963":1,"1975":1,"2025":2,"2047":1,"2083":1,"2102":1,"2120":1,"2156":1,"2167":1,"2232":2}}],["deprovisioned",{"3":{"1568":1}}],["deprecation",{"0":{"1737":1},"3":{"447":1,"448":1,"1006":1,"1017":1,"1019":1,"1020":1,"1311":1,"1376":1,"1435":1,"1438":1,"1453":1,"1455":1,"1492":1,"2129":1,"2130":1,"2131":1,"2133":2}}],["deprecate",{"3":{"447":1,"449":1,"940":1,"2130":1,"2133":1}}],["deprecatedversions",{"3":{"1311":1}}],["deprecated",{"3":{"0":4,"109":1,"448":5,"449":2,"528":1,"572":1,"881":1,"1212":1,"1300":1,"1311":11,"1376":3,"1380":4,"1436":5,"1459":2,"1488":2,"1526":2,"1535":1,"1591":1,"2055":1,"2131":5,"2133":3,"2232":1}}],["depersonalizes",{"3":{"1402":1}}],["dependable",{"3":{"1311":1}}],["dependabot",{"2":{"149":1,"152":1,"1078":1},"3":{"0":2,"145":1,"147":2,"148":1,"149":6,"150":1,"152":4,"373":2,"870":1,"873":1,"1074":5,"1075":1,"1078":3,"1459":7,"1576":2,"1577":2,"1586":2,"1673":1}}],["dependants",{"3":{"1237":1}}],["depended",{"3":{"1034":1,"1191":1,"1200":1,"1323":2,"1415":1,"1505":1,"1881":1}}],["dependence",{"3":{"1324":1,"1359":1,"1396":1,"1402":1,"2002":1}}],["dependencies",{"2":{"584":1,"585":1,"1314":1,"1880":1,"1881":1,"2094":1,"2098":1},"3":{"0":1,"59":1,"122":1,"145":1,"146":1,"168":1,"237":1,"265":1,"371":1,"376":1,"583":5,"584":1,"585":6,"608":1,"609":1,"665":1,"708":1,"733":1,"742":1,"939":2,"940":2,"990":1,"1191":2,"1196":2,"1199":1,"1211":1,"1214":1,"1229":1,"1237":1,"1247":1,"1259":2,"1270":1,"1271":2,"1286":5,"1300":4,"1301":1,"1308":1,"1309":2,"1310":2,"1311":5,"1312":1,"1314":2,"1315":2,"1323":11,"1324":2,"1325":1,"1339":2,"1359":10,"1360":1,"1371":1,"1380":4,"1393":1,"1395":1,"1396":20,"1402":3,"1406":1,"1415":17,"1417":4,"1431":1,"1432":1,"1436":36,"1445":1,"1454":2,"1467":1,"1475":1,"1489":2,"1496":2,"1504":1,"1538":1,"1540":1,"1551":2,"1552":2,"1569":3,"1570":1,"1577":1,"1591":1,"1597":1,"1600":1,"1638":1,"1653":1,"1671":2,"1693":1,"1709":1,"1775":1,"1878":1,"1879":1,"1880":2,"1881":3,"1886":2,"1948":1,"1964":1,"2041":1,"2087":1,"2094":4,"2095":1,"2098":2,"2158":1,"2164":1,"2230":1}}],["dependencyversion",{"3":{"1576":1,"1577":1}}],["dependencyversiontests",{"2":{"1214":1,"1454":1,"1801":1,"2042":1,"2048":1},"3":{"147":1,"1074":1,"1199":1,"1207":2,"1214":1,"1436":10,"1454":1,"1489":3,"1491":2,"1493":1,"1577":1,"1801":1,"2042":1,"2048":1}}],["dependencyversiontestsbase",{"2":{"147":1,"1075":1,"1671":1},"3":{"0":1,"147":2,"150":1,"1074":1,"1075":1,"1199":2,"1207":2,"1212":1,"1431":5,"1436":7,"1489":2,"1491":1,"1576":1,"1577":1,"1586":1,"1671":1}}],["dependencycycles",{"3":{"1436":1}}],["dependencymodule",{"3":{"1323":2}}],["dependencyinjectionoutboxgatetests",{"3":{"1199":1,"1207":2,"1438":2}}],["dependencyinjectionbrokermessagingtests",{"2":{"1496":1},"3":{"1199":1,"1207":4,"1496":1,"1577":1}}],["dependencyinjectionadditionaltests",{"3":{"1199":1,"1207":2}}],["dependencyinjectionasserttests",{"3":{"1199":1,"1207":4,"1436":1,"1438":1,"1487":1}}],["dependencyinjectionassert",{"2":{"1497":1},"3":{"1199":4,"1207":2,"1428":1,"1436":7,"1438":1,"1488":2,"1496":1,"1497":1}}],["dependencyinjectionpushnotificationstests",{"3":{"1199":1,"1207":2}}],["dependencyinjectioninfrastructuretests",{"3":{"1175":1,"1199":1,"1207":2,"1286":3}}],["dependencyinjectiontests",{"2":{"1262":1},"3":{"996":1,"1199":6,"1207":13,"1262":2,"1270":1,"1323":1,"1436":6,"1438":1,"1487":1}}],["dependencyinjection",{"2":{"115":4,"145":1,"152":1,"203":1,"220":1,"354":1,"378":1,"514":1,"543":2,"552":1,"644":1,"652":1,"677":1,"696":1,"703":1,"736":1,"794":1,"822":1,"884":1,"917":1,"929":1,"991":1,"999":1,"1062":1,"1074":1,"1078":2,"1214":1,"1247":1,"1262":2,"1270":1,"1312":1,"1365":1,"1368":1,"1369":1,"1414":1,"1427":1,"1500":2,"1685":1,"1771":1},"3":{"24":1,"25":2,"27":4,"53":1,"68":3,"74":2,"96":1,"100":1,"109":1,"111":1,"112":1,"115":4,"121":1,"122":9,"126":1,"145":2,"150":4,"152":2,"180":1,"186":5,"193":1,"196":1,"198":1,"200":1,"201":1,"203":3,"216":2,"219":1,"220":2,"225":2,"228":1,"230":1,"231":2,"243":5,"245":1,"250":1,"251":1,"252":1,"254":1,"256":1,"257":1,"258":2,"259":3,"260":3,"261":4,"285":1,"300":1,"303":1,"306":1,"310":2,"318":7,"324":2,"326":1,"343":1,"352":1,"354":1,"378":1,"384":1,"413":6,"415":1,"434":2,"436":2,"459":2,"463":2,"464":2,"465":2,"466":2,"507":8,"511":3,"513":2,"514":1,"536":2,"543":3,"545":1,"549":2,"552":2,"583":2,"585":1,"640":7,"642":2,"644":2,"649":4,"652":1,"656":1,"657":1,"660":2,"674":23,"675":1,"676":5,"677":2,"682":1,"696":1,"698":1,"703":1,"707":1,"710":1,"715":2,"720":3,"725":1,"733":3,"736":3,"740":1,"790":1,"792":1,"794":1,"798":1,"804":1,"806":1,"819":3,"821":1,"822":3,"827":1,"846":1,"850":2,"881":1,"884":1,"905":2,"915":1,"917":1,"922":3,"924":4,"926":4,"927":3,"929":1,"931":4,"934":2,"946":1,"949":1,"954":2,"955":1,"960":1,"964":2,"965":1,"967":1,"979":6,"980":29,"982":5,"983":4,"988":1,"991":1,"996":5,"999":1,"1018":8,"1021":2,"1042":1,"1050":2,"1055":1,"1059":6,"1062":1,"1074":1,"1075":1,"1078":2,"1090":1,"1091":10,"1092":1,"1093":2,"1094":1,"1095":1,"1100":2,"1103":2,"1108":1,"1111":1,"1116":1,"1133":1,"1136":1,"1175":1,"1176":1,"1177":1,"1184":3,"1199":30,"1203":29,"1207":79,"1208":40,"1212":1,"1214":1,"1229":1,"1232":2,"1237":9,"1247":6,"1257":5,"1258":1,"1259":38,"1262":28,"1263":12,"1265":7,"1269":3,"1270":90,"1271":9,"1286":105,"1290":2,"1294":1,"1297":6,"1300":90,"1301":56,"1302":3,"1304":3,"1306":1,"1307":4,"1308":3,"1309":73,"1310":8,"1311":70,"1312":26,"1313":1,"1316":43,"1317":11,"1318":5,"1319":2,"1320":14,"1322":1,"1323":162,"1324":142,"1339":14,"1350":9,"1352":1,"1354":8,"1359":234,"1365":2,"1366":2,"1367":2,"1368":7,"1369":10,"1377":4,"1378":3,"1380":26,"1395":60,"1396":136,"1400":1,"1402":13,"1404":3,"1406":1,"1410":1,"1413":2,"1414":3,"1415":97,"1417":253,"1419":1,"1420":2,"1421":4,"1425":3,"1427":41,"1436":13,"1438":5,"1441":6,"1442":2,"1496":26,"1500":2,"1526":7,"1531":1,"1576":1,"1577":11,"1582":1,"1583":1,"1585":1,"1586":2,"1591":3,"1600":1,"1651":1,"1685":1,"1686":1,"1707":2,"1771":1,"1994":2,"1995":1,"2000":2,"2085":1,"2118":2,"2119":2,"2122":3,"2126":5,"2136":2,"2138":1,"2150":1,"2163":1,"2166":1,"2167":1,"2172":2,"2173":1,"2175":1,"2179":3,"2198":1,"2199":2,"2202":1}}],["dependency",{"0":{"1402":1,"1435":1,"1504":1,"1505":1,"1569":1},"1":{"1196":1,"1197":1,"1198":1,"1199":1,"2170":1,"2171":1,"2172":1,"2173":1,"2174":1,"2175":1,"2176":1,"2177":1,"2178":1,"2179":1,"2180":1},"3":{"0":6,"23":2,"33":1,"68":1,"71":1,"72":1,"73":1,"111":1,"122":1,"130":1,"135":2,"145":4,"147":1,"220":1,"227":1,"230":3,"234":2,"235":9,"236":1,"237":2,"263":1,"265":1,"281":1,"310":1,"312":1,"343":1,"369":3,"370":2,"372":1,"376":2,"381":1,"397":1,"403":1,"405":1,"408":1,"415":1,"440":1,"441":1,"454":1,"459":2,"465":1,"509":1,"545":2,"546":1,"547":1,"583":7,"584":1,"585":1,"591":1,"599":1,"601":1,"603":1,"607":1,"609":7,"611":1,"612":1,"665":1,"666":1,"682":1,"684":2,"707":3,"708":1,"734":1,"741":2,"745":1,"801":1,"811":1,"818":1,"821":1,"827":2,"828":1,"830":1,"861":1,"914":1,"939":1,"941":1,"954":2,"980":1,"994":1,"996":4,"999":1,"1018":1,"1036":1,"1043":1,"1050":1,"1051":3,"1059":1,"1066":1,"1067":2,"1068":2,"1069":1,"1075":2,"1077":1,"1090":1,"1091":1,"1092":3,"1093":1,"1117":2,"1124":1,"1135":1,"1162":1,"1179":1,"1185":1,"1191":4,"1192":1,"1194":1,"1195":1,"1196":1,"1200":2,"1201":1,"1210":1,"1211":3,"1212":2,"1214":1,"1216":1,"1229":1,"1230":2,"1237":2,"1244":1,"1247":6,"1250":1,"1256":2,"1259":10,"1270":14,"1271":2,"1275":1,"1285":2,"1286":23,"1287":1,"1289":2,"1293":1,"1297":1,"1300":27,"1301":7,"1303":1,"1307":1,"1308":1,"1309":20,"1310":4,"1311":18,"1313":1,"1315":6,"1320":2,"1323":38,"1324":35,"1328":2,"1331":1,"1332":3,"1333":2,"1335":2,"1336":1,"1337":2,"1339":38,"1347":1,"1349":1,"1350":12,"1358":1,"1359":44,"1369":11,"1377":1,"1380":17,"1391":2,"1395":11,"1396":35,"1400":1,"1402":8,"1403":1,"1406":1,"1407":1,"1415":15,"1416":5,"1417":22,"1418":2,"1419":1,"1422":1,"1427":15,"1428":1,"1429":1,"1430":1,"1431":3,"1432":2,"1435":2,"1436":100,"1438":4,"1441":1,"1442":1,"1443":6,"1445":1,"1446":1,"1449":1,"1450":2,"1452":1,"1453":5,"1454":3,"1455":4,"1459":3,"1461":1,"1465":2,"1467":10,"1469":1,"1474":1,"1475":1,"1476":6,"1483":1,"1488":1,"1489":3,"1490":1,"1491":1,"1492":1,"1493":1,"1496":6,"1498":1,"1499":1,"1500":1,"1501":1,"1502":2,"1517":1,"1525":1,"1526":3,"1534":2,"1535":1,"1540":1,"1544":1,"1548":1,"1550":1,"1551":1,"1553":1,"1563":1,"1566":2,"1569":2,"1571":2,"1572":1,"1576":1,"1577":9,"1582":1,"1585":1,"1586":2,"1591":3,"1599":1,"1600":1,"1611":1,"1653":1,"1654":1,"1660":1,"1662":1,"1663":1,"1670":3,"1671":1,"1673":2,"1677":2,"1709":1,"1738":1,"1751":2,"1772":2,"1779":1,"1782":1,"1785":1,"1796":1,"1805":1,"1807":1,"1815":4,"1831":1,"1833":2,"1841":2,"1843":1,"1865":1,"1877":1,"1878":2,"1881":4,"1882":3,"1883":1,"1885":1,"1886":1,"1934":1,"1935":1,"1940":1,"1945":1,"1948":1,"1970":1,"1971":1,"2000":1,"2007":2,"2008":1,"2010":3,"2014":2,"2024":1,"2030":1,"2037":1,"2039":2,"2040":2,"2041":3,"2043":2,"2047":1,"2048":1,"2049":3,"2053":1,"2054":1,"2056":1,"2071":1,"2079":2,"2098":1,"2099":1,"2104":1,"2126":2,"2127":2,"2135":1,"2157":2,"2158":2,"2169":3,"2170":4,"2171":1,"2172":1,"2173":1,"2174":2,"2178":1,"2179":1,"2180":4,"2193":1,"2209":1,"2230":1,"2232":2}}],["dependentforeignkey",{"3":{"1247":1,"1359":2}}],["dependents",{"3":{"0":1,"543":2,"547":1,"549":1,"1286":2,"1315":2,"1323":1,"1339":1}}],["dependent",{"2":{"1239":1,"1859":1},"3":{"0":2,"386":1,"387":1,"388":1,"392":1,"461":1,"462":1,"487":1,"550":1,"551":1,"582":1,"583":2,"584":1,"585":1,"692":1,"1029":1,"1066":1,"1162":1,"1191":1,"1196":1,"1199":2,"1207":1,"1237":1,"1239":3,"1247":6,"1271":1,"1315":1,"1323":4,"1324":3,"1328":1,"1339":3,"1350":1,"1359":9,"1375":1,"1380":1,"1392":1,"1395":5,"1396":2,"1402":1,"1417":2,"1436":5,"1438":5,"1441":1,"1444":1,"1486":1,"1490":1,"1492":1,"1689":1,"1859":1,"1881":2,"1922":1,"2050":1,"2052":1,"2060":1,"2232":1}}],["depend",{"0":{"2071":1},"2":{"1504":1},"3":{"54":1,"69":1,"185":1,"226":1,"265":1,"310":1,"389":1,"415":1,"509":1,"536":1,"555":1,"585":1,"592":1,"651":1,"698":2,"716":1,"862":1,"987":1,"1060":1,"1187":1,"1211":1,"1214":1,"1222":1,"1229":1,"1247":4,"1259":2,"1269":2,"1270":7,"1275":1,"1278":1,"1286":9,"1300":9,"1301":1,"1309":3,"1311":5,"1312":1,"1316":1,"1317":1,"1323":5,"1324":8,"1330":1,"1339":4,"1350":5,"1359":3,"1369":2,"1377":1,"1395":7,"1396":6,"1415":3,"1416":1,"1417":7,"1427":1,"1431":1,"1433":1,"1436":22,"1438":4,"1441":1,"1443":1,"1453":1,"1489":4,"1492":1,"1504":1,"1538":2,"1585":1,"1638":1,"1660":1,"1781":1,"1832":1,"1841":1,"1861":1,"1880":2,"1881":1,"1882":1,"1894":1,"1908":1,"1946":1,"1961":1,"2039":1,"2042":1,"2043":1,"2047":1,"2069":1,"2104":2}}],["depending",{"3":{"0":1,"244":1,"397":1,"507":1,"513":1,"547":1,"1035":1,"1126":1,"1237":1,"1259":1,"1286":5,"1300":4,"1303":1,"1309":1,"1311":1,"1324":1,"1339":2,"1350":1,"1359":3,"1369":2,"1380":2,"1395":3,"1396":5,"1402":2,"1415":1,"1436":3,"1456":1,"1596":1,"1727":1,"1781":1,"1850":1,"1898":1,"2043":1,"2156":2}}],["dependson",{"3":{"1467":1}}],["dependsonconstanttimecomparison",{"3":{"1436":1}}],["dependsonaslowkeyderivationfunction",{"3":{"1436":1}}],["depends",{"3":{"0":3,"24":1,"54":1,"123":1,"131":1,"159":1,"166":1,"177":1,"236":1,"243":1,"259":1,"303":1,"320":1,"342":1,"349":1,"372":1,"399":1,"460":1,"461":1,"494":1,"507":3,"509":1,"511":2,"513":2,"543":1,"544":1,"549":1,"556":1,"566":1,"574":1,"584":1,"599":1,"642":1,"667":1,"676":1,"682":1,"707":1,"709":1,"720":1,"727":1,"742":1,"748":1,"749":1,"753":3,"756":1,"801":1,"809":2,"827":3,"829":1,"890":1,"905":1,"926":1,"946":2,"988":1,"1020":1,"1059":1,"1091":1,"1093":1,"1170":1,"1191":1,"1193":1,"1200":1,"1211":3,"1212":1,"1229":16,"1237":36,"1241":1,"1247":46,"1257":1,"1259":41,"1269":1,"1270":64,"1271":11,"1273":1,"1285":1,"1286":188,"1292":1,"1300":176,"1301":17,"1307":1,"1309":68,"1310":14,"1311":95,"1312":11,"1320":1,"1323":96,"1324":186,"1339":69,"1341":1,"1346":1,"1349":1,"1350":114,"1351":1,"1359":385,"1369":32,"1380":55,"1382":1,"1383":1,"1395":170,"1396":214,"1402":89,"1404":1,"1413":1,"1415":113,"1416":20,"1417":116,"1423":1,"1427":31,"1436":438,"1456":2,"1460":1,"1465":2,"1467":1,"1489":1,"1490":1,"1498":1,"1503":1,"1553":1,"1577":1,"1660":1,"1666":1,"1684":1,"1781":1,"1791":1,"1793":1,"1844":1,"1846":1,"1849":1,"1863":1,"1878":1,"1883":1,"1915":2,"1924":1,"1935":1,"2000":1,"2001":2,"2078":1,"2094":2,"2098":1,"2099":2,"2100":1,"2117":1,"2122":1,"2123":1,"2175":1,"2178":1,"2191":1,"2199":1,"2232":1}}],["deps",{"3":{"1192":1,"1199":1,"1456":2,"1496":9}}],["departure",{"3":{"725":1}}],["departs",{"3":{"698":1,"1300":1,"1416":1}}],["depthbound",{"3":{"1436":2}}],["depthorinsightquality",{"3":{"1369":1}}],["depthorinsightqualityscore",{"3":{"1350":1,"1359":1,"1369":1}}],["depth",{"0":{"1903":1},"2":{"24":1,"1365":1,"2191":1},"3":{"0":1,"24":1,"27":2,"119":1,"207":1,"328":1,"333":1,"562":1,"566":2,"567":1,"650":1,"707":2,"725":1,"883":1,"1116":1,"1124":1,"1185":1,"1228":2,"1229":1,"1241":1,"1242":1,"1247":4,"1256":1,"1259":11,"1263":1,"1270":2,"1275":1,"1286":6,"1300":2,"1311":5,"1324":4,"1336":1,"1339":3,"1350":1,"1359":1,"1365":1,"1366":1,"1369":1,"1395":1,"1396":1,"1402":1,"1415":4,"1417":1,"1436":17,"1453":2,"1454":1,"1455":2,"1467":2,"1475":1,"1476":1,"1480":1,"1548":1,"1590":1,"1627":1,"1639":2,"1640":1,"1658":1,"1665":1,"1692":1,"1757":1,"1821":1,"1823":1,"1826":1,"1827":1,"1969":1,"2006":1,"2122":1,"2126":1,"2148":1,"2156":3,"2157":2,"2159":1,"2191":2,"2193":2}}],["deploynotificationhub=false",{"3":{"1481":1}}],["deploynotificationhub",{"2":{"1470":1,"1481":1,"1484":1},"3":{"1467":4,"1470":1,"1481":1,"1484":1}}],["deployability",{"3":{"1323":1,"1544":1,"1577":1,"1589":1,"1596":1,"1597":1}}],["deployable",{"3":{"46":1,"597":1,"599":2,"603":1,"610":1,"838":1,"868":1,"1237":1,"1396":1,"1436":2,"1439":1,"1445":1,"1450":1,"1455":1,"1464":1,"1467":2,"1477":2,"1544":1,"1788":2,"1793":1,"2110":1}}],["deployables",{"3":{"0":1,"61":1,"62":1,"599":1,"1339":2,"1436":1,"1442":1,"1467":3,"1792":1,"2027":1}}],["deploying",{"3":{"626":1,"1044":1,"1427":1,"1455":1,"1467":1,"1577":1,"2192":1}}],["deployers",{"3":{"1577":1,"1582":1}}],["deployer",{"3":{"69":1,"1578":1,"1583":1,"1680":1,"1904":1}}],["deployed",{"2":{"1331":1},"3":{"0":10,"18":1,"26":1,"58":1,"62":1,"79":1,"91":1,"95":1,"96":1,"136":2,"155":1,"159":1,"195":1,"259":1,"361":1,"390":1,"395":1,"396":1,"397":2,"459":1,"509":1,"550":1,"564":1,"591":2,"598":1,"599":1,"607":2,"609":2,"611":5,"618":1,"620":1,"625":1,"626":2,"629":1,"665":1,"667":3,"676":1,"690":1,"713":1,"725":1,"732":1,"757":1,"758":1,"765":1,"766":1,"773":1,"776":1,"827":2,"860":1,"861":2,"863":1,"913":1,"914":1,"915":1,"918":1,"933":1,"971":1,"972":2,"995":1,"1017":1,"1018":2,"1021":1,"1040":1,"1058":1,"1060":1,"1066":1,"1082":1,"1085":1,"1107":1,"1108":1,"1109":1,"1118":2,"1119":1,"1124":1,"1135":1,"1143":1,"1147":1,"1254":2,"1259":5,"1270":10,"1286":6,"1293":1,"1300":2,"1301":6,"1309":2,"1311":3,"1313":1,"1323":3,"1324":2,"1331":1,"1333":1,"1335":1,"1339":14,"1350":3,"1359":3,"1368":1,"1369":4,"1396":3,"1401":1,"1402":3,"1415":2,"1427":1,"1434":1,"1435":1,"1436":13,"1437":1,"1438":2,"1441":2,"1442":2,"1443":2,"1445":2,"1455":1,"1465":4,"1467":4,"1474":1,"1477":2,"1478":2,"1481":1,"1484":1,"1486":1,"1489":1,"1492":2,"1496":2,"1500":2,"1526":4,"1531":1,"1577":1,"1596":2,"1641":1,"1671":1,"1672":1,"1673":1,"1677":1,"1679":1,"1718":1,"1723":1,"1785":2,"1841":1,"1848":1,"1855":1,"1861":1,"1886":1,"1887":2,"1888":2,"1892":3,"1909":1,"1916":1,"1923":2,"1924":1,"1970":1,"2010":1,"2043":2,"2044":2,"2047":2,"2056":1,"2057":1,"2110":1,"2143":1,"2154":1,"2156":1,"2194":3,"2198":1,"2214":1,"2221":2,"2228":1,"2229":1}}],["deploymentmode",{"2":{"1470":1},"3":{"1470":1}}],["deploymentparameters",{"2":{"1466":1},"3":{"1466":1}}],["deployment",{"0":{"1466":1,"1468":1,"1554":1},"1":{"1462":1,"1463":1,"1464":1,"1465":1,"1466":1,"1467":1,"1468":1,"1469":1,"1470":1},"2":{"1331":1,"1335":1,"1575":1,"1674":1},"3":{"0":3,"31":1,"32":1,"46":1,"47":1,"55":1,"158":1,"159":1,"168":2,"174":1,"177":2,"193":1,"196":1,"216":1,"227":1,"234":1,"242":1,"243":1,"244":1,"245":1,"255":1,"283":1,"312":1,"343":1,"360":1,"361":1,"386":1,"397":1,"398":1,"403":1,"526":1,"598":1,"599":6,"600":1,"601":2,"608":1,"609":2,"611":3,"612":1,"640":1,"641":2,"650":1,"664":1,"665":4,"666":1,"667":1,"675":1,"700":2,"756":1,"790":1,"792":1,"820":1,"821":1,"827":6,"837":1,"838":1,"839":1,"870":1,"871":1,"915":1,"930":1,"933":1,"948":1,"972":2,"1042":1,"1044":1,"1059":1,"1076":1,"1092":1,"1109":1,"1192":1,"1216":1,"1237":1,"1255":2,"1257":1,"1259":8,"1286":9,"1300":10,"1301":3,"1309":1,"1311":13,"1320":3,"1323":21,"1324":14,"1331":2,"1332":2,"1333":1,"1334":1,"1335":1,"1336":1,"1337":1,"1338":2,"1339":22,"1350":2,"1357":1,"1358":1,"1380":2,"1391":1,"1395":3,"1396":10,"1400":1,"1402":1,"1415":2,"1416":1,"1417":1,"1424":1,"1427":3,"1429":1,"1431":1,"1436":17,"1438":1,"1441":2,"1442":1,"1443":1,"1445":1,"1446":1,"1450":1,"1453":3,"1454":2,"1455":8,"1461":2,"1462":2,"1463":2,"1466":1,"1467":22,"1468":1,"1469":1,"1470":2,"1472":3,"1474":1,"1475":1,"1476":2,"1481":1,"1483":1,"1486":1,"1488":1,"1489":2,"1492":1,"1493":1,"1496":4,"1499":1,"1525":1,"1526":2,"1534":1,"1535":1,"1554":1,"1574":1,"1575":4,"1577":21,"1578":1,"1581":2,"1582":4,"1583":2,"1584":1,"1586":3,"1591":1,"1599":1,"1600":1,"1630":1,"1670":2,"1674":2,"1677":2,"1678":1,"1706":1,"1723":2,"1724":1,"1784":1,"1796":1,"1800":3,"1842":1,"1843":1,"1849":2,"1852":1,"1893":1,"1898":1,"1904":2,"1911":1,"1915":1,"1917":1,"1936":1,"1940":1,"1970":1,"1999":1,"2001":3,"2010":1,"2022":1,"2036":1,"2046":1,"2109":1,"2113":2,"2141":1,"2150":2,"2152":1,"2156":1,"2157":1,"2158":4,"2160":1,"2192":2,"2232":1}}],["deployments",{"3":{"0":1,"224":1,"246":1,"382":1,"395":1,"397":1,"409":1,"434":1,"584":1,"698":1,"811":1,"973":1,"990":1,"1152":1,"1154":1,"1259":1,"1300":1,"1301":1,"1311":1,"1323":2,"1443":1,"1453":1,"1454":1,"1475":1,"1554":1,"1898":1,"1923":1,"1949":1,"2109":2,"2114":1,"2115":1}}],["deploys",{"3":{"0":2,"67":1,"403":2,"564":2,"590":1,"627":1,"766":1,"768":1,"863":1,"865":1,"869":1,"916":1,"1091":1,"1212":1,"1436":1,"1445":1,"1455":4,"1465":2,"1467":2,"1470":1,"1472":1,"1475":1,"1525":1,"1526":3,"1531":1,"1534":2,"1535":1,"1554":1,"1588":1,"1596":1,"1889":1}}],["deploy",{"0":{"1455":1,"1466":1,"2036":1,"2189":1},"1":{"623":1,"624":1,"625":1,"626":1,"627":1,"628":1,"629":1,"630":1,"763":1,"764":1,"765":1,"766":1,"767":1,"768":1,"769":1,"770":1,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1},"2":{"63":1,"291":1,"296":1,"375":1,"452":1,"567":1,"589":1,"594":1,"597":1,"604":1,"616":1,"620":1,"621":1,"624":2,"625":1,"626":1,"628":1,"629":2,"642":1,"668":1,"755":1,"756":2,"761":2,"764":2,"766":1,"767":2,"768":3,"769":1,"770":1,"863":1,"864":1,"867":1,"877":1,"975":1,"1075":1,"1457":1,"1458":1,"1460":2,"1461":2,"1462":1,"1463":2,"1470":1,"1475":1,"1478":1,"1482":1,"1524":1,"1525":1,"1531":1,"1532":1,"1598":1},"3":{"0":35,"25":1,"58":2,"60":1,"62":5,"63":2,"145":2,"150":2,"228":1,"234":1,"246":1,"290":1,"291":14,"292":1,"293":3,"295":1,"296":1,"299":1,"300":1,"301":1,"310":1,"360":1,"365":1,"374":1,"375":4,"405":1,"451":1,"452":3,"453":1,"517":1,"558":1,"563":9,"564":18,"565":1,"566":7,"567":3,"573":2,"589":2,"591":13,"594":3,"595":1,"597":1,"599":8,"600":2,"601":3,"602":1,"604":1,"609":1,"616":4,"618":5,"619":1,"620":6,"621":1,"623":1,"624":10,"625":11,"626":56,"627":10,"628":11,"629":12,"630":2,"638":1,"639":1,"640":6,"641":1,"642":5,"644":1,"665":3,"668":3,"669":3,"698":1,"706":1,"708":1,"718":1,"732":1,"733":1,"755":2,"756":10,"757":36,"758":6,"759":8,"760":5,"761":5,"762":1,"763":1,"764":5,"765":2,"766":23,"767":5,"768":9,"769":2,"770":2,"774":1,"776":1,"827":2,"840":1,"858":1,"860":1,"861":6,"862":1,"863":6,"864":2,"865":2,"867":1,"868":3,"869":4,"870":7,"871":1,"872":1,"873":3,"874":1,"875":2,"876":1,"877":4,"907":1,"914":1,"915":2,"917":1,"953":1,"971":3,"972":4,"973":2,"974":2,"975":1,"976":1,"1017":1,"1018":6,"1019":1,"1020":2,"1058":1,"1075":3,"1101":1,"1116":1,"1119":1,"1192":1,"1212":10,"1215":1,"1247":1,"1286":1,"1300":1,"1301":2,"1309":3,"1311":2,"1312":2,"1318":1,"1323":2,"1324":1,"1328":1,"1339":2,"1357":1,"1359":4,"1366":1,"1380":1,"1396":6,"1409":1,"1414":1,"1415":2,"1436":19,"1438":6,"1445":20,"1446":1,"1449":2,"1450":2,"1452":2,"1453":1,"1454":1,"1455":249,"1456":14,"1457":13,"1458":2,"1459":6,"1460":12,"1461":19,"1462":2,"1463":16,"1464":4,"1465":21,"1466":15,"1467":52,"1468":3,"1469":3,"1470":8,"1472":10,"1474":15,"1475":29,"1476":9,"1477":7,"1478":3,"1481":1,"1482":7,"1484":3,"1485":1,"1486":31,"1489":1,"1490":11,"1491":2,"1492":70,"1493":1,"1494":1,"1496":16,"1509":1,"1523":1,"1524":9,"1525":7,"1526":75,"1530":1,"1531":14,"1532":1,"1533":3,"1534":12,"1535":14,"1544":3,"1545":1,"1547":1,"1554":3,"1570":2,"1575":3,"1577":6,"1581":1,"1582":3,"1583":1,"1584":1,"1588":2,"1589":10,"1590":4,"1591":80,"1595":2,"1596":24,"1597":3,"1598":2,"1599":10,"1600":16,"1611":6,"1631":1,"1670":2,"1724":1,"1739":2,"1775":1,"1884":1,"1887":1,"1888":1,"1889":2,"1916":1,"1947":1,"1948":1,"1964":1,"2003":1,"2034":3,"2036":3,"2037":2,"2043":1,"2057":2,"2105":1,"2110":1,"2112":1,"2134":1,"2142":1,"2146":1,"2147":1,"2154":1,"2181":1,"2190":1,"2194":1,"2199":1,"2232":11}}],["devops",{"0":{"1554":1},"2":{"1335":1},"3":{"1192":6,"1194":2,"1212":25,"1216":2,"1259":1,"1286":3,"1300":3,"1309":1,"1311":7,"1323":7,"1324":3,"1331":1,"1332":1,"1334":1,"1335":1,"1339":13,"1357":1,"1358":1,"1369":1,"1380":1,"1396":4,"1415":1,"1416":1,"1417":1,"1427":1,"1436":6,"1441":1,"1445":1,"1446":1,"1450":1,"1453":1,"1454":1,"1455":4,"1461":1,"1462":1,"1463":1,"1467":1,"1469":1,"1472":1,"1475":1,"1477":1,"1481":1,"1483":1,"1486":1,"1489":1,"1492":1,"1493":1,"1494":2,"1496":67,"1497":1,"1499":11,"1526":1,"1535":1,"1577":1,"1581":1,"1582":1,"1583":1,"1584":1,"1590":1,"1591":1,"1600":1,"1674":1,"1796":1,"1800":1,"2014":1,"2038":1,"2213":2,"2217":1,"2218":1,"2219":2,"2220":2}}],["develop",{"3":{"1570":1,"1648":1,"1661":1}}],["developing",{"3":{"1537":1,"1797":1,"2220":1}}],["developed",{"3":{"1499":22,"1572":1,"2220":2}}],["developercertificate",{"2":{"1328":1,"1449":1},"3":{"1328":1,"1339":1,"1430":1,"1449":1}}],["developercertificateavailabilitytests",{"3":{"1199":1,"1207":2,"1487":1}}],["developercertificateavailability",{"2":{"1067":1,"1428":1,"1430":1,"1488":1},"3":{"1067":3,"1199":3,"1207":2,"1428":1,"1430":2,"1488":2}}],["developers",{"1":{"1628":1,"1629":1,"1630":1,"1631":1,"1632":1,"1633":1,"1634":1,"1635":1,"1636":1,"1637":1,"1638":1,"1639":1,"1640":1,"1641":1},"3":{"86":1,"527":1,"566":1,"1211":1,"1311":1,"1340":1,"1364":3,"1369":4,"1395":2,"1416":2,"1455":1,"1523":1,"1570":1,"1628":1,"1629":1,"1637":1,"1638":1,"1785":1,"2004":1,"2050":1,"2095":1,"2110":1,"2214":1,"2216":1,"2221":1,"2239":2,"2240":3,"2242":1}}],["developer",{"0":{"1570":1},"2":{"1327":1},"3":{"0":1,"265":1,"350":1,"486":1,"576":1,"640":2,"641":2,"642":1,"650":1,"665":2,"819":1,"820":1,"914":2,"916":1,"930":1,"932":1,"982":1,"1012":1,"1067":1,"1069":1,"1091":1,"1109":1,"1161":1,"1216":1,"1237":1,"1259":1,"1270":1,"1271":1,"1286":3,"1311":4,"1323":8,"1324":4,"1326":1,"1327":1,"1328":1,"1335":2,"1336":1,"1339":12,"1354":1,"1359":1,"1396":5,"1415":1,"1416":2,"1417":4,"1427":1,"1436":23,"1440":2,"1441":2,"1442":1,"1446":1,"1449":1,"1450":1,"1453":1,"1455":1,"1456":1,"1461":1,"1464":5,"1481":1,"1482":2,"1486":1,"1488":1,"1489":1,"1490":1,"1492":1,"1493":1,"1499":1,"1526":1,"1535":1,"1553":1,"1554":1,"1577":1,"1581":1,"1582":1,"1583":1,"1591":1,"1597":1,"1796":1,"1843":1,"1885":1,"1894":1,"1956":1,"1970":1,"2030":1,"2106":1,"2220":2,"2225":1,"2238":1}}],["development",{"0":{"2225":1},"2":{"92":1,"95":1,"96":1,"102":1,"573":1,"1429":1,"1490":1},"3":{"0":4,"31":2,"92":1,"93":1,"95":3,"96":1,"102":1,"104":1,"212":1,"214":2,"218":1,"219":2,"265":4,"290":1,"296":1,"397":1,"401":1,"451":1,"452":1,"573":1,"638":1,"639":1,"640":1,"666":1,"667":1,"774":3,"775":1,"790":1,"914":1,"915":1,"1066":1,"1067":1,"1068":1,"1091":1,"1108":7,"1110":1,"1112":1,"1183":1,"1212":1,"1259":1,"1273":2,"1284":1,"1286":13,"1298":1,"1300":2,"1309":2,"1311":22,"1320":4,"1323":13,"1324":8,"1333":1,"1336":2,"1339":10,"1380":1,"1395":1,"1396":1,"1412":1,"1415":4,"1416":1,"1417":1,"1421":2,"1427":3,"1429":2,"1430":1,"1434":1,"1436":6,"1438":3,"1441":3,"1443":3,"1453":1,"1460":1,"1467":1,"1486":1,"1487":1,"1488":2,"1489":1,"1490":3,"1591":1,"1631":1,"1640":1,"1653":1,"1655":1,"1661":1,"1663":1,"1664":1,"1666":1,"1667":2,"1719":1,"1723":1,"1842":1,"1843":1,"1898":3,"1904":2,"1925":1,"2016":1,"2028":1,"2083":1,"2126":1,"2132":1,"2149":1,"2150":2,"2156":1,"2172":2,"2213":2,"2218":1,"2219":1,"2221":1,"2225":1,"2240":1,"2243":1}}],["deviates",{"3":{"1370":1}}],["deviate",{"3":{"1359":1}}],["deviations",{"0":{"1376":1},"3":{"470":1,"1169":1,"1311":1,"1396":1}}],["deviation",{"3":{"0":1,"107":1,"109":1,"111":2,"1237":1,"1300":1,"1309":2,"1311":3,"1324":3,"1350":1,"1417":1,"1436":1,"1638":1}}],["devicetestchecklist",{"3":{"1481":1}}],["devicetype",{"3":{"1300":1,"1415":2,"1630":1,"1641":1}}],["deviceownerauthentication",{"3":{"1417":1}}],["devicecredential",{"3":{"1417":1}}],["devicecapabilitiesinitializer",{"2":{"413":1},"3":{"413":1,"1199":2,"1203":1,"1207":2,"1417":22}}],["devicefieldmaxlength",{"2":{"1405":1},"3":{"1405":1,"1415":3}}],["deviceformfactor",{"3":{"1300":1,"1415":2,"1630":1,"1641":1}}],["devicename",{"3":{"1300":1,"1415":2,"1630":1,"1641":1}}],["devicemanufacturer",{"3":{"1300":1,"1415":3,"1630":1,"1641":1}}],["devicemodel",{"3":{"1300":1,"1415":3,"1630":1,"1641":1}}],["deviceprefs",{"3":{"1417":6}}],["devicepreferencekeys",{"3":{"1199":3,"1203":1,"1207":2,"1417":13}}],["deviceplatform",{"3":{"1300":1,"1415":2,"1417":4,"1630":1,"1641":1}}],["deviceid",{"3":{"1300":1,"1415":3,"1630":1,"1641":6}}],["deviceinstallationrequest",{"2":{"1303":1,"1308":1},"3":{"1199":6,"1203":1,"1207":2,"1286":2,"1303":1,"1308":2,"1309":9,"1323":3,"1496":2}}],["deviceinfo",{"2":{"682":1},"3":{"682":1,"1417":11,"1641":1}}],["deviceuimodule",{"2":{"1626":1},"3":{"649":2,"652":1,"1199":2,"1203":1,"1207":2,"1324":6,"1416":18,"1496":1,"1599":2,"1626":1,"1627":1}}],["devicesettings",{"3":{"1396":1,"1416":1}}],["devicestorage",{"2":{"1417":1},"3":{"416":1,"1203":9,"1207":18,"1417":34,"2118":1}}],["devicestatus",{"2":{"1417":2},"3":{"416":1,"1203":13,"1207":26,"1417":39,"2118":2}}],["devices",{"0":{"1713":1,"1936":1},"3":{"0":2,"186":1,"225":1,"274":1,"434":1,"616":1,"618":1,"903":1,"905":2,"906":2,"907":1,"910":1,"1212":1,"1286":1,"1300":5,"1302":1,"1309":4,"1311":2,"1323":3,"1324":15,"1395":1,"1415":2,"1416":1,"1417":10,"1436":3,"1438":1,"1467":1,"1559":1,"1641":1,"1666":1,"1747":1,"1932":1,"1933":1,"1936":2,"1938":1,"1982":2,"1984":1,"2081":1,"2220":1}}],["devicescontrollertests",{"3":{"1199":1,"1207":2,"1309":2}}],["devicescontroller",{"2":{"1302":1,"1936":1,"1938":1,"1941":1},"3":{"0":1,"188":1,"434":1,"1199":2,"1203":1,"1207":2,"1286":4,"1302":1,"1303":1,"1308":3,"1309":10,"1311":1,"1323":4,"1496":3,"1936":1,"1938":1,"1941":1,"2137":1}}],["device",{"0":{"1290":1,"1308":1,"1559":1,"1982":1},"1":{"410":1,"411":1,"412":1,"413":1,"414":1,"415":1,"416":1,"679":1,"680":1,"681":1,"682":1,"683":1,"684":1,"685":1,"686":1,"902":2,"903":2,"904":2,"905":2,"906":2,"907":2,"908":2,"909":2,"910":2,"1417":1,"1980":1,"1981":1,"1982":1,"1983":1,"1984":1,"1985":1,"2116":1,"2117":1,"2118":1,"2119":1,"2120":1,"2121":1,"2122":1,"2123":1},"3":{"0":12,"188":1,"265":4,"266":1,"267":1,"273":1,"350":1,"353":1,"410":1,"411":1,"412":1,"413":1,"422":1,"426":1,"427":1,"433":2,"434":1,"435":3,"497":2,"499":1,"500":1,"501":5,"510":1,"556":1,"618":2,"649":1,"650":1,"653":1,"679":1,"681":2,"683":1,"686":1,"689":1,"691":2,"736":1,"902":2,"903":3,"904":3,"905":12,"906":7,"907":3,"910":1,"954":1,"1192":3,"1195":1,"1202":2,"1203":2,"1212":5,"1216":1,"1283":2,"1285":1,"1286":9,"1287":2,"1289":2,"1290":5,"1291":1,"1296":1,"1300":55,"1302":1,"1308":2,"1309":8,"1311":15,"1322":1,"1323":12,"1324":59,"1350":2,"1359":2,"1381":2,"1391":3,"1395":16,"1396":19,"1402":1,"1405":2,"1409":1,"1412":1,"1415":20,"1416":25,"1417":131,"1427":1,"1436":11,"1438":8,"1479":3,"1481":2,"1482":1,"1489":1,"1490":1,"1491":1,"1496":11,"1499":1,"1505":1,"1511":1,"1517":1,"1577":3,"1585":2,"1599":1,"1626":2,"1627":1,"1629":1,"1630":5,"1631":2,"1632":1,"1641":29,"1659":1,"1660":1,"1667":3,"1668":1,"1669":6,"1672":1,"1711":1,"1779":3,"1796":1,"1936":3,"1938":1,"1940":1,"1976":1,"1977":1,"1979":1,"1980":2,"1982":6,"1984":2,"1985":6,"2073":1,"2082":1,"2083":1,"2086":1,"2115":1,"2116":2,"2117":2,"2118":1,"2120":1,"2123":3,"2137":1,"2208":1,"2210":2,"2232":4}}],["dev",{"0":{"1364":1},"1":{"637":1,"638":1,"639":1,"640":1,"641":1,"642":1,"643":1,"644":1,"645":1,"1105":1,"1106":1,"1107":1,"1108":1,"1109":1,"1110":1,"1111":1,"1112":1},"3":{"0":6,"92":1,"95":2,"96":1,"102":1,"104":1,"290":1,"349":1,"351":1,"450":2,"451":1,"556":1,"557":1,"637":1,"643":1,"818":1,"820":1,"838":1,"1067":1,"1069":1,"1105":1,"1116":1,"1212":2,"1257":1,"1286":1,"1300":1,"1311":2,"1323":2,"1339":2,"1364":2,"1369":3,"1416":3,"1421":1,"1430":1,"1436":2,"1441":3,"1442":1,"1449":1,"1455":1,"1456":2,"1488":1,"1490":1,"1492":1,"1554":1,"1568":1,"1570":3,"1577":1,"1611":1,"1718":1,"1727":1,"1898":1,"1904":1,"1974":1,"2055":1,"2132":1,"2133":1,"2232":2}}],["december",{"3":{"2220":2}}],["decaying",{"3":{"1436":1}}],["decays",{"3":{"1005":1,"1019":1,"1436":1}}],["decade",{"3":{"1436":1}}],["deck",{"3":{"1115":2,"1117":1,"1118":1,"1340":1,"1342":1,"1350":2,"1357":1,"1389":2,"1395":2,"2239":1}}],["decks",{"3":{"0":1,"443":1,"1373":1,"1380":1,"1389":1,"1467":2,"1640":1}}],["decreasing",{"3":{"1767":1}}],["decreaseinventory",{"3":{"1746":1,"1749":2,"1769":1}}],["decreaseinventoryhandler",{"3":{"1270":1}}],["decreaseinventoryapplier",{"3":{"1270":1}}],["decrease",{"3":{"1270":1,"1496":1,"1742":2,"1746":1,"1749":1,"1767":1,"1768":1}}],["decreaseorderapplier",{"3":{"1199":3,"1207":1}}],["decreased",{"3":{"926":1,"1270":1}}],["decrementasync",{"2":{"1765":1},"3":{"1765":1}}],["decrementing",{"3":{"1323":1}}],["decremented",{"3":{"1125":1,"1324":1,"1835":1,"2161":1}}],["decrements",{"3":{"535":1,"1309":1,"1324":2,"1591":2,"1749":2,"1765":1,"2161":1}}],["decrement",{"3":{"201":1,"535":1,"1286":2,"1309":2,"1315":1,"1324":4,"1600":2,"1749":1,"1881":1,"2161":2}}],["decryptable",{"3":{"667":1}}],["decrypted",{"3":{"664":2,"1336":1,"1339":2,"1442":1,"1970":2}}],["decryption",{"3":{"359":1,"365":1,"665":1,"667":1,"670":1,"1284":1,"1286":1,"1339":1,"1442":1,"2142":1}}],["decrypting",{"3":{"359":2,"2142":2,"2146":1}}],["decrypts",{"3":{"358":2,"359":1,"361":1,"1286":4,"1903":1,"2140":2,"2142":1}}],["decrypt",{"2":{"359":1,"360":1,"2142":1},"3":{"0":1,"359":4,"360":2,"361":1,"1212":1,"1286":4,"1970":1,"2142":4,"2143":1,"2232":1}}],["declining",{"3":{"649":1,"827":1,"828":1,"1324":1,"1339":1,"1350":1,"1396":1,"1415":2,"1417":1}}],["declinequeue",{"3":{"1350":1,"1359":4,"1395":1}}],["decline",{"2":{"1633":1},"3":{"0":1,"742":1,"827":3,"840":1,"915":1,"979":1,"1311":1,"1324":1,"1344":1,"1350":1,"1417":1,"1455":1,"1492":1,"1630":2,"1631":3,"1632":1,"1633":1,"1635":2,"1640":1,"2135":1,"2139":1}}],["declinedsessions",{"3":{"1350":1}}],["declined",{"2":{"1633":1},"3":{"0":2,"151":1,"245":1,"286":1,"405":1,"539":1,"543":1,"659":1,"785":1,"800":1,"827":1,"832":1,"947":1,"1035":1,"1051":3,"1141":1,"1212":1,"1311":1,"1339":1,"1344":1,"1347":1,"1350":3,"1359":27,"1375":1,"1380":2,"1395":1,"1396":3,"1630":1,"1631":2,"1633":1,"1635":2,"1639":2,"2032":1}}],["declines",{"1":{"824":1,"825":1,"826":1,"827":1,"828":1,"829":1,"830":1,"831":1,"832":1,"833":1,"834":1},"3":{"0":2,"692":1,"706":1,"742":1,"745":1,"824":1,"825":1,"827":1,"834":1,"840":1,"948":1,"964":1,"968":1,"1017":1,"1212":1,"1301":1,"1311":1,"1324":1,"1337":1,"1339":3,"1395":1,"1422":1,"1948":1,"2028":1,"2232":1}}],["declarable",{"3":{"1286":1}}],["declaratively",{"3":{"1237":1,"1321":1,"1350":1,"1359":3,"1396":1,"1411":1,"1465":1,"1662":1}}],["declarative",{"3":{"12":1,"838":1,"1237":1,"1247":1,"1264":1,"1270":1,"1286":4,"1297":1,"1309":1,"1310":2,"1323":5,"1324":3,"1359":11,"1369":1,"1380":5,"1396":3,"1402":3,"1415":2,"1416":1,"1436":2,"1662":1,"1668":1,"1865":1,"1867":1,"1868":1,"1952":1,"1955":1,"2066":1,"2229":1}}],["declarativenavigationpopulatortests",{"3":{"1199":1,"1207":3}}],["declarativenavigationpopulator",{"2":{"11":1,"12":1,"1865":1},"3":{"11":1,"12":1,"1199":19,"1203":1,"1207":2,"1310":16,"1359":29,"1402":3,"1865":1}}],["declarationfailure",{"3":{"1436":2}}],["declarationrule",{"2":{"1436":2},"3":{"1436":6}}],["declaration",{"0":{"1384":1},"3":{"0":3,"102":1,"126":1,"130":4,"135":1,"138":1,"139":1,"141":1,"142":2,"160":2,"166":1,"184":1,"251":2,"256":2,"257":1,"336":1,"364":1,"388":1,"413":1,"428":1,"454":1,"463":1,"464":1,"471":1,"492":1,"543":1,"550":1,"585":4,"609":1,"618":1,"633":1,"682":1,"683":1,"726":1,"741":1,"819":1,"821":1,"827":1,"832":1,"834":1,"838":1,"848":1,"889":1,"906":1,"923":1,"954":1,"966":1,"967":1,"980":1,"981":3,"982":1,"1050":1,"1051":1,"1052":1,"1124":1,"1146":1,"1156":1,"1184":1,"1204":1,"1212":1,"1229":3,"1237":6,"1247":4,"1270":15,"1286":20,"1300":7,"1301":1,"1309":1,"1310":3,"1311":10,"1312":1,"1323":18,"1324":15,"1326":1,"1331":1,"1339":1,"1350":7,"1353":1,"1357":1,"1359":38,"1366":1,"1369":8,"1375":1,"1376":1,"1380":2,"1395":11,"1396":15,"1402":1,"1415":17,"1416":1,"1417":6,"1431":1,"1436":50,"1441":3,"1443":1,"1451":1,"1455":1,"1457":1,"1473":1,"1488":1,"1496":10,"1500":3,"1600":1,"1962":1,"2008":1,"2042":1,"2057":1,"2089":1,"2232":1}}],["declarations",{"2":{"1214":1},"3":{"0":4,"11":1,"130":2,"135":2,"136":1,"138":2,"139":1,"141":4,"142":1,"395":1,"531":1,"543":2,"550":2,"551":1,"572":1,"583":1,"607":1,"610":1,"621":1,"681":1,"966":1,"967":1,"980":1,"999":1,"1052":1,"1055":1,"1205":1,"1214":1,"1233":1,"1237":1,"1270":2,"1286":2,"1311":1,"1323":1,"1324":1,"1350":1,"1359":2,"1369":2,"1396":1,"1415":3,"1417":1,"1436":11,"1441":1,"1483":1,"1488":1,"1489":1,"1577":2,"1582":1,"1881":1,"2044":1,"2055":1,"2100":1}}],["declaringentitytype",{"3":{"1247":1,"1310":2}}],["declaringtype",{"2":{"10":1},"3":{"10":1,"1310":1,"1436":3,"1865":1}}],["declaring",{"0":{"1266":1,"1326":1},"3":{"0":2,"24":1,"39":2,"109":1,"131":1,"132":1,"330":1,"471":1,"584":1,"611":1,"642":1,"650":1,"676":1,"782":1,"829":1,"889":1,"1019":1,"1051":1,"1055":1,"1116":1,"1161":3,"1196":1,"1236":1,"1237":1,"1241":1,"1247":3,"1249":1,"1256":1,"1259":4,"1264":1,"1270":4,"1271":1,"1276":1,"1282":1,"1285":1,"1286":13,"1297":1,"1300":7,"1309":1,"1310":5,"1311":3,"1323":6,"1324":6,"1339":1,"1350":3,"1359":27,"1369":2,"1373":1,"1380":3,"1389":1,"1395":2,"1396":4,"1402":2,"1412":1,"1415":6,"1417":1,"1431":1,"1436":29,"1441":1,"1447":1,"1451":1,"1467":4,"1478":1,"1489":1,"1698":1,"1810":1,"1821":1,"1834":1,"1884":1,"1994":1,"2033":1,"2063":1,"2126":1}}],["declareatleastonehandler",{"3":{"1436":1}}],["declareanauthorizationdecision",{"2":{"189":1},"3":{"189":1,"1300":1,"1436":3}}],["declare",{"3":{"0":5,"5":1,"24":1,"59":1,"68":1,"71":1,"91":1,"95":1,"117":2,"136":1,"145":2,"147":2,"149":1,"155":1,"160":2,"162":1,"166":2,"184":2,"186":2,"189":1,"243":2,"265":2,"373":1,"448":1,"530":1,"602":1,"607":1,"609":2,"651":1,"674":2,"782":1,"784":1,"825":1,"827":2,"838":2,"846":1,"881":1,"889":1,"922":1,"923":1,"946":1,"963":1,"964":1,"976":1,"1054":1,"1059":1,"1074":1,"1085":1,"1116":1,"1117":1,"1237":4,"1242":1,"1247":3,"1255":1,"1259":4,"1266":1,"1270":8,"1279":2,"1286":18,"1300":9,"1301":1,"1309":2,"1310":1,"1320":1,"1323":2,"1324":10,"1338":1,"1339":2,"1345":1,"1349":1,"1350":1,"1359":14,"1362":2,"1363":1,"1369":3,"1374":3,"1380":3,"1384":1,"1395":2,"1396":4,"1402":2,"1406":1,"1409":1,"1415":7,"1416":1,"1417":3,"1431":2,"1436":29,"1455":1,"1474":1,"1489":3,"1491":1,"1600":1,"1627":1,"1653":1,"1654":1,"1667":2,"1697":1,"1719":1,"1721":1,"1753":1,"1827":2,"1879":1,"1886":1,"1953":1,"1955":1,"1961":1,"1962":1,"1965":1,"1994":1,"1997":1,"2014":1,"2022":1,"2033":1,"2035":1,"2037":1,"2038":2,"2046":1,"2080":1,"2085":1,"2098":2,"2103":1,"2114":1,"2126":1,"2132":1,"2136":1,"2158":1}}],["declaredpublicproperties",{"3":{"1436":1}}],["declaredengines",{"3":{"1286":1}}],["declaredresulttype",{"3":{"1270":3}}],["declarednamecache",{"3":{"1259":1,"1286":1}}],["declaredonly",{"2":{"966":1},"3":{"0":1,"135":1,"964":1,"966":1,"1212":1,"1237":2,"1324":1,"1436":8}}],["declared",{"0":{"2033":1},"3":{"0":12,"24":1,"31":1,"57":1,"76":1,"78":1,"81":3,"92":1,"93":1,"95":1,"96":1,"102":1,"135":6,"136":1,"138":1,"139":1,"155":1,"160":1,"166":1,"184":1,"185":1,"188":1,"225":1,"258":1,"260":1,"284":1,"290":1,"326":1,"330":1,"341":1,"346":1,"386":1,"388":1,"397":2,"409":1,"418":1,"423":2,"428":1,"430":1,"440":1,"443":1,"448":1,"470":1,"472":1,"473":1,"545":2,"549":1,"583":4,"607":2,"609":2,"612":2,"635":1,"649":1,"651":1,"657":2,"749":1,"764":1,"765":2,"766":2,"782":1,"790":1,"801":1,"827":2,"831":1,"836":1,"838":1,"846":1,"847":2,"848":2,"849":1,"875":1,"889":1,"891":1,"964":1,"966":3,"971":1,"979":1,"980":1,"990":1,"995":2,"1018":2,"1019":1,"1034":2,"1035":1,"1052":1,"1067":3,"1075":1,"1083":1,"1116":3,"1119":1,"1161":1,"1170":1,"1191":1,"1212":2,"1214":1,"1229":2,"1233":1,"1235":2,"1237":16,"1239":1,"1240":1,"1241":1,"1247":17,"1250":2,"1259":5,"1260":1,"1263":2,"1264":1,"1266":1,"1269":2,"1270":18,"1271":4,"1276":1,"1277":1,"1281":4,"1285":2,"1286":88,"1297":1,"1300":20,"1303":1,"1307":1,"1309":12,"1310":4,"1311":24,"1312":2,"1315":2,"1323":23,"1324":25,"1339":9,"1344":3,"1345":1,"1347":3,"1350":33,"1353":1,"1358":2,"1359":115,"1362":1,"1369":5,"1373":3,"1380":15,"1395":25,"1396":31,"1398":1,"1402":11,"1405":1,"1406":1,"1411":1,"1415":21,"1416":7,"1417":4,"1427":2,"1428":1,"1430":1,"1431":3,"1435":1,"1436":89,"1438":3,"1441":5,"1442":1,"1443":1,"1444":1,"1446":1,"1450":1,"1458":1,"1459":1,"1463":2,"1464":1,"1467":15,"1474":1,"1476":3,"1481":1,"1488":1,"1489":5,"1500":1,"1513":1,"1526":1,"1534":1,"1547":1,"1577":1,"1591":3,"1597":1,"1599":1,"1631":1,"1662":1,"1670":1,"1671":1,"1673":1,"1686":1,"1731":1,"1815":1,"1819":1,"1849":1,"1856":1,"1880":1,"1881":1,"1886":2,"1889":1,"1891":2,"1922":1,"1939":1,"1953":1,"1964":1,"1994":1,"1996":1,"2000":2,"2003":1,"2009":1,"2029":2,"2033":2,"2034":6,"2037":3,"2038":2,"2044":1,"2046":1,"2075":2,"2080":1,"2094":1,"2125":1,"2126":3,"2128":2,"2131":1,"2138":1,"2156":1,"2160":1,"2174":1,"2232":3}}],["declaresauthorizationanywhere",{"3":{"1436":1}}],["declareseveryexpectedlayer",{"3":{"1436":2,"1489":1}}],["declarespii",{"2":{"1436":1},"3":{"1436":1}}],["declares",{"0":{"1880":1},"3":{"0":12,"5":1,"7":1,"37":1,"42":1,"47":1,"59":1,"78":1,"80":1,"81":2,"94":1,"95":2,"98":1,"115":1,"121":2,"122":1,"128":1,"146":1,"147":1,"155":1,"157":1,"160":1,"180":2,"185":1,"186":4,"187":1,"189":2,"190":1,"298":1,"305":1,"310":2,"325":1,"341":1,"370":1,"408":1,"426":1,"442":1,"448":2,"468":4,"470":2,"475":2,"476":1,"477":1,"478":1,"486":1,"497":1,"523":3,"545":2,"549":2,"551":2,"552":1,"556":3,"576":1,"582":1,"583":7,"585":1,"599":2,"609":3,"611":1,"626":1,"631":2,"633":2,"640":2,"643":2,"650":1,"676":1,"698":2,"707":1,"725":2,"733":1,"764":1,"766":1,"767":1,"768":1,"783":1,"798":5,"804":2,"825":1,"827":6,"838":4,"842":2,"881":2,"889":1,"891":1,"914":1,"922":1,"954":1,"956":1,"959":1,"963":2,"964":1,"966":1,"972":2,"973":1,"980":3,"983":1,"1018":2,"1033":2,"1050":7,"1052":1,"1054":1,"1067":2,"1100":1,"1116":1,"1133":5,"1155":1,"1156":1,"1161":1,"1168":1,"1227":1,"1229":3,"1230":1,"1231":2,"1232":1,"1237":5,"1241":6,"1244":2,"1249":1,"1250":1,"1259":6,"1269":1,"1270":51,"1271":7,"1274":1,"1275":1,"1276":2,"1279":2,"1281":1,"1286":31,"1300":15,"1301":3,"1304":1,"1308":1,"1309":4,"1310":5,"1311":13,"1312":1,"1318":1,"1320":1,"1321":1,"1322":1,"1323":11,"1324":13,"1325":1,"1326":2,"1327":2,"1328":1,"1331":1,"1332":1,"1338":2,"1339":15,"1350":3,"1353":1,"1359":85,"1360":2,"1362":3,"1363":1,"1366":1,"1369":9,"1371":1,"1372":1,"1373":1,"1374":2,"1376":1,"1380":6,"1384":2,"1389":1,"1391":1,"1393":1,"1395":23,"1396":24,"1401":1,"1402":7,"1406":1,"1407":1,"1409":2,"1415":22,"1416":6,"1417":7,"1427":2,"1430":1,"1431":1,"1436":100,"1438":7,"1441":3,"1445":1,"1447":3,"1449":1,"1451":1,"1454":3,"1455":2,"1460":1,"1464":1,"1467":9,"1470":1,"1472":1,"1473":1,"1475":2,"1476":1,"1484":1,"1489":6,"1490":1,"1492":1,"1496":2,"1526":1,"1577":2,"1591":1,"1596":1,"1627":2,"1649":2,"1651":1,"1653":2,"1662":1,"1667":1,"1673":1,"1685":1,"1703":2,"1718":1,"1727":1,"1751":1,"1765":1,"1772":1,"1810":5,"1815":2,"1823":1,"1826":1,"1828":1,"1829":1,"1865":1,"1878":1,"1880":2,"1889":1,"1891":1,"1898":1,"1916":1,"1922":1,"1933":1,"1944":1,"1953":1,"1960":1,"1962":1,"1963":2,"1988":1,"1994":1,"2000":2,"2008":1,"2024":1,"2042":2,"2046":1,"2070":1,"2072":1,"2083":1,"2087":1,"2104":3,"2131":2,"2139":1,"2150":1,"2157":1,"2158":1,"2190":1,"2203":1,"2232":1}}],["decommission",{"3":{"1463":1,"1467":1}}],["decomposition",{"3":{"1395":4,"1402":3,"1600":1}}],["decomposed",{"3":{"1311":1,"1395":1,"1638":1}}],["decompression",{"3":{"1323":2,"1324":3,"2126":1}}],["decoupling",{"3":{"1270":1,"1300":1,"1359":1,"1402":1,"1416":1,"1526":1,"1539":1,"1577":1,"1591":1,"1831":1,"2134":1}}],["decouples",{"3":{"332":1,"1311":1,"1323":2,"1350":1,"1396":1,"1438":1,"1665":1,"1837":1,"1964":1,"1965":1,"1990":1}}],["decouple",{"1":{"1863":1,"1864":1,"1865":1,"1866":1,"1867":1,"1868":1},"3":{"187":1,"299":1,"1347":1,"1863":1,"2207":1}}],["decoupled",{"1":{"1310":1},"3":{"186":1,"301":1,"399":1,"404":1,"1192":1,"1202":1,"1203":1,"1259":1,"1309":1,"1310":2,"1311":2,"1323":2,"1324":3,"1339":1,"1350":3,"1359":1,"1374":1,"1380":1,"1395":1,"1396":1,"1402":1,"1415":1,"1417":1,"1441":1,"1538":1,"1546":1,"1554":1,"1670":1,"1992":1,"2157":1,"2159":1}}],["deconstructs",{"3":{"1436":1}}],["deconstruction",{"3":{"1300":2,"1324":1}}],["deconstruct",{"3":{"1050":1,"1234":1,"1237":1,"1286":1,"1300":1,"1436":1}}],["decodable",{"3":{"1874":1}}],["decoding",{"3":{"442":1,"1186":1,"1228":1,"1300":3,"1323":2,"1359":1,"1380":2,"1417":1}}],["decoderoptions",{"3":{"1323":1}}],["decoder",{"3":{"683":1,"1229":1,"1311":1,"1312":7,"1378":2,"1380":3,"1396":2}}],["decoded",{"3":{"341":1,"346":1,"438":1,"442":1,"682":1,"684":1,"922":1,"1229":2,"1300":1,"1311":2,"1312":1,"1323":2,"1324":1,"1359":6,"1380":1,"1396":1,"1417":6,"1651":1,"1877":1,"2126":4,"2128":1}}],["decode",{"3":{"0":1,"359":1,"360":2,"365":1,"440":1,"442":1,"681":1,"682":1,"1115":1,"1116":1,"1229":1,"1247":2,"1286":6,"1300":2,"1311":1,"1312":2,"1323":5,"1380":2,"1417":4,"1438":2,"1496":1,"2126":3,"2128":3,"2143":1}}],["decodes",{"3":{"0":1,"341":1,"682":1,"683":1,"684":1,"922":1,"1241":1,"1247":2,"1271":1,"1300":3,"1311":4,"1312":2,"1323":2,"1380":1,"1396":2,"1417":1,"1874":1,"2126":1,"2142":1}}],["decoratable",{"3":{"1924":1}}],["decorate",{"3":{"1311":1,"1323":1,"1324":5,"1380":1,"1396":2,"1436":1}}],["decoratedentity",{"3":{"1199":2,"1207":1}}],["decorated",{"3":{"330":1,"1286":3,"1311":1,"1323":1,"1339":1,"1342":1,"1343":1,"1350":2,"1359":5,"1379":1,"1380":1,"1395":1,"1396":2,"1436":3,"1488":1,"1489":1,"1540":1,"1966":1,"1988":1,"2055":1,"2095":1,"2098":1}}],["decorates",{"3":{"0":1,"122":1,"175":2,"1042":1,"1059":1,"1091":1,"1286":1,"1297":1,"1300":1,"1318":1,"1323":2,"1324":1,"1380":1,"1396":1,"1421":1,"1488":1,"2186":1,"2199":1}}],["decorating",{"3":{"1286":2,"1324":2}}],["decoration",{"3":{"117":1,"122":2,"175":1,"415":1,"592":1,"681":1,"1045":1,"1270":1,"1286":3,"1290":1,"1300":1,"1316":1,"1323":3,"1339":1,"1359":4,"1380":1,"1395":5,"1396":4,"1415":1,"1417":3,"1436":6,"1489":1,"1686":1,"1698":1,"1701":1,"1827":2,"1911":1,"2117":1,"2122":1,"2172":1}}],["decoratively",{"3":{"1270":1,"1286":1}}],["decorative",{"3":{"0":1,"195":1,"305":1,"342":1,"684":1,"774":1,"1229":1,"1259":1,"1324":3,"1359":1,"1417":1,"2030":1}}],["decoratorpipelineseal",{"3":{"1199":2,"1203":1,"1207":1,"1313":1,"1316":1,"1323":7,"1496":1}}],["decoratorpipelineordertests",{"2":{"127":1,"1600":1},"3":{"121":4,"127":3,"572":4,"1199":3,"1207":8,"1262":3,"1316":1,"1323":4,"1415":2,"1436":11,"1438":2,"1487":1,"1488":3,"1489":3,"1600":1}}],["decoratorpipelineordertestsbase",{"2":{"121":1,"122":1,"126":1,"572":1,"579":1,"749":1,"1262":1,"1428":1,"1507":1,"2055":1},"3":{"0":1,"122":1,"126":1,"572":6,"574":1,"579":1,"749":1,"1199":3,"1207":2,"1262":5,"1428":1,"1436":17,"1438":1,"1488":2,"1489":1,"1496":1,"1507":1,"2055":2}}],["decorators",{"2":{"1270":1},"3":{"0":8,"115":4,"117":4,"119":1,"121":5,"122":5,"123":3,"125":3,"126":7,"127":1,"186":1,"242":1,"243":1,"246":3,"247":1,"300":2,"306":1,"395":2,"397":4,"406":1,"407":3,"522":1,"523":1,"571":1,"572":1,"674":1,"698":2,"701":1,"702":1,"732":1,"733":2,"735":1,"737":1,"827":1,"923":1,"987":1,"1004":2,"1058":1,"1059":3,"1100":1,"1192":1,"1203":21,"1207":208,"1212":2,"1223":1,"1229":1,"1246":2,"1260":2,"1261":1,"1262":5,"1263":16,"1264":3,"1265":1,"1266":1,"1267":4,"1268":3,"1270":97,"1271":11,"1275":1,"1284":1,"1286":5,"1297":1,"1300":12,"1301":12,"1309":2,"1311":6,"1313":1,"1316":2,"1318":1,"1323":18,"1324":1,"1339":1,"1359":16,"1396":1,"1402":1,"1415":3,"1427":1,"1436":8,"1437":1,"1438":10,"1496":2,"1526":1,"1538":1,"1542":1,"1566":1,"1577":3,"1651":1,"1653":1,"1700":1,"1717":1,"1774":1,"1818":1,"1821":3,"1823":1,"1824":2,"1825":1,"1826":2,"1827":1,"1851":1,"1879":2,"1883":3,"1886":1,"1914":1,"1915":2,"1919":1,"1920":3,"1922":1,"1923":1,"1924":2,"2010":1,"2136":2,"2156":2,"2199":1}}],["decorator",{"0":{"1507":1,"2096":1,"2136":1},"1":{"114":1,"115":1,"116":1,"117":1,"118":1,"119":1,"120":1,"121":1,"122":1,"123":1,"124":1,"125":1,"126":1,"127":1,"128":1,"1260":1,"1261":1,"1262":1,"1263":1,"1264":1,"1265":1,"1266":1,"1267":1,"1268":1,"1269":1,"1270":1,"1818":1,"1819":1,"1820":1,"1821":1,"1822":1,"1823":1,"1824":1,"1825":1,"1826":1,"1827":1},"3":{"0":15,"113":1,"114":1,"115":6,"117":3,"119":1,"120":1,"122":4,"123":2,"124":4,"125":3,"126":2,"128":3,"135":2,"143":1,"184":1,"242":1,"265":2,"299":2,"300":1,"303":1,"304":1,"383":1,"395":3,"397":1,"398":2,"406":1,"407":2,"409":1,"518":1,"521":1,"550":1,"571":1,"572":6,"573":2,"574":5,"578":1,"579":1,"692":1,"699":1,"700":1,"714":1,"716":1,"733":3,"735":2,"748":1,"749":1,"750":1,"753":1,"790":1,"905":1,"907":1,"909":1,"922":1,"923":1,"924":1,"925":1,"987":1,"992":1,"1042":1,"1043":2,"1046":1,"1104":1,"1192":4,"1202":3,"1203":3,"1212":7,"1213":3,"1223":1,"1229":2,"1237":1,"1246":2,"1247":1,"1259":1,"1260":5,"1261":4,"1262":1,"1263":2,"1264":3,"1266":1,"1267":4,"1268":2,"1269":1,"1270":98,"1271":9,"1278":1,"1286":22,"1300":7,"1301":4,"1309":11,"1311":7,"1313":1,"1315":1,"1316":2,"1318":2,"1320":2,"1323":27,"1324":10,"1339":1,"1347":1,"1350":2,"1352":1,"1359":68,"1380":2,"1395":1,"1396":9,"1402":13,"1406":1,"1415":26,"1418":1,"1427":3,"1431":1,"1436":36,"1437":1,"1438":2,"1487":2,"1488":3,"1496":3,"1506":1,"1507":1,"1526":2,"1534":1,"1539":2,"1543":1,"1577":7,"1585":2,"1591":1,"1651":5,"1654":1,"1660":1,"1662":2,"1671":1,"1720":1,"1730":1,"1774":1,"1779":1,"1786":1,"1817":1,"1818":3,"1819":1,"1820":3,"1821":4,"1822":1,"1823":3,"1825":3,"1826":2,"1827":4,"1828":1,"1831":2,"1881":1,"1883":1,"1886":1,"1914":1,"1915":2,"1916":1,"1917":1,"1918":5,"1920":2,"1924":2,"1944":1,"1962":1,"2087":1,"2097":1,"2098":1,"2111":1,"2134":3,"2136":4,"2137":1,"2139":1,"2156":2,"2160":1,"2166":1,"2180":2,"2181":1,"2182":1,"2184":1,"2186":1,"2190":1,"2193":1,"2194":1,"2206":1,"2227":3,"2230":1,"2232":3,"2239":1}}],["decisively",{"3":{"1350":1}}],["decisive",{"3":{"827":1,"1350":1}}],["decisionsupport",{"3":{"493":1,"494":1,"495":1,"995":1,"1018":2,"1082":1,"1099":1,"1203":34,"1207":86,"1264":1,"1270":2,"1286":1,"1347":5,"1350":27,"1359":71,"1367":1,"1369":2,"1390":1,"1395":1,"1438":4,"1496":3,"1526":1}}],["decisions",{"0":{"1201":1,"1648":1},"3":{"0":3,"64":1,"175":1,"242":1,"265":1,"387":1,"555":1,"618":1,"664":1,"724":1,"726":1,"736":1,"775":1,"825":1,"827":1,"867":1,"868":1,"905":1,"912":1,"975":1,"1016":1,"1021":1,"1051":1,"1093":1,"1109":1,"1162":1,"1270":1,"1271":1,"1286":5,"1287":1,"1300":7,"1302":1,"1311":10,"1312":2,"1323":3,"1324":4,"1330":1,"1339":2,"1350":1,"1359":6,"1369":1,"1380":1,"1389":1,"1393":1,"1395":3,"1396":8,"1409":1,"1415":5,"1417":4,"1427":1,"1429":1,"1436":7,"1438":1,"1455":4,"1457":1,"1467":2,"1471":2,"1476":1,"1488":1,"1489":2,"1492":1,"1521":1,"1534":1,"1552":1,"1571":3,"1572":1,"1585":3,"1638":1,"1639":1,"1640":1,"1686":1,"1708":1,"1727":2,"1755":1,"1778":1,"1863":1,"1874":1,"1900":1,"1947":2,"2000":1,"2029":1,"2038":1,"2150":1,"2172":1,"2179":1,"2191":1,"2199":1,"2215":1,"2220":1,"2221":1,"2232":1,"2236":1}}],["decision",{"0":{"5":1,"11":1,"17":1,"31":1,"39":1,"47":1,"53":1,"59":1,"68":1,"78":1,"86":1,"98":1,"109":1,"117":1,"132":1,"147":1,"157":1,"166":1,"175":1,"186":1,"195":1,"207":1,"214":1,"225":1,"235":1,"243":1,"265":1,"273":1,"281":1,"291":1,"300":1,"310":1,"318":1,"330":1,"341":1,"350":1,"359":1,"370":1,"380":1,"388":1,"397":1,"413":1,"420":1,"434":1,"440":1,"448":1,"459":1,"470":1,"482":1,"499":1,"507":1,"518":1,"528":1,"536":1,"545":1,"556":1,"564":1,"572":1,"583":1,"591":1,"599":1,"609":1,"618":1,"626":1,"633":1,"640":1,"649":1,"657":1,"665":1,"674":1,"682":1,"690":1,"698":1,"707":1,"715":1,"725":1,"733":1,"741":1,"749":1,"757":1,"766":1,"774":1,"782":1,"790":1,"799":1,"810":1,"819":1,"827":1,"838":1,"846":1,"854":1,"861":1,"869":1,"881":1,"889":1,"897":1,"905":1,"914":1,"922":1,"931":1,"939":1,"946":1,"954":1,"964":1,"972":1,"980":1,"988":1,"996":1,"1004":1,"1012":1,"1018":1,"1026":1,"1034":1,"1042":1,"1050":1,"1059":1,"1067":1,"1075":1,"1083":1,"1091":1,"1100":1,"1108":1,"1116":1,"1124":1,"1133":1,"1141":1,"1151":1,"1161":1,"1168":1,"1175":1,"1184":1,"1347":1,"1359":1,"1376":1,"1390":1,"2232":1},"1":{"0":1,"1":1},"3":{"0":18,"1":3,"3":1,"21":1,"22":2,"23":2,"24":2,"27":2,"34":1,"43":2,"57":2,"67":1,"71":3,"72":2,"73":2,"74":2,"81":1,"82":1,"85":1,"87":1,"104":1,"109":1,"112":1,"115":1,"121":1,"125":1,"128":2,"130":2,"135":3,"136":1,"137":2,"138":1,"139":1,"140":2,"141":1,"142":1,"145":1,"147":1,"149":1,"150":2,"151":1,"152":1,"155":2,"160":2,"161":1,"164":4,"165":1,"167":1,"170":1,"171":2,"173":1,"174":1,"175":1,"178":1,"181":2,"187":1,"190":1,"200":4,"201":1,"203":2,"219":2,"220":2,"230":1,"231":1,"233":5,"238":1,"245":2,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":3,"257":1,"258":3,"259":3,"260":2,"261":1,"264":1,"265":5,"267":2,"268":6,"269":1,"272":1,"275":1,"276":1,"279":1,"295":2,"296":2,"303":1,"306":4,"308":1,"310":1,"314":1,"317":1,"326":1,"329":1,"336":1,"337":2,"345":1,"354":2,"358":2,"364":2,"365":2,"366":1,"369":1,"375":3,"382":1,"383":1,"386":1,"390":1,"392":1,"393":2,"396":1,"399":1,"400":2,"409":3,"416":2,"419":1,"423":4,"424":1,"425":1,"426":2,"428":2,"429":1,"430":2,"436":1,"444":1,"447":1,"453":3,"454":1,"455":1,"463":3,"464":2,"465":1,"466":4,"468":4,"469":1,"472":1,"473":1,"474":3,"475":2,"476":3,"477":4,"478":3,"483":1,"486":2,"490":1,"495":1,"497":1,"502":1,"508":1,"511":1,"514":2,"516":1,"519":1,"520":1,"522":2,"523":1,"524":1,"526":2,"528":1,"530":1,"531":1,"534":1,"535":1,"536":1,"537":1,"540":1,"543":6,"549":3,"551":2,"552":2,"555":1,"557":1,"559":1,"560":2,"562":1,"567":1,"571":1,"574":1,"578":2,"582":1,"586":1,"591":1,"594":2,"601":1,"604":1,"607":7,"608":1,"611":1,"613":1,"616":4,"621":1,"627":1,"629":3,"632":1,"636":2,"638":1,"642":1,"644":1,"652":1,"656":1,"660":2,"667":1,"669":2,"677":1,"685":1,"688":3,"693":2,"696":1,"703":1,"706":1,"709":1,"710":1,"715":1,"716":1,"719":1,"720":1,"728":1,"731":1,"733":1,"734":1,"737":1,"744":1,"748":2,"751":1,"752":1,"758":1,"761":1,"769":2,"773":1,"775":1,"777":1,"790":2,"794":2,"795":1,"798":1,"800":2,"801":1,"802":2,"803":1,"804":1,"805":1,"806":1,"810":1,"814":1,"819":2,"820":1,"822":2,"825":6,"827":4,"828":1,"829":2,"831":3,"832":1,"833":1,"836":3,"839":2,"841":1,"849":2,"850":2,"853":1,"856":1,"857":2,"864":2,"865":1,"867":2,"869":1,"871":2,"874":1,"875":2,"877":3,"884":1,"892":2,"896":1,"900":1,"905":1,"907":1,"909":1,"912":1,"913":1,"914":1,"915":1,"916":1,"917":1,"924":1,"926":2,"927":2,"929":1,"932":1,"934":4,"944":2,"949":2,"955":1,"958":1,"959":1,"960":2,"963":1,"964":2,"966":1,"967":2,"968":2,"979":1,"980":1,"983":1,"987":1,"988":1,"991":1,"997":1,"1003":1,"1004":1,"1006":1,"1007":1,"1012":1,"1013":1,"1016":1,"1018":1,"1027":1,"1029":1,"1032":1,"1033":1,"1037":2,"1042":1,"1044":3,"1045":2,"1046":1,"1048":1,"1052":1,"1053":1,"1054":3,"1055":1,"1057":1,"1059":2,"1067":1,"1068":1,"1070":3,"1073":2,"1074":1,"1075":3,"1076":1,"1077":1,"1078":2,"1079":2,"1081":1,"1082":1,"1083":2,"1085":1,"1086":1,"1095":1,"1101":1,"1103":1,"1111":1,"1116":1,"1119":1,"1128":3,"1134":1,"1136":1,"1140":1,"1146":1,"1155":1,"1156":1,"1173":1,"1175":1,"1182":1,"1192":1,"1202":1,"1203":1,"1212":3,"1217":1,"1218":1,"1229":3,"1231":1,"1232":2,"1235":1,"1236":1,"1237":13,"1239":1,"1240":1,"1244":2,"1247":1,"1259":12,"1260":4,"1263":2,"1269":1,"1270":12,"1271":1,"1273":3,"1281":2,"1285":2,"1286":38,"1287":1,"1300":26,"1301":5,"1303":1,"1309":6,"1310":3,"1311":22,"1312":2,"1315":1,"1320":1,"1322":2,"1323":30,"1324":38,"1329":1,"1334":1,"1337":1,"1339":29,"1340":1,"1342":1,"1343":1,"1350":14,"1351":1,"1352":2,"1355":1,"1357":1,"1359":54,"1369":2,"1373":1,"1376":1,"1380":7,"1390":1,"1395":30,"1396":42,"1397":1,"1402":19,"1407":1,"1413":1,"1415":17,"1416":3,"1417":5,"1418":1,"1422":1,"1424":1,"1427":1,"1430":1,"1436":42,"1440":1,"1452":1,"1455":3,"1462":1,"1465":1,"1467":6,"1472":1,"1473":1,"1475":1,"1476":1,"1482":1,"1483":1,"1485":1,"1488":2,"1489":1,"1496":3,"1521":1,"1524":1,"1526":6,"1530":1,"1532":1,"1534":3,"1536":1,"1545":2,"1571":1,"1575":2,"1577":6,"1582":1,"1583":1,"1585":2,"1589":1,"1591":1,"1599":3,"1604":1,"1632":2,"1639":2,"1640":2,"1641":1,"1656":1,"1673":1,"1674":1,"1686":2,"1687":1,"1706":1,"1722":1,"1727":1,"1747":4,"1749":2,"1765":1,"1787":1,"1788":1,"1792":1,"1799":2,"1801":1,"1802":1,"1822":2,"1823":1,"1825":1,"1827":1,"1841":2,"1842":1,"1845":1,"1848":1,"1854":1,"1856":1,"1862":2,"1876":1,"1877":1,"1889":1,"1890":1,"1903":1,"1904":1,"1910":1,"1921":1,"1927":2,"1930":1,"1935":2,"1941":1,"1943":1,"1947":2,"1949":1,"1951":1,"1963":1,"1971":1,"1979":1,"1999":1,"2000":1,"2001":1,"2002":1,"2024":1,"2028":1,"2032":1,"2035":1,"2038":1,"2043":1,"2047":1,"2048":1,"2053":1,"2064":2,"2067":1,"2069":1,"2081":1,"2085":2,"2108":1,"2128":1,"2130":1,"2131":1,"2133":1,"2145":1,"2151":1,"2153":1,"2156":1,"2158":2,"2160":1,"2161":1,"2164":1,"2166":1,"2169":1,"2175":1,"2180":1,"2181":1,"2184":2,"2192":4,"2193":1,"2196":1,"2203":2,"2212":1,"2226":1,"2231":1,"2232":5,"2233":2,"2239":1,"2242":1,"2245":1}}],["decimals",{"3":{"1324":1,"1359":1,"1436":1,"1767":1}}],["decimalfilterstrategytests",{"3":{"1199":1,"1207":3}}],["decimalfilterstrategy",{"2":{"1241":1},"3":{"1199":2,"1203":1,"1207":2,"1241":4,"1247":9}}],["decimal",{"2":{"656":1,"1541":1,"1764":1},"3":{"330":1,"656":1,"657":2,"692":1,"717":1,"1234":1,"1237":3,"1247":11,"1271":1,"1286":6,"1300":2,"1312":7,"1323":1,"1324":10,"1342":1,"1350":3,"1359":2,"1362":1,"1366":1,"1369":9,"1395":4,"1396":2,"1417":1,"1436":5,"1438":1,"1496":3,"1541":1,"1600":2,"1685":5,"1686":1,"1764":3,"1810":2,"1988":1}}],["decidable",{"3":{"1416":1}}],["decide",{"0":{"1257":1},"3":{"0":1,"69":1,"150":1,"244":1,"299":1,"305":1,"536":1,"539":1,"681":1,"765":1,"827":1,"898":1,"987":1,"1018":1,"1033":1,"1035":1,"1100":1,"1107":1,"1115":1,"1187":1,"1236":1,"1237":1,"1244":1,"1247":2,"1249":1,"1259":3,"1278":1,"1286":9,"1296":1,"1300":6,"1309":2,"1311":2,"1323":6,"1324":7,"1339":2,"1350":5,"1351":1,"1359":7,"1361":1,"1368":1,"1369":2,"1380":3,"1395":7,"1396":4,"1402":2,"1415":2,"1417":4,"1423":1,"1426":1,"1427":3,"1436":5,"1467":1,"1485":1,"1639":1,"1648":1,"1670":1,"1686":1,"1717":1,"1727":2,"1853":1,"1873":1,"1876":1,"1877":1,"1912":1,"1950":1,"1959":1,"2068":1,"2143":1,"2169":2,"2190":1,"2193":2}}],["decides",{"3":{"0":2,"179":1,"201":1,"225":1,"243":1,"253":1,"258":1,"299":1,"318":1,"336":1,"343":1,"407":1,"535":1,"539":1,"540":1,"544":1,"563":1,"568":1,"571":1,"582":1,"583":1,"598":2,"605":1,"630":1,"639":3,"648":1,"653":1,"664":3,"673":2,"678":2,"689":1,"691":1,"734":1,"741":2,"748":1,"765":1,"770":1,"774":1,"781":3,"783":1,"784":1,"785":1,"786":1,"789":5,"791":1,"813":1,"888":1,"893":1,"896":1,"899":1,"924":1,"945":1,"953":1,"987":1,"1085":1,"1092":1,"1101":1,"1127":1,"1212":1,"1237":3,"1238":1,"1243":1,"1244":1,"1247":5,"1250":1,"1252":1,"1259":7,"1270":2,"1273":1,"1277":1,"1280":3,"1286":19,"1287":1,"1289":1,"1299":1,"1300":14,"1301":2,"1304":1,"1309":2,"1310":6,"1311":6,"1312":1,"1318":1,"1323":8,"1324":10,"1336":1,"1339":4,"1350":4,"1357":1,"1359":19,"1369":2,"1372":1,"1373":1,"1380":2,"1389":1,"1391":2,"1395":12,"1396":9,"1401":1,"1402":4,"1406":1,"1409":1,"1414":1,"1415":4,"1417":3,"1419":1,"1431":1,"1436":7,"1438":4,"1455":1,"1462":1,"1467":2,"1470":1,"1492":1,"1508":1,"1640":1,"1728":1,"1750":1,"1810":1,"1826":1,"1838":1,"1840":2,"1845":1,"1854":1,"1865":1,"1874":1,"1897":1,"1902":1,"1920":1,"1921":1,"1933":1,"1947":1,"1977":1,"1981":1,"1982":1,"1989":1,"1991":1,"2010":1,"2156":2,"2158":1,"2162":1,"2191":1,"2195":1,"2196":1}}],["decided",{"3":{"0":12,"125":1,"145":1,"200":1,"225":1,"255":1,"309":1,"360":1,"380":1,"443":1,"497":1,"498":1,"517":1,"538":1,"541":1,"598":1,"599":1,"608":1,"625":1,"648":1,"664":1,"673":1,"681":1,"683":1,"717":1,"724":2,"728":1,"731":1,"744":1,"790":1,"798":1,"809":1,"852":1,"870":1,"876":1,"880":2,"903":1,"926":1,"953":1,"992":1,"1012":1,"1028":1,"1075":1,"1116":1,"1117":2,"1229":1,"1237":1,"1241":1,"1243":1,"1259":2,"1265":2,"1270":4,"1281":1,"1286":5,"1300":7,"1301":2,"1309":1,"1324":8,"1336":1,"1339":2,"1342":1,"1347":1,"1348":1,"1350":7,"1357":1,"1359":17,"1361":1,"1369":2,"1395":6,"1396":9,"1399":1,"1401":1,"1402":3,"1415":2,"1417":4,"1421":1,"1422":1,"1425":1,"1436":5,"1475":1,"1526":1,"1545":1,"1547":1,"1666":1,"1727":1,"1748":1,"1822":1,"1933":2,"1960":1,"2033":1,"2126":1,"2135":1,"2168":1,"2177":1,"2181":1,"2232":1}}],["deciding",{"3":{"0":1,"109":1,"544":1,"555":1,"582":1,"598":1,"764":1,"775":1,"783":1,"805":1,"987":1,"1229":1,"1237":2,"1285":1,"1286":4,"1311":1,"1339":1,"1359":3,"1395":1,"1396":1,"1402":1,"1417":3,"1418":1,"1436":1,"1811":1,"1956":1,"1972":1,"2183":1}}],["del",{"3":{"1301":2,"1323":2,"1685":6,"1686":6}}],["delaying",{"3":{"1324":2}}],["delays",{"3":{"27":1,"520":1,"709":1,"883":1,"916":1,"1102":1,"1259":1,"1270":1,"1323":1,"1324":2,"1436":1,"1942":1,"2052":1}}],["delay",{"2":{"1162":1,"2185":1},"3":{"18":3,"71":1,"72":1,"293":1,"536":1,"819":1,"1042":2,"1162":1,"1254":2,"1259":8,"1270":3,"1275":2,"1286":7,"1317":1,"1323":14,"1324":2,"1330":1,"1339":1,"1395":2,"1430":1,"1436":7,"1438":1,"1488":1,"1664":1,"1840":1,"1845":1,"1852":1,"2032":1,"2167":2,"2185":1,"2188":2}}],["delayed",{"2":{"818":1,"1843":1},"3":{"0":3,"150":1,"627":1,"640":1,"642":1,"735":1,"818":1,"819":1,"820":1,"821":2,"1075":1,"1076":1,"1263":1,"1270":5,"1323":3,"1339":1,"1467":1,"1641":1,"1665":1,"1749":1,"1843":1,"1922":1,"1923":1}}],["deltas",{"3":{"0":1,"914":1,"1213":1,"1436":1}}],["delta",{"2":{"1689":1,"1699":1},"3":{"0":1,"480":1,"482":1,"484":2,"490":1,"491":1,"492":1,"493":2,"494":2,"495":1,"631":2,"633":2,"635":1,"665":1,"666":1,"832":1,"1212":1,"1300":1,"1353":1,"1359":34,"1402":2,"1488":2,"1490":2,"1496":13,"1575":5,"1576":5,"1689":1,"1699":1,"1727":2}}],["delegatingchatclient",{"2":{"1418":1,"1422":1,"1423":1,"1427":1,"2174":1,"2180":1},"3":{"1418":1,"1422":1,"1423":1,"1427":8,"2174":1,"2180":1}}],["delegatinghandler",{"3":{"1311":1,"1312":1,"1324":9}}],["delegating",{"3":{"0":1,"507":1,"508":1,"513":1,"926":1,"1034":1,"1042":2,"1091":2,"1192":1,"1212":1,"1229":5,"1231":1,"1237":1,"1241":1,"1244":1,"1247":1,"1256":1,"1259":1,"1262":1,"1269":1,"1270":1,"1271":2,"1286":3,"1290":2,"1300":5,"1309":2,"1310":3,"1311":2,"1324":13,"1339":3,"1343":1,"1350":7,"1355":1,"1359":14,"1376":1,"1380":3,"1395":8,"1396":6,"1399":1,"1402":3,"1405":1,"1410":1,"1413":1,"1415":2,"1417":3,"1418":1,"1427":2,"1431":1,"1436":45,"1496":1,"1957":1,"2074":1,"2170":1,"2180":1}}],["delegation",{"3":{"0":1,"267":1,"640":1,"777":1,"825":2,"827":1,"833":2,"922":1,"1237":3,"1240":1,"1259":1,"1270":1,"1286":5,"1300":2,"1310":1,"1339":4,"1350":3,"1352":1,"1359":5,"1372":1,"1380":2,"1395":2,"1396":2,"1402":2,"1467":1}}],["delegations",{"3":{"0":1,"825":1,"829":1,"1286":1,"1395":1,"1396":2}}],["delegatescheduledjob",{"3":{"1199":2,"1207":1}}],["delegates",{"3":{"0":2,"19":1,"27":1,"53":1,"109":1,"318":2,"326":1,"331":1,"459":1,"513":1,"536":1,"545":1,"549":1,"640":1,"680":1,"682":1,"686":1,"774":1,"814":1,"827":2,"1050":1,"1220":1,"1229":4,"1237":9,"1243":1,"1244":1,"1247":17,"1259":5,"1261":1,"1270":1,"1271":1,"1273":1,"1278":1,"1286":13,"1290":1,"1298":1,"1300":8,"1301":1,"1307":1,"1309":4,"1310":3,"1311":8,"1312":2,"1323":4,"1324":10,"1339":4,"1348":2,"1350":17,"1355":1,"1359":27,"1369":1,"1380":11,"1395":19,"1396":7,"1402":5,"1415":7,"1416":7,"1417":13,"1421":1,"1427":3,"1431":1,"1436":29,"1438":2,"1488":1,"1653":1,"1820":1,"1946":1,"2000":1,"2167":1,"2196":1}}],["delegated",{"3":{"0":1,"295":1,"320":1,"827":3,"829":1,"1286":3,"1323":3,"1339":1,"1350":1,"1353":1,"1354":1,"1359":3,"1369":1,"1383":1,"1395":3,"1396":3,"1402":1,"1415":1,"1417":1,"1575":1,"1904":1,"1963":1,"1996":1}}],["delegate",{"3":{"0":2,"11":1,"15":1,"21":1,"27":2,"109":4,"115":1,"135":1,"214":1,"326":1,"459":2,"534":1,"536":2,"539":2,"540":1,"545":1,"591":1,"592":1,"595":1,"675":1,"676":1,"677":1,"684":1,"749":1,"751":1,"819":1,"881":2,"987":2,"988":4,"989":1,"990":3,"1203":2,"1205":1,"1207":2,"1212":1,"1223":1,"1229":9,"1234":1,"1239":2,"1247":11,"1252":1,"1259":3,"1268":2,"1270":16,"1286":19,"1300":9,"1301":1,"1310":8,"1311":17,"1324":45,"1327":2,"1339":16,"1350":5,"1353":1,"1359":19,"1372":1,"1380":4,"1383":1,"1395":58,"1396":16,"1402":4,"1415":2,"1416":11,"1417":3,"1430":1,"1432":1,"1436":50,"1438":1,"1441":2,"1488":1,"1496":1,"1664":1,"1665":1,"1670":1,"1685":1,"1805":1,"1815":1,"1821":1,"1824":1,"1825":1,"1840":2,"1881":2,"2047":1,"2136":2,"2149":1,"2151":1}}],["deletable",{"3":{"312":1,"889":3,"890":1,"891":2,"1237":2,"1270":1,"1276":1,"1281":1,"1286":8,"1362":1,"1369":1,"1396":2,"1415":1,"1851":1}}],["deleting",{"3":{"0":4,"39":1,"40":1,"59":1,"248":1,"434":1,"440":1,"444":1,"459":1,"461":1,"462":1,"528":1,"781":1,"847":1,"907":1,"965":1,"997":1,"1059":1,"1116":2,"1231":1,"1237":2,"1281":2,"1286":3,"1300":4,"1301":1,"1308":1,"1311":2,"1318":1,"1323":4,"1324":2,"1346":1,"1350":4,"1352":1,"1359":13,"1362":1,"1369":1,"1380":1,"1396":1,"1402":4,"1410":1,"1415":3,"1436":8,"1467":3,"1473":1,"1489":1,"1631":6,"1635":3,"1638":1,"1639":10,"1641":1,"1727":1,"1755":1,"1776":1,"1889":1,"1950":1,"2000":1,"2066":1,"2069":1,"2107":1,"2127":1,"2200":1}}],["deletions",{"3":{"461":1,"1120":1,"1359":1,"1396":1,"1402":1,"1415":1,"1496":1}}],["deletion",{"3":{"0":3,"24":1,"38":3,"39":1,"438":1,"444":2,"458":2,"459":1,"462":1,"530":1,"618":1,"724":2,"725":1,"790":1,"791":2,"794":1,"832":1,"888":1,"947":1,"948":1,"1140":1,"1231":1,"1232":1,"1237":4,"1275":1,"1286":2,"1300":4,"1311":1,"1323":4,"1350":1,"1354":1,"1359":12,"1395":2,"1396":5,"1402":4,"1410":1,"1411":4,"1415":17,"1436":5,"1453":1,"1469":1,"1483":1,"1488":1,"1496":4,"1567":2,"1577":1,"1631":2,"1632":4,"1634":2,"1637":1,"1638":9,"1641":1,"1755":1,"1776":2,"1810":1,"1923":1,"2061":2,"2097":1,"2127":1,"2167":1,"2203":1}}],["delete+anonymize",{"3":{"1591":1}}],["deleteoverridethatonlydeletesitself",{"3":{"1436":1}}],["deletewhileopen",{"3":{"1402":1}}],["deletewithconfirmationasync",{"2":{"649":1,"1395":1},"3":{"649":1,"1324":3,"1395":6}}],["deleteroomasync",{"3":{"1395":2}}],["deletequestionasync",{"3":{"1395":2}}],["deleteproduct",{"3":{"1436":1}}],["deleteproducthandlertests",{"3":{"1685":1}}],["deleteproducthandler",{"2":{"924":1,"927":1},"3":{"924":1,"927":1}}],["deletepollasync",{"3":{"1402":1}}],["deletepartnerasync",{"3":{"1395":2}}],["deleteitemasync",{"3":{"1395":1}}],["deletefailed",{"3":{"1323":3,"1324":2}}],["deleteforbidden",{"3":{"1323":2}}],["deletehandler",{"3":{"1311":3,"1359":1}}],["deletelivepollhandlertests",{"3":{"1199":1,"1207":2,"1438":1}}],["deletelivepollhandler",{"3":{"1199":3,"1203":1,"1207":2,"1270":2,"1396":3,"1402":4}}],["deletebookmarkasync",{"3":{"1396":2}}],["deleteblobifexistsasync",{"3":{"1323":1}}],["deleteblobinternalcommandhandlerbasetests",{"3":{"1199":1,"1207":4}}],["deleteblobinternalcommandhandlerbase",{"2":{"1313":1,"1318":1,"1411":1},"3":{"1199":4,"1203":1,"1207":2,"1286":3,"1313":1,"1318":4,"1323":6,"1357":1,"1359":6,"1411":1,"1415":6,"1496":1}}],["deletebatchsize",{"3":{"1301":3}}],["deletebyidasync",{"3":{"881":1,"1324":3,"1395":5}}],["deletebehaviortestdbcontext",{"3":{"1199":2,"1207":1}}],["deletebehaviorconventiontests",{"3":{"1083":3,"1085":1,"1199":2,"1207":10,"1286":1,"1436":31,"1489":1,"1526":1}}],["deletebehaviorconventiontestsbase",{"2":{"1083":1,"1281":1,"1431":1,"2064":1},"3":{"0":1,"1083":2,"1199":3,"1207":2,"1281":1,"1286":1,"1431":3,"1436":8,"2064":2}}],["deletebehavior",{"2":{"1083":2,"1281":1,"2064":2},"3":{"1083":3,"1207":2,"1281":2,"1286":5,"1362":1,"1369":3,"1436":8,"1489":1,"2064":2}}],["deletebehaviorsourceannotation",{"3":{"1286":1}}],["deletebehaviorsource",{"3":{"0":1,"1083":1,"1281":1,"1286":1,"1431":1,"1436":3,"2064":1}}],["deletecategoryasync",{"3":{"1395":1}}],["deletecategoryhandler",{"2":{"924":1},"3":{"924":1,"1270":2}}],["deleteconfirm",{"3":{"1324":1,"1395":5,"1402":1}}],["deleteconfirmationtests",{"3":{"1199":1,"1207":2}}],["deleteconfirmation",{"3":{"649":1,"1324":5,"1395":21,"1402":2,"1577":1}}],["deleteconferencecategoryhandlertests",{"3":{"1199":1,"1207":2,"1438":1}}],["deleteconferencecategoryhandler",{"3":{"1199":3,"1203":1,"1207":2,"1270":2,"1359":9}}],["deletechildren",{"2":{"37":1,"39":1,"43":1,"1168":1,"1343":1},"3":{"37":1,"39":2,"43":1,"1168":1,"1231":1,"1237":2,"1343":1,"1350":8,"1359":2,"1402":3,"1436":7,"1577":1,"1586":1,"1662":1,"1810":1}}],["deleteuserasync",{"3":{"1324":1}}],["deleteusercommand",{"3":{"988":1,"1199":5,"1203":1,"1207":2,"1323":6,"1410":2,"1415":6}}],["deleteuserhandlertests",{"3":{"1199":1,"1207":2}}],["deleteuserhandler",{"2":{"463":1},"3":{"444":1,"459":3,"463":3,"464":2,"465":2,"466":2,"1199":2,"1203":1,"1207":2,"1286":1,"1323":7,"1410":2,"1415":19,"1496":4,"1526":1,"1535":2,"1591":1,"1600":1,"2127":1}}],["deleteuserhandlerbasetests",{"3":{"497":1,"1199":1,"1207":4,"1323":1,"1438":2}}],["deleteuserhandlerbase",{"2":{"457":1,"459":1,"461":1,"465":1,"466":1,"724":1,"728":1,"1291":1,"1403":1,"1672":1},"3":{"0":1,"457":1,"459":6,"461":3,"465":6,"466":1,"724":3,"725":3,"728":1,"910":1,"1199":3,"1203":1,"1207":2,"1286":2,"1291":2,"1300":7,"1322":2,"1323":8,"1403":1,"1410":1,"1415":11,"1496":4,"1577":1,"1672":1,"2066":1}}],["deleteuser",{"2":{"1323":1},"3":{"444":1,"459":3,"463":2,"465":3,"466":1,"724":1,"910":1,"988":1,"1203":3,"1207":8,"1286":1,"1291":1,"1300":5,"1322":1,"1323":12,"1410":1,"1415":7,"1496":1,"1500":1,"1526":1,"1577":1,"2066":1}}],["deleteaccountasync",{"3":{"1415":1}}],["deleteactivityasync",{"3":{"1395":2}}],["deleteanswerasync",{"3":{"1395":6,"1396":8}}],["deleteall",{"2":{"1346":1},"3":{"1346":1,"1350":4}}],["deleteavatarblob",{"2":{"444":1,"1411":1},"3":{"444":2,"1203":3,"1207":8,"1318":1,"1323":2,"1410":1,"1411":1,"1415":6}}],["deleteavatarblobinternalcommandvalidator",{"2":{"1411":1},"3":{"1199":1,"1203":1,"1207":2,"1411":2,"1415":4}}],["deleteavatarblobinternalcommandhandlertests",{"3":{"1199":1,"1207":2}}],["deleteavatarblobinternalcommandhandler",{"2":{"1411":1,"2127":1},"3":{"1199":2,"1203":1,"1207":2,"1286":2,"1318":1,"1323":3,"1411":2,"1415":7,"2127":1}}],["deleteavatarblobinternalcommand",{"2":{"440":1,"444":1,"1410":1,"1411":1,"2127":1},"3":{"440":1,"444":2,"1199":10,"1203":1,"1207":2,"1323":2,"1410":2,"1411":2,"1415":20,"1496":1,"2127":1}}],["deleteasync",{"2":{"318":1,"326":1,"434":1,"1386":1},"3":{"318":1,"326":1,"434":1,"881":2,"1286":4,"1301":2,"1309":2,"1311":4,"1323":6,"1324":4,"1359":7,"1380":18,"1382":2,"1386":1,"1389":1,"1395":29,"1396":5,"1402":2,"1415":7,"1429":1,"1436":1,"1488":1,"1496":1,"2055":1,"2074":1,"2126":1}}],["deleteeventasync",{"3":{"1395":2}}],["deleteeventhandlertests",{"3":{"1199":1,"1207":2,"1350":2,"1359":2}}],["deleteeventhandler",{"3":{"1116":1,"1199":2,"1203":1,"1207":2,"1323":1,"1350":4,"1352":9,"1359":11,"1496":2}}],["deleteentitycommand",{"2":{"1265":1},"3":{"1199":45,"1203":1,"1207":2,"1260":1,"1265":6,"1270":6,"1311":3,"1359":5,"1380":8,"1396":6,"1402":3,"1436":1,"1922":1}}],["deleteentityhandlertests",{"3":{"1199":1,"1207":3,"1270":1}}],["deleteentityhandlerextensiontests",{"2":{"926":1},"3":{"926":1,"1199":1,"1207":1}}],["deleteentityhandler",{"2":{"920":1,"1265":1},"3":{"0":1,"920":1,"926":3,"1199":11,"1203":1,"1207":2,"1260":1,"1265":8,"1270":7,"1286":2,"1323":2,"1352":1,"1359":29,"1396":2,"1402":8,"1577":2}}],["deleteerror",{"3":{"265":1,"1324":2}}],["deletesponsorasync",{"3":{"1395":2}}],["deletespeakerasync",{"3":{"1395":2}}],["deletespeakercommand",{"3":{"1270":1}}],["deletesessionasync",{"3":{"1395":1}}],["deletesessionassetcommandvalidator",{"3":{"1199":1,"1203":1,"1207":2,"1359":3}}],["deletesessionassetcommand",{"3":{"1199":6,"1203":1,"1207":2,"1359":7,"1380":1}}],["deletesessionassethandlertests",{"3":{"1199":1,"1207":2}}],["deletesessionassethandler",{"3":{"1116":1,"1199":2,"1203":1,"1207":2,"1323":1,"1350":1,"1357":2,"1359":9,"1496":1}}],["deletesessionassetblobinternalcommandhandler",{"2":{"1203":1,"1323":1,"1415":1},"3":{"1199":2,"1203":1,"1207":2,"1286":2,"1318":1,"1323":3,"1357":2,"1359":6,"1415":1}}],["deletesessionassetblobinternalcommandhandlertests",{"2":{"1199":1,"1207":1},"3":{"1199":1,"1207":2}}],["deletesessionassetblobinternalcommandvalidator",{"2":{"1199":1,"1203":1},"3":{"1199":2,"1203":1,"1207":2,"1359":5}}],["deletesessionassetblobinternalcommand",{"2":{"1496":1},"3":{"536":1,"1116":1,"1199":10,"1203":1,"1207":2,"1323":2,"1357":3,"1359":15,"1496":1}}],["deletesessionassetblob",{"2":{"536":1,"1116":1,"1631":1},"3":{"0":1,"536":2,"1116":2,"1203":3,"1207":6,"1286":1,"1318":1,"1323":2,"1357":1,"1359":6,"1631":1}}],["deletesessioncommand",{"3":{"1270":1}}],["deletesessionhandlertests",{"3":{"1199":1,"1207":2,"1350":1,"1359":2}}],["deletesessionhandler",{"3":{"1116":1,"1199":3,"1203":1,"1207":2,"1270":2,"1323":1,"1359":10,"1496":1}}],["deletes",{"3":{"0":1,"39":2,"435":1,"458":1,"459":1,"490":1,"592":1,"690":1,"698":1,"733":1,"790":1,"792":2,"831":1,"881":2,"905":1,"996":1,"1061":1,"1082":2,"1086":1,"1116":1,"1168":1,"1229":1,"1270":4,"1275":1,"1281":1,"1283":1,"1285":1,"1286":6,"1294":1,"1300":7,"1301":9,"1323":7,"1324":4,"1343":1,"1346":1,"1350":7,"1357":1,"1359":13,"1369":4,"1380":1,"1395":13,"1396":17,"1402":9,"1415":7,"1417":2,"1422":1,"1436":13,"1438":1,"1445":1,"1455":2,"1460":4,"1463":1,"1465":3,"1467":3,"1472":1,"1475":3,"1489":1,"1496":1,"1545":1,"1577":1,"1630":1,"1631":5,"1632":6,"1635":4,"1639":9,"1640":1,"1664":1,"1682":1,"1707":1,"1747":1,"1749":2,"1765":1,"1845":1,"1936":1,"1968":1,"2034":1,"2063":1,"2126":2,"2127":1,"2202":1}}],["deletedvote",{"3":{"1402":1}}],["deletedbookmark",{"3":{"1396":2}}],["deletedbynamespecification",{"3":{"1199":2,"1207":1}}],["deletedby",{"2":{"37":1,"41":1},"3":{"37":1,"39":1,"41":1,"1231":1,"1237":4,"1286":1}}],["deletedon",{"2":{"37":1,"41":1},"3":{"37":1,"39":1,"41":1,"1231":2,"1237":5,"1274":1,"1286":2,"1415":1,"1664":1}}],["deleted",{"1":{"456":1,"457":1,"458":1,"459":1,"460":1,"461":1,"462":1,"463":1,"464":1,"465":1,"466":1},"2":{"781":1,"785":1,"1082":1,"1355":1},"3":{"0":15,"39":3,"41":2,"62":1,"109":1,"135":1,"264":1,"308":1,"441":2,"456":1,"457":1,"458":1,"459":11,"461":4,"463":2,"465":3,"468":1,"503":1,"529":1,"538":1,"545":1,"551":1,"562":1,"607":1,"690":1,"698":2,"707":1,"715":1,"716":2,"717":1,"721":1,"748":1,"749":2,"781":1,"782":6,"784":1,"785":1,"790":1,"832":1,"833":1,"838":1,"848":1,"881":1,"888":1,"889":3,"890":4,"891":3,"905":2,"910":1,"926":1,"945":1,"966":1,"972":1,"1033":1,"1034":1,"1082":1,"1116":1,"1168":2,"1192":1,"1212":5,"1229":1,"1231":7,"1232":1,"1233":1,"1237":17,"1240":1,"1247":4,"1249":1,"1259":4,"1270":1,"1274":1,"1276":1,"1277":1,"1281":1,"1283":2,"1286":43,"1287":1,"1289":3,"1300":15,"1301":1,"1304":1,"1309":1,"1311":10,"1323":14,"1342":1,"1343":3,"1345":2,"1346":3,"1347":1,"1350":58,"1352":2,"1355":1,"1357":1,"1358":1,"1359":87,"1362":1,"1369":9,"1380":1,"1395":1,"1396":54,"1398":3,"1402":33,"1406":1,"1409":1,"1410":1,"1415":23,"1417":1,"1436":19,"1438":3,"1442":1,"1451":1,"1456":2,"1467":1,"1471":1,"1473":3,"1475":2,"1479":1,"1486":1,"1490":1,"1496":19,"1611":1,"1630":3,"1631":12,"1632":9,"1633":4,"1634":25,"1635":4,"1636":1,"1637":1,"1638":15,"1639":34,"1640":6,"1641":2,"1653":1,"1664":1,"1667":3,"1672":2,"1675":2,"1684":1,"1685":2,"1698":1,"1704":1,"1719":1,"1728":1,"1747":1,"1748":6,"1749":1,"1755":1,"1764":2,"1765":3,"1767":1,"1769":12,"1776":2,"1810":1,"1825":1,"1839":1,"1851":2,"1891":1,"1898":1,"1923":1,"1984":1,"2045":1,"2061":4,"2066":1,"2098":1,"2126":1,"2127":1,"2158":1,"2164":1,"2230":1,"2232":3}}],["delete",{"0":{"1386":1,"2064":1},"1":{"36":1,"37":1,"38":1,"39":1,"40":1,"41":1,"42":1,"43":1,"886":1,"887":1,"888":1,"889":1,"890":1,"891":1,"892":1,"893":1,"919":1,"920":1,"921":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"1080":1,"1081":1,"1082":1,"1083":1,"1084":1,"1085":1,"1086":1,"1087":1,"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1956":1,"1957":1,"1958":1,"1959":1,"2061":1,"2062":1,"2063":1,"2064":1,"2065":1,"2066":1,"2067":1,"2068":1,"2069":1},"2":{"38":1,"40":1,"43":1,"207":1,"458":1,"718":1,"719":1,"1082":1,"1140":1,"1478":1,"1687":1,"1746":1,"1936":1,"2034":1,"2061":1,"2064":1,"2203":1},"3":{"0":26,"36":1,"37":1,"38":5,"39":5,"40":2,"41":2,"43":2,"59":1,"157":1,"207":2,"243":1,"245":1,"257":1,"265":1,"291":1,"305":1,"313":1,"318":2,"326":1,"328":1,"329":1,"341":1,"346":1,"358":1,"361":1,"362":2,"366":1,"434":4,"435":2,"440":2,"458":3,"459":4,"461":1,"462":2,"463":1,"464":1,"466":1,"497":1,"499":1,"534":1,"549":1,"550":1,"609":1,"610":1,"611":1,"633":1,"640":1,"642":1,"690":1,"698":3,"699":2,"700":1,"707":1,"710":1,"713":1,"714":1,"715":1,"716":2,"718":1,"719":1,"721":1,"723":1,"725":1,"733":3,"774":1,"782":2,"790":1,"791":1,"853":1,"886":1,"888":3,"889":3,"890":2,"891":1,"893":1,"905":1,"907":1,"910":1,"919":1,"920":1,"921":1,"922":3,"924":2,"925":1,"926":5,"950":1,"996":1,"1033":1,"1059":1,"1061":1,"1063":1,"1069":1,"1080":1,"1081":1,"1082":6,"1083":3,"1084":3,"1085":2,"1086":2,"1087":1,"1116":8,"1117":1,"1118":2,"1120":2,"1140":1,"1141":1,"1142":1,"1146":1,"1147":1,"1168":4,"1170":1,"1174":2,"1175":5,"1180":1,"1192":1,"1201":2,"1202":1,"1203":8,"1207":18,"1212":9,"1213":1,"1230":2,"1231":9,"1232":2,"1236":2,"1237":37,"1240":1,"1244":1,"1247":7,"1259":2,"1260":2,"1265":5,"1270":27,"1272":1,"1273":1,"1274":2,"1275":1,"1276":1,"1277":1,"1278":2,"1280":2,"1281":5,"1286":93,"1290":1,"1291":3,"1300":35,"1301":13,"1306":1,"1308":3,"1309":8,"1311":18,"1316":1,"1318":2,"1323":44,"1324":20,"1339":4,"1340":1,"1342":5,"1343":6,"1344":1,"1345":2,"1346":4,"1350":97,"1352":2,"1353":1,"1354":3,"1355":3,"1357":4,"1359":192,"1361":1,"1362":3,"1363":1,"1369":25,"1371":4,"1373":1,"1375":1,"1380":25,"1383":1,"1386":1,"1388":2,"1392":1,"1395":95,"1396":101,"1398":3,"1402":63,"1405":7,"1406":2,"1410":2,"1411":1,"1412":2,"1414":1,"1415":69,"1417":6,"1431":3,"1436":149,"1438":20,"1446":1,"1453":2,"1455":1,"1465":3,"1467":3,"1469":1,"1473":1,"1474":2,"1475":1,"1478":4,"1487":2,"1488":1,"1489":2,"1490":3,"1496":4,"1526":4,"1535":1,"1545":2,"1567":4,"1570":1,"1572":1,"1577":7,"1586":2,"1591":1,"1607":1,"1608":1,"1611":1,"1612":1,"1614":1,"1618":2,"1630":3,"1631":8,"1632":40,"1633":2,"1635":9,"1637":1,"1638":6,"1639":14,"1640":3,"1641":1,"1643":1,"1644":1,"1651":5,"1657":1,"1662":1,"1664":1,"1666":2,"1667":1,"1668":1,"1669":1,"1671":3,"1672":2,"1684":1,"1685":5,"1686":7,"1687":2,"1689":1,"1699":1,"1700":1,"1701":1,"1710":1,"1717":1,"1720":3,"1727":4,"1729":1,"1741":1,"1742":4,"1746":13,"1747":6,"1748":17,"1749":1,"1750":2,"1751":1,"1765":4,"1768":2,"1769":16,"1779":2,"1784":1,"1795":2,"1796":1,"1799":1,"1810":6,"1812":1,"1815":1,"1839":1,"1851":2,"1909":1,"1912":1,"1915":1,"1916":1,"1922":2,"1923":1,"1936":1,"1951":1,"1964":1,"1986":1,"1987":1,"1991":1,"2034":1,"2047":1,"2060":1,"2061":9,"2062":1,"2063":7,"2064":6,"2066":2,"2068":1,"2069":7,"2089":1,"2090":1,"2098":1,"2126":1,"2138":1,"2164":1,"2166":1,"2167":1,"2168":1,"2186":1,"2202":2,"2203":1,"2208":1,"2210":1,"2221":1,"2230":2,"2232":7}}],["delimiting",{"3":{"1089":1,"1093":1,"2179":1}}],["delimiters",{"3":{"1019":1,"1324":1}}],["delimiter",{"3":{"1018":1,"1019":1,"1020":1,"1301":1,"1324":1,"1366":1,"1455":1}}],["delimited",{"3":{"0":1,"1017":1,"1018":2,"1090":1,"1212":1,"1339":1,"1366":2,"1369":1,"1435":1,"1438":1,"1492":1,"1496":1,"1526":1}}],["delimit",{"3":{"1018":1}}],["deliberateness",{"3":{"1436":1}}],["deliberate",{"0":{"1497":1,"1534":1,"1585":1,"1599":1},"3":{"0":5,"42":1,"53":1,"61":1,"81":1,"85":1,"122":2,"126":1,"135":1,"136":1,"149":1,"157":1,"175":2,"178":1,"189":1,"201":1,"230":1,"237":1,"255":1,"264":1,"265":1,"267":1,"302":1,"359":1,"361":1,"365":1,"372":1,"374":1,"382":1,"388":1,"391":1,"408":1,"446":1,"461":1,"469":1,"473":1,"536":1,"545":1,"566":1,"585":1,"607":1,"609":1,"628":1,"629":1,"661":1,"715":1,"725":1,"735":1,"743":1,"751":1,"764":1,"766":2,"767":1,"776":1,"785":1,"790":1,"792":2,"793":1,"802":1,"821":1,"825":1,"827":1,"831":1,"832":1,"840":1,"872":1,"897":1,"906":1,"913":1,"922":1,"931":1,"932":1,"941":1,"988":1,"996":1,"998":1,"1018":1,"1042":1,"1043":1,"1044":1,"1052":1,"1061":1,"1077":1,"1082":1,"1116":1,"1126":1,"1135":1,"1162":1,"1211":1,"1212":2,"1229":3,"1230":1,"1236":1,"1237":11,"1239":1,"1242":1,"1244":1,"1247":8,"1254":1,"1257":2,"1259":13,"1263":3,"1264":1,"1265":1,"1267":1,"1270":8,"1271":4,"1276":1,"1286":46,"1298":1,"1300":19,"1301":3,"1304":1,"1307":1,"1309":4,"1310":1,"1311":19,"1312":6,"1316":1,"1322":1,"1323":21,"1324":28,"1327":1,"1332":1,"1333":1,"1337":1,"1339":28,"1341":1,"1344":1,"1347":1,"1350":6,"1352":2,"1353":1,"1358":1,"1359":47,"1361":1,"1369":4,"1372":1,"1378":1,"1380":7,"1383":1,"1387":1,"1394":1,"1395":26,"1396":31,"1399":1,"1402":9,"1414":1,"1415":13,"1416":3,"1417":15,"1421":1,"1427":1,"1431":1,"1434":1,"1436":52,"1438":2,"1441":3,"1443":1,"1447":1,"1453":2,"1455":3,"1456":1,"1457":1,"1467":6,"1473":1,"1474":1,"1483":1,"1487":2,"1489":2,"1491":1,"1492":1,"1495":1,"1496":2,"1500":1,"1504":2,"1526":3,"1530":1,"1531":4,"1532":2,"1534":3,"1545":1,"1547":1,"1555":1,"1569":1,"1575":1,"1577":4,"1581":1,"1582":2,"1583":1,"1584":1,"1585":1,"1586":1,"1589":1,"1591":5,"1596":3,"1597":3,"1599":2,"1638":2,"1640":1,"1641":1,"1653":1,"1654":2,"1708":1,"1722":1,"1735":1,"1805":1,"1815":1,"1821":3,"1827":1,"1833":1,"1840":1,"1861":1,"1867":1,"1876":1,"1898":1,"1902":1,"1909":1,"1915":1,"1918":1,"1920":1,"1923":2,"1933":1,"1940":1,"1948":1,"1949":1,"1953":1,"1963":1,"1977":1,"1984":1,"1989":1,"1994":1,"2000":2,"2001":2,"2009":1,"2012":1,"2013":1,"2019":2,"2024":1,"2027":1,"2030":1,"2032":1,"2041":1,"2043":1,"2064":3,"2065":1,"2071":1,"2075":1,"2081":1,"2098":1,"2111":1,"2117":1,"2127":1,"2132":1,"2137":1,"2142":1,"2143":1,"2155":1,"2159":1,"2166":1,"2167":1,"2177":1,"2192":2,"2198":1,"2232":2}}],["deliberately",{"0":{"1368":1,"1389":1,"1721":1,"1902":1},"3":{"0":35,"23":1,"24":2,"26":1,"31":1,"45":1,"55":1,"62":1,"66":1,"68":1,"92":1,"97":1,"100":1,"109":2,"122":1,"124":1,"125":1,"135":3,"136":2,"142":1,"143":1,"147":1,"149":1,"164":1,"173":1,"178":1,"188":1,"200":1,"207":1,"219":1,"230":2,"235":1,"237":1,"243":1,"245":1,"248":2,"256":1,"265":5,"267":1,"273":1,"283":1,"290":1,"291":1,"309":1,"312":1,"314":1,"318":1,"322":1,"328":1,"353":1,"355":1,"358":1,"359":1,"365":1,"369":1,"372":1,"373":1,"379":1,"401":1,"402":1,"403":1,"413":2,"435":1,"448":3,"455":1,"459":2,"470":1,"474":1,"482":1,"494":1,"500":1,"501":2,"509":1,"517":1,"530":1,"535":1,"545":2,"546":1,"547":1,"549":5,"550":2,"556":1,"558":2,"564":1,"572":2,"576":1,"583":1,"591":2,"592":1,"593":1,"599":1,"600":2,"602":1,"603":1,"607":1,"609":1,"618":2,"620":1,"625":1,"626":1,"632":1,"633":1,"641":1,"649":1,"657":1,"665":6,"672":3,"682":1,"690":2,"692":1,"698":2,"700":1,"703":1,"707":1,"715":1,"725":1,"733":1,"741":2,"749":1,"751":1,"753":1,"757":1,"765":1,"766":2,"774":1,"778":1,"782":2,"786":1,"790":3,"795":1,"800":1,"819":2,"820":1,"825":1,"826":1,"827":4,"834":1,"836":1,"837":1,"848":1,"854":1,"869":2,"873":1,"881":1,"883":1,"889":1,"897":2,"904":1,"905":4,"907":3,"914":1,"926":2,"933":1,"939":1,"947":1,"963":1,"964":1,"971":1,"972":1,"988":5,"995":1,"998":1,"999":1,"1005":1,"1012":1,"1018":1,"1020":1,"1034":1,"1041":1,"1042":2,"1046":1,"1050":2,"1051":1,"1053":1,"1059":1,"1066":1,"1067":1,"1069":1,"1083":1,"1093":1,"1099":1,"1118":1,"1124":3,"1133":1,"1212":8,"1213":1,"1215":1,"1217":1,"1222":1,"1228":2,"1229":4,"1231":2,"1232":1,"1233":1,"1234":1,"1235":1,"1237":10,"1239":1,"1240":1,"1242":1,"1244":1,"1247":13,"1252":2,"1253":1,"1254":1,"1259":14,"1261":1,"1263":1,"1265":1,"1267":2,"1269":1,"1270":25,"1271":1,"1273":4,"1275":1,"1276":1,"1278":2,"1279":2,"1281":1,"1285":1,"1286":78,"1289":3,"1290":3,"1291":2,"1292":1,"1293":1,"1294":2,"1298":1,"1300":42,"1301":11,"1304":3,"1305":1,"1306":1,"1308":1,"1309":18,"1310":3,"1311":53,"1312":3,"1314":1,"1315":2,"1317":2,"1318":3,"1319":1,"1320":1,"1322":1,"1323":37,"1324":58,"1325":1,"1328":2,"1330":2,"1332":1,"1335":1,"1336":2,"1337":1,"1338":1,"1339":28,"1340":1,"1342":3,"1343":3,"1349":1,"1350":21,"1353":2,"1355":2,"1357":1,"1359":87,"1362":2,"1363":1,"1364":1,"1365":1,"1366":1,"1369":17,"1372":1,"1374":1,"1375":1,"1377":2,"1378":1,"1380":10,"1383":1,"1389":1,"1391":1,"1394":1,"1395":29,"1396":69,"1397":1,"1398":1,"1399":2,"1400":1,"1402":27,"1404":1,"1405":2,"1406":2,"1408":1,"1409":1,"1410":2,"1412":2,"1413":1,"1414":1,"1415":33,"1416":8,"1417":36,"1421":1,"1422":1,"1423":1,"1424":1,"1425":1,"1427":4,"1429":1,"1430":3,"1431":3,"1433":3,"1434":1,"1435":2,"1436":142,"1438":16,"1441":3,"1442":1,"1443":4,"1445":1,"1446":1,"1447":2,"1448":1,"1453":3,"1454":1,"1455":7,"1458":1,"1459":1,"1460":2,"1465":3,"1467":16,"1472":1,"1473":1,"1474":2,"1475":2,"1476":1,"1477":2,"1478":1,"1481":1,"1486":3,"1487":1,"1488":2,"1489":8,"1490":1,"1491":1,"1492":7,"1496":3,"1507":2,"1512":1,"1526":7,"1530":1,"1531":1,"1532":1,"1534":1,"1537":1,"1550":1,"1556":1,"1577":6,"1585":1,"1586":1,"1589":1,"1591":2,"1596":1,"1602":1,"1630":1,"1631":1,"1632":1,"1639":1,"1640":1,"1641":2,"1652":1,"1653":1,"1654":1,"1655":1,"1661":2,"1665":1,"1670":2,"1684":1,"1686":2,"1689":1,"1692":1,"1699":1,"1701":1,"1707":1,"1723":1,"1727":2,"1729":1,"1731":1,"1735":1,"1748":1,"1749":2,"1761":1,"1764":2,"1765":4,"1766":1,"1771":1,"1792":1,"1795":1,"1797":1,"1799":1,"1806":1,"1810":1,"1815":3,"1823":1,"1833":1,"1838":1,"1839":1,"1841":1,"1843":1,"1845":1,"1849":1,"1852":1,"1855":1,"1856":1,"1858":1,"1860":1,"1873":1,"1876":1,"1880":1,"1891":1,"1897":1,"1898":1,"1902":1,"1909":1,"1911":1,"1916":1,"1917":1,"1918":1,"1922":1,"1923":2,"1924":1,"1927":2,"1929":1,"1933":1,"1934":1,"1935":1,"1942":1,"1952":1,"1968":1,"1969":1,"1970":1,"1982":2,"1984":1,"1990":1,"1994":2,"1995":2,"1998":1,"2001":3,"2002":1,"2004":1,"2007":1,"2010":1,"2024":1,"2029":1,"2030":1,"2034":1,"2044":3,"2047":1,"2050":1,"2054":1,"2057":1,"2059":1,"2063":1,"2064":1,"2067":3,"2068":1,"2075":1,"2077":1,"2082":1,"2085":2,"2089":1,"2098":1,"2107":1,"2113":1,"2119":1,"2120":1,"2126":1,"2138":1,"2141":1,"2142":1,"2145":1,"2151":1,"2156":2,"2157":1,"2158":1,"2163":1,"2164":1,"2167":2,"2171":1,"2177":1,"2180":1,"2183":1,"2184":1,"2186":1,"2188":1,"2191":1,"2192":1,"2198":1,"2200":1,"2228":1,"2232":1}}],["deliverorderhandler",{"2":{"1875":1},"3":{"1765":1,"1875":1}}],["deliverat",{"3":{"1396":6,"1417":2}}],["deliverable",{"3":{"0":1,"24":1,"528":1,"800":1,"811":1,"1259":1,"1766":1}}],["deliveries",{"3":{"536":1,"702":1,"789":2,"1258":1,"1259":1,"1309":1,"1577":1,"1748":2,"1749":1,"1765":1,"1776":1,"2164":2,"2166":2,"2169":1}}],["delivering",{"3":{"391":1,"1300":1,"1339":1,"1396":1,"2162":1,"2213":1,"2217":1,"2218":1,"2220":2}}],["delivers",{"3":{"96":1,"896":1,"926":1,"988":1,"1091":1,"1212":1,"1247":1,"1270":1,"1286":3,"1309":1,"1324":1,"1345":1,"1350":1,"1359":1,"1415":1,"1416":1,"1467":1,"1665":1,"2177":1}}],["deliver",{"2":{"1771":1},"3":{"18":1,"91":1,"224":1,"225":1,"1101":1,"1212":1,"1229":1,"1254":1,"1257":1,"1259":1,"1286":1,"1300":1,"1302":1,"1309":1,"1323":1,"1324":1,"1396":1,"1425":1,"1746":2,"1749":2,"1765":3,"1771":1,"1775":1,"1837":1,"1846":1,"1933":2,"1941":1,"2025":1,"2214":1,"2220":1}}],["deliveredstate",{"3":{"1767":1}}],["deliveredon",{"3":{"1764":1,"1769":1}}],["delivered",{"3":{"0":2,"26":2,"27":2,"91":1,"120":1,"193":1,"202":1,"370":1,"725":1,"726":1,"854":1,"955":1,"1116":1,"1140":1,"1237":1,"1249":1,"1253":1,"1254":1,"1255":1,"1259":9,"1270":1,"1285":1,"1286":6,"1304":1,"1309":2,"1317":1,"1323":5,"1324":1,"1359":1,"1396":2,"1417":1,"1436":2,"1438":1,"1467":2,"1631":1,"1637":1,"1651":1,"1657":1,"1686":1,"1748":2,"1749":4,"1751":1,"1755":1,"1759":1,"1764":3,"1765":6,"1766":2,"1767":2,"1768":3,"1769":1,"1776":2,"1840":1,"1842":1,"1845":1,"1890":1,"1933":1,"1940":1,"1941":1,"2091":1,"2156":1,"2167":1,"2195":1,"2203":1,"2220":1,"2230":1}}],["delivery",{"0":{"1253":1,"1255":1,"1837":1,"1841":1,"2166":1},"1":{"431":1,"432":1,"433":1,"434":1,"435":1,"436":1},"3":{"0":9,"18":1,"20":1,"24":3,"26":1,"40":1,"67":1,"68":1,"80":1,"135":1,"156":1,"194":1,"195":1,"199":1,"201":2,"224":1,"225":5,"226":1,"228":1,"230":5,"255":3,"380":1,"383":1,"431":1,"433":1,"434":1,"435":2,"436":1,"518":1,"536":2,"540":2,"541":1,"605":1,"611":1,"640":1,"694":1,"696":1,"698":1,"702":3,"726":1,"757":1,"759":1,"761":1,"789":1,"790":4,"791":1,"792":3,"795":1,"815":1,"818":2,"820":2,"845":1,"846":2,"847":1,"849":1,"856":1,"896":1,"901":1,"930":1,"931":1,"933":1,"957":1,"988":1,"989":1,"1011":2,"1012":2,"1100":1,"1140":1,"1142":1,"1143":1,"1144":1,"1147":1,"1212":2,"1237":5,"1247":1,"1252":3,"1253":2,"1254":1,"1255":1,"1256":2,"1257":2,"1258":1,"1259":51,"1269":1,"1270":3,"1274":1,"1285":2,"1286":10,"1302":1,"1304":3,"1307":1,"1309":33,"1311":3,"1313":1,"1316":1,"1320":1,"1323":14,"1324":2,"1350":4,"1355":3,"1359":10,"1380":1,"1396":19,"1397":1,"1402":5,"1413":2,"1415":10,"1417":10,"1436":5,"1437":1,"1438":5,"1455":1,"1460":1,"1467":1,"1492":1,"1508":1,"1526":1,"1547":1,"1554":1,"1566":1,"1572":1,"1577":1,"1641":3,"1660":1,"1665":1,"1669":1,"1749":1,"1750":1,"1755":1,"1759":1,"1763":2,"1764":2,"1765":2,"1767":4,"1768":1,"1769":1,"1770":1,"1771":1,"1776":3,"1790":3,"1837":1,"1838":1,"1841":1,"1842":3,"1843":1,"1845":1,"1846":1,"1889":1,"1890":1,"1892":3,"1910":1,"1912":3,"1922":1,"1923":1,"1932":2,"1933":8,"1934":1,"1936":1,"1940":5,"1941":3,"1943":2,"1944":1,"1949":2,"1950":1,"2020":1,"2027":1,"2032":1,"2065":1,"2091":1,"2164":1,"2165":1,"2166":3,"2167":1,"2168":1,"2169":3,"2182":1,"2189":1,"2212":1,"2213":1,"2220":2,"2225":1,"2232":2}}],["desde",{"3":{"1686":1}}],["desk",{"3":{"1396":4,"1630":1}}],["desktops",{"3":{"741":1}}],["desktop",{"0":{"1669":1},"3":{"618":1,"1324":13,"1385":1,"1395":12,"1396":1,"1416":2,"1417":7,"1436":3,"1438":1,"1480":1,"1559":2,"1648":1,"1669":1,"1675":1,"1682":1,"1693":1,"1712":2,"1713":2,"1718":2,"2072":2,"2073":3,"2079":1,"2119":1}}],["desired",{"3":{"1300":3,"1323":1,"1324":1,"1402":2,"1417":1,"1764":2,"1776":1}}],["desire",{"3":{"1145":1}}],["designs",{"3":{"1800":1,"1802":1}}],["designsqliteentityconfiguration",{"3":{"1199":1,"1207":1}}],["designsqliteentity",{"3":{"1199":3,"1207":1}}],["designated",{"3":{"1664":1}}],["designation",{"3":{"1629":2}}],["designalphaentityconfiguration",{"3":{"1199":1,"1207":1}}],["designalphaentity",{"3":{"1199":3,"1207":1}}],["designing",{"3":{"1396":2,"1811":1,"2213":1,"2217":1,"2218":1}}],["designtimesqlitedbcontextfactory",{"2":{"1722":1,"1727":1,"1728":1,"1731":1},"3":{"1722":1,"1727":1,"1728":1,"1731":1}}],["designtimesqlserverdbcontextfactory",{"3":{"1209":4,"1286":6,"1359":1,"1369":1,"1396":2,"1415":3,"1496":1}}],["designtimeconnection",{"3":{"1436":1}}],["designtime",{"3":{"1286":3}}],["designtimedatabaseinitializationtests",{"3":{"1199":1,"1207":2,"1438":2}}],["designtimedbcontexthelpertests",{"3":{"1199":1,"1207":10,"1286":2}}],["designtimedbcontexthelper",{"2":{"696":1,"698":1,"703":1,"707":1,"715":1,"1034":1,"1284":1},"3":{"696":2,"698":2,"703":3,"707":2,"710":1,"715":1,"905":2,"909":1,"1034":2,"1199":4,"1203":1,"1207":4,"1284":2,"1286":44,"1300":2,"1323":2,"1436":3}}],["designtimedbcontextoptions",{"2":{"0":1,"707":1,"905":1,"1284":1},"3":{"0":1,"707":2,"905":3,"907":1,"1199":3,"1203":1,"1207":2,"1284":2,"1286":14,"1496":1}}],["designpostgresqlentityconfiguration",{"3":{"1199":1,"1207":1}}],["designpostgresqlentity",{"3":{"1199":3,"1207":1}}],["designbetaentityconfiguration",{"3":{"1199":1,"1207":1}}],["designbetaentity",{"3":{"1199":3,"1207":1}}],["designed",{"3":{"139":1,"272":1,"319":1,"550":1,"611":1,"692":1,"1229":1,"1270":1,"1286":3,"1300":3,"1311":1,"1323":3,"1324":1,"1369":2,"1395":1,"1396":2,"1417":4,"1436":1,"1561":1,"1591":1,"1632":1,"1641":1,"1900":1,"1993":1,"2062":1,"2140":1,"2143":1,"2145":1,"2220":1,"2221":1}}],["designer",{"3":{"0":1,"564":1,"567":1,"1004":1,"1209":65,"1323":1,"1436":2,"1455":1,"1497":1}}],["design",{"0":{"1201":1,"1284":1,"1539":1,"1541":1,"1543":1,"1546":1,"1555":1,"1557":1,"1559":1,"1680":1},"1":{"83":1,"84":1,"85":1,"86":1,"87":1,"88":1,"1711":1,"1712":1,"1713":1,"1714":1,"1715":1,"1716":1,"1942":1,"1943":1,"1944":1,"1945":1,"1946":1,"1947":1,"1948":1,"1949":1,"1950":1},"2":{"1261":1,"1346":1,"1381":1,"1418":1},"3":{"0":10,"24":1,"27":1,"41":1,"59":1,"83":1,"99":1,"135":1,"141":1,"145":1,"159":1,"201":1,"209":1,"225":1,"230":1,"235":1,"244":1,"263":1,"267":1,"274":1,"282":1,"308":1,"311":1,"312":1,"318":1,"340":1,"350":1,"359":2,"365":1,"387":2,"399":2,"419":1,"435":1,"441":1,"451":2,"452":2,"453":1,"464":1,"465":1,"468":1,"470":1,"472":1,"490":1,"501":1,"585":1,"590":1,"607":1,"611":1,"618":1,"632":1,"633":1,"634":1,"640":1,"649":1,"650":1,"667":1,"698":2,"700":1,"703":2,"706":1,"707":5,"710":1,"727":1,"732":1,"733":1,"766":1,"792":1,"803":1,"809":1,"810":3,"811":2,"812":2,"818":1,"825":1,"827":2,"830":1,"840":1,"855":1,"867":1,"876":2,"882":1,"905":4,"907":4,"909":1,"914":1,"916":2,"926":1,"945":1,"950":1,"964":1,"966":1,"1000":1,"1020":1,"1033":2,"1034":2,"1036":1,"1043":1,"1053":1,"1059":2,"1063":1,"1074":1,"1084":1,"1107":1,"1110":1,"1175":1,"1177":1,"1182":1,"1183":1,"1193":1,"1202":1,"1203":5,"1207":8,"1212":1,"1213":1,"1214":1,"1216":6,"1218":2,"1222":1,"1225":1,"1226":1,"1228":1,"1229":19,"1230":1,"1231":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":20,"1239":2,"1240":1,"1241":1,"1245":1,"1247":15,"1248":1,"1249":1,"1252":2,"1253":1,"1259":14,"1261":1,"1263":1,"1264":1,"1266":2,"1270":27,"1271":11,"1272":1,"1273":4,"1278":1,"1282":1,"1284":4,"1286":92,"1288":1,"1300":46,"1301":5,"1302":1,"1309":20,"1310":12,"1311":62,"1312":9,"1314":1,"1317":1,"1320":1,"1322":1,"1323":27,"1324":95,"1328":1,"1333":1,"1335":1,"1337":2,"1339":22,"1340":1,"1343":1,"1346":1,"1347":1,"1350":59,"1353":1,"1356":1,"1358":1,"1359":156,"1369":2,"1370":1,"1376":1,"1377":1,"1380":36,"1381":1,"1382":1,"1389":3,"1395":51,"1396":103,"1397":1,"1398":1,"1402":31,"1403":1,"1405":1,"1406":3,"1407":1,"1408":1,"1415":39,"1416":6,"1417":29,"1418":1,"1427":3,"1429":2,"1431":2,"1436":97,"1438":1,"1447":1,"1453":1,"1455":6,"1460":2,"1467":1,"1471":1,"1473":1,"1474":1,"1478":1,"1481":1,"1488":3,"1489":1,"1491":1,"1492":1,"1493":3,"1496":5,"1499":5,"1500":1,"1524":1,"1526":8,"1530":1,"1531":1,"1532":2,"1534":3,"1535":4,"1537":1,"1538":1,"1546":1,"1551":1,"1557":1,"1559":1,"1562":2,"1564":1,"1568":1,"1572":1,"1574":1,"1576":1,"1577":9,"1581":2,"1582":5,"1583":2,"1585":5,"1586":2,"1591":6,"1595":1,"1596":1,"1597":2,"1599":3,"1600":3,"1602":1,"1630":2,"1638":2,"1639":1,"1640":1,"1641":1,"1651":1,"1652":2,"1653":1,"1664":1,"1667":1,"1674":1,"1675":1,"1684":1,"1689":2,"1697":2,"1698":1,"1711":1,"1714":1,"1719":3,"1727":2,"1749":1,"1779":1,"1784":1,"1789":1,"1796":7,"1797":1,"1800":5,"1812":1,"1816":1,"1827":2,"1829":1,"1842":1,"1846":1,"1851":1,"1852":1,"1853":2,"1854":1,"1885":2,"1896":1,"1903":1,"1911":2,"1920":2,"1923":4,"1924":1,"1929":2,"1930":1,"1933":1,"1935":1,"1942":1,"1943":1,"1947":1,"1950":1,"1953":2,"1959":1,"1962":1,"1967":1,"1968":1,"1969":1,"1970":1,"1971":1,"1972":1,"1978":1,"1981":1,"1984":1,"2000":1,"2024":1,"2034":1,"2037":1,"2048":2,"2049":1,"2052":2,"2056":1,"2066":1,"2068":1,"2071":1,"2079":3,"2080":2,"2085":1,"2086":1,"2096":1,"2098":2,"2101":1,"2106":1,"2114":2,"2116":1,"2117":1,"2127":2,"2129":1,"2131":2,"2133":1,"2142":1,"2146":1,"2150":1,"2159":1,"2162":1,"2164":1,"2165":1,"2171":1,"2172":1,"2182":1,"2185":1,"2193":1,"2195":1,"2198":1,"2203":2,"2208":1,"2212":1,"2213":2,"2217":1,"2218":1,"2219":3,"2220":2,"2226":1,"2230":1,"2232":2,"2239":3}}],["desynchronize",{"3":{"609":1,"1350":1,"1359":3,"1417":1}}],["deserve",{"3":{"842":1,"1300":1,"1348":1,"1489":1,"1804":1,"1953":1,"2140":1}}],["deserves",{"3":{"290":1,"784":1,"898":1,"1286":1,"1300":1,"1359":3,"1406":1,"1817":1,"1834":1}}],["deserializable",{"3":{"1311":1}}],["deserialization",{"3":{"255":1,"1229":2,"1237":1,"1259":1,"1286":1,"1311":1,"1324":1,"1342":1,"1344":1,"1350":1,"1358":1,"1359":2,"1396":2,"1436":1}}],["deserializing",{"3":{"1237":1,"1324":1,"1395":1,"1396":1,"1438":1}}],["deserializecommand",{"3":{"1286":1}}],["deserializer",{"3":{"1223":1,"1229":1,"1343":1,"1350":1}}],["deserializeevent",{"2":{"848":1,"849":1,"850":1},"3":{"848":1,"849":1,"850":1,"1259":2}}],["deserializes",{"3":{"109":1,"1042":1,"1227":1,"1229":2,"1237":1,"1249":1,"1259":3,"1286":1,"1300":3,"1309":1,"1311":1,"1324":2,"1350":1,"1359":5,"1365":1,"1366":1,"1369":4,"1396":3,"1415":1,"1416":1,"1417":5,"1436":13,"1887":1,"1946":1,"2186":1}}],["deserialize",{"2":{"1366":1},"3":{"78":1,"382":1,"732":1,"845":1,"1229":1,"1237":1,"1259":3,"1300":1,"1301":5,"1324":2,"1366":2,"1369":1,"1396":6,"1415":1,"1416":3,"1417":2,"1427":3,"1438":1,"1889":1,"1949":1}}],["deserialized",{"3":{"26":1,"784":1,"1033":1,"1229":1,"1237":1,"1249":1,"1252":1,"1253":1,"1270":1,"1286":1,"1301":1,"1324":3,"1359":1,"1369":1,"1395":1,"1396":6,"1415":2,"1416":2,"1436":1}}],["despiteframeworkevents",{"3":{"1436":1}}],["despite",{"3":{"135":1,"208":1,"1247":1,"1259":1,"1323":3,"1350":1,"1359":1,"1396":1,"1436":3,"1443":1,"1488":1,"1496":2,"1638":1}}],["descent",{"3":{"1436":1}}],["descended",{"3":{"2010":1}}],["descendant",{"3":{"1333":1,"1380":1}}],["descendants",{"3":{"402":3,"1333":1,"1339":4,"1436":1,"1443":3,"2010":1}}],["descend",{"3":{"1309":1}}],["descends",{"3":{"373":1,"1436":1}}],["descending",{"3":{"27":1,"330":1,"549":3,"1228":1,"1229":2,"1240":1,"1247":5,"1270":2,"1286":8,"1309":5,"1311":1,"1324":2,"1350":1,"1359":5,"1396":2,"1402":4,"1415":1,"1436":3,"1532":1,"1640":1,"1988":1}}],["desc",{"3":{"1242":1,"1247":4,"1286":1,"1324":3,"1415":3,"1597":2,"1598":2,"1640":1}}],["descripción",{"3":{"1685":2}}],["descriptive",{"3":{"1175":1,"1214":1,"1280":1,"1286":6,"1320":1,"1359":1,"1369":1,"1402":2,"1417":1,"1630":1,"1639":1,"1764":1,"1782":1,"2041":1,"2100":1,"2106":1}}],["descriptionquality",{"3":{"1369":1}}],["descriptionqualityscore",{"3":{"1350":1,"1359":1,"1369":1}}],["descriptionmaxlength",{"2":{"1344":1,"1353":1},"3":{"1344":1,"1350":9,"1353":1,"1359":10,"1369":2,"1384":1,"1395":2}}],["descriptions",{"3":{"448":2,"677":1,"839":1,"1311":2,"1417":1,"1637":1}}],["description",{"2":{"528":1,"1017":1,"1685":2},"3":{"0":1,"43":1,"57":1,"96":1,"115":1,"244":1,"245":1,"360":3,"425":1,"428":1,"466":1,"512":1,"528":1,"609":1,"626":1,"633":1,"684":1,"790":1,"792":1,"837":1,"839":2,"921":1,"1017":2,"1184":1,"1247":1,"1270":1,"1286":1,"1300":5,"1311":5,"1324":4,"1339":5,"1350":16,"1359":54,"1366":1,"1369":7,"1393":1,"1395":13,"1396":1,"1417":11,"1427":3,"1435":1,"1436":4,"1467":6,"1474":1,"1476":1,"1488":1,"1496":3,"1500":1,"1630":7,"1631":1,"1635":3,"1641":6,"1661":1,"1682":1,"1685":6,"1687":2,"1697":1,"1727":4,"1728":1,"1729":1,"1746":2,"1748":2,"1750":1,"1764":26,"1767":6,"1768":3,"1769":1,"1771":1,"1773":1,"1775":1,"2089":1,"2097":1,"2146":1,"2158":1,"2166":1}}],["descriptors",{"3":{"12":1,"448":1,"583":1,"651":1,"1018":1,"1236":1,"1310":12,"1315":1,"1323":4,"1324":1,"1339":3,"1350":4,"1359":10,"1368":1,"1369":1,"1417":1,"1421":1,"1424":1,"1427":2,"1436":2,"1437":1,"1526":1,"2175":1}}],["descriptor",{"3":{"11":1,"122":2,"448":1,"649":3,"650":1,"1230":1,"1236":1,"1247":1,"1262":1,"1286":9,"1300":2,"1309":3,"1310":26,"1311":2,"1316":1,"1323":8,"1324":11,"1350":7,"1359":45,"1378":1,"1380":1,"1395":6,"1396":8,"1402":8,"1404":1,"1412":1,"1414":2,"1415":8,"1416":2,"1417":1,"1421":2,"1427":4,"1436":4,"1666":1,"1669":1,"1865":1,"2131":1,"2133":1}}],["describable",{"3":{"1417":1}}],["describing",{"3":{"359":1,"360":1,"497":1,"518":1,"534":1,"545":1,"551":1,"638":1,"684":1,"691":1,"944":1,"1100":1,"1229":1,"1237":1,"1247":1,"1277":1,"1283":1,"1286":8,"1300":3,"1309":4,"1310":1,"1311":4,"1323":4,"1324":1,"1339":2,"1350":4,"1359":3,"1380":1,"1395":2,"1396":3,"1402":1,"1415":1,"1417":3,"1427":2,"1436":4,"1462":1,"1631":2,"1949":1,"2048":1,"2142":1,"2146":3}}],["describeprotofile",{"3":{"1436":2}}],["describemember",{"3":{"1436":3}}],["describemismatch",{"3":{"1427":5}}],["describefailure",{"3":{"1395":2,"1427":1}}],["describeseverycorepublicresource",{"3":{"1436":1}}],["describesize",{"3":{"1395":2}}],["describes",{"3":{"0":1,"15":1,"93":1,"135":2,"139":1,"170":1,"171":1,"178":1,"201":1,"252":1,"256":1,"257":1,"258":1,"296":1,"305":1,"325":2,"335":1,"373":1,"375":1,"395":1,"400":1,"422":1,"424":1,"513":1,"522":1,"539":1,"549":1,"572":1,"578":1,"642":1,"714":1,"715":1,"736":1,"769":1,"794":1,"813":1,"833":1,"836":1,"837":1,"839":2,"870":1,"874":1,"876":1,"939":1,"980":1,"999":1,"1006":1,"1018":1,"1021":1,"1050":1,"1085":1,"1119":1,"1176":1,"1178":1,"1229":2,"1237":3,"1247":3,"1259":2,"1270":2,"1271":1,"1273":1,"1286":14,"1300":2,"1309":2,"1310":2,"1311":3,"1323":3,"1324":2,"1339":6,"1350":3,"1359":5,"1395":3,"1396":3,"1402":6,"1415":6,"1431":1,"1436":11,"1458":1,"1467":1,"1474":1,"1476":1,"1477":1,"1481":1,"1488":1,"1496":1,"1526":2,"1591":1,"1639":1,"1652":1,"1692":1,"1727":1,"1839":2,"1844":1,"1896":1,"2024":1,"2043":1,"2051":1,"2055":1,"2101":1,"2232":1}}],["describedevice",{"3":{"1324":3}}],["described",{"3":{"0":1,"20":1,"22":1,"29":1,"66":1,"95":1,"135":1,"138":2,"141":1,"142":1,"161":1,"249":1,"254":1,"258":1,"268":1,"322":1,"336":1,"340":1,"368":1,"423":1,"451":1,"463":2,"474":1,"497":1,"512":1,"534":2,"543":1,"549":1,"562":1,"566":1,"572":1,"607":1,"624":1,"638":1,"764":1,"825":1,"837":2,"864":1,"867":1,"874":1,"881":1,"980":1,"986":1,"1174":1,"1191":1,"1229":1,"1230":1,"1237":2,"1238":1,"1247":2,"1259":2,"1269":1,"1270":9,"1271":2,"1278":1,"1284":1,"1286":10,"1297":1,"1300":3,"1301":1,"1308":1,"1309":3,"1310":1,"1311":5,"1315":1,"1316":1,"1322":1,"1323":4,"1324":8,"1339":4,"1342":1,"1350":3,"1352":2,"1359":18,"1369":6,"1380":8,"1395":13,"1396":8,"1402":2,"1415":3,"1416":3,"1417":2,"1436":13,"1443":1,"1447":1,"1455":2,"1458":1,"1459":1,"1465":1,"1467":6,"1475":1,"1476":1,"1487":1,"1492":1,"1496":3,"1500":1,"1526":1,"1591":1,"1639":1,"1653":1,"1688":1,"1689":1,"1702":1,"1706":1,"1707":1,"1755":1,"1769":1,"1777":1,"1865":1,"1883":1,"2048":1,"2106":1,"2232":1}}],["describe",{"2":{"1266":1},"3":{"0":1,"27":1,"93":1,"130":1,"138":1,"257":1,"279":1,"306":1,"326":1,"360":1,"375":1,"395":1,"421":1,"497":1,"506":1,"552":1,"685":1,"688":1,"751":1,"820":1,"825":1,"861":1,"863":1,"876":1,"912":1,"1089":1,"1091":1,"1123":1,"1230":1,"1237":1,"1244":1,"1247":1,"1259":1,"1260":1,"1266":1,"1270":7,"1274":1,"1275":1,"1277":1,"1285":1,"1286":9,"1300":11,"1301":2,"1309":1,"1311":1,"1339":2,"1359":4,"1380":1,"1395":1,"1396":1,"1415":1,"1417":6,"1426":1,"1427":1,"1436":5,"1489":1,"1571":1,"1601":1,"1639":1,"1697":2,"1701":1,"1723":1,"1729":1,"1815":1,"1868":1,"2176":1,"2182":1}}],["destruction",{"3":{"1416":1,"1810":1}}],["destructive",{"3":{"0":1,"293":1,"565":1,"566":3,"759":1,"907":1,"1237":1,"1286":1,"1300":1,"1324":6,"1415":1,"1436":1,"1455":1,"1526":1,"1561":1,"1570":1,"1590":1,"1591":1,"2064":1}}],["destructured",{"3":{"1324":1}}],["destructures",{"3":{"1300":1,"1359":1,"1395":3}}],["destructuring",{"3":{"1237":1,"1395":1,"2063":1}}],["destroyed",{"3":{"1359":1,"1436":4}}],["destroying",{"3":{"1300":1,"1416":1}}],["destroys",{"3":{"853":1,"1236":1,"1275":1,"1286":1,"1359":1,"1395":1,"1415":1,"1416":1,"1456":1,"2064":1}}],["destroy",{"3":{"790":1,"1259":1,"1300":1,"1323":2,"1467":1,"2166":1}}],["destinationheader",{"3":{"1436":1}}],["destinationtype",{"3":{"1300":1}}],["destinations",{"3":{"420":1,"810":1,"837":1,"838":1,"1144":1,"1339":2,"1395":1,"1417":2,"1436":1}}],["destination",{"3":{"0":7,"57":1,"825":1,"827":9,"829":4,"833":2,"837":2,"838":2,"839":3,"842":1,"1212":1,"1324":4,"1325":1,"1338":3,"1339":15,"1395":1,"1416":1,"1436":5,"1438":3,"1441":1,"1447":6,"1465":1,"1607":1,"1741":1,"1976":1,"2024":1}}],["a641126d",{"3":{"1475":1}}],["a85d00",{"3":{"1324":4}}],["a1b2c3d4",{"3":{"1286":1}}],["a11y",{"0":{"1558":1},"3":{"618":1,"619":1,"1216":1,"1436":7,"1438":1,"1488":1,"1490":1,"1491":1,"1493":1,"1499":1,"1526":2,"1530":1,"1532":1,"1533":1,"1534":1,"1558":1,"1565":1,"1577":2,"1586":1,"1591":1,"1595":2,"1596":1,"1597":1,"1598":1,"1605":1,"1739":1}}],["akka",{"2":{"1150":1},"3":{"1150":1}}],["aot",{"3":{"869":1,"1417":4}}],["awesomeassertions",{"2":{"960":1},"3":{"960":1,"1213":1,"1436":50,"1654":1,"2058":1}}],["awk",{"3":{"564":1,"1455":2}}],["awkwardly",{"3":{"1816":1,"1952":1}}],["awkward",{"3":{"6":1,"506":1,"1270":1,"1395":1,"1396":1,"1436":1,"1491":1,"1641":1,"1844":1,"1955":1,"2194":1}}],["aws",{"3":{"175":1}}],["awayfromzero",{"3":{"1600":1}}],["away",{"3":{"0":1,"24":1,"160":1,"499":1,"501":1,"558":1,"683":1,"691":1,"773":1,"812":1,"863":1,"881":1,"882":1,"897":2,"903":1,"905":1,"907":1,"948":1,"965":1,"1020":1,"1107":1,"1142":1,"1162":1,"1185":1,"1237":3,"1247":1,"1259":1,"1270":2,"1286":2,"1297":1,"1300":1,"1311":2,"1312":1,"1323":3,"1324":14,"1339":1,"1355":1,"1359":9,"1362":1,"1369":3,"1390":1,"1395":5,"1396":13,"1402":2,"1409":1,"1415":2,"1417":6,"1424":1,"1431":1,"1436":21,"1444":1,"1453":1,"1455":1,"1486":1,"1488":2,"1561":1,"1651":1,"1654":1,"1663":1,"1727":1,"1737":1,"1767":1,"1819":1,"1918":1,"1974":1,"1982":1,"1987":1,"2146":1,"2213":1}}],["awardable",{"3":{"1438":1,"1527":1,"1578":1,"1592":1}}],["awardasync",{"3":{"690":1,"1396":3,"1436":3}}],["awardcount",{"3":{"1396":1}}],["awardcall",{"3":{"1199":2,"1207":1}}],["awarded",{"3":{"1396":13,"1415":2,"1438":2,"1631":4,"1639":2}}],["awarder",{"3":{"1350":1,"1396":17,"1436":3,"1487":1}}],["awardermocks",{"3":{"1199":2,"1207":1}}],["awarding",{"3":{"691":1,"1415":1,"1436":1}}],["awards",{"3":{"0":1,"1350":2,"1359":1,"1396":13,"1438":1,"1490":1,"1631":5}}],["award",{"3":{"0":1,"688":1,"690":2,"691":3,"692":5,"693":1,"782":1,"1349":1,"1350":2,"1359":6,"1396":75,"1415":2,"1436":1,"1438":2,"1487":1,"1631":6,"1639":4}}],["awareness",{"3":{"27":1,"698":1,"1309":2,"1311":1,"1416":2,"1417":3,"1554":1,"1577":1,"1635":1,"2104":1,"2189":1}}],["aware",{"3":{"0":8,"86":2,"93":1,"94":1,"100":1,"136":1,"160":1,"164":1,"166":1,"265":1,"709":1,"819":1,"883":1,"920":1,"926":3,"1034":1,"1046":1,"1191":1,"1192":2,"1196":1,"1202":1,"1203":1,"1212":2,"1241":1,"1242":2,"1247":1,"1267":1,"1270":11,"1271":1,"1282":1,"1286":18,"1289":2,"1297":1,"1300":5,"1309":1,"1311":3,"1315":1,"1323":4,"1324":8,"1331":1,"1339":3,"1350":6,"1354":1,"1356":1,"1359":6,"1380":3,"1394":1,"1395":6,"1396":8,"1416":1,"1417":3,"1436":1,"1438":4,"1444":1,"1446":1,"1461":1,"1467":2,"1496":2,"1500":2,"1526":2,"1534":2,"1562":1,"1564":1,"1577":1,"1586":1,"1591":3,"1596":1,"1600":2,"1660":2,"1666":1,"1670":1,"1671":1,"1673":1,"1815":1,"1856":1,"1857":1,"1862":1,"1882":1,"1883":1,"2028":1,"2055":1}}],["awaiter",{"3":{"1324":1}}],["awaited",{"3":{"0":2,"235":1,"383":1,"435":1,"436":1,"854":1,"896":1,"897":1,"901":1,"1067":1,"1229":2,"1247":1,"1259":2,"1270":3,"1286":1,"1310":2,"1311":1,"1323":1,"1324":6,"1372":1,"1380":3,"1383":1,"1395":8,"1396":2,"1402":2,"1415":1,"1417":3,"1430":2,"1436":1,"1449":1,"1770":1,"1947":1,"2056":2}}],["awaitable",{"3":{"135":2,"1417":1,"1436":4}}],["awaiting",{"3":{"24":1,"514":1,"707":1,"809":1,"898":1,"1220":1,"1229":2,"1270":1,"1286":3,"1300":2,"1301":3,"1311":1,"1323":2,"1324":6,"1334":1,"1395":1,"1396":3,"1402":3,"1416":1,"1417":4,"1425":1,"1436":2,"1805":1,"1946":1}}],["await",{"2":{"488":1,"494":1,"495":1,"1954":1},"3":{"0":3,"109":1,"481":1,"483":1,"484":1,"486":4,"487":1,"488":1,"489":2,"490":4,"491":5,"492":5,"493":6,"494":6,"495":3,"519":1,"743":1,"996":2,"1101":1,"1212":1,"1220":1,"1229":4,"1247":3,"1259":5,"1263":1,"1270":5,"1274":1,"1286":1,"1300":3,"1301":1,"1309":2,"1311":10,"1312":1,"1323":5,"1324":21,"1328":1,"1339":3,"1359":15,"1369":1,"1380":2,"1383":1,"1395":29,"1396":13,"1400":1,"1402":4,"1415":7,"1417":5,"1425":1,"1427":1,"1436":17,"1438":1,"1805":1,"1946":1,"1954":1,"2134":1,"2177":1,"2232":2}}],["awaits",{"3":{"0":1,"109":1,"230":1,"295":1,"481":1,"482":1,"486":1,"490":1,"491":2,"492":2,"493":1,"494":1,"522":3,"523":4,"524":2,"897":1,"926":1,"988":1,"1100":2,"1212":2,"1220":1,"1229":2,"1246":1,"1247":2,"1259":6,"1263":1,"1270":9,"1286":2,"1300":5,"1301":1,"1309":11,"1310":2,"1311":6,"1323":3,"1324":14,"1339":2,"1359":6,"1369":1,"1378":1,"1380":4,"1391":1,"1395":24,"1396":10,"1399":1,"1402":3,"1415":5,"1416":2,"1417":10,"1427":1,"1436":6,"1805":1,"1836":1,"1919":1,"1946":1,"2056":1}}],["afraid",{"3":{"1853":1}}],["afford",{"3":{"1058":1,"1300":1,"1323":1,"1324":1,"1455":1,"1950":2}}],["affordances",{"3":{"1324":4,"1369":1,"1395":3,"1415":1,"1417":6,"1436":2,"1559":1,"1761":2,"2126":1}}],["affordance",{"3":{"109":1,"413":1,"439":1,"440":1,"681":1,"682":1,"685":1,"1286":1,"1300":1,"1323":1,"1324":9,"1350":1,"1359":2,"1369":1,"1375":1,"1380":3,"1384":1,"1391":1,"1392":1,"1395":14,"1402":2,"1415":1,"1416":1,"1417":32,"1436":6,"1438":1,"1577":1,"1714":1,"1742":1,"1765":1,"1898":1,"1904":1,"2118":1,"2126":1}}],["affordable",{"3":{"0":1,"365":1,"608":1,"627":1,"1286":1,"1311":1,"1359":1,"1396":2,"1436":1}}],["affecting",{"3":{"1455":1,"1481":1,"1531":1,"1534":1}}],["affects",{"3":{"905":2,"1267":1,"1270":1,"1286":3,"1300":1,"1311":1,"1339":2,"1350":1,"1427":1,"1675":1,"1872":1}}],["affect",{"3":{"682":1,"1264":1,"1359":1,"1395":1,"1415":1,"1417":1,"1455":1,"1476":1}}],["affected",{"3":{"185":1,"387":1,"448":1,"990":1,"1021":1,"1249":1,"1259":1,"1270":1,"1286":5,"1324":1,"1339":1,"1395":1,"1401":1,"1402":6,"1436":1,"1496":3,"1631":1,"1638":1,"1735":1,"2195":1}}],["affinity",{"3":{"664":3,"996":1,"1339":1,"1467":2}}],["afternoon",{"3":{"1973":1,"2049":1,"2067":1,"2171":1}}],["aftersimulateddataloss",{"3":{"1707":1}}],["afterthought",{"3":{"528":1,"1090":1,"1324":1,"1359":1,"1396":1,"1415":1,"1417":1,"1937":1,"2141":1,"2160":1}}],["aftercommit",{"2":{"465":1,"466":1},"3":{"459":2,"465":1,"466":2,"1323":6,"1415":1}}],["afterward",{"3":{"159":1,"1259":1,"1286":1,"1323":2,"1359":1,"1402":1,"1966":1}}],["afterwards",{"3":{"0":1,"24":1,"25":1,"122":1,"198":1,"216":1,"360":1,"538":1,"549":1,"689":1,"702":2,"765":1,"954":1,"988":1,"1237":3,"1243":1,"1259":2,"1270":3,"1275":1,"1286":7,"1290":1,"1300":4,"1309":1,"1311":2,"1316":1,"1323":6,"1324":9,"1339":1,"1346":1,"1350":2,"1359":6,"1369":1,"1380":1,"1392":1,"1395":7,"1396":5,"1402":3,"1417":2,"1429":1,"1436":6,"1463":1,"1478":1,"1488":1,"1662":1,"1686":1,"1689":1,"1727":2,"1824":1,"1840":1,"2142":1,"2163":1,"2167":1,"2198":1}}],["after",{"0":{"1610":1,"1744":1,"2119":1},"2":{"1704":1},"3":{"0":22,"16":1,"17":3,"18":2,"20":2,"21":2,"24":1,"25":1,"27":2,"33":1,"68":1,"78":1,"99":1,"112":1,"115":1,"117":1,"119":1,"120":2,"122":1,"124":1,"135":2,"145":1,"157":1,"159":1,"160":1,"175":1,"182":2,"186":2,"188":1,"194":1,"195":2,"196":2,"197":1,"199":1,"201":6,"202":2,"220":1,"225":1,"229":1,"235":1,"248":1,"255":1,"258":1,"265":2,"273":2,"279":2,"284":1,"295":2,"330":1,"341":2,"355":1,"365":1,"371":1,"374":1,"382":1,"383":2,"384":1,"388":1,"391":1,"392":1,"412":1,"413":2,"418":1,"424":1,"433":1,"434":2,"442":1,"444":1,"451":1,"452":2,"459":4,"463":2,"464":2,"465":1,"468":2,"494":1,"499":2,"507":1,"514":1,"517":1,"518":2,"522":1,"524":1,"528":1,"530":1,"536":3,"537":2,"538":2,"539":3,"541":1,"543":1,"547":1,"549":1,"550":2,"556":1,"558":1,"565":1,"566":1,"572":1,"583":1,"584":1,"587":1,"590":1,"595":1,"609":2,"612":1,"624":1,"626":2,"628":1,"638":2,"639":1,"640":1,"649":1,"665":2,"672":2,"674":1,"675":1,"681":1,"682":1,"686":1,"692":1,"693":1,"696":1,"698":2,"703":1,"707":2,"714":1,"715":1,"716":1,"741":3,"748":1,"749":5,"751":2,"753":2,"756":1,"757":3,"758":1,"767":1,"774":1,"781":1,"782":1,"785":1,"791":1,"793":1,"794":1,"812":1,"813":1,"818":1,"821":1,"827":1,"828":1,"844":1,"846":1,"849":1,"857":2,"860":1,"861":2,"865":1,"874":1,"881":3,"889":1,"891":1,"897":2,"900":1,"904":2,"908":2,"909":1,"916":1,"922":1,"924":1,"953":1,"954":2,"956":1,"960":1,"966":1,"988":8,"989":1,"990":1,"991":2,"998":2,"1006":1,"1018":1,"1028":1,"1029":1,"1034":1,"1041":1,"1042":1,"1044":2,"1050":1,"1051":1,"1052":1,"1059":1,"1069":1,"1083":1,"1086":1,"1091":1,"1093":1,"1100":1,"1116":4,"1118":1,"1124":2,"1125":1,"1129":1,"1133":1,"1134":1,"1140":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":4,"1191":1,"1195":1,"1200":1,"1212":4,"1215":1,"1228":1,"1229":8,"1232":1,"1233":2,"1237":9,"1239":1,"1241":2,"1244":1,"1247":16,"1249":1,"1251":1,"1252":2,"1254":4,"1258":2,"1259":39,"1262":2,"1263":3,"1264":2,"1265":1,"1269":3,"1270":31,"1271":3,"1274":2,"1275":1,"1278":2,"1279":1,"1281":3,"1284":1,"1286":76,"1288":1,"1289":1,"1292":1,"1293":2,"1294":1,"1297":1,"1298":1,"1300":51,"1301":8,"1308":1,"1309":16,"1310":5,"1311":39,"1312":3,"1315":2,"1316":2,"1317":1,"1318":1,"1320":1,"1323":57,"1324":98,"1332":1,"1334":1,"1335":1,"1338":1,"1339":31,"1342":1,"1343":1,"1347":1,"1350":21,"1352":1,"1354":1,"1355":2,"1357":1,"1358":1,"1359":122,"1364":3,"1369":11,"1375":1,"1378":1,"1379":1,"1380":17,"1392":1,"1395":46,"1396":36,"1399":3,"1402":27,"1405":1,"1406":3,"1407":1,"1408":1,"1410":1,"1413":1,"1415":40,"1416":18,"1417":41,"1422":1,"1423":1,"1425":3,"1427":5,"1429":1,"1430":1,"1432":2,"1433":1,"1436":61,"1438":11,"1441":2,"1445":2,"1447":1,"1451":1,"1454":3,"1455":10,"1456":2,"1457":3,"1460":1,"1465":3,"1466":1,"1467":10,"1472":1,"1473":1,"1474":2,"1475":2,"1478":2,"1480":1,"1481":1,"1482":1,"1486":1,"1488":8,"1489":1,"1490":2,"1491":1,"1492":3,"1496":15,"1508":2,"1526":1,"1534":1,"1547":1,"1554":1,"1558":1,"1566":1,"1568":2,"1572":1,"1630":2,"1631":7,"1632":2,"1634":1,"1635":4,"1637":1,"1638":2,"1639":11,"1641":4,"1643":1,"1644":1,"1648":1,"1651":6,"1654":1,"1655":1,"1657":1,"1662":1,"1665":4,"1667":2,"1668":1,"1673":1,"1675":1,"1677":1,"1679":1,"1685":3,"1686":2,"1690":4,"1692":1,"1701":1,"1702":1,"1704":1,"1705":1,"1707":2,"1710":1,"1716":1,"1727":5,"1729":1,"1730":1,"1749":1,"1750":1,"1755":1,"1765":2,"1767":6,"1769":1,"1773":1,"1806":1,"1814":1,"1818":1,"1819":2,"1821":1,"1822":1,"1824":1,"1826":1,"1827":1,"1839":3,"1840":3,"1841":1,"1843":2,"1845":1,"1846":1,"1851":2,"1852":1,"1866":1,"1871":1,"1873":1,"1874":1,"1881":1,"1883":1,"1886":1,"1889":2,"1892":1,"1900":1,"1903":1,"1909":3,"1911":2,"1912":1,"1918":2,"1923":1,"1924":1,"1933":1,"1936":1,"1944":2,"1945":2,"1946":1,"1948":2,"1949":1,"1950":1,"1962":1,"1964":1,"1966":1,"1968":2,"1982":2,"1998":1,"2000":3,"2001":1,"2012":1,"2019":3,"2038":1,"2051":2,"2058":1,"2063":1,"2065":1,"2066":2,"2072":2,"2073":1,"2078":1,"2085":1,"2086":1,"2087":1,"2090":1,"2094":1,"2095":2,"2098":1,"2113":1,"2119":2,"2121":1,"2123":2,"2128":1,"2131":1,"2143":1,"2161":1,"2163":4,"2164":1,"2166":1,"2168":2,"2169":3,"2174":1,"2177":1,"2179":1,"2181":1,"2185":1,"2189":1,"2191":1,"2192":2,"2193":1,"2196":1,"2199":2,"2200":2,"2203":1,"2232":1}}],["avg",{"3":{"1467":1,"1764":1,"1767":1}}],["averse",{"3":{"1237":1}}],["averaged",{"3":{"1350":1,"1467":1}}],["averagerating",{"3":{"1350":1,"1359":1,"1764":1}}],["averages",{"3":{"875":1,"1350":2,"1395":1}}],["average",{"3":{"608":1,"609":1,"1324":1,"1350":3,"1359":1,"1395":2,"1455":1,"1467":4,"1474":2,"1492":1,"1764":1,"1765":2,"2158":1}}],["averaging",{"3":{"608":1,"609":1,"1467":1,"2158":2}}],["av0030",{"3":{"454":1,"1311":1}}],["av0024",{"3":{"448":1,"1311":1,"2131":1}}],["av0011",{"3":{"448":1,"1311":1,"2131":1}}],["avoidance",{"0":{"1328":1},"3":{"1310":1,"1359":2,"1396":2,"1635":1}}],["avoidable",{"3":{"1247":1,"1300":1,"1323":2,"1324":4,"1359":2,"1395":2,"1417":1}}],["avoiding",{"3":{"1041":1,"1309":1,"1311":2,"1339":1,"1359":3,"1395":1,"1396":1,"1402":1,"1417":1,"1436":1,"1441":1,"1447":1,"1453":1,"1630":1,"2183":1}}],["avoids",{"3":{"182":1,"209":1,"226":1,"266":1,"360":1,"508":1,"529":1,"547":1,"666":1,"741":1,"935":1,"1069":1,"1212":1,"1220":1,"1237":5,"1247":1,"1252":1,"1259":3,"1270":7,"1286":4,"1300":4,"1301":2,"1309":1,"1311":4,"1323":1,"1324":8,"1329":1,"1339":5,"1350":5,"1359":15,"1369":1,"1395":6,"1396":3,"1402":6,"1415":3,"1416":1,"1417":7,"1427":1,"1436":5,"1443":2,"1450":1,"1454":1,"1488":1,"1490":1,"1492":1,"1526":2,"1639":2,"1677":1,"1684":1,"1805":1,"2009":1}}],["avoid",{"3":{"3":1,"11":1,"55":1,"151":1,"272":1,"381":1,"415":1,"458":1,"460":1,"549":1,"556":1,"558":1,"559":1,"592":1,"792":1,"848":1,"924":1,"1005":1,"1229":1,"1244":1,"1247":3,"1248":1,"1270":2,"1286":9,"1300":8,"1307":1,"1309":1,"1311":1,"1323":1,"1324":2,"1339":1,"1350":1,"1359":2,"1380":1,"1396":3,"1402":6,"1415":1,"1416":1,"1417":1,"1427":1,"1456":1,"1467":1,"1476":1,"1526":1,"1556":1,"1560":1,"1565":1,"1611":1,"1639":2,"1640":1,"1761":1,"1792":1,"1864":1,"1954":1,"1958":1,"1959":1,"1981":1,"1989":1,"2013":1,"2035":1,"2050":1}}],["avoided",{"3":{"0":1,"472":1,"549":1,"691":1,"800":2,"941":1,"1237":2,"1270":1,"1286":1,"1324":3,"1359":1,"1369":1,"1395":1,"1396":2,"1402":1,"1416":1,"1417":1,"1815":1}}],["avaluethatisnotafixedpluralsentence",{"3":{"1436":2}}],["avatarblobname",{"3":{"1415":1}}],["avatarurl",{"2":{"2063":1},"3":{"1237":2,"1415":15,"1631":1,"2063":1}}],["avatar",{"2":{"1411":1},"3":{"0":3,"403":1,"438":3,"439":1,"440":3,"441":2,"443":2,"444":3,"459":1,"465":1,"466":2,"599":1,"665":1,"666":1,"725":1,"926":3,"1114":1,"1115":2,"1118":1,"1120":1,"1192":1,"1265":1,"1270":3,"1286":7,"1323":6,"1395":2,"1403":1,"1405":1,"1406":1,"1410":1,"1411":4,"1414":2,"1415":76,"1417":10,"1436":2,"1438":1,"1467":7,"1472":1,"1474":1,"1481":3,"1491":1,"1496":2,"1631":4,"1640":4,"1748":1,"2063":1,"2116":1,"2124":2,"2125":1,"2126":7,"2127":5,"2128":2}}],["avatarsize",{"3":{"1415":2}}],["avatars",{"0":{"1411":1},"1":{"437":1,"438":1,"439":1,"440":1,"441":1,"442":1,"443":1,"444":1},"3":{"0":2,"437":1,"440":2,"441":1,"443":3,"598":1,"665":1,"1116":2,"1118":1,"1212":1,"1286":2,"1318":1,"1395":2,"1415":2,"1417":3,"1467":7,"1496":2,"1631":2,"1636":1,"1640":1,"1765":1,"2126":4,"2127":2,"2128":1,"2232":2}}],["availability",{"3":{"0":3,"92":1,"236":2,"237":1,"283":1,"353":1,"402":1,"461":1,"530":1,"607":1,"609":6,"611":2,"681":1,"768":1,"793":1,"798":1,"914":1,"1187":1,"1300":2,"1324":5,"1332":1,"1334":1,"1339":6,"1395":1,"1402":1,"1416":2,"1417":5,"1436":1,"1443":1,"1447":2,"1455":1,"1467":13,"1469":1,"1474":2,"1476":1,"1630":1,"1639":1,"1672":1,"1677":1,"1707":1,"1708":1,"1709":1,"1776":1,"1911":1,"2001":1,"2010":1,"2067":1,"2122":1,"2158":1}}],["availabledestinationspolicy",{"3":{"1339":1}}],["available=false",{"3":{"1323":1}}],["availablequantity",{"2":{"1591":1,"1749":1},"3":{"1286":1,"1591":1,"1749":1,"1764":2}}],["available",{"2":{"727":1,"1299":1,"2067":1},"3":{"0":4,"7":1,"62":1,"111":1,"137":1,"159":1,"265":2,"280":1,"300":3,"303":1,"306":2,"361":1,"506":1,"527":1,"528":2,"537":1,"549":1,"556":1,"681":1,"691":1,"706":1,"725":3,"727":1,"741":1,"800":1,"818":1,"827":1,"840":2,"853":1,"914":1,"972":1,"1074":1,"1116":1,"1187":1,"1212":1,"1229":3,"1237":1,"1271":1,"1286":10,"1299":1,"1300":11,"1301":5,"1309":3,"1311":6,"1316":1,"1322":1,"1323":7,"1324":15,"1327":1,"1339":3,"1350":2,"1359":4,"1369":3,"1380":1,"1392":1,"1395":5,"1396":10,"1410":1,"1415":5,"1417":15,"1427":1,"1436":5,"1440":1,"1441":1,"1456":1,"1459":1,"1467":1,"1474":1,"1480":1,"1486":1,"1488":1,"1490":1,"1570":1,"1575":1,"1577":2,"1626":1,"1630":4,"1631":4,"1632":1,"1639":3,"1640":1,"1662":1,"1764":1,"1765":1,"1767":2,"1769":4,"1776":2,"1799":1,"1854":1,"1905":1,"1912":1,"1916":1,"2067":1,"2137":1,"2143":1,"2234":1}}],["azd",{"0":{"1464":1},"3":{"1464":8}}],["az",{"3":{"0":2,"563":1,"757":2,"759":1,"764":1,"766":3,"767":1,"768":2,"1116":1,"1119":1,"1212":1,"1441":1,"1445":1,"1455":2,"1465":2,"1467":5,"1470":1,"1472":5,"1473":1,"1474":7,"1475":6,"1476":1,"1478":5,"1481":2,"1482":1,"1484":2,"1577":2,"1582":1,"1583":1,"2034":3,"2036":1,"2224":4}}],["azurite",{"3":{"0":1,"440":1,"1323":3,"2126":1}}],["azuremessaging",{"3":{"1585":1}}],["azuremonitor",{"2":{"914":1},"3":{"914":1,"1438":1,"1443":1,"1446":1,"1467":3}}],["azureadtokenexchange",{"3":{"1472":1}}],["azureadonlyauthentication",{"2":{"599":1,"1477":1,"1534":1},"3":{"0":1,"599":1,"1467":1,"1477":1,"1526":2,"1534":1}}],["azurecontainerapps",{"3":{"1436":2}}],["azurecosmosdbdatabaseresource",{"2":{"1441":1},"3":{"1339":1,"1441":1}}],["azureclientidenv",{"2":{"603":1},"3":{"603":1}}],["azurekeyvaultconfigurationoptions",{"3":{"1339":2}}],["azurenotificationhubnativepushsender",{"2":{"1313":1,"1320":1,"1932":1,"1936":1},"3":{"1199":2,"1203":1,"1207":2,"1286":1,"1313":1,"1320":1,"1323":10,"1496":2,"1932":1,"1936":1}}],["azurenotificationhubdeviceregistrartests",{"3":{"1199":1,"1207":2,"1323":1,"1438":1}}],["azurenotificationhubdeviceregistrar",{"2":{"1313":1,"1320":1,"1932":1,"1936":1},"3":{"435":1,"1199":3,"1203":1,"1207":2,"1286":1,"1313":1,"1320":1,"1323":17,"1438":1,"1932":1,"1936":1}}],["azureservicebus",{"2":{"1842":1},"3":{"640":1,"1320":1,"1323":1,"1446":1,"1467":2,"1842":1}}],["azureservicebustransport",{"2":{"150":1},"3":{"150":1}}],["azureblobfilestorageservice",{"2":{"443":1,"1119":1,"1313":1,"1320":1,"2126":1},"3":{"443":2,"1116":2,"1119":2,"1199":2,"1203":1,"1207":2,"1286":2,"1313":1,"1320":1,"1323":13,"1496":1,"2126":2}}],["azure",{"0":{"1464":1,"1472":2},"1":{"1462":1,"1463":1,"1464":1,"1465":1,"1466":1,"1467":1,"1468":1,"1469":1,"1470":1,"2154":1,"2155":1,"2156":1,"2157":1,"2158":1,"2159":1,"2160":1},"2":{"145":1,"147":1,"149":1,"150":1,"152":1,"599":1,"601":1,"603":1,"665":1,"667":1,"669":1,"766":1,"810":1,"1074":1,"1075":2,"1077":1,"1078":1,"1339":2,"1462":1,"1463":1,"1464":1,"1468":3,"1470":2,"1472":2,"1475":1,"1477":1,"2178":1},"3":{"0":15,"31":1,"47":1,"59":1,"61":1,"62":1,"91":1,"97":1,"98":1,"145":4,"147":2,"149":1,"150":3,"152":1,"159":1,"175":1,"201":1,"234":2,"245":1,"259":1,"291":1,"359":1,"390":1,"395":1,"397":2,"401":1,"402":1,"422":1,"434":2,"439":1,"440":1,"517":1,"599":4,"601":1,"603":1,"609":1,"611":2,"624":1,"625":1,"626":2,"627":1,"638":1,"639":1,"640":5,"641":2,"642":1,"665":9,"666":2,"667":2,"669":1,"756":2,"757":1,"765":1,"766":2,"768":2,"790":1,"810":2,"818":1,"819":1,"821":1,"823":1,"826":1,"827":1,"838":1,"868":1,"913":1,"914":1,"972":3,"973":1,"974":2,"975":1,"976":1,"1020":1,"1033":1,"1043":1,"1068":1,"1074":1,"1075":4,"1077":1,"1078":1,"1091":1,"1101":1,"1192":1,"1202":1,"1203":1,"1211":1,"1212":6,"1213":5,"1257":1,"1259":3,"1285":1,"1286":5,"1302":1,"1309":1,"1311":4,"1313":1,"1318":1,"1320":1,"1323":33,"1325":1,"1327":1,"1332":1,"1333":4,"1335":4,"1336":1,"1339":48,"1359":1,"1369":1,"1379":1,"1415":1,"1436":13,"1437":1,"1438":4,"1440":1,"1441":4,"1442":8,"1443":7,"1444":2,"1446":8,"1447":2,"1450":2,"1451":1,"1455":20,"1457":3,"1458":2,"1460":6,"1461":1,"1462":4,"1463":3,"1464":10,"1465":5,"1467":38,"1468":3,"1470":3,"1471":2,"1472":29,"1473":3,"1474":6,"1475":12,"1476":2,"1477":1,"1482":1,"1483":3,"1484":3,"1487":1,"1488":1,"1489":2,"1490":1,"1496":1,"1526":2,"1534":1,"1570":1,"1575":1,"1577":3,"1582":1,"1585":1,"1589":1,"1591":1,"1596":1,"1597":1,"1663":2,"1665":1,"1668":2,"1669":1,"1670":3,"1673":1,"1677":1,"1679":1,"1707":2,"1721":1,"1752":1,"1779":1,"1785":1,"1790":1,"1792":1,"1842":2,"1843":1,"1936":2,"1970":2,"2006":1,"2008":2,"2010":3,"2011":1,"2013":2,"2014":1,"2020":1,"2025":2,"2033":1,"2034":1,"2109":1,"2110":1,"2113":1,"2123":1,"2124":1,"2126":1,"2128":1,"2142":1,"2153":2,"2154":3,"2156":2,"2157":1,"2158":3,"2160":3,"2169":1,"2178":1,"2209":1,"2212":1,"2213":2,"2214":1,"2215":1,"2217":1,"2218":1,"2219":2,"2220":4,"2221":2,"2224":6,"2228":1,"2229":1,"2232":5,"2239":1,"2240":1,"2241":2,"2243":1,"2244":1}}],["a+aa",{"3":{"0":1}}],["aab",{"3":{"1481":1}}],["aad",{"2":{"1470":2,"1477":2},"3":{"1455":2,"1470":2,"1472":1,"1477":6,"1591":1}}],["aadsts700213",{"3":{"1455":1,"1467":1}}],["aasa",{"3":{"1212":1,"1481":1}}],["aa8297fb",{"3":{"1174":1}}],["aaaaaaaab9e=",{"3":{"1300":1,"1395":1}}],["aaa",{"3":{"620":1,"1714":1}}],["aa",{"0":{"1643":1},"1":{"615":1,"616":1,"617":1,"618":1,"619":1,"620":1,"621":1,"622":1},"3":{"0":1,"615":1,"617":1,"618":3,"619":1,"651":1,"865":1,"906":1,"953":1,"1212":1,"1213":1,"1324":7,"1396":1,"1414":1,"1433":2,"1436":4,"1438":2,"1461":1,"1487":1,"1488":4,"1490":1,"1494":1,"1526":1,"1531":1,"1535":1,"1558":2,"1577":8,"1591":2,"1596":1,"1605":1,"1642":1,"1644":1,"1645":1,"1669":1,"1671":1,"1674":1,"1714":1,"1716":1,"1739":1,"2055":1,"2057":4,"2060":1,"2075":1,"2079":2,"2213":1,"2221":1,"2232":1}}],["aieval",{"3":{"1492":1}}],["aiapikey",{"3":{"1455":1,"1466":1,"1467":3,"1470":1}}],["aitool",{"3":{"1427":5}}],["aitelemetryexporttests",{"3":{"1199":1,"1207":2,"1435":3}}],["aitelemetryname",{"2":{"408":1},"3":{"408":1,"1333":1,"1339":4}}],["aireadydotnet",{"3":{"1395":1}}],["airtight",{"3":{"1221":1,"1292":1,"1359":1}}],["aijsonutilities",{"2":{"1091":1,"1427":1},"3":{"1091":1,"1427":2}}],["aiproviderselectiontests",{"3":{"1199":1,"1207":3,"1427":1,"1487":1}}],["aiprovider",{"3":{"1089":1,"1094":1,"1427":1,"1496":2}}],["aiprovidervalidator",{"2":{"676":1},"3":{"676":2,"677":1,"1091":4,"1093":1,"1199":2,"1203":1,"1207":2,"1420":2,"1421":1,"1427":22}}],["aiservicecollectionextensions",{"2":{"1427":1},"3":{"1199":1,"1203":1,"1207":1,"1421":1,"1427":12,"1496":2}}],["aiservicecollectionextensionstests",{"3":{"1199":1,"1207":2,"1427":1,"1487":1}}],["aisettingstests",{"3":{"1199":1,"1207":2,"1487":1}}],["aisettings",{"2":{"1095":1,"1096":1,"1419":1,"2173":1},"3":{"674":1,"676":3,"1091":5,"1095":2,"1096":1,"1199":15,"1203":1,"1207":2,"1419":8,"1420":1,"1427":46,"1496":3,"1526":2,"1577":1,"1585":1,"2173":2}}],["aiscoring",{"3":{"1359":1}}],["aiscoringservice",{"3":{"1350":2,"1359":1}}],["aiscoringtokenceiling",{"2":{"609":1,"1469":1},"3":{"609":1,"1018":1,"1467":3,"1469":1}}],["aiscorelookup",{"3":{"1395":4}}],["aiscores",{"3":{"1350":3,"1395":4}}],["aiscoreresponse",{"3":{"1018":1,"1021":1,"1199":3,"1203":1,"1207":2,"1366":5,"1369":10,"1526":2}}],["aids",{"3":{"1630":1}}],["aidependencyisolationtests",{"3":{"1199":2,"1207":4,"1427":3,"1436":13,"1526":2,"1577":2}}],["aidependencyisolationtestsbase",{"2":{"1091":1,"1096":1,"1435":1,"2178":1,"2180":1},"3":{"0":1,"1091":2,"1096":1,"1199":3,"1207":2,"1339":1,"1431":3,"1435":1,"1436":9,"2178":2,"2180":1}}],["aid",{"3":{"265":1,"1109":1,"1110":1,"1864":1}}],["aiming",{"3":{"1474":1}}],["aimed",{"0":{"2046":1},"3":{"997":1,"1093":1,"1369":2,"1396":1,"1424":1,"1436":1,"1450":1,"1467":1,"2044":1,"2162":1}}],["aim",{"3":{"91":1,"1134":1,"1981":1,"2048":1}}],["aiusagemeter",{"2":{"408":1,"2177":1,"2180":1},"3":{"0":1,"404":1,"408":3,"1089":1,"1091":2,"1199":7,"1203":1,"1207":2,"1418":1,"1425":3,"1427":26,"1496":3,"1526":1,"1577":1,"2177":3,"2180":1}}],["ai",{"0":{"408":1,"1347":1,"1359":1,"1366":1,"1435":1,"1553":1},"1":{"1015":1,"1016":1,"1017":1,"1018":1,"1019":1,"1020":1,"1021":1,"1022":1,"1088":1,"1089":1,"1090":1,"1091":1,"1092":1,"1093":1,"1094":1,"1095":1,"1096":1,"1418":1,"1419":1,"1420":1,"1421":1,"1422":1,"1423":1,"1424":1,"1425":1,"1426":1,"1427":1},"2":{"141":1,"142":3,"395":1,"404":1,"406":1,"408":1,"1012":1,"1016":1,"1017":1,"1020":1,"1089":4,"1090":2,"1091":2,"1093":1,"1211":2,"1213":3,"1366":1,"1418":4,"1425":5,"1427":3,"1503":4,"1527":1,"1578":1,"2006":1,"2172":1,"2177":6,"2178":3,"2180":2},"3":{"0":22,"74":4,"135":1,"141":1,"142":4,"395":3,"404":3,"406":1,"408":5,"517":1,"607":3,"609":8,"624":2,"626":3,"672":1,"674":2,"676":5,"755":1,"756":2,"764":1,"766":1,"980":7,"983":2,"994":1,"995":1,"996":1,"1011":1,"1012":5,"1013":1,"1015":1,"1016":3,"1017":7,"1018":16,"1019":1,"1020":2,"1021":1,"1022":1,"1088":1,"1089":12,"1090":6,"1091":46,"1092":1,"1093":6,"1094":3,"1095":1,"1096":1,"1099":1,"1108":2,"1111":1,"1112":1,"1192":3,"1195":1,"1198":2,"1199":69,"1202":3,"1203":36,"1206":5,"1207":208,"1208":8,"1211":6,"1212":4,"1213":5,"1216":1,"1270":3,"1286":1,"1333":5,"1339":9,"1340":1,"1342":1,"1347":2,"1350":14,"1351":1,"1354":1,"1359":29,"1360":3,"1362":1,"1364":3,"1365":1,"1366":11,"1367":1,"1368":5,"1369":26,"1376":1,"1380":1,"1390":2,"1395":18,"1417":1,"1418":7,"1419":5,"1420":6,"1421":3,"1422":5,"1423":5,"1424":6,"1425":13,"1426":1,"1427":177,"1428":2,"1431":3,"1435":11,"1436":18,"1437":9,"1438":5,"1442":1,"1455":14,"1459":1,"1461":2,"1463":4,"1466":3,"1467":22,"1469":2,"1470":1,"1485":1,"1486":8,"1487":7,"1488":1,"1489":1,"1492":9,"1493":1,"1496":26,"1499":1,"1503":5,"1505":1,"1517":1,"1519":1,"1526":26,"1527":4,"1534":1,"1535":1,"1536":1,"1553":2,"1570":1,"1577":10,"1578":3,"1585":2,"1586":2,"1591":1,"1592":1,"1599":1,"1625":1,"1632":2,"1640":1,"1673":1,"1781":3,"1784":1,"1796":1,"1799":2,"1947":1,"2006":4,"2013":1,"2111":2,"2156":1,"2160":1,"2170":1,"2171":1,"2172":2,"2173":2,"2174":3,"2177":6,"2178":3,"2179":2,"2180":5,"2190":1,"2213":3,"2214":1,"2216":1,"2218":2,"2219":1,"2220":4,"2221":2,"2224":4,"2225":1,"2227":5,"2228":1,"2232":3,"2238":1,"2239":6,"2240":2,"2243":1}}],["aesthetic",{"3":{"839":1}}],["aesgcm",{"2":{"359":1,"360":1,"2142":2},"3":{"359":3,"360":1,"1286":2,"2142":4}}],["aes",{"1":{"356":1,"357":1,"358":1,"359":1,"360":1,"361":1,"362":1,"363":1,"364":1,"365":1,"366":1,"2140":1,"2141":1,"2142":1,"2143":1,"2144":1,"2145":1,"2146":1},"3":{"0":3,"39":1,"356":1,"359":2,"360":1,"361":2,"365":1,"1212":1,"1236":1,"1237":1,"1284":2,"1286":3,"1575":1,"1577":2,"1664":1,"1672":1,"1903":2,"1905":2,"2063":1,"2139":1,"2140":2,"2142":2,"2143":1,"2145":1,"2146":2,"2153":1,"2208":1,"2232":1}}],["axecore",{"2":{"1453":1},"3":{"1213":1,"1436":2,"1488":1}}],["axerunoptions",{"2":{"618":1},"3":{"618":1,"1436":2}}],["axe",{"0":{"1643":1},"3":{"0":2,"617":2,"618":3,"620":3,"1212":1,"1213":1,"1324":1,"1414":1,"1415":1,"1433":2,"1434":3,"1436":14,"1438":5,"1461":1,"1486":1,"1487":1,"1488":5,"1490":1,"1492":2,"1524":1,"1526":6,"1531":3,"1532":1,"1535":2,"1558":1,"1565":1,"1577":11,"1585":1,"1586":1,"1589":2,"1591":3,"1596":5,"1600":2,"1605":2,"1608":1,"1642":1,"1643":2,"1644":1,"1645":2,"1660":1,"1669":1,"1671":1,"1674":1,"1716":1,"1739":2,"1742":1,"2055":1,"2057":4,"2060":1,"2079":2,"2213":1,"2221":1,"2232":1}}],["axeoptions",{"2":{"618":2,"1488":1,"2057":1},"3":{"0":1,"618":5,"620":2,"1199":17,"1207":2,"1212":1,"1428":1,"1433":6,"1436":18,"1488":7,"1493":1,"1577":1,"1591":2,"2057":2}}],["axes",{"0":{"1191":1,"1517":1,"1784":1,"1797":1,"1799":1,"1855":1},"3":{"0":1,"635":1,"697":1,"776":1,"1191":1,"1200":1,"1216":1,"1286":3,"1323":2,"1447":1,"1467":1,"1516":1,"1517":1,"1529":2,"1537":2,"1577":1,"1580":1,"1592":1,"1594":1,"1661":1,"1684":1,"1686":1,"1699":1,"1731":1,"1784":2,"1794":2,"1797":1,"1799":1,"1801":1,"1802":1,"1855":1,"2231":1}}],["axis",{"0":{"1851":1,"2231":1},"3":{"0":5,"45":2,"81":1,"164":2,"165":2,"166":2,"169":2,"317":1,"318":1,"447":3,"455":1,"582":1,"587":1,"633":1,"683":1,"697":1,"701":2,"721":1,"729":1,"743":1,"774":1,"775":1,"924":1,"1034":2,"1038":1,"1079":1,"1087":1,"1191":2,"1192":2,"1200":1,"1219":1,"1229":1,"1286":10,"1300":2,"1310":1,"1323":1,"1324":1,"1339":1,"1396":3,"1427":1,"1436":5,"1452":1,"1489":1,"1505":1,"1523":2,"1526":3,"1529":1,"1537":1,"1568":1,"1574":2,"1575":1,"1577":4,"1578":1,"1580":1,"1584":1,"1588":2,"1591":3,"1592":5,"1594":1,"1683":4,"1684":1,"1687":1,"1701":1,"1727":8,"1728":1,"1729":1,"1731":1,"1784":1,"1798":2,"1802":1,"1805":1,"1851":2,"1853":2,"1855":4,"1860":1,"1902":1,"1903":1,"1991":1,"1992":1,"1993":1,"1995":1,"1996":1,"1997":2,"2062":2,"2069":2,"2089":1,"2114":2,"2130":4,"2132":1,"2133":3,"2232":1}}],["augments",{"3":{"1339":1}}],["augment",{"3":{"1286":1}}],["augmented",{"3":{"1229":1,"2220":1}}],["august",{"3":{"804":1,"1049":1,"1051":1,"1054":1,"1074":1,"1467":1,"1474":1,"2220":1}}],["audible",{"3":{"1259":1}}],["audio",{"3":{"426":1,"1417":1}}],["audiences",{"3":{"227":1,"1309":1,"1348":1,"1380":1,"1395":4,"1396":2,"1402":1,"1935":1}}],["audience",{"2":{"2012":1},"3":{"0":2,"31":1,"86":2,"225":1,"226":1,"227":1,"303":1,"325":1,"378":1,"384":1,"386":4,"388":3,"391":2,"392":1,"434":1,"435":1,"499":2,"674":1,"820":1,"1124":1,"1202":1,"1203":1,"1300":4,"1307":1,"1309":11,"1311":6,"1323":4,"1324":10,"1348":1,"1350":7,"1359":4,"1372":2,"1373":1,"1379":1,"1380":28,"1391":1,"1393":1,"1395":36,"1396":2,"1397":1,"1399":1,"1402":2,"1406":3,"1415":13,"1416":1,"1436":3,"1438":2,"1472":1,"1488":2,"1496":2,"1576":1,"1630":1,"1637":1,"1922":3,"1933":1,"1940":1,"1982":2,"1985":1,"2012":1,"2136":1,"2138":1}}],["auditor",{"3":{"1844":1}}],["audit+coverage",{"3":{"1577":1}}],["audit+sbom+licence",{"3":{"1526":1}}],["auditactionsandgroups",{"3":{"1467":1}}],["auditableaggregaterootentity<",{"3":{"1286":1,"1369":1}}],["auditableaggregaterootentitytests",{"3":{"1199":1,"1207":4,"1237":1}}],["auditableaggregaterootentityadditionaltests",{"2":{"1237":1},"3":{"1199":1,"1207":7,"1237":3}}],["auditableaggregaterootentity",{"2":{"39":1,"43":1,"459":1,"470":1,"725":1,"1168":1,"1230":1,"1231":1,"1251":1,"1340":1,"1342":1,"1398":1,"1403":1,"1651":1,"1662":1,"1810":1,"1812":1,"1838":1,"2090":1,"2091":1,"2111":1},"3":{"0":1,"39":2,"43":2,"459":1,"470":2,"725":1,"1168":4,"1199":114,"1203":1,"1207":2,"1212":1,"1230":1,"1231":9,"1237":24,"1251":1,"1259":1,"1270":8,"1286":12,"1300":5,"1309":3,"1311":4,"1323":12,"1340":1,"1342":1,"1350":21,"1359":6,"1369":1,"1396":6,"1398":1,"1402":5,"1403":1,"1415":2,"1436":23,"1438":1,"1496":3,"1577":2,"1586":1,"1651":1,"1662":1,"1810":1,"1812":1,"1838":1,"2090":2,"2091":1,"2111":1}}],["auditablebaseentity<",{"3":{"1237":1}}],["auditablebaseentitytests",{"3":{"1199":1,"1207":3,"1237":1}}],["auditablebaseentityadditionaltests",{"3":{"1199":1,"1207":3,"1237":1}}],["auditablebaseentity",{"2":{"38":1,"346":1,"458":1,"470":1,"714":1,"1035":1,"1140":1,"1168":1,"1230":1,"1231":1,"1232":1,"1291":1,"1342":1,"1812":1,"1871":1,"1953":1,"2061":1,"2090":1},"3":{"0":1,"38":1,"39":3,"341":2,"346":1,"458":1,"470":2,"536":1,"714":1,"1035":1,"1140":2,"1168":2,"1199":152,"1203":1,"1207":2,"1212":1,"1230":1,"1231":9,"1232":1,"1237":34,"1239":1,"1247":11,"1270":3,"1286":30,"1291":1,"1300":2,"1310":4,"1311":6,"1323":3,"1342":1,"1350":15,"1359":2,"1402":2,"1415":1,"1436":18,"1438":1,"1577":1,"1651":2,"1662":1,"1810":1,"1812":1,"1815":1,"1871":1,"1953":1,"2061":1,"2090":1,"2164":1}}],["auditable",{"3":{"0":2,"41":1,"230":1,"341":4,"343":1,"345":1,"370":1,"470":1,"536":2,"628":1,"675":1,"715":1,"862":1,"1034":1,"1084":1,"1150":1,"1237":4,"1270":4,"1286":4,"1300":1,"1301":1,"1317":1,"1323":1,"1324":1,"1350":2,"1358":1,"1359":5,"1363":1,"1369":1,"1396":4,"1402":1,"1415":1,"1436":7,"1567":1,"1569":1,"1577":1,"1630":1,"1631":1,"1664":1,"1877":1,"1898":1,"2164":2,"2186":1}}],["auditevent",{"3":{"1465":1,"1467":1}}],["auditedthing",{"3":{"1199":4,"1207":1}}],["audited",{"3":{"230":1,"373":1,"441":1,"667":1,"713":1,"715":2,"716":1,"717":2,"1083":1,"1214":1,"1281":1,"1286":5,"1300":1,"1396":1,"1415":2,"1446":1,"1453":1,"1467":2,"1489":2,"1552":1,"1563":1,"1572":1,"1738":1,"1764":1,"1839":1,"1971":1,"2127":1}}],["audittrailtableenabled",{"3":{"1286":1}}],["audittrailtestharness",{"3":{"1199":4,"1207":10}}],["audittrailtestcontext",{"3":{"1198":1,"1199":5,"1207":1}}],["audittrailreadertests",{"3":{"1199":1,"1207":2,"1286":1,"1438":1}}],["audittrailreader",{"2":{"1277":1},"3":{"1199":4,"1203":1,"1207":2,"1269":1,"1270":6,"1277":2,"1286":21,"1317":1,"1323":3,"1438":1}}],["audittrailmodelgatetests",{"3":{"1191":1,"1199":1,"1207":4,"1286":4,"1496":1}}],["audittraildatabases",{"3":{"1436":2}}],["audittraildatabasenames",{"2":{"719":1},"3":{"719":1,"1436":2,"1467":1}}],["audittraildmlauditing",{"2":{"719":1},"3":{"719":1,"1467":1}}],["audittraildiagnostics",{"2":{"719":1},"3":{"719":1,"1467":1}}],["audittrailentrydtotests",{"3":{"1199":1,"1207":2}}],["audittrailentrydto",{"3":{"1199":4,"1203":1,"1207":2,"1270":1,"1286":3,"1317":1,"1323":4}}],["audittrailentry",{"2":{"720":1,"1232":1,"1277":1,"1839":1},"3":{"715":2,"720":1,"905":1,"1199":12,"1203":1,"1207":2,"1232":1,"1237":1,"1270":1,"1273":1,"1277":2,"1286":22,"1290":1,"1300":1,"1311":1,"1323":1,"1496":1,"1839":2}}],["audittrailentries",{"2":{"715":2,"719":1,"720":1,"1273":2,"1277":1,"1446":1,"1469":1},"3":{"0":3,"715":3,"719":3,"720":1,"1212":2,"1273":2,"1277":1,"1286":8,"1436":4,"1446":1,"1467":3,"1469":1}}],["audittrailsavechangesinterceptortests",{"3":{"1199":1,"1207":2,"1237":2,"1286":1,"1438":2}}],["audittrailsavechangesinterceptor",{"2":{"715":1,"1232":1,"1236":1,"1269":1,"1277":1,"1317":1,"1839":1},"3":{"715":2,"720":3,"1198":1,"1199":8,"1203":1,"1207":4,"1232":1,"1236":2,"1237":11,"1269":1,"1277":3,"1286":40,"1317":1,"1323":2,"1496":2,"1577":1,"1839":1}}],["audittrailsettings",{"2":{"721":1,"1272":1,"1277":1},"3":{"674":1,"715":1,"720":1,"721":1,"1199":11,"1203":1,"1207":2,"1237":5,"1272":1,"1277":2,"1286":20,"1317":1,"1320":4,"1323":2,"1577":1}}],["audittrailcleanupjobtests",{"3":{"1199":1,"1207":2,"1286":2,"1438":2}}],["audittrailcleanupjob",{"2":{"720":1,"1277":1},"3":{"707":1,"715":2,"720":1,"1082":1,"1086":1,"1199":4,"1203":1,"1207":2,"1270":3,"1277":2,"1286":32,"1323":2,"1436":3,"1442":3}}],["audittrail",{"2":{"1286":1},"3":{"170":1,"713":1,"715":3,"720":3,"1082":1,"1191":1,"1203":7,"1207":46,"1232":1,"1236":1,"1237":6,"1270":5,"1273":1,"1277":6,"1286":31,"1311":1,"1320":1,"1323":4,"1438":3,"1442":1,"1487":1,"1496":2,"1585":1}}],["audit=all",{"3":{"147":1,"1576":1,"1577":2,"1586":1}}],["auditsources",{"2":{"1649":1},"3":{"1526":1,"1591":1,"1649":1}}],["auditstampfidelitytests",{"3":{"1199":1,"1207":2}}],["audits",{"3":{"130":1,"139":1,"1459":1,"1467":1,"1531":1}}],["auditsavechangesinterceptortests",{"3":{"1199":1,"1207":6,"1286":2,"1436":1}}],["auditsavechangesinterceptor",{"2":{"39":1,"43":1,"343":1,"714":1,"715":1,"1201":1,"1231":1,"1274":1},"3":{"39":3,"43":1,"343":2,"345":1,"714":1,"715":2,"1082":1,"1086":1,"1175":1,"1198":1,"1199":55,"1201":1,"1203":1,"1207":2,"1231":4,"1237":5,"1274":2,"1286":14,"1323":1,"1436":3,"1498":1,"1577":1,"1810":1}}],["auditingsettings",{"3":{"1436":1,"1467":1}}],["auditing",{"3":{"0":3,"395":1,"403":1,"642":1,"713":2,"714":1,"718":2,"719":3,"720":1,"1203":1,"1207":4,"1286":1,"1323":2,"1350":1,"1359":1,"1396":1,"1436":2,"1467":6,"1469":1,"1496":1,"1569":1,"1591":1,"1978":1}}],["audit",{"0":{"1277":1,"1459":1,"1501":1},"1":{"367":1,"368":1,"369":1,"370":1,"371":1,"372":1,"373":1,"374":1,"375":1,"376":1,"712":1,"713":1,"714":1,"715":1,"716":1,"717":1,"718":1,"719":1,"720":1,"721":1,"1495":1,"1496":1,"1497":1,"1498":1,"1499":1,"1500":1,"1501":1},"3":{"0":8,"26":1,"38":1,"39":4,"40":3,"137":1,"139":1,"140":1,"159":1,"166":1,"170":1,"188":1,"195":1,"197":1,"200":1,"225":2,"230":1,"245":2,"249":1,"265":1,"283":1,"302":1,"304":1,"318":2,"320":1,"322":1,"336":1,"337":1,"341":2,"346":1,"352":1,"361":1,"367":1,"368":1,"369":1,"370":4,"371":5,"372":4,"373":1,"374":3,"375":3,"390":1,"403":1,"423":1,"424":1,"425":1,"426":1,"428":1,"430":1,"434":1,"450":1,"458":1,"461":1,"470":1,"486":1,"530":2,"531":2,"532":1,"545":1,"573":1,"574":1,"590":1,"591":1,"600":1,"601":1,"611":1,"625":1,"667":1,"692":1,"696":1,"698":2,"711":1,"712":1,"715":3,"716":4,"717":1,"718":1,"719":1,"727":1,"737":1,"782":1,"785":1,"820":1,"825":1,"829":1,"874":1,"905":1,"1006":1,"1033":1,"1034":3,"1041":1,"1082":2,"1083":1,"1085":2,"1086":1,"1140":2,"1141":1,"1142":1,"1145":2,"1163":1,"1168":2,"1174":1,"1192":3,"1194":1,"1201":3,"1202":1,"1203":1,"1212":9,"1213":1,"1216":1,"1230":2,"1231":3,"1236":2,"1237":22,"1239":2,"1241":1,"1247":6,"1253":2,"1259":6,"1270":2,"1272":1,"1273":2,"1274":2,"1275":3,"1276":1,"1278":1,"1280":1,"1281":1,"1284":1,"1286":61,"1290":1,"1300":13,"1303":1,"1304":4,"1306":1,"1309":11,"1311":4,"1322":1,"1323":12,"1342":2,"1350":9,"1359":11,"1361":1,"1363":1,"1369":4,"1379":1,"1380":3,"1395":1,"1396":21,"1402":2,"1405":1,"1410":2,"1412":1,"1415":15,"1423":1,"1427":1,"1433":1,"1436":28,"1438":5,"1446":2,"1452":1,"1453":7,"1454":3,"1455":2,"1459":20,"1460":2,"1461":4,"1465":6,"1467":10,"1495":2,"1496":13,"1512":1,"1525":1,"1526":13,"1531":5,"1532":1,"1534":1,"1535":2,"1545":2,"1548":2,"1554":1,"1567":2,"1569":2,"1577":11,"1582":2,"1585":3,"1586":2,"1591":14,"1596":3,"1597":1,"1600":4,"1634":1,"1641":1,"1649":1,"1651":2,"1653":1,"1657":1,"1660":1,"1662":1,"1664":3,"1672":4,"1673":1,"1675":1,"1677":1,"1685":1,"1686":1,"1698":1,"1704":1,"1717":1,"1719":1,"1720":2,"1748":1,"1749":1,"1780":1,"1794":1,"1810":2,"1812":1,"1815":2,"1838":1,"1839":4,"1844":2,"1851":1,"1923":1,"1933":7,"1934":1,"1935":1,"1940":2,"1941":1,"1964":1,"1995":1,"1996":1,"2012":1,"2049":1,"2052":1,"2061":2,"2063":3,"2068":2,"2069":4,"2090":1,"2091":1,"2132":1,"2138":1,"2140":1,"2145":2,"2183":1,"2186":1,"2221":1,"2227":1,"2230":3,"2232":2}}],["autonomously",{"3":{"1632":1}}],["autonomous",{"3":{"1632":1}}],["autonomy",{"3":{"46":1,"48":1,"1436":1,"1467":3,"1852":1}}],["autorotate",{"3":{"1417":1}}],["autoreplenishment",{"3":{"1339":2}}],["autoawesome",{"3":{"1395":1}}],["autoinclude",{"3":{"1359":1}}],["autoincrement",{"3":{"1323":1}}],["autoincrementing",{"3":{"1323":1}}],["autoorient",{"3":{"1323":1}}],["autodeleteonidle",{"3":{"1323":1,"1436":1}}],["autodetectchangesenabled",{"3":{"1286":2}}],["autocompletes",{"3":{"1324":2}}],["autocomplete",{"3":{"1311":2,"1395":1,"1640":1}}],["autocrlf",{"2":{"1426":1,"2176":1},"3":{"1091":1,"1426":1,"1427":1,"2176":1}}],["automitigate",{"3":{"609":2,"1018":1,"1467":4}}],["automatable",{"3":{"1436":1,"1657":1,"1704":1}}],["automates",{"3":{"1577":1,"1846":1}}],["automate",{"3":{"758":1,"762":1,"1481":1,"1530":1,"1531":1,"1532":1,"1576":1}}],["automated",{"0":{"1608":1,"1643":1,"1742":1},"3":{"132":1,"282":1,"293":1,"361":1,"372":1,"528":1,"641":1,"696":1,"700":1,"756":1,"759":1,"791":1,"792":1,"821":1,"1192":1,"1359":1,"1395":1,"1436":3,"1438":2,"1453":2,"1454":1,"1455":1,"1457":1,"1459":3,"1461":1,"1467":1,"1471":1,"1472":1,"1486":1,"1488":1,"1491":1,"1496":1,"1524":2,"1525":1,"1526":4,"1530":1,"1531":4,"1533":1,"1554":2,"1558":1,"1565":1,"1566":1,"1568":1,"1572":2,"1577":3,"1581":1,"1582":1,"1584":1,"1585":1,"1591":4,"1605":1,"1631":1,"1642":1,"1645":1,"1678":1,"1739":1,"1741":1,"1800":1,"1843":1,"2002":1,"2034":1,"2099":1,"2145":1,"2166":1,"2220":2,"2221":1}}],["automating",{"3":{"1526":1,"1576":1}}],["automation",{"0":{"1459":1},"3":{"483":1,"490":1,"620":1,"1452":2,"1475":1,"1487":1,"1548":1,"1570":1,"1576":1,"1605":2,"1642":1,"1644":1,"1680":1,"1739":2,"2036":1,"2060":1}}],["automatically",{"3":{"12":1,"122":1,"141":1,"235":1,"265":1,"283":1,"547":1,"698":1,"739":1,"883":1,"889":1,"1216":1,"1229":1,"1237":2,"1259":1,"1263":1,"1264":1,"1270":1,"1271":1,"1274":1,"1286":4,"1301":1,"1309":1,"1310":1,"1311":5,"1312":1,"1316":1,"1323":1,"1324":3,"1339":2,"1350":1,"1359":4,"1362":1,"1368":1,"1369":1,"1395":2,"1396":4,"1402":2,"1415":1,"1417":1,"1427":1,"1431":1,"1436":6,"1440":1,"1443":2,"1454":1,"1457":2,"1465":1,"1467":2,"1489":1,"1490":1,"1526":7,"1537":1,"1551":1,"1571":1,"1575":1,"1577":7,"1582":1,"1584":3,"1591":1,"1633":1,"1635":2,"1639":3,"1653":1,"1663":1,"1666":1,"1707":2,"1724":1,"1767":1,"1784":1,"1797":1,"1799":1,"1849":1,"1862":1,"1866":1,"1868":1,"1913":1,"1924":1,"1945":1,"2000":1,"2005":1,"2011":1,"2019":1,"2035":1,"2041":1,"2090":1,"2093":1,"2096":1,"2098":1,"2164":1}}],["automatic",{"1":{"754":1,"755":1,"756":1,"757":1,"758":1,"759":1,"760":1,"761":1,"762":1},"3":{"0":4,"135":1,"175":1,"312":1,"320":1,"361":1,"380":1,"659":1,"715":1,"734":1,"741":1,"754":1,"757":1,"819":1,"874":1,"889":1,"1034":1,"1212":1,"1236":1,"1237":3,"1247":1,"1259":1,"1270":3,"1281":1,"1286":8,"1301":2,"1309":1,"1311":3,"1322":1,"1323":4,"1324":7,"1339":1,"1349":1,"1350":1,"1359":2,"1400":1,"1415":5,"1436":4,"1438":1,"1455":3,"1459":1,"1525":1,"1526":1,"1577":2,"1592":2,"1630":1,"1641":7,"1664":1,"1665":1,"1668":1,"1670":1,"1765":1,"1838":1,"1843":1,"1845":1,"1907":1,"1945":1,"1996":1,"2048":1,"2142":1,"2145":1,"2221":1,"2232":1}}],["automapper",{"1":{"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1956":1,"1957":1,"1958":1,"1959":1},"3":{"0":1,"2":1,"3":1,"4":1,"1212":1,"1213":1,"1347":1,"1353":1,"1359":1,"1489":1,"1577":1,"1779":1,"1950":1,"1951":3,"1955":1,"1956":1,"1958":4,"1959":2,"2043":1,"2208":1,"2232":1}}],["autoverify",{"3":{"0":1,"418":1,"420":1,"423":1,"1416":1}}],["auto",{"0":{"1956":1,"2036":1},"2":{"99":1,"1447":1},"3":{"0":9,"53":1,"91":1,"99":1,"117":1,"166":1,"241":1,"243":1,"244":1,"253":1,"259":2,"290":1,"291":1,"293":1,"295":1,"342":1,"348":1,"350":1,"352":1,"353":1,"396":3,"398":2,"423":1,"440":1,"549":1,"640":1,"757":2,"759":1,"766":1,"767":1,"788":1,"790":3,"791":1,"793":1,"820":1,"1004":1,"1123":1,"1125":1,"1129":1,"1212":3,"1244":1,"1247":2,"1259":3,"1269":1,"1270":7,"1271":3,"1273":1,"1281":1,"1286":8,"1300":2,"1301":1,"1309":1,"1311":2,"1312":1,"1323":3,"1324":6,"1333":1,"1337":3,"1338":1,"1339":9,"1350":1,"1355":4,"1356":1,"1359":9,"1369":1,"1379":1,"1380":2,"1395":3,"1396":6,"1399":1,"1402":7,"1406":3,"1413":1,"1415":19,"1416":4,"1417":12,"1433":1,"1436":21,"1438":4,"1443":1,"1447":1,"1455":1,"1467":5,"1490":1,"1496":1,"1510":1,"1526":4,"1535":1,"1566":1,"1572":1,"1577":4,"1588":1,"1590":1,"1591":2,"1611":2,"1612":1,"1627":1,"1639":5,"1641":2,"1651":2,"1656":1,"1662":1,"1663":1,"1671":1,"1747":1,"1749":1,"1751":1,"1754":1,"1767":1,"1769":1,"1790":1,"1811":1,"1830":1,"1858":1,"1871":1,"1883":1,"1916":1,"1959":2,"1977":1,"2000":1,"2017":1,"2028":1,"2063":1,"2093":1,"2095":1,"2113":2,"2128":2,"2154":3,"2156":2,"2160":3,"2166":1,"2230":1,"2232":1}}],["authfailurespikealert",{"2":{"2158":1},"3":{"2158":1}}],["authresult",{"3":{"1641":1}}],["authresponse",{"3":{"1199":3,"1207":1}}],["authz",{"3":{"1526":3,"1535":1,"1548":1,"1577":2,"1591":1,"1611":2,"1616":1,"1617":1}}],["authn",{"3":{"1526":1,"1548":1,"1577":1}}],["authpagepath",{"3":{"1488":1}}],["authprovider",{"3":{"1436":3}}],["authpreferencestests",{"3":{"1199":1,"1207":3}}],["authunavailable",{"3":{"1436":1}}],["authuiservicetests",{"3":{"1199":1,"1207":2,"1324":6}}],["authuiservice",{"2":{"418":1,"2078":1},"3":{"243":2,"258":2,"259":1,"261":1,"418":1,"420":1,"423":1,"426":1,"428":1,"430":1,"434":2,"436":1,"905":2,"909":1,"1199":3,"1203":1,"1207":2,"1300":7,"1324":47,"1436":3,"1496":1,"2078":1}}],["auth=1",{"3":{"1434":1,"1436":1}}],["authgracetimeout",{"3":{"1433":1,"1436":2}}],["authtimeout",{"3":{"1433":1,"1436":2}}],["authtightpolicyname",{"3":{"1326":1,"1339":1,"1444":1}}],["authsucceededwithingraceasync",{"2":{"1433":1},"3":{"1433":1,"1436":1,"1488":1}}],["authservice",{"3":{"1415":1}}],["authsessionissuer",{"3":{"1199":6,"1203":1,"1207":4,"1287":1,"1290":23,"1300":32,"1496":4}}],["authstateprovider",{"2":{"1415":1},"3":{"1415":2,"1641":1}}],["authstate",{"3":{"1395":1,"1396":1,"1402":2}}],["authipratelimitpartition",{"3":{"1311":4}}],["authippermitlimit",{"2":{"178":1,"1599":1,"2001":1},"3":{"175":1,"178":1,"1311":5,"1599":1,"2001":1}}],["authmodelvalidationtests",{"3":{"1199":1,"1207":2,"1324":2}}],["authoutcomerulestests",{"3":{"1199":1,"1207":2,"1436":4,"1438":2}}],["authoutcomerules",{"3":{"1199":3,"1207":3,"1428":1,"1433":4,"1436":9,"1488":6}}],["authoutcome",{"3":{"1199":4,"1207":1,"1433":1,"1436":4,"1488":1}}],["authoruserid",{"3":{"1686":3}}],["authorname",{"3":{"1427":1}}],["authorship",{"3":{"1359":3,"1402":1}}],["authors",{"3":{"123":1,"557":1,"748":1,"750":1,"846":1,"990":1,"1270":1,"1271":2,"1286":1,"1369":1,"1396":1,"1402":1,"1417":1,"1436":1,"1496":1,"1737":1,"1830":1}}],["authored",{"3":{"0":1,"122":1,"333":1,"881":1,"887":1,"889":1,"890":2,"891":1,"972":1,"973":1,"976":1,"1006":1,"1081":1,"1085":1,"1117":1,"1212":2,"1238":2,"1241":4,"1242":4,"1245":1,"1247":10,"1263":1,"1270":1,"1271":2,"1281":3,"1286":10,"1310":1,"1311":1,"1324":1,"1339":2,"1342":1,"1350":2,"1359":3,"1362":1,"1395":4,"1396":7,"1398":1,"1402":8,"1415":3,"1416":3,"1433":1,"1436":13,"1496":39,"1498":1,"1513":1,"1576":1,"1577":1,"1630":1,"1664":1,"1815":1,"1816":2,"1817":1,"2220":1,"2221":1,"2230":1,"2232":1}}],["author",{"3":{"0":2,"135":1,"136":1,"194":1,"196":1,"295":1,"333":1,"342":1,"472":1,"538":1,"676":1,"716":1,"732":1,"799":1,"846":1,"847":2,"880":1,"889":1,"890":1,"912":1,"1004":1,"1005":1,"1006":1,"1135":1,"1152":1,"1237":1,"1247":6,"1258":1,"1259":1,"1267":1,"1270":3,"1271":1,"1286":3,"1310":1,"1311":1,"1313":1,"1323":2,"1339":1,"1350":1,"1359":6,"1369":1,"1380":1,"1396":2,"1398":1,"1402":17,"1417":1,"1436":12,"1438":1,"1455":1,"1474":1,"1488":1,"1491":1,"1496":19,"1632":3,"1889":1,"1987":1,"2040":1,"2043":1,"2077":1,"2109":2,"2168":1}}],["authoring",{"3":{"384":1,"1270":1,"1402":8,"1436":1,"1496":24,"1526":1,"1534":1,"1631":1}}],["authoritative",{"3":{"0":2,"145":1,"146":1,"147":1,"150":1,"395":1,"397":1,"400":1,"404":1,"407":1,"408":1,"509":1,"536":2,"537":1,"638":1,"640":1,"642":1,"937":1,"1073":1,"1075":1,"1093":1,"1155":1,"1271":1,"1300":2,"1311":1,"1324":8,"1333":2,"1339":2,"1350":1,"1395":5,"1396":8,"1402":3,"1415":1,"1436":3,"1438":2,"1443":2,"1453":1,"1454":1,"1455":2,"1460":1,"1474":1,"1490":1,"1492":1,"1526":1,"1531":1,"1535":1,"1561":1,"1600":1,"1639":1,"1646":1,"1654":1,"1675":1,"1692":1,"1732":1,"2010":1,"2112":1,"2156":1,"2160":2,"2167":1,"2169":1,"2179":1}}],["authority",{"2":{"1326":1,"1441":1,"1444":1,"1467":1,"1613":1,"2009":1},"3":{"0":2,"31":6,"59":1,"91":3,"93":4,"98":2,"100":1,"101":1,"104":1,"235":3,"236":1,"237":1,"451":1,"825":1,"827":3,"828":1,"834":1,"1018":1,"1044":1,"1118":2,"1152":1,"1212":1,"1286":3,"1300":2,"1311":20,"1312":1,"1323":1,"1324":12,"1326":1,"1334":2,"1339":5,"1347":1,"1350":3,"1359":3,"1395":2,"1396":6,"1402":1,"1415":1,"1427":1,"1429":2,"1436":6,"1438":2,"1441":1,"1443":3,"1444":1,"1467":5,"1611":1,"1613":3,"1626":1,"1650":1,"1663":1,"1667":1,"1703":1,"1724":1,"1898":1,"1904":1,"2009":1,"2024":1,"2078":1,"2192":1,"2203":1}}],["authorizable",{"3":{"1396":1}}],["authorizationpolicies",{"3":{"1496":1}}],["authorizationpolicy",{"3":{"189":1,"827":1,"1300":3,"1447":3,"1703":1}}],["authorizationresult",{"3":{"1436":3}}],["authorizationheader",{"3":{"1312":1}}],["authorizationhandlercontext",{"3":{"1300":1}}],["authorizationhandler",{"3":{"1300":3}}],["authorizationmultifactordecoratortests",{"2":{"1270":1},"3":{"1199":1,"1207":5,"1264":1,"1270":1}}],["authorizationtests",{"3":{"1199":1,"1207":2,"1436":3,"1438":2}}],["authorizationtestsbase",{"3":{"1199":2,"1207":2,"1428":1,"1433":3,"1436":8,"1438":1,"1488":1,"1496":2}}],["authorizationoptions",{"2":{"189":1},"3":{"189":1,"1300":4}}],["authorizationextensionstests",{"3":{"1199":1,"1207":2}}],["authorizationextensions",{"2":{"1300":1},"3":{"186":1,"189":2,"190":1,"707":1,"710":1,"980":1,"1059":1,"1199":1,"1203":1,"1207":2,"1208":1,"1287":1,"1297":10,"1300":40,"1496":1,"1577":1}}],["authorizationquerydecoratortests",{"3":{"1199":1,"1207":4,"1270":3}}],["authorizationquerydecorator",{"2":{"126":1,"1260":1,"1263":1,"1297":1},"3":{"121":2,"126":2,"1199":4,"1203":1,"1207":2,"1260":1,"1263":4,"1270":16,"1297":1,"1323":1,"1436":1,"1496":1}}],["authorizationdenied",{"2":{"121":1},"3":{"121":1,"1270":1}}],["authorizationcommanddecoratortests",{"3":{"1199":1,"1207":5,"1270":3,"1438":2}}],["authorizationcommanddecorator",{"2":{"121":1,"126":1,"1260":1,"1263":1,"1297":1,"1316":1},"3":{"121":3,"126":3,"1199":4,"1203":1,"1207":2,"1260":1,"1263":4,"1268":1,"1270":22,"1297":1,"1300":1,"1316":1,"1323":1,"1436":1,"1496":1}}],["authorizationgate",{"2":{"115":1,"126":1,"1821":1,"1962":1,"1965":1},"3":{"0":1,"115":1,"126":8,"186":2,"923":1,"927":1,"1059":4,"1199":3,"1203":1,"1207":2,"1260":1,"1263":10,"1270":20,"1496":2,"1821":1,"1962":1,"1965":1}}],["authorization",{"0":{"1297":1,"1348":1,"1353":1,"1357":1,"1373":1,"1402":1,"1511":1,"1667":1,"1761":1,"1771":1},"1":{"183":1,"184":1,"185":1,"186":1,"187":1,"188":1,"189":1,"190":1,"191":1,"315":1,"316":1,"317":1,"318":1,"319":1,"320":1,"321":1,"322":1,"323":1,"324":1,"325":1,"326":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1292":1,"1293":1,"1294":1,"1295":1,"1296":1,"1297":1,"1298":1,"1299":1,"1300":1,"1960":1,"1961":1,"1962":1,"1963":1,"1964":1,"1965":1,"1992":1,"1993":1,"1994":1,"1995":1,"1996":1,"1997":1},"2":{"121":1,"126":1,"206":1,"227":1,"387":1,"388":1,"389":1,"826":1,"1004":1,"1263":2,"1268":1,"1271":1,"1309":1,"1395":1,"1472":1,"1966":1,"1972":1,"2019":1,"2156":1},"3":{"0":24,"31":1,"57":1,"59":3,"95":1,"115":2,"117":1,"121":5,"122":3,"124":2,"126":6,"183":1,"186":8,"189":5,"190":1,"206":1,"217":1,"227":1,"243":1,"304":1,"315":1,"317":1,"318":7,"321":1,"324":1,"325":2,"326":1,"387":1,"388":3,"389":1,"443":1,"459":2,"464":3,"466":1,"507":1,"544":1,"546":2,"612":1,"640":1,"643":2,"651":1,"674":1,"690":1,"707":1,"715":1,"725":1,"739":1,"743":1,"748":1,"749":3,"753":1,"757":1,"774":1,"825":3,"826":1,"827":6,"906":1,"907":1,"952":1,"954":1,"956":1,"960":2,"980":3,"1004":1,"1012":1,"1026":1,"1042":1,"1043":1,"1044":1,"1058":3,"1059":4,"1061":1,"1115":1,"1116":3,"1117":2,"1118":1,"1184":2,"1192":2,"1202":2,"1203":27,"1207":78,"1208":3,"1212":5,"1213":1,"1238":1,"1239":1,"1245":1,"1247":5,"1253":1,"1259":1,"1260":2,"1263":5,"1264":1,"1268":2,"1270":25,"1271":4,"1275":2,"1283":2,"1286":8,"1287":5,"1296":1,"1297":16,"1298":1,"1300":215,"1301":3,"1304":3,"1309":18,"1311":22,"1312":9,"1316":3,"1318":1,"1323":15,"1324":28,"1339":2,"1342":1,"1348":4,"1350":16,"1353":3,"1354":1,"1357":2,"1359":53,"1372":1,"1373":4,"1375":1,"1377":1,"1379":1,"1380":39,"1395":12,"1396":24,"1402":23,"1403":1,"1404":2,"1405":1,"1406":1,"1409":1,"1410":2,"1414":1,"1415":37,"1417":3,"1427":3,"1436":51,"1437":7,"1438":11,"1447":1,"1455":1,"1467":8,"1472":2,"1481":1,"1487":2,"1488":2,"1496":12,"1497":1,"1507":3,"1511":1,"1526":4,"1534":1,"1535":2,"1548":3,"1562":1,"1563":2,"1572":2,"1575":2,"1577":5,"1582":1,"1589":1,"1591":2,"1595":1,"1596":2,"1600":2,"1621":1,"1626":1,"1627":1,"1629":1,"1630":1,"1632":1,"1638":4,"1639":3,"1640":1,"1641":4,"1660":1,"1662":2,"1666":1,"1667":4,"1671":2,"1672":1,"1698":2,"1720":1,"1747":2,"1753":1,"1757":1,"1759":1,"1775":1,"1779":2,"1813":2,"1814":1,"1815":2,"1816":1,"1817":2,"1821":3,"1825":1,"1826":1,"1827":1,"1902":1,"1903":1,"1904":1,"1922":2,"1938":1,"1940":1,"1959":1,"1960":1,"1961":2,"1963":1,"1965":5,"1966":1,"1972":1,"1991":1,"1992":2,"1993":2,"1994":4,"1997":3,"2019":2,"2036":1,"2055":1,"2066":1,"2096":1,"2111":2,"2113":1,"2136":2,"2149":1,"2156":2,"2179":1,"2184":1,"2186":1,"2192":1,"2193":2,"2194":1,"2195":3,"2199":2,"2202":1,"2208":3,"2220":1,"2227":1,"2230":1,"2232":2}}],["authorizing",{"3":{"209":1,"1359":1}}],["authorizeview",{"2":{"509":1,"650":1,"953":1,"960":1,"1758":1,"1761":1,"2078":1},"3":{"507":1,"509":1,"650":1,"1324":3,"1396":1,"1436":1,"1591":1,"1641":1,"1671":1,"1758":2,"1761":1,"2078":1}}],["authorizer",{"3":{"378":1,"1309":3,"1945":1}}],["authorizerouteview",{"2":{"189":1,"649":1,"651":1,"652":1},"3":{"189":1,"649":1,"651":1,"652":1,"1300":1,"1436":3}}],["authorizeattribute",{"3":{"186":1,"1297":1,"1300":3,"1431":1,"1436":2,"1962":1}}],["authorizesessionwriteasync",{"3":{"1359":9}}],["authorizes",{"3":{"0":1,"191":1,"530":1,"749":1,"1059":1,"1300":6,"1311":1,"1322":1,"1323":1,"1324":1,"1357":1,"1359":6,"1402":4,"1415":1,"1627":2,"1962":1,"1993":1,"2202":1}}],["authorize",{"2":{"185":1,"188":1,"206":1,"207":1,"208":1,"555":1,"557":1,"906":1,"910":1,"1626":1,"1703":1,"1724":1,"1757":1,"1758":1,"1759":1,"1760":1,"1761":1,"1823":1,"1898":1,"1960":1,"1964":1,"1965":1,"1966":1,"1967":1,"1972":1,"2045":1},"3":{"0":2,"185":2,"186":1,"188":1,"206":2,"207":1,"208":1,"225":1,"235":1,"434":1,"529":1,"530":1,"555":1,"557":1,"618":1,"690":1,"707":1,"724":1,"725":2,"905":2,"906":1,"907":1,"910":1,"1116":1,"1212":1,"1247":1,"1263":1,"1270":4,"1298":1,"1300":11,"1306":1,"1307":1,"1309":6,"1311":10,"1323":4,"1324":7,"1334":1,"1339":1,"1350":4,"1357":1,"1359":5,"1373":6,"1375":1,"1377":1,"1380":14,"1388":2,"1391":1,"1395":16,"1396":18,"1402":6,"1414":1,"1415":14,"1416":1,"1417":3,"1427":3,"1431":2,"1432":1,"1434":1,"1436":30,"1438":2,"1488":1,"1526":1,"1534":1,"1577":2,"1591":4,"1596":1,"1599":1,"1600":1,"1626":1,"1627":9,"1640":1,"1641":1,"1650":1,"1651":1,"1703":1,"1724":1,"1747":3,"1749":1,"1757":1,"1758":1,"1759":1,"1760":2,"1761":3,"1765":1,"1823":1,"1825":1,"1898":1,"1935":1,"1948":1,"1960":4,"1964":2,"1965":2,"1966":3,"1967":1,"1968":1,"1971":1,"1972":2,"2045":2,"2232":1}}],["authorizedsessionids",{"3":{"1359":1}}],["authorized",{"3":{"0":3,"95":1,"96":1,"98":1,"384":1,"528":1,"599":1,"960":1,"1092":1,"1286":2,"1300":3,"1309":1,"1324":1,"1359":2,"1396":2,"1415":1,"1436":5,"1467":1,"1474":1,"1548":1,"1553":1,"1672":1,"1821":1,"1993":1,"2179":1}}],["autherrorcodes",{"3":{"1199":3,"1203":1,"1207":2,"1295":3,"1300":5,"1496":1}}],["authentic",{"3":{"1489":1}}],["authenticity",{"3":{"790":1,"1300":1}}],["authenticators",{"3":{"1417":2}}],["authenticator",{"3":{"1059":2,"1293":2,"1300":7,"1311":1,"1322":1,"1323":4,"1417":2,"2196":1,"2198":2,"2203":1}}],["authenticating",{"3":{"0":1,"348":1,"353":1,"1300":1,"1312":1,"1324":1,"1339":1,"1436":1,"1902":1}}],["authenticationcallback",{"3":{"1417":1}}],["authenticationtype",{"3":{"1323":4,"1436":1}}],["authenticationticket",{"3":{"1300":1,"1436":1}}],["authenticationproperties",{"2":{"1312":1},"3":{"1311":2,"1312":1}}],["authenticationheadervalue",{"2":{"1311":1,"1324":1},"3":{"1311":2,"1324":3}}],["authenticationhandler",{"3":{"1300":2,"1436":1}}],["authenticationbuilder",{"2":{"1208":1},"3":{"1208":1,"1300":3}}],["authenticationvalidatorstests",{"3":{"1199":1,"1207":2}}],["authenticationvalidators",{"2":{"1289":1},"3":{"1199":12,"1203":1,"1207":2,"1287":1,"1289":3,"1300":9,"1406":1,"1415":5,"1496":2}}],["authenticationrequest",{"2":{"1289":1},"3":{"1199":1,"1203":1,"1207":2,"1289":1,"1300":8}}],["authenticationresponse",{"2":{"420":1,"426":1,"1289":1,"1977":1},"3":{"350":1,"1199":31,"1203":1,"1207":2,"1289":1,"1300":18,"1311":3,"1324":4,"1415":2,"1436":1,"1977":1}}],["authenticationservicesettings",{"3":{"1300":1,"1415":2,"1436":1,"1496":2}}],["authenticationservicetests",{"3":{"1199":1,"1207":3,"1300":1,"1436":1}}],["authenticationserviceidentitycompletionstests",{"2":{"1199":1,"1300":1},"3":{"1199":1,"1207":7,"1300":2,"1436":1}}],["authenticationservice",{"2":{"283":1,"310":1,"314":1,"354":1,"502":1,"672":1,"949":1,"988":1,"1192":1,"1289":1,"1407":1,"1408":1,"1534":1,"1597":1,"1599":1,"1978":1,"1983":1},"3":{"281":1,"283":1,"310":5,"314":1,"350":17,"352":2,"353":1,"354":1,"497":2,"499":13,"502":2,"545":1,"552":1,"672":1,"674":4,"946":2,"949":1,"988":2,"1192":1,"1199":5,"1203":1,"1207":2,"1289":1,"1300":14,"1311":1,"1405":1,"1406":7,"1407":1,"1408":1,"1413":2,"1415":36,"1436":4,"1489":1,"1496":6,"1526":2,"1534":2,"1591":3,"1597":2,"1599":2,"1765":2,"1896":1,"1978":1,"1983":4,"2000":2}}],["authenticationservicebasetests",{"3":{"906":1,"1199":1,"1207":6,"1300":2,"1436":1}}],["authenticationservicebase",{"2":{"279":1,"281":1,"283":1,"284":1,"285":1,"310":1,"353":1,"470":1,"476":1,"498":1,"502":1,"503":1,"725":1,"949":1,"1058":1,"1060":1,"1061":1,"1192":1,"1289":1,"1293":1,"1403":1,"1667":1,"1896":1,"1982":1,"2196":1,"2203":1},"3":{"0":3,"279":1,"283":1,"284":2,"285":1,"310":4,"353":2,"470":2,"476":2,"497":2,"498":2,"499":21,"500":1,"501":2,"502":3,"503":1,"674":2,"725":1,"905":10,"906":1,"907":4,"909":1,"910":1,"946":1,"949":1,"1058":2,"1059":3,"1060":1,"1061":1,"1192":1,"1199":5,"1203":1,"1207":2,"1270":1,"1271":2,"1286":2,"1287":1,"1289":34,"1290":5,"1293":1,"1300":66,"1403":1,"1406":1,"1415":2,"1436":3,"1489":1,"1496":5,"1577":2,"1582":1,"1583":1,"1586":1,"1667":1,"1896":1,"1982":9,"1984":2,"2000":1,"2196":2,"2197":2,"2203":1}}],["authenticationstatetask",{"3":{"1395":1}}],["authenticationstate",{"3":{"0":1,"954":1,"1324":2,"1395":1,"1396":1,"1402":2,"1432":1,"1436":6,"1488":1,"1491":2}}],["authenticationstateprovider",{"2":{"508":1,"953":1,"954":1,"1432":1,"1491":1,"1641":1},"3":{"0":1,"508":1,"953":1,"954":2,"1324":8,"1395":2,"1415":2,"1432":1,"1436":9,"1488":1,"1491":1,"1641":1}}],["authentication=active",{"3":{"0":1,"599":1,"1467":1,"1477":1}}],["authentication",{"0":{"1289":1,"1406":1,"1511":1,"1641":1},"1":{"204":1,"205":1,"206":1,"207":1,"208":1,"209":1,"210":1,"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"504":1,"505":1,"506":1,"507":1,"508":1,"509":1,"510":1,"511":1,"512":1,"513":1,"514":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1292":1,"1293":1,"1294":1,"1295":1,"1296":1,"1297":1,"1298":1,"1299":1,"1300":1},"2":{"776":1,"833":1,"1293":2,"1295":2,"1311":1,"1312":1,"1322":1,"1326":1,"1441":1,"1444":1,"1467":4,"2009":1},"3":{"0":7,"26":1,"31":4,"34":1,"59":1,"160":1,"173":1,"175":1,"177":1,"178":1,"182":1,"186":1,"202":1,"204":1,"206":1,"207":1,"235":2,"278":1,"280":1,"313":2,"350":1,"352":1,"359":1,"363":1,"364":1,"365":1,"403":1,"422":1,"457":1,"458":1,"459":2,"462":2,"464":3,"466":1,"500":1,"504":1,"506":1,"528":1,"555":1,"599":2,"602":1,"612":1,"618":1,"665":1,"666":1,"674":1,"698":1,"719":2,"748":2,"749":6,"753":3,"774":1,"776":1,"778":1,"792":1,"827":4,"833":1,"852":1,"950":2,"980":2,"1011":1,"1012":1,"1058":1,"1059":3,"1061":1,"1108":1,"1111":1,"1184":1,"1192":1,"1202":1,"1203":3,"1207":7,"1208":2,"1212":1,"1213":2,"1253":1,"1259":1,"1270":3,"1271":1,"1273":1,"1283":1,"1284":1,"1286":5,"1287":2,"1290":1,"1293":4,"1295":4,"1298":2,"1300":49,"1301":2,"1311":50,"1312":4,"1320":2,"1322":1,"1323":3,"1324":13,"1326":1,"1334":2,"1335":1,"1336":1,"1339":7,"1380":2,"1391":1,"1395":7,"1396":7,"1402":1,"1406":1,"1408":1,"1415":14,"1416":3,"1417":3,"1432":1,"1434":1,"1436":34,"1437":1,"1441":1,"1442":1,"1443":2,"1444":1,"1465":1,"1467":8,"1488":2,"1496":5,"1526":1,"1548":2,"1575":1,"1622":1,"1626":2,"1627":2,"1629":4,"1630":1,"1631":5,"1632":3,"1635":1,"1638":2,"1639":1,"1640":2,"1641":9,"1647":1,"1650":2,"1666":1,"1669":1,"1671":1,"1703":2,"1721":1,"1724":1,"1747":1,"1758":1,"1763":2,"1764":1,"1765":2,"1767":1,"1825":5,"1898":1,"1899":2,"1902":1,"1904":1,"1970":1,"1972":3,"1975":1,"1982":1,"1985":1,"1999":1,"2000":3,"2001":2,"2002":2,"2009":1,"2066":2,"2142":2,"2144":1,"2166":1,"2168":1,"2186":1,"2194":2,"2195":1,"2203":2,"2208":1,"2220":2,"2221":1,"2232":2}}],["authenticateasspeaker",{"3":{"1488":1}}],["authenticateasattendee",{"3":{"1488":1,"1491":1}}],["authenticateasorganizer",{"3":{"1488":1}}],["authenticateasync",{"2":{"420":1,"430":1,"2117":1},"3":{"420":1,"426":1,"428":1,"430":1,"1324":3,"1415":1,"1416":1,"1417":6,"2117":1}}],["authenticateresult",{"3":{"1300":1,"1312":1,"1415":1,"1436":2}}],["authenticateresultfeaturestub",{"3":{"1199":2,"1207":1}}],["authenticate",{"3":{"30":1,"279":1,"284":1,"527":1,"601":1,"789":1,"905":1,"946":1,"947":1,"1300":3,"1309":1,"1324":3,"1415":1,"1417":1,"1455":2,"1467":2,"1591":1,"1632":1,"1641":3,"1768":1,"1982":1,"2127":1,"2146":1,"2166":1}}],["authenticateduser",{"3":{"1436":2}}],["authenticateduserpath",{"3":{"1433":1,"1436":1}}],["authenticatedpage",{"3":{"1436":1}}],["authenticatedservicebaseretrytests",{"3":{"1199":1,"1207":3}}],["authenticatedservicebase",{"2":{"879":1,"881":1,"883":1,"884":1,"1388":1,"1389":1,"1395":1,"1414":1,"1668":1,"2074":1,"2080":1},"3":{"0":3,"827":2,"879":1,"881":6,"883":2,"884":2,"1199":25,"1203":1,"1207":2,"1212":1,"1324":64,"1339":2,"1388":1,"1389":1,"1395":19,"1396":20,"1402":1,"1414":1,"1415":1,"1496":2,"1600":1,"1668":1,"2074":1,"2080":1}}],["authenticated",{"0":{"1298":1,"1391":1,"1623":1,"1759":1,"1922":1,"2000":1},"1":{"172":1,"173":1,"174":1,"175":1,"176":1,"177":1,"178":1,"179":1,"180":1,"181":1,"182":1,"385":1,"386":1,"387":1,"388":1,"389":1,"390":1,"391":1,"392":1,"393":1},"3":{"0":8,"26":1,"31":1,"172":1,"175":5,"176":1,"177":2,"178":1,"179":1,"181":2,"182":1,"184":1,"188":1,"189":1,"191":1,"228":1,"234":1,"237":1,"243":2,"244":1,"247":1,"259":1,"265":1,"273":1,"280":2,"282":1,"334":1,"359":3,"360":2,"384":1,"385":1,"387":2,"388":1,"390":1,"434":1,"458":2,"459":2,"460":1,"461":1,"462":1,"507":1,"556":1,"649":1,"690":1,"725":1,"727":1,"749":1,"759":1,"825":1,"853":1,"945":1,"960":1,"1192":1,"1212":4,"1253":1,"1271":2,"1284":1,"1286":7,"1287":1,"1288":1,"1296":1,"1297":1,"1300":16,"1304":1,"1306":1,"1308":1,"1309":15,"1311":23,"1312":3,"1323":7,"1324":23,"1336":1,"1339":4,"1350":3,"1357":1,"1359":12,"1373":1,"1380":4,"1391":1,"1395":16,"1396":27,"1401":1,"1402":5,"1406":1,"1407":1,"1414":1,"1415":15,"1417":9,"1431":1,"1432":2,"1436":18,"1438":6,"1443":1,"1455":1,"1465":1,"1488":1,"1496":2,"1526":4,"1534":3,"1535":1,"1548":1,"1572":1,"1577":1,"1591":1,"1611":1,"1621":5,"1625":1,"1626":1,"1627":3,"1629":1,"1631":7,"1632":18,"1638":2,"1639":6,"1640":3,"1641":6,"1672":1,"1698":1,"1747":3,"1748":2,"1749":10,"1750":2,"1757":3,"1765":3,"1768":3,"1771":1,"1773":1,"1825":1,"1903":1,"1905":1,"1922":3,"1923":2,"1924":1,"1936":1,"1937":1,"1938":1,"1945":1,"1963":1,"1968":1,"1992":1,"1997":1,"1998":1,"1999":2,"2000":4,"2001":2,"2002":3,"2007":1,"2066":1,"2083":1,"2142":2,"2146":5,"2166":1,"2169":1,"2232":5}}],["authenticates",{"3":{"0":5,"59":1,"207":1,"227":1,"350":1,"598":1,"599":1,"601":1,"665":1,"825":1,"905":1,"972":1,"1183":1,"1185":1,"1290":1,"1300":5,"1309":1,"1311":3,"1323":1,"1324":4,"1339":3,"1406":1,"1415":2,"1434":1,"1436":2,"1447":1,"1455":1,"1459":1,"1465":1,"1467":3,"1472":1,"1547":1,"1641":1,"1667":1,"1732":1,"1902":1,"1905":1,"1966":1,"1968":1,"1975":1,"1982":1,"1993":1,"2000":1,"2126":1}}],["authclaimtypes",{"2":{"186":1,"303":1,"649":1,"1296":1,"2138":1,"2197":1},"3":{"126":1,"186":3,"303":2,"649":1,"905":4,"907":1,"1059":3,"1199":34,"1203":1,"1207":2,"1270":3,"1287":1,"1293":4,"1296":5,"1300":30,"1323":1,"1436":3,"1496":1,"2138":2,"2197":3}}],["authcontrollertests",{"3":{"1199":1,"1207":2}}],["authcontroller",{"2":{"1747":2},"3":{"0":1,"854":1,"1199":2,"1203":1,"1207":2,"1300":1,"1311":8,"1323":5,"1406":3,"1407":2,"1408":1,"1415":31,"1436":1,"1496":3,"1747":2,"2000":2}}],["authcontrollerbaseratelimittests",{"2":{"2000":1,"2001":1},"3":{"1199":1,"1207":3,"1311":1,"1438":2,"1496":2,"2000":2,"2001":2}}],["authcontrollerbasetests",{"3":{"906":1,"909":1,"1199":1,"1207":3,"1311":1}}],["authcontrollerbase",{"2":{"160":1,"161":1,"280":1,"1599":1,"1600":1,"2000":1,"2002":1},"3":{"0":4,"160":2,"161":2,"175":1,"280":1,"854":1,"905":6,"1199":6,"1203":1,"1207":2,"1290":4,"1300":15,"1311":30,"1406":1,"1415":2,"1436":3,"1496":3,"1599":1,"1600":1,"2002":1}}],["authdelegatinghandlertests",{"3":{"1199":1,"1207":2,"1324":2}}],["authdelegatinghandler",{"2":{"387":1,"514":1,"1183":1},"3":{"0":1,"387":1,"507":6,"514":3,"881":1,"1183":2,"1199":6,"1203":1,"1207":2,"1324":20,"1436":3}}],["auth",{"0":{"1667":1,"2208":1},"1":{"1893":1,"1894":1,"1895":1,"1896":1,"1897":1,"1898":1,"1899":1,"1966":1,"1967":1,"1968":1,"1969":1,"1970":1,"1971":1,"1972":1},"2":{"186":1,"190":1,"207":2,"281":2,"284":1,"350":5,"352":3,"353":2,"354":3,"511":2,"854":1,"1062":1,"1295":1,"1300":2,"1406":2,"1582":1,"1583":1,"1641":1,"1761":1,"1962":1,"1977":4,"1978":1,"1982":1},"3":{"0":31,"29":1,"31":2,"53":1,"59":1,"60":1,"121":1,"126":2,"136":1,"155":1,"157":1,"160":1,"161":2,"162":1,"173":5,"175":4,"176":1,"177":2,"178":3,"179":5,"180":8,"181":1,"182":2,"186":4,"190":2,"207":7,"209":3,"210":1,"217":1,"221":1,"227":1,"229":2,"236":1,"239":1,"243":2,"246":1,"279":2,"280":3,"281":3,"282":2,"284":3,"287":2,"303":2,"310":1,"318":1,"349":1,"350":7,"352":5,"353":4,"354":3,"379":1,"389":1,"403":1,"415":2,"416":1,"418":1,"420":6,"423":3,"426":3,"428":5,"430":2,"433":1,"434":1,"440":1,"459":2,"463":1,"470":1,"476":1,"497":4,"498":1,"499":4,"502":1,"506":1,"507":17,"508":3,"509":2,"510":2,"511":2,"513":2,"514":2,"536":2,"555":3,"556":2,"557":1,"599":1,"600":1,"608":1,"609":2,"611":1,"612":1,"616":1,"643":1,"649":5,"664":1,"666":1,"668":1,"670":1,"674":9,"676":1,"736":1,"757":1,"758":1,"775":1,"809":2,"827":2,"833":1,"834":1,"853":1,"854":6,"856":1,"857":1,"904":1,"905":30,"906":11,"907":2,"908":1,"909":4,"926":1,"946":4,"963":1,"980":1,"988":1,"996":1,"999":1,"1006":2,"1058":3,"1059":36,"1062":2,"1082":2,"1183":1,"1184":9,"1185":1,"1186":1,"1192":3,"1194":1,"1201":1,"1202":2,"1203":152,"1207":547,"1208":2,"1212":9,"1213":1,"1237":4,"1270":3,"1271":5,"1273":1,"1283":8,"1286":36,"1287":1,"1288":6,"1289":8,"1290":8,"1291":6,"1292":7,"1293":9,"1294":5,"1295":7,"1296":3,"1297":20,"1298":4,"1300":519,"1301":10,"1304":1,"1311":49,"1312":4,"1313":1,"1316":1,"1320":3,"1322":2,"1323":19,"1324":301,"1326":1,"1336":1,"1338":1,"1339":16,"1350":4,"1359":2,"1380":1,"1391":1,"1395":9,"1396":10,"1402":2,"1403":1,"1406":3,"1407":4,"1408":3,"1415":45,"1416":5,"1417":43,"1428":1,"1429":2,"1430":1,"1432":2,"1433":2,"1434":2,"1436":78,"1437":2,"1438":33,"1440":1,"1441":1,"1442":2,"1444":2,"1447":10,"1453":1,"1454":1,"1455":4,"1456":3,"1458":1,"1467":11,"1477":6,"1481":1,"1482":1,"1484":1,"1487":2,"1488":7,"1496":24,"1511":1,"1524":1,"1526":7,"1534":3,"1535":1,"1562":1,"1563":2,"1571":1,"1577":13,"1582":8,"1583":2,"1586":3,"1591":5,"1611":3,"1612":1,"1617":2,"1618":1,"1619":2,"1627":2,"1632":10,"1638":1,"1639":1,"1640":3,"1641":26,"1642":1,"1644":1,"1647":2,"1650":1,"1653":1,"1656":1,"1658":1,"1660":3,"1663":1,"1666":1,"1667":2,"1669":3,"1689":1,"1698":2,"1712":1,"1746":6,"1747":10,"1748":5,"1749":3,"1750":1,"1757":1,"1759":1,"1761":3,"1765":7,"1767":1,"1771":3,"1778":1,"1779":3,"1780":1,"1783":1,"1786":1,"1790":2,"1793":1,"1892":1,"1893":1,"1899":1,"1923":1,"1942":1,"1962":1,"1965":1,"1966":2,"1967":1,"1968":6,"1969":1,"1971":3,"1972":5,"1974":1,"1975":4,"1976":1,"1977":4,"1978":2,"1982":1,"1994":1,"1997":1,"1999":1,"2000":7,"2001":4,"2002":1,"2009":1,"2015":1,"2023":1,"2025":1,"2028":1,"2036":2,"2045":1,"2055":2,"2066":3,"2074":1,"2078":3,"2080":1,"2085":1,"2110":2,"2118":1,"2122":1,"2149":1,"2158":1,"2208":3,"2227":1,"2228":1,"2230":1,"2232":3,"2243":1}}],["abbreviates",{"3":{"1681":1}}],["abbreviated",{"3":{"1496":1}}],["abbreviating",{"3":{"1339":1}}],["abi",{"3":{"1417":1}}],["ability",{"3":{"58":1,"726":1,"1237":1,"1311":1,"1323":1,"1339":1,"1369":1,"1396":1,"1415":1,"1443":1,"1985":1}}],["able",{"3":{"380":1,"447":1,"549":1,"691":1,"760":1,"811":1,"829":1,"989":1,"1042":1,"1237":1,"1238":1,"1259":1,"1279":1,"1286":3,"1300":4,"1301":1,"1311":1,"1323":1,"1324":3,"1339":3,"1359":1,"1366":1,"1395":3,"1396":6,"1410":1,"1415":4,"1430":1,"1435":1,"1436":1,"1438":3,"1439":1,"1441":1,"1467":1,"1487":1,"1677":1,"1840":1,"1942":1,"1943":1,"1967":1,"1981":1,"1984":1,"2130":1,"2134":1,"2192":1}}],["abcdefghijklmnopqrstuvwxyz0123456789",{"3":{"1309":1}}],["abc",{"3":{"335":1,"1237":1,"1241":2,"1247":2}}],["abuse",{"3":{"175":1,"381":1,"827":1,"1125":1,"1126":1,"1324":3,"1326":1,"1339":3,"1396":3,"1402":1,"1416":1,"1444":1,"1534":1,"1599":1,"1640":1,"1641":1,"1945":1}}],["abuser",{"3":{"174":1,"1998":1,"1999":1}}],["abusive",{"3":{"174":1,"1402":1,"1998":1}}],["abac",{"3":{"0":1,"188":1,"320":1,"1577":1,"1583":1,"1964":1,"1996":2,"1997":1}}],["abandonaftercommitfailure",{"2":{"988":1},"3":{"988":2,"1286":2}}],["abandoning",{"3":{"332":1,"751":1,"923":1,"1324":1,"1390":1,"1417":1,"1990":1}}],["abandons",{"3":{"24":1,"237":1,"538":1,"1293":1,"1339":2,"1395":2}}],["abandoned",{"3":{"21":1,"24":1,"109":2,"235":1,"352":1,"793":1,"897":1,"1247":1,"1259":2,"1285":1,"1286":5,"1300":2,"1324":4,"1339":1,"1395":2,"1399":1,"1402":3,"1436":4,"1442":1,"1467":1,"1591":1,"1759":1,"1910":1,"1919":1,"1927":1,"1978":1}}],["abandon",{"3":{"0":1,"24":1,"27":1,"201":4,"255":1,"757":1,"1247":1,"1258":2,"1259":6,"1311":1,"1323":1,"1359":1,"1438":1,"1455":1,"1922":1}}],["aborterror",{"3":{"1417":1}}],["aborted",{"3":{"109":1,"1263":1,"1270":1,"1278":1,"1286":2,"1300":2,"1304":1,"1307":1,"1309":1,"1312":1,"1323":1,"1346":1,"1359":1,"1436":2,"1945":1}}],["aborting",{"3":{"757":1,"1301":1,"1324":1,"1358":1,"1359":2,"1369":1,"1396":1,"1438":1,"1804":1,"1881":1}}],["abort",{"3":{"72":1,"109":1,"1286":2,"1311":2,"1324":1,"1350":1,"1359":5,"1366":1,"1369":1,"1396":1,"1415":1,"1417":2,"1438":1,"1447":1,"1671":1,"1806":1,"1941":1}}],["aborts",{"3":{"43":2,"790":1,"881":1,"1042":1,"1259":1,"1315":1,"1323":4,"1339":1,"1350":1,"1359":3,"1396":2,"1415":2,"1684":1,"2185":1}}],["about",{"0":{"2056":1},"3":{"0":9,"19":3,"23":1,"32":1,"58":1,"60":1,"71":1,"72":1,"73":1,"74":2,"100":1,"104":1,"110":1,"125":1,"140":1,"142":1,"145":1,"150":1,"156":2,"160":2,"175":1,"193":1,"200":1,"254":1,"255":2,"284":1,"317":1,"358":1,"360":1,"365":1,"383":1,"390":3,"414":1,"423":1,"425":1,"428":1,"460":1,"477":1,"486":1,"491":1,"527":1,"536":1,"539":1,"543":2,"555":1,"563":1,"564":1,"566":1,"567":1,"585":1,"587":1,"589":1,"590":2,"617":1,"625":1,"628":1,"629":1,"632":1,"633":1,"676":2,"681":1,"683":1,"689":1,"698":1,"699":1,"702":1,"707":2,"708":1,"716":3,"724":1,"740":2,"742":1,"750":1,"751":1,"757":1,"759":2,"760":1,"764":1,"768":2,"784":1,"793":2,"798":2,"804":1,"805":1,"819":1,"820":1,"827":4,"838":3,"848":1,"854":1,"862":2,"869":1,"871":1,"880":1,"889":2,"890":1,"893":1,"897":2,"898":1,"900":1,"906":1,"914":1,"926":2,"930":1,"933":1,"938":1,"940":1,"953":1,"956":1,"963":1,"964":2,"971":1,"972":1,"976":1,"980":1,"981":2,"984":1,"998":1,"1007":1,"1018":1,"1027":1,"1033":3,"1035":1,"1049":2,"1050":1,"1051":2,"1066":1,"1067":2,"1068":1,"1077":1,"1085":1,"1089":1,"1093":1,"1116":1,"1118":1,"1125":1,"1142":1,"1154":1,"1155":1,"1176":2,"1194":1,"1212":1,"1229":3,"1237":3,"1239":1,"1241":1,"1247":3,"1252":2,"1254":2,"1259":6,"1264":1,"1265":1,"1267":1,"1269":1,"1270":10,"1273":1,"1276":1,"1277":1,"1280":1,"1286":29,"1289":1,"1295":1,"1296":1,"1300":31,"1301":4,"1309":4,"1310":1,"1311":10,"1312":2,"1318":2,"1322":2,"1323":20,"1324":42,"1337":2,"1338":1,"1339":13,"1340":1,"1341":1,"1347":1,"1348":2,"1350":9,"1355":1,"1359":53,"1366":2,"1369":6,"1371":1,"1372":1,"1380":5,"1395":16,"1396":42,"1397":1,"1399":1,"1402":13,"1415":28,"1416":3,"1417":10,"1427":2,"1429":1,"1430":1,"1433":1,"1436":72,"1438":1,"1440":1,"1443":1,"1445":2,"1446":1,"1447":2,"1449":1,"1451":1,"1453":4,"1454":2,"1455":6,"1456":2,"1459":1,"1460":1,"1465":5,"1467":13,"1472":1,"1473":1,"1474":7,"1475":1,"1476":3,"1478":1,"1484":1,"1488":1,"1489":2,"1490":3,"1492":5,"1496":2,"1526":1,"1529":1,"1553":1,"1586":1,"1630":1,"1638":1,"1641":1,"1649":1,"1650":1,"1651":1,"1654":1,"1685":1,"1686":3,"1692":1,"1699":1,"1700":1,"1708":1,"1725":1,"1726":1,"1727":1,"1729":2,"1732":1,"1765":1,"1782":1,"1792":1,"1794":1,"1801":1,"1803":3,"1808":1,"1824":1,"1828":1,"1835":1,"1843":1,"1855":1,"1856":1,"1863":1,"1870":1,"1874":1,"1886":1,"1890":1,"1902":2,"1903":1,"1907":1,"1909":1,"1916":1,"1919":1,"1922":1,"1932":1,"1944":1,"1948":2,"1950":1,"1952":1,"1953":1,"1954":1,"1962":1,"1964":1,"1971":1,"1973":1,"1978":1,"1982":2,"1992":2,"1994":1,"1996":1,"1998":1,"2000":3,"2001":1,"2002":1,"2029":1,"2030":1,"2031":1,"2033":4,"2036":1,"2037":1,"2041":1,"2042":1,"2043":2,"2045":1,"2047":2,"2050":1,"2053":1,"2058":2,"2059":3,"2060":1,"2066":1,"2067":1,"2075":1,"2086":1,"2087":3,"2091":1,"2096":1,"2099":1,"2100":1,"2101":1,"2109":1,"2114":1,"2116":1,"2118":1,"2123":1,"2127":1,"2132":1,"2135":1,"2138":1,"2142":1,"2151":1,"2156":2,"2157":1,"2158":1,"2165":2,"2166":1,"2167":3,"2174":1,"2180":1,"2181":1,"2183":1,"2184":1,"2185":1,"2191":1,"2193":1,"2194":2,"2197":1,"2198":2,"2200":1,"2212":1,"2232":1,"2239":1}}],["abovethroughputfloor",{"3":{"1492":1}}],["above",{"3":{"0":3,"1":1,"20":1,"22":2,"27":1,"62":1,"71":1,"93":3,"94":2,"95":2,"97":1,"98":2,"99":1,"100":2,"101":1,"104":1,"121":2,"122":1,"128":1,"131":1,"135":6,"136":3,"137":3,"138":3,"139":1,"142":1,"151":1,"160":2,"166":1,"175":2,"178":2,"179":1,"180":1,"181":1,"200":6,"218":1,"219":2,"230":1,"231":1,"237":1,"243":2,"245":3,"247":1,"248":1,"249":1,"250":1,"251":1,"253":1,"256":1,"257":1,"258":2,"265":1,"276":1,"291":1,"295":2,"303":2,"325":2,"330":2,"336":2,"337":1,"359":1,"364":1,"365":2,"374":1,"391":1,"392":2,"395":3,"400":1,"401":1,"403":1,"407":1,"415":1,"418":1,"423":1,"424":1,"425":2,"426":3,"428":1,"429":1,"441":1,"443":1,"444":1,"448":3,"450":1,"451":1,"471":1,"474":1,"475":2,"476":1,"477":3,"493":1,"497":2,"507":1,"511":1,"512":2,"523":3,"524":1,"528":1,"536":1,"540":1,"548":1,"549":3,"550":1,"551":4,"555":1,"556":1,"560":1,"566":2,"567":1,"572":2,"574":1,"575":2,"576":1,"577":1,"578":1,"584":1,"591":1,"592":1,"603":1,"604":1,"607":1,"609":3,"612":1,"613":2,"616":1,"626":2,"633":1,"635":1,"638":1,"642":2,"660":1,"665":1,"668":1,"669":1,"674":2,"675":1,"676":1,"688":1,"691":1,"702":1,"716":1,"719":1,"731":1,"733":1,"750":1,"751":1,"766":1,"767":1,"774":1,"790":1,"799":1,"801":1,"811":1,"812":1,"814":1,"819":2,"825":2,"826":1,"827":6,"829":1,"831":2,"833":1,"837":1,"839":1,"849":1,"860":1,"870":1,"874":1,"876":1,"891":1,"899":1,"906":1,"909":1,"914":1,"915":1,"926":2,"931":1,"954":1,"957":1,"975":1,"996":1,"1003":1,"1008":1,"1018":1,"1020":2,"1026":1,"1036":1,"1059":1,"1060":1,"1074":1,"1076":1,"1077":1,"1094":1,"1102":1,"1117":1,"1124":2,"1125":1,"1127":1,"1128":1,"1178":1,"1184":1,"1188":1,"1211":1,"1212":1,"1223":1,"1229":3,"1236":1,"1237":9,"1240":1,"1243":1,"1247":5,"1259":7,"1262":2,"1266":1,"1269":1,"1270":15,"1271":1,"1275":1,"1281":1,"1286":14,"1300":14,"1301":5,"1309":13,"1310":1,"1311":11,"1312":2,"1314":1,"1315":1,"1323":26,"1324":42,"1326":1,"1329":1,"1333":1,"1337":1,"1339":30,"1346":1,"1350":14,"1353":1,"1358":1,"1359":76,"1362":1,"1364":1,"1365":1,"1369":20,"1372":1,"1380":18,"1387":1,"1392":1,"1395":30,"1396":53,"1402":7,"1414":1,"1415":34,"1416":2,"1417":10,"1427":2,"1430":1,"1431":1,"1435":1,"1436":44,"1438":4,"1440":1,"1441":1,"1442":1,"1443":2,"1444":4,"1445":4,"1446":1,"1447":5,"1449":2,"1453":1,"1454":1,"1455":6,"1456":1,"1458":2,"1459":1,"1460":2,"1465":5,"1467":26,"1474":3,"1475":3,"1476":1,"1478":1,"1479":1,"1487":3,"1488":5,"1489":3,"1490":2,"1491":1,"1492":4,"1496":15,"1504":1,"1517":1,"1532":1,"1534":2,"1577":1,"1582":1,"1591":3,"1599":1,"1610":1,"1611":2,"1619":1,"1627":2,"1636":1,"1640":1,"1644":1,"1645":2,"1653":1,"1658":1,"1660":1,"1673":1,"1675":1,"1678":1,"1685":1,"1686":2,"1687":1,"1689":1,"1699":1,"1701":1,"1708":1,"1720":1,"1723":1,"1724":1,"1725":1,"1727":4,"1728":1,"1744":1,"1748":1,"1749":1,"1750":1,"1755":1,"1760":1,"1761":1,"1769":1,"1777":1,"1785":1,"1791":1,"1814":1,"1840":1,"1842":1,"1844":1,"1849":1,"1851":1,"1854":1,"1856":1,"1859":1,"1860":1,"1861":1,"1868":1,"1898":2,"1911":1,"1914":1,"1916":1,"1919":1,"1920":2,"1923":1,"1929":1,"1930":1,"1948":1,"1970":1,"1976":1,"2000":2,"2024":1,"2044":1,"2045":1,"2047":2,"2048":1,"2053":2,"2055":1,"2056":1,"2058":1,"2064":1,"2068":1,"2073":1,"2074":1,"2078":2,"2084":1,"2089":1,"2094":1,"2099":1,"2100":2,"2122":1,"2131":1,"2142":1,"2146":1,"2158":1,"2166":2,"2167":2,"2168":3,"2174":1,"2195":1,"2198":1,"2201":1,"2232":1}}],["abs",{"3":{"1300":2,"1324":1}}],["abstain",{"3":{"1300":1}}],["abstains",{"3":{"1300":1}}],["abstaining",{"3":{"1300":1}}],["abstractlocalizationinterceptor",{"3":{"1324":3}}],["abstracted",{"3":{"1324":2,"1641":1}}],["abstractvalidator",{"2":{"1353":1,"1829":1,"1830":1},"3":{"1271":13,"1300":9,"1309":1,"1353":1,"1359":102,"1396":2,"1402":8,"1415":4,"1436":7,"1829":2,"1830":1}}],["abstracting",{"3":{"1247":1,"1324":1}}],["abstraction",{"0":{"1915":1},"1":{"410":1,"411":1,"412":1,"413":1,"414":1,"415":1,"416":1,"1417":1},"3":{"0":5,"13":1,"39":1,"150":1,"225":1,"226":1,"230":1,"243":1,"248":1,"319":1,"359":1,"361":1,"380":1,"381":1,"410":1,"439":1,"459":1,"460":1,"507":1,"510":1,"513":1,"640":2,"707":1,"732":1,"799":1,"810":1,"815":1,"827":1,"846":2,"1033":1,"1059":1,"1075":1,"1090":1,"1091":1,"1092":1,"1120":1,"1133":1,"1192":3,"1202":3,"1203":3,"1212":1,"1213":2,"1243":1,"1247":3,"1259":2,"1270":4,"1271":1,"1286":18,"1300":13,"1301":5,"1303":1,"1309":5,"1310":2,"1311":9,"1316":1,"1323":3,"1324":10,"1339":4,"1350":2,"1355":1,"1359":15,"1369":1,"1395":5,"1396":16,"1402":1,"1414":1,"1415":3,"1416":2,"1417":12,"1427":5,"1429":1,"1436":27,"1438":1,"1490":1,"1491":1,"1496":2,"1526":1,"1599":1,"1641":1,"1669":1,"1790":1,"1815":3,"1817":1,"1842":1,"1867":1,"1936":1,"1946":1,"1948":1,"1950":1,"1994":1,"2001":1,"2028":1,"2078":1,"2116":2,"2117":1,"2118":1,"2120":1,"2123":1,"2126":1,"2142":1,"2145":1,"2153":1,"2180":1,"2193":1,"2232":3}}],["abstractions",{"2":{"109":1,"1229":1,"1653":1,"1702":1},"3":{"0":2,"39":1,"43":1,"109":6,"166":1,"225":1,"226":1,"230":1,"434":1,"506":1,"507":1,"508":1,"549":1,"725":1,"1050":1,"1074":1,"1090":1,"1168":1,"1203":12,"1207":40,"1212":1,"1219":3,"1220":1,"1222":1,"1227":1,"1228":1,"1229":25,"1237":2,"1270":4,"1271":6,"1278":1,"1286":9,"1300":9,"1301":1,"1302":2,"1309":6,"1310":1,"1311":2,"1312":8,"1323":2,"1324":8,"1339":1,"1350":5,"1351":1,"1359":21,"1369":1,"1381":1,"1395":5,"1396":10,"1402":3,"1415":5,"1436":21,"1438":2,"1489":2,"1496":1,"1509":1,"1538":1,"1577":3,"1653":1,"1656":1,"1660":1,"1702":1,"1789":1,"1793":1,"2016":1,"2026":1,"2028":2,"2043":1,"2111":1,"2227":1,"2230":1}}],["abstractfitnesscontrollerbase",{"3":{"1199":3,"1207":1,"1436":6}}],["abstractanonymousfixturecontrollerbase",{"3":{"1199":4,"1207":1,"1436":5}}],["abstracts",{"3":{"434":1,"507":2,"1324":1,"1416":1,"1417":2,"1496":1}}],["abstract",{"2":{"1051":1},"3":{"0":13,"47":1,"130":1,"132":1,"135":1,"136":1,"137":1,"140":1,"160":1,"166":1,"255":1,"314":1,"443":1,"536":1,"545":1,"549":2,"572":3,"583":1,"657":2,"723":1,"725":3,"726":2,"800":1,"881":1,"905":1,"907":1,"926":2,"953":1,"972":3,"973":1,"975":2,"1033":1,"1050":3,"1051":1,"1052":1,"1059":1,"1083":1,"1087":1,"1114":1,"1116":1,"1117":1,"1119":1,"1161":1,"1168":2,"1169":1,"1170":1,"1192":1,"1202":1,"1203":1,"1212":4,"1214":1,"1226":1,"1229":4,"1234":1,"1237":10,"1239":2,"1247":8,"1259":19,"1265":4,"1270":21,"1272":1,"1273":2,"1284":1,"1286":30,"1287":1,"1289":2,"1294":1,"1300":15,"1301":1,"1311":24,"1322":1,"1323":38,"1324":11,"1334":1,"1339":6,"1359":30,"1360":1,"1363":2,"1369":5,"1380":1,"1384":1,"1395":23,"1396":6,"1412":1,"1415":14,"1417":2,"1427":2,"1428":1,"1429":1,"1431":4,"1433":1,"1436":193,"1488":8,"1489":1,"1496":1,"1510":1,"1526":2,"1539":1,"1565":1,"1576":1,"1577":4,"1650":1,"1651":2,"1658":1,"1664":1,"1671":1,"1697":1,"1700":1,"1719":1,"1734":1,"1782":1,"1810":2,"1815":2,"1833":1,"1849":1,"1857":1,"1869":1,"1931":1,"2042":1,"2055":2,"2072":1,"2074":1,"2101":2,"2102":1,"2111":1,"2114":2,"2178":2,"2232":1}}],["absexp",{"3":{"245":1,"1301":1,"1438":1}}],["absent",{"1":{"807":1,"808":1,"809":1,"810":1,"811":1,"812":1,"813":1,"814":1,"815":1},"3":{"0":7,"66":1,"74":1,"135":1,"157":1,"158":1,"160":2,"177":1,"178":1,"259":1,"265":1,"303":1,"318":1,"343":1,"397":3,"402":1,"404":1,"409":1,"413":1,"415":1,"468":1,"469":1,"486":1,"490":1,"491":1,"492":1,"493":1,"501":1,"545":1,"583":1,"585":2,"599":2,"608":2,"610":1,"632":1,"658":1,"665":2,"673":1,"674":4,"684":1,"702":1,"703":1,"713":1,"726":1,"727":1,"741":1,"789":1,"798":1,"799":1,"807":1,"808":1,"809":3,"813":2,"827":3,"829":1,"904":1,"905":2,"915":1,"926":2,"946":1,"964":1,"972":1,"974":1,"997":1,"999":1,"1018":1,"1020":1,"1033":1,"1091":1,"1093":1,"1108":1,"1109":1,"1212":2,"1253":1,"1259":4,"1264":1,"1270":5,"1271":1,"1275":1,"1281":1,"1286":22,"1291":1,"1300":19,"1301":2,"1304":1,"1309":8,"1311":13,"1312":2,"1315":1,"1320":1,"1323":15,"1324":19,"1326":1,"1328":1,"1332":2,"1333":1,"1335":1,"1336":1,"1339":19,"1342":1,"1343":1,"1344":1,"1350":15,"1353":1,"1359":38,"1368":2,"1369":4,"1377":1,"1380":5,"1391":2,"1395":13,"1396":22,"1402":6,"1406":3,"1415":16,"1416":1,"1417":17,"1425":1,"1427":3,"1430":1,"1431":1,"1432":1,"1435":2,"1436":52,"1438":2,"1441":2,"1442":4,"1443":1,"1450":1,"1451":1,"1455":2,"1463":1,"1467":5,"1472":2,"1486":2,"1488":2,"1489":1,"1490":1,"1492":1,"1496":3,"1526":1,"1534":1,"1537":1,"1571":1,"1575":1,"1577":4,"1582":2,"1583":1,"1584":2,"1591":2,"1596":1,"1600":1,"1612":1,"1631":1,"1635":1,"1639":3,"1670":1,"1684":1,"1749":1,"1797":1,"1823":1,"1856":1,"1881":1,"1923":1,"1927":1,"1934":1,"1941":1,"1948":1,"1970":1,"1975":1,"1982":1,"1984":1,"1994":2,"2008":1,"2010":1,"2047":1,"2067":1,"2118":1,"2120":1,"2156":2,"2158":2,"2171":1,"2175":1,"2177":1,"2179":2,"2180":1,"2196":1,"2203":1,"2232":2}}],["absences",{"3":{"915":1,"1359":1,"1436":1,"2177":1}}],["absence",{"0":{"2173":1},"3":{"0":6,"109":1,"126":1,"136":1,"160":1,"189":1,"233":1,"401":1,"404":1,"468":1,"487":1,"583":3,"585":2,"592":1,"626":2,"627":1,"629":1,"643":1,"674":1,"690":1,"696":1,"698":1,"768":1,"800":1,"811":1,"813":1,"814":1,"838":1,"861":1,"876":1,"912":1,"913":1,"916":1,"931":1,"972":1,"994":1,"1033":1,"1050":1,"1052":1,"1059":2,"1092":1,"1093":1,"1141":1,"1150":1,"1212":1,"1225":1,"1247":1,"1259":2,"1264":1,"1270":1,"1274":1,"1277":1,"1286":6,"1296":1,"1300":3,"1301":1,"1323":3,"1324":5,"1339":3,"1350":9,"1359":11,"1380":1,"1395":5,"1396":9,"1415":3,"1417":7,"1421":1,"1427":1,"1436":10,"1447":1,"1467":1,"1476":1,"1585":1,"1674":1,"1685":1,"1702":1,"1708":1,"1731":1,"1794":1,"1795":1,"1821":1,"2001":1,"2179":1,"2180":1,"2193":1,"2197":1,"2199":1,"2203":1,"2232":1}}],["absorbing",{"3":{"1339":1,"1577":1}}],["absorbed",{"3":{"235":1,"743":1,"800":1,"883":1,"1150":1,"1258":1,"1259":3,"1323":1,"1324":2,"1350":1,"1359":2,"1380":1,"1396":4,"1402":1,"1436":1,"1450":1,"1496":1,"2112":1}}],["absorb",{"3":{"33":1,"111":1,"390":1,"609":1,"642":1,"799":1,"1157":1,"1259":1,"1270":1,"1300":1,"1301":1,"1311":2,"1402":1,"1417":1,"1467":1,"1534":1,"1947":1,"2027":1,"2165":1}}],["absorbs",{"3":{"18":1,"141":1,"162":1,"201":1,"239":1,"244":2,"735":1,"863":1,"897":1,"1254":1,"1300":1,"1327":1,"1339":2,"1395":1,"1444":1,"1456":1,"1475":1,"1571":1,"1671":1,"1806":1,"1922":1}}],["absorption",{"3":{"0":1,"201":1}}],["absolutepath",{"3":{"1339":1,"1436":5}}],["absolutes",{"3":{"1324":1}}],["absoluteuri",{"3":{"1324":1,"1415":1}}],["absoluteurl",{"3":{"1324":2,"1384":1,"1395":12}}],["absoluteurlrules<",{"3":{"1271":2}}],["absoluteurlrules",{"2":{"1829":1},"3":{"1199":12,"1203":1,"1207":1,"1271":5,"1324":2,"1353":1,"1359":28,"1829":1}}],["absoluteurlattributetests",{"3":{"1199":1,"1207":5,"1438":2}}],["absoluteurlattribute",{"2":{"1669":1},"3":{"1199":1,"1203":1,"1207":2,"1271":3,"1324":8,"1395":7,"1669":1}}],["absoluteexpirationrelativetonow",{"2":{"243":1,"254":1,"257":1,"1301":1},"3":{"243":1,"254":1,"257":1,"1301":3}}],["absolutely",{"3":{"0":1,"591":1,"1492":1,"2047":1,"2049":1,"2232":1}}],["absolute",{"3":{"0":4,"219":1,"243":1,"350":1,"420":1,"500":2,"501":2,"590":1,"591":2,"592":3,"631":1,"860":3,"862":2,"1116":1,"1184":1,"1212":2,"1237":3,"1271":3,"1286":1,"1300":2,"1301":2,"1311":1,"1323":7,"1324":34,"1334":1,"1339":6,"1344":1,"1350":4,"1359":29,"1369":1,"1388":1,"1395":6,"1396":1,"1417":5,"1427":2,"1436":4,"1438":1,"1443":1,"1467":1,"1492":5,"1496":1,"1526":1,"1631":4,"1640":1,"1666":1,"1708":1,"1749":1,"1764":1,"1765":1,"1767":1,"1976":1,"1984":2,"1985":1,"2047":4,"2126":1}}],["apm",{"3":{"1894":1}}],["aps",{"3":{"1323":3,"1417":1}}],["apache",{"3":{"0":1,"440":1,"1075":1,"1577":1,"1673":1,"1780":1,"1787":1,"1793":1,"1802":1,"1807":1,"1812":1,"1817":1,"1827":1,"1834":1,"1846":1,"1853":1,"1862":1,"1868":1,"1877":1,"1886":1,"1892":1,"1899":1,"1905":1,"1912":1,"1924":1,"1930":1,"1941":1,"1950":1,"1959":1,"1965":1,"1972":1,"1979":1,"1985":1,"1991":1,"1997":1,"2002":1,"2014":1,"2028":1,"2038":1,"2049":1,"2060":1,"2069":1,"2080":1,"2086":1,"2098":1,"2108":1,"2115":1,"2123":1,"2127":1,"2139":1,"2146":1,"2153":1,"2204":1,"2211":1,"2216":1,"2229":1,"2234":1,"2242":1}}],["apart",{"3":{"0":2,"23":1,"62":1,"109":1,"235":1,"397":1,"449":1,"626":2,"629":1,"648":1,"691":1,"767":1,"783":1,"784":1,"799":1,"881":1,"974":1,"1059":1,"1068":1,"1083":1,"1124":1,"1126":1,"1191":1,"1211":1,"1237":3,"1238":1,"1240":1,"1247":2,"1259":1,"1270":3,"1271":2,"1280":1,"1281":1,"1286":5,"1290":1,"1299":1,"1300":8,"1301":3,"1305":1,"1309":4,"1311":3,"1323":3,"1324":10,"1325":1,"1332":1,"1339":5,"1350":5,"1358":2,"1359":25,"1369":1,"1371":1,"1380":2,"1395":10,"1396":18,"1398":1,"1402":6,"1405":1,"1410":1,"1415":7,"1416":1,"1417":3,"1436":6,"1438":2,"1441":1,"1443":2,"1458":1,"1459":2,"1487":1,"1488":1,"1496":1,"1503":1,"1653":1,"1677":1,"1748":1,"1805":1,"1834":1,"1948":1,"1982":1,"2055":1,"2083":1,"2130":1,"2131":1,"2167":1}}],["april",{"3":{"0":1,"1074":1}}],["apnsplatform",{"3":{"1309":1}}],["apnspushdevicetokenprovider",{"2":{"434":1,"436":1},"3":{"434":2,"436":2,"1416":1,"1417":4}}],["apnstokenbridge",{"2":{"436":1},"3":{"436":1,"1416":3,"1417":1}}],["apns",{"3":{"0":2,"413":1,"432":1,"433":2,"434":1,"435":1,"436":1,"1286":1,"1308":1,"1309":3,"1323":7,"1416":6,"1417":15,"1467":1,"1481":1,"1496":1,"1669":2,"1932":1,"1936":1,"1940":1,"2122":1,"2232":1}}],["api1",{"3":{"1993":1}}],["apibase",{"3":{"1417":1}}],["apibaseaddress",{"3":{"1311":1}}],["api=1",{"3":{"1417":1}}],["apiuri",{"3":{"1324":1}}],["apiuserpreferencereader",{"3":{"1199":2,"1203":1,"1207":2,"1300":2,"1324":11,"1496":1}}],["apiuserpreferencewritertests",{"3":{"1199":1,"1207":2,"1324":1,"1438":2}}],["apiuserpreferencewriter",{"3":{"1199":3,"1203":1,"1207":3,"1300":2,"1324":31}}],["apioptions",{"3":{"1324":2}}],["apiece",{"3":{"1396":1}}],["apiexplorer",{"3":{"1311":1}}],["apiexplorersettings",{"3":{"1311":1}}],["apiendpoint",{"2":{"1188":1},"3":{"1184":2,"1188":1,"1324":30,"1416":2,"1436":3,"1441":1,"1489":1,"2150":1}}],["apidesign",{"3":{"1286":1,"1324":1}}],["apidescription",{"2":{"451":1,"452":1},"3":{"451":1,"452":1,"1311":1}}],["apifiledownloadbuttontests",{"3":{"1199":1,"1207":2,"1324":1,"1438":2}}],["apifiledownloadbutton",{"3":{"1184":2,"1199":2,"1203":1,"1207":2,"1324":12,"1436":3}}],["apikeycredential",{"2":{"1420":1},"3":{"1420":1,"1427":2}}],["apikey",{"2":{"1017":1,"2173":1},"3":{"1017":2,"1091":2,"1096":1,"1419":3,"1427":7,"1467":2,"1492":1,"2173":2}}],["apiparameterdescription",{"2":{"448":1,"2131":1},"3":{"448":1,"2131":1}}],["apiparameterdescriptorbackfillprovidertests",{"3":{"1199":1,"1207":4}}],["apiparameterdescriptorbackfillprovider",{"2":{"448":1,"2131":1},"3":{"0":1,"448":2,"1199":3,"1203":1,"1207":2,"1311":11,"2131":2}}],["apiversioningtests",{"3":{"448":3,"572":3,"1199":1,"1207":2,"1436":2,"1488":1,"1526":1,"1535":1,"1591":1,"2131":2}}],["apiversion",{"2":{"450":1,"455":1,"1753":1,"1978":1,"2132":1},"3":{"330":1,"350":1,"448":4,"450":1,"455":2,"1309":2,"1311":7,"1376":3,"1380":5,"1415":5,"1436":3,"1489":1,"1496":1,"1526":1,"1591":2,"1753":1,"1978":1,"1988":1,"2131":3,"2132":2}}],["apicontrollerattribute",{"3":{"1436":1}}],["apicontroller",{"2":{"455":1,"1978":1},"3":{"330":1,"350":1,"455":1,"1309":1,"1311":7,"1380":2,"1415":4,"1436":2,"1978":1,"1988":1}}],["apicontrollerbasetests",{"3":{"1199":2,"1207":3,"1311":2}}],["apicontrollerbase",{"2":{"109":1,"111":1,"265":1,"300":1,"1224":1,"1498":1,"1805":1,"1930":1,"2018":1},"3":{"109":6,"111":1,"265":1,"300":2,"306":1,"1199":31,"1203":1,"1207":2,"1224":5,"1229":4,"1270":1,"1300":1,"1309":4,"1311":38,"1312":3,"1323":1,"1371":2,"1380":8,"1396":6,"1402":3,"1415":4,"1436":11,"1438":1,"1487":1,"1489":1,"1496":2,"1498":1,"1805":1,"1927":1,"1930":1,"2018":1}}],["apiclientname",{"3":{"1324":4}}],["apiclientregistrationtests",{"3":{"1199":1,"1207":2,"1324":1}}],["apiclient",{"3":{"0":2,"265":1,"507":1,"514":1,"881":1,"883":1,"1183":1,"1184":2,"1324":38,"1395":2,"1396":7,"1416":3,"1417":3,"1436":4,"1438":1,"2074":1}}],["apisectionname",{"2":{"1324":1},"3":{"1324":3}}],["apisettings",{"2":{"216":1,"219":1,"1184":1,"2152":1},"3":{"214":1,"216":2,"219":1,"649":1,"674":1,"881":1,"1184":2,"1188":1,"1199":24,"1203":1,"1207":2,"1324":29,"1417":1,"1436":1,"1496":1,"1653":1,"2150":2,"2152":2}}],["apis",{"1":{"1906":1,"1907":1,"1908":1,"1909":1,"1910":1,"1911":1,"1912":1},"3":{"0":1,"412":1,"447":1,"774":1,"1050":1,"1311":1,"1324":1,"1339":2,"1350":1,"1369":1,"1380":1,"1396":1,"1415":2,"1417":4,"1436":4,"1546":1,"1548":1,"1627":1,"1735":1,"1906":1,"1926":1,"1993":1,"2116":1,"2129":1,"2130":1,"2133":1,"2208":1}}],["api",{"0":{"138":1,"1546":1,"1640":1,"1666":1,"2166":1,"2201":1,"2208":1},"1":{"445":1,"446":1,"447":1,"448":1,"449":1,"450":1,"451":1,"452":1,"453":1,"454":1,"455":1,"1181":1,"1182":1,"1183":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1,"1311":1,"1370":1,"1371":1,"1372":1,"1373":1,"1374":1,"1375":1,"1376":1,"1377":1,"1378":1,"1379":1,"1380":1,"1998":1,"1999":1,"2000":1,"2001":1,"2002":1,"2129":1,"2130":1,"2131":1,"2132":1,"2133":1},"2":{"61":1,"160":1,"207":1,"250":1,"252":1,"388":1,"460":1,"509":1,"527":1,"789":1,"826":1,"938":1,"1020":2,"1207":1,"1271":1,"1336":1,"1370":1,"1380":7,"1455":1,"1466":1,"1470":1,"1472":1,"1489":1,"1504":1,"1615":1,"1648":1,"1650":2,"1653":1,"1702":1,"1793":1,"1807":1,"1812":1,"1817":1,"1827":1,"1846":1,"1853":1,"1868":1,"1877":1,"1886":1,"1892":1,"1899":1,"1912":1,"1924":1,"1930":1,"1941":1,"1965":1,"1972":1,"1975":1,"1979":1,"1991":1,"1997":1,"2002":1,"2014":1,"2115":1,"2133":1,"2169":1,"2180":1,"2193":1,"2203":1,"2211":1},"3":{"0":26,"4":1,"31":1,"34":1,"42":1,"58":1,"59":1,"61":1,"81":2,"99":1,"103":2,"109":15,"110":1,"111":2,"113":1,"125":2,"130":4,"135":7,"138":1,"139":1,"140":1,"141":1,"142":2,"143":1,"147":1,"150":1,"157":2,"160":5,"161":2,"166":1,"175":3,"178":3,"179":3,"181":1,"186":11,"189":4,"190":2,"206":2,"207":5,"208":2,"209":4,"210":1,"212":1,"213":2,"214":2,"215":1,"216":2,"220":2,"225":1,"235":1,"238":1,"241":1,"243":17,"245":1,"246":1,"250":1,"252":1,"255":4,"256":4,"258":4,"259":3,"265":6,"290":1,"295":2,"296":1,"300":3,"301":1,"303":2,"306":3,"318":13,"325":3,"330":4,"332":1,"337":1,"341":6,"342":1,"343":1,"345":1,"350":1,"352":3,"354":1,"384":2,"386":1,"387":1,"388":3,"391":2,"397":1,"400":1,"407":2,"409":1,"418":2,"419":2,"423":2,"424":1,"426":1,"428":1,"434":3,"445":1,"446":1,"448":20,"449":1,"450":1,"453":1,"454":2,"458":1,"459":4,"460":1,"463":1,"464":2,"470":1,"483":1,"506":3,"507":3,"509":3,"511":2,"517":2,"527":2,"528":6,"539":1,"540":1,"545":3,"549":1,"550":1,"551":1,"556":2,"558":2,"571":2,"572":1,"576":1,"579":1,"583":13,"585":1,"586":2,"591":1,"626":3,"627":2,"628":1,"629":1,"635":1,"656":1,"657":4,"660":1,"674":11,"675":2,"676":2,"690":1,"691":1,"692":1,"698":3,"707":1,"723":1,"725":6,"727":1,"728":1,"733":2,"734":1,"740":3,"741":6,"743":3,"744":1,"749":4,"751":1,"773":2,"774":4,"775":1,"776":2,"789":3,"790":2,"793":1,"799":1,"803":1,"826":1,"827":4,"829":1,"833":1,"854":1,"880":2,"881":6,"882":1,"883":1,"897":3,"905":1,"906":1,"921":1,"922":1,"938":2,"939":1,"940":1,"942":1,"956":1,"960":1,"963":1,"964":3,"965":1,"966":1,"968":1,"972":1,"975":1,"976":1,"979":2,"980":20,"981":1,"982":1,"983":1,"984":1,"995":1,"996":3,"999":1,"1003":1,"1004":7,"1006":3,"1007":1,"1008":2,"1011":1,"1012":1,"1017":2,"1018":3,"1020":2,"1026":2,"1034":2,"1035":1,"1049":1,"1050":5,"1052":2,"1053":1,"1055":2,"1058":1,"1059":3,"1061":2,"1067":2,"1076":1,"1078":1,"1091":1,"1099":2,"1103":1,"1108":2,"1116":3,"1117":1,"1119":1,"1124":4,"1150":2,"1161":1,"1163":1,"1170":1,"1174":1,"1175":1,"1176":1,"1181":1,"1182":1,"1183":5,"1184":9,"1185":3,"1186":6,"1187":5,"1188":2,"1192":4,"1198":6,"1199":408,"1201":2,"1202":6,"1203":206,"1206":10,"1207":1344,"1208":60,"1211":3,"1212":11,"1213":4,"1216":1,"1218":1,"1222":2,"1224":1,"1225":1,"1226":1,"1227":2,"1229":30,"1234":1,"1235":1,"1237":21,"1239":1,"1241":2,"1245":2,"1247":26,"1249":2,"1259":13,"1266":1,"1269":2,"1270":23,"1271":11,"1281":1,"1285":1,"1286":36,"1287":2,"1288":3,"1289":1,"1290":1,"1294":1,"1297":14,"1298":9,"1300":422,"1301":14,"1303":1,"1304":4,"1306":1,"1308":6,"1309":59,"1310":1,"1311":540,"1312":9,"1314":1,"1315":3,"1320":2,"1323":63,"1324":214,"1336":2,"1337":2,"1338":1,"1339":23,"1341":2,"1347":6,"1348":1,"1349":1,"1350":59,"1351":1,"1353":1,"1358":6,"1359":299,"1364":3,"1365":1,"1367":1,"1369":9,"1370":5,"1371":7,"1372":2,"1373":2,"1374":1,"1376":5,"1377":8,"1379":2,"1380":254,"1382":5,"1384":1,"1387":4,"1389":6,"1390":2,"1391":2,"1395":84,"1396":229,"1401":1,"1402":77,"1403":1,"1404":1,"1406":4,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1414":2,"1415":182,"1416":8,"1417":32,"1420":1,"1421":1,"1425":1,"1427":3,"1431":3,"1435":2,"1436":287,"1437":19,"1438":27,"1441":5,"1443":2,"1444":1,"1447":2,"1454":3,"1455":9,"1466":2,"1467":9,"1470":1,"1472":3,"1475":1,"1481":4,"1486":4,"1487":6,"1488":3,"1489":10,"1492":6,"1493":1,"1496":52,"1499":1,"1503":1,"1504":1,"1509":1,"1513":2,"1524":1,"1526":13,"1530":1,"1531":2,"1532":1,"1534":1,"1535":3,"1540":1,"1546":2,"1547":1,"1551":1,"1563":1,"1577":22,"1582":5,"1583":1,"1585":3,"1586":2,"1591":8,"1596":1,"1597":1,"1600":1,"1602":1,"1611":1,"1615":1,"1619":1,"1627":4,"1630":2,"1631":7,"1632":76,"1635":1,"1636":2,"1638":1,"1639":21,"1640":27,"1641":10,"1647":2,"1648":1,"1650":2,"1651":3,"1653":10,"1656":1,"1660":3,"1661":1,"1662":1,"1663":1,"1665":1,"1666":1,"1669":3,"1670":1,"1673":3,"1675":1,"1681":1,"1685":3,"1686":2,"1687":1,"1689":1,"1693":1,"1698":3,"1702":4,"1707":1,"1718":3,"1719":9,"1722":1,"1724":2,"1727":4,"1728":2,"1729":2,"1732":1,"1736":2,"1737":1,"1749":1,"1750":1,"1753":3,"1755":1,"1761":2,"1765":5,"1770":2,"1771":1,"1773":1,"1776":1,"1779":4,"1781":1,"1793":1,"1796":1,"1805":2,"1807":1,"1810":1,"1812":1,"1817":1,"1821":1,"1827":1,"1831":1,"1834":1,"1844":1,"1846":1,"1853":1,"1868":1,"1871":1,"1877":1,"1883":1,"1886":1,"1892":1,"1895":1,"1898":2,"1899":1,"1912":3,"1913":1,"1919":1,"1921":1,"1922":3,"1924":1,"1927":1,"1928":2,"1929":3,"1930":3,"1931":1,"1932":2,"1941":1,"1951":1,"1959":2,"1965":1,"1966":2,"1967":2,"1968":4,"1971":5,"1972":4,"1975":1,"1979":1,"1985":1,"1988":2,"1990":2,"1991":4,"1993":1,"1994":4,"1995":7,"1997":4,"1998":1,"2000":6,"2001":1,"2002":2,"2004":1,"2012":1,"2014":1,"2021":1,"2024":1,"2043":2,"2055":1,"2066":1,"2070":1,"2071":1,"2074":1,"2080":1,"2081":1,"2083":4,"2088":1,"2089":1,"2097":1,"2101":1,"2104":2,"2105":1,"2106":1,"2107":1,"2111":1,"2115":1,"2128":1,"2129":2,"2130":1,"2131":9,"2132":1,"2133":5,"2136":1,"2137":1,"2138":1,"2139":1,"2147":2,"2149":1,"2150":2,"2152":2,"2153":3,"2156":1,"2169":1,"2172":1,"2180":1,"2193":1,"2194":1,"2200":1,"2203":1,"2208":1,"2210":2,"2211":1,"2213":1,"2219":2,"2220":4,"2225":1,"2227":4,"2232":2,"2236":1,"2239":1}}],["app→db",{"3":{"1526":1}}],["apptraces",{"3":{"1467":3,"1476":1}}],["appthemebinding",{"3":{"1416":1}}],["apptheme",{"3":{"1416":2}}],["appxmanifest",{"3":{"1416":2}}],["appwidgetmanager",{"3":{"1416":1}}],["appwidgetprovider",{"3":{"1416":1}}],["appicon",{"3":{"1416":1}}],["appcontext",{"2":{"1035":1},"3":{"1034":1,"1035":1,"1427":2,"1436":1,"1489":1}}],["apparent",{"3":{"1214":1}}],["appaction",{"3":{"1416":3}}],["appactions",{"3":{"1416":5}}],["appactionsinitializer",{"2":{"428":1},"3":{"428":1,"1199":2,"1203":1,"1207":2,"1416":17}}],["appactionroutemaptests",{"3":{"1199":1,"1207":2,"1324":2,"1416":2,"1438":1}}],["appactionroutemap",{"2":{"428":1},"3":{"428":1,"1199":4,"1203":1,"1207":2,"1416":18}}],["appassociationendpointtests",{"3":{"1199":1,"1207":2,"1311":2}}],["appassociationendpointextensions",{"3":{"418":2,"423":1,"424":1,"1199":2,"1203":1,"1207":2,"1208":1,"1311":7,"1496":1}}],["appassociation",{"2":{"429":1},"3":{"418":2,"423":1,"424":1,"425":1,"426":1,"428":2,"429":2,"1311":2,"1481":1}}],["appassociationoptions",{"2":{"418":1,"425":1,"426":1,"428":1},"3":{"418":1,"425":1,"426":1,"428":1,"429":1,"1199":3,"1203":1,"1207":2,"1311":12,"1496":1}}],["appassembly",{"3":{"0":1,"649":1,"905":1,"1416":1}}],["appdatadirectory",{"3":{"1417":1}}],["appdelegate",{"3":{"1199":2,"1203":1,"1207":4,"1212":1,"1416":24,"1417":3,"1496":4}}],["appdependencies",{"2":{"1476":1},"3":{"402":1,"1333":1,"1339":2,"1443":1,"1446":1,"1447":1,"1467":4,"1476":1,"1677":2}}],["appdomainsource",{"3":{"1417":1}}],["appdomain",{"2":{"413":1},"3":{"413":1,"1212":1,"1286":5,"1311":1,"1315":2,"1323":1,"1416":2,"1417":5,"1881":1}}],["appraisal",{"3":{"2220":1}}],["appraiser",{"3":{"2220":1}}],["appresumedeventargs",{"3":{"1199":4,"1203":1,"1207":2,"1417":10,"1496":1}}],["apprendermode",{"3":{"556":1}}],["apprequests",{"2":{"612":1,"1450":1,"1451":1},"3":{"402":1,"612":1,"1333":1,"1339":2,"1443":3,"1446":1,"1447":1,"1450":1,"1451":1,"1467":7,"1476":1,"1677":1,"2010":1}}],["approvals",{"3":{"1455":1}}],["approval",{"3":{"1402":2,"1436":3,"1457":1,"1461":1,"1467":1,"1641":1,"1765":1}}],["approving",{"3":{"1350":1,"1396":1,"1399":1,"1402":1,"1426":1,"1459":1,"1632":1}}],["approvequestionasync",{"3":{"1402":1}}],["approveasync",{"3":{"1396":2,"1402":1}}],["approve",{"3":{"1396":2,"1397":1,"1398":1,"1402":17,"1436":1,"1526":1}}],["approves",{"3":{"1349":1,"1350":1,"1402":1,"1632":1}}],["approver",{"3":{"628":1}}],["approvedquestions",{"3":{"1402":2}}],["approved",{"3":{"522":1,"523":1,"524":1,"1100":1,"1286":1,"1311":1,"1349":1,"1350":4,"1396":4,"1398":2,"1399":1,"1401":3,"1402":46,"1436":1,"1496":16,"1630":2,"1632":8,"2043":1}}],["approximation",{"3":{"1286":1,"1427":1,"1500":2,"2059":1}}],["approximates",{"3":{"1436":1}}],["approximated",{"3":{"1436":1}}],["approximate",{"3":{"827":1,"1339":1,"1417":4,"1427":1,"1591":1}}],["approlename",{"3":{"609":1,"1467":1}}],["appropriately",{"3":{"1237":1,"1562":1}}],["appropriate",{"3":{"86":1,"1286":1,"1324":1,"1350":1,"1416":1,"1417":1,"1539":1,"1549":1,"1568":1,"1632":2,"1639":2,"1765":1}}],["approachingquota",{"2":{"403":1,"612":1},"3":{"403":1,"612":1}}],["approach",{"3":{"69":1,"79":1,"131":1,"455":1,"1060":1,"1259":1,"1286":1,"1395":1,"1436":7,"1445":1,"1638":1,"1639":1,"1819":1,"1830":1,"1889":1}}],["approaches",{"3":{"4":1,"1153":1,"1323":1,"1324":1,"1396":1,"1943":1}}],["appbarbackground",{"3":{"1324":1}}],["appbarcomponenttypes",{"2":{"649":1,"650":1},"3":{"649":2,"650":1,"1324":3,"1395":1}}],["appbar",{"3":{"273":1,"1436":2,"2083":1}}],["appmetrics",{"3":{"0":1,"914":1,"1443":2,"1451":1,"1467":9,"1476":1,"1677":2}}],["appetite",{"3":{"2032":1}}],["appearance",{"3":{"1212":1,"1324":1,"2230":1}}],["appearing",{"3":{"629":1,"827":1,"840":1,"1369":2,"1396":1,"1417":1,"1436":7,"1468":1,"1488":2}}],["appeared",{"3":{"254":1,"454":1,"1324":2,"1492":1,"2119":1}}],["appear",{"3":{"37":1,"303":1,"502":1,"564":1,"799":1,"812":1,"964":1,"1116":1,"1247":2,"1270":1,"1285":1,"1286":5,"1300":3,"1309":1,"1311":1,"1323":3,"1324":4,"1339":3,"1350":2,"1359":6,"1395":7,"1396":6,"1402":2,"1404":1,"1415":2,"1416":3,"1417":4,"1433":1,"1436":28,"1438":4,"1441":1,"1467":3,"1470":1,"1489":1,"1496":1,"1499":1,"1625":1,"1626":2,"1627":1,"1631":1,"1700":1,"1760":1,"1764":1,"2016":1}}],["appears",{"3":{"0":5,"95":1,"265":1,"349":1,"390":1,"420":1,"450":1,"470":1,"490":1,"491":1,"492":2,"493":1,"545":1,"547":1,"549":1,"550":1,"556":1,"599":1,"634":1,"639":1,"649":1,"676":2,"700":1,"773":1,"782":1,"810":1,"827":3,"832":1,"833":1,"838":1,"881":1,"891":1,"963":1,"980":1,"1004":1,"1026":1,"1079":1,"1082":1,"1085":1,"1109":1,"1116":1,"1155":1,"1190":1,"1191":1,"1193":1,"1200":1,"1202":1,"1229":2,"1242":2,"1247":2,"1270":2,"1273":1,"1286":5,"1289":1,"1300":7,"1309":1,"1311":4,"1312":1,"1323":3,"1324":5,"1339":4,"1350":3,"1358":1,"1359":15,"1369":1,"1380":2,"1395":4,"1396":7,"1402":1,"1413":1,"1415":5,"1417":4,"1425":1,"1433":2,"1436":22,"1438":2,"1443":1,"1456":1,"1467":2,"1469":1,"1489":1,"1496":3,"1526":1,"1577":2,"1582":1,"1583":1,"1626":1,"1627":1,"1630":1,"1631":2,"1632":1,"1639":1,"1651":1,"1653":2,"1654":1,"1663":1,"1698":1,"1749":1,"1815":1,"1881":1,"1913":1,"1970":1,"1993":1,"2180":1}}],["appendix",{"0":{"1572":1}}],["appending",{"3":{"749":1,"889":1,"890":2,"1267":1,"1270":1,"1281":1,"1286":4,"1300":1,"1322":1,"1324":1,"1339":2,"1359":1,"1369":1,"1395":1,"1396":1,"1411":1,"1415":1,"1436":2,"1826":1,"2232":1}}],["appendquery",{"3":{"1311":2}}],["appendevent",{"3":{"1300":1}}],["appended",{"3":{"0":4,"21":1,"122":1,"166":1,"545":1,"566":1,"583":1,"601":1,"665":1,"889":1,"890":2,"1034":1,"1140":1,"1174":1,"1195":1,"1212":1,"1242":1,"1247":2,"1270":1,"1279":1,"1286":2,"1300":3,"1311":3,"1312":1,"1323":2,"1324":5,"1339":2,"1359":1,"1369":1,"1395":1,"1415":1,"1417":1,"1436":4,"1438":2,"1455":3,"1466":4,"1661":1,"1664":1,"1701":1,"1729":1,"1749":1,"1766":1,"1779":3,"1920":1,"2089":1}}],["appendline",{"3":{"1300":2,"1496":1}}],["appendable",{"3":{"1237":1}}],["appends",{"3":{"0":1,"123":1,"177":1,"219":1,"246":1,"350":1,"549":1,"633":1,"887":1,"891":1,"905":1,"1133":1,"1237":1,"1247":3,"1267":1,"1269":1,"1270":1,"1282":1,"1286":6,"1300":10,"1311":6,"1323":2,"1324":10,"1339":2,"1359":6,"1380":5,"1383":1,"1386":1,"1388":1,"1389":1,"1395":4,"1396":1,"1402":2,"1412":1,"1415":6,"1416":5,"1417":3,"1436":6,"1489":1,"1666":1,"1727":1,"1840":1,"1852":1,"1920":1,"2197":1,"2232":1}}],["append",{"2":{"1968":1},"3":{"0":1,"137":1,"138":1,"139":1,"140":1,"265":1,"420":1,"633":1,"690":1,"692":1,"719":1,"784":1,"889":1,"891":1,"1140":1,"1142":2,"1144":1,"1179":1,"1192":1,"1212":1,"1242":1,"1281":1,"1286":6,"1300":5,"1311":1,"1323":1,"1324":6,"1359":1,"1395":1,"1396":8,"1436":1,"1467":1,"1496":4,"1629":1,"1631":1,"1638":1,"1839":1,"1968":1,"1975":1,"2232":1}}],["apphostbicepparitytests",{"3":{"1199":1,"1207":2,"1437":2,"1438":2,"1487":1,"1489":2,"1493":1}}],["apphostenvironmentrequirement",{"2":{"1069":1,"1428":1,"1430":1},"3":{"1069":1,"1199":6,"1207":2,"1339":3,"1428":1,"1430":2,"1488":2}}],["apphostenvironmentgatetests",{"3":{"1199":1,"1207":2,"1487":1}}],["apphostenvironmentgate",{"2":{"1067":1,"1430":1,"1449":1,"2056":1},"3":{"1067":3,"1199":3,"1207":2,"1428":1,"1430":4,"1449":1,"1488":2,"2056":1}}],["apphostprobepathstests",{"3":{"1199":1,"1207":2,"1487":1}}],["apphostprobepaths",{"3":{"1067":1,"1199":5,"1207":2,"1428":1,"1430":2,"1488":2}}],["apphostreadinessbudgettests",{"3":{"1199":1,"1207":2,"1487":1}}],["apphostreadinessbudget",{"2":{"1430":1},"3":{"1067":1,"1199":5,"1207":2,"1339":2,"1428":1,"1430":2,"1449":2,"1488":2}}],["apphostcompositionsmoketests",{"3":{"914":1,"1067":1,"1069":1,"1487":1,"1496":1}}],["apphosttestbase",{"2":{"2056":1},"3":{"914":1,"1067":3,"1069":1,"1199":3,"1207":2,"1328":1,"1339":1,"1428":1,"1430":2,"1449":4,"1488":2,"2055":1,"2056":1}}],["apphosttests",{"2":{"1430":1,"1437":1,"1487":1,"1488":1,"1496":1},"3":{"913":2,"917":1,"1067":2,"1069":1,"1199":3,"1203":1,"1206":1,"1207":9,"1430":2,"1437":1,"1486":1,"1487":1,"1488":1,"1496":1}}],["apphostfixturebase",{"2":{"912":1,"1069":1,"1328":1,"1460":1,"2056":1},"3":{"912":1,"913":1,"914":2,"1067":5,"1069":1,"1070":1,"1199":5,"1207":4,"1212":1,"1328":1,"1339":5,"1428":1,"1430":8,"1436":3,"1449":5,"1460":1,"1488":4,"1490":1,"1496":1,"2055":1,"2056":1}}],["apphosts",{"3":{"99":1,"555":1,"556":1,"559":1,"1339":1,"1444":1,"1487":1}}],["apphost",{"0":{"1430":1,"1441":1,"1444":1,"1449":1,"2008":1,"2022":1,"2056":1},"1":{"1064":1,"1065":1,"1066":1,"1067":1,"1068":1,"1069":1,"1070":1,"1071":1},"2":{"913":1,"914":1,"1069":2,"1325":1,"1328":1,"1449":1,"1492":1,"1657":1,"2056":1},"3":{"0":13,"31":2,"55":1,"59":2,"63":2,"92":1,"93":2,"95":2,"96":2,"98":2,"99":1,"104":2,"164":1,"166":1,"350":2,"373":1,"554":3,"556":2,"557":1,"624":1,"626":1,"629":1,"632":3,"633":4,"638":3,"640":13,"641":1,"642":4,"644":5,"837":3,"838":2,"839":2,"841":1,"854":2,"857":2,"912":2,"913":12,"914":8,"915":1,"916":2,"917":5,"918":1,"939":1,"980":1,"1033":1,"1034":1,"1037":1,"1038":1,"1064":1,"1065":1,"1066":5,"1067":14,"1068":1,"1069":7,"1070":1,"1150":2,"1192":2,"1199":4,"1202":1,"1203":5,"1206":2,"1207":12,"1208":2,"1212":5,"1213":1,"1309":3,"1311":5,"1312":2,"1323":3,"1325":3,"1326":10,"1327":2,"1328":5,"1334":1,"1339":86,"1363":1,"1364":2,"1369":3,"1377":2,"1378":2,"1380":5,"1396":4,"1407":2,"1412":1,"1415":5,"1428":1,"1430":8,"1436":7,"1437":6,"1438":5,"1439":1,"1440":3,"1441":26,"1442":2,"1444":11,"1445":1,"1446":5,"1448":1,"1449":7,"1450":1,"1451":3,"1453":7,"1455":4,"1456":6,"1460":3,"1461":2,"1464":1,"1467":2,"1486":4,"1487":5,"1488":5,"1489":5,"1490":13,"1492":6,"1493":1,"1496":6,"1503":1,"1526":9,"1535":1,"1570":1,"1577":4,"1582":1,"1596":2,"1606":1,"1627":2,"1641":1,"1653":8,"1656":1,"1657":1,"1660":1,"1661":1,"1663":1,"1670":2,"1683":1,"1684":2,"1689":1,"1692":1,"1698":1,"1700":2,"1718":5,"1720":1,"1724":1,"1727":7,"1728":1,"1729":2,"1740":1,"1792":1,"1842":1,"1978":1,"2004":2,"2005":2,"2008":4,"2009":1,"2013":1,"2014":5,"2022":1,"2028":1,"2030":1,"2055":2,"2056":3,"2060":1,"2089":1,"2097":1,"2227":1,"2232":4}}],["app",{"0":{"1291":1,"1727":1,"1937":1,"2008":1,"2113":1},"1":{"417":1,"418":1,"419":1,"420":1,"421":1,"422":1,"423":1,"424":1,"425":1,"426":1,"427":1,"428":1,"429":1,"430":1,"631":1,"632":1,"633":1,"634":1,"635":1,"636":1,"1302":1,"1303":1,"1304":1,"1305":1,"1306":1,"1307":1,"1308":1,"1309":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1698":1,"1699":1,"1700":1,"1701":1,"1702":1,"1703":1,"1704":1,"1705":1,"1726":1,"1727":1,"1728":1,"1729":1,"1730":1,"1731":1,"1931":1,"1932":1,"1933":1,"1934":1,"1935":1,"1936":1,"1937":1,"1938":1,"1939":1,"1940":1,"1941":1,"2003":1,"2004":1,"2005":1,"2006":1,"2007":1,"2008":1,"2009":1,"2010":1,"2011":1,"2012":1,"2013":1,"2014":1},"2":{"220":1,"254":1,"256":1,"258":1,"418":1,"425":1,"426":1,"428":1,"454":1,"464":2,"466":1,"554":1,"555":1,"559":1,"648":1,"674":1,"760":1,"774":1,"776":1,"1091":1,"1613":1,"1699":1,"1727":1,"1787":1,"1921":1,"2004":1,"2075":1,"2079":1,"2089":1,"2151":1},"3":{"0":40,"38":1,"39":1,"46":1,"60":1,"68":2,"72":1,"85":1,"92":1,"93":1,"94":2,"95":2,"96":1,"98":1,"101":1,"102":3,"145":2,"147":3,"175":3,"180":1,"186":3,"208":2,"213":2,"215":1,"216":3,"217":1,"219":3,"220":1,"223":2,"224":1,"225":2,"226":1,"230":2,"235":3,"237":1,"241":1,"242":1,"243":3,"254":1,"256":1,"258":2,"259":1,"261":1,"264":1,"265":4,"267":1,"272":1,"273":3,"276":1,"281":1,"284":1,"293":1,"309":1,"310":4,"311":1,"314":3,"349":1,"350":6,"351":3,"352":1,"355":1,"360":1,"378":1,"381":1,"382":1,"405":1,"413":7,"414":1,"417":1,"418":6,"419":8,"420":4,"422":4,"423":3,"424":1,"425":1,"426":2,"427":5,"428":4,"429":1,"433":1,"435":1,"440":1,"441":1,"454":1,"457":1,"459":10,"460":2,"461":2,"463":5,"464":4,"465":5,"466":1,"470":1,"497":3,"498":3,"499":6,"500":1,"511":4,"512":1,"545":1,"551":2,"554":1,"555":2,"556":17,"559":2,"562":1,"563":1,"564":1,"566":1,"576":1,"598":8,"599":10,"600":1,"601":6,"603":1,"617":1,"618":2,"619":1,"620":1,"626":2,"631":5,"632":1,"633":13,"634":1,"635":7,"638":2,"640":5,"641":1,"643":4,"648":2,"649":2,"650":4,"659":1,"664":2,"667":1,"672":1,"674":1,"681":2,"682":1,"683":3,"723":2,"724":8,"725":11,"726":3,"727":3,"749":6,"757":9,"758":1,"759":6,"760":1,"764":1,"765":2,"766":1,"767":1,"768":1,"774":5,"776":1,"782":1,"790":1,"793":1,"827":1,"828":1,"832":3,"833":2,"837":1,"854":3,"857":1,"861":1,"862":1,"863":2,"873":1,"904":1,"905":1,"912":1,"913":2,"914":7,"915":3,"917":1,"918":2,"932":2,"938":1,"941":1,"946":1,"954":1,"959":1,"971":1,"995":1,"998":1,"1017":2,"1018":1,"1022":1,"1034":1,"1036":1,"1037":1,"1058":4,"1059":10,"1060":2,"1063":1,"1067":1,"1083":1,"1091":1,"1092":1,"1093":1,"1117":2,"1125":1,"1126":1,"1150":1,"1154":1,"1156":1,"1163":1,"1178":1,"1184":2,"1186":1,"1190":2,"1192":4,"1194":1,"1199":3,"1202":4,"1203":6,"1207":4,"1208":13,"1211":1,"1212":8,"1237":1,"1270":9,"1271":1,"1283":1,"1284":3,"1286":20,"1287":1,"1288":1,"1289":11,"1290":1,"1291":4,"1292":1,"1293":8,"1295":1,"1296":1,"1297":3,"1298":1,"1299":2,"1300":123,"1301":3,"1302":4,"1304":1,"1308":1,"1309":24,"1310":1,"1311":92,"1312":1,"1320":2,"1322":10,"1323":88,"1324":152,"1325":1,"1326":1,"1332":1,"1333":1,"1338":1,"1339":36,"1350":5,"1352":1,"1359":5,"1364":1,"1369":3,"1380":3,"1395":16,"1396":5,"1409":1,"1415":46,"1416":142,"1417":161,"1421":1,"1425":1,"1427":1,"1428":1,"1433":3,"1434":1,"1436":65,"1438":7,"1441":1,"1442":1,"1443":5,"1445":7,"1446":1,"1447":3,"1455":14,"1456":1,"1457":4,"1459":2,"1463":1,"1464":1,"1465":2,"1467":62,"1468":1,"1469":1,"1470":1,"1472":2,"1473":4,"1474":7,"1476":3,"1477":4,"1478":1,"1479":1,"1480":1,"1481":6,"1487":1,"1488":7,"1489":5,"1490":2,"1492":3,"1496":15,"1500":1,"1512":1,"1524":1,"1526":9,"1531":2,"1534":1,"1550":2,"1562":1,"1565":1,"1577":7,"1582":1,"1583":1,"1585":2,"1586":1,"1589":4,"1590":1,"1591":9,"1595":1,"1596":6,"1597":2,"1599":2,"1600":1,"1601":2,"1602":1,"1606":1,"1611":2,"1613":1,"1641":6,"1646":5,"1647":1,"1648":5,"1649":1,"1650":2,"1651":8,"1653":2,"1654":5,"1655":4,"1656":1,"1657":1,"1658":2,"1661":8,"1666":1,"1667":4,"1669":4,"1670":4,"1671":1,"1673":1,"1674":1,"1677":1,"1681":1,"1684":1,"1685":1,"1686":1,"1687":1,"1688":1,"1689":1,"1690":1,"1691":1,"1692":3,"1694":1,"1695":1,"1696":1,"1698":1,"1699":1,"1701":1,"1702":1,"1704":1,"1705":2,"1706":1,"1707":1,"1709":4,"1714":1,"1717":3,"1718":3,"1719":1,"1720":1,"1721":1,"1722":2,"1723":2,"1725":1,"1726":5,"1727":7,"1728":3,"1729":4,"1730":2,"1731":2,"1740":1,"1750":2,"1773":2,"1781":2,"1784":1,"1785":2,"1787":2,"1810":1,"1825":1,"1884":1,"1896":2,"1901":1,"1921":2,"1922":1,"1930":1,"1931":1,"1932":1,"1933":4,"1936":1,"1945":1,"1949":1,"1964":1,"1966":4,"1970":1,"1972":1,"1973":3,"1974":2,"1975":2,"1976":3,"1977":1,"1978":3,"1979":2,"1981":1,"1983":2,"2000":1,"2001":3,"2002":3,"2003":2,"2004":5,"2006":2,"2007":1,"2012":1,"2014":1,"2025":1,"2033":3,"2034":2,"2036":2,"2042":1,"2043":2,"2055":1,"2057":2,"2060":1,"2061":2,"2063":1,"2067":2,"2070":1,"2072":1,"2073":1,"2075":6,"2077":3,"2079":1,"2081":1,"2082":1,"2083":4,"2085":1,"2086":1,"2087":1,"2089":3,"2097":1,"2098":3,"2101":2,"2107":2,"2111":5,"2113":1,"2114":1,"2115":3,"2116":2,"2121":10,"2124":1,"2126":1,"2127":3,"2128":1,"2137":1,"2147":4,"2150":2,"2151":2,"2152":2,"2153":1,"2155":1,"2156":1,"2157":1,"2158":1,"2160":3,"2178":2,"2194":1,"2195":1,"2196":1,"2197":2,"2198":4,"2201":4,"2202":2,"2203":1,"2208":1,"2209":1,"2210":2,"2213":1,"2219":1,"2220":2,"2221":4,"2227":1,"2228":2,"2231":2,"2232":11,"2234":3,"2239":5}}],["applogsconfiguration",{"2":{"1465":1},"3":{"1465":1,"1467":1}}],["applockkeymigration",{"3":{"1496":1}}],["applock",{"3":{"1417":1}}],["applockenabled",{"3":{"1324":1,"1417":3}}],["appleoauthprivatekeypem",{"3":{"1467":1}}],["applenotification",{"3":{"1323":1}}],["applelogin",{"3":{"1311":1}}],["appleappsiteassociationpath",{"3":{"1311":1}}],["appleappid",{"2":{"429":1},"3":{"428":1,"429":1,"1311":1}}],["appleapplinkcomponents",{"2":{"428":1,"429":1},"3":{"428":1,"429":1,"1311":1}}],["appleenabled",{"3":{"0":1,"350":1,"1324":1}}],["apple",{"1":{"347":1,"348":1,"349":1,"350":1,"351":1,"352":1,"353":1,"354":1,"355":1,"1973":1,"1974":1,"1975":1,"1976":1,"1977":1,"1978":1,"1979":1},"2":{"1468":1},"3":{"0":3,"347":1,"348":1,"349":1,"350":8,"419":1,"420":1,"426":1,"1212":1,"1300":1,"1311":22,"1406":2,"1415":2,"1416":1,"1417":1,"1455":1,"1459":2,"1466":2,"1467":3,"1468":1,"1481":2,"1526":1,"1591":1,"1666":1,"1667":2,"1779":1,"1973":3,"1974":1,"1975":9,"1979":1,"2121":1,"2208":1}}],["applyupvoteasync",{"3":{"1402":2}}],["applyvoteasync",{"3":{"1402":2}}],["applydeeplinktarget",{"3":{"1324":1}}],["applyfreshdashboard",{"3":{"1395":5}}],["applyfragment",{"3":{"1324":1}}],["applyfiltertouniqueindexes",{"3":{"1286":1}}],["applyfilters",{"2":{"330":1,"335":1,"591":1,"593":1,"1243":1,"1988":1},"3":{"330":1,"335":1,"591":1,"593":1,"1241":1,"1243":1,"1247":7,"1395":16,"1415":4,"1815":1,"1988":1}}],["applyfieldselection",{"2":{"330":1,"1242":1,"1988":1},"3":{"330":1,"1242":2,"1247":3,"1988":1}}],["applyrestoredstate",{"3":{"1324":1}}],["applyrefreshsessionconfiguration",{"2":{"905":1,"1290":1},"3":{"905":1,"1286":2,"1290":1,"1300":2}}],["applycurrentpagefromurl",{"3":{"1324":1}}],["applyconfiguration",{"3":{"1286":7}}],["applyconfigurationsforentitiesincontext",{"2":{"1034":1},"3":{"1034":1,"1286":11,"1396":1}}],["applyeventdatedefaults",{"3":{"1395":1}}],["applyeventfilter",{"2":{"1392":1},"3":{"1383":1,"1392":1,"1395":9}}],["applyemulatorentityquotas",{"3":{"1323":1}}],["applyengineconventions",{"2":{"1176":1,"1280":1,"1286":1,"1856":1},"3":{"1176":1,"1280":1,"1282":1,"1286":6,"1369":1,"1856":1}}],["applybackpressure",{"3":{"1323":5,"1577":1}}],["applybetween",{"3":{"1247":5}}],["applynonestrategytotenantasync",{"3":{"1311":2}}],["applyall",{"3":{"1396":2,"1415":2}}],["applyallconfigurations",{"3":{"1282":1,"1286":4}}],["applyadditionalfilters",{"3":{"1395":2}}],["applyaiscorefilters",{"3":{"1395":2}}],["applyasync",{"3":{"0":1,"922":1,"1212":1,"1270":5,"1324":3,"1359":5,"1417":1}}],["applykeyandtablemapping",{"2":{"1286":1},"3":{"1282":1,"1286":10}}],["applyorderingstep",{"3":{"1286":3}}],["applyordering",{"3":{"1247":1,"1286":4}}],["applyincludes",{"2":{"1278":1},"3":{"1278":1,"1286":4}}],["applyincludescriteriaandfilters",{"2":{"1243":1},"3":{"1243":1,"1247":2,"1310":1}}],["applyin",{"3":{"1247":10}}],["applyinorrange",{"3":{"1247":4}}],["applying",{"3":{"0":1,"41":1,"135":1,"178":1,"248":1,"265":1,"291":1,"295":1,"318":1,"363":1,"388":1,"499":1,"633":1,"918":1,"1233":1,"1245":1,"1247":1,"1259":1,"1270":1,"1286":2,"1300":2,"1301":2,"1311":1,"1324":2,"1347":1,"1355":1,"1359":4,"1374":1,"1380":4,"1395":2,"1396":5,"1402":2,"1409":1,"1415":1,"1436":1,"1447":1,"1455":2,"1459":1,"1492":1,"1664":1,"1729":1,"1767":1,"1769":1,"1830":1,"1871":1,"2000":2,"2187":1,"2240":1}}],["applysettingsasync",{"3":{"1396":2}}],["applysearchterm",{"3":{"1396":1}}],["applyspeakerfilters",{"3":{"1395":1}}],["applystoredpreferencesandnavigateasync",{"3":{"1324":2}}],["applyshape",{"3":{"1240":1,"1247":1,"1286":7}}],["applysoftdeletefilters",{"2":{"697":1,"1146":1},"3":{"697":1,"698":1,"1146":1,"1286":2,"1526":1,"1577":1}}],["applysorting",{"2":{"330":1,"549":1,"552":1,"1243":1,"1988":1},"3":{"330":1,"549":1,"552":1,"1242":1,"1243":1,"1247":7,"1359":1,"1415":4,"1988":1}}],["applytenant",{"3":{"1286":2}}],["applytenantfilters",{"2":{"1232":1,"1851":1},"3":{"698":2,"1232":1,"1237":1,"1286":4,"1851":1}}],["applytoprocess",{"3":{"1417":8}}],["applyto",{"2":{"1026":1},"3":{"1026":1,"1767":1}}],["applypermissiongrantconfiguration",{"3":{"1286":2}}],["applypresenceorset",{"3":{"1247":2}}],["applypipeline",{"2":{"466":1,"703":1,"752":1},"3":{"459":1,"464":1,"466":1,"698":1,"703":1,"752":1,"1311":5}}],["applypaging",{"2":{"337":1},"3":{"330":1,"337":1,"549":1,"740":1,"1240":1,"1243":1,"1247":5,"1436":1}}],["apply",{"0":{"1793":1,"1802":1,"1807":1,"1812":1,"1817":1,"1827":1,"1834":1,"1846":1,"1853":1,"1862":1,"1868":1,"1877":1,"1886":1,"1892":1,"1899":1,"1905":1,"1912":1,"1924":1,"1930":1,"1941":1,"1950":1,"1959":1,"1965":1,"1972":1,"1979":1,"1985":1,"1991":1,"1997":1,"2002":1,"2014":1,"2028":1,"2038":1,"2049":1,"2060":1,"2069":1,"2080":1,"2086":1,"2108":1,"2123":1,"2128":1,"2133":1,"2139":1,"2146":1,"2153":1,"2160":1,"2169":1,"2180":1,"2193":1,"2203":1},"2":{"202":1,"1050":1,"1304":1,"1377":1,"1406":1,"1810":1,"2176":1},"3":{"0":3,"100":1,"175":2,"201":1,"202":2,"230":1,"255":1,"265":1,"290":3,"291":3,"292":1,"318":1,"390":1,"538":1,"572":1,"574":1,"740":1,"770":1,"789":1,"827":1,"845":1,"914":1,"922":1,"923":1,"1005":1,"1013":1,"1027":1,"1034":1,"1050":1,"1091":1,"1124":1,"1170":1,"1212":2,"1237":1,"1241":3,"1243":2,"1244":1,"1247":22,"1259":1,"1261":1,"1265":1,"1270":9,"1271":2,"1279":1,"1280":1,"1281":1,"1286":30,"1300":7,"1301":2,"1304":3,"1309":7,"1310":1,"1311":5,"1312":1,"1320":1,"1323":20,"1324":9,"1339":9,"1344":1,"1348":2,"1350":4,"1351":1,"1353":2,"1359":32,"1366":1,"1369":3,"1377":3,"1380":8,"1390":1,"1395":8,"1396":25,"1402":9,"1406":1,"1415":11,"1417":2,"1426":1,"1427":4,"1436":26,"1438":1,"1447":2,"1453":1,"1455":2,"1478":1,"1488":3,"1496":3,"1577":2,"1584":1,"1586":1,"1632":1,"1635":1,"1639":1,"1640":3,"1646":1,"1661":1,"1689":1,"1719":1,"1765":1,"1768":1,"1799":1,"1806":1,"1810":1,"1814":1,"1820":1,"1844":2,"1879":1,"1902":1,"1922":2,"1924":1,"1962":1,"1995":1,"2000":1,"2055":1,"2168":1,"2176":1,"2193":1,"2204":1}}],["applifecycle",{"3":{"1324":4}}],["applifecyclenotifier",{"2":{"2118":1},"3":{"1199":3,"1203":1,"1207":2,"1417":14,"1496":2,"2118":1}}],["appliable",{"3":{"1311":2}}],["applinks",{"3":{"426":1,"1311":3,"1416":1,"1436":3}}],["applicable",{"3":{"1311":2,"1323":1,"1436":2,"1554":1,"1640":1,"1641":1,"1771":1,"2101":1}}],["applicability",{"0":{"62":1},"3":{"1526":1,"1527":1,"1553":1,"1577":1,"1578":1,"2171":1}}],["application+infrastructure+api",{"3":{"1653":1}}],["applicationmodel",{"2":{"1416":1},"3":{"1339":2,"1416":2}}],["applicationpart",{"3":{"1311":1}}],["applicationpipelinecompositiontests",{"3":{"1199":1,"1207":7,"1270":1,"1316":1,"1323":5,"1438":2}}],["applicationnamekey",{"3":{"1323":1}}],["applicationname=",{"3":{"665":1}}],["applicationname",{"2":{"998":1,"999":1},"3":{"665":1,"667":1,"998":1,"999":1,"1323":3,"1336":1,"1339":3,"1467":2}}],["applicationnamespacetests",{"3":{"1199":1,"1207":2,"1323":1}}],["applicationnamespace",{"2":{"25":1,"198":1},"3":{"25":2,"198":2,"228":1,"384":1,"998":1,"999":1,"1199":3,"1203":1,"1207":2,"1301":12,"1313":1,"1320":4,"1323":5,"1496":2}}],["applicationid",{"2":{"424":1},"3":{"424":1,"1311":1}}],["applicationinsights",{"2":{"397":1,"914":1,"1333":1,"1443":1,"1446":1,"1467":1,"2010":1,"2156":1,"2159":1},"3":{"397":1,"914":1,"1333":1,"1339":1,"1443":1,"1446":1,"1467":2,"2010":1,"2156":1,"2159":1}}],["applicationurl",{"3":{"95":2}}],["applicationservices",{"3":{"1436":5}}],["applicationsettingstests",{"3":{"1199":1,"1207":2}}],["applicationsettings",{"2":{"290":1,"291":1,"330":1,"741":1,"1245":1,"1314":1,"1315":1,"1467":1,"1652":1,"1880":1,"2012":1,"2094":1},"3":{"0":1,"290":1,"291":1,"330":1,"583":1,"674":1,"741":4,"1199":46,"1203":1,"1207":2,"1245":1,"1286":5,"1309":8,"1311":17,"1313":1,"1314":2,"1315":1,"1320":3,"1323":15,"1324":1,"1359":4,"1380":8,"1395":1,"1396":11,"1415":11,"1436":2,"1455":1,"1467":1,"1577":1,"1652":1,"1880":1,"2012":1,"2094":1}}],["applicationsourcedirectories",{"3":{"1436":4,"1489":1}}],["applicationstate",{"3":{"1324":1}}],["applicationstarted",{"3":{"235":1,"1334":1,"1339":2,"1436":1}}],["applicationstopped",{"3":{"572":1,"1436":3,"1438":1,"1488":1,"1526":1,"2055":1}}],["applicationstopping",{"3":{"572":1,"1436":3,"1438":1,"1488":1,"1526":1,"2055":1}}],["applications",{"0":{"2228":1},"3":{"0":1,"25":1,"85":1,"228":1,"246":1,"257":1,"259":1,"481":2,"482":1,"543":1,"545":1,"550":2,"551":1,"555":1,"665":1,"698":1,"699":1,"742":1,"773":1,"774":1,"868":1,"869":1,"872":1,"880":1,"920":1,"926":1,"945":1,"1081":1,"1082":2,"1083":1,"1091":1,"1229":1,"1237":1,"1259":2,"1286":2,"1300":4,"1301":4,"1323":2,"1324":1,"1332":1,"1339":1,"1415":5,"1416":1,"1424":1,"1436":2,"1478":1,"1673":1,"1717":1,"1815":2,"1825":1,"1920":1,"2109":2,"2115":1,"2220":2,"2224":1,"2226":2,"2227":1,"2234":1,"2239":1}}],["application",{"0":{"1269":1,"1467":1,"1553":1},"1":{"646":1,"647":1,"648":1,"649":1,"650":1,"651":1,"652":1,"653":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1,"1351":1,"1352":1,"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1,"1359":1,"1416":1},"2":{"117":1,"225":1,"281":1,"310":1,"576":1,"1207":4,"1211":1,"1247":3,"1259":2,"1264":1,"1270":6,"1300":1,"1301":1,"1310":2,"1323":3,"1349":1,"1351":1,"1359":4,"1396":3,"1402":1,"1404":1,"1415":1,"1418":1,"1436":1,"1437":1,"1453":1,"1454":1,"1487":1,"1650":2,"1660":1,"1781":1,"1815":1,"1828":1,"1834":1,"1865":1,"1896":1,"1902":1,"1920":1,"1931":1,"1953":1,"1959":1,"1985":1,"2016":1,"2139":1},"3":{"0":33,"10":1,"15":1,"18":1,"24":1,"25":3,"27":2,"39":1,"47":1,"49":1,"52":2,"53":1,"54":2,"58":1,"59":3,"62":1,"63":1,"71":1,"81":1,"90":1,"103":1,"109":2,"110":1,"117":2,"121":2,"122":3,"123":4,"125":2,"126":2,"128":1,"131":1,"135":1,"150":3,"165":1,"166":1,"167":1,"175":1,"179":1,"186":1,"193":1,"198":4,"203":1,"223":1,"225":4,"226":1,"228":2,"230":7,"231":1,"241":1,"242":2,"243":4,"244":1,"245":2,"246":6,"255":1,"256":1,"281":2,"284":1,"285":1,"295":1,"300":1,"306":1,"310":5,"314":1,"318":4,"326":1,"328":1,"330":5,"331":1,"333":4,"335":1,"336":1,"337":3,"341":4,"343":1,"344":6,"350":1,"352":3,"353":4,"358":3,"359":3,"360":2,"365":1,"378":1,"380":1,"381":2,"384":4,"390":1,"391":3,"395":1,"396":1,"397":5,"399":1,"400":1,"401":3,"407":2,"408":1,"434":2,"440":2,"443":1,"444":2,"448":1,"459":11,"461":1,"463":7,"464":2,"465":6,"466":3,"470":1,"476":1,"481":2,"482":2,"483":1,"490":1,"493":3,"494":2,"495":2,"497":8,"498":1,"499":3,"502":2,"506":1,"508":1,"522":2,"523":3,"524":2,"535":1,"536":3,"539":3,"544":4,"545":26,"547":1,"549":6,"550":2,"551":5,"552":5,"555":1,"556":2,"558":1,"566":1,"572":2,"576":1,"582":1,"583":6,"584":1,"585":1,"586":3,"591":1,"593":2,"601":3,"602":1,"604":1,"633":2,"640":2,"646":1,"648":1,"649":1,"657":2,"665":2,"674":16,"676":1,"677":1,"690":3,"692":2,"698":1,"707":2,"708":1,"714":2,"715":1,"718":2,"723":1,"724":4,"725":6,"726":1,"727":1,"728":1,"729":1,"733":1,"736":1,"740":1,"741":4,"749":2,"782":6,"789":2,"790":4,"791":1,"793":2,"794":1,"795":1,"799":2,"813":2,"819":1,"827":4,"828":1,"845":1,"846":6,"848":2,"849":1,"850":1,"854":2,"857":2,"870":1,"875":1,"881":1,"888":1,"890":1,"897":6,"899":1,"900":1,"905":9,"906":2,"907":1,"908":3,"910":1,"914":1,"921":3,"922":8,"923":1,"924":4,"926":12,"927":1,"930":2,"932":1,"933":1,"938":2,"939":2,"940":1,"941":1,"946":5,"954":1,"956":1,"960":1,"979":1,"980":12,"982":2,"983":1,"987":3,"988":4,"994":1,"995":1,"996":4,"998":2,"999":1,"1004":8,"1006":1,"1012":1,"1017":2,"1018":3,"1020":1,"1022":1,"1025":2,"1026":2,"1033":1,"1034":1,"1035":1,"1036":1,"1037":1,"1038":1,"1041":1,"1042":3,"1049":1,"1050":3,"1051":1,"1058":1,"1059":7,"1067":1,"1068":1,"1069":1,"1074":5,"1078":1,"1082":2,"1090":2,"1091":7,"1093":2,"1099":1,"1100":5,"1116":3,"1119":1,"1133":6,"1140":1,"1142":1,"1150":3,"1158":1,"1161":5,"1168":4,"1170":1,"1174":1,"1175":1,"1192":2,"1194":1,"1199":1475,"1201":2,"1202":4,"1203":779,"1206":10,"1207":4571,"1208":22,"1211":4,"1212":12,"1213":2,"1214":1,"1216":2,"1223":1,"1224":1,"1229":12,"1230":1,"1236":1,"1237":14,"1238":3,"1239":6,"1240":1,"1241":5,"1242":3,"1243":6,"1244":2,"1246":4,"1247":161,"1248":2,"1249":1,"1252":3,"1256":1,"1257":6,"1258":1,"1259":63,"1260":2,"1261":3,"1262":3,"1263":1,"1264":11,"1265":3,"1266":1,"1267":5,"1268":2,"1269":4,"1270":311,"1271":101,"1272":1,"1276":1,"1278":5,"1279":3,"1282":1,"1285":9,"1286":234,"1288":1,"1289":7,"1290":3,"1291":6,"1292":3,"1293":5,"1294":6,"1295":3,"1296":1,"1297":8,"1300":356,"1301":48,"1302":4,"1303":4,"1304":5,"1305":1,"1306":6,"1307":1,"1308":5,"1309":161,"1310":87,"1311":71,"1312":8,"1313":3,"1314":2,"1315":4,"1316":11,"1317":1,"1318":6,"1319":3,"1320":14,"1322":14,"1323":290,"1324":22,"1325":1,"1333":2,"1336":1,"1337":2,"1339":13,"1340":1,"1341":2,"1346":1,"1347":4,"1348":1,"1349":2,"1350":83,"1351":4,"1352":20,"1353":21,"1354":10,"1355":9,"1356":4,"1357":4,"1358":3,"1359":1975,"1360":1,"1361":1,"1365":2,"1366":1,"1367":5,"1369":29,"1370":1,"1374":2,"1377":1,"1379":1,"1380":22,"1389":1,"1390":1,"1395":9,"1396":309,"1398":1,"1399":1,"1400":3,"1401":1,"1402":195,"1404":3,"1405":2,"1406":3,"1407":1,"1409":2,"1410":3,"1411":1,"1413":2,"1415":301,"1416":23,"1417":7,"1418":1,"1421":1,"1423":3,"1424":2,"1427":10,"1429":2,"1430":2,"1431":3,"1435":1,"1436":247,"1437":6,"1438":23,"1440":1,"1441":1,"1442":2,"1443":4,"1446":1,"1452":1,"1453":1,"1454":2,"1455":7,"1460":1,"1461":1,"1462":1,"1465":3,"1467":20,"1469":1,"1471":1,"1473":1,"1486":1,"1487":6,"1488":1,"1489":22,"1490":1,"1492":2,"1496":62,"1499":1,"1503":1,"1504":1,"1509":1,"1519":2,"1526":15,"1527":2,"1534":2,"1535":1,"1536":3,"1538":1,"1540":4,"1551":1,"1553":2,"1555":2,"1572":2,"1575":1,"1576":2,"1577":30,"1578":2,"1582":3,"1586":2,"1591":14,"1592":1,"1599":1,"1600":3,"1602":1,"1620":1,"1639":1,"1646":1,"1650":4,"1651":5,"1653":1,"1654":3,"1656":3,"1660":2,"1661":1,"1662":2,"1663":2,"1666":1,"1669":1,"1670":3,"1671":2,"1673":1,"1679":1,"1681":1,"1685":11,"1686":8,"1687":1,"1693":2,"1718":1,"1719":1,"1720":1,"1724":1,"1731":1,"1748":1,"1756":1,"1765":21,"1781":5,"1783":1,"1784":1,"1789":1,"1790":3,"1792":1,"1793":2,"1796":2,"1799":2,"1805":1,"1810":1,"1813":1,"1815":9,"1817":2,"1820":1,"1824":2,"1828":1,"1830":1,"1831":1,"1833":1,"1834":1,"1840":1,"1842":2,"1852":1,"1854":2,"1861":2,"1864":1,"1865":4,"1866":1,"1872":1,"1875":1,"1876":1,"1877":1,"1883":1,"1884":1,"1896":1,"1902":2,"1915":2,"1916":1,"1919":1,"1920":2,"1922":1,"1926":1,"1929":1,"1931":2,"1932":2,"1933":1,"1934":2,"1935":1,"1936":1,"1940":1,"1941":1,"1944":1,"1948":1,"1953":1,"1956":1,"1959":1,"1961":1,"1962":1,"1963":1,"1970":1,"1972":1,"1985":1,"1994":1,"1995":1,"1997":1,"1998":1,"2000":4,"2003":1,"2005":1,"2006":1,"2010":1,"2014":1,"2015":2,"2016":4,"2017":1,"2024":1,"2028":1,"2034":1,"2036":1,"2042":1,"2043":7,"2051":1,"2052":1,"2055":1,"2056":2,"2061":1,"2063":1,"2066":3,"2071":1,"2077":1,"2080":1,"2088":1,"2095":1,"2099":2,"2101":1,"2104":5,"2110":2,"2111":2,"2126":1,"2132":1,"2139":1,"2140":2,"2141":3,"2142":2,"2144":3,"2154":1,"2156":1,"2157":1,"2163":1,"2166":2,"2169":1,"2170":1,"2171":2,"2175":4,"2177":1,"2180":1,"2193":1,"2214":1,"2219":1,"2220":3,"2221":1,"2227":5,"2230":3,"2232":4,"2240":2}}],["applicationdbcontexteffactory",{"3":{"1496":1}}],["applicationdbcontexttests",{"3":{"1199":1,"1207":4}}],["applicationdbcontexttenantfiltertests",{"3":{"1199":1,"1207":2,"1286":3,"1438":2}}],["applicationdbcontext",{"2":{"0":1,"26":1,"47":1,"200":1,"201":1,"345":1,"696":1,"703":1,"713":1,"715":2,"720":1,"798":1,"806":1,"889":1,"892":1,"893":1,"1033":1,"1036":1,"1042":1,"1050":1,"1083":1,"1086":1,"1174":1,"1175":2,"1192":1,"1201":1,"1212":1,"1259":1,"1272":1,"1276":1,"1286":2,"1363":1,"1412":1,"1510":1,"1517":1,"1664":1,"1849":1,"1857":1,"2064":1,"2199":1},"3":{"0":3,"26":3,"47":1,"166":1,"200":4,"201":1,"203":1,"341":1,"345":1,"536":1,"540":1,"696":2,"697":1,"698":6,"702":1,"703":2,"707":1,"710":2,"713":1,"715":4,"720":3,"798":2,"806":2,"888":1,"889":3,"892":1,"893":1,"905":3,"907":1,"931":1,"934":1,"1033":1,"1034":3,"1036":1,"1037":1,"1042":4,"1050":2,"1055":1,"1059":2,"1061":1,"1062":1,"1083":2,"1086":2,"1140":3,"1146":1,"1150":1,"1156":1,"1174":1,"1175":4,"1192":1,"1198":1,"1199":109,"1201":1,"1202":1,"1203":2,"1207":3,"1212":3,"1232":4,"1233":1,"1237":10,"1251":2,"1253":2,"1258":2,"1259":16,"1272":1,"1273":10,"1274":1,"1276":2,"1278":1,"1281":6,"1282":1,"1283":1,"1286":170,"1300":7,"1323":13,"1359":1,"1363":2,"1369":9,"1396":9,"1412":1,"1415":2,"1436":8,"1437":2,"1496":5,"1498":1,"1510":1,"1517":1,"1526":1,"1577":3,"1651":1,"1664":1,"1810":1,"1849":1,"1857":1,"2064":2,"2164":1,"2199":2}}],["applicationlayers",{"3":{"1436":1}}],["applicationlayersdeclarehandlers",{"3":{"1436":1}}],["applicationlayer",{"2":{"0":1,"700":1,"1815":1},"3":{"0":1,"545":1,"700":1,"1436":5,"1815":1}}],["appliedid",{"3":{"1324":2}}],["applied",{"0":{"2142":1},"2":{"1140":1},"3":{"0":1,"23":3,"24":2,"27":1,"67":1,"72":1,"80":1,"98":1,"103":1,"116":1,"135":1,"136":1,"156":1,"179":2,"186":2,"200":1,"212":1,"231":1,"235":1,"237":1,"246":1,"248":1,"265":1,"292":1,"293":1,"318":1,"335":3,"343":1,"359":2,"361":1,"371":1,"376":1,"384":1,"400":1,"401":2,"403":1,"427":1,"430":1,"440":1,"459":1,"461":1,"464":1,"478":1,"483":2,"485":1,"535":1,"539":1,"548":1,"552":1,"557":1,"595":1,"640":1,"657":3,"658":1,"659":1,"666":1,"690":1,"697":1,"698":1,"707":2,"715":1,"740":1,"741":1,"751":1,"759":1,"766":1,"769":1,"798":1,"806":1,"827":2,"829":1,"830":1,"831":1,"832":1,"837":1,"838":1,"865":2,"872":2,"876":1,"883":1,"890":1,"905":1,"907":1,"988":1,"996":1,"1018":1,"1028":1,"1042":1,"1059":1,"1100":1,"1116":1,"1119":1,"1124":1,"1137":1,"1140":1,"1191":1,"1229":1,"1232":1,"1237":8,"1239":1,"1240":1,"1241":2,"1242":1,"1243":3,"1244":1,"1247":10,"1259":4,"1261":1,"1265":1,"1267":1,"1270":7,"1271":6,"1273":3,"1275":1,"1277":1,"1279":1,"1286":42,"1297":1,"1299":1,"1300":19,"1301":4,"1309":7,"1310":2,"1311":20,"1312":5,"1318":1,"1320":1,"1323":13,"1324":20,"1325":1,"1326":1,"1327":1,"1328":1,"1330":1,"1334":1,"1336":1,"1337":1,"1339":18,"1340":1,"1343":1,"1347":1,"1350":12,"1352":2,"1353":1,"1359":67,"1362":2,"1363":1,"1369":7,"1373":1,"1374":1,"1380":8,"1389":1,"1395":13,"1396":33,"1399":1,"1401":1,"1402":11,"1410":1,"1415":8,"1417":8,"1423":1,"1427":3,"1435":1,"1436":36,"1438":6,"1440":1,"1441":1,"1443":2,"1447":1,"1453":1,"1455":1,"1457":1,"1461":1,"1463":1,"1465":1,"1467":6,"1469":1,"1472":1,"1473":1,"1474":1,"1478":1,"1481":1,"1488":2,"1489":1,"1492":2,"1496":4,"1525":1,"1526":5,"1531":2,"1534":1,"1537":4,"1539":1,"1545":2,"1552":1,"1566":1,"1577":6,"1591":2,"1637":2,"1647":1,"1665":1,"1666":1,"1675":1,"1689":1,"1707":1,"1764":1,"1765":4,"1767":1,"1771":1,"1774":1,"1797":1,"1814":1,"1820":1,"1839":1,"1840":1,"1844":1,"1851":1,"1852":1,"1869":1,"1874":1,"1920":3,"1946":1,"1954":1,"1994":1,"2001":1,"2030":1,"2031":1,"2038":1,"2063":1,"2070":1,"2074":1,"2080":2,"2111":1,"2134":1,"2147":1,"2162":1,"2174":1,"2177":1,"2185":1,"2192":1,"2232":1}}],["appliertype",{"3":{"1270":1}}],["appliers",{"3":{"1262":1,"1265":1,"1270":1,"1316":1,"1323":2,"1359":1,"1662":1,"1875":1,"1883":1}}],["applier",{"3":{"0":3,"263":1,"265":2,"291":1,"293":1,"328":1,"920":2,"922":2,"923":1,"924":2,"925":1,"926":8,"1260":1,"1265":3,"1270":28,"1286":2,"1301":2,"1323":1,"1324":4,"1354":1,"1359":37,"1417":3,"1436":1,"1455":2,"1496":2,"1871":1,"1956":1}}],["appliesto",{"3":{"1369":2}}],["applies",{"0":{"1920":1},"1":{"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"294":1,"295":1,"296":1},"3":{"0":8,"80":1,"95":1,"100":2,"117":1,"130":1,"152":1,"162":1,"166":1,"173":2,"175":1,"193":1,"195":1,"243":1,"280":1,"288":1,"291":2,"292":1,"295":1,"318":3,"333":1,"340":1,"341":1,"345":1,"359":1,"370":1,"392":1,"397":1,"404":1,"458":1,"501":1,"511":1,"536":2,"549":1,"550":1,"551":2,"556":2,"562":1,"563":2,"564":2,"568":2,"572":1,"592":3,"607":1,"611":1,"618":1,"622":1,"628":1,"631":1,"634":2,"635":1,"640":1,"651":2,"674":1,"676":1,"684":1,"691":1,"698":1,"701":2,"711":1,"728":1,"733":1,"741":1,"743":1,"744":1,"749":4,"757":1,"768":1,"774":2,"817":1,"818":1,"838":1,"842":1,"846":1,"847":2,"875":1,"876":1,"883":1,"889":1,"891":1,"917":1,"926":1,"963":1,"965":1,"972":2,"973":1,"1000":1,"1017":1,"1018":1,"1034":1,"1037":1,"1044":1,"1049":1,"1050":1,"1052":2,"1059":1,"1074":1,"1108":1,"1116":2,"1124":1,"1127":1,"1128":1,"1134":1,"1162":1,"1193":1,"1212":3,"1229":3,"1237":4,"1240":1,"1243":1,"1244":2,"1247":13,"1253":1,"1259":29,"1261":1,"1262":1,"1270":9,"1271":6,"1273":3,"1275":2,"1276":1,"1282":1,"1286":49,"1292":1,"1300":33,"1301":3,"1304":2,"1306":1,"1309":2,"1310":2,"1311":33,"1312":4,"1314":1,"1318":2,"1323":28,"1324":59,"1328":1,"1329":1,"1331":1,"1333":1,"1338":1,"1339":17,"1348":2,"1350":17,"1353":1,"1357":1,"1359":61,"1363":1,"1366":1,"1369":6,"1373":1,"1378":1,"1380":8,"1395":27,"1396":40,"1401":3,"1402":22,"1406":1,"1409":1,"1414":1,"1415":14,"1416":14,"1417":14,"1418":1,"1421":1,"1422":1,"1427":4,"1433":1,"1436":67,"1438":5,"1441":2,"1442":1,"1443":2,"1447":1,"1453":1,"1454":1,"1455":2,"1457":1,"1467":4,"1470":1,"1477":1,"1479":1,"1488":1,"1489":1,"1490":1,"1492":2,"1496":2,"1510":1,"1577":2,"1627":1,"1631":1,"1639":3,"1640":2,"1641":1,"1650":1,"1652":1,"1664":1,"1666":1,"1667":1,"1673":1,"1701":1,"1719":1,"1728":1,"1730":1,"1750":1,"1755":1,"1776":1,"1815":2,"1821":2,"1843":2,"1856":1,"1869":1,"1892":1,"1903":1,"1910":1,"1928":1,"1935":1,"1953":1,"1994":3,"1995":1,"1997":1,"2000":1,"2012":1,"2025":1,"2045":1,"2057":1,"2068":1,"2097":1,"2098":1,"2111":1,"2126":3,"2132":1,"2134":1,"2136":1,"2156":1,"2160":1,"2164":1,"2167":1,"2192":1,"2193":2,"2199":1,"2232":2}}],["appsidentity",{"3":{"1467":3}}],["appsec",{"3":{"1339":1}}],["appsetting",{"3":{"312":1,"948":1}}],["appsettingsalt",{"3":{"1416":1}}],["appsettings",{"2":{"92":1,"95":1,"96":1,"102":2,"418":1,"422":1,"534":1,"584":1,"794":1,"836":1,"837":1,"838":1,"840":1,"841":1,"857":1,"933":1,"1066":1,"1107":1,"1128":1,"1178":1,"1429":1,"1481":1,"1490":1,"1656":1,"1684":1,"1701":1,"1719":1,"1720":1,"1722":1,"1723":1,"1729":1,"1850":1,"2097":1},"3":{"0":1,"59":1,"63":1,"92":3,"95":4,"96":2,"98":1,"102":2,"104":1,"179":1,"180":3,"195":7,"418":3,"422":2,"423":1,"424":1,"425":1,"426":4,"428":2,"429":3,"534":2,"536":1,"539":1,"540":1,"583":6,"584":1,"585":3,"633":1,"639":1,"641":1,"690":1,"691":1,"725":2,"727":1,"774":4,"776":1,"790":2,"792":1,"794":1,"827":9,"831":1,"832":13,"833":4,"836":1,"837":1,"838":11,"839":1,"840":2,"841":1,"854":4,"857":1,"933":1,"998":1,"999":1,"1017":1,"1018":4,"1021":1,"1066":1,"1091":1,"1107":1,"1116":1,"1124":9,"1125":1,"1126":1,"1128":1,"1178":1,"1184":1,"1286":7,"1300":5,"1301":3,"1309":1,"1311":2,"1312":2,"1313":1,"1320":1,"1323":3,"1324":3,"1337":1,"1338":2,"1339":22,"1380":18,"1395":1,"1396":6,"1407":1,"1415":3,"1416":14,"1417":1,"1419":1,"1427":1,"1429":1,"1430":1,"1436":10,"1438":1,"1441":5,"1446":1,"1447":26,"1450":2,"1467":4,"1481":2,"1488":2,"1490":1,"1496":4,"1526":10,"1531":1,"1532":1,"1535":1,"1548":1,"1591":4,"1627":3,"1656":1,"1684":2,"1701":1,"1719":1,"1720":1,"1722":1,"1723":1,"1728":1,"1729":1,"1761":1,"1849":1,"1850":1,"2097":1,"2150":1,"2167":1,"2173":1}}],["apps",{"0":{"94":1,"719":1,"1785":1,"1983":1,"2110":1,"2151":1},"1":{"1717":1,"1718":1,"1719":1,"1720":1,"1721":1,"1722":1,"1723":1,"1724":1,"1725":1,"2109":1,"2110":1,"2111":1,"2112":1,"2113":1,"2114":1,"2115":1},"2":{"759":1},"3":{"0":25,"31":1,"61":1,"62":1,"63":1,"91":1,"93":1,"94":2,"96":1,"97":2,"98":2,"104":1,"146":2,"147":1,"148":1,"175":2,"184":1,"206":2,"207":1,"208":1,"213":1,"214":2,"216":1,"233":1,"234":2,"235":1,"243":1,"245":1,"265":1,"283":2,"290":1,"291":1,"293":1,"299":1,"310":3,"314":2,"369":1,"390":2,"392":1,"395":1,"402":1,"412":1,"422":2,"457":2,"459":2,"463":1,"465":1,"483":1,"490":2,"491":1,"492":1,"493":1,"494":1,"497":3,"499":3,"500":1,"511":1,"517":1,"551":1,"552":1,"555":1,"556":2,"597":1,"599":9,"600":2,"602":1,"603":3,"618":1,"620":1,"625":1,"626":2,"629":3,"640":1,"648":1,"649":1,"650":1,"656":1,"665":3,"713":1,"718":1,"719":1,"724":2,"725":3,"726":2,"727":1,"748":1,"749":1,"756":2,"757":5,"759":2,"760":3,"764":1,"765":1,"766":2,"780":3,"784":1,"791":1,"826":1,"827":3,"832":1,"833":1,"838":1,"853":1,"854":1,"855":1,"857":1,"860":1,"861":3,"862":1,"863":3,"865":1,"867":2,"869":3,"875":2,"882":1,"913":1,"971":2,"972":2,"995":1,"996":1,"1058":3,"1060":2,"1101":1,"1123":1,"1124":1,"1154":1,"1156":1,"1202":1,"1203":1,"1212":5,"1237":4,"1247":4,"1259":2,"1264":1,"1270":7,"1271":2,"1286":12,"1295":1,"1300":13,"1309":4,"1311":12,"1313":1,"1322":2,"1323":21,"1324":14,"1325":1,"1339":26,"1359":1,"1415":2,"1416":1,"1417":8,"1433":1,"1436":21,"1438":4,"1442":1,"1443":5,"1447":8,"1455":9,"1465":2,"1467":46,"1469":2,"1472":1,"1473":1,"1474":6,"1477":5,"1478":1,"1486":2,"1488":1,"1492":3,"1496":2,"1524":1,"1525":1,"1526":4,"1534":2,"1535":1,"1555":1,"1565":1,"1574":1,"1575":2,"1577":3,"1582":1,"1589":2,"1591":1,"1596":1,"1602":1,"1645":1,"1646":1,"1650":1,"1658":1,"1661":2,"1670":1,"1673":1,"1674":1,"1676":1,"1677":3,"1692":1,"1705":2,"1706":1,"1717":1,"1718":1,"1726":1,"1727":1,"1779":1,"1782":1,"1784":1,"1785":1,"1792":1,"1799":1,"1811":1,"1839":1,"1875":2,"1900":1,"1903":1,"1904":1,"1909":1,"1953":1,"1958":1,"1963":1,"1968":1,"1972":1,"1982":1,"2000":2,"2001":1,"2006":2,"2010":1,"2011":1,"2025":2,"2033":1,"2037":1,"2043":2,"2047":1,"2055":1,"2059":1,"2077":1,"2083":1,"2087":1,"2108":1,"2109":3,"2110":1,"2111":2,"2112":1,"2113":2,"2114":3,"2123":1,"2138":1,"2147":1,"2150":1,"2152":1,"2153":1,"2158":2,"2194":3,"2198":2,"2202":1,"2203":1,"2210":2,"2213":1,"2214":1,"2219":1,"2221":1,"2228":1,"2229":1,"2232":1,"2239":1,"2243":1,"2245":1}}],["agile",{"3":{"2219":1,"2220":1}}],["aggregator",{"3":{"1308":1,"1309":4}}],["aggregating",{"3":{"1259":2,"1309":1,"1332":1,"1350":3,"1359":1,"1396":2}}],["aggregation",{"3":{"692":1,"1229":1,"1278":1,"1286":3,"1309":5,"1312":1,"1350":1,"1359":5,"1369":1,"1395":2,"1396":3,"1415":1,"1438":1,"1441":1,"1448":1,"2067":1}}],["aggregations",{"3":{"545":1,"547":1,"1396":1,"1436":1}}],["aggregatewithoutdeleteoverride",{"3":{"1436":1}}],["aggregatewithnochildentities",{"3":{"1436":2}}],["aggregateroots",{"2":{"1489":1},"3":{"1436":6,"1489":3}}],["aggregaterootshaveresultfactory",{"2":{"1577":1},"3":{"1431":1,"1577":1}}],["aggregaterootentity",{"3":{"1286":1}}],["aggregaterootentitycontrollerbasetests",{"3":{"1199":1,"1207":6,"1311":2}}],["aggregaterootentitycontrollerbase",{"2":{"318":1,"328":1,"921":1,"922":1,"925":1,"1666":1,"1991":1},"3":{"0":2,"318":1,"328":1,"330":5,"332":1,"334":1,"921":3,"922":1,"925":1,"1199":11,"1203":1,"1207":2,"1270":4,"1311":15,"1359":5,"1371":1,"1380":10,"1436":2,"1666":1,"1988":1,"1991":1}}],["aggregateconvention",{"3":{"1576":1,"1577":1}}],["aggregateconventiontests",{"2":{"1811":1,"2048":1},"3":{"1168":1,"1199":1,"1207":2,"1436":9,"1489":3,"1493":1,"1577":2,"1811":1,"2048":1}}],["aggregateconventiontestsbase",{"2":{"1168":1},"3":{"1168":2,"1199":2,"1207":2,"1431":4,"1436":28,"1489":1,"1496":1,"1577":1,"1586":1}}],["aggregatecapture",{"3":{"1199":3,"1203":1,"1207":1,"1274":1,"1286":5,"1496":2}}],["aggregated",{"3":{"107":1,"1017":1,"1220":1,"1286":2,"1350":2,"1359":2,"1380":2,"1395":2,"1396":3,"1415":1,"1427":1,"1535":1,"1641":2,"1765":1}}],["aggregate",{"0":{"1233":1,"1291":1,"1343":1,"1346":1,"1405":1,"2090":1},"1":{"919":1,"920":1,"921":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"1165":1,"1166":1,"1167":1,"1168":1,"1169":1,"1170":1,"1171":1},"2":{"1381":1},"3":{"0":27,"16":1,"17":1,"18":1,"21":4,"24":3,"39":4,"41":1,"42":1,"107":1,"109":1,"110":2,"164":1,"165":3,"166":1,"167":2,"168":1,"171":1,"175":1,"181":1,"225":1,"227":1,"230":1,"283":1,"314":1,"339":1,"340":1,"341":5,"342":1,"344":1,"350":2,"353":2,"402":1,"413":1,"443":1,"444":1,"459":2,"460":1,"466":1,"468":1,"470":2,"476":1,"497":2,"499":3,"536":2,"537":2,"541":1,"545":1,"551":1,"572":1,"609":1,"633":4,"636":1,"639":1,"656":1,"657":2,"658":1,"661":1,"688":1,"690":3,"691":1,"707":1,"724":1,"725":3,"726":1,"781":1,"782":2,"785":1,"799":1,"809":4,"810":3,"813":3,"815":2,"856":1,"881":1,"904":1,"905":1,"907":1,"919":1,"920":1,"921":7,"922":8,"923":4,"924":7,"925":1,"926":14,"930":1,"1004":10,"1006":1,"1025":1,"1026":1,"1027":1,"1028":1,"1041":2,"1042":1,"1043":1,"1052":1,"1058":1,"1082":2,"1114":1,"1116":3,"1117":2,"1120":1,"1140":5,"1141":1,"1142":5,"1143":2,"1144":2,"1145":1,"1147":1,"1150":1,"1160":1,"1161":1,"1165":1,"1167":1,"1168":6,"1169":1,"1170":2,"1171":1,"1191":1,"1192":2,"1201":1,"1202":3,"1203":3,"1212":11,"1220":1,"1222":1,"1224":1,"1226":1,"1229":5,"1230":1,"1231":4,"1233":9,"1237":44,"1240":1,"1247":4,"1248":2,"1249":2,"1251":3,"1255":1,"1259":16,"1260":1,"1265":12,"1270":85,"1272":1,"1274":4,"1275":1,"1278":2,"1281":1,"1283":1,"1286":57,"1287":1,"1289":1,"1290":1,"1291":1,"1293":1,"1294":1,"1295":1,"1300":30,"1301":1,"1304":3,"1309":28,"1311":22,"1312":2,"1318":1,"1322":2,"1323":47,"1324":5,"1328":1,"1339":2,"1340":3,"1341":1,"1342":6,"1343":2,"1344":2,"1345":2,"1346":3,"1347":1,"1349":1,"1350":156,"1352":4,"1353":5,"1354":6,"1355":3,"1357":2,"1359":430,"1366":1,"1369":26,"1371":3,"1374":1,"1380":45,"1385":1,"1386":2,"1395":30,"1396":107,"1398":3,"1399":1,"1402":56,"1403":2,"1404":1,"1405":5,"1406":1,"1407":1,"1408":2,"1409":1,"1410":2,"1411":1,"1413":1,"1415":65,"1431":3,"1436":76,"1437":2,"1438":8,"1441":2,"1442":1,"1443":1,"1447":4,"1450":1,"1453":1,"1467":4,"1487":5,"1488":1,"1489":6,"1490":2,"1496":6,"1498":1,"1517":1,"1526":5,"1541":1,"1545":1,"1547":1,"1577":5,"1582":1,"1583":1,"1585":3,"1586":2,"1591":3,"1630":18,"1631":9,"1632":17,"1634":1,"1635":8,"1637":2,"1638":27,"1639":5,"1640":6,"1641":1,"1646":1,"1647":1,"1650":1,"1651":6,"1654":1,"1660":1,"1661":1,"1662":2,"1665":2,"1666":1,"1671":1,"1677":1,"1683":1,"1685":6,"1686":6,"1689":1,"1695":2,"1697":2,"1699":1,"1700":1,"1701":5,"1704":1,"1719":2,"1720":1,"1727":16,"1728":5,"1729":4,"1730":8,"1747":1,"1748":1,"1749":3,"1750":1,"1764":2,"1765":2,"1769":1,"1773":1,"1805":1,"1807":1,"1810":3,"1811":3,"1812":1,"1836":1,"1838":3,"1842":1,"1856":1,"1861":1,"1869":1,"1871":3,"1875":1,"1876":4,"1922":1,"1927":1,"1933":3,"1934":1,"1937":2,"1948":1,"1953":1,"1977":2,"1982":1,"1985":1,"2001":1,"2010":1,"2020":1,"2043":1,"2063":2,"2064":1,"2065":1,"2066":1,"2068":1,"2083":1,"2085":1,"2087":3,"2089":3,"2090":4,"2091":4,"2092":1,"2097":1,"2098":2,"2104":1,"2110":3,"2117":1,"2123":1,"2164":2,"2167":1,"2168":1,"2169":1,"2181":1,"2183":3,"2192":2,"2193":1,"2198":2,"2227":1,"2232":3}}],["aggregatescascadesoftdeletetochildren",{"3":{"1436":14}}],["aggregateswithchildcollections",{"3":{"1436":2}}],["aggregates",{"0":{"1342":1,"1398":1},"1":{"1230":1,"1231":1,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":1,"1808":1,"1809":1,"1810":1,"1811":1,"1812":1},"3":{"0":2,"39":2,"95":1,"109":1,"383":1,"497":3,"691":1,"698":1,"781":1,"921":1,"922":1,"924":1,"1058":1,"1066":1,"1068":1,"1140":2,"1141":1,"1168":2,"1169":1,"1192":1,"1202":2,"1203":2,"1212":3,"1222":1,"1229":2,"1230":1,"1237":12,"1247":1,"1251":1,"1254":1,"1259":4,"1270":2,"1274":1,"1275":1,"1286":12,"1291":1,"1300":4,"1302":1,"1303":2,"1309":2,"1311":2,"1323":4,"1324":1,"1328":1,"1339":1,"1340":1,"1341":1,"1342":3,"1345":1,"1346":4,"1350":10,"1351":1,"1352":6,"1354":1,"1359":46,"1371":1,"1380":3,"1388":1,"1396":19,"1398":2,"1399":1,"1401":1,"1402":7,"1430":1,"1436":13,"1438":3,"1441":2,"1443":1,"1467":2,"1476":1,"1487":1,"1496":4,"1526":2,"1534":1,"1541":5,"1547":2,"1577":1,"1585":1,"1600":1,"1630":1,"1635":2,"1638":2,"1639":1,"1651":1,"1671":1,"1681":1,"1689":1,"1720":1,"1765":1,"1808":1,"1811":1,"1812":1,"1815":1,"1838":1,"1932":1,"1937":1,"2056":1,"2104":1,"2110":1,"2186":1,"2206":1,"2227":1,"2230":1,"2239":1}}],["aggressive",{"3":{"501":1,"907":1,"1984":1,"1985":1,"2037":1}}],["agreeing",{"3":{"1270":2,"1286":2,"1300":1,"1323":1,"1324":1,"1380":2,"1415":1,"1436":1}}],["agreeingmarkedqueryhandler",{"3":{"1199":2,"1207":1}}],["agreeingmarkedquery",{"3":{"1199":2,"1207":1}}],["agreeingmarkedcommandhandler",{"3":{"1199":2,"1207":1}}],["agreeingmarkedcommand",{"3":{"1199":2,"1207":1,"1270":1}}],["agrees",{"3":{"650":1,"703":1,"839":1,"974":1,"1229":1,"1324":1,"1402":1,"2039":2}}],["agreement",{"3":{"265":1,"301":1,"560":1,"781":1,"1005":1,"1270":1,"1286":1,"1300":1,"1301":1,"1324":2,"1339":1,"1359":2,"1395":2,"1396":1,"1436":2,"1653":1,"2086":1,"2138":1}}],["agreed",{"3":{"77":1,"648":1,"1270":1,"1300":1,"1324":1,"1359":3,"1395":1,"1396":3}}],["agree",{"3":{"0":2,"109":1,"217":1,"220":1,"243":1,"265":1,"272":1,"302":1,"443":1,"539":1,"540":1,"558":1,"611":1,"889":1,"890":1,"905":1,"907":1,"954":1,"956":1,"959":1,"1018":1,"1116":1,"1176":1,"1229":1,"1237":1,"1247":3,"1259":1,"1267":1,"1270":2,"1271":1,"1279":1,"1285":1,"1286":10,"1300":12,"1301":2,"1309":3,"1311":5,"1312":1,"1323":3,"1324":12,"1339":2,"1347":1,"1359":12,"1369":1,"1380":2,"1395":2,"1402":7,"1415":3,"1417":3,"1436":5,"1467":1,"1597":1,"1631":1,"1640":1,"1697":1,"1829":1,"1833":1,"1927":1,"1937":1,"1953":1,"2024":1,"2046":1,"2082":1,"2083":1,"2135":1,"2137":1,"2167":1}}],["ago",{"2":{"1475":1},"3":{"0":2,"535":1,"539":1,"905":3,"1247":1,"1286":2,"1359":2,"1436":2,"1455":1,"1465":2,"1475":2,"1844":1,"1887":1,"2161":2}}],["again",{"1":{"796":1,"797":1,"798":1,"799":1,"800":1,"801":1,"802":1,"803":1,"804":1,"805":1,"806":1,"1835":1,"1836":1,"1837":1,"1838":1,"1839":1,"1840":1,"1841":1,"1842":1,"1843":1,"1844":1,"1845":1,"1846":1},"3":{"0":3,"21":1,"130":2,"178":1,"251":2,"252":1,"254":1,"295":1,"335":1,"390":1,"395":2,"418":1,"425":1,"426":1,"443":1,"468":2,"488":1,"489":1,"508":1,"520":1,"524":1,"534":1,"539":1,"543":1,"551":1,"552":1,"607":1,"624":1,"696":1,"758":1,"776":1,"792":1,"796":1,"797":1,"799":1,"800":1,"819":1,"825":1,"827":1,"861":1,"869":1,"876":1,"905":1,"907":1,"934":1,"996":1,"1014":1,"1018":1,"1024":1,"1043":1,"1044":1,"1049":1,"1090":1,"1094":1,"1117":1,"1144":1,"1212":1,"1227":1,"1229":1,"1237":1,"1239":1,"1242":1,"1247":2,"1251":1,"1253":1,"1259":5,"1269":1,"1270":6,"1271":1,"1275":1,"1283":1,"1286":15,"1300":6,"1301":1,"1304":2,"1306":1,"1309":3,"1311":10,"1320":1,"1323":8,"1324":19,"1332":1,"1333":1,"1338":1,"1339":3,"1350":2,"1359":16,"1362":1,"1366":1,"1369":5,"1380":4,"1390":1,"1391":1,"1395":15,"1396":20,"1402":10,"1410":1,"1415":9,"1416":2,"1417":5,"1426":1,"1436":11,"1438":2,"1453":1,"1454":2,"1465":1,"1467":1,"1481":1,"1488":1,"1492":1,"1496":2,"1591":1,"1632":2,"1640":1,"1684":1,"1685":1,"1686":1,"1749":2,"1765":2,"1769":1,"1815":1,"1835":1,"1908":1,"1924":1,"1950":1,"1975":1,"1992":1,"2026":1,"2070":2,"2075":1,"2082":1,"2083":1,"2147":1,"2165":1,"2171":1,"2190":2,"2192":1,"2206":1,"2232":1}}],["against",{"0":{"1952":1,"2167":1},"1":{"1780":1,"1781":1,"1782":1,"1783":1,"1784":1,"1785":1,"1786":1,"1787":1,"1794":1,"1795":1,"1796":1,"1797":1,"1798":1,"1799":1,"1800":1,"1801":1,"1802":1},"3":{"0":37,"21":1,"31":2,"42":1,"53":1,"57":2,"62":1,"63":1,"80":1,"82":1,"90":1,"92":1,"98":1,"99":2,"100":1,"109":3,"121":1,"125":1,"128":1,"130":1,"135":3,"136":3,"137":3,"139":1,"143":1,"147":1,"150":2,"151":1,"159":1,"179":1,"182":1,"185":1,"186":1,"189":1,"196":1,"201":1,"206":1,"217":1,"220":1,"225":1,"234":1,"235":2,"245":2,"249":1,"250":1,"251":1,"252":1,"255":1,"257":1,"265":6,"280":1,"282":1,"284":2,"286":2,"295":1,"312":1,"314":1,"320":2,"322":1,"328":1,"333":1,"341":1,"342":1,"344":1,"350":1,"363":1,"365":1,"370":1,"371":1,"380":1,"405":1,"409":1,"427":1,"439":1,"446":1,"447":1,"451":1,"458":1,"459":1,"463":1,"464":1,"465":1,"466":1,"468":1,"472":2,"478":1,"486":1,"489":1,"497":3,"499":3,"502":1,"507":1,"510":1,"511":1,"536":2,"537":1,"538":2,"539":1,"543":1,"556":2,"563":1,"564":2,"566":1,"572":7,"573":1,"576":2,"577":1,"583":1,"585":3,"586":1,"591":3,"592":2,"593":1,"599":1,"608":1,"609":5,"610":1,"611":3,"618":1,"620":1,"625":1,"626":6,"628":1,"631":2,"632":1,"633":5,"640":3,"649":1,"657":1,"665":1,"682":1,"696":1,"698":2,"713":1,"715":2,"717":1,"718":1,"727":1,"733":2,"740":1,"743":1,"756":1,"759":2,"760":1,"765":3,"767":1,"768":2,"797":1,"798":1,"799":1,"804":1,"805":1,"810":2,"811":1,"812":1,"813":1,"818":1,"820":1,"821":1,"825":3,"827":3,"828":1,"832":1,"833":1,"837":1,"838":2,"842":2,"845":1,"859":1,"860":2,"861":4,"862":3,"863":2,"875":1,"876":1,"881":1,"890":1,"897":1,"905":3,"906":1,"910":1,"912":1,"914":1,"917":1,"918":1,"926":3,"930":1,"933":1,"941":1,"946":2,"948":1,"953":1,"954":1,"955":1,"966":1,"971":1,"973":1,"974":1,"976":1,"982":2,"983":1,"988":1,"989":1,"998":2,"1004":1,"1011":1,"1012":4,"1014":2,"1016":1,"1018":6,"1020":1,"1026":3,"1028":1,"1034":2,"1036":1,"1044":1,"1050":2,"1052":1,"1054":2,"1055":1,"1059":3,"1061":1,"1066":1,"1067":6,"1069":1,"1075":1,"1076":2,"1082":1,"1089":1,"1091":2,"1092":1,"1108":2,"1116":1,"1124":1,"1125":1,"1126":1,"1135":1,"1140":1,"1161":1,"1184":1,"1186":2,"1196":1,"1210":1,"1212":4,"1213":3,"1214":1,"1216":3,"1224":1,"1229":1,"1237":5,"1238":1,"1240":1,"1241":1,"1243":1,"1244":4,"1247":19,"1249":1,"1252":1,"1254":2,"1256":2,"1258":2,"1259":10,"1262":1,"1263":1,"1265":1,"1266":1,"1267":2,"1269":1,"1270":32,"1271":3,"1273":1,"1275":1,"1276":1,"1278":2,"1281":2,"1283":1,"1284":1,"1285":1,"1286":76,"1288":1,"1289":2,"1290":1,"1293":2,"1297":2,"1298":1,"1300":76,"1301":14,"1304":1,"1306":1,"1307":1,"1309":13,"1310":6,"1311":30,"1312":2,"1315":1,"1317":3,"1323":41,"1324":77,"1326":2,"1328":3,"1330":1,"1332":2,"1334":1,"1337":2,"1339":42,"1340":1,"1342":2,"1344":2,"1346":1,"1347":3,"1350":27,"1352":1,"1353":2,"1357":2,"1358":2,"1359":138,"1365":2,"1366":2,"1369":10,"1372":1,"1373":5,"1375":1,"1377":1,"1380":13,"1382":1,"1383":2,"1384":1,"1385":1,"1388":1,"1390":1,"1391":1,"1394":1,"1395":61,"1396":46,"1398":1,"1399":1,"1402":19,"1405":1,"1413":1,"1414":2,"1415":31,"1416":6,"1417":21,"1418":1,"1419":1,"1420":1,"1421":1,"1427":10,"1428":2,"1429":1,"1430":1,"1431":7,"1432":2,"1434":1,"1435":4,"1436":241,"1437":6,"1438":30,"1441":1,"1442":3,"1443":3,"1445":4,"1446":3,"1447":1,"1448":1,"1449":2,"1450":1,"1453":10,"1455":8,"1456":3,"1458":4,"1460":3,"1461":1,"1462":1,"1465":4,"1467":20,"1469":1,"1471":1,"1474":3,"1475":3,"1476":4,"1477":1,"1480":1,"1481":1,"1484":2,"1486":4,"1487":9,"1488":7,"1489":7,"1490":10,"1492":11,"1495":1,"1496":52,"1499":1,"1504":1,"1516":1,"1519":1,"1522":1,"1524":1,"1525":1,"1526":8,"1527":2,"1531":3,"1534":2,"1536":2,"1537":1,"1545":1,"1548":1,"1549":1,"1551":1,"1567":1,"1572":2,"1573":1,"1574":1,"1575":1,"1577":14,"1578":1,"1582":2,"1587":1,"1589":1,"1591":4,"1592":1,"1596":3,"1599":2,"1600":1,"1606":1,"1611":2,"1631":4,"1632":2,"1639":2,"1640":2,"1641":5,"1643":1,"1648":1,"1649":1,"1653":5,"1654":2,"1656":1,"1659":1,"1661":2,"1663":1,"1666":1,"1667":3,"1668":1,"1669":1,"1671":4,"1672":2,"1673":2,"1675":1,"1683":1,"1684":1,"1685":1,"1695":2,"1696":1,"1698":1,"1706":1,"1707":3,"1716":2,"1719":2,"1720":1,"1722":2,"1727":6,"1728":2,"1729":2,"1731":3,"1736":1,"1740":1,"1747":1,"1748":2,"1749":2,"1750":2,"1765":6,"1767":2,"1779":1,"1780":3,"1782":1,"1783":1,"1784":1,"1790":2,"1793":1,"1794":2,"1795":1,"1797":1,"1800":1,"1801":1,"1802":2,"1810":2,"1812":1,"1815":4,"1816":1,"1817":2,"1822":1,"1826":1,"1839":1,"1840":1,"1847":1,"1852":1,"1859":1,"1861":1,"1863":1,"1871":1,"1889":1,"1891":2,"1895":1,"1897":2,"1898":1,"1899":2,"1900":1,"1901":1,"1902":1,"1909":1,"1911":1,"1915":1,"1918":2,"1919":2,"1920":1,"1923":1,"1925":1,"1927":1,"1929":1,"1930":1,"1934":1,"1944":1,"1945":1,"1948":1,"1950":1,"1962":1,"1966":1,"1974":1,"1982":2,"1983":1,"1990":1,"1996":2,"1999":2,"2000":1,"2001":2,"2002":1,"2009":1,"2021":1,"2024":1,"2030":1,"2034":4,"2040":1,"2041":2,"2044":4,"2045":4,"2046":2,"2047":6,"2048":1,"2053":3,"2055":3,"2056":1,"2057":2,"2059":3,"2062":1,"2066":1,"2067":1,"2072":1,"2074":1,"2079":3,"2082":1,"2083":2,"2085":1,"2086":1,"2087":1,"2089":2,"2096":1,"2097":1,"2100":2,"2104":1,"2108":1,"2114":1,"2117":1,"2123":1,"2126":3,"2127":1,"2129":1,"2130":1,"2141":1,"2144":2,"2145":1,"2149":1,"2152":1,"2155":1,"2157":1,"2158":3,"2164":1,"2167":2,"2168":1,"2180":1,"2186":1,"2192":2,"2198":1,"2204":1,"2205":2,"2214":1,"2220":1,"2231":1,"2232":3,"2234":1,"2245":1}}],["agnostic",{"0":{"1361":1},"3":{"0":1,"59":1,"109":1,"110":1,"118":1,"164":1,"166":1,"167":1,"177":1,"265":2,"507":1,"690":1,"774":1,"1068":1,"1192":1,"1212":1,"1217":1,"1229":1,"1247":1,"1248":1,"1259":3,"1271":1,"1278":1,"1286":7,"1300":2,"1301":2,"1309":1,"1311":4,"1312":2,"1323":1,"1324":6,"1332":1,"1339":1,"1350":1,"1351":1,"1359":6,"1360":1,"1395":1,"1396":5,"1402":2,"1412":1,"1415":5,"1416":2,"1417":7,"1427":1,"1436":6,"1438":1,"1445":1,"1496":2,"1510":1,"1576":1,"1651":1,"1653":1,"1663":1,"1720":1,"1765":1,"1783":1,"1790":1,"1793":2,"1805":1,"1926":1,"1933":1,"1935":1,"1937":1,"2001":1,"2016":1,"2079":1,"2083":1,"2085":1,"2086":1,"2104":1,"2179":1,"2227":1,"2230":1}}],["aged",{"3":{"1465":1}}],["agerangespec",{"3":{"1199":2,"1207":1}}],["agegreaterthanspecification",{"3":{"1199":2,"1207":1}}],["agegreaterthanspec",{"3":{"1199":2,"1207":1}}],["agenda",{"3":{"387":1,"1309":5,"1350":1,"1351":1,"1358":1,"1359":4,"1369":2,"1436":3,"1438":1,"1489":1,"1631":1,"1922":2}}],["agents",{"2":{"636":1},"3":{"633":1,"636":2,"914":1,"917":1,"1004":1,"1007":1,"1167":1,"1212":1,"1496":1,"1526":3,"1531":1,"1570":1,"1577":2,"1591":1,"2239":2}}],["agentic",{"3":{"0":1,"1570":1,"1591":1,"2239":1}}],["agent",{"3":{"0":2,"1":1,"774":2,"905":1,"1013":1,"1067":1,"1069":1,"1090":2,"1169":1,"1283":1,"1286":2,"1300":3,"1311":1,"1324":2,"1339":1,"1395":1,"1406":1,"1415":2,"1430":1,"1436":5,"1438":1,"1449":1,"1465":1,"1553":3,"1577":2,"1582":1,"2171":1,"2221":1,"2239":5}}],["age=",{"3":{"1436":1}}],["age=0",{"3":{"217":1,"1324":1,"1339":1,"2149":1}}],["age=31536000",{"3":{"214":1,"1339":1,"1359":1,"2149":1}}],["ages",{"3":{"0":1,"903":1,"905":2,"907":1,"930":1,"1259":1,"1311":1,"1339":1,"1455":1,"1591":1,"1801":1,"1984":1}}],["age",{"2":{"24":1,"2156":1,"2191":1},"3":{"0":2,"15":1,"24":3,"200":1,"501":1,"536":2,"538":1,"540":1,"572":1,"591":1,"594":1,"625":1,"626":3,"627":1,"628":1,"629":1,"707":2,"733":1,"735":1,"809":1,"1256":1,"1259":5,"1286":3,"1298":1,"1300":1,"1301":2,"1324":5,"1395":2,"1445":1,"1455":1,"1460":1,"1465":1,"1467":1,"1475":2,"1476":1,"1488":1,"1665":1,"1982":1,"1984":2,"1985":1,"2055":1,"2156":3,"2159":1,"2167":2,"2168":1,"2191":3}}],["ahead",{"3":{"0":3,"62":1,"107":1,"109":1,"111":1,"170":1,"202":2,"214":1,"217":1,"265":1,"284":1,"290":1,"296":1,"348":1,"350":1,"353":1,"401":1,"459":1,"462":1,"464":1,"465":1,"534":1,"749":1,"751":1,"790":2,"848":1,"988":2,"1012":1,"1076":1,"1117":1,"1259":2,"1264":1,"1265":1,"1267":1,"1270":2,"1286":9,"1300":1,"1311":5,"1323":2,"1324":3,"1333":1,"1339":3,"1344":1,"1359":21,"1369":2,"1383":1,"1395":3,"1396":6,"1402":2,"1415":2,"1416":1,"1417":1,"1436":4,"1455":2,"1489":1,"1496":2,"1686":1,"1799":1,"1821":1,"1825":1,"1852":1,"1888":1,"1891":1,"1920":1,"1977":1,"2122":1,"2126":1,"2157":1}}],["amd64",{"3":{"1465":1}}],["am",{"0":{"2062":1},"3":{"1396":1,"1415":1,"1416":1,"1417":4,"1436":1,"2078":1,"2116":2,"2136":1,"2213":2}}],["amber",{"3":{"1324":2}}],["ambiguities",{"3":{"1639":1}}],["ambiguity",{"1":{"985":1,"986":1,"987":1,"988":1,"989":1,"990":1,"991":1,"992":1},"3":{"0":1,"109":1,"361":1,"592":1,"629":1,"985":1,"987":1,"988":5,"989":1,"990":1,"991":1,"1270":1,"1286":5,"1293":1,"1300":1,"1359":1,"1369":1,"1380":1,"1395":1,"1436":1,"1454":1,"1577":1,"1664":1,"1840":1,"2232":1}}],["ambiguousmatchexception",{"3":{"1324":2}}],["ambiguoussource",{"2":{"1840":1,"1852":1},"3":{"988":1,"1286":1,"1840":1,"1852":1}}],["ambiguous",{"0":{"1840":1},"3":{"0":1,"109":1,"318":1,"320":1,"324":1,"325":1,"326":1,"617":1,"709":1,"861":1,"988":3,"992":1,"1191":1,"1196":1,"1270":3,"1278":1,"1286":5,"1323":1,"1324":2,"1350":1,"1359":3,"1380":1,"1395":1,"1415":4,"1436":5,"1496":16,"1500":1,"1611":1,"1661":1,"1664":1,"1729":1,"1840":2,"1852":1}}],["ambiently",{"3":{"1286":1}}],["ambienthandler",{"3":{"1247":2}}],["ambientscope",{"2":{"1246":1},"3":{"1199":2,"1203":1,"1207":1,"1246":1,"1247":3,"1496":1}}],["ambientorigin",{"2":{"26":1,"202":1,"702":1,"1040":1,"1042":1,"1253":1,"1318":1,"2186":1,"2193":1},"3":{"0":1,"26":4,"202":3,"702":2,"1040":1,"1042":3,"1199":6,"1203":1,"1207":2,"1253":1,"1259":5,"1286":6,"1313":1,"1318":6,"1323":14,"1496":2,"2186":1,"2193":1}}],["ambient",{"1":{"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1},"3":{"0":3,"15":1,"202":1,"359":1,"365":1,"372":1,"583":1,"585":1,"899":1,"905":1,"926":1,"988":1,"1158":1,"1161":1,"1212":2,"1229":1,"1237":2,"1246":2,"1247":4,"1251":1,"1253":2,"1259":10,"1263":2,"1265":1,"1270":10,"1271":2,"1274":1,"1277":1,"1278":1,"1286":20,"1296":1,"1300":5,"1306":1,"1309":7,"1311":5,"1313":1,"1318":2,"1323":5,"1324":11,"1350":3,"1352":1,"1359":23,"1395":1,"1396":8,"1402":2,"1412":1,"1415":2,"1417":1,"1436":12,"1496":1,"1526":1,"1577":1,"1585":1,"1591":2,"1664":1,"1840":1,"2060":1,"2135":1,"2142":1,"2232":1}}],["america",{"3":{"1350":1,"1359":2,"1364":1,"1369":1,"1384":1,"1395":3,"1438":1,"1630":1,"1631":1,"1637":1,"2220":1}}],["american",{"3":{"1020":1,"1091":1,"1424":1}}],["amends",{"1":{"730":1,"731":1,"732":1,"733":1,"734":1,"735":1,"736":1,"737":1},"3":{"0":5,"247":2,"432":1,"730":1,"731":1,"817":1,"836":1,"929":1,"1212":5,"2032":1,"2232":2}}],["amendments",{"3":{"66":1,"72":1,"827":1,"1339":1}}],["amendment",{"0":{"81":1},"3":{"0":1,"76":1,"81":2,"82":1,"230":1,"241":1,"243":1,"249":1,"258":1,"395":1,"409":1,"411":2,"438":1,"825":2,"827":2,"1079":1,"1339":1,"1455":1,"1458":1,"1467":1,"1600":1,"1748":1,"1755":1,"1765":1,"2025":1}}],["amended",{"0":{"401":1,"402":1,"407":1},"3":{"0":16,"15":1,"57":1,"66":1,"76":1,"145":7,"184":1,"223":1,"241":3,"243":3,"244":1,"253":1,"263":1,"265":2,"328":2,"386":6,"395":14,"397":1,"408":1,"409":1,"411":1,"439":1,"446":1,"526":2,"534":3,"680":2,"688":2,"691":1,"737":1,"817":1,"842":1,"1073":1,"1212":1,"1496":1,"1577":1,"1631":1}}],["amqpendpoint",{"3":{"1339":1}}],["amqpendpointname",{"3":{"1339":1}}],["amqptargetport",{"3":{"1339":1}}],["amqp",{"3":{"626":1,"640":1,"1075":1,"1323":2,"1327":1,"1339":5,"1436":3,"1438":2,"1444":1,"1455":1}}],["amplify",{"3":{"2037":1}}],["amplifies",{"3":{"2007":1}}],["amplified",{"3":{"1339":1}}],["amplifier",{"3":{"369":1,"1311":1,"1324":1}}],["amplification",{"3":{"227":1,"717":1,"792":1,"1259":1,"1324":1,"1332":1,"1339":1,"1940":1}}],["ample",{"3":{"361":1,"1286":1,"1300":1,"2145":1}}],["amortized",{"3":{"1534":1}}],["amortizes",{"3":{"459":1}}],["amortises",{"3":{"1247":1}}],["amounts",{"3":{"1229":1,"1237":1,"1324":4,"1767":1}}],["amount",{"2":{"660":1},"3":{"284":1,"536":1,"592":1,"657":5,"658":1,"660":1,"683":1,"1026":2,"1027":1,"1234":1,"1237":6,"1247":1,"1282":1,"1286":3,"1312":2,"1324":6,"1389":1,"1395":3,"1396":3,"1402":1,"1415":1,"1436":5,"1467":1,"1749":2,"1764":3,"1765":2,"1767":3,"1770":1,"1776":1,"1810":1,"1909":1,"1941":1,"2047":1,"2073":1}}],["among",{"3":{"0":2,"111":1,"132":1,"175":1,"386":1,"549":1,"551":1,"577":1,"591":1,"628":1,"633":1,"655":1,"743":1,"765":1,"766":1,"891":1,"1116":1,"1124":1,"1196":1,"1213":1,"1229":1,"1237":1,"1239":1,"1243":1,"1247":1,"1270":1,"1286":2,"1309":1,"1323":2,"1324":2,"1339":1,"1350":2,"1359":5,"1369":3,"1395":1,"1404":1,"1406":1,"1415":2,"1417":2,"1427":1,"1436":3,"1467":1,"1469":1,"1496":8,"1526":1,"1582":1,"1583":1,"1631":1,"1635":1,"1722":1,"1824":1,"1839":1,"1963":1,"2000":1,"2043":1,"2087":1}}],["amr",{"2":{"2197":1},"3":{"0":1,"1059":1,"1300":1,"2197":1}}],["ac5b175",{"3":{"1496":1}}],["acompiledpermission",{"3":{"1436":1}}],["acontrollerthatinheritsitsdecisionfromanabstractbase",{"3":{"1436":3}}],["acountinafixedpluralsentence",{"3":{"1436":2}}],["achieved",{"3":{"1339":1,"1395":1}}],["achieves",{"3":{"809":1}}],["acute",{"3":{"1324":1}}],["acme",{"2":{"1718":1,"1723":1},"3":{"1270":1,"1718":1,"1719":2,"1723":1,"1851":1,"1920":3}}],["acyclicfixtures",{"3":{"1207":1,"1436":2}}],["acyclicconsumer",{"3":{"1199":1,"1207":1,"1436":3}}],["acyclicity",{"3":{"846":1,"1247":1,"1270":1,"1436":4,"1671":1}}],["acyclic",{"3":{"507":1,"513":1,"1207":2,"1286":1,"1324":2,"1417":3,"1431":1,"1436":7,"1572":1}}],["acquiring",{"3":{"707":1,"1270":1,"1298":1,"1300":3,"1324":1,"1339":1,"1395":1,"1909":1,"1912":1}}],["acquireasynccore",{"3":{"1311":2}}],["acquireasync",{"3":{"1270":3,"1300":3,"1301":1}}],["acquireaccesstokenasync",{"2":{"507":1},"3":{"507":1,"1324":7,"1417":1,"1436":1}}],["acquires",{"3":{"0":1,"510":1,"524":1,"549":1,"996":1,"1145":1,"1300":1,"1301":2,"1311":1,"1324":3,"1396":1}}],["acquire",{"3":{"0":1,"157":2,"827":1,"863":1,"996":2,"998":1,"1270":2,"1286":1,"1300":2,"1301":1,"1311":1,"1316":1,"1323":7,"1324":3,"1402":1,"1417":1,"1436":1,"1438":1,"1577":2,"1909":1,"2174":1}}],["acquired",{"3":{"0":1,"688":1,"1300":1,"1311":6,"1323":1,"1324":4,"1436":1}}],["acquisitions",{"3":{"507":1}}],["acquisition",{"3":{"506":1,"507":1,"513":2,"550":1,"551":1,"996":3,"997":1,"1269":1,"1300":1,"1311":1,"1316":1,"1323":9,"1324":8,"1417":2,"1669":1,"1909":2}}],["acknowledging",{"3":{"1306":1,"1396":1,"1933":1}}],["acknowledge",{"3":{"1417":1}}],["acknowledges",{"3":{"1396":1,"2169":1}}],["acknowledgement",{"3":{"988":1,"1286":1,"1339":1,"1380":1,"1438":1,"1840":1}}],["acknowledged",{"3":{"790":1,"794":1,"1323":1,"1359":1,"1402":1,"1415":1}}],["acks",{"3":{"1259":2,"1323":1}}],["acked",{"3":{"1258":1,"1259":1,"1311":2,"1355":2,"1359":1,"1396":1,"1402":1,"1413":1,"1415":1,"1910":1}}],["acking",{"3":{"195":1,"1259":1,"1396":2}}],["ack",{"3":{"194":1,"201":2,"1259":2,"1323":2}}],["acl",{"3":{"0":1,"1576":1,"1577":1}}],["acted",{"3":{"1237":1,"1286":1,"1324":1,"1350":1,"1395":1,"1402":2}}],["acts",{"3":{"435":1,"538":1,"601":1,"741":1,"793":1,"813":1,"821":1,"1228":1,"1233":1,"1286":1,"1311":1,"1318":1,"1323":1,"1324":1,"1359":2,"1402":1,"1415":2,"1436":2,"1438":1,"1455":1,"1473":1,"1478":1,"1651":1,"1822":1,"1840":1,"1843":1,"2133":1}}],["actual",{"2":{"1457":1,"1458":1},"3":{"93":1,"235":1,"265":1,"368":1,"529":1,"666":1,"707":1,"765":1,"767":1,"800":1,"839":1,"1237":1,"1241":1,"1247":2,"1251":1,"1259":1,"1270":1,"1271":1,"1281":1,"1285":1,"1286":7,"1300":6,"1307":1,"1310":1,"1311":1,"1323":7,"1324":5,"1339":2,"1350":1,"1359":5,"1369":3,"1380":1,"1395":5,"1396":7,"1402":2,"1415":5,"1416":1,"1417":3,"1427":1,"1429":1,"1436":10,"1438":1,"1441":1,"1455":1,"1457":2,"1458":2,"1466":1,"1467":1,"1488":1,"1496":3,"1498":1,"1567":1,"1591":2,"1653":1,"1686":1,"1764":1,"1815":1,"1817":1,"1818":1,"1865":1,"1927":1,"2042":1,"2053":1,"2126":1,"2194":1}}],["actually",{"0":{"1363":1,"1399":1,"1425":1,"1872":1,"1883":1,"1909":1,"2111":1,"2144":1},"1":{"1794":1,"1795":1,"1796":1,"1797":1,"1798":1,"1799":1,"1800":1,"1801":1,"1802":1,"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1956":1,"1957":1,"1958":1,"1959":1,"2029":1,"2030":1,"2031":1,"2032":1,"2033":1,"2034":1,"2035":1,"2036":1,"2037":1,"2038":1},"3":{"0":2,"10":1,"21":1,"38":1,"70":1,"103":1,"109":1,"115":1,"125":1,"126":1,"130":1,"136":1,"150":1,"157":2,"177":1,"200":1,"215":1,"259":1,"286":1,"318":1,"335":2,"342":1,"343":1,"359":1,"369":1,"371":1,"373":1,"395":1,"401":1,"407":1,"428":1,"447":1,"461":2,"463":1,"506":1,"526":2,"543":2,"549":1,"564":1,"572":1,"585":1,"592":2,"601":1,"607":1,"626":1,"627":1,"630":1,"638":1,"639":1,"642":1,"650":1,"674":1,"682":1,"689":2,"690":1,"691":1,"714":1,"716":1,"718":1,"741":1,"758":1,"767":1,"773":1,"774":1,"789":1,"791":1,"797":1,"832":1,"836":1,"838":1,"861":1,"873":1,"876":1,"881":1,"905":1,"906":1,"914":1,"946":1,"962":1,"963":1,"971":1,"972":2,"1033":1,"1041":1,"1074":1,"1084":1,"1123":1,"1142":1,"1147":1,"1162":1,"1176":1,"1190":1,"1212":1,"1220":1,"1229":2,"1231":2,"1237":1,"1244":2,"1247":6,"1253":1,"1259":5,"1263":1,"1267":1,"1270":9,"1271":1,"1276":1,"1277":1,"1286":42,"1300":16,"1301":3,"1302":1,"1309":5,"1310":4,"1311":17,"1312":3,"1317":1,"1323":23,"1324":28,"1339":18,"1344":1,"1350":14,"1351":1,"1352":1,"1359":45,"1363":1,"1369":7,"1379":1,"1380":7,"1381":1,"1389":2,"1392":1,"1395":28,"1396":33,"1397":1,"1398":1,"1402":13,"1405":1,"1414":1,"1415":24,"1416":8,"1417":12,"1421":1,"1424":1,"1425":1,"1427":4,"1431":3,"1432":2,"1435":2,"1436":78,"1438":8,"1443":1,"1448":1,"1449":1,"1451":1,"1453":1,"1454":1,"1455":5,"1458":1,"1461":1,"1467":3,"1470":3,"1471":1,"1476":1,"1484":1,"1486":1,"1488":2,"1489":5,"1490":3,"1491":1,"1492":1,"1496":2,"1500":1,"1537":1,"1566":1,"1572":1,"1605":1,"1651":1,"1665":1,"1684":1,"1685":2,"1686":1,"1701":1,"1710":1,"1721":1,"1726":1,"1730":1,"1739":1,"1749":1,"1764":1,"1767":1,"1769":5,"1783":1,"1784":1,"1788":1,"1793":2,"1794":1,"1796":2,"1810":1,"1813":1,"1818":1,"1821":1,"1825":1,"1852":1,"1860":1,"1865":1,"1867":1,"1876":2,"1877":1,"1895":1,"1908":1,"1909":1,"1916":1,"1920":1,"1929":1,"1930":1,"1931":1,"1950":1,"1951":1,"1953":2,"1955":1,"1960":1,"1965":1,"1968":1,"1973":1,"1980":1,"1994":1,"1998":1,"2000":1,"2002":1,"2010":1,"2012":1,"2013":1,"2027":1,"2029":3,"2033":1,"2038":1,"2041":1,"2042":1,"2043":1,"2044":1,"2045":1,"2048":1,"2053":1,"2055":2,"2059":1,"2060":1,"2070":1,"2077":1,"2082":1,"2087":1,"2098":1,"2104":1,"2105":1,"2109":1,"2114":1,"2119":1,"2126":1,"2128":2,"2129":1,"2141":1,"2142":1,"2145":1,"2147":1,"2150":1,"2151":1,"2153":1,"2154":1,"2156":1,"2160":3,"2169":2,"2177":1,"2183":1,"2191":1,"2203":1,"2205":1,"2208":1,"2209":1,"2232":1}}],["act",{"0":{"1236":1},"3":{"39":1,"81":1,"120":1,"202":1,"215":1,"225":1,"231":1,"830":1,"888":1,"898":1,"905":2,"989":2,"1005":1,"1051":1,"1067":2,"1091":1,"1175":1,"1186":1,"1252":1,"1259":2,"1265":1,"1270":2,"1280":1,"1286":8,"1289":1,"1300":2,"1304":1,"1309":2,"1311":1,"1313":1,"1318":1,"1323":1,"1324":4,"1327":1,"1339":2,"1342":1,"1350":2,"1357":1,"1358":1,"1359":11,"1376":1,"1380":1,"1396":5,"1402":5,"1415":4,"1427":2,"1430":1,"1436":7,"1438":2,"1441":1,"1478":5,"1485":1,"1639":1,"1654":1,"1771":1,"1824":1,"1873":1,"1877":1,"1934":1,"1940":1,"1941":1,"2033":1,"2056":1,"2152":1,"2170":1,"2174":1,"2190":1}}],["actors",{"0":{"1621":1,"1757":1},"3":{"1152":1,"1154":1,"1350":1,"1438":1,"1632":21,"1638":1,"1641":6}}],["actor",{"1":{"1148":1,"1149":1,"1150":1,"1151":1,"1152":1,"1153":1,"1154":1,"1155":1,"1156":1,"1157":1},"2":{"1150":1},"3":{"0":1,"340":1,"718":1,"1148":1,"1149":1,"1150":7,"1151":2,"1152":2,"1153":1,"1154":2,"1155":1,"1212":2,"1300":4,"1359":5,"1395":1,"1396":2,"1436":1,"1438":1,"1459":1,"1496":1,"1526":1,"1562":2,"1591":1,"1620":2,"1621":1,"1626":1,"1632":5,"1639":1,"1756":3,"1757":1,"1761":2,"1768":2,"1844":1,"1869":1,"2232":1}}],["actingspeakerid",{"2":{"1357":1},"3":{"1357":1,"1359":14,"1373":1,"1380":5}}],["acting",{"3":{"290":1,"797":1,"802":1,"1274":1,"1286":1,"1300":2,"1324":1,"1359":7,"1377":1,"1396":1,"1402":1,"1415":4,"1417":2,"1438":1,"1765":1}}],["activitiescontrollertests",{"3":{"1199":1,"1207":2,"1438":2}}],["activitiescontroller",{"3":{"1199":4,"1203":1,"1207":2,"1350":4,"1359":21,"1371":2,"1372":3,"1373":3,"1375":2,"1380":11,"1436":1}}],["activitiescache",{"2":{"386":1},"3":{"386":1,"1350":1,"1379":1,"1380":2}}],["activitiesmanage",{"2":{"184":1},"3":{"184":1,"1350":2,"1359":2,"1373":1,"1380":2}}],["activities",{"0":{"1392":1},"2":{"1359":1,"1380":1,"1395":1},"3":{"15":1,"186":1,"418":1,"836":1,"881":1,"1004":1,"1043":1,"1203":36,"1207":96,"1270":2,"1271":1,"1342":4,"1346":2,"1347":1,"1348":2,"1350":30,"1351":2,"1352":3,"1353":2,"1359":114,"1361":1,"1362":1,"1363":1,"1364":1,"1369":11,"1375":1,"1380":8,"1381":1,"1382":3,"1383":5,"1384":3,"1391":1,"1392":5,"1395":48,"1415":1,"1436":1,"1437":1,"1438":5,"1447":1,"1526":4,"1621":1,"1625":1,"1626":3,"1627":7,"1639":1,"2110":1,"2232":1}}],["activitylabel",{"3":{"1396":2}}],["activitylistener",{"3":{"1339":2}}],["activitylist",{"2":{"1627":1},"3":{"1199":1,"1203":1,"1207":2,"1324":2,"1350":2,"1383":4,"1392":2,"1395":12,"1627":1}}],["activitykind",{"3":{"1339":4}}],["activityvalidationrules",{"3":{"1207":9,"1271":1,"1350":1,"1353":1,"1359":44}}],["activityvenueurlrules",{"3":{"1199":2,"1203":1,"1207":1,"1271":1,"1359":4}}],["activityvenuenamerules",{"3":{"1199":2,"1203":1,"1207":1,"1359":2}}],["activityvenueaddressrules",{"3":{"1199":2,"1203":1,"1207":1,"1359":2}}],["activitynavigationpopulatortests",{"3":{"1199":1,"1207":2,"1359":2}}],["activitynavigationpopulator",{"3":{"1199":3,"1203":1,"1207":2,"1350":1,"1354":1,"1359":10}}],["activitynamerules",{"3":{"1199":2,"1203":1,"1207":1,"1350":1,"1359":5}}],["activitybuilder",{"3":{"1199":3,"1207":2,"1436":1,"1438":1}}],["activityupdateappliertests",{"3":{"1199":1,"1207":2}}],["activityupdateapplier",{"3":{"1199":2,"1203":1,"1207":2,"1270":2,"1354":1,"1359":7}}],["activityupdaterequestvalidatortests",{"3":{"1199":1,"1207":2}}],["activityupdaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1359":11}}],["activityupdaterequest",{"3":{"1199":9,"1203":1,"1207":2,"1350":1,"1359":15,"1380":1}}],["activityformfields",{"3":{"1350":1,"1395":2}}],["activityformmodel",{"3":{"1199":3,"1203":1,"1207":2,"1350":1,"1384":2,"1395":9}}],["activityfieldrules",{"3":{"1199":3,"1203":1,"1207":1,"1353":1,"1359":18}}],["activitydetails",{"2":{"1394":1},"3":{"1394":1,"1395":1}}],["activitydetailtests",{"3":{"1199":1,"1207":2}}],["activitydetail",{"2":{"1392":1,"1627":1},"3":{"1199":2,"1203":1,"1207":2,"1350":1,"1384":2,"1392":2,"1395":20,"1627":1}}],["activitydescriptionrules",{"3":{"1199":2,"1203":1,"1207":1,"1359":7}}],["activitydtomappertests",{"3":{"1199":1,"1207":2,"1359":2}}],["activitydtomapper",{"3":{"1199":5,"1203":1,"1207":2,"1350":3,"1353":1,"1359":10}}],["activitydto",{"3":{"1199":20,"1203":1,"1207":2,"1347":2,"1350":16,"1359":14,"1380":1,"1382":1,"1395":14}}],["activityid",{"3":{"1350":2}}],["activityidentifiertype",{"2":{"468":1,"475":1,"803":1},"3":{"468":1,"475":1,"803":1,"1350":3,"1359":5,"1395":4}}],["activityinvariantstests",{"3":{"1199":1,"1207":2,"1438":1}}],["activityinvariants",{"2":{"1344":1,"1362":1},"3":{"1199":12,"1203":1,"1207":2,"1344":4,"1350":10,"1359":29,"1362":1,"1369":5}}],["activityevent",{"3":{"1369":1}}],["activityeventidrules",{"3":{"1199":2,"1203":1,"1207":1,"1359":12}}],["activityeditmodel",{"3":{"1199":3,"1203":1,"1207":2,"1395":6}}],["activitycontext",{"3":{"1259":1}}],["activityconfiguration",{"2":{"1361":1},"3":{"1199":1,"1203":1,"1207":2,"1350":3,"1361":2,"1362":5,"1369":4}}],["activitycreatetests",{"3":{"1199":1,"1207":2,"1436":1}}],["activitycreate",{"2":{"1392":1,"1627":1},"3":{"1199":2,"1203":1,"1207":2,"1350":3,"1392":2,"1395":19,"1526":1,"1627":1}}],["activitycreaterequestvalidatortests",{"3":{"1199":1,"1207":2,"1359":2}}],["activitycreaterequestvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1359":18}}],["activitycreaterequestmapper",{"3":{"1199":1,"1203":1,"1207":2,"1359":9}}],["activitycreaterequest",{"3":{"1199":10,"1203":1,"1207":2,"1270":1,"1350":1,"1359":23,"1380":1}}],["activitycreatemodel",{"3":{"1199":4,"1203":1,"1207":2,"1395":5}}],["activitychanged",{"2":{"780":1},"3":{"780":1,"782":1,"1199":3,"1203":1,"1207":2,"1345":1,"1350":10,"1359":2}}],["activitysortorderrules",{"3":{"1199":2,"1203":1,"1207":1,"1359":4}}],["activitysource",{"2":{"405":1,"1096":1},"3":{"405":1,"1096":1,"1286":1,"1427":1}}],["activityservice",{"3":{"881":1,"1199":5,"1203":1,"1207":2,"1382":3,"1395":19}}],["activitytraceflags",{"2":{"1333":1},"3":{"1333":2,"1339":4,"1443":1}}],["activitytests",{"3":{"1199":1,"1207":2,"1438":1}}],["activitytimerangerules",{"3":{"1199":2,"1203":1,"1207":1,"1353":2,"1359":7}}],["activitytimeoutheader",{"3":{"1436":1}}],["activitytimeout",{"2":{"837":1,"838":1},"3":{"837":1,"838":2,"1339":1,"1447":2}}],["activitytype",{"3":{"0":1,"690":1,"1212":1,"1396":25,"1415":3,"1631":1}}],["activity",{"2":{"692":1,"924":1,"1346":1,"1425":1},"3":{"0":1,"18":1,"395":2,"397":1,"399":2,"404":2,"406":1,"427":2,"428":1,"690":1,"692":3,"725":1,"827":1,"836":1,"837":1,"838":2,"842":1,"924":1,"1091":2,"1184":1,"1192":1,"1199":44,"1203":1,"1207":2,"1253":1,"1256":2,"1259":9,"1270":2,"1275":1,"1277":1,"1286":8,"1311":5,"1324":1,"1333":4,"1337":1,"1338":2,"1339":31,"1340":1,"1342":12,"1343":4,"1346":3,"1350":57,"1351":1,"1352":3,"1354":1,"1359":115,"1361":1,"1369":9,"1375":1,"1380":7,"1387":1,"1391":1,"1392":2,"1395":28,"1396":48,"1415":4,"1416":17,"1417":3,"1421":1,"1425":4,"1427":10,"1435":1,"1436":2,"1438":12,"1443":2,"1447":1,"1496":3,"1526":5,"1535":1,"1626":2,"1627":2,"1629":1,"1631":5,"1638":2,"1639":1,"1656":1,"1677":1,"1841":1,"2006":1,"2010":1,"2024":1,"2156":2,"2157":4,"2159":2,"2160":1}}],["activatable",{"3":{"1324":3}}],["activating",{"3":{"1286":1,"1293":1,"1300":1,"1322":1,"1417":1,"1436":1}}],["activationfailed",{"3":{"1455":1}}],["activations",{"3":{"1286":1}}],["activation",{"3":{"0":2,"558":1,"607":2,"609":4,"611":2,"755":1,"757":3,"758":1,"759":4,"760":3,"1150":1,"1270":1,"1286":4,"1300":1,"1323":1,"1396":4,"1416":8,"1417":2,"1436":2,"1442":1,"1455":6,"1461":4,"1465":2,"1467":7,"1469":3,"1474":2,"1476":3,"1477":1,"1482":1,"1496":1,"1591":1,"1764":1,"2036":2,"2038":1,"2158":1}}],["activatorutilities",{"3":{"1286":10}}],["activator",{"2":{"583":1,"585":1,"1315":1},"3":{"583":1,"585":1,"1229":2,"1237":1,"1247":1,"1286":3,"1300":2,"1315":1,"1323":1,"1436":1}}],["activate",{"3":{"1270":1,"1297":1,"1300":1,"1323":2,"1416":1,"1417":1,"1467":1}}],["activates",{"3":{"99":1,"234":1,"1278":1,"1286":1,"1293":1,"1300":1,"1311":1,"1322":1,"1417":3,"1431":3,"1436":3,"1441":1,"1443":1,"1455":2,"1591":1}}],["activated",{"3":{"0":1,"757":1,"760":1,"1432":1,"1436":3}}],["activeprobepath",{"3":{"1436":2}}],["activeprobetimeout",{"3":{"1436":1}}],["activeprobeinterval",{"3":{"1436":1}}],["activeutterance",{"3":{"1417":2}}],["activevote",{"3":{"1402":1}}],["activehealthcheckconfig",{"3":{"1339":3}}],["activehealthchecksexpected",{"2":{"578":1},"3":{"572":2,"578":1,"1436":5}}],["activecircuits",{"3":{"1324":4}}],["activespec",{"3":{"1199":2,"1207":1,"1496":1}}],["activerevisionsmode",{"2":{"757":1},"3":{"757":1,"1467":1}}],["actively",{"3":{"162":1,"174":1,"500":1,"1270":2,"1311":1,"1417":1,"1436":1,"1465":1,"1467":2,"1476":1,"1478":1,"1537":1,"1599":1,"2045":1}}],["active",{"1":{"456":1,"457":1,"458":1,"459":1,"460":1,"461":1,"462":1,"463":1,"464":1,"465":1,"466":1},"2":{"759":1},"3":{"0":7,"39":1,"40":1,"100":1,"120":1,"175":1,"195":1,"236":1,"265":4,"371":1,"372":1,"381":1,"390":1,"397":1,"402":2,"456":1,"458":1,"501":1,"545":1,"551":1,"572":5,"575":1,"578":2,"690":1,"715":1,"757":1,"759":1,"825":1,"827":5,"829":1,"831":1,"832":2,"833":6,"1026":3,"1042":2,"1124":2,"1125":1,"1168":2,"1231":1,"1237":1,"1247":2,"1274":1,"1286":14,"1293":1,"1294":1,"1300":15,"1309":1,"1311":2,"1312":2,"1318":2,"1323":3,"1324":28,"1329":1,"1330":1,"1333":2,"1338":2,"1339":24,"1350":7,"1355":2,"1359":13,"1385":1,"1395":17,"1396":38,"1398":2,"1400":1,"1401":1,"1402":30,"1413":1,"1415":6,"1417":8,"1424":1,"1436":21,"1438":1,"1443":6,"1447":4,"1450":1,"1451":1,"1455":2,"1467":4,"1477":1,"1488":1,"1526":5,"1534":1,"1562":1,"1590":1,"1591":1,"1600":1,"1627":1,"1630":3,"1631":3,"1632":2,"1633":1,"1635":2,"1639":7,"1640":1,"1641":2,"1643":1,"1653":1,"1663":1,"1667":1,"1671":1,"1677":2,"1747":2,"1749":3,"1750":3,"1761":1,"1764":2,"1765":6,"1767":5,"1768":1,"1769":1,"1773":2,"1810":1,"1861":1,"1984":1,"2010":1,"2030":1,"2055":1,"2063":1,"2069":1,"2083":1,"2085":1,"2086":1,"2156":1,"2158":1,"2185":2,"2220":1,"2238":2,"2241":1}}],["actiongroups",{"3":{"2158":1}}],["actiongroup",{"3":{"1577":1,"2158":1}}],["actionability",{"3":{"1436":1}}],["actionabletakeaways",{"3":{"1369":1}}],["actionabletakeawaysscore",{"3":{"1350":1,"1359":1,"1369":1}}],["actionable",{"3":{"1067":1,"1286":1,"1301":1,"1309":1,"1311":1,"1323":1,"1324":3,"1350":2,"1359":5,"1402":2,"1436":6,"1453":1,"1455":2,"1466":1,"1467":1,"1472":1,"1479":1,"1802":1,"2196":2,"2203":1}}],["actionview",{"3":{"1416":3}}],["actionid",{"3":{"1416":1}}],["actioned",{"3":{"1396":1}}],["action<",{"3":{"1395":1}}],["actioncolor",{"3":{"1324":1}}],["actiontext",{"3":{"1324":2}}],["actiondescriptor",{"2":{"1300":1},"3":{"1300":1}}],["actionresult",{"2":{"1227":1},"3":{"1227":1,"1311":1,"1380":1,"1415":2}}],["actions",{"1":{"1992":1,"1993":1,"1994":1,"1995":1,"1996":1,"1997":1},"2":{"626":1},"3":{"0":6,"149":1,"161":1,"175":2,"189":1,"273":1,"318":1,"324":1,"340":1,"343":1,"368":1,"370":2,"371":2,"372":2,"373":2,"374":5,"375":3,"413":1,"419":1,"448":1,"459":1,"591":2,"594":1,"626":17,"627":2,"628":2,"629":4,"692":1,"719":2,"725":1,"743":2,"854":1,"1012":1,"1074":1,"1116":1,"1247":1,"1270":1,"1286":4,"1294":1,"1300":6,"1309":5,"1311":25,"1323":5,"1324":16,"1350":3,"1359":11,"1369":1,"1371":2,"1372":1,"1373":1,"1375":1,"1380":19,"1395":4,"1396":5,"1402":9,"1406":2,"1407":2,"1408":1,"1409":1,"1414":2,"1415":18,"1416":7,"1417":4,"1432":1,"1436":14,"1438":1,"1452":1,"1453":5,"1454":5,"1455":4,"1456":2,"1457":1,"1459":2,"1461":2,"1465":1,"1466":1,"1467":6,"1468":1,"1472":2,"1474":1,"1475":8,"1482":1,"1483":1,"1484":3,"1488":2,"1491":1,"1492":2,"1496":11,"1526":3,"1531":2,"1534":1,"1553":1,"1559":2,"1561":1,"1577":4,"1582":2,"1583":1,"1585":1,"1586":1,"1591":2,"1599":2,"1626":2,"1627":1,"1630":1,"1631":4,"1632":13,"1633":1,"1641":2,"1747":1,"1749":1,"1750":3,"1760":2,"1870":1,"1874":1,"1898":1,"1925":1,"1930":1,"1991":1,"1992":1,"2000":1,"2001":2,"2083":1,"2131":1,"2208":1,"2219":1}}],["action",{"0":{"1350":1,"1372":1},"1":{"315":1,"316":1,"317":1,"318":1,"319":1,"320":1,"321":1,"322":1,"323":1,"324":1,"325":1,"326":1},"2":{"1370":1,"1919":1},"3":{"0":11,"24":1,"77":1,"136":1,"155":2,"157":8,"159":1,"160":8,"175":2,"197":1,"209":1,"214":1,"219":1,"226":1,"265":2,"299":1,"300":1,"315":1,"318":14,"319":1,"325":1,"326":1,"330":2,"332":1,"334":1,"340":1,"341":5,"343":2,"345":1,"368":1,"370":12,"371":2,"372":3,"374":3,"375":7,"428":1,"444":1,"448":1,"450":1,"523":1,"528":1,"530":1,"534":1,"536":3,"538":2,"591":4,"594":3,"609":2,"611":1,"624":1,"626":22,"628":2,"629":5,"643":1,"644":1,"674":1,"690":1,"694":1,"723":1,"725":4,"726":1,"729":1,"733":1,"741":2,"744":1,"749":1,"788":1,"793":1,"809":1,"868":1,"869":1,"873":1,"881":1,"883":1,"897":4,"903":1,"905":2,"907":2,"914":1,"921":1,"922":3,"923":1,"995":1,"996":1,"1019":1,"1116":1,"1117":1,"1118":1,"1212":4,"1217":1,"1218":1,"1220":2,"1229":4,"1237":2,"1244":1,"1247":10,"1249":1,"1259":2,"1264":1,"1270":2,"1286":6,"1297":4,"1300":33,"1301":2,"1304":1,"1306":1,"1309":13,"1310":2,"1311":82,"1316":1,"1323":9,"1324":28,"1339":6,"1350":6,"1359":68,"1369":1,"1370":1,"1371":2,"1372":5,"1373":6,"1374":3,"1375":5,"1376":2,"1380":61,"1385":1,"1391":2,"1392":2,"1395":29,"1396":37,"1399":2,"1402":42,"1408":1,"1409":2,"1410":2,"1415":34,"1416":15,"1417":6,"1431":2,"1436":51,"1438":4,"1452":2,"1453":8,"1454":4,"1455":2,"1459":8,"1467":9,"1470":2,"1474":1,"1475":11,"1476":3,"1484":1,"1488":1,"1489":1,"1490":1,"1492":5,"1496":5,"1526":5,"1530":1,"1531":1,"1534":1,"1553":1,"1556":1,"1577":4,"1581":2,"1582":4,"1583":1,"1586":1,"1589":1,"1591":3,"1595":1,"1596":1,"1627":1,"1631":3,"1632":23,"1633":1,"1634":1,"1638":1,"1639":2,"1651":2,"1653":1,"1666":1,"1667":1,"1685":5,"1686":6,"1727":2,"1730":1,"1748":1,"1764":1,"1776":1,"1803":1,"1805":3,"1814":1,"1870":1,"1871":1,"1873":2,"1874":6,"1876":1,"1888":1,"1908":6,"1909":4,"1911":1,"1912":1,"1919":3,"1946":1,"1971":1,"1988":2,"1990":1,"1991":1,"1992":1,"1993":1,"1994":9,"1995":2,"1997":3,"2121":1,"2131":1,"2137":2,"2149":1,"2150":1,"2158":1,"2163":3,"2166":1,"2168":2,"2169":1,"2232":1}}],["acrname",{"2":{"1465":1},"3":{"1455":1,"1465":4,"1466":2,"1467":1}}],["acrpurgetask",{"3":{"1465":1}}],["acrpush",{"3":{"1455":1,"1465":2,"1472":2}}],["acrpull",{"3":{"601":1,"1455":1,"1465":1,"1467":1,"1470":1}}],["acrloginserver",{"2":{"1455":1,"1465":1},"3":{"1455":1,"1465":1,"1467":1}}],["acr",{"2":{"1463":1},"3":{"0":1,"599":2,"600":1,"756":1,"757":1,"868":1,"1445":3,"1455":11,"1461":2,"1465":10,"1466":1,"1467":8,"1469":1,"1472":1,"1474":2,"1496":1,"1526":1,"1535":1,"1577":1,"1591":1,"2033":1,"2037":1}}],["acrosstwotypes",{"3":{"1436":3}}],["across",{"1":{"504":1,"505":1,"506":1,"507":1,"508":1,"509":1,"510":1,"511":1,"512":1,"513":1,"514":1,"1925":1,"1926":1,"1927":1,"1928":1,"1929":1,"1930":1},"3":{"0":33,"3":3,"7":1,"19":1,"23":1,"24":3,"26":1,"39":1,"49":2,"80":3,"92":1,"109":2,"126":1,"128":1,"130":1,"135":4,"136":2,"137":3,"138":2,"139":2,"141":3,"142":3,"145":2,"146":1,"147":1,"148":1,"150":2,"157":3,"158":2,"168":3,"169":1,"177":1,"178":1,"184":1,"186":1,"187":1,"199":2,"227":1,"265":1,"272":1,"274":1,"280":1,"298":1,"302":1,"303":1,"311":1,"312":1,"313":1,"320":1,"349":1,"352":1,"358":1,"359":1,"360":1,"391":1,"397":1,"400":1,"418":2,"421":1,"448":1,"451":1,"454":1,"457":1,"461":3,"468":2,"474":1,"475":2,"476":1,"477":1,"483":1,"484":2,"486":1,"487":5,"488":6,"489":7,"490":11,"491":9,"492":8,"493":10,"494":11,"495":9,"497":1,"499":1,"504":1,"506":1,"509":1,"511":1,"524":1,"528":1,"543":1,"545":1,"547":1,"550":2,"551":2,"552":1,"556":2,"557":1,"574":1,"583":1,"593":1,"610":1,"616":4,"617":1,"618":3,"628":1,"633":2,"650":1,"674":2,"675":1,"677":1,"690":1,"696":2,"698":1,"699":1,"700":1,"702":2,"707":2,"709":1,"717":1,"724":1,"727":1,"729":1,"733":3,"735":1,"757":2,"764":2,"780":4,"782":1,"783":1,"784":1,"790":1,"793":1,"798":3,"799":2,"800":1,"803":3,"804":3,"805":3,"806":1,"810":1,"819":1,"821":3,"823":2,"827":5,"837":1,"846":1,"867":1,"880":2,"881":1,"882":1,"883":1,"885":1,"889":1,"891":1,"905":2,"931":1,"946":1,"948":1,"950":1,"954":1,"955":1,"958":1,"963":1,"964":2,"965":1,"966":1,"973":1,"980":7,"981":2,"982":1,"983":1,"988":1,"996":2,"998":1,"1004":2,"1006":1,"1011":1,"1014":1,"1020":3,"1025":1,"1027":1,"1036":1,"1049":2,"1050":3,"1051":2,"1052":1,"1054":2,"1059":1,"1060":1,"1062":1,"1067":1,"1074":1,"1076":1,"1082":1,"1092":2,"1094":1,"1099":1,"1100":2,"1133":2,"1134":1,"1136":1,"1150":1,"1154":1,"1157":1,"1189":1,"1194":1,"1200":1,"1201":1,"1202":1,"1212":6,"1229":8,"1230":1,"1231":1,"1233":2,"1234":1,"1237":15,"1239":1,"1243":2,"1247":15,"1248":1,"1253":2,"1256":1,"1257":2,"1259":19,"1260":1,"1267":2,"1269":4,"1270":17,"1271":8,"1276":3,"1278":1,"1279":1,"1281":1,"1283":1,"1284":1,"1286":54,"1287":1,"1289":1,"1296":1,"1298":2,"1300":27,"1301":12,"1302":1,"1304":1,"1308":1,"1309":8,"1310":4,"1311":39,"1312":8,"1316":1,"1317":1,"1318":1,"1323":30,"1324":43,"1333":1,"1336":1,"1339":12,"1340":2,"1342":2,"1347":1,"1348":1,"1350":24,"1351":1,"1352":1,"1353":1,"1359":41,"1364":1,"1369":9,"1375":1,"1378":1,"1380":8,"1381":1,"1395":17,"1396":46,"1401":1,"1402":20,"1403":2,"1406":2,"1410":1,"1415":23,"1416":6,"1417":33,"1424":1,"1427":1,"1428":1,"1431":2,"1436":87,"1438":13,"1441":2,"1442":2,"1447":1,"1454":1,"1455":4,"1456":1,"1459":1,"1460":1,"1461":3,"1463":1,"1464":1,"1465":3,"1467":6,"1474":2,"1475":1,"1486":1,"1487":6,"1488":2,"1489":5,"1492":1,"1496":41,"1498":2,"1499":1,"1513":1,"1514":1,"1519":1,"1525":3,"1526":14,"1532":1,"1533":1,"1534":3,"1535":2,"1536":1,"1542":1,"1544":2,"1550":2,"1551":1,"1556":1,"1557":1,"1559":1,"1565":1,"1567":1,"1569":2,"1571":1,"1576":2,"1577":11,"1582":1,"1583":1,"1584":1,"1591":4,"1592":1,"1600":3,"1629":1,"1630":3,"1631":2,"1638":4,"1640":3,"1641":3,"1648":1,"1649":1,"1650":1,"1660":1,"1661":1,"1664":2,"1665":1,"1666":1,"1667":1,"1669":1,"1671":3,"1672":1,"1673":3,"1675":1,"1677":1,"1683":1,"1694":1,"1701":1,"1714":1,"1723":1,"1726":1,"1736":1,"1747":2,"1750":1,"1767":2,"1768":2,"1769":1,"1772":1,"1779":1,"1782":2,"1784":1,"1788":1,"1790":1,"1792":1,"1793":1,"1795":3,"1796":1,"1799":1,"1813":1,"1815":3,"1823":1,"1829":2,"1832":1,"1839":1,"1842":1,"1843":3,"1845":1,"1849":1,"1850":1,"1851":1,"1852":3,"1853":2,"1859":3,"1860":1,"1861":4,"1865":1,"1866":1,"1868":1,"1874":3,"1875":1,"1893":1,"1894":1,"1895":1,"1896":1,"1898":1,"1899":1,"1908":3,"1912":3,"1913":1,"1920":1,"1923":1,"1924":3,"1925":2,"1927":1,"1930":3,"1931":1,"1935":1,"1940":2,"1948":1,"1950":1,"1953":1,"1958":1,"1960":2,"1961":1,"1963":1,"1965":1,"1976":1,"1978":1,"1979":1,"1987":1,"1990":1,"1993":1,"1995":1,"1996":1,"1998":1,"2001":2,"2006":1,"2010":1,"2011":1,"2013":1,"2018":1,"2024":1,"2025":1,"2031":1,"2034":1,"2037":2,"2039":1,"2042":2,"2043":1,"2048":1,"2049":1,"2055":2,"2057":2,"2065":1,"2067":1,"2073":1,"2075":1,"2079":1,"2080":1,"2081":4,"2097":1,"2100":2,"2101":2,"2104":1,"2110":1,"2111":1,"2114":1,"2131":1,"2138":2,"2143":1,"2155":1,"2156":1,"2157":1,"2159":1,"2161":1,"2168":1,"2172":1,"2174":1,"2177":2,"2178":1,"2180":1,"2187":1,"2202":1,"2208":2,"2220":7,"2230":1,"2232":2,"2238":1,"2240":1,"2244":1}}],["acc",{"3":{"1455":1,"1463":1,"1467":1,"1470":1,"1471":1,"1472":2,"1473":1,"1474":1,"1475":1}}],["accrete",{"3":{"1475":1}}],["accreted",{"3":{"1436":1,"1792":1}}],["accrue",{"3":{"1396":1}}],["accrues",{"3":{"800":1}}],["accruing",{"3":{"692":1}}],["according",{"3":{"1089":1,"1282":1,"1300":1,"1311":1,"1324":1}}],["accordingly",{"3":{"298":2,"511":1,"512":1,"744":1,"892":1,"967":1,"1304":1,"1359":2,"1446":1,"1496":3,"2037":1}}],["accomplish",{"3":{"1626":1}}],["accompany",{"3":{"1323":1}}],["accompanying",{"3":{"372":1,"1417":1}}],["accommodates",{"3":{"1631":1}}],["accommodate",{"3":{"1237":1,"1436":2}}],["accommodations",{"3":{"1300":1,"1630":1,"1631":1}}],["accommodation",{"3":{"988":1}}],["accountlocked",{"3":{"1415":1}}],["accountname",{"3":{"1300":1}}],["accounterased",{"3":{"1396":2}}],["accountendpoint",{"3":{"1286":1}}],["accountentity",{"3":{"1199":3,"1207":1}}],["accounted",{"3":{"178":1,"402":1,"1333":1,"1339":1,"1436":1,"1443":1}}],["accountdeletiontests",{"3":{"1199":1,"1207":2}}],["accountdto",{"3":{"1199":3,"1207":1}}],["accounting",{"3":{"1101":1,"1270":2,"1396":1,"1436":5}}],["accounts",{"0":{"1409":1},"2":{"314":1},"3":{"314":1,"350":2,"352":1,"355":1,"487":1,"488":1,"489":1,"490":1,"1286":6,"1289":1,"1293":2,"1300":5,"1311":2,"1322":2,"1324":1,"1326":1,"1396":3,"1408":1,"1410":1,"1412":1,"1415":23,"1417":1,"1436":2,"1441":3,"1444":1,"1456":1,"1490":1,"1529":1,"1595":1,"1596":2,"1638":1,"1641":3,"1664":1,"1667":1,"1769":1,"1977":1,"1978":1,"1999":1}}],["accountability",{"3":{"40":1,"1237":1,"1567":1,"2063":1}}],["account",{"3":{"0":12,"126":1,"160":1,"174":1,"175":1,"176":1,"230":1,"243":1,"279":2,"280":2,"282":1,"283":1,"284":7,"286":1,"310":2,"348":4,"349":1,"350":11,"351":3,"352":5,"353":11,"403":2,"433":1,"435":1,"438":1,"441":1,"458":4,"459":8,"460":2,"465":1,"493":1,"497":4,"498":1,"499":4,"500":1,"524":1,"527":2,"528":1,"530":3,"602":1,"618":1,"649":1,"663":1,"665":7,"666":6,"667":1,"669":2,"670":2,"690":1,"724":1,"725":1,"727":1,"734":1,"749":1,"790":1,"792":2,"853":3,"854":4,"855":1,"857":1,"904":2,"905":5,"906":1,"908":2,"946":2,"948":1,"950":1,"1059":2,"1116":3,"1119":1,"1212":1,"1237":2,"1264":1,"1270":1,"1271":1,"1279":1,"1283":1,"1284":2,"1286":20,"1287":1,"1289":3,"1290":3,"1291":2,"1293":4,"1294":3,"1295":2,"1300":105,"1307":1,"1309":2,"1311":24,"1322":6,"1323":76,"1324":35,"1332":1,"1339":1,"1350":1,"1355":2,"1359":9,"1382":1,"1395":3,"1396":17,"1402":6,"1403":1,"1405":5,"1406":12,"1407":1,"1408":3,"1409":5,"1410":3,"1411":2,"1412":1,"1414":5,"1415":142,"1417":7,"1432":1,"1436":22,"1437":1,"1438":5,"1455":1,"1467":16,"1470":2,"1473":1,"1478":1,"1481":2,"1484":1,"1488":2,"1496":1,"1596":4,"1597":1,"1600":1,"1608":1,"1621":2,"1626":1,"1627":3,"1631":1,"1632":9,"1634":2,"1639":2,"1641":24,"1667":5,"1672":1,"1675":1,"1746":1,"1747":4,"1755":1,"1764":3,"1765":7,"1767":1,"1768":2,"1771":1,"1777":2,"1821":1,"1825":1,"1900":1,"1902":1,"1940":1,"1945":1,"1970":3,"1973":3,"1975":1,"1977":10,"1978":6,"1979":5,"1980":1,"1982":3,"1983":1,"1984":1,"1998":2,"1999":2,"2000":1,"2001":3,"2002":4,"2010":1,"2036":2,"2038":1,"2063":2,"2066":5,"2067":1,"2127":2,"2196":1,"2197":2,"2198":2,"2203":1,"2220":1,"2232":1}}],["accumulation",{"3":{"1020":1,"1300":2,"1395":2,"1415":1,"1427":1,"1685":1}}],["accumulating",{"3":{"861":1,"1270":1,"1271":2,"1286":1,"1300":1,"1323":5,"1383":1,"1396":2,"1423":1,"1427":1,"1436":3,"2190":1}}],["accumulators",{"3":{"1359":2}}],["accumulator",{"3":{"658":1,"1234":1,"1237":2,"1300":1,"1324":1}}],["accumulated",{"3":{"657":1,"826":1,"1259":1,"1286":2,"1301":1,"1310":1,"1324":4,"1395":10,"1396":1,"1433":1,"1436":2,"1473":1,"1552":1}}],["accumulate",{"3":{"657":1,"707":1,"717":1,"750":1,"757":1,"846":1,"1093":1,"1237":1,"1247":1,"1256":1,"1271":1,"1286":2,"1300":1,"1301":1,"1316":1,"1323":1,"1324":2,"1359":1,"1396":1,"1402":1,"1409":1,"1415":3,"1427":1,"1436":1,"1845":1}}],["accumulates",{"3":{"26":1,"186":1,"329":1,"1005":1,"1123":3,"1237":2,"1286":1,"1309":3,"1311":1,"1317":1,"1324":1,"1358":1,"1359":1,"1395":1,"1402":1,"1416":1,"1423":1,"1425":1,"1436":1,"1465":1,"1685":1,"1687":1,"1962":1,"1986":1,"2000":1}}],["accuracy",{"3":{"250":1,"1019":1,"1191":1,"1196":1,"1341":1,"1359":1,"1417":2,"1500":1}}],["accurately",{"3":{"190":1}}],["accurate",{"3":{"71":1,"245":1,"632":1,"768":1,"1324":2,"1417":1,"1427":2,"1500":1,"2098":1}}],["accidents",{"3":{"1949":1}}],["accidentally",{"3":{"386":1,"619":1,"1219":1,"1221":1,"1229":1,"1247":1,"1259":1,"1271":3,"1286":3,"1300":1,"1301":1,"1311":3,"1323":1,"1324":4,"1350":3,"1359":5,"1369":1,"1380":1,"1395":3,"1396":1,"1415":3,"1417":1,"1427":1,"1436":4,"1455":1,"1488":1,"1489":1,"1638":1,"1805":1,"1953":1,"2016":1,"2085":1}}],["accidental",{"3":{"68":1,"265":1,"768":1,"1286":3,"1309":1,"1310":1,"1350":2,"1359":2,"1369":1,"1395":1,"1396":3,"1415":1,"1436":3,"1454":1,"1474":1,"1489":1,"1526":1,"1552":1,"1556":1,"1569":1,"1638":1,"1686":1,"1709":1,"2033":1,"2188":1}}],["accident",{"3":{"0":1,"22":1,"25":1,"131":1,"236":1,"255":1,"558":1,"642":1,"700":1,"827":1,"832":1,"855":1,"907":1,"913":1,"1085":1,"1109":1,"1124":1,"1135":1,"1163":1,"1237":1,"1259":2,"1270":1,"1286":7,"1300":4,"1301":1,"1309":1,"1320":1,"1323":4,"1324":5,"1330":1,"1339":5,"1350":1,"1359":1,"1395":2,"1396":3,"1415":1,"1417":1,"1421":1,"1427":1,"1438":1,"1444":1,"1474":1,"1489":1,"1819":1,"1827":1,"1841":1,"1851":1,"1923":1,"1995":2,"2033":1,"2100":1,"2157":1,"2196":1}}],["accelerate",{"3":{"2225":1}}],["accelerated",{"3":{"461":1}}],["accent",{"3":{"1324":5,"1395":2,"1436":2,"1526":1,"1591":1,"1595":1,"1596":2,"1597":1}}],["accented",{"3":{"741":1,"1271":1,"1289":1,"1300":1,"1311":1,"1312":2,"1324":5,"1436":4,"1438":1,"1577":1,"1591":1,"1596":1}}],["accents",{"3":{"265":1,"1311":1,"1324":3,"1395":1}}],["accessing",{"3":{"1627":1}}],["accessiblelabel",{"3":{"1324":1}}],["accessible",{"3":{"617":1,"618":1,"619":1,"682":1,"954":1,"1026":1,"1324":7,"1415":1,"1432":1,"1436":9,"1438":1,"1455":1,"1488":1,"1491":1,"1607":1,"1620":1,"1622":1,"1630":1,"1631":2,"1632":3,"1640":1,"1641":1,"1643":1,"1644":1,"1741":2,"1750":2,"1756":1,"1758":1,"1773":2,"2057":1,"2228":1}}],["accessibilityinfomaxlength",{"3":{"1350":3,"1359":2,"1395":1}}],["accessibilityinfo",{"2":{"1631":1},"3":{"1350":4,"1359":11,"1369":3,"1380":2,"1395":5,"1496":1,"1630":2,"1631":2}}],["accessibilitytests",{"0":{"1608":1,"1742":1},"2":{"620":1,"621":1,"1596":1},"3":{"618":2,"620":1,"621":1,"1199":1,"1207":2,"1414":1,"1436":2,"1438":2,"1487":2,"1490":4,"1526":2,"1531":1,"1532":1,"1535":1,"1591":2,"1596":2,"1600":1,"1605":1,"1739":1}}],["accessibility",{"0":{"1433":1,"1558":1,"2057":1},"1":{"615":1,"616":1,"617":1,"618":1,"619":1,"620":1,"621":1,"622":1,"1605":1,"1606":1,"1607":1,"1608":1,"1609":1,"1610":1,"1642":1,"1643":1,"1644":1,"1645":1,"1739":1,"1740":1,"1741":1,"1742":1,"1743":1,"1744":1},"2":{"1417":1},"3":{"0":1,"276":1,"416":1,"615":1,"617":4,"618":1,"619":1,"620":2,"622":2,"860":1,"861":1,"862":1,"863":2,"955":1,"1170":1,"1202":1,"1203":5,"1207":8,"1213":1,"1214":1,"1216":1,"1324":14,"1350":4,"1359":14,"1369":3,"1380":5,"1395":7,"1396":6,"1402":1,"1414":1,"1415":3,"1417":35,"1428":2,"1433":3,"1436":26,"1438":3,"1453":1,"1461":1,"1488":4,"1490":1,"1491":1,"1492":1,"1493":1,"1496":1,"1499":1,"1524":1,"1526":3,"1530":1,"1531":2,"1532":1,"1533":2,"1535":1,"1558":1,"1561":1,"1565":1,"1574":1,"1577":4,"1585":1,"1586":1,"1588":1,"1589":3,"1591":3,"1595":2,"1596":2,"1597":1,"1598":2,"1602":1,"1605":1,"1629":1,"1630":2,"1631":2,"1637":2,"1642":2,"1669":3,"1671":2,"1673":1,"1674":1,"1739":1,"1796":1,"2075":1,"2107":1,"2213":1,"2221":1,"2232":1}}],["accessibilityviolationexception",{"2":{"618":1,"1428":1,"1433":1,"2057":1},"3":{"0":1,"618":2,"1199":2,"1207":2,"1428":1,"1433":2,"1436":6,"1488":3,"2057":1}}],["accessed",{"3":{"1436":1}}],["accesses",{"3":{"1324":1}}],["accesscoarselocation",{"3":{"1417":2}}],["accessservice",{"3":{"1359":5}}],["accessdenied",{"3":{"1300":1}}],["accessdeniedmessage",{"3":{"1271":3}}],["accesstokenkey",{"3":{"1417":1}}],["accesstokenname",{"3":{"1312":1}}],["accesstokenlifetime",{"3":{"1300":5}}],["accesstokencookiename",{"3":{"1300":1}}],["accesstokenexpiry",{"2":{"1968":1},"3":{"1300":4,"1968":1}}],["accesstokenexpirationminutes",{"2":{"499":1,"1982":1},"3":{"499":1,"1300":3,"1982":1}}],["accesstoken",{"3":{"1184":1,"1300":9,"1311":1,"1324":18,"1417":1,"1436":7,"1641":2,"1968":1}}],["accesstokenprovider",{"2":{"1183":1},"3":{"1183":1,"1324":1,"1436":3}}],["accessorcache",{"3":{"1247":2}}],["accessors",{"3":{"135":2,"547":2,"657":1,"660":1,"674":1,"741":1,"1247":2,"1259":2,"1270":1,"1278":1,"1286":1,"1300":1,"1311":1,"1324":3,"1350":4,"1359":2,"1395":4,"1417":2,"1431":1,"1436":8}}],["accessor",{"3":{"26":1,"298":1,"303":2,"304":1,"549":1,"699":1,"1163":1,"1227":1,"1228":1,"1229":3,"1231":1,"1234":1,"1237":1,"1242":1,"1247":3,"1253":1,"1271":1,"1277":1,"1286":11,"1300":2,"1311":8,"1324":2,"1339":3,"1348":1,"1350":4,"1359":24,"1395":5,"1396":1,"1417":4,"1436":3,"1526":1,"1535":1,"1586":1,"1591":1,"1611":1,"1810":1,"2138":2}}],["access",{"0":{"2074":1},"1":{"496":1,"497":1,"498":1,"499":1,"500":1,"501":1,"502":1,"503":1,"542":1,"543":1,"544":1,"545":1,"546":1,"547":1,"548":1,"549":1,"550":1,"551":1,"552":1,"878":1,"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1},"2":{"207":1,"227":1,"229":1,"426":3,"511":1,"1761":1},"3":{"0":9,"31":2,"39":1,"41":1,"165":1,"166":1,"167":1,"171":1,"185":1,"186":1,"206":2,"207":6,"227":1,"229":1,"320":1,"350":2,"359":1,"403":2,"426":3,"458":2,"459":1,"461":1,"496":1,"498":3,"499":6,"500":3,"501":1,"503":2,"505":1,"506":3,"507":9,"508":1,"511":1,"512":2,"514":1,"527":1,"542":1,"545":2,"549":1,"605":1,"632":1,"718":1,"724":2,"727":1,"776":1,"798":1,"878":1,"880":1,"881":2,"884":1,"903":1,"904":1,"905":4,"906":1,"910":2,"973":1,"1059":1,"1117":1,"1133":1,"1168":3,"1170":1,"1183":2,"1184":3,"1187":1,"1196":1,"1212":3,"1228":1,"1229":1,"1239":1,"1241":1,"1247":8,"1271":4,"1272":1,"1276":1,"1279":1,"1286":14,"1288":2,"1289":2,"1290":2,"1297":2,"1298":5,"1300":86,"1301":1,"1304":1,"1306":1,"1309":6,"1311":18,"1312":5,"1322":2,"1323":12,"1324":68,"1339":1,"1347":1,"1348":1,"1350":3,"1359":7,"1362":2,"1369":5,"1380":4,"1381":1,"1395":10,"1396":19,"1406":1,"1408":1,"1409":1,"1410":1,"1415":19,"1417":14,"1427":1,"1436":15,"1438":4,"1445":1,"1454":1,"1455":1,"1457":1,"1459":1,"1460":1,"1467":6,"1477":1,"1487":1,"1496":1,"1524":3,"1526":4,"1534":1,"1535":2,"1544":1,"1548":1,"1555":1,"1562":1,"1567":3,"1577":2,"1582":2,"1590":1,"1591":2,"1599":1,"1611":2,"1621":2,"1623":1,"1624":1,"1627":1,"1630":2,"1631":1,"1632":2,"1635":1,"1637":3,"1638":1,"1639":2,"1640":1,"1641":14,"1648":1,"1653":1,"1667":2,"1747":3,"1753":1,"1757":2,"1761":1,"1764":1,"1765":2,"1767":2,"1769":1,"1771":2,"1772":1,"1815":3,"1849":1,"1923":1,"1965":1,"1966":3,"1968":11,"1972":2,"1973":2,"1975":3,"1980":3,"1981":1,"1982":5,"1984":1,"1985":4,"1989":1,"1996":1,"2063":2,"2066":3,"2068":1,"2069":1,"2074":1,"2126":1,"2128":1,"2141":1,"2200":1,"2203":1,"2228":1,"2232":2,"2240":1}}],["acceptverbs",{"3":{"1436":2}}],["accepturl",{"3":{"1359":1}}],["acceptqueuesessions",{"3":{"1350":1}}],["acceptqueuesessioncount",{"3":{"1350":1}}],["acceptqueuecount",{"3":{"1350":1}}],["acceptqueue",{"3":{"1350":1,"1359":17,"1395":1}}],["acceptlanguage",{"3":{"1324":1}}],["accepttimesteponceasync",{"3":{"1300":2}}],["acceptattribute",{"3":{"1350":1,"1359":2}}],["acceptable",{"3":{"149":1,"435":1,"507":1,"530":1,"791":1,"990":1,"1116":1,"1229":1,"1237":1,"1286":1,"1300":3,"1301":1,"1309":1,"1324":4,"1359":3,"1396":2,"1436":2,"1455":1,"1467":1,"1630":1,"1638":3,"1639":1,"1898":1,"1947":1,"1971":1,"2166":1}}],["acceptances",{"3":{"1359":1}}],["acceptance",{"1":{"787":1,"788":1,"789":1,"790":1,"791":1,"792":1,"793":1,"794":1,"795":1},"3":{"0":2,"68":1,"87":1,"305":1,"390":1,"454":1,"562":1,"621":1,"787":1,"790":3,"791":1,"792":2,"794":1,"857":3,"946":1,"976":1,"1020":1,"1212":1,"1300":2,"1359":6,"1369":1,"1376":1,"1380":1,"1402":1,"1477":1,"1526":1,"1566":2,"1585":1,"1599":1,"1630":2,"1631":1,"1709":1,"2127":1,"2166":2,"2168":1,"2169":2,"2190":1}}],["accepting",{"3":{"0":1,"291":1,"690":1,"767":1,"1247":2,"1267":1,"1270":3,"1300":2,"1301":1,"1311":3,"1324":4,"1350":2,"1359":4,"1395":2,"1396":3,"1400":1,"1413":1,"1427":1,"1436":2,"1455":1,"1953":1,"1957":1,"2033":1,"2038":1,"2125":1}}],["acceptsdirectandinheritedandoptedoutdeclarations",{"3":{"1436":2}}],["accepts",{"3":{"0":3,"68":1,"72":1,"81":1,"93":1,"136":1,"170":1,"198":1,"237":1,"303":1,"312":1,"335":1,"341":1,"350":1,"374":1,"402":1,"427":1,"443":1,"469":1,"535":1,"536":1,"540":1,"541":1,"620":1,"674":1,"690":1,"740":1,"741":1,"761":1,"764":1,"766":2,"769":1,"773":1,"785":1,"863":1,"975":1,"1017":1,"1033":1,"1044":1,"1100":1,"1116":1,"1133":1,"1135":1,"1176":1,"1184":2,"1229":1,"1237":1,"1242":1,"1247":3,"1264":1,"1270":2,"1271":2,"1286":14,"1297":1,"1300":7,"1309":5,"1310":1,"1311":7,"1312":1,"1323":3,"1324":10,"1327":1,"1339":3,"1350":3,"1359":10,"1369":1,"1384":2,"1395":16,"1396":6,"1402":3,"1414":1,"1415":5,"1416":1,"1417":3,"1427":1,"1430":1,"1436":32,"1438":2,"1441":1,"1455":1,"1467":2,"1492":1,"1526":2,"1531":1,"1577":1,"1591":2,"1596":2,"1600":1,"1631":1,"1640":1,"1653":1,"1685":1,"1765":1,"1843":1,"1912":1,"1950":1,"1958":1,"1962":1,"1982":1,"2033":1,"2036":1,"2037":1,"2044":1,"2126":2,"2162":1,"2192":1}}],["accept",{"2":{"742":1,"745":1,"1633":1},"3":{"0":3,"98":1,"103":1,"135":1,"265":4,"293":1,"309":1,"370":2,"371":3,"372":1,"375":1,"404":1,"440":1,"517":1,"520":1,"618":1,"733":1,"740":4,"742":2,"743":1,"745":1,"773":1,"775":1,"790":1,"826":1,"881":1,"1018":1,"1059":1,"1160":1,"1212":2,"1231":1,"1247":1,"1270":2,"1271":1,"1286":7,"1298":1,"1300":10,"1306":1,"1309":1,"1311":6,"1323":3,"1324":7,"1339":1,"1343":1,"1344":1,"1347":2,"1348":1,"1350":12,"1351":1,"1358":1,"1359":11,"1366":1,"1369":2,"1380":2,"1396":3,"1402":1,"1415":1,"1417":3,"1436":16,"1438":1,"1444":1,"1453":1,"1454":1,"1455":2,"1459":1,"1472":1,"1474":1,"1492":1,"1534":1,"1577":1,"1589":1,"1591":1,"1600":1,"1630":2,"1631":4,"1632":1,"1633":1,"1635":2,"1640":1,"1666":1,"1765":1,"1912":1,"1924":1,"1937":1,"1954":1,"1965":2,"1968":1,"2083":1,"2086":2,"2126":1,"2128":1,"2167":1,"2169":1,"2232":1}}],["acceptedsessions",{"3":{"1350":1}}],["acceptedsessioncount",{"3":{"1350":1}}],["acceptedcount",{"3":{"1350":1}}],["accepted",{"0":{"139":1,"1534":1,"1585":1,"1599":1},"2":{"1099":1,"1103":1,"1367":1,"1633":1,"2190":1},"3":{"0":10,"1":1,"3":1,"9":1,"15":1,"17":1,"27":1,"29":1,"37":1,"45":1,"51":1,"57":1,"66":1,"76":1,"80":1,"84":1,"88":1,"90":1,"107":1,"115":1,"130":2,"135":5,"136":1,"139":3,"145":1,"155":1,"164":1,"173":1,"177":1,"178":1,"184":1,"193":1,"205":1,"212":1,"223":1,"230":1,"233":1,"241":1,"245":2,"248":1,"249":1,"263":1,"267":2,"271":1,"279":2,"283":1,"286":1,"289":1,"295":1,"298":1,"308":1,"316":1,"328":1,"339":1,"344":1,"348":1,"357":1,"368":1,"370":2,"371":2,"372":1,"373":1,"378":1,"386":2,"390":1,"395":1,"411":1,"415":3,"418":1,"422":1,"432":1,"438":1,"441":1,"446":1,"457":1,"461":1,"468":1,"480":1,"497":1,"505":1,"516":1,"520":1,"526":1,"530":1,"534":1,"543":2,"544":1,"545":1,"547":2,"554":1,"555":1,"562":1,"566":1,"570":1,"581":1,"585":1,"589":1,"597":1,"607":1,"616":1,"620":1,"624":1,"631":1,"638":1,"647":1,"651":1,"655":1,"663":1,"672":1,"674":1,"680":1,"688":1,"690":1,"696":1,"705":1,"713":1,"723":1,"731":1,"733":1,"739":1,"747":1,"748":1,"751":3,"755":1,"756":1,"758":1,"764":1,"766":2,"772":1,"775":1,"776":1,"780":1,"788":1,"790":2,"797":2,"808":2,"815":1,"817":1,"825":1,"827":2,"836":1,"844":1,"848":1,"852":1,"856":1,"859":1,"867":1,"879":1,"887":1,"895":1,"903":1,"907":1,"912":1,"920":1,"929":1,"933":1,"937":1,"944":1,"952":1,"962":1,"970":1,"978":1,"986":1,"994":1,"996":1,"1002":1,"1010":1,"1012":1,"1016":1,"1024":1,"1028":1,"1032":1,"1036":1,"1040":1,"1048":1,"1052":1,"1057":1,"1065":1,"1073":1,"1081":1,"1089":1,"1098":1,"1099":1,"1102":1,"1103":2,"1106":1,"1108":1,"1114":1,"1116":1,"1118":2,"1122":1,"1126":1,"1131":1,"1139":2,"1149":1,"1159":1,"1166":1,"1173":1,"1182":1,"1212":4,"1247":2,"1270":1,"1285":2,"1286":3,"1293":1,"1300":19,"1301":6,"1309":2,"1311":5,"1322":1,"1323":9,"1324":10,"1328":1,"1339":7,"1344":2,"1347":1,"1350":12,"1351":1,"1352":1,"1353":2,"1357":1,"1359":74,"1367":1,"1369":2,"1375":1,"1376":1,"1380":7,"1390":1,"1395":10,"1396":9,"1399":1,"1402":4,"1415":13,"1417":4,"1430":2,"1436":15,"1438":2,"1453":1,"1455":1,"1459":1,"1474":2,"1476":1,"1477":1,"1483":1,"1496":1,"1515":1,"1521":1,"1524":2,"1526":6,"1530":1,"1531":4,"1532":3,"1534":8,"1554":1,"1572":1,"1575":3,"1576":1,"1577":3,"1581":3,"1582":1,"1583":2,"1584":2,"1585":6,"1586":1,"1589":3,"1591":10,"1595":3,"1596":6,"1597":4,"1598":1,"1599":3,"1600":1,"1630":3,"1631":10,"1632":5,"1633":2,"1635":2,"1638":3,"1640":4,"1641":3,"1741":2,"1764":1,"1765":1,"1767":2,"1768":1,"1815":2,"2001":2,"2032":1,"2038":1,"2122":2,"2126":1,"2127":1,"2158":1,"2166":2,"2190":1,"2191":1,"2198":1,"2232":48}}],["aca",{"0":{"1334":1},"3":{"0":3,"91":3,"92":2,"93":2,"95":1,"96":2,"98":2,"99":1,"100":2,"102":3,"293":1,"757":2,"827":1,"829":1,"913":2,"914":2,"1312":1,"1325":1,"1329":1,"1331":1,"1334":1,"1339":8,"1379":1,"1441":2,"1443":5,"1446":2,"1450":2,"1467":16,"1476":1,"1534":1,"1577":1,"1707":1}}],["alquimia",{"3":{"2220":1}}],["aloud",{"3":{"1395":1,"1417":7}}],["along",{"0":{"2234":1},"3":{"58":1,"81":1,"165":1,"186":1,"573":1,"616":1,"657":1,"697":1,"700":1,"711":1,"744":2,"778":1,"798":1,"826":1,"981":1,"991":1,"1175":1,"1178":1,"1217":1,"1251":1,"1259":3,"1271":2,"1280":1,"1286":5,"1293":1,"1297":1,"1300":2,"1301":2,"1304":1,"1312":2,"1323":1,"1324":2,"1343":1,"1350":7,"1359":7,"1395":4,"1396":8,"1402":4,"1404":1,"1409":1,"1415":2,"1436":4,"1441":1,"1443":1,"1455":1,"1465":1,"1467":2,"1486":1,"1492":1,"1805":1,"1843":1,"1891":1,"1938":1,"1977":1,"1983":1,"1999":1,"2019":2,"2071":1,"2096":1,"2166":1,"2183":1}}],["alongside",{"3":{"0":4,"31":1,"71":1,"78":1,"91":1,"95":1,"173":1,"195":1,"219":1,"225":1,"272":1,"312":1,"319":1,"341":1,"350":1,"363":1,"406":1,"418":1,"449":1,"470":1,"499":1,"534":1,"545":1,"549":1,"563":1,"572":1,"599":1,"609":2,"611":1,"617":1,"642":1,"643":1,"647":1,"657":1,"665":1,"696":1,"728":1,"785":1,"790":2,"804":1,"832":1,"892":1,"905":1,"926":1,"932":1,"960":1,"982":1,"1027":1,"1042":1,"1059":1,"1069":1,"1075":1,"1104":1,"1116":1,"1232":1,"1237":2,"1247":2,"1259":4,"1262":1,"1270":2,"1271":2,"1286":19,"1288":1,"1290":1,"1300":20,"1301":6,"1309":3,"1311":13,"1312":2,"1316":1,"1323":10,"1324":17,"1332":1,"1333":1,"1336":1,"1339":9,"1342":1,"1347":1,"1350":10,"1353":1,"1359":25,"1366":1,"1369":6,"1374":3,"1380":5,"1395":23,"1396":10,"1398":1,"1400":1,"1402":2,"1403":1,"1409":1,"1415":9,"1416":1,"1417":10,"1427":3,"1428":1,"1432":1,"1434":1,"1436":18,"1438":5,"1441":1,"1442":2,"1443":2,"1445":1,"1455":1,"1467":8,"1475":1,"1487":1,"1488":2,"1489":1,"1496":3,"1526":1,"1531":1,"1537":1,"1546":1,"1570":1,"1577":2,"1585":1,"1627":1,"1630":3,"1631":5,"1639":1,"1640":1,"1655":1,"1663":1,"1686":1,"1699":1,"1715":1,"1737":1,"1755":1,"1761":1,"1764":1,"1769":1,"1770":1,"1771":1,"1815":1,"1927":1,"1929":1,"1933":1,"1944":1,"1946":1,"1956":1,"1981":1,"1982":1,"2000":1,"2006":1,"2024":1,"2100":1,"2126":1,"2133":1,"2150":1,"2166":1,"2232":1}}],["alone",{"3":{"0":12,"23":1,"24":2,"38":1,"54":1,"57":1,"63":1,"69":1,"73":1,"74":1,"81":1,"95":1,"135":2,"180":1,"186":2,"203":1,"207":1,"255":1,"265":1,"268":1,"285":1,"312":1,"314":1,"318":1,"324":1,"325":1,"341":1,"386":1,"390":1,"391":1,"399":1,"409":1,"426":1,"434":1,"449":1,"452":1,"465":1,"497":1,"523":1,"536":2,"539":2,"547":1,"549":3,"589":1,"593":1,"601":1,"607":1,"616":1,"624":1,"635":1,"638":1,"655":1,"674":1,"675":1,"707":1,"733":1,"758":1,"766":1,"767":1,"789":1,"794":1,"799":1,"806":1,"826":1,"827":1,"828":1,"830":1,"849":1,"854":1,"883":1,"889":1,"891":1,"897":1,"905":2,"906":2,"907":1,"926":1,"938":1,"939":1,"972":1,"988":1,"994":1,"1018":1,"1026":2,"1059":2,"1061":1,"1065":1,"1067":1,"1074":1,"1083":1,"1091":3,"1094":1,"1116":1,"1124":1,"1127":1,"1128":1,"1134":1,"1212":1,"1225":1,"1229":4,"1237":6,"1242":1,"1247":1,"1259":3,"1263":1,"1270":5,"1271":1,"1281":2,"1286":28,"1290":3,"1300":10,"1301":6,"1309":5,"1310":1,"1311":11,"1312":1,"1320":1,"1322":1,"1323":13,"1324":18,"1332":1,"1339":12,"1349":1,"1350":9,"1359":28,"1362":1,"1369":2,"1377":1,"1379":1,"1380":2,"1395":4,"1396":15,"1402":6,"1406":1,"1415":6,"1416":1,"1417":7,"1424":1,"1427":4,"1433":1,"1436":34,"1438":5,"1447":2,"1455":1,"1458":1,"1467":4,"1470":1,"1474":1,"1477":1,"1481":1,"1492":2,"1496":3,"1507":1,"1526":1,"1548":1,"1558":1,"1577":3,"1591":2,"1638":1,"1641":1,"1651":1,"1670":1,"1685":1,"1688":1,"1727":1,"1730":1,"1742":1,"1749":1,"1765":1,"1773":1,"1786":1,"1794":1,"1805":1,"1815":1,"1832":1,"1849":1,"1871":2,"1876":1,"1933":3,"1936":1,"1969":1,"1982":2,"1985":3,"1989":1,"2001":1,"2008":1,"2038":1,"2044":1,"2058":1,"2064":1,"2141":1,"2160":1}}],["alarm",{"3":{"1417":1,"1467":1,"1476":2,"1525":1,"1526":1}}],["alarms",{"3":{"1417":1}}],["alarming",{"3":{"1324":1,"1436":1,"1968":1}}],["alan",{"3":{"1364":1,"1369":2}}],["alb",{"3":{"175":1}}],["alpine",{"3":{"1438":1}}],["alpha",{"3":{"1324":1,"1417":2,"1480":1}}],["alphanumeric",{"3":{"1271":1,"1300":1,"1324":2,"1436":2}}],["alphabetical",{"3":{"1436":1,"1639":1,"1640":1}}],["alphabetically",{"3":{"166":1,"1034":1,"1279":1,"1286":5,"1395":2,"1436":1,"1654":1}}],["alphabet",{"3":{"1059":1,"1286":2,"1293":1,"1300":1,"1436":1,"1631":1}}],["alpn",{"1":{"89":1,"90":1,"91":1,"92":1,"93":1,"94":1,"95":1,"96":1,"97":1,"98":1,"99":1,"100":1,"101":1,"102":1,"103":1,"104":1,"105":1},"3":{"0":1,"31":1,"53":1,"89":1,"91":2,"97":2,"98":8,"99":1,"100":1,"1067":1,"1326":1,"1337":1,"1339":5,"1379":1,"1430":1,"1441":1,"1444":1,"1447":1,"1467":1,"1656":1,"2009":1,"2025":3}}],["almost",{"0":{"1371":1,"2071":1},"3":{"58":1,"265":1,"331":1,"386":1,"396":1,"740":1,"805":1,"881":1,"965":1,"1006":2,"1132":1,"1217":1,"1259":1,"1261":1,"1287":1,"1310":1,"1311":1,"1323":2,"1324":3,"1339":1,"1340":1,"1370":1,"1371":1,"1379":1,"1380":3,"1396":2,"1403":1,"1415":2,"1423":1,"1436":3,"1447":1,"1453":1,"1455":1,"1465":1,"1467":1,"1491":1,"1527":1,"1537":1,"1578":1,"1592":1,"1682":1,"1686":1,"1788":1,"1797":1,"1800":1,"1802":1,"1810":1,"1820":1,"1854":2,"1900":2,"1909":1,"1913":1,"1951":2,"1987":2,"2000":1,"2029":1,"2039":1,"2050":1,"2061":1,"2070":1,"2129":1,"2134":1,"2154":2}}],["algebra",{"3":{"1359":2}}],["alg=rs256",{"3":{"1300":1}}],["alg",{"2":{"31":1,"1897":1,"1899":1},"3":{"0":1,"31":1,"1184":1,"1300":4,"1576":2,"1577":1,"1897":4,"1899":1}}],["algorithms",{"3":{"178":1,"1895":1,"1899":1}}],["algorithm",{"0":{"1881":1,"1897":1},"3":{"0":4,"31":2,"32":1,"173":1,"178":1,"279":1,"280":1,"309":1,"310":1,"311":2,"312":1,"360":1,"499":2,"503":1,"674":1,"945":1,"946":2,"947":2,"963":2,"1212":1,"1237":1,"1247":1,"1287":1,"1288":2,"1291":1,"1292":3,"1300":22,"1301":3,"1311":18,"1315":1,"1323":3,"1350":2,"1359":4,"1395":4,"1436":3,"1438":1,"1467":1,"1488":2,"1496":2,"1526":1,"1577":2,"1596":1,"1653":1,"1667":3,"1772":1,"1878":1,"1881":1,"1886":1,"1897":4,"1899":3,"1902":6,"1903":1,"1904":1,"1905":1,"1982":3,"1985":2,"2012":1,"2094":1,"2143":1}}],["altitudes",{"3":{"1832":1,"1833":1,"1834":1}}],["altitude",{"0":{"1832":1},"3":{"1832":1,"1833":1,"2098":1}}],["alt",{"2":{"1606":1,"1740":1},"3":{"1324":1,"1395":1,"1415":1,"1438":1,"1491":1,"1558":1,"1606":1,"1740":1}}],["alt=",{"3":{"1324":1,"1395":1}}],["altogether",{"3":{"740":1,"1247":1,"1311":1,"1359":2,"1373":1,"1478":1}}],["alters",{"3":{"1427":1,"1734":1}}],["altered",{"3":{"1286":1,"1324":1,"1391":1,"1436":1}}],["alter",{"3":{"620":1,"1286":1,"1427":1,"1477":1,"1639":1}}],["altercolumn",{"2":{"566":1},"3":{"566":1}}],["alternation",{"3":{"626":1,"1309":1}}],["alternating",{"3":{"625":1,"626":2,"1456":3,"1461":1,"1492":1,"1493":1,"1534":1,"1599":1}}],["alternatives",{"0":{"151":1,"286":1,"405":1,"785":1,"1144":1,"1154":1,"1178":1,"1187":1},"3":{"1":2,"255":1,"256":1,"989":1,"1035":1,"1043":1,"1051":1,"1117":1,"1133":1,"1324":1,"1436":2,"1559":1,"1729":1,"2067":1}}],["alternative",{"3":{"0":4,"1":1,"99":1,"137":1,"255":1,"283":1,"343":1,"401":1,"461":1,"468":1,"469":1,"470":1,"474":1,"541":1,"551":1,"557":1,"627":1,"635":1,"676":1,"683":1,"699":1,"733":1,"792":1,"797":1,"799":3,"803":1,"804":1,"805":1,"809":1,"815":2,"828":1,"829":1,"855":1,"856":1,"871":1,"889":1,"901":1,"907":1,"924":1,"930":1,"947":1,"965":1,"981":2,"989":1,"1000":1,"1051":1,"1090":1,"1139":1,"1140":1,"1149":1,"1212":1,"1229":2,"1237":2,"1247":3,"1259":1,"1270":2,"1271":1,"1286":9,"1300":6,"1301":1,"1309":1,"1311":2,"1323":7,"1324":5,"1339":5,"1359":10,"1369":2,"1380":1,"1395":5,"1396":8,"1417":4,"1436":7,"1441":1,"1455":1,"1477":1,"1524":1,"1526":1,"1531":1,"1534":1,"1545":1,"1559":1,"1577":1,"1638":1,"1752":1,"1754":1,"1763":1,"1774":2,"1810":1,"1812":1,"1815":1,"1911":1,"1951":1,"2024":1,"2075":1,"2150":1,"2166":1,"2169":1}}],["alternates",{"3":{"109":1,"1526":1}}],["alternate",{"3":{"109":1,"1220":1,"1229":1,"1286":1,"1436":2,"1438":1,"1492":1,"1632":13,"1641":5}}],["although",{"3":{"540":1,"1259":1,"1324":1,"1350":1,"1359":1,"1396":2,"1524":1,"1536":1,"1577":2,"1596":2}}],["alttext",{"3":{"0":1,"682":1,"1395":1,"1396":1}}],["alertkeyregex",{"3":{"1436":1}}],["alertable",{"3":{"1311":1,"1359":1}}],["alertemailaddress",{"2":{"1466":1,"1472":1,"1474":1,"2158":1},"3":{"609":1,"1455":1,"1466":1,"1467":2,"1472":1,"1474":1,"1600":1,"2158":1}}],["alerted",{"3":{"403":2,"612":1,"1017":1,"1018":1,"1212":1}}],["alerting",{"1":{"606":1,"607":1,"608":1,"609":1,"610":1,"611":1,"612":1,"613":1,"614":1},"3":{"0":2,"125":1,"606":1,"765":1,"770":1,"1212":1,"1259":1,"1324":1,"1436":1,"1467":4,"1474":1,"1476":1,"1482":1,"1550":1,"1575":1,"1577":2,"1583":1,"1586":1,"1706":1,"2046":1,"2158":1,"2220":1,"2232":1}}],["alertseverityregex",{"3":{"1436":1}}],["alerts",{"3":{"0":6,"395":1,"403":1,"409":1,"607":3,"608":2,"609":10,"610":1,"611":5,"614":1,"759":1,"770":1,"791":1,"899":1,"914":1,"971":1,"1259":1,"1323":1,"1396":2,"1431":1,"1436":11,"1455":3,"1456":1,"1457":1,"1458":1,"1462":1,"1467":8,"1469":2,"1472":1,"1474":3,"1476":8,"1482":1,"1483":1,"1525":1,"1526":3,"1550":1,"1553":1,"1568":1,"1577":1,"1582":2,"1583":1,"1591":3,"1670":2,"1676":1,"1678":1,"1679":1,"1706":1,"1709":1,"2046":1,"2158":5,"2160":2,"2232":1}}],["alert",{"0":{"1476":1,"2046":1},"1":{"606":1,"607":1,"608":1,"609":1,"610":1,"611":1,"612":1,"613":1,"614":1},"2":{"607":1,"1474":1},"3":{"0":10,"24":1,"109":1,"125":1,"224":1,"255":1,"395":1,"400":1,"403":1,"407":1,"409":1,"606":1,"607":7,"608":6,"609":26,"610":5,"611":5,"612":3,"613":2,"649":1,"759":1,"765":1,"821":1,"823":1,"896":1,"901":1,"914":4,"916":1,"918":1,"971":1,"976":1,"1016":3,"1018":3,"1019":4,"1020":2,"1090":1,"1093":1,"1096":1,"1102":1,"1199":2,"1207":1,"1212":1,"1256":1,"1259":1,"1270":1,"1286":1,"1311":3,"1323":5,"1324":6,"1359":1,"1389":1,"1395":3,"1396":4,"1402":3,"1431":2,"1433":1,"1436":54,"1442":1,"1443":3,"1447":1,"1455":1,"1462":1,"1463":1,"1465":2,"1466":3,"1467":32,"1469":2,"1470":1,"1471":1,"1472":3,"1474":11,"1476":23,"1482":3,"1483":3,"1484":1,"1488":4,"1489":2,"1493":1,"1496":2,"1523":1,"1524":7,"1526":10,"1530":1,"1531":4,"1532":1,"1533":1,"1535":2,"1547":1,"1577":4,"1589":3,"1591":7,"1595":1,"1596":5,"1597":1,"1598":1,"1600":2,"1607":1,"1627":1,"1641":1,"1643":1,"1645":1,"1670":1,"1671":1,"1678":1,"1709":3,"1741":1,"1843":1,"1909":1,"1911":1,"1919":1,"1946":1,"2043":1,"2046":8,"2049":1,"2079":1,"2156":3,"2158":11,"2232":2}}],["align",{"3":{"1526":1,"1544":1,"2035":1}}],["aligns",{"3":{"1286":1}}],["alignment",{"3":{"147":1,"1300":1,"1323":1,"1359":1,"1496":3,"1526":1,"1639":1}}],["aligned",{"3":{"32":1,"86":1,"642":1,"650":1,"1286":1,"1298":1,"1300":2,"1306":1,"1311":2,"1324":1,"1350":1,"1359":2,"1396":2,"1416":1,"1417":1,"1534":1,"1535":1,"1564":1,"1712":1}}],["aliased",{"3":{"186":1,"1052":1,"1054":1,"1286":1,"1311":1,"1323":1,"1359":2,"1396":1,"1398":1,"1415":4,"1416":3,"1436":1}}],["aliases",{"0":{"1939":1},"1":{"467":1,"468":1,"469":1,"470":1,"471":1,"472":1,"473":1,"474":1,"475":1,"476":1,"477":1,"478":1,"796":1,"797":1,"798":1,"799":1,"800":1,"801":1,"802":1,"803":1,"804":1,"805":1,"806":1,"1047":1,"1048":1,"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1},"3":{"0":11,"467":1,"468":8,"470":3,"471":1,"472":3,"473":1,"474":1,"475":3,"476":3,"477":3,"478":3,"548":1,"633":1,"656":1,"660":1,"661":2,"796":1,"797":2,"798":3,"799":4,"805":1,"1047":1,"1049":4,"1050":3,"1051":2,"1054":2,"1055":1,"1192":1,"1196":1,"1202":1,"1203":1,"1212":7,"1231":2,"1281":1,"1286":3,"1300":2,"1309":7,"1311":2,"1344":1,"1350":42,"1359":38,"1369":1,"1380":10,"1394":1,"1395":31,"1396":32,"1402":23,"1409":2,"1415":4,"1416":1,"1431":1,"1436":4,"1455":1,"1489":1,"1496":1,"1541":1,"1577":1,"1591":1,"1650":1,"1651":1,"1662":2,"1685":1,"1686":1,"1689":1,"1727":1,"1810":4,"1812":1,"1939":2,"1941":1,"2089":1,"2230":1,"2232":4}}],["aliasing",{"3":{"0":1,"844":1,"848":1,"1415":1,"1891":1}}],["alias",{"3":{"0":2,"295":1,"342":1,"468":7,"469":2,"470":7,"471":1,"472":5,"474":1,"475":3,"476":3,"477":3,"478":3,"547":1,"633":2,"656":1,"675":1,"676":1,"698":1,"701":1,"797":4,"798":2,"799":3,"800":1,"801":1,"802":1,"803":6,"804":4,"805":4,"905":1,"910":1,"1048":2,"1049":1,"1051":1,"1053":1,"1054":2,"1175":1,"1177":1,"1196":1,"1212":3,"1231":2,"1237":11,"1247":3,"1259":2,"1271":1,"1286":13,"1296":1,"1300":27,"1301":1,"1309":12,"1311":4,"1323":9,"1324":5,"1338":1,"1339":1,"1344":1,"1350":36,"1359":64,"1369":2,"1380":10,"1382":1,"1395":28,"1396":40,"1398":1,"1402":11,"1404":1,"1415":22,"1416":1,"1436":7,"1447":1,"1465":1,"1480":1,"1496":1,"1526":1,"1611":1,"1649":5,"1650":1,"1651":1,"1661":1,"1684":2,"1685":1,"1695":1,"1699":2,"1700":2,"1701":1,"1727":5,"1728":3,"1810":4,"1816":1,"1963":1,"2089":2,"2097":2,"2230":1,"2232":2}}],["alike",{"3":{"0":2,"201":1,"243":1,"460":1,"490":1,"491":1,"492":1,"640":1,"827":1,"972":1,"1050":1,"1237":1,"1247":2,"1259":1,"1271":1,"1281":1,"1286":4,"1294":1,"1300":2,"1307":1,"1324":1,"1329":1,"1339":2,"1344":1,"1350":1,"1380":1,"1395":2,"1396":1,"1402":1,"1415":1,"1436":4,"1447":1,"1456":1,"1475":1,"1577":1,"1631":1,"1637":1,"1662":1,"1666":1,"1761":1,"1922":1,"2135":1,"2156":1}}],["aliveresponse",{"3":{"1436":1}}],["alive",{"2":{"1337":1},"3":{"0":7,"94":1,"99":4,"100":1,"175":1,"233":1,"235":1,"238":1,"239":1,"399":1,"402":2,"404":1,"572":1,"698":1,"757":1,"761":1,"827":6,"829":3,"830":1,"832":1,"833":1,"848":1,"849":1,"913":1,"1066":1,"1068":1,"1144":1,"1186":1,"1276":1,"1286":2,"1300":3,"1301":3,"1311":1,"1312":2,"1324":5,"1328":5,"1329":1,"1330":1,"1332":5,"1333":2,"1337":3,"1338":1,"1339":36,"1343":1,"1359":1,"1395":1,"1396":1,"1415":1,"1416":3,"1417":3,"1430":3,"1436":4,"1438":1,"1441":6,"1443":4,"1445":1,"1446":2,"1447":4,"1450":2,"1455":1,"1467":8,"1488":2,"1490":1,"1550":1,"1591":1,"1668":1,"1669":1,"1670":2,"1677":3,"1719":1,"1720":1,"1727":1,"1891":1,"2000":1,"2007":1,"2010":1,"2014":1,"2024":3,"2038":2,"2055":1,"2056":1,"2159":1}}],["alsoanalyzesqueryspecifications",{"3":{"1436":2}}],["alsoliftwhen",{"3":{"1326":2,"1339":3,"1441":1,"1444":4}}],["also",{"0":{"576":1,"1247":1},"3":{"0":24,"5":1,"19":1,"21":2,"24":2,"30":2,"46":1,"57":1,"62":1,"71":1,"72":1,"73":1,"74":1,"98":3,"100":1,"103":1,"109":1,"110":1,"122":2,"125":1,"126":1,"127":1,"136":1,"145":1,"147":1,"150":1,"152":2,"157":2,"161":1,"164":1,"166":1,"168":1,"175":2,"182":1,"188":1,"189":1,"193":1,"197":1,"200":1,"201":1,"212":2,"214":1,"225":2,"229":1,"234":2,"235":1,"241":1,"242":1,"248":2,"250":1,"255":2,"256":1,"258":1,"265":1,"279":1,"280":1,"283":1,"284":1,"295":1,"318":1,"320":1,"326":1,"328":1,"349":1,"350":1,"352":1,"364":1,"370":1,"374":2,"375":1,"390":1,"395":1,"397":1,"399":1,"402":1,"426":1,"433":1,"436":1,"446":1,"448":2,"452":1,"453":1,"458":2,"459":1,"462":1,"468":1,"475":1,"497":1,"499":2,"502":1,"505":1,"514":1,"526":1,"528":1,"529":1,"530":2,"531":1,"536":1,"538":2,"539":1,"540":1,"545":3,"547":2,"549":1,"550":2,"551":2,"552":1,"554":1,"556":1,"564":1,"570":1,"575":1,"576":1,"583":1,"598":1,"599":1,"607":2,"609":2,"612":1,"624":2,"626":1,"628":2,"633":2,"634":1,"649":1,"651":2,"652":1,"655":1,"657":3,"664":1,"665":1,"676":1,"690":1,"691":1,"692":1,"702":1,"708":1,"715":1,"717":1,"720":1,"732":1,"733":2,"735":1,"736":1,"743":1,"744":1,"759":1,"764":1,"765":1,"766":2,"774":1,"775":1,"781":1,"782":2,"790":1,"792":1,"798":1,"799":1,"809":1,"812":1,"817":1,"818":2,"821":2,"822":1,"825":1,"826":1,"827":1,"829":1,"833":3,"840":1,"846":1,"848":1,"854":1,"857":1,"863":2,"864":1,"868":2,"871":1,"877":1,"891":1,"907":1,"909":1,"913":1,"917":2,"920":1,"922":1,"926":1,"927":1,"934":1,"938":1,"940":1,"946":1,"954":2,"960":2,"963":1,"967":1,"971":1,"988":1,"1004":3,"1011":1,"1018":2,"1020":1,"1021":1,"1029":1,"1043":1,"1045":1,"1066":1,"1068":1,"1074":1,"1078":2,"1086":1,"1089":1,"1093":2,"1095":1,"1115":1,"1118":1,"1124":3,"1128":1,"1132":1,"1133":1,"1136":1,"1140":1,"1152":1,"1160":1,"1168":1,"1169":1,"1170":1,"1174":2,"1216":1,"1220":1,"1227":1,"1229":7,"1231":2,"1233":1,"1234":2,"1235":1,"1236":2,"1237":15,"1239":1,"1240":1,"1242":1,"1247":13,"1252":1,"1255":1,"1256":2,"1258":3,"1259":15,"1263":3,"1264":1,"1265":1,"1267":1,"1270":33,"1271":6,"1272":1,"1273":3,"1274":3,"1275":1,"1276":1,"1277":2,"1278":5,"1279":2,"1285":1,"1286":62,"1287":1,"1289":2,"1294":1,"1300":51,"1301":11,"1304":1,"1305":1,"1307":2,"1309":19,"1310":4,"1311":44,"1312":8,"1313":1,"1315":2,"1316":3,"1317":2,"1318":1,"1320":1,"1322":2,"1323":53,"1324":82,"1326":1,"1328":1,"1333":2,"1336":1,"1337":2,"1339":40,"1342":4,"1343":2,"1344":1,"1345":1,"1346":1,"1347":4,"1349":2,"1350":42,"1352":1,"1355":4,"1359":110,"1362":2,"1365":1,"1366":1,"1368":1,"1369":8,"1371":1,"1373":2,"1375":1,"1376":3,"1378":1,"1379":1,"1380":16,"1387":1,"1391":1,"1395":56,"1396":65,"1397":1,"1398":1,"1399":2,"1400":1,"1401":1,"1402":24,"1405":1,"1408":1,"1411":2,"1415":33,"1416":9,"1417":26,"1418":1,"1422":1,"1424":1,"1425":1,"1427":5,"1429":1,"1431":2,"1434":3,"1435":1,"1436":85,"1438":7,"1441":3,"1443":6,"1444":1,"1446":2,"1447":1,"1449":2,"1453":1,"1454":1,"1455":5,"1456":1,"1457":3,"1458":1,"1459":1,"1460":3,"1462":1,"1463":1,"1465":3,"1466":2,"1467":27,"1472":2,"1474":3,"1476":2,"1477":1,"1480":1,"1481":1,"1486":1,"1487":2,"1488":1,"1489":3,"1490":4,"1492":4,"1496":12,"1499":5,"1508":1,"1509":1,"1512":1,"1526":4,"1531":1,"1532":1,"1534":1,"1536":1,"1554":1,"1577":5,"1582":1,"1583":3,"1585":1,"1591":2,"1596":2,"1597":2,"1599":1,"1611":1,"1626":1,"1631":2,"1632":2,"1639":1,"1640":1,"1641":5,"1646":1,"1651":3,"1653":3,"1656":1,"1668":1,"1671":1,"1673":1,"1677":1,"1680":1,"1684":2,"1687":1,"1692":1,"1695":1,"1714":1,"1720":1,"1724":1,"1726":1,"1727":3,"1729":3,"1731":1,"1749":1,"1750":1,"1765":2,"1795":1,"1799":1,"1810":2,"1815":1,"1821":2,"1822":1,"1829":1,"1832":1,"1837":1,"1841":1,"1842":1,"1843":1,"1845":1,"1864":1,"1881":2,"1882":1,"1883":1,"1884":1,"1885":1,"1886":1,"1890":1,"1893":2,"1897":2,"1899":1,"1902":1,"1908":1,"1910":1,"1915":2,"1920":1,"1922":1,"1927":1,"1933":1,"1935":1,"1944":1,"1946":1,"1948":2,"1953":1,"1962":1,"1963":1,"1964":1,"1970":1,"1971":1,"1972":1,"1975":1,"1977":1,"1987":1,"2000":1,"2006":2,"2008":2,"2013":2,"2017":1,"2024":1,"2029":1,"2032":1,"2034":1,"2037":1,"2042":2,"2043":1,"2045":1,"2053":1,"2055":1,"2056":2,"2061":1,"2063":1,"2066":1,"2089":1,"2096":1,"2098":1,"2106":1,"2107":1,"2109":1,"2113":1,"2121":1,"2131":1,"2135":1,"2137":1,"2138":1,"2142":2,"2143":1,"2144":1,"2156":1,"2165":1,"2166":2,"2167":1,"2168":3,"2181":1,"2185":1,"2189":1,"2193":1,"2202":1,"2204":1,"2214":1,"2216":1,"2221":1,"2236":1}}],["allcarrytypeconstraints",{"3":{"1436":1}}],["allmetrics",{"3":{"1465":1,"1467":1}}],["allmmcacommonpackages",{"3":{"1436":2}}],["allmarkedread",{"3":{"1324":1}}],["allbyvalue",{"3":{"1415":1}}],["allfailed",{"3":{"1359":1}}],["allknownstatuses",{"3":{"1350":1}}],["allerrors",{"3":{"1229":1}}],["allspecification",{"3":{"1199":2,"1207":1}}],["allocator",{"3":{"1438":1}}],["allocate",{"3":{"1222":1,"1229":1,"1286":2,"1323":2,"1324":1,"1339":1,"1359":4,"2166":1}}],["allocates",{"3":{"1220":1,"1221":1,"1237":2,"1246":1,"1247":5,"1259":2,"1270":2,"1279":1,"1286":1,"1300":2,"1301":1,"1310":1,"1311":3,"1328":1,"1339":1,"1350":1,"1359":10,"1380":1,"1395":3,"1639":1}}],["allocated",{"3":{"438":1,"1221":1,"1229":3,"1237":1,"1247":3,"1270":5,"1271":1,"1286":1,"1300":1,"1301":1,"1311":2,"1323":2,"1339":5,"1359":3,"1395":4,"1396":1,"1436":3,"1438":1,"1441":1,"1639":2,"2126":1}}],["allocationceilingsbytes",{"2":{"591":1},"3":{"591":1}}],["allocation",{"3":{"0":1,"442":1,"471":1,"591":3,"592":1,"593":1,"595":1,"1212":1,"1213":1,"1220":1,"1229":5,"1237":3,"1244":1,"1247":9,"1259":3,"1263":1,"1270":4,"1286":5,"1300":13,"1301":2,"1309":1,"1311":5,"1312":1,"1323":5,"1324":2,"1328":1,"1339":6,"1350":1,"1359":19,"1380":1,"1395":3,"1396":2,"1402":2,"1415":2,"1416":1,"1417":3,"1423":1,"1427":1,"1436":4,"1438":1,"1441":1,"1467":1,"1492":1,"1577":4,"1639":1,"1668":1,"1800":1,"1805":1,"1821":1,"1923":1,"1955":2,"1958":1,"2047":1,"2196":1}}],["allocations",{"3":{"0":2,"589":1,"591":1,"592":1,"593":1,"860":1,"1237":1,"1492":1,"2047":1,"2048":1,"2049":1,"2232":1}}],["allocating",{"3":{"0":1,"1219":1,"1286":2,"1300":1,"1311":1,"1323":1,"1350":1,"1359":2}}],["allowinsecure",{"3":{"1467":3}}],["allowing",{"3":{"1286":1,"1309":1,"1323":1,"1336":1,"1339":2,"1359":1,"1369":1,"1396":1,"1415":1,"1417":1,"1455":1,"1467":1,"1776":1}}],["allowreadingfromstring",{"3":{"1359":1}}],["allowresponsebuffering",{"2":{"838":1},"3":{"838":1,"1339":1}}],["allowunscopedexport",{"3":{"1300":1,"1311":3,"1372":1,"1380":24,"1496":2}}],["allowwriteablefields",{"3":{"1247":7}}],["allowmultiple",{"3":{"1237":3,"1300":2,"1310":3,"1323":3,"1324":2}}],["allowmissingownerattribute",{"2":{"1297":1},"3":{"318":1,"1199":3,"1203":1,"1207":2,"1297":4,"1300":9,"1496":2,"1994":2}}],["allowmissingowner",{"2":{"322":1,"326":1,"1996":1,"1997":1},"3":{"318":4,"322":1,"324":1,"326":1,"1300":1,"1667":1,"1994":1,"1995":1,"1996":1,"1997":1}}],["allowazureservices",{"3":{"1467":1,"1477":1,"1524":1,"1526":2,"1534":1,"1591":1}}],["allowautoredirect",{"3":{"1324":1}}],["allowall",{"3":{"774":2}}],["allowanyorigin",{"2":{"775":1},"3":{"774":1,"775":1,"1339":1}}],["allowanymethod",{"2":{"774":1},"3":{"774":2,"1339":1}}],["allowanyheader",{"2":{"774":1},"3":{"774":2,"1339":1}}],["allowanonymousattribute",{"3":{"1436":3}}],["allowanonymous",{"2":{"387":1,"388":1,"453":1,"454":1,"1062":1,"1376":1,"1532":1,"1589":1,"1632":1,"1647":1,"1650":1,"1689":1,"1698":1,"1703":1,"1721":1,"1758":1,"1761":1,"1898":1,"1923":1,"1963":1},"3":{"0":2,"189":3,"243":1,"387":1,"388":1,"448":2,"453":1,"454":1,"790":1,"854":1,"1062":1,"1116":1,"1212":1,"1294":1,"1300":7,"1307":1,"1309":1,"1311":15,"1323":1,"1324":5,"1339":2,"1350":1,"1359":15,"1371":1,"1373":3,"1375":1,"1376":1,"1379":1,"1380":24,"1396":4,"1408":1,"1415":5,"1416":1,"1436":26,"1438":1,"1489":1,"1526":1,"1532":1,"1534":1,"1589":1,"1591":1,"1596":1,"1627":1,"1631":2,"1632":3,"1641":4,"1647":1,"1650":1,"1651":1,"1689":1,"1698":3,"1703":1,"1721":1,"1747":3,"1748":9,"1749":1,"1758":1,"1761":1,"1898":3,"1922":3,"1923":1,"1963":1,"2131":1}}],["allowances",{"3":{"1436":1}}],["allowance",{"3":{"0":2,"135":2,"136":1,"139":3,"216":1,"219":1,"1067":1,"1126":1,"1263":1,"1298":1,"1311":5,"1324":3,"1337":1,"1339":1,"1416":1,"1430":1,"1436":15,"1438":1,"1449":1,"1465":2,"1488":1,"1492":1,"1675":1,"2075":1,"2150":1,"2152":1}}],["allowcredentials",{"2":{"774":1,"775":1},"3":{"774":2,"775":1,"1311":1,"1339":2}}],["allowcachelookup",{"2":{"740":1},"3":{"740":1,"1311":1}}],["allowcachestorage",{"2":{"740":1},"3":{"740":1,"1311":1,"1380":2}}],["allowblobpublicaccess",{"3":{"665":1}}],["allowdistributed",{"2":{"178":1},"3":{"178":1,"1311":6,"2000":1}}],["allowtools",{"2":{"1419":1,"2173":1},"3":{"0":1,"1089":1,"1091":2,"1093":1,"1419":1,"1421":1,"1422":2,"1427":7,"1526":1,"1577":1,"2173":1,"2174":1,"2179":1}}],["allowsharedkeyaccess",{"3":{"1467":1}}],["allowspecificorigins",{"2":{"774":1},"3":{"774":1}}],["allowsclientkey",{"2":{"333":1},"3":{"333":1,"1241":1,"1247":3}}],["allows",{"3":{"0":1,"11":1,"110":1,"126":1,"360":1,"412":1,"530":1,"626":1,"742":1,"774":2,"775":1,"776":1,"853":1,"926":1,"1026":1,"1029":1,"1091":1,"1150":1,"1196":1,"1239":1,"1263":1,"1270":1,"1271":1,"1286":2,"1300":2,"1311":3,"1324":5,"1339":5,"1348":1,"1350":1,"1359":3,"1369":2,"1380":3,"1409":1,"1415":1,"1423":1,"1424":2,"1427":2,"1436":4,"1447":1,"1457":1,"1458":1,"1467":2,"1517":1,"1591":1,"1630":1,"1640":1,"1678":1,"1687":1,"1749":1,"1765":1,"2142":1}}],["allowlisting",{"3":{"1436":4}}],["allowlistedcollaborator",{"3":{"1436":3}}],["allowlistedcommand",{"3":{"1436":2}}],["allowlistedhandler",{"3":{"1436":2}}],["allowlistedtype",{"3":{"1436":7}}],["allowlistednamespace",{"3":{"1436":5}}],["allowlisted",{"3":{"547":1,"1311":1,"1416":1,"1417":1,"1436":14,"1816":1}}],["allowlists",{"3":{"27":2,"1161":1,"1311":4,"1436":9}}],["allowlist",{"3":{"0":4,"17":1,"27":1,"39":2,"41":1,"109":1,"265":4,"350":2,"418":2,"420":4,"421":1,"426":1,"552":1,"1061":1,"1063":1,"1161":4,"1162":2,"1164":1,"1311":8,"1324":4,"1405":1,"1415":4,"1416":3,"1417":2,"1436":103,"1438":1,"1526":1,"1534":1,"1577":1,"1591":1,"1599":1,"1600":1,"1653":1,"1976":1,"2083":2,"2085":1,"2086":1,"2202":2}}],["allow",{"1":{"771":1,"772":1,"773":1,"774":1,"775":1,"776":1,"777":1,"778":1},"3":{"0":3,"128":1,"175":1,"177":1,"189":3,"219":3,"265":1,"350":1,"355":1,"418":1,"423":1,"427":1,"428":2,"626":1,"749":1,"766":1,"771":1,"773":1,"774":4,"775":2,"776":4,"778":1,"1107":1,"1108":1,"1111":1,"1116":2,"1124":1,"1127":1,"1175":2,"1179":1,"1212":2,"1241":2,"1243":1,"1247":5,"1270":1,"1271":1,"1285":1,"1286":5,"1300":1,"1309":1,"1311":1,"1323":1,"1324":2,"1336":3,"1338":2,"1339":8,"1342":1,"1344":2,"1347":1,"1350":4,"1351":1,"1353":5,"1356":1,"1359":22,"1366":1,"1369":3,"1380":2,"1391":1,"1394":1,"1395":6,"1396":4,"1402":2,"1416":2,"1417":2,"1423":1,"1427":9,"1436":46,"1438":1,"1443":2,"1455":1,"1467":3,"1488":1,"1496":1,"1526":2,"1534":1,"1577":1,"1591":1,"1600":1,"1631":2,"1635":1,"1637":1,"1640":1,"1653":1,"1666":1,"1671":4,"1673":1,"1727":1,"1815":1,"1817":1,"1898":5,"1989":1,"2148":1,"2151":1,"2175":1,"2203":1,"2232":2}}],["allowedpurgetypes",{"3":{"1436":3}}],["allowedpublicimplementations",{"3":{"1436":2}}],["allowedhandlers",{"3":{"1436":4}}],["allowedharddeletetypes",{"2":{"1061":1},"3":{"39":1,"1061":1,"1436":4}}],["allowedthrowingtypes",{"3":{"1436":2}}],["allowedsharedcodes",{"3":{"1436":3}}],["allowedsavingtypes",{"3":{"1436":2}}],["allowedstaticmembers",{"2":{"884":1},"3":{"881":1,"884":1,"1436":4}}],["allowedunvalidatedcommands",{"3":{"1436":3}}],["allowedanonymousendpoints",{"3":{"1436":10}}],["allowedauthenticators",{"3":{"1417":1}}],["allowedfileextensions",{"3":{"1350":4,"1359":2}}],["allowedfiles",{"2":{"546":1,"1134":1,"1135":1,"1136":1},"3":{"0":3,"545":2,"546":1,"1133":1,"1134":1,"1135":2,"1136":1,"1436":12,"1489":1,"1815":2}}],["allowedcodeprefixes",{"3":{"1436":2}}],["allowedcascadeexempttypes",{"3":{"1436":2}}],["allowedchars",{"3":{"1309":2}}],["allowedclockreaders",{"2":{"1161":1},"3":{"1161":2,"1431":1,"1436":1}}],["allowedcyclenamespaces",{"3":{"0":1,"135":2,"1436":6}}],["allowedorigins",{"2":{"774":1},"3":{"0":2,"774":5,"775":1,"776":2,"778":1,"1311":1,"1336":1,"1339":3,"1443":1}}],["allowedreturnurlschemes",{"3":{"0":1,"350":1,"420":1,"428":1,"429":1,"1311":1,"1416":1,"1417":1,"1976":1}}],["allowed",{"0":{"1422":1,"2071":1},"3":{"0":2,"81":1,"135":1,"333":1,"384":1,"390":1,"540":1,"766":1,"773":1,"774":1,"791":1,"896":1,"897":1,"914":1,"926":1,"1067":1,"1091":1,"1168":1,"1178":1,"1184":2,"1191":1,"1200":1,"1229":1,"1237":2,"1239":1,"1246":1,"1257":1,"1259":1,"1270":2,"1285":1,"1286":11,"1297":1,"1300":6,"1311":5,"1323":3,"1324":4,"1339":5,"1350":7,"1359":10,"1380":3,"1395":3,"1396":8,"1398":2,"1399":2,"1402":7,"1415":2,"1417":3,"1418":1,"1422":1,"1427":2,"1431":1,"1436":17,"1438":1,"1467":1,"1489":1,"1498":1,"1561":1,"1640":1,"1721":1,"1749":1,"1755":1,"1765":1,"1767":3,"1813":1,"1815":2,"1817":1,"1821":1,"1851":1,"1892":1,"1899":1,"1932":1,"1934":1,"1960":1,"1992":2,"2043":2,"2071":1,"2137":1,"2147":1,"2150":1}}],["all",{"0":{"1339":1,"1395":1,"1790":1,"1938":1,"2118":1,"2178":1},"2":{"184":1,"659":1,"1373":1,"2083":1},"3":{"0":68,"12":2,"23":2,"24":2,"26":1,"31":2,"46":1,"51":1,"54":1,"58":1,"59":1,"62":2,"73":1,"76":1,"78":2,"93":3,"95":1,"99":2,"107":1,"109":6,"111":3,"115":1,"118":1,"122":3,"125":1,"126":1,"133":1,"135":2,"138":1,"140":1,"142":1,"146":1,"147":3,"148":2,"149":1,"150":2,"152":2,"156":1,"157":1,"164":1,"165":1,"166":2,"168":1,"170":4,"174":1,"175":2,"176":1,"177":1,"179":3,"180":5,"184":2,"186":9,"189":1,"195":3,"201":2,"203":1,"207":1,"214":1,"215":1,"217":1,"219":2,"224":1,"225":1,"234":1,"237":1,"241":3,"242":1,"243":1,"245":3,"251":1,"253":1,"254":3,"255":1,"256":4,"257":2,"258":1,"259":4,"260":1,"261":1,"263":1,"265":6,"267":1,"279":1,"280":1,"283":2,"284":1,"291":1,"314":1,"319":1,"328":1,"330":1,"332":1,"335":1,"341":2,"343":2,"346":1,"350":1,"359":2,"373":1,"390":1,"396":1,"397":4,"398":1,"399":1,"400":1,"401":2,"402":1,"409":1,"412":1,"413":1,"414":1,"415":1,"419":1,"420":1,"425":1,"426":1,"427":1,"440":1,"454":1,"459":1,"464":2,"465":1,"471":1,"472":1,"477":1,"486":2,"487":1,"488":4,"489":4,"490":4,"491":3,"492":4,"493":1,"494":2,"499":1,"505":1,"506":2,"507":1,"508":1,"509":1,"511":1,"512":1,"513":1,"514":1,"522":1,"523":1,"531":1,"536":1,"537":1,"538":2,"539":1,"540":1,"544":1,"545":3,"546":1,"547":1,"549":5,"551":1,"555":2,"556":1,"566":1,"572":8,"574":1,"576":1,"577":1,"578":3,"582":1,"583":1,"585":2,"590":1,"591":2,"592":1,"593":1,"599":4,"600":2,"602":2,"603":2,"607":1,"609":6,"611":5,"612":1,"616":1,"618":3,"619":1,"620":1,"624":1,"626":5,"627":1,"628":1,"633":2,"635":4,"638":1,"640":2,"643":1,"644":1,"645":1,"649":1,"655":1,"657":3,"659":1,"660":1,"665":5,"666":1,"672":3,"674":6,"682":2,"683":2,"690":5,"692":1,"693":1,"696":1,"697":1,"698":2,"699":3,"700":1,"702":1,"707":1,"709":1,"714":1,"715":1,"716":1,"717":2,"720":1,"725":1,"733":1,"739":1,"740":2,"743":4,"747":1,"748":1,"749":2,"751":3,"759":1,"764":1,"766":3,"767":1,"768":1,"773":1,"774":1,"776":1,"781":1,"782":2,"784":1,"790":3,"793":1,"794":1,"798":2,"799":4,"800":2,"803":1,"809":2,"810":3,"814":1,"820":1,"821":1,"825":1,"826":2,"827":6,"829":2,"832":2,"837":2,"838":3,"840":3,"842":1,"853":3,"856":1,"859":1,"860":2,"861":3,"863":2,"867":1,"868":1,"869":1,"870":3,"872":1,"875":1,"877":1,"881":5,"889":1,"891":1,"892":1,"896":1,"905":6,"906":3,"914":1,"915":1,"920":1,"923":2,"924":1,"926":1,"930":1,"932":1,"933":1,"935":1,"940":1,"941":1,"946":1,"954":1,"955":1,"956":1,"958":1,"959":2,"963":1,"964":4,"972":1,"979":2,"980":2,"986":1,"987":2,"988":1,"995":1,"996":1,"998":2,"1004":3,"1005":1,"1006":2,"1010":1,"1012":1,"1013":1,"1014":1,"1016":1,"1018":3,"1021":1,"1026":2,"1033":1,"1036":2,"1041":1,"1043":1,"1044":1,"1050":2,"1051":1,"1052":1,"1054":2,"1055":2,"1059":1,"1060":1,"1070":1,"1074":3,"1075":2,"1078":1,"1081":2,"1083":1,"1084":1,"1085":1,"1091":1,"1092":2,"1107":1,"1109":1,"1115":2,"1116":1,"1124":1,"1132":1,"1133":1,"1134":1,"1135":1,"1136":1,"1137":1,"1150":1,"1152":1,"1168":2,"1169":1,"1171":1,"1175":2,"1184":1,"1191":1,"1192":1,"1196":1,"1200":1,"1201":1,"1202":1,"1203":1,"1211":1,"1212":9,"1213":1,"1214":4,"1220":3,"1222":1,"1224":2,"1225":1,"1227":1,"1229":11,"1230":1,"1231":1,"1232":4,"1234":3,"1235":1,"1237":21,"1239":1,"1241":2,"1242":1,"1243":3,"1246":1,"1247":36,"1248":1,"1249":1,"1252":5,"1257":3,"1258":2,"1259":40,"1263":4,"1264":1,"1265":3,"1267":1,"1268":1,"1270":44,"1271":15,"1273":1,"1274":1,"1275":3,"1277":2,"1279":1,"1281":2,"1282":2,"1283":1,"1285":4,"1286":144,"1287":2,"1289":2,"1290":1,"1292":1,"1294":2,"1297":2,"1300":72,"1301":20,"1302":1,"1306":1,"1307":1,"1309":26,"1310":12,"1311":89,"1312":16,"1314":1,"1315":1,"1318":2,"1320":3,"1323":57,"1324":132,"1325":3,"1326":4,"1330":1,"1332":2,"1333":6,"1334":1,"1335":2,"1338":4,"1339":89,"1341":1,"1342":3,"1343":2,"1344":1,"1347":2,"1348":3,"1349":4,"1350":91,"1352":4,"1353":1,"1354":1,"1357":1,"1358":2,"1359":219,"1360":1,"1361":1,"1362":4,"1364":1,"1365":1,"1366":1,"1368":1,"1369":35,"1371":1,"1373":2,"1374":1,"1375":1,"1376":1,"1377":2,"1378":1,"1379":3,"1380":53,"1382":1,"1383":3,"1384":2,"1385":3,"1388":1,"1389":1,"1390":2,"1391":4,"1392":1,"1394":1,"1395":114,"1396":145,"1399":3,"1402":41,"1406":3,"1408":1,"1409":3,"1411":1,"1414":3,"1415":73,"1416":18,"1417":57,"1418":1,"1419":1,"1421":1,"1422":2,"1423":1,"1425":1,"1427":7,"1428":1,"1429":2,"1430":1,"1431":2,"1432":2,"1433":1,"1436":242,"1438":17,"1440":1,"1441":12,"1442":4,"1443":9,"1444":3,"1445":7,"1446":3,"1447":10,"1448":1,"1449":1,"1450":2,"1453":5,"1454":6,"1455":17,"1456":3,"1460":3,"1461":2,"1463":1,"1465":4,"1466":1,"1467":53,"1473":2,"1474":6,"1475":2,"1476":5,"1477":6,"1478":1,"1480":2,"1486":6,"1487":3,"1488":15,"1489":7,"1490":10,"1491":4,"1492":11,"1495":2,"1496":46,"1498":2,"1499":1,"1500":1,"1503":1,"1504":1,"1509":1,"1514":1,"1519":1,"1525":3,"1526":21,"1528":1,"1531":2,"1534":4,"1535":6,"1537":2,"1541":1,"1561":1,"1563":1,"1569":3,"1576":2,"1577":12,"1578":1,"1579":1,"1582":1,"1585":2,"1586":1,"1587":1,"1589":2,"1591":21,"1593":1,"1594":1,"1596":6,"1597":1,"1599":1,"1600":3,"1611":1,"1622":1,"1623":1,"1624":1,"1625":1,"1626":1,"1627":4,"1629":2,"1630":11,"1631":16,"1632":16,"1633":4,"1634":2,"1635":8,"1636":1,"1637":3,"1638":7,"1639":10,"1640":11,"1641":12,"1644":1,"1646":2,"1651":2,"1652":2,"1653":2,"1654":3,"1657":1,"1661":2,"1663":1,"1665":1,"1666":2,"1667":1,"1670":1,"1671":2,"1673":2,"1675":1,"1681":1,"1682":1,"1683":1,"1684":1,"1685":4,"1686":7,"1687":1,"1691":1,"1693":1,"1694":1,"1696":1,"1701":1,"1704":1,"1705":1,"1707":1,"1708":1,"1716":1,"1718":1,"1719":2,"1720":3,"1721":1,"1725":1,"1726":2,"1727":6,"1728":3,"1729":2,"1736":1,"1738":1,"1746":1,"1747":1,"1748":3,"1749":7,"1750":3,"1753":6,"1754":1,"1755":1,"1759":2,"1760":2,"1764":2,"1765":10,"1767":4,"1768":6,"1769":4,"1771":1,"1773":1,"1774":3,"1775":1,"1776":2,"1777":3,"1779":5,"1780":1,"1781":1,"1782":1,"1784":5,"1788":1,"1789":1,"1794":1,"1796":1,"1799":2,"1800":1,"1801":1,"1802":1,"1805":4,"1806":1,"1807":1,"1808":2,"1811":1,"1815":4,"1821":2,"1829":1,"1833":1,"1840":1,"1841":1,"1842":1,"1849":2,"1851":3,"1852":1,"1854":1,"1856":2,"1857":1,"1859":3,"1860":3,"1861":1,"1864":1,"1865":2,"1869":1,"1874":1,"1875":1,"1876":3,"1881":2,"1883":1,"1884":3,"1888":1,"1894":1,"1895":1,"1900":1,"1903":1,"1908":1,"1909":2,"1916":1,"1920":1,"1922":1,"1923":2,"1927":2,"1931":1,"1932":3,"1933":1,"1935":2,"1938":1,"1940":1,"1941":1,"1942":1,"1944":1,"1945":1,"1946":3,"1947":2,"1948":1,"1949":1,"1952":1,"1953":1,"1954":1,"1956":1,"1960":2,"1962":1,"1963":2,"1970":2,"1971":1,"1975":2,"1977":2,"1978":1,"1979":1,"1982":2,"1985":2,"1987":1,"1988":2,"1989":1,"1990":1,"1992":1,"1995":2,"1997":1,"1998":2,"1999":3,"2000":2,"2001":2,"2002":1,"2003":1,"2007":1,"2010":1,"2018":1,"2024":2,"2029":1,"2030":1,"2032":1,"2033":1,"2034":2,"2036":2,"2037":1,"2038":1,"2042":2,"2043":3,"2046":1,"2047":1,"2053":1,"2054":1,"2055":2,"2057":2,"2063":3,"2067":1,"2069":1,"2070":1,"2071":1,"2072":1,"2073":2,"2077":1,"2079":1,"2083":3,"2086":1,"2087":1,"2089":2,"2090":1,"2094":1,"2097":3,"2098":1,"2108":1,"2111":1,"2112":1,"2117":3,"2118":3,"2120":1,"2121":3,"2124":1,"2127":1,"2128":1,"2131":1,"2138":1,"2142":1,"2143":1,"2147":1,"2148":2,"2149":1,"2150":2,"2154":2,"2156":7,"2158":4,"2159":1,"2160":1,"2161":1,"2167":4,"2168":1,"2169":1,"2170":1,"2172":1,"2173":1,"2174":1,"2176":1,"2177":1,"2178":1,"2180":2,"2185":1,"2189":1,"2192":1,"2194":1,"2196":2,"2197":1,"2204":1,"2210":1,"2215":1,"2220":2,"2228":1,"2230":1,"2232":2,"2234":2,"2242":1}}],["alreadyhaveaccountlink",{"3":{"1436":1}}],["alreadyanswered",{"3":{"1402":1}}],["alreadyvisited",{"3":{"1396":7}}],["alreadyreviewed",{"3":{"1765":1,"1767":1}}],["alreadyrevoked",{"3":{"1300":1}}],["alreadyrecorded",{"3":{"1396":9}}],["alreadyunpublished",{"3":{"1350":1,"1359":2}}],["alreadylinked",{"3":{"1350":1,"1359":1}}],["alreadypublished",{"3":{"1343":1,"1344":1,"1350":1,"1359":2,"1380":1}}],["alreadyprocessedasync",{"2":{"195":1,"201":1,"1258":1},"3":{"195":2,"201":1,"1258":1,"1259":6}}],["alreadyclosed",{"3":{"1300":1,"1436":6}}],["alreadyclaimed",{"3":{"1300":1}}],["alreadycheckedin",{"3":{"690":1,"1396":17}}],["alreadysoftdeletefilteredentity",{"3":{"1199":3,"1207":1}}],["alreadydeleted",{"2":{"1219":1},"3":{"39":1,"1219":1,"1229":1,"1237":1,"1323":1,"1359":1,"1810":1}}],["already",{"3":{"0":30,"18":1,"24":4,"26":3,"27":2,"39":3,"46":1,"53":1,"54":1,"58":1,"60":3,"67":1,"69":1,"71":4,"72":3,"73":1,"81":2,"95":2,"99":1,"100":2,"102":1,"109":2,"110":1,"113":1,"115":1,"120":2,"122":3,"123":2,"127":1,"130":1,"131":1,"135":4,"136":1,"140":1,"142":1,"147":1,"148":1,"150":2,"158":1,"160":1,"164":1,"165":1,"166":1,"167":1,"175":1,"176":1,"178":3,"179":2,"180":1,"184":1,"186":1,"189":2,"194":2,"195":1,"198":1,"200":4,"201":2,"202":2,"207":1,"214":1,"225":3,"228":1,"230":1,"237":1,"246":1,"255":2,"256":1,"259":1,"260":1,"264":1,"265":3,"266":2,"273":1,"274":2,"292":1,"295":1,"299":1,"309":1,"310":1,"314":1,"317":1,"318":2,"340":2,"341":1,"342":1,"349":1,"350":3,"351":1,"352":1,"353":2,"358":2,"362":1,"369":1,"370":1,"382":1,"383":2,"390":1,"391":1,"392":1,"398":1,"400":2,"401":2,"402":1,"404":2,"405":3,"407":1,"408":1,"412":1,"413":1,"415":1,"420":1,"423":1,"424":1,"448":1,"458":2,"459":5,"461":2,"465":2,"471":1,"483":1,"499":1,"500":1,"508":1,"517":2,"519":1,"527":2,"528":1,"534":2,"535":1,"536":6,"537":4,"538":1,"539":2,"544":2,"549":2,"555":1,"556":1,"558":1,"563":1,"564":1,"565":1,"566":1,"573":1,"578":1,"583":1,"599":3,"600":1,"601":1,"603":1,"609":3,"618":1,"627":1,"633":2,"640":1,"641":1,"647":1,"648":1,"650":2,"657":1,"658":1,"665":3,"666":1,"673":1,"675":1,"681":1,"682":1,"683":1,"689":1,"691":2,"692":1,"697":1,"698":4,"702":3,"706":1,"707":4,"708":2,"710":1,"714":1,"715":1,"716":1,"718":1,"724":1,"725":3,"726":1,"733":2,"734":1,"735":1,"740":1,"741":4,"742":1,"743":1,"748":1,"750":1,"756":1,"757":1,"765":2,"767":1,"773":1,"789":1,"790":4,"792":1,"793":1,"794":1,"800":1,"801":1,"803":1,"809":1,"810":4,"811":2,"813":1,"818":1,"819":3,"820":2,"826":1,"827":7,"828":1,"829":1,"832":2,"833":1,"837":1,"838":1,"839":2,"846":1,"847":2,"848":1,"849":1,"853":2,"854":1,"856":1,"860":2,"862":1,"864":1,"865":1,"870":1,"872":1,"877":1,"882":2,"883":1,"884":1,"889":2,"890":2,"891":1,"896":2,"897":2,"905":8,"906":2,"912":1,"914":4,"921":2,"922":3,"926":3,"930":1,"931":1,"932":2,"933":1,"934":1,"940":1,"945":1,"947":1,"964":1,"965":1,"971":2,"973":1,"980":1,"981":1,"982":2,"988":5,"989":3,"995":1,"996":5,"997":2,"1003":1,"1004":1,"1011":2,"1013":1,"1017":1,"1018":2,"1019":1,"1025":1,"1033":1,"1034":1,"1035":1,"1040":1,"1041":1,"1042":2,"1043":3,"1044":2,"1049":1,"1055":1,"1058":3,"1059":5,"1060":2,"1061":1,"1066":2,"1067":2,"1068":2,"1073":1,"1074":1,"1075":2,"1077":1,"1082":2,"1083":1,"1084":1,"1085":1,"1090":1,"1093":1,"1115":2,"1116":4,"1117":2,"1123":1,"1125":3,"1135":1,"1140":1,"1142":2,"1143":1,"1145":2,"1150":1,"1152":1,"1154":1,"1155":1,"1157":1,"1160":1,"1162":1,"1178":1,"1183":1,"1185":1,"1187":1,"1190":1,"1196":1,"1212":1,"1218":1,"1220":2,"1227":1,"1228":1,"1229":11,"1231":2,"1233":1,"1237":10,"1239":2,"1241":1,"1242":2,"1247":21,"1249":1,"1250":1,"1253":1,"1255":1,"1256":2,"1257":1,"1258":2,"1259":34,"1262":2,"1263":3,"1265":3,"1267":2,"1270":40,"1271":4,"1274":3,"1275":3,"1276":1,"1278":2,"1281":7,"1286":105,"1288":1,"1289":1,"1290":2,"1292":1,"1300":52,"1301":10,"1304":1,"1307":1,"1309":19,"1310":4,"1311":58,"1312":9,"1315":2,"1316":1,"1318":1,"1320":1,"1322":2,"1323":58,"1324":89,"1328":1,"1330":2,"1335":1,"1337":2,"1338":1,"1339":42,"1343":2,"1345":1,"1346":1,"1347":1,"1350":23,"1353":2,"1354":1,"1355":3,"1357":1,"1358":2,"1359":122,"1362":1,"1363":1,"1365":1,"1366":1,"1369":13,"1371":2,"1372":2,"1376":2,"1378":2,"1380":21,"1385":1,"1389":1,"1390":1,"1395":49,"1396":92,"1399":1,"1400":2,"1401":1,"1402":33,"1406":2,"1407":3,"1408":5,"1409":2,"1410":1,"1411":1,"1413":2,"1414":1,"1415":66,"1416":9,"1417":37,"1420":1,"1421":1,"1422":1,"1423":1,"1425":1,"1426":1,"1427":13,"1429":2,"1436":64,"1438":6,"1441":2,"1442":3,"1443":2,"1445":2,"1446":1,"1447":3,"1448":1,"1453":2,"1454":3,"1455":10,"1456":2,"1459":4,"1465":2,"1467":17,"1472":2,"1473":1,"1474":1,"1475":1,"1477":1,"1479":1,"1486":1,"1488":2,"1492":2,"1496":14,"1507":1,"1531":1,"1577":2,"1596":2,"1627":1,"1631":5,"1632":8,"1638":1,"1640":1,"1641":8,"1646":1,"1648":1,"1649":2,"1651":1,"1653":1,"1656":1,"1661":1,"1664":1,"1672":1,"1684":2,"1685":5,"1686":12,"1687":1,"1689":1,"1692":1,"1697":1,"1700":1,"1701":5,"1702":2,"1703":1,"1708":1,"1719":1,"1720":1,"1723":1,"1724":2,"1728":1,"1729":2,"1739":1,"1747":2,"1748":3,"1749":4,"1755":1,"1764":2,"1765":7,"1766":1,"1767":2,"1768":1,"1770":1,"1776":1,"1781":1,"1788":1,"1790":1,"1800":1,"1804":1,"1805":2,"1809":1,"1821":2,"1823":1,"1824":1,"1832":1,"1840":3,"1843":1,"1844":1,"1847":1,"1849":1,"1851":3,"1870":1,"1873":2,"1874":1,"1879":1,"1881":1,"1883":1,"1887":2,"1890":1,"1891":1,"1892":1,"1897":1,"1908":2,"1909":1,"1910":1,"1916":1,"1918":1,"1920":1,"1922":1,"1923":3,"1924":1,"1933":2,"1941":1,"1944":1,"1946":1,"1948":3,"1950":2,"1953":1,"1954":1,"1962":1,"1967":1,"1968":2,"1970":1,"1976":1,"1977":5,"1978":2,"1979":5,"1982":2,"1989":1,"1994":1,"1995":1,"1997":2,"2000":3,"2001":1,"2012":1,"2016":1,"2017":1,"2019":1,"2024":1,"2028":1,"2030":1,"2032":3,"2046":1,"2063":1,"2064":1,"2066":3,"2067":1,"2069":1,"2076":1,"2081":1,"2083":3,"2087":1,"2092":1,"2095":2,"2096":1,"2097":1,"2098":1,"2101":1,"2117":1,"2118":2,"2131":1,"2132":1,"2134":1,"2135":1,"2136":1,"2139":1,"2140":1,"2141":1,"2143":1,"2150":1,"2154":1,"2156":2,"2160":1,"2161":1,"2163":3,"2164":1,"2165":4,"2167":2,"2168":5,"2169":3,"2177":1,"2180":1,"2182":1,"2184":1,"2185":1,"2187":2,"2190":1,"2191":1,"2192":1,"2193":2,"2194":3,"2198":1,"2199":1,"2215":1,"2232":1,"2234":1}}],["alwaysonline",{"3":{"1417":1}}],["alwaysonlineconnectivitystatusservice",{"2":{"2118":1},"3":{"1199":5,"1203":1,"1207":2,"1417":14,"2118":2}}],["alwaysbypassedprefixes",{"3":{"1339":1}}],["alwaysfailsvalidator",{"3":{"1199":2,"1207":1}}],["alwaysretryexecutionstrategy",{"3":{"1199":2,"1207":1}}],["always",{"0":{"1364":1},"1":{"928":1,"929":1,"930":1,"931":1,"932":1,"933":1,"934":1,"935":1},"2":{"1483":1},"3":{"0":9,"24":1,"53":1,"58":1,"78":1,"123":1,"140":1,"160":1,"175":2,"202":1,"221":1,"230":2,"231":1,"243":2,"245":1,"255":1,"256":1,"258":1,"267":1,"272":1,"280":1,"310":2,"334":1,"339":1,"344":1,"349":1,"353":1,"359":1,"363":1,"383":1,"388":1,"401":1,"404":1,"407":1,"413":1,"420":1,"440":1,"501":1,"511":1,"513":1,"518":1,"536":1,"549":1,"564":1,"565":1,"583":2,"585":1,"593":1,"599":1,"609":1,"620":1,"626":2,"657":1,"684":1,"690":2,"698":2,"749":2,"766":1,"784":1,"818":1,"819":1,"827":1,"848":1,"856":1,"863":1,"881":1,"905":1,"924":1,"926":2,"928":1,"946":1,"1033":1,"1034":2,"1042":1,"1046":1,"1061":1,"1067":1,"1099":1,"1100":1,"1108":1,"1117":1,"1123":1,"1175":1,"1176":1,"1211":1,"1212":3,"1214":1,"1215":1,"1221":1,"1227":1,"1229":3,"1237":5,"1240":1,"1247":4,"1253":1,"1255":1,"1259":9,"1261":1,"1263":1,"1269":1,"1270":8,"1271":3,"1276":1,"1278":1,"1279":1,"1280":1,"1286":43,"1291":1,"1300":20,"1301":10,"1302":1,"1309":11,"1310":4,"1311":35,"1312":5,"1315":1,"1320":2,"1323":22,"1324":21,"1332":1,"1333":1,"1337":1,"1339":19,"1348":1,"1349":1,"1350":16,"1359":27,"1364":1,"1366":1,"1369":3,"1380":3,"1383":1,"1384":1,"1386":1,"1387":1,"1388":1,"1391":2,"1395":27,"1396":23,"1402":11,"1406":1,"1408":1,"1415":15,"1416":2,"1417":21,"1423":1,"1427":4,"1430":1,"1431":2,"1433":1,"1435":1,"1436":30,"1438":5,"1440":1,"1441":1,"1444":1,"1447":2,"1454":1,"1455":4,"1456":3,"1460":2,"1466":1,"1467":2,"1470":1,"1475":3,"1478":1,"1483":1,"1486":3,"1488":3,"1489":1,"1490":1,"1492":2,"1496":1,"1503":1,"1526":3,"1531":1,"1534":1,"1577":1,"1607":2,"1627":1,"1630":1,"1631":3,"1639":1,"1640":1,"1641":2,"1649":1,"1652":1,"1653":2,"1670":1,"1684":1,"1686":1,"1697":1,"1700":1,"1701":1,"1730":1,"1737":1,"1741":2,"1761":1,"1768":1,"1769":1,"1781":1,"1805":1,"1815":1,"1820":1,"1825":1,"1843":2,"1849":1,"1865":1,"1867":1,"1871":1,"1876":2,"1877":2,"1878":1,"1881":1,"1889":1,"1900":1,"1902":2,"1922":2,"1927":1,"1933":1,"1938":1,"1940":1,"1944":1,"1947":1,"1949":1,"1963":1,"1974":1,"1976":1,"1984":1,"1991":1,"1993":1,"2000":1,"2003":1,"2006":1,"2010":1,"2019":1,"2024":1,"2044":1,"2046":1,"2049":1,"2057":1,"2064":1,"2091":1,"2094":1,"2118":2,"2119":1,"2123":2,"2126":1,"2129":1,"2134":1,"2142":1,"2156":1,"2183":1,"2184":1,"2202":1,"2232":2}}],["asleep",{"3":{"1479":1}}],["aswellformedopenapidescribingtheapisurface",{"3":{"1436":1}}],["asreadonly",{"2":{"1343":1},"3":{"1343":1,"1350":3,"1369":1,"1396":1,"1402":1}}],["asreadonlylist",{"3":{"1324":1}}],["astronomically",{"3":{"1902":1}}],["astracking",{"3":{"0":1,"549":1,"551":1,"926":1,"1240":1,"1244":1,"1247":3,"1270":6,"1286":16,"1310":3,"1311":1,"1350":1,"1359":48,"1369":3,"1396":10,"1402":5,"1415":5,"1577":1,"1591":1}}],["asterisk",{"3":{"1324":2,"1395":1}}],["asnotracking",{"3":{"1259":1,"1286":6,"1306":1,"1526":1,"1577":2,"1591":1}}],["ascends",{"3":{"1286":1}}],["ascending",{"3":{"330":1,"549":3,"1175":1,"1191":1,"1200":1,"1211":1,"1242":1,"1247":2,"1286":7,"1311":1,"1324":2,"1339":1,"1344":1,"1350":3,"1359":4,"1392":1,"1395":17,"1396":1,"1402":1,"1438":1,"1498":2,"1532":1,"1631":4,"1639":2,"1640":15,"1988":1}}],["ascii",{"3":{"1286":7,"1300":1,"1305":1,"1309":1,"1312":5,"1324":3,"1347":1,"1350":1,"1396":3,"1415":1,"1417":1,"1436":9,"1438":2,"1496":1,"1821":1}}],["asc",{"3":{"1242":1,"1247":4,"1286":1,"1395":5,"1396":1,"1597":1,"1598":1,"1640":1}}],["asitsfallback",{"3":{"1436":2}}],["asimplementedinterfaces",{"3":{"1395":4,"1415":1}}],["asia",{"3":{"1350":1}}],["aside",{"3":{"556":1,"1311":1,"1655":1}}],["asichatclient",{"2":{"1420":1,"2172":1},"3":{"0":1,"1091":2,"1420":2,"1427":5,"2172":1}}],["asker",{"3":{"1396":3,"1402":1}}],["asked",{"3":{"0":2,"24":1,"73":1,"108":1,"350":1,"365":1,"402":1,"529":1,"537":1,"549":1,"691":1,"714":1,"727":1,"733":1,"740":1,"924":1,"939":1,"1020":1,"1026":1,"1027":1,"1028":1,"1043":1,"1058":1,"1066":1,"1091":1,"1092":1,"1093":1,"1116":1,"1142":1,"1229":1,"1238":1,"1241":1,"1243":1,"1244":1,"1245":1,"1246":1,"1247":5,"1254":1,"1259":5,"1270":3,"1271":1,"1272":1,"1275":1,"1286":8,"1295":1,"1300":1,"1306":1,"1309":1,"1310":2,"1311":2,"1323":2,"1324":11,"1328":1,"1339":1,"1350":4,"1359":13,"1369":1,"1389":1,"1395":3,"1396":6,"1405":1,"1415":6,"1416":1,"1417":4,"1424":1,"1427":2,"1436":3,"1438":1,"1443":1,"1450":1,"1459":1,"1467":1,"1630":1,"1639":2,"1682":1,"1727":2,"1804":1,"1814":1,"1815":1,"1863":1,"1947":1,"1962":1,"1975":1,"1977":1,"2177":1,"2194":1}}],["ask",{"3":{"527":1,"536":1,"549":1,"649":1,"675":1,"684":1,"716":1,"725":2,"741":1,"798":1,"1019":1,"1237":2,"1244":1,"1247":3,"1259":1,"1270":2,"1273":2,"1277":1,"1278":1,"1283":1,"1286":10,"1300":7,"1301":2,"1309":3,"1311":1,"1323":2,"1324":9,"1328":1,"1339":4,"1359":6,"1369":1,"1373":1,"1380":2,"1391":1,"1395":5,"1396":9,"1403":1,"1415":4,"1416":2,"1417":4,"1427":1,"1436":9,"1455":1,"1632":1,"1686":2,"1697":1,"1768":1,"1794":1,"1796":1,"1802":1,"1815":1,"1840":1,"1844":1,"1916":1,"1950":1,"1973":1,"2056":1,"2067":1,"2098":2,"2110":1,"2116":1,"2124":1,"2170":1}}],["asking",{"3":{"24":1,"123":1,"254":1,"389":1,"508":1,"557":1,"690":1,"740":1,"741":1,"757":1,"758":1,"782":1,"819":1,"881":1,"890":1,"899":1,"932":1,"1024":1,"1229":1,"1232":1,"1237":2,"1240":1,"1247":2,"1259":2,"1267":1,"1276":1,"1277":1,"1286":15,"1300":1,"1301":1,"1323":2,"1324":1,"1350":1,"1359":15,"1380":1,"1386":1,"1395":6,"1402":1,"1415":1,"1417":2,"1422":1,"1436":1,"1438":1,"1455":1,"1534":1,"1585":1,"1631":1,"1639":1,"1664":1,"1749":1,"1765":1,"1920":1,"1945":1,"2032":1,"2048":1,"2075":1,"2169":1,"2174":1,"2195":1}}],["asksforafreshlinkandsayssowithoutconfirmingtheaccountexists",{"3":{"1436":1}}],["asks",{"3":{"0":1,"10":1,"24":1,"73":1,"81":1,"126":1,"200":1,"265":1,"350":2,"369":1,"407":1,"427":1,"428":1,"458":1,"459":1,"465":1,"536":1,"545":1,"546":1,"573":1,"577":2,"590":1,"592":1,"626":2,"650":1,"682":1,"690":1,"692":2,"698":1,"724":1,"725":1,"793":2,"860":1,"871":1,"996":1,"1003":1,"1027":1,"1042":1,"1067":1,"1090":1,"1091":2,"1092":1,"1116":1,"1142":1,"1161":1,"1228":2,"1234":1,"1237":1,"1243":2,"1245":1,"1247":3,"1259":2,"1264":1,"1270":4,"1271":1,"1274":1,"1281":1,"1286":26,"1300":6,"1301":1,"1304":1,"1307":1,"1309":2,"1310":2,"1311":6,"1312":1,"1313":1,"1318":2,"1322":1,"1323":6,"1324":23,"1332":1,"1333":1,"1337":1,"1339":5,"1349":1,"1350":3,"1352":1,"1357":1,"1359":23,"1369":2,"1380":3,"1395":11,"1396":12,"1402":6,"1408":1,"1411":1,"1416":5,"1417":6,"1418":1,"1431":1,"1432":1,"1433":1,"1435":1,"1436":16,"1438":2,"1455":1,"1475":1,"1476":1,"1492":2,"1496":1,"1524":1,"1526":1,"1686":2,"1727":1,"1748":1,"1765":2,"1769":1,"1776":1,"1788":1,"1795":1,"1810":1,"1815":1,"1823":1,"1844":1,"1851":1,"1865":1,"1866":1,"1868":1,"1882":1,"1896":1,"1933":1,"1972":1,"1977":1,"2047":1,"2063":1,"2078":2,"2083":1,"2098":1,"2109":1,"2116":1,"2117":1,"2123":2,"2141":2,"2149":1,"2154":1,"2162":2,"2163":2,"2167":2,"2169":1,"2171":1,"2174":1,"2177":1,"2179":1,"2182":1,"2191":1,"2194":1}}],["asymmetries",{"3":{"1324":1,"1359":1}}],["asymmetricsignatureprovider",{"3":{"1300":1}}],["asymmetrically",{"3":{"1212":1,"1323":1}}],["asymmetric",{"3":{"31":3,"32":1,"186":1,"401":1,"1034":1,"1288":1,"1300":6,"1324":1,"1395":1,"1396":1,"1436":1,"1799":1,"1893":1,"1894":1,"1895":1,"1897":1,"1898":1,"1899":3}}],["asymmetry",{"3":{"0":2,"71":1,"230":1,"260":1,"343":1,"392":1,"401":1,"457":1,"463":1,"464":1,"465":1,"609":1,"624":2,"656":1,"658":1,"766":1,"819":2,"820":1,"823":1,"876":1,"1229":2,"1234":1,"1267":1,"1270":2,"1276":1,"1286":6,"1309":1,"1311":1,"1323":4,"1332":1,"1339":1,"1350":5,"1359":14,"1395":3,"1396":4,"1402":1,"1415":1,"1417":1,"1436":1,"1438":1,"1441":1,"1443":1,"1486":1,"1492":1,"1543":1,"1639":1,"1876":1,"1920":1,"1938":1,"1964":1,"1989":1,"2009":1,"2032":1,"2085":1,"2232":1}}],["asyncapi",{"3":{"1524":2,"1526":1,"1530":1,"1531":2,"1532":1,"1546":1}}],["asyncmethods",{"3":{"1436":1}}],["asyncmethodsdeclaretrailingcancellationtoken",{"2":{"0":1,"135":1},"3":{"0":1,"135":1,"1436":2}}],["asyncretrypolicy",{"3":{"1324":2}}],["asyncduplexstreamingcall",{"3":{"1312":1}}],["asyncclientstreamingcall",{"3":{"1312":1}}],["asyncclockreadingfixture",{"3":{"1199":2,"1207":1,"1436":5}}],["asyncunarycall",{"3":{"1312":1}}],["asyncstatemachineattribute",{"3":{"1436":1}}],["asyncserverstreamingcall",{"3":{"1312":1}}],["asyncservicescope",{"2":{"1252":1},"3":{"1252":1}}],["asyncscopedreadcontroller",{"3":{"1199":2,"1207":1}}],["asynchrony",{"3":{"743":1,"1436":1}}],["asynchronously",{"3":{"248":1,"350":1,"1342":1,"1395":5,"1413":1,"1415":1,"1438":1,"1467":1,"1634":1,"1638":1,"1751":1,"1770":1,"1887":1,"2111":1}}],["asynchronous",{"0":{"1390":1},"3":{"52":1,"59":1,"109":1,"255":1,"447":1,"455":1,"727":1,"1220":2,"1229":1,"1253":1,"1259":8,"1286":7,"1311":3,"1312":1,"1323":4,"1324":4,"1349":1,"1350":1,"1359":4,"1372":1,"1380":5,"1390":1,"1395":6,"1415":1,"1436":6,"1765":1,"1805":1,"1923":1,"2016":1,"2020":1,"2130":1}}],["asynclocal",{"2":{"1246":1},"3":{"265":3,"1246":2,"1247":4,"1318":1,"1324":2,"1417":2}}],["async",{"3":{"27":1,"77":1,"135":1,"483":1,"743":2,"1012":1,"1161":7,"1192":1,"1220":1,"1229":6,"1247":2,"1259":1,"1270":3,"1284":1,"1286":9,"1300":4,"1301":4,"1309":2,"1310":1,"1311":3,"1312":1,"1323":2,"1324":15,"1339":1,"1350":3,"1359":15,"1372":1,"1380":2,"1395":4,"1396":7,"1402":7,"1415":8,"1416":1,"1417":9,"1431":2,"1436":33,"1489":1,"1491":1,"1496":3,"1524":2,"1526":4,"1531":1,"1544":1,"1546":1,"1549":3,"1556":1,"1558":1,"1577":3,"1591":2,"1805":1,"1887":1}}],["aspiration",{"3":{"1669":1,"2059":1}}],["aspirational",{"3":{"1237":1,"1270":1,"1300":1,"1324":1,"1325":1,"1395":1,"1436":4,"1489":1,"1558":1,"1788":1,"2037":1,"2060":1}}],["aspire010",{"3":{"1441":1,"1526":1}}],["aspire",{"0":{"99":1,"1442":1,"1443":1,"1444":1,"2022":1},"1":{"911":1,"912":1,"913":1,"914":1,"915":1,"916":1,"917":1,"918":1,"1325":1,"1326":1,"1327":1,"1328":1,"1329":1,"1330":1,"1331":1,"1332":1,"1333":1,"1334":1,"1335":1,"1336":1,"1337":1,"1338":1,"1339":1,"1439":1,"1440":1,"1441":1,"1442":1,"1443":1,"1444":1,"1445":1,"1446":1,"1447":1,"1448":1,"1449":1,"1450":1,"1451":1,"2003":1,"2004":1,"2005":1,"2006":1,"2007":1,"2008":1,"2009":1,"2010":1,"2011":1,"2012":1,"2013":1,"2014":1},"2":{"61":1,"67":1,"99":1,"100":1,"135":1,"141":1,"142":1,"214":2,"241":1,"247":1,"259":1,"638":1,"821":1,"825":1,"826":1,"899":1,"912":2,"913":1,"914":1,"916":1,"917":1,"939":1,"1004":1,"1034":1,"1035":1,"1036":3,"1067":2,"1068":4,"1069":2,"1211":2,"1213":1,"1325":4,"1326":1,"1328":1,"1329":1,"1337":1,"1339":1,"1430":3,"1437":1,"1439":1,"1445":1,"1449":1,"1460":1,"1486":1,"1487":1,"1488":1,"1496":1,"1503":1,"1504":1,"1656":1,"1660":2,"1677":1,"1781":2,"1790":1,"2004":3,"2008":1,"2014":2,"2022":1,"2028":1,"2030":1,"2038":1,"2055":1,"2111":2,"2149":1,"2151":1,"2153":1,"2160":1},"3":{"0":28,"18":1,"31":2,"47":1,"53":1,"61":3,"66":1,"67":1,"72":1,"91":1,"92":1,"93":2,"95":4,"98":2,"99":6,"100":1,"135":1,"141":1,"142":1,"150":1,"166":3,"171":1,"179":2,"180":1,"189":1,"214":3,"216":1,"217":1,"219":1,"220":1,"221":1,"233":1,"234":1,"235":14,"237":3,"238":1,"241":2,"243":2,"245":2,"247":2,"255":2,"256":1,"258":2,"259":5,"260":1,"261":1,"390":1,"392":1,"395":3,"397":9,"399":1,"400":2,"401":2,"402":3,"404":1,"406":1,"407":1,"408":2,"409":1,"599":1,"601":1,"603":1,"618":1,"619":1,"632":1,"633":2,"638":1,"640":3,"642":1,"665":6,"668":1,"669":2,"674":5,"774":2,"776":1,"792":1,"818":1,"819":5,"821":1,"822":3,"825":1,"826":4,"827":7,"829":3,"830":2,"833":2,"837":2,"838":1,"854":1,"862":1,"883":1,"884":1,"897":2,"899":1,"900":1,"911":1,"912":3,"913":12,"914":4,"915":1,"916":1,"917":2,"918":1,"932":1,"938":1,"939":3,"941":1,"980":8,"996":1,"1004":2,"1034":5,"1035":3,"1036":3,"1037":1,"1044":2,"1065":2,"1066":2,"1067":13,"1068":6,"1069":6,"1070":3,"1089":1,"1103":1,"1108":3,"1150":1,"1154":2,"1157":1,"1184":1,"1191":1,"1192":3,"1199":121,"1200":1,"1202":3,"1203":52,"1206":7,"1207":372,"1208":30,"1211":4,"1212":9,"1213":3,"1247":3,"1259":4,"1268":1,"1270":3,"1286":5,"1288":1,"1301":2,"1309":2,"1311":9,"1312":7,"1323":15,"1324":4,"1325":9,"1326":5,"1327":2,"1328":8,"1329":2,"1330":2,"1331":2,"1332":9,"1333":7,"1334":7,"1335":1,"1336":3,"1337":8,"1339":242,"1350":1,"1364":1,"1365":1,"1368":1,"1369":1,"1378":1,"1380":2,"1396":2,"1407":1,"1415":3,"1428":2,"1430":16,"1435":3,"1436":6,"1437":13,"1438":11,"1439":4,"1440":1,"1441":39,"1442":9,"1443":21,"1444":5,"1445":1,"1446":1,"1447":3,"1448":1,"1449":5,"1450":1,"1451":1,"1453":1,"1454":1,"1455":3,"1456":7,"1460":2,"1464":1,"1467":10,"1473":1,"1476":2,"1482":1,"1486":7,"1487":9,"1488":9,"1489":2,"1490":8,"1492":3,"1496":37,"1499":2,"1503":3,"1504":2,"1505":1,"1526":3,"1531":1,"1535":1,"1551":1,"1554":1,"1570":2,"1575":1,"1577":19,"1582":1,"1583":1,"1585":2,"1586":1,"1591":1,"1596":1,"1600":1,"1602":1,"1606":1,"1612":1,"1641":1,"1647":2,"1648":2,"1653":3,"1654":1,"1656":2,"1660":2,"1661":2,"1668":1,"1670":2,"1674":2,"1677":2,"1681":1,"1682":1,"1683":1,"1684":3,"1687":1,"1689":1,"1692":1,"1693":3,"1695":1,"1698":1,"1701":1,"1702":1,"1705":1,"1707":2,"1717":1,"1718":5,"1720":1,"1724":1,"1725":1,"1726":1,"1727":10,"1729":1,"1731":1,"1735":1,"1740":1,"1752":1,"1763":2,"1774":1,"1779":1,"1781":2,"1783":1,"1790":2,"1792":1,"1793":1,"1841":1,"1842":1,"1843":1,"1916":1,"1923":1,"1938":1,"2002":1,"2003":4,"2004":4,"2005":1,"2008":1,"2010":2,"2013":4,"2014":5,"2015":1,"2019":1,"2022":2,"2024":1,"2027":1,"2028":1,"2030":3,"2038":1,"2055":1,"2057":1,"2089":1,"2111":3,"2149":2,"2151":1,"2153":1,"2154":1,"2156":5,"2157":1,"2159":1,"2160":3,"2192":1,"2209":2,"2219":1,"2221":1,"2227":6,"2229":2,"2232":1,"2239":3,"2243":1}}],["aspect",{"3":{"881":1,"885":1,"1480":3,"1636":2}}],["aspnet",{"2":{"877":1,"1450":1},"3":{"868":1,"877":1,"1445":4,"1450":1}}],["aspnetcore",{"2":{"95":1,"99":1,"234":1,"401":1,"409":1,"633":1,"665":1,"1036":1,"1091":1,"1110":1,"1300":1,"1311":2,"1312":2,"1324":4,"1331":1,"1332":1,"1334":1,"1339":3,"1395":1,"1443":1,"2043":1,"2099":1},"3":{"95":2,"99":1,"234":1,"235":1,"397":2,"401":1,"409":1,"572":1,"633":1,"665":2,"776":1,"869":1,"1036":1,"1091":1,"1110":1,"1213":6,"1300":9,"1309":5,"1311":23,"1312":2,"1323":1,"1324":28,"1331":1,"1332":1,"1334":1,"1339":21,"1369":1,"1380":1,"1395":4,"1396":1,"1416":1,"1417":1,"1429":1,"1436":27,"1441":1,"1442":1,"1443":3,"1445":1,"1467":3,"1489":3,"1490":1,"1577":2,"1677":2,"2000":1,"2043":1,"2099":1}}],["asp",{"2":{"446":1,"447":1,"448":1,"454":1,"2129":1,"2131":1},"3":{"0":2,"109":3,"111":1,"174":1,"185":1,"207":1,"243":1,"265":2,"387":2,"395":2,"396":1,"397":5,"398":1,"405":1,"409":1,"411":1,"415":1,"446":1,"447":1,"448":4,"451":1,"454":1,"481":3,"482":1,"572":1,"664":1,"665":1,"748":1,"826":1,"831":1,"840":1,"853":1,"914":1,"916":1,"917":1,"1124":1,"1191":1,"1200":1,"1202":1,"1203":1,"1212":2,"1213":2,"1217":1,"1229":2,"1230":1,"1249":1,"1259":1,"1270":1,"1286":1,"1300":25,"1301":2,"1307":1,"1308":1,"1309":11,"1311":56,"1312":2,"1323":1,"1324":22,"1332":1,"1333":2,"1336":1,"1337":1,"1338":1,"1339":30,"1341":1,"1351":1,"1359":9,"1367":1,"1369":1,"1380":11,"1396":10,"1402":2,"1404":1,"1415":15,"1416":4,"1417":2,"1430":1,"1431":1,"1436":21,"1438":3,"1442":1,"1443":4,"1451":1,"1453":1,"1460":1,"1465":1,"1467":4,"1488":1,"1490":1,"1496":3,"1505":1,"1540":1,"1577":1,"1640":1,"1671":1,"1677":1,"1782":1,"1805":2,"1825":1,"1895":1,"1922":1,"1929":1,"1970":1,"1998":1,"2002":1,"2006":2,"2010":1,"2039":1,"2043":1,"2055":1,"2104":1,"2129":1,"2131":2,"2153":1,"2154":1,"2156":2,"2160":1,"2219":1,"2220":2,"2224":1,"2225":1,"2232":1}}],["assurant",{"3":{"2220":1}}],["assumeuniversal",{"3":{"1247":1,"1309":2,"1312":3,"1396":3}}],["assumes",{"3":{"320":1,"352":1,"405":1,"448":1,"735":1,"837":1,"1229":1,"1270":1,"1286":1,"1293":1,"1300":1,"1311":2,"1324":3,"1359":4,"1395":1,"1396":1,"1415":1,"1435":1,"1436":3,"1585":1,"1640":1,"1718":2,"1789":1,"1815":1,"1847":1,"1885":1,"1978":1,"2016":1,"2198":1}}],["assume",{"3":{"266":1,"389":1,"449":1,"709":1,"756":1,"827":1,"1247":1,"1270":2,"1286":3,"1300":1,"1312":1,"1323":2,"1324":3,"1359":2,"1369":1,"1396":3,"1402":1,"1415":2,"1436":1,"1626":1,"1737":1,"1881":1,"1901":1,"1905":1,"1909":1,"2015":1,"2125":1,"2133":2}}],["assumedefaultversionwhenunspecified",{"2":{"448":1,"449":1,"2131":1,"2133":1},"3":{"448":2,"449":1,"1311":1,"2131":1,"2133":1}}],["assumed",{"0":{"2034":1},"3":{"0":1,"69":1,"97":1,"265":1,"590":1,"813":1,"860":1,"871":1,"1229":1,"1247":1,"1279":1,"1286":1,"1311":1,"1318":1,"1323":1,"1324":2,"1339":2,"1359":2,"1395":2,"1416":1,"1436":7,"1438":2,"1492":2,"1549":1,"1560":1,"1577":1,"1854":1,"1863":1,"1864":1,"1878":1,"2037":1,"2038":1,"2203":1}}],["assuming",{"3":{"170":1,"603":1,"810":1,"1270":1,"1286":1,"1300":1,"1311":1,"1323":2,"1324":3,"1369":1,"1395":1,"1417":1,"1467":2,"1868":1}}],["assumption",{"3":{"97":1,"101":1,"254":1,"461":1,"481":1,"751":1,"831":1,"840":1,"972":1,"1134":1,"1286":3,"1324":1,"1359":3,"1396":3,"1415":1,"1436":2,"1467":1,"1585":1,"1692":1,"1844":1,"1863":1,"2027":1}}],["assumptions",{"3":{"55":1,"1402":1,"1717":1,"1718":2}}],["assplitquery",{"2":{"1864":1},"3":{"1243":1,"1244":1,"1247":3,"1278":1,"1286":8,"1310":1,"1436":1,"1815":1,"1864":1}}],["assinglequery",{"3":{"1286":1}}],["assistive",{"3":{"1395":1,"1396":1,"1417":6,"1526":1,"1532":1,"1558":1,"1750":1}}],["assistant",{"3":{"1091":1,"1424":1,"1459":2,"1460":1,"2220":1}}],["assisted",{"3":{"1012":1,"1351":1,"1570":1,"1625":1,"2213":2,"2218":1,"2219":1,"2225":1,"2243":1}}],["assignaction",{"3":{"1310":4,"1359":18,"1402":3}}],["assignableroles",{"3":{"1324":1,"1415":2,"1436":1}}],["assignable",{"3":{"707":1,"889":1,"1237":1,"1286":2,"1324":1,"1402":1,"1414":2,"1436":1}}],["assign",{"3":{"1236":1,"1237":3,"1286":4,"1310":3,"1359":10,"1395":1,"1472":1,"1630":1,"1632":2,"1689":1,"1746":2,"1748":2,"1768":1,"1868":2}}],["assignparentcategory",{"3":{"545":1,"1270":1}}],["assignparentcategoryhandler",{"2":{"543":1,"551":1},"3":{"543":1,"551":1}}],["assigning",{"3":{"265":3,"924":1,"1229":1,"1286":1,"1310":1,"1324":3,"1343":1,"1350":6,"1359":6,"1395":1,"1396":4,"1402":1,"1417":2,"1436":1}}],["assigns",{"3":{"263":1,"265":1,"303":1,"470":1,"551":1,"633":1,"690":1,"698":1,"700":1,"703":1,"1229":3,"1231":2,"1237":4,"1246":1,"1247":1,"1270":1,"1286":7,"1303":1,"1310":3,"1323":3,"1324":11,"1339":4,"1342":1,"1350":15,"1359":24,"1395":2,"1396":6,"1402":2,"1405":1,"1415":5,"1417":4,"1436":5,"1498":1,"1631":1,"1632":1,"1639":1,"1727":1,"1810":1,"1865":1,"2089":1}}],["assignees",{"3":{"224":1}}],["assignedproperties",{"3":{"1286":1}}],["assigned",{"2":{"1844":1},"3":{"0":1,"238":1,"545":3,"550":1,"599":3,"600":1,"657":2,"667":1,"830":1,"1018":1,"1196":1,"1200":1,"1212":1,"1231":1,"1237":5,"1259":1,"1270":3,"1277":1,"1278":1,"1286":13,"1300":4,"1309":3,"1310":1,"1311":1,"1324":2,"1339":2,"1342":3,"1349":2,"1350":29,"1352":1,"1355":1,"1359":36,"1364":1,"1369":1,"1380":1,"1395":23,"1396":7,"1402":17,"1415":2,"1417":1,"1438":1,"1459":1,"1467":2,"1472":1,"1488":1,"1496":1,"1498":2,"1526":1,"1624":1,"1627":1,"1630":2,"1631":2,"1632":4,"1635":1,"1637":1,"1638":1,"1639":8,"1727":1,"1755":1,"1776":1,"1844":1,"1929":1,"2232":1}}],["assignments",{"3":{"26":1,"599":1,"600":1,"923":1,"1286":3,"1300":1,"1339":1,"1350":3,"1359":16,"1395":3,"1402":1,"1467":3,"1472":2,"1632":2,"1636":2,"1844":2,"1955":2,"1956":1,"1959":1}}],["assignment",{"3":{"0":3,"265":1,"302":1,"424":1,"545":1,"549":1,"599":1,"600":1,"602":1,"665":5,"666":1,"668":2,"669":1,"692":1,"698":1,"714":1,"801":2,"804":1,"926":1,"1050":1,"1116":1,"1191":1,"1192":1,"1229":1,"1270":1,"1286":5,"1297":1,"1300":1,"1309":1,"1323":3,"1324":5,"1339":5,"1350":3,"1359":27,"1380":3,"1388":1,"1395":9,"1396":4,"1402":6,"1415":2,"1417":1,"1436":2,"1438":1,"1442":1,"1465":1,"1467":5,"1470":1,"1472":3,"1474":1,"1481":2,"1631":2,"1670":1,"1865":1,"1870":1,"1955":1,"1970":2,"1972":1,"2127":1,"2138":1}}],["associating",{"3":{"1359":1}}],["associations",{"3":{"1311":1,"1350":3,"1359":2,"1436":1,"1611":1,"1636":1,"1639":1}}],["association",{"1":{"417":1,"418":1,"419":1,"420":1,"421":1,"422":1,"423":1,"424":1,"425":1,"426":1,"427":1,"428":1,"429":1,"430":1},"3":{"0":5,"417":1,"418":4,"419":1,"420":2,"421":1,"425":1,"426":1,"428":1,"429":1,"1192":1,"1212":1,"1300":1,"1311":9,"1339":1,"1350":17,"1359":17,"1369":1,"1380":7,"1383":1,"1395":7,"1416":4,"1436":4,"1441":1,"1496":2,"1577":1,"1599":1,"1630":2,"1631":3,"1637":2,"1639":1,"1640":1,"1666":1,"2121":3,"2232":1}}],["associates",{"3":{"1328":1,"1339":2,"1359":1,"2220":1}}],["associate",{"3":{"1324":1,"1350":1,"1639":1,"2224":2}}],["associated",{"3":{"0":1,"359":1,"360":1,"365":1,"418":1,"420":1,"422":1,"1284":1,"1286":3,"1324":1,"1359":4,"1416":1,"1436":1,"1481":2,"1558":1,"1561":1,"1599":1,"1627":1,"1630":4,"1631":2,"1632":2,"1639":3,"1767":1,"2142":1,"2146":1}}],["asselfwithinterfaces",{"3":{"1311":2,"1323":1,"1359":3}}],["assessments",{"3":{"2234":1}}],["assessed",{"3":{"1300":1,"1536":1,"1585":2}}],["assesses",{"3":{"1216":1,"1218":2,"1229":14,"1237":37,"1247":38,"1259":36,"1270":68,"1271":17,"1286":182,"1300":147,"1301":22,"1309":66,"1310":6,"1311":87,"1312":9,"1313":1,"1323":116,"1324":198,"1339":20,"1350":54,"1352":2,"1353":2,"1354":1,"1357":1,"1359":390,"1360":1,"1369":38,"1380":32,"1381":1,"1394":1,"1395":156,"1396":192,"1402":86,"1415":98,"1416":12,"1417":140,"1427":18,"1436":217,"1438":2,"1440":1,"1441":5,"1442":1,"1443":4,"1445":1,"1449":1,"1453":2,"1454":2,"1455":5,"1456":1,"1457":2,"1458":1,"1460":1,"1463":1,"1464":1,"1465":4,"1467":9,"1472":2,"1473":2,"1474":3,"1475":3,"1476":2,"1477":1,"1478":2,"1479":1,"1480":1,"1481":1,"1486":3,"1487":1,"1488":4,"1489":6,"1490":1,"1492":5}}],["assess",{"3":{"1270":1,"1286":1,"1300":3,"1324":1,"1339":1,"1359":1,"1369":3,"1380":2,"1395":4,"1396":6,"1417":4,"1436":19,"1554":1}}],["assertmessagesuffix",{"3":{"1436":1}}],["assertnoviolations",{"3":{"1436":1}}],["assertnodependency",{"3":{"1436":3}}],["assertnoaccessibilityviolationsasync",{"2":{"618":1,"1433":1,"1488":1,"1490":1,"2057":1},"3":{"618":1,"1433":1,"1436":5,"1488":3,"1490":1,"1493":1,"2057":1}}],["assertdarkpaletteasync",{"3":{"1436":2}}],["assertdatasourceasync",{"2":{"1069":1},"3":{"1067":2,"1069":1,"1339":2}}],["assertdisabledstubs",{"3":{"1436":8,"1438":1}}],["assertprobepresentunderdefaultculture",{"3":{"1436":2}}],["assertproblemdetailsshapeasync",{"2":{"572":1,"2055":1},"3":{"572":1,"1436":3,"2055":1}}],["assertprotocontract",{"3":{"1436":2}}],["assertpipeline",{"3":{"1436":3}}],["assertcaseasync",{"3":{"1427":2}}],["asserth2casync",{"2":{"2056":1},"3":{"1067":2,"1069":1,"1339":3,"2056":1}}],["asserthealthyasync",{"2":{"2056":1},"3":{"1067":1,"1069":1,"1339":2,"2056":1}}],["assertjwksasync",{"2":{"2056":1},"3":{"1067":1,"1069":1,"1339":2,"2056":1}}],["assertreadyasync",{"2":{"2056":1},"3":{"1067":1,"2056":1}}],["assertaliveasync",{"2":{"2056":1},"3":{"1067":1,"2056":1}}],["assertable",{"3":{"860":1,"1109":1,"1300":1,"1309":1,"1311":1,"1339":1,"1436":6}}],["assertwithinbudget",{"2":{"861":1},"3":{"861":2,"1436":3}}],["asserted",{"3":{"0":6,"71":1,"73":1,"160":2,"252":1,"350":1,"352":1,"353":1,"371":1,"448":1,"455":1,"463":1,"546":1,"590":1,"591":1,"609":1,"640":1,"690":1,"707":1,"741":1,"757":3,"766":1,"838":2,"863":1,"876":1,"883":1,"941":1,"966":1,"972":1,"996":1,"1067":1,"1069":1,"1083":1,"1212":2,"1237":2,"1262":1,"1270":3,"1286":6,"1300":6,"1309":1,"1311":7,"1323":3,"1324":2,"1339":7,"1359":8,"1380":2,"1395":3,"1396":4,"1415":5,"1416":3,"1417":3,"1427":1,"1428":1,"1431":3,"1433":1,"1435":2,"1436":42,"1438":8,"1451":1,"1455":3,"1467":1,"1488":2,"1492":1,"1500":1,"1526":1,"1535":1,"1577":3,"1596":1,"1600":1,"1641":1,"1701":1,"1707":1,"1898":1,"1977":2,"2000":1,"2131":1,"2132":1,"2232":1}}],["asserting",{"3":{"0":2,"54":1,"70":1,"250":1,"265":1,"558":1,"573":1,"616":1,"618":1,"627":1,"633":1,"676":1,"691":1,"717":1,"797":1,"827":1,"838":1,"972":1,"1068":1,"1084":2,"1163":1,"1212":1,"1262":1,"1270":1,"1286":2,"1300":4,"1312":1,"1323":2,"1324":2,"1339":2,"1350":1,"1359":7,"1396":1,"1415":2,"1427":1,"1428":1,"1430":1,"1432":1,"1435":1,"1436":51,"1438":4,"1455":1,"1461":1,"1488":1,"1492":4,"1577":1,"1585":1,"1686":1,"2055":1,"2056":1,"2057":1,"2107":1,"2122":1,"2129":1,"2151":1,"2232":2}}],["assertions",{"3":{"0":3,"147":1,"237":1,"531":1,"572":1,"573":1,"574":1,"616":5,"618":2,"621":1,"640":1,"832":1,"861":2,"863":1,"963":1,"967":1,"972":1,"1018":1,"1050":1,"1067":2,"1093":1,"1212":1,"1213":1,"1229":1,"1234":1,"1259":1,"1286":1,"1311":1,"1324":1,"1339":3,"1427":1,"1428":1,"1433":1,"1436":48,"1437":1,"1438":3,"1449":2,"1453":1,"1461":1,"1488":2,"1526":4,"1531":1,"1534":1,"1586":1,"1591":1,"1618":1,"1651":1,"1668":1,"1671":2,"1731":1,"2001":1,"2042":1,"2055":1,"2056":1}}],["assertion",{"0":{"2197":1},"3":{"0":5,"157":1,"218":1,"277":1,"318":1,"348":1,"350":3,"368":1,"526":1,"530":1,"572":2,"590":1,"592":1,"611":1,"618":1,"619":1,"718":1,"861":1,"862":2,"863":1,"914":2,"972":3,"973":1,"974":1,"975":1,"1059":1,"1066":1,"1067":2,"1068":1,"1069":2,"1262":1,"1270":3,"1278":1,"1286":3,"1300":5,"1328":2,"1339":7,"1359":1,"1369":1,"1380":1,"1396":3,"1402":1,"1406":4,"1415":8,"1427":4,"1429":1,"1430":1,"1432":1,"1433":1,"1436":110,"1437":1,"1438":6,"1449":1,"1453":3,"1454":2,"1467":1,"1488":3,"1489":1,"1490":3,"1491":3,"1492":5,"1526":3,"1531":2,"1534":1,"1535":1,"1577":1,"1591":2,"1645":1,"1654":1,"1671":1,"1685":1,"1707":2,"1714":1,"1883":1,"1973":1,"1975":1,"1977":4,"1978":1,"1996":1,"2043":1,"2045":1,"2055":1,"2059":1,"2180":1,"2197":1,"2203":1}}],["assertsomethingwasmeasured",{"2":{"861":1,"863":1},"3":{"861":1,"863":1}}],["asserts",{"3":{"0":3,"70":1,"72":1,"73":1,"74":1,"78":1,"122":2,"127":1,"136":1,"175":2,"180":1,"189":1,"265":2,"302":1,"312":2,"350":2,"352":1,"448":1,"451":1,"459":1,"563":1,"571":1,"572":8,"574":1,"576":1,"577":1,"578":1,"583":3,"585":1,"609":1,"618":3,"633":2,"665":1,"725":2,"749":1,"776":1,"831":2,"861":3,"890":1,"946":2,"955":1,"962":1,"963":1,"972":1,"1018":3,"1020":1,"1034":2,"1037":1,"1067":4,"1068":1,"1091":1,"1212":1,"1214":1,"1236":1,"1237":4,"1247":2,"1270":2,"1271":2,"1286":7,"1300":3,"1301":2,"1309":1,"1311":3,"1312":1,"1323":3,"1324":7,"1326":1,"1339":15,"1359":15,"1365":1,"1369":1,"1380":4,"1395":1,"1396":7,"1402":3,"1405":1,"1408":1,"1415":6,"1416":2,"1417":6,"1427":3,"1430":1,"1431":1,"1433":1,"1435":3,"1436":325,"1438":16,"1444":1,"1449":2,"1451":1,"1455":1,"1463":1,"1467":2,"1476":1,"1484":1,"1487":1,"1488":8,"1489":3,"1490":1,"1491":2,"1492":2,"1524":1,"1526":10,"1531":2,"1577":1,"1591":5,"1671":1,"1672":1,"1707":2,"1731":1,"1817":1,"1825":1,"1826":1,"1842":1,"1889":1,"1902":1,"1929":1,"1930":1,"1977":1,"1978":1,"1979":1,"1993":1,"2000":1,"2001":1,"2013":1,"2031":1,"2036":1,"2041":1,"2043":2,"2045":3,"2055":5,"2057":2,"2063":1,"2075":1,"2079":1,"2098":1,"2104":2,"2131":1,"2133":1,"2138":1,"2178":2,"2197":1,"2232":1}}],["assert",{"2":{"1070":1},"3":{"0":7,"122":1,"134":1,"150":2,"188":1,"353":1,"373":1,"415":1,"449":1,"590":1,"592":1,"614":1,"618":3,"619":1,"626":1,"757":1,"759":1,"776":1,"842":1,"860":1,"861":2,"862":1,"863":1,"891":1,"915":1,"945":1,"952":1,"953":1,"954":1,"955":1,"956":1,"960":1,"972":1,"973":1,"1018":2,"1051":1,"1067":3,"1070":1,"1074":1,"1085":1,"1213":1,"1237":1,"1247":1,"1270":6,"1271":1,"1281":1,"1286":7,"1300":1,"1309":1,"1311":2,"1323":2,"1324":3,"1328":1,"1339":7,"1359":2,"1369":1,"1380":2,"1395":1,"1396":4,"1415":5,"1417":1,"1428":2,"1429":1,"1430":2,"1432":1,"1433":1,"1436":69,"1438":7,"1449":1,"1489":6,"1491":1,"1492":1,"1504":1,"1551":1,"1600":1,"1611":1,"1651":1,"1671":1,"1782":1,"1902":1,"1951":1,"2041":1,"2043":1,"2047":2,"2054":1,"2055":2,"2064":1,"2100":1,"2103":1,"2104":1,"2107":1,"2122":1,"2133":1,"2165":1,"2167":1,"2180":1,"2232":1}}],["assembling",{"3":{"1247":1,"1300":1,"1356":1,"1396":1,"1436":2,"1438":1}}],["assemblies",{"3":{"0":4,"27":1,"41":1,"60":1,"80":2,"109":1,"132":2,"135":2,"265":2,"269":1,"341":1,"413":1,"573":1,"576":1,"583":2,"584":3,"649":3,"651":3,"726":1,"881":2,"884":1,"982":1,"1005":1,"1050":2,"1052":1,"1161":1,"1163":2,"1170":1,"1203":1,"1212":1,"1213":1,"1214":1,"1237":2,"1250":1,"1259":2,"1266":1,"1270":5,"1276":1,"1279":3,"1282":2,"1286":27,"1300":8,"1311":4,"1313":1,"1315":2,"1323":7,"1324":4,"1325":1,"1333":1,"1339":2,"1363":1,"1369":1,"1380":2,"1396":2,"1409":1,"1415":5,"1416":2,"1431":1,"1436":111,"1437":1,"1442":1,"1443":1,"1445":1,"1455":3,"1460":2,"1489":5,"1492":4,"1496":3,"1504":1,"1526":2,"1585":1,"1591":4,"1653":1,"1664":1,"1669":1,"1671":1,"1782":1,"1810":1,"1849":2,"1857":1,"1875":1,"1881":4,"1885":1,"1886":1,"1891":1,"2004":1,"2013":2,"2042":2,"2048":1,"2054":1,"2100":1,"2101":1,"2102":1,"2108":1,"2120":1,"2156":1,"2159":1}}],["assembler",{"3":{"1247":1}}],["assemble",{"3":{"660":1,"724":1,"1276":1,"1300":1,"1309":1,"1313":1,"1324":1,"1402":3,"1640":1,"2077":1,"2087":1}}],["assembled",{"0":{"1262":1,"1466":1,"1821":1},"3":{"0":1,"256":1,"545":1,"585":1,"599":2,"649":1,"727":1,"745":1,"846":1,"861":1,"1017":1,"1021":1,"1107":1,"1240":1,"1247":2,"1271":1,"1284":1,"1286":1,"1300":3,"1309":1,"1311":1,"1323":4,"1339":1,"1350":8,"1353":1,"1359":10,"1366":2,"1368":1,"1369":1,"1395":1,"1396":1,"1407":1,"1410":1,"1415":4,"1416":1,"1423":1,"1427":1,"1436":1,"1445":1,"1466":1,"1467":4,"1492":1,"1496":4,"1577":1,"1585":1,"1987":1}}],["assembles",{"3":{"0":1,"725":1,"1212":1,"1247":2,"1270":1,"1271":2,"1309":1,"1311":1,"1323":2,"1324":3,"1359":3,"1381":1,"1396":2,"1402":1,"1415":1,"1427":1,"1436":4,"2067":1,"2150":1}}],["assemblyfilters",{"3":{"1455":1}}],["assemblyname",{"2":{"1319":1},"3":{"1311":1,"1319":1,"1323":3,"1350":2,"1359":1,"1369":1,"1380":2,"1396":4,"1415":7,"1436":1}}],["assemblyprovider",{"3":{"1286":1,"1369":2,"1396":1}}],["assemblyqualifiedname",{"3":{"1259":1,"1286":1}}],["assemblyinfo",{"2":{"1497":1},"3":{"1209":1,"1497":1}}],["assemblyreference",{"2":{"1319":1},"3":{"1199":16,"1203":16,"1207":48,"1311":14,"1313":1,"1319":4,"1323":46,"1341":3,"1350":12,"1354":2,"1359":7,"1369":11,"1377":2,"1380":8,"1396":36,"1404":2,"1415":46,"1436":1,"1496":3}}],["assembly",{"0":{"1199":1,"1206":1,"1319":1},"2":{"1266":1,"1319":1,"1773":1,"2077":2},"3":{"0":8,"5":1,"24":4,"41":1,"54":1,"59":2,"63":1,"76":2,"80":2,"81":3,"128":1,"133":1,"135":2,"166":1,"290":1,"305":1,"408":1,"451":1,"454":1,"549":1,"564":3,"571":1,"583":9,"585":6,"586":1,"607":1,"609":4,"633":2,"640":1,"648":1,"649":3,"651":2,"684":1,"698":1,"699":1,"725":2,"726":1,"782":1,"844":1,"848":2,"849":4,"922":1,"939":2,"940":1,"954":1,"962":1,"963":1,"971":1,"972":1,"974":1,"980":2,"982":1,"1004":1,"1018":1,"1034":3,"1042":1,"1050":1,"1052":1,"1067":1,"1074":1,"1091":1,"1118":1,"1161":3,"1162":1,"1168":1,"1170":1,"1175":1,"1193":1,"1196":1,"1199":1,"1202":1,"1203":2,"1206":1,"1207":1,"1208":3,"1212":2,"1213":1,"1229":1,"1233":2,"1237":5,"1247":2,"1250":2,"1256":1,"1259":13,"1262":2,"1263":1,"1266":1,"1270":8,"1271":7,"1273":2,"1275":1,"1276":1,"1279":5,"1280":4,"1286":86,"1300":32,"1301":1,"1304":1,"1308":1,"1309":15,"1310":1,"1311":42,"1312":2,"1313":1,"1314":1,"1315":3,"1316":4,"1318":1,"1319":6,"1323":94,"1324":37,"1326":1,"1339":15,"1341":4,"1348":1,"1350":20,"1351":1,"1352":1,"1354":1,"1359":60,"1367":1,"1369":22,"1380":28,"1395":28,"1396":69,"1402":5,"1404":1,"1415":86,"1416":9,"1417":1,"1427":1,"1431":8,"1434":1,"1435":3,"1436":409,"1437":3,"1438":10,"1441":1,"1444":1,"1447":1,"1465":1,"1476":1,"1480":1,"1487":2,"1488":1,"1489":11,"1490":1,"1491":1,"1492":2,"1494":1,"1496":5,"1503":1,"1526":3,"1553":1,"1577":3,"1582":1,"1591":4,"1600":1,"1614":1,"1627":1,"1648":1,"1651":3,"1653":1,"1654":2,"1671":1,"1684":3,"1693":1,"1701":1,"1719":2,"1727":1,"1728":2,"1729":1,"1761":1,"1773":1,"1781":1,"1810":1,"1830":2,"1851":1,"1881":2,"1883":2,"1885":3,"1891":1,"1929":1,"1956":1,"2016":1,"2041":2,"2043":1,"2046":1,"2048":2,"2049":1,"2077":2,"2095":1,"2103":1,"2104":2,"2106":1,"2159":1,"2178":1,"2187":2,"2188":1}}],["assetcachecontrol",{"3":{"1359":1}}],["assetid",{"3":{"0":1,"1116":1,"1357":1,"1467":2,"1631":2}}],["assets",{"3":{"0":5,"186":1,"1116":6,"1117":2,"1300":1,"1311":2,"1318":1,"1324":5,"1342":1,"1346":2,"1347":1,"1348":2,"1350":14,"1357":2,"1359":14,"1362":1,"1369":2,"1380":1,"1389":1,"1393":1,"1395":2,"1416":4,"1434":1,"1436":2,"1437":1,"1438":1,"1459":1,"1467":4,"1479":4,"1480":5,"1482":1,"1599":1,"1631":3,"1640":2,"1666":1,"2110":1,"2127":1,"2150":1}}],["assetlinkspath",{"3":{"1311":1}}],["assetlinks",{"2":{"418":1,"419":1,"424":1,"2121":1},"3":{"0":1,"418":1,"419":1,"420":1,"424":1,"1212":1,"1311":2,"1416":2,"1481":1,"2121":1}}],["asset",{"3":{"0":3,"57":1,"59":1,"189":1,"217":1,"424":1,"426":1,"534":1,"536":1,"681":1,"683":1,"861":1,"945":1,"954":1,"1006":1,"1116":7,"1117":1,"1120":2,"1124":1,"1126":1,"1285":1,"1286":1,"1300":3,"1311":4,"1324":4,"1342":2,"1344":1,"1346":1,"1348":1,"1350":16,"1352":1,"1354":1,"1357":1,"1359":60,"1362":1,"1369":3,"1380":3,"1389":2,"1395":23,"1417":2,"1436":1,"1438":1,"1467":2,"1471":1,"1496":4,"1560":1,"1586":1,"1591":1,"1631":1,"1640":4,"1666":1,"1698":1}}],["as",{"0":{"576":1,"1250":1,"1253":1,"1274":1,"1335":1,"1344":1,"1357":2,"1378":1,"1389":1,"1433":1,"1734":1,"1871":2,"1895":1,"2045":1,"2057":1,"2100":1,"2164":1},"1":{"129":1,"130":1,"131":1,"132":1,"133":1,"134":1,"135":1,"136":1,"137":1,"138":1,"139":1,"140":1,"141":1,"142":1,"143":1,"542":1,"543":1,"544":1,"545":1,"546":1,"547":1,"548":1,"549":1,"550":1,"551":1,"552":1,"561":1,"562":1,"563":1,"564":1,"565":1,"566":1,"567":1,"568":1,"569":1,"570":1,"571":1,"572":1,"573":1,"574":1,"575":1,"576":1,"577":1,"578":1,"579":1,"606":1,"607":1,"608":1,"609":1,"610":1,"611":1,"612":1,"613":1,"614":1,"615":1,"616":1,"617":1,"618":1,"619":1,"620":1,"621":1,"622":1,"623":1,"624":1,"625":1,"626":1,"627":1,"628":1,"629":1,"630":1,"654":1,"655":1,"656":1,"657":1,"658":1,"659":1,"660":1,"661":1,"722":1,"723":1,"724":1,"725":1,"726":1,"727":1,"728":1,"729":1,"730":1,"731":1,"732":1,"733":1,"734":1,"735":1,"736":1,"737":1,"738":1,"739":1,"740":1,"741":1,"742":1,"743":1,"744":1,"745":1,"763":1,"764":1,"765":1,"766":1,"767":1,"768":1,"769":1,"770":1,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1,"951":1,"952":1,"953":1,"954":1,"955":1,"956":1,"957":1,"958":1,"959":1,"960":1,"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1,"969":1,"970":1,"971":1,"972":1,"973":1,"974":1,"975":1,"976":1,"977":1,"978":1,"979":1,"980":1,"981":1,"982":1,"983":1,"984":1,"1001":1,"1002":1,"1003":1,"1004":1,"1005":1,"1006":1,"1007":1,"1008":1,"1031":1,"1032":1,"1033":1,"1034":1,"1035":1,"1036":1,"1037":1,"1038":1,"1047":1,"1048":1,"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1,"1056":2,"1057":2,"1058":2,"1059":2,"1060":2,"1061":2,"1062":2,"1063":2,"1064":1,"1065":1,"1066":1,"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1,"1462":1,"1463":1,"1464":1,"1465":1,"1466":1,"1467":1,"1468":1,"1469":1,"1470":1,"1931":1,"1932":1,"1933":1,"1934":1,"1935":1,"1936":1,"1937":1,"1938":1,"1939":1,"1940":1,"1941":1,"2070":1,"2071":1,"2072":1,"2073":1,"2074":1,"2075":1,"2076":1,"2077":1,"2078":1,"2079":1,"2080":1},"3":{"0":165,"5":1,"11":1,"12":1,"15":1,"17":1,"18":2,"19":1,"24":6,"26":3,"27":1,"31":2,"39":2,"41":1,"47":1,"53":1,"57":3,"58":2,"59":1,"66":2,"68":1,"71":2,"72":2,"73":2,"74":2,"76":1,"77":1,"79":1,"81":2,"86":1,"87":1,"91":1,"92":1,"93":2,"94":1,"95":5,"96":3,"97":1,"98":3,"99":1,"100":2,"101":2,"103":1,"104":1,"107":2,"109":15,"111":1,"115":3,"120":1,"121":3,"122":2,"123":1,"126":3,"127":1,"128":2,"129":1,"130":1,"132":1,"135":11,"136":5,"137":4,"138":4,"139":5,"140":3,"141":1,"142":3,"145":7,"147":2,"155":3,"157":6,"159":1,"160":5,"161":2,"164":1,"167":1,"168":1,"169":1,"170":1,"171":2,"173":1,"174":1,"175":1,"177":1,"178":1,"180":2,"181":2,"186":8,"188":4,"189":1,"195":2,"197":1,"198":1,"200":4,"201":2,"202":6,"203":1,"206":1,"207":1,"209":1,"214":6,"215":1,"216":1,"219":1,"223":1,"225":2,"226":2,"230":6,"231":3,"233":4,"234":3,"235":6,"237":3,"243":8,"244":4,"245":8,"247":1,"248":2,"249":3,"250":1,"252":1,"253":1,"254":2,"255":2,"256":1,"257":2,"258":4,"259":4,"260":2,"264":1,"265":15,"267":2,"268":3,"273":4,"275":1,"279":1,"281":2,"282":1,"283":2,"285":2,"286":1,"290":3,"291":1,"295":1,"296":2,"299":1,"303":8,"304":1,"305":4,"306":1,"308":1,"309":1,"310":2,"313":1,"314":2,"318":12,"319":1,"320":2,"321":3,"322":2,"325":3,"326":2,"329":2,"335":2,"336":1,"337":1,"341":10,"344":1,"345":3,"348":1,"349":1,"350":6,"351":2,"352":1,"353":5,"354":1,"358":3,"359":7,"360":3,"361":2,"362":2,"363":1,"365":3,"366":2,"369":4,"370":2,"371":1,"373":2,"374":2,"375":2,"376":1,"379":1,"381":1,"382":2,"383":1,"384":1,"388":2,"390":4,"392":2,"395":5,"397":5,"399":1,"401":4,"403":1,"404":3,"405":1,"406":2,"407":1,"408":1,"409":5,"411":2,"413":1,"415":3,"418":4,"420":1,"422":2,"423":3,"424":1,"425":2,"426":1,"427":2,"428":2,"434":2,"438":2,"440":2,"442":1,"443":2,"444":1,"447":1,"448":2,"451":1,"453":1,"454":2,"459":6,"461":1,"463":2,"464":1,"466":1,"469":2,"470":2,"471":1,"474":2,"476":1,"477":2,"478":1,"481":2,"484":1,"485":1,"486":3,"487":2,"488":3,"489":3,"490":7,"491":5,"492":5,"493":4,"494":3,"495":2,"497":4,"499":2,"500":1,"501":1,"502":1,"507":3,"512":1,"513":1,"514":2,"516":2,"518":4,"522":2,"523":2,"524":3,"526":2,"528":2,"530":1,"531":1,"534":3,"535":2,"536":8,"537":2,"538":2,"539":3,"540":3,"541":1,"542":1,"543":2,"544":1,"545":4,"547":4,"548":2,"549":10,"550":5,"551":3,"552":1,"555":1,"556":3,"561":1,"562":1,"563":1,"564":6,"565":1,"566":1,"569":1,"570":1,"572":2,"573":4,"575":2,"578":2,"579":1,"582":4,"583":7,"584":2,"585":7,"586":1,"587":1,"591":3,"592":3,"593":1,"597":1,"598":2,"599":9,"600":4,"601":4,"602":3,"604":1,"606":1,"607":6,"608":2,"609":9,"610":2,"611":1,"612":3,"613":1,"615":1,"617":2,"618":3,"619":3,"620":3,"622":1,"623":1,"624":3,"625":1,"626":7,"627":2,"629":1,"631":1,"632":1,"633":6,"634":2,"635":2,"638":2,"640":1,"642":1,"648":1,"649":3,"653":1,"654":1,"655":1,"656":2,"657":9,"658":1,"660":1,"661":1,"665":2,"667":1,"669":1,"670":1,"674":2,"676":4,"682":5,"683":3,"684":2,"688":2,"689":1,"690":3,"691":3,"692":7,"693":1,"694":1,"698":8,"699":2,"702":5,"703":1,"706":1,"707":7,"709":1,"711":1,"715":3,"717":1,"720":3,"722":1,"724":2,"725":10,"727":2,"728":2,"729":1,"730":1,"732":3,"733":3,"734":1,"735":3,"736":1,"737":1,"738":1,"740":1,"741":7,"742":3,"743":6,"744":1,"747":4,"748":4,"749":4,"750":1,"751":1,"755":1,"757":7,"758":2,"761":1,"762":1,"763":1,"764":2,"765":2,"766":2,"768":1,"769":1,"773":3,"774":4,"776":1,"778":1,"782":3,"783":1,"784":4,"785":1,"789":3,"790":10,"791":2,"794":1,"798":1,"799":1,"801":2,"803":2,"804":2,"808":1,"809":2,"810":4,"813":2,"814":1,"815":2,"817":1,"819":5,"820":1,"825":5,"826":1,"827":16,"828":1,"829":4,"831":2,"832":3,"833":1,"834":1,"836":1,"837":7,"838":7,"839":4,"840":2,"842":3,"844":1,"845":3,"846":5,"849":1,"850":1,"854":3,"857":2,"858":1,"859":1,"860":2,"861":7,"863":3,"864":3,"867":1,"868":2,"869":2,"870":5,"871":1,"872":3,"873":3,"875":1,"876":1,"877":1,"879":2,"880":3,"881":10,"882":1,"883":1,"884":2,"888":3,"889":6,"890":2,"892":1,"893":2,"896":1,"897":3,"903":1,"904":4,"905":11,"906":2,"907":5,"908":1,"909":1,"912":3,"914":2,"915":1,"920":1,"922":3,"923":3,"926":10,"927":1,"930":1,"931":1,"933":2,"937":1,"938":2,"939":1,"946":2,"948":3,"951":1,"953":5,"954":2,"955":4,"956":1,"961":1,"964":2,"965":1,"966":3,"968":3,"969":1,"971":1,"972":2,"973":2,"974":2,"975":2,"976":1,"977":1,"979":4,"980":8,"981":2,"982":1,"983":1,"986":1,"987":2,"988":4,"989":1,"990":1,"992":1,"994":2,"995":1,"996":8,"999":1,"1000":1,"1001":1,"1003":1,"1004":4,"1005":1,"1006":1,"1010":1,"1012":6,"1013":1,"1014":4,"1016":1,"1017":3,"1018":8,"1019":2,"1020":1,"1021":1,"1022":1,"1026":2,"1028":1,"1031":1,"1032":2,"1033":2,"1034":3,"1035":1,"1036":3,"1037":1,"1041":2,"1042":4,"1044":1,"1046":1,"1047":1,"1050":10,"1051":6,"1052":4,"1055":1,"1056":2,"1057":1,"1058":1,"1059":7,"1061":2,"1062":1,"1064":1,"1065":4,"1066":2,"1067":8,"1068":3,"1069":2,"1070":1,"1071":1,"1073":2,"1074":2,"1075":2,"1076":2,"1082":1,"1083":2,"1084":1,"1089":2,"1090":1,"1091":11,"1092":2,"1095":1,"1099":1,"1100":4,"1101":1,"1102":2,"1108":4,"1109":1,"1110":1,"1115":2,"1116":11,"1117":2,"1118":2,"1119":2,"1123":2,"1124":6,"1125":1,"1126":1,"1127":1,"1132":2,"1133":4,"1139":1,"1140":3,"1142":4,"1143":1,"1149":1,"1151":1,"1153":1,"1155":2,"1158":1,"1160":1,"1161":7,"1163":1,"1164":1,"1168":6,"1169":1,"1173":1,"1175":3,"1176":1,"1178":2,"1179":1,"1184":6,"1185":2,"1186":2,"1187":1,"1188":2,"1189":1,"1190":3,"1191":1,"1192":1,"1193":1,"1196":2,"1201":1,"1209":1,"1211":1,"1212":50,"1216":2,"1217":1,"1218":1,"1219":1,"1220":2,"1225":2,"1228":1,"1229":38,"1231":6,"1232":3,"1233":4,"1234":4,"1235":1,"1236":2,"1237":76,"1239":7,"1240":1,"1241":4,"1242":2,"1243":5,"1244":3,"1245":1,"1247":112,"1248":1,"1249":2,"1251":1,"1252":1,"1253":4,"1254":1,"1255":3,"1256":3,"1257":1,"1258":1,"1259":95,"1260":1,"1262":1,"1263":9,"1264":1,"1265":5,"1266":2,"1267":2,"1268":3,"1269":4,"1270":175,"1271":31,"1272":2,"1273":8,"1274":4,"1275":4,"1276":9,"1277":3,"1278":6,"1279":1,"1281":6,"1282":3,"1283":4,"1284":2,"1286":453,"1287":5,"1288":2,"1289":4,"1290":8,"1291":1,"1293":5,"1294":1,"1295":1,"1296":2,"1297":6,"1298":1,"1299":2,"1300":362,"1301":59,"1302":1,"1304":6,"1305":2,"1306":1,"1307":2,"1308":3,"1309":131,"1310":29,"1311":254,"1312":41,"1313":3,"1315":5,"1316":4,"1317":2,"1318":12,"1320":6,"1322":1,"1323":264,"1324":502,"1325":2,"1326":4,"1327":1,"1328":5,"1329":1,"1332":7,"1333":6,"1334":4,"1335":2,"1336":2,"1337":7,"1338":4,"1339":212,"1342":8,"1343":8,"1344":3,"1347":6,"1348":1,"1349":6,"1350":230,"1351":2,"1352":7,"1353":7,"1354":3,"1355":3,"1356":2,"1357":7,"1358":10,"1359":811,"1362":4,"1363":1,"1364":2,"1365":3,"1366":5,"1368":5,"1369":57,"1370":1,"1371":1,"1372":1,"1373":2,"1374":1,"1375":3,"1377":2,"1378":3,"1379":6,"1380":103,"1381":1,"1382":3,"1383":2,"1384":2,"1387":1,"1389":5,"1390":3,"1391":6,"1392":2,"1393":2,"1394":1,"1395":434,"1396":484,"1397":2,"1398":2,"1399":4,"1400":5,"1401":1,"1402":145,"1403":1,"1404":1,"1405":2,"1406":3,"1407":4,"1408":4,"1410":1,"1411":3,"1412":3,"1413":2,"1414":3,"1415":248,"1416":49,"1417":203,"1419":1,"1420":1,"1421":6,"1423":2,"1424":2,"1425":4,"1426":1,"1427":56,"1428":1,"1429":1,"1430":6,"1431":12,"1432":5,"1433":10,"1434":1,"1435":4,"1436":651,"1437":3,"1438":34,"1441":13,"1442":6,"1443":4,"1444":5,"1445":3,"1446":1,"1447":3,"1449":1,"1450":4,"1452":1,"1453":7,"1454":5,"1455":30,"1456":6,"1457":4,"1458":8,"1459":10,"1460":4,"1461":8,"1462":4,"1463":1,"1465":12,"1466":2,"1467":77,"1468":1,"1469":2,"1470":5,"1472":4,"1473":6,"1474":12,"1475":1,"1476":13,"1477":3,"1478":4,"1479":3,"1480":2,"1481":2,"1482":2,"1484":1,"1485":3,"1486":3,"1487":3,"1488":19,"1489":10,"1490":5,"1491":1,"1492":23,"1494":2,"1496":33,"1498":1,"1500":6,"1503":2,"1508":2,"1509":3,"1513":1,"1517":2,"1521":1,"1522":1,"1523":1,"1524":5,"1525":1,"1526":24,"1527":2,"1529":1,"1531":3,"1533":1,"1534":6,"1535":1,"1537":1,"1538":1,"1543":1,"1545":1,"1547":2,"1551":2,"1552":1,"1553":3,"1554":2,"1558":2,"1563":1,"1565":1,"1567":1,"1569":1,"1570":2,"1571":4,"1572":1,"1573":1,"1574":1,"1575":3,"1576":2,"1577":33,"1580":1,"1582":2,"1583":2,"1584":1,"1585":7,"1586":3,"1587":1,"1588":1,"1589":3,"1591":11,"1594":2,"1595":3,"1596":8,"1597":1,"1598":3,"1599":5,"1600":2,"1601":1,"1605":1,"1610":1,"1611":6,"1615":1,"1620":1,"1626":2,"1627":4,"1628":1,"1630":5,"1631":25,"1632":5,"1633":1,"1634":2,"1635":1,"1637":4,"1638":5,"1639":20,"1640":8,"1641":21,"1642":1,"1646":2,"1648":3,"1651":8,"1653":2,"1654":3,"1656":6,"1659":1,"1660":1,"1661":1,"1662":3,"1663":1,"1664":10,"1665":3,"1666":3,"1667":3,"1668":4,"1669":1,"1670":5,"1673":5,"1674":2,"1675":3,"1676":1,"1677":2,"1681":1,"1682":1,"1684":1,"1685":8,"1686":10,"1687":1,"1689":2,"1692":2,"1693":2,"1699":2,"1700":1,"1701":2,"1702":5,"1706":1,"1707":2,"1709":1,"1710":1,"1711":1,"1717":1,"1718":1,"1720":2,"1722":1,"1723":3,"1724":1,"1726":1,"1727":5,"1728":4,"1730":1,"1732":2,"1735":1,"1737":1,"1739":1,"1742":1,"1744":1,"1745":1,"1748":5,"1749":7,"1750":1,"1755":1,"1756":1,"1760":2,"1761":1,"1762":1,"1763":1,"1764":4,"1765":9,"1767":8,"1768":2,"1770":1,"1771":1,"1774":1,"1776":1,"1779":1,"1780":1,"1781":1,"1782":1,"1784":5,"1786":1,"1790":1,"1797":1,"1799":3,"1802":3,"1803":1,"1804":2,"1805":4,"1806":2,"1807":3,"1810":5,"1811":1,"1812":1,"1813":2,"1814":2,"1815":7,"1816":1,"1821":2,"1822":1,"1824":1,"1825":3,"1826":2,"1827":2,"1828":1,"1831":1,"1832":1,"1833":2,"1834":3,"1836":2,"1839":1,"1840":1,"1844":1,"1846":1,"1851":6,"1853":2,"1860":2,"1861":1,"1865":2,"1866":1,"1868":4,"1869":1,"1871":3,"1872":1,"1873":2,"1875":1,"1876":1,"1877":6,"1878":2,"1879":1,"1881":1,"1882":1,"1883":5,"1884":2,"1885":1,"1889":2,"1892":1,"1894":1,"1897":1,"1898":3,"1899":1,"1902":4,"1903":2,"1904":1,"1905":1,"1907":1,"1908":8,"1909":3,"1910":5,"1911":3,"1915":1,"1916":3,"1919":4,"1920":1,"1921":1,"1922":2,"1923":5,"1924":2,"1926":1,"1927":4,"1928":1,"1930":3,"1931":3,"1932":1,"1933":2,"1934":1,"1936":1,"1937":2,"1939":1,"1940":3,"1941":5,"1942":1,"1943":1,"1944":2,"1947":2,"1948":2,"1949":2,"1950":3,"1952":1,"1954":3,"1955":2,"1956":1,"1958":1,"1962":1,"1963":1,"1964":5,"1965":1,"1966":1,"1968":2,"1970":2,"1971":3,"1972":2,"1974":1,"1975":1,"1976":1,"1977":2,"1978":2,"1979":3,"1980":2,"1982":3,"1983":1,"1984":2,"1985":2,"1986":1,"1988":1,"1989":3,"1993":1,"1994":5,"1995":2,"1996":4,"1997":8,"1998":1,"1999":4,"2000":8,"2001":2,"2002":1,"2003":1,"2004":1,"2007":1,"2008":2,"2009":1,"2010":2,"2011":3,"2012":2,"2013":1,"2014":1,"2015":1,"2016":1,"2018":1,"2019":1,"2022":1,"2024":3,"2025":1,"2028":1,"2032":3,"2033":1,"2034":7,"2036":1,"2037":3,"2040":1,"2041":2,"2043":3,"2044":3,"2046":3,"2047":5,"2048":4,"2049":2,"2051":3,"2053":1,"2055":2,"2056":1,"2057":4,"2058":1,"2059":3,"2060":1,"2063":1,"2064":5,"2065":2,"2066":1,"2067":2,"2068":1,"2069":1,"2070":2,"2074":1,"2075":3,"2077":1,"2078":2,"2079":1,"2080":2,"2081":2,"2083":3,"2085":4,"2086":3,"2089":1,"2090":1,"2091":1,"2096":2,"2098":1,"2100":2,"2101":1,"2104":1,"2105":2,"2106":1,"2107":3,"2108":3,"2111":1,"2112":1,"2115":1,"2119":1,"2121":1,"2122":2,"2124":2,"2125":1,"2126":6,"2128":3,"2129":1,"2130":1,"2135":4,"2136":1,"2138":7,"2139":1,"2140":1,"2141":1,"2142":3,"2143":1,"2145":2,"2146":2,"2147":1,"2148":1,"2149":2,"2150":4,"2151":1,"2152":1,"2153":2,"2154":1,"2156":6,"2157":2,"2158":5,"2159":1,"2160":3,"2161":6,"2162":3,"2163":1,"2164":1,"2165":1,"2166":5,"2167":2,"2168":5,"2169":4,"2170":1,"2171":2,"2172":4,"2174":1,"2175":2,"2177":2,"2178":3,"2179":4,"2180":4,"2182":1,"2183":4,"2185":3,"2186":1,"2189":4,"2190":4,"2192":5,"2193":1,"2194":1,"2195":1,"2196":1,"2198":1,"2199":4,"2202":2,"2203":5,"2204":1,"2208":1,"2210":1,"2214":3,"2215":2,"2219":1,"2220":2,"2226":2,"2229":1,"2230":3,"2232":53,"2233":2,"2234":1,"2239":3}}],["atroot",{"3":{"1324":3,"1417":2}}],["atom",{"3":{"1311":1}}],["atomicity",{"3":{"120":1,"201":1,"245":1,"248":1,"249":1,"279":1,"990":1,"1042":1,"1043":1,"1044":1,"1251":1,"1259":3,"1270":3,"1275":1,"1301":2,"1309":2,"1318":1,"1324":1,"1350":1,"1359":3,"1380":3,"1406":1,"1415":1,"1572":1,"1822":1,"1835":1,"1852":1,"1915":1,"2092":1,"2096":1,"2185":1}}],["atomically",{"3":{"18":1,"193":1,"341":1,"342":1,"499":1,"1043":1,"1259":3,"1286":1,"1290":1,"1300":1,"1309":1,"1323":2,"1339":1,"1349":1,"1350":1,"1359":1,"1374":1,"1380":2,"1396":7,"1415":2,"1417":2,"1438":1,"1455":1,"1467":1,"1837":1,"1872":1,"1877":1,"1890":1,"2214":1}}],["atomic",{"3":{"0":1,"20":1,"39":1,"43":1,"245":1,"253":1,"279":1,"285":1,"286":3,"497":1,"524":1,"534":1,"535":1,"536":1,"540":1,"708":1,"905":1,"996":1,"1251":1,"1259":1,"1270":3,"1274":1,"1286":3,"1290":1,"1292":1,"1300":4,"1301":6,"1309":1,"1323":5,"1339":1,"1352":1,"1359":7,"1380":4,"1396":4,"1399":1,"1402":1,"1415":4,"1436":1,"1438":1,"1496":1,"1526":1,"1543":1,"1577":2,"1590":1,"1591":2,"1597":1,"1600":2,"1667":2,"1668":1,"1749":2,"1765":3,"1823":1,"1836":1,"1841":1,"1846":1,"2001":3,"2161":1,"2182":1}}],["atop",{"3":{"1212":1}}],["atl",{"3":{"2241":1}}],["atlcloudconf",{"3":{"2240":1}}],["atlanta",{"0":{"2240":1},"1":{"1628":1,"1629":1,"1630":1,"1631":1,"1632":1,"1633":1,"1634":1,"1635":1,"1636":1,"1637":1,"1638":1,"1639":1,"1640":1,"1641":1},"3":{"86":1,"1211":1,"1238":1,"1340":1,"1347":1,"1350":2,"1359":3,"1364":2,"1369":2,"1395":3,"1416":2,"1436":1,"1455":1,"1523":1,"1628":1,"1629":1,"1637":1,"1785":1,"2004":1,"2110":1,"2212":1,"2214":2,"2216":4,"2221":2,"2228":1,"2238":3,"2239":4,"2240":4,"2241":4,"2244":1,"2245":1}}],["atldevcon",{"0":{"1478":1},"2":{"45":1,"1451":1,"1463":1,"1471":1,"1478":2,"1481":1,"1482":1,"2034":1,"2036":1},"3":{"0":4,"45":1,"47":2,"418":1,"420":1,"426":1,"428":1,"429":1,"1286":1,"1300":2,"1311":1,"1359":2,"1395":2,"1416":4,"1441":2,"1446":1,"1451":1,"1463":1,"1466":1,"1467":7,"1469":2,"1470":2,"1471":1,"1473":11,"1474":5,"1475":2,"1478":12,"1481":3,"1482":4,"1483":2,"1484":2,"1976":1,"2034":1,"2036":2,"2240":1}}],["attheceiling",{"3":{"1436":8}}],["attractive",{"3":{"1998":1}}],["attr",{"3":{"1286":1}}],["attributing",{"3":{"1438":1,"1489":1}}],["attributions",{"3":{"1496":2}}],["attributiontags",{"2":{"2177":1},"3":{"1427":1,"2177":1}}],["attribution",{"0":{"1679":1},"3":{"348":2,"418":1,"963":1,"1012":1,"1017":1,"1339":1,"1396":1,"1425":1,"1427":1,"1436":3,"1465":1,"1467":1,"1476":1,"1496":1,"1526":1,"1568":1,"1575":3,"1577":2,"1585":2,"1676":1,"1679":1,"1680":1,"1799":1,"2171":1,"2177":1}}],["attributable",{"3":{"26":1,"175":1,"176":1,"178":1,"827":1,"829":1,"898":1,"1019":1,"1237":1,"1311":1,"1380":1,"1436":1,"1455":1,"1465":1,"1467":3,"1553":1,"1568":1,"1591":1,"1678":1,"1999":1,"2000":1,"2001":1,"2002":2,"2024":1,"2177":1,"2180":1}}],["attributed",{"3":{"175":1,"423":1,"741":1,"799":1,"1010":1,"1161":2,"1279":1,"1286":3,"1309":1,"1337":1,"1339":2,"1359":7,"1396":1,"1415":1,"1425":2,"1427":1,"1436":2,"1496":1,"2177":1}}],["attributeusageattribute",{"3":{"1300":1}}],["attributeusage",{"2":{"160":1,"175":1},"3":{"160":1,"175":1,"1237":3,"1300":2,"1310":2,"1311":3,"1312":1,"1323":2,"1324":2,"2000":1}}],["attributetargets",{"2":{"160":1,"1994":2},"3":{"160":1,"1237":3,"1300":2,"1310":2,"1311":4,"1312":3,"1323":2,"1324":2,"1994":2}}],["attributes",{"0":{"1321":1},"2":{"39":1},"3":{"24":1,"39":1,"135":1,"189":1,"320":1,"350":1,"418":1,"446":1,"448":2,"450":3,"554":1,"556":1,"651":1,"726":1,"814":1,"841":1,"964":1,"965":1,"980":1,"992":1,"1018":1,"1118":2,"1133":1,"1175":1,"1192":2,"1196":1,"1202":2,"1203":6,"1207":18,"1212":1,"1214":1,"1230":2,"1233":1,"1236":3,"1237":7,"1247":1,"1259":3,"1270":1,"1271":1,"1279":1,"1286":9,"1298":1,"1300":11,"1309":3,"1310":4,"1311":18,"1313":1,"1321":2,"1322":1,"1323":1,"1324":10,"1339":2,"1350":7,"1358":1,"1359":5,"1369":2,"1372":1,"1376":1,"1380":9,"1395":8,"1396":3,"1402":2,"1406":1,"1415":7,"1416":2,"1433":1,"1436":32,"1438":2,"1496":1,"1526":1,"1540":1,"1577":2,"1600":1,"1630":3,"1632":1,"1641":1,"1662":1,"1667":1,"1679":1,"1685":1,"1761":1,"1764":1,"1850":1,"1957":1,"1960":1,"1961":1,"1978":1,"1996":3,"2002":1,"2131":3,"2132":3,"2133":1}}],["attribute",{"0":{"1908":1,"1962":1},"1":{"1854":1,"1855":1,"1856":1,"1857":1,"1858":1,"1859":1,"1860":1,"1861":1,"1862":1,"1906":1,"1907":1,"1908":1,"1909":1,"1910":1,"1911":1,"1912":1},"3":{"0":3,"10":2,"24":2,"39":1,"54":2,"121":1,"124":1,"135":1,"136":1,"155":1,"160":2,"166":2,"167":2,"175":2,"185":1,"188":1,"189":4,"257":1,"273":1,"305":1,"318":1,"341":2,"343":1,"418":1,"422":2,"423":1,"556":1,"659":1,"718":1,"725":1,"789":1,"819":1,"849":1,"906":1,"964":2,"965":1,"966":1,"982":1,"1050":4,"1052":1,"1083":1,"1103":1,"1116":2,"1118":1,"1126":1,"1212":3,"1229":3,"1233":1,"1235":1,"1236":2,"1237":31,"1247":3,"1250":1,"1259":9,"1270":3,"1271":1,"1280":1,"1281":2,"1282":3,"1286":24,"1289":1,"1292":1,"1297":1,"1300":36,"1309":10,"1310":7,"1311":28,"1312":10,"1318":1,"1321":1,"1323":27,"1324":21,"1333":1,"1339":5,"1343":1,"1347":1,"1350":20,"1357":2,"1359":42,"1360":1,"1361":1,"1369":2,"1372":2,"1373":2,"1380":25,"1388":1,"1391":1,"1395":14,"1396":28,"1402":4,"1415":19,"1416":5,"1417":1,"1427":1,"1431":1,"1433":1,"1436":77,"1438":6,"1447":2,"1467":2,"1496":1,"1510":1,"1575":2,"1577":4,"1582":1,"1585":2,"1591":1,"1596":1,"1626":1,"1627":3,"1630":17,"1641":2,"1643":1,"1651":1,"1665":1,"1671":1,"1678":1,"1686":1,"1698":1,"1747":1,"1753":1,"1758":1,"1774":2,"1779":1,"1805":1,"1844":1,"1849":1,"1854":3,"1856":5,"1857":1,"1861":1,"1862":3,"1865":1,"1874":2,"1898":1,"1904":1,"1905":1,"1906":3,"1908":2,"1909":1,"1910":2,"1912":1,"1929":5,"1959":1,"1960":2,"1964":2,"1965":1,"1994":2,"1996":1,"2000":8,"2001":5,"2002":3,"2030":1,"2039":1,"2045":1,"2075":1,"2085":1,"2138":1,"2139":1,"2187":2,"2190":1,"2193":1,"2207":1,"2208":1,"2230":1,"2232":2}}],["attacked",{"3":{"1998":1,"2002":1}}],["attackers",{"3":{"1567":1}}],["attacker",{"3":{"31":1,"280":1,"283":5,"353":1,"373":1,"422":1,"427":1,"439":1,"853":1,"906":1,"1124":1,"1212":1,"1286":1,"1288":1,"1292":2,"1300":14,"1311":1,"1323":1,"1324":4,"1339":2,"1347":1,"1359":2,"1396":1,"1402":2,"1408":1,"1415":2,"1416":3,"1417":3,"1435":1,"1436":1,"1438":1,"1467":1,"1897":4,"1900":2,"1901":1,"1902":3,"1981":1,"1999":2,"2001":5,"2124":2,"2141":1}}],["attacks",{"3":{"1324":1,"1436":1}}],["attack",{"0":{"1897":1},"3":{"280":1,"332":1,"508":1,"528":1,"612":1,"1300":1,"1324":1,"1339":1,"1366":1,"1369":1,"1396":2,"1415":1,"1438":1,"1445":1,"1465":1,"1577":1,"1897":2,"1899":2,"1990":1}}],["attachable",{"3":{"1369":1}}],["attachobserverasync",{"3":{"1324":3}}],["attachtenantaccessor",{"3":{"1286":1}}],["attachscopeaccessors",{"3":{"1259":1,"1286":1}}],["attaching",{"3":{"0":1,"180":1,"351":1,"507":1,"827":1,"1184":1,"1212":1,"1259":1,"1278":1,"1286":1,"1300":1,"1311":1,"1324":2,"1339":1,"1359":4,"1395":1,"1415":1,"1438":1}}],["attachments",{"3":{"1117":1}}],["attachment",{"2":{"443":1},"3":{"0":2,"443":1,"725":2,"741":1,"881":1,"1116":2,"1286":3,"1300":1,"1311":2,"1339":1,"1359":1,"1395":1,"1436":1,"1438":1,"1441":1,"1631":1}}],["attachmmcaapplifecycle",{"3":{"0":1,"413":1,"1416":2,"1417":4}}],["attached",{"3":{"0":2,"81":1,"179":1,"180":1,"383":1,"397":1,"598":1,"619":1,"657":1,"797":1,"827":1,"871":1,"896":1,"897":1,"907":1,"915":1,"1050":1,"1051":1,"1092":1,"1184":1,"1223":1,"1229":3,"1259":1,"1281":1,"1286":5,"1297":1,"1300":6,"1311":4,"1323":4,"1324":12,"1339":4,"1350":3,"1359":6,"1369":6,"1380":1,"1395":2,"1396":3,"1400":1,"1402":3,"1415":4,"1416":2,"1417":6,"1436":4,"1438":1,"1465":1,"1467":1,"1479":1,"1488":2,"1944":1,"2002":1,"2064":1,"2121":1,"2180":1,"2193":1}}],["attaches",{"3":{"0":1,"26":1,"42":1,"243":1,"387":1,"402":1,"454":1,"506":1,"507":1,"521":1,"599":1,"640":2,"767":1,"833":1,"885":1,"1104":1,"1183":1,"1184":1,"1253":1,"1271":2,"1286":3,"1297":1,"1300":3,"1307":1,"1311":11,"1312":1,"1323":3,"1324":12,"1326":1,"1328":1,"1337":1,"1338":1,"1339":3,"1350":1,"1359":11,"1369":1,"1378":1,"1379":1,"1383":1,"1395":5,"1396":3,"1402":1,"1406":1,"1414":1,"1415":2,"1416":1,"1417":1,"1436":2,"1467":2,"1589":1,"1596":1,"1906":1,"1922":1,"1933":1,"1977":1,"2000":1,"2154":1}}],["attach",{"3":{"0":1,"166":1,"175":1,"352":1,"599":1,"644":1,"832":2,"883":1,"1271":3,"1286":1,"1300":1,"1311":5,"1324":6,"1326":1,"1327":1,"1339":6,"1350":1,"1359":3,"1380":3,"1395":1,"1402":1,"1414":1,"1415":2,"1416":1,"1417":1,"1765":1,"1950":1,"1978":1,"1979":1,"2142":1}}],["attention",{"3":{"757":1,"1270":1,"1324":2,"1350":1,"1417":1,"1455":1,"2175":1}}],["attend",{"3":{"1359":1,"1630":1,"1637":1,"1638":1,"1641":1}}],["attended",{"3":{"689":1,"691":1,"1117":1}}],["attendee+speaker",{"3":{"1641":1}}],["attendee→403",{"3":{"1611":1}}],["attendeelookup",{"3":{"1396":1}}],["attendeelookupservicetests",{"3":{"1199":1,"1207":4,"1396":3}}],["attendeelookupservice",{"3":{"1199":3,"1203":1,"1207":3,"1396":19}}],["attendeename",{"3":{"1396":1}}],["attendeenotificationrecipientprovidertests",{"2":{"1487":1},"3":{"1199":1,"1207":2,"1438":1,"1487":1}}],["attendeenotificationrecipientprovider",{"2":{"1304":1,"1933":1},"3":{"1199":4,"1203":1,"1207":2,"1304":2,"1309":9,"1415":2,"1438":1,"1933":1}}],["attendeeprofiletests",{"3":{"1199":1,"1207":2,"1491":1}}],["attendeeclaims",{"2":{"1617":1},"3":{"1617":1}}],["attendeeclaimstests",{"3":{"1199":1,"1207":2,"1611":1}}],["attendeecheckedinpointshandlertests",{"3":{"1199":1,"1207":2,"1396":1}}],["attendeecheckedinpointshandler",{"3":{"1199":2,"1203":1,"1207":2,"1259":1,"1396":16}}],["attendeecheckedin",{"2":{"692":1,"694":1},"3":{"195":2,"692":1,"694":1,"1199":13,"1203":1,"1207":2,"1237":2,"1396":15,"1436":5,"1438":3,"1467":1,"1631":2,"1639":2}}],["attendeeauthtests",{"3":{"1199":1,"1207":3,"1491":1}}],["attendeeaccessdeniedtests",{"3":{"1199":2,"1207":4}}],["attendeequestionanswertests",{"3":{"1199":1,"1207":2}}],["attendeequeryservicebase",{"2":{"1415":1},"3":{"1415":2}}],["attendeequeryserviceclient",{"2":{"1415":1},"3":{"1415":2}}],["attendeequeryservicetests",{"3":{"1199":1,"1207":2}}],["attendeequeryservicegrpcadapter",{"2":{"1413":1},"3":{"1199":2,"1203":1,"1207":2,"1413":2,"1415":10}}],["attendeequeryservice",{"2":{"1309":1,"1415":2},"3":{"1199":7,"1203":1,"1207":2,"1309":1,"1413":2,"1415":13}}],["attendeefeedbacktests",{"3":{"1199":1,"1207":2}}],["attendeebookmarkedgecasetests",{"3":{"1496":1}}],["attendeebookmarktests",{"2":{"1611":1},"3":{"1199":2,"1207":4,"1611":1}}],["attendeebadges",{"3":{"1396":1}}],["attendeebadgetests",{"3":{"1199":1,"1207":2,"1396":2,"1438":1}}],["attendeebadgeinvariants",{"3":{"1199":2,"1203":1,"1207":2,"1396":5}}],["attendeebadgeconfiguration",{"3":{"690":1,"1199":1,"1203":1,"1207":2,"1396":11}}],["attendeebadge",{"3":{"690":3,"1199":11,"1203":1,"1207":2,"1396":24,"1438":1}}],["attendeerow",{"3":{"1199":2,"1203":1,"1207":1,"1396":4}}],["attendee",{"0":{"1356":1,"1623":1,"1626":1},"2":{"1621":1,"1626":1},"3":{"0":1,"243":1,"325":1,"350":1,"387":1,"545":1,"585":1,"586":1,"681":2,"683":1,"684":1,"688":1,"689":3,"690":5,"691":2,"692":4,"725":1,"799":1,"831":1,"853":1,"1115":1,"1117":1,"1124":1,"1192":2,"1207":20,"1247":2,"1300":3,"1302":1,"1304":2,"1305":1,"1308":1,"1309":8,"1312":2,"1324":1,"1340":1,"1349":1,"1350":13,"1351":1,"1359":20,"1369":5,"1373":1,"1377":1,"1380":9,"1381":1,"1391":1,"1392":1,"1395":20,"1396":234,"1401":2,"1402":26,"1405":2,"1406":1,"1412":1,"1413":3,"1415":34,"1416":1,"1436":4,"1438":13,"1441":1,"1487":3,"1496":2,"1608":1,"1611":5,"1621":6,"1624":1,"1625":1,"1626":2,"1627":7,"1629":7,"1630":7,"1631":8,"1632":44,"1637":5,"1638":8,"1639":3,"1641":8,"1814":1,"1922":1,"1933":1,"1977":1,"1995":1,"2240":1}}],["attendeesgrpcservicetests",{"3":{"1199":1,"1207":2,"1438":1}}],["attendeesgrpcservice",{"3":{"1199":2,"1203":1,"1207":2,"1413":2,"1415":6}}],["attendeesummary",{"3":{"1199":8,"1203":1,"1207":1,"1396":10}}],["attendeeshareandexporttests",{"3":{"1199":1,"1207":2,"1438":1}}],["attendeesearchfield",{"3":{"1199":2,"1203":1,"1207":1,"1396":4}}],["attendeesearchpanel",{"3":{"556":2,"881":2,"1199":3,"1203":1,"1207":2,"1324":3,"1396":21}}],["attendees",{"3":{"0":1,"224":1,"688":1,"689":1,"690":1,"692":1,"831":1,"832":1,"1116":2,"1124":1,"1207":2,"1309":3,"1350":5,"1359":6,"1367":1,"1377":1,"1380":2,"1392":2,"1395":7,"1396":25,"1397":1,"1402":3,"1415":2,"1438":2,"1626":1,"1627":1,"1629":2,"1630":7,"1631":11,"1632":1,"1635":1,"1636":1,"1637":2,"1638":4,"1639":8,"1640":2,"1641":3,"2110":1,"2216":1,"2232":1,"2238":1,"2239":2,"2240":1,"2241":1}}],["attendancestatsdto",{"3":{"1199":9,"1203":1,"1207":2,"1396":16}}],["attendance",{"2":{"1396":1},"3":{"0":1,"186":1,"689":1,"1150":1,"1192":1,"1203":2,"1207":4,"1343":1,"1350":2,"1359":1,"1396":54,"1415":3,"1438":3,"1487":1,"1496":1,"1625":1,"1627":2,"1629":1,"1631":1}}],["attests",{"3":{"1396":2,"1415":1,"1454":1}}],["attest",{"3":{"374":1,"530":1,"1454":1,"1484":1}}],["attestation",{"2":{"374":1,"1454":1},"3":{"372":1,"374":3,"1454":2,"1577":2}}],["attestations",{"0":{"374":1},"2":{"374":1,"1454":1},"3":{"368":1,"374":1,"1454":1,"1576":1}}],["attested",{"3":{"372":1,"374":1,"1359":1}}],["attempting",{"3":{"1467":1,"1631":1,"1632":3,"1941":1}}],["attempttimeout",{"3":{"1339":4}}],["attemptlifetime",{"3":{"1324":1}}],["attemptacquirecore",{"3":{"1311":2}}],["attemptoutputcaching",{"3":{"1311":1}}],["attempted",{"3":{"21":1,"201":1,"397":1,"409":1,"757":1,"758":1,"1259":2,"1270":2,"1300":1,"1301":2,"1309":1,"1311":1,"1323":2,"1324":6,"1415":1,"1417":2,"1632":3,"2192":1}}],["attemptskey",{"3":{"1300":1,"2000":1}}],["attempts",{"3":{"19":1,"22":1,"23":2,"72":1,"73":1,"282":2,"757":1,"759":1,"827":1,"854":1,"880":1,"881":1,"883":1,"987":1,"988":1,"998":1,"1042":1,"1212":1,"1259":5,"1270":1,"1273":1,"1275":2,"1286":8,"1292":1,"1300":12,"1301":2,"1309":1,"1311":2,"1318":2,"1323":7,"1324":5,"1330":1,"1339":10,"1359":4,"1396":1,"1438":1,"1447":1,"1455":2,"1459":1,"1537":1,"1631":2,"1632":1,"1639":2,"1641":5,"1665":1,"1666":1,"1840":1,"1896":1,"1911":2,"2000":1,"2001":1,"2002":1,"2032":1,"2074":1,"2186":1,"2188":1,"2191":1}}],["attemptunitofworkkey",{"3":{"1359":1}}],["attemptunitofwork",{"2":{"523":1},"3":{"0":1,"523":1,"926":1,"1270":1,"1359":2}}],["attempt",{"2":{"407":1,"1330":1},"3":{"0":7,"19":2,"21":2,"22":1,"24":3,"70":1,"73":3,"100":1,"120":1,"159":1,"199":1,"201":1,"279":1,"281":1,"282":1,"284":1,"286":1,"343":1,"353":2,"397":1,"407":1,"409":1,"434":1,"539":1,"715":1,"727":1,"736":1,"760":1,"813":1,"818":1,"819":1,"821":1,"827":1,"833":2,"849":1,"854":2,"855":1,"881":6,"882":2,"883":1,"920":1,"926":3,"988":6,"990":1,"991":1,"992":1,"996":1,"1018":1,"1019":1,"1042":4,"1043":1,"1045":1,"1059":1,"1099":2,"1212":1,"1229":1,"1256":2,"1258":1,"1259":6,"1265":1,"1270":10,"1274":1,"1278":1,"1286":16,"1289":1,"1292":1,"1294":2,"1295":1,"1300":32,"1304":2,"1309":3,"1311":2,"1312":5,"1317":1,"1323":10,"1324":35,"1330":3,"1339":13,"1352":1,"1355":1,"1359":20,"1366":1,"1369":1,"1378":1,"1380":1,"1389":2,"1395":14,"1396":9,"1402":2,"1408":1,"1413":1,"1415":8,"1417":7,"1427":1,"1435":1,"1436":3,"1438":6,"1443":1,"1455":1,"1457":1,"1492":2,"1496":1,"1547":1,"1577":1,"1600":1,"1631":1,"1638":1,"1641":4,"1664":1,"1668":1,"1765":1,"1767":1,"1840":4,"1841":1,"1843":2,"1933":1,"1976":2,"1998":1,"2001":1,"2006":1,"2030":2,"2032":1,"2165":1,"2167":1,"2182":2,"2188":2,"2190":1,"2192":1,"2198":1}}],["at",{"0":{"1336":1,"1339":1,"1369":1,"1373":1,"1466":1,"1523":1,"1530":1,"1574":1,"1581":1,"1588":1,"1595":1,"1687":1,"1702":1,"1873":1,"1903":1,"1935":2,"2046":1,"2150":1,"2151":1,"2178":1},"1":{"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"294":1,"295":1,"296":1,"356":1,"357":1,"358":1,"359":1,"360":1,"361":1,"362":1,"363":1,"364":1,"365":1,"366":1,"1009":1,"1010":1,"1011":1,"1012":1,"1013":1,"1014":1,"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1},"2":{"626":1},"3":{"0":116,"5":1,"6":2,"10":1,"15":2,"17":1,"19":10,"20":1,"21":1,"22":4,"23":2,"24":30,"25":2,"26":4,"27":21,"29":1,"31":5,"32":1,"34":4,"39":8,"41":1,"43":1,"46":2,"53":1,"54":1,"57":3,"58":1,"59":8,"60":1,"61":1,"62":3,"63":1,"66":2,"67":1,"68":1,"69":1,"70":1,"71":4,"72":7,"73":2,"74":1,"76":2,"80":2,"81":5,"85":1,"91":2,"92":1,"93":4,"94":2,"95":6,"96":1,"99":3,"100":3,"102":1,"103":2,"104":1,"107":1,"108":1,"109":31,"110":5,"111":6,"115":7,"117":1,"118":1,"119":1,"121":4,"122":21,"125":2,"126":4,"127":1,"128":1,"130":7,"131":1,"132":2,"133":2,"135":19,"136":3,"137":5,"138":5,"139":7,"140":2,"141":3,"142":6,"145":2,"146":1,"147":3,"148":2,"149":3,"150":10,"155":1,"156":2,"157":2,"158":1,"159":1,"160":6,"161":4,"162":1,"164":1,"166":8,"168":1,"170":10,"173":1,"175":3,"176":1,"177":2,"178":8,"179":8,"180":8,"184":1,"186":10,"189":6,"190":1,"191":1,"193":5,"194":3,"195":2,"198":2,"199":2,"200":8,"201":7,"202":7,"203":2,"206":1,"207":4,"208":1,"209":2,"212":1,"213":1,"214":2,"215":1,"216":1,"218":1,"219":4,"220":2,"224":2,"225":4,"226":2,"228":2,"230":10,"231":3,"234":1,"235":10,"237":3,"238":2,"241":5,"242":2,"243":11,"244":1,"245":3,"246":4,"250":2,"251":3,"252":2,"253":1,"254":4,"255":3,"256":11,"257":11,"258":8,"259":10,"260":7,"261":16,"265":10,"266":1,"273":1,"276":2,"279":1,"280":2,"282":1,"283":2,"284":3,"288":1,"290":4,"291":1,"295":2,"298":2,"299":1,"300":3,"303":2,"305":9,"306":1,"309":1,"310":3,"311":1,"312":3,"313":1,"314":6,"318":8,"319":1,"321":1,"325":4,"326":5,"328":1,"330":8,"331":2,"333":4,"334":1,"335":3,"336":1,"337":1,"341":5,"343":2,"346":1,"349":1,"350":7,"351":2,"352":5,"353":6,"354":2,"356":1,"358":1,"359":9,"360":3,"361":4,"362":1,"364":1,"365":2,"366":2,"368":1,"369":1,"371":1,"373":9,"375":1,"379":1,"380":2,"384":3,"386":1,"387":1,"388":1,"389":1,"390":13,"391":6,"392":1,"393":9,"395":9,"397":26,"398":1,"399":4,"400":6,"401":10,"402":4,"403":8,"405":2,"407":7,"408":3,"409":2,"412":2,"413":2,"414":1,"415":1,"416":1,"418":8,"420":3,"422":1,"423":10,"424":3,"425":5,"426":16,"427":4,"428":27,"429":16,"430":7,"433":1,"434":1,"435":2,"436":1,"441":1,"442":1,"443":1,"444":2,"448":7,"451":1,"453":1,"454":1,"457":1,"459":11,"460":1,"461":3,"463":16,"464":11,"465":5,"466":4,"468":4,"469":1,"470":1,"471":2,"472":1,"475":1,"476":3,"477":1,"478":2,"483":3,"484":1,"486":6,"487":6,"488":4,"489":6,"490":20,"491":16,"492":17,"493":12,"494":1,"495":4,"497":4,"499":11,"500":1,"501":2,"502":1,"506":1,"508":1,"511":1,"512":1,"513":1,"514":1,"516":1,"520":2,"522":15,"523":20,"524":10,"528":2,"529":1,"532":1,"534":1,"535":4,"536":9,"537":2,"538":6,"539":9,"540":4,"543":5,"545":17,"546":1,"547":1,"549":17,"550":1,"551":1,"552":13,"555":2,"556":7,"557":2,"559":1,"563":1,"564":8,"565":1,"568":1,"570":1,"571":1,"572":2,"574":1,"575":1,"576":1,"577":1,"579":1,"583":6,"585":5,"586":2,"587":1,"590":1,"591":8,"594":3,"595":1,"598":2,"599":10,"600":1,"601":5,"602":8,"603":6,"604":6,"605":1,"607":1,"608":4,"609":48,"611":2,"612":8,"613":2,"617":1,"618":17,"619":1,"620":1,"622":1,"625":1,"626":11,"627":2,"628":4,"629":1,"631":4,"633":8,"635":3,"636":2,"638":1,"640":26,"641":2,"642":2,"643":1,"644":1,"645":1,"648":1,"649":10,"651":1,"652":1,"656":2,"657":15,"658":3,"664":1,"665":13,"666":2,"667":1,"668":7,"673":3,"674":21,"675":3,"676":8,"678":1,"682":4,"683":1,"684":5,"689":1,"690":13,"691":3,"692":5,"693":1,"694":1,"696":3,"697":3,"698":14,"699":2,"700":3,"702":3,"703":2,"706":1,"707":2,"708":2,"709":4,"711":1,"713":1,"714":1,"715":6,"716":2,"717":4,"719":4,"720":1,"721":1,"724":1,"725":3,"728":1,"729":2,"732":4,"733":9,"735":1,"740":6,"741":16,"743":9,"744":3,"745":1,"749":10,"751":2,"752":2,"753":2,"757":8,"758":1,"760":5,"765":3,"766":7,"767":3,"768":2,"769":1,"773":1,"774":5,"776":1,"782":6,"789":5,"790":10,"791":2,"792":3,"793":6,"794":1,"795":1,"797":3,"798":1,"799":4,"800":3,"801":1,"803":2,"804":2,"805":1,"809":2,"810":5,"812":3,"814":1,"815":2,"818":2,"819":10,"820":1,"821":5,"822":2,"823":1,"825":2,"826":5,"827":43,"828":2,"829":4,"830":9,"831":5,"832":13,"833":3,"837":3,"838":12,"840":2,"844":1,"846":4,"847":2,"848":2,"849":3,"850":6,"853":4,"854":3,"855":1,"857":2,"860":2,"861":15,"862":1,"863":3,"865":1,"867":3,"869":1,"870":3,"871":2,"872":2,"873":2,"874":1,"875":3,"876":2,"881":18,"883":4,"884":2,"888":1,"889":6,"891":4,"893":1,"896":2,"897":17,"898":2,"900":1,"904":1,"905":61,"906":33,"907":2,"908":1,"913":8,"914":17,"915":5,"916":3,"917":1,"918":1,"920":1,"921":1,"922":1,"923":2,"924":2,"925":1,"926":11,"930":2,"931":14,"933":2,"937":1,"938":1,"940":3,"942":1,"945":2,"946":10,"947":1,"948":4,"949":2,"950":1,"952":2,"954":1,"956":1,"958":1,"959":2,"960":1,"963":1,"964":10,"965":1,"966":3,"971":3,"972":11,"974":1,"979":4,"980":14,"981":1,"982":2,"984":1,"987":2,"988":1,"990":2,"991":1,"995":5,"996":8,"997":1,"998":3,"1000":1,"1004":9,"1005":3,"1006":2,"1008":1,"1009":1,"1012":9,"1014":3,"1016":1,"1017":4,"1018":44,"1019":5,"1020":5,"1023":1,"1026":6,"1027":2,"1028":4,"1033":3,"1034":3,"1037":1,"1042":6,"1044":4,"1045":2,"1048":2,"1049":1,"1050":2,"1051":1,"1052":3,"1054":2,"1058":1,"1059":7,"1060":1,"1062":2,"1066":1,"1067":6,"1068":1,"1069":3,"1074":6,"1075":12,"1077":1,"1078":2,"1082":6,"1083":2,"1084":2,"1086":2,"1087":1,"1089":2,"1090":6,"1091":37,"1092":3,"1093":4,"1094":1,"1095":4,"1099":2,"1100":27,"1101":2,"1102":3,"1103":4,"1107":2,"1108":6,"1109":1,"1110":1,"1112":1,"1115":1,"1116":19,"1118":1,"1123":1,"1124":13,"1125":3,"1126":4,"1129":1,"1132":3,"1133":7,"1134":3,"1135":5,"1136":1,"1140":7,"1142":1,"1146":3,"1147":1,"1150":5,"1156":2,"1160":4,"1161":1,"1162":1,"1163":1,"1168":9,"1169":2,"1173":2,"1174":1,"1175":4,"1176":2,"1177":1,"1182":2,"1184":4,"1185":1,"1186":1,"1187":1,"1192":1,"1194":1,"1202":1,"1203":1,"1212":28,"1213":2,"1214":3,"1215":1,"1216":2,"1220":3,"1221":1,"1222":2,"1225":2,"1227":1,"1228":3,"1229":31,"1231":8,"1232":6,"1233":6,"1234":6,"1236":5,"1237":72,"1238":2,"1239":7,"1240":13,"1241":27,"1242":16,"1243":8,"1244":7,"1246":4,"1247":114,"1248":2,"1249":1,"1251":1,"1252":2,"1253":2,"1254":3,"1255":2,"1256":4,"1257":7,"1258":2,"1259":126,"1260":1,"1261":2,"1262":6,"1263":10,"1264":15,"1265":6,"1266":1,"1267":4,"1268":3,"1269":4,"1270":178,"1271":48,"1273":26,"1274":10,"1275":10,"1276":3,"1277":12,"1278":12,"1279":10,"1280":1,"1281":10,"1282":9,"1283":2,"1284":7,"1285":4,"1286":465,"1288":2,"1289":2,"1290":2,"1291":3,"1292":3,"1293":1,"1294":2,"1297":8,"1298":3,"1300":302,"1301":62,"1302":5,"1304":2,"1305":1,"1306":1,"1307":2,"1309":86,"1310":34,"1311":383,"1312":31,"1313":1,"1314":5,"1315":3,"1316":6,"1317":4,"1318":7,"1320":7,"1322":1,"1323":244,"1324":375,"1325":2,"1326":10,"1327":5,"1328":3,"1329":1,"1330":8,"1331":3,"1332":8,"1333":15,"1334":6,"1335":4,"1336":5,"1337":4,"1338":12,"1339":215,"1341":1,"1342":9,"1343":12,"1344":13,"1345":4,"1346":1,"1347":5,"1348":7,"1349":4,"1350":214,"1351":1,"1352":9,"1353":7,"1354":7,"1355":6,"1356":2,"1357":6,"1358":10,"1359":670,"1360":1,"1362":11,"1363":3,"1364":2,"1365":1,"1366":4,"1368":1,"1369":79,"1371":3,"1372":1,"1373":8,"1374":5,"1375":2,"1376":2,"1377":2,"1378":1,"1379":5,"1380":138,"1382":6,"1383":12,"1384":5,"1385":4,"1386":2,"1387":13,"1388":6,"1389":17,"1390":8,"1391":15,"1392":10,"1393":9,"1394":2,"1395":321,"1396":436,"1397":1,"1398":7,"1399":5,"1400":7,"1401":3,"1402":173,"1404":6,"1405":4,"1406":11,"1407":1,"1408":1,"1409":9,"1410":6,"1411":3,"1412":1,"1413":6,"1414":2,"1415":221,"1416":63,"1417":146,"1419":7,"1420":4,"1421":5,"1422":1,"1423":1,"1424":3,"1425":3,"1427":33,"1429":17,"1430":5,"1431":20,"1432":4,"1433":13,"1434":5,"1435":4,"1436":545,"1437":2,"1438":54,"1439":2,"1440":2,"1441":22,"1442":8,"1443":19,"1444":6,"1445":3,"1446":3,"1447":20,"1448":1,"1449":4,"1450":2,"1451":1,"1452":2,"1453":8,"1454":8,"1455":28,"1456":5,"1457":5,"1458":4,"1459":2,"1460":11,"1461":2,"1463":2,"1464":2,"1465":4,"1466":4,"1467":92,"1468":3,"1469":2,"1470":1,"1472":2,"1473":6,"1474":8,"1475":10,"1476":12,"1477":4,"1478":4,"1479":2,"1480":5,"1481":2,"1482":2,"1483":1,"1484":1,"1486":13,"1487":1,"1488":29,"1489":15,"1490":17,"1491":7,"1492":30,"1494":1,"1496":64,"1497":2,"1498":1,"1499":1,"1500":6,"1503":1,"1504":1,"1508":1,"1509":1,"1510":1,"1513":1,"1517":1,"1522":2,"1523":1,"1524":6,"1525":5,"1526":90,"1527":5,"1529":1,"1530":4,"1531":14,"1532":4,"1534":19,"1535":5,"1537":2,"1540":1,"1544":2,"1546":2,"1547":1,"1548":2,"1549":1,"1550":1,"1551":2,"1552":1,"1553":1,"1554":1,"1556":1,"1565":1,"1566":1,"1568":1,"1570":1,"1572":1,"1573":2,"1574":5,"1575":3,"1576":2,"1577":71,"1578":7,"1580":1,"1581":1,"1582":15,"1583":7,"1584":3,"1585":12,"1586":22,"1587":2,"1588":2,"1589":3,"1590":2,"1591":102,"1592":2,"1594":1,"1596":15,"1597":15,"1598":4,"1599":12,"1600":12,"1611":1,"1614":1,"1621":1,"1626":2,"1627":9,"1630":5,"1631":12,"1632":5,"1635":1,"1637":2,"1638":5,"1639":4,"1640":1,"1641":12,"1646":4,"1649":2,"1651":4,"1652":2,"1653":7,"1654":1,"1655":1,"1656":2,"1659":1,"1660":2,"1661":4,"1662":1,"1663":1,"1664":2,"1665":4,"1666":1,"1668":4,"1669":3,"1670":5,"1671":1,"1672":3,"1673":4,"1675":1,"1677":1,"1678":1,"1683":1,"1684":3,"1685":7,"1686":10,"1687":1,"1689":3,"1690":2,"1693":2,"1696":1,"1698":1,"1699":1,"1700":3,"1701":1,"1702":1,"1705":1,"1707":1,"1708":1,"1719":2,"1720":1,"1721":2,"1722":1,"1724":1,"1727":7,"1728":3,"1729":4,"1731":2,"1738":1,"1740":1,"1747":1,"1748":5,"1749":3,"1750":1,"1755":1,"1757":1,"1758":1,"1760":3,"1761":3,"1764":4,"1765":10,"1767":6,"1769":3,"1776":8,"1777":1,"1780":1,"1781":1,"1782":2,"1783":1,"1784":3,"1788":1,"1789":1,"1790":2,"1791":1,"1793":1,"1794":3,"1795":1,"1796":4,"1797":3,"1799":3,"1800":1,"1803":1,"1804":2,"1805":4,"1806":1,"1807":2,"1810":7,"1811":1,"1813":1,"1814":1,"1815":9,"1817":1,"1818":2,"1820":1,"1821":4,"1824":1,"1825":2,"1826":4,"1827":2,"1829":3,"1830":1,"1831":1,"1832":2,"1833":2,"1834":2,"1835":1,"1837":1,"1839":2,"1840":1,"1841":2,"1842":4,"1843":2,"1845":1,"1846":1,"1848":3,"1849":3,"1851":3,"1852":2,"1854":3,"1856":2,"1857":1,"1858":1,"1862":1,"1864":1,"1865":3,"1866":1,"1867":1,"1868":1,"1869":2,"1874":1,"1876":1,"1877":2,"1879":1,"1882":1,"1884":4,"1885":2,"1886":3,"1888":1,"1890":2,"1891":2,"1892":2,"1893":1,"1894":1,"1895":3,"1896":1,"1899":2,"1900":1,"1902":4,"1903":2,"1905":4,"1908":4,"1909":3,"1910":2,"1912":4,"1914":1,"1915":2,"1917":2,"1918":1,"1919":1,"1920":2,"1922":2,"1923":3,"1924":2,"1928":1,"1929":3,"1933":1,"1934":2,"1935":2,"1936":1,"1937":1,"1938":1,"1939":1,"1940":1,"1941":2,"1942":2,"1943":1,"1944":3,"1946":1,"1947":2,"1948":5,"1949":1,"1950":4,"1951":2,"1952":2,"1953":1,"1954":2,"1955":2,"1956":4,"1959":2,"1960":1,"1962":2,"1963":2,"1964":1,"1966":1,"1967":1,"1968":2,"1970":4,"1971":3,"1972":1,"1973":2,"1975":5,"1977":2,"1978":2,"1979":2,"1982":10,"1983":1,"1984":1,"1988":4,"1990":1,"1992":3,"1994":6,"1995":10,"1997":1,"1998":3,"1999":1,"2000":13,"2001":4,"2002":2,"2003":1,"2004":1,"2005":1,"2007":2,"2008":1,"2010":3,"2011":1,"2012":6,"2013":1,"2014":4,"2016":1,"2019":1,"2024":6,"2026":1,"2027":1,"2029":3,"2030":2,"2031":1,"2032":5,"2033":2,"2034":3,"2036":1,"2040":1,"2041":1,"2042":1,"2043":4,"2044":4,"2046":4,"2047":2,"2048":3,"2049":2,"2050":3,"2051":2,"2054":1,"2055":2,"2057":2,"2058":3,"2059":3,"2062":1,"2063":5,"2064":1,"2066":8,"2067":1,"2069":1,"2070":2,"2071":1,"2072":1,"2073":1,"2075":1,"2077":2,"2080":2,"2082":2,"2083":2,"2085":3,"2086":7,"2089":1,"2090":1,"2091":1,"2093":1,"2095":2,"2097":2,"2098":1,"2099":2,"2100":2,"2102":1,"2106":1,"2108":1,"2109":1,"2111":3,"2112":2,"2114":3,"2115":3,"2116":1,"2118":5,"2120":2,"2121":1,"2123":2,"2126":2,"2127":1,"2128":3,"2131":1,"2134":3,"2135":2,"2137":2,"2138":4,"2139":6,"2140":1,"2141":2,"2142":5,"2143":1,"2144":1,"2145":3,"2147":3,"2148":1,"2149":2,"2150":6,"2151":2,"2153":2,"2155":1,"2156":12,"2157":6,"2158":23,"2159":3,"2160":1,"2161":1,"2162":5,"2163":2,"2164":5,"2165":1,"2166":11,"2167":3,"2168":7,"2169":2,"2171":4,"2172":6,"2173":3,"2174":2,"2175":5,"2177":1,"2178":2,"2182":3,"2188":1,"2189":3,"2192":5,"2193":1,"2194":1,"2195":3,"2196":4,"2197":3,"2199":3,"2200":2,"2201":3,"2202":2,"2204":2,"2208":1,"2211":1,"2212":1,"2214":2,"2216":1,"2220":1,"2221":2,"2227":2,"2228":1,"2230":4,"2232":22,"2234":1,"2242":1,"2244":1}}],["angular",{"3":{"2219":1,"2220":5}}],["angles",{"3":{"1359":1,"1436":1}}],["anglesharp",{"2":{"952":1,"956":1,"958":1,"959":1},"3":{"0":1,"952":1,"954":1,"956":1,"958":2,"959":1,"1436":2,"2232":1}}],["angle",{"3":{"1018":2,"1020":1,"1161":1,"1237":1,"1249":1,"1300":1,"1311":2,"1312":1,"1366":1,"1395":1,"1436":8,"1526":1,"2121":1}}],["anemic",{"1":{"1808":1,"1809":1,"1810":1,"1811":1,"1812":1},"3":{"1539":1,"1541":1,"1577":1,"1779":1,"1807":1,"1808":2,"1810":1,"1811":1,"1812":2,"1828":1,"1834":1,"2206":1}}],["anupcasterwhosesourceisanotherupcasterstarget",{"3":{"1436":1}}],["anundecoratedendpointthatisallowlisted",{"3":{"1436":3}}],["aninheritedattributeonthederivedcontroller",{"3":{"1436":3}}],["animates",{"3":{"1417":1}}],["animated",{"3":{"1323":1,"1438":1}}],["animation",{"3":{"1359":1,"1433":1,"1436":1}}],["anr",{"3":{"1416":1,"1417":1}}],["anabstracthandlerstrandedfromitscontract",{"3":{"1436":3}}],["anabstracthandlerbesideitscontract",{"3":{"1436":3}}],["anabstracthandleroveragenericcontract",{"3":{"1436":3}}],["anaemic",{"3":{"1359":1}}],["anatomy",{"0":{"1352":1}}],["analogous",{"3":{"1323":1}}],["analogue",{"3":{"265":2,"1324":2,"1339":1,"1395":1,"1402":1,"1417":3,"1436":2,"1938":1}}],["analog",{"3":{"135":1,"1707":1,"2220":1}}],["analyser",{"3":{"1436":1}}],["analyses",{"3":{"1350":3,"1601":1}}],["analysismode=all",{"3":{"869":1,"1445":1,"1577":1}}],["analysis",{"1":{"1745":1,"1746":1,"1747":1,"1748":1,"1749":1,"1750":1,"1751":1,"1752":1,"1753":1,"1754":1,"1755":1},"3":{"0":2,"160":1,"360":1,"546":1,"1033":1,"1049":1,"1350":11,"1380":1,"1395":3,"1396":2,"1417":1,"1436":3,"1453":1,"1467":1,"1496":1,"1566":1,"1577":1,"1586":1,"1603":1,"1639":1,"1679":1,"1745":1,"1755":1,"1815":1,"2219":1}}],["analyze",{"3":{"1359":3}}],["analyzed",{"3":{"1247":1,"1627":1}}],["analyzes",{"3":{"1239":1,"1247":1,"1347":1,"1436":1}}],["analyzers",{"2":{"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"495":1,"2089":1},"3":{"0":1,"138":1,"481":1,"483":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":2,"495":1,"633":1,"982":2,"1170":1,"1213":4,"1214":1,"1229":1,"1237":2,"1247":2,"1286":1,"1309":1,"1359":2,"1395":1,"1396":1,"1453":2,"1524":1,"1526":2,"1531":1,"1537":1,"1552":2,"1554":1,"1570":1,"1572":1,"1577":2,"1586":1,"1591":4,"1596":2,"1597":1,"1649":1,"1654":1,"1657":1,"1661":1,"1673":1,"1683":1,"1685":1,"1696":1,"1704":1,"1719":1,"1726":1,"1781":1,"1797":1,"1804":1,"1903":1,"1905":1,"2089":1,"2131":1}}],["analyzer",{"2":{"135":1,"138":1,"1526":1},"3":{"0":3,"135":5,"138":1,"139":1,"140":1,"142":2,"265":1,"454":1,"482":2,"484":2,"486":1,"490":2,"491":2,"492":2,"493":2,"494":1,"497":2,"502":1,"558":1,"591":1,"631":2,"633":4,"776":1,"801":1,"869":1,"899":1,"980":3,"981":1,"1004":1,"1102":1,"1108":1,"1213":1,"1229":1,"1237":2,"1247":1,"1270":1,"1286":2,"1300":6,"1309":1,"1311":4,"1312":3,"1323":4,"1324":5,"1339":7,"1359":3,"1369":1,"1373":1,"1395":2,"1396":2,"1402":1,"1416":3,"1417":3,"1436":6,"1441":1,"1445":1,"1453":2,"1455":1,"1488":1,"1492":1,"1526":1,"1577":3,"1582":1,"1595":1,"1596":1,"1673":3,"1688":1,"1689":1,"1727":1,"1731":1,"1825":2,"1897":1,"2001":1,"2086":1,"2089":2,"2232":1}}],["analytical",{"3":{"0":1,"1012":1,"1359":2,"1395":2,"1526":2,"1545":1,"1577":1}}],["analytics",{"0":{"1359":1},"2":{"1465":1},"3":{"0":2,"395":1,"601":1,"604":1,"609":4,"718":1,"768":1,"914":1,"974":1,"1192":1,"1202":1,"1203":1,"1339":1,"1350":1,"1351":1,"1359":4,"1436":1,"1447":1,"1455":1,"1465":6,"1467":7,"1474":1,"1476":1,"1525":1,"1526":1,"1534":2,"1545":1,"1591":1,"1611":2,"1621":1,"1625":1,"1630":1,"1634":2,"1638":1,"1641":3,"1670":1,"1677":2,"1678":1,"1685":1,"1835":1,"2158":1,"2160":1,"2219":1,"2220":1,"2221":1,"2232":1}}],["ansi",{"3":{"741":1,"1311":2}}],["answerid",{"3":{"1359":2,"1395":6,"1396":1}}],["answering",{"3":{"0":4,"72":1,"99":1,"157":1,"234":1,"237":1,"284":1,"342":1,"353":3,"698":1,"735":1,"758":1,"790":1,"826":1,"827":1,"879":1,"881":1,"906":1,"1075":1,"1099":1,"1237":2,"1270":1,"1286":4,"1293":1,"1300":3,"1309":1,"1311":4,"1323":2,"1324":3,"1328":1,"1339":3,"1350":2,"1359":4,"1373":1,"1378":1,"1380":4,"1395":2,"1396":4,"1415":2,"1417":2,"1418":1,"1436":1,"1441":1,"1474":1,"1476":1,"1526":1,"1600":1,"1670":1,"1795":1,"1909":1,"2170":1,"2175":1}}],["answervaluemaxlength",{"3":{"1350":4,"1359":2,"1369":3,"1396":5}}],["answervalue",{"3":{"1350":23,"1359":39,"1369":3,"1380":16,"1396":5,"1630":3,"1631":2,"1632":2,"1635":3,"1639":1,"1640":2}}],["answerable",{"3":{"39":1,"402":1,"498":1,"1237":1,"1286":2,"1415":1,"1417":1,"1764":1}}],["answered",{"3":{"0":3,"109":1,"126":1,"176":1,"243":1,"286":1,"341":1,"399":1,"639":1,"690":1,"698":1,"741":1,"743":2,"757":1,"759":1,"809":1,"818":1,"854":1,"857":1,"882":1,"896":1,"901":1,"903":1,"905":1,"907":1,"922":1,"930":1,"996":2,"1049":1,"1052":1,"1055":1,"1066":1,"1093":1,"1142":1,"1145":1,"1153":1,"1184":4,"1263":1,"1270":1,"1286":2,"1290":1,"1300":12,"1301":4,"1311":9,"1313":1,"1320":1,"1324":18,"1328":1,"1337":1,"1339":2,"1350":6,"1359":33,"1380":6,"1387":1,"1395":10,"1396":21,"1397":1,"1401":1,"1402":21,"1408":1,"1415":3,"1417":2,"1427":1,"1430":1,"1436":3,"1441":1,"1455":1,"1526":1,"1534":1,"1630":1,"1632":3,"1638":1,"1641":1,"1748":1,"1765":1,"1810":1,"1815":1,"1821":1,"1851":1,"1857":1,"1912":1,"1916":1,"1933":1,"1984":1,"1992":1}}],["answer",{"0":{"93":1,"1423":1,"1790":1,"1805":1,"1810":1,"1815":1,"1849":1,"1865":1,"1871":1,"1889":1,"1895":1,"1902":1,"1927":1,"1944":1,"1962":1,"1968":1,"1975":1,"1982":1,"1988":1,"1994":1,"2000":1,"2063":1,"2083":1,"2117":1,"2126":1,"2131":1,"2136":1,"2142":1,"2149":1,"2156":1,"2163":1,"2172":1,"2184":1,"2196":1},"2":{"1335":1},"3":{"0":6,"26":1,"52":1,"81":1,"93":2,"94":2,"101":1,"126":1,"146":1,"166":2,"185":1,"186":1,"194":1,"202":1,"212":1,"235":2,"245":1,"265":2,"283":1,"284":1,"301":1,"325":1,"340":1,"342":2,"343":1,"344":1,"365":2,"390":1,"391":1,"392":1,"396":1,"434":1,"435":1,"497":1,"535":1,"536":1,"537":1,"539":1,"544":1,"573":2,"574":1,"577":1,"582":1,"583":1,"625":2,"673":1,"688":1,"689":1,"690":5,"693":2,"697":1,"706":1,"714":3,"717":3,"724":1,"725":1,"726":3,"740":1,"741":2,"742":1,"743":1,"756":1,"767":1,"773":1,"781":1,"783":2,"809":2,"813":1,"818":1,"821":1,"827":4,"829":1,"832":1,"833":1,"837":1,"896":2,"898":2,"905":1,"921":1,"926":2,"942":1,"946":1,"947":1,"953":1,"955":1,"963":1,"965":1,"995":1,"996":1,"1016":1,"1018":3,"1019":2,"1021":2,"1025":1,"1026":1,"1028":1,"1035":1,"1041":1,"1043":2,"1049":1,"1066":3,"1074":1,"1089":1,"1091":2,"1092":1,"1093":4,"1115":1,"1117":2,"1123":1,"1124":1,"1126":1,"1132":1,"1153":1,"1154":1,"1175":2,"1177":1,"1178":1,"1220":1,"1222":1,"1228":1,"1229":1,"1232":1,"1235":1,"1237":4,"1239":1,"1247":5,"1248":3,"1250":1,"1253":2,"1259":5,"1263":2,"1265":1,"1267":1,"1270":18,"1271":6,"1273":1,"1276":1,"1280":1,"1286":33,"1296":1,"1300":25,"1301":2,"1307":1,"1308":1,"1309":5,"1310":1,"1311":14,"1312":6,"1313":1,"1318":2,"1320":1,"1323":14,"1324":49,"1328":1,"1331":1,"1332":1,"1335":1,"1337":2,"1338":1,"1339":25,"1340":1,"1344":1,"1346":1,"1348":1,"1349":3,"1350":57,"1351":1,"1352":5,"1353":1,"1355":5,"1357":2,"1359":241,"1360":1,"1362":1,"1366":5,"1369":12,"1372":8,"1373":4,"1374":3,"1380":42,"1382":1,"1388":1,"1395":36,"1396":59,"1402":12,"1405":1,"1406":2,"1408":2,"1409":2,"1414":1,"1415":18,"1417":23,"1418":3,"1422":1,"1423":7,"1424":2,"1426":1,"1427":18,"1431":1,"1435":4,"1436":22,"1438":10,"1441":1,"1447":2,"1448":1,"1449":3,"1455":4,"1456":1,"1465":1,"1467":1,"1476":1,"1482":1,"1487":2,"1492":3,"1496":1,"1526":3,"1577":1,"1585":1,"1629":1,"1630":3,"1631":12,"1632":14,"1635":7,"1638":4,"1639":7,"1640":4,"1653":1,"1656":1,"1667":1,"1670":1,"1672":1,"1673":1,"1681":1,"1702":1,"1708":1,"1718":1,"1720":1,"1722":1,"1727":1,"1728":1,"1729":1,"1790":2,"1815":1,"1819":1,"1821":1,"1839":1,"1847":1,"1854":1,"1859":1,"1870":1,"1874":1,"1877":1,"1888":1,"1891":1,"1892":1,"1893":2,"1902":2,"1905":1,"1908":1,"1909":1,"1910":1,"1911":1,"1912":2,"1926":1,"1933":1,"1934":1,"1940":1,"1941":1,"1947":2,"1948":2,"1954":2,"1963":1,"1967":1,"1970":1,"1972":1,"1980":1,"1981":1,"1982":2,"1985":1,"1992":1,"1996":1,"1997":2,"2000":1,"2001":1,"2002":1,"2024":1,"2035":1,"2036":1,"2055":1,"2063":1,"2067":1,"2069":1,"2078":1,"2143":1,"2144":1,"2154":1,"2159":2,"2160":1,"2163":1,"2167":1,"2168":1,"2170":2,"2175":2,"2177":1,"2179":2,"2181":1,"2183":1,"2185":1,"2191":1,"2194":1}}],["answers",{"0":{"1280":1},"3":{"0":13,"24":1,"40":2,"41":1,"81":2,"92":1,"94":1,"96":1,"97":1,"109":1,"110":1,"111":1,"235":2,"236":3,"317":1,"318":1,"320":1,"324":1,"326":1,"333":2,"340":1,"343":1,"344":1,"353":1,"369":1,"499":1,"507":1,"524":1,"545":1,"549":1,"571":2,"583":1,"585":3,"590":1,"650":1,"652":1,"714":1,"725":1,"727":1,"732":1,"741":1,"743":1,"749":1,"767":1,"818":1,"827":5,"828":1,"853":1,"860":1,"876":1,"881":3,"905":3,"907":1,"922":1,"925":1,"926":2,"932":1,"938":1,"946":1,"948":1,"953":1,"956":2,"963":1,"966":1,"998":1,"1016":1,"1018":5,"1020":2,"1021":1,"1022":2,"1024":1,"1028":1,"1034":2,"1041":1,"1059":3,"1066":1,"1067":3,"1082":1,"1091":2,"1092":1,"1099":1,"1103":2,"1116":1,"1132":1,"1142":3,"1175":1,"1176":1,"1178":1,"1183":1,"1184":2,"1185":1,"1228":1,"1229":5,"1231":1,"1237":3,"1238":1,"1247":2,"1250":1,"1256":1,"1259":3,"1265":1,"1270":10,"1271":7,"1272":1,"1279":1,"1283":1,"1286":27,"1290":1,"1294":1,"1297":1,"1300":31,"1301":5,"1305":1,"1308":1,"1309":5,"1311":17,"1312":4,"1313":1,"1322":1,"1323":11,"1324":42,"1328":3,"1332":2,"1337":1,"1339":14,"1342":4,"1343":2,"1344":1,"1346":1,"1347":1,"1349":1,"1350":29,"1352":3,"1355":3,"1357":1,"1359":104,"1366":2,"1369":3,"1372":2,"1373":1,"1375":2,"1380":30,"1384":1,"1386":1,"1388":1,"1390":2,"1391":2,"1395":38,"1396":79,"1398":1,"1401":1,"1402":11,"1408":1,"1411":1,"1414":1,"1415":19,"1416":1,"1417":9,"1421":1,"1423":2,"1426":1,"1427":2,"1430":3,"1432":1,"1435":1,"1436":18,"1438":8,"1441":1,"1447":1,"1453":2,"1454":1,"1467":1,"1488":2,"1490":1,"1492":2,"1496":1,"1534":2,"1535":1,"1567":1,"1577":2,"1627":1,"1630":3,"1631":7,"1632":10,"1635":3,"1638":2,"1639":14,"1640":4,"1641":2,"1651":1,"1666":1,"1670":1,"1708":1,"1720":1,"1748":1,"1767":2,"1769":1,"1770":1,"1794":1,"1806":1,"1819":1,"1821":1,"1839":1,"1863":1,"1870":1,"1892":2,"1896":1,"1902":1,"1909":1,"1920":1,"1922":1,"1923":1,"1927":1,"1931":1,"1934":1,"1947":1,"1962":1,"1991":1,"1992":2,"1995":1,"2024":2,"2029":1,"2036":1,"2056":1,"2063":2,"2073":1,"2109":1,"2110":1,"2141":2,"2149":1,"2156":1,"2167":1,"2176":1,"2180":2,"2183":2,"2190":1,"2199":1,"2200":1}}],["annual",{"3":{"361":1,"1286":1,"1467":1,"1481":1,"2145":1}}],["annoying",{"3":{"2070":1}}],["announcing",{"3":{"1212":1,"1259":1,"1417":3,"2139":1}}],["announceasync",{"3":{"1417":4}}],["announceable",{"3":{"1309":1}}],["announcer",{"3":{"1396":1,"1417":2,"1669":1}}],["announcement",{"3":{"619":1,"620":1,"1324":2,"1359":2,"1396":5,"1410":1,"1415":4,"1417":8,"1526":1,"1532":1,"1642":1,"1644":1,"2116":1}}],["announcements",{"3":{"384":1,"433":1,"1402":2,"1417":7,"1605":1,"1739":1,"1936":1}}],["announce",{"3":{"573":1,"1212":1,"1324":1,"1350":1,"1396":1,"1402":2,"1415":1,"1417":5,"1436":1,"1607":4,"1608":1,"1741":4,"1742":2,"1835":1,"2230":1}}],["announces",{"3":{"0":1,"109":1,"1323":2,"1350":4,"1395":1,"1396":2,"1402":1,"1415":1,"1417":3,"1607":1,"1608":2,"1741":1,"1742":3,"1870":1,"1988":1}}],["announced",{"3":{"0":2,"170":1,"790":2,"1074":1,"1118":1,"1231":1,"1259":1,"1309":1,"1311":1,"1316":1,"1324":1,"1359":2,"1380":3,"1392":1,"1396":1,"1402":1,"1417":1,"1436":1,"1558":1,"1561":1,"1607":3,"1608":6,"1631":1,"1643":1,"1644":2,"1741":3,"1742":13,"2091":1,"2166":1}}],["annotates",{"3":{"166":1,"305":1}}],["annotate",{"3":{"135":2,"1286":1,"1311":1,"1436":2}}],["annotated",{"3":{"0":1,"135":1,"250":1,"251":1,"252":1,"254":1,"256":1,"257":1,"258":2,"259":1,"305":1,"324":1,"343":1,"725":1,"1243":1,"1259":1,"1270":1,"1282":1,"1286":4,"1300":1,"1309":1,"1324":1,"1350":1,"1359":5,"1395":2,"1396":3,"1436":2,"1458":1,"1459":1,"1577":1,"1733":1,"2138":1}}],["annotations",{"3":{"318":1,"655":1,"657":1,"674":2,"675":1,"676":1,"1270":1,"1286":5,"1300":1,"1323":4,"1324":2,"1339":5,"1384":1,"1395":4,"1396":1,"1415":1,"1420":1,"1427":1,"1436":6,"1585":1,"1805":1,"2012":2}}],["annotation",{"3":{"0":2,"158":1,"160":1,"170":1,"318":1,"626":1,"627":1,"659":1,"674":1,"676":2,"1083":1,"1084":1,"1279":1,"1281":2,"1286":21,"1300":2,"1310":1,"1311":5,"1320":1,"1323":2,"1324":1,"1339":2,"1350":2,"1395":3,"1396":2,"1416":1,"1417":1,"1427":1,"1436":5,"1438":1,"1455":1,"1641":1,"1670":1,"1994":1,"2064":2,"2232":1}}],["annotating",{"3":{"0":1,"160":1,"1286":1,"1323":1}}],["ancestry",{"3":{"526":1,"530":1,"531":1,"1454":1}}],["ancestorfullname",{"3":{"1436":1}}],["ancestor",{"3":{"402":1,"530":1,"1196":1,"1333":2,"1339":3,"1417":1,"1436":5,"1438":2,"1443":1,"1454":1,"2157":1}}],["ancestors",{"3":{"214":1,"219":2,"572":1,"1324":2,"1339":2,"1436":5,"1438":2,"1488":1,"1525":1,"1577":1,"1591":2,"1600":1,"2055":1,"2147":1,"2148":1,"2149":1,"2150":2}}],["anchortype",{"3":{"1436":1}}],["anchoring",{"3":{"1359":1,"1380":1,"1415":1,"1436":4}}],["anchor",{"0":{"2103":1},"3":{"34":1,"57":1,"139":2,"203":1,"250":1,"251":2,"252":4,"254":2,"256":2,"257":2,"258":2,"259":2,"260":1,"261":1,"276":1,"306":1,"323":2,"324":1,"354":1,"366":2,"392":1,"413":1,"415":1,"418":1,"420":1,"423":1,"424":2,"425":1,"426":2,"428":1,"429":1,"430":1,"464":2,"465":2,"492":2,"493":1,"495":1,"543":2,"547":1,"609":1,"631":2,"644":1,"747":1,"752":1,"761":1,"833":1,"876":1,"917":1,"949":1,"991":1,"994":1,"1122":1,"1127":1,"1184":1,"1263":1,"1309":3,"1311":17,"1312":1,"1319":2,"1323":21,"1324":11,"1341":1,"1350":2,"1354":1,"1359":9,"1369":1,"1377":2,"1380":6,"1396":9,"1402":1,"1404":2,"1415":6,"1417":19,"1431":1,"1436":37,"1449":1,"1467":1,"1489":2,"1496":9,"1498":1,"1500":1,"1599":2,"1641":1,"1684":1,"1686":1,"1729":1,"1750":1,"1758":1,"1759":1,"1773":1,"1976":1,"2103":2,"2108":2,"2111":1,"2121":2,"2122":1}}],["anchored",{"0":{"137":1,"138":1,"139":1},"3":{"0":2,"34":1,"112":1,"130":2,"137":3,"138":1,"139":4,"140":1,"141":1,"142":1,"171":1,"249":1,"254":1,"324":1,"354":2,"375":1,"378":1,"393":1,"446":2,"453":1,"468":2,"475":1,"476":1,"477":1,"478":1,"480":1,"486":1,"501":1,"514":1,"534":5,"540":2,"543":2,"550":1,"552":1,"554":3,"559":1,"567":1,"586":1,"589":1,"594":1,"597":3,"604":1,"607":5,"613":2,"621":1,"624":3,"629":1,"631":1,"636":1,"638":3,"647":1,"652":1,"669":1,"672":4,"677":1,"680":2,"685":1,"696":4,"703":1,"723":1,"744":1,"764":3,"769":1,"777":1,"780":5,"794":1,"806":1,"841":1,"844":1,"849":1,"867":1,"877":1,"895":1,"909":1,"917":1,"927":1,"929":1,"934":1,"949":1,"1007":1,"1021":1,"1045":1,"1055":2,"1078":1,"1111":1,"1119":1,"1146":1,"1324":1,"1347":1,"1350":1,"1364":1,"1369":1,"1424":1,"1436":6,"1496":29,"1661":1,"1684":1,"1729":2,"1984":1,"2001":1,"2232":1}}],["anchors",{"0":{"1319":1},"3":{"0":2,"43":1,"63":1,"94":1,"98":1,"104":3,"128":1,"137":1,"138":2,"139":2,"140":2,"142":3,"181":1,"203":1,"220":2,"231":1,"241":2,"249":1,"250":3,"251":3,"252":5,"254":4,"256":4,"257":4,"258":3,"259":7,"260":1,"261":1,"268":1,"285":1,"322":1,"324":1,"326":3,"336":1,"337":1,"345":2,"354":1,"364":1,"375":1,"386":1,"393":1,"395":1,"409":1,"418":4,"424":1,"425":2,"426":1,"428":2,"429":1,"463":1,"464":1,"465":1,"466":2,"490":3,"491":1,"492":1,"493":2,"497":3,"502":2,"522":1,"526":1,"543":3,"551":1,"552":2,"589":1,"604":1,"609":2,"611":1,"613":1,"616":3,"644":2,"655":2,"660":2,"669":1,"696":2,"703":5,"713":1,"720":2,"731":1,"736":1,"747":1,"749":1,"755":2,"761":1,"814":1,"825":1,"850":1,"875":1,"877":1,"879":1,"884":1,"912":1,"927":2,"958":1,"960":1,"967":1,"983":1,"986":1,"991":1,"994":1,"999":1,"1021":1,"1029":1,"1062":1,"1065":1,"1070":1,"1136":1,"1193":1,"1309":1,"1311":2,"1313":1,"1324":2,"1350":1,"1359":2,"1377":1,"1417":2,"1436":7,"1467":3,"1476":1,"1488":1,"1496":7,"1591":1,"2046":2,"2086":1}}],["antes",{"3":{"1436":1}}],["anthropiccontentblock",{"3":{"1496":1}}],["anthropicclient",{"2":{"1420":1,"2172":1},"3":{"1420":1,"1427":2,"2172":1}}],["anthropicmessage",{"3":{"1496":1}}],["anthropicresponse",{"3":{"1496":1}}],["anthropicrequest",{"3":{"1496":1}}],["anthropicusage",{"3":{"1496":2}}],["anthropicjsonschemaformat",{"3":{"1496":2}}],["anthropicoutputconfig",{"3":{"1496":2}}],["anthropics",{"3":{"1459":1,"1496":2}}],["anthropicscoringservice",{"3":{"0":1,"1369":2,"1427":4,"1442":2,"1496":5,"2179":1}}],["anthropicaiservicecollectionextensions",{"3":{"1199":1,"1203":1,"1207":1,"1420":1,"1427":4}}],["anthropicaiproviderfactory",{"3":{"74":1,"1091":2,"1092":1,"1199":3,"1203":1,"1207":2,"1420":2,"1427":16}}],["anthropic",{"2":{"142":1,"1020":1,"1089":1,"1091":1,"1093":2,"1211":1,"1213":1,"1418":1,"1455":1,"1466":1,"1470":1,"1472":1,"1503":1,"2178":1},"3":{"0":2,"74":1,"142":1,"598":1,"599":1,"980":2,"983":1,"995":1,"1012":1,"1017":4,"1020":2,"1089":1,"1090":1,"1091":4,"1093":2,"1094":1,"1095":1,"1192":1,"1199":2,"1203":2,"1206":1,"1207":6,"1208":2,"1211":1,"1213":2,"1350":1,"1359":5,"1376":1,"1380":1,"1418":1,"1420":3,"1427":16,"1436":1,"1438":4,"1455":2,"1466":2,"1467":6,"1470":1,"1472":1,"1474":1,"1486":2,"1492":3,"1496":3,"1503":1,"2178":1,"2179":1,"2190":1,"2227":1}}],["anticipated",{"3":{"827":1,"1359":2,"1436":1,"1804":1,"1946":1}}],["anticipate",{"3":{"812":1,"1270":1,"1417":1}}],["anticipates",{"3":{"241":1,"245":1,"974":1,"1359":1,"1400":1}}],["antiforgery",{"3":{"0":1,"207":2,"664":3,"665":2,"670":1,"1184":1,"1187":1,"1212":1,"1300":3,"1311":2,"1336":2,"1339":4,"1442":1,"1467":2,"1526":1,"1969":2,"1970":2,"2232":1}}],["anti",{"3":{"0":3,"51":1,"53":2,"557":1,"591":1,"592":1,"688":1,"690":2,"691":1,"692":1,"854":1,"855":1,"859":1,"871":1,"1012":1,"1018":1,"1019":1,"1020":1,"1212":1,"1294":1,"1300":6,"1311":1,"1312":3,"1323":1,"1324":8,"1326":2,"1339":4,"1358":1,"1359":3,"1369":1,"1395":3,"1396":12,"1402":1,"1415":1,"1417":1,"1433":2,"1436":8,"1438":5,"1444":2,"1457":1,"1489":4,"1492":3,"1493":1,"1526":1,"1532":1,"1544":1,"1546":1,"1548":1,"1576":1,"1577":1,"1586":1,"1591":1,"1599":1,"1631":1,"1639":1,"1641":2,"1654":1,"1663":1,"1767":1,"1832":1,"2047":1,"2232":1}}],["anomalies",{"3":{"1359":1}}],["anomalous",{"3":{"1301":1}}],["anomaly",{"3":{"790":1,"1311":1,"1436":1}}],["anon",{"3":{"157":2,"161":1,"1311":1,"1908":1}}],["anonymizability",{"3":{"1671":1}}],["anonymizable",{"3":{"718":2,"1764":1}}],["anonymization",{"3":{"0":1,"37":1,"41":1,"42":4,"1201":1,"1237":2,"1323":4,"1415":2,"1438":2,"1567":1,"1640":1,"1672":1,"1765":1,"1784":1,"1903":2,"2062":1,"2063":1,"2064":1,"2068":1,"2221":1,"2232":1}}],["anonymizing",{"3":{"318":1,"326":1,"1142":1,"1300":1,"1764":1,"1767":1,"2066":1}}],["anonymizes",{"3":{"358":1,"716":1,"897":1,"1396":1,"1577":1,"1747":1,"1748":1,"1751":1,"1765":2,"1769":1,"2141":1}}],["anonymized",{"3":{"38":1,"42":1,"444":1,"1237":1,"1300":2,"1402":2,"1410":3,"1415":5,"1436":2,"1765":2,"2065":1,"2069":1}}],["anonymize",{"2":{"42":1,"355":1,"497":1,"718":1,"2063":1,"2064":1},"3":{"0":2,"39":1,"40":1,"42":1,"350":1,"355":1,"440":1,"497":2,"499":2,"690":1,"694":1,"718":1,"724":1,"725":1,"897":1,"1212":2,"1236":2,"1237":7,"1300":10,"1323":6,"1396":1,"1406":1,"1410":3,"1415":11,"1436":5,"1438":1,"1526":2,"1535":1,"1567":1,"1572":1,"1577":2,"1590":1,"1631":1,"1672":1,"1767":1,"1769":2,"2063":6,"2064":1,"2069":2,"2126":1,"2230":1}}],["anonymity",{"3":{"0":1,"184":1,"189":1,"446":1,"448":1,"1300":1,"1339":1,"1380":1,"1402":2,"1436":1,"2131":1}}],["anonymoususer",{"3":{"1436":2}}],["anonymousendpointsfortest",{"3":{"1436":3}}],["anonymousendpoints",{"3":{"1436":6}}],["anonymousendpointsof",{"3":{"1436":3}}],["anonymousendpointtests",{"3":{"1199":2,"1207":4,"1311":3,"1324":2,"1380":2,"1436":18,"1489":3,"1526":1,"1534":1,"1535":1,"1591":2}}],["anonymousendpointtestsbasetests",{"3":{"1198":1,"1199":7,"1207":14,"1436":44,"1489":1,"1496":1}}],["anonymousendpointtestsbase",{"2":{"189":1,"1586":1,"1898":1},"3":{"0":1,"189":2,"1199":10,"1207":2,"1300":2,"1436":38,"1586":1,"1898":1}}],["anonymousstate",{"3":{"1324":1}}],["anonymoussessionassettests",{"3":{"1199":1,"1207":2}}],["anonymously",{"3":{"1259":1,"1288":1,"1298":1,"1300":3,"1311":2,"1324":1,"1402":5,"1408":1,"1432":1,"1467":1,"1626":1,"1629":1,"1630":1,"1758":1}}],["anonymousauthenticationstateprovider",{"3":{"1496":2}}],["anonymousauthedgecasetests",{"3":{"1199":1,"1207":2}}],["anonymousauthtests",{"3":{"1199":1,"1207":2,"1611":1}}],["anonymousaccessdeniedtests",{"3":{"1199":2,"1207":4}}],["anonymousbookmarkaccessdeniedtests",{"3":{"1199":1,"1207":2}}],["anonymousconferencereadtests",{"2":{"1611":1},"3":{"1199":1,"1207":2,"1611":1}}],["anonymouscurrentuserservice",{"3":{"1199":2,"1207":1}}],["anonymousfixturecontroller",{"3":{"1199":3,"1207":1,"1436":9}}],["anonymouspartition",{"2":{"178":1},"3":{"178":1,"1311":5,"2000":1}}],["anonymoushubpermitlimit",{"2":{"175":1,"179":1},"3":{"0":1,"175":1,"179":1,"1311":5,"2000":1}}],["anonymous",{"0":{"1407":1,"1622":1,"1758":1},"2":{"57":1,"825":1},"3":{"0":15,"31":1,"33":1,"37":1,"42":3,"57":1,"59":2,"159":1,"173":5,"174":2,"175":9,"176":3,"177":4,"178":3,"179":3,"181":4,"189":1,"233":1,"242":1,"243":2,"244":3,"247":2,"265":2,"273":1,"279":1,"280":2,"287":2,"303":1,"306":1,"333":1,"349":1,"387":2,"390":1,"391":2,"403":1,"420":2,"441":1,"459":2,"464":1,"507":1,"527":1,"577":1,"618":1,"649":1,"748":1,"749":2,"757":3,"759":1,"789":1,"790":1,"792":1,"793":2,"794":1,"825":1,"826":4,"827":4,"828":1,"830":1,"834":1,"853":1,"954":2,"956":1,"960":1,"1059":1,"1115":1,"1116":2,"1118":2,"1184":1,"1207":12,"1212":4,"1246":1,"1253":1,"1259":1,"1264":1,"1270":2,"1275":1,"1278":1,"1286":2,"1294":1,"1300":18,"1311":46,"1323":8,"1324":38,"1332":2,"1337":1,"1339":10,"1343":1,"1344":1,"1350":12,"1359":24,"1372":1,"1375":2,"1380":23,"1381":2,"1389":1,"1391":1,"1395":19,"1396":15,"1402":4,"1407":3,"1408":1,"1414":1,"1415":19,"1416":2,"1417":1,"1432":2,"1433":1,"1434":1,"1436":60,"1438":13,"1447":5,"1455":1,"1456":1,"1467":6,"1488":1,"1489":1,"1491":2,"1496":1,"1526":2,"1591":3,"1596":3,"1597":1,"1608":1,"1611":2,"1621":1,"1623":1,"1627":1,"1631":4,"1640":7,"1641":2,"1643":1,"1666":1,"1671":1,"1673":1,"1703":1,"1742":2,"1748":4,"1749":1,"1750":1,"1757":2,"1758":1,"1759":1,"1761":2,"1765":3,"1768":7,"1771":1,"1773":1,"1775":1,"1825":3,"1898":1,"1911":1,"1922":4,"1963":1,"1966":1,"1997":1,"1998":5,"1999":4,"2000":6,"2001":6,"2002":5,"2007":1,"2024":3,"2028":1,"2036":1,"2066":1,"2083":2,"2125":1,"2127":1,"2166":2,"2168":1,"2193":1,"2196":1,"2232":2}}],["another",{"3":{"0":3,"21":1,"24":1,"47":1,"126":1,"131":1,"136":2,"137":1,"157":1,"159":1,"164":1,"168":1,"180":1,"187":1,"188":1,"225":1,"241":1,"247":1,"255":1,"318":1,"376":1,"386":1,"387":1,"434":1,"459":1,"566":1,"571":1,"634":1,"640":1,"650":2,"658":1,"699":1,"702":1,"709":1,"725":1,"727":1,"733":2,"735":1,"768":1,"792":1,"799":1,"827":1,"840":1,"862":2,"881":2,"905":4,"906":2,"907":1,"948":1,"972":1,"974":2,"1019":1,"1051":1,"1055":1,"1061":2,"1124":1,"1128":1,"1170":1,"1184":1,"1185":1,"1212":1,"1229":2,"1231":1,"1232":1,"1233":1,"1237":6,"1240":1,"1241":1,"1242":2,"1246":1,"1247":8,"1249":1,"1253":3,"1259":6,"1263":2,"1264":1,"1268":1,"1270":17,"1276":2,"1281":1,"1283":2,"1286":27,"1290":2,"1293":2,"1300":22,"1301":2,"1303":1,"1308":1,"1309":4,"1310":2,"1311":12,"1312":1,"1317":1,"1318":1,"1323":9,"1324":8,"1339":4,"1350":6,"1359":27,"1362":1,"1369":2,"1379":1,"1380":10,"1389":1,"1395":8,"1396":21,"1402":12,"1408":1,"1409":1,"1412":1,"1415":20,"1417":3,"1427":2,"1436":44,"1438":5,"1441":2,"1455":1,"1465":1,"1467":3,"1473":2,"1475":1,"1487":1,"1489":6,"1492":2,"1496":1,"1534":1,"1541":1,"1631":2,"1650":1,"1651":2,"1669":1,"1686":1,"1702":1,"1727":1,"1748":1,"1749":1,"1765":1,"1789":1,"1821":2,"1823":1,"1841":1,"1849":2,"1851":1,"1852":1,"1872":1,"1880":1,"1908":1,"1911":1,"1920":2,"1922":1,"1923":1,"1924":1,"1925":1,"1932":1,"1937":1,"1948":2,"1961":1,"1970":1,"1982":2,"1986":1,"1995":2,"1997":1,"2002":1,"2021":1,"2044":2,"2074":1,"2082":1,"2098":1,"2111":1,"2118":1,"2163":2,"2198":1,"2199":1,"2202":1}}],["anybody",{"3":{"1083":1,"1092":1,"1281":1,"1323":2,"1359":1,"2002":1,"2043":1,"2180":1}}],["anyone",{"3":{"0":3,"24":1,"224":1,"281":1,"284":1,"358":1,"441":1,"528":1,"530":1,"539":1,"633":1,"634":1,"689":1,"691":2,"692":1,"727":1,"735":1,"743":1,"805":1,"825":1,"905":1,"907":1,"914":1,"924":1,"926":1,"933":1,"938":1,"939":1,"971":1,"1042":1,"1044":1,"1116":1,"1118":2,"1232":1,"1237":1,"1270":1,"1286":3,"1290":1,"1300":4,"1301":1,"1323":3,"1342":1,"1350":1,"1359":7,"1380":1,"1389":1,"1396":5,"1402":2,"1406":1,"1416":2,"1436":6,"1454":1,"1467":5,"1481":1,"1484":1,"1638":1,"1640":1,"1641":1,"1765":1,"1808":2,"1874":1,"1893":1,"1931":1,"1938":1,"1950":1,"1982":1,"1995":2,"2045":1,"2099":1,"2124":1,"2127":1,"2140":1,"2191":1,"2192":1,"2216":1}}],["anything",{"0":{"1696":1},"3":{"0":2,"21":1,"54":1,"59":1,"109":1,"125":1,"136":1,"170":1,"201":1,"218":1,"225":1,"235":1,"265":1,"284":1,"291":1,"333":1,"335":1,"340":1,"350":1,"353":1,"370":1,"373":1,"380":2,"384":1,"402":1,"407":1,"415":1,"434":1,"439":1,"442":1,"518":1,"524":1,"546":1,"558":2,"566":1,"583":1,"585":1,"592":2,"611":1,"620":1,"626":1,"633":1,"642":1,"651":1,"659":1,"665":1,"676":1,"681":1,"696":1,"715":1,"717":1,"725":1,"731":1,"749":1,"751":1,"756":2,"766":1,"768":1,"774":1,"776":1,"790":1,"793":1,"821":1,"826":2,"827":1,"876":2,"897":1,"904":1,"907":1,"922":1,"926":1,"931":1,"938":1,"982":1,"988":1,"990":2,"995":1,"1018":1,"1026":1,"1048":1,"1052":1,"1066":1,"1075":1,"1091":2,"1093":1,"1100":2,"1101":1,"1118":1,"1124":1,"1126":2,"1129":1,"1133":1,"1134":1,"1175":2,"1184":1,"1193":1,"1212":1,"1228":2,"1229":5,"1233":1,"1237":5,"1243":1,"1244":1,"1247":3,"1251":1,"1259":8,"1268":1,"1270":9,"1271":1,"1278":1,"1285":1,"1286":32,"1289":1,"1290":1,"1293":1,"1300":23,"1301":5,"1304":2,"1307":1,"1308":1,"1309":2,"1311":23,"1312":4,"1323":31,"1324":30,"1326":1,"1327":2,"1332":2,"1339":20,"1343":2,"1350":9,"1352":1,"1359":42,"1362":1,"1369":4,"1379":3,"1380":2,"1385":1,"1391":1,"1395":20,"1396":19,"1402":5,"1405":1,"1406":1,"1408":1,"1415":13,"1416":7,"1417":8,"1418":1,"1421":1,"1423":1,"1424":1,"1427":6,"1428":1,"1433":1,"1435":1,"1436":22,"1438":5,"1442":1,"1443":1,"1445":1,"1447":1,"1455":4,"1456":2,"1457":1,"1459":2,"1460":2,"1466":1,"1467":2,"1472":1,"1473":1,"1475":1,"1482":1,"1490":1,"1492":1,"1513":1,"1591":1,"1631":2,"1632":1,"1652":1,"1653":2,"1662":1,"1666":1,"1668":1,"1676":1,"1677":1,"1683":1,"1685":1,"1702":1,"1704":1,"1719":2,"1729":1,"1769":1,"1776":1,"1805":1,"1824":2,"1840":1,"1847":1,"1856":1,"1859":1,"1869":1,"1874":1,"1886":1,"1892":1,"1894":1,"1908":1,"1924":1,"1933":2,"1934":1,"1940":1,"1941":1,"1945":1,"1959":1,"1966":1,"1972":1,"1974":1,"1980":1,"1982":1,"2000":1,"2006":1,"2008":1,"2010":1,"2024":1,"2034":1,"2039":1,"2049":1,"2061":1,"2089":1,"2109":1,"2121":1,"2126":2,"2142":1,"2145":1,"2149":1,"2156":1,"2158":1,"2165":1,"2172":1,"2179":1,"2180":1,"2183":2,"2185":1,"2192":1,"2193":1,"2194":1,"2203":1}}],["anyway",{"0":{"1226":1,"1720":1},"3":{"0":2,"135":1,"148":1,"201":1,"538":1,"539":1,"648":1,"691":1,"733":1,"743":1,"766":2,"826":1,"827":1,"839":1,"853":1,"869":1,"883":1,"941":1,"1027":1,"1178":1,"1214":1,"1252":1,"1254":1,"1259":3,"1270":1,"1286":4,"1300":1,"1309":2,"1311":5,"1323":1,"1324":2,"1339":1,"1359":4,"1384":1,"1395":3,"1396":4,"1406":1,"1415":2,"1416":1,"1417":5,"1427":1,"1436":7,"1455":1,"1459":1,"1467":1,"1582":1,"1654":1,"1684":1,"1729":1,"1821":1,"1840":2,"1901":1,"1905":1,"1909":1,"1922":1,"1943":1,"1946":1,"1947":2,"1952":1,"1954":1,"1966":1,"2000":1,"2024":1,"2077":1,"2101":1,"2192":1,"2194":1}}],["anywhere",{"0":{"94":1},"3":{"0":5,"81":1,"127":1,"170":2,"237":2,"324":1,"343":2,"373":1,"413":1,"422":1,"471":1,"477":1,"483":1,"490":1,"493":1,"529":1,"556":2,"564":1,"566":1,"591":1,"633":1,"635":1,"638":1,"642":1,"665":1,"676":1,"751":1,"757":1,"790":1,"797":1,"804":2,"827":1,"837":2,"868":1,"963":1,"974":1,"1050":2,"1075":1,"1082":1,"1134":1,"1141":1,"1183":1,"1229":3,"1237":1,"1244":1,"1247":1,"1270":1,"1271":1,"1286":8,"1300":3,"1306":1,"1309":3,"1323":2,"1324":8,"1339":2,"1357":1,"1359":13,"1369":3,"1380":3,"1395":4,"1396":10,"1402":2,"1414":1,"1415":8,"1417":1,"1423":1,"1431":1,"1436":13,"1441":1,"1455":1,"1465":2,"1474":1,"1476":2,"1492":1,"1496":3,"1526":2,"1577":1,"1584":1,"1626":1,"1654":1,"1684":1,"1726":1,"1729":1,"1804":1,"1810":1,"1842":1,"1935":1,"2046":1,"2121":1,"2129":1,"2184":1,"2197":1,"2199":1,"2201":1}}],["anyasync",{"3":{"0":1,"549":1,"550":1,"552":1,"1259":1,"1286":6,"1815":1}}],["any",{"1":{"771":1,"772":1,"773":1,"774":1,"775":1,"776":1,"777":1,"778":1},"3":{"0":32,"21":1,"30":1,"38":1,"39":3,"42":1,"53":1,"54":1,"59":1,"71":2,"72":1,"95":2,"96":1,"98":1,"100":1,"102":1,"109":1,"122":1,"125":1,"126":2,"132":1,"135":1,"137":1,"150":2,"155":1,"159":1,"160":1,"170":1,"171":1,"175":3,"177":2,"178":2,"180":2,"186":2,"193":1,"196":1,"200":1,"201":2,"217":1,"219":1,"243":3,"248":1,"259":2,"265":5,"284":1,"290":2,"300":1,"303":1,"305":1,"310":1,"318":4,"330":2,"333":1,"341":2,"342":1,"343":1,"358":1,"359":4,"361":2,"365":3,"370":3,"372":1,"374":1,"380":1,"384":1,"386":1,"387":2,"388":1,"397":1,"402":1,"405":1,"412":1,"415":1,"418":1,"427":1,"428":1,"434":1,"438":1,"448":2,"449":1,"459":2,"460":1,"461":1,"463":1,"465":2,"468":1,"469":1,"470":2,"477":1,"481":1,"486":1,"487":1,"497":1,"500":1,"507":1,"527":2,"536":1,"538":1,"540":1,"543":1,"545":2,"549":1,"550":2,"556":1,"564":1,"566":1,"573":1,"576":1,"583":4,"586":1,"597":1,"599":2,"601":1,"602":1,"609":1,"611":1,"618":2,"626":3,"627":1,"633":1,"648":1,"657":1,"672":1,"698":2,"700":1,"702":1,"707":2,"708":1,"709":1,"715":1,"723":1,"725":2,"733":1,"740":2,"741":3,"749":5,"750":1,"751":3,"757":2,"758":1,"766":2,"768":2,"771":1,"773":2,"774":1,"775":1,"776":3,"782":1,"790":3,"798":1,"799":1,"800":2,"801":1,"802":1,"803":2,"804":1,"809":1,"813":1,"818":1,"819":2,"826":2,"827":3,"833":1,"838":2,"845":1,"853":1,"869":1,"872":2,"881":2,"883":1,"891":2,"897":1,"904":1,"905":2,"906":1,"912":1,"921":1,"931":2,"939":1,"944":1,"945":1,"946":1,"948":1,"954":1,"963":1,"964":2,"971":1,"972":2,"979":1,"980":2,"981":1,"982":2,"986":1,"987":1,"988":1,"991":1,"1004":1,"1011":1,"1014":2,"1018":7,"1025":1,"1026":2,"1033":2,"1050":3,"1051":2,"1052":1,"1055":1,"1059":1,"1061":1,"1066":2,"1067":2,"1068":1,"1075":1,"1083":2,"1089":1,"1090":1,"1091":1,"1093":4,"1107":2,"1108":1,"1111":1,"1116":3,"1117":1,"1124":1,"1133":1,"1135":1,"1160":1,"1163":1,"1168":3,"1169":1,"1175":2,"1179":1,"1184":6,"1191":1,"1196":1,"1212":3,"1214":1,"1217":1,"1218":1,"1223":1,"1224":1,"1227":1,"1229":4,"1231":2,"1236":1,"1237":15,"1240":2,"1241":5,"1243":1,"1244":1,"1245":1,"1247":20,"1249":1,"1252":1,"1255":1,"1259":14,"1270":20,"1271":11,"1275":1,"1276":1,"1278":5,"1280":1,"1281":3,"1284":1,"1286":78,"1291":1,"1294":1,"1296":1,"1297":1,"1300":69,"1301":9,"1302":1,"1304":1,"1306":1,"1307":1,"1308":1,"1309":25,"1310":2,"1311":53,"1312":4,"1315":4,"1318":1,"1320":1,"1323":50,"1324":90,"1332":1,"1333":1,"1335":1,"1336":4,"1338":3,"1339":42,"1343":2,"1345":1,"1347":1,"1350":19,"1352":2,"1353":1,"1357":1,"1358":2,"1359":113,"1362":1,"1364":1,"1366":2,"1369":11,"1371":1,"1372":1,"1373":3,"1376":1,"1380":14,"1383":1,"1389":1,"1392":2,"1395":52,"1396":67,"1398":1,"1401":1,"1402":26,"1405":1,"1406":2,"1408":1,"1411":1,"1414":1,"1415":26,"1416":10,"1417":62,"1419":1,"1422":3,"1423":2,"1424":1,"1426":1,"1427":14,"1429":1,"1430":1,"1431":7,"1436":146,"1438":9,"1439":1,"1441":2,"1442":3,"1443":7,"1444":1,"1445":3,"1447":1,"1448":1,"1451":1,"1453":1,"1454":3,"1455":10,"1456":3,"1457":1,"1459":2,"1460":1,"1463":1,"1465":1,"1466":1,"1467":18,"1468":1,"1471":1,"1472":1,"1473":2,"1474":2,"1475":1,"1476":1,"1478":2,"1481":2,"1484":1,"1488":7,"1489":3,"1490":2,"1491":1,"1492":1,"1496":1,"1508":1,"1521":1,"1525":1,"1526":7,"1534":1,"1535":1,"1536":1,"1544":1,"1545":1,"1548":1,"1553":2,"1572":1,"1575":1,"1576":1,"1577":4,"1585":2,"1589":2,"1591":7,"1596":3,"1599":1,"1609":1,"1610":1,"1611":2,"1626":1,"1627":1,"1631":3,"1632":6,"1634":1,"1635":2,"1636":1,"1638":1,"1639":2,"1640":1,"1641":13,"1644":1,"1648":2,"1649":1,"1653":2,"1661":1,"1662":1,"1663":2,"1664":1,"1666":1,"1667":2,"1668":1,"1678":1,"1682":1,"1686":2,"1688":1,"1690":1,"1698":1,"1710":1,"1714":1,"1723":1,"1727":3,"1731":1,"1734":1,"1743":1,"1744":1,"1748":1,"1750":1,"1760":1,"1761":1,"1765":4,"1767":1,"1768":3,"1769":1,"1771":1,"1773":1,"1776":1,"1781":1,"1790":1,"1793":1,"1794":1,"1799":1,"1802":2,"1807":2,"1815":2,"1817":2,"1822":1,"1825":1,"1827":1,"1828":1,"1831":1,"1834":2,"1836":1,"1838":1,"1840":2,"1843":1,"1846":1,"1849":2,"1851":1,"1853":1,"1857":1,"1862":1,"1868":1,"1871":1,"1873":1,"1875":1,"1877":1,"1881":2,"1883":1,"1886":1,"1889":1,"1892":1,"1898":2,"1899":1,"1902":3,"1903":1,"1905":2,"1910":2,"1912":2,"1916":1,"1918":1,"1919":1,"1922":2,"1923":1,"1924":2,"1929":1,"1930":2,"1933":4,"1936":1,"1937":1,"1941":2,"1945":2,"1950":1,"1959":1,"1962":1,"1963":1,"1965":2,"1972":1,"1973":1,"1975":1,"1978":1,"1979":1,"1985":1,"1988":2,"1990":1,"1991":2,"1994":3,"1997":2,"2000":2,"2002":1,"2006":1,"2009":1,"2014":1,"2016":1,"2017":1,"2021":1,"2025":3,"2028":1,"2032":1,"2038":1,"2039":1,"2043":1,"2045":2,"2049":1,"2057":1,"2060":1,"2063":1,"2064":2,"2067":1,"2070":1,"2074":2,"2080":1,"2082":1,"2083":1,"2085":1,"2086":1,"2091":1,"2097":1,"2101":1,"2107":2,"2108":1,"2123":1,"2128":2,"2131":2,"2132":2,"2133":1,"2134":1,"2139":1,"2140":1,"2142":3,"2143":2,"2146":2,"2150":1,"2153":2,"2160":1,"2164":1,"2166":1,"2167":1,"2169":3,"2171":2,"2179":1,"2180":1,"2200":1,"2201":1,"2203":2,"2232":1,"2239":1}}],["an",{"0":{"1218":1,"1224":1,"1233":1,"1250":1,"1274":1,"1279":1,"1305":1,"1686":1,"1858":1,"1871":1,"1935":1,"1962":1,"1968":1,"1988":1,"2000":1,"2054":1,"2074":1,"2136":1,"2166":1,"2189":1},"1":{"172":1,"173":1,"174":1,"175":1,"176":1,"177":1,"178":1,"179":1,"180":1,"181":1,"182":1,"240":1,"241":1,"242":1,"243":1,"244":1,"245":1,"246":1,"247":1,"248":1,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"260":1,"261":1,"606":1,"607":1,"608":1,"609":1,"610":1,"611":1,"612":1,"613":1,"614":1,"730":1,"731":1,"732":1,"733":1,"734":1,"735":1,"736":1,"737":1,"771":1,"772":1,"773":1,"774":1,"775":1,"776":1,"777":1,"778":1,"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1,"1001":1,"1002":1,"1003":1,"1004":1,"1005":1,"1006":1,"1007":1,"1008":1,"1047":1,"1048":1,"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1,"1080":1,"1081":1,"1082":1,"1083":1,"1084":1,"1085":1,"1086":1,"1087":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1,"1726":1,"1727":1,"1728":1,"1729":1,"1730":1,"1731":1,"1835":1,"1836":1,"1837":1,"1838":1,"1839":1,"1840":1,"1841":1,"1842":1,"1843":1,"1844":1,"1845":1,"1846":1,"1887":1,"1888":1,"1889":1,"1890":1,"1891":1,"1892":1},"3":{"0":231,"1":1,"10":1,"15":3,"17":1,"18":1,"19":1,"20":4,"21":3,"22":2,"23":1,"24":15,"26":4,"27":6,"31":7,"33":1,"37":1,"39":8,"41":5,"42":2,"46":1,"47":1,"48":1,"53":2,"57":1,"62":5,"66":1,"67":1,"68":1,"71":2,"72":8,"73":3,"74":3,"76":1,"77":1,"78":5,"79":1,"81":7,"85":2,"86":1,"87":1,"92":2,"93":2,"94":2,"95":5,"96":4,"97":1,"99":3,"100":7,"101":1,"102":2,"103":2,"107":1,"108":2,"109":30,"110":5,"111":6,"113":1,"115":3,"116":1,"117":2,"120":1,"121":1,"122":6,"125":1,"126":1,"128":1,"130":1,"131":1,"132":2,"135":14,"136":4,"137":1,"139":1,"141":1,"145":1,"147":2,"150":3,"155":3,"156":1,"157":13,"158":1,"159":2,"160":8,"164":3,"165":3,"166":4,"167":3,"168":2,"170":2,"171":1,"172":1,"173":1,"174":1,"175":7,"176":4,"177":2,"178":3,"179":2,"181":2,"184":1,"185":2,"186":6,"187":1,"188":1,"189":5,"194":1,"195":4,"196":1,"197":1,"199":1,"200":4,"201":8,"202":4,"203":2,"206":5,"207":3,"208":1,"209":2,"212":2,"213":1,"214":4,"215":2,"216":2,"217":1,"219":2,"223":2,"224":2,"225":5,"227":1,"228":1,"229":1,"230":5,"233":2,"235":4,"236":1,"237":3,"240":1,"241":3,"242":1,"243":12,"245":6,"246":3,"248":3,"249":2,"250":1,"251":1,"252":1,"254":2,"255":10,"256":3,"258":4,"259":1,"260":1,"261":2,"265":15,"266":2,"267":2,"273":2,"274":1,"280":4,"281":1,"282":1,"283":5,"284":4,"285":1,"290":1,"291":1,"292":1,"293":3,"295":2,"296":1,"298":1,"299":1,"300":2,"301":1,"303":3,"305":4,"309":2,"312":2,"314":1,"317":1,"318":12,"319":3,"320":2,"321":1,"322":1,"324":3,"325":8,"326":1,"328":3,"329":1,"330":2,"331":1,"332":3,"333":2,"335":5,"336":1,"337":2,"340":2,"341":6,"342":2,"343":6,"344":1,"345":1,"348":3,"349":2,"350":15,"351":2,"352":4,"353":13,"358":3,"359":8,"360":1,"361":5,"363":1,"364":1,"365":1,"369":1,"370":4,"371":3,"372":3,"373":4,"375":1,"376":1,"378":1,"379":1,"380":2,"381":1,"382":1,"384":2,"386":1,"387":2,"388":4,"390":7,"391":5,"397":2,"398":4,"400":3,"402":2,"403":2,"404":3,"405":2,"407":3,"408":1,"409":1,"413":3,"415":1,"418":2,"420":4,"422":3,"423":2,"424":1,"425":1,"426":3,"427":4,"428":3,"430":1,"433":1,"434":1,"435":5,"438":1,"440":6,"443":2,"444":5,"446":1,"448":5,"450":1,"451":1,"452":1,"454":1,"458":2,"459":8,"461":4,"463":1,"465":4,"466":1,"468":1,"469":1,"470":1,"471":1,"472":2,"474":1,"475":1,"476":1,"481":1,"483":1,"484":1,"486":4,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"497":2,"499":4,"500":2,"501":2,"506":4,"507":8,"508":2,"509":2,"513":2,"514":1,"517":4,"518":4,"519":1,"520":1,"522":2,"523":1,"528":2,"529":1,"530":6,"534":6,"535":3,"536":8,"537":4,"538":5,"539":7,"540":1,"543":4,"544":4,"545":11,"546":3,"547":1,"548":1,"549":13,"551":3,"555":2,"556":8,"558":1,"559":1,"564":9,"565":1,"566":5,"571":2,"572":5,"573":1,"574":1,"577":2,"578":2,"582":1,"583":14,"584":1,"585":6,"586":5,"590":1,"591":3,"592":1,"598":2,"599":6,"601":4,"602":2,"606":1,"607":1,"608":7,"609":12,"610":1,"611":2,"612":4,"618":1,"619":1,"624":2,"626":6,"627":5,"628":4,"631":1,"632":1,"633":8,"634":2,"635":2,"639":1,"640":5,"642":2,"648":2,"649":5,"651":2,"656":3,"657":10,"658":4,"660":3,"661":1,"664":3,"665":6,"666":3,"672":2,"673":2,"674":12,"676":2,"677":1,"681":4,"682":6,"683":3,"689":9,"690":5,"691":3,"692":11,"696":4,"697":2,"698":8,"699":2,"700":3,"701":2,"702":3,"706":5,"707":5,"708":5,"709":1,"713":1,"714":3,"715":6,"716":2,"717":7,"718":2,"723":4,"724":3,"725":15,"726":3,"727":8,"729":1,"730":1,"731":1,"732":2,"733":5,"734":3,"735":2,"740":3,"741":7,"742":7,"743":6,"745":1,"748":2,"749":4,"756":1,"757":4,"758":1,"759":1,"760":1,"761":2,"764":1,"765":1,"766":5,"767":5,"768":8,"769":2,"771":1,"773":1,"774":2,"776":4,"781":2,"782":6,"783":4,"784":2,"789":5,"790":14,"791":3,"792":4,"793":5,"794":2,"795":1,"797":1,"798":2,"799":9,"802":1,"803":1,"804":1,"805":1,"806":1,"809":4,"810":3,"811":3,"813":5,"815":1,"817":3,"818":2,"819":9,"820":3,"821":4,"823":2,"825":3,"826":3,"827":23,"828":2,"829":10,"830":1,"831":2,"832":1,"833":4,"838":5,"839":3,"840":1,"841":1,"845":3,"846":2,"847":2,"848":5,"849":3,"852":1,"853":6,"854":6,"855":2,"856":4,"857":3,"859":3,"860":5,"861":6,"862":3,"863":1,"864":1,"868":2,"869":2,"870":2,"871":4,"874":1,"876":1,"880":2,"881":7,"882":2,"883":2,"888":2,"889":6,"890":2,"891":2,"896":2,"897":12,"898":2,"899":1,"901":1,"903":2,"904":3,"905":23,"906":4,"907":4,"908":2,"909":1,"912":2,"913":4,"914":7,"915":4,"916":3,"917":1,"918":1,"920":2,"921":3,"922":5,"923":1,"924":3,"926":11,"930":1,"931":2,"932":4,"933":7,"936":1,"938":4,"939":3,"941":1,"945":4,"946":6,"947":6,"948":9,"953":1,"954":4,"959":1,"960":1,"963":3,"964":7,"965":3,"966":1,"971":1,"972":5,"973":1,"974":2,"975":1,"979":1,"980":8,"981":3,"982":3,"988":13,"989":3,"990":1,"992":1,"995":1,"996":9,"997":1,"998":1,"999":2,"1001":1,"1003":1,"1004":3,"1005":2,"1006":2,"1012":4,"1013":3,"1016":1,"1017":6,"1018":14,"1019":6,"1020":8,"1022":1,"1024":2,"1025":1,"1026":9,"1027":3,"1028":5,"1029":1,"1032":1,"1033":5,"1034":9,"1035":2,"1037":3,"1041":3,"1042":10,"1043":4,"1044":4,"1045":2,"1046":2,"1047":1,"1049":3,"1050":13,"1051":4,"1052":5,"1053":1,"1055":2,"1058":5,"1059":13,"1060":2,"1061":3,"1066":3,"1067":16,"1068":2,"1069":1,"1074":1,"1075":2,"1077":2,"1078":1,"1079":1,"1080":1,"1082":3,"1083":3,"1084":3,"1085":2,"1089":5,"1090":4,"1091":18,"1092":5,"1093":5,"1095":1,"1096":1,"1099":2,"1100":6,"1101":2,"1102":2,"1107":4,"1108":4,"1109":3,"1115":5,"1116":17,"1117":4,"1118":4,"1119":1,"1123":1,"1124":6,"1125":1,"1126":5,"1133":8,"1134":2,"1135":4,"1140":7,"1141":1,"1142":7,"1143":3,"1144":1,"1145":2,"1147":1,"1150":5,"1151":1,"1152":4,"1153":1,"1154":1,"1155":5,"1157":1,"1158":1,"1161":10,"1162":4,"1163":3,"1164":2,"1167":1,"1168":11,"1169":1,"1170":8,"1171":1,"1174":2,"1175":5,"1176":4,"1177":5,"1178":2,"1179":1,"1183":2,"1184":8,"1185":2,"1186":1,"1187":2,"1188":2,"1190":2,"1191":1,"1196":1,"1210":2,"1212":56,"1214":3,"1215":1,"1217":3,"1219":3,"1220":7,"1221":3,"1222":3,"1223":1,"1224":3,"1225":3,"1228":6,"1229":70,"1230":1,"1231":10,"1232":4,"1233":9,"1234":5,"1235":1,"1236":11,"1237":137,"1238":2,"1239":13,"1240":3,"1241":14,"1242":8,"1243":10,"1244":7,"1245":4,"1246":4,"1247":179,"1248":3,"1249":9,"1250":4,"1251":1,"1252":9,"1253":4,"1254":3,"1255":2,"1256":11,"1257":5,"1258":5,"1259":160,"1260":3,"1261":2,"1262":5,"1263":10,"1264":8,"1265":10,"1266":2,"1267":2,"1268":4,"1269":11,"1270":236,"1271":56,"1272":3,"1273":7,"1274":13,"1275":12,"1276":9,"1277":3,"1278":13,"1279":8,"1280":9,"1281":9,"1282":5,"1283":7,"1284":1,"1285":8,"1286":642,"1287":2,"1288":9,"1289":10,"1290":13,"1291":4,"1292":5,"1293":13,"1294":5,"1295":3,"1296":2,"1297":13,"1298":6,"1299":4,"1300":540,"1301":79,"1302":3,"1303":4,"1304":7,"1305":4,"1306":3,"1307":6,"1308":8,"1309":146,"1310":34,"1311":382,"1312":66,"1314":3,"1315":8,"1316":11,"1317":9,"1318":14,"1319":1,"1320":8,"1321":1,"1322":8,"1323":417,"1324":641,"1325":1,"1326":3,"1328":5,"1329":1,"1330":2,"1331":3,"1332":6,"1333":8,"1334":5,"1335":2,"1336":5,"1337":5,"1338":9,"1339":245,"1340":4,"1342":18,"1343":9,"1344":10,"1345":2,"1346":1,"1347":3,"1348":3,"1349":6,"1350":318,"1351":3,"1352":11,"1353":11,"1354":1,"1355":9,"1356":2,"1357":6,"1358":15,"1359":1082,"1361":1,"1362":4,"1363":1,"1364":2,"1365":3,"1366":11,"1367":5,"1368":1,"1369":114,"1371":3,"1372":5,"1373":6,"1374":1,"1375":5,"1376":3,"1377":1,"1378":4,"1379":7,"1380":176,"1381":2,"1382":6,"1383":3,"1384":3,"1387":3,"1389":12,"1390":5,"1391":11,"1392":5,"1393":3,"1394":2,"1395":478,"1396":692,"1397":3,"1398":8,"1399":9,"1400":2,"1401":7,"1402":202,"1403":3,"1404":1,"1405":4,"1406":17,"1408":6,"1409":2,"1410":3,"1411":5,"1412":1,"1413":2,"1414":7,"1415":362,"1416":64,"1417":230,"1418":5,"1419":2,"1420":4,"1421":8,"1422":3,"1423":11,"1424":2,"1425":3,"1427":77,"1429":2,"1430":9,"1431":8,"1432":6,"1433":7,"1435":6,"1436":947,"1438":115,"1440":2,"1441":11,"1442":7,"1443":16,"1444":3,"1445":2,"1446":2,"1447":11,"1449":4,"1451":2,"1453":14,"1454":9,"1455":39,"1456":6,"1457":2,"1458":5,"1459":16,"1460":6,"1463":2,"1464":1,"1465":6,"1466":3,"1467":69,"1469":1,"1470":2,"1471":2,"1472":7,"1473":4,"1474":7,"1475":15,"1476":14,"1477":2,"1478":6,"1479":3,"1481":1,"1482":2,"1484":3,"1487":1,"1488":22,"1489":25,"1490":8,"1491":3,"1492":13,"1493":1,"1496":36,"1498":1,"1500":2,"1503":1,"1504":1,"1508":3,"1512":2,"1524":9,"1525":2,"1526":28,"1527":1,"1530":3,"1531":7,"1532":4,"1533":2,"1534":9,"1536":1,"1537":1,"1540":1,"1541":1,"1543":1,"1544":1,"1545":2,"1546":1,"1547":5,"1553":7,"1554":1,"1561":2,"1567":1,"1568":1,"1569":1,"1571":1,"1572":3,"1574":1,"1575":5,"1576":3,"1577":44,"1578":1,"1580":1,"1581":1,"1582":5,"1583":1,"1585":5,"1588":1,"1589":5,"1590":1,"1591":17,"1592":2,"1595":1,"1596":8,"1597":1,"1598":3,"1599":3,"1600":5,"1602":2,"1608":1,"1611":3,"1613":1,"1621":1,"1626":2,"1627":16,"1629":5,"1630":6,"1631":38,"1632":16,"1633":1,"1634":3,"1635":2,"1636":1,"1637":14,"1638":12,"1639":15,"1640":15,"1641":28,"1642":1,"1643":2,"1646":1,"1647":1,"1649":3,"1650":2,"1651":9,"1653":6,"1654":6,"1655":1,"1656":2,"1657":1,"1658":1,"1659":1,"1661":3,"1662":6,"1663":1,"1664":8,"1665":3,"1666":7,"1667":5,"1668":3,"1669":7,"1670":3,"1671":3,"1672":2,"1673":6,"1674":2,"1675":6,"1677":5,"1678":1,"1681":1,"1683":2,"1684":6,"1685":7,"1686":9,"1687":3,"1688":1,"1689":5,"1690":2,"1692":1,"1697":1,"1698":3,"1699":1,"1700":1,"1701":3,"1702":4,"1703":1,"1704":1,"1705":1,"1706":1,"1707":2,"1708":6,"1718":5,"1719":2,"1720":3,"1721":1,"1723":2,"1724":5,"1726":1,"1727":18,"1728":3,"1729":2,"1730":2,"1731":1,"1734":2,"1735":1,"1736":1,"1747":1,"1748":3,"1749":6,"1750":4,"1751":1,"1755":4,"1758":3,"1759":1,"1763":1,"1764":10,"1765":18,"1767":6,"1768":4,"1769":2,"1770":1,"1771":3,"1772":1,"1773":2,"1774":1,"1776":8,"1778":1,"1781":1,"1782":1,"1783":1,"1784":1,"1785":1,"1787":1,"1790":5,"1794":2,"1795":2,"1797":1,"1799":3,"1800":1,"1801":2,"1802":2,"1803":5,"1804":5,"1805":11,"1806":2,"1807":2,"1808":1,"1809":4,"1810":13,"1811":2,"1812":3,"1813":1,"1815":23,"1816":2,"1817":6,"1820":1,"1821":3,"1822":3,"1823":4,"1824":1,"1825":7,"1826":1,"1827":1,"1829":2,"1830":7,"1831":3,"1832":1,"1834":1,"1835":2,"1837":2,"1838":2,"1839":3,"1840":4,"1841":4,"1842":4,"1843":6,"1844":2,"1845":4,"1846":1,"1848":1,"1849":3,"1851":7,"1853":3,"1854":2,"1855":4,"1856":6,"1858":1,"1859":5,"1860":1,"1861":1,"1862":3,"1864":1,"1865":4,"1867":2,"1868":1,"1869":2,"1871":2,"1873":1,"1874":2,"1876":2,"1877":4,"1878":2,"1880":2,"1881":3,"1882":1,"1883":5,"1885":1,"1886":4,"1887":3,"1888":4,"1889":9,"1890":1,"1891":1,"1892":5,"1894":1,"1896":3,"1897":4,"1898":9,"1899":3,"1900":2,"1901":1,"1902":10,"1903":1,"1904":5,"1905":3,"1906":1,"1907":1,"1908":9,"1909":9,"1910":4,"1911":2,"1912":5,"1916":3,"1917":1,"1918":2,"1919":2,"1920":3,"1921":2,"1922":9,"1923":10,"1924":3,"1925":2,"1926":3,"1927":17,"1928":3,"1929":7,"1930":2,"1931":2,"1932":2,"1933":8,"1934":2,"1935":3,"1937":1,"1938":2,"1939":1,"1940":5,"1941":4,"1942":1,"1943":2,"1944":2,"1945":5,"1946":4,"1947":3,"1948":7,"1949":1,"1950":9,"1951":1,"1952":3,"1953":4,"1954":5,"1955":2,"1956":2,"1958":1,"1959":5,"1960":4,"1961":3,"1962":4,"1963":3,"1964":4,"1965":9,"1966":5,"1967":1,"1968":2,"1970":2,"1971":2,"1972":6,"1973":3,"1974":1,"1975":2,"1976":7,"1977":13,"1978":8,"1979":7,"1981":1,"1982":11,"1983":1,"1984":3,"1985":5,"1987":1,"1988":9,"1990":4,"1991":3,"1992":2,"1993":2,"1994":14,"1995":12,"1996":6,"1997":5,"1998":3,"1999":5,"2000":8,"2001":11,"2002":5,"2003":1,"2004":3,"2005":2,"2007":2,"2010":3,"2011":1,"2012":3,"2013":2,"2016":1,"2017":1,"2018":1,"2019":1,"2020":2,"2022":1,"2024":2,"2025":3,"2027":1,"2028":3,"2030":2,"2031":1,"2032":3,"2033":3,"2034":3,"2035":1,"2036":1,"2038":7,"2041":1,"2043":5,"2044":3,"2045":4,"2046":8,"2047":7,"2048":1,"2049":1,"2051":1,"2055":6,"2056":6,"2057":2,"2058":1,"2059":2,"2060":1,"2062":1,"2063":11,"2064":4,"2065":2,"2066":2,"2067":1,"2068":1,"2069":3,"2070":3,"2072":5,"2073":2,"2074":4,"2075":5,"2076":1,"2077":4,"2078":2,"2079":1,"2080":1,"2082":1,"2083":11,"2085":1,"2086":5,"2087":1,"2089":3,"2090":4,"2092":2,"2094":2,"2096":2,"2097":1,"2098":2,"2099":4,"2100":2,"2101":1,"2104":2,"2105":2,"2106":1,"2107":2,"2110":3,"2111":1,"2112":1,"2113":1,"2114":1,"2115":1,"2116":1,"2117":3,"2119":1,"2121":2,"2122":1,"2123":4,"2124":4,"2125":4,"2126":8,"2127":1,"2128":8,"2131":2,"2133":1,"2134":1,"2135":2,"2136":2,"2137":2,"2138":2,"2139":2,"2140":2,"2141":1,"2142":4,"2143":2,"2144":4,"2145":4,"2146":6,"2147":4,"2148":3,"2149":5,"2150":3,"2151":2,"2152":3,"2153":2,"2154":1,"2155":1,"2156":11,"2157":4,"2158":10,"2160":4,"2161":2,"2162":3,"2163":4,"2165":4,"2166":9,"2167":7,"2168":4,"2169":8,"2170":2,"2171":2,"2172":3,"2174":2,"2175":8,"2176":1,"2177":5,"2178":2,"2179":4,"2180":7,"2181":3,"2182":5,"2183":3,"2185":4,"2186":5,"2188":6,"2189":1,"2190":2,"2191":7,"2192":7,"2193":11,"2194":2,"2195":2,"2196":6,"2197":6,"2198":4,"2199":7,"2200":2,"2201":4,"2202":3,"2203":5,"2204":1,"2206":2,"2207":1,"2214":1,"2220":5,"2221":2,"2226":1,"2228":2,"2230":5,"2232":39,"2238":1,"2239":7,"2241":1}}],["andhasnowcag21aaviolations",{"3":{"1436":3}}],["andkeepstheformwitharesend",{"3":{"1436":1}}],["andaneditableoneisnot",{"3":{"1436":1}}],["andallowedsharedcodes",{"3":{"1436":3}}],["andalso",{"2":{"1859":1},"3":{"545":1,"549":1,"1239":1,"1247":5,"1815":1,"1859":1}}],["anddoesnotoverflowhorizontally",{"3":{"1436":1}}],["andbesealed",{"3":{"1436":1}}],["andisreportedsupported",{"3":{"1436":1}}],["andisreporteddeprecated",{"3":{"1436":1}}],["anding",{"3":{"546":1,"1247":1,"1359":1,"1395":1}}],["andcatchesawrappedregistration",{"3":{"1436":1}}],["andpassesthroughnonpii",{"3":{"1436":1}}],["andpinsanintermediatebase",{"3":{"1436":3}}],["andeverydocumentedroute",{"3":{"1436":1}}],["anded",{"3":{"545":1,"1239":2,"1247":3,"1353":2,"1359":7,"1375":1,"1380":1,"1396":1,"1438":1,"1666":1,"1815":1}}],["andtheapisharestheirenvironment",{"3":{"1436":1}}],["andneveranadministrativecommand",{"3":{"1339":1}}],["andsignaturestherepodoesnotown",{"3":{"1436":1}}],["ands",{"3":{"545":1,"1247":3,"1286":1,"1311":1,"1359":5,"1380":3,"1385":1,"1395":2,"1396":5,"1815":4}}],["andspecification",{"2":{"319":1,"550":1},"3":{"319":1,"545":2,"549":3,"550":1,"552":1,"1199":7,"1203":1,"1207":1,"1239":1,"1247":13,"1359":2,"1380":2,"1815":2,"1994":1}}],["androidsigningkeypass",{"2":{"1481":1},"3":{"1481":1}}],["androidsigningstorepass",{"2":{"1481":1},"3":{"1481":1}}],["androidpublicwebhost",{"3":{"1436":1}}],["androidpackagename",{"2":{"424":1,"425":1,"426":1,"428":1,"429":1},"3":{"424":1,"425":1,"426":1,"428":1,"429":1,"1311":1}}],["androidx",{"3":{"1417":3}}],["androidcertfingerprints",{"2":{"423":1,"424":1,"425":1,"426":1,"428":1,"429":1},"3":{"418":1,"423":2,"424":2,"425":1,"426":1,"428":2,"429":2,"1311":1}}],["androidmanifest",{"3":{"418":1,"423":1,"426":1,"1416":1,"1436":1}}],["android",{"0":{"1479":1},"2":{"1481":1},"3":{"0":3,"265":1,"412":1,"413":2,"418":11,"419":1,"420":1,"422":2,"423":5,"424":1,"425":1,"426":5,"427":3,"428":2,"429":2,"433":1,"434":2,"436":2,"681":1,"682":1,"1207":6,"1212":1,"1311":7,"1323":1,"1324":4,"1347":1,"1359":1,"1381":1,"1396":3,"1416":69,"1417":41,"1436":6,"1438":1,"1454":1,"1455":1,"1456":1,"1459":4,"1479":3,"1481":7,"1513":1,"1526":3,"1531":3,"1532":1,"1591":1,"1599":1,"1630":1,"1660":1,"1666":1,"1669":2,"1773":1,"1936":1,"2078":1,"2120":1,"2230":1}}],["and",{"0":{"93":1,"95":1,"99":1,"137":1,"138":1,"141":1,"374":1,"452":1,"539":1,"577":1,"720":1,"813":1,"1128":1,"1214":1,"1221":1,"1225":1,"1226":1,"1242":1,"1247":1,"1251":1,"1255":1,"1256":2,"1257":1,"1258":1,"1263":1,"1267":1,"1270":1,"1278":1,"1281":1,"1282":1,"1283":1,"1284":1,"1285":1,"1292":1,"1297":1,"1300":1,"1303":1,"1305":1,"1307":1,"1308":1,"1314":1,"1315":1,"1316":1,"1318":1,"1328":1,"1331":1,"1332":1,"1333":1,"1336":1,"1342":1,"1345":1,"1347":1,"1348":1,"1349":1,"1353":1,"1354":1,"1355":1,"1356":1,"1359":1,"1363":1,"1364":1,"1366":1,"1367":1,"1368":1,"1375":1,"1376":1,"1377":1,"1378":1,"1379":1,"1384":1,"1385":2,"1386":1,"1391":1,"1392":1,"1394":1,"1398":1,"1399":1,"1400":1,"1401":1,"1402":1,"1405":1,"1409":1,"1410":1,"1412":1,"1413":1,"1423":1,"1427":1,"1433":1,"1436":1,"1459":1,"1473":1,"1475":1,"1478":1,"1486":1,"1490":1,"1492":1,"1520":1,"1534":1,"1585":1,"1599":1,"1648":1,"1649":1,"1652":1,"1653":1,"1654":1,"1661":1,"1664":1,"1665":1,"1667":1,"1668":1,"1669":1,"1670":1,"1671":1,"1672":1,"1673":1,"1696":1,"1707":1,"1730":1,"1769":1,"1797":1,"1805":1,"1821":1,"1831":1,"1842":1,"1870":1,"1881":1,"1889":1,"1896":1,"1910":1,"1921":1,"1934":1,"1938":1,"1945":1,"1955":1,"1962":1,"1969":1,"1988":1,"2009":1,"2010":1,"2011":1,"2013":1,"2033":1,"2036":1,"2041":1,"2054":1,"2058":2,"2062":1,"2067":1,"2090":1,"2092":1,"2097":1,"2098":1,"2102":1,"2107":1,"2120":1,"2126":1,"2137":1,"2172":1,"2186":1,"2190":1,"2197":1,"2199":1,"2207":1,"2208":1,"2209":1,"2210":1,"2239":1},"1":{"144":1,"145":1,"146":1,"147":1,"148":1,"149":1,"150":1,"151":1,"152":1,"153":1,"232":1,"233":1,"234":1,"235":1,"236":1,"237":1,"238":1,"239":1,"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"394":1,"395":1,"396":1,"397":1,"398":1,"399":1,"400":1,"401":1,"402":1,"403":1,"404":1,"405":1,"406":1,"407":1,"408":1,"409":1,"417":1,"418":1,"419":1,"420":1,"421":1,"422":1,"423":1,"424":1,"425":1,"426":1,"427":1,"428":1,"429":1,"430":1,"437":1,"438":1,"439":1,"440":1,"441":1,"442":1,"443":1,"444":1,"496":1,"497":1,"498":1,"499":1,"500":1,"501":1,"502":1,"503":1,"580":1,"581":1,"582":1,"583":1,"584":1,"585":1,"586":1,"587":1,"596":1,"597":1,"598":1,"599":1,"600":1,"601":1,"602":1,"603":1,"604":1,"605":1,"615":1,"616":1,"617":1,"618":1,"619":1,"620":1,"621":1,"622":1,"637":1,"638":1,"639":1,"640":1,"641":1,"642":1,"643":1,"644":1,"645":1,"646":1,"647":1,"648":1,"649":1,"650":1,"651":1,"652":1,"653":1,"679":1,"680":1,"681":1,"682":1,"683":1,"684":1,"685":1,"686":1,"687":1,"688":1,"689":1,"690":1,"691":1,"692":1,"693":1,"694":1,"787":1,"788":1,"789":1,"790":1,"791":1,"792":1,"793":1,"794":1,"795":1,"816":1,"817":1,"818":1,"819":1,"820":1,"821":1,"822":1,"823":1,"824":1,"825":1,"826":1,"827":1,"828":1,"829":1,"830":1,"831":1,"832":1,"833":1,"834":1,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1,"866":1,"867":1,"868":1,"869":1,"870":1,"871":1,"872":1,"873":1,"874":1,"875":1,"876":1,"877":1,"985":1,"986":1,"987":1,"988":1,"989":1,"990":1,"991":1,"992":1,"1072":1,"1073":1,"1074":1,"1075":1,"1076":1,"1077":1,"1078":1,"1079":1,"1105":1,"1106":1,"1107":1,"1108":1,"1109":1,"1110":1,"1111":1,"1112":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1,"1210":1,"1211":1,"1212":1,"1213":1,"1214":1,"1215":1,"1216":1,"1439":1,"1440":1,"1441":1,"1442":1,"1443":1,"1444":1,"1445":1,"1446":1,"1447":1,"1448":1,"1449":1,"1450":1,"1451":1,"1452":1,"1453":1,"1454":1,"1455":1,"1456":1,"1457":1,"1458":1,"1459":1,"1460":1,"1461":1,"1780":1,"1781":1,"1782":1,"1783":1,"1784":1,"1785":1,"1786":1,"1787":1,"1818":1,"1819":1,"1820":1,"1821":1,"1822":1,"1823":1,"1824":1,"1825":1,"1826":1,"1827":1,"1847":1,"1848":1,"1849":1,"1850":1,"1851":1,"1852":1,"1853":1,"1878":1,"1879":1,"1880":1,"1881":1,"1882":1,"1883":1,"1884":1,"1885":1,"1886":1,"1925":1,"1926":1,"1927":1,"1928":1,"1929":1,"1930":1,"1931":1,"1932":1,"1933":1,"1934":1,"1935":1,"1936":1,"1937":1,"1938":1,"1939":1,"1940":1,"1941":1,"1973":1,"1974":1,"1975":1,"1976":1,"1977":1,"1978":1,"1979":1,"1980":1,"1981":1,"1982":1,"1983":1,"1984":1,"1985":1,"1986":1,"1987":1,"1988":1,"1989":1,"1990":1,"1991":1,"2029":1,"2030":1,"2031":1,"2032":1,"2033":1,"2034":1,"2035":1,"2036":1,"2037":1,"2038":1,"2061":1,"2062":1,"2063":1,"2064":1,"2065":1,"2066":1,"2067":1,"2068":1,"2069":1,"2081":2,"2082":2,"2083":2,"2084":2,"2085":2,"2086":2,"2109":1,"2110":1,"2111":1,"2112":1,"2113":1,"2114":1,"2115":1,"2147":1,"2148":1,"2149":1,"2150":1,"2151":1,"2152":1,"2153":1,"2154":1,"2155":1,"2156":1,"2157":1,"2158":1,"2159":1,"2160":1,"2161":1,"2162":1,"2163":1,"2164":1,"2165":1,"2166":1,"2167":1,"2168":1,"2169":1,"2181":1,"2182":1,"2183":1,"2184":1,"2185":1,"2186":1,"2187":1,"2188":1,"2189":1,"2190":1,"2191":1,"2192":1,"2193":1,"2194":1,"2195":1,"2196":1,"2197":1,"2198":1,"2199":1,"2200":1,"2201":1,"2202":1,"2203":1},"2":{"260":1,"319":1,"545":1,"1082":1,"1504":1,"1814":1,"2105":1},"3":{"0":938,"1":12,"3":1,"5":1,"10":2,"15":13,"16":1,"17":1,"18":2,"19":6,"20":9,"21":7,"22":3,"23":6,"24":29,"25":6,"26":15,"27":23,"29":2,"30":3,"31":15,"32":6,"33":6,"34":1,"35":1,"37":2,"38":4,"39":17,"40":4,"41":4,"42":10,"43":4,"47":6,"48":2,"49":2,"52":2,"53":8,"54":5,"55":2,"57":6,"58":4,"59":13,"60":5,"61":3,"62":8,"63":4,"64":1,"66":3,"67":3,"68":8,"69":3,"70":6,"71":17,"72":14,"73":4,"74":5,"76":3,"77":5,"78":5,"79":4,"80":11,"81":10,"82":2,"85":2,"86":3,"90":2,"91":11,"92":9,"93":14,"94":12,"95":14,"96":6,"97":6,"98":7,"99":7,"100":9,"101":4,"102":5,"103":2,"104":6,"105":1,"107":3,"108":2,"109":61,"110":8,"111":10,"112":4,"113":3,"115":14,"116":4,"117":11,"118":3,"120":5,"121":12,"122":17,"123":3,"124":2,"125":15,"126":18,"127":4,"128":11,"130":15,"131":2,"132":5,"133":1,"134":2,"135":63,"136":28,"137":12,"138":15,"139":23,"140":15,"141":8,"142":23,"143":4,"144":1,"145":22,"146":6,"147":11,"148":3,"149":4,"150":22,"151":3,"152":6,"153":3,"155":7,"156":2,"157":20,"158":3,"159":10,"160":20,"161":10,"162":1,"164":9,"165":3,"166":21,"167":2,"168":8,"169":2,"170":12,"171":8,"173":11,"174":5,"175":25,"176":4,"177":8,"178":19,"179":9,"180":11,"181":3,"182":7,"184":9,"185":3,"186":23,"187":5,"188":4,"189":14,"190":5,"191":1,"193":6,"194":4,"195":29,"196":2,"197":4,"198":3,"199":2,"200":11,"201":27,"202":12,"203":6,"206":5,"207":10,"208":1,"209":5,"210":1,"212":9,"213":9,"214":15,"215":5,"216":4,"217":10,"218":4,"219":18,"220":12,"221":1,"223":3,"224":2,"225":19,"226":1,"227":1,"228":3,"229":3,"230":24,"231":5,"232":1,"233":5,"234":9,"235":30,"236":2,"237":10,"238":3,"239":3,"241":10,"242":8,"243":36,"244":2,"245":20,"246":2,"247":6,"248":8,"249":4,"250":5,"251":3,"252":10,"253":5,"254":13,"255":26,"256":22,"257":20,"258":25,"259":32,"260":20,"261":18,"263":2,"264":3,"265":57,"266":5,"267":4,"268":5,"269":2,"272":2,"273":8,"274":3,"275":4,"276":5,"277":2,"278":1,"279":8,"280":6,"281":8,"282":7,"283":7,"284":5,"285":5,"286":7,"287":1,"290":6,"291":6,"292":4,"293":2,"294":1,"295":8,"296":7,"298":3,"299":1,"300":4,"301":3,"303":9,"304":2,"305":12,"306":5,"308":2,"309":4,"310":12,"311":3,"312":4,"313":1,"314":5,"316":1,"317":4,"318":29,"319":7,"320":3,"321":2,"322":4,"323":5,"324":12,"325":9,"326":11,"328":5,"329":5,"330":13,"331":5,"332":7,"333":10,"335":8,"336":3,"337":4,"339":1,"340":8,"341":27,"342":7,"343":13,"344":4,"345":6,"346":5,"348":6,"349":6,"350":35,"351":9,"352":6,"353":9,"354":4,"355":1,"358":4,"359":27,"360":10,"361":16,"363":2,"364":2,"365":16,"366":3,"368":6,"369":13,"370":14,"371":5,"372":4,"373":16,"374":8,"375":7,"376":3,"378":2,"379":5,"380":5,"381":3,"382":1,"383":4,"384":8,"386":11,"387":7,"388":10,"389":1,"390":14,"391":14,"392":10,"393":5,"394":1,"395":33,"396":6,"397":34,"398":7,"399":13,"400":9,"401":16,"402":19,"403":11,"404":9,"405":8,"406":6,"407":16,"408":9,"409":13,"411":3,"412":4,"413":23,"414":5,"415":10,"417":1,"418":17,"419":9,"420":9,"421":2,"422":7,"423":14,"424":6,"425":5,"426":11,"427":4,"428":17,"429":6,"430":4,"432":3,"433":5,"434":15,"435":6,"436":8,"437":1,"438":2,"439":4,"440":2,"441":2,"442":3,"443":15,"444":7,"446":4,"447":5,"448":24,"449":5,"450":5,"451":7,"452":5,"453":4,"454":10,"455":1,"457":3,"458":6,"459":27,"460":4,"461":7,"462":2,"463":12,"464":17,"465":15,"466":7,"468":13,"469":5,"470":10,"471":4,"472":6,"473":2,"474":6,"475":7,"476":7,"477":15,"478":5,"480":4,"481":4,"482":6,"483":4,"484":2,"486":11,"487":9,"488":8,"489":11,"490":26,"491":19,"492":23,"493":24,"494":17,"495":10,"496":1,"497":23,"498":8,"499":26,"500":7,"501":7,"502":6,"503":1,"505":2,"506":8,"507":28,"508":7,"509":6,"510":4,"511":8,"512":4,"513":8,"514":4,"516":3,"517":12,"518":13,"519":5,"520":5,"521":2,"522":12,"523":10,"524":11,"526":7,"527":8,"528":19,"529":5,"530":11,"531":5,"532":1,"534":19,"535":4,"536":53,"537":11,"538":17,"539":26,"540":10,"541":5,"543":21,"544":6,"545":43,"546":5,"547":14,"548":5,"549":46,"550":17,"551":17,"552":13,"554":12,"555":8,"556":32,"557":7,"558":8,"559":6,"560":4,"562":4,"563":4,"564":20,"565":5,"566":14,"567":3,"570":3,"571":6,"572":45,"573":2,"574":8,"575":6,"576":8,"577":10,"578":10,"579":2,"580":1,"582":7,"583":49,"584":8,"585":21,"586":5,"589":2,"590":4,"591":33,"592":9,"593":8,"594":4,"595":2,"596":1,"597":6,"598":7,"599":36,"600":6,"601":10,"602":11,"603":7,"604":7,"605":1,"607":37,"608":13,"609":60,"610":4,"611":16,"612":4,"613":4,"614":5,"615":1,"616":10,"617":6,"618":35,"619":6,"620":7,"621":7,"622":3,"624":11,"625":9,"626":62,"627":9,"628":11,"629":11,"630":5,"631":18,"632":14,"633":36,"634":7,"635":10,"636":11,"637":1,"638":12,"639":7,"640":33,"641":9,"642":15,"643":6,"644":6,"646":1,"647":4,"648":4,"649":39,"650":11,"651":6,"652":2,"655":5,"656":8,"657":51,"658":8,"659":11,"660":11,"661":1,"663":3,"664":8,"665":23,"666":8,"667":7,"668":9,"669":11,"670":1,"672":10,"673":4,"674":56,"675":4,"676":12,"677":8,"678":1,"679":1,"680":3,"681":7,"682":22,"683":10,"684":12,"685":3,"686":4,"687":1,"688":7,"689":10,"690":40,"691":19,"692":20,"693":6,"694":7,"696":17,"697":7,"698":51,"699":5,"700":10,"701":6,"702":14,"703":13,"705":3,"706":7,"707":40,"708":13,"709":11,"710":5,"711":6,"713":9,"714":4,"715":28,"716":10,"717":7,"718":2,"719":5,"720":6,"721":6,"723":5,"724":12,"725":38,"726":12,"727":13,"728":4,"729":2,"731":8,"732":5,"733":30,"734":4,"735":8,"736":6,"737":8,"739":2,"740":14,"741":30,"742":11,"743":22,"744":10,"745":8,"747":3,"748":5,"749":42,"750":7,"751":16,"752":2,"753":3,"755":4,"756":10,"757":34,"758":7,"759":11,"760":4,"761":7,"764":11,"765":12,"766":34,"767":11,"768":10,"769":4,"770":1,"773":11,"774":15,"775":8,"776":9,"777":6,"778":5,"780":9,"781":7,"782":27,"783":6,"784":7,"785":6,"786":1,"787":1,"788":1,"789":8,"790":32,"791":7,"792":15,"793":6,"794":6,"795":1,"797":11,"798":16,"799":23,"800":11,"801":8,"802":5,"803":14,"804":11,"805":7,"806":2,"808":1,"809":13,"810":13,"811":6,"812":5,"813":14,"814":1,"815":6,"816":1,"817":2,"818":12,"819":34,"820":9,"821":15,"822":5,"823":8,"824":1,"825":9,"826":20,"827":98,"828":6,"829":18,"830":11,"831":13,"832":37,"833":11,"834":7,"836":6,"837":22,"838":30,"839":12,"840":11,"841":5,"842":1,"844":3,"845":6,"846":12,"847":3,"848":6,"849":9,"850":2,"852":2,"853":7,"854":24,"855":8,"856":10,"857":12,"858":1,"859":3,"860":5,"861":27,"862":12,"863":18,"864":5,"865":2,"866":1,"867":4,"868":12,"869":20,"870":10,"871":6,"872":10,"873":8,"874":3,"875":13,"876":12,"877":11,"879":6,"880":8,"881":59,"882":8,"883":11,"884":9,"885":4,"888":4,"889":24,"890":8,"891":10,"892":6,"893":4,"896":12,"897":32,"898":6,"899":8,"900":5,"901":5,"903":7,"904":6,"905":73,"906":22,"907":24,"908":5,"909":5,"910":13,"912":4,"913":12,"914":36,"915":5,"916":11,"917":6,"918":5,"920":6,"921":12,"922":23,"923":11,"924":14,"925":4,"926":52,"927":8,"929":3,"930":10,"931":17,"932":6,"933":8,"934":1,"935":2,"937":3,"938":5,"939":11,"940":6,"941":6,"942":3,"944":2,"945":5,"946":30,"947":6,"948":4,"949":5,"950":2,"952":1,"953":11,"954":34,"955":9,"956":8,"957":5,"958":1,"959":5,"960":8,"962":2,"963":10,"964":28,"965":9,"966":7,"967":7,"968":4,"971":9,"972":28,"973":9,"974":7,"975":6,"976":2,"979":13,"980":37,"981":6,"982":13,"983":11,"984":3,"985":1,"986":5,"987":8,"988":47,"989":6,"990":8,"991":9,"992":5,"994":6,"995":8,"996":37,"997":10,"998":17,"999":9,"1000":3,"1002":1,"1003":7,"1004":40,"1005":5,"1006":9,"1007":5,"1008":2,"1010":2,"1011":12,"1012":36,"1013":4,"1014":7,"1016":18,"1017":14,"1018":90,"1019":23,"1020":19,"1021":10,"1022":3,"1024":3,"1025":8,"1026":26,"1027":10,"1028":6,"1029":5,"1030":3,"1032":2,"1033":11,"1034":39,"1035":10,"1036":8,"1037":8,"1040":2,"1041":12,"1042":26,"1043":17,"1044":16,"1045":7,"1046":1,"1048":4,"1049":17,"1050":53,"1051":22,"1052":15,"1053":4,"1054":10,"1055":9,"1057":3,"1058":13,"1059":48,"1060":8,"1061":9,"1062":7,"1063":1,"1065":1,"1066":16,"1067":34,"1068":10,"1069":11,"1070":3,"1071":1,"1072":1,"1073":6,"1074":32,"1075":25,"1076":5,"1077":5,"1078":4,"1079":6,"1081":4,"1082":15,"1083":11,"1084":4,"1085":7,"1086":3,"1087":2,"1089":17,"1090":16,"1091":75,"1092":17,"1093":22,"1094":12,"1095":9,"1096":8,"1099":11,"1100":28,"1101":6,"1102":6,"1103":5,"1104":3,"1105":1,"1107":7,"1108":13,"1109":3,"1110":3,"1111":4,"1112":3,"1114":3,"1115":14,"1116":43,"1117":18,"1118":8,"1119":7,"1120":1,"1122":2,"1123":11,"1124":42,"1125":10,"1126":8,"1127":10,"1128":6,"1129":5,"1132":12,"1133":27,"1134":5,"1135":8,"1136":2,"1137":3,"1139":3,"1140":13,"1141":3,"1142":6,"1143":2,"1144":10,"1145":2,"1146":1,"1147":2,"1149":3,"1150":10,"1151":2,"1152":5,"1153":3,"1154":9,"1155":2,"1156":2,"1157":6,"1158":1,"1160":5,"1161":25,"1162":5,"1163":4,"1164":3,"1167":6,"1168":30,"1169":6,"1170":6,"1171":3,"1173":1,"1174":5,"1175":28,"1176":7,"1177":5,"1178":5,"1179":2,"1180":2,"1182":3,"1183":8,"1184":30,"1185":10,"1186":8,"1187":10,"1188":5,"1189":3,"1190":5,"1191":12,"1192":13,"1193":3,"1194":1,"1195":3,"1196":2,"1200":4,"1201":7,"1202":29,"1203":29,"1210":1,"1211":14,"1212":168,"1213":20,"1214":9,"1215":4,"1216":4,"1217":8,"1218":9,"1219":8,"1220":17,"1221":4,"1222":7,"1223":11,"1224":3,"1225":8,"1226":3,"1227":4,"1228":11,"1229":235,"1230":21,"1231":27,"1232":13,"1233":13,"1234":30,"1235":7,"1236":16,"1237":398,"1238":16,"1239":22,"1240":15,"1241":45,"1242":24,"1243":27,"1244":29,"1245":7,"1246":11,"1247":525,"1248":9,"1249":14,"1250":4,"1251":10,"1252":20,"1253":15,"1254":8,"1255":7,"1256":18,"1257":14,"1258":8,"1259":635,"1260":22,"1261":8,"1262":14,"1263":51,"1264":19,"1265":42,"1266":11,"1267":21,"1268":6,"1269":25,"1270":881,"1271":241,"1272":11,"1273":31,"1274":24,"1275":39,"1276":31,"1277":16,"1278":32,"1279":18,"1280":25,"1281":33,"1282":16,"1283":17,"1284":22,"1285":22,"1286":2531,"1287":23,"1288":12,"1289":27,"1290":33,"1291":12,"1292":11,"1293":28,"1294":21,"1295":15,"1296":10,"1297":36,"1298":10,"1299":3,"1300":1781,"1301":336,"1302":17,"1303":12,"1304":26,"1305":10,"1306":12,"1307":21,"1308":11,"1309":597,"1310":141,"1311":1585,"1312":202,"1313":20,"1314":5,"1315":20,"1316":35,"1317":27,"1318":30,"1319":4,"1320":40,"1321":1,"1322":29,"1323":1381,"1324":2667,"1325":13,"1326":26,"1327":8,"1328":39,"1329":6,"1330":13,"1331":8,"1332":36,"1333":40,"1334":18,"1335":15,"1336":29,"1337":29,"1338":33,"1339":1199,"1340":15,"1341":14,"1342":53,"1343":19,"1344":28,"1345":11,"1346":15,"1347":43,"1348":16,"1349":19,"1350":1268,"1351":10,"1352":52,"1353":41,"1354":20,"1355":38,"1356":11,"1357":23,"1358":21,"1359":4203,"1360":9,"1361":2,"1362":33,"1363":10,"1364":21,"1365":7,"1366":47,"1367":11,"1368":14,"1369":384,"1370":7,"1371":22,"1372":17,"1373":29,"1374":12,"1375":27,"1376":11,"1377":14,"1378":19,"1379":22,"1380":720,"1381":8,"1382":20,"1383":23,"1384":26,"1385":21,"1386":6,"1387":13,"1388":16,"1389":28,"1390":16,"1391":23,"1392":16,"1393":14,"1394":7,"1395":2486,"1396":2605,"1397":10,"1398":22,"1399":34,"1400":21,"1401":22,"1402":1114,"1403":11,"1404":18,"1405":20,"1406":42,"1407":11,"1408":19,"1409":26,"1410":24,"1411":10,"1412":11,"1413":18,"1414":30,"1415":1412,"1416":349,"1417":1142,"1418":15,"1419":9,"1420":13,"1421":22,"1422":24,"1423":21,"1424":19,"1425":19,"1426":10,"1427":250,"1428":20,"1429":23,"1430":32,"1431":66,"1432":29,"1433":48,"1434":11,"1435":41,"1436":4212,"1437":56,"1438":553,"1439":9,"1440":6,"1441":106,"1442":52,"1443":89,"1444":27,"1445":47,"1446":22,"1447":70,"1448":3,"1449":27,"1450":15,"1451":7,"1452":12,"1453":68,"1454":48,"1455":254,"1456":36,"1457":11,"1458":17,"1459":43,"1460":37,"1461":19,"1462":3,"1463":18,"1464":8,"1465":35,"1466":10,"1467":418,"1468":2,"1469":8,"1470":18,"1471":12,"1472":21,"1473":19,"1474":47,"1475":63,"1476":55,"1477":21,"1478":28,"1479":4,"1480":13,"1481":19,"1482":22,"1483":7,"1484":10,"1485":6,"1486":36,"1487":74,"1488":129,"1489":99,"1490":67,"1491":14,"1492":121,"1493":9,"1494":5,"1495":1,"1496":460,"1497":9,"1498":10,"1499":1,"1500":10,"1501":1,"1502":2,"1503":11,"1504":8,"1505":1,"1506":2,"1507":3,"1508":3,"1509":7,"1511":2,"1512":2,"1513":4,"1514":1,"1515":3,"1516":1,"1517":3,"1518":3,"1519":4,"1521":3,"1522":1,"1523":6,"1524":21,"1525":19,"1526":224,"1527":3,"1528":2,"1529":2,"1530":8,"1531":34,"1532":13,"1533":1,"1534":40,"1535":21,"1536":11,"1537":5,"1538":2,"1539":3,"1540":2,"1541":3,"1542":1,"1543":6,"1544":5,"1545":6,"1546":4,"1547":13,"1548":9,"1549":1,"1550":5,"1551":3,"1552":1,"1553":14,"1554":6,"1555":4,"1556":2,"1557":1,"1558":3,"1559":3,"1560":1,"1561":3,"1562":4,"1563":4,"1564":3,"1565":5,"1566":7,"1567":5,"1568":2,"1569":2,"1570":9,"1571":10,"1572":16,"1574":11,"1575":26,"1576":15,"1577":213,"1578":8,"1579":2,"1580":1,"1581":8,"1582":62,"1583":17,"1584":6,"1585":32,"1586":23,"1588":6,"1589":14,"1590":1,"1591":129,"1592":4,"1593":1,"1594":3,"1595":8,"1596":54,"1597":16,"1598":2,"1599":16,"1600":24,"1601":1,"1602":2,"1604":1,"1605":2,"1607":8,"1608":2,"1611":12,"1614":1,"1620":1,"1621":3,"1622":1,"1623":1,"1624":1,"1625":3,"1626":12,"1627":38,"1629":24,"1630":32,"1631":130,"1632":43,"1633":5,"1634":4,"1635":14,"1636":9,"1637":9,"1638":33,"1639":61,"1640":30,"1641":84,"1642":2,"1643":3,"1644":4,"1645":1,"1646":10,"1647":8,"1648":7,"1649":5,"1650":7,"1651":40,"1652":7,"1653":37,"1654":19,"1655":5,"1656":14,"1657":4,"1658":5,"1659":2,"1660":20,"1661":16,"1662":32,"1663":17,"1664":24,"1665":19,"1666":41,"1667":35,"1668":26,"1669":41,"1670":39,"1671":40,"1672":10,"1673":27,"1674":15,"1675":13,"1676":3,"1677":18,"1678":4,"1679":2,"1680":2,"1681":7,"1682":3,"1683":8,"1684":25,"1685":43,"1686":66,"1687":16,"1688":2,"1689":12,"1690":3,"1691":4,"1692":9,"1693":5,"1694":1,"1695":4,"1696":3,"1697":8,"1698":10,"1699":5,"1700":11,"1701":19,"1702":11,"1703":7,"1704":4,"1705":2,"1706":7,"1707":10,"1708":4,"1709":1,"1710":2,"1711":3,"1712":2,"1713":2,"1714":1,"1715":5,"1716":1,"1717":6,"1718":10,"1719":17,"1720":14,"1721":11,"1722":4,"1723":4,"1724":11,"1725":2,"1726":6,"1727":59,"1728":18,"1729":18,"1730":5,"1731":10,"1732":4,"1733":3,"1735":8,"1737":1,"1739":2,"1741":8,"1742":7,"1747":7,"1748":20,"1749":24,"1750":15,"1751":4,"1752":3,"1755":10,"1756":1,"1757":2,"1758":7,"1759":2,"1760":9,"1761":9,"1763":6,"1764":21,"1765":69,"1766":2,"1767":21,"1768":14,"1769":32,"1770":10,"1771":8,"1772":1,"1773":8,"1775":1,"1776":15,"1777":4,"1778":1,"1779":14,"1780":8,"1781":10,"1782":4,"1783":7,"1784":14,"1785":4,"1786":2,"1787":4,"1788":8,"1789":3,"1790":18,"1792":8,"1793":8,"1794":5,"1795":4,"1796":31,"1797":5,"1798":1,"1799":11,"1800":10,"1801":6,"1802":10,"1803":5,"1804":6,"1805":26,"1806":9,"1807":7,"1808":4,"1809":4,"1810":40,"1811":9,"1812":8,"1813":2,"1814":6,"1815":61,"1816":6,"1817":16,"1818":9,"1819":3,"1820":5,"1821":20,"1822":5,"1823":6,"1824":9,"1825":22,"1826":11,"1827":8,"1828":8,"1829":9,"1830":7,"1831":5,"1832":6,"1833":7,"1834":10,"1835":2,"1836":3,"1837":3,"1838":4,"1839":11,"1840":19,"1841":10,"1842":13,"1843":18,"1844":11,"1845":9,"1846":7,"1847":8,"1848":2,"1849":18,"1850":2,"1851":27,"1852":10,"1853":12,"1854":8,"1855":5,"1856":13,"1857":5,"1858":7,"1859":6,"1860":10,"1861":10,"1862":12,"1863":7,"1864":3,"1865":9,"1866":7,"1867":2,"1868":8,"1869":10,"1870":2,"1871":10,"1872":3,"1873":4,"1874":12,"1875":8,"1876":14,"1877":10,"1878":5,"1879":5,"1880":3,"1881":9,"1882":5,"1883":9,"1884":5,"1885":4,"1886":9,"1887":9,"1888":11,"1889":18,"1890":2,"1891":9,"1892":14,"1893":5,"1894":3,"1895":6,"1896":4,"1897":5,"1898":19,"1899":11,"1900":3,"1901":2,"1902":21,"1903":7,"1904":6,"1905":12,"1906":5,"1907":4,"1908":12,"1909":30,"1910":10,"1911":13,"1912":17,"1913":1,"1914":3,"1915":8,"1916":15,"1917":9,"1918":11,"1919":5,"1920":20,"1921":6,"1922":38,"1923":43,"1924":19,"1925":8,"1926":2,"1927":16,"1928":5,"1929":12,"1930":9,"1931":10,"1932":11,"1933":26,"1934":8,"1935":6,"1936":13,"1937":8,"1938":9,"1939":4,"1940":14,"1941":14,"1942":11,"1943":4,"1944":14,"1945":9,"1946":14,"1947":15,"1948":27,"1949":9,"1950":18,"1951":8,"1952":6,"1953":10,"1954":5,"1955":5,"1956":8,"1957":2,"1958":8,"1959":9,"1960":4,"1961":4,"1962":8,"1963":10,"1964":8,"1965":13,"1966":11,"1967":5,"1968":13,"1969":1,"1970":18,"1971":9,"1972":12,"1973":15,"1974":4,"1975":17,"1976":9,"1977":21,"1978":12,"1979":20,"1980":10,"1981":3,"1982":23,"1983":5,"1984":10,"1985":17,"1986":8,"1987":6,"1988":23,"1989":7,"1990":9,"1991":15,"1992":6,"1993":4,"1994":21,"1995":29,"1996":13,"1997":20,"1998":8,"1999":9,"2000":57,"2001":28,"2002":24,"2003":9,"2004":7,"2005":6,"2006":20,"2007":7,"2008":8,"2009":4,"2010":23,"2011":8,"2012":12,"2013":9,"2014":13,"2015":6,"2016":4,"2017":7,"2018":4,"2019":5,"2020":7,"2021":3,"2022":3,"2023":7,"2024":22,"2025":15,"2026":4,"2027":9,"2028":17,"2029":8,"2030":12,"2031":2,"2032":16,"2033":6,"2034":18,"2035":9,"2036":14,"2037":9,"2038":18,"2039":7,"2040":5,"2041":7,"2042":8,"2043":31,"2044":21,"2045":7,"2046":13,"2047":14,"2048":18,"2049":11,"2050":8,"2051":2,"2052":6,"2053":7,"2054":4,"2055":38,"2056":15,"2057":25,"2058":6,"2059":7,"2060":12,"2061":9,"2062":3,"2063":20,"2064":8,"2065":3,"2066":12,"2067":15,"2068":7,"2069":13,"2070":14,"2071":3,"2072":9,"2073":8,"2074":3,"2075":18,"2076":10,"2077":14,"2078":7,"2079":19,"2080":19,"2081":16,"2082":3,"2083":22,"2084":2,"2085":15,"2086":19,"2087":8,"2088":4,"2089":12,"2090":5,"2091":7,"2092":2,"2093":2,"2094":4,"2095":6,"2096":13,"2097":17,"2098":16,"2099":6,"2100":5,"2101":5,"2102":3,"2103":4,"2104":10,"2105":5,"2106":2,"2107":9,"2108":6,"2109":6,"2110":17,"2111":11,"2112":7,"2113":9,"2114":12,"2115":8,"2116":11,"2117":7,"2118":9,"2119":9,"2120":9,"2121":10,"2122":12,"2123":10,"2124":11,"2125":3,"2126":29,"2127":13,"2128":12,"2129":9,"2130":6,"2131":32,"2132":12,"2133":14,"2134":10,"2135":7,"2136":10,"2137":1,"2138":20,"2139":10,"2140":4,"2141":4,"2142":31,"2143":7,"2144":9,"2145":10,"2146":17,"2147":12,"2148":10,"2149":18,"2150":23,"2151":8,"2152":6,"2153":20,"2154":20,"2155":6,"2156":47,"2157":17,"2158":30,"2159":17,"2160":26,"2161":14,"2162":9,"2163":13,"2164":13,"2165":17,"2166":20,"2167":39,"2168":24,"2169":27,"2170":10,"2171":7,"2172":10,"2173":7,"2174":11,"2175":10,"2176":11,"2177":14,"2178":5,"2179":13,"2180":32,"2181":9,"2182":16,"2183":12,"2184":2,"2185":9,"2186":14,"2187":3,"2188":10,"2189":6,"2190":18,"2191":10,"2192":19,"2193":26,"2194":12,"2195":6,"2196":10,"2197":7,"2198":12,"2199":11,"2200":12,"2201":5,"2202":23,"2203":16,"2204":4,"2205":2,"2206":4,"2207":4,"2208":10,"2209":4,"2210":10,"2211":6,"2212":2,"2213":11,"2214":8,"2215":3,"2216":6,"2217":3,"2218":5,"2219":1,"2220":36,"2221":12,"2224":2,"2225":5,"2227":10,"2228":5,"2229":1,"2230":18,"2231":2,"2232":105,"2233":5,"2234":3,"2235":1,"2236":3,"2237":1,"2238":3,"2239":14,"2240":4,"2241":1,"2242":4,"2243":2,"2244":1,"2245":5}}],["ar",{"3":{"2220":2}}],["arbiter",{"3":{"691":1,"1304":1,"1309":2,"1311":1,"1396":2}}],["arbitrating",{"3":{"1436":1}}],["arbitration",{"3":{"1286":2,"1359":1}}],["arbitrated",{"3":{"1395":1,"1985":1}}],["arbitrate",{"3":{"1278":1,"1286":2,"1309":1,"1941":1}}],["arbitrates",{"3":{"0":1,"903":1,"905":2,"1286":3,"1290":1,"1300":1,"1324":1,"1934":1,"1982":1}}],["arbitrarily",{"3":{"1261":1,"1300":1,"1395":1,"1820":1}}],["arbitrary",{"3":{"0":1,"110":1,"219":1,"320":1,"332":1,"380":1,"774":1,"940":1,"1212":1,"1237":1,"1270":1,"1286":3,"1300":2,"1301":1,"1309":3,"1312":1,"1323":3,"1324":11,"1336":1,"1339":1,"1350":3,"1356":1,"1359":3,"1369":1,"1395":2,"1396":1,"1402":1,"1415":2,"1416":1,"1431":1,"1436":4,"1443":1,"1458":1,"1467":1,"1640":1,"1989":1,"1990":1,"1991":1,"1996":1,"2037":1,"2232":1}}],["archunit",{"3":{"2049":1}}],["archunitnet",{"3":{"1540":1,"2049":1}}],["archetype",{"3":{"1380":1}}],["arch",{"3":{"1214":1,"1487":1,"1489":1,"1526":6,"1530":1,"1531":1,"1532":1,"1535":2,"1577":2,"1586":1,"1591":5,"1595":1,"1597":1,"1600":1,"2042":1,"2107":1}}],["archaeology",{"3":{"838":1,"1019":1,"1300":1,"1840":1}}],["archaeological",{"3":{"88":1}}],["architected",{"3":{"2220":5,"2238":1}}],["architecting",{"3":{"2213":1,"2218":1}}],["architect",{"1":{"2212":1,"2213":1,"2214":1,"2215":1,"2216":1},"3":{"2212":1,"2213":1,"2217":1,"2218":1,"2220":3,"2224":1}}],["architecturally",{"3":{"1815":1}}],["architectural",{"0":{"1212":1,"1662":1,"2230":1},"3":{"132":1,"643":1,"1004":1,"1210":1,"1239":1,"1259":1,"1270":1,"1286":2,"1310":2,"1396":1,"1428":1,"1436":8,"1439":1,"1447":1,"1467":1,"1471":1,"1489":1,"1523":1,"1602":1,"1795":1,"1800":1,"1802":1,"2099":1,"2213":1,"2218":1,"2220":1}}],["architecturalanalysis",{"3":{"1":1,"46":1,"1190":1,"1525":1,"1526":2,"1577":2,"1582":1,"1586":1}}],["architecturemap",{"3":{"1728":1,"2097":1}}],["architecturemapbase",{"2":{"975":1,"2103":1},"3":{"972":1,"974":1,"975":1,"1199":47,"1207":2,"1428":1,"1431":5,"1436":92,"1654":1,"2042":1,"2103":1}}],["architectureremediation",{"3":{"1594":1}}],["architecturerules",{"2":{"39":1,"135":2,"136":1,"142":1,"305":1,"576":1,"657":1,"846":1,"849":1,"1431":1,"1489":1,"2043":1,"2044":2,"2102":1,"2103":1,"2138":1},"3":{"0":1,"17":1,"27":1,"39":5,"54":1,"78":1,"80":2,"81":2,"82":1,"109":1,"132":1,"135":5,"136":3,"139":3,"142":1,"147":1,"160":3,"161":1,"168":1,"171":1,"305":2,"341":2,"576":1,"657":3,"782":4,"798":1,"801":1,"804":1,"846":1,"849":1,"963":1,"966":1,"980":1,"982":1,"1004":2,"1005":1,"1006":2,"1050":1,"1074":1,"1078":1,"1083":1,"1091":1,"1160":1,"1161":3,"1162":2,"1163":2,"1168":12,"1169":1,"1170":3,"1199":68,"1207":79,"1235":1,"1236":1,"1237":5,"1239":3,"1247":3,"1259":1,"1270":1,"1286":2,"1300":5,"1311":7,"1312":6,"1324":1,"1359":1,"1415":1,"1428":1,"1431":17,"1436":369,"1489":8,"1496":4,"1524":1,"1526":3,"1531":1,"1535":1,"1576":1,"1577":9,"1582":1,"1583":1,"1586":2,"1591":1,"1596":1,"1660":1,"1671":1,"1875":1,"2042":1,"2043":1,"2044":3,"2085":1,"2102":1,"2103":1,"2138":2}}],["architecturetesthelper",{"2":{"2042":1},"3":{"1436":1,"2042":1}}],["architecturetests",{"2":{"1689":1},"3":{"80":2,"128":1,"633":1,"635":1,"636":1,"1133":2,"1136":1,"1168":1,"1436":23,"1489":1,"1689":1}}],["architectureassert",{"3":{"1199":10,"1207":2,"1428":1,"1431":4,"1436":25,"1489":1}}],["architectureevaluation",{"3":{"1193":1}}],["architectureevaluationcriteria",{"2":{"375":1,"1522":1,"1573":1,"1577":1,"1587":1},"3":{"369":2,"371":1,"375":1,"376":1,"590":1,"594":1,"860":1,"864":1,"1003":1,"1007":1,"1011":1,"1090":1,"1092":1,"1093":1,"1216":1,"1522":1,"1526":14,"1527":1,"1531":1,"1573":1,"1577":9,"1578":1,"1587":1,"1591":3,"1596":2,"1598":1,"2155":1,"2162":1,"2171":1,"2179":1,"2182":1,"2195":1}}],["architecturescorecard",{"2":{"1529":1,"1594":1},"3":{"1006":1,"1007":1,"1012":3,"1017":1,"1090":1,"1094":1,"1095":1,"1216":3,"1496":1,"1526":1,"1529":1,"1577":2,"1591":1,"1594":1,"2171":1,"2179":2,"2182":1,"2195":1}}],["architecture",{"0":{"1216":1,"1431":1,"1489":1,"1504":1,"1540":1,"1542":1,"1545":1,"1547":1,"1553":1,"1555":1,"1562":1,"1571":1,"1641":1,"1654":1,"1795":1,"2213":1,"2231":1,"2232":1},"1":{"0":1,"1":1,"129":1,"130":1,"131":1,"132":1,"133":1,"134":1,"135":1,"136":1,"137":1,"138":1,"139":1,"140":1,"141":1,"142":1,"143":1,"1485":1,"1486":1,"1487":1,"1488":1,"1489":1,"1490":1,"1491":1,"1492":1,"1493":1,"1494":1,"1518":1,"1519":1,"1520":1,"1521":1,"1522":1,"1523":1,"1524":1,"1525":1,"1526":1,"1527":1,"1528":1,"1529":1,"1530":1,"1531":1,"1532":1,"1533":1,"1534":1,"1535":1,"1536":1,"1537":1,"1538":1,"1539":1,"1540":1,"1541":1,"1542":1,"1543":1,"1544":1,"1545":1,"1546":1,"1547":1,"1548":1,"1549":1,"1550":1,"1551":1,"1552":1,"1553":1,"1554":1,"1555":1,"1556":1,"1557":1,"1558":1,"1559":1,"1560":1,"1561":1,"1562":1,"1563":1,"1564":1,"1565":1,"1566":1,"1567":1,"1568":1,"1569":1,"1570":1,"1571":1,"1572":1,"1573":1,"1574":1,"1575":1,"1576":1,"1577":1,"1578":1,"1579":1,"1580":1,"1581":1,"1582":1,"1583":1,"1584":1,"1585":1,"1586":1,"1587":1,"1588":1,"1589":1,"1590":1,"1591":1,"1592":1,"1593":1,"1594":1,"1595":1,"1596":1,"1597":1,"1598":1,"1599":1,"1600":1,"1794":1,"1795":1,"1796":1,"1797":1,"1798":1,"1799":1,"1800":1,"1801":1,"1802":1,"2039":1,"2040":1,"2041":1,"2042":1,"2043":1,"2044":1,"2045":1,"2046":1,"2047":1,"2048":1,"2049":1,"2099":1,"2100":1,"2101":1,"2102":1,"2103":1,"2104":1,"2105":1,"2106":1,"2107":1,"2108":1},"2":{"78":1,"80":3,"132":1,"265":1,"572":1,"1161":1,"1211":1,"1214":1,"1251":1,"1360":1,"1381":1,"1394":1,"1418":1,"1428":1,"1436":2,"1476":1,"1486":1,"1497":1,"1523":1,"1525":1,"1526":1,"1576":1,"1577":1,"1654":1,"1660":1,"1728":1,"1782":1,"2042":1,"2044":1,"2046":1,"2049":1,"2055":1,"2101":1,"2108":1,"2111":2},"3":{"0":7,"1":1,"17":2,"27":6,"39":1,"41":2,"54":2,"58":1,"59":1,"62":2,"78":1,"80":10,"81":3,"82":1,"85":1,"107":1,"109":7,"117":2,"121":6,"127":4,"128":7,"129":1,"131":1,"132":1,"135":5,"136":3,"137":2,"139":1,"140":2,"142":1,"147":4,"150":1,"160":1,"168":1,"189":1,"265":2,"305":3,"341":6,"370":1,"376":1,"381":1,"477":3,"545":7,"552":2,"565":1,"571":2,"572":16,"576":1,"585":1,"587":1,"590":1,"594":1,"609":13,"613":3,"618":1,"622":1,"631":1,"632":1,"633":1,"635":2,"657":1,"676":1,"678":1,"700":1,"718":2,"719":2,"749":6,"782":7,"798":1,"846":1,"849":1,"868":1,"881":9,"884":1,"899":1,"938":1,"939":1,"946":3,"954":1,"966":1,"971":7,"972":16,"974":4,"975":4,"980":2,"982":2,"1003":1,"1004":12,"1006":1,"1007":1,"1011":1,"1012":2,"1017":1,"1018":3,"1022":1,"1033":1,"1050":10,"1052":1,"1054":1,"1055":1,"1067":2,"1074":4,"1083":7,"1085":2,"1090":1,"1091":1,"1118":1,"1132":1,"1133":10,"1136":2,"1160":1,"1161":11,"1168":14,"1175":2,"1190":2,"1192":2,"1194":1,"1199":379,"1201":1,"1202":1,"1203":4,"1206":3,"1207":1254,"1208":6,"1210":1,"1211":5,"1212":6,"1213":1,"1214":5,"1216":7,"1217":1,"1229":2,"1230":1,"1231":1,"1232":1,"1235":1,"1236":4,"1237":35,"1239":2,"1243":1,"1247":12,"1249":1,"1251":1,"1257":2,"1259":46,"1262":4,"1263":1,"1270":16,"1271":11,"1272":2,"1286":113,"1290":1,"1300":35,"1301":5,"1303":2,"1306":1,"1309":17,"1310":8,"1311":40,"1312":12,"1313":2,"1316":3,"1322":1,"1323":48,"1324":70,"1326":1,"1339":7,"1341":5,"1343":1,"1344":1,"1345":1,"1346":1,"1349":1,"1350":35,"1352":2,"1359":83,"1360":2,"1361":1,"1369":21,"1371":3,"1377":1,"1380":15,"1381":1,"1382":1,"1389":2,"1394":1,"1395":97,"1396":72,"1402":15,"1404":3,"1409":1,"1412":1,"1415":51,"1416":7,"1417":25,"1418":1,"1427":14,"1428":6,"1431":34,"1432":1,"1436":2481,"1437":2,"1441":1,"1446":1,"1447":1,"1450":1,"1452":1,"1453":1,"1455":8,"1457":1,"1459":1,"1461":3,"1467":14,"1469":1,"1471":1,"1473":2,"1476":4,"1478":1,"1483":2,"1485":2,"1486":3,"1487":6,"1488":7,"1489":39,"1491":1,"1492":4,"1493":2,"1494":1,"1496":27,"1497":2,"1499":8,"1518":1,"1519":3,"1521":1,"1522":3,"1523":3,"1525":9,"1526":58,"1527":3,"1528":1,"1529":1,"1530":3,"1531":7,"1532":3,"1533":1,"1534":8,"1535":12,"1536":7,"1537":1,"1540":1,"1551":1,"1553":1,"1554":1,"1571":4,"1572":2,"1573":3,"1575":1,"1576":4,"1577":59,"1578":3,"1579":1,"1580":1,"1581":2,"1582":14,"1583":7,"1584":2,"1585":2,"1586":10,"1587":3,"1588":3,"1590":1,"1591":42,"1592":2,"1593":1,"1594":2,"1595":1,"1596":3,"1597":3,"1598":1,"1599":1,"1600":7,"1604":1,"1631":2,"1638":1,"1641":1,"1646":1,"1647":1,"1650":1,"1653":1,"1654":5,"1657":1,"1659":2,"1660":2,"1661":3,"1671":1,"1673":3,"1675":2,"1681":1,"1683":1,"1684":1,"1686":3,"1690":1,"1692":1,"1696":1,"1701":2,"1719":1,"1720":1,"1726":1,"1728":2,"1729":3,"1763":1,"1769":1,"1775":1,"1778":1,"1779":1,"1780":3,"1781":2,"1782":3,"1784":4,"1786":1,"1787":1,"1793":3,"1794":3,"1796":12,"1797":1,"1798":1,"1799":4,"1802":4,"1807":1,"1812":1,"1817":1,"1827":1,"1831":1,"1834":2,"1846":1,"1852":1,"1853":1,"1860":1,"1862":1,"1868":1,"1877":1,"1886":1,"1892":2,"1899":1,"1902":2,"1905":3,"1915":1,"1916":1,"1924":1,"1929":1,"1930":3,"1931":1,"1941":1,"1950":1,"1959":1,"1965":1,"1979":1,"1985":1,"1991":1,"1997":1,"2002":1,"2015":1,"2016":1,"2028":2,"2031":1,"2033":1,"2038":2,"2039":2,"2040":5,"2041":3,"2042":1,"2043":2,"2044":12,"2046":7,"2049":6,"2050":1,"2051":1,"2052":1,"2053":2,"2054":1,"2055":3,"2059":1,"2060":2,"2062":1,"2063":1,"2064":1,"2069":2,"2071":1,"2080":1,"2085":1,"2086":1,"2097":2,"2098":4,"2099":2,"2100":1,"2101":3,"2102":3,"2107":1,"2108":7,"2109":1,"2111":5,"2114":1,"2115":3,"2120":1,"2123":1,"2126":1,"2128":1,"2133":2,"2139":1,"2153":1,"2160":1,"2169":2,"2171":2,"2178":2,"2179":1,"2180":2,"2182":1,"2193":2,"2203":2,"2204":2,"2205":1,"2210":2,"2211":1,"2212":3,"2213":5,"2214":2,"2216":1,"2217":2,"2218":2,"2219":4,"2220":4,"2221":4,"2224":1,"2225":1,"2226":3,"2227":3,"2230":2,"2232":1,"2233":3,"2236":1,"2239":6,"2242":1,"2243":2,"2244":1,"2245":3}}],["archival",{"3":{"1467":1,"1568":1,"1631":1,"1678":1}}],["archivetickethandler",{"3":{"1199":1,"1207":1,"1436":5}}],["archiveticketcommand",{"3":{"1199":3,"1207":1,"1436":9}}],["archived",{"3":{"372":1,"1469":1,"1591":1,"1600":1,"1678":1}}],["archives",{"3":{"0":1,"443":1,"1467":1}}],["archive",{"0":{"1478":1},"3":{"0":1,"45":1,"47":1,"195":1,"562":1,"717":1,"764":1,"766":3,"947":1,"1116":1,"1285":1,"1286":2,"1323":1,"1342":1,"1350":1,"1389":1,"1441":1,"1455":1,"1467":6,"1469":1,"1471":1,"1473":6,"1474":7,"1475":2,"1478":4,"1482":2,"1483":3,"1484":1,"1486":1,"1534":1,"1730":1,"2034":1,"2036":3,"2124":1}}],["arthur",{"3":{"2220":1}}],["art",{"3":{"38":1}}],["articulo",{"3":{"1591":1}}],["article",{"1":{"1778":1,"1779":1},"3":{"365":1,"527":1,"1074":2,"1271":1,"1359":4,"1778":1,"1779":3,"1780":1,"1786":2,"1790":2,"1794":1,"1803":1,"1810":1,"1815":1,"1828":1,"1831":1,"1839":1,"1840":1,"1844":1,"1849":1,"1851":2,"1855":4,"1860":1,"1898":1,"1931":1,"1935":1,"1942":1,"1947":2,"1948":1,"1951":1,"1953":1,"1957":1,"1964":1,"1968":1,"1992":3,"1994":1,"1997":1,"2011":1,"2015":1,"2029":4,"2030":3,"2032":1,"2033":1,"2037":1,"2041":1,"2046":1,"2047":1,"2049":1,"2055":1,"2061":1,"2062":1,"2085":1,"2089":1,"2091":1,"2098":2,"2100":1,"2109":1,"2122":1,"2126":1,"2133":3,"2139":2,"2146":3,"2153":3,"2156":1,"2158":1,"2160":3,"2165":1,"2168":1,"2169":3,"2180":3,"2183":2,"2184":1,"2189":1,"2193":2,"2194":5,"2199":1,"2202":1,"2203":3,"2204":1,"2211":2,"2215":3,"2235":1,"2236":1,"2237":1}}],["articles",{"0":{"1779":1},"3":{"0":1,"1011":1,"1012":1,"1014":1,"1860":1,"2029":1,"2031":1,"2204":1,"2215":1,"2234":1}}],["artificial",{"3":{"1436":1,"1489":1}}],["artifacts",{"0":{"1209":1,"1482":1},"3":{"150":1,"529":1,"591":3,"837":1,"861":1,"862":1,"1191":1,"1309":2,"1359":1,"1396":1,"1427":1,"1436":1,"1454":1,"1456":3,"1461":1,"1473":2,"1482":1,"1489":2,"1490":1,"1492":2,"1496":1,"1518":1,"1553":1,"1577":1,"1591":1,"1671":1,"2047":1,"2233":1}}],["artifact",{"0":{"451":1,"1426":1},"3":{"0":2,"371":1,"373":1,"450":1,"451":2,"483":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"591":1,"609":1,"627":1,"690":1,"700":1,"709":1,"861":2,"862":1,"863":2,"873":1,"973":1,"974":1,"1019":1,"1090":1,"1216":1,"1259":1,"1270":1,"1309":4,"1311":2,"1323":2,"1339":2,"1359":2,"1396":1,"1415":1,"1416":1,"1431":2,"1433":2,"1436":13,"1445":1,"1454":2,"1455":3,"1456":3,"1465":1,"1466":1,"1471":1,"1482":1,"1483":1,"1492":2,"1496":4,"1526":1,"1577":2,"1948":1,"2045":1,"2047":1,"2132":1,"2180":1}}],["argon2id",{"3":{"1902":1,"1904":1,"1905":1}}],["argv",{"3":{"1323":1}}],["args",{"3":{"869":1,"1247":1,"1286":5,"1309":1,"1312":2,"1324":9,"1402":1,"1416":2,"1417":10,"1436":1,"1577":1}}],["arguing",{"3":{"1018":1,"1436":1}}],["argues",{"3":{"813":1,"945":1,"1017":1,"1051":1,"1259":1,"1270":1,"1286":2,"1300":1,"1323":1,"1324":1,"1339":2,"1396":5,"1436":1,"1459":1,"1952":1,"2163":1}}],["argue",{"0":{"1787":1},"3":{"465":1,"820":1,"871":1,"1436":2,"1816":2,"1833":2,"1852":2,"1946":1,"1964":1,"1996":1,"2001":1,"2048":2,"2059":1,"2079":2,"2085":2,"2122":2,"2152":2}}],["argued",{"3":{"109":2,"463":1,"716":1,"799":1,"905":5,"1270":5,"1311":1,"1323":1,"1324":1,"1332":1,"1339":1,"1395":1,"1436":2,"1447":1}}],["argumentguards",{"3":{"1436":1}}],["argumentguardfixture",{"3":{"1199":2,"1207":1,"1436":2}}],["argumenttypes",{"3":{"1286":3}}],["argumentoutofrangeexception",{"2":{"1229":1,"1399":1},"3":{"1229":2,"1300":1,"1399":1,"1402":1,"1436":4}}],["argumentexception",{"2":{"359":1,"1221":1,"1237":1,"1247":1,"1312":1,"1427":1,"2142":1},"3":{"359":1,"897":1,"1221":1,"1229":3,"1237":2,"1247":2,"1270":1,"1286":4,"1300":9,"1309":2,"1311":3,"1312":1,"1323":4,"1324":5,"1339":1,"1396":1,"1416":1,"1417":11,"1427":2,"1436":3,"2142":1}}],["argumentnullexception",{"2":{"359":1,"897":1,"1070":1,"1229":1,"1328":1,"2142":1},"3":{"359":1,"897":1,"1067":1,"1070":1,"1229":2,"1237":2,"1247":3,"1259":1,"1270":1,"1271":4,"1286":2,"1309":6,"1310":1,"1311":1,"1312":1,"1323":2,"1324":4,"1328":1,"1339":3,"1350":2,"1359":12,"1380":2,"1395":5,"1396":10,"1402":3,"1415":2,"1417":2,"1436":8,"2142":1}}],["arguments",{"3":{"0":1,"39":1,"43":1,"81":1,"95":1,"126":1,"135":1,"388":1,"474":1,"640":1,"799":1,"837":1,"861":1,"946":1,"1059":2,"1091":1,"1095":1,"1168":1,"1229":1,"1240":1,"1247":9,"1259":2,"1270":1,"1271":3,"1286":13,"1297":1,"1300":13,"1309":3,"1311":10,"1323":6,"1324":23,"1339":6,"1353":1,"1359":27,"1369":1,"1389":1,"1395":3,"1396":18,"1406":1,"1415":5,"1416":1,"1417":4,"1436":15,"1438":1,"1442":1,"1475":1,"1496":1,"1524":1,"1526":1,"1531":1,"1535":1,"1667":1,"1701":1,"1727":1,"2024":1,"2194":1,"2196":1,"2203":1}}],["argument",{"0":{"1825":1},"2":{"1989":1},"3":{"0":4,"26":1,"31":2,"93":1,"98":1,"109":1,"122":1,"135":2,"150":1,"160":1,"175":1,"186":2,"209":1,"259":1,"265":1,"266":1,"303":1,"318":4,"320":1,"341":1,"350":2,"359":1,"422":1,"460":1,"539":1,"545":2,"551":1,"556":2,"559":1,"565":1,"691":1,"707":1,"716":1,"724":1,"733":1,"749":2,"799":3,"801":2,"809":1,"811":1,"813":1,"827":4,"837":1,"838":1,"839":3,"855":1,"867":2,"875":1,"897":1,"905":1,"917":1,"946":2,"964":2,"1049":1,"1059":1,"1062":1,"1092":1,"1093":1,"1108":1,"1116":1,"1119":1,"1156":1,"1157":1,"1162":1,"1168":1,"1170":1,"1220":1,"1229":1,"1237":6,"1244":1,"1247":33,"1253":1,"1259":5,"1263":1,"1266":1,"1270":11,"1271":4,"1286":34,"1292":1,"1297":1,"1300":22,"1301":4,"1309":10,"1310":6,"1311":11,"1312":1,"1323":9,"1324":23,"1328":1,"1339":9,"1350":5,"1359":44,"1369":5,"1380":5,"1382":1,"1395":19,"1396":38,"1402":3,"1415":24,"1417":5,"1427":6,"1432":1,"1436":71,"1442":1,"1445":1,"1453":1,"1455":4,"1459":1,"1465":1,"1475":1,"1489":1,"1490":1,"1577":1,"1638":1,"1656":2,"1667":1,"1685":2,"1701":2,"1730":1,"1788":1,"1806":1,"1807":1,"1821":1,"1826":1,"1827":1,"1859":1,"1874":1,"1898":1,"1904":1,"1946":1,"1952":1,"1971":1,"1975":1,"1977":1,"1989":2,"1994":2,"1995":1,"1996":1,"2024":1,"2025":2,"2072":1,"2126":1,"2135":1,"2142":1,"2170":1,"2180":1,"2195":1}}],["arg",{"2":{"871":1},"3":{"0":1,"91":1,"869":2,"871":1,"1212":1,"1286":3,"1311":1,"1402":1,"1445":2,"1455":3,"1466":2}}],["arise",{"3":{"1286":1}}],["arithmetically",{"3":{"1436":1}}],["arithmetic",{"0":{"1242":1},"3":{"335":1,"628":1,"658":1,"760":1,"799":1,"803":1,"804":1,"805":1,"1019":1,"1051":1,"1054":1,"1237":4,"1242":2,"1243":1,"1247":4,"1254":1,"1259":1,"1309":1,"1323":1,"1324":1,"1339":2,"1350":1,"1359":3,"1366":1,"1369":1,"1396":3,"1436":5,"1438":2,"1474":2,"1475":2,"1496":1,"1815":1}}],["arity",{"3":{"0":2,"328":1,"921":1,"924":1,"926":1,"1270":4,"1289":1,"1300":3,"1311":3,"1436":23,"1496":4,"1671":1}}],["ariarole",{"3":{"1436":4}}],["arial",{"3":{"1324":1}}],["arialabel",{"3":{"1324":6}}],["aria",{"3":{"0":1,"412":1,"618":2,"620":1,"1324":15,"1389":1,"1395":5,"1396":1,"1402":1,"1415":1,"1417":5,"1433":1,"1436":7,"1488":2,"1526":3,"1558":1,"1577":2,"1591":2,"1596":1,"1605":1,"1607":2,"1643":3,"1644":1,"1739":1,"1741":2,"2057":1}}],["arrows",{"3":{"1339":1,"1505":1,"1517":1,"2039":2}}],["arrow",{"3":{"1195":1,"1259":1,"1309":1,"1310":1,"1311":1,"1417":1,"1436":3,"1503":1}}],["arriving",{"3":{"1":1,"159":1,"202":2,"352":1,"428":1,"702":1,"809":1,"845":1,"1049":1,"1067":1,"1109":1,"1184":1,"1249":1,"1311":1,"1324":4,"1359":1,"1366":2,"1396":1,"1415":1,"1417":1,"1436":1,"1438":1,"1467":1,"1639":1,"1909":1,"1911":1,"2166":1,"2181":1}}],["arrive",{"3":{"21":1,"24":1,"110":1,"235":1,"402":1,"548":1,"549":1,"585":1,"601":1,"674":1,"678":1,"684":1,"717":1,"774":1,"789":1,"829":1,"870":1,"880":1,"965":1,"979":1,"1020":1,"1033":1,"1093":1,"1123":1,"1153":1,"1229":1,"1237":1,"1241":1,"1247":1,"1259":3,"1270":1,"1273":1,"1281":1,"1300":4,"1309":2,"1311":3,"1317":1,"1318":1,"1324":6,"1339":3,"1350":2,"1359":6,"1364":1,"1369":1,"1380":1,"1382":1,"1395":5,"1396":10,"1402":2,"1415":2,"1417":2,"1436":1,"1438":2,"1443":1,"1453":1,"1467":2,"1468":1,"1641":1,"1664":1,"1673":1,"1682":1,"1685":1,"1686":1,"1694":1,"1727":1,"1792":1,"1890":1,"1909":1,"1934":1,"1944":1,"2000":1,"2179":1,"2194":1}}],["arrived",{"3":{"0":1,"423":1,"563":1,"696":1,"728":1,"790":1,"809":2,"813":1,"1259":1,"1270":3,"1287":1,"1293":2,"1300":3,"1309":2,"1312":1,"1324":2,"1359":2,"1395":4,"1396":1,"1402":2,"1415":1,"1436":3,"1489":1,"1496":1,"1686":1,"1727":1,"1759":1,"1765":2,"1806":1,"1875":1,"1890":1,"2093":1,"2158":1,"2167":1}}],["arrives",{"3":{"0":3,"113":1,"148":1,"255":1,"265":1,"350":1,"353":1,"390":1,"403":1,"415":1,"423":1,"535":2,"539":1,"573":1,"583":1,"631":1,"633":1,"673":1,"674":1,"689":1,"692":1,"698":2,"714":1,"741":1,"827":1,"829":1,"831":1,"869":1,"881":1,"897":1,"914":1,"1018":1,"1050":1,"1091":1,"1123":1,"1232":1,"1237":1,"1247":1,"1259":1,"1261":1,"1271":1,"1286":5,"1300":4,"1301":1,"1305":1,"1309":1,"1311":2,"1320":1,"1323":4,"1324":11,"1326":1,"1335":1,"1339":6,"1350":1,"1358":2,"1359":18,"1366":1,"1369":2,"1380":3,"1395":20,"1396":20,"1401":1,"1402":6,"1415":6,"1417":3,"1433":1,"1436":8,"1442":1,"1443":1,"1444":1,"1447":3,"1452":1,"1455":1,"1476":1,"1488":1,"1626":1,"1661":1,"1685":2,"1686":2,"1700":1,"1724":1,"1727":3,"1728":1,"1749":1,"1815":1,"1883":1,"1890":1,"1909":1,"1911":1,"1922":1,"1960":1,"1992":1,"2016":1,"2027":1,"2041":1,"2067":1,"2089":1,"2101":1,"2149":1,"2150":1,"2154":1,"2161":2,"2167":2,"2169":1}}],["arrivals",{"3":{"1396":1}}],["arrival",{"3":{"0":2,"635":1,"689":1,"691":1,"726":1,"1286":1,"1379":1,"1395":2,"1396":19,"1416":1,"1432":1,"1436":1,"1467":1,"1631":1,"1661":1,"1686":1,"1699":1,"1727":2,"1915":2,"2000":1,"2002":1}}],["arranging",{"3":{"1671":1}}],["arranges",{"3":{"1441":1}}],["arrange",{"3":{"1436":8,"1488":2,"1824":1}}],["arranged",{"3":{"1344":1}}],["arrangement",{"3":{"0":2,"93":1,"96":1,"109":1,"837":1,"881":1,"930":1,"1247":1,"1286":1,"1309":1,"1311":1,"1350":1,"1359":2,"1380":1,"1395":1,"1436":3,"1438":1,"1490":1}}],["arrays",{"3":{"81":1,"696":1,"698":1,"1247":1,"1286":1,"1300":1,"1311":6,"1350":3,"1405":1,"1415":2,"1423":1,"1427":1,"1436":2,"1639":1}}],["array",{"3":{"0":3,"109":2,"111":3,"122":2,"135":1,"243":1,"260":1,"265":1,"359":1,"388":1,"393":1,"418":1,"420":1,"422":1,"423":1,"424":1,"591":1,"603":1,"609":6,"610":1,"881":1,"1175":2,"1225":1,"1229":9,"1237":1,"1239":1,"1242":2,"1247":8,"1259":3,"1263":1,"1270":2,"1271":4,"1286":13,"1300":10,"1311":10,"1323":3,"1324":4,"1339":3,"1350":6,"1359":19,"1380":2,"1391":1,"1395":7,"1396":5,"1402":1,"1415":5,"1427":2,"1436":18,"1467":10,"1473":2,"1577":1,"1586":1,"1632":1,"1640":1,"1805":4,"1851":1,"1859":1,"1922":1,"1927":1,"1929":1,"1976":1,"2158":2}}],["armphasetimerforeventend",{"3":{"1395":1}}],["arming",{"3":{"539":1,"1300":4,"1600":1,"2197":1}}],["armasync",{"2":{"814":1},"3":{"539":1,"540":1,"814":1}}],["armed",{"3":{"0":1,"534":2,"536":2,"537":1,"539":2,"905":1,"909":1,"1300":4,"1383":1,"1416":2,"1427":1}}],["arm",{"3":{"0":4,"62":1,"63":1,"164":1,"166":2,"171":1,"609":1,"610":1,"611":1,"626":1,"756":3,"758":1,"766":1,"814":1,"1034":2,"1075":4,"1100":1,"1247":13,"1270":4,"1286":1,"1311":4,"1323":1,"1339":1,"1395":4,"1396":4,"1402":6,"1415":1,"1416":2,"1417":3,"1436":10,"1455":5,"1459":1,"1464":1,"1466":2,"1467":11,"1470":1,"1472":3,"1475":1,"1481":1,"1589":1,"1591":1,"1596":1,"1597":1,"1686":1,"2000":1,"2034":1,"2158":1,"2197":1,"2219":1,"2220":1}}],["arms",{"3":{"0":3,"539":3,"794":1,"809":1,"813":2,"905":1,"1042":1,"1045":1,"1247":2,"1270":1,"1286":1,"1290":1,"1300":5,"1324":2,"1337":1,"1359":1,"1393":1,"1395":3,"1396":1,"1416":1,"1417":2,"1436":1,"1917":1,"2161":1,"2165":1,"2167":2,"2197":1}}],["around",{"0":{"1387":1},"3":{"0":2,"24":1,"58":1,"62":1,"71":1,"109":1,"113":1,"118":1,"126":1,"138":1,"150":1,"155":1,"260":1,"265":1,"281":1,"285":1,"396":1,"463":1,"534":1,"555":1,"607":5,"627":1,"638":2,"642":1,"650":1,"655":1,"684":1,"733":1,"764":1,"819":3,"826":1,"827":1,"836":1,"921":1,"926":1,"971":1,"987":1,"1019":1,"1020":1,"1043":1,"1049":1,"1051":1,"1066":1,"1090":1,"1133":1,"1169":1,"1229":1,"1237":2,"1246":1,"1247":3,"1256":1,"1259":4,"1260":1,"1262":1,"1267":1,"1270":6,"1286":5,"1290":1,"1291":1,"1294":2,"1300":11,"1309":4,"1310":2,"1311":4,"1313":1,"1322":1,"1323":4,"1324":9,"1339":3,"1350":5,"1352":1,"1353":1,"1355":1,"1357":1,"1359":19,"1369":2,"1375":1,"1380":3,"1395":5,"1396":11,"1402":7,"1403":1,"1415":5,"1416":1,"1417":4,"1435":1,"1436":9,"1438":6,"1467":2,"1481":1,"1488":1,"1496":6,"1537":1,"1557":1,"1585":1,"1635":1,"1640":1,"1641":1,"1643":1,"1651":2,"1675":1,"1700":1,"1712":1,"1720":1,"1749":1,"1790":1,"1797":1,"1811":1,"1818":1,"1827":1,"1851":1,"1904":1,"1958":1,"1966":1,"2032":1,"2038":1,"2062":1,"2072":1,"2075":2,"2079":2,"2110":1,"2154":1,"2156":1,"2174":1,"2178":1,"2180":1,"2209":1}}],["areflagged",{"3":{"1436":10}}],["areflaggedundertheirsourcemember",{"3":{"1436":4}}],["arepinnedtooneversion",{"3":{"1436":2}}],["arecomplete",{"3":{"1436":3}}],["areidentical",{"3":{"1436":1}}],["arestserviceanswersoverh2c",{"2":{"1449":1},"3":{"1339":1,"1449":1}}],["arerestricted",{"2":{"1083":1},"3":{"1083":1,"1436":3}}],["areexplicitlyoptedin",{"2":{"1083":1},"3":{"1083":1,"1436":3}}],["arediscovered",{"2":{"609":1},"3":{"609":1,"1436":6}}],["areaccepted",{"3":{"1436":1}}],["areallowlisted",{"3":{"1436":2}}],["areas",{"0":{"1388":1,"1392":1},"3":{"1350":1,"1395":1,"1431":1,"1763":1}}],["areaof",{"3":{"1324":2}}],["area",{"3":{"442":1,"801":1,"1058":1,"1300":1,"1311":1,"1323":1,"1324":6,"1381":2,"1389":1,"1392":2,"1394":2,"1395":3,"1396":1,"1414":1,"1455":1,"1480":1,"1489":2,"1714":1,"2126":2}}],["are",{"0":{"93":1,"1317":1,"1363":1,"1473":1,"1521":1,"1804":1,"2144":1,"2200":1},"1":{"1105":1,"1106":1,"1107":1,"1108":1,"1109":1,"1110":1,"1111":1,"1112":1,"1863":1,"1864":1,"1865":1,"1866":1,"1867":1,"1868":1,"2029":1,"2030":1,"2031":1,"2032":1,"2033":1,"2034":1,"2035":1,"2036":1,"2037":1,"2038":1},"2":{"453":1},"3":{"0":102,"3":1,"4":1,"10":1,"12":1,"13":1,"15":1,"16":1,"17":4,"19":3,"21":2,"22":1,"23":3,"24":2,"26":2,"27":3,"30":1,"31":1,"33":1,"34":1,"39":7,"41":3,"43":1,"46":1,"47":2,"54":1,"59":2,"61":1,"62":1,"63":4,"64":1,"71":2,"73":2,"74":2,"77":1,"79":1,"80":1,"81":4,"82":1,"86":1,"88":1,"93":1,"94":1,"95":3,"99":1,"100":4,"101":1,"102":1,"103":1,"108":1,"109":5,"110":2,"115":1,"117":3,"120":1,"121":3,"122":2,"123":1,"125":2,"126":3,"127":1,"128":2,"130":6,"131":1,"134":2,"135":20,"136":10,"137":5,"138":3,"139":7,"140":3,"141":3,"142":12,"145":9,"147":3,"150":7,"152":1,"153":1,"155":2,"156":1,"157":2,"159":3,"160":3,"161":2,"166":4,"168":3,"171":1,"174":2,"175":6,"177":3,"178":5,"179":1,"181":2,"184":1,"186":4,"188":3,"189":1,"193":1,"194":1,"195":2,"198":2,"199":1,"201":2,"202":4,"203":1,"206":2,"207":1,"212":1,"214":2,"216":1,"218":1,"219":5,"220":1,"221":2,"224":2,"226":1,"229":1,"230":4,"231":3,"233":2,"235":4,"241":3,"242":2,"243":5,"244":2,"245":3,"246":3,"249":1,"250":2,"252":1,"253":2,"254":4,"255":4,"256":2,"257":1,"258":1,"259":4,"260":3,"265":11,"266":1,"267":1,"272":1,"274":1,"276":2,"281":1,"283":1,"285":2,"293":1,"295":1,"300":1,"302":1,"305":2,"306":1,"308":1,"309":1,"310":2,"312":1,"316":1,"317":2,"318":2,"319":1,"323":1,"324":3,"326":2,"328":1,"329":1,"330":1,"331":3,"332":3,"334":1,"335":1,"337":2,"341":4,"343":1,"344":1,"345":2,"349":1,"350":4,"351":2,"353":2,"354":2,"358":1,"359":1,"360":1,"361":3,"365":2,"368":1,"369":1,"370":4,"371":1,"372":1,"373":3,"374":2,"375":3,"378":1,"379":1,"380":2,"382":1,"383":2,"384":3,"387":1,"388":2,"391":2,"392":1,"393":2,"395":6,"396":1,"397":6,"398":2,"399":3,"400":2,"402":4,"405":1,"406":1,"407":3,"408":1,"409":5,"413":4,"414":1,"418":1,"419":1,"420":3,"422":1,"425":2,"429":1,"433":1,"434":3,"435":1,"436":1,"438":2,"439":2,"440":1,"444":2,"446":1,"448":2,"449":1,"450":1,"451":2,"452":1,"453":2,"459":1,"463":2,"464":2,"465":3,"466":2,"468":1,"470":4,"471":2,"472":2,"473":2,"475":1,"477":2,"480":2,"482":2,"484":1,"486":3,"487":1,"488":2,"489":2,"490":4,"491":5,"492":4,"493":6,"494":5,"495":2,"497":6,"498":1,"500":1,"501":2,"507":1,"508":1,"509":1,"511":3,"512":1,"513":1,"514":2,"517":1,"518":1,"519":1,"522":2,"523":2,"524":2,"526":2,"527":4,"528":4,"529":1,"530":4,"531":1,"534":9,"536":5,"537":2,"538":2,"539":3,"540":3,"543":3,"544":2,"545":5,"546":2,"547":3,"549":3,"550":2,"551":4,"552":2,"555":1,"556":8,"562":2,"564":3,"565":1,"567":1,"570":1,"571":1,"572":5,"573":1,"574":3,"575":3,"577":3,"578":5,"583":7,"584":2,"585":5,"586":3,"589":2,"590":1,"591":6,"592":4,"593":4,"594":1,"599":10,"600":2,"601":3,"602":1,"604":2,"607":10,"609":3,"610":1,"611":6,"612":3,"616":4,"617":1,"618":3,"620":4,"621":2,"624":5,"626":8,"627":1,"628":1,"629":4,"632":3,"633":4,"635":2,"638":6,"639":2,"640":6,"641":4,"642":3,"643":1,"644":1,"649":2,"650":3,"651":2,"656":1,"657":7,"658":3,"659":3,"660":1,"664":1,"665":5,"666":1,"669":1,"670":1,"672":2,"673":1,"674":5,"675":1,"676":2,"677":2,"680":1,"681":2,"682":4,"683":1,"684":3,"685":1,"688":1,"689":1,"690":10,"691":5,"692":3,"693":1,"698":7,"699":2,"700":5,"702":1,"703":2,"707":5,"708":3,"713":1,"714":1,"715":5,"717":2,"720":2,"721":1,"724":1,"725":4,"726":2,"727":1,"729":1,"731":1,"732":1,"733":2,"735":1,"736":2,"740":1,"741":5,"743":3,"744":2,"747":2,"749":7,"751":7,"755":1,"756":2,"758":1,"759":1,"760":3,"761":1,"764":5,"766":6,"767":1,"768":2,"769":1,"774":3,"775":1,"776":3,"780":2,"782":3,"784":2,"785":1,"786":1,"789":1,"790":3,"794":1,"797":1,"798":2,"799":5,"800":1,"801":1,"802":1,"803":6,"804":2,"805":4,"809":3,"810":3,"813":1,"814":1,"818":3,"819":5,"821":3,"822":1,"825":5,"826":5,"827":16,"828":1,"829":6,"830":3,"831":2,"832":6,"833":4,"836":1,"837":2,"838":4,"840":5,"844":1,"846":1,"848":2,"849":3,"850":2,"853":1,"854":2,"855":1,"857":1,"860":3,"861":6,"862":1,"863":2,"864":1,"867":2,"868":4,"869":1,"871":3,"872":3,"873":2,"874":2,"875":4,"876":3,"877":1,"879":2,"880":1,"881":5,"882":1,"883":4,"884":3,"889":2,"891":1,"892":2,"897":3,"900":2,"903":1,"904":2,"905":10,"906":2,"907":2,"910":2,"912":1,"913":1,"914":4,"916":2,"917":1,"918":1,"922":1,"923":2,"924":1,"926":4,"927":2,"930":2,"931":3,"932":2,"938":1,"941":1,"944":1,"946":5,"949":1,"953":1,"954":1,"958":1,"959":1,"960":1,"963":3,"964":8,"966":3,"967":1,"968":1,"971":1,"972":4,"973":3,"974":2,"975":1,"979":1,"980":6,"981":3,"982":3,"983":2,"986":1,"987":1,"988":6,"989":1,"991":2,"995":1,"996":3,"998":1,"999":1,"1003":1,"1004":6,"1005":1,"1006":2,"1011":1,"1012":3,"1014":1,"1016":3,"1017":1,"1018":15,"1019":1,"1020":2,"1021":3,"1024":2,"1025":1,"1026":3,"1027":1,"1028":1,"1029":1,"1030":2,"1032":1,"1033":2,"1034":8,"1036":1,"1037":2,"1042":2,"1043":1,"1044":1,"1049":2,"1050":9,"1051":3,"1052":8,"1054":5,"1055":4,"1058":2,"1059":8,"1061":2,"1065":1,"1067":6,"1069":3,"1073":1,"1074":8,"1075":5,"1077":1,"1078":2,"1079":1,"1082":3,"1083":1,"1084":1,"1085":1,"1089":1,"1090":1,"1091":12,"1092":4,"1093":7,"1095":1,"1096":1,"1099":1,"1100":2,"1101":1,"1102":1,"1103":1,"1105":1,"1107":1,"1108":4,"1115":1,"1116":7,"1117":4,"1118":3,"1119":2,"1123":2,"1124":6,"1125":1,"1126":3,"1127":1,"1128":1,"1133":2,"1135":1,"1140":3,"1142":1,"1143":1,"1150":2,"1152":1,"1160":1,"1161":4,"1163":3,"1168":10,"1169":1,"1174":1,"1175":4,"1176":1,"1178":1,"1182":1,"1184":4,"1186":1,"1188":1,"1190":1,"1191":10,"1193":2,"1196":4,"1200":1,"1201":4,"1202":1,"1203":2,"1211":2,"1212":21,"1213":3,"1214":3,"1215":2,"1216":1,"1217":2,"1218":5,"1220":1,"1221":2,"1222":1,"1223":3,"1225":1,"1226":3,"1227":6,"1228":1,"1229":24,"1230":2,"1231":8,"1232":4,"1233":1,"1234":6,"1235":1,"1236":5,"1237":72,"1238":4,"1239":4,"1240":2,"1241":3,"1242":1,"1243":4,"1244":3,"1246":1,"1247":73,"1248":1,"1249":4,"1251":2,"1252":4,"1253":2,"1254":2,"1256":4,"1257":1,"1258":1,"1259":54,"1260":2,"1261":3,"1262":2,"1263":6,"1264":5,"1265":3,"1266":3,"1267":4,"1269":5,"1270":113,"1271":36,"1272":1,"1273":4,"1274":2,"1275":2,"1276":6,"1277":5,"1278":4,"1279":4,"1280":4,"1281":3,"1283":7,"1284":2,"1285":5,"1286":301,"1287":3,"1288":2,"1289":4,"1290":7,"1291":3,"1292":2,"1293":4,"1294":4,"1295":2,"1296":1,"1297":7,"1298":1,"1299":1,"1300":207,"1301":40,"1302":2,"1303":1,"1304":3,"1307":4,"1309":81,"1310":38,"1311":174,"1312":27,"1313":3,"1314":2,"1315":1,"1316":6,"1317":4,"1318":6,"1319":2,"1320":5,"1321":2,"1322":3,"1323":128,"1324":280,"1325":2,"1326":4,"1328":1,"1330":3,"1332":8,"1333":5,"1334":1,"1335":1,"1336":2,"1337":3,"1338":4,"1339":178,"1341":1,"1342":13,"1343":4,"1344":6,"1345":2,"1346":3,"1347":6,"1349":6,"1350":183,"1352":3,"1353":5,"1354":2,"1355":5,"1357":3,"1358":1,"1359":503,"1362":6,"1363":2,"1364":4,"1366":6,"1367":1,"1368":4,"1369":60,"1370":3,"1371":1,"1372":1,"1373":5,"1374":2,"1375":3,"1376":1,"1377":3,"1378":2,"1379":5,"1380":98,"1382":2,"1383":6,"1384":2,"1385":1,"1389":3,"1390":2,"1391":2,"1392":4,"1393":2,"1394":3,"1395":234,"1396":360,"1397":1,"1398":9,"1399":6,"1401":4,"1402":106,"1403":3,"1405":3,"1406":7,"1407":1,"1408":2,"1409":4,"1410":2,"1411":1,"1412":1,"1413":1,"1414":5,"1415":158,"1416":47,"1417":139,"1418":2,"1421":1,"1422":3,"1423":3,"1424":6,"1425":1,"1426":1,"1427":19,"1428":4,"1429":7,"1430":3,"1431":11,"1432":1,"1433":4,"1434":3,"1435":6,"1436":567,"1437":1,"1438":57,"1441":24,"1442":12,"1443":22,"1444":5,"1445":4,"1446":2,"1447":12,"1448":1,"1449":4,"1451":1,"1452":4,"1453":16,"1454":6,"1455":50,"1456":7,"1457":5,"1458":6,"1459":2,"1460":6,"1462":3,"1463":5,"1465":7,"1466":7,"1467":62,"1468":1,"1470":4,"1471":2,"1472":7,"1473":2,"1474":11,"1475":5,"1476":5,"1477":4,"1478":2,"1479":3,"1480":2,"1481":7,"1482":1,"1484":3,"1485":5,"1486":11,"1487":4,"1488":21,"1489":19,"1490":9,"1491":2,"1492":11,"1494":1,"1495":2,"1496":61,"1497":5,"1498":4,"1499":1,"1500":5,"1502":1,"1503":2,"1507":1,"1508":3,"1509":1,"1512":1,"1515":1,"1517":3,"1519":1,"1521":2,"1523":1,"1524":4,"1525":1,"1526":35,"1527":1,"1531":16,"1532":5,"1533":1,"1534":7,"1536":4,"1537":2,"1538":2,"1539":1,"1540":1,"1541":1,"1543":3,"1544":4,"1545":3,"1546":3,"1547":6,"1548":3,"1550":1,"1551":1,"1552":1,"1553":4,"1554":3,"1555":1,"1558":1,"1562":1,"1563":2,"1565":1,"1566":1,"1568":1,"1569":1,"1570":2,"1571":3,"1572":12,"1574":2,"1575":5,"1577":41,"1578":4,"1580":2,"1581":2,"1582":7,"1583":2,"1584":2,"1585":13,"1586":2,"1588":1,"1591":20,"1592":2,"1594":2,"1596":6,"1597":3,"1599":1,"1601":1,"1605":1,"1607":3,"1611":3,"1614":1,"1621":1,"1623":1,"1626":4,"1627":6,"1630":27,"1631":74,"1632":16,"1633":6,"1634":4,"1635":17,"1636":6,"1637":3,"1638":17,"1639":48,"1640":17,"1641":30,"1644":1,"1645":3,"1646":2,"1648":1,"1649":2,"1650":1,"1651":6,"1652":3,"1653":10,"1654":2,"1656":1,"1658":1,"1661":3,"1662":4,"1664":1,"1665":1,"1666":4,"1667":2,"1668":5,"1669":1,"1670":3,"1671":3,"1672":1,"1673":2,"1675":2,"1677":9,"1679":1,"1680":3,"1682":1,"1683":2,"1684":4,"1685":7,"1686":13,"1689":2,"1691":1,"1693":2,"1695":2,"1697":2,"1698":3,"1699":1,"1700":4,"1701":4,"1702":2,"1703":1,"1706":1,"1707":1,"1708":1,"1710":1,"1712":1,"1717":1,"1718":3,"1719":2,"1720":3,"1721":3,"1727":13,"1728":3,"1729":2,"1730":1,"1731":1,"1733":1,"1734":1,"1735":1,"1736":4,"1738":3,"1739":1,"1741":3,"1742":1,"1748":6,"1749":3,"1750":4,"1751":1,"1755":4,"1758":2,"1759":2,"1760":1,"1761":4,"1764":1,"1765":19,"1766":2,"1767":4,"1769":3,"1771":1,"1772":2,"1774":1,"1776":9,"1777":2,"1779":1,"1781":2,"1782":3,"1783":2,"1784":1,"1787":1,"1788":1,"1789":3,"1790":2,"1792":2,"1794":2,"1795":1,"1796":1,"1798":1,"1799":1,"1801":3,"1802":2,"1804":4,"1805":2,"1806":3,"1807":1,"1808":2,"1809":2,"1810":9,"1811":7,"1812":4,"1814":2,"1815":10,"1816":3,"1818":3,"1819":1,"1820":1,"1821":1,"1822":2,"1823":1,"1824":1,"1825":5,"1826":3,"1828":1,"1830":1,"1832":2,"1837":1,"1838":1,"1839":1,"1840":8,"1841":3,"1842":3,"1843":2,"1844":5,"1845":3,"1848":1,"1849":1,"1850":1,"1851":2,"1852":1,"1853":2,"1854":2,"1855":1,"1856":1,"1860":1,"1861":6,"1862":1,"1863":1,"1864":1,"1865":2,"1872":1,"1875":1,"1876":2,"1879":2,"1881":1,"1883":2,"1884":1,"1885":3,"1887":1,"1888":3,"1889":1,"1890":1,"1891":3,"1892":1,"1893":1,"1896":1,"1898":2,"1899":1,"1900":2,"1901":4,"1902":3,"1903":3,"1904":2,"1905":1,"1907":2,"1909":7,"1910":1,"1911":5,"1913":1,"1915":1,"1916":2,"1917":2,"1918":1,"1919":1,"1920":6,"1921":1,"1922":4,"1923":7,"1924":1,"1927":1,"1929":1,"1931":1,"1933":2,"1934":1,"1935":3,"1936":4,"1939":1,"1940":1,"1941":1,"1942":2,"1943":1,"1944":1,"1947":1,"1948":1,"1949":5,"1950":1,"1951":2,"1952":3,"1953":4,"1954":2,"1955":1,"1956":1,"1957":1,"1958":1,"1959":2,"1960":3,"1961":3,"1962":3,"1964":8,"1966":2,"1967":1,"1968":2,"1969":3,"1971":2,"1973":3,"1977":4,"1978":1,"1979":3,"1980":1,"1981":1,"1982":1,"1983":1,"1984":3,"1986":1,"1987":2,"1988":2,"1990":4,"1992":1,"1994":2,"1995":8,"1996":2,"1997":1,"1998":2,"1999":1,"2000":8,"2001":4,"2002":2,"2004":1,"2007":1,"2009":1,"2010":5,"2011":2,"2012":1,"2013":6,"2015":1,"2020":1,"2022":1,"2024":4,"2025":2,"2027":5,"2028":1,"2029":3,"2030":4,"2033":3,"2034":2,"2035":2,"2036":2,"2037":3,"2038":2,"2040":1,"2041":2,"2042":2,"2043":6,"2044":4,"2047":2,"2048":3,"2049":1,"2051":1,"2052":1,"2053":1,"2054":3,"2055":4,"2056":3,"2057":4,"2059":1,"2060":2,"2061":2,"2062":1,"2063":2,"2064":1,"2067":2,"2068":3,"2069":2,"2070":1,"2072":2,"2074":1,"2075":1,"2077":2,"2079":3,"2080":1,"2081":4,"2083":3,"2086":2,"2087":4,"2088":4,"2090":1,"2091":1,"2095":2,"2096":2,"2097":4,"2098":7,"2100":1,"2101":5,"2103":1,"2104":3,"2105":2,"2107":3,"2108":1,"2109":5,"2110":2,"2111":4,"2112":2,"2114":1,"2115":1,"2116":1,"2117":2,"2118":1,"2121":2,"2124":4,"2125":6,"2126":3,"2127":2,"2128":2,"2130":1,"2131":3,"2132":4,"2133":2,"2134":1,"2135":1,"2136":2,"2138":5,"2139":2,"2140":3,"2142":5,"2143":2,"2144":2,"2145":3,"2146":1,"2147":1,"2148":3,"2149":3,"2150":5,"2151":1,"2152":1,"2154":4,"2156":5,"2157":4,"2158":2,"2159":6,"2160":2,"2162":1,"2163":2,"2164":3,"2165":2,"2167":4,"2168":1,"2171":3,"2172":2,"2173":1,"2174":4,"2176":2,"2177":2,"2178":1,"2179":5,"2181":1,"2182":1,"2183":1,"2185":1,"2188":2,"2189":2,"2190":2,"2191":3,"2192":1,"2195":1,"2198":2,"2199":1,"2200":2,"2201":1,"2202":4,"2203":2,"2207":1,"2209":1,"2211":5,"2214":1,"2216":1,"2227":1,"2230":5,"2232":5,"2234":2,"2242":1,"2244":1}}],["arenotthrottledevenafterthewindowisexhausted",{"3":{"1436":1}}],["arenotflagged",{"3":{"1436":10}}],["arenotificationsenabled",{"3":{"1417":1}}],["aren",{"3":{"0":1,"1538":1,"1543":1,"2162":1,"2182":1}}],["a",{"0":{"1":1,"91":1,"92":1,"93":1,"95":1,"137":1,"170":1,"451":1,"813":1,"1193":1,"1218":1,"1222":1,"1223":2,"1224":1,"1225":1,"1228":1,"1233":1,"1235":1,"1240":1,"1246":1,"1250":1,"1265":1,"1294":1,"1295":1,"1305":2,"1346":1,"1352":1,"1357":1,"1382":1,"1386":1,"1389":1,"1400":1,"1406":1,"1420":2,"1422":1,"1426":1,"1429":3,"1430":1,"1433":1,"1435":1,"1442":1,"1523":1,"1530":1,"1574":1,"1581":1,"1588":1,"1595":1,"1610":1,"1656":1,"1685":1,"1722":1,"1744":1,"1795":1,"1804":1,"1805":1,"1810":1,"1831":2,"1837":1,"1842":1,"1858":1,"1859":1,"1865":1,"1871":2,"1872":1,"1873":1,"1874":2,"1879":2,"1880":1,"1882":1,"1883":1,"1889":2,"1895":1,"1902":1,"1919":3,"1933":1,"1945":1,"1946":1,"1948":1,"1954":1,"1955":1,"1957":1,"1962":2,"1975":1,"2025":1,"2040":1,"2045":1,"2046":1,"2047":1,"2056":1,"2057":1,"2058":1,"2063":1,"2066":1,"2073":2,"2075":1,"2090":2,"2091":1,"2093":1,"2102":1,"2112":1,"2126":2,"2136":1,"2149":1,"2158":1,"2163":2,"2164":1,"2166":2,"2167":1,"2172":1,"2176":1,"2184":1,"2187":1,"2189":1,"2190":1,"2197":1,"2231":1},"1":{"114":1,"115":1,"116":1,"117":1,"118":1,"119":1,"120":1,"121":1,"122":1,"123":1,"124":1,"125":1,"126":1,"127":1,"128":1,"211":1,"212":1,"213":1,"214":1,"215":1,"216":1,"217":1,"218":1,"219":1,"220":1,"221":1,"240":1,"241":1,"242":1,"243":1,"244":1,"245":1,"246":1,"247":1,"248":1,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"260":1,"261":1,"327":1,"328":1,"329":1,"330":1,"331":1,"332":1,"333":1,"334":1,"335":1,"336":1,"337":1,"496":1,"497":1,"498":1,"499":1,"500":1,"501":1,"502":1,"503":1,"533":1,"534":1,"535":1,"536":1,"537":1,"538":1,"539":1,"540":1,"541":1,"561":1,"562":1,"563":1,"564":1,"565":1,"566":1,"567":1,"568":1,"569":1,"570":1,"571":1,"572":1,"573":1,"574":1,"575":1,"576":1,"577":1,"578":1,"579":1,"615":1,"616":1,"617":1,"618":1,"619":1,"620":1,"621":1,"622":1,"722":1,"723":1,"724":1,"725":1,"726":1,"727":1,"728":1,"729":1,"738":1,"739":1,"740":1,"741":1,"742":1,"743":1,"744":1,"745":1,"763":1,"764":1,"765":1,"766":1,"767":1,"768":1,"769":1,"770":1,"779":1,"780":1,"781":1,"782":1,"783":1,"784":1,"785":1,"786":1,"807":1,"808":1,"809":1,"810":1,"811":1,"812":1,"813":1,"814":1,"815":1,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1,"919":1,"920":1,"921":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"951":1,"952":1,"953":1,"954":1,"955":1,"956":1,"957":1,"958":1,"959":1,"960":1,"969":1,"970":1,"971":1,"972":1,"973":1,"974":1,"975":1,"976":1,"1031":1,"1032":1,"1033":1,"1034":1,"1035":1,"1036":1,"1037":1,"1038":1,"1039":1,"1040":1,"1041":1,"1042":1,"1043":1,"1044":1,"1045":1,"1046":1,"1064":1,"1065":1,"1066":1,"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"1130":1,"1131":1,"1132":1,"1133":1,"1134":1,"1135":1,"1136":1,"1137":1,"1181":1,"1182":1,"1183":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1,"1397":1,"1398":1,"1399":1,"1400":1,"1401":1,"1402":1,"1681":1,"1682":1,"1683":1,"1684":1,"1685":1,"1686":1,"1687":1,"1688":1,"1689":1,"1690":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1698":1,"1699":1,"1700":1,"1701":1,"1702":1,"1703":1,"1704":1,"1705":1,"1794":1,"1795":1,"1796":1,"1797":1,"1798":1,"1799":1,"1800":1,"1801":1,"1802":1,"1818":1,"1819":1,"1820":1,"1821":1,"1822":1,"1823":1,"1824":1,"1825":1,"1826":1,"1827":1,"1828":1,"1829":1,"1830":1,"1831":1,"1832":1,"1833":1,"1834":1,"1847":1,"1848":1,"1849":1,"1850":1,"1851":1,"1852":1,"1853":1,"1854":1,"1855":1,"1856":1,"1857":1,"1858":1,"1859":1,"1860":1,"1861":1,"1862":1,"1863":1,"1864":1,"1865":1,"1866":1,"1867":1,"1868":1,"1869":1,"1870":1,"1871":1,"1872":1,"1873":1,"1874":1,"1875":1,"1876":1,"1877":1,"1893":1,"1894":1,"1895":1,"1896":1,"1897":1,"1898":1,"1899":1,"1931":1,"1932":1,"1933":1,"1934":1,"1935":1,"1936":1,"1937":1,"1938":1,"1939":1,"1940":1,"1941":1,"2015":2,"2016":2,"2017":2,"2018":2,"2019":2,"2020":2,"2021":2,"2022":2,"2023":2,"2024":2,"2025":2,"2026":2,"2027":2,"2028":2,"2029":2,"2030":2,"2031":2,"2032":2,"2033":2,"2034":2,"2035":2,"2036":2,"2037":2,"2038":2,"2039":1,"2040":1,"2041":1,"2042":1,"2043":1,"2044":1,"2045":1,"2046":1,"2047":1,"2048":1,"2049":1,"2070":3,"2071":3,"2072":3,"2073":3,"2074":3,"2075":3,"2076":3,"2077":3,"2078":3,"2079":3,"2080":3,"2081":1,"2082":1,"2083":1,"2084":1,"2085":1,"2086":1,"2087":1,"2088":1,"2089":1,"2090":1,"2091":1,"2092":1,"2093":1,"2094":1,"2095":1,"2096":1,"2097":1,"2098":1,"2109":2,"2110":2,"2111":2,"2112":2,"2113":2,"2114":2,"2115":2,"2116":1,"2117":1,"2118":1,"2119":1,"2120":1,"2121":1,"2122":1,"2123":1,"2161":1,"2162":1,"2163":1,"2164":1,"2165":1,"2166":1,"2167":1,"2168":1,"2169":1,"2170":2,"2171":2,"2172":2,"2173":2,"2174":2,"2175":2,"2176":2,"2177":2,"2178":2,"2179":2,"2180":2},"3":{"0":949,"1":4,"5":6,"6":4,"7":2,"10":4,"11":4,"12":1,"13":1,"15":12,"17":9,"18":1,"19":14,"20":8,"21":7,"22":9,"23":7,"24":56,"25":7,"26":10,"27":21,"30":6,"31":9,"32":5,"33":5,"37":3,"38":4,"39":25,"41":8,"42":4,"43":2,"45":2,"46":5,"47":4,"48":1,"51":2,"52":4,"53":15,"54":5,"55":4,"57":5,"58":4,"59":11,"60":10,"61":4,"62":13,"63":3,"66":2,"67":6,"68":12,"69":4,"70":6,"71":20,"72":17,"73":7,"74":5,"76":4,"77":3,"78":15,"79":4,"80":11,"81":29,"82":1,"85":2,"86":1,"87":1,"88":3,"90":1,"91":11,"92":9,"93":8,"94":9,"95":7,"96":4,"97":7,"98":4,"99":12,"100":19,"101":3,"102":10,"103":8,"104":2,"107":5,"108":3,"109":98,"110":9,"111":24,"113":5,"114":1,"115":10,"116":1,"117":7,"118":1,"119":4,"120":7,"121":9,"122":40,"123":5,"124":2,"125":8,"126":21,"127":3,"128":3,"130":5,"131":3,"132":12,"133":3,"134":4,"135":65,"136":34,"137":15,"138":8,"139":16,"140":3,"141":4,"142":7,"143":1,"145":7,"146":4,"147":17,"148":7,"149":7,"150":21,"151":7,"152":1,"155":5,"156":6,"157":43,"158":8,"159":19,"160":19,"161":8,"162":2,"164":5,"165":8,"166":24,"167":6,"168":11,"170":24,"171":3,"173":6,"174":9,"175":18,"176":7,"177":20,"178":25,"179":5,"180":5,"181":3,"184":8,"185":8,"186":30,"187":6,"188":13,"189":9,"190":2,"193":9,"194":6,"195":15,"196":5,"197":6,"198":5,"199":2,"200":12,"201":31,"202":14,"203":1,"206":5,"207":4,"208":6,"209":8,"211":1,"212":5,"213":8,"214":16,"215":13,"216":12,"217":11,"218":6,"219":10,"220":3,"223":1,"224":9,"225":23,"226":5,"227":13,"228":8,"229":3,"230":20,"231":1,"233":6,"234":20,"235":47,"236":16,"237":23,"239":1,"240":1,"241":9,"242":6,"243":48,"244":3,"245":23,"246":7,"247":1,"248":7,"249":3,"250":1,"251":1,"252":1,"253":8,"254":8,"255":46,"256":10,"257":3,"258":11,"259":10,"260":3,"261":6,"263":1,"264":7,"265":78,"266":6,"267":10,"269":1,"272":4,"273":12,"275":3,"276":1,"279":6,"280":8,"281":12,"282":7,"283":12,"284":9,"285":2,"286":6,"287":1,"290":6,"291":10,"292":4,"293":9,"294":2,"295":8,"296":3,"298":5,"299":8,"300":6,"301":7,"302":10,"303":12,"304":1,"305":18,"306":2,"309":9,"310":14,"311":12,"312":14,"313":1,"314":5,"317":12,"318":50,"319":15,"320":17,"321":1,"323":1,"324":4,"325":5,"326":6,"327":1,"328":3,"329":10,"330":19,"331":4,"332":3,"333":14,"335":17,"336":2,"337":3,"339":2,"340":15,"341":50,"342":28,"343":30,"344":11,"345":3,"348":4,"349":7,"350":35,"351":9,"352":14,"353":18,"358":12,"359":36,"360":16,"361":26,"363":7,"364":1,"365":11,"366":1,"368":2,"369":7,"370":17,"371":8,"372":8,"373":18,"374":11,"375":4,"376":1,"379":6,"380":8,"381":2,"382":2,"383":7,"384":14,"386":9,"387":8,"388":11,"389":2,"390":22,"391":12,"392":7,"393":1,"395":12,"396":4,"397":27,"398":13,"399":10,"400":10,"401":14,"402":10,"403":8,"404":9,"405":7,"406":1,"407":14,"408":9,"409":5,"411":3,"412":3,"413":15,"414":4,"415":15,"416":2,"418":4,"419":6,"420":2,"421":1,"422":10,"423":2,"424":4,"425":5,"426":3,"427":10,"428":4,"429":1,"432":1,"433":8,"434":4,"435":9,"436":4,"438":2,"439":3,"440":4,"441":4,"442":7,"443":12,"444":6,"446":4,"447":11,"448":16,"449":12,"450":14,"451":10,"452":3,"454":6,"457":2,"458":12,"459":48,"460":6,"461":17,"462":4,"463":3,"464":6,"465":10,"466":2,"468":1,"469":6,"470":18,"471":2,"472":9,"473":1,"474":5,"477":4,"478":1,"481":5,"482":5,"483":7,"484":2,"485":1,"486":5,"487":3,"488":2,"489":3,"490":8,"491":8,"492":8,"493":6,"494":5,"495":3,"496":1,"497":7,"498":10,"499":23,"500":15,"501":17,"502":1,"503":2,"505":3,"506":7,"507":19,"508":4,"509":9,"511":7,"512":7,"513":7,"514":1,"516":3,"517":11,"518":22,"519":3,"520":17,"522":3,"523":4,"524":9,"526":5,"527":11,"528":18,"529":8,"530":18,"531":2,"533":1,"534":5,"535":9,"536":50,"537":18,"538":25,"539":29,"540":12,"541":5,"543":7,"544":7,"545":27,"546":13,"547":13,"548":2,"549":59,"550":8,"551":13,"552":1,"555":6,"556":16,"557":10,"558":11,"559":1,"560":1,"561":1,"562":3,"563":6,"564":28,"565":12,"566":18,"568":5,"569":1,"571":6,"572":32,"573":20,"574":16,"576":9,"577":8,"578":2,"579":3,"582":7,"583":41,"584":12,"585":36,"586":5,"587":5,"589":1,"590":10,"591":34,"592":25,"593":15,"594":1,"598":15,"599":22,"600":6,"601":12,"602":5,"603":5,"605":3,"607":9,"608":8,"609":43,"610":28,"611":12,"612":9,"613":1,"614":1,"615":1,"616":4,"617":12,"618":28,"619":12,"620":15,"622":1,"624":5,"625":19,"626":46,"627":21,"628":14,"629":5,"630":4,"631":4,"632":8,"633":43,"634":12,"635":12,"638":5,"639":7,"640":37,"641":16,"642":17,"643":4,"645":1,"648":12,"649":23,"650":23,"651":11,"652":1,"655":1,"656":14,"657":36,"658":18,"659":15,"660":5,"664":10,"665":24,"666":10,"667":12,"668":2,"669":1,"670":2,"672":2,"673":14,"674":26,"675":19,"676":29,"677":2,"678":2,"680":1,"681":16,"682":25,"683":24,"684":20,"685":2,"688":7,"689":18,"690":32,"691":41,"692":27,"693":1,"694":1,"696":4,"697":15,"698":72,"699":16,"700":17,"701":4,"702":14,"703":1,"705":1,"706":15,"707":61,"708":18,"709":28,"710":2,"711":3,"713":2,"714":15,"715":19,"716":25,"717":22,"718":6,"719":3,"720":8,"721":3,"722":1,"723":3,"724":12,"725":58,"726":24,"727":20,"728":3,"729":5,"731":5,"732":17,"733":49,"734":17,"735":19,"736":2,"737":3,"738":1,"740":24,"741":63,"742":20,"743":41,"744":3,"745":6,"747":2,"748":8,"749":25,"750":9,"751":24,"755":2,"756":7,"757":31,"758":16,"759":28,"760":6,"761":5,"762":1,"763":1,"764":4,"765":12,"766":24,"767":24,"768":17,"769":2,"770":3,"773":8,"774":7,"775":7,"776":19,"778":2,"779":1,"781":8,"782":27,"783":12,"784":23,"785":5,"786":2,"788":1,"789":14,"790":37,"791":14,"792":28,"793":11,"794":7,"795":4,"797":3,"798":16,"799":29,"800":12,"801":13,"802":4,"803":8,"804":4,"807":1,"808":2,"809":23,"810":24,"811":21,"812":10,"813":10,"814":3,"815":7,"817":1,"818":12,"819":42,"820":25,"821":20,"822":1,"823":3,"825":14,"826":21,"827":101,"828":18,"829":38,"830":12,"831":10,"832":18,"833":4,"834":2,"836":5,"837":13,"838":32,"839":18,"840":19,"842":3,"844":2,"845":8,"846":13,"847":8,"848":5,"849":5,"852":6,"853":18,"854":18,"855":13,"856":19,"857":6,"858":1,"859":2,"860":18,"861":36,"862":20,"863":18,"865":2,"867":2,"868":10,"869":12,"870":10,"871":9,"872":8,"873":8,"874":4,"875":7,"876":6,"877":1,"879":4,"880":5,"881":56,"882":20,"883":15,"884":2,"887":1,"888":9,"889":20,"890":15,"891":20,"892":1,"893":2,"896":8,"897":41,"898":18,"899":13,"900":2,"901":2,"903":13,"904":17,"905":95,"906":27,"907":36,"908":7,"909":5,"910":6,"912":4,"913":7,"914":22,"915":7,"916":14,"917":3,"918":3,"919":1,"920":14,"921":15,"922":33,"923":16,"924":11,"926":91,"927":1,"929":2,"930":16,"931":14,"932":12,"933":12,"935":1,"937":1,"938":4,"939":14,"940":9,"941":12,"942":1,"944":1,"945":10,"946":19,"947":10,"948":17,"951":1,"952":2,"953":15,"954":27,"955":16,"956":19,"957":5,"958":3,"959":2,"960":10,"963":26,"964":43,"965":20,"966":32,"967":1,"968":5,"969":1,"970":1,"971":18,"972":24,"973":20,"974":26,"975":7,"976":3,"979":16,"980":23,"981":15,"982":17,"983":1,"984":2,"986":1,"987":8,"988":45,"989":12,"990":15,"991":3,"992":1,"994":3,"995":14,"996":72,"997":14,"998":17,"999":2,"1000":3,"1003":13,"1004":28,"1005":14,"1006":14,"1007":1,"1008":3,"1010":1,"1011":9,"1012":19,"1013":9,"1014":1,"1016":6,"1017":24,"1018":101,"1019":52,"1020":39,"1021":4,"1022":4,"1024":1,"1025":11,"1026":20,"1027":13,"1028":21,"1029":7,"1030":4,"1031":1,"1032":2,"1033":24,"1034":31,"1035":13,"1036":13,"1037":4,"1038":1,"1039":1,"1040":4,"1041":10,"1042":40,"1043":22,"1044":17,"1045":4,"1046":2,"1048":6,"1049":12,"1050":59,"1051":31,"1052":29,"1053":3,"1054":1,"1055":7,"1058":16,"1059":42,"1060":19,"1061":14,"1064":1,"1065":2,"1066":25,"1067":59,"1068":19,"1069":15,"1070":2,"1071":3,"1073":1,"1074":19,"1075":19,"1076":15,"1077":11,"1078":1,"1079":4,"1082":13,"1083":15,"1084":7,"1085":19,"1086":1,"1087":1,"1089":12,"1090":29,"1091":75,"1092":39,"1093":37,"1094":6,"1095":4,"1099":13,"1100":23,"1101":11,"1102":16,"1103":2,"1107":14,"1108":13,"1109":11,"1110":7,"1112":1,"1114":3,"1115":22,"1116":58,"1117":30,"1118":17,"1119":3,"1122":3,"1123":8,"1124":45,"1125":15,"1126":27,"1128":4,"1129":6,"1130":1,"1132":18,"1133":25,"1134":17,"1135":21,"1137":5,"1139":1,"1140":12,"1141":6,"1142":8,"1143":7,"1144":3,"1145":6,"1147":3,"1149":1,"1150":21,"1151":1,"1152":5,"1153":4,"1154":7,"1155":6,"1156":1,"1157":2,"1160":9,"1161":18,"1162":16,"1163":9,"1164":1,"1167":10,"1168":22,"1169":13,"1170":8,"1171":2,"1173":1,"1174":3,"1175":13,"1176":11,"1177":9,"1178":5,"1179":4,"1181":1,"1182":2,"1183":13,"1184":25,"1185":19,"1186":5,"1187":11,"1188":3,"1190":7,"1191":8,"1192":2,"1193":7,"1194":1,"1196":8,"1200":5,"1201":3,"1202":2,"1203":2,"1204":1,"1210":1,"1211":7,"1212":205,"1213":10,"1214":13,"1215":6,"1216":7,"1217":13,"1218":5,"1219":10,"1220":18,"1221":8,"1222":13,"1223":10,"1224":5,"1225":7,"1226":4,"1227":6,"1228":19,"1229":271,"1230":6,"1231":24,"1232":17,"1233":17,"1234":19,"1235":11,"1236":20,"1237":439,"1238":1,"1239":22,"1240":13,"1241":56,"1242":26,"1243":18,"1244":29,"1245":7,"1246":22,"1247":697,"1248":7,"1249":32,"1250":8,"1251":6,"1252":12,"1253":16,"1254":19,"1255":13,"1256":20,"1257":16,"1258":18,"1259":626,"1260":12,"1261":8,"1262":12,"1263":72,"1264":33,"1265":50,"1266":18,"1267":27,"1268":14,"1269":38,"1270":1080,"1271":245,"1272":18,"1273":37,"1274":27,"1275":48,"1276":28,"1277":22,"1278":38,"1279":27,"1280":25,"1281":38,"1282":15,"1283":27,"1284":27,"1285":22,"1286":2691,"1287":12,"1288":18,"1289":28,"1290":41,"1291":7,"1292":17,"1293":33,"1294":37,"1295":11,"1296":6,"1297":37,"1298":19,"1299":6,"1300":2030,"1301":393,"1302":11,"1303":5,"1304":28,"1305":13,"1306":11,"1307":29,"1308":10,"1309":659,"1310":175,"1311":1740,"1312":201,"1313":10,"1314":12,"1315":31,"1316":21,"1317":30,"1318":54,"1319":10,"1320":47,"1321":4,"1322":21,"1323":1507,"1324":3020,"1325":5,"1326":15,"1327":11,"1328":34,"1329":4,"1330":22,"1331":9,"1332":39,"1333":38,"1334":22,"1335":11,"1336":22,"1337":41,"1338":34,"1339":1358,"1340":8,"1341":4,"1342":48,"1343":23,"1344":22,"1345":6,"1346":6,"1347":41,"1348":19,"1349":25,"1350":1293,"1351":8,"1352":44,"1353":38,"1354":8,"1355":27,"1356":6,"1357":36,"1358":35,"1359":4088,"1360":1,"1361":3,"1362":21,"1363":7,"1364":8,"1365":11,"1366":61,"1367":11,"1368":15,"1369":387,"1370":6,"1371":14,"1372":26,"1373":24,"1374":6,"1375":18,"1376":17,"1377":6,"1378":25,"1379":24,"1380":654,"1381":6,"1382":15,"1383":24,"1384":25,"1385":5,"1386":9,"1387":15,"1388":9,"1389":39,"1390":17,"1391":26,"1392":15,"1393":10,"1394":8,"1395":2089,"1396":2756,"1397":16,"1398":26,"1399":29,"1400":16,"1401":20,"1402":1020,"1403":9,"1404":4,"1405":19,"1406":31,"1407":12,"1408":11,"1409":24,"1410":18,"1411":21,"1412":8,"1413":9,"1414":11,"1415":1296,"1416":280,"1417":1296,"1418":17,"1419":9,"1420":11,"1421":14,"1422":25,"1423":37,"1424":21,"1425":24,"1426":15,"1427":376,"1428":11,"1429":36,"1430":48,"1431":98,"1432":40,"1433":32,"1434":9,"1435":56,"1436":4571,"1437":18,"1438":498,"1439":7,"1440":11,"1441":73,"1442":32,"1443":84,"1444":18,"1445":28,"1446":13,"1447":60,"1448":4,"1449":23,"1450":2,"1451":4,"1452":9,"1453":83,"1454":46,"1455":264,"1456":55,"1457":18,"1458":16,"1459":52,"1460":36,"1461":11,"1462":5,"1463":6,"1464":4,"1465":48,"1466":9,"1467":344,"1468":3,"1469":7,"1470":6,"1471":1,"1472":19,"1473":16,"1474":49,"1475":81,"1476":44,"1477":8,"1478":20,"1479":11,"1480":16,"1481":11,"1482":11,"1483":4,"1484":4,"1485":4,"1486":45,"1487":20,"1488":158,"1489":97,"1490":81,"1491":28,"1492":151,"1493":2,"1494":1,"1496":165,"1497":2,"1498":9,"1499":2,"1500":14,"1502":1,"1503":3,"1504":3,"1507":6,"1508":12,"1509":4,"1510":1,"1511":2,"1512":6,"1513":6,"1514":1,"1517":2,"1518":1,"1521":1,"1523":3,"1524":17,"1525":15,"1526":160,"1527":5,"1528":2,"1529":1,"1530":1,"1531":36,"1532":7,"1534":54,"1535":7,"1536":7,"1537":19,"1538":2,"1539":3,"1540":1,"1541":2,"1542":5,"1543":2,"1544":8,"1545":7,"1546":3,"1547":14,"1548":4,"1549":2,"1550":1,"1551":2,"1552":1,"1553":22,"1554":8,"1555":2,"1556":5,"1557":4,"1558":1,"1559":1,"1561":1,"1565":2,"1566":4,"1567":1,"1568":2,"1569":5,"1570":6,"1571":2,"1572":14,"1574":3,"1575":18,"1576":14,"1577":208,"1578":6,"1579":2,"1581":5,"1582":29,"1583":7,"1584":2,"1585":36,"1586":10,"1588":5,"1589":18,"1590":2,"1591":91,"1592":8,"1595":4,"1596":36,"1597":4,"1598":5,"1599":27,"1600":6,"1602":2,"1605":6,"1607":6,"1608":1,"1609":3,"1610":2,"1611":9,"1612":1,"1613":1,"1614":2,"1615":1,"1616":1,"1617":1,"1621":5,"1626":5,"1627":24,"1629":5,"1630":41,"1631":115,"1632":53,"1633":2,"1634":10,"1635":17,"1636":2,"1637":39,"1638":40,"1639":75,"1640":32,"1641":113,"1642":1,"1643":7,"1644":1,"1645":1,"1646":13,"1647":11,"1648":5,"1649":6,"1650":5,"1651":50,"1652":10,"1653":29,"1654":22,"1655":2,"1656":20,"1657":1,"1658":3,"1659":4,"1660":1,"1661":19,"1662":31,"1663":16,"1664":40,"1665":21,"1666":23,"1667":30,"1668":33,"1669":22,"1670":41,"1671":27,"1672":13,"1673":23,"1674":4,"1675":23,"1676":1,"1677":16,"1678":5,"1679":4,"1681":5,"1682":3,"1683":4,"1684":13,"1685":54,"1686":43,"1687":7,"1688":2,"1689":7,"1692":12,"1693":4,"1694":3,"1695":2,"1696":5,"1697":5,"1698":4,"1699":5,"1700":11,"1701":16,"1702":35,"1703":3,"1704":3,"1705":2,"1706":6,"1707":15,"1708":20,"1709":4,"1710":5,"1714":4,"1715":3,"1716":2,"1717":1,"1718":12,"1719":10,"1720":10,"1721":11,"1722":10,"1723":7,"1724":19,"1725":1,"1726":8,"1727":50,"1728":17,"1729":21,"1730":8,"1731":13,"1732":5,"1733":1,"1734":10,"1735":6,"1736":3,"1737":4,"1738":4,"1739":6,"1741":6,"1742":3,"1743":3,"1744":2,"1747":1,"1748":28,"1749":25,"1750":18,"1751":6,"1755":8,"1757":1,"1758":3,"1759":5,"1760":7,"1761":10,"1763":2,"1764":50,"1765":77,"1766":1,"1767":42,"1768":25,"1769":28,"1770":2,"1771":6,"1772":3,"1773":9,"1774":2,"1775":1,"1776":19,"1778":2,"1779":10,"1780":10,"1781":16,"1782":9,"1783":11,"1784":12,"1785":5,"1786":4,"1787":3,"1788":14,"1789":10,"1790":34,"1792":12,"1793":9,"1794":9,"1795":7,"1796":8,"1797":9,"1798":3,"1799":19,"1800":20,"1801":20,"1802":11,"1803":6,"1804":6,"1805":51,"1806":18,"1807":14,"1808":7,"1809":5,"1810":45,"1811":10,"1812":3,"1813":6,"1814":7,"1815":46,"1816":12,"1817":20,"1818":14,"1820":6,"1821":35,"1822":8,"1823":17,"1824":3,"1825":19,"1826":17,"1827":10,"1828":13,"1829":21,"1830":9,"1831":13,"1832":9,"1833":21,"1834":16,"1835":8,"1836":1,"1837":1,"1838":3,"1839":21,"1840":23,"1841":14,"1842":16,"1843":16,"1844":9,"1845":4,"1846":8,"1847":10,"1848":6,"1849":25,"1850":2,"1851":29,"1852":9,"1853":16,"1854":18,"1855":2,"1856":14,"1857":1,"1858":8,"1859":5,"1860":3,"1861":8,"1862":15,"1863":10,"1864":8,"1865":14,"1866":4,"1867":9,"1868":15,"1869":14,"1870":6,"1871":12,"1872":2,"1873":7,"1874":18,"1875":10,"1876":32,"1877":25,"1878":9,"1879":7,"1880":6,"1881":23,"1882":15,"1883":9,"1884":7,"1885":15,"1886":13,"1887":10,"1888":8,"1889":28,"1890":4,"1891":18,"1892":16,"1893":11,"1894":9,"1895":6,"1896":8,"1897":6,"1898":19,"1899":14,"1900":13,"1901":4,"1902":35,"1903":5,"1904":13,"1905":16,"1906":8,"1907":6,"1908":27,"1909":49,"1910":17,"1911":38,"1912":26,"1913":8,"1914":4,"1915":14,"1916":35,"1917":14,"1918":16,"1919":17,"1920":20,"1921":10,"1922":52,"1923":48,"1924":16,"1925":9,"1926":15,"1927":30,"1928":7,"1929":16,"1930":20,"1931":8,"1932":5,"1933":40,"1934":12,"1935":12,"1936":8,"1937":8,"1938":1,"1939":8,"1940":30,"1941":18,"1942":11,"1943":6,"1944":27,"1945":16,"1946":27,"1947":28,"1948":50,"1949":15,"1950":37,"1951":12,"1952":14,"1953":6,"1954":18,"1955":12,"1956":8,"1957":8,"1958":14,"1959":20,"1960":5,"1961":10,"1962":23,"1963":7,"1964":13,"1965":18,"1966":14,"1967":8,"1968":12,"1969":4,"1970":21,"1971":17,"1972":24,"1973":13,"1974":6,"1975":18,"1976":10,"1977":27,"1978":16,"1979":27,"1980":26,"1981":4,"1982":27,"1983":6,"1984":20,"1985":24,"1986":15,"1987":12,"1988":26,"1989":20,"1990":7,"1991":31,"1992":15,"1993":8,"1994":37,"1995":37,"1996":18,"1997":16,"1998":17,"1999":13,"2000":59,"2001":46,"2002":32,"2003":19,"2004":9,"2005":5,"2006":17,"2007":18,"2008":5,"2009":4,"2010":20,"2011":11,"2012":15,"2013":16,"2014":8,"2015":14,"2016":6,"2017":3,"2018":3,"2019":6,"2020":4,"2021":2,"2022":2,"2023":2,"2024":36,"2025":15,"2026":4,"2027":8,"2028":13,"2029":13,"2030":17,"2031":3,"2032":23,"2033":9,"2034":31,"2035":8,"2036":10,"2037":14,"2038":15,"2039":15,"2040":4,"2041":15,"2042":9,"2043":48,"2044":34,"2045":15,"2046":15,"2047":37,"2048":30,"2049":23,"2050":9,"2051":6,"2052":2,"2053":12,"2054":5,"2055":25,"2056":28,"2057":24,"2058":9,"2059":11,"2060":12,"2061":8,"2062":3,"2063":26,"2064":18,"2065":5,"2066":14,"2067":30,"2068":4,"2069":10,"2070":23,"2071":6,"2072":11,"2073":15,"2074":19,"2075":22,"2076":13,"2077":15,"2078":6,"2079":19,"2080":17,"2081":20,"2082":9,"2083":19,"2084":6,"2085":19,"2086":11,"2087":21,"2088":2,"2089":11,"2090":4,"2091":1,"2092":11,"2093":1,"2094":11,"2095":1,"2096":15,"2097":8,"2098":15,"2099":16,"2100":15,"2101":3,"2102":1,"2103":3,"2104":8,"2105":13,"2106":7,"2107":24,"2108":12,"2109":9,"2110":15,"2111":5,"2112":11,"2113":21,"2114":10,"2115":8,"2116":23,"2117":15,"2118":9,"2119":5,"2120":1,"2121":13,"2122":9,"2123":24,"2124":17,"2125":5,"2126":48,"2127":15,"2128":8,"2129":19,"2130":13,"2131":26,"2132":18,"2133":19,"2134":14,"2135":14,"2136":17,"2137":8,"2138":26,"2139":23,"2140":14,"2141":15,"2142":37,"2143":14,"2144":10,"2145":17,"2146":20,"2147":15,"2148":6,"2149":22,"2150":26,"2151":7,"2152":10,"2153":20,"2154":6,"2155":9,"2156":55,"2157":11,"2158":41,"2159":9,"2160":29,"2161":15,"2162":6,"2163":15,"2164":20,"2165":21,"2166":31,"2167":30,"2168":25,"2169":30,"2170":20,"2171":8,"2172":11,"2173":8,"2174":20,"2175":16,"2176":8,"2177":19,"2178":14,"2179":16,"2180":39,"2181":10,"2182":9,"2183":20,"2184":7,"2185":14,"2186":11,"2187":5,"2188":22,"2189":9,"2190":14,"2191":6,"2192":32,"2193":41,"2194":14,"2195":14,"2196":10,"2197":20,"2198":8,"2199":13,"2200":12,"2201":8,"2202":24,"2203":30,"2204":8,"2205":1,"2206":4,"2207":6,"2208":2,"2209":7,"2210":11,"2211":1,"2212":1,"2213":2,"2214":8,"2215":6,"2220":15,"2221":7,"2225":1,"2226":3,"2227":11,"2228":7,"2230":26,"2231":2,"2232":160,"2234":1,"2236":2,"2239":12,"2240":3,"2245":4}}],["adb",{"2":{"1479":1},"3":{"1479":6}}],["ada",{"3":{"1364":1,"1369":2,"1436":1}}],["adaptation",{"3":{"1359":1,"1417":1,"1461":1,"1492":2}}],["adapted",{"3":{"1312":1,"1611":1}}],["adapterfactorytests",{"3":{"1199":1,"1207":2,"1427":3,"1437":1,"1487":1}}],["adapters",{"1":{"1417":1},"3":{"53":2,"74":1,"482":1,"1004":2,"1091":3,"1096":1,"1192":2,"1202":1,"1203":1,"1212":1,"1237":1,"1259":1,"1301":12,"1309":1,"1311":1,"1312":3,"1359":1,"1360":2,"1366":1,"1368":1,"1370":1,"1377":1,"1380":5,"1396":13,"1402":1,"1416":9,"1417":15,"1420":1,"1427":2,"1436":3,"1437":1,"1438":3,"1487":1,"1496":3,"1500":1,"1503":1,"1513":1,"1526":2,"1540":2,"1577":1,"1660":1,"1931":1,"1932":2,"1941":2,"2067":1,"2071":2,"2116":2,"2119":1,"2120":1,"2123":1,"2230":1}}],["adapter",{"0":{"1365":1,"1366":1,"1367":1,"1935":1},"3":{"0":4,"51":1,"53":5,"54":2,"117":1,"380":1,"584":1,"660":1,"725":1,"1016":1,"1020":1,"1022":1,"1075":1,"1076":1,"1077":2,"1089":1,"1091":7,"1092":3,"1093":1,"1094":2,"1150":1,"1192":2,"1202":2,"1203":2,"1212":1,"1213":1,"1258":1,"1259":3,"1271":2,"1286":2,"1296":2,"1300":10,"1301":18,"1307":2,"1309":28,"1311":1,"1312":19,"1323":3,"1324":6,"1339":2,"1349":2,"1350":9,"1359":23,"1360":2,"1365":2,"1366":1,"1368":2,"1369":13,"1378":5,"1380":16,"1395":1,"1396":30,"1400":3,"1402":3,"1404":1,"1415":11,"1416":4,"1417":79,"1418":1,"1419":1,"1420":3,"1422":1,"1425":1,"1427":18,"1435":1,"1436":1,"1437":1,"1438":3,"1455":1,"1496":7,"1526":2,"1539":1,"1553":1,"1577":3,"1591":1,"1663":1,"1735":1,"1790":3,"1915":2,"1916":4,"1920":1,"1923":1,"1927":1,"1932":1,"1938":1,"1946":2,"1948":1,"1949":2,"2017":3,"2018":1,"2019":2,"2026":1,"2028":2,"2119":2,"2123":1,"2170":1,"2172":2,"2180":1,"2227":1}}],["adapting",{"3":{"1286":1,"1324":2,"1395":1}}],["adapts",{"3":{"690":1,"1230":1,"1247":1,"1309":1,"1311":1,"1324":3,"1395":1,"1417":6,"1420":1,"1436":1,"1627":1}}],["adapt",{"3":{"413":1,"1417":3,"1559":1,"1649":1,"2121":1}}],["adherence",{"3":{"1359":3,"1417":4}}],["adequate",{"3":{"1324":1,"1537":1,"1559":1,"2171":1,"2179":1}}],["adee5058",{"2":{"563":1},"3":{"563":1,"1496":2}}],["adjudicated",{"3":{"1526":1,"1531":1,"1578":1}}],["adjustable",{"3":{"1798":1}}],["adjusting",{"3":{"1638":1,"1765":1}}],["adjustinventory",{"3":{"1270":1}}],["adjustinventorycommand",{"3":{"1270":1}}],["adjustinventoryhandler",{"3":{"897":2,"900":1,"1767":1}}],["adjusted",{"3":{"1359":1,"1767":1,"2195":1}}],["adjustment",{"3":{"1323":1,"1742":1,"1749":1,"1767":1}}],["adjustments",{"3":{"1237":1}}],["adjusttouniversal",{"3":{"1247":1,"1309":1,"1312":3,"1396":2}}],["adjust",{"3":{"343":1,"1311":2,"1339":1,"1359":1,"1396":1,"1537":1,"1639":1,"1742":1}}],["adjacency",{"3":{"583":1,"749":1,"751":1,"1311":4,"1315":1,"1323":1,"1436":3,"1488":1,"1666":1,"1825":1}}],["adjacencies",{"3":{"0":2,"464":1,"749":1,"750":1,"751":1,"1212":2,"1311":5,"1436":4,"1488":1,"1825":2,"2230":1,"2232":1}}],["adjacent",{"0":{"1300":1},"3":{"325":1,"371":1,"464":1,"535":1,"608":1,"974":1,"1052":1,"1270":2,"1285":1,"1300":1,"1395":3,"1416":1,"1825":1,"1870":1}}],["ad",{"3":{"158":1,"709":1,"809":1,"1216":1,"1237":1,"1286":1,"1300":1,"1323":2,"1324":4,"1350":3,"1359":3,"1396":4,"1402":1,"1436":1,"1467":2,"1475":1,"1489":1,"1537":2,"1545":1,"1813":1,"1930":1,"1991":1}}],["admissibility",{"3":{"1241":1}}],["admission",{"3":{"689":1,"691":1,"1286":1,"1309":1,"1324":1,"1416":1,"1629":1,"1631":1}}],["admit",{"3":{"1118":1,"1286":1,"1339":1,"1350":1,"1359":1,"1653":1}}],["admits",{"3":{"235":1,"237":1,"707":1,"827":1,"916":1,"948":1,"1049":1,"1091":1,"1241":1,"1242":1,"1301":1,"1307":1,"1324":1,"1339":2,"1372":1,"1395":1,"1396":1,"1591":1}}],["admitting",{"3":{"178":1,"682":1,"946":1,"1242":1,"1247":2,"1286":2,"1323":1,"1359":2,"1369":2,"1417":1,"1423":1,"1436":1,"2175":1}}],["admitted",{"3":{"0":1,"111":1,"237":1,"1116":1,"1122":1,"1124":1,"1128":1,"1212":1,"1247":1,"1309":2,"1324":7,"1339":1,"1436":3,"1640":1,"2065":1}}],["adminuserscontroller",{"3":{"2202":1}}],["adminuserenabled",{"2":{"1465":1,"1469":1},"3":{"1465":1,"1469":1}}],["admincreateforms",{"3":{"1436":1}}],["admincredentials",{"3":{"1199":1,"1207":1,"1433":1,"1436":5}}],["adminclient",{"3":{"1436":1}}],["adminpages",{"2":{"1627":1},"3":{"1436":1,"1627":1}}],["adminpaths",{"3":{"1433":1,"1436":2}}],["adminpanelsettings",{"3":{"1415":1}}],["adminplaneport",{"3":{"640":1,"1436":1}}],["admin123",{"3":{"1415":2,"1436":1,"1488":1}}],["admins",{"3":{"1396":2,"1402":7,"1749":1,"1771":1,"1869":1}}],["admintoken",{"3":{"1436":1}}],["admintargetport",{"3":{"1339":1}}],["admintestcontext",{"3":{"1199":2,"1207":1}}],["adminendpoint",{"3":{"1339":2}}],["adminendpointname",{"3":{"1339":1}}],["adminendpointexpression",{"2":{"1327":1},"3":{"1327":1,"1339":1}}],["administer",{"3":{"1415":1,"2200":1}}],["administered",{"3":{"1286":1}}],["administers",{"3":{"1285":1,"1415":2}}],["administrative",{"3":{"461":1,"497":1,"1300":4,"1339":2,"1395":1,"1414":1,"1415":2,"1630":1}}],["administrationrequests",{"3":{"1207":2,"1300":8}}],["administrationrequestvalidators",{"3":{"1207":2,"1300":9}}],["administrationcontrollerbasetests",{"3":{"1199":1,"1207":5,"1311":2}}],["administrationpermissions",{"2":{"1059":2,"1963":1,"2200":1,"2201":2},"3":{"1059":3,"1199":14,"1203":1,"1207":2,"1297":4,"1300":13,"1311":2,"1324":3,"1415":8,"1496":1,"1963":1,"2200":1,"2201":2}}],["administration",{"0":{"1409":1,"2201":1},"2":{"1300":1},"3":{"0":3,"19":1,"27":1,"186":1,"305":1,"444":1,"497":3,"499":1,"536":2,"552":1,"640":1,"649":1,"698":1,"809":2,"881":2,"931":1,"1042":1,"1044":2,"1057":1,"1058":1,"1059":19,"1060":1,"1061":1,"1082":3,"1086":2,"1203":28,"1207":110,"1212":2,"1256":1,"1257":1,"1259":12,"1275":6,"1286":17,"1287":2,"1297":5,"1300":61,"1311":23,"1318":1,"1320":1,"1323":11,"1324":58,"1327":1,"1339":1,"1403":3,"1409":4,"1414":3,"1415":34,"1436":11,"1438":1,"1441":1,"1467":1,"1496":4,"1526":2,"1535":1,"1621":3,"1625":1,"1626":1,"1627":1,"1760":2,"1962":1,"1963":1,"2193":1,"2194":1,"2200":1,"2201":1,"2202":1,"2203":1}}],["administrators",{"3":{"1286":1,"1300":1,"1311":1,"1324":1,"1763":1,"1764":1,"1776":1}}],["administrator",{"3":{"458":1,"714":1,"1300":1,"1304":1,"1311":2,"1324":1,"1339":1,"1406":2,"1415":3,"1467":1,"1637":1,"1638":1,"1765":4,"1768":1,"1777":1,"2224":1}}],["adminrolescontrollertests",{"3":{"1199":1,"1207":2}}],["adminrolescontroller",{"2":{"1409":1},"3":{"1199":2,"1203":1,"1207":2,"1300":1,"1311":2,"1409":2,"1415":6,"1496":2,"2202":2}}],["adminbypassroles",{"2":{"260":1,"261":1,"392":1,"393":1},"3":{"243":1,"260":2,"261":1,"392":1,"393":1,"1350":1,"1380":1}}],["admin",{"0":{"1760":1},"2":{"1466":1,"1468":1,"1470":2,"1472":1,"1477":2,"1637":1,"1757":1,"1760":1},"3":{"0":5,"24":1,"186":3,"233":1,"234":1,"235":2,"236":1,"245":1,"259":1,"317":1,"318":6,"320":3,"324":2,"340":1,"388":1,"390":1,"570":1,"578":1,"599":3,"600":2,"601":1,"618":1,"640":3,"642":1,"698":1,"719":1,"743":1,"897":1,"905":1,"1028":2,"1058":1,"1059":3,"1075":1,"1212":1,"1237":1,"1247":1,"1248":1,"1256":1,"1259":2,"1270":2,"1286":9,"1297":2,"1300":14,"1309":6,"1311":6,"1318":1,"1323":5,"1324":42,"1332":1,"1339":9,"1359":1,"1379":1,"1380":2,"1395":11,"1396":6,"1402":9,"1409":6,"1414":4,"1415":15,"1431":1,"1433":1,"1436":31,"1437":2,"1438":6,"1440":1,"1441":2,"1442":1,"1443":1,"1444":1,"1446":3,"1447":2,"1455":4,"1456":1,"1465":3,"1466":1,"1467":11,"1468":1,"1469":1,"1470":2,"1472":3,"1474":1,"1477":9,"1488":4,"1490":1,"1524":1,"1526":4,"1535":1,"1548":1,"1577":1,"1591":11,"1596":3,"1600":4,"1608":1,"1625":1,"1626":2,"1627":5,"1630":1,"1637":1,"1638":1,"1641":1,"1653":1,"1668":1,"1742":3,"1746":1,"1747":11,"1748":17,"1749":18,"1750":5,"1754":2,"1755":1,"1757":6,"1759":1,"1760":7,"1761":5,"1764":2,"1765":7,"1767":4,"1768":13,"1769":1,"1771":4,"1773":1,"1775":2,"1776":1,"1777":2,"1828":1,"1869":2,"1881":1,"1945":1,"1963":1,"1994":1,"1995":1,"2008":2,"2034":1,"2045":1,"2195":1,"2201":3,"2232":1}}],["adornments",{"3":{"1324":1}}],["adojobstore",{"2":{"1043":1},"3":{"1043":1}}],["ado",{"3":{"0":1,"1067":1,"1068":1,"1135":1,"1273":1,"1286":3}}],["adoptable",{"3":{"136":1,"923":1,"1417":1,"1492":1,"1929":1}}],["adopting",{"3":{"0":2,"24":1,"25":1,"41":1,"60":1,"62":1,"91":1,"160":1,"189":1,"228":1,"245":1,"305":1,"333":1,"352":2,"359":1,"472":1,"620":1,"657":1,"665":1,"667":1,"668":1,"670":1,"732":1,"733":1,"734":1,"854":1,"891":1,"1043":1,"1061":2,"1075":1,"1144":1,"1154":1,"1161":1,"1232":1,"1233":1,"1237":2,"1241":1,"1270":1,"1271":1,"1277":1,"1281":1,"1286":10,"1300":3,"1323":4,"1369":1,"1425":1,"1436":2,"1489":1,"1492":2,"1599":1,"1602":1,"1653":1,"1659":1,"1661":1,"1810":1,"1923":1,"1978":1,"2055":1,"2142":1,"2192":1,"2202":3}}],["adoption",{"0":{"1860":1},"3":{"0":17,"27":1,"128":1,"160":1,"186":1,"193":1,"195":1,"223":1,"230":2,"279":1,"298":1,"305":1,"308":1,"318":1,"320":1,"322":1,"324":1,"325":1,"326":1,"343":1,"350":2,"357":1,"358":1,"359":1,"360":1,"365":2,"401":1,"450":1,"468":2,"470":1,"477":1,"484":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":2,"527":1,"529":1,"530":1,"534":2,"536":1,"543":1,"545":2,"546":1,"550":3,"556":1,"564":1,"566":1,"572":1,"573":1,"574":1,"575":2,"578":1,"599":2,"603":1,"609":1,"611":1,"618":1,"626":1,"629":1,"632":1,"640":1,"649":1,"655":1,"657":2,"663":1,"667":1,"669":1,"696":1,"698":1,"702":1,"713":1,"715":1,"723":1,"725":1,"728":1,"733":2,"734":1,"741":1,"766":1,"769":1,"774":1,"790":1,"792":1,"825":2,"829":1,"832":1,"861":2,"863":1,"864":1,"879":1,"881":1,"891":2,"897":1,"900":2,"913":1,"920":1,"924":1,"926":1,"942":1,"954":1,"956":1,"963":2,"964":2,"968":1,"972":1,"981":1,"986":1,"988":1,"994":2,"996":2,"999":1,"1050":3,"1051":1,"1053":1,"1054":1,"1055":1,"1084":1,"1133":1,"1143":1,"1147":1,"1153":1,"1161":1,"1164":1,"1168":1,"1184":2,"1188":1,"1190":1,"1212":3,"1250":1,"1259":1,"1264":2,"1266":2,"1270":8,"1286":3,"1300":2,"1301":3,"1311":1,"1323":1,"1324":1,"1339":2,"1350":1,"1415":1,"1417":2,"1436":18,"1488":1,"1489":2,"1496":5,"1500":2,"1524":2,"1532":1,"1574":1,"1581":2,"1582":3,"1583":1,"1585":2,"1591":2,"1596":1,"1597":1,"1599":4,"1600":1,"1601":1,"1737":1,"1815":2,"1862":1,"1876":1,"1948":1,"1963":1,"1970":1,"1971":1,"1975":1,"1978":2,"1979":1,"2044":1,"2046":1,"2055":1,"2060":1,"2132":1,"2133":1,"2143":2,"2168":1,"2179":1,"2203":1,"2232":1}}],["adopt",{"3":{"0":6,"47":1,"149":1,"258":1,"265":1,"350":1,"395":1,"413":1,"448":1,"449":1,"599":1,"619":1,"649":1,"698":1,"706":1,"723":1,"725":1,"747":1,"749":1,"833":1,"838":1,"853":1,"913":1,"974":1,"1052":1,"1093":1,"1094":1,"1122":1,"1127":1,"1134":1,"1141":1,"1145":1,"1151":1,"1161":1,"1162":1,"1186":1,"1241":1,"1247":1,"1270":1,"1286":1,"1311":3,"1323":1,"1324":3,"1339":1,"1395":3,"1417":1,"1436":5,"1467":1,"1473":1,"1482":1,"1488":2,"1496":4,"1524":1,"1530":1,"1532":1,"1595":1,"1599":2,"1675":1,"1709":1,"1765":1,"1807":1,"1851":1,"1959":1,"2000":1,"2026":1,"2063":1,"2079":1,"2133":1,"2143":1,"2153":1,"2197":1,"2239":1}}],["adopts",{"3":{"0":4,"246":1,"305":1,"325":1,"400":1,"408":1,"446":1,"448":1,"572":2,"618":1,"619":1,"657":1,"665":1,"715":2,"790":1,"792":1,"794":1,"827":1,"1034":1,"1059":1,"1061":1,"1161":3,"1212":1,"1247":1,"1270":1,"1286":3,"1300":2,"1301":1,"1311":2,"1324":1,"1337":1,"1339":3,"1395":3,"1396":1,"1417":1,"1421":1,"1424":1,"1427":2,"1436":4,"1447":1,"1488":2,"1492":1,"1526":1,"1531":1,"1670":1,"2024":1,"2057":1,"2131":2,"2175":1,"2179":1,"2194":1}}],["adopter",{"3":{"0":3,"243":1,"245":1,"325":1,"360":1,"361":1,"551":1,"633":4,"634":2,"635":1,"651":2,"702":1,"726":1,"780":2,"782":2,"938":1,"939":1,"966":1,"1133":1,"1163":1,"1212":2,"1270":3,"1436":1,"1675":1,"1727":1,"1851":1,"1922":1,"1978":1,"2077":1,"2167":1,"2202":1}}],["adopters",{"3":{"0":3,"243":4,"244":1,"247":1,"249":1,"256":2,"324":1,"353":1,"415":1,"634":1,"635":1,"649":1,"782":1,"786":1,"1052":1,"1237":1,"1270":4,"1301":1,"1436":3,"1671":1,"1673":1,"1923":1}}],["adopted",{"0":{"1963":1,"2151":1},"1":{"1138":1,"1139":1,"1140":1,"1141":1,"1142":1,"1143":1,"1144":1,"1145":1,"1146":1,"1147":1,"1148":1,"1149":1,"1150":1,"1151":1,"1152":1,"1153":1,"1154":1,"1155":1,"1156":1,"1157":1},"3":{"0":23,"150":1,"153":1,"214":1,"218":1,"243":1,"259":1,"261":1,"302":1,"310":1,"350":1,"358":1,"359":1,"360":1,"365":1,"418":1,"454":1,"536":1,"545":1,"556":1,"572":1,"609":2,"642":1,"650":1,"655":1,"657":1,"664":1,"665":1,"667":1,"725":1,"726":1,"747":1,"790":1,"827":1,"854":1,"857":1,"954":1,"962":1,"973":1,"976":1,"1043":1,"1049":1,"1061":1,"1075":1,"1081":2,"1089":1,"1095":1,"1138":1,"1148":1,"1212":5,"1237":1,"1259":1,"1264":1,"1265":1,"1270":3,"1271":1,"1286":3,"1293":1,"1295":1,"1300":2,"1311":1,"1312":1,"1323":1,"1324":1,"1395":1,"1436":7,"1447":1,"1488":1,"1489":1,"1496":3,"1531":1,"1534":1,"1545":1,"1582":1,"1583":1,"1706":1,"1748":1,"1815":1,"1875":1,"1972":1,"2127":1,"2128":1,"2138":1,"2143":2,"2145":1,"2146":1,"2168":1,"2181":1,"2220":2,"2232":2}}],["advantage",{"3":{"2220":1}}],["advancing",{"3":{"1477":1}}],["advanceable",{"3":{"1396":1}}],["advanceabletimeprovider",{"3":{"1199":2,"1207":1}}],["advanced",{"3":{"1300":1,"1350":1,"1358":1,"1359":2,"2220":1}}],["advances",{"3":{"707":2,"848":1,"1229":2,"1247":3,"1270":2,"1323":2,"1324":1,"1339":1,"1395":2,"1427":1,"1438":1,"1876":1,"1948":1,"2232":1}}],["advance",{"3":{"0":1,"539":1,"690":1,"709":1,"810":1,"1075":1,"1212":1,"1323":1,"1324":2,"1395":1,"1482":1,"1912":1}}],["advice",{"3":{"1278":1,"1369":1,"1847":1,"2085":1}}],["advised",{"3":{"698":1}}],["advisories",{"3":{"0":1,"370":1,"618":1,"1436":1,"1453":1,"1455":1,"1459":1}}],["advisory",{"3":{"0":5,"135":1,"145":1,"150":1,"226":1,"265":1,"370":4,"371":3,"372":1,"373":1,"397":1,"440":1,"509":1,"584":1,"591":1,"617":1,"619":2,"624":3,"626":4,"640":1,"952":1,"956":1,"958":1,"1032":1,"1034":1,"1036":1,"1037":1,"1067":1,"1069":1,"1073":1,"1075":2,"1077":2,"1212":1,"1312":1,"1323":1,"1333":1,"1359":11,"1417":1,"1433":1,"1436":1,"1438":2,"1443":1,"1453":5,"1454":1,"1455":2,"1456":2,"1459":4,"1460":4,"1488":2,"1490":2,"1492":1,"1526":4,"1534":2,"1577":4,"1582":1,"1585":1,"1591":7,"1599":1,"1600":2,"1630":1,"1631":1,"1638":1,"1643":1,"1648":1,"1673":1,"1716":2,"2010":1,"2057":1,"2126":1,"2156":1,"2232":2,"2234":1}}],["adversarially",{"3":{"1496":2}}],["adversarial",{"3":{"1185":1,"1436":11,"1487":1,"1489":3,"1496":11,"1500":1,"1578":1,"1896":1,"1898":1}}],["advertised",{"3":{"741":1,"1380":1,"1396":1,"1489":1,"2063":1}}],["advertise",{"3":{"447":1,"1244":1,"1247":1,"1309":1,"1324":1,"2130":1}}],["advertises",{"3":{"31":1,"33":1,"448":1,"1218":1,"1229":1,"1270":1,"1288":1,"1311":2,"1324":1,"1350":1,"1376":1,"1380":1,"1436":2,"2131":1}}],["advertising",{"3":{"300":1,"1265":1,"1300":1,"1320":1,"1359":1,"2137":1}}],["adcpartnercollectionresult",{"3":{"1496":1}}],["adcpartnerinfo",{"3":{"1496":1}}],["adcprodstpys4way4uzb3g",{"2":{"1473":1,"1478":1},"3":{"1467":1,"1473":1,"1478":1}}],["adccollectionresult",{"3":{"1496":1}}],["adceventinfo",{"3":{"1496":1}}],["adcoauthuisettings",{"3":{"1496":1}}],["adckv",{"3":{"1474":1}}],["adc2026",{"3":{"1347":1,"1350":1}}],["adcapphostcollection",{"2":{"1449":1},"3":{"1199":2,"1203":1,"1207":1,"1328":1,"1339":5,"1449":1,"1496":1}}],["adcapphostsmoketests",{"2":{"1069":1,"1450":1},"3":{"914":1,"1067":2,"1069":2,"1070":1,"1199":1,"1203":1,"1207":2,"1328":2,"1339":11,"1449":10,"1450":2,"1496":2}}],["adcapphostfixture",{"2":{"1450":1},"3":{"914":1,"1069":2,"1070":1,"1199":3,"1203":1,"1207":3,"1328":2,"1339":6,"1449":7,"1450":1,"1496":2}}],["adcarchitecturemap",{"2":{"132":1,"1161":1,"2103":1},"3":{"132":1,"1161":1,"1199":42,"1207":2,"1323":1,"1341":1,"1396":1,"1436":60,"1487":1,"1489":5,"1526":5,"1535":1,"2042":1,"2103":1}}],["adchometicketingtests",{"3":{"1199":1,"1207":1}}],["adchometests",{"3":{"1199":1,"1207":5,"1350":1,"1395":1,"1438":1}}],["adchomepartnerstests",{"3":{"1199":1,"1207":1}}],["adchomepagecontent",{"3":{"1199":3,"1203":2,"1207":4,"1324":3,"1395":3,"1416":21}}],["adchomeservicedoubles",{"3":{"1199":3,"1207":1}}],["adchomecontent",{"2":{"1393":1},"3":{"1199":2,"1203":1,"1207":5,"1385":2,"1393":3,"1394":1,"1395":18,"1496":1}}],["adchome",{"2":{"554":1},"3":{"554":1,"556":1,"559":2,"1199":5,"1203":1,"1207":3,"1350":7,"1359":4,"1392":5,"1393":9,"1394":1,"1395":36,"1396":1,"1416":8,"1436":3,"1489":2,"1496":8,"1526":7,"1531":1,"1535":2}}],["adc",{"0":{"92":1,"93":1,"1441":1,"1455":1,"1456":1,"1457":1,"1458":1,"1514":1,"1604":1,"1679":1},"1":{"687":1,"688":1,"689":1,"690":1,"691":1,"692":1,"693":1,"694":1,"1190":1,"1191":1,"1192":1,"1193":1,"1194":1,"1195":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":1,"1346":1,"1347":1,"1348":1,"1349":1,"1350":1,"1351":1,"1352":1,"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1,"1359":1,"1360":1,"1361":1,"1362":1,"1363":1,"1364":1,"1365":1,"1366":1,"1367":1,"1368":1,"1369":1,"1370":1,"1371":1,"1372":1,"1373":1,"1374":1,"1375":1,"1376":1,"1377":1,"1378":1,"1379":1,"1380":1,"1381":1,"1382":1,"1383":1,"1384":1,"1385":1,"1386":1,"1387":1,"1388":1,"1389":1,"1390":1,"1391":1,"1392":1,"1393":1,"1394":1,"1395":1,"1396":1,"1397":1,"1398":1,"1399":1,"1400":1,"1401":1,"1402":1,"1403":1,"1404":1,"1405":1,"1406":1,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1413":1,"1414":1,"1415":1,"1416":1,"1462":1,"1463":1,"1464":1,"1465":1,"1466":1,"1467":1,"1468":1,"1469":1,"1470":1,"1522":1,"1523":1,"1524":1,"1525":1,"1526":1,"1527":1,"1528":1,"1529":1,"1530":1,"1531":1,"1532":1,"1533":1,"1534":1,"1535":1,"1628":1,"1629":1,"1630":1,"1631":1,"1632":1,"1633":1,"1634":1,"1635":1,"1636":1,"1637":1,"1638":1,"1639":1,"1640":1,"1641":1},"2":{"47":3,"58":1,"59":2,"80":1,"95":1,"229":1,"395":1,"401":1,"564":1,"642":1,"644":1,"667":1,"998":1,"999":1,"1069":2,"1191":1,"1194":1,"1200":1,"1201":1,"1203":2,"1207":10,"1215":1,"1271":1,"1309":2,"1325":1,"1328":1,"1340":1,"1341":2,"1350":6,"1359":5,"1363":1,"1369":2,"1370":3,"1378":1,"1379":2,"1380":7,"1381":1,"1395":6,"1396":11,"1402":6,"1404":6,"1415":4,"1427":1,"1435":1,"1437":8,"1438":2,"1440":1,"1442":1,"1445":7,"1446":1,"1449":1,"1455":2,"1457":1,"1458":1,"1459":1,"1471":1,"1473":4,"1474":3,"1475":1,"1481":1,"1486":9,"1487":6,"1490":2,"1492":1,"1494":1,"1496":1,"1500":1,"1503":1,"1509":1,"1512":1,"1524":1,"1525":1,"1526":1,"1531":2,"1534":1,"1611":2,"1615":2,"1619":2,"1639":1,"2034":4,"2035":4,"2036":4,"2037":1,"2038":1,"2105":1,"2111":1,"2202":1},"3":{"0":118,"3":6,"7":2,"17":1,"27":3,"31":3,"39":1,"47":5,"57":2,"58":2,"59":13,"60":2,"62":13,"63":5,"68":2,"76":1,"80":3,"85":1,"91":2,"92":2,"93":12,"94":2,"95":13,"96":4,"97":1,"98":8,"99":2,"100":1,"101":2,"102":1,"104":4,"109":3,"115":1,"117":3,"121":3,"127":3,"128":7,"132":1,"135":1,"145":6,"146":1,"147":3,"149":3,"150":1,"151":1,"152":3,"159":1,"164":1,"173":1,"175":5,"179":4,"180":7,"181":3,"184":3,"186":12,"189":1,"195":19,"200":1,"203":4,"214":1,"217":4,"218":7,"219":2,"220":3,"229":2,"230":2,"235":7,"238":1,"241":2,"243":16,"245":9,"249":1,"250":5,"251":2,"252":2,"254":2,"257":2,"258":1,"259":5,"260":9,"261":6,"279":1,"281":2,"283":3,"285":1,"291":4,"293":1,"295":13,"300":1,"310":4,"314":2,"318":7,"324":2,"325":7,"326":2,"333":1,"341":4,"344":3,"348":2,"350":11,"352":10,"353":10,"354":1,"359":2,"368":1,"373":2,"374":3,"375":1,"378":2,"384":7,"386":5,"387":3,"388":9,"390":11,"391":1,"392":6,"393":2,"395":2,"397":1,"401":10,"403":8,"405":1,"409":1,"412":2,"413":5,"414":1,"418":24,"422":5,"423":8,"424":7,"425":8,"426":18,"427":4,"428":16,"429":13,"430":3,"438":1,"439":1,"443":1,"444":2,"446":2,"448":14,"450":1,"451":3,"452":8,"453":3,"454":5,"455":1,"459":4,"463":6,"464":2,"465":5,"466":5,"468":2,"470":22,"472":1,"474":1,"475":13,"476":18,"477":14,"478":6,"480":1,"481":1,"482":1,"483":2,"486":1,"487":2,"488":2,"489":2,"490":3,"491":6,"492":5,"493":7,"494":9,"495":7,"497":12,"499":12,"502":4,"507":6,"511":4,"513":2,"514":1,"522":2,"523":2,"524":2,"527":1,"534":1,"536":4,"543":7,"545":22,"547":2,"550":4,"551":1,"552":5,"554":3,"556":33,"558":4,"559":2,"563":5,"564":17,"566":4,"567":7,"572":38,"574":2,"575":6,"576":1,"577":4,"578":7,"582":1,"583":31,"585":14,"586":1,"589":1,"591":7,"593":1,"594":3,"597":2,"598":1,"599":36,"600":5,"601":6,"602":7,"603":7,"604":3,"607":13,"608":2,"609":59,"611":10,"613":2,"616":5,"618":8,"619":1,"620":3,"621":4,"624":8,"625":7,"626":41,"627":2,"628":6,"629":7,"632":2,"638":5,"640":25,"641":1,"642":10,"643":4,"644":7,"647":1,"649":12,"651":2,"655":4,"657":9,"663":1,"664":3,"665":12,"666":1,"667":4,"668":8,"669":8,"674":16,"677":1,"681":2,"683":4,"684":1,"685":1,"686":1,"687":1,"688":2,"689":3,"690":6,"691":4,"692":1,"698":2,"702":1,"707":1,"715":1,"717":1,"719":5,"720":2,"723":2,"724":3,"725":22,"726":1,"727":3,"728":1,"733":3,"737":1,"739":1,"741":2,"743":4,"744":6,"745":1,"749":17,"751":4,"752":2,"755":2,"756":5,"757":19,"758":2,"759":4,"760":4,"761":3,"764":4,"765":2,"766":33,"767":1,"768":3,"769":4,"774":16,"780":4,"782":15,"784":4,"785":1,"790":1,"798":5,"799":4,"803":7,"804":2,"805":6,"806":1,"825":4,"826":8,"827":25,"829":1,"830":5,"831":14,"832":11,"833":14,"836":2,"837":11,"838":20,"839":4,"840":2,"841":3,"853":1,"854":6,"855":1,"857":3,"859":1,"861":15,"862":1,"863":10,"864":3,"867":2,"868":10,"869":23,"870":10,"872":1,"873":11,"875":5,"876":26,"877":10,"881":15,"884":1,"891":4,"892":1,"893":1,"897":6,"900":1,"912":1,"913":8,"914":14,"915":2,"916":2,"917":6,"920":1,"924":3,"926":8,"927":1,"939":1,"946":3,"949":1,"954":19,"956":4,"958":4,"959":3,"960":6,"963":3,"964":1,"971":10,"972":6,"973":2,"974":4,"979":1,"980":6,"981":1,"983":2,"986":2,"988":7,"991":3,"994":2,"995":5,"996":4,"998":10,"999":7,"1004":13,"1006":1,"1007":2,"1010":2,"1012":6,"1013":1,"1014":1,"1016":2,"1017":9,"1018":17,"1021":4,"1022":1,"1036":1,"1044":2,"1045":2,"1049":4,"1050":4,"1052":2,"1054":9,"1055":2,"1058":2,"1065":2,"1066":6,"1067":2,"1068":1,"1069":6,"1070":3,"1073":2,"1074":3,"1075":3,"1078":3,"1081":1,"1082":7,"1083":3,"1085":3,"1089":2,"1090":9,"1093":4,"1094":9,"1095":6,"1096":1,"1099":4,"1100":9,"1103":2,"1114":1,"1115":2,"1116":35,"1117":2,"1119":1,"1123":2,"1124":14,"1125":3,"1126":3,"1127":3,"1129":1,"1133":4,"1135":1,"1140":6,"1142":3,"1150":13,"1154":1,"1156":1,"1161":3,"1168":7,"1183":2,"1184":3,"1190":1,"1191":4,"1192":14,"1194":2,"1195":2,"1199":2028,"1200":3,"1201":4,"1202":12,"1203":1184,"1204":3,"1206":65,"1207":6153,"1208":44,"1209":138,"1211":4,"1212":31,"1213":3,"1214":1,"1215":3,"1216":1,"1229":2,"1230":1,"1237":62,"1238":1,"1239":13,"1242":3,"1244":2,"1247":67,"1259":15,"1262":2,"1264":10,"1266":1,"1270":94,"1271":70,"1286":146,"1300":200,"1301":24,"1302":2,"1304":13,"1305":3,"1307":16,"1308":9,"1309":182,"1310":32,"1311":161,"1312":89,"1313":1,"1314":5,"1315":1,"1318":5,"1322":2,"1323":138,"1324":210,"1325":14,"1326":18,"1327":6,"1328":9,"1329":1,"1331":9,"1332":23,"1333":1,"1334":7,"1335":11,"1336":11,"1338":5,"1339":194,"1340":3,"1341":17,"1342":24,"1344":15,"1345":10,"1346":2,"1347":32,"1348":10,"1349":20,"1350":770,"1351":1,"1352":19,"1353":21,"1354":10,"1355":8,"1356":4,"1357":3,"1358":3,"1359":2783,"1360":1,"1361":2,"1363":5,"1364":5,"1365":2,"1366":3,"1367":3,"1368":3,"1369":201,"1370":5,"1371":15,"1373":8,"1374":2,"1375":1,"1376":2,"1377":14,"1378":15,"1379":7,"1380":440,"1381":4,"1383":1,"1384":1,"1389":2,"1390":4,"1395":952,"1396":1398,"1397":4,"1398":3,"1399":2,"1400":5,"1401":5,"1402":443,"1403":8,"1404":22,"1405":4,"1406":18,"1407":5,"1408":3,"1409":9,"1410":10,"1411":1,"1412":7,"1413":8,"1414":13,"1415":866,"1416":258,"1417":98,"1426":3,"1427":39,"1428":2,"1431":6,"1433":1,"1435":13,"1436":873,"1437":94,"1438":127,"1439":2,"1440":5,"1441":11,"1442":16,"1443":11,"1444":4,"1445":19,"1446":11,"1447":5,"1449":5,"1450":1,"1451":2,"1452":3,"1453":2,"1454":1,"1455":26,"1456":1,"1457":6,"1458":2,"1459":19,"1460":15,"1461":4,"1462":3,"1464":14,"1465":2,"1466":1,"1467":52,"1470":4,"1471":4,"1472":7,"1473":9,"1474":25,"1475":10,"1476":14,"1477":6,"1478":5,"1479":1,"1480":1,"1481":10,"1482":2,"1485":2,"1486":35,"1487":46,"1488":23,"1489":24,"1490":18,"1491":2,"1492":31,"1494":1,"1496":214,"1498":4,"1500":3,"1503":1,"1505":1,"1509":2,"1512":1,"1514":1,"1520":1,"1522":2,"1523":1,"1524":8,"1525":5,"1526":119,"1529":1,"1530":1,"1531":21,"1532":6,"1533":1,"1534":15,"1535":10,"1573":1,"1577":8,"1582":10,"1583":1,"1585":3,"1588":2,"1589":4,"1591":23,"1593":1,"1596":8,"1598":2,"1599":4,"1601":1,"1605":1,"1606":1,"1611":17,"1615":2,"1619":6,"1620":1,"1627":10,"1628":1,"1629":1,"1630":1,"1631":4,"1637":1,"1638":1,"1639":1,"1641":1,"1645":1,"1646":1,"1649":1,"1650":2,"1653":2,"1654":1,"1656":2,"1658":2,"1659":1,"1673":1,"1675":1,"1676":1,"1677":1,"1678":3,"1679":2,"1703":1,"1705":2,"1706":1,"1709":3,"1710":1,"1724":1,"1736":1,"1748":1,"1756":1,"1765":1,"1784":1,"1785":1,"1810":2,"1815":3,"1828":1,"1839":1,"1851":1,"1859":1,"1860":3,"1861":1,"1862":1,"1865":1,"1874":1,"1875":2,"1880":1,"1884":2,"1891":1,"1896":2,"1898":1,"1916":1,"1922":6,"1923":1,"1927":1,"1933":2,"1937":1,"1946":2,"1947":2,"1948":1,"1963":2,"1970":1,"1978":5,"1979":2,"1983":6,"1995":5,"1996":1,"2000":13,"2001":1,"2004":2,"2008":1,"2013":2,"2024":3,"2025":2,"2030":1,"2033":2,"2034":7,"2035":4,"2036":4,"2037":3,"2038":2,"2042":1,"2043":2,"2044":3,"2046":6,"2047":1,"2055":3,"2057":1,"2063":2,"2067":3,"2068":1,"2083":2,"2085":1,"2101":1,"2103":1,"2105":2,"2110":1,"2111":5,"2112":2,"2113":6,"2114":1,"2127":3,"2131":9,"2132":1,"2136":1,"2137":1,"2138":2,"2150":2,"2151":5,"2156":2,"2157":1,"2158":8,"2168":1,"2179":4,"2190":1,"2192":1,"2202":4,"2228":1,"2231":1,"2232":4}}],["addview",{"3":{"1339":2}}],["addvalidationerror",{"3":{"1300":1}}],["addvalidatorsfromassemblycontaining",{"2":{"1271":1,"1319":1},"3":{"1271":1,"1300":1,"1309":3,"1319":1,"1323":5,"1415":1}}],["addvalidatorsfromassembly",{"2":{"1830":1},"3":{"1270":3,"1271":2,"1323":3,"1359":5,"1415":3,"1436":3,"1830":1}}],["addvariantcommand",{"2":{"991":1},"3":{"897":1,"988":1,"991":2}}],["addvariant",{"3":{"897":1}}],["addvarianthandler",{"2":{"898":1,"900":1,"901":1},"3":{"0":1,"897":3,"898":1,"900":3,"901":1,"1591":1,"1597":1,"1767":1,"1769":1}}],["addxdevicecapabilities",{"3":{"1417":1}}],["addx",{"3":{"1415":1}}],["addxxxui",{"3":{"1395":1}}],["addxui",{"3":{"1324":2,"1396":1}}],["addxmodule",{"3":{"1316":1}}],["addxmldatacontractserializerformatters",{"2":{"964":1},"3":{"964":1}}],["addwasmformfactor",{"3":{"1324":3,"1416":1,"1417":5}}],["addwarmuptask",{"2":{"238":1},"3":{"235":1,"238":1,"1334":1,"1339":4,"1379":1,"1380":1,"1396":1,"1415":1,"1443":1}}],["addwarmupreadiness",{"2":{"1329":1,"1334":1},"3":{"235":1,"1329":1,"1334":2,"1339":9,"1396":1,"1415":1,"1443":2,"1577":1}}],["addjsonstream",{"3":{"1324":3,"1416":1}}],["addjsonoptions",{"3":{"1311":1}}],["addjwtbearer",{"3":{"0":1,"827":2}}],["addgrpcclient",{"3":{"1312":1}}],["addgrpcreflection",{"3":{"1312":1}}],["addgrpc",{"3":{"1312":1,"1441":1}}],["addgrpcservicedefaults",{"0":{"2018":1},"2":{"1656":1,"1663":1,"1927":1,"2018":1,"2028":1},"3":{"1312":7,"1380":1,"1396":2,"1415":1,"1441":1,"1656":1,"1663":1,"1927":1,"2018":1,"2028":1}}],["addgithubprovider",{"3":{"1311":1}}],["addgithub",{"3":{"1311":1}}],["addgoogleprovider",{"3":{"1311":1}}],["addgoogle",{"3":{"1311":1}}],["addgatewayroutepolicies",{"2":{"1338":1},"3":{"1338":1,"1339":5}}],["addgatewayratelimiting",{"2":{"1337":1,"1339":1,"1447":1,"2024":1},"3":{"0":1,"674":1,"827":1,"1337":1,"1339":7,"1447":1,"2024":1}}],["addgatewaydownstreamhealthchecks",{"2":{"99":1,"827":1,"829":1,"1339":1,"1670":1,"2024":1},"3":{"0":1,"99":1,"827":1,"829":1,"1337":1,"1339":9,"1447":1,"1467":1,"1670":1,"2024":1}}],["addquestionansweruniqueupsertindexes",{"3":{"1209":2}}],["addlinerequest",{"3":{"1727":1}}],["addline",{"3":{"1727":1}}],["addlinkasync",{"3":{"1380":2,"1389":1,"1395":10}}],["addlink",{"3":{"1203":4,"1207":8,"1357":1,"1359":9,"1395":1}}],["addlivepollsessionidstatusindex",{"3":{"1209":2}}],["addlivepolls",{"3":{"1209":2}}],["addlogging",{"3":{"954":1,"1436":2}}],["addlocalization",{"2":{"954":1},"3":{"265":1,"954":1,"1311":2,"1324":3,"1432":1,"1436":1}}],["addlocalizationinterceptor",{"2":{"265":1},"3":{"265":1,"1324":2}}],["addrazorcomponents",{"3":{"1436":1}}],["addrabbitmq",{"2":{"2008":1},"3":{"1339":2,"1441":1,"1444":1,"2008":1}}],["addratelimiter",{"3":{"1324":1,"1339":5,"1436":1}}],["addrangeasync",{"3":{"1286":5,"1359":1}}],["addrange",{"3":{"1237":2,"1259":4,"1271":2,"1309":1,"1380":2,"1395":1,"1396":3,"1415":1}}],["addrawsqlqueryexecutor",{"3":{"1175":1,"1286":1}}],["addrow",{"3":{"1286":1}}],["addroleadministrationui",{"3":{"1059":1,"1324":3,"1415":2}}],["addroomnameuniqueindex",{"3":{"1209":2}}],["addroomcommandvalidatortests",{"3":{"1199":1,"1207":1,"1359":2}}],["addroomcommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1359":20}}],["addroomcommand",{"3":{"1199":7,"1203":1,"1207":2,"1359":8,"1374":1,"1380":3,"1395":1}}],["addroomrequest",{"3":{"1199":3,"1203":1,"1207":1,"1359":1,"1374":1,"1380":5,"1382":1,"1395":2}}],["addroomhandlertests",{"3":{"1199":1,"1207":2,"1359":1}}],["addroomhandler",{"3":{"926":1,"1199":2,"1203":1,"1207":2,"1270":3,"1350":2,"1359":13,"1395":3}}],["addroom",{"3":{"926":1,"1203":3,"1207":8,"1270":1,"1343":1,"1350":3,"1359":11,"1395":1}}],["addrequesttransform",{"3":{"1339":2}}],["addref",{"3":{"1324":2}}],["addreverseproxy",{"2":{"837":1},"3":{"837":1,"838":1,"1338":1,"1447":1,"2024":1}}],["address~~",{"3":{"1631":1}}],["addresssavedmessage",{"3":{"1436":2}}],["addressstate",{"3":{"1237":1,"1286":1}}],["addressaddressline2",{"3":{"1286":1}}],["addressaddressline1",{"3":{"1286":1}}],["addressable",{"3":{"572":1,"1150":1,"1270":1,"1286":1,"1311":1,"1350":2,"1359":5,"1402":1}}],["addresspropertyprefix",{"3":{"1286":1}}],["addresscolumn",{"3":{"1286":1}}],["addresscountry",{"3":{"1237":1,"1286":1}}],["addresscity",{"3":{"657":1,"1237":1,"1286":1}}],["addresszipcode",{"3":{"1237":1,"1286":1}}],["addresstestdbcontext",{"3":{"1199":2,"1207":1}}],["addresstests",{"3":{"1199":1,"1207":2}}],["addressvalidationrules",{"2":{"1829":1},"3":{"1207":7,"1271":28,"1810":1,"1829":1}}],["addressvalidationrulestests",{"3":{"1199":1,"1207":3,"1271":8}}],["addressvalidator",{"2":{"1834":1},"3":{"1199":3,"1203":1,"1207":1,"1237":1,"1271":11,"1300":1,"1415":2,"1829":1,"1834":1}}],["addressline2maxlength",{"2":{"1271":1},"3":{"1237":1,"1271":1}}],["addressline2",{"3":{"1237":2,"1286":2,"1324":1,"1764":1}}],["addressline2rules",{"3":{"1199":3,"1203":1,"1207":1,"1271":5}}],["addressline1maxlength",{"2":{"1237":1,"1271":1},"3":{"1237":2,"1271":1}}],["addressline1rules",{"2":{"1829":1},"3":{"1199":3,"1203":1,"1207":1,"1237":2,"1271":7,"1829":1}}],["addressline1",{"3":{"657":2,"1234":1,"1237":5,"1271":1,"1286":3,"1324":1,"1764":2,"1767":1}}],["addressing",{"3":{"1152":1,"1286":2,"1324":2,"2019":1}}],["addressinvariantstests",{"3":{"1199":1,"1207":2}}],["addressinvariants",{"2":{"657":1,"658":1,"1237":3,"1271":4,"1829":1,"1833":1},"3":{"657":4,"658":1,"1198":1,"1199":13,"1203":1,"1207":2,"1229":2,"1230":1,"1234":4,"1237":16,"1271":24,"1286":2,"1767":1,"1810":2,"1829":1,"1833":1}}],["addressee",{"3":{"1309":1}}],["addressed",{"3":{"0":1,"751":1,"1264":1,"1270":3,"1285":1,"1300":1,"1323":1,"1350":1,"1359":5,"1395":1,"1402":1,"1415":1,"1810":1}}],["addresses",{"3":{"0":2,"100":1,"160":1,"175":1,"219":1,"280":1,"282":1,"284":1,"395":1,"403":1,"574":1,"658":1,"749":1,"828":1,"831":1,"832":1,"853":1,"888":1,"1017":1,"1091":1,"1093":1,"1124":1,"1237":2,"1270":2,"1286":1,"1300":4,"1309":7,"1311":4,"1312":2,"1322":2,"1323":3,"1324":1,"1334":1,"1339":1,"1359":7,"1369":1,"1395":4,"1406":2,"1407":1,"1408":2,"1415":6,"1417":3,"1436":5,"1441":1,"1447":1,"1455":1,"1467":1,"1473":1,"1474":1,"1639":1,"1641":1,"1890":2,"1935":1,"2002":1,"2196":1}}],["address",{"0":{"1408":1},"2":{"655":2,"660":1,"1908":1},"3":{"0":11,"59":1,"157":2,"159":1,"161":1,"175":1,"177":1,"179":1,"180":1,"225":1,"235":1,"280":2,"284":3,"348":1,"350":4,"352":2,"353":8,"363":1,"390":1,"403":1,"450":1,"655":2,"657":15,"658":1,"659":1,"660":2,"717":1,"733":1,"749":1,"827":5,"828":1,"829":1,"831":1,"833":1,"837":1,"838":2,"839":1,"841":1,"853":3,"854":5,"855":1,"856":2,"857":1,"881":1,"1018":1,"1020":1,"1059":1,"1124":2,"1126":1,"1184":2,"1188":1,"1198":1,"1199":15,"1203":1,"1207":2,"1212":3,"1229":2,"1230":1,"1234":7,"1237":39,"1241":1,"1247":2,"1270":1,"1271":43,"1286":13,"1289":3,"1292":1,"1293":1,"1294":6,"1295":3,"1300":73,"1309":4,"1311":11,"1312":1,"1322":1,"1323":31,"1324":56,"1327":1,"1330":1,"1337":1,"1339":19,"1344":1,"1347":1,"1350":6,"1355":4,"1359":28,"1364":1,"1365":2,"1368":1,"1369":3,"1380":1,"1389":2,"1391":2,"1394":1,"1395":16,"1396":3,"1402":1,"1405":1,"1406":6,"1407":4,"1408":6,"1413":1,"1415":60,"1416":3,"1417":35,"1424":1,"1427":1,"1432":2,"1433":1,"1436":40,"1438":5,"1441":5,"1444":1,"1447":3,"1467":3,"1476":1,"1492":1,"1496":1,"1498":1,"1526":1,"1541":1,"1577":1,"1596":1,"1629":1,"1630":4,"1631":1,"1639":8,"1641":34,"1653":1,"1662":2,"1666":1,"1667":1,"1719":1,"1727":1,"1742":1,"1746":2,"1747":3,"1750":2,"1764":6,"1765":8,"1767":8,"1768":2,"1769":1,"1790":1,"1810":3,"1825":1,"1828":1,"1829":2,"1833":1,"1834":1,"1896":1,"1908":1,"1911":1,"1973":4,"1977":5,"1978":1,"1979":3,"1998":1,"2006":1,"2023":1,"2024":1,"2076":1,"2132":1,"2140":1,"2144":1,"2196":1,"2198":1,"2235":1,"2237":1}}],["addredisdistributedcache",{"2":{"234":1,"245":1,"254":1,"257":1,"259":1,"260":1},"3":{"0":1,"234":1,"245":1,"254":1,"257":1,"259":1,"260":1,"1339":2,"1467":1}}],["addredisoutputcaching",{"2":{"241":1,"243":1,"247":1,"259":1,"260":1,"261":1,"390":1,"392":1,"393":1,"1332":1,"1668":1},"3":{"0":3,"241":1,"243":1,"247":1,"259":1,"260":2,"261":2,"390":1,"392":1,"393":1,"1332":2,"1339":6,"1442":1,"1526":1,"1600":1,"1668":1}}],["addredisclient",{"2":{"234":1,"257":1,"260":1},"3":{"0":2,"234":1,"245":1,"254":1,"257":1,"259":1,"260":1,"1301":2,"1323":1,"1339":2}}],["addrediscaching",{"2":{"237":1,"241":1,"245":2,"247":1,"260":1,"261":1,"1923":1},"3":{"0":3,"235":1,"237":1,"241":1,"245":2,"247":1,"259":1,"260":2,"261":2,"996":1,"1301":2,"1332":2,"1339":6,"1442":1,"1668":1,"1923":1}}],["addbunitpersistentcomponentstate",{"3":{"1436":2}}],["addbookmarkasync",{"3":{"1395":2}}],["addboundedblazorcircuits",{"2":{"831":1,"832":1,"1124":1},"3":{"831":1,"832":3,"1124":1,"1324":6,"1416":1}}],["addbrokermessaging",{"2":{"195":1,"196":1,"200":1,"201":1,"915":1,"917":1,"931":1,"934":1,"979":1,"1257":1,"1446":1,"1451":1,"1612":1,"2008":1,"2022":1},"3":{"195":1,"196":1,"200":1,"201":1,"640":3,"690":1,"915":1,"917":1,"931":1,"934":1,"979":1,"1257":1,"1259":10,"1311":1,"1316":1,"1317":1,"1320":2,"1323":12,"1326":1,"1339":3,"1380":1,"1441":3,"1444":2,"1446":1,"1451":1,"1467":1,"1534":1,"1591":2,"1612":1,"2008":1,"2022":1}}],["addbrowserdevicecapabilities",{"2":{"413":1,"1599":1,"2119":1},"3":{"0":1,"413":1,"1416":2,"1417":20,"1599":1,"2119":1}}],["addhours",{"3":{"1436":1}}],["addhostedservice",{"3":{"1100":1,"1259":1,"1286":1,"1317":1,"1323":5,"1396":4,"1442":1}}],["addhandler",{"3":{"1359":1}}],["addhybridcache",{"3":{"733":1,"1301":3,"1323":2,"1438":1}}],["addhttpforwarder",{"3":{"1324":2}}],["addhttpforwarderwithservicediscovery",{"2":{"837":1,"1467":1},"3":{"837":1,"1467":1}}],["addhttpmessagehandler",{"3":{"507":1,"1311":1,"1324":2}}],["addhttpclient",{"3":{"1359":1,"1369":2,"1416":2,"1653":1,"1702":1}}],["addhttpclientinstrumentation",{"2":{"402":1,"1443":1},"3":{"402":1,"1443":1}}],["addhttpcontextaccessor",{"2":{"303":1},"3":{"303":1,"1311":3,"1312":1,"1324":2,"1415":2,"1436":2}}],["addunsupportedrange",{"3":{"1310":2}}],["addunsupported",{"3":{"1310":2}}],["addunchecked",{"3":{"1237":2}}],["adduseremailconfirmation",{"3":{"1209":2}}],["adduserlockedon",{"3":{"1209":2}}],["adduseravatar",{"3":{"1209":2}}],["adduseradministrationui",{"3":{"1059":1,"1324":4,"1415":3}}],["adduserpreferences",{"3":{"1209":2,"1496":2,"1591":1}}],["addusersessionbookmarksessionidindex",{"3":{"1209":2}}],["adduserdataexportsection",{"2":{"846":1,"1317":1,"1672":1},"3":{"583":2,"846":1,"1317":1,"1323":5,"1415":5,"1672":1}}],["adduiratelimiting",{"3":{"832":2,"1124":1,"1324":4}}],["adduimodule",{"2":{"652":1},"3":{"649":1,"652":1,"1324":6,"1395":14,"1415":5,"1669":1}}],["adduishared",{"2":{"412":1,"414":1,"415":1,"558":1,"647":1,"681":1,"686":1,"2122":1,"2123":1},"3":{"0":2,"243":1,"245":1,"258":2,"265":4,"273":1,"412":1,"413":2,"414":1,"415":1,"556":1,"558":1,"647":1,"649":2,"681":1,"686":1,"881":1,"954":1,"979":2,"1212":1,"1324":33,"1395":2,"1416":11,"1417":19,"1434":2,"1436":13,"1653":2,"2085":1,"2118":1,"2119":2,"2122":1,"2123":1}}],["adddays",{"3":{"1369":1}}],["adddataprotection",{"3":{"1324":2,"1339":1}}],["adddatabasehealthchecks",{"2":{"1332":1},"3":{"1332":1,"1339":2,"1443":1}}],["adddatabase",{"2":{"2097":1},"3":{"633":1,"1684":1,"2097":1}}],["adddevicecapabilities",{"3":{"1417":2}}],["adddevicecapabilitydefaults",{"2":{"413":1,"681":1,"682":1,"1491":1,"2118":1,"2123":1},"3":{"413":2,"681":1,"682":1,"1324":2,"1417":35,"1491":1,"2118":1,"2123":1}}],["adddebug",{"3":{"1416":1}}],["adddefaulthealthchecks",{"2":{"1329":1,"1332":1},"3":{"1329":1,"1332":1,"1339":4,"1443":1,"1577":1}}],["adddefaultpolicy",{"2":{"774":1},"3":{"774":1}}],["adddsarcontrollers",{"3":{"725":1}}],["adddomainevent",{"0":{"2091":1},"2":{"20":1,"1233":1,"1251":1,"1701":1,"1838":1,"2020":1,"2091":1,"2098":1,"2111":1},"3":{"20":2,"1231":1,"1233":1,"1237":4,"1251":1,"1252":1,"1259":1,"1286":1,"1345":1,"1349":1,"1350":8,"1359":3,"1396":3,"1415":6,"1665":1,"1701":1,"1810":1,"1838":1,"2020":1,"2091":2,"2098":1,"2111":1}}],["addfeaturemanagement",{"2":{"300":1,"2138":1},"3":{"300":1,"303":1,"1311":3,"1436":3,"2136":1,"2138":1}}],["addforwardedjwtbearercore",{"3":{"827":1,"1300":1,"1311":2}}],["addforwardedjwtbearer",{"2":{"31":1,"34":1,"1586":1,"1611":1,"1613":1,"1619":1,"1667":1,"1897":1,"1898":1,"1904":1},"3":{"31":3,"34":1,"827":1,"1212":1,"1300":2,"1311":16,"1415":1,"1436":2,"1438":1,"1490":1,"1526":1,"1586":1,"1611":1,"1613":1,"1619":1,"1667":1,"1897":1,"1898":1,"1904":1}}],["addtokenpermissiongrants",{"3":{"1415":2}}],["addtokenifnotstopword",{"3":{"1359":1}}],["addtocalendarbutton",{"3":{"1324":1}}],["addtocalendarbuttontests",{"3":{"1199":1,"1207":2}}],["addtogroupasync",{"3":{"1309":1,"1323":1}}],["addtransforms",{"3":{"1339":3}}],["addtransient",{"3":{"1309":1,"1323":1,"1324":2}}],["addtrustedcallerheader",{"2":{"180":1},"3":{"0":1,"180":1,"827":1,"1324":5}}],["addtwofactorauthentication",{"2":{"674":1,"1059":1,"1316":1,"1322":1,"2202":1},"3":{"674":1,"1059":1,"1270":1,"1300":3,"1316":1,"1322":1,"1323":1,"2202":1}}],["addtypedserviceclient",{"2":{"67":1,"68":1,"70":1,"74":1},"3":{"67":1,"68":2,"70":1,"74":1,"1311":2,"1316":1,"1323":2,"1339":1,"1438":1}}],["addtypedgrpcclient",{"0":{"2019":1}}],["addtypedgrpcclient",{"2":{"53":1,"67":1,"68":1,"70":1,"100":1,"585":1,"1328":1,"1656":1,"1663":1,"2009":1,"2019":1,"2028":1,"2030":1,"2031":1,"2038":1},"3":{"53":1,"67":1,"68":2,"70":1,"100":1,"585":1,"1309":3,"1311":1,"1312":7,"1323":1,"1328":1,"1339":3,"1378":1,"1380":4,"1396":2,"1415":2,"1441":3,"1467":1,"1577":1,"1656":1,"1663":1,"2009":1,"2019":1,"2028":1,"2030":1,"2031":1,"2038":1}}],["addnotificationui",{"3":{"1324":13,"1396":2}}],["addnotificationuserexportclient",{"3":{"1309":4,"1415":1}}],["addnotificationlivechannelclient",{"2":{"1946":1},"3":{"1309":4,"1946":1}}],["addnotificationmodule",{"2":{"1308":1},"3":{"1308":1,"1309":5}}],["addnotificationapplicationservices",{"2":{"1304":1,"1309":1},"3":{"1304":1,"1309":8}}],["addnotificationinfrastructure",{"2":{"1316":1},"3":{"1286":3,"1309":2,"1316":1,"1323":1}}],["addnotificationcontrollers",{"2":{"434":1,"725":1,"1666":1},"3":{"434":1,"725":1,"1309":10,"1666":1}}],["addnpgsql",{"3":{"0":1,"1034":1,"1339":1}}],["addnativepushnotifications",{"2":{"434":1,"1316":1,"1936":1},"3":{"0":1,"434":1,"1309":2,"1316":1,"1323":9,"1936":1}}],["addidentityui",{"3":{"1415":8}}],["addidentitymodule",{"3":{"1415":7}}],["addidentityattendeeclient",{"2":{"1413":1},"3":{"1413":1,"1415":6}}],["additemhandler",{"3":{"1686":1,"1765":1,"1767":2}}],["additemrequest",{"3":{"1686":1}}],["additem",{"3":{"1684":1,"1686":4,"1728":1,"1765":1}}],["additemasync",{"3":{"1395":1}}],["additemcommand",{"2":{"91":1},"3":{"91":1,"1270":1,"1686":1}}],["additively",{"3":{"0":2,"186":1,"265":1,"1270":1,"1311":5,"1338":1,"1415":1,"1667":1,"1962":1}}],["additive",{"3":{"0":7,"39":1,"41":1,"78":1,"93":1,"95":1,"150":1,"158":1,"255":1,"305":1,"546":1,"564":2,"565":1,"568":1,"599":1,"600":1,"698":1,"699":1,"784":1,"827":1,"852":1,"854":1,"903":1,"905":1,"907":2,"920":2,"922":1,"926":1,"1024":1,"1026":1,"1027":1,"1034":1,"1075":1,"1084":1,"1229":1,"1234":1,"1237":1,"1247":1,"1249":1,"1259":2,"1266":1,"1270":7,"1286":5,"1290":1,"1300":11,"1309":2,"1311":10,"1323":2,"1324":2,"1338":1,"1339":6,"1350":1,"1369":1,"1395":1,"1396":3,"1409":1,"1415":7,"1417":1,"1420":1,"1427":1,"1436":3,"1442":1,"1455":1,"1467":3,"1477":1,"1547":1,"1611":1,"1631":1,"1632":1,"1639":1,"1667":1,"1720":1,"1724":1,"1727":1,"1734":1,"1889":1,"1892":1,"1965":1,"2063":1,"2123":1,"2198":1}}],["additions",{"3":{"0":2,"99":1,"111":1,"135":1,"141":1,"344":1,"413":1,"718":1,"906":1,"1093":1,"1116":1,"1132":1,"1262":1,"1286":1,"1324":1,"1350":1,"1369":1,"1415":1,"1422":1,"1436":5,"1443":1,"1489":1,"1496":5,"1631":1,"1638":1,"1685":1,"1779":2,"2174":1,"2210":1}}],["additionalhandlers",{"3":{"1324":1}}],["additionaltoken",{"3":{"1324":4}}],["additionaltokenissuingpaths",{"2":{"1184":1},"3":{"1184":1,"1324":2}}],["additionalclaims",{"2":{"1288":1},"3":{"1288":1,"1300":4,"1488":1}}],["additionalrequestpatterns",{"2":{"1091":1,"1424":1},"3":{"1091":1,"1093":1,"1424":1,"1427":3}}],["additionalproperties",{"3":{"1018":1,"1366":1,"1369":1,"1427":9}}],["additionalportmappings",{"2":{"92":1,"95":1,"96":1,"102":1},"3":{"92":1,"95":2,"96":1,"102":1,"790":1,"1467":1}}],["additionalfiles",{"3":{"135":1}}],["additionalfilters",{"3":{"1324":3}}],["additionalfilter",{"2":{"889":1},"3":{"0":1,"889":2,"1286":6,"1396":2}}],["additionally",{"3":{"18":1,"126":1,"132":1,"135":1,"140":1,"207":1,"212":1,"214":1,"235":1,"383":1,"556":1,"562":1,"618":1,"626":1,"633":1,"666":1,"691":1,"692":1,"698":1,"725":1,"827":1,"832":1,"881":1,"1229":1,"1256":1,"1259":2,"1263":1,"1270":2,"1286":3,"1301":1,"1309":3,"1311":3,"1323":3,"1324":2,"1332":1,"1334":1,"1339":3,"1350":4,"1352":1,"1353":1,"1359":2,"1369":1,"1373":1,"1380":4,"1396":3,"1398":1,"1402":5,"1415":1,"1416":1,"1417":3,"1427":3,"1428":1,"1431":1,"1436":8,"1443":1,"1445":1,"1455":2,"1488":1,"1591":1,"1627":1,"1635":1,"1639":1,"1640":1,"1641":2,"1643":1,"1757":1,"1821":1,"1969":1,"2000":1}}],["additionalassemblies=",{"3":{"1324":1}}],["additionalassemblies",{"2":{"649":1,"651":1,"2077":1},"3":{"0":1,"649":1,"651":2,"905":1,"1212":1,"1286":2,"1324":1,"1416":1,"2077":1}}],["additional",{"2":{"1459":1},"3":{"0":1,"92":1,"395":1,"401":1,"1281":1,"1286":4,"1311":1,"1323":1,"1324":1,"1339":1,"1359":1,"1395":2,"1415":1,"1417":1,"1436":1,"1459":1,"1630":1,"1641":2,"1776":1}}],["addition",{"3":{"0":1,"71":1,"135":1,"145":1,"197":1,"265":1,"564":1,"566":1,"649":1,"657":1,"686":1,"903":1,"915":1,"1034":1,"1237":1,"1286":2,"1300":2,"1311":2,"1350":1,"1380":1,"1395":4,"1415":4,"1436":3,"1438":2,"1455":1,"1459":1,"1585":1,"1638":1,"1639":1,"1641":2,"1654":1,"1779":1}}],["addinitscript",{"3":{"1436":1}}],["addinitscriptasync",{"3":{"1436":1}}],["addinterceptor",{"3":{"1312":1}}],["addinterceptors",{"2":{"715":1},"3":{"715":1}}],["addinternalcommands",{"2":{"1044":1,"2192":1},"3":{"674":1,"1044":1,"1209":8,"1316":1,"1318":1,"1323":2,"2192":1}}],["addinteractiveserverrendermode",{"2":{"556":1},"3":{"556":1,"1436":1}}],["addinteractiveservercomponents",{"2":{"556":1,"831":1,"832":1,"1124":1},"3":{"556":1,"831":1,"832":1,"1124":1,"1324":2,"1416":1,"1436":1}}],["addinteractivewebassemblyrendermode",{"2":{"556":1},"3":{"556":1}}],["addinteractivewebassemblycomponents",{"2":{"556":1},"3":{"556":1}}],["addinclude",{"3":{"549":1,"1240":1,"1247":1,"1436":1}}],["addinboxmessages",{"2":{"200":1},"3":{"195":1,"200":1,"1209":8,"1591":1}}],["adding",{"0":{"1722":1},"2":{"983":1},"3":{"0":2,"7":1,"78":2,"88":1,"96":1,"100":1,"135":2,"158":1,"166":1,"168":1,"187":1,"255":1,"265":3,"293":1,"318":1,"361":1,"365":1,"372":1,"390":1,"391":2,"397":3,"415":1,"443":1,"448":1,"465":1,"470":1,"490":1,"552":1,"564":2,"574":1,"585":1,"642":1,"648":1,"698":1,"707":1,"708":1,"725":2,"740":1,"741":2,"743":1,"759":1,"781":1,"783":2,"784":1,"819":1,"827":2,"838":2,"839":1,"855":1,"862":1,"913":1,"921":1,"923":1,"948":1,"983":1,"986":1,"1011":1,"1012":1,"1016":1,"1048":1,"1052":1,"1068":1,"1109":1,"1144":1,"1147":1,"1174":1,"1179":1,"1212":3,"1229":2,"1237":5,"1243":1,"1247":5,"1249":1,"1259":6,"1262":1,"1270":3,"1271":1,"1278":1,"1280":1,"1286":18,"1300":7,"1309":2,"1310":3,"1311":18,"1312":1,"1323":5,"1324":13,"1339":8,"1343":1,"1344":1,"1350":7,"1352":2,"1359":42,"1362":1,"1369":6,"1380":6,"1386":1,"1389":1,"1395":16,"1396":7,"1401":1,"1402":1,"1415":14,"1416":1,"1417":2,"1427":1,"1436":18,"1438":2,"1441":1,"1447":1,"1453":1,"1467":2,"1473":2,"1488":1,"1489":2,"1496":3,"1526":2,"1542":2,"1576":1,"1638":1,"1643":1,"1646":1,"1653":2,"1658":1,"1677":1,"1686":1,"1689":2,"1692":1,"1698":1,"1720":1,"1722":1,"1727":2,"1729":2,"1755":1,"1767":3,"1769":1,"1776":1,"1781":1,"1826":1,"1835":1,"1841":1,"1858":1,"1865":1,"1868":1,"1889":1,"1911":1,"1913":1,"1932":1,"1945":1,"1958":2,"1961":1,"1972":1,"2028":1,"2030":1,"2077":1,"2078":1,"2079":1,"2080":1,"2083":2,"2085":1,"2100":2,"2104":1,"2106":1,"2108":1,"2131":1,"2142":1,"2147":1,"2172":1,"2194":1,"2195":1,"2220":1,"2230":2}}],["addinfrastructurehealthchecks",{"2":{"166":1,"1034":1,"1332":1,"1442":1,"1443":1,"1670":1,"1727":1},"3":{"166":1,"1034":1,"1332":1,"1339":6,"1438":1,"1442":1,"1443":1,"1670":1,"1727":1}}],["addinfrastructure",{"2":{"157":1,"230":1,"257":1,"260":1,"344":1,"672":1,"931":1,"949":1,"979":1,"996":1,"1286":1,"1319":1,"1368":1,"1824":1,"1842":1,"1851":1,"1909":1,"1935":1,"1938":1,"2095":1},"3":{"0":1,"117":2,"157":1,"230":1,"243":1,"257":1,"259":2,"260":1,"281":1,"310":1,"344":1,"434":1,"672":1,"674":3,"733":1,"931":2,"946":1,"949":1,"979":2,"996":2,"1059":1,"1212":1,"1259":7,"1270":1,"1279":1,"1286":14,"1300":2,"1301":3,"1309":4,"1311":1,"1316":1,"1317":2,"1318":3,"1319":1,"1320":2,"1323":20,"1324":2,"1368":1,"1369":1,"1395":1,"1396":2,"1406":1,"1415":1,"1436":2,"1438":1,"1492":1,"1824":1,"1842":1,"1851":1,"1909":1,"1935":1,"1938":1,"2000":1,"2095":1,"2098":1}}],["addorupdate",{"3":{"1309":1}}],["addorderitemrequest",{"3":{"1686":1}}],["addordersmodule",{"3":{"1651":1,"1653":1}}],["addorderstatusandcreatedonindexes",{"3":{"564":1}}],["addorderlinecount",{"3":{"1600":1}}],["addorderlinecommand",{"3":{"1199":3,"1207":1}}],["addorderlinelistprice",{"3":{"1026":1}}],["addorderby",{"3":{"549":1,"1240":1,"1247":2,"1436":1}}],["addoutcome",{"3":{"1396":2}}],["addoutboxorigin",{"3":{"1209":8}}],["addoutboxinboxretentionindexes",{"3":{"564":2,"1209":8,"1591":1}}],["addoutputcache",{"2":{"245":1,"390":1,"393":1,"1923":1},"3":{"243":3,"245":1,"390":2,"393":1,"1301":1,"1311":2,"1339":1,"1923":1}}],["addoutputcacheevictionhandler",{"2":{"255":1,"258":1,"390":1,"391":1,"393":1,"1379":1},"3":{"0":1,"255":1,"258":1,"390":1,"391":1,"393":1,"1259":2,"1311":5,"1379":1,"1526":1}}],["addoption",{"3":{"1402":5}}],["addoptions",{"3":{"0":1,"674":3,"676":1,"707":1,"733":1,"827":1,"832":1,"1212":1,"1286":1,"1300":2,"1301":1,"1309":1,"1320":1,"1323":4,"1324":3,"1339":3,"1396":2,"2013":1}}],["addoperationtransformer",{"3":{"1311":1}}],["addopenaiprovider",{"2":{"1420":1},"3":{"1091":1,"1420":1,"1427":5}}],["addopenapi",{"2":{"454":1,"1929":1},"3":{"448":1,"454":5,"1050":1,"1311":9,"1438":2,"1929":1}}],["addopentelemetryexporters",{"2":{"397":1,"1333":1,"2156":1,"2157":1},"3":{"397":1,"1333":1,"1339":4,"1443":1,"1467":1,"2156":1,"2157":1}}],["addmaildev",{"3":{"1326":2,"1339":1,"1441":1,"1444":1,"1670":1}}],["addmasstransit",{"3":{"1323":2}}],["addmauiformfactor",{"3":{"1324":1,"1416":3,"1417":5}}],["addmauiblazorwebview",{"3":{"1212":1,"1416":1}}],["addmauipushdevicetokenprovider",{"2":{"413":1,"434":1,"436":1,"1669":1},"3":{"0":1,"413":1,"434":1,"436":1,"1416":3,"1417":5,"1669":1}}],["addmauidevicecapabilities",{"2":{"411":1,"413":1,"415":1,"1417":1,"2118":1,"2119":1,"2122":1},"3":{"0":1,"411":2,"413":3,"415":1,"1417":30,"2118":1,"2119":1,"2122":1}}],["addminiprofiler",{"3":{"1311":1}}],["addminiprofilerifenabled",{"2":{"1666":1},"3":{"1311":1,"1666":1}}],["addmultitenancytests",{"3":{"1199":1,"1207":2,"1286":2,"1438":2}}],["addmultitenancy",{"2":{"696":1,"979":1,"1232":1,"1851":1},"3":{"674":1,"696":1,"698":1,"979":1,"1232":1,"1237":2,"1270":2,"1286":6,"1311":1,"1316":1,"1317":1,"1323":4,"1526":1,"1851":1}}],["addmudservices",{"2":{"954":1},"3":{"265":1,"954":1,"1324":5,"1416":1,"1436":6}}],["addmetrics",{"3":{"1421":1,"1427":1}}],["addmeter",{"2":{"404":1,"407":1,"408":1},"3":{"258":1,"261":1,"404":1,"407":1,"408":1,"1247":1,"1270":1,"1339":2,"1442":1,"2159":1}}],["addmemorycache",{"3":{"1301":2}}],["addmessagebroker",{"2":{"644":1,"2008":1},"3":{"640":1,"644":1,"1326":2,"1339":3,"1441":1,"1444":1,"1670":1,"2008":1}}],["addmvc",{"3":{"448":1}}],["addmoduleordersinfrastructure",{"2":{"1651":1},"3":{"1651":1}}],["addmodule",{"3":{"1415":1}}],["addmoduleidentityinfrastructure",{"3":{"1415":4}}],["addmoduleidentityapplication",{"3":{"1415":6}}],["addmoduleidentityapi",{"3":{"1406":1,"1415":6}}],["addmoduleengagementinfrastructure",{"3":{"1396":6}}],["addmoduleengagementapplication",{"3":{"1396":4}}],["addmoduleengagementapi",{"3":{"318":1,"1380":1,"1396":12,"1995":1}}],["addmoduleconferenceinfrastructure",{"2":{"1368":1},"3":{"1368":1,"1369":2,"1380":2}}],["addmoduleconferenceapplication",{"3":{"1359":2,"1380":2}}],["addmoduleconferenceapi",{"2":{"1348":1,"1377":1},"3":{"1348":1,"1377":1,"1380":3}}],["addmodulenotificationapplication",{"3":{"1309":4,"1438":1}}],["addmodulesalesinfrastructure",{"2":{"790":1},"3":{"790":1}}],["addmodulehealthchecks",{"2":{"1700":1},"3":{"583":1,"1311":6,"1666":1,"1700":1}}],["addmodulehost",{"2":{"59":1,"63":1,"672":1,"675":1,"984":1,"1881":1},"3":{"0":1,"59":1,"63":1,"583":1,"672":1,"674":1,"675":1,"980":1,"984":1,"1311":8,"1315":2,"1323":4,"1663":1,"1670":1,"1881":1}}],["addmmcachatclient",{"2":{"1094":1,"1368":1,"1421":1,"1424":1,"1427":1,"2172":1,"2175":1},"3":{"1018":2,"1091":1,"1094":1,"1368":2,"1369":4,"1421":1,"1424":1,"1427":18,"1492":2,"1526":1,"2172":1,"2175":1}}],["addmmcaapplicationpipeline",{"2":{"115":1,"117":1,"122":1,"126":1,"127":1,"128":1,"1262":1,"1316":1,"1662":1,"1824":1,"1883":1},"3":{"0":3,"115":1,"117":1,"122":2,"126":1,"127":1,"128":1,"1262":2,"1311":1,"1312":1,"1316":2,"1323":11,"1396":1,"1415":2,"1436":2,"1438":1,"1662":1,"1824":2,"1883":1}}],["addmmcagatewaytests",{"3":{"1199":1,"1207":2,"1339":9,"1438":2}}],["addmmcagateway",{"2":{"825":1,"829":1,"1509":1},"3":{"0":3,"674":1,"825":1,"827":1,"829":1,"1338":2,"1339":11,"1447":4,"1509":1,"1663":1,"2024":1}}],["addenda",{"0":{"1639":1}}],["addengagementui",{"3":{"1396":6,"1416":1}}],["addengagementuserexportclient",{"3":{"1396":4,"1415":1}}],["addengagementmodule",{"3":{"1396":11}}],["addengagementbookmarkcountclient",{"2":{"1378":1},"3":{"1323":1,"1359":1,"1378":1,"1396":5}}],["addentityframework",{"3":{"1311":1}}],["addentityconfigurationassembly",{"2":{"1316":1},"3":{"1286":1,"1316":1,"1323":1}}],["addentitycrudtests",{"3":{"1199":1,"1207":1}}],["addentitycrud",{"2":{"328":1,"920":1,"924":1,"925":1,"1352":1,"1354":1},"3":{"0":3,"122":1,"328":1,"572":1,"920":2,"922":2,"924":1,"925":1,"926":2,"1262":1,"1265":2,"1270":8,"1316":1,"1323":4,"1350":3,"1352":1,"1354":1,"1359":21,"1666":1,"1883":1}}],["addentityupdate",{"2":{"122":1,"1883":1},"3":{"0":1,"122":1,"926":2,"1262":1,"1265":1,"1270":2,"1316":1,"1323":4,"1359":5,"1666":1,"1883":1}}],["addentityupdateverb",{"2":{"122":1,"1265":1},"3":{"0":1,"122":1,"926":1,"1262":1,"1265":1,"1270":2,"1316":1,"1323":4,"1666":1}}],["adder",{"3":{"1286":1}}],["adderrorlocalization",{"3":{"1311":8,"1415":1}}],["adderrors",{"3":{"1229":1}}],["adderrorresources",{"2":{"636":1,"979":1,"1650":1,"1653":1,"1684":1,"1701":1,"1728":1,"2097":1},"3":{"979":1,"1311":9,"1379":1,"1380":1,"1396":3,"1415":2,"1591":1,"1650":1,"1653":1,"1666":1,"1684":1,"1701":1,"1728":1,"2097":1}}],["adderrorresources<",{"3":{"633":1,"636":1}}],["addexternallogincookie",{"3":{"1311":1}}],["addexternalauthproviders",{"2":{"350":1,"1667":1,"1975":1,"1978":1,"1979":1},"3":{"0":1,"350":2,"1311":1,"1415":1,"1667":1,"1975":1,"1978":1,"1979":1}}],["addexplicitmigrationsassemblies",{"3":{"1286":1}}],["addeventlistener",{"3":{"1417":1}}],["addeventticketingurl",{"3":{"1209":2}}],["addeventsponsorshippacketurl",{"3":{"1209":2}}],["addeventspeaker",{"3":{"1203":3,"1207":8,"1270":1,"1350":3,"1359":9,"1369":1}}],["addeventspeakerhandlertests",{"3":{"1199":1,"1207":2,"1359":1}}],["addeventspeakerhandler",{"3":{"1199":2,"1203":1,"1207":2,"1270":2,"1359":11}}],["addeventspeakercommandvalidatortests",{"3":{"1199":1,"1207":1,"1359":1}}],["addeventspeakercommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1359":5}}],["addeventspeakercommand",{"3":{"1199":7,"1203":1,"1207":2,"1359":8,"1380":4}}],["addeventspeakerrequest",{"3":{"1199":3,"1203":1,"1207":1,"1359":1,"1374":1,"1380":5}}],["addeventorganizercontactemail",{"3":{"1209":2}}],["addeventquestionmoderationdefault",{"3":{"1209":2}}],["addeventquestionanswer",{"3":{"1203":3,"1207":8,"1350":4,"1352":1,"1359":14,"1526":1}}],["addeventquestionanswercommandvalidatortests",{"3":{"1199":1,"1207":1,"1359":1}}],["addeventquestionanswercommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1359":11}}],["addeventquestionanswercommand",{"3":{"1199":7,"1203":1,"1207":2,"1359":12,"1380":4}}],["addeventquestionanswerrequest",{"2":{"1374":1},"3":{"1199":3,"1203":1,"1207":1,"1359":1,"1374":1,"1380":6}}],["addeventquestionanswerhandlertests",{"3":{"1199":1,"1207":2,"1359":1}}],["addeventquestionanswerhandler",{"3":{"690":1,"1199":2,"1203":1,"1207":2,"1350":7,"1352":2,"1359":20}}],["addeventupcaster",{"2":{"846":1,"848":1,"850":1,"1249":1,"1269":1,"1891":1},"3":{"0":1,"80":1,"846":1,"848":1,"850":1,"1247":3,"1249":1,"1259":3,"1269":1,"1270":3,"1316":1,"1323":3,"1891":1}}],["addemailconfirmation",{"2":{"2202":1},"3":{"674":1,"1059":1,"1300":2,"1316":1,"1322":1,"1323":1,"2202":1}}],["addedroomkey",{"3":{"1359":1}}],["added",{"2":{"784":1,"1755":1},"3":{"0":7,"3":1,"11":1,"21":1,"22":1,"27":1,"45":1,"47":1,"57":1,"93":2,"94":1,"109":1,"111":1,"121":1,"135":2,"138":1,"139":1,"140":1,"145":2,"149":1,"177":1,"184":1,"195":1,"201":3,"216":2,"219":1,"228":1,"248":1,"252":1,"254":1,"256":4,"258":1,"259":1,"317":1,"323":1,"342":1,"348":2,"364":1,"374":1,"383":1,"395":1,"397":3,"401":1,"408":1,"448":1,"450":1,"468":4,"475":1,"477":1,"489":1,"511":1,"526":1,"550":1,"558":1,"564":6,"565":1,"566":2,"575":1,"601":1,"611":1,"616":3,"618":2,"624":3,"626":1,"629":1,"633":1,"640":1,"655":1,"656":1,"665":1,"676":1,"690":3,"697":1,"698":3,"703":1,"715":3,"725":2,"741":1,"744":1,"745":1,"750":1,"759":1,"766":1,"780":2,"782":7,"784":2,"805":1,"812":1,"819":1,"825":1,"826":1,"827":1,"829":3,"838":2,"862":1,"869":1,"871":1,"889":1,"915":1,"921":1,"960":1,"988":3,"1004":2,"1018":2,"1026":1,"1034":2,"1051":1,"1062":1,"1108":1,"1110":1,"1117":1,"1185":1,"1195":1,"1212":2,"1222":1,"1229":3,"1233":1,"1237":4,"1247":2,"1249":1,"1251":1,"1259":8,"1270":13,"1271":2,"1274":2,"1277":1,"1278":1,"1280":1,"1281":3,"1283":1,"1286":34,"1300":6,"1301":2,"1309":5,"1311":13,"1315":1,"1316":1,"1323":15,"1324":14,"1326":1,"1332":1,"1333":1,"1335":2,"1339":18,"1343":2,"1345":1,"1349":1,"1350":50,"1359":24,"1369":5,"1380":1,"1386":1,"1395":15,"1396":33,"1402":12,"1409":1,"1415":6,"1416":2,"1417":1,"1425":1,"1436":26,"1438":9,"1441":1,"1442":2,"1443":3,"1447":1,"1455":8,"1465":2,"1467":3,"1476":2,"1478":1,"1486":1,"1489":1,"1492":2,"1496":92,"1497":4,"1498":2,"1500":1,"1505":1,"1519":1,"1534":1,"1572":1,"1591":1,"1597":1,"1611":2,"1631":8,"1632":2,"1633":1,"1634":11,"1641":4,"1651":1,"1652":1,"1653":1,"1656":1,"1661":1,"1666":1,"1680":1,"1684":3,"1685":1,"1686":2,"1693":1,"1697":1,"1701":1,"1703":1,"1727":1,"1728":1,"1729":1,"1731":1,"1742":1,"1747":1,"1749":2,"1755":1,"1764":1,"1767":1,"1769":13,"1776":2,"1826":1,"1840":1,"1851":1,"1856":1,"1858":1,"1876":1,"1897":1,"1915":1,"1932":1,"1933":1,"2001":1,"2013":1,"2031":1,"2063":1,"2081":2,"2085":1,"2092":1,"2096":1,"2097":1,"2131":1,"2132":1,"2156":1,"2158":1,"2203":2,"2232":1}}],["addchatclient",{"3":{"1421":1,"1427":2}}],["addcheckinsandattendeebadges",{"3":{"1209":2}}],["addchildentityhandlerbase",{"2":{"1260":1,"1265":1},"3":{"1199":7,"1203":1,"1207":1,"1260":1,"1265":1,"1270":2,"1359":8}}],["addcategoryitemasync",{"3":{"1395":1}}],["addcategoryitem",{"3":{"1203":3,"1207":6,"1270":1,"1350":4,"1359":13}}],["addcategoryitemhandlertests",{"3":{"1199":1,"1207":2}}],["addcategoryitemhandler",{"3":{"1199":2,"1203":1,"1207":2,"1270":5,"1350":1,"1359":8}}],["addcategoryitemcommandvalidatortests",{"3":{"1199":1,"1207":1}}],["addcategoryitemcommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1359":13}}],["addcategoryitemcommand",{"3":{"1199":7,"1203":1,"1207":2,"1359":13,"1380":4}}],["addcategoryitemrequest",{"3":{"1199":3,"1203":1,"1207":1,"1359":1,"1374":1,"1380":7}}],["addcaching",{"2":{"249":1,"250":1,"251":1,"252":1,"253":1,"260":1,"672":1,"677":1,"999":1,"1909":1},"3":{"0":3,"157":1,"243":1,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"256":1,"257":1,"259":1,"260":1,"672":1,"674":3,"677":1,"733":1,"996":1,"999":1,"1270":3,"1301":9,"1316":1,"1320":1,"1323":7,"1438":1,"1577":1,"1668":1,"1909":1,"1916":1}}],["addcors",{"3":{"1339":1}}],["addcompensatingindex",{"3":{"1286":1}}],["addcommentrequest",{"3":{"1651":1}}],["addcomment",{"3":{"1651":1,"1701":1}}],["addcommandrequestvalidator",{"2":{"926":1,"1263":1},"3":{"0":1,"926":1,"1263":1,"1270":1,"1271":2,"1323":4,"1359":1}}],["addcommonui",{"3":{"1577":1}}],["addcommonuifacades",{"2":{"647":1,"649":1,"652":1,"954":1,"955":1,"957":1},"3":{"0":2,"647":1,"649":1,"652":1,"954":1,"955":1,"957":1,"1324":10,"1436":3}}],["addcommonmauipubliclinkbuilder",{"3":{"1324":2,"1395":1,"1416":4,"1417":3}}],["addcommonmauitokenstorage",{"2":{"412":1,"507":1,"511":1,"513":1},"3":{"0":1,"412":1,"507":2,"511":1,"513":1,"1416":3,"1417":5}}],["addcommonwebformfactor",{"3":{"1324":3,"1416":1,"1417":2}}],["addcommonresponsecompression",{"3":{"1311":3}}],["addcommonratelimiting",{"2":{"175":1,"826":1,"1599":1,"2002":1,"2024":1},"3":{"175":3,"826":1,"1311":11,"1339":1,"1438":1,"1535":1,"1599":1,"1666":1,"2000":1,"2002":1,"2024":1}}],["addcommongatewaycors",{"2":{"774":1,"1336":1,"1670":1},"3":{"774":2,"1336":1,"1339":3,"1443":1,"1670":1}}],["addcommonkeyvaultconfiguration",{"2":{"599":1,"603":2,"665":1,"1442":1,"1450":1,"1575":1,"1586":1,"1670":1},"3":{"599":1,"603":2,"665":1,"1335":1,"1339":3,"1427":2,"1442":1,"1450":1,"1467":1,"1496":1,"1575":1,"1577":1,"1586":1,"1670":1}}],["addcommonopenapihostregistrationtests",{"2":{"454":1},"3":{"454":1,"1199":1,"1207":4,"1438":2}}],["addcommonopenapi",{"2":{"446":1,"453":1,"454":2,"1929":1},"3":{"446":2,"448":2,"453":1,"454":3,"1050":2,"1311":10,"1577":3,"1666":1,"1929":2}}],["addcommonexceptionhandlers",{"2":{"109":1,"112":1},"3":{"109":1,"112":1,"1311":9}}],["addcommonauthentication",{"2":{"31":1,"350":1,"1613":1,"1619":1,"1975":1},"3":{"31":2,"350":1,"674":1,"1300":1,"1311":8,"1320":1,"1488":1,"1526":1,"1613":1,"1619":1,"1975":1}}],["addcommonapiversioning",{"2":{"446":1,"448":1,"449":1,"450":1,"453":1,"1929":1,"2131":1,"2132":1,"2133":1},"3":{"0":1,"446":1,"448":3,"449":1,"450":1,"453":1,"1212":1,"1311":8,"1380":1,"1666":1,"1929":1,"2131":2,"2132":1,"2133":1}}],["addcommonsameoriginapiproxy",{"2":{"1182":1,"1184":1,"1186":1},"3":{"0":1,"1182":1,"1184":1,"1186":1,"1212":1,"1324":7}}],["addcommonservertokenstorage",{"2":{"507":1},"3":{"507":1,"1324":3,"1416":1,"1417":1}}],["addcommonserilog",{"2":{"395":1,"401":1},"3":{"0":1,"395":4,"401":2,"1333":2,"1339":3,"1442":1,"1670":1}}],["addcommonsecurityheaders",{"2":{"214":1,"216":1,"220":1,"1336":1,"2011":1,"2014":1,"2149":1,"2150":1,"2151":1,"2152":1},"3":{"0":1,"214":4,"216":2,"220":2,"1324":3,"1336":2,"1339":10,"1416":1,"1436":1,"1443":1,"1577":1,"2011":1,"2014":1,"2149":1,"2150":2,"2151":2,"2152":2}}],["addcommoncors",{"2":{"775":1,"776":1,"777":1,"1336":1},"3":{"0":1,"774":2,"775":1,"776":1,"777":1,"1212":1,"1311":2,"1336":1,"1339":1}}],["addcommondataprotection",{"2":{"665":1,"667":1,"668":1,"1335":1,"1336":1,"1442":1,"1450":1,"1670":1,"1970":1},"3":{"0":1,"665":3,"667":1,"668":1,"1212":1,"1335":1,"1336":1,"1339":3,"1442":1,"1450":1,"1467":1,"1496":1,"1670":1,"1970":2}}],["addcommonhybridcachetests",{"3":{"1199":1,"1207":2}}],["addcommonhybridcache",{"2":{"241":1,"245":1,"253":1,"259":1,"260":1,"261":1,"731":1,"735":1,"736":1,"1916":1},"3":{"0":2,"241":1,"243":1,"245":1,"253":1,"259":1,"260":3,"261":1,"731":1,"733":2,"735":1,"736":1,"1212":1,"1301":5,"1316":1,"1323":5,"1442":1,"1916":2}}],["addcommonhybridcachewhenredisconfigured",{"2":{"0":1,"241":1,"243":1,"245":1,"261":1,"279":1,"281":1,"283":1,"285":1,"733":1,"736":1,"1301":1},"3":{"0":1,"241":1,"243":1,"245":1,"261":1,"279":1,"283":1,"285":1,"733":1,"736":1,"1301":2}}],["addcommonblazorcsp",{"2":{"214":1,"216":1,"219":1,"220":1,"1336":1,"1525":1,"2150":1,"2151":1},"3":{"0":1,"214":1,"216":2,"219":1,"220":2,"1324":7,"1336":1,"1339":2,"1416":1,"1525":1,"1526":1,"2150":2,"2151":1}}],["addcontainer",{"3":{"1339":2}}],["addcontrollers",{"3":{"1309":3,"1311":1}}],["addcontentpolicyguardrail",{"2":{"1089":1,"1091":1,"1094":1,"1424":1},"3":{"1089":1,"1091":1,"1094":1,"1369":1,"1424":1,"1427":5,"1526":1,"1577":1}}],["addconfigfilter",{"3":{"1339":4}}],["addconfigurationassembly",{"3":{"1286":1,"1359":1,"1396":1,"1415":1}}],["addconferenceui",{"3":{"1395":4}}],["addconferenceeventlivevalidationclient",{"2":{"1378":1},"3":{"1350":2,"1378":1,"1380":2}}],["addconferencemodule",{"2":{"1314":1,"1880":1},"3":{"1314":1,"1377":1,"1380":5,"1880":1}}],["addconferencesessionvalidationclient",{"2":{"1312":1,"1378":1},"3":{"1312":2,"1350":1,"1378":1,"1380":2}}],["addconferenceaiguardrails",{"2":{"1018":1,"1021":1,"1094":1,"1095":1,"1368":1,"1435":1},"3":{"1018":3,"1021":1,"1094":3,"1095":1,"1368":1,"1369":5,"1427":2,"1435":1,"1496":1,"1526":2}}],["addconsumer",{"3":{"1259":2}}],["addclientauthsessioncookiesync",{"2":{"507":1},"3":{"507":1,"1324":4}}],["addpolicy",{"3":{"1311":1,"1339":3}}],["addpooleddbcontextfactory",{"3":{"1286":1}}],["addpoints",{"3":{"1209":2}}],["addpermissiongrants",{"3":{"1209":2}}],["addpermissions",{"2":{"188":1,"190":1,"349":1,"710":1,"1377":1,"1963":1,"1964":1,"1965":1},"3":{"0":1,"186":4,"188":1,"190":1,"349":1,"707":1,"710":1,"1059":1,"1270":1,"1297":1,"1300":8,"1304":1,"1309":3,"1311":1,"1323":1,"1377":1,"1380":2,"1396":4,"1415":4,"1496":1,"1526":1,"1653":2,"1667":1,"1962":1,"1963":2,"1964":1,"1965":1}}],["addpartners",{"3":{"1209":2}}],["addpiiredactionguardrail",{"2":{"1091":1,"1094":1,"1421":1,"1424":1},"3":{"1091":1,"1094":1,"1369":1,"1421":1,"1424":1,"1427":5,"1526":1}}],["addproductimage",{"3":{"1769":1}}],["addproductvariantdiscounts",{"3":{"1026":1}}],["addproject",{"3":{"1489":1,"1727":1}}],["addprocessor",{"3":{"1333":1,"1339":4}}],["addproblemdetails",{"3":{"109":1,"1311":1}}],["addpushnotificationdedupkey",{"3":{"1209":2}}],["addpushnotificationscopekey",{"3":{"1209":2}}],["addpushnotifications",{"2":{"227":1,"231":1,"380":1,"1935":1,"1944":1,"1949":1},"3":{"0":2,"225":3,"227":1,"231":1,"380":1,"674":1,"1307":1,"1309":11,"1316":1,"1320":2,"1323":4,"1467":1,"1935":2,"1944":2,"1949":1}}],["addpublicendpointpolicy",{"2":{"243":1,"259":1,"390":1,"1668":1,"1922":1},"3":{"0":2,"243":3,"259":1,"388":2,"390":1,"1301":1,"1311":2,"1379":1,"1668":1,"1922":2}}],["addazurekeyvault",{"3":{"1339":2}}],["addazureblobfilestorage",{"2":{"440":1,"1116":1,"1670":1,"2126":1,"2127":1,"2128":1},"3":{"0":1,"440":1,"1116":1,"1286":1,"1316":1,"1323":9,"1670":1,"2126":1,"2127":1,"2128":1}}],["addable",{"3":{"1309":1,"1396":1}}],["addactivity",{"3":{"1209":2}}],["addanthropicaiprovider",{"2":{"1094":1,"1095":1,"1420":1},"3":{"1018":1,"1091":1,"1094":1,"1095":1,"1420":1,"1427":6}}],["addadditionalassemblies",{"2":{"558":1,"651":1},"3":{"558":1,"651":1,"1416":1,"1436":1}}],["addassociationrequest",{"3":{"1380":1}}],["addaspnetcoreinstrumentation",{"2":{"402":1,"409":1},"3":{"402":1,"409":1}}],["addasync",{"2":{"883":1,"1386":1},"3":{"0":1,"881":2,"883":1,"1270":1,"1286":7,"1300":2,"1324":5,"1359":8,"1382":2,"1386":1,"1395":24,"1396":2,"1402":2,"1436":4,"2074":1}}],["addattributes",{"2":{"1052":1},"3":{"0":1,"1050":1,"1052":1,"1300":2}}],["addappleprovider",{"3":{"1311":1}}],["addapple",{"3":{"1311":1}}],["addapplicationpart",{"3":{"1309":1}}],["addapplicationprofiling",{"2":{"117":1,"1262":1,"1662":1,"1821":1},"3":{"117":1,"1262":1,"1270":8,"1316":1,"1323":4,"1438":1,"1662":1,"1821":1}}],["addapplicationdecorators",{"2":{"117":1,"119":1,"121":1,"122":1,"127":1,"587":1,"632":1,"922":1,"979":1,"1262":1,"1507":1,"1651":1,"1653":1,"1700":1,"1824":1,"1827":1,"1883":1,"2095":1,"2098":1,"2136":1},"3":{"0":3,"117":4,"119":1,"121":2,"122":2,"127":1,"572":2,"587":1,"632":1,"922":1,"979":1,"1212":1,"1262":3,"1263":1,"1270":15,"1300":2,"1309":1,"1311":6,"1316":5,"1323":17,"1359":2,"1415":1,"1436":6,"1438":1,"1507":1,"1651":1,"1653":1,"1700":1,"1824":4,"1827":1,"1883":2,"2095":2,"2098":2,"2136":1}}],["addapplication",{"2":{"127":1,"979":1,"980":1,"982":1,"1319":1,"1881":1},"3":{"0":1,"117":1,"121":1,"122":1,"127":1,"572":1,"979":3,"980":4,"982":1,"1214":1,"1247":1,"1259":1,"1262":2,"1270":2,"1271":1,"1300":1,"1311":3,"1315":1,"1316":2,"1319":1,"1323":8,"1415":1,"1436":4,"1438":2,"1881":1,"1883":1,"2098":1}}],["addapiparameterdescriptorbackfill",{"2":{"2131":1},"3":{"1311":2,"2131":1}}],["addapiversioning",{"2":{"454":1},"3":{"454":1}}],["addapiexplorer",{"3":{"448":1,"1311":1}}],["addapi",{"2":{"306":1,"656":1,"965":1,"966":1,"1824":1,"2095":1},"3":{"0":3,"117":2,"265":1,"300":1,"303":2,"306":1,"318":1,"326":1,"583":1,"656":1,"657":1,"740":1,"744":1,"964":4,"965":1,"966":1,"979":2,"1050":2,"1237":5,"1300":1,"1311":24,"1323":2,"1396":1,"1436":1,"1824":1,"1994":3,"2095":1,"2098":1,"2136":1,"2138":1}}],["addautomapper",{"2":{"1958":1},"3":{"1958":1}}],["addauthentication",{"2":{"1975":1},"3":{"350":1,"827":1,"1300":2,"1311":1,"1436":1,"1975":1}}],["addauthorizationcore",{"2":{"960":1,"1432":1},"3":{"954":2,"960":2,"1416":1,"1432":1,"1436":4,"1488":1,"1491":1}}],["addauthorization",{"2":{"59":1,"833":1,"1447":1},"3":{"0":1,"59":1,"827":2,"833":1,"1300":1,"1436":2,"1447":1}}],["addauthorizationpolicies",{"2":{"186":1,"189":1,"1300":1,"1698":1,"1963":1},"3":{"0":1,"186":1,"189":2,"1270":1,"1297":3,"1300":15,"1311":2,"1323":2,"1698":2,"1963":1}}],["addaudittrailandscheduler",{"3":{"1209":6}}],["addaudittrailtests",{"3":{"1199":1,"1207":2,"1286":2}}],["addaudittrail",{"2":{"713":1,"717":1,"979":1,"1232":1,"1277":1,"1839":1},"3":{"0":1,"674":1,"713":1,"715":3,"717":1,"720":3,"979":1,"1212":2,"1232":1,"1237":2,"1269":1,"1270":3,"1274":1,"1277":1,"1286":10,"1316":1,"1317":1,"1323":4,"1442":1,"1664":1,"1839":1}}],["addshoppingcartitem",{"3":{"1765":1,"1769":1}}],["addsomething",{"3":{"1380":1}}],["addsource",{"3":{"1339":5}}],["addsqlite",{"3":{"1339":1}}],["addsqlserver",{"3":{"1339":1,"1467":1}}],["addsupportedrange",{"3":{"1310":2}}],["addsupported",{"3":{"1310":2}}],["addschematransformer",{"3":{"1311":1}}],["addscheme",{"3":{"1300":1,"1436":1}}],["addscheduledjob",{"2":{"710":1},"3":{"707":1,"710":1,"846":1,"1270":1,"1286":1,"1317":1,"1323":3}}],["addscheduledjobstests",{"3":{"1199":1,"1207":4,"1323":2,"1438":2}}],["addscheduledjobs",{"2":{"705":1,"708":1,"720":1,"979":1,"1948":1},"3":{"0":1,"674":1,"705":1,"707":2,"708":1,"715":1,"720":1,"979":1,"1270":3,"1286":5,"1316":1,"1317":1,"1323":8,"1442":2,"1446":1,"1496":1,"1948":1}}],["addscoped",{"3":{"1286":1,"1304":1,"1309":1,"1311":1,"1323":2,"1324":9,"1359":2,"1395":6,"1396":5,"1415":3,"1417":4,"1653":1}}],["addspeakerquestionanswer",{"2":{"1639":1},"3":{"1350":2,"1496":1,"1639":1}}],["addspeakercategoryitem",{"3":{"1203":3,"1207":8,"1270":1,"1350":2,"1359":8}}],["addspeakercategoryitemhandlertests",{"3":{"1199":1,"1207":2,"1359":2}}],["addspeakercategoryitemhandler",{"3":{"1199":2,"1203":1,"1207":2,"1270":2,"1359":19}}],["addspeakercategoryitemcommandvalidatortests",{"3":{"1199":1,"1207":1,"1359":1}}],["addspeakercategoryitemcommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1359":6}}],["addspeakercategoryitemcommand",{"3":{"1199":7,"1203":1,"1207":2,"1359":11,"1380":4}}],["addspeakercategoryitemrequest",{"2":{"1374":1},"3":{"1199":3,"1203":1,"1207":1,"1359":1,"1374":1,"1380":3}}],["addsponsorvisitcheckins",{"3":{"1209":2}}],["addsponsors",{"3":{"1209":2,"1369":6,"1496":1}}],["addsessionscoreresponseguardrail",{"2":{"1368":1,"1369":1,"1526":1},"3":{"1368":1,"1369":3,"1526":1}}],["addsessionspeakerasync",{"3":{"1395":1}}],["addsessionspeaker",{"3":{"1203":3,"1207":8,"1270":1,"1350":6,"1359":10,"1369":1}}],["addsessionspeakerhandlertests",{"3":{"1199":1,"1207":2,"1359":2}}],["addsessionspeakerhandler",{"3":{"1199":2,"1203":1,"1207":2,"1270":2,"1350":1,"1359":10}}],["addsessionspeakercommandvalidatortests",{"3":{"1199":1,"1207":1,"1359":1}}],["addsessionspeakercommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1359":5}}],["addsessionspeakercommand",{"3":{"1199":7,"1203":1,"1207":2,"1359":9,"1380":4}}],["addsessionspeakerrequest",{"3":{"1199":3,"1203":1,"1207":1,"1359":1,"1374":1,"1380":3}}],["addsessioncookieauthentication",{"2":{"1525":1},"3":{"1300":2,"1525":1,"1526":1}}],["addsessioncategoryitemasync",{"3":{"1395":1}}],["addsessioncategoryitem",{"3":{"1203":3,"1207":8,"1350":3,"1359":15}}],["addsessioncategoryitemhandlertests",{"3":{"1199":1,"1207":2,"1359":2}}],["addsessioncategoryitemhandler",{"3":{"1199":2,"1203":1,"1207":2,"1350":1,"1359":13}}],["addsessioncategoryitemcommandvalidatortests",{"3":{"1199":1,"1207":1,"1359":1}}],["addsessioncategoryitemcommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1359":7}}],["addsessioncategoryitemcommand",{"3":{"1199":7,"1203":1,"1207":2,"1359":13,"1380":4}}],["addsessioncategoryitemrequest",{"2":{"1374":1},"3":{"1199":3,"1203":1,"1207":1,"1359":1,"1374":1,"1380":3}}],["addsessionquestions",{"3":{"1209":2}}],["addsessionquestionanswer",{"3":{"1203":3,"1207":8,"1349":1,"1350":4,"1359":15}}],["addsessionquestionanswercommandvalidator",{"3":{"1199":2,"1203":1,"1207":2,"1359":6}}],["addsessionquestionanswercommandvalidatortests",{"2":{"1199":1},"3":{"1199":1,"1207":1,"1359":1}}],["addsessionquestionanswercommand",{"3":{"1199":7,"1203":1,"1207":2,"1359":10,"1380":3}}],["addsessionquestionanswerrequest",{"2":{"1374":1},"3":{"1199":3,"1203":1,"1207":1,"1359":1,"1374":1,"1380":3}}],["addsessionquestionanswerhandlertests",{"3":{"1199":1,"1207":2,"1359":2}}],["addsessionquestionanswerhandler",{"3":{"690":1,"1199":2,"1203":1,"1207":2,"1349":1,"1350":3,"1359":14}}],["addsessiondurationcomputedcolumn",{"3":{"1209":2}}],["addsessionasset",{"3":{"1209":2}}],["addsessionassetlinkhandlertests",{"3":{"1199":1,"1207":2}}],["addsessionassetlinkhandler",{"3":{"1199":2,"1203":1,"1207":2,"1350":2,"1357":2,"1359":9,"1496":1}}],["addsessionassetlinkcommand",{"3":{"1199":5,"1203":1,"1207":2,"1359":7,"1380":1}}],["addsessionaiscorepromptversion",{"3":{"1018":1,"1209":2,"1350":1,"1369":1}}],["addserverauthsessioncookie",{"3":{"1300":4,"1311":5,"1324":1}}],["addservicediscovery",{"2":{"1329":1},"3":{"1329":1,"1337":1,"1339":2,"1443":1}}],["addservicediscoverydestinationresolver",{"2":{"827":1,"837":1,"1447":1},"3":{"827":1,"837":1,"1338":1,"1339":1,"1447":2}}],["addservicedefaults",{"0":{"1329":1,"2006":1},"2":{"61":1,"72":1,"235":1,"236":1,"401":1,"404":1,"406":1,"407":1,"408":1,"663":1,"668":1,"833":1,"883":1,"913":1,"917":1,"918":1,"938":1,"1325":1,"1329":1,"1334":1,"1335":1,"1677":1,"1720":1,"1724":1,"1727":1,"2006":1,"2010":1,"2011":1,"2014":1,"2030":1,"2031":1,"2038":1,"2157":1,"2160":1},"3":{"0":5,"61":1,"72":1,"235":1,"236":1,"397":1,"401":6,"404":1,"406":1,"407":1,"408":1,"663":1,"665":2,"668":1,"833":1,"883":1,"913":1,"917":1,"918":1,"938":1,"1212":1,"1324":1,"1325":1,"1329":2,"1333":2,"1334":2,"1335":2,"1337":1,"1338":1,"1339":18,"1380":1,"1396":1,"1442":4,"1443":3,"1447":1,"1467":1,"1487":1,"1496":1,"1577":2,"1653":2,"1670":1,"1677":1,"1707":1,"1720":1,"1724":1,"1727":1,"2006":2,"2010":1,"2011":1,"2014":1,"2030":2,"2031":1,"2038":1,"2156":1,"2157":1,"2160":1}}],["addservicebusemulatorbroker",{"2":{"638":1,"640":1,"644":1,"1326":1,"1327":1,"1444":1,"1670":1,"2008":1,"2013":1},"3":{"638":1,"640":1,"644":1,"1326":1,"1327":1,"1339":6,"1441":1,"1444":1,"1526":1,"1670":1,"2008":1,"2013":1}}],["addservices",{"2":{"949":1,"1938":1},"3":{"230":1,"310":1,"946":1,"949":2,"1259":3,"1270":1,"1300":1,"1311":1,"1316":2,"1317":1,"1318":1,"1320":1,"1323":5,"1935":1,"1938":1}}],["addserilog",{"2":{"401":1,"1333":1,"1442":1},"3":{"0":1,"401":1,"1333":2,"1339":4,"1442":1}}],["addsignalr",{"2":{"231":1},"3":{"225":1,"231":1,"1935":1}}],["addsingleton",{"2":{"216":1},"3":{"216":1,"220":2,"300":1,"649":1,"682":1,"733":2,"832":1,"1301":1,"1311":1,"1323":1,"1324":9,"1339":3,"1396":1,"1415":3,"1417":4,"1427":1,"1436":4,"2150":1}}],["addstandardresiliencehandler",{"2":{"1707":1,"2019":1,"2030":1},"3":{"1312":2,"1339":3,"1707":1,"2019":1,"2030":1}}],["addstackexchangeredisoutputcache",{"2":{"243":1,"250":1,"251":1,"252":1,"254":1,"256":1,"257":1,"259":1,"260":1,"390":1},"3":{"243":1,"250":1,"251":1,"252":1,"254":1,"256":2,"257":1,"259":2,"260":1,"390":1,"1339":2}}],["addstackexchangeredis",{"3":{"225":1}}],["addstoredpermissiongrants",{"2":{"674":1,"1061":1,"1283":1,"1409":1,"2199":1,"2201":1,"2202":1},"3":{"674":1,"1059":3,"1061":1,"1273":1,"1283":1,"1286":8,"1297":1,"1300":2,"1316":1,"1323":1,"1409":1,"2199":1,"2201":1,"2202":1}}],["addstronglytypedids",{"2":{"798":1,"804":1,"806":1,"1051":1,"1052":1,"1810":1},"3":{"0":2,"798":1,"804":1,"806":1,"1050":2,"1051":1,"1052":1,"1247":1,"1281":1,"1286":3,"1311":1,"1316":1,"1323":1,"1810":2}}],["addsalesuserexportclient",{"2":{"96":1,"104":1},"3":{"96":1,"104":1}}],["adds",{"0":{"92":1,"1362":1},"2":{"450":1},"3":{"0":10,"15":1,"55":1,"87":1,"93":1,"94":1,"121":1,"130":1,"150":1,"157":1,"214":1,"219":1,"225":1,"233":1,"235":2,"246":1,"255":1,"264":1,"265":1,"275":1,"326":1,"330":1,"350":1,"352":1,"355":1,"370":1,"379":1,"390":1,"393":1,"395":1,"397":1,"400":1,"401":1,"402":2,"411":1,"450":1,"508":1,"524":1,"530":1,"540":1,"564":1,"565":1,"566":1,"572":1,"583":2,"586":1,"599":1,"616":1,"642":1,"649":1,"651":1,"665":1,"674":3,"691":1,"700":1,"707":1,"715":2,"718":1,"719":1,"740":1,"755":1,"756":1,"757":1,"759":1,"774":1,"800":1,"810":2,"819":1,"820":1,"827":5,"828":1,"834":1,"846":1,"853":1,"854":1,"861":1,"869":1,"881":2,"916":1,"922":1,"923":1,"926":2,"931":1,"933":1,"937":1,"954":4,"957":1,"964":1,"1018":1,"1035":1,"1038":1,"1042":1,"1043":1,"1050":1,"1052":1,"1059":1,"1065":1,"1083":4,"1089":1,"1093":1,"1129":1,"1135":1,"1152":1,"1168":2,"1175":1,"1184":2,"1212":2,"1214":1,"1219":1,"1228":1,"1229":1,"1230":1,"1231":3,"1237":10,"1244":1,"1247":1,"1249":3,"1252":1,"1258":1,"1259":8,"1262":1,"1270":9,"1271":3,"1273":2,"1276":2,"1277":1,"1278":2,"1281":2,"1286":28,"1288":1,"1292":1,"1300":17,"1301":3,"1308":1,"1309":5,"1311":27,"1312":4,"1315":1,"1316":2,"1317":1,"1318":1,"1320":4,"1322":2,"1323":17,"1324":25,"1325":1,"1326":1,"1329":1,"1332":2,"1333":6,"1336":1,"1338":2,"1339":29,"1350":9,"1352":2,"1355":1,"1359":61,"1362":1,"1369":6,"1375":4,"1378":1,"1379":2,"1380":8,"1382":2,"1394":1,"1395":39,"1396":25,"1402":4,"1406":1,"1414":1,"1415":6,"1416":3,"1417":3,"1421":1,"1427":6,"1429":1,"1430":1,"1432":1,"1436":42,"1438":3,"1441":5,"1442":3,"1443":3,"1444":2,"1447":2,"1448":1,"1455":2,"1459":1,"1463":2,"1467":3,"1481":1,"1486":1,"1488":3,"1489":4,"1491":1,"1496":5,"1526":1,"1536":1,"1575":1,"1576":1,"1577":1,"1591":2,"1625":1,"1631":1,"1632":1,"1640":1,"1641":1,"1650":1,"1651":2,"1663":1,"1666":2,"1669":2,"1670":1,"1714":1,"1722":1,"1749":1,"1750":2,"1765":1,"1776":2,"1809":1,"1810":3,"1829":1,"1839":2,"1842":1,"1843":1,"1851":1,"1878":1,"1880":1,"1882":1,"1920":1,"1922":1,"1935":1,"1936":1,"1942":1,"1971":1,"1976":1,"1978":2,"1988":1,"2001":1,"2002":1,"2006":1,"2010":1,"2012":1,"2024":2,"2032":1,"2039":1,"2053":1,"2066":1,"2067":1,"2085":1,"2090":2,"2099":1,"2103":1,"2121":1,"2126":2,"2149":1,"2156":3,"2160":1,"2175":1,"2177":1,"2232":1,"2240":1}}],["add",{"0":{"1684":1,"1701":1,"1729":1,"2093":1,"2106":1},"2":{"527":1,"659":1,"721":1,"938":1,"1044":1,"1585":1,"1652":1,"1688":1,"1793":1,"1827":1,"1846":1,"1853":1,"1862":1,"1868":1,"1886":1,"1892":1,"1899":1,"1905":1,"1912":1,"1924":1,"1930":1,"1941":1,"1950":1,"1959":1,"1972":1,"1979":1,"1985":1,"2002":1,"2038":1,"2060":1,"2108":1,"2115":1,"2122":1,"2123":1,"2128":1,"2133":1,"2139":1,"2146":1,"2153":1,"2169":1,"2192":1,"2203":1,"2211":1},"3":{"0":8,"1":1,"5":1,"69":1,"100":1,"195":1,"207":1,"230":1,"235":1,"236":1,"264":1,"265":2,"312":1,"333":1,"341":1,"350":1,"360":1,"365":1,"372":1,"397":1,"404":1,"413":2,"415":1,"459":1,"472":1,"494":1,"495":1,"517":1,"527":1,"528":1,"537":1,"627":1,"631":1,"633":3,"635":3,"636":1,"649":1,"650":1,"657":1,"659":1,"665":1,"674":1,"675":1,"691":1,"698":1,"707":1,"715":2,"721":1,"733":1,"734":1,"759":1,"809":1,"811":2,"820":1,"821":1,"827":1,"829":1,"834":1,"881":1,"882":1,"883":1,"905":1,"922":2,"938":1,"939":1,"964":1,"979":3,"981":2,"984":1,"997":1,"1004":1,"1018":1,"1026":2,"1027":2,"1030":1,"1035":1,"1044":1,"1052":1,"1058":1,"1059":1,"1060":1,"1061":1,"1062":1,"1068":1,"1085":2,"1089":1,"1093":1,"1157":1,"1176":1,"1177":1,"1179":3,"1186":1,"1193":1,"1196":1,"1212":3,"1214":1,"1229":2,"1234":3,"1237":10,"1238":1,"1241":1,"1243":1,"1247":4,"1251":1,"1259":6,"1265":1,"1270":12,"1280":1,"1284":1,"1286":19,"1287":2,"1290":2,"1291":2,"1297":1,"1300":8,"1301":2,"1309":4,"1310":2,"1311":10,"1312":2,"1316":1,"1320":1,"1323":10,"1324":29,"1333":1,"1339":9,"1343":1,"1344":1,"1350":12,"1351":1,"1357":1,"1359":86,"1366":1,"1369":6,"1371":2,"1374":1,"1375":1,"1380":27,"1382":2,"1385":1,"1386":1,"1389":2,"1391":1,"1395":45,"1396":10,"1399":3,"1401":1,"1402":9,"1415":11,"1416":9,"1417":17,"1418":1,"1427":3,"1429":1,"1433":1,"1436":16,"1438":4,"1445":1,"1453":1,"1455":1,"1467":3,"1472":1,"1477":2,"1487":1,"1490":2,"1492":1,"1496":4,"1497":1,"1524":1,"1526":1,"1530":2,"1531":1,"1576":1,"1581":1,"1582":1,"1585":2,"1589":1,"1595":3,"1596":2,"1598":1,"1599":1,"1600":1,"1616":1,"1617":1,"1619":1,"1630":1,"1631":2,"1632":11,"1638":1,"1639":1,"1641":1,"1647":2,"1648":1,"1649":1,"1650":2,"1651":6,"1652":2,"1653":1,"1654":4,"1656":1,"1657":1,"1661":1,"1663":1,"1666":1,"1668":1,"1671":1,"1674":1,"1678":1,"1679":1,"1681":1,"1682":1,"1683":2,"1684":1,"1685":1,"1686":3,"1687":2,"1688":1,"1689":2,"1690":1,"1691":1,"1692":1,"1697":2,"1701":2,"1702":2,"1703":1,"1704":1,"1719":3,"1720":1,"1722":1,"1724":1,"1726":1,"1727":5,"1728":4,"1729":2,"1730":2,"1731":2,"1737":1,"1742":1,"1746":2,"1748":1,"1749":1,"1750":4,"1754":1,"1755":3,"1758":1,"1761":1,"1764":1,"1765":4,"1767":1,"1768":4,"1769":1,"1773":2,"1775":2,"1776":3,"1786":1,"1793":1,"1805":2,"1807":1,"1812":2,"1815":1,"1817":1,"1823":1,"1824":1,"1827":1,"1834":1,"1844":1,"1846":1,"1849":1,"1853":1,"1862":1,"1865":1,"1868":1,"1877":1,"1883":1,"1885":1,"1886":1,"1890":1,"1892":3,"1899":2,"1905":1,"1907":1,"1912":1,"1924":1,"1925":1,"1930":2,"1941":1,"1946":1,"1950":1,"1959":1,"1960":2,"1963":1,"1965":1,"1970":1,"1972":1,"1973":4,"1979":1,"1985":1,"1991":1,"1997":1,"1999":1,"2002":1,"2014":1,"2023":1,"2024":1,"2028":1,"2029":2,"2030":1,"2032":1,"2038":1,"2041":2,"2048":2,"2049":2,"2060":1,"2069":1,"2075":1,"2077":1,"2080":1,"2081":1,"2086":1,"2087":1,"2091":1,"2097":5,"2098":1,"2099":2,"2102":1,"2104":1,"2105":1,"2106":1,"2108":2,"2115":1,"2119":1,"2122":1,"2123":2,"2128":1,"2129":1,"2133":1,"2138":1,"2139":1,"2142":1,"2146":2,"2147":1,"2153":1,"2156":1,"2160":4,"2169":2,"2170":1,"2180":1,"2192":1,"2193":1,"2194":2,"2202":1,"2203":1,"2211":1,"2234":1}}],["adr",{"0":{"1":1,"1475":1,"1507":1,"1508":1,"1509":1,"1510":1,"1512":1,"2025":1},"1":{"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":1,"9":1,"10":1,"11":1,"12":1,"13":1,"14":1,"15":1,"16":1,"17":1,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1,"24":1,"25":1,"26":1,"27":1,"28":1,"29":1,"30":1,"31":1,"32":1,"33":1,"34":1,"35":1,"36":1,"37":1,"38":1,"39":1,"40":1,"41":1,"42":1,"43":1,"44":1,"45":1,"46":1,"47":1,"48":1,"49":1,"50":1,"51":1,"52":1,"53":1,"54":1,"55":1,"56":1,"57":1,"58":1,"59":1,"60":1,"61":1,"62":1,"63":1,"64":1,"65":1,"66":1,"67":1,"68":1,"69":1,"70":1,"71":1,"72":1,"73":1,"74":1,"75":1,"76":1,"77":1,"78":1,"79":1,"80":1,"81":1,"82":1,"83":1,"84":1,"85":1,"86":1,"87":1,"88":1,"89":1,"90":1,"91":1,"92":1,"93":1,"94":1,"95":1,"96":1,"97":1,"98":1,"99":1,"100":1,"101":1,"102":1,"103":1,"104":1,"105":1,"106":1,"107":1,"108":1,"109":1,"110":1,"111":1,"112":1,"113":1,"114":1,"115":1,"116":1,"117":1,"118":1,"119":1,"120":1,"121":1,"122":1,"123":1,"124":1,"125":1,"126":1,"127":1,"128":1,"129":1,"130":1,"131":1,"132":1,"133":1,"134":1,"135":1,"136":1,"137":1,"138":1,"139":1,"140":1,"141":1,"142":1,"143":1,"144":1,"145":1,"146":1,"147":1,"148":1,"149":1,"150":1,"151":1,"152":1,"153":1,"154":1,"155":1,"156":1,"157":1,"158":1,"159":1,"160":1,"161":1,"162":1,"163":1,"164":1,"165":1,"166":1,"167":1,"168":1,"169":1,"170":1,"171":1,"172":1,"173":1,"174":1,"175":1,"176":1,"177":1,"178":1,"179":1,"180":1,"181":1,"182":1,"183":1,"184":1,"185":1,"186":1,"187":1,"188":1,"189":1,"190":1,"191":1,"192":1,"193":1,"194":1,"195":1,"196":1,"197":1,"198":1,"199":1,"200":1,"201":1,"202":1,"203":1,"204":1,"205":1,"206":1,"207":1,"208":1,"209":1,"210":1,"211":1,"212":1,"213":1,"214":1,"215":1,"216":1,"217":1,"218":1,"219":1,"220":1,"221":1,"222":1,"223":1,"224":1,"225":1,"226":1,"227":1,"228":1,"229":1,"230":1,"231":1,"232":1,"233":1,"234":1,"235":1,"236":1,"237":1,"238":1,"239":1,"240":1,"241":1,"242":1,"243":1,"244":1,"245":1,"246":1,"247":1,"248":1,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"260":1,"261":1,"262":2,"263":2,"264":2,"265":2,"266":2,"267":2,"268":2,"269":2,"270":1,"271":1,"272":1,"273":1,"274":1,"275":1,"276":1,"277":1,"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"294":1,"295":1,"296":1,"297":1,"298":1,"299":1,"300":1,"301":1,"302":1,"303":1,"304":1,"305":1,"306":1,"307":1,"308":1,"309":1,"310":1,"311":1,"312":1,"313":1,"314":1,"315":1,"316":1,"317":1,"318":1,"319":1,"320":1,"321":1,"322":1,"323":1,"324":1,"325":1,"326":1,"327":1,"328":1,"329":1,"330":1,"331":1,"332":1,"333":1,"334":1,"335":1,"336":1,"337":1,"338":1,"339":1,"340":1,"341":1,"342":1,"343":1,"344":1,"345":1,"346":1,"347":1,"348":1,"349":1,"350":1,"351":1,"352":1,"353":1,"354":1,"355":1,"356":1,"357":1,"358":1,"359":1,"360":1,"361":1,"362":1,"363":1,"364":1,"365":1,"366":1,"367":1,"368":1,"369":1,"370":1,"371":1,"372":1,"373":1,"374":1,"375":1,"376":1,"377":1,"378":1,"379":1,"380":1,"381":1,"382":1,"383":1,"384":1,"385":1,"386":1,"387":1,"388":1,"389":1,"390":1,"391":1,"392":1,"393":1,"394":1,"395":1,"396":1,"397":1,"398":1,"399":1,"400":1,"401":1,"402":1,"403":1,"404":1,"405":1,"406":1,"407":1,"408":1,"409":1,"410":1,"411":1,"412":1,"413":1,"414":1,"415":1,"416":1,"417":1,"418":1,"419":1,"420":1,"421":1,"422":1,"423":1,"424":1,"425":1,"426":1,"427":1,"428":1,"429":1,"430":1,"431":1,"432":1,"433":1,"434":1,"435":1,"436":1,"437":1,"438":1,"439":1,"440":1,"441":1,"442":1,"443":1,"444":1,"445":1,"446":1,"447":1,"448":1,"449":1,"450":1,"451":1,"452":1,"453":1,"454":1,"455":1,"456":1,"457":1,"458":1,"459":1,"460":1,"461":1,"462":1,"463":1,"464":1,"465":1,"466":1,"467":1,"468":1,"469":1,"470":1,"471":1,"472":1,"473":1,"474":1,"475":1,"476":1,"477":1,"478":1,"479":1,"480":1,"481":1,"482":1,"483":1,"484":1,"485":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"495":1,"496":1,"497":1,"498":1,"499":1,"500":1,"501":1,"502":1,"503":1,"504":1,"505":1,"506":1,"507":1,"508":1,"509":1,"510":1,"511":1,"512":1,"513":1,"514":1,"515":1,"516":1,"517":1,"518":1,"519":1,"520":1,"521":1,"522":1,"523":1,"524":1,"525":1,"526":1,"527":1,"528":1,"529":1,"530":1,"531":1,"532":1,"533":1,"534":1,"535":1,"536":1,"537":1,"538":1,"539":1,"540":1,"541":1,"542":1,"543":1,"544":1,"545":1,"546":1,"547":1,"548":1,"549":1,"550":1,"551":1,"552":1,"553":1,"554":1,"555":1,"556":1,"557":1,"558":1,"559":1,"560":1,"561":1,"562":1,"563":1,"564":1,"565":1,"566":1,"567":1,"568":1,"569":1,"570":1,"571":1,"572":1,"573":1,"574":1,"575":1,"576":1,"577":1,"578":1,"579":1,"580":1,"581":1,"582":1,"583":1,"584":1,"585":1,"586":1,"587":1,"588":1,"589":1,"590":1,"591":1,"592":1,"593":1,"594":1,"595":1,"596":1,"597":1,"598":1,"599":1,"600":1,"601":1,"602":1,"603":1,"604":1,"605":1,"606":1,"607":1,"608":1,"609":1,"610":1,"611":1,"612":1,"613":1,"614":1,"615":1,"616":1,"617":1,"618":1,"619":1,"620":1,"621":1,"622":1,"623":1,"624":1,"625":1,"626":1,"627":1,"628":1,"629":1,"630":1,"631":1,"632":1,"633":1,"634":1,"635":1,"636":1,"637":1,"638":1,"639":1,"640":1,"641":1,"642":1,"643":1,"644":1,"645":1,"646":1,"647":1,"648":1,"649":1,"650":1,"651":1,"652":1,"653":1,"654":1,"655":1,"656":1,"657":1,"658":1,"659":1,"660":1,"661":1,"662":1,"663":1,"664":1,"665":1,"666":1,"667":1,"668":1,"669":1,"670":1,"671":1,"672":1,"673":1,"674":1,"675":1,"676":1,"677":1,"678":1,"679":1,"680":1,"681":1,"682":1,"683":1,"684":1,"685":1,"686":1,"687":1,"688":1,"689":1,"690":1,"691":1,"692":1,"693":1,"694":1,"695":1,"696":1,"697":1,"698":1,"699":1,"700":1,"701":1,"702":1,"703":1,"704":1,"705":1,"706":1,"707":1,"708":1,"709":1,"710":1,"711":1,"712":1,"713":1,"714":1,"715":1,"716":1,"717":1,"718":1,"719":1,"720":1,"721":1,"722":1,"723":1,"724":1,"725":1,"726":1,"727":1,"728":1,"729":1,"730":2,"731":2,"732":2,"733":2,"734":2,"735":2,"736":2,"737":2,"738":1,"739":1,"740":1,"741":1,"742":1,"743":1,"744":1,"745":1,"746":1,"747":1,"748":1,"749":1,"750":1,"751":1,"752":1,"753":1,"754":1,"755":1,"756":1,"757":1,"758":1,"759":1,"760":1,"761":1,"762":1,"763":1,"764":1,"765":1,"766":1,"767":1,"768":1,"769":1,"770":1,"771":1,"772":1,"773":1,"774":1,"775":1,"776":1,"777":1,"778":1,"779":1,"780":1,"781":1,"782":1,"783":1,"784":1,"785":1,"786":1,"787":1,"788":1,"789":1,"790":1,"791":1,"792":1,"793":1,"794":1,"795":1,"796":1,"797":1,"798":1,"799":1,"800":1,"801":1,"802":1,"803":1,"804":1,"805":1,"806":1,"807":2,"808":2,"809":2,"810":2,"811":2,"812":2,"813":2,"814":2,"815":2,"816":1,"817":1,"818":1,"819":1,"820":1,"821":1,"822":1,"823":1,"824":1,"825":1,"826":1,"827":1,"828":1,"829":1,"830":1,"831":1,"832":1,"833":1,"834":1,"835":1,"836":1,"837":1,"838":1,"839":1,"840":1,"841":1,"842":1,"843":1,"844":1,"845":1,"846":1,"847":1,"848":1,"849":1,"850":1,"851":1,"852":1,"853":1,"854":1,"855":1,"856":1,"857":1,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1,"866":1,"867":1,"868":1,"869":1,"870":1,"871":1,"872":1,"873":1,"874":1,"875":1,"876":1,"877":1,"878":1,"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"886":1,"887":1,"888":1,"889":1,"890":1,"891":1,"892":1,"893":1,"894":1,"895":1,"896":1,"897":1,"898":1,"899":1,"900":1,"901":1,"902":1,"903":1,"904":1,"905":1,"906":1,"907":1,"908":1,"909":1,"910":1,"911":1,"912":1,"913":1,"914":1,"915":1,"916":1,"917":1,"918":1,"919":1,"920":1,"921":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"928":1,"929":1,"930":1,"931":1,"932":1,"933":1,"934":1,"935":1,"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1,"943":1,"944":1,"945":1,"946":1,"947":1,"948":1,"949":1,"950":1,"951":1,"952":1,"953":1,"954":1,"955":1,"956":1,"957":1,"958":1,"959":1,"960":1,"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1,"969":1,"970":1,"971":1,"972":1,"973":1,"974":1,"975":1,"976":1,"977":1,"978":1,"979":1,"980":1,"981":1,"982":1,"983":1,"984":1,"985":1,"986":1,"987":1,"988":1,"989":1,"990":1,"991":1,"992":1,"993":1,"994":1,"995":1,"996":1,"997":1,"998":1,"999":1,"1000":1,"1001":1,"1002":1,"1003":1,"1004":1,"1005":1,"1006":1,"1007":1,"1008":1,"1009":1,"1010":1,"1011":1,"1012":1,"1013":1,"1014":1,"1015":1,"1016":1,"1017":1,"1018":1,"1019":1,"1020":1,"1021":1,"1022":1,"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1,"1031":1,"1032":1,"1033":1,"1034":1,"1035":1,"1036":1,"1037":1,"1038":1,"1039":1,"1040":1,"1041":1,"1042":1,"1043":1,"1044":1,"1045":1,"1046":1,"1047":1,"1048":1,"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1,"1056":1,"1057":1,"1058":1,"1059":1,"1060":1,"1061":1,"1062":1,"1063":1,"1064":1,"1065":1,"1066":1,"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"1072":1,"1073":1,"1074":1,"1075":1,"1076":1,"1077":1,"1078":1,"1079":1,"1080":1,"1081":1,"1082":1,"1083":1,"1084":1,"1085":1,"1086":1,"1087":1,"1088":1,"1089":1,"1090":1,"1091":1,"1092":1,"1093":1,"1094":1,"1095":1,"1096":1,"1097":1,"1098":1,"1099":1,"1100":1,"1101":1,"1102":1,"1103":1,"1104":1,"1105":1,"1106":1,"1107":1,"1108":1,"1109":1,"1110":1,"1111":1,"1112":1,"1113":1,"1114":1,"1115":1,"1116":1,"1117":1,"1118":1,"1119":1,"1120":1,"1121":1,"1122":1,"1123":1,"1124":1,"1125":1,"1126":1,"1127":1,"1128":1,"1129":1,"1130":1,"1131":1,"1132":1,"1133":1,"1134":1,"1135":1,"1136":1,"1137":1,"1138":1,"1139":1,"1140":1,"1141":1,"1142":1,"1143":1,"1144":1,"1145":1,"1146":1,"1147":1,"1148":1,"1149":1,"1150":1,"1151":1,"1152":1,"1153":1,"1154":1,"1155":1,"1156":1,"1157":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1,"1165":1,"1166":1,"1167":1,"1168":1,"1169":1,"1170":1,"1171":1,"1172":1,"1173":1,"1174":1,"1175":1,"1176":1,"1177":1,"1178":1,"1179":1,"1180":1,"1181":1,"1182":1,"1183":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1,"1986":1,"1987":1,"1988":1,"1989":1,"1990":1,"1991":1},"3":{"0":211,"1":5,"2":1,"8":1,"14":1,"15":2,"19":1,"20":3,"24":4,"26":4,"28":1,"30":1,"31":1,"33":2,"35":4,"36":1,"38":1,"39":2,"40":1,"41":2,"44":1,"45":2,"46":1,"47":3,"50":1,"52":1,"53":2,"55":1,"56":1,"57":2,"59":9,"61":2,"62":2,"64":4,"65":1,"66":2,"67":2,"68":1,"71":1,"72":2,"75":1,"76":1,"77":2,"79":1,"80":1,"81":1,"83":1,"84":2,"85":1,"87":1,"89":1,"92":1,"96":1,"97":2,"98":2,"105":4,"106":1,"109":5,"113":4,"114":1,"117":3,"120":1,"121":3,"124":7,"125":1,"126":1,"128":2,"129":1,"130":1,"131":5,"133":3,"134":1,"135":3,"136":3,"140":1,"143":8,"144":1,"145":1,"147":1,"148":1,"149":1,"150":3,"153":4,"154":1,"156":3,"157":1,"158":4,"159":1,"160":4,"162":8,"163":1,"164":3,"165":2,"166":6,"168":2,"169":5,"172":1,"174":1,"177":1,"178":2,"182":7,"183":1,"184":1,"186":2,"188":2,"191":4,"192":1,"193":1,"194":4,"195":2,"196":2,"197":3,"199":6,"202":3,"204":1,"206":1,"207":1,"208":1,"210":4,"211":1,"221":3,"222":1,"223":1,"225":4,"227":1,"229":8,"230":10,"232":1,"235":1,"236":1,"239":4,"240":1,"241":4,"242":6,"243":9,"244":3,"245":5,"247":11,"248":1,"249":3,"253":2,"254":1,"255":12,"256":5,"258":2,"259":4,"262":2,"263":1,"264":5,"265":2,"266":2,"267":1,"269":7,"270":1,"272":2,"273":2,"275":1,"277":3,"278":1,"279":3,"280":2,"281":1,"282":3,"283":2,"284":1,"287":4,"288":1,"290":1,"291":1,"293":3,"294":3,"295":1,"297":1,"299":2,"300":1,"301":1,"304":7,"305":1,"307":1,"308":2,"313":5,"314":1,"315":1,"317":3,"318":3,"319":1,"320":4,"321":5,"322":2,"324":4,"326":2,"327":1,"328":1,"330":3,"331":2,"334":5,"336":1,"337":2,"338":1,"340":3,"341":1,"342":1,"343":1,"346":8,"347":1,"348":1,"349":12,"350":5,"351":1,"352":1,"355":7,"356":1,"358":6,"360":2,"361":4,"362":5,"365":1,"366":2,"367":1,"369":5,"370":1,"371":2,"373":1,"376":3,"377":1,"379":2,"380":1,"382":1,"383":2,"385":1,"386":1,"390":1,"391":1,"394":1,"396":4,"398":3,"400":5,"401":1,"402":1,"404":2,"406":6,"407":2,"408":1,"410":1,"412":1,"413":2,"414":2,"417":1,"419":2,"420":2,"423":2,"424":1,"425":1,"426":4,"428":3,"429":1,"430":1,"431":1,"432":1,"433":1,"434":2,"437":1,"438":1,"439":1,"440":2,"443":2,"444":1,"445":1,"446":1,"447":1,"449":1,"450":1,"454":1,"455":4,"456":1,"457":2,"458":3,"459":4,"460":1,"461":1,"462":6,"463":2,"464":3,"466":1,"467":1,"468":2,"469":2,"470":1,"471":3,"472":1,"473":6,"474":3,"475":1,"476":1,"477":2,"479":1,"481":1,"485":3,"490":1,"491":1,"492":1,"493":2,"496":1,"497":4,"498":3,"499":1,"500":1,"503":5,"504":1,"506":4,"508":2,"509":2,"510":3,"511":1,"515":1,"516":3,"517":4,"518":3,"520":1,"521":5,"522":2,"524":1,"525":1,"526":1,"528":1,"529":1,"531":1,"532":3,"533":1,"534":1,"535":3,"536":4,"537":4,"538":2,"539":1,"541":9,"542":1,"543":1,"544":6,"546":1,"548":12,"549":7,"551":1,"553":1,"554":5,"555":7,"556":6,"557":2,"558":1,"560":5,"561":1,"563":2,"565":3,"568":6,"569":1,"571":5,"572":6,"573":3,"574":2,"575":1,"576":1,"577":3,"578":2,"579":7,"580":1,"582":5,"586":1,"587":5,"588":1,"590":5,"591":1,"592":2,"594":1,"595":5,"596":1,"598":4,"599":5,"601":2,"605":4,"606":1,"608":2,"610":2,"611":1,"612":1,"614":4,"615":1,"616":1,"617":2,"618":2,"619":3,"622":3,"623":1,"624":1,"625":3,"626":2,"627":1,"630":2,"631":2,"632":2,"633":2,"634":1,"635":1,"637":1,"639":2,"642":1,"645":6,"646":1,"647":1,"648":4,"649":1,"650":1,"651":1,"653":4,"654":1,"656":3,"657":3,"658":2,"659":2,"660":1,"661":4,"662":1,"664":4,"665":1,"666":1,"667":3,"670":4,"671":1,"673":3,"676":2,"678":4,"679":1,"681":3,"682":3,"683":4,"684":4,"686":5,"687":1,"689":2,"690":3,"691":1,"694":6,"695":1,"697":2,"698":2,"700":1,"701":9,"702":1,"704":1,"706":2,"707":6,"708":1,"709":2,"711":7,"712":1,"714":2,"715":5,"716":2,"717":1,"721":8,"722":1,"724":1,"725":5,"726":1,"727":2,"729":7,"730":2,"731":4,"732":4,"733":6,"734":2,"735":1,"737":7,"738":1,"740":2,"741":5,"742":1,"743":4,"745":8,"746":1,"748":4,"749":7,"750":6,"751":2,"753":9,"754":1,"756":6,"757":2,"758":1,"759":3,"761":1,"762":7,"763":1,"764":1,"765":5,"766":1,"768":2,"769":2,"770":4,"771":1,"773":8,"774":1,"775":1,"776":3,"777":2,"778":7,"779":1,"781":2,"782":2,"783":2,"784":2,"785":1,"786":7,"787":1,"789":6,"790":4,"792":2,"795":7,"796":1,"797":4,"798":3,"799":5,"800":2,"801":3,"802":8,"803":1,"804":1,"807":2,"808":2,"809":5,"810":9,"811":2,"812":2,"813":2,"815":9,"816":1,"817":3,"818":2,"819":2,"821":4,"823":8,"824":1,"825":4,"826":4,"827":18,"828":2,"829":3,"834":10,"835":1,"836":2,"837":1,"838":2,"839":1,"840":2,"842":8,"843":1,"844":2,"845":3,"847":3,"848":1,"849":1,"851":1,"852":2,"853":2,"854":3,"856":3,"857":3,"858":1,"860":3,"861":1,"862":2,"863":2,"865":5,"866":1,"868":1,"869":1,"870":1,"872":1,"873":1,"874":5,"878":1,"880":5,"881":7,"882":4,"885":7,"886":1,"888":3,"889":1,"891":3,"892":1,"893":4,"894":1,"895":1,"896":6,"897":2,"899":2,"900":3,"901":7,"902":1,"903":1,"904":2,"905":3,"907":3,"910":12,"911":1,"912":1,"913":2,"914":3,"916":4,"918":9,"919":1,"920":1,"921":2,"922":6,"923":3,"924":2,"925":8,"926":1,"927":1,"928":1,"929":2,"930":2,"931":1,"932":2,"933":2,"935":6,"936":1,"937":2,"938":1,"939":3,"940":1,"941":1,"942":5,"943":1,"944":1,"945":1,"946":1,"947":2,"948":1,"950":6,"951":1,"953":6,"954":3,"955":7,"956":2,"957":9,"960":1,"961":1,"962":1,"963":3,"964":4,"965":4,"966":2,"968":7,"969":1,"971":3,"972":1,"973":2,"976":7,"977":1,"979":2,"980":2,"981":1,"984":6,"985":1,"987":6,"988":2,"989":1,"992":8,"993":1,"994":1,"995":4,"1000":6,"1001":1,"1003":2,"1004":1,"1005":1,"1006":2,"1008":5,"1009":1,"1010":1,"1012":4,"1013":1,"1015":1,"1016":2,"1017":1,"1018":3,"1020":1,"1022":7,"1023":1,"1024":1,"1025":3,"1026":6,"1027":2,"1030":8,"1031":1,"1032":2,"1033":2,"1034":1,"1035":2,"1036":1,"1038":7,"1039":1,"1040":1,"1041":4,"1042":2,"1044":3,"1046":6,"1047":1,"1048":4,"1049":5,"1050":4,"1051":7,"1052":4,"1053":8,"1054":1,"1056":1,"1057":2,"1058":2,"1060":1,"1061":1,"1063":5,"1064":1,"1065":1,"1069":2,"1071":5,"1072":1,"1073":4,"1074":4,"1075":1,"1076":1,"1077":1,"1078":1,"1079":10,"1080":1,"1082":1,"1083":1,"1084":2,"1087":6,"1088":1,"1089":1,"1090":2,"1092":1,"1093":1,"1096":8,"1097":1,"1098":1,"1099":2,"1100":4,"1101":2,"1102":3,"1103":1,"1104":7,"1105":1,"1107":1,"1108":2,"1112":6,"1113":1,"1114":2,"1115":1,"1116":2,"1118":1,"1120":7,"1121":1,"1123":3,"1126":1,"1129":5,"1130":1,"1132":2,"1134":3,"1137":5,"1138":1,"1139":3,"1142":2,"1144":4,"1145":2,"1147":8,"1148":1,"1150":1,"1152":3,"1154":2,"1155":2,"1157":9,"1158":1,"1160":1,"1162":2,"1164":2,"1165":1,"1167":4,"1169":3,"1171":5,"1172":1,"1173":3,"1174":1,"1176":1,"1180":7,"1181":1,"1182":3,"1183":1,"1186":2,"1189":8,"1192":18,"1193":1,"1202":4,"1203":4,"1211":2,"1212":40,"1213":7,"1214":1,"1217":1,"1219":1,"1222":1,"1225":1,"1227":2,"1228":2,"1229":11,"1231":4,"1232":2,"1233":3,"1234":1,"1235":1,"1236":1,"1237":28,"1238":5,"1239":2,"1241":1,"1243":1,"1244":1,"1247":23,"1248":4,"1249":2,"1251":1,"1254":1,"1256":1,"1257":2,"1258":2,"1259":70,"1260":2,"1261":1,"1262":1,"1263":4,"1264":1,"1265":1,"1267":1,"1269":6,"1270":61,"1271":8,"1273":2,"1274":1,"1275":1,"1276":1,"1277":2,"1278":3,"1279":1,"1281":2,"1282":1,"1283":2,"1284":2,"1285":3,"1286":193,"1287":15,"1289":1,"1290":1,"1292":2,"1293":1,"1294":1,"1297":2,"1298":2,"1299":1,"1300":165,"1301":23,"1302":5,"1303":2,"1304":2,"1307":2,"1308":1,"1309":38,"1310":14,"1311":123,"1312":21,"1313":4,"1314":1,"1316":4,"1317":6,"1318":1,"1320":4,"1321":1,"1322":4,"1323":114,"1324":204,"1325":4,"1326":7,"1328":4,"1330":1,"1331":1,"1332":2,"1333":2,"1334":2,"1336":3,"1337":3,"1338":2,"1339":100,"1343":1,"1345":1,"1347":3,"1348":1,"1349":3,"1350":70,"1352":2,"1353":4,"1354":2,"1355":4,"1357":1,"1358":1,"1359":237,"1361":2,"1363":1,"1364":1,"1365":1,"1366":1,"1367":3,"1368":1,"1369":13,"1371":2,"1372":2,"1373":2,"1374":1,"1375":2,"1376":1,"1378":3,"1379":8,"1380":38,"1384":1,"1387":1,"1389":3,"1391":1,"1394":2,"1395":82,"1396":183,"1397":2,"1398":1,"1399":1,"1400":3,"1401":3,"1402":70,"1404":3,"1405":4,"1406":7,"1407":1,"1408":1,"1409":1,"1410":2,"1411":2,"1412":2,"1413":6,"1414":1,"1415":144,"1416":27,"1417":106,"1418":2,"1419":1,"1421":2,"1423":1,"1427":30,"1428":4,"1429":2,"1430":4,"1431":10,"1432":3,"1433":1,"1435":2,"1436":244,"1438":76,"1441":6,"1442":1,"1446":3,"1447":3,"1449":3,"1450":1,"1453":3,"1454":8,"1455":5,"1458":1,"1460":4,"1461":3,"1462":3,"1467":20,"1468":1,"1469":5,"1471":2,"1473":4,"1474":1,"1475":1,"1476":1,"1477":1,"1481":1,"1482":6,"1483":1,"1486":2,"1487":2,"1488":7,"1489":10,"1490":1,"1491":1,"1492":4,"1493":3,"1494":10,"1496":111,"1499":2,"1500":4,"1502":1,"1503":6,"1504":1,"1508":1,"1509":3,"1510":3,"1512":1,"1513":6,"1514":1,"1515":2,"1519":1,"1521":1,"1524":2,"1525":3,"1526":28,"1527":3,"1531":2,"1534":5,"1535":5,"1536":2,"1541":1,"1543":1,"1545":2,"1546":1,"1547":1,"1548":2,"1554":1,"1573":2,"1575":2,"1576":8,"1577":83,"1578":3,"1582":8,"1583":1,"1585":9,"1586":4,"1591":20,"1592":3,"1596":1,"1599":7,"1600":2,"1626":1,"1627":2,"1629":1,"1630":2,"1631":10,"1632":1,"1636":2,"1640":4,"1641":2,"1642":2,"1646":1,"1648":2,"1649":1,"1650":1,"1651":6,"1652":1,"1653":4,"1654":5,"1655":1,"1656":5,"1658":1,"1660":4,"1661":1,"1662":3,"1663":3,"1664":4,"1665":2,"1666":2,"1667":4,"1668":4,"1669":1,"1670":4,"1671":2,"1672":1,"1673":5,"1674":1,"1675":5,"1680":1,"1682":1,"1684":2,"1685":2,"1686":1,"1693":3,"1695":1,"1697":1,"1699":1,"1703":1,"1705":1,"1706":1,"1707":1,"1708":2,"1719":1,"1720":12,"1721":3,"1723":2,"1724":8,"1726":1,"1727":2,"1728":1,"1732":1,"1735":1,"1737":1,"1747":3,"1748":5,"1749":5,"1751":1,"1755":1,"1759":1,"1760":1,"1761":2,"1764":1,"1765":9,"1769":4,"1772":1,"1778":1,"1779":4,"1781":4,"1783":1,"1784":4,"1790":4,"1792":1,"1793":4,"1796":2,"1799":3,"1801":1,"1802":1,"1804":1,"1805":1,"1806":1,"1807":3,"1810":1,"1820":1,"1824":1,"1826":1,"1827":6,"1839":1,"1840":1,"1841":2,"1842":3,"1843":2,"1845":1,"1846":3,"1848":1,"1851":1,"1852":1,"1853":4,"1855":3,"1856":2,"1858":1,"1861":2,"1864":1,"1867":1,"1868":3,"1869":2,"1870":2,"1875":2,"1877":2,"1884":1,"1886":3,"1888":1,"1889":2,"1890":1,"1891":2,"1892":5,"1896":2,"1899":4,"1902":1,"1903":4,"1906":2,"1910":1,"1915":1,"1916":1,"1920":1,"1921":3,"1922":1,"1923":2,"1924":1,"1929":1,"1932":1,"1933":3,"1935":1,"1936":1,"1941":1,"1942":1,"1943":1,"1946":2,"1947":1,"1948":1,"1949":1,"1950":6,"1951":1,"1952":1,"1953":1,"1955":1,"1958":1,"1959":1,"1965":3,"1970":2,"1971":1,"1972":5,"1974":1,"1976":1,"1978":1,"1979":2,"1983":1,"1984":1,"1985":3,"1986":1,"1987":1,"1988":4,"1990":1,"1991":7,"1994":2,"1995":1,"1996":1,"1997":4,"2000":7,"2001":4,"2002":4,"2006":1,"2008":2,"2009":2,"2012":1,"2020":1,"2021":1,"2022":1,"2023":1,"2024":4,"2025":1,"2027":2,"2028":6,"2029":2,"2030":1,"2032":3,"2033":1,"2034":2,"2035":2,"2037":1,"2038":2,"2043":2,"2044":1,"2046":1,"2047":1,"2055":2,"2056":1,"2057":1,"2059":1,"2060":3,"2062":2,"2063":2,"2064":1,"2065":4,"2066":3,"2068":2,"2069":6,"2081":4,"2083":3,"2085":5,"2086":3,"2087":2,"2098":1,"2099":1,"2100":1,"2106":2,"2107":1,"2108":4,"2112":1,"2116":1,"2117":1,"2118":1,"2120":1,"2121":2,"2123":1,"2126":5,"2127":2,"2128":2,"2130":2,"2131":4,"2132":2,"2133":2,"2136":1,"2137":1,"2138":3,"2139":2,"2140":1,"2141":3,"2143":2,"2146":1,"2147":1,"2149":3,"2150":3,"2151":3,"2152":2,"2153":3,"2155":1,"2156":2,"2158":3,"2160":2,"2161":1,"2162":6,"2163":1,"2164":4,"2165":1,"2166":6,"2168":7,"2169":4,"2170":1,"2179":5,"2180":3,"2181":2,"2182":1,"2183":10,"2184":3,"2185":3,"2186":1,"2188":2,"2190":1,"2192":9,"2193":5,"2194":1,"2195":1,"2198":3,"2199":1,"2202":3,"2203":2,"2204":1,"2208":1,"2232":27}}],["adrs",{"0":{"1515":1},"3":{"0":4,"1":1,"131":1,"324":2,"555":1,"789":1,"1011":1,"1013":1,"1190":1,"1212":3,"1270":1,"1286":5,"1309":1,"1311":1,"1313":1,"1317":1,"1350":1,"1359":3,"1396":1,"1417":2,"1428":1,"1471":2,"1494":1,"1496":8,"1502":1,"1517":1,"1534":1,"1537":1,"1552":1,"1571":4,"1572":2,"1577":4,"1591":2,"1658":1,"1659":1,"1663":1,"1673":1,"1674":1,"1705":1,"1725":1,"1783":1,"1787":1,"1792":1,"1793":1,"1797":1,"2002":1,"2028":1,"2086":1,"2211":1,"2219":1,"2221":1,"2232":2}}]],"serializationVersion":2}} \ No newline at end of file diff --git a/docs-src/onboarding/00-dependency-manifest.md b/docs-src/onboarding/00-dependency-manifest.md index 9103fd9c..72ffc34c 100644 --- a/docs-src/onboarding/00-dependency-manifest.md +++ b/docs-src/onboarding/00-dependency-manifest.md @@ -16,10 +16,10 @@ candidate exists but the bare name is **globally unique** among first-party type is still linked (only one possible target). Names that are neither visible nor unique are dropped as unresolvable without full semantic binding. -- Edges resolved by namespace visibility: **18427** (~96%) -- Edges resolved by globally-unique name (fallback): **745** -- References dropped as ambiguous (matched >1 type, none visible): **97** -- Sensitivity: **1012 / 5034** type levels would change if the globally-unique fallback +- Edges resolved by namespace visibility: **19214** (~96%) +- Edges resolved by globally-unique name (fallback): **786** +- References dropped as ambiguous (matched >1 type, none visible): **101** +- Sensitivity: **1189 / 5234** type levels would change if the globally-unique fallback were excluded; the fallback is retained because a globally-unique first-party name is unambiguous, so excluding it would under-count real dependencies. @@ -32,37 +32,36 @@ alias `using`s name a target whose bare name already matches (so they resolve re | Level | Distinct types | |-------|------| -| 0 | 960 | -| 1 | 614 | -| 2 | 391 | -| 3 | 365 | -| 4 | 445 | -| 5 | 380 | -| 6 | 200 | -| 7 | 159 | -| 8 | 244 | -| 9 | 287 | -| 10 | 268 | -| 11 | 104 | -| 12 | 88 | -| 13 | 157 | -| 14 | 84 | -| 15 | 172 | -| 16 | 22 | -| 17 | 23 | -| 18 | 70 | +| 0 | 998 | +| 1 | 631 | +| 2 | 404 | +| 3 | 376 | +| 4 | 446 | +| 5 | 378 | +| 6 | 208 | +| 7 | 156 | +| 8 | 210 | +| 9 | 277 | +| 10 | 263 | +| 11 | 123 | +| 12 | 70 | +| 13 | 198 | +| 14 | 146 | +| 15 | 200 | +| 16 | 35 | +| 17 | 37 | +| 18 | 77 | | 19 | 1 | -## Cycles (SCC size > 1): 46 +## Cycles (SCC size > 1): 47 | Level | Size | Members | |-------|------|---------| | 1 | 2 | Hosting:H2cEndpointHealthCheck, Hosting:H2cHealthCheckExtensions | | 2 | 3 | Shared:Result, Shared:ResultJsonConverterFactory, Shared:ResultConverter | | 2 | 2 | Shared:IStronglyTypedId, Shared:StronglyTypedId | -| 2 | 2 | API:SessionCookieEndpoints, API:SessionCookieJar | | 2 | 2 | AI:ContentPolicyGuardrail, AI:ContentPolicySettings | | 2 | 2 | UI:NotificationHubService, UI:ChannelSubscription | | 2 | 2 | Web:ClientConfigBuilder, Web:ClientConfigEndpointExtensions | @@ -93,18 +92,20 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 8 | 4 | Domain:Session, Domain:SessionCategoryItem, Domain:SessionQuestionAnswer, Domain:SessionSpeaker | | 8 | 2 | Domain:LivePoll, Domain:LivePollOption | | 10 | 2 | API:WebApplicationExtensions, API:MiddlewarePipelineBuilder | -| 11 | 8 | Infrastructure:AuditTrailSaveChangesInterceptor, Infrastructure:ApplicationDbContext, Infrastructure:DataSourceModelCacheKeyFactory, Infrastructure:AuditSaveChangesInterceptor, Infrastructure:DomainEventSaveChangesInterceptor, Infrastructure:DeferredDispatch, Infrastructure:TenantSaveChangesInterceptor, Infrastructure:OutboxFinalizer | +| 11 | 23 | Infrastructure:SoftDeleteFilterSql, Infrastructure:AuditTrailSaveChangesInterceptor, Infrastructure:CrossDataSourceDegradeConvention, Infrastructure:RestrictDeleteByDefaultConvention, Infrastructure:SoftDeleteUniqueIndexConvention, Infrastructure:PhysicalDataSource, Infrastructure:ApplicationDbContext, Infrastructure:CosmosDbContext, Infrastructure:DataSourceModelCacheKeyFactory, Infrastructure:PostgreSQLDbContext, Infrastructure:SqliteDbContext, Infrastructure:SQLServerDbContext, Infrastructure:AuditSaveChangesInterceptor, Infrastructure:DomainEventSaveChangesInterceptor, Infrastructure:DeferredDispatch, Infrastructure:TenantSaveChangesInterceptor, Infrastructure:CosmosDataSourceEngine, Infrastructure:DataSourceEngines, Infrastructure:IDataSourceEngine, Infrastructure:PostgreSQLDataSourceEngine, Infrastructure:SqliteDataSourceEngine, Infrastructure:SQLServerDataSourceEngine, Infrastructure:OutboxFinalizer | +| 11 | 2 | API:SessionCookieEndpoints, API:SessionCookieJar | | 12 | 2 | Tests:GateTestContext, Tests:GateTestContext | -| 12 | 2 | Tests:AuditTrailTestContext, Tests:FailingSaveInterceptor | -| 12 | 2 | Tests:MidSaveContextCreatingDbContext, Tests:ReentrantSaveInterceptor | -| 12 | 2 | Tests:CommitFailingDbContext, Tests:FailingDatabaseFacade | -| 12 | 2 | Tests:FailingSaveInterceptor, Tests:OutboxRoutingTestDbContext | | 13 | 2 | Tests:EventScopeFitnessTests, Tests:FakeConsumerMap | | 13 | 2 | Tests:EventUpcasterFitnessTests, Tests:UpcasterTestMap | -| 14 | 3 | Tests:CosmosConfigurationPortabilityTests, Tests:FixedAssemblyProvider, Tests:MultiSourceSqliteIntegrationTests | +| 13 | 2 | Tests:AuditTrailTestContext, Tests:FailingSaveInterceptor | +| 13 | 2 | Tests:MidSaveContextCreatingDbContext, Tests:ReentrantSaveInterceptor | +| 13 | 2 | Tests:CommitFailingDbContext, Tests:FailingDatabaseFacade | +| 13 | 2 | Tests:FailingSaveInterceptor, Tests:OutboxRoutingTestDbContext | | 14 | 2 | Tests:PostgreSQLPersistenceTests, Tests:FixedAssemblyProvider | -| 14 | 2 | Tests:DatabaseInitializationExtensionsTests, Tests:FixedAssemblyProvider | | 14 | 2 | Tests:PostgreSQLDbContextModelTests, Tests:FixedAssemblyProvider | +| 15 | 3 | Tests:CosmosConfigurationPortabilityTests, Tests:FixedAssemblyProvider, Tests:MultiSourceSqliteIntegrationTests | +| 15 | 2 | Tests:SQLServerPersistenceTests, Tests:FixedAssemblyProvider | +| 15 | 2 | Tests:DatabaseInitializationExtensionsTests, Tests:FixedAssemblyProvider | ## Manifest (by level, then assembly) @@ -239,6 +240,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `SessionSchedulePageRequest` | MMCA.ADC.Conference.UI | 0 | (none) | | 0 | `SpeakerInfo` | MMCA.ADC.Conference.UI | 0 | (none) | | 0 | `VenueMapLinks` | MMCA.ADC.Conference.UI | 0 | (none) | +| 0 | `FixedTimeProvider` | MMCA.ADC.Conference.UI.Tests | 0 | (none) | | 0 | `EventFeedbackPage` | MMCA.ADC.E2E.Tests | 0 | (none) | | 0 | `FeaturedEvent` | MMCA.ADC.E2E.Tests | 0 | (none) | | 0 | `GatewayApi` | MMCA.ADC.E2E.Tests | 0 | (none) | @@ -373,6 +375,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `PiiLogCapture` | MMCA.ADC.Identity.IntegrationTests | 0 | (none) | | 0 | `PreferencesResponse` | MMCA.ADC.Identity.IntegrationTests | 0 | (none) | | 0 | `IAttendeeQueryService` | MMCA.ADC.Identity.Shared | 0 | (none) | +| 0 | `JwtAudience` | MMCA.ADC.Identity.Shared | 0 | (none) | | 0 | `RoleNames` | MMCA.ADC.Identity.Shared | 0 | (none) | | 0 | `UserAdminDTO` | MMCA.ADC.Identity.Shared | 0 | (none) | | 0 | `UserAvatarDTO` | MMCA.ADC.Identity.Shared | 0 | (none) | @@ -387,6 +390,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `RoleList` | MMCA.ADC.Identity.UI | 0 | (none) | | 0 | `UserNotificationExportItemDTO` | MMCA.ADC.Notification.Shared | 0 | (none) | | 0 | `FakeServerCallContext` | MMCA.ADC.Services.Tests | 0 | (none) | +| 0 | `GrpcCalls` | MMCA.ADC.Services.Tests | 0 | (none) | | 0 | `NowNextSession` | MMCA.ADC.UI | 0 | (none) | | 0 | `AiSettings` | MMCA.Common.AI | 0 | (none) | | 0 | `AiUsageMeter` | MMCA.Common.AI | 0 | (none) | @@ -421,6 +425,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `IdempotencyRecord` | MMCA.Common.API | 0 | (none) | | 0 | `IdempotencySettings` | MMCA.Common.API | 0 | (none) | | 0 | `IErrorLocalizer` | MMCA.Common.API | 0 | (none) | +| 0 | `ISessionCookieStore` | MMCA.Common.API | 0 | (none) | | 0 | `JwtAuthorityExtensions` | MMCA.Common.API | 0 | (none) | | 0 | `MiddlewarePipelineStep` | MMCA.Common.API | 0 | (none) | | 0 | `MiddlewarePipelineStepNames` | MMCA.Common.API | 0 | (none) | @@ -436,14 +441,19 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `RedisRateLimitLease` | MMCA.Common.API | 0 | (none) | | 0 | `ServiceInfoResponse` | MMCA.Common.API | 0 | (none) | | 0 | `ServiceInfoV2Response` | MMCA.Common.API | 0 | (none) | +| 0 | `SessionClaimsToken` | MMCA.Common.API | 0 | (none) | | 0 | `SessionCookieRequest` | MMCA.Common.API | 0 | (none) | +| 0 | `SessionCookieSettings` | MMCA.Common.API | 0 | (none) | +| 0 | `SessionRefreshStatus` | MMCA.Common.API | 0 | (none) | | 0 | `SessionTokenResponse` | MMCA.Common.API | 0 | (none) | | 0 | `SessionTokenResult` | MMCA.Common.API | 0 | (none) | | 0 | `ValidationExceptionHandler` | MMCA.Common.API | 0 | (none) | | 0 | `CultureEndpointTests` | MMCA.Common.API.Tests | 0 | (none) | | 0 | `EndpointFeatureStub` | MMCA.Common.API.Tests | 0 | (none) | +| 0 | `ExportRow` | MMCA.Common.API.Tests | 0 | (none) | | 0 | `FakeCategoriesController` | MMCA.Common.API.Tests | 0 | (none) | | 0 | `FakeGrpcMetadata` | MMCA.Common.API.Tests | 0 | (none) | +| 0 | `MapCommonOpenApiAuthorizationTests` | MMCA.Common.API.Tests | 0 | (none) | | 0 | `NextDelegateSpy` | MMCA.Common.API.Tests | 0 | (none) | | 0 | `NonSeekableStream` | MMCA.Common.API.Tests | 0 | (none) | | 0 | `OutputCacheEvictTagsTests` | MMCA.Common.API.Tests | 0 | (none) | @@ -456,6 +466,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `TestUpdateRequest` | MMCA.Common.API.Tests | 0 | (none) | | 0 | `TestUserDto` | MMCA.Common.API.Tests | 0 | (none) | | 0 | `TrackingHandle` | MMCA.Common.API.Tests | 0 | (none) | +| 0 | `Wrapped` | MMCA.Common.API.Tests | 0 | (none) | | 0 | `AmbientScope` | MMCA.Common.Application | 0 | (none) | | 0 | `ApplicationSettings` | MMCA.Common.Application | 0 | (none) | | 0 | `AssemblyReference` | MMCA.Common.Application | 0 | (none) | @@ -543,12 +554,10 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `RefreshSessionSettings` | MMCA.Common.Application | 0 | (none) | | 0 | `RequiredIdRules` | MMCA.Common.Application | 0 | (none) | | 0 | `RequiredStringRules` | MMCA.Common.Application | 0 | (none) | -| 0 | `StrongPasswordRules` | MMCA.Common.Application | 0 | (none) | | 0 | `TwoFactorOutcome` | MMCA.Common.Application | 0 | (none) | | 0 | `TwoFactorSettings` | MMCA.Common.Application | 0 | (none) | | 0 | `UserAdministrationQuery` | MMCA.Common.Application | 0 | (none) | | 0 | `UserDataExportSectionDefaults` | MMCA.Common.Application | 0 | (none) | -| 0 | `UserUseCaseLog` | MMCA.Common.Application | 0 | (none) | | 0 | `AddOrderLineCommand` | MMCA.Common.Application.Tests | 0 | (none) | | 0 | `BillingFakeCommand` | MMCA.Common.Application.Tests | 0 | (none) | | 0 | `BillingFakeQuery` | MMCA.Common.Application.Tests | 0 | (none) | @@ -585,6 +594,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `RemoveOrderLineCommand` | MMCA.Common.Application.Tests | 0 | (none) | | 0 | `RenameOrderCommand` | MMCA.Common.Application.Tests | 0 | (none) | | 0 | `RenameOrderResult` | MMCA.Common.Application.Tests | 0 | (none) | +| 0 | `ScanFailingAssembly` | MMCA.Common.Application.Tests | 0 | (none) | | 0 | `ScopedProbe` | MMCA.Common.Application.Tests | 0 | (none) | | 0 | `SortTestEntity` | MMCA.Common.Application.Tests | 0 | (none) | | 0 | `SpeakerDto` | MMCA.Common.Application.Tests | 0 | (none) | @@ -615,6 +625,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `AsyncClockReadingFixture` | MMCA.Common.Architecture.Tests | 0 | (none) | | 0 | `CompliantFixtureService` | MMCA.Common.Architecture.Tests | 0 | (none) | | 0 | `CreateTicketCommand` | MMCA.Common.Architecture.Tests | 0 | (none) | +| 0 | `EngineHit` | MMCA.Common.Architecture.Tests | 0 | (none) | | 0 | `ExemptableFixtureService` | MMCA.Common.Architecture.Tests | 0 | (none) | | 0 | `ExternalContractFixtureService` | MMCA.Common.Architecture.Tests | 0 | (none) | | 0 | `FatFixtureController` | MMCA.Common.Architecture.Tests | 0 | (none) | @@ -634,15 +645,19 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `NonIdempotentFitnessController` | MMCA.Common.Architecture.Tests | 0 | (none) | | 0 | `NonThrowingFixture` | MMCA.Common.Architecture.Tests | 0 | (none) | | 0 | `OffsetNowReadingFixture` | MMCA.Common.Architecture.Tests | 0 | (none) | +| 0 | `PasswordProbe` | MMCA.Common.Architecture.Tests | 0 | (none) | | 0 | `PurgeTicketsCommand` | MMCA.Common.Architecture.Tests | 0 | (none) | | 0 | `ReadRepositoryFixtureCommand` | MMCA.Common.Architecture.Tests | 0 | (none) | | 0 | `ReadRepositoryFixtureQuery` | MMCA.Common.Architecture.Tests | 0 | (none) | | 0 | `RebuildFixtureProjectionCommand` | MMCA.Common.Architecture.Tests | 0 | (none) | | 0 | `RebuildTicketIndexCommand` | MMCA.Common.Architecture.Tests | 0 | (none) | | 0 | `ReopenTicketRequest` | MMCA.Common.Architecture.Tests | 0 | (none) | +| 0 | `ResourceEntry` | MMCA.Common.Architecture.Tests | 0 | (none) | | 0 | `RethrowingFixture` | MMCA.Common.Architecture.Tests | 0 | (none) | +| 0 | `SwitchExpressionFixture` | MMCA.Common.Architecture.Tests | 0 | (none) | | 0 | `ThinFixtureController` | MMCA.Common.Architecture.Tests | 0 | (none) | | 0 | `TicketDomainException` | MMCA.Common.Architecture.Tests | 0 | (none) | +| 0 | `TodayReadingFixture` | MMCA.Common.Architecture.Tests | 0 | (none) | | 0 | `TwoMemberClockFixture` | MMCA.Common.Architecture.Tests | 0 | (none) | | 0 | `TypeLevelAnonymousFixtureController` | MMCA.Common.Architecture.Tests | 0 | (none) | | 0 | `UndeclaredFitnessController` | MMCA.Common.Architecture.Tests | 0 | (none) | @@ -677,8 +692,10 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `FakeEnvironment` | MMCA.Common.Aspire.Tests | 0 | (none) | | 0 | `FakeLifetime` | MMCA.Common.Aspire.Tests | 0 | (none) | | 0 | `FakeServer` | MMCA.Common.Aspire.Tests | 0 | (none) | +| 0 | `LiveMetricsConfigurationTests` | MMCA.Common.Aspire.Tests | 0 | (none) | | 0 | `ProbeAttempt` | MMCA.Common.Aspire.Tests | 0 | (none) | | 0 | `RecordingHttpResponseFeature` | MMCA.Common.Aspire.Tests | 0 | (none) | +| 0 | `ServiceDefaultsRetryTests` | MMCA.Common.Aspire.Tests | 0 | (none) | | 0 | `SourceCollectingConfigurationManager` | MMCA.Common.Aspire.Tests | 0 | (none) | | 0 | `StubClock` | MMCA.Common.Aspire.Tests | 0 | (none) | | 0 | `StubHostEnvironment` | MMCA.Common.Aspire.Tests | 0 | (none) | @@ -714,6 +731,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `InvocationFinder` | MMCA.Common.Domain.Tests | 0 | (none) | | 0 | `NoPii` | MMCA.Common.Domain.Tests | 0 | (none) | | 0 | `ParameterFinder` | MMCA.Common.Domain.Tests | 0 | (none) | +| 0 | `PiiBase` | MMCA.Common.Domain.Tests | 0 | (none) | | 0 | `Subject` | MMCA.Common.Domain.Tests | 0 | (none) | | 0 | `TestScope` | MMCA.Common.Domain.Tests | 0 | (none) | | 0 | `UndecoratedEntity` | MMCA.Common.Domain.Tests | 0 | (none) | @@ -724,7 +742,9 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `GatewayRoutePolicyPartition` | MMCA.Common.Gateway | 0 | (none) | | 0 | `GatewayTraceHeaderSettings` | MMCA.Common.Gateway | 0 | (none) | | 0 | `EmptyServiceProvider` | MMCA.Common.Gateway.Tests | 0 | (none) | +| 0 | `GrpcWireFormat` | MMCA.Common.Grpc | 0 | (none) | | 0 | `JwtForwardingClientInterceptor` | MMCA.Common.Grpc | 0 | (none) | +| 0 | `AuthenticateResultFeatureStub` | MMCA.Common.Grpc.Tests | 0 | (none) | | 0 | `CountingFailureHandler` | MMCA.Common.Grpc.Tests | 0 | (none) | | 0 | `FakeClient` | MMCA.Common.Grpc.Tests | 0 | (none) | | 0 | `FakeGrpcClient` | MMCA.Common.Grpc.Tests | 0 | (none) | @@ -749,11 +769,11 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `EmailConfirmationEntry` | MMCA.Common.Infrastructure | 0 | (none) | | 0 | `EncryptedStringConverter` | MMCA.Common.Infrastructure | 0 | (none) | | 0 | `EntityConfigurationOptions` | MMCA.Common.Infrastructure | 0 | (none) | +| 0 | `ExplicitKeyInsertGroup` | MMCA.Common.Infrastructure | 0 | (none) | | 0 | `FileStorageSettings` | MMCA.Common.Infrastructure | 0 | (none) | | 0 | `GroupedCount` | MMCA.Common.Infrastructure | 0 | (none) | | 0 | `GroupedSum` | MMCA.Common.Infrastructure | 0 | (none) | | 0 | `IDbSeeder` | MMCA.Common.Infrastructure | 0 | (none) | -| 0 | `IdentityInsertGroup` | MMCA.Common.Infrastructure | 0 | (none) | | 0 | `IInboxStore` | MMCA.Common.Infrastructure | 0 | (none) | | 0 | `IInternalCommandSignal` | MMCA.Common.Infrastructure | 0 | (none) | | 0 | `IJwksProvider` | MMCA.Common.Infrastructure | 0 | (none) | @@ -771,6 +791,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `LoginProtectionSettings` | MMCA.Common.Infrastructure | 0 | (none) | | 0 | `LookupRow` | MMCA.Common.Infrastructure | 0 | (none) | | 0 | `MessageBusProvider` | MMCA.Common.Infrastructure | 0 | (none) | +| 0 | `MigrationPolicy` | MMCA.Common.Infrastructure | 0 | (none) | | 0 | `ModelBuilderExtensions` | MMCA.Common.Infrastructure | 0 | (none) | | 0 | `NativePushPayloads` | MMCA.Common.Infrastructure | 0 | (none) | | 0 | `NativePushSettings` | MMCA.Common.Infrastructure | 0 | (none) | @@ -784,6 +805,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `ProfilingHelper` | MMCA.Common.Infrastructure | 0 | (none) | | 0 | `RedisLockHandle` | MMCA.Common.Infrastructure | 0 | (none) | | 0 | `RedisPrefixScanner` | MMCA.Common.Infrastructure | 0 | (none) | +| 0 | `RowVersionStrategy` | MMCA.Common.Infrastructure | 0 | (none) | | 0 | `ScheduledJobEntry` | MMCA.Common.Infrastructure | 0 | (none) | | 0 | `ScheduledJobOverrideSettings` | MMCA.Common.Infrastructure | 0 | (none) | | 0 | `SchedulerMetrics` | MMCA.Common.Infrastructure | 0 | (none) | @@ -807,15 +829,20 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `CaseNoSettings` | MMCA.Common.Infrastructure.Tests | 0 | (none) | | 0 | `CaseOptedInDefaultSource` | MMCA.Common.Infrastructure.Tests | 0 | (none) | | 0 | `CaseSettingsOnly` | MMCA.Common.Infrastructure.Tests | 0 | (none) | +| 0 | `CountingAllocator` | MMCA.Common.Infrastructure.Tests | 0 | (none) | | 0 | `DrillResult` | MMCA.Common.Infrastructure.Tests | 0 | (none) | | 0 | `FakeEntity` | MMCA.Common.Infrastructure.Tests | 0 | (none) | | 0 | `FakeTimeProvider` | MMCA.Common.Infrastructure.Tests | 0 | (none) | | 0 | `FaultingHybridCache` | MMCA.Common.Infrastructure.Tests | 0 | (none) | | 0 | `GrantOnlyContextBase` | MMCA.Common.Infrastructure.Tests | 0 | (none) | +| 0 | `IFakeContract` | MMCA.Common.Infrastructure.Tests | 0 | (none) | +| 0 | `ImageFrameBoundCollection` | MMCA.Common.Infrastructure.Tests | 0 | (none) | +| 0 | `ManualClock` | MMCA.Common.Infrastructure.Tests | 0 | (none) | | 0 | `ManualClock` | MMCA.Common.Infrastructure.Tests | 0 | (none) | | 0 | `Observation` | MMCA.Common.Infrastructure.Tests | 0 | (none) | | 0 | `OrderPlacedTestEvent` | MMCA.Common.Infrastructure.Tests | 0 | (none) | | 0 | `ParentDetail` | MMCA.Common.Infrastructure.Tests | 0 | (none) | +| 0 | `PiiBaseThing` | MMCA.Common.Infrastructure.Tests | 0 | (none) | | 0 | `PlainThing` | MMCA.Common.Infrastructure.Tests | 0 | (none) | | 0 | `PropertyFacets` | MMCA.Common.Infrastructure.Tests | 0 | (none) | | 0 | `RecordedExecution` | MMCA.Common.Infrastructure.Tests | 0 | (none) | @@ -830,6 +857,8 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `TestItem` | MMCA.Common.Infrastructure.Tests | 0 | (none) | | 0 | `UnregisteredEntity` | MMCA.Common.Infrastructure.Tests | 0 | (none) | | 0 | `WidgetRow` | MMCA.Common.Infrastructure.Tests | 0 | (none) | +| 0 | `LoadResults` | MMCA.Common.LoadTests | 0 | (none) | +| 0 | `PagedQuery` | MMCA.Common.LoadTests | 0 | (none) | | 0 | `AdministrationPermissions` | MMCA.Common.Shared | 0 | (none) | | 0 | `AuthClaimTypes` | MMCA.Common.Shared | 0 | (none) | | 0 | `AuthenticationRequest` | MMCA.Common.Shared | 0 | (none) | @@ -864,6 +893,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `NotificationScopeKey` | MMCA.Common.Shared | 0 | (none) | | 0 | `OAuthCodeExchangeRequest` | MMCA.Common.Shared | 0 | (none) | | 0 | `PaginationMetadata` | MMCA.Common.Shared | 0 | (none) | +| 0 | `PasswordComplexity` | MMCA.Common.Shared | 0 | (none) | | 0 | `PermissionCatalogResponse` | MMCA.Common.Shared | 0 | (none) | | 0 | `PropertyReader` | MMCA.Common.Shared | 0 | (none) | | 0 | `RefreshSessionSummaryResponse` | MMCA.Common.Shared | 0 | (none) | @@ -952,7 +982,6 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `CapturedRequest` | MMCA.Common.Testing.UI | 0 | (none) | | 0 | `ErrorSummaryExtensions` | MMCA.Common.Testing.UI | 0 | (none) | | 0 | `FreshApiClientFactory` | MMCA.Common.Testing.UI | 0 | (none) | -| 0 | `IsAuthenticatedAuthorizationService` | MMCA.Common.Testing.UI | 0 | (none) | | 0 | `MarkupSnapshotResult` | MMCA.Common.Testing.UI | 0 | (none) | | 0 | `MudProviderHandles` | MMCA.Common.Testing.UI | 0 | (none) | | 0 | `MutableAuthenticationStateProvider` | MMCA.Common.Testing.UI | 0 | (none) | @@ -1012,12 +1041,10 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `ListPageState` | MMCA.Common.UI | 0 | (none) | | 0 | `LocalNotificationRequest` | MMCA.Common.UI | 0 | (none) | | 0 | `LoginModel` | MMCA.Common.UI | 0 | (none) | -| 0 | `MmcaClientConfigBootstrap` | MMCA.Common.UI | 0 | (none) | | 0 | `MudTranslations` | MMCA.Common.UI | 0 | (none) | | 0 | `NavSection` | MMCA.Common.UI | 0 | (none) | | 0 | `NotificationBellOptions` | MMCA.Common.UI | 0 | (none) | | 0 | `NotificationState` | MMCA.Common.UI | 0 | (none) | -| 0 | `PasswordComplexityAttribute` | MMCA.Common.UI | 0 | (none) | | 0 | `PendingAttempt` | MMCA.Common.UI | 0 | (none) | | 0 | `PermissionGroup` | MMCA.Common.UI | 0 | (none) | | 0 | `PersistedGridState` | MMCA.Common.UI | 0 | (none) | @@ -1026,12 +1053,11 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `PushDeviceToken` | MMCA.Common.UI | 0 | (none) | | 0 | `QrErrorCorrectionLevel` | MMCA.Common.UI | 0 | (none) | | 0 | `RatingStars` | MMCA.Common.UI | 0 | (none) | -| 0 | `RegisterModel` | MMCA.Common.UI | 0 | (none) | -| 0 | `ResetPasswordModel` | MMCA.Common.UI | 0 | (none) | | 0 | `ReturnUrlProtector` | MMCA.Common.UI | 0 | (none) | | 0 | `RoleAdminEditResources` | MMCA.Common.UI | 0 | (none) | | 0 | `RoleAdminListResources` | MMCA.Common.UI | 0 | (none) | | 0 | `RoutePaths` | MMCA.Common.UI | 0 | (none) | +| 0 | `SameOriginProxyHeaders` | MMCA.Common.UI | 0 | (none) | | 0 | `SharedResource` | MMCA.Common.UI | 0 | (none) | | 0 | `StringLocalizerPluralExtensions` | MMCA.Common.UI | 0 | (none) | | 0 | `ToastSeverity` | MMCA.Common.UI | 0 | (none) | @@ -1043,21 +1069,26 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `UserPreferences` | MMCA.Common.UI | 0 | (none) | | 0 | `UserPreferencesRequest` | MMCA.Common.UI | 0 | (none) | | 0 | `WebApplicationExtensions` | MMCA.Common.UI | 0 | (none) | +| 0 | `InpProbe` | MMCA.Common.UI.E2E.Tests | 0 | (none) | | 0 | `GalleryFakeAuthenticationHandler` | MMCA.Common.UI.Gallery | 0 | (none) | | 0 | `SampleGridRow` | MMCA.Common.UI.Gallery | 0 | (none) | | 0 | `BarcodeScanPage` | MMCA.Common.UI.Maui | 0 | (none) | | 0 | `MauiErrorHandlingInitializer` | MMCA.Common.UI.Maui | 0 | (none) | | 0 | `BareUrlModel` | MMCA.Common.UI.Tests | 0 | (none) | | 0 | `CapturedRequest` | MMCA.Common.UI.Tests | 0 | (none) | +| 0 | `CapturingHandler` | MMCA.Common.UI.Tests | 0 | (none) | | 0 | `ChildModel` | MMCA.Common.UI.Tests | 0 | (none) | | 0 | `CultureScope` | MMCA.Common.UI.Tests | 0 | (none) | | 0 | `FakeLocalizer` | MMCA.Common.UI.Tests | 0 | (none) | | 0 | `FakeStringLocalizer` | MMCA.Common.UI.Tests | 0 | (none) | +| 0 | `GatedGetHandler` | MMCA.Common.UI.Tests | 0 | (none) | | 0 | `KeyedModel` | MMCA.Common.UI.Tests | 0 | (none) | | 0 | `MembershipDto` | MMCA.Common.UI.Tests | 0 | (none) | +| 0 | `PipePair` | MMCA.Common.UI.Tests | 0 | (none) | | 0 | `RecordingNavigationManager` | MMCA.Common.UI.Tests | 0 | (none) | | 0 | `ResxMudLocalizerTests` | MMCA.Common.UI.Tests | 0 | (none) | | 0 | `ScriptedHandler` | MMCA.Common.UI.Tests | 0 | (none) | +| 0 | `StubHandler` | MMCA.Common.UI.Tests | 0 | (none) | | 0 | `StubHttpClientFactory` | MMCA.Common.UI.Tests | 0 | (none) | | 0 | `StubLocalizer` | MMCA.Common.UI.Tests | 0 | (none) | | 0 | `StubLocalizer` | MMCA.Common.UI.Tests | 0 | (none) | @@ -1067,8 +1098,16 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 0 | `WidgetRow` | MMCA.Common.UI.Tests | 0 | (none) | | 0 | `BlazorCircuitLimitSettings` | MMCA.Common.UI.Web | 0 | (none) | | 0 | `BlazorCspSettings` | MMCA.Common.UI.Web | 0 | (none) | +| 0 | `HandoffBody` | MMCA.Common.UI.Web | 0 | (none) | +| 0 | `ProxyResponseMode` | MMCA.Common.UI.Web | 0 | (none) | +| 0 | `SameOriginApiProxyMarker` | MMCA.Common.UI.Web | 0 | (none) | +| 0 | `SameOriginApiProxySettings` | MMCA.Common.UI.Web | 0 | (none) | +| 0 | `SameOriginProxyInvoker` | MMCA.Common.UI.Web | 0 | (none) | +| 0 | `TokenPair` | MMCA.Common.UI.Web | 0 | (none) | | 0 | `TrustedCallerHandler` | MMCA.Common.UI.Web | 0 | (none) | | 0 | `UiRateLimitingSettings` | MMCA.Common.UI.Web | 0 | (none) | +| 0 | `Jwt` | MMCA.Common.UI.Web.Tests | 0 | (none) | +| 0 | `SeenRequest` | MMCA.Common.UI.Web.Tests | 0 | (none) | | 0 | `SentRequest` | MMCA.Common.UI.Web.Tests | 0 | (none) | | 1 | `BrandColorTokenTests` | MMCA.ADC.Architecture.Tests | 1 | BrandColorTokenTestsBase | | 1 | `ObservabilityConventionTests` | MMCA.ADC.Architecture.Tests | 1 | ObservabilityConventionTestsBase | @@ -1138,6 +1177,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 1 | `SpeakerQr` | MMCA.ADC.Conference.UI | 2 | ConferenceRoutePaths, IPublicLinkBuilder | | 1 | `ChildEntityDeletePathTests` | MMCA.ADC.Conference.UI.Tests | 1 | ChildEntityDeletePath | | 1 | `FixedOriginLinkBuilder` | MMCA.ADC.Conference.UI.Tests | 1 | IPublicLinkBuilder | +| 1 | `RecordingCacheStore` | MMCA.ADC.Conference.UI.Tests | 1 | ILocalCacheStore | | 1 | `VenueMapLinksTests` | MMCA.ADC.Conference.UI.Tests | 1 | VenueMapLinks | | 1 | `FakeCrossServiceAttendeeQueryService` | MMCA.ADC.CrossService.IntegrationTests | 1 | IAttendeeQueryService | | 1 | `TestSetup` | MMCA.ADC.E2E.Tests | 1 | E2ETestConfiguration | @@ -1180,7 +1220,6 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 1 | `SessionReminderPlanner` | MMCA.ADC.Engagement.UI | 3 | EngagementRoutePaths, SessionInfo, SessionReminder | | 1 | `HttpContextExternalLoginEmailVerifier` | MMCA.ADC.Identity.API | 2 | ExternalAuthExtensions, IExternalLoginEmailVerifier | | 1 | `IdentityErrorResourcesTests` | MMCA.ADC.Identity.API.Tests | 2 | IdentityErrorResources, IErrorLocalizer | -| 1 | `ChangePasswordRequestValidator` | MMCA.ADC.Identity.Application | 2 | ChangePasswordRequest, StrongPasswordRules | | 1 | `ConfirmEmailCommand` | MMCA.ADC.Identity.Application | 2 | ConfirmEmailRequest, ICommandWithRequest | | 1 | `ForgotPasswordCommand` | MMCA.ADC.Identity.Application | 2 | ForgotPasswordRequest, ICommandWithRequest | | 1 | `SetUserAvatarCommand` | MMCA.ADC.Identity.Application | 1 | IHasTimeout | @@ -1191,6 +1230,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 1 | `UserDataExportNotificationSectionDTO` | MMCA.ADC.Identity.Shared | 1 | UserDataExportNotificationDTO | | 1 | `UserDTO` | MMCA.ADC.Identity.Shared | 1 | IBaseDTO | | 1 | `UserListDTO` | MMCA.ADC.Identity.Shared | 1 | IUserAdminDTO | +| 1 | `JwtAudienceTests` | MMCA.ADC.Identity.Shared.Tests | 1 | JwtAudience | | 1 | `AttendeeNotificationRecipientProvider` | MMCA.ADC.Notification.Application | 2 | IAttendeeQueryService, INotificationRecipientProvider | | 1 | `LiveChannelPublisherGrpcAdapter` | MMCA.ADC.Notification.Contracts | 1 | ILiveChannelPublisher | | 1 | `FakeAttendeeQueryService` | MMCA.ADC.Notification.IntegrationTests | 1 | IAttendeeQueryService | @@ -1221,7 +1261,6 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 1 | `FallbackAuthorizationHandler` | MMCA.Common.API | 2 | FallbackAuthorizationOptions, FallbackAuthorizationRequirement | | 1 | `GlobalExceptionHandler` | MMCA.Common.API | 1 | CrossTenantWriteException | | 1 | `HasPermissionAttribute` | MMCA.Common.API | 1 | PermissionPolicy | -| 1 | `ICookieSessionRefresher` | MMCA.Common.API | 1 | SessionTokenResult | | 1 | `JwksEndpointExtensions` | MMCA.Common.API | 1 | IJwksProvider | | 1 | `MiniProfilerExtensions` | MMCA.Common.API | 1 | ApplicationSettings | | 1 | `PermissionAuthorizationHandler` | MMCA.Common.API | 2 | IPermissionRegistry, PermissionRequirement | @@ -1236,14 +1275,17 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 1 | `ErrorLocalizerTests` | MMCA.Common.API.Tests | 1 | IErrorLocalizer | | 1 | `ExportTestDTO` | MMCA.Common.API.Tests | 1 | IBaseDTO | | 1 | `ExternalAuthExtensionsTests` | MMCA.Common.API.Tests | 1 | ExternalAuthExtensions | +| 1 | `HostRegistrationProbeController` | MMCA.Common.API.Tests | 1 | Route | | 1 | `IdempotencySettingsTests` | MMCA.Common.API.Tests | 1 | IdempotencySettings | | 1 | `JwtAuthorityExtensionsTests` | MMCA.Common.API.Tests | 1 | JwtAuthorityExtensions | | 1 | `OpenApiProbeHost` | MMCA.Common.API.Tests | 1 | ProbeControllerFeatureProvider | +| 1 | `OperationCanceledExceptionHandlerTests` | MMCA.Common.API.Tests | 1 | OperationCanceledExceptionHandler | | 1 | `PlainDTO` | MMCA.Common.API.Tests | 1 | IBaseDTO | | 1 | `ProblemDetailsProbeController` | MMCA.Common.API.Tests | 1 | Route | | 1 | `QueryFilterModelBinderTests` | MMCA.Common.API.Tests | 1 | QueryFilterModelBinder | | 1 | `ReadScopeDTO` | MMCA.Common.API.Tests | 1 | IBaseDTO | | 1 | `SegmentVersionedProbeController` | MMCA.Common.API.Tests | 1 | Route | +| 1 | `SessionClaimsTokenTests` | MMCA.Common.API.Tests | 1 | SessionClaimsToken | | 1 | `StubErrorLocalizer` | MMCA.Common.API.Tests | 1 | IErrorLocalizer | | 1 | `StubTenantContext` | MMCA.Common.API.Tests | 1 | ITenantContext | | 1 | `TestAggDTO` | MMCA.Common.API.Tests | 1 | IBaseDTO | @@ -1255,6 +1297,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 1 | `TestResetPasswordCommand` | MMCA.Common.API.Tests | 2 | ICommandWithRequest, ResetPasswordRequest | | 1 | `UnboundRouteTokenProbeController` | MMCA.Common.API.Tests | 1 | Route | | 1 | `VersionedDTO` | MMCA.Common.API.Tests | 2 | IBaseDTO, IConcurrencyAware | +| 1 | `WrappedAsStringConverter` | MMCA.Common.API.Tests | 1 | Wrapped | | 1 | `BestEffort` | MMCA.Common.Application | 2 | BestEffortLog, BestEffortMetrics | | 1 | `BoolFilterStrategy` | MMCA.Common.Application | 3 | DynamicQueryConfig, FilterValueParser, IFilterStrategy | | 1 | `CommandRequestValidator` | MMCA.Common.Application | 1 | ICommandWithRequest | @@ -1287,11 +1330,11 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 1 | `ProfilingQueryDecorator` | MMCA.Common.Application | 1 | IQueryHandler | | 1 | `QueryTagScope` | MMCA.Common.Application | 1 | AmbientScope | | 1 | `RefreshTokenRequestValidator` | MMCA.Common.Application | 1 | RefreshTokenRequest | -| 1 | `ResetPasswordRequestValidator` | MMCA.Common.Application | 2 | ResetPasswordRequest, StrongPasswordRules | | 1 | `SendEmailConfirmationRequestValidator` | MMCA.Common.Application | 1 | SendEmailConfirmationRequest | | 1 | `SendPushNotificationCommand` | MMCA.Common.Application | 3 | ICommandWithRequest, ITransactional, SendPushNotificationRequest | | 1 | `SessionStampingTokenService` | MMCA.Common.Application | 2 | AuthClaimTypes, ITokenService | | 1 | `StringFilterStrategy` | MMCA.Common.Application | 3 | DynamicQueryConfig, FilterValueParser, IFilterStrategy | +| 1 | `StrongPasswordRules` | MMCA.Common.Application | 1 | PasswordComplexity | | 1 | `TenantCacheKey` | MMCA.Common.Application | 1 | ITenantContext | | 1 | `TwoFactorCodeRequestValidator` | MMCA.Common.Application | 1 | TwoFactorCodeRequest | | 1 | `UnconfiguredPermissionRegistry` | MMCA.Common.Application | 2 | IPermissionCatalog, IPermissionRegistry | @@ -1361,6 +1404,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 1 | `CreateTicketCommandValidator` | MMCA.Common.Architecture.Tests | 1 | CreateTicketCommand | | 1 | `CustomExceptionThrowingFixture` | MMCA.Common.Architecture.Tests | 1 | TicketDomainException | | 1 | `DisabledFakeExportService` | MMCA.Common.Architecture.Tests | 1 | IFakeExportService | +| 1 | `EditorRequiredParameterConventionTests` | MMCA.Common.Architecture.Tests | 1 | UISharedAssemblyReference | | 1 | `FixtureBadFeatures` | MMCA.Common.Architecture.Tests | 1 | FeatureFlagLifetime | | 1 | `FixtureDomainEvent` | MMCA.Common.Architecture.Tests | 1 | IDomainEvent | | 1 | `FixtureExpiredFeatures` | MMCA.Common.Architecture.Tests | 1 | FeatureFlagLifetime | @@ -1406,17 +1450,20 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 1 | `PiiRedactor` | MMCA.Common.Domain | 2 | PiiAttribute, RedactableProperty | | 1 | `IdValueGeneratedAttributeTests` | MMCA.Common.Domain.Tests | 3 | DecoratedEntity, IdValueGeneratedAttribute, UndecoratedEntity | | 1 | `NavigationAttributeTests` | MMCA.Common.Domain.Tests | 2 | EntityWithNavigation, NavigationAttribute | +| 1 | `PiiOverride` | MMCA.Common.Domain.Tests | 1 | PiiBase | | 1 | `GatewayHealthCheckDefaults` | MMCA.Common.Gateway | 2 | GatewayActiveHealthCheckDefaults, GatewayPassiveHealthCheckDefaults | | 1 | `GatewayRoutePolicySettings` | MMCA.Common.Gateway | 1 | GatewayRoutePolicyPartition | | 1 | `ForwardedHeadersExtensionsTests` | MMCA.Common.Gateway.Tests | 1 | ForwardedHeadersExtensions | | 1 | `FakeStreamReader` | MMCA.Common.Grpc.Tests | 1 | FakeResponse | | 1 | `FakeStreamWriter` | MMCA.Common.Grpc.Tests | 1 | FakeRequest | +| 1 | `GrpcWireFormatTests` | MMCA.Common.Grpc.Tests | 1 | GrpcWireFormat | | 1 | `ResilienceCircuitBreakerFaultInjectionTests` | MMCA.Common.Grpc.Tests | 1 | CountingFailureHandler | | 1 | `AuditTrailSettings` | MMCA.Common.Infrastructure | 1 | DataSource | | 1 | `AzureNotificationHubNativePushSender` | MMCA.Common.Infrastructure | 2 | INativePushSender, NativePushPayloads | | 1 | `CacheKeyNamespace` | MMCA.Common.Infrastructure | 1 | CacheKeyPrefixOptions | | 1 | `CacheSettings` | MMCA.Common.Infrastructure | 1 | CacheOptions | | 1 | `CorrelationContext` | MMCA.Common.Infrastructure | 1 | ICorrelationContext | +| 1 | `DataSourceEngineCapabilities` | MMCA.Common.Infrastructure | 2 | MigrationPolicy, RowVersionStrategy | | 1 | `DataSourcesSettings` | MMCA.Common.Infrastructure | 1 | DataSourceEntrySettings | | 1 | `DbSeeder` | MMCA.Common.Infrastructure | 1 | IDbSeeder | | 1 | `DefaultEntityConfigurationAssemblyProvider` | MMCA.Common.Infrastructure | 2 | EntityConfigurationOptions, IEntityConfigurationAssemblyProvider | @@ -1425,6 +1472,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 1 | `EFQueryableExecutor` | MMCA.Common.Infrastructure | 1 | IQueryableExecutor | | 1 | `EventNameResolver` | MMCA.Common.Infrastructure | 1 | EventNameAttribute | | 1 | `ExplicitAssemblyProvider` | MMCA.Common.Infrastructure | 1 | IEntityConfigurationAssemblyProvider | +| 1 | `IExplicitKeyInsertDialect` | MMCA.Common.Infrastructure | 1 | ExplicitKeyInsertGroup | | 1 | `InProcessDistributedLock` | MMCA.Common.Infrastructure | 2 | IDistributedLock, InProcessLockHandle | | 1 | `InternalCommandMetrics` | MMCA.Common.Infrastructure | 1 | Measurement | | 1 | `InternalCommandNameResolver` | MMCA.Common.Infrastructure | 1 | InternalCommandNameAttribute | @@ -1442,17 +1490,16 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 1 | `PendingEntityKey` | MMCA.Common.Infrastructure | 1 | AuditTrailEntry | | 1 | `PermissionGrantRefreshService` | MMCA.Common.Infrastructure | 3 | IPermissionGrantCache, PeriodicBackgroundService, PermissionGrantSettings | | 1 | `PushNotificationSettings` | MMCA.Common.Infrastructure | 1 | NotificationScopeKey | -| 1 | `RestrictDeleteByDefaultConvention` | MMCA.Common.Infrastructure | 1 | DataSource | | 1 | `RsaJwksProvider` | MMCA.Common.Infrastructure | 2 | IJwksProvider, JwksSettings | | 1 | `SchedulerSettings` | MMCA.Common.Infrastructure | 2 | DataSource, ScheduledJobOverrideSettings | | 1 | `SensitiveDataLoggingGate` | MMCA.Common.Infrastructure | 1 | PersistenceSettings | | 1 | `SmtpTransportSecurity` | MMCA.Common.Infrastructure | 1 | SmtpSettings | -| 1 | `SoftDeleteFilterSql` | MMCA.Common.Infrastructure | 2 | DataSource, IAuditableEntity | | 1 | `SqlServerUniqueConstraintViolationDetector` | MMCA.Common.Infrastructure | 1 | IUniqueConstraintViolationDetector | | 1 | `TenantContext` | MMCA.Common.Infrastructure | 1 | ITenantContext | | 1 | `TenantEntrySettings` | MMCA.Common.Infrastructure | 1 | TenantDataSourceOverrideSettings | | 1 | `UpdatePropertySetterBuilder` | MMCA.Common.Infrastructure | 1 | IUpdatePropertySetter | | 1 | `UseDataSourceAttribute` | MMCA.Common.Infrastructure | 1 | DataSource | +| 1 | `NoTenantContext` | MMCA.Common.Infrastructure.SQLServer.Tests | 1 | ITenantContext | | 1 | `AuditedThing` | MMCA.Common.Infrastructure.Tests | 1 | IAuditedEntity | | 1 | `AuditTrailTestHarness` | MMCA.Common.Infrastructure.Tests | 1 | FakeTimeProvider | | 1 | `CacheOptionsTests` | MMCA.Common.Infrastructure.Tests | 1 | CacheOptions | @@ -1469,6 +1516,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 1 | `EntityConfigurationOptionsTests` | MMCA.Common.Infrastructure.Tests | 1 | EntityConfigurationOptions | | 1 | `EvictionSignalingMemoryCache` | MMCA.Common.Infrastructure.Tests | 2 | ManualClock, SignalingEntry | | 1 | `FakeClockLoop` | MMCA.Common.Infrastructure.Tests | 1 | FakeTimeProvider | +| 1 | `FakeContract` | MMCA.Common.Infrastructure.Tests | 1 | IFakeContract | | 1 | `FakeGrantCache` | MMCA.Common.Infrastructure.Tests | 1 | IPermissionGrantCache | | 1 | `FirstJob` | MMCA.Common.Infrastructure.Tests | 1 | IScheduledJob | | 1 | `GrantOnlyCustomSchemaContext` | MMCA.Common.Infrastructure.Tests | 1 | GrantOnlyContextBase | @@ -1499,6 +1547,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 1 | `NullAssemblyProvider` | MMCA.Common.Infrastructure.Tests | 1 | IEntityConfigurationAssemblyProvider | | 1 | `OrderedTestEvent` | MMCA.Common.Infrastructure.Tests | 1 | IDomainEvent | | 1 | `OrderPlacedConsumer` | MMCA.Common.Infrastructure.Tests | 1 | OrderPlacedTestEvent | +| 1 | `OverridingPiiThing` | MMCA.Common.Infrastructure.Tests | 2 | IAuditedEntity, PiiBaseThing | | 1 | `PersistenceSettingsTests` | MMCA.Common.Infrastructure.Tests | 1 | PersistenceSettings | | 1 | `ProfilingHelperTests` | MMCA.Common.Infrastructure.Tests | 1 | ProfilingHelper | | 1 | `RecordingInboxStore` | MMCA.Common.Infrastructure.Tests | 1 | IInboxStore | @@ -1511,6 +1560,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 1 | `StubChannelJoinAuthorizer` | MMCA.Common.Infrastructure.Tests | 1 | IChannelJoinAuthorizer | | 1 | `TenantDetail` | MMCA.Common.Infrastructure.Tests | 1 | ITenantEntity | | 1 | `TestDomainEvent` | MMCA.Common.Infrastructure.Tests | 1 | IDomainEvent | +| 1 | `LoadItemDTO` | MMCA.Common.LoadTests | 1 | IBaseDTO | | 1 | `BaseLookup` | MMCA.Common.Shared | 1 | IBaseDTO | | 1 | `ClaimsPrincipalExtensions` | MMCA.Common.Shared | 1 | AuthClaimTypes | | 1 | `DomainInvariantViolationException` | MMCA.Common.Shared | 1 | DomainException | @@ -1533,6 +1583,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 1 | `ModuleNameConventionsTests` | MMCA.Common.Shared.Tests | 4 | ModuleNameConventions, SalesFakeAggregate, SalesFakeOrder, SalesFakeUseCase | | 1 | `NotificationScopeKeyTests` | MMCA.Common.Shared.Tests | 1 | NotificationScopeKey | | 1 | `PaginationMetadataTests` | MMCA.Common.Shared.Tests | 1 | PaginationMetadata | +| 1 | `PasswordComplexityTests` | MMCA.Common.Shared.Tests | 1 | PasswordComplexity | | 1 | `SupportedCulturesTests` | MMCA.Common.Shared.Tests | 1 | SupportedCultures | | 1 | `TestValueObject` | MMCA.Common.Shared.Tests | 1 | ValueObject | | 1 | `CrossServiceFixtureBase` | MMCA.Common.Testing | 1 | CrossServiceDataSource | @@ -1541,7 +1592,6 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 1 | `InMemoryQueryableExecutor` | MMCA.Common.Testing | 1 | IQueryableExecutor | | 1 | `IntegrationTestBase` | MMCA.Common.Testing | 1 | IIntegrationTestFixture | | 1 | `RecordingHttpForwarder` | MMCA.Common.Testing | 1 | Route | -| 1 | `SqlServerIntegrationTestFixtureBase` | MMCA.Common.Testing | 1 | IIntegrationTestFixture | | 1 | `LayerRef` | MMCA.Common.Testing.Architecture | 1 | Layer | | 1 | `ProtoScope` | MMCA.Common.Testing.Architecture | 1 | ProtoScopeKind | | 1 | `AppHostEnvironmentGate` | MMCA.Common.Testing.Aspire | 3 | AppHostEnvironmentRequirement, DeveloperCertificateAvailability, DockerAvailability | @@ -1578,7 +1628,6 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 1 | `DeepLinkRouteEventArgs` | MMCA.Common.UI | 1 | Route | | 1 | `DefaultOAuthUISettings` | MMCA.Common.UI | 1 | IOAuthUISettings | | 1 | `DetailPageBase` | MMCA.Common.UI | 1 | LatestLoadGuard | -| 1 | `DirectApiTokenRefresher` | MMCA.Common.UI | 4 | AuthenticationResponse, ISecureTokenStore, ITokenRefresher, RefreshTokenRequest | | 1 | `EndpointCultureApplier` | MMCA.Common.UI | 1 | ICultureApplier | | 1 | `IAppLifecycleNotifier` | MMCA.Common.UI | 1 | AppResumedEventArgs | | 1 | `IGeocodingService` | MMCA.Common.UI | 1 | GeoPoint | @@ -1618,8 +1667,12 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 1 | `NullTextToSpeechService` | MMCA.Common.UI | 1 | ITextToSpeechService | | 1 | `OAuthFlowStateStore` | MMCA.Common.UI | 2 | ILocalCacheStore, PendingAttempt | | 1 | `OfflineFirstPageSnapshot` | MMCA.Common.UI | 3 | CachedPage, IConnectivityStatusService, ILocalCacheStore | +| 1 | `PasswordComplexityAttribute` | MMCA.Common.UI | 1 | PasswordComplexity | | 1 | `PseudoStringLocalizer` | MMCA.Common.UI | 2 | PseudoLocalizer, SupportedCultures | +| 1 | `RegisterModel` | MMCA.Common.UI | 1 | PasswordComplexity | +| 1 | `ResetPasswordModel` | MMCA.Common.UI | 1 | PasswordComplexity | | 1 | `ResxMudLocalizer` | MMCA.Common.UI | 1 | MudTranslations | +| 1 | `SameOriginProxyRequestHandler` | MMCA.Common.UI | 1 | SameOriginProxyHeaders | | 1 | `SameOriginProxyTokenRefresher` | MMCA.Common.UI | 1 | ITokenRefresher | | 1 | `ThemeService` | MMCA.Common.UI | 1 | LazyJsModule | | 1 | `TokenHydrationWarmup` | MMCA.Common.UI | 1 | ITokenStorageService | @@ -1661,9 +1714,9 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 1 | `FakeExternalLinkService` | MMCA.Common.UI.Tests | 1 | IExternalLinkService | | 1 | `FakeLocalCacheStore` | MMCA.Common.UI.Tests | 1 | ILocalCacheStore | | 1 | `FakeStringLocalizerFactory` | MMCA.Common.UI.Tests | 1 | FakeStringLocalizer | +| 1 | `InMemoryHubServer` | MMCA.Common.UI.Tests | 1 | PipePair | | 1 | `LatestLoadGuardTests` | MMCA.Common.UI.Tests | 1 | LatestLoadGuard | | 1 | `LazyJsModuleTests` | MMCA.Common.UI.Tests | 1 | LazyJsModule | -| 1 | `MmcaClientConfigBootstrapTests` | MMCA.Common.UI.Tests | 2 | MmcaClientConfigBootstrap, ScriptedHandler | | 1 | `NamedScopeProvider` | MMCA.Common.UI.Tests | 1 | INotificationScopeProvider | | 1 | `NotificationStateTests` | MMCA.Common.UI.Tests | 2 | FakeTimeProvider, NotificationState | | 1 | `RecordingCultureApplier` | MMCA.Common.UI.Tests | 1 | ICultureApplier | @@ -1680,9 +1733,12 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 1 | `WidgetDto` | MMCA.Common.UI.Tests | 1 | IBaseDTO | | 1 | `BlazorCspSettingsValidator` | MMCA.Common.UI.Web | 1 | BlazorCspSettings | | 1 | `BoundedCircuitHandler` | MMCA.Common.UI.Web | 1 | BlazorCircuitLimitSettings | +| 1 | `SameOriginApiProxySettingsValidator` | MMCA.Common.UI.Web | 1 | SameOriginApiProxySettings | +| 1 | `SessionHandoffProtector` | MMCA.Common.UI.Web | 1 | TokenPair | | 1 | `UiRateLimitingExtensions` | MMCA.Common.UI.Web | 1 | UiRateLimitingSettings | | 1 | `WebFormFactor` | MMCA.Common.UI.Web | 1 | IFormFactor | | 1 | `CapturingHandler` | MMCA.Common.UI.Web.Tests | 1 | SentRequest | +| 1 | `FakeGateway` | MMCA.Common.UI.Web.Tests | 3 | AuthenticationResponse, Jwt, SeenRequest | | 1 | `Mocks` | MMCA.Common.UI.Web.Tests | 2 | ISessionCookieSync, ITokenRefresher | | 2 | `ServiceInfoController` | MMCA.ADC.Conference.API | 2 | Route, ServiceInfoControllerBase | | 2 | `AddSessionAssetLinkCommand` | MMCA.ADC.Conference.Application | 2 | ICommandWithRequest, SessionAssetLinkRequest | @@ -1713,6 +1769,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 2 | `ActivityFormModel` | MMCA.ADC.Conference.UI | 1 | ActivityDTO | | 2 | `ConferenceCategoryItemEditModel` | MMCA.ADC.Conference.UI | 1 | CategoryItemDTO | | 2 | `PartnerFormModel` | MMCA.ADC.Conference.UI | 2 | PartnerDTO, PartnerType | +| 2 | `PublicReadAudience` | MMCA.ADC.Conference.UI | 1 | ConferenceReadAudience | | 2 | `QuestionFormModel` | MMCA.ADC.Conference.UI | 1 | QuestionDTO | | 2 | `RoomFormModel` | MMCA.ADC.Conference.UI | 1 | RoomDTO | | 2 | `SessionAssetDisplay` | MMCA.ADC.Conference.UI | 3 | SessionAssetDTO, SessionAssetKind, SessionAssetLimits | @@ -1775,9 +1832,9 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 2 | `CurrentEventNotificationScopeProvider` | MMCA.ADC.Engagement.UI | 4 | ILiveEventUIService, INotificationScopeProvider, LiveEventContext, NotificationScopeKey | | 2 | `LiveBroadcastPatch` | MMCA.ADC.Engagement.UI | 3 | LivePollResultsDTO, SessionQuestionDTO, SessionQuestionUpvoteChangedPayload | | 2 | `IdentityModule` | MMCA.ADC.Identity.API | 4 | ApplicationSettings, DisabledAttendeeQueryService, IAttendeeQueryService, IModule | +| 2 | `ChangePasswordRequestValidator` | MMCA.ADC.Identity.Application | 2 | ChangePasswordRequest, StrongPasswordRules | | 2 | `ExportUserDataQuery` | MMCA.ADC.Identity.Application | 1 | IUserOwnedRequest | | 2 | `SetUserAvatarCommandValidator` | MMCA.ADC.Identity.Application | 2 | ImageContentSniffer, SetUserAvatarCommand | -| 2 | `ChangePasswordRequestValidatorTests` | MMCA.ADC.Identity.Application.Tests | 2 | ChangePasswordRequest, ChangePasswordRequestValidator | | 2 | `LoginRequestValidatorTests` | MMCA.ADC.Identity.Application.Tests | 2 | LoginRequest, LoginRequestValidator | | 2 | `RefreshTokenRequestValidatorTests` | MMCA.ADC.Identity.Application.Tests | 2 | RefreshTokenRequest, RefreshTokenRequestValidator | | 2 | `SetUserAvatarCommandMarkerTests` | MMCA.ADC.Identity.Application.Tests | 1 | SetUserAvatarCommand | @@ -1789,12 +1846,13 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 2 | `DisabledAttendeeQueryServiceTests` | MMCA.ADC.Identity.Shared.Tests | 1 | DisabledAttendeeQueryService | | 2 | `AttendeeNotificationRecipientProviderTests` | MMCA.ADC.Notification.Application.Tests | 2 | AttendeeNotificationRecipientProvider, IAttendeeQueryService | | 2 | `DisabledUserNotificationExportService` | MMCA.ADC.Notification.Shared | 2 | IUserNotificationExportService, UserNotificationExportItemDTO | +| 2 | `LiveChannelGrpcServiceTests` | MMCA.ADC.Services.Tests | 5 | FakeServerCallContext, ILiveChannelPublisher, LiveChannelGrpcService, LivePollChannel, SessionQuestionChannel | +| 2 | `LiveChannelPublisherGrpcAdapterTests` | MMCA.ADC.Services.Tests | 2 | GrpcCalls, LiveChannelPublisherGrpcAdapter | | 2 | `AiProviderValidator` | MMCA.Common.AI | 2 | AiSettings, IAiProviderFactory | | 2 | `BoundedChatClient` | MMCA.Common.AI | 5 | AiSettings, ChatToolPolicy, IAiTokenEstimator, IChatToolPolicy, ToolAuthorization | | 2 | `ContentPolicyGuardrail` | MMCA.Common.AI | 5 | ContentPolicyInjectionMode, ContentPolicySettings, GuardrailVerdict, IChatGuardrail, IChatRequestRedactor | | 2 | `ContentPolicySettings` | MMCA.Common.AI | 2 | ContentPolicyGuardrail, ContentPolicyInjectionMode | | 2 | `GuardrailChatClient` | MMCA.Common.AI | 4 | ChatGuardrailException, GuardrailVerdict, IChatGuardrail, IChatRequestRedactor | -| 2 | `PiiRedactionGuardrail` | MMCA.Common.AI | 3 | GuardrailVerdict, IChatGuardrail, IChatRequestRedactor | | 2 | `AnthropicAiProviderFactory` | MMCA.Common.AI.Anthropic | 2 | AiSettings, IAiProviderFactory | | 2 | `OpenAiProviderFactory` | MMCA.Common.AI.OpenAI | 2 | AiSettings, IAiProviderFactory | | 2 | `EmptyCorpusHarness` | MMCA.Common.AI.Tests | 3 | GoldenReplayCase, GoldenReplayTestsBase, ReplayChatClient | @@ -1809,18 +1867,16 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 2 | `StubGuardrail` | MMCA.Common.AI.Tests | 2 | GuardrailVerdict, IChatGuardrail | | 2 | `StubToolPolicy` | MMCA.Common.AI.Tests | 2 | IChatToolPolicy, ToolAuthorization | | 2 | `UsageRecordingChatClientTests` | MMCA.Common.AI.Tests | 5 | AiUsageMeter, PromptContract, StubChatClient, UsageRecorder, UsageRecordingChatClient | -| 2 | `CookieSessionRefreshMiddleware` | MMCA.Common.API | 1 | ICookieSessionRefresher | | 2 | `IEntityControllerBase` | MMCA.Common.API | 5 | BaseLookup, CollectionResult, IBaseDTO, PagedCollectionResult, QueryFilterModelBinder | | 2 | `ModuleControllerFeatureProvider` | MMCA.Common.API | 1 | ModulesSettings | | 2 | `OidcDiscoveryEndpointExtensions` | MMCA.Common.API | 1 | JwksEndpointExtensions | | 2 | `OutputCacheOptionsExtensions` | MMCA.Common.API | 1 | PublicEndpointOutputCachePolicy | -| 2 | `SessionCookieEndpoints` | MMCA.Common.API | 4 | ICookieSessionRefresher, SessionCookieJar, SessionCookieRequest, SessionTokenResponse | -| 2 | `SessionCookieJar` | MMCA.Common.API | 1 | SessionCookieEndpoints | | 2 | `ApiParameterDescriptorBackfillProviderTests` | MMCA.Common.API.Tests | 4 | ApiParameterDescriptorBackfillProvider, OpenApiProbeHost, SegmentVersionedProbeController, UnboundRouteTokenProbeController | | 2 | `AppAssociationEndpointTests` | MMCA.Common.API.Tests | 2 | AppAssociationEndpointExtensions, AppAssociationOptions | | 2 | `AuthorizationExtensionsTests` | MMCA.Common.API.Tests | 3 | IPermissionRegistry, PermissionAuthorizationHandler, PermissionPolicyProvider | | 2 | `ExceptionHandlerTests` | MMCA.Common.API.Tests | 8 | CrossTenantWriteException, DbUpdateExceptionHandler, DomainExceptionHandler, DomainInvariantViolationException, GlobalExceptionHandler, OperationCanceledExceptionHandler, TestDomainException, ValidationExceptionHandler | | 2 | `FallbackAuthorizationTests` | MMCA.Common.API.Tests | 3 | FallbackAuthorizationHandler, FallbackAuthorizationOptions, FallbackAuthorizationRequirement | +| 2 | `HostRegistrationProbeFeatureProvider` | MMCA.Common.API.Tests | 1 | HostRegistrationProbeController | | 2 | `JwksEndpointTests` | MMCA.Common.API.Tests | 4 | IJwksProvider, JwksEndpointExtensions, JwksSettings, RsaJwksProvider | | 2 | `OpenApiBaselineTests` | MMCA.Common.API.Tests | 4 | OpenApiProbeHost, ProblemDetailsProbeController, SegmentVersionedProbeController, UnboundRouteTokenProbeController | | 2 | `PermissionPolicyProviderTests` | MMCA.Common.API.Tests | 2 | PermissionPolicyProvider, PermissionRequirement | @@ -1828,7 +1884,6 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 2 | `RateLimitingSettingsTests` | MMCA.Common.API.Tests | 2 | RateLimitAlgorithm, RateLimitingSettings | | 2 | `RedisFixedWindowRateLimiterTests` | MMCA.Common.API.Tests | 2 | FakeTimeProvider, RedisFixedWindowRateLimiter | | 2 | `StubFeatureManager` | MMCA.Common.API.Tests | 1 | PrivacyFeatures | -| 2 | `StubRefresher` | MMCA.Common.API.Tests | 2 | ICookieSessionRefresher, SessionTokenResult | | 2 | `TestChangePasswordCommand` | MMCA.Common.API.Tests | 2 | ChangePasswordRequest, IUserScopedCommand | | 2 | `TestChangePreferencesCommand` | MMCA.Common.API.Tests | 2 | ChangePreferencesRequest, IUserScopedCommand | | 2 | `TestExportQuery` | MMCA.Common.API.Tests | 1 | IUserOwnedRequest | @@ -1845,6 +1900,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 2 | `IUserDataExportSection` | MMCA.Common.Application | 1 | UserDataExportSectionResult | | 2 | `ModuleLoader` | MMCA.Common.Application | 4 | ApplicationSettings, IModule, IModuleSeeder, ModulesSettings | | 2 | `QueryCacheKeyLocks` | MMCA.Common.Application | 1 | KeyedSemaphoreStripe | +| 2 | `ResetPasswordRequestValidator` | MMCA.Common.Application | 2 | ResetPasswordRequest, StrongPasswordRules | | 2 | `SafeDomainEventHandler` | MMCA.Common.Application | 2 | BaseDomainEvent, IDomainEventHandler | | 2 | `TwoFactorErrors` | MMCA.Common.Application | 1 | Error | | 2 | `UserOwnershipRule` | MMCA.Common.Application | 2 | Error, IUserOwnedRequest | @@ -1867,7 +1923,6 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 2 | `MultiHandlerEvent` | MMCA.Common.Application.Tests | 1 | BaseDomainEvent | | 2 | `NullNotificationRecipientProviderTests` | MMCA.Common.Application.Tests | 1 | NullNotificationRecipientProvider | | 2 | `RefreshTokenRequestValidatorTests` | MMCA.Common.Application.Tests | 2 | RefreshTokenRequest, RefreshTokenRequestValidator | -| 2 | `ResetPasswordRequestValidatorTests` | MMCA.Common.Application.Tests | 2 | ResetPasswordRequest, ResetPasswordRequestValidator | | 2 | `StubTwoFactorService` | MMCA.Common.Application.Tests | 2 | ITwoFactorService, RecoveryCodeSet | | 2 | `TestChangePasswordCommand` | MMCA.Common.Application.Tests | 2 | ChangePasswordRequest, IUserScopedCommand | | 2 | `TestChangePreferencesCommand` | MMCA.Common.Application.Tests | 2 | ChangePreferencesRequest, IUserScopedCommand | @@ -1885,6 +1940,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 2 | `LeftService` | MMCA.Common.Architecture.Tests | 1 | RightModel | | 2 | `NoDomainEventDispatcher` | MMCA.Common.Architecture.Tests | 2 | IDomainEvent, IDomainEventDispatcher | | 2 | `PasswordHashingFitnessTests` | MMCA.Common.Architecture.Tests | 2 | ArchitectureAssert, PasswordHasher | +| 2 | `PasswordRuleParityTests` | MMCA.Common.Architecture.Tests | 3 | PasswordComplexityAttribute, PasswordProbe, StrongPasswordRules | | 2 | `SharedCodeErrors` | MMCA.Common.Architecture.Tests | 1 | Error | | 2 | `TicketErrors` | MMCA.Common.Architecture.Tests | 1 | Error | | 2 | `TwoBranchTicketErrors` | MMCA.Common.Architecture.Tests | 1 | Error | @@ -1910,30 +1966,27 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 2 | `SpecificationComposer` | MMCA.Common.Domain | 3 | IBaseEntity, ISpecification, ParameterReplacer | | 2 | `GuidIdEntity` | MMCA.Common.Domain.Tests | 1 | BaseEntity | | 2 | `OtherTestEntity` | MMCA.Common.Domain.Tests | 1 | BaseEntity | -| 2 | `PiiRedactorTests` | MMCA.Common.Domain.Tests | 3 | NoPii, PiiRedactor, Subject | +| 2 | `PiiRedactorTests` | MMCA.Common.Domain.Tests | 4 | NoPii, PiiOverride, PiiRedactor, Subject | | 2 | `StringIdEntity` | MMCA.Common.Domain.Tests | 1 | BaseEntity | | 2 | `TestDomainEvent` | MMCA.Common.Domain.Tests | 1 | BaseDomainEvent | | 2 | `TestDomainEvent` | MMCA.Common.Domain.Tests | 1 | BaseDomainEvent | | 2 | `GatewaySettings` | MMCA.Common.Gateway | 4 | GatewayClusterRequestProfile, GatewayHealthCheckDefaults, GatewayRoutePolicySettings, GatewayTraceHeaderSettings | | 2 | `ResultFailureException` | MMCA.Common.Grpc | 1 | Error | -| 2 | `JwtForwardingClientInterceptorTests` | MMCA.Common.Grpc.Tests | 5 | FakeRequest, FakeResponse, FakeStreamReader, FakeStreamWriter, JwtForwardingClientInterceptor | +| 2 | `JwtForwardingClientInterceptorTests` | MMCA.Common.Grpc.Tests | 6 | AuthenticateResultFeatureStub, FakeRequest, FakeResponse, FakeStreamReader, FakeStreamWriter, JwtForwardingClientInterceptor | | 2 | `ResilienceHandlerTests` | MMCA.Common.Grpc.Tests | 3 | FakeGrpcClient, GrpcResilienceDefaults, HttpResilienceDefaults | | 2 | `ResultGrpcExtensionsDecoderTests` | MMCA.Common.Grpc.Tests | 2 | Error, ErrorType | | 2 | `AggregateCapture` | MMCA.Common.Infrastructure | 2 | IAggregateRoot, IDomainEvent | | 2 | `ConnectionStringSettingsValidator` | MMCA.Common.Infrastructure | 2 | ConnectionStringSettings, DataSourcesSettings | | 2 | `FaultIntegrationEventConsumer` | MMCA.Common.Infrastructure | 2 | BrokerMetrics, IIntegrationEvent | | 2 | `IEntityDataSourceRegistry` | MMCA.Common.Infrastructure | 1 | DataSourceKey | -| 2 | `IndexBuilderExtensions` | MMCA.Common.Infrastructure | 2 | DataSource, SoftDeleteFilterSql | | 2 | `InternalCommandsSettings` | MMCA.Common.Infrastructure | 2 | DataSource, DataSourceKey | | 2 | `NotificationHub` | MMCA.Common.Infrastructure | 2 | IChannelJoinAuthorizer, PushNotificationSettings | | 2 | `NullDomainEventDispatcher` | MMCA.Common.Infrastructure | 2 | IDomainEvent, IDomainEventDispatcher | | 2 | `OutboxSettings` | MMCA.Common.Infrastructure | 2 | DataSource, DataSourceKey | -| 2 | `PhysicalDataSource` | MMCA.Common.Infrastructure | 2 | DataSource, DataSourceKey | | 2 | `QueryTags` | MMCA.Common.Infrastructure | 1 | QueryTagScope | | 2 | `RedisDistributedLock` | MMCA.Common.Infrastructure | 3 | CacheKeyNamespace, IDistributedLock, RedisLockHandle | | 2 | `SmtpEmailSender` | MMCA.Common.Infrastructure | 3 | IEmailSender, SmtpSettings, SmtpTransportSecurity | | 2 | `Snapshot` | MMCA.Common.Infrastructure | 1 | DataSourceKey | -| 2 | `SoftDeleteUniqueIndexConvention` | MMCA.Common.Infrastructure | 3 | DataSource, IAuditableEntity, SoftDeleteFilterSql | | 2 | `TenancySettings` | MMCA.Common.Infrastructure | 2 | TenantEntrySettings, TenantResolutionStrategy | | 2 | `TenantDataSourceTarget` | MMCA.Common.Infrastructure | 1 | DataSourceKey | | 2 | `TokenService` | MMCA.Common.Infrastructure | 6 | AuthClaimTypes, IPermissionRegistry, ITokenService, JwksSettings, JwtSettings, JwtSigningAlgorithm | @@ -1941,6 +1994,8 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 2 | `PgThingCreated` | MMCA.Common.Infrastructure.PostgreSQL.Tests | 1 | BaseDomainEvent | | 2 | `PgThingShipped` | MMCA.Common.Infrastructure.PostgreSQL.Tests | 2 | BaseDomainEvent, IIntegrationEvent | | 2 | `RecordingDomainEventDispatcher` | MMCA.Common.Infrastructure.PostgreSQL.Tests | 2 | IDomainEvent, IDomainEventDispatcher | +| 2 | `RecordingDomainEventDispatcher` | MMCA.Common.Infrastructure.SQLServer.Tests | 2 | IDomainEvent, IDomainEventDispatcher | +| 2 | `SqlThingShipped` | MMCA.Common.Infrastructure.SQLServer.Tests | 2 | BaseDomainEvent, IIntegrationEvent | | 2 | `ApplicationNamespaceTests` | MMCA.Common.Infrastructure.Tests | 3 | ApplicationNamespace, CacheKeyNamespace, CacheKeyPrefixOptions | | 2 | `CacheSettingsTests` | MMCA.Common.Infrastructure.Tests | 3 | CacheOptions, CacheSettings, QueryCachePipelineSettings | | 2 | `CorrelationContextTests` | MMCA.Common.Infrastructure.Tests | 1 | CorrelationContext | @@ -1978,7 +2033,9 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 2 | `TestLocalEvent` | MMCA.Common.Infrastructure.Tests | 1 | BaseDomainEvent | | 2 | `TestLocalEvent` | MMCA.Common.Infrastructure.Tests | 1 | BaseDomainEvent | | 2 | `TestOrderedEvent` | MMCA.Common.Infrastructure.Tests | 3 | BaseDomainEvent, IHasOrderingKey, IIntegrationEvent | +| 2 | `TypedServiceClientRegistrationTests` | MMCA.Common.Infrastructure.Tests | 2 | FakeContract, IFakeContract | | 2 | `UseDataSourceAttributeTests` | MMCA.Common.Infrastructure.Tests | 2 | DataSource, UseDataSourceAttribute | +| 2 | `Measured` | MMCA.Common.LoadTests | 1 | PagedCollectionResult | | 2 | `ErrorTypeSeverity` | MMCA.Common.Shared | 2 | Error, ErrorType | | 2 | `FeatureFlagRegistry` | MMCA.Common.Shared | 2 | FeatureFlagAttribute, FeatureFlagDescriptor | | 2 | `IStronglyTypedId` | MMCA.Common.Shared | 1 | StronglyTypedId | @@ -1996,6 +2053,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 2 | `OpenApiContractTestsBase` | MMCA.Common.Testing | 2 | IIntegrationTestFixture, IntegrationTestBase | | 2 | `ProblemDetailsContractTestsBase` | MMCA.Common.Testing | 2 | IIntegrationTestFixture, IntegrationTestBase | | 2 | `ServiceInfoVersioningContractTestsBase` | MMCA.Common.Testing | 2 | IIntegrationTestFixture, IntegrationTestBase | +| 2 | `SqlServerIntegrationTestFixtureBase` | MMCA.Common.Testing | 2 | CrossServiceFixtureBase, IIntegrationTestFixture | | 2 | `IArchitectureMap` | MMCA.Common.Testing.Architecture | 2 | Layer, LayerRef | | 2 | `AppHostFixtureBase` | MMCA.Common.Testing.Aspire | 4 | AppHostEnvironmentGate, AppHostEnvironmentRequirement, AppHostReadinessBudget, EphemeralRsaKeyPair | | 2 | `AppHostEnvironmentGateTests` | MMCA.Common.Testing.Aspire.Tests | 2 | AppHostEnvironmentGate, AppHostEnvironmentRequirement | @@ -2007,7 +2065,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 2 | `FakeCrossServiceFixture` | MMCA.Common.Testing.Tests | 2 | CrossServiceDataSource, CrossServiceFixtureBase | | 2 | `OverridingFixture` | MMCA.Common.Testing.Tests | 1 | ProbeFixture | | 2 | `RecordingHttpForwarderTests` | MMCA.Common.Testing.Tests | 2 | RecordingHttpForwarder, StampingTransformer | -| 2 | `BunitComponentTestBase` | MMCA.Common.Testing.UI | 5 | IsAuthenticatedAuthorizationService, ListPageQueryStateService, ListPageStateService, MudProviderHandles, MutableAuthenticationStateProvider | +| 2 | `BunitComponentTestBase` | MMCA.Common.Testing.UI | 4 | ListPageQueryStateService, ListPageStateService, MudProviderHandles, MutableAuthenticationStateProvider | | 2 | `UiHttpServiceHarness` | MMCA.Common.Testing.UI | 3 | CapturingHttpMessageHandler, FreshApiClientFactory, StubTokenStorageService | | 2 | `ApiUserPreferenceReader` | MMCA.Common.UI | 4 | ITokenStorageService, IUserPreferenceReader, JwtTokenInfo, UserPreferences | | 2 | `AppLifecycleNotifier` | MMCA.Common.UI | 2 | AppResumedEventArgs, IAppLifecycleNotifier | @@ -2019,12 +2077,16 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 2 | `BrowserLocalCacheStore` | MMCA.Common.UI | 2 | CapabilitiesJsModule, ILocalCacheStore | | 2 | `BrowserShareService` | MMCA.Common.UI | 2 | CapabilitiesJsModule, IShareService | | 2 | `ChannelSubscription` | MMCA.Common.UI | 1 | NotificationHubService | +| 2 | `DirectApiTokenRefresher` | MMCA.Common.UI | 5 | AuthDelegatingHandler, AuthenticationResponse, ISecureTokenStore, ITokenRefresher, RefreshTokenRequest | | 2 | `IDeepLinkDispatcher` | MMCA.Common.UI | 1 | DeepLinkRouteEventArgs | +| 2 | `IdempotentReadRetry` | MMCA.Common.UI | 1 | AuthenticatedServiceBase | | 2 | `IUIModule` | MMCA.Common.UI | 1 | NavItem | +| 2 | `LocalizedDataAnnotationsValidator` | MMCA.Common.UI | 2 | DataAnnotationsModelValidator, SharedResource | +| 2 | `MmcaClientConfigBootstrap` | MMCA.Common.UI | 1 | ApiSettings | | 2 | `MMCATheme` | MMCA.Common.UI | 2 | BrandColors, Error | | 2 | `ModelValidation` | MMCA.Common.UI | 2 | DataAnnotationsModelValidator, IModelValidator | | 2 | `MudToastService` | MMCA.Common.UI | 2 | IToastService, ToastSeverity | -| 2 | `NotificationHubService` | MMCA.Common.UI | 5 | ApiSettings, ChannelReferenceCounter, ChannelSubscription, ITokenStorageService, State | +| 2 | `NotificationHubService` | MMCA.Common.UI | 6 | ApiSettings, ChannelReferenceCounter, ChannelSubscription, ITokenStorageService, SameOriginProxyHeaders, State | | 2 | `NullGeocodingService` | MMCA.Common.UI | 2 | GeoPoint, IGeocodingService | | 2 | `NullGeolocationService` | MMCA.Common.UI | 2 | GeoPoint, IGeolocationService | | 2 | `NullLocalNotificationService` | MMCA.Common.UI | 2 | ILocalNotificationService, LocalNotificationRequest | @@ -2045,20 +2107,25 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 2 | `WindowLifecycleExtensions` | MMCA.Common.UI.Maui | 1 | IAppLifecycleNotifier | | 2 | `AbsoluteUrlAttributeTests` | MMCA.Common.UI.Tests | 4 | BareUrlModel, DataAnnotationsModelValidator, StubLocalizer, UrlModel | | 2 | `ApiClientRegistrationTests` | MMCA.Common.UI.Tests | 4 | ApiSettings, HttpResilienceDefaults, ITokenStorageService, StubTokenStorageService | -| 2 | `ApiUserPreferenceWriterTests` | MMCA.Common.UI.Tests | 4 | ApiUserPreferenceWriter, ITokenStorageService, StubHttpClientFactory, StubHttpMessageHandler | +| 2 | `ApiUserPreferenceWriterTests` | MMCA.Common.UI.Tests | 5 | ApiUserPreferenceWriter, ITokenStorageService, Jwt, StubHttpClientFactory, StubHttpMessageHandler | | 2 | `AuthDelegatingHandlerTests` | MMCA.Common.UI.Tests | 3 | AuthDelegatingHandler, ITokenStorageService, StubHttpMessageHandler | | 2 | `AuthenticatedServiceBaseRetryTests` | MMCA.Common.UI.Tests | 2 | AuthenticatedServiceBase, TrackingHttpResponseMessage | +| 2 | `BrowserMapNavigationServiceTests` | MMCA.Common.UI.Tests | 2 | BrowserMapNavigationService, IExternalLinkService | | 2 | `CapturingHttpMessageHandlerTests` | MMCA.Common.UI.Tests | 1 | CapturingHttpMessageHandler | | 2 | `ErrorMessagesTests` | MMCA.Common.UI.Tests | 2 | DomainInvariantViolationException, ErrorMessages | | 2 | `InvariantMudLocalizationInterceptorTests` | MMCA.Common.UI.Tests | 1 | InvariantMudLocalizationInterceptor | +| 2 | `JsFetchSessionCookieSyncTests` | MMCA.Common.UI.Tests | 1 | JsFetchSessionCookieSync | | 2 | `JwtAuthenticationStateProviderTests` | MMCA.Common.UI.Tests | 2 | ITokenStorageService, JwtAuthenticationStateProvider | | 2 | `ListPageQueryStateServiceTests` | MMCA.Common.UI.Tests | 3 | ListPageQueryStateService, ListPageState, RecordingNavigationManager | | 2 | `ListPageStateServiceTests` | MMCA.Common.UI.Tests | 2 | ListPageState, ListPageStateService | +| 2 | `MauiBackNavigationBridgeTests` | MMCA.Common.UI.Tests | 2 | BackNavigationResult, MauiBackNavigationBridge | | 2 | `Mocks` | MMCA.Common.UI.Tests | 2 | StubHttpClientFactory, StubHttpMessageHandler | | 2 | `Mocks` | MMCA.Common.UI.Tests | 5 | ISecureTokenStore, ISessionCookieSync, ITokenRefresher, StubHttpClientFactory, StubHttpMessageHandler | | 2 | `Mocks` | MMCA.Common.UI.Tests | 2 | StubHttpClientFactory, StubHttpMessageHandler | | 2 | `OAuthFlowStateStoreTests` | MMCA.Common.UI.Tests | 3 | FakeCacheStore, FakeTimeProvider, OAuthFlowStateStore | | 2 | `OfflineFirstPageSnapshotTests` | MMCA.Common.UI.Tests | 4 | FakeConnectivity, FakeLocalCacheStore, ILocalCacheStore, OfflineFirstPageSnapshot | +| 2 | `PasswordComplexityAttributeTests` | MMCA.Common.UI.Tests | 2 | PasswordComplexityAttribute, RegisterModel | +| 2 | `PolicyProbe` | MMCA.Common.UI.Tests | 2 | AuthenticatedServiceBase, ITokenStorageService | | 2 | `ProbePage` | MMCA.Common.UI.Tests | 2 | DetailPageBase, LatestLoadGuard | | 2 | `SameOriginProxyTokenRefresherTests` | MMCA.Common.UI.Tests | 1 | SameOriginProxyTokenRefresher | | 2 | `StubTokenStorageServiceTests` | MMCA.Common.UI.Tests | 1 | StubTokenStorageService | @@ -2069,7 +2136,9 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 2 | `BlazorCircuitLimitExtensions` | MMCA.Common.UI.Web | 2 | BlazorCircuitLimitSettings, BoundedCircuitHandler | | 2 | `BlazorCspPolicyProvider` | MMCA.Common.UI.Web | 5 | ApiSettings, BlazorCspSettings, BlazorCspSettingsValidator, CspPolicy, ICspPolicyProvider | | 2 | `ClientConfigBuilder` | MMCA.Common.UI.Web | 1 | ClientConfigEndpointExtensions | -| 2 | `ClientConfigEndpointExtensions` | MMCA.Common.UI.Web | 2 | ApiSettings, ClientConfigBuilder | +| 2 | `ClientConfigEndpointExtensions` | MMCA.Common.UI.Web | 4 | ApiSettings, ClientConfigBuilder, SameOriginApiProxyMarker, SameOriginApiProxySettings | +| 2 | `HandoffSessionCookieSync` | MMCA.Common.UI.Web | 2 | ISessionCookieSync, SessionHandoffProtector | +| 2 | `HandoffTokenRefresher` | MMCA.Common.UI.Web | 2 | ITokenRefresher, SessionHandoffProtector | | 2 | `BlazorCspPolicyProviderTests` | MMCA.Common.UI.Web.Tests | 4 | ApiSettings, BlazorCspSettings, CspPolicy, ICspPolicyProvider | | 2 | `TrustedCallerHandlerTests` | MMCA.Common.UI.Web.Tests | 2 | CapturingHandler, TrustedCallerHandler | | 2 | `TrustedCallerHeaderRegistrationTests` | MMCA.Common.UI.Web.Tests | 1 | CapturingHandler | @@ -2138,6 +2207,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 3 | `SpeakerFormModel` | MMCA.ADC.Conference.UI | 1 | SpeakerDTO | | 3 | `SponsorCreateModel` | MMCA.ADC.Conference.UI | 2 | SponsorDTO, SponsorFormModel | | 3 | `SponsorEditModel` | MMCA.ADC.Conference.UI | 2 | SponsorDTO, SponsorFormModel | +| 3 | `PublicReadAudienceTests` | MMCA.ADC.Conference.UI.Tests | 3 | ConferenceReadAudience, PublicReadAudience, RoleNames | | 3 | `E2ETestCollection` | MMCA.ADC.E2E.Tests | 2 | E2ETestCollection, PlaywrightFixture | | 3 | `IPointsAwarder` | MMCA.ADC.Engagement.Application | 2 | PointsActivityType, Result | | 3 | `LivePollAuthorization` | MMCA.ADC.Engagement.Application | 3 | Error, Result, SessionLiveInfo | @@ -2163,6 +2233,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 3 | `CurrentEventNotificationScopeProviderTests` | MMCA.ADC.Engagement.UI.Tests | 4 | CurrentEventNotificationScopeProvider, FakeTimeProvider, ILiveEventUIService, LiveEventContext | | 3 | `IdentityModuleTests` | MMCA.ADC.Identity.API.Tests | 2 | IdentityModule, ModuleConformanceTestsBase | | 3 | `NotificationUserDataExportSection` | MMCA.ADC.Identity.Application | 5 | IUserDataExportSection, IUserNotificationExportService, UserDataExportNotificationDTO, UserDataExportNotificationSectionDTO, UserDataExportSectionResult | +| 3 | `ChangePasswordRequestValidatorTests` | MMCA.ADC.Identity.Application.Tests | 2 | ChangePasswordRequest, ChangePasswordRequestValidator | | 3 | `ThrowingExportSection` | MMCA.ADC.Identity.Application.Tests | 2 | IUserDataExportSection, UserDataExportSectionResult | | 3 | `IdentityPermissionGrants` | MMCA.ADC.Identity.Shared | 3 | IdentityPermissions, PermissionRegistryBuilder, RoleNames | | 3 | `UserDeleted` | MMCA.ADC.Identity.Shared | 1 | BaseIntegrationEvent | @@ -2174,7 +2245,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 3 | `NotificationPermissionGrants` | MMCA.ADC.Notification.Shared | 3 | NotificationPermissions, PermissionRegistryBuilder, RoleNames | | 3 | `DeviceUIModule` | MMCA.ADC.UI | 3 | BiometricGate, IUIModule, NavItem | | 3 | `MainPage` | MMCA.ADC.UI | 1 | MainPageBase | -| 3 | `GuardrailServiceCollectionExtensions` | MMCA.Common.AI | 5 | ContentPolicyGuardrail, ContentPolicySettings, IChatGuardrail, IChatRequestRedactor, PiiRedactionGuardrail | +| 3 | `PiiRedactionGuardrail` | MMCA.Common.AI | 4 | GuardrailVerdict, IChatGuardrail, IChatRequestRedactor, Result | | 3 | `AnthropicAiServiceCollectionExtensions` | MMCA.Common.AI.Anthropic | 2 | AnthropicAiProviderFactory, IAiProviderFactory | | 3 | `OpenAiServiceCollectionExtensions` | MMCA.Common.AI.OpenAI | 2 | IAiProviderFactory, OpenAiProviderFactory | | 3 | `AdapterFactoryTests` | MMCA.Common.AI.Tests | 6 | AiSettings, AnthropicAiProviderFactory, BoundedChatClient, IAiProviderFactory, OpenAiProviderFactory, UsageRecordingChatClient | @@ -2183,30 +2254,26 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 3 | `ContentPolicyGuardrailTests` | MMCA.Common.AI.Tests | 3 | ContentPolicyGuardrail, ContentPolicyInjectionMode, ContentPolicySettings | | 3 | `ContentPolicyRegistrationTests` | MMCA.Common.AI.Tests | 7 | ContentPolicyGuardrail, ContentPolicyInjectionMode, ContentPolicySettings, GuardrailChatClient, IChatGuardrail, IChatRequestRedactor, StubChatClient | | 3 | `GuardrailChatClientTests` | MMCA.Common.AI.Tests | 5 | ChatGuardrailException, GuardrailChatClient, GuardrailVerdict, StubChatClient, StubGuardrail | -| 3 | `GuardrailRegistrationTests` | MMCA.Common.AI.Tests | 7 | AiSettings, BoundedChatClient, GuardrailChatClient, IChatGuardrail, IChatRequestRedactor, PiiRedactionGuardrail, StubChatClient | | 3 | `RecordedResponsesTests` | MMCA.Common.AI.Tests | 3 | PinHarness, PromptContract, RecordedResponses | | 3 | `ReplayChatClientTests` | MMCA.Common.AI.Tests | 3 | EmptyCorpusHarness, MissingRecordingHarness, ReplayChatClient | -| 3 | `RequestRedactionTests` | MMCA.Common.AI.Tests | 6 | GuardrailChatClient, PiiRedactionGuardrail, RecordingGuardrail, StubChatClient, SuffixRedactor, UppercaseRedactor | | 3 | `StreamedGuardrailTests` | MMCA.Common.AI.Tests | 6 | ChatGuardrailException, GuardrailChatClient, GuardrailVerdict, PreStreamingGuardrail, StubChatClient, StubGuardrail | | 3 | `ToolPolicyTests` | MMCA.Common.AI.Tests | 8 | AiSettings, BoundedChatClient, ChatToolPolicy, IChatToolPolicy, StubChatClient, StubTool, StubToolPolicy, ToolAuthorization | | 3 | `AuthorizationExtensions` | MMCA.Common.API | 9 | FallbackAuthorizationHandler, FallbackAuthorizationOptions, FallbackAuthorizationRequirement, IPermissionCatalog, IPermissionRegistry, PermissionAuthorizationHandler, PermissionPolicyProvider, PermissionRegistry, PermissionRegistryBuilder | -| 3 | `CookieSessionRefreshMiddlewareExtensions` | MMCA.Common.API | 1 | CookieSessionRefreshMiddleware | -| 3 | `CookieTokenReader` | MMCA.Common.API | 1 | SessionCookieEndpoints | | 3 | `ErrorHttpMapping` | MMCA.Common.API | 4 | Error, ErrorType, ErrorTypeSeverity, IErrorLocalizer | | 3 | `IAggregateRootEntityControllerBase` | MMCA.Common.API | 3 | IBaseDTO, ICreateRequest, IEntityControllerBase | | 3 | `ModuleHostContext` | MMCA.Common.API | 3 | ApplicationSettings, ModuleLoader, ModulesSettings | | 3 | `SignalRExtensions` | MMCA.Common.API | 2 | NotificationHub, PushNotificationSettings | | 3 | `StronglyTypedIdSchemaTransformer` | MMCA.Common.API | 1 | StronglyTypedId | | 3 | `TenantResolutionMiddleware` | MMCA.Common.API | 3 | ITenantContext, TenancySettings, TenantResolutionStrategy | +| 3 | `AddCommonOpenApiHostRegistrationTests` | MMCA.Common.API.Tests | 1 | HostRegistrationProbeFeatureProvider | | 3 | `ModuleHostExtensionsTests` | MMCA.Common.API.Tests | 3 | ApplicationSettings, ModuleLoader, ModulesSettings | | 3 | `OidcDiscoveryEndpointTests` | MMCA.Common.API.Tests | 2 | JwksEndpointExtensions, OidcDiscoveryEndpointExtensions | | 3 | `PermissionAuthorizationHandlerTests` | MMCA.Common.API.Tests | 4 | AuthClaimTypes, PermissionAuthorizationHandler, PermissionRegistryBuilder, PermissionRequirement | | 3 | `ProbeOrderId` | MMCA.Common.API.Tests | 1 | IStronglyTypedId | | 3 | `ProbeSkuId` | MMCA.Common.API.Tests | 1 | IStronglyTypedId | -| 3 | `SessionCookieEndpointsTests` | MMCA.Common.API.Tests | 6 | ICookieSessionRefresher, SessionCookieEndpoints, SessionCookieRequest, SessionTokenResponse, SessionTokenResult, StubRefresher | -| 3 | `SessionCookieJarTests` | MMCA.Common.API.Tests | 2 | SessionCookieEndpoints, SessionCookieJar | | 3 | `DomainEventDispatcher` | MMCA.Common.Application | 6 | IDomainEvent, IDomainEventDispatcher, IDomainEventHandler, IEventUpcasterRegistry, IIntegrationEvent, IIntegrationEventHandler | | 3 | `EventUpcasterRegistry` | MMCA.Common.Application | 4 | IDomainEvent, IEventUpcaster, IEventUpcasterRegistry, IIntegrationEvent | +| 3 | `IAuthSessionIssuer` | MMCA.Common.Application | 4 | AuthenticationResponse, ITokenService, RefreshSessionSummaryResponse, Result | | 3 | `ICacheService` | MMCA.Common.Application | 1 | CacheKeyLocks | | 3 | `IEmailConfirmationTokenService` | MMCA.Common.Application | 1 | Result | | 3 | `IFileStorageService` | MMCA.Common.Application | 2 | FileUploadOptions, Result | @@ -2239,7 +2306,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 3 | `DriftedMarkedCommand` | MMCA.Common.Application.Tests | 2 | ICommand, Result | | 3 | `LayeredPermissionRegistryTests` | MMCA.Common.Application.Tests | 4 | FakeGrantCache, LayeredPermissionRegistry, PermissionRegistry, PermissionRegistryBuilder | | 3 | `MappedOrderId` | MMCA.Common.Application.Tests | 1 | IStronglyTypedId | -| 3 | `ModuleLoaderTests` | MMCA.Common.Application.Tests | 10 | ApplicationSettings, FakeCycleModuleOne, FakeCycleModuleTwo, FakeModuleTracker, FakeRemoteContractRealAdapter, FakeRemoteContractStub, IFakeRemoteContract, ModuleLoader, ModuleSettings, ModulesSettings | +| 3 | `ModuleLoaderTests` | MMCA.Common.Application.Tests | 12 | ApplicationSettings, FakeCycleModuleOne, FakeCycleModuleTwo, FakeModuleAlpha, FakeModuleTracker, FakeRemoteContractRealAdapter, FakeRemoteContractStub, IFakeRemoteContract, ModuleLoader, ModuleSettings, ModulesSettings, ScanFailingAssembly | | 3 | `MultiHandlerEventHandler1` | MMCA.Common.Application.Tests | 2 | IDomainEventHandler, MultiHandlerEvent | | 3 | `MultiHandlerEventHandler2` | MMCA.Common.Application.Tests | 2 | IDomainEventHandler, MultiHandlerEvent | | 3 | `OrderId` | MMCA.Common.Application.Tests | 1 | IStronglyTypedId | @@ -2249,6 +2316,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 3 | `ProfilingQueryDecoratorTests` | MMCA.Common.Application.Tests | 5 | Error, IQueryHandler, ProfilingQueryDecorator, ProfilingTestQuery, Result | | 3 | `RecordingIntegrationHandler` | MMCA.Common.Application.Tests | 2 | IIntegrationEvent, IIntegrationEventHandler | | 3 | `RecordingSection` | MMCA.Common.Application.Tests | 2 | IUserDataExportSection, UserDataExportSectionResult | +| 3 | `ResetPasswordRequestValidatorTests` | MMCA.Common.Application.Tests | 2 | ResetPasswordRequest, ResetPasswordRequestValidator | | 3 | `RetiredEvent` | MMCA.Common.Application.Tests | 1 | BaseIntegrationEvent | | 3 | `SkuId` | MMCA.Common.Application.Tests | 2 | IStronglyTypedId, SkuId | | 3 | `SuccessorEvent` | MMCA.Common.Application.Tests | 1 | BaseIntegrationEvent | @@ -2280,6 +2348,13 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 3 | `FixtureContestedV1` | MMCA.Common.Architecture.Tests | 1 | BaseIntegrationEvent | | 3 | `FixtureContestedV2` | MMCA.Common.Architecture.Tests | 1 | BaseIntegrationEvent | | 3 | `FixtureContestedV3` | MMCA.Common.Architecture.Tests | 1 | BaseIntegrationEvent | +| 3 | `FixtureCountEvent` | MMCA.Common.Architecture.Tests | 1 | BaseIntegrationEvent | +| 3 | `FixtureLabelEvent` | MMCA.Common.Architecture.Tests | 1 | BaseIntegrationEvent | +| 3 | `FixtureNamedEvent` | MMCA.Common.Architecture.Tests | 1 | BaseIntegrationEvent | +| 3 | `FixtureNullableCountEvent` | MMCA.Common.Architecture.Tests | 1 | BaseIntegrationEvent | +| 3 | `FixtureNullableLabelEvent` | MMCA.Common.Architecture.Tests | 1 | BaseIntegrationEvent | +| 3 | `FixtureShapedBase` | MMCA.Common.Architecture.Tests | 1 | BaseIntegrationEvent | +| 3 | `FixtureTallyEvent` | MMCA.Common.Architecture.Tests | 1 | BaseIntegrationEvent | | 3 | `GetFixtureEntityHandlerBase` | MMCA.Common.Architecture.Tests | 2 | IQueryHandler, Result | | 3 | `GetFixturePreferencesHandlerBase` | MMCA.Common.Architecture.Tests | 3 | GetFixturePreferencesQuery, IQueryHandler, Result | | 3 | `GetFixtureProjectionHandler` | MMCA.Common.Architecture.Tests | 3 | GetFixtureProjectionQuery, IQueryHandler, Result | @@ -2314,7 +2389,6 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 3 | `OutputCacheEvictionRequested` | MMCA.Common.Domain | 1 | BaseIntegrationEvent | | 3 | `PermissionGrant` | MMCA.Common.Domain | 2 | Error, Result | | 3 | `QuerySpecification` | MMCA.Common.Domain | 3 | IBaseEntity, OrderExpression, Specification | -| 3 | `RefreshSession` | MMCA.Common.Domain | 2 | Error, Result | | 3 | `BaseDomainEventTests` | MMCA.Common.Domain.Tests | 1 | TestDomainEvent | | 3 | `PushNotificationCreatedTests` | MMCA.Common.Domain.Tests | 3 | BaseDomainEvent, IDomainEvent, PushNotificationCreated | | 3 | `TestEntityChangedEvent` | MMCA.Common.Domain.Tests | 3 | DomainEntityState, EntityChangedEvent, State | @@ -2328,10 +2402,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 3 | `ResultGrpcExtensions` | MMCA.Common.Grpc | 5 | Error, ErrorType, ErrorTypeSeverity, Result, ResultFailureException | | 3 | `ResultFailureExceptionTests` | MMCA.Common.Grpc.Tests | 2 | Error, ResultFailureException | | 3 | `ResultGrpcExtensionsTests` | MMCA.Common.Grpc.Tests | 5 | Error, ErrorType, ErrorTypeSeverity, Result, ResultFailureException | -| 3 | `CrossDataSourceDegradeConvention` | MMCA.Common.Infrastructure | 3 | DataSource, DataSourceKey, IEntityDataSourceRegistry | -| 3 | `DataSourceService` | MMCA.Common.Infrastructure | 4 | DataSource, DataSourceKey, IDataSourceService, IEntityDataSourceRegistry | | 3 | `EventUpcasterStartupValidator` | MMCA.Common.Infrastructure | 2 | IEventUpcasterRegistry, IIntegrationEvent | -| 3 | `IDataSourceResolver` | MMCA.Common.Infrastructure | 3 | DataSource, DataSourceKey, PhysicalDataSource | | 3 | `InProcessMessageBus` | MMCA.Common.Infrastructure | 3 | IDomainEventDispatcher, IIntegrationEvent, IMessageBus | | 3 | `NullableStronglyTypedIdValueConverter` | MMCA.Common.Infrastructure | 1 | IStronglyTypedId | | 3 | `PollingLoop` | MMCA.Common.Infrastructure | 1 | TenantDataSourceTarget | @@ -2342,7 +2413,6 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 3 | `CustomerId` | MMCA.Common.Infrastructure.Tests | 2 | CustomerId, IStronglyTypedId | | 3 | `DbSeederTests` | MMCA.Common.Infrastructure.Tests | 1 | TestableDbSeeder | | 3 | `EmptyEntityDataSourceRegistry` | MMCA.Common.Infrastructure.Tests | 2 | DataSourceKey, IEntityDataSourceRegistry | -| 3 | `ExecutionLog` | MMCA.Common.Infrastructure.Tests | 2 | RecordedExecution, Result | | 3 | `FixedSourcesRegistry` | MMCA.Common.Infrastructure.Tests | 2 | DataSourceKey, IEntityDataSourceRegistry | | 3 | `FixedSourcesRegistry` | MMCA.Common.Infrastructure.Tests | 2 | DataSourceKey, IEntityDataSourceRegistry | | 3 | `HarnessFaultingEvent` | MMCA.Common.Infrastructure.Tests | 1 | BaseIntegrationEvent | @@ -2356,7 +2426,6 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 3 | `OrderPlacedV2` | MMCA.Common.Infrastructure.Tests | 1 | BaseIntegrationEvent | | 3 | `OtherIntegrationEvent` | MMCA.Common.Infrastructure.Tests | 1 | BaseIntegrationEvent | | 3 | `OutboxSettingsTests` | MMCA.Common.Infrastructure.Tests | 2 | DataSource, OutboxSettings | -| 3 | `PhysicalDataSourceTests` | MMCA.Common.Infrastructure.Tests | 3 | DataSource, DataSourceKey, PhysicalDataSource | | 3 | `RecordingHandler` | MMCA.Common.Infrastructure.Tests | 2 | IIntegrationEvent, IIntegrationEventHandler | | 3 | `RedisDistributedLockTests` | MMCA.Common.Infrastructure.Tests | 1 | RedisDistributedLock | | 3 | `RetiredOrderPlaced` | MMCA.Common.Infrastructure.Tests | 1 | BaseIntegrationEvent | @@ -2368,13 +2437,14 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 3 | `TestIntegrationEvent` | MMCA.Common.Infrastructure.Tests | 2 | BaseIntegrationEvent, IIntegrationEvent | | 3 | `TestIntegrationEvent` | MMCA.Common.Infrastructure.Tests | 1 | BaseIntegrationEvent | | 3 | `TestIntegrationEventV2` | MMCA.Common.Infrastructure.Tests | 1 | BaseIntegrationEvent | -| 3 | `TestPhysicalDataSources` | MMCA.Common.Infrastructure.Tests | 3 | DataSource, DataSourceKey, PhysicalDataSource | | 3 | `ThrowingHandler` | MMCA.Common.Infrastructure.Tests | 2 | IIntegrationEvent, IIntegrationEventHandler | | 3 | `TokenServiceTests` | MMCA.Common.Infrastructure.Tests | 8 | AuthClaimTypes, IPermissionRegistry, JwksSettings, JwtSettings, JwtSigningAlgorithm, PermissionRegistryBuilder, RsaJwksProvider, TokenService | | 3 | `TotpTwoFactorServiceTests` | MMCA.Common.Infrastructure.Tests | 3 | RecoveryCodeSet, TotpTwoFactorService, TwoFactorSettings | | 3 | `ValidatorSampleV1` | MMCA.Common.Infrastructure.Tests | 1 | BaseIntegrationEvent | | 3 | `ValidatorSampleV2` | MMCA.Common.Infrastructure.Tests | 1 | BaseIntegrationEvent | | 3 | `ValidatorSampleV3` | MMCA.Common.Infrastructure.Tests | 1 | BaseIntegrationEvent | +| 3 | `CountingMessageBus` | MMCA.Common.LoadTests | 2 | IIntegrationEvent, IMessageBus | +| 3 | `LoadIntegrationEvent` | MMCA.Common.LoadTests | 1 | BaseIntegrationEvent | | 3 | `Address` | MMCA.Common.Shared | 4 | AddressInvariants, Result, State, ValueObject | | 3 | `AddressInvariants` | MMCA.Common.Shared | 3 | Address, Error, Result | | 3 | `Currency` | MMCA.Common.Shared | 4 | CurrencyJsonConverter, Error, Result, ValueObject | @@ -2395,6 +2465,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 3 | `CustomerId` | MMCA.Common.Shared.Tests | 2 | CustomerId, IStronglyTypedId | | 3 | `ErrorTypeSeverityTests` | MMCA.Common.Shared.Tests | 4 | Error, ErrorType, ErrorTypeSeverity, Result | | 3 | `FeatureFlagRegistryTests` | MMCA.Common.Shared.Tests | 7 | FeatureFlagAttribute, FeatureFlagLifetime, FeatureFlagRegistry, NotificationFeatures, PrivacyFeatures, ProbeFeatures, ProbeSettings | +| 3 | `LateRegisteredId` | MMCA.Common.Shared.Tests | 1 | IStronglyTypedId | | 3 | `LineId` | MMCA.Common.Shared.Tests | 2 | IStronglyTypedId, LineId | | 3 | `OrderId` | MMCA.Common.Shared.Tests | 1 | IStronglyTypedId | | 3 | `PermissionCatalogTests` | MMCA.Common.Shared.Tests | 2 | IPermissionCatalog, PermissionRegistryBuilder | @@ -2424,24 +2495,34 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 3 | `IPushNotificationUIService` | MMCA.Common.UI | 4 | PagedCollectionResult, PushNotificationDTO, Result, SendPushNotificationRequest | | 3 | `IRoleAdminUIService` | MMCA.Common.UI | 3 | PermissionCatalogResponse, Result, RolePermissionsResponse | | 3 | `IUserAdminActionsUIService` | MMCA.Common.UI | 1 | Result | -| 3 | `MobileInfiniteScrollList` | MMCA.Common.UI | 3 | IToastService, Result, SharedResource | +| 3 | `MobileInfiniteScrollList` | MMCA.Common.UI | 2 | Result, SharedResource | +| 3 | `PagedReadAll` | MMCA.Common.UI | 2 | PagedCollectionResult, Result | | 3 | `ResultUiExtensions` | MMCA.Common.UI | 6 | Error, ErrorType, ErrorTypeSeverity, IToastService, Result, ToastSeverity | | 3 | `GalleryUIModule` | MMCA.Common.UI.Gallery | 3 | IUIModule, NavItem, SharedResource | | 3 | `DependencyInjection` | MMCA.Common.UI.Maui | 48 | IAccessibilityAnnouncer, IBatteryStatusService, IBiometricAuthenticator, IClipboardService, IConnectivityStatusService, IDevicePreferences, IExternalAuthBroker, IExternalLinkService, IFormFactor, IGeocodingService, IGeolocationService, IHapticFeedbackService, ILocalCacheStore, ILocalNotificationService, IMapNavigationService, IMediaPickerService, IPublicLinkBuilder, IPushRegistrationService, IScreenshotService, ISecureTokenStore …(+28) | | 3 | `DeviceCapabilitiesInitializer` | MMCA.Common.UI.Maui | 1 | IDeepLinkDispatcher | | 3 | `BrandColorTokenTests` | MMCA.Common.UI.Tests | 3 | BrandColors, BrandColorTokenTests, MMCATheme | +| 3 | `BrowserExternalLinkServiceTests` | MMCA.Common.UI.Tests | 2 | BrowserExternalLinkService, CapabilitiesJsModule | +| 3 | `BunitComponentTestBaseAuthorizationTests` | MMCA.Common.UI.Tests | 2 | BunitComponentTestBase, TestPrincipal | | 3 | `BunitComponentTestBaseFacadeTests` | MMCA.Common.UI.Tests | 5 | BunitComponentTestBase, IAppDialogService, MudAppDialogService, MudToastService, ToastConsumer | | 3 | `BunitTestBase` | MMCA.Common.UI.Tests | 10 | AlwaysOnlineConnectivityStatusService, BunitComponentTestBase, EndpointCultureApplier, IConnectivityStatusService, ICultureApplier, IExternalAuthBroker, IPublicLinkBuilder, NavigationPublicLinkBuilder, ThemeService, UnavailableExternalAuthBroker | | 3 | `CapabilityFallbackTests` | MMCA.Common.UI.Tests | 21 | AlwaysOnlineConnectivityStatusService, GeoPoint, InMemoryDevicePreferences, LocalNotificationRequest, NullAccessibilityAnnouncer, NullBarcodeScannerService, NullBatteryStatusService, NullBiometricAuthenticator, NullClipboardService, NullExternalLinkService, NullGeocodingService, NullGeolocationService, NullHapticFeedbackService, NullLocalCacheStore, NullLocalNotificationService, NullMapNavigationService, NullScreenshotService, NullShareService, NullSpeechToTextService, NullTextToSpeechService …(+1) | | 3 | `CapturingLogger` | MMCA.Common.UI.Tests | 1 | NotificationHubService | -| 3 | `DirectApiTokenRefresherTests` | MMCA.Common.UI.Tests | 6 | AuthenticationResponse, DirectApiTokenRefresher, ISecureTokenStore, Mocks, StubHttpClientFactory, StubHttpMessageHandler | +| 3 | `DirectApiTokenRefresherTests` | MMCA.Common.UI.Tests | 7 | AuthDelegatingHandler, AuthenticationResponse, DirectApiTokenRefresher, ISecureTokenStore, Mocks, StubHttpClientFactory, StubHttpMessageHandler | +| 3 | `IdempotentReadRetryTests` | MMCA.Common.UI.Tests | 3 | IdempotentReadRetry, PolicyProbe, StubHandler | +| 3 | `MmcaClientConfigBootstrapTests` | MMCA.Common.UI.Tests | 2 | MmcaClientConfigBootstrap, ScriptedHandler | | 3 | `MudToastServiceTests` | MMCA.Common.UI.Tests | 2 | MudToastService, ToastSeverity | +| 3 | `OtherModule` | MMCA.Common.UI.Tests | 2 | IUIModule, NavItem | +| 3 | `SameOriginProxyClientTests` | MMCA.Common.UI.Tests | 7 | ApiSettings, CapturingHandler, ITokenStorageService, MmcaClientConfigBootstrap, NotificationHubService, SameOriginProxyHeaders, StubTokenStorageService | | 3 | `StubUiModule` | MMCA.Common.UI.Tests | 2 | IUIModule, NavItem | +| 3 | `TokenRefreshPipelineTests` | MMCA.Common.UI.Tests | 9 | AuthDelegatingHandler, AuthenticationResponse, DirectApiTokenRefresher, ISecureTokenStore, ISessionCookieSync, ITokenRefresher, ITokenStorageService, StubHttpMessageHandler, WasmTokenStorageService | | 3 | `UiHttpServiceHarnessTests` | MMCA.Common.UI.Tests | 1 | UiHttpServiceHarness | | 3 | `WasmTokenStorageServiceTests` | MMCA.Common.UI.Tests | 4 | ISessionCookieSync, ITokenRefresher, Mocks, WasmTokenStorageService | | 3 | `BoundedCircuitHandlerTests` | MMCA.Common.UI.Web.Tests | 3 | BlazorCircuitLimitExtensions, BlazorCircuitLimitSettings, BoundedCircuitHandler | +| 3 | `SessionHandoffServicesTests` | MMCA.Common.UI.Web.Tests | 3 | HandoffSessionCookieSync, HandoffTokenRefresher, SessionHandoffProtector | | 4 | `AdcAppHostCollection` | MMCA.ADC.AppHost.SmokeTests | 1 | AdcAppHostFixture | | 4 | `InlineStyleTests` | MMCA.ADC.Architecture.Tests | 1 | ArchitectureMapBase | +| 4 | `MobileHostParityTests` | MMCA.ADC.Architecture.Tests | 1 | ArchitectureMapBase | | 4 | `DependencyInjection` | MMCA.ADC.Conference.API | 2 | ApplicationSettings, ConferencePermissionGrants | | 4 | `ScoreEventSessionsInternalCommand` | MMCA.ADC.Conference.Application | 6 | ConferenceFeatures, ConferencePermissions, IFeatureGated, IHasTimeout, IInternalCommand, IRequiresPermission | | 4 | `SpeakerDeletedHandler` | MMCA.ADC.Conference.Application | 5 | DomainEntityState, IDomainEventHandler, IEventBus, SpeakerChanged, SpeakerUnlinkedFromUser | @@ -2453,12 +2534,12 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 4 | `SessionScoringPromptContractPinTests` | MMCA.ADC.Conference.Scoring.Evaluation.Tests | 3 | PromptContract, PromptContractPinTestsBase, SessionScoringService | | 4 | `DisabledEventLiveValidationService` | MMCA.ADC.Conference.Shared | 7 | EventLiveInfo, IEventLiveValidationService, QuestionModerationDefault, Result, RoomSessionInfo, SessionLiveInfo, SponsorLiveInfo | | 4 | `DisabledSessionBookmarkValidationService` | MMCA.ADC.Conference.Shared | 2 | ISessionBookmarkValidationService, Result | -| 4 | `CategoryItemLookupService` | MMCA.ADC.Conference.UI | 9 | CategoryItemDTO, CategoryItemInfo, CollectionResult, ConferenceCategoryDTO, HttpResultExecutor, ICategoryItemLookupService, PagedCollectionResult, ProblemDetailsResultReader, Result | +| 4 | `CategoryItemLookupService` | MMCA.ADC.Conference.UI | 11 | CategoryItemDTO, CategoryItemInfo, CollectionResult, ConferenceCategoryDTO, HttpResultExecutor, ICategoryItemLookupService, IdempotentReadRetry, PagedCollectionResult, PagedReadAll, ProblemDetailsResultReader, Result | | 4 | `ConferenceCategoryCreateModel` | MMCA.ADC.Conference.UI | 2 | ConferenceCategoryDTO, ConferenceCategoryFormModel | | 4 | `ConferenceCategoryEditModel` | MMCA.ADC.Conference.UI | 2 | ConferenceCategoryDTO, ConferenceCategoryFormModel | | 4 | `EventCreateModel` | MMCA.ADC.Conference.UI | 2 | EventDTO, EventFormModel | | 4 | `EventEditModel` | MMCA.ADC.Conference.UI | 3 | EventDTO, EventFormModel, QuestionModerationDefault | -| 4 | `EventLookupService` | MMCA.ADC.Conference.UI | 7 | EventDTO, EventInfo, HttpResultExecutor, IEventLookupService, PagedCollectionResult, ProblemDetailsResultReader, Result | +| 4 | `EventLookupService` | MMCA.ADC.Conference.UI | 9 | EventDTO, EventInfo, HttpResultExecutor, IdempotentReadRetry, IEventLookupService, PagedCollectionResult, PagedReadAll, ProblemDetailsResultReader, Result | | 4 | `IActivityUIService` | MMCA.ADC.Conference.UI | 2 | ActivityDTO, IEntityService | | 4 | `ICategoryItemUIService` | MMCA.ADC.Conference.UI | 2 | CategoryItemDTO, IEntityService | | 4 | `IConferenceCategoryUIService` | MMCA.ADC.Conference.UI | 2 | ConferenceCategoryDTO, IEntityService | @@ -2470,8 +2551,8 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 4 | `ISessionUIService` | MMCA.ADC.Conference.UI | 2 | IEntityService, SessionDTO | | 4 | `ISpeakerUIService` | MMCA.ADC.Conference.UI | 3 | IEntityService, Result, SpeakerDTO | | 4 | `ISponsorUIService` | MMCA.ADC.Conference.UI | 2 | IEntityService, SponsorDTO | -| 4 | `OrganizerEventFeedbackService` | MMCA.ADC.Conference.UI | 8 | AuthenticatedServiceBase, EventQuestionAnswerDTO, HttpResultExecutor, IOrganizerEventFeedbackUIService, ITokenStorageService, PagedCollectionResult, ProblemDetailsResultReader, Result | -| 4 | `OrganizerSessionFeedbackService` | MMCA.ADC.Conference.UI | 8 | AuthenticatedServiceBase, HttpResultExecutor, IOrganizerSessionFeedbackUIService, ITokenStorageService, PagedCollectionResult, ProblemDetailsResultReader, Result, SessionQuestionAnswerDTO | +| 4 | `OrganizerEventFeedbackService` | MMCA.ADC.Conference.UI | 9 | AuthenticatedServiceBase, EventQuestionAnswerDTO, HttpResultExecutor, IOrganizerEventFeedbackUIService, ITokenStorageService, PagedCollectionResult, PagedReadAll, ProblemDetailsResultReader, Result | +| 4 | `OrganizerSessionFeedbackService` | MMCA.ADC.Conference.UI | 9 | AuthenticatedServiceBase, HttpResultExecutor, IOrganizerSessionFeedbackUIService, ITokenStorageService, PagedCollectionResult, PagedReadAll, ProblemDetailsResultReader, Result, SessionQuestionAnswerDTO | | 4 | `ScorePollHost` | MMCA.ADC.Conference.UI | 2 | SessionSelectionDashboardDTO, ToastSeverity | | 4 | `SessionAssetService` | MMCA.ADC.Conference.UI | 12 | AuthenticatedServiceBase, ConcurrencyETag, HttpResultExecutor, IdempotencyHeaders, ISessionAssetUIService, ITokenStorageService, ProblemDetailsResultReader, Result, SessionAssetDTO, SessionAssetLimits, SessionAssetLinkRequest, SessionAssetUpdateRequest | | 4 | `SessionCreateModel` | MMCA.ADC.Conference.UI | 2 | SessionDTO, SessionFormModel | @@ -2479,9 +2560,10 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 4 | `SessionSelectionAiScores` | MMCA.ADC.Conference.UI | 3 | SessionAiScoreDTO, SessionSelectionDashboardDTO, SessionSelectionDisplay | | 4 | `SessionSelectionFilterOptions` | MMCA.ADC.Conference.UI | 2 | SessionSelectionDashboardDTO, SessionStatuses | | 4 | `SpeakerDashboardService` | MMCA.ADC.Conference.UI | 9 | AuthenticatedServiceBase, HttpResultExecutor, ISpeakerDashboardUIService, ITokenStorageService, PagedCollectionResult, ProblemDetailsResultReader, Result, SessionDTO, SessionFeedbackDTO | -| 4 | `SpeakerLookupService` | MMCA.ADC.Conference.UI | 7 | HttpResultExecutor, ISpeakerLookupService, PagedCollectionResult, ProblemDetailsResultReader, Result, SpeakerDTO, SpeakerInfo | +| 4 | `SpeakerLookupService` | MMCA.ADC.Conference.UI | 9 | HttpResultExecutor, IdempotentReadRetry, ISpeakerLookupService, PagedCollectionResult, PagedReadAll, ProblemDetailsResultReader, Result, SpeakerDTO, SpeakerInfo | | 4 | `SpeakerUserSearch` | MMCA.ADC.Conference.UI | 3 | IUserUIService, Result, UserListDTO | | 4 | `InertSessionAssetService` | MMCA.ADC.Conference.UI.Tests | 6 | Error, ISessionAssetUIService, Result, SessionAssetDTO, SessionAssetLinkRequest, SessionAssetUpdateRequest | +| 4 | `RoomCreateModelTests` | MMCA.ADC.Conference.UI.Tests | 1 | RoomCreateModel | | 4 | `AccessibilityTests` | MMCA.ADC.E2E.Tests | 42 | CheckInScanPage, ConferenceCategoryCreatePage, ConferenceCategoryListPage, E2ETestBase, E2ETestCollection, EventCreatePage, EventDetailPage, EventListPage, HappeningNowPage, MyBadgePage, MyPointsPage, OrganizerAttendancePage, OrganizerPointsOverviewPage, PartnerCreatePage, PartnerDetailPage, PartnerListPage, PlaywrightFixture, PublicEventDetailPage, PublicEventListPage, PublicSessionDetailPage …(+22) | | 4 | `AccountDeletionTests` | MMCA.ADC.E2E.Tests | 5 | E2ETestBase, E2ETestCollection, PlaywrightFixture, ProfilePage, State | | 4 | `AttendeeBookmarkTests` | MMCA.ADC.E2E.Tests | 5 | E2ETestBase, E2ETestCollection, PlaywrightFixture, PublicSessionListPage, SessionCreatePage | @@ -2528,14 +2610,14 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 4 | `EventFeedback` | MMCA.ADC.Engagement.UI | 9 | DataAnnotationsModelValidator, EventQuestionAnswerDTO, FeedbackAnswerModel, IEventFeedbackUIService, IEventLookupService, IQuestionLookupService, IToastService, ModelValidation, QuestionDTO | | 4 | `EventFeedbackService` | MMCA.ADC.Engagement.UI | 9 | AuthenticatedServiceBase, EventQuestionAnswerDTO, HttpResultExecutor, IdempotencyHeaders, IEventFeedbackUIService, ITokenStorageService, PagedCollectionResult, ProblemDetailsResultReader, Result | | 4 | `LivePollUIService` | MMCA.ADC.Engagement.UI | 12 | AuthenticatedServiceBase, CastVoteRequest, ConcurrencyETag, CreateLivePollRequest, HttpResultExecutor, IdempotencyHeaders, ILivePollUIService, ITokenStorageService, LivePollDTO, LivePollResultsDTO, ProblemDetailsResultReader, Result | -| 4 | `NowNextService` | MMCA.ADC.Engagement.UI | 5 | HttpResultExecutor, INowNextService, NowNextSnapshot, ProblemDetailsResultReader, Result | +| 4 | `NowNextService` | MMCA.ADC.Engagement.UI | 6 | HttpResultExecutor, IdempotentReadRetry, INowNextService, NowNextSnapshot, ProblemDetailsResultReader, Result | | 4 | `PointsService` | MMCA.ADC.Engagement.UI | 10 | AuthenticatedServiceBase, HttpResultExecutor, IPointsUIService, ITokenStorageService, LeaderboardEntryDTO, MyPointsDTO, PointsOverviewDTO, ProblemDetailsResultReader, Result, SetLeaderboardParticipationRequest | | 4 | `QuestionLookupService` | MMCA.ADC.Engagement.UI | 8 | AuthenticatedServiceBase, HttpResultExecutor, IQuestionLookupService, ITokenStorageService, PagedCollectionResult, ProblemDetailsResultReader, QuestionDTO, Result | | 4 | `SessionFeedbackService` | MMCA.ADC.Engagement.UI | 9 | AuthenticatedServiceBase, HttpResultExecutor, IdempotencyHeaders, ISessionFeedbackUIService, ITokenStorageService, PagedCollectionResult, ProblemDetailsResultReader, Result, SessionQuestionAnswerDTO | | 4 | `SessionLivePollPanel` | MMCA.ADC.Engagement.UI | 6 | ErrorType, IHapticFeedbackService, ILivePollUIService, IToastService, LivePollResultsDTO, Result | -| 4 | `SessionLookupService` | MMCA.ADC.Engagement.UI | 7 | HttpResultExecutor, ISessionLookupService, PagedCollectionResult, ProblemDetailsResultReader, Result, SessionDTO, SessionInfo | +| 4 | `SessionLookupService` | MMCA.ADC.Engagement.UI | 8 | HttpResultExecutor, IdempotentReadRetry, ISessionLookupService, PagedCollectionResult, ProblemDetailsResultReader, Result, SessionDTO, SessionInfo | | 4 | `SessionQuestionUIService` | MMCA.ADC.Engagement.UI | 10 | AuthenticatedServiceBase, ConcurrencyETag, HttpResultExecutor, IdempotencyHeaders, ISessionQuestionUIService, ITokenStorageService, ProblemDetailsResultReader, Result, SessionQuestionDTO, SubmitQuestionRequest | -| 4 | `SessionReminderCoordinator` | MMCA.ADC.Engagement.UI | 6 | IDevicePreferences, ILiveEventUIService, ILocalNotificationService, ISessionLookupService, LocalNotificationRequest, SessionReminderPlanner | +| 4 | `SessionReminderCoordinator` | MMCA.ADC.Engagement.UI | 7 | IDevicePreferences, ILiveEventUIService, ILocalNotificationService, ISessionLookupService, LocalNotificationRequest, SessionReminder, SessionReminderPlanner | | 4 | `HappeningNowTests` | MMCA.ADC.Engagement.UI.Tests | 19 | ApiSettings, BunitComponentTestBase, HappeningNowPage, IHapticFeedbackService, ILiveEventUIService, ILivePollUIService, INowNextService, ITokenStorageService, LiveEventContext, LivePollDTO, LivePollResultsDTO, NotificationHubService, NotificationState, NowNextSessionInfo, NowNextSnapshot, NullHapticFeedbackService, Result, RoleNames, TestPrincipal | | 4 | `LiveChannelJoinTests` | MMCA.ADC.Engagement.UI.Tests | 23 | ApiSettings, BunitComponentTestBase, HappeningNowPage, IHapticFeedbackService, ILiveEventUIService, ILivePollUIService, INowNextService, ISessionLookupService, ISessionQuestionUIService, ISpeechToTextService, ITokenStorageService, LiveEventContext, LivePollResultsDTO, NotificationHubService, NotificationState, NowNextSnapshot, NullHapticFeedbackService, NullSpeechToTextService, PresenterViewPage, Result …(+3) | | 4 | `DependencyInjection` | MMCA.ADC.Identity.API | 4 | ApplicationSettings, HttpContextExternalLoginEmailVerifier, IdentityPermissionGrants, IExternalLoginEmailVerifier | @@ -2553,21 +2635,23 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 4 | `TestSupport` | MMCA.ADC.Notification.Application.Tests | 2 | AuditableBaseEntity, BaseEntity | | 4 | `LiveChannelJoinAuthorizer` | MMCA.ADC.Notification.Service | 4 | ConferenceReadAudience, IChannelJoinAuthorizer, IEventLiveValidationService, NotificationScopeKey | | 4 | `ServiceBusEmulatorFixture` | MMCA.ADC.ServiceBusEmulator.IntegrationTests | 3 | ServiceBusEmulatorFixtureBase, SpeakerLinkedToUser, UserRegistered | +| 4 | `SelfHttpOutputCacheWarmupTaskTests` | MMCA.ADC.Services.Tests | 2 | PagedReadAll, SelfHttpOutputCacheWarmupTask | | 4 | `App` | MMCA.ADC.UI | 1 | MainPage | +| 4 | `GuardrailServiceCollectionExtensions` | MMCA.Common.AI | 5 | ContentPolicyGuardrail, ContentPolicySettings, IChatGuardrail, IChatRequestRedactor, PiiRedactionGuardrail | +| 4 | `GuardrailRegistrationTests` | MMCA.Common.AI.Tests | 7 | AiSettings, BoundedChatClient, GuardrailChatClient, IChatGuardrail, IChatRequestRedactor, PiiRedactionGuardrail, StubChatClient | +| 4 | `RequestRedactionTests` | MMCA.Common.AI.Tests | 6 | GuardrailChatClient, PiiRedactionGuardrail, RecordingGuardrail, StubChatClient, SuffixRedactor, UppercaseRedactor | | 4 | `ApiControllerBase` | MMCA.Common.API | 3 | Error, ErrorHttpMapping, IErrorLocalizer | -| 4 | `CookieSessionRefresher` | MMCA.Common.API | 8 | AuthenticationResponse, CookieTokenReader, ICookieSessionRefresher, KeyedSemaphoreStripe, RefreshTokenRequest, SessionCookieEndpoints, SessionCookieJar, SessionTokenResult | | 4 | `CurrencyJsonConverter` | MMCA.Common.API | 1 | Currency | -| 4 | `EntityCsvExporter` | MMCA.Common.API | 5 | CsvWriter, Error, PagedCollectionResult, QueryFieldService, Result | +| 4 | `EntityCsvExporter` | MMCA.Common.API | 6 | CsvWriter, Error, PagedCollectionResult, PaginationMetadata, QueryFieldService, Result | | 4 | `IdempotencyFilter` | MMCA.Common.API | 7 | ICacheService, IdempotencyHeaders, IdempotencyMetrics, IdempotencyRecord, IdempotencySettings, IDistributedLock, KeyedSemaphoreStripe | | 4 | `ModuleHostExtensions` | MMCA.Common.API | 4 | ApplicationSettings, ModuleHostContext, ModuleLoader, ModulesSettings | | 4 | `OutputCacheEvictionHandler` | MMCA.Common.API | 3 | IIntegrationEventHandler, OutputCacheEvictionRequested, OutputCacheMetrics | -| 4 | `SessionCookieAuthenticationHandler` | MMCA.Common.API | 1 | CookieTokenReader | | 4 | `StronglyTypedIdParameterTransformer` | MMCA.Common.API | 2 | StronglyTypedId, StronglyTypedIdSchemaTransformer | | 4 | `SupportsIfMatchAttribute` | MMCA.Common.API | 4 | ConcurrencyETag, Error, ErrorHttpMapping, IErrorLocalizer | | 4 | `UnhandledResultFailureFilter` | MMCA.Common.API | 4 | Error, ErrorHttpMapping, IErrorLocalizer, Result | -| 4 | `CookieSessionRefreshMiddlewareTests` | MMCA.Common.API.Tests | 5 | CookieSessionRefreshMiddleware, CookieSessionRefreshMiddlewareExtensions, ICookieSessionRefresher, NextDelegateSpy, SessionTokenResult | -| 4 | `CookieTokenReaderTests` | MMCA.Common.API.Tests | 2 | CookieTokenReader, SessionCookieEndpoints | | 4 | `CurrencyJsonConverterTests` | MMCA.Common.API.Tests | 1 | Currency | +| 4 | `DesignTimeDatabaseInitializationTests` | MMCA.Common.API.Tests | 1 | ModuleHostContext | +| 4 | `ErrorHttpMappingTests` | MMCA.Common.API.Tests | 2 | ErrorHttpMapping, ErrorType | | 4 | `ExportMoney` | MMCA.Common.API.Tests | 1 | Currency | | 4 | `ExportTestEntity` | MMCA.Common.API.Tests | 1 | AuditableBaseEntity | | 4 | `OrderProbe` | MMCA.Common.API.Tests | 2 | ProbeOrderId, ProbeSkuId | @@ -2579,13 +2663,13 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 4 | `VersionedEntity` | MMCA.Common.API.Tests | 1 | AuditableBaseEntity | | 4 | `AddressLine1Rules` | MMCA.Common.Application | 1 | AddressInvariants | | 4 | `AddressLine2Rules` | MMCA.Common.Application | 1 | AddressInvariants | -| 4 | `BeginTwoFactorEnrollmentHandlerBase` | MMCA.Common.Application | 9 | Error, ICommandHandler, ITwoFactorService, ITwoFactorStore, ITwoFactorUserState, IUserScopedRequest, Result, TwoFactorSetupResponse, UserUseCaseLog | +| 4 | `BeginTwoFactorEnrollmentHandlerBase` | MMCA.Common.Application | 8 | Error, ICommandHandler, ITwoFactorService, ITwoFactorStore, ITwoFactorUserState, IUserScopedRequest, Result, TwoFactorSetupResponse | | 4 | `CachingCommandDecorator` | MMCA.Common.Application | 6 | ICacheInvalidating, ICacheService, ICommandHandler, ITenantContext, Result, TenantCacheKey | | 4 | `CachingQueryDecorator` | MMCA.Common.Application | 12 | CqrsMetrics, ICacheService, IQueryCacheable, IQueryHandler, ITenantContext, KeyedSemaphoreStripe, QueryCacheKeyLocks, QueryCachePipelineSettings, Releaser, Result, TenantCacheKey, UserCacheKey | | 4 | `CityRules` | MMCA.Common.Application | 1 | AddressInvariants | -| 4 | `ConfirmTwoFactorEnrollmentHandlerBase` | MMCA.Common.Application | 9 | ICommandHandler, ITwoFactorService, ITwoFactorStore, IUserScopedCommand, Result, TwoFactorCodeRequest, TwoFactorErrors, TwoFactorRecoveryCodesResponse, UserUseCaseLog | +| 4 | `ConfirmTwoFactorEnrollmentHandlerBase` | MMCA.Common.Application | 8 | ICommandHandler, ITwoFactorService, ITwoFactorStore, IUserScopedCommand, Result, TwoFactorCodeRequest, TwoFactorErrors, TwoFactorRecoveryCodesResponse | | 4 | `CountryRules` | MMCA.Common.Application | 1 | AddressInvariants | -| 4 | `DisableTwoFactorHandlerBase` | MMCA.Common.Application | 9 | ICommandHandler, ITwoFactorAuthenticator, ITwoFactorStore, IUserScopedCommand, Result, TwoFactorCodeRequest, TwoFactorErrors, TwoFactorOutcome, UserUseCaseLog | +| 4 | `DisableTwoFactorHandlerBase` | MMCA.Common.Application | 8 | ICommandHandler, ITwoFactorAuthenticator, ITwoFactorStore, IUserScopedCommand, Result, TwoFactorCodeRequest, TwoFactorErrors, TwoFactorOutcome | | 4 | `FeatureGateCommandDecorator` | MMCA.Common.Application | 4 | Error, ICommandHandler, IFeatureGated, ResultFailureFactory | | 4 | `FeatureGateQueryDecorator` | MMCA.Common.Application | 4 | Error, IFeatureGated, IQueryHandler, ResultFailureFactory | | 4 | `IDeleteBlobInternalCommand` | MMCA.Common.Application | 1 | IInternalCommand | @@ -2600,9 +2684,8 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 4 | `INavigationMetadataProvider` | MMCA.Common.Application | 1 | NavigationMetadata | | 4 | `INavigationPopulator` | MMCA.Common.Application | 1 | NavigationMetadata | | 4 | `IPermissionGrantStore` | MMCA.Common.Application | 2 | PermissionGrant, Result | -| 4 | `IRefreshSessionStore` | MMCA.Common.Application | 1 | RefreshSession | | 4 | `QueryFilterService` | MMCA.Common.Application | 12 | BoolFilterStrategy, DateTimeFilterStrategy, DecimalFilterStrategy, Error, GuidFilterStrategy, IFilterStrategy, IntFilterStrategy, LongFilterStrategy, QueryFieldContract, Result, StringFilterStrategy, StronglyTypedIdFilterStrategy | -| 4 | `RegenerateRecoveryCodesHandlerBase` | MMCA.Common.Application | 11 | ICommandHandler, ITwoFactorAuthenticator, ITwoFactorService, ITwoFactorStore, IUserScopedCommand, Result, TwoFactorCodeRequest, TwoFactorErrors, TwoFactorOutcome, TwoFactorRecoveryCodesResponse, UserUseCaseLog | +| 4 | `RegenerateRecoveryCodesHandlerBase` | MMCA.Common.Application | 10 | ICommandHandler, ITwoFactorAuthenticator, ITwoFactorService, ITwoFactorStore, IUserScopedCommand, Result, TwoFactorCodeRequest, TwoFactorErrors, TwoFactorOutcome, TwoFactorRecoveryCodesResponse | | 4 | `SetRolePermissionsRequestValidator` | MMCA.Common.Application | 2 | PermissionGrant, SetRolePermissionsRequest | | 4 | `SetUserRolesRequestValidator` | MMCA.Common.Application | 2 | PermissionGrant, SetUserRolesRequest | | 4 | `SoftDeletedUserCache` | MMCA.Common.Application | 1 | ICacheService | @@ -2672,6 +2755,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 4 | `BareMap` | MMCA.Common.Architecture.Tests | 4 | ArchitectureMapBase, Layer, LayerRef, Result | | 4 | `CascadeChildFixture` | MMCA.Common.Architecture.Tests | 1 | AuditableBaseEntity | | 4 | `ConformantTests` | MMCA.Common.Architecture.Tests | 5 | AbstractAnonymousFixtureControllerBase, AnonymousEndpointTestsBase, AnonymousEndpointTestsBaseTests, AnonymousFixtureController, TypeLevelAnonymousFixtureController | +| 4 | `DataSourceBranchingFitnessTests` | MMCA.Common.Architecture.Tests | 3 | ArchitectureAssert, ArchitectureMapBase, EngineHit | | 4 | `DriftedTests` | MMCA.Common.Architecture.Tests | 2 | AnonymousEndpointTestsBase, AnonymousEndpointTestsBaseTests | | 4 | `FakeArchitectureMap` | MMCA.Common.Architecture.Tests | 2 | ArchitectureMapBase, LayerRef | | 4 | `FitnessPrincipal` | MMCA.Common.Architecture.Tests | 1 | AuditableBaseEntity | @@ -2688,7 +2772,9 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 4 | `FixtureModuleMap` | MMCA.Common.Architecture.Tests | 4 | ArchitectureMapBase, Layer, LayerRef, TicketErrors | | 4 | `FixtureModuleMap` | MMCA.Common.Architecture.Tests | 4 | ArchitectureMapBase, CreateTicketCommand, Layer, LayerRef | | 4 | `FixtureRivalClaimUpcaster` | MMCA.Common.Architecture.Tests | 3 | FixtureContestedV1, FixtureContestedV3, IEventUpcaster | +| 4 | `FixtureShapedEvent` | MMCA.Common.Architecture.Tests | 1 | FixtureShapedBase | | 4 | `ModuleConformanceTestsBaseTests` | MMCA.Common.Architecture.Tests | 2 | DriftedTests, FakeLeafModuleConformanceTests | +| 4 | `PluralSentenceResourceTests` | MMCA.Common.Architecture.Tests | 3 | ArchitectureAssert, ArchitectureMapBase, ResourceEntry | | 4 | `StaleAllowListTests` | MMCA.Common.Architecture.Tests | 2 | AnonymousEndpointTestsBase, AnonymousEndpointTestsBaseTests | | 4 | `StaleUndecoratedAllowListTests` | MMCA.Common.Architecture.Tests | 2 | AnonymousEndpointTestsBase, AnonymousEndpointTestsBaseTests | | 4 | `StrictConformantTests` | MMCA.Common.Architecture.Tests | 2 | AnonymousEndpointTestsBase, AnonymousEndpointTestsBaseTests | @@ -2697,6 +2783,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 4 | `AuditableAggregateRootEntity` | MMCA.Common.Domain | 7 | AuditableBaseEntity, Error, IAggregateRoot, IAuditableEntity, IDomainEvent, IReactivatable, Result | | 4 | `IErasableUser` | MMCA.Common.Domain | 2 | IAnonymizable, Result | | 4 | `OwnedByUserSpecification` | MMCA.Common.Domain | 2 | AuditableBaseEntity, Specification | +| 4 | `RefreshSession` | MMCA.Common.Domain | 3 | Error, IAnonymizable, Result | | 4 | `SpecificationExtensions` | MMCA.Common.Domain | 5 | AndSpecification, IBaseEntity, ISpecification, NotSpecification, OrSpecification | | 4 | `BaseIntegrationEventTests` | MMCA.Common.Domain.Tests | 2 | IIntegrationEvent, TestIntegrationEvent | | 4 | `ChildEntity` | MMCA.Common.Domain.Tests | 1 | AuditableBaseEntity | @@ -2708,7 +2795,6 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 4 | `OutputCacheEvictionRequestedTests` | MMCA.Common.Domain.Tests | 2 | IIntegrationEvent, OutputCacheEvictionRequested | | 4 | `QueryTestEntity` | MMCA.Common.Domain.Tests | 1 | AuditableBaseEntity | | 4 | `ReactivatableChildEntity` | MMCA.Common.Domain.Tests | 3 | AuditableBaseEntity, IReactivatable, Result | -| 4 | `RefreshSessionTests` | MMCA.Common.Domain.Tests | 2 | RefreshSession, Result | | 4 | `TestEntity` | MMCA.Common.Domain.Tests | 2 | AuditableBaseEntity, BaseEntity | | 4 | `TestEntity` | MMCA.Common.Domain.Tests | 1 | AuditableBaseEntity | | 4 | `UndeletableChildEntity` | MMCA.Common.Domain.Tests | 2 | AuditableBaseEntity, Result | @@ -2724,7 +2810,6 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 4 | `GrpcResultExceptionInterceptorTests` | MMCA.Common.Grpc.Tests | 4 | Error, FakeServerCallContext, GrpcResultExceptionInterceptor, ResultFailureException | | 4 | `AzureBlobFileStorageService` | MMCA.Common.Infrastructure | 4 | Error, FileUploadOptions, IFileStorageService, Result | | 4 | `AzureNotificationHubDeviceRegistrar` | MMCA.Common.Infrastructure | 5 | DeviceInstallationRequest, Error, IPushDeviceRegistrar, NativePushPayloads, Result | -| 4 | `DataSourceResolver` | MMCA.Common.Infrastructure | 8 | ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourcesSettings, DefaultSeed, IDataSourceResolver, PhysicalDataSource | | 4 | `DistributedCacheService` | MMCA.Common.Infrastructure | 5 | CacheKeyNamespace, CacheOptions, CacheSettings, ICacheService, RedisPrefixScanner | | 4 | `EnumerationValueConverter` | MMCA.Common.Infrastructure | 1 | Enumeration | | 4 | `HybridCacheService` | MMCA.Common.Infrastructure | 4 | CacheKeyNamespace, CacheSettings, ICacheService, RedisPrefixScanner | @@ -2732,19 +2817,16 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 4 | `ImageSharpImageProcessor` | MMCA.Common.Infrastructure | 3 | Error, IImageProcessor, Result | | 4 | `InternalCommandDispatcher` | MMCA.Common.Infrastructure | 3 | ICommandHandler, IInternalCommand, Result | | 4 | `InternalCommandMessage` | MMCA.Common.Infrastructure | 4 | IInternalCommand, InternalCommandNameResolver, InternalCommandOrigin, Payload | -| 4 | `MemoryCacheService` | MMCA.Common.Infrastructure | 2 | ICacheService, KeyedSemaphoreStripe | +| 4 | `MemoryCacheService` | MMCA.Common.Infrastructure | 3 | CacheSettings, ICacheService, KeyedSemaphoreStripe | | 4 | `NullableEnumerationValueConverter` | MMCA.Common.Infrastructure | 1 | Enumeration | | 4 | `NullFileStorageService` | MMCA.Common.Infrastructure | 4 | Error, FileUploadOptions, IFileStorageService, Result | | 4 | `NullPushDeviceRegistrar` | MMCA.Common.Infrastructure | 3 | DeviceInstallationRequest, IPushDeviceRegistrar, Result | | 4 | `PermissionGrantModelBuilderExtensions` | MMCA.Common.Infrastructure | 1 | PermissionGrant | -| 4 | `RefreshSessionModelBuilderExtensions` | MMCA.Common.Infrastructure | 1 | RefreshSession | | 4 | `SpecificationEvaluator` | MMCA.Common.Infrastructure | 5 | IBaseEntity, ISpecification, OrderExpression, QuerySpecification, QueryTags | -| 4 | `TenancySettingsValidator` | MMCA.Common.Infrastructure | 7 | DataSource, DataSourceKey, IDataSourceResolver, TenancySettings, TenantDataSourceOverrideSettings, TenantEntrySettings, TenantResolutionStrategy | -| 4 | `TwoFactorAuthenticator` | MMCA.Common.Infrastructure | 6 | ITwoFactorAuthenticator, ITwoFactorService, ITwoFactorStore, Result, TwoFactorErrors, TwoFactorOutcome | +| 4 | `TwoFactorAuthenticator` | MMCA.Common.Infrastructure | 8 | ICacheService, ITwoFactorAuthenticator, ITwoFactorService, ITwoFactorStore, Result, TwoFactorErrors, TwoFactorOutcome, TwoFactorSettings | | 4 | `AlreadySoftDeleteFilteredEntity` | MMCA.Common.Infrastructure.Tests | 1 | AuditableBaseEntity | | 4 | `BracketQuotedFilterEntity` | MMCA.Common.Infrastructure.Tests | 1 | AuditableBaseEntity | | 4 | `CosmosIndexedEntity` | MMCA.Common.Infrastructure.Tests | 1 | AuditableBaseEntity | -| 4 | `DefaultDataSourceResolver` | MMCA.Common.Infrastructure.Tests | 4 | DataSource, DataSourceKey, IDataSourceResolver, PhysicalDataSource | | 4 | `DependencyInjectionPushNotificationsTests` | MMCA.Common.Infrastructure.Tests | 5 | ILiveChannelPublisher, IPushNotificationSender, PushNotificationSettings, SignalRLiveChannelPublisher, SignalRPushNotificationSender | | 4 | `FakeCacheService` | MMCA.Common.Infrastructure.Tests | 1 | ICacheService | | 4 | `FakeConfirmationCacheService` | MMCA.Common.Infrastructure.Tests | 1 | ICacheService | @@ -2754,7 +2836,6 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 4 | `FakeTwoFactorStore` | MMCA.Common.Infrastructure.Tests | 6 | Error, ITwoFactorStore, ITwoFactorUserState, RecoveryCodeSet, Result, State | | 4 | `FaultIntegrationEventConsumerTests` | MMCA.Common.Infrastructure.Tests | 2 | FaultIntegrationEventConsumer, TestFaultedEvent | | 4 | `FilteredIndexEntity` | MMCA.Common.Infrastructure.Tests | 1 | AuditableBaseEntity | -| 4 | `FixedEngineResolver` | MMCA.Common.Infrastructure.Tests | 4 | DataSource, DataSourceKey, IDataSourceResolver, PhysicalDataSource | | 4 | `Parent` | MMCA.Common.Infrastructure.Tests | 2 | AuditableBaseEntity, ParentDetail | | 4 | `PlainThing` | MMCA.Common.Infrastructure.Tests | 1 | AuditableBaseEntity | | 4 | `Priority` | MMCA.Common.Infrastructure.Tests | 2 | Enumeration, Priority | @@ -2868,9 +2949,10 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 4 | `MobileCardListTests` | MMCA.Common.UI.Tests | 1 | BunitTestBase | | 4 | `MobileInfiniteScrollListTests` | MMCA.Common.UI.Tests | 4 | BunitTestBase, Error, MobileInfiniteScrollList, Result | | 4 | `NavigationPublicLinkBuilderTests` | MMCA.Common.UI.Tests | 5 | BunitTestBase, IPublicLinkBuilder, ITokenStorageService, NavigationPublicLinkBuilder, StubTokenStorageService | -| 4 | `NotificationHubServiceTests` | MMCA.Common.UI.Tests | 5 | ApiSettings, CapturingLogger, ConcurrencyTrackingTokenStorage, ITokenStorageService, NotificationHubService | +| 4 | `NotificationHubServiceTests` | MMCA.Common.UI.Tests | 6 | ApiSettings, CapturingLogger, ConcurrencyTrackingTokenStorage, InMemoryHubServer, ITokenStorageService, NotificationHubService | | 4 | `NotificationListenerTests` | MMCA.Common.UI.Tests | 8 | ApiSettings, BunitTestBase, IToastService, ITokenStorageService, NotificationHubService, NotificationState, TestPrincipal, ToastSeverity | | 4 | `OfflineBannerTests` | MMCA.Common.UI.Tests | 4 | AlwaysOnlineConnectivityStatusService, BunitTestBase, FakeConnectivityService, IConnectivityStatusService | +| 4 | `PagedReadAllTests` | MMCA.Common.UI.Tests | 5 | Error, PagedCollectionResult, PagedReadAll, PaginationMetadata, Result | | 4 | `PageStateScopeTests` | MMCA.Common.UI.Tests | 1 | BunitTestBase | | 4 | `PrimitivesSnapshotTests` | MMCA.Common.UI.Tests | 2 | BunitTestBase, MarkupSnapshot | | 4 | `PrimitivesTests` | MMCA.Common.UI.Tests | 1 | BunitTestBase | @@ -2884,7 +2966,6 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 4 | `TestGridPage` | MMCA.Common.UI.Tests | 3 | DataGridListPageBase, Result, WidgetRow | | 4 | `ThemeToggleTests` | MMCA.Common.UI.Tests | 2 | BunitTestBase, ThemeService | | 4 | `UnsavedChangesGuardTests` | MMCA.Common.UI.Tests | 1 | BunitTestBase | -| 4 | `ServerTokenStorageService` | MMCA.Common.UI.Web | 5 | CookieTokenReader, ISessionCookieSync, ITokenRefresher, ITokenStorageService, JwtTokenInfo | | 5 | `AdcAppHostSmokeTests` | MMCA.ADC.AppHost.SmokeTests | 3 | AdcAppHostCollection, AdcAppHostFixture, AppHostTestBase | | 5 | `ConferenceModule` | MMCA.ADC.Conference.API | 6 | ApplicationSettings, DisabledEventLiveValidationService, DisabledSessionBookmarkValidationService, IEventLiveValidationService, IModule, ISessionBookmarkValidationService | | 5 | `SessionCalendarController` | MMCA.ADC.Conference.API | 6 | ApiControllerBase, ConferencePermissions, ExportSessionCalendarQuery, IQueryHandler, Result, Route | @@ -2933,6 +3014,10 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 5 | `ClientEventFormatValidationTests` | MMCA.ADC.Conference.UI.Tests | 4 | EventCreateModel, EventEditModel, EventFormModel, SessionizeCodeFormat | | 5 | `OrganizerEventFeedbackServiceTests` | MMCA.ADC.Conference.UI.Tests | 7 | CapturingHttpMessageHandler, ErrorType, EventQuestionAnswerDTO, HttpTestDoubles, OrganizerEventFeedbackService, PagedCollectionResult, PaginationMetadata | | 5 | `OrganizerSessionFeedbackServiceTests` | MMCA.ADC.Conference.UI.Tests | 6 | CapturingHttpMessageHandler, HttpTestDoubles, OrganizerSessionFeedbackService, PagedCollectionResult, PaginationMetadata, SessionQuestionAnswerDTO | +| 5 | `SessionAssetServiceTests` | MMCA.ADC.Conference.UI.Tests | 4 | CapturingHttpMessageHandler, HttpTestDoubles, SessionAssetDTO, SessionAssetService | +| 5 | `SessionCreateModelTests` | MMCA.ADC.Conference.UI.Tests | 1 | SessionCreateModel | +| 5 | `SessionEditModelTests` | MMCA.ADC.Conference.UI.Tests | 2 | SessionDTO, SessionEditModel | +| 5 | `SpeakerLookupServiceTests` | MMCA.ADC.Conference.UI.Tests | 6 | CapturingHttpMessageHandler, HttpTestDoubles, PagedCollectionResult, PaginationMetadata, SpeakerDTO, SpeakerLookupService | | 5 | `AuthorizationTests` | MMCA.ADC.E2E.Tests | 2 | AuthorizationTestsBase, PlaywrightFixture | | 5 | `LogoutTests` | MMCA.ADC.E2E.Tests | 2 | LogoutTestsBase, PlaywrightFixture | | 5 | `PasswordResetTests` | MMCA.ADC.E2E.Tests | 2 | PasswordResetTestsBase, PlaywrightFixture | @@ -2973,18 +3058,16 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 5 | `IdempotentAttribute` | MMCA.Common.API | 1 | IdempotencyFilter | | 5 | `InsecureJwtMetadataWarningStartupFilter` | MMCA.Common.API | 1 | WebApplicationBuilderExtensions | | 5 | `OutputCacheEvictionExtensions` | MMCA.Common.API | 4 | BestEffort, IIntegrationEventHandler, OutputCacheEvictionHandler, OutputCacheEvictionRequested | -| 5 | `SessionCookieAuthenticationExtensions` | MMCA.Common.API | 1 | SessionCookieAuthenticationHandler | | 5 | `UsersAdminControllerBase` | MMCA.Common.API | 6 | AdministrationPermissions, ApiControllerBase, IUserAdministrationService, PagedCollectionResult, SetUserRolesRequest, UserAdministrationQuery | | 5 | `WebApplicationBuilderExtensions` | MMCA.Common.API | 9 | ApiParameterDescriptorBackfillProvider, InsecureJwtMetadataWarningStartupFilter, JwtSettings, JwtSigningAlgorithm, RateLimitAlgorithm, RateLimitingSettings, RedisFixedWindowRateLimiter, StronglyTypedIdParameterTransformer, StronglyTypedIdSchemaTransformer | +| 5 | `EntityCsvExporterTests` | MMCA.Common.API.Tests | 5 | EntityCsvExporter, ExportRow, PagedCollectionResult, PaginationMetadata, Result | | 5 | `ExportShapeTestDTO` | MMCA.Common.API.Tests | 2 | ExportMoney, IBaseDTO | | 5 | `IdempotencyFilterPassthroughTests` | MMCA.Common.API.Tests | 2 | ICacheService, IdempotencyFilter | -| 5 | `IdempotencyFilterTests` | MMCA.Common.API.Tests | 8 | AuthClaimTypes, ICacheService, IdempotencyFilter, IdempotencyRecord, IDistributedLock, NonSeekableStream, Result, TrackingHandle | +| 5 | `IdempotencyFilterTests` | MMCA.Common.API.Tests | 10 | AuthClaimTypes, ICacheService, IdempotencyFilter, IdempotencyRecord, IDistributedLock, NonSeekableStream, Result, TrackingHandle, Wrapped, WrappedAsStringConverter | | 5 | `InitTestMigratedWidget` | MMCA.Common.API.Tests | 1 | AuditableAggregateRootEntity | | 5 | `InitTestWidget` | MMCA.Common.API.Tests | 1 | AuditableAggregateRootEntity | | 5 | `RecordingLogger` | MMCA.Common.API.Tests | 1 | OutputCacheEvictionHandler | -| 5 | `RefresherHarness` | MMCA.Common.API.Tests | 3 | CookieSessionRefresher, StubHttpClientFactory, StubHttpMessageHandler | | 5 | `RoundTripController` | MMCA.Common.API.Tests | 2 | ApiControllerBase, Error | -| 5 | `SessionCookieAuthenticationHandlerTests` | MMCA.Common.API.Tests | 4 | CookieTokenReader, FakeTimeProvider, SessionCookieAuthenticationHandler, SessionCookieEndpoints | | 5 | `SupportsIfMatchAttributeTests` | MMCA.Common.API.Tests | 3 | ConcurrencyETag, Result, SupportsIfMatchAttribute | | 5 | `TestAggregateEntity` | MMCA.Common.API.Tests | 1 | AuditableAggregateRootEntity | | 5 | `TestApiController` | MMCA.Common.API.Tests | 2 | ApiControllerBase, Error | @@ -2998,10 +3081,10 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 5 | `IAuthenticationService` | MMCA.Common.Application | 7 | AuthenticationResponse, Error, LoginRequest, RefreshSessionSummaryResponse, RefreshTokenRequest, RegisterRequest, Result | | 5 | `IEntityQueryService` | MMCA.Common.Application | 7 | AuditableBaseEntity, BaseLookup, IBaseDTO, IEntityDTOMapper, ISpecification, PagedCollectionResult, Result | | 5 | `IReadRepository` | MMCA.Common.Application | 3 | AuditableBaseEntity, IEntityQuerier, IEntityReader | +| 5 | `IRefreshSessionStore` | MMCA.Common.Application | 1 | RefreshSession | | 5 | `IWriteRepository` | MMCA.Common.Application | 3 | AuditableAggregateRootEntity, IRowVersioned, IUpdatePropertySetter | | 5 | `NavigationMetadataProvider` | MMCA.Common.Application | 6 | IDataSourceService, INavigationMetadataProvider, NavigationAttribute, NavigationMetadata, NavigationPropertyInfo, NavigationType | | 5 | `NullNavigationPopulator` | MMCA.Common.Application | 2 | INavigationPopulator, NavigationMetadata | -| 5 | `RefreshSessionRevocation` | MMCA.Common.Application | 2 | IRefreshSessionStore, RefreshSession | | 5 | `UpdateEntityCommand` | MMCA.Common.Application | 4 | AuditableBaseEntity, ICacheInvalidating, ICommandWithRequest, IEntityUpdateApplier | | 5 | `BoolFilterStrategyTests` | MMCA.Common.Application.Tests | 2 | Item, QueryFilterService | | 5 | `CacheServiceGetOrCreateTests` | MMCA.Common.Application.Tests | 3 | ICacheService, KeyedSemaphoreStripe, RecordingCacheService | @@ -3013,7 +3096,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 5 | `ConfirmableAuthUser` | MMCA.Common.Application.Tests | 4 | AuditableAggregateRootEntity, IAuthUser, IEmailConfirmableUser, Result | | 5 | `ConfirmableUser` | MMCA.Common.Application.Tests | 4 | AuditableAggregateRootEntity, Error, IEmailConfirmableUser, Result | | 5 | `CqrsContractInspectorTests` | MMCA.Common.Application.Tests | 11 | AgreeingMarkedCommandHandler, AgreeingMarkedQueryHandler, CqrsContractInspector, CqrsContractMismatch, CqrsContractMismatchKind, DriftedMarkedCommand, DriftedMarkedCommandHandler, ICommandHandler, Result, UnmarkedCommandHandler, WrongKindHandler | -| 5 | `DateTimeFilterStrategyTests` | MMCA.Common.Application.Tests | 2 | Item, QueryFilterService | +| 5 | `DateTimeFilterStrategyTests` | MMCA.Common.Application.Tests | 3 | DateTimeFilterStrategy, Item, QueryFilterService | | 5 | `DecimalFilterStrategyTests` | MMCA.Common.Application.Tests | 2 | Item, QueryFilterService | | 5 | `DomainEventDispatcherAdditionalTests` | MMCA.Common.Application.Tests | 16 | DomainEventDispatcher, EventUpcasterRegistry, IDomainEventHandler, IEventUpcasterRegistry, IIntegrationEventHandler, MultiHandlerEvent, MultiHandlerEventHandler1, MultiHandlerEventHandler2, RecordingDomainHandlerForRetired, RecordingIntegrationHandler, RetiredEvent, RetiredToSuccessorUpcaster, SuccessorEvent, TestDomainEventHandlerForIntegration, TestIntegrationEvent, TestIntegrationEventHandler | | 5 | `DomainEventDispatcherTests` | MMCA.Common.Application.Tests | 8 | DomainEventDispatcher, IDomainEventHandler, IIntegrationEventHandler, TestEvent, TestEventHandler, TestIntegrationEvent, TestIntegrationEventDomainHandler, TestIntegrationEventHandler | @@ -3040,7 +3123,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 5 | `ScopedIntegrationEventHandlerBaseTests` | MMCA.Common.Application.Tests | 6 | CustomLoggingIntegrationEventHandler, RecordingLogger, ScopedProbe, TestIntegrationEvent, TestIntegrationEvent, TestScopedIntegrationEventHandler | | 5 | `SoftDeletedUserCacheTests` | MMCA.Common.Application.Tests | 2 | ICacheService, SoftDeletedUserCache | | 5 | `SpyMapper` | MMCA.Common.Application.Tests | 3 | IEntityDTOMapper, ProjectedEntity, ProjectedEntityDTO | -| 5 | `StringFilterStrategyTests` | MMCA.Common.Application.Tests | 2 | Item, QueryFilterService | +| 5 | `StringFilterStrategyTests` | MMCA.Common.Application.Tests | 3 | Item, QueryFilterService, StringFilterStrategy | | 5 | `StronglyTypedIdFilterStrategyTests` | MMCA.Common.Application.Tests | 4 | Item, OrderId, QueryFilterService, SkuId | | 5 | `SupportedChild` | MMCA.Common.Application.Tests | 2 | AuditableBaseEntity, ChildA | | 5 | `SupportedFK` | MMCA.Common.Application.Tests | 2 | AuditableBaseEntity, RelatedA | @@ -3109,11 +3192,11 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 5 | `NameStartsWithSpecification` | MMCA.Common.Domain.Tests | 2 | CompositionTestEntity, Specification | | 5 | `NegativePagingSpecification` | MMCA.Common.Domain.Tests | 2 | QuerySpecification, QueryTestEntity | | 5 | `OwnedByUserSpecificationTests` | MMCA.Common.Domain.Tests | 3 | FakeAnswer, NotSpecification, OwnedByUserSpecification | +| 5 | `RefreshSessionTests` | MMCA.Common.Domain.Tests | 2 | RefreshSession, Result | | 5 | `TestAggregate` | MMCA.Common.Domain.Tests | 5 | AuditableAggregateRootEntity, ChildEntity, ReactivatableChildEntity, Result, UndeletableChildEntity | | 5 | `ValidatingAggregate` | MMCA.Common.Domain.Tests | 2 | AuditableAggregateRootEntity, ChildEntity | | 5 | `EmailIdentity` | MMCA.Common.Infrastructure | 1 | Email | | 5 | `EmailValueConverter` | MMCA.Common.Infrastructure | 1 | Email | -| 5 | `EntityDataSourceRegistry` | MMCA.Common.Infrastructure | 10 | DataSource, DataSourceKey, IDataSourceResolver, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IEntityTypeConfigurationBase, NamespaceConventions, Snapshot, UseDatabaseAttribute, UseDataSourceAttribute | | 5 | `EntityTypeBuilderExtensions` | MMCA.Common.Infrastructure | 4 | Address, AddressInvariants, Currency, Money | | 5 | `EntityTypeConfigurationBase` | MMCA.Common.Infrastructure | 4 | AuditableAggregateRootEntity, AuditableBaseEntity, IAggregateRoot, IEntityTypeConfigurationBase | | 5 | `IEntityTypeConfigurationCosmos` | MMCA.Common.Infrastructure | 2 | AuditableBaseEntity, IEntityTypeConfigurationBase | @@ -3125,18 +3208,16 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 5 | `NullablePhoneNumberValueConverter` | MMCA.Common.Infrastructure | 1 | PhoneNumber | | 5 | `PermissionGrantCache` | MMCA.Common.Infrastructure | 4 | IPermissionGrantCache, IPermissionGrantCacheInvalidator, IPermissionGrantStore, PermissionGrantSettings | | 5 | `PhoneNumberValueConverter` | MMCA.Common.Infrastructure | 1 | PhoneNumber | +| 5 | `RefreshSessionModelBuilderExtensions` | MMCA.Common.Infrastructure | 1 | RefreshSession | | 5 | `ScopedUserOverride` | MMCA.Common.Infrastructure | 1 | Principal | | 5 | `StoredPermissionRoleAdministrationService` | MMCA.Common.Infrastructure | 12 | AdministrationPermissions, Error, IPermissionCatalog, IPermissionGrantCache, IPermissionGrantCacheInvalidator, IPermissionGrantStore, IPermissionRegistry, IRoleAdministrationService, PermissionCatalogResponse, PermissionGrantSettings, Result, RolePermissionsResponse | -| 5 | `TenantDataSourceTargets` | MMCA.Common.Infrastructure | 8 | DataSource, DataSourceKey, IDataSourceResolver, IEntityDataSourceRegistry, ITenantContext, TenancySettings, TenancySettingsValidator, TenantDataSourceTarget | | 5 | `PgThing` | MMCA.Common.Infrastructure.PostgreSQL.Tests | 3 | AuditableAggregateRootEntity, PgThingCreated, PgThingShipped | | 5 | `DistributedCacheServiceRedisTests` | MMCA.Common.Infrastructure.Redis.Tests | 1 | DistributedCacheService | -| 5 | `HybridCacheServiceRedisTests` | MMCA.Common.Infrastructure.Redis.Tests | 2 | DistributedCacheService, HybridCacheService | +| 5 | `HybridCacheServiceRedisTests` | MMCA.Common.Infrastructure.Redis.Tests | 3 | DistributedCacheService, HybridCacheService, ICacheService | +| 5 | `SqlThing` | MMCA.Common.Infrastructure.SQLServer.Tests | 2 | AuditableAggregateRootEntity, SqlThingShipped | | 5 | `AddCommonHybridCacheTests` | MMCA.Common.Infrastructure.Tests | 5 | CacheOptions, DistributedCacheService, HybridCacheService, ICacheService, MemoryCacheService | | 5 | `AzureNotificationHubDeviceRegistrarTests` | MMCA.Common.Infrastructure.Tests | 1 | AzureNotificationHubDeviceRegistrar | | 5 | `CascadingChild` | MMCA.Common.Infrastructure.Tests | 2 | AuditableBaseEntity, Parent | -| 5 | `DataSourceResolverTests` | MMCA.Common.Infrastructure.Tests | 9 | AuditTrailSettings, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourceResolver, DataSourcesSettings, OutboxSettings, SchedulerSettings | -| 5 | `DataSourceServiceAdditionalTests` | MMCA.Common.Infrastructure.Tests | 7 | DataSource, DataSourceKey, DataSourceService, FakeEntity, FakeEntity, IEntityDataSourceRegistry, UnregisteredEntity | -| 5 | `DataSourceServiceTests` | MMCA.Common.Infrastructure.Tests | 6 | DataSource, DataSourceKey, DataSourceService, FakeEntity, FakeEntity, IEntityDataSourceRegistry | | 5 | `DegradeCustomer` | MMCA.Common.Infrastructure.Tests | 2 | AuditableAggregateRootEntity, DegradeOrder | | 5 | `DegradeOrder` | MMCA.Common.Infrastructure.Tests | 2 | AuditableAggregateRootEntity, DegradeCustomer | | 5 | `DesignAlphaEntity` | MMCA.Common.Infrastructure.Tests | 1 | AuditableAggregateRootEntity | @@ -3146,6 +3227,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 5 | `EnumerationValueConverterTests` | MMCA.Common.Infrastructure.Tests | 3 | EnumerationValueConverter, NullableEnumerationValueConverter, Priority | | 5 | `EventUpcasterStartupValidatorTests` | MMCA.Common.Infrastructure.Tests | 8 | EventUpcasterRegistry, EventUpcasterStartupValidator, IEventUpcaster, IEventUpcasterRegistry, RivalV1ToV3Upcaster, SampleV1ToV2Upcaster, ValidatorSampleV1, ValidatorSampleV2 | | 5 | `ExclusionAggregate` | MMCA.Common.Infrastructure.Tests | 1 | AuditableAggregateRootEntity | +| 5 | `ExecutionLog` | MMCA.Common.Infrastructure.Tests | 3 | RecordedExecution, RecordingCommand, Result | | 5 | `FakeAggregate` | MMCA.Common.Infrastructure.Tests | 1 | AuditableAggregateRootEntity | | 5 | `FakeAggregate` | MMCA.Common.Infrastructure.Tests | 1 | AuditableAggregateRootEntity | | 5 | `FakeAggregateEntity` | MMCA.Common.Infrastructure.Tests | 1 | AuditableAggregateRootEntity | @@ -3154,17 +3236,17 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 5 | `FilteredIndexTestDbContext` | MMCA.Common.Infrastructure.Tests | 5 | CosmosIndexedEntity, DataSource, RenamedFlagEntity, SqliteIndexedEntity, SqlServerIndexedEntity | | 5 | `HandRolledOwner` | MMCA.Common.Infrastructure.Tests | 2 | Address, Money | | 5 | `HelperOwner` | MMCA.Common.Infrastructure.Tests | 2 | Address, Money | -| 5 | `HybridCacheServiceTests` | MMCA.Common.Infrastructure.Tests | 8 | CacheKeyNamespace, CacheOptions, CacheSettings, DistributedCacheService, FaultingHybridCache, HybridCacheService, RecordingDistributedCache, RecordingHybridCache | +| 5 | `HybridCacheServiceTests` | MMCA.Common.Infrastructure.Tests | 9 | CacheKeyNamespace, CacheOptions, CacheSettings, DistributedCacheService, FaultingHybridCache, HybridCacheService, ICacheService, RecordingDistributedCache, RecordingHybridCache | +| 5 | `ImageSharpImageProcessorFrameBoundTests` | MMCA.Common.Infrastructure.Tests | 3 | CountingAllocator, ImageFrameBoundCollection, ImageSharpImageProcessor | | 5 | `ImageSharpImageProcessorTests` | MMCA.Common.Infrastructure.Tests | 1 | ImageSharpImageProcessor | | 5 | `IntegrityAggregate` | MMCA.Common.Infrastructure.Tests | 1 | AuditableAggregateRootEntity | -| 5 | `MemoryCacheServiceTests` | MMCA.Common.Infrastructure.Tests | 3 | EvictionSignalingMemoryCache, KeyedSemaphoreStripe, MemoryCacheService | +| 5 | `MemoryCacheServiceTests` | MMCA.Common.Infrastructure.Tests | 5 | CacheSettings, EvictionSignalingMemoryCache, ICacheService, KeyedSemaphoreStripe, MemoryCacheService | | 5 | `MultiSourceCustomer` | MMCA.Common.Infrastructure.Tests | 1 | AuditableAggregateRootEntity | | 5 | `OptionalChild` | MMCA.Common.Infrastructure.Tests | 2 | AuditableBaseEntity, Parent | | 5 | `PermissionGrantModelBuilderExtensionsTests` | MMCA.Common.Infrastructure.Tests | 5 | GrantOnlyCustomSchemaContext, GrantOnlyPostgreSqlContext, GrantOnlySqlServerContext, PermissionGrant, PermissionGrantModelBuilderExtensions | | 5 | `PortablePrincipal` | MMCA.Common.Infrastructure.Tests | 1 | AuditableAggregateRootEntity | | 5 | `PostgresThing` | MMCA.Common.Infrastructure.Tests | 1 | AuditableAggregateRootEntity | | 5 | `RecordingCommandValidator` | MMCA.Common.Infrastructure.Tests | 1 | RecordingCommand | -| 5 | `RefreshSessionModelBuilderExtensionsTests` | MMCA.Common.Infrastructure.Tests | 4 | CustomSchemaContext, RefreshSession, RefreshSessionModelBuilderExtensions, RefreshSessionOnlyContext | | 5 | `RegistryDuplicate` | MMCA.Common.Infrastructure.Tests | 1 | AuditableAggregateRootEntity | | 5 | `RegistryInvoice` | MMCA.Common.Infrastructure.Tests | 1 | AuditableAggregateRootEntity | | 5 | `RegistryOrder` | MMCA.Common.Infrastructure.Tests | 1 | AuditableAggregateRootEntity | @@ -3180,9 +3262,10 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 5 | `TestAggregateEntity` | MMCA.Common.Infrastructure.Tests | 1 | AuditableAggregateRootEntity | | 5 | `TestEntity` | MMCA.Common.Infrastructure.Tests | 1 | AuditableAggregateRootEntity | | 5 | `TestSeedUser` | MMCA.Common.Infrastructure.Tests | 1 | AuditableAggregateRootEntity | -| 5 | `TwoFactorAuthenticatorTests` | MMCA.Common.Infrastructure.Tests | 10 | Error, ErrorType, FakeTwoFactorStore, RecoveryCodeSet, Result, TotpTwoFactorService, TwoFactorAuthenticator, TwoFactorErrors, TwoFactorOutcome, TwoFactorSettings | +| 5 | `TwoFactorAuthenticatorTests` | MMCA.Common.Infrastructure.Tests | 12 | Error, ErrorType, FakeCacheService, FakeTwoFactorStore, ICacheService, RecoveryCodeSet, Result, TotpTwoFactorService, TwoFactorAuthenticator, TwoFactorErrors, TwoFactorOutcome, TwoFactorSettings | | 5 | `WarningCountingLogger` | MMCA.Common.Infrastructure.Tests | 1 | DistributedCacheService | | 5 | `WrappedOrder` | MMCA.Common.Infrastructure.Tests | 3 | AuditableAggregateRootEntity, CustomerId, OrderId | +| 5 | `LoadItem` | MMCA.Common.LoadTests | 1 | AuditableAggregateRootEntity | | 5 | `StronglyTypedIdRegistry` | MMCA.Common.Shared | 2 | StronglyTypedId, StronglyTypedIdTypeConverters | | 5 | `Alert` | MMCA.Common.Shared.Tests | 2 | Severity, Severity | | 5 | `ClaimsPrincipalExtensionsTests` | MMCA.Common.Shared.Tests | 2 | AuthClaimTypes, Principal | @@ -3192,9 +3275,8 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 5 | `MoneyTests` | MMCA.Common.Shared.Tests | 2 | Currency, Money | | 5 | `PhoneNumberTests` | MMCA.Common.Shared.Tests | 1 | PhoneNumber | | 5 | `StronglyTypedIdSerializationTests` | MMCA.Common.Shared.Tests | 6 | CustomerId, OrderDto, OrderId, SkuId, SpeakerId, StronglyTypedIdJsonConverterFactory | -| 5 | `StronglyTypedIdTypeConverterTests` | MMCA.Common.Shared.Tests | 5 | LineId, OrderId, SkuId, StronglyTypedIdTypeConverter, StronglyTypedIdTypeConverters | +| 5 | `StronglyTypedIdTypeConverterTests` | MMCA.Common.Shared.Tests | 6 | LateRegisteredId, LineId, OrderId, SkuId, StronglyTypedIdTypeConverter, StronglyTypedIdTypeConverters | | 5 | `ValueObjectTests` | MMCA.Common.Shared.Tests | 7 | Address, Currency, DateRange, DateTimeRange, Money, TestValueObject, ValueObject | -| 5 | `InMemoryRefreshSessionStore` | MMCA.Common.Testing | 2 | IRefreshSessionStore, RefreshSession | | 5 | `AggregateConventionTestsBase` | MMCA.Common.Testing.Architecture | 2 | ArchitectureRules, IArchitectureMap | | 5 | `AiDependencyIsolationTestsBase` | MMCA.Common.Testing.Architecture | 2 | ArchitectureRules, IArchitectureMap | | 5 | `CancellationTokenConventionTestsBase` | MMCA.Common.Testing.Architecture | 2 | ArchitectureRules, IArchitectureMap | @@ -3261,10 +3343,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 5 | `RoleAdminListTests` | MMCA.Common.UI.Tests | 7 | BunitTestBase, Error, IRoleAdminUIService, Result, RoleAdminList, RolePermissionsResponse, StubLocalizer | | 5 | `RosterShell` | MMCA.Common.UI.Tests | 1 | RoleAdminList | | 5 | `WidgetService` | MMCA.Common.UI.Tests | 6 | EntityServiceBase, ITokenStorageService, IUiReadCache, PagedCollectionResult, Result, WidgetDto | -| 5 | `DependencyInjection` | MMCA.Common.UI.Web | 11 | ApiSettings, BlazorCspPolicyProvider, BlazorCspSettings, BlazorCspSettingsValidator, GatewayRateLimitingSettings, ICspPolicyProvider, IFormFactor, ITokenStorageService, ServerTokenStorageService, TrustedCallerHandler, WebFormFactor | | 5 | `ClientConfigEndpointTests` | MMCA.Common.UI.Web.Tests | 3 | ApiSettings, ClientConfigEndpointExtensions, Email | -| 5 | `ServerTokenStorageServiceTests` | MMCA.Common.UI.Web.Tests | 6 | CookieTokenReader, ISessionCookieSync, ITokenRefresher, Mocks, ServerTokenStorageService, SessionCookieEndpoints | -| 5 | `WebFormFactorTests` | MMCA.Common.UI.Web.Tests | 5 | ICspPolicyProvider, IFormFactor, ITokenStorageService, ServerTokenStorageService, WebFormFactor | | 6 | `AnonymousEndpointTests` | MMCA.ADC.Architecture.Tests | 4 | AnonymousEndpointTestsBase, ConferenceModule, EngagementModule, IdentityModule | | 6 | `FolderWidthTests` | MMCA.ADC.Architecture.Tests | 2 | ArchitectureMapBase, FolderWidthTestsBase | | 6 | `ProtoContractTests` | MMCA.ADC.Architecture.Tests | 1 | ProtoContractTestsBase | @@ -3295,14 +3374,17 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 6 | `QuestionCreate` | MMCA.ADC.Conference.UI | 9 | ConferenceRoutePaths, DataAnnotationsModelValidator, ErrorMessages, IQuestionUIService, IToastService, ModelValidation, QuestionCreateModel, QuestionService, Result | | 6 | `QuestionList` | MMCA.ADC.Conference.UI | 9 | ConferenceRoutePaths, DataGridListPageBase, ErrorMessages, IQuestionUIService, ListPageActions, MobileInfiniteScrollList, QuestionDTO, QuestionService, Result | | 6 | `RoomCreate` | MMCA.ADC.Conference.UI | 12 | ConferenceRoutePaths, DataAnnotationsModelValidator, ErrorMessages, EventInfo, EventLookupService, IEventLookupService, IRoomUIService, IToastService, ModelValidation, Result, RoomCreateModel, RoomService | -| 6 | `SessionCreate` | MMCA.ADC.Conference.UI | 15 | ConferenceRoutePaths, DataAnnotationsModelValidator, ErrorMessages, EventInfo, EventLookupService, IEventLookupService, IRoomUIService, ISessionUIService, IToastService, ModelValidation, Result, RoomDTO, RoomService, SessionCreateModel, SessionService | +| 6 | `SessionCreate` | MMCA.ADC.Conference.UI | 16 | ConferenceRoutePaths, DataAnnotationsModelValidator, ErrorMessages, EventInfo, EventLookupService, IEventLookupService, IRoomUIService, ISessionUIService, IToastService, ModelValidation, Result, RoomDTO, RoomService, SessionCreateModel, SessionFormModel, SessionService | | 6 | `SpeakerCategoryItemsPanel` | MMCA.ADC.Conference.UI | 6 | CategoryItemInfo, ISpeakerCategoryItemUIService, IToastService, SpeakerCategoryItemDTO, SpeakerCategoryItemService, SpeakerDTO | | 6 | `SpeakerCreate` | MMCA.ADC.Conference.UI | 9 | ConferenceRoutePaths, DataAnnotationsModelValidator, ErrorMessages, ISpeakerUIService, IToastService, ModelValidation, Result, SpeakerCreateModel, SpeakerService | | 6 | `SpeakerDetail` | MMCA.ADC.Conference.UI | 18 | ConferenceRoutePaths, DataAnnotationsModelValidator, ErrorMessages, ISessionUIService, ISpeakerDetailLookupService, ISpeakerUIService, IToastService, IUserUIService, ModelValidation, SessionDTO, SessionService, SpeakerDetailLookups, SpeakerDTO, SpeakerEditModel, SpeakerService, SpeakerUserSearch, UserListDTO, UserService | | 6 | `BunitTestBase` | MMCA.ADC.Conference.UI.Tests | 8 | ApiSettings, BunitComponentTestBase, InertSessionAssetService, IPublicLinkBuilder, IPublicSessionScheduleService, ISessionAssetUIService, NavigationPublicLinkBuilder, PublicSessionScheduleService | | 6 | `ClientUrlValidationTests` | MMCA.ADC.Conference.UI.Tests | 9 | ActivityCreateModel, ActivityEditModel, EventCreateModel, EventEditModel, PartnerCreateModel, PartnerEditModel, SpeakerCreateModel, SpeakerEditModel, SponsorCreateModel | | 6 | `EventServiceTests` | MMCA.ADC.Conference.UI.Tests | 5 | CapturingHttpMessageHandler, ErrorType, EventService, HttpTestDoubles, RefreshFromSessionizeResultDTO | +| 6 | `PublicSessionScheduleServiceTests` | MMCA.ADC.Conference.UI.Tests | 6 | IConnectivityStatusService, ISessionUIService, PublicSessionScheduleService, RecordingCacheStore, SessionDTO, SessionSchedulePageRequest | +| 6 | `RoomServiceTests` | MMCA.ADC.Conference.UI.Tests | 4 | CapturingHttpMessageHandler, HttpTestDoubles, RoomDTO, RoomService | | 6 | `SessionSelectionServiceTests` | MMCA.ADC.Conference.UI.Tests | 9 | CapturingHttpMessageHandler, CategoryDistributionDTO, ErrorType, HttpTestDoubles, ScoreEventSessionsResultDTO, SessionSelectionDashboardDTO, SessionSelectionService, SpeakerLocalitySummary, SpeakerSessionOverlapDTO | +| 6 | `SpeakerEditModelTests` | MMCA.ADC.Conference.UI.Tests | 2 | SpeakerDTO, SpeakerEditModel | | 6 | `AttendeeBadgeInvariants` | MMCA.ADC.Engagement.Domain | 2 | CommonInvariants, Result | | 6 | `CheckInInvariants` | MMCA.ADC.Engagement.Domain | 4 | CheckInScope, CommonInvariants, Error, Result | | 6 | `LeaderboardOptIn` | MMCA.ADC.Engagement.Domain | 5 | AuditableAggregateRootEntity, DomainEntityState, LeaderboardOptInChanged, LeaderboardOptInInvariants, Result | @@ -3341,7 +3423,6 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 6 | `EntityControllerBase` | MMCA.Common.API | 15 | ApiControllerBase, ApplicationSettings, AuditableBaseEntity, BaseLookup, CollectionResult, ConcurrencyETag, EntityCsvExporter, Error, IBaseDTO, IEntityControllerBase, IEntityQueryService, PagedCollectionResult, QueryFilterModelBinder, Route, Specification | | 6 | `OAuthControllerBase` | MMCA.Common.API | 6 | AuthenticationResponse, Error, ExternalAuthExtensions, IAuthenticationService, ICacheService, OAuthCodeExchangeRequest | | 6 | `ApiControllerBaseTests` | MMCA.Common.API.Tests | 3 | Error, Result, TestApiController | -| 6 | `CookieSessionRefresherTests` | MMCA.Common.API.Tests | 7 | AuthenticationResponse, CookieSessionRefresher, CookieTokenReader, KeyedSemaphoreStripe, RefresherHarness, SessionCookieEndpoints, SessionTokenResult | | 6 | `EdgeErrorLocalizationTests` | MMCA.Common.API.Tests | 4 | Error, IErrorLocalizer, StubErrorLocalizer, TestController | | 6 | `ForwardedJwtBearerSecurityTests` | MMCA.Common.API.Tests | 2 | StubHostEnvironment, WebApplicationBuilderExtensions | | 6 | `Mocks` | MMCA.Common.API.Tests | 2 | IAuthenticationService, ICacheService | @@ -3349,16 +3430,18 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 6 | `ProbeControllerFeatureProvider` | MMCA.Common.API.Tests | 1 | WrappedIdProbeController | | 6 | `ProblemDetailsRoundTripTests` | MMCA.Common.API.Tests | 6 | Error, ErrorType, ErrorTypeSeverity, ProblemDetailsResultReader, Result, RoundTripController | | 6 | `RateLimitAlgorithmSelectionTests` | MMCA.Common.API.Tests | 4 | RateLimitAlgorithm, RateLimitingSettings, RedisFixedWindowRateLimiter, WebApplicationBuilderExtensions | -| 6 | `RateLimitPartitionTests` | MMCA.Common.API.Tests | 2 | AuthClaimTypes, WebApplicationBuilderExtensions | +| 6 | `RateLimitPartitionTests` | MMCA.Common.API.Tests | 3 | AuthClaimTypes, RateLimitingSettings, WebApplicationBuilderExtensions | | 6 | `RecordingQueryService` | MMCA.Common.API.Tests | 10 | BaseLookup, Error, IEntityDTOMapper, IEntityQueryService, ISpecification, PagedCollectionResult, PaginationMetadata, ReadScopeDTO, ReadScopeEntity, Result | | 6 | `SpecificationHonoringQueryService` | MMCA.Common.API.Tests | 9 | BaseLookup, ExportTestDTO, ExportTestEntity, IEntityDTOMapper, IEntityQueryService, ISpecification, PagedCollectionResult, PaginationMetadata, Result | | 6 | `TestUsersAdminController` | MMCA.Common.API.Tests | 3 | IUserAdministrationService, TestUserDto, UsersAdminControllerBase | | 6 | `WebApplicationBuilderExtensionsTests` | MMCA.Common.API.Tests | 1 | WebApplicationBuilderExtensions | | 6 | `AbsoluteUrlRules` | MMCA.Common.Application | 1 | CommonInvariants | +| 6 | `AuthSessionIssuer` | MMCA.Common.Application | 12 | AuthenticationResponse, Error, IAuthenticationService, IAuthSessionIssuer, IRefreshSessionStore, IssuedSession, ITokenService, RefreshSession, RefreshSessionSettings, RefreshSessionSummaryResponse, Result, SessionStampingTokenService | | 6 | `IEntityUpdateCommandApplier` | MMCA.Common.Application | 4 | AuditableBaseEntity, MutationContext, Result, UpdateEntityCommand | | 6 | `IRepository` | MMCA.Common.Application | 3 | AuditableAggregateRootEntity, IReadRepository, IWriteRepository | | 6 | `NavigationLoader` | MMCA.Common.Application | 2 | AuditableBaseEntity, IReadRepository | | 6 | `ReadRepositoryExtensions` | MMCA.Common.Application | 4 | AuditableBaseEntity, Error, IReadRepository, Result | +| 6 | `RefreshSessionRevocation` | MMCA.Common.Application | 2 | IRefreshSessionStore, RefreshSession | | 6 | `AddressValidationRulesTests` | MMCA.Common.Application.Tests | 11 | Address, AddressInvariants, AddressLine1Rules, AddressLine2Rules, AddressValidator, CityRules, CountryRules, State, StateRules, TestAddressModel, ZipCodeRules | | 6 | `AuthenticationValidatorsTests` | MMCA.Common.Application.Tests | 4 | AuthenticationValidators, LoginRequest, RefreshTokenRequest, RegisterRequest | | 6 | `CachingCommandDecoratorTests` | MMCA.Common.Application.Tests | 10 | CacheInvalidatingTestCommand, CachingCommandDecorator, CachingTestEntity, DeleteEntityCommand, Error, ICacheService, ICommandHandler, OptedOutCacheInvalidatingTestCommand, PlainTestCommand, Result | @@ -3372,6 +3455,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 6 | `NavigationMetadataProviderTests` | MMCA.Common.Application.Tests | 12 | ChildD, IDataSourceService, MixedEntity, NavigationMetadata, NavigationMetadataProvider, NavigationType, NoNavEntity, ReadOnlyCollectionEntity, SupportedChild, SupportedFK, UnsupportedChild, UnsupportedFK | | 6 | `NullNavigationPopulatorTests` | MMCA.Common.Application.Tests | 4 | INavigationPopulator, NavigationMetadata, NullNavigationPopulator, StubEntity | | 6 | `ReadRepositoryExtensionsTests` | MMCA.Common.Application.Tests | 4 | ErrorType, IReadRepository, Result, TestReadEntity | +| 6 | `RecordingSetter` | MMCA.Common.Application.Tests | 2 | IUpdatePropertySetter, UserNotification | | 6 | `RenameOrderByOwnerCommand` | MMCA.Common.Application.Tests | 3 | OrderAggregate, OrderUpdateRequest, UpdateEntityCommand | | 6 | `StronglyTypedIdMapperTests` | MMCA.Common.Application.Tests | 5 | MappedOrder, MappedOrderId, OrderPrimitiveDTO, PrimitiveMapper, WrapperMapper | | 6 | `TestDeleteBlobHandler` | MMCA.Common.Application.Tests | 3 | DeleteBlobInternalCommandHandlerBase, IFileStorageService, TestDeleteBlobCommand | @@ -3383,7 +3467,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 6 | `FixtureMap` | MMCA.Common.Architecture.Tests | 4 | ArchitectureMapBase, IntegrationEventContractTestsBaseTests, Layer, LayerRef | | 6 | `FolderWidthTests` | MMCA.Common.Architecture.Tests | 2 | ArchitectureMapBase, FolderWidthTestsBase | | 6 | `FrameworkProbeMap` | MMCA.Common.Architecture.Tests | 4 | ArchitectureMapBase, FixtureLeakingEvent, Layer, LayerRef | -| 6 | `IntegrationEventContractTestsBaseTests` | MMCA.Common.Architecture.Tests | 3 | ArchitectureRules, FixtureMap, ProbeTests | +| 6 | `IntegrationEventContractTestsBaseTests` | MMCA.Common.Architecture.Tests | 4 | ArchitectureRules, FixtureMap, IntegrationEventContractTestsBase, ProbeTests | | 6 | `LocalizationResourceTests` | MMCA.Common.Architecture.Tests | 2 | LocalizationResourceTestsBase, SupportedCultures | | 6 | `ModuleProbeMap` | MMCA.Common.Architecture.Tests | 4 | ArchitectureMapBase, FixtureLeakingEvent, Layer, LayerRef | | 6 | `NavigatingQuerySpec` | MMCA.Common.Architecture.Tests | 2 | FitnessDependent, QuerySpecification | @@ -3403,12 +3487,10 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 6 | `SpecificationTests` | MMCA.Common.Domain.Tests | 6 | AgeGreaterThanSpec, AndSpecification, NameStartsWithSpec, NotSpecification, OrSpecification, TestEntity | | 6 | `UserNotificationTests` | MMCA.Common.Domain.Tests | 1 | UserNotification | | 6 | `AmbientOrigin` | MMCA.Common.Infrastructure | 4 | AuthClaimTypes, ICorrelationContext, ITenantContext, ScopedUserOverride | -| 6 | `EFReadRepository` | MMCA.Common.Infrastructure | 17 | AuditableBaseEntity, BaseLookup, EntityQueryPipeline, Error, GroupedCount, GroupedSum, IReadRepository, ISpecification, KeysetCollectionResult, KeysetCursor, KeysetPageRequest, KeysetQueryBuilder, LookupRow, QuerySpecification, QueryTags, Result, SpecificationEvaluator | | 6 | `EFReadRepositoryDecorator` | MMCA.Common.Infrastructure | 8 | AuditableBaseEntity, BaseLookup, IReadRepository, ISpecification, KeysetCollectionResult, KeysetPageRequest, ProfilingHelper, Result | | 6 | `EmailConfirmationTokenService` | MMCA.Common.Infrastructure | 8 | EmailConfirmationEntry, EmailConfirmationErrors, EmailConfirmationSettings, EmailIdentity, Error, ICacheService, IEmailConfirmationTokenService, Result | -| 6 | `EntityTypeConfiguration` | MMCA.Common.Infrastructure | 10 | AuditableBaseEntity, CosmosIntIdValueGenerator, DataSource, EntityTypeConfigurationBase, IEntityTypeConfigurationCosmos, IEntityTypeConfigurationPostgreSQL, IEntityTypeConfigurationSqlite, IEntityTypeConfigurationSQLServer, NamespaceConventions, UseDataSourceAttribute | | 6 | `LoginProtectionService` | MMCA.Common.Infrastructure | 6 | EmailIdentity, Error, ICacheService, ILoginProtectionService, LoginProtectionSettings, Result | -| 6 | `PasswordResetTokenService` | MMCA.Common.Infrastructure | 7 | EmailIdentity, Error, ICacheService, IPasswordResetTokenService, PasswordResetEntry, PasswordResetSettings, Result | +| 6 | `PasswordResetTokenService` | MMCA.Common.Infrastructure | 8 | EmailIdentity, Error, ICacheService, IDistributedLock, IPasswordResetTokenService, PasswordResetEntry, PasswordResetSettings, Result | | 6 | `StronglyTypedIdModelConfiguration` | MMCA.Common.Infrastructure | 3 | StronglyTypedIdRegistry, StronglyTypedIdValueComparer, StronglyTypedIdValueConverter | | 6 | `AddressTestDbContext` | MMCA.Common.Infrastructure.Tests | 3 | AddressInvariants, HandRolledOwner, HelperOwner | | 6 | `AllSpecification` | MMCA.Common.Infrastructure.Tests | 2 | Specification, SpecTestEntity | @@ -3416,7 +3498,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 6 | `BetaSpecification` | MMCA.Common.Infrastructure.Tests | 2 | Specification, SpecTestEntity | | 6 | `BetaSpecification` | MMCA.Common.Infrastructure.Tests | 2 | Specification, SpecTestEntity | | 6 | `DeletedByNameSpecification` | MMCA.Common.Infrastructure.Tests | 2 | Specification, SpecTestEntity | -| 6 | `DistributedCacheServiceTests` | MMCA.Common.Infrastructure.Tests | 3 | CacheKeyNamespace, DistributedCacheService, WarningCountingLogger | +| 6 | `DistributedCacheServiceTests` | MMCA.Common.Infrastructure.Tests | 4 | CacheKeyNamespace, DistributedCacheService, ICacheService, WarningCountingLogger | | 6 | `EmailIdentityTests` | MMCA.Common.Infrastructure.Tests | 1 | EmailIdentity | | 6 | `EmailValueConverterTests` | MMCA.Common.Infrastructure.Tests | 3 | Email, EmailValueConverter, NullableEmailValueConverter | | 6 | `HighestRankedBetaSpecification` | MMCA.Common.Infrastructure.Tests | 2 | QuerySpecification, SpecTestEntity | @@ -3432,11 +3514,13 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 6 | `NoMatchSpecification` | MMCA.Common.Infrastructure.Tests | 2 | Specification, SpecTestEntity | | 6 | `OrderedSpecification` | MMCA.Common.Infrastructure.Tests | 2 | QuerySpecification, SpecTestEntity | | 6 | `PagedSpecification` | MMCA.Common.Infrastructure.Tests | 2 | QuerySpecification, SpecTestEntity | +| 6 | `PermissionGrantCacheTests` | MMCA.Common.Infrastructure.Tests | 5 | IPermissionGrantStore, ManualClock, PermissionGrant, PermissionGrantCache, PermissionGrantSettings | | 6 | `PhoneNumberValueConverterTests` | MMCA.Common.Infrastructure.Tests | 3 | NullablePhoneNumberValueConverter, PhoneNumber, PhoneNumberValueConverter | | 6 | `PortableThing` | MMCA.Common.Infrastructure.Tests | 2 | AuditableAggregateRootEntity, PortablePrincipal | | 6 | `RankDescendingSpecification` | MMCA.Common.Infrastructure.Tests | 2 | QuerySpecification, SpecTestEntity | +| 6 | `RefreshSessionModelBuilderExtensionsTests` | MMCA.Common.Infrastructure.Tests | 4 | CustomSchemaContext, RefreshSession, RefreshSessionModelBuilderExtensions, RefreshSessionOnlyContext | | 6 | `RegistryUnattributedConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | IEntityTypeConfigurationSqlite, RegistryUnattributed | -| 6 | `StoredPermissionRoleAdministrationServiceTests` | MMCA.Common.Infrastructure.Tests | 8 | AdministrationPermissions, ErrorType, FakeGrantCache, FakeGrantStore, IPermissionGrantCacheInvalidator, PermissionGrantSettings, PermissionRegistryBuilder, StoredPermissionRoleAdministrationService | +| 6 | `StoredPermissionRoleAdministrationServiceTests` | MMCA.Common.Infrastructure.Tests | 11 | AdministrationPermissions, Error, ErrorType, FakeGrantCache, FakeGrantStore, IPermissionGrantCacheInvalidator, IPermissionGrantStore, PermissionGrantSettings, PermissionRegistryBuilder, Result, StoredPermissionRoleAdministrationService | | 6 | `TestAggregateEntityConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationBase, TestAggregateEntity | | 6 | `TestDbContext` | MMCA.Common.Infrastructure.Tests | 6 | FakeAggregate, FakeAggregate, FakeEntity, FakeEntity, TestChildEntity, TestEntity | | 6 | `TestEntitySqliteConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | IEntityTypeConfigurationSqlite, TestMappedEntity | @@ -3445,22 +3529,25 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 6 | `TopTwoByRankSpecification` | MMCA.Common.Infrastructure.Tests | 2 | QuerySpecification, SpecTestEntity | | 6 | `TrackedSpecification` | MMCA.Common.Infrastructure.Tests | 2 | QuerySpecification, SpecTestEntity | | 6 | `UnorderedQuerySpecification` | MMCA.Common.Infrastructure.Tests | 2 | QuerySpecification, SpecTestEntity | +| 6 | `LoadItemMapper` | MMCA.Common.LoadTests | 3 | IEntityDTOMapper, LoadItem, LoadItemDTO | +| 6 | `LoadItemNavigationPopulator` | MMCA.Common.LoadTests | 3 | INavigationPopulator, LoadItem, NavigationMetadata | | 6 | `EnumerationSerializationTests` | MMCA.Common.Shared.Tests | 5 | Alert, EnumerationJsonConverterFactory, Grade, Severity, Severity | | 6 | `StronglyTypedIdTests` | MMCA.Common.Shared.Tests | 7 | CustomerId, LineId, OrderId, SkuId, SpeakerId, StronglyTypedId, StronglyTypedIdRegistry | +| 6 | `InMemoryRefreshSessionStore` | MMCA.Common.Testing | 2 | IRefreshSessionStore, RefreshSession | | 6 | `AuthUIService` | MMCA.Common.UI | 19 | AuthenticationResponse, ChangePasswordRequest, Error, ForgotPasswordRequest, HttpResultExecutor, IAuthUIService, ILocalCacheStore, IPushRegistrationService, ITokenRefresher, ITokenStorageService, IUiReadCache, JwtAuthenticationStateProvider, LoginRequest, OAuthCodeExchangeRequest, ProblemDetailsResultReader, RefreshSessionSummaryResponse, RegisterRequest, ResetPasswordRequest, Result | | 6 | `NotificationBell` | MMCA.Common.UI | 6 | INotificationInboxUIService, NotificationBellOptions, NotificationRoutePaths, NotificationState, SharedResource, State | | 6 | `NotificationList` | MMCA.Common.UI | 5 | IPushNotificationUIService, IToastService, NotificationRoutePaths, PushNotificationDTO, SharedResource | | 6 | `NotificationSend` | MMCA.Common.UI | 12 | DataAnnotationsModelValidator, ErrorMessages, INotificationScopeProvider, IPushNotificationUIService, IToastService, ModelValidation, NotificationRoutePaths, NotificationSendModel, PushNotificationDTO, Result, SendPushNotificationRequest, SharedResource | -| 6 | `Sessions` | MMCA.Common.UI | 7 | IAuthUIService, IToastService, RefreshSessionSummaryResponse, Result, RoutePaths, SharedResource, UserAgentSummary | +| 6 | `Sessions` | MMCA.Common.UI | 8 | IAppDialogService, IAuthUIService, IToastService, RefreshSessionSummaryResponse, Result, RoutePaths, SharedResource, UserAgentSummary | | 6 | `NoOpAuthUIService` | MMCA.Common.UI.Gallery | 7 | AuthenticationResponse, Error, IAuthUIService, LoginRequest, RefreshSessionSummaryResponse, RegisterRequest, Result | | 6 | `ChildEntityServiceBaseTests` | MMCA.Common.UI.Tests | 7 | ErrorType, ITokenStorageService, MembershipDto, MembershipService, Mocks, StubHttpClientFactory, StubHttpMessageHandler | | 6 | `ConfirmEmailPageTests` | MMCA.Common.UI.Tests | 1 | ConfirmEmailPageTestsBase | -| 6 | `EntityServiceBaseCachingTests` | MMCA.Common.UI.Tests | 14 | BaseLookup, CollectionResult, FakeTimeProvider, ITokenStorageService, IUiReadCache, PagedCollectionResult, PaginationMetadata, Result, StubHttpClientFactory, StubHttpMessageHandler, UiReadCache, UiReadCacheOptions, WidgetDto, WidgetService | +| 6 | `EntityServiceBaseCachingTests` | MMCA.Common.UI.Tests | 15 | BaseLookup, CollectionResult, FakeTimeProvider, GatedGetHandler, ITokenStorageService, IUiReadCache, PagedCollectionResult, PaginationMetadata, Result, StubHttpClientFactory, StubHttpMessageHandler, UiReadCache, UiReadCacheOptions, WidgetDto, WidgetService | | 6 | `EntityServiceBaseIdempotencyRetryTests` | MMCA.Common.UI.Tests | 6 | ErrorType, FreshApiClientFactory, ScriptedHandler, StubTokenStorageService, WidgetDto, WidgetService | | 6 | `EntityServiceBaseTests` | MMCA.Common.UI.Tests | 13 | BaseLookup, CollectionResult, ErrorType, HttpResultExecutor, ITokenStorageService, Mocks, PagedCollectionResult, PaginationMetadata, ProblemDetailsResultReader, StubHttpClientFactory, StubHttpMessageHandler, WidgetDto, WidgetService | | 6 | `NavMenuTests` | MMCA.Common.UI.Tests | 11 | AuthClaimTypes, BunitTestBase, IAuthUIService, IUIModule, LayoutSettings, NavItem, NavSection, RoutePaths, SharedResource, StubUiModule, TestPrincipal | | 6 | `PseudoLocalizationTests` | MMCA.Common.UI.Tests | 7 | FakeStringLocalizer, FakeStringLocalizerFactory, PseudoLocalizationTests, PseudoLocalizer, PseudoStringLocalizer, PseudoStringLocalizerFactory, SupportedCultures | -| 6 | `PushNotificationServiceTests` | MMCA.Common.UI.Tests | 12 | ErrorType, ITokenStorageService, Mocks, PagedCollectionResult, PaginationMetadata, ProblemDetailsResultReader, PushNotificationDTO, PushNotificationService, SendPushNotificationRequest, StubHttpClientFactory, StubHttpMessageHandler, StubScopeProvider | +| 6 | `PushNotificationServiceTests` | MMCA.Common.UI.Tests | 13 | ErrorType, IdempotencyHeaders, ITokenStorageService, Mocks, PagedCollectionResult, PaginationMetadata, ProblemDetailsResultReader, PushNotificationDTO, PushNotificationService, SendPushNotificationRequest, StubHttpClientFactory, StubHttpMessageHandler, StubScopeProvider | | 6 | `RegisterFormTests` | MMCA.Common.UI.Tests | 7 | AuthenticationResponse, AuthErrorCodes, BunitTestBase, Error, IAuthUIService, RegisterRequest, Result | | 6 | `RoleAdminEditPageTests` | MMCA.Common.UI.Tests | 2 | EditorShell, RoleAdminEditPageTestsBase | | 6 | `RoleAdminListPageTests` | MMCA.Common.UI.Tests | 2 | RoleAdminListPageTestsBase, RosterShell | @@ -3528,7 +3615,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 7 | `SpeakerQuestionAnswer` | MMCA.ADC.Conference.Domain | 4 | AuditableBaseEntity, Result, Speaker, SpeakerInvariants | | 7 | `ActivityInvariantsTests` | MMCA.ADC.Conference.Domain.Tests | 1 | ActivityInvariants | | 7 | `CategoryInvariantsTests` | MMCA.ADC.Conference.Domain.Tests | 2 | Category, CategoryInvariants | -| 7 | `CategoryTests` | MMCA.ADC.Conference.Domain.Tests | 3 | Category, CategoryChanged, DomainEntityState | +| 7 | `CategoryTests` | MMCA.ADC.Conference.Domain.Tests | 4 | Category, CategoryChanged, CategoryInvariants, DomainEntityState | | 7 | `EventInvariantsTests` | MMCA.ADC.Conference.Domain.Tests | 1 | EventInvariants | | 7 | `PartnerInvariantsTests` | MMCA.ADC.Conference.Domain.Tests | 1 | PartnerInvariants | | 7 | `QuestionInvariantsTests` | MMCA.ADC.Conference.Domain.Tests | 1 | QuestionInvariants | @@ -3546,6 +3633,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 7 | `SessionAssetsDownloadListTests` | MMCA.ADC.Conference.UI.Tests | 7 | BunitTestBase, Error, ISessionAssetUIService, Result, SessionAssetDTO, SessionAssetKind, SessionAssetsDownloadList | | 7 | `SessionAssetsPanelTests` | MMCA.ADC.Conference.UI.Tests | 9 | BunitTestBase, Error, ISessionAssetUIService, Result, SessionAssetDTO, SessionAssetKind, SessionAssetLimits, SessionAssetLinkRequest, SessionAssetsPanel | | 7 | `SessionBookmarkButtonTests` | MMCA.ADC.Conference.UI.Tests | 6 | AuthClaimTypes, BunitTestBase, ISessionBookmarkUIService, IToastService, Result, SessionBookmarkButton | +| 7 | `SessionCreateTests` | MMCA.ADC.Conference.UI.Tests | 9 | BunitTestBase, Error, EventInfo, IEventLookupService, IRoomUIService, ISessionUIService, Result, SessionCreate, SessionDTO | | 7 | `SessionSelectionAiScoresTests` | MMCA.ADC.Conference.UI.Tests | 6 | BunitTestBase, CategoryDistributionDTO, SessionAiScoreDTO, SessionSelectionAiScores, SessionSelectionDashboardDTO, SpeakerSessionOverlapDTO | | 7 | `SharePageButtonTests` | MMCA.ADC.Conference.UI.Tests | 3 | BunitTestBase, IClipboardService, IShareService | | 7 | `SpeakerCategoryItemsPanelTests` | MMCA.ADC.Conference.UI.Tests | 9 | BunitTestBase, CategoryItemInfo, ISpeakerCategoryItemUIService, Result, RoleNames, SpeakerCategoryItemDTO, SpeakerCategoryItemsPanel, SpeakerDTO, TestPrincipal | @@ -3554,7 +3642,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 7 | `CreateLivePollRequestValidator` | MMCA.ADC.Engagement.Application | 2 | CreateLivePollRequest, LivePollInvariants | | 7 | `SubmitQuestionCommandValidator` | MMCA.ADC.Engagement.Application | 2 | SessionQuestionInvariants, SubmitQuestionCommand | | 7 | `HandlerMocks` | MMCA.ADC.Engagement.Application.Tests | 2 | IRepository, LeaderboardOptIn | -| 7 | `TestSupport` | MMCA.ADC.Engagement.Application.Tests | 5 | AuditableAggregateRootEntity, AuditableBaseEntity, BaseEntity, IReadRepository, IRepository | +| 7 | `TestSupport` | MMCA.ADC.Engagement.Application.Tests | 10 | AuditableAggregateRootEntity, AuditableBaseEntity, BaseEntity, EventLiveInfo, IEventLiveValidationService, IReadRepository, IRepository, QuestionModerationDefault, Result, SessionLiveInfo | | 7 | `AttendeeBadge` | MMCA.ADC.Engagement.Domain | 3 | AttendeeBadgeInvariants, AuditableAggregateRootEntity, Result | | 7 | `LivePollVote` | MMCA.ADC.Engagement.Domain | 5 | AuditableAggregateRootEntity, DomainEntityState, LivePollVoteChanged, LivePollVoteInvariants, Result | | 7 | `PointsEntry` | MMCA.ADC.Engagement.Domain | 7 | AuditableAggregateRootEntity, DomainEntityState, IAuditedEntity, PointsActivityType, PointsEntryChanged, PointsEntryInvariants, Result | @@ -3579,6 +3667,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 7 | `AggregateRootEntityControllerBase` | MMCA.Common.API | 10 | AuditableAggregateRootEntity, DeleteEntityCommand, EntityControllerBase, IAggregateRootEntityControllerBase, IBaseDTO, ICommandHandler, ICreateRequest, IEntityQueryService, Result, Route | | 7 | `AsyncScopedReadController` | MMCA.Common.API.Tests | 5 | EntityControllerBase, IEntityQueryService, ReadScopeDTO, ReadScopeEntity, Specification | | 7 | `BothHooksReadController` | MMCA.Common.API.Tests | 5 | EntityControllerBase, IEntityQueryService, ReadScopeDTO, ReadScopeEntity, Specification | +| 7 | `DefaultExportTestController` | MMCA.Common.API.Tests | 4 | EntityControllerBase, ExportTestDTO, ExportTestEntity, IEntityQueryService | | 7 | `ExportShapeTestController` | MMCA.Common.API.Tests | 4 | EntityControllerBase, ExportShapeTestDTO, ExportTestEntity, IEntityQueryService | | 7 | `ExportTestController` | MMCA.Common.API.Tests | 4 | EntityControllerBase, ExportTestDTO, ExportTestEntity, IEntityQueryService | | 7 | `ModuleControllerFeatureProviderTests` | MMCA.Common.API.Tests | 5 | ApiControllerBaseTests, FakeCategoriesController, ModuleControllerFeatureProvider, ModuleSettings, ModulesSettings | @@ -3602,28 +3691,23 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 7 | `PushNotificationInvariantsTests` | MMCA.Common.Domain.Tests | 2 | PushNotificationInvariants, Result | | 7 | `ConsumerOriginRestore` | MMCA.Common.Infrastructure | 2 | AmbientOrigin, MessageHeaders | | 7 | `EFRepositoryDecorator` | MMCA.Common.Infrastructure | 6 | AuditableAggregateRootEntity, EFReadRepositoryDecorator, IRepository, IRowVersioned, IUpdatePropertySetter, ProfilingHelper | -| 7 | `EntityTypeConfigurationCosmos` | MMCA.Common.Infrastructure | 3 | AuditableBaseEntity, DataSource, EntityTypeConfiguration | -| 7 | `EntityTypeConfigurationPostgreSQL` | MMCA.Common.Infrastructure | 3 | AuditableBaseEntity, DataSource, EntityTypeConfiguration | -| 7 | `EntityTypeConfigurationSqlite` | MMCA.Common.Infrastructure | 3 | AuditableBaseEntity, DataSource, EntityTypeConfiguration | -| 7 | `EntityTypeConfigurationSQLServer` | MMCA.Common.Infrastructure | 3 | AuditableBaseEntity, DataSource, EntityTypeConfiguration | | 7 | `IRepositoryFactory` | MMCA.Common.Infrastructure | 4 | AuditableAggregateRootEntity, AuditableBaseEntity, IReadRepository, IRepository | | 7 | `EFReadRepositoryDecoratorAdditionalTests` | MMCA.Common.Infrastructure.Tests | 7 | EFReadRepositoryDecorator, FakeEntity, FakeEntity, FakeEntity, InlineSpecification, IReadRepository, ISpecification | | 7 | `EFReadRepositoryDecoratorTests` | MMCA.Common.Infrastructure.Tests | 6 | BaseLookup, EFReadRepositoryDecorator, FakeEntity, FakeEntity, FakeEntity, IReadRepository | -| 7 | `EmailConfirmationTokenServiceTests` | MMCA.Common.Infrastructure.Tests | 7 | EmailConfirmationEntry, EmailConfirmationErrors, EmailConfirmationSettings, EmailConfirmationTokenService, ErrorType, FakeConfirmationCacheService, Result | +| 7 | `EmailConfirmationTokenServiceTests` | MMCA.Common.Infrastructure.Tests | 9 | EmailConfirmationEntry, EmailConfirmationErrors, EmailConfirmationSettings, EmailConfirmationTokenService, ErrorType, FakeConfirmationCacheService, FakeTimeProvider, ICacheService, Result | | 7 | `LoginProtectionServiceTests` | MMCA.Common.Infrastructure.Tests | 5 | ErrorType, FakeCacheService, LoginProtectionService, LoginProtectionSettings, Result | | 7 | `OwnsAddressTests` | MMCA.Common.Infrastructure.Tests | 6 | Address, AddressInvariants, AddressTestDbContext, HandRolledOwner, HelperOwner, PropertyFacets | | 7 | `OwnsMoneyTests` | MMCA.Common.Infrastructure.Tests | 6 | Currency, HandRolledOwner, HelperOwner, Money, MoneyTestDbContext, PropertyFacets | -| 7 | `PasswordResetTokenServiceTests` | MMCA.Common.Infrastructure.Tests | 6 | ErrorType, FakeCacheService, PasswordResetEntry, PasswordResetSettings, PasswordResetTokenService, Result | -| 7 | `PortableThingConfiguration` | MMCA.Common.Infrastructure.Tests | 3 | DataSource, EntityTypeConfiguration, PortableThing | +| 7 | `PasswordResetTokenServiceTests` | MMCA.Common.Infrastructure.Tests | 9 | ErrorType, FakeCacheService, FakeTimeProvider, ICacheService, InProcessDistributedLock, PasswordResetEntry, PasswordResetSettings, PasswordResetTokenService, Result | | 7 | `TestConfigDbContext` | MMCA.Common.Infrastructure.Tests | 2 | TestAggregateEntity, TestAggregateEntityConfiguration | | 7 | `TestNonAggregateConfigDbContext` | MMCA.Common.Infrastructure.Tests | 2 | TestNonAggregateEntity, TestNonAggregateEntityConfiguration | -| 7 | `DependencyInjection` | MMCA.Common.UI | 46 | ApiSettings, ApiUserPreferenceReader, ApiUserPreferenceWriter, AuthDelegatingHandler, AuthUIService, CultureDelegatingHandler, DefaultOAuthUISettings, EmailConfirmationUIService, EndpointCultureApplier, HttpResilienceDefaults, IAppDialogService, IAuthUIService, ICultureApplier, IEmailConfirmationUIService, IEntityService, IFormFactor, InvariantMudLocalizationInterceptor, IOAuthUISettings, IPublicLinkBuilder, IRoleAdminUIService …(+26) | +| 7 | `DependencyInjection` | MMCA.Common.UI | 47 | ApiSettings, ApiUserPreferenceReader, ApiUserPreferenceWriter, AuthDelegatingHandler, AuthUIService, CultureDelegatingHandler, DefaultOAuthUISettings, EmailConfirmationUIService, EndpointCultureApplier, HttpResilienceDefaults, IAppDialogService, IAuthUIService, ICultureApplier, IEmailConfirmationUIService, IEntityService, IFormFactor, InvariantMudLocalizationInterceptor, IOAuthUISettings, IPublicLinkBuilder, IRoleAdminUIService …(+27) | | 7 | `NotificationUIModule` | MMCA.Common.UI | 7 | IUIModule, NavItem, NavSection, NotificationBell, NotificationPermissions, NotificationRoutePaths, SharedResource | -| 7 | `AuthUIServiceTests` | MMCA.Common.UI.Tests | 14 | AuthenticationResponse, AuthResponse, AuthUIService, ErrorType, HttpResultExecutor, ILocalCacheStore, IPushRegistrationService, ITokenRefresher, ITokenStorageService, JwtAuthenticationStateProvider, LoginRequest, RegisterRequest, StubHttpClientFactory, StubHttpMessageHandler | +| 7 | `AuthUIServiceTests` | MMCA.Common.UI.Tests | 15 | AuthenticationResponse, AuthResponse, AuthUIService, ErrorType, HttpResultExecutor, ILocalCacheStore, IPushRegistrationService, ITokenRefresher, ITokenStorageService, Jwt, JwtAuthenticationStateProvider, LoginRequest, RegisterRequest, StubHttpClientFactory, StubHttpMessageHandler | | 7 | `NotificationBellHost` | MMCA.Common.UI.Tests | 1 | NotificationBell | | 7 | `NotificationListTests` | MMCA.Common.UI.Tests | 10 | BunitTestBase, Error, IPushNotificationUIService, IToastService, NotificationList, PagedCollectionResult, PaginationMetadata, PushNotificationDTO, Result, ToastSeverity | | 7 | `NotificationSendTests` | MMCA.Common.UI.Tests | 12 | BunitTestBase, Error, INotificationScopeProvider, IPushNotificationUIService, IToastService, NamedScopeProvider, NotificationSend, NullNotificationScopeProvider, PushNotificationDTO, Result, SendPushNotificationRequest, ToastSeverity | -| 7 | `SessionsTests` | MMCA.Common.UI.Tests | 9 | BunitTestBase, Error, IAuthUIService, IToastService, RefreshSessionSummaryResponse, Result, Sessions, TestPrincipal, ToastSeverity | +| 7 | `SessionsTests` | MMCA.Common.UI.Tests | 10 | BunitTestBase, Error, IAppDialogService, IAuthUIService, IToastService, RefreshSessionSummaryResponse, Result, Sessions, TestPrincipal, ToastSeverity | | 8 | `CategoryItemsController` | MMCA.ADC.Conference.API | 17 | AddCategoryItemCommand, AddCategoryItemRequest, BaseLookup, CategoryItem, CategoryItemDTO, CollectionResult, ConferencePermissions, EntityControllerBase, ICommandHandler, IEntityQueryService, PagedCollectionResult, QueryFilterModelBinder, RemoveCategoryItemCommand, Result, Route, UpdateCategoryItemCommand, UpdateCategoryItemRequest | | 8 | `ConferenceCategoriesController` | MMCA.ADC.Conference.API | 17 | AggregateRootEntityControllerBase, BaseLookup, Category, CollectionResult, ConferenceCategoryCreateRequest, ConferenceCategoryDTO, ConferenceCategoryUpdateRequest, ConferencePermissions, DeleteEntityCommand, ICommandHandler, IEntityQueryService, PagedCollectionResult, QueryFilterModelBinder, Result, Route, SupportsIfMatchAttribute, UpdateEntityCommand | | 8 | `ActivityFieldRules` | MMCA.ADC.Conference.Application | 8 | ActivityDescriptionRules, ActivityNameRules, ActivitySortOrderRules, ActivityTimeRangeRules, ActivityVenueAddressRules, ActivityVenueNameRules, ActivityVenueUrlRules, IActivityFieldsRequest | @@ -3650,7 +3734,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 8 | `QuestionCreateRequest` | MMCA.ADC.Conference.Application | 3 | ICacheInvalidating, ICreateRequest, Question | | 8 | `QuestionDTOMapper` | MMCA.ADC.Conference.Application | 3 | IEntityDTOMapper, Question, QuestionDTO | | 8 | `QuestionUpdateRequestValidator` | MMCA.ADC.Conference.Application | 2 | QuestionTextRules, QuestionUpdateRequest | -| 8 | `RefreshFromSessionizeCommand` | MMCA.ADC.Conference.Application | 5 | ConferenceFeatures, Event, ICacheInvalidating, IFeatureGated, ITransactional | +| 8 | `RefreshFromSessionizeCommand` | MMCA.ADC.Conference.Application | 4 | ConferenceFeatures, Event, ICacheInvalidating, IFeatureGated | | 8 | `RemoveEventQuestionAnswerCommand` | MMCA.ADC.Conference.Application | 2 | Event, ICacheInvalidating | | 8 | `RemoveEventSpeakerCommand` | MMCA.ADC.Conference.Application | 2 | Event, ICacheInvalidating | | 8 | `RemoveRoomCommand` | MMCA.ADC.Conference.Application | 2 | Event, ICacheInvalidating | @@ -3701,23 +3785,12 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 8 | `SpeakerBuilder` | MMCA.ADC.Conference.Domain.Tests | 2 | EntityBuilderBase, Speaker | | 8 | `SpeakerCategoryItemTests` | MMCA.ADC.Conference.Domain.Tests | 5 | DomainEntityState, ErrorType, Speaker, SpeakerCategoryItem, SpeakerCategoryItemChanged | | 8 | `SpeakerQuestionAnswerTests` | MMCA.ADC.Conference.Domain.Tests | 6 | DomainEntityState, ErrorType, Speaker, SpeakerInvariants, SpeakerQuestionAnswer, SpeakerQuestionAnswerChanged | -| 8 | `SpeakerTests` | MMCA.ADC.Conference.Domain.Tests | 4 | DomainEntityState, Speaker, SpeakerCategoryItemChanged, SpeakerChanged | -| 8 | `CategoryItemConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | CategoryInvariants, CategoryItem, EntityTypeConfigurationSQLServer | -| 8 | `ConferenceCategoryConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | Category, CategoryInvariants, EntityTypeConfigurationSQLServer | -| 8 | `EventConfiguration` | MMCA.ADC.Conference.Infrastructure | 4 | EntityTypeConfigurationSQLServer, Event, EventInvariants, NullableEmailValueConverter | -| 8 | `EventQuestionAnswerConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | EntityTypeConfigurationSQLServer, EventInvariants, EventQuestionAnswer | -| 8 | `EventSpeakerConfiguration` | MMCA.ADC.Conference.Infrastructure | 2 | EntityTypeConfigurationSQLServer, EventSpeaker | -| 8 | `QuestionConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | EntityTypeConfigurationSQLServer, Question, QuestionInvariants | -| 8 | `RoomConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | EntityTypeConfigurationSQLServer, EventInvariants, Room | -| 8 | `SessionAiScoreConfiguration` | MMCA.ADC.Conference.Infrastructure | 2 | EntityTypeConfigurationSQLServer, SessionAiScore | -| 8 | `SpeakerCategoryItemConfiguration` | MMCA.ADC.Conference.Infrastructure | 2 | EntityTypeConfigurationSQLServer, SpeakerCategoryItem | -| 8 | `SpeakerConfiguration` | MMCA.ADC.Conference.Infrastructure | 4 | EntityTypeConfigurationSQLServer, NullableEmailValueConverter, Speaker, SpeakerInvariants | -| 8 | `SpeakerQuestionAnswerConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | EntityTypeConfigurationSQLServer, SpeakerInvariants, SpeakerQuestionAnswer | +| 8 | `SpeakerTests` | MMCA.ADC.Conference.Domain.Tests | 5 | DomainEntityState, Speaker, SpeakerCategoryItemChanged, SpeakerChanged, SpeakerInvariants | | 8 | `SeederMocks` | MMCA.ADC.Conference.Infrastructure.Tests | 4 | Event, IRepository, IUnitOfWork, Question | | 8 | `SessionizeServiceTests` | MMCA.ADC.Conference.Infrastructure.Tests | 8 | Question, SessionizeCategory, SessionizeCodeFormat, SessionizeQuestion, SessionizeResponse, SessionizeRoom, SessionizeService, Sessions | | 8 | `CurrentEventSelector` | MMCA.ADC.Conference.Shared | 1 | Event | | 8 | `EventDetail` | MMCA.ADC.Conference.UI | 11 | ConferenceRoutePaths, DataAnnotationsModelValidator, ErrorMessages, Event, EventDTO, EventEditModel, EventService, IEventUIService, IToastService, ModelValidation, RefreshFromSessionizeResultDTO | -| 8 | `PublicEventDetail` | MMCA.ADC.Conference.UI | 13 | ConferenceReadAudience, ConferenceRoutePaths, Event, EventDTO, EventService, IClipboardService, IEventUIService, IGeocodingService, IGeolocationService, IMapNavigationService, IToastService, LatestLoadGuard, ToastSeverity | +| 8 | `PublicEventDetail` | MMCA.ADC.Conference.UI | 13 | ConferenceRoutePaths, Event, EventDTO, EventService, IClipboardService, IEventUIService, IGeocodingService, IGeolocationService, IMapNavigationService, IToastService, LatestLoadGuard, PublicReadAudience, ToastSeverity | | 8 | `QuestionDetail` | MMCA.ADC.Conference.UI | 10 | ConferenceRoutePaths, DataAnnotationsModelValidator, ErrorMessages, IQuestionUIService, IToastService, ModelValidation, Question, QuestionDTO, QuestionEditModel, QuestionService | | 8 | `RoomDetail` | MMCA.ADC.Conference.UI | 13 | ConferenceRoutePaths, DataAnnotationsModelValidator, ErrorMessages, EventInfo, EventLookupService, IEventLookupService, IRoomUIService, IToastService, ModelValidation, Room, RoomDTO, RoomEditModel, RoomService | | 8 | `OrganizerEventFeedbackTests` | MMCA.ADC.Conference.UI.Tests | 11 | BunitTestBase, Error, EventInfo, EventQuestionAnswerDTO, IEventLookupService, IOrganizerEventFeedbackUIService, IQuestionUIService, OrganizerEventFeedback, Question, QuestionDTO, Result | @@ -3730,8 +3803,12 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 8 | `GetPointsOverviewHandler` | MMCA.ADC.Engagement.Application | 9 | GetPointsOverviewQuery, IQueryHandler, IUnitOfWork, OverviewRow, PointsActivityTotalDTO, PointsEntry, PointsEntryDTO, PointsOverviewDTO, Result | | 8 | `SessionQuestionUpvoteChangedHandler` | MMCA.ADC.Engagement.Application | 11 | BestEffort, IDomainEventHandler, ILiveChannelPublishQueue, IUnitOfWork, LiveChannelPublishWorkItem, LivePollChannel, SessionQuestion, SessionQuestionChannel, SessionQuestionUpvote, SessionQuestionUpvoteChanged, SessionQuestionUpvoteChangedPayload | | 8 | `SessionQuestionViewBuilder` | MMCA.ADC.Engagement.Application | 5 | IQueryableExecutor, IUnitOfWork, SessionQuestion, SessionQuestionDTO, SessionQuestionUpvote | -| 8 | `ToggleUpvoteHandler` | MMCA.ADC.Engagement.Application | 9 | Error, ICommandHandler, IEntityReader, IRepository, IUnitOfWork, Result, SessionQuestion, SessionQuestionUpvote, ToggleUpvoteCommand | +| 8 | `ToggleUpvoteHandler` | MMCA.ADC.Engagement.Application | 11 | Error, ICommandHandler, IEntityReader, IEventLiveValidationService, IRepository, IUniqueConstraintViolationDetector, IUnitOfWork, Result, SessionQuestion, SessionQuestionUpvote, ToggleUpvoteCommand | +| 8 | `UserDeletedBadgeHandler` | MMCA.ADC.Engagement.Application | 4 | AttendeeBadge, IUnitOfWork, ScopedIntegrationEventHandlerBase, UserDeleted | +| 8 | `UserDeletedBookmarksHandler` | MMCA.ADC.Engagement.Application | 4 | IUnitOfWork, ScopedIntegrationEventHandlerBase, UserDeleted, UserSessionBookmark | | 8 | `UserDeletedPointsHandler` | MMCA.ADC.Engagement.Application | 4 | IUnitOfWork, LeaderboardOptIn, ScopedIntegrationEventHandlerBase, UserDeleted | +| 8 | `UserDeletedSessionQuestionsHandler` | MMCA.ADC.Engagement.Application | 5 | IUnitOfWork, ScopedIntegrationEventHandlerBase, SessionQuestion, SessionQuestionUpvote, UserDeleted | +| 8 | `UserDeletedVotesHandler` | MMCA.ADC.Engagement.Application | 4 | IUnitOfWork, LivePollVote, ScopedIntegrationEventHandlerBase, UserDeleted | | 8 | `UserSessionBookmarkDTOMapper` | MMCA.ADC.Engagement.Application | 3 | IEntityDTOMapper, UserSessionBookmark, UserSessionBookmarkDTO | | 8 | `AwarderMocks` | MMCA.ADC.Engagement.Application.Tests | 3 | IRepository, IUniqueConstraintViolationDetector, PointsEntry | | 8 | `CreateLivePollRequestValidatorTests` | MMCA.ADC.Engagement.Application.Tests | 4 | CreateLivePollRequest, CreateLivePollRequestValidator, LivePollInvariants, Question | @@ -3748,13 +3825,6 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 8 | `SessionQuestionTests` | MMCA.ADC.Engagement.Domain.Tests | 5 | DomainEntityState, QuestionStatus, SessionQuestion, SessionQuestionChanged, SessionQuestionInvariants | | 8 | `SessionQuestionUpvoteTests` | MMCA.ADC.Engagement.Domain.Tests | 3 | DomainEntityState, SessionQuestionUpvote, SessionQuestionUpvoteChanged | | 8 | `UserSessionBookmarkTests` | MMCA.ADC.Engagement.Domain.Tests | 3 | DomainEntityState, UserSessionBookmark, UserSessionBookmarkChanged | -| 8 | `AttendeeBadgeConfiguration` | MMCA.ADC.Engagement.Infrastructure | 2 | AttendeeBadge, EntityTypeConfigurationSQLServer | -| 8 | `LeaderboardOptInConfiguration` | MMCA.ADC.Engagement.Infrastructure | 2 | EntityTypeConfigurationSQLServer, LeaderboardOptIn | -| 8 | `LivePollVoteConfiguration` | MMCA.ADC.Engagement.Infrastructure | 2 | EntityTypeConfigurationSQLServer, LivePollVote | -| 8 | `PointsEntryConfiguration` | MMCA.ADC.Engagement.Infrastructure | 3 | EntityTypeConfigurationSQLServer, PointsEntry, PointsSubjectKeys | -| 8 | `SessionQuestionConfiguration` | MMCA.ADC.Engagement.Infrastructure | 3 | EntityTypeConfigurationSQLServer, SessionQuestion, SessionQuestionInvariants | -| 8 | `SessionQuestionUpvoteConfiguration` | MMCA.ADC.Engagement.Infrastructure | 2 | EntityTypeConfigurationSQLServer, SessionQuestionUpvote | -| 8 | `UserSessionBookmarkConfiguration` | MMCA.ADC.Engagement.Infrastructure | 2 | EntityTypeConfigurationSQLServer, UserSessionBookmark | | 8 | `PointsSettingsTests` | MMCA.ADC.Engagement.Shared.Tests | 5 | EventFeedback, PointsActivityType, PointsSettings, SessionFeedback, SponsorVisit | | 8 | `PollManagementPanel` | MMCA.ADC.Engagement.UI | 8 | CreateLivePollRequest, ErrorType, ILivePollUIService, IToastService, LivePollDTO, OptionState, Question, Result | | 8 | `SessionLiveModerationPanel` | MMCA.ADC.Engagement.UI | 12 | CreateLivePollRequest, ErrorType, ILivePollUIService, ISessionQuestionUIService, IToastService, LivePollDTO, LivePollStatus, OptionState, Question, QuestionService, Result, SessionQuestionDTO | @@ -3785,7 +3855,6 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 8 | `UserBuilder` | MMCA.ADC.Identity.Domain.Tests | 3 | EntityBuilderBase, User, UserRole | | 8 | `UserInvariantsAndRoleTests` | MMCA.ADC.Identity.Domain.Tests | 4 | Result, User, UserDeleted, UserRole | | 8 | `UserTests` | MMCA.ADC.Identity.Domain.Tests | 3 | User, UserPasswordChanged, UserRole | -| 8 | `UserConfiguration` | MMCA.ADC.Identity.Infrastructure | 4 | EmailValueConverter, EntityTypeConfigurationSQLServer, User, UserInvariants | | 8 | `SeederMocks` | MMCA.ADC.Identity.Infrastructure.Tests | 4 | IPasswordHasher, IRepository, IUnitOfWork, User | | 8 | `UserListTests` | MMCA.ADC.Identity.UI.Tests | 12 | AuthClaimTypes, BunitTestBase, Email, Error, IAppDialogService, IUserAdminActionsUIService, IUserUIService, Result, User, UserAdminList, UserList, UserListDTO | | 8 | `UserNotificationExportService` | MMCA.ADC.Notification.Application | 6 | IQueryableExecutor, IUnitOfWork, IUserNotificationExportService, PushNotification, UserNotification, UserNotificationExportItemDTO | @@ -3793,37 +3862,35 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 8 | `CurrentUserTargetingContextAccessor` | MMCA.Common.API | 1 | User | | 8 | `EntityControllerBaseETagTests` | MMCA.Common.API.Tests | 12 | ConcurrencyETag, EntityControllerBase, Error, IEntityQueryService, PlainDTO, PlainEntity, PlainEntityController, Result, Specification, VersionedDTO, VersionedEntity, VersionedEntityController | | 8 | `EntityControllerBaseExportColumnTests` | MMCA.Common.API.Tests | 12 | ApplicationSettings, EntityControllerBase, ExportMoney, ExportShapeTestController, ExportShapeTestDTO, ExportTestEntity, FakeTimeProvider, IEntityQueryService, PagedCollectionResult, PaginationMetadata, Result, Specification | -| 8 | `EntityControllerBaseExportTests` | MMCA.Common.API.Tests | 15 | ApplicationSettings, EntityControllerBase, Error, ExportTestController, ExportTestDTO, ExportTestEntity, FakeTimeProvider, IEntityQueryService, InlineSpecification, PagedCollectionResult, PaginationMetadata, Result, ScopedExportTestController, Specification, SpecificationHonoringQueryService | +| 8 | `EntityControllerBaseExportTests` | MMCA.Common.API.Tests | 16 | ApplicationSettings, DefaultExportTestController, EntityControllerBase, Error, ExportTestController, ExportTestDTO, ExportTestEntity, FakeTimeProvider, IEntityQueryService, InlineSpecification, PagedCollectionResult, PaginationMetadata, Result, ScopedExportTestController, Specification, SpecificationHonoringQueryService | | 8 | `EntityControllerBaseReadSpecificationTests` | MMCA.Common.API.Tests | 17 | ApplicationSettings, AsyncScopedReadController, BaseLookup, BothHooksReadController, CollectionResult, ConcurrencyETag, EntityControllerBase, FakeTimeProvider, IEntityQueryService, InlineSpecification, PagedCollectionResult, ReadScopeDTO, ReadScopeEntity, RecordingQueryService, Specification, SyncScopedReadController, UnscopedReadController | | 8 | `EntityControllerBaseTests` | MMCA.Common.API.Tests | 13 | ApplicationSettings, BaseLookup, CollectionResult, EntityControllerBase, Error, IEntityQueryService, PagedCollectionResult, PaginationMetadata, Result, Specification, TestDTO, TestEntity, TestEntityController | -| 8 | `InitTestMigratedWidgetConfiguration` | MMCA.Common.API.Tests | 2 | EntityTypeConfigurationSqlite, InitTestMigratedWidget | -| 8 | `InitTestWidgetConfiguration` | MMCA.Common.API.Tests | 2 | EntityTypeConfigurationSqlite, InitTestWidget | | 8 | `OAuthControllerBaseTests` | MMCA.Common.API.Tests | 10 | AuthenticationResponse, Error, ExternalAuthExtensions, IAuthenticationService, ICacheService, Mocks, OAuthCodeExchangeRequest, Result, SingleServiceProvider, TestOAuthController | | 8 | `TestAggregateRootController` | MMCA.Common.API.Tests | 9 | AggregateRootEntityControllerBase, DeleteEntityCommand, EntityControllerBase, ICommandHandler, IEntityQueryService, Result, TestAggDTO, TestAggregateEntity, TestCreateRequest | | 8 | `AddChildEntityHandlerBase` | MMCA.Common.Application | 5 | AuditableAggregateRootEntity, Error, ICommandHandler, IUnitOfWork, Result | -| 8 | `AuthenticationServiceBase` | MMCA.Common.Application | 29 | AuditableAggregateRootEntity, AuthClaimTypes, AuthenticationResponse, AuthenticationValidators, AuthErrorCodes, Email, EmailConfirmationErrors, EmailConfirmationSettings, Error, IAuthenticationService, IAuthUser, IEmailConfirmableUser, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, IRepository, IssuedSession, ITokenService, ITwoFactorAuthenticator, IUnitOfWork …(+9) | -| 8 | `ChangePasswordHandlerBase` | MMCA.Common.Application | 12 | AuditableAggregateRootEntity, ChangePasswordRequest, Error, ICommandHandler, IPasswordChangeableUser, IPasswordHasher, IRefreshSessionStore, IUnitOfWork, IUserScopedCommand, RefreshSessionRevocation, Result, UserUseCaseLog | -| 8 | `ChangePreferencesHandlerBase` | MMCA.Common.Application | 9 | AuditableAggregateRootEntity, ChangePreferencesRequest, Error, ICommandHandler, IUnitOfWork, IUserPreferences, IUserScopedCommand, Result, UserUseCaseLog | -| 8 | `ConfirmEmailHandlerBase` | MMCA.Common.Application | 10 | AuditableAggregateRootEntity, ConfirmEmailRequest, EmailConfirmationErrors, ICommandHandler, ICommandWithRequest, IEmailConfirmableUser, IEmailConfirmationTokenService, IUnitOfWork, Result, UserUseCaseLog | +| 8 | `AuthenticationServiceBase` | MMCA.Common.Application | 27 | AuditableAggregateRootEntity, AuthClaimTypes, AuthenticationResponse, AuthenticationValidators, AuthErrorCodes, Email, EmailConfirmationErrors, EmailConfirmationSettings, Error, IAuthenticationService, IAuthSessionIssuer, IAuthUser, IEmailConfirmableUser, ILoginProtectionService, IPasswordHasher, IRepository, ITokenService, ITwoFactorAuthenticator, IUnitOfWork, LoginRequest …(+7) | +| 8 | `ChangePasswordHandlerBase` | MMCA.Common.Application | 12 | AuditableAggregateRootEntity, ChangePasswordRequest, Error, ICommandHandler, ILoginProtectionService, IPasswordChangeableUser, IPasswordHasher, IRefreshSessionStore, IUnitOfWork, IUserScopedCommand, RefreshSessionRevocation, Result | +| 8 | `ChangePreferencesHandlerBase` | MMCA.Common.Application | 8 | AuditableAggregateRootEntity, ChangePreferencesRequest, Error, ICommandHandler, IUnitOfWork, IUserPreferences, IUserScopedCommand, Result | +| 8 | `ConfirmEmailHandlerBase` | MMCA.Common.Application | 9 | AuditableAggregateRootEntity, ConfirmEmailRequest, EmailConfirmationErrors, ICommandHandler, ICommandWithRequest, IEmailConfirmableUser, IEmailConfirmationTokenService, IUnitOfWork, Result | | 8 | `CreateEntityHandlerBase` | MMCA.Common.Application | 9 | AuditableAggregateRootEntity, IBaseDTO, ICommandHandler, ICreateRequest, IEntityDTOMapper, IEntityRequestMapper, IRepository, IUnitOfWork, Result | | 8 | `CrossSourceSpecification` | MMCA.Common.Application | 6 | AuditableBaseEntity, IBaseEntity, InlineSpecification, IUnitOfWork, ParameterReplacer, Specification | | 8 | `DeleteEntityHandler` | MMCA.Common.Application | 7 | AuditableAggregateRootEntity, DeleteEntityCommand, Error, ICommandHandler, IRepository, IUnitOfWork, Result | -| 8 | `DeleteUserHandlerBase` | MMCA.Common.Application | 11 | AuditableAggregateRootEntity, Error, ICacheService, ICommandHandler, IErasableUser, IUnitOfWork, IUserOwnedRequest, Result, SoftDeletedUserCache, UserOwnershipRule, UserUseCaseLog | +| 8 | `DeleteUserHandlerBase` | MMCA.Common.Application | 10 | AuditableAggregateRootEntity, Error, ICacheService, ICommandHandler, IErasableUser, IUnitOfWork, IUserOwnedRequest, Result, SoftDeletedUserCache, UserOwnershipRule | | 8 | `EntityQueryService` | MMCA.Common.Application | 22 | AuditableBaseEntity, BaseLookup, EntityQueryParameters, Error, IBaseDTO, IEntityDTOMapper, IEntityDTOProjector, IEntityQueryPipeline, IEntityQueryService, INavigationMetadataProvider, INavigationPopulator, IReadRepository, ISpecification, IUnitOfWork, NavigationMetadata, NavigationMetadataProvider, PagedCollectionResult, PaginationMetadata, QueryFieldContract, QueryFieldService …(+2) | -| 8 | `ExportUserDataHandlerBase` | MMCA.Common.Application | 13 | AuditableAggregateRootEntity, Error, IQueryHandler, IUnitOfWork, IUserDataExportSection, IUserOwnedRequest, Result, Subject, UserDataExportDTO, UserDataExportSectionDefaults, UserDataExportSectionDTO, UserOwnershipRule, UserUseCaseLog | -| 8 | `ForgotPasswordHandlerBase` | MMCA.Common.Application | 11 | AuditableAggregateRootEntity, Email, ForgotPasswordRequest, ICommandHandler, ICommandWithRequest, IEmailSender, IPasswordResetTokenService, IUnitOfWork, PasswordResetSettings, Result, UserUseCaseLog | +| 8 | `ExportUserDataHandlerBase` | MMCA.Common.Application | 12 | AuditableAggregateRootEntity, Error, IQueryHandler, IUnitOfWork, IUserDataExportSection, IUserOwnedRequest, Result, Subject, UserDataExportDTO, UserDataExportSectionDefaults, UserDataExportSectionDTO, UserOwnershipRule | +| 8 | `ForgotPasswordHandlerBase` | MMCA.Common.Application | 10 | AuditableAggregateRootEntity, Email, ForgotPasswordRequest, ICommandHandler, ICommandWithRequest, IEmailSender, IPasswordResetTokenService, IUnitOfWork, PasswordResetSettings, Result | | 8 | `GetMyNotificationsHandler` | MMCA.Common.Application | 11 | GetMyNotificationsQuery, IQueryableExecutor, IQueryHandler, IUnitOfWork, PagedCollectionResult, PaginationMetadata, PagingMath, PushNotification, Result, UserNotification, UserNotificationDTO | | 8 | `GetUnreadNotificationCountHandler` | MMCA.Common.Application | 7 | GetUnreadNotificationCountQuery, IQueryableExecutor, IQueryHandler, IUnitOfWork, PushNotification, Result, UserNotification | | 8 | `GetUserPreferencesHandlerBase` | MMCA.Common.Application | 8 | AuditableBaseEntity, Error, GetUserPreferencesQuery, IQueryHandler, IUnitOfWork, IUserPreferences, Result, UserPreferencesResponse | | 8 | `ICurrentUserService` | MMCA.Common.Application | 1 | User | | 8 | `INavigationDescriptor` | MMCA.Common.Application | 1 | IUnitOfWork | -| 8 | `MarkAllNotificationsReadHandler` | MMCA.Common.Application | 7 | ICommandHandler, IQueryableExecutor, IUnitOfWork, MarkAllNotificationsReadCommand, PushNotification, Result, UserNotification | +| 8 | `MarkAllNotificationsReadHandler` | MMCA.Common.Application | 6 | ICommandHandler, IUnitOfWork, MarkAllNotificationsReadCommand, PushNotification, Result, UserNotification | | 8 | `MarkNotificationReadHandler` | MMCA.Common.Application | 7 | Error, ICommandHandler, IQueryableExecutor, IUnitOfWork, MarkNotificationReadCommand, Result, UserNotification | | 8 | `MutateEntityHandlerCore` | MMCA.Common.Application | 6 | AuditableAggregateRootEntity, Error, IRepository, IUnitOfWork, MutationContext, Result | | 8 | `PushNotificationDTOMapper` | MMCA.Common.Application | 4 | IEntityDTOMapper, PushNotification, PushNotificationDTO, PushNotificationStatus | | 8 | `PushNotificationDTOProjection` | MMCA.Common.Application | 2 | PushNotification, PushNotificationDTO | -| 8 | `ResetPasswordHandlerBase` | MMCA.Common.Application | 14 | AuditableAggregateRootEntity, Error, ICommandHandler, ICommandWithRequest, ILoginProtectionService, IPasswordChangeableUser, IPasswordHasher, IPasswordResetTokenService, IRefreshSessionStore, IUnitOfWork, RefreshSessionRevocation, ResetPasswordRequest, Result, UserUseCaseLog | -| 8 | `SendEmailConfirmationHandlerBase` | MMCA.Common.Application | 12 | AuditableAggregateRootEntity, Email, EmailConfirmationSettings, ICommandHandler, ICommandWithRequest, IEmailConfirmableUser, IEmailConfirmationTokenService, IEmailSender, IUnitOfWork, Result, SendEmailConfirmationRequest, UserUseCaseLog | +| 8 | `ResetPasswordHandlerBase` | MMCA.Common.Application | 13 | AuditableAggregateRootEntity, Error, ICommandHandler, ICommandWithRequest, ILoginProtectionService, IPasswordChangeableUser, IPasswordHasher, IPasswordResetTokenService, IRefreshSessionStore, IUnitOfWork, RefreshSessionRevocation, ResetPasswordRequest, Result | +| 8 | `SendEmailConfirmationHandlerBase` | MMCA.Common.Application | 11 | AuditableAggregateRootEntity, Email, EmailConfirmationSettings, ICommandHandler, ICommandWithRequest, IEmailConfirmableUser, IEmailConfirmationTokenService, IEmailSender, IUnitOfWork, Result, SendEmailConfirmationRequest | | 8 | `SendPushNotificationRequestValidator` | MMCA.Common.Application | 3 | PushNotification, PushNotificationInvariants, SendPushNotificationRequest | | 8 | `SoftDeletedUserValidator` | MMCA.Common.Application | 3 | AuditableAggregateRootEntity, ISoftDeletedUserValidator, IUnitOfWork | | 8 | `TransactionalCommandDecorator` | MMCA.Common.Application | 3 | ICommandHandler, ITransactional, IUnitOfWork | @@ -3840,32 +3907,15 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 8 | `PushNotificationTests` | MMCA.Common.Domain.Tests | 3 | PushNotification, PushNotificationCreated, PushNotificationStatus | | 8 | `ClaimBasedUserIdProvider` | MMCA.Common.Infrastructure | 1 | User | | 8 | `IntegrationEventConsumer` | MMCA.Common.Infrastructure | 5 | ConsumerOriginRestore, EventNameResolver, IInboxStore, IIntegrationEvent, IIntegrationEventHandler | -| 8 | `PushNotificationConfiguration` | MMCA.Common.Infrastructure | 3 | EntityTypeConfigurationSQLServer, PushNotification, PushNotificationInvariants | -| 8 | `UserNotificationConfiguration` | MMCA.Common.Infrastructure | 2 | EntityTypeConfigurationSQLServer, UserNotification | -| 8 | `PgThingConfiguration` | MMCA.Common.Infrastructure.PostgreSQL.Tests | 2 | EntityTypeConfigurationPostgreSQL, PgThing | -| 8 | `DesignAlphaEntityConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | DesignAlphaEntity, EntityTypeConfigurationSQLServer | -| 8 | `DesignBetaEntityConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | DesignBetaEntity, EntityTypeConfigurationSQLServer | -| 8 | `DesignPostgreSQLEntityConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | DesignPostgreSQLEntity, EntityTypeConfigurationPostgreSQL | -| 8 | `DesignSqliteEntityConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | DesignSqliteEntity, EntityTypeConfigurationSqlite | | 8 | `EFRepositoryDecoratorAdditionalTests` | MMCA.Common.Infrastructure.Tests | 3 | EFRepositoryDecorator, FakeAggregateEntity, IRepository | | 8 | `EFRepositoryDecoratorTests` | MMCA.Common.Infrastructure.Tests | 3 | EFRepositoryDecorator, FakeAggregateEntity, IRepository | | 8 | `EntityTypeConfigurationBaseTests` | MMCA.Common.Infrastructure.Tests | 6 | TestAggregateEntity, TestAggregateEntityConfiguration, TestConfigDbContext, TestNonAggregateConfigDbContext, TestNonAggregateEntity, TestNonAggregateEntityConfiguration | -| 8 | `MultiSourceCustomerConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationSqlite, MultiSourceCustomer | -| 8 | `MultiSourceOrderConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationSqlite, MultiSourceOrder | | 8 | `NotificationTestDbContext` | MMCA.Common.Infrastructure.Tests | 2 | PushNotification, UserNotification | -| 8 | `PortablePrincipalConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationSQLServer, PortablePrincipal | -| 8 | `PostgresThingConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationPostgreSQL, PostgresThing | | 8 | `ProjectionTestDbContext` | MMCA.Common.Infrastructure.Tests | 1 | PushNotification | -| 8 | `RegistryDuplicateConfigurationA` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationSqlite, RegistryDuplicate | -| 8 | `RegistryDuplicateConfigurationB` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationSqlite, RegistryDuplicate | -| 8 | `RegistryInvoiceConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationSqlite, RegistryInvoice | -| 8 | `RegistryOrderConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationSqlite, RegistryOrder | -| 8 | `RegistrySqlServerEntityConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationSQLServer, RegistrySqlServerEntity | | 8 | `SeederMocks` | MMCA.Common.Infrastructure.Tests | 4 | IPasswordHasher, IRepository, IUnitOfWork, TestSeedUser | -| 8 | `SqliteTestEntityConfig` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationSqlite, SqliteTestEntity | -| 8 | `SqlServerThingConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationSQLServer, SqlServerThing | | 8 | `TestConnectionContext` | MMCA.Common.Infrastructure.Tests | 2 | TestDuplexPipe, User | | 8 | `DependencyInjection` | MMCA.Common.UI | 10 | INotificationInboxUIService, INotificationScopeProvider, IPushNotificationUIService, IUIModule, NotificationHubService, NotificationInboxService, NotificationState, NotificationUIModule, NullNotificationScopeProvider, PushNotificationService | +| 8 | `NotificationPageGate` | MMCA.Common.UI | 6 | IUIModule, LayoutSettings, NotificationInbox, NotificationList, NotificationSend, NotificationUIModule | | 8 | `GalleryAuthenticationStateProvider` | MMCA.Common.UI.Gallery | 1 | User | | 8 | `NotificationBellTests` | MMCA.Common.UI.Tests | 11 | BunitTestBase, Error, FakeTimeProvider, INotificationInboxUIService, IToastService, NotificationBell, NotificationBellHost, NotificationBellOptions, NotificationState, Result, TimerCountingTimeProvider | | 9 | `DecoratorPipelineOrderTests` | MMCA.ADC.Architecture.Tests | 11 | ChangePreferencesCommand, ClassReference, DecoratorPipelineOrderTestsBase, GetUserPreferencesQuery, ICacheService, ICorrelationContext, ICurrentUserService, IPermissionRegistry, IUnitOfWork, Result, UserPreferencesResponse | @@ -3877,10 +3927,10 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 9 | `SessionAssetsController` | MMCA.ADC.Conference.API | 19 | AddSessionAssetLinkCommand, ApiControllerBase, ConferencePermissions, DeleteSessionAssetCommand, Error, GetSessionAssetsQuery, ICommandHandler, ICurrentUserService, IPermissionRegistry, IQueryHandler, Result, Route, SessionAssetDTO, SessionAssetLimits, SessionAssetLinkRequest, SessionAssetUpdateRequest, SupportsIfMatchAttribute, UpdateSessionAssetCommand, UploadSessionAssetCommand | | 9 | `SpeakerCategoryItemsController` | MMCA.ADC.Conference.API | 19 | AddSpeakerCategoryItemCommand, AddSpeakerCategoryItemRequest, BaseLookup, CollectionResult, ConferencePermissions, EntityControllerBase, GetPublicSpeakerCategoryItemFilterQuery, ICommandHandler, ICurrentUserService, IEntityQueryService, IQueryHandler, PagedCollectionResult, QueryFilterModelBinder, RemoveSpeakerCategoryItemCommand, Result, Route, SpeakerCategoryItem, SpeakerCategoryItemDTO, Specification | | 9 | `SpeakerLinksController` | MMCA.ADC.Conference.API | 8 | ApiControllerBase, ConferencePermissions, ICommandHandler, LinkUserRequest, LinkUserToSpeakerCommand, Result, Route, UnlinkUserFromSpeakerCommand | -| 9 | `SpeakersController` | MMCA.ADC.Conference.API | 24 | AggregateRootEntityControllerBase, BaseLookup, CollectionResult, ConferencePermissions, DeleteEntityCommand, Error, GetPublicSpeakerFilterQuery, GetSpeakersByEventFilterQuery, ICommandHandler, ICurrentUserService, IEntityQueryService, IQueryHandler, PagedCollectionResult, QueryFilterModelBinder, Result, RoleNames, Route, Speaker, SpeakerCreateRequest, SpeakerDTO …(+4) | +| 9 | `SpeakersController` | MMCA.ADC.Conference.API | 24 | AggregateRootEntityControllerBase, BaseLookup, CollectionResult, ConferencePermissions, DeleteEntityCommand, Error, GetPublicSpeakerFilterQuery, GetSpeakersByEventFilterQuery, ICommandHandler, ICurrentUserService, IEntityQueryService, IPermissionRegistry, IQueryHandler, PagedCollectionResult, QueryFilterModelBinder, Result, Route, Speaker, SpeakerCreateRequest, SpeakerDTO …(+4) | | 9 | `SpeakerSessionsController` | MMCA.ADC.Conference.API | 10 | ApiControllerBase, GetSessionBookmarkCountQuery, GetSessionBookmarkCountsQuery, GetSessionFeedbackQuery, ICurrentUserService, IQueryHandler, Result, RoleNames, Route, SessionFeedbackDTO | -| 9 | `CategoryItemsControllerTests` | MMCA.ADC.Conference.API.Tests | 12 | AddCategoryItemCommand, AddCategoryItemRequest, CategoryItem, CategoryItemDTO, CategoryItemsController, Error, ICommandHandler, IEntityQueryService, RemoveCategoryItemCommand, Result, UpdateCategoryItemCommand, UpdateCategoryItemRequest | -| 9 | `ConferenceCategoriesControllerTests` | MMCA.ADC.Conference.API.Tests | 12 | Category, ConferenceCategoriesController, ConferenceCategoryCreateRequest, ConferenceCategoryDTO, ConferenceCategoryUpdateRequest, DeleteEntityCommand, Error, ICommandHandler, IEntityQueryService, Result, SupportsIfMatchAttribute, UpdateEntityCommand | +| 9 | `CategoryItemsControllerTests` | MMCA.ADC.Conference.API.Tests | 14 | AddCategoryItemCommand, AddCategoryItemRequest, CategoryItem, CategoryItemDTO, CategoryItemsController, Error, ICommandHandler, IEntityQueryService, ISpecification, PagedCollectionResult, RemoveCategoryItemCommand, Result, UpdateCategoryItemCommand, UpdateCategoryItemRequest | +| 9 | `ConferenceCategoriesControllerTests` | MMCA.ADC.Conference.API.Tests | 14 | Category, ConferenceCategoriesController, ConferenceCategoryCreateRequest, ConferenceCategoryDTO, ConferenceCategoryUpdateRequest, DeleteEntityCommand, Error, ICommandHandler, IEntityQueryService, ISpecification, PagedCollectionResult, Result, SupportsIfMatchAttribute, UpdateEntityCommand | | 9 | `ActivityCreateRequest` | MMCA.ADC.Conference.Application | 4 | Activity, IActivityFieldsRequest, ICacheInvalidating, ICreateRequest | | 9 | `ActivityDTOMapper` | MMCA.ADC.Conference.Application | 3 | Activity, ActivityDTO, IEntityDTOMapper | | 9 | `ActivityUpdateApplier` | MMCA.ADC.Conference.Application | 4 | Activity, ActivityUpdateRequest, IEntityUpdateApplier, Result | @@ -3957,6 +4007,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 9 | `UpdateSessionQuestionAnswerCommand` | MMCA.ADC.Conference.Application | 2 | ICacheInvalidating, Session | | 9 | `UploadSessionAssetCommandValidator` | MMCA.ADC.Conference.Application | 7 | DocumentContentSniffer, DocumentFormats, SessionAssetFieldRules, SessionAssetFileNameRules, SessionAssetLimits, SessionAssetSessionIdRules, UploadSessionAssetCommand | | 9 | `UploadSessionAssetHandler` | MMCA.ADC.Conference.Application | 17 | BlobNames, DeleteSessionAssetBlobInternalCommand, DocumentContentSniffer, DocumentFormats, Error, FileUploadOptions, ICommandHandler, IFileStorageService, IInternalCommandScheduler, ISessionAssetAccessService, IUnitOfWork, Result, SessionAsset, SessionAssetDTO, SessionAssetDTOMapper, SessionAssetLimits, UploadSessionAssetCommand | +| 9 | `UserDeletedFeedbackHandler` | MMCA.ADC.Conference.Application | 5 | Event, IUnitOfWork, ScopedIntegrationEventHandlerBase, Session, UserDeleted | | 9 | `AddCategoryItemCommandValidatorTests` | MMCA.ADC.Conference.Application.Tests | 3 | AddCategoryItemCommand, AddCategoryItemCommandValidator, CategoryInvariants | | 9 | `ConferenceCategoryCreateRequestValidatorTests` | MMCA.ADC.Conference.Application.Tests | 3 | CategoryInvariants, ConferenceCategoryCreateRequest, ConferenceCategoryCreateRequestValidator | | 9 | `ConferenceCategoryDTOMapperTests` | MMCA.ADC.Conference.Application.Tests | 3 | Category, CategoryItemDTOMapper, ConferenceCategoryDTOMapper | @@ -3986,15 +4037,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 9 | `SessionBuilder` | MMCA.ADC.Conference.Domain.Tests | 2 | EntityBuilderBase, Session | | 9 | `SessionTests` | MMCA.ADC.Conference.Domain.Tests | 5 | DomainEntityState, Session, SessionCategoryItemChanged, SessionChanged, SessionSpeakerChanged | | 9 | `SponsorBuilder` | MMCA.ADC.Conference.Domain.Tests | 3 | EntityBuilderBase, Sponsor, SponsorTier | -| 9 | `ActivityConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | Activity, ActivityInvariants, EntityTypeConfigurationSQLServer | | 9 | `ConferenceModuleDbSeeder` | MMCA.ADC.Conference.Infrastructure | 14 | Activity, DbSeeder, Event, IRepository, IUnitOfWork, Partner, PartnerType, Question, QuestionInvariants, Session, SessionInvariants, Speaker, Sponsor, SponsorTier | -| 9 | `PartnerConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | EntityTypeConfigurationSQLServer, Partner, PartnerInvariants | -| 9 | `SessionAssetConfiguration` | MMCA.ADC.Conference.Infrastructure | 5 | EntityTypeConfigurationSQLServer, Event, Session, SessionAsset, SessionAssetInvariants | -| 9 | `SessionCategoryItemConfiguration` | MMCA.ADC.Conference.Infrastructure | 2 | EntityTypeConfigurationSQLServer, SessionCategoryItem | -| 9 | `SessionConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | EntityTypeConfigurationSQLServer, Session, SessionInvariants | -| 9 | `SessionQuestionAnswerConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | EntityTypeConfigurationSQLServer, SessionInvariants, SessionQuestionAnswer | -| 9 | `SessionSpeakerConfiguration` | MMCA.ADC.Conference.Infrastructure | 2 | EntityTypeConfigurationSQLServer, SessionSpeaker | -| 9 | `SponsorConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | EntityTypeConfigurationSQLServer, Sponsor, SponsorInvariants | | 9 | `ConferenceTestDbContext` | MMCA.ADC.Conference.Infrastructure.Tests | 14 | Category, CategoryItem, Event, EventQuestionAnswer, EventSpeaker, Question, Room, Session, SessionCategoryItem, SessionQuestionAnswer, SessionSpeaker, Speaker, SpeakerCategoryItem, SpeakerQuestionAnswer | | 9 | `SessionMappingOnlyDbContext` | MMCA.ADC.Conference.Infrastructure.Tests | 1 | Session | | 9 | `CurrentEventDefaults` | MMCA.ADC.Conference.Shared | 2 | CurrentEventSelector, EventDTO | @@ -4006,21 +4049,21 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 9 | `PartnerCreate` | MMCA.ADC.Conference.UI | 13 | ConferenceRoutePaths, CurrentEventSelector, DataAnnotationsModelValidator, ErrorMessages, EventInfo, EventLookupService, IEventLookupService, IPartnerUIService, IToastService, ModelValidation, PartnerCreateModel, PartnerService, Result | | 9 | `PartnerDetail` | MMCA.ADC.Conference.UI | 14 | ConferenceRoutePaths, DataAnnotationsModelValidator, ErrorMessages, EventInfo, EventLookupService, IEventLookupService, IPartnerUIService, IToastService, ModelValidation, Partner, PartnerDTO, PartnerEditModel, PartnerService, PartnerType | | 9 | `PublicActivityList` | MMCA.ADC.Conference.UI | 8 | ActivityDTO, ActivityService, CurrentEventSelector, EventLookupService, IActivityUIService, IEventLookupService, IMapNavigationService, IToastService | -| 9 | `PublicEventList` | MMCA.ADC.Conference.UI | 13 | ConferenceReadAudience, ConferenceRoutePaths, CurrentEventSelector, DataGridListPageBase, EventDTO, EventInfo, EventLookupService, EventService, IEventLookupService, IEventUIService, ListPageActions, MobileInfiniteScrollList, Result | +| 9 | `PublicEventList` | MMCA.ADC.Conference.UI | 13 | ConferenceRoutePaths, CurrentEventSelector, DataGridListPageBase, EventDTO, EventInfo, EventLookupService, EventService, IEventLookupService, IEventUIService, ListPageActions, MobileInfiniteScrollList, PublicReadAudience, Result | | 9 | `PublicSessionDetail` | MMCA.ADC.Conference.UI | 16 | ConferenceRoutePaths, ICategoryItemLookupService, IRoomUIService, ISessionLiveUIService, ISessionUIService, ISpeakerLookupService, ITextToSpeechService, IToastService, LatestLoadGuard, RoomDTO, RoomService, Session, SessionDTO, SessionLive, SessionService, SessionStatuses | | 9 | `PublicSponsorList` | MMCA.ADC.Conference.UI | 7 | CurrentEventSelector, EventLookupService, IEventLookupService, ISponsorUIService, SponsorDTO, SponsorService, SponsorTier | -| 9 | `SessionDetail` | MMCA.ADC.Conference.UI | 26 | CategoryItemInfo, CategoryItemLookupService, ConferenceRoutePaths, DataAnnotationsModelValidator, ErrorMessages, EventLookupService, ICategoryItemLookupService, IEventLookupService, IRoomUIService, ISessionCategoryItemUIService, ISessionSpeakerUIService, ISessionUIService, ISpeakerLookupService, IToastService, ModelValidation, Result, RoomService, Session, SessionCategoryItemService, SessionDTO …(+6) | +| 9 | `SessionDetail` | MMCA.ADC.Conference.UI | 27 | CategoryItemInfo, CategoryItemLookupService, ConferenceRoutePaths, DataAnnotationsModelValidator, ErrorMessages, EventLookupService, ICategoryItemLookupService, IEventLookupService, IRoomUIService, ISessionCategoryItemUIService, ISessionSpeakerUIService, ISessionUIService, ISpeakerLookupService, IToastService, ModelValidation, Result, RoomService, Session, SessionCategoryItemService, SessionDTO …(+7) | | 9 | `SessionSelectionDashboard` | MMCA.ADC.Conference.UI | 12 | ConferenceRoutePaths, CurrentEventSelector, EventInfo, EventLookupService, IEventLookupService, ISessionSelectionUIService, IToastService, ScorePollHost, ScorePollSession, SessionSelectionDashboardDTO, SessionSelectionFilters, ToastSeverity | -| 9 | `SpeakerDashboard` | MMCA.ADC.Conference.UI | 12 | CurrentEventSelector, Email, EventInfo, EventLookupService, IEventLookupService, ISpeakerDashboardUIService, ISpeakerUIService, IToastService, SessionDTO, SessionFeedbackDTO, SpeakerDTO, SpeakerService | +| 9 | `SpeakerDashboard` | MMCA.ADC.Conference.UI | 11 | CurrentEventSelector, EventInfo, EventLookupService, IEventLookupService, ISpeakerDashboardUIService, ISpeakerUIService, IToastService, SessionDTO, SessionFeedbackDTO, SpeakerDTO, SpeakerService | | 9 | `SponsorCreate` | MMCA.ADC.Conference.UI | 13 | ConferenceRoutePaths, CurrentEventSelector, DataAnnotationsModelValidator, ErrorMessages, EventInfo, EventLookupService, IEventLookupService, ISponsorUIService, IToastService, ModelValidation, Result, SponsorCreateModel, SponsorService | | 9 | `SponsorDetail` | MMCA.ADC.Conference.UI | 14 | ConferenceRoutePaths, DataAnnotationsModelValidator, ErrorMessages, EventInfo, EventLookupService, IEventLookupService, ISponsorUIService, IToastService, ModelValidation, Sponsor, SponsorDTO, SponsorEditModel, SponsorService, SponsorTier | | 9 | `ComponentsSnapshotTests` | MMCA.ADC.Conference.UI.Tests | 10 | ApiSettings, BunitTestBase, EventDTO, MarkupSnapshot, PublicSessionListFilterBar, PublicSessionListView, Result, RoomDTO, Session, SessionDTO | -| 9 | `EventDetailTests` | MMCA.ADC.Conference.UI.Tests | 6 | BunitTestBase, EventDetail, EventDTO, IEventUIService, QuestionModerationDefault, Result | +| 9 | `EventDetailTests` | MMCA.ADC.Conference.UI.Tests | 8 | BunitTestBase, Error, EventDetail, EventDTO, IEventUIService, QuestionModerationDefault, Result, SessionizeRefreshOutcome | | 9 | `ManagementRouteAuthorizationTests` | MMCA.ADC.Conference.UI.Tests | 2 | PublicEventDetail, RouteAuthorizationTestsBase | | 9 | `PublicEventDetailTests` | MMCA.ADC.Conference.UI.Tests | 9 | BunitTestBase, Error, Event, EventDTO, IClipboardService, IEventUIService, IMapNavigationService, PublicEventDetail, Result | | 9 | `PublicSessionListViewBookmarkTests` | MMCA.ADC.Conference.UI.Tests | 10 | BunitTestBase, Error, ISessionBookmarkUIService, IToastService, ProblemDetailsResultReader, PublicSessionListView, Result, Session, SessionDTO, UserSessionBookmarkDTO | | 9 | `QuestionDetailTests` | MMCA.ADC.Conference.UI.Tests | 7 | BunitTestBase, Error, IQuestionUIService, Question, QuestionDetail, QuestionDTO, Result | -| 9 | `RoomDetailTests` | MMCA.ADC.Conference.UI.Tests | 6 | BunitTestBase, EventInfo, IEventLookupService, IRoomUIService, RoomDetail, RoomDTO | +| 9 | `RoomDetailTests` | MMCA.ADC.Conference.UI.Tests | 7 | BunitTestBase, EventInfo, IEventLookupService, IRoomUIService, Result, RoomDetail, RoomDTO | | 9 | `SpeakerDashboardServiceTests` | MMCA.ADC.Conference.UI.Tests | 11 | CapturingHttpMessageHandler, ErrorType, HttpTestDoubles, PagedCollectionResult, PaginationMetadata, RatingQuestionSummary, Session, SessionDTO, SessionFeedbackDTO, SessionSpeakerDTO, SpeakerDashboardService | | 9 | `LivePollsController` | MMCA.ADC.Engagement.API | 19 | ApiControllerBase, CloseLivePollCommand, CreateLivePollCommand, CreateLivePollRequest, DeleteEntityCommand, EngagementFeatures, EngagementPermissions, GetEventPollsQuery, GetSessionManagePollsQuery, ICommandHandler, ICurrentUserService, IQueryHandler, LivePoll, LivePollDTO, OpenLivePollCommand, Result, RoleNames, Route, SupportsIfMatchAttribute | | 9 | `LivePollVotingController` | MMCA.ADC.Engagement.API | 14 | ApiControllerBase, CastVoteCommand, CastVoteRequest, EngagementFeatures, Error, GetOpenPollsQuery, GetPollResultsQuery, ICommandHandler, ICurrentUserService, IQueryHandler, LivePollResultsDTO, Result, RoleNames, Route | @@ -4031,7 +4074,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 9 | `GetModerationQueueHandler` | MMCA.ADC.Engagement.Application | 10 | GetModerationQueueQuery, IEventLiveValidationService, IQueryableExecutor, IQueryHandler, IUnitOfWork, LivePollAuthorization, Result, SessionQuestion, SessionQuestionDTO, SessionQuestionViewBuilder | | 9 | `GetMyPointsHandler` | MMCA.ADC.Engagement.Application | 10 | GetMyPointsQuery, ICurrentUserService, IQueryHandler, IUnitOfWork, LeaderboardOptIn, MyPointsDTO, PagingMath, PointsEntry, PointsEntryDTO, Result | | 9 | `GetOrCreateMyBadgeHandler` | MMCA.ADC.Engagement.Application | 10 | AttendeeBadge, Error, GetOrCreateMyBadgeCommand, ICommandHandler, ICurrentUserService, IEntityQuerier, IUniqueConstraintViolationDetector, IUnitOfWork, MyBadgeDTO, Result | -| 9 | `GetSessionQuestionsHandler` | MMCA.ADC.Engagement.Application | 10 | GetSessionQuestionsQuery, IQueryableExecutor, IQueryHandler, IUnitOfWork, QuestionStatus, Result, SessionQuestion, SessionQuestionDTO, SessionQuestionUpvote, SessionQuestionViewBuilder | +| 9 | `GetSessionQuestionsHandler` | MMCA.ADC.Engagement.Application | 11 | GetSessionQuestionsQuery, IEventLiveValidationService, IQueryableExecutor, IQueryHandler, IUnitOfWork, QuestionStatus, Result, SessionQuestion, SessionQuestionDTO, SessionQuestionUpvote, SessionQuestionViewBuilder | | 9 | `GetUserBookmarksHandler` | MMCA.ADC.Engagement.Application | 12 | GetUserBookmarksQuery, IQueryableExecutor, IQueryHandler, ISessionBookmarkValidationService, IUnitOfWork, PagedCollectionResult, PaginationMetadata, PagingMath, Result, UserSessionBookmark, UserSessionBookmarkDTO, UserSessionBookmarkDTOMapper | | 9 | `LivePollDTOMapper` | MMCA.ADC.Engagement.Application | 3 | IEntityDTOMapper, LivePoll, LivePollDTO | | 9 | `LivePollResultsBuilder` | MMCA.ADC.Engagement.Application | 7 | IQueryableExecutor, IUnitOfWork, LivePoll, LivePollOptionResultDTO, LivePollResultsDTO, LivePollVote, Question | @@ -4044,8 +4087,6 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 9 | `BookmarkCountServiceGrpcAdapter` | MMCA.ADC.Engagement.Contracts | 2 | BookmarkCountService, IBookmarkCountService | | 9 | `BookmarkManagementDomainServiceTests` | MMCA.ADC.Engagement.Domain.Tests | 2 | BookmarkManagementDomainService, UserSessionBookmark | | 9 | `LivePollTests` | MMCA.ADC.Engagement.Domain.Tests | 6 | DomainEntityState, LivePoll, LivePollChanged, LivePollInvariants, LivePollOption, LivePollStatus | -| 9 | `LivePollConfiguration` | MMCA.ADC.Engagement.Infrastructure | 3 | EntityTypeConfigurationSQLServer, LivePoll, LivePollInvariants | -| 9 | `LivePollOptionConfiguration` | MMCA.ADC.Engagement.Infrastructure | 3 | EntityTypeConfigurationSQLServer, LivePollInvariants, LivePollOption | | 9 | `BookmarkCountsGrpcService` | MMCA.ADC.Engagement.Service | 2 | BookmarkCountService, IBookmarkCountService | | 9 | `CheckInScopeNames` | MMCA.ADC.Engagement.Shared | 4 | CheckInScope, Event, Session, Sponsor | | 9 | `LiveEventService` | MMCA.ADC.Engagement.UI | 5 | CurrentEventSelector, EventDTO, ILiveEventUIService, LiveEventContext, PagedCollectionResult | @@ -4053,7 +4094,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 9 | `SessionLiveModerationPanelTests` | MMCA.ADC.Engagement.UI.Tests | 11 | BunitComponentTestBase, CreateLivePollRequest, ILivePollUIService, ISessionQuestionUIService, LivePollDTO, LivePollStatus, Question, QuestionStatus, Result, SessionLiveModerationPanel, SessionQuestionDTO | | 9 | `SessionReminderPlannerTests` | MMCA.ADC.Engagement.UI.Tests | 3 | Session, SessionInfo, SessionReminderPlanner | | 9 | `UsersController` | MMCA.ADC.Identity.API | 16 | ApiControllerBase, DeleteUserCommand, Error, GetUserAvatarQuery, GetUsersQuery, ICommandHandler, ICurrentUserService, IdentityPermissions, IQueryHandler, PagedCollectionResult, RemoveUserAvatarCommand, Result, Route, SetUserAvatarCommand, UserAvatarDTO, UserListDTO | -| 9 | `ChangePasswordHandler` | MMCA.ADC.Identity.Application | 6 | ChangePasswordCommand, ChangePasswordHandlerBase, IPasswordHasher, IRefreshSessionStore, IUnitOfWork, User | +| 9 | `ChangePasswordHandler` | MMCA.ADC.Identity.Application | 7 | ChangePasswordCommand, ChangePasswordHandlerBase, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, IUnitOfWork, User | | 9 | `ChangePreferencesCommandValidator` | MMCA.ADC.Identity.Application | 3 | ChangePreferencesCommand, CommonInvariants, SupportedCultures | | 9 | `ChangePreferencesHandler` | MMCA.ADC.Identity.Application | 4 | ChangePreferencesCommand, ChangePreferencesHandlerBase, IUnitOfWork, User | | 9 | `ConfirmEmailHandler` | MMCA.ADC.Identity.Application | 5 | ConfirmEmailCommand, ConfirmEmailHandlerBase, IEmailConfirmationTokenService, IUnitOfWork, User | @@ -4066,12 +4107,11 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 9 | `SoftDeletedUserValidatorTests` | MMCA.ADC.Identity.Application.Tests | 4 | IRepository, IUnitOfWork, SoftDeletedUserValidator, User | | 9 | `UserDTOMapperTests` | MMCA.ADC.Identity.Application.Tests | 3 | User, UserDTOMapper, UserRole | | 9 | `AttendeeQueryServiceGrpcAdapter` | MMCA.ADC.Identity.Contracts | 2 | AttendeeQueryService, IAttendeeQueryService | -| 9 | `IdentityTestDbContext` | MMCA.ADC.Identity.Infrastructure.Tests | 4 | DataSource, SoftDeleteUniqueIndexConvention, User, UserConfiguration | | 9 | `AttendeesGrpcService` | MMCA.ADC.Identity.Service | 2 | AttendeeQueryService, IAttendeeQueryService | | 9 | `DependencyInjection` | MMCA.ADC.Notification.Application | 5 | ApplicationSettings, AttendeeNotificationRecipientProvider, INotificationRecipientProvider, IUserNotificationExportService, UserNotificationExportService | | 9 | `DependencyInjectionTests` | MMCA.ADC.Notification.Application.Tests | 6 | ApplicationSettings, AttendeeNotificationRecipientProvider, DependencyInjectionAssert, INotificationRecipientProvider, IUserNotificationExportService, UserNotificationExportService | -| 9 | `UserNotificationExportServiceGrpcAdapter` | MMCA.ADC.Notification.Contracts | 3 | IUserNotificationExportService, UserNotificationExportItemDTO, UserNotificationExportService | -| 9 | `UserNotificationExportGrpcService` | MMCA.ADC.Notification.Service | 2 | IUserNotificationExportService, UserNotificationExportService | +| 9 | `UserNotificationExportServiceGrpcAdapter` | MMCA.ADC.Notification.Contracts | 4 | GrpcWireFormat, IUserNotificationExportService, UserNotificationExportItemDTO, UserNotificationExportService | +| 9 | `UserNotificationExportGrpcService` | MMCA.ADC.Notification.Service | 3 | GrpcWireFormat, IUserNotificationExportService, UserNotificationExportService | | 9 | `MainActivity` | MMCA.ADC.UI | 3 | Activity, DeepLinkDispatcher, IDeepLinkDispatcher | | 9 | `WebAuthenticatorCallbackActivity` | MMCA.ADC.UI | 1 | Activity | | 9 | `PromptTaggingChatClient` | MMCA.Common.AI | 3 | Activity, AiUsageMeter, PromptContract | @@ -4079,7 +4119,8 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 9 | `DevicesController` | MMCA.Common.API | 8 | ApiControllerBase, DeviceInstallationRequest, Error, ICurrentUserService, IPushDeviceRegistrar, NotificationFeatures, Result, Route | | 9 | `InboxController` | MMCA.Common.API | 15 | ApiControllerBase, Error, GetMyNotificationsQuery, GetUnreadNotificationCountQuery, ICommandHandler, ICurrentUserService, IQueryHandler, MarkAllNotificationsReadCommand, MarkNotificationReadCommand, NotificationFeatures, PagedCollectionResult, PushNotification, Result, Route, UserNotificationDTO | | 9 | `NotificationsController` | MMCA.Common.API | 15 | ApiControllerBase, Error, GetNotificationHistoryQuery, ICommandHandler, ICurrentUserService, IdempotencyHeaders, IQueryHandler, NotificationFeatures, NotificationPermissions, PagedCollectionResult, PushNotificationDTO, Result, Route, SendPushNotificationCommand, SendPushNotificationRequest | -| 9 | `OwnershipHelper` | MMCA.Common.API | 1 | ICurrentUserService | +| 9 | `OwnershipHelper` | MMCA.Common.API | 3 | Error, ICurrentUserService, Result | +| 9 | `SessionRefreshOutcome` | MMCA.Common.API | 3 | Session, SessionRefreshStatus, SessionTokenResult | | 9 | `SoftDeletedUserMiddleware` | MMCA.Common.API | 4 | ICacheService, ICurrentUserService, ISoftDeletedUserValidator, SoftDeletedUserCache | | 9 | `AggregateRootEntityControllerBaseTests` | MMCA.Common.API.Tests | 11 | ApplicationSettings, DeleteEntityCommand, EntityControllerBase, Error, ICommandHandler, IEntityQueryService, Result, TestAggDTO, TestAggregateEntity, TestAggregateRootController, TestCreateRequest | | 9 | `CurrentUserTargetingContextAccessorTests` | MMCA.Common.API.Tests | 3 | AuthClaimTypes, CurrentUserTargetingContextAccessor, User | @@ -4097,7 +4138,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 9 | `SendPushNotificationHandler` | MMCA.Common.Application | 12 | Error, ICommandHandler, INativePushSender, INotificationRecipientProvider, IPushNotificationSender, IUnitOfWork, PushNotification, PushNotificationDTO, PushNotificationDTOMapper, Result, SendPushNotificationCommand, UserNotification | | 9 | `ApplicationPipelineCompositionTests` | MMCA.Common.Application.Tests | 14 | ICacheService, ICommandHandler, ICorrelationContext, ICurrentUserService, IDomainEventDispatcher, IPermissionRegistry, IQueryHandler, IUnitOfWork, PipelineMarker, PipelinePingCommand, PipelinePingCommandHandler, PipelinePingQuery, PipelinePingQueryHandler, Result | | 9 | `CommandDecoratorPipelineTests` | MMCA.Common.Application.Tests | 13 | CachePipelineTestCommand, CachingCommandDecorator, Error, FullPipelineTestCommand, ICacheService, ICommandHandler, ICorrelationContext, IUnitOfWork, LoggingCommandDecorator, PipelineTestCommand, Result, TransactionalCommandDecorator, TransactionalPipelineTestCommand | -| 9 | `ConfirmableAuthenticationService` | MMCA.Common.Application.Tests | 15 | AuthenticationServiceBase, AuthenticationValidators, ConfirmableAuthUser, Email, EmailConfirmationSettings, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, ITokenService, ITwoFactorAuthenticator, IUnitOfWork, RefreshSessionSettings, RegisterRequest, Result, TokenService | +| 9 | `ConfirmableAuthenticationService` | MMCA.Common.Application.Tests | 16 | AuthenticationServiceBase, AuthenticationValidators, AuthSessionIssuer, ConfirmableAuthUser, Email, EmailConfirmationSettings, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, ITokenService, ITwoFactorAuthenticator, IUnitOfWork, RefreshSessionSettings, RegisterRequest, Result, TokenService | | 9 | `CrossSourceSpecificationTests` | MMCA.Common.Application.Tests | 5 | CrossSourceSpecification, Dependent, IReadRepository, IUnitOfWork, Principal | | 9 | `DeleteEntityHandlerTests` | MMCA.Common.Application.Tests | 6 | DeleteEntityCommand, DeleteEntityHandler, ErrorType, IRepository, IUnitOfWork, TestAggregateEntity | | 9 | `EntityQueryServiceProjectionTests` | MMCA.Common.Application.Tests | 16 | EntityQueryPipeline, EntityQueryService, IEntityQueryPipeline, INavigationMetadataProvider, INavigationPopulator, InlineSpecification, InMemoryQueryableExecutor, IReadRepository, IUnitOfWork, NavigationMetadata, NavigationPropertyInfo, NavigationType, ProjectedEntity, ProjectedEntityDTO, SpyMapper, TestProjector | @@ -4105,19 +4146,18 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 9 | `GetMyNotificationsHandlerTests` | MMCA.Common.Application.Tests | 10 | GetMyNotificationsHandler, GetMyNotificationsQuery, IQueryableExecutor, IRepository, IUnitOfWork, PagedCollectionResult, PushNotification, Result, UserNotification, UserNotificationDTO | | 9 | `GetUnreadNotificationCountHandlerTests` | MMCA.Common.Application.Tests | 9 | GetUnreadNotificationCountHandler, GetUnreadNotificationCountQuery, HandlerMocks, IQueryableExecutor, IRepository, IUnitOfWork, PushNotification, Result, UserNotification | | 9 | `MappedEntityQueryService` | MMCA.Common.Application.Tests | 8 | EntityQueryService, FakeEntity, FakeEntityDTO, IEntityDTOMapper, IEntityQueryPipeline, INavigationMetadataProvider, INavigationPopulator, IUnitOfWork | -| 9 | `MarkAllNotificationsReadHandlerTests` | MMCA.Common.Application.Tests | 10 | FixedTimeProvider, HandlerMocks, IQueryableExecutor, IRepository, IUnitOfWork, MarkAllNotificationsReadCommand, MarkAllNotificationsReadHandler, PushNotification, Result, UserNotification | | 9 | `MarkNotificationReadHandlerTests` | MMCA.Common.Application.Tests | 9 | FixedTimeProvider, HandlerMocks, IQueryableExecutor, IRepository, IUnitOfWork, MarkNotificationReadCommand, MarkNotificationReadHandler, Result, UserNotification | | 9 | `NarrowedQueryService` | MMCA.Common.Application.Tests | 9 | AccountDTO, AccountEntity, EntityQueryService, IEntityDTOMapper, IEntityQueryPipeline, INavigationMetadataProvider, INavigationPopulator, IUnitOfWork, QueryFieldContract | | 9 | `PushNotificationDTOMapperTests` | MMCA.Common.Application.Tests | 5 | PushNotification, PushNotificationDTO, PushNotificationDTOMapper, PushNotificationStatus, Result | | 9 | `RefusingPrepareCreateOrderHandler` | MMCA.Common.Application.Tests | 9 | CreateEntityHandlerBase, Error, IEntityDTOMapper, IEntityRequestMapper, IUnitOfWork, OrderAggregate, OrderCreateRequest, OrderDTO, Result | | 9 | `RewritingPrepareCreateOrderHandler` | MMCA.Common.Application.Tests | 8 | CreateEntityHandlerBase, IEntityDTOMapper, IEntityRequestMapper, IUnitOfWork, OrderAggregate, OrderCreateRequest, OrderDTO, Result | | 9 | `SendPushNotificationRequestValidatorTests` | MMCA.Common.Application.Tests | 4 | PushNotification, PushNotificationInvariants, SendPushNotificationRequest, SendPushNotificationRequestValidator | -| 9 | `SessionAwareAuthenticationService` | MMCA.Common.Application.Tests | 13 | AuthenticationServiceBase, AuthenticationValidators, Email, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, ITokenService, IUnitOfWork, RefreshSessionSettings, RegisterRequest, Result, TestAuthUser, TokenService | +| 9 | `SessionAwareAuthenticationService` | MMCA.Common.Application.Tests | 14 | AuthenticationServiceBase, AuthenticationValidators, AuthSessionIssuer, Email, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, ITokenService, IUnitOfWork, RefreshSessionSettings, RegisterRequest, Result, TestAuthUser, TokenService | | 9 | `SoftDeletedUserValidatorTests` | MMCA.Common.Application.Tests | 4 | IRepository, IUnitOfWork, SoftDeletedUserValidator, TestIdentityUser | | 9 | `TestableEntityQueryService` | MMCA.Common.Application.Tests | 8 | EntityQueryService, FakeEntity, FakeEntityDTO, IEntityDTOMapper, IEntityQueryPipeline, INavigationMetadataProvider, INavigationPopulator, IUnitOfWork | | 9 | `TestAddOrderLineHandler` | MMCA.Common.Application.Tests | 7 | AddChildEntityHandlerBase, AddOrderLineCommand, IUnitOfWork, OrderAggregate, OrderLine, OrderLineDTO, Result | -| 9 | `TestAuthenticationService` | MMCA.Common.Application.Tests | 12 | AuthenticationServiceBase, AuthenticationValidators, Email, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, ITokenService, IUnitOfWork, RefreshSessionSettings, RegisterRequest, Result, TestAuthUser | -| 9 | `TestChangePasswordHandler` | MMCA.Common.Application.Tests | 6 | ChangePasswordHandlerBase, IPasswordHasher, IRefreshSessionStore, IUnitOfWork, TestChangePasswordCommand, TestIdentityUser | +| 9 | `TestAuthenticationService` | MMCA.Common.Application.Tests | 13 | AuthenticationServiceBase, AuthenticationValidators, AuthSessionIssuer, Email, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, ITokenService, IUnitOfWork, RefreshSessionSettings, RegisterRequest, Result, TestAuthUser | +| 9 | `TestChangePasswordHandler` | MMCA.Common.Application.Tests | 7 | ChangePasswordHandlerBase, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, IUnitOfWork, TestChangePasswordCommand, TestIdentityUser | | 9 | `TestChangePreferencesHandler` | MMCA.Common.Application.Tests | 4 | ChangePreferencesHandlerBase, IUnitOfWork, TestChangePreferencesCommand, TestIdentityUser | | 9 | `TestConfirmEmailHandler` | MMCA.Common.Application.Tests | 5 | ConfirmableUser, ConfirmEmailHandlerBase, IEmailConfirmationTokenService, IUnitOfWork, TestConfirmEmailCommand | | 9 | `TestCreateOrderHandler` | MMCA.Common.Application.Tests | 7 | CreateEntityHandlerBase, IEntityDTOMapper, IEntityRequestMapper, IUnitOfWork, OrderAggregate, OrderCreateRequest, OrderDTO | @@ -4137,10 +4177,11 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 9 | `ProbeTelemetryToggleTests` | MMCA.Common.Aspire.Tests | 1 | Activity | | 9 | `BrokerMessageBus` | MMCA.Common.Infrastructure | 6 | ICorrelationContext, ICurrentUserService, IIntegrationEvent, IMessageBus, ITenantContext, MessageHeaders | | 9 | `CurrentUserService` | MMCA.Common.Infrastructure | 2 | ICurrentUserService, User | -| 9 | `EFRepository` | MMCA.Common.Infrastructure | 9 | AuditableAggregateRootEntity, AuditableBaseEntity, EFReadRepository, IAuditableEntity, ICurrentUserService, IRepository, IRowVersioned, IUpdatePropertySetter, UpdatePropertySetterBuilder | | 9 | `ImpersonatingCurrentUserService` | MMCA.Common.Infrastructure | 2 | ICurrentUserService, ScopedUserOverride | +| 9 | `InternalCommandOriginCapture` | MMCA.Common.Infrastructure | 6 | Activity, AmbientOrigin, ICorrelationContext, ICurrentUserService, InternalCommandOrigin, ITenantContext | | 9 | `OutboxMessage` | MMCA.Common.Infrastructure | 6 | Activity, EventNameResolver, IDomainEvent, IHasOrderingKey, OutboxOrigin, Payload | | 9 | `UpcastingIntegrationEventConsumer` | MMCA.Common.Infrastructure | 7 | ConsumerOriginRestore, EventNameResolver, IEventUpcasterRegistry, IInboxStore, IIntegrationEvent, IIntegrationEventHandler, IntegrationEventConsumer | +| 9 | `FixedCurrentUserService` | MMCA.Common.Infrastructure.SQLServer.Tests | 1 | ICurrentUserService | | 9 | `AnonymousCurrentUserService` | MMCA.Common.Infrastructure.Tests | 1 | ICurrentUserService | | 9 | `CapturingMessageBus` | MMCA.Common.Infrastructure.Tests | 6 | ICorrelationContext, ICurrentUserService, IIntegrationEvent, IMessageBus, ITenantContext, Observation | | 9 | `ClaimBasedUserIdProviderTests` | MMCA.Common.Infrastructure.Tests | 3 | AuthClaimTypes, ClaimBasedUserIdProvider, TestConnectionContext | @@ -4149,30 +4190,29 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 9 | `IntegrationEventConsumerTests` | MMCA.Common.Infrastructure.Tests | 6 | IInboxStore, IIntegrationEventHandler, IntegrationEventConsumer, NamedIntegrationEvent, TestIntegrationEvent, TestIntegrationEvent | | 9 | `NullUserService` | MMCA.Common.Infrastructure.Tests | 1 | ICurrentUserService | | 9 | `OverrideOnlyCurrentUserService` | MMCA.Common.Infrastructure.Tests | 3 | ICurrentUserService, ScopedUserOverride, User | -| 9 | `PushNotificationTestDbContext` | MMCA.Common.Infrastructure.Tests | 1 | PushNotificationConfiguration | | 9 | `RecordingCommandHandler` | MMCA.Common.Infrastructure.Tests | 7 | ExecutionLog, ICommandHandler, ICurrentUserService, ITenantContext, RecordedExecution, RecordingCommand, Result | | 9 | `RoleOnlyService` | MMCA.Common.Infrastructure.Tests | 1 | ICurrentUserService | -| 9 | `SqliteTestDbContext` | MMCA.Common.Infrastructure.Tests | 2 | SqliteTestEntity, SqliteTestEntityConfig | | 9 | `StubCurrentUserService` | MMCA.Common.Infrastructure.Tests | 1 | ICurrentUserService | | 9 | `GalleryHost` | MMCA.Common.UI.Gallery | 18 | GalleryAuthenticationStateProvider, GalleryFakeAuthenticationHandler, GalleryUIModule, IAuthUIService, INotificationInboxUIService, INotificationScopeProvider, IPushNotificationUIService, ITokenRefresher, ITokenStorageService, IUIModule, NoOpAuthUIService, NotificationState, NullNotificationScopeProvider, NullTokenRefresher, NullTokenStorageService, StubNotificationInboxUIService, StubPushNotificationUIService, SupportedCultures | +| 9 | `NotificationPageGateTests` | MMCA.Common.UI.Tests | 7 | LayoutSettings, NotificationInbox, NotificationList, NotificationPageGate, NotificationSend, NotificationUIModule, OtherModule | | 10 | `ActivitiesController` | MMCA.ADC.Conference.API | 21 | Activity, ActivityCreateRequest, ActivityDTO, ActivityUpdateRequest, AggregateRootEntityControllerBase, BaseLookup, CollectionResult, ConferencePermissions, DeleteEntityCommand, GetPublicActivityFilterQuery, ICommandHandler, ICurrentUserService, IEntityQueryService, IQueryHandler, PagedCollectionResult, QueryFilterModelBinder, Result, Route, Specification, SupportsIfMatchAttribute …(+1) | | 10 | `ConferenceModuleSeeder` | MMCA.ADC.Conference.API | 3 | ConferenceModuleDbSeeder, IModuleSeeder, IUnitOfWork | -| 10 | `EventQuestionAnswersController` | MMCA.ADC.Conference.API | 25 | AddEventQuestionAnswerCommand, AddEventQuestionAnswerRequest, BaseLookup, BatchAddEventQuestionAnswersCommand, BatchAddEventQuestionAnswersRequest, BatchEventQuestionAnswerItem, CollectionResult, EntityControllerBase, Error, EventQuestionAnswer, EventQuestionAnswerDTO, ICommandHandler, ICurrentUserService, IEntityQueryService, OwnedByUserSpecification, OwnershipHelper, PagedCollectionResult, QueryFilterModelBinder, RemoveEventQuestionAnswerCommand, Result …(+5) | +| 10 | `EventQuestionAnswersController` | MMCA.ADC.Conference.API | 24 | AddEventQuestionAnswerCommand, AddEventQuestionAnswerRequest, BaseLookup, BatchAddEventQuestionAnswersCommand, BatchAddEventQuestionAnswersRequest, BatchEventQuestionAnswerItem, CollectionResult, EntityControllerBase, EventQuestionAnswer, EventQuestionAnswerDTO, ICommandHandler, ICurrentUserService, IEntityQueryService, OwnedByUserSpecification, OwnershipHelper, PagedCollectionResult, QueryFilterModelBinder, RemoveEventQuestionAnswerCommand, Result, RoleNames …(+4) | | 10 | `EventsController` | MMCA.ADC.Conference.API | 25 | AggregateRootEntityControllerBase, BaseLookup, CollectionResult, ConferencePermissions, DeleteEntityCommand, Event, EventCreateRequest, EventDTO, EventUpdateRequest, ExportEventCalendarQuery, GetNowNextQuery, ICommandHandler, ICurrentUserService, IEntityQueryService, IQueryHandler, NowNextDTO, PagedCollectionResult, PublishedEventSpecification, QueryFilterModelBinder, Result …(+5) | | 10 | `PartnersController` | MMCA.ADC.Conference.API | 21 | AggregateRootEntityControllerBase, BaseLookup, CollectionResult, ConferencePermissions, DeleteEntityCommand, GetPublicPartnerFilterQuery, ICommandHandler, ICurrentUserService, IEntityQueryService, IQueryHandler, PagedCollectionResult, Partner, PartnerCreateRequest, PartnerDTO, PartnerUpdateRequest, QueryFilterModelBinder, Result, Route, Specification, SupportsIfMatchAttribute …(+1) | | 10 | `SessionCategoryItemsController` | MMCA.ADC.Conference.API | 19 | AddSessionCategoryItemCommand, AddSessionCategoryItemRequest, BaseLookup, CollectionResult, ConferencePermissions, EntityControllerBase, GetPublicSessionCategoryItemFilterQuery, ICommandHandler, ICurrentUserService, IEntityQueryService, IQueryHandler, PagedCollectionResult, QueryFilterModelBinder, RemoveSessionCategoryItemCommand, Result, Route, SessionCategoryItem, SessionCategoryItemDTO, Specification | -| 10 | `SessionQuestionAnswersController` | MMCA.ADC.Conference.API | 25 | AddSessionQuestionAnswerCommand, AddSessionQuestionAnswerRequest, BaseLookup, BatchAddSessionQuestionAnswersCommand, BatchAddSessionQuestionAnswersRequest, BatchSessionQuestionAnswerItem, CollectionResult, EntityControllerBase, Error, ICommandHandler, ICurrentUserService, IEntityQueryService, OwnedByUserSpecification, OwnershipHelper, PagedCollectionResult, QueryFilterModelBinder, RemoveSessionQuestionAnswerCommand, Result, RoleNames, Route …(+5) | +| 10 | `SessionQuestionAnswersController` | MMCA.ADC.Conference.API | 24 | AddSessionQuestionAnswerCommand, AddSessionQuestionAnswerRequest, BaseLookup, BatchAddSessionQuestionAnswersCommand, BatchAddSessionQuestionAnswersRequest, BatchSessionQuestionAnswerItem, CollectionResult, EntityControllerBase, ICommandHandler, ICurrentUserService, IEntityQueryService, OwnedByUserSpecification, OwnershipHelper, PagedCollectionResult, QueryFilterModelBinder, RemoveSessionQuestionAnswerCommand, Result, RoleNames, Route, SessionQuestionAnswer …(+4) | | 10 | `SessionsController` | MMCA.ADC.Conference.API | 25 | AggregateRootEntityControllerBase, BaseLookup, CollectionResult, ConferencePermissions, DeleteEntityCommand, Event, EventDTO, GetPublicSessionFilterQuery, GetSessionsBySpeakerFilterQuery, ICommandHandler, ICurrentUserService, IEntityQueryService, IQueryHandler, PagedCollectionResult, QueryFilterModelBinder, Result, Route, Session, SessionCreateRequest, SessionDTO …(+5) | | 10 | `SessionSpeakersController` | MMCA.ADC.Conference.API | 19 | AddSessionSpeakerCommand, AddSessionSpeakerRequest, BaseLookup, CollectionResult, ConferencePermissions, EntityControllerBase, GetPublicSessionSpeakerFilterQuery, ICommandHandler, ICurrentUserService, IEntityQueryService, IQueryHandler, PagedCollectionResult, QueryFilterModelBinder, RemoveSessionSpeakerCommand, Result, Route, SessionSpeaker, SessionSpeakerDTO, Specification | | 10 | `SponsorsController` | MMCA.ADC.Conference.API | 21 | AggregateRootEntityControllerBase, BaseLookup, CollectionResult, ConferencePermissions, DeleteEntityCommand, GetPublicSponsorFilterQuery, ICommandHandler, ICurrentUserService, IEntityQueryService, IQueryHandler, PagedCollectionResult, QueryFilterModelBinder, Result, Route, Specification, Sponsor, SponsorCreateRequest, SponsorDTO, SponsorUpdateRequest, SupportsIfMatchAttribute …(+1) | | 10 | `EventLifecycleControllerTests` | MMCA.ADC.Conference.API.Tests | 10 | Error, ErrorType, EventLifecycleController, ICommandHandler, PublishEventCommand, RefreshFromSessionizeCommand, RefreshFromSessionizeResultDTO, Result, SupportsIfMatchAttribute, UnpublishEventCommand | | 10 | `EventSpeakersControllerTests` | MMCA.ADC.Conference.API.Tests | 19 | AddEventSpeakerCommand, AddEventSpeakerRequest, BaseLookup, Error, EventSpeaker, EventSpeakerDTO, EventSpeakersController, GetPublicEventSpeakerFilterQuery, ICommandHandler, ICurrentUserService, IEntityQueryService, InlineSpecification, IQueryHandler, ISpecification, PagedCollectionResult, RemoveEventSpeakerCommand, Result, RoleNames, Specification | -| 10 | `QuestionsControllerTests` | MMCA.ADC.Conference.API.Tests | 12 | DeleteEntityCommand, Error, ICommandHandler, IEntityQueryService, Question, QuestionCreateRequest, QuestionDTO, QuestionsController, QuestionUpdateRequest, Result, SupportsIfMatchAttribute, UpdateQuestionCommand | +| 10 | `QuestionsControllerTests` | MMCA.ADC.Conference.API.Tests | 14 | DeleteEntityCommand, Error, ICommandHandler, IEntityQueryService, ISpecification, PagedCollectionResult, Question, QuestionCreateRequest, QuestionDTO, QuestionsController, QuestionUpdateRequest, Result, SupportsIfMatchAttribute, UpdateQuestionCommand | | 10 | `RoomsControllerTests` | MMCA.ADC.Conference.API.Tests | 22 | AddRoomCommand, AddRoomRequest, BaseLookup, CollectionResult, Error, GetPublicRoomFilterQuery, ICommandHandler, ICurrentUserService, IEntityQueryService, InlineSpecification, IQueryHandler, ISpecification, PagedCollectionResult, RemoveRoomCommand, Result, RoleNames, Room, RoomDTO, RoomsController, Specification …(+2) | | 10 | `SessionAssetsControllerTests` | MMCA.ADC.Conference.API.Tests | 20 | AddSessionAssetLinkCommand, ConferencePermissions, DeleteSessionAssetCommand, Error, GetSessionAssetsQuery, HasPermissionAttribute, ICommandHandler, ICurrentUserService, IPermissionRegistry, IQueryHandler, Result, SessionAssetDTO, SessionAssetKind, SessionAssetLimits, SessionAssetLinkRequest, SessionAssetsController, SessionAssetUpdateRequest, SupportsIfMatchAttribute, UpdateSessionAssetCommand, UploadSessionAssetCommand | | 10 | `SpeakerCategoryItemsControllerTests` | MMCA.ADC.Conference.API.Tests | 19 | AddSpeakerCategoryItemCommand, AddSpeakerCategoryItemRequest, BaseLookup, Error, GetPublicSpeakerCategoryItemFilterQuery, ICommandHandler, ICurrentUserService, IEntityQueryService, InlineSpecification, IQueryHandler, ISpecification, PagedCollectionResult, RemoveSpeakerCategoryItemCommand, Result, RoleNames, SpeakerCategoryItem, SpeakerCategoryItemDTO, SpeakerCategoryItemsController, Specification | | 10 | `SpeakerLinksControllerTests` | MMCA.ADC.Conference.API.Tests | 7 | Error, ICommandHandler, LinkUserRequest, LinkUserToSpeakerCommand, Result, SpeakerLinksController, UnlinkUserFromSpeakerCommand | -| 10 | `SpeakersControllerTests` | MMCA.ADC.Conference.API.Tests | 23 | AndSpecification, BaseLookup, DeleteEntityCommand, Error, GetPublicSpeakerFilterQuery, GetSpeakersByEventFilterQuery, ICommandHandler, ICurrentUserService, IEntityQueryService, InlineSpecification, IQueryHandler, ISpecification, PagedCollectionResult, Result, RoleNames, Speaker, SpeakerCreateRequest, SpeakerDTO, SpeakersController, SpeakerUpdateRequest …(+3) | +| 10 | `SpeakersControllerTests` | MMCA.ADC.Conference.API.Tests | 25 | AndSpecification, BaseLookup, DeleteEntityCommand, Error, GetPublicSpeakerFilterQuery, GetSpeakersByEventFilterQuery, ICommandHandler, ICurrentUserService, IEntityQueryService, InlineSpecification, IPermissionRegistry, IQueryHandler, ISpecification, PagedCollectionResult, PermissionRegistry, Result, RoleNames, Speaker, SpeakerCreateRequest, SpeakerDTO …(+5) | | 10 | `SpeakerSessionsControllerTests` | MMCA.ADC.Conference.API.Tests | 10 | Error, GetSessionBookmarkCountQuery, GetSessionBookmarkCountsQuery, GetSessionFeedbackQuery, ICurrentUserService, IQueryHandler, Result, RoleNames, SessionFeedbackDTO, SpeakerSessionsController | | 10 | `ActivityCreateRequestMapper` | MMCA.ADC.Conference.Application | 4 | Activity, ActivityCreateRequest, IEntityRequestMapper, Result | | 10 | `ActivityCreateRequestValidator` | MMCA.ADC.Conference.Application | 3 | ActivityCreateRequest, ActivityEventIdRules, ActivityFieldRules | @@ -4186,7 +4226,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 10 | `BatchAddSessionQuestionAnswersCommandValidator` | MMCA.ADC.Conference.Application | 1 | BatchAddSessionQuestionAnswersCommand | | 10 | `BatchAddSessionQuestionAnswersHandler` | MMCA.ADC.Conference.Application | 14 | BatchAddSessionQuestionAnswersCommand, Error, Event, ICommandHandler, ICurrentUserService, IUnitOfWork, Question, Result, Session, SessionFeedbackSubmitted, SessionQuestionAnswer, SessionQuestionAnswerDTO, SessionQuestionAnswerDTOMapper, SessionQuestionAnswerRules | | 10 | `CategoryItemNavigationPopulator` | MMCA.ADC.Conference.Application | 5 | Category, CategoryItem, DeclarativeNavigationPopulator, FKNavigationDescriptor, IUnitOfWork | -| 10 | `CategorySyncStrategy` | MMCA.ADC.Conference.Application | 7 | Category, ISessionizeSyncStrategy, SessionizeCategory, SessionizeCategoryItem, SessionizeSyncContext, SessionizeSyncResult, SessionizeSyncWarnings | +| 10 | `CategorySyncStrategy` | MMCA.ADC.Conference.Application | 8 | Category, ISessionizeSyncStrategy, Result, SessionizeCategory, SessionizeCategoryItem, SessionizeSyncContext, SessionizeSyncResult, SessionizeSyncWarnings | | 10 | `ConferenceCategoryNavigationPopulator` | MMCA.ADC.Conference.Application | 5 | Category, CategoryItem, ChildNavigationDescriptor, DeclarativeNavigationPopulator, IUnitOfWork | | 10 | `CreateActivityHandler` | MMCA.ADC.Conference.Application | 7 | Activity, ActivityCreateRequest, ActivityDTO, ActivityDTOMapper, CreateEntityHandlerBase, IEntityRequestMapper, IUnitOfWork | | 10 | `CreateEventHandler` | MMCA.ADC.Conference.Application | 7 | CreateEntityHandlerBase, Event, EventCreateRequest, EventDTO, EventDTOMapper, IEntityRequestMapper, IUnitOfWork | @@ -4207,7 +4247,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 10 | `PartnerNavigationPopulator` | MMCA.ADC.Conference.Application | 5 | DeclarativeNavigationPopulator, Event, FKNavigationDescriptor, IUnitOfWork, Partner | | 10 | `PublicConferenceVisibility` | MMCA.ADC.Conference.Application | 8 | CrossSourceSpecification, Event, IEntityQuerier, InlineSpecification, IUnitOfWork, PublicSessionStatusSpecification, Session, SessionSpeaker | | 10 | `PublishEventHandler` | MMCA.ADC.Conference.Application | 5 | Event, IUnitOfWork, MutateEntityHandlerBase, PublishEventCommand, Result | -| 10 | `QuestionSyncStrategy` | MMCA.ADC.Conference.Application | 5 | ISessionizeSyncStrategy, Question, QuestionInvariants, SessionizeSyncContext, SessionizeSyncResult | +| 10 | `QuestionSyncStrategy` | MMCA.ADC.Conference.Application | 6 | ISessionizeSyncStrategy, Question, QuestionInvariants, SessionizeSyncContext, SessionizeSyncResult, SessionizeSyncWarnings | | 10 | `RemoveEventQuestionAnswerHandler` | MMCA.ADC.Conference.Application | 9 | Error, Event, EventQuestionAnswer, ICurrentUserService, IUnitOfWork, MutateEntityHandlerBase, RemoveEventQuestionAnswerCommand, Result, RoleNames | | 10 | `RemoveSessionQuestionAnswerHandler` | MMCA.ADC.Conference.Application | 10 | Error, ICurrentUserService, IRepository, IUnitOfWork, MutateEntityHandlerBase, RemoveSessionQuestionAnswerCommand, Result, RoleNames, Session, SessionQuestionAnswer | | 10 | `RoomNavigationPopulator` | MMCA.ADC.Conference.Application | 5 | DeclarativeNavigationPopulator, Event, FKNavigationDescriptor, IUnitOfWork, Room | @@ -4224,19 +4264,16 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 10 | `SpeakerCategoryItemNavigationPopulator` | MMCA.ADC.Conference.Application | 5 | DeclarativeNavigationPopulator, FKNavigationDescriptor, IUnitOfWork, Speaker, SpeakerCategoryItem | | 10 | `SpeakerEntityQueryService` | MMCA.ADC.Conference.Application | 8 | EntityQueryService, IEntityQueryPipeline, INavigationMetadataProvider, INavigationPopulator, IUnitOfWork, Speaker, SpeakerDTO, SpeakerDTOMapper | | 10 | `SpeakerNavigationPopulator` | MMCA.ADC.Conference.Application | 6 | ChildNavigationDescriptor, DeclarativeNavigationPopulator, IUnitOfWork, Speaker, SpeakerCategoryItem, SpeakerQuestionAnswer | -| 10 | `SpeakerQuestionAnswerNavigationPopulator` | MMCA.ADC.Conference.Application | 5 | DeclarativeNavigationPopulator, FKNavigationDescriptor, IUnitOfWork, Speaker, SpeakerQuestionAnswer | -| 10 | `SpeakerSyncStrategy` | MMCA.ADC.Conference.Application | 9 | EventSpeaker, ISessionizeSyncStrategy, SessionizeLink, SessionizeQuestionAnswer, SessionizeSpeaker, SessionizeSyncContext, SessionizeSyncResult, SessionizeSyncWarnings, Speaker | +| 10 | `SpeakerSyncStrategy` | MMCA.ADC.Conference.Application | 10 | CommonInvariants, EventSpeaker, ISessionizeSyncStrategy, SessionizeQuestionAnswer, SessionizeSpeaker, SessionizeSyncContext, SessionizeSyncResult, SessionizeSyncWarnings, Speaker, SpeakerInvariants | | 10 | `SponsorCreateRequestMapper` | MMCA.ADC.Conference.Application | 4 | IEntityRequestMapper, Result, Sponsor, SponsorCreateRequest | | 10 | `SponsorCreateRequestValidator` | MMCA.ADC.Conference.Application | 3 | SponsorCreateRequest, SponsorEventIdRules, SponsorFieldRules | | 10 | `SponsorNavigationPopulator` | MMCA.ADC.Conference.Application | 5 | DeclarativeNavigationPopulator, Event, FKNavigationDescriptor, IUnitOfWork, Sponsor | | 10 | `UnlinkUserFromSpeakerHandler` | MMCA.ADC.Conference.Application | 6 | IUnitOfWork, MutateEntityHandlerBase, Result, Speaker, SpeakerUnlinkedFromUser, UnlinkUserFromSpeakerCommand | | 10 | `UnpublishEventHandler` | MMCA.ADC.Conference.Application | 5 | Event, IUnitOfWork, MutateEntityHandlerBase, Result, UnpublishEventCommand | | 10 | `UpdateCategoryItemHandler` | MMCA.ADC.Conference.Application | 5 | Category, IUnitOfWork, MutateEntityHandlerBase, Result, UpdateCategoryItemCommand | -| 10 | `UpdateEventQuestionAnswerHandler` | MMCA.ADC.Conference.Application | 9 | Error, Event, EventQuestionAnswer, ICurrentUserService, IUnitOfWork, MutateEntityHandlerBase, Result, RoleNames, UpdateEventQuestionAnswerCommand | | 10 | `UpdateRoomHandler` | MMCA.ADC.Conference.Application | 5 | Event, IUnitOfWork, MutateEntityHandlerBase, Result, UpdateRoomCommand | | 10 | `UpdateSessionAssetHandler` | MMCA.ADC.Conference.Application | 8 | ISessionAssetAccessService, IUnitOfWork, MutateEntityHandlerBase, Result, SessionAsset, SessionAssetDTO, SessionAssetDTOMapper, UpdateSessionAssetCommand | | 10 | `UpdateSessionQuestionAnswerCommandValidator` | MMCA.ADC.Conference.Application | 2 | QuestionInvariants, UpdateSessionQuestionAnswerCommand | -| 10 | `UpdateSessionQuestionAnswerHandler` | MMCA.ADC.Conference.Application | 9 | Error, ICurrentUserService, IUnitOfWork, MutateEntityHandlerBase, Result, RoleNames, Session, SessionQuestionAnswer, UpdateSessionQuestionAnswerCommand | | 10 | `ActivityDTOMapperTests` | MMCA.ADC.Conference.Application.Tests | 2 | Activity, ActivityDTOMapper | | 10 | `ActivityUpdateRequestValidatorTests` | MMCA.ADC.Conference.Application.Tests | 3 | ActivityInvariants, ActivityUpdateRequest, ActivityUpdateRequestValidator | | 10 | `AddEventQuestionAnswerCommandValidatorTests` | MMCA.ADC.Conference.Application.Tests | 2 | AddEventQuestionAnswerCommand, AddEventQuestionAnswerCommandValidator | @@ -4264,6 +4301,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 10 | `SessionRoomSchedulingTests` | MMCA.ADC.Conference.Application.Tests | 4 | ErrorType, Session, SessionBuilder, SessionRoomScheduling | | 10 | `SessionSpeakerDTOMapperTests` | MMCA.ADC.Conference.Application.Tests | 4 | Session, SessionBuilder, SessionSpeaker, SessionSpeakerDTOMapper | | 10 | `SessionUpdateRequestValidatorTests` | MMCA.ADC.Conference.Application.Tests | 3 | SessionInvariants, SessionUpdateRequest, SessionUpdateRequestValidator | +| 10 | `SpeakerCreateRequestMapperTests` | MMCA.ADC.Conference.Application.Tests | 2 | SpeakerCreateRequest, SpeakerCreateRequestMapper | | 10 | `SpeakerCreateRequestValidatorTests` | MMCA.ADC.Conference.Application.Tests | 4 | Email, SpeakerCreateRequest, SpeakerCreateRequestValidator, SpeakerInvariants | | 10 | `SpeakerDTOMapperTests` | MMCA.ADC.Conference.Application.Tests | 6 | ICurrentUserService, Speaker, SpeakerBuilder, SpeakerCategoryItemDTOMapper, SpeakerDTOMapper, SpeakerQuestionAnswerDTOMapper | | 10 | `SpeakerUpdateRequestValidatorTests` | MMCA.ADC.Conference.Application.Tests | 4 | Email, SpeakerInvariants, SpeakerUpdateRequest, SpeakerUpdateRequestValidator | @@ -4271,6 +4309,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 10 | `SponsorUpdateRequestValidatorTests` | MMCA.ADC.Conference.Application.Tests | 4 | SponsorInvariants, SponsorTier, SponsorUpdateRequest, SponsorUpdateRequestValidator | | 10 | `UpdateRoomCommandValidatorTests` | MMCA.ADC.Conference.Application.Tests | 3 | EventInvariants, UpdateRoomCommand, UpdateRoomCommandValidator | | 10 | `UploadSessionAssetCommandValidatorTests` | MMCA.ADC.Conference.Application.Tests | 4 | SessionAssetFixtures, SessionAssetLimits, UploadSessionAssetCommand, UploadSessionAssetCommandValidator | +| 10 | `UserDeletedFeedbackHandlerTests` | MMCA.ADC.Conference.Application.Tests | 9 | Event, EventQuestionAnswer, InMemoryRepository, IUnitOfWork, RecordingUnitOfWork, Session, SessionQuestionAnswer, UserDeleted, UserDeletedFeedbackHandler | | 10 | `UserRegisteredHandlerTests` | MMCA.ADC.Conference.Application.Tests | 11 | Fakes, IEventBus, InMemoryRepository, IUnitOfWork, RecordingEventBus, RecordingUnitOfWork, Speaker, SpeakerBuilder, SpeakerLinkedToUser, UserRegistered, UserRegisteredHandler | | 10 | `SessionBookmarkValidationServiceGrpcAdapter` | MMCA.ADC.Conference.Contracts | 3 | ISessionBookmarkValidationService, Result, SessionBookmarkValidationService | | 10 | `ActivityTests` | MMCA.ADC.Conference.Domain.Tests | 6 | Activity, ActivityBuilder, ActivityChanged, ActivityInvariants, DomainEntityState, Result | @@ -4280,15 +4319,14 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 10 | `SessionQuestionAnswerTests` | MMCA.ADC.Conference.Domain.Tests | 6 | DomainEntityState, ErrorType, SessionBuilder, SessionInvariants, SessionQuestionAnswer, SessionQuestionAnswerChanged | | 10 | `SessionSpeakerTests` | MMCA.ADC.Conference.Domain.Tests | 5 | DomainEntityState, ErrorType, SessionBuilder, SessionSpeaker, SessionSpeakerChanged | | 10 | `SponsorTests` | MMCA.ADC.Conference.Domain.Tests | 7 | DomainEntityState, Result, Sponsor, SponsorBuilder, SponsorChanged, SponsorInvariants, SponsorTier | -| 10 | `ConferenceEntityConfigurationTests` | MMCA.ADC.Conference.Infrastructure.Tests | 35 | Category, CategoryInvariants, CategoryItem, CategoryItemConfiguration, ConferenceCategoryConfiguration, ConferenceTestDbContext, Event, EventConfiguration, EventInvariants, EventQuestionAnswer, EventQuestionAnswerConfiguration, EventSpeaker, EventSpeakerConfiguration, Question, QuestionConfiguration, QuestionInvariants, Room, RoomConfiguration, Session, SessionCategoryItem …(+15) | | 10 | `ConferenceModuleDbSeederTests` | MMCA.ADC.Conference.Infrastructure.Tests | 6 | ConferenceModuleDbSeeder, Event, IRepository, IUnitOfWork, Question, SeederMocks | | 10 | `SessionBookmarksGrpcService` | MMCA.ADC.Conference.Service | 2 | ISessionBookmarkValidationService, SessionBookmarkValidationService | | 10 | `CurrentEventDefaultsTests` | MMCA.ADC.Conference.Shared.Tests | 3 | CurrentEventDefaults, Event, EventDTO | | 10 | `ActivityList` | MMCA.ADC.Conference.UI | 9 | ActivityDTO, ActivityService, ConferenceRoutePaths, ErrorMessages, EventFilteredListPageBase, IActivityUIService, ListPageActions, MobileInfiniteScrollList, Result | | 10 | `PartnerList` | MMCA.ADC.Conference.UI | 10 | ConferenceRoutePaths, ErrorMessages, EventFilteredListPageBase, IPartnerUIService, ListPageActions, MobileInfiniteScrollList, PartnerDTO, PartnerService, PartnerType, Result | -| 10 | `PublicSessionList` | MMCA.ADC.Conference.UI | 18 | BookmarkService, ConferenceReadAudience, CurrentEventDefaults, DataGridListPageBase, EventDTO, EventService, IEventUIService, IPublicSessionScheduleService, ISessionBookmarkUIService, ISpeakerLookupService, PublicScheduleRoomOptions, PublicSessionListFilterState, PublicSessionListView, Result, RoomDTO, SessionDTO, SessionSchedulePageRequest, SpeakerInfo | +| 10 | `PublicSessionList` | MMCA.ADC.Conference.UI | 18 | BookmarkService, CurrentEventDefaults, DataGridListPageBase, EventDTO, EventService, IEventUIService, IPublicSessionScheduleService, ISessionBookmarkUIService, ISpeakerLookupService, PublicReadAudience, PublicScheduleRoomOptions, PublicSessionListFilterState, PublicSessionListView, Result, RoomDTO, SessionDTO, SessionSchedulePageRequest, SpeakerInfo | | 10 | `PublicSpeakerDetail` | MMCA.ADC.Conference.UI | 13 | ConferenceReadAudience, ConferenceRoutePaths, CurrentEventDefaults, EventService, IEventUIService, ISessionUIService, ISpeakerUIService, IToastService, LatestLoadGuard, SessionDTO, SessionService, SpeakerDTO, SpeakerService | -| 10 | `PublicSpeakerList` | MMCA.ADC.Conference.UI | 5 | ConferenceReadAudience, EventFilteredListPageBase, ISpeakerUIService, SpeakerDTO, SpeakerService | +| 10 | `PublicSpeakerList` | MMCA.ADC.Conference.UI | 5 | EventFilteredListPageBase, ISpeakerUIService, PublicReadAudience, SpeakerDTO, SpeakerService | | 10 | `RoomList` | MMCA.ADC.Conference.UI | 9 | ConferenceRoutePaths, ErrorMessages, EventFilteredListPageBase, IRoomUIService, ListPageActions, MobileInfiniteScrollList, Result, RoomDTO, RoomService | | 10 | `SessionList` | MMCA.ADC.Conference.UI | 15 | ConferenceRoutePaths, CurrentEventDefaults, DataGridListPageBase, ErrorMessages, EventDTO, EventService, IEventUIService, ISessionUIService, ISpeakerLookupService, ListPageActions, MobileInfiniteScrollList, Result, SessionDTO, SessionService, SpeakerInfo | | 10 | `SpeakerList` | MMCA.ADC.Conference.UI | 9 | ConferenceRoutePaths, ErrorMessages, EventFilteredListPageBase, ISpeakerUIService, ListPageActions, MobileInfiniteScrollList, Result, SpeakerDTO, SpeakerService | @@ -4300,7 +4338,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 10 | `ADCHomeTicketingTests` | MMCA.ADC.Conference.UI.Tests | 3 | ADCHome, ADCHomeServiceDoubles, BunitTestBase | | 10 | `PartnerCreateTests` | MMCA.ADC.Conference.UI.Tests | 7 | BunitTestBase, EventInfo, IEventLookupService, IPartnerUIService, PartnerCreate, PartnerDTO, PartnerType | | 10 | `PartnerDetailTests` | MMCA.ADC.Conference.UI.Tests | 10 | BunitTestBase, Error, EventInfo, IEventLookupService, IPartnerUIService, Partner, PartnerDetail, PartnerDTO, PartnerType, Result | -| 10 | `PublicActivityListTests` | MMCA.ADC.Conference.UI.Tests | 6 | ActivityDTO, BunitTestBase, EventInfo, IActivityUIService, IEventLookupService, PublicActivityList | +| 10 | `PublicActivityListTests` | MMCA.ADC.Conference.UI.Tests | 8 | ActivityDTO, BunitTestBase, Error, EventInfo, IActivityUIService, IEventLookupService, PublicActivityList, Result | | 10 | `PublicEventListRedirectTests` | MMCA.ADC.Conference.UI.Tests | 10 | BunitTestBase, EventDTO, EventInfo, IEventLookupService, IEventUIService, MobileInfiniteScrollList, PublicEventList, Result, RoleNames, TestPrincipal | | 10 | `PublicSessionDetailBookmarkTests` | MMCA.ADC.Conference.UI.Tests | 16 | BunitTestBase, CategoryItemInfo, Error, ICategoryItemLookupService, IRoomUIService, ISessionBookmarkUIService, ISessionLiveUIService, ISessionUIService, ISpeakerLookupService, IToastService, ProblemDetailsResultReader, PublicSessionDetail, Result, SessionDTO, SpeakerInfo, UserSessionBookmarkDTO | | 10 | `PublicSessionDetailLiveButtonTests` | MMCA.ADC.Conference.UI.Tests | 12 | BunitTestBase, CategoryItemInfo, ICategoryItemLookupService, IRoomUIService, ISessionBookmarkUIService, ISessionLiveUIService, ISessionUIService, ISpeakerLookupService, PublicSessionDetail, Result, SessionDTO, SpeakerInfo | @@ -4308,6 +4346,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 10 | `PublicSponsorListTests` | MMCA.ADC.Conference.UI.Tests | 10 | BunitTestBase, Error, EventInfo, HttpResultExecutor, IEventLookupService, ISponsorUIService, PublicSponsorList, Result, SponsorDTO, SponsorTier | | 10 | `SessionDetailCategoryChipTests` | MMCA.ADC.Conference.UI.Tests | 18 | BunitTestBase, CategoryItemInfo, EventInfo, ICategoryItemLookupService, IEventLookupService, IRoomUIService, ISessionCategoryItemUIService, ISessionSpeakerUIService, ISessionUIService, ISpeakerLookupService, Result, RoleNames, RoomDTO, SessionCategoryItemDTO, SessionDetail, SessionDTO, SpeakerInfo, TestPrincipal | | 10 | `SessionDetailRoomCacheTests` | MMCA.ADC.Conference.UI.Tests | 18 | BunitTestBase, CategoryItemInfo, EventInfo, ICategoryItemLookupService, IEventLookupService, IRoomUIService, ISessionCategoryItemUIService, ISessionSpeakerUIService, ISessionUIService, ISpeakerLookupService, Result, RoleNames, Room, RoomDTO, SessionDetail, SessionDTO, SpeakerInfo, TestPrincipal | +| 10 | `SessionDetailScheduleTests` | MMCA.ADC.Conference.UI.Tests | 17 | BunitTestBase, CategoryItemInfo, EventInfo, ICategoryItemLookupService, IEventLookupService, IRoomUIService, ISessionCategoryItemUIService, ISessionSpeakerUIService, ISessionUIService, ISpeakerLookupService, Result, RoleNames, RoomDTO, SessionDetail, SessionDTO, SpeakerInfo, TestPrincipal | | 10 | `SessionDetailStaleLoadTests` | MMCA.ADC.Conference.UI.Tests | 18 | BunitTestBase, CategoryItemInfo, EventInfo, ICategoryItemLookupService, IEventLookupService, IRoomUIService, ISessionCategoryItemUIService, ISessionSpeakerUIService, ISessionUIService, ISpeakerLookupService, Result, RoleNames, RoomDTO, Session, SessionDetail, SessionDTO, SpeakerInfo, TestPrincipal | | 10 | `SessionSelectionDashboardTests` | MMCA.ADC.Conference.UI.Tests | 18 | BunitTestBase, CategoryDistributionDTO, CategoryGroupDistribution, CategoryItemDistribution, Error, EventInfo, IEventLookupService, ISessionSelectionUIService, MultiSessionSpeaker, Result, ScoreEventSessionsResultDTO, SessionAiScoreDTO, Sessions, SessionSelectionDashboard, SessionSelectionDashboardDTO, SpeakerLocalitySummary, SpeakerSessionOverlapDTO, SpeakerSessionSummary | | 10 | `SessionSelectionStaleResponseTests` | MMCA.ADC.Conference.UI.Tests | 17 | BunitTestBase, CategoryDistributionDTO, Error, EventInfo, IEventLookupService, ISessionSelectionUIService, IToastService, MultiSessionSpeaker, Result, ScoreEventSessionsResultDTO, SessionAiScoreDTO, Sessions, SessionSelectionDashboard, SessionSelectionDashboardDTO, SpeakerSessionOverlapDTO, SpeakerSessionSummary, ToastSeverity | @@ -4321,11 +4360,11 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 10 | `PointsControllerTests` | MMCA.ADC.Engagement.API.Tests | 21 | ControllerMocks, EngagementFeatures, EngagementPermissions, Entry, Error, GetLeaderboardQuery, GetMyPointsQuery, GetPointsOverviewQuery, HasPermissionAttribute, ICommandHandler, IQueryHandler, LeaderboardEntryDTO, MyPoints, MyPointsDTO, PointsActivityTotalDTO, PointsActivityType, PointsController, PointsEntryDTO, PointsOverviewDTO, Result …(+1) | | 10 | `SessionQuestionsControllerTests` | MMCA.ADC.Engagement.API.Tests | 18 | ControllerMocks, Error, GetModerationQueueQuery, GetSessionQuestionsQuery, ICommandHandler, ICurrentUserService, IdempotentAttribute, IQueryHandler, ModerateQuestionCommand, ModerationAction, QuestionStatus, Result, SessionQuestionDTO, SessionQuestionsController, SubmitQuestionCommand, SubmitQuestionRequest, SupportsIfMatchAttribute, ToggleUpvoteCommand | | 10 | `AttendeeCheckedInPointsHandler` | MMCA.ADC.Engagement.Application | 6 | AttendeeCheckedIn, CheckInScopeNames, IPointsAwarder, PointsActivityType, PointsSubjectKeys, ScopedIntegrationEventHandlerBase | -| 10 | `CastVoteHandler` | MMCA.ADC.Engagement.Application | 10 | CastVoteCommand, Error, ICommandHandler, IEntityReader, IUnitOfWork, LivePoll, LivePollResultsBuilder, LivePollResultsDTO, LivePollVote, Result | +| 10 | `CastVoteHandler` | MMCA.ADC.Engagement.Application | 12 | CastVoteCommand, Error, ICommandHandler, IEntityReader, IEventLiveValidationService, IUniqueConstraintViolationDetector, IUnitOfWork, LivePoll, LivePollResultsBuilder, LivePollResultsDTO, LivePollVote, Result | | 10 | `CloseLivePollHandler` | MMCA.ADC.Engagement.Application | 11 | CloseLivePollCommand, IEventLiveValidationService, ILiveChannelPublishQueue, IUnitOfWork, LiveChannelPublishWorkItem, LivePoll, LivePollAuthorization, LivePollChannel, LivePollClosedPayload, MutateEntityHandlerBase, Result | | 10 | `CreateLivePollHandler` | MMCA.ADC.Engagement.Application | 10 | CreateLivePollCommand, Error, ICommandHandler, IEventLiveValidationService, IUnitOfWork, LivePoll, LivePollAuthorization, LivePollDTO, LivePollDTOMapper, Result | | 10 | `GetEventPollsHandler` | MMCA.ADC.Engagement.Application | 8 | GetEventPollsQuery, IEntityQuerier, IQueryHandler, IUnitOfWork, LivePoll, LivePollDTO, LivePollDTOMapper, Result | -| 10 | `GetOpenPollsHandler` | MMCA.ADC.Engagement.Application | 10 | Error, GetOpenPollsQuery, IEntityQuerier, IQueryHandler, IUnitOfWork, LivePoll, LivePollResultsBuilder, LivePollResultsDTO, LivePollStatus, Result | +| 10 | `GetOpenPollsHandler` | MMCA.ADC.Engagement.Application | 11 | Error, GetOpenPollsQuery, IEntityQuerier, IEventLiveValidationService, IQueryHandler, IUnitOfWork, LivePoll, LivePollResultsBuilder, LivePollResultsDTO, LivePollStatus, Result | | 10 | `GetPollResultsHandler` | MMCA.ADC.Engagement.Application | 13 | Error, GetPollResultsQuery, IEntityReader, IEventLiveValidationService, IQueryHandler, IUnitOfWork, LivePoll, LivePollAuthorization, LivePollResultsBuilder, LivePollResultsDTO, LivePollStatus, Result, SessionLiveInfo | | 10 | `GetSessionManagePollsHandler` | MMCA.ADC.Engagement.Application | 9 | GetSessionManagePollsQuery, IEventLiveValidationService, IQueryHandler, IUnitOfWork, LivePoll, LivePollAuthorization, LivePollDTO, LivePollDTOMapper, Result | | 10 | `LivePollNavigationPopulator` | MMCA.ADC.Engagement.Application | 5 | ChildNavigationDescriptor, DeclarativeNavigationPopulator, IUnitOfWork, LivePoll, LivePollOption | @@ -4336,7 +4375,6 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 10 | `LivePollDTOMapperTests` | MMCA.ADC.Engagement.Application.Tests | 3 | LivePoll, LivePollDTOMapper, LivePollStatus | | 10 | `LivePollResultsBuilderTests` | MMCA.ADC.Engagement.Application.Tests | 9 | AuditableBaseEntity, CountingQueryableExecutor, InMemoryQueryableExecutor, IQueryableExecutor, IReadRepository, IUnitOfWork, LivePoll, LivePollResultsBuilder, LivePollVote | | 10 | `CheckIn` | MMCA.ADC.Engagement.Domain | 7 | AttendeeCheckedIn, AuditableAggregateRootEntity, CheckInInvariants, CheckInScope, CheckInScopeNames, IAuditedEntity, Result | -| 10 | `EngagementTestDbContext` | MMCA.ADC.Engagement.Infrastructure.Tests | 12 | LivePoll, LivePollConfiguration, LivePollOption, LivePollOptionConfiguration, LivePollVote, LivePollVoteConfiguration, SessionQuestion, SessionQuestionConfiguration, SessionQuestionUpvote, SessionQuestionUpvoteConfiguration, UserSessionBookmark, UserSessionBookmarkConfiguration | | 10 | `CheckInScopeNamesTests` | MMCA.ADC.Engagement.Shared.Tests | 2 | CheckInScope, CheckInScopeNames | | 10 | `CheckInScan` | MMCA.ADC.Engagement.UI | 20 | AttendeeSummary, BadgePayload, CheckInAttendeeRequest, CheckInResultDTO, CheckInScope, CheckInService, ErrorType, IAttendeeLookupService, IBarcodeScannerService, ICheckInUIService, ILiveEventUIService, ISessionLookupService, IToastService, LiveEventContext, LiveEventService, ManualCheckInRequest, Result, ScanOutcome, ScanOutcomeKind, SessionInfo | | 10 | `HappeningNow` | MMCA.ADC.Engagement.UI | 18 | ErrorType, IHapticFeedbackService, ILiveEventUIService, ILivePollUIService, INowNextService, IToastService, LiveBroadcastPatch, LiveChannelSubscription, LiveEventContext, LiveEventService, LivePollChannel, LivePollDTO, LivePollResultsDTO, NotificationHubService, NotificationState, NowNextSessionInfo, Result, RoleNames | @@ -4347,8 +4385,10 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 10 | `SpeakerLinkedToUserHandlerTests` | MMCA.ADC.Identity.Application.Tests | 8 | Fakes, InMemoryRepository, IUnitOfWork, RecordingUnitOfWork, SpeakerLinkedToUser, SpeakerLinkedToUserHandler, User, UserRole | | 10 | `SpeakerUnlinkedFromUserHandlerTests` | MMCA.ADC.Identity.Application.Tests | 8 | Fakes, InMemoryRepository, IUnitOfWork, RecordingUnitOfWork, SpeakerUnlinkedFromUser, SpeakerUnlinkedFromUserHandler, User, UserRole | | 10 | `DependencyInjection` | MMCA.ADC.Identity.Contracts | 3 | AttendeeQueryService, AttendeeQueryServiceGrpcAdapter, IAttendeeQueryService | -| 10 | `IdentityEntityConfigurationTests` | MMCA.ADC.Identity.Infrastructure.Tests | 3 | IdentityTestDbContext, User, UserInvariants | | 10 | `DependencyInjection` | MMCA.ADC.Notification.Contracts | 5 | ILiveChannelPublisher, IUserNotificationExportService, LiveChannelPublisherGrpcAdapter, UserNotificationExportService, UserNotificationExportServiceGrpcAdapter | +| 10 | `AttendeesGrpcServiceTests` | MMCA.ADC.Services.Tests | 5 | AttendeeQueryService, AttendeesGrpcService, FakeServerCallContext, GrpcCalls, IAttendeeQueryService | +| 10 | `BookmarkCountsGrpcServiceTests` | MMCA.ADC.Services.Tests | 6 | BookmarkCountService, BookmarkCountServiceGrpcAdapter, BookmarkCountsGrpcService, FakeServerCallContext, GrpcCalls, IBookmarkCountService | +| 10 | `SessionBookmarkValidationServiceGrpcAdapterTests` | MMCA.ADC.Services.Tests | 4 | Error, GrpcCalls, ISessionBookmarkValidationService, SessionBookmarkValidationService | | 10 | `UserNotificationExportGrpcServiceTests` | MMCA.ADC.Services.Tests | 4 | FakeServerCallContext, IUserNotificationExportService, UserNotificationExportGrpcService, UserNotificationExportItemDTO | | 10 | `UserNotificationExportServiceGrpcAdapterTests` | MMCA.ADC.Services.Tests | 3 | UserNotificationExportItemDTO, UserNotificationExportService, UserNotificationExportServiceGrpcAdapter | | 10 | `ADCHomePageContent` | MMCA.ADC.UI | 2 | ADCHome, IHomePageContent | @@ -4358,6 +4398,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 10 | `PromptTaggingChatClientTests` | MMCA.Common.AI.Tests | 4 | AiUsageMeter, PromptContract, PromptTaggingChatClient, StubChatClient | | 10 | `DataExportControllerBase` | MMCA.Common.API | 9 | ApiControllerBase, CurrentUserService, Error, ICurrentUserService, IQueryHandler, IUserOwnedRequest, PrivacyFeatures, Result, UserDataExportDTO | | 10 | `DependencyInjection` | MMCA.Common.API | 1 | NotificationsController | +| 10 | `ICookieSessionRefresher` | MMCA.Common.API | 2 | SessionRefreshOutcome, SessionTokenResult | | 10 | `MiddlewarePipelineBuilder` | MMCA.Common.API | 8 | CommonForwardedHeaders, CorrelationIdMiddleware, MiddlewarePipelineStep, MiddlewarePipelineStepNames, SoftDeletedUserMiddleware, TenantResolutionMiddleware, WebApplicationBuilderExtensions, WebApplicationExtensions | | 10 | `OwnerOrAdminFilter` | MMCA.Common.API | 4 | AllowMissingOwnerAttribute, ICurrentUserService, OwnerOrAdminFilterOptions, OwnershipHelper | | 10 | `RolesAdminControllerBase` | MMCA.Common.API | 8 | AdministrationPermissions, ApiControllerBase, CurrentUserService, ICurrentUserService, IRoleAdministrationService, PermissionCatalogResponse, RolePermissionsResponse, SetRolePermissionsRequest | @@ -4367,6 +4408,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 10 | `DevicesControllerTests` | MMCA.Common.API.Tests | 6 | DeviceInstallationRequest, DevicesController, Error, ICurrentUserService, IPushDeviceRegistrar, Result | | 10 | `NotificationInboxControllerTests` | MMCA.Common.API.Tests | 13 | Error, GetMyNotificationsQuery, GetUnreadNotificationCountQuery, ICommandHandler, ICurrentUserService, InboxController, IQueryHandler, MarkAllNotificationsReadCommand, MarkNotificationReadCommand, PagedCollectionResult, PaginationMetadata, Result, UserNotificationDTO | | 10 | `NotificationsControllerTests` | MMCA.Common.API.Tests | 13 | Error, GetNotificationHistoryQuery, ICommandHandler, ICurrentUserService, IdempotencyHeaders, IQueryHandler, NotificationsController, PagedCollectionResult, PaginationMetadata, PushNotificationDTO, Result, SendPushNotificationCommand, SendPushNotificationRequest | +| 10 | `OwnershipHelperGateTests` | MMCA.Common.API.Tests | 4 | Error, ICurrentUserService, OwnershipHelper, Result | | 10 | `OwnershipHelperTests` | MMCA.Common.API.Tests | 3 | ICurrentUserService, OwnershipHelper, TestOwnerSpecification | | 10 | `SoftDeletedUserMiddlewareTests` | MMCA.Common.API.Tests | 5 | ICacheService, ICurrentUserService, ISoftDeletedUserValidator, SoftDeletedUserCache, SoftDeletedUserMiddleware | | 10 | `AuthorizationCommandDecorator` | MMCA.Common.Application | 6 | AuthorizationGate, Error, ICommandHandler, ICurrentUserService, IPermissionRegistry, ResultFailureFactory | @@ -4376,7 +4418,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 10 | `UpdateEntityCommandHandler` | MMCA.Common.Application | 9 | AuditableAggregateRootEntity, IBaseDTO, IEntityDTOMapper, IEntityUpdateCommandApplier, IUnitOfWork, MutateEntityHandlerBase, MutationContext, Result, UpdateEntityCommand | | 10 | `UpdateEntityHandler` | MMCA.Common.Application | 8 | AuditableAggregateRootEntity, IBaseDTO, IEntityDTOMapper, IEntityUpdateApplier, IUnitOfWork, MutateEntityHandlerBase, Result, UpdateEntityCommand | | 10 | `AuthenticationServiceBaseTests` | MMCA.Common.Application.Tests | 21 | AuthClaimTypes, AuthenticationResponse, AuthenticationValidators, Error, ErrorType, FixedTimeProvider, ILoginProtectionService, InMemoryRefreshSessionStore, IPasswordHasher, IRepository, ITokenService, IUnitOfWork, LoginRequest, RefreshSession, RefreshSessionSettings, RefreshTokenRequest, RegisterRequest, Result, ServiceMocks, TestAuthenticationService …(+1) | -| 10 | `ChangePasswordHandlerBaseTests` | MMCA.Common.Application.Tests | 13 | ChangePasswordRequest, Error, ErrorType, HandlerMocks, IPasswordHasher, IRefreshSessionStore, IRepository, IUnitOfWork, RefreshSession, Result, TestChangePasswordCommand, TestChangePasswordHandler, TestIdentityUser | +| 10 | `ChangePasswordHandlerBaseTests` | MMCA.Common.Application.Tests | 14 | ChangePasswordRequest, Error, ErrorType, HandlerMocks, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, IRepository, IUnitOfWork, RefreshSession, Result, TestChangePasswordCommand, TestChangePasswordHandler, TestIdentityUser | | 10 | `ChangePreferencesHandlerBaseTests` | MMCA.Common.Application.Tests | 10 | ChangePreferencesRequest, Error, ErrorType, HandlerMocks, IRepository, IUnitOfWork, Result, TestChangePreferencesCommand, TestChangePreferencesHandler, TestIdentityUser | | 10 | `ChildNavigationDescriptorTests` | MMCA.Common.Application.Tests | 6 | ChildNavigationDescriptor, INavigationDescriptor, IReadRepository, IUnitOfWork, OrderEntity, OrderLineEntity | | 10 | `CreateEntityHandlerBaseTests` | MMCA.Common.Application.Tests | 13 | Error, ErrorType, IEntityDTOMapper, IEntityRequestMapper, IRepository, IUnitOfWork, OrderAggregate, OrderCreateRequest, OrderDTO, RefusingPrepareCreateOrderHandler, Result, RewritingPrepareCreateOrderHandler, TestCreateOrderHandler | @@ -4409,16 +4451,9 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 10 | `OutboxPollFilterProcessorTests` | MMCA.Common.Aspire.Tests | 2 | Activity, OutboxPollFilterProcessor | | 10 | `CapturedState` | MMCA.Common.Infrastructure | 3 | AggregateCapture, IDomainEvent, OutboxMessage | | 10 | `IntegrationEventConsumerExtensions` | MMCA.Common.Infrastructure | 5 | FaultIntegrationEventConsumer, IIntegrationEvent, IntegrationEventConsumer, OutputCacheEvictionRequested, UpcastingIntegrationEventConsumer | -| 10 | `RepositoryFactory` | MMCA.Common.Infrastructure | 10 | ApplicationSettings, AuditableAggregateRootEntity, AuditableBaseEntity, EFReadRepository, EFReadRepositoryDecorator, EFRepository, EFRepositoryDecorator, IReadRepository, IRepository, IRepositoryFactory | | 10 | `CurrentUserServiceAdditionalTests` | MMCA.Common.Infrastructure.Tests | 3 | AuthClaimTypes, CurrentUserService, User | | 10 | `CurrentUserServiceTests` | MMCA.Common.Infrastructure.Tests | 6 | AuthClaimTypes, CurrentUserService, ICurrentUserService, NullUserService, RoleOnlyService, User | -| 10 | `EFRepositoryAdditionalTests` | MMCA.Common.Infrastructure.Tests | 3 | EFRepository, TestDbContext, TestEntity | -| 10 | `EFRepositoryConcurrencyTouchTests` | MMCA.Common.Infrastructure.Tests | 3 | EFRepository, TestDbContext, TestEntity | -| 10 | `EFRepositoryIntegrationTests` | MMCA.Common.Infrastructure.Tests | 7 | EFReadRepository, EFRepository, FakeTimeProvider, ICurrentUserService, TestChildEntity, TestDbContext, TestEntity | -| 10 | `EntityTypeConfigurationTests` | MMCA.Common.Infrastructure.Tests | 2 | SqliteTestDbContext, SqliteTestEntity | -| 10 | `MarkAllNotificationsReadHandlerTrackingTests` | MMCA.Common.Infrastructure.Tests | 10 | EFQueryableExecutor, EFRepository, IUnitOfWork, MarkAllNotificationsReadCommand, MarkAllNotificationsReadHandler, NotificationTestDbContext, PushNotification, Result, SeededIds, UserNotification | | 10 | `OutboxMessageTests` | MMCA.Common.Infrastructure.Tests | 7 | NamedDomainEvent, OrderedDomainEvent, OutboxMessage, OutboxOrigin, Payload, TestDomainEvent, TestDomainEventWithData | -| 10 | `PushNotificationConfigurationTests` | MMCA.Common.Infrastructure.Tests | 2 | PushNotification, PushNotificationTestDbContext | | 10 | `PushNotificationProjectionTranslationTests` | MMCA.Common.Infrastructure.Tests | 5 | ProjectionTestDbContext, PushNotification, PushNotificationDTOMapper, PushNotificationDTOProjector, PushNotificationStatus | | 10 | `UpcastingIntegrationEventConsumerTests` | MMCA.Common.Infrastructure.Tests | 9 | EventUpcasterRegistry, IEventUpcaster, IInboxStore, IIntegrationEventHandler, OrderPlacedV2, RetiredOrderPlaced, RetiredToV2Upcaster, RetiredToV2Upcaster, UpcastingIntegrationEventConsumer | | 10 | `DecoratorPipelineOrderTests` | MMCA.Common.Testing.Tests | 11 | DecoratorPipelineOrderTests, DecoratorPipelineOrderTestsBase, ICacheService, ICorrelationContext, ICurrentUserService, IPermissionRegistry, IUnitOfWork, PingCommand, PingCommandHandler, PingQuery, Result | @@ -4464,16 +4499,18 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 11 | `SessionCreateRequestValidatorTests` | MMCA.ADC.Conference.Application.Tests | 3 | SessionCreateRequest, SessionCreateRequestValidator, SessionInvariants | | 11 | `SessionDTOMapperTests` | MMCA.ADC.Conference.Application.Tests | 7 | Event, Session, SessionBuilder, SessionCategoryItemDTOMapper, SessionDTOMapper, SessionQuestionAnswerDTOMapper, SessionSpeakerDTOMapper | | 11 | `SponsorCreateRequestValidatorTests` | MMCA.ADC.Conference.Application.Tests | 4 | SponsorCreateRequest, SponsorCreateRequestValidator, SponsorInvariants, SponsorTier | -| 11 | `EventLiveValidationServiceGrpcAdapter` | MMCA.ADC.Conference.Contracts | 8 | EventLiveInfo, EventLiveValidationService, IEventLiveValidationService, QuestionModerationDefault, Result, RoomSessionInfo, SessionLiveInfo, SponsorLiveInfo | +| 11 | `EventLiveValidationServiceGrpcAdapter` | MMCA.ADC.Conference.Contracts | 9 | Error, EventLiveInfo, EventLiveValidationService, IEventLiveValidationService, QuestionModerationDefault, Result, RoomSessionInfo, SessionLiveInfo, SponsorLiveInfo | | 11 | `EventLiveValidationGrpcService` | MMCA.ADC.Conference.Service | 3 | EventLiveValidationService, IEventLiveValidationService, QuestionModerationDefault | | 11 | `EventFilteredListPageBaseTests` | MMCA.ADC.Conference.UI.Tests | 9 | BunitTestBase, Error, EventInfo, IEventLookupService, ISponsorUIService, Result, RoleNames, SponsorList, TestPrincipal | | 11 | `EventFilteredListPagePrerenderTests` | MMCA.ADC.Conference.UI.Tests | 7 | BunitTestBase, EventInfo, IEventLookupService, ISponsorUIService, RoleNames, SponsorList, TestPrincipal | -| 11 | `PublicSessionListEventFilterTests` | MMCA.ADC.Conference.UI.Tests | 11 | BunitTestBase, Event, EventDTO, IEventUIService, ISessionUIService, ISpeakerLookupService, PublicSessionList, Result, RoleNames, SpeakerInfo, TestPrincipal | +| 11 | `PublicSessionListEventFilterTests` | MMCA.ADC.Conference.UI.Tests | 12 | BunitTestBase, Error, Event, EventDTO, IEventUIService, ISessionUIService, ISpeakerLookupService, PublicSessionList, Result, RoleNames, SpeakerInfo, TestPrincipal | +| 11 | `PublicSessionListMyScheduleTests` | MMCA.ADC.Conference.UI.Tests | 11 | BunitTestBase, Error, EventDTO, IEventUIService, ISessionBookmarkUIService, ISessionUIService, ISpeakerLookupService, PublicSessionList, Result, SpeakerInfo, TestPrincipal | | 11 | `PublicSessionListRoomFilterTests` | MMCA.ADC.Conference.UI.Tests | 10 | BunitTestBase, EventDTO, IEventUIService, ISessionUIService, ISpeakerLookupService, PublicSessionList, Result, Room, RoomDTO, SpeakerInfo | | 11 | `PublicSessionListSortTests` | MMCA.ADC.Conference.UI.Tests | 8 | BunitTestBase, EventDTO, IEventUIService, ISessionUIService, ISpeakerLookupService, PublicSessionList, Result, SpeakerInfo | | 11 | `PublicSpeakerDetailTests` | MMCA.ADC.Conference.UI.Tests | 10 | BunitTestBase, Error, EventDTO, IEventUIService, ISessionUIService, ISpeakerUIService, PublicSpeakerDetail, Result, SessionDTO, SpeakerDTO | | 11 | `PublicSpeakerListCardGridTests` | MMCA.ADC.Conference.UI.Tests | 7 | BunitTestBase, EventInfo, IEventLookupService, InfiniteScrollSentinel, ISpeakerUIService, PublicSpeakerList, SpeakerDTO | | 11 | `PublicSpeakerListEventFilterTests` | MMCA.ADC.Conference.UI.Tests | 7 | BunitTestBase, EventInfo, IEventLookupService, ISpeakerUIService, PublicSpeakerList, RoleNames, TestPrincipal | +| 11 | `SessionListCurrentEventClockTests` | MMCA.ADC.Conference.UI.Tests | 12 | BunitTestBase, Event, EventDTO, FixedTimeProvider, IEventUIService, ISessionUIService, ISpeakerLookupService, Result, RoleNames, SessionList, SpeakerInfo, TestPrincipal | | 11 | `SessionListEventFilterTests` | MMCA.ADC.Conference.UI.Tests | 11 | BunitTestBase, Event, EventDTO, IEventUIService, ISessionUIService, ISpeakerLookupService, Result, RoleNames, SessionList, SpeakerInfo, TestPrincipal | | 11 | `BookmarksController` | MMCA.ADC.Engagement.API | 18 | ApiControllerBase, CreateBookmarkRequest, DeleteEntityCommand, EngagementFeatures, Error, GetBookmarkedSessionIdsQuery, GetUserBookmarksQuery, ICommandHandler, ICurrentUserService, IEntityQueryService, IQueryHandler, OwnerOrAdminFilter, PagedCollectionResult, Result, RoleNames, Route, UserSessionBookmark, UserSessionBookmarkDTO | | 11 | `CheckInDTOMapper` | MMCA.ADC.Engagement.Application | 3 | CheckIn, CheckInDTO, IEntityDTOMapper | @@ -4483,8 +4520,6 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 11 | `HandlerMocks` | MMCA.ADC.Engagement.Application.Tests | 4 | AttendeeBadge, CheckIn, IEventLiveValidationService, IRepository | | 11 | `LivePollVoteChangedHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 12 | DomainEntityState, InMemoryQueryableExecutor, IReadRepository, IUnitOfWork, LivePoll, LivePollChannel, LivePollResultsBuilder, LivePollResultsDTO, LivePollVote, LivePollVoteChanged, LivePollVoteChangedHandler, RecordingQueue | | 11 | `CheckInTests` | MMCA.ADC.Engagement.Domain.Tests | 4 | AttendeeCheckedIn, CheckIn, CheckInScope, CheckInScopeNames | -| 11 | `CheckInConfiguration` | MMCA.ADC.Engagement.Infrastructure | 2 | CheckIn, EntityTypeConfigurationSQLServer | -| 11 | `EngagementEntityConfigurationTests` | MMCA.ADC.Engagement.Infrastructure.Tests | 9 | EngagementTestDbContext, LivePoll, LivePollInvariants, LivePollOption, LivePollVote, SessionQuestion, SessionQuestionInvariants, SessionQuestionUpvote, UserSessionBookmark | | 11 | `EngagementUIModule` | MMCA.ADC.Engagement.UI | 6 | EngagementRoutePaths, IUIModule, LiveEventListener, NavItem, NavSection, RoleNames | | 11 | `CheckInScanTests` | MMCA.ADC.Engagement.UI.Tests | 13 | AttendeeSearchPanel, AttendeeSummary, BunitComponentTestBase, CheckInScan, IAttendeeLookupService, IBarcodeScannerService, ICheckInUIService, ILiveEventUIService, ISessionLookupService, LiveEventContext, RoleNames, SessionInfo, TestPrincipal | | 11 | `LiveEventListenerResilienceTests` | MMCA.ADC.Engagement.UI.Tests | 15 | ApiSettings, BunitComponentTestBase, EngagementRoutePaths, IAccessibilityAnnouncer, IBatteryStatusService, ILiveEventUIService, IToastService, ITokenStorageService, LiveEventContext, LiveEventListener, LivePollChannel, NotificationHubService, NullAccessibilityAnnouncer, TestPrincipal, ToastSeverity | @@ -4492,12 +4527,17 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 11 | `RoomCheckInTests` | MMCA.ADC.Engagement.UI.Tests | 8 | BunitComponentTestBase, CheckIn, CheckInErrorCodes, ICheckInUIService, RoomCheckIn, RoomCheckInResultDTO, SelfCheckInOutcome, TestPrincipal | | 11 | `AdminRolesController` | MMCA.ADC.Identity.API | 4 | ICurrentUserService, IRoleAdministrationService, RolesAdminControllerBase, Route | | 11 | `UsersDataExportController` | MMCA.ADC.Identity.API | 7 | DataExportControllerBase, ExportUserDataQuery, ICurrentUserService, IQueryHandler, Result, Route, UserDataExportDTO | +| 11 | `EventLiveValidationServiceGrpcAdapterTests` | MMCA.ADC.Services.Tests | 5 | EventLiveValidationService, IEventLiveValidationService, QuestionModerationDefault, Result, SessionLiveInfo | +| 11 | `SessionBookmarksGrpcServiceTests` | MMCA.ADC.Services.Tests | 6 | Error, FakeServerCallContext, ISessionBookmarkValidationService, Result, ResultFailureException, SessionBookmarksGrpcService | | 11 | `MauiProgram` | MMCA.ADC.UI | 14 | ADCHomePageContent, App, AppActionRouteMap, AppActionsInitializer, ConfigurationOAuthUISettings, DeviceUIModule, DirectApiTokenRefresher, IDeepLinkDispatcher, IHomePageContent, IOAuthUISettings, ITokenRefresher, IUIModule, JwtAuthenticationStateProvider, UIModuleConfiguration | -| 11 | `DependencyInjection` | MMCA.Common.API | 25 | CookieSessionRefresher, CookieTokenReader, CurrencyJsonConverter, CurrentUserTargetingContextAccessor, DbUpdateExceptionHandler, DisabledFeatureHandler, DomainExceptionHandler, EnumerationJsonConverterFactory, ErrorLocalizer, ErrorResources, ErrorResourceSource, GlobalExceptionHandler, ICookieSessionRefresher, IdempotencyFilter, IdempotencySettings, IErrorLocalizer, ModuleControllerFeatureProvider, ModuleLoader, ModulesSettings, OperationCanceledExceptionHandler …(+5) | +| 11 | `CookieSessionRefreshMiddleware` | MMCA.Common.API | 1 | ICookieSessionRefresher | +| 11 | `SessionCookieEndpoints` | MMCA.Common.API | 6 | ICookieSessionRefresher, SessionClaimsToken, SessionCookieJar, SessionCookieRequest, SessionCookieSettings, SessionTokenResponse | +| 11 | `SessionCookieJar` | MMCA.Common.API | 1 | SessionCookieEndpoints | | 11 | `DependencyInjectionTests` | MMCA.Common.API.Tests | 11 | DbUpdateExceptionHandler, DisabledFeatureHandler, DomainExceptionHandler, GlobalExceptionHandler, IdempotencyFilter, IdempotencySettings, IModule, ModuleLoader, OperationCanceledExceptionHandler, OwnerOrAdminFilter, ValidationExceptionHandler | | 11 | `MiddlewarePipelineBuilderTests` | MMCA.Common.API.Tests | 3 | MiddlewarePipelineBuilder, MiddlewarePipelineStep, MiddlewarePipelineStepNames | | 11 | `OwnerOrAdminFilterTests` | MMCA.Common.API.Tests | 4 | AllowMissingOwnerAttribute, ICurrentUserService, OwnerOrAdminFilter, OwnerOrAdminFilterOptions | | 11 | `RateLimitEdgeCaseSecurityTests` | MMCA.Common.API.Tests | 5 | EndpointFeatureStub, FakeGrpcMetadata, MiddlewarePipelineBuilder, RateLimitingSettings, WebApplicationBuilderExtensions | +| 11 | `StubRefresher` | MMCA.Common.API.Tests | 3 | ICookieSessionRefresher, SessionRefreshOutcome, SessionTokenResult | | 11 | `TestDataExportController` | MMCA.Common.API.Tests | 6 | DataExportControllerBase, ICurrentUserService, IQueryHandler, Result, TestExportQuery, UserDataExportDTO | | 11 | `TestRolesAdminController` | MMCA.Common.API.Tests | 3 | ICurrentUserService, IRoleAdministrationService, RolesAdminControllerBase | | 11 | `DependencyInjection` | MMCA.Common.Application | 53 | AuditableAggregateRootEntity, AuthorizationCommandDecorator, AuthorizationQueryDecorator, CachingCommandDecorator, CachingQueryDecorator, ClassReference, CommandRequestValidator, CreateEntityHandler, DecoratorPipelineSeal, DeleteEntityCommand, DeleteEntityHandler, DomainEventDispatcher, EntityQueryPipeline, EventUpcasterRegistry, FeatureGateCommandDecorator, FeatureGateQueryDecorator, IBaseDTO, ICommandHandler, ICommandWithRequest, ICreateRequest …(+33) | @@ -4516,15 +4556,29 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 11 | `DomainEventHandlerSaveFitnessTests` | MMCA.Common.Architecture.Tests | 8 | ArchitectureRules, DirectSavingHandler, FixtureAssemblyMap, InnocentHandler, InterfaceDispatchSavingHandler, PointsAwarder, PointsWriter, TransitiveSavingHandler | | 11 | `Extensions` | MMCA.Common.Aspire | 13 | CachedHealthReportProvider, HealthCheckTags, HealthEndpointPaths, HealthReportCacheOptions, HttpResilienceDefaults, IWarmupTask, OpenIdConnectMetadataWarmupTask, OutboxPollFilterProcessor, ProbeTelemetryFilterProcessor, RedisPingHealthCheck, WarmupHostedService, WarmupReadinessGate, WarmupReadinessHealthCheck | | 11 | `ProbeTelemetryFilterProcessorTests` | MMCA.Common.Aspire.Tests | 3 | Activity, ProbeTelemetryFilter, ProbeTelemetryFilterProcessor | -| 11 | `ApplicationDbContext` | MMCA.Common.Infrastructure | 36 | AuditableBaseEntity, AuditSaveChangesInterceptor, AuditTrailEntry, AuditTrailSaveChangesInterceptor, AuditTrailSettings, CrossDataSourceDegradeConvention, DataSource, DataSourceKey, DataSourceModelCacheKeyFactory, DetectChangesScope, DomainEventSaveChangesInterceptor, IAuditableEntity, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IEntityTypeConfigurationCosmos, IEntityTypeConfigurationPostgreSQL, IEntityTypeConfigurationSqlite, IEntityTypeConfigurationSQLServer, InboxMessage, InternalCommandMessage …(+16) | -| 11 | `AuditSaveChangesInterceptor` | MMCA.Common.Infrastructure | 2 | ApplicationDbContext, IAuditableEntity | +| 11 | `ApplicationDbContext` | MMCA.Common.Infrastructure | 36 | AuditableBaseEntity, AuditSaveChangesInterceptor, AuditTrailEntry, AuditTrailSaveChangesInterceptor, AuditTrailSettings, CrossDataSourceDegradeConvention, DataSource, DataSourceEngines, DataSourceKey, DataSourceModelCacheKeyFactory, DetectChangesScope, DomainEventSaveChangesInterceptor, IAuditableEntity, IDataSourceEngine, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, InboxMessage, InternalCommandMessage, ITenantEntity, OutboxMessage …(+16) | +| 11 | `AuditSaveChangesInterceptor` | MMCA.Common.Infrastructure | 4 | ApplicationDbContext, IAuditableEntity, IRowVersioned, RowVersionStrategy | | 11 | `AuditTrailSaveChangesInterceptor` | MMCA.Common.Infrastructure | 12 | Activity, ApplicationDbContext, AuditTrailEntry, CaptureContext, ColumnWidth, IAuditedEntity, InboxMessage, OutboxMessage, PendingEntityKey, PiiAttribute, PiiRedactor, ScheduledJobEntry | +| 11 | `CosmosDataSourceEngine` | MMCA.Common.Infrastructure | 17 | ApplicationDbContext, AuditableBaseEntity, ConnectionStringSettings, CosmosDbContext, CosmosIntIdValueGenerator, DataSource, DataSourceEngineCapabilities, DataSourceEntrySettings, IDataSourceEngine, IEntityConfigurationAssemblyProvider, IEntityTypeConfigurationCosmos, IExplicitKeyInsertDialect, MigrationPolicy, NamespaceConventions, PhysicalDataSource, RowVersionStrategy, TenantDataSourceOverrideSettings | +| 11 | `CosmosDbContext` | MMCA.Common.Infrastructure | 6 | ApplicationDbContext, DataSource, IEntityConfigurationAssemblyProvider, InternalCommandMessage, OutboxMessage, PhysicalDataSource | +| 11 | `CrossDataSourceDegradeConvention` | MMCA.Common.Infrastructure | 3 | DataSourceEngines, DataSourceKey, IEntityDataSourceRegistry | +| 11 | `DataSourceEngines` | MMCA.Common.Infrastructure | 6 | CosmosDataSourceEngine, DataSource, IDataSourceEngine, PostgreSQLDataSourceEngine, SqliteDataSourceEngine, SQLServerDataSourceEngine | | 11 | `DataSourceModelCacheKeyFactory` | MMCA.Common.Infrastructure | 1 | ApplicationDbContext | | 11 | `DeferredDispatch` | MMCA.Common.Infrastructure | 2 | CapturedState, DomainEventSaveChangesInterceptor | | 11 | `DomainEventSaveChangesInterceptor` | MMCA.Common.Infrastructure | 12 | AggregateCapture, ApplicationDbContext, CapturedState, DeferredDispatch, IAggregateRoot, IDomainEvent, IDomainEventDispatcher, IIntegrationEvent, IOutboxSignal, MessageBusSettings, OutboxFinalizer, OutboxMessage | +| 11 | `IDataSourceEngine` | MMCA.Common.Infrastructure | 10 | ApplicationDbContext, AuditableBaseEntity, ConnectionStringSettings, DataSource, DataSourceEngineCapabilities, DataSourceEntrySettings, IEntityConfigurationAssemblyProvider, IExplicitKeyInsertDialect, PhysicalDataSource, TenantDataSourceOverrideSettings | | 11 | `OutboxFinalizer` | MMCA.Common.Infrastructure | 2 | ApplicationDbContext, OutboxMessage | +| 11 | `PhysicalDataSource` | MMCA.Common.Infrastructure | 3 | DataSourceEngines, DataSourceKey, MigrationPolicy | +| 11 | `PostgreSQLDataSourceEngine` | MMCA.Common.Infrastructure | 16 | ApplicationDbContext, AuditableBaseEntity, ConnectionStringSettings, DataSource, DataSourceEngineCapabilities, DataSourceEntrySettings, IDataSourceEngine, IEntityConfigurationAssemblyProvider, IEntityTypeConfigurationPostgreSQL, IExplicitKeyInsertDialect, MigrationPolicy, NamespaceConventions, PhysicalDataSource, PostgreSQLDbContext, RowVersionStrategy, TenantDataSourceOverrideSettings | +| 11 | `PostgreSQLDbContext` | MMCA.Common.Infrastructure | 6 | ApplicationDbContext, DataSource, IEntityConfigurationAssemblyProvider, PersistenceSettings, PhysicalDataSource, UtcDateTimeConverter | +| 11 | `RestrictDeleteByDefaultConvention` | MMCA.Common.Infrastructure | 2 | DataSource, DataSourceEngines | +| 11 | `SoftDeleteFilterSql` | MMCA.Common.Infrastructure | 3 | DataSource, DataSourceEngines, IAuditableEntity | +| 11 | `SoftDeleteUniqueIndexConvention` | MMCA.Common.Infrastructure | 4 | DataSource, DataSourceEngines, IAuditableEntity, SoftDeleteFilterSql | +| 11 | `SqliteDataSourceEngine` | MMCA.Common.Infrastructure | 15 | ApplicationDbContext, AuditableBaseEntity, ConnectionStringSettings, DataSource, DataSourceEngineCapabilities, DataSourceEntrySettings, IDataSourceEngine, IEntityConfigurationAssemblyProvider, IEntityTypeConfigurationSqlite, IExplicitKeyInsertDialect, MigrationPolicy, PhysicalDataSource, RowVersionStrategy, SqliteDbContext, TenantDataSourceOverrideSettings | +| 11 | `SqliteDbContext` | MMCA.Common.Infrastructure | 4 | ApplicationDbContext, DataSource, IEntityConfigurationAssemblyProvider, PhysicalDataSource | +| 11 | `SQLServerDataSourceEngine` | MMCA.Common.Infrastructure | 17 | ApplicationDbContext, AuditableBaseEntity, ConnectionStringSettings, DataSource, DataSourceEngineCapabilities, DataSourceEntrySettings, ExplicitKeyInsertGroup, IDataSourceEngine, IEntityConfigurationAssemblyProvider, IEntityTypeConfigurationSQLServer, IExplicitKeyInsertDialect, MigrationPolicy, NamespaceConventions, PhysicalDataSource, RowVersionStrategy, SQLServerDbContext, TenantDataSourceOverrideSettings | +| 11 | `SQLServerDbContext` | MMCA.Common.Infrastructure | 5 | ApplicationDbContext, DataSource, IEntityConfigurationAssemblyProvider, PersistenceSettings, PhysicalDataSource | | 11 | `TenantSaveChangesInterceptor` | MMCA.Common.Infrastructure | 3 | ApplicationDbContext, CrossTenantWriteException, ITenantEntity | -| 11 | `RepositoryFactoryTests` | MMCA.Common.Infrastructure.Tests | 13 | ApplicationSettings, EFReadRepository, EFReadRepositoryDecorator, EFRepository, EFRepositoryDecorator, FakeAggregate, FakeAggregate, FakeEntity, FakeEntity, IReadRepository, IRepository, RepositoryFactory, TestDbContext | | 11 | `MiddlewarePipelineOrderTestsBase` | MMCA.Common.Testing | 2 | MiddlewarePipelineBuilder, MiddlewarePipelineStepNames | | 11 | `GalleryE2ECollection` | MMCA.Common.UI.E2E.Tests | 2 | GalleryHostFixture, PlaywrightFixture | | 12 | `AdcArchitectureMap` | MMCA.ADC.Architecture.Tests | 19 | ApiControllerBase, ApplicationDbContext, ArchitectureMapBase, BaseEntity, ConferenceModule, EngagementModule, EntityQueryService, Event, EventDTO, IdentityModule, Layer, LayerRef, NotificationModule, Result, User, UserDTO, UserNotificationExportItemDTO, UserSessionBookmark, UserSessionBookmarkDTO | @@ -4537,21 +4591,30 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 12 | `ManualCheckInHandler` | MMCA.ADC.Engagement.Application | 8 | CheckInProcessor, CheckInResultDTO, ICommandHandler, ICurrentUserService, IEventLiveValidationService, IUnitOfWork, ManualCheckInRequest, Result | | 12 | `RecordRoomCheckInHandler` | MMCA.ADC.Engagement.Application | 13 | CheckIn, CheckInProcessor, CheckInScope, CheckInSettings, Error, ErrorType, ICommandHandler, ICurrentUserService, IEventLiveValidationService, IUnitOfWork, Result, RoomCheckInRequest, RoomCheckInResultDTO | | 12 | `RecordSponsorVisitHandler` | MMCA.ADC.Engagement.Application | 9 | CheckInProcessor, CheckInScope, ICommandHandler, ICurrentUserService, IEventLiveValidationService, IUnitOfWork, Result, SponsorVisitRequest, SponsorVisitResultDTO | -| 12 | `UserEngagementExportServiceGrpcAdapter` | MMCA.ADC.Engagement.Contracts | 9 | CheckInScope, IUserEngagementExportService, PointsActivityType, UserEngagementBookmarkExportDTO, UserEngagementCheckInExportDTO, UserEngagementExportDTO, UserEngagementExportService, UserEngagementPointsEntryExportDTO, UserEngagementSubmittedQuestionExportDTO | +| 12 | `UserEngagementExportServiceGrpcAdapter` | MMCA.ADC.Engagement.Contracts | 10 | CheckInScope, GrpcWireFormat, IUserEngagementExportService, PointsActivityType, UserEngagementBookmarkExportDTO, UserEngagementCheckInExportDTO, UserEngagementExportDTO, UserEngagementExportService, UserEngagementPointsEntryExportDTO, UserEngagementSubmittedQuestionExportDTO | | 12 | `ModuleApplicationDbContext` | MMCA.ADC.Engagement.Infrastructure | 13 | ApplicationDbContext, AttendeeBadge, CheckIn, IEntityConfigurationAssemblyProvider, LeaderboardOptIn, LivePoll, LivePollOption, LivePollVote, PhysicalDataSource, PointsEntry, SessionQuestion, SessionQuestionUpvote, UserSessionBookmark | -| 12 | `UserEngagementExportGrpcService` | MMCA.ADC.Engagement.Service | 3 | IUserEngagementExportService, LeaderboardOptIn, UserEngagementExportService | +| 12 | `UserEngagementExportGrpcService` | MMCA.ADC.Engagement.Service | 4 | GrpcWireFormat, IUserEngagementExportService, LeaderboardOptIn, UserEngagementExportService | | 12 | `DependencyInjection` | MMCA.ADC.Engagement.UI | 33 | AttendeeLookupService, BookmarkService, CheckInService, CurrentEventNotificationScopeProvider, EngagementUIModule, EventFeedbackService, IAttendeeLookupService, IBookmarkUIService, ICheckInUIService, IEventFeedbackUIService, ILiveEventUIService, ILivePollUIService, INotificationScopeProvider, INowNextService, IPointsUIService, IQuestionLookupService, ISessionBookmarkUIService, ISessionFeedbackUIService, ISessionLiveUIService, ISessionLookupService …(+13) | | 12 | `AdminRolesControllerTests` | MMCA.ADC.Identity.API.Tests | 12 | AdministrationPermissions, AdminRolesController, Error, HasPermissionAttribute, ICurrentUserService, IdentityPermissionGrants, IRoleAdministrationService, PermissionRegistryBuilder, Result, RoleNames, RolePermissionsResponse, SetRolePermissionsRequest | | 12 | `UsersDataExportControllerTests` | MMCA.ADC.Identity.API.Tests | 10 | Email, Error, ExportUserDataQuery, ICurrentUserService, IQueryHandler, Result, Subject, UserDataExportDTO, UserDataExportSubjectDTO, UsersDataExportController | | 12 | `ModuleApplicationDbContext` | MMCA.ADC.Identity.Infrastructure | 4 | ApplicationDbContext, IEntityConfigurationAssemblyProvider, PhysicalDataSource, User | | 12 | `GateContext` | MMCA.ADC.Identity.Infrastructure.Tests | 3 | ApplicationDbContext, IEntityConfigurationAssemblyProvider, PhysicalDataSource | +| 12 | `EventLiveValidationGrpcServiceTests` | MMCA.ADC.Services.Tests | 13 | Error, EventLiveInfo, EventLiveValidationGrpcService, EventLiveValidationService, FakeServerCallContext, GrpcCalls, IEventLiveValidationService, QuestionModerationDefault, Result, ResultFailureException, RoomSessionInfo, SessionLiveInfo, SponsorLiveInfo | | 12 | `App` | MMCA.ADC.UI | 1 | MauiProgram | -| 12 | `AppDelegate` | MMCA.ADC.UI | 2 | IDeepLinkDispatcher, MauiProgram | +| 12 | `AppDelegate` | MMCA.ADC.UI | 3 | DeepLinkDispatcher, IDeepLinkDispatcher, MauiProgram | | 12 | `MainApplication` | MMCA.ADC.UI | 1 | MauiProgram | +| 12 | `CookieSessionRefreshMiddlewareExtensions` | MMCA.Common.API | 1 | CookieSessionRefreshMiddleware | +| 12 | `CookieTokenReader` | MMCA.Common.API | 1 | SessionCookieEndpoints | +| 12 | `SessionCookieStore` | MMCA.Common.API | 3 | ISessionCookieStore, SessionCookieJar, SessionCookieSettings | | 12 | `AdministrationControllerBaseTests` | MMCA.Common.API.Tests | 19 | AdministrationPermissions, Error, HasPermissionAttribute, ICurrentUserService, IRoleAdministrationService, IUserAdministrationService, PagedCollectionResult, PaginationMetadata, PermissionCatalogResponse, Result, RolePermissionsResponse, RolesAdminControllerBase, SetRolePermissionsRequest, SetUserRolesRequest, TestRolesAdminController, TestUserDto, TestUsersAdminController, UserAdministrationQuery, UsersAdminControllerBase | +| 12 | `ClaimsOnlySessionCookieEndpointsTests` | MMCA.Common.API.Tests | 7 | ICookieSessionRefresher, SessionCookieEndpoints, SessionCookieRequest, SessionCookieSettings, SessionTokenResponse, SessionTokenResult, StubRefresher | | 12 | `DataExportControllerBaseTests` | MMCA.Common.API.Tests | 13 | DataExportControllerBase, Error, ICurrentUserService, IQueryHandler, PrivacyFeatures, Result, StubFeatureManager, Subject, SubjectSnapshot, TestDataExportController, TestExportQuery, UserDataExportDTO, UserDataExportSectionDTO | +| 12 | `SessionCookieEndpointsTests` | MMCA.Common.API.Tests | 6 | ICookieSessionRefresher, SessionCookieEndpoints, SessionCookieRequest, SessionTokenResponse, SessionTokenResult, StubRefresher | +| 12 | `SessionCookieJarTests` | MMCA.Common.API.Tests | 2 | SessionCookieEndpoints, SessionCookieJar | | 12 | `ChildEntityHandlerBaseTests` | MMCA.Common.Application.Tests | 8 | AddOrderLineCommand, ErrorType, IRepository, IUnitOfWork, OrderAggregate, RemoveOrderLineCommand, TestAddOrderLineHandler, TestRemoveOrderLineHandler | | 12 | `CommonArchitectureMap` | MMCA.Common.Architecture.Tests | 10 | ApiControllerBase, ApplicationDbContext, ArchitectureMapBase, BaseEntity, DomainEventDispatcher, Layer, LayerRef, Result, ResultGrpcExtensions, UISharedAssemblyReference | +| 12 | `FrameworkModels` | MMCA.Common.Architecture.Tests | 12 | AuditSaveChangesInterceptor, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, IEntityDataSourceRegistry, NoDomainEventDispatcher, NoEntityDataSources, NoModuleAssemblies, NoOutboxSignal, PhysicalDataSource, SqliteDbContext, SQLServerDbContext | +| 12 | `FrameworkModuleArchitectureMap` | MMCA.Common.Architecture.Tests | 7 | ApplicationDbContext, ArchitectureMapBase, BaseEntity, DomainEventDispatcher, Layer, LayerRef, Result | | 12 | `FrameworkSanityTests` | MMCA.Common.Architecture.Tests | 7 | ApplicationDbContext, ArchitectureAssert, DomainEventDispatcher, IJwksProvider, ILiveChannelPublisher, IMessageBus, ResultGrpcExtensions | | 12 | `ModuleIsolationTestsBaseTests` | MMCA.Common.Architecture.Tests | 5 | ApplicationDbContext, ArchitectureRules, BaseEntity, Layer, StubMap | | 12 | `ServiceBusEmulatorBrokerTests` | MMCA.Common.Aspire.Hosting.Tests | 3 | Extensions, Extensions, ServiceBusEmulatorResource | @@ -4563,58 +4626,31 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 12 | `RedisReadinessSafetyTests` | MMCA.Common.Aspire.Tests | 2 | Extensions, HealthCheckTags | | 12 | `StubHostEnvironment` | MMCA.Common.Aspire.Tests | 1 | Extensions | | 12 | `TracesSampleRatioTests` | MMCA.Common.Aspire.Tests | 1 | Extensions | -| 12 | `CosmosDbContext` | MMCA.Common.Infrastructure | 6 | ApplicationDbContext, DataSource, IEntityConfigurationAssemblyProvider, InternalCommandMessage, OutboxMessage, PhysicalDataSource | +| 12 | `DataSourceService` | MMCA.Common.Infrastructure | 5 | DataSource, DataSourceEngines, DataSourceKey, IDataSourceService, IEntityDataSourceRegistry | +| 12 | `EFReadRepository` | MMCA.Common.Infrastructure | 19 | ApplicationDbContext, AuditableBaseEntity, BaseLookup, DataSourceEngineCapabilities, EntityQueryPipeline, Error, GroupedCount, GroupedSum, IReadRepository, ISpecification, KeysetCollectionResult, KeysetCursor, KeysetPageRequest, KeysetQueryBuilder, LookupRow, QuerySpecification, QueryTags, Result, SpecificationEvaluator | +| 12 | `EntityTypeConfiguration` | MMCA.Common.Infrastructure | 9 | AuditableBaseEntity, DataSource, DataSourceEngines, EntityTypeConfigurationBase, IEntityTypeConfigurationCosmos, IEntityTypeConfigurationPostgreSQL, IEntityTypeConfigurationSqlite, IEntityTypeConfigurationSQLServer, UseDataSourceAttribute | +| 12 | `IDataSourceResolver` | MMCA.Common.Infrastructure | 3 | DataSource, DataSourceKey, PhysicalDataSource | | 12 | `IDbContextFactory` | MMCA.Common.Infrastructure | 2 | ApplicationDbContext, DataSourceKey | +| 12 | `IndexBuilderExtensions` | MMCA.Common.Infrastructure | 2 | DataSource, SoftDeleteFilterSql | | 12 | `IPhysicalDbContextFactory` | MMCA.Common.Infrastructure | 3 | ApplicationDbContext, DataSourceKey, PhysicalDataSource | -| 12 | `PostgreSQLDbContext` | MMCA.Common.Infrastructure | 6 | ApplicationDbContext, DataSource, IEntityConfigurationAssemblyProvider, PersistenceSettings, PhysicalDataSource, UtcDateTimeConverter | -| 12 | `SqliteDbContext` | MMCA.Common.Infrastructure | 4 | ApplicationDbContext, DataSource, IEntityConfigurationAssemblyProvider, PhysicalDataSource | -| 12 | `SQLServerDbContext` | MMCA.Common.Infrastructure | 5 | ApplicationDbContext, DataSource, IEntityConfigurationAssemblyProvider, PersistenceSettings, PhysicalDataSource | -| 12 | `AddMultiTenancyTests` | MMCA.Common.Infrastructure.Tests | 11 | ConnectionStringSettings, DataSourceResolver, DataSourcesSettings, ITenantContext, TenancySettings, TenancySettingsValidator, TenantContext, TenantDataSourceOverrideSettings, TenantEntrySettings, TenantResolutionStrategy, TenantSaveChangesInterceptor | -| 12 | `AdminTestContext` | MMCA.Common.Infrastructure.Tests | 10 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NoAssemblies, OutboxMessage, TestPhysicalDataSources | -| 12 | `AuditTrailTestContext` | MMCA.Common.Infrastructure.Tests | 16 | ApplicationDbContext, AuditedThing, AuditSaveChangesInterceptor, AuditTrailEntry, AuditTrailSaveChangesInterceptor, CompositeKeyThing, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FailingSaveInterceptor, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, PlainThing, TestPhysicalDataSources | -| 12 | `CleanupTestContext` | MMCA.Common.Infrastructure.Tests | 13 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, InboxMessage, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, NullAssemblyProvider, OutboxMessage, TestPhysicalDataSources | -| 12 | `CommitFailingDbContext` | MMCA.Common.Infrastructure.Tests | 13 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FailingDatabaseFacade, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, OutboxMessage, TestAggregate, TestPhysicalDataSources | +| 12 | `CosmosDbContextTests` | MMCA.Common.Infrastructure.Tests | 1 | CosmosDbContext | | 12 | `DegradeTestContext` | MMCA.Common.Infrastructure.Tests | 7 | ApplicationDbContext, DataSourceKey, DegradeCustomer, DegradeOrder, EmptyAssemblyProvider, IEntityDataSourceRegistry, PhysicalDataSource | -| 12 | `DeleteBehaviorTestDbContext` | MMCA.Common.Infrastructure.Tests | 14 | ApplicationDbContext, AuditSaveChangesInterceptor, CascadingChild, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NoModuleAssemblies, NoModuleAssemblies, OptionalChild, Parent, RequiredChild, TestPhysicalDataSources | -| 12 | `DetectionTestDbContext` | MMCA.Common.Infrastructure.Tests | 12 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources, Widget, Widget | -| 12 | `ExclusionTestDbContext` | MMCA.Common.Infrastructure.Tests | 9 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, ExclusionAggregate, IEntityDataSourceRegistry, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources | -| 12 | `FailingDatabaseFacade` | MMCA.Common.Infrastructure.Tests | 2 | AlwaysRetryExecutionStrategy, CommitFailingDbContext | -| 12 | `FailingSaveInterceptor` | MMCA.Common.Infrastructure.Tests | 1 | AuditTrailTestContext | -| 12 | `FailingSaveInterceptor` | MMCA.Common.Infrastructure.Tests | 1 | OutboxRoutingTestDbContext | | 12 | `GateContext` | MMCA.Common.Infrastructure.Tests | 3 | ApplicationDbContext, IEntityConfigurationAssemblyProvider, PhysicalDataSource | | 12 | `GateTestContext` | MMCA.Common.Infrastructure.Tests | 13 | ApplicationDbContext, AuditSaveChangesInterceptor, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, GateTestContext, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, PhysicalDataSource, SchedulerSettings | | 12 | `GateTestContext` | MMCA.Common.Infrastructure.Tests | 14 | ApplicationDbContext, AuditSaveChangesInterceptor, AuditTrailSettings, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, GateTestContext, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, PhysicalDataSource | -| 12 | `InboxTestDbContext` | MMCA.Common.Infrastructure.Tests | 4 | ApplicationDbContext, IEntityConfigurationAssemblyProvider, InboxMessage, TestPhysicalDataSources | -| 12 | `IntegrityTestDbContext` | MMCA.Common.Infrastructure.Tests | 12 | ApplicationDbContext, AuditSaveChangesInterceptor, DataSourceKey, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IntegrityAggregate, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, PhysicalDataSource | -| 12 | `InternalCommandTestContext` | MMCA.Common.Infrastructure.Tests | 11 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, InternalCommandMessage, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources | -| 12 | `MidSaveContextCreatingDbContext` | MMCA.Common.Infrastructure.Tests | 10 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IOutboxSignal, ReentrantSaveInterceptor, TestPhysicalDataSources | +| 12 | `IntegrityTestDbContext` | MMCA.Common.Infrastructure.Tests | 13 | ApplicationDbContext, AuditSaveChangesInterceptor, DataSourceKey, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IntegrityAggregate, IOutboxSignal, MessageBusSettings, NullAssemblyProvider, NullAssemblyProvider, PhysicalDataSource | | 12 | `ModelTestContext` | MMCA.Common.Infrastructure.Tests | 12 | ApplicationDbContext, AuditSaveChangesInterceptor, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, PhysicalDataSource | | 12 | `NamedSoftDeleteTestDbContext` | MMCA.Common.Infrastructure.Tests | 2 | ApplicationDbContext, ProjectedTestEntity | -| 12 | `NoSessionTableContext` | MMCA.Common.Infrastructure.Tests | 6 | ApplicationDbContext, NullAssemblyProvider, NullAssemblyProvider, NullAssemblyProvider, OutboxMessage, TestPhysicalDataSources | -| 12 | `OrderingTestContext` | MMCA.Common.Infrastructure.Tests | 10 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NoAssemblies, OutboxMessage, TestPhysicalDataSources | -| 12 | `OutboxRoutingTestDbContext` | MMCA.Common.Infrastructure.Tests | 11 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FailingSaveInterceptor, IEntityDataSourceRegistry, NullAssemblyProvider, NullAssemblyProvider, OutboxMessage, TestAggregate, TestPhysicalDataSources | -| 12 | `OutboxTestDbContext` | MMCA.Common.Infrastructure.Tests | 4 | ApplicationDbContext, IEntityConfigurationAssemblyProvider, OutboxMessage, TestPhysicalDataSources | | 12 | `Participant` | MMCA.Common.Infrastructure.Tests | 2 | ApplicationDbContext, IRefreshSessionStore | -| 12 | `QueryShapeTestDbContext` | MMCA.Common.Infrastructure.Tests | 11 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, Product, TestPhysicalDataSources | -| 12 | `ReentrantSaveInterceptor` | MMCA.Common.Infrastructure.Tests | 1 | MidSaveContextCreatingDbContext | -| 12 | `RestoreTestDbContext` | MMCA.Common.Infrastructure.Tests | 4 | ApplicationDbContext, IEntityConfigurationAssemblyProvider, OutboxMessage, TestPhysicalDataSources | -| 12 | `SchedulerTestContext` | MMCA.Common.Infrastructure.Tests | 10 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, ScheduledJobEntry, TestPhysicalDataSources | -| 12 | `SessionCleanupTestContext` | MMCA.Common.Infrastructure.Tests | 5 | ApplicationDbContext, NullAssemblyProvider, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources | -| 12 | `SoftDeleteTestDbContext` | MMCA.Common.Infrastructure.Tests | 11 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, SoftDeletableEntity, SoftDeleteTestDbContext, TestPhysicalDataSources | +| 12 | `PhysicalDataSourceTests` | MMCA.Common.Infrastructure.Tests | 3 | DataSource, DataSourceKey, PhysicalDataSource | | 12 | `SpecificationTestDbContext` | MMCA.Common.Infrastructure.Tests | 4 | ApplicationDbContext, NullableEmailValueConverter, SpecTestChild, SpecTestEntity | -| 12 | `StampTestDbContext` | MMCA.Common.Infrastructure.Tests | 11 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, StampedEntity, TestPhysicalDataSources | -| 12 | `TenantTestContext` | MMCA.Common.Infrastructure.Tests | 17 | ApplicationDbContext, AuditSaveChangesInterceptor, AuditTrailEntry, AuditTrailSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, PlainThing, TenantOnlyThing, TenantSaveChangesInterceptor, TenantThing, TestPhysicalDataSources, TrailedTenantThing | -| 12 | `TestApplicationDbContext` | MMCA.Common.Infrastructure.Tests | 10 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IOutboxSignal, TestEntity, TestPhysicalDataSources | -| 12 | `TestAuditDbContext` | MMCA.Common.Infrastructure.Tests | 11 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, TestAuditEntity, TestPhysicalDataSources | -| 12 | `TestDomainEventDbContext` | MMCA.Common.Infrastructure.Tests | 9 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IEntityDataSourceRegistry, NullAssemblyProvider, NullAssemblyProvider, TestAggregate, TestPhysicalDataSources | -| 12 | `TestNonOutboxContext` | MMCA.Common.Infrastructure.Tests | 9 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, TestPhysicalDataSources | -| 12 | `TestOutboxContext` | MMCA.Common.Infrastructure.Tests | 10 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, OutboxMessage, TestPhysicalDataSources | -| 12 | `TransactionTestDbContext` | MMCA.Common.Infrastructure.Tests | 12 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, OutboxMessage, TestAggregate, TestPhysicalDataSources | -| 12 | `UniqueIndexTestDbContext` | MMCA.Common.Infrastructure.Tests | 15 | AlreadySoftDeleteFilteredEntity, ApplicationDbContext, AuditSaveChangesInterceptor, BracketQuotedFilterEntity, DataSource, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FilteredIndexEntity, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources, UniqueNamedEntity | -| 12 | `WidgetContext` | MMCA.Common.Infrastructure.Tests | 10 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NoModuleAssemblies, TestPhysicalDataSources, Widget | +| 12 | `TestPhysicalDataSources` | MMCA.Common.Infrastructure.Tests | 3 | DataSource, DataSourceKey, PhysicalDataSource | | 12 | `WrappedIdContextServices` | MMCA.Common.Infrastructure.Tests | 13 | AuditSaveChangesInterceptor, CustomerId, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, LineId, OrderId, SpeakerId, StronglyTypedIdRegistry, WrappedOrder, WrappedSpeaker | +| 12 | `CreateMigrationProofTable` | MMCA.Common.Infrastructure.Tests.MigrationsFixture | 1 | SqliteDbContext | | 12 | `MiddlewarePipelineOrderTests` | MMCA.Common.Testing.Tests | 1 | MiddlewarePipelineOrderTestsBase | | 12 | `GalleryAxeTestBase` | MMCA.Common.UI.E2E.Tests | 4 | E2ETestConfiguration, GalleryE2ECollection, GalleryHostFixture, PlaywrightFixture | +| 12 | `SameOriginProxyTransformer` | MMCA.Common.UI.Web | 5 | ISessionCookieStore, ProxyResponseMode, SameOriginProxyHeaders, SessionClaimsToken, SessionCookieEndpoints | +| 12 | `ProxyHost` | MMCA.Common.UI.Web.Tests | 4 | ApiSettings, FakeGateway, SameOriginProxyInvoker, SessionCookieEndpoints | | 13 | `AiDependencyIsolationTests` | MMCA.ADC.Architecture.Tests | 3 | AdcArchitectureMap, AiDependencyIsolationTestsBase, IArchitectureMap | | 13 | `CascadeSoftDeleteConventionTests` | MMCA.ADC.Architecture.Tests | 3 | AdcArchitectureMap, CascadeSoftDeleteConventionTestsBase, IArchitectureMap | | 13 | `ClockReadTests` | MMCA.ADC.Architecture.Tests | 3 | AdcArchitectureMap, ClockReadTestsBase, IArchitectureMap | @@ -4657,36 +4693,42 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 13 | `StronglyTypedIdTests` | MMCA.ADC.Architecture.Tests | 3 | AdcArchitectureMap, IArchitectureMap, StronglyTypedIdTestsBase | | 13 | `UIArchitectureConventionTests` | MMCA.ADC.Architecture.Tests | 3 | AdcArchitectureMap, IArchitectureMap, UIArchitectureConventionTestsBase | | 13 | `DependencyInjection` | MMCA.ADC.Engagement.Contracts | 6 | BookmarkCountService, BookmarkCountServiceGrpcAdapter, IBookmarkCountService, IUserEngagementExportService, UserEngagementExportService, UserEngagementExportServiceGrpcAdapter | -| 13 | `RefreshSessionModelGateTests` | MMCA.ADC.Identity.Infrastructure.Tests | 19 | AuditSaveChangesInterceptor, CaseConference, CaseIdentity, CaseWrongSource, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourceResolver, DataSourcesSettings, DomainEventSaveChangesInterceptor, GateContext, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IOutboxSignal, PhysicalDataSource, RefreshSession, RefreshSessionSettings | | 13 | `UserEngagementExportGrpcServiceTests` | MMCA.ADC.Services.Tests | 8 | CheckInScope, FakeServerCallContext, IUserEngagementExportService, UserEngagementBookmarkExportDTO, UserEngagementCheckInExportDTO, UserEngagementExportDTO, UserEngagementExportGrpcService, UserEngagementSubmittedQuestionExportDTO | | 13 | `UserEngagementExportServiceGrpcAdapterTests` | MMCA.ADC.Services.Tests | 4 | CheckInScope, UserEngagementExportDTO, UserEngagementExportService, UserEngagementExportServiceGrpcAdapter | | 13 | `Program` | MMCA.ADC.UI | 1 | AppDelegate | -| 13 | `DatabaseInitializationExtensions` | MMCA.Common.API | 11 | ApplicationSettings, DataSource, DataSourceKey, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, ModuleLoader, PhysicalDataSource, TenancySettings, TenantDataSourceTarget, TenantDataSourceTargets | +| 13 | `CookieSessionRefresher` | MMCA.Common.API | 11 | AuthenticationResponse, CookieTokenReader, ICookieSessionRefresher, KeyedSemaphoreStripe, RefreshTokenRequest, SessionCookieEndpoints, SessionCookieJar, SessionCookieSettings, SessionRefreshOutcome, SessionRefreshStatus, SessionTokenResult | +| 13 | `SessionCookieAuthenticationHandler` | MMCA.Common.API | 1 | CookieTokenReader | +| 13 | `CookieSessionRefreshMiddlewareTests` | MMCA.Common.API.Tests | 5 | CookieSessionRefreshMiddleware, CookieSessionRefreshMiddlewareExtensions, ICookieSessionRefresher, NextDelegateSpy, SessionTokenResult | +| 13 | `CookieTokenReaderTests` | MMCA.Common.API.Tests | 2 | CookieTokenReader, SessionCookieEndpoints | | 13 | `AggregateConventionTests` | MMCA.Common.Architecture.Tests | 3 | AggregateConventionTestsBase, CommonArchitectureMap, IArchitectureMap | | 13 | `AiDependencyIsolationTests` | MMCA.Common.Architecture.Tests | 3 | AiDependencyIsolationTestsBase, CommonArchitectureMap, IArchitectureMap | | 13 | `CancellationTokenConventionTests` | MMCA.Common.Architecture.Tests | 3 | CancellationTokenConventionTestsBase, CommonArchitectureMap, IArchitectureMap | | 13 | `CascadeSoftDeleteConventionTests` | MMCA.Common.Architecture.Tests | 3 | CascadeSoftDeleteConventionTestsBase, CommonArchitectureMap, IArchitectureMap | -| 13 | `ClockReadTests` | MMCA.Common.Architecture.Tests | 11 | ArchitectureRules, AsyncClockReadingFixture, ClockReadTestsBase, CommonArchitectureMap, FixtureAssemblyMap, IArchitectureMap, InjectedClockFixture, LambdaClockReadingFixture, OffsetNowReadingFixture, TwoMemberClockFixture, UtcNowReadingFixture | +| 13 | `ClockReadTests` | MMCA.Common.Architecture.Tests | 12 | ArchitectureRules, AsyncClockReadingFixture, ClockReadTestsBase, CommonArchitectureMap, FixtureAssemblyMap, IArchitectureMap, InjectedClockFixture, LambdaClockReadingFixture, OffsetNowReadingFixture, TodayReadingFixture, TwoMemberClockFixture, UtcNowReadingFixture | | 13 | `ConcurrencyConventionTests` | MMCA.Common.Architecture.Tests | 3 | CommonArchitectureMap, ConcurrencyConventionTestsBase, IArchitectureMap | | 13 | `ContractImplementationTests` | MMCA.Common.Architecture.Tests | 3 | CommonArchitectureMap, ContractImplementationTestsBase, IArchitectureMap | +| 13 | `DeleteBehaviorConventionTests` | MMCA.Common.Architecture.Tests | 3 | ArchitectureRules, DeleteBehaviorConventionTestsBase, FrameworkModels | | 13 | `DomainPurityTests` | MMCA.Common.Architecture.Tests | 3 | CommonArchitectureMap, DomainPurityTestsBase, IArchitectureMap | | 13 | `DomainThrowFitnessTests` | MMCA.Common.Architecture.Tests | 9 | ArchitectureRules, ArgumentGuardFixture, CommonArchitectureMap, CustomExceptionThrowingFixture, FixtureAssemblyMap, IndirectThrowFixture, InvalidOperationThrowingFixture, NonThrowingFixture, RethrowingFixture | +| 13 | `EntityConventionTests` | MMCA.Common.Architecture.Tests | 3 | EntityConventionTestsBase, FrameworkModuleArchitectureMap, IArchitectureMap | | 13 | `EventScopeFitnessTests` | MMCA.Common.Architecture.Tests | 3 | ArchitectureRules, CommonArchitectureMap, FakeConsumerMap | | 13 | `EventUpcasterFitnessTests` | MMCA.Common.Architecture.Tests | 9 | ArchitectureRules, CommonArchitectureMap, FixtureBackwardsVersionUpcaster, FixtureCompliantV1ToV2Upcaster, FixtureCompliantV2ToV3Upcaster, FixtureContestedClaimUpcaster, FixtureContestedV1, FixtureRivalClaimUpcaster, UpcasterTestMap | | 13 | `EventVersioningConventionTests` | MMCA.Common.Architecture.Tests | 3 | CommonArchitectureMap, EventConventionTestsBase, IArchitectureMap | | 13 | `FakeConsumerMap` | MMCA.Common.Architecture.Tests | 5 | ArchitectureMapBase, BaseIntegrationEvent, EventScopeFitnessTests, Layer, LayerRef | | 13 | `FeatureFlagLifecycleRuleTests` | MMCA.Common.Architecture.Tests | 6 | ArchitectureRules, CommonArchitectureMap, FixtureBadFeatures, FixtureExpiredFeatures, FixtureGoodFeatures, FixtureMap | | 13 | `FeatureFlagLifecycleTests` | MMCA.Common.Architecture.Tests | 3 | CommonArchitectureMap, FeatureFlagLifecycleTestsBase, IArchitectureMap | -| 13 | `FrameworkModels` | MMCA.Common.Architecture.Tests | 12 | AuditSaveChangesInterceptor, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, IEntityDataSourceRegistry, NoDomainEventDispatcher, NoEntityDataSources, NoModuleAssemblies, NoOutboxSignal, PhysicalDataSource, SqliteDbContext, SQLServerDbContext | +| 13 | `FrameworkConstructorDependencyTests` | MMCA.Common.Architecture.Tests | 4 | CommonArchitectureMap, ConstructorDependencyCountTestsBase, IArchitectureMap, Layer | | 13 | `HandlerResultConventionTests` | MMCA.Common.Architecture.Tests | 3 | CommonArchitectureMap, HandlerResultConventionTestsBase, IArchitectureMap | | 13 | `IdempotencyConventionTests` | MMCA.Common.Architecture.Tests | 3 | CommonArchitectureMap, IArchitectureMap, IdempotencyConventionTestsBase | +| 13 | `ImmutabilityTests` | MMCA.Common.Architecture.Tests | 3 | FrameworkModuleArchitectureMap, IArchitectureMap, ImmutabilityTestsBase | | 13 | `IntegrationEventPayloadPurityRuleTests` | MMCA.Common.Architecture.Tests | 7 | ArchitectureRules, CommonArchitectureMap, FixtureCleanEvent, FixtureLeakedPayload, FixtureLeakingEvent, FrameworkProbeMap, ModuleProbeMap | | 13 | `IntegrationEventPayloadPurityTests` | MMCA.Common.Architecture.Tests | 3 | CommonArchitectureMap, IArchitectureMap, IntegrationEventPayloadPurityTestsBase | | 13 | `LayerDependencyTests` | MMCA.Common.Architecture.Tests | 3 | CommonArchitectureMap, IArchitectureMap, LayerDependencyTestsBase | | 13 | `LocalizedTextConventionTests` | MMCA.Common.Architecture.Tests | 3 | CommonArchitectureMap, IArchitectureMap, LocalizedTextConventionTestsBase | | 13 | `MicroserviceExtractionTests` | MMCA.Common.Architecture.Tests | 3 | CommonArchitectureMap, IArchitectureMap, MicroserviceExtractionTestsBase | | 13 | `NamespaceCycleTests` | MMCA.Common.Architecture.Tests | 3 | CommonArchitectureMap, IArchitectureMap, NamespaceCycleTestsBase | -| 13 | `PiiConventionTests` | MMCA.Common.Architecture.Tests | 3 | CommonArchitectureMap, IArchitectureMap, PiiConventionTestsBase | +| 13 | `PiiConventionTests` | MMCA.Common.Architecture.Tests | 4 | CommonArchitectureMap, IArchitectureMap, Layer, PiiConventionTestsBase | +| 13 | `Probe` | MMCA.Common.Architecture.Tests | 3 | CommonArchitectureMap, DataResidencyTestsBase, IArchitectureMap | | 13 | `QueryHandlerReadRepositoryTests` | MMCA.Common.Architecture.Tests | 9 | ArchitectureRules, CommonArchitectureMap, FixtureAssemblyMap, FixtureAssemblyMap, IArchitectureMap, QueryHandlerReadRepositoryTestsBase, ReadRepositoryQueryHandlerFixture, WriteRepositoryCommandHandlerFixture, WriteRepositoryQueryHandlerFixture | | 13 | `RawQueryableConventionTests` | MMCA.Common.Architecture.Tests | 4 | ArchitectureMapBase, CommonArchitectureMap, IArchitectureMap, RawQueryableConventionTestsBase | | 13 | `RawSqlConventionTests` | MMCA.Common.Architecture.Tests | 4 | ArchitectureMapBase, CommonArchitectureMap, IArchitectureMap, RawSqlConventionTestsBase | @@ -4695,68 +4737,94 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 13 | `SoftDeleteEnforcementTests` | MMCA.Common.Architecture.Tests | 3 | CommonArchitectureMap, IArchitectureMap, SoftDeleteEnforcementTestsBase | | 13 | `StateManagementConventionTests` | MMCA.Common.Architecture.Tests | 3 | CommonArchitectureMap, IArchitectureMap, StateManagementConventionTestsBase | | 13 | `StronglyTypedIdConventionTests` | MMCA.Common.Architecture.Tests | 3 | CommonArchitectureMap, IArchitectureMap, StronglyTypedIdTestsBase | +| 13 | `TenantEntityConventionTests` | MMCA.Common.Architecture.Tests | 3 | CommonArchitectureMap, ITenantEntity, Layer | | 13 | `UIArchitectureConventionTests` | MMCA.Common.Architecture.Tests | 3 | CommonArchitectureMap, IArchitectureMap, UIArchitectureConventionTestsBase | | 13 | `UpcasterTestMap` | MMCA.Common.Architecture.Tests | 4 | ArchitectureMapBase, EventUpcasterFitnessTests, Layer, LayerRef | | 13 | `SerilogHostExtensionsTests` | MMCA.Common.Aspire.Tests | 3 | Extensions, SerilogHostExtensions, StubHostEnvironment | -| 13 | `AuditTrailCleanupJob` | MMCA.Common.Infrastructure | 8 | AuditTrailEntry, AuditTrailSettings, IDbContextFactory, IEntityDataSourceRegistry, IScheduledJob, TenancySettings, TenantDataSourceTarget, TenantDataSourceTargets | | 13 | `AuditTrailReader` | MMCA.Common.Infrastructure | 7 | AuditTrailEntry, AuditTrailEntryDTO, AuditTrailSettings, DataSourceKey, IAuditTrailReader, IDataSourceResolver, IDbContextFactory | | 13 | `BrokerEventBus` | MMCA.Common.Infrastructure | 7 | IDataSourceResolver, IDbContextFactory, IEventBus, IIntegrationEvent, IOutboxSignal, OutboxMessage, OutboxSettings | -| 13 | `DbContextFactory` | MMCA.Common.Infrastructure | 22 | AmbientOrigin, ApplicationDbContext, CosmosDbContext, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, Entry, ICorrelationContext, ICurrentUserService, IDataSourceResolver, IDbContextFactory, IdentityInsertGroup, IEntityDataSourceRegistry, IPhysicalDbContextFactory, ITenantContext, OutboxOrigin, PhysicalDataSource, Result, SQLServerDbContext, TenancySettings …(+2) | -| 13 | `DesignTimeDbContextHelper` | MMCA.Common.Infrastructure | 28 | AuditSaveChangesInterceptor, AuditTrailSaveChangesInterceptor, AuditTrailSettings, DataSource, DataSourceKey, DataSourceResolver, DataSourcesSettings, DesignTimeDbContextOptions, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, ExplicitAssemblyProvider, IDataSourceResolver, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IOutboxSignal, NullDomainEventDispatcher, OutboxSignal, PermissionGrantModelGate, PermissionGrantSettings …(+8) | +| 13 | `DataSourceResolver` | MMCA.Common.Infrastructure | 10 | ConnectionStringSettings, DataSource, DataSourceEngines, DataSourceEntrySettings, DataSourceKey, DataSourcesSettings, DefaultSeed, IDataSourceResolver, MigrationPolicy, PhysicalDataSource | | 13 | `EfInboxStore` | MMCA.Common.Infrastructure | 6 | ApplicationDbContext, IDataSourceResolver, IDbContextFactory, IInboxStore, InboxMessage, OutboxSettings | -| 13 | `EFPermissionGrantStore` | MMCA.Common.Infrastructure | 10 | ApplicationDbContext, DataSource, DataSourceKey, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, IPermissionGrantStore, PermissionGrant, PermissionGrantSettings, Result | +| 13 | `EFPermissionGrantStore` | MMCA.Common.Infrastructure | 11 | ApplicationDbContext, DataSource, DataSourceKey, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, IPermissionGrantStore, IUniqueConstraintViolationDetector, PermissionGrant, PermissionGrantSettings, Result | | 13 | `EFRawSqlQueryExecutor` | MMCA.Common.Infrastructure | 5 | DataSource, DataSourceKey, IDataSourceResolver, IDbContextFactory, IRawSqlQueryExecutor | | 13 | `EFRefreshSessionStore` | MMCA.Common.Infrastructure | 10 | ApplicationDbContext, DataSource, DataSourceKey, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, IRefreshSessionStore, RefreshSession, RefreshSessionSettings, Sessions | +| 13 | `EFRepository` | MMCA.Common.Infrastructure | 9 | AuditableAggregateRootEntity, AuditableBaseEntity, EFReadRepository, IAuditableEntity, ICurrentUserService, IRepository, IRowVersioned, IUpdatePropertySetter, UpdatePropertySetterBuilder | +| 13 | `EntityDataSourceRegistry` | MMCA.Common.Infrastructure | 10 | DataSource, DataSourceKey, IDataSourceResolver, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IEntityTypeConfigurationBase, NamespaceConventions, Snapshot, UseDatabaseAttribute, UseDataSourceAttribute | +| 13 | `EntityTypeConfigurationCosmos` | MMCA.Common.Infrastructure | 3 | AuditableBaseEntity, DataSource, EntityTypeConfiguration | +| 13 | `EntityTypeConfigurationPostgreSQL` | MMCA.Common.Infrastructure | 3 | AuditableBaseEntity, DataSource, EntityTypeConfiguration | +| 13 | `EntityTypeConfigurationSqlite` | MMCA.Common.Infrastructure | 3 | AuditableBaseEntity, DataSource, EntityTypeConfiguration | +| 13 | `EntityTypeConfigurationSQLServer` | MMCA.Common.Infrastructure | 3 | AuditableBaseEntity, DataSource, EntityTypeConfiguration | | 13 | `InProcessEventBus` | MMCA.Common.Infrastructure | 9 | IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IEventBus, IIntegrationEvent, MessageBusSettings, OutboxFinalizer, OutboxMessage, OutboxSettings | -| 13 | `InternalCommandAdministration` | MMCA.Common.Infrastructure | 14 | ApplicationDbContext, Error, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, IInternalCommandAdministration, IInternalCommandSignal, InternalCommandDeadLetter, InternalCommandMessage, InternalCommandsSettings, Result, TenancySettings, TenantDataSourceTarget, TenantDataSourceTargets | -| 13 | `InternalCommandCleanupService` | MMCA.Common.Infrastructure | 10 | ApplicationDbContext, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, InternalCommandMessage, InternalCommandsSettings, PeriodicBackgroundService, TenancySettings, TenantDataSourceTarget, TenantDataSourceTargets | -| 13 | `InternalCommandProcessor` | MMCA.Common.Infrastructure | 19 | Activity, AmbientOrigin, ApplicationDbContext, ColumnWidth, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, IInternalCommand, IInternalCommandSignal, InternalCommandCycleResult, InternalCommandDispatcher, InternalCommandMessage, InternalCommandMetrics, InternalCommandsSettings, PollingLoop, Result, TenancySettings, TenantDataSourceTarget, TenantDataSourceTargets | -| 13 | `InternalCommandScheduler` | MMCA.Common.Infrastructure | 15 | Activity, AmbientOrigin, Error, ICorrelationContext, ICurrentUserService, IDataSourceResolver, IDbContextFactory, IInternalCommand, IInternalCommandScheduler, IInternalCommandSignal, InternalCommandMessage, InternalCommandOrigin, InternalCommandsSettings, ITenantContext, Result | -| 13 | `OutboxAdministration` | MMCA.Common.Infrastructure | 14 | ApplicationDbContext, Error, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, IOutboxAdministration, IOutboxSignal, OutboxDeadLetter, OutboxMessage, OutboxSettings, Result, TenancySettings, TenantDataSourceTarget, TenantDataSourceTargets | -| 13 | `OutboxCleanupService` | MMCA.Common.Infrastructure | 12 | ApplicationDbContext, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, InboxMessage, MessageBusSettings, OutboxMessage, OutboxSettings, PeriodicBackgroundService, TenancySettings, TenantDataSourceTarget, TenantDataSourceTargets | -| 13 | `OutboxProcessor` | MMCA.Common.Infrastructure | 21 | Activity, ApplicationDbContext, BrokerMetrics, BrokerResilienceDefaults, DataSourceKey, Event, IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IEntityDataSourceRegistry, IIntegrationEvent, IMessageBus, IOutboxSignal, OutboxCycleResult, OutboxMessage, OutboxMetrics, OutboxSettings, PollingLoop, TenancySettings, TenantDataSourceTarget …(+1) | -| 13 | `PhysicalDbContextFactory` | MMCA.Common.Infrastructure | 11 | ApplicationDbContext, CosmosDbContext, DataSource, DataSourceKey, IDataSourceResolver, IEntityConfigurationAssemblyProvider, IPhysicalDbContextFactory, PhysicalDataSource, PostgreSQLDbContext, SqliteDbContext, SQLServerDbContext | +| 13 | `InternalCommandScheduler` | MMCA.Common.Infrastructure | 11 | AmbientOrigin, Error, IDataSourceResolver, IDbContextFactory, IInternalCommand, IInternalCommandScheduler, IInternalCommandSignal, InternalCommandMessage, InternalCommandOriginCapture, InternalCommandsSettings, Result | +| 13 | `PhysicalDbContextFactory` | MMCA.Common.Infrastructure | 7 | ApplicationDbContext, DataSourceEngines, DataSourceKey, IDataSourceResolver, IEntityConfigurationAssemblyProvider, IPhysicalDbContextFactory, PhysicalDataSource | | 13 | `RefreshSessionCleanupService` | MMCA.Common.Infrastructure | 8 | DataSource, DataSourceKey, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, PeriodicBackgroundService, RefreshSession, RefreshSessionSettings | | 13 | `ScheduledJobRunner` | MMCA.Common.Infrastructure | 10 | ApplicationDbContext, ColumnWidth, DataSourceKey, IDataSourceResolver, IDbContextFactory, IScheduledJob, JobClaim, ScheduledJobEntry, SchedulerMetrics, SchedulerSettings | +| 13 | `TenancySettingsValidator` | MMCA.Common.Infrastructure | 8 | DataSource, DataSourceEngines, DataSourceKey, IDataSourceResolver, TenancySettings, TenantDataSourceOverrideSettings, TenantEntrySettings, TenantResolutionStrategy | | 13 | `UnitOfWork` | MMCA.Common.Infrastructure | 8 | AuditableAggregateRootEntity, AuditableBaseEntity, IDataSourceService, IDbContextFactory, IReadRepository, IRepository, IRepositoryFactory, IUnitOfWork | -| 13 | `ApplicationDbContextTenantFilterTests` | MMCA.Common.Infrastructure.Tests | 8 | ApplicationDbContext, EFReadRepository, IAuditableEntity, PlainThing, TenantDetail, TenantOnlyThing, TenantTestContext, TenantThing | -| 13 | `ApplicationDbContextTests` | MMCA.Common.Infrastructure.Tests | 4 | ApplicationDbContext, DataSource, TestApplicationDbContext, TestEntity | -| 13 | `AuditSaveChangesInterceptorTests` | MMCA.Common.Infrastructure.Tests | 4 | AuditSaveChangesInterceptor, FakeTimeProvider, TestAuditDbContext, TestAuditEntity | +| 13 | `AdminTestContext` | MMCA.Common.Infrastructure.Tests | 10 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NoAssemblies, OutboxMessage, TestPhysicalDataSources | | 13 | `AuditTrailModelGateTests` | MMCA.Common.Infrastructure.Tests | 3 | AuditTrailEntry, DataSourceKey, GateTestContext | -| 13 | `AuditTrailSaveChangesInterceptorTests` | MMCA.Common.Infrastructure.Tests | 13 | AuditedThing, AuditTrailEntry, AuditTrailSaveChangesInterceptor, AuditTrailTestContext, AuditTrailTestHarness, CompositeKeyThing, Email, FakeTimeProvider, InboxMessage, OutboxMessage, PiiRedactor, PlainThing, ScheduledJobEntry | +| 13 | `AuditTrailTestContext` | MMCA.Common.Infrastructure.Tests | 17 | ApplicationDbContext, AuditedThing, AuditSaveChangesInterceptor, AuditTrailEntry, AuditTrailSaveChangesInterceptor, CompositeKeyThing, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FailingSaveInterceptor, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, OverridingPiiThing, PlainThing, TestPhysicalDataSources | +| 13 | `CleanupTestContext` | MMCA.Common.Infrastructure.Tests | 13 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, InboxMessage, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, NullAssemblyProvider, OutboxMessage, TestPhysicalDataSources | +| 13 | `CommitFailingDbContext` | MMCA.Common.Infrastructure.Tests | 13 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FailingDatabaseFacade, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, OutboxMessage, TestAggregate, TestPhysicalDataSources | | 13 | `CrossDataSourceDegradeConventionTests` | MMCA.Common.Infrastructure.Tests | 14 | AuditSaveChangesInterceptor, DataSource, DataSourceKey, DataSourceModelCacheKeyFactory, DegradeCustomer, DegradeOrder, DegradeTestContext, DomainEventSaveChangesInterceptor, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, MapRegistry, OutboxSignal, PhysicalDataSource | +| 13 | `DataSourceServiceAdditionalTests` | MMCA.Common.Infrastructure.Tests | 7 | DataSource, DataSourceKey, DataSourceService, FakeEntity, FakeEntity, IEntityDataSourceRegistry, UnregisteredEntity | +| 13 | `DataSourceServiceTests` | MMCA.Common.Infrastructure.Tests | 6 | DataSource, DataSourceKey, DataSourceService, FakeEntity, FakeEntity, IEntityDataSourceRegistry | +| 13 | `DefaultDataSourceResolver` | MMCA.Common.Infrastructure.Tests | 4 | DataSource, DataSourceKey, IDataSourceResolver, PhysicalDataSource | +| 13 | `DeleteBehaviorTestDbContext` | MMCA.Common.Infrastructure.Tests | 14 | ApplicationDbContext, AuditSaveChangesInterceptor, CascadingChild, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NoModuleAssemblies, NoModuleAssemblies, OptionalChild, Parent, RequiredChild, TestPhysicalDataSources | | 13 | `DependencyInjectionAdditionalTests` | MMCA.Common.Infrastructure.Tests | 6 | EntityConfigurationOptions, IDataSourceService, IDbContextFactory, IQueryableExecutor, IRepositoryFactory, IUnitOfWork | -| 13 | `DomainEventCaptureExclusionTests` | MMCA.Common.Infrastructure.Tests | 7 | DomainEventSaveChangesInterceptor, ExclusionAggregate, ExclusionEvent, ExclusionTestDbContext, IDomainEvent, IDomainEventDispatcher, IOutboxSignal | -| 13 | `DomainEventSaveChangesInterceptorOutboxRoutingTests` | MMCA.Common.Infrastructure.Tests | 11 | DomainEventSaveChangesInterceptor, FakeTimeProvider, IDomainEvent, IDomainEventDispatcher, IOutboxSignal, OutboxMessage, OutboxRoutingTestDbContext, TestAggregate, TestIntegrationEvent, TestLocalEvent, TestOrderedEvent | -| 13 | `DomainEventSaveChangesInterceptorTests` | MMCA.Common.Infrastructure.Tests | 7 | DomainEventSaveChangesInterceptor, IDomainEvent, IDomainEventDispatcher, IOutboxSignal, TestAggregate, TestDomainEvent, TestDomainEventDbContext | -| 13 | `EFReadRepositoryGetByIdFilterTests` | MMCA.Common.Infrastructure.Tests | 4 | EFReadRepository, SoftDeletableTestEntity, SoftDeleteTestDbContext, SoftDeleteTestDbContext | +| 13 | `DetectionTestDbContext` | MMCA.Common.Infrastructure.Tests | 12 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources, Widget, Widget | | 13 | `EFReadRepositoryKeysetPagingTests` | MMCA.Common.Infrastructure.Tests | 7 | BbbSpecification, EFReadRepository, ErrorType, KeysetCursor, KeysetPageRequest, SpecificationTestDbContext, SpecTestEntity | | 13 | `EFReadRepositoryLookupProjectionTests` | MMCA.Common.Infrastructure.Tests | 4 | EFReadRepository, Email, SpecificationTestDbContext, SpecTestEntity | | 13 | `EFReadRepositoryLookupSecurityTests` | MMCA.Common.Infrastructure.Tests | 4 | EFReadRepository, EntityQueryPipeline, SpecificationTestDbContext, SpecTestEntity | | 13 | `EFReadRepositoryProjectedFilterTests` | MMCA.Common.Infrastructure.Tests | 3 | EFReadRepository, NamedSoftDeleteTestDbContext, ProjectedTestEntity | | 13 | `EFReadRepositoryReadSurfaceTests` | MMCA.Common.Infrastructure.Tests | 7 | EFReadRepository, HighestRankedBetaSpecification, LowestRankedBetaSpecification, NoMatchSpecification, SpecificationTestDbContext, SpecTestChild, SpecTestEntity | | 13 | `EFReadRepositorySpecificationTests` | MMCA.Common.Infrastructure.Tests | 16 | AllSpecification, BetaSpecification, BetaSpecification, DeletedByNameSpecification, EFReadRepository, HighRankSpecification, IncludingSoftDeletedSpecification, IncludingSpecification, IncludingSpecification, ISpecification, NoMatchSpecification, SpecificationTestDbContext, SpecTestChild, SpecTestEntity, TopTwoByRankSpecification, TrackedSpecification | +| 13 | `ExclusionTestDbContext` | MMCA.Common.Infrastructure.Tests | 9 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, ExclusionAggregate, IEntityDataSourceRegistry, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources | +| 13 | `FailingDatabaseFacade` | MMCA.Common.Infrastructure.Tests | 2 | AlwaysRetryExecutionStrategy, CommitFailingDbContext | +| 13 | `FailingSaveInterceptor` | MMCA.Common.Infrastructure.Tests | 1 | AuditTrailTestContext | +| 13 | `FailingSaveInterceptor` | MMCA.Common.Infrastructure.Tests | 1 | OutboxRoutingTestDbContext | +| 13 | `FixedEngineResolver` | MMCA.Common.Infrastructure.Tests | 4 | DataSource, DataSourceKey, IDataSourceResolver, PhysicalDataSource | +| 13 | `GrantTestContext` | MMCA.Common.Infrastructure.Tests | 10 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources | +| 13 | `InboxTestDbContext` | MMCA.Common.Infrastructure.Tests | 4 | ApplicationDbContext, IEntityConfigurationAssemblyProvider, InboxMessage, TestPhysicalDataSources | | 13 | `InternalCommandModelTests` | MMCA.Common.Infrastructure.Tests | 6 | ApplicationDbContext, DataSource, DataSourceKey, InternalCommandMessage, ModelTestContext, OutboxMessage | +| 13 | `InternalCommandTestContext` | MMCA.Common.Infrastructure.Tests | 11 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, InternalCommandMessage, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources | +| 13 | `KeysetQueryBuilderNullOrderingTests` | MMCA.Common.Infrastructure.Tests | 3 | KeysetQueryBuilder, SpecificationTestDbContext, SpecTestEntity | | 13 | `KeysetQueryBuilderSqlTests` | MMCA.Common.Infrastructure.Tests | 3 | KeysetQueryBuilder, SpecificationTestDbContext, SpecTestEntity | +| 13 | `MidSaveContextCreatingDbContext` | MMCA.Common.Infrastructure.Tests | 10 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IOutboxSignal, ReentrantSaveInterceptor, TestPhysicalDataSources | | 13 | `Mocks` | MMCA.Common.Infrastructure.Tests | 4 | IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IOutboxSignal | | 13 | `Mocks` | MMCA.Common.Infrastructure.Tests | 3 | IDataSourceService, IDbContextFactory, IRepositoryFactory | +| 13 | `NoSessionTableContext` | MMCA.Common.Infrastructure.Tests | 6 | ApplicationDbContext, NullAssemblyProvider, NullAssemblyProvider, NullAssemblyProvider, OutboxMessage, TestPhysicalDataSources | +| 13 | `OrderingTestContext` | MMCA.Common.Infrastructure.Tests | 10 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NoAssemblies, OutboxMessage, TestPhysicalDataSources | +| 13 | `OutboxRoutingTestDbContext` | MMCA.Common.Infrastructure.Tests | 11 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FailingSaveInterceptor, IEntityDataSourceRegistry, NullAssemblyProvider, NullAssemblyProvider, OutboxMessage, TestAggregate, TestPhysicalDataSources | +| 13 | `OutboxTestDbContext` | MMCA.Common.Infrastructure.Tests | 4 | ApplicationDbContext, IEntityConfigurationAssemblyProvider, OutboxMessage, TestPhysicalDataSources | | 13 | `PermissionGrantModelGateTests` | MMCA.Common.Infrastructure.Tests | 19 | AuditSaveChangesInterceptor, CaseMappingShape, CaseNamedSource, CaseNoOptIn, CaseOptedInDefaultSource, CaseSettingsOnly, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, GateContext, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IOutboxSignal, PermissionGrant, PermissionGrantModelBuilderExtensions, PermissionGrantModelGate, PermissionGrantSettings, PhysicalDataSource | -| 13 | `QueryParameterizationTests` | MMCA.Common.Infrastructure.Tests | 3 | QueryFieldService, QueryFilterService, QueryShapeTestDbContext | +| 13 | `PortableThingConfiguration` | MMCA.Common.Infrastructure.Tests | 3 | DataSource, EntityTypeConfiguration, PortableThing | +| 13 | `QueryShapeTestDbContext` | MMCA.Common.Infrastructure.Tests | 11 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, Product, TestPhysicalDataSources | +| 13 | `ReentrantSaveInterceptor` | MMCA.Common.Infrastructure.Tests | 1 | MidSaveContextCreatingDbContext | | 13 | `RefreshSessionModelGateTests` | MMCA.Common.Infrastructure.Tests | 18 | AuditSaveChangesInterceptor, CaseDefaultSettings, CaseEnabledDefaultSource, CaseMappingShape, CaseNamedSource, CaseNoSettings, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, GateContext, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IOutboxSignal, PhysicalDataSource, RefreshSession, RefreshSessionModelBuilderExtensions, RefreshSessionSettings | -| 13 | `RestrictDeleteByDefaultConventionTests` | MMCA.Common.Infrastructure.Tests | 6 | CascadingChild, DeleteBehaviorTestDbContext, OptionalChild, Parent, RequiredChild, RestrictDeleteByDefaultConvention | -| 13 | `SaveChangeDetectionTests` | MMCA.Common.Infrastructure.Tests | 3 | DetectionTestDbContext, Widget, Widget | +| 13 | `RestoreTestDbContext` | MMCA.Common.Infrastructure.Tests | 4 | ApplicationDbContext, IEntityConfigurationAssemblyProvider, OutboxMessage, TestPhysicalDataSources | | 13 | `SchedulerModelGateTests` | MMCA.Common.Infrastructure.Tests | 3 | DataSourceKey, GateTestContext, ScheduledJobEntry | +| 13 | `SchedulerTestContext` | MMCA.Common.Infrastructure.Tests | 10 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, ScheduledJobEntry, TestPhysicalDataSources | +| 13 | `SessionCleanupTestContext` | MMCA.Common.Infrastructure.Tests | 5 | ApplicationDbContext, NullAssemblyProvider, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources | | 13 | `SingleContextFactory` | MMCA.Common.Infrastructure.Tests | 3 | ApplicationDbContext, DataSourceKey, IDbContextFactory | -| 13 | `SoftDeleteQueryFilterTests` | MMCA.Common.Infrastructure.Tests | 2 | SoftDeletableEntity, SoftDeleteTestDbContext | -| 13 | `SoftDeleteUniqueIndexConventionTests` | MMCA.Common.Infrastructure.Tests | 5 | AlreadySoftDeleteFilteredEntity, BracketQuotedFilterEntity, FilteredIndexEntity, UniqueIndexTestDbContext, UniqueNamedEntity | +| 13 | `SoftDeleteTestDbContext` | MMCA.Common.Infrastructure.Tests | 11 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, SoftDeletableEntity, SoftDeleteTestDbContext, TestPhysicalDataSources | | 13 | `SpecificationEvaluatorTests` | MMCA.Common.Infrastructure.Tests | 11 | BetaSpecification, IncludingSpecification, OrderedSpecification, PagedSpecification, QueryTagScope, RankDescendingSpecification, SpecificationEvaluator, SpecificationTestDbContext, SpecTestChild, SpecTestEntity, UnorderedQuerySpecification | | 13 | `SQLServerDbContextTests` | MMCA.Common.Infrastructure.Tests | 11 | AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyAssemblyProvider, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, OutboxSignal, PersistenceSettings, SQLServerDbContext, TestPhysicalDataSources | -| 13 | `TenantSaveChangesInterceptorTests` | MMCA.Common.Infrastructure.Tests | 5 | CrossTenantWriteException, PlainThing, TenantTestContext, TenantThing, TrailedTenantThing | +| 13 | `StampTestDbContext` | MMCA.Common.Infrastructure.Tests | 11 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, StampedEntity, TestPhysicalDataSources | +| 13 | `TenantTestContext` | MMCA.Common.Infrastructure.Tests | 17 | ApplicationDbContext, AuditSaveChangesInterceptor, AuditTrailEntry, AuditTrailSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, PlainThing, TenantOnlyThing, TenantSaveChangesInterceptor, TenantThing, TestPhysicalDataSources, TrailedTenantThing | +| 13 | `TestApplicationDbContext` | MMCA.Common.Infrastructure.Tests | 10 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IOutboxSignal, TestEntity, TestPhysicalDataSources | +| 13 | `TestAuditDbContext` | MMCA.Common.Infrastructure.Tests | 11 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, TestAuditEntity, TestPhysicalDataSources | +| 13 | `TestDomainEventDbContext` | MMCA.Common.Infrastructure.Tests | 9 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IEntityDataSourceRegistry, NullAssemblyProvider, NullAssemblyProvider, TestAggregate, TestPhysicalDataSources | +| 13 | `TestNonOutboxContext` | MMCA.Common.Infrastructure.Tests | 9 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, TestPhysicalDataSources | +| 13 | `TestOutboxContext` | MMCA.Common.Infrastructure.Tests | 10 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, OutboxMessage, TestPhysicalDataSources | +| 13 | `TransactionTestDbContext` | MMCA.Common.Infrastructure.Tests | 14 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, InternalCommandMessage, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, NullAssemblyProvider, OutboxMessage, TestAggregate, TestPhysicalDataSources | +| 13 | `UniqueIndexTestDbContext` | MMCA.Common.Infrastructure.Tests | 15 | AlreadySoftDeleteFilteredEntity, ApplicationDbContext, AuditSaveChangesInterceptor, BracketQuotedFilterEntity, DataSource, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FilteredIndexEntity, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources, UniqueNamedEntity | +| 13 | `WidgetContext` | MMCA.Common.Infrastructure.Tests | 10 | ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NoModuleAssemblies, TestPhysicalDataSources, Widget | | 13 | `WrappedIdBareSqliteContext` | MMCA.Common.Infrastructure.Tests | 4 | ApplicationDbContext, NoAssemblies, TestPhysicalDataSources, WrappedIdContextServices | | 13 | `WrappedIdPostgresContext` | MMCA.Common.Infrastructure.Tests | 4 | ApplicationDbContext, NoAssemblies, TestPhysicalDataSources, WrappedIdContextServices | | 13 | `WrappedIdSqliteContext` | MMCA.Common.Infrastructure.Tests | 6 | ApplicationDbContext, NoAssemblies, TestPhysicalDataSources, WrappedIdContextServices, WrappedOrder, WrappedSpeaker | | 13 | `WrappedIdSqlServerContext` | MMCA.Common.Infrastructure.Tests | 4 | ApplicationDbContext, NoAssemblies, TestPhysicalDataSources, WrappedIdContextServices | -| 13 | `CreateMigrationProofTable` | MMCA.Common.Infrastructure.Tests.MigrationsFixture | 1 | SqliteDbContext | +| 13 | `LoadStack` | MMCA.Common.LoadTests | 5 | ApplicationDbContext, DataSource, DataSourceKey, IDataSourceResolver, IDbContextFactory | | 13 | `ComponentsPageE2ETests` | MMCA.Common.UI.E2E.Tests | 4 | AxeOptions, GalleryAxeTestBase, GalleryHostFixture, PlaywrightFixture | | 13 | `DarkModeE2ETests` | MMCA.Common.UI.E2E.Tests | 4 | AxeOptions, GalleryAxeTestBase, GalleryHostFixture, PlaywrightFixture | | 13 | `ForgotPasswordPageE2ETests` | MMCA.Common.UI.E2E.Tests | 5 | AxeOptions, ForgotPasswordPage, GalleryAxeTestBase, GalleryHostFixture, PlaywrightFixture | @@ -4771,94 +4839,165 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 13 | `SessionsPageE2ETests` | MMCA.Common.UI.E2E.Tests | 4 | AxeOptions, GalleryAxeTestBase, GalleryHostFixture, PlaywrightFixture | | 13 | `ShellPagesE2ETests` | MMCA.Common.UI.E2E.Tests | 4 | AxeOptions, GalleryAxeTestBase, GalleryHostFixture, PlaywrightFixture | | 13 | `StickySidebarE2ETests` | MMCA.Common.UI.E2E.Tests | 3 | GalleryAxeTestBase, GalleryHostFixture, PlaywrightFixture | -| 13 | `WebVitalsE2ETests` | MMCA.Common.UI.E2E.Tests | 5 | GalleryAxeTestBase, GalleryHostFixture, PlaywrightFixture, WebVitalsBudget, WebVitalsSample | -| 14 | `ServiceModels` | MMCA.ADC.Architecture.Tests | 3 | DataSourceEntrySettings, DefaultEntityConfigurationAssemblyProvider, DesignTimeDbContextHelper | +| 13 | `WebVitalsE2ETests` | MMCA.Common.UI.E2E.Tests | 6 | GalleryAxeTestBase, GalleryHostFixture, InpProbe, PlaywrightFixture, WebVitalsBudget, WebVitalsSample | +| 13 | `SameOriginApiProxyEndpoint` | MMCA.Common.UI.Web | 11 | ICookieSessionRefresher, ISessionCookieStore, ProxyResponseMode, SameOriginApiProxySettings, SameOriginProxyHeaders, SameOriginProxyInvoker, SameOriginProxyTransformer, SessionClaimsToken, SessionCookieEndpoints, SessionRefreshOutcome, SessionRefreshStatus | +| 13 | `ServerTokenStorageService` | MMCA.Common.UI.Web | 5 | CookieTokenReader, ISessionCookieSync, ITokenRefresher, ITokenStorageService, JwtTokenInfo | +| 13 | `SameOriginApiProxyAuthFlowTests` | MMCA.Common.UI.Web.Tests | 7 | FakeGateway, Jwt, LoginRequest, ProxyHost, SessionCookieEndpoints, SessionHandoffProtector, SessionTokenResponse | +| 13 | `SameOriginApiProxyCsrfTests` | MMCA.Common.UI.Web.Tests | 3 | FakeGateway, Jwt, ProxyHost | +| 13 | `SameOriginApiProxyHubTests` | MMCA.Common.UI.Web.Tests | 5 | Address, FakeGateway, Jwt, ProxyHost, SessionCookieEndpoints | +| 13 | `SameOriginApiProxyOptInTests` | MMCA.Common.UI.Web.Tests | 10 | FakeGateway, HandoffSessionCookieSync, HandoffTokenRefresher, ISessionCookieSync, ITokenRefresher, JsFetchSessionCookieSync, ProxyHost, SameOriginApiProxySettings, SameOriginProxyTokenRefresher, SessionCookieSettings | +| 13 | `SameOriginApiProxyOriginTests` | MMCA.Common.UI.Web.Tests | 3 | FakeGateway, Jwt, ProxyHost | +| 13 | `SameOriginApiProxyRefreshOutcomeTests` | MMCA.Common.UI.Web.Tests | 3 | FakeGateway, Jwt, ProxyHost | +| 13 | `SameOriginApiProxyTokenTests` | MMCA.Common.UI.Web.Tests | 4 | FakeGateway, Jwt, ProxyHost, SessionCookieEndpoints | | 14 | `CreateQuestionHandler` | MMCA.ADC.Conference.Application | 11 | CreateEntityHandlerBase, Error, IEntityRequestMapper, IUnitOfWork, Question, QuestionCreateRequest, QuestionDTO, QuestionDTOMapper, QuestionInvariants, Result, UnitOfWork | | 14 | `CreateSessionHandler` | MMCA.ADC.Conference.Application | 14 | CreateEntityHandlerBase, Error, Event, IEntityRequestMapper, IUniqueConstraintViolationDetector, IUnitOfWork, Result, Session, SessionCreateRequest, SessionDTO, SessionDTOMapper, SessionInvariants, SessionRoomScheduling, UnitOfWork | | 14 | `DeleteSessionHandler` | MMCA.ADC.Conference.Application | 9 | DeleteEntityCommand, DeleteEntityHandler, DeleteSessionAssetBlobInternalCommand, IInternalCommandScheduler, IUnitOfWork, Result, Session, SessionAsset, UnitOfWork | | 14 | `LinkUserToSpeakerHandler` | MMCA.ADC.Conference.Application | 8 | Error, IUnitOfWork, LinkUserToSpeakerCommand, MutateEntityHandlerBase, Result, Speaker, SpeakerLinkedToUser, UnitOfWork | | 14 | `RefreshFromSessionizeHandler` | MMCA.ADC.Conference.Application | 20 | CategorySyncStrategy, Error, Event, ICommandHandler, IConcurrencyConflictDetector, ICurrentUserService, ISessionizeService, ISessionizeSyncStrategy, IUnitOfWork, QuestionSyncStrategy, RefreshFromSessionizeCommand, RefreshFromSessionizeResultDTO, Result, RoomSyncStrategy, SessionizeResponse, SessionizeSyncContext, SessionizeSyncResult, SessionSyncStrategy, SpeakerSyncStrategy, UnitOfWork | +| 14 | `UpdateEventQuestionAnswerHandler` | MMCA.ADC.Conference.Application | 12 | Error, Event, EventQuestionAnswer, EventQuestionAnswerRules, ICurrentUserService, IUnitOfWork, MutateEntityHandlerBase, Question, Result, RoleNames, UnitOfWork, UpdateEventQuestionAnswerCommand | | 14 | `UpdateQuestionHandler` | MMCA.ADC.Conference.Application | 12 | Error, EventQuestionAnswer, IUnitOfWork, MutateEntityHandlerBase, Question, QuestionDTO, QuestionDTOMapper, Result, SessionQuestionAnswer, SpeakerQuestionAnswer, UnitOfWork, UpdateQuestionCommand | +| 14 | `UpdateSessionQuestionAnswerHandler` | MMCA.ADC.Conference.Application | 13 | Error, Event, ICurrentUserService, IUnitOfWork, MutateEntityHandlerBase, Question, Result, RoleNames, Session, SessionQuestionAnswer, SessionQuestionAnswerRules, UnitOfWork, UpdateSessionQuestionAnswerCommand | | 14 | `CategorySyncStrategyTests` | MMCA.ADC.Conference.Application.Tests | 10 | Category, CategorySyncStrategy, Event, IRepository, IUnitOfWork, SessionizeCategory, SessionizeCategoryItem, SessionizeResponse, SessionizeSyncContext, UnitOfWork | | 14 | `QuestionSyncStrategyTests` | MMCA.ADC.Conference.Application.Tests | 11 | Event, IRepository, IUnitOfWork, Question, QuestionSyncStrategy, SessionizeQuestion, SessionizeQuestionAnswer, SessionizeResponse, SessionizeSpeaker, SessionizeSyncContext, UnitOfWork | | 14 | `RoomSyncStrategyTests` | MMCA.ADC.Conference.Application.Tests | 10 | Event, EventInvariants, IReadRepository, IUnitOfWork, Room, RoomSyncStrategy, SessionizeResponse, SessionizeRoom, SessionizeSyncContext, UnitOfWork | | 14 | `SessionSyncStrategyTests` | MMCA.ADC.Conference.Application.Tests | 11 | Event, IRepository, IUnitOfWork, Session, SessionInvariants, SessionizeResponse, SessionizeSession, SessionizeSyncContext, Sessions, SessionSyncStrategy, UnitOfWork | -| 14 | `SpeakerSyncStrategyTests` | MMCA.ADC.Conference.Application.Tests | 11 | Event, EventSpeaker, IReadRepository, IRepository, IUnitOfWork, SessionizeResponse, SessionizeSpeaker, SessionizeSyncContext, Speaker, SpeakerSyncStrategy, UnitOfWork | +| 14 | `SpeakerSyncStrategyTests` | MMCA.ADC.Conference.Application.Tests | 12 | Event, EventSpeaker, IReadRepository, IRepository, IUnitOfWork, SessionizeLink, SessionizeResponse, SessionizeSpeaker, SessionizeSyncContext, Speaker, SpeakerSyncStrategy, UnitOfWork | +| 14 | `ActivityConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | Activity, ActivityInvariants, EntityTypeConfigurationSQLServer | +| 14 | `CategoryItemConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | CategoryInvariants, CategoryItem, EntityTypeConfigurationSQLServer | +| 14 | `ConferenceCategoryConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | Category, CategoryInvariants, EntityTypeConfigurationSQLServer | +| 14 | `EventConfiguration` | MMCA.ADC.Conference.Infrastructure | 4 | EntityTypeConfigurationSQLServer, Event, EventInvariants, NullableEmailValueConverter | +| 14 | `EventQuestionAnswerConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | EntityTypeConfigurationSQLServer, EventInvariants, EventQuestionAnswer | +| 14 | `EventSpeakerConfiguration` | MMCA.ADC.Conference.Infrastructure | 2 | EntityTypeConfigurationSQLServer, EventSpeaker | +| 14 | `PartnerConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | EntityTypeConfigurationSQLServer, Partner, PartnerInvariants | +| 14 | `QuestionConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | EntityTypeConfigurationSQLServer, Question, QuestionInvariants | +| 14 | `RoomConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | EntityTypeConfigurationSQLServer, EventInvariants, Room | +| 14 | `SessionAiScoreConfiguration` | MMCA.ADC.Conference.Infrastructure | 2 | EntityTypeConfigurationSQLServer, SessionAiScore | +| 14 | `SessionAssetConfiguration` | MMCA.ADC.Conference.Infrastructure | 5 | EntityTypeConfigurationSQLServer, Event, Session, SessionAsset, SessionAssetInvariants | +| 14 | `SessionCategoryItemConfiguration` | MMCA.ADC.Conference.Infrastructure | 2 | EntityTypeConfigurationSQLServer, SessionCategoryItem | +| 14 | `SessionConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | EntityTypeConfigurationSQLServer, Session, SessionInvariants | +| 14 | `SessionQuestionAnswerConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | EntityTypeConfigurationSQLServer, SessionInvariants, SessionQuestionAnswer | +| 14 | `SessionSpeakerConfiguration` | MMCA.ADC.Conference.Infrastructure | 2 | EntityTypeConfigurationSQLServer, SessionSpeaker | +| 14 | `SpeakerCategoryItemConfiguration` | MMCA.ADC.Conference.Infrastructure | 2 | EntityTypeConfigurationSQLServer, SpeakerCategoryItem | +| 14 | `SpeakerConfiguration` | MMCA.ADC.Conference.Infrastructure | 4 | EntityTypeConfigurationSQLServer, NullableEmailValueConverter, Speaker, SpeakerInvariants | +| 14 | `SpeakerQuestionAnswerConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | EntityTypeConfigurationSQLServer, SpeakerInvariants, SpeakerQuestionAnswer | +| 14 | `SponsorConfiguration` | MMCA.ADC.Conference.Infrastructure | 3 | EntityTypeConfigurationSQLServer, Sponsor, SponsorInvariants | | 14 | `ConferenceTestWebApplicationFactory` | MMCA.ADC.Conference.IntegrationTests | 9 | FakeAiScoringService, FakeBookmarkCountService, FakeSessionizeService, IAiScoringService, IBookmarkCountService, ISessionizeService, JwtTokenGenerator, Program, WebApplicationBuilderExtensions | | 14 | `ConferenceCrossServiceFactory` | MMCA.ADC.CrossService.IntegrationTests | 3 | JwtTokenGenerator, Program, WebApplicationBuilderExtensions | | 14 | `EngagementCrossServiceFactory` | MMCA.ADC.CrossService.IntegrationTests | 3 | JwtTokenGenerator, Program, WebApplicationBuilderExtensions | | 14 | `IdentityCrossServiceFactory` | MMCA.ADC.CrossService.IntegrationTests | 1 | Program | | 14 | `NotificationCrossServiceFactory` | MMCA.ADC.CrossService.IntegrationTests | 5 | FakeCrossServiceAttendeeQueryService, IAttendeeQueryService, JwtTokenGenerator, Program, WebApplicationBuilderExtensions | | 14 | `ModerateQuestionHandler` | MMCA.ADC.Engagement.Application | 20 | BestEffort, Error, IEventLiveValidationService, ILiveChannelPublishQueue, IUnitOfWork, LiveChannelPublishWorkItem, LivePollAuthorization, LivePollChannel, ModerateQuestionCommand, ModerationAction, MutateEntityHandlerBase, QuestionStatus, Result, SessionQuestion, SessionQuestionAnsweredPayload, SessionQuestionApprovedPayload, SessionQuestionChannel, SessionQuestionDismissedPayload, SessionQuestionPendingCountChangedPayload, UnitOfWork | +| 14 | `AttendeeBadgeConfiguration` | MMCA.ADC.Engagement.Infrastructure | 2 | AttendeeBadge, EntityTypeConfigurationSQLServer | +| 14 | `CheckInConfiguration` | MMCA.ADC.Engagement.Infrastructure | 2 | CheckIn, EntityTypeConfigurationSQLServer | +| 14 | `LeaderboardOptInConfiguration` | MMCA.ADC.Engagement.Infrastructure | 2 | EntityTypeConfigurationSQLServer, LeaderboardOptIn | +| 14 | `LivePollConfiguration` | MMCA.ADC.Engagement.Infrastructure | 3 | EntityTypeConfigurationSQLServer, LivePoll, LivePollInvariants | +| 14 | `LivePollOptionConfiguration` | MMCA.ADC.Engagement.Infrastructure | 3 | EntityTypeConfigurationSQLServer, LivePollInvariants, LivePollOption | +| 14 | `LivePollVoteConfiguration` | MMCA.ADC.Engagement.Infrastructure | 2 | EntityTypeConfigurationSQLServer, LivePollVote | +| 14 | `PointsEntryConfiguration` | MMCA.ADC.Engagement.Infrastructure | 3 | EntityTypeConfigurationSQLServer, PointsEntry, PointsSubjectKeys | +| 14 | `SessionQuestionConfiguration` | MMCA.ADC.Engagement.Infrastructure | 3 | EntityTypeConfigurationSQLServer, SessionQuestion, SessionQuestionInvariants | +| 14 | `SessionQuestionUpvoteConfiguration` | MMCA.ADC.Engagement.Infrastructure | 2 | EntityTypeConfigurationSQLServer, SessionQuestionUpvote | +| 14 | `UserSessionBookmarkConfiguration` | MMCA.ADC.Engagement.Infrastructure | 2 | EntityTypeConfigurationSQLServer, UserSessionBookmark | | 14 | `EngagementTestWebApplicationFactory` | MMCA.ADC.Engagement.IntegrationTests | 9 | FakeEventLiveValidationService, FakeSessionBookmarkValidationService, IEventLiveValidationService, ILiveChannelPublisher, ISessionBookmarkValidationService, JwtTokenGenerator, NullLiveChannelPublisher, Program, WebApplicationBuilderExtensions | | 14 | `GatewayApplicationFactory` | MMCA.ADC.Gateway.Tests | 3 | ProductionHostApplicationFactory, Program, RecordingHttpForwarder | | 14 | `GracefulShutdownTests` | MMCA.ADC.Gateway.Tests | 2 | GracefulShutdownTestsBase, Program | | 14 | `RobotsTxtTests` | MMCA.ADC.Gateway.Tests | 2 | ProductionHostApplicationFactory, Program | | 14 | `RouteMapApplicationFactory` | MMCA.ADC.Gateway.Tests | 2 | Program, RecordingHttpForwarder | | 14 | `SecurityHeadersTests` | MMCA.ADC.Gateway.Tests | 3 | ProductionHostApplicationFactory, Program, SecurityHeadersTestsBase | -| 14 | `AuthenticationService` | MMCA.ADC.Identity.Application | 21 | AuthenticationResponse, AuthenticationServiceBase, AuthenticationValidators, Email, EmailConfirmationSettings, Error, IAuthenticationService, IExternalLoginEmailVerifier, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, ITokenService, IUnitOfWork, RefreshSessionSettings, RegisterRequest, Result, TokenService, UnitOfWork, User, UserRegistered …(+1) | +| 14 | `AuthenticationService` | MMCA.ADC.Identity.Application | 19 | AuthenticationResponse, AuthenticationServiceBase, AuthenticationValidators, Email, EmailConfirmationSettings, Error, IAuthenticationService, IAuthSessionIssuer, IExternalLoginEmailVerifier, ILoginProtectionService, IPasswordHasher, IUnitOfWork, RegisterRequest, Result, TokenService, UnitOfWork, User, UserRegistered, UserRole | | 14 | `ForgotPasswordHandler` | MMCA.ADC.Identity.Application | 9 | Email, ForgotPasswordCommand, ForgotPasswordHandlerBase, IEmailSender, IPasswordResetTokenService, IUnitOfWork, PasswordResetSettings, UnitOfWork, User | | 14 | `SendEmailConfirmationHandler` | MMCA.ADC.Identity.Application | 9 | Email, EmailConfirmationSettings, IEmailConfirmationTokenService, IEmailSender, IUnitOfWork, SendEmailConfirmationCommand, SendEmailConfirmationHandlerBase, UnitOfWork, User | +| 14 | `UserConfiguration` | MMCA.ADC.Identity.Infrastructure | 4 | EmailValueConverter, EntityTypeConfigurationSQLServer, User, UserInvariants | +| 14 | `RefreshSessionModelGateTests` | MMCA.ADC.Identity.Infrastructure.Tests | 19 | AuditSaveChangesInterceptor, CaseConference, CaseIdentity, CaseWrongSource, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourceResolver, DataSourcesSettings, DomainEventSaveChangesInterceptor, GateContext, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IOutboxSignal, PhysicalDataSource, RefreshSession, RefreshSessionSettings | | 14 | `IdentityTestWebApplicationFactory` | MMCA.ADC.Identity.IntegrationTests | 6 | FakeUserEngagementExportService, FakeUserNotificationExportService, IUserEngagementExportService, IUserNotificationExportService, PiiCaptureLoggerProvider, Program | | 14 | `NotificationTestWebApplicationFactory` | MMCA.ADC.Notification.IntegrationTests | 5 | FakeAttendeeQueryService, IAttendeeQueryService, JwtTokenGenerator, Program, WebApplicationBuilderExtensions | | 14 | `ConferenceUiHostApplicationFactory` | MMCA.ADC.UI.Web.Tests | 2 | ProductionHostApplicationFactory, Program | -| 14 | `DatabaseInitializationExtensionsTests` | MMCA.Common.API.Tests | 27 | ApplicationSettings, AuditSaveChangesInterceptor, ConnectionStringSettings, CreateMigrationProofTable, DataSource, DataSourceEntrySettings, DataSourceResolver, DataSourcesSettings, DbContextFactory, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, FixedAssemblyProvider, ICurrentUserService, IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, InitTestMigratedWidget, InitTestWidget …(+7) | -| 14 | `FixedAssemblyProvider` | MMCA.Common.API.Tests | 2 | DatabaseInitializationExtensionsTests, IEntityConfigurationAssemblyProvider | +| 14 | `DependencyInjection` | MMCA.Common.API | 28 | CookieSessionRefresher, CookieTokenReader, CurrencyJsonConverter, CurrentUserTargetingContextAccessor, DbUpdateExceptionHandler, DisabledFeatureHandler, DomainExceptionHandler, EnumerationJsonConverterFactory, ErrorLocalizer, ErrorResources, ErrorResourceSource, GlobalExceptionHandler, ICookieSessionRefresher, IdempotencyFilter, IdempotencySettings, IErrorLocalizer, ISessionCookieStore, ModuleControllerFeatureProvider, ModuleLoader, ModulesSettings …(+8) | +| 14 | `SessionCookieAuthenticationExtensions` | MMCA.Common.API | 1 | SessionCookieAuthenticationHandler | +| 14 | `InitTestMigratedWidgetConfiguration` | MMCA.Common.API.Tests | 2 | EntityTypeConfigurationSqlite, InitTestMigratedWidget | +| 14 | `InitTestWidgetConfiguration` | MMCA.Common.API.Tests | 2 | EntityTypeConfigurationSqlite, InitTestWidget | +| 14 | `RefresherHarness` | MMCA.Common.API.Tests | 4 | CookieSessionRefresher, SessionCookieSettings, StubHttpClientFactory, StubHttpMessageHandler | +| 14 | `SessionCookieAuthenticationHandlerTests` | MMCA.Common.API.Tests | 4 | CookieTokenReader, FakeTimeProvider, SessionCookieAuthenticationHandler, SessionCookieEndpoints | | 14 | `MutateEntityPayloadHandlerBase` | MMCA.Common.Application | 7 | AuditableAggregateRootEntity, ICommandHandler, IUnitOfWork, MutateEntityHandlerCore, MutationContext, Result, UnitOfWork | +| 14 | `Harness` | MMCA.Common.Application.Tests | 8 | IRepository, IUnitOfWork, IUpdatePropertySetter, MarkAllNotificationsReadHandler, PushNotification, RecordingSetter, UnitOfWork, UserNotification | | 14 | `TestRetryingRenameHandler` | MMCA.Common.Application.Tests | 7 | Error, IUnitOfWork, MutateEntityHandlerBase, OrderAggregate, RenameOrderCommand, Result, UnitOfWork | -| 14 | `DeleteBehaviorConventionTests` | MMCA.Common.Architecture.Tests | 3 | ArchitectureRules, DeleteBehaviorConventionTestsBase, FrameworkModels | -| 14 | `DependencyInjection` | MMCA.Common.Infrastructure | 166 | ApplicationNamespace, AuditSaveChangesInterceptor, AuditTrailCleanupJob, AuditTrailReader, AuditTrailSaveChangesInterceptor, AuditTrailSettings, AzureBlobFileStorageService, AzureNotificationHubDeviceRegistrar, AzureNotificationHubNativePushSender, BrokerEventBus, BrokerMessageBus, CacheKeyNamespace, CacheKeyPrefixOptions, CacheSettings, ClaimBasedUserIdProvider, ClassReference, ConnectionStringSettings, ConnectionStringSettingsValidator, CorrelationContext, CurrentUserService …(+146) | +| 14 | `DataResidencyTestsBaseTests` | MMCA.Common.Architecture.Tests | 1 | Probe | +| 14 | `DbContextFactory` | MMCA.Common.Infrastructure | 22 | AmbientOrigin, ApplicationDbContext, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, Entry, ExplicitKeyInsertGroup, ICorrelationContext, ICurrentUserService, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, IExplicitKeyInsertDialect, InternalCommandMessage, IPhysicalDbContextFactory, ITenantContext, OutboxOrigin, PhysicalDataSource, Result, TenancySettings …(+2) | +| 14 | `DesignTimeDbContextHelper` | MMCA.Common.Infrastructure | 28 | AuditSaveChangesInterceptor, AuditTrailSaveChangesInterceptor, AuditTrailSettings, DataSource, DataSourceKey, DataSourceResolver, DataSourcesSettings, DesignTimeDbContextOptions, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, ExplicitAssemblyProvider, IDataSourceResolver, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IOutboxSignal, NullDomainEventDispatcher, OutboxSignal, PermissionGrantModelGate, PermissionGrantSettings …(+8) | | 14 | `IdentityModuleDbSeederBase` | MMCA.Common.Infrastructure | 9 | AuditableAggregateRootEntity, DbSeeder, Email, IPasswordHasher, IUnitOfWork, PasswordHasher, Result, SeedAccount, UnitOfWork | +| 14 | `PushNotificationConfiguration` | MMCA.Common.Infrastructure | 4 | EntityTypeConfigurationSQLServer, PushNotification, PushNotificationInvariants, SoftDeleteFilterSql | +| 14 | `RepositoryFactory` | MMCA.Common.Infrastructure | 10 | ApplicationSettings, AuditableAggregateRootEntity, AuditableBaseEntity, EFReadRepository, EFReadRepositoryDecorator, EFRepository, EFRepositoryDecorator, IReadRepository, IRepository, IRepositoryFactory | +| 14 | `TenantDataSourceTargets` | MMCA.Common.Infrastructure | 5 | DataSourceKey, ITenantContext, TenancySettings, TenancySettingsValidator, TenantDataSourceTarget | +| 14 | `UserNotificationConfiguration` | MMCA.Common.Infrastructure | 2 | EntityTypeConfigurationSQLServer, UserNotification | | 14 | `FixedAssemblyProvider` | MMCA.Common.Infrastructure.PostgreSQL.Tests | 2 | IEntityConfigurationAssemblyProvider, PostgreSQLPersistenceTests | +| 14 | `PgThingConfiguration` | MMCA.Common.Infrastructure.PostgreSQL.Tests | 2 | EntityTypeConfigurationPostgreSQL, PgThing | | 14 | `PostgreSQLPersistenceTests` | MMCA.Common.Infrastructure.PostgreSQL.Tests | 20 | ApplicationDbContext, AuditSaveChangesInterceptor, ConnectionStringSettings, DataSource, DataSourceKey, DataSourceResolver, DataSourcesSettings, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, FixedAssemblyProvider, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, OutboxMessage, OutboxSignal, PgThing, PgThingCreated, PhysicalDbContextFactory, RecordingDomainEventDispatcher | -| 14 | `AddAuditTrailTests` | MMCA.Common.Infrastructure.Tests | 6 | AuditTrailCleanupJob, AuditTrailReader, AuditTrailSaveChangesInterceptor, AuditTrailSettings, IAuditTrailReader, IScheduledJob | +| 14 | `SqlThingConfiguration` | MMCA.Common.Infrastructure.SQLServer.Tests | 2 | EntityTypeConfigurationSQLServer, SqlThing | +| 14 | `AddMultiTenancyTests` | MMCA.Common.Infrastructure.Tests | 11 | ConnectionStringSettings, DataSourceResolver, DataSourcesSettings, ITenantContext, TenancySettings, TenancySettingsValidator, TenantContext, TenantDataSourceOverrideSettings, TenantEntrySettings, TenantResolutionStrategy, TenantSaveChangesInterceptor | | 14 | `AddScheduledJobsTests` | MMCA.Common.Infrastructure.Tests | 5 | FirstJob, IScheduledJob, ScheduledJobRunner, SchedulerSettings, SecondJob | +| 14 | `ApplicationDbContextTenantFilterTests` | MMCA.Common.Infrastructure.Tests | 8 | ApplicationDbContext, EFReadRepository, IAuditableEntity, PlainThing, TenantDetail, TenantOnlyThing, TenantTestContext, TenantThing | +| 14 | `ApplicationDbContextTests` | MMCA.Common.Infrastructure.Tests | 4 | ApplicationDbContext, DataSource, TestApplicationDbContext, TestEntity | +| 14 | `AuditSaveChangesInterceptorTests` | MMCA.Common.Infrastructure.Tests | 4 | AuditSaveChangesInterceptor, FakeTimeProvider, TestAuditDbContext, TestAuditEntity | +| 14 | `AuditTrailSaveChangesInterceptorTests` | MMCA.Common.Infrastructure.Tests | 14 | AuditedThing, AuditTrailEntry, AuditTrailSaveChangesInterceptor, AuditTrailTestContext, AuditTrailTestHarness, CompositeKeyThing, Email, FakeTimeProvider, InboxMessage, OutboxMessage, OverridingPiiThing, PiiRedactor, PlainThing, ScheduledJobEntry | | 14 | `BrokerEventBusTests` | MMCA.Common.Infrastructure.Tests | 19 | ApplicationDbContext, AuditSaveChangesInterceptor, BrokerEventBus, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IEntityDataSourceRegistry, IIntegrationEvent, IOutboxSignal, Mocks, OutboxMessage, OutboxSettings, TestIntegrationEvent, TestNonOutboxContext, TestOutboxContext | | 14 | `BrokerMessageBusTests` | MMCA.Common.Infrastructure.Tests | 9 | BrokerMessageBus, ICorrelationContext, ICurrentUserService, IIntegrationEvent, ITenantContext, MessageHeaders, Mocks, OtherIntegrationEvent, TestIntegrationEvent | -| 14 | `CosmosConfigurationPortabilityTests` | MMCA.Common.Infrastructure.Tests | 17 | AuditSaveChangesInterceptor, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceResolver, DataSourcesSettings, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, FixedAssemblyProvider, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, OutboxSignal, PhysicalDbContextFactory, PortablePrincipal, PortableThing | | 14 | `CronosNextOccurrenceTests` | MMCA.Common.Infrastructure.Tests | 1 | ScheduledJobRunner | -| 14 | `DbContextFactoryAdditionalTests` | MMCA.Common.Infrastructure.Tests | 10 | DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, ICurrentUserService, IEntityDataSourceRegistry, IPhysicalDbContextFactory, ITenantContext, MidSaveContextCreatingDbContext, TenancySettings | -| 14 | `DbContextFactoryCommitAmbiguityTests` | MMCA.Common.Infrastructure.Tests | 16 | CommitFailingDbContext, DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, ICurrentUserService, IDomainEvent, IDomainEventDispatcher, IEntityDataSourceRegistry, IPhysicalDbContextFactory, ITenantContext, Result, TenancySettings, TestAggregate, TestLocalEvent, TransactionCommitAmbiguousException | -| 14 | `DbContextFactoryMigrationTargetTests` | MMCA.Common.Infrastructure.Tests | 22 | AuditSaveChangesInterceptor, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourceResolver, DataSourcesSettings, DbContextFactory, DomainEventSaveChangesInterceptor, FixedSourcesRegistry, FixedSourcesRegistry, ICurrentUserService, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, ITenantContext, NoConfigurationAssemblyProvider, NoConfigurationAssemblyProvider, OutboxSignal …(+2) | -| 14 | `DbContextFactorySaveIntegrityTests` | MMCA.Common.Infrastructure.Tests | 14 | DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, ICurrentUserService, IDomainEvent, IDomainEventDispatcher, IEntityDataSourceRegistry, IntegrityAggregate, IntegrityEvent, IntegrityTestDbContext, IPhysicalDbContextFactory, ITenantContext, TenancySettings | -| 14 | `DbContextFactoryTenantTests` | MMCA.Common.Infrastructure.Tests | 16 | ApplicationDbContext, DataSource, DataSourceKey, DbContextFactory, ICurrentUserService, IDataSourceResolver, IEntityDataSourceRegistry, IPhysicalDbContextFactory, ITenantContext, MutableTenantContext, PhysicalDataSource, TenancySettings, TenantContext, TenantDataSourceOverrideSettings, TenantEntrySettings, TenantTestContext | -| 14 | `DbContextFactoryTests` | MMCA.Common.Infrastructure.Tests | 10 | ApplicationDbContext, DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, ICurrentUserService, IEntityDataSourceRegistry, IPhysicalDbContextFactory, ITenantContext, TenancySettings | -| 14 | `DbContextFactoryTransactionTests` | MMCA.Common.Infrastructure.Tests | 17 | DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, Error, ICurrentUserService, IDomainEvent, IDomainEventDispatcher, IEntityDataSourceRegistry, IPhysicalDbContextFactory, ITenantContext, OutboxMessage, Result, TenancySettings, TestAggregate, TestLocalEvent, TransactionTestDbContext | +| 14 | `DataSourceResolverTests` | MMCA.Common.Infrastructure.Tests | 9 | AuditTrailSettings, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourceResolver, DataSourcesSettings, OutboxSettings, SchedulerSettings | | 14 | `DependencyInjectionBrokerMessagingTests` | MMCA.Common.Infrastructure.Tests | 6 | EfInboxStore, IInboxStore, InboxDisabledWarningService, MessageBusSettings, NoOpInboxStore, OrderPlacedConsumer | -| 14 | `DependencyInjectionInfrastructureTests` | MMCA.Common.Infrastructure.Tests | 15 | AuditSaveChangesInterceptor, ConnectionStringSettings, DomainEventSaveChangesInterceptor, EntityConfigurationOptions, IDataSourceService, IEntityConfigurationAssemblyProvider, IQueryableExecutor, IRepository, IRepositoryFactory, IUniqueConstraintViolationDetector, IUnitOfWork, OutboxProcessor, OutboxSettings, SmtpSettings, SqlServerUniqueConstraintViolationDetector | -| 14 | `DependencyInjectionOutboxGateTests` | MMCA.Common.Infrastructure.Tests | 3 | OutboxCleanupService, OutboxDisabledNoticeService, OutboxProcessor | | 14 | `DependencyInjectionTests` | MMCA.Common.Infrastructure.Tests | 23 | CorrelationContext, CurrentUserService, DistributedCacheService, EntityConfigurationOptions, ICacheService, ICorrelationContext, ICurrentUserService, IDistributedLock, IEmailSender, IEventBus, ILiveChannelPublisher, InProcessDistributedLock, InProcessEventBus, IPasswordHasher, IPushNotificationSender, ITokenService, MemoryCacheService, NullLiveChannelPublisher, NullPushNotificationSender, PasswordHasher …(+3) | -| 14 | `DesignTimeDbContextHelperTests` | MMCA.Common.Infrastructure.Tests | 12 | ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DesignAlphaEntity, DesignBetaEntity, DesignPostgreSQLEntity, DesignSqliteEntity, DesignTimeDbContextHelper, DesignTimeDbContextOptions, PermissionGrant, RefreshSession | -| 14 | `DomainEventSaveChangesInterceptorOutboxDisabledTests` | MMCA.Common.Infrastructure.Tests | 11 | DomainEventSaveChangesInterceptor, DomainEventSaveChangesInterceptorOutboxRoutingTests, IDomainEvent, IDomainEventDispatcher, IOutboxSignal, MessageBusSettings, OutboxMessage, OutboxRoutingTestDbContext, TestAggregate, TestIntegrationEvent, TestLocalEvent | -| 14 | `EfInboxStoreTests` | MMCA.Common.Infrastructure.Tests | 16 | ApplicationDbContext, AuditSaveChangesInterceptor, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, EfInboxStore, EmptyEntityDataSourceRegistry, IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, InboxMessage, InboxTestDbContext, IOutboxSignal, OutboxSettings | +| 14 | `DesignAlphaEntityConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | DesignAlphaEntity, EntityTypeConfigurationSQLServer | +| 14 | `DesignBetaEntityConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | DesignBetaEntity, EntityTypeConfigurationSQLServer | +| 14 | `DesignPostgreSQLEntityConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | DesignPostgreSQLEntity, EntityTypeConfigurationPostgreSQL | +| 14 | `DesignSqliteEntityConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | DesignSqliteEntity, EntityTypeConfigurationSqlite | +| 14 | `DomainEventCaptureExclusionTests` | MMCA.Common.Infrastructure.Tests | 8 | DomainEventSaveChangesInterceptor, ExclusionAggregate, ExclusionEvent, ExclusionTestDbContext, IDomainEvent, IDomainEventDispatcher, IOutboxSignal, MessageBusSettings | +| 14 | `DomainEventSaveChangesInterceptorOutboxRoutingTests` | MMCA.Common.Infrastructure.Tests | 11 | DomainEventSaveChangesInterceptor, FakeTimeProvider, IDomainEvent, IDomainEventDispatcher, IOutboxSignal, OutboxMessage, OutboxRoutingTestDbContext, TestAggregate, TestIntegrationEvent, TestLocalEvent, TestOrderedEvent | +| 14 | `DomainEventSaveChangesInterceptorTests` | MMCA.Common.Infrastructure.Tests | 8 | DomainEventSaveChangesInterceptor, IDomainEvent, IDomainEventDispatcher, IOutboxSignal, MessageBusSettings, TestAggregate, TestDomainEvent, TestDomainEventDbContext | +| 14 | `EfInboxStoreTests` | MMCA.Common.Infrastructure.Tests | 17 | ApplicationDbContext, AuditSaveChangesInterceptor, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, EfInboxStore, EmptyEntityDataSourceRegistry, FakeTimeProvider, IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, InboxMessage, InboxTestDbContext, IOutboxSignal, OutboxSettings | +| 14 | `EFPermissionGrantStoreTests` | MMCA.Common.Infrastructure.Tests | 10 | ApplicationDbContext, DataSourceKey, EFPermissionGrantStore, EmptyEntityDataSourceRegistry, GrantTestContext, IDataSourceResolver, IDbContextFactory, PermissionGrant, PermissionGrantSettings, SqlServerUniqueConstraintViolationDetector | | 14 | `EFRawSqlQueryExecutorTests` | MMCA.Common.Infrastructure.Tests | 7 | DataSource, EFRawSqlQueryExecutor, FixedEngineResolver, SingleContextFactory, Widget, WidgetContext, WidgetRow | -| 14 | `EFRepositoryAuditStampTests` | MMCA.Common.Infrastructure.Tests | 15 | DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, EFRepository, ICurrentUserService, IDataSourceService, IEntityDataSourceRegistry, IPhysicalDbContextFactory, IRepositoryFactory, ITenantContext, StampedEntity, StampTestDbContext, TenancySettings, UnitOfWork | -| 14 | `FixedAssemblyProvider` | MMCA.Common.Infrastructure.Tests | 3 | CosmosConfigurationPortabilityTests, IEntityConfigurationAssemblyProvider, MultiSourceSqliteIntegrationTests | +| 14 | `EFReadRepositoryGetByIdFilterTests` | MMCA.Common.Infrastructure.Tests | 4 | EFReadRepository, SoftDeletableTestEntity, SoftDeleteTestDbContext, SoftDeleteTestDbContext | +| 14 | `EFRepositoryAdditionalTests` | MMCA.Common.Infrastructure.Tests | 3 | EFRepository, TestDbContext, TestEntity | +| 14 | `EFRepositoryConcurrencyTouchTests` | MMCA.Common.Infrastructure.Tests | 3 | EFRepository, TestDbContext, TestEntity | +| 14 | `EFRepositoryIntegrationTests` | MMCA.Common.Infrastructure.Tests | 8 | EFReadRepository, EFRepository, FakeTimeProvider, IAuditableEntity, ICurrentUserService, TestChildEntity, TestDbContext, TestEntity | | 14 | `FixedAssemblyProvider` | MMCA.Common.Infrastructure.Tests | 2 | IEntityConfigurationAssemblyProvider, PostgreSQLDbContextModelTests | | 14 | `InProcessEventBusOutboxTests` | MMCA.Common.Infrastructure.Tests | 12 | DataSource, DataSourceKey, IDataSourceResolver, IDbContextFactory, IDomainEvent, IDomainEventDispatcher, InProcessEventBus, MessageBusSettings, OutboxMessage, OutboxSettings, TestIntegrationEvent, TestOutboxContext | | 14 | `InProcessEventBusTests` | MMCA.Common.Infrastructure.Tests | 10 | DataSource, DataSourceKey, IDataSourceResolver, IDbContextFactory, IDomainEvent, IDomainEventDispatcher, IIntegrationEvent, InProcessEventBus, OutboxSettings, TestNonOutboxContext | | 14 | `InProcessMessageBusTests` | MMCA.Common.Infrastructure.Tests | 16 | DomainEventDispatcher, IDomainEvent, IDomainEventDispatcher, IDomainEventHandler, IIntegrationEvent, IIntegrationEventHandler, InProcessMessageBus, Mocks, RecordingDomainHandler, RecordingIntegrationHandler, RecordingOriginalHandler, RecordingSuccessorHandler, RetiredTestIntegrationEvent, RetiredToV2Upcaster, TestIntegrationEvent, TestIntegrationEventV2 | -| 14 | `InternalCommandTestHarness` | MMCA.Common.Infrastructure.Tests | 28 | AnonymousCurrentUserService, ApplicationDbContext, CorrelationContext, DataSource, DataSourceKey, DefaultDataSourceResolver, EmptyEntityDataSourceRegistry, ExecutionLog, FakeTimeProvider, ICommandHandler, ICorrelationContext, ICurrentUserService, IDbContextFactory, IInternalCommand, IInternalCommandSignal, ImpersonatingCurrentUserService, InternalCommandMessage, InternalCommandProcessor, InternalCommandScheduler, InternalCommandsSettings …(+8) | -| 14 | `MigrationApplyProofTests` | MMCA.Common.Infrastructure.Tests | 21 | AuditSaveChangesInterceptor, ConnectionStringSettings, CreateMigrationProofTable, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourceResolver, DataSourcesSettings, DbContextFactory, DomainEventSaveChangesInterceptor, FixedSourcesRegistry, ICurrentUserService, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, ITenantContext, NoConfigurationAssemblyProvider, OutboxSignal, PhysicalDbContextFactory …(+1) | -| 14 | `Mocks` | MMCA.Common.Infrastructure.Tests | 3 | IDataSourceResolver, IEntityDataSourceRegistry, OutboxCleanupService | -| 14 | `MultiSourceSqliteIntegrationTests` | MMCA.Common.Infrastructure.Tests | 28 | ApplicationSettings, AuditSaveChangesInterceptor, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceResolver, DataSourceService, DataSourcesSettings, DbContextFactory, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, FixedAssemblyProvider, ICurrentUserService, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, ITenantContext, MultiSourceCustomer, MultiSourceOrder …(+8) | -| 14 | `OutboxAdministrationTests` | MMCA.Common.Infrastructure.Tests | 12 | AdminTestContext, DataSource, DataSourceKey, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, IOutboxSignal, OutboxAdministration, OutboxDeadLetter, OutboxMessage, OutboxSettings, Payload | -| 14 | `OutboxProcessorContextRestoreTests` | MMCA.Common.Infrastructure.Tests | 26 | AuditSaveChangesInterceptor, CapturingMessageBus, CorrelationContext, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, ICorrelationContext, ICurrentUserService, IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IMessageBus, IOutboxSignal, ITenantContext, OutboxMessage, OutboxOrigin, OutboxProcessor …(+6) | -| 14 | `OutboxProcessorOrderingTests` | MMCA.Common.Infrastructure.Tests | 16 | DataSource, DataSourceKey, FakeTimeProvider, IDataSourceResolver, IDbContextFactory, IDomainEvent, IDomainEventDispatcher, IEntityDataSourceRegistry, IMessageBus, IOutboxSignal, OrderedTestEvent, OrderingTestContext, OutboxMessage, OutboxProcessor, OutboxSettings, Payload | -| 14 | `OutboxProcessorTests` | MMCA.Common.Infrastructure.Tests | 25 | AuditSaveChangesInterceptor, BrokerResilienceDefaults, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FakeTimeProvider, IDataSourceResolver, IDbContextFactory, IDomainEvent, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IIntegrationEvent, IMessageBus, IOutboxSignal, OutboxCycleResult, OutboxMessage, OutboxProcessor, OutboxSettings …(+5) | -| 14 | `OutboxProcessorWaitTests` | MMCA.Common.Infrastructure.Tests | 1 | OutboxProcessor | +| 14 | `MarkAllNotificationsReadHandlerTrackingTests` | MMCA.Common.Infrastructure.Tests | 9 | EFRepository, IUnitOfWork, MarkAllNotificationsReadCommand, MarkAllNotificationsReadHandler, NotificationTestDbContext, PushNotification, Result, SeededIds, UserNotification | +| 14 | `MultiSourceCustomerConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationSqlite, MultiSourceCustomer | +| 14 | `MultiSourceOrderConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationSqlite, MultiSourceOrder | +| 14 | `PortablePrincipalConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationSQLServer, PortablePrincipal | | 14 | `PostgreSQLDbContextModelTests` | MMCA.Common.Infrastructure.Tests | 21 | ApplicationDbContext, AuditSaveChangesInterceptor, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceResolver, DataSourcesSettings, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, FixedAssemblyProvider, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, OutboxMessage, OutboxSignal, PhysicalDbContextFactory, PostgreSQLDbContext, PostgresThing, SqlServerThing …(+1) | +| 14 | `PostgresThingConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationPostgreSQL, PostgresThing | +| 14 | `QueryParameterizationTests` | MMCA.Common.Infrastructure.Tests | 3 | QueryFieldService, QueryFilterService, QueryShapeTestDbContext | +| 14 | `RegistryDuplicateConfigurationA` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationSqlite, RegistryDuplicate | +| 14 | `RegistryDuplicateConfigurationB` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationSqlite, RegistryDuplicate | +| 14 | `RegistryInvoiceConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationSqlite, RegistryInvoice | +| 14 | `RegistryOrderConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationSqlite, RegistryOrder | +| 14 | `RegistrySqlServerEntityConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationSQLServer, RegistrySqlServerEntity | +| 14 | `RestrictDeleteByDefaultConventionTests` | MMCA.Common.Infrastructure.Tests | 6 | CascadingChild, DeleteBehaviorTestDbContext, OptionalChild, Parent, RequiredChild, RestrictDeleteByDefaultConvention | +| 14 | `SaveChangeDetectionTests` | MMCA.Common.Infrastructure.Tests | 3 | DetectionTestDbContext, Widget, Widget | | 14 | `ScheduledJobRunnerTests` | MMCA.Common.Infrastructure.Tests | 10 | ApplicationDbContext, DataSource, DelegateScheduledJob, FakeTimeProvider, IDataSourceResolver, ScheduledJobEntry, ScheduledJobOverrideSettings, ScheduledJobRunner, SchedulerSettings, SchedulerTestContext | | 14 | `SchedulerTestHarness` | MMCA.Common.Infrastructure.Tests | 9 | ApplicationDbContext, DataSource, DataSourceKey, FakeTimeProvider, IDataSourceResolver, IDbContextFactory, IScheduledJob, ScheduledJobRunner, SchedulerSettings | +| 14 | `SoftDeleteQueryFilterTests` | MMCA.Common.Infrastructure.Tests | 2 | SoftDeletableEntity, SoftDeleteTestDbContext | +| 14 | `SoftDeleteUniqueIndexConventionTests` | MMCA.Common.Infrastructure.Tests | 5 | AlreadySoftDeleteFilteredEntity, BracketQuotedFilterEntity, FilteredIndexEntity, UniqueIndexTestDbContext, UniqueNamedEntity | +| 14 | `SqliteTestEntityConfig` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationSqlite, SqliteTestEntity | +| 14 | `SqlServerThingConfiguration` | MMCA.Common.Infrastructure.Tests | 2 | EntityTypeConfigurationSQLServer, SqlServerThing | | 14 | `StronglyTypedIdPersistenceTests` | MMCA.Common.Infrastructure.Tests | 15 | ApplicationDbContext, CustomerId, LineId, NullableStronglyTypedIdValueConverter, OrderId, QueryFilterService, SpeakerId, StronglyTypedIdValueComparer, StronglyTypedIdValueConverter, WrappedIdBareSqliteContext, WrappedIdPostgresContext, WrappedIdSqliteContext, WrappedIdSqlServerContext, WrappedOrder, WrappedSpeaker | | 14 | `SweepHarness` | MMCA.Common.Infrastructure.Tests | 14 | ApplicationDbContext, DataSourceKey, DefaultDataSourceResolver, EmptyEntityDataSourceRegistry, FakeClockLoop, FakeTimeProvider, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, NoSessionTableContext, RefreshSession, RefreshSessionCleanupService, RefreshSessionSettings, SessionCleanupTestContext | -| 14 | `TenantDataSourceTargetTests` | MMCA.Common.Infrastructure.Tests | 14 | DataSource, DataSourceKey, IDataSourceResolver, IEntityDataSourceRegistry, IOutboxSignal, MessageBusSettings, OutboxCleanupService, OutboxProcessor, OutboxSettings, TenancySettings, TenantDataSourceOverrideSettings, TenantDataSourceTarget, TenantDataSourceTargets, TenantEntrySettings | +| 14 | `TenantSaveChangesInterceptorTests` | MMCA.Common.Infrastructure.Tests | 5 | CrossTenantWriteException, PlainThing, TenantTestContext, TenantThing, TrailedTenantThing | | 14 | `UnitOfWorkAdditionalTests` | MMCA.Common.Infrastructure.Tests | 12 | ApplicationDbContext, DataSource, DataSourceKey, FakeAggregate, FakeEntity, IDataSourceService, IDbContextFactory, IReadRepository, IRepository, IRepositoryFactory, Mocks, UnitOfWork | | 14 | `UnitOfWorkTests` | MMCA.Common.Infrastructure.Tests | 12 | ApplicationDbContext, DataSource, DataSourceKey, FakeAggregate, FakeEntity, IDataSourceService, IDbContextFactory, IReadRepository, IRepository, IRepositoryFactory, Mocks, UnitOfWork | +| 14 | `LoadItemConfiguration` | MMCA.Common.LoadTests | 2 | EntityTypeConfigurationSqlite, LoadItem | +| 14 | `PagedQueryFixture` | MMCA.Common.LoadTests | 11 | EntityQueryService, IEntityDTOMapper, IEntityQueryService, INavigationPopulator, LoadItem, LoadItemDTO, LoadItemMapper, LoadItemNavigationPopulator, LoadStack, PagedCollectionResult, PagedQuery | | 14 | `HandlerTestBase` | MMCA.Common.Testing | 6 | AuditableAggregateRootEntity, AuditableBaseEntity, IReadRepository, IRepository, IUnitOfWork, UnitOfWork | -| 15 | `DeleteBehaviorConventionTests` | MMCA.ADC.Architecture.Tests | 3 | ArchitectureRules, DeleteBehaviorConventionTestsBase, ServiceModels | +| 14 | `DependencyInjection` | MMCA.Common.UI.Web | 11 | ApiSettings, BlazorCspPolicyProvider, BlazorCspSettings, BlazorCspSettingsValidator, GatewayRateLimitingSettings, ICspPolicyProvider, IFormFactor, ITokenStorageService, ServerTokenStorageService, TrustedCallerHandler, WebFormFactor | +| 14 | `SameOriginApiProxyServiceExtensions` | MMCA.Common.UI.Web | 12 | ApiSettings, HandoffSessionCookieSync, HandoffTokenRefresher, ISessionCookieSync, ITokenRefresher, SameOriginApiProxyEndpoint, SameOriginApiProxyMarker, SameOriginApiProxySettings, SameOriginApiProxySettingsValidator, SameOriginProxyInvoker, SessionCookieSettings, SessionHandoffProtector | +| 14 | `SessionHandoffEndpoints` | MMCA.Common.UI.Web | 5 | HandoffBody, ICookieSessionRefresher, ISessionCookieStore, SameOriginApiProxyEndpoint, SessionHandoffProtector | +| 14 | `ServerTokenStorageServiceTests` | MMCA.Common.UI.Web.Tests | 6 | CookieTokenReader, ISessionCookieSync, ITokenRefresher, Mocks, ServerTokenStorageService, SessionCookieEndpoints | +| 14 | `WebFormFactorTests` | MMCA.Common.UI.Web.Tests | 5 | ICspPolicyProvider, IFormFactor, ITokenStorageService, ServerTokenStorageService, WebFormFactor | +| 15 | `ServiceModels` | MMCA.ADC.Architecture.Tests | 3 | DataSourceEntrySettings, DefaultEntityConfigurationAssemblyProvider, DesignTimeDbContextHelper | | 15 | `AddRoomHandler` | MMCA.ADC.Conference.Application | 13 | AddRoomCommand, Error, Event, EventInvariants, IEntityQuerier, IUnitOfWork, MutateEntityPayloadHandlerBase, MutationContext, Result, Room, RoomDTO, RoomDTOMapper, UnitOfWork | -| 15 | `DependencyInjection` | MMCA.ADC.Conference.Application | 86 | Activity, ActivityCreateRequest, ActivityDTO, ActivityNavigationPopulator, ActivityUpdateRequest, ApplicationSettings, Category, CategoryItem, CategoryItemDTO, CategoryItemNavigationPopulator, ClassReference, ClassReference, ConferenceCategoryCreateRequest, ConferenceCategoryDTO, ConferenceCategoryEntityQueryService, ConferenceCategoryNavigationPopulator, ConferenceCategoryUpdateRequest, DeleteConferenceCategoryHandler, DeleteEntityCommand, DeleteEntityHandler …(+66) | +| 15 | `DependencyInjection` | MMCA.ADC.Conference.Application | 84 | Activity, ActivityCreateRequest, ActivityDTO, ActivityNavigationPopulator, ActivityUpdateRequest, ApplicationSettings, Category, CategoryItem, CategoryItemDTO, CategoryItemNavigationPopulator, ClassReference, ClassReference, ConferenceCategoryCreateRequest, ConferenceCategoryDTO, ConferenceCategoryEntityQueryService, ConferenceCategoryNavigationPopulator, ConferenceCategoryUpdateRequest, DeleteConferenceCategoryHandler, DeleteEntityCommand, DeleteEntityHandler …(+64) | | 15 | `UpdateEventHandler` | MMCA.ADC.Conference.Application | 11 | Event, EventDTOMapper, IEntityReader, IUnitOfWork, MutateEntityPayloadHandlerBase, MutationContext, Result, Session, UnitOfWork, UpdateEventCommand, UpdateEventResult | | 15 | `UpdateSessionHandler` | MMCA.ADC.Conference.Application | 13 | Error, Event, IEntityReader, IUnitOfWork, MutateEntityPayloadHandlerBase, MutationContext, Result, Session, SessionDTOMapper, SessionRoomScheduling, UnitOfWork, UpdateSessionCommand, UpdateSessionResult | | 15 | `ActivityNavigationPopulatorTests` | MMCA.ADC.Conference.Application.Tests | 6 | Activity, ActivityNavigationPopulator, HandlerTestBase, INavigationPopulator, NavigationMetadata, UnitOfWork | @@ -4886,6 +5025,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 15 | `CreateSessionHandlerTests` | MMCA.ADC.Conference.Application.Tests | 18 | CreateSessionHandler, Error, ErrorType, Event, HandlerTestBase, IEntityRequestMapper, IRepository, IUniqueConstraintViolationDetector, IUnitOfWork, Result, Session, SessionCategoryItemDTOMapper, SessionCreateRequest, SessionDTOMapper, SessionInvariants, SessionQuestionAnswerDTOMapper, SessionSpeakerDTOMapper, UnitOfWork | | 15 | `CreateSpeakerHandlerTests` | MMCA.ADC.Conference.Application.Tests | 14 | CreateSpeakerHandler, Email, Error, HandlerTestBase, ICurrentUserService, IEntityRequestMapper, IRepository, Result, Speaker, SpeakerCategoryItemDTOMapper, SpeakerCreateRequest, SpeakerDTOMapper, SpeakerQuestionAnswerDTOMapper, UnitOfWork | | 15 | `CreateSponsorHandlerTests` | MMCA.ADC.Conference.Application.Tests | 11 | CreateSponsorHandler, Error, HandlerTestBase, IEntityRequestMapper, IRepository, Result, Sponsor, SponsorCreateRequest, SponsorDTOMapper, SponsorTier, UnitOfWork | +| 15 | `DeleteConferenceCategoryHandlerTests` | MMCA.ADC.Conference.Application.Tests | 6 | Category, DeleteConferenceCategoryHandler, DeleteEntityCommand, HandlerTestBase, IRepository, UnitOfWork | | 15 | `DeleteEventHandlerTests` | MMCA.ADC.Conference.Application.Tests | 15 | Activity, DeleteEntityCommand, DeleteEventHandler, ErrorType, Event, HandlerTestBase, IInternalCommandScheduler, IRepository, Partner, PartnerType, Session, SessionAsset, Sponsor, SponsorTier, UnitOfWork | | 15 | `DeleteSessionAssetHandlerTests` | MMCA.ADC.Conference.Application.Tests | 14 | DeleteSessionAssetBlobInternalCommand, DeleteSessionAssetCommand, DeleteSessionAssetHandler, Error, ErrorType, HandlerTestBase, IInternalCommand, IInternalCommandScheduler, IRepository, ISessionAssetAccessService, Result, SessionAsset, SessionAssetFixtures, UnitOfWork | | 15 | `DeleteSessionHandlerTests` | MMCA.ADC.Conference.Application.Tests | 9 | DeleteEntityCommand, DeleteSessionHandler, ErrorType, HandlerTestBase, IInternalCommandScheduler, IRepository, Session, SessionAsset, UnitOfWork | @@ -4914,7 +5054,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 15 | `PartnerNavigationPopulatorTests` | MMCA.ADC.Conference.Application.Tests | 6 | HandlerTestBase, INavigationPopulator, NavigationMetadata, Partner, PartnerNavigationPopulator, UnitOfWork | | 15 | `PartnerUpdateApplierTests` | MMCA.ADC.Conference.Application.Tests | 12 | ErrorType, HandlerTestBase, IRepository, Partner, PartnerDTO, PartnerDTOMapper, PartnerType, PartnerUpdateApplier, PartnerUpdateRequest, UnitOfWork, UpdateEntityCommand, UpdateEntityHandler | | 15 | `PublishEventHandlerTests` | MMCA.ADC.Conference.Application.Tests | 7 | ErrorType, Event, HandlerTestBase, IRepository, PublishEventCommand, PublishEventHandler, UnitOfWork | -| 15 | `RefreshFromSessionizeHandlerTests` | MMCA.ADC.Conference.Application.Tests | 12 | Error, ErrorType, Event, IConcurrencyConflictDetector, ICurrentUserService, IRepository, ISessionizeService, IUnitOfWork, RefreshFromSessionizeCommand, RefreshFromSessionizeHandler, Result, SessionizeResponse | +| 15 | `RefreshFromSessionizeHandlerTests` | MMCA.ADC.Conference.Application.Tests | 14 | Error, ErrorType, Event, IConcurrencyConflictDetector, ICurrentUserService, IRepository, ISessionizeService, ITransactional, IUnitOfWork, RefreshFromSessionizeCommand, RefreshFromSessionizeHandler, RefreshFromSessionizeResultDTO, Result, SessionizeResponse | | 15 | `RemoveCategoryItemHandlerTests` | MMCA.ADC.Conference.Application.Tests | 7 | Category, ErrorType, HandlerTestBase, IRepository, RemoveCategoryItemCommand, RemoveCategoryItemHandler, UnitOfWork | | 15 | `RemoveEventQuestionAnswerHandlerTests` | MMCA.ADC.Conference.Application.Tests | 9 | ErrorType, Event, HandlerTestBase, ICurrentUserService, IRepository, RemoveEventQuestionAnswerCommand, RemoveEventQuestionAnswerHandler, RoleNames, UnitOfWork | | 15 | `RemoveEventSpeakerHandlerTests` | MMCA.ADC.Conference.Application.Tests | 7 | ErrorType, Event, HandlerTestBase, IRepository, RemoveEventSpeakerCommand, RemoveEventSpeakerHandler, UnitOfWork | @@ -4931,32 +5071,34 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 15 | `SessionEntityQueryServiceTests` | MMCA.ADC.Conference.Application.Tests | 15 | EntityQueryParameters, HandlerTestBase, IEntityQueryPipeline, INavigationMetadataProvider, INavigationPopulator, IReadRepository, NavigationMetadata, Session, SessionCategoryItemDTOMapper, SessionDTO, SessionDTOMapper, SessionEntityQueryService, SessionQuestionAnswerDTOMapper, SessionSpeakerDTOMapper, UnitOfWork | | 15 | `SessionNavigationPopulatorTests` | MMCA.ADC.Conference.Application.Tests | 6 | HandlerTestBase, INavigationPopulator, NavigationMetadata, Session, SessionNavigationPopulator, UnitOfWork | | 15 | `SessionQuestionAnswerNavigationPopulatorTests` | MMCA.ADC.Conference.Application.Tests | 6 | HandlerTestBase, INavigationPopulator, NavigationMetadata, SessionQuestionAnswer, SessionQuestionAnswerNavigationPopulator, UnitOfWork | -| 15 | `SessionScoringRunnerTests` | MMCA.ADC.Conference.Application.Tests | 13 | AuditableBaseEntity, HandlerTestBase, IAiScoringService, IRepository, Session, SessionAiScore, SessionBuilder, SessionScoringInput, SessionScoringResult, SessionScoringRunner, SessionStatuses, Speaker, UnitOfWork | +| 15 | `SessionScoringRunnerTests` | MMCA.ADC.Conference.Application.Tests | 14 | AuditableBaseEntity, HandlerTestBase, IAiScoringService, IRepository, IUnitOfWork, Session, SessionAiScore, SessionBuilder, SessionScoringInput, SessionScoringResult, SessionScoringRunner, SessionStatuses, Speaker, UnitOfWork | | 15 | `SessionSpeakerNavigationPopulatorTests` | MMCA.ADC.Conference.Application.Tests | 6 | HandlerTestBase, INavigationPopulator, NavigationMetadata, SessionSpeaker, SessionSpeakerNavigationPopulator, UnitOfWork | | 15 | `SpeakerCategoryItemNavigationPopulatorTests` | MMCA.ADC.Conference.Application.Tests | 6 | HandlerTestBase, INavigationPopulator, NavigationMetadata, SpeakerCategoryItem, SpeakerCategoryItemNavigationPopulator, UnitOfWork | | 15 | `SpeakerEntityQueryServiceTests` | MMCA.ADC.Conference.Application.Tests | 17 | EntityQueryParameters, ErrorType, HandlerTestBase, ICurrentUserService, IEntityQueryPipeline, INavigationMetadataProvider, INavigationPopulator, InlineSpecification, IReadRepository, NavigationMetadata, Speaker, SpeakerBuilder, SpeakerCategoryItemDTOMapper, SpeakerDTOMapper, SpeakerEntityQueryService, SpeakerQuestionAnswerDTOMapper, UnitOfWork | | 15 | `SpeakerNavigationPopulatorTests` | MMCA.ADC.Conference.Application.Tests | 6 | HandlerTestBase, INavigationPopulator, NavigationMetadata, Speaker, SpeakerNavigationPopulator, UnitOfWork | -| 15 | `SpeakerQuestionAnswerNavigationPopulatorTests` | MMCA.ADC.Conference.Application.Tests | 6 | HandlerTestBase, INavigationPopulator, NavigationMetadata, SpeakerQuestionAnswer, SpeakerQuestionAnswerNavigationPopulator, UnitOfWork | | 15 | `SponsorNavigationPopulatorTests` | MMCA.ADC.Conference.Application.Tests | 6 | HandlerTestBase, INavigationPopulator, NavigationMetadata, Sponsor, SponsorNavigationPopulator, UnitOfWork | | 15 | `SponsorUpdateApplierTests` | MMCA.ADC.Conference.Application.Tests | 12 | ErrorType, HandlerTestBase, IRepository, Sponsor, SponsorDTO, SponsorDTOMapper, SponsorTier, SponsorUpdateApplier, SponsorUpdateRequest, UnitOfWork, UpdateEntityCommand, UpdateEntityHandler | | 15 | `UnlinkUserFromSpeakerHandlerTests` | MMCA.ADC.Conference.Application.Tests | 8 | ErrorType, HandlerTestBase, IRepository, Speaker, SpeakerUnlinkedFromUser, UnitOfWork, UnlinkUserFromSpeakerCommand, UnlinkUserFromSpeakerHandler | | 15 | `UnpublishEventHandlerTests` | MMCA.ADC.Conference.Application.Tests | 7 | ErrorType, Event, HandlerTestBase, IRepository, UnitOfWork, UnpublishEventCommand, UnpublishEventHandler | | 15 | `UpdateCategoryItemHandlerTests` | MMCA.ADC.Conference.Application.Tests | 7 | Category, ErrorType, HandlerTestBase, IRepository, UnitOfWork, UpdateCategoryItemCommand, UpdateCategoryItemHandler | -| 15 | `UpdateEventQuestionAnswerHandlerTests` | MMCA.ADC.Conference.Application.Tests | 9 | ErrorType, Event, HandlerTestBase, ICurrentUserService, IRepository, RoleNames, UnitOfWork, UpdateEventQuestionAnswerCommand, UpdateEventQuestionAnswerHandler | +| 15 | `UpdateEventQuestionAnswerHandlerTests` | MMCA.ADC.Conference.Application.Tests | 10 | ErrorType, Event, HandlerTestBase, ICurrentUserService, IRepository, Question, RoleNames, UnitOfWork, UpdateEventQuestionAnswerCommand, UpdateEventQuestionAnswerHandler | | 15 | `UpdateQuestionHandlerTests` | MMCA.ADC.Conference.Application.Tests | 13 | ErrorType, EventQuestionAnswer, HandlerTestBase, IReadRepository, IRepository, Question, QuestionDTOMapper, QuestionUpdateRequest, SessionQuestionAnswer, SpeakerQuestionAnswer, UnitOfWork, UpdateQuestionCommand, UpdateQuestionHandler | | 15 | `UpdateRoomHandlerTests` | MMCA.ADC.Conference.Application.Tests | 7 | ErrorType, Event, HandlerTestBase, IRepository, UnitOfWork, UpdateRoomCommand, UpdateRoomHandler | -| 15 | `UpdateSessionQuestionAnswerHandlerTests` | MMCA.ADC.Conference.Application.Tests | 10 | ErrorType, Event, HandlerTestBase, ICurrentUserService, IRepository, RoleNames, Session, UnitOfWork, UpdateSessionQuestionAnswerCommand, UpdateSessionQuestionAnswerHandler | +| 15 | `UpdateSessionAssetHandlerTests` | MMCA.ADC.Conference.Application.Tests | 13 | ErrorType, HandlerTestBase, IRepository, Session, SessionAsset, SessionAssetAccessService, SessionAssetDTOMapper, SessionAssetFixtures, SessionAssetUpdateRequest, SessionBuilder, UnitOfWork, UpdateSessionAssetCommand, UpdateSessionAssetHandler | +| 15 | `UpdateSessionQuestionAnswerHandlerTests` | MMCA.ADC.Conference.Application.Tests | 11 | ErrorType, Event, HandlerTestBase, ICurrentUserService, IRepository, Question, RoleNames, Session, UnitOfWork, UpdateSessionQuestionAnswerCommand, UpdateSessionQuestionAnswerHandler | | 15 | `UpdateSpeakerHandlerTests` | MMCA.ADC.Conference.Application.Tests | 14 | Email, ErrorType, HandlerTestBase, ICurrentUserService, IRepository, Speaker, SpeakerCategoryItemDTOMapper, SpeakerDTOMapper, SpeakerQuestionAnswerDTOMapper, SpeakerUpdateApplier, SpeakerUpdateRequest, UnitOfWork, UpdateSpeakerCommand, UpdateSpeakerHandler | | 15 | `UploadSessionAssetHandlerTests` | MMCA.ADC.Conference.Application.Tests | 17 | DeleteSessionAssetBlobInternalCommand, Error, FileUploadOptions, HandlerTestBase, IFileStorageService, IInternalCommand, IInternalCommandScheduler, IRepository, ISessionAssetAccessService, Result, SessionAsset, SessionAssetDTOMapper, SessionAssetFixtures, SessionAssetKind, UnitOfWork, UploadSessionAssetCommand, UploadSessionAssetHandler | +| 15 | `ConferenceEntityConfigurationTests` | MMCA.ADC.Conference.Infrastructure.Tests | 35 | Category, CategoryInvariants, CategoryItem, CategoryItemConfiguration, ConferenceCategoryConfiguration, ConferenceTestDbContext, Event, EventConfiguration, EventInvariants, EventQuestionAnswer, EventQuestionAnswerConfiguration, EventSpeaker, EventSpeakerConfiguration, Question, QuestionConfiguration, QuestionInvariants, Room, RoomConfiguration, Session, SessionCategoryItem …(+15) | | 15 | `ConferenceIntegrationTestFixture` | MMCA.ADC.Conference.IntegrationTests | 4 | ConferenceTestWebApplicationFactory, JwtTokenGenerator, Program, SqlServerIntegrationTestFixtureBase | | 15 | `CrossServiceFixture` | MMCA.ADC.CrossService.IntegrationTests | 7 | ConferenceCrossServiceFactory, CrossServiceDataSource, CrossServiceFixtureBase, EngagementCrossServiceFactory, IdentityCrossServiceFactory, JwtTokenGenerator, NotificationCrossServiceFactory | | 15 | `AttendeeCheckedInPointsHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 11 | AttendeeCheckedIn, AttendeeCheckedInPointsHandler, CheckInScopeNames, Error, HandlerTestBase, IPointsAwarder, PointsActivityType, RecordingPointsAwarder, Result, SponsorVisit, TestSupport | | 15 | `BookmarkCountServiceTests` | MMCA.ADC.Engagement.Application.Tests | 5 | BookmarkCountService, HandlerTestBase, IBookmarkCountService, UnitOfWork, UserSessionBookmark | -| 15 | `CastVoteHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 12 | CastVoteCommand, CastVoteHandler, ErrorType, FakeTimeProvider, HandlerMocks, HandlerTestBase, InMemoryQueryableExecutor, IReadRepository, LivePoll, LivePollResultsBuilder, LivePollVote, UnitOfWork | +| 15 | `CastVoteHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 14 | CastVoteCommand, CastVoteHandler, ErrorType, FakeTimeProvider, HandlerMocks, HandlerTestBase, InMemoryQueryableExecutor, IReadRepository, IUniqueConstraintViolationDetector, LivePoll, LivePollResultsBuilder, LivePollVote, TestSupport, UnitOfWork | | 15 | `CheckInAttendeeHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 20 | AttendeeBadge, AttendeeCheckedIn, BadgePayload, CheckIn, CheckInAttendeeHandler, CheckInAttendeeRequest, CheckInScope, CheckInScopeNames, Error, ErrorType, EventLiveInfo, FakeTimeProvider, HandlerMocks, HandlerTestBase, ICurrentUserService, IEventLiveValidationService, QuestionModerationDefault, Result, SessionLiveInfo, UnitOfWork | | 15 | `CloseLivePollHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 16 | CloseLivePollCommand, CloseLivePollHandler, Error, ErrorType, HandlerMocks, HandlerTestBase, IEventLiveValidationService, ILiveChannelPublishQueue, LiveChannelPublishWorkItem, LivePoll, LivePollChannel, LivePollStatus, QuestionModerationDefault, Result, SessionLiveInfo, UnitOfWork | | 15 | `CreateBookmarkHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 12 | CreateBookmarkHandler, CreateBookmarkRequest, Error, ErrorType, HandlerMocks, HandlerTestBase, ISessionBookmarkValidationService, IUniqueConstraintViolationDetector, Result, UnitOfWork, UserSessionBookmark, UserSessionBookmarkDTOMapper | | 15 | `CreateLivePollHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 17 | CreateLivePollCommand, CreateLivePollHandler, CreateLivePollRequest, Error, ErrorType, EventLiveInfo, HandlerMocks, HandlerTestBase, IEventLiveValidationService, LivePoll, LivePollDTOMapper, LivePollStatus, Question, QuestionModerationDefault, Result, SessionLiveInfo, UnitOfWork | +| 15 | `DeleteLivePollHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 6 | DeleteEntityCommand, DeleteLivePollHandler, HandlerTestBase, IRepository, LivePoll, UnitOfWork | | 15 | `EventFeedbackSubmittedPointsHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 9 | Error, EventFeedbackSubmitted, EventFeedbackSubmittedPointsHandler, HandlerTestBase, IPointsAwarder, PointsActivityType, RecordingPointsAwarder, Result, TestSupport | | 15 | `GetAttendanceStatsHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 6 | CheckIn, CheckInScope, GetAttendanceStatsHandler, GetAttendanceStatsQuery, HandlerTestBase, UnitOfWork | | 15 | `GetBookmarkedSessionIdsHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 5 | GetBookmarkedSessionIdsHandler, GetBookmarkedSessionIdsQuery, HandlerTestBase, UnitOfWork, UserSessionBookmark | @@ -4964,12 +5106,12 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 15 | `GetLeaderboardHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 9 | Entry, GetLeaderboardHandler, GetLeaderboardQuery, HandlerTestBase, LeaderboardOptIn, PointsActivityType, PointsEntry, PointsSettings, UnitOfWork | | 15 | `GetModerationQueueHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 16 | Error, ErrorType, GetModerationQueueHandler, GetModerationQueueQuery, HandlerMocks, HandlerTestBase, IEventLiveValidationService, InMemoryQueryableExecutor, QuestionModerationDefault, QuestionStatus, Result, SessionLiveInfo, SessionQuestion, SessionQuestionUpvote, SessionQuestionViewBuilder, UnitOfWork | | 15 | `GetMyPointsHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 10 | Entry, ErrorType, GetMyPointsHandler, GetMyPointsQuery, HandlerTestBase, ICurrentUserService, LeaderboardOptIn, PointsActivityType, PointsEntry, UnitOfWork | -| 15 | `GetOpenPollsHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 11 | CountingQueryableExecutor, ErrorType, GetOpenPollsHandler, GetOpenPollsQuery, HandlerTestBase, InMemoryQueryableExecutor, IQueryableExecutor, LivePoll, LivePollResultsBuilder, LivePollVote, UnitOfWork | +| 15 | `GetOpenPollsHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 12 | CountingQueryableExecutor, ErrorType, GetOpenPollsHandler, GetOpenPollsQuery, HandlerTestBase, InMemoryQueryableExecutor, IQueryableExecutor, LivePoll, LivePollResultsBuilder, LivePollVote, TestSupport, UnitOfWork | | 15 | `GetOrCreateMyBadgeHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 9 | AttendeeBadge, ErrorType, GetOrCreateMyBadgeCommand, GetOrCreateMyBadgeHandler, HandlerMocks, HandlerTestBase, ICurrentUserService, IUniqueConstraintViolationDetector, UnitOfWork | | 15 | `GetPointsOverviewHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 8 | Entry, GetPointsOverviewHandler, GetPointsOverviewQuery, HandlerTestBase, PointsActivityType, PointsEntry, PointsEntryDTO, UnitOfWork | | 15 | `GetPollResultsHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 18 | Error, ErrorType, EventLiveInfo, GetPollResultsHandler, GetPollResultsQuery, HandlerMocks, HandlerTestBase, IEventLiveValidationService, InMemoryQueryableExecutor, IReadRepository, LivePoll, LivePollResultsBuilder, LivePollStatus, LivePollVote, QuestionModerationDefault, Result, SessionLiveInfo, UnitOfWork | | 15 | `GetSessionManagePollsHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 15 | Error, ErrorType, GetSessionManagePollsHandler, GetSessionManagePollsQuery, HandlerMocks, HandlerTestBase, IEventLiveValidationService, IRepository, LivePoll, LivePollDTOMapper, LivePollStatus, QuestionModerationDefault, Result, SessionLiveInfo, UnitOfWork | -| 15 | `GetSessionQuestionsHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 9 | GetSessionQuestionsHandler, GetSessionQuestionsQuery, HandlerTestBase, InMemoryQueryableExecutor, QuestionStatus, SessionQuestion, SessionQuestionUpvote, SessionQuestionViewBuilder, UnitOfWork | +| 15 | `GetSessionQuestionsHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 10 | GetSessionQuestionsHandler, GetSessionQuestionsQuery, HandlerTestBase, InMemoryQueryableExecutor, QuestionStatus, SessionQuestion, SessionQuestionUpvote, SessionQuestionViewBuilder, TestSupport, UnitOfWork | | 15 | `GetUserBookmarksHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 11 | Error, GetUserBookmarksHandler, GetUserBookmarksQuery, HandlerMocks, HandlerTestBase, IQueryableExecutor, ISessionBookmarkValidationService, Result, UnitOfWork, UserSessionBookmark, UserSessionBookmarkDTOMapper | | 15 | `LivePollOptionNavigationPopulatorTests` | MMCA.ADC.Engagement.Application.Tests | 6 | HandlerTestBase, INavigationPopulator, LivePollOption, LivePollOptionNavigationPopulator, NavigationMetadata, UnitOfWork | | 15 | `ManualCheckInHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 17 | AttendeeCheckedIn, CheckIn, CheckInScope, CheckInScopeNames, ErrorType, EventLiveInfo, FakeTimeProvider, HandlerMocks, HandlerTestBase, ICurrentUserService, IEventLiveValidationService, ManualCheckInHandler, ManualCheckInRequest, QuestionModerationDefault, Result, SessionLiveInfo, UnitOfWork | @@ -4982,16 +5124,21 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 15 | `SessionQuestionSubmittedPointsHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 13 | DomainEntityState, Error, HandlerTestBase, IPointsAwarder, PointsActivityType, QuestionStatus, RecordingPointsAwarder, Result, SessionQuestion, SessionQuestionChanged, SessionQuestionSubmittedPointsHandler, TestSupport, ThrowingPointsAwarder | | 15 | `SetLeaderboardParticipationHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 9 | ErrorType, HandlerMocks, HandlerTestBase, ICurrentUserService, IUniqueConstraintViolationDetector, LeaderboardOptIn, SetLeaderboardParticipationHandler, SetLeaderboardParticipationRequest, UnitOfWork | | 15 | `SubmitQuestionHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 25 | Error, ErrorType, FakeTimeProvider, HandlerMocks, HandlerTestBase, IDistributedLock, IEventLiveValidationService, ILiveChannelPublishQueue, InMemoryQueryableExecutor, IReadRepository, LiveChannelPublishWorkItem, QuestionModerationDefault, QuestionStatus, Result, SessionLiveInfo, SessionQuestion, SessionQuestionApprovedPayload, SessionQuestionChannel, SessionQuestionInvariants, SessionQuestionPendingCountChangedPayload …(+5) | -| 15 | `ToggleUpvoteHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 11 | ErrorType, FakeTimeProvider, HandlerMocks, HandlerTestBase, IRepository, QuestionStatus, SessionQuestion, SessionQuestionUpvote, ToggleUpvoteCommand, ToggleUpvoteHandler, UnitOfWork | +| 15 | `ToggleUpvoteHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 13 | ErrorType, FakeTimeProvider, HandlerMocks, HandlerTestBase, IRepository, IUniqueConstraintViolationDetector, QuestionStatus, SessionQuestion, SessionQuestionUpvote, TestSupport, ToggleUpvoteCommand, ToggleUpvoteHandler, UnitOfWork | +| 15 | `UserDeletedBadgeHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 7 | AttendeeBadge, HandlerTestBase, IRepository, TestSupport, UnitOfWork, UserDeleted, UserDeletedBadgeHandler | +| 15 | `UserDeletedBookmarksHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 7 | HandlerTestBase, IRepository, TestSupport, UnitOfWork, UserDeleted, UserDeletedBookmarksHandler, UserSessionBookmark | | 15 | `UserDeletedPointsHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 7 | HandlerTestBase, IRepository, LeaderboardOptIn, TestSupport, UnitOfWork, UserDeleted, UserDeletedPointsHandler | +| 15 | `UserDeletedSessionQuestionsHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 10 | HandlerTestBase, IRepository, Question, QuestionStatus, SessionQuestion, SessionQuestionUpvote, TestSupport, UnitOfWork, UserDeleted, UserDeletedSessionQuestionsHandler | +| 15 | `UserDeletedVotesHandlerTests` | MMCA.ADC.Engagement.Application.Tests | 7 | HandlerTestBase, IRepository, LivePollVote, TestSupport, UnitOfWork, UserDeleted, UserDeletedVotesHandler | +| 15 | `EngagementTestDbContext` | MMCA.ADC.Engagement.Infrastructure.Tests | 12 | LivePoll, LivePollConfiguration, LivePollOption, LivePollOptionConfiguration, LivePollVote, LivePollVoteConfiguration, SessionQuestion, SessionQuestionConfiguration, SessionQuestionUpvote, SessionQuestionUpvoteConfiguration, UserSessionBookmark, UserSessionBookmarkConfiguration | | 15 | `EngagementIntegrationTestFixture` | MMCA.ADC.Engagement.IntegrationTests | 4 | EngagementTestWebApplicationFactory, JwtTokenGenerator, Program, SqlServerIntegrationTestFixtureBase | | 15 | `GatewayHardeningTests` | MMCA.ADC.Gateway.Tests | 3 | GatewayApplicationFactory, MmcaGatewayHardeningTestsBase, Program | | 15 | `RouteMapTests` | MMCA.ADC.Gateway.Tests | 5 | ClusterProfile, GatewayRoutePolicyPartition, GatewaySettings, RecordingHttpForwarder, RouteMapApplicationFactory | | 15 | `DependencyInjection` | MMCA.ADC.Identity.Application | 16 | ApplicationSettings, AttendeeQueryService, AuthenticationService, AuthenticationValidators, ClassReference, ClassReference, EngagementUserDataExportSection, IAttendeeQueryService, IAuthenticationService, ISoftDeletedUserValidator, IUserAdministrationService, NotificationUserDataExportSection, SoftDeletedUserValidator, User, UserAdminDTO, UserAdministrationService | | 15 | `SetUserAvatarHandler` | MMCA.ADC.Identity.Application | 13 | DeleteAvatarBlobInternalCommand, Error, IFileStorageService, IImageProcessor, IInternalCommandScheduler, ImageContentSniffer, IUnitOfWork, MutateEntityPayloadHandlerBase, MutationContext, Result, SetUserAvatarCommand, User, UserAvatarDTO | | 15 | `AttendeeQueryServiceTests` | MMCA.ADC.Identity.Application.Tests | 6 | AttendeeQueryService, HandlerTestBase, IAttendeeQueryService, IRepository, UnitOfWork, User | -| 15 | `AuthenticationServiceTests` | MMCA.ADC.Identity.Application.Tests | 24 | AuthClaimTypes, AuthenticationResponse, AuthenticationService, AuthenticationValidators, EmailConfirmationSettings, Error, ErrorType, IExternalLoginEmailVerifier, ILoginProtectionService, InMemoryRefreshSessionStore, IPasswordHasher, IRepository, ITokenService, IUnitOfWork, LoginRequest, RefreshSession, RefreshSessionSettings, RefreshTokenRequest, RegisterRequest, Result …(+4) | -| 15 | `ChangePasswordHandlerTests` | MMCA.ADC.Identity.Application.Tests | 11 | ChangePasswordCommand, ChangePasswordHandler, ChangePasswordRequest, ErrorType, HandlerTestBase, IPasswordHasher, IRefreshSessionStore, IRepository, UnitOfWork, User, UserRole | +| 15 | `AuthenticationServiceTests` | MMCA.ADC.Identity.Application.Tests | 25 | AuthClaimTypes, AuthenticationResponse, AuthenticationService, AuthenticationValidators, AuthSessionIssuer, EmailConfirmationSettings, Error, ErrorType, IExternalLoginEmailVerifier, ILoginProtectionService, InMemoryRefreshSessionStore, IPasswordHasher, IRepository, ITokenService, IUnitOfWork, LoginRequest, RefreshSession, RefreshSessionSettings, RefreshTokenRequest, RegisterRequest …(+5) | +| 15 | `ChangePasswordHandlerTests` | MMCA.ADC.Identity.Application.Tests | 13 | ChangePasswordCommand, ChangePasswordHandler, ChangePasswordRequest, ErrorType, HandlerTestBase, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, IRepository, Result, UnitOfWork, User, UserRole | | 15 | `ChangePreferencesHandlerTests` | MMCA.ADC.Identity.Application.Tests | 9 | ChangePreferencesCommand, ChangePreferencesHandler, ChangePreferencesRequest, ErrorType, HandlerTestBase, IRepository, UnitOfWork, User, UserRole | | 15 | `ConfirmEmailHandlerTests` | MMCA.ADC.Identity.Application.Tests | 13 | ConfirmEmailCommand, ConfirmEmailHandler, ConfirmEmailRequest, Email, EmailConfirmationErrors, Error, HandlerTestBase, IEmailConfirmationTokenService, IRepository, Result, UnitOfWork, User, UserRole | | 15 | `DeleteUserHandlerTests` | MMCA.ADC.Identity.Application.Tests | 16 | DeleteAvatarBlobInternalCommand, DeleteUserCommand, DeleteUserHandler, Error, ErrorType, FakeTimeProvider, HandlerTestBase, ICacheService, IInternalCommand, IInternalCommandScheduler, IRepository, Result, SoftDeletedUserCache, UnitOfWork, User, UserRole | @@ -5003,6 +5150,7 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 15 | `SendEmailConfirmationHandlerTests` | MMCA.ADC.Identity.Application.Tests | 13 | Email, EmailConfirmationSettings, HandlerTestBase, IEmailConfirmationTokenService, IEmailSender, IRepository, Result, SendEmailConfirmationCommand, SendEmailConfirmationHandler, SendEmailConfirmationRequest, UnitOfWork, User, UserRole | | 15 | `UserAdministrationServiceTests` | MMCA.ADC.Identity.Application.Tests | 13 | ErrorType, FakeTimeProvider, HandlerTestBase, IQueryableExecutor, IRefreshSessionStore, IRepository, RefreshSession, UnitOfWork, User, UserAdminDTO, UserAdministrationQuery, UserAdministrationService, UserRole | | 15 | `IdentityModuleDbSeeder` | MMCA.ADC.Identity.Infrastructure | 9 | Email, IdentityModuleDbSeederBase, IPasswordHasher, IUnitOfWork, Result, SeedAccount, UnitOfWork, User, UserRole | +| 15 | `IdentityTestDbContext` | MMCA.ADC.Identity.Infrastructure.Tests | 4 | DataSource, SoftDeleteUniqueIndexConvention, User, UserConfiguration | | 15 | `IdentityIntegrationTestFixture` | MMCA.ADC.Identity.IntegrationTests | 4 | IdentityTestWebApplicationFactory, JwtTokenGenerator, Program, SqlServerIntegrationTestFixtureBase | | 15 | `UserNotificationExportServiceTests` | MMCA.ADC.Notification.Application.Tests | 8 | HandlerTestBase, InMemoryQueryableExecutor, IRepository, IUserNotificationExportService, PushNotification, UnitOfWork, UserNotification, UserNotificationExportService | | 15 | `NotificationIntegrationTestFixture` | MMCA.ADC.Notification.IntegrationTests | 4 | JwtTokenGenerator, NotificationTestWebApplicationFactory, Program, SqlServerIntegrationTestFixtureBase | @@ -5013,31 +5161,53 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 15 | `SerilogBootstrapTests` | MMCA.ADC.UI.Web.Tests | 1 | ConferenceUiHostApplicationFactory | | 15 | `UiRateLimitingTests` | MMCA.ADC.UI.Web.Tests | 2 | ConferenceUiHostApplicationFactory, UiRateLimitingSettings | | 15 | `AuthControllerBase` | MMCA.Common.API | 12 | ApiControllerBase, AuthenticationResponse, AuthenticationService, CurrentUserService, IAuthenticationService, ICurrentUserService, LoginRequest, RefreshSessionSummaryResponse, RefreshTokenRequest, RegisterRequest, User, WebApplicationBuilderExtensions | +| 15 | `DatabaseInitializationExtensions` | MMCA.Common.API | 10 | ApplicationSettings, DataSourceKey, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, ModuleHostContext, ModuleLoader, TenancySettings, TenantDataSourceTarget, TenantDataSourceTargets | | 15 | `PasswordResetAuthControllerBase` | MMCA.Common.API | 9 | ApiControllerBase, ForgotPasswordHandler, ForgotPasswordRequest, ICommandHandler, ICommandWithRequest, ResetPasswordHandler, ResetPasswordRequest, Result, WebApplicationBuilderExtensions | +| 15 | `CookieSessionRefresherTests` | MMCA.Common.API.Tests | 10 | AuthenticationResponse, CookieSessionRefresher, CookieTokenReader, FakeTimeProvider, KeyedSemaphoreStripe, RefresherHarness, SessionCookieEndpoints, SessionRefreshOutcome, SessionRefreshStatus, SessionTokenResult | +| 15 | `DatabaseInitializationExtensionsTests` | MMCA.Common.API.Tests | 30 | ApplicationSettings, AuditSaveChangesInterceptor, ConnectionStringSettings, CreateMigrationProofTable, DataSource, DataSourceEntrySettings, DataSourceResolver, DataSourcesSettings, DbContextFactory, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, FixedAssemblyProvider, ICurrentUserService, IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, InitTestMigratedWidget, InitTestWidget …(+10) | +| 15 | `FixedAssemblyProvider` | MMCA.Common.API.Tests | 2 | DatabaseInitializationExtensionsTests, IEntityConfigurationAssemblyProvider | +| 15 | `MarkAllNotificationsReadHandlerTests` | MMCA.Common.Application.Tests | 6 | FixedTimeProvider, Harness, MarkAllNotificationsReadCommand, PushNotification, Result, UserNotification | | 15 | `MutateAttemptScopeTests` | MMCA.Common.Application.Tests | 5 | IRepository, IUnitOfWork, OrderAggregate, RenameOrderCommand, TestRetryingRenameHandler | | 15 | `TestRenameOrderPayloadHandler` | MMCA.Common.Application.Tests | 7 | IUnitOfWork, MutateEntityPayloadHandlerBase, MutationContext, OrderAggregate, RenameOrderCommand, RenameOrderResult, Result | -| 15 | `AuditTrailCleanupJobTests` | MMCA.Common.Infrastructure.Tests | 13 | ApplicationDbContext, AuditedThing, AuditTrailCleanupJob, AuditTrailEntry, AuditTrailSettings, AuditTrailTestContext, AuditTrailTestHarness, DataSource, DataSourceKey, FakeTimeProvider, IDbContextFactory, IEntityDataSourceRegistry, SchedulerTestHarness | +| 15 | `FrameworkTableTargets` | MMCA.Common.Infrastructure | 8 | DataSource, DataSourceEngines, DataSourceKey, IDataSourceResolver, IEntityDataSourceRegistry, TenancySettings, TenantDataSourceTarget, TenantDataSourceTargets | +| 15 | `FixedAssemblyProvider` | MMCA.Common.Infrastructure.SQLServer.Tests | 2 | IEntityConfigurationAssemblyProvider, SQLServerPersistenceTests | +| 15 | `SQLServerPersistenceTests` | MMCA.Common.Infrastructure.SQLServer.Tests | 23 | ApplicationDbContext, AuditSaveChangesInterceptor, ConnectionStringSettings, DataSource, DataSourceKey, DataSourceResolver, DataSourcesSettings, DbContextFactory, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, FixedAssemblyProvider, FixedCurrentUserService, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NoTenantContext, OutboxMessage, OutboxSignal, PhysicalDbContextFactory …(+3) | | 15 | `AuditTrailReaderTests` | MMCA.Common.Infrastructure.Tests | 12 | ApplicationDbContext, AuditTrailEntry, AuditTrailReader, AuditTrailSettings, AuditTrailTestContext, AuditTrailTestHarness, DataSource, DataSourceKey, FakeTimeProvider, IDataSourceResolver, IDbContextFactory, SchedulerTestHarness | -| 15 | `EntityDataSourceRegistryTests` | MMCA.Common.Infrastructure.Tests | 15 | ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourceResolver, DataSourcesSettings, EntityDataSourceRegistry, FixedAssemblyProvider, NamespaceConventions, PushNotification, RegistryDuplicate, RegistryInvoice, RegistryOrder, RegistrySqlServerEntity, RegistryUnattributed | -| 15 | `InternalCommandAdministrationTests` | MMCA.Common.Infrastructure.Tests | 11 | DataSourceKey, DefaultDataSourceResolver, EmptyEntityDataSourceRegistry, FakeTimeProvider, IDbContextFactory, IInternalCommandSignal, InternalCommandAdministration, InternalCommandMessage, InternalCommandTestContext, InternalCommandTestHarness, RecordingCommand | -| 15 | `InternalCommandCleanupServiceTests` | MMCA.Common.Infrastructure.Tests | 12 | DataSource, DataSourceKey, DefaultDataSourceResolver, EmptyEntityDataSourceRegistry, FakeTimeProvider, IDbContextFactory, InternalCommandCleanupService, InternalCommandMessage, InternalCommandsSettings, InternalCommandTestContext, InternalCommandTestHarness, RecordingCommand | -| 15 | `InternalCommandProcessorTests` | MMCA.Common.Infrastructure.Tests | 11 | Error, ExecutionLog, FakeTimeProvider, IInternalCommandSignal, InternalCommandMessage, InternalCommandProcessor, InternalCommandTestContext, InternalCommandTestHarness, RecordingCommand, Result, StubCurrentUserService | -| 15 | `InternalCommandSchedulerTests` | MMCA.Common.Infrastructure.Tests | 9 | FakeTimeProvider, ICurrentUserService, IInternalCommandSignal, InternalCommandMessage, InternalCommandScheduler, InternalCommandTestContext, InternalCommandTestHarness, RecordingCommand, StubCurrentUserService | -| 15 | `OutboxCleanupServiceTests` | MMCA.Common.Infrastructure.Tests | 17 | ApplicationDbContext, CleanupTestContext, DataSource, DataSourceKey, FakeClockLoop, FakeTimeProvider, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, InboxMessage, MessageBusSettings, Mocks, Mocks, OutboxCleanupService, OutboxMessage, OutboxSettings, Payload | -| 15 | `OutboxProcessorExecuteAsyncTests` | MMCA.Common.Infrastructure.Tests | 9 | DataSource, DataSourceKey, DependencyInjection, FakeTimeProvider, IDataSourceResolver, IEntityDataSourceRegistry, IOutboxSignal, OutboxProcessor, OutboxSettings | +| 15 | `CosmosConfigurationPortabilityTests` | MMCA.Common.Infrastructure.Tests | 17 | AuditSaveChangesInterceptor, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceResolver, DataSourcesSettings, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, FixedAssemblyProvider, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, OutboxSignal, PhysicalDbContextFactory, PortablePrincipal, PortableThing | +| 15 | `DbContextFactoryAdditionalTests` | MMCA.Common.Infrastructure.Tests | 10 | DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, ICurrentUserService, IEntityDataSourceRegistry, IPhysicalDbContextFactory, ITenantContext, MidSaveContextCreatingDbContext, TenancySettings | +| 15 | `DbContextFactoryCommitAmbiguityTests` | MMCA.Common.Infrastructure.Tests | 16 | CommitFailingDbContext, DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, ICurrentUserService, IDomainEvent, IDomainEventDispatcher, IEntityDataSourceRegistry, IPhysicalDbContextFactory, ITenantContext, Result, TenancySettings, TestAggregate, TestLocalEvent, TransactionCommitAmbiguousException | +| 15 | `DbContextFactoryMigrationTargetTests` | MMCA.Common.Infrastructure.Tests | 22 | AuditSaveChangesInterceptor, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourceResolver, DataSourcesSettings, DbContextFactory, DomainEventSaveChangesInterceptor, FixedSourcesRegistry, FixedSourcesRegistry, ICurrentUserService, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, ITenantContext, NoConfigurationAssemblyProvider, NoConfigurationAssemblyProvider, OutboxSignal …(+2) | +| 15 | `DbContextFactorySaveIntegrityTests` | MMCA.Common.Infrastructure.Tests | 14 | DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, ICurrentUserService, IDomainEvent, IDomainEventDispatcher, IEntityDataSourceRegistry, IntegrityAggregate, IntegrityEvent, IntegrityTestDbContext, IPhysicalDbContextFactory, ITenantContext, TenancySettings | +| 15 | `DbContextFactoryTenantTests` | MMCA.Common.Infrastructure.Tests | 16 | ApplicationDbContext, DataSource, DataSourceKey, DbContextFactory, ICurrentUserService, IDataSourceResolver, IEntityDataSourceRegistry, IPhysicalDbContextFactory, ITenantContext, MutableTenantContext, PhysicalDataSource, TenancySettings, TenantContext, TenantDataSourceOverrideSettings, TenantEntrySettings, TenantTestContext | +| 15 | `DbContextFactoryTests` | MMCA.Common.Infrastructure.Tests | 10 | ApplicationDbContext, DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, ICurrentUserService, IEntityDataSourceRegistry, IPhysicalDbContextFactory, ITenantContext, TenancySettings | +| 15 | `DesignTimeDbContextHelperTests` | MMCA.Common.Infrastructure.Tests | 12 | ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DesignAlphaEntity, DesignBetaEntity, DesignPostgreSQLEntity, DesignSqliteEntity, DesignTimeDbContextHelper, DesignTimeDbContextOptions, PermissionGrant, RefreshSession | +| 15 | `DomainEventSaveChangesInterceptorOutboxDisabledTests` | MMCA.Common.Infrastructure.Tests | 11 | DomainEventSaveChangesInterceptor, DomainEventSaveChangesInterceptorOutboxRoutingTests, IDomainEvent, IDomainEventDispatcher, IOutboxSignal, MessageBusSettings, OutboxMessage, OutboxRoutingTestDbContext, TestAggregate, TestIntegrationEvent, TestLocalEvent | +| 15 | `EFRepositoryAuditStampTests` | MMCA.Common.Infrastructure.Tests | 15 | DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, EFRepository, ICurrentUserService, IDataSourceService, IEntityDataSourceRegistry, IPhysicalDbContextFactory, IRepositoryFactory, ITenantContext, StampedEntity, StampTestDbContext, TenancySettings, UnitOfWork | +| 15 | `FixedAssemblyProvider` | MMCA.Common.Infrastructure.Tests | 3 | CosmosConfigurationPortabilityTests, IEntityConfigurationAssemblyProvider, MultiSourceSqliteIntegrationTests | +| 15 | `MigrationApplyProofTests` | MMCA.Common.Infrastructure.Tests | 21 | AuditSaveChangesInterceptor, ConnectionStringSettings, CreateMigrationProofTable, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourceResolver, DataSourcesSettings, DbContextFactory, DomainEventSaveChangesInterceptor, FixedSourcesRegistry, ICurrentUserService, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, ITenantContext, NoConfigurationAssemblyProvider, OutboxSignal, PhysicalDbContextFactory …(+1) | +| 15 | `MultiSourceSqliteIntegrationTests` | MMCA.Common.Infrastructure.Tests | 28 | ApplicationSettings, AuditSaveChangesInterceptor, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceResolver, DataSourceService, DataSourcesSettings, DbContextFactory, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, FixedAssemblyProvider, ICurrentUserService, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, ITenantContext, MultiSourceCustomer, MultiSourceOrder …(+8) | +| 15 | `NotificationConfigurationEngineTests` | MMCA.Common.Infrastructure.Tests | 7 | ApplicationDbContext, ConnectionStringSettings, DataSourceEntrySettings, DesignTimeDbContextHelper, PushNotification, UserNotification, UserNotificationConfiguration | +| 15 | `PushNotificationTestDbContext` | MMCA.Common.Infrastructure.Tests | 1 | PushNotificationConfiguration | | 15 | `RefreshSessionCleanupServiceTests` | MMCA.Common.Infrastructure.Tests | 14 | AuditSaveChangesInterceptor, DataSource, DataSourceKey, DefaultDataSourceResolver, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, RefreshSession, RefreshSessionCleanupService, RefreshSessionSettings, SweepHarness | +| 15 | `RepositoryFactoryTests` | MMCA.Common.Infrastructure.Tests | 13 | ApplicationSettings, EFReadRepository, EFReadRepositoryDecorator, EFRepository, EFRepositoryDecorator, FakeAggregate, FakeAggregate, FakeEntity, FakeEntity, IReadRepository, IRepository, RepositoryFactory, TestDbContext | +| 15 | `SqliteTestDbContext` | MMCA.Common.Infrastructure.Tests | 2 | SqliteTestEntity, SqliteTestEntityConfig | | 15 | `TestIdentityModuleDbSeeder` | MMCA.Common.Infrastructure.Tests | 8 | Email, Error, IdentityModuleDbSeederBase, IPasswordHasher, IUnitOfWork, Result, SeedAccount, TestSeedUser | +| 15 | `PagedQueryLoadTests` | MMCA.Common.LoadTests | 7 | LoadItem, LoadItemDTO, LoadResults, Measured, PagedCollectionResult, PagedQuery, PagedQueryFixture | | 15 | `HandlerTestBaseTests` | MMCA.Common.Testing.Tests | 5 | FakeHandler, HandlerTestBase, TestAggregate, TestChildEntity, UnitOfWork | +| 15 | `SameOriginApiProxyEndpointExtensions` | MMCA.Common.UI.Web | 8 | HandoffSessionCookieSync, HandoffTokenRefresher, ISessionCookieSync, ITokenRefresher, SameOriginApiProxyEndpoint, SameOriginApiProxyMarker, SameOriginApiProxySettings, SessionHandoffEndpoints | +| 16 | `DeleteBehaviorConventionTests` | MMCA.ADC.Architecture.Tests | 3 | ArchitectureRules, DeleteBehaviorConventionTestsBase, ServiceModels | | 16 | `AddRoomHandlerTests` | MMCA.ADC.Conference.Application.Tests | 12 | AddRoomCommand, AddRoomHandler, ErrorType, Event, EventInvariants, HandlerTestBase, IReadRepository, IRepository, IUnitOfWork, Room, RoomDTOMapper, UnitOfWork | | 16 | `UpdateEventHandlerTests` | MMCA.ADC.Conference.Application.Tests | 13 | ErrorType, Event, EventDTOMapper, EventQuestionAnswerDTOMapper, EventSpeakerDTOMapper, EventUpdateRequest, HandlerTestBase, IRepository, RoomDTOMapper, Session, UnitOfWork, UpdateEventCommand, UpdateEventHandler | | 16 | `UpdateSessionHandlerTests` | MMCA.ADC.Conference.Application.Tests | 13 | ErrorType, Event, HandlerTestBase, IRepository, Session, SessionCategoryItemDTOMapper, SessionDTOMapper, SessionQuestionAnswerDTOMapper, SessionSpeakerDTOMapper, SessionUpdateRequest, UnitOfWork, UpdateSessionCommand, UpdateSessionHandler | | 16 | `ConferenceIntegrationTestCollection` | MMCA.ADC.Conference.IntegrationTests | 1 | ConferenceIntegrationTestFixture | | 16 | `CrossServiceCollection` | MMCA.ADC.CrossService.IntegrationTests | 1 | CrossServiceFixture | +| 16 | `EngagementEntityConfigurationTests` | MMCA.ADC.Engagement.Infrastructure.Tests | 9 | EngagementTestDbContext, LivePoll, LivePollInvariants, LivePollOption, LivePollVote, SessionQuestion, SessionQuestionInvariants, SessionQuestionUpvote, UserSessionBookmark | | 16 | `EngagementIntegrationTestCollection` | MMCA.ADC.Engagement.IntegrationTests | 1 | EngagementIntegrationTestFixture | | 16 | `IdentityModuleSeeder` | MMCA.ADC.Identity.API | 4 | IdentityModuleDbSeeder, IModuleSeeder, IPasswordHasher, IUnitOfWork | | 16 | `PasswordResetController` | MMCA.ADC.Identity.API | 8 | ForgotPasswordCommand, ForgotPasswordRequest, ICommandHandler, PasswordResetAuthControllerBase, ResetPasswordCommand, ResetPasswordRequest, Result, Route | | 16 | `RemoveUserAvatarHandler` | MMCA.ADC.Identity.Application | 9 | DeleteAvatarBlobInternalCommand, IInternalCommandScheduler, IUnitOfWork, MutateEntityHandlerBase, MutationContext, RemoveUserAvatarCommand, Result, SetUserAvatarHandler, User | | 16 | `SetUserAvatarHandlerTests` | MMCA.ADC.Identity.Application.Tests | 15 | DeleteAvatarBlobInternalCommand, Error, ErrorType, IFileStorageService, IImageProcessor, IInternalCommand, IInternalCommandScheduler, ImageContentSniffer, IRepository, IUnitOfWork, Result, SetUserAvatarCommand, SetUserAvatarHandler, User, UserRole | +| 16 | `IdentityEntityConfigurationTests` | MMCA.ADC.Identity.Infrastructure.Tests | 3 | IdentityTestDbContext, User, UserInvariants | | 16 | `IdentityModuleDbSeederTests` | MMCA.ADC.Identity.Infrastructure.Tests | 6 | IdentityModuleDbSeeder, IPasswordHasher, IRepository, IUnitOfWork, SeederMocks, User | | 16 | `IdentityIntegrationTestCollection` | MMCA.ADC.Identity.IntegrationTests | 1 | IdentityIntegrationTestFixture | | 16 | `JwksEnabledIdentityFixture` | MMCA.ADC.Identity.IntegrationTests | 2 | IdentityIntegrationTestFixture, JwtTokenGenerator | @@ -5047,7 +5217,17 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 16 | `TestAuthController` | MMCA.Common.API.Tests | 3 | AuthControllerBase, IAuthenticationService, ICurrentUserService | | 16 | `TestPasswordResetController` | MMCA.Common.API.Tests | 7 | ForgotPasswordRequest, ICommandHandler, PasswordResetAuthControllerBase, ResetPasswordRequest, Result, TestForgotPasswordCommand, TestResetPasswordCommand | | 16 | `MutationContextHandlerTests` | MMCA.Common.Application.Tests | 8 | IRepository, IUnitOfWork, OrderAggregate, RenameOrderCommand, TestNoMutationHandler, TestRenameOrderHandler, TestRenameOrderPayloadHandler, TestSkippingRenameHandler | +| 16 | `AuditTrailCleanupJob` | MMCA.Common.Infrastructure | 6 | AuditTrailEntry, AuditTrailSettings, FrameworkTableTargets, IDbContextFactory, IScheduledJob, TenantDataSourceTarget | +| 16 | `InternalCommandAdministration` | MMCA.Common.Infrastructure | 11 | ApplicationDbContext, Error, FrameworkTableTargets, IDbContextFactory, IInternalCommandAdministration, IInternalCommandSignal, InternalCommandDeadLetter, InternalCommandMessage, InternalCommandsSettings, Result, TenantDataSourceTarget | +| 16 | `InternalCommandCleanupService` | MMCA.Common.Infrastructure | 7 | ApplicationDbContext, FrameworkTableTargets, IDbContextFactory, InternalCommandMessage, InternalCommandsSettings, PeriodicBackgroundService, TenantDataSourceTarget | +| 16 | `InternalCommandProcessor` | MMCA.Common.Infrastructure | 16 | Activity, AmbientOrigin, ApplicationDbContext, ColumnWidth, FrameworkTableTargets, IDbContextFactory, IInternalCommand, IInternalCommandSignal, InternalCommandCycleResult, InternalCommandDispatcher, InternalCommandMessage, InternalCommandMetrics, InternalCommandsSettings, PollingLoop, Result, TenantDataSourceTarget | +| 16 | `OutboxAdministration` | MMCA.Common.Infrastructure | 11 | ApplicationDbContext, Error, FrameworkTableTargets, IDbContextFactory, IOutboxAdministration, IOutboxSignal, OutboxDeadLetter, OutboxMessage, OutboxSettings, Result, TenantDataSourceTarget | +| 16 | `OutboxCleanupService` | MMCA.Common.Infrastructure | 9 | ApplicationDbContext, FrameworkTableTargets, IDbContextFactory, InboxMessage, MessageBusSettings, OutboxMessage, OutboxSettings, PeriodicBackgroundService, TenantDataSourceTarget | +| 16 | `OutboxProcessor` | MMCA.Common.Infrastructure | 19 | Activity, ApplicationDbContext, BrokerMetrics, BrokerResilienceDefaults, ColumnWidth, DataSourceKey, Event, FrameworkTableTargets, IDbContextFactory, IDomainEventDispatcher, IIntegrationEvent, IMessageBus, IOutboxSignal, OutboxCycleResult, OutboxMessage, OutboxMetrics, OutboxSettings, PollingLoop, TenantDataSourceTarget | +| 16 | `EntityDataSourceRegistryTests` | MMCA.Common.Infrastructure.Tests | 15 | ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourceResolver, DataSourcesSettings, EntityDataSourceRegistry, FixedAssemblyProvider, NamespaceConventions, PushNotification, RegistryDuplicate, RegistryInvoice, RegistryOrder, RegistrySqlServerEntity, RegistryUnattributed | +| 16 | `EntityTypeConfigurationTests` | MMCA.Common.Infrastructure.Tests | 2 | SqliteTestDbContext, SqliteTestEntity | | 16 | `IdentityModuleDbSeederBaseTests` | MMCA.Common.Infrastructure.Tests | 7 | IPasswordHasher, IRepository, IUnitOfWork, SeedAccount, SeederMocks, TestIdentityModuleDbSeeder, TestSeedUser | +| 16 | `PushNotificationConfigurationTests` | MMCA.Common.Infrastructure.Tests | 2 | PushNotification, PushNotificationTestDbContext | | 16 | `RotationHarness` | MMCA.Common.Infrastructure.Tests | 9 | ApplicationDbContext, DataSourceKey, EmptyEntityDataSourceRegistry, IDataSourceResolver, IDbContextFactory, Participant, RefreshSession, RefreshSessionCleanupServiceTests, RefreshSessionSettings | | 16 | `StoreHarness` | MMCA.Common.Infrastructure.Tests | 9 | ApplicationDbContext, DataSourceKey, EmptyEntityDataSourceRegistry, IDataSourceResolver, IDbContextFactory, IRefreshSessionStore, RefreshSession, RefreshSessionCleanupServiceTests, RefreshSessionSettings | | 17 | `ApiVersioningTests` | MMCA.ADC.Conference.IntegrationTests | 3 | ConferenceIntegrationTestCollection, ConferenceIntegrationTestFixture, ServiceInfoVersioningContractTestsBase | @@ -5068,11 +5248,25 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 17 | `OpenApiContractTests` | MMCA.ADC.Notification.IntegrationTests | 3 | NotificationIntegrationTestCollection, NotificationIntegrationTestFixture, OpenApiContractTestsBase | | 17 | `ProblemDetailsContractTests` | MMCA.ADC.Notification.IntegrationTests | 4 | JwtTokenGenerator, NotificationIntegrationTestCollection, NotificationIntegrationTestFixture, ProblemDetailsContractTestsBase | | 17 | `AuthControllerBaseRateLimitTests` | MMCA.Common.API.Tests | 3 | AuthControllerBase, OverridingAuthController, WebApplicationBuilderExtensions | -| 17 | `AuthControllerBaseTests` | MMCA.Common.API.Tests | 13 | AuthClaimTypes, AuthControllerBase, AuthenticationResponse, Error, IAuthenticationService, ICurrentUserService, LoginRequest, NonIdempotentAttribute, RefreshSessionSummaryResponse, RefreshTokenRequest, RegisterRequest, Result, TestAuthController | +| 17 | `AuthControllerBaseTests` | MMCA.Common.API.Tests | 14 | AuthClaimTypes, AuthControllerBase, AuthenticationResponse, Error, IAuthenticationService, ICurrentUserService, IdempotentAttribute, LoginRequest, NonIdempotentAttribute, RefreshSessionSummaryResponse, RefreshTokenRequest, RegisterRequest, Result, TestAuthController | | 17 | `PasswordResetAuthControllerBaseTests` | MMCA.Common.API.Tests | 11 | Error, ForgotPasswordRequest, ICommandHandler, IdempotentAttribute, PasswordResetAuthControllerBase, ResetPasswordRequest, Result, TestForgotPasswordCommand, TestPasswordResetController, TestResetPasswordCommand, WebApplicationBuilderExtensions | | 17 | `TestUserAccountAuthController` | MMCA.Common.API.Tests | 12 | ChangePasswordRequest, ChangePreferencesRequest, GetUserPreferencesQuery, IAuthenticationService, ICommandHandler, ICurrentUserService, IQueryHandler, Result, TestChangePasswordCommand, TestChangePreferencesCommand, UserAccountAuthControllerBase, UserPreferencesResponse | +| 17 | `DependencyInjection` | MMCA.Common.Infrastructure | 173 | ApplicationNamespace, AuditSaveChangesInterceptor, AuditTrailCleanupJob, AuditTrailReader, AuditTrailSaveChangesInterceptor, AuditTrailSettings, AuthSessionIssuer, AzureBlobFileStorageService, AzureNotificationHubDeviceRegistrar, AzureNotificationHubNativePushSender, BrokerEventBus, BrokerMessageBus, CacheKeyNamespace, CacheKeyPrefixOptions, CacheSettings, ClaimBasedUserIdProvider, ClassReference, ConnectionStringSettings, ConnectionStringSettingsValidator, CorrelationContext …(+153) | +| 17 | `AddAuditTrailTests` | MMCA.Common.Infrastructure.Tests | 6 | AuditTrailCleanupJob, AuditTrailReader, AuditTrailSaveChangesInterceptor, AuditTrailSettings, IAuditTrailReader, IScheduledJob | +| 17 | `AuditTrailCleanupJobTests` | MMCA.Common.Infrastructure.Tests | 15 | ApplicationDbContext, AuditedThing, AuditTrailCleanupJob, AuditTrailEntry, AuditTrailSettings, AuditTrailTestContext, AuditTrailTestHarness, DataSource, DataSourceKey, FakeTimeProvider, FrameworkTableTargets, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, SchedulerTestHarness | +| 17 | `DependencyInjectionInfrastructureTests` | MMCA.Common.Infrastructure.Tests | 16 | AuditSaveChangesInterceptor, ConnectionStringSettings, DomainEventSaveChangesInterceptor, EntityConfigurationOptions, IDataSourceService, IEntityConfigurationAssemblyProvider, IQueryableExecutor, IRawSqlQueryExecutor, IRepository, IRepositoryFactory, IUniqueConstraintViolationDetector, IUnitOfWork, OutboxProcessor, OutboxSettings, SmtpSettings, SqlServerUniqueConstraintViolationDetector | +| 17 | `DependencyInjectionOutboxGateTests` | MMCA.Common.Infrastructure.Tests | 3 | OutboxCleanupService, OutboxDisabledNoticeService, OutboxProcessor | | 17 | `EFRefreshSessionStoreFindByIdTests` | MMCA.Common.Infrastructure.Tests | 2 | RefreshSession, StoreHarness | | 17 | `EFRefreshSessionStoreRotationTests` | MMCA.Common.Infrastructure.Tests | 3 | Participant, RefreshSession, RotationHarness | +| 17 | `InternalCommandTestHarness` | MMCA.Common.Infrastructure.Tests | 30 | AnonymousCurrentUserService, ApplicationDbContext, CorrelationContext, DataSource, DataSourceKey, DefaultDataSourceResolver, EmptyEntityDataSourceRegistry, ExecutionLog, FakeTimeProvider, FrameworkTableTargets, ICommandHandler, ICorrelationContext, ICurrentUserService, IDbContextFactory, IInternalCommand, IInternalCommandSignal, ImpersonatingCurrentUserService, InternalCommandMessage, InternalCommandOriginCapture, InternalCommandProcessor …(+10) | +| 17 | `Mocks` | MMCA.Common.Infrastructure.Tests | 3 | IDataSourceResolver, IEntityDataSourceRegistry, OutboxCleanupService | +| 17 | `OutboxAdministrationTests` | MMCA.Common.Infrastructure.Tests | 13 | AdminTestContext, DataSource, DataSourceKey, FrameworkTableTargets, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, IOutboxSignal, OutboxAdministration, OutboxDeadLetter, OutboxMessage, OutboxSettings, Payload | +| 17 | `OutboxProcessorContextRestoreTests` | MMCA.Common.Infrastructure.Tests | 27 | AuditSaveChangesInterceptor, CapturingMessageBus, CorrelationContext, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FrameworkTableTargets, ICorrelationContext, ICurrentUserService, IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IMessageBus, IOutboxSignal, ITenantContext, OutboxMessage, OutboxOrigin …(+7) | +| 17 | `OutboxProcessorOrderingTests` | MMCA.Common.Infrastructure.Tests | 17 | DataSource, DataSourceKey, FakeTimeProvider, FrameworkTableTargets, IDataSourceResolver, IDbContextFactory, IDomainEvent, IDomainEventDispatcher, IEntityDataSourceRegistry, IMessageBus, IOutboxSignal, OrderedTestEvent, OrderingTestContext, OutboxMessage, OutboxProcessor, OutboxSettings, Payload | +| 17 | `OutboxProcessorTests` | MMCA.Common.Infrastructure.Tests | 26 | AuditSaveChangesInterceptor, BrokerResilienceDefaults, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FakeTimeProvider, FrameworkTableTargets, IDataSourceResolver, IDbContextFactory, IDomainEvent, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IIntegrationEvent, IMessageBus, IOutboxSignal, OutboxCycleResult, OutboxMessage, OutboxProcessor …(+6) | +| 17 | `OutboxProcessorWaitTests` | MMCA.Common.Infrastructure.Tests | 1 | OutboxProcessor | +| 17 | `TenantDataSourceTargetTests` | MMCA.Common.Infrastructure.Tests | 15 | DataSource, DataSourceKey, FrameworkTableTargets, IDataSourceResolver, IEntityDataSourceRegistry, IOutboxSignal, MessageBusSettings, OutboxCleanupService, OutboxProcessor, OutboxSettings, TenancySettings, TenantDataSourceOverrideSettings, TenantDataSourceTarget, TenantDataSourceTargets, TenantEntrySettings | +| 17 | `OutboxThroughputLoadTests` | MMCA.Common.LoadTests | 7 | CountingMessageBus, IMessageBus, LoadIntegrationEvent, LoadResults, LoadStack, OutboxMessage, OutboxProcessor | | 18 | `AnonymousAccessDeniedTests` | MMCA.ADC.Conference.IntegrationTests | 2 | ConferenceIntegrationTestBase, ConferenceIntegrationTestFixture | | 18 | `AnonymousConferenceReadTests` | MMCA.ADC.Conference.IntegrationTests | 2 | ConferenceIntegrationTestBase, ConferenceIntegrationTestFixture | | 18 | `AnonymousSessionAssetTests` | MMCA.ADC.Conference.IntegrationTests | 2 | ConferenceIntegrationTestBase, ConferenceIntegrationTestFixture | @@ -5143,4 +5337,11 @@ alias `using`s name a target whose bare name already matches (so they resolve re | 18 | `NotificationControllerTests` | MMCA.ADC.Notification.IntegrationTests | 3 | FakeAttendeeQueryService, NotificationIntegrationTestBase, NotificationIntegrationTestFixture | | 18 | `NotificationHubTests` | MMCA.ADC.Notification.IntegrationTests | 4 | FakeAttendeeQueryService, NotificationHub, NotificationIntegrationTestBase, NotificationIntegrationTestFixture | | 18 | `UserAccountAuthControllerBaseTests` | MMCA.Common.API.Tests | 15 | AuthenticationResponse, ChangePasswordRequest, ChangePreferencesRequest, Error, GetUserPreferencesQuery, IAuthenticationService, ICommandHandler, ICurrentUserService, IQueryHandler, LoginRequest, Result, TestChangePasswordCommand, TestChangePreferencesCommand, TestUserAccountAuthController, UserPreferencesResponse | +| 18 | `DbContextFactoryTransactionTests` | MMCA.Common.Infrastructure.Tests | 23 | DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, Error, FakeTimeProvider, ICurrentUserService, IDomainEvent, IDomainEventDispatcher, IEntityDataSourceRegistry, IInternalCommandSignal, InternalCommandMessage, InternalCommandScheduler, InternalCommandTestHarness, IPhysicalDbContextFactory, ITenantContext, OutboxMessage, RecordingCommand, Result, TenancySettings …(+3) | +| 18 | `InternalCommandAdministrationTests` | MMCA.Common.Infrastructure.Tests | 12 | DataSourceKey, DefaultDataSourceResolver, EmptyEntityDataSourceRegistry, FakeTimeProvider, FrameworkTableTargets, IDbContextFactory, IInternalCommandSignal, InternalCommandAdministration, InternalCommandMessage, InternalCommandTestContext, InternalCommandTestHarness, RecordingCommand | +| 18 | `InternalCommandCleanupServiceTests` | MMCA.Common.Infrastructure.Tests | 13 | DataSource, DataSourceKey, DefaultDataSourceResolver, EmptyEntityDataSourceRegistry, FakeTimeProvider, FrameworkTableTargets, IDbContextFactory, InternalCommandCleanupService, InternalCommandMessage, InternalCommandsSettings, InternalCommandTestContext, InternalCommandTestHarness, RecordingCommand | +| 18 | `InternalCommandProcessorTests` | MMCA.Common.Infrastructure.Tests | 11 | Error, ExecutionLog, FakeTimeProvider, IInternalCommandSignal, InternalCommandMessage, InternalCommandProcessor, InternalCommandTestContext, InternalCommandTestHarness, RecordingCommand, Result, StubCurrentUserService | +| 18 | `InternalCommandSchedulerTests` | MMCA.Common.Infrastructure.Tests | 9 | FakeTimeProvider, ICurrentUserService, IInternalCommandSignal, InternalCommandMessage, InternalCommandScheduler, InternalCommandTestContext, InternalCommandTestHarness, RecordingCommand, StubCurrentUserService | +| 18 | `OutboxCleanupServiceTests` | MMCA.Common.Infrastructure.Tests | 18 | ApplicationDbContext, CleanupTestContext, DataSource, DataSourceKey, FakeClockLoop, FakeTimeProvider, FrameworkTableTargets, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, InboxMessage, MessageBusSettings, Mocks, Mocks, OutboxCleanupService, OutboxMessage, OutboxSettings, Payload | +| 18 | `OutboxProcessorExecuteAsyncTests` | MMCA.Common.Infrastructure.Tests | 10 | DataSource, DataSourceKey, DependencyInjection, FakeTimeProvider, FrameworkTableTargets, IDataSourceResolver, IEntityDataSourceRegistry, IOutboxSignal, OutboxProcessor, OutboxSettings | | 19 | `JwksDiscoveryTests` | MMCA.ADC.Identity.IntegrationTests | 3 | JwksEnabledIdentityFixture, JwksIntegrationTestBase, JwtTokenGenerator | diff --git a/docs-src/onboarding/00-group-taxonomy.md b/docs-src/onboarding/00-group-taxonomy.md index ab2a34cd..84e42d52 100644 --- a/docs-src/onboarding/00-group-taxonomy.md +++ b/docs-src/onboarding/00-group-taxonomy.md @@ -1,6 +1,6 @@ # Phase 1b - Functional Group Taxonomy -This is the **primary axis** of the guide. Every one of the **5,034** distinct first-party type +This is the **primary axis** of the guide. Every one of the **5,234** distinct first-party type nodes from [`00-inventory.md`](00-inventory.md) is assigned to **exactly one** functional group - its primary *home*: the capability or cross-cutting concern it most exists to serve. A type used across many groups (e.g. `Result`, the entity base) lives in the one foundational group that @@ -56,33 +56,33 @@ disclosure) and is cross-linked in the chapter. | G01 | **Result & Error Handling**
group-01-result-error-handling.md | 16 | L0-L3 | The Result/Error railway that every operation returns instead of throwing; pagination result shapes. | | G02 | **Domain Building Blocks (Entities, Value Objects, Aggregates)**
group-02-domain-building-blocks.md | 36 | L0-L5 | The DDD primitives: entity/aggregate base classes, audit fields, value objects + invariants, domain markers, attributes, identifier aliases. | | G03 | **Querying: Specifications, Filtering & the Entity Query Service**
group-03-querying-specifications.md | 42 | L0-L8 | Composable read-side: the Specification pattern, dynamic filtering/sorting/paging, and the generic entity query pipeline. | -| G04 | **Domain & Integration Events + Outbox Dual-Dispatch**
group-04-events-outbox.md | 38 | L0-L13 | Event contracts, the domain-event dispatcher, the transactional outbox/inbox, and the in-process + broker message buses. | +| G04 | **Domain & Integration Events + Outbox Dual-Dispatch**
group-04-events-outbox.md | 38 | L0-L16 | Event contracts, the domain-event dispatcher, the transactional outbox/inbox, and the in-process + broker message buses. | | G05 | **CQRS: Commands, Queries & the Decorator Pipeline**
group-05-cqrs-pipeline.md | 61 | L0-L14 | The command/query handler abstraction and the cross-cutting decorator pipeline (logging, transaction, caching, feature-gate, idempotency) wrapping it. | | G06 | **Validation**
group-06-validation.md | 22 | L0-L9 | The FluentValidation-based validation contracts and failure mapping that gate commands before they execute. | -| G07 | **Persistence & EF Core**
group-07-persistence-ef-core.md | 165 | L0-L14 | The single SQLServerDbContext over the abstract ApplicationDbContext, interceptors, repositories, specifications evaluation, data-source routing (database-per-service), conventions, value generators, encryption, factories and design-time. | -| G08 | **Authentication & Authorization**
group-08-auth.md | 153 | L0-L10 | JWT/JWKS dual-fetch token validation, current-user/claims, password hashing, cookie sessions, and policy/authorization plumbing. | +| G07 | **Persistence & EF Core**
group-07-persistence-ef-core.md | 177 | L0-L16 | The single SQLServerDbContext over the abstract ApplicationDbContext, interceptors, repositories, specifications evaluation, data-source routing (database-per-service), conventions, value generators, encryption, factories and design-time. | +| G08 | **Authentication & Authorization**
group-08-auth.md | 162 | L0-L14 | JWT/JWKS dual-fetch token validation, current-user/claims, password hashing, cookie sessions, and policy/authorization plumbing. | | G09 | **Caching**
group-09-caching.md | 9 | L0-L4 | The cache abstraction and its decorator-driven, invalidation-aware integration into the query pipeline. | | G10 | **Notifications (Push + In-App Inbox + Email)**
group-10-notifications.md | 59 | L0-L10 | The notification subsystem: push (SignalR), the in-app inbox, email sending, recipient providers, and the thin ADC Notification module host. | | G11 | **Navigation Metadata & Populators (EF-decoupled eager loading)**
group-11-navigation-populators.md | 12 | L0-L9 | INavigationMetadata/INavigationPopulator and the loader that hydrate cross-container/cross-source relationships without EF Include coupling ([ADR-002](https://ivanball.github.io/docs/adr/002-navigation-populators.html)). | | G12 | **API Hosting, Middleware, Idempotency & DTO/Contract Mapping**
group-12-api-hosting-mapping.md | 88 | L0-L16 | The ASP.NET Core edge: controller bases, middleware, startup, model binders, JSON converters, feature management, idempotency, correlation, and manual DTO/request mapping. | -| G13 | **gRPC & Inter-Service Contracts**
group-13-grpc-contracts.md | 6 | L0-L4 | Typed gRPC clients/servers, interceptors, Result-over-the-wire, and the ServiceContract marker for synchronous inter-service calls ([ADR-007](https://ivanball.github.io/docs/adr/007-grpc-extraction.html)). | -| G14 | **Module System, Composition & Configuration**
group-14-module-system-composition.md | 88 | L0-L14 | IModule discovery + Kahn-ordered ModuleLoader, the DI composition roots, assembly markers, data-source/database attributes, and options/settings binding. | -| G15 | **Common UI Framework (MudBlazor components, theme, base pages)**
group-15-common-ui-framework.md | 153 | L0-L8 | Reusable Blazor building blocks: the data-grid list page base, theme, common pages/services, and UI extensions shared by every consumer app. | +| G13 | **gRPC & Inter-Service Contracts**
group-13-grpc-contracts.md | 7 | L0-L4 | Typed gRPC clients/servers, interceptors, Result-over-the-wire, and the ServiceContract marker for synchronous inter-service calls ([ADR-007](https://ivanball.github.io/docs/adr/007-grpc-extraction.html)). | +| G14 | **Module System, Composition & Configuration**
group-14-module-system-composition.md | 87 | L0-L17 | IModule discovery + Kahn-ordered ModuleLoader, the DI composition roots, assembly markers, data-source/database attributes, and options/settings binding. | +| G15 | **Common UI Framework (MudBlazor components, theme, base pages)**
group-15-common-ui-framework.md | 174 | L0-L15 | Reusable Blazor building blocks: the data-grid list page base, theme, common pages/services, and UI extensions shared by every consumer app. | | G16 | **Aspire Orchestration & Service Defaults**
group-16-aspire-orchestration.md | 65 | L0-L11 | The Aspire AppHost wiring, ServiceDefaults, warmup, telemetry and security helpers that compose and run the distributed app locally and in Azure. | | G17 | **ADC Conference - Domain Model & Module Contracts**
group-17-conference-domain.md | 111 | L0-L9 | The Conference bounded context: Event/Session/Speaker/Category/Question aggregates, their domain events and invariants, plus the Shared identifiers/DTOs/integration-event contracts. | | G18 | **ADC Conference - Application & Use Cases**
group-18-conference-application.md | 358 | L0-L15 | Conference CQRS handlers, validators, DTOs, specifications, the Sessionize import, and the session-selection decision-support analytics. | -| G19 | **ADC Conference - Infrastructure & Persistence**
group-19-conference-infrastructure.md | 29 | L0-L12 | The Conference module DbContext registration, EF entity configurations, database seeding, and infrastructure services. | +| G19 | **ADC Conference - Infrastructure & Persistence**
group-19-conference-infrastructure.md | 29 | L0-L14 | The Conference module DbContext registration, EF entity configurations, database seeding, and infrastructure services. | | G20 | **ADC Conference - API, gRPC Contracts & Service Host**
group-20-conference-api-grpc.md | 52 | L0-L12 | Conference REST controllers, the .Contracts gRPC surface, the extractable service host, and the gRPC adapter. | -| G21 | **ADC Conference - UI**
group-21-conference-ui.md | 162 | L0-L10 | The Conference Blazor pages (events, sessions, speakers, categories, questions, rooms, feedback, public, session-selection) and their UI services. | -| G22 | **ADC Engagement Module (Session Bookmarks)**
group-22-engagement-module.md | 194 | L0-L13 | The Engagement bounded context end-to-end: bookmark aggregate, use cases, persistence, API/contracts/service, and feedback UI. | -| G26 | **ADC Engagement Live Layer (Real-Time Polls & Session Q&A)**
group-23-engagement-live-layer.md | 85 | L0-L14 | Real-time audience interaction in the Engagement bounded context: event-wide live polls with voting and moderated per-session Q&A with upvoting, over the SignalR hub-channel transport ([ADR-039](https://ivanball.github.io/docs/adr/039-live-channel-push.html)) and the cross-service gRPC live-channel adapter. | -| G23 | **ADC Identity Module (Users, Profiles, GDPR Export/Erasure)**
group-24-identity-module.md | 107 | L0-L17 | The Identity bounded context end-to-end: the User aggregate, change-password/delete/export use cases, persistence, API/contracts/service, and profile/user UI. | +| G21 | **ADC Conference - UI**
group-21-conference-ui.md | 163 | L0-L10 | The Conference Blazor pages (events, sessions, speakers, categories, questions, rooms, feedback, public, session-selection) and their UI services. | +| G22 | **ADC Engagement Module (Session Bookmarks)**
group-22-engagement-module.md | 196 | L0-L14 | The Engagement bounded context end-to-end: bookmark aggregate, use cases, persistence, API/contracts/service, and feedback UI. | +| G26 | **ADC Engagement Live Layer (Real-Time Polls & Session Q&A)**
group-23-engagement-live-layer.md | 87 | L0-L14 | Real-time audience interaction in the Engagement bounded context: event-wide live polls with voting and moderated per-session Q&A with upvoting, over the SignalR hub-channel transport ([ADR-039](https://ivanball.github.io/docs/adr/039-live-channel-push.html)) and the cross-service gRPC live-channel adapter. | +| G23 | **ADC Identity Module (Users, Profiles, GDPR Export/Erasure)**
group-24-identity-module.md | 108 | L0-L17 | The Identity bounded context end-to-end: the User aggregate, change-password/delete/export use cases, persistence, API/contracts/service, and profile/user UI. | | G24 | **ADC Application Host, UI Shell & Cross-Module Composition**
group-25-adc-host-composition.md | 17 | L0-L13 | The ADC host: the Blazor Web/WASM/WinUI shells, host pages/services, security, and the cross-module application composition. | | G27 | **Device Capability Abstraction Layer (Native Contracts, MAUI, Browser & Fallback Adapters)**
group-26-device-capability-layer.md | 103 | L0-L4 | Per-capability interface contracts (biometric, geocoding/geolocation, speech, push registration, media/clipboard/screenshot, haptics, share, external auth/links, local cache/notifications, connectivity/battery/accessibility, deep links) plus their MAUI-native, browser-JS-interop, and inert fallback implementations, selected per host at DI composition time ([ADR-042](https://ivanball.github.io/docs/adr/042-device-capability-abstraction.html)/043/044/045). | | G28 | **Common AI Integration**
group-27-common-ai-integration.md | 32 | L0-L10 | The AI provider settings, bounded/metered chat client wrapper, and prompt contract types used to integrate LLM calls into Common-based apps. | -| G25 | **Testing & Quality Infrastructure**
group-28-testing-infrastructure.md | 2771 | L0-L19 | All test projects + the reusable Testing/Testing.E2E/Testing.UI bases, architecture-fitness tests, and the component Gallery harness; individual [Fact]s are rolled up by project (logged exception). | +| G25 | **Testing & Quality Infrastructure**
group-28-testing-infrastructure.md | 2923 | L0-L19 | All test projects + the reusable Testing/Testing.E2E/Testing.UI bases, architecture-fitness tests, and the component Gallery harness; individual [Fact]s are rolled up by project (logged exception). | -**Reconciliation:** 2263 production types across 27 groups + 2771 test/testing types in G25 = **5034** (matches the inventory's distinct-node count). No type appears twice; none dropped. +**Reconciliation:** 2311 production types across 27 groups + 2923 test/testing types in G25 = **5234** (matches the inventory's distinct-node count). No type appears twice; none dropped. --- @@ -244,9 +244,9 @@ disclosure) and is cross-linked in the chapter. | 13 | `BrokerEventBus` | class | MMCA.Common.Infrastructure.Messaging | | 13 | `EfInboxStore` | class | MMCA.Common.Infrastructure.Persistence.Inbox | | 13 | `InProcessEventBus` | class | MMCA.Common.Infrastructure.Messaging | -| 13 | `OutboxAdministration` | class | MMCA.Common.Infrastructure.Persistence.Outbox.Administration | -| 13 | `OutboxCleanupService` | class | MMCA.Common.Infrastructure.Persistence.Outbox.Administration | -| 13 | `OutboxProcessor` | class | MMCA.Common.Infrastructure.Persistence.Outbox.Processing | +| 16 | `OutboxAdministration` | class | MMCA.Common.Infrastructure.Persistence.Outbox.Administration | +| 16 | `OutboxCleanupService` | class | MMCA.Common.Infrastructure.Persistence.Outbox.Administration | +| 16 | `OutboxProcessor` | class | MMCA.Common.Infrastructure.Persistence.Outbox.Processing | ### G05 - CQRS: Commands, Queries & the Decorator Pipeline @@ -332,8 +332,8 @@ disclosure) and is cross-linked in the chapter. | 0 | `PositiveIntRules` | class | MMCA.Common.Application.Validation | | 0 | `RequiredIdRules` | class | MMCA.Common.Application.Validation | | 0 | `RequiredStringRules` | class | MMCA.Common.Application.Validation | -| 0 | `StrongPasswordRules` | class | MMCA.Common.Application.Validation | | 1 | `CommandRequestValidator` | class | MMCA.Common.Application.Validation | +| 1 | `StrongPasswordRules` | class | MMCA.Common.Application.Validation | | 2 | `ValidationFailureExtensions` | class | MMCA.Common.Application.Extensions | | 4 | `AddressLine1Rules` | class | MMCA.Common.Application.Validation | | 4 | `AddressLine2Rules` | class | MMCA.Common.Application.Validation | @@ -347,7 +347,7 @@ disclosure) and is cross-linked in the chapter. ### G07 - Persistence & EF Core -> `group-07-persistence-ef-core.md` | 165 types | The single SQLServerDbContext over the abstract ApplicationDbContext, interceptors, repositories, specifications evaluation, data-source routing (database-per-service), conventions, value generators, encryption, factories and design-time. +> `group-07-persistence-ef-core.md` | 177 types | The single SQLServerDbContext over the abstract ApplicationDbContext, interceptors, repositories, specifications evaluation, data-source routing (database-per-service), conventions, value generators, encryption, factories and design-time. | Level | Type | Kind | Namespace | |-------|------|------|-----------| @@ -365,13 +365,13 @@ disclosure) and is cross-linked in the chapter. | 0 | `DocumentFormats` | enum | MMCA.Common.Application.Interfaces.Infrastructure.Storage | | 0 | `EncryptedStringConverter` | class | MMCA.Common.Infrastructure.Persistence.Encryption | | 0 | `EntityConfigurationOptions` | class | MMCA.Common.Infrastructure.Persistence | +| 0 | `ExplicitKeyInsertGroup` | record | MMCA.Common.Infrastructure.Persistence.DataSources.Engines | | 0 | `FileUploadOptions` | record | MMCA.Common.Application.Interfaces.Infrastructure.Storage | | 0 | `GroupedCount` | record | MMCA.Common.Infrastructure.Persistence.Repositories | | 0 | `GroupedSum` | record | MMCA.Common.Infrastructure.Persistence.Repositories | | 0 | `IChannelJoinAuthorizer` | interface | MMCA.Common.Application.Interfaces.Infrastructure.Notifications | | 0 | `IConcurrencyConflictDetector` | interface | MMCA.Common.Application.Interfaces.Infrastructure.Persistence | | 0 | `IDbSeeder` | interface | MMCA.Common.Infrastructure.Persistence.DbContexts.Seeding | -| 0 | `IdentityInsertGroup` | record | MMCA.Common.Infrastructure.Persistence.DbContexts.Factory | | 0 | `IEntityConfigurationAssemblyProvider` | interface | MMCA.Common.Application.Interfaces.Infrastructure.Persistence | | 0 | `IInternalCommandSignal` | interface | MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing | | 0 | `ImageContentSniffer` | class | MMCA.Common.Application.Interfaces.Infrastructure.Storage | @@ -384,11 +384,13 @@ disclosure) and is cross-linked in the chapter. | 0 | `IUniqueConstraintViolationDetector` | interface | MMCA.Common.Application.Interfaces.Infrastructure.Persistence | | 0 | `IUpdatePropertySetter` | interface | MMCA.Common.Application.Interfaces.Infrastructure.Persistence | | 0 | `LookupRow` | class | MMCA.Common.Infrastructure.Persistence.Repositories | +| 0 | `MigrationPolicy` | enum | MMCA.Common.Infrastructure.Persistence.DataSources.Engines | | 0 | `ModelBuilderExtensions` | class | MMCA.Common.Infrastructure.Persistence.DbContexts | | 0 | `OutboxDeadLetter` | record | MMCA.Common.Application.Interfaces.Infrastructure.Persistence | | 0 | `PermissionGrantModelGate` | class | MMCA.Common.Infrastructure.Persistence.Auth | | 0 | `PersistenceSettings` | class | MMCA.Common.Infrastructure.Persistence | | 0 | `ProfilingHelper` | class | MMCA.Common.Infrastructure.Persistence | +| 0 | `RowVersionStrategy` | enum | MMCA.Common.Infrastructure.Persistence.DataSources.Engines | | 0 | `SeedAccount` | record | MMCA.Common.Infrastructure.Persistence.DbContexts.Seeding | | 0 | `StronglyTypedIdValueComparer` | class | MMCA.Common.Infrastructure.Persistence.Conversions | | 0 | `TenantDataSourceOverrideSettings` | class | MMCA.Common.Infrastructure.Persistence.Tenancy | @@ -398,6 +400,7 @@ disclosure) and is cross-linked in the chapter. | 0 | `ValueHolder` | class | MMCA.Common.Infrastructure.Persistence.Repositories | | 0 | `WakeUpSignal` | class | MMCA.Common.Infrastructure.Persistence.Polling | | 1 | `AuditTrailSettings` | class | MMCA.Common.Infrastructure.Persistence.AuditTrail | +| 1 | `DataSourceEngineCapabilities` | record | MMCA.Common.Infrastructure.Persistence.DataSources.Engines | | 1 | `DataSourceKey` | record struct | MMCA.Common.Application.Interfaces.Infrastructure.Persistence | | 1 | `DataSourcesSettings` | class | MMCA.Common.Infrastructure.Persistence.DataSources | | 1 | `DbSeeder` | class | MMCA.Common.Infrastructure.Persistence.DbContexts.Seeding | @@ -407,15 +410,14 @@ disclosure) and is cross-linked in the chapter. | 1 | `EfCoreConcurrencyConflictDetector` | class | MMCA.Common.Infrastructure.Persistence | | 1 | `EFQueryableExecutor` | class | MMCA.Common.Infrastructure.Persistence | | 1 | `ExplicitAssemblyProvider` | class | MMCA.Common.Infrastructure.Persistence.DbContexts.Design | +| 1 | `IExplicitKeyInsertDialect` | interface | MMCA.Common.Infrastructure.Persistence.DataSources.Engines | | 1 | `InternalCommandMetrics` | class | MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing | | 1 | `InternalCommandNameResolver` | class | MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing | | 1 | `InternalCommandSignal` | class | MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing | | 1 | `NamespaceConventions` | class | MMCA.Common.Infrastructure.Persistence | | 1 | `PendingEntityKey` | record | MMCA.Common.Infrastructure.Persistence.AuditTrail | | 1 | `PermissionGrantRefreshService` | class | MMCA.Common.Infrastructure.Persistence.Auth | -| 1 | `RestrictDeleteByDefaultConvention` | class | MMCA.Common.Infrastructure.Persistence.Conventions | | 1 | `SensitiveDataLoggingGate` | class | MMCA.Common.Infrastructure.Persistence | -| 1 | `SoftDeleteFilterSql` | class | MMCA.Common.Infrastructure.Persistence | | 1 | `SqlServerUniqueConstraintViolationDetector` | class | MMCA.Common.Infrastructure.Persistence | | 1 | `TenantEntrySettings` | class | MMCA.Common.Infrastructure.Persistence.Tenancy | | 1 | `UpdatePropertySetterBuilder` | class | MMCA.Common.Infrastructure.Persistence.Repositories | @@ -423,18 +425,12 @@ disclosure) and is cross-linked in the chapter. | 2 | `ConnectionStringSettingsValidator` | class | MMCA.Common.Infrastructure.Persistence.DataSources | | 2 | `IDataSourceService` | interface | MMCA.Common.Application.Interfaces.Infrastructure.Persistence | | 2 | `IEntityDataSourceRegistry` | interface | MMCA.Common.Infrastructure.Persistence.DataSources | -| 2 | `IndexBuilderExtensions` | class | MMCA.Common.Infrastructure.Persistence.Configuration | | 2 | `InternalCommandsSettings` | class | MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration | | 2 | `NullDomainEventDispatcher` | class | MMCA.Common.Infrastructure.Persistence.DbContexts.Design | -| 2 | `PhysicalDataSource` | record | MMCA.Common.Infrastructure.Persistence.DataSources | | 2 | `QueryTags` | class | MMCA.Common.Infrastructure.Persistence.Repositories | | 2 | `Snapshot` | record | MMCA.Common.Infrastructure.Persistence.DataSources | -| 2 | `SoftDeleteUniqueIndexConvention` | class | MMCA.Common.Infrastructure.Persistence.Conventions | | 2 | `TenancySettings` | class | MMCA.Common.Infrastructure.Persistence.Tenancy | | 2 | `TenantDataSourceTarget` | record struct | MMCA.Common.Infrastructure.Persistence.DataSources | -| 3 | `CrossDataSourceDegradeConvention` | class | MMCA.Common.Infrastructure.Persistence.Conventions | -| 3 | `DataSourceService` | class | MMCA.Common.Infrastructure.Persistence.DataSources | -| 3 | `IDataSourceResolver` | interface | MMCA.Common.Infrastructure.Persistence.DataSources | | 3 | `IFileStorageService` | interface | MMCA.Common.Application.Interfaces.Infrastructure.Storage | | 3 | `IImageProcessor` | interface | MMCA.Common.Application.Interfaces.Infrastructure.Storage | | 3 | `IOutboxAdministration` | interface | MMCA.Common.Application.Interfaces.Infrastructure.Persistence | @@ -442,7 +438,6 @@ disclosure) and is cross-linked in the chapter. | 3 | `NullableStronglyTypedIdValueConverter` | class | MMCA.Common.Infrastructure.Persistence.Conversions | | 3 | `PollingLoop` | class | MMCA.Common.Infrastructure.Persistence.Polling | | 3 | `StronglyTypedIdValueConverter` | class | MMCA.Common.Infrastructure.Persistence.Conversions | -| 4 | `DataSourceResolver` | class | MMCA.Common.Infrastructure.Persistence.DataSources | | 4 | `EnumerationValueConverter` | class | MMCA.Common.Infrastructure.Persistence.Conversions | | 4 | `IEntityQuerier` | interface | MMCA.Common.Application.Interfaces.Infrastructure.Persistence | | 4 | `IEntityReader` | interface | MMCA.Common.Application.Interfaces.Infrastructure.Persistence | @@ -451,11 +446,8 @@ disclosure) and is cross-linked in the chapter. | 4 | `InternalCommandMessage` | class | MMCA.Common.Infrastructure.Persistence.InternalCommands | | 4 | `NullableEnumerationValueConverter` | class | MMCA.Common.Infrastructure.Persistence.Conversions | | 4 | `PermissionGrantModelBuilderExtensions` | class | MMCA.Common.Infrastructure.Persistence.Auth | -| 4 | `RefreshSessionModelBuilderExtensions` | class | MMCA.Common.Infrastructure.Persistence.Auth | | 4 | `SpecificationEvaluator` | class | MMCA.Common.Infrastructure.Persistence.Repositories | -| 4 | `TenancySettingsValidator` | class | MMCA.Common.Infrastructure.Persistence.Tenancy | | 5 | `EmailValueConverter` | class | MMCA.Common.Infrastructure.Persistence.Conversions | -| 5 | `EntityDataSourceRegistry` | class | MMCA.Common.Infrastructure.Persistence.DataSources | | 5 | `EntityTypeBuilderExtensions` | class | MMCA.Common.Infrastructure.Persistence.Configuration | | 5 | `EntityTypeConfigurationBase` | class | MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration | | 5 | `IEntityTypeConfigurationCosmos` | interface | MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration | @@ -469,57 +461,77 @@ disclosure) and is cross-linked in the chapter. | 5 | `NullablePhoneNumberValueConverter` | class | MMCA.Common.Infrastructure.Persistence.Conversions | | 5 | `PermissionGrantCache` | class | MMCA.Common.Infrastructure.Persistence.Auth | | 5 | `PhoneNumberValueConverter` | class | MMCA.Common.Infrastructure.Persistence.Conversions | -| 5 | `TenantDataSourceTargets` | class | MMCA.Common.Infrastructure.Persistence.DataSources | -| 6 | `EFReadRepository` | class | MMCA.Common.Infrastructure.Persistence.Repositories | +| 5 | `RefreshSessionModelBuilderExtensions` | class | MMCA.Common.Infrastructure.Persistence.Auth | | 6 | `EFReadRepositoryDecorator` | class | MMCA.Common.Infrastructure.Persistence.Repositories | -| 6 | `EntityTypeConfiguration` | class | MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration | | 6 | `IRepository` | interface | MMCA.Common.Application.Interfaces.Infrastructure.Persistence | | 6 | `ReadRepositoryExtensions` | class | MMCA.Common.Application.Extensions | | 6 | `StronglyTypedIdModelConfiguration` | class | MMCA.Common.Infrastructure.Persistence.Conventions | | 7 | `EFRepositoryDecorator` | class | MMCA.Common.Infrastructure.Persistence.Repositories | -| 7 | `EntityTypeConfigurationCosmos` | class | MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration | -| 7 | `EntityTypeConfigurationPostgreSQL` | class | MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration | -| 7 | `EntityTypeConfigurationSqlite` | class | MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration | -| 7 | `EntityTypeConfigurationSQLServer` | class | MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration | | 7 | `IRepositoryFactory` | interface | MMCA.Common.Infrastructure.Persistence.Repositories.Factory | | 7 | `IUnitOfWork` | interface | MMCA.Common.Application.Interfaces.Infrastructure.Persistence | -| 8 | `PushNotificationConfiguration` | class | MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration.Notifications | -| 8 | `UserNotificationConfiguration` | class | MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration.Notifications | -| 9 | `EFRepository` | class | MMCA.Common.Infrastructure.Persistence.Repositories | +| 9 | `InternalCommandOriginCapture` | class | MMCA.Common.Infrastructure.Persistence.InternalCommands | | 10 | `CapturedState` | record | MMCA.Common.Infrastructure.Persistence.Interceptors | -| 10 | `RepositoryFactory` | class | MMCA.Common.Infrastructure.Persistence.Repositories.Factory | | 11 | `ApplicationDbContext` | class | MMCA.Common.Infrastructure.Persistence.DbContexts | | 11 | `AuditSaveChangesInterceptor` | class | MMCA.Common.Infrastructure.Persistence.Interceptors | | 11 | `AuditTrailSaveChangesInterceptor` | class | MMCA.Common.Infrastructure.Persistence.AuditTrail | +| 11 | `CosmosDataSourceEngine` | class | MMCA.Common.Infrastructure.Persistence.DataSources.Engines | +| 11 | `CosmosDbContext` | class | MMCA.Common.Infrastructure.Persistence.DbContexts | +| 11 | `CrossDataSourceDegradeConvention` | class | MMCA.Common.Infrastructure.Persistence.Conventions | +| 11 | `DataSourceEngines` | class | MMCA.Common.Infrastructure.Persistence.DataSources.Engines | | 11 | `DataSourceModelCacheKeyFactory` | class | MMCA.Common.Infrastructure.Persistence.DbContexts | | 11 | `DeferredDispatch` | record | MMCA.Common.Infrastructure.Persistence.Interceptors | | 11 | `DomainEventSaveChangesInterceptor` | class | MMCA.Common.Infrastructure.Persistence.Interceptors | +| 11 | `IDataSourceEngine` | interface | MMCA.Common.Infrastructure.Persistence.DataSources.Engines | +| 11 | `PhysicalDataSource` | record | MMCA.Common.Infrastructure.Persistence.DataSources | +| 11 | `PostgreSQLDataSourceEngine` | class | MMCA.Common.Infrastructure.Persistence.DataSources.Engines | +| 11 | `PostgreSQLDbContext` | class | MMCA.Common.Infrastructure.Persistence.DbContexts | +| 11 | `RestrictDeleteByDefaultConvention` | class | MMCA.Common.Infrastructure.Persistence.Conventions | +| 11 | `SoftDeleteFilterSql` | class | MMCA.Common.Infrastructure.Persistence | +| 11 | `SoftDeleteUniqueIndexConvention` | class | MMCA.Common.Infrastructure.Persistence.Conventions | +| 11 | `SqliteDataSourceEngine` | class | MMCA.Common.Infrastructure.Persistence.DataSources.Engines | +| 11 | `SqliteDbContext` | class | MMCA.Common.Infrastructure.Persistence.DbContexts | +| 11 | `SQLServerDataSourceEngine` | class | MMCA.Common.Infrastructure.Persistence.DataSources.Engines | +| 11 | `SQLServerDbContext` | class | MMCA.Common.Infrastructure.Persistence.DbContexts | | 11 | `TenantSaveChangesInterceptor` | class | MMCA.Common.Infrastructure.Persistence.Interceptors | -| 12 | `CosmosDbContext` | class | MMCA.Common.Infrastructure.Persistence.DbContexts | +| 12 | `DataSourceService` | class | MMCA.Common.Infrastructure.Persistence.DataSources | +| 12 | `EFReadRepository` | class | MMCA.Common.Infrastructure.Persistence.Repositories | +| 12 | `EntityTypeConfiguration` | class | MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration | +| 12 | `IDataSourceResolver` | interface | MMCA.Common.Infrastructure.Persistence.DataSources | | 12 | `IDbContextFactory` | interface | MMCA.Common.Infrastructure.Persistence.DbContexts.Factory | +| 12 | `IndexBuilderExtensions` | class | MMCA.Common.Infrastructure.Persistence.Configuration | | 12 | `IPhysicalDbContextFactory` | interface | MMCA.Common.Infrastructure.Persistence.DbContexts.Factory | -| 12 | `PostgreSQLDbContext` | class | MMCA.Common.Infrastructure.Persistence.DbContexts | -| 12 | `SqliteDbContext` | class | MMCA.Common.Infrastructure.Persistence.DbContexts | -| 12 | `SQLServerDbContext` | class | MMCA.Common.Infrastructure.Persistence.DbContexts | -| 13 | `AuditTrailCleanupJob` | class | MMCA.Common.Infrastructure.Persistence.AuditTrail | | 13 | `AuditTrailReader` | class | MMCA.Common.Infrastructure.Persistence.AuditTrail | -| 13 | `DbContextFactory` | class | MMCA.Common.Infrastructure.Persistence.DbContexts.Factory | -| 13 | `DesignTimeDbContextHelper` | class | MMCA.Common.Infrastructure.Persistence.DbContexts.Design | +| 13 | `DataSourceResolver` | class | MMCA.Common.Infrastructure.Persistence.DataSources | | 13 | `EFPermissionGrantStore` | class | MMCA.Common.Infrastructure.Persistence.Auth | | 13 | `EFRawSqlQueryExecutor` | class | MMCA.Common.Infrastructure.Persistence | | 13 | `EFRefreshSessionStore` | class | MMCA.Common.Infrastructure.Persistence.Auth | -| 13 | `InternalCommandAdministration` | class | MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration | -| 13 | `InternalCommandCleanupService` | class | MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration | -| 13 | `InternalCommandProcessor` | class | MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing | +| 13 | `EFRepository` | class | MMCA.Common.Infrastructure.Persistence.Repositories | +| 13 | `EntityDataSourceRegistry` | class | MMCA.Common.Infrastructure.Persistence.DataSources | +| 13 | `EntityTypeConfigurationCosmos` | class | MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration | +| 13 | `EntityTypeConfigurationPostgreSQL` | class | MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration | +| 13 | `EntityTypeConfigurationSqlite` | class | MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration | +| 13 | `EntityTypeConfigurationSQLServer` | class | MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration | | 13 | `InternalCommandScheduler` | class | MMCA.Common.Infrastructure.Persistence.InternalCommands | | 13 | `PhysicalDbContextFactory` | class | MMCA.Common.Infrastructure.Persistence.DbContexts.Factory | | 13 | `RefreshSessionCleanupService` | class | MMCA.Common.Infrastructure.Persistence.Auth | +| 13 | `TenancySettingsValidator` | class | MMCA.Common.Infrastructure.Persistence.Tenancy | | 13 | `UnitOfWork` | class | MMCA.Common.Infrastructure.Persistence | +| 14 | `DbContextFactory` | class | MMCA.Common.Infrastructure.Persistence.DbContexts.Factory | +| 14 | `DesignTimeDbContextHelper` | class | MMCA.Common.Infrastructure.Persistence.DbContexts.Design | | 14 | `IdentityModuleDbSeederBase` | class | MMCA.Common.Infrastructure.Persistence.DbContexts.Seeding | +| 14 | `PushNotificationConfiguration` | class | MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration.Notifications | +| 14 | `RepositoryFactory` | class | MMCA.Common.Infrastructure.Persistence.Repositories.Factory | +| 14 | `TenantDataSourceTargets` | class | MMCA.Common.Infrastructure.Persistence.DataSources | +| 14 | `UserNotificationConfiguration` | class | MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration.Notifications | +| 15 | `FrameworkTableTargets` | class | MMCA.Common.Infrastructure.Persistence.DataSources | +| 16 | `AuditTrailCleanupJob` | class | MMCA.Common.Infrastructure.Persistence.AuditTrail | +| 16 | `InternalCommandAdministration` | class | MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration | +| 16 | `InternalCommandCleanupService` | class | MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration | +| 16 | `InternalCommandProcessor` | class | MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing | ### G08 - Authentication & Authorization -> `group-08-auth.md` | 153 types | JWT/JWKS dual-fetch token validation, current-user/claims, password hashing, cookie sessions, and policy/authorization plumbing. +> `group-08-auth.md` | 162 types | JWT/JWKS dual-fetch token validation, current-user/claims, password hashing, cookie sessions, and policy/authorization plumbing. | Level | Type | Kind | Namespace | |-------|------|------|-----------| @@ -548,8 +560,9 @@ disclosure) and is cross-linked in the chapter. | 0 | `IPermissionGrantCache` | interface | MMCA.Common.Application.Auth.Permissions | | 0 | `IPermissionGrantCacheInvalidator` | interface | MMCA.Common.Application.Auth.Permissions | | 0 | `IPermissionRegistry` | interface | MMCA.Common.Shared.Auth.Permissions | +| 0 | `ISessionCookieStore` | interface | MMCA.Common.API.SessionCookies | | 0 | `ISoftDeletedUserValidator` | interface | MMCA.Common.Application.Interfaces.Infrastructure.Auth | -| 0 | `IssuedSession` | record | MMCA.Common.Application.Auth | +| 0 | `IssuedSession` | record | MMCA.Common.Application.Auth.Sessions | | 0 | `ITokenService` | interface | MMCA.Common.Application.Interfaces.Infrastructure.Auth | | 0 | `ITwoFactorUserState` | interface | MMCA.Common.Domain.Auth | | 0 | `IUserAdminDTO` | interface | MMCA.Common.Shared.Auth.Administration | @@ -561,6 +574,7 @@ disclosure) and is cross-linked in the chapter. | 0 | `ModuleNameConventions` | class | MMCA.Common.Shared.Conventions | | 0 | `OAuthCodeExchangeRequest` | record struct | MMCA.Common.Shared.Auth.Requests | | 0 | `OwnerOrAdminFilterOptions` | class | MMCA.Common.API.Authorization | +| 0 | `PasswordComplexity` | class | MMCA.Common.Shared.Auth | | 0 | `PasswordResetEntry` | record | MMCA.Common.Infrastructure.Auth | | 0 | `PasswordResetSettings` | class | MMCA.Common.Application.Auth | | 0 | `PermissionCatalogResponse` | record | MMCA.Common.Shared.Auth.Responses | @@ -575,7 +589,10 @@ disclosure) and is cross-linked in the chapter. | 0 | `ResetPasswordRequest` | record struct | MMCA.Common.Shared.Auth.Requests | | 0 | `RolePermissionsResponse` | record | MMCA.Common.Shared.Auth.Responses | | 0 | `SendEmailConfirmationRequest` | record struct | MMCA.Common.Shared.Auth.Requests | +| 0 | `SessionClaimsToken` | class | MMCA.Common.API.SessionCookies | | 0 | `SessionCookieRequest` | record | MMCA.Common.API.SessionCookies | +| 0 | `SessionCookieSettings` | class | MMCA.Common.API.SessionCookies | +| 0 | `SessionRefreshStatus` | enum | MMCA.Common.API.SessionCookies | | 0 | `SessionTokenResponse` | record | MMCA.Common.API.SessionCookies | | 0 | `SessionTokenResult` | record struct | MMCA.Common.API.SessionCookies | | 0 | `SetRolePermissionsRequest` | record | MMCA.Common.Shared.Auth.Requests | @@ -596,7 +613,6 @@ disclosure) and is cross-linked in the chapter. | 1 | `FeatureFlagDescriptor` | record | MMCA.Common.Shared.FeatureFlags | | 1 | `ForgotPasswordRequestValidator` | class | MMCA.Common.Application.Auth.Validation | | 1 | `HasPermissionAttribute` | class | MMCA.Common.API.Authorization | -| 1 | `ICookieSessionRefresher` | interface | MMCA.Common.API.SessionCookies | | 1 | `IcsCalendarBuilder` | class | MMCA.Common.Shared.Calendars | | 1 | `ITwoFactorService` | interface | MMCA.Common.Application.Auth.TwoFactor | | 1 | `JwtSettings` | class | MMCA.Common.Infrastructure.Auth | @@ -609,28 +625,24 @@ disclosure) and is cross-linked in the chapter. | 1 | `PermissionRegistry` | class | MMCA.Common.Shared.Auth.Permissions | | 1 | `PrivacyFeatures` | class | MMCA.Common.Shared.Privacy | | 1 | `RefreshTokenRequestValidator` | class | MMCA.Common.Application.Auth.Validation | -| 1 | `ResetPasswordRequestValidator` | class | MMCA.Common.Application.Auth.Validation | | 1 | `RsaJwksProvider` | class | MMCA.Common.Infrastructure.Auth | | 1 | `SendEmailConfirmationRequestValidator` | class | MMCA.Common.Application.Auth.Validation | -| 1 | `SessionStampingTokenService` | class | MMCA.Common.Application.Auth | +| 1 | `SessionStampingTokenService` | class | MMCA.Common.Application.Auth.Sessions | | 1 | `StronglyTypedIdValueParser` | class | MMCA.Common.Shared.Identifiers | | 1 | `TwoFactorCodeRequestValidator` | class | MMCA.Common.Application.Auth.Validation | | 1 | `UnconfiguredPermissionRegistry` | class | MMCA.Common.Application.Auth | | 1 | `UserDataExportDTO` | record | MMCA.Common.Shared.Privacy | -| 2 | `CookieSessionRefreshMiddleware` | class | MMCA.Common.API.SessionCookies | | 2 | `EmailConfirmationErrors` | class | MMCA.Common.Application.Auth.EmailConfirmation | | 2 | `FeatureFlagRegistry` | class | MMCA.Common.Shared.FeatureFlags | | 2 | `IStronglyTypedId` | interface | MMCA.Common.Shared.Identifiers | | 2 | `PermissionRegistryBuilder` | class | MMCA.Common.Shared.Auth.Permissions | -| 2 | `SessionCookieEndpoints` | class | MMCA.Common.API.SessionCookies | -| 2 | `SessionCookieJar` | class | MMCA.Common.API.SessionCookies | +| 2 | `ResetPasswordRequestValidator` | class | MMCA.Common.Application.Auth.Validation | | 2 | `StronglyTypedId` | class | MMCA.Common.Shared.Identifiers | | 2 | `TokenService` | class | MMCA.Common.Infrastructure.Auth | | 2 | `TotpTwoFactorService` | class | MMCA.Common.Infrastructure.Auth.TwoFactor | | 2 | `TwoFactorErrors` | class | MMCA.Common.Application.Auth.TwoFactor | | 3 | `AuthorizationExtensions` | class | MMCA.Common.API.Authorization | -| 3 | `CookieSessionRefreshMiddlewareExtensions` | class | MMCA.Common.API.SessionCookies | -| 3 | `CookieTokenReader` | class | MMCA.Common.API.SessionCookies | +| 3 | `IAuthSessionIssuer` | interface | MMCA.Common.Application.Auth.Sessions | | 3 | `IEmailConfirmableUser` | interface | MMCA.Common.Domain.Auth | | 3 | `IEmailConfirmationTokenService` | interface | MMCA.Common.Application.Auth.EmailConfirmation | | 3 | `ILoginProtectionService` | interface | MMCA.Common.Application.Auth | @@ -643,17 +655,14 @@ disclosure) and is cross-linked in the chapter. | 3 | `IUserPreferences` | interface | MMCA.Common.Domain.Auth | | 3 | `PermissionGrant` | class | MMCA.Common.Domain.Auth | | 3 | `ProblemDetailsResultReader` | class | MMCA.Common.Shared.Http | -| 3 | `RefreshSession` | class | MMCA.Common.Domain.Auth | | 3 | `RoleValue` | class | MMCA.Common.Shared.Auth | | 3 | `StronglyTypedIdConverter` | class | MMCA.Common.Shared.Identifiers | | 3 | `StronglyTypedIdMappings` | class | MMCA.Common.Shared.Identifiers | | 3 | `StronglyTypedIdTypeConverter` | class | MMCA.Common.Shared.Identifiers | -| 4 | `CookieSessionRefresher` | class | MMCA.Common.API.SessionCookies | | 4 | `IErasableUser` | interface | MMCA.Common.Domain.Auth | | 4 | `IPermissionGrantStore` | interface | MMCA.Common.Application.Auth.Permissions | -| 4 | `IRefreshSessionStore` | interface | MMCA.Common.Application.Auth | +| 4 | `RefreshSession` | class | MMCA.Common.Domain.Auth | | 4 | `RegisterRequest` | record struct | MMCA.Common.Shared.Auth.Requests | -| 4 | `SessionCookieAuthenticationHandler` | class | MMCA.Common.API.SessionCookies | | 4 | `SetRolePermissionsRequestValidator` | class | MMCA.Common.Application.Auth.Validation | | 4 | `SetUserRolesRequestValidator` | class | MMCA.Common.Application.Auth.Validation | | 4 | `SoftDeletedUserCache` | class | MMCA.Common.Application.Auth | @@ -663,19 +672,31 @@ disclosure) and is cross-linked in the chapter. | 5 | `AuthenticationValidators` | class | MMCA.Common.Application.Auth | | 5 | `EmailIdentity` | class | MMCA.Common.Infrastructure.Auth | | 5 | `IAuthenticationService` | interface | MMCA.Common.Application.Auth | -| 5 | `RefreshSessionRevocation` | class | MMCA.Common.Application.Auth | -| 5 | `SessionCookieAuthenticationExtensions` | class | MMCA.Common.API.SessionCookies | +| 5 | `IRefreshSessionStore` | interface | MMCA.Common.Application.Auth | | 5 | `StoredPermissionRoleAdministrationService` | class | MMCA.Common.Infrastructure.Auth.Administration | | 5 | `StronglyTypedIdRegistry` | class | MMCA.Common.Shared.Identifiers | +| 6 | `AuthSessionIssuer` | class | MMCA.Common.Application.Auth.Sessions | | 6 | `EmailConfirmationTokenService` | class | MMCA.Common.Infrastructure.Auth | | 6 | `LoginProtectionService` | class | MMCA.Common.Infrastructure.Auth | | 6 | `PasswordResetTokenService` | class | MMCA.Common.Infrastructure.Auth | +| 6 | `RefreshSessionRevocation` | class | MMCA.Common.Application.Auth | | 8 | `AuthenticationServiceBase` | class | MMCA.Common.Application.Auth | | 8 | `ClaimBasedUserIdProvider` | class | MMCA.Common.Infrastructure.Context | | 8 | `ICurrentUserService` | interface | MMCA.Common.Application.Interfaces.Infrastructure.Auth | | 9 | `CurrentUserService` | class | MMCA.Common.Infrastructure.Context | | 9 | `OwnershipHelper` | class | MMCA.Common.API.Authorization | +| 9 | `SessionRefreshOutcome` | class | MMCA.Common.API.SessionCookies | +| 10 | `ICookieSessionRefresher` | interface | MMCA.Common.API.SessionCookies | | 10 | `OwnerOrAdminFilter` | class | MMCA.Common.API.Authorization | +| 11 | `CookieSessionRefreshMiddleware` | class | MMCA.Common.API.SessionCookies | +| 11 | `SessionCookieEndpoints` | class | MMCA.Common.API.SessionCookies | +| 11 | `SessionCookieJar` | class | MMCA.Common.API.SessionCookies | +| 12 | `CookieSessionRefreshMiddlewareExtensions` | class | MMCA.Common.API.SessionCookies | +| 12 | `CookieTokenReader` | class | MMCA.Common.API.SessionCookies | +| 12 | `SessionCookieStore` | class | MMCA.Common.API.SessionCookies | +| 13 | `CookieSessionRefresher` | class | MMCA.Common.API.SessionCookies | +| 13 | `SessionCookieAuthenticationHandler` | class | MMCA.Common.API.SessionCookies | +| 14 | `SessionCookieAuthenticationExtensions` | class | MMCA.Common.API.SessionCookies | ### G09 - Caching @@ -867,18 +888,19 @@ disclosure) and is cross-linked in the chapter. | 10 | `MiddlewarePipelineBuilder` | class | MMCA.Common.API.Startup.Pipeline | | 10 | `RolesAdminControllerBase` | class | MMCA.Common.API.Controllers.Administration | | 10 | `WebApplicationExtensions` | class | MMCA.Common.API.Startup | -| 11 | `DependencyInjection` | class | MMCA.Common.API | -| 13 | `DatabaseInitializationExtensions` | class | MMCA.Common.API.Startup | +| 14 | `DependencyInjection` | class | MMCA.Common.API | | 15 | `AuthControllerBase` | class | MMCA.Common.API.Controllers | +| 15 | `DatabaseInitializationExtensions` | class | MMCA.Common.API.Startup | | 15 | `PasswordResetAuthControllerBase` | class | MMCA.Common.API.Controllers | | 16 | `UserAccountAuthControllerBase` | class | MMCA.Common.API.Controllers | ### G13 - gRPC & Inter-Service Contracts -> `group-13-grpc-contracts.md` | 6 types | Typed gRPC clients/servers, interceptors, Result-over-the-wire, and the ServiceContract marker for synchronous inter-service calls ([ADR-007](https://ivanball.github.io/docs/adr/007-grpc-extraction.html)). +> `group-13-grpc-contracts.md` | 7 types | Typed gRPC clients/servers, interceptors, Result-over-the-wire, and the ServiceContract marker for synchronous inter-service calls ([ADR-007](https://ivanball.github.io/docs/adr/007-grpc-extraction.html)). | Level | Type | Kind | Namespace | |-------|------|------|-----------| +| 0 | `GrpcWireFormat` | class | MMCA.Common.Grpc | | 0 | `JwtForwardingClientInterceptor` | class | MMCA.Common.Grpc.Interceptors | | 0 | `ServiceContractAttribute` | class | MMCA.Common.Shared.Abstractions | | 2 | `ResultFailureException` | class | MMCA.Common.Grpc.Exceptions | @@ -888,20 +910,20 @@ disclosure) and is cross-linked in the chapter. ### G14 - Module System, Composition & Configuration -> `group-14-module-system-composition.md` | 88 types | IModule discovery + Kahn-ordered ModuleLoader, the DI composition roots, assembly markers, data-source/database attributes, and options/settings binding. +> `group-14-module-system-composition.md` | 87 types | IModule discovery + Kahn-ordered ModuleLoader, the DI composition roots, assembly markers, data-source/database attributes, and options/settings binding. | Level | Type | Kind | Namespace | |-------|------|------|-----------| | 0 | `ApplicationNamespace` | class | MMCA.Common.Infrastructure.Configuration | | 0 | `ApplicationSettings` | class | MMCA.Common.Application.Settings | | 0 | `AssemblyReference` | class | MMCA.Common.Application | -| 0 | `AssemblyReference` | class | MMCA.Common.Domain | | 0 | `AssemblyReference` | class | MMCA.Common.Infrastructure | +| 0 | `AssemblyReference` | class | MMCA.Common.Domain | | 0 | `AuditTrailEntryDTO` | record | MMCA.Common.Application.Auditing | | 0 | `BrokerMetrics` | class | MMCA.Common.Infrastructure.Messaging | +| 0 | `ClassReference` | class | MMCA.Common.Infrastructure | | 0 | `ClassReference` | class | MMCA.Common.Application | | 0 | `ClassReference` | class | MMCA.Common.Domain | -| 0 | `ClassReference` | class | MMCA.Common.Infrastructure | | 0 | `DecoratorPipelineSeal` | class | MMCA.Common.Application | | 0 | `FileStorageSettings` | class | MMCA.Common.Infrastructure.Storage | | 0 | `IModuleSeeder` | interface | MMCA.Common.Application.Modules | @@ -925,7 +947,6 @@ disclosure) and is cross-linked in the chapter. | 0 | `SmtpSettings` | class | MMCA.Common.Infrastructure.Mail | | 0 | `UseDatabaseAttribute` | class | MMCA.Common.Infrastructure | | 0 | `UserDataExportSectionDefaults` | class | MMCA.Common.Application.Users.UseCases.ExportUserData | -| 0 | `UserUseCaseLog` | class | MMCA.Common.Application.Users | | 1 | `AzureNotificationHubNativePushSender` | class | MMCA.Common.Infrastructure.Notifications.Push | | 1 | `GetUserPreferencesQuery` | record | MMCA.Common.Application.Users.UseCases.GetPreferences | | 1 | `IModule` | interface | MMCA.Common.Application.Modules | @@ -977,13 +998,13 @@ disclosure) and is cross-linked in the chapter. | 9 | `ImpersonatingCurrentUserService` | class | MMCA.Common.Infrastructure.Context | | 9 | `UpcastingIntegrationEventConsumer` | class | MMCA.Common.Infrastructure.Messaging.Consumers | | 11 | `DependencyInjection` | class | MMCA.Common.Application | -| 13 | `CreateMigrationProofTable` | class | MMCA.Common.Infrastructure.Tests.MigrationsFixture | +| 12 | `CreateMigrationProofTable` | class | MMCA.Common.Infrastructure.Tests.MigrationsFixture | | 13 | `ScheduledJobRunner` | class | MMCA.Common.Infrastructure.Scheduling | -| 14 | `DependencyInjection` | class | MMCA.Common.Infrastructure | +| 17 | `DependencyInjection` | class | MMCA.Common.Infrastructure | ### G15 - Common UI Framework (MudBlazor components, theme, base pages) -> `group-15-common-ui-framework.md` | 153 types | Reusable Blazor building blocks: the data-grid list page base, theme, common pages/services, and UI extensions shared by every consumer app. +> `group-15-common-ui-framework.md` | 174 types | Reusable Blazor building blocks: the data-grid list page base, theme, common pages/services, and UI extensions shared by every consumer app. | Level | Type | Kind | Namespace | |-------|------|------|-----------| @@ -1001,6 +1022,7 @@ disclosure) and is cross-linked in the chapter. | 0 | `CultureDelegatingHandler` | class | MMCA.Common.UI.Services.Culture | | 0 | `ErrorMessages` | class | MMCA.Common.UI.Pages.Common | | 0 | `ForgotPasswordModel` | class | MMCA.Common.UI.Pages.Auth | +| 0 | `HandoffBody` | record | MMCA.Common.UI.Web.SameOriginProxy | | 0 | `IApiSettings` | interface | MMCA.Common.UI.Common.Settings | | 0 | `IAppDialogService` | interface | MMCA.Common.UI.Common.Interfaces | | 0 | `ICultureApplier` | interface | MMCA.Common.UI.Services.Culture | @@ -1022,27 +1044,29 @@ disclosure) and is cross-linked in the chapter. | 0 | `LazyJsModule` | class | MMCA.Common.UI.Services | | 0 | `ListPageState` | record | MMCA.Common.UI.Services | | 0 | `LoginModel` | class | MMCA.Common.UI.Pages.Auth | -| 0 | `MmcaClientConfigBootstrap` | class | MMCA.Common.UI.Common.Settings | | 0 | `MudTranslations` | class | MMCA.Common.UI.Resources | | 0 | `NavSection` | enum | MMCA.Common.UI.Common | | 0 | `NotificationBellOptions` | class | MMCA.Common.UI.Common.Settings | | 0 | `NotificationState` | class | MMCA.Common.UI.Services.Notifications | -| 0 | `PasswordComplexityAttribute` | class | MMCA.Common.UI.Pages.Auth | | 0 | `PendingAttempt` | record | MMCA.Common.UI.Services.Auth.OAuth | | 0 | `PermissionGroup` | record | MMCA.Common.UI.Pages.Administration | | 0 | `PersistedGridState` | record | MMCA.Common.UI.Pages.Common | +| 0 | `ProxyResponseMode` | enum | MMCA.Common.UI.Web.SameOriginProxy | | 0 | `PseudoLocalizer` | class | MMCA.Common.UI.Globalization | | 0 | `QrErrorCorrectionLevel` | enum | MMCA.Common.UI.Components.Sharing | | 0 | `RatingStars` | class | MMCA.Common.UI.Components.Ratings | -| 0 | `RegisterModel` | class | MMCA.Common.UI.Pages.Auth | -| 0 | `ResetPasswordModel` | class | MMCA.Common.UI.Pages.Auth | | 0 | `ReturnUrlProtector` | class | MMCA.Common.UI.Services.Navigation | | 0 | `RoleAdminEditResources` | class | MMCA.Common.UI.Pages.Administration | | 0 | `RoleAdminListResources` | class | MMCA.Common.UI.Pages.Administration | | 0 | `RoutePaths` | class | MMCA.Common.UI.Common | +| 0 | `SameOriginApiProxyMarker` | class | MMCA.Common.UI.Web.SameOriginProxy | +| 0 | `SameOriginApiProxySettings` | class | MMCA.Common.UI.Web.SameOriginProxy | +| 0 | `SameOriginProxyHeaders` | class | MMCA.Common.UI.Services.Auth | +| 0 | `SameOriginProxyInvoker` | class | MMCA.Common.UI.Web.SameOriginProxy | | 0 | `SharedResource` | class | MMCA.Common.UI.Resources | | 0 | `StringLocalizerPluralExtensions` | class | MMCA.Common.UI.Globalization | | 0 | `ToastSeverity` | enum | MMCA.Common.UI.Common.Interfaces | +| 0 | `TokenPair` | record | MMCA.Common.UI.Web.SameOriginProxy | | 0 | `TrustedCallerHandler` | class | MMCA.Common.UI.Web.Security | | 0 | `UIModuleConfiguration` | class | MMCA.Common.UI.Common.Settings | | 0 | `UiRateLimitingSettings` | class | MMCA.Common.UI.Web.Hardening | @@ -1064,7 +1088,6 @@ disclosure) and is cross-linked in the chapter. | 1 | `DataAnnotationsModelValidator` | class | MMCA.Common.UI.Validation | | 1 | `DefaultOAuthUISettings` | class | MMCA.Common.UI.Services.Auth.OAuth | | 1 | `DetailPageBase` | class | MMCA.Common.UI.Pages.Common | -| 1 | `DirectApiTokenRefresher` | class | MMCA.Common.UI.Services.Auth.Tokens | | 1 | `EndpointCultureApplier` | class | MMCA.Common.UI.Services.Culture | | 1 | `InvariantMudLocalizationInterceptor` | class | MMCA.Common.UI.Globalization | | 1 | `IToastService` | interface | MMCA.Common.UI.Common.Interfaces | @@ -1083,9 +1106,15 @@ disclosure) and is cross-linked in the chapter. | 1 | `NullNotificationScopeProvider` | class | MMCA.Common.UI.Services.Notifications | | 1 | `OAuthFlowStateStore` | class | MMCA.Common.UI.Services.Auth.OAuth | | 1 | `OfflineFirstPageSnapshot` | class | MMCA.Common.UI.Pages.Common | +| 1 | `PasswordComplexityAttribute` | class | MMCA.Common.UI.Pages.Auth | | 1 | `PseudoStringLocalizer` | class | MMCA.Common.UI.Globalization | +| 1 | `RegisterModel` | class | MMCA.Common.UI.Pages.Auth | +| 1 | `ResetPasswordModel` | class | MMCA.Common.UI.Pages.Auth | | 1 | `ResxMudLocalizer` | class | MMCA.Common.UI.Globalization | +| 1 | `SameOriginApiProxySettingsValidator` | class | MMCA.Common.UI.Web.SameOriginProxy | +| 1 | `SameOriginProxyRequestHandler` | class | MMCA.Common.UI.Services.Auth | | 1 | `SameOriginProxyTokenRefresher` | class | MMCA.Common.UI.Services.Auth.Tokens | +| 1 | `SessionHandoffProtector` | class | MMCA.Common.UI.Web.SameOriginProxy | | 1 | `ThemeService` | class | MMCA.Common.UI.Theme | | 1 | `TokenHydrationWarmup` | class | MMCA.Common.UI.Services.Auth.Tokens | | 1 | `UiRateLimitingExtensions` | class | MMCA.Common.UI.Web.Hardening | @@ -1097,7 +1126,13 @@ disclosure) and is cross-linked in the chapter. | 2 | `ChannelSubscription` | class | MMCA.Common.UI.Services.Notifications | | 2 | `ClientConfigBuilder` | class | MMCA.Common.UI.Web.ClientConfig | | 2 | `ClientConfigEndpointExtensions` | class | MMCA.Common.UI.Web.ClientConfig | +| 2 | `DirectApiTokenRefresher` | class | MMCA.Common.UI.Services.Auth.Tokens | +| 2 | `HandoffSessionCookieSync` | class | MMCA.Common.UI.Web.SameOriginProxy | +| 2 | `HandoffTokenRefresher` | class | MMCA.Common.UI.Web.SameOriginProxy | +| 2 | `IdempotentReadRetry` | class | MMCA.Common.UI.Services.Api | | 2 | `IUIModule` | interface | MMCA.Common.UI.Common.Interfaces | +| 2 | `LocalizedDataAnnotationsValidator` | class | MMCA.Common.UI.Validation | +| 2 | `MmcaClientConfigBootstrap` | class | MMCA.Common.UI.Common.Settings | | 2 | `MMCATheme` | class | MMCA.Common.UI.Theme | | 2 | `ModelValidation` | class | MMCA.Common.UI.Validation | | 2 | `MudToastService` | class | MMCA.Common.UI.Services | @@ -1112,6 +1147,7 @@ disclosure) and is cross-linked in the chapter. | 3 | `IRoleAdminUIService` | interface | MMCA.Common.UI.Services.Administration | | 3 | `IUserAdminActionsUIService` | interface | MMCA.Common.UI.Services.Administration | | 3 | `MobileInfiniteScrollList` | class | MMCA.Common.UI.Components.Lists | +| 3 | `PagedReadAll` | class | MMCA.Common.UI.Services.Api | | 3 | `ResultUiExtensions` | class | MMCA.Common.UI.Common | | 4 | `ChildEntityServiceBase` | class | MMCA.Common.UI.Services.Api | | 4 | `ConfirmEmail` | class | MMCA.Common.UI.Pages.Auth | @@ -1124,8 +1160,6 @@ disclosure) and is cross-linked in the chapter. | 4 | `RoleAdminEdit` | class | MMCA.Common.UI.Pages.Administration | | 4 | `RoleAdminList` | class | MMCA.Common.UI.Pages.Administration | | 4 | `RoleAdminService` | class | MMCA.Common.UI.Services.Administration | -| 4 | `ServerTokenStorageService` | class | MMCA.Common.UI.Web.Services | -| 5 | `DependencyInjection` | class | MMCA.Common.UI.Web | | 5 | `IAuthUIService` | interface | MMCA.Common.UI.Services.Auth | | 5 | `MoneyExtensions` | class | MMCA.Common.UI.Extensions | | 5 | `NotificationRoutePaths` | class | MMCA.Common.UI.Common | @@ -1140,6 +1174,14 @@ disclosure) and is cross-linked in the chapter. | 7 | `DependencyInjection` | class | MMCA.Common.UI | | 7 | `NotificationUIModule` | class | MMCA.Common.UI.Notifications | | 8 | `DependencyInjection` | class | MMCA.Common.UI.Notifications | +| 8 | `NotificationPageGate` | class | MMCA.Common.UI.Notifications | +| 12 | `SameOriginProxyTransformer` | class | MMCA.Common.UI.Web.SameOriginProxy | +| 13 | `SameOriginApiProxyEndpoint` | class | MMCA.Common.UI.Web.SameOriginProxy | +| 13 | `ServerTokenStorageService` | class | MMCA.Common.UI.Web.Services | +| 14 | `DependencyInjection` | class | MMCA.Common.UI.Web | +| 14 | `SameOriginApiProxyServiceExtensions` | class | MMCA.Common.UI.Web.SameOriginProxy | +| 14 | `SessionHandoffEndpoints` | class | MMCA.Common.UI.Web.SameOriginProxy | +| 15 | `SameOriginApiProxyEndpointExtensions` | class | MMCA.Common.UI.Web.SameOriginProxy | ### G16 - Aspire Orchestration & Service Defaults @@ -1605,6 +1647,7 @@ disclosure) and is cross-linked in the chapter. | 9 | `UpdateSessionQuestionAnswerCommand` | record | MMCA.ADC.Conference.Application.Sessions.UseCases.UpdateSessionQuestionAnswer | | 9 | `UploadSessionAssetCommandValidator` | class | MMCA.ADC.Conference.Application.SessionAssets.UseCases.UploadFile | | 9 | `UploadSessionAssetHandler` | class | MMCA.ADC.Conference.Application.SessionAssets.UseCases.UploadFile | +| 9 | `UserDeletedFeedbackHandler` | class | MMCA.ADC.Conference.Application.Users.IntegrationEventHandlers | | 10 | `ActivityCreateRequestMapper` | class | MMCA.ADC.Conference.Application.Activities.UseCases.Create | | 10 | `ActivityCreateRequestValidator` | class | MMCA.ADC.Conference.Application.Activities.UseCases.Create | | 10 | `ActivityNavigationPopulator` | class | MMCA.ADC.Conference.Application.Activities | @@ -1655,7 +1698,6 @@ disclosure) and is cross-linked in the chapter. | 10 | `SpeakerCategoryItemNavigationPopulator` | class | MMCA.ADC.Conference.Application.Speakers | | 10 | `SpeakerEntityQueryService` | class | MMCA.ADC.Conference.Application.Speakers | | 10 | `SpeakerNavigationPopulator` | class | MMCA.ADC.Conference.Application.Speakers | -| 10 | `SpeakerQuestionAnswerNavigationPopulator` | class | MMCA.ADC.Conference.Application.Speakers | | 10 | `SpeakerSyncStrategy` | class | MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize | | 10 | `SponsorCreateRequestMapper` | class | MMCA.ADC.Conference.Application.Sponsors.UseCases.Create | | 10 | `SponsorCreateRequestValidator` | class | MMCA.ADC.Conference.Application.Sponsors.UseCases.Create | @@ -1663,11 +1705,9 @@ disclosure) and is cross-linked in the chapter. | 10 | `UnlinkUserFromSpeakerHandler` | class | MMCA.ADC.Conference.Application.Speakers.UseCases.UnlinkUser | | 10 | `UnpublishEventHandler` | class | MMCA.ADC.Conference.Application.Events.UseCases.Unpublish | | 10 | `UpdateCategoryItemHandler` | class | MMCA.ADC.Conference.Application.Categories.UseCases.UpdateCategoryItem | -| 10 | `UpdateEventQuestionAnswerHandler` | class | MMCA.ADC.Conference.Application.Events.UseCases.UpdateEventQuestionAnswer | | 10 | `UpdateRoomHandler` | class | MMCA.ADC.Conference.Application.Events.UseCases.UpdateRoom | | 10 | `UpdateSessionAssetHandler` | class | MMCA.ADC.Conference.Application.SessionAssets.UseCases.Update | | 10 | `UpdateSessionQuestionAnswerCommandValidator` | class | MMCA.ADC.Conference.Application.Sessions.UseCases.UpdateSessionQuestionAnswer | -| 10 | `UpdateSessionQuestionAnswerHandler` | class | MMCA.ADC.Conference.Application.Sessions.UseCases.UpdateSessionQuestionAnswer | | 11 | `GetPublicActivityFilterHandler` | class | MMCA.ADC.Conference.Application.Activities.UseCases.GetPublicActivityFilter | | 11 | `GetPublicEventSpeakerFilterHandler` | class | MMCA.ADC.Conference.Application.Events.UseCases.GetPublicEventSpeakerFilter | | 11 | `GetPublicPartnerFilterHandler` | class | MMCA.ADC.Conference.Application.Partners.UseCases.GetPublicPartnerFilter | @@ -1690,7 +1730,9 @@ disclosure) and is cross-linked in the chapter. | 14 | `DeleteSessionHandler` | class | MMCA.ADC.Conference.Application.Sessions.UseCases.Delete | | 14 | `LinkUserToSpeakerHandler` | class | MMCA.ADC.Conference.Application.Speakers.UseCases.LinkUser | | 14 | `RefreshFromSessionizeHandler` | class | MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize | +| 14 | `UpdateEventQuestionAnswerHandler` | class | MMCA.ADC.Conference.Application.Events.UseCases.UpdateEventQuestionAnswer | | 14 | `UpdateQuestionHandler` | class | MMCA.ADC.Conference.Application.Questions.UseCases.Update | +| 14 | `UpdateSessionQuestionAnswerHandler` | class | MMCA.ADC.Conference.Application.Sessions.UseCases.UpdateSessionQuestionAnswer | | 15 | `AddRoomHandler` | class | MMCA.ADC.Conference.Application.Events.UseCases.AddRoom | | 15 | `DependencyInjection` | class | MMCA.ADC.Conference.Application | | 15 | `UpdateEventHandler` | class | MMCA.ADC.Conference.Application.Events.UseCases.Update | @@ -1710,27 +1752,27 @@ disclosure) and is cross-linked in the chapter. | 4 | `SessionizeService` | class | MMCA.ADC.Conference.Infrastructure.Events.Sessionize | | 4 | `SessionScoreResponseGuardrail` | class | MMCA.ADC.Conference.Infrastructure.Sessions.Scoring | | 5 | `DependencyInjection` | class | MMCA.ADC.Conference.Infrastructure | -| 8 | `CategoryItemConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Categories | -| 8 | `ConferenceCategoryConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Categories | -| 8 | `EventConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Events | -| 8 | `EventQuestionAnswerConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Events | -| 8 | `EventSpeakerConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Events | -| 8 | `QuestionConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Questions | -| 8 | `RoomConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Events | -| 8 | `SessionAiScoreConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Sessions | -| 8 | `SpeakerCategoryItemConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Speakers | -| 8 | `SpeakerConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Speakers | -| 8 | `SpeakerQuestionAnswerConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Speakers | -| 9 | `ActivityConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Activities | | 9 | `ConferenceModuleDbSeeder` | class | MMCA.ADC.Conference.Infrastructure.Persistence.DbContexts.Seeding | -| 9 | `PartnerConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Partners | -| 9 | `SessionAssetConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.SessionAssets | -| 9 | `SessionCategoryItemConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Sessions | -| 9 | `SessionConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Sessions | -| 9 | `SessionQuestionAnswerConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Sessions | -| 9 | `SessionSpeakerConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Sessions | -| 9 | `SponsorConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Sponsors | | 12 | `ModuleApplicationDbContext` | class | MMCA.ADC.Conference.Infrastructure.Persistence.DbContexts | +| 14 | `ActivityConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Activities | +| 14 | `CategoryItemConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Categories | +| 14 | `ConferenceCategoryConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Categories | +| 14 | `EventConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Events | +| 14 | `EventQuestionAnswerConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Events | +| 14 | `EventSpeakerConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Events | +| 14 | `PartnerConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Partners | +| 14 | `QuestionConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Questions | +| 14 | `RoomConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Events | +| 14 | `SessionAiScoreConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Sessions | +| 14 | `SessionAssetConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.SessionAssets | +| 14 | `SessionCategoryItemConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Sessions | +| 14 | `SessionConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Sessions | +| 14 | `SessionQuestionAnswerConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Sessions | +| 14 | `SessionSpeakerConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Sessions | +| 14 | `SpeakerCategoryItemConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Speakers | +| 14 | `SpeakerConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Speakers | +| 14 | `SpeakerQuestionAnswerConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Speakers | +| 14 | `SponsorConfiguration` | class | MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Sponsors | ### G20 - ADC Conference - API, gRPC Contracts & Service Host @@ -1793,7 +1835,7 @@ disclosure) and is cross-linked in the chapter. ### G21 - ADC Conference - UI -> `group-21-conference-ui.md` | 162 types | The Conference Blazor pages (events, sessions, speakers, categories, questions, rooms, feedback, public, session-selection) and their UI services. +> `group-21-conference-ui.md` | 163 types | The Conference Blazor pages (events, sessions, speakers, categories, questions, rooms, feedback, public, session-selection) and their UI services. | Level | Type | Kind | Namespace | |-------|------|------|-----------| @@ -1824,6 +1866,7 @@ disclosure) and is cross-linked in the chapter. | 2 | `ActivityFormModel` | class | MMCA.ADC.Conference.UI.Pages.Activities | | 2 | `ConferenceCategoryItemEditModel` | class | MMCA.ADC.Conference.UI.Pages.Categories | | 2 | `PartnerFormModel` | class | MMCA.ADC.Conference.UI.Pages.Partners | +| 2 | `PublicReadAudience` | class | MMCA.ADC.Conference.UI.Pages.Public | | 2 | `QuestionFormModel` | class | MMCA.ADC.Conference.UI.Pages.Questions | | 2 | `RoomFormModel` | class | MMCA.ADC.Conference.UI.Pages.Rooms | | 2 | `SessionAssetDisplay` | class | MMCA.ADC.Conference.UI.Pages.SessionAssets | @@ -1962,14 +2005,14 @@ disclosure) and is cross-linked in the chapter. ### G22 - ADC Engagement Module (Session Bookmarks) -> `group-22-engagement-module.md` | 194 types | The Engagement bounded context end-to-end: bookmark aggregate, use cases, persistence, API/contracts/service, and feedback UI. +> `group-22-engagement-module.md` | 196 types | The Engagement bounded context end-to-end: bookmark aggregate, use cases, persistence, API/contracts/service, and feedback UI. | Level | Type | Kind | Namespace | |-------|------|------|-----------| | 0 | `AssemblyReference` | class | MMCA.ADC.Engagement.API | | 0 | `AssemblyReference` | class | MMCA.ADC.Engagement.Infrastructure | -| 0 | `AssemblyReference` | class | MMCA.ADC.Engagement.Application | | 0 | `AssemblyReference` | class | MMCA.ADC.Engagement.Domain | +| 0 | `AssemblyReference` | class | MMCA.ADC.Engagement.Application | | 0 | `AttendeeRow` | record | MMCA.ADC.Engagement.UI.Services.Lookups | | 0 | `AttendeeSearchField` | enum | MMCA.ADC.Engagement.UI.Services.Lookups | | 0 | `BadgePayload` | class | MMCA.ADC.Engagement.Shared.CheckIns.Badges | @@ -1978,9 +2021,9 @@ disclosure) and is cross-linked in the chapter. | 0 | `CheckInScope` | enum | MMCA.ADC.Engagement.Shared.CheckIns | | 0 | `CheckInSettings` | class | MMCA.ADC.Engagement.Shared.CheckIns | | 0 | `CheckInState` | enum | MMCA.ADC.Engagement.UI.Pages.CheckIns.Rooms | -| 0 | `ClassReference` | class | MMCA.ADC.Engagement.Application | | 0 | `ClassReference` | class | MMCA.ADC.Engagement.Infrastructure | | 0 | `ClassReference` | class | MMCA.ADC.Engagement.API | +| 0 | `ClassReference` | class | MMCA.ADC.Engagement.Application | | 0 | `ClassReference` | class | MMCA.ADC.Engagement.Domain | | 0 | `CreateBookmarkRequest` | record | MMCA.ADC.Engagement.Shared.UserSessionBookmarks | | 0 | `EngagementErrorResources` | class | MMCA.ADC.Engagement.API.Resources | @@ -2070,8 +2113,8 @@ disclosure) and is cross-linked in the chapter. | 3 | `LiveChannelSubscription` | class | MMCA.ADC.Engagement.UI.Services.SessionLive | | 4 | `BookmarkService` | class | MMCA.ADC.Engagement.UI.Services.Bookmarks | | 4 | `CheckInService` | class | MMCA.ADC.Engagement.UI.Services.CheckIns | -| 4 | `DependencyInjection` | class | MMCA.ADC.Engagement.API | | 4 | `DependencyInjection` | class | MMCA.ADC.Engagement.Infrastructure | +| 4 | `DependencyInjection` | class | MMCA.ADC.Engagement.API | | 4 | `DisabledUserEngagementExportService` | class | MMCA.ADC.Engagement.Shared.Exports | | 4 | `EventFeedback` | class | MMCA.ADC.Engagement.UI.Pages.Feedback | | 4 | `EventFeedbackService` | class | MMCA.ADC.Engagement.UI.Services.Feedback | @@ -2111,19 +2154,14 @@ disclosure) and is cross-linked in the chapter. | 7 | `PointsEntry` | class | MMCA.ADC.Engagement.Domain.Points | | 7 | `PointsSettings` | class | MMCA.ADC.Engagement.Shared.Points | | 7 | `UserSessionBookmark` | class | MMCA.ADC.Engagement.Domain.UserSessionBookmarks | -| 8 | `AttendeeBadgeConfiguration` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.CheckIns | | 8 | `BookmarkCountService` | class | MMCA.ADC.Engagement.Application.UserSessionBookmarks.Services | | 8 | `BookmarkManagementDomainService` | class | MMCA.ADC.Engagement.Domain.UserSessionBookmarks | | 8 | `GetBookmarkedSessionIdsHandler` | class | MMCA.ADC.Engagement.Application.UserSessionBookmarks.UseCases.GetBookmarkedSessionIds | | 8 | `GetLeaderboardHandler` | class | MMCA.ADC.Engagement.Application.Points.UseCases.GetLeaderboard | | 8 | `GetPointsOverviewHandler` | class | MMCA.ADC.Engagement.Application.Points.UseCases.GetPointsOverview | -| 8 | `LeaderboardOptInConfiguration` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.Points | -| 8 | `LivePollVoteConfiguration` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.LivePolls | -| 8 | `PointsEntryConfiguration` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.Points | -| 8 | `SessionQuestionConfiguration` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.SessionQuestions | -| 8 | `SessionQuestionUpvoteConfiguration` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.SessionQuestions | +| 8 | `UserDeletedBadgeHandler` | class | MMCA.ADC.Engagement.Application.CheckIns.IntegrationEventHandlers | +| 8 | `UserDeletedBookmarksHandler` | class | MMCA.ADC.Engagement.Application.UserSessionBookmarks.IntegrationEventHandlers | | 8 | `UserDeletedPointsHandler` | class | MMCA.ADC.Engagement.Application.Points.IntegrationEventHandlers | -| 8 | `UserSessionBookmarkConfiguration` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.UserSessionBookmarks | | 8 | `UserSessionBookmarkDTOMapper` | class | MMCA.ADC.Engagement.Application.UserSessionBookmarks.DTOs | | 9 | `BookmarkCountServiceGrpcAdapter` | class | MMCA.ADC.Engagement.Contracts | | 9 | `BookmarkCountsGrpcService` | class | MMCA.ADC.Engagement.Service.Grpc | @@ -2133,8 +2171,6 @@ disclosure) and is cross-linked in the chapter. | 9 | `GetOrCreateMyBadgeHandler` | class | MMCA.ADC.Engagement.Application.CheckIns.UseCases.GetOrCreateMyBadge | | 9 | `GetUserBookmarksHandler` | class | MMCA.ADC.Engagement.Application.UserSessionBookmarks.UseCases.GetUserBookmarks | | 9 | `LiveEventService` | class | MMCA.ADC.Engagement.UI.Services.SessionLive | -| 9 | `LivePollConfiguration` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.LivePolls | -| 9 | `LivePollOptionConfiguration` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.LivePolls | | 9 | `LivePollVotingController` | class | MMCA.ADC.Engagement.API.Controllers | | 9 | `SetLeaderboardParticipationHandler` | class | MMCA.ADC.Engagement.Application.Points.UseCases.SetLeaderboardParticipation | | 10 | `AttendeeCheckedInPointsHandler` | class | MMCA.ADC.Engagement.Application.Points.IntegrationEventHandlers | @@ -2144,15 +2180,14 @@ disclosure) and is cross-linked in the chapter. | 10 | `OrganizerAttendance` | class | MMCA.ADC.Engagement.UI.Pages.CheckIns | | 10 | `PointsAwarder` | class | MMCA.ADC.Engagement.Application.Points.Services | | 11 | `BookmarksController` | class | MMCA.ADC.Engagement.API.Controllers | -| 11 | `CheckInConfiguration` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.CheckIns | | 11 | `CheckInDTOMapper` | class | MMCA.ADC.Engagement.Application.CheckIns.DTOs | | 11 | `CheckInProcessor` | class | MMCA.ADC.Engagement.Application.CheckIns.Services | | 11 | `EngagementUIModule` | class | MMCA.ADC.Engagement.UI | | 11 | `GetAttendanceStatsHandler` | class | MMCA.ADC.Engagement.Application.CheckIns.UseCases.GetAttendanceStats | | 11 | `UserEngagementExportService` | class | MMCA.ADC.Engagement.Application.Exports | | 12 | `CheckInAttendeeHandler` | class | MMCA.ADC.Engagement.Application.CheckIns.UseCases.CheckInAttendee | -| 12 | `DependencyInjection` | class | MMCA.ADC.Engagement.UI | | 12 | `DependencyInjection` | class | MMCA.ADC.Engagement.Application | +| 12 | `DependencyInjection` | class | MMCA.ADC.Engagement.UI | | 12 | `ManualCheckInHandler` | class | MMCA.ADC.Engagement.Application.CheckIns.UseCases.ManualCheckIn | | 12 | `ModuleApplicationDbContext` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.DbContexts | | 12 | `RecordRoomCheckInHandler` | class | MMCA.ADC.Engagement.Application.CheckIns.UseCases.RecordRoomCheckIn | @@ -2160,10 +2195,20 @@ disclosure) and is cross-linked in the chapter. | 12 | `UserEngagementExportGrpcService` | class | MMCA.ADC.Engagement.Service.Grpc | | 12 | `UserEngagementExportServiceGrpcAdapter` | class | MMCA.ADC.Engagement.Contracts | | 13 | `DependencyInjection` | class | MMCA.ADC.Engagement.Contracts | +| 14 | `AttendeeBadgeConfiguration` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.CheckIns | +| 14 | `CheckInConfiguration` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.CheckIns | +| 14 | `LeaderboardOptInConfiguration` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.Points | +| 14 | `LivePollConfiguration` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.LivePolls | +| 14 | `LivePollOptionConfiguration` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.LivePolls | +| 14 | `LivePollVoteConfiguration` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.LivePolls | +| 14 | `PointsEntryConfiguration` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.Points | +| 14 | `SessionQuestionConfiguration` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.SessionQuestions | +| 14 | `SessionQuestionUpvoteConfiguration` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.SessionQuestions | +| 14 | `UserSessionBookmarkConfiguration` | class | MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.UserSessionBookmarks | ### G26 - ADC Engagement Live Layer (Real-Time Polls & Session Q&A) -> `group-23-engagement-live-layer.md` | 85 types | Real-time audience interaction in the Engagement bounded context: event-wide live polls with voting and moderated per-session Q&A with upvoting, over the SignalR hub-channel transport ([ADR-039](https://ivanball.github.io/docs/adr/039-live-channel-push.html)) and the cross-service gRPC live-channel adapter. +> `group-23-engagement-live-layer.md` | 87 types | Real-time audience interaction in the Engagement bounded context: event-wide live polls with voting and moderated per-session Q&A with upvoting, over the SignalR hub-channel transport ([ADR-039](https://ivanball.github.io/docs/adr/039-live-channel-push.html)) and the cross-service gRPC live-channel adapter. | Level | Type | Kind | Namespace | |-------|------|------|-----------| @@ -2231,6 +2276,8 @@ disclosure) and is cross-linked in the chapter. | 8 | `SessionQuestionUpvoteChangedHandler` | class | MMCA.ADC.Engagement.Application.SessionQuestions.DomainEventHandlers | | 8 | `SessionQuestionViewBuilder` | class | MMCA.ADC.Engagement.Application.SessionQuestions.Services | | 8 | `ToggleUpvoteHandler` | class | MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.ToggleUpvote | +| 8 | `UserDeletedSessionQuestionsHandler` | class | MMCA.ADC.Engagement.Application.SessionQuestions.IntegrationEventHandlers | +| 8 | `UserDeletedVotesHandler` | class | MMCA.ADC.Engagement.Application.LivePolls.IntegrationEventHandlers | | 9 | `DeleteLivePollHandler` | class | MMCA.ADC.Engagement.Application.LivePolls.UseCases.Delete | | 9 | `GetModerationQueueHandler` | class | MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.GetModerationQueue | | 9 | `GetSessionQuestionsHandler` | class | MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.GetSessionQuestions | @@ -2255,13 +2302,13 @@ disclosure) and is cross-linked in the chapter. ### G23 - ADC Identity Module (Users, Profiles, GDPR Export/Erasure) -> `group-24-identity-module.md` | 107 types | The Identity bounded context end-to-end: the User aggregate, change-password/delete/export use cases, persistence, API/contracts/service, and profile/user UI. +> `group-24-identity-module.md` | 108 types | The Identity bounded context end-to-end: the User aggregate, change-password/delete/export use cases, persistence, API/contracts/service, and profile/user UI. | Level | Type | Kind | Namespace | |-------|------|------|-----------| -| 0 | `AssemblyReference` | class | MMCA.ADC.Identity.Domain | -| 0 | `AssemblyReference` | class | MMCA.ADC.Identity.API | | 0 | `AssemblyReference` | class | MMCA.ADC.Identity.Infrastructure | +| 0 | `AssemblyReference` | class | MMCA.ADC.Identity.API | +| 0 | `AssemblyReference` | class | MMCA.ADC.Identity.Domain | | 0 | `AssemblyReference` | class | MMCA.ADC.Identity.Application | | 0 | `ClassReference` | class | MMCA.ADC.Identity.Infrastructure | | 0 | `ClassReference` | class | MMCA.ADC.Identity.API | @@ -2274,6 +2321,7 @@ disclosure) and is cross-linked in the chapter. | 0 | `IdentityErrorResources` | class | MMCA.ADC.Identity.API.Resources | | 0 | `IdentityRoutePaths` | class | MMCA.ADC.Identity.UI | | 0 | `IExternalLoginEmailVerifier` | interface | MMCA.ADC.Identity.Application.Users | +| 0 | `JwtAudience` | class | MMCA.ADC.Identity.Shared.Authorization | | 0 | `RemoveUserAvatarCommand` | record | MMCA.ADC.Identity.Application.Users.UseCases.RemoveUserAvatar | | 0 | `RoleEdit` | class | MMCA.ADC.Identity.UI.Pages.Roles | | 0 | `RoleList` | class | MMCA.ADC.Identity.UI.Pages.Roles | @@ -2286,7 +2334,6 @@ disclosure) and is cross-linked in the chapter. | 0 | `UserDataExportPointsEntryDTO` | record | MMCA.ADC.Identity.Shared.Users.DataExport | | 0 | `UserDataExportSubjectDTO` | record | MMCA.ADC.Identity.Shared.Users.DataExport | | 0 | `UserDataExportSubmittedQuestionDTO` | record | MMCA.ADC.Identity.Shared.Users.DataExport | -| 1 | `ChangePasswordRequestValidator` | class | MMCA.ADC.Identity.Application.Users.Validation | | 1 | `ConfirmEmailCommand` | record | MMCA.ADC.Identity.Application.Users.UseCases.ConfirmEmail | | 1 | `DisabledAttendeeQueryService` | class | MMCA.ADC.Identity.Shared.Users | | 1 | `ForgotPasswordCommand` | record | MMCA.ADC.Identity.Application.Users.UseCases.ForgotPassword | @@ -2297,6 +2344,7 @@ disclosure) and is cross-linked in the chapter. | 1 | `UserDataExportNotificationSectionDTO` | record | MMCA.ADC.Identity.Shared.Users.DataExport | | 1 | `UserDTO` | record | MMCA.ADC.Identity.Shared.Users | | 1 | `UserListDTO` | record | MMCA.ADC.Identity.Shared.Users | +| 2 | `ChangePasswordRequestValidator` | class | MMCA.ADC.Identity.Application.Users.Validation | | 2 | `ExportUserDataQuery` | record | MMCA.ADC.Identity.Application.Users.UseCases.ExportUserData | | 2 | `IdentityModule` | class | MMCA.ADC.Identity.API | | 2 | `SelfHttpWarmupTask` | class | MMCA.ADC.Identity.Service | @@ -2339,7 +2387,6 @@ disclosure) and is cross-linked in the chapter. | 8 | `SpeakerLinkedToUserHandler` | class | MMCA.ADC.Identity.Application.Speakers.IntegrationEventHandlers | | 8 | `SpeakerUnlinkedFromUserHandler` | class | MMCA.ADC.Identity.Application.Speakers.IntegrationEventHandlers | | 8 | `UserAdministrationService` | class | MMCA.ADC.Identity.Application.Users.Administration | -| 8 | `UserConfiguration` | class | MMCA.ADC.Identity.Infrastructure.Persistence.EntityConfiguration | | 8 | `UserDTOMapper` | class | MMCA.ADC.Identity.Application.Users.DTOs | | 9 | `AttendeeQueryServiceGrpcAdapter` | class | MMCA.ADC.Identity.Contracts | | 9 | `AttendeesGrpcService` | class | MMCA.ADC.Identity.Service.Grpc | @@ -2359,6 +2406,7 @@ disclosure) and is cross-linked in the chapter. | 14 | `AuthenticationService` | class | MMCA.ADC.Identity.Application.Users | | 14 | `ForgotPasswordHandler` | class | MMCA.ADC.Identity.Application.Users.UseCases.ForgotPassword | | 14 | `SendEmailConfirmationHandler` | class | MMCA.ADC.Identity.Application.Users.UseCases.SendEmailConfirmation | +| 14 | `UserConfiguration` | class | MMCA.ADC.Identity.Infrastructure.Persistence.EntityConfiguration | | 15 | `DependencyInjection` | class | MMCA.ADC.Identity.Application | | 15 | `IdentityModuleDbSeeder` | class | MMCA.ADC.Identity.Infrastructure.Persistence.DbContexts.Seeding | | 15 | `SetUserAvatarHandler` | class | MMCA.ADC.Identity.Application.Users.UseCases.SetUserAvatar | @@ -2533,37 +2581,37 @@ disclosure) and is cross-linked in the chapter. | 2 | `ContentPolicySettings` | class | MMCA.Common.AI.Guardrails | | 2 | `GuardrailChatClient` | class | MMCA.Common.AI.Chat | | 2 | `OpenAiProviderFactory` | class | MMCA.Common.AI.OpenAI | -| 2 | `PiiRedactionGuardrail` | class | MMCA.Common.AI.Guardrails | | 3 | `AnthropicAiServiceCollectionExtensions` | class | MMCA.Common.AI.Anthropic | -| 3 | `GuardrailServiceCollectionExtensions` | class | MMCA.Common.AI.Guardrails | | 3 | `OpenAiServiceCollectionExtensions` | class | MMCA.Common.AI.OpenAI | +| 3 | `PiiRedactionGuardrail` | class | MMCA.Common.AI.Guardrails | +| 4 | `GuardrailServiceCollectionExtensions` | class | MMCA.Common.AI.Guardrails | | 9 | `PromptTaggingChatClient` | class | MMCA.Common.AI.Chat | | 10 | `AiServiceCollectionExtensions` | class | MMCA.Common.AI | ### G25 - Testing & Quality Infrastructure -> `group-28-testing-infrastructure.md` | 2771 types | All test projects + the reusable Testing/Testing.E2E/Testing.UI bases, architecture-fitness tests, and the component Gallery harness; individual [Fact]s are rolled up by project (logged exception). +> `group-28-testing-infrastructure.md` | 2923 types | All test projects + the reusable Testing/Testing.E2E/Testing.UI bases, architecture-fitness tests, and the component Gallery harness; individual [Fact]s are rolled up by project (logged exception). Rolled up by project (individual `[Fact]`s not sectioned - logged exception). Reusable test infrastructure assemblies (sectioned in full in the chapter) are marked **(infra)**. | Test project (assembly) | Types | Levels | Kind | |--------------------------|-------|--------|------| -| `MMCA.ADC.Architecture.Tests` **(infra)** | 56 | L0-L15 | | +| `MMCA.ADC.Architecture.Tests` **(infra)** | 57 | L0-L16 | | | `MMCA.ADC.Conference.API.Tests` | 26 | L0-L11 | | -| `MMCA.ADC.Conference.Application.Tests` | 193 | L0-L16 | | +| `MMCA.ADC.Conference.Application.Tests` | 196 | L0-L16 | | | `MMCA.ADC.Conference.Domain.Tests` | 34 | L6-L10 | | -| `MMCA.ADC.Conference.Infrastructure.Tests` | 11 | L0-L10 | | +| `MMCA.ADC.Conference.Infrastructure.Tests` | 11 | L0-L15 | | | `MMCA.ADC.Conference.IntegrationTests` | 41 | L1-L18 | | | `MMCA.ADC.Conference.Scoring.Evaluation.Tests` | 10 | L0-L4 | | | `MMCA.ADC.Conference.Shared.Tests` | 9 | L0-L10 | | -| `MMCA.ADC.Conference.UI.Tests` | 66 | L1-L11 | | +| `MMCA.ADC.Conference.UI.Tests` | 81 | L0-L11 | | | `MMCA.ADC.CrossService.IntegrationTests` | 13 | L1-L18 | | | `MMCA.ADC.E2E.Tests` | 92 | L0-L8 | | | `MMCA.ADC.Engagement.API.Tests` | 11 | L1-L12 | | -| `MMCA.ADC.Engagement.Application.Tests` | 65 | L1-L15 | | +| `MMCA.ADC.Engagement.Application.Tests` | 70 | L1-L15 | | | `MMCA.ADC.Engagement.Domain.Tests` | 11 | L7-L11 | | -| `MMCA.ADC.Engagement.Infrastructure.Tests` | 4 | L1-L11 | | +| `MMCA.ADC.Engagement.Infrastructure.Tests` | 4 | L1-L16 | | | `MMCA.ADC.Engagement.IntegrationTests` | 22 | L0-L18 | | | `MMCA.ADC.Engagement.Shared.Tests` | 6 | L1-L10 | | | `MMCA.ADC.Engagement.UI.Tests` | 36 | L0-L11 | | @@ -2573,28 +2621,30 @@ infrastructure assemblies (sectioned in full in the chapter) are marked **(infra | `MMCA.ADC.Identity.Domain.Tests` | 4 | L8-L8 | | | `MMCA.ADC.Identity.Infrastructure.Tests` | 9 | L0-L16 | | | `MMCA.ADC.Identity.IntegrationTests` | 36 | L0-L19 | | -| `MMCA.ADC.Identity.Shared.Tests` | 2 | L2-L4 | | +| `MMCA.ADC.Identity.Shared.Tests` | 3 | L1-L4 | | | `MMCA.ADC.Identity.UI.Tests` | 11 | L3-L8 | | | `MMCA.ADC.Notification.API.Tests` | 1 | L4-L4 | | | `MMCA.ADC.Notification.Application.Tests` | 4 | L2-L15 | | | `MMCA.ADC.Notification.IntegrationTests` | 9 | L1-L18 | | | `MMCA.ADC.ServiceBusEmulator.IntegrationTests` | 3 | L4-L6 | | -| `MMCA.ADC.Services.Tests` | 7 | L0-L13 | | +| `MMCA.ADC.Services.Tests` | 17 | L0-L13 | | | `MMCA.ADC.UI.Web.Tests` | 7 | L14-L15 | | | `MMCA.Common.AI.Tests` | 37 | L0-L10 | | -| `MMCA.Common.API.Tests` | 155 | L0-L18 | | -| `MMCA.Common.Application.Tests` | 398 | L0-L16 | | -| `MMCA.Common.Architecture.Tests` **(infra)** | 230 | L0-L14 | | +| `MMCA.Common.API.Tests` | 170 | L0-L18 | | +| `MMCA.Common.Application.Tests` | 401 | L0-L16 | | +| `MMCA.Common.Architecture.Tests` **(infra)** | 254 | L0-L14 | | | `MMCA.Common.Aspire.Hosting.Tests` | 4 | L0-L12 | | -| `MMCA.Common.Aspire.Tests` | 50 | L0-L13 | | +| `MMCA.Common.Aspire.Tests` | 52 | L0-L13 | | | `MMCA.Common.Benchmarks` | 6 | L0-L5 | | -| `MMCA.Common.Domain.Tests` | 62 | L0-L8 | | +| `MMCA.Common.Domain.Tests` | 64 | L0-L8 | | | `MMCA.Common.Gateway.Tests` | 7 | L0-L4 | | -| `MMCA.Common.Grpc.Tests` | 16 | L0-L4 | | +| `MMCA.Common.Grpc.Tests` | 18 | L0-L4 | | | `MMCA.Common.Infrastructure.PostgreSQL.Tests` | 7 | L2-L14 | | | `MMCA.Common.Infrastructure.Redis.Tests` | 2 | L5-L5 | | -| `MMCA.Common.Infrastructure.Tests` | 499 | L0-L17 | | -| `MMCA.Common.Shared.Tests` | 58 | L0-L6 | | +| `MMCA.Common.Infrastructure.SQLServer.Tests` | 8 | L1-L15 | | +| `MMCA.Common.Infrastructure.Tests` | 514 | L0-L18 | | +| `MMCA.Common.LoadTests` | 14 | L0-L17 | | +| `MMCA.Common.Shared.Tests` | 60 | L0-L6 | | | `MMCA.Common.Testing` **(infra)** | 25 | L0-L14 | | | `MMCA.Common.Testing.Architecture` **(infra)** | 68 | L0-L5 | | | `MMCA.Common.Testing.Aspire` | 10 | L0-L3 | | @@ -2602,9 +2652,9 @@ infrastructure assemblies (sectioned in full in the chapter) are marked **(infra | `MMCA.Common.Testing.Aspire.Tests` | 8 | L1-L2 | | | `MMCA.Common.Testing.E2E` **(infra)** | 32 | L0-L4 | | | `MMCA.Common.Testing.Tests` | 27 | L0-L15 | | -| `MMCA.Common.Testing.UI` **(infra)** | 19 | L0-L5 | | -| `MMCA.Common.UI.E2E.Tests` | 20 | L2-L13 | | +| `MMCA.Common.Testing.UI` **(infra)** | 18 | L0-L5 | | +| `MMCA.Common.UI.E2E.Tests` | 21 | L0-L13 | | | `MMCA.Common.UI.Gallery` **(infra)** | 11 | L0-L9 | | -| `MMCA.Common.UI.Tests` | 152 | L0-L8 | | -| `MMCA.Common.UI.Web.Tests` | 12 | L0-L5 | | +| `MMCA.Common.UI.Tests` | 170 | L0-L9 | | +| `MMCA.Common.UI.Web.Tests` | 24 | L0-L14 | | diff --git a/docs-src/onboarding/00-index.md b/docs-src/onboarding/00-index.md index 26638289..6cc645e7 100644 --- a/docs-src/onboarding/00-index.md +++ b/docs-src/onboarding/00-index.md @@ -68,7 +68,7 @@ parser (`Tools/invtool/`), so the type list, namespaces, and `file:line` are exa are resolved by **namespace-aware name matching**; ~96% bind by namespace visibility, the rest by a globally-unique-name fallback (586 edges), and 38 references are dropped as ambiguous. The functional grouping is then applied mechanically (`Tools/invtool/classify.ps1` → `00-group-taxonomy.md`), so every one of the -**5,034** distinct type nodes maps to exactly one group with no silent drops. See the +**5,234** distinct type nodes maps to exactly one group with no silent drops. See the [manifest's accuracy note](00-dependency-manifest.md#edge-resolution--accuracy) for residual caveats. --- @@ -79,7 +79,7 @@ then applied mechanically (`Tools/invtool/classify.ps1` → `00-group-taxonomy.m | File | Contents | |------|----------| | [`00-primer.md`](00-primer.md) | Cross-cutting concepts, the BCL/NuGet stack, build/language conventions, and the 34-category rubric, taught **once** | -| [`00-inventory.md`](00-inventory.md) | Phase 0, every in-scope type (5,034 distinct), mechanically extracted, with `file:line` | +| [`00-inventory.md`](00-inventory.md) | Phase 0, every in-scope type (5,234 distinct), mechanically extracted, with `file:line` | | [`00-dependency-manifest.md`](00-dependency-manifest.md) | Phase 1a, per-type first-party deps + computed Level; the 36 cycles | | [`00-group-taxonomy.md`](00-group-taxonomy.md) | Phase 1b, the ordered groups + every type's group assignment (the primary axis) | @@ -92,28 +92,28 @@ then applied mechanically (`Tools/invtool/classify.ps1` → `00-group-taxonomy.m | 4 | [Domain & Integration Events + Outbox Dual-Dispatch](group-04-events-outbox.md) | 38 | Event contracts, dispatcher, transactional outbox/inbox (incl. the async `OutboxFinalizer`), message buses, `OutboxSettings` | | 5 | [CQRS: Commands, Queries & the Decorator Pipeline](group-05-cqrs-pipeline.md) | 61 | Handler abstraction + the logging/transaction/caching/feature-gate/idempotency decorators (incl. the cache-stampede lock) | | 6 | [Validation](group-06-validation.md) | 22 | FluentValidation contracts + failure mapping that gate commands | -| 7 | [Persistence & EF Core](group-07-persistence-ef-core.md) | 165 | `SQLServerDbContext` over abstract `ApplicationDbContext`, interceptors (incl. the deferred-dispatch record), repositories, engine-aware entity config, data-source routing, conventions (incl. the soft-delete unique-index convention), factories, and the persistence/audit-trail/connection-string/data-source/tenancy settings classes (regrouped here from the module-system chapter on 2026-09-05) | -| 8 | [Authentication & Authorization](group-08-auth.md) | 153 | JWT/JWKS dual-fetch, the shared `AuthenticationServiceBase` login/refresh workflow (`IAuthUser`), current-user/claims, password hashing, cookie sessions, role policies + the permission-based authorization mechanism (registry, `[HasPermission]`), the `RoleValue` base, the external-auth-broker contract ([ADR-042](https://ivanball.github.io/docs/adr/042-device-capability-abstraction.html)/043), the JWT/JWKS settings (`JwtSettings`, `JwksSettings`, `JwtSigningAlgorithm`) | +| 7 | [Persistence & EF Core](group-07-persistence-ef-core.md) | 177 | `SQLServerDbContext` over abstract `ApplicationDbContext`, interceptors (incl. the deferred-dispatch record), repositories, engine-aware entity config, data-source routing, conventions (incl. the soft-delete unique-index convention), factories, and the persistence/audit-trail/connection-string/data-source/tenancy settings classes (regrouped here from the module-system chapter on 2026-09-05) | +| 8 | [Authentication & Authorization](group-08-auth.md) | 162 | JWT/JWKS dual-fetch, the shared `AuthenticationServiceBase` login/refresh workflow (`IAuthUser`), current-user/claims, password hashing, cookie sessions, role policies + the permission-based authorization mechanism (registry, `[HasPermission]`), the `RoleValue` base, the external-auth-broker contract ([ADR-042](https://ivanball.github.io/docs/adr/042-device-capability-abstraction.html)/043), the JWT/JWKS settings (`JwtSettings`, `JwksSettings`, `JwtSigningAlgorithm`) | | 9 | [Caching](group-09-caching.md) | 9 | The cache abstraction + its invalidation-aware decorator integration, `CacheSettings` | | 10 | [Notifications (Push + In-App Inbox + Email)](group-10-notifications.md) | 59 | Push (SignalR), in-app inbox, email, recipient providers, the [ADR-039](https://ivanball.github.io/docs/adr/039-live-channel-push.html) hub-channel live publisher, [ADR-044](https://ivanball.github.io/docs/adr/044-native-push-delivery.html) native OS-level push (Azure Notification Hubs, shipped inert), ADC Notification module + its cross-service gRPC live-channel plumbing + extractable-host Kestrel config | | 11 | [Navigation Metadata & Populators](group-11-navigation-populators.md) | 12 | EF-decoupled cross-container/cross-source eager loading ([ADR-002](https://ivanball.github.io/docs/adr/002-navigation-populators.html)) | | 12 | [API Hosting, Middleware, Idempotency & DTO/Contract Mapping](group-12-api-hosting-mapping.md) | 88 | Controller bases (incl. OAuth + service-info + API versioning, [ADR-046](https://ivanball.github.io/docs/adr/046-http-api-versioning.html)), middleware (incl. soft-deleted-user revocation, [ADR-047](https://ivanball.github.io/docs/adr/047-soft-deleted-user-session-revocation.html)), startup, model binders, JSON converters, feature mgmt, idempotency, mapping, edge error localization (i18n), authenticated output caching ([ADR-040](https://ivanball.github.io/docs/adr/040-authenticated-output-caching-for-public-reads.html)), app-association/deep-link endpoints ([ADR-043](https://ivanball.github.io/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html)) | -| 13 | [gRPC & Inter-Service Contracts](group-13-grpc-contracts.md) | 6 | Typed gRPC clients/servers, interceptors, Result-over-the-wire ([ADR-007](https://ivanball.github.io/docs/adr/007-grpc-extraction.html)) | -| 14 | [Module System, Composition & Configuration](group-14-module-system-composition.md) | 88 | `IModule` + Kahn-ordered loader, DI composition roots, data-source attributes, options-binding plumbing (the per-subsystem settings classes live with their subsystems since 2026-09-05), plus the host-level infrastructure services: managed file storage + image processing ([ADR-045](https://ivanball.github.io/docs/adr/045-managed-file-storage-and-avatars.html)), native-push sender + device registrar ([ADR-044](https://ivanball.github.io/docs/adr/044-native-push-delivery.html)), `TenantContext`, the upcasting/fault integration-event consumers and `PeriodicBackgroundService` | -| 15 | [Common UI Framework](group-15-common-ui-framework.md) | 153 | Reusable MudBlazor building blocks: data-grid list page base, theme, common pages/services, the Blazor Server UI-host hardening kit (circuit limits + UI rate limiting), i18n culture bootstrap + day/dark `ThemeService`, user-preference readers/writers, pseudo-localization gate, OAuth UI settings + token storage (Web/WASM), hub-channel subscriptions ([ADR-039](https://ivanball.github.io/docs/adr/039-live-channel-push.html)), the shared `DetailPageBase`, the email-confirmation page + UI service and the client-config endpoint (extracted from ADC in v1.211.0) | +| 13 | [gRPC & Inter-Service Contracts](group-13-grpc-contracts.md) | 7 | Typed gRPC clients/servers, interceptors, Result-over-the-wire ([ADR-007](https://ivanball.github.io/docs/adr/007-grpc-extraction.html)) | +| 14 | [Module System, Composition & Configuration](group-14-module-system-composition.md) | 87 | `IModule` + Kahn-ordered loader, DI composition roots, data-source attributes, options-binding plumbing (the per-subsystem settings classes live with their subsystems since 2026-09-05), plus the host-level infrastructure services: managed file storage + image processing ([ADR-045](https://ivanball.github.io/docs/adr/045-managed-file-storage-and-avatars.html)), native-push sender + device registrar ([ADR-044](https://ivanball.github.io/docs/adr/044-native-push-delivery.html)), `TenantContext`, the upcasting/fault integration-event consumers and `PeriodicBackgroundService` | +| 15 | [Common UI Framework](group-15-common-ui-framework.md) | 174 | Reusable MudBlazor building blocks: data-grid list page base, theme, common pages/services, the Blazor Server UI-host hardening kit (circuit limits + UI rate limiting), i18n culture bootstrap + day/dark `ThemeService`, user-preference readers/writers, pseudo-localization gate, OAuth UI settings + token storage (Web/WASM), hub-channel subscriptions ([ADR-039](https://ivanball.github.io/docs/adr/039-live-channel-push.html)), the shared `DetailPageBase`, the email-confirmation page + UI service and the client-config endpoint (extracted from ADC in v1.211.0) | | 16 | [Aspire Orchestration & Service Defaults](group-16-aspire-orchestration.md) | 65 | AppHost wiring, ServiceDefaults, warmup, telemetry, security helpers, the shared `HttpResilienceDefaults` Polly source of truth, the shared `HealthCheckTags` liveness/readiness vocabulary | | 17 | [ADC Conference, Domain Model & Module Contracts](group-17-conference-domain.md) | 111 | Event/Session/Speaker/Category/Question/Partner aggregates + domain events + invariants + Shared contracts (incl. `ConferencePermissions`, the current/next-event selector + live-validation contracts) | | 18 | [ADC Conference, Application & Use Cases](group-18-conference-application.md) | 358 | Conference CQRS handlers, validators (incl. the per-field session validation-rule family), DTOs, specs, Sessionize import, decision-support analytics, batch bookmark-count query, event-filtering-by-role handlers, calendar (.ics) export slice ([ADR-042](https://ivanball.github.io/docs/adr/042-device-capability-abstraction.html)) | | 19 | [ADC Conference, Infrastructure & Persistence](group-19-conference-infrastructure.md) | 29 | Conference DbContext registration, EF configs, seeding, infra services | | 20 | [ADC Conference, API, gRPC Contracts & Service Host](group-20-conference-api-grpc.md) | 52 | REST controllers, `.Contracts` gRPC, the extractable service host (incl. its Kestrel config), the gRPC adapters (incl. cross-service live-validation), localized error resources | -| 21 | [ADC Conference, UI](group-21-conference-ui.md) | 162 | Conference Blazor pages + UI services (incl. the Partner management pages), the single canonical ADC Home page (bound to the Conference DTOs through the UI services), the AI-scoring poll recovery tracker, calendar/QR export UI, OfflineBanner, PresenterLayout on Common theme providers | -| 22 | [ADC Engagement Module (Session Bookmarks)](group-22-engagement-module.md) | 194 | The attendee-activity slice of the Engagement bounded context, four capability families: the session-bookmark aggregate, the QR badge check-in surface (organizer scanning, manual fallback, attendee self-service, attendance rollup), the points economy (append-only ledger + opt-in public leaderboard), and the feedback UI; plus use cases, persistence, API/contracts/service, the durable live-channel publish queue ([ADR-039](https://ivanball.github.io/docs/adr/039-live-channel-push.html)), the cross-service user-engagement export slice (gRPC); the live-layer UI services (`SessionLiveUIService`, `LivePollUIService`, `SessionQuestionUIService`, `LiveEventService`, `SessionLookupService`) and the live-poll EF configurations sit here too (regrouped from the live-layer chapter on 2026-09-05) | -| 23 | [ADC Engagement Live Layer (Real-Time Polls & Session Q&A)](group-23-engagement-live-layer.md) | 85 | Event-wide live polls with voting + moderated per-session Q&A with upvoting, over the [ADR-039](https://ivanball.github.io/docs/adr/039-live-channel-push.html) hub-channel transport and the cross-service gRPC live-channel adapter (HappeningNow / SessionLive / PresenterView) | -| 24 | [ADC Identity Module (Users, Profiles, GDPR Export/Erasure)](group-24-identity-module.md) | 107 | The Identity bounded context end-to-end (incl. `IdentityPermissions`, user culture/theme preferences, the [ADR-045](https://ivanball.github.io/docs/adr/045-managed-file-storage-and-avatars.html) user-avatar photo slice end to end, the external-login email verifier + extractable-host Kestrel config; `AuthenticationService` now extends Common's shared base) | +| 21 | [ADC Conference, UI](group-21-conference-ui.md) | 163 | Conference Blazor pages + UI services (incl. the Partner management pages), the single canonical ADC Home page (bound to the Conference DTOs through the UI services), the AI-scoring poll recovery tracker, calendar/QR export UI, OfflineBanner, PresenterLayout on Common theme providers | +| 22 | [ADC Engagement Module (Session Bookmarks)](group-22-engagement-module.md) | 196 | The attendee-activity slice of the Engagement bounded context, four capability families: the session-bookmark aggregate, the QR badge check-in surface (organizer scanning, manual fallback, attendee self-service, attendance rollup), the points economy (append-only ledger + opt-in public leaderboard), and the feedback UI; plus use cases, persistence, API/contracts/service, the durable live-channel publish queue ([ADR-039](https://ivanball.github.io/docs/adr/039-live-channel-push.html)), the cross-service user-engagement export slice (gRPC); the live-layer UI services (`SessionLiveUIService`, `LivePollUIService`, `SessionQuestionUIService`, `LiveEventService`, `SessionLookupService`) and the live-poll EF configurations sit here too (regrouped from the live-layer chapter on 2026-09-05) | +| 23 | [ADC Engagement Live Layer (Real-Time Polls & Session Q&A)](group-23-engagement-live-layer.md) | 87 | Event-wide live polls with voting + moderated per-session Q&A with upvoting, over the [ADR-039](https://ivanball.github.io/docs/adr/039-live-channel-push.html) hub-channel transport and the cross-service gRPC live-channel adapter (HappeningNow / SessionLive / PresenterView) | +| 24 | [ADC Identity Module (Users, Profiles, GDPR Export/Erasure)](group-24-identity-module.md) | 108 | The Identity bounded context end-to-end (incl. `IdentityPermissions`, user culture/theme preferences, the [ADR-045](https://ivanball.github.io/docs/adr/045-managed-file-storage-and-avatars.html) user-avatar photo slice end to end, the external-login email verifier + extractable-host Kestrel config; `AuthenticationService` now extends Common's shared base) | | 25 | [ADC Application Host, UI Shell & Cross-Module Composition](group-25-adc-host-composition.md) | 17 | Blazor Web/WASM/WinUI shells, host pages/services, security (the circuit/rate-limit hardening kit now lives in the Common UI chapter), app composition, device-capability DI wiring, one-time preference migrator (the shared ADC Home page now lives in the Conference UI chapter) | | 26 | [Device Capability Abstraction Layer (Native Contracts, MAUI, Browser & Fallback Adapters)](group-26-device-capability-layer.md) | 103 | Per-capability interface contracts (biometric, geolocation, speech, push registration, clipboard/share/haptics, external auth/links, connectivity/battery, deep links) + their MAUI-native, browser-JS-interop, and inert-fallback implementations, selected per host at DI composition time ([ADR-042](https://ivanball.github.io/docs/adr/042-device-capability-abstraction.html)/043/044/045) | | 27 | [Common AI Integration](group-27-common-ai-integration.md) | 32 | The optional, provider-agnostic `MMCA.Common.AI` package plus its Anthropic adapter: the provider settings + validator + factory contract, one composition entry point that builds the delegating-client pipeline, the bounding and usage-metering chat clients over `IChatClient`, the token-estimator contract, the usage meter, the IChatGuardrail verdict contract + its GuardrailChatClient decorator, the shipped content-policy guardrail, the chat tool policy, request redaction, the golden-replay evaluation base, and the hashed, versioned prompt contract ([ADR-120](https://ivanball.github.io/docs/adr/120-governed-chat-client-boundary.html)) | -| 28 | [Testing & Quality Infrastructure](group-28-testing-infrastructure.md) | 2,771 | All test projects + reusable Testing/Testing.E2E/Testing.UI/**Testing.Architecture** bases (incl. the handler + decorator-pipeline test bases, the shared production-host/graceful-shutdown bases, the observability-convention fitness base, and the Gallery auth stubs) + architecture-fitness tests + Gallery + the BenchmarkDotNet perf-smoke suite | +| 28 | [Testing & Quality Infrastructure](group-28-testing-infrastructure.md) | 2,923 | All test projects + reusable Testing/Testing.E2E/Testing.UI/**Testing.Architecture** bases (incl. the handler + decorator-pipeline test bases, the shared production-host/graceful-shutdown bases, the observability-convention fitness base, and the Gallery auth stubs) + architecture-fitness tests + Gallery + the BenchmarkDotNet perf-smoke suite | ### DevOps & operations chapters | File | Contents | diff --git a/docs-src/onboarding/00-inventory.md b/docs-src/onboarding/00-inventory.md index 294f7a0a..9e3bf67a 100644 --- a/docs-src/onboarding/00-inventory.md +++ b/docs-src/onboarding/00-inventory.md @@ -3,21 +3,21 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file under `MMCA.Common/Source`, `MMCA.Common/Tests`, `MMCA.ADC/Source`, `MMCA.ADC/Tests`. -- Files scanned: **3860** (in-scope **3724**, generated/excluded **136**) -- Type declaration rows (including partial-class fragments): **5200** -- Distinct type nodes (partials collapsed): **5034** -- `extension(T)` blocks: **115** -- Source HEADs: MMCA.Common `834a8fa`, MMCA.ADC `3a32fd34` +- Files scanned: **4000** (in-scope **3862**, generated/excluded **138**) +- Type declaration rows (including partial-class fragments): **5402** +- Distinct type nodes (partials collapsed): **5234** +- `extension(T)` blocks: **118** +- Source HEADs: MMCA.Common `cd0026df`, MMCA.ADC `024e4a98` ## Counts by kind | Kind | Count (declarations) | |------|------| -| class | 4145 | -| record | 706 | -| interface | 257 | -| record struct | 45 | -| enum | 43 | +| class | 4315 | +| record | 728 | +| interface | 262 | +| enum | 47 | +| record struct | 46 | | delegate | 2 | | struct | 2 | @@ -27,11 +27,11 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file |----------|------| | MMCA.ADC.AppHost | 1 | | MMCA.ADC.AppHost.SmokeTests | 3 | -| MMCA.ADC.Architecture.Tests | 56 | +| MMCA.ADC.Architecture.Tests | 57 | | MMCA.ADC.Conference.API | 46 | | MMCA.ADC.Conference.API.Tests | 26 | | MMCA.ADC.Conference.Application | 358 | -| MMCA.ADC.Conference.Application.Tests | 193 | +| MMCA.ADC.Conference.Application.Tests | 196 | | MMCA.ADC.Conference.Contracts | 3 | | MMCA.ADC.Conference.Domain | 49 | | MMCA.ADC.Conference.Domain.Tests | 34 | @@ -42,14 +42,14 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | MMCA.ADC.Conference.Service | 3 | | MMCA.ADC.Conference.Shared | 62 | | MMCA.ADC.Conference.Shared.Tests | 9 | -| MMCA.ADC.Conference.UI | 162 | -| MMCA.ADC.Conference.UI.Tests | 66 | +| MMCA.ADC.Conference.UI | 163 | +| MMCA.ADC.Conference.UI.Tests | 81 | | MMCA.ADC.CrossService.IntegrationTests | 13 | | MMCA.ADC.E2E.Tests | 92 | | MMCA.ADC.Engagement.API | 11 | | MMCA.ADC.Engagement.API.Tests | 11 | -| MMCA.ADC.Engagement.Application | 87 | -| MMCA.ADC.Engagement.Application.Tests | 65 | +| MMCA.ADC.Engagement.Application | 91 | +| MMCA.ADC.Engagement.Application.Tests | 70 | | MMCA.ADC.Engagement.Contracts | 3 | | MMCA.ADC.Engagement.Domain | 29 | | MMCA.ADC.Engagement.Domain.Tests | 11 | @@ -73,8 +73,8 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | MMCA.ADC.Identity.Infrastructure.Tests | 9 | | MMCA.ADC.Identity.IntegrationTests | 36 | | MMCA.ADC.Identity.Service | 3 | -| MMCA.ADC.Identity.Shared | 19 | -| MMCA.ADC.Identity.Shared.Tests | 2 | +| MMCA.ADC.Identity.Shared | 20 | +| MMCA.ADC.Identity.Shared.Tests | 3 | | MMCA.ADC.Identity.UI | 10 | | MMCA.ADC.Identity.UI.Tests | 11 | | MMCA.ADC.Notification.API | 2 | @@ -86,7 +86,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | MMCA.ADC.Notification.Service | 3 | | MMCA.ADC.Notification.Shared | 4 | | MMCA.ADC.ServiceBusEmulator.IntegrationTests | 3 | -| MMCA.ADC.Services.Tests | 7 | +| MMCA.ADC.Services.Tests | 17 | | MMCA.ADC.UI | 16 | | MMCA.ADC.UI.Web.Client | 1 | | MMCA.ADC.UI.Web.Tests | 7 | @@ -95,29 +95,31 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | MMCA.Common.AI.OpenAI | 2 | | MMCA.Common.AI.Testing | 5 | | MMCA.Common.AI.Tests | 37 | -| MMCA.Common.API | 108 | -| MMCA.Common.API.Tests | 155 | -| MMCA.Common.Application | 273 | -| MMCA.Common.Application.Tests | 398 | -| MMCA.Common.Architecture.Tests | 230 | +| MMCA.Common.API | 114 | +| MMCA.Common.API.Tests | 170 | +| MMCA.Common.Application | 274 | +| MMCA.Common.Application.Tests | 401 | +| MMCA.Common.Architecture.Tests | 254 | | MMCA.Common.Aspire | 39 | | MMCA.Common.Aspire.Hosting | 5 | | MMCA.Common.Aspire.Hosting.Tests | 4 | -| MMCA.Common.Aspire.Tests | 50 | +| MMCA.Common.Aspire.Tests | 52 | | MMCA.Common.Benchmarks | 6 | | MMCA.Common.Domain | 54 | -| MMCA.Common.Domain.Tests | 62 | +| MMCA.Common.Domain.Tests | 64 | | MMCA.Common.Gateway | 14 | | MMCA.Common.Gateway.Tests | 7 | -| MMCA.Common.Grpc | 5 | -| MMCA.Common.Grpc.Tests | 16 | -| MMCA.Common.Infrastructure | 238 | +| MMCA.Common.Grpc | 6 | +| MMCA.Common.Grpc.Tests | 18 | +| MMCA.Common.Infrastructure | 250 | | MMCA.Common.Infrastructure.PostgreSQL.Tests | 7 | | MMCA.Common.Infrastructure.Redis.Tests | 2 | -| MMCA.Common.Infrastructure.Tests | 499 | +| MMCA.Common.Infrastructure.SQLServer.Tests | 8 | +| MMCA.Common.Infrastructure.Tests | 514 | | MMCA.Common.Infrastructure.Tests.MigrationsFixture | 1 | -| MMCA.Common.Shared | 104 | -| MMCA.Common.Shared.Tests | 58 | +| MMCA.Common.LoadTests | 14 | +| MMCA.Common.Shared | 105 | +| MMCA.Common.Shared.Tests | 60 | | MMCA.Common.Testing | 25 | | MMCA.Common.Testing.Architecture | 68 | | MMCA.Common.Testing.Aspire | 10 | @@ -125,14 +127,14 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | MMCA.Common.Testing.Aspire.Tests | 8 | | MMCA.Common.Testing.E2E | 32 | | MMCA.Common.Testing.Tests | 27 | -| MMCA.Common.Testing.UI | 19 | -| MMCA.Common.UI | 207 | -| MMCA.Common.UI.E2E.Tests | 20 | +| MMCA.Common.Testing.UI | 18 | +| MMCA.Common.UI | 213 | +| MMCA.Common.UI.E2E.Tests | 21 | | MMCA.Common.UI.Gallery | 11 | | MMCA.Common.UI.Maui | 35 | -| MMCA.Common.UI.Tests | 152 | -| MMCA.Common.UI.Web | 14 | -| MMCA.Common.UI.Web.Tests | 12 | +| MMCA.Common.UI.Tests | 170 | +| MMCA.Common.UI.Web | 29 | +| MMCA.Common.UI.Web.Tests | 24 | ## Full inventory @@ -194,6 +196,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `FormsConventionTests` | class | MMCA.ADC.Architecture.Tests | `MMCA.ADC.Architecture.Tests.Ui` | `MMCA.ADC.Architecture.Tests/Ui/FormsConventionTests.cs:18` | | `InlineStyleTests` | class | MMCA.ADC.Architecture.Tests | `MMCA.ADC.Architecture.Tests.Ui` | `MMCA.ADC.Architecture.Tests/Ui/InlineStyleTests.cs:16` | | `LocalizedTextConventionTests` | class | MMCA.ADC.Architecture.Tests | `MMCA.ADC.Architecture.Tests.Ui` | `MMCA.ADC.Architecture.Tests/Ui/LocalizedTextConventionTests.cs:14` | +| `MobileHostParityTests` | class | MMCA.ADC.Architecture.Tests | `MMCA.ADC.Architecture.Tests.Ui` | `MMCA.ADC.Architecture.Tests/Ui/MobileHostParityTests.cs:16` | | `SortableColumnConventionTests` | class | MMCA.ADC.Architecture.Tests | `MMCA.ADC.Architecture.Tests.Ui` | `MMCA.ADC.Architecture.Tests/Ui/SortableColumnConventionTests.cs:11` | | `StateManagementConventionTests` | class | MMCA.ADC.Architecture.Tests | `MMCA.ADC.Architecture.Tests.Ui` | `MMCA.ADC.Architecture.Tests/Ui/StateManagementConventionTests.cs:9` | | `TranslationCompletenessTests` | class | MMCA.ADC.Architecture.Tests | `MMCA.ADC.Architecture.Tests.Ui` | `MMCA.ADC.Architecture.Tests/Ui/TranslationCompletenessTests.cs:12` | @@ -246,15 +249,15 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ConferenceErrorResources` | class | MMCA.ADC.Conference.API | `MMCA.ADC.Conference.API.Resources` | `MMCA.ADC.Conference.API/Resources/ConferenceErrorResources.cs:11` | | `ConferencePermissionGrantsTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Authorization` | `MMCA.ADC.Conference.API.Tests/Authorization/ConferencePermissionGrantsTests.cs:21` | | `ActivitiesControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Activities` | `MMCA.ADC.Conference.API.Tests/Controllers/Activities/ActivitiesControllerTests.cs:28` | -| `CategoryItemsControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Categories` | `MMCA.ADC.Conference.API.Tests/Controllers/Categories/CategoryItemsControllerTests.cs:19` | -| `ConferenceCategoriesControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Categories` | `MMCA.ADC.Conference.API.Tests/Controllers/Categories/ConferenceCategoriesControllerTests.cs:20` | +| `CategoryItemsControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Categories` | `MMCA.ADC.Conference.API.Tests/Controllers/Categories/CategoryItemsControllerTests.cs:20` | +| `ConferenceCategoriesControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Categories` | `MMCA.ADC.Conference.API.Tests/Controllers/Categories/ConferenceCategoriesControllerTests.cs:21` | | `EventLifecycleControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Events` | `MMCA.ADC.Conference.API.Tests/Controllers/Events/EventLifecycleControllerTests.cs:23` | | `EventQuestionAnswersControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Events` | `MMCA.ADC.Conference.API.Tests/Controllers/Events/EventQuestionAnswersControllerTests.cs:23` | | `EventsControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Events` | `MMCA.ADC.Conference.API.Tests/Controllers/Events/EventsControllerTests.cs:27` | | `EventSpeakersControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Events` | `MMCA.ADC.Conference.API.Tests/Controllers/Events/EventSpeakersControllerTests.cs:25` | | `RoomsControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Events` | `MMCA.ADC.Conference.API.Tests/Controllers/Events/RoomsControllerTests.cs:26` | | `PartnersControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Partners` | `MMCA.ADC.Conference.API.Tests/Controllers/Partners/PartnersControllerTests.cs:28` | -| `QuestionsControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Questions` | `MMCA.ADC.Conference.API.Tests/Controllers/Questions/QuestionsControllerTests.cs:20` | +| `QuestionsControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Questions` | `MMCA.ADC.Conference.API.Tests/Controllers/Questions/QuestionsControllerTests.cs:21` | | `SessionAssetsControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.SessionAssets` | `MMCA.ADC.Conference.API.Tests/Controllers/SessionAssets/SessionAssetsControllerTests.cs:25` | | `FixedTimeProvider` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Sessions` | `MMCA.ADC.Conference.API.Tests/Controllers/Sessions/SessionSelectionControllerTests.cs:32` | | `SessionCategoryItemsControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Sessions` | `MMCA.ADC.Conference.API.Tests/Controllers/Sessions/SessionCategoryItemsControllerTests.cs:25` | @@ -264,7 +267,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `SessionSpeakersControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Sessions` | `MMCA.ADC.Conference.API.Tests/Controllers/Sessions/SessionSpeakersControllerTests.cs:25` | | `SpeakerCategoryItemsControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Speakers` | `MMCA.ADC.Conference.API.Tests/Controllers/Speakers/SpeakerCategoryItemsControllerTests.cs:25` | | `SpeakerLinksControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Speakers` | `MMCA.ADC.Conference.API.Tests/Controllers/Speakers/SpeakerLinksControllerTests.cs:20` | -| `SpeakersControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Speakers` | `MMCA.ADC.Conference.API.Tests/Controllers/Speakers/SpeakersControllerTests.cs:28` | +| `SpeakersControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Speakers` | `MMCA.ADC.Conference.API.Tests/Controllers/Speakers/SpeakersControllerTests.cs:30` | | `SpeakerSessionsControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Speakers` | `MMCA.ADC.Conference.API.Tests/Controllers/Speakers/SpeakerSessionsControllerTests.cs:23` | | `SponsorsControllerTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Controllers.Sponsors` | `MMCA.ADC.Conference.API.Tests/Controllers/Sponsors/SponsorsControllerTests.cs:28` | | `ConditionalWriteConventionTests` | class | MMCA.ADC.Conference.API.Tests | `MMCA.ADC.Conference.API.Tests.Conventions` | `MMCA.ADC.Conference.API.Tests/Conventions/ConditionalWriteConventionTests.cs:22` | @@ -365,17 +368,17 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `GetPublicRoomFilterQuery` | record | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.GetPublicRoomFilter` | `MMCA.ADC.Conference.Application/Events/UseCases/GetPublicRoomFilter/GetPublicRoomFilterQuery.cs:14` | | `PublishEventCommand` | record | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.Publish` | `MMCA.ADC.Conference.Application/Events/UseCases/Publish/PublishEventCommand.cs:13` | | `PublishEventHandler` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.Publish` | `MMCA.ADC.Conference.Application/Events/UseCases/Publish/PublishEventHandler.cs:13` | -| `CategorySyncStrategy` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/CategorySyncStrategy.cs:12` | +| `CategorySyncStrategy` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/CategorySyncStrategy.cs:13` | | `ISessionizeSyncStrategy` | interface | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/ISessionizeSyncStrategy.cs:7` | | `QuestionSyncStrategy` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/QuestionSyncStrategy.cs:12` | -| `RefreshFromSessionizeCommand` | record | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeCommand.cs:13` | +| `RefreshFromSessionizeCommand` | record | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeCommand.cs:15` | | `RefreshFromSessionizeHandler` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeHandler.cs:20` | | `RoomSyncStrategy` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RoomSyncStrategy.cs:20` | | `SessionizeSyncContext` | record | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/SessionizeSyncContext.cs:11` | | `SessionizeSyncResult` | record | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/ISessionizeSyncStrategy.cs:21` | | `SessionizeSyncWarnings` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/SessionizeSyncWarnings.cs:9` | | `SessionSyncStrategy` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/SessionSyncStrategy.cs:14` | -| `SpeakerSyncStrategy` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/SpeakerSyncStrategy.cs:14` | +| `SpeakerSyncStrategy` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/SpeakerSyncStrategy.cs:16` | | `RemoveEventQuestionAnswerCommand` | record | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.RemoveEventQuestionAnswer` | `MMCA.ADC.Conference.Application/Events/UseCases/RemoveEventQuestionAnswer/RemoveEventQuestionAnswerCommand.cs:9` | | `RemoveEventQuestionAnswerHandler` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.RemoveEventQuestionAnswer` | `MMCA.ADC.Conference.Application/Events/UseCases/RemoveEventQuestionAnswer/RemoveEventQuestionAnswerHandler.cs:21` | | `RemoveEventSpeakerCommand` | record | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.RemoveEventSpeaker` | `MMCA.ADC.Conference.Application/Events/UseCases/RemoveEventSpeaker/RemoveEventSpeakerCommand.cs:9` | @@ -391,7 +394,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `UpdateEventResult` | record | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.Update` | `MMCA.ADC.Conference.Application/Events/UseCases/Update/UpdateEventCommand.cs:25` | | `UpdateEventQuestionAnswerCommand` | record | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.UpdateEventQuestionAnswer` | `MMCA.ADC.Conference.Application/Events/UseCases/UpdateEventQuestionAnswer/UpdateEventQuestionAnswerCommand.cs:10` | | `UpdateEventQuestionAnswerCommandValidator` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.UpdateEventQuestionAnswer` | `MMCA.ADC.Conference.Application/Events/UseCases/UpdateEventQuestionAnswer/UpdateEventQuestionAnswerCommandValidator.cs:16` | -| `UpdateEventQuestionAnswerHandler` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.UpdateEventQuestionAnswer` | `MMCA.ADC.Conference.Application/Events/UseCases/UpdateEventQuestionAnswer/UpdateEventQuestionAnswerHandler.cs:21` | +| `UpdateEventQuestionAnswerHandler` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.UpdateEventQuestionAnswer` | `MMCA.ADC.Conference.Application/Events/UseCases/UpdateEventQuestionAnswer/UpdateEventQuestionAnswerHandler.cs:22` | | `UpdateRoomCommand` | record | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.UpdateRoom` | `MMCA.ADC.Conference.Application/Events/UseCases/UpdateRoom/UpdateRoomCommand.cs:15` | | `UpdateRoomCommandValidator` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.UpdateRoom` | `MMCA.ADC.Conference.Application/Events/UseCases/UpdateRoom/UpdateRoomCommandValidator.cs:7` | | `UpdateRoomHandler` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Events.UseCases.UpdateRoom` | `MMCA.ADC.Conference.Application/Events/UseCases/UpdateRoom/UpdateRoomHandler.cs:13` | @@ -459,7 +462,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `UpdateSessionAssetHandler` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.SessionAssets.UseCases.Update` | `MMCA.ADC.Conference.Application/SessionAssets/UseCases/Update/UpdateSessionAssetHandler.cs:20` | | `UploadSessionAssetCommand` | record | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.SessionAssets.UseCases.UploadFile` | `MMCA.ADC.Conference.Application/SessionAssets/UseCases/UploadFile/UploadSessionAssetCommand.cs:27` | | `UploadSessionAssetCommandValidator` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.SessionAssets.UseCases.UploadFile` | `MMCA.ADC.Conference.Application/SessionAssets/UseCases/UploadFile/UploadSessionAssetCommandValidator.cs:19` | -| `UploadSessionAssetHandler` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.SessionAssets.UseCases.UploadFile` | `MMCA.ADC.Conference.Application/SessionAssets/UseCases/UploadFile/UploadSessionAssetHandler.cs:35` | +| `UploadSessionAssetHandler` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.SessionAssets.UseCases.UploadFile` | `MMCA.ADC.Conference.Application/SessionAssets/UseCases/UploadFile/UploadSessionAssetHandler.cs:37` | | `ISessionAssetFieldsRequest` | interface | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.SessionAssets.Validation` | `MMCA.ADC.Conference.Application/SessionAssets/Validation/ISessionAssetFieldsRequest.cs:14` | | `SessionAssetFieldRules` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.SessionAssets.Validation` | `MMCA.ADC.Conference.Application/SessionAssets/Validation/SessionAssetValidationRules.cs:115` | | `SessionAssetFileNameRules` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.SessionAssets.Validation` | `MMCA.ADC.Conference.Application/SessionAssets/Validation/SessionAssetValidationRules.cs:84` | @@ -516,7 +519,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ScoreEventSessionsInternalCommandHandler` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Sessions.UseCases.DecisionSupport.ScoreEventSessions` | `MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/ScoreEventSessionsInternalCommandHandler.cs:42` | | `SessionScoringInput` | record | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Sessions.UseCases.DecisionSupport.ScoreEventSessions` | `MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/IAiScoringService.cs:47` | | `SessionScoringResult` | record | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Sessions.UseCases.DecisionSupport.ScoreEventSessions` | `MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/IAiScoringService.cs:54` | -| `SessionScoringRunner` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Sessions.UseCases.DecisionSupport.ScoreEventSessions` | `MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/SessionScoringRunner.cs:17` | +| `SessionScoringRunner` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Sessions.UseCases.DecisionSupport.ScoreEventSessions` | `MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/SessionScoringRunner.cs:23` | | `SpeakerInfo` | record | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Sessions.UseCases.DecisionSupport.ScoreEventSessions` | `MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/IAiScoringService.cs:37` | | `DeleteSessionHandler` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Sessions.UseCases.Delete` | `MMCA.ADC.Conference.Application/Sessions/UseCases/Delete/DeleteSessionHandler.cs:25` | | `CalendarExportMapper` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Sessions.UseCases.ExportCalendar` | `MMCA.ADC.Conference.Application/Sessions/UseCases/ExportCalendar/CalendarExportMapper.cs:15` | @@ -547,7 +550,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `UpdateSessionResult` | record | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Sessions.UseCases.Update` | `MMCA.ADC.Conference.Application/Sessions/UseCases/Update/UpdateSessionCommand.cs:25` | | `UpdateSessionQuestionAnswerCommand` | record | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Sessions.UseCases.UpdateSessionQuestionAnswer` | `MMCA.ADC.Conference.Application/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerCommand.cs:10` | | `UpdateSessionQuestionAnswerCommandValidator` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Sessions.UseCases.UpdateSessionQuestionAnswer` | `MMCA.ADC.Conference.Application/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerCommandValidator.cs:16` | -| `UpdateSessionQuestionAnswerHandler` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Sessions.UseCases.UpdateSessionQuestionAnswer` | `MMCA.ADC.Conference.Application/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerHandler.cs:21` | +| `UpdateSessionQuestionAnswerHandler` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Sessions.UseCases.UpdateSessionQuestionAnswer` | `MMCA.ADC.Conference.Application/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerHandler.cs:23` | | `ISessionFieldsRequest` | interface | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Sessions.Validation` | `MMCA.ADC.Conference.Application/Sessions/Validation/ISessionFieldsRequest.cs:13` | | `SessionAccessibilityInfoRules` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Sessions.Validation` | `MMCA.ADC.Conference.Application/Sessions/Validation/SessionValidationRules.cs:86` | | `SessionDescriptionRules` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Sessions.Validation` | `MMCA.ADC.Conference.Application/Sessions/Validation/SessionValidationRules.cs:36` | @@ -562,7 +565,6 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `SpeakerCategoryItemNavigationPopulator` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Speakers` | `MMCA.ADC.Conference.Application/Speakers/SpeakerCategoryItemNavigationPopulator.cs:11` | | `SpeakerEntityQueryService` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Speakers` | `MMCA.ADC.Conference.Application/Speakers/SpeakerEntityQueryService.cs:15` | | `SpeakerNavigationPopulator` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Speakers` | `MMCA.ADC.Conference.Application/Speakers/SpeakerNavigationPopulator.cs:11` | -| `SpeakerQuestionAnswerNavigationPopulator` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Speakers` | `MMCA.ADC.Conference.Application/Speakers/SpeakerQuestionAnswerNavigationPopulator.cs:11` | | `SpeakerDeletedHandler` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Speakers.DomainEventHandlers` | `MMCA.ADC.Conference.Application/Speakers/DomainEventHandlers/SpeakerDeletedHandler.cs:20` | | `SpeakerCategoryItemDTOMapper` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Speakers.DTOs` | `MMCA.ADC.Conference.Application/Speakers/DTOs/SpeakerCategoryItemDTOMapper.cs:12` | | `SpeakerDTOMapper` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Speakers.DTOs` | `MMCA.ADC.Conference.Application/Speakers/DTOs/SpeakerDTOMapper.cs:17` | @@ -627,6 +629,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `SponsorSortRules` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Sponsors.Validation` | `MMCA.ADC.Conference.Application/Sponsors/Validation/SponsorValidationRules.cs:126` | | `SponsorTwitterHandleRules` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Sponsors.Validation` | `MMCA.ADC.Conference.Application/Sponsors/Validation/SponsorValidationRules.cs:91` | | `SponsorWebsiteUrlRules` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Sponsors.Validation` | `MMCA.ADC.Conference.Application/Sponsors/Validation/SponsorValidationRules.cs:56` | +| `UserDeletedFeedbackHandler` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Users.IntegrationEventHandlers` | `MMCA.ADC.Conference.Application/Users/IntegrationEventHandlers/UserDeletedFeedbackHandler.cs:23` | | `UserRegisteredHandler` | class | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application.Users.IntegrationEventHandlers` | `MMCA.ADC.Conference.Application/Users/IntegrationEventHandlers/UserRegisteredHandler.cs:54` | | `ConferenceCrudRegistrationTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests` | `MMCA.ADC.Conference.Application.Tests/ConferenceCrudRegistrationTests.cs:36` | | `ActivityNavigationPopulatorTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Activities` | `MMCA.ADC.Conference.Application.Tests/Activities/ActivityNavigationPopulatorTests.cs:9` | @@ -644,6 +647,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `AddCategoryItemHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Categories.UseCases` | `MMCA.ADC.Conference.Application.Tests/Categories/UseCases/AddCategoryItemHandlerTests.cs:12` | | `ConferenceCategoryUpdateApplierTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Categories.UseCases` | `MMCA.ADC.Conference.Application.Tests/Categories/UseCases/ConferenceCategoryUpdateApplierTests.cs:19` | | `CreateConferenceCategoryHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Categories.UseCases` | `MMCA.ADC.Conference.Application.Tests/Categories/UseCases/CreateConferenceCategoryHandlerTests.cs:13` | +| `DeleteConferenceCategoryHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Categories.UseCases` | `MMCA.ADC.Conference.Application.Tests/Categories/UseCases/DeleteConferenceCategoryHandlerTests.cs:17` | | `RemoveCategoryItemHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Categories.UseCases` | `MMCA.ADC.Conference.Application.Tests/Categories/UseCases/RemoveCategoryItemHandlerTests.cs:11` | | `UpdateCategoryItemHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Categories.UseCases` | `MMCA.ADC.Conference.Application.Tests/Categories/UseCases/UpdateCategoryItemHandlerTests.cs:11` | | `AddCategoryItemCommandValidatorTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Categories.Validation` | `MMCA.ADC.Conference.Application.Tests/Categories/Validation/CategoryCommandValidatorTests.cs:8` | @@ -677,7 +681,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `PublishEventHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Events.UseCases.Publish` | `MMCA.ADC.Conference.Application.Tests/Events/UseCases/Publish/PublishEventHandlerTests.cs:11` | | `CategorySyncStrategyTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/CategorySyncStrategyTests.cs:15` | | `QuestionSyncStrategyTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/QuestionSyncStrategyTests.cs:16` | -| `RefreshFromSessionizeHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeHandlerTests.cs:16` | +| `RefreshFromSessionizeHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeHandlerTests.cs:17` | | `RoomSyncStrategyTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/RoomSyncStrategyTests.cs:11` | | `SessionSyncStrategyTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/SessionSyncStrategyTests.cs:15` | | `SpeakerSyncStrategyTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Events.UseCases.RefreshFromSessionize` | `MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/SpeakerSyncStrategyTests.cs:12` | @@ -686,7 +690,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `RemoveRoomHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Events.UseCases.RemoveRoom` | `MMCA.ADC.Conference.Application.Tests/Events/UseCases/RemoveRoom/RemoveRoomHandlerTests.cs:11` | | `UnpublishEventHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Events.UseCases.Unpublish` | `MMCA.ADC.Conference.Application.Tests/Events/UseCases/Unpublish/UnpublishEventHandlerTests.cs:11` | | `UpdateEventHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Events.UseCases.Update` | `MMCA.ADC.Conference.Application.Tests/Events/UseCases/Update/UpdateEventHandlerTests.cs:14` | -| `UpdateEventQuestionAnswerHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Events.UseCases.UpdateEventQuestionAnswer` | `MMCA.ADC.Conference.Application.Tests/Events/UseCases/UpdateEventQuestionAnswer/UpdateEventQuestionAnswerHandlerTests.cs:13` | +| `UpdateEventQuestionAnswerHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Events.UseCases.UpdateEventQuestionAnswer` | `MMCA.ADC.Conference.Application.Tests/Events/UseCases/UpdateEventQuestionAnswer/UpdateEventQuestionAnswerHandlerTests.cs:14` | | `UpdateRoomHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Events.UseCases.UpdateRoom` | `MMCA.ADC.Conference.Application.Tests/Events/UseCases/UpdateRoom/UpdateRoomHandlerTests.cs:11` | | `AddEventQuestionAnswerCommandValidatorTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Events.Validation` | `MMCA.ADC.Conference.Application.Tests/Events/Validation/CommandValidatorTests.cs:10` | | `AddEventSpeakerCommandValidatorTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Events.Validation` | `MMCA.ADC.Conference.Application.Tests/Events/Validation/CommandValidatorTests.cs:40` | @@ -729,8 +733,9 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `DeleteSessionAssetBlobInternalCommandHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.SessionAssets.UseCases` | `MMCA.ADC.Conference.Application.Tests/SessionAssets/UseCases/DeleteSessionAssetBlobInternalCommandHandlerTests.cs:15` | | `DeleteSessionAssetHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.SessionAssets.UseCases` | `MMCA.ADC.Conference.Application.Tests/SessionAssets/UseCases/DeleteSessionAssetHandlerTests.cs:14` | | `GetSessionAssetsHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.SessionAssets.UseCases` | `MMCA.ADC.Conference.Application.Tests/SessionAssets/UseCases/GetSessionAssetsHandlerTests.cs:13` | +| `UpdateSessionAssetHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.SessionAssets.UseCases` | `MMCA.ADC.Conference.Application.Tests/SessionAssets/UseCases/UpdateSessionAssetHandlerTests.cs:21` | | `UploadSessionAssetCommandMarkerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.SessionAssets.UseCases` | `MMCA.ADC.Conference.Application.Tests/SessionAssets/UseCases/UploadSessionAssetCommandMarkerTests.cs:11` | -| `UploadSessionAssetHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.SessionAssets.UseCases` | `MMCA.ADC.Conference.Application.Tests/SessionAssets/UseCases/UploadSessionAssetHandlerTests.cs:17` | +| `UploadSessionAssetHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.SessionAssets.UseCases` | `MMCA.ADC.Conference.Application.Tests/SessionAssets/UseCases/UploadSessionAssetHandlerTests.cs:18` | | `SessionAssetLinkRequestValidatorTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.SessionAssets.Validation` | `MMCA.ADC.Conference.Application.Tests/SessionAssets/Validation/SessionAssetLinkRequestValidatorTests.cs:7` | | `SessionAssetUpdateRequestValidatorTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.SessionAssets.Validation` | `MMCA.ADC.Conference.Application.Tests/SessionAssets/Validation/SessionAssetUpdateRequestValidatorTests.cs:7` | | `UploadSessionAssetCommandValidatorTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.SessionAssets.Validation` | `MMCA.ADC.Conference.Application.Tests/SessionAssets/Validation/UploadSessionAssetCommandValidatorTests.cs:12` | @@ -757,7 +762,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `GetSpeakerSessionOverlapHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Sessions.UseCases.DecisionSupport` | `MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/DecisionSupport/GetSpeakerSessionOverlapHandlerTests.cs:15` | | `ScoreEventSessionsInternalCommandHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Sessions.UseCases.DecisionSupport` | `MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/DecisionSupport/ScoreEventSessionsInternalCommandHandlerTests.cs:17` | | `ScoreEventSessionsInternalCommandMarkerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Sessions.UseCases.DecisionSupport` | `MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/DecisionSupport/ScoreEventSessionsInternalCommandMarkerTests.cs:14` | -| `SessionScoringRunnerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Sessions.UseCases.DecisionSupport` | `MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/DecisionSupport/SessionScoringRunnerTests.cs:16` | +| `SessionScoringRunnerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Sessions.UseCases.DecisionSupport` | `MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/DecisionSupport/SessionScoringRunnerTests.cs:17` | | `SessionSimilarityCalculatorTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Sessions.UseCases.DecisionSupport` | `MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/DecisionSupport/SessionSimilarityCalculatorTests.cs:6` | | `SpeakerLocalityHelperTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Sessions.UseCases.DecisionSupport` | `MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/DecisionSupport/SpeakerLocalityHelperTests.cs:9` | | `DeleteSessionHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Sessions.UseCases.Delete` | `MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/Delete/DeleteSessionHandlerTests.cs:15` | @@ -774,7 +779,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `RemoveSessionQuestionAnswerHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Sessions.UseCases.RemoveSessionQuestionAnswer` | `MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/RemoveSessionQuestionAnswer/RemoveSessionQuestionAnswerHandlerTests.cs:14` | | `RemoveSessionSpeakerHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Sessions.UseCases.RemoveSessionSpeaker` | `MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/RemoveSessionSpeaker/RemoveSessionSpeakerHandlerTests.cs:12` | | `UpdateSessionHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Sessions.UseCases.Update` | `MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/Update/UpdateSessionHandlerTests.cs:13` | -| `UpdateSessionQuestionAnswerHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Sessions.UseCases.UpdateSessionQuestionAnswer` | `MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerHandlerTests.cs:14` | +| `UpdateSessionQuestionAnswerHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Sessions.UseCases.UpdateSessionQuestionAnswer` | `MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerHandlerTests.cs:15` | | `AddSessionCategoryItemCommandValidatorTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Sessions.Validation` | `MMCA.ADC.Conference.Application.Tests/Sessions/Validation/SessionCommandValidatorTests.cs:109` | | `AddSessionQuestionAnswerCommandValidatorTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Sessions.Validation` | `MMCA.ADC.Conference.Application.Tests/Sessions/Validation/SessionCommandValidatorTests.cs:9` | | `AddSessionSpeakerCommandValidatorTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Sessions.Validation` | `MMCA.ADC.Conference.Application.Tests/Sessions/Validation/SessionCommandValidatorTests.cs:87` | @@ -788,7 +793,6 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `SpeakerCategoryItemNavigationPopulatorTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Speakers` | `MMCA.ADC.Conference.Application.Tests/Speakers/SpeakerCategoryItemNavigationPopulatorTests.cs:9` | | `SpeakerEntityQueryServiceTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Speakers` | `MMCA.ADC.Conference.Application.Tests/Speakers/SpeakerEntityQueryServiceTests.cs:17` | | `SpeakerNavigationPopulatorTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Speakers` | `MMCA.ADC.Conference.Application.Tests/Speakers/SpeakerNavigationPopulatorTests.cs:9` | -| `SpeakerQuestionAnswerNavigationPopulatorTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Speakers` | `MMCA.ADC.Conference.Application.Tests/Speakers/SpeakerQuestionAnswerNavigationPopulatorTests.cs:9` | | `Mocks` | record | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Speakers.DomainEventHandlers` | `MMCA.ADC.Conference.Application.Tests/Speakers/DomainEventHandlers/SpeakerDeletedHandlerTests.cs:17` | | `SpeakerDeletedHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Speakers.DomainEventHandlers` | `MMCA.ADC.Conference.Application.Tests/Speakers/DomainEventHandlers/SpeakerDeletedHandlerTests.cs:14` | | `SpeakerCategoryItemDTOMapperTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Speakers.DTOs` | `MMCA.ADC.Conference.Application.Tests/Speakers/DTOs/SpeakerCategoryItemDTOMapperTests.cs:8` | @@ -796,6 +800,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `SpeakerQuestionAnswerDTOMapperTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Speakers.DTOs` | `MMCA.ADC.Conference.Application.Tests/Speakers/DTOs/SpeakerQuestionAnswerDTOMapperTests.cs:8` | | `AddSpeakerCategoryItemHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Speakers.UseCases.AddSpeakerCategoryItem` | `MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/AddSpeakerCategoryItem/AddSpeakerCategoryItemHandlerTests.cs:12` | | `CreateSpeakerHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Speakers.UseCases.Create` | `MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/Create/CreateSpeakerHandlerTests.cs:14` | +| `SpeakerCreateRequestMapperTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Speakers.UseCases.Create` | `MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/Create/SpeakerCreateRequestMapperTests.cs:7` | | `GetPublicSpeakerCategoryItemFilterHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Speakers.UseCases.GetPublicSpeakerCategoryItemFilter` | `MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/GetPublicSpeakerCategoryItemFilter/GetPublicSpeakerCategoryItemFilterHandlerTests.cs:19` | | `GetPublicSpeakerFilterHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Speakers.UseCases.GetPublicSpeakerFilter` | `MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/GetPublicSpeakerFilter/GetPublicSpeakerFilterHandlerTests.cs:24` | | `GetSessionBookmarkCountHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Speakers.UseCases.GetSessionBookmarkCount` | `MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/GetSessionBookmarkCount/GetSessionBookmarkCountHandlerTests.cs:12` | @@ -820,6 +825,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `SponsorCreateRequestValidatorTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Sponsors.Validation` | `MMCA.ADC.Conference.Application.Tests/Sponsors/Validation/SponsorCreateRequestValidatorTests.cs:8` | | `SponsorUpdateRequestValidatorTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Sponsors.Validation` | `MMCA.ADC.Conference.Application.Tests/Sponsors/Validation/SponsorUpdateRequestValidatorTests.cs:8` | | `Fakes` | record | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Users.IntegrationEventHandlers` | `MMCA.ADC.Conference.Application.Tests/Users/IntegrationEventHandlers/UserRegisteredHandlerTests.cs:19` | +| `UserDeletedFeedbackHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Users.IntegrationEventHandlers` | `MMCA.ADC.Conference.Application.Tests/Users/IntegrationEventHandlers/UserDeletedFeedbackHandlerTests.cs:16` | | `UserRegisteredHandlerTests` | class | MMCA.ADC.Conference.Application.Tests | `MMCA.ADC.Conference.Application.Tests.Users.IntegrationEventHandlers` | `MMCA.ADC.Conference.Application.Tests/Users/IntegrationEventHandlers/UserRegisteredHandlerTests.cs:16` | | `DependencyInjection` | class | MMCA.ADC.Conference.Contracts | `MMCA.ADC.Conference.Contracts` | `MMCA.ADC.Conference.Contracts/DependencyInjection.cs:15` | | `EventLiveValidationServiceGrpcAdapter` | class | MMCA.ADC.Conference.Contracts | `MMCA.ADC.Conference.Contracts` | `MMCA.ADC.Conference.Contracts/EventLiveValidationServiceGrpcAdapter.cs:27` | @@ -1076,7 +1082,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `SpeakerDTOTests` | class | MMCA.ADC.Conference.Shared.Tests | `MMCA.ADC.Conference.Shared.Tests.Speakers` | `MMCA.ADC.Conference.Shared.Tests/Speakers/SpeakerDTOTests.cs:6` | | `ConferenceRoutePaths` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI` | `MMCA.ADC.Conference.UI/ConferenceRoutePaths.cs:8` | | `ConferenceUIModule` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI` | `MMCA.ADC.Conference.UI/ConferenceUIModule.cs:14` | -| `DependencyInjection` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI` | `MMCA.ADC.Conference.UI/DependencyInjection.cs:18` | +| `DependencyInjection` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI` | `MMCA.ADC.Conference.UI/DependencyInjection.cs:19` | | `ActivityCreate` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Activities` | `MMCA.ADC.Conference.UI/Pages/Activities/ActivityCreate.razor.cs:20` | | `ActivityCreateModel` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Activities` | `MMCA.ADC.Conference.UI/Pages/Activities/ActivityCreateModel.cs:11` | | `ActivityDetail` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Activities` | `MMCA.ADC.Conference.UI/Pages/Activities/ActivityDetail.razor.cs:20` | @@ -1106,7 +1112,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ADCHome` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Home` | `MMCA.ADC.Conference.UI/Pages/Home/ADCHome.razor.cs:22` | | `ADCHomeContent` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Home` | `MMCA.ADC.Conference.UI/Pages/Home/ADCHomeContent.cs:15` | | `ConferenceTrackInfo` | record | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Home` | `MMCA.ADC.Conference.UI/Pages/Home/ADCHomeContent.cs:95` | -| `EventPhase` | enum | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Home` | `MMCA.ADC.Conference.UI/Pages/Home/ADCHome.razor.cs:90` | +| `EventPhase` | enum | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Home` | `MMCA.ADC.Conference.UI/Pages/Home/ADCHome.razor.cs:93` | | `KeynoteSpeakerInfo` | record | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Home` | `MMCA.ADC.Conference.UI/Pages/Home/ADCHomeContent.cs:86` | | `PreConferenceWorkshopInfo` | record | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Home` | `MMCA.ADC.Conference.UI/Pages/Home/ADCHomeContent.cs:103` | | `PartnerCreate` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Partners` | `MMCA.ADC.Conference.UI/Pages/Partners/PartnerCreate.razor.cs:19` | @@ -1115,19 +1121,20 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `PartnerEditModel` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Partners` | `MMCA.ADC.Conference.UI/Pages/Partners/PartnerEditModel.cs:10` | | `PartnerFormModel` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Partners` | `MMCA.ADC.Conference.UI/Pages/Partners/PartnerFormModel.cs:18` | | `PartnerList` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Partners` | `MMCA.ADC.Conference.UI/Pages/Partners/PartnerList.razor.cs:19` | +| `PublicReadAudience` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Public` | `MMCA.ADC.Conference.UI/Pages/Public/PublicReadAudience.cs:10` | | `PublicScheduleRoomOptions` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Public` | `MMCA.ADC.Conference.UI/Pages/Public/PublicScheduleRoomOptions.cs:13` | | `PublicSessionListFilterState` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Public` | `MMCA.ADC.Conference.UI/Pages/Public/PublicSessionListFilterState.cs:12` | | `PublicActivityList` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Public.Activities` | `MMCA.ADC.Conference.UI/Pages/Public/Activities/PublicActivityList.razor.cs:21` | -| `PublicEventDetail` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Public.Events` | `MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventDetail.razor.cs:19` | -| `PublicEventList` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Public.Events` | `MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventList.razor.cs:31` | +| `PublicEventDetail` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Public.Events` | `MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventDetail.razor.cs:18` | +| `PublicEventList` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Public.Events` | `MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventList.razor.cs:30` | | `VenueMapLinks` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Public.Events` | `MMCA.ADC.Conference.UI/Pages/Public/Events/VenueMapLinks.cs:10` | | `PublicSessionDetail` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Public.Sessions` | `MMCA.ADC.Conference.UI/Pages/Public/Sessions/PublicSessionDetail.razor.cs:22` | -| `PublicSessionList` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Public.Sessions` | `MMCA.ADC.Conference.UI/Pages/Public/Sessions/PublicSessionList.razor.cs:30` | +| `PublicSessionList` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Public.Sessions` | `MMCA.ADC.Conference.UI/Pages/Public/Sessions/PublicSessionList.razor.cs:29` | | `PublicSessionListFilterBar` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Public.Sessions` | `MMCA.ADC.Conference.UI/Pages/Public/Sessions/PublicSessionListFilterBar.razor.cs:16` | | `PublicSessionListView` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Public.Sessions` | `MMCA.ADC.Conference.UI/Pages/Public/Sessions/PublicSessionListView.razor.cs:26` | | `SessionBookmarkButton` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Public.Sessions` | `MMCA.ADC.Conference.UI/Pages/Public/Sessions/SessionBookmarkButton.razor.cs:27` | | `PublicSpeakerDetail` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Public.Speakers` | `MMCA.ADC.Conference.UI/Pages/Public/Speakers/PublicSpeakerDetail.razor.cs:22` | -| `PublicSpeakerList` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Public.Speakers` | `MMCA.ADC.Conference.UI/Pages/Public/Speakers/PublicSpeakerList.razor.cs:33` | +| `PublicSpeakerList` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Public.Speakers` | `MMCA.ADC.Conference.UI/Pages/Public/Speakers/PublicSpeakerList.razor.cs:32` | | `PublicSponsorList` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Public.Sponsors` | `MMCA.ADC.Conference.UI/Pages/Public/Sponsors/PublicSponsorList.razor.cs:20` | | `QuestionCreate` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Questions` | `MMCA.ADC.Conference.UI/Pages/Questions/QuestionCreate.razor.cs:12` | | `QuestionCreateModel` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Questions` | `MMCA.ADC.Conference.UI/Pages/Questions/QuestionCreateModel.cs:11` | @@ -1136,7 +1143,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `QuestionFormModel` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Questions` | `MMCA.ADC.Conference.UI/Pages/Questions/QuestionFormModel.cs:24` | | `QuestionList` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Questions` | `MMCA.ADC.Conference.UI/Pages/Questions/QuestionList.razor.cs:12` | | `RoomCreate` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Rooms` | `MMCA.ADC.Conference.UI/Pages/Rooms/RoomCreate.razor.cs:13` | -| `RoomCreateModel` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Rooms` | `MMCA.ADC.Conference.UI/Pages/Rooms/RoomCreateModel.cs:12` | +| `RoomCreateModel` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Rooms` | `MMCA.ADC.Conference.UI/Pages/Rooms/RoomCreateModel.cs:11` | | `RoomDetail` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Rooms` | `MMCA.ADC.Conference.UI/Pages/Rooms/RoomDetail.razor.cs:16` | | `RoomEditModel` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Rooms` | `MMCA.ADC.Conference.UI/Pages/Rooms/RoomEditModel.cs:11` | | `RoomFormModel` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Pages.Rooms` | `MMCA.ADC.Conference.UI/Pages/Rooms/RoomFormModel.cs:24` | @@ -1206,7 +1213,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `IOrganizerEventFeedbackUIService` | interface | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Services.Feedback` | `MMCA.ADC.Conference.UI/Services/Feedback/IOrganizerFeedbackUIService.cs:11` | | `IOrganizerSessionFeedbackUIService` | interface | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Services.Feedback` | `MMCA.ADC.Conference.UI/Services/Feedback/IOrganizerFeedbackUIService.cs:27` | | `OrganizerEventFeedbackService` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Services.Feedback` | `MMCA.ADC.Conference.UI/Services/Feedback/OrganizerFeedbackService.cs:15` | -| `OrganizerSessionFeedbackService` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Services.Feedback` | `MMCA.ADC.Conference.UI/Services/Feedback/OrganizerFeedbackService.cs:66` | +| `OrganizerSessionFeedbackService` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Services.Feedback` | `MMCA.ADC.Conference.UI/Services/Feedback/OrganizerFeedbackService.cs:70` | | `IPartnerUIService` | interface | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Services.Partners` | `MMCA.ADC.Conference.UI/Services/Partners/IPartnerUIService.cs:9` | | `PartnerService` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Services.Partners` | `MMCA.ADC.Conference.UI/Services/Partners/PartnerService.cs:10` | | `IPublicSessionScheduleService` | interface | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Services.Public` | `MMCA.ADC.Conference.UI/Services/Public/IPublicSessionScheduleService.cs:34` | @@ -1237,7 +1244,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ISponsorUIService` | interface | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Services.Sponsors` | `MMCA.ADC.Conference.UI/Services/Sponsors/ISponsorUIService.cs:9` | | `SponsorService` | class | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI.Services.Sponsors` | `MMCA.ADC.Conference.UI/Services/Sponsors/SponsorService.cs:10` | | `BunitTestBase` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests` | `MMCA.ADC.Conference.UI.Tests/BunitTestBase.cs:23` | -| `InertSessionAssetService` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests` | `MMCA.ADC.Conference.UI.Tests/BunitTestBase.cs:65` | +| `InertSessionAssetService` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests` | `MMCA.ADC.Conference.UI.Tests/BunitTestBase.cs:69` | | `ManagementRouteAuthorizationTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests` | `MMCA.ADC.Conference.UI.Tests/ManagementRouteAuthorizationTests.cs:19` | | `AddToCalendarButtonTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Components` | `MMCA.ADC.Conference.UI.Tests/Components/AddToCalendarButtonTests.cs:22` | | `ComponentsSnapshotTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Components` | `MMCA.ADC.Conference.UI.Tests/Components/ComponentsSnapshotTests.cs:28` | @@ -1259,15 +1266,17 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ADCHomeTicketingTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Home` | `MMCA.ADC.Conference.UI.Tests/Pages/Home/ADCHomeTests.cs:204` | | `PartnerCreateTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Partners` | `MMCA.ADC.Conference.UI.Tests/Pages/Partners/PartnerCreateTests.cs:23` | | `PartnerDetailTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Partners` | `MMCA.ADC.Conference.UI.Tests/Pages/Partners/PartnerDetailTests.cs:19` | -| `PublicActivityListTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Public` | `MMCA.ADC.Conference.UI.Tests/Pages/Public/PublicActivityListTests.cs:17` | +| `PublicActivityListTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Public` | `MMCA.ADC.Conference.UI.Tests/Pages/Public/PublicActivityListTests.cs:19` | | `PublicEventDetailTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Public` | `MMCA.ADC.Conference.UI.Tests/Pages/Public/PublicEventDetailTests.cs:17` | | `PublicEventListRedirectTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Public` | `MMCA.ADC.Conference.UI.Tests/Pages/Public/PublicEventListRedirectTests.cs:35` | -| `PublicSponsorListTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Public` | `MMCA.ADC.Conference.UI.Tests/Pages/Public/PublicSponsorListTests.cs:19` | +| `PublicReadAudienceTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Public` | `MMCA.ADC.Conference.UI.Tests/Pages/Public/PublicReadAudienceTests.cs:16` | +| `PublicSponsorListTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Public` | `MMCA.ADC.Conference.UI.Tests/Pages/Public/PublicSponsorListTests.cs:20` | | `VenueMapLinksTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Public` | `MMCA.ADC.Conference.UI.Tests/Pages/Public/VenueMapLinksTests.cs:10` | | `PublicSessionDetailBookmarkTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Public.Sessions` | `MMCA.ADC.Conference.UI.Tests/Pages/Public/Sessions/PublicSessionDetailBookmarkTests.cs:29` | | `PublicSessionDetailLiveButtonTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Public.Sessions` | `MMCA.ADC.Conference.UI.Tests/Pages/Public/Sessions/PublicSessionDetailLiveButtonTests.cs:27` | | `PublicSessionDetailTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Public.Sessions` | `MMCA.ADC.Conference.UI.Tests/Pages/Public/Sessions/PublicSessionDetailTests.cs:26` | | `PublicSessionListEventFilterTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Public.Sessions` | `MMCA.ADC.Conference.UI.Tests/Pages/Public/Sessions/PublicSessionListEventFilterTests.cs:23` | +| `PublicSessionListMyScheduleTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Public.Sessions` | `MMCA.ADC.Conference.UI.Tests/Pages/Public/Sessions/PublicSessionListMyScheduleTests.cs:21` | | `PublicSessionListRoomFilterTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Public.Sessions` | `MMCA.ADC.Conference.UI.Tests/Pages/Public/Sessions/PublicSessionListRoomFilterTests.cs:22` | | `PublicSessionListSortTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Public.Sessions` | `MMCA.ADC.Conference.UI.Tests/Pages/Public/Sessions/PublicSessionListSortTests.cs:21` | | `PublicSessionListViewBookmarkTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Public.Sessions` | `MMCA.ADC.Conference.UI.Tests/Pages/Public/Sessions/PublicSessionListViewBookmarkTests.cs:22` | @@ -1277,12 +1286,19 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `PublicSpeakerListEventFilterTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Public.Speakers` | `MMCA.ADC.Conference.UI.Tests/Pages/Public/Speakers/PublicSpeakerListEventFilterTests.cs:20` | | `QuestionCreateTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Questions` | `MMCA.ADC.Conference.UI.Tests/Pages/Questions/QuestionCreateTests.cs:19` | | `QuestionDetailTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Questions` | `MMCA.ADC.Conference.UI.Tests/Pages/Questions/QuestionDetailTests.cs:18` | -| `RoomDetailTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Rooms` | `MMCA.ADC.Conference.UI.Tests/Pages/Rooms/RoomDetailTests.cs:17` | +| `RoomCreateModelTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Rooms` | `MMCA.ADC.Conference.UI.Tests/Pages/Rooms/RoomCreateModelTests.cs:7` | +| `RoomDetailTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Rooms` | `MMCA.ADC.Conference.UI.Tests/Pages/Rooms/RoomDetailTests.cs:18` | | `SessionAssetsDownloadListTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.SessionAssets` | `MMCA.ADC.Conference.UI.Tests/Pages/SessionAssets/SessionAssetsDownloadListTests.cs:17` | | `SessionAssetsPanelTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.SessionAssets` | `MMCA.ADC.Conference.UI.Tests/Pages/SessionAssets/SessionAssetsPanelTests.cs:19` | +| `FixedTimeProvider` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Sessions` | `MMCA.ADC.Conference.UI.Tests/Pages/Sessions/SessionListCurrentEventClockTests.cs:91` | +| `SessionCreateModelTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Sessions` | `MMCA.ADC.Conference.UI.Tests/Pages/Sessions/SessionCreateModelTests.cs:7` | +| `SessionCreateTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Sessions` | `MMCA.ADC.Conference.UI.Tests/Pages/Sessions/SessionCreateTests.cs:21` | | `SessionDetailCategoryChipTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Sessions` | `MMCA.ADC.Conference.UI.Tests/Pages/Sessions/SessionDetailCategoryChipTests.cs:26` | | `SessionDetailRoomCacheTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Sessions` | `MMCA.ADC.Conference.UI.Tests/Pages/Sessions/SessionDetailRoomCacheTests.cs:26` | +| `SessionDetailScheduleTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Sessions` | `MMCA.ADC.Conference.UI.Tests/Pages/Sessions/SessionDetailScheduleTests.cs:25` | | `SessionDetailStaleLoadTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Sessions` | `MMCA.ADC.Conference.UI.Tests/Pages/Sessions/SessionDetailStaleLoadTests.cs:26` | +| `SessionEditModelTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Sessions` | `MMCA.ADC.Conference.UI.Tests/Pages/Sessions/SessionEditModelTests.cs:8` | +| `SessionListCurrentEventClockTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Sessions` | `MMCA.ADC.Conference.UI.Tests/Pages/Sessions/SessionListCurrentEventClockTests.cs:23` | | `SessionListEventFilterTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Sessions` | `MMCA.ADC.Conference.UI.Tests/Pages/Sessions/SessionListEventFilterTests.cs:22` | | `ScorePollTrackerTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Sessions.Selection` | `MMCA.ADC.Conference.UI.Tests/Pages/Sessions/Selection/ScorePollTrackerTests.cs:14` | | `SessionSelectionAiScoresTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Sessions.Selection` | `MMCA.ADC.Conference.UI.Tests/Pages/Sessions/Selection/SessionSelectionAiScoresTests.cs:15` | @@ -1294,14 +1310,20 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `SpeakerCategoryItemsPanelTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Speakers` | `MMCA.ADC.Conference.UI.Tests/Pages/Speakers/SpeakerCategoryItemsPanelTests.cs:25` | | `SpeakerDashboardTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Speakers` | `MMCA.ADC.Conference.UI.Tests/Pages/Speakers/SpeakerDashboardTests.cs:23` | | `SpeakerDetailTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Speakers` | `MMCA.ADC.Conference.UI.Tests/Pages/Speakers/SpeakerDetailTests.cs:24` | +| `SpeakerEditModelTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Speakers` | `MMCA.ADC.Conference.UI.Tests/Pages/Speakers/SpeakerEditModelTests.cs:8` | | `SpeakerQrTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Speakers` | `MMCA.ADC.Conference.UI.Tests/Pages/Speakers/SpeakerQrTests.cs:17` | | `SponsorCreateTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Sponsors` | `MMCA.ADC.Conference.UI.Tests/Pages/Sponsors/SponsorCreateTests.cs:27` | | `SponsorDetailTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Pages.Sponsors` | `MMCA.ADC.Conference.UI.Tests/Pages/Sponsors/SponsorDetailTests.cs:20` | | `EventServiceTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Services` | `MMCA.ADC.Conference.UI.Tests/Services/EventServiceTests.cs:15` | | `OrganizerEventFeedbackServiceTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Services` | `MMCA.ADC.Conference.UI.Tests/Services/OrganizerEventFeedbackServiceTests.cs:14` | | `OrganizerSessionFeedbackServiceTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Services` | `MMCA.ADC.Conference.UI.Tests/Services/OrganizerSessionFeedbackServiceTests.cs:14` | +| `PublicSessionScheduleServiceTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Services` | `MMCA.ADC.Conference.UI.Tests/Services/PublicSessionScheduleServiceTests.cs:15` | +| `RecordingCacheStore` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Services` | `MMCA.ADC.Conference.UI.Tests/Services/PublicSessionScheduleServiceTests.cs:79` | +| `RoomServiceTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Services` | `MMCA.ADC.Conference.UI.Tests/Services/RoomServiceTests.cs:10` | +| `SessionAssetServiceTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Services` | `MMCA.ADC.Conference.UI.Tests/Services/SessionAssetServiceTests.cs:12` | | `SessionSelectionServiceTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Services` | `MMCA.ADC.Conference.UI.Tests/Services/SessionSelectionServiceTests.cs:15` | | `SpeakerDashboardServiceTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Services` | `MMCA.ADC.Conference.UI.Tests/Services/SpeakerDashboardServiceTests.cs:20` | +| `SpeakerLookupServiceTests` | class | MMCA.ADC.Conference.UI.Tests | `MMCA.ADC.Conference.UI.Tests.Services` | `MMCA.ADC.Conference.UI.Tests/Services/SpeakerLookupServiceTests.cs:13` | | `BookmarkCountGrpcTests` | class | MMCA.ADC.CrossService.IntegrationTests | `MMCA.ADC.CrossService.IntegrationTests.CrossService` | `MMCA.ADC.CrossService.IntegrationTests/CrossService/BookmarkCountGrpcTests.cs:17` | | `CrossServiceSmokeTests` | class | MMCA.ADC.CrossService.IntegrationTests | `MMCA.ADC.CrossService.IntegrationTests.CrossService` | `MMCA.ADC.CrossService.IntegrationTests/CrossService/CrossServiceSmokeTests.cs:15` | | `SpeakerLinkBrokerFlowTests` | class | MMCA.ADC.CrossService.IntegrationTests | `MMCA.ADC.CrossService.IntegrationTests.CrossService` | `MMCA.ADC.CrossService.IntegrationTests/CrossService/SpeakerLinkBrokerFlowTests.cs:17` | @@ -1438,6 +1460,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ClassReference` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application` | `MMCA.ADC.Engagement.Application/AssemblyReference.cs:11` | | `DependencyInjection` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application` | `MMCA.ADC.Engagement.Application/DependencyInjection.cs:29` | | `CheckInDTOMapper` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.CheckIns.DTOs` | `MMCA.ADC.Engagement.Application/CheckIns/DTOs/CheckInDTOMapper.cs:12` | +| `UserDeletedBadgeHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.CheckIns.IntegrationEventHandlers` | `MMCA.ADC.Engagement.Application/CheckIns/IntegrationEventHandlers/UserDeletedBadgeHandler.cs:22` | | `CheckInProcessor` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.CheckIns.Services` | `MMCA.ADC.Engagement.Application/CheckIns/Services/CheckInProcessor.cs:23` | | `RecordedCheckIn` | record struct | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.CheckIns.Services` | `MMCA.ADC.Engagement.Application/CheckIns/Services/CheckInProcessor.cs:38` | | `CheckInAttendeeHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.CheckIns.UseCases.CheckInAttendee` | `MMCA.ADC.Engagement.Application/CheckIns/UseCases/CheckInAttendee/CheckInAttendeeHandler.cs:21` | @@ -1458,13 +1481,14 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `LiveChannelPublishWorkItem` | record | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.Live` | `MMCA.ADC.Engagement.Application/Live/ILiveChannelPublishQueue.cs:10` | | `LivePollVoteChangedHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.DomainEventHandlers` | `MMCA.ADC.Engagement.Application/LivePolls/DomainEventHandlers/LivePollVoteChangedHandler.cs:38` | | `LivePollDTOMapper` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.DTOs` | `MMCA.ADC.Engagement.Application/LivePolls/DTOs/LivePollDTOMapper.cs:13` | +| `UserDeletedVotesHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.IntegrationEventHandlers` | `MMCA.ADC.Engagement.Application/LivePolls/IntegrationEventHandlers/UserDeletedVotesHandler.cs:21` | | `LivePollAuthorization` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.Services` | `MMCA.ADC.Engagement.Application/LivePolls/Services/LivePollAuthorization.cs:12` | | `LivePollNavigationPopulator` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.Services` | `MMCA.ADC.Engagement.Application/LivePolls/Services/LivePollNavigationPopulator.cs:11` | | `LivePollOptionNavigationPopulator` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.Services` | `MMCA.ADC.Engagement.Application/LivePolls/Services/LivePollOptionNavigationPopulator.cs:11` | | `LivePollResultsBuilder` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.Services` | `MMCA.ADC.Engagement.Application/LivePolls/Services/LivePollResultsBuilder.cs:12` | | `CastVoteCommand` | record | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.UseCases.CastVote` | `MMCA.ADC.Engagement.Application/LivePolls/UseCases/CastVote/CastVoteCommand.cs:11` | | `CastVoteCommandValidator` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.UseCases.CastVote` | `MMCA.ADC.Engagement.Application/LivePolls/UseCases/CastVote/CastVoteCommandValidator.cs:8` | -| `CastVoteHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.UseCases.CastVote` | `MMCA.ADC.Engagement.Application/LivePolls/UseCases/CastVote/CastVoteHandler.cs:19` | +| `CastVoteHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.UseCases.CastVote` | `MMCA.ADC.Engagement.Application/LivePolls/UseCases/CastVote/CastVoteHandler.cs:20` | | `CloseLivePollCommand` | record | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.UseCases.Close` | `MMCA.ADC.Engagement.Application/LivePolls/UseCases/Close/CloseLivePollCommand.cs:12` | | `CloseLivePollHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.UseCases.Close` | `MMCA.ADC.Engagement.Application/LivePolls/UseCases/Close/CloseLivePollHandler.cs:19` | | `CreateLivePollCommand` | record | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.UseCases.Create` | `MMCA.ADC.Engagement.Application/LivePolls/UseCases/Create/CreateLivePollCommand.cs:14` | @@ -1474,7 +1498,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `DeleteLivePollHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.UseCases.Delete` | `MMCA.ADC.Engagement.Application/LivePolls/UseCases/Delete/DeleteLivePollHandler.cs:14` | | `GetEventPollsHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.UseCases.GetEventPolls` | `MMCA.ADC.Engagement.Application/LivePolls/UseCases/GetEventPolls/GetEventPollsHandler.cs:14` | | `GetEventPollsQuery` | record | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.UseCases.GetEventPolls` | `MMCA.ADC.Engagement.Application/LivePolls/UseCases/GetEventPolls/GetEventPollsQuery.cs:7` | -| `GetOpenPollsHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.UseCases.GetOpenPolls` | `MMCA.ADC.Engagement.Application/LivePolls/UseCases/GetOpenPolls/GetOpenPollsHandler.cs:15` | +| `GetOpenPollsHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.UseCases.GetOpenPolls` | `MMCA.ADC.Engagement.Application/LivePolls/UseCases/GetOpenPolls/GetOpenPollsHandler.cs:21` | | `GetOpenPollsQuery` | record | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.UseCases.GetOpenPolls` | `MMCA.ADC.Engagement.Application/LivePolls/UseCases/GetOpenPolls/GetOpenPollsQuery.cs:11` | | `GetPollResultsHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.UseCases.GetPollResults` | `MMCA.ADC.Engagement.Application/LivePolls/UseCases/GetPollResults/GetPollResultsHandler.cs:23` | | `GetPollResultsQuery` | record | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.LivePolls.UseCases.GetPollResults` | `MMCA.ADC.Engagement.Application/LivePolls/UseCases/GetPollResults/GetPollResultsQuery.cs:17` | @@ -1486,7 +1510,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `AttendeeCheckedInPointsHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.Points.IntegrationEventHandlers` | `MMCA.ADC.Engagement.Application/Points/IntegrationEventHandlers/AttendeeCheckedInPointsHandler.cs:31` | | `EventFeedbackSubmittedPointsHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.Points.IntegrationEventHandlers` | `MMCA.ADC.Engagement.Application/Points/IntegrationEventHandlers/EventFeedbackSubmittedPointsHandler.cs:27` | | `SessionFeedbackSubmittedPointsHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.Points.IntegrationEventHandlers` | `MMCA.ADC.Engagement.Application/Points/IntegrationEventHandlers/SessionFeedbackSubmittedPointsHandler.cs:29` | -| `UserDeletedPointsHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.Points.IntegrationEventHandlers` | `MMCA.ADC.Engagement.Application/Points/IntegrationEventHandlers/UserDeletedPointsHandler.cs:37` | +| `UserDeletedPointsHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.Points.IntegrationEventHandlers` | `MMCA.ADC.Engagement.Application/Points/IntegrationEventHandlers/UserDeletedPointsHandler.cs:38` | | `IPointsAwarder` | interface | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.Points.Services` | `MMCA.ADC.Engagement.Application/Points/Services/IPointsAwarder.cs:19` | | `PointsAwarder` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.Points.Services` | `MMCA.ADC.Engagement.Application/Points/Services/PointsAwarder.cs:29` | | `GetLeaderboardHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.Points.UseCases.GetLeaderboard` | `MMCA.ADC.Engagement.Application/Points/UseCases/GetLeaderboard/GetLeaderboardHandler.cs:28` | @@ -1499,10 +1523,11 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `OverviewRow` | record | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.Points.UseCases.GetPointsOverview` | `MMCA.ADC.Engagement.Application/Points/UseCases/GetPointsOverview/GetPointsOverviewHandler.cs:102` | | `SetLeaderboardParticipationHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.Points.UseCases.SetLeaderboardParticipation` | `MMCA.ADC.Engagement.Application/Points/UseCases/SetLeaderboardParticipation/SetLeaderboardParticipationHandler.cs:31` | | `SessionQuestionUpvoteChangedHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.SessionQuestions.DomainEventHandlers` | `MMCA.ADC.Engagement.Application/SessionQuestions/DomainEventHandlers/SessionQuestionUpvoteChangedHandler.cs:39` | +| `UserDeletedSessionQuestionsHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.SessionQuestions.IntegrationEventHandlers` | `MMCA.ADC.Engagement.Application/SessionQuestions/IntegrationEventHandlers/UserDeletedSessionQuestionsHandler.cs:22` | | `SessionQuestionViewBuilder` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.SessionQuestions.Services` | `MMCA.ADC.Engagement.Application/SessionQuestions/Services/SessionQuestionViewBuilder.cs:12` | | `GetModerationQueueHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.GetModerationQueue` | `MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/GetModerationQueue/GetModerationQueueHandler.cs:19` | | `GetModerationQueueQuery` | record | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.GetModerationQueue` | `MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/GetModerationQueue/GetModerationQueueQuery.cs:11` | -| `GetSessionQuestionsHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.GetSessionQuestions` | `MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/GetSessionQuestions/GetSessionQuestionsHandler.cs:26` | +| `GetSessionQuestionsHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.GetSessionQuestions` | `MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/GetSessionQuestions/GetSessionQuestionsHandler.cs:27` | | `GetSessionQuestionsQuery` | record | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.GetSessionQuestions` | `MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/GetSessionQuestions/GetSessionQuestionsQuery.cs:11` | | `ModerateQuestionCommand` | record | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.Moderate` | `MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/Moderate/ModerateQuestionCommand.cs:15` | | `ModerateQuestionHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.Moderate` | `MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/Moderate/ModerateQuestionHandler.cs:23` | @@ -1511,9 +1536,10 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `SubmitQuestionHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.Submit` | `MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/Submit/SubmitQuestionHandler.cs:31` | | `ToggleUpvoteCommand` | record | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.ToggleUpvote` | `MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/ToggleUpvote/ToggleUpvoteCommand.cs:11` | | `ToggleUpvoteCommandValidator` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.ToggleUpvote` | `MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/ToggleUpvote/ToggleUpvoteCommandValidator.cs:8` | -| `ToggleUpvoteHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.ToggleUpvote` | `MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/ToggleUpvote/ToggleUpvoteHandler.cs:17` | +| `ToggleUpvoteHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.ToggleUpvote` | `MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/ToggleUpvote/ToggleUpvoteHandler.cs:18` | | `UserSessionBookmarkCacheEvictionHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.UserSessionBookmarks.DomainEventHandlers` | `MMCA.ADC.Engagement.Application/UserSessionBookmarks/DomainEventHandlers/UserSessionBookmarkCacheEvictionHandler.cs:43` | | `UserSessionBookmarkDTOMapper` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.UserSessionBookmarks.DTOs` | `MMCA.ADC.Engagement.Application/UserSessionBookmarks/DTOs/UserSessionBookmarkDTOMapper.cs:12` | +| `UserDeletedBookmarksHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.UserSessionBookmarks.IntegrationEventHandlers` | `MMCA.ADC.Engagement.Application/UserSessionBookmarks/IntegrationEventHandlers/UserDeletedBookmarksHandler.cs:22` | | `BookmarkCountService` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.UserSessionBookmarks.Services` | `MMCA.ADC.Engagement.Application/UserSessionBookmarks/Services/BookmarkCountService.cs:11` | | `CreateBookmarkHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.UserSessionBookmarks.UseCases.Create` | `MMCA.ADC.Engagement.Application/UserSessionBookmarks/UseCases/Create/CreateBookmarkHandler.cs:16` | | `CreateBookmarkRequestValidator` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.UserSessionBookmarks.UseCases.Create` | `MMCA.ADC.Engagement.Application/UserSessionBookmarks/UseCases/Create/CreateBookmarkRequestValidator.cs:9` | @@ -1521,6 +1547,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `GetBookmarkedSessionIdsQuery` | record | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.UserSessionBookmarks.UseCases.GetBookmarkedSessionIds` | `MMCA.ADC.Engagement.Application/UserSessionBookmarks/UseCases/GetBookmarkedSessionIds/GetBookmarkedSessionIdsQuery.cs:5` | | `GetUserBookmarksHandler` | class | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.UserSessionBookmarks.UseCases.GetUserBookmarks` | `MMCA.ADC.Engagement.Application/UserSessionBookmarks/UseCases/GetUserBookmarks/GetUserBookmarksHandler.cs:17` | | `GetUserBookmarksQuery` | record | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application.UserSessionBookmarks.UseCases.GetUserBookmarks` | `MMCA.ADC.Engagement.Application/UserSessionBookmarks/UseCases/GetUserBookmarks/GetUserBookmarksQuery.cs:8` | +| `UserDeletedBadgeHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.CheckIns.IntegrationEventHandlers` | `MMCA.ADC.Engagement.Application.Tests/CheckIns/IntegrationEventHandlers/UserDeletedBadgeHandlerTests.cs:14` | | `CheckInAttendeeHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.CheckIns.UseCases` | `MMCA.ADC.Engagement.Application.Tests/CheckIns/UseCases/CheckInAttendeeHandlerTests.cs:19` | | `GetAttendanceStatsHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.CheckIns.UseCases` | `MMCA.ADC.Engagement.Application.Tests/CheckIns/UseCases/GetAttendanceStatsHandlerTests.cs:12` | | `GetOrCreateMyBadgeHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.CheckIns.UseCases` | `MMCA.ADC.Engagement.Application.Tests/CheckIns/UseCases/GetOrCreateMyBadgeHandlerTests.cs:13` | @@ -1536,22 +1563,24 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ManualCheckInRequestValidatorTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.CheckIns.Validation` | `MMCA.ADC.Engagement.Application.Tests/CheckIns/Validation/ManualCheckInRequestValidatorTests.cs:7` | | `RoomCheckInRequestValidatorTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.CheckIns.Validation` | `MMCA.ADC.Engagement.Application.Tests/CheckIns/Validation/RoomCheckInRequestValidatorTests.cs:7` | | `SponsorVisitRequestValidatorTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.CheckIns.Validation` | `MMCA.ADC.Engagement.Application.Tests/CheckIns/Validation/SponsorVisitRequestValidatorTests.cs:7` | -| `CountingQueryableExecutor` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.Helpers` | `MMCA.ADC.Engagement.Application.Tests/Helpers/TestSupport.cs:16` | -| `TestSupport` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.Helpers` | `MMCA.ADC.Engagement.Application.Tests/Helpers/TestSupport.cs:44` | +| `CountingQueryableExecutor` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.Helpers` | `MMCA.ADC.Engagement.Application.Tests/Helpers/TestSupport.cs:18` | +| `TestSupport` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.Helpers` | `MMCA.ADC.Engagement.Application.Tests/Helpers/TestSupport.cs:46` | | `LiveChannelPublishQueueTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.Live` | `MMCA.ADC.Engagement.Application.Tests/Live/LiveChannelPublishQueueTests.cs:10` | | `LivePollVoteChangedHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.LivePolls.DomainEventHandlers` | `MMCA.ADC.Engagement.Application.Tests/LivePolls/DomainEventHandlers/LivePollVoteChangedHandlerTests.cs:24` | | `RecordingQueue` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.LivePolls.DomainEventHandlers` | `MMCA.ADC.Engagement.Application.Tests/LivePolls/DomainEventHandlers/LivePollVoteChangedHandlerTests.cs:158` | | `LivePollDTOMapperTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.LivePolls.DTOs` | `MMCA.ADC.Engagement.Application.Tests/LivePolls/DTOs/LivePollDTOMapperTests.cs:9` | +| `UserDeletedVotesHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.LivePolls.IntegrationEventHandlers` | `MMCA.ADC.Engagement.Application.Tests/LivePolls/IntegrationEventHandlers/UserDeletedVotesHandlerTests.cs:14` | | `LivePollOptionNavigationPopulatorTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.LivePolls.Services` | `MMCA.ADC.Engagement.Application.Tests/LivePolls/Services/LivePollOptionNavigationPopulatorTests.cs:9` | | `LivePollResultsBuilderTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.LivePolls.Services` | `MMCA.ADC.Engagement.Application.Tests/LivePolls/Services/LivePollResultsBuilderTests.cs:23` | | `CastVoteHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.LivePolls.UseCases` | `MMCA.ADC.Engagement.Application.Tests/LivePolls/UseCases/CastVoteHandlerTests.cs:14` | | `CloseLivePollHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.LivePolls.UseCases` | `MMCA.ADC.Engagement.Application.Tests/LivePolls/UseCases/CloseLivePollHandlerTests.cs:15` | | `CreateLivePollHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.LivePolls.UseCases` | `MMCA.ADC.Engagement.Application.Tests/LivePolls/UseCases/CreateLivePollHandlerTests.cs:14` | +| `DeleteLivePollHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.LivePolls.UseCases` | `MMCA.ADC.Engagement.Application.Tests/LivePolls/UseCases/DeleteLivePollHandlerTests.cs:18` | | `GetEventPollsHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.LivePolls.UseCases` | `MMCA.ADC.Engagement.Application.Tests/LivePolls/UseCases/GetEventPollsHandlerTests.cs:12` | | `GetOpenPollsHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.LivePolls.UseCases` | `MMCA.ADC.Engagement.Application.Tests/LivePolls/UseCases/GetOpenPollsHandlerTests.cs:13` | | `GetPollResultsHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.LivePolls.UseCases` | `MMCA.ADC.Engagement.Application.Tests/LivePolls/UseCases/GetPollResultsHandlerTests.cs:26` | | `GetSessionManagePollsHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.LivePolls.UseCases` | `MMCA.ADC.Engagement.Application.Tests/LivePolls/UseCases/GetSessionManagePollsHandlerTests.cs:23` | -| `HandlerMocks` | record | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.LivePolls.UseCases` | `MMCA.ADC.Engagement.Application.Tests/LivePolls/UseCases/CastVoteHandlerTests.cs:169` | +| `HandlerMocks` | record | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.LivePolls.UseCases` | `MMCA.ADC.Engagement.Application.Tests/LivePolls/UseCases/CastVoteHandlerTests.cs:201` | | `HandlerMocks` | record | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.LivePolls.UseCases` | `MMCA.ADC.Engagement.Application.Tests/LivePolls/UseCases/CloseLivePollHandlerTests.cs:128` | | `HandlerMocks` | record | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.LivePolls.UseCases` | `MMCA.ADC.Engagement.Application.Tests/LivePolls/UseCases/CreateLivePollHandlerTests.cs:199` | | `HandlerMocks` | record | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.LivePolls.UseCases` | `MMCA.ADC.Engagement.Application.Tests/LivePolls/UseCases/GetPollResultsHandlerTests.cs:249` | @@ -1575,16 +1604,17 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `GetLeaderboardHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.Points.UseCases` | `MMCA.ADC.Engagement.Application.Tests/Points/UseCases/GetLeaderboardHandlerTests.cs:12` | | `GetMyPointsHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.Points.UseCases` | `MMCA.ADC.Engagement.Application.Tests/Points/UseCases/GetMyPointsHandlerTests.cs:14` | | `GetPointsOverviewHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.Points.UseCases` | `MMCA.ADC.Engagement.Application.Tests/Points/UseCases/GetPointsOverviewHandlerTests.cs:11` | -| `HandlerMocks` | record | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.Points.UseCases` | `MMCA.ADC.Engagement.Application.Tests/Points/UseCases/SetLeaderboardParticipationHandlerTests.cs:297` | +| `HandlerMocks` | record | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.Points.UseCases` | `MMCA.ADC.Engagement.Application.Tests/Points/UseCases/SetLeaderboardParticipationHandlerTests.cs:316` | | `SetLeaderboardParticipationHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.Points.UseCases` | `MMCA.ADC.Engagement.Application.Tests/Points/UseCases/SetLeaderboardParticipationHandlerTests.cs:15` | | `RecordingQueue` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.SessionQuestions.DomainEventHandlers` | `MMCA.ADC.Engagement.Application.Tests/SessionQuestions/DomainEventHandlers/SessionQuestionUpvoteChangedHandlerTests.cs:174` | | `SessionQuestionUpvoteChangedHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.SessionQuestions.DomainEventHandlers` | `MMCA.ADC.Engagement.Application.Tests/SessionQuestions/DomainEventHandlers/SessionQuestionUpvoteChangedHandlerTests.cs:25` | +| `UserDeletedSessionQuestionsHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.SessionQuestions.IntegrationEventHandlers` | `MMCA.ADC.Engagement.Application.Tests/SessionQuestions/IntegrationEventHandlers/UserDeletedSessionQuestionsHandlerTests.cs:15` | | `GetModerationQueueHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.SessionQuestions.UseCases` | `MMCA.ADC.Engagement.Application.Tests/SessionQuestions/UseCases/GetModerationQueueHandlerTests.cs:16` | | `GetSessionQuestionsHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.SessionQuestions.UseCases` | `MMCA.ADC.Engagement.Application.Tests/SessionQuestions/UseCases/GetSessionQuestionsHandlerTests.cs:12` | | `HandlerMocks` | record | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.SessionQuestions.UseCases` | `MMCA.ADC.Engagement.Application.Tests/SessionQuestions/UseCases/GetModerationQueueHandlerTests.cs:121` | | `HandlerMocks` | record | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.SessionQuestions.UseCases` | `MMCA.ADC.Engagement.Application.Tests/SessionQuestions/UseCases/ModerateQuestionHandlerTests.cs:249` | | `HandlerMocks` | record | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.SessionQuestions.UseCases` | `MMCA.ADC.Engagement.Application.Tests/SessionQuestions/UseCases/SubmitQuestionHandlerTests.cs:235` | -| `HandlerMocks` | record | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.SessionQuestions.UseCases` | `MMCA.ADC.Engagement.Application.Tests/SessionQuestions/UseCases/ToggleUpvoteHandlerTests.cs:246` | +| `HandlerMocks` | record | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.SessionQuestions.UseCases` | `MMCA.ADC.Engagement.Application.Tests/SessionQuestions/UseCases/ToggleUpvoteHandlerTests.cs:277` | | `ModerateQuestionHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.SessionQuestions.UseCases` | `MMCA.ADC.Engagement.Application.Tests/SessionQuestions/UseCases/ModerateQuestionHandlerTests.cs:15` | | `SubmitQuestionHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.SessionQuestions.UseCases` | `MMCA.ADC.Engagement.Application.Tests/SessionQuestions/UseCases/SubmitQuestionHandlerTests.cs:20` | | `ToggleUpvoteHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.SessionQuestions.UseCases` | `MMCA.ADC.Engagement.Application.Tests/SessionQuestions/UseCases/ToggleUpvoteHandlerTests.cs:14` | @@ -1592,6 +1622,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ToggleUpvoteCommandValidatorTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.SessionQuestions.Validation` | `MMCA.ADC.Engagement.Application.Tests/SessionQuestions/Validation/ToggleUpvoteCommandValidatorTests.cs:6` | | `UserSessionBookmarkCacheEvictionHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.UserSessionBookmarks.DomainEventHandlers` | `MMCA.ADC.Engagement.Application.Tests/UserSessionBookmarks/DomainEventHandlers/UserSessionBookmarkCacheEvictionHandlerTests.cs:20` | | `UserSessionBookmarkDTOMapperTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.UserSessionBookmarks.DTOs` | `MMCA.ADC.Engagement.Application.Tests/UserSessionBookmarks/DTOs/UserSessionBookmarkDTOMapperTests.cs:7` | +| `UserDeletedBookmarksHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.UserSessionBookmarks.IntegrationEventHandlers` | `MMCA.ADC.Engagement.Application.Tests/UserSessionBookmarks/IntegrationEventHandlers/UserDeletedBookmarksHandlerTests.cs:14` | | `BookmarkCountServiceTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.UserSessionBookmarks.Services` | `MMCA.ADC.Engagement.Application.Tests/UserSessionBookmarks/Services/BookmarkCountServiceTests.cs:12` | | `CreateBookmarkHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.UserSessionBookmarks.UseCases` | `MMCA.ADC.Engagement.Application.Tests/UserSessionBookmarks/UseCases/CreateBookmarkHandlerTests.cs:16` | | `GetBookmarkedSessionIdsHandlerTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.UserSessionBookmarks.UseCases` | `MMCA.ADC.Engagement.Application.Tests/UserSessionBookmarks/UseCases/GetBookmarkedSessionIdsHandlerTests.cs:9` | @@ -1601,7 +1632,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `CreateBookmarkRequestValidatorTests` | class | MMCA.ADC.Engagement.Application.Tests | `MMCA.ADC.Engagement.Application.Tests.UserSessionBookmarks.Validation` | `MMCA.ADC.Engagement.Application.Tests/UserSessionBookmarks/Validation/CreateBookmarkRequestValidatorTests.cs:7` | | `BookmarkCountServiceGrpcAdapter` | class | MMCA.ADC.Engagement.Contracts | `MMCA.ADC.Engagement.Contracts` | `MMCA.ADC.Engagement.Contracts/BookmarkCountServiceGrpcAdapter.cs:14` | | `DependencyInjection` | class | MMCA.ADC.Engagement.Contracts | `MMCA.ADC.Engagement.Contracts` | `MMCA.ADC.Engagement.Contracts/DependencyInjection.cs:16` | -| `UserEngagementExportServiceGrpcAdapter` | class | MMCA.ADC.Engagement.Contracts | `MMCA.ADC.Engagement.Contracts` | `MMCA.ADC.Engagement.Contracts/UserEngagementExportServiceGrpcAdapter.cs:18` | +| `UserEngagementExportServiceGrpcAdapter` | class | MMCA.ADC.Engagement.Contracts | `MMCA.ADC.Engagement.Contracts` | `MMCA.ADC.Engagement.Contracts/UserEngagementExportServiceGrpcAdapter.cs:19` | | `AssemblyReference` | class | MMCA.ADC.Engagement.Domain | `MMCA.ADC.Engagement.Domain` | `MMCA.ADC.Engagement.Domain/AssemblyReference.cs:5` | | `ClassReference` | class | MMCA.ADC.Engagement.Domain | `MMCA.ADC.Engagement.Domain` | `MMCA.ADC.Engagement.Domain/AssemblyReference.cs:11` | | `AttendeeBadge` | class | MMCA.ADC.Engagement.Domain | `MMCA.ADC.Engagement.Domain.Badges` | `MMCA.ADC.Engagement.Domain/Badges/AttendeeBadge.cs:18` | @@ -1685,7 +1716,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `SessionQuestionLifecycleTests` | class | MMCA.ADC.Engagement.IntegrationTests | `MMCA.ADC.Engagement.IntegrationTests.SessionQuestions` | `MMCA.ADC.Engagement.IntegrationTests/SessionQuestions/SessionQuestionLifecycleTests.cs:18` | | `SelfHttpWarmupTask` | class | MMCA.ADC.Engagement.Service | `MMCA.ADC.Engagement.Service` | `MMCA.ADC.Engagement.Service/SelfHttpWarmupTask.cs:23` | | `BookmarkCountsGrpcService` | class | MMCA.ADC.Engagement.Service | `MMCA.ADC.Engagement.Service.Grpc` | `MMCA.ADC.Engagement.Service/Grpc/BookmarkCountsGrpcService.cs:24` | -| `UserEngagementExportGrpcService` | class | MMCA.ADC.Engagement.Service | `MMCA.ADC.Engagement.Service.Grpc` | `MMCA.ADC.Engagement.Service/Grpc/UserEngagementExportGrpcService.cs:23` | +| `UserEngagementExportGrpcService` | class | MMCA.ADC.Engagement.Service | `MMCA.ADC.Engagement.Service.Grpc` | `MMCA.ADC.Engagement.Service/Grpc/UserEngagementExportGrpcService.cs:24` | | `EngagementFeatures` | class | MMCA.ADC.Engagement.Shared | `MMCA.ADC.Engagement.Shared` | `MMCA.ADC.Engagement.Shared/EngagementFeatures.cs:10` | | `EngagementPermissionGrants` | class | MMCA.ADC.Engagement.Shared | `MMCA.ADC.Engagement.Shared.Authorization` | `MMCA.ADC.Engagement.Shared/Authorization/EngagementPermissionGrants.cs:24` | | `EngagementPermissions` | class | MMCA.ADC.Engagement.Shared | `MMCA.ADC.Engagement.Shared.Authorization` | `MMCA.ADC.Engagement.Shared/Authorization/EngagementPermissions.cs:9` | @@ -1799,8 +1830,8 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `NowNextSessionInfo` | record | MMCA.ADC.Engagement.UI | `MMCA.ADC.Engagement.UI.Services.HappeningNow` | `MMCA.ADC.Engagement.UI/Services/HappeningNow/INowNextService.cs:27` | | `NowNextSnapshot` | record | MMCA.ADC.Engagement.UI | `MMCA.ADC.Engagement.UI.Services.HappeningNow` | `MMCA.ADC.Engagement.UI/Services/HappeningNow/INowNextService.cs:15` | | `SessionReminder` | record | MMCA.ADC.Engagement.UI | `MMCA.ADC.Engagement.UI.Services.HappeningNow` | `MMCA.ADC.Engagement.UI/Services/HappeningNow/SessionReminderPlanner.cs:15` | -| `SessionReminderCoordinator` | class | MMCA.ADC.Engagement.UI | `MMCA.ADC.Engagement.UI.Services.HappeningNow` | `MMCA.ADC.Engagement.UI/Services/HappeningNow/SessionReminderCoordinator.cs:20` | -| `SessionReminderPlanner` | class | MMCA.ADC.Engagement.UI | `MMCA.ADC.Engagement.UI.Services.HappeningNow` | `MMCA.ADC.Engagement.UI/Services/HappeningNow/SessionReminderPlanner.cs:31` | +| `SessionReminderCoordinator` | class | MMCA.ADC.Engagement.UI | `MMCA.ADC.Engagement.UI.Services.HappeningNow` | `MMCA.ADC.Engagement.UI/Services/HappeningNow/SessionReminderCoordinator.cs:25` | +| `SessionReminderPlanner` | class | MMCA.ADC.Engagement.UI | `MMCA.ADC.Engagement.UI.Services.HappeningNow` | `MMCA.ADC.Engagement.UI/Services/HappeningNow/SessionReminderPlanner.cs:39` | | `AttendeeLookupService` | class | MMCA.ADC.Engagement.UI | `MMCA.ADC.Engagement.UI.Services.Lookups` | `MMCA.ADC.Engagement.UI/Services/Lookups/AttendeeLookupService.cs:17` | | `AttendeeRow` | record | MMCA.ADC.Engagement.UI | `MMCA.ADC.Engagement.UI.Services.Lookups` | `MMCA.ADC.Engagement.UI/Services/Lookups/AttendeeLookupService.cs:184` | | `AttendeeSearchField` | enum | MMCA.ADC.Engagement.UI | `MMCA.ADC.Engagement.UI.Services.Lookups` | `MMCA.ADC.Engagement.UI/Services/Lookups/IAttendeeLookupService.cs:41` | @@ -1898,10 +1929,10 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `AssemblyReference` | class | MMCA.ADC.Identity.Application | `MMCA.ADC.Identity.Application` | `MMCA.ADC.Identity.Application/AssemblyReference.cs:5` | | `ClassReference` | class | MMCA.ADC.Identity.Application | `MMCA.ADC.Identity.Application` | `MMCA.ADC.Identity.Application/AssemblyReference.cs:11` | | `DependencyInjection` | class | MMCA.ADC.Identity.Application | `MMCA.ADC.Identity.Application` | `MMCA.ADC.Identity.Application/DependencyInjection.cs:21` | -| `SpeakerLinkedToUserHandler` | class | MMCA.ADC.Identity.Application | `MMCA.ADC.Identity.Application.Speakers.IntegrationEventHandlers` | `MMCA.ADC.Identity.Application/Speakers/IntegrationEventHandlers/SpeakerLinkedToUserHandler.cs:27` | +| `SpeakerLinkedToUserHandler` | class | MMCA.ADC.Identity.Application | `MMCA.ADC.Identity.Application.Speakers.IntegrationEventHandlers` | `MMCA.ADC.Identity.Application/Speakers/IntegrationEventHandlers/SpeakerLinkedToUserHandler.cs:32` | | `SpeakerUnlinkedFromUserHandler` | class | MMCA.ADC.Identity.Application | `MMCA.ADC.Identity.Application.Speakers.IntegrationEventHandlers` | `MMCA.ADC.Identity.Application/Speakers/IntegrationEventHandlers/SpeakerUnlinkedFromUserHandler.cs:27` | | `AttendeeQueryService` | class | MMCA.ADC.Identity.Application | `MMCA.ADC.Identity.Application.Users` | `MMCA.ADC.Identity.Application/Users/AttendeeQueryService.cs:11` | -| `AuthenticationService` | class | MMCA.ADC.Identity.Application | `MMCA.ADC.Identity.Application.Users` | `MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:48` | +| `AuthenticationService` | class | MMCA.ADC.Identity.Application | `MMCA.ADC.Identity.Application.Users` | `MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:50` | | `IExternalLoginEmailVerifier` | interface | MMCA.ADC.Identity.Application | `MMCA.ADC.Identity.Application.Users` | `MMCA.ADC.Identity.Application/Users/IExternalLoginEmailVerifier.cs:11` | | `UserAdministrationService` | class | MMCA.ADC.Identity.Application | `MMCA.ADC.Identity.Application.Users.Administration` | `MMCA.ADC.Identity.Application/Users/Administration/UserAdministrationService.cs:38` | | `UserDTOMapper` | class | MMCA.ADC.Identity.Application | `MMCA.ADC.Identity.Application.Users.DTOs` | `MMCA.ADC.Identity.Application/Users/DTOs/UserDTOMapper.cs:14` | @@ -1936,7 +1967,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `SendEmailConfirmationHandler` | class | MMCA.ADC.Identity.Application | `MMCA.ADC.Identity.Application.Users.UseCases.SendEmailConfirmation` | `MMCA.ADC.Identity.Application/Users/UseCases/SendEmailConfirmation/SendEmailConfirmationHandler.cs:27` | | `SetUserAvatarCommand` | record | MMCA.ADC.Identity.Application | `MMCA.ADC.Identity.Application.Users.UseCases.SetUserAvatar` | `MMCA.ADC.Identity.Application/Users/UseCases/SetUserAvatar/SetUserAvatarCommand.cs:12` | | `SetUserAvatarCommandValidator` | class | MMCA.ADC.Identity.Application | `MMCA.ADC.Identity.Application.Users.UseCases.SetUserAvatar` | `MMCA.ADC.Identity.Application/Users/UseCases/SetUserAvatar/SetUserAvatarCommandValidator.cs:17` | -| `SetUserAvatarHandler` | class | MMCA.ADC.Identity.Application | `MMCA.ADC.Identity.Application.Users.UseCases.SetUserAvatar` | `MMCA.ADC.Identity.Application/Users/UseCases/SetUserAvatar/SetUserAvatarHandler.cs:26` | +| `SetUserAvatarHandler` | class | MMCA.ADC.Identity.Application | `MMCA.ADC.Identity.Application.Users.UseCases.SetUserAvatar` | `MMCA.ADC.Identity.Application/Users/UseCases/SetUserAvatar/SetUserAvatarHandler.cs:27` | | `ChangePasswordRequestValidator` | class | MMCA.ADC.Identity.Application | `MMCA.ADC.Identity.Application.Users.Validation` | `MMCA.ADC.Identity.Application/Users/Validation/ChangePasswordRequestValidator.cs:11` | | `RegisterRequestValidator` | class | MMCA.ADC.Identity.Application | `MMCA.ADC.Identity.Application.Users.Validation` | `MMCA.ADC.Identity.Application/Users/Validation/RegisterRequestValidator.cs:12` | | `InMemoryRepository` | class | MMCA.ADC.Identity.Application.Tests | `MMCA.ADC.Identity.Application.Tests.Helpers` | `MMCA.ADC.Identity.Application.Tests/Helpers/TestSupport.cs:17` | @@ -1946,8 +1977,8 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `SpeakerLinkedToUserHandlerTests` | class | MMCA.ADC.Identity.Application.Tests | `MMCA.ADC.Identity.Application.Tests.Speakers.IntegrationEventHandlers` | `MMCA.ADC.Identity.Application.Tests/Speakers/IntegrationEventHandlers/SpeakerLinkedToUserHandlerTests.cs:13` | | `SpeakerUnlinkedFromUserHandlerTests` | class | MMCA.ADC.Identity.Application.Tests | `MMCA.ADC.Identity.Application.Tests.Speakers.IntegrationEventHandlers` | `MMCA.ADC.Identity.Application.Tests/Speakers/IntegrationEventHandlers/SpeakerUnlinkedFromUserHandlerTests.cs:13` | | `AttendeeQueryServiceTests` | class | MMCA.ADC.Identity.Application.Tests | `MMCA.ADC.Identity.Application.Tests.Users` | `MMCA.ADC.Identity.Application.Tests/Users/AttendeeQueryServiceTests.cs:12` | -| `AuthenticationServiceTests` | class | MMCA.ADC.Identity.Application.Tests | `MMCA.ADC.Identity.Application.Tests.Users` | `MMCA.ADC.Identity.Application.Tests/Users/AuthenticationServiceTests.cs:31` | -| `ServiceMocks` | record | MMCA.ADC.Identity.Application.Tests | `MMCA.ADC.Identity.Application.Tests.Users` | `MMCA.ADC.Identity.Application.Tests/Users/AuthenticationServiceTests.cs:892` | +| `AuthenticationServiceTests` | class | MMCA.ADC.Identity.Application.Tests | `MMCA.ADC.Identity.Application.Tests.Users` | `MMCA.ADC.Identity.Application.Tests/Users/AuthenticationServiceTests.cs:32` | +| `ServiceMocks` | record | MMCA.ADC.Identity.Application.Tests | `MMCA.ADC.Identity.Application.Tests.Users` | `MMCA.ADC.Identity.Application.Tests/Users/AuthenticationServiceTests.cs:930` | | `SoftDeletedUserValidatorTests` | class | MMCA.ADC.Identity.Application.Tests | `MMCA.ADC.Identity.Application.Tests.Users` | `MMCA.ADC.Identity.Application.Tests/Users/SoftDeletedUserValidatorTests.cs:15` | | `UserAdministrationServiceTests` | class | MMCA.ADC.Identity.Application.Tests | `MMCA.ADC.Identity.Application.Tests.Users.Administration` | `MMCA.ADC.Identity.Application.Tests/Users/Administration/UserAdministrationServiceTests.cs:23` | | `UserDTOMapperTests` | class | MMCA.ADC.Identity.Application.Tests | `MMCA.ADC.Identity.Application.Tests.Users.DTOs` | `MMCA.ADC.Identity.Application.Tests/Users/DTOs/UserDTOMapperTests.cs:7` | @@ -1969,7 +2000,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ResetPasswordHandlerTests` | class | MMCA.ADC.Identity.Application.Tests | `MMCA.ADC.Identity.Application.Tests.Users.UseCases.ResetPassword` | `MMCA.ADC.Identity.Application.Tests/Users/UseCases/ResetPassword/ResetPasswordHandlerTests.cs:20` | | `SendEmailConfirmationHandlerTests` | class | MMCA.ADC.Identity.Application.Tests | `MMCA.ADC.Identity.Application.Tests.Users.UseCases.SendEmailConfirmation` | `MMCA.ADC.Identity.Application.Tests/Users/UseCases/SendEmailConfirmation/SendEmailConfirmationHandlerTests.cs:22` | | `SetUserAvatarCommandMarkerTests` | class | MMCA.ADC.Identity.Application.Tests | `MMCA.ADC.Identity.Application.Tests.Users.UseCases.SetUserAvatar` | `MMCA.ADC.Identity.Application.Tests/Users/UseCases/SetUserAvatar/SetUserAvatarCommandMarkerTests.cs:11` | -| `SetUserAvatarHandlerTests` | class | MMCA.ADC.Identity.Application.Tests | `MMCA.ADC.Identity.Application.Tests.Users.UseCases.SetUserAvatar` | `MMCA.ADC.Identity.Application.Tests/Users/UseCases/SetUserAvatar/SetUserAvatarHandlerTests.cs:20` | +| `SetUserAvatarHandlerTests` | class | MMCA.ADC.Identity.Application.Tests | `MMCA.ADC.Identity.Application.Tests.Users.UseCases.SetUserAvatar` | `MMCA.ADC.Identity.Application.Tests/Users/UseCases/SetUserAvatar/SetUserAvatarHandlerTests.cs:21` | | `ChangePasswordRequestValidatorTests` | class | MMCA.ADC.Identity.Application.Tests | `MMCA.ADC.Identity.Application.Tests.Users.Validation` | `MMCA.ADC.Identity.Application.Tests/Users/Validation/ChangePasswordRequestValidatorTests.cs:7` | | `LoginRequestValidatorTests` | class | MMCA.ADC.Identity.Application.Tests | `MMCA.ADC.Identity.Application.Tests.Users.Validation` | `MMCA.ADC.Identity.Application.Tests/Users/Validation/LoginRequestValidatorTests.cs:7` | | `RefreshTokenRequestValidatorTests` | class | MMCA.ADC.Identity.Application.Tests | `MMCA.ADC.Identity.Application.Tests.Users.Validation` | `MMCA.ADC.Identity.Application.Tests/Users/Validation/RefreshTokenRequestValidatorTests.cs:7` | @@ -1998,7 +2029,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `CaseWrongSource` | class | MMCA.ADC.Identity.Infrastructure.Tests | `MMCA.ADC.Identity.Infrastructure.Tests.Persistence` | `MMCA.ADC.Identity.Infrastructure.Tests/Persistence/RefreshSessionModelGateTests.cs:248` | | `GateContext` | class | MMCA.ADC.Identity.Infrastructure.Tests | `MMCA.ADC.Identity.Infrastructure.Tests.Persistence` | `MMCA.ADC.Identity.Infrastructure.Tests/Persistence/RefreshSessionModelGateTests.cs:261` | | `IdentityEntityConfigurationTests` | class | MMCA.ADC.Identity.Infrastructure.Tests | `MMCA.ADC.Identity.Infrastructure.Tests.Persistence` | `MMCA.ADC.Identity.Infrastructure.Tests/Persistence/IdentityEntityConfigurationTests.cs:11` | -| `IdentityTestDbContext` | class | MMCA.ADC.Identity.Infrastructure.Tests | `MMCA.ADC.Identity.Infrastructure.Tests.Persistence` | `MMCA.ADC.Identity.Infrastructure.Tests/Persistence/IdentityEntityConfigurationTests.cs:209` | +| `IdentityTestDbContext` | class | MMCA.ADC.Identity.Infrastructure.Tests | `MMCA.ADC.Identity.Infrastructure.Tests.Persistence` | `MMCA.ADC.Identity.Infrastructure.Tests/Persistence/IdentityEntityConfigurationTests.cs:223` | | `RefreshSessionModelGateTests` | class | MMCA.ADC.Identity.Infrastructure.Tests | `MMCA.ADC.Identity.Infrastructure.Tests.Persistence` | `MMCA.ADC.Identity.Infrastructure.Tests/Persistence/RefreshSessionModelGateTests.cs:46` | | `IdentityModuleDbSeederTests` | class | MMCA.ADC.Identity.Infrastructure.Tests | `MMCA.ADC.Identity.Infrastructure.Tests.Seeding` | `MMCA.ADC.Identity.Infrastructure.Tests/Seeding/IdentityModuleDbSeederTests.cs:11` | | `SeederMocks` | record | MMCA.ADC.Identity.Infrastructure.Tests | `MMCA.ADC.Identity.Infrastructure.Tests.Seeding` | `MMCA.ADC.Identity.Infrastructure.Tests/Seeding/IdentityModuleDbSeederTests.cs:85` | @@ -2043,6 +2074,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `AttendeesGrpcService` | class | MMCA.ADC.Identity.Service | `MMCA.ADC.Identity.Service.Grpc` | `MMCA.ADC.Identity.Service/Grpc/AttendeesGrpcService.cs:19` | | `IdentityPermissionGrants` | class | MMCA.ADC.Identity.Shared | `MMCA.ADC.Identity.Shared.Authorization` | `MMCA.ADC.Identity.Shared/Authorization/IdentityPermissionGrants.cs:17` | | `IdentityPermissions` | class | MMCA.ADC.Identity.Shared | `MMCA.ADC.Identity.Shared.Authorization` | `MMCA.ADC.Identity.Shared/Authorization/IdentityPermissions.cs:10` | +| `JwtAudience` | class | MMCA.ADC.Identity.Shared | `MMCA.ADC.Identity.Shared.Authorization` | `MMCA.ADC.Identity.Shared/Authorization/JwtAudience.cs:14` | | `RoleNames` | class | MMCA.ADC.Identity.Shared | `MMCA.ADC.Identity.Shared.Authorization` | `MMCA.ADC.Identity.Shared/Authorization/RoleNames.cs:20` | | `DisabledAttendeeQueryService` | class | MMCA.ADC.Identity.Shared | `MMCA.ADC.Identity.Shared.Users` | `MMCA.ADC.Identity.Shared/Users/DisabledAttendeeQueryService.cs:7` | | `IAttendeeQueryService` | interface | MMCA.ADC.Identity.Shared | `MMCA.ADC.Identity.Shared.Users` | `MMCA.ADC.Identity.Shared/Users/IAttendeeQueryService.cs:11` | @@ -2061,6 +2093,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `UserDeleted` | record | MMCA.ADC.Identity.Shared | `MMCA.ADC.Identity.Shared.Users.IntegrationEvents` | `MMCA.ADC.Identity.Shared/Users/IntegrationEvents/UserDeleted.cs:26` | | `UserRegistered` | record | MMCA.ADC.Identity.Shared | `MMCA.ADC.Identity.Shared.Users.IntegrationEvents` | `MMCA.ADC.Identity.Shared/Users/IntegrationEvents/UserRegistered.cs:36` | | `IdentityPermissionGrantsTests` | class | MMCA.ADC.Identity.Shared.Tests | `MMCA.ADC.Identity.Shared.Tests.Authorization` | `MMCA.ADC.Identity.Shared.Tests/Authorization/IdentityPermissionGrantsTests.cs:14` | +| `JwtAudienceTests` | class | MMCA.ADC.Identity.Shared.Tests | `MMCA.ADC.Identity.Shared.Tests.Authorization` | `MMCA.ADC.Identity.Shared.Tests/Authorization/JwtAudienceTests.cs:11` | | `DisabledAttendeeQueryServiceTests` | class | MMCA.ADC.Identity.Shared.Tests | `MMCA.ADC.Identity.Shared.Tests.Users` | `MMCA.ADC.Identity.Shared.Tests/Users/DisabledAttendeeQueryServiceTests.cs:6` | | `DependencyInjection` | class | MMCA.ADC.Identity.UI | `MMCA.ADC.Identity.UI` | `MMCA.ADC.Identity.UI/DependencyInjection.cs:12` | | `IdentityRoutePaths` | class | MMCA.ADC.Identity.UI | `MMCA.ADC.Identity.UI` | `MMCA.ADC.Identity.UI/IdentityRoutePaths.cs:6` | @@ -2087,7 +2120,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `NotificationModule` | class | MMCA.ADC.Notification.API | `MMCA.ADC.Notification.API` | `MMCA.ADC.Notification.API/NotificationModule.cs:15` | | `NotificationModuleTests` | class | MMCA.ADC.Notification.API.Tests | `MMCA.ADC.Notification.API.Tests` | `MMCA.ADC.Notification.API.Tests/NotificationModuleTests.cs:8` | | `AttendeeNotificationRecipientProvider` | class | MMCA.ADC.Notification.Application | `MMCA.ADC.Notification.Application` | `MMCA.ADC.Notification.Application/AttendeeNotificationRecipientProvider.cs:10` | -| `DependencyInjection` | class | MMCA.ADC.Notification.Application | `MMCA.ADC.Notification.Application` | `MMCA.ADC.Notification.Application/DependencyInjection.cs:12` | +| `DependencyInjection` | class | MMCA.ADC.Notification.Application | `MMCA.ADC.Notification.Application` | `MMCA.ADC.Notification.Application/DependencyInjection.cs:13` | | `UserNotificationExportService` | class | MMCA.ADC.Notification.Application | `MMCA.ADC.Notification.Application` | `MMCA.ADC.Notification.Application/UserNotificationExportService.cs:15` | | `AttendeeNotificationRecipientProviderTests` | class | MMCA.ADC.Notification.Application.Tests | `MMCA.ADC.Notification.Application.Tests` | `MMCA.ADC.Notification.Application.Tests/AttendeeNotificationRecipientProviderTests.cs:7` | | `DependencyInjectionTests` | class | MMCA.ADC.Notification.Application.Tests | `MMCA.ADC.Notification.Application.Tests` | `MMCA.ADC.Notification.Application.Tests/DependencyInjectionTests.cs:10` | @@ -2105,7 +2138,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `NotificationTestWebApplicationFactory` | class | MMCA.ADC.Notification.IntegrationTests | `MMCA.ADC.Notification.IntegrationTests.Infrastructure` | `MMCA.ADC.Notification.IntegrationTests/Infrastructure/NotificationTestWebApplicationFactory.cs:29` | | `NotificationControllerTests` | class | MMCA.ADC.Notification.IntegrationTests | `MMCA.ADC.Notification.IntegrationTests.Notifications` | `MMCA.ADC.Notification.IntegrationTests/Notifications/NotificationControllerTests.cs:16` | | `NotificationHubTests` | class | MMCA.ADC.Notification.IntegrationTests | `MMCA.ADC.Notification.IntegrationTests.Notifications` | `MMCA.ADC.Notification.IntegrationTests/Notifications/NotificationHubTests.cs:15` | -| `LiveChannelGrpcService` | class | MMCA.ADC.Notification.Service | `MMCA.ADC.Notification.Service.Grpc` | `MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:22` | +| `LiveChannelGrpcService` | class | MMCA.ADC.Notification.Service | `MMCA.ADC.Notification.Service.Grpc` | `MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:31` | | `UserNotificationExportGrpcService` | class | MMCA.ADC.Notification.Service | `MMCA.ADC.Notification.Service.Grpc` | `MMCA.ADC.Notification.Service/Grpc/UserNotificationExportGrpcService.cs:27` | | `LiveChannelJoinAuthorizer` | class | MMCA.ADC.Notification.Service | `MMCA.ADC.Notification.Service.Live` | `MMCA.ADC.Notification.Service/Live/LiveChannelJoinAuthorizer.cs:43` | | `NotificationPermissionGrants` | class | MMCA.ADC.Notification.Shared | `MMCA.ADC.Notification.Shared.Authorization` | `MMCA.ADC.Notification.Shared/Authorization/NotificationPermissionGrants.cs:26` | @@ -2115,13 +2148,23 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ServiceBusRoundTripSmokeTests` | class | MMCA.ADC.ServiceBusEmulator.IntegrationTests | `MMCA.ADC.ServiceBusEmulator.IntegrationTests` | `MMCA.ADC.ServiceBusEmulator.IntegrationTests/ServiceBusRoundTripSmokeTests.cs:26` | | `ServiceBusEmulatorCollection` | class | MMCA.ADC.ServiceBusEmulator.IntegrationTests | `MMCA.ADC.ServiceBusEmulator.IntegrationTests.Infrastructure` | `MMCA.ADC.ServiceBusEmulator.IntegrationTests/Infrastructure/ServiceBusEmulatorFixture.cs:52` | | `ServiceBusEmulatorFixture` | class | MMCA.ADC.ServiceBusEmulator.IntegrationTests | `MMCA.ADC.ServiceBusEmulator.IntegrationTests.Infrastructure` | `MMCA.ADC.ServiceBusEmulator.IntegrationTests/Infrastructure/ServiceBusEmulatorFixture.cs:22` | +| `AttendeesGrpcServiceTests` | class | MMCA.ADC.Services.Tests | `MMCA.ADC.Services.Tests.Attendees` | `MMCA.ADC.Services.Tests/Attendees/AttendeesGrpcServiceTests.cs:23` | | `TokenPermissionGrantsTests` | class | MMCA.ADC.Services.Tests | `MMCA.ADC.Services.Tests.Authorization` | `MMCA.ADC.Services.Tests/Authorization/TokenPermissionGrantsTests.cs:33` | +| `BookmarkCountsGrpcServiceTests` | class | MMCA.ADC.Services.Tests | `MMCA.ADC.Services.Tests.Bookmarks` | `MMCA.ADC.Services.Tests/Bookmarks/BookmarkCountsGrpcServiceTests.cs:17` | +| `SessionBookmarksGrpcServiceTests` | class | MMCA.ADC.Services.Tests | `MMCA.ADC.Services.Tests.Bookmarks` | `MMCA.ADC.Services.Tests/Bookmarks/SessionBookmarksGrpcServiceTests.cs:18` | +| `SessionBookmarkValidationServiceGrpcAdapterTests` | class | MMCA.ADC.Services.Tests | `MMCA.ADC.Services.Tests.Bookmarks` | `MMCA.ADC.Services.Tests/Bookmarks/SessionBookmarkValidationServiceGrpcAdapterTests.cs:23` | | `UserEngagementExportGrpcServiceTests` | class | MMCA.ADC.Services.Tests | `MMCA.ADC.Services.Tests.Exports` | `MMCA.ADC.Services.Tests/Exports/UserEngagementExportGrpcServiceTests.cs:17` | | `UserEngagementExportServiceGrpcAdapterTests` | class | MMCA.ADC.Services.Tests | `MMCA.ADC.Services.Tests.Exports` | `MMCA.ADC.Services.Tests/Exports/UserEngagementExportServiceGrpcAdapterTests.cs:16` | | `UserNotificationExportGrpcServiceTests` | class | MMCA.ADC.Services.Tests | `MMCA.ADC.Services.Tests.Exports` | `MMCA.ADC.Services.Tests/Exports/UserNotificationExportGrpcServiceTests.cs:21` | | `UserNotificationExportServiceGrpcAdapterTests` | class | MMCA.ADC.Services.Tests | `MMCA.ADC.Services.Tests.Exports` | `MMCA.ADC.Services.Tests/Exports/UserNotificationExportServiceGrpcAdapterTests.cs:19` | | `FakeServerCallContext` | class | MMCA.ADC.Services.Tests | `MMCA.ADC.Services.Tests.Helpers` | `MMCA.ADC.Services.Tests/Helpers/FakeServerCallContext.cs:10` | +| `GrpcCalls` | class | MMCA.ADC.Services.Tests | `MMCA.ADC.Services.Tests.Helpers` | `MMCA.ADC.Services.Tests/Helpers/GrpcCalls.cs:9` | +| `EventLiveValidationGrpcServiceTests` | class | MMCA.ADC.Services.Tests | `MMCA.ADC.Services.Tests.Live` | `MMCA.ADC.Services.Tests/Live/EventLiveValidationGrpcServiceTests.cs:23` | +| `EventLiveValidationServiceGrpcAdapterTests` | class | MMCA.ADC.Services.Tests | `MMCA.ADC.Services.Tests.Live` | `MMCA.ADC.Services.Tests/Live/EventLiveValidationServiceGrpcAdapterTests.cs:24` | +| `LiveChannelGrpcServiceTests` | class | MMCA.ADC.Services.Tests | `MMCA.ADC.Services.Tests.Live` | `MMCA.ADC.Services.Tests/Live/LiveChannelGrpcServiceTests.cs:27` | | `LiveChannelJoinAuthorizerTests` | class | MMCA.ADC.Services.Tests | `MMCA.ADC.Services.Tests.Live` | `MMCA.ADC.Services.Tests/Live/LiveChannelJoinAuthorizerTests.cs:18` | +| `LiveChannelPublisherGrpcAdapterTests` | class | MMCA.ADC.Services.Tests | `MMCA.ADC.Services.Tests.Live` | `MMCA.ADC.Services.Tests/Live/LiveChannelPublisherGrpcAdapterTests.cs:16` | +| `SelfHttpOutputCacheWarmupTaskTests` | class | MMCA.ADC.Services.Tests | `MMCA.ADC.Services.Tests.Warmup` | `MMCA.ADC.Services.Tests/Warmup/SelfHttpOutputCacheWarmupTaskTests.cs:15` | | `App` | class | MMCA.ADC.UI | `MMCA.ADC.UI` | `MMCA.ADC.UI/App.xaml.cs:9` | | `AppDelegate` | class | MMCA.ADC.UI | `MMCA.ADC.UI` | `MMCA.ADC.UI/Platforms/iOS/AppDelegate.cs:20` | | `AppDelegate` | class | MMCA.ADC.UI | `MMCA.ADC.UI` | `MMCA.ADC.UI/Platforms/MacCatalyst/AppDelegate.cs:12` | @@ -2156,10 +2199,10 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `GuardrailChatClient` | class | MMCA.Common.AI | `MMCA.Common.AI.Chat` | `MMCA.Common.AI/Chat/GuardrailChatClient.cs:31` | | `GuardrailVerdict` | record struct | MMCA.Common.AI | `MMCA.Common.AI.Chat` | `MMCA.Common.AI/Chat/GuardrailVerdict.cs:13` | | `IAiTokenEstimator` | interface | MMCA.Common.AI | `MMCA.Common.AI.Chat` | `MMCA.Common.AI/Chat/IAiTokenEstimator.cs:14` | -| `IChatGuardrail` | interface | MMCA.Common.AI | `MMCA.Common.AI.Chat` | `MMCA.Common.AI/Chat/IChatGuardrail.cs:20` | +| `IChatGuardrail` | interface | MMCA.Common.AI | `MMCA.Common.AI.Chat` | `MMCA.Common.AI/Chat/IChatGuardrail.cs:21` | | `PromptTaggingChatClient` | class | MMCA.Common.AI | `MMCA.Common.AI.Chat` | `MMCA.Common.AI/Chat/PromptTaggingChatClient.cs:21` | | `UsageRecordingChatClient` | class | MMCA.Common.AI | `MMCA.Common.AI.Chat` | `MMCA.Common.AI/Chat/UsageRecordingChatClient.cs:38` | -| `ChatToolPolicy` | class | MMCA.Common.AI | `MMCA.Common.AI.Guardrails` | `MMCA.Common.AI/Guardrails/ChatToolPolicy.cs:23` | +| `ChatToolPolicy` | class | MMCA.Common.AI | `MMCA.Common.AI.Guardrails` | `MMCA.Common.AI/Guardrails/ChatToolPolicy.cs:24` | | `ContentPolicyGuardrail` | class | MMCA.Common.AI | `MMCA.Common.AI.Guardrails` | `MMCA.Common.AI/Guardrails/ContentPolicyGuardrail.cs:64` | | `ContentPolicyInjectionMode` | enum | MMCA.Common.AI | `MMCA.Common.AI.Guardrails` | `MMCA.Common.AI/Guardrails/ContentPolicyInjectionMode.cs:16` | | `ContentPolicySettings` | class | MMCA.Common.AI | `MMCA.Common.AI.Guardrails` | `MMCA.Common.AI/Guardrails/ContentPolicySettings.cs:23` | @@ -2192,8 +2235,8 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `PromptTaggingChatClientTests` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests` | `MMCA.Common.AI.Tests/PromptTaggingChatClientTests.cs:14` | | `UsageRecorder` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests` | `MMCA.Common.AI.Tests/UsageRecordingChatClientTests.cs:192` | | `UsageRecordingChatClientTests` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests` | `MMCA.Common.AI.Tests/UsageRecordingChatClientTests.cs:15` | -| `EmptyCorpusHarness` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Evaluation` | `MMCA.Common.AI.Tests/Evaluation/ReplayChatClientTests.cs:124` | -| `MissingRecordingHarness` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Evaluation` | `MMCA.Common.AI.Tests/Evaluation/ReplayChatClientTests.cs:135` | +| `EmptyCorpusHarness` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Evaluation` | `MMCA.Common.AI.Tests/Evaluation/ReplayChatClientTests.cs:145` | +| `MissingRecordingHarness` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Evaluation` | `MMCA.Common.AI.Tests/Evaluation/ReplayChatClientTests.cs:156` | | `PinHarness` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Evaluation` | `MMCA.Common.AI.Tests/Evaluation/RecordedResponsesTests.cs:169` | | `RecordedResponsesTests` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Evaluation` | `MMCA.Common.AI.Tests/Evaluation/RecordedResponsesTests.cs:12` | | `ReferenceGoldenReplayTests` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Evaluation` | `MMCA.Common.AI.Tests/Evaluation/ReferenceGoldenReplayTests.cs:17` | @@ -2209,12 +2252,12 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ContentPolicyRegistrationTests` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Guardrails` | `MMCA.Common.AI.Tests/Guardrails/ContentPolicyRegistrationTests.cs:17` | | `GuardrailRegistrationTests` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Guardrails` | `MMCA.Common.AI.Tests/Guardrails/GuardrailRegistrationTests.cs:16` | | `PreStreamingGuardrail` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Guardrails` | `MMCA.Common.AI.Tests/Guardrails/StreamedGuardrailTests.cs:88` | -| `RecordingGuardrail` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Guardrails` | `MMCA.Common.AI.Tests/Guardrails/RequestRedactionTests.cs:163` | +| `RecordingGuardrail` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Guardrails` | `MMCA.Common.AI.Tests/Guardrails/RequestRedactionTests.cs:199` | | `RequestRedactionTests` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Guardrails` | `MMCA.Common.AI.Tests/Guardrails/RequestRedactionTests.cs:14` | | `StreamedGuardrailTests` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Guardrails` | `MMCA.Common.AI.Tests/Guardrails/StreamedGuardrailTests.cs:12` | -| `SuffixRedactor` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Guardrails` | `MMCA.Common.AI.Tests/Guardrails/RequestRedactionTests.cs:157` | -| `ToolPolicyTests` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Guardrails` | `MMCA.Common.AI.Tests/Guardrails/ToolPolicyTests.cs:15` | -| `UppercaseRedactor` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Guardrails` | `MMCA.Common.AI.Tests/Guardrails/RequestRedactionTests.cs:151` | +| `SuffixRedactor` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Guardrails` | `MMCA.Common.AI.Tests/Guardrails/RequestRedactionTests.cs:193` | +| `ToolPolicyTests` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Guardrails` | `MMCA.Common.AI.Tests/Guardrails/ToolPolicyTests.cs:16` | +| `UppercaseRedactor` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Guardrails` | `MMCA.Common.AI.Tests/Guardrails/RequestRedactionTests.cs:187` | | `AdapterFactoryTests` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Providers` | `MMCA.Common.AI.Tests/Providers/AdapterFactoryTests.cs:16` | | `ProviderTagTests` | class | MMCA.Common.AI.Tests | `MMCA.Common.AI.Tests.Providers` | `MMCA.Common.AI.Tests/Providers/ProviderTagTests.cs:13` | | `AssemblyReference` | class | MMCA.Common.API | `MMCA.Common.API` | `MMCA.Common.API/AssemblyReference.cs:8` | @@ -2227,7 +2270,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `HasPermissionAttribute` | class | MMCA.Common.API | `MMCA.Common.API.Authorization` | `MMCA.Common.API/Authorization/HasPermissionAttribute.cs:13` | | `OwnerOrAdminFilter` | class | MMCA.Common.API | `MMCA.Common.API.Authorization` | `MMCA.Common.API/Authorization/OwnerOrAdminFilter.cs:31` | | `OwnerOrAdminFilterOptions` | class | MMCA.Common.API | `MMCA.Common.API.Authorization` | `MMCA.Common.API/Authorization/OwnerOrAdminFilterOptions.cs:13` | -| `OwnershipHelper` | class | MMCA.Common.API | `MMCA.Common.API.Authorization` | `MMCA.Common.API/Authorization/OwnershipHelper.cs:10` | +| `OwnershipHelper` | class | MMCA.Common.API | `MMCA.Common.API.Authorization` | `MMCA.Common.API/Authorization/OwnershipHelper.cs:11` | | `PermissionAuthorizationHandler` | class | MMCA.Common.API | `MMCA.Common.API.Authorization` | `MMCA.Common.API/Authorization/PermissionAuthorizationHandler.cs:13` | | `PermissionPolicy` | class | MMCA.Common.API | `MMCA.Common.API.Authorization` | `MMCA.Common.API/Authorization/PermissionPolicy.cs:9` | | `PermissionPolicyProvider` | class | MMCA.Common.API | `MMCA.Common.API.Authorization` | `MMCA.Common.API/Authorization/PermissionPolicyProvider.cs:13` | @@ -2259,14 +2302,14 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `DevicesController` | class | MMCA.Common.API | `MMCA.Common.API.Controllers.Notifications` | `MMCA.Common.API/Controllers/Notifications/DevicesController.cs:26` | | `InboxController` | class | MMCA.Common.API | `MMCA.Common.API.Controllers.Notifications` | `MMCA.Common.API/Controllers/Notifications/NotificationInboxController.cs:29` | | `NotificationsController` | class | MMCA.Common.API | `MMCA.Common.API.Controllers.Notifications` | `MMCA.Common.API/Controllers/Notifications/NotificationsController.cs:30` | -| `DataExportControllerBase` | class | MMCA.Common.API | `MMCA.Common.API.Controllers.Privacy` | `MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:59` | +| `DataExportControllerBase` | class | MMCA.Common.API | `MMCA.Common.API.Controllers.Privacy` | `MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:61` | | `CsvWriter` | class | MMCA.Common.API | `MMCA.Common.API.Export` | `MMCA.Common.API/Export/CsvWriter.cs:34` | | `EntityCsvExporter` | class | MMCA.Common.API | `MMCA.Common.API.Export` | `MMCA.Common.API/Export/EntityCsvExporter.cs:17` | | `CurrentUserTargetingContextAccessor` | class | MMCA.Common.API | `MMCA.Common.API.FeatureManagement` | `MMCA.Common.API/FeatureManagement/CurrentUserTargetingContextAccessor.cs:54` | | `DisabledFeatureHandler` | class | MMCA.Common.API | `MMCA.Common.API.FeatureManagement` | `MMCA.Common.API/FeatureManagement/DisabledFeatureHandler.cs:13` | -| `IdempotencyFilter` | class | MMCA.Common.API | `MMCA.Common.API.Idempotency` | `MMCA.Common.API/Idempotency/IdempotencyFilter.cs:67` | +| `IdempotencyFilter` | class | MMCA.Common.API | `MMCA.Common.API.Idempotency` | `MMCA.Common.API/Idempotency/IdempotencyFilter.cs:68` | | `IdempotencyMetrics` | class | MMCA.Common.API | `MMCA.Common.API.Idempotency` | `MMCA.Common.API/Idempotency/IdempotencyMetrics.cs:16` | -| `IdempotencyRecord` | record | MMCA.Common.API | `MMCA.Common.API.Idempotency` | `MMCA.Common.API/Idempotency/IdempotencyRecord.cs:14` | +| `IdempotencyRecord` | record | MMCA.Common.API | `MMCA.Common.API.Idempotency` | `MMCA.Common.API/Idempotency/IdempotencyRecord.cs:21` | | `IdempotencySettings` | class | MMCA.Common.API | `MMCA.Common.API.Idempotency` | `MMCA.Common.API/Idempotency/IdempotencySettings.cs:9` | | `IdempotentAttribute` | class | MMCA.Common.API | `MMCA.Common.API.Idempotency` | `MMCA.Common.API/Idempotency/IdempotentAttribute.cs:16` | | `NonIdempotentAttribute` | class | MMCA.Common.API | `MMCA.Common.API.Idempotency` | `MMCA.Common.API/Idempotency/NonIdempotentAttribute.cs:23` | @@ -2274,12 +2317,12 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ErrorLocalizer` | class | MMCA.Common.API | `MMCA.Common.API.Localization` | `MMCA.Common.API/Localization/ErrorLocalizer.cs:11` | | `ErrorResourceSource` | class | MMCA.Common.API | `MMCA.Common.API.Localization` | `MMCA.Common.API/Localization/ErrorResourceSource.cs:12` | | `IErrorLocalizer` | interface | MMCA.Common.API | `MMCA.Common.API.Localization` | `MMCA.Common.API/Localization/IErrorLocalizer.cs:9` | -| `CorrelationIdMiddleware` | class | MMCA.Common.API | `MMCA.Common.API.Middleware` | `MMCA.Common.API/Middleware/CorrelationIdMiddleware.cs:15` | +| `CorrelationIdMiddleware` | class | MMCA.Common.API | `MMCA.Common.API.Middleware` | `MMCA.Common.API/Middleware/CorrelationIdMiddleware.cs:18` | | `DbUpdateExceptionHandler` | class | MMCA.Common.API | `MMCA.Common.API.Middleware` | `MMCA.Common.API/Middleware/DbUpdateExceptionHandler.cs:17` | | `DomainExceptionHandler` | class | MMCA.Common.API | `MMCA.Common.API.Middleware` | `MMCA.Common.API/Middleware/DomainExceptionHandler.cs:16` | | `ErrorHttpMapping` | class | MMCA.Common.API | `MMCA.Common.API.Middleware` | `MMCA.Common.API/Middleware/ErrorHttpMapping.cs:14` | | `GlobalExceptionHandler` | class | MMCA.Common.API | `MMCA.Common.API.Middleware` | `MMCA.Common.API/Middleware/GlobalExceptionHandler.cs:23` | -| `OperationCanceledExceptionHandler` | class | MMCA.Common.API | `MMCA.Common.API.Middleware` | `MMCA.Common.API/Middleware/OperationCanceledExceptionHandler.cs:16` | +| `OperationCanceledExceptionHandler` | class | MMCA.Common.API | `MMCA.Common.API.Middleware` | `MMCA.Common.API/Middleware/OperationCanceledExceptionHandler.cs:18` | | `SoftDeletedUserMiddleware` | class | MMCA.Common.API | `MMCA.Common.API.Middleware` | `MMCA.Common.API/Middleware/SoftDeletedUserMiddleware.cs:31` | | `TenantResolutionMiddleware` | class | MMCA.Common.API | `MMCA.Common.API.Middleware` | `MMCA.Common.API/Middleware/TenantResolutionMiddleware.cs:36` | | `UnhandledResultFailureFilter` | class | MMCA.Common.API | `MMCA.Common.API.Middleware` | `MMCA.Common.API/Middleware/UnhandledResultFailureFilter.cs:22` | @@ -2294,21 +2337,27 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `RedisFixedWindowRateLimiter` | class | MMCA.Common.API | `MMCA.Common.API.RateLimiting` | `MMCA.Common.API/RateLimiting/RedisFixedWindowRateLimiter.cs:37` | | `RedisRateLimitLease` | class | MMCA.Common.API | `MMCA.Common.API.RateLimiting` | `MMCA.Common.API/RateLimiting/RedisFixedWindowRateLimiter.cs:169` | | `ErrorResources` | class | MMCA.Common.API | `MMCA.Common.API.Resources` | `MMCA.Common.API/Resources/ErrorResources.cs:9` | -| `CookieSessionRefresher` | class | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:52` | +| `CookieSessionRefresher` | class | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:74` | | `CookieSessionRefreshMiddleware` | class | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/CookieSessionRefreshMiddleware.cs:13` | | `CookieSessionRefreshMiddlewareExtensions` | class | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/CookieSessionRefreshMiddleware.cs:35` | | `CookieTokenReader` | class | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/CookieTokenReader.cs:10` | -| `ICookieSessionRefresher` | interface | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:30` | +| `ICookieSessionRefresher` | interface | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:32` | +| `ISessionCookieStore` | interface | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/ISessionCookieStore.cs:14` | +| `SessionClaimsToken` | class | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/SessionClaimsToken.cs:14` | | `SessionCookieAuthenticationExtensions` | class | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/SessionCookieAuthenticationHandler.cs:90` | | `SessionCookieAuthenticationHandler` | class | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/SessionCookieAuthenticationHandler.cs:24` | -| `SessionCookieEndpoints` | class | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:15` | +| `SessionCookieEndpoints` | class | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:16` | | `SessionCookieJar` | class | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/SessionCookieJar.cs:11` | -| `SessionCookieRequest` | record | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:77` | -| `SessionTokenResponse` | record | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:21` | -| `SessionTokenResult` | record struct | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:15` | +| `SessionCookieRequest` | record | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:92` | +| `SessionCookieSettings` | class | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/SessionCookieSettings.cs:11` | +| `SessionCookieStore` | class | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/ISessionCookieStore.cs:28` | +| `SessionRefreshOutcome` | class | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/SessionRefreshOutcome.cs:32` | +| `SessionRefreshStatus` | enum | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/SessionRefreshOutcome.cs:4` | +| `SessionTokenResponse` | record | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:23` | +| `SessionTokenResult` | record struct | MMCA.Common.API | `MMCA.Common.API.SessionCookies` | `MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:17` | | `CommonForwardedHeaders` | class | MMCA.Common.API | `MMCA.Common.API.Startup` | `MMCA.Common.API/Startup/CommonForwardedHeaders.cs:27` | | `CommonForwardedHeadersExtensions` | class | MMCA.Common.API | `MMCA.Common.API.Startup` | `MMCA.Common.API/Startup/CommonForwardedHeadersExtensions.cs:9` | -| `DatabaseInitializationExtensions` | class | MMCA.Common.API | `MMCA.Common.API.Startup` | `MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:20` | +| `DatabaseInitializationExtensions` | class | MMCA.Common.API | `MMCA.Common.API.Startup` | `MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:28` | | `MiniProfilerExtensions` | class | MMCA.Common.API | `MMCA.Common.API.Startup` | `MMCA.Common.API/Startup/MiniProfilerExtensions.cs:9` | | `ModuleHostContext` | class | MMCA.Common.API | `MMCA.Common.API.Startup` | `MMCA.Common.API/Startup/ModuleHostContext.cs:21` | | `ModuleHostExtensions` | class | MMCA.Common.API | `MMCA.Common.API.Startup` | `MMCA.Common.API/Startup/ModuleHostExtensions.cs:22` | @@ -2323,7 +2372,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `AppAssociationOptions` | class | MMCA.Common.API | `MMCA.Common.API.Startup.Endpoints` | `MMCA.Common.API/Startup/Endpoints/AppAssociationOptions.cs:9` | | `JwksEndpointExtensions` | class | MMCA.Common.API | `MMCA.Common.API.Startup.Endpoints` | `MMCA.Common.API/Startup/Endpoints/JwksEndpointExtensions.cs:15` | | `OidcDiscoveryEndpointExtensions` | class | MMCA.Common.API | `MMCA.Common.API.Startup.Endpoints` | `MMCA.Common.API/Startup/Endpoints/OidcDiscoveryEndpointExtensions.cs:22` | -| `OpenApiEndpointExtensions` | class | MMCA.Common.API | `MMCA.Common.API.Startup.Endpoints` | `MMCA.Common.API/Startup/Endpoints/OpenApiEndpointExtensions.cs:22` | +| `OpenApiEndpointExtensions` | class | MMCA.Common.API | `MMCA.Common.API.Startup.Endpoints` | `MMCA.Common.API/Startup/Endpoints/OpenApiEndpointExtensions.cs:25` | | `MiddlewarePipelineBuilder` | class | MMCA.Common.API | `MMCA.Common.API.Startup.Pipeline` | `MMCA.Common.API/Startup/Pipeline/MiddlewarePipelineBuilder.cs:15` | | `MiddlewarePipelineStep` | record | MMCA.Common.API | `MMCA.Common.API.Startup.Pipeline` | `MMCA.Common.API/Startup/Pipeline/MiddlewarePipelineStep.cs:21` | | `MiddlewarePipelineStepNames` | class | MMCA.Common.API | `MMCA.Common.API.Startup.Pipeline` | `MMCA.Common.API/Startup/Pipeline/MiddlewarePipelineStepNames.cs:14` | @@ -2334,6 +2383,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `AuthorizationExtensionsTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Authorization` | `MMCA.Common.API.Tests/Authorization/AuthorizationExtensionsTests.cs:9` | | `FallbackAuthorizationTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Authorization` | `MMCA.Common.API.Tests/Authorization/FallbackAuthorizationTests.cs:19` | | `OwnerOrAdminFilterTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Authorization` | `MMCA.Common.API.Tests/Authorization/OwnerOrAdminFilterTests.cs:15` | +| `OwnershipHelperGateTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Authorization` | `MMCA.Common.API.Tests/Authorization/OwnershipHelperGateTests.cs:18` | | `OwnershipHelperTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Authorization` | `MMCA.Common.API.Tests/Authorization/OwnershipHelperTests.cs:11` | | `PermissionAuthorizationHandlerTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Authorization` | `MMCA.Common.API.Tests/Authorization/PermissionAuthorizationHandlerTests.cs:10` | | `PermissionPolicyProviderTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Authorization` | `MMCA.Common.API.Tests/Authorization/PermissionPolicyProviderTests.cs:8` | @@ -2346,31 +2396,32 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `SupportsIfMatchAttributeTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Concurrency` | `MMCA.Common.API.Tests/Concurrency/SupportsIfMatchAttributeTests.cs:17` | | `AggregateRootEntityControllerBaseTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/AggregateRootEntityControllerBaseTests.cs:19` | | `ApiControllerBaseTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/ApiControllerBaseTests.cs:9` | -| `AsyncScopedReadController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:343` | -| `BothHooksReadController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:381` | +| `AsyncScopedReadController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:345` | +| `BothHooksReadController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:383` | | `CrudEntityControllerBaseTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/CrudEntityControllerBaseTests.cs:20` | +| `DefaultExportTestController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:538` | | `EntityControllerBaseETagTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseETagTests.cs:22` | -| `EntityControllerBaseExportColumnTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:652` | +| `EntityControllerBaseExportColumnTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:699` | | `EntityControllerBaseExportTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:25` | | `EntityControllerBaseReadSpecificationTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:29` | | `EntityControllerBaseTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseTests.cs:19` | -| `ExportMoney` | record | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:641` | -| `ExportShapeTestController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:603` | -| `ExportShapeTestDTO` | record | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:625` | -| `ExportTestController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:494` | -| `ExportTestDTO` | record | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:610` | -| `ExportTestEntity` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:608` | +| `ExportMoney` | record | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:688` | +| `ExportShapeTestController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:647` | +| `ExportShapeTestDTO` | record | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:672` | +| `ExportTestController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:529` | +| `ExportTestDTO` | record | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:657` | +| `ExportTestEntity` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:655` | | `PlainDTO` | record | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseETagTests.cs:168` | | `PlainEntity` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseETagTests.cs:165` | | `PlainEntityController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseETagTests.cs:181` | | `ProblemDetailsRoundTripTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/ProblemDetailsRoundTripTests.cs:22` | -| `ReadScopeDTO` | record | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:316` | -| `ReadScopeEntity` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:313` | -| `RecordingQueryService` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:400` | +| `ReadScopeDTO` | record | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:318` | +| `ReadScopeEntity` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:315` | +| `RecordingQueryService` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:402` | | `RoundTripController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/ProblemDetailsRoundTripTests.cs:153` | -| `ScopedExportTestController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:503` | -| `SpecificationHonoringQueryService` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:517` | -| `SyncScopedReadController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:370` | +| `ScopedExportTestController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:547` | +| `SpecificationHonoringQueryService` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:561` | +| `SyncScopedReadController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:372` | | `TestAggDTO` | record | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/AggregateRootEntityControllerBaseTests.cs:151` | | `TestAggregateEntity` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/AggregateRootEntityControllerBaseTests.cs:149` | | `TestAggregateRootController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/AggregateRootEntityControllerBaseTests.cs:141` | @@ -2382,7 +2433,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `TestEntity` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseTests.cs:342` | | `TestEntityController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseTests.cs:331` | | `TestUpdateRequest` | record | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/CrudEntityControllerBaseTests.cs:155` | -| `UnscopedReadController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:329` | +| `UnscopedReadController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:331` | | `VersionedDTO` | record | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseETagTests.cs:155` | | `VersionedEntity` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseETagTests.cs:152` | | `VersionedEntityController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers` | `MMCA.Common.API.Tests/Controllers/EntityControllerBaseETagTests.cs:175` | @@ -2396,12 +2447,12 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `OAuthControllerBaseTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers.Auth` | `MMCA.Common.API.Tests/Controllers/Auth/OAuthControllerBaseTests.cs:28` | | `OverridingAuthController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers.Auth` | `MMCA.Common.API.Tests/Controllers/Auth/AuthControllerBaseRateLimitTests.cs:88` | | `PasswordResetAuthControllerBaseTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers.Auth` | `MMCA.Common.API.Tests/Controllers/Auth/PasswordResetAuthControllerBaseTests.cs:23` | -| `SingleServiceProvider` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers.Auth` | `MMCA.Common.API.Tests/Controllers/Auth/OAuthControllerBaseTests.cs:643` | -| `TestAuthController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers.Auth` | `MMCA.Common.API.Tests/Controllers/Auth/AuthControllerBaseTests.cs:339` | +| `SingleServiceProvider` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers.Auth` | `MMCA.Common.API.Tests/Controllers/Auth/OAuthControllerBaseTests.cs:664` | +| `TestAuthController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers.Auth` | `MMCA.Common.API.Tests/Controllers/Auth/AuthControllerBaseTests.cs:350` | | `TestChangePasswordCommand` | record | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers.Auth` | `MMCA.Common.API.Tests/Controllers/Auth/UserAccountAuthControllerBaseTests.cs:243` | | `TestChangePreferencesCommand` | record | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers.Auth` | `MMCA.Common.API.Tests/Controllers/Auth/UserAccountAuthControllerBaseTests.cs:250` | | `TestForgotPasswordCommand` | record | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers.Auth` | `MMCA.Common.API.Tests/Controllers/Auth/PasswordResetAuthControllerBaseTests.cs:155` | -| `TestOAuthController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers.Auth` | `MMCA.Common.API.Tests/Controllers/Auth/OAuthControllerBaseTests.cs:653` | +| `TestOAuthController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers.Auth` | `MMCA.Common.API.Tests/Controllers/Auth/OAuthControllerBaseTests.cs:674` | | `TestPasswordResetController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers.Auth` | `MMCA.Common.API.Tests/Controllers/Auth/PasswordResetAuthControllerBaseTests.cs:165` | | `TestResetPasswordCommand` | record | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers.Auth` | `MMCA.Common.API.Tests/Controllers/Auth/PasswordResetAuthControllerBaseTests.cs:162` | | `TestUserAccountAuthController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers.Auth` | `MMCA.Common.API.Tests/Controllers/Auth/UserAccountAuthControllerBaseTests.cs:253` | @@ -2415,61 +2466,75 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `TestDataExportController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers.Privacy` | `MMCA.Common.API.Tests/Controllers/Privacy/DataExportControllerBaseTests.cs:267` | | `TestExportQuery` | record | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Controllers.Privacy` | `MMCA.Common.API.Tests/Controllers/Privacy/DataExportControllerBaseTests.cs:259` | | `CsvWriterTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Export` | `MMCA.Common.API.Tests/Export/CsvWriterTests.cs:8` | +| `EntityCsvExporterTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Export` | `MMCA.Common.API.Tests/Export/EntityCsvExporterTests.cs:13` | +| `ExportRow` | record | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Export` | `MMCA.Common.API.Tests/Export/EntityCsvExporterTests.cs:55` | | `CurrentUserTargetingContextAccessorTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.FeatureManagement` | `MMCA.Common.API.Tests/FeatureManagement/CurrentUserTargetingContextAccessorTests.cs:15` | | `DisabledFeatureHandlerTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.FeatureManagement` | `MMCA.Common.API.Tests/FeatureManagement/DisabledFeatureHandlerTests.cs:11` | | `IdempotencyFilterPassthroughTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Idempotency` | `MMCA.Common.API.Tests/Idempotency/IdempotencyFilterPassthroughTests.cs:22` | | `IdempotencyFilterTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Idempotency` | `MMCA.Common.API.Tests/Idempotency/IdempotencyFilterTests.cs:21` | | `IdempotencySettingsTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Idempotency` | `MMCA.Common.API.Tests/Idempotency/IdempotencySettingsTests.cs:6` | -| `NonSeekableStream` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Idempotency` | `MMCA.Common.API.Tests/Idempotency/IdempotencyFilterTests.cs:954` | -| `TrackingHandle` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Idempotency` | `MMCA.Common.API.Tests/Idempotency/IdempotencyFilterTests.cs:992` | -| `OrderProbe` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Identifiers` | `MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:208` | -| `ProbeControllerFeatureProvider` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Identifiers` | `MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:186` | -| `ProbeOrderId` | record struct | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Identifiers` | `MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:194` | -| `ProbeSkuId` | record struct | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Identifiers` | `MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:201` | +| `NonSeekableStream` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Idempotency` | `MMCA.Common.API.Tests/Idempotency/IdempotencyFilterTests.cs:1065` | +| `TrackingHandle` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Idempotency` | `MMCA.Common.API.Tests/Idempotency/IdempotencyFilterTests.cs:1103` | +| `Wrapped` | record | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Idempotency` | `MMCA.Common.API.Tests/Idempotency/IdempotencyFilterTests.cs:657` | +| `WrappedAsStringConverter` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Idempotency` | `MMCA.Common.API.Tests/Idempotency/IdempotencyFilterTests.cs:659` | +| `OrderProbe` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Identifiers` | `MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:222` | +| `ProbeControllerFeatureProvider` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Identifiers` | `MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:200` | +| `ProbeOrderId` | record struct | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Identifiers` | `MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:208` | +| `ProbeSkuId` | record struct | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Identifiers` | `MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:215` | | `StronglyTypedIdApiTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Identifiers` | `MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:30` | -| `WrappedIdProbeController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Identifiers` | `MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:221` | +| `WrappedIdProbeController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Identifiers` | `MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:235` | | `CurrencyJsonConverterTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.JsonConverters` | `MMCA.Common.API.Tests/JsonConverters/CurrencyJsonConverterTests.cs:9` | | `EdgeErrorLocalizationTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Localization` | `MMCA.Common.API.Tests/Localization/EdgeErrorLocalizationTests.cs:16` | | `ErrorLocalizerTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Localization` | `MMCA.Common.API.Tests/Localization/ErrorLocalizerTests.cs:13` | | `StubErrorLocalizer` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Localization` | `MMCA.Common.API.Tests/Localization/EdgeErrorLocalizationTests.cs:18` | | `TestController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Localization` | `MMCA.Common.API.Tests/Localization/EdgeErrorLocalizationTests.cs:24` | | `CorrelationIdMiddlewareTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Middleware` | `MMCA.Common.API.Tests/Middleware/CorrelationIdMiddlewareTests.cs:10` | +| `ErrorHttpMappingTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Middleware` | `MMCA.Common.API.Tests/Middleware/ErrorHttpMappingTests.cs:12` | | `ExceptionHandlerTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Middleware` | `MMCA.Common.API.Tests/Middleware/ExceptionHandlerTests.cs:14` | +| `OperationCanceledExceptionHandlerTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Middleware` | `MMCA.Common.API.Tests/Middleware/OperationCanceledExceptionHandlerTests.cs:14` | | `SoftDeletedUserMiddlewareTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Middleware` | `MMCA.Common.API.Tests/Middleware/SoftDeletedUserMiddlewareTests.cs:13` | | `TenantResolutionMiddlewareTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Middleware` | `MMCA.Common.API.Tests/Middleware/TenantResolutionMiddlewareTests.cs:17` | -| `TestDomainException` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Middleware` | `MMCA.Common.API.Tests/Middleware/ExceptionHandlerTests.cs:411` | +| `TestDomainException` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Middleware` | `MMCA.Common.API.Tests/Middleware/ExceptionHandlerTests.cs:414` | | `UnhandledResultFailureFilterTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Middleware` | `MMCA.Common.API.Tests/Middleware/UnhandledResultFailureFilterTests.cs:13` | | `QueryFilterModelBinderTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.ModelBinders` | `MMCA.Common.API.Tests/ModelBinders/QueryFilterModelBinderTests.cs:9` | +| `AddCommonOpenApiHostRegistrationTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.OpenApi` | `MMCA.Common.API.Tests/OpenApi/AddCommonOpenApiHostRegistrationTests.cs:36` | | `ApiParameterDescriptorBackfillProviderTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.OpenApi` | `MMCA.Common.API.Tests/OpenApi/ApiParameterDescriptorBackfillProviderTests.cs:31` | +| `HostRegistrationProbeController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.OpenApi` | `MMCA.Common.API.Tests/OpenApi/AddCommonOpenApiHostRegistrationTests.cs:163` | +| `HostRegistrationProbeFeatureProvider` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.OpenApi` | `MMCA.Common.API.Tests/OpenApi/AddCommonOpenApiHostRegistrationTests.cs:150` | +| `MapCommonOpenApiAuthorizationTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.OpenApi` | `MMCA.Common.API.Tests/OpenApi/MapCommonOpenApiAuthorizationTests.cs:23` | | `OpenApiBaselineTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.OpenApi` | `MMCA.Common.API.Tests/OpenApi/OpenApiBaselineTests.cs:35` | | `OpenApiProbeHost` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.OpenApi` | `MMCA.Common.API.Tests/OpenApi/OpenApiProbeHost.cs:21` | -| `ProbeControllerFeatureProvider` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.OpenApi` | `MMCA.Common.API.Tests/OpenApi/OpenApiProbeHost.cs:66` | +| `ProbeControllerFeatureProvider` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.OpenApi` | `MMCA.Common.API.Tests/OpenApi/OpenApiProbeHost.cs:67` | | `ProblemDetailsProbeController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.OpenApi` | `MMCA.Common.API.Tests/OpenApi/OpenApiBaselineTests.cs:172` | | `SegmentVersionedProbeController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.OpenApi` | `MMCA.Common.API.Tests/OpenApi/ApiParameterDescriptorBackfillProviderTests.cs:157` | | `UnboundRouteTokenProbeController` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.OpenApi` | `MMCA.Common.API.Tests/OpenApi/ApiParameterDescriptorBackfillProviderTests.cs:172` | -| `RateLimitingSettingsTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.RateLimiting` | `MMCA.Common.API.Tests/RateLimiting/RateLimitingSettingsTests.cs:13` | +| `RateLimitingSettingsTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.RateLimiting` | `MMCA.Common.API.Tests/RateLimiting/RateLimitingSettingsTests.cs:16` | | `RedisFixedWindowRateLimiterTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.RateLimiting` | `MMCA.Common.API.Tests/RateLimiting/RedisFixedWindowRateLimiterTests.cs:16` | -| `CookieSessionRefresherTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.SessionCookies` | `MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs:24` | +| `ClaimsOnlySessionCookieEndpointsTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.SessionCookies` | `MMCA.Common.API.Tests/SessionCookies/ClaimsOnlySessionCookieEndpointsTests.cs:24` | +| `CookieSessionRefresherTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.SessionCookies` | `MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs:25` | | `CookieSessionRefreshMiddlewareTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.SessionCookies` | `MMCA.Common.API.Tests/SessionCookies/CookieSessionRefreshMiddlewareTests.cs:15` | | `CookieTokenReaderTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.SessionCookies` | `MMCA.Common.API.Tests/SessionCookies/CookieTokenReaderTests.cs:14` | | `NextDelegateSpy` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.SessionCookies` | `MMCA.Common.API.Tests/SessionCookies/CookieSessionRefreshMiddlewareTests.cs:144` | -| `RefresherHarness` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.SessionCookies` | `MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs:335` | +| `RefresherHarness` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.SessionCookies` | `MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs:563` | +| `SessionClaimsTokenTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.SessionCookies` | `MMCA.Common.API.Tests/SessionCookies/SessionClaimsTokenTests.cs:15` | | `SessionCookieAuthenticationHandlerTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.SessionCookies` | `MMCA.Common.API.Tests/SessionCookies/SessionCookieAuthenticationHandlerTests.cs:21` | | `SessionCookieEndpointsTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.SessionCookies` | `MMCA.Common.API.Tests/SessionCookies/SessionCookieEndpointsTests.cs:20` | | `SessionCookieJarTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.SessionCookies` | `MMCA.Common.API.Tests/SessionCookies/SessionCookieJarTests.cs:17` | -| `StubHttpClientFactory` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.SessionCookies` | `MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs:364` | -| `StubHttpMessageHandler` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.SessionCookies` | `MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs:370` | +| `StubHttpClientFactory` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.SessionCookies` | `MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs:594` | +| `StubHttpMessageHandler` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.SessionCookies` | `MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs:600` | +| `StubRefresher` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.SessionCookies` | `MMCA.Common.API.Tests/SessionCookies/ClaimsOnlySessionCookieEndpointsTests.cs:94` | | `StubRefresher` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.SessionCookies` | `MMCA.Common.API.Tests/SessionCookies/SessionCookieEndpointsTests.cs:145` | | `CommonForwardedHeadersTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Startup` | `MMCA.Common.API.Tests/Startup/CommonForwardedHeadersTests.cs:17` | | `CultureEndpointTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Startup` | `MMCA.Common.API.Tests/Startup/CultureEndpointTests.cs:16` | -| `DatabaseInitializationExtensionsTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Startup` | `MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:33` | +| `DatabaseInitializationExtensionsTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Startup` | `MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:34` | +| `DesignTimeDatabaseInitializationTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Startup` | `MMCA.Common.API.Tests/Startup/DesignTimeDatabaseInitializationTests.cs:23` | | `EndpointFeatureStub` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Startup` | `MMCA.Common.API.Tests/Startup/RateLimitEdgeCaseSecurityTests.cs:61` | | `FakeGrpcMetadata` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Startup` | `MMCA.Common.API.Tests/Startup/RateLimitEdgeCaseSecurityTests.cs:20` | -| `FixedAssemblyProvider` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Startup` | `MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:259` | -| `InitTestMigratedWidget` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Startup` | `MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:273` | -| `InitTestMigratedWidgetConfiguration` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Startup` | `MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:279` | -| `InitTestWidget` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Startup` | `MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:265` | -| `InitTestWidgetConfiguration` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Startup` | `MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:271` | +| `FixedAssemblyProvider` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Startup` | `MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:309` | +| `InitTestMigratedWidget` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Startup` | `MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:323` | +| `InitTestMigratedWidgetConfiguration` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Startup` | `MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:329` | +| `InitTestWidget` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Startup` | `MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:315` | +| `InitTestWidgetConfiguration` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Startup` | `MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:321` | | `ModuleHostExtensionsTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Startup` | `MMCA.Common.API.Tests/Startup/ModuleHostExtensionsTests.cs:22` | | `RateLimitAlgorithmSelectionTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Startup` | `MMCA.Common.API.Tests/Startup/RateLimitAlgorithmSelectionTests.cs:21` | | `RateLimitEdgeCaseSecurityTests` | class | MMCA.Common.API.Tests | `MMCA.Common.API.Tests.Startup` | `MMCA.Common.API.Tests/Startup/RateLimitEdgeCaseSecurityTests.cs:17` | @@ -2491,17 +2556,15 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `DependencyInjection` | class | MMCA.Common.Application | `MMCA.Common.Application` | `MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:13` | | `MmcaApplicationPipelineBuilder` | class | MMCA.Common.Application | `MMCA.Common.Application` | `MMCA.Common.Application/MmcaApplicationPipelineBuilder.cs:12` | | `AuditTrailEntryDTO` | record | MMCA.Common.Application | `MMCA.Common.Application.Auditing` | `MMCA.Common.Application/Auditing/AuditTrailEntryDTO.cs:12` | -| `AuthenticationServiceBase` | class | MMCA.Common.Application | `MMCA.Common.Application.Auth` | `MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:74` | +| `AuthenticationServiceBase` | class | MMCA.Common.Application | `MMCA.Common.Application.Auth` | `MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:63` | | `AuthenticationValidators` | class | MMCA.Common.Application | `MMCA.Common.Application.Auth` | `MMCA.Common.Application/Auth/AuthenticationValidators.cs:16` | | `IAuthenticationService` | interface | MMCA.Common.Application | `MMCA.Common.Application.Auth` | `MMCA.Common.Application/Auth/IAuthenticationService.cs:12` | | `ILoginProtectionService` | interface | MMCA.Common.Application | `MMCA.Common.Application.Auth` | `MMCA.Common.Application/Auth/ILoginProtectionService.cs:10` | | `IPasswordResetTokenService` | interface | MMCA.Common.Application | `MMCA.Common.Application.Auth` | `MMCA.Common.Application/Auth/IPasswordResetTokenService.cs:10` | | `IRefreshSessionStore` | interface | MMCA.Common.Application | `MMCA.Common.Application.Auth` | `MMCA.Common.Application/Auth/IRefreshSessionStore.cs:21` | -| `IssuedSession` | record | MMCA.Common.Application | `MMCA.Common.Application.Auth` | `MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:924` | | `PasswordResetSettings` | class | MMCA.Common.Application | `MMCA.Common.Application.Auth` | `MMCA.Common.Application/Auth/PasswordResetSettings.cs:10` | | `RefreshSessionRevocation` | class | MMCA.Common.Application | `MMCA.Common.Application.Auth` | `MMCA.Common.Application/Auth/RefreshSessionRevocation.cs:17` | | `RefreshSessionSettings` | class | MMCA.Common.Application | `MMCA.Common.Application.Auth` | `MMCA.Common.Application/Auth/RefreshSessionSettings.cs:9` | -| `SessionStampingTokenService` | class | MMCA.Common.Application | `MMCA.Common.Application.Auth` | `MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:936` | | `SoftDeletedUserCache` | class | MMCA.Common.Application | `MMCA.Common.Application.Auth` | `MMCA.Common.Application/Auth/SoftDeletedUserCache.cs:17` | | `UnconfiguredPermissionRegistry` | class | MMCA.Common.Application | `MMCA.Common.Application.Auth` | `MMCA.Common.Application/Auth/UnconfiguredPermissionRegistry.cs:20` | | `IRoleAdministrationService` | interface | MMCA.Common.Application | `MMCA.Common.Application.Auth.Administration` | `MMCA.Common.Application/Auth/Administration/IRoleAdministrationService.cs:29` | @@ -2515,10 +2578,14 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `IPermissionGrantStore` | interface | MMCA.Common.Application | `MMCA.Common.Application.Auth.Permissions` | `MMCA.Common.Application/Auth/Permissions/IPermissionGrantStore.cs:16` | | `LayeredPermissionRegistry` | class | MMCA.Common.Application | `MMCA.Common.Application.Auth.Permissions` | `MMCA.Common.Application/Auth/Permissions/LayeredPermissionRegistry.cs:30` | | `PermissionGrantSettings` | class | MMCA.Common.Application | `MMCA.Common.Application.Auth.Permissions` | `MMCA.Common.Application/Auth/Permissions/PermissionGrantSettings.cs:9` | +| `AuthSessionIssuer` | class | MMCA.Common.Application | `MMCA.Common.Application.Auth.Sessions` | `MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:39` | +| `IAuthSessionIssuer` | interface | MMCA.Common.Application | `MMCA.Common.Application.Auth.Sessions` | `MMCA.Common.Application/Auth/Sessions/IAuthSessionIssuer.cs:26` | +| `IssuedSession` | record | MMCA.Common.Application | `MMCA.Common.Application.Auth.Sessions` | `MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:436` | +| `SessionStampingTokenService` | class | MMCA.Common.Application | `MMCA.Common.Application.Auth.Sessions` | `MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:448` | | `ITwoFactorAuthenticator` | interface | MMCA.Common.Application | `MMCA.Common.Application.Auth.TwoFactor` | `MMCA.Common.Application/Auth/TwoFactor/ITwoFactorAuthenticator.cs:24` | | `ITwoFactorService` | interface | MMCA.Common.Application | `MMCA.Common.Application.Auth.TwoFactor` | `MMCA.Common.Application/Auth/TwoFactor/ITwoFactorService.cs:16` | | `ITwoFactorStore` | interface | MMCA.Common.Application | `MMCA.Common.Application.Auth.TwoFactor` | `MMCA.Common.Application/Auth/TwoFactor/ITwoFactorStore.cs:24` | -| `RecoveryCodeSet` | record | MMCA.Common.Application | `MMCA.Common.Application.Auth.TwoFactor` | `MMCA.Common.Application/Auth/TwoFactor/ITwoFactorService.cs:84` | +| `RecoveryCodeSet` | record | MMCA.Common.Application | `MMCA.Common.Application.Auth.TwoFactor` | `MMCA.Common.Application/Auth/TwoFactor/ITwoFactorService.cs:97` | | `TwoFactorErrors` | class | MMCA.Common.Application | `MMCA.Common.Application.Auth.TwoFactor` | `MMCA.Common.Application/Auth/TwoFactor/TwoFactorErrors.cs:15` | | `TwoFactorOutcome` | enum | MMCA.Common.Application | `MMCA.Common.Application.Auth.TwoFactor` | `MMCA.Common.Application/Auth/TwoFactor/ITwoFactorAuthenticator.cs:47` | | `TwoFactorSettings` | class | MMCA.Common.Application | `MMCA.Common.Application.Auth.TwoFactor` | `MMCA.Common.Application/Auth/TwoFactor/TwoFactorSettings.cs:15` | @@ -2536,7 +2603,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `CurrentUserServiceExtensions` | class | MMCA.Common.Application | `MMCA.Common.Application.Extensions` | `MMCA.Common.Application/Extensions/CurrentUserServiceExtensions.cs:9` | | `ReadRepositoryExtensions` | class | MMCA.Common.Application | `MMCA.Common.Application.Extensions` | `MMCA.Common.Application/Extensions/ReadRepositoryExtensions.cs:10` | | `ValidationFailureExtensions` | class | MMCA.Common.Application | `MMCA.Common.Application.Extensions` | `MMCA.Common.Application/Extensions/ValidationFailureExtensions.cs:9` | -| `CacheKeyLocks` | class | MMCA.Common.Application | `MMCA.Common.Application.Interfaces` | `MMCA.Common.Application/Interfaces/ICacheService.cs:142` | +| `CacheKeyLocks` | class | MMCA.Common.Application | `MMCA.Common.Application.Interfaces` | `MMCA.Common.Application/Interfaces/ICacheService.cs:189` | | `IAuditTrailReader` | interface | MMCA.Common.Application | `MMCA.Common.Application.Interfaces` | `MMCA.Common.Application/Interfaces/IAuditTrailReader.cs:20` | | `ICacheService` | interface | MMCA.Common.Application | `MMCA.Common.Application.Interfaces` | `MMCA.Common.Application/Interfaces/ICacheService.cs:10` | | `ICorrelationContext` | interface | MMCA.Common.Application | `MMCA.Common.Application.Interfaces` | `MMCA.Common.Application/Interfaces/ICorrelationContext.cs:8` | @@ -2571,13 +2638,13 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `IEntityReader` | interface | MMCA.Common.Application | `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` | `MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:21` | | `IOutboxAdministration` | interface | MMCA.Common.Application | `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` | `MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IOutboxAdministration.cs:16` | | `IQueryableExecutor` | interface | MMCA.Common.Application | `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` | `MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IQueryableExecutor.cs:7` | -| `IRawSqlQueryExecutor` | interface | MMCA.Common.Application | `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` | `MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRawSqlQueryExecutor.cs:23` | -| `IReadRepository` | interface | MMCA.Common.Application | `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` | `MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:330` | -| `IRepository` | interface | MMCA.Common.Application | `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` | `MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:493` | +| `IRawSqlQueryExecutor` | interface | MMCA.Common.Application | `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` | `MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRawSqlQueryExecutor.cs:24` | +| `IReadRepository` | interface | MMCA.Common.Application | `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` | `MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:332` | +| `IRepository` | interface | MMCA.Common.Application | `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` | `MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:495` | | `IUniqueConstraintViolationDetector` | interface | MMCA.Common.Application | `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` | `MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IUniqueConstraintViolationDetector.cs:31` | | `IUnitOfWork` | interface | MMCA.Common.Application | `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` | `MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IUnitOfWork.cs:10` | | `IUpdatePropertySetter` | interface | MMCA.Common.Application | `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` | `MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IUpdatePropertySetter.cs:13` | -| `IWriteRepository` | interface | MMCA.Common.Application | `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` | `MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:367` | +| `IWriteRepository` | interface | MMCA.Common.Application | `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` | `MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:369` | | `OutboxDeadLetter` | record | MMCA.Common.Application | `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` | `MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IOutboxAdministration.cs:80` | | `BlobNames` | class | MMCA.Common.Application | `MMCA.Common.Application.Interfaces.Infrastructure.Storage` | `MMCA.Common.Application/Interfaces/Infrastructure/Storage/BlobNames.cs:10` | | `DocumentContentSniffer` | class | MMCA.Common.Application | `MMCA.Common.Application.Interfaces.Infrastructure.Storage` | `MMCA.Common.Application/Interfaces/Infrastructure/Storage/DocumentContentSniffer.cs:49` | @@ -2607,7 +2674,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `IMessageBus` | interface | MMCA.Common.Application | `MMCA.Common.Application.Messaging` | `MMCA.Common.Application/Messaging/IMessageBus.cs:28` | | `IModule` | interface | MMCA.Common.Application | `MMCA.Common.Application.Modules` | `MMCA.Common.Application/Modules/IModule.cs:7` | | `IModuleSeeder` | interface | MMCA.Common.Application | `MMCA.Common.Application.Modules` | `MMCA.Common.Application/Modules/IModuleSeeder.cs:8` | -| `ModuleLoader` | class | MMCA.Common.Application | `MMCA.Common.Application.Modules` | `MMCA.Common.Application/Modules/ModuleLoader.cs:15` | +| `ModuleLoader` | class | MMCA.Common.Application | `MMCA.Common.Application.Modules` | `MMCA.Common.Application/Modules/ModuleLoader.cs:16` | | `DependencyInjection` | class | MMCA.Common.Application | `MMCA.Common.Application.Notifications` | `MMCA.Common.Application/Notifications/DependencyInjection.cs:27` | | `PushNotificationDTOMapper` | class | MMCA.Common.Application | `MMCA.Common.Application.Notifications.PushNotifications.DTOs` | `MMCA.Common.Application/Notifications/PushNotifications/DTOs/PushNotificationDTOMapper.cs:12` | | `PushNotificationDTOProjection` | class | MMCA.Common.Application | `MMCA.Common.Application.Notifications.PushNotifications.DTOs` | `MMCA.Common.Application/Notifications/PushNotifications/DTOs/PushNotificationDTOProjector.cs:22` | @@ -2615,14 +2682,14 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `GetNotificationHistoryHandler` | class | MMCA.Common.Application | `MMCA.Common.Application.Notifications.PushNotifications.UseCases.GetHistory` | `MMCA.Common.Application/Notifications/PushNotifications/UseCases/GetHistory/GetNotificationHistoryHandler.cs:15` | | `GetNotificationHistoryQuery` | record | MMCA.Common.Application | `MMCA.Common.Application.Notifications.PushNotifications.UseCases.GetHistory` | `MMCA.Common.Application/Notifications/PushNotifications/UseCases/GetHistory/GetNotificationHistoryQuery.cs:6` | | `SendPushNotificationCommand` | record | MMCA.Common.Application | `MMCA.Common.Application.Notifications.PushNotifications.UseCases.Send` | `MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationCommand.cs:21` | -| `SendPushNotificationHandler` | class | MMCA.Common.Application | `MMCA.Common.Application.Notifications.PushNotifications.UseCases.Send` | `MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:27` | +| `SendPushNotificationHandler` | class | MMCA.Common.Application | `MMCA.Common.Application.Notifications.PushNotifications.UseCases.Send` | `MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:32` | | `SendPushNotificationRequestValidator` | class | MMCA.Common.Application | `MMCA.Common.Application.Notifications.PushNotifications.UseCases.Send` | `MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationRequestValidator.cs:12` | | `GetMyNotificationsHandler` | class | MMCA.Common.Application | `MMCA.Common.Application.Notifications.UserNotifications.UseCases.GetInbox` | `MMCA.Common.Application/Notifications/UserNotifications/UseCases/GetInbox/GetMyNotificationsHandler.cs:16` | | `GetMyNotificationsQuery` | record | MMCA.Common.Application | `MMCA.Common.Application.Notifications.UserNotifications.UseCases.GetInbox` | `MMCA.Common.Application/Notifications/UserNotifications/UseCases/GetInbox/GetMyNotificationsQuery.cs:11` | | `GetUnreadNotificationCountHandler` | class | MMCA.Common.Application | `MMCA.Common.Application.Notifications.UserNotifications.UseCases.GetUnreadCount` | `MMCA.Common.Application/Notifications/UserNotifications/UseCases/GetUnreadCount/GetUnreadNotificationCountHandler.cs:13` | | `GetUnreadNotificationCountQuery` | record | MMCA.Common.Application | `MMCA.Common.Application.Notifications.UserNotifications.UseCases.GetUnreadCount` | `MMCA.Common.Application/Notifications/UserNotifications/UseCases/GetUnreadCount/GetUnreadNotificationCountQuery.cs:9` | | `MarkAllNotificationsReadCommand` | record | MMCA.Common.Application | `MMCA.Common.Application.Notifications.UserNotifications.UseCases.MarkAllRead` | `MMCA.Common.Application/Notifications/UserNotifications/UseCases/MarkAllRead/MarkAllNotificationsReadCommand.cs:10` | -| `MarkAllNotificationsReadHandler` | class | MMCA.Common.Application | `MMCA.Common.Application.Notifications.UserNotifications.UseCases.MarkAllRead` | `MMCA.Common.Application/Notifications/UserNotifications/UseCases/MarkAllRead/MarkAllNotificationsReadHandler.cs:12` | +| `MarkAllNotificationsReadHandler` | class | MMCA.Common.Application | `MMCA.Common.Application.Notifications.UserNotifications.UseCases.MarkAllRead` | `MMCA.Common.Application/Notifications/UserNotifications/UseCases/MarkAllRead/MarkAllNotificationsReadHandler.cs:18` | | `MarkNotificationReadCommand` | record | MMCA.Common.Application | `MMCA.Common.Application.Notifications.UserNotifications.UseCases.MarkRead` | `MMCA.Common.Application/Notifications/UserNotifications/UseCases/MarkRead/MarkNotificationReadCommand.cs:6` | | `MarkNotificationReadHandler` | class | MMCA.Common.Application | `MMCA.Common.Application.Notifications.UserNotifications.UseCases.MarkRead` | `MMCA.Common.Application/Notifications/UserNotifications/UseCases/MarkRead/MarkNotificationReadHandler.cs:12` | | `BestEffort` | class | MMCA.Common.Application | `MMCA.Common.Application.Services` | `MMCA.Common.Application/Services/BestEffort.cs:25` | @@ -2708,7 +2775,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `TenantCacheKey` | class | MMCA.Common.Application | `MMCA.Common.Application.UseCases.Decorators` | `MMCA.Common.Application/UseCases/Decorators/TenantCacheKey.cs:25` | | `TimeoutCommandDecorator` | class | MMCA.Common.Application | `MMCA.Common.Application.UseCases.Decorators` | `MMCA.Common.Application/UseCases/Decorators/TimeoutCommandDecorator.cs:35` | | `TimeoutQueryDecorator` | class | MMCA.Common.Application | `MMCA.Common.Application.UseCases.Decorators` | `MMCA.Common.Application/UseCases/Decorators/TimeoutQueryDecorator.cs:35` | -| `TransactionalCommandDecorator` | class | MMCA.Common.Application | `MMCA.Common.Application.UseCases.Decorators` | `MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:20` | +| `TransactionalCommandDecorator` | class | MMCA.Common.Application | `MMCA.Common.Application.UseCases.Decorators` | `MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:22` | | `UserCacheKey` | class | MMCA.Common.Application | `MMCA.Common.Application.UseCases.Decorators` | `MMCA.Common.Application/UseCases/Decorators/UserCacheKey.cs:26` | | `ValidatingCommandDecorator` | class | MMCA.Common.Application | `MMCA.Common.Application.UseCases.Decorators` | `MMCA.Common.Application/UseCases/Decorators/ValidatingCommandDecorator.cs:32` | | `ValidatingQueryDecorator` | class | MMCA.Common.Application | `MMCA.Common.Application.UseCases.Decorators` | `MMCA.Common.Application/UseCases/Decorators/ValidatingQueryDecorator.cs:35` | @@ -2725,10 +2792,9 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `IUserScopedRequest` | interface | MMCA.Common.Application | `MMCA.Common.Application.Users` | `MMCA.Common.Application/Users/IUserScopedRequest.cs:8` | | `SoftDeletedUserValidator` | class | MMCA.Common.Application | `MMCA.Common.Application.Users` | `MMCA.Common.Application/Users/SoftDeletedUserValidator.cs:20` | | `UserOwnershipRule` | class | MMCA.Common.Application | `MMCA.Common.Application.Users` | `MMCA.Common.Application/Users/UserOwnershipRule.cs:21` | -| `UserUseCaseLog` | class | MMCA.Common.Application | `MMCA.Common.Application.Users` | `MMCA.Common.Application/Users/UserUseCaseLog.cs:11` | -| `ChangePasswordHandlerBase` | class | MMCA.Common.Application | `MMCA.Common.Application.Users.UseCases.ChangePassword` | `MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:34` | +| `ChangePasswordHandlerBase` | class | MMCA.Common.Application | `MMCA.Common.Application.Users.UseCases.ChangePassword` | `MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:42` | | `ChangePreferencesHandlerBase` | class | MMCA.Common.Application | `MMCA.Common.Application.Users.UseCases.ChangePreferences` | `MMCA.Common.Application/Users/UseCases/ChangePreferences/ChangePreferencesHandlerBase.cs:23` | -| `DeleteUserHandlerBase` | class | MMCA.Common.Application | `MMCA.Common.Application.Users.UseCases.DeleteUser` | `MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:58` | +| `DeleteUserHandlerBase` | class | MMCA.Common.Application | `MMCA.Common.Application.Users.UseCases.DeleteUser` | `MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:62` | | `ConfirmEmailHandlerBase` | class | MMCA.Common.Application | `MMCA.Common.Application.Users.UseCases.EmailConfirmation` | `MMCA.Common.Application/Users/UseCases/EmailConfirmation/ConfirmEmailHandlerBase.cs:33` | | `SendEmailConfirmationHandlerBase` | class | MMCA.Common.Application | `MMCA.Common.Application.Users.UseCases.EmailConfirmation` | `MMCA.Common.Application/Users/UseCases/EmailConfirmation/SendEmailConfirmationHandlerBase.cs:40` | | `ExportUserDataHandlerBase` | class | MMCA.Common.Application | `MMCA.Common.Application.Users.UseCases.ExportUserData` | `MMCA.Common.Application/Users/UseCases/ExportUserData/ExportUserDataHandlerBase.cs:49` | @@ -2743,25 +2809,25 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ConfirmTwoFactorEnrollmentHandlerBase` | class | MMCA.Common.Application | `MMCA.Common.Application.Users.UseCases.TwoFactor` | `MMCA.Common.Application/Users/UseCases/TwoFactor/ConfirmTwoFactorEnrollmentHandlerBase.cs:29` | | `DisableTwoFactorHandlerBase` | class | MMCA.Common.Application | `MMCA.Common.Application.Users.UseCases.TwoFactor` | `MMCA.Common.Application/Users/UseCases/TwoFactor/DisableTwoFactorHandlerBase.cs:29` | | `RegenerateRecoveryCodesHandlerBase` | class | MMCA.Common.Application | `MMCA.Common.Application.Users.UseCases.TwoFactor` | `MMCA.Common.Application/Users/UseCases/TwoFactor/RegenerateRecoveryCodesHandlerBase.cs:30` | -| `AbsoluteUrlRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:85` | +| `AbsoluteUrlRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:86` | | `AddressLine1Rules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/AddressValidationRules.cs:31` | | `AddressLine2Rules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/AddressValidationRules.cs:42` | | `AddressValidator` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/AddressValidationRules.cs:13` | | `CityRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/AddressValidationRules.cs:52` | | `CommandRequestValidator` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommandRequestValidator.cs:30` | | `CountryRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/AddressValidationRules.cs:82` | -| `EmailRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:64` | -| `NonNegativeIntRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:122` | -| `OptionalErrorCodeExtensions` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:19` | -| `OptionalPositiveIdRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:161` | -| `OptionalStringRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:53` | -| `PasswordRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:174` | -| `PositiveDecimalRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:111` | -| `PositiveIntRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:100` | -| `RequiredIdRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:142` | -| `RequiredStringRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:41` | +| `EmailRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:65` | +| `NonNegativeIntRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:123` | +| `OptionalErrorCodeExtensions` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:20` | +| `OptionalPositiveIdRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:162` | +| `OptionalStringRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:54` | +| `PasswordRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:175` | +| `PositiveDecimalRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:112` | +| `PositiveIntRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:101` | +| `RequiredIdRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:143` | +| `RequiredStringRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:42` | | `StateRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/AddressValidationRules.cs:62` | -| `StrongPasswordRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:188` | +| `StrongPasswordRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/CommonValidationRules.cs:191` | | `ZipCodeRules` | class | MMCA.Common.Application | `MMCA.Common.Application.Validation` | `MMCA.Common.Application/Validation/AddressValidationRules.cs:72` | | `ApplicationPipelineCompositionTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests` | `MMCA.Common.Application.Tests/ApplicationPipelineCompositionTests.cs:22` | | `BlobNamesTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests` | `MMCA.Common.Application.Tests/BlobNamesTests.cs:11` | @@ -2796,25 +2862,25 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `TestIntegrationEventHandler` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests` | `MMCA.Common.Application.Tests/DomainEventDispatcherAdditionalTests.cs:16` | | `TestIntegrationEventHandler` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests` | `MMCA.Common.Application.Tests/DomainEventDispatcherTests.cs:39` | | `AuditTrailEntryDTOTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auditing` | `MMCA.Common.Application.Tests/Auditing/AuditTrailEntryDTOTests.cs:13` | -| `AuthenticationServiceBaseTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:28` | -| `AuthenticationServiceIdentityCompletionsTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:33` | +| `AuthenticationServiceBaseTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:29` | +| `AuthenticationServiceIdentityCompletionsTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:34` | | `AuthenticationValidatorsTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationValidatorsTests.cs:14` | -| `ConfirmableAuthenticationService` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:327` | -| `ConfirmableAuthUser` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:306` | +| `ConfirmableAuthenticationService` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:355` | +| `ConfirmableAuthUser` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:334` | | `FakeGrantCache` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/LayeredPermissionRegistryTests.cs:139` | -| `FixedClock` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:299` | -| `FixedTimeProvider` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:936` | -| `FixedTimeProvider` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/RefreshSessionManagementTests.cs:420` | -| `Harness` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:202` | +| `FixedClock` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:327` | +| `FixedTimeProvider` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:937` | +| `FixedTimeProvider` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/RefreshSessionManagementTests.cs:421` | +| `Harness` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:229` | | `LayeredPermissionRegistryTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/LayeredPermissionRegistryTests.cs:12` | -| `RefreshSessionManagementTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/RefreshSessionManagementTests.cs:30` | -| `ServiceMocks` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:868` | -| `ServiceMocks` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/RefreshSessionManagementTests.cs:293` | -| `SessionAwareAuthenticationService` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/RefreshSessionManagementTests.cs:430` | +| `RefreshSessionManagementTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/RefreshSessionManagementTests.cs:31` | +| `ServiceMocks` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:869` | +| `ServiceMocks` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/RefreshSessionManagementTests.cs:294` | +| `SessionAwareAuthenticationService` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/RefreshSessionManagementTests.cs:431` | | `SoftDeletedUserCacheTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/SoftDeletedUserCacheTests.cs:13` | -| `TestAuthenticationService` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:954` | -| `TestAuthUser` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:946` | -| `TwoFactorStub` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:366` | +| `TestAuthenticationService` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:955` | +| `TestAuthUser` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:947` | +| `TwoFactorStub` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth` | `MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:391` | | `ForgotPasswordRequestValidatorTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth.Validation` | `MMCA.Common.Application.Tests/Auth/Validation/ForgotPasswordRequestValidatorTests.cs:7` | | `LoginRequestValidatorTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth.Validation` | `MMCA.Common.Application.Tests/Auth/Validation/LoginRequestValidatorTests.cs:7` | | `RefreshTokenRequestValidatorTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Auth.Validation` | `MMCA.Common.Application.Tests/Auth/Validation/RefreshTokenRequestValidatorTests.cs:7` | @@ -2929,34 +2995,36 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `PrimitiveMapper` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Mapping` | `MMCA.Common.Application.Tests/Mapping/StronglyTypedIdMapperTests.cs:123` | | `StronglyTypedIdMapperTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Mapping` | `MMCA.Common.Application.Tests/Mapping/StronglyTypedIdMapperTests.cs:23` | | `WrapperMapper` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Mapping` | `MMCA.Common.Application.Tests/Mapping/StronglyTypedIdMapperTests.cs:107` | -| `FakeConsumerModule` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:344` | -| `FakeCycleModuleOne` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:358` | -| `FakeCycleModuleTwo` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:370` | -| `FakeModuleAlpha` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:298` | -| `FakeModuleAlphaSeeder` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:307` | -| `FakeModuleBravo` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:288` | -| `FakeModuleCharlie` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:278` | -| `FakeModuleTracker` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:260` | -| `FakeRemoteContractRealAdapter` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:274` | -| `FakeRemoteContractStub` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:271` | -| `FakeStrictModule` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:319` | -| `FakeStubbedModule` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:332` | -| `IFakeRemoteContract` | interface | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:268` | -| `ModuleLoaderTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:19` | -| `FixedTimeProvider` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/MarkAllNotificationsReadHandlerTests.cs:161` | +| `FakeConsumerModule` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:417` | +| `FakeCycleModuleOne` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:431` | +| `FakeCycleModuleTwo` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:443` | +| `FakeModuleAlpha` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:371` | +| `FakeModuleAlphaSeeder` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:380` | +| `FakeModuleBravo` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:361` | +| `FakeModuleCharlie` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:351` | +| `FakeModuleTracker` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:333` | +| `FakeRemoteContractRealAdapter` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:347` | +| `FakeRemoteContractStub` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:344` | +| `FakeStrictModule` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:392` | +| `FakeStubbedModule` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:405` | +| `IFakeRemoteContract` | interface | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:341` | +| `ModuleLoaderTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:20` | +| `ScanFailingAssembly` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Modules` | `MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:325` | +| `FixedTimeProvider` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/MarkAllNotificationsReadHandlerTests.cs:135` | | `FixedTimeProvider` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/MarkNotificationReadHandlerTests.cs:67` | | `GetMyNotificationsHandlerTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/GetMyNotificationsHandlerTests.cs:13` | | `GetNotificationHistoryHandlerTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/GetNotificationHistoryHandlerTests.cs:13` | | `GetUnreadNotificationCountHandlerTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/GetUnreadNotificationCountHandlerTests.cs:11` | | `HandlerMocks` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/GetUnreadNotificationCountHandlerTests.cs:148` | -| `HandlerMocks` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/MarkAllNotificationsReadHandlerTests.cs:166` | | `HandlerMocks` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/MarkNotificationReadHandlerTests.cs:72` | -| `HandlerMocks` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/SendPushNotificationHandlerTests.cs:316` | -| `MarkAllNotificationsReadHandlerTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/MarkAllNotificationsReadHandlerTests.cs:11` | +| `HandlerMocks` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/SendPushNotificationHandlerTests.cs:366` | +| `Harness` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/MarkAllNotificationsReadHandlerTests.cs:141` | +| `MarkAllNotificationsReadHandlerTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/MarkAllNotificationsReadHandlerTests.cs:18` | | `MarkNotificationReadHandlerTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/MarkNotificationReadHandlerTests.cs:10` | | `NotificationDependencyInjectionTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/NotificationDependencyInjectionTests.cs:21` | | `PushNotificationDTOMapperTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/PushNotificationDTOMapperTests.cs:9` | | `PushNotificationDTOProjectorTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/PushNotificationDTOProjectorTests.cs:16` | +| `RecordingSetter` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/MarkAllNotificationsReadHandlerTests.cs:207` | | `SendPushNotificationHandlerTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/SendPushNotificationHandlerTests.cs:17` | | `SendPushNotificationRequestValidatorTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Notifications` | `MMCA.Common.Application.Tests/Notifications/SendPushNotificationRequestValidatorTests.cs:10` | | `BestEffortTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Services` | `MMCA.Common.Application.Tests/Services/BestEffortTests.cs:13` | @@ -2996,7 +3064,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `SkuId` | record struct | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Services.Filtering` | `MMCA.Common.Application.Tests/Services/Filtering/StronglyTypedIdFilterStrategyTests.cs:21` | | `StringFilterStrategyTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Services.Filtering` | `MMCA.Common.Application.Tests/Services/Filtering/StringFilterStrategyTests.cs:6` | | `StronglyTypedIdFilterStrategyTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Services.Filtering` | `MMCA.Common.Application.Tests/Services/Filtering/StronglyTypedIdFilterStrategyTests.cs:13` | -| `TestStrategy` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Services.Filtering` | `MMCA.Common.Application.Tests/Services/Filtering/QueryFilterServiceTests.cs:265` | +| `TestStrategy` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Services.Filtering` | `MMCA.Common.Application.Tests/Services/Filtering/QueryFilterServiceTests.cs:286` | | `Widget` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Services.Filtering` | `MMCA.Common.Application.Tests/Services/Filtering/QueryFilterServicePropertyCacheTests.cs:18` | | `ChildA` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Services.Navigation` | `MMCA.Common.Application.Tests/Services/Navigation/NavigationMetadataProviderTests.cs:64` | | `ChildB` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Services.Navigation` | `MMCA.Common.Application.Tests/Services/Navigation/NavigationMetadataProviderTests.cs:66` | @@ -3129,10 +3197,10 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `DeleteUserHandlerBaseTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/DeleteUserHandlerBaseTests.cs:17` | | `EmailConfirmationHandlerBaseTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/EmailConfirmationHandlerBaseTests.cs:23` | | `ExportUserDataHandlerBaseTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/ExportUserDataHandlerBaseTests.cs:17` | -| `FakeAuthenticator` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:319` | +| `FakeAuthenticator` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:326` | | `ForgotPasswordHandlerBaseTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/ForgotPasswordHandlerBaseTests.cs:21` | | `GetUserPreferencesHandlerBaseTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/GetUserPreferencesHandlerBaseTests.cs:14` | -| `HandlerMocks` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/ChangePasswordHandlerBaseTests.cs:165` | +| `HandlerMocks` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/ChangePasswordHandlerBaseTests.cs:213` | | `HandlerMocks` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/ChangePreferencesHandlerBaseTests.cs:90` | | `HandlerMocks` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/DeleteUserHandlerBaseTests.cs:302` | | `HandlerMocks` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/EmailConfirmationHandlerBaseTests.cs:279` | @@ -3141,22 +3209,22 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `HandlerMocks` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/GetUserPreferencesHandlerBaseTests.cs:70` | | `HandlerMocks` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/ResetPasswordHandlerBaseTests.cs:163` | | `RecordingSection` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/ExportUserDataHandlerBaseTests.cs:289` | -| `RecordingTwoFactorStore` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:354` | +| `RecordingTwoFactorStore` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:361` | | `ResetPasswordHandlerBaseTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/ResetPasswordHandlerBaseTests.cs:20` | | `SoftDeletedUserValidatorTests` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/SoftDeletedUserValidatorTests.cs:13` | -| `StoredState` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:421` | +| `StoredState` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:428` | | `StubTwoFactorService` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:261` | -| `TestBeginHandler` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:428` | +| `TestBeginHandler` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:435` | | `TestChangePasswordCommand` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/UserUseCaseTestDoubles.cs:116` | -| `TestChangePasswordHandler` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/ChangePasswordHandlerBaseTests.cs:196` | +| `TestChangePasswordHandler` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/ChangePasswordHandlerBaseTests.cs:254` | | `TestChangePreferencesCommand` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/UserUseCaseTestDoubles.cs:120` | | `TestChangePreferencesHandler` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/ChangePreferencesHandlerBaseTests.cs:108` | | `TestConfirmEmailCommand` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/EmailConfirmationHandlerBaseTests.cs:300` | | `TestConfirmEmailHandler` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/EmailConfirmationHandlerBaseTests.cs:351` | -| `TestConfirmHandler` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:436` | +| `TestConfirmHandler` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:443` | | `TestDeleteUserCommand` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/UserUseCaseTestDoubles.cs:124` | | `TestDeleteUserHandler` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/DeleteUserHandlerBaseTests.cs:325` | -| `TestDisableHandler` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:440` | +| `TestDisableHandler` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:447` | | `TestExportUserDataHandler` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/ExportUserDataHandlerBaseTests.cs:252` | | `TestExportUserDataQuery` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/UserUseCaseTestDoubles.cs:130` | | `TestForgotPasswordCommand` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/ForgotPasswordHandlerBaseTests.cs:204` | @@ -3164,7 +3232,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `TestGetUserPreferencesHandler` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/GetUserPreferencesHandlerBaseTests.cs:87` | | `TestHidingDeleteUser` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/UserUseCaseTestDoubles.cs:103` | | `TestIdentityUser` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/UserUseCaseTestDoubles.cs:13` | -| `TestRegenerateHandler` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:444` | +| `TestRegenerateHandler` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:451` | | `TestResetPasswordCommand` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/ResetPasswordHandlerBaseTests.cs:206` | | `TestResetPasswordHandler` | class | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/ResetPasswordHandlerBaseTests.cs:210` | | `TestSendConfirmationCommand` | record | MMCA.Common.Application.Tests | `MMCA.Common.Application.Tests.Users` | `MMCA.Common.Application.Tests/Users/EmailConfirmationHandlerBaseTests.cs:296` | @@ -3220,6 +3288,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `InjectedClockFixture` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.ClockReadFixtures` | `MMCA.Common.Architecture.Tests/ClockReadFixtures/ClockReadFixtures.cs:34` | | `LambdaClockReadingFixture` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.ClockReadFixtures` | `MMCA.Common.Architecture.Tests/ClockReadFixtures/ClockReadFixtures.cs:18` | | `OffsetNowReadingFixture` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.ClockReadFixtures` | `MMCA.Common.Architecture.Tests/ClockReadFixtures/ClockReadFixtures.cs:12` | +| `TodayReadingFixture` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.ClockReadFixtures` | `MMCA.Common.Architecture.Tests/ClockReadFixtures/ClockReadFixtures.cs:48` | | `TwoMemberClockFixture` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.ClockReadFixtures` | `MMCA.Common.Architecture.Tests/ClockReadFixtures/ClockReadFixtures.cs:40` | | `UtcNowReadingFixture` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.ClockReadFixtures` | `MMCA.Common.Architecture.Tests/ClockReadFixtures/ClockReadFixtures.cs:6` | | `ArchiveTicketCommand` | record | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.CommandValidatorFixtures` | `MMCA.Common.Architecture.Tests/CommandValidatorFixtures/CommandValidatorFixtures.cs:60` | @@ -3250,16 +3319,24 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `EventUpcasterFitnessTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Contracts` | `MMCA.Common.Architecture.Tests/Contracts/EventUpcasterFitnessTests.cs:12` | | `EventVersioningConventionTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Contracts` | `MMCA.Common.Architecture.Tests/Contracts/EventVersioningConventionTests.cs:12` | | `FakeConsumerMap` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Contracts` | `MMCA.Common.Architecture.Tests/Contracts/EventScopeFitnessTests.cs:50` | -| `FixtureMap` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Contracts` | `MMCA.Common.Architecture.Tests/Contracts/IntegrationEventContractTestsBaseTests.cs:124` | +| `FixtureMap` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Contracts` | `MMCA.Common.Architecture.Tests/Contracts/IntegrationEventContractTestsBaseTests.cs:254` | | `FixtureModuleMap` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Contracts` | `MMCA.Common.Architecture.Tests/Contracts/ErrorCatalogFitnessTests.cs:106` | | `FrameworkProbeMap` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Contracts` | `MMCA.Common.Architecture.Tests/Contracts/IntegrationEventPayloadPurityTests.cs:78` | | `IntegrationEventContractTestsBaseTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Contracts` | `MMCA.Common.Architecture.Tests/Contracts/IntegrationEventContractTestsBaseTests.cs:13` | | `IntegrationEventPayloadPurityRuleTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Contracts` | `MMCA.Common.Architecture.Tests/Contracts/IntegrationEventPayloadPurityTests.cs:28` | | `IntegrationEventPayloadPurityTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Contracts` | `MMCA.Common.Architecture.Tests/Contracts/IntegrationEventPayloadPurityTests.cs:18` | | `ModuleProbeMap` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Contracts` | `MMCA.Common.Architecture.Tests/Contracts/IntegrationEventPayloadPurityTests.cs:87` | -| `ProbeTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Contracts` | `MMCA.Common.Architecture.Tests/Contracts/IntegrationEventContractTestsBaseTests.cs:134` | +| `ProbeTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Contracts` | `MMCA.Common.Architecture.Tests/Contracts/IntegrationEventContractTestsBaseTests.cs:264` | | `ProtoContractFitnessTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Contracts` | `MMCA.Common.Architecture.Tests/Contracts/ProtoContractFitnessTests.cs:14` | | `UpcasterTestMap` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Contracts` | `MMCA.Common.Architecture.Tests/Contracts/EventUpcasterFitnessTests.cs:74` | +| `FixtureCountEvent` | record | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents` | `MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:33` | +| `FixtureLabelEvent` | record | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents` | `MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:42` | +| `FixtureNamedEvent` | record | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents` | `MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:55` | +| `FixtureNullableCountEvent` | record | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents` | `MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:37` | +| `FixtureNullableLabelEvent` | record | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents` | `MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:47` | +| `FixtureShapedBase` | record | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents` | `MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:10` | +| `FixtureShapedEvent` | record | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents` | `MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:22` | +| `FixtureTallyEvent` | record | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents` | `MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:29` | | `AbstractFitnessControllerBase` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Cqrs` | `MMCA.Common.Architecture.Tests/Cqrs/IdempotencyFitnessTests.cs:71` | | `BareMap` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Cqrs` | `MMCA.Common.Architecture.Tests/Cqrs/ConstructorDependencyCountTestsBaseTests.cs:87` | | `CancellationTestMap` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Cqrs` | `MMCA.Common.Architecture.Tests/Cqrs/CancellationTokenFitnessTests.cs:63` | @@ -3287,6 +3364,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `SliceCohesionTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Cqrs` | `MMCA.Common.Architecture.Tests/Cqrs/SliceCohesionTests.cs:10` | | `TightCeilingTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Cqrs` | `MMCA.Common.Architecture.Tests/Cqrs/ConstructorDependencyCountTestsBaseTests.cs:98` | | `UndeclaredFitnessController` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Cqrs` | `MMCA.Common.Architecture.Tests/Cqrs/IdempotencyFitnessTests.cs:94` | +| `FrameworkConstructorDependencyTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Cqrs.ConstructorDependencies` | `MMCA.Common.Architecture.Tests/Cqrs/ConstructorDependencies/FrameworkConstructorDependencyTests.cs:25` | | `FixtureAssemblyMap` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Cqrs.Repositories` | `MMCA.Common.Architecture.Tests/Cqrs/Repositories/QueryHandlerReadRepositoryTests.cs:58` | | `QueryHandlerReadRepositoryTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Cqrs.Repositories` | `MMCA.Common.Architecture.Tests/Cqrs/Repositories/QueryHandlerReadRepositoryTests.cs:14` | | `AcyclicConsumer` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.CycleFixtures.Acyclic` | `MMCA.Common.Architecture.Tests/CycleFixtures/Acyclic/AcyclicFixtures.cs:6` | @@ -3303,8 +3381,8 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `FitnessDependent` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Domain` | `MMCA.Common.Architecture.Tests/Domain/SpecificationFitnessTests.cs:48` | | `FitnessPrincipal` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Domain` | `MMCA.Common.Architecture.Tests/Domain/SpecificationFitnessTests.cs:57` | | `FixtureAssemblyMap` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Domain` | `MMCA.Common.Architecture.Tests/Domain/CascadeSoftDeleteFitnessTests.cs:97` | -| `FixtureAssemblyMap` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Domain` | `MMCA.Common.Architecture.Tests/Domain/ClockReadTests.cs:78` | -| `FixtureAssemblyMap` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Domain` | `MMCA.Common.Architecture.Tests/Domain/DomainThrowFitnessTests.cs:138` | +| `FixtureAssemblyMap` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Domain` | `MMCA.Common.Architecture.Tests/Domain/ClockReadTests.cs:84` | +| `FixtureAssemblyMap` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Domain` | `MMCA.Common.Architecture.Tests/Domain/DomainThrowFitnessTests.cs:149` | | `FixtureAssemblyMap` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Domain` | `MMCA.Common.Architecture.Tests/Domain/SoftDeleteEnforcementFitnessTests.cs:77` | | `FrameworkModels` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Domain` | `MMCA.Common.Architecture.Tests/Domain/DeleteBehaviorConventionTests.cs:55` | | `IdentifierFixtureMap` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Domain` | `MMCA.Common.Architecture.Tests/Domain/StronglyTypedIdFitnessTests.cs:53` | @@ -3322,6 +3400,10 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `SpecTestMap` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Domain` | `MMCA.Common.Architecture.Tests/Domain/SpecificationFitnessTests.cs:40` | | `StronglyTypedIdConventionTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Domain` | `MMCA.Common.Architecture.Tests/Domain/StronglyTypedIdConventionTests.cs:12` | | `StronglyTypedIdFitnessTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Domain` | `MMCA.Common.Architecture.Tests/Domain/StronglyTypedIdFitnessTests.cs:11` | +| `EntityConventionTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Domain.EntityModel` | `MMCA.Common.Architecture.Tests/Domain/EntityModel/EntityConventionTests.cs:12` | +| `FrameworkModuleArchitectureMap` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Domain.EntityModel` | `MMCA.Common.Architecture.Tests/Domain/EntityModel/FrameworkModuleArchitectureMap.cs:16` | +| `ImmutabilityTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Domain.EntityModel` | `MMCA.Common.Architecture.Tests/Domain/EntityModel/ImmutabilityTests.cs:11` | +| `TenantEntityConventionTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Domain.EntityModel` | `MMCA.Common.Architecture.Tests/Domain/EntityModel/TenantEntityConventionTests.cs:20` | | `BadgeGranter` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.DomainEventSaveFixtures` | `MMCA.Common.Architecture.Tests/DomainEventSaveFixtures/DomainEventSaveFixtures.cs:70` | | `DirectSavingHandler` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.DomainEventSaveFixtures` | `MMCA.Common.Architecture.Tests/DomainEventSaveFixtures/DomainEventSaveFixtures.cs:16` | | `FixtureDomainEvent` | record | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.DomainEventSaveFixtures` | `MMCA.Common.Architecture.Tests/DomainEventSaveFixtures/DomainEventSaveFixtures.cs:13` | @@ -3337,6 +3419,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `InvalidOperationThrowingFixture` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.DomainThrowFixtures` | `MMCA.Common.Architecture.Tests/DomainThrowFixtures/DomainThrowFixtures.cs:37` | | `NonThrowingFixture` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.DomainThrowFixtures` | `MMCA.Common.Architecture.Tests/DomainThrowFixtures/DomainThrowFixtures.cs:105` | | `RethrowingFixture` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.DomainThrowFixtures` | `MMCA.Common.Architecture.Tests/DomainThrowFixtures/DomainThrowFixtures.cs:76` | +| `SwitchExpressionFixture` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.DomainThrowFixtures` | `MMCA.Common.Architecture.Tests/DomainThrowFixtures/DomainThrowFixtures.cs:115` | | `TicketDomainException` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.DomainThrowFixtures` | `MMCA.Common.Architecture.Tests/DomainThrowFixtures/DomainThrowFixtures.cs:49` | | `DuplicateTicketErrors` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.ErrorCodeFixtures` | `MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs:19` | | `DynamicErrors` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.ErrorCodeFixtures` | `MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs:46` | @@ -3347,8 +3430,11 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `FixtureBadFeatures` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.FeatureFlagFixtures` | `MMCA.Common.Architecture.Tests/FeatureFlagFixtures/FeatureFlagFixtures.cs:23` | | `FixtureExpiredFeatures` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.FeatureFlagFixtures` | `MMCA.Common.Architecture.Tests/FeatureFlagFixtures/FeatureFlagFixtures.cs:42` | | `FixtureGoodFeatures` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.FeatureFlagFixtures` | `MMCA.Common.Architecture.Tests/FeatureFlagFixtures/FeatureFlagFixtures.cs:11` | +| `DataResidencyTestsBaseTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Governance` | `MMCA.Common.Architecture.Tests/Governance/DataResidencyTestsBaseTests.cs:10` | +| `DataSourceBranchingFitnessTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Governance` | `MMCA.Common.Architecture.Tests/Governance/DataSourceBranchingFitnessTests.cs:17` | | `DataSubjectSample` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Governance` | `MMCA.Common.Architecture.Tests/Governance/PiiErasureContractFitnessTests.cs:79` | | `DependencyVersionTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Governance` | `MMCA.Common.Architecture.Tests/Governance/DependencyVersionTests.cs:9` | +| `EngineHit` | record | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Governance` | `MMCA.Common.Architecture.Tests/Governance/DataSourceBranchingFitnessTests.cs:149` | | `FeatureFlagLifecycleRuleTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Governance` | `MMCA.Common.Architecture.Tests/Governance/FeatureFlagLifecycleTests.cs:23` | | `FeatureFlagLifecycleTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Governance` | `MMCA.Common.Architecture.Tests/Governance/FeatureFlagLifecycleTests.cs:13` | | `FixtureMap` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Governance` | `MMCA.Common.Architecture.Tests/Governance/FeatureFlagLifecycleTests.cs:104` | @@ -3356,8 +3442,9 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `FrameworkSanityTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Governance` | `MMCA.Common.Architecture.Tests/Governance/FrameworkSanityTests.cs:13` | | `ObservabilityConventionTestsBaseTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Governance` | `MMCA.Common.Architecture.Tests/Governance/ObservabilityConventionTestsBaseTests.cs:14` | | `PasswordHashingFitnessTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Governance` | `MMCA.Common.Architecture.Tests/Governance/PasswordHashingFitnessTests.cs:15` | -| `PiiConventionTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Governance` | `MMCA.Common.Architecture.Tests/Governance/PiiConventionTests.cs:13` | +| `PiiConventionTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Governance` | `MMCA.Common.Architecture.Tests/Governance/PiiConventionTests.cs:20` | | `PiiErasureContractFitnessTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Governance` | `MMCA.Common.Architecture.Tests/Governance/PiiErasureContractFitnessTests.cs:19` | +| `Probe` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Governance` | `MMCA.Common.Architecture.Tests/Governance/DataResidencyTestsBaseTests.cs:24` | | `SampleDeploymentObservabilityTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Governance` | `MMCA.Common.Architecture.Tests/Governance/SampleDeploymentObservabilityTests.cs:12` | | `DbSetRemovingFixture` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.HardDeleteFixtures` | `MMCA.Common.Architecture.Tests/HardDeleteFixtures/HardDeleteFixtures.cs:17` | | `ExecuteDeletingFixture` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.HardDeleteFixtures` | `MMCA.Common.Architecture.Tests/HardDeleteFixtures/HardDeleteFixtures.cs:26` | @@ -3402,9 +3489,14 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ExtraStateFixtureId` | record struct | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.StronglyTypedIdFixtures` | `MMCA.Common.Architecture.Tests/StronglyTypedIdFixtures/StronglyTypedIdFixtures.cs:63` | | `MutableFixtureId` | record struct | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.StronglyTypedIdFixtures` | `MMCA.Common.Architecture.Tests/StronglyTypedIdFixtures/StronglyTypedIdFixtures.cs:53` | | `NotARecordFixtureId` | struct | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.StronglyTypedIdFixtures` | `MMCA.Common.Architecture.Tests/StronglyTypedIdFixtures/StronglyTypedIdFixtures.cs:40` | +| `EditorRequiredParameterConventionTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Ui` | `MMCA.Common.Architecture.Tests/Ui/EditorRequiredParameterConventionTests.cs:26` | | `LocalizationResourceTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Ui` | `MMCA.Common.Architecture.Tests/Ui/LocalizationResourceTests.cs:12` | | `LocalizedTextConventionTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Ui` | `MMCA.Common.Architecture.Tests/Ui/LocalizedTextConventionTests.cs:11` | | `NavigationContractTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Ui` | `MMCA.Common.Architecture.Tests/Ui/NavigationContractTests.cs:19` | +| `PasswordProbe` | record | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Ui` | `MMCA.Common.Architecture.Tests/Ui/PasswordRuleParityTests.cs:54` | +| `PasswordRuleParityTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Ui` | `MMCA.Common.Architecture.Tests/Ui/PasswordRuleParityTests.cs:21` | +| `PluralSentenceResourceTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Ui` | `MMCA.Common.Architecture.Tests/Ui/PluralSentenceResourceTests.cs:15` | +| `ResourceEntry` | record | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Ui` | `MMCA.Common.Architecture.Tests/Ui/PluralSentenceResourceTests.cs:70` | | `SortableColumnFitnessTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Ui` | `MMCA.Common.Architecture.Tests/Ui/SortableColumnFitnessTests.cs:15` | | `StateManagementConventionTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Ui` | `MMCA.Common.Architecture.Tests/Ui/StateManagementConventionTests.cs:11` | | `UIArchitectureConventionTests` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.Ui` | `MMCA.Common.Architecture.Tests/Ui/UIArchitectureConventionTests.cs:11` | @@ -3422,7 +3514,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `FixtureContestedV3` | record | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.UpcasterFixtures.IntegrationEvents` | `MMCA.Common.Architecture.Tests/UpcasterFixtures/IntegrationEvents/EventUpcasterFixtures.cs:39` | | `FixtureRivalClaimUpcaster` | class | MMCA.Common.Architecture.Tests | `MMCA.Common.Architecture.Tests.UpcasterFixtures.IntegrationEvents` | `MMCA.Common.Architecture.Tests/UpcasterFixtures/IntegrationEvents/EventUpcasterFixtures.cs:72` | | `DataProtectionExtensions` | class | MMCA.Common.Aspire | `MMCA.Common.Aspire` | `MMCA.Common.Aspire/DataProtection/DataProtectionExtensions.cs:19` | -| `Extensions` | class | MMCA.Common.Aspire | `MMCA.Common.Aspire` | `MMCA.Common.Aspire/Extensions.cs:18` | +| `Extensions` | class | MMCA.Common.Aspire | `MMCA.Common.Aspire` | `MMCA.Common.Aspire/Extensions.cs:19` | | `Extensions` | class | MMCA.Common.Aspire | `MMCA.Common.Aspire` | `MMCA.Common.Aspire/Extensions.Health.cs:14` | | `Extensions` | class | MMCA.Common.Aspire | `MMCA.Common.Aspire` | `MMCA.Common.Aspire/Extensions.Telemetry.cs:13` | | `GatewayCorsExtensions` | class | MMCA.Common.Aspire | `MMCA.Common.Aspire` | `MMCA.Common.Aspire/GatewayCorsExtensions.cs:16` | @@ -3497,10 +3589,12 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `KestrelEndpointExtensionsTests` | class | MMCA.Common.Aspire.Tests | `MMCA.Common.Aspire.Tests.Kestrel` | `MMCA.Common.Aspire.Tests/Kestrel/KestrelEndpointExtensionsTests.cs:14` | | `SerilogHostExtensionsTests` | class | MMCA.Common.Aspire.Tests | `MMCA.Common.Aspire.Tests.Logging` | `MMCA.Common.Aspire.Tests/Logging/SerilogHostExtensionsTests.cs:19` | | `StubHostEnvironment` | class | MMCA.Common.Aspire.Tests | `MMCA.Common.Aspire.Tests.Logging` | `MMCA.Common.Aspire.Tests/Logging/SerilogHostExtensionsTests.cs:175` | +| `ServiceDefaultsRetryTests` | class | MMCA.Common.Aspire.Tests | `MMCA.Common.Aspire.Tests.Resilience` | `MMCA.Common.Aspire.Tests/Resilience/ServiceDefaultsRetryTests.cs:18` | | `SecurityHeadersMiddlewareTests` | class | MMCA.Common.Aspire.Tests | `MMCA.Common.Aspire.Tests.Security` | `MMCA.Common.Aspire.Tests/Security/SecurityHeadersMiddlewareTests.cs:16` | | `StubCspProvider` | class | MMCA.Common.Aspire.Tests | `MMCA.Common.Aspire.Tests.Security` | `MMCA.Common.Aspire.Tests/Security/SecurityHeadersMiddlewareTests.cs:226` | | `StubWebHostEnvironment` | class | MMCA.Common.Aspire.Tests | `MMCA.Common.Aspire.Tests.Security` | `MMCA.Common.Aspire.Tests/Security/SecurityHeadersMiddlewareTests.cs:231` | | `AiTelemetryExportTests` | class | MMCA.Common.Aspire.Tests | `MMCA.Common.Aspire.Tests.Telemetry` | `MMCA.Common.Aspire.Tests/Telemetry/AiTelemetryExportTests.cs:20` | +| `LiveMetricsConfigurationTests` | class | MMCA.Common.Aspire.Tests | `MMCA.Common.Aspire.Tests.Telemetry` | `MMCA.Common.Aspire.Tests/Telemetry/LiveMetricsConfigurationTests.cs:20` | | `MetricsInstrumentationToggleTests` | class | MMCA.Common.Aspire.Tests | `MMCA.Common.Aspire.Tests.Telemetry` | `MMCA.Common.Aspire.Tests/Telemetry/MetricsInstrumentationToggleTests.cs:16` | | `OutboxPollFilterProcessorTests` | class | MMCA.Common.Aspire.Tests | `MMCA.Common.Aspire.Tests.Telemetry` | `MMCA.Common.Aspire.Tests/Telemetry/OutboxPollFilterProcessorTests.cs:12` | | `PollyResilienceMetricsTests` | class | MMCA.Common.Aspire.Tests | `MMCA.Common.Aspire.Tests.Telemetry` | `MMCA.Common.Aspire.Tests/Telemetry/PollyResilienceMetricsTests.cs:23` | @@ -3540,7 +3634,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ITwoFactorUserState` | interface | MMCA.Common.Domain | `MMCA.Common.Domain.Auth` | `MMCA.Common.Domain/Auth/ITwoFactorUserState.cs:27` | | `IUserPreferences` | interface | MMCA.Common.Domain | `MMCA.Common.Domain.Auth` | `MMCA.Common.Domain/Auth/IUserPreferences.cs:10` | | `PermissionGrant` | class | MMCA.Common.Domain | `MMCA.Common.Domain.Auth` | `MMCA.Common.Domain/Auth/PermissionGrant.cs:24` | -| `RefreshSession` | class | MMCA.Common.Domain | `MMCA.Common.Domain.Auth` | `MMCA.Common.Domain/Auth/RefreshSession.cs:31` | +| `RefreshSession` | class | MMCA.Common.Domain | `MMCA.Common.Domain.Auth` | `MMCA.Common.Domain/Auth/RefreshSession.cs:39` | | `BaseDomainEvent` | record | MMCA.Common.Domain | `MMCA.Common.Domain.DomainEvents` | `MMCA.Common.Domain/DomainEvents/BaseDomainEvent.cs:26` | | `BaseIntegrationEvent` | record | MMCA.Common.Domain | `MMCA.Common.Domain.DomainEvents` | `MMCA.Common.Domain/DomainEvents/BaseIntegrationEvent.cs:11` | | `EntityChangedEvent` | record | MMCA.Common.Domain | `MMCA.Common.Domain.DomainEvents` | `MMCA.Common.Domain/DomainEvents/EntityChangedEvent.cs:24` | @@ -3556,7 +3650,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `IAuditedEntity` | interface | MMCA.Common.Domain | `MMCA.Common.Domain.Interfaces` | `MMCA.Common.Domain/Interfaces/IAuditedEntity.cs:34` | | `IBaseEntity` | interface | MMCA.Common.Domain | `MMCA.Common.Domain.Interfaces` | `MMCA.Common.Domain/Interfaces/IBaseEntity.cs:7` | | `IDomainEvent` | interface | MMCA.Common.Domain | `MMCA.Common.Domain.Interfaces` | `MMCA.Common.Domain/Interfaces/IDomainEvent.cs:7` | -| `IHasOrderingKey` | interface | MMCA.Common.Domain | `MMCA.Common.Domain.Interfaces` | `MMCA.Common.Domain/Interfaces/IHasOrderingKey.cs:24` | +| `IHasOrderingKey` | interface | MMCA.Common.Domain | `MMCA.Common.Domain.Interfaces` | `MMCA.Common.Domain/Interfaces/IHasOrderingKey.cs:29` | | `IIntegrationEvent` | interface | MMCA.Common.Domain | `MMCA.Common.Domain.Interfaces` | `MMCA.Common.Domain/Interfaces/IIntegrationEvent.cs:15` | | `IReactivatable` | interface | MMCA.Common.Domain | `MMCA.Common.Domain.Interfaces` | `MMCA.Common.Domain/Interfaces/IReactivatable.cs:19` | | `IRowVersioned` | interface | MMCA.Common.Domain | `MMCA.Common.Domain.Interfaces` | `MMCA.Common.Domain/Interfaces/IRowVersioned.cs:11` | @@ -3569,7 +3663,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `PushNotificationInvariants` | class | MMCA.Common.Domain | `MMCA.Common.Domain.Notifications.PushNotifications.Invariants` | `MMCA.Common.Domain/Notifications/PushNotifications/Invariants/PushNotificationInvariants.cs:10` | | `UserNotification` | class | MMCA.Common.Domain | `MMCA.Common.Domain.Notifications.UserNotifications` | `MMCA.Common.Domain/Notifications/UserNotifications/UserNotification.cs:12` | | `PiiRedactor` | class | MMCA.Common.Domain | `MMCA.Common.Domain.Privacy` | `MMCA.Common.Domain/Privacy/PiiRedactor.cs:24` | -| `RedactableProperty` | class | MMCA.Common.Domain | `MMCA.Common.Domain.Privacy` | `MMCA.Common.Domain/Privacy/PiiRedactor.cs:123` | +| `RedactableProperty` | class | MMCA.Common.Domain | `MMCA.Common.Domain.Privacy` | `MMCA.Common.Domain/Privacy/PiiRedactor.cs:126` | | `AndSpecification` | class | MMCA.Common.Domain | `MMCA.Common.Domain.Specifications` | `MMCA.Common.Domain/Specifications/Specification.cs:81` | | `InlineSpecification` | class | MMCA.Common.Domain | `MMCA.Common.Domain.Specifications` | `MMCA.Common.Domain/Specifications/Specification.cs:45` | | `NotSpecification` | class | MMCA.Common.Domain | `MMCA.Common.Domain.Specifications` | `MMCA.Common.Domain/Specifications/Specification.cs:128` | @@ -3623,6 +3717,8 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `PushNotificationTests` | class | MMCA.Common.Domain.Tests | `MMCA.Common.Domain.Tests.Notifications` | `MMCA.Common.Domain.Tests/Notifications/PushNotificationTests.cs:7` | | `UserNotificationTests` | class | MMCA.Common.Domain.Tests | `MMCA.Common.Domain.Tests.Notifications` | `MMCA.Common.Domain.Tests/Notifications/UserNotificationTests.cs:6` | | `NoPii` | class | MMCA.Common.Domain.Tests | `MMCA.Common.Domain.Tests.Privacy` | `MMCA.Common.Domain.Tests/Privacy/PiiRedactorTests.cs:27` | +| `PiiBase` | class | MMCA.Common.Domain.Tests | `MMCA.Common.Domain.Tests.Privacy` | `MMCA.Common.Domain.Tests/Privacy/PiiRedactorTests.cs:32` | +| `PiiOverride` | class | MMCA.Common.Domain.Tests | `MMCA.Common.Domain.Tests.Privacy` | `MMCA.Common.Domain.Tests/Privacy/PiiRedactorTests.cs:38` | | `PiiRedactorTests` | class | MMCA.Common.Domain.Tests | `MMCA.Common.Domain.Tests.Privacy` | `MMCA.Common.Domain.Tests/Privacy/PiiRedactorTests.cs:12` | | `Subject` | class | MMCA.Common.Domain.Tests | `MMCA.Common.Domain.Tests.Privacy` | `MMCA.Common.Domain.Tests/Privacy/PiiRedactorTests.cs:14` | | `AgeGreaterThanSpec` | class | MMCA.Common.Domain.Tests | `MMCA.Common.Domain.Tests.Specifications` | `MMCA.Common.Domain.Tests/Specifications/SpecificationTests.cs:22` | @@ -3668,23 +3764,26 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `EmptyServiceProvider` | class | MMCA.Common.Gateway.Tests | `MMCA.Common.Gateway.Tests.Transforms` | `MMCA.Common.Gateway.Tests/Transforms/GatewayTraceHeaderTransformProviderTests.cs:125` | | `GatewayTraceHeaderTransformProviderTests` | class | MMCA.Common.Gateway.Tests | `MMCA.Common.Gateway.Tests.Transforms` | `MMCA.Common.Gateway.Tests/Transforms/GatewayTraceHeaderTransformProviderTests.cs:15` | | `DependencyInjection` | class | MMCA.Common.Grpc | `MMCA.Common.Grpc` | `MMCA.Common.Grpc/DependencyInjection.cs:25` | -| `ResultGrpcExtensions` | class | MMCA.Common.Grpc | `MMCA.Common.Grpc` | `MMCA.Common.Grpc/ResultGrpcExtensions.cs:29` | +| `GrpcWireFormat` | class | MMCA.Common.Grpc | `MMCA.Common.Grpc` | `MMCA.Common.Grpc/GrpcWireFormat.cs:30` | +| `ResultGrpcExtensions` | class | MMCA.Common.Grpc | `MMCA.Common.Grpc` | `MMCA.Common.Grpc/ResultGrpcExtensions.cs:30` | | `ResultFailureException` | class | MMCA.Common.Grpc | `MMCA.Common.Grpc.Exceptions` | `MMCA.Common.Grpc/Exceptions/ResultFailureException.cs:16` | | `GrpcResultExceptionInterceptor` | class | MMCA.Common.Grpc | `MMCA.Common.Grpc.Interceptors` | `MMCA.Common.Grpc/Interceptors/GrpcResultExceptionInterceptor.cs:19` | -| `JwtForwardingClientInterceptor` | class | MMCA.Common.Grpc | `MMCA.Common.Grpc.Interceptors` | `MMCA.Common.Grpc/Interceptors/JwtForwardingClientInterceptor.cs:19` | +| `JwtForwardingClientInterceptor` | class | MMCA.Common.Grpc | `MMCA.Common.Grpc.Interceptors` | `MMCA.Common.Grpc/Interceptors/JwtForwardingClientInterceptor.cs:27` | +| `AuthenticateResultFeatureStub` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:337` | | `CountingFailureHandler` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/ResilienceCircuitBreakerFaultInjectionTests.cs:63` | | `DependencyInjectionTests` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/DependencyInjectionTests.cs:20` | | `FakeClient` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/DependencyInjectionTests.cs:22` | -| `FakeGrpcClient` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/ResilienceHandlerTests.cs:19` | -| `FakeRequest` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:280` | -| `FakeResponse` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:282` | +| `FakeGrpcClient` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/ResilienceHandlerTests.cs:23` | +| `FakeRequest` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:343` | +| `FakeResponse` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:345` | | `FakeServerCallContext` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/GrpcResultExceptionInterceptorTests.cs:125` | -| `FakeStreamReader` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:284` | -| `FakeStreamWriter` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:291` | +| `FakeStreamReader` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:347` | +| `FakeStreamWriter` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:354` | | `GrpcResultExceptionInterceptorTests` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/GrpcResultExceptionInterceptorTests.cs:24` | -| `JwtForwardingClientInterceptorTests` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:19` | +| `GrpcWireFormatTests` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/GrpcWireFormatTests.cs:18` | +| `JwtForwardingClientInterceptorTests` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:21` | | `ResilienceCircuitBreakerFaultInjectionTests` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/ResilienceCircuitBreakerFaultInjectionTests.cs:14` | -| `ResilienceHandlerTests` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/ResilienceHandlerTests.cs:15` | +| `ResilienceHandlerTests` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/ResilienceHandlerTests.cs:19` | | `ResultFailureExceptionTests` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/ResultFailureExceptionTests.cs:13` | | `ResultGrpcExtensionsDecoderTests` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/ResultGrpcExtensionsDecoderTests.cs:14` | | `ResultGrpcExtensionsTests` | class | MMCA.Common.Grpc.Tests | `MMCA.Common.Grpc.Tests` | `MMCA.Common.Grpc.Tests/ResultGrpcExtensionsTests.cs:14` | @@ -3692,14 +3791,14 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ClassReference` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure` | `MMCA.Common.Infrastructure/AssemblyReference.cs:11` | | `DependencyInjection` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure` | `MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:16` | | `DependencyInjection` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure` | `MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:17` | -| `DependencyInjection` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure` | `MMCA.Common.Infrastructure/DependencyInjection.cs:46` | +| `DependencyInjection` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure` | `MMCA.Common.Infrastructure/DependencyInjection.cs:47` | | `DependencyInjection` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure` | `MMCA.Common.Infrastructure/DependencyInjection.Jobs.cs:13` | -| `DependencyInjection` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure` | `MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:15` | +| `DependencyInjection` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure` | `MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:16` | | `DependencyInjection` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure` | `MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:17` | | `UseDatabaseAttribute` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure` | `MMCA.Common.Infrastructure/UseDatabaseAttribute.cs:22` | | `UseDataSourceAttribute` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure` | `MMCA.Common.Infrastructure/UseDataSourceAttribute.cs:13` | -| `EmailConfirmationEntry` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth` | `MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:169` | -| `EmailConfirmationTokenService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth` | `MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:35` | +| `EmailConfirmationEntry` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth` | `MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:174` | +| `EmailConfirmationTokenService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth` | `MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:36` | | `EmailIdentity` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth` | `MMCA.Common.Infrastructure/Auth/EmailIdentity.cs:12` | | `IJwksProvider` | interface | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth` | `MMCA.Common.Infrastructure/Auth/IJwksProvider.cs:11` | | `JwksSettings` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth` | `MMCA.Common.Infrastructure/Auth/JwksSettings.cs:17` | @@ -3708,20 +3807,20 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `LoginProtectionService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth` | `MMCA.Common.Infrastructure/Auth/LoginProtectionService.cs:19` | | `LoginProtectionSettings` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth` | `MMCA.Common.Infrastructure/Auth/LoginProtectionSettings.cs:9` | | `PasswordHasher` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth` | `MMCA.Common.Infrastructure/Auth/PasswordHasher.cs:12` | -| `PasswordResetEntry` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth` | `MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:160` | -| `PasswordResetTokenService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth` | `MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:26` | +| `PasswordResetEntry` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth` | `MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:199` | +| `PasswordResetTokenService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth` | `MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:33` | | `RsaJwksProvider` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth` | `MMCA.Common.Infrastructure/Auth/RsaJwksProvider.cs:14` | -| `TokenService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth` | `MMCA.Common.Infrastructure/Auth/TokenService.cs:26` | +| `TokenService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth` | `MMCA.Common.Infrastructure/Auth/TokenService.cs:27` | | `StoredPermissionRoleAdministrationService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth.Administration` | `MMCA.Common.Infrastructure/Auth/Administration/StoredPermissionRoleAdministrationService.cs:45` | | `TotpTwoFactorService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth.TwoFactor` | `MMCA.Common.Infrastructure/Auth/TwoFactor/TotpTwoFactorService.cs:35` | -| `TwoFactorAuthenticator` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth.TwoFactor` | `MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs:25` | +| `TwoFactorAuthenticator` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Auth.TwoFactor` | `MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs:36` | | `CacheKeyNamespace` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Caching` | `MMCA.Common.Infrastructure/Caching/CacheKeyPrefix.cs:50` | | `CacheKeyPrefixOptions` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Caching` | `MMCA.Common.Infrastructure/Caching/CacheKeyPrefix.cs:31` | | `CacheOptions` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Caching` | `MMCA.Common.Infrastructure/Caching/CacheOptions.cs:15` | | `CacheSettings` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Caching` | `MMCA.Common.Infrastructure/Caching/CacheSettings.cs:22` | | `DistributedCacheService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Caching` | `MMCA.Common.Infrastructure/Caching/DistributedCacheService.cs:19` | -| `HybridCacheService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Caching` | `MMCA.Common.Infrastructure/Caching/HybridCacheService.cs:36` | -| `MemoryCacheService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Caching` | `MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:18` | +| `HybridCacheService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Caching` | `MMCA.Common.Infrastructure/Caching/HybridCacheService.cs:44` | +| `MemoryCacheService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Caching` | `MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:19` | | `RedisPrefixScanner` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Caching` | `MMCA.Common.Infrastructure/Caching/RedisPrefixScanner.cs:24` | | `InProcessDistributedLock` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Concurrency` | `MMCA.Common.Infrastructure/Concurrency/InProcessDistributedLock.cs:31` | | `InProcessLockHandle` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Concurrency` | `MMCA.Common.Infrastructure/Concurrency/InProcessDistributedLock.cs:79` | @@ -3769,23 +3868,23 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `DefaultEntityConfigurationAssemblyProvider` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence` | `MMCA.Common.Infrastructure/Persistence/DefaultEntityConfigurationAssemblyProvider.cs:12` | | `EfCoreConcurrencyConflictDetector` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence` | `MMCA.Common.Infrastructure/Persistence/EfCoreConcurrencyConflictDetector.cs:18` | | `EFQueryableExecutor` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence` | `MMCA.Common.Infrastructure/Persistence/EFQueryableExecutor.cs:11` | -| `EFRawSqlQueryExecutor` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence` | `MMCA.Common.Infrastructure/Persistence/EFRawSqlQueryExecutor.cs:22` | +| `EFRawSqlQueryExecutor` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence` | `MMCA.Common.Infrastructure/Persistence/EFRawSqlQueryExecutor.cs:28` | | `EntityConfigurationOptions` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence` | `MMCA.Common.Infrastructure/Persistence/EntityConfigurationOptions.cs:10` | | `NamespaceConventions` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence` | `MMCA.Common.Infrastructure/Persistence/NamespaceConventions.cs:11` | | `PersistenceSettings` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence` | `MMCA.Common.Infrastructure/Persistence/PersistenceSettings.cs:10` | | `ProfilingHelper` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence` | `MMCA.Common.Infrastructure/Persistence/ProfilingHelper.cs:9` | | `SensitiveDataLoggingGate` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence` | `MMCA.Common.Infrastructure/Persistence/SensitiveDataLoggingGate.cs:24` | -| `SoftDeleteFilterSql` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence` | `MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:16` | +| `SoftDeleteFilterSql` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence` | `MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:17` | | `SqlServerUniqueConstraintViolationDetector` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence` | `MMCA.Common.Infrastructure/Persistence/SqlServerUniqueConstraintViolationDetector.cs:31` | | `UnitOfWork` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence` | `MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:13` | -| `AuditTrailCleanupJob` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.AuditTrail` | `MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailCleanupJob.cs:47` | +| `AuditTrailCleanupJob` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.AuditTrail` | `MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailCleanupJob.cs:45` | | `AuditTrailEntry` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.AuditTrail` | `MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailEntry.cs:23` | | `AuditTrailReader` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.AuditTrail` | `MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailReader.cs:34` | | `AuditTrailSaveChangesInterceptor` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.AuditTrail` | `MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:63` | | `AuditTrailSettings` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.AuditTrail` | `MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSettings.cs:16` | -| `CaptureContext` | record struct | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.AuditTrail` | `MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:538` | -| `PendingEntityKey` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.AuditTrail` | `MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:547` | -| `EFPermissionGrantStore` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Auth` | `MMCA.Common.Infrastructure/Persistence/Auth/EFPermissionGrantStore.cs:34` | +| `CaptureContext` | record struct | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.AuditTrail` | `MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:540` | +| `PendingEntityKey` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.AuditTrail` | `MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:549` | +| `EFPermissionGrantStore` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Auth` | `MMCA.Common.Infrastructure/Persistence/Auth/EFPermissionGrantStore.cs:35` | | `EFRefreshSessionStore` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Auth` | `MMCA.Common.Infrastructure/Persistence/Auth/EFRefreshSessionStore.cs:30` | | `PermissionGrantCache` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Auth` | `MMCA.Common.Infrastructure/Persistence/Auth/PermissionGrantCache.cs:35` | | `PermissionGrantModelBuilderExtensions` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Auth` | `MMCA.Common.Infrastructure/Persistence/Auth/PermissionGrantModelBuilderExtensions.cs:15` | @@ -3795,7 +3894,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `RefreshSessionModelBuilderExtensions` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Auth` | `MMCA.Common.Infrastructure/Persistence/Auth/RefreshSessionModelBuilderExtensions.cs:16` | | `EntityTypeBuilderExtensions` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Configuration` | `MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeBuilderExtensions.cs:13` | | `IndexBuilderExtensions` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Configuration` | `MMCA.Common.Infrastructure/Persistence/Configuration/IndexBuilderExtensions.cs:10` | -| `EntityTypeConfiguration` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration` | `MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:29` | +| `EntityTypeConfiguration` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration` | `MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:27` | | `EntityTypeConfigurationBase` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration` | `MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfigurationBase.cs:19` | | `EntityTypeConfigurationCosmos` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration` | `MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfigurationCosmos.cs:18` | | `EntityTypeConfigurationPostgreSQL` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration` | `MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfigurationPostgreSQL.cs:22` | @@ -3808,9 +3907,9 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `IEntityTypeConfigurationSQLServer` | interface | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration` | `MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/IEntityTypeConfigurationSQLServer.cs:13` | | `PushNotificationConfiguration` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration.Notifications` | `MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/Notifications/PushNotificationConfiguration.cs:16` | | `UserNotificationConfiguration` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration.Notifications` | `MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/Notifications/UserNotificationConfiguration.cs:15` | -| `CrossDataSourceDegradeConvention` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Conventions` | `MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:33` | -| `RestrictDeleteByDefaultConvention` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Conventions` | `MMCA.Common.Infrastructure/Persistence/Conventions/RestrictDeleteByDefaultConvention.cs:41` | -| `SoftDeleteUniqueIndexConvention` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Conventions` | `MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:33` | +| `CrossDataSourceDegradeConvention` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Conventions` | `MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:34` | +| `RestrictDeleteByDefaultConvention` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Conventions` | `MMCA.Common.Infrastructure/Persistence/Conventions/RestrictDeleteByDefaultConvention.cs:42` | +| `SoftDeleteUniqueIndexConvention` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Conventions` | `MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:34` | | `StronglyTypedIdModelConfiguration` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Conventions` | `MMCA.Common.Infrastructure/Persistence/Conventions/StronglyTypedIdModelConfiguration.cs:21` | | `ColumnWidth` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Conversions` | `MMCA.Common.Infrastructure/Persistence/Conversions/ColumnWidth.cs:4` | | `EmailValueConverter` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Conversions` | `MMCA.Common.Infrastructure/Persistence/Conversions/EmailValueConverter.cs:33` | @@ -3826,21 +3925,33 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ConnectionStringSettings` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources` | `MMCA.Common.Infrastructure/Persistence/DataSources/ConnectionStringSettings.cs:12` | | `ConnectionStringSettingsValidator` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources` | `MMCA.Common.Infrastructure/Persistence/DataSources/ConnectionStringSettingsValidator.cs:30` | | `DataSourceEntrySettings` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources` | `MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceEntrySettings.cs:19` | -| `DataSourceResolver` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources` | `MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:15` | -| `DataSourceService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources` | `MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceService.cs:11` | +| `DataSourceResolver` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources` | `MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:16` | +| `DataSourceService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources` | `MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceService.cs:12` | | `DataSourcesSettings` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources` | `MMCA.Common.Infrastructure/Persistence/DataSources/DataSourcesSettings.cs:13` | -| `DefaultSeed` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources` | `MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:177` | +| `DefaultSeed` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources` | `MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:216` | | `EntityDataSourceRegistry` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources` | `MMCA.Common.Infrastructure/Persistence/DataSources/EntityDataSourceRegistry.cs:21` | +| `FrameworkTableTargets` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources` | `MMCA.Common.Infrastructure/Persistence/DataSources/FrameworkTableTargets.cs:32` | | `IDataSourceResolver` | interface | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources` | `MMCA.Common.Infrastructure/Persistence/DataSources/IDataSourceResolver.cs:15` | | `IEntityDataSourceRegistry` | interface | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources` | `MMCA.Common.Infrastructure/Persistence/DataSources/IEntityDataSourceRegistry.cs:11` | -| `PhysicalDataSource` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources` | `MMCA.Common.Infrastructure/Persistence/DataSources/PhysicalDataSource.cs:20` | +| `PhysicalDataSource` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources` | `MMCA.Common.Infrastructure/Persistence/DataSources/PhysicalDataSource.cs:21` | | `Snapshot` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources` | `MMCA.Common.Infrastructure/Persistence/DataSources/EntityDataSourceRegistry.cs:25` | | `TenantDataSourceTarget` | record struct | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources` | `MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:15` | | `TenantDataSourceTargets` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources` | `MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:42` | +| `CosmosDataSourceEngine` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` | `MMCA.Common.Infrastructure/Persistence/DataSources/Engines/CosmosDataSourceEngine.cs:18` | +| `DataSourceEngineCapabilities` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` | `MMCA.Common.Infrastructure/Persistence/DataSources/Engines/DataSourceEngineCapabilities.cs:23` | +| `DataSourceEngines` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` | `MMCA.Common.Infrastructure/Persistence/DataSources/Engines/DataSourceEngines.cs:19` | +| `ExplicitKeyInsertGroup` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` | `MMCA.Common.Infrastructure/Persistence/DataSources/Engines/ExplicitKeyInsertGroup.cs:12` | +| `IDataSourceEngine` | interface | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` | `MMCA.Common.Infrastructure/Persistence/DataSources/Engines/IDataSourceEngine.cs:18` | +| `IExplicitKeyInsertDialect` | interface | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` | `MMCA.Common.Infrastructure/Persistence/DataSources/Engines/IExplicitKeyInsertDialect.cs:13` | +| `MigrationPolicy` | enum | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` | `MMCA.Common.Infrastructure/Persistence/DataSources/Engines/MigrationPolicy.cs:8` | +| `PostgreSQLDataSourceEngine` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` | `MMCA.Common.Infrastructure/Persistence/DataSources/Engines/PostgreSQLDataSourceEngine.cs:17` | +| `RowVersionStrategy` | enum | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` | `MMCA.Common.Infrastructure/Persistence/DataSources/Engines/RowVersionStrategy.cs:8` | +| `SqliteDataSourceEngine` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` | `MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SqliteDataSourceEngine.cs:16` | +| `SQLServerDataSourceEngine` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` | `MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SQLServerDataSourceEngine.cs:19` | | `ApplicationDbContext` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts` | `MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:46` | | `CosmosDbContext` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts` | `MMCA.Common.Infrastructure/Persistence/DbContexts/CosmosDbContext.cs:15` | | `DataSourceModelCacheKeyFactory` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts` | `MMCA.Common.Infrastructure/Persistence/DbContexts/DataSourceModelCacheKeyFactory.cs:16` | -| `DetectChangesScope` | struct | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts` | `MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:279` | +| `DetectChangesScope` | struct | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts` | `MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:284` | | `ModelBuilderExtensions` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts` | `MMCA.Common.Infrastructure/Persistence/DbContexts/ModelBuilderExtensions.cs:10` | | `PostgreSQLDbContext` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts` | `MMCA.Common.Infrastructure/Persistence/DbContexts/PostgreSQLDbContext.cs:23` | | `SqliteDbContext` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts` | `MMCA.Common.Infrastructure/Persistence/DbContexts/SqliteDbContext.cs:12` | @@ -3849,11 +3960,10 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `DesignTimeDbContextOptions` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts.Design` | `MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextOptions.cs:11` | | `ExplicitAssemblyProvider` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts.Design` | `MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextHelper.cs:223` | | `NullDomainEventDispatcher` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts.Design` | `MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextHelper.cs:228` | -| `DbContextFactory` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts.Factory` | `MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:46` | +| `DbContextFactory` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts.Factory` | `MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:48` | | `IDbContextFactory` | interface | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts.Factory` | `MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/IDbContextFactory.cs:10` | -| `IdentityInsertGroup` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts.Factory` | `MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:424` | | `IPhysicalDbContextFactory` | interface | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts.Factory` | `MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/IPhysicalDbContextFactory.cs:15` | -| `PhysicalDbContextFactory` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts.Factory` | `MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/PhysicalDbContextFactory.cs:16` | +| `PhysicalDbContextFactory` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts.Factory` | `MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/PhysicalDbContextFactory.cs:19` | | `TransactionCommitAmbiguousException` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts.Factory` | `MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/TransactionCommitAmbiguousException.cs:22` | | `DbSeeder` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts.Seeding` | `MMCA.Common.Infrastructure/Persistence/DbContexts/Seeding/DbSeeder.cs:7` | | `IDbSeeder` | interface | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.DbContexts.Seeding` | `MMCA.Common.Infrastructure/Persistence/DbContexts/Seeding/IDbSeeder.cs:7` | @@ -3866,7 +3976,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `InboxMessage` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Inbox` | `MMCA.Common.Infrastructure/Persistence/Inbox/InboxMessage.cs:8` | | `NoOpInboxStore` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Inbox` | `MMCA.Common.Infrastructure/Persistence/Inbox/NoOpInboxStore.cs:7` | | `AggregateCapture` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Interceptors` | `MMCA.Common.Infrastructure/Persistence/Interceptors/DomainEventSaveChangesInterceptor.cs:380` | -| `AuditSaveChangesInterceptor` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Interceptors` | `MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:22` | +| `AuditSaveChangesInterceptor` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Interceptors` | `MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:30` | | `CapturedState` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Interceptors` | `MMCA.Common.Infrastructure/Persistence/Interceptors/DomainEventSaveChangesInterceptor.cs:389` | | `CrossTenantWriteException` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Interceptors` | `MMCA.Common.Infrastructure/Persistence/Interceptors/CrossTenantWriteException.cs:24` | | `DeferredDispatch` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Interceptors` | `MMCA.Common.Infrastructure/Persistence/Interceptors/DomainEventSaveChangesInterceptor.cs:396` | @@ -3874,9 +3984,10 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `TenantSaveChangesInterceptor` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Interceptors` | `MMCA.Common.Infrastructure/Persistence/Interceptors/TenantSaveChangesInterceptor.cs:36` | | `InternalCommandMessage` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.InternalCommands` | `MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandMessage.cs:21` | | `InternalCommandOrigin` | record struct | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.InternalCommands` | `MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandOrigin.cs:16` | -| `InternalCommandScheduler` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.InternalCommands` | `MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandScheduler.cs:39` | -| `InternalCommandAdministration` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration` | `MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandAdministration.cs:37` | -| `InternalCommandCleanupService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration` | `MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandCleanupService.cs:44` | +| `InternalCommandOriginCapture` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.InternalCommands` | `MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandOriginCapture.cs:21` | +| `InternalCommandScheduler` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.InternalCommands` | `MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandScheduler.cs:34` | +| `InternalCommandAdministration` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration` | `MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandAdministration.cs:34` | +| `InternalCommandCleanupService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration` | `MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandCleanupService.cs:40` | | `InternalCommandsDisabledNoticeService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration` | `MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandsDisabledNoticeService.cs:20` | | `InternalCommandsSettings` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration` | `MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandsSettings.cs:15` | | `IInternalCommandSignal` | interface | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing` | `MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/IInternalCommandSignal.cs:9` | @@ -3884,12 +3995,12 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `InternalCommandDispatcher` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing` | `MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/InternalCommandDispatcher.cs:21` | | `InternalCommandMetrics` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing` | `MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/InternalCommandMetrics.cs:17` | | `InternalCommandNameResolver` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing` | `MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/InternalCommandNameResolver.cs:19` | -| `InternalCommandProcessor` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing` | `MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/InternalCommandProcessor.cs:46` | +| `InternalCommandProcessor` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing` | `MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/InternalCommandProcessor.cs:43` | | `InternalCommandSignal` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing` | `MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/InternalCommandSignal.cs:10` | | `OutboxMessage` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Outbox` | `MMCA.Common.Infrastructure/Persistence/Outbox/OutboxMessage.cs:15` | | `OutboxOrigin` | record struct | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Outbox` | `MMCA.Common.Infrastructure/Persistence/Outbox/OutboxOrigin.cs:29` | -| `OutboxAdministration` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Outbox.Administration` | `MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:35` | -| `OutboxCleanupService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Outbox.Administration` | `MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxCleanupService.cs:45` | +| `OutboxAdministration` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Outbox.Administration` | `MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:32` | +| `OutboxCleanupService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Outbox.Administration` | `MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxCleanupService.cs:42` | | `OutboxDisabledNoticeService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Outbox.Administration` | `MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxDisabledNoticeService.cs:22` | | `OutboxSettings` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Outbox.Administration` | `MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxSettings.cs:10` | | `EventNameResolver` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure/Persistence/Outbox/Processing/EventNameResolver.cs:19` | @@ -3897,31 +4008,31 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `OutboxCycleResult` | record struct | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxCycleResult.cs:19` | | `OutboxFinalizer` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxFinalizer.cs:12` | | `OutboxMetrics` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxMetrics.cs:16` | -| `OutboxProcessor` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:56` | +| `OutboxProcessor` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:53` | | `OutboxSignal` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxSignal.cs:10` | | `PollingLoop` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Polling` | `MMCA.Common.Infrastructure/Persistence/Polling/PollingLoop.cs:12` | | `WakeUpSignal` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Polling` | `MMCA.Common.Infrastructure/Persistence/Polling/WakeUpSignal.cs:16` | -| `EFReadRepository` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Repositories` | `MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:20` | +| `EFReadRepository` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Repositories` | `MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:21` | | `EFReadRepositoryDecorator` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Repositories` | `MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepositoryDecorator.cs:17` | | `EFRepository` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Repositories` | `MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:23` | | `EFRepositoryDecorator` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Repositories` | `MMCA.Common.Infrastructure/Persistence/Repositories/EFRepositoryDecorator.cs:14` | -| `GroupedCount` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Repositories` | `MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:185` | -| `GroupedSum` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Repositories` | `MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:190` | +| `GroupedCount` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Repositories` | `MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:186` | +| `GroupedSum` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Repositories` | `MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:191` | | `KeysetQueryBuilder` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Repositories` | `MMCA.Common.Infrastructure/Persistence/Repositories/KeysetQueryBuilder.cs:22` | -| `LookupRow` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Repositories` | `MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:720` | +| `LookupRow` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Repositories` | `MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:735` | | `QueryTags` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Repositories` | `MMCA.Common.Infrastructure/Persistence/Repositories/QueryTags.cs:18` | | `SpecificationEvaluator` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Repositories` | `MMCA.Common.Infrastructure/Persistence/Repositories/SpecificationEvaluator.cs:21` | | `UpdatePropertySetterBuilder` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Repositories` | `MMCA.Common.Infrastructure/Persistence/Repositories/UpdatePropertySetterBuilder.cs:14` | -| `ValueHolder` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Repositories` | `MMCA.Common.Infrastructure/Persistence/Repositories/KeysetQueryBuilder.cs:252` | +| `ValueHolder` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Repositories` | `MMCA.Common.Infrastructure/Persistence/Repositories/KeysetQueryBuilder.cs:266` | | `IRepositoryFactory` | interface | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Repositories.Factory` | `MMCA.Common.Infrastructure/Persistence/Repositories/Factory/IRepositoryFactory.cs:11` | | `RepositoryFactory` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Repositories.Factory` | `MMCA.Common.Infrastructure/Persistence/Repositories/Factory/RepositoryFactory.cs:15` | | `TenancySettings` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Tenancy` | `MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettings.cs:50` | -| `TenancySettingsValidator` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Tenancy` | `MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:23` | +| `TenancySettingsValidator` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Tenancy` | `MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:24` | | `TenantDataSourceOverrideSettings` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Tenancy` | `MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettings.cs:138` | | `TenantEntrySettings` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Tenancy` | `MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettings.cs:121` | | `TenantResolutionStrategy` | enum | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.Tenancy` | `MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettings.cs:6` | | `CosmosIntIdValueGenerator` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Persistence.ValueGenerators` | `MMCA.Common.Infrastructure/Persistence/ValueGenerators/CosmosIntIdValueGenerator.cs:16` | -| `JobClaim` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Scheduling` | `MMCA.Common.Infrastructure/Scheduling/ScheduledJobRunner.cs:447` | +| `JobClaim` | record | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Scheduling` | `MMCA.Common.Infrastructure/Scheduling/ScheduledJobRunner.cs:455` | | `ScheduledJobEntry` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Scheduling` | `MMCA.Common.Infrastructure/Scheduling/ScheduledJobEntry.cs:20` | | `ScheduledJobOverrideSettings` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Scheduling` | `MMCA.Common.Infrastructure/Scheduling/SchedulerSettings.cs:66` | | `ScheduledJobRunner` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Scheduling` | `MMCA.Common.Infrastructure/Scheduling/ScheduledJobRunner.cs:39` | @@ -3929,7 +4040,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `SchedulerSettings` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Scheduling` | `MMCA.Common.Infrastructure/Scheduling/SchedulerSettings.cs:16` | | `AzureBlobFileStorageService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Storage` | `MMCA.Common.Infrastructure/Storage/AzureBlobFileStorageService.cs:15` | | `FileStorageSettings` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Storage` | `MMCA.Common.Infrastructure/Storage/FileStorageSettings.cs:10` | -| `ImageSharpImageProcessor` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Storage` | `MMCA.Common.Infrastructure/Storage/ImageSharpImageProcessor.cs:15` | +| `ImageSharpImageProcessor` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Storage` | `MMCA.Common.Infrastructure/Storage/ImageSharpImageProcessor.cs:16` | | `NullFileStorageService` | class | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure.Storage` | `MMCA.Common.Infrastructure/Storage/NullFileStorageService.cs:11` | | `FixedAssemblyProvider` | class | MMCA.Common.Infrastructure.PostgreSQL.Tests | `MMCA.Common.Infrastructure.PostgreSQL.Tests` | `MMCA.Common.Infrastructure.PostgreSQL.Tests/PostgreSQLPersistenceTests.cs:267` | | `PgThing` | class | MMCA.Common.Infrastructure.PostgreSQL.Tests | `MMCA.Common.Infrastructure.PostgreSQL.Tests` | `MMCA.Common.Infrastructure.PostgreSQL.Tests/PostgreSQLPersistenceTests.cs:284` | @@ -3939,7 +4050,15 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `PostgreSQLPersistenceTests` | class | MMCA.Common.Infrastructure.PostgreSQL.Tests | `MMCA.Common.Infrastructure.PostgreSQL.Tests` | `MMCA.Common.Infrastructure.PostgreSQL.Tests/PostgreSQLPersistenceTests.cs:40` | | `RecordingDomainEventDispatcher` | class | MMCA.Common.Infrastructure.PostgreSQL.Tests | `MMCA.Common.Infrastructure.PostgreSQL.Tests` | `MMCA.Common.Infrastructure.PostgreSQL.Tests/PostgreSQLPersistenceTests.cs:277` | | `DistributedCacheServiceRedisTests` | class | MMCA.Common.Infrastructure.Redis.Tests | `MMCA.Common.Infrastructure.Redis.Tests` | `MMCA.Common.Infrastructure.Redis.Tests/DistributedCacheServiceRedisTests.cs:27` | -| `HybridCacheServiceRedisTests` | class | MMCA.Common.Infrastructure.Redis.Tests | `MMCA.Common.Infrastructure.Redis.Tests` | `MMCA.Common.Infrastructure.Redis.Tests/HybridCacheServiceRedisTests.cs:35` | +| `HybridCacheServiceRedisTests` | class | MMCA.Common.Infrastructure.Redis.Tests | `MMCA.Common.Infrastructure.Redis.Tests` | `MMCA.Common.Infrastructure.Redis.Tests/HybridCacheServiceRedisTests.cs:36` | +| `FixedAssemblyProvider` | class | MMCA.Common.Infrastructure.SQLServer.Tests | `MMCA.Common.Infrastructure.SQLServer.Tests` | `MMCA.Common.Infrastructure.SQLServer.Tests/SQLServerPersistenceTests.cs:223` | +| `FixedCurrentUserService` | class | MMCA.Common.Infrastructure.SQLServer.Tests | `MMCA.Common.Infrastructure.SQLServer.Tests` | `MMCA.Common.Infrastructure.SQLServer.Tests/SQLServerPersistenceTests.cs:239` | +| `NoTenantContext` | class | MMCA.Common.Infrastructure.SQLServer.Tests | `MMCA.Common.Infrastructure.SQLServer.Tests` | `MMCA.Common.Infrastructure.SQLServer.Tests/SQLServerPersistenceTests.cs:251` | +| `RecordingDomainEventDispatcher` | class | MMCA.Common.Infrastructure.SQLServer.Tests | `MMCA.Common.Infrastructure.SQLServer.Tests` | `MMCA.Common.Infrastructure.SQLServer.Tests/SQLServerPersistenceTests.cs:233` | +| `SQLServerPersistenceTests` | class | MMCA.Common.Infrastructure.SQLServer.Tests | `MMCA.Common.Infrastructure.SQLServer.Tests` | `MMCA.Common.Infrastructure.SQLServer.Tests/SQLServerPersistenceTests.cs:44` | +| `SqlThing` | class | MMCA.Common.Infrastructure.SQLServer.Tests | `MMCA.Common.Infrastructure.SQLServer.Tests` | `MMCA.Common.Infrastructure.SQLServer.Tests/SQLServerPersistenceTests.cs:261` | +| `SqlThingConfiguration` | class | MMCA.Common.Infrastructure.SQLServer.Tests | `MMCA.Common.Infrastructure.SQLServer.Tests` | `MMCA.Common.Infrastructure.SQLServer.Tests/SQLServerPersistenceTests.cs:273` | +| `SqlThingShipped` | record | MMCA.Common.Infrastructure.SQLServer.Tests | `MMCA.Common.Infrastructure.SQLServer.Tests` | `MMCA.Common.Infrastructure.SQLServer.Tests/SQLServerPersistenceTests.cs:270` | | `DependencyInjectionAdditionalTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests` | `MMCA.Common.Infrastructure.Tests/DependencyInjectionAdditionalTests.cs:12` | | `DependencyInjectionBrokerMessagingTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests` | `MMCA.Common.Infrastructure.Tests/DependencyInjectionBrokerMessagingTests.cs:17` | | `DependencyInjectionInfrastructureTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests` | `MMCA.Common.Infrastructure.Tests/DependencyInjectionInfrastructureTests.cs:18` | @@ -3949,36 +4068,37 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `OrderPlacedConsumer` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests` | `MMCA.Common.Infrastructure.Tests/DependencyInjectionBrokerMessagingTests.cs:209` | | `OrderPlacedTestEvent` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests` | `MMCA.Common.Infrastructure.Tests/DependencyInjectionBrokerMessagingTests.cs:215` | | `UseDataSourceAttributeTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests` | `MMCA.Common.Infrastructure.Tests/UseDataSourceAttributeTests.cs:6` | -| `EmailConfirmationTokenServiceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/EmailConfirmationTokenServiceTests.cs:19` | +| `EmailConfirmationTokenServiceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/EmailConfirmationTokenServiceTests.cs:21` | | `EmailIdentityTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/EmailIdentityTests.cs:12` | | `FakeCacheService` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/LoginProtectionServiceTests.cs:291` | -| `FakeCacheService` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/PasswordResetTokenServiceTests.cs:222` | -| `FakeConfirmationCacheService` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/EmailConfirmationTokenServiceTests.cs:214` | -| `FakeTwoFactorStore` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/TwoFactorAuthenticatorTests.cs:137` | +| `FakeCacheService` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/PasswordResetTokenServiceTests.cs:296` | +| `FakeCacheService` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/TwoFactorAuthenticatorTests.cs:181` | +| `FakeConfirmationCacheService` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/EmailConfirmationTokenServiceTests.cs:259` | +| `FakeTwoFactorStore` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/TwoFactorAuthenticatorTests.cs:212` | | `LoginProtectionServiceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/LoginProtectionServiceTests.cs:14` | | `PasswordHasherSecurityTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/PasswordHasherSecurityTests.cs:18` | | `PasswordHasherTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/PasswordHasherTests.cs:6` | -| `PasswordResetTokenServiceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/PasswordResetTokenServiceTests.cs:18` | +| `PasswordResetTokenServiceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/PasswordResetTokenServiceTests.cs:22` | | `RsaJwksProviderTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/RsaJwksProviderTests.cs:13` | -| `State` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/TwoFactorAuthenticatorTests.cs:203` | -| `TokenServiceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/TokenServiceTests.cs:13` | +| `State` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/TwoFactorAuthenticatorTests.cs:278` | +| `TokenServiceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/TokenServiceTests.cs:16` | | `TotpTwoFactorServiceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/TotpTwoFactorServiceTests.cs:14` | -| `TwoFactorAuthenticatorTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/TwoFactorAuthenticatorTests.cs:16` | -| `FakeGrantCache` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth.Administration` | `MMCA.Common.Infrastructure.Tests/Auth/Administration/StoredPermissionRoleAdministrationServiceTests.cs:257` | -| `FakeGrantStore` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth.Administration` | `MMCA.Common.Infrastructure.Tests/Auth/Administration/StoredPermissionRoleAdministrationServiceTests.cs:206` | +| `TwoFactorAuthenticatorTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth` | `MMCA.Common.Infrastructure.Tests/Auth/TwoFactorAuthenticatorTests.cs:19` | +| `FakeGrantCache` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth.Administration` | `MMCA.Common.Infrastructure.Tests/Auth/Administration/StoredPermissionRoleAdministrationServiceTests.cs:297` | +| `FakeGrantStore` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth.Administration` | `MMCA.Common.Infrastructure.Tests/Auth/Administration/StoredPermissionRoleAdministrationServiceTests.cs:246` | | `StoredPermissionRoleAdministrationServiceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Auth.Administration` | `MMCA.Common.Infrastructure.Tests/Auth/Administration/StoredPermissionRoleAdministrationServiceTests.cs:22` | -| `AddCommonHybridCacheTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Caching` | `MMCA.Common.Infrastructure.Tests/Caching/AddCommonHybridCacheTests.cs:18` | +| `AddCommonHybridCacheTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Caching` | `MMCA.Common.Infrastructure.Tests/Caching/AddCommonHybridCacheTests.cs:19` | | `CacheOptionsTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Caching` | `MMCA.Common.Infrastructure.Tests/Caching/CacheOptionsTests.cs:6` | | `DistributedCacheServiceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Caching` | `MMCA.Common.Infrastructure.Tests/Caching/DistributedCacheServiceTests.cs:13` | | `EvictionSignalingMemoryCache` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Caching` | `MMCA.Common.Infrastructure.Tests/Caching/EvictionSignalingMemoryCache.cs:20` | -| `FaultingHybridCache` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Caching` | `MMCA.Common.Infrastructure.Tests/Caching/HybridCacheServiceTests.cs:485` | -| `HybridCacheServiceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Caching` | `MMCA.Common.Infrastructure.Tests/Caching/HybridCacheServiceTests.cs:23` | +| `FaultingHybridCache` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Caching` | `MMCA.Common.Infrastructure.Tests/Caching/HybridCacheServiceTests.cs:509` | +| `HybridCacheServiceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Caching` | `MMCA.Common.Infrastructure.Tests/Caching/HybridCacheServiceTests.cs:24` | | `ManualClock` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Caching` | `MMCA.Common.Infrastructure.Tests/Caching/EvictionSignalingMemoryCache.cs:63` | -| `MemoryCacheServiceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Caching` | `MMCA.Common.Infrastructure.Tests/Caching/MemoryCacheServiceTests.cs:10` | -| `RecordingDistributedCache` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Caching` | `MMCA.Common.Infrastructure.Tests/Caching/HybridCacheServiceTests.cs:380` | -| `RecordingHybridCache` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Caching` | `MMCA.Common.Infrastructure.Tests/Caching/HybridCacheServiceTests.cs:435` | +| `MemoryCacheServiceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Caching` | `MMCA.Common.Infrastructure.Tests/Caching/MemoryCacheServiceTests.cs:12` | +| `RecordingDistributedCache` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Caching` | `MMCA.Common.Infrastructure.Tests/Caching/HybridCacheServiceTests.cs:404` | +| `RecordingHybridCache` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Caching` | `MMCA.Common.Infrastructure.Tests/Caching/HybridCacheServiceTests.cs:459` | | `SignalingEntry` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Caching` | `MMCA.Common.Infrastructure.Tests/Caching/EvictionSignalingMemoryCache.cs:69` | -| `WarningCountingLogger` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Caching` | `MMCA.Common.Infrastructure.Tests/Caching/DistributedCacheServiceTests.cs:117` | +| `WarningCountingLogger` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Caching` | `MMCA.Common.Infrastructure.Tests/Caching/DistributedCacheServiceTests.cs:142` | | `InProcessDistributedLockTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Concurrency` | `MMCA.Common.Infrastructure.Tests/Concurrency/InProcessDistributedLockTests.cs:12` | | `RedisDistributedLockTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Concurrency` | `MMCA.Common.Infrastructure.Tests/Concurrency/RedisDistributedLockTests.cs:15` | | `ApplicationNamespaceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Configuration` | `MMCA.Common.Infrastructure.Tests/Configuration/ApplicationNamespaceTests.cs:16` | @@ -3998,15 +4118,17 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `SmtpTransportSecurityTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Mail` | `MMCA.Common.Infrastructure.Tests/Mail/SmtpTransportSecurityTests.cs:15` | | `BrokerEventBusTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/BrokerEventBusTests.cs:28` | | `BrokerMessageBusTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/BrokerMessageBusTests.cs:25` | +| `FakeContract` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/TypedServiceClientRegistrationTests.cs:35` | +| `IFakeContract` | interface | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/TypedServiceClientRegistrationTests.cs:31` | | `InProcessEventBusOutboxTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/InProcessEventBusOutboxTests.cs:25` | | `InProcessEventBusTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/InProcessEventBusTests.cs:20` | | `InProcessMessageBusTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/InProcessMessageBusTests.cs:20` | | `Mocks` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/BrokerEventBusTests.cs:31` | | `Mocks` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/BrokerMessageBusTests.cs:32` | | `Mocks` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/InProcessMessageBusTests.cs:25` | -| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/BrokerEventBusTests.cs:333` | -| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/InProcessEventBusOutboxTests.cs:190` | -| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/InProcessEventBusTests.cs:153` | +| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/BrokerEventBusTests.cs:330` | +| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/InProcessEventBusOutboxTests.cs:188` | +| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/InProcessEventBusTests.cs:152` | | `OtherIntegrationEvent` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/BrokerMessageBusTests.cs:29` | | `RecordingDomainHandler` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/InProcessMessageBusTests.cs:231` | | `RecordingIntegrationHandler` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/InProcessMessageBusTests.cs:240` | @@ -4020,10 +4142,11 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `TestIntegrationEvent` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/InProcessEventBusOutboxTests.cs:134` | | `TestIntegrationEvent` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/InProcessMessageBusTests.cs:22` | | `TestIntegrationEventV2` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/InProcessMessageBusTests.cs:197` | -| `TestNonOutboxContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/BrokerEventBusTests.cs:293` | -| `TestNonOutboxContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/InProcessEventBusTests.cs:118` | +| `TestNonOutboxContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/BrokerEventBusTests.cs:292` | +| `TestNonOutboxContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/InProcessEventBusTests.cs:119` | | `TestOutboxContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/BrokerEventBusTests.cs:238` | | `TestOutboxContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/InProcessEventBusOutboxTests.cs:145` | +| `TypedServiceClientRegistrationTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging` | `MMCA.Common.Infrastructure.Tests/Messaging/TypedServiceClientRegistrationTests.cs:12` | | `EventUpcasterStartupValidatorTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging.Consumers` | `MMCA.Common.Infrastructure.Tests/Messaging/Consumers/EventUpcasterStartupValidatorTests.cs:20` | | `FaultIntegrationEventConsumerTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging.Consumers` | `MMCA.Common.Infrastructure.Tests/Messaging/Consumers/FaultIntegrationEventConsumerTests.cs:15` | | `HarnessFaultingEvent` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Messaging.Consumers` | `MMCA.Common.Infrastructure.Tests/Messaging/Consumers/IntegrationEventConsumerHarnessTests.cs:32` | @@ -4060,22 +4183,22 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `FakeAggregate` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/UnitOfWorkTests.cs:110` | | `FakeEntity` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/UnitOfWorkAdditionalTests.cs:98` | | `FakeEntity` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/UnitOfWorkTests.cs:115` | -| `FixedEngineResolver` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:176` | +| `FixedEngineResolver` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:167` | | `FixedSourcesRegistry` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/MigrationApplyProofTests.cs:202` | -| `MarkAllNotificationsReadHandlerTrackingTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/MarkAllNotificationsReadHandlerTrackingTests.cs:24` | +| `MarkAllNotificationsReadHandlerTrackingTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/MarkAllNotificationsReadHandlerTrackingTests.cs:22` | | `MigrationApplyProofTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/MigrationApplyProofTests.cs:37` | | `Mocks` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/UnitOfWorkAdditionalTests.cs:13` | | `Mocks` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/UnitOfWorkTests.cs:15` | | `NoConfigurationAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/MigrationApplyProofTests.cs:223` | -| `NoModuleAssemblies` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:222` | -| `NotificationTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/MarkAllNotificationsReadHandlerTrackingTests.cs:162` | -| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/SoftDeleteQueryFilterTests.cs:114` | +| `NoModuleAssemblies` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:211` | +| `NotificationTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/MarkAllNotificationsReadHandlerTrackingTests.cs:160` | +| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/SoftDeleteQueryFilterTests.cs:112` | | `ProfilingHelperTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/ProfilingHelperTests.cs:10` | | `ProjectionTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/PushNotificationProjectionTranslationTests.cs:108` | | `PushNotificationProjectionTranslationTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/PushNotificationProjectionTranslationTests.cs:15` | -| `SeededIds` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/MarkAllNotificationsReadHandlerTrackingTests.cs:152` | +| `SeededIds` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/MarkAllNotificationsReadHandlerTrackingTests.cs:150` | | `SensitiveDataLoggingGateTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/SensitiveDataLoggingGateTests.cs:15` | -| `SingleContextFactory` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:128` | +| `SingleContextFactory` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:119` | | `SoftDeletableEntity` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/SoftDeleteQueryFilterTests.cs:62` | | `SoftDeleteQueryFilterTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/SoftDeleteQueryFilterTests.cs:22` | | `SoftDeleteTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/SoftDeleteQueryFilterTests.cs:67` | @@ -4083,33 +4206,40 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `StubHostEnvironment` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/SensitiveDataLoggingGateTests.cs:63` | | `UnitOfWorkAdditionalTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/UnitOfWorkAdditionalTests.cs:11` | | `UnitOfWorkTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/UnitOfWorkTests.cs:13` | -| `Widget` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:114` | -| `WidgetContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:184` | -| `WidgetRow` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:120` | +| `Widget` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:105` | +| `WidgetContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:175` | +| `WidgetRow` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence` | `MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:111` | | `AddAuditTrailTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AddAuditTrailTests.cs:14` | | `AuditedThing` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:40` | | `AuditTrailCleanupJobTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailCleanupJobTests.cs:21` | | `AuditTrailModelGateTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailModelGateTests.cs:26` | | `AuditTrailReaderTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailReaderTests.cs:19` | | `AuditTrailSaveChangesInterceptorTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailSaveChangesInterceptorTests.cs:18` | -| `AuditTrailTestContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:74` | +| `AuditTrailTestContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:89` | | `AuditTrailTestHarness` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:28` | -| `CompositeKeyThing` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:61` | -| `FailingSaveInterceptor` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:173` | +| `CompositeKeyThing` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:76` | +| `FailingSaveInterceptor` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:194` | | `GateTestContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailModelGateTests.cs:75` | | `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailModelGateTests.cs:121` | -| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:187` | -| `PlainThing` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:53` | +| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:208` | +| `OverridingPiiThing` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:60` | +| `PiiBaseThing` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:53` | +| `PlainThing` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail` | `MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:68` | | `CustomSchemaContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Auth` | `MMCA.Common.Infrastructure.Tests/Persistence/Auth/RefreshSessionModelBuilderExtensionsTests.cs:124` | +| `EFPermissionGrantStoreTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Auth` | `MMCA.Common.Infrastructure.Tests/Persistence/Auth/EFPermissionGrantStoreTests.cs:29` | | `EFRefreshSessionStoreFindByIdTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Auth` | `MMCA.Common.Infrastructure.Tests/Persistence/Auth/EFRefreshSessionStoreFindByIdTests.cs:21` | | `EFRefreshSessionStoreRotationTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Auth` | `MMCA.Common.Infrastructure.Tests/Persistence/Auth/EFRefreshSessionStoreRotationTests.cs:27` | | `GrantOnlyContextBase` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Auth` | `MMCA.Common.Infrastructure.Tests/Persistence/Auth/PermissionGrantModelBuilderExtensionsTests.cs:155` | | `GrantOnlyCustomSchemaContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Auth` | `MMCA.Common.Infrastructure.Tests/Persistence/Auth/PermissionGrantModelBuilderExtensionsTests.cs:167` | | `GrantOnlyPostgreSqlContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Auth` | `MMCA.Common.Infrastructure.Tests/Persistence/Auth/PermissionGrantModelBuilderExtensionsTests.cs:173` | | `GrantOnlySqlServerContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Auth` | `MMCA.Common.Infrastructure.Tests/Persistence/Auth/PermissionGrantModelBuilderExtensionsTests.cs:161` | +| `GrantTestContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Auth` | `MMCA.Common.Infrastructure.Tests/Persistence/Auth/EFPermissionGrantStoreTests.cs:92` | +| `ManualClock` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Auth` | `MMCA.Common.Infrastructure.Tests/Persistence/Auth/PermissionGrantCacheTests.cs:79` | | `NoSessionTableContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Auth` | `MMCA.Common.Infrastructure.Tests/Persistence/Auth/RefreshSessionCleanupServiceTests.cs:449` | +| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Auth` | `MMCA.Common.Infrastructure.Tests/Persistence/Auth/EFPermissionGrantStoreTests.cs:128` | | `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Auth` | `MMCA.Common.Infrastructure.Tests/Persistence/Auth/RefreshSessionCleanupServiceTests.cs:493` | | `Participant` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Auth` | `MMCA.Common.Infrastructure.Tests/Persistence/Auth/EFRefreshSessionStoreRotationTests.cs:96` | +| `PermissionGrantCacheTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Auth` | `MMCA.Common.Infrastructure.Tests/Persistence/Auth/PermissionGrantCacheTests.cs:18` | | `PermissionGrantModelBuilderExtensionsTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Auth` | `MMCA.Common.Infrastructure.Tests/Persistence/Auth/PermissionGrantModelBuilderExtensionsTests.cs:22` | | `RefreshSessionCleanupServiceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Auth` | `MMCA.Common.Infrastructure.Tests/Persistence/Auth/RefreshSessionCleanupServiceTests.cs:35` | | `RefreshSessionModelBuilderExtensionsTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Auth` | `MMCA.Common.Infrastructure.Tests/Persistence/Auth/RefreshSessionModelBuilderExtensionsTests.cs:14` | @@ -4133,6 +4263,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `IndexBuilderExtensionsTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Configuration` | `MMCA.Common.Infrastructure.Tests/Persistence/Configuration/IndexBuilderExtensionsTests.cs:16` | | `ModelBuilderExtensionsTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Configuration` | `MMCA.Common.Infrastructure.Tests/Persistence/Configuration/ModelBuilderExtensionsTests.cs:12` | | `MoneyTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Configuration` | `MMCA.Common.Infrastructure.Tests/Persistence/Configuration/OwnsMoneyTests.cs:178` | +| `NotificationConfigurationEngineTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Configuration` | `MMCA.Common.Infrastructure.Tests/Persistence/Configuration/NotificationConfigurationEngineTests.cs:23` | | `OwnsAddressTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Configuration` | `MMCA.Common.Infrastructure.Tests/Persistence/Configuration/OwnsAddressTests.cs:18` | | `OwnsMoneyTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Configuration` | `MMCA.Common.Infrastructure.Tests/Persistence/Configuration/OwnsMoneyTests.cs:18` | | `PropertyFacets` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Configuration` | `MMCA.Common.Infrastructure.Tests/Persistence/Configuration/OwnsAddressTests.cs:123` | @@ -4160,8 +4291,8 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `CascadingChild` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conventions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conventions/RestrictDeleteByDefaultConventionTests.cs:132` | | `DeleteBehaviorTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conventions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conventions/RestrictDeleteByDefaultConventionTests.cs:139` | | `FilteredIndexEntity` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conventions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conventions/SoftDeleteUniqueIndexConventionTests.cs:137` | -| `NoModuleAssemblies` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conventions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conventions/RestrictDeleteByDefaultConventionTests.cs:221` | -| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conventions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conventions/SoftDeleteUniqueIndexConventionTests.cs:223` | +| `NoModuleAssemblies` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conventions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conventions/RestrictDeleteByDefaultConventionTests.cs:219` | +| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conventions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conventions/SoftDeleteUniqueIndexConventionTests.cs:221` | | `OptionalChild` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conventions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conventions/RestrictDeleteByDefaultConventionTests.cs:125` | | `Parent` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conventions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conventions/RestrictDeleteByDefaultConventionTests.cs:105` | | `ParentDetail` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conventions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conventions/RestrictDeleteByDefaultConventionTests.cs:113` | @@ -4174,17 +4305,17 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `EmailValueConverterTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/EmailValueConverterTests.cs:12` | | `EnumerationValueConverterTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/EnumerationValueConverterTests.cs:13` | | `LineId` | record struct | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:41` | -| `NoAssemblies` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:237` | +| `NoAssemblies` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:229` | | `OrderId` | record struct | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:20` | | `PhoneNumberValueConverterTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/PhoneNumberValueConverterTests.cs:11` | | `Priority` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/EnumerationValueConverterTests.cs:89` | | `SpeakerId` | record struct | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:34` | | `StronglyTypedIdPersistenceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/StronglyTypedIdPersistenceTests.cs:20` | -| `WrappedIdBareSqliteContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:106` | -| `WrappedIdContextServices` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:190` | -| `WrappedIdPostgresContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:156` | +| `WrappedIdBareSqliteContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:104` | +| `WrappedIdContextServices` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:182` | +| `WrappedIdPostgresContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:150` | | `WrappedIdSqliteContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:70` | -| `WrappedIdSqlServerContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:131` | +| `WrappedIdSqlServerContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:127` | | `WrappedOrder` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:49` | | `WrappedSpeaker` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Conversions` | `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:57` | | `CosmosConfigurationPortabilityTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DataSources` | `MMCA.Common.Infrastructure.Tests/Persistence/DataSources/CosmosConfigurationPortabilityTests.cs:28` | @@ -4235,7 +4366,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `RegistryUnattributed` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DataSources` | `MMCA.Common.Infrastructure.Tests/Persistence/DataSources/EntityDataSourceRegistryTests.cs:204` | | `RegistryUnattributedConfiguration` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DataSources` | `MMCA.Common.Infrastructure.Tests/Persistence/DataSources/EntityDataSourceRegistryTests.cs:233` | | `UnregisteredEntity` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DataSources` | `MMCA.Common.Infrastructure.Tests/Persistence/DataSources/DataSourceServiceAdditionalTests.cs:85` | -| `AlwaysRetryExecutionStrategy` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:345` | +| `AlwaysRetryExecutionStrategy` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:343` | | `ApplicationDbContextTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/ApplicationDbContextTests.cs:19` | | `CaseDefaultSettings` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/RefreshSessionModelGateTests.cs:141` | | `CaseEnabledDefaultSource` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/RefreshSessionModelGateTests.cs:143` | @@ -4248,26 +4379,27 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `CaseOptedInDefaultSource` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/PermissionGrantModelGateTests.cs:163` | | `CaseSettingsOnly` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/PermissionGrantModelGateTests.cs:161` | | `CommitFailingDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:276` | +| `CosmosDbContextTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/CosmosDbContextTests.cs:12` | | `DbContextFactoryAdditionalTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryAdditionalTests.cs:22` | | `DbContextFactoryCommitAmbiguityTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:37` | | `DbContextFactoryMigrationTargetTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryMigrationTargetTests.cs:27` | -| `DbContextFactorySaveIntegrityTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:31` | +| `DbContextFactorySaveIntegrityTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:32` | | `DbContextFactoryTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTests.cs:15` | -| `DbContextFactoryTransactionTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:33` | +| `DbContextFactoryTransactionTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:37` | | `EmptyAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/SQLServerDbContextTests.cs:76` | -| `FailingDatabaseFacade` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:331` | +| `FailingDatabaseFacade` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:329` | | `FixedAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/PostgreSQLDbContextModelTests.cs:226` | | `FixedSourcesRegistry` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryMigrationTargetTests.cs:172` | | `GateContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/PermissionGrantModelGateTests.cs:179` | | `GateContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/RefreshSessionModelGateTests.cs:159` | -| `IntegrityAggregate` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:135` | -| `IntegrityEvent` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:133` | -| `IntegrityTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:140` | +| `IntegrityAggregate` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:136` | +| `IntegrityEvent` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:134` | +| `IntegrityTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:141` | | `MidSaveContextCreatingDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryAdditionalTests.cs:206` | | `NoConfigurationAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryMigrationTargetTests.cs:193` | -| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:352` | -| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:190` | -| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:289` | +| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:350` | +| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:191` | +| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:391` | | `PermissionGrantModelGateTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/PermissionGrantModelGateTests.cs:37` | | `PostgreSQLDbContextModelTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/PostgreSQLDbContextModelTests.cs:36` | | `PostgresThing` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/PostgreSQLDbContextModelTests.cs:233` | @@ -4278,12 +4410,12 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `SqlServerThing` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/PostgreSQLDbContextModelTests.cs:239` | | `SqlServerThingConfiguration` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/PostgreSQLDbContextModelTests.cs:257` | | `TestAggregate` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:266` | -| `TestAggregate` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:235` | -| `TestApplicationDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/ApplicationDbContextTests.cs:93` | -| `TestEntity` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/ApplicationDbContextTests.cs:88` | +| `TestAggregate` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:330` | +| `TestApplicationDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/ApplicationDbContextTests.cs:86` | +| `TestEntity` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/ApplicationDbContextTests.cs:81` | | `TestLocalEvent` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:264` | -| `TestLocalEvent` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:233` | -| `TransactionTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:240` | +| `TestLocalEvent` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:328` | +| `TransactionTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:335` | | `DbSeederTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts.Seeding` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/Seeding/DbSeederTests.cs:6` | | `IdentityModuleDbSeederBaseTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts.Seeding` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/Seeding/IdentityModuleDbSeederBaseTests.cs:18` | | `SeederMocks` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts.Seeding` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/Seeding/IdentityModuleDbSeederBaseTests.cs:109` | @@ -4291,34 +4423,34 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `TestIdentityModuleDbSeeder` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts.Seeding` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/Seeding/IdentityModuleDbSeederBaseTests.cs:144` | | `TestSeedUser` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.DbContexts.Seeding` | `MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/Seeding/IdentityModuleDbSeederBaseTests.cs:132` | | `EncryptedStringConverterTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Encryption` | `MMCA.Common.Infrastructure.Tests/Persistence/Encryption/EncryptedStringConverterTests.cs:6` | -| `EfInboxStoreTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Inbox` | `MMCA.Common.Infrastructure.Tests/Persistence/Inbox/EfInboxStoreTests.cs:27` | +| `EfInboxStoreTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Inbox` | `MMCA.Common.Infrastructure.Tests/Persistence/Inbox/EfInboxStoreTests.cs:28` | | `InboxDisabledWarningServiceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Inbox` | `MMCA.Common.Infrastructure.Tests/Persistence/Inbox/InboxDisabledWarningServiceTests.cs:11` | -| `InboxTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Inbox` | `MMCA.Common.Infrastructure.Tests/Persistence/Inbox/EfInboxStoreTests.cs:304` | +| `InboxTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Inbox` | `MMCA.Common.Infrastructure.Tests/Persistence/Inbox/EfInboxStoreTests.cs:322` | | `RecordingLogger` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Inbox` | `MMCA.Common.Infrastructure.Tests/Persistence/Inbox/InboxDisabledWarningServiceTests.cs:19` | | `AuditSaveChangesInterceptorTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/AuditSaveChangesInterceptorTests.cs:13` | | `DetectionTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/SaveChangeDetectionTests.cs:105` | -| `DomainEventCaptureExclusionTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:26` | +| `DomainEventCaptureExclusionTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:28` | | `DomainEventSaveChangesInterceptorOutboxDisabledTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorOutboxDisabledTests.cs:22` | | `DomainEventSaveChangesInterceptorOutboxRoutingTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorOutboxRoutingTests.cs:28` | -| `DomainEventSaveChangesInterceptorTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:17` | -| `ExclusionAggregate` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:142` | -| `ExclusionEvent` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:140` | -| `ExclusionTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:147` | +| `DomainEventSaveChangesInterceptorTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:19` | +| `ExclusionAggregate` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:146` | +| `ExclusionEvent` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:144` | +| `ExclusionTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:151` | | `FailingSaveInterceptor` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorOutboxRoutingTests.cs:302` | -| `FakeTimeProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/AuditSaveChangesInterceptorTests.cs:223` | -| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/AuditSaveChangesInterceptorTests.cs:287` | -| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:184` | -| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorOutboxRoutingTests.cs:376` | -| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:219` | -| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/SaveChangeDetectionTests.cs:147` | +| `FakeTimeProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/AuditSaveChangesInterceptorTests.cs:244` | +| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/AuditSaveChangesInterceptorTests.cs:306` | +| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:186` | +| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorOutboxRoutingTests.cs:374` | +| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:225` | +| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/SaveChangeDetectionTests.cs:145` | | `OutboxRoutingTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorOutboxRoutingTests.cs:316` | | `SaveChangeDetectionTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/SaveChangeDetectionTests.cs:24` | | `TestAggregate` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorOutboxRoutingTests.cs:292` | -| `TestAggregate` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:174` | -| `TestAuditDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/AuditSaveChangesInterceptorTests.cs:238` | -| `TestAuditEntity` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/AuditSaveChangesInterceptorTests.cs:232` | -| `TestDomainEvent` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:172` | -| `TestDomainEventDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:179` | +| `TestAggregate` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:182` | +| `TestAuditDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/AuditSaveChangesInterceptorTests.cs:259` | +| `TestAuditEntity` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/AuditSaveChangesInterceptorTests.cs:253` | +| `TestDomainEvent` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:180` | +| `TestDomainEventDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:187` | | `TestIntegrationEvent` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorOutboxRoutingTests.cs:290` | | `TestLocalEvent` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorOutboxRoutingTests.cs:282` | | `TestOrderedEvent` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Interceptors` | `MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorOutboxRoutingTests.cs:285` | @@ -4329,67 +4461,68 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `InternalCommandSchedulerTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandSchedulerTests.cs:16` | | `InternalCommandTestContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandTestHarness.cs:166` | | `InternalCommandTestHarness` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandTestHarness.cs:38` | -| `ModelTestContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandModelTests.cs:128` | -| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandModelTests.cs:160` | +| `ModelTestContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandModelTests.cs:141` | +| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandModelTests.cs:173` | | `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandTestHarness.cs:214` | | `RecordedExecution` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandTestHarness.cs:258` | -| `RecordingCommand` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandTestHarness.cs:275` | -| `RecordingCommandHandler` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandTestHarness.cs:285` | -| `RecordingCommandValidator` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandTestHarness.cs:278` | +| `RecordingCommand` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandTestHarness.cs:278` | +| `RecordingCommandHandler` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandTestHarness.cs:288` | +| `RecordingCommandValidator` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandTestHarness.cs:281` | | `StubCurrentUserService` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandTestHarness.cs:239` | -| `InternalCommandAdministrationTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands.Administration` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/Administration/InternalCommandAdministrationTests.cs:22` | -| `InternalCommandCleanupServiceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands.Administration` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/Administration/InternalCommandCleanupServiceTests.cs:21` | +| `InternalCommandAdministrationTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands.Administration` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/Administration/InternalCommandAdministrationTests.cs:23` | +| `InternalCommandCleanupServiceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands.Administration` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/Administration/InternalCommandCleanupServiceTests.cs:22` | | `InternalCommandProcessorTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/Processing/InternalCommandProcessorTests.cs:18` | | `NamedDomainEvent` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/OutboxMessageTests.cs:26` | | `OrderedDomainEvent` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/OutboxMessageTests.cs:20` | | `OutboxMessageTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/OutboxMessageTests.cs:14` | | `TestDomainEvent` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/OutboxMessageTests.cs:16` | | `TestDomainEventWithData` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/OutboxMessageTests.cs:18` | -| `AdminTestContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Administration` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Administration/OutboxAdministrationTests.cs:238` | -| `CleanupTestContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Administration` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Administration/OutboxCleanupServiceTests.cs:559` | +| `AdminTestContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Administration` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Administration/OutboxAdministrationTests.cs:237` | +| `CleanupTestContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Administration` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Administration/OutboxCleanupServiceTests.cs:557` | | `Mocks` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Administration` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Administration/OutboxCleanupServiceTests.cs:47` | -| `NoAssemblies` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Administration` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Administration/OutboxAdministrationTests.cs:278` | -| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Administration` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Administration/OutboxCleanupServiceTests.cs:616` | +| `NoAssemblies` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Administration` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Administration/OutboxAdministrationTests.cs:275` | +| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Administration` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Administration/OutboxCleanupServiceTests.cs:612` | | `OutboxAdministrationTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Administration` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Administration/OutboxAdministrationTests.cs:28` | | `OutboxCleanupServiceTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Administration` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Administration/OutboxCleanupServiceTests.cs:34` | -| `CapturingMessageBus` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:192` | -| `NoAssemblies` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorOrderingTests.cs:292` | -| `Observation` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:186` | -| `OrderedTestEvent` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorOrderingTests.cs:238` | -| `OrderingTestContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorOrderingTests.cs:252` | +| `CapturingMessageBus` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:220` | +| `NoAssemblies` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorOrderingTests.cs:289` | +| `Observation` | record | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:214` | +| `OrderedTestEvent` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorOrderingTests.cs:237` | +| `OrderingTestContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorOrderingTests.cs:251` | | `OutboxProcessorContextRestoreTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:34` | | `OutboxProcessorExecuteAsyncTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorExecuteAsyncTests.cs:20` | | `OutboxProcessorOrderingTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorOrderingTests.cs:36` | | `OutboxProcessorTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorTests.cs:32` | | `OutboxProcessorWaitTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorWaitTests.cs:10` | | `OutboxSignalTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxSignalTests.cs:13` | -| `OutboxTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorTests.cs:1100` | -| `OverrideOnlyCurrentUserService` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:225` | -| `RestoreTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:248` | -| `TestDomainEvent` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorTests.cs:1076` | -| `TestIntegrationEvent` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:178` | -| `TestIntegrationEvent` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorTests.cs:1088` | +| `OutboxTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorTests.cs:1131` | +| `OverrideOnlyCurrentUserService` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:253` | +| `RestoreTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:276` | +| `TestDomainEvent` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorTests.cs:1107` | +| `TestIntegrationEvent` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:206` | +| `TestIntegrationEvent` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing` | `MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorTests.cs:1119` | | `EFRepositoryAdditionalTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryAdditionalTests.cs:9` | | `EFRepositoryAuditStampTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryAuditStampTests.cs:34` | | `EFRepositoryConcurrencyTouchTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryConcurrencyTouchTests.cs:17` | | `EFRepositoryDecoratorAdditionalTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryDecoratorAdditionalTests.cs:10` | | `EFRepositoryDecoratorTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryDecoratorTests.cs:9` | -| `EFRepositoryIntegrationTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryIntegrationTests.cs:13` | +| `EFRepositoryIntegrationTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryIntegrationTests.cs:14` | | `FakeAggregate` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/RepositoryFactoryTests.cs:119` | | `FakeAggregateEntity` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryDecoratorAdditionalTests.cs:49` | | `FakeAggregateEntity` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryDecoratorTests.cs:44` | | `FakeEntity` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/RepositoryFactoryTests.cs:124` | +| `KeysetQueryBuilderNullOrderingTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/KeysetQueryBuilderNullOrderingTests.cs:22` | | `KeysetQueryBuilderSqlTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/KeysetQueryBuilderSqlTests.cs:24` | -| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryAuditStampTests.cs:199` | +| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryAuditStampTests.cs:197` | | `RepositoryFactoryTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/RepositoryFactoryTests.cs:14` | | `StampedEntity` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryAuditStampTests.cs:155` | | `StampTestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryAuditStampTests.cs:160` | -| `TestChildEntity` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryIntegrationTests.cs:570` | +| `TestChildEntity` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryIntegrationTests.cs:588` | | `TestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryAdditionalTests.cs:229` | -| `TestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryIntegrationTests.cs:575` | +| `TestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryIntegrationTests.cs:593` | | `TestDbContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/RepositoryFactoryTests.cs:129` | | `TestEntity` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryAdditionalTests.cs:221` | -| `TestEntity` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryIntegrationTests.cs:559` | +| `TestEntity` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryIntegrationTests.cs:577` | | `AllSpecification` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories.Read` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/Read/EFReadRepositorySpecificationTests.cs:209` | | `BbbSpecification` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories.Read` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/Read/EFReadRepositoryKeysetPagingTests.cs:267` | | `BetaSpecification` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Repositories.Read` | `MMCA.Common.Infrastructure.Tests/Persistence/Repositories/Read/EFReadRepositorySpecificationTests.cs:214` | @@ -4421,7 +4554,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `BetaSpecification` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Specifications` | `MMCA.Common.Infrastructure.Tests/Persistence/Specifications/SpecificationEvaluatorTests.cs:263` | | `EFQueryableExecutorTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Specifications` | `MMCA.Common.Infrastructure.Tests/Persistence/Specifications/EFQueryableExecutorTests.cs:11` | | `IncludingSpecification` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Specifications` | `MMCA.Common.Infrastructure.Tests/Persistence/Specifications/SpecificationEvaluatorTests.cs:291` | -| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Specifications` | `MMCA.Common.Infrastructure.Tests/Persistence/Specifications/QueryParameterizationTests.cs:149` | +| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Specifications` | `MMCA.Common.Infrastructure.Tests/Persistence/Specifications/QueryParameterizationTests.cs:147` | | `OrderedSpecification` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Specifications` | `MMCA.Common.Infrastructure.Tests/Persistence/Specifications/SpecificationEvaluatorTests.cs:268` | | `PagedSpecification` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Specifications` | `MMCA.Common.Infrastructure.Tests/Persistence/Specifications/SpecificationEvaluatorTests.cs:298` | | `Product` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Specifications` | `MMCA.Common.Infrastructure.Tests/Persistence/Specifications/QueryParameterizationTests.cs:99` | @@ -4437,8 +4570,8 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `AddMultiTenancyTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Tenancy` | `MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/AddMultiTenancyTests.cs:19` | | `ApplicationDbContextTenantFilterTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Tenancy` | `MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/ApplicationDbContextTenantFilterTests.cs:15` | | `DbContextFactoryTenantTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Tenancy` | `MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/DbContextFactoryTenantTests.cs:22` | -| `MutableTenantContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Tenancy` | `MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/DbContextFactoryTenantTests.cs:247` | -| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Tenancy` | `MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/TenantTestHarness.cs:200` | +| `MutableTenantContext` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Tenancy` | `MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/DbContextFactoryTenantTests.cs:264` | +| `NullAssemblyProvider` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Tenancy` | `MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/TenantTestHarness.cs:198` | | `PlainThing` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Tenancy` | `MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/TenantTestHarness.cs:42` | | `TenantDataSourceTargetTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Tenancy` | `MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/TenantDataSourceTargetTests.cs:20` | | `TenantDetail` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Persistence.Tenancy` | `MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/TenantTestHarness.cs:33` | @@ -4476,12 +4609,29 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `PushNotificationSettingsTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Settings` | `MMCA.Common.Infrastructure.Tests/Settings/SettingsTests.cs:332` | | `SmtpSettingsTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Settings` | `MMCA.Common.Infrastructure.Tests/Settings/SettingsTests.cs:94` | | `SqlClientEntraAuthenticationTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.SqlClient` | `MMCA.Common.Infrastructure.Tests/SqlClient/SqlClientEntraAuthenticationTests.cs:14` | +| `CountingAllocator` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Storage` | `MMCA.Common.Infrastructure.Tests/Storage/ImageSharpImageProcessorFrameBoundTests.cs:71` | | `FileUploadOptionsOverloadTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Storage` | `MMCA.Common.Infrastructure.Tests/Storage/FileUploadOptionsOverloadTests.cs:13` | +| `ImageFrameBoundCollection` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Storage` | `MMCA.Common.Infrastructure.Tests/Storage/ImageSharpImageProcessorFrameBoundTests.cs:91` | +| `ImageSharpImageProcessorFrameBoundTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Storage` | `MMCA.Common.Infrastructure.Tests/Storage/ImageSharpImageProcessorFrameBoundTests.cs:25` | | `ImageSharpImageProcessorTests` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.Storage` | `MMCA.Common.Infrastructure.Tests/Storage/ImageSharpImageProcessorTests.cs:15` | | `DefaultDataSourceResolver` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.TestDoubles` | `MMCA.Common.Infrastructure.Tests/TestDoubles/TestDataSourceDoubles.cs:34` | | `EmptyEntityDataSourceRegistry` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.TestDoubles` | `MMCA.Common.Infrastructure.Tests/TestDoubles/TestDataSourceDoubles.cs:11` | | `TestPhysicalDataSources` | class | MMCA.Common.Infrastructure.Tests | `MMCA.Common.Infrastructure.Tests.TestDoubles` | `MMCA.Common.Infrastructure.Tests/TestDoubles/TestDataSourceDoubles.cs:47` | | `CreateMigrationProofTable` | class | MMCA.Common.Infrastructure.Tests.MigrationsFixture | `MMCA.Common.Infrastructure.Tests.MigrationsFixture` | `MMCA.Common.Infrastructure.Tests.MigrationsFixture/CreateMigrationProofTable.cs:24` | +| `Measured` | record | MMCA.Common.LoadTests | `MMCA.Common.LoadTests` | `MMCA.Common.LoadTests/PagedQueryLoadTests.cs:201` | +| `OutboxThroughputLoadTests` | class | MMCA.Common.LoadTests | `MMCA.Common.LoadTests` | `MMCA.Common.LoadTests/OutboxThroughputLoadTests.cs:20` | +| `PagedQueryLoadTests` | class | MMCA.Common.LoadTests | `MMCA.Common.LoadTests` | `MMCA.Common.LoadTests/PagedQueryLoadTests.cs:13` | +| `CountingMessageBus` | class | MMCA.Common.LoadTests | `MMCA.Common.LoadTests.Support` | `MMCA.Common.LoadTests/Support/CountingMessageBus.cs:20` | +| `LoadIntegrationEvent` | record | MMCA.Common.LoadTests | `MMCA.Common.LoadTests.Support` | `MMCA.Common.LoadTests/Support/CountingMessageBus.cs:11` | +| `LoadItem` | class | MMCA.Common.LoadTests | `MMCA.Common.LoadTests.Support` | `MMCA.Common.LoadTests/Support/LoadItems.cs:16` | +| `LoadItemConfiguration` | class | MMCA.Common.LoadTests | `MMCA.Common.LoadTests.Support` | `MMCA.Common.LoadTests/Support/LoadItems.cs:52` | +| `LoadItemDTO` | record | MMCA.Common.LoadTests | `MMCA.Common.LoadTests.Support` | `MMCA.Common.LoadTests/Support/LoadItems.cs:40` | +| `LoadItemMapper` | class | MMCA.Common.LoadTests | `MMCA.Common.LoadTests.Support` | `MMCA.Common.LoadTests/Support/LoadItems.cs:55` | +| `LoadItemNavigationPopulator` | class | MMCA.Common.LoadTests | `MMCA.Common.LoadTests.Support` | `MMCA.Common.LoadTests/Support/LoadItems.cs:67` | +| `LoadResults` | class | MMCA.Common.LoadTests | `MMCA.Common.LoadTests.Support` | `MMCA.Common.LoadTests/Support/LoadResults.cs:11` | +| `LoadStack` | class | MMCA.Common.LoadTests | `MMCA.Common.LoadTests.Support` | `MMCA.Common.LoadTests/Support/LoadStack.cs:25` | +| `PagedQuery` | record | MMCA.Common.LoadTests | `MMCA.Common.LoadTests.Support` | `MMCA.Common.LoadTests/Support/PagedQueryFixture.cs:103` | +| `PagedQueryFixture` | class | MMCA.Common.LoadTests | `MMCA.Common.LoadTests.Support` | `MMCA.Common.LoadTests/Support/PagedQueryFixture.cs:16` | | `AuthenticationRequest` | record struct | MMCA.Common.Shared | `MMCA.Common.Shared` | `MMCA.Common.Shared/AuthenticationRequest.cs:15` | | `CollectionResult` | record | MMCA.Common.Shared | `MMCA.Common.Shared.Abstractions` | `MMCA.Common.Shared/Abstractions/PaginationMetadata.cs:92` | | `Error` | record | MMCA.Common.Shared | `MMCA.Common.Shared.Abstractions` | `MMCA.Common.Shared/Abstractions/Error.cs:15` | @@ -4499,6 +4649,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `AuthClaimTypes` | class | MMCA.Common.Shared | `MMCA.Common.Shared.Auth` | `MMCA.Common.Shared/Auth/AuthClaimTypes.cs:9` | | `AuthErrorCodes` | class | MMCA.Common.Shared | `MMCA.Common.Shared.Auth` | `MMCA.Common.Shared/Auth/AuthErrorCodes.cs:12` | | `ClaimsPrincipalExtensions` | class | MMCA.Common.Shared | `MMCA.Common.Shared.Auth` | `MMCA.Common.Shared/Auth/ClaimsPrincipalExtensions.cs:18` | +| `PasswordComplexity` | class | MMCA.Common.Shared | `MMCA.Common.Shared.Auth` | `MMCA.Common.Shared/Auth/PasswordComplexity.cs:18` | | `RoleValue` | class | MMCA.Common.Shared | `MMCA.Common.Shared.Auth` | `MMCA.Common.Shared/Auth/RoleValue.cs:26` | | `IUserAdminDTO` | interface | MMCA.Common.Shared | `MMCA.Common.Shared.Auth.Administration` | `MMCA.Common.Shared/Auth/Administration/IUserAdminDTO.cs:15` | | `AdministrationPermissions` | class | MMCA.Common.Shared | `MMCA.Common.Shared.Auth.Permissions` | `MMCA.Common.Shared/Auth/Permissions/AdministrationPermissions.cs:15` | @@ -4573,9 +4724,9 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ResultConverter` | class | MMCA.Common.Shared | `MMCA.Common.Shared.Serialization` | `MMCA.Common.Shared/Serialization/ResultJsonConverterFactory.cs:35` | | `ResultConverter` | class | MMCA.Common.Shared | `MMCA.Common.Shared.Serialization` | `MMCA.Common.Shared/Serialization/ResultJsonConverterFactory.cs:63` | | `ResultJsonConverterFactory` | class | MMCA.Common.Shared | `MMCA.Common.Shared.Serialization` | `MMCA.Common.Shared/Serialization/ResultJsonConverterFactory.cs:15` | -| `Enumeration` | class | MMCA.Common.Shared | `MMCA.Common.Shared.ValueObjects` | `MMCA.Common.Shared/ValueObjects/Enumeration.cs:76` | -| `EnumerationConverter` | class | MMCA.Common.Shared | `MMCA.Common.Shared.ValueObjects` | `MMCA.Common.Shared/ValueObjects/Enumeration.cs:229` | -| `EnumerationJsonConverterFactory` | class | MMCA.Common.Shared | `MMCA.Common.Shared.ValueObjects` | `MMCA.Common.Shared/ValueObjects/Enumeration.cs:200` | +| `Enumeration` | class | MMCA.Common.Shared | `MMCA.Common.Shared.ValueObjects` | `MMCA.Common.Shared/ValueObjects/Enumeration.cs:77` | +| `EnumerationConverter` | class | MMCA.Common.Shared | `MMCA.Common.Shared.ValueObjects` | `MMCA.Common.Shared/ValueObjects/Enumeration.cs:230` | +| `EnumerationJsonConverterFactory` | class | MMCA.Common.Shared | `MMCA.Common.Shared.ValueObjects` | `MMCA.Common.Shared/ValueObjects/Enumeration.cs:201` | | `ValueObject` | record | MMCA.Common.Shared | `MMCA.Common.Shared.ValueObjects` | `MMCA.Common.Shared/ValueObjects/ValueObject.cs:8` | | `Address` | record | MMCA.Common.Shared | `MMCA.Common.Shared.ValueObjects.Contact` | `MMCA.Common.Shared/ValueObjects/Contact/Address.cs:16` | | `AddressInvariants` | class | MMCA.Common.Shared | `MMCA.Common.Shared.ValueObjects.Contact` | `MMCA.Common.Shared/ValueObjects/Contact/AddressInvariants.cs:9` | @@ -4596,6 +4747,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ResultExtensionsTests` | class | MMCA.Common.Shared.Tests | `MMCA.Common.Shared.Tests.Abstractions` | `MMCA.Common.Shared.Tests/Abstractions/ResultExtensionsTests.cs:10` | | `ResultTests` | class | MMCA.Common.Shared.Tests | `MMCA.Common.Shared.Tests.Abstractions` | `MMCA.Common.Shared.Tests/Abstractions/ResultTests.cs:6` | | `ClaimsPrincipalExtensionsTests` | class | MMCA.Common.Shared.Tests | `MMCA.Common.Shared.Tests.Auth` | `MMCA.Common.Shared.Tests/Auth/ClaimsPrincipalExtensionsTests.cs:12` | +| `PasswordComplexityTests` | class | MMCA.Common.Shared.Tests | `MMCA.Common.Shared.Tests.Auth` | `MMCA.Common.Shared.Tests/Auth/PasswordComplexityTests.cs:12` | | `RoleValueTests` | class | MMCA.Common.Shared.Tests | `MMCA.Common.Shared.Tests.Auth` | `MMCA.Common.Shared.Tests/Auth/RoleValueTests.cs:13` | | `PermissionCatalogTests` | class | MMCA.Common.Shared.Tests | `MMCA.Common.Shared.Tests.Auth.Permissions` | `MMCA.Common.Shared.Tests/Auth/Permissions/PermissionCatalogTests.cs:12` | | `PermissionRegistryTests` | class | MMCA.Common.Shared.Tests | `MMCA.Common.Shared.Tests.Auth.Permissions` | `MMCA.Common.Shared.Tests/Auth/Permissions/PermissionRegistryTests.cs:6` | @@ -4616,6 +4768,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `Payload` | record | MMCA.Common.Shared.Tests | `MMCA.Common.Shared.Tests.Http` | `MMCA.Common.Shared.Tests/Http/ProblemDetailsResultReaderTests.cs:368` | | `ProblemDetailsResultReaderTests` | class | MMCA.Common.Shared.Tests | `MMCA.Common.Shared.Tests.Http` | `MMCA.Common.Shared.Tests/Http/ProblemDetailsResultReaderTests.cs:15` | | `CustomerId` | record struct | MMCA.Common.Shared.Tests | `MMCA.Common.Shared.Tests.Identifiers` | `MMCA.Common.Shared.Tests/Identifiers/TestIdentifiers.cs:17` | +| `LateRegisteredId` | record struct | MMCA.Common.Shared.Tests | `MMCA.Common.Shared.Tests.Identifiers` | `MMCA.Common.Shared.Tests/Identifiers/StronglyTypedIdTypeConverterTests.cs:98` | | `LineId` | record struct | MMCA.Common.Shared.Tests | `MMCA.Common.Shared.Tests.Identifiers` | `MMCA.Common.Shared.Tests/Identifiers/TestIdentifiers.cs:24` | | `OrderDto` | record | MMCA.Common.Shared.Tests | `MMCA.Common.Shared.Tests.Identifiers` | `MMCA.Common.Shared.Tests/Identifiers/StronglyTypedIdSerializationTests.cs:20` | | `OrderId` | record struct | MMCA.Common.Shared.Tests | `MMCA.Common.Shared.Tests.Identifiers` | `MMCA.Common.Shared.Tests/Identifiers/TestIdentifiers.cs:10` | @@ -4681,7 +4834,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ArchitectureRules` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.ErrorCatalog.cs:6` | | `ArchitectureRules` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Events.cs:3` | | `ArchitectureRules` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.IntegrationEventPurity.cs:3` | -| `ArchitectureRules` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:5` | +| `ArchitectureRules` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:6` | | `ArchitectureRules` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Upcasters.cs:5` | | `ArchitectureRules` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Rules/Cqrs/ArchitectureRules.CancellationTokens.cs:5` | | `ArchitectureRules` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Rules/Cqrs/ArchitectureRules.CommandValidators.cs:3` | @@ -4720,7 +4873,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ClockReadTestsBase` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Bases/Domain/ClockReadTestsBase.cs:15` | | `CommandValidatorCoverageTestsBase` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Bases/Cqrs/CommandValidatorCoverageTestsBase.cs:22` | | `ConcurrencyConventionTestsBase` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Bases/Domain/ConcurrencyConventionTestsBase.cs:9` | -| `ConstructorDependencyCountTestsBase` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Bases/Cqrs/ConstructorDependencyCountTestsBase.cs:20` | +| `ConstructorDependencyCountTestsBase` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Bases/Cqrs/ConstructorDependencyCountTestsBase.cs:26` | | `ContractImplementationTestsBase` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Bases/Contracts/ContractImplementationTestsBase.cs:20` | | `ControllerConventionTestsBase` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Bases/Api/ControllerConventionTestsBase.cs:7` | | `CrossEntityNavigationFinder` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.Specifications.cs:97` | @@ -4759,8 +4912,8 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ObservabilityConventionTestsBase` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Bases/Governance/ObservabilityConventionTestsBase.cs:30` | | `PiiConventionTestsBase` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Bases/Governance/PiiConventionTestsBase.cs:7` | | `ProtoContractTestsBase` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Bases/Contracts/ProtoContractTestsBase.cs:19` | -| `ProtoScope` | record | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:297` | -| `ProtoScopeKind` | enum | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:286` | +| `ProtoScope` | record | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:310` | +| `ProtoScopeKind` | enum | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:299` | | `QueryHandlerReadRepositoryTestsBase` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Bases/Cqrs/QueryHandlerReadRepositoryTestsBase.cs:15` | | `RawQueryableConventionTestsBase` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Bases/Cqrs/RawQueryableConventionTestsBase.cs:30` | | `RawSqlConventionTestsBase` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Bases/Cqrs/RawSqlConventionTestsBase.cs:31` | @@ -4772,10 +4925,10 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `SoftDeleteEnforcementTestsBase` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Bases/Domain/SoftDeleteEnforcementTestsBase.cs:17` | | `SortableColumnConventionTestsBase` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Bases/Ui/SortableColumnConventionTestsBase.cs:16` | | `SpecificationConventionTestsBase` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Bases/Domain/SpecificationConventionTestsBase.cs:10` | -| `StateManagementConventionTestsBase` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Bases/Ui/StateManagementConventionTestsBase.cs:17` | +| `StateManagementConventionTestsBase` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Bases/Ui/StateManagementConventionTestsBase.cs:18` | | `StronglyTypedIdTestsBase` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Bases/Domain/StronglyTypedIdTestsBase.cs:9` | | `UIArchitectureConventionTestsBase` | class | MMCA.Common.Testing.Architecture | `MMCA.Common.Testing.Architecture` | `MMCA.Common.Testing.Architecture/Bases/Ui/UIArchitectureConventionTestsBase.cs:14` | -| `AppHostFixtureBase` | class | MMCA.Common.Testing.Aspire | `MMCA.Common.Testing.Aspire.Fixtures` | `MMCA.Common.Testing.Aspire/Fixtures/AppHostFixtureBase.cs:38` | +| `AppHostFixtureBase` | class | MMCA.Common.Testing.Aspire | `MMCA.Common.Testing.Aspire.Fixtures` | `MMCA.Common.Testing.Aspire/Fixtures/AppHostFixtureBase.cs:41` | | `AppHostFixtureBase` | class | MMCA.Common.Testing.Aspire | `MMCA.Common.Testing.Aspire.Fixtures` | `MMCA.Common.Testing.Aspire/Fixtures/AppHostFixtureBase.Generic.cs:20` | | `AppHostReadinessBudget` | record | MMCA.Common.Testing.Aspire | `MMCA.Common.Testing.Aspire.Fixtures` | `MMCA.Common.Testing.Aspire/Fixtures/AppHostReadinessBudget.cs:16` | | `AppHostTestBase` | class | MMCA.Common.Testing.Aspire | `MMCA.Common.Testing.Aspire.Fixtures` | `MMCA.Common.Testing.Aspire/Fixtures/AppHostTestBase.cs:29` | @@ -4836,10 +4989,10 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `PingCommandHandler` | class | MMCA.Common.Testing.Tests | `MMCA.Common.Testing.Tests.Conformance` | `MMCA.Common.Testing.Tests/Conformance/DecoratorPipelineOrderTests.cs:47` | | `PingQuery` | record | MMCA.Common.Testing.Tests | `MMCA.Common.Testing.Tests.Conformance` | `MMCA.Common.Testing.Tests/Conformance/DecoratorPipelineOrderTests.cs:45` | | `PingQueryHandler` | class | MMCA.Common.Testing.Tests | `MMCA.Common.Testing.Tests.Conformance` | `MMCA.Common.Testing.Tests/Conformance/DecoratorPipelineOrderTests.cs:53` | -| `SampleGatewayEntryPoint` | class | MMCA.Common.Testing.Tests | `MMCA.Common.Testing.Tests.Conformance` | `MMCA.Common.Testing.Tests/Conformance/MmcaGatewayHardeningTestsBaseTests.cs:97` | +| `SampleGatewayEntryPoint` | class | MMCA.Common.Testing.Tests | `MMCA.Common.Testing.Tests.Conformance` | `MMCA.Common.Testing.Tests/Conformance/MmcaGatewayHardeningTestsBaseTests.cs:99` | | `SampleGatewayHardeningTests` | class | MMCA.Common.Testing.Tests | `MMCA.Common.Testing.Tests.Conformance` | `MMCA.Common.Testing.Tests/Conformance/MmcaGatewayHardeningTestsBaseTests.cs:75` | | `CrossServiceFixtureBaseTests` | class | MMCA.Common.Testing.Tests | `MMCA.Common.Testing.Tests.Fixtures` | `MMCA.Common.Testing.Tests/Fixtures/CrossServiceFixtureBaseTests.cs:14` | -| `FakeCrossServiceFixture` | class | MMCA.Common.Testing.Tests | `MMCA.Common.Testing.Tests.Fixtures` | `MMCA.Common.Testing.Tests/Fixtures/CrossServiceFixtureBaseTests.cs:107` | +| `FakeCrossServiceFixture` | class | MMCA.Common.Testing.Tests | `MMCA.Common.Testing.Tests.Fixtures` | `MMCA.Common.Testing.Tests/Fixtures/CrossServiceFixtureBaseTests.cs:119` | | `OverridingFixture` | class | MMCA.Common.Testing.Tests | `MMCA.Common.Testing.Tests.Fixtures` | `MMCA.Common.Testing.Tests/Fixtures/ServiceBusEmulatorFixtureBaseTests.cs:120` | | `ProbeFixture` | class | MMCA.Common.Testing.Tests | `MMCA.Common.Testing.Tests.Fixtures` | `MMCA.Common.Testing.Tests/Fixtures/ServiceBusEmulatorFixtureBaseTests.cs:110` | | `ServiceBusEmulatorFixtureBaseTests` | class | MMCA.Common.Testing.Tests | `MMCA.Common.Testing.Tests.Fixtures` | `MMCA.Common.Testing.Tests/Fixtures/ServiceBusEmulatorFixtureBaseTests.cs:15` | @@ -4858,17 +5011,16 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `TestPollingTests` | class | MMCA.Common.Testing.Tests | `MMCA.Common.Testing.Tests.Support` | `MMCA.Common.Testing.Tests/Support/TestPollingTests.cs:12` | | `BunitComponentTestBase` | class | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:37` | | `BunitInteractionExtensions` | class | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/BunitInteractionExtensions.cs:12` | -| `CapturedRequest` | record | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:158` | -| `CapturingHttpMessageHandler` | class | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:19` | +| `CapturedRequest` | record | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:189` | +| `CapturingHttpMessageHandler` | class | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:20` | | `ErrorSummaryExtensions` | class | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/ErrorSummaryExtensions.cs:10` | | `FreshApiClientFactory` | class | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/UiHttpServiceHarness.cs:73` | | `HttpTestDoubles` | class | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/HttpTestDoubles.cs:12` | -| `IsAuthenticatedAuthorizationService` | class | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:176` | | `MarkupSnapshot` | class | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/MarkupSnapshot.cs:21` | | `MarkupSnapshotResult` | record struct | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/MarkupSnapshot.cs:104` | -| `MudProviderHandles` | record | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:157` | -| `MutableAuthenticationStateProvider` | class | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:162` | -| `Route` | record | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:139` | +| `MudProviderHandles` | record | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:162` | +| `MutableAuthenticationStateProvider` | class | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:167` | +| `Route` | record | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:170` | | `StubTokenStorageService` | class | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/StubTokenStorageService.cs:13` | | `TestPrincipal` | class | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/TestPrincipal.cs:7` | | `UiHttpServiceHarness` | class | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI` | `MMCA.Common.Testing.UI/Infrastructure/UiHttpServiceHarness.cs:12` | @@ -4876,7 +5028,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `RoleAdminEditPageTestsBase` | class | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI.Pages` | `MMCA.Common.Testing.UI/Pages/RoleAdminEditPageTestsBase.cs:22` | | `RoleAdminListPageTestsBase` | class | MMCA.Common.Testing.UI | `MMCA.Common.Testing.UI.Pages` | `MMCA.Common.Testing.UI/Pages/RoleAdminListPageTestsBase.cs:21` | | `DependencyInjection` | class | MMCA.Common.UI | `MMCA.Common.UI` | `MMCA.Common.UI/DependencyInjection.cs:28` | -| `UISharedAssemblyReference` | class | MMCA.Common.UI | `MMCA.Common.UI` | `MMCA.Common.UI/DependencyInjection.cs:288` | +| `UISharedAssemblyReference` | class | MMCA.Common.UI | `MMCA.Common.UI` | `MMCA.Common.UI/DependencyInjection.cs:304` | | `BreakpointConstants` | class | MMCA.Common.UI | `MMCA.Common.UI.Common` | `MMCA.Common.UI/Common/BreakpointConstants.cs:9` | | `LatestLoadGuard` | class | MMCA.Common.UI | `MMCA.Common.UI.Common` | `MMCA.Common.UI/Common/LatestLoadGuard.cs:38` | | `NavItem` | record | MMCA.Common.UI | `MMCA.Common.UI.Common` | `MMCA.Common.UI/Common/NavItem.cs:20` | @@ -4893,14 +5045,14 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ApiSettings` | class | MMCA.Common.UI | `MMCA.Common.UI.Common.Settings` | `MMCA.Common.UI/Common/Settings/ApiSettings.cs:9` | | `IApiSettings` | interface | MMCA.Common.UI | `MMCA.Common.UI.Common.Settings` | `MMCA.Common.UI/Common/Settings/IApiSettings.cs:6` | | `LayoutSettings` | class | MMCA.Common.UI | `MMCA.Common.UI.Common.Settings` | `MMCA.Common.UI/Common/Settings/LayoutSettings.cs:9` | -| `MmcaClientConfigBootstrap` | class | MMCA.Common.UI | `MMCA.Common.UI.Common.Settings` | `MMCA.Common.UI/Common/Settings/MmcaClientConfigBootstrap.cs:24` | +| `MmcaClientConfigBootstrap` | class | MMCA.Common.UI | `MMCA.Common.UI.Common.Settings` | `MMCA.Common.UI/Common/Settings/MmcaClientConfigBootstrap.cs:28` | | `NotificationBellOptions` | class | MMCA.Common.UI | `MMCA.Common.UI.Common.Settings` | `MMCA.Common.UI/Common/Settings/NotificationBellOptions.cs:12` | | `UIModuleConfiguration` | class | MMCA.Common.UI | `MMCA.Common.UI.Common.Settings` | `MMCA.Common.UI/Common/Settings/UIModuleConfiguration.cs:10` | | `UiReadCacheOptions` | class | MMCA.Common.UI | `MMCA.Common.UI.Common.Settings` | `MMCA.Common.UI/Common/Settings/UiReadCacheOptions.cs:13` | -| `BiometricGate` | class | MMCA.Common.UI | `MMCA.Common.UI.Components.Capabilities` | `MMCA.Common.UI/Components/Capabilities/BiometricGate.razor.cs:17` | +| `BiometricGate` | class | MMCA.Common.UI | `MMCA.Common.UI.Components.Capabilities` | `MMCA.Common.UI/Components/Capabilities/BiometricGate.razor.cs:18` | | `ApiFileDownloadButton` | class | MMCA.Common.UI | `MMCA.Common.UI.Components.Forms` | `MMCA.Common.UI/Components/Forms/ApiFileDownloadButton.razor.cs:14` | | `InfiniteScrollSentinel` | class | MMCA.Common.UI | `MMCA.Common.UI.Components.Lists` | `MMCA.Common.UI/Components/Lists/InfiniteScrollSentinel.razor.cs:21` | -| `MobileInfiniteScrollList` | class | MMCA.Common.UI | `MMCA.Common.UI.Components.Lists` | `MMCA.Common.UI/Components/Lists/MobileInfiniteScrollList.razor.cs:21` | +| `MobileInfiniteScrollList` | class | MMCA.Common.UI | `MMCA.Common.UI.Components.Lists` | `MMCA.Common.UI/Components/Lists/MobileInfiniteScrollList.razor.cs:20` | | `NotificationBell` | class | MMCA.Common.UI | `MMCA.Common.UI.Components.Notifications` | `MMCA.Common.UI/Components/Notifications/NotificationBell.razor.cs:30` | | `RatingStars` | class | MMCA.Common.UI | `MMCA.Common.UI.Components.Ratings` | `MMCA.Common.UI/Components/Ratings/RatingStars.razor.cs:13` | | `QrErrorCorrectionLevel` | enum | MMCA.Common.UI | `MMCA.Common.UI.Components.Sharing` | `MMCA.Common.UI/Components/Sharing/QrErrorCorrectionLevel.cs:9` | @@ -4914,6 +5066,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ResxMudLocalizer` | class | MMCA.Common.UI | `MMCA.Common.UI.Globalization` | `MMCA.Common.UI/Globalization/ResxMudLocalizer.cs:17` | | `StringLocalizerPluralExtensions` | class | MMCA.Common.UI | `MMCA.Common.UI.Globalization` | `MMCA.Common.UI/Globalization/StringLocalizerPluralExtensions.cs:20` | | `DependencyInjection` | class | MMCA.Common.UI | `MMCA.Common.UI.Notifications` | `MMCA.Common.UI/Notifications/DependencyInjection.cs:12` | +| `NotificationPageGate` | class | MMCA.Common.UI | `MMCA.Common.UI.Notifications` | `MMCA.Common.UI/Notifications/NotificationPageGate.cs:12` | | `NotificationUIModule` | class | MMCA.Common.UI | `MMCA.Common.UI.Notifications` | `MMCA.Common.UI/Notifications/NotificationUIModule.cs:15` | | `PermissionGroup` | record | MMCA.Common.UI | `MMCA.Common.UI.Pages.Administration` | `MMCA.Common.UI/Pages/Administration/RoleAdminEdit.razor.cs:303` | | `RoleAdminEdit` | class | MMCA.Common.UI | `MMCA.Common.UI.Pages.Administration` | `MMCA.Common.UI/Pages/Administration/RoleAdminEdit.razor.cs:35` | @@ -4924,11 +5077,11 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `UserAdminListResources` | class | MMCA.Common.UI | `MMCA.Common.UI.Pages.Administration` | `MMCA.Common.UI/Pages/Administration/UserAdminListResources.cs:14` | | `ConfirmationState` | enum | MMCA.Common.UI | `MMCA.Common.UI.Pages.Auth` | `MMCA.Common.UI/Pages/Auth/ConfirmEmail.razor.cs:50` | | `ConfirmEmail` | class | MMCA.Common.UI | `MMCA.Common.UI.Pages.Auth` | `MMCA.Common.UI/Pages/Auth/ConfirmEmail.razor.cs:36` | -| `ForgotPasswordModel` | class | MMCA.Common.UI | `MMCA.Common.UI.Pages.Auth` | `MMCA.Common.UI/Pages/Auth/ForgotPasswordModel.cs:9` | -| `LoginModel` | class | MMCA.Common.UI | `MMCA.Common.UI.Pages.Auth` | `MMCA.Common.UI/Pages/Auth/LoginModel.cs:9` | -| `PasswordComplexityAttribute` | class | MMCA.Common.UI | `MMCA.Common.UI.Pages.Auth` | `MMCA.Common.UI/Pages/Auth/PasswordComplexityAttribute.cs:12` | -| `RegisterModel` | class | MMCA.Common.UI | `MMCA.Common.UI.Pages.Auth` | `MMCA.Common.UI/Pages/Auth/RegisterModel.cs:9` | -| `ResetPasswordModel` | class | MMCA.Common.UI | `MMCA.Common.UI.Pages.Auth` | `MMCA.Common.UI/Pages/Auth/ResetPasswordModel.cs:10` | +| `ForgotPasswordModel` | class | MMCA.Common.UI | `MMCA.Common.UI.Pages.Auth` | `MMCA.Common.UI/Pages/Auth/ForgotPasswordModel.cs:13` | +| `LoginModel` | class | MMCA.Common.UI | `MMCA.Common.UI.Pages.Auth` | `MMCA.Common.UI/Pages/Auth/LoginModel.cs:13` | +| `PasswordComplexityAttribute` | class | MMCA.Common.UI | `MMCA.Common.UI.Pages.Auth` | `MMCA.Common.UI/Pages/Auth/PasswordComplexityAttribute.cs:15` | +| `RegisterModel` | class | MMCA.Common.UI | `MMCA.Common.UI.Pages.Auth` | `MMCA.Common.UI/Pages/Auth/RegisterModel.cs:14` | +| `ResetPasswordModel` | class | MMCA.Common.UI | `MMCA.Common.UI.Pages.Auth` | `MMCA.Common.UI/Pages/Auth/ResetPasswordModel.cs:15` | | `Sessions` | class | MMCA.Common.UI | `MMCA.Common.UI.Pages.Auth` | `MMCA.Common.UI/Pages/Auth/Sessions.razor.cs:26` | | `CachedPage` | record | MMCA.Common.UI | `MMCA.Common.UI.Pages.Common` | `MMCA.Common.UI/Pages/Common/OfflineFirstPageSnapshot.cs:39` | | `DataGridListPageBase` | class | MMCA.Common.UI | `MMCA.Common.UI.Pages.Common` | `MMCA.Common.UI/Pages/Common/DataGridListPageBase.cs:22` | @@ -4960,21 +5113,25 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ChildEntityServiceBase` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Api` | `MMCA.Common.UI/Services/Api/ChildEntityServiceBase.cs:19` | | `EntityServiceBase` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Api` | `MMCA.Common.UI/Services/Api/EntityServiceBase.cs:43` | | `HttpResultExecutor` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Api` | `MMCA.Common.UI/Services/Api/HttpResultExecutor.cs:31` | -| `AuthDelegatingHandler` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth` | `MMCA.Common.UI/Services/Auth/AuthDelegatingHandler.cs:10` | +| `IdempotentReadRetry` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Api` | `MMCA.Common.UI/Services/Api/IdempotentReadRetry.cs:18` | +| `PagedReadAll` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Api` | `MMCA.Common.UI/Services/Api/PagedReadAll.cs:19` | +| `AuthDelegatingHandler` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth` | `MMCA.Common.UI/Services/Auth/AuthDelegatingHandler.cs:12` | | `AuthUIService` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth` | `MMCA.Common.UI/Services/Auth/AuthUIService.cs:44` | | `EmailConfirmationUIService` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth` | `MMCA.Common.UI/Services/Auth/EmailConfirmationUIService.cs:23` | | `IAuthUIService` | interface | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth` | `MMCA.Common.UI/Services/Auth/IAuthUIService.cs:18` | | `IEmailConfirmationUIService` | interface | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth` | `MMCA.Common.UI/Services/Auth/IEmailConfirmationUIService.cs:21` | | `ISessionCookieSync` | interface | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth` | `MMCA.Common.UI/Services/Auth/ISessionCookieSync.cs:8` | -| `JsFetchSessionCookieSync` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth` | `MMCA.Common.UI/Services/Auth/JsFetchSessionCookieSync.cs:11` | +| `JsFetchSessionCookieSync` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth` | `MMCA.Common.UI/Services/Auth/JsFetchSessionCookieSync.cs:13` | | `JwtAuthenticationStateProvider` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth` | `MMCA.Common.UI/Services/Auth/JwtAuthenticationStateProvider.cs:13` | +| `SameOriginProxyHeaders` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth` | `MMCA.Common.UI/Services/Auth/SameOriginProxyHeaders.cs:11` | +| `SameOriginProxyRequestHandler` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth` | `MMCA.Common.UI/Services/Auth/SameOriginProxyRequestHandler.cs:11` | | `UserAgentSummary` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth` | `MMCA.Common.UI/Services/Auth/UserAgentSummary.cs:18` | | `ConfigurationOAuthUISettings` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth.OAuth` | `MMCA.Common.UI/Services/Auth/OAuth/ConfigurationOAuthUISettings.cs:13` | | `DefaultOAuthUISettings` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth.OAuth` | `MMCA.Common.UI/Services/Auth/OAuth/DefaultOAuthUISettings.cs:7` | | `IOAuthUISettings` | interface | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth.OAuth` | `MMCA.Common.UI/Services/Auth/OAuth/IOAuthUISettings.cs:9` | | `OAuthFlowStateStore` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth.OAuth` | `MMCA.Common.UI/Services/Auth/OAuth/OAuthFlowStateStore.cs:20` | -| `PendingAttempt` | record | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth.OAuth` | `MMCA.Common.UI/Services/Auth/OAuth/OAuthFlowStateStore.cs:89` | -| `DirectApiTokenRefresher` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth.Tokens` | `MMCA.Common.UI/Services/Auth/Tokens/DirectApiTokenRefresher.cs:19` | +| `PendingAttempt` | record | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth.OAuth` | `MMCA.Common.UI/Services/Auth/OAuth/OAuthFlowStateStore.cs:92` | +| `DirectApiTokenRefresher` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth.Tokens` | `MMCA.Common.UI/Services/Auth/Tokens/DirectApiTokenRefresher.cs:25` | | `ISecureTokenStore` | interface | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth.Tokens` | `MMCA.Common.UI/Services/Auth/Tokens/ISecureTokenStore.cs:16` | | `ITokenRefresher` | interface | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth.Tokens` | `MMCA.Common.UI/Services/Auth/Tokens/ITokenRefresher.cs:13` | | `ITokenStorageService` | interface | MMCA.Common.UI | `MMCA.Common.UI.Services.Auth.Tokens` | `MMCA.Common.UI/Services/Auth/Tokens/ITokenStorageService.cs:8` | @@ -5038,7 +5195,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `NullSpeechToTextService` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Capabilities.Media` | `MMCA.Common.UI/Services/Capabilities/Media/NullSpeechToTextService.cs:6` | | `NullTextToSpeechService` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Capabilities.Media` | `MMCA.Common.UI/Services/Capabilities/Media/NullTextToSpeechService.cs:4` | | `PickedMedia` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Capabilities.Media` | `MMCA.Common.UI/Services/Capabilities/Media/IMediaPickerService.cs:29` | -| `DeepLinkDispatcher` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Capabilities.Navigation` | `MMCA.Common.UI/Services/Capabilities/Navigation/DeepLinkDispatcher.cs:9` | +| `DeepLinkDispatcher` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Capabilities.Navigation` | `MMCA.Common.UI/Services/Capabilities/Navigation/DeepLinkDispatcher.cs:11` | | `DeepLinkRouteEventArgs` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Capabilities.Navigation` | `MMCA.Common.UI/Services/Capabilities/Navigation/DeepLinkRouteEventArgs.cs:4` | | `IDeepLinkDispatcher` | interface | MMCA.Common.UI | `MMCA.Common.UI.Services.Capabilities.Navigation` | `MMCA.Common.UI/Services/Capabilities/Navigation/IDeepLinkDispatcher.cs:10` | | `ILocalNotificationService` | interface | MMCA.Common.UI | `MMCA.Common.UI.Services.Capabilities.Notifications` | `MMCA.Common.UI/Services/Capabilities/Notifications/ILocalNotificationService.cs:10` | @@ -5060,11 +5217,11 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `NavigationPublicLinkBuilder` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Navigation` | `MMCA.Common.UI/Services/Navigation/NavigationPublicLinkBuilder.cs:11` | | `ReturnUrlProtector` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Navigation` | `MMCA.Common.UI/Services/Navigation/ReturnUrlProtector.cs:9` | | `ChannelReferenceCounter` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Notifications` | `MMCA.Common.UI/Services/Notifications/ChannelReferenceCounter.cs:16` | -| `ChannelSubscription` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Notifications` | `MMCA.Common.UI/Services/Notifications/NotificationHubService.cs:412` | +| `ChannelSubscription` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Notifications` | `MMCA.Common.UI/Services/Notifications/NotificationHubService.cs:490` | | `INotificationInboxUIService` | interface | MMCA.Common.UI | `MMCA.Common.UI.Services.Notifications` | `MMCA.Common.UI/Services/Notifications/INotificationInboxUIService.cs:11` | | `INotificationScopeProvider` | interface | MMCA.Common.UI | `MMCA.Common.UI.Services.Notifications` | `MMCA.Common.UI/Services/Notifications/INotificationScopeProvider.cs:15` | | `IPushNotificationUIService` | interface | MMCA.Common.UI | `MMCA.Common.UI.Services.Notifications` | `MMCA.Common.UI/Services/Notifications/IPushNotificationUIService.cs:10` | -| `NotificationHubService` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Notifications` | `MMCA.Common.UI/Services/Notifications/NotificationHubService.cs:26` | +| `NotificationHubService` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Notifications` | `MMCA.Common.UI/Services/Notifications/NotificationHubService.cs:34` | | `NotificationInboxService` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Notifications` | `MMCA.Common.UI/Services/Notifications/NotificationInboxService.cs:29` | | `NotificationState` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Notifications` | `MMCA.Common.UI/Services/Notifications/NotificationState.cs:18` | | `NullNotificationScopeProvider` | class | MMCA.Common.UI | `MMCA.Common.UI.Services.Notifications` | `MMCA.Common.UI/Services/Notifications/NullNotificationScopeProvider.cs:8` | @@ -5081,6 +5238,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `AbsoluteUrlAttribute` | class | MMCA.Common.UI | `MMCA.Common.UI.Validation` | `MMCA.Common.UI/Validation/AbsoluteUrlAttribute.cs:26` | | `DataAnnotationsModelValidator` | class | MMCA.Common.UI | `MMCA.Common.UI.Validation` | `MMCA.Common.UI/Validation/DataAnnotationsModelValidator.cs:21` | | `IModelValidator` | interface | MMCA.Common.UI | `MMCA.Common.UI.Validation` | `MMCA.Common.UI/Validation/IModelValidator.cs:13` | +| `LocalizedDataAnnotationsValidator` | class | MMCA.Common.UI | `MMCA.Common.UI.Validation` | `MMCA.Common.UI/Validation/LocalizedDataAnnotationsValidator.cs:21` | | `ModelValidation` | class | MMCA.Common.UI | `MMCA.Common.UI.Validation` | `MMCA.Common.UI/Validation/ModelValidation.cs:26` | | `NotificationPagesE2ETests` | class | MMCA.Common.UI.E2E.Tests | `MMCA.Common.UI.E2E.Tests` | `MMCA.Common.UI.E2E.Tests/NotificationPagesE2ETests.cs:13` | | `PseudoLocalizationE2ETests` | class | MMCA.Common.UI.E2E.Tests | `MMCA.Common.UI.E2E.Tests` | `MMCA.Common.UI.E2E.Tests/PseudoLocalizationE2ETests.cs:24` | @@ -5100,8 +5258,9 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `MobileTopRowE2ETests` | class | MMCA.Common.UI.E2E.Tests | `MMCA.Common.UI.E2E.Tests.Layout` | `MMCA.Common.UI.E2E.Tests/Layout/MobileTopRowE2ETests.cs:18` | | `ShellPagesE2ETests` | class | MMCA.Common.UI.E2E.Tests | `MMCA.Common.UI.E2E.Tests.Layout` | `MMCA.Common.UI.E2E.Tests/Layout/ShellPagesE2ETests.cs:16` | | `StickySidebarE2ETests` | class | MMCA.Common.UI.E2E.Tests | `MMCA.Common.UI.E2E.Tests.Layout` | `MMCA.Common.UI.E2E.Tests/Layout/StickySidebarE2ETests.cs:23` | +| `InpProbe` | record | MMCA.Common.UI.E2E.Tests | `MMCA.Common.UI.E2E.Tests.WebVitals` | `MMCA.Common.UI.E2E.Tests/WebVitals/WebVitalsE2ETests.cs:146` | | `WebVitalsBudgetTests` | class | MMCA.Common.UI.E2E.Tests | `MMCA.Common.UI.E2E.Tests.WebVitals` | `MMCA.Common.UI.E2E.Tests/WebVitals/WebVitalsBudgetTests.cs:12` | -| `WebVitalsE2ETests` | class | MMCA.Common.UI.E2E.Tests | `MMCA.Common.UI.E2E.Tests.WebVitals` | `MMCA.Common.UI.E2E.Tests/WebVitals/WebVitalsE2ETests.cs:15` | +| `WebVitalsE2ETests` | class | MMCA.Common.UI.E2E.Tests | `MMCA.Common.UI.E2E.Tests.WebVitals` | `MMCA.Common.UI.E2E.Tests/WebVitals/WebVitalsE2ETests.cs:17` | | `GalleryHost` | class | MMCA.Common.UI.Gallery | `MMCA.Common.UI.Gallery` | `MMCA.Common.UI.Gallery/GalleryHost.cs:22` | | `SampleGridData` | class | MMCA.Common.UI.Gallery | `MMCA.Common.UI.Gallery.Pages` | `MMCA.Common.UI.Gallery/Pages/SampleGridRow.cs:16` | | `SampleGridRow` | record | MMCA.Common.UI.Gallery | `MMCA.Common.UI.Gallery.Pages` | `MMCA.Common.UI.Gallery/Pages/SampleGridRow.cs:9` | @@ -5139,7 +5298,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `MauiBarcodeScannerService` | class | MMCA.Common.UI.Maui | `MMCA.Common.UI.Maui.Capabilities.Media` | `MMCA.Common.UI.Maui/Capabilities/Media/MauiBarcodeScannerService.cs:24` | | `MauiMediaPickerService` | class | MMCA.Common.UI.Maui | `MMCA.Common.UI.Maui.Capabilities.Media` | `MMCA.Common.UI.Maui/Capabilities/Media/MauiMediaPickerService.cs:11` | | `MauiSpeechToTextService` | class | MMCA.Common.UI.Maui | `MMCA.Common.UI.Maui.Capabilities.Media` | `MMCA.Common.UI.Maui/Capabilities/Media/MauiSpeechToTextService.cs:14` | -| `MauiTextToSpeechService` | class | MMCA.Common.UI.Maui | `MMCA.Common.UI.Maui.Capabilities.Media` | `MMCA.Common.UI.Maui/Capabilities/Media/MauiTextToSpeechService.cs:12` | +| `MauiTextToSpeechService` | class | MMCA.Common.UI.Maui | `MMCA.Common.UI.Maui.Capabilities.Media` | `MMCA.Common.UI.Maui/Capabilities/Media/MauiTextToSpeechService.cs:14` | | `MauiLocalNotificationService` | class | MMCA.Common.UI.Maui | `MMCA.Common.UI.Maui.Capabilities.Notifications` | `MMCA.Common.UI.Maui/Capabilities/Notifications/MauiLocalNotificationService.cs:14` | | `MauiPushRegistrationService` | class | MMCA.Common.UI.Maui | `MMCA.Common.UI.Maui.Capabilities.Notifications` | `MMCA.Common.UI.Maui/Capabilities/Notifications/MauiPushRegistrationService.cs:16` | | `MauiCultureApplier` | class | MMCA.Common.UI.Maui | `MMCA.Common.UI.Maui.Globalization` | `MMCA.Common.UI.Maui/Globalization/MauiCultureApplier.cs:22` | @@ -5147,7 +5306,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `MauiCultureStore` | class | MMCA.Common.UI.Maui | `MMCA.Common.UI.Maui.Globalization` | `MMCA.Common.UI.Maui/Globalization/MauiCultureStore.cs:19` | | `MauiPublicLinkBuilder` | class | MMCA.Common.UI.Maui | `MMCA.Common.UI.Maui.Services` | `MMCA.Common.UI.Maui/Services/MauiPublicLinkBuilder.cs:14` | | `MauiSecureTokenStore` | class | MMCA.Common.UI.Maui | `MMCA.Common.UI.Maui.Services` | `MMCA.Common.UI.Maui/Services/MauiSecureTokenStore.cs:22` | -| `MauiTokenStorageService` | class | MMCA.Common.UI.Maui | `MMCA.Common.UI.Maui.Services` | `MMCA.Common.UI.Maui/Services/MauiTokenStorageService.cs:19` | +| `MauiTokenStorageService` | class | MMCA.Common.UI.Maui | `MMCA.Common.UI.Maui.Services` | `MMCA.Common.UI.Maui/Services/MauiTokenStorageService.cs:25` | | `BunitTestBase` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests` | `MMCA.Common.UI.Tests/BunitTestBase.cs:17` | | `LatestLoadGuardTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Common` | `MMCA.Common.UI.Tests/Common/LatestLoadGuardTests.cs:11` | | `ResultUiExtensionsTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Common` | `MMCA.Common.UI.Tests/Common/ResultUiExtensionsTests.cs:17` | @@ -5157,12 +5316,12 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `PrimitivesSnapshotTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components` | `MMCA.Common.UI.Tests/Components/PrimitivesSnapshotTests.cs:14` | | `PrimitivesTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components` | `MMCA.Common.UI.Tests/Components/PrimitivesTests.cs:6` | | `RedirectToLoginTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Auth` | `MMCA.Common.UI.Tests/Components/Auth/RedirectToLoginTests.cs:12` | -| `BiometricGateTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Capabilities` | `MMCA.Common.UI.Tests/Components/Capabilities/BiometricGateTests.cs:22` | +| `BiometricGateTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Capabilities` | `MMCA.Common.UI.Tests/Components/Capabilities/BiometricGateTests.cs:23` | | `DeepLinkListenerTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Capabilities` | `MMCA.Common.UI.Tests/Components/Capabilities/DeepLinkListenerTests.cs:14` | | `ExternalLinkTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Capabilities` | `MMCA.Common.UI.Tests/Components/Capabilities/ExternalLinkTests.cs:14` | -| `FakeBiometricAuthenticator` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Capabilities` | `MMCA.Common.UI.Tests/Components/Capabilities/BiometricGateTests.cs:172` | +| `FakeBiometricAuthenticator` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Capabilities` | `MMCA.Common.UI.Tests/Components/Capabilities/BiometricGateTests.cs:204` | | `FakeConnectivityService` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Capabilities` | `MMCA.Common.UI.Tests/Components/Capabilities/OfflineBannerTests.cs:55` | -| `FakeDevicePreferences` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Capabilities` | `MMCA.Common.UI.Tests/Components/Capabilities/BiometricGateTests.cs:188` | +| `FakeDevicePreferences` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Capabilities` | `MMCA.Common.UI.Tests/Components/Capabilities/BiometricGateTests.cs:220` | | `FakeExternalLinkService` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Capabilities` | `MMCA.Common.UI.Tests/Components/Capabilities/ExternalLinkTests.cs:62` | | `OfflineBannerTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Capabilities` | `MMCA.Common.UI.Tests/Components/Capabilities/OfflineBannerTests.cs:14` | | `ApiFileDownloadButtonTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Forms` | `MMCA.Common.UI.Tests/Components/Forms/ApiFileDownloadButtonTests.cs:23` | @@ -5174,11 +5333,11 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ClickableCardTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Lists` | `MMCA.Common.UI.Tests/Components/Lists/ClickableCardTests.cs:15` | | `InfiniteScrollSentinelTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Lists` | `MMCA.Common.UI.Tests/Components/Lists/InfiniteScrollSentinelTests.cs:14` | | `MobileCardListTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Lists` | `MMCA.Common.UI.Tests/Components/Lists/MobileCardListTests.cs:10` | -| `MobileInfiniteScrollListTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Lists` | `MMCA.Common.UI.Tests/Components/Lists/MobileInfiniteScrollListTests.cs:12` | +| `MobileInfiniteScrollListTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Lists` | `MMCA.Common.UI.Tests/Components/Lists/MobileInfiniteScrollListTests.cs:13` | | `NotificationBellHost` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Notifications` | `MMCA.Common.UI.Tests/Components/Notifications/NotificationBellTests.cs:22` | | `NotificationBellTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Notifications` | `MMCA.Common.UI.Tests/Components/Notifications/NotificationBellTests.cs:53` | | `NotificationListenerTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Notifications` | `MMCA.Common.UI.Tests/Components/Notifications/NotificationListenerTests.cs:22` | -| `TimerCountingTimeProvider` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Notifications` | `MMCA.Common.UI.Tests/Components/Notifications/NotificationBellTests.cs:357` | +| `TimerCountingTimeProvider` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.Notifications` | `MMCA.Common.UI.Tests/Components/Notifications/NotificationBellTests.cs:397` | | `EmptyStateTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.PageState` | `MMCA.Common.UI.Tests/Components/PageState/EmptyStateTests.cs:6` | | `ListNoRecordsContentTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.PageState` | `MMCA.Common.UI.Tests/Components/PageState/ListNoRecordsContentTests.cs:14` | | `PageStateScopeTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Components.PageState` | `MMCA.Common.UI.Tests/Components/PageState/PageStateScopeTests.cs:12` | @@ -5199,6 +5358,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `RecordingCultureApplier` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Globalization` | `MMCA.Common.UI.Tests/Globalization/CultureSwitcherTests.cs:83` | | `ResxMudLocalizerTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Globalization` | `MMCA.Common.UI.Tests/Globalization/ResxMudLocalizerTests.cs:14` | | `StringLocalizerPluralExtensionsTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Globalization` | `MMCA.Common.UI.Tests/Globalization/StringLocalizerPluralExtensionsTests.cs:13` | +| `BunitComponentTestBaseAuthorizationTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Infrastructure` | `MMCA.Common.UI.Tests/Infrastructure/BunitComponentTestBaseAuthorizationTests.cs:13` | | `BunitComponentTestBaseFacadeTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Infrastructure` | `MMCA.Common.UI.Tests/Infrastructure/BunitComponentTestBaseFacadeTests.cs:18` | | `CapturedRequest` | record | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Infrastructure` | `MMCA.Common.UI.Tests/Infrastructure/HttpTestDoubles.cs:11` | | `CapturingHttpMessageHandlerTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Infrastructure` | `MMCA.Common.UI.Tests/Infrastructure/CapturingHttpMessageHandlerTests.cs:16` | @@ -5209,7 +5369,9 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ToastConsumer` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Infrastructure` | `MMCA.Common.UI.Tests/Infrastructure/BunitComponentTestBaseFacadeTests.cs:35` | | `UiHttpServiceHarnessTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Infrastructure` | `MMCA.Common.UI.Tests/Infrastructure/UiHttpServiceHarnessTests.cs:13` | | `NavMenuTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Layout` | `MMCA.Common.UI.Tests/Layout/NavMenuTests.cs:24` | -| `StubUiModule` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Layout` | `MMCA.Common.UI.Tests/Layout/NavMenuTests.cs:290` | +| `StubUiModule` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Layout` | `MMCA.Common.UI.Tests/Layout/NavMenuTests.cs:308` | +| `NotificationPageGateTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Notifications` | `MMCA.Common.UI.Tests/Notifications/NotificationPageGateTests.cs:16` | +| `OtherModule` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Notifications` | `MMCA.Common.UI.Tests/Notifications/NotificationPageGateTests.cs:46` | | `ForbiddenTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Pages` | `MMCA.Common.UI.Tests/Pages/ForbiddenTests.cs:10` | | `EditorShell` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Pages.Administration` | `MMCA.Common.UI.Tests/Pages/Administration/RoleAdminEditPageTests.cs:27` | | `RoleAdminEditPageTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Pages.Administration` | `MMCA.Common.UI.Tests/Pages/Administration/RoleAdminEditPageTests.cs:12` | @@ -5223,7 +5385,8 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `UserAdminListTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Pages.Administration` | `MMCA.Common.UI.Tests/Pages/Administration/UserAdminListTests.cs:27` | | `AuthModelValidationTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Pages.Auth` | `MMCA.Common.UI.Tests/Pages/Auth/AuthModelValidationTests.cs:11` | | `ConfirmEmailPageTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Pages.Auth` | `MMCA.Common.UI.Tests/Pages/Auth/ConfirmEmailPageTests.cs:9` | -| `RegisterFormTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Pages.Auth` | `MMCA.Common.UI.Tests/Pages/Auth/RegisterFormTests.cs:21` | +| `PasswordComplexityAttributeTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Pages.Auth` | `MMCA.Common.UI.Tests/Pages/Auth/PasswordComplexityAttributeTests.cs:12` | +| `RegisterFormTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Pages.Auth` | `MMCA.Common.UI.Tests/Pages/Auth/RegisterFormTests.cs:22` | | `SessionsTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Pages.Auth` | `MMCA.Common.UI.Tests/Pages/Auth/SessionsTests.cs:27` | | `DataGridListPageBaseTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Pages.Common` | `MMCA.Common.UI.Tests/Pages/Common/DataGridListPageBaseTests.cs:22` | | `DetailPageBaseTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Pages.Common` | `MMCA.Common.UI.Tests/Pages/Common/DetailPageBaseTests.cs:12` | @@ -5241,23 +5404,30 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `NotificationListTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Pages.Notifications` | `MMCA.Common.UI.Tests/Pages/Notifications/NotificationListTests.cs:19` | | `NotificationSendTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Pages.Notifications` | `MMCA.Common.UI.Tests/Pages/Notifications/NotificationSendTests.cs:22` | | `ApiClientRegistrationTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services` | `MMCA.Common.UI.Tests/Services/ApiClientRegistrationTests.cs:18` | +| `CapturingHandler` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services` | `MMCA.Common.UI.Tests/Services/SameOriginProxyClientTests.cs:141` | | `LazyJsModuleTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services` | `MMCA.Common.UI.Tests/Services/LazyJsModuleTests.cs:14` | | `ListPageQueryStateServiceTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services` | `MMCA.Common.UI.Tests/Services/ListPageQueryStateServiceTests.cs:6` | | `ListPageStateServiceTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services` | `MMCA.Common.UI.Tests/Services/ListPageStateServiceTests.cs:8` | | `MudToastServiceTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services` | `MMCA.Common.UI.Tests/Services/MudToastServiceTests.cs:16` | -| `RecordingNavigationManager` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services` | `MMCA.Common.UI.Tests/Services/ListPageQueryStateServiceTests.cs:264` | +| `RecordingNavigationManager` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services` | `MMCA.Common.UI.Tests/Services/ListPageQueryStateServiceTests.cs:278` | +| `SameOriginProxyClientTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services` | `MMCA.Common.UI.Tests/Services/SameOriginProxyClientTests.cs:26` | | `WasmFormFactorTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services` | `MMCA.Common.UI.Tests/Services/WasmFormFactorTests.cs:13` | | `AuthenticatedServiceBaseRetryTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Api` | `MMCA.Common.UI.Tests/Services/Api/AuthenticatedServiceBaseRetryTests.cs:13` | | `ChildEntityServiceBaseTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Api` | `MMCA.Common.UI.Tests/Services/Api/ChildEntityServiceBaseTests.cs:20` | | `EntityServiceBaseCachingTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Api` | `MMCA.Common.UI.Tests/Services/Api/EntityServiceBaseCachingTests.cs:24` | | `EntityServiceBaseIdempotencyRetryTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Api` | `MMCA.Common.UI.Tests/Services/Api/EntityServiceBaseIdempotencyRetryTests.cs:21` | | `EntityServiceBaseTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Api` | `MMCA.Common.UI.Tests/Services/Api/EntityServiceBaseTests.cs:27` | +| `GatedGetHandler` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Api` | `MMCA.Common.UI.Tests/Services/Api/EntityServiceBaseCachingTests.cs:340` | | `HttpResultExecutorTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Api` | `MMCA.Common.UI.Tests/Services/Api/HttpResultExecutorTests.cs:15` | +| `IdempotentReadRetryTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Api` | `MMCA.Common.UI.Tests/Services/Api/IdempotentReadRetryTests.cs:16` | | `MembershipDto` | record | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Api` | `MMCA.Common.UI.Tests/Services/Api/ChildEntityServiceBaseTests.cs:22` | | `MembershipService` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Api` | `MMCA.Common.UI.Tests/Services/Api/ChildEntityServiceBaseTests.cs:29` | | `Mocks` | record | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Api` | `MMCA.Common.UI.Tests/Services/Api/ChildEntityServiceBaseTests.cs:44` | | `Mocks` | record | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Api` | `MMCA.Common.UI.Tests/Services/Api/EntityServiceBaseTests.cs:39` | +| `PagedReadAllTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Api` | `MMCA.Common.UI.Tests/Services/Api/PagedReadAllTests.cs:14` | +| `PolicyProbe` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Api` | `MMCA.Common.UI.Tests/Services/Api/IdempotentReadRetryTests.cs:72` | | `ScriptedHandler` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Api` | `MMCA.Common.UI.Tests/Services/Api/EntityServiceBaseIdempotencyRetryTests.cs:46` | +| `StubHandler` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Api` | `MMCA.Common.UI.Tests/Services/Api/IdempotentReadRetryTests.cs:79` | | `TrackingHttpResponseMessage` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Api` | `MMCA.Common.UI.Tests/Services/Api/AuthenticatedServiceBaseRetryTests.cs:59` | | `WidgetDto` | record | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Api` | `MMCA.Common.UI.Tests/Services/Api/EntityServiceBaseCachingTests.cs:26` | | `WidgetDto` | record | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Api` | `MMCA.Common.UI.Tests/Services/Api/EntityServiceBaseIdempotencyRetryTests.cs:29` | @@ -5267,31 +5437,38 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `WidgetService` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Api` | `MMCA.Common.UI.Tests/Services/Api/EntityServiceBaseTests.cs:36` | | `AuthDelegatingHandlerTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Auth` | `MMCA.Common.UI.Tests/Services/Auth/AuthDelegatingHandlerTests.cs:16` | | `AuthUIServiceTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Auth` | `MMCA.Common.UI.Tests/Services/Auth/AuthUIServiceTests.cs:37` | -| `DirectApiTokenRefresherTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Auth` | `MMCA.Common.UI.Tests/Services/Auth/DirectApiTokenRefresherTests.cs:17` | +| `DirectApiTokenRefresherTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Auth` | `MMCA.Common.UI.Tests/Services/Auth/DirectApiTokenRefresherTests.cs:18` | | `EmailConfirmationUIServiceTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Auth` | `MMCA.Common.UI.Tests/Services/Auth/EmailConfirmationUIServiceTests.cs:14` | -| `FakeCacheStore` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Auth` | `MMCA.Common.UI.Tests/Services/Auth/OAuthFlowStateStoreTests.cs:99` | +| `FakeCacheStore` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Auth` | `MMCA.Common.UI.Tests/Services/Auth/OAuthFlowStateStoreTests.cs:115` | +| `JsFetchSessionCookieSyncTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Auth` | `MMCA.Common.UI.Tests/Services/Auth/JsFetchSessionCookieSyncTests.cs:13` | | `JwtAuthenticationStateProviderTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Auth` | `MMCA.Common.UI.Tests/Services/Auth/JwtAuthenticationStateProviderTests.cs:14` | -| `Mocks` | record | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Auth` | `MMCA.Common.UI.Tests/Services/Auth/DirectApiTokenRefresherTests.cs:19` | +| `Mocks` | record | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Auth` | `MMCA.Common.UI.Tests/Services/Auth/DirectApiTokenRefresherTests.cs:20` | | `Mocks` | record | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Auth` | `MMCA.Common.UI.Tests/Services/Auth/WasmTokenStorageServiceTests.cs:18` | | `OAuthFlowStateStoreTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Auth` | `MMCA.Common.UI.Tests/Services/Auth/OAuthFlowStateStoreTests.cs:15` | | `SameOriginProxyTokenRefresherTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Auth` | `MMCA.Common.UI.Tests/Services/Auth/SameOriginProxyTokenRefresherTests.cs:14` | | `TokenHydrationWarmupTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Auth` | `MMCA.Common.UI.Tests/Services/Auth/TokenHydrationWarmupTests.cs:13` | +| `TokenRefreshPipelineTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Auth` | `MMCA.Common.UI.Tests/Services/Auth/TokenRefreshPipelineTests.cs:20` | | `UserAgentSummaryTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Auth` | `MMCA.Common.UI.Tests/Services/Auth/UserAgentSummaryTests.cs:13` | | `WasmTokenStorageServiceTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Auth` | `MMCA.Common.UI.Tests/Services/Auth/WasmTokenStorageServiceTests.cs:16` | | `UiReadCacheTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Caching` | `MMCA.Common.UI.Tests/Services/Caching/UiReadCacheTests.cs:15` | | `CapabilityFallbackTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Capabilities` | `MMCA.Common.UI.Tests/Services/Capabilities/CapabilityFallbackTests.cs:18` | | `DeepLinkDispatcherTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Capabilities` | `MMCA.Common.UI.Tests/Services/Capabilities/DeepLinkDispatcherTests.cs:11` | | `DeepLinkRouteShapeTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Capabilities` | `MMCA.Common.UI.Tests/Services/Capabilities/DeepLinkRouteShapeTests.cs:13` | +| `BrowserMapNavigationServiceTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Capabilities.Geo` | `MMCA.Common.UI.Tests/Services/Capabilities/Geo/BrowserMapNavigationServiceTests.cs:13` | +| `BrowserExternalLinkServiceTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Capabilities.Interop` | `MMCA.Common.UI.Tests/Services/Capabilities/Interop/BrowserExternalLinkServiceTests.cs:14` | +| `MauiBackNavigationBridgeTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Navigation` | `MMCA.Common.UI.Tests/Services/Navigation/MauiBackNavigationBridgeTests.cs:12` | | `NavigationPublicLinkBuilderTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Navigation` | `MMCA.Common.UI.Tests/Services/Navigation/NavigationPublicLinkBuilderTests.cs:16` | | `ReturnUrlProtectorTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Navigation` | `MMCA.Common.UI.Tests/Services/Navigation/ReturnUrlProtectorTests.cs:6` | -| `CapturingLogger` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Notifications` | `MMCA.Common.UI.Tests/Services/Notifications/NotificationHubServiceTests.cs:286` | -| `ChannelReferenceCounterTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Notifications` | `MMCA.Common.UI.Tests/Services/Notifications/NotificationHubServiceTests.cs:320` | -| `ConcurrencyTrackingTokenStorage` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Notifications` | `MMCA.Common.UI.Tests/Services/Notifications/NotificationHubServiceTests.cs:241` | +| `CapturingLogger` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Notifications` | `MMCA.Common.UI.Tests/Services/Notifications/NotificationHubServiceTests.cs:347` | +| `ChannelReferenceCounterTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Notifications` | `MMCA.Common.UI.Tests/Services/Notifications/NotificationHubServiceTests.cs:381` | +| `ConcurrencyTrackingTokenStorage` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Notifications` | `MMCA.Common.UI.Tests/Services/Notifications/NotificationHubServiceTests.cs:302` | +| `InMemoryHubServer` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Notifications` | `MMCA.Common.UI.Tests/Services/Notifications/InMemoryHubServer.cs:17` | | `Mocks` | record | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Notifications` | `MMCA.Common.UI.Tests/Services/Notifications/NotificationInboxServiceTests.cs:23` | | `Mocks` | record | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Notifications` | `MMCA.Common.UI.Tests/Services/Notifications/PushNotificationServiceTests.cs:23` | | `NotificationHubServiceTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Notifications` | `MMCA.Common.UI.Tests/Services/Notifications/NotificationHubServiceTests.cs:29` | | `NotificationInboxServiceTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Notifications` | `MMCA.Common.UI.Tests/Services/Notifications/NotificationInboxServiceTests.cs:21` | | `NotificationStateTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Notifications` | `MMCA.Common.UI.Tests/Services/Notifications/NotificationStateTests.cs:13` | +| `PipePair` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Notifications` | `MMCA.Common.UI.Tests/Services/Notifications/InMemoryHubServer.cs:108` | | `PushNotificationServiceTests` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Notifications` | `MMCA.Common.UI.Tests/Services/Notifications/PushNotificationServiceTests.cs:21` | | `StubScopeProvider` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Notifications` | `MMCA.Common.UI.Tests/Services/Notifications/NotificationInboxServiceTests.cs:26` | | `StubScopeProvider` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Services.Notifications` | `MMCA.Common.UI.Tests/Services/Notifications/PushNotificationServiceTests.cs:26` | @@ -5312,12 +5489,27 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `UrlModel` | class | MMCA.Common.UI.Tests | `MMCA.Common.UI.Tests.Validation` | `MMCA.Common.UI.Tests/Validation/AbsoluteUrlAttributeTests.cs:101` | | `DependencyInjection` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web` | `MMCA.Common.UI.Web/DependencyInjection.cs:20` | | `ClientConfigBuilder` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.ClientConfig` | `MMCA.Common.UI.Web/ClientConfig/ClientConfigBuilder.cs:17` | -| `ClientConfigEndpointExtensions` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.ClientConfig` | `MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs:15` | +| `ClientConfigEndpointExtensions` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.ClientConfig` | `MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs:17` | | `BlazorCircuitLimitExtensions` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.Hardening` | `MMCA.Common.UI.Web/Hardening/BlazorCircuitLimitExtensions.cs:19` | | `BlazorCircuitLimitSettings` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.Hardening` | `MMCA.Common.UI.Web/Hardening/BlazorCircuitLimitSettings.cs:17` | -| `BoundedCircuitHandler` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.Hardening` | `MMCA.Common.UI.Web/Hardening/BoundedCircuitHandler.cs:39` | +| `BoundedCircuitHandler` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.Hardening` | `MMCA.Common.UI.Web/Hardening/BoundedCircuitHandler.cs:43` | | `UiRateLimitingExtensions` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.Hardening` | `MMCA.Common.UI.Web/Hardening/UiRateLimitingExtensions.cs:22` | | `UiRateLimitingSettings` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.Hardening` | `MMCA.Common.UI.Web/Hardening/UiRateLimitingSettings.cs:33` | +| `HandoffBody` | record | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.SameOriginProxy` | `MMCA.Common.UI.Web/SameOriginProxy/SessionHandoffEndpoints.cs:64` | +| `HandoffSessionCookieSync` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.SameOriginProxy` | `MMCA.Common.UI.Web/SameOriginProxy/HandoffSessionServices.cs:39` | +| `HandoffTokenRefresher` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.SameOriginProxy` | `MMCA.Common.UI.Web/SameOriginProxy/HandoffSessionServices.cs:14` | +| `ProxyResponseMode` | enum | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.SameOriginProxy` | `MMCA.Common.UI.Web/SameOriginProxy/SameOriginProxyTransformer.cs:14` | +| `SameOriginApiProxyEndpoint` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.SameOriginProxy` | `MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpoint.cs:29` | +| `SameOriginApiProxyEndpointExtensions` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.SameOriginProxy` | `MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpointExtensions.cs:15` | +| `SameOriginApiProxyMarker` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.SameOriginProxy` | `MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyServiceExtensions.cs:92` | +| `SameOriginApiProxyServiceExtensions` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.SameOriginProxy` | `MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyServiceExtensions.cs:37` | +| `SameOriginApiProxySettings` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.SameOriginProxy` | `MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxySettings.cs:21` | +| `SameOriginApiProxySettingsValidator` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.SameOriginProxy` | `MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxySettingsValidator.cs:14` | +| `SameOriginProxyInvoker` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.SameOriginProxy` | `MMCA.Common.UI.Web/SameOriginProxy/SameOriginProxyInvoker.cs:19` | +| `SameOriginProxyTransformer` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.SameOriginProxy` | `MMCA.Common.UI.Web/SameOriginProxy/SameOriginProxyTransformer.cs:34` | +| `SessionHandoffEndpoints` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.SameOriginProxy` | `MMCA.Common.UI.Web/SameOriginProxy/SessionHandoffEndpoints.cs:15` | +| `SessionHandoffProtector` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.SameOriginProxy` | `MMCA.Common.UI.Web/SameOriginProxy/SessionHandoffProtector.cs:16` | +| `TokenPair` | record | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.SameOriginProxy` | `MMCA.Common.UI.Web/SameOriginProxy/SessionHandoffProtector.cs:75` | | `BlazorCspPolicyProvider` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.Security` | `MMCA.Common.UI.Web/Security/BlazorCspPolicyProvider.cs:28` | | `BlazorCspSettings` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.Security` | `MMCA.Common.UI.Web/Security/BlazorCspSettings.cs:18` | | `BlazorCspSettingsValidator` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.Security` | `MMCA.Common.UI.Web/Security/BlazorCspSettingsValidator.cs:13` | @@ -5325,8 +5517,20 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ServerTokenStorageService` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.Services` | `MMCA.Common.UI.Web/Services/ServerTokenStorageService.cs:18` | | `WebFormFactor` | class | MMCA.Common.UI.Web | `MMCA.Common.UI.Web.Services` | `MMCA.Common.UI.Web/Services/WebFormFactor.cs:12` | | `ClientConfigEndpointTests` | class | MMCA.Common.UI.Web.Tests | `MMCA.Common.UI.Web.Tests.ClientConfig` | `MMCA.Common.UI.Web.Tests/ClientConfig/ClientConfigEndpointTests.cs:21` | -| `BoundedCircuitHandlerTests` | class | MMCA.Common.UI.Web.Tests | `MMCA.Common.UI.Web.Tests.Hardening` | `MMCA.Common.UI.Web.Tests/Hardening/BoundedCircuitHandlerTests.cs:19` | +| `BoundedCircuitHandlerTests` | class | MMCA.Common.UI.Web.Tests | `MMCA.Common.UI.Web.Tests.Hardening` | `MMCA.Common.UI.Web.Tests/Hardening/BoundedCircuitHandlerTests.cs:20` | | `UiRateLimitingTests` | class | MMCA.Common.UI.Web.Tests | `MMCA.Common.UI.Web.Tests.Hardening` | `MMCA.Common.UI.Web.Tests/Hardening/UiRateLimitingTests.cs:18` | +| `FakeGateway` | class | MMCA.Common.UI.Web.Tests | `MMCA.Common.UI.Web.Tests.SameOriginProxy` | `MMCA.Common.UI.Web.Tests/SameOriginProxy/ProxyTestHarness.cs:33` | +| `Jwt` | class | MMCA.Common.UI.Web.Tests | `MMCA.Common.UI.Web.Tests.SameOriginProxy` | `MMCA.Common.UI.Web.Tests/SameOriginProxy/ProxyTestHarness.cs:246` | +| `ProxyHost` | class | MMCA.Common.UI.Web.Tests | `MMCA.Common.UI.Web.Tests.SameOriginProxy` | `MMCA.Common.UI.Web.Tests/SameOriginProxy/ProxyTestHarness.cs:148` | +| `SameOriginApiProxyAuthFlowTests` | class | MMCA.Common.UI.Web.Tests | `MMCA.Common.UI.Web.Tests.SameOriginProxy` | `MMCA.Common.UI.Web.Tests/SameOriginProxy/SameOriginApiProxyAuthFlowTests.cs:20` | +| `SameOriginApiProxyCsrfTests` | class | MMCA.Common.UI.Web.Tests | `MMCA.Common.UI.Web.Tests.SameOriginProxy` | `MMCA.Common.UI.Web.Tests/SameOriginProxy/SameOriginApiProxyCsrfTests.cs:12` | +| `SameOriginApiProxyHubTests` | class | MMCA.Common.UI.Web.Tests | `MMCA.Common.UI.Web.Tests.SameOriginProxy` | `MMCA.Common.UI.Web.Tests/SameOriginProxy/SameOriginApiProxyHubTests.cs:29` | +| `SameOriginApiProxyOptInTests` | class | MMCA.Common.UI.Web.Tests | `MMCA.Common.UI.Web.Tests.SameOriginProxy` | `MMCA.Common.UI.Web.Tests/SameOriginProxy/SameOriginApiProxyOptInTests.cs:21` | +| `SameOriginApiProxyOriginTests` | class | MMCA.Common.UI.Web.Tests | `MMCA.Common.UI.Web.Tests.SameOriginProxy` | `MMCA.Common.UI.Web.Tests/SameOriginProxy/SameOriginApiProxyOriginTests.cs:14` | +| `SameOriginApiProxyRefreshOutcomeTests` | class | MMCA.Common.UI.Web.Tests | `MMCA.Common.UI.Web.Tests.SameOriginProxy` | `MMCA.Common.UI.Web.Tests/SameOriginProxy/SameOriginApiProxyRefreshOutcomeTests.cs:14` | +| `SameOriginApiProxyTokenTests` | class | MMCA.Common.UI.Web.Tests | `MMCA.Common.UI.Web.Tests.SameOriginProxy` | `MMCA.Common.UI.Web.Tests/SameOriginProxy/SameOriginApiProxyTokenTests.cs:15` | +| `SeenRequest` | record | MMCA.Common.UI.Web.Tests | `MMCA.Common.UI.Web.Tests.SameOriginProxy` | `MMCA.Common.UI.Web.Tests/SameOriginProxy/ProxyTestHarness.cs:26` | +| `SessionHandoffServicesTests` | class | MMCA.Common.UI.Web.Tests | `MMCA.Common.UI.Web.Tests.SameOriginProxy` | `MMCA.Common.UI.Web.Tests/SameOriginProxy/SessionHandoffServicesTests.cs:14` | | `BlazorCspPolicyProviderTests` | class | MMCA.Common.UI.Web.Tests | `MMCA.Common.UI.Web.Tests.Security` | `MMCA.Common.UI.Web.Tests/Security/BlazorCspPolicyProviderTests.cs:26` | | `CapturingHandler` | class | MMCA.Common.UI.Web.Tests | `MMCA.Common.UI.Web.Tests.Security` | `MMCA.Common.UI.Web.Tests/Security/TrustedCallerHandlerTests.cs:97` | | `CapturingHandler` | class | MMCA.Common.UI.Web.Tests | `MMCA.Common.UI.Web.Tests.Security` | `MMCA.Common.UI.Web.Tests/Security/TrustedCallerHeaderRegistrationTests.cs:172` | @@ -5349,7 +5553,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `IServiceCollection services` | MMCA.ADC.Conference.Application | `MMCA.ADC.Conference.Application/DependencyInjection.cs:57` | | `IServiceCollection services` | MMCA.ADC.Conference.Contracts | `MMCA.ADC.Conference.Contracts/DependencyInjection.cs:17` | | `IServiceCollection services` | MMCA.ADC.Conference.Infrastructure | `MMCA.ADC.Conference.Infrastructure/DependencyInjection.cs:20` | -| `IServiceCollection services` | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI/DependencyInjection.cs:20` | +| `IServiceCollection services` | MMCA.ADC.Conference.UI | `MMCA.ADC.Conference.UI/DependencyInjection.cs:21` | | `IPage page` | MMCA.ADC.E2E.Tests | `MMCA.ADC.E2E.Tests/PageObjects/Conference/Events/EventFilterPageExtensions.cs:34` | | `IServiceCollection services` | MMCA.ADC.Engagement.API | `MMCA.ADC.Engagement.API/DependencyInjection.cs:17` | | `IServiceCollection services` | MMCA.ADC.Engagement.Application | `MMCA.ADC.Engagement.Application/DependencyInjection.cs:31` | @@ -5363,7 +5567,7 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `IServiceCollection services` | MMCA.ADC.Identity.Service | `MMCA.ADC.Identity.Service/Authorization/TokenPermissionGrants.cs:36` | | `IServiceCollection services` | MMCA.ADC.Identity.UI | `MMCA.ADC.Identity.UI/DependencyInjection.cs:14` | | `IServiceCollection services` | MMCA.ADC.Notification.API | `MMCA.ADC.Notification.API/DependencyInjection.cs:19` | -| `IServiceCollection services` | MMCA.ADC.Notification.Application | `MMCA.ADC.Notification.Application/DependencyInjection.cs:14` | +| `IServiceCollection services` | MMCA.ADC.Notification.Application | `MMCA.ADC.Notification.Application/DependencyInjection.cs:15` | | `IServiceCollection services` | MMCA.ADC.Notification.Contracts | `MMCA.ADC.Notification.Contracts/DependencyInjection.cs:18` | | `IServiceCollection services` | MMCA.Common.AI | `MMCA.Common.AI/DependencyInjection.cs:79` | | `IServiceCollection services` | MMCA.Common.AI | `MMCA.Common.AI/Guardrails/GuardrailServiceCollectionExtensions.cs:19` | @@ -5378,20 +5582,21 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `IMvcBuilder builder` | MMCA.Common.API | `MMCA.Common.API/Notifications/DependencyInjection.cs:11` | | `IApplicationBuilder app` | MMCA.Common.API | `MMCA.Common.API/SessionCookies/CookieSessionRefreshMiddleware.cs:37` | | `AuthenticationBuilder builder` | MMCA.Common.API | `MMCA.Common.API/SessionCookies/SessionCookieAuthenticationHandler.cs:92` | -| `IEndpointRouteBuilder endpoints` | MMCA.Common.API | `MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:20` | +| `IEndpointRouteBuilder endpoints` | MMCA.Common.API | `MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:21` | | `IConfiguration configuration` | MMCA.Common.API | `MMCA.Common.API/Startup/Auth/JwtAuthorityExtensions.cs:23` | | `IApplicationBuilder app` | MMCA.Common.API | `MMCA.Common.API/Startup/CommonForwardedHeadersExtensions.cs:11` | -| `IServiceProvider services` | MMCA.Common.API | `MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:22` | +| `IServiceProvider services` | MMCA.Common.API | `MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:33` | +| `WebApplication app` | MMCA.Common.API | `MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:128` | | `IEndpointRouteBuilder endpoints` | MMCA.Common.API | `MMCA.Common.API/Startup/Endpoints/AppAssociationEndpointExtensions.cs:26` | | `IEndpointRouteBuilder endpoints` | MMCA.Common.API | `MMCA.Common.API/Startup/Endpoints/JwksEndpointExtensions.cs:22` | | `IEndpointRouteBuilder endpoints` | MMCA.Common.API | `MMCA.Common.API/Startup/Endpoints/OidcDiscoveryEndpointExtensions.cs:49` | -| `WebApplication app` | MMCA.Common.API | `MMCA.Common.API/Startup/Endpoints/OpenApiEndpointExtensions.cs:24` | +| `WebApplication app` | MMCA.Common.API | `MMCA.Common.API/Startup/Endpoints/OpenApiEndpointExtensions.cs:30` | | `IServiceCollection services` | MMCA.Common.API | `MMCA.Common.API/Startup/MiniProfilerExtensions.cs:11` | | `WebApplicationBuilder builder` | MMCA.Common.API | `MMCA.Common.API/Startup/ModuleHostExtensions.cs:24` | | `WebApplication app` | MMCA.Common.API | `MMCA.Common.API/Startup/SignalRExtensions.cs:14` | | `IServiceCollection services` | MMCA.Common.API | `MMCA.Common.API/Startup/WebApplicationBuilderExtensions.Authentication.cs:26` | | `IServiceCollection services` | MMCA.Common.API | `MMCA.Common.API/Startup/WebApplicationBuilderExtensions.cs:30` | -| `IServiceCollection services` | MMCA.Common.API | `MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:294` | +| `IServiceCollection services` | MMCA.Common.API | `MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:296` | | `WebApplication app` | MMCA.Common.API | `MMCA.Common.API/Startup/WebApplicationExtensions.cs:37` | | `IServiceCollection services` | MMCA.Common.Application | `MMCA.Common.Application/DependencyInjection.Crud.cs:16` | | `IServiceCollection services` | MMCA.Common.Application | `MMCA.Common.Application/DependencyInjection.cs:26` | @@ -5400,12 +5605,12 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `ICurrentUserService currentUserService` | MMCA.Common.Application | `MMCA.Common.Application/Extensions/CurrentUserServiceExtensions.cs:14` | | `ValidationResult result` | MMCA.Common.Application | `MMCA.Common.Application/Extensions/ValidationFailureExtensions.cs:11` | | `IServiceCollection services` | MMCA.Common.Application | `MMCA.Common.Application/Notifications/DependencyInjection.cs:29` | -| `IServiceCollection services` | MMCA.Common.Aspire | `MMCA.Common.Aspire/Extensions.cs:105` | +| `IServiceCollection services` | MMCA.Common.Aspire | `MMCA.Common.Aspire/Extensions.cs:113` | | `WebApplication app` | MMCA.Common.Aspire | `MMCA.Common.Aspire/Extensions.Health.cs:110` | | `IApplicationBuilder app` | MMCA.Common.Aspire | `MMCA.Common.Aspire/Gateway/GatewayCorrelationMiddleware.cs:75` | | `IServiceCollection services` | MMCA.Common.Aspire | `MMCA.Common.Aspire/Gateway/GatewayHealthCheckExtensions.cs:96` | -| `IServiceCollection services` | MMCA.Common.Aspire | `MMCA.Common.Aspire/Gateway/GatewayRateLimitingExtensions.cs:240` | -| `IApplicationBuilder app` | MMCA.Common.Aspire | `MMCA.Common.Aspire/Gateway/GatewayRateLimitingExtensions.cs:297` | +| `IServiceCollection services` | MMCA.Common.Aspire | `MMCA.Common.Aspire/Gateway/GatewayRateLimitingExtensions.cs:258` | +| `IApplicationBuilder app` | MMCA.Common.Aspire | `MMCA.Common.Aspire/Gateway/GatewayRateLimitingExtensions.cs:315` | | `IServiceCollection services` | MMCA.Common.Aspire | `MMCA.Common.Aspire/GatewayCorsExtensions.cs:18` | | `WebApplicationBuilder builder` | MMCA.Common.Aspire | `MMCA.Common.Aspire/Kestrel/KestrelEndpointExtensions.cs:39` | | `WebApplicationBuilder builder` | MMCA.Common.Aspire | `MMCA.Common.Aspire/Logging/SerilogHostExtensions.cs:29` | @@ -5418,18 +5623,18 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `Type entityType` | MMCA.Common.Domain | `MMCA.Common.Domain/Extensions/EntityTypeExtensions.cs:11` | | `IApplicationBuilder app` | MMCA.Common.Gateway | `MMCA.Common.Gateway/ForwardedHeadersExtensions.cs:25` | | `IReverseProxyBuilder builder` | MMCA.Common.Gateway | `MMCA.Common.Gateway/GatewayReverseProxyExtensions.cs:28` | -| `IServiceCollection services` | MMCA.Common.Gateway | `MMCA.Common.Gateway/RateLimiting/GatewayRoutePolicyExtensions.cs:29` | +| `IServiceCollection services` | MMCA.Common.Gateway | `MMCA.Common.Gateway/RateLimiting/GatewayRoutePolicyExtensions.cs:40` | | `IServiceCollection services` | MMCA.Common.Grpc | `MMCA.Common.Grpc/DependencyInjection.cs:27` | -| `ErrorType errorType` | MMCA.Common.Grpc | `MMCA.Common.Grpc/ResultGrpcExtensions.cs:49` | -| `Result result` | MMCA.Common.Grpc | `MMCA.Common.Grpc/ResultGrpcExtensions.cs:60` | -| `IReadOnlyList errors` | MMCA.Common.Grpc | `MMCA.Common.Grpc/ResultGrpcExtensions.cs:98` | -| `Metadata? trailers` | MMCA.Common.Grpc | `MMCA.Common.Grpc/ResultGrpcExtensions.cs:146` | -| `RpcException exception` | MMCA.Common.Grpc | `MMCA.Common.Grpc/ResultGrpcExtensions.cs:196` | +| `ErrorType errorType` | MMCA.Common.Grpc | `MMCA.Common.Grpc/ResultGrpcExtensions.cs:50` | +| `Result result` | MMCA.Common.Grpc | `MMCA.Common.Grpc/ResultGrpcExtensions.cs:61` | +| `IReadOnlyList errors` | MMCA.Common.Grpc | `MMCA.Common.Grpc/ResultGrpcExtensions.cs:99` | +| `Metadata? trailers` | MMCA.Common.Grpc | `MMCA.Common.Grpc/ResultGrpcExtensions.cs:151` | +| `RpcException exception` | MMCA.Common.Grpc | `MMCA.Common.Grpc/ResultGrpcExtensions.cs:202` | | `IServiceCollection services` | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:18` | | `IServiceCollection services` | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:19` | -| `IServiceCollection services` | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure/DependencyInjection.cs:48` | +| `IServiceCollection services` | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure/DependencyInjection.cs:49` | | `IServiceCollection services` | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure/DependencyInjection.Jobs.cs:15` | -| `IServiceCollection services` | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:17` | +| `IServiceCollection services` | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:18` | | `IServiceCollection services` | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:19` | | `IBusRegistrationConfigurator x` | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure/Messaging/Consumers/IntegrationEventConsumerExtensions.cs:14` | | `IndexBuilder indexBuilder` | MMCA.Common.Infrastructure | `MMCA.Common.Infrastructure/Persistence/Configuration/IndexBuilderExtensions.cs:12` | @@ -5453,15 +5658,17 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `IServiceCollection services` | MMCA.Common.UI.Maui | `MMCA.Common.UI.Maui/DependencyInjection.cs:34` | | `MauiAppBuilder builder` | MMCA.Common.UI.Maui | `MMCA.Common.UI.Maui/HostingDependencyInjection.cs:17` | | `Window window` | MMCA.Common.UI.Maui | `MMCA.Common.UI.Maui/WindowLifecycleExtensions.cs:24` | -| `IEndpointRouteBuilder endpoints` | MMCA.Common.UI.Web | `MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs:23` | +| `IEndpointRouteBuilder endpoints` | MMCA.Common.UI.Web | `MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs:25` | | `IServiceCollection services` | MMCA.Common.UI.Web | `MMCA.Common.UI.Web/DependencyInjection.cs:22` | | `IServiceCollection services` | MMCA.Common.UI.Web | `MMCA.Common.UI.Web/Hardening/BlazorCircuitLimitExtensions.cs:43` | -| `IServiceCollection services` | MMCA.Common.UI.Web | `MMCA.Common.UI.Web/Hardening/UiRateLimitingExtensions.cs:136` | -| `IApplicationBuilder app` | MMCA.Common.UI.Web | `MMCA.Common.UI.Web/Hardening/UiRateLimitingExtensions.cs:181` | +| `IServiceCollection services` | MMCA.Common.UI.Web | `MMCA.Common.UI.Web/Hardening/UiRateLimitingExtensions.cs:152` | +| `IApplicationBuilder app` | MMCA.Common.UI.Web | `MMCA.Common.UI.Web/Hardening/UiRateLimitingExtensions.cs:197` | +| `IEndpointRouteBuilder endpoints` | MMCA.Common.UI.Web | `MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpointExtensions.cs:17` | +| `IServiceCollection services` | MMCA.Common.UI.Web | `MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyServiceExtensions.cs:39` | ## Generated / excluded artifacts (no type sections written) -136 files excluded as generated (EF migrations, snapshots, *.g.cs, AssemblyInfo). +138 files excluded as generated (EF migrations, snapshots, *.g.cs, AssemblyInfo). | File | |------| @@ -5576,6 +5783,8 @@ Generated mechanically by a Roslyn syntactic parse of every in-scope `.cs` file | `MMCA.ADC.Migrations.SqlServer.Identity/Migrations/20260912014043_AddOutboxOrigin.Designer.cs` | | `MMCA.ADC.Migrations.SqlServer.Identity/Migrations/20260913044149_AddPermissionGrants.cs` | | `MMCA.ADC.Migrations.SqlServer.Identity/Migrations/20260913044149_AddPermissionGrants.Designer.cs` | +| `MMCA.ADC.Migrations.SqlServer.Identity/Migrations/20260930161158_UserNamesUnicode.cs` | +| `MMCA.ADC.Migrations.SqlServer.Identity/Migrations/20260930161158_UserNamesUnicode.Designer.cs` | | `MMCA.ADC.Migrations.SqlServer.Identity/Migrations/SQLServerDbContextModelSnapshot.cs` | | `MMCA.ADC.Migrations.SqlServer.Notification/DesignTimeSQLServerDbContextFactory.cs` | | `MMCA.ADC.Migrations.SqlServer.Notification/Migrations/20260606053154_InitialCreate.cs` | diff --git a/docs-src/onboarding/00-primer.md b/docs-src/onboarding/00-primer.md index 67db2f9a..bc82c158 100644 --- a/docs-src/onboarding/00-primer.md +++ b/docs-src/onboarding/00-primer.md @@ -362,6 +362,10 @@ the full set, for orientation: | 125 | Parameterized SQL only: hand-written SQL has exactly one door, `IRawSqlQueryExecutor`, whose two entry points accept `FormattableString` and nothing else (a concatenated statement does not compile), `EFRawSqlQueryExecutor` names the relational-only limit, and `RawSqlConventionTestsBase` bans the four `*Raw` EF members in module code as a fitness gate | [g07](group-07-persistence-ef-core.md)/[g28](group-28-testing-infrastructure.md) | | 126 | Event sourcing is not adopted: aggregates stay current-state EF rows that raise domain events for notification, carry audit fields and soft-delete, so no repository gains an event store, per-aggregate stream, projection layer or rehydration path | [g02](group-02-domain-building-blocks.md)/[g04](group-04-events-outbox.md) | | 127 | The actor model is not adopted: per-entity state stays in the owning module's database behind optimistic concurrency, read pressure stays on the caching tiers, and live fan-out stays on the notification hub, with no actor runtime as a hosting layer | [g07](group-07-persistence-ef-core.md)/[g09](group-09-caching.md)/[g10](group-10-notifications.md) | +| 128 | Time as an input: Domain and Application code never reads the ambient clock (`DateTime.UtcNow`, `Now`, `Today`, `DateTimeOffset.UtcNow`, `Now`); handlers inject `TimeProvider` and pass the instant into the domain, `BaseDomainEvent.DateOccurred` is the single exemption, and an IL-scanning fitness base (`ClockReadTestsBase`) enforces it | [g28](group-28-testing-infrastructure.md) | +| 129 | The tactical aggregate contract: entities are sealed classes over `BaseEntity` / `AuditableBaseEntity` / `AuditableAggregateRootEntity`, built only through a `Create` returning `Result`, with private setters, child access through the root and invariants in static `*Invariants` classes; `EntityConventionTestsBase` gates construction, encapsulation and placement | [g28](group-28-testing-infrastructure.md) | +| 130 | Per-engine data source strategy: one internal `IDataSourceEngine` per engine (SQL Server, PostgreSQL, SQLite, Cosmos DB) behind the static `DataSourceEngines.For(DataSource)` registry, so call sites read engine facts instead of branching on the `DataSource` enum, with `DataSourceBranchingFitnessTests` as the gate | [g07](group-07-persistence-ef-core.md)/[g28](group-28-testing-infrastructure.md) | +| 131 | Same-origin API proxy (backend-for-frontend): an opt-in `AddCommonSameOriginApiProxy` plus `MapCommonSameOriginApiProxy` serves `/api/**` on the UI host's own origin and forwards to the gateway through YARP, attaching the bearer from the HttpOnly session cookie so no browser script ever holds an API token | [g15](group-15-common-ui-framework.md)/[g08](group-08-auth.md)/[g12](group-12-api-hosting-mapping.md) | The canonical index for the full set can be found at . diff --git a/docs-src/onboarding/99-coverage-audit.md b/docs-src/onboarding/99-coverage-audit.md index e5b46660..c39fef45 100644 --- a/docs-src/onboarding/99-coverage-audit.md +++ b/docs-src/onboarding/99-coverage-audit.md @@ -1194,6 +1194,56 @@ double-counted (each type maps to exactly one group). > - **Verification:** `verify.ps1`: **0 missing**, rollup contract **0 failures**, citation integrity **0 failures**, rubric **34/34**. `verify.ps1` gained a fourth check this pass, **anchor integrity**: every in-guide `#anchor` link must land on a heading id the renderer emits. Before it existed, a rougher scan had already fixed 46 links pointing at an existing type in the wrong chapter (40 of them `RoleNames`, which lives in G23, not G08). Its first run then found **591** dead links across 24,498. Most were two systematic classes, now repaired by `fixlinks.ps1` on every pass: 426 links to rolled-up test classes (catalogued by project, so never given a heading; they now link the per-project rollup table) and 128 to single members of a family section (now the family heading). The rest were renamed or re-slugged headings, fixed by hand, and the cycles link now uses a stable `#cycles` id. The same pass found a stale second copy of the per-project rollup table rendering above the current one (the rollup unit's id moved when the group grew); it was removed, and the rollup contract now fails on a duplicated row or a restated count that disagrees with the inventory. The last **26** named types that no longer exist in source (mostly the earlier scoring pipeline, `ScoreEventSessionsHandler`, `AnthropicScoringService`, `SessionScoringProcessor`, `SessionScoringQueue`), in prose the type-drift pipeline never reopens because the sections holding it were unchanged. A follow-up pass re-authored those sentences in 15 parts from current source (the durable scoring internal command, `ScoreEventSessionsInternalCommandHandler` plus `SessionScoringRunner`, and the AppHost smoke tier in `devops-aspire`); spot-check 4 CONFIRMED, 2 DRIFTED on framing only (an unchartered but intended rollup subsection, an evidence row that is not a cite). Anchor integrity **0 failures**. > - **Governance events:** none (no new group, no classifier rule). The primer ADR table already matches the 127-record index. +> **Regeneration note (re-verified against current source, 2026-10-02 full drift sweep).** Regenerated +> at MMCA.Common `cd0026df` + MMCA.ADC `024e4a98` (both clean; prior pass `834a8fa` / +> `3a32fd34`, Common releases v1.212.0 through v1.218.1 in between). Net change: **+192** distinct nodes (5,042 to **5,234**), +> 205 added and 13 removed by name, **186** relocated (same type, new `file:line`), +> 581 body-only, 0 regrouped; `classify.ps1` reports 0 (after the approved `MMCA.Common.LoadTests` rule below; the first run refused 14) unmapped and the per-group counts +> sum to 5,234. Individually-sectioned types 2,704 to **2,776**, roll-ups 2,330 to **2,458**, +> `###` sections 2,647 to **2,720**, cycles 46 to **47**, +> edges 19,214 by namespace / 786 by unique-name fallback / 101 dropped. +> - **Per-group movement** (26 of 28 groups moved; the rest are unchanged): +> - **G02 Domain Building Blocks (Entities, Value Objects, Aggregates)** (members 36): body 3, lineShift 5. PiiRedactor now finds [Pii] through Attribute.IsDefined with inherit: true so an override sees the base declaration, DateRange.Overlaps became inclusive at both ends, and AuditableAggregateRootEntity.SetItems always copies into a fresh list; these are the Common bug-hunt 2026-09-30 and 1.218.0 remediation waves (0cf2a909, e9c28d15), anchored by MMCA.Common/Source/Core/MMCA.Common.Domain/Privacy/PiiRedactor.cs:122. +> - **G03 Querying: Specifications, Filtering & the Entity Query Service** (members 42): body 4. StringFilterStrategy.CanParseValue now overrides for the IN operator only and QueryFilterService gained validation hooks, with the three new RS0026 SuppressMessage attributes shifting QueryFieldService anchors; the filter fix is Common 1.213.1 (11434c80, server-mapped filter applied verbatim), anchored by MMCA.Common/Source/Core/MMCA.Common.Application/Services/Filtering/StringFilterStrategy.cs:26. +> - **G04 Domain & Integration Events + Outbox Dual-Dispatch** (members 38): body 6. OutboxProcessor, OutboxCleanupService, OutboxAdministration and the event buses now take FrameworkTableTargets and gate on context.Engine.Capabilities.IsRelational instead of ApplicationDbContext.SupportsOutbox, and EfInboxStore takes TimeProvider; this is the 1.218.0 constructor-narrowing (ceiling 7) and per-engine contract plus the 1.216.0 TimeProvider change, anchored by MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:58. +> - **G05 CQRS: Commands, Queries & the Decorator Pipeline** (members 61): lineShift 2. No type body changed; TransactionalCommandDecorator and CacheKeyLocks only moved lines (CacheKeyLocks because ICacheService gained TryGetAsync and GetFromSharedStoreAsync), reason for the decorator shift not stated in source, anchored by MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:22. +> - **G06 Validation** (members 22): body 1, lineShift 11. StrongPasswordRules now takes its lengths and four character classes from the new PasswordComplexity type in Common.Shared (Level 0 to 1), and the other CommonValidationRules fragments shifted one line; the stated driver is the 1.218.0 password-rule parity gate (3 non-ASCII rows), anchored by MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:191. +> - **G07 Persistence & EF Core** (members 177): added 13, removed 1, body 39, lineShift 10. Per-engine strategy replaces enum branching: IDataSourceEngine, DataSourceEngines, DataSourceEngineCapabilities, MigrationPolicy, RowVersionStrategy, IExplicitKeyInsertDialect, ExplicitKeyInsertGroup, four engine classes and FrameworkTableTargets were added and IdentityInsertGroup removed (RequestIdentityInsert became RequestExplicitKeyInsert), so adding an engine no longer needs a hand hunt for branches (ADR-130, e9c28d15), anchored by MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/IDataSourceEngine.cs:18. +> - **G08 Authentication & Authorization** (members 162): added 9, body 21, lineShift 6. Refresh-session issue, rotation, reuse detection and session cap moved out of AuthenticationServiceBase (10 to 7 constructor dependencies) into the new IAuthSessionIssuer/AuthSessionIssuer, the SessionCookieStore family (ISessionCookieStore, SessionCookieSettings, SessionClaimsToken, SessionRefreshOutcome) was added for the same-origin proxy, and PasswordComplexity was added (e9c28d15, ADR-131), anchored by MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:39. +> - **G09 Caching** (members 9): body 4. ICacheService gained GetFromSharedStoreAsync so single-use auth records (OAuth code, reset and confirmation tokens, last 2FA step) skip the local L1 tier, plus TryGetAsync for value-type misses, and MemoryCacheService now applies Cache:DefaultDuration; reasons are a record redeemable from another replica's L1 for 30s (0439838b) and the bug-hunt items L47 and L57 (0cf2a909), anchored by MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:62. +> - **G10 Notifications (Push + In-App Inbox + Email)** (members 59): body 9. MarkAllNotificationsReadHandler became one set-based ExecuteUpdateAsync (bug-hunt L49), and ADC LiveChannelGrpcService now rejects blank or over-long channel keys, event names, bad characters and payloads over 64 KiB so the anonymous internal surface is bounded (9b423f97), anchored by MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/UserNotifications/UseCases/MarkAllRead/MarkAllNotificationsReadHandler.cs:50. +> - **G12 API Hosting, Middleware, Idempotency & DTO/Contract Mapping** (members 88): body 14. AuthControllerBase login and register are [NonIdempotent] so token pairs never enter the replay cache (M137), EntityControllerBase.ExportAsync is fail-closed unless AllowUnscopedExport is set (403 Export.RowScopeRequired, 1.218.0 privacy item #30), and startup gained InitializeDatabaseUnlessDesignTimeAsync, anchored by MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:273. +> - **G13 gRPC & Inter-Service Contracts** (members 7): added 1, body 3. GrpcWireFormat was added so UTC DateTime and invariant decimal wire strings are written and parsed in one tested place, replacing nine service and adapter copies in ADC and Store (0439838b), anchored by MMCA.Common/Source/Presentation/MMCA.Common.Grpc/GrpcWireFormat.cs:30. +> - **G14 Module System, Composition & Configuration** (members 87): removed 1, body 19, lineShift 1. UserUseCaseLog was removed and each Users handler base now declares its own private partial [LoggerMessage] methods (event ids unchanged, 46 verified identical), ModuleLoader keeps loadable types and logs Error on a ReflectionTypeLoadException instead of a Warning, and ScheduledJobRunner stamps under its own 5-second token (1.218.0 slices refactor, 0439838b, M119), anchored by MMCA.Common/Source/Core/MMCA.Common.Application/Modules/ModuleLoader.cs:81. +> - **G15 Common UI Framework (MudBlazor components, theme, base pages)** (members 174): added 21, body 42, lineShift 3. The same-origin API proxy family was added to MMCA.Common.UI.Web (SameOriginApiProxyEndpoint, SameOriginProxyInvoker, SessionHandoffEndpoints, HandoffTokenRefresher and others) so access and refresh tokens stay in HttpOnly cookies (ADR-131), PagedReadAll moved in from ADC, IdempotentReadRetry and NotificationPageGate were added, and the bundled Bootstrap stylesheet was dropped, anchored by MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpoint.cs:29. +> - **G16 Aspire Orchestration & Service Defaults** (members 65): body 5. Service-default HTTP retry now skips POST and PATCH (a timed-out attempt may still be running; L80), and a Telemetry:DisableAspNetCoreMetrics knob and Live Metrics off-switch were added for idle-billing cost (d4824b36, ADC e882f40e), anchored by MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:56. +> - **G17 ADC Conference - Domain Model & Module Contracts** (members 111): body 5. Conference domain invariants tightened: Speaker optional fields and Category type are length-checked (M139), question entity and type compared ordinally (M143), speaker link URLs must be absolute http(s) (M161), from the ADC bug-hunt remediation (495c3167), anchored by MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/SpeakerInvariants.cs:16. +> - **G18 ADC Conference - Application & Use Cases** (members 358): added 1, removed 1, body 15. SpeakerQuestionAnswerNavigationPopulator was removed, UserDeletedFeedbackHandler was added so erasure also deletes Conference feedback answers (L106), UpdateEventQuestionAnswerHandler now applies the create-path rules via EnsureAnswerIsValid (M140), and RefreshFromSessionizeCommand is no longer ITransactional so the throttle stamp commits alone (L96), all from ADC 495c3167, anchored by MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/UpdateEventQuestionAnswer/UpdateEventQuestionAnswerHandler.cs:80. +> - **G19 ADC Conference - Infrastructure & Persistence** (members 29): body 1. SessionScoringService body changed only in comments (the PII guardrail is now composed by AddConferenceAiGuardrails and the fail-closed path is described as a direct construction over a bare chat client), from the ADR-audit comment fix (3478eb27), anchored by MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Sessions/Scoring/SessionScoringService.cs:42. +> - **G20 ADC Conference - API, gRPC Contracts & Service Host** (members 52): body 20. Every Conference controller that returns a null read scope now sets AllowUnscopedExport (privileged-reader predicate, Organizer check or true for reference data) to match Common 1.218.0's fail-closed CSV export (024e4a98), and EventLiveValidationServiceGrpcAdapter returns a failure Result for a malformed speaker id instead of throwing (9b423f97), anchored by MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:138. +> - **G21 ADC Conference - UI** (members 163): added 1, body 40, lineShift 1. Public pages now share PublicReadAudience.IsPrivilegedReaderAsync (cancellation propagates, no bare catch), the Conference and Engagement UI read time through TimeProvider, session create and edit enforce end-after-start (BR-122), and the AI scoring panel counts this run's scores, from ADC 9b423f97, 495c3167 and 85ef8c18, anchored by MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/PublicReadAudience.cs:18. +> - **G22 ADC Engagement Module (Session Bookmarks)** (members 196): added 2, body 13, lineShift 1. UserDeletedBookmarksHandler and UserDeletedBadgeHandler were added so UserDeleted erases session bookmarks and the attendee badge (L106), and SetLeaderboardParticipationHandler plus BookmarkService bodies changed for the terminal erased opt-in (L100) and cancellation-aware retry (L104), from ADC 495c3167, anchored by MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/CheckIns/IntegrationEventHandlers/UserDeletedBadgeHandler.cs:22. +> - **G26 ADC Engagement Live Layer (Real-Time Polls & Session Q&A)** (members 87): added 2, body 5. UserDeletedVotesHandler and UserDeletedSessionQuestionsHandler were added to the UserDeleted fan-out (L106), CastVote and ToggleUpvote treat a lost first-insert unique-index race as success (M146), the poll and question reads apply the published-scope gate (L105), and HappeningNow reads time through TimeProvider, from ADC 495c3167 and 9b423f97, anchored by MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/LivePolls/UseCases/CastVote/CastVoteHandler.cs:58. +> - **G23 ADC Identity Module (Users, Profiles, GDPR Export/Erasure)** (members 108): added 1, body 4, lineShift 1. AuthenticationService now takes IAuthSessionIssuer in place of ITokenService, TimeProvider, IRefreshSessionStore and RefreshSessionSettings, JwtAudience was added for a fail-fast JWT audience, and user name and device columns widened to nvarchar (M148), from ADC 024e4a98, 9b423f97 and 495c3167, anchored by MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:56. +> - **G24 ADC Application Host, UI Shell & Cross-Module Composition** (members 17): body 1. AppDelegate (iOS) now asks DeepLinkDispatcher.IsAppRelativeRoute before publishing a universal link, because Publish throws on a non-app-relative route and an exception out of a UIKit delegate callback terminates the app (SEC-Common-88, mirrors MainActivity), from ADC 495c3167 and 46271321, anchored by MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/Platforms/iOS/AppDelegate.cs:20. +> - **G27 Device Capability Abstraction Layer (Native Contracts, MAUI, Browser & Fallback Adapters)** (members 103): body 7, lineShift 2. IExternalLinkService.OpenAsync now returns Task so map navigation reports the real outcome (L70), BrowserExternalLinkService explains why noopener hides a blocked tab, and MauiPushRegistrationService serializes registration passes (L79), from the Common bug-hunt wave C-5 (0cf2a909), anchored by MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Capabilities/Interop/IExternalLinkService.cs:23. +> - **G28 Common AI Integration** (members 32): body 4, lineShift 1. PiiRedactionGuardrail now scrubs tool results, tool-call string arguments and reasoning text (M136), ChatToolPolicy fails closed on unrecognised consequential-marker shapes (M135), AiSettings rejects Ai:Timeout above one hour at startup (L90), and ReplayChatClient records under a lock (L84), from the Common bug-hunt wave C-6 (0cf2a909), anchored by MMCA.Common/Source/Core/MMCA.Common.AI/Guardrails/PiiRedactionGuardrail.cs:108. +> - **G25 Testing & Quality Infrastructure** (members 2923): added 154, removed 10, body 296, lineShift 142. New gates landed in Common 1.218.0 (FrameworkConstructorDependencyTests with a 7-dependency ceiling, PiiConventionTests, PasswordRuleParityTests, EditorRequiredParameterConventionTests, DataSourceBranchingFitnessTests), a weekly MMCA.Common.LoadTests tier (OutboxThroughputLoadTests, PagedQueryLoadTests) and a sqlserver-integration tier were added, and many existing test bodies moved with the bug-hunt, same-origin proxy and gRPC adapter changes, anchored by MMCA.Common/Tests/Performance/MMCA.Common.LoadTests/OutboxThroughputLoadTests.cs:20. +> - **Handled mechanically (no re-authoring):** citation remaps 3,589 over 212 part file(s), 405 flagged for a human; 18 more fixed by the residual baseline-text match; 0 relocations; repack moved 609 sections, 19 flagged; fixlinks rewrote 4 anchors. +> - **Authoring pass:** 162 units re-authored (13 overview, 1 rollup, 148 sections) across 25 groups, all against real source +> with `path:line` citations. Spot-checks (`evidence-verifier`): first pass 5 CONFIRMED (g07, g09, g13, g14, g15 overviews) and 1 DRIFTED (g08 overview still placed the refresh-session workflow in `AuthenticationServiceBase.cs`, re-anchored to `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:39`). A short-form cite scan then found 299 `File.cs:N` cites past end-of-file in 56 parts (198 in parts re-authored this pass), which `verify.ps1` did not check; a 70-unit citation fix pass took that to 0. Its spot-checks: 4 CONFIRMED (g04, g08, g23, g28 overviews), 2 DRIFTED in group-02 (a non-existent `Email` implicit operator, stale Store `Order.cs` cites), both corrected by hand against `MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Contact/Email.cs:44` and `MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Domain/Orders/Order.cs:220`. +> - **Outside the type pipeline:** 5 of 5 `devops-*` chapters refreshed (devops-aspire, devops-cicd, devops-iac, devops-runbooks, devops-testing), +> each because a file it cites has newer commits than the chapter. +> - `devops-aspire`: MMCA.ADC/.github/workflows/cross-service-tests.yml: 1 commit(s) [9b423f97 2026-10-01 Conference-day hardening, tighter deploy gates, shared Common code; bump MMCA.Common to v1.216.0 (#233)] | MMCA.ADC/.github/workflows/deploy.yml: 3 commit(s) [024e4a98 2026-10-01 Adopt MMCA.Common 1.218.1 (remediation wave) (#236); 2140ebd1 2026-10-01 chore(deps): Bump the github-actions group across 1 directory with 4 updates (#231)] | MMCA.ADC/infra/main.bicep: 1 commit(s) [e882f40e 2026-09-29 chore(infra): idle-billing cost settings (Live Metrics off, ASP.NET Core metrics drop, 120s gateway probes) (#226)] | MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/appsettings.json: 1 commit(s) [e882f40e 2026-09-29 chore(infra): idle-billing cost settings (Live Metrics off, ASP.NET Core metrics drop, 120s gateway probes) (#226)] | MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/Dockerfile: 1 commit(s) [9b423f97 2026-10-01 Conference-day hardening, tighter deploy gates, shared Common code; bump MMCA.Common to v1.216.0 (#233)] | MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Dockerfile: 1 commit(s) [9b423f97 2026-10-01 Conference-day hardening, tighter deploy gates, shared Common code; bump MMCA.Common to v1.216.0 (#233)] +> - `devops-cicd`: MMCA.ADC/.github/workflows/claude-code-review.yml: 1 commit(s) [2140ebd1 2026-10-01 chore(deps): Bump the github-actions group across 1 directory with 4 updates (#231)] | MMCA.ADC/.github/workflows/claude.yml: 1 commit(s) [2140ebd1 2026-10-01 chore(deps): Bump the github-actions group across 1 directory with 4 updates (#231)] | MMCA.ADC/.github/workflows/cost-guard.yml: 1 commit(s) [2140ebd1 2026-10-01 chore(deps): Bump the github-actions group across 1 directory with 4 updates (#231)] | MMCA.ADC/.github/workflows/cross-service-tests.yml: 1 commit(s) [9b423f97 2026-10-01 Conference-day hardening, tighter deploy gates, shared Common code; bump MMCA.Common to v1.216.0 (#233)] | MMCA.ADC/.github/workflows/deploy.yml: 3 commit(s) [024e4a98 2026-10-01 Adopt MMCA.Common 1.218.1 (remediation wave) (#236); 2140ebd1 2026-10-01 chore(deps): Bump the github-actions group across 1 directory with 4 updates (#231)] | MMCA.ADC/.github/workflows/dr-drill.yml: 2 commit(s) [2140ebd1 2026-10-01 chore(deps): Bump the github-actions group across 1 directory with 4 updates (#231); 9b423f97 2026-10-01 Conference-day hardening, tighter deploy gates, shared Common code; bump MMCA.Common to v1.216.0 (#233)] | MMCA.ADC/.github/workflows/e2e.yml: 1 commit(s) [9b423f97 2026-10-01 Conference-day hardening, tighter deploy gates, shared Common code; bump MMCA.Common to v1.216.0 (#233)] | MMCA.ADC/.github/workflows/load-test.yml: 1 commit(s) [2140ebd1 2026-10-01 chore(deps): Bump the github-actions group across 1 directory with 4 updates (#231)] | MMCA.Common/.github/workflows/ci.yml: 2 commit(s) [1f4df71f 2026-10-01 docs: fix stale source comments found by the 2026-10-01 ADR audit (#468); 0439838b 2026-10-01 feat: auth-cache hardening, tighter gates, shared consumer code (v1.216.0) (#463)] | MMCA.Common/.github/workflows/claude-code-review.yml: 2 commit(s) [0439838b 2026-10-01 feat: auth-cache hardening, tighter gates, shared consumer code (v1.216.0) (#463); 693038e7 2026-09-30 ci: Bump anthropics/claude-code-action from 1.0.231 to 1.0.235 (#446)] | MMCA.Common/.github/workflows/claude.yml: 2 commit(s) [0439838b 2026-10-01 feat: auth-cache hardening, tighter gates, shared consumer code (v1.216.0) (#463); 693038e7 2026-09-30 ci: Bump anthropics/claude-code-action from 1.0.231 to 1.0.235 (#446)] | MMCA.Common/.github/workflows/release.yml: 1 commit(s) [0439838b 2026-10-01 feat: auth-cache hardening, tighter gates, shared consumer code (v1.216.0) (#463)] +> - `devops-iac`: MMCA.ADC/infra/main.bicep: 1 commit(s) [e882f40e 2026-09-29 chore(infra): idle-billing cost settings (Live Metrics off, ASP.NET Core metrics drop, 120s gateway probes) (#226)] | MMCA.Common/samples/deployment/main.bicep: 1 commit(s) [e9c28d15 2026-10-01 feat!: remediation backlog wave (1.218.0) (#469)] +> - `devops-runbooks`: MMCA.ADC/.github/workflows/dr-drill.yml: 2 commit(s) [2140ebd1 2026-10-01 chore(deps): Bump the github-actions group across 1 directory with 4 updates (#231); 9b423f97 2026-10-01 Conference-day hardening, tighter deploy gates, shared Common code; bump MMCA.Common to v1.216.0 (#233)] +> - `devops-testing`: MMCA.ADC/.github/workflows/cross-service-tests.yml: 1 commit(s) [9b423f97 2026-10-01 Conference-day hardening, tighter deploy gates, shared Common code; bump MMCA.Common to v1.216.0 (#233)] | MMCA.ADC/.github/workflows/deploy.yml: 3 commit(s) [024e4a98 2026-10-01 Adopt MMCA.Common 1.218.1 (remediation wave) (#236); 2140ebd1 2026-10-01 chore(deps): Bump the github-actions group across 1 directory with 4 updates (#231)] | MMCA.ADC/.github/workflows/e2e.yml: 1 commit(s) [9b423f97 2026-10-01 Conference-day hardening, tighter deploy gates, shared Common code; bump MMCA.Common to v1.216.0 (#233)] | MMCA.Common/.github/workflows/ci.yml: 2 commit(s) [1f4df71f 2026-10-01 docs: fix stale source comments found by the 2026-10-01 ADR audit (#468); 0439838b 2026-10-01 feat: auth-cache hardening, tighter gates, shared consumer code (v1.216.0) (#463)] | test-count drift: MMCA.Common.Shared.Tests chapter 58 vs inventory 60; MMCA.Common.Domain.Tests chapter 62 vs inventory 64; MMCA.Common.Application.Tests chapter 398 vs inventory 401; MMCA.Common.Infrastructure.Tests chapter 499 vs inventory 514; MMCA.Common.API.Tests chapter 155 vs inventory 170; MMCA.Common.Grpc.Tests chapter 16 vs inventory 18; MMCA.Common.UI.Tests chapter 152 vs inventory 170; MMCA.Common.UI.Web.Tests chapter 12 vs inventory 24; MMCA.Common.Aspire.Tests chapter 50 vs inventory 52; MMCA.Common.Architecture.Tests chapter 230 vs inventory 254; MMCA.Common.UI.E2E.Tests chapter 20 vs inventory 21; MMCA.ADC.Identity.Shared.Tests chapter 2 vs inventory 3; MMCA.ADC.Conference.Application.Tests chapter 193 vs inventory 196; MMCA.ADC.Conference.UI.Tests chapter 66 vs inventory 81; MMCA.ADC.Engagement.Application.Tests chapter 65 vs inventory 70; MMCA.ADC.Architecture.Tests chapter 56 vs inventory 57; MMCA.ADC.Services.Tests chapter 7 vs inventory 17 +> - Coverage gaps (operational files no chapter mentions): MMCA.Common/.github/workflows/load-tests.yml, MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Fixtures/observability-main.bicep. `load-tests.yml` is now covered in `devops-testing`; `observability-main.bicep` is an architecture-test fixture, not an operational file, and stays uncovered by design. +> - **CONCEPT-MAPS.md:** 1 mechanical mismatch(es) corrected. The flagged number ("four AI packages" vs the 22-package total) was a checker false positive and stands (FACTS.md lists exactly four AI packages). Non-numeric drift fixed: map 8 gains the per-engine `IDataSourceEngine` strategy and the PostgreSQL shim (ADR-130), map 9 moves session issuance and rotation to `AuthSessionIssuer`, map 11 adds the same-origin proxy. +> - **Verification:** `verify.ps1`: **0 missing**, rubric **34/34**, 0 rollup, 0 citation (7,971 full-path cites), 0 anchor (25,310 links), and the new short-cite check 0. `verify.ps1` gained check 5 (short-form `File.cs:line` past end-of-file, exit bit 16) with a `test-verify.ps1` negative case (38/38 pass). +> - **Governance event:** a classifier rule, no new group. The new `MMCA.Common.LoadTests` assembly (14 types, e.g. `MMCA.Common/Tests/Performance/MMCA.Common.LoadTests/OutboxThroughputLoadTests.cs:20`) routes to G25 Testing & Quality Infrastructure beside `MMCA.Common.Benchmarks` (user-approved 2026-10-01; `Tools/invtool/classify.ps1:268`). + --- ## 2. Exceptions log (every deliberate omission, with reason) @@ -1395,4 +1445,4 @@ pwsh -File fixanchors2.ps1 # conservative cross-link repair (unique-heading-toke pwsh -File verify.ps1 # 0-missing coverage check + rubric 34/34 check ``` Then copy the refreshed `out/00-inventory.md` and `out/00-dependency-manifest.md` into -`Docs/Onboarding/` (the `00-group-taxonomy.md` is written there directly by `classify.ps1`). \ No newline at end of file +`Docs/Onboarding/` (the `00-group-taxonomy.md` is written there directly by `classify.ps1`). diff --git a/docs-src/onboarding/CONCEPT-MAPS.md b/docs-src/onboarding/CONCEPT-MAPS.md index 5f77c3fd..4fff7332 100644 --- a/docs-src/onboarding/CONCEPT-MAPS.md +++ b/docs-src/onboarding/CONCEPT-MAPS.md @@ -475,7 +475,12 @@ One sealed context class per engine (`SQLServerDbContext`, `PostgreSQLDbContext` `CosmosDbContext`) over the abstract `ApplicationDbContext`, **one instance per database**. Each entity is engine-agnostic; a single `[UseDataSource(engine)]` attribute on its config class picks SQL Server, PostgreSQL ([ADR-113](https://ivanball.github.io/docs/adr/113-postgresql-as-a-first-class-engine.html)), Cosmos, or SQLite. Cross-source relationships auto-degrade; the outbox -is the cross-source consistency mechanism. Each service self-applies its EF migrations at boot +is the cross-source consistency mechanism. Engine facts (relational or not, migrations, row-version +strategy, explicit-key inserts) come from one internal `IDataSourceEngine` per engine, looked up +through the static `DataSourceEngines.For(DataSource)` registry, so call sites read capabilities +instead of branching on the engine +([ADR-130](https://ivanball.github.io/docs/adr/130-per-engine-data-source-strategy.html)). Each +service self-applies its EF migrations at boot ([ADR-030](https://ivanball.github.io/docs/adr/030-startup-sole-migrator.html)). ```mermaid @@ -487,10 +492,13 @@ flowchart TD subgraph SHIMS["Engine shim base classes (one token = one engine)"] SQL["…SQLServer<T,Id>
(all prod configs today)"] + PG["...PostgreSQL<T,Id>
(first-class engine, ADR-113, staged)"] COS["…Cosmos<T,Id>
(shipped + tested, staged)"] LITE["…Sqlite<T,Id>
(fast integration tests, staged)"] end + ENG["DataSourceEngines.For: one internal IDataSourceEngine
per engine, capabilities not branches (ADR-130)"] + subgraph DBS["Database-per-service (ADR-006)"] CTX1["SQLServerDbContext instance: Conference DB + outbox"] CTX2["SQLServerDbContext instance: Engagement DB + outbox"] @@ -502,13 +510,15 @@ flowchart TD MIG["Startup sole-migrator: DatabaseInitStrategy=Migrate (ADR-030)"] ENTITY --> CFG --> ATTR --> REG - REG --> SQL & COS & LITE + REG --> SQL & PG & COS & LITE SQL --> DBS DBS -->|"FK dropped across sources → batch loaders"| XSPEC MIG -.-> DBS + ENG -.->|"engine facts"| REG + ENG -.-> MIG classDef p fill:#fef7e0,stroke:#f9ab00,color:#111 - class ENTITY,CFG,ATTR,REG,XSPEC,MIG p + class ENTITY,CFG,ATTR,REG,XSPEC,MIG,ENG p ``` --- @@ -524,11 +534,11 @@ flowchart TD subgraph AUTHN["Authentication"] JWT["JWT bearer validation"] JWKS["JWKS discovery + fallback fetch (ADR-004)"] - COOKIE["HttpOnly session cookie + non-validating SSR scheme (ADR-022)"] + COOKIE["HttpOnly session cookie + non-validating SSR scheme (ADR-022);
ISessionCookieStore writes them server-side for the proxy (ADR-131)"] HASH["PBKDF2-HMAC-SHA512, 32-byte salt, 600k iters:
one algorithm, no legacy branch (ADR-102)"] LOGIN["ILoginProtectionService: lockout + per-IP cap (ADR-029)"] EXT["External OAuth: Google / GitHub behind a short-lived
ExternalLogin cookie + single-use code (ADR-036/043)"] - ROT["AuthenticationServiceBase: 15-min access token +
one refresh session per device, SHA-256 at rest,
rotated with reuse detection (ADR-097);
ITokenRefresher per head (ADR-051)"] + ROT["AuthenticationServiceBase decides who is signed in;
IAuthSessionIssuer: 15-min access token +
one refresh session per device, SHA-256 at rest,
rotated with reuse detection (ADR-097);
ITokenRefresher per head (ADR-051)"] RESET["Forgot/reset password: cache-backed single-use
hashed token + email leg (ADR-091)"] end @@ -612,7 +622,11 @@ time ([ADR-042](https://ivanball.github.io/docs/adr/042-device-capability-abstra G26). Culture cookie + `IStringLocalizer` drive i18n ([ADR-027](https://ivanball.github.io/docs/adr/027-multi-locale-i18n.html)); `ThemeService` drives day/dark ([ADR-028](https://ivanball.github.io/docs/adr/028-dark-theme-mode.html)). Each module's UI plugs into one shared shell by contributing navigation and routes through `IUIModule` -([ADR-067](https://ivanball.github.io/docs/adr/067-ui-module-shell-composition.html)). +([ADR-067](https://ivanball.github.io/docs/adr/067-ui-module-shell-composition.html)). A Blazor +Web host can opt in to the same-origin API proxy in `MMCA.Common.UI.Web`, which serves `/api/**` on +the UI host's own origin and forwards to the gateway through YARP with the bearer read from the +HttpOnly session cookie +([ADR-131](https://ivanball.github.io/docs/adr/131-same-origin-api-proxy.html)). ```mermaid flowchart TD @@ -638,6 +652,7 @@ flowchart TD CSP["Security headers + pluggable CSP (ADR-023)"] RM["Render mode: InteractiveAuto on the root router,
prerender on (ADR-056)"] CAP["Device capability contracts in MMCA.Common.UI:
browser-JS, inert fallback and MAUI-native adapters
chosen per host at DI time (ADR-042, G26)"] + BFF["Opt-in same-origin API proxy, MMCA.Common.UI.Web:
/api/** forwarded to the gateway via YARP,
bearer from the HttpOnly cookie (ADR-131, G15)"] PAGE --> COMMONUI COMMONUI --> WEB @@ -652,9 +667,10 @@ flowchart TD COMMONUI --> CAP CAP -.->|"browser + fallback adapters"| WEB CAP -.->|"MMCA.Common.UI.Maui adapters"| MAUI + BFF -.->|"browser API calls"| WEB classDef u fill:#f3e8fd,stroke:#a142f4,color:#111 - class PAGE,COMMONUI,SHELL,WEB,MAUI,CAP u + class PAGE,COMMONUI,SHELL,WEB,MAUI,CAP,BFF u ``` --- diff --git a/docs-src/onboarding/devops-aspire.md b/docs-src/onboarding/devops-aspire.md index 3ff6d114..f533eed0 100644 --- a/docs-src/onboarding/devops-aspire.md +++ b/docs-src/onboarding/devops-aspire.md @@ -97,8 +97,8 @@ touches another service's database; no service races for another service's outbo legacy single `AtlDevCon` database is deliberately not provisioned here, and it is gone in Azure too. It was exported to the bacpac blob `sql-archive/AtlDevCon-20260902.bacpac` and dropped on 2026-09-02, so the template no longer declares it and the blob, restorable with `az sql db import`, is the rollback -source of record (`MMCA.ADC/infra/main.bicep:871-880`). These four databases are the entire application -data estate (main.bicep:885-897). Database-per-service is the topology everywhere, not a +source of record (`MMCA.ADC/infra/main.bicep:890-899`). These four databases are the entire application +data estate (main.bicep:904-916). Database-per-service is the topology everywhere, not a local-development shortcut. **Redis** is also persistent (Program.cs:44-45), used by service hosts for distributed output caching @@ -256,8 +256,8 @@ With no top-level connection string present, the one database a host declares th check all resolve from it, and the logical name collapses onto Default: one `SQLServerDbContext` instance, one EF change tracker, one migrations set per service. The Azure side matches exactly: the Bicep hands Identity only `DataSources__Identity__SQLServerConnectionString` -(`MMCA.ADC/infra/main.bicep:1665`) plus its migrations assembly (main.bicep:1666) and -`Outbox__DatabaseName` (main.bicep:1667), with no top-level `ConnectionStrings__SQLServerConnectionString` +(`MMCA.ADC/infra/main.bicep:1686`) plus its migrations assembly (main.bicep:1687) and +`Outbox__DatabaseName` (main.bicep:1688), with no top-level `ConnectionStrings__SQLServerConnectionString` anywhere. The `WaitFor(database)` (Extensions.cs:492) ensures the service process does not start until SQL Server is healthy. @@ -381,7 +381,7 @@ timeouts and retry budget are **re-exposed** from `HttpResilienceDefaults` so th drift from the outbound-HTTP path (`Core/MMCA.Common.Shared/Resilience/GrpcResilienceDefaults.cs:15-24`), while its circuit-breaker shape is stated explicitly (failure ratio 0.5 at line 27, minimum throughput 10 at line 30, break duration 10 seconds at line 33) because an east-west gRPC call addresses a peer -directly and bypasses the gateway's active health checks, so the breaker is the only thing that notices +directly and bypasses the gateway's destination health checks, so the breaker is the only thing that notices a peer going bad. The h2c choice is not a shortcut: Aspire's endpoint discovery does not reliably produce a `services__{name}__https__0` key for project resources, so the resolver falls back to `http` regardless of the requested scheme, and the target must therefore serve HTTP/2 on its cleartext endpoint @@ -453,7 +453,7 @@ The issuer Identity stamps into every token it signs has to be the same URL its or every cross-service call fails validation on the issuer claim. The `appsettings` default hardcodes the pinned dev port for a standalone F5 run, so deriving the value from the gateway resource means an Aspire run cannot mint wrong-issuer tokens if that port ever moves (Program.cs:368-374). Production sets -the equivalent from the gateway's ACA FQDN (`MMCA.ADC/infra/main.bicep:1690`). +the equivalent from the gateway's ACA FQDN (`MMCA.ADC/infra/main.bicep:1711`). [Rubric §11, Security] assesses how credentials and tokens flow through the system. No symmetric JWT secret is shared between services; each non-Identity service fetches the RSA public key dynamically. @@ -486,7 +486,7 @@ Three environment-derived values are then pushed back onto other resources, all AppHost knows: - `Api__ApiEndpoint` and `Api__WasmApiEndpoint` on the UI (Program.cs:450-451). These are both halves of - the split `infra/main.bicep:2479` and `2481` make in production, derived from the gateway resource + the split `infra/main.bicep:2516` and `2518` make in production, derived from the gateway resource instead of hardcoded. The WASM one is what `/client-config` hands the **browser** (and what the Blazor CSP pins `connect-src` to), so it must be an externally reachable URL: a browser cannot resolve an Aspire service-discovery name. The server-side one is overridden here because exactly one consumer is @@ -572,8 +572,8 @@ configuration differs. owns. A non-integer probe port throws at startup rather than silently producing no listener, since the platform would then probe a closed port and the revision would never come up (KestrelEndpointExtensions.cs:72-76). In Azure the key is `8081` on Identity, Conference and - Engagement (main.bicep:1661, 1886, 2024) and `8082` on Notification (main.bicep:2174), because the - [ADR-012](https://ivanball.github.io/docs/adr/012-grpc-host-transport.html) mixed profile already owns 8080 for ingress and 8081 for gRPC there (main.bicep:2134-2139, 2232-2233). + Engagement (main.bicep:1682, 1909, 2049) and `8082` on Notification (main.bicep:2201), because the + [ADR-012](https://ivanball.github.io/docs/adr/012-grpc-host-transport.html) mixed profile already owns 8080 for ingress and 8081 for gRPC there (main.bicep:2159-2164, 2259-2260). Locally the key is absent on every host, which is exactly why the AppHost has to use the h2c probe instead. - **`AddCommonKeyVaultConfiguration()`** @@ -780,7 +780,7 @@ additions stand out: 2. **`OutboxPollFilterProcessor`** is added to the tracing pipeline (Common.Aspire/Extensions.cs:246) before the exporters, so its `OnEnd` runs first (comment at Extensions.cs:241-245). -3. **Four cost knobs** ([Rubric §31, Cost and FinOps]), three off by default and one on. +3. **Five cost knobs** ([Rubric §31, Cost and FinOps]), four off by default and one on. `Telemetry:DisableHttpClientMetrics` (Extensions.cs:148-169) and `Telemetry:DisableRuntimeMetrics` (Extensions.cs:175-188) each drop a metric family. Both branches are more than "skip the instrumentation call", and the comments explain why (Extensions.cs:150-159 and 177-179): a deployed @@ -797,15 +797,27 @@ additions stand out: (`TryGetTraceSampleRatio`, Extensions.cs:448-461; the same defensive shape in `IsInstrumentationDisabled`, Extensions.cs:471-472). The fourth, `Telemetry:FilterProbeTelemetry`, is the one that defaults **on** and is covered on its own below - (`IsProbeTelemetryFilterEnabled`, Extensions.cs:483-484). ADC's production Bicep sets the first three - explicitly: `Telemetry__TracesSampleRatio=0.25` (`MMCA.ADC/infra/main.bicep:252-255`), - `Telemetry__DisableHttpClientMetrics=true` (main.bicep:284-287) and - `Telemetry__DisableRuntimeMetrics=true` (main.bicep:288-291), the last two measured at roughly 65% of - AppMetrics ingestion, about 290 MB of a 500 MB daily stream (main.bicep:278-283). It also stretches - the OTel export cadence with the standard `OTEL_METRIC_EXPORT_INTERVAL=300000` (main.bicep:299-302): - the exporter ships cumulative aggregates, so a 5x longer interval drops roughly 80% of the remaining - datapoints while five-minute alert windows keep the same signal (main.bicep:293-298). An unset host - keeps every metric family, samples every trace and exports on the SDK's 60 second default. + (`IsProbeTelemetryFilterEnabled`, Extensions.cs:483-484). A fifth, + `Telemetry:DisableAspNetCoreMetrics`, drops the ASP.NET Core meter family (`http.server.*`, + `kestrel.*`, `aspnetcore.*`, `signalr.server.*`) with the same whole-provider `Drop` View, matched on + the `Microsoft.AspNetCore.` meter-name prefix, for the same reason: the distro adds those meters on its + own (`ConfigureAspNetCoreMetrics`, `MMCA.Common.Aspire/Extensions.Telemetry.cs:344-370`). ADC's + production Bicep sets four of the five explicitly: `Telemetry__TracesSampleRatio=0.25` + (`MMCA.ADC/infra/main.bicep:252-255`), `Telemetry__DisableHttpClientMetrics=true` (main.bicep:284-287) + and `Telemetry__DisableRuntimeMetrics=true` (main.bicep:288-291), those two measured at roughly 65% of + AppMetrics ingestion, about 290 MB of a 500 MB daily stream (main.bicep:278-283), and + `Telemetry__DisableAspNetCoreMetrics=true` (main.bicep:297-300), whose family was 73% of workspace + ingestion over 2026-09-22..28 with no alert reading it, since request latency and failures alert off + `AppRequests` (main.bicep:293-296). Alongside them it sets the distro's own + `AzureMonitor__EnableLiveMetrics=false` (main.bicep:307-310): Live Metrics is on by default and keeps + every replica talking to the Live Metrics service with nobody watching, which held every app just + above the Container Apps idle line so that 64% of vCPU-seconds billed at the active rate + (main.bicep:302-306). All six container apps carry both entries (for example main.bicep:1675-1676 + on Identity and 2371-2372 on the gateway). It also stretches the OTel export cadence with the + standard `OTEL_METRIC_EXPORT_INTERVAL=300000` (main.bicep:318-321): the exporter ships cumulative + aggregates, so a 5x longer interval drops roughly 80% of the remaining datapoints while five-minute + alert windows keep the same signal (main.bicep:312-317). An unset host keeps every metric family, + samples every trace and exports on the SDK's 60 second default. ### `MapDefaultEndpoints` @@ -901,7 +913,7 @@ that does not take the persistence stack (OutboxPollFilterProcessor.cs:17-22). [Rubric §31, Cost and FinOps] assesses whether observability costs are controlled. Suppressing poll spans on a 300 s polling interval in production (`Outbox__PollingIntervalSeconds=300` on all four ADC -container apps, `MMCA.ADC/infra/main.bicep:1672, 1893, 2031, 2183`) eliminates the majority of +container apps, `MMCA.ADC/infra/main.bicep:1693, 1916, 2056, 2210`) eliminates the majority of idle-process telemetry ingestion. The framework makes this the default for every consumer; individual services do not need to configure it. @@ -1119,7 +1131,7 @@ the same base images. None build the AppHost: it is a local-only orchestration a ### Common structure **Stage `base`** (first `FROM` in all six): `mcr.microsoft.com/dotnet/aspnet:10.0` **pinned by digest** -(`@sha256:1fe8...1497`, Gateway.Dockerfile:4), with `WORKDIR /app` and `EXPOSE 8080` / `EXPOSE 8081` +(`@sha256:2d58...746f`, Gateway.Dockerfile:4), with `WORKDIR /app` and `EXPOSE 8080` / `EXPOSE 8081` (Gateway.Dockerfile:5-7). This is the runtime-only image; it has no SDK tools, minimizing the attack surface of the final image. The digest is the point of that `FROM` line: the tag still names the channel, but the digest is what resolves, so a re-tagged `:10.0` cannot silently change the runtime under a @@ -1219,35 +1231,35 @@ mode (lines 30-32) and publishes it with ReadyToRun (lines 51-53) independently; [Rubric §17, DevOps and Deployment] continues: having one Dockerfile per deployable means each image is independently versioned and deployed. CI declares all six as a six-way parallel matrix -(`.github/workflows/deploy.yml:1186-1207`), gated as a whole on the `changes` job classifying the diff as -code (`deploy.yml:1183`), and each leg additionally carries a `changed` column from that job's per-image -dirty map (`deploy.yml:1192, 1195, 1198, 1201, 1204, 1207`). A leg whose image is clean skips the build and -push (`deploy.yml:1227-1228`) and re-tags the last `:latest` to this sha instead -(`deploy.yml:1261-1274`), a registry-side `az acr import` manifest copy that pulls and pushes no layer -(deploy.yml:1257-1258), so it still concludes **success**: the comment above the job -(deploy.yml:1174-1178) records why that matters, since `deploy` gates on the job-level -`needs.build-images.result == 'success'` equality (`deploy.yml:1354`) and a skipped leg would turn the +(`.github/workflows/deploy.yml:1095-1116`), gated as a whole on the `changes` job classifying the diff as +code (`deploy.yml:1086`), and each leg additionally carries a `changed` column from that job's per-image +dirty map (`deploy.yml:1101, 1104, 1107, 1110, 1113, 1116`). A leg whose image is clean skips the build and +push (`deploy.yml:1136-1137`) and re-tags the last `:latest` to this sha instead +(`deploy.yml:1170-1183`), a registry-side `az acr import` manifest copy that pulls and pushes no layer +(deploy.yml:1166-1167), so it still concludes **success**: the comment above the job +(deploy.yml:1077-1081) records why that matters, since `deploy` gates on the job-level +`needs.build-images.result == 'success'` equality (`deploy.yml:1263`) and a skipped leg would turn the whole job `skipped` and silently cancel the deploy. The `UseAppHost=false` publish flag strips the native executable wrapper; the Docker entrypoint invokes the DLL directly via the already-present runtime in the base image. -The same clean leg then copies the fresh sha tag back onto `:latest` (deploy.yml:1283-1287). That +The same clean leg then copies the fresh sha tag back onto `:latest` (deploy.yml:1192-1196). That second copy exists because of the registry's own housekeeping: the daily ACR purge task keeps only the three most recently updated tags per repository and deletes anything older than three days, and minting a sha tag never touches `:latest`, so after a few deploys that leave an image clean the very tag the re-tag reads from would age out, get purged, and fail the next clean leg, which blocks the whole deploy -through the same `success` equality (deploy.yml:1275-1282). Copying the identical digest moves only the +through the same `success` equality (deploy.yml:1184-1191). Copying the identical digest moves only the tag's timestamp; the build-and-push leg already pushes `:latest` on every build, so both legs keep it alive. Each leg ends with a **container-image CVE scan** of the exact tag this deploy will roll out: Trivy against `{acr}/{image}:{sha}`, table format, `CRITICAL,HIGH`, `ignore-unfixed: true`, `exit-code: 1` -(deploy.yml:1297-1305). It exists because the supply-chain job's CycloneDX SBOM and +(deploy.yml:1206-1214). It exists because the supply-chain job's CycloneDX SBOM and `dotnet list --vulnerable` cover the managed NuGet graph only, so nothing else inspects the base layer of -any of the six images (deploy.yml:1289-1291). It is **non-gating** (`continue-on-error: true`, -deploy.yml:1298): findings are printed in the step log rather than reddening every deploy. Note the +any of the six images (deploy.yml:1198-1200). It is **non-gating** (`continue-on-error: true`, +deploy.yml:1207): findings are printed in the step log rather than reddening every deploy. Note the honest residue, the comment justifies that choice by the images building on a *floating* aspnet base -(deploy.yml:1293-1296), while the six Dockerfiles now pin that base by digest +(deploy.yml:1202-1205), while the six Dockerfiles now pin that base by digest (Gateway.Dockerfile:4): the stated precondition for flipping `continue-on-error` to `false` is met on the pinning half, and the step is still non-gating. @@ -1266,18 +1278,18 @@ table below cross-references the local resource with its Azure equivalent: | Local (AppHost) | Azure (Bicep) | |---|---| -| SQL Server container (persistent) | Azure SQL Server; the same four databases (`ADC_Identity`, `ADC_Conference`, `ADC_Engagement`, `ADC_Notification`, main.bicep:892-897), each Basic 5 DTU / 2 GB (main.bicep:907-915) and carrying a `service` tag equal to the owning container app's, so one tag-grouped cost report splits compute and storage per service (main.bicep:170-174, 903-905), with weekly/monthly/yearly long-term retention on top of Basic-tier PITR (main.bicep:919-935). The three databases that hold an audit-trail table (`ADC_Identity`, `ADC_Conference`, `ADC_Engagement`; Notification has no audited entities) also get a database-level audit policy recording every `UPDATE` or `DELETE` on `dbo.AuditTrailEntries` outside the database (main.bicep:962-966, 984-1001), because the services connect with the server admin login and could otherwise rewrite the [ADR-075](https://ivanball.github.io/docs/adr/075-audit-trail.html) trail without a record (main.bicep:940-948). The legacy `AtlDevCon` database is declared in neither place (main.bicep:871-880) | -| Redis container (persistent) | Azure Managed Redis (`Microsoft.Cache/redisEnterprise`, Balanced B0, no HA, main.bicep:1364-1375), OSS-cluster policy and volatile-LRU eviction (main.bicep:1383, 1386), injected as `ConnectionStrings__redis` from Key Vault (main.bicep:1687) | -| RabbitMQ container (persistent, management plugin), or the Service Bus emulator container under `ADC_BROKER=servicebus` | Azure Service Bus (Standard tier, main.bicep:1016-1027; Basic lacks the topics MassTransit needs, main.bicep:1011-1012), with one per-service SAS rule rather than one credential for the whole fleet (main.bicep:1029-1031) | -| MailDev container (fixed ports 1080/1025) | Not provisioned; a real SMTP relay via `Smtp__Host` / `Smtp__Port` / `Smtp__From` (main.bicep:1733 for Identity, 2211 for Notification) | -| `MessageBus__Provider=RabbitMq` (AppHost default) | `MessageBus__Provider=AzureServiceBus` (Bicep env var on all four services, main.bicep:1712, 1920, 2060, 2209) | +| SQL Server container (persistent) | Azure SQL Server; the same four databases (`ADC_Identity`, `ADC_Conference`, `ADC_Engagement`, `ADC_Notification`, main.bicep:911-916), each Basic 5 DTU / 2 GB (main.bicep:926-934) and carrying a `service` tag equal to the owning container app's, so one tag-grouped cost report splits compute and storage per service (main.bicep:170-174, 922-924), with weekly/monthly/yearly long-term retention on top of Basic-tier PITR (main.bicep:938-954). The three databases that hold an audit-trail table (`ADC_Identity`, `ADC_Conference`, `ADC_Engagement`; Notification has no audited entities) also get a database-level audit policy recording every `UPDATE` or `DELETE` on `dbo.AuditTrailEntries` outside the database (main.bicep:981-985, 1003-1020), because the services connect with the server admin login and could otherwise rewrite the [ADR-075](https://ivanball.github.io/docs/adr/075-audit-trail.html) trail without a record (main.bicep:959-967). The legacy `AtlDevCon` database is declared in neither place (main.bicep:890-899) | +| Redis container (persistent) | Azure Managed Redis (`Microsoft.Cache/redisEnterprise`, Balanced B0, no HA, main.bicep:1383-1394), OSS-cluster policy and volatile-LRU eviction (main.bicep:1402, 1405), injected as `ConnectionStrings__redis` from Key Vault (main.bicep:1708) | +| RabbitMQ container (persistent, management plugin), or the Service Bus emulator container under `ADC_BROKER=servicebus` | Azure Service Bus (Standard tier, main.bicep:1035-1046; Basic lacks the topics MassTransit needs, main.bicep:1030-1031), with one per-service SAS rule rather than one credential for the whole fleet (main.bicep:1048-1050) | +| MailDev container (fixed ports 1080/1025) | Not provisioned; a real SMTP relay via `Smtp__Host` / `Smtp__Port` / `Smtp__From` (main.bicep:1754 for Identity, 2238 for Notification) | +| `MessageBus__Provider=RabbitMq` (AppHost default) | `MessageBus__Provider=AzureServiceBus` (Bicep env var on all four services, main.bicep:1733, 1943, 2085, 2236) | | Aspire dashboard (OTLP) | Application Insights workspace-based resource (`APPLICATIONINSIGHTS_CONNECTION_STRING`, main.bicep:243-246) | -| `WithSQLServerDataSource` injects one connection-string env var | Bicep injects the same one plus `DataSources__{Module}__SQLServerMigrationsAssembly` and `Outbox__DatabaseName` (main.bicep:1665-1667) | -| h2c health probe from the AppHost (`WithH2cHealthCheck`) | Dedicated HTTP/1.1 probe listener via `HealthProbe__Port`, 8081 on the three h2c services (main.bicep:1661, 1886, 2024) and 8082 on Notification (main.bicep:2174), because the ACA platform probes speak HTTP/1.1 | -| `WaitFor` gates on `/alive`; only the UI gates on `/health/ready` | The ACA probe block splits the same two paths by job: startup and liveness on `/alive`, readiness on `/health/ready`, all three against the probe port (main.bicep:1794-1818). Readiness polls every 30 s rather than 10 s, because the DB-aware check issues a `SELECT 1` per probe and neither the probe request nor its dependency row is sampled (main.bicep:1788-1793) | -| Outbox poll interval: framework default 2 s | `Outbox__PollingIntervalSeconds=300` on every service (main.bicep:1672, 1893, 2031, 2183); Identity, Conference and Engagement, the three hosts that call `AddScheduledJobs`, also slow the runner's idle wake to `Scheduler__PollingIntervalSeconds=300` (main.bicep:1683, 1901, 2034). Internal commands (ADR-114) deliberately poll faster at `InternalCommands__PollingIntervalSeconds=60` (main.bicep:1678), because only a row enrolled in a transaction cannot be signalled | -| Telemetry knobs at their defaults (sample everything, all metric families on, probe traces filtered) | `Telemetry__TracesSampleRatio=0.25`, `Telemetry__DisableHttpClientMetrics=true`, `Telemetry__DisableRuntimeMetrics=true`, `OTEL_METRIC_EXPORT_INTERVAL=300000` (main.bicep:252-255, 284-291, 299-302). `Telemetry__FilterProbeTelemetry` is set nowhere, because its default is already the production behavior | -| Gateway: YARP request-routing lines at `Information` (Gateway/appsettings.json:6) | `Logging__LogLevel__Yarp=Warning` on the gateway container and no other (main.bicep:267-276, 2341), because YARP's two lines per proxied request duplicated `AppRequests` and `AppDependencies` in the console-log stream; `Warning` keeps the forwarder's error lines | +| `WithSQLServerDataSource` injects one connection-string env var | Bicep injects the same one plus `DataSources__{Module}__SQLServerMigrationsAssembly` and `Outbox__DatabaseName` (main.bicep:1686-1688) | +| h2c health probe from the AppHost (`WithH2cHealthCheck`) | Dedicated HTTP/1.1 probe listener via `HealthProbe__Port`, 8081 on the three h2c services (main.bicep:1682, 1909, 2049) and 8082 on Notification (main.bicep:2201), because the ACA platform probes speak HTTP/1.1 | +| `WaitFor` gates on `/alive`; only the UI gates on `/health/ready` | The ACA probe block splits the same two paths by job: startup and liveness on `/alive`, readiness on `/health/ready`, all three against the probe port (main.bicep:1815-1839). Readiness polls every 30 s rather than 10 s, because the DB-aware check issues a `SELECT 1` per probe and neither the probe request nor its dependency row is sampled (main.bicep:1809-1814) | +| Outbox poll interval: framework default 2 s | `Outbox__PollingIntervalSeconds=300` on every service (main.bicep:1693, 1916, 2056, 2210); Identity, Conference and Engagement, the three hosts that call `AddScheduledJobs`, also slow the runner's idle wake to `Scheduler__PollingIntervalSeconds=300` (main.bicep:1704, 1924, 2059). Internal commands (ADR-114) deliberately poll faster at `InternalCommands__PollingIntervalSeconds=60` (main.bicep:1699), because only a row enrolled in a transaction cannot be signalled | +| Telemetry knobs at their defaults (sample everything, all metric families on, probe traces filtered) | `Telemetry__TracesSampleRatio=0.25`, `Telemetry__DisableHttpClientMetrics=true`, `Telemetry__DisableRuntimeMetrics=true`, `Telemetry__DisableAspNetCoreMetrics=true`, `OTEL_METRIC_EXPORT_INTERVAL=300000`, plus the distro switch `AzureMonitor__EnableLiveMetrics=false` (main.bicep:252-255, 284-291, 297-300, 307-310, 318-321). `Telemetry__FilterProbeTelemetry` is set nowhere, because its default is already the production behavior | +| Gateway: YARP request-routing lines at `Information` (Gateway/appsettings.json:6) | `Logging__LogLevel__Yarp=Warning` on the gateway container and no other (main.bicep:267-276, 2368), because YARP's two lines per proxied request duplicated `AppRequests` and `AppDependencies` in the console-log stream; `Warning` keeps the forwarder's error lines | The transport switch (`RabbitMq` to `AzureServiceBus`) is entirely environment-driven. No code path changes between local and production: the same `AddBrokerMessaging(configuration)` call in each @@ -1319,11 +1331,11 @@ cluster, so adding or repointing a route is an appsettings edit plus a matching ### The route table -`Gateway/appsettings.json:80-260` declares **33 routes across 5 clusters** (clusters at -appsettings.json:262-300), every destination an in-cluster service-discovery name over cleartext, never a +`Gateway/appsettings.json:78-258` declares **33 routes across 5 clusters** (clusters at +appsettings.json:260-298), every destination an in-cluster service-discovery name over cleartext, never a public URL: -- Eight Identity routes to cluster `identity` (`http://identity`, appsettings.json:90-135, 263-271): +- Eight Identity routes to cluster `identity` (`http://identity`, appsettings.json:88-133, 261-269): `/Auth/login` and `/Auth/register` (90-103), the `/Auth/{**catch-all}` that carries everything else (106-110), `/Users` (111-115), `/UserClaims` (116-120), `/Admin/Users` (121-125), `/Admin/Roles` (126-130) and `/.well-known/*` (131-135). Only the two credential-submission routes carry @@ -1333,16 +1345,16 @@ public URL: `/CategoryItems`, `/SessionSpeakers`, `/EventSpeakers`, `/SessionCategoryItems`, `/SessionQuestionAnswers`, `/EventQuestionAnswers`, `/SpeakerCategoryItems`, `/SessionSelection`, `/Questions`, `/Sponsors`, `/Partners`, `/Activities`, `/SessionAssets`) to cluster `conference` - (appsettings.json:136-225, 272-280). `/Partners` is the newest of them - (`conference-partners`, appsettings.json:211-215), and it is the shape every new aggregate takes at the + (appsettings.json:134-223, 270-278). `/Partners` is the newest of them + (`conference-partners`, appsettings.json:209-213), and it is the shape every new aggregate takes at the edge: one prefix route, `"AuthorizationPolicy": "anonymous"`, the same `conference` cluster, no forwarder config of its own, and a matching row in the test that pins the table (`Tests/Hosts/MMCA.ADC.Gateway.Tests/RouteMapTests.cs:162`). Nothing in the gateway image or its `Program.cs` changed to carry it. - Five Engagement prefixes (`/Bookmarks`, `/CheckIns`, `/LivePolls`, `/Points`, `/SessionQuestions`) to - cluster `engagement` (appsettings.json:226-250, 281-289). + cluster `engagement` (appsettings.json:224-248, 279-287). - `/Notifications` to cluster `notification-rest` and `/hubs/*` to cluster `notification-hub` - (appsettings.json:251-260, 290-299). Both point at the same `http://notification` destination; they are + (appsettings.json:249-258, 288-297). Both point at the same `http://notification` destination; they are two clusters because a cluster is what carries the forwarder request config, and these two need different ones. @@ -1354,13 +1366,13 @@ catch-all, which also matched `/Auth/refresh`, and the UI container calls the ga the ClientIp partition keyed every user's refresh on the UI replica's own container IP: one shared 30-per-minute bucket for the whole site. ADR-019 deliberately leaves `RefreshAsync` unthrottled at the service layer for exactly that shared-IP reason, and the gateway policy was silently undoing it -(appsettings.json:81-89). Login and register are split out and keep the tight policy; everything else +(appsettings.json:79-87). Login and register are split out and keep the tight policy; everything else under `/Auth` (refresh, logout, forgot/reset password, the OAuth callbacks) stays on the edge global -limiter alone (appsettings.json:104-105). +limiter alone (appsettings.json:102-103). **Every route states `"AuthorizationPolicy": "anonymous"`.** A proxied route carries no authorization metadata of its own, so the anonymous posture of the whole table used to be an absence rather than a -statement, with nothing for a reviewer or a fitness test to read (SEC-Common-16, appsettings.json:74-79, +statement, with nothing for a reviewer or a fitness test to read (SEC-Common-16, appsettings.json:72-77, Gateway/Program.cs:98-106). Declaring it per route is what makes a route added without a policy fail closed at the edge. `builder.Services.AddAuthorization()` (Program.cs:112) plus `app.UseAuthorization()` (Program.cs:218) evaluate it, with no `UseAuthentication` above them on purpose: the gateway @@ -1376,7 +1388,7 @@ comment enumerates them, Gateway/Program.cs:15-31). The `identity`, `conference` and `engagement` clusters each state `"Version": "2.0"` with `"VersionPolicy": "RequestVersionExact"` in their own `HttpRequest` block -(appsettings.json:267-270, 276-279, 285-288). Exact is load-bearing: on cleartext there is no ALPN to +(appsettings.json:265-268, 274-277, 283-286). Exact is load-bearing: on cleartext there is no ALPN to negotiate, so `RequestVersionOrLower` silently downgrades to HTTP/1.1 and the `Http2`-only backend rejects it. That pair stays per-cluster rather than moving into the shared defaults, because **which** clusters speak h2c is a per-cluster fact, not a default (Program.cs:42-43). @@ -1417,13 +1429,15 @@ the loaded table (Program.cs:132-145): switch to flip (Program.cs:45-47). 3. Passive destination health checks on every cluster that declares none, so YARP ejects a failing destination instead of continuing to balance onto it, and it is free: it watches the responses the - gateway is already forwarding (Program.cs:140-142, defaults at `GatewaySettings.cs:121-122`). ADC then - turns the **active** probe on in configuration, at a 30 second interval, with the path (`/alive`) and - timeout (5 s) coming from the package defaults (appsettings.json:58-62). The comment above it - (appsettings.json:52-57) explains why it pays for out-of-band traffic: passive checks only demote a - destination after real traffic has already failed against it, so a restarting service keeps absorbing - requests until enough of them error, whereas an active probe takes a destination out of rotation - before a client request lands on it and puts it back once it answers again. + gateway is already forwarding (Program.cs:140-142, defaults at `GatewaySettings.cs:121-122`). ADC + keeps the **active** probe explicitly **off** in configuration + (`MmcaGateway:HealthCheckDefaults:Active:Enabled = false`, appsettings.json:57-60), and the comment + above it (appsettings.json:52-56) gives two reasons. Every cluster fronts exactly one Container Apps + internal address, so an active probe could only ever eject the sole destination, turning a slow + request into a 503, while the platform already routes only to ready replicas. And each probe is an + HTTP request that bills an otherwise idle downstream replica at the Container Apps active rate, eight + times the idle rate ([Rubric §31, Cost and FinOps]). Passive checks cost no extra traffic, so they + are the only destination health signal the gateway runs. 4. `X-MMCA-Route` / `X-MMCA-Cluster` trace headers on every proxied request, with any inbound value stripped first so a downstream can trust them (`GatewaySettings.cs:181-184`), plus the named per-route rate-limiter policies routes reference by name. @@ -1464,7 +1478,7 @@ replica-wide concurrency ceiling of 200, and `/hubs` on the bypass list because long-lived and its negotiate/reconnect traffic must not be throttled (appsettings.json:21-25, defaults in `MMCA.Common.Aspire/Gateway/GatewayRateLimitingSettings.cs:59, 63, 73`); health probes and `/.well-known` are always exempt (Program.cs:69-73). The `auth-tight` route policy is -30 requests per 60 seconds partitioned on client IP with no queue (appsettings.json:64-70). +30 requests per 60 seconds partitioned on client IP with no queue (appsettings.json:62-68). One exemption is configured but inert until a secret arrives. `GatewayRateLimiting:TrustedCallerHeaderName` names the header (`X-Internal-Caller-Key`, appsettings.json:32) and **only** the name: the secret itself @@ -1479,9 +1493,9 @@ anonymous and unconditionally exempt from both limiters (which ADR-088 requires starve Container Apps' own probe), and without a cache that exemption turns one anonymous request into four live internal GETs against a 0.25 vCPU gateway. `MMCA.Common.Aspire` now serves both paths from a single-flight `CachedHealthReportProvider` whose window is `HealthChecks:CacheSeconds`, pinned to 10 here -(appsettings.json:40-41, rationale at Program.cs:90-96). Ten seconds is one third of the 30 second -readiness period on the gateway's Container Apps probe, so a real outage is still seen within one probe -interval (appsettings.json:34-39). +(appsettings.json:40-41, rationale at Program.cs:90-96). The Container Apps readiness probe does not read +this report (it targets `/alive`, see below), so its consumers are the availability web test and +operators, and ten seconds keeps a real outage visible almost immediately (appsettings.json:34-39). ### Readiness that reflects the edge's job @@ -1492,6 +1506,17 @@ through a service-discovery-resolved client, tagged `Ready` outage pulls the gateway out of the load balancer without ever failing liveness, because restarting the gateway fixes nothing about a downstream being down (Gateway/Program.cs:76-79). +Production does not point the platform's readiness probe at that aggregate. The gateway container app +probes `/alive` for startup, liveness **and** readiness, all on port 8080 (`MMCA.ADC/infra/main.bicep:2408-2434`), +and the comment on the readiness entry records why (main.bicep:2423-2429). `/health/ready` fans out to +every downstream's `/alive`, so a 30 second probe there was an HTTP request on every service every 30 +seconds, which alone keeps an idle replica off the Container Apps idle rate; and one failed downstream +marked the **only** gateway replica unready and took the whole site down, healthy routes included. The +full aggregate stays on `/health` for the availability web test and its alert. No cold-revision +protection is lost, because the gateway registers no JwtBearer scheme, so the OIDC metadata warm-up task +`AddServiceDefaults` would otherwise wait on has nothing to fetch. This is the gateway's exception to the +service pattern in the parity table, where readiness stays on `/health/ready`. + One call covers all four because the probe no longer needs to be told which protocol each downstream speaks. `DownstreamProbeVersion.Auto` is the default (GatewayHealthCheckExtensions.cs:59); it negotiates per downstream and latches the answer for the life of @@ -1499,13 +1524,13 @@ the process, so the three h2c REST services latch HTTP/2 on their first poll and `HTTP_1_1_REQUIRED` once and latches HTTP/1.1. The fallback is a one-time cost per downstream, not a per-poll one (Gateway/Program.cs:80-87). -That active probing has an observable cost, and the gateway's `appsettings.json` pays it down at the -logging layer rather than by probing less (appsettings.json:2-16): three categories (`Polly` retry +Whatever still probes that aggregate has an observable cost, and the gateway's `appsettings.json` pays it +down at the logging layer (appsettings.json:2-15): at `Information` three categories (`Polly` retry attempts, the per-request `System.Net.Http.HttpClient` logs for the `gateway-downstream-*` clients, and -`Yarp.ReverseProxy.Health`) wrote roughly 300k stdout lines a day for traffic no user made, so they sit -at `Warning` while YARP's own request-routing lines stay at `Information` (appsettings.json:6). Production -goes one step further and caps YARP too: the Bicep sets `Logging__LogLevel__Yarp=Warning` on the gateway -container and no other host (main.bicep:267-276, 2341), because at `Information` YARP writes two lines per +the YARP health monitor, `Yarp.ReverseProxy.Health`) write a line per probe for traffic no user made, so +they sit at `Warning` while YARP's own request-routing lines stay at `Information` (appsettings.json:6, +13). Production goes one step further and caps YARP too: the Bicep sets `Logging__LogLevel__Yarp=Warning` +on the gateway container and no other host (main.bicep:267-276, 2368), because at `Information` YARP writes two lines per proxied request that Container Apps ships to Log Analytics, measured at about 177k lines and 77 MB a week, the largest console-log stream in the workspace and all of it already recorded as `AppRequests` and `AppDependencies`. `Warning` keeps the forwarder's error lines, so a failed proxy hop still logs. @@ -1626,11 +1651,11 @@ exactly the runner the tier exists for (lines 58-66). It needs a Docker daemon and it is the slowest thing in the repo per assertion, so per [ADR-098](https://ivanball.github.io/docs/adr/098-aspire-orchestration-not-testing-or-dashboards.html) it is probational and non-gating. The `apphost-smoke` job in the nightly `cross-service-tests.yml` runs it with -`continue-on-error: true` and a 30-minute timeout (`.github/workflows/cross-service-tests.yml:204-209`), -trusts the dev certificate first (line 271), sets the `MMCA_APPHOST_TESTS: "1"` opt-in (line 285), and +`continue-on-error: true` and a 30-minute timeout (`.github/workflows/cross-service-tests.yml:208-213`), +trusts the dev certificate first (line 275), sets the `MMCA_APPHOST_TESTS: "1"` opt-in (line 289), and passes `--minimum-expected-tests 1` so a run where every test silently skipped cannot pass (lines -286-288). The `cross-service-freshness` deploy gate does not look at this job at all, so it can never block -a deploy (lines 194-199). +290-292). The `cross-service-freshness` deploy gate does not look at this job at all, so it can never block +a deploy (lines 198-203). Before building, the job also asserts that every `MMCA.Common.*` entry in the project's committed `packages.lock.json` resolves the single central pin (lines 229-265), because that out-of-solution lock is covered by no gating restore and a pin bump that forgot it once left it a version behind while the job stayed green (lines 230-233). It cannot simply restore `--locked-mode` like the two gating tiers above it (lines 96-99, 175-176): an Aspire AppHost lock carries a RID-specific `Aspire.Dashboard.Sdk.` entry, so locked mode fails with NU1004 on the Linux runner regardless of drift (lines 235-238). [Rubric §14, Testability and Test Strategy] assesses whether the test suite covers the risks the system actually carries. An orchestration file is a genuine failure surface with no compiler covering it, and the @@ -1649,10 +1674,10 @@ pretending it is cheap. | §12 Performance & Scalability | The ACA-tuned `SocketsHttpHandler` (Extensions.cs:85-93) and the OIDC metadata warm-up task, both aimed at Consumption-plan cold starts and idle-replica penalties; ReadyToRun publish on the five non-UI images | | §13 Observability & Operability | Dual OTLP / Azure Monitor export from one binary (Extensions.cs:361-378); seven MMCA.Common meters registered by literal name (Extensions.cs:199-205); probe-trace filtering that raises the signal ratio of `AppRequests` rather than only cutting volume | | §14 Testability & Test Strategy | `AdcAppHostFixture` plus `AdcAppHostSmokeTests`, one shared stack and one narrow claim per wiring contract against the one failure surface no compiler covers (AdcAppHostSmokeTests.cs:75-136), kept `continue-on-error` in the nightly per [ADR-098](https://ivanball.github.io/docs/adr/098-aspire-orchestration-not-testing-or-dashboards.html) | -| §17 DevOps & Deployment | Persistent container lifetimes shared by the inner loop and the Aspire-driven E2E CI run; one Dockerfile per deployable behind the six-way `build-images` matrix with per-image dirty gating (`deploy.yml:1186-1207`); the parity table's environment-driven local-to-cloud mapping | +| §17 DevOps & Deployment | Persistent container lifetimes shared by the inner loop and the Aspire-driven E2E CI run; one Dockerfile per deployable behind the six-way `build-images` matrix with per-image dirty gating (`deploy.yml:1095-1116`); the parity table's environment-driven local-to-cloud mapping | | §29 Resilience & Business Continuity | The liveness-versus-readiness split on every startup gate (Program.cs:324-343), including the gateway's `/alive` gate that avoids the readiness-aggregate wedge; `WithReference` without `WaitFor` on the four cycle-closing edges, absorbed by the gRPC resilience pipeline; `"optional"`-tagged dependency checks that keep a degradation partial | -| §31 Cost / FinOps | The four telemetry knobs and the metric export interval; `OutboxPollFilterProcessor` and `ProbeTelemetryFilterProcessor` suppressing the two highest-volume classes of span nobody asked for; the gateway's probe-log trim (Gateway/appsettings.json:2-16) and its production-only YARP log floor (main.bicep:267-276, 2341); the 30 s readiness cadence in the ACA probe block (main.bicep:1788-1793) | -| §32 Dependency & Supply-Chain | Digest-pinned `aspnet` and `sdk` base images in all six Dockerfiles (Gateway.Dockerfile:4, 10); `--locked-mode` restore in five of the six (Gateway.Dockerfile:32, the UI's documented exception at UI.Web.Dockerfile:36-38); the per-image Trivy scan of the exact tag being rolled out, non-gating today (`deploy.yml:1297-1305`) | +| §31 Cost / FinOps | The five telemetry knobs, the Live Metrics switch (main.bicep:307-310) and the metric export interval; `OutboxPollFilterProcessor` and `ProbeTelemetryFilterProcessor` suppressing the two highest-volume classes of span nobody asked for; the gateway's probe-log trim (Gateway/appsettings.json:2-16) and its production-only YARP log floor (main.bicep:267-276, 2368); the 30 s readiness cadence in the ACA probe block (main.bicep:1809-1814); the gateway's YARP active probe switched off (Gateway/appsettings.json:52-60) and its readiness probe on `/alive` rather than the downstream fan-out (main.bicep:2423-2432) | +| §32 Dependency & Supply-Chain | Digest-pinned `aspnet` and `sdk` base images in all six Dockerfiles (Gateway.Dockerfile:4, 10); `--locked-mode` restore in five of the six (Gateway.Dockerfile:32, the UI's documented exception at UI.Web.Dockerfile:36-38); the per-image Trivy scan of the exact tag being rolled out, non-gating today (`deploy.yml:1206-1214`) | | §33 Developer Experience | One command brings up six processes and four containers with no Compose file and no hand-set environment variables; the `ADC_BROKER=servicebus` parity opt-in that is paid for only when needed | --- @@ -1670,12 +1695,11 @@ pretending it is cheap. in `MMCA.Common.Infrastructure`, outside the files this chapter walks. The emulator resource's own doc comment (ServiceBusEmulatorResource.cs:18-24) is the only statement of that contract cited here. - Every ingestion figure quoted in the cost sections (roughly 65% of AppMetrics, about 290 MB of a - 500 MB daily stream, 100% of `AppRequests` rows from probes, the gateway's roughly 300k stdout lines a - day, the YARP forwarder's roughly 177k console lines a week) comes from the inline comment that records the measurement, not from a workspace query this - chapter ran: `MMCA.ADC/infra/main.bicep:267-272` and `278-283`, - `MMCA.Common.Aspire/Telemetry/ProbeTelemetryFilter.cs:8-11` - and `MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/appsettings.json:2-6`. + 500 MB daily stream, 100% of `AppRequests` rows from probes, the ASP.NET Core meter family at 73% of + ingestion, Live Metrics holding 64% of vCPU-seconds on the active rate, the YARP forwarder's roughly 177k console lines a week) comes from the inline comment that records the measurement, not from a workspace query this + chapter ran: `MMCA.ADC/infra/main.bicep:267-272`, `278-283`, `293-296` and `302-306`, + and `MMCA.Common.Aspire/Telemetry/ProbeTelemetryFilter.cs:8-11`. - That the `AtlDevCon` database is actually gone from the Azure SQL server is asserted by the template's - comment (main.bicep:871-880), which is explicit that Incremental-mode Bicep never deleted it and an + comment (main.bicep:890-899), which is explicit that Incremental-mode Bicep never deleted it and an operator did, after the template stopped declaring it. The template can only prove the declaration is absent, not the server's current state. diff --git a/docs-src/onboarding/devops-cicd.md b/docs-src/onboarding/devops-cicd.md index 2bdc5071..364411f5 100644 --- a/docs-src/onboarding/devops-cicd.md +++ b/docs-src/onboarding/devops-cicd.md @@ -36,7 +36,7 @@ are consumed by every downstream application, a regression here propagates to bo (`ci.yml:174`), a `ui-e2e` cross-browser matrix for real-browser accessibility and render-smoke testing (`ci.yml:245`), a `performance-smoke` benchmark gate (`ci.yml:352`), a `coverage` job that merges the coverage tiers and enforces a floor (`ci.yml:399`), three canaries that catch failure modes the -solution build cannot see: `consumer-source-build` (`ci.yml:469`), `package-consumption` +solution build cannot see: `consumer-source-build` (`ci.yml:475`), `package-consumption` (`ci.yml:669`) and `sample-deployment-validate` (`ci.yml:795`), and three engine-or-orchestrator tiers for the components whose behavior only a real server can falsify: `redis-integration` (`ci.yml:813`), `postgresql-integration` (`ci.yml:854`) and the advisory `apphost-testing` (`ci.yml:889`). @@ -116,6 +116,10 @@ One step deliberately escapes the guard, covered next. **Runs on:** `ubuntu-latest` (`ci.yml:89`). The Ubuntu runner matters: the Linux file system is case-sensitive, so path-casing bugs that Windows masks are caught in CI. This is a deliberate choice documented in `MMCA.Common/CLAUDE.md` ("CI runs on Ubuntu, file paths are case-sensitive"). +The job is bounded at `timeout-minutes: 15` (`ci.yml:92`), about twice the slowest of four recent code PR +runs per the comment (`ci.yml:90-91`), so a hung restore or test host fails the job instead of holding a +runner for GitHub's 6-hour default. The `coverage` job gets the same treatment at 5 minutes +(`ci.yml:405-408`). **Step 1, Checkout with full history** (`ci.yml:91-94`): @@ -452,7 +456,7 @@ backstop on top of the `--minimum-expected-tests` guard. The remaining jobs all exist for the same reason: **a green solution build does not prove the framework works for anyone who is not the framework.** -**`consumer-source-build`** (`ci.yml:469-657`) is a cross-repo pre-merge canary, and it now proves two +**`consumer-source-build`** (`ci.yml:475-691`) is a cross-repo pre-merge canary, and it now proves two different things. It checks out MMCA.Helpdesk as a sibling directory (`ci.yml:486-525`) and builds and tests it against *this PR's* framework source, so a breaking public-API change fails here rather than surfacing after a release and a lockstep sweep. Helpdesk is the ideal canary precisely because it is @@ -471,9 +475,16 @@ against MMCA.Common `main`, so neither repo can adapt first while the canary pin mutual deadlock, resolved by letting one PR name its counterpart branch (`ci.yml:493-497`). Second, the job runs the consumer's **real EF migrations against a real SQL Server**. An ephemeral -`mcr.microsoft.com/mssql/server:2022-latest` container starts *before* the build so it warms up while the -solution compiles (`ci.yml:547-555`), `go-sqlcmd` is installed as a single static binary rather than the -mssql-tools deb (`ci.yml:573-577`), a 30 x 5s poll waits on a real `SELECT 1` rather than on Docker's +SQL Server container starts *before* the build so it warms up while the solution compiles +(`ci.yml:557-566`). Its image is the job-level `SQLSERVER_IMAGE`, pinned to one cumulative update, +`mcr.microsoft.com/mssql/server:2022-CU27-ubuntu-22.04`, rather than `2022-latest` (`ci.yml:490-495`): +this is a required gate, so a new CU must not change what it runs without a reviewed diff, and the +comment ties the tag to the `sqlserver-integration` tier, which pins the same one ("bump both"). +`go-sqlcmd` is installed as a single static binary rather than the mssql-tools deb, and is itself pinned +(`ci.yml:582-601`): a fixed `SQLCMD_VERSION` (`v1.10.0`) is downloaded to `$RUNNER_TEMP` and checked +against `SQLCMD_SHA256` with `sha256sum -c` before `sudo tar` extracts it, because piping +`releases/latest` straight into a root `tar` ran whatever the newest upload was, unchecked. Version and +digest are bumped together. A 30 x 5s poll waits on a real `SELECT 1` rather than on Docker's notion of "running" (`ci.yml:581-594`), and `dotnet ef database update` applies the Tickets migrations with the same `dotnet-ef 10.0.8` the consumers deploy with (`ci.yml:598-623`). The reason is stated in the comment (`ci.yml:602-610`): `migrations add` and the model-drift gate never open a connection, so @@ -486,8 +497,8 @@ step therefore reads `__EFMigrationsHistory` for at least one row and checks tha (`Tickets.Ticket`) and a **framework** table (`dbo.OutboxMessages`) exist. The framework table is the half that belongs to MMCA.Common, so a change that stops the framework's own tables reaching a consumer's schema fails right here rather than at a consumer's deploy. The job's timeout was raised to 30 minutes to -pay for the container, the poll and the apply (`ci.yml:473-475`), and the throwaway SA password is inline -rather than a secret so the gate still runs from a fork (`ci.yml:476-480`). +pay for the container, the poll and the apply (`ci.yml:479-481`), and the throwaway SA password is inline +rather than a secret so the gate still runs from a fork (`ci.yml:483-486`). [Rubric §8, Data Architecture] is served in a way no build-only canary can reach: the framework's migration path is exercised end to end, on a real engine, by a real consumer. @@ -536,45 +547,65 @@ production-proven versions. ### Job: `redis-integration` -The last job (`ci.yml:813-852`) runs `MMCA.Common.Infrastructure.Redis.Tests` against a real Redis via +`redis-integration` (`ci.yml:837-879`) runs `MMCA.Common.Infrastructure.Redis.Tests` against a real Redis via Testcontainers, which Ubuntu runners support with no extra setup since they ship a Docker daemon. Like the E2E and benchmark projects it lives outside `MMCA.Common.slnx` so the fast solution-wide unit loop never requires Docker, and is therefore built and run by path. -The comment (`ci.yml:818-822`) states the falsifiability argument better than a summary can: -`DistributedCacheService` is the one place where the **storage format** matters, and a +The comment (`ci.yml:842-846`) states the falsifiability argument better than a summary can: +[`DistributedCacheService`](group-09-caching.md#distributedcacheservice) is the one place where the **storage format** matters, and a `Mock` cannot express it. Redis keys are typed, so a counter written as a string and read back as a hash round-trips perfectly against a mock and answers `WRONGTYPE` against a server. A test that cannot fail against a mock is not a test of the thing you care about. -Its heavy step is code-guarded like every other job (`ci.yml:845-852`) so a docs-only PR does not pull a +Its heavy step is code-guarded like every other job (`ci.yml:869-879`) so a docs-only PR does not pull a Redis image, while the job itself still runs and posts its context green, keeping it safe to add to branch -protection. +protection. The test step carries `--minimum-expected-tests 15` (`ci.yml:874-879`), and the comment says +why the number is exactly 15: it is the `[Fact]` count in the project (5 `DistributedCacheService` plus 10 +`HybridCacheService`), so a tier that silently discovers fewer tests fails, and adding a test means raising +the floor in the same PR. The same exact-count floor applies to the two database tiers below. -### Jobs: `postgresql-integration` and `apphost-testing` +### Jobs: `postgresql-integration`, `sqlserver-integration` and `apphost-testing` -`postgresql-integration` (`ci.yml:854-887`) is the Redis argument applied to the second database engine. +`postgresql-integration` (`ci.yml:881-916`) is the Redis argument applied to the second database engine. The PostgreSQL provider is the one place where the SQL the framework *emits* matters, and a model -assertion cannot express it: the comment is specific about the failure class (`ci.yml:859-864`), a +assertion cannot express it: the comment is specific about the failure class (`ci.yml:886-891`), a partial-index predicate written the SQL Server way (`[ProcessedOn] IS NULL`), a soft-delete predicate comparing a boolean to `0`, and a `DateTime` whose `Kind` is not UTC all build a perfectly valid EF model and are rejected by the server. The job runs `MMCA.Common.Infrastructure.PostgreSQL.Tests` against a real -PostgreSQL over Testcontainers (`ci.yml:880-887`), built and run by path for the same reason the Redis -tier is: the project sits outside `MMCA.Common.slnx` so the fast unit loop never requires Docker. - -`apphost-testing` (`ci.yml:889-946`) is the only tier that starts a real orchestrator. It boots the sample +PostgreSQL over Testcontainers (`ci.yml:907-916`), built and run by path for the same reason the Redis +tier is: the project sits outside `MMCA.Common.slnx` so the fast unit loop never requires Docker. Its floor +is `--minimum-expected-tests 7`, the `[Fact]` count in `PostgreSQLPersistenceTests.cs` (`ci.yml:913-916`). + +`sqlserver-integration` (`ci.yml:918-956`) completes the set for the default engine. The comment +(`ci.yml:925-932`) names what neither a mock nor SQLite can express on SQL Server, which is **session +state and server-generated values**: `SET IDENTITY_INSERT` holds only on the session that ran it, a +`rowversion` is issued by the server on every write, and the outbox row must reach the database in the +same `SaveChanges` as its aggregate. The job runs the shipped +[`SQLServerDbContext`](group-07-persistence-ef-core.md#sqlserverdbcontext) and +[`DbContextFactory`](group-07-persistence-ef-core.md#dbcontextfactory) against a real SQL Server over +Testcontainers. Its timeout is 20 minutes, the PostgreSQL tier's 15 plus headroom for the larger image pull +and slower engine start (`ci.yml:921-924`), and the image tag is the same pinned `2022-CU27-ubuntu-22.04` +the `consumer-source-build` canary uses, which is why the canary's comment says to bump both +(`ci.yml:490-495`). The heavy step is code-guarded with the job still posting green (`ci.yml:948-951`), +the project is outside `MMCA.Common.slnx` and run by path, and the floor is +`--minimum-expected-tests 3`, the `[Fact]` count in `SQLServerPersistenceTests.cs` (`ci.yml:952-956`). +It is **not a required check yet**: the comment records that promoting it is a branch-protection setting, +not a workflow change (`ci.yml:931-932`). + +`apphost-testing` (`ci.yml:958-1015`) is the only tier that starts a real orchestrator. It boots the sample AppHost through `Aspire.Hosting.Testing` and closes the one layer nothing else executes, the AppHost -wiring itself (`ci.yml:899-902`): the solution build never runs an AppHost, and every in-process test tier +wiring itself (`ci.yml:968-971`): the solution build never runs an AppHost, and every in-process test tier boots hosts directly through `WebApplicationFactory`, bypassing the orchestration, so a renamed resource, an unresolvable reference or a `WaitFor` cycle is invisible everywhere else. Three properties are worth -carrying away. It is **advisory**, `continue-on-error: true` (`ci.yml:907`), because it is the slowest +carrying away. It is **advisory**, `continue-on-error: true` (`ci.yml:976`), because it is the slowest thing in the repository per assertion and its failure modes on a shared runner are not proven yet; the -comment states the exit condition rather than leaving it open (`ci.yml:894-897`), promote it once it has a +comment states the exit condition rather than leaving it open (`ci.yml:963-966`), promote it once it has a green streak, delete it if it proves to be a flake generator. It trusts the ASP.NET Core development -certificate as an explicit job step (`ci.yml:923-932`), because a resource launched with the `https` +certificate as an explicit job step (`ci.yml:992-1000`), because a resource launched with the `https` profile answers its health probe over TLS terminated by that certificate: untrusted on a fresh runner, every probe fails with `UntrustedRoot`, the resource never turns healthy, and every `WaitFor` edge into it -waits out the budget. And the test step opts in through `MMCA_APPHOST_TESTS` (`ci.yml:933-946`), the +waits out the budget. And the test step opts in through `MMCA_APPHOST_TESTS` (`ci.yml:1001-1015`), the environment gate the fixture reads, so a developer machine and every other CI job skip the collection with a named reason instead of paying for an orchestrator. @@ -662,11 +693,15 @@ token only has write access to Packages, not to repo contents, issues, or deploy registry is reached with no stored API key at all. **The `release` environment and the merged-main assertion.** Publishing to nuget.org is irreversible, a -version can never be withdrawn (ADR-053), so the job declares `environment: release` (`release.yml:11-15`) +version can never be withdrawn (ADR-053), so the job declares `environment: release` (`release.yml:15-18`) and waits on that environment's protection rules (a required reviewer, and a deployment policy limited to `v*` tags) before it runs at all. The first step after checkout then refuses to publish a tag that is not reachable from `origin/main` (`release.yml:35-46`): it fetches the branch tip and fails unless -`git merge-base --is-ancestor` places the tagged commit on merged `main`. A `v*` tag is the one ref pushed +`git merge-base --is-ancestor` places the tagged commit on merged `main`. The job is bounded at +`timeout-minutes: 15` (`release.yml:12-14`), about twice the slowest recent tag run, so a hung restore, +test or push fails instead of holding a runner for six hours. `publish-maui` deliberately keeps 40 +minutes (`release.yml:146-149`): it publishes after the main package set is already on nuget.org, so a +timeout there would leave a half-published release. A `v*` tag is the one ref pushed directly, outside the branch-protection pull-request flow, and this workflow re-runs only restore, build, test and SBOM, so the ancestry check is what makes the checks that ran on the merged pull request (the FACTS drift gate, the vulnerability audit, the Helpdesk consumer canary, the package-consumption canary, @@ -693,7 +728,7 @@ echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT `v`, yielding `1.52.0`. This string is then passed to the build and pack steps as an explicit version override. -**Step 5, Build with explicit version** (`release.yml:69-70`): +**Step 5, Build with explicit version** (`release.yml:72-73`): ```bash dotnet build MMCA.Common.slnx -c Release --no-restore -p:MinVerSkip=true -p:Version=${{ steps.version.outputs.VERSION }} ``` @@ -702,15 +737,22 @@ tag-derived version directly. This pattern avoids a subtle race: if MinVer ran h version from the tag, which should be the same value, but in edge cases (e.g. detached HEAD, retagged commit) the two sources could diverge. Making the version explicit from the start removes the ambiguity. -**Step 6, Test** (`release.yml:72-73`): +**Step 5b, Audit dependencies** (`release.yml:75-83`): the same +`./.github/actions/nuget-vulnerability-audit` composite action that `ci.yml` `build-and-test` runs, with +the same accept-list (`NuGetAuditSuppress` in `Directory.Build.props`) and the same fail-closed +behavior, re-run on the tag build. The comment (`release.yml:75-78`) gives the reason: the graph that +gets packed is restored from the lock files here, and an advisory published between the PR run and the +tag must stop a nuget.org push that can never be withdrawn. + +**Step 6, Test** (`release.yml:85-87`): ```bash -dotnet test --solution MMCA.Common.slnx -c Release --no-build +dotnet test --solution MMCA.Common.slnx -c Release --no-build --minimum-expected-tests 2000 ``` -Tests run again (no `--minimum-expected-tests` floor here, the release workflow is not the primary test -gate; CI already covered this). This is a belt-and-suspenders pass to ensure the tagged commit is green -before packaging. +Tests run again on the tagged commit, with the same 2000-test floor as `ci.yml` `build-and-test`. The +comment (`release.yml:86-87`) states why the floor is repeated here: a discovery or filter regression that +silently drops thousands of tests must fail the release, not publish behind a near-empty green run. -**Step 7, Pack** (`release.yml:75-76`): +**Step 7, Pack** (`release.yml:89-90`): ```bash dotnet pack MMCA.Common.slnx -c Release --no-build -o ./nupkgs -p:MinVerSkip=true -p:PackageVersion=${{ steps.version.outputs.VERSION }} ``` @@ -2473,11 +2515,18 @@ The two copies have diverged, and each difference is a lesson: - **Action pinning.** Both repositories pin the third-party action to a **commit SHA** (`MMCA.ADC/.github/workflows/claude-code-review.yml:41-43`, `MMCA.ADC/.github/workflows/claude.yml:35-37`, - `MMCA.Common/.github/workflows/claude-code-review.yml:41`, - `MMCA.Common/.github/workflows/claude.yml:35`), with the comment stating the supply-chain argument: a + `MMCA.Common/.github/workflows/claude-code-review.yml:45`, + `MMCA.Common/.github/workflows/claude.yml:39`), with the comment stating the supply-chain argument: a mutable tag can be repointed at malicious code, a SHA cannot, and Dependabot's `github-actions` - ecosystem bumps it. The two sit on different revisions of the same action, the expected steady state - when each repository's Dependabot bumps it independently. + ecosystem bumps it. Common's pin is annotated `v1.0.235`, ADC's only `v1`: the two sit on different + revisions of the same action, the expected steady state when each repository's Dependabot bumps it + independently. +- **Checkout credentials.** Common's checkout step sets `persist-credentials: false` + (`MMCA.Common/.github/workflows/claude-code-review.yml:36-40`, + `MMCA.Common/.github/workflows/claude.yml:30-34`), and the comment says why nothing is lost: the + action removes the checkout token anyway and authenticates git with its own app token, and the workflow + token is read-only in any case. That brings the Claude pair in line with every other Common job, none of + which leaves a token on disk. [Rubric §34, Architecture Governance & Documentation] is served: with a single maintainer and no second diff --git a/docs-src/onboarding/devops-iac.md b/docs-src/onboarding/devops-iac.md index 2abb5c4b..470e5019 100644 --- a/docs-src/onboarding/devops-iac.md +++ b/docs-src/onboarding/devops-iac.md @@ -379,7 +379,7 @@ that exists only for the duration of the workflow run. `main.bicep` declares every application-layer Azure resource: Application Insights, five SLO scheduled query rules (one of them the AI-scoring token ceiling, `main.bicep:420-432`) and their action group, three operational scheduled query rules, a log-ingestion-cap rule, a Gateway -availability web test and its severity-1 alert, a saved SLO workbook (`main.bicep:704-724`), the +availability web test and its severity-1 alert, a saved SLO workbook (`main.bicep:721-742`), the monthly cost budget, SQL Server with the four per-service databases, Service Bus, references to the manually provisioned Notification Hub, the blob storage account with its two declared containers (public avatars and the private DataProtection key ring), an Azure Managed Redis instance, the @@ -511,47 +511,66 @@ var is what Azure Monitor maps to the Cloud Role Name, without it, all services framework automatically routes OpenTelemetry spans, logs, and metrics to Azure Monitor in production with no service-level code change. -Five more shared env entries ride along with the connection string on every app, and all of them -are cost controls on a pay-per-GB workspace: +Seven more shared env entries ride along with the connection string on every app, and all of them +are cost controls, six on the pay-per-GB workspace and one on the Container Apps compute bill: -- `Telemetry__TracesSampleRatio: '0.25'` (`main.bicep:230-233`), head-based trace sampling that keeps +- `Telemetry__TracesSampleRatio: '0.25'` (`main.bicep:252-255`), head-based trace sampling that keeps 25% of traces. `ParentBased` sampling in `MMCA.Common.Aspire` keeps a sampled-in trace intact across service boundaries, so a kept trace is still end-to-end rather than a fragment. -- `Logging__OpenTelemetry__LogLevel__Default: 'Warning'` (`main.bicep:241-244`), the floor for what the +- `Logging__OpenTelemetry__LogLevel__Default: 'Warning'` (`main.bicep:263-266`), the floor for what the OpenTelemetry logging provider ships to Azure Monitor. Serilog still writes Information to stdout (container logs), but only Warning and above bills against the workspace. The value is set explicitly because `OpenTelemetry` is the `ProviderAlias` of `OpenTelemetryLoggerProvider`, so the key gates that provider only, and because the service hosts register Serilog as one provider alongside OpenTelemetry instead of calling `UseSerilog()`, which would replace the `ILoggerFactory` and drop every application log line before it could reach App Insights. -- `Telemetry__DisableHttpClientMetrics: 'true'` (`main.bicep:252-255`) and - `Telemetry__DisableRuntimeMetrics: 'true'` (`main.bicep:256-259`), which drop the two +- `Telemetry__DisableHttpClientMetrics: 'true'` (`main.bicep:284-287`) and + `Telemetry__DisableRuntimeMetrics: 'true'` (`main.bicep:288-291`), which drop the two highest-volume instrument groups from the `AppMetrics` stream. The comment records the - measurement that motivated them (`main.bicep:246-251`): the `http.client.*` connection gauges + measurement that motivated them (`main.bicep:278-283`): the `http.client.*` connection gauges plus the `dotnet.*` runtime instruments were about 65% of AppMetrics ingestion between 2026-08-03 and 2026-08-09, roughly 290 MB/day of a roughly 500 MB/day stream, while `http.server.request.duration` and the MMCA.Common meters carry the operational signal. Both keys are read by `MMCA.Common.Aspire`'s `ConfigureOpenTelemetry`, and the outbound-dependency latency the client metrics would have shown is still captured as (sampled) `AppDependencies` traces, so this trims volume rather than visibility. -- `OTEL_METRIC_EXPORT_INTERVAL: '300000'` (`main.bicep:267-270`) is the second stage of the same - cost control, and it works on cadence rather than on instrument selection. AppMetrics remained +- `Telemetry__DisableAspNetCoreMetrics: 'true'` (`main.bicep:297-300`) drops a third instrument + group, and the largest one: the ASP.NET Core meter family (`http.server.*`, `kestrel.*`, + `aspnetcore.*`, `signalr.server.*`) was 73% of workspace ingestion between 2026-09-22 and + 2026-09-28, and no alert reads it (`main.bicep:293-296`). Request latency and failure alerting + queries `AppRequests`, the request telemetry rather than the metric stream, so this trims volume + without blinding an alert; it does mean the `http.server.request.duration` histogram named in the + bullet above no longer ships either. The key is read by `MMCA.Common.Aspire`'s cost knob, which + drops the whole `Microsoft.AspNetCore.*` meter family through a View + (`MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.Telemetry.cs:344-354`). +- `AzureMonitor__EnableLiveMetrics: 'false'` (`main.bicep:307-310`) is the one entry aimed at + compute rather than ingestion. Live Metrics is on by default in the Azure Monitor distro and keeps + every replica talking to the Live Metrics service whether or not anyone has the blade open. On + Container Apps that chatter decides the bill (`main.bicep:302-306`): measured on 2026-09-28, every + app sat right on the idle-billing line (about 0.011 cores against the 0.01 threshold, about 950 B/s + inbound against 1,000), so 64% of vCPU-seconds billed at the active rate, eight times the idle + rate. The key lives under `AzureMonitor`, not `Telemetry`, because the distro binds its own + `AzureMonitor` configuration section, and MMCA.Common pins that the key actually reaches it + (`MMCA.Common/Tests/Hosting/MMCA.Common.Aspire.Tests/Telemetry/LiveMetricsConfigurationTests.cs:20`), + so a distro change that stopped honoring it fails a test instead of silently restoring the chatter. +- `OTEL_METRIC_EXPORT_INTERVAL: '300000'` (`main.bicep:318-321`) is the second stage of the + AppMetrics cost control, and it works on cadence rather than on instrument selection. AppMetrics remained about 63% of workspace ingestion after the two instrument groups above were dropped (measured - 2026-08-01 to 2026-08-22, `main.bicep:261-266`). The exporter ships **cumulative** aggregates, so + 2026-08-01 to 2026-08-22, `main.bicep:311-316`). The exporter ships **cumulative** aggregates, so stretching the export interval from the SDK default of 60s to 300s drops roughly 80% of the remaining datapoints without losing the signal: every alert rule in this template evaluates over a 15-minute window, so a 5-minute export cadence still lands datapoints in every window. This is the standard OpenTelemetry SDK env var, read by the periodic exporting metric reader rather than by any MMCA.Common code. -Every one of the six apps gets all five: Identity (`main.bicep:1651-1656`), Conference -(`:1878-1883`), Engagement (`:2016-2021`), Notification (`:2162-2167`), Gateway (`:2338-2344`), -UI (`:2471-2476`). They are declared once as Bicep variables and spliced into each `env` array by +Every one of the six apps gets all seven: Identity (`main.bicep:1671-1677`), Conference +(`:1900-1906`), Engagement (`:2040-2046`), Notification (`:2188-2194`), Gateway (`:2366-2373`), +UI (`:2507-2513`). They are declared once as Bicep variables and spliced into each `env` array by name, which is what keeps a cost decision from being applied to five apps and forgotten on the sixth. The Gateway carries one more, and it is the only per-host entry in the set: -`Logging__LogLevel__Yarp: 'Warning'` (`yarpLogLevelEnv`, `main.bicep:267-276`, spliced at `:2341`). +`Logging__LogLevel__Yarp: 'Warning'` (`yarpLogLevelEnv`, `main.bicep:267-276`, spliced at `:2368`). YARP writes two Information lines per proxied request (`HttpForwarder` events 9 and 56) to stdout, which Container Apps ships to Log Analytics as `ContainerAppConsoleLogs_CL`. The comment records the measurement behind it: about 177k lines and 77 MB per week between 2026-09-13 and 2026-09-19, the @@ -660,7 +679,7 @@ Each SLO alert is paired with a same-severity triage section in `MMCA.ADC/infra/ (`OPERATIONS.md:17`, `:31`, `:50`, `:63`, `:111`), and that pairing is enforced by a framework fitness test rather than by discipline: `ObservabilityConventionTestsBase` parses this template between the literal anchors `var sloAlertSpecs` and `resource sloAlerts` -(`MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/ObservabilityConventionTestsBase.cs:109-110`) +(`MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/ObservabilityConventionTestsBase.cs:135-136`) and fails the build in both directions. ADC raises the base class's floor of three discovered specs (`ObservabilityConventionTestsBase.cs:39`) to five (`MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Governance/ObservabilityConventionTests.cs:14`), @@ -828,13 +847,31 @@ subscription spend, `cost-guard.yml` bounds a surge left un-reverted, and this b that neither of them can see, with the envelope itself expressed as a reviewable Bicep parameter rather than as an assumption inside the scoring code. -### SLO workbook (`main.bicep:704-724`) +### SLO workbook (`main.bicep:721-742`) A saved Azure Monitor workbook renders the same three SLO signals plus exceptions, grouped per service by `AppRoleName` (which is the `OTEL_SERVICE_NAME` value). It is bound to the Log Analytics workspace and embeds `workbooks/adc-slo-workbook.json` at **compile time** via `loadTextContent` -(`main.bicep:620`), so the visualization cannot diverge from the alerts by being maintained -somewhere else, and the JSON stays independently validatable as a file. +(`main.bicep:739`), so the visualization cannot diverge from the alerts by being maintained +somewhere else, and the JSON stays independently validatable as a file. No test holds it in place, +though: ADC's `ObservabilityConventionTests` subclass raises only the spec floor and does not opt +into the base class's `RequireWorkbook` switch (`ObservabilityConventionTestsBase.cs:58`, +`MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Governance/ObservabilityConventionTests.cs:7-15`), +so deleting this resource would not fail the build. + +MMCA.Common's reference sample reaches the same goal by another route +(`MMCA.Common/samples/deployment/main.bicep:286-351`): it has no workbook JSON file at all and +generates the workbook from its own `sloAlertSpecs`, one KQL table tile per spec that runs that +spec's own query against the workspace the rules scope to (`:290-305`), behind a time-range +parameter defaulting to the 15-minute window the rules evaluate (`:323`). Adding an alert therefore +adds its tile, and a tile cannot drift from the query that pages (`:286-289`). The resource name is a +GUID derived from the resource group and prefix (`:339-340`), because a workbook's name must be a +GUID and deriving it keeps redeploys updating the same workbook. Unlike ADC, the sample's +architecture test opts into `RequireWorkbook` +(`MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/SampleDeploymentObservabilityTests.cs:24`), +so `MonitoringWorkbookOrDashboard_IsProvisioned_WhenRequired` (`ObservabilityConventionTestsBase.cs:66-77`) +fails if the sample stops declaring a `Microsoft.Insights/workbooks` or `Microsoft.Portal/dashboards` +resource. ### Cost budget (`main.bicep:725-757`) @@ -1647,27 +1684,40 @@ Kestrel in HTTP/2 prior-knowledge mode rejects the platform's HTTP/1.1 `httpGet` `GOAWAY HTTP_1_1_REQUIRED`, which would fail the liveness check and cause a reboot loop. Rather than degrading the three h2c services to port-only `tcpSocket` probes, each service opens a **dedicated HTTP/1.1 probe listener** that is not exposed via ingress: `HealthProbe__Port: '8081'` -on Identity, Conference and Engagement (`main.bicep:1212`, `:1419`, `:1542`) and `'8082'` on -Notification (`main.bicep:1688`, because 8080 and 8081 are already the ADR-012 pair). ACA probes may +on Identity, Conference and Engagement (`main.bicep:1682`, `:1909`, `:2049`) and `'8082'` on +Notification (`main.bicep:2201`, because 8080 and 8081 are already the ADR-012 pair). ACA probes may target a port that ingress does not publish, so all six apps use `httpGet` probes and all six carry -the same three (`main.bicep:1326-1351` Identity, `:1460-1485` Conference, `:1587-1612` Engagement, -`:1742-1767` Notification, `:1861-1886` Gateway, `:1983-2008` UI): +the same three (`main.bicep:1817-1833` Identity, `:1968-1984` Conference, `:2101-2117` Engagement, +`:2266-2282` Notification, `:2409-2432` Gateway, `:2558-2574` UI), with one deliberate difference in +where the Gateway's readiness probe points: | Probe | Path | Cadence | Semantics | |---|---|---|---| | `startup` | `/alive` | `initialDelaySeconds: 5`, `periodSeconds: 5`, `failureThreshold: 30` | up to 150s for a cold container to answer at all | | `liveness` | `/alive` | `periodSeconds: 30`, `failureThreshold: 3` | self-only, so a SQL outage never restarts the container | -| `readiness` | `/health/ready` | `initialDelaySeconds: 3`, `periodSeconds: 30`, `failureThreshold: 3` | warmup gate plus the DB-aware `AddSqlServer` check | +| `readiness` | `/health/ready` (Gateway: `/alive`) | `initialDelaySeconds: 3`, `periodSeconds: 30`, `failureThreshold: 3` | warmup gate plus the DB-aware `AddSqlServer` check (Gateway: self-only, see below) | -The liveness/readiness split is the load-bearing part (`main.bicep:1313-1319`): `/alive` checks the +The liveness/readiness split is the load-bearing part (`main.bicep:1806-1808`): `/alive` checks the process only, so a database outage does not trigger a restart loop, while `/health/ready` fails when a replica cannot reach its database, pulling it out of rotation instead of letting it serve 500s. Readiness is also gated on `WarmupHostedService` completing (OIDC discovery fetched), so ACA holds -back user traffic until the replica is warm. Gateway and UI probe their own 8080 (`main.bicep:1861-1886`, -`:1983-2008`) because their Kestrel accepts HTTP/1.1 directly. +back user traffic until the replica is warm. Gateway and UI probe their own 8080 (`main.bicep:2409-2432`, +`:2558-2574`) because their Kestrel accepts HTTP/1.1 directly. + +**The Gateway's readiness probe targets `/alive`, not `/health/ready`** (`main.bicep:2423-2432`). +On the Gateway, `/health/ready` is an aggregate that fans out to every downstream's `/alive` +(`AddGatewayDownstreamHealthChecks`), so probing it every 30 seconds put an HTTP request on every +service every 30 seconds. The comment records two costs of that. On the bill, a request every 30 +seconds is on its own enough to keep an otherwise idle downstream replica off the Container Apps +idle rate (the same idle-line problem the Live Metrics switch above addresses). On availability, one +failed downstream marked the only Gateway replica unready and took the whole site down, healthy +routes included. The switch costs no cold-revision protection: the Gateway registers no JwtBearer +scheme, so the only warm-up task `AddServiceDefaults` gives it (OIDC metadata) has nothing to wait +for. The full downstream aggregate is still served on `/health`, which is what the availability web +test and its alert probe. **Readiness runs every 30 seconds, not every 10, and that is a telemetry-cost decision** -(`main.bicep:1320-1325`). The DB-aware readiness check issues a SQL `SELECT 1` per probe, and +(`main.bicep:1809-1814`). The DB-aware readiness check issues a SQL `SELECT 1` per probe, and neither the probe request nor its dependency row is sampled, so a 10-second period cost 360 request rows plus 360 dependency rows per app per hour of App Insights ingestion, on six apps, forever. `failureThreshold` stays at 3, so the honest trade is stated in the comment: an unhealthy replica @@ -1710,7 +1760,7 @@ The same service names work locally because the AppHost's `WithReference` inject `AddHttpForwarderWithServiceDiscovery()` or `AddTypedGrpcClient(serviceName)` in both environments and resolves the endpoint from that env var key. -#### Identity Service specifics (`main.bicep:1603-1827`) +#### Identity Service specifics (`main.bicep:1624-1850`) Identity is the JWT issuer and JWKS endpoint. Its JWT configuration (`main.bicep:1233-1237`): @@ -1778,7 +1828,7 @@ the post-login redirect target is provider-independent. Identity is sized at 0.25 CPU / 0.5 Gi (`main.bicep:1198`). JWT operations are CPU-cheap once the key is loaded; the bottleneck is typically network I/O to SQL. -#### Conference Service specifics (`main.bicep:1828-1976`) +#### Conference Service specifics (`main.bicep:1851-2001`) Conference carries the heaviest surface of the four services: seventeen API controllers (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/`), an AI scoring path @@ -1809,7 +1859,7 @@ This is the `union()` + conditional array pattern used throughout `main.bicep` t secrets and env vars out of the resource definition when not configured, rather than passing empty strings to the container. -#### Notification Service specifics (`main.bicep:2108-2280`) +#### Notification Service specifics (`main.bicep:2135-2313`) Notification differs from the other three back-end services in four ways: @@ -1831,40 +1881,50 @@ it, SignalR connections made during warmup would fail because the JWT validator initialized. Its replica cap is no longer the outlier it once was: see the shared scale discussion above. -#### Gateway specifics (`main.bicep:2281-2427`) +#### Gateway specifics (`main.bicep:2314-2465`) Gateway is the sole externally-reachable back-end entry point (`external: true`, -`allowInsecure: false`, `main.bicep:1806-1811`). It is a pure YARP reverse proxy: no DbContext, no -JWT issuing, no module. Its env configuration is service-discovery entries, CORS, and one optional -rate-limiter key: +`allowInsecure: false`, `main.bicep:2328-2333`). It is a pure YARP reverse proxy: no DbContext, no +JWT issuing, no module. Its env configuration is service-discovery entries, CORS, the vault +reference, and two optional rate-limiter keys: ```bicep { name: 'Cors__AllowedOrigins__0', value: 'https://${prefix}-ui.${...defaultDomain}' } ``` -CORS is scoped to exactly the UI's FQDN (`main.bicep:1843`), not a wildcard. Gateway was right-sized -alongside Conference on 2026-09-02 and now runs at 0.25 CPU / 0.5 Gi (`main.bicep:2334`); its -comment records the easier half of that decision (`main.bicep:1824-1830`), a 190 to 235 MB working -set comfortably inside the new limit because pure YARP forwarding holds no DbContext. It uses the -readiness gate at `main.bicep:1878-1885` because its warmup involves establishing connections to all -back-end services. It is also the target of the availability web test described above, and the only -app with no `KeyVault__Uri`. - -Its one conditional secret is the ADR-088 synthetic-traffic bypass. When -`hasSyntheticTrafficSecret` is true the app declares a `synthetic-traffic-secret` Key Vault -reference (`main.bicep:1815-1817`) and receives -`GatewayRateLimiting__SyntheticTrafficSecret` as a `secretRef` (`main.bicep:1858`). A request +CORS is scoped to exactly the UI's FQDN (`main.bicep:2375`), not a wildcard. Gateway was right-sized +alongside Conference on 2026-09-02 and now runs at 0.25 CPU / 0.5 Gi (`main.bicep:2361`); its +comment records the easier half of that decision (`main.bicep:2354-2360`), a 190 to 235 MB working +set comfortably inside the new limit because pure YARP forwarding holds no DbContext. Its readiness +probe is the one exception to the fleet's `/health/ready` convention: it targets `/alive` +(`main.bicep:2423-2432`), for the cost and blast-radius reasons given in the probe section above. It +is also the target of the availability web test described above, which is where the full downstream +aggregate on `/health` is still checked. Like the other five deployables it reads Key Vault +configuration at startup through `KeyVault__Uri` plus `AZURE_CLIENT_ID` (`main.bicep:2385-2391`); the +client id is there because the app carries only the user-assigned identity and the ACA identity +endpoint needs it named, or `DefaultAzureCredential` would fail the startup vault read. + +Its two conditional secrets are both rate-limiter keys, unioned rather than kept as one conditional +array because either can be present on its own (`main.bicep:2335-2347`). The first is the ADR-088 +synthetic-traffic bypass. When `hasSyntheticTrafficSecret` is true the app declares a +`synthetic-traffic-secret` Key Vault reference (`main.bicep:2339-2341`) and receives +`GatewayRateLimiting__SyntheticTrafficSecret` as a `secretRef` (`main.bicep:2397`). A request presenting that value in the `X-Synthetic-Traffic-Key` header skips both chained edge limiters, so -the monthly k6 run measures backend capacity instead of the per-IP window. Absent, the bypass is -off and every request stays rate limited, which is the correct default for a public entry point. - -The template also records the transport contract the Gateway holds up (`main.bicep:1843-1846`): +the monthly k6 run measures backend capacity instead of the per-IP window. The second is the +trusted-internal-caller exemption (`main.bicep:2344-2346`, env at `:2404`): a request carrying that +value in the `X-Internal-Caller-Key` header takes the no-limiter partition on both chained edge +limiters (`main.bicep:2398-2403`). It exists for the UI host, whose server-side token refresh calls +all leave from one container address, which the per-IP window would otherwise collapse into a single +partition and throttle for the whole site. Absent, each key's exemption is off and every request +stays rate limited, which is the correct default for a public entry point. + +The template also records the transport contract the Gateway holds up (`main.bicep:2376-2379`): `ForwardHttp2` defaults to true in the gateway code and YARP uses `VersionPolicy=RequestVersionExact`, so it sends the HTTP/2 preface to the three h2c-prior-knowledge backends whose ACA ingress is `transport: http2`. That pairing is why the ingress choice on those three services and the forwarder policy here cannot be changed independently. -#### UI specifics (`main.bicep:2428-2567`) +#### UI specifics (`main.bicep:2466-2604`) UI is the other externally-reachable app (`external: true`, `main.bicep:1928`), the one app with `secrets: []` (`main.bicep:1938`) and sized at 0.25 CPU / 0.5 Gi (`main.bicep:1945`). Three @@ -1903,7 +1963,7 @@ The UI receives only the OAuth **client ids** when a provider is configured, one including Apple (`main.bicep:1971-1979`); every client secret stays on Identity, which is the app that completes the exchange. -### Outputs (`main.bicep:2568-2573`) +### Outputs (`main.bicep:2605-2610`) ```bicep output acrLoginServer string = acr.properties.loginServer diff --git a/docs-src/onboarding/devops-runbooks.md b/docs-src/onboarding/devops-runbooks.md index 14fca479..de83b926 100644 --- a/docs-src/onboarding/devops-runbooks.md +++ b/docs-src/onboarding/devops-runbooks.md @@ -113,11 +113,11 @@ still do by hand: create the GitHub environment, add six required secrets (`AZUR (`OAUTH_GITHUB_CLIENT_ID`, `OAUTH_GITHUB_CLIENT_SECRET`, `ANTHROPIC_API_KEY`, `SMTP_PASSWORD`) and optional variables (`SMTP_HOST`, `SMTP_PORT`, `SMTP_FROM`, `SMTP_USERNAME`). There is no HS256 fallback signing key in that list: RS256 is the only signing path, which is why both RSA PEMs are -listed as required and `deploy.yml:1416-1419` fails the run when either is missing. +listed as required and `deploy.yml:1329-1332` fails the run when either is missing. **One gap to know about before your first deploy.** The checklist never mentions `ALERT_EMAIL`, but `main.bicep:115-117` declares `alertEmailAddress` as a required parameter with `@minLength(3)` and -no default (alerts that notify nobody are silent failures), and `deploy.yml:1408-1411` refuses the +no default (alerts that notify nobody are silent failures), and `deploy.yml:1321-1324` refuses the run with an actionable error when `vars.ALERT_EMAIL` is unset. Follow the printed checklist exactly and the first deploy stops there. Set `ALERT_EMAIL` as well. @@ -280,7 +280,7 @@ which is the right level of evidence for a change that loosens an alert. Per-ale are created and visible in Azure Monitor even without the variable, "they just don't email". That state is no longer reachable: `alertEmailAddress` is a required parameter with `@minLength(3)` and no default (`main.bicep:115-117`), the action group's email receiver is unconditional -(`main.bicep:269-286`), and `deploy.yml:1408-1411` fails the deploy before bicep validation when +(`main.bicep:269-286`), and `deploy.yml:1321-1324` fails the deploy before bicep validation when `vars.ALERT_EMAIL` is unset. An alert that notifies nobody is now impossible in a deployed environment by construction, which is the stronger version of what the runbook was aiming at. @@ -288,12 +288,12 @@ environment by construction, which is the stronger version of what the runbook w post-deploy smoke gate as Gateway `/health` plus `/.well-known/jwks.json` plus the UI root. The live gate is broader in both dimensions: a revision-activation gate that requires the newest revision of every app to report Healthy, Running and 100% traffic weight, followed by six probes that reach -every service through the Gateway (`deploy.yml:1607-1628` for the reasoning, -`deploy.yml:1707-1717` for the probes). The activation gate exists because the HTTP probes alone +every service through the Gateway (`deploy.yml:1520-1541` for the reasoning, +`deploy.yml:1620-1630` for the probes). The activation gate exists because the HTTP probes alone cannot prove the new code is serving: a healthy Gateway keeps answering from the previous backend revision when the new one never goes ready, which is exactly how a readiness regression stayed -hidden for four days (`deploy.yml:1616-1619`). The rollback mechanism the runbook names, -`az containerapp revision copy`, is still what runs (`deploy.yml:1761`). +hidden for four days (`deploy.yml:1529-1532`). The rollback mechanism the runbook names, +`az containerapp revision copy`, is still what runs (`deploy.yml:1674`). ### Recovery objectives @@ -400,7 +400,7 @@ take about two minutes" falsifiable; a single row cannot show variance at all. T the archive is gone, rather than being edited out. The ledger stays honest because it is gated, not remembered: `dr-freshness` fails a deploy when the -newest successful `dr-drill.yml` run is older than 8 days (`deploy.yml:823`, +newest successful `dr-drill.yml` run is older than 8 days (`deploy.yml:899`, `DISASTER-RECOVERY.md:222-224`). The rotation itself is prose here but arithmetic in the workflow, which is the source of truth. The next section walks it. @@ -408,7 +408,7 @@ which is the source of truth. The next section walks it. ## dr-drill.yml and dr-restore-drill.ps1, the ADR-009 restore drill -**Files:** `MMCA.ADC/.github/workflows/dr-drill.yml` (112 lines), +**Files:** `MMCA.ADC/.github/workflows/dr-drill.yml` (120 lines), `MMCA.ADC/scripts/dr-restore-drill.ps1` (101 lines) **What it is.** The automation behind the drill requirement above: the workflow picks a target @@ -427,33 +427,50 @@ the §29 weight: The job holds `id-token: write` plus `contents: read` and nothing else (`dr-drill.yml:35-37`), which is the least privilege an OIDC login needs, and it logs in with the same three `AZURE_*` secrets -`azure-setup.sh` printed (`dr-drill.yml:53-58`). Both third-party actions it uses are pinned to a +`azure-setup.sh` printed (`dr-drill.yml:61-66`). Both third-party actions it uses are pinned to a full commit SHA with the human-readable version parked in a trailing comment: -`actions/checkout@3d3c42e5...` (`dr-drill.yml:51`) and `azure/login@7ddb5af1...` -(`dr-drill.yml:54`). +`actions/checkout@3d3c42e5...` (`dr-drill.yml:59`) and `azure/login@a641126d...` +(`dr-drill.yml:62`). [Rubric §32, Dependency & Supply-Chain] assesses whether third-party dependencies are pinned, scanned, and cannot change underfoot. A Git tag is mutable, so `@v3` in a job that mints an Azure OIDC token means a repointed tag could run new code with production credentials; the SHA pin removes -that path, and the `# v7.0.1` / `# v3.0.2` comments keep the pin readable and updatable. +that path, and the `# v7.0.1` / `# v3.1.0` comments keep the pin readable and updatable. + +**Why the drill shares the deploy's concurrency group.** The workflow joins the `prod-azure` group +with `cancel-in-progress: false` (`dr-drill.yml:43-45`). Its comment gives the reason +(`dr-drill.yml:39-42`): the drill creates and deletes a database on the production SQL server, and +`deploy.yml` declares that every workflow mutating production Azure state joins that same group +(`deploy.yml:39-47`), so a drill and a deploy (which migrates those very databases) never run at the +same time. Never cancelling an in-flight drill matches the deploy side, for the reason the cleanup +sweep below exists: a drill killed mid-restore never reaches the script's own cleanup. The cost is +queueing. A push to `main` that lands during a drill waits behind it, for at most the drill's +60-minute timeout. And because GitHub Actions keeps only one pending run per concurrency group, a +scheduled drill still waiting behind a deploy can be displaced by a newer pending deploy; the 8-day +`dr-freshness` window, wider than the weekly cadence, is what absorbs one lost run, and a manual +dispatch restores the proof. + +[Rubric §17, DevOps & Deployment] assesses whether deployments are safe and repeatable. Serializing +every production-mutating workflow on one group turns "do not drill during a migration" from an +operator habit into a property of the pipeline. Scheduled runs **rotate** across the four live per-service databases by ISO week number modulo 4 -(`dr-drill.yml:60-79`, the arithmetic at `dr-drill.yml:72-75`), so each live database gets a recovery +(`dr-drill.yml:68-87`, the arithmetic at `dr-drill.yml:80-83`), so each live database gets a recovery proof roughly monthly. Which branch runs is decided purely by whether the dispatch input is present -(`dr-drill.yml:66`), which is what lets one job serve both triggers. The chosen database is echoed -into the step summary before the drill starts (`dr-drill.yml:79`), so a reader of a failed run knows +(`dr-drill.yml:74`), which is what lets one job serve both triggers. The chosen database is echoed +into the step summary before the drill starts (`dr-drill.yml:87`), so a reader of a failed run knows immediately which database was under test. Note how the input is read: the step declares `DISPATCH_DATABASE: ${{ inputs.source_database }}` as an environment variable -(`dr-drill.yml:62-63`) and the shell tests `"${DISPATCH_DATABASE:-}"` (`dr-drill.yml:66`) instead of +(`dr-drill.yml:70-71`) and the shell tests `"${DISPATCH_DATABASE:-}"` (`dr-drill.yml:74`) instead of expanding the `${{ }}` expression inline, and the whole script runs under `set -euo pipefail` -(`dr-drill.yml:65`) so an unset variable or a failed `az` call stops the step rather than silently +(`dr-drill.yml:73`) so an unset variable or a failed `az` call stops the step rather than silently rotating to the wrong database. **How the inputs reach the PowerShell script.** The drill step sets three variables in its own `env` block, `DRILL_RESOURCE_GROUP` from `vars.AZURE_RESOURCE_GROUP`, `DRILL_SOURCE_DATABASE` from the rotation step's output, and `DRILL_RESTORE_POINT_MINUTES_AGO` from the dispatch input with a literal -`'10'` fallback for scheduled runs (`dr-drill.yml:85-88`), then passes those variables to the script -(`dr-drill.yml:89-94`). The workflow's own comment states the reason (`dr-drill.yml:83-84`): values +`'10'` fallback for scheduled runs (`dr-drill.yml:93-96`), then passes those variables to the script +(`dr-drill.yml:97-102`). The workflow's own comment states the reason (`dr-drill.yml:91-92`): values reach the script through the environment rather than being interpolated into the command line, so a dispatch input cannot inject extra PowerShell arguments. This matters more here than in most jobs, because the step runs with an Azure session that holds Contributor on the production resource group. @@ -464,8 +481,8 @@ hostile input value becomes code; routing every input through `env` and quoting value back into data. The `type: choice` input (`dr-drill.yml:20-26`) already constrains the database name to four options, so this is defence in depth rather than the only control. -**Why the job gets 60 minutes.** The job carries a 60-minute `timeout-minutes` (`dr-drill.yml:49`), -and the comment above it records why it is not 30 (`dr-drill.yml:42-48`): the 2026-09-14 scheduled +**Why the job gets 60 minutes.** The job carries a 60-minute `timeout-minutes` (`dr-drill.yml:57`), +and the comment above it records why it is not 30 (`dr-drill.yml:50-56`): the 2026-09-14 scheduled run (run 34838047421) was killed at the old 30-minute limit and recorded no freshness proof. A measured PITR restore of an `ADC_*` database takes minutes, but Azure SQL queues the restore on the platform side and its duration varies from run to run, so 60 leaves headroom for a slow one (MMCA.Store @@ -476,22 +493,22 @@ walked below). **The sweep that runs even when the drill does not finish.** A job cancelled at its `timeout-minutes` is killed mid-step, so the script's own `finally` cleanup never runs. The workflow -comment records the incident that exposed this (`dr-drill.yml:96-100`): the same 2026-09-14 +comment records the incident that exposed this (`dr-drill.yml:104-108`): the same 2026-09-14 scheduled run was cancelled mid-restore and left `ADC_Engagement-drill` behind, and the weekly rotation meant the next run targeted a different database, so the script's name-specific stale-copy check would not have removed it either; the copy sat at Basic-tier cost for five days. The last step, `Remove leftover drill copies`, therefore runs -under `if: always()` (`dr-drill.yml:101-102`), which fires on success, failure and cancellation alike. +under `if: always()` (`dr-drill.yml:109-110`), which fires on success, failure and cancellation alike. It resolves the server by the same `adc-prod-sql-` name prefix the script uses, exits 0 with a -workflow warning when no server matches (`dr-drill.yml:107-108`), and deletes every database whose -name ends in `-drill` (`dr-drill.yml:109-112`), not just the copy this run created. The suffix +workflow warning when no server matches (`dr-drill.yml:115-116`), and deletes every database whose +name ends in `-drill` (`dr-drill.yml:117-120`), not just the copy this run created. The suffix filter is what keeps the sweep safe on a production server: the live `ADC_*` databases never end in `-drill`, so the only names it can match are throwaway copies. One consequence for a local run: a copy kept with the script's `-KeepCopy` switch for a manual row-count check (`dr-restore-drill.ps1:83-88` honors it) is deleted by the next workflow run, so finish that check before Monday 06:00 UTC. It reads the resource group through -`env` like the drill step (`dr-drill.yml:103-104`) and runs under `set -euo pipefail` -(`dr-drill.yml:106`). +`env` like the drill step (`dr-drill.yml:111-112`) and runs under `set -euo pipefail` +(`dr-drill.yml:114`). **`AtlDevCon` is gone from both ends of this pair.** The dispatch `choice` input now offers only the four live databases (`dr-drill.yml:22-26`), and the script's own `-SourceDatabase` default moved from @@ -509,9 +526,9 @@ runbook from a document into a measurement. **Parameters (`dr-restore-drill.ps1:23-30`).** `-ResourceGroup` (default `acc-rg`), `-SourceDatabase` (default `ADC_Identity`), `-RestorePointMinutesAgo` (default 10), `-KeepCopy`, and -`-SummaryPath` (the workflow passes `$env:GITHUB_STEP_SUMMARY`, `dr-drill.yml:81-94`). The workflow +`-SummaryPath` (the workflow passes `$env:GITHUB_STEP_SUMMARY`, `dr-drill.yml:89-102`). The workflow never leans on a default: it passes an explicit `-SourceDatabase` from the rotation step and an -explicit `-ResourceGroup` from `vars.AZURE_RESOURCE_GROUP` (`dr-drill.yml:86-87`, `:91-92`), so the script's +explicit `-ResourceGroup` from `vars.AZURE_RESOURCE_GROUP` (`dr-drill.yml:94-95`, `:99-100`), so the script's own defaults only matter to a local CLI run. **Server discovery (`dr-restore-drill.ps1:40-44`).** Queries by name prefix (`adc-prod-sql-*`) rather @@ -524,7 +541,7 @@ previous run, so the restore name is free. Without it, one crashed run would fai drill on a name collision, and the freshness gate would then start blocking deploys for a reason that has nothing to do with recoverability. The check only covers the database being drilled this run, which under the weekly rotation is usually a different one, so the workflow's `always()` sweep -(`dr-drill.yml:101-112`) is what catches a copy left by an earlier run against another database. +(`dr-drill.yml:109-120`) is what catches a copy left by an earlier run against another database. **Restore and timing (`dr-restore-drill.ps1:59-66`).** A stopwatch brackets `az sql db restore`, and `$LASTEXITCODE` is checked explicitly (`dr-restore-drill.ps1:65`) because a failing `az` call does @@ -538,7 +555,7 @@ needs `-KeepCopy` plus a manual pass (`dr-restore-drill.ps1:18-21`). **Cleanup (`dr-restore-drill.ps1:83-88`).** The delete sits in a `finally` block, so the throwaway copy is removed even when the restore or the verification threw. A `finally` block cannot run in a process that GitHub Actions kills at `timeout-minutes`, though, which is why the workflow backs it -with the `always()` sweep described above (`dr-drill.yml:96-112`): between them, a weekly drill +with the `always()` sweep described above (`dr-drill.yml:104-120`): between them, a weekly drill does not accrete paid databases. **Result row and exit code (`dr-restore-drill.ps1:90-101`).** Prints the markdown table row (date, @@ -551,21 +568,32 @@ recovery proof. The drill never touches a live database and it does not gate production directly. The link to production is the age of its newest successful run: `deploy.yml`'s `dr-freshness` job -(`deploy.yml:815-866`) reads the Actions API for the latest successful `dr-drill.yml` run -(`deploy.yml:851-853`) and fails the deploy when that run is older than the 8-day window -(`deploy.yml:823`, compared at `deploy.yml:862-865`), or when there is no successful run at all -(`deploy.yml:854-857`). The comment at `deploy.yml:849-850` records why the gate can trust the run -list at face value: `dr-drill.yml` has no skip-if-unchanged guard, so every successful run really -performed a PITR restore. The job itself asks for only `actions: read` plus `contents: read` -(`deploy.yml:819-821`) and runs on a 5-minute timeout (`deploy.yml:817`), because it is one API read -and no restore cost per deploy. +(`deploy.yml:883-902`) is a single step that calls the shared MMCA.Common composite action +`ivanball/MMCA.Common/.github/actions/freshness-gate@main` (`deploy.yml:895`) with +`workflow: dr-drill.yml` and `window-days: '8'` (`deploy.yml:898-899`). The job names no +`required-jobs`, so the action runs in its success mode: it reads the newest `dr-drill.yml` run with +`status=success` (`MMCA.Common/.github/actions/freshness-gate/action.yml:184-187`), fails the deploy +when there is none (`action.yml:188-191`), and otherwise fails it when that run's age in whole days +is greater than the window (`action.yml:133-143`). The comment at `deploy.yml:892-894` records why +the gate can trust that run at face value: `dr-drill.yml` has no skip-if-unchanged guard, so every +successful run really performed a PITR restore. The job itself asks for only `actions: read` plus +`contents: read` (`deploy.yml:887-889`) and runs on a 5-minute timeout (`deploy.yml:885`), because +it is one API read and no restore cost per deploy. + +Why a shared action rather than inline bash: the DR, load, cross-service and cross-browser gates all +call the same action (`deploy.yml:895`, `:920`, `:949`, `:1001`), and so does MMCA.Store's deploy +(`action.yml:6-8`), so the staleness arithmetic and the break-glass rules live in one place and a fix +lands for every gate at once instead of in several drifting copies. The reference is the MMCA.Common +`main` branch rather than a SHA pin like the drill's third-party actions: it is first-party code, +and that branch only moves through a PR. `dr-freshness` sits in `deploy.needs` alongside `load-freshness`, `cross-service-freshness` and -`cross-browser-freshness` (`deploy.yml:1310`), and the deploy's condition requires all four to have -concluded `success` (`deploy.yml:1349-1352`). Break-glass exists but is deliberately expensive to -use: the `skip_freshness_gates` input is honored only with a non-empty `skip_justification`, and the -run refuses without one (`deploy.yml:834-838`), then writes the justification into the step summary -and raises a workflow warning (`deploy.yml:839-846`). +`cross-browser-freshness` (`deploy.yml:1219`), and the deploy's condition requires all four to have +concluded `success` (`deploy.yml:1258-1261`). Break-glass exists but is deliberately expensive to +use: the job forwards the `skip_freshness_gates` and `skip_justification` dispatch inputs +(`deploy.yml:13-18`) to the action (`deploy.yml:901-902`), which refuses to skip without a non-empty +justification (`action.yml:94-97`) and, when it does skip, writes the justification into the step +summary and raises a workflow warning (`action.yml:98-106`). The operational consequence for an on-call reader: **a red or skipped weekly drill blocks the next production deploy.** If a deploy fails on `dr-freshness`, the fix is to re-run `dr-drill.yml` (and @@ -707,7 +735,7 @@ alerts "the honour system". A reader paged by `adc-prod-alert-revision-activatio two things to lean on: the alert's own description, which names the first triage step (check `/health/ready` on the named app, an untagged infrastructure health check gating readiness being the usual cause, `main.bicep:417`), and the deploy-side story, the activation gate and rollback at -`deploy.yml:1607-1628`, described in the [CI/CD chapter](devops-cicd.md). +`deploy.yml:1520-1541`, described in the [CI/CD chapter](devops-cicd.md). ### Recovery moves @@ -716,10 +744,10 @@ revision list` and `revision copy`, follow `DISASTER-RECOVERY.md` for a database referenced workflow when a freshness gate blocks a deploy, and revert a conference-day surge when `cost-guard.yml` fails. Its freshness quick-reference (`OPERATIONS.md:158-161`) names all three windows, and each one matches the workflow that enforces it: `dr-freshness` 8 days -(`deploy.yml:823`), `load-freshness` 35 days (`deploy.yml:881`), `cross-service-freshness` 5 days -(`deploy.yml:943`). Those numbers live in two places, so treat the workflow as the source of truth +(`deploy.yml:899`), `load-freshness` 35 days (`deploy.yml:924`), `cross-service-freshness` 5 days +(`deploy.yml:955`). Those numbers live in two places, so treat the workflow as the source of truth and re-check the runbook line whenever a window moves: `deploy.yml` now enforces a fourth window the -runbook's quick reference does not name, `cross-browser-freshness` at 10 days (`deploy.yml:1048`), +runbook's quick reference does not name, `cross-browser-freshness` at 10 days (`deploy.yml:1006`), which is the drift this pairing produces whenever a gate is added and the prose is updated separately. @@ -766,8 +794,8 @@ deploy path: all three bicep knobs are default-safe, so a deploy with the defaul [Rubric §11, Security] assesses credential hardening. **All three stages have been run in ADC production.** The template default is still `false` (`main.bicep:36`), because that is what makes a fresh environment start on password auth and each stage independently deployable, but the deployed -value comes from a repository variable, not the default: `deploy.yml:1400` reads -`vars.USE_MANAGED_IDENTITY_SQL` and `deploy.yml:1569-1574` rewrites the parameter to a literal JSON +value comes from a repository variable, not the default: `deploy.yml:1313` reads +`vars.USE_MANAGED_IDENTITY_SQL` and `deploy.yml:1482-1487` rewrites the parameter to a literal JSON `true` when it is set. All three variables are set in `ivanball/ADC` (`USE_MANAGED_IDENTITY_SQL`, `SQL_AAD_ADMIN_LOGIN` and `SQL_AAD_ADMIN_OID`, all dated 2026-06-28), so the deployed apps use passwordless `Active Directory Managed Identity` connection strings. The ADC scorecard records the @@ -792,7 +820,7 @@ Flipping the connection strings before the grants exist takes every app offline is independently deployable and reversible. 1. **Stage 1, add the Entra admin** (`SQL-MANAGED-IDENTITY.md:50-54`). Set the `SQL_AAD_ADMIN_LOGIN` - and `SQL_AAD_ADMIN_OID` repository variables; `deploy.yml:1561-1568` folds them into the bicep + and `SQL_AAD_ADMIN_OID` repository variables; `deploy.yml:1474-1481` folds them into the bicep parameter file, and `main.bicep:624-633` provisions the AAD admin only when the object id is non-empty. Additive, zero app impact. 2. **Stage 2, grant the identity in each database** (`SQL-MANAGED-IDENTITY.md:56-68`). Connect to @@ -1098,7 +1126,7 @@ flip, then a Basic-tier archive, and since 2026-09-02 a bacpac blob rather than (`main.bicep:635-647`, `POST-CUTOVER-atldevcon-downgrade.md:79-121`). Three practical consequences follow for an operator. The four live `ADC_*` databases are the entire estate covered by PITR and LTR (`main.bicep:681-685`). The weekly drill rotates over exactly those four and cannot target anything -else (`dr-drill.yml:22-26`, `:72-75`). And recovering pre-cutover data is an `az sql db import` into +else (`dr-drill.yml:22-26`, `:80-83`). And recovering pre-cutover data is an `az sql db import` into a new database name, not a point-in-time restore. Cross-links: @@ -1106,7 +1134,7 @@ Cross-links: policies, the SLO alerts and workbook, and the Service Bus namespace. - [CI/CD chapter](devops-cicd.md): `deploy.yml` carries the revision-activation and smoke gates, the rollback, and the four recency gates (`dr-freshness`, `load-freshness`, - `cross-service-freshness`, `cross-browser-freshness`, `deploy.yml:1310`). + `cross-service-freshness`, `cross-browser-freshness`, `deploy.yml:1219`). - [ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html), the decision to adopt database-per-service, its trade-offs, and the `CrossDataSourceDegradeConvention` that removes cross-database FKs. - [ADR-009](https://ivanball.github.io/docs/adr/009-resilience-and-recovery-objectives.html), the resilience and recovery objectives framework, including the requirement @@ -1132,8 +1160,8 @@ Cross-links: | §17 DevOps & Deployment | `azure-setup.sh`, `POST-CUTOVER-atldevcon-downgrade.md`, `Docs/MobileReleaseRunbook.md` (the manual store-submission path) | | §29 Resilience & Business Continuity | `DISASTER-RECOVERY.md` (RTO/RPO, PITR, LTR, restore runbook, drill ledger), `dr-drill.yml` plus `dr-restore-drill.ps1` (the drill itself, gated for recency by `dr-freshness`) | | §30 Compliance/Privacy | `play-store-capture.ps1`, `play-store-compose.ps1` | -| §31 Cost/FinOps | `POST-CUTOVER-atldevcon-downgrade.md` (S0 to Basic, then archive-and-drop on a measured 0 DTU); the 2026-09-02 alert-cadence changes (`main.bicep:345-349`, `:464-469`); the thinned telemetry stream documented in `OPERATIONS.md:176-207`; the `dr-drill.yml` `always()` sweep of leftover `-drill` copies (`dr-drill.yml:96-112`), so a drill cancelled at its timeout cannot leave a billed database behind | -| §32 Dependency & Supply-Chain | `dr-drill.yml` (`actions/checkout` and `azure/login` pinned to full commit SHAs with the version in a trailing comment, `dr-drill.yml:51`, `:54`) | +| §31 Cost/FinOps | `POST-CUTOVER-atldevcon-downgrade.md` (S0 to Basic, then archive-and-drop on a measured 0 DTU); the 2026-09-02 alert-cadence changes (`main.bicep:345-349`, `:464-469`); the thinned telemetry stream documented in `OPERATIONS.md:176-207`; the `dr-drill.yml` `always()` sweep of leftover `-drill` copies (`dr-drill.yml:104-120`), so a drill cancelled at its timeout cannot leave a billed database behind | +| §32 Dependency & Supply-Chain | `dr-drill.yml` (`actions/checkout` and `azure/login` pinned to full commit SHAs with the version in a trailing comment, `dr-drill.yml:59`, `:62`) | | §34 Architecture Governance | The deliberate deletion of the spent one-time cutover tooling, and then of the archive database itself once it was measurably idle; `OPERATIONS.md:165-171`, which states exactly which alerts the pairing gate does and does not cover | --- @@ -1144,9 +1172,9 @@ Cross-links: (`DISASTER-RECOVERY.md:210-217`), but `dr-drill.yml` runs every Monday and a run reaches the ledger only when an operator pastes the printed row back into `DISASTER-RECOVERY.md`. Any drill newer than the last ledger row exists only in the Actions history, which is exactly what `dr-freshness` queries - (`deploy.yml:851-853`); it cannot be read from the repository. + (`deploy.yml:895-899`, which reads `MMCA.Common/.github/actions/freshness-gate/action.yml:184-187`); it cannot be read from the repository. - **The live values of the three SQL managed-identity repository variables**: `main.bicep:36` shows - the safe default (`false`) and `deploy.yml:1400` shows that the deployed value comes from + the safe default (`false`) and `deploy.yml:1313` shows that the deployed value comes from `vars.USE_MANAGED_IDENTITY_SQL`. The variable's current value is repository configuration, not source. Check Settings, then Secrets and variables, then Actions. - **Whether the archive bacpac is still present and readable**: the blob path and storage account are diff --git a/docs-src/onboarding/devops-testing.md b/docs-src/onboarding/devops-testing.md index 6396ba5e..3e720abf 100644 --- a/docs-src/onboarding/devops-testing.md +++ b/docs-src/onboarding/devops-testing.md @@ -2,7 +2,7 @@ > **Chapter scope note.** The tier chapters (`tier-00` through the sweep) document every type > in the production codebase one by one. Test types are the logged exception: this chapter covers -> the **2,621** types that live in test projects, grouped by project purpose and foundational +> the **2,774** types that live in test projects, grouped by project purpose and foundational > infrastructure, not written as one section per `[Fact]`. Individual test methods are cited only > as worked examples. Cross-reference the tier chapters for the production types being tested. > The counts come from the Roslyn inventory (`00-inventory.md:25-135`), which scans @@ -55,10 +55,10 @@ the full Aspire stack running. None of that is available in the fast build job, its own job with its own prerequisites. **Read the gating carefully, it is easy to state wrongly.** ADC's `integration-tests` job is -**pull-request-only** (`MMCA.ADC/.github/workflows/deploy.yml:651`, with the reasoning at -`deploy.yml:646-650`) and is **not** in the `deploy` job's `needs` list (`deploy.yml:1310`). Its -sibling `build-and-test` is pull-request-only for the same stated reason (`deploy.yml:214-217`). The -comment under the `needs` list spells it out (`deploy.yml:1311-1313`): with strict branch protection +**pull-request-only** (`MMCA.ADC/.github/workflows/deploy.yml:702`, with the reasoning at +`deploy.yml:697-701`) and is **not** in the `deploy` job's `needs` list (`deploy.yml:1219`). Its +sibling `build-and-test` is pull-request-only for the same stated reason (`deploy.yml:222-225`). The +comment under the `needs` list spells it out (`deploy.yml:1220-1222`): with strict branch protection the PR validated the exact merge tree, so those jobs are required PR checks rather than push-time deploy gates, and they are not re-run on the merge push. The required contexts are exactly four, `build-and-test`, `supply-chain`, `integration-tests` and `coverage`, as the repo's own contributing @@ -68,16 +68,16 @@ What actually blocks the deploy is `supply-chain`, `cost-guard`, the **four** fr (`dr-freshness`, `load-freshness`, `cross-service-freshness`, `cross-browser-freshness`), `foundation`, `build-images`, the always-on `ai-eval-gate` (section 7), and then **exactly one of two complementary test gates**: the chromium `e2e-gate` on a UI diff, or -`backend-test-gate` on every other code diff (`deploy.yml:1310`, condition at `deploy.yml:1342-1357`). -`backend-test-gate` (`deploy.yml:448`) exists because those PR-only checks plus a ui-scoped +`backend-test-gate` on every other code diff (`deploy.yml:1219`, condition at `deploy.yml:1251-1266`). +`backend-test-gate` (`deploy.yml:499`) exists because those PR-only checks plus a ui-scoped `e2e-gate` composed into a hole: a backend-only push to `main` ran **no tests at all** before rolling out, with the post-deploy smoke gate as the only backstop, which is detection after the rollout -rather than prevention (`deploy.yml:432-436`). Its `if` is the exact complement of `e2e-gate`'s -(`deploy.yml:450` versus `deploy.yml:804`), so one of the two always runs on a code deploy, at zero +rather than prevention (`deploy.yml:486-487`). Its `if` is the exact complement of `e2e-gate`'s +(`deploy.yml:501` versus `deploy.yml:872`), so one of the two always runs on a code deploy, at zero added minutes on a UI deploy and one `CI.slnf` pass on a backend-only one. It deliberately runs -`MMCA.ADC.CI.slnf` and skips coverage collection (`deploy.yml:444-447`, run step at -`deploy.yml:476`): coverage is a review-time regression signal, not a rollout gate. It restores with -`--locked-mode` against the committed lock files (`deploy.yml:466`), so the gate cannot silently +`MMCA.ADC.CI.slnf` and skips coverage collection (`deploy.yml:495-498`, run step at +`deploy.yml:527`): coverage is a review-time regression signal, not a rollout gate. It restores with +`--locked-mode` against the committed lock files (`deploy.yml:517`), so the gate cannot silently resolve a different graph than the PR validated. [Rubric §17, DevOps & Deployment]: §17 assesses how consistently CI/CD enforces quality gates; the two-filter pattern is how the build stays fast on every push while the SQL-dependent tier still has @@ -98,22 +98,23 @@ excluded (`MMCA.ADC/MMCA.ADC.slnx:106-113` records the removal, along with the r `MMCA.Common.API.Tests`). `MMCA.Store.Integration.slnf` is the same shape over Catalog, Sales and Identity (`MMCA.Store/MMCA.Store.Integration.slnf:5-7`). -`MMCA.Common.slnx` (`MMCA.Common/MMCA.Common.slnx:1-61`) includes sixteen of the seventeen published -packages (`MMCA.Common/FACTS.md:19-38`): the meta package `MMCA.Common` (line 8), four Core -(`.Shared`, `.Domain`, `.Application`, `.Infrastructure`, lines 11-14), four Presentation (`.API`, -`.Grpc`, `.UI`, `.UI.Web`, lines 17-20), and seven Hosting (`.Aspire`, `.Aspire.Hosting`, `.Gateway`, -`.Testing`, `.Testing.Architecture`, `.Testing.E2E`, `.Testing.UI`, lines 23-29), plus **fourteen** -test projects (lines 33-52). The seventeenth package, `MMCA.Common.UI.Maui`, sits **outside the +`MMCA.Common.slnx` holds the framework's source projects (the published package list itself is owned +by `MMCA.Common/FACTS.md`): the meta package `MMCA.Common` (`MMCA.Common/MMCA.Common.slnx:8`), seven +Core (`.AI`, `.AI.Anthropic`, `.AI.OpenAI`, `.Shared`, `.Domain`, `.Application`, `.Infrastructure`, +lines 11-17), four Presentation (`.API`, `.Grpc`, `.UI`, `.UI.Web`, lines 20-23), and nine Hosting +(`.Aspire`, `.Aspire.Hosting`, `.Gateway`, `.Testing`, `.Testing.Architecture`, `.AI.Testing`, +`.Testing.Aspire`, `.Testing.E2E`, `.Testing.UI`, lines 26-34), plus **sixteen** test projects +(lines 38-59). The MAUI package, `MMCA.Common.UI.Maui`, sits **outside the `.slnx`** on purpose: its four MAUI target frameworks cannot build on the ubuntu runners the solution's CI uses, so it is built and packed by a dedicated windows job -(`MMCA.Common/.github/workflows/ci.yml:199`, rationale at `ci.yml:170-173`, build step at -`ci.yml:236`, -[ADR-042](https://ivanball.github.io/docs/adr/042-device-capability-abstraction.html)). Six test +(`MMCA.Common/.github/workflows/ci.yml:177`, rationale at `ci.yml:171-176`, build step at +`ci.yml:241`, +[ADR-042](https://ivanball.github.io/docs/adr/042-device-capability-abstraction.html)). Eight test projects are **also intentionally absent from the `.slnx`**: - `Tests/Presentation/MMCA.Common.UI.Gallery`, a backend-less Blazor host that renders the real login and register pages, a UI-primitives showcase and the notification pages; it exists solely to - give Playwright something to hit (`ci.yml:240-243`). + give Playwright something to hit (`ci.yml:243-245`). - `Tests/Presentation/MMCA.Common.UI.E2E.Tests`, the axe-core + render-smoke suite that hits the Gallery. - `Tests/Core/MMCA.Common.Infrastructure.Redis.Tests`, which runs `DistributedCacheService` against a @@ -121,19 +122,26 @@ projects are **also intentionally absent from the `.slnx`**: - `Tests/Performance/MMCA.Common.Benchmarks`, the BenchmarkDotNet suite behind the ADR-060 performance gate (see section 7). - `Tests/Core/MMCA.Common.Infrastructure.PostgreSQL.Tests`, which runs the shipped - `PostgreSQLDbContext` against a real PostgreSQL via Testcontainers (`ci.yml:887`). + `PostgreSQLDbContext` against a real PostgreSQL via Testcontainers (`ci.yml:916`). +- `Tests/Core/MMCA.Common.Infrastructure.SQLServer.Tests`, which runs the shipped + `SQLServerDbContext` and `DbContextFactory` against a real SQL Server via Testcontainers + (`ci.yml:925-931`, run step at `ci.yml:956`). - `Tests/Hosting/MMCA.Common.Testing.Aspire.AppHostTests`, which boots a sample AppHost through `Aspire.Hosting.Testing`; its two companion sample projects are out of the `.slnx` with it - (`ci.yml:944-946`). + (`ci.yml:1013-1015`). +- `Tests/Performance/MMCA.Common.LoadTests`, the weekly load tier, which runs from its own workflow + rather than from `ci.yml` (`MMCA.Common/.github/workflows/load-tests.yml:10-12`, section 7). The exclusions are not all for the same reason, and the comments say which is which. The gallery pair and the benchmark suite are out so `dotnet test --solution MMCA.Common.slnx` stays fast (no browser -install, no benchmark wall clock: `ci.yml:243` and `ci.yml:351`); the Redis, PostgreSQL and AppHost -projects are out because they need a **Docker daemon** or a real orchestrator that the fast unit loop -must not require (`ci.yml:813`, `ci.yml:859-864`, `ci.yml:894-897`). Each runs in its own CI job, +install, no benchmark wall clock: `ci.yml:246-247` and `ci.yml:354`); the Redis, PostgreSQL, SQL Server +and AppHost projects are out because they need a **Docker daemon** or a real orchestrator that the +fast unit loop must not require (`ci.yml:874-875`, `ci.yml:912-913`, `ci.yml:952-953`, +`ci.yml:1013-1014`), and the load tier is out because a timing tier on shared runners is a trend +signal rather than a merge gate (`load-tests.yml:10-12`). Each runs in its own CI job or workflow, built by csproj path, for example `dotnet test --project Tests/Presentation/MMCA.Common.UI.E2E.Tests/MMCA.Common.UI.E2E.Tests.csproj` -(`ci.yml:321`) and the Redis tier at `ci.yml:852`. +(`ci.yml:324`) and the Redis tier at `ci.yml:879`. [Rubric §28, Front-End Testing & Quality]: §28 assesses whether UI components have automated tests; the Gallery + E2E split is the mechanism that adds browser-level coverage without slowing the primary test loop. @@ -159,16 +167,18 @@ consequences: 1. **Exit code 8**, if a test project discovers zero tests MTP exits 8, not 0. Every CI `dotnet test` call passes `--minimum-expected-tests` with a floor sized to the tier it runs, so a discovery regression is a visible failure rather than a silent skip: `2000` for the whole - MMCA.Common solution (`MMCA.Common/.github/workflows/ci.yml:183`), `1` for the browser tier - (`ci.yml:321`), `1` for the AppHost tier (`ci.yml:946`), `40` for the cross-repo Helpdesk canary - (`ci.yml:569`), and `1` for four of - ADC's five test invocations (`MMCA.ADC/.github/workflows/deploy.yml:338,476,532,718`). The + MMCA.Common solution (`MMCA.Common/.github/workflows/ci.yml:161`), `1` for the browser tier + (`ci.yml:324`), `1` for the AppHost tier (`ci.yml:1015`), `40` for the cross-repo Helpdesk canary + (`ci.yml:580`), the exact `[Fact]` count of each Testcontainers tier (`15` for Redis at + `ci.yml:876-879`, `7` for PostgreSQL at `ci.yml:914-916`, `3` for SQL Server at `ci.yml:954-956`), + and `1` for four of + ADC's five test invocations (`MMCA.ADC/.github/workflows/deploy.yml:346,527,583,781`). The `backend-test-gate` step comment states the reasoning in one line: a filter or discovery breakage - that runs zero tests must fail here, not report a vacuous pass (`deploy.yml:474-475`). The one + that runs zero tests must fail here, not report a vacuous pass (`deploy.yml:525-526`). The one invocation deliberately without a floor is the paid live judge of `ai-eval-gate` - (`deploy.yml:535`): without `AI_API_KEY` (mapped from the `ANTHROPIC_API_KEY` repository secret, - `deploy.yml:545-547`) every case skips itself dynamically, and a zero-run there must not red a - deploy on a repo whose secret is absent (`deploy.yml:536-538`). + (`deploy.yml:586`): without `AI_API_KEY` (mapped from the `ANTHROPIC_API_KEY` repository secret, + `deploy.yml:596-598`) every case skips itself dynamically, and a zero-run there must not red a + deploy on a repo whose secret is absent (`deploy.yml:587-589`). 2. **Filter syntax differs.** You pass a `--` separator and then MTP's own filter flags: ```bash @@ -201,16 +211,16 @@ The inventory below is drawn from `00-inventory.md:25-135` (test-assembly counts files above. Counts are distinct types per project as reported by the Roslyn inventory scan, not `[Fact]` counts. -### MMCA.Common, 1,716 test types across 16 in-solution projects + 49 across 6 out-of-solution +### MMCA.Common, 1,811 test types across 16 in-solution projects + 72 across 8 out-of-solution **Unit, Core layer** | Project | Types | Purpose | |---|---|---| -| `MMCA.Common.Shared.Tests` | 58 | Unit tests for the Result pattern, `Error`, `ErrorType`, value objects, DTO contracts, supported cultures | -| `MMCA.Common.Domain.Tests` | 62 | Unit tests for entity hierarchy, aggregate root, domain events, specifications, soft-delete, PII redaction | -| `MMCA.Common.Application.Tests` | 398 | Unit tests for CQRS dispatcher, decorator pipeline, module loader, `IMessageBus`, validators, query pipeline, the exportable-user-data handler base, tenant-scoped cache keys and `ICacheService.GetOrCreate` | -| `MMCA.Common.Infrastructure.Tests` | 499 | Unit/integration tests for EF base contexts, outbox processor, repository, caching, JWT generation, JWKS provider, data-source resolver, two-factor and email-confirmation token services, plus the `Scheduling/`, `Persistence/Tenancy/`, `Persistence/AuditTrail/`, `Persistence/InternalCommands/` and hybrid-cache subtrees | +| `MMCA.Common.Shared.Tests` | 60 | Unit tests for the Result pattern, `Error`, `ErrorType`, value objects, DTO contracts, supported cultures | +| `MMCA.Common.Domain.Tests` | 64 | Unit tests for entity hierarchy, aggregate root, domain events, specifications, soft-delete, PII redaction | +| `MMCA.Common.Application.Tests` | 401 | Unit tests for CQRS dispatcher, decorator pipeline, module loader, `IMessageBus`, validators, query pipeline, the exportable-user-data handler base, tenant-scoped cache keys and `ICacheService.GetOrCreate` | +| `MMCA.Common.Infrastructure.Tests` | 514 | Unit/integration tests for EF base contexts, outbox processor, repository, caching, JWT generation, JWKS provider, data-source resolver, two-factor and email-confirmation token services, plus the `Scheduling/`, `Persistence/Tenancy/`, `Persistence/AuditTrail/`, `Persistence/InternalCommands/` and hybrid-cache subtrees | | `MMCA.Common.Infrastructure.Tests.MigrationsFixture` | 1 | A single-type companion project that gives the infrastructure suite a real migrations assembly to point EF at | | `MMCA.Common.AI.Tests` | 37 | The `MMCA.Common.AI` package's decorator chain over `IChatClient`: `BoundedChatClientTests`, `GuardrailChatClientTests`, `PromptTaggingChatClientTests`, `UsageRecordingChatClientTests`, `PromptContractTests`, `AiSettingsTests`, `AiProviderSelectionTests` and `AiServiceCollectionExtensionsTests`, plus three subtrees: `Providers/` (`AdapterFactoryTests`, `ProviderTagTests`), `Guardrails/` (request redaction, content policy, tool policy, streamed guardrails and their registration) and `Evaluation/` (`ReplayChatClientTests`, `RecordedResponsesTests` and the reference golden-replay and prompt-contract suites), driven by a `StubChatClient`/`StubGuardrail` pair in `Fixtures/` (`MMCA.Common/MMCA.Common.slnx:35`) | @@ -218,16 +228,16 @@ files above. Counts are distinct types per project as reported by the Roslyn inv | Project | Types | Purpose | |---|---|---| -| `MMCA.Common.API.Tests` | 155 | Tests for `ApiControllerBase`, exception handlers, idempotency filter, the shared middleware pipeline, JWKS endpoint (also the consolidated home of the ADC/Store middleware coverage), session-cookie auth, CSV export and tenant resolution | -| `MMCA.Common.Grpc.Tests` | 16 | Tests for `GrpcResultExceptionInterceptor`, `JwtForwardingClientInterceptor`, Result to `RpcException` mapping | -| `MMCA.Common.UI.Tests` | 152 | bUnit component tests for shared Blazor components (login/register forms, nav, theming, notification pages) | -| `MMCA.Common.UI.Web.Tests` | 12 | The Blazor Web host layer: `ServerTokenStorageService`, `BlazorCspPolicyProvider`, `WebFormFactor`, the client-config endpoint (`ClientConfig/ClientConfigEndpointTests`), the trusted-caller header (`Security/TrustedCaller*Tests`) and the UI-host hardening kit (`Hardening/BoundedCircuitHandlerTests`, `Hardening/UiRateLimitingTests`) | +| `MMCA.Common.API.Tests` | 170 | Tests for `ApiControllerBase`, exception handlers, idempotency filter, the shared middleware pipeline, JWKS endpoint (also the consolidated home of the ADC/Store middleware coverage), session-cookie auth, CSV export and tenant resolution | +| `MMCA.Common.Grpc.Tests` | 18 | Tests for `GrpcResultExceptionInterceptor`, `JwtForwardingClientInterceptor`, Result to `RpcException` mapping | +| `MMCA.Common.UI.Tests` | 170 | bUnit component tests for shared Blazor components (login/register forms, nav, theming, notification pages) | +| `MMCA.Common.UI.Web.Tests` | 24 | The Blazor Web host layer: `ServerTokenStorageService`, `BlazorCspPolicyProvider`, `WebFormFactor`, the client-config endpoint (`ClientConfig/ClientConfigEndpointTests`), the trusted-caller header (`Security/TrustedCaller*Tests`) and the UI-host hardening kit (`Hardening/BoundedCircuitHandlerTests`, `Hardening/UiRateLimitingTests`) | **Hosting** | Project | Types | Purpose | |---|---|---| -| `MMCA.Common.Aspire.Tests` | 50 | Tests for `AddServiceDefaults`, health-check registration, `OutboxPollFilterProcessor` telemetry suppression, the warmup readiness gate, metrics/trace-ratio toggles, security headers, Key Vault configuration, data protection and the Kestrel endpoint extensions | +| `MMCA.Common.Aspire.Tests` | 52 | Tests for `AddServiceDefaults`, health-check registration, `OutboxPollFilterProcessor` telemetry suppression, the warmup readiness gate, metrics/trace-ratio toggles, security headers, Key Vault configuration, data protection and the Kestrel endpoint extensions | | `MMCA.Common.Aspire.Hosting.Tests` | 4 | The AppHost-side resource builders the framework ships for consumer AppHosts | | `MMCA.Common.Gateway.Tests` | 7 | The shared YARP gateway kit: the rate-limit partitioning, correlation and downstream-readiness behavior the two app gateways inherit | | `MMCA.Common.Testing.Tests` | 27 | The framework dogfooding its own shipped test bases and helpers: `DecoratorPipelineOrderTests` and `MiddlewarePipelineOrderTests`, `MmcaGatewayHardeningTestsBaseTests`, `HandlerTestBaseTests`, `CrossServiceFixtureBaseTests`, `ServiceBusEmulatorFixtureBaseTests`, `DependencyInjectionAssertTests`, `FeatureManagementTestExtensionsTests`, `JwtTokenGeneratorTests`, `RateLimiterTestExtensionsTests`, `RecordingHttpForwarderTests` and `TestPollingTests` | @@ -237,26 +247,29 @@ files above. Counts are distinct types per project as reported by the Roslyn inv | Project | Types | Purpose | |---|---|---| -| `MMCA.Common.Architecture.Tests` | 230 | Thin subclasses of the shared bases plus the Common-only `*FitnessTests` family, alongside `CommonArchitectureMap` and the fake modules, drifted probes and fixtures the adversarial suites drive (see section 4) | +| `MMCA.Common.Architecture.Tests` | 254 | Thin subclasses of the shared bases plus the Common-only `*FitnessTests` family, alongside `CommonArchitectureMap` and the fake modules, drifted probes and fixtures the adversarial suites drive (see section 4) | -**Out-of-solution (each run by its own dedicated CI job)** +**Out-of-solution (each run by its own dedicated CI job or workflow)** | Project | Types | Purpose | |---|---|---| | `MMCA.Common.UI.Gallery` | 11 | Backend-less Blazor host; renders the real login/register pages, a primitives showcase and the notification pages for Playwright to hit | -| `MMCA.Common.UI.E2E.Tests` | 20 | Playwright axe-core WCAG 2.1 AA scans, render smoke, dark mode, web vitals, pseudo-localization and mobile top row against the Gallery | +| `MMCA.Common.UI.E2E.Tests` | 21 | Playwright axe-core WCAG 2.1 AA scans, render smoke, dark mode, web vitals, pseudo-localization and mobile top row against the Gallery | | `MMCA.Common.Infrastructure.Redis.Tests` | 2 | `DistributedCacheService` against a real Redis via Testcontainers (storage FORMAT fidelity: a mocked `IDistributedCache` cannot answer WRONGTYPE) | | `MMCA.Common.Benchmarks` | 6 | BenchmarkDotNet hot-path suite behind the ADR-060 performance gate (section 7) | -| `MMCA.Common.Infrastructure.PostgreSQL.Tests` | 7 | `PostgreSQLPersistenceTests` drives the shipped `PostgreSQLDbContext` against a real PostgreSQL container, with its own `PgThing` fixture aggregate, events and a recording dispatcher (`MMCA.Common/.github/workflows/ci.yml:880`) | -| `MMCA.Common.Testing.Aspire.AppHostTests` | 3 | `SampleAppHostFixture`, `SampleAppHostCollection` and `SampleAppHostTests`: a sample AppHost booted through the shipped `MMCA.Common.Testing.Aspire` fixtures (`ci.yml:946`) | +| `MMCA.Common.Infrastructure.PostgreSQL.Tests` | 7 | `PostgreSQLPersistenceTests` drives the shipped `PostgreSQLDbContext` against a real PostgreSQL container, with its own `PgThing` fixture aggregate, events and a recording dispatcher (`MMCA.Common/.github/workflows/ci.yml:916`) | +| `MMCA.Common.Testing.Aspire.AppHostTests` | 3 | `SampleAppHostFixture`, `SampleAppHostCollection` and `SampleAppHostTests`: a sample AppHost booted through the shipped `MMCA.Common.Testing.Aspire` fixtures (`ci.yml:1015`) | +| `MMCA.Common.Infrastructure.SQLServer.Tests` | 8 | The shipped `SQLServerDbContext` and `DbContextFactory` against a real SQL Server container: session-scoped `SET IDENTITY_INSERT`, server-issued rowversions and the outbox row landing in the same `SaveChanges` as its aggregate (`ci.yml:925-929`) | +| `MMCA.Common.LoadTests` | 14 | The weekly load tier: `OutboxThroughputLoadTests`, `PagedQueryLoadTests` and the `Support/` stack (`LoadStack`, `PagedQueryFixture`, `LoadItem`, `CountingMessageBus`, `LoadResults`) over a temp-file SQLite database, run by `load-tests.yml` rather than `ci.yml` (section 7) | -The last two rows are the tiers that landed after the previous inventory pass; the current scan covers -them, so the totals below cover all 22 MMCA.Common test projects. Both run outside every solution -file, each from its own CI job, and both are described in section 7. Keeping them out of the `.slnx` -is deliberate: a tier that needs Docker or a trusted development certificate must not be able to break -a plain `dotnet build` of the solution for an engineer who has neither. +The current scan covers every row above, so the totals below cover all 24 MMCA.Common test projects. +Every out-of-solution row runs from its own CI job, or in the load tier's case its own weekly +workflow, and each is described in section 5 or section 7. Keeping them out of the `.slnx` is +deliberate: a tier that needs Docker, a trusted development certificate or a wall-clock budget must +not be able to break or slow a plain `dotnet build` of the solution for an engineer who has none of +those. -### MMCA.ADC, 853 test types across 32 in-solution projects + 3 across 1 out-of-solution +### MMCA.ADC, 888 test types across 32 in-solution projects + 3 across 1 out-of-solution **Unit, per-module, per-layer (Identity module)** @@ -264,7 +277,7 @@ a plain `dotnet build` of the solution for an engineer who has neither. |---|---|---| | `MMCA.ADC.Identity.Domain.Tests` | 4 | `User` aggregate factory methods, invariants, soft-delete | | `MMCA.ADC.Identity.Application.Tests` | 34 | Command/query handler tests for register, login, external OAuth, profile management, plus the GDPR export path and the per-module export sections | -| `MMCA.ADC.Identity.Shared.Tests` | 2 | The module's permission grants (`IdentityPermissionGrantsTests`) and the disabled attendee-query service (`DisabledAttendeeQueryServiceTests`) | +| `MMCA.ADC.Identity.Shared.Tests` | 3 | The module's permission grants (`IdentityPermissionGrantsTests`) and the disabled attendee-query service (`DisabledAttendeeQueryServiceTests`) | | `MMCA.ADC.Identity.API.Tests` | 11 | Controller helper tests, rate-limit bypass | | `MMCA.ADC.Identity.Infrastructure.Tests` | 9 | Token service, JWKS provider, EF configuration, the refresh-session store | | `MMCA.ADC.Identity.UI.Tests` | 11 | bUnit tests for `Profile`, login route authorization | @@ -274,19 +287,19 @@ a plain `dotnet build` of the solution for an engineer who has neither. | Project | Types | Purpose | |---|---|---| | `MMCA.ADC.Conference.Domain.Tests` | 34 | Event/Session/Speaker/Sponsor aggregate factories, invariants, domain events | -| `MMCA.ADC.Conference.Application.Tests` | 193 | Handler tests for the Conference controllers' use cases (bulk), including the `Sponsors/` create, update, public-filter and mapper tests, the `Partners/` and `SessionAssets/` subtrees, and the batch event question-answer handler and validator | +| `MMCA.ADC.Conference.Application.Tests` | 196 | Handler tests for the Conference controllers' use cases (bulk), including the `Sponsors/` create, update, public-filter and mapper tests, the `Partners/` and `SessionAssets/` subtrees, and the batch event question-answer handler and validator | | `MMCA.ADC.Conference.Shared.Tests` | 9 | DTO tests (category, event, session, speaker), the current-event defaults and selector, the Sessionize code format and the disabled event-live validation service | | `MMCA.ADC.Conference.API.Tests` | 26 | Controller registration, route tests, `SponsorsControllerTests`, `PartnersControllerTests`, `SessionAssetsControllerTests`, the split event and speaker controllers (`EventLifecycleControllerTests`, `SpeakerLinksControllerTests`, `SpeakerSessionsControllerTests`), `ConferencePermissionGrantsTests` and `EntityExportAuthorizationTests` | | `MMCA.ADC.Conference.Infrastructure.Tests` | 11 | EF entity configuration, module seeding, the Sessionize import, the AI session-scoring services, the score-response guardrail and its registration (`SessionScoreResponseGuardrailTests`, `ConferenceAiGuardrailsRegistrationTests`) and the session-scores cache evictor | -| `MMCA.ADC.Conference.Scoring.Evaluation.Tests` | 10 | The AI session scorer's behavioural suite: `GoldenReplayTests`, `PromptContractTests` and the opt-in `LiveJudgeTests` (`MMCA.ADC/MMCA.ADC.slnx:88`, in `CI.slnf:49`). Its 10 types are inside the 853 below (`00-inventory.md`, current scan); the gate that runs it is in section 7 | -| `MMCA.ADC.Conference.UI.Tests` | 66 | bUnit tests for session/speaker components and dashboards, the sponsor create/detail pages and the public sponsor list | +| `MMCA.ADC.Conference.Scoring.Evaluation.Tests` | 10 | The AI session scorer's behavioural suite: `GoldenReplayTests`, `PromptContractTests` and the opt-in `LiveJudgeTests` (`MMCA.ADC/MMCA.ADC.slnx:88`, in `CI.slnf:49`). Its 10 types are inside the 888 below (`00-inventory.md`, current scan); the gate that runs it is in section 7 | +| `MMCA.ADC.Conference.UI.Tests` | 81 | bUnit tests for session/speaker components and dashboards, the sponsor create/detail pages and the public sponsor list | **Unit, per-module, per-layer (Engagement module)** | Project | Types | Purpose | |---|---|---| | `MMCA.ADC.Engagement.Domain.Tests` | 11 | Bookmark, LivePoll and SessionQuestion aggregates, plus `CheckIn`, `PointsEntry` and the leaderboard opt-in | -| `MMCA.ADC.Engagement.Application.Tests` | 65 | Bookmark, feedback and live-layer handlers, plus the `CheckIns/` subtree (attendee, manual, room and sponsor-visit check-in, badge issue, attendance stats) and the `Points/` subtree (awarder, leaderboard, my-points, organizer overview, and the domain/integration-event points handlers) | +| `MMCA.ADC.Engagement.Application.Tests` | 70 | Bookmark, feedback and live-layer handlers, plus the `CheckIns/` subtree (attendee, manual, room and sponsor-visit check-in, badge issue, attendance stats) and the `Points/` subtree (awarder, leaderboard, my-points, organizer overview, and the domain/integration-event points handlers) | | `MMCA.ADC.Engagement.Shared.Tests` | 6 | The badge payload, check-in scope names and settings, the points settings and subject keys, and the disabled bookmark-count service (`DisabledBookmarkCountServiceTests`) | | `MMCA.ADC.Engagement.API.Tests` | 11 | Controller surface (bookmarks, live polls and live-poll voting, session questions, check-ins, points) and the module's permission grants | | `MMCA.ADC.Engagement.Infrastructure.Tests` | 4 | EF config | @@ -303,14 +316,14 @@ a plain `dotnet build` of the solution for an engineer who has neither. | Project | Types | Purpose | |---|---|---| -| `MMCA.ADC.Architecture.Tests` | 56 | 54 fitness-function classes plus `AdcArchitectureMap` and the `ServiceModels` fixture (see section 4) | +| `MMCA.ADC.Architecture.Tests` | 57 | Fitness-function classes plus `AdcArchitectureMap` and the `ServiceModels` fixture (see section 4) | **Hosts and services** | Project | Types | Purpose | |---|---|---| | `MMCA.ADC.Gateway.Tests` | 9 | YARP route map, the ADR-058 conformance subclasses `SecurityHeadersTests`, `GracefulShutdownTests` and `GatewayHardeningTests` against a Production-pinned Gateway boot, plus `AppHostBicepParityTests`, which reads the AppHost and the production Bicep as text (see section 4) | -| `MMCA.ADC.Services.Tests` | 7 | The gRPC export services and their adapters, with a `FakeServerCallContext` | +| `MMCA.ADC.Services.Tests` | 17 | The gRPC export services and their adapters, with a `FakeServerCallContext` | | `MMCA.ADC.UI.Web.Tests` | 7 | The Blazor Web head itself: `SecurityHeadersTests` and `UiRateLimitingTests` against a `ConferenceUiHostApplicationFactory`, plus `BoundedCircuitHandlerTests`, `FallbackAuthorizationTests`, `ClientConfigEndpointTests` and `SerilogBootstrapTests` (`MMCA.ADC/MMCA.ADC.slnx:117`, in `CI.slnf:61`) | **Integration (per-service WebApplicationFactory, in `MMCA.ADC.Integration.slnf` only)** @@ -339,23 +352,23 @@ a plain `dotnet build` of the solution for an engineer who has neither. | Project | Types | Purpose | |---|---|---| -| `MMCA.ADC.AppHost.SmokeTests` | 3 | `AppHostCompositionSmokeTests`: boots the real Aspire stack and asserts the composition. Deliberately outside every `.slnx`/`.slnf` and restored, lock-checked and built by explicit path (`MMCA.ADC/.github/workflows/cross-service-tests.yml:221,225,264`) | +| `MMCA.ADC.AppHost.SmokeTests` | 3 | `AppHostCompositionSmokeTests`: boots the real Aspire stack and asserts the composition. Deliberately outside every `.slnx`/`.slnf` and restored, lock-checked and built by explicit path (`MMCA.ADC/.github/workflows/cross-service-tests.yml:225,229,268`) | ### Test-type totals -- **MMCA.Common:** the 16 in-solution projects sum to 58 + 62 + 398 + 499 + 1 + 37 + 155 + 16 + 152 + - 12 + 50 + 4 + 8 + 7 + 27 + 230 = **1,716**; the 6 out-of-solution projects add 11 + 20 + 2 + 6 + 7 + - 3 = **49**, for **1,765**. -- **MMCA.ADC:** 71 (Identity) + 349 (Conference, incl. the 10-type scoring evaluation suite) + 133 (Engagement) + 5 (Notification) + 56 - (architecture) + 108 (four integration projects) + 16 (two Testcontainers tiers) + 9 (Gateway) + 7 - (Services) + 7 (UI.Web) + 92 (E2E) = **853** in-solution, plus the 3-type AppHost smoke project = - **856**. -- **Combined test projects: 2,621.** Separately, the five shipped testing packages contribute - another **154** types (`MMCA.Common.Testing` 25, `.Testing.Architecture` 68, `.Testing.Aspire` 10, - `.Testing.E2E` 32, `.Testing.UI` 19): those are shipped product, not tests, which is why they are +- **MMCA.Common:** the 16 in-solution projects sum to 60 + 64 + 401 + 514 + 1 + 37 + 170 + 18 + 170 + + 24 + 52 + 4 + 8 + 7 + 27 + 254 = **1,811**; the 8 out-of-solution projects add 11 + 21 + 2 + 6 + 7 + + 3 + 8 + 14 = **72**, for **1,883**. +- **MMCA.ADC:** 72 (Identity) + 367 (Conference, incl. the 10-type scoring evaluation suite) + 138 (Engagement) + 5 (Notification) + 57 + (architecture) + 108 (four integration projects) + 16 (two Testcontainers tiers) + 9 (Gateway) + 17 + (Services) + 7 (UI.Web) + 92 (E2E) = **888** in-solution, plus the 3-type AppHost smoke project = + **891**. +- **Combined test projects: 2,774.** Separately, the five shipped testing packages contribute + another **153** types (`MMCA.Common.Testing` 25, `.Testing.Architecture` 68, `.Testing.Aspire` 10, + `.Testing.E2E` 32, `.Testing.UI` 18): those are shipped product, not tests, which is why they are counted apart. The inventory also lists `MMCA.Common.AI.Testing` (5 types, `MMCA.Common/Source/Hosting/MMCA.Common.AI.Testing/`), which ships beside the AI package rather - than in this family and is not in the 154. + than in this family and is not in the 153. [Rubric §14, Testability & Test Strategy]: §14 assesses the breadth and meaningfulness of the test suite across all layers; the project layout above, unit per layer, arch per repo, @@ -888,7 +901,7 @@ and typed assertions for the framework wiring contracts, health, liveness, JWKS, MMCA.Common consumes the package from both sides: `MMCA.Common.Testing.Aspire.Tests` (8 types, in-solution) unit-tests the preconditions and probes, and the out-of-solution `MMCA.Common.Testing.Aspire.AppHostTests` (3 types) boots a sample AppHost through the fixtures in its -own CI job (`MMCA.Common/.github/workflows/ci.yml:939`, section 7). +own CI job (`MMCA.Common/.github/workflows/ci.yml:958`, section 7). [Rubric §33, Developer Experience]: §33 assesses how much harness an engineer has to build before writing the test they actually wanted. Shipping the AppHost boot, the readiness wait and the @@ -962,7 +975,7 @@ The walkthroughs below describe **what each rule enforces** (and the count of fa the rule *implementations* live in the shared package's `ArchitectureRules.*` + `*TestsBase` files, not in the per-repo test class. -### MMCA.Common.Architecture.Tests, 230 types +### MMCA.Common.Architecture.Tests, 254 types Located at `MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/`, filed into the same per-area folders as the rule library (`Api/`, `Contracts/`, `Cqrs/`, `Domain/`, `Governance/`, @@ -1129,7 +1142,7 @@ Supply-Chain]: §32 assesses whether dependency versions are tracked, pinned, an accidental bumps; this is the build-time gate for the most dangerous version upgrades in the codebase. (See the primer `00-primer.md#nuget-lock-files--pinned-audited-sources` for context.) -### MMCA.ADC.Architecture.Tests, 56 types +### MMCA.ADC.Architecture.Tests, 57 types Located at `MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/`: 54 fitness-function classes plus `AdcArchitectureMap` and the `ServiceModels` fixture (`Domain/DeleteBehaviorConventionTests.cs:70`). Like the Common project, most are thin subclasses of the shared @@ -1316,14 +1329,14 @@ inventory and an unexamined one. The runtime rules above have a compile-time counterpart that ships inside the packages rather than in a test project. `MMCA.Common.Shared` carries a `buildTransitive` targets file that gives any consumer project named `{App}.{Module}.{Layer}` the layer rules at `ResolveProjectReferences`, with no opt-in -(`MMCA.Common/.github/workflows/ci.yml:759-761`): `MMCA0001` for a forbidden project reference and -`MMCA0002` for a forbidden package reference. The proof runs in `package-consumption` (`ci.yml:669`), +(`MMCA.Common/.github/workflows/ci.yml:783-785`): `MMCA0001` for a forbidden project reference and +`MMCA0002` for a forbidden package reference. The proof runs in `package-consumption` (`ci.yml:693`), the job that consumes the freshly packed local feed, in the step "Layer rules ship with the packages -(compile-time probes)" (`ci.yml:757`). It builds one positive probe and two negative ones, and each -negative probe must both fail and fail for its named code (`ci.yml:783`, `ci.yml:786`). The negatives +(compile-time probes)" (`ci.yml:781`). It builds one positive probe and two negative ones, and each +negative probe must both fail and fail for its named code (`ci.yml:801`, `ci.yml:807`, `ci.yml:810`). The negatives carry the weight: a passing probe on its own would also pass if the targets file were silently missing from the package, so only a build refused with the expected code proves the rules arrived -(`ci.yml:762-763`). +(`ci.yml:786-787`). --- @@ -1399,11 +1412,11 @@ Docker daemon (`MMCA.ADC.slnx:99-102` records exactly that). Neither is in `deploy.needs`, because the gating jobs have no Docker daemon. Both instead run on a weekday-nightly schedule (`cross-service-tests.yml:27`, jobs `cross-service` at -line 78 and `servicebus-emulator-smoke` at line 156) and their **recency** gates the deploy through -`cross-service-freshness` (`MMCA.ADC/.github/workflows/deploy.yml:933`): the deploy fails if the last +line 78 and `servicebus-emulator-smoke` at line 159) and their **recency** gates the deploy through +`cross-service-freshness` (`MMCA.ADC/.github/workflows/deploy.yml:936`): the deploy fails if the last successful nightly proving **both** tiers is stale, and it counts a run only when both required -broker jobs actually succeeded rather than skipped (`deploy.yml:994`). The emulator smoke has been **authoritative** rather than advisory since -2026-08-31 (`cross-service-tests.yml:126-135`), and the same comment records the standing rule: if it +broker jobs actually succeeded rather than skipped (`deploy.yml:944-948`, through the shared `freshness-gate` action). The emulator smoke has been **authoritative** rather than advisory since +2026-08-31 (`cross-service-tests.yml:132-141`), and the same comment records the standing rule: if it goes red, fix it or dispatch a green run, do not re-add `continue-on-error` to unblock a deploy. The window was widened from 3 to 5 days when the nightly moved to weekdays plus skip-if-unchanged, so a legitimate weekend or holiday gap does not red the gate. Both jobs also sit @@ -1413,23 +1426,23 @@ on the freshness of the evidence rather than on the run itself. MMCA.Store mirro with `MMCA.Store.CrossService.IntegrationTests` and `MMCA.Store.ServiceBusEmulator.IntegrationTests` behind its own `cross-service-freshness` gate (`MMCA.Store/.github/workflows/deploy.yml:716`). -A third nightly job, `apphost-smoke` (`cross-service-tests.yml:204`), boots the real Aspire stack and -asserts its composition. It is **`continue-on-error: true`** (line 209) and, unlike the emulator smoke +A third nightly job, `apphost-smoke` (`cross-service-tests.yml:208`), boots the real Aspire stack and +asserts its composition. It is **`continue-on-error: true`** (line 213) and, unlike the emulator smoke above it, the `cross-service-freshness` deploy gate does not look at it at all, so nothing it does can ever gate a deploy. Three of its steps are worth reading, because each one is a precondition the job has to create for itself. A **lock-drift guard** compares every `MMCA.Common.*` entry in the project's committed -`packages.lock.json` against the single central pin and fails on a mismatch (`cross-service-tests.yml:225`): +`packages.lock.json` against the single central pin and fails on a mismatch (`cross-service-tests.yml:229`): this project sits outside every solution, so no gating restore covers its lock, and one lock really did sit at `1.176.0` while the repo pinned `1.177.0`. It deliberately does **not** use `--locked-mode`, because an Aspire AppHost's lock carries a RID-specific `Aspire.Dashboard.Sdk.` entry that makes locked mode fail with NU1004 on this runner whether or not anything drifted. A **dev-certificate trust** -step follows the build (`cross-service-tests.yml:270-271`): Notification launches with the `https` +step follows the build (`cross-service-tests.yml:274-275`): Notification launches with the `https` profile, so its stock `/alive` probe negotiates TLS against the ASP.NET development certificate, and on a fresh runner every probe fails with `UntrustedRoot` until the certificate is trusted, leaving the gateway's `WaitFor(notification)` edge waiting out the budget. The run step sets `MMCA_APPHOST_TESTS` -(`cross-service-tests.yml:273`), the framework's ADR-117 opt-in: without it the fixture never boots an +(`cross-service-tests.yml:278`, env at line 289), the framework's ADR-117 opt-in: without it the fixture never boots an orchestrator and every test skips with a named reason, which is exactly what a developer machine and every other CI job should get. The ephemeral RS256 keypair Identity needs is no longer minted by an `openssl` step here: `AppHostFixtureBase` mints it and exports it through the same `E2E_JWT_*` channel @@ -1443,7 +1456,7 @@ generator. That is what an honestly staged new gate looks like. ### MMCA.Common unit-level infrastructure tests -`MMCA.Common.Infrastructure.Tests` (499 types) uses SQLite-backed `EnsureCreated` contexts for +`MMCA.Common.Infrastructure.Tests` (514 types) uses SQLite-backed `EnsureCreated` contexts for tests that need a real EF pipeline, with `MMCA.Common.Infrastructure.Tests.MigrationsFixture` beside it as the real migrations assembly those tests point EF at. SQLite avoids the SQL Server dependency entirely, which is why `MMCA.Common` builds and tests without any SQL Server or Docker in the local @@ -1453,22 +1466,27 @@ tested (outbox persistence, repository queries, soft-delete filters). Where the is the thing under test, SQLite is not enough: `MMCA.Common.Infrastructure.Redis.Tests` exists precisely because a counter written as a string and read back as a hash round-trips fine against a mocked `IDistributedCache` and answers WRONGTYPE against a real server, so that one project runs Redis -via Testcontainers, out of the slnx, in its own CI job (`MMCA.Common/.github/workflows/ci.yml:806,845`). -The same argument now has a second instance: `postgresql-integration` (`ci.yml:854`) runs the shipped +via Testcontainers, out of the slnx, in its own CI job (`MMCA.Common/.github/workflows/ci.yml:837,869`). +The same argument now has a second instance: `postgresql-integration` (`ci.yml:881`) runs the shipped `PostgreSQLDbContext` against a real PostgreSQL container because the SQL the framework **emits** is what matters there, and a model assertion cannot express it (a partial-index predicate written the SQL Server way, a soft-delete predicate comparing a boolean to `0`, or a non-UTC `DateTime` Kind all build -a valid EF model and are rejected by the server, `ci.yml:859-864`). A third, `apphost-testing` -(`ci.yml:889`), boots a sample AppHost through `Aspire.Hosting.Testing` to execute the one layer +a valid EF model and are rejected by the server, `ci.yml:886-891`). A third, `sqlserver-integration` +(`ci.yml:918`), runs the shipped `SQLServerDbContext` and `DbContextFactory` against a real SQL Server +because there the **session** state and server-generated values matter, and neither a mock nor SQLite +can express them: `SET IDENTITY_INSERT` holds only on the session that ran it, a rowversion is issued +by the server on every write, and the outbox row must reach the database in the same `SaveChanges` as +its aggregate (`ci.yml:925-929`). It is not a required check yet; promoting it is a branch-protection +setting (`ci.yml:930-931`). A fourth, `apphost-testing` (`ci.yml:958`), boots a sample AppHost through `Aspire.Hosting.Testing` to execute the one layer nothing else runs, the AppHost wiring itself: the solution build never runs an AppHost and every in-process tier boots hosts directly through `WebApplicationFactory`, so a renamed resource, an -unresolvable reference or a `WaitFor` cycle is invisible everywhere else (`ci.yml:899-902`). It is -**advisory** (`continue-on-error`, `ci.yml:907`) on purpose: it is the only tier that starts a real +unresolvable reference or a `WaitFor` cycle is invisible everywhere else (`ci.yml:968-971`). It is +**advisory** (`continue-on-error`, `ci.yml:976`) on purpose: it is the only tier that starts a real orchestrator, it is the slowest thing in the repo per assertion, and its shared-runner failure modes are not yet proven, so it reds the job for visibility without failing the run until it earns a green streak. Like the ADC smoke it trusts nothing about the runner: it trusts the development certificate -itself (`ci.yml:923`) and gates on `MMCA_APPHOST_TESTS` with a `--minimum-expected-tests` floor so a -silently skipped tier is visible rather than a green no-op (`ci.yml:946`). +itself (`ci.yml:992`) and gates on `MMCA_APPHOST_TESTS` with a `--minimum-expected-tests` floor so a +silently skipped tier is visible rather than a green no-op (`ci.yml:1015`). ### E2E tests @@ -1495,12 +1513,12 @@ do not "fix" it by disabling prerender. When a run does go red, the evidence is in the processes' own logs, not the AppHost's. The `Collect service logs` step runs on `always()` and sweeps the working tree, `$HOME` and `/tmp` for every `MMCAADC*.txt` rolling Serilog file, then flattens them into `artifacts/service-logs/` -(`e2e.yml:334-349`). The glob covers the four services and the UI host (`MMCAADCUIWeb.txt`), -and the UI host is in the sweep because it runs the Blazor Server circuit (lines 336-337). The search +(`e2e.yml:339-352`). The glob covers the four services and the UI host (`MMCAADCUIWeb.txt`), +and the UI host is in the sweep because it runs the Blazor Server circuit (lines 341-342). The search is that broad because the directory each process writes to depends on the working directory Aspire launches it from, and it is needed at all because the AppHost orchestrator log carries only DCP and -dashboard chatter, never the services' own output (lines 338-341). The bundle is uploaded only on -`failure()` with a 3-day retention (lines 355-365): a green run needs no offline triage, and each +dashboard chatter, never the services' own output (lines 343-346). The bundle is uploaded only on +`failure()` with a 3-day retention (the upload step at line 360): a green run needs no offline triage, and each browser's diagnostics bundle runs to roughly 350 MB. The cross-browser matrix runs the same suite once per engine by varying `E2E_BROWSER`: @@ -1704,43 +1722,45 @@ A test tier only means something once you know what it blocks. This is the map. | Testcontainers cross-service / broker | Docker | Nightly `cross-service-tests.yml` | The deploy, indirectly, via `cross-service-freshness` | | AppHost composition smoke | Docker | Nightly `cross-service-tests.yml`, `continue-on-error` | Nothing, deliberately | | Real-engine persistence (PostgreSQL) | Docker | Common `postgresql-integration` | Nothing until it is added to branch protection; the job is written to be promotable | +| Real-engine persistence (SQL Server) | Docker | Common `sqlserver-integration` | Nothing until it is added to branch protection (`MMCA.Common/.github/workflows/ci.yml:930-931`) | | AppHost orchestration | Dev HTTPS certificate, `MMCA_APPHOST_TESTS` | Common `apphost-testing`, `continue-on-error` | Nothing, deliberately | | Browser (gallery) | Playwright, no backend | Common `ui-e2e`, three engines | Merge, all three engines | | Browser (full stack) | full Aspire stack | ADC/Store `e2e-gate`, chromium only | The deploy, when the change is ui-scoped | | Browser (full stack), firefox and webkit | full Aspire stack | ADC `e2e.yml` on alternating weekly crons, one engine each | The deploy, indirectly, via `cross-browser-freshness` | | Benchmarks | none | Common `performance-smoke` | Merge | +| Load at volume (outbox drain, paged queries) | none, temp-file SQLite | Common `load-tests.yml`, weekly cron plus manual dispatch | Nothing, deliberately: a trend signal, not a merge gate | | Cross-repo consumer canary | ephemeral SQL Server | Common `consumer-source-build`, building Helpdesk against this PR's framework source | Merge, on MMCA.Common PRs | | Template generation smoke | none | Helpdesk `template-smoke`, package mode from nuget.org | Merge, on Helpdesk PRs | ### The accessibility gate (ADR-063) -MMCA.Common's `ui-e2e` job (`MMCA.Common/.github/workflows/ci.yml:270`) builds the out-of-slnx -gallery plus E2E project (`ci.yml:284`) and runs the axe scans across a `chromium, firefox, webkit` -matrix (`ci.yml:253-254`), one engine per leg via `E2E_BROWSER` (`ci.yml:318`), with -`fail-fast: false` (`ci.yml:252`) so each engine reports independently. **All three are required merge +MMCA.Common's `ui-e2e` job (`MMCA.Common/.github/workflows/ci.yml:248`) builds the out-of-slnx +gallery plus E2E project (`ci.yml:287`) and runs the axe scans across a `chromium, firefox, webkit` +matrix (`ci.yml:256-257`), one engine per leg via `E2E_BROWSER` (`ci.yml:321`), with +`fail-fast: false` (`ci.yml:255`) so each engine reports independently. **All three are required merge checks**, three of the eight enumerated in `MMCA.Common/CONTRIBUTING.md:60-71` (firefox was promoted -2026-07-12 and webkit 2026-07-16 after 11 consecutive green main runs, `ci.yml:256-258` and +2026-07-12 and webkit 2026-07-16 after 11 consecutive green main runs, `ci.yml:258-260` and `CONTRIBUTING.md:63-65`). Only the chromium leg collects coverage; the other two run the same command -plain, so the merged report is not engine-dependent (`ci.yml:321-326`). That file also names the live ruleset as authoritative over its own copy +plain, so the merged report is not engine-dependent (`ci.yml:318`, run step at `ci.yml:322`). That file also names the live ruleset as authoritative over its own copy (`CONTRIBUTING.md:75-77`), which is the right instinct for any list of gates. -The deployed apps gate the deploy instead: ADC's `e2e-gate` (`MMCA.ADC/.github/workflows/deploy.yml:793`) +The deployed apps gate the deploy instead: ADC's `e2e-gate` (`MMCA.ADC/.github/workflows/deploy.yml:861`) calls the reusable `e2e.yml` (line 805) with `browsers: '["chromium"]'` (line 807), and the `deploy` -job waits on it (`deploy.yml:1310,1355`). Store's is the same shape at +job waits on it (`deploy.yml:1219,1264`). Store's is the same shape at `MMCA.Store/.github/workflows/deploy.yml:584,594,945`. **The deploy gate is ui-scoped and may legitimately skip.** Both apps gate `e2e-gate` on a `ui` change -filter (`MMCA.ADC/.github/workflows/deploy.yml:804`), and the `deploy` job's condition accepts +filter (`MMCA.ADC/.github/workflows/deploy.yml:872`), and the `deploy` job's condition accepts `success` **or** `skipped` for that need while requiring `success` from every unconditional one -(`deploy.yml:1355`). That asymmetry is deliberate and was learned the hard way: under default +(`deploy.yml:1264`). That asymmetry is deliberate and was learned the hard way: under default `success()` semantics a legitimately skipped `e2e-gate` cascaded into a skipped deploy, so a -green run shipped nothing (`deploy.yml:1329-1331`). +green run shipped nothing (`deploy.yml:1238-1240`). On ADC the cost of that fix is now bounded rather than open-ended. `backend-test-gate` -(`deploy.yml:450`) carries the exact complementary condition and the same success-or-skipped -allowance (`deploy.yml:1356`), so a skipped `e2e-gate` means a *run* `backend-test-gate` and the +(`deploy.yml:501`) carries the exact complementary condition and the same success-or-skipped +allowance (`deploy.yml:1265`), so a skipped `e2e-gate` means a *run* `backend-test-gate` and the invariant "no production deploy without test execution" holds without making either gate -unconditional (`deploy.yml:799-803`, `deploy.yml:1334-1340`). What a +unconditional (`deploy.yml:867-871`, `deploy.yml:1243-1249`). What a backend-only ADC deploy still ships without is a **browser scan**: axe did not run on that commit, and the post-deploy smoke gate is the backstop for anything the unit tier cannot see. On MMCA.Store, which has no `backend-test-gate`, the original exposure remains: a backend-only deploy runs no test tier at @@ -1756,12 +1776,12 @@ Moving firefox and webkit off the deploy gate bought about 40 minutes per UI dep hole of the same shape the broker tier once had: the nightly `e2e.yml` matrix is `fail-fast: false` with `continue-on-error` on the non-chromium legs, so those legs could stay red for weeks without blocking a single deploy. The proof was produced and then discarded -(`MMCA.ADC/.github/workflows/deploy.yml:1017-1022`). `cross-browser-freshness` (`deploy.yml:1039`) -closes it the way `cross-service-freshness` (`deploy.yml:933`) closes the broker one: it does not run +(`MMCA.ADC/.github/workflows/deploy.yml:966-970`). `cross-browser-freshness` (`deploy.yml:987`) +closes it the way `cross-service-freshness` (`deploy.yml:936`) closes the broker one: it does not run the engines, it refuses to deploy unless a recent run passed on **each** of them. -- **Push-only** (`deploy.yml:1042`), like every other freshness gate: a PR is not a rollout. -- **A 10-day window** (`deploy.yml:1048`). The two engines alternate on separate weekly crons +- **Push-only** (`deploy.yml:990`), like every other freshness gate: a PR is not a rollout. +- **A 10-day window** (`deploy.yml:1006`). The two engines alternate on separate weekly crons (Monday firefox, Thursday webkit), so the window is a 7-day cadence plus slack for a skipped night (the nightly's should-run guard skips a night with no new commits) and for a manual re-run landing late. @@ -1771,13 +1791,16 @@ the engines, it refuses to deploy unless a recent run passed on **each** of them an engine's red need not red the run, another job's red can red a run in which this engine passed, and the should-run guard can conclude a run `success` with every leg skipped. Each engine is resolved independently, because their proofs normally come from two different runs, and the older - of the two decides the gate (the per-engine job lookup is at `deploy.yml:1085-1098`, the - "no run found" failure at `deploy.yml:1100`). + of the two decides the gate (the step at `deploy.yml:995-1000` hands this to MMCA.Common's shared `freshness-gate` + composite action, `deploy.yml:1001`, in its `per-job` mode, + `MMCA.Common/.github/actions/freshness-gate/action.yml:44-46`, which fails when either engine is + missing or stale). - **Break-glass is loud, not silent.** A `workflow_dispatch` skip requires a justification; without one the step fails rather than waving the deploy through, and with one it writes a warning and a - step-summary block naming what was not verified (`deploy.yml:1064-1070`). The skip path exits `success` + step-summary entry (`MMCA.Common/.github/actions/freshness-gate/action.yml:93-106`). That logic lives + in the shared action rather than in this file, so ADC and Store run one copy of it (`action.yml:7-8`). The skip path exits `success` inside the step, which is why `deploy`'s condition can demand `success` from this need - unconditionally (`deploy.yml:1352`). + unconditionally (`deploy.yml:1261`). [Rubric §22, Responsive & Cross-Browser]: §22 assesses whether the app is verified on more than one rendering engine; this gate is what makes the nightly cross-engine matrix *enforced* coverage rather @@ -1788,12 +1811,12 @@ than a report nobody reads, without putting two more browser legs on the per-dep The AI session scorer is the one place in this repo where behavior can change with **no code change**: a prompt edit, a model deprecation or a provider-side contract change all move the numbers an organizer uses to accept or decline talks, and every unit test in `CI.slnf` stays green through -all three (`MMCA.ADC/.github/workflows/deploy.yml:479-482`). `ai-eval-gate` (`deploy.yml:500`) is the +all three (`MMCA.ADC/.github/workflows/deploy.yml:530-533`). `ai-eval-gate` (`deploy.yml:551`) is the behavioral check, and it is split into two tiers by cost. **Tier 1, golden replay plus prompt contract, always.** The step runs the evaluation project with `--filter-not-trait "Category=AiEval.Live"` and a `--minimum-expected-tests 1` floor -(`deploy.yml:526-532`). No API key and no network: seven recorded proposals in +(`deploy.yml:577-583`). No API key and no network: seven recorded proposals in `Tests/Modules/Conference/MMCA.ADC.Conference.Scoring.Evaluation.Tests/Golden/` are replayed through the **real** `SessionScoringService`. The harness is the framework's: `GoldenReplayTests` (`GoldenReplayTests.cs:35`) derives from `GoldenReplayTestsBase` and drives a `ReplayChatClient`, @@ -1838,23 +1861,23 @@ hands the framework (its name, version, model and system prompt) to those same v calls to the configured provider for each golden proposal, asserting the overall lands in the case's band. It composes the same governed `AddMmcaChatClient` chain as the host and names no vendor type (`LiveJudgeTests.cs:26`, `LiveJudgeTests.cs:112`). The deploy -runs it only when `needs.changes.outputs.scoring == 'true'` (`deploy.yml:535`), and that filter +runs it only when `needs.changes.outputs.scoring == 'true'` (`deploy.yml:586`), and that filter is deliberately **narrow**: unlike `code` and `ui` it does not fail safe to true on an unrecognized path, because a false positive here spends money on every unrelated deploy while the key-free tier -already runs unconditionally (`deploy.yml:154-158`, the three matched paths at `deploy.yml:160-162`). +already runs unconditionally (`deploy.yml:162-166`, the three matched paths at `deploy.yml:168-170`). The whole-diff fail-safe still applies one level up: when the push range cannot be determined the -classifier sets `scoring=true` along with everything else (`deploy.yml:95-101`, `deploy.yml:109-115`). +classifier sets `scoring=true` along with everything else (`deploy.yml:99-104`, `deploy.yml:113-119`). Without `AI_API_KEY` (`LiveJudgeTests.cs:41`) each case calls `Assert.Skip` (`LiveJudgeTests.cs:69`), reported as skipped and never as passed, which is why that step alone -carries no `--minimum-expected-tests` floor (`deploy.yml:536-538`). The variable name is +carries no `--minimum-expected-tests` floor (`deploy.yml:587-589`). The variable name is provider-neutral to match the `Ai:ApiKey` configuration key, while the repository secret that feeds -it keeps its `ANTHROPIC_API_KEY` name because it holds an Anthropic credential (`deploy.yml:545-547`). +it keeps its `ANTHROPIC_API_KEY` name because it holds an Anthropic credential (`deploy.yml:596-598`). Two choices are worth naming. The gate runs on the same `code` condition as the `CI.slnf` tiers rather than the ui/backend split, because the replay tier is cheap and its whole point is catching -what the other two gates cannot see (`deploy.yml:496-499`, condition at `deploy.yml:502`); and the +what the other two gates cannot see (`deploy.yml:547-550`, condition at `deploy.yml:553`); and the live bands are wide on purpose, because a judge model is not deterministic and a flaky gate gets -ignored (`deploy.yml:493-494`). +ignored (`deploy.yml:544-545`). [Rubric §14, Testability & Test Strategy]: §14 assesses whether the suite meaningfully covers the system's real behavior; a deterministic replay of recorded provider responses is how a non-deterministic dependency becomes testable at all, and the corpus-shape fact is how the tier @@ -1872,13 +1895,13 @@ noisy neighbour or loose enough never to fire ([ADR-060](https://ivanball.github.io/docs/adr/060-performance-regression-gate.html)). The answer splits the baseline by measurement stability. -- **A dedicated job measures, then verifies.** `performance-smoke` (`MMCA.Common/.github/workflows/ci.yml:377`), - named "Performance gate (BenchmarkDotNet Short + baseline verify)" (`ci.yml:353`), runs the - suite with `--filter "*" --job Short --exporters json` (`ci.yml:385`) and then runs `build/perfgate` - over the exported artifacts (`ci.yml:387`). `--job Short` is 3 warmup plus 3 iterations, chosen to +- **A dedicated job measures, then verifies.** `performance-smoke` (`MMCA.Common/.github/workflows/ci.yml:355`), + named "Performance gate (BenchmarkDotNet Short + baseline verify)" (`ci.yml:356`), runs the + suite with `--filter "*" --job Short --exporters json` (`ci.yml:388`) and then runs `build/perfgate` + over the exported artifacts (`ci.yml:397`). `--job Short` is 3 warmup plus 3 iterations, chosen to produce real measurements inside a bounded budget; `--filter "*"` is required because - BenchmarkDotNet otherwise prompts for a selection and would hang the runner (`ci.yml:381-384`). -- **The baseline is a committed JSON file**, `Tests/Performance/perf-baseline.json` (`ci.yml:387`), + BenchmarkDotNet otherwise prompts for a selection and would hang the runner (`ci.yml:384-387`). +- **The baseline is a committed JSON file**, `Tests/Performance/perf-baseline.json` (`ci.yml:397`), not a stored previous run. A gate comparing against the previous run ratchets silently: every PR is only slightly worse than the last, and the sum is invisible. A committed number is a line a reviewer questions in the same PR as the code that spends it. @@ -1895,7 +1918,7 @@ splits the baseline by measurement stability. with an instruction to keep `[MemoryDiagnoser]` on the suite. Vacuity is the failure mode a benchmark gate actually has: a renamed method or a filter selecting nothing would leave a gate that passes while measuring nothing, which is worse than no gate because it reads as evidence - (the verifier invocation is `ci.yml:387`; the rules it enforces live in `MMCA.Common/build/perfgate`). + (the verifier invocation is `ci.yml:397`; the rules it enforces live in `MMCA.Common/build/perfgate`). The job is a **required merge gate**, not advisory: it is the eighth of the eight contexts listed in `MMCA.Common/CONTRIBUTING.md:68-71`, which also names raising a ceiling to silence a red gate as @@ -1904,27 +1927,80 @@ defeating it. That last part is enforced by review, not by the tool. **This gate is MMCA.Common only.** The harness, the baseline and the verifier exist in that repo and nowhere else. ADC and Store have no benchmark suite and no perfgate; their performance artifact is a periodic k6 load test against deployed read endpoints whose recency is gated by `load-freshness` -(`MMCA.ADC/.github/workflows/deploy.yml:873`), not a pull-request gate. Helpdesk has neither. +(`MMCA.ADC/.github/workflows/deploy.yml:909`), not a pull-request gate. Helpdesk has neither. Consumers inherit the framework's bounded hot paths through the released packages, not the gate over their own code. +### The weekly load tier (`load-tests.yml`) + +The benchmark gate answers "did a hot path regress", per pull request, without trusting wall-clock +time. It cannot answer "does the persistence stack hold up at realistic volume", which needs real +rows, a real background processor and real timings. MMCA.Common answers that second question in a +separate workflow, `MMCA.Common/.github/workflows/load-tests.yml`, over +`Tests/Performance/MMCA.Common.LoadTests` (14 types, `00-inventory.md:120`; the types are rolled up in +[`group-28-testing-infrastructure.md`](group-28-testing-infrastructure.md)). + +- **Three scenarios, each a correctness assertion plus a timing bound** (`load-tests.yml:3-8`). + `OutboxProcessor_Drains10000Messages_ExactlyOnce_AboveThroughputFloor` enqueues 10,000 messages + and lets the **real** `OutboxProcessor`, resolved from `AddInfrastructure` as a hosted service, + drain them, asserting exactly-once delivery and a floor of 500 messages per second + (`MMCA.Common/Tests/Performance/MMCA.Common.LoadTests/OutboxThroughputLoadTests.cs:22,30,36,63`). + `PagedQueryLoadTests` seeds 100,000 rows (`Support/PagedQueryFixture.cs:18`), bounds a first page, + a deep page and a filtered, sorted page at 30, 45 and 65 ms, and runs 50 concurrent paged reads + against a 750 ms p95 ceiling (`PagedQueryLoadTests.cs:23-25,27,31`). Each ceiling carries the local + measurements it was set from in a trailing comment, at about 3x headroom (`load-tests.yml:7-8`), so + a reviewer moving a number can see what it was measured against. +- **The database is a temp-file SQLite with connection pooling off.** `LoadStack` points the + framework's real persistence and outbox wiring at a file database (`Support/LoadStack.cs:62,66`) + and turns `Microsoft.Data.Sqlite` pooling off, with the flake rate that motivated it recorded in + the comment above the setting (`LoadStack.cs:56`). No Docker is needed, so the tier runs on a stock + runner. +- **Schedule plus manual dispatch, never a pull request.** The cron is Sunday 04:17 UTC, off-peak and + off the top of the hour where scheduled runs queue up (`load-tests.yml:14-17`). The header states + why it is neither in `ci.yml` nor a required check (`load-tests.yml:10-12`): the project sits + outside `MMCA.Common.slnx`, so the solution-wide `dotnet test --solution` never discovers it, and a + timing tier on shared runners is a trend signal, not a merge gate. It is ADR-060's refusal to gate + on absolute latency applied from the other side: where absolute ceilings exist, they stay off the + merge path. +- **The job keeps `ci.yml`'s posture.** Read-only `contents` permission (`load-tests.yml:19-21`), a + non-cancelling concurrency group (`load-tests.yml:26-28`), a 20-minute timeout that bounds a hung + processor or restore against a local run of about 15 seconds plus the build (`load-tests.yml:34-35`), + full history for MinVer (`load-tests.yml:40`), the same lock-file cache key as `ci.yml` + (`load-tests.yml:45-49`), a `--locked-mode` restore (`load-tests.yml:51-52`) and a build by csproj + path, like the other out-of-solution tiers (`load-tests.yml:54-56`). The run step executes the + self-hosted test executable directly (`load-tests.yml:61`) with `MMCA_LOAD_RESULTS_DIR` pointing at + the artifact folder (`load-tests.yml:60`, read by `Support/LoadResults.cs:16`). +- **A missing scenario fails.** Each scenario writes its JSON summary before asserting, and the + "Verify every scenario reported" step requires exactly three of them (`load-tests.yml:63-69`), so a + discovery or filter regression cannot pass as a no-op: the same anti-vacuity rule the perfgate and + every `--minimum-expected-tests` floor enforce. +- **The results are kept as a trend.** The summaries upload on `always()`, so a failing run still + leaves its numbers, with a 90-day retention (`load-tests.yml:71-78`) so runs can be compared over + time. + +[Rubric §12, Performance & Efficiency]: §12 assesses whether performance is measured at realistic +volume rather than assumed; this tier measures the outbox drain and paged reads at production-like +row counts on a schedule, and keeps the absolute numbers it produces out of the merge path, where +runner noise would turn them into a flaky gate. + ### The two cross-repo gates, and MMCA.Helpdesk's own CI + Two gates run a *different repo's* code than the one being changed, and both exist because a green build in one repo has repeatedly proved nothing about the other. **MMCA.Common's consumer canary** (`consumer-source-build`, -`MMCA.Common/.github/workflows/ci.yml:494`, named "Consumer source build (Helpdesk)" at `ci.yml:470`) -checks MMCA.Helpdesk out as a sibling (`ci.yml:515`) and builds it against **this PR's** framework -source (`ci.yml:557`), so a breaking public-API change reds here rather than after a release plus a +`MMCA.Common/.github/workflows/ci.yml:475`, named "Consumer source build (Helpdesk)" at `ci.yml:476`) +checks MMCA.Helpdesk out as a sibling (`ci.yml:526`) and builds it against **this PR's** framework +source (`ci.yml:568`), so a breaking public-API change reds here rather than after a release plus a lockstep sweep. Helpdesk is the right canary precisely because it is minimal: no database and no GitHub Packages token, and its committed `local.props` already swaps the `MMCA.Common.*` package references for project references into `../MMCA.Common/Source`. The job also proves the framework's **migration** path end to end, starting an ephemeral SQL Server -(`ci.yml:547`), applying the consumer's real EF migrations to it (`ci.yml:611`) and then asserting the -migrations were recorded and the schema exists (`ci.yml:630`), and it runs the seed's suite with a -floor of `40` (`ci.yml:569`). The same-name-branch resolution step described below has its Common-side -twin here (`ci.yml:498`). +(`ci.yml:558`), applying the consumer's real EF migrations to it (`ci.yml:635`) and then asserting the +migrations were recorded and the schema exists (`ci.yml:654`), and it runs the seed's suite with a +floor of `40` (`ci.yml:580`). The same-name-branch resolution step described below has its Common-side +twin here (`ci.yml:509`). **MMCA.Helpdesk's own `ci.yml`** is the other half of that pair, and it is deliberately small. It runs on pull requests and pushes to `main` (`MMCA.Helpdesk/.github/workflows/ci.yml:3-7`) with one @@ -1962,32 +2038,32 @@ this workspace can ship, and this is the gate that catches it. Coverage is enforced as a floor, not reported as a number, and every floor is scoped so it measures the code it claims to measure. -- **MMCA.Common:** the `coverage` job (`MMCA.Common/.github/workflows/ci.yml:424`) merges the tiers - with ReportGenerator (`ci.yml:416`) and fails if the **unit tier drops below 68.3% line coverage** - (`ci.yml:445`). It gates the +- **MMCA.Common:** the `coverage` job (`MMCA.Common/.github/workflows/ci.yml:402`) merges the tiers + with ReportGenerator (`ci.yml:422`) and fails if the **unit tier drops below 68.3% line coverage** + (`ci.yml:462`). It gates the unit tier rather than the merged report because the gallery E2E tier dilutes it, and it excludes generated code (`*.generated.cs` / `*.g.cs`) because source generators emit large uncovered files that otherwise tank the number: 45.3% raw versus 61.9% hand-written on the run that prompted the - filter (`ci.yml:439-444`, assembly and file filters applied at `ci.yml:453`). It runs only when + filter (`ci.yml:445-450`, assembly and file filters applied at `ci.yml:459`). It runs only when `build-and-test` succeeded, so an upstream failure does - not add a confusing secondary coverage failure (`ci.yml:448`). + not add a confusing secondary coverage failure (`ci.yml:452-454`). - **MMCA.ADC:** two floors run inside `build-and-test`, both PR-only. The global unit-tier floor sits - at **55.5%** (`MMCA.ADC/.github/workflows/deploy.yml:373`), measured over **ADC's own code only**. + at **55.5%** (`MMCA.ADC/.github/workflows/deploy.yml:386`), measured over **ADC's own code only**. The comment above it is the part worth reading: the raw cobertura also instruments the consumed framework assemblies (tested in their own repo, near 0% here), plus the service hosts and the protobuf-dominated contracts assemblies, and leaving them in deflated the number to something that measured nothing (26.8% raw, then 52.8% versus 62.5% filtered after the service-host assemblies appeared). The assembly filter is what makes the floor mean what it says, and it is one line: `+MMCA.ADC.*;-*.Tests;-MMCA.ADC.*.Service;-MMCA.ADC.*.Contracts` - (`deploy.yml:371`). A second, tighter floor gates **Application-layer branch coverage at 77.5%** - (`deploy.yml:405`) over `+MMCA.ADC.*.Application` only (`deploy.yml:396`), because the repo-wide + (`deploy.yml:384`). A second, tighter floor gates **Application-layer branch coverage at 77.5%** + (`deploy.yml:422`) over `+MMCA.ADC.*.Application` only (`deploy.yml:413`), because the repo-wide average is dominated by UI, Infrastructure and generated code, and because the business decisions - it wants to measure are branches rather than lines (`deploy.yml:381-385`); the floor sits about two + it wants to measure are branches rather than lines (`deploy.yml:392-398`); the floor sits about two points under the 79.7% branch coverage measured on the full unit tier, so ordinary churn does not - trip it while a real regression does (`deploy.yml:387-390`). It carries its own + trip it while a real regression does (`deploy.yml:400-403`). It carries its own anti-vacuity guard: if the filtered report covers fewer than four assemblies (Conference, Engagement, Identity, Notification) the step fails outright, on the grounds that a filter matching - nothing would make the floor pass vacuously (`deploy.yml:398-401`). + nothing would make the floor pass vacuously (`deploy.yml:414-418`). - **MMCA.Store:** the equivalent unit-tier floor sits at **51.6%** (`MMCA.Store/.github/workflows/deploy.yml:255`). - **MMCA.Helpdesk has no coverage floor at all**, and that is consistent with what it is. The seed's @@ -2011,7 +2087,7 @@ right to. | §4 Domain-Driven Design | §4 AggregateConventionTests factory-method rules | | §9 API Design & Contracts | §3 the ADR-058 ProblemDetails, OpenAPI and versioning bases | | §11 Security (test RS256 keypair) | §3 JwtTokenGenerator design note; §3 SecurityHeadersTestsBase and MmcaGatewayHardeningTestsBase; §7 the prompt-contract test that pins the anti-injection brief | -| §12 Performance & Efficiency | §7 the ADR-060 benchmark baseline gate | +| §12 Performance & Efficiency | §7 the ADR-060 benchmark baseline gate and the weekly load tier | | §13 Observability & Operability | §4 ObservabilityConventionTests alert-to-runbook pairing, and AppHostBicepParityTests turning an unresolvable service name into a build failure | | §14 Testability & Test Strategy | §1 CI filter rationale, §2 project layout, §5 integration strategy, §7 the AI scoring evaluation gate and the coverage floors | | §17 DevOps & Deployment | §1 two-filter CI rationale and the complementary deploy gates, §4 the AppHost/Bicep topology parity gate, §7 the tier-to-gate map | diff --git a/docs-src/onboarding/group-01-result-error-handling.md b/docs-src/onboarding/group-01-result-error-handling.md index 0eea0959..f99be5ca 100644 --- a/docs-src/onboarding/group-01-result-error-handling.md +++ b/docs-src/onboarding/group-01-result-error-handling.md @@ -159,7 +159,7 @@ The point of hoisting this into `MMCA.Common.Shared` rather than into one presen `ErrorHttpMapping.GetStatusCode(IReadOnlyList)` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/ErrorHttpMapping.cs:50-51`) and the gRPC side from `ToRpcException` -(`MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:117`). Every error still +(`MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:122`). Every error still travels in the payload; ranking selects the status only. This is the 2026-08-26 and 2026-08-27 revision of [ADR-013](https://ivanball.github.io/docs/adr/013-result-pattern.html), and it is [Rubric §9, API & Contract Design] in the small: the meaning of a failure must not depend on the order @@ -227,19 +227,19 @@ drift (`ErrorHttpMapping.cs:10-12`). On the gRPC side, [`ResultGrpcExtensions`](group-13-grpc-contracts.md#resultgrpcextensions) carries a *mirror* of that table, `ErrorType` to `Grpc.Core.StatusCode`, in its own `FrozenDictionary` -(`MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:35-47`, with `Unexpected` -mapping to `StatusCode.Internal` at `ResultGrpcExtensions.cs:46`), reachable as an extension member -`ToGrpcStatusCode()` (`ResultGrpcExtensions.cs:56`). A service implementation guards with a single -`result.ThrowIfFailure()` (`ResultGrpcExtensions.cs:69`) or takes the value with `UnwrapOrThrow()` -(`ResultGrpcExtensions.cs:86`), both raising a +(`MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:36-48`, with `Unexpected` +mapping to `StatusCode.Internal` at `ResultGrpcExtensions.cs:47`), reachable as an extension member +`ToGrpcStatusCode()` (`ResultGrpcExtensions.cs:57`). A service implementation guards with a single +`result.ThrowIfFailure()` (`ResultGrpcExtensions.cs:70`) or takes the value with `UnwrapOrThrow()` +(`ResultGrpcExtensions.cs:87`), both raising a [`ResultFailureException`](group-13-grpc-contracts.md#resultfailureexception) that the server interceptor `GrpcResultExceptionInterceptor` (`MMCA.Common/Source/Presentation/MMCA.Common.Grpc/Interceptors/GrpcResultExceptionInterceptor.cs:19`) -translates through `ToRpcException` (`ResultGrpcExtensions.cs:112`): the status comes from -`ErrorTypeSeverity.MostSevere` (`ResultGrpcExtensions.cs:116-117`) and every error is still written +translates through `ToRpcException` (`ResultGrpcExtensions.cs:117`): the status comes from +`ErrorTypeSeverity.MostSevere` (`ResultGrpcExtensions.cs:121-122`) and every error is still written into the trailers as `error-{i}-code`, `error-{i}-message` and `error-{i}-type` entries, plus `-source` / `-target` when present, so the far side can rebuild the list -(`ResultGrpcExtensions.cs:124-140`). See +(`ResultGrpcExtensions.cs:129-145`). See [ADR-007](https://ivanball.github.io/docs/adr/007-grpc-extraction.html) and [Chapter 13](group-13-grpc-contracts.md). @@ -343,13 +343,13 @@ source. The consumer is the repository layer: [`EFReadRepository`](group-07-persistence-ef-core.md#efreadrepositorytentity-tidentifiertype)`.GetPageByCursorAsync` -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:617`) +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:632`) returns a `Result>`, turning an unknown sort column -(`EFReadRepository.cs:624-633`) or a malformed cursor (`EFReadRepository.cs:641-649`) into a `Result` +(`EFReadRepository.cs:639-648`) or a malformed cursor (`EFReadRepository.cs:656-664`) into a `Result` failure rather than an exception, fetching `PageSize + 1` rows so the extra row acts as a next-page -probe instead of paying for a `COUNT` (`EFReadRepository.cs:656-662`), and encoding the last kept row +probe instead of paying for a `COUNT` (`EFReadRepository.cs:671-677`), and encoding the last kept row into the next cursor via [`KeysetQueryBuilder`](group-07-persistence-ef-core.md#keysetquerybuilder) -(`EFReadRepository.cs:667-670`). Per +(`EFReadRepository.cs:682-685`). Per [ADR-055](https://ivanball.github.io/docs/adr/055-repository-and-specification-contract.html) this is a repository-level capability only: it is deliberately not exposed on the HTTP query contract of [ADR-034](https://ivanball.github.io/docs/adr/034-generic-entity-query-layer.html), which still offers @@ -418,8 +418,8 @@ after them takes the railway for granted. - `Encode(string? sortValue, string id)` (`KeysetPagination.cs:139-153`) null-checks the id, then builds the payload `v1|{hasSortValue}|{sortValue}|{id}` where `hasSortValue` is the literal `"0"` or `"1"` and both value segments are themselves base64url (`KeysetPagination.cs:143-150`). Encoding the segments is what stops a sort value that happens to contain a `|` from forging an extra segment. The whole payload is then base64url encoded once more (`KeysetPagination.cs:152`), which is what makes the result URL-safe and visibly opaque. - `TryDecode(string? cursor, out string? sortValue, out string id)` (`KeysetPagination.cs:169-190`) is a strict inverse and a `Try` method by design, because its input is client-supplied. It clears both outputs first (`KeysetPagination.cs:171-172`), then rejects: a blank or non-base64url cursor (`KeysetPagination.cs:174-175`), a payload that does not split into exactly four segments or whose first segment is not `v1` (`KeysetPagination.cs:177-179`), a flag segment that is neither `"0"` nor `"1"` (`KeysetPagination.cs:181-182`), and a segment that is not valid base64url (`KeysetPagination.cs:184-185`). Only then does it publish the decoded values, honoring the flag by returning `null` for the sort value when the cursor carries none (`KeysetPagination.cs:187-189`). - `TryFromBase64Url` (`KeysetPagination.cs:195-214`) holds the subtle bit and says so in a comment: `Base64Url.TryDecodeFromChars` **throws** on an invalid character instead of returning `false`, and a client-supplied cursor is precisely the input that will contain one, so the method calls `Base64Url.IsValid` first (`KeysetPagination.cs:202-206`). An empty string short-circuits to a successful empty decode (`KeysetPagination.cs:199-200`), which is how a cursor with no sort value round-trips. -- **Why it's built this way**: the version prefix plus the total-failure `Try` contract means a malformed or stale cursor becomes a *validation failure the caller can see*, never a silent reset to the first page. [`EFReadRepository`](group-07-persistence-ef-core.md#efreadrepositorytentity-tidentifiertype) turns a `false` return into `Error.Validation("Error.InvalidCursor", ...)` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:643-648`). This is the keyset half of [ADR-055](https://ivanball.github.io/docs/adr/055-repository-and-specification-contract.html). -- **Where it's used**: `Encode` is called once per page by [`EFReadRepository`](group-07-persistence-ef-core.md#efreadrepositorytentity-tidentifiertype) with the last row's sort value and id, both rendered invariantly by [`KeysetQueryBuilder`](group-07-persistence-ef-core.md#keysetquerybuilder) (`EFReadRepository.cs:668-670`); `TryDecode` is called from the same class's `TryBuildSeekPredicate` to rebuild the seek predicate (`EFReadRepository.cs:688`). +- **Why it's built this way**: the version prefix plus the total-failure `Try` contract means a malformed or stale cursor becomes a *validation failure the caller can see*, never a silent reset to the first page. [`EFReadRepository`](group-07-persistence-ef-core.md#efreadrepositorytentity-tidentifiertype) turns a `false` return into `Error.Validation("Error.InvalidCursor", ...)` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:658-663`). This is the keyset half of [ADR-055](https://ivanball.github.io/docs/adr/055-repository-and-specification-contract.html). +- **Where it's used**: `Encode` is called once per page by [`EFReadRepository`](group-07-persistence-ef-core.md#efreadrepositorytentity-tidentifiertype) with the last row's sort value and id, both rendered invariantly by [`KeysetQueryBuilder`](group-07-persistence-ef-core.md#keysetquerybuilder) (`EFReadRepository.cs:683-685`); `TryDecode` is called from the same class's `TryBuildSeekPredicate` to rebuild the seek predicate (`EFReadRepository.cs:703`). ### KeysetPageRequest > MMCA.Common.Shared · `MMCA.Common.Shared.Abstractions` · `MMCA.Common/Source/Core/MMCA.Common.Shared/Abstractions/KeysetPagination.cs:20` · Level 0 · record (sealed) @@ -433,8 +433,8 @@ after them takes the railway for granted. - The page size is **clamped, not rejected**: `Math.Clamp(pageSize, 1, MaxPageSize)` in the constructor (`KeysetPagination.cs:51`) and again in the `init` accessor (`KeysetPagination.cs:62`), so a caller asking for zero or a million gets a sane page instead of an error. The `init` accessor uses the C# `field` keyword to write the clamped value into the compiler-generated backing field, which is what closes the hole that an object initializer, a `with` expression, or a System.Text.Json deserialization would otherwise open by bypassing the constructor entirely. - The remaining three properties are plain `init` values: `SortColumn` (nullable; null means order by `Id` alone, `KeysetPagination.cs:67`), `Descending` (`KeysetPagination.cs:71`), and `Cursor` (nullable; null means the first page, `KeysetPagination.cs:75`). - `[DataContract]` on the record and `[DataMember(Order = 1..4)]` on the four properties (`KeysetPagination.cs:19,58,66,70,74`) pin the wire order. -- **Why it's built this way**: exactly one sort key is supported, with the entity's `Id` as the tie-break, because that is what keeps the seek predicate a single composable comparison. A null `SortColumn` keys the page on `Id` alone (stated on the contract at `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:306-309`). The sort column must name a real public property: [`EFReadRepository`](group-07-persistence-ef-core.md#efreadrepositorytentity-tidentifiertype) resolves it through [`KeysetQueryBuilder`](group-07-persistence-ef-core.md#keysetquerybuilder) and returns an `Error.InvalidEntityField` copy carrying the offending column name when it does not (`EFReadRepository.cs:624-633`), so an unknown name is a validation failure rather than a silently ignored parameter. See [ADR-055](https://ivanball.github.io/docs/adr/055-repository-and-specification-contract.html). -- **Where it's used**: the sole parameter object of `GetPageByCursorAsync`, declared on [`IEntityQuerier`](group-07-persistence-ef-core.md#ientityqueriertentity-tidentifiertype) (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:316-319`, inherited by [`IReadRepository`](group-07-persistence-ef-core.md#ireadrepositorytentity-tidentifiertype) at `IRepository.cs:330`), implemented by [`EFReadRepository`](group-07-persistence-ef-core.md#efreadrepositorytentity-tidentifiertype) (`EFReadRepository.cs:617-674`) and forwarded by [`EFReadRepositoryDecorator`](group-07-persistence-ef-core.md#efreadrepositorydecoratortentity-tidentifiertype) (`EFReadRepositoryDecorator.cs:151-152`). +- **Why it's built this way**: exactly one sort key is supported, with the entity's `Id` as the tie-break, because that is what keeps the seek predicate a single composable comparison. A null `SortColumn` keys the page on `Id` alone (stated on the contract at `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:308-311`). The sort column must name a real public property: [`EFReadRepository`](group-07-persistence-ef-core.md#efreadrepositorytentity-tidentifiertype) resolves it through [`KeysetQueryBuilder`](group-07-persistence-ef-core.md#keysetquerybuilder) and returns an `Error.InvalidEntityField` copy carrying the offending column name when it does not (`EFReadRepository.cs:639-648`), so an unknown name is a validation failure rather than a silently ignored parameter. See [ADR-055](https://ivanball.github.io/docs/adr/055-repository-and-specification-contract.html). +- **Where it's used**: the sole parameter object of `GetPageByCursorAsync`, declared on [`IEntityQuerier`](group-07-persistence-ef-core.md#ientityqueriertentity-tidentifiertype) (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:318-321`, inherited by [`IReadRepository`](group-07-persistence-ef-core.md#ireadrepositorytentity-tidentifiertype) at `IRepository.cs:332`), implemented by [`EFReadRepository`](group-07-persistence-ef-core.md#efreadrepositorytentity-tidentifiertype) (`EFReadRepository.cs:632-689`) and forwarded by [`EFReadRepositoryDecorator`](group-07-persistence-ef-core.md#efreadrepositorydecoratortentity-tidentifiertype) (`EFReadRepositoryDecorator.cs:151-152`). - **Caveats / not-in-source**: `MaxPageSize` and `MaxUnboundedResultLimit` are two independent constants that happen to be equal. Nothing in source ties them together, so a change to one does not move the other. ### PaginationMetadata @@ -449,7 +449,7 @@ after them takes the railway for granted. - Each `init` accessor **re-validates** with `ArgumentOutOfRangeException.ThrowIfNegative` and assigns through the C# `field` keyword. The comment above them says why (`PaginationMetadata.cs:33-35`): object initializers, record `with` expressions, and System.Text.Json (which builds this type through the parameterless constructor plus the `init` setters) all bypass the constructor guards. Without the duplicated check the guard would be decorative on exactly the paths a client controls. - Three computed properties tagged `[IgnoreDataMember]`, so they are derived rather than transmitted: `TotalPageCount`, a ceiling division that returns 0 when `PageSize` is 0 (`PaginationMetadata.cs:74-75`); `FirstRowOnPage`, which returns 0 for the several empty cases and otherwise computes the 1-based first index (`PaginationMetadata.cs:78-79`); and `LastRowOnPage`, clamped to `TotalItemCount` (`PaginationMetadata.cs:82-83`). Note the `(long)` casts inside both row calculations: `CurrentPage * PageSize` is a deliberate overflow guard, since two large `int`s multiply out of range long before either is individually implausible. - **Why it's built this way**: `[DataContract]` / `[DataMember]` / `[IgnoreDataMember]` make the wire shape explicit and stable, so only the three primary values travel and the rest are recomputed on the other side. Constructor-plus-`init` validation makes an invalid instance unconstructable, which is the same "validate at the boundary of the type" discipline the value objects of [Chapter 2](group-02-domain-building-blocks.md) use. -- **Where it's used**: embedded in [`PagedCollectionResult`](#pagedcollectionresultt); serialized whole into the `X-Pagination` response header by [`EntityControllerBase`](group-12-api-hosting-mapping.md#entitycontrollerbasetentity-tentitydto-tidentifiertype) (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:185`); the CSV export endpoint on the same base class does not use this type at all, since truncation there is decided by `EntityCsvExporter.WriteAsync` comparing rows written against the `MaxExportRows` ceiling (`EntityControllerBase.cs:264-286`). Built by [`EntityQueryService`](group-03-querying-specifications.md#entityqueryservicetentity-tentitydto-tidentifiertype) in one private helper (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:608`, called at `:332`). +- **Where it's used**: embedded in [`PagedCollectionResult`](#pagedcollectionresultt); serialized whole into the `X-Pagination` response header by [`EntityControllerBase`](group-12-api-hosting-mapping.md#entitycontrollerbasetentity-tentitydto-tidentifiertype) (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:188`); the CSV export endpoint on the same base class does not use this type at all, since truncation there is decided by `EntityCsvExporter.WriteAsync` comparing rows written against the `MaxExportRows` ceiling (`EntityControllerBase.cs:276-298`). Built by [`EntityQueryService`](group-03-querying-specifications.md#entityqueryservicetentity-tentitydto-tidentifiertype) in one private helper (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:608`, called at `:332`). ### PropertyReader > MMCA.Common.Shared · `MMCA.Common.Shared.Serialization` · `MMCA.Common/Source/Core/MMCA.Common.Shared/Serialization/ResultJsonConverterFactory.cs:118` · Level 0 · delegate @@ -484,7 +484,7 @@ after them takes the railway for granted. - Three pre-built `static readonly` singletons for the ubiquitous cases (`Error.cs:23,26,29`): `Error.NotFound`, `Error.AlreadyDeleted`, and `Error.InvalidEntityField`. Each is itself built through a factory method, so the singletons cannot drift from the factory contract. - **Nine factory methods**, one per [`ErrorType`](#errortype) member (`Error.cs:37,46,55,64,73,82,91,100,114`): `Validation`, `Invariant`, `NotFoundError`, `Conflict`, `Unauthorized`, `Forbidden`, `UnprocessableEntity`, `Failure`, and `Unexpected`. Each hard-codes the matching `ErrorType`, so a caller cannot pair the wrong classification with a code. Two details worth carrying: the factory is `NotFoundError` rather than `NotFound` because the static field already owns that name and C# forbids a field and a method sharing one; and `Unexpected` carries a written usage policy on the factory itself (`Error.cs:103-108`), namely that it is for a genuine server-side fault the caller cannot fix by changing the request, with business-rule violations routed to `Invariant` or `Failure`. - Two `with`-expression helpers (`Error.cs:120-121,126-127`): `WithSource(string)` and `WithTarget(string)` each return a copy with one component replaced, which is how a generic framework error gets enriched with caller context without being mutated. - - The singletons are `record`s, so they are also freely refinable at the call site: [`EFReadRepository`](group-07-persistence-ef-core.md#efreadrepositorytentity-tidentifiertype) takes `Error.InvalidEntityField with { Message = ..., Source = ..., Target = ... }` to report an unknown keyset sort column (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:626-632`), keeping the shared `Code` while specializing everything else. + - The singletons are `record`s, so they are also freely refinable at the call site: [`EFReadRepository`](group-07-persistence-ef-core.md#efreadrepositorytentity-tidentifiertype) takes `Error.InvalidEntityField with { Message = ..., Source = ..., Target = ... }` to report an unknown keyset sort column (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:641-647`), keeping the shared `Code` while specializing everything else. - **Why it's built this way**: modeling an expected failure as data rather than as control flow is the whole point of [ADR-013](https://ivanball.github.io/docs/adr/013-result-pattern.html). The pre-built singletons keep the common cases allocation-free, and the factory methods fix the `Code` to `ErrorType` pairing at the point of creation so no mapping table is needed anywhere else in the codebase. - **Where it's used**: carried by every failing [`Result`](#result) in both repos; [`ValidationFailureExtensions`](group-06-validation.md#validationfailureextensions) converts each FluentValidation failure into an `Error.Validation(...)` carrying the rule's code, message, source, and property name (`MMCA.Common/Source/Core/MMCA.Common.Application/Extensions/ValidationFailureExtensions.cs:21`); [`ApiControllerBase`](group-12-api-hosting-mapping.md#apicontrollerbase) renders the whole list into the Problem Details `errors` extension, optionally through an [`IErrorLocalizer`](group-12-api-hosting-mapping.md#ierrorlocalizer) (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/ApiControllerBase.cs:57-58`, projection at `ErrorHttpMapping.cs:61-69`); [`ErrorTypeSeverity`](#errortypeseverity) picks one of them to classify the whole failure; and [`ResultJsonConverterFactory`](#resultjsonconverterfactory) serializes the list as the `errors` array. @@ -498,8 +498,8 @@ after them takes the railway for granted. - `sealed record` inheriting `CollectionResult` (`KeysetPagination.cs:85`), tagged `[DataContract]` (`KeysetPagination.cs:84`). - Two constructors mirroring the base, both `[SetsRequiredMembers]` (`KeysetPagination.cs:88,98`): a parameterless one producing an empty page with no next cursor (`KeysetPagination.cs:89-90`), and the data constructor that passes items to `base(items)` and assigns the cursor (`KeysetPagination.cs:99-100`). - One added property: `string? NextCursor { get; init; }` tagged `[DataMember(Order = 2)]` so it serializes after `Items` (`KeysetPagination.cs:106-107`). Null means this page is the last one. The doc comment instructs consumers to treat it as opaque and warns that its encoding is versioned (`KeysetPagination.cs:102-105`). -- **Why it's built this way**: nullable-means-last is what lets the repository skip a count entirely. It fetches `PageSize + 1` rows as a next-page probe, drops the extra row, and only then emits a cursor (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:656-673`); the comment there states the trade explicitly, that a one-row probe is cheaper and more honest than a `COUNT` over the whole set (`EFReadRepository.cs:657-658`). See [ADR-055](https://ivanball.github.io/docs/adr/055-repository-and-specification-contract.html). -- **Where it's used**: the success payload of `GetPageByCursorAsync`, declared on [`IEntityQuerier`](group-07-persistence-ef-core.md#ientityqueriertentity-tidentifiertype) as `Task>>` (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:316`) and returned by [`EFReadRepository`](group-07-persistence-ef-core.md#efreadrepositorytentity-tidentifiertype) (`EFReadRepository.cs:673`). Note the composition: the *outcome* is a [`Result`](#result), the *payload* is this envelope, which is the standard pairing throughout the codebase. +- **Why it's built this way**: nullable-means-last is what lets the repository skip a count entirely. It fetches `PageSize + 1` rows as a next-page probe, drops the extra row, and only then emits a cursor (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:671-688`); the comment there states the trade explicitly, that a one-row probe is cheaper and more honest than a `COUNT` over the whole set (`EFReadRepository.cs:672-673`). See [ADR-055](https://ivanball.github.io/docs/adr/055-repository-and-specification-contract.html). +- **Where it's used**: the success payload of `GetPageByCursorAsync`, declared on [`IEntityQuerier`](group-07-persistence-ef-core.md#ientityqueriertentity-tidentifiertype) as `Task>>` (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:318`) and returned by [`EFReadRepository`](group-07-persistence-ef-core.md#efreadrepositorytentity-tidentifiertype) (`EFReadRepository.cs:688`). Note the composition: the *outcome* is a [`Result`](#result), the *payload* is this envelope, which is the standard pairing throughout the codebase. ### PagedCollectionResult > MMCA.Common.Shared · `MMCA.Common.Shared.Abstractions` · `MMCA.Common/Source/Core/MMCA.Common.Shared/Abstractions/PaginationMetadata.cs:119` · Level 1 · record (sealed) @@ -512,7 +512,7 @@ after them takes the railway for granted. - Two constructors mirroring the base, both `[SetsRequiredMembers]` (`PaginationMetadata.cs:122,129`): a parameterless one producing an empty result with a default `PaginationMetadata` (`PaginationMetadata.cs:123-124`), and the data constructor that calls `base(items)` and null-checks the metadata with `ArgumentNullException.ThrowIfNull` (`PaginationMetadata.cs:130-135`). - One added property: `required PaginationMetadata PaginationMetadata { get; init; }` tagged `[DataMember(Order = 2)]` (`PaginationMetadata.cs:138-139`), so it is never null and always serializes after `Items`. - **Why it's built this way**: `required` plus a null-checking constructor covers both construction routes (object initializer and direct constructor call), and the explicit `Order = 2` keeps the wire shape deterministic across the base/derived split, which member-declaration order alone would not guarantee. -- **Where it's used**: the payload of every offset-paged read, and a genuinely *shared* contract rather than a server-only shape. The controller surface declares `Task>>` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/IEntityControllerBase.cs:43`, implemented at `EntityControllerBase.cs:155`) and copies the metadata into the `X-Pagination` header (`EntityControllerBase.cs:185`), while the Blazor client deserializes the very same type (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:73,111`), so there is no hand-written mirror DTO to drift. [`EntityQueryService`](group-03-querying-specifications.md#entityqueryservicetentity-tentitydto-tidentifiertype) builds it (`EntityQueryService.cs:374-378`), and the notification read handlers in `MMCA.Common.Application` return it too. +- **Where it's used**: the payload of every offset-paged read, and a genuinely *shared* contract rather than a server-only shape. The controller surface declares `Task>>` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/IEntityControllerBase.cs:43`, implemented at `EntityControllerBase.cs:158`) and copies the metadata into the `X-Pagination` header (`EntityControllerBase.cs:188`), while the Blazor client deserializes the very same type (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:73,111`), so there is no hand-written mirror DTO to drift. [`EntityQueryService`](group-03-querying-specifications.md#entityqueryservicetentity-tentitydto-tidentifiertype) builds it (`EntityQueryService.cs:374-378`), and the notification read handlers in `MMCA.Common.Application` return it too. ### ErrorTypeSeverity > MMCA.Common.Shared · `MMCA.Common.Shared.Abstractions` · `MMCA.Common/Source/Core/MMCA.Common.Shared/Abstractions/ErrorTypeSeverity.cs:30` · Level 2 · class (static) @@ -528,7 +528,7 @@ after them takes the railway for granted. - `MostSevere(IReadOnlyList)` (`ErrorTypeSeverity.cs:69-96`) selects the representative error. It null-checks (`ErrorTypeSeverity.cs:71`) and rejects an empty list outright with `ArgumentException` (`ErrorTypeSeverity.cs:73-78`), because a failure with no errors is not classifiable and the [`Result`](#result) factories already make that state unreachable. It then seeds from `errors[0]` (`ErrorTypeSeverity.cs:80-81`) and scans forward keeping strictly greater ranks (`ErrorTypeSeverity.cs:85-93`). The strict `>` is what implements "ties keep the earliest error", so a list of same-rank errors behaves exactly as a positional selection would, which is the compatibility property that made the change safe to make. - The scan is a hand-written index loop with a comment defending it (`ErrorTypeSeverity.cs:83-84`): a LINQ `MaxBy` would allocate an enumerator plus a comparer closure on a path that runs on *every* failure response. That is `[Rubric §12, Performance & Scalability]` reasoning applied at the right scale, a hot path, with the trade written down rather than assumed. - **Why it's built this way**: [ADR-013](https://ivanball.github.io/docs/adr/013-result-pattern.html) records the decision and its history. Only the *status* is ranked: every error still travels in the payload, in the Problem Details `errors` array on HTTP (`MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/ErrorHttpMapping.cs:61-69`) and in the gRPC trailers on the other edge. The ranking answers exactly one question, "what is this failure, in one word", and answers it the same way on both transports. -- **Where it's used**: HTTP resolves a status through it, `ErrorHttpMapping.GetStatusCode(IReadOnlyList)` calling `MostSevere` (`ErrorHttpMapping.cs:50-51`) on behalf of [`ApiControllerBase`](group-12-api-hosting-mapping.md#apicontrollerbase)`.HandleFailure` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/ApiControllerBase.cs:48`). gRPC does the same in [`ResultGrpcExtensions`](group-13-grpc-contracts.md#resultgrpcextensions)`.ToRpcException` (`MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:117`, falling back to `Internal` for the impossible empty case). The UI uses the other half of the type: [`ResultUiExtensions`](group-15-common-ui-framework.md#resultuiextensions)`.LocalizedErrorMessages` orders the messages it shows a user by `Rank` descending, so the most severe error is read first (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/ResultUiExtensions.cs:155`). +- **Where it's used**: HTTP resolves a status through it, `ErrorHttpMapping.GetStatusCode(IReadOnlyList)` calling `MostSevere` (`ErrorHttpMapping.cs:50-51`) on behalf of [`ApiControllerBase`](group-12-api-hosting-mapping.md#apicontrollerbase)`.HandleFailure` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/ApiControllerBase.cs:48`). gRPC does the same in [`ResultGrpcExtensions`](group-13-grpc-contracts.md#resultgrpcextensions)`.ToRpcException` (`MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:122`, falling back to `Internal` for the impossible empty case). The UI uses the other half of the type: [`ResultUiExtensions`](group-15-common-ui-framework.md#resultuiextensions)`.LocalizedErrorMessages` orders the messages it shows a user by `Rank` descending, so the most severe error is read first (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/ResultUiExtensions.cs:155`). ### Result > MMCA.Common.Shared · `MMCA.Common.Shared.Abstractions` · `MMCA.Common/Source/Core/MMCA.Common.Shared/Abstractions/Result.cs:19` · Level 2 · class @@ -588,7 +588,7 @@ after them takes the railway for granted. - `CreateConverter` (`ResultJsonConverterFactory.cs:26-33`): returns a [`ResultConverter`](#resultconverter) for the non-generic case (`ResultJsonConverterFactory.cs:28-29`), and otherwise pulls the value type off the closed generic and constructs `ResultConverter` reflectively via `Activator.CreateInstance(typeof(ResultConverter<>).MakeGenericType(valueType))` (`ResultJsonConverterFactory.cs:31-32`). That reflective construction happens once per closed type, not once per payload, because System.Text.Json caches converters per type in the `JsonSerializerOptions`. - `ReadObject` (`ResultJsonConverterFactory.cs:121-144`) is the shared object walker both converters drive through a [`PropertyReader`](#propertyreader). It requires a `StartObject` token (`ResultJsonConverterFactory.cs:125-126`), returns on `EndObject` (`ResultJsonConverterFactory.cs:130-131`), demands a `PropertyName` token at each step (`ResultJsonConverterFactory.cs:133-134`), advances onto the value and throws on truncation (`ResultJsonConverterFactory.cs:137-138`), invokes the callback (`ResultJsonConverterFactory.cs:140`), and throws `JsonException` if the reader runs out before the closing brace (`ResultJsonConverterFactory.cs:143`). Its `options` parameter is deliberately discarded with `_ = options` (`ResultJsonConverterFactory.cs:123`). - `WriteErrors` (`ResultJsonConverterFactory.cs:146-153`) is the shared writer: it returns without writing anything when the result is a success, and otherwise emits the `errors` array through the ambient options. -- **Why it's built this way**: the Result types deliberately keep internal constructors and get-only properties (the construction discipline described under [`Result`](#result)), and the doc comment states the consequence plainly (`ResultJsonConverterFactory.cs:7-14`): default reflection-based deserialization cannot rehydrate them, so a purpose-built factory is what makes the type round-trippable for the distributed query cache. That cache path is real: [`CachingQueryDecorator`](group-05-cqrs-pipeline.md#cachingquerydecoratortquery-tresult) reads and writes whole handler results (a `Result<...>` in practice) through [`ICacheService`](group-09-caching.md#icacheservice) (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/CachingQueryDecorator.cs:43-48`), and [`DistributedCacheService`](group-09-caching.md#distributedcacheservice) writes and reads those values as UTF-8 JSON via `JsonSerializer` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/DistributedCacheService.cs:154,157`). Centralizing the token bookkeeping in `ReadObject` / `WriteErrors` and the callback shape in [`PropertyReader`](#propertyreader) keeps the two concrete converters small and structurally parallel. +- **Why it's built this way**: the Result types deliberately keep internal constructors and get-only properties (the construction discipline described under [`Result`](#result)), and the doc comment states the consequence plainly (`ResultJsonConverterFactory.cs:7-14`): default reflection-based deserialization cannot rehydrate them, so a purpose-built factory is what makes the type round-trippable for the distributed query cache. That cache path is real: [`CachingQueryDecorator`](group-05-cqrs-pipeline.md#cachingquerydecoratortquery-tresult) reads and writes whole handler results (a `Result<...>` in practice) through [`ICacheService`](group-09-caching.md#icacheservice) (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/CachingQueryDecorator.cs:43-48`), and [`DistributedCacheService`](group-09-caching.md#distributedcacheservice) writes and reads those values as UTF-8 JSON via `JsonSerializer` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/DistributedCacheService.cs:162,165`). Centralizing the token bookkeeping in `ReadObject` / `WriteErrors` and the callback shape in [`PropertyReader`](#propertyreader) keeps the two concrete converters small and structurally parallel. - **Where it's used**: attached to [`Result`](#result) and `Result` by attribute, so System.Text.Json picks it up automatically wherever a result is serialized, most consequentially on the cache path of [Chapter 9](group-09-caching.md) and anywhere a result is written to an HTTP or gRPC payload. - **Caveats / not-in-source**: the doc comment names Redis specifically (`ResultJsonConverterFactory.cs:11-12`), but the code path is `IDistributedCache`-shaped and [`DistributedCacheService`](group-09-caching.md#distributedcacheservice) documents Redis only as an example backing store (`DistributedCacheService.cs:11`). The converter is backing-store agnostic; which store a given host runs is a composition-time choice, not visible here. diff --git a/docs-src/onboarding/group-02-domain-building-blocks.md b/docs-src/onboarding/group-02-domain-building-blocks.md index 9016f8fb..080fc449 100644 --- a/docs-src/onboarding/group-02-domain-building-blocks.md +++ b/docs-src/onboarding/group-02-domain-building-blocks.md @@ -89,10 +89,10 @@ optimistic-concurrency token (`AuditableBaseEntity.cs:53`). The domain never wri are stamped centrally by [`AuditSaveChangesInterceptor`](group-07-persistence-ef-core.md#auditsavechangesinterceptor), which walks `ChangeTracker.Entries()` and assigns through `entry.Property(...).CurrentValue` -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:52-60`), -freezes `CreatedOn/By` as unmodified on updates (`AuditSaveChangesInterceptor.cs:67-68`), and writes or +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:61-69`), +freezes `CreatedOn/By` as unmodified on updates (`AuditSaveChangesInterceptor.cs:77-78`), and writes or clears `DeletedOn/By` only when the soft-delete flag actually transitions -(`AuditSaveChangesInterceptor.cs:98-105`). `Undelete()` is deliberately `protected` +(`AuditSaveChangesInterceptor.cs:122-129`). `Undelete()` is deliberately `protected` (`AuditableBaseEntity.cs:89`): reversing a soft delete is a per-entity business decision, not a capability the base hands out. The class declares both [`IAuditableEntity`](#iauditableentity) and [`IRowVersioned`](#irowversioned) (`AuditableBaseEntity.cs:13`); the latter exists so a repository can @@ -116,25 +116,25 @@ its own consistency boundary without each aggregate hand-rolling the same loops: - `SetItems` replaces a child collection through an overridable `ValidateSetItems` hook, so a root can veto (say) removing a shipped order line - (`AuditableAggregateRootEntity.cs:60-90`). + (`AuditableAggregateRootEntity.cs:60-92`). - `GetChildOrNotFound` finds an *active*, non-soft-deleted child by id or returns an [`Error.NotFound`](group-01-result-error-handling.md#error) failure - (`AuditableAggregateRootEntity.cs:103-120`). + (`AuditableAggregateRootEntity.cs:105-122`). - `RemoveChildOrNotFound` is that lookup followed by the child's own `Delete()`, short-circuiting on either failure and handing the deleted child *back* rather than consuming it, because which domain event a removal raises is aggregate vocabulary the framework must not invent - (`AuditableAggregateRootEntity.cs:156-178`). + (`AuditableAggregateRootEntity.cs:158-180`). - `RestoreChild` brings a soft-deleted child back (BR-135). It takes the child as an instance rather than an id, because a soft-deleted row is excluded by the global query filter and is not reachable through the loaded collection: the caller resolves it with an `ignoreQueryFilters` read and hands it in. The helper enforces only the "must actually be soft-deleted" rule, calls `Reactivate()`, and re-adds the child only when the collection does not already carry it - (`AuditableAggregateRootEntity.cs:212-249`, the duplicate guard at `:243`). Its `TChild` is + (`AuditableAggregateRootEntity.cs:214-251`, the duplicate guard at `:243`). Its `TChild` is constrained to [`IReactivatable`](#ireactivatable), which is exactly how the type system expresses "resurrection is opt-in". - `DeleteChildren` cascades a soft delete across a child collection, skipping already-deleted children so re-deleting a parent stays idempotent, and combining the failures into one - result (`AuditableAggregateRootEntity.cs:273-292`). + result (`AuditableAggregateRootEntity.cs:275-294`). `RemoveDomainEvents` is worth pausing on: it takes out exactly the events the persistence layer captured, matched by **reference** equality (`AuditableAggregateRootEntity.cs:43`), because two @@ -152,9 +152,9 @@ pay for it. [`ITenantEntity`](#itenantentity) read-only `string TenantId` (`ITenantEntity.cs:39`), and marking an entity with it buys two behaviors at once. On reads, a **named** `Tenant` global query filter is applied alongside the existing `SoftDelete` filter (`ApplyTenantFilters` at -`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:509` -and `entity.HasQueryFilter(TenantFilterName, filter)` at `ApplicationDbContext.cs:567`, with the filter -name constant at `ApplicationDbContext.cs:472` and the soft-delete filter at `ApplicationDbContext.cs:460`); +`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:514` +and `entity.HasQueryFilter(TenantFilterName, filter)` at `ApplicationDbContext.cs:572`, with the filter +name constant at `ApplicationDbContext.cs:477` and the soft-delete filter at `ApplicationDbContext.cs:465`); named filters compose with AND, so a tenant sees neither another tenant's rows nor soft-deleted ones. On writes, [`TenantSaveChangesInterceptor`](group-07-persistence-ef-core.md#tenantsavechangesinterceptor) stamps the value on insert and refuses a cross-tenant save, which is why the property is read-only on @@ -162,7 +162,7 @@ the domain type: the value is not a caller's to choose (`ITenantEntity.cs:16-20` 64-character `string` on purpose, because it arrives from a claim, a header, or configuration, all of which are strings (`ITenantEntity.cs:22-25`). Adopting tenancy is three things together: marking entities, calling `AddMultiTenancy(configuration)` -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:266`), and setting +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:276`), and setting `Tenancy:Enabled` (`ITenantEntity.cs:26-31`); a host that never resolves a tenant behaves exactly as it did before ([ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html)). @@ -244,21 +244,21 @@ reads the attribute in both directions ([ADR-090](https://ivanball.github.io/docs/adr/090-event-upcaster-registration.html)). [`IHasOrderingKey`](#ihasorderingkey) -(`MMCA.Common/Source/Core/MMCA.Common.Domain/Interfaces/IHasOrderingKey.cs:24`) is the opt-in ordering +(`MMCA.Common/Source/Core/MMCA.Common.Domain/Interfaces/IHasOrderingKey.cs:29`) is the opt-in ordering contract: an event returns a key naming the entity whose stream must stay sequential, typically the -aggregate id, or `null` to opt that individual instance out (`IHasOrderingKey.cs:26-30`). The outbox +aggregate id, or `null` to opt that individual instance out (`IHasOrderingKey.cs:31-35`). The outbox copies the value onto the row it writes (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/OutboxMessage.cs:152`) and the processor refuses to claim a row while an earlier unprocessed, non-dead-lettered row carries the same key in the same data source -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:474-475`, +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:463-464`, with an in-batch guard at `:516`), so ordering holds across batches and across scaled-out replicas rather than only within one batch. Read the doc comment before adopting it, because the trade-off is explicit: this is head-of-line blocking by design, so keys must be as NARROW as the requirement really is (one key per aggregate serializes that aggregate, a constant key serializes the whole outbox), and a dead-lettered row stops blocking so one poison event cannot freeze its key forever (`IHasOrderingKey.cs:15-22`). That pairing of a domain-declared intent with an indexed infrastructure -predicate (`ApplicationDbContext.cs:559-561`) is a compact [Rubric §12, Performance & Scalability] and +predicate (`ApplicationDbContext.cs:564-566`) is a compact [Rubric §12, Performance & Scalability] and [Rubric §29, Resilience & Business Continuity] example. ## Value objects, invalid instances cannot exist @@ -344,20 +344,20 @@ The concrete value objects split into a few patterns worth knowing up front: near-identical: a validated start/end pair with `Overlaps`, `Contains`, `Deconstruct`, and a length/duration accessor (`LengthInDays` at `DateRange.cs:38`, `Duration` at `DateTimeRange.cs:39`); `Create` rejects `end < start` (`DateRange.cs:30-35`, `DateTimeRange.cs:31-36`). Read the boundary - rules carefully: `Contains` is inclusive on both ends (`DateRange.cs:55-56`) while `Overlaps` - compares half-open (`DateRange.cs:46-50`). + rules carefully: `Contains` is inclusive on both ends (`DateRange.cs:56-57`) while `Overlaps` + compares half-open (`DateRange.cs:47-51`). ## Smart enumerations, a closed set that can carry behavior [`Enumeration`](#enumerationtenumeration) -(`MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:76`) is the answer to a +(`MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:77`) is the answer to a recurring shape a CLR `enum` handles badly: a closed set of named members that need behavior hanging off them (policies, rates, display rules) instead of a `switch` statement somewhere else (`Enumeration.cs:13-18`). Members are declared as `public static readonly` fields on the derived type and discovered by reflection over that type's own declared fields on first use, then frozen into a `ReadOnlyCollection` plus two `FrozenDictionary` lookups keyed by value and by name -(`Enumeration.cs:79-87`, `Enumeration.cs:170`); `All`, `FromValue`, and `FromName` read from those -(`Enumeration.cs:110`, `:115`, `:136`). The two resolvers return +(`Enumeration.cs:80-88`, `Enumeration.cs:171`); `All`, `FromValue`, and `FromName` read from those +(`Enumeration.cs:111`, `:115`, `:136`). The two resolvers return [`Result`](group-01-result-error-handling.md#result) with `Enumeration.UnknownValue` / `Enumeration.UnknownName` codes rather than throwing, which is the same contract every value-object factory in this group honors. Plain CLR enums stay the default, and this base is the documented opt-in @@ -370,18 +370,18 @@ The interesting part is what it deliberately does *not* do. It does **not** deri forces every `ValueObject` derivative to be a sealed record in the Shared layer, which would forbid the static-member idiom this type exists for (`Enumeration.cs:30-33`). It also does not implement `IEquatable`, for the same S4035 reason [`BaseEntity`](#baseentitytidentifiertype) -does not; equality is a type-guarded `Equals(object?)` override instead (`Enumeration.cs:42-47`, -`Enumeration.cs:157`). On the wire, [`EnumerationJsonConverterFactory`](#enumerationjsonconverterfactory) -(`Enumeration.cs:200`) walks the base chain to confirm a type is the self-referencing closed type and no -further derivative (`Enumeration.cs:203-204`, `:213-222`), then builds the private nested -[`EnumerationConverter`](#enumerationconvertertenumeration) (`Enumeration.cs:229`), which +does not; equality is a type-guarded `Equals(object?)` override instead (`Enumeration.cs:43-48`, +`Enumeration.cs:158`). On the wire, [`EnumerationJsonConverterFactory`](#enumerationjsonconverterfactory) +(`Enumeration.cs:201`) walks the base chain to confirm a type is the self-referencing closed type and no +further derivative (`Enumeration.cs:204-205`, `:213-222`), then builds the private nested +[`EnumerationConverter`](#enumerationconvertertenumeration) (`Enumeration.cs:230`), which writes the member's `Name` and reads it back through `FromName`, throwing `JsonException` on a non-string -token or an unknown name (`Enumeration.cs:232-247`) exactly the way `CurrencyJsonConverter` does, so the +token or an unknown name (`Enumeration.cs:233-248`) exactly the way `CurrencyJsonConverter` does, so the non-MVC paths (cache, outbox, integration events, typed `HttpClient` calls) fail the same way MVC model binding does. Note the registration gotcha the doc comment calls out: System.Text.Json reads `[JsonConverter]` off the type being converted without walking base types, so a concrete enumeration either repeats the attribute or the host registers the factory once in `JsonSerializerOptions.Converters` -(`Enumeration.cs:49-54`). This is a [Rubric §9, API & Contract Design] and [Rubric §15, Best Practices & +(`Enumeration.cs:50-55`). This is a [Rubric §9, API & Contract Design] and [Rubric §15, Best Practices & Code Quality] decision: one serialization shape, chosen once, with the trade-off written down where the next reader will find it. @@ -394,7 +394,7 @@ data-subject PII, and it is a property-only, non-inherited, single-use attribute Three mechanisms rely on the marker today. First, an architecture fitness test asserts that any entity declaring a `[Pii]` property also implements [`IAnonymizable`](#ianonymizable), so every piece of personal data has an erasure path (`PiiConventionTests`, driven by the shared `PiiConventionTestsBase`, -at `MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/PiiConventionTests.cs:13` over the rule +at `MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/PiiConventionTests.cs:20` over the rule body at `MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Governance/ArchitectureRules.Governance.cs:11-17`; the scan is structurally vacuous inside the framework itself because no data-subject type lives in @@ -404,9 +404,9 @@ reflects over an object's public readable properties and replaces every `[Pii]` `"[REDACTED]"` token (`PiiRedactor.cs:27`, `PiiRedactor.cs:42-53`), offering `Redact` (a property map), `RedactToString` (a single-line rendering, `PiiRedactor.cs:65`), and `HasPii` (a type probe, `PiiRedactor.cs:98`). Its per-type reflection metadata is cached in a `ConcurrentDictionary` of -[`RedactableProperty`](#redactableproperty) descriptors (`PiiRedactor.cs:31`, `PiiRedactor.cs:112-121`, -the descriptor itself at `PiiRedactor.cs:123`), and a property getter that throws is caught and rendered -as `"[unreadable]"` so a logging call site can never be broken by redaction (`PiiRedactor.cs:129-140`). +[`RedactableProperty`](#redactableproperty) descriptors (`PiiRedactor.cs:31`, `PiiRedactor.cs:112-124`, +the descriptor itself at `PiiRedactor.cs:126`), and a property getter that throws is caught and rendered +as `"[unreadable]"` so a logging call site can never be broken by redaction (`PiiRedactor.cs:132-143`). Third, the audit trail consumes both halves: [`AuditTrailSaveChangesInterceptor`](group-07-persistence-ef-core.md#audittrailsavechangesinterceptor) calls `PiiRedactor.HasPii` per entity type and writes `PiiRedactor.RedactedToken` on *both* sides of a @@ -492,8 +492,8 @@ in it. usage note spells out the convention (`EntityChangedEvent.cs:10-13`): raise `Added` from factories, `Updated` from mutation methods, `Deleted` from `Delete()`. Aggregates follow it literally (for example [`Category`](group-17-conference-domain.md#category) at - `MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/Category.cs:72,95,111` - for the root and `Category.cs:144,176,194` for its child items), and handlers short-circuit on it + `MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/Category.cs:73,97,113` + for the root and `Category.cs:146,178,196` for its child items), and handlers short-circuit on it (for example `MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Speakers/DomainEventHandlers/SpeakerDeletedHandler.cs:29` and @@ -546,7 +546,7 @@ in it. `[Parameter] string` route value into the module's identifier alias, for example `MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Speakers/SpeakerDetail.razor.cs:98`, `.../Pages/Session/SessionDetail.razor.cs:104`, `.../Pages/Event/EventDetail.razor.cs:86`, and - `MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Pages/Feedback/EventFeedback.razor.cs:254`; + `MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Pages/Feedback/EventFeedback.razor.cs:284`; Store's detail pages import the same namespace (for example `MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.UI/Pages/Product/ProductDetail.razor.cs:4`). Unit-covered by `DomainHelperTests` @@ -732,7 +732,7 @@ in it. `TicketComment` deliberately does NOT carry it, and says so (`MMCA.Helpdesk/Source/Modules/Tickets/MMCA.Helpdesk.Tickets.Domain/Tickets/TicketComment.cs:12,16`). The opting-in hosts are the three ADC services - (`MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:239`, + (`MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:238`, `MMCA.ADC.Conference.Service/Program.cs:350`, `MMCA.ADC.Engagement.Service/Program.cs:198`) and the Helpdesk web host (`MMCA.Helpdesk/Source/Hosts/MMCA.Helpdesk.Web/Program.cs:78`). - **Caveats / not-in-source**: retention is not automatic. `AuditTrailSettings.RetentionDays` defaults @@ -795,11 +795,11 @@ in it. `MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:33`). ### IHasOrderingKey -> MMCA.Common.Domain · `MMCA.Common.Domain.Interfaces` · `MMCA.Common/Source/Core/MMCA.Common.Domain/Interfaces/IHasOrderingKey.cs:24` · Level 0 · interface +> MMCA.Common.Domain · `MMCA.Common.Domain.Interfaces` · `MMCA.Common/Source/Core/MMCA.Common.Domain/Interfaces/IHasOrderingKey.cs:29` · Level 0 · interface - **What it is**: an opt-in contract for a domain or integration event that must be delivered **in order** relative to other events sharing the same key. One member, `string? OrderingKey` - (`IHasOrderingKey.cs:30`), returning a value that identifies the entity whose event stream must stay + (`IHasOrderingKey.cs:35`), returning a value that identifies the entity whose event stream must stay sequential, typically the aggregate id. - **Depends on**: nothing first-party. Implemented on an event record, most often a [`BaseIntegrationEvent`](group-04-events-outbox.md#baseintegrationevent). @@ -828,16 +828,16 @@ in it. (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/OutboxMessage.cs:149-152`). - **Why it's built this way**: ordering is enforced at **claim** time rather than at fetch time, which is what makes it survive batching and scale-out - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:363-370`). + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:352-359`). Making it opt-in per event keeps the unordered fast path free: a batch containing no keyed row runs exactly the query it always ran, with no subquery for the optimizer to prove away - (`OutboxProcessor.cs:394-399`). The decision is recorded in + (`OutboxProcessor.cs:383-388`). The decision is recorded in [ADR-003](https://ivanball.github.io/docs/adr/003-outbox-dual-dispatch.html) (`003-outbox-dual-dispatch.md:142-157`). - **Where it's used**: [`OutboxMessage`](group-04-events-outbox.md#outboxmessage) copies the key onto the row it writes (`OutboxMessage.cs:86` for the column, `:115` inside `FromDomainEvent` at `:98`). [`OutboxProcessor`](group-04-events-outbox.md#outboxprocessor) enforces it in two places: - `SelectOrderedCandidates` (`OutboxProcessor.cs:431-448`) keeps at most one row per key in this - cycle's candidate set (`:516`), and `FilterUnblocked` (`OutboxProcessor.cs:468-478`) adds the + `SelectOrderedCandidates` (`OutboxProcessor.cs:420-437`) keeps at most one row per key in this + cycle's candidate set (`:516`), and `FilterUnblocked` (`OutboxProcessor.cs:457-467`) adds the `NOT EXISTS` predicate to the claim update itself, so a second replica racing the same key loses on the row rather than on a check made before the race (`:550-554`; the retry-count conjunct at `:552` is what lets a dead-lettered predecessor stop blocking). The storage side is configured on @@ -845,7 +845,7 @@ in it. non-Unicode column (`.../Persistence/DbContexts/ApplicationDbContext.cs:538`) and the filtered `IX_OutboxMessages_Ordering` index over `(OrderingKey, OccurredOn)`, which stays empty for hosts that never declare a key (`ApplicationDbContext.cs:554-562`). Covered by `OutboxProcessorOrderingTests` - (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorOrderingTests.cs:101,181,197`) + (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorOrderingTests.cs:100,180,196`) and `OutboxMessageTests` (`.../OutboxMessageTests.cs:111,119`). - **Caveats / not-in-source**: no event in ADC, Store or Helpdesk implements this interface today; the only implementors in the workspace are test doubles @@ -854,7 +854,7 @@ in it. tested, not exercised by an application event. Ordering is also **not** total under a timestamp tie: the predecessor test is on `OccurredOn` alone, so two rows sharing a key and an exact timestamp are ordered within a cycle by `Id` but neither blocks the other in SQL, which the code states as a - deliberate non-guarantee (`OutboxProcessor.cs:372-377`). + deliberate non-guarantee (`OutboxProcessor.cs:361-366`). ### IRowVersioned > MMCA.Common.Domain · `MMCA.Common.Domain.Interfaces` · `MMCA.Common/Source/Core/MMCA.Common.Domain/Interfaces/IRowVersioned.cs:11` · Level 0 · interface @@ -870,13 +870,13 @@ in it. client sends back the token it last read, and the `UPDATE` includes it in the `WHERE` clause. If someone else changed the row in between, zero rows match, EF Core raises `DbUpdateConcurrencyException`, and the API maps that to `409 Conflict` - (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:399-403`). + (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:401-405`). The interesting design point is *why the token needs its own interface at all*: the repository's - aggregate-typed overload `SetOriginalRowVersion(TEntity, byte[])` (`IRepository.cs:406`) can only + aggregate-typed overload `SetOriginalRowVersion(TEntity, byte[])` (`IRepository.cs:408`) can only reach the aggregate **root**, because `TEntity` is the root type. A child entity edit (a `ProductVariant` under a `Product`) would otherwise need a second generic parameter for the child's own identifier type. `IRowVersioned` erases that identifier type: the child overload - (`IRepository.cs:417`) accepts any `IRowVersioned`, so child-level edits get the same stale-token + (`IRepository.cs:419`) accepts any `IRowVersioned`, so child-level edits get the same stale-token protection as the root. The doc comment states this rationale and cites [ADR-035](https://ivanball.github.io/docs/adr/035-optimistic-concurrency.html) (`IRowVersioned.cs:3-10`). - **Walkthrough**: one getter, `byte[] RowVersion` (`IRowVersioned.cs:15`), wrapped in a scoped @@ -893,7 +893,7 @@ in it. `RowVersion` property is a private-set `byte[]` defaulting to `[]` (`AuditableBaseEntity.cs:53`), so every auditable entity (aggregate roots **and** their children) satisfies it. Consumed by [`IRepository`](group-07-persistence-ef-core.md#irepositorytentity-tidentifiertype) - (`IRepository.cs:417`) and implemented in + (`IRepository.cs:419`) and implemented in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:86-93`, which casts the child to `object`, walks to `_context.Entry(...).Property(nameof(AuditableBaseEntity<>.RowVersion))` and assigns `OriginalValue`; @@ -934,8 +934,8 @@ in it. claim, a header, or configuration, all of which are strings, so a stronger domain type would only add a conversion at every boundary without adding a guarantee. The cap is enforced at the model (`TenantIdMaxLength = 64` at - `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:478`, - applied via `IsRequired().HasMaxLength(...).IsUnicode(false)` at `ApplicationDbContext.cs:526-529`). + `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:483`, + applied via `IsRequired().HasMaxLength(...).IsUnicode(false)` at `ApplicationDbContext.cs:531-534`). - *Marking is host-gated in practice* (`ITenantEntity.cs:26-31`): a host that never resolves a tenant behaves exactly as it did before. Adopting tenancy is marking entities, calling `AddMultiTenancy(configuration)`, and setting `Tenancy:Enabled`. @@ -948,7 +948,7 @@ in it. (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/TenantSaveChangesInterceptor.cs:29-34`). - **Where it's used**: the read side is [`ApplicationDbContext`](group-07-persistence-ef-core.md#applicationdbcontext)`.ApplyTenantFilters` - (`ApplicationDbContext.cs:509-569`, called from `OnModelCreating` at `:334`), which selects every + (`ApplicationDbContext.cs:514-574`, called from `OnModelCreating` at `:334`), which selects every non-owned `ITenantEntity` type (`:439-441`), indexes the discriminator on non-Cosmos engines, widening it to `(TenantId, IsDeleted)` when the entity is also auditable (`:457-466`), and installs the named filter `CurrentTenantId == null || EF.Property(e, "TenantId") == CurrentTenantId` @@ -961,7 +961,7 @@ in it. [`CrossTenantWriteException`](group-07-persistence-ef-core.md#crosstenantwriteexception) rather than writing a row nobody can read (`:101-110`), and a declared-versus-current mismatch is rejected the same way (`:123`). The host gate is `AddMultiTenancy` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:266`) plus + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:276`) plus [`TenancySettings`](group-07-persistence-ef-core.md#tenancysettings) (`Tenancy:Enabled` at `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettings.cs:67`, claim-then-header resolution order at `TenancySettings.cs:57`). The only entities marked in the workspace apps today @@ -972,7 +972,7 @@ in it. today. - **Caveats / not-in-source**: `Tenancy:Enabled` gates **resolution**, not isolation. The filter and the interceptor are always registered and are inert whenever no tenant is resolved - (`TenancySettings.cs:37-39`, and the registration note at `DependencyInjection.cs:290`), so an + (`TenancySettings.cs:37-39`, and the registration note at `DependencyInjection.cs:300`), so an untenanted code path (a job, a seeder) reads every tenant's rows by design. That is the documented behavior, not an oversight, but it means "tenant safety" is a property of the request pipeline resolving a tenant, not of the entity marker alone. @@ -1028,7 +1028,7 @@ in it. `PiiConventionTestsBase` (`MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/PiiConventionTestsBase.cs:7`) that just passes its `IArchitectureMap`: - `MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/PiiConventionTests.cs:13` (the *scan* + `MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/PiiConventionTests.cs:20` (the *scan* is structurally vacuous today, the framework Domain ships no data-subject type), `MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Governance/PiiConventionTests.cs:3`, and `MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/PiiConventionTests.cs:3`. The framework @@ -1049,7 +1049,7 @@ in it. it is never treated as personal data (`AuditTrailSaveChangesInterceptor.cs:489-496`). ### RedactableProperty -> MMCA.Common.Domain · `MMCA.Common.Domain.Privacy` · `MMCA.Common/Source/Core/MMCA.Common.Domain/Privacy/PiiRedactor.cs:123` · Level 0 · class (private sealed, nested) +> MMCA.Common.Domain · `MMCA.Common.Domain.Privacy` · `MMCA.Common/Source/Core/MMCA.Common.Domain/Privacy/PiiRedactor.cs:126` · Level 0 · class (private sealed, nested) - **What it is**: [`PiiRedactor`](#piiredactor)'s private sealed nested cached-metadata helper, one entry per public readable property, capturing the name, whether the property is PII, and how to read @@ -1057,16 +1057,16 @@ in it. the redactor. - **Depends on**: `System.Reflection.PropertyInfo` (BCL); constructed by [`PiiRedactor`](#piiredactor). - **Walkthrough**: a primary-constructor class - `RedactableProperty(string name, bool isPii, PropertyInfo info)` (`PiiRedactor.cs:123`) exposing - `Name` (`PiiRedactor.cs:125`), the precomputed `IsPii` flag (`PiiRedactor.cs:127`), and - `Read(object target)` (`PiiRedactor.cs:129`), which calls `info.GetValue(target)` and catches + `RedactableProperty(string name, bool isPii, PropertyInfo info)` (`PiiRedactor.cs:126`) exposing + `Name` (`PiiRedactor.cs:128`), the precomputed `IsPii` flag (`PiiRedactor.cs:130`), and + `Read(object target)` (`PiiRedactor.cs:132`), which calls `info.GetValue(target)` and catches `TargetInvocationException` to return `UnreadableToken` rather than propagate, the inline comment - noting that a throwing getter must never break a logging call site (`PiiRedactor.cs:131-139`). + noting that a throwing getter must never break a logging call site (`PiiRedactor.cs:134-142`). - **Why it's built this way**: precomputing the `IsPii` flag and holding the `PropertyInfo` once per type (cached in `PiiRedactor.Cache`) means redaction never re-evaluates the `[Pii]` reflection check on the hot path, it just reads the cached flag and (for non-PII members) invokes the captured getter. - **Where it's used**: produced and consumed entirely within [`PiiRedactor`](#piiredactor) - (`GetProperties`, `PiiRedactor.cs:112-121`); it has no independent consumers. + (`GetProperties`, `PiiRedactor.cs:112-124`); it has no independent consumers. ### IAggregateRoot > MMCA.Common.Domain · `MMCA.Common.Domain.Interfaces` · `MMCA.Common/Source/Core/MMCA.Common.Domain/Interfaces/IAggregateRoot.cs:9` · Level 1 · interface @@ -1122,7 +1122,7 @@ in it. - **What it is**: a static helper that produces a log- and telemetry-safe view of any object by masking every property marked with [`PiiAttribute`](#piiattribute), replacing each PII value with the literal `[REDACTED]`. It is the **redaction half** of the [`PiiAttribute`](#piiattribute) contract. -- **Depends on**: [`PiiAttribute`](#piiattribute) (the marker it reads, `PiiRedactor.cs:6,119`); BCL +- **Depends on**: [`PiiAttribute`](#piiattribute) (the marker it reads, `PiiRedactor.cs:6,122`); BCL only (`System.Reflection`, `System.Collections.Concurrent`, `System.Collections.ObjectModel`, `System.Text`, `System.Globalization`). - **Concept introduced, value-erasing PII redaction for logs/telemetry.** `[Rubric §13, Observability @@ -1160,8 +1160,14 @@ in it. - `GetProperties(Type)` (`PiiRedactor.cs:112`): the cache filler. `Cache.GetOrAdd` runs a `static` lambda that reflects public, instance, readable, non-indexer properties and builds a [`RedactableProperty`](#redactableproperty) for each, recording whether it carries the marker via - `p.IsDefined(typeof(PiiAttribute), inherit: false)` (`PiiRedactor.cs:112-121`). The `inherit: false` - mirrors [`PiiAttribute`](#piiattribute)'s `Inherited = false`. + `Attribute.IsDefined(p, typeof(PiiAttribute), inherit: true)` (`PiiRedactor.cs:112-124`, the check + at line 122). The static `Attribute.IsDefined` is used rather than the `PropertyInfo` instance + method because the instance method ignores `inherit` for properties, while the static one walks an + override back to its base declaration (`PiiRedactor.cs:119-121`): a derived type that overrides a + `[Pii]` property without repeating the marker stays redacted (asserted at `PiiRedactorTests.cs:51`). + This matches [`PiiAttribute`](#piiattribute)'s `Inherited = true` + (`MMCA.Common/Source/Core/MMCA.Common.Domain/Attributes/PiiAttribute.cs:18`), and + `AuditTrailSaveChangesInterceptor` resolves the marker the same way (`AuditTrailSaveChangesInterceptor.cs:504`). - **Why it's built this way**: a `static` pure helper has no DI dependency, so it can be called from any layer, including a transport boundary, without wiring. Per-type caching keeps the logging path cheap; value-erasure (over truncation/hashing) is the conservative §30 choice; and routing personal @@ -1171,7 +1177,7 @@ in it. calls `HasPii` once per changed entity type (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:287`) and writes `RedactedToken` into both the `OldValue` and `NewValue` columns of a `[Pii]` property's - change row (`:309-310`), which is how the change history avoids becoming a second copy of personal + change row (`:310-311`), which is how the change history avoids becoming a second copy of personal data ([ADR-075](https://ivanball.github.io/docs/adr/075-audit-trail.html)); that behavior is asserted in `AuditTrailSaveChangesInterceptorTests` (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailSaveChangesInterceptorTests.cs:180-181`). @@ -1180,14 +1186,14 @@ in it. end to end (composed with [`IAnonymizable`](#ianonymizable)) by `PiiErasureContractFitnessTests` (`MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/PiiErasureContractFitnessTests.cs:19`). - **Caveats / not-in-source**: `Redact` and `RedactToString` have **no production call site**; only - tests invoke them (`PiiRedactorTests.cs:35,46,53,58,68` and `PiiErasureContractFitnessTests.cs:29,42,46,69`), + tests invoke them (`PiiRedactorTests.cs:51,56,67,74,79,89` and `PiiErasureContractFitnessTests.cs:29,42,46,69`), so the log-side control is ready and tested but opt-in per call site rather than an automatic pipeline stage. Redaction is also **shallow** (one level), as the remarks state (`PiiRedactor.cs:19`): a non-PII property whose value is itself an object with nested `[Pii]` members is read and emitted as-is, not recursively masked. Only public instance properties are inspected (`PiiRedactor.cs:115`), so fields and non-public members are ignored. A property getter that throws `TargetInvocationException` yields `[unreadable]` instead of crashing the log call - (`PiiRedactor.cs:135-139`). + (`PiiRedactor.cs:138-142`). ### IAnonymizable > MMCA.Common.Domain · `MMCA.Common.Domain.Interfaces` · `MMCA.Common/Source/Core/MMCA.Common.Domain/Interfaces/IAnonymizable.cs:22` · Level 3 · interface @@ -1260,12 +1266,12 @@ in it. interface teeth: the aggregate helper [`AuditableAggregateRootEntity`](#auditableaggregaterootentitytidentifiertype)`.RestoreChild` constrains its child to `where TChild : AuditableBaseEntity, IReactivatable` - (`MMCA.Common/Source/Core/MMCA.Common.Domain/Entities/AuditableAggregateRootEntity.cs:212-218`). A + (`MMCA.Common/Source/Core/MMCA.Common.Domain/Entities/AuditableAggregateRootEntity.cs:214-220`). A child that does not implement the interface simply cannot be passed to the helper: resurrection is a business decision per entity, not a capability the base class hands out to every soft-deletable row. - **Why it's built this way**: `RestoreChild` shows the payoff. It checks only the framework-level rule - ("this candidate is soft-deleted", `AuditableAggregateRootEntity.cs:223-231`), delegates the entity's + ("this candidate is soft-deleted", `AuditableAggregateRootEntity.cs:225-233`), delegates the entity's own rule to `child.Reactivate()` and propagates its failure verbatim (`:233-237`), then re-adds the child to the aggregate's collection only when it is not already there, because a caller who resolved the child through an `ignoreQueryFilters` read holds an instance the loaded collection never @@ -1548,7 +1554,7 @@ in it. helpers in the Application validation layer. ### Enumeration -> MMCA.Common.Shared · `MMCA.Common.Shared.ValueObjects` · `MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:76` · Level 3 · class (abstract, generic) +> MMCA.Common.Shared · `MMCA.Common.Shared.ValueObjects` · `MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:77` · Level 3 · class (abstract, generic) - **What it is**: the abstract base for a **smart enumeration**: a closed set of named, integer-valued members declared on the derived type as `public static readonly` fields. Unlike a CLR `enum`, each @@ -1557,50 +1563,50 @@ in it. - **Depends on**: [`Error`](group-01-result-error-handling.md#error), [`Result`](group-01-result-error-handling.md#result), and [`EnumerationJsonConverterFactory`](#enumerationjsonconverterfactory) (mutual: the base carries - `[JsonConverter(typeof(EnumerationJsonConverterFactory))]` at `Enumeration.cs:71` while the factory + `[JsonConverter(typeof(EnumerationJsonConverterFactory))]` at `Enumeration.cs:72` while the factory is constrained on `Enumeration`). Externals: `System.Collections.Frozen`, `System.Collections.ObjectModel`, `System.Reflection`, `System.Text.Json`. - **Concept introduced, the self-referencing generic (curiously recurring) constraint.** `[Rubric §4, DDD]` (a closed domain vocabulary that carries behaviour) and `[Rubric §1, SOLID]` (open for extension: adding a member is a field, not a new `case` in every switch). The declaration is `abstract class Enumeration where TEnumeration : Enumeration` - (`Enumeration.cs:76-77`). The type parameter is the concrete type itself, which is what lets `All`, + (`Enumeration.cs:77-78`). The type parameter is the concrete type itself, which is what lets `All`, `FromValue` and `FromName` be **per-enumeration** and strongly typed: `Priority.FromValue(2)` returns `Result` with no type argument written by hand, and each closed type gets its own static lookup tables (static fields on a generic type are per-constructed-type). The - `CA1000` suppression (`Enumeration.cs:72-75`) exists for exactly this and states the reasoning: a + `CA1000` suppression (`Enumeration.cs:73-76`) exists for exactly this and states the reasoning: a non-generic sibling would return the base type and force a cast at every call site. - **Concept introduced, lazy reflection frozen into a lookup.** `[Rubric §12, Performance & - Scalability]`. Three `Lazy` statics (`Enumeration.cs:79-87`) build the member set once per closed + Scalability]`. Three `Lazy` statics (`Enumeration.cs:80-88`) build the member set once per closed type on first touch: `MembersLazy` runs `DiscoverMembers`, `ByValueLazy` and `ByNameLazy` project it into `FrozenDictionary` instances (the name dictionary using `StringComparer.OrdinalIgnoreCase`). `FrozenDictionary` is the right structure for a build-once, read-forever table: construction is more expensive, lookups are faster than `Dictionary`. `ToFrozenDictionary` also throws `ArgumentException` on a duplicate key, which turns two members sharing a `Value` or a `Name` into a fail-fast at first - use rather than a silent shadowing bug (`Enumeration.cs:35-40`). + use rather than a silent shadowing bug (`Enumeration.cs:35-41`). - **Walkthrough** - - `protected Enumeration(int value, string name)` (`Enumeration.cs:92`): the only constructor; + - `protected Enumeration(int value, string name)` (`Enumeration.cs:93`): the only constructor; derived types keep theirs private and expose members as static fields. - - `Name` (`Enumeration.cs:100`) and `Value` (`Enumeration.cs:104`): getter-only, tagged + - `Name` (`Enumeration.cs:101`) and `Value` (`Enumeration.cs:105`): getter-only, tagged `[DataMember(Order = 1)]` and `[DataMember(Order = 2)]` under the class-level `[DataContract]` - (`Enumeration.cs:70`). The split is intentional and documented: `Value` is the **persisted** + (`Enumeration.cs:71`). The split is intentional and documented: `Value` is the **persisted** representation, `Name` is the **serialized/display** one. - - `All` (`Enumeration.cs:110`): `IReadOnlyCollection`, ordered by `Value`, cached for + - `All` (`Enumeration.cs:111`): `IReadOnlyCollection`, ordered by `Value`, cached for the lifetime of the closed type. - - `FromValue(int value)` (`Enumeration.cs:120`): `TryGetValue` on the frozen by-value map, else + - `FromValue(int value)` (`Enumeration.cs:121`): `TryGetValue` on the frozen by-value map, else `Error.Invariant(code: "Enumeration.UnknownValue", ...)` naming the concrete type in the message - (`Enumeration.cs:125-129`). - - `FromName(string name)` (`Enumeration.cs:141`): the case-insensitive twin, null-coalescing the - argument to `string.Empty` first (`Enumeration.cs:143`), else + (`Enumeration.cs:126-130`). + - `FromName(string name)` (`Enumeration.cs:142`): the case-insensitive twin, null-coalescing the + argument to `string.Empty` first (`Enumeration.cs:144`), else `Error.Invariant(code: "Enumeration.UnknownName", ...)`. - - `ToString()` (`Enumeration.cs:154`): returns `Name`. - - `Equals(object?)` (`Enumeration.cs:157-160`) and `GetHashCode()` (`Enumeration.cs:163`): + - `ToString()` (`Enumeration.cs:155`): returns `Name`. + - `Equals(object?)` (`Enumeration.cs:158-161`) and `GetHashCode()` (`Enumeration.cs:164`): type-guarded equality, `GetType() == other.GetType() && Value == other.Value`, hashed as `HashCode.Combine(GetType(), Value)`. The class deliberately does **not** implement - `IEquatable`: the remark at `Enumeration.cs:41-47` cites Sonar S4035 (an unsealed + `IEquatable`: the remark at `Enumeration.cs:42-48` cites Sonar S4035 (an unsealed `IEquatable` breaks the equality contract for subclasses) and leaves that to a sealed derived type. `[Rubric §15, Best Practices & Code Quality]`. - - `DiscoverMembers()` (`Enumeration.cs:170-179`): reflects over + - `DiscoverMembers()` (`Enumeration.cs:171-180`): reflects over `BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly`, keeps fields that are `IsInitOnly` (that is, `readonly`) and assignable to `TEnumeration`, reads their values, orders by `Value` and freezes to a `ReadOnlyCollection`. `DeclaredOnly` is the load-bearing flag: a derived @@ -1626,16 +1632,16 @@ in it. types in the workspace are the fixtures in `MMCA.Common.Shared.Tests/ValueObjects/EnumerationTests.cs` and `EnumerationSerializationTests.cs`, plus `MMCA.Common.Infrastructure.Tests/Persistence/Conversions/EnumerationValueConverterTests.cs`. Treat - the `Priority` sample in the doc comment (`Enumeration.cs:55-67`) as the usage template. + the `Priority` sample in the doc comment (`Enumeration.cs:56-68`) as the usage template. ### EnumerationConverter -> MMCA.Common.Shared · `MMCA.Common.Shared.ValueObjects` · `MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:229` · Level 3 · class (private nested, sealed) +> MMCA.Common.Shared · `MMCA.Common.Shared.ValueObjects` · `MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:230` · Level 3 · class (private nested, sealed) - **What it is**: the actual `JsonConverter` for one closed enumeration type. It is a **private nested class** inside - [`EnumerationJsonConverterFactory`](#enumerationjsonconverterfactory) (`Enumeration.cs:229-248`), + [`EnumerationJsonConverterFactory`](#enumerationjsonconverterfactory) (`Enumeration.cs:230-249`), constrained the same way as the base: `where TEnumeration : Enumeration` - (`Enumeration.cs:230`). + (`Enumeration.cs:231`). - **Depends on**: [`Enumeration`](#enumerationtenumeration) (it calls `Enumeration.FromName`) and `System.Text.Json`. - **Concept, the generic worker behind a converter factory.** `[Rubric §2, Design Patterns]` @@ -1644,28 +1650,28 @@ in it. to obtain one is through `CreateConverter`, which guarantees the generic argument is a legal closed enumeration. - **Walkthrough** - - `Read` (`Enumeration.cs:232`): rejects a non-string token with + - `Read` (`Enumeration.cs:233`): rejects a non-string token with `throw new JsonException($"{typeof(TEnumeration).Name} must be a string.")` - (`Enumeration.cs:234-235`), reads the string (`Enumeration.cs:237`), resolves it through - `Enumeration.FromName(name)` (`Enumeration.cs:239`) and throws a naming - `JsonException` when that fails (`Enumeration.cs:240-241`). Identical failure behaviour to + (`Enumeration.cs:235-236`), reads the string (`Enumeration.cs:238`), resolves it through + `Enumeration.FromName(name)` (`Enumeration.cs:240`) and throws a naming + `JsonException` when that fails (`Enumeration.cs:241-242`). Identical failure behaviour to [`CurrencyJsonConverter`](#currencyjsonconverter), which is deliberate. - - `Write` (`Enumeration.cs:246-247`): `writer.WriteStringValue(value.Name)`. The wire shape is the + - `Write` (`Enumeration.cs:247-248`): `writer.WriteStringValue(value.Name)`. The wire shape is the member name, never the integer, so a JSON payload stays readable and a renumbering is not a breaking API change (the integer is the *persistence* representation, handled by [`EnumerationValueConverter`](group-07-persistence-ef-core.md#enumerationvalueconvertertenumeration)). - **Where it's used**: instantiated reflectively by - `EnumerationJsonConverterFactory.CreateConverter` (`Enumeration.cs:207-209`). It has no other + `EnumerationJsonConverterFactory.CreateConverter` (`Enumeration.cs:208-210`). It has no other caller and no public surface. ### EnumerationJsonConverterFactory -> MMCA.Common.Shared · `MMCA.Common.Shared.ValueObjects` · `MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:200` · Level 3 · class (sealed) +> MMCA.Common.Shared · `MMCA.Common.Shared.ValueObjects` · `MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:201` · Level 3 · class (sealed) - **What it is**: a `JsonConverterFactory` that hands System.Text.Json a [`EnumerationConverter`](#enumerationconvertertenumeration) for any concrete smart enumeration, so every member serializes as its `Name`. - **Depends on**: [`Enumeration`](#enumerationtenumeration) (mutual: the base type - carries `[JsonConverter(typeof(EnumerationJsonConverterFactory))]` at `Enumeration.cs:71`), + carries `[JsonConverter(typeof(EnumerationJsonConverterFactory))]` at `Enumeration.cs:72`), [`EnumerationConverter`](#enumerationconvertertenumeration), and `System.Text.Json.Serialization`. - **Concept introduced, why an *open generic* needs a factory.** `[Rubric §9, API & Contract @@ -1673,34 +1679,34 @@ in it. serves `Priority`, `Severity` and every future enumeration. `JsonConverterFactory` is the System.Text.Json extension point for exactly that: `CanConvert` answers "is this type mine?" and `CreateConverter` builds the closed converter on demand. There is a second, subtler reason the - factory has to exist at all, documented at `Enumeration.cs:48-54` and again at `Enumeration.cs:189-193`: + factory has to exist at all, documented at `Enumeration.cs:49-55` and again at `Enumeration.cs:190-194`: System.Text.Json reads `[JsonConverter]` off the type it is converting **without walking base types**, so the attribute on `Enumeration` does not reach `Priority`. A host therefore either repeats the attribute on each concrete type or registers this factory once. `AddAPI` takes the second route (`MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:59`, with the inline comment explaining the `inherit: false` behaviour). - **Walkthrough** - - `CanConvert(Type typeToConvert)` (`Enumeration.cs:203-204`): + - `CanConvert(Type typeToConvert)` (`Enumeration.cs:204-205`): `GetEnumerationArgument(typeToConvert) == typeToConvert`. Read that carefully: it is true only when the type *is* the type argument of its own `Enumeration` base, that is, only for the self-referencing closed type. A class deriving further from a concrete enumeration is left to the - default converter rather than being silently serialized as its base (`Enumeration.cs:211-217`). - - `CreateConverter(...)` (`Enumeration.cs:207-209`): + default converter rather than being silently serialized as its base (`Enumeration.cs:212-218`). + - `CreateConverter(...)` (`Enumeration.cs:208-210`): `Activator.CreateInstance(typeof(EnumerationConverter<>).MakeGenericType(typeToConvert))` cast to `JsonConverter`. Reflection runs once per type; System.Text.Json caches the resulting converter. - - `GetEnumerationArgument(Type?)` (`Enumeration.cs:218-227`): walks `type.BaseType` upward looking + - `GetEnumerationArgument(Type?)` (`Enumeration.cs:219-228`): walks `type.BaseType` upward looking for a generic type whose definition is `typeof(Enumeration<>)`, returning its single generic argument, or `null` at the top of the chain. - **Why it's built this way**: registering one factory in `JsonSerializerOptions.Converters` gives uniform name-based JSON for every enumeration across the whole API surface, including the non-MVC paths (cache entries, outbox payloads, integration events, typed `HttpClient` calls) that never see MVC model binding. The `HandleNull` default of `false` is left alone on purpose - (`Enumeration.cs:194-198`) so nullable members still deserialize to `null`. + (`Enumeration.cs:195-199`) so nullable members still deserialize to `null`. - **Where it's used**: registered in `AddAPI` (`MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:59`), named in that method's doc comment alongside `CurrencyJsonConverter` (`.../DependencyInjection.cs:30-31`); also reachable via the `[JsonConverter]` attribute on - [`Enumeration`](#enumerationtenumeration) (`Enumeration.cs:71`) for a member typed as + [`Enumeration`](#enumerationtenumeration) (`Enumeration.cs:72`) for a member typed as the base itself. ### PhoneNumberInvariants @@ -1795,12 +1801,12 @@ in it. `CategoryItem`, `SessionSpeaker`; Store's `OrderLine`, `ShoppingCartItem`). The stamping side is implemented by [`AuditSaveChangesInterceptor`](group-07-persistence-ef-core.md#auditsavechangesinterceptor) - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:104-105`) + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:128-129`) and covered by [`AuditableBaseEntityTests`](group-28-testing-infrastructure.md#per-project-test-rollup) and [`AuditableBaseEntityAdditionalTests`](group-28-testing-infrastructure.md#per-project-test-rollup). - **Caveats / not-in-source**: the delete stamps are written only on a **transition** of the flag - (`AuditSaveChangesInterceptor.cs:98-102`), so updating an already-deleted row keeps the stamps of + (`AuditSaveChangesInterceptor.cs:122-126`), so updating an already-deleted row keeps the stamps of the delete that produced it rather than refreshing them. ### Email @@ -1811,14 +1817,13 @@ in it. constructor exists for JSON round-tripping only. - **Depends on**: [`ValueObject`](#valueobject) (Level 0), [`EmailInvariants`](#emailinvariants) (Level 3), [`Result`](group-01-result-error-handling.md#result) (Level 2). -- **Concept introduced, normalization at construction plus implicit conversion.** `[Rubric §4, +- **Concept introduced, normalization at construction.** `[Rubric §4, Domain-Driven Design]` (rich value objects with invariant-protected construction). Three ideas combine here: (1) the `[JsonConstructor]`-tagged private constructor (`Email.cs:22-23`) keeps ad-hoc construction out while letting System.Text.Json rehydrate; (2) `Create` validates *and - normalizes*, returning `Result` instead of throwing; (3) - `public static implicit operator string(Email email)` (`Email.cs:45`) lets an `Email` drop into a - `string` position without a cast, a pragmatic bridge for code that has not adopted the value object - yet. The `#pragma warning disable CA1308` around `ToLowerInvariant` (`Email.cs:38-40`) is a scoped + normalizes*, returning `Result` instead of throwing; (3) there is no implicit conversion to + `string`, so leaving the value object is always explicit, through `Value` or `ToString()` + (`Email.cs:44`). The `#pragma warning disable CA1308` around `ToLowerInvariant` (`Email.cs:38-40`) is a scoped suppression with its justification on the same line ("Email addresses are conventionally lowercase per RFC 5321"). `[Rubric §15, Best Practices & Code Quality]` (suppressions are narrow and explained, never blanket). @@ -1830,7 +1835,7 @@ in it. (`Email.cs:34`), propagates `result.Errors` on failure (`Email.cs:36`), and only then lowercases (`Email.cs:39`). Order matters: validation runs on the trimmed input, normalization on the validated value. - - `implicit operator string` (`Email.cs:45`) and `ToString()` (`Email.cs:48`): both return `Value`. + - `ToString()` (`Email.cs:44`): returns `Value`; there is no implicit conversion to `string`. - **Why it's built this way**: normalizing once at construction means the rest of the system can compare, index and store emails case-insensitively without a `.ToLower()` at every use. The remarks (`Email.cs:7-14`) also pin the persistence shape: EF maps this with `HasConversion`, **not** @@ -1858,7 +1863,7 @@ in it. - **Depends on**: [`ValueObject`](#valueobject) (Level 0), [`PhoneNumberInvariants`](#phonenumberinvariants) (Level 3), [`Result`](group-01-result-error-handling.md#result) (Level 2). -- **Concept**: the same private-constructor + static-factory + implicit-conversion shape taught under +- **Concept**: the same private-constructor + static-factory shape taught under [`Email`](#email); this section cross-references rather than repeating it. One difference worth noting: there is no case normalization (a phone number has no case), and `Create` validates the **raw** string then stores `value.Trim()` (`PhoneNumber.cs:32,36`), whereas `Email.Create` trims @@ -1869,8 +1874,8 @@ in it. `[DataContract]` at `PhoneNumber.cs:15`); the `[JsonConstructor]` private constructor (`PhoneNumber.cs:22-23`); `Create(string value)` (`PhoneNumber.cs:30`) delegating to `PhoneNumberInvariants.EnsurePhoneNumberIsValid` and returning `Result.Failure` with - the propagated errors (`PhoneNumber.cs:34`); the implicit `operator string` - (`PhoneNumber.cs:41`) and `ToString()` (`PhoneNumber.cs:44`). + the propagated errors (`PhoneNumber.cs:34`); and `ToString()` (`PhoneNumber.cs:40`), which + returns `Value` (there is no implicit conversion to `string`). - **Why it's built this way**: as with `Email`, the remarks (`PhoneNumber.cs:7-14`) specify `HasConversion` rather than `OwnsOne` so the column stays `nvarchar`, and point at the shipped [`PhoneNumberValueConverter`](group-07-persistence-ef-core.md#phonenumbervalueconverter) @@ -1916,51 +1921,54 @@ in it. two structurally equal events raised separately are still two distinct occurrences (`:41-42`). An empty input returns early (`:44-47`). - `SetItems(List, IEnumerable)` - (`AuditableAggregateRootEntity.cs:60-74`): materializes the incoming sequence once to avoid - double enumeration (`:69`), calls the validation hook, then `Clear()` + `AddRange()` on the - **same list instance** (`:72-73`). Never replacing the list reference is what keeps EF change - tracking able to see the adds and removes. - - `ValidateSetItems` (`AuditableAggregateRootEntity.cs:85-90`): `protected virtual`, + (`AuditableAggregateRootEntity.cs:60-76`): always materializes the incoming sequence into a + **fresh** `List` (`:71`), which both avoids double enumeration and guards the + case where the caller passes the backing collection itself (or a read-only view over it): an + in-place reuse would be emptied by the `Clear()` before `AddRange` could read it (`:69-70`). + It then calls the validation hook and runs `Clear()` + `AddRange()` on the **same list + instance** (`:74-75`). Never replacing the list reference is what keeps EF change tracking able + to see the adds and removes. + - `ValidateSetItems` (`AuditableAggregateRootEntity.cs:87-92`): `protected virtual`, empty by default. The extension point for rules such as "a fulfilled order line cannot be removed". - - `GetChildOrNotFound` (`AuditableAggregateRootEntity.cs:103-120`): a + - `GetChildOrNotFound` (`AuditableAggregateRootEntity.cs:105-122`): a `FirstOrDefault` over the in-memory collection matching on `Id.Equals(childId) && !c.IsDeleted` - (`:110`), returning `Error.NotFound` with source and target rather than throwing or returning + (`:112`), returning `Error.NotFound` with source and target rather than throwing or returning `null`. - - `RemoveChildOrNotFound` (`AuditableAggregateRootEntity.cs:156-178`): the + - `RemoveChildOrNotFound` (`AuditableAggregateRootEntity.cs:158-180`): the lookup above followed by the child's own `Delete()`, short-circuiting on either failure. The deleted child comes back **in the result** rather than being consumed here, because which domain event a removal raises is aggregate vocabulary and therefore the caller's decision - (`:129-145` shows the intended call shape). - - `RestoreChild` (`AuditableAggregateRootEntity.cs:212-249`): constrained - `where TChild : AuditableBaseEntity, IReactivatable` (`:217`). It takes the child as an + (`:135-146` shows the intended call shape). + - `RestoreChild` (`AuditableAggregateRootEntity.cs:214-251`): constrained + `where TChild : AuditableBaseEntity, IReactivatable` (`:219`). It takes the child as an **instance**, not an id, because a soft-deleted row is hidden by the global query filter and is not reachable through the loaded collection: the caller resolves it with an `ignoreQueryFilters` - read (`:183-189`). It rejects a candidate that is not soft-deleted using an error code the - **caller** supplies (`:225-231`), calls `Reactivate()`, and re-adds the child only when the - collection does not already carry that id (`:243-246`). - - `DeleteChildren` (`AuditableAggregateRootEntity.cs:273-292`): cascades a soft - delete across a child collection, **skipping** children that are already deleted (`:283-286`) so + read (`:186-190`). It rejects a candidate that is not soft-deleted using an error code the + **caller** supplies (`:225-233`), calls `Reactivate()`, and re-adds the child only when the + collection does not already carry that id (`:245-248`). + - `DeleteChildren` (`AuditableAggregateRootEntity.cs:275-294`): cascades a soft + delete across a child collection, **skipping** children that are already deleted (`:285-288`) so re-deleting a parent is idempotent, and combining the rest with - [`Result.Combine`](group-01-result-error-handling.md#result) (`:291`). The results list is - allocated lazily (`:279`, `:288`), so a childless cascade allocates nothing. + [`Result.Combine`](group-01-result-error-handling.md#result) (`:293`). The results list is + allocated lazily (`:281`, `:290`), so a childless cascade allocates nothing. - **Why it's built this way**: every one of these helpers replaces a loop that each aggregate used to hand-roll. The consistent split is that the base class owns the mechanics (find, delete, restore, cascade, aggregate the errors) while the aggregate method owns the vocabulary (which event, which error code), which is why `RemoveChildOrNotFound` and `RestoreChild` hand the child back instead of raising an event themselves, and why `RestoreChild` takes `notDeletedErrorCode` as a parameter for the same reason `GetChildOrNotFound` takes `source` - (`AuditableAggregateRootEntity.cs:201-206`). Ownership checks and field re-validation stay in the + (`AuditableAggregateRootEntity.cs:203-208`). Ownership checks and field re-validation stay in the calling method and run **before** the helper, so a rejected restore leaves the child untouched - (`:190-195`). + (`:193-196`). - **Where it's used**: base class for every aggregate root in the consumers. ADC's `Event` uses three `DeleteChildren` calls in one `Result.Combine` (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/Event.cs:361-363`), `RestoreChild` for room reinstatement (`Event.cs:496`) and `RemoveChildOrNotFound` for room - removal (`Event.cs:486`); `Session` cascades to its speakers and question answers - (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:311-312`) and + removal (`Event.cs:513`); `Session` cascades to its speakers, question answers and category items + (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:311-313`) and `Category` to its items - (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/Category.cs:107`). The + (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/Category.cs:109`). The event queue is drained by [`DomainEventSaveChangesInterceptor`](group-07-persistence-ef-core.md#domaineventsavechangesinterceptor), which calls `RemoveDomainEvents` per captured entry @@ -2038,22 +2046,24 @@ in it. dates: a validation problem, not corrupted internal state. - `LengthInDays` (`DateRange.cs:38`): `End.DayNumber - Start.DayNumber`, avoiding `TimeSpan` arithmetic on `DateOnly`. - - `Overlaps(DateRange other)` (`DateRange.cs:46`): `ArgumentNullException.ThrowIfNull(other)` then - the standard half-open formula `Start < other.End && End > other.Start` (`DateRange.cs:48-49`). - - `Contains(DateOnly instant)` (`DateRange.cs:55`): inclusive on both ends, + - `Overlaps(DateRange other)` (`DateRange.cs:47`): `ArgumentNullException.ThrowIfNull(other)` then + the inclusive formula `Start <= other.End && End >= other.Start` (`DateRange.cs:49-50`). Both + ranges are treated as inclusive on both ends, consistent with `Contains`, so two ranges that + share only their boundary day overlap and a single-day range overlaps itself. + - `Contains(DateOnly instant)` (`DateRange.cs:56`): inclusive on both ends, `instant >= Start && instant <= End`. - - `Deconstruct` (`DateRange.cs:61`): enables `var (start, end) = dateRange`. + - `Deconstruct` (`DateRange.cs:62`): enables `var (start, end) = dateRange`. - **Why it's built this way**: `DateOnly` rather than `DateTime` signals that the concept carries no time-of-day and no time zone; wrapping the pair in a type makes swapping `start` and `end` at a call site impossible. - **Where it's used**: no production entity in ADC or Store holds a `DateRange` today; it is a shipped - framework primitive covered by `MMCA.Common.Shared.Tests/ValueObjects/DateRangeTests.cs`. The ADC + framework primitive covered by `MMCA.Common.Shared.Tests/ValueObjects/Time/DateRangeTests.cs`. The ADC Conference `Event` enforces the same rule over two loose `DateOnly` parameters instead, via `EventInvariants.EnsureDateRangeIsValid` - (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:114`, - called from `Event.cs:179` and `Event.cs:252`), with the request-side counterpart + (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:112`, + called from `Event.cs:198` and `Event.cs:274`), with the request-side counterpart `EventDateRangeRules` - (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/Validation/EventValidationRules.cs:93`). + (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/Validation/EventValidationRules.cs:146`). That is the honest state of the code: the primitive exists, the app has not adopted it. ### DateTimeRange @@ -2168,9 +2178,10 @@ in it. which produces the amount column plus ISO-code column mapping together with the currency round-trip fallback every hand-rolled `OwnsOne` block would otherwise have to repeat. - **Where it's used**: the Store Sales `Order` aggregate holds `public Money Total` - (`MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Domain/Orders/Order.cs:37`), seeds it with - `Money.Zero()` (`Order.cs:90`), takes `Money UnitPrice` on its line-item tuple (`Order.cs:105`) and - accumulates with `Money.Add(order.Total, unitPrice * quantity)` (`Order.cs:122`), the exact + (`MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Domain/Orders/Order.cs:58`), seeds it with + `Money.Zero()` (`Order.cs:171`), takes an `OrderLinePricing Pricing` on its line-item tuple + (`Order.cs:194`) and accumulates with `Money.Add(order.Total, pricing.UnitPrice * quantity)` + (`Order.cs:220`), the exact combination of the `None` identity, the `*` operator and the `Result`-safe `Add` described above. The Catalog module carries prices the same way, and the UI formats values through [`MoneyExtensions`](group-15-common-ui-framework.md#moneyextensions). diff --git a/docs-src/onboarding/group-03-querying-specifications.md b/docs-src/onboarding/group-03-querying-specifications.md index 3f6d36a0..a2c99ec6 100644 --- a/docs-src/onboarding/group-03-querying-specifications.md +++ b/docs-src/onboarding/group-03-querying-specifications.md @@ -24,7 +24,7 @@ Two members round the family out for polyglot persistence ([ADR-018](https://iva A plain specification is only a predicate, which leaves includes, ordering, paging, and tracking to be threaded through every layer as loose arguments. [`QuerySpecification`](#queryspecificationtentity-tidentifiertype) (`MMCA.Common/Source/Core/MMCA.Common.Domain/Specifications/QuerySpecification.cs:38`) carries them instead. State is exposed read-only (`OrderBy` at `QuerySpecification.cs:54`, `IncludePaths` at `QuerySpecification.cs:60`, `Skip` and `Take` at `QuerySpecification.cs:63` and `QuerySpecification.cs:66`, `AsTracking` at `QuerySpecification.cs:72`, `IgnoreQueryFilters` at `QuerySpecification.cs:82`) and assembled through protected builder methods a derived specification calls from its constructor: `AddOrderBy` (`QuerySpecification.cs:90`), `AddInclude` (`QuerySpecification.cs:102`, which ignores blanks and duplicates at `QuerySpecification.cs:104-108`), `ApplyPaging` (`QuerySpecification.cs:117`, both values floored at zero at `QuerySpecification.cs:119-120`), `WithTracking` (`QuerySpecification.cs:127`), and `WithSoftDeleted` (`QuerySpecification.cs:133`). Two design notes are worth carrying forward. The base chain stays `QuerySpecification` over `Specification` on purpose, because the fitness rule above keys on that base-type prefix and on a property literally named `Criteria` (`QuerySpecification.cs:29-34`). And `WithSoftDeleted` drops the named `SoftDelete` global query filter and only that one, so a specification asking for deleted rows can never reach another tenant's data (`QuerySpecification.cs:74-81`). Each ordering key is an [`OrderExpression`](#orderexpression) (`QuerySpecification.cs:150`), a record of an untyped `LambdaExpression` plus a descending flag, declared top-level rather than nested inside the generic class because a nested type is a different type per closed generic, which would stop the repository evaluator from handling an ordering list generically (`QuerySpecification.cs:140-144`). -That object is consumed on the persistence side, not by the pipeline in this chapter. The repository's `ListAsync(specification)` (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:260`) takes any `ISpecification`, and [`SpecificationEvaluator`](group-07-persistence-ef-core.md#specificationevaluator) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/SpecificationEvaluator.cs:21`) applies the `Criteria` always (`SpecificationEvaluator.cs:46`) and the rest only when the instance is a `QuerySpecification` (`SpecificationEvaluator.cs:56-66`). Tracking and soft-delete scope are deliberately not applied there: those choose the base queryable, which only the repository can do, in [`EFReadRepository`](group-07-persistence-ef-core.md#efreadrepositorytentity-tidentifiertype)'s `BaseQueryFor` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:560-568`, rationale at `EFReadRepository.cs:554-559`), with the concrete reads at `EFReadRepository.cs:572` (`ListAsync`), `EFReadRepository.cs:585` (the projecting overload, which selects last so a paged specification pages the rows it means to, `EFReadRepository.cs:593-597`), and `EFReadRepository.cs:117` (`FirstOrDefaultAsync`, which keeps the full shape because "first" is only meaningful against a defined order, `EFReadRepository.cs:123-124`). Aggregate reads (count, exists) pass `applyShape: false` so counting does not join in includes or count "page 3 of the matches" (`SpecificationEvaluator.cs:30-35`, the call sites at `EFReadRepository.cs:454` and `EFReadRepository.cs:637`). Includes go through one shared helper that also opts the query into split-query mode whenever any include targets a collection navigation (`SpecificationEvaluator.cs:85`, the decision at `SpecificationEvaluator.cs:101`), so the string-include path and the specification path cannot drift apart (`SpecificationEvaluator.cs:77-80`, the delegation at `EFReadRepository.cs:547-550`). +That object is consumed on the persistence side, not by the pipeline in this chapter. The repository's `ListAsync(specification)` (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:262`) takes any `ISpecification`, and [`SpecificationEvaluator`](group-07-persistence-ef-core.md#specificationevaluator) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/SpecificationEvaluator.cs:21`) applies the `Criteria` always (`SpecificationEvaluator.cs:46`) and the rest only when the instance is a `QuerySpecification` (`SpecificationEvaluator.cs:56-66`). Tracking and soft-delete scope are deliberately not applied there: those choose the base queryable, which only the repository can do, in [`EFReadRepository`](group-07-persistence-ef-core.md#efreadrepositorytentity-tidentifiertype)'s `BaseQueryFor` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:575-583`, rationale at `EFReadRepository.cs:569-574`), with the concrete reads at `EFReadRepository.cs:587` (`ListAsync`), `EFReadRepository.cs:600` (the projecting overload, which selects last so a paged specification pages the rows it means to, `EFReadRepository.cs:608-612`), and `EFReadRepository.cs:118` (`FirstOrDefaultAsync`, which keeps the full shape because "first" is only meaningful against a defined order, `EFReadRepository.cs:124-125`). Aggregate reads (count, exists) pass `applyShape: false` so counting does not join in includes or count "page 3 of the matches" (`SpecificationEvaluator.cs:30-35`, the call sites at `EFReadRepository.cs:455` and `EFReadRepository.cs:652`). Includes go through one shared helper that also opts the query into split-query mode whenever any include targets a collection navigation (`SpecificationEvaluator.cs:85`, the decision at `SpecificationEvaluator.cs:101`), so the string-include path and the specification path cannot drift apart (`SpecificationEvaluator.cs:77-80`, the delegation at `EFReadRepository.cs:562-565`). ## Dynamic filtering, one Strategy per CLR type @@ -34,19 +34,19 @@ The seven built-ins each override `SupportedOperators` with a `FrozenSet`: [`Str Every clause is built through **System.Linq.Dynamic.Core** string predicates with parameter placeholders (`@0`), never string-concatenated values, and every call site passes the one shared [`DynamicQueryConfig.Parameterized`](#dynamicqueryconfig) parsing config (`DynamicQueryConfig.cs:18`, the instance at `DynamicQueryConfig.cs:21-24`). That flag is not cosmetic: Dynamic LINQ defaults `UseParameterizedNamesInDynamicQuery` to `false`, which turns each `@0` into a `ConstantExpression` that EF inlines, so one filter value produces one distinct SQL string, one SQL Server plan-cache entry per value, and an EF compiled-query cache miss on every request (`DynamicQueryConfig.cs:8-16`). With the flag on the value is reached through a member access and EF parameterizes it, and `QueryParameterizationTests` (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Specifications/QueryParameterizationTests.cs:26`) is the regression guard, the only test in the suite that inspects the emitted SQL. This is [Rubric §12, Performance & Scalability] hiding inside a one-property config object. -The static [`QueryFilterService`](#queryfilterservice) (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/Filtering/QueryFilterService.cs:22`) is the registry and dispatcher. It seeds a `ConcurrentDictionary` with the built-ins, registering both the value type and its `Nullable<>` form (`QueryFilterService.cs:32-48`), keeps a dedicated string instance used whenever the resolved value type is `string`, and exposes `RegisterStrategy` so a module can add a custom type without touching framework code (`QueryFilterService.cs:54`, `QueryFilterService.cs:62`, the open/closed principle made literal, [Rubric §1, SOLID]). `ResolveStrategy` (`QueryFilterService.cs:396`) is the three-step dispatch behind both phases: the string instance for `string` (`QueryFilterService.cs:398-399`), an explicitly registered strategy next (`QueryFilterService.cs:401-402`), and the strongly-typed-identifier fallback last, memoized beside the built-ins on first use so the dictionary grows with the number of CLR types the model declares, never with client input (`QueryFilterService.cs:404-406`, rationale at `QueryFilterService.cs:389-394`). Reflection is memoized per (entity type, property name) but **hits only** (`QueryFilterService.cs:30`, `LookupProperty` at `QueryFilterService.cs:343`): the probed names come from the client's query string, so caching misses would let any caller grow a never-evicted static dictionary simply by filtering on names that do not exist, while the request still gets a clean 400 (`QueryFilterService.cs:322-329`). One shared resolver, `ResolvePropertyInfo` (`QueryFilterService.cs:331`), backs both phases so they cannot disagree about what resolves; when they did, a plain rename entry passed validation and was then silently dropped, returning an unfiltered 200 (`QueryFilterService.cs:315-321`). A dotted path like `"Category.Name"` is walked segment by segment to its leaf type by `ResolveFilterValueType` (`QueryFilterService.cs:368`), so the leaf's own strategy validates the operator instead of every nested path defaulting to the string strategy (`QueryFilterService.cs:247-252`). A path whose leaf cannot be reached is failed closed rather than guessed at: validation rejects it as an unknown property (`QueryFilterService.cs:257-265`) and application skips it (`QueryFilterService.cs:128-130`), because letting Dynamic LINQ meet an unexpressible filter turns a bad request into a 500 (`QueryFilterService.cs:361-367`). +The static [`QueryFilterService`](#queryfilterservice) (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/Filtering/QueryFilterService.cs:22`) is the registry and dispatcher. It seeds a `ConcurrentDictionary` with the built-ins, registering both the value type and its `Nullable<>` form (`QueryFilterService.cs:32-48`), keeps a dedicated string instance used whenever the resolved value type is `string`, and exposes `RegisterStrategy` so a module can add a custom type without touching framework code (`QueryFilterService.cs:54`, `QueryFilterService.cs:62`, the open/closed principle made literal, [Rubric §1, SOLID]). `ResolveStrategy` (`QueryFilterService.cs:406`) is the three-step dispatch behind both phases: the string instance for `string` (`QueryFilterService.cs:408-409`), an explicitly registered strategy next (`QueryFilterService.cs:411-412`), and the strongly-typed-identifier fallback last, memoized beside the built-ins on first use so the dictionary grows with the number of CLR types the model declares, never with client input (`QueryFilterService.cs:414-416`, rationale at `QueryFilterService.cs:399-404`). Reflection is memoized per (entity type, property name) but **hits only** (`QueryFilterService.cs:30`, `LookupProperty` at `QueryFilterService.cs:352`): the probed names come from the client's query string, so caching misses would let any caller grow a never-evicted static dictionary simply by filtering on names that do not exist, while the request still gets a clean 400 (`QueryFilterService.cs:331-338`). One shared resolver, `ResolvePropertyInfo` (`QueryFilterService.cs:340`), backs both phases so they cannot disagree about what resolves; when they did, a plain rename entry passed validation and was then silently dropped, returning an unfiltered 200 (`QueryFilterService.cs:324-330`). A dotted path like `"Category.Name"` is walked segment by segment to its leaf type by `ResolveFilterValueType` (`QueryFilterService.cs:378`), so the leaf's own strategy validates the operator instead of every nested path defaulting to the string strategy (`QueryFilterService.cs:256-261`). A path whose leaf cannot be reached is failed closed rather than guessed at: validation rejects it as an unknown property (`QueryFilterService.cs:266-274`) and application skips it (`QueryFilterService.cs:128-130`), because letting Dynamic LINQ meet an unexpressible filter turns a bad request into a 500 (`QueryFilterService.cs:371-377`). -What a client may name at all is decided before any of that, and the allow-list is the **response contract, not the entity**. [`QueryFieldContract`](#queryfieldcontract) (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/QueryFieldContract.cs:34`) is a frozen, case-insensitive set of names: `For()` reflects a DTO's public instance properties once per type and caches them (`QueryFieldContract.cs:56`, `QueryFieldContract.cs:64`, the cache at `QueryFieldContract.cs:44`, the set built at `QueryFieldContract.cs:70-73`), and `ForNames` narrows it to an explicit, server-authored list for a DTO that declares a field the response redacts per role (`QueryFieldContract.cs:83`). `AllowsClientKey` admits a name only when the contract declares it **and** it carries no dot, so a caller can neither address a column no DTO exposes nor walk the object graph (`QueryFieldContract.cs:103-106`), while the static `IsWithinNavigationDepth` caps any resolved path, server-authored ones included, at `MaxNavigationDepth = 3` segments (`QueryFieldContract.cs:42`, `QueryFieldContract.cs:115-137`). The reasoning is recorded on the type (`QueryFieldContract.cs:12-32`): an entity carries columns the response never does (audit fields, credential material, columns a mapper redacts per role), ordering a public list by one of them leaks its total order and a `STARTS WITH` filter over one turns a list endpoint into a character-by-character oracle, and an unbounded dotted path is another `LEFT JOIN` per segment in the emitted SQL. Map entries stay the escape hatch, because the server wrote them. In the filter service that gate is `IsAdmissibleKey` (`QueryFilterService.cs:200`): depth first, then map-authored or contract-declared (`QueryFilterService.cs:206-209`, rationale at `QueryFilterService.cs:186-198`). This is [Rubric §11, Security] in one small type. +What a client may name at all is decided before any of that, and the allow-list is the **response contract, not the entity**. [`QueryFieldContract`](#queryfieldcontract) (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/QueryFieldContract.cs:34`) is a frozen, case-insensitive set of names: `For()` reflects a DTO's public instance properties once per type and caches them (`QueryFieldContract.cs:56`, `QueryFieldContract.cs:64`, the cache at `QueryFieldContract.cs:44`, the set built at `QueryFieldContract.cs:70-73`), and `ForNames` narrows it to an explicit, server-authored list for a DTO that declares a field the response redacts per role (`QueryFieldContract.cs:83`). `AllowsClientKey` admits a name only when the contract declares it **and** it carries no dot, so a caller can neither address a column no DTO exposes nor walk the object graph (`QueryFieldContract.cs:103-106`), while the static `IsWithinNavigationDepth` caps any resolved path, server-authored ones included, at `MaxNavigationDepth = 3` segments (`QueryFieldContract.cs:42`, `QueryFieldContract.cs:115-137`). The reasoning is recorded on the type (`QueryFieldContract.cs:12-32`): an entity carries columns the response never does (audit fields, credential material, columns a mapper redacts per role), ordering a public list by one of them leaks its total order and a `STARTS WITH` filter over one turns a list endpoint into a character-by-character oracle, and an unbounded dotted path is another `LEFT JOIN` per segment in the emitted SQL. Map entries stay the escape hatch, because the server wrote them. In the filter service that gate is `IsAdmissibleKey` (`QueryFilterService.cs:209`): depth first, then map-authored or contract-declared (`QueryFilterService.cs:215-218`, rationale at `QueryFilterService.cs:195-207`). This is [Rubric §11, Security] in one small type. -The two phases and their ordering are the rest of the security story. `ValidateFilters` (the contract-aware overload at `QueryFilterService.cs:166`, with the contract-less signature kept for callers whose filter keys are server-authored at `QueryFilterService.cs:150`) runs before the query and returns a [`Result`](group-01-result-error-handling.md#result) carrying every [`Error`](group-01-result-error-handling.md#error) it found: `Filter.Property.NotFound` three times over, for a key the contract refuses (`QueryFilterService.cs:225-231`), a property the entity does not have (`QueryFilterService.cs:237-242`), and a dotted path whose leaf does not resolve (`QueryFilterService.cs:259-265`); then `Filter.Type.NotSupported` (`QueryFilterService.cs:271-276`), `Filter.Operator.NotSupported` (`QueryFilterService.cs:419-424`), and `Filter.Value.Invalid` (`QueryFilterService.cs:304-308`), the last suppressed when the operator itself was already rejected so one mistake does not produce two errors (`QueryFilterService.cs:297-300`). A bad filter is therefore a validation failure, not a SQL exception and not a silently widened result set. `ApplyFilters` (`QueryFilterService.cs:99`, the contract-less overload at `QueryFilterService.cs:78`) then builds the actual `.Where()` chain, resolving the DTO name through the property map first (`QueryFilterService.cs:111-112`), running the **same** admissibility gate so a caller that skipped validation cannot reach the Dynamic LINQ builder with a refused key (`QueryFilterService.cs:117-118`, the note at `QueryFilterService.cs:114-116`), and skipping any property it cannot resolve (`QueryFilterService.cs:122-123`). Strategy dispatch plus allow-listing untrusted input against the response contract is [Rubric §2, Design Patterns] and [Rubric §11, Security] together. +The two phases and their ordering are the rest of the security story. `ValidateFilters` (the contract-aware overload at `QueryFilterService.cs:175`, with the contract-less signature kept for callers whose filter keys are server-authored at `QueryFilterService.cs:159`) runs before the query and returns a [`Result`](group-01-result-error-handling.md#result) carrying every [`Error`](group-01-result-error-handling.md#error) it found: `Filter.Property.NotFound` three times over, for a key the contract refuses (`QueryFilterService.cs:234-240`), a property the entity does not have (`QueryFilterService.cs:246-251`), and a dotted path whose leaf does not resolve (`QueryFilterService.cs:268-274`); then `Filter.Type.NotSupported` (`QueryFilterService.cs:280-285`), `Filter.Operator.NotSupported` (`QueryFilterService.cs:429-434`), and `Filter.Value.Invalid` (`QueryFilterService.cs:313-317`), the last suppressed when the operator itself was already rejected so one mistake does not produce two errors (`QueryFilterService.cs:306-309`). A bad filter is therefore a validation failure, not a SQL exception and not a silently widened result set. `ApplyFilters` (`QueryFilterService.cs:99`, the contract-less overload at `QueryFilterService.cs:78`) then builds the actual `.Where()` chain, resolving the DTO name through the property map first (`QueryFilterService.cs:111-112`), running the **same** admissibility gate so a caller that skipped validation cannot reach the Dynamic LINQ builder with a refused key (`QueryFilterService.cs:117-118`, the note at `QueryFilterService.cs:114-116`), and skipping any property it cannot resolve (`QueryFilterService.cs:122-123`). Strategy dispatch plus allow-listing untrusted input against the response contract is [Rubric §2, Design Patterns] and [Rubric §11, Security] together. ## Sorting, sparse fieldsets, and paging arithmetic -[`QueryFieldService`](#queryfieldservice) (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/QueryFieldService.cs:16`) owns the rest of read shaping. `ApplySorting` (`QueryFieldService.cs:183`, with a contract-less overload that passes `fieldContract: null` for server-authored callers at `QueryFieldService.cs:155-162`) resolves a DTO sort name through the server-authored map, and for a name the map does not cover it consults the response contract **before** the entity is reflected over at all: a column the DTO does not declare resolves to nothing (`ResolveSortExpression` at `QueryFieldService.cs:226`, the contract gate at `QueryFieldService.cs:239-242`, the entity lookup that only runs past it at `QueryFieldService.cs:245-247`), and the query falls back to the optional default sort instead (`QueryFieldService.cs:201-207`). Whatever the source, the resolved path is refused past `QueryFieldContract.MaxNavigationDepth` (`QueryFieldService.cs:250`). That guard is deliberate and documented on the overload (`QueryFieldService.cs:219-224`): a client-supplied string can never reach Dynamic LINQ to order by nested paths, nor order a public page by a column the response never carries. The same contract gates `fields`: `ValidateSingleField` checks the server-authored map first, capping its depth, then the response contract, and only then the entity's own property set (`QueryFieldService.cs:462`, the map branch at `QueryFieldService.cs:471-483`, the contract branch at `QueryFieldService.cs:485-498`), reached through a third `Validate` overload that takes the contract (`QueryFieldService.cs:423`). Map entries, being server-authored, may be expressions: ADC sorts speakers by `FullName` through an entry that concatenates first and last name (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Speakers/SpeakerEntityQueryService.cs:30`, wired in by overriding the map at `SpeakerEntityQueryService.cs:34`). The method also takes an optional `tieBreakProperty` appended as a final ascending key (`QueryFieldService.cs:190`, applied by `BuildOrdering` at `QueryFieldService.cs:258`, and used alone when no valid sort column was given at `QueryFieldService.cs:209-211`). It exists because `Skip`/`Take` over a non-total `ORDER BY` is undefined: rows sharing a sort value can come back in a different order per statement, so the same row appears on two consecutive pages while another appears on neither, from data that never changed (`QueryFieldService.cs:139-153`). The append is repeated-key aware, skipped when the caller already sorted by that very column (`QueryFieldService.cs:263-266`). +[`QueryFieldService`](#queryfieldservice) (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/QueryFieldService.cs:16`) owns the rest of read shaping. `ApplySorting` (`QueryFieldService.cs:184`, with a contract-less overload that passes `fieldContract: null` for server-authored callers at `QueryFieldService.cs:155-162`) resolves a DTO sort name through the server-authored map, and for a name the map does not cover it consults the response contract **before** the entity is reflected over at all: a column the DTO does not declare resolves to nothing (`ResolveSortExpression` at `QueryFieldService.cs:227`, the contract gate at `QueryFieldService.cs:240-243`, the entity lookup that only runs past it at `QueryFieldService.cs:246-248`), and the query falls back to the optional default sort instead (`QueryFieldService.cs:202-208`). Whatever the source, the resolved path is refused past `QueryFieldContract.MaxNavigationDepth` (`QueryFieldService.cs:251`). That guard is deliberate and documented on the overload (`QueryFieldService.cs:220-225`): a client-supplied string can never reach Dynamic LINQ to order by nested paths, nor order a public page by a column the response never carries. The same contract gates `fields`: `ValidateSingleField` checks the server-authored map first, capping its depth, then the response contract, and only then the entity's own property set (`QueryFieldService.cs:465`, the map branch at `QueryFieldService.cs:474-486`, the contract branch at `QueryFieldService.cs:488-501`), reached through a third `Validate` overload that takes the contract (`QueryFieldService.cs:426`). Map entries, being server-authored, may be expressions: ADC sorts speakers by `FullName` through an entry that concatenates first and last name (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Speakers/SpeakerEntityQueryService.cs:30`, wired in by overriding the map at `SpeakerEntityQueryService.cs:34`). The method also takes an optional `tieBreakProperty` appended as a final ascending key (`QueryFieldService.cs:191`, applied by `BuildOrdering` at `QueryFieldService.cs:259`, and used alone when no valid sort column was given at `QueryFieldService.cs:210-212`). It exists because `Skip`/`Take` over a non-total `ORDER BY` is undefined: rows sharing a sort value can come back in a different order per statement, so the same row appears on two consecutive pages while another appears on neither, from data that never changed (`QueryFieldService.cs:139-153`). The append is repeated-key aware, skipped when the caller already sorted by that very column (`QueryFieldService.cs:264-267`). -`ApplyFieldSelection` (`QueryFieldService.cs:278`) builds a `MemberInit` `Select` expression so a `fields=name,bio` request pulls only those columns from the database ([Rubric §12, Performance & Scalability]), restricted to writable properties because the projection needs setters (`QueryFieldService.cs:331-340`, the `CanWrite` filter at `QueryFieldService.cs:336`). The compiled lambda is cached per (entity type, normalized field set) (`QueryFieldService.cs:286`, cache at `QueryFieldService.cs:329`), and a `null` is cached on purpose to record "this field set projects nothing writable" so the miss is not recomputed per request (`QueryFieldService.cs:318-320`). `ShapeData` and `ShapeCollectionData` (`QueryFieldService.cs:75`, `QueryFieldService.cs:96`) produce the wire shape: an `ExpandoObject` (or a list of them) holding only the requested fields under camelCase keys. To make that cheap on large result sets the service caches a per-type array of [`PropertyAccessor`](#propertyaccessor) (`QueryFieldService.cs:42`), a private `readonly record struct` bundling each property's name, its precomputed camelCase key, and a compiled `Func` getter built with `Expression.Lambda(...).Compile()` rather than `PropertyInfo.GetValue` (`QueryFieldService.cs:46`, built at `QueryFieldService.cs:48-65`); the field-filtered subset is cached again per field set (`QueryFieldService.cs:579`, `QueryFieldService.cs:585`), under an order- and case-insensitive key so `name,id` and `Id, Name` share one entry (`QueryFieldService.cs:616-617`). +`ApplyFieldSelection` (`QueryFieldService.cs:279`) builds a `MemberInit` `Select` expression so a `fields=name,bio` request pulls only those columns from the database ([Rubric §12, Performance & Scalability]), restricted to writable properties because the projection needs setters (`QueryFieldService.cs:332-341`, the `CanWrite` filter at `QueryFieldService.cs:337`). The compiled lambda is cached per (entity type, normalized field set) (`QueryFieldService.cs:287`, cache at `QueryFieldService.cs:330`), and a `null` is cached on purpose to record "this field set projects nothing writable" so the miss is not recomputed per request (`QueryFieldService.cs:319-321`). `ShapeData` and `ShapeCollectionData` (`QueryFieldService.cs:75`, `QueryFieldService.cs:96`) produce the wire shape: an `ExpandoObject` (or a list of them) holding only the requested fields under camelCase keys. To make that cheap on large result sets the service caches a per-type array of [`PropertyAccessor`](#propertyaccessor) (`QueryFieldService.cs:42`), a private `readonly record struct` bundling each property's name, its precomputed camelCase key, and a compiled `Func` getter built with `Expression.Lambda(...).Compile()` rather than `PropertyInfo.GetValue` (`QueryFieldService.cs:46`, built at `QueryFieldService.cs:48-65`); the field-filtered subset is cached again per field set (`QueryFieldService.cs:582`, `QueryFieldService.cs:588`), under an order- and case-insensitive key so `name,id` and `Id, Name` share one entry (`QueryFieldService.cs:619-620`). -Both field-set caches are bounded, and the reason is the same one that shapes the filter cache. Their key is half client-supplied, so an entity with N properties admits up to 2^N distinct subsets and a caller could grow either dictionary by permuting the list (`QueryFieldService.cs:18-38`). The cap is a `const int MaxCacheEntries = 512` per cache (`QueryFieldService.cs:39`), with deliberately no LRU: past the cap `ApplyFieldSelection` skips server-side projection rather than admitting another key (`QueryFieldService.cs:297-298`, and the response is unchanged because shaping still trims the payload), and `GetShapedAccessors` filters per request instead (`QueryFieldService.cs:603-604`), which is a scan over an already-compiled accessor array rather than an expression rebuild. Validation mirrors the filter side: `Validate` rejects unknown field names and (when shaping) read-only properties (`QueryFieldService.cs:366` and the map-aware overload at `QueryFieldService.cs:401`, shared body at `QueryFieldService.cs:434`), and `ValidateSortDirection` accepts only `asc` or `desc` (`QueryFieldService.cs:529`). Paging arithmetic is small enough to look trivial and is not, which is why it has its own type: [`PagingMath.Clamp`](#pagingmath) (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/PagingMath.cs:32`) clamps the page size into `[1, maxPageSize]` and the page number to at least 1 (`PagingMath.cs:37-38`), computes the offset in 64-bit (`PagingMath.cs:40`), and returns `(0, 0)` for a page beyond the reachable offset range, materializing the empty page that page genuinely holds (`PagingMath.cs:42`). A 32-bit `(pageNumber - 1) * pageSize` overflows and wraps negative near `int.MaxValue`, and SQL Server rejects a negative `OFFSET` outright, so the request surfaced as a 500 instead of an empty page (`PagingMath.cs:8-12`). Every paginating caller routes through here rather than open-coding the multiply, because the arithmetic previously lived only inside the pipeline and the handlers that paginate their own queryable each re-derived it in 32-bit (`PagingMath.cs:14-17`). +Both field-set caches are bounded, and the reason is the same one that shapes the filter cache. Their key is half client-supplied, so an entity with N properties admits up to 2^N distinct subsets and a caller could grow either dictionary by permuting the list (`QueryFieldService.cs:18-38`). The cap is a `const int MaxCacheEntries = 512` per cache (`QueryFieldService.cs:39`), with deliberately no LRU: past the cap `ApplyFieldSelection` skips server-side projection rather than admitting another key (`QueryFieldService.cs:298-299`, and the response is unchanged because shaping still trims the payload), and `GetShapedAccessors` filters per request instead (`QueryFieldService.cs:606-607`), which is a scan over an already-compiled accessor array rather than an expression rebuild. Validation mirrors the filter side: `Validate` rejects unknown field names and (when shaping) read-only properties (`QueryFieldService.cs:368` and the map-aware overload at `QueryFieldService.cs:404`, shared body at `QueryFieldService.cs:437`), and `ValidateSortDirection` accepts only `asc` or `desc` (`QueryFieldService.cs:532`). Paging arithmetic is small enough to look trivial and is not, which is why it has its own type: [`PagingMath.Clamp`](#pagingmath) (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/PagingMath.cs:32`) clamps the page size into `[1, maxPageSize]` and the page number to at least 1 (`PagingMath.cs:37-38`), computes the offset in 64-bit (`PagingMath.cs:40`), and returns `(0, 0)` for a page beyond the reachable offset range, materializing the empty page that page genuinely holds (`PagingMath.cs:42`). A 32-bit `(pageNumber - 1) * pageSize` overflows and wraps negative near `int.MaxValue`, and SQL Server rejects a negative `OFFSET` outright, so the request surfaced as a 500 instead of an empty page (`PagingMath.cs:8-12`). Every paginating caller routes through here rather than open-coding the multiply, because the arithmetic previously lived only inside the pipeline and the handlers that paginate their own queryable each re-derived it in 32-bit (`PagingMath.cs:14-17`). ## The pipeline: two entity paths plus projection pushdown @@ -64,17 +64,17 @@ All three paths share one [Rubric §12, Performance & Scalability] safety ceilin `GetAllAsync` (`EntityQueryService.cs:283`, with a six-parameter convenience overload at `EntityQueryService.cs:262`) is the four-step orchestration. **(1) Validate** every parameter up front with `Result.Combine` over the fields, sort-column, sort-direction, and filter validators, so a bad `fields` fails before any database hit (`EntityQueryService.cs:297-302`), re-stamping each error with the operation and entity name (`EntityQueryService.cs:305-311`). **(2) Build the query**: ask the metadata provider which includes are supported (`EntityQueryService.cs:317`), pack everything into `EntityQueryParameters` (`EntityQueryService.cs:319-332`), and pick `Repository.Table` or `TableNoTracking` from the `asTracking` flag (`EntityQueryService.cs:334`). **(3) Execute** on one of two branches, chosen by `CanProject` (`EntityQueryService.cs:541-544`): a registered projector, no tracking request, and no cross-source includes routes to `ExecuteProjectedAsync` and the mapper is never involved (`EntityQueryService.cs:339-349`); otherwise `ExecuteAsync` runs and `DTOMapper.MapToDTOs` converts the materialized entities (`EntityQueryService.cs:352-360`). Field shaping deliberately does not disqualify projection, because shaping runs after materialization over whatever object the pipeline produced (`EntityQueryService.cs:536-539`). **(4) Shape and wrap**: shape **only when a field subset was requested**, otherwise return the typed DTOs as-is to avoid a per-row `ExpandoObject` allocation and boxing (`EntityQueryService.cs:370-372`); both forms serialize to the same camelCase JSON, which is why the return type is `PagedCollectionResult` rather than a typed collection ([`PagedCollectionResult`](group-01-result-error-handling.md#pagedcollectionresultt), and the contract note at `IEntityQueryService.cs:12-14`). The [`PaginationMetadata`](group-01-result-error-handling.md#paginationmetadata) comes from `BuildPaginationMetadata` (`EntityQueryService.cs:368`, method at `EntityQueryService.cs:608`), whose job is to describe what the pipeline actually did rather than what the caller asked for: an unpaginated call reports the true total with the page size floored to the rows actually returnable, `Math.Min(total, MaxUnboundedResultLimit)`, on page 1 (`EntityQueryService.cs:622-625`), and a paginated call reports `Math.Clamp(pageSize, 1, MaxUnboundedResultLimit)` on `Math.Max(pageNumber, 1)` (`EntityQueryService.cs:632-635`), mirroring exactly the floor and ceiling `PagingMath` applied. The clamp is recomputed here rather than read back from `PagingMath`, because that helper's `(0, 0)` sentinel for an unreachable page would otherwise advertise `PageSize = 0` for a perfectly valid page size (`EntityQueryService.cs:601-606`). -The by-id path has a fast lane worth knowing. `GetEntityByIdAsync` (`EntityQueryService.cs:428`) validates the fields (`EntityQueryService.cs:438`), then tries `TryGetByIdFastPathAsync` (`EntityQueryService.cs:154`), which issues a single keyed `TOP 1 WHERE Id = @id` through the repository's include overload (`EntityQueryService.cs:170`). `TryGetFastPathIncludes` (`EntityQueryService.cs:196`) decides eligibility: a field projection, a specification, or a non-default `idField` disqualifies the request (`EntityQueryService.cs:206-211`), and so do unsupported (cross-source) navigations, since only the pipeline's populator can batch-load those (`EntityQueryService.cs:219-222`, rationale at `EntityQueryService.cs:190-194`). Requested includes do **not** disqualify it: the repository's include overload applies the same `Include` calls and auto-applies `AsSplitQuery` for a child collection (`EFReadRepository.cs:417-430`, delegating the split decision to `SpecificationEvaluator.cs:101`), and disqualifying on includes left the fast path unreachable for every entity that declares a navigation, because the REST by-id action defaults `includeFKs` to true (`EntityQueryService.cs:182-188`). The string id is converted with a `TypeConverter` cached per identifier type (`EntityQueryService.cs:233`, cache at `EntityQueryService.cs:142`), and the read runs on the filtered `TableNoTracking` (`EFReadRepository.cs:387`), so soft-delete query filters still apply, unlike `FindAsync` (`EntityQueryService.cs:148-152`, and the same trap documented at `EFReadRepository.cs:408-413`). Anything else falls through to the pipeline with a synthetic `Id EQUALS ` filter and returns `Error.NotFound` when the page comes back empty. `GetByIdAsync` (`EntityQueryService.cs:489`) layers DTO mapping and the same shape-only-if-fields rule on top; `GetAllForLookupAsync` (`EntityQueryService.cs:384`) returns lightweight [`BaseLookup`](group-12-api-hosting-mapping.md#baselookuptidentifiertype) id/name pairs for dropdowns; `ExistsAsync` (`EntityQueryService.cs:519`) delegates straight to the repository. The class is built for extension over modification ([Rubric §1, SOLID]): `Repository` (`EntityQueryService.cs:88`), `DTOToEntityPropertyMap` (`EntityQueryService.cs:101`), and every query method are `virtual`, so a module subclass such as [`SpeakerEntityQueryService`](group-18-conference-application.md#speakerentityqueryservice) (`SpeakerEntityQueryService.cs:15`) overrides one behavior (`SpeakerEntityQueryService.cs:34`) without reimplementing the engine. +The by-id path has a fast lane worth knowing. `GetEntityByIdAsync` (`EntityQueryService.cs:428`) validates the fields (`EntityQueryService.cs:438`), then tries `TryGetByIdFastPathAsync` (`EntityQueryService.cs:154`), which issues a single keyed `TOP 1 WHERE Id = @id` through the repository's include overload (`EntityQueryService.cs:170`). `TryGetFastPathIncludes` (`EntityQueryService.cs:196`) decides eligibility: a field projection, a specification, or a non-default `idField` disqualifies the request (`EntityQueryService.cs:206-211`), and so do unsupported (cross-source) navigations, since only the pipeline's populator can batch-load those (`EntityQueryService.cs:219-222`, rationale at `EntityQueryService.cs:190-194`). Requested includes do **not** disqualify it: the repository's include overload applies the same `Include` calls and auto-applies `AsSplitQuery` for a child collection (`EFReadRepository.cs:418-431`, delegating the split decision to `SpecificationEvaluator.cs:101`), and disqualifying on includes left the fast path unreachable for every entity that declares a navigation, because the REST by-id action defaults `includeFKs` to true (`EntityQueryService.cs:182-188`). The string id is converted with a `TypeConverter` cached per identifier type (`EntityQueryService.cs:233`, cache at `EntityQueryService.cs:142`), and the read runs on the filtered `TableNoTracking` (`EFReadRepository.cs:388`), so soft-delete query filters still apply, unlike `FindAsync` (`EntityQueryService.cs:148-152`, and the same trap documented at `EFReadRepository.cs:409-414`). Anything else falls through to the pipeline with a synthetic `Id EQUALS ` filter and returns `Error.NotFound` when the page comes back empty. `GetByIdAsync` (`EntityQueryService.cs:489`) layers DTO mapping and the same shape-only-if-fields rule on top; `GetAllForLookupAsync` (`EntityQueryService.cs:384`) returns lightweight [`BaseLookup`](group-12-api-hosting-mapping.md#baselookuptidentifiertype) id/name pairs for dropdowns; `ExistsAsync` (`EntityQueryService.cs:519`) delegates straight to the repository. The class is built for extension over modification ([Rubric §1, SOLID]): `Repository` (`EntityQueryService.cs:88`), `DTOToEntityPropertyMap` (`EntityQueryService.cs:101`), and every query method are `virtual`, so a module subclass such as [`SpeakerEntityQueryService`](group-18-conference-application.md#speakerentityqueryservice) (`SpeakerEntityQueryService.cs:15`) overrides one behavior (`SpeakerEntityQueryService.cs:34`) without reimplementing the engine. ## End to end, one list request -The request reaches a read controller, [`EntityControllerBase`](group-12-api-hosting-mapping.md#entitycontrollerbasetentity-tentitydto-tidentifiertype) (Group 12), which resolves `MaxPageSize` per request from [`ApplicationSettings`](group-14-module-system-composition.md#applicationsettings), falling back to 500 when unset (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:56-61`), clamps the requested page size to it (`EntityControllerBase.cs:166`), binds `?filter=` through `QueryFilterModelBinder` (`EntityControllerBase.cs:163`), and asks its own `GetReadSpecificationAsync` hook for a server-authored authorization scope (`EntityControllerBase.cs:168`). It calls `IEntityQueryService.GetAllAsync` (`EntityControllerBase.cs:170`). The service validates fields, sort, and filters (an early failure short-circuits to an error result), classifies the requested includes, and packages an `EntityQueryParameters`. `EntityQueryPipeline` then takes the projection path when a projector is registered and the read qualifies, or one of the two entity paths otherwise, applying the specification criteria plus the dynamic filters as translated, parameterized `WHERE` clauses, sorting with the key tie-break when paginating, counting, paging through `PagingMath`, projecting the requested columns, materializing, and batch-loading any cross-source navigations. The service maps to DTOs (or skips mapping entirely on the projected path), shapes only if a field subset was asked for, and returns a `Result>` that the controller unwraps into the HTTP body plus an `X-Pagination` header carrying the serialized metadata (`EntityControllerBase.cs:185`). One pipeline, every entity, validated input, server-side execution, and a clean extension point for navigations that cross a service boundary ([Rubric §6, CQRS & Event-Driven] on the read side, [Rubric §9, API & Contract Design] for the uniform query contract). +The request reaches a read controller, [`EntityControllerBase`](group-12-api-hosting-mapping.md#entitycontrollerbasetentity-tentitydto-tidentifiertype) (Group 12), which resolves `MaxPageSize` per request from [`ApplicationSettings`](group-14-module-system-composition.md#applicationsettings), falling back to 500 when unset (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:56-61`), clamps the requested page size to it (`EntityControllerBase.cs:169`), binds `?filter=` through `QueryFilterModelBinder` (`EntityControllerBase.cs:166`), and asks its own `GetReadSpecificationAsync` hook for a server-authored authorization scope (`EntityControllerBase.cs:171`). It calls `IEntityQueryService.GetAllAsync` (`EntityControllerBase.cs:173`). The service validates fields, sort, and filters (an early failure short-circuits to an error result), classifies the requested includes, and packages an `EntityQueryParameters`. `EntityQueryPipeline` then takes the projection path when a projector is registered and the read qualifies, or one of the two entity paths otherwise, applying the specification criteria plus the dynamic filters as translated, parameterized `WHERE` clauses, sorting with the key tie-break when paginating, counting, paging through `PagingMath`, projecting the requested columns, materializing, and batch-loading any cross-source navigations. The service maps to DTOs (or skips mapping entirely on the projected path), shapes only if a field subset was asked for, and returns a `Result>` that the controller unwraps into the HTTP body plus an `X-Pagination` header carrying the serialized metadata (`EntityControllerBase.cs:188`). One pipeline, every entity, validated input, server-side execution, and a clean extension point for navigations that cross a service boundary ([Rubric §6, CQRS & Event-Driven] on the read side, [Rubric §9, API & Contract Design] for the uniform query contract). ## Query tags, naming the use case behind a statement Two types in the `Services/Query` folder serve the operator reading a query store rather than the read path itself. [`QueryTagScope`](#querytagscope) (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/QueryTagScope.cs:20`) is a static holder over an `AsyncLocal` (`QueryTagScope.cs:22`). `Begin(handlerName)` publishes the name of the CQRS use case the current flow is executing and returns a handle that restores the previous value on dispose, so a nested scope (an internal command executed inside another handler) unwinds correctly (`QueryTagScope.cs:40-53`); a `null`, empty, or whitespace name leaves the ambient value untouched, so no call site needs a guard (`QueryTagScope.cs:44-47`); and `Current` reads the innermost open scope, or `null` when none is open, which is the normal state for a background service, a seeder, or a directly constructed repository in a test (`QueryTagScope.cs:28`). The handle is the private [`AmbientScope`](#ambientscope) (`QueryTagScope.cs:61`), a one-line `IDisposable` that assigns the captured previous value back (`QueryTagScope.cs:63`). The outermost scope of a request, whose previous value is `null`, is served by a single shared static instance, so opening a scope on the hot path allocates nothing at all and only a genuinely nested scope pays for an object (`QueryTagScope.cs:49-52`, the shared handle at `QueryTagScope.cs:59`). -The two ends are the decorator pipeline and the EF repositories. The logging decorators open the scope around each handler (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/LoggingQueryDecorator.cs:30` and `LoggingCommandDecorator.cs:31`), and `QueryTags` composes the ambient name with the query builder's own detail into the string the repository passes to EF Core's `TagWith` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/QueryTags.cs:42`, used on the specification path at `SpecificationEvaluator.cs:38` and the include path at `EFReadRepository.cs:524`), turning an anonymous statement in a DBA's query store into a SQL comment naming the handler and the specification that asked for it. The value flows with the `ExecutionContext`, which is what lets a repository several awaits below the decorator read it without every signature in between growing a parameter, and nothing branches on it: a missing scope costs a less specific comment, never a behavior change (`QueryTagScope.cs:6-18`). It lives in the Application layer rather than Infrastructure because both ends need it and Application is the highest layer Infrastructure is allowed to see (`QueryTagScope.cs:15-17`). This is [Rubric §13, Observability & Operability] bought for one `AsyncLocal` and one `using`. +The two ends are the decorator pipeline and the EF repositories. The logging decorators open the scope around each handler (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/LoggingQueryDecorator.cs:30` and `LoggingCommandDecorator.cs:31`), and `QueryTags` composes the ambient name with the query builder's own detail into the string the repository passes to EF Core's `TagWith` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/QueryTags.cs:42`, used on the specification path at `SpecificationEvaluator.cs:38` and the include path at `EFReadRepository.cs:539`), turning an anonymous statement in a DBA's query store into a SQL comment naming the handler and the specification that asked for it. The value flows with the `ExecutionContext`, which is what lets a repository several awaits below the decorator read it without every signature in between growing a parameter, and nothing branches on it: a missing scope costs a less specific comment, never a behavior change (`QueryTagScope.cs:6-18`). It lives in the Application layer rather than Infrastructure because both ends need it and Application is the highest layer Infrastructure is allowed to see (`QueryTagScope.cs:15-17`). This is [Rubric §13, Observability & Operability] bought for one `AsyncLocal` and one `using`. ## Also filed here: best-effort dispatch and the upcaster registry @@ -403,18 +403,18 @@ The fourth co-located type is [`EventUpcasterRegistry`](#eventupcasterregistry) `GetAccessors` (`:48-65`), which compiles one `Expression.Lambda>` per property (`:56-60`) and pre-computes the camelCase name (`:61`). - - `ProjectionCache` (`(Type, string Fields) -> LambdaExpression?`, `:280`) so the `MemberInit` - tree is built once per (entity, field set) instead of per request. Its remarks (`:262-279`) + - `ProjectionCache` (`(Type, string Fields) -> LambdaExpression?`, `:330`) so the `MemberInit` + tree is built once per (entity, field set) instead of per request. Its remarks (`:315-329`) explain that a `null` value is cached **deliberately**: it records "this field set projects nothing writable", so the miss is not recomputed on every subsequent request. - - `ShapedAccessorCache` (`(Type, string Fields) -> PropertyAccessor[]`, `:465`) so a repeated + - `ShapedAccessorCache` (`(Type, string Fields) -> PropertyAccessor[]`, `:582`) so a repeated `fields=` request reuses the filtered accessor array instead of re-filtering per call. - The keys of the last two are normalized by `NormalizeFieldsKey` (`:502-503`), which uppercases and + The keys of the last two are normalized by `NormalizeFieldsKey` (`:619-620`), which uppercases and sorts the field names so `"name,id"` and `"Id, Name"` share one entry rather than multiplying the cache by however many spellings callers happen to send. The hot-path `GetOrAdd` calls pass `static` - lambdas and thread state through the overload's extra argument (`:49`, `:51`, `:251-254`, - `:492-495`), so no closure is allocated per call. + lambdas and thread state through the overload's extra argument (`:49`, `:51`, `:301-304`, + `:609-612`), so no closure is allocated per call. - **Concept introduced, the client-keyed cache cap.** `[Rubric §11, Security]` (assesses whether a caller can grow process-lifetime state at will) and `[Rubric §12]` both apply to `private const int MaxCacheEntries = 512` (`:39`). The two field-set caches are keyed partly by the @@ -428,53 +428,54 @@ The fourth co-located type is [`EventUpcasterRegistry`](#eventupcasterregistry) - `ShapeData(entity, fields)` (`:75-87`) and `ShapeCollectionData(entities, fields)` (`:96-117`): resolve accessors through `GetShapedAccessors` (`:77`, `:100`), then fill an `ExpandoObject` keyed by `CamelCaseName` - (`:83`, `:110`). An empty field list means all properties (`GetShapedAccessors`, `:476-477`). - - `ApplySorting` has two overloads (`:155-162`, `:183-212`). The five-argument overload is - a thin wrapper that calls the six-argument one with `fieldContract: null` (`:162`), which resolves - the sort column through `ResolveSortExpression` (`:192`), then emits - `query.OrderBy(Filtering.DynamicQueryConfig.Parameterized, BuildOrdering(...))` (`:196-198`). When - no valid sort column survives it falls back to the optional `defaultSort` lambda (`:201-206`) and, - failing that, to the tie-break key alone (`:209-211`). - - `ResolveSortExpression` (`:226-251`) is the security-relevant half, hardened by a + (`:83`, `:110`). An empty field list means all properties (`GetShapedAccessors`, `:593-594`). + - `ApplySorting` has two overloads (`:155-162`, `:184-213`). The six-parameter overload is + a thin wrapper that calls the seven-parameter one with `fieldContract: null` (`:162`), which resolves + the sort column through `ResolveSortExpression` (`:193`), then emits + `query.OrderBy(Filtering.DynamicQueryConfig.Parameterized, BuildOrdering(...))` (`:197-199`). When + no valid sort column survives it falls back to the optional `defaultSort` lambda (`:202-208`) and, + failing that, to the tie-break key alone (`:210-212`). + - `ResolveSortExpression` (`:227-252`) is the security-relevant half, hardened by a [`QueryFieldContract`](#queryfieldcontract) parameter (SEC-ADC-09). A server-authored map entry - still wins outright (`:235-238`); an unmapped name is refused immediately when a `fieldContract` - was supplied and does not declare it (`:239-242`, `AllowsClientKey`), and otherwise (no contract, + still wins outright (`:236-239`); an unmapped name is refused immediately when a `fieldContract` + was supplied and does not declare it (`:240-243`, `AllowsClientKey`), and otherwise (no contract, the pre-hardening path) is accepted only when reflection finds a real public property of the - entity, matched with `BindingFlags.IgnoreCase` (`:244-248`). Either way the resolved path is then - refused past `QueryFieldContract.MaxNavigationDepth` segments (`:250`, SEC-Store-13). Anything + entity, matched with `BindingFlags.IgnoreCase` (`:244-249`). Either way the resolved path is then + refused past `QueryFieldContract.MaxNavigationDepth` segments (`:251`, SEC-Store-13). Anything refused returns `null` and never reaches Dynamic LINQ. - - `BuildOrdering` (`:258-267`) turns the resolved expression plus `"asc"`/`"desc"` into the Dynamic + - `BuildOrdering` (`:259-268`) turns the resolved expression plus `"asc"`/`"desc"` into the Dynamic LINQ clause and appends `", {tieBreakProperty} ascending"` unless the caller already sorted by that very column, because repeating a key in an `ORDER BY` is redundant and some providers reject it outright. - - `ApplyFieldSelection` (`:278-309`): returns the query untouched for an empty field list, + - `ApplyFieldSelection` (`:279-310`): returns the query untouched for an empty field list, otherwise pulls the compiled projection out of `ProjectionCache` on the lock-free `TryGetValue` hit path, skips projection entirely once the cache is at its cap, - and applies the lambda as `query.Select(...)`. `BuildProjection` (`:331-353`) + and applies the lambda as `query.Select(...)`. `BuildProjection` (`:332-354`) is the builder: it keeps only **writable** properties that match the field set (`p.CanWrite`), since EF cannot translate a `MemberInit` that assigns a read-only member, returns `null` when nothing survives, and otherwise builds `new TEntity { Prop = e.Prop, ... }` so the projection is pushed into the SQL `SELECT`. - Three `Validate` overloads, each thinner than the next: `Validate(fields, - allowWriteableFields)` (`:366-367`), the map-aware `Validate(fields, - dtoToEntityPropertyMap, allowWriteableFields = false)` (`:401-405`, calls the shared body with + allowWriteableFields)` (`:368-369`), the map-aware `Validate(fields, + dtoToEntityPropertyMap, allowWriteableFields = false)` (`:404-408`, calls the shared body with `fieldContract: null`), and the fully hardened `Validate(fields, dtoToEntityPropertyMap, - allowWriteableFields, fieldContract)` (`:423-428`, SEC-ADC-09). All three delegate to the shared - `ValidateFields` (`:434-456`), which loops the requested field set through - `ValidateSingleField` (`:462-522`) per name. `ValidateSingleField` checks, in order: a - **map entry wins unconditionally** and is never reflected over (`:471-483`), except that a mapped + allowWriteableFields, fieldContract)` (`:426-431`, SEC-ADC-09). All three delegate to the shared + `ValidateFields` (`:437-459`), which loops the requested field set through + `ValidateSingleField` (`:465-525`) per name. `ValidateSingleField` checks, in order: a + **map entry wins unconditionally** and is never reflected over (`:474-486`), except that a mapped path deeper than `QueryFieldContract.MaxNavigationDepth` is now rejected too - (`:473-480`, SEC-Store-13); an unmapped name is refused when a `fieldContract` was supplied and - does not declare it (`:489-498`, the RESPONSE contract, not the entity, decides what a client may + (`:476-483`, SEC-Store-13); an unmapped name is refused when a `fieldContract` was supplied and + does not declare it (`:492-501`, the RESPONSE contract, not the entity, decides what a client may name); and only then does it fall through to the entity-reflection check, existence - (`:500-512`) plus, when `allowWriteableFields` is false, not read-only (`:514-521`). All + (`:503-515`) plus, when `allowWriteableFields` is false, not read-only (`:517-524`). All offenders accumulate into one aggregate [`Result`](group-01-result-error-handling.md#result). - - `ValidateSortDirection` (`:529-548`): accepts only `"asc"`, `"desc"`, or null/empty, and returns + - `ValidateSortDirection` (`:532-551`): accepts only `"asc"`, `"desc"`, or null/empty, and returns an `Error.Validation("Error.InvalidSortDirection", ...)` failure otherwise. - - Private helpers: `ParseFields` (`:550-554`) splits the comma list into a case-insensitive - `HashSet`; `GetProperties` (`:556-559`) reads through `PropertiesCache`; - `FilterAccessorsByFields` (`:561-567`) narrows an accessor array; `GetShapedAccessors` - (`:585-599`+) is the cached front door to it. + - Private helpers: `ParseFields` (`:553-557`) splits the comma list into a case-insensitive + `HashSet`; `GetProperties` (`:559-562`) reads through `PropertiesCache`; + `FilterAccessorsByFields` (`:564-570`) narrows an accessor array; `GetShapedAccessors` + (`:588-613`) is the cached front door to it, filtering per request instead of caching once + `ShapedAccessorCache` is at its cap (`:606-607`). - **Concept introduced, gating client-named sort/filter/lookup columns on the response contract, not the entity.** `[Rubric §11, Security]`: `QueryFieldContract` (SEC-ADC-09, SEC-Common-24, SEC-Common-25, SEC-Store-13) is the allow-list a client-supplied name must clear before an unmapped @@ -502,10 +503,15 @@ The fourth co-located type is [`EventUpcasterRegistry`](#eventupcasterregistry) `ShapeCollectionData` and `ShapeData` are called after mapping (`EntityQueryService.cs:372`, `:515`); `ApplySorting` and `ApplyFieldSelection` are called inside [`EntityQueryPipeline`](#entityquerypipeline) - (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/EntityQueryPipeline.cs:81`, `:175`, - `:208`, `:227`, `:251`). + (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/EntityQueryPipeline.cs:81`, `:176`, + `:210`, `:229`, `:254`). - **Caveats / not-in-source**: the class is `sealed` but every member is `static`, so it is never - instantiated or injected; treat it as a static utility despite the shape. + instantiated or injected; treat it as a static utility despite the shape. Three public overloads + carry a `SuppressMessage` for the public-API analyzer's optional-parameter rules: the seven-parameter + `ApplySorting` for RS0026 (`:183`) and the two older `Validate` overloads for RS0027 (`:367`, + `:403`). Each justification records a grandfathered overload whose signature cannot change + without a breaking change (the RS0026/RS0027 baseline), so the overload set is frozen as shipped + rather than collapsed. --- @@ -725,10 +731,10 @@ The fourth co-located type is [`EventUpcasterRegistry`](#eventupcasterregistry) runs with the default (constant-folding) config. Keeping it `internal` means no consumer can pass a different config into the built-in strategies. - **Where it's used**: passed as the first argument to every `query.Where(...)` in all seven built-in - strategies (for example `IntFilterStrategy.cs:31`, `StringFilterStrategy.cs:23`, + strategies (for example `IntFilterStrategy.cs:31`, `StringFilterStrategy.cs:32`, `DateTimeFilterStrategy.cs:32`) and to every ordering call in [`QueryFieldService.ApplySorting`](#queryfieldservice) - (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/QueryFieldService.cs:197`, `:177`, + (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/QueryFieldService.cs:198`, `:177`, `:182`, which qualify it as `Filtering.DynamicQueryConfig.Parameterized` because that class sits in the parent namespace). - **Caveats / not-in-source**: `internal`, so a consumer writing a custom @@ -786,14 +792,14 @@ The fourth co-located type is [`EventUpcasterRegistry`](#eventupcasterregistry) `List` rather than an array matters downstream: LINQ Dynamic binds it as the receiver of a `Contains` call. Putting `CanParse` here rather than in each strategy is what keeps the *validation* rule and the *application* rule from drifting apart, which is exactly the class of bug - [`QueryFilterService`](#queryfilterservice) documents at `QueryFilterService.cs:284-288`. + [`QueryFilterService`](#queryfilterservice) documents at `QueryFilterService.cs:293-297`. - **Where it's used**: every value strategy that supports `IN` or `BETWEEN` routes through `ParseList`: [`DateTimeFilterStrategy`](#datetimefilterstrategy) (`DateTimeFilterStrategy.cs:59`, `:66`), [`DecimalFilterStrategy`](#decimalfilterstrategy) (`DecimalFilterStrategy.cs:55`, `:62`), [`GuidFilterStrategy`](#guidfilterstrategy) (`GuidFilterStrategy.cs:38`), [`IntFilterStrategy`](#intfilterstrategy) (`IntFilterStrategy.cs:56`, `:63`), and [`LongFilterStrategy`](#longfilterstrategy) (`LongFilterStrategy.cs:55`, `:62`); - [`StringFilterStrategy`](#stringfilterstrategy) (`StringFilterStrategy.cs:45`) calls + [`StringFilterStrategy`](#stringfilterstrategy) (`StringFilterStrategy.cs:54`) calls `ParseStringList`. `CanParse` backs the `CanParseValue` override on all six value strategies (`BoolFilterStrategy.cs:21`, `DateTimeFilterStrategy.cs:26`, `DecimalFilterStrategy.cs:25`, `GuidFilterStrategy.cs:22`, `IntFilterStrategy.cs:26`, `LongFilterStrategy.cs:25`). @@ -849,7 +855,7 @@ The fourth co-located type is [`EventUpcasterRegistry`](#eventupcasterregistry) out-of-tree implementer. - **Where it's used**: implemented by the seven built-in strategies below; the registry, the dispatch, and the up-front validation all live in [`QueryFilterService`](#queryfilterservice) - (`QueryFilterService.cs:280`, `:195` for the two validation hooks). + (`QueryFilterService.cs:289`, `:195` for the two validation hooks). --- @@ -875,8 +881,8 @@ The fourth co-located type is [`EventUpcasterRegistry`](#eventupcasterregistry) > *widened* response, the six value strategies override `CanParseValue` by delegating to > [`FilterValueParser.CanParse`](#filtervalueparser) with their own parse function, so > [`QueryFilterService`](#queryfilterservice) rejects the request before it executes. -> [`StringFilterStrategy`](#stringfilterstrategy) is the one that does not override it: every raw -> string is a valid string, so the interface default of `true` is already correct. +> [`StringFilterStrategy`](#stringfilterstrategy) overrides it more narrowly: every raw string is +> a valid string, so only an `IN` list with no usable item is refused. > 4. **`IN` is set membership.** The strategies that support `IN` decode a comma list through > [`FilterValueParser`](#filtervalueparser) and emit `@0.Contains({property})`, with the parsed > list as the *receiver* `@0` and the entity property as the argument, the shape LINQ Dynamic turns @@ -942,32 +948,42 @@ The fourth co-located type is [`EventUpcasterRegistry`](#eventupcasterregistry) - **Depends on**: [`IFilterStrategy`](#ifilterstrategy), [`FilterValueParser`](#filtervalueparser), [`DynamicQueryConfig`](#dynamicqueryconfig); `System.Globalization`, `System.Collections.Frozen`, `System.Linq.Dynamic.Core`. -- **Concept introduced, culture-invariant parsing of untrusted input.** `FormatProvider` is a static - `CultureInfo.InvariantCulture` (`:15`) used by every `DateTime.TryParse` call, so `"2026-07-21"` - parses identically regardless of the server's locale. That is the single-locale, culture-safe - posture described in [primer §4](00-primer.md#4-c-build-and-code-style-conventions), and it is what - keeps a filter from meaning different things on two hosts in the same cluster. +- **Concept introduced, culture-invariant and zone-invariant parsing of untrusted input.** + `FormatProvider` is a static `CultureInfo.InvariantCulture` (`:15`), so `"2026-07-21"` parses + identically regardless of the server's locale. That is the single-locale, culture-safe posture + described in [primer §4](00-primer.md#4-c-build-and-code-style-conventions). The second half is + the time zone: `ParseDateTime` (`:80-81`) passes + `DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal`, so an offset or `Z` in the + value is honoured and normalized to UTC, a bare value is taken as UTC, and the result is always a + `DateTimeKind.Utc` instant. Its doc comment (`:72-79`) states the reason: that is how the framework + stores every `DateTime`, and the host's local zone never enters the result, so the same filter + selects the same rows on every server in the cluster. - **Walkthrough**: `SupportedOperators` (`:17-22`) is the ten-entry frozen set. `CanParseValue` (`:25-26`) delegates to [`FilterValueParser.CanParse`](#filtervalueparser) with the `ParseDateTime` - method group. `Apply` (`:28-47`) parses **inside** each temporal arm via a `when` clause, for - example - `"IS AFTER" when DateTime.TryParse(value, FormatProvider, DateTimeStyles.None, out var dt) => + method group. `Apply` (`:28-47`) parses **inside** each temporal arm via a `when` clause over + the same helper, for example + `"IS AFTER" when ParseDateTime(value) is { } dt => query.Where(DynamicQueryConfig.Parameterized, $"{property} > @0", dt)` (`:35-36`). A failed parse - means the `when` guard is false and the arm does not match. The two null operators need no value - (`:43-44`). The `_ =>` arm routes to `ApplyInOrRange` (`:46`), which dispatches `IN` to `ApplyIn` - and `BETWEEN` to `ApplyBetween` (`:49-55`). `ApplyIn` (`:57-61`) parses through + yields `null`, the property pattern does not match, and the arm is skipped. The two null operators + need no value (`:43-44`). The `_ =>` arm routes to `ApplyInOrRange` (`:46`), which dispatches `IN` + to `ApplyIn` and `BETWEEN` to `ApplyBetween` (`:49-55`). `ApplyIn` (`:57-61`) parses through [`FilterValueParser.ParseList`](#filtervalueparser) with `ParseDateTime` (`:59`) and emits `@0.Contains({property})` (`:60`); `ApplyBetween` (`:63-70`) requires exactly two bounds (`:67`) - and emits `{property} >= @0 && {property} <= @1` (`:68`). `ParseDateTime` (`:72-73`) is the shared - culture-invariant `TryParse`. + and emits `{property} >= @0 && {property} <= @1` (`:68`). `ParseDateTime` (`:80-81`, `internal + static`) is therefore the single parse path for validation, the scalar arms, `IN` and `BETWEEN`. - **Why it's built this way**: parsing per arm keeps the value-taking and the value-free operators in - one switch without a pre-parse that would reject `IS EMPTY` for having no parsable value; splitting - `IN`/`BETWEEN` into a helper keeps the main switch under the analyzers' cyclomatic-complexity - ceiling (the inline comment at `:45` records the reason). + one switch without a pre-parse that would reject `IS EMPTY` for having no parsable value; routing + every arm through one helper means validation and application cannot disagree on the parse + styles; splitting `IN`/`BETWEEN` into a helper keeps the main switch under the analyzers' + cyclomatic-complexity ceiling (the inline comment at `:45` records the reason). - **Where it's used**: registered against `typeof(DateTime)` and `typeof(DateTime?)` in [`QueryFilterService`](#queryfilterservice) (`QueryFilterService.cs:42-43`), as two instances. -- **Caveats / not-in-source**: nothing here normalizes to UTC: the parsed value is used as given - (`DateTimeStyles.None`), so the caller is responsible for supplying a value in the column's kind. + Behavior is pinned by + `MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Services/Filtering/DateTimeFilterStrategyTests.cs`. +- **Caveats / not-in-source**: a bare value is read as UTC, not as the caller's local time, so a + client filtering on a local wall-clock time must send an explicit offset. Whether a given column + actually holds UTC values depends on how the entity was written, which is not visible from this + class. --- @@ -1106,37 +1122,43 @@ The fourth co-located type is [`EventUpcasterRegistry`](#eventupcasterregistry) - **Concept introduced, method-call expressions in LINQ Dynamic.** Where the value strategies emit operator comparisons, the text operators emit *method calls* on the property: `{property}.Contains(@0)`, `!{property}.Contains(@0)`, `{property}.StartsWith(@0)`, - `{property}.EndsWith(@0)` (`:23-28`), and `string.IsNullOrEmpty({property})` / - `!string.IsNullOrEmpty({property})` for the presence checks (`:37-38`). LINQ Dynamic parses the + `{property}.EndsWith(@0)` (`:32-37`), and `string.IsNullOrEmpty({property})` / + `!string.IsNullOrEmpty({property})` for the presence checks (`:46-47`). LINQ Dynamic parses the string into an expression tree, and EF Core then translates those calls into `LIKE` predicates, so the match still runs in the database. `[Rubric §12, Performance & Scalability]`: `CONTAINS` translates to a leading-wildcard `LIKE`, which cannot use a normal B-tree index, that is a known cost of the convenience, not a defect of this class. -- **Walkthrough**: `SupportedOperators` (`:14-18`) is the nine-entry frozen set. This is the only - built-in strategy that does **not** override `CanParseValue`, so it inherits the interface default - of `true` (`IFilterStrategy.cs:44`): every raw string is a usable string value, so there is nothing - to reject. `Apply` (`:20-30`) handles the six value-taking text operators, then delegates the - rest to `ApplyPresenceOrSet` (`:34-41`); the inline comment (`:32-33`) records why, the split keeps - each method under the cyclomatic-complexity ceiling the analyzers enforce as errors. - `ApplyPresenceOrSet` covers `IS EMPTY`, `IS NOT EMPTY`, and routes `IN` to `ApplyIn` (`:43-47`), - which uses [`FilterValueParser.ParseStringList`](#filtervalueparser) (`:45`) and emits the same +- **Walkthrough**: `SupportedOperators` (`:14-18`) is the nine-entry frozen set. `CanParseValue` + (`:26-27`) is overridden, but narrowly: it returns `true` for every operator except `IN`, and for + `IN` it requires [`FilterValueParser.ParseStringList`](#filtervalueparser) to yield at least one + item. The remarks (`:21-25`) record why only `IN` has a value shape to reject: an `IN` list with no + usable item would otherwise fail open and return the unfiltered set, where every value-typed + strategy refuses the same input; every other string operator accepts any value, including the + empty string for `EQUALS`. Unlike the six value strategies it does not delegate to + `FilterValueParser.CanParse`, because a string has no scalar parse to fail. `Apply` (`:29-39`) + handles the six value-taking text operators, then delegates the rest to `ApplyPresenceOrSet` + (`:43-50`); the inline comment (`:41-42`) records why, the split keeps each method under the + cyclomatic-complexity ceiling the analyzers enforce as errors. `ApplyPresenceOrSet` covers + `IS EMPTY`, `IS NOT EMPTY`, and routes `IN` to `ApplyIn` (`:52-56`), which uses + [`FilterValueParser.ParseStringList`](#filtervalueparser) (`:54`) and emits the same `@0.Contains({property})` receiver-inverted form as the other `IN` strategies. - **Why it's built this way**: the class doc comment (`:6-11`) still describes this strategy as the one used "for nested property paths (e.g. `Category.Name`) regardless of the target type, since LINQ Dynamic evaluates the full path as a string expression." **Trust the code, not that doc comment**: it describes an arrangement the code no longer has. [`QueryFilterService.ResolveFilterValueType`](#queryfilterservice) - (`QueryFilterService.cs:368-387`) walks a dotted path to its leaf and returns the leaf's own type, - and `ResolveStrategy` (`:303-306`) hands back this strategy only when that resolved type is - `typeof(string)`. A path whose leaf cannot be walked no longer falls back here at all: it now fails - closed (`ResolveFilterValueType` returns `null`, and both callers reject or skip the filter, - `QueryFilterService.cs:257-265` and `:98-99`). So `"Category.Id"` on a Guid key gets + (`QueryFilterService.cs:378-397`) walks a dotted path to its leaf and returns the leaf's own type, + and `ResolveStrategy` (`QueryFilterService.cs:408-409`) hands back this strategy only when that + resolved type is `typeof(string)`. A path whose leaf cannot be walked does not fall back here at + all: it fails closed (`ResolveFilterValueType` returns `null`, and both callers reject or skip the + filter, `QueryFilterService.cs:266-274` and `:129-130`). So `"Category.Id"` on a Guid key gets [`GuidFilterStrategy`](#guidfilterstrategy)'s operator set, and `"Category.Nonexistent"` gets a 400 rather than this strategy's operator set. - **Where it's used**: registered against `typeof(string)` in [`QueryFilterService`](#queryfilterservice) (`QueryFilterService.cs:35`), and held a second time as the dedicated `StringStrategy` field (`QueryFilterService.cs:54`) that `ResolveStrategy` returns for - any resolved value type equal to `typeof(string)` (`:303-306`). Behavior is pinned by + any resolved value type equal to `typeof(string)` (`QueryFilterService.cs:408-409`). Behavior is + pinned by `MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Services/Filtering/StringFilterStrategyTests.cs`. - **Caveats / not-in-source**: nothing here escapes LIKE wildcards, so a `%` inside a `CONTAINS` value reaches the database as part of the pattern. The value is still a bound parameter (rule 1 of @@ -1184,11 +1206,11 @@ The fourth co-located type is [`EventUpcasterRegistry`](#eventupcasterregistry) `Activator.CreateInstance(typeof(StronglyTypedIdFilterStrategy<,>).MakeGenericType(identifierType, valueType))` (`:81-84`). [`QueryFilterService.ResolveStrategy`](#queryfilterservice) calls `TryCreate` only after its own `Strategies` table misses, and memoizes the result with - `Strategies.GetOrAdd` (`QueryFilterService.cs:404-406`), so the reflection cost is paid once per + `Strategies.GetOrAdd` (`QueryFilterService.cs:414-416`), so the reflection cost is paid once per identifier type, not once per request. That is what lets a consumer adopt `services.AddStronglyTypedIds(...)` without a startup `RegisterStrategy` call per identifier. - **Where it's used**: created on demand inside - [`QueryFilterService.ResolveStrategy`](#queryfilterservice) (`QueryFilterService.cs:396-407`) + [`QueryFilterService.ResolveStrategy`](#queryfilterservice) (`QueryFilterService.cs:406-417`) whenever a filter's resolved value type is a strongly typed identifier with no explicitly registered strategy. Behavior is pinned by `MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Services/Filtering/StronglyTypedIdFilterStrategyTests.cs`. @@ -1237,40 +1259,52 @@ The fourth co-located type is [`EventUpcasterRegistry`](#eventupcasterregistry) disappear, returning an unfiltered 200. `[Rubric §9, API & Contract Design]`: the alternative failure, letting Dynamic LINQ blow up at query-build time, turns a bad request into a 500. - **Concept introduced, gating an unmapped filter key against the response contract rather than the - entity (SEC-Common-25, SEC-ADC-09, SEC-Store-13).** Both public entry points now come in a pair: the + entity (SEC-Common-25, SEC-ADC-09, SEC-Store-13).** Both public entry points come in a pair: the two-argument `ApplyFilters`/`ValidateFilters` overloads forward to the three-argument ones with - `fieldContract: null` (`:78-84`, `:150-153`), preserving the historical entity-reflection behaviour + `fieldContract: null` (`:78-82`, `:159-162`), preserving the historical entity-reflection behaviour for a caller whose filter keys are all server-authored. The three-argument overloads - (`:99-103`, `:166-169`) accept a [`QueryFieldContract`](group-03-querying-specifications.md#queryfieldcontract)`?` - and route every key through `IsAdmissibleKey` (`:200-210`) before it reaches reflection: a + (`:99-103`, `:175-178`) accept a [`QueryFieldContract`](group-03-querying-specifications.md#queryfieldcontract)`?` + and route every key through `IsAdmissibleKey` (`:209-218`) before it reaches reflection: a server-mapped key is trusted for *what* it names but is still capped at - `QueryFieldContract.IsWithinNavigationDepth` (`:206`), since every extra dotted segment past + `QueryFieldContract.IsWithinNavigationDepth` (`:215`), since every extra dotted segment past `MaxNavigationDepth` is another LEFT JOIN, the join-explosion load a self-referencing navigation repeated hundreds of times produces; a key the map did **not** author is client input and must both - pass that depth check and be declared filterable by `fieldContract.AllowsClientKey` (`:209`), so a + pass that depth check and be declared filterable by `fieldContract.AllowsClientKey` (`:218`), so a caller can neither walk the object graph nor address a column no DTO exposes. `ApplyFilters` drops a - key that fails the gate (`:117-118`); `ValidateFilters`, through `ValidateSingleFilter` (`:223-232`), + key that fails the gate (`:117-118`); `ValidateFilters`, through `ValidateSingleFilter` (`:232-240`), turns it into the same `Filter.Property.NotFound` error an unknown property gets, so the two paths agree on what a caller may reach. `[Rubric §11, Security]`: this closes a hole the entity-reflection path left open, an unmapped key could address any public entity property, including one the response DTO never carries. +- **Concept introduced, case-insensitive key resolution with a canonical member name.** A filter key + resolves against the entity case-insensitively: `LookupProperty` passes `BindingFlags.IgnoreCase` + (`:358-359`, the comment naming the sort column, the field contract and the filter dictionary as + the precedents). Resolving loosely is only half of it: the Dynamic LINQ expression must then name a + member that actually exists, so `ApplyFilters` picks a `memberPath` (`:134-141`). A flat client key + is applied under the property's declared name (`propertyInfo.Name`), so the emitted member access + never depends on how the client cased it; a server-authored map entry, or any dotted path, is + applied verbatim. The comment records why the server-authored case must not be substituted: an + entry may be an expression rather than a member (ADC maps Speaker `"FullName"` to a + `FirstName + " " + LastName` concatenation because the entity's own `FullName` is unmapped), and + replacing it with the property name produces a query EF cannot translate. - **Walkthrough** - `PropertyCache` (`:30`): `ConcurrentDictionary<(Type EntityType, string PropertyName), PropertyInfo>`, so a reflected lookup is paid once per entity/property pair. Read the doc comment - (`:24-28`) and `LookupProperty` (`:343-354`) together: **only successful lookups are - memoized** (`if (resolved is not null) PropertyCache[key] = resolved;`, `:350-351`). Caching + (`:24-29`) and `LookupProperty` (`:352-364`) together: **only successful lookups are + memoized** (`if (resolved is not null) PropertyCache[key] = resolved;`, `:360-361`). Caching misses too would let any caller grow a process-lifetime static dictionary without bound simply by filtering on names that do not exist, and because the request still gets a clean 400 the growth - would be invisible in error metrics. A miss now costs one reflection lookup, bounded per request + would be invisible in error metrics. A miss costs one reflection lookup, bounded per request by `MaxFilters = 50` in [`QueryFilterModelBinder`](group-12-api-hosting-mapping.md#queryfiltermodelbinder). This is an unbounded-memory-growth defense, `[Rubric §11, Security]` and `[Rubric §12, Performance & - Scalability]`. + Scalability]`. The cache key is the name as probed (`:354`), so two casings of one property are + two entries pointing at the same `PropertyInfo`. - `StringStrategy` (`:54`): a dedicated [`StringFilterStrategy`](#stringfilterstrategy) instance held apart from the table, returned by `ResolveStrategy` whenever the resolved value type is `typeof(string)` (its doc comment at `:50-53` names both cases: a flat string property, or a nested path whose leaf is one). - - `ApplyFilters` (`:99-140`, the `fieldContract`-accepting overload; `:78-84` is the + - `ApplyFilters` (`:99-149`, the `fieldContract`-accepting overload; `:78-82` is the two-argument forwarder): for each `(property, (op, value))` it translates the DTO name through `dtoToEntityPropertyMap` (`:111-112`), gates the resolved key through `IsAdmissibleKey` (`:117-118`), resolves the `PropertyInfo` through `ResolvePropertyInfo` (`:120`) and **silently @@ -1280,73 +1314,83 @@ The fourth co-located type is [`EventUpcasterRegistry`](#eventupcasterregistry) rejects all of these cases, so reaching here means the caller never validated, and applying a filter the query cannot express is worse than dropping it. Only then does it uppercase the operator once (`:132`, the caller-side half of the [`IFilterStrategy`](#ifilterstrategy) - contract), resolve the strategy from the value type (`:134`), and apply it when one exists - (`:135-136`). - - `ValidateFilters` (`:166-184`, the `fieldContract`-accepting overload; `:150-153` is the - two-argument forwarder): returns success for a null/empty filter map (`:173-174`), otherwise runs - `ValidateSingleFilter` per entry with `fieldContract` threaded through (`:178-179`) and collects - **all** errors into one [`Result`](group-01-result-error-handling.md#result) (`:181-183`), so a + contract), choose the `memberPath` (`:139-141`), resolve the strategy from the value type + (`:143`), and apply it under that member path when one exists (`:144-145`). + - `ValidateFilters` (`:175-193`, the `fieldContract`-accepting overload; `:159-162` is the + two-argument forwarder): returns success for a null/empty filter map (`:182-183`), otherwise runs + `ValidateSingleFilter` per entry with `fieldContract` threaded through (`:187-188`) and collects + **all** errors into one [`Result`](group-01-result-error-handling.md#result) (`:190-192`), so a client sees every problem at once rather than one per round trip. - - `ValidateSingleFilter` (`:212-`, continuing past the excerpt shown here) is a five-gate - ladder, each gate returning early so one filter never yields two errors about the same mistake. - Gate zero: `IsAdmissibleKey` fails, `Filter.Property.NotFound` naming the key as not filterable - (`:223-232`). Gate one: reflection cannot resolve the property at all, the same code, a different - message. Gate two: the property resolves but the dotted path's leaf does not, again + - `ValidateSingleFilter` (`:221-291`) is a five-gate ladder, each gate returning early so + one filter never yields two errors about the same mistake. Gate zero: `IsAdmissibleKey` fails, + `Filter.Property.NotFound` naming the key as not filterable (`:232-240`). Gate one: reflection + cannot resolve the property at all (`:244`), the same code, a different message. Gate two: the + property resolves but the dotted path's leaf does not (`:266-274`), again `Filter.Property.NotFound` naming the path. Gate three: no strategy is registered for the resolved - value type, `Filter.Type.NotSupported`. Gate four: it delegates to `ValidateOperatorSupported` for - `Filter.Operator.NotSupported` and `ValidateValueParseable` (`:289-`) for `Filter.Value.Invalid`. - Every error carries `source: nameof(ValidateFilters)` so the origin is identifiable in a - problem-details payload. - - `ValidateValueParseable` (`:289-`) is the guard behind rule 3 of the family preamble: without it + value type (`:278`), `Filter.Type.NotSupported`. Gate four: it delegates to + `ValidateOperatorSupported` for `Filter.Operator.NotSupported` and `ValidateValueParseable` for + `Filter.Value.Invalid` (`:289-290`). Every error carries `source: nameof(ValidateFilters)` so the + origin is identifiable in a problem-details payload. + - `ValidateValueParseable` (`:298-`) is the guard behind rule 3 of the family preamble: without it the strategy returns the query unfiltered, so a malformed value **widened** the response to the full result set instead of narrowing it to no matches. It deliberately stays silent when the operator itself is already invalid, so one bad filter does not produce two errors describing the - same mistake. - - `ResolvePropertyInfo` (`:331-338`): probes the DTO-facing name first, then the mapped entity name - (its root segment for a dotted path, `:333-335`). The doc comment records why both the apply path + same mistake; otherwise it asks `strategy.CanParseValue` (`:311`). + - `ResolvePropertyInfo` (`:340-347`): probes the DTO-facing name first, then the mapped entity name + (its root segment for a dotted path, `:342-344`). The doc comment records why both the apply path and the validate path share it: they used to disagree on the fallback order, so a plain rename entry such as `["Name"] = "Title"` passed validation and was then **silently dropped**, returning an unfiltered result set with a 200. - - `ResolveFilterValueType` (`:368-387`): for a flat property this is the property's own - type (`:370-371`); for a dotted path it walks each segment to the **leaf's** type (`:373-386`), - starting from the path's own root rather than from the already-resolved property (`:375-377`, + - `ResolveFilterValueType` (`:378-397`): for a flat property this is the property's own + type (`:380-381`); for a dotted path it walks each segment to the **leaf's** type (`:383-396`), + starting from the path's own root rather than from the already-resolved property (`:385-387`, because for a nested path the DTO-facing name and the path root need not agree). The walk stops the moment a segment is not a public instance property of the preceding segment's type - (`:379-384`), and the remarks spell out that the `null` result is a *fail-closed* signal, not a + (`:389-394`), and the remarks spell out that the `null` result is a *fail-closed* signal, not a fallback: validation rejects it as an unknown property and application skips it. - - `ResolveStrategy` (`:396-407`): `typeof(string)` maps to `StringStrategy`, an explicitly - registered type is a `TryGetValue` hit on the table (`:401-402`); otherwise it asks + - `ResolveStrategy` (`:406-417`): `typeof(string)` maps to `StringStrategy` (`:408-409`), an + explicitly registered type is a `TryGetValue` hit on the table (`:411-412`); otherwise it asks [`StronglyTypedIdFilterStrategy.TryCreate`](#stronglytypedidfilterstrategytself-tvalue) to build a strategy for a strongly typed identifier column and, when that succeeds, memoizes it into - `Strategies` with `GetOrAdd` (`:404-406`), so an unregistered, non-identifier type is the only case + `Strategies` with `GetOrAdd` (`:414-416`), so an unregistered, non-identifier type is the only case that still yields `null` and fires gate three of the validation ladder. - **Why it's built this way**: validating up front converts would-be runtime exceptions and silently widened responses into precise validation errors at the API boundary; the property cache amortises resolution to first use per process while refusing to memoize client-controlled misses; gating an unmapped key against the response contract closes the same class of over-exposure the property cache and the nested-path fail-closed rule already close, just at the key-name layer instead of the - type layer; and resolving a nested path to its leaf type is what lets one operator vocabulary apply + type layer; resolving case-insensitively but emitting the declared name keeps the key matching + rule consistent with sorting and the field contract without letting client casing reach the + expression; and resolving a nested path to its leaf type is what lets one operator vocabulary apply uniformly to flat and nested properties without the string strategy standing in for everything. - **Where it's used**: `ValidateFilters` is called from [`EntityQueryService.GetAllAsync`](#entityqueryservicetentity-tentitydto-tidentifiertype) - (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:267`); + (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:301`); `ApplyFilters` is called inside [`EntityQueryPipeline`](#entityquerypipeline) on both of its paths, before materialization - (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/EntityQueryPipeline.cs:77` for the - projected path and `:151` for the entity path). The filter map itself is produced at the API edge by + (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/EntityQueryPipeline.cs:77` and + `:152`). All three calls use the three-argument overload with the caller's `FieldContract`. The + filter map itself is produced at the API edge by [`QueryFilterModelBinder`](group-12-api-hosting-mapping.md#queryfiltermodelbinder). Its own behavior is pinned by four test classes under `MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Services/Filtering/` (`QueryFilterServiceTests`, `QueryFilterServiceValidateTests`, `QueryFilterServicePropertyCacheTests`, `StronglyTypedIdFilterStrategyTests`) plus `MMCA.Common.Application.Tests/Services/Query/QueryFieldContractSecurityTests.cs`, on top of the - seven per-strategy suites in the same folder. + seven per-strategy suites in the same folder. Downstream, ADC pins its public schedule's Room filter + through the same two calls + (`MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/SessionRoomFilterTests.cs:16`), + so a renamed or retyped `Session.RoomId` fails a test rather than silently dropping the filter. - **Caveats / not-in-source**: this is a **static class**, not a DI-resolved service, so `RegisterStrategy` mutates process-global state and is only safe at startup; there is no deregistration and no per-tenant table. Nullable variants are registered as separate instances rather than a shared one (`:35-47`), which costs a few extra objects but keeps the table declaration flat. `ResolveFilterValueType` walks only public instance properties (`BindingFlags.Public | - BindingFlags.Instance`), so a path through a non-public member is unreachable by design. + BindingFlags.Instance`), so a path through a non-public member is unreachable by design. The + case-insensitive lookup covers the root segment only: the segment walk past it (`:392`) does not + pass `BindingFlags.IgnoreCase`, so a dotted path needs every segment after the first cased as + declared, and because a dotted path is applied verbatim (`:139`) its casing reaches the Dynamic + LINQ expression unchanged. ### AmbientScope > MMCA.Common.Application · `MMCA.Common.Application.Services.Query` · `MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/QueryTagScope.cs:61` · Level 0 · class (private, sealed, nested) @@ -1363,7 +1407,7 @@ The fourth co-located type is [`EventUpcasterRegistry`](#eventupcasterregistry) `try`/`finally` at each call site, lets [`QueryTagScope.Begin`](#querytagscope) hand back a uniform `IDisposable` regardless of whether the scope is nested; the outermost, by far the most common, case never allocates one at all (see [`QueryTagScope`](#querytagscope)'s shared `OutermostScope` handle). -- **Where it's used**: constructed only inside `QueryTagScope.Begin` (`QueryTagScope.cs:333`) when a +- **Where it's used**: constructed only inside `QueryTagScope.Begin` (`QueryTagScope.cs:52`) when a scope is already open; not referenced anywhere else. --- @@ -1515,7 +1559,7 @@ The fourth co-located type is [`EventUpcasterRegistry`](#eventupcasterregistry) filtering and sorting can use DTO-facing names even when they differ from the entity's own properties; it defaults to `FrozenDictionary.Empty` so callers that do not need remapping can omit it. - - `QueryFieldContract? FieldContract` (`EntityQueryParameters.cs:280`), the response contract a + - `QueryFieldContract? FieldContract` (`EntityQueryParameters.cs:52`), the response contract a client-supplied sort column or filter key must belong to when `DTOToEntityPropertyMap` does not cover it (SEC-Common-25, SEC-ADC-09). `null` leaves the pipeline resolving names against the entity, the pre-hardening behavior, which is only correct for a caller whose keys are server-authored rather @@ -1914,7 +1958,7 @@ The fourth co-located type is [`EventUpcasterRegistry`](#eventupcasterregistry) [`IEntityQueryService`](#ientityqueryservicetentity-tentitydto-tidentifiertype) (for example `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Mapping/IEntityQueryService.cs:40`), and of the repository's specification-driven reads - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:572`). + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:587`). --- @@ -2056,7 +2100,7 @@ The fourth co-located type is [`EventUpcasterRegistry`](#eventupcasterregistry) through the fluent [`SpecificationExtensions`](#specificationextensions)`.And` rather than the constructor. ADC's [`SessionsController`](group-20-conference-api-grpc.md#sessionscontroller) ANDs the public-session filter with the speaker-scoped filter when a `SpeakerId` filter is present - (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:125-127`, + (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:133-135`, the composite then passed as the `specification` argument at `:187`) and [`SpeakersController`](group-20-conference-api-grpc.md#speakerscontroller) does the same for its own public/filtered pair (`.../Controllers/SpeakersController.cs:171-173`); ADC's @@ -2186,7 +2230,7 @@ The fourth co-located type is [`EventUpcasterRegistry`](#eventupcasterregistry) deleted rows into scope drops the named `SoftDelete` filter and **only** that one, so the tenant filter stays in force and a specification asking for deleted rows can never reach another tenant's data (`:74-82`, enforced repository-side at - `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:566-568`). + `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:581-583`). - **Walkthrough**: state is exposed read-only and assembled through protected builders a derived specification calls from its constructor. - Backing fields `_orderBy` and `_includePaths` (`:43-44`) with the read-only projections @@ -2217,8 +2261,8 @@ The fourth co-located type is [`EventUpcasterRegistry`](#eventupcasterregistry) a plain `ISpecification` against this type (`SpecificationEvaluator.cs:56`) and, when it matches, applies the includes (`:51`), the ordering chain (`:52`) and the `Skip`/`Take` window (`:54-58`); aggregate reads pass `applyShape: false` so a count never joins in includes or pages (`:29-34`, with - the call sites at `EFReadRepository.cs:454` and `:516`). The tracking and soft-delete flags are - consumed one level up, in `EFReadRepository.BaseQueryFor` (`EFReadRepository.cs:560-569`), which + the call sites at `EFReadRepository.cs:455` and `:516`). The tracking and soft-delete flags are + consumed one level up, in `EFReadRepository.BaseQueryFor` (`EFReadRepository.cs:575-584`), which chooses `Table` vs `TableNoTracking` (`:443`) and drops the named soft-delete filter (`:445-447`). Behavior is pinned by `MMCA.Common/Tests/Core/MMCA.Common.Domain.Tests/Specifications/QuerySpecificationTests.cs:14`, @@ -2331,7 +2375,7 @@ The fourth co-located type is [`EventUpcasterRegistry`](#eventupcasterregistry) - **Where it's used**: `And` is the spelling every production composition in the workspace uses. ADC's [`SessionsController`](group-20-conference-api-grpc.md#sessionscontroller) writes `publicSpecification.And(speakerResult.Value!)` - (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:127`), + (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:135`), [`SpeakersController`](group-20-conference-api-grpc.md#speakerscontroller) writes `publicSpecification.And(filterResult.Value!)` (`.../Controllers/SpeakersController.cs:173`), and [`PublicConferenceVisibility`](group-18-conference-application.md#publicconferencevisibility) chains @@ -2424,7 +2468,7 @@ The fourth co-located type is [`EventUpcasterRegistry`](#eventupcasterregistry) generic controller base (G12), for example ADC's [`SessionsController`](group-20-conference-api-grpc.md#sessionscontroller), which passes its specification as the `specification` argument at - `MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:148` + `MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:156` and `:187`. --- diff --git a/docs-src/onboarding/group-04-events-outbox.md b/docs-src/onboarding/group-04-events-outbox.md index 3007c8a4..e6962fda 100644 --- a/docs-src/onboarding/group-04-events-outbox.md +++ b/docs-src/onboarding/group-04-events-outbox.md @@ -126,7 +126,7 @@ this atomicity. Crucially, the rows go to the same physical database as the aggr relational source owns its own `OutboxMessages` table, never a shared one ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html); the table, its origin columns, and its three filtered indexes, pending, processed, and ordering, are configured in -`MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:664-711`, with the inbox's +`MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:667-714`, with the inbox's table and unique index at `ApplicationDbContext.cs:721-731`; see the [primer on database-per-service](00-primer.md#2-architectural-styles-this-codebase-commits-to)). @@ -221,7 +221,7 @@ a caller with nothing to capture keeps the single-argument call it always made Capture is one read per save, not one per event. The interceptor is a singleton and cannot reach a scoped service, so [`DbContextFactory`](group-07-persistence-ef-core.md#dbcontextfactory) attaches a -live accessor to the scoped context (`DbContextFactory.cs:149-151`) and the interceptor reads it once +live accessor to the scoped context (`DbContextFactory.cs:155-157`) and the interceptor reads it once through `CurrentOutboxOrigin` (`ApplicationDbContext.cs:158,164`, `DomainEventSaveChangesInterceptor.cs:242-247`); the two event buses do the same once per batch (`InProcessEventBus.cs:87-89`, `BrokerEventBus.cs:79-81`). One save is one scope's work, so every row @@ -261,7 +261,7 @@ that the *same* event may be delivered more than once, so **handlers must be ide a wart; it is the documented contract and a healthy discipline regardless. The processor never blindly polls on a fixed clock. Its loop is the shared `PollingLoop` -(`MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:108-122`): after a five second startup delay +(`MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:106-120`): after a five second startup delay (`MMCA.Common.Infrastructure/Persistence/Polling/PollingLoop.cs:18,51`) it drains every outbox **target** once per cycle and aggregates the per-target results into an [`OutboxCycleResult`](#outboxcycleresult) (`OutboxProcessor.cs:157-174`), then waits on @@ -328,7 +328,7 @@ broker publish throwing) increments the row's `RetryCount`, records `LastError`, row for an explicit exponential backoff rather than leaving this cycle's claim on it (`OutboxProcessor.cs:668-680`). The backoff is `RetryBackoffBaseSeconds * 2^(n-1)` (base 10s, `OutboxSettings.cs:99`) multiplied by a random jitter factor in `[0.8, 1.2]` and capped at the lease -(`OutboxProcessor.cs:767-781`); the jitter is what stops fifty rows that failed together on one +(`OutboxProcessor.cs:680-681`, delegating to `PollingLoop.cs:183-196`); the jitter is what stops fifty rows that failed together on one dependency outage from retrying in lockstep against that same dependency. The poll query only ever selects rows with `RetryCount < MaxRetries` (5 by default, `OutboxProcessor.cs:431`, `OutboxSettings.cs:21`), so once a row exhausts its retries it stops being fetched, stalls unprocessed @@ -363,19 +363,19 @@ that count run inside a named `OutboxPoll` activity (`OutboxProcessor.cs:75,373, [`OutboxPollFilterProcessor`](group-16-aspire-orchestration.md#outboxpollfilterprocessor) (G16) suppresses from telemetry export, so a fleet of idle services polling around the clock does not flood Application Insights, and the per-message success line is `Debug` for the same reason -(`OutboxProcessor.cs:847-851`). Each dispatched message also re-parents an `OutboxProcess` consumer +(`OutboxProcessor.cs:747-751`). Each dispatched message also re-parents an `OutboxProcess` consumer activity onto the trace context stored on its row, so the broker hop stays linked to the request that -raised the event (`OutboxProcessor.cs:808-831`). +raised the event (`OutboxProcessor.cs:708-730`). A sibling [`OutboxCleanupService`](#outboxcleanupservice) keeps the tables bounded. Every `CleanupIntervalHours` (default 6, `OutboxSettings.cs:73`) it purges processed rows older than -`RetentionDays` (default 7, `OutboxCleanupService.cs:95,109`, `OutboxSettings.cs:65`), then purges +`RetentionDays` (default 7, `OutboxCleanupService.cs:90,104`, `OutboxSettings.cs:65`), then purges *dead-lettered* rows on their own `DeadLetterRetentionDays` window, falling back to `RetentionDays` when -that setting is left at its default of zero (`OutboxCleanupService.cs:150-159`, `OutboxSettings.cs:108`), +that setting is left at its default of zero (`OutboxCleanupService.cs:145-154`, `OutboxSettings.cs:108`), since those rows never get a `ProcessedOn` and would otherwise accumulate forever inside the pending index that every poll re-scans. When the inbox is enabled it purges processed inbox rows on the same -cutoff (`OutboxCleanupService.cs:57,174-181`). Setting `RetentionDays` to `0` disables the sweep entirely -(`OutboxCleanupService.cs:73-85`). Because payloads may contain personal data, this sweep is also part of +cutoff (`OutboxCleanupService.cs:52,169-176`). Setting `RetentionDays` to `0` disables the sweep entirely +(`OutboxCleanupService.cs:68-80`). Because payloads may contain personal data, this sweep is also part of the privacy posture of [ADR-005](https://ivanball.github.io/docs/adr/005-soft-delete-vs-erasure.html). Both background services take an injected `TimeProvider` (`OutboxProcessor.cs:63`, `OutboxCleanupService.cs:52-54`) so tests can drive an hour-scale loop deterministically instead of @@ -385,7 +385,7 @@ Dead letters are not only swept, they are **operable**. [`OutboxAdministration`] is the scoped EF-backed admin surface behind [`IOutboxAdministration`](group-07-persistence-ef-core.md#ioutboxadministration): it lists dead letters with merged paging across every target and a hard 500-row page cap -(`MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:45,56`), counts pending rows +(`MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:40,51`), counts pending rows (`OutboxAdministration.cs:174`), and replays dead letters as one set-based `UPDATE` per target followed by a `Signal()` so the processor picks them up immediately rather than on the next interval (`OutboxAdministration.cs:115,146,167`). It visits the same tenant-expanded targets in the same @@ -400,7 +400,7 @@ that wants to publish an integration event depends on [`IEventBus`](#ieventbus) [`IMessageBus`](#imessagebus), both defined in `Application`, so neither ever sees MassTransit). Infrastructure supplies two interchangeable implementations of each, selected by registration: -- **Monolith mode** (the defaults, `MMCA.Common.Infrastructure/DependencyInjection.cs:305,311`). +- **Monolith mode** (the defaults, `MMCA.Common.Infrastructure/DependencyInjection.cs:315,321`). [`InProcessEventBus`](#inprocesseventbus) writes the events to the outbox in one save, dispatches them in-process, and marks them processed through the same [`OutboxFinalizer`](#outboxfinalizer) path as the interceptor (`MMCA.Common.Infrastructure/Messaging/InProcessEventBus.cs:76-103`), falling back to a @@ -495,7 +495,7 @@ implementations of the three, so an external implementation keeps working unchan (`IInboxStore.cs:19-22`). What `TryBeginAsync` adds is **staging**: [`EfInboxStore`](#efinboxstore) does not write the [`InboxMessage`](#inboxmessage) row after the handlers run, it stages it into the same scoped `ApplicationDbContext` the handlers write through -(`MMCA.Common.Infrastructure/Persistence/Inbox/EfInboxStore.cs:49,61-68`). A handler's own +(`MMCA.Common.Infrastructure/Persistence/Inbox/EfInboxStore.cs:50,62-69`). A handler's own `SaveChangesAsync` therefore commits the dedup row in the same transaction as its mutations, closing by construction the window where a crash between "handler committed" and "inbox written" reprocessed the whole event; `CompleteAsync` saves the staged row only when nothing else already did, which covers @@ -643,8 +643,8 @@ edge) are the primary references. (`IntegrationEventConsumer.cs:122`). Implemented by [`EfInboxStore`](#efinboxstore) and [`NoOpInboxStore`](#noopinboxstore). - **Caveats / not-in-source**: both registrations live inside `AddBrokerMessaging` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:41`), which returns - early when the configured provider is in-process (`DependencyInjection.Messaging.cs:50-53`), so a monolith + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:42`), which returns + early when the configured provider is in-process (`DependencyInjection.Messaging.cs:51-54`), so a monolith host that never calls it has no `IInboxStore` in the container at all. That is consistent (nothing consumes the port without a broker consumer) but it does mean the inbox posture is a *broker-mode* decision, not a container-wide one. @@ -688,12 +688,12 @@ edge) are the primary references. startup log next to the rest of the boot sequence, where an operator reads it. - **Where it's used**: added by `AddBrokerMessaging` inside the `else` branch of the `IsInboxEnabled` check, immediately after the [`NoOpInboxStore`](#noopinboxstore) registration - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:112-117`, the + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:117-122`, the `AddHostedService` call at line 117). Covered by `InboxDisabledWarningServiceTests` (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Inbox/InboxDisabledWarningServiceTests.cs:11`), which is what makes a log-only class testable at all (`[Rubric §14, Testability]`). - **Caveats / not-in-source**: because the whole registration sits inside `AddBrokerMessaging`, which - returns early for the in-process provider (`DependencyInjection.Messaging.cs:50-53`), a monolith host never + returns early for the in-process provider (`DependencyInjection.Messaging.cs:51-54`), a monolith host never sees this warning. That is correct (in-process dispatch does not redeliver) but it does mean the absence of the line is not by itself evidence that dedup is on. @@ -719,13 +719,13 @@ edge) are the primary references. `InboxMessage.cs:14`) is the event's own id, the **deduplication key**. `EventType` (`required`, `InboxMessage.cs:17`) is retained for diagnostics. `ProcessedOn` (`InboxMessage.cs:20`) is the UTC timestamp stamped at staging time. The shape only makes sense together with its EF configuration - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:718-732`): - the table is `dbo.InboxMessages` (`ApplicationDbContext.cs:721`), `EventType` is capped at 500 - non-Unicode characters (`ApplicationDbContext.cs:723`), `MessageId` carries a **unique** index named - `IX_InboxMessages_MessageId` (`ApplicationDbContext.cs:724-726`), and `ProcessedOn` carries a - second, non-unique index `IX_InboxMessages_ProcessedOn` (`ApplicationDbContext.cs:730-731`) purely + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:721-735`): + the table is `dbo.InboxMessages` (`ApplicationDbContext.cs:724`), `EventType` is capped at 500 + non-Unicode characters (`ApplicationDbContext.cs:726`), `MessageId` carries a **unique** index named + `IX_InboxMessages_MessageId` (`ApplicationDbContext.cs:727-729`), and `ProcessedOn` carries a + second, non-unique index `IX_InboxMessages_ProcessedOn` (`ApplicationDbContext.cs:733-734`) purely so the age-based retention purge has something to seek instead of scanning the table - (`ApplicationDbContext.cs:728-729`, `[Rubric §12, Performance & Scalability]`). + (`ApplicationDbContext.cs:731-732`, `[Rubric §12, Performance & Scalability]`). - **Why it's built this way**: separating `Id` (the PK for EF internals) from `MessageId` (the business dedup key with the unique index) follows the surrogate-key convention used elsewhere in the codebase, and the unique index is what turns a racing duplicate delivery into a catchable @@ -735,15 +735,15 @@ edge) are the primary references. **[ADR-021](https://ivanball.github.io/docs/adr/021-consumer-inbox-idempotency.html)** governs the mechanism, and the row lives in the consumer's own database ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)). - **Where it's used**: staged, saved, and queried by [`EfInboxStore`](#efinboxstore) - (`EfInboxStore.cs:55,120`); purged by [`OutboxCleanupService`](#outboxcleanupservice) when the inbox - is enabled (`OutboxCleanupService.cs:174-189`, gated on `_inboxEnabled` at - `OutboxCleanupService.cs:57` and called at `OutboxCleanupService.cs:119-122`); configured on every - relational context by `ApplicationDbContext.ConfigureInbox` (`ApplicationDbContext.cs:718`, called + (`EfInboxStore.cs:56,121`); purged by [`OutboxCleanupService`](#outboxcleanupservice) when the inbox + is enabled (`OutboxCleanupService.cs:169-184`, gated on `_inboxEnabled` at + `OutboxCleanupService.cs:52` and called at `OutboxCleanupService.cs:114-117`); configured on every + relational context by `ApplicationDbContext.ConfigureInbox` (`ApplicationDbContext.cs:721`, called from line 347) (G07). - **Caveats / not-in-source**: the type itself has **no** first-party reference (it is a plain POCO), so the links to `IInboxStore`/`IDomainEvent` above are conceptual, not compile dependencies. The configuration is skipped by the Cosmos context, which overrides `OnModelCreating` - (`ApplicationDbContext.cs:714-716`). There is also no tenant column: a tenant with its own database + (`ApplicationDbContext.cs:717-719`). There is also no tenant column: a tenant with its own database gets its own `InboxMessages` table instead (see [`OutboxCleanupService`](#outboxcleanupservice)). `[Rubric §10, Messaging & Integration Architecture]` applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores. @@ -765,7 +765,7 @@ edge) are the primary references. (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Messaging/MessageBusSettings.cs:175`), and in that mode neither [`OutboxProcessor`](#outboxprocessor) nor [`OutboxCleanupService`](#outboxcleanupservice) is registered - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:207-215`). The delivery + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:217-225`). The delivery guarantee changes with it: events reach their handlers synchronously inside the raising process, and a crash between the commit and the dispatch loses them. The class doc states that this is the right trade for a single-process application and the wrong one to discover from an absent hosted service @@ -783,7 +783,7 @@ edge) are the primary references. is the **default** posture of an in-process host rather than an opt-out of a safety feature, and a warning on every small application's startup would train operators to ignore the category. - **Where it's used**: registered by `AddInfrastructure` in the `else` branch of the outbox gate - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:212-215`), so a host + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:222-225`), so a host either gets the two outbox background services or gets this notice, never both and never neither. `[Rubric §10, Messaging & Integration Architecture]` applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores. @@ -814,10 +814,10 @@ edge) are the primary references. (an unauthenticated background job, for instance). Keeping it a `record struct` rather than a class avoids a heap allocation on the hot save path for what is, per row, four small fields copied once. - **Where it's used**: constructed by `DbContextFactory.AttachScopeAccessors` - (`DbContextFactory.cs:149-153`) from `ICurrentUserService`, `AmbientOrigin.FlattenRoles`, the tenant + (`DbContextFactory.cs:155-159`) from `ICurrentUserService`, `AmbientOrigin.FlattenRoles`, the tenant context and the correlation context, and exposed through `ApplicationDbContext.OutboxOriginAccessor`/`CurrentOutboxOrigin` - (`ApplicationDbContext.cs:158,164`); read by [`OutboxMessage.FromDomainEvent`](#outboxmessage) to + (`ApplicationDbContext.cs:171,177`); read by [`OutboxMessage.FromDomainEvent`](#outboxmessage) to populate the row's four capture columns. - **Caveats / not-in-source**: the struct itself does not restore anything, it only carries the values; the restore side (putting the captured user/tenant/correlation back onto a delivery scope) is the @@ -847,12 +847,12 @@ edge) are the primary references. - **Why it's built this way**: making integration events a *subtype* of domain events means one outbox mechanism serves both, and the routing decision is a single `is IIntegrationEvent` pattern match in the processor - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:540`), + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:532`), with no parallel capture pipeline to keep in step. - **Where it's used**: implemented by integration events across modules and by [`BaseIntegrationEvent`](#baseintegrationevent), which adds the `SchemaVersion` convention; routed by the [`OutboxProcessor`](#outboxprocessor) to [`IMessageBus`](#imessagebus) - (`OutboxProcessor.cs:540`); published by [`InProcessEventBus`](#inprocesseventbus) and + (`OutboxProcessor.cs:532`); published by [`InProcessEventBus`](#inprocesseventbus) and [`BrokerEventBus`](#brokereventbus); consumed via [`IntegrationEventConsumer`](#integrationeventconsumertevent), whose type parameter is constrained to `class, IIntegrationEvent` (`IntegrationEventConsumer.cs:43`). @@ -883,13 +883,13 @@ edge) are the primary references. (`MessageBusSettings.IsInboxEnabled`, `MessageBusSettings.cs:141`), so this store is the deliberate opt-out path for a host that cannot query the `InboxMessages` table yet, not a quiet default. Note that it is registered as a **singleton** while [`EfInboxStore`](#efinboxstore) is scoped - (`DependencyInjection.Messaging.cs:108,112`): a stateless no-op needs no per-request lifetime, an EF-backed + (`DependencyInjection.Messaging.cs:109,117`): a stateless no-op needs no per-request lifetime, an EF-backed store that stages rows in the scope's unit of work does. The same `else` branch also registers - [`InboxDisabledWarningService`](#inboxdisabledwarningservice) (`DependencyInjection.Messaging.cs:117`), so + [`InboxDisabledWarningService`](#inboxdisabledwarningservice) (`DependencyInjection.Messaging.cs:122`), so choosing the Null Object is never silent (**[ADR-021](https://ivanball.github.io/docs/adr/021-consumer-inbox-idempotency.html)**). - **Where it's used**: registered as `IInboxStore` inside `AddBrokerMessaging` on the `else` branch of `settings.IsInboxEnabled`, that is when `MessageBus:EnableInbox=false` is set - explicitly (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:106-118`); + explicitly (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:107-123`); consumed by [`IntegrationEventConsumer`](#integrationeventconsumertevent). `[Rubric §10, Messaging & Integration Architecture]` applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores. @@ -909,7 +909,7 @@ edge) are the primary references. - **Concept introduced, options binding with a static `SectionName`.** Note the convention that runs through every settings class in the framework: `public static readonly string SectionName = "Outbox";` (`OutboxSettings.cs:13`) is the single source of truth for the section name, referenced at the bind - call instead of duplicating the literal (`DependencyInjection.cs:144`). The properties are + call instead of duplicating the literal (`DependencyInjection.cs:150`). The properties are `init`-only, so once materialized from configuration they are immutable for the process lifetime. `[Rubric §6, CQRS & Event-Driven]` assesses how reliably state changes turn into dispatched events. @@ -923,7 +923,7 @@ edge) are the primary references. `[Rubric §29, Resilience & Business Continuity]` assesses behavior under replication and repeated failure. Three properties carry the weight. `LeaseSeconds` (`:82`) claims a batch for a replica so concurrent replicas never double-dispatch, and expires so a dead replica's rows become claimable - again (`:75-81`, applied at `OutboxProcessor.cs:386`). `RetryBackoffBaseSeconds` (`:99`) makes the + again (`:75-81`, applied at `OutboxProcessor.cs:375`). `RetryBackoffBaseSeconds` (`:99`) makes the retry cadence explicit: attempt `n` waits `base * 2^(n-1)`, multiplied by a jitter factor in [0.8, 1.2] so rows that failed together do not retry in lockstep, then capped at `LeaseSeconds` (`:84-89`, implemented at `OutboxProcessor.cs:675-681`). The remark is worth reading as a design @@ -941,34 +941,34 @@ edge) are the primary references. [`IEventBus`](#ieventbus) are written, defaulting to the top-level connection strings so single-database behavior is preserved. It is a per-write target, not a global switch: the doc is explicit that the PROCESSOR still drains the outbox table of every relational physical source in use - (`:53-56`, and see `OutboxProcessor.cs:136-142`). + (`:53-56`, and see `OutboxProcessor.cs:134-140`). - **Walkthrough**: one static field then eleven `init` properties, nine of them `[Range]`-validated. - `SectionName` (`OutboxSettings.cs:13`): static readonly `"Outbox"`, the bind key. - `BatchSize` (`:16-17`): `[Range(1, 1000)]`, default `50`; messages per cycle, used both to size the - fetch (`OutboxProcessor.cs:350`) and to decide whether more eligible work remains - (`OutboxProcessor.cs:263`, `:361`). + fetch (`OutboxProcessor.cs:339`) and to decide whether more eligible work remains + (`OutboxProcessor.cs:252`, `:361`). - `MaxRetries` (`:20-21`): `[Range(1, 20)]`, default `5`; attempts before a message is treated as - dead-lettered and excluded from the poll (`OutboxProcessor.cs:293`, `:424`, `:669`). The first + dead-lettered and excluded from the poll (`OutboxProcessor.cs:282`, `:424`, `:669`). The first failure is only re-scheduled when `MaxRetries > 1`, so `1` is honored as "the host asked for no - retries at all" (`OutboxProcessor.cs:657`). + retries at all" (`OutboxProcessor.cs:656`). - `PollingIntervalSeconds` (`:30-31`): `[Range(1, 3600)]`, default `2`; the fallback interval. - `ProcessingDelaySeconds` (`:39-40`): `[Range(0, 600)]`, default `5`; the eligibility delay, applied - as a cutoff on the message timestamp (`OutboxProcessor.cs:196`, `:275`). + as a cutoff on the message timestamp (`OutboxProcessor.cs:187`, `:275`). - `DataSource` (`:48`): default `DataSource.SQLServer`; must be a relational provider (SQL Server or SQLite), since the outbox is a table. - `DatabaseName` (`:57`): default `DataSourceKey.DefaultName`; the logical source name paired with `DataSource`. - `RetentionDays` (`:64-65`): `[Range(0, 3650)]`, default `7`; days a PROCESSED message is kept - before purge, with `0` disabling purging entirely (`OutboxCleanupService.cs:77`, cutoff at `:94`). + before purge, with `0` disabling purging entirely (`OutboxCleanupService.cs:72`, cutoff at `:94`). - `CleanupIntervalHours` (`:72-73`): `[Range(1, 168)]`, default `6`; the purge sweep cadence, ignored - when `RetentionDays` is `0` (`OutboxCleanupService.cs:70`). + when `RetentionDays` is `0` (`OutboxCleanupService.cs:65`). - `LeaseSeconds` (`:81-82`): `[Range(10, 3600)]`, default `300`; the batch claim window. - `RetryBackoffBaseSeconds` (`:98-99`): `[Range(1, 3600)]`, default `10`; the exponential-backoff base described above. - `DeadLetterRetentionDays` (`:107-108`): `[Range(0, 3650)]`, default `0`, which falls back to `RetentionDays`. Set it higher to keep exhausted payloads around for diagnosis and manual replay; the cleanup service resolves the fallback explicitly before computing its cutoff - (`OutboxCleanupService.cs:155-157`). + (`OutboxCleanupService.cs:150-152`). - **Why it's built this way**: the defaults encode the framework's out-of-the-box posture (**[ADR-003](https://ivanball.github.io/docs/adr/003-outbox-dual-dispatch.html)** outbox, **[ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)** @@ -978,9 +978,9 @@ edge) are the primary references. `[Range]` guards give fail-fast validation at bind time rather than a bad value surfacing mid-cycle (**[ADR-070](https://ivanball.github.io/docs/adr/070-fail-fast-configuration-contract.html)**). - **Where it's used**: bound with `.ValidateDataAnnotations().ValidateOnStart()` in `AddInfrastructure` - (`DependencyInjection.cs:143-146`). Consumed by [`OutboxProcessor`](#outboxprocessor) - (`OutboxProcessor.cs:59`, `:66`) and [`OutboxCleanupService`](#outboxcleanupservice) - (`OutboxCleanupService.cs:48`, `:57`) for batching, retry pacing and retention; by both event buses + (`DependencyInjection.cs:149-152`). Consumed by [`OutboxProcessor`](#outboxprocessor) + (`OutboxProcessor.cs:56`, `:66`) and [`OutboxCleanupService`](#outboxcleanupservice) + (`OutboxCleanupService.cs:45`, `:57`) for batching, retry pacing and retention; by both event buses to pick the write target when publishing an integration event ([`InProcessEventBus`](#inprocesseventbus) `InProcessEventBus.cs:37`, `:78`; [`BrokerEventBus`](#brokereventbus) `BrokerEventBus.cs:35`, `:67`); and by @@ -1078,7 +1078,7 @@ edge) are the primary references. principal a synchronous dispatch would have seen instead of running anonymously; and the lease pair moves scale-out safety from a deployment convention (`minReplicas: 1`) into the data model. The EF configuration completes the picture - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:528-563`): + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:533-568`): table `dbo.OutboxMessages` (line 531), bounded columns for `EventType` (500, non-Unicode, line 533), `LastError` (4000, line 535) and `OrderingKey` (200, line 538), and three **filtered** indexes, each with its reason written above it: `IX_OutboxMessages_Pending` (line 545) whose included `RetryCount` @@ -1112,8 +1112,9 @@ edge) are the primary references. - **Depends on**: [`IDbContextFactory`](group-07-persistence-ef-core.md#idbcontextfactory), [`IDataSourceResolver`](group-07-persistence-ef-core.md#idatasourceresolver), `IOptions<`[`OutboxSettings`](group-04-events-outbox.md#outboxsettings)`>` (to find the - publish-target source) and `ILogger`, all via primary constructor - (`EfInboxStore.cs:38-42`); the [`InboxMessage`](#inboxmessage) entity; resolves an + publish-target source), `ILogger` and a `TimeProvider` (the clock that stamps + `ProcessedOn`), all via primary constructor (`EfInboxStore.cs:38-43`); the + [`InboxMessage`](#inboxmessage) entity; resolves an [`ApplicationDbContext`](group-07-persistence-ef-core.md#applicationdbcontext); EF Core's `EntityEntry` and `EntityState` for the staging bookkeeping. - **Concept introduced, staging the dedup row into the handler's transaction.** @@ -1131,51 +1132,52 @@ edge) are the primary references. source is back to two transactions, and delivery is then at-least-once again, which is the contract handlers are written against anyway. - **Walkthrough** - - `_staged` (`EfInboxStore.cs:49`) is a plain `Dictionary>` of rows + - `_staged` (`EfInboxStore.cs:50`) is a plain `Dictionary>` of rows opened but not yet closed out. The comment justifies the non-concurrent collection - (`EfInboxStore.cs:44-48`): the store is scoped per consumed message, so it holds one entry in + (`EfInboxStore.cs:45-49`): the store is scoped per consumed message, so it holds one entry in practice and is never touched from two threads. - - `AlreadyProcessedAsync` (`EfInboxStore.cs:52-58`) resolves the context and issues a single + - `AlreadyProcessedAsync` (`EfInboxStore.cs:53-59`) resolves the context and issues a single `AnyAsync` for an [`InboxMessage`](#inboxmessage) with the given `MessageId` - (`EfInboxStore.cs:55-57`), which the unique index turns into an index seek. - - `TryBeginAsync` (`EfInboxStore.cs:61-68`) short-circuits to `false` when the message was already - processed (lines 63-64), otherwise stages a row into `_staged` and returns `true` (lines 66-67). - - `Stage` (`EfInboxStore.cs:116-127`) resolves the context and `Add`s a new - [`InboxMessage`](#inboxmessage) stamped `DateTime.UtcNow` (lines 120-125), with a scoped - `VSTHRD103` suppression noting that EF's `DbSet.Add` is intentionally synchronous because it is an - in-memory operation (lines 119 and 126). Note it returns the `EntityEntry`, which is the handle the - rest of the class reads state from. - - `CompleteAsync` (`EfInboxStore.cs:71-89`) removes the staged entry and inspects its state - (lines 73-78). `Added` still means no handler saved, so the row is persisted now (line 80); + (`EfInboxStore.cs:56-58`), which the unique index turns into an index seek. + - `TryBeginAsync` (`EfInboxStore.cs:62-69`) short-circuits to `false` when the message was already + processed (lines 64-65), otherwise stages a row into `_staged` and returns `true` (lines 67-68). + - `Stage` (`EfInboxStore.cs:117-128`) resolves the context and `Add`s a new + [`InboxMessage`](#inboxmessage) (lines 121-126) whose `ProcessedOn` comes from the injected clock, + `timeProvider.GetUtcNow().UtcDateTime` (line 125), rather than the ambient `DateTime.UtcNow`, so a + test can pin the stamp. A scoped `VSTHRD103` suppression notes that EF's `DbSet.Add` is + intentionally synchronous because it is an in-memory operation (lines 120 and 127). Note it + returns the `EntityEntry`, which is the handle the rest of the class reads state from. + - `CompleteAsync` (`EfInboxStore.cs:72-90`) removes the staged entry and inspects its state + (lines 74-79). `Added` still means no handler saved, so the row is persisted now (line 81); anything else means a handler's own `SaveChangesAsync` already committed it atomically with its - mutations, which is the whole point of staging, so there is nothing left to write (lines 75-77). + mutations, which is the whole point of staging, so there is nothing left to write (lines 76-78). When there is no staged entry at all (a caller that skipped `TryBeginAsync`, or a second `CompleteAsync`), it falls back to the stage-then-save path so the message is still recorded - (lines 86-88). - - `Abandon` (`EfInboxStore.cs:92-110`) is the failure branch. No staged row means nothing to undo, - return `true` (lines 94-95). A staged entry whose state is no longer `Added` means a handler + (lines 87-89). + - `Abandon` (`EfInboxStore.cs:93-111`) is the failure branch. No staged row means nothing to undo, + return `true` (lines 95-96). A staged entry whose state is no longer `Added` means a handler committed the row before a later handler failed: the store logs a Warning and returns `false` - (lines 97-103), and the comment says plainly that the redelivery will be skipped as a duplicate so + (lines 98-104), and the comment says plainly that the redelivery will be skipped as a duplicate so the handlers that had not run yet never will, which is the one case where this design loses work a pure after-the-fact inbox would have retried. Otherwise the entry is **detached** rather than left - `Added` (line 108), because the context is cached for the whole scope and a surviving `Added` row - would be re-attempted by any later save on that scope (lines 106-107). - - `MarkProcessedAsync` (`EfInboxStore.cs:113-114`) is now a thin stage-and-save, kept because it is + `Added` (line 109), because the context is cached for the whole scope and a surviving `Added` row + would be re-attempted by any later save on that scope (lines 107-108). + - `MarkProcessedAsync` (`EfInboxStore.cs:114-115`) is a thin stage-and-save, kept because it is the abstract member of the port. - - `SaveStagedAsync` (`EfInboxStore.cs:129-158`) saves and then handles the race. Its - `catch (DbUpdateException)` (line 140) does three things in order. First it **detaches the rejected - entry** (line 146), for the same scope-caching reason, and the comment names the identical idiom in + - `SaveStagedAsync` (`EfInboxStore.cs:130-159`) saves and then handles the race. Its + `catch (DbUpdateException)` (line 141) does three things in order. First it **detaches the rejected + entry** (line 147), for the same scope-caching reason, and the comment names the identical idiom in [`DomainEventSaveChangesInterceptor`](group-07-persistence-ef-core.md#domaineventsavechangesinterceptor) - (lines 142-145). Second it **re-queries** through `AlreadyProcessedAsync` and rethrows when the row - is still absent (lines 153-154): only a concurrent duplicate delivery tripping the unique index is + (lines 143-146). Second it **re-queries** through `AlreadyProcessedAsync` and rethrows when the row + is still absent (lines 154-155): only a concurrent duplicate delivery tripping the unique index is safe to absorb, and the comment is explicit that re-querying beats sniffing provider-specific error codes because the check must hold for SQL Server and SQLite alike, and that swallowing any other - write failure would ack a message whose inbox row was never written (lines 148-152). Third, and - only then, it logs the absorbed duplicate at Debug (line 156, source-generated at lines 166-167). - - `ResolveContext` (`EfInboxStore.cs:160-164`) routes to the configured outbox data source by + write failure would ack a message whose inbox row was never written (lines 149-153). Third, and + only then, it logs the absorbed duplicate at Debug (line 157, source-generated at lines 167-168). + - `ResolveContext` (`EfInboxStore.cs:161-165`) routes to the configured outbox data source by resolving `OutboxSettings.DataSource`/`DatabaseName` through - [`IDataSourceResolver`](group-07-persistence-ef-core.md#idatasourceresolver) (line 162) and asking - the factory for that context (line 163), so the inbox lands in the same database as the outbox. + [`IDataSourceResolver`](group-07-persistence-ef-core.md#idatasourceresolver) (line 163) and asking + the factory for that context (line 164), so the inbox lands in the same database as the outbox. - **Why it's built this way**: dedup by `MessageId` (the [`IDomainEvent`](#idomainevent) member introduced at Level 0) makes redelivery safe without distributed locks, and storing the row in the consumer's *own* database keeps it within the database-per-service boundary ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)) while @@ -1188,12 +1190,12 @@ edge) are the primary references. of work. - **Where it's used**: registered as the scoped `IInboxStore` whenever `MessageBusSettings.IsInboxEnabled` resolves true, which for a broker transport is the default - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:106-108`); driven by + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:107-109`); driven by [`IntegrationEventConsumer`](#integrationeventconsumertevent) around handler invocation (`IntegrationEventConsumer.cs:81,101,122`); its rows are purged by - [`OutboxCleanupService`](#outboxcleanupservice) (`OutboxCleanupService.cs:174-189`). Exercised + [`OutboxCleanupService`](#outboxcleanupservice) (`OutboxCleanupService.cs:169-184`). Exercised directly by `EfInboxStoreTests` - (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Inbox/EfInboxStoreTests.cs:27`). + (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Inbox/EfInboxStoreTests.cs:28`). - **Caveats / not-in-source**: the inbox key is the event's `[EventName]` identity when it declares one and its short type name otherwise, resolved by the caller, not by this store ([`EventNameResolver`](#eventnameresolver), called at `IntegrationEventConsumer.cs:70`). Whether a @@ -1203,7 +1205,7 @@ edge) are the primary references. `[Rubric §10, Messaging & Integration Architecture]` applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores. ### OutboxAdministration -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Outbox.Administration` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:35` · Level 13 · class (public sealed partial) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Outbox.Administration` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:32` · Level 16 · class (public sealed partial) - **What it is**: the EF-backed operator surface over the outbox tables this host owns. It lists dead letters, replays them back into the pending pool, and counts the pending backlog, over exactly the @@ -1211,11 +1213,9 @@ edge) are the primary references. [`OutboxCleanupService`](#outboxcleanupservice) sweeps. - **Depends on**: `IServiceScopeFactory`, `ILogger`, `IOptions<`[`OutboxSettings`](group-04-events-outbox.md#outboxsettings)`>`, - [`IEntityDataSourceRegistry`](group-07-persistence-ef-core.md#ientitydatasourceregistry), - [`IDataSourceResolver`](group-07-persistence-ef-core.md#idatasourceresolver), - [`IOutboxSignal`](#ioutboxsignal) and an optional - `IOptions<`[`TenancySettings`](group-07-persistence-ef-core.md#tenancysettings)`>`, all via - primary constructor (`OutboxAdministration.cs:35-42`); implements + [`FrameworkTableTargets`](group-07-persistence-ef-core.md#frameworktabletargets) and + [`IOutboxSignal`](#ioutboxsignal), all via primary constructor (`OutboxAdministration.cs:32-37`); + implements [`IOutboxAdministration`](group-07-persistence-ef-core.md#ioutboxadministration) and projects [`OutboxDeadLetter`](group-07-persistence-ef-core.md#outboxdeadletter); resolves an [`IDbContextFactory`](group-07-persistence-ef-core.md#idbcontextfactory) and, for a tenant target, an @@ -1236,46 +1236,50 @@ edge) are the primary references. operator decides about a replay depends on reading it (`IOutboxAdministration.cs:68-72`), a `[Rubric §30, Compliance, Privacy & Data Governance]` choice. - **Walkthrough** - - **Guards and paging.** `MaxPageSize` is 500 (`OutboxAdministration.cs:45`), so an admin call cannot + - **Guards and paging.** `MaxPageSize` is 500 (`OutboxAdministration.cs:40`), so an admin call cannot ask for the whole table at once, and the two validation errors are preallocated `Error.Validation` - values (lines 47-51). - - `ListDeadLettersAsync` (`OutboxAdministration.cs:56-112`) validates `skip` and `take` (lines 62-66), + values (lines 42-46). + - `ListDeadLettersAsync` (`OutboxAdministration.cs:51-107`) validates `skip` and `take` (lines 57-61), resolves its targets and fails with a `NotFound`-shaped error when a named source is not owned by - this host (lines 68-70, 197-200), then queries each target for unprocessed rows whose `RetryCount` + this host (lines 63-65, 192-195), then queries each target for unprocessed rows whose `RetryCount` has reached `MaxRetries`, ordered by `OccurredOn` then `Id`, projected straight into - `OutboxDeadLetter` under `AsNoTracking` (lines 86-100). Two details are commented in place: the + `OutboxDeadLetter` under `AsNoTracking` (lines 81-95). Two details are commented in place: the source name is materialized *outside* the query because inside the projection it would be a method - call EF has to translate (lines 77-79), and each target returns at most `skip + take` rows because + call EF has to translate (lines 72-74), and each target returns at most `skip + take` rows because **paging is applied across the merged result**, so "skip 50" means the same thing whether the host - owns one database or four (lines 81-83, merged at lines 106-109). - - `ReplayDeadLettersAsync` (`OutboxAdministration.cs:115-171`) is expressed as one set-based + owns one database or four (lines 76-78, merged at lines 101-104). + - `ReplayDeadLettersAsync` (`OutboxAdministration.cs:110-166`) is expressed as one set-based `ExecuteUpdateAsync` per target rather than as loaded entities, because an operator replaying a backlog is replaying thousands of rows and none of the values written depend on the row's current - state (class doc, lines 21-25). The update resets `RetryCount` to zero, which is what returns the + state (class doc, lines 22-24). The update resets `RetryCount` to zero, which is what returns the row to the poll's predicate, and clears `LockedUntil` and `LockToken` so it is claimable on the very - next cycle instead of after `LeaseSeconds` (lines 146-151). `LastError` survives on purpose: the + next cycle instead of after `LeaseSeconds` (lines 141-146). `LastError` survives on purpose: the comment calls it the record of *why* this row needed replaying, and a replay that erased it would - destroy the only evidence (lines 142-145). An optional id filter narrows the scope (lines 137-140), - each non-empty target logs at Warning (lines 155-158, `LogReplayed` at 247-248), and when anything + destroy the only evidence (lines 137-140). An optional id filter narrows the scope (lines 132-135), + each non-empty target logs at Warning (lines 150-153, `LogReplayed` at 223-224), and when anything was replayed it calls [`IOutboxSignal.Signal()`](#ioutboxsignal) rather than leaving the work to a - polling interval deployed environments set as high as 300 seconds (lines 163-168). - - `CountPendingAsync` (`OutboxAdministration.cs:174-195`) sums `LongCountAsync` over unprocessed rows + polling interval deployed environments set as high as 300 seconds (lines 158-163). + - `CountPendingAsync` (`OutboxAdministration.cs:169-190`) sums `LongCountAsync` over unprocessed rows with `RetryCount < MaxRetries` across every selected target. Its interface doc draws the line against the gauge (`IOutboxAdministration.cs:56-61`): this counts the tables at the moment of the call and **includes** rows currently under a claim lease, where `outbox.pending.depth` reports what the processor last observed. - - **Target selection and scoping.** `SelectTargets` (`OutboxAdministration.cs:207-219`) now delegates - the same set the two background services use to a single shared helper, - [`TenantDataSourceTargets.ExpandRelational`](group-07-persistence-ef-core.md#tenantdatasourcetargets) - (every relational physical source in use, minus Cosmos, plus the configured publish target, expanded - per tenant), rather than assembling the source list inline, and optionally filters to one name - case-insensitively (lines 216-218). It is recomputed per call for the same reason the processor - recomputes it per cycle: module assemblies can register entities after startup (doc, lines 202-205). - `VisitAsync` (lines 226-237) runs the work for one target in its **own** DI scope, created through - the same shared `scopeFactory.CreateTenantScope(target)` extension + - **Target selection and scoping.** `SelectTargets` (`OutboxAdministration.cs:202-203`) is a one-line + call to the injected + [`FrameworkTableTargets.Named`](group-07-persistence-ef-core.md#frameworktabletargets), passing the + outbox's own `DataSource`/`DatabaseName` and the optional operator-supplied name. That shared type + answers the target question once for every framework-table sweeper: every relational physical + source in use (Cosmos skipped), plus the configured publish target when its engine is relational, + expanded per tenant that keeps its own copy + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/FrameworkTableTargets.cs:54-64`), + then optionally narrowed to one target matched case-insensitively against its display name + (`FrameworkTableTargets.cs:75-82`). It is recomputed per call for the same reason the processor + recomputes it per cycle: module assemblies can register entities after startup (doc, lines 197-201). + `VisitAsync` (lines 210-221) runs the work for one target in its **own** DI scope, created through + the shared `scopeFactory.CreateTenantScope(target)` extension ([`TenantDataSourceTargets`](group-07-persistence-ef-core.md#tenantdatasourcetargets)) that sets the tenant *before* the context is asked for, because the tenant is what routes the scoped factory to - that tenant's database and is also what the query filter reads (line 233). + that tenant's database and is also what the query filter reads (line 217). - **Why it's built this way**: reusing the processor's exact target expansion means an operator screen can never show a different set of databases than the one being drained, including per-tenant copies ([ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html)). Replay is intentionally @@ -1285,15 +1289,16 @@ edge) are the primary references. - **Where it's used**: registered scoped as [`IOutboxAdministration`](group-07-persistence-ef-core.md#ioutboxadministration) by `AddInfrastructure` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:203-206`), with the + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:227-230`), with the comment explaining the lifetime: scoped, because it creates one child scope per data source it visits - and holds no state of its own. The framework ships no endpoint for it; a host exposes it from an admin + and holds no state of its own. Its `FrameworkTableTargets` dependency is registered with + `TryAddSingleton` (`DependencyInjection.cs:96`). The framework ships no endpoint for it; a host exposes it from an admin endpoint, a support command or a scheduled job (`IOutboxAdministration.cs:10-14`). `[Rubric §10, Messaging & Integration Architecture]` applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores. ### OutboxCleanupService -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Outbox.Administration` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxCleanupService.cs:45` · Level 13 · class (public sealed partial, `PeriodicBackgroundService`) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Outbox.Administration` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxCleanupService.cs:42` · Level 16 · class (public sealed partial, `PeriodicBackgroundService`) - **What it is**: the periodic sweeper that purges spent outbox rows (both **processed** rows and **dead-lettered** rows whose retries are exhausted) and, when the inbox is enabled, inbox rows, past @@ -1302,19 +1307,17 @@ edge) are the primary references. - **Depends on**: `IServiceScopeFactory`, `ILogger`, `IOptions<`[`OutboxSettings`](group-04-events-outbox.md#outboxsettings)`>`, `IOptions<`[`MessageBusSettings`](group-14-module-system-composition.md#messagebussettings)`>`, - [`IEntityDataSourceRegistry`](group-07-persistence-ef-core.md#ientitydatasourceregistry), - [`IDataSourceResolver`](group-07-persistence-ef-core.md#idatasourceresolver), a required + [`FrameworkTableTargets`](group-07-persistence-ef-core.md#frameworktabletargets) (which decides the + databases, including per-tenant copies, that hold the tables it sweeps) and a required `TimeProvider` (forwarded straight to the [`PeriodicBackgroundService`](group-14-module-system-composition.md#periodicbackgroundservice) base - it now extends) and an optional - `IOptions<`[`TenancySettings`](group-07-persistence-ef-core.md#tenancysettings)`>` - (`OutboxCleanupService.cs:45-54`); resolves an + it extends) (`OutboxCleanupService.cs:42-49`); resolves an [`IDbContextFactory`](group-07-persistence-ef-core.md#idbcontextfactory) and, for a tenant target, an [`ITenantContext`](group-05-cqrs-pipeline.md#itenantcontext) per sweep, both through the shared `scopeFactory.CreateTenantScope(target)` helper - ([`TenantDataSourceTargets`](group-07-persistence-ef-core.md#tenantdatasourcetargets)); expands its - work list through the same type into - [`TenantDataSourceTarget`](group-07-persistence-ef-core.md#tenantdatasourcetarget) values; operates on + ([`TenantDataSourceTargets`](group-07-persistence-ef-core.md#tenantdatasourcetargets)); its work list + is a list of [`TenantDataSourceTarget`](group-07-persistence-ef-core.md#tenantdatasourcetarget) + values; operates on the [`OutboxMessage`](#outboxmessage) and [`InboxMessage`](#inboxmessage) entities. - **Concept introduced, retention as a privacy and storage control (plus a clock injection point).** `[Rubric §30, Compliance, Privacy & Data Governance]` assesses bounded retention of data that may @@ -1323,54 +1326,60 @@ edge) are the primary references. whether time-driven code can be tested. The [`OutboxProcessor`](#outboxprocessor) only ever *sets* `ProcessedOn`, and a message that exhausts `MaxRetries` keeps `ProcessedOn` null forever, so without this sweep the outbox, which stores serialized event payloads that may contain personal data, grows - without bound and dead rows linger in the pending index every poll re-scans (class doc, lines 16-24, - citing ADR-003 and ADR-005). The `TimeProvider` is now a required constructor parameter rather than an + without bound and dead rows linger in the pending index every poll re-scans (class doc, lines 15-30, + citing ADR-003 and ADR-005 at line 21). The `TimeProvider` is now a required constructor parameter rather than an optional one defaulting to `TimeProvider.System`, because the base [`PeriodicBackgroundService`](group-14-module-system-composition.md#periodicbackgroundservice) owns the clock all its waits go through; a private `_timeProvider` field re-reads the same value under this type's own name only because capturing the base constructor's own parameter into this type's state - would be a compiler error, CS9107 (comment, `OutboxCleanupService.cs:59-60`). A test can still drive + would be a compiler error, CS9107 (comment, `OutboxCleanupService.cs:54-55`). A test can still drive the hour-scale sweep loop deterministically instead of waiting real hours, now through the base's injected clock. - **Walkthrough** - The fixed-interval loop itself (delay, cycle, retry-on-failure, log-and-continue) moved to the shared [`PeriodicBackgroundService`](group-14-module-system-composition.md#periodicbackgroundservice) base; this type now supplies only the four hooks the base calls. `Interval` returns - `CleanupIntervalHours` hours (`OutboxCleanupService.cs:64`, default 6, `OutboxSettings.cs:73`) and - `StartupDelay` reuses it verbatim (lines 66-70), so the first sweep still waits one full interval + `CleanupIntervalHours` hours (`OutboxCleanupService.cs:59`, default 6, `OutboxSettings.cs:73`) and + `StartupDelay` reuses it verbatim (lines 61-65), so the first sweep still waits one full interval before running, which keeps cleanup off the critical path of startup or migration work. `IsEnabled` - (lines 72-85) is the documented off switch: it returns `false`, and logs, when `RetentionDays <= 0` - (line 77). `ExecuteCycleAsync` delegates straight to `PurgeAsync` (line 88), and `LogCycleFailure` - overrides the base's generic message with this service's own event id (line 91, `LogCleanupError` - at 217-218). - - `PurgeAsync` (`OutboxCleanupService.cs:93-135`) computes the cutoff from - `_timeProvider.GetUtcNow().UtcDateTime` minus `RetentionDays` (line 95, default 7, - `OutboxSettings.cs:65`), then walks `GetRelationalTargets()` (line 97). For each target it opens a - scope through the shared `scopeFactory.CreateTenantScope(target)` helper (line 103), which sets the + (lines 67-80) is the documented off switch: it returns `false`, and logs, when `RetentionDays <= 0` + (line 72). `ExecuteCycleAsync` delegates straight to `PurgeAsync` (line 83), and `LogCycleFailure` + overrides the base's generic message with this service's own event id (line 86, `LogCleanupError` + at 207-208). + - `PurgeAsync` (`OutboxCleanupService.cs:88-130`) computes the cutoff from + `_timeProvider.GetUtcNow().UtcDateTime` minus `RetentionDays` (line 90, default 7, + `OutboxSettings.cs:65`), then walks `GetRelationalTargets()` (line 92). For each target it opens a + scope through the shared `scopeFactory.CreateTenantScope(target)` helper (line 98), which sets the tenant on the scope **before** the context is asked for, because the tenant is what routes the scoped factory to that tenant's database. It then deletes processed rows older than the cutoff with - `ExecuteDeleteAsync`, a set-based SQL `DELETE` with no entity materialization (lines 108-111), - logging at Information when anything went (lines 113-116). - - **The dead-letter sweep** (`SweepDeadLettersAsync`, `OutboxCleanupService.cs:150-172`) is the - second, separate pass, and its doc is worth reading in full (lines 137-149): dead-lettered rows keep + `ExecuteDeleteAsync`, a set-based SQL `DELETE` with no entity materialization (lines 103-106), + logging at Information when anything went (lines 108-111). + - **The dead-letter sweep** (`SweepDeadLettersAsync`, `OutboxCleanupService.cs:145-167`) is the + second, separate pass, and its doc is worth reading in full (lines 132-144): dead-lettered rows keep `ProcessedOn` null forever, so the processor's poll excludes them (`RetryCount < MaxRetries`) but the processed sweep never reaches them either, and they accumulate *inside* the pending index. They are purged on their own window, `DeadLetterRetentionDays` falling back to `RetentionDays` when it is 0 - (lines 155-157, and 0 is the default, `OutboxSettings.cs:108`), keyed on `OccurredOn` since they - have no `ProcessedOn` (lines 161-164). This permanently abandons an undelivered event, which is why - the deletion logs at **Warning** (line 170, `LogDeadLetterPurged` at 211-212) while the processed - purge logs at Information (`LogPurged` at 208-209), and why the doc points at + (lines 150-152, and 0 is the default, `OutboxSettings.cs:108`), keyed on `OccurredOn` since they + have no `ProcessedOn` (lines 156-159). This permanently abandons an undelivered event, which is why + the deletion logs at **Warning** (line 165, `LogDeadLetterPurged` at 201-202) while the processed + purge logs at Information (`LogPurged` at 198-199), and why the doc points at [`OutboxAdministration`](#outboxadministration) as the thing to use before the window closes. - - Inbox rows are purged only when the inbox is enabled (lines 120-123, the flag captured once at - construction from `MessageBusSettings.IsInboxEnabled`, line 57), delegating to `PurgeInboxAsync` - (lines 174-189), which deletes [`InboxMessage`](#inboxmessage) rows with `ProcessedOn < cutoff`. + - Inbox rows are purged only when the inbox is enabled (lines 115-118, the flag captured once at + construction from `MessageBusSettings.IsInboxEnabled`, line 52), delegating to `PurgeInboxAsync` + (lines 169-184), which deletes [`InboxMessage`](#inboxmessage) rows with `ProcessedOn < cutoff`. - A single unreachable database does not stop the others: the per-target `catch` logs and moves on - (lines 129-133), while a real cancellation is rethrown (lines 125-128). `GetRelationalTargets` - (lines 197-203) now delegates target expansion to - [`TenantDataSourceTargets.ExpandRelational`](group-07-persistence-ef-core.md#tenantdatasourcetargets) - (the same relational sources the processor drains against the shared database, plus one extra per - tenant that keeps its own copy), which is the only reason a per-tenant database's outbox and inbox - tables ever get swept ([ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html)). + (lines 124-128), while a real cancellation is rethrown (lines 120-123). `GetRelationalTargets` + (lines 192-193) is a one-line call to the injected + [`FrameworkTableTargets.Relational`](group-07-persistence-ef-core.md#frameworktabletargets) with the + outbox's own `DataSource`/`DatabaseName`: every relational source backing a registered entity plus + the configured publish target, against the shared database, plus one extra unit per tenant that + keeps its own copy + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/FrameworkTableTargets.cs:54-64`). + That per-tenant expansion is the only reason a per-tenant database's outbox and inbox tables ever + get swept ([ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html)), and because + the processor, both operator surfaces and the other cleanup services ask the same type, the sweep + cannot visit a different set of databases than the one being drained + (`FrameworkTableTargets.cs:16-18`). - **Why it's built this way**: bounded retention keeps both storage cost and PII exposure in check; doing it as a `DELETE` rather than load-then-remove is the efficient path; and per-target error isolation keeps one bad database from blocking the sweep. @@ -1378,11 +1387,11 @@ edge) are the primary references. this same sweep, gated on the inbox flag, rather than adding a second housekeeping service. - **Where it's used**: registered as a hosted service alongside the [`OutboxProcessor`](#outboxprocessor), inside the same `IsOutboxEnabled` gate - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:207-211`), so a host with + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:217-221`), so a host with the outbox disabled runs neither and gets [`OutboxDisabledNoticeService`](#outboxdisablednoticeservice) instead. - **Caveats / not-in-source**: the inbox purge uses the *outbox* `RetentionDays` cutoff - (`OutboxCleanupService.cs:122`), not a separate inbox window, so shortening outbox retention shortens + (`OutboxCleanupService.cs:117`), not a separate inbox window, so shortening outbox retention shortens the dedup memory with it. `[Rubric §10, Messaging & Integration Architecture]` applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores. @@ -1416,7 +1425,7 @@ edge) are the primary references. injection point, and it keeps the `SemaphoreSlim` detail (including its one-permit cap) out of every call site. - **Where it's used**: registered as a singleton by `AddInfrastructure` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:195`). `Signal()` is + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:205`). `Signal()` is called by [`DomainEventSaveChangesInterceptor`](group-07-persistence-ef-core.md#domaineventsavechangesinterceptor) on all three of its paths @@ -1424,9 +1433,9 @@ edge) are the primary references. by [`BrokerEventBus`](#brokereventbus) after writing its outbox batch (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Messaging/BrokerEventBus.cs:94`), and by [`OutboxAdministration`](#outboxadministration) after a replay - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:167`). + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:162`). `WaitAsync` is awaited by the [`OutboxProcessor`](#outboxprocessor) loop - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:157`), + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:150`), with the duration computed from [`OutboxCycleResult`](#outboxcycleresult). `[Rubric §10, Messaging & Integration Architecture]` applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores. @@ -1457,10 +1466,10 @@ edge) are the primary references. the Infrastructure layer where the only two participants live. - **Where it's used**: returned by `OutboxProcessor.ProcessPendingMessagesAsync` after aggregating the per-target results - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:174`), - produced per source by `ProcessSourceAsync` (`OutboxProcessor.cs:220,229,261-265`), and consumed by + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:167`), + produced per source by `ProcessSourceAsync` (`OutboxProcessor.cs:211,220,250-254`), and consumed by `ExecuteAsync` to either continue immediately (`OutboxProcessor.cs:143-147`) or wait for the - duration `ComputeWaitTime` derives from it (`OutboxProcessor.cs:152-157`). + duration `ComputeWaitTime` derives from it (`OutboxProcessor.cs:145-150`). `[Rubric §10, Messaging & Integration Architecture]` applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores. @@ -1523,16 +1532,16 @@ edge) are the primary references. lock-free while the processor writes them from its own loop. Making the depth a *gauge fed by the cycle* rather than an independent query means the steady state pays no extra database round-trip: see `CountPendingAsync`, which derives the depth from the fetch itself unless the batch came back - saturated (`OutboxProcessor.cs:276-297`). + saturated (`OutboxProcessor.cs:265-286`). - **Where it's used**: `DeadLetterCounter` on both dead-letter paths (`OutboxProcessor.cs:667-670` for an unresolvable type, `:712-715` for exhausted retries); `ProcessedCounter` and - `DispatchLagHistogram` on the success path (`OutboxProcessor.cs:562,567-569`); `SetOldestPendingAge` - per source right after its fetch (`OutboxProcessor.cs:204-206`); and `SetPendingDepth` once per - cycle after every target has been drained (`OutboxProcessor.cs:172`). + `DispatchLagHistogram` on the success path (`OutboxProcessor.cs:557,562-564`); `SetOldestPendingAge` + per source right after its fetch (`OutboxProcessor.cs:195-197`); and `SetPendingDepth` once per + cycle after every target has been drained (`OutboxProcessor.cs:165`). - **Caveats / not-in-source**: the circuit-open signal the processor emits alongside these lives on a *different* meter, [`BrokerMetrics.CircuitOpenCounter`](group-14-module-system-composition.md#brokermetrics), not on - `OutboxMetrics` (`OutboxProcessor.cs:606-608`). + `OutboxMetrics` (`OutboxProcessor.cs:601-603`). `[Rubric §10, Messaging & Integration Architecture]` applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores. @@ -1629,7 +1638,7 @@ edge) are the primary references. calls `DispatchAsync` for the immediate in-process reactions (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/DomainEventSaveChangesInterceptor.cs:337`); the background [`OutboxProcessor`](#outboxprocessor) routes non-integration events through it - (`OutboxProcessor.cs:554`), as do [`InProcessMessageBus`](#inprocessmessagebus) + (`OutboxProcessor.cs:549`), as do [`InProcessMessageBus`](#inprocessmessagebus) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Messaging/InProcessMessageBus.cs:25,32`) and [`InProcessEventBus`](#inprocesseventbus) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Messaging/InProcessEventBus.cs:83,100`). @@ -1693,7 +1702,7 @@ edge) are the primary references. the "at-least-once is fine, duplicates are absorbed" instinct that runs through this whole group in one place rather than two. - **Where it's used**: registered as the singleton `IOutboxSignal` by `AddInfrastructure` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:195`). Its callers are + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:205`). Its callers are listed under [`IOutboxSignal`](#ioutboxsignal). Note the registration is unconditional, above the outbox gate, so the producers can signal without checking whether a processor exists. @@ -1911,8 +1920,8 @@ edge) are the primary references. attempts (default 5, [`OutboxSettings`](group-04-events-outbox.md#outboxsettings), `MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxSettings.cs:21`; the retry-count check that stops - fetching an exhausted row is `OutboxProcessor.cs:291` and the dead-letter branch is - `OutboxProcessor.cs:615`). `[Rubric §13, Observability & Operability]`: the one job the base class + fetching an exhausted row is `OutboxProcessor.cs:280` and the dead-letter branch is + `OutboxProcessor.cs:610`). `[Rubric §13, Observability & Operability]`: the one job the base class keeps is the error line naming the concrete handler and the event type, so an operator can tell which handler failed for which event without every subclass hand-rolling that context. - **Concept introduced, batch redelivery.** The consequence subclasses have to design for is in the @@ -2055,7 +2064,7 @@ edge) are the primary references. ADC's `UserRegisteredHandler` (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Users/IntegrationEventHandlers/UserRegisteredHandler.cs:57`), `SpeakerLinkedToUserHandler` - (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Speakers/IntegrationEventHandlers/SpeakerLinkedToUserHandler.cs:30`), + (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Speakers/IntegrationEventHandlers/SpeakerLinkedToUserHandler.cs:35`), `SpeakerUnlinkedFromUserHandler` (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Speakers/IntegrationEventHandlers/SpeakerUnlinkedFromUserHandler.cs:30`), the Engagement points handlers (`AttendeeCheckedInPointsHandler`, @@ -2111,7 +2120,7 @@ edge) are the primary references. [ADR-003](https://ivanball.github.io/docs/adr/003-outbox-dual-dispatch.html)'s in-process dispatch stays cheap; the durability net is the background [`OutboxProcessor`](#outboxprocessor), which deliberately does **not** use this helper (it stamps `ProcessedOn` on tracked rows and issues one - ordinary `SaveChangesAsync` per source, `OutboxProcessor.cs:257`, because it must persist + ordinary `SaveChangesAsync` per source, `OutboxProcessor.cs:246`, because it must persist `RetryCount`, `LastError` and lease changes in the same save). - **Where it's used**: called by [`DomainEventSaveChangesInterceptor`](group-07-persistence-ef-core.md#domaineventsavechangesinterceptor) @@ -2122,7 +2131,7 @@ edge) are the primary references. `[Rubric §10, Messaging & Integration Architecture]` applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores. ### OutboxProcessor -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Outbox.Processing` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:56` · Level 13 · class (public sealed partial, `BackgroundService`) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Outbox.Processing` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:53` · Level 16 · class (public sealed partial, `BackgroundService`) - **What it is**: the background service that drains every outbox table the host owns, claims rows under a lease, and dispatches the [`OutboxMessage`](#outboxmessage)s. It is the engine of at-least-once @@ -2131,20 +2140,20 @@ edge) are the primary references. - **Depends on**: `IServiceScopeFactory`, `ILogger`, `IOptions<`[`OutboxSettings`](group-04-events-outbox.md#outboxsettings)`>`, [`IOutboxSignal`](#ioutboxsignal), - [`IEntityDataSourceRegistry`](group-07-persistence-ef-core.md#ientitydatasourceregistry), - [`IDataSourceResolver`](group-07-persistence-ef-core.md#idatasourceresolver), a required - `TimeProvider` (resolved from the `TryAddSingleton(TimeProvider.System)` registration in - `AddInfrastructure`, `DependencyInjection.cs:313`, rather than defaulted in the constructor) and an - optional `IOptions<`[`TenancySettings`](group-07-persistence-ef-core.md#tenancysettings)`>` - (`OutboxProcessor.cs:56-64`); the shared `PollingLoop` + [`FrameworkTableTargets`](group-07-persistence-ef-core.md#frameworktabletargets) (registered once as a + singleton by `AddInfrastructure`, `DependencyInjection.cs:96`) and a required `TimeProvider` (resolved + from the `TryAddSingleton(TimeProvider.System)` registration, `DependencyInjection.cs:323`, rather than + defaulted in the constructor) (`OutboxProcessor.cs:53-59`); the shared `PollingLoop` (`MMCA.Common.Infrastructure.Persistence.Polling`), the loop, smart-wait and per-source drain engine - this processor and `InternalCommandProcessor` both run on; per scope - [`IDbContextFactory`](group-07-persistence-ef-core.md#idbcontextfactory), - [`IDomainEventDispatcher`](#idomaineventdispatcher), [`IMessageBus`](#imessagebus) and, for a tenant - target, [`ITenantContext`](group-05-cqrs-pipeline.md#itenantcontext) (lines 275-283); the - [`OutboxMessage`](#outboxmessage) entity, [`OutboxMetrics`](#outboxmetrics), - [`OutboxCycleResult`](#outboxcycleresult), - [`TenantDataSourceTargets`](group-07-persistence-ef-core.md#tenantdatasourcetargets), + this processor and `InternalCommandProcessor` both run on; per cycle scope + [`IDbContextFactory`](group-07-persistence-ef-core.md#idbcontextfactory) (line 183), and per row scope + [`IMessageBus`](#imessagebus) or [`IDomainEventDispatcher`](#idomaineventdispatcher) (lines 534, 548), + both scopes opened through `CreateTenantScope` + ([`TenantDataSourceTargets`](group-07-persistence-ef-core.md#tenantdatasourcetargets), + `TenantDataSourceTargets.cs:92`); the [`OutboxMessage`](#outboxmessage) entity, + [`OutboxMetrics`](#outboxmetrics), [`OutboxCycleResult`](#outboxcycleresult), + [`TenantDataSourceTarget`](group-07-persistence-ef-core.md#tenantdatasourcetarget), + [`ColumnWidth`](group-07-persistence-ef-core.md#columnwidth), [`BrokerResilienceDefaults`](group-16-aspire-orchestration.md#brokerresiliencedefaults), [`BrokerMetrics`](group-14-module-system-composition.md#brokermetrics) and [`AmbientOrigin`](group-14-module-system-composition.md#ambientorigin); externally Polly @@ -2158,192 +2167,204 @@ edge) are the primary references. claim lease expires, not immediately on restart, because the claim is persisted before dispatch and the poll skips leased rows. Take the rest a layer at a time. - **Walkthrough** - - **The loop, extracted.** `ExecuteAsync` (`OutboxProcessor.cs:108-122`) no longer contains the loop - body: it is a one-expression call into `PollingLoop.RunAsync` - (`MMCA.Common.Infrastructure.Persistence.Polling.PollingLoop.cs:40-91`), passing `_timeProvider`, + - **The loop, extracted.** `ExecuteAsync` (`OutboxProcessor.cs:106-120`) contains no loop body: it is + a one-expression call into `PollingLoop.RunAsync` + (`Persistence/Polling/PollingLoop.cs:40-91`), passing `_timeProvider`, whether this host owns any target (`GetOutboxTargets().Count > 0`), `LogOutboxDisabled` for the no-target case, a cycle delegate that calls `ProcessPendingMessagesAsync` and projects its result to `(HasMoreWork, EarliestUpcoming)`, `LogProcessingError`, the configured delay and interval seconds, and `outboxSignal.WaitAsync` itself as the wait function (`MMCA.Common.Infrastructure.Persistence.Polling.PollingLoop`, - `Persistence/Polling/PollingLoop.cs:40-91`). `PollingLoop.RunAsync` runs the mechanics this section - used to describe inline: it waits `StartupDelay` (a shared 5-second constant, `PollingLoop.cs:18`, - matching the internal command processor's own delay) so the application finishes - initializing, returns immediately when `hasTargets()` is false, and otherwise loops until cancelled, - treating a cancellation as a clean stop and any other cycle exception as a reported, non-fatal error - (`PollingLoop.cs:51-74`). A cycle with more work re-polls immediately; otherwise it awaits the - injected `waitForSignal` for whichever comes first of a signal, the **smart wait**, or the fallback - interval (`PollingLoop.cs:76-90`). The remark on `ExecuteAsync` says this in one line - (`OutboxProcessor.cs:107`): the loop, startup delay and smart wait are all the shared `PollingLoop` - now. - - **The smart wait.** `OutboxProcessor.ComputeWaitTime` (`OutboxProcessor.cs:129-134`) is a thin + `Persistence/Polling/PollingLoop.cs:40-91`). `PollingLoop.RunAsync` runs the mechanics: it waits + `StartupDelay` (a shared 5-second constant, `PollingLoop.cs:18`, matching the internal command + processor's own delay) so the application finishes initializing, returns immediately when + `hasTargets()` is false, and otherwise loops until cancelled, treating a cancellation as a clean stop + and any other cycle exception as a reported, non-fatal error (`PollingLoop.cs:51-74`). A cycle with + more work re-polls immediately; otherwise it awaits the injected `waitForSignal` for whichever comes + first of a signal, the **smart wait**, or the fallback interval (`PollingLoop.cs:76-90`). The remark + on `ExecuteAsync` says this in one line (`OutboxProcessor.cs:105`): the loop, startup delay and smart + wait are the shared `PollingLoop`. + - **The smart wait.** `OutboxProcessor.ComputeWaitTime` (`OutboxProcessor.cs:127-132`) is a thin forward to `PollingLoop.ComputeWaitTime` (`PollingLoop.cs:104-122`), kept as a named static method - only so existing unit tests keep calling it as before. It returns the full polling interval when - nothing is upcoming; otherwise it waits until the earliest upcoming row becomes ready, its - `OccurredOn` plus `ProcessingDelaySeconds` (delay default 5, `OutboxSettings.cs:40`), floored at - `MinimumWait` of 1 second so an overdue row cannot hot-loop the processor (`PollingLoop.cs:21`) and - capped at the polling interval. Its doc keeps the same subtle rule (`PollingLoop.cs:96-97`): - failed-but-already-ready rows never shorten the wait, which throttles a permanently failing message - instead of letting it drive the loop. This is why a deployed host can set a long poll interval - without adding latency: real messages wake it by signal or smart wait, and the slow fallback only - cuts idle database chatter and telemetry cost. - - **Which databases.** `GetOutboxTargets` (`OutboxProcessor.cs:143-149`) no longer computes the owned - sources itself; it forwards straight to - [`TenantDataSourceTargets.ExpandRelational`](group-07-persistence-ef-core.md#tenantdatasourcetargets), - passing `entityDataSourceRegistry`, `dataSourceResolver`, `_settings.DataSource`, - `_settings.DatabaseName` and `tenancyOptions?.Value`. The method's own doc still states the same - contract this section previously split across two methods (`OutboxProcessor.cs:136-142`): every - relational source this host owns (every source backing a registered entity plus the configured - publish target; Cosmos has no outbox table) against the shared database, plus one extra unit per - tenant that keeps its own copy of a source, because a tenant database has its own `OutboxMessages` - table that nothing else would drain + so existing unit tests keep calling it. It returns the full polling interval when nothing is + upcoming; otherwise it waits until the earliest upcoming row becomes ready, its `OccurredOn` plus + `ProcessingDelaySeconds` (delay default 5, `OutboxSettings.cs:40`), floored at `MinimumWait` of 1 + second so an overdue row cannot hot-loop the processor (`PollingLoop.cs:21`) and capped at the + polling interval. Its doc keeps the subtle rule (`PollingLoop.cs:96-97`): failed-but-already-ready + rows never shorten the wait, which throttles a permanently failing message instead of letting it + drive the loop. This is why a deployed host can set a long poll interval without adding latency: + real messages wake it by signal or smart wait, and the slow fallback only cuts idle database chatter + and telemetry cost. + - **Which databases.** `GetOutboxTargets` (`OutboxProcessor.cs:141-142`) asks the injected + [`FrameworkTableTargets`](group-07-persistence-ef-core.md#frameworktabletargets) one question, + `tableTargets.Relational(_settings.DataSource, _settings.DatabaseName)`, the overload that adds the + caller's configured home source to every relational source in use and then expands per tenant + (`FrameworkTableTargets.cs:54-64`). That type exists so the outbox processor, the internal-command + processor, both cleanup services, both operator surfaces and the audit-trail sweep all share one + answer rather than each re-deriving it from the registry, the resolver and the tenancy settings + (`FrameworkTableTargets.cs:16-18`). The method's own doc states the contract + (`OutboxProcessor.cs:134-140`): every relational source this host owns (every source backing a + registered entity plus the configured publish target; Cosmos has no outbox table) against the shared + database, plus one extra unit per tenant that keeps its own copy of a source, because a tenant + database has its own `OutboxMessages` table that nothing else would drain ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html); - [ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html)). A host therefore still - only touches *its own* databases, never racing another service for its rows; the source-enumeration - step this used to name as `GetOutboxSources` now lives inside `ExpandRelational` itself. - - **Aggregating a cycle.** `ProcessPendingMessagesAsync` (`OutboxProcessor.cs:157-175`) also delegates, - to `PollingLoop.DrainAllAsync` (`PollingLoop.cs:135-171`), passing `GetOutboxTargets()`, a delegate - that calls `ProcessSourceAsync` per target and projects its tuple to + [ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html)). A host therefore only + touches *its own* databases, never racing another service for its rows. + - **Aggregating a cycle.** `ProcessPendingMessagesAsync` (`OutboxProcessor.cs:150-168`) delegates to + `PollingLoop.DrainAllAsync` (`PollingLoop.cs:135-171`), passing `GetOutboxTargets()`, a delegate that + calls `ProcessSourceAsync` per target and projects its tuple to `(HasMoreWork, EarliestUpcoming, PendingDepth)`, and `LogSourceProcessingError`. `DrainAllAsync` ORs the `HasMoreWork` flags, keeps the earliest upcoming timestamp across all targets, and sums the observed backlog; one unreachable database must not starve the others, so a per-target failure is reported and skipped while a real cancellation propagates (`PollingLoop.cs:145-167`). Back in `ProcessPendingMessagesAsync`, the summed depth is published through - [`OutboxMetrics.SetPendingDepth`](#outboxmetrics) (`OutboxProcessor.cs:172`), so a target that threw + [`OutboxMetrics.SetPendingDepth`](#outboxmetrics) (`OutboxProcessor.cs:165`), so a target that threw contributes zero and an outage reads as a drop rather than a stale plateau (comment, - `OutboxProcessor.cs:170-171`). - - **Draining one target.** `ProcessSourceAsync` (`OutboxProcessor.cs:181-266`) now opens its scope - through `scopeFactory.CreateTenantScope(target)` (`OutboxProcessor.cs:188`) in one call, rather than - calling `CreateScope()` and then separately resolving `ITenantContext.SetTenant` when the target had - a tenant; the comment on the call site (line 187) notes the tenant is set before the context is asked - for, which is what `CreateTenantScope` now guarantees internally. It gets the context for that source - and resolves the dispatcher and message bus. It fetches a candidate - batch (line 288), derives the backlog depth (line 289) and publishes the oldest-pending age - from the batch's own first row (lines 294-296). Then it splits the ordered batch: the eligible - prefix is everything with `OccurredOn` before the `ProcessingDelaySeconds` cutoff (lines 286, - 300-304), and the first row past it becomes `earliestPending` (line 306). Nothing eligible means an - early return carrying only the wait information (lines 308-310). Otherwise it **claims** the prefix - (line 313), returns early if another replica claimed all of it between fetch and claim (lines - 316-319), dispatches (lines 327-329), and saves with a plain `DbContext.SaveChangesAsync` (line - 347). The comment above that save is worth noting (lines 343-346): no user id is passed, so the - audit interceptor stamps its system sentinel, and although the EF interceptors still run there is - nothing for them to capture because `OutboxMessage` is not an aggregate root. It returns a - `(OutboxCycleResult, long PendingDepth)` tuple (lines 351-355) so the caller can sum the depth. - - **Fetching.** `FetchCandidatesAsync` (`OutboxProcessor.cs:335-353`) selects rows that are - unprocessed, under `MaxRetries`, and not under another replica's unexpired lease (lines 421-423), - ordered by `OccurredOn` then `Id` and capped at `BatchSize` (lines 424-426, default 50, + `OutboxProcessor.cs:163-164`). + - **Draining one target.** `ProcessSourceAsync` (`OutboxProcessor.cs:174-255`) opens the cycle scope + through `scopeFactory.CreateTenantScope(target)` (line 181; the comment at line 180 notes the tenant + is set before the context is asked for) and gets the context for that source (lines 183-184). It + resolves no dispatcher or message bus: delivery happens on a separate scope per row (see below). It + fetches a candidate batch (line 189), derives the backlog depth (line 190) and publishes the + oldest-pending age from the batch's own first row (lines 195-197). Then it splits the ordered batch: + the eligible prefix is everything with `OccurredOn` before the `ProcessingDelaySeconds` cutoff (lines + 187, 201-205), and the first row past it becomes `earliestPending` (line 207). Nothing eligible means + an early return carrying only the wait information (lines 209-212). Otherwise it **claims** the + prefix (line 214), returns early if another replica claimed all of it between fetch and claim (lines + 217-221), dispatches with the target itself (line 228), and saves with a plain + `DbContext.SaveChangesAsync` (line 246). The comment above that save is worth noting (lines 242-245): + no user id is passed, so the audit interceptor stamps its system sentinel, and although the EF + interceptors still run there is nothing for them to capture because `OutboxMessage` is not an + aggregate root. It returns a `(OutboxCycleResult, long PendingDepth)` tuple (lines 250-254) so the + caller can sum the depth. + - **Fetching.** `FetchCandidatesAsync` (`OutboxProcessor.cs:324-342`) selects rows that are + unprocessed, under `MaxRetries`, and not under another replica's unexpired lease (lines 334-336), + ordered by `OccurredOn` then `Id` and capped at `BatchSize` (lines 337-339, default 50, `OutboxSettings.cs:17`). There is deliberately **no** `OccurredOn` cutoff in SQL (doc, lines - 402-410): pending rows are fetched too so the caller can smart-wait, and ordering by `OccurredOn` + 315-323): pending rows are fetched too so the caller can smart-wait, and ordering by `OccurredOn` guarantees eligible rows sort before pending ones, which is what stops a full batch from starving - eligible work. The query runs inside an explicit `OutboxPoll` activity (lines 417-418; the name - constant `PollActivityName` is at line 73) that the Aspire + eligible work. The query runs inside an explicit `OutboxPoll` activity (line 330; the name constant + `PollActivityName` is at line 70) that the Aspire [`OutboxPollFilterProcessor`](group-16-aspire-orchestration.md#outboxpollfilterprocessor) suppresses from telemetry export along with its SqlClient child span; the string is deliberately duplicated - there because Aspire has no project reference back to Infrastructure (comment, lines 67-72). - - **Backlog depth almost for free.** `CountPendingAsync` (`OutboxProcessor.cs:276-297`) returns the + there because Aspire has no project reference back to Infrastructure (comment, lines 64-69). + - **Backlog depth almost for free.** `CountPendingAsync` (`OutboxProcessor.cs:265-286`) returns the fetched count directly whenever the batch came back short, because a short batch *is* the whole - backlog (lines 359-362). Only a saturated batch, exactly the state an operator alerts on, pays for a + backlog (lines 272-275). Only a saturated batch, exactly the state an operator alerts on, pays for a `LongCountAsync`, and that query runs inside its own `OutboxPoll` activity so it is suppressed like - the poll itself (lines 364-372). The predicate mirrors the fetch (lines 368-370), so the gauge counts + the poll itself (lines 277-278). The predicate mirrors the fetch (lines 281-283), so the gauge counts the rows this processor considers workable. - - **Claiming: how scale-out is made safe.** `ClaimEligibleAsync` (`OutboxProcessor.cs:378-422`) mints - a `lockToken` and a `leaseUntil` of now plus `LeaseSeconds` (lines 461-462, default 300, - `OutboxSettings.cs:82`), narrows the prefix (line 463), then issues one conditional - `ExecuteUpdateAsync` setting `LockedUntil` and `LockToken` (lines 477-481). A claim of zero rows - means another replica took the whole prefix (lines 483-484); a full claim returns the candidates - as-is (lines 486-487); a **partial** claim re-queries which ids carry *this* replica's token and - processes only those (lines 490-497). The doc states the property this buys (lines 431-437): two + - **Claiming: how scale-out is made safe.** `ClaimEligibleAsync` (`OutboxProcessor.cs:367-411`) mints + a `lockToken` and a `leaseUntil` of now plus `LeaseSeconds` (lines 374-375, default 300, + `OutboxSettings.cs:82`), narrows the prefix (line 376), then issues one conditional + `ExecuteUpdateAsync` setting `LockedUntil` and `LockToken` (lines 390-394). A claim of zero rows + means another replica took the whole prefix (lines 396-397); a full claim returns the candidates + as-is (lines 399-400); a **partial** claim re-queries which ids carry *this* replica's token and + processes only those (lines 402-410). The doc states the property this buys (lines 345-350): two replicas can never dispatch the same message, and a replica that dies mid-batch releases its rows implicitly when the lease expires. That is scale-out safety by construction rather than by a `minReplicas: 1` deployment convention. - **Ordered delivery, enforced inside the claim.** This is the piece that is easy to get wrong, and - the doc explains why it lives here rather than after the fetch (lines 438-446): enforcing it in the + the doc explains why it lives here rather than after the fetch (lines 352-358): enforcing it in the claim is what makes it survive batching *and* scale-out. Three pieces cooperate. - `SelectOrderedCandidates` (`OutboxProcessor.cs:431-447`) narrows the eligible prefix to every + `SelectOrderedCandidates` (`OutboxProcessor.cs:420-436`) narrows the eligible prefix to every unkeyed row plus the **first** row of each ordering key, which is what stops one cycle from - dispatching two events of a key in parallel. `FilterClaimable` (lines 543-549) is the shared - predicate (these ids, still unprocessed, not leased). `FilterUnblocked` (lines 558-568) adds the + dispatching two events of a key in parallel. `FilterClaimable` (lines 442-448) is the shared + predicate (these ids, still unprocessed, not leased). `FilterUnblocked` (lines 457-467) adds the ordering guard as a correlated `NOT EXISTS`: a keyed row is refused while any earlier unprocessed, non-dead-lettered row shares its key, evaluated by the database at the instant of the update, so a second replica racing the same key loses on the row rather than on a check it made before the race - started. Which of the two runs is decided per batch (lines 470-475): a batch with no keyed row runs + started. Which of the two runs is decided per batch (lines 383-388): a batch with no keyed row runs exactly the query it always ran, so hosts that never declare an ordering key pay nothing for the feature, not even a subquery the optimizer has to prove away. Two documented consequences: a predecessor still blocks while it is retrying, which is the head-of-line blocking [`IHasOrderingKey`](group-02-domain-building-blocks.md#ihasorderingkey) documents, but once it exhausts its retries it stops blocking, so a poison event cannot freeze its key forever (lines - 443-445); and the predecessor test is on `OccurredOn` alone, so two rows sharing a key and an exact + 356-358); and the predecessor test is on `OccurredOn` alone, so two rows sharing a key and an exact timestamp are ordered by `Id` within a cycle but neither blocks the other in SQL, because `Guid` has - no order that .NET and every provider agree on (remarks, lines 448-453). - - **Restoring the request's identity, per row.** `DispatchMessagesAsync` now also takes the cycle's - `IServiceProvider scopeServices` (`OutboxProcessor.cs:503`), passed as `scope.ServiceProvider` from - the `ProcessSourceAsync` call site (lines 322-324). Before anything else runs for a row, `Context.AmbientOrigin.Restore` writes that - row's captured `UserId`, `UserRoles`, `TenantId` and `CorrelationId` onto the scope, tagged with the - authentication type `OutboxPrincipalAuthenticationType` (`"Outbox"`, line 81) so the rebuilt identity - reads `IsAuthenticated` true and names the hop it came back from (lines 605-613). It is overwritten - again for the next row (doc, lines 570-573), so one row's identity can never answer for another's: - `BrokerMessageBus` reads the restored values through the same scoped services when it stamps its - publish headers, and the in-process path (`InProcessMessageBus` to - [`IDomainEventDispatcher`](#idomaineventdispatcher)) gets the right ambient context for free without - either transport reaching back into the row itself (remarks, lines 570-578). - - **Dispatching.** `DispatchMessagesAsync` (`OutboxProcessor.cs:500-637`) walks the claimed batch - inside a per-message activity (line 607). A row whose payload will not deserialize goes to - `HandleUnresolvableType` (lines 621-625). Otherwise an [`IIntegrationEvent`](#iintegrationevent) is + no order that .NET and every provider agree on (remarks, lines 362-365). + - **A fresh scope per row, and the request's identity restored onto it.** `DispatchMessagesAsync` + takes the claimed rows and the batch's + [`TenantDataSourceTarget`](group-07-persistence-ef-core.md#tenantdatasourcetarget) + (`OutboxProcessor.cs:490-493`), and for each row opens its own `rowScope` through + `scopeFactory.CreateTenantScope(target)` (line 509): a tenant-owned target keeps its tenant, while + the shared target starts unresolved so the restore can set this row's tenant or leave it unset + (comment, lines 507-508). The doc gives the reason (remarks, lines 476-485): the tenant context + refuses a change once resolved, so on one shared scope every later row of a shared-target batch ran + under the first row's tenant. Before anything else runs, `Context.AmbientOrigin.Restore` writes that + row's captured `UserId`, `UserRoles`, `TenantId` and `CorrelationId` onto `rowScope.ServiceProvider`, + tagged with the authentication type `OutboxPrincipalAuthenticationType` (`"Outbox"`, line 76) so the + rebuilt identity reads `IsAuthenticated` true and names the hop it came back from (lines 514-520). + The message bus and the dispatcher are resolved from that row scope too (lines 534, 548), so + `BrokerMessageBus` reads the restored values when it stamps its publish headers, and the in-process + path (`InProcessMessageBus` to [`IDomainEventDispatcher`](#idomaineventdispatcher)) gets the right + ambient context for free, without either transport reaching back into the row. Only delivery moves + to the row scope: the cycle scope's context keeps tracking the rows for the batch save (lines + 484-485). + - **Dispatching.** `DispatchMessagesAsync` (`OutboxProcessor.cs:490-632`) walks the claimed batch + inside a per-message activity (line 505). A row whose payload will not deserialize goes to + `HandleUnresolvableType` (lines 522-527). Otherwise an [`IIntegrationEvent`](#iintegrationevent) is published through [`IMessageBus`](#imessagebus) and a pure domain event goes to - [`IDomainEventDispatcher`](#idomaineventdispatcher) (lines 630-645). On success the row is stamped - (lines 647-649), `ProcessedCounter` is incremented (line 652) and `DispatchLagHistogram` records the + [`IDomainEventDispatcher`](#idomaineventdispatcher) (lines 532-550). On success the row is stamped + (lines 552-554), `ProcessedCounter` is incremented (line 557) and `DispatchLagHistogram` records the seconds between `OccurredOn` and `ProcessedOn`, clamped at zero because the two timestamps come from - different hosts and clock skew must not publish a negative duration (lines 657-659). The per-message + different hosts and clock skew must not publish a negative duration (lines 559-564). The per-message success log is deliberately Debug, not Information, and the comment prices the difference: it would otherwise be the single noisiest line in steady state, a real telemetry-ingestion cost, while - failures stay loud (lines 852-854). + failures stay loud (lines 747-751). - **Dead-lettering an unresolvable type, with one grace attempt.** `HandleUnresolvableType` - (`OutboxProcessor.cs:651-673`) treats the **first** failure to resolve as transient and retries it - through the normal backoff path, because the assembly declaring the type may simply not be loaded - yet, a module assembly resolved lazily or a host still coming up, and a name that resolves one cycle - later was never a dead letter (doc, lines 730-734). Only the second attempt is terminal, which is - also the point at which an operator has already had a Warning naming the row (lines 747-754, - `LogTypeUnresolvableRetry` at 864-865, whose message names the fix: give the event an - [`EventName`](group-02-domain-building-blocks.md#eventnameattribute)). A host that set `MaxRetries` - to 1 asked for no retries at all, so that case skips the grace attempt rather than scheduling one - the poll's filter would never pick up (lines 745-746). The terminal path stamps `ProcessedOn`, - increments the dead-letter counter with `reason=type_unresolvable` and logs at Error (lines 756-762). + (`OutboxProcessor.cs:650-673`) records `LastError` (line 652) and treats the **first** failure to + resolve as transient, retrying it through the normal backoff path, because the assembly declaring + the type may simply not be loaded yet, a module assembly resolved lazily or a host still coming up, + and a name that resolves one cycle later was never a dead letter (doc, lines 635-639). Only the + second attempt is terminal, which is also the point at which an operator has already had a Warning + naming the row (lines 656-663, `LogTypeUnresolvableRetry` at 759-760, whose message names the fix: + give the event an [`EventName`](group-02-domain-building-blocks.md#eventnameattribute)). A host that + set `MaxRetries` to 1 asked for no retries at all, so that case skips the grace attempt rather than + scheduling one the poll's filter would never pick up (lines 654-655). The terminal path dead-letters + the row the way exhausted retries do: it sets `RetryCount` to `MaxRetries` and clears `LockedUntil`, + leaving `ProcessedOn` null (lines 665-666), increments the dead-letter counter with + `reason=type_unresolvable` and logs at Error (lines 667-671). The doc names the effect (lines + 640-643): the row leaves the poll but stays listed and replayable by the outbox administration and + is kept for the dead-letter retention window, instead of being purged as delivered. - **The broker circuit breaker.** Only the broker hop is wrapped. `_brokerPublishPipeline` - (`OutboxProcessor.cs:104`) is a Polly `ResiliencePipeline` built by `BuildBrokerPublishPipeline` - (lines 795-806) from + (`OutboxProcessor.cs:102`) is a Polly `ResiliencePipeline` built by `BuildBrokerPublishPipeline` + (lines 690-701) from [`BrokerResilienceDefaults`](group-16-aspire-orchestration.md#brokerresiliencedefaults) (failure - ratio, minimum throughput, sampling and break durations, lines 799-802), and the integration-event - branch executes the publish through it (lines 636-640). Three deliberate choices sit in the doc + ratio, minimum throughput, sampling and break durations, lines 694-697), and the integration-event + branch executes the publish through it (lines 540-544). Three deliberate choices sit in the doc comments. It guards the publish call **only**, never the database calls, because a breaker on those - would open exactly when the processor most needs to persist retry state (lines 101-103). It carries + would open exactly when the processor most needs to persist retry state (lines 90-92). It carries **no** retry strategy, because the outbox already owns retry through `RetryCount` and - `ComputeRetryBackoffSeconds` (lines 104-105). And it is an **instance** field rather than a static + `ComputeRetryBackoffSeconds` (lines 93-94). And it is an **instance** field rather than a static one, so breaker state cannot leak across the many processors a test assembly constructs in parallel - (lines 106-111). `OperationCanceledException` is excluded from the handled set (line 804), because - a host shutdown cancelling a batch is not evidence that the broker is unhealthy (doc, lines 789-794). - The in-process dispatcher branch is left unwrapped on purpose: it is a direct method call into the - same process, so a breaker there would only add a way to reject work that would have succeeded - (comment, lines 632-635). + (lines 95-100). `OperationCanceledException` is excluded from the handled set (lines 698-699), + because a host shutdown cancelling a batch is not evidence that the broker is unhealthy (doc, lines + 683-689). The in-process dispatcher branch is left unwrapped on purpose: it is a direct method call + into the same process, so a breaker there would only add a way to reject work that would have + succeeded (comment, lines 536-539). - **Failure handling.** A cancellation during dispatch is rethrown rather than treated as a delivery - failure, and the comment explains the bug that guard prevents (lines 665-668): falling into the + failure, and the comment explains the bug that guard prevents (lines 568-575): falling into the generic handler would increment `RetryCount` and stamp `LastError` on this message and, since every later `await` fails the same way, on the whole remainder of the batch, so a graceful restart could - dead-letter messages that were never attempted. A genuine exception bumps `RetryCount` (line 673), - records `LastError` (line 674) and **re-leases** the row for an explicit backoff (lines 682-683); - the comment notes that simply keeping the original claim made every retry wait the full - `LeaseSeconds` no matter what the polling interval or a signal said, turning the retry cadence into - an accident of the lease (lines 676-681). A `BrokenCircuitException` follows that same failure path - but is counted separately on + dead-letter messages that were never attempted. A genuine exception bumps `RetryCount` (line 578), + records `LastError` truncated to the 4000-character column width (`MaxErrorLength`, line 79, through + [`ColumnWidth.Truncate`](group-07-persistence-ef-core.md#columnwidth), line 579) and **re-leases** + the row for an explicit backoff (lines 587-588); the comment notes that simply keeping the original + claim made every retry wait the full `LeaseSeconds` no matter what the polling interval or a signal + said, turning the retry cadence into an accident of the lease (lines 581-586). A + `BrokenCircuitException` follows that same failure path but is counted separately on [`BrokerMetrics.CircuitOpenCounter`](group-14-module-system-composition.md#brokermetrics) (lines - 693-699) and logged **once per batch** through a local latch (lines 603, 701-705), because an open + 598-604) and logged **once per batch** through a local latch (lines 501, 606-610), because an open circuit rejects every remaining row in the same instant, and "the broker refused 50 messages" and - "we did not try, the broker is known-dead" are different operational facts (comment, lines 687-692). + "we did not try, the broker is known-dead" are different operational facts (comment, lines 592-597). When `RetryCount` reaches `MaxRetries` (5 by default, `OutboxSettings.cs:21`) the dead-letter counter - is incremented with `reason=retries_exhausted` and it logs at Error (lines 707-716); the row then + is incremented with `reason=retries_exhausted` and it logs at Error (lines 612-622); the row then leaves the poll through the `RetryCount` filter and is eventually purged by - [`OutboxCleanupService`](#outboxcleanupservice), unless an operator replays it first through + [`OutboxCleanupService`](#outboxcleanupservice) after the dead-letter retention window (comment, lines + 614-616), unless an operator replays it first through [`OutboxAdministration`](#outboxadministration). - - **Backoff.** `OutboxProcessor.ComputeRetryBackoffSeconds` (`OutboxProcessor.cs:680-681`) is now a + - **Backoff.** `OutboxProcessor.ComputeRetryBackoffSeconds` (`OutboxProcessor.cs:680-681`) is a one-line forward to `PollingLoop.ComputeRetryBackoffSeconds` (`PollingLoop.cs:183-197`), passing `_settings.RetryBackoffBaseSeconds` and `_settings.LeaseSeconds` as the base and cap. The shared implementation is `RetryBackoffBaseSeconds * 2^(retryCount - 1)` (default base 10, @@ -2357,18 +2378,18 @@ edge) are the primary references. shared schedule. The `S2245`/`CA5394` suppression (`PollingLoop.cs:192,194`) is justified inline: the randomness feeds no security, token, key or cryptographic decision. - **Graceful shutdown.** If cancellation lands mid-batch, `ProcessSourceAsync` calls - `TryPersistStampsOnCancellationAsync` (lines 331-340, implemented at 402-414) before rethrowing, so + `TryPersistStampsOnCancellationAsync` (lines 230-240, implemented at 301-313) before rethrowing, so messages already delivered keep their `ProcessedOn` instead of being redelivered when their lease - expires. Two constraints are deliberate (doc, lines 389-401): its own try/catch, because a failure + expires. Two constraints are deliberate (doc, lines 288-300): its own try/catch, because a failure here must never replace the propagating `OperationCanceledException` the loop uses to recognize - shutdown; and its own 5-second token (`ShutdownSaveTimeout`, line 97) rather than + shutdown; and its own 5-second token (`ShutdownSaveTimeout`, line 86) rather than `CancellationToken.None`, so an uncancellable save against a dead connection cannot hold host shutdown open until the command timeout. A failure there logs at Warning and says plainly that the - delivered messages will be redelivered when the lease expires (lines 846-847). + delivered messages will be redelivered when the lease expires (lines 741-742). - **Trace continuity.** `StartOutboxActivity` (`OutboxProcessor.cs:708-730`) rebuilds the original - request's `ActivityContext` from the row's `TraceId`/`SpanId` (lines 820-823) and starts a + request's `ActivityContext` from the row's `TraceId`/`SpanId` (lines 715-718) and starts a `Consumer`-kind `OutboxProcess` activity tagged with the message id, event type and data source - (lines 825-832), returning null when no trace context was captured (lines 815-818), so traces span + (lines 720-727), returning null when no trace context was captured (lines 710-713), so traces span the asynchronous hop. - **Why it's built this way**: [ADR-003](https://ivanball.github.io/docs/adr/003-outbox-dual-dispatch.html) makes the outbox the @@ -2380,11 +2401,12 @@ edge) are the primary references. unresolvable types stops one poison message from blocking the queue, the *progress* requirement on re-poll (see [`OutboxCycleResult`](#outboxcycleresult)) prevents a fully-failing batch from hot-spinning, the circuit breaker keeps a known-dead broker from being hammered once per row per - cycle, the lease-plus-token pair is what makes running more than one replica safe, and the ordering - guard inside the claim is what makes ordered delivery survive both batching and scale-out. + cycle, the lease-plus-token pair is what makes running more than one replica safe, the ordering + guard inside the claim is what makes ordered delivery survive both batching and scale-out, and the + per-row scope is what lets one batch deliver rows belonging to different tenants. - **Where it's used**: registered as a hosted service by `AddInfrastructure` whenever the transport enables the outbox - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:207-211`), so every + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:217-221`), so every broker-backed service host runs exactly one. The producer side is the two `IEventBus` implementations ([`InProcessEventBus`](#inprocesseventbus) and [`BrokerEventBus`](#brokereventbus)) plus the `SaveChanges` capture in @@ -2433,10 +2455,10 @@ edge) are the primary references. The **MassTransit v8 pin** is a separate constraint enforced by the dependency-version fitness test (v9 requires a commercial licence); see the primer's external-stack section. - **Where it's used**: implemented by [`InProcessMessageBus`](#inprocessmessagebus), the default - scoped registration (`MMCA.Common.Infrastructure/DependencyInjection.cs:311`, with the rationale + scoped registration (`MMCA.Common.Infrastructure/DependencyInjection.cs:321`, with the rationale comment at `DependencyInjection.cs:551-555`), and by [`BrokerMessageBus`](#brokermessagebus), which `Replace`s that registration inside `AddBrokerMessaging` - (`MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:93`). At runtime it is resolved per cycle by + (`MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:94`). At runtime it is resolved per cycle by the background [`OutboxProcessor`](#outboxprocessor) (`MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:193`) and invoked for every integration-event row it drains (`OutboxProcessor.cs:590-600`). @@ -2519,9 +2541,9 @@ edge) are the primary references. identity headers, and a publish with nothing to stamp pays no extra allocation for the pipe (comment, `BrokerMessageBus.cs:59-61`). - **Where it's used**: swapped in for the default in-process registration by `AddBrokerMessaging` - through `services.Replace` (`MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:93`), which returns + through `services.Replace` (`MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:94`), which returns early without touching the container when `MessageBus:Provider` is `InProcess` - (`MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:50-53`; see + (`MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:51-54`; see [`MessageBusSettings`](group-14-module-system-composition.md#messagebussettings)). It is driven at runtime by the [`OutboxProcessor`](#outboxprocessor), which resolves `IMessageBus` per cycle (`MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:193`) and calls it inside a @@ -2615,7 +2637,7 @@ edge) are the primary references. after the outbox rows are written (`MMCA.Common.Infrastructure/Persistence/Interceptors/DomainEventSaveChangesInterceptor.cs:337`), by the background [`OutboxProcessor`](#outboxprocessor) when re-dispatching persisted domain events - (`MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:554`), and by both in-process + (`MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:549`), and by both in-process buses ([`InProcessMessageBus`](#inprocessmessagebus), [`InProcessEventBus`](#inprocesseventbus)). @@ -2649,12 +2671,12 @@ edge) are the primary references. latency. When the outbox is enabled, the [`OutboxProcessor`](#outboxprocessor) still supplies the at-least-once safety net around this bus, because the processor is what invokes it. - **Where it's used**: registered as the default scoped `IMessageBus` in `AddServices` - (`MMCA.Common.Infrastructure/DependencyInjection.cs:311`, with the rationale comment at + (`MMCA.Common.Infrastructure/DependencyInjection.cs:321`, with the rationale comment at `DependencyInjection.cs:551-555`), and therefore the bus resolved by [`OutboxProcessor`](#outboxprocessor) in monolith mode (`MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:193`). It is replaced by [`BrokerMessageBus`](#brokermessagebus) in broker-mode service hosts - (`MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:93`). + (`MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:94`). `[Rubric §10, Messaging & Integration Architecture]` applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores. @@ -2764,7 +2786,7 @@ edge) are the primary references. [`IInboxStore`](#iinboxstore) is registered. `AddBrokerMessaging` registers [`EfInboxStore`](#efinboxstore) when `MessageBusSettings.IsInboxEnabled` resolves true, which it does by default for a broker transport - (`MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:106-117`, + (`MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:107-122`, `MMCA.Common.Infrastructure/Messaging/MessageBusSettings.cs:141`); an explicit `MessageBus:EnableInbox=false` opts down to [`NoOpInboxStore`](#noopinboxstore), where the inbox calls do nothing and every redelivery re-runs the handlers, a posture announced once at startup by @@ -2799,7 +2821,7 @@ edge) are the primary references. framework-shipped integration events are the framework's own contract, gated by its conventions and public API baseline, so consumer residency rules and frozen snapshots neither police nor churn on them - (`MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Events.cs:59-66`). + (`MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Events.cs:130-137`). - **Walkthrough**: the type carries one member and one attribute. `[EventName("Common.OutputCacheEvictionRequested.v1")]` (`OutputCacheEvictionRequested.cs:28`) pins the stable wire and storage identity, which is what the @@ -2912,7 +2934,7 @@ edge) are the primary references. fault consumer to *on* means the safe posture is the one you get by not thinking about it, `[Rubric §15, Best Practices & Code Quality]`. - **Where it's used**: inside the `configureConsumers` callback passed to `AddBrokerMessaging` - (`MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:87`) in each broker-mode service's + (`MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:88`) in each broker-mode service's `Program.cs`, for example `x.RegisterIntegrationEventConsumer()`. `[Rubric §10, Messaging & Integration Architecture]` applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores. @@ -2949,8 +2971,9 @@ edge) are the primary references. (`BrokerEventBus.cs:51-52`). `PublishBatchAsync` (`BrokerEventBus.cs:65-95`) resolves the outbox's logical data source through [`IDataSourceResolver`](group-07-persistence-ef-core.md#idatasourceresolver) - (`BrokerEventBus.cs:67`) and gets its context (`BrokerEventBus.cs:68`). If `!context.SupportsOutbox` - (`BrokerEventBus.cs:70`, Cosmos for example) it throws an `InvalidOperationException` naming the + (`BrokerEventBus.cs:67`) and gets its context (`BrokerEventBus.cs:68`). The outbox gate is the + context's engine capability, `!context.Engine.Capabilities.IsRelational` (`BrokerEventBus.cs:70`): + when the resolved engine is not relational (Cosmos for example) it throws an `InvalidOperationException` naming the misconfigured `Outbox:DataSource` and `Outbox:DatabaseName` rather than silently dropping the events (`BrokerEventBus.cs:75-76`, with the rationale at `BrokerEventBus.cs:72-74`). Otherwise it reads the scope's ambient [`OutboxOrigin`](#outboxorigin) **once** for the whole batch, @@ -2976,11 +2999,11 @@ edge) are the primary references. at the first publish rather than lose events quietly, and the same constraint is checked once at startup as well: `EnsureOutboxAvailableForProvider` rejects `MessageBus:EnableOutbox=false` under a broker transport with a message that names this class as the reason - (`MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:58`, + (`MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:59`, `DependencyInjection.Messaging.cs:181-185`). - **Where it's used**: registered as the scoped `IEventBus` when `AddBrokerMessaging` runs, replacing [`InProcessEventBus`](#inprocesseventbus) - (`MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:99`, with the explanatory comment at + (`MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:100`, with the explanatory comment at `DependencyInjection.Messaging.cs:95-98`). Every `IEventBus` injection in application code resolves this implementation in broker mode. @@ -3031,8 +3054,9 @@ edge) are the primary references. single overload wraps one event (`InProcessEventBus.cs:50`), and the batch overload coerces the sequence to an array once and returns early when empty (`InProcessEventBus.cs:58-60`). - `PublishBatchAsync` resolves the outbox target (`InProcessEventBus.cs:78`) and its context - (`InProcessEventBus.cs:79`). If `!context.SupportsOutbox || !_outboxEnabled` - (`InProcessEventBus.cs:81`) it dispatches directly with **no** outbox persistence and returns + (`InProcessEventBus.cs:79`). If the resolved engine is not relational or the outbox is + off, `!context.Engine.Capabilities.IsRelational || !_outboxEnabled` (`InProcessEventBus.cs:81`), it + dispatches directly with **no** outbox persistence and returns (`InProcessEventBus.cs:83-84`). - Otherwise it reads the scope's ambient [`OutboxOrigin`](#outboxorigin) **once** for the whole batch, `context.CurrentOutboxOrigin` (`InProcessEventBus.cs:87-89`), because the batch is one @@ -3049,15 +3073,16 @@ edge) are the primary references. `SaveChangesAsync` closes the dual-write gap, and dispatching immediately afterward gives synchronous in-process reactions without giving up the durable retry path. Finishing through [`OutboxFinalizer`](#outboxfinalizer) rather than a second full save keeps the hottest write path - down to one extra statement, `[Rubric §12, Performance & Scalability]`. The `SupportsOutbox` fast - path keeps the framework usable on a store without an outbox table (dispatch-only) rather than + down to one extra statement, `[Rubric §12, Performance & Scalability]`. The non-relational + (`Engine.Capabilities.IsRelational`) fast path keeps the framework usable on a store without an + outbox table (dispatch-only) rather than failing, which is the deliberate opposite of the choice [`BrokerEventBus`](#brokereventbus) makes: with no local consumers, a silent dispatch-only fallback in broker mode would drop the event entirely, so that class throws instead. - **Where it's used**: the default scoped `IEventBus` registration - (`MMCA.Common.Infrastructure/DependencyInjection.cs:305`), superseded by + (`MMCA.Common.Infrastructure/DependencyInjection.cs:315`), superseded by [`BrokerEventBus`](#brokereventbus) once `AddBrokerMessaging` is called - (`MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:99`). + (`MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:100`). `[Rubric §10, Messaging & Integration Architecture]` applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores. diff --git a/docs-src/onboarding/group-05-cqrs-pipeline.md b/docs-src/onboarding/group-05-cqrs-pipeline.md index 52147cfc..7a5cdc5b 100644 --- a/docs-src/onboarding/group-05-cqrs-pipeline.md +++ b/docs-src/onboarding/group-05-cqrs-pipeline.md @@ -191,7 +191,7 @@ order that works: `AddApplication()`, then the caller's registrations through an [`MmcaApplicationPipelineBuilder`](group-14-module-system-composition.md#mmcaapplicationpipelinebuilder), then `AddApplicationDecorators()` (`DependencyInjection.cs:209-215`). Seven production service hosts compose it that way (for example -`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:407` and +`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:409` and `MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:233`), while MMCA.Helpdesk's web host still writes the hand-composed form and ends with the decorators call (`MMCA.Helpdesk/Source/Hosts/MMCA.Helpdesk.Web/Program.cs:120`). Alongside it, @@ -386,13 +386,13 @@ XML-doc (`DependencyInjection.cs:87-109`): call, in [`DbContextFactory`](group-07-persistence-ef-core.md#dbcontextfactory) (`[Rubric §8, Data Architecture]`), and it is worth reading: **a returned failed `Result` rolls the transaction back, exactly like an exception** - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:582-588`); + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:598-604`); the call is re-entrant, so a nested transaction joins the ambient one and only the outermost call - begins, commits, or rolls back (`DbContextFactory.cs:529-530`); in-process domain event dispatch is - deferred until after a successful commit and dropped on rollback (`DbContextFactory.cs:597-602`); + begins, commits, or rolls back (`DbContextFactory.cs:545-546`); in-process domain event dispatch is + deferred until after a successful commit and dropped on rollback (`DbContextFactory.cs:616-621`); and a failure of the *commit itself* is never retried, surfacing as - `TransactionCommitAmbiguousException` instead (`DbContextFactory.cs:499-504`, - `DbContextFactory.cs:591-593`). + `TransactionCommitAmbiguousException` instead (`DbContextFactory.cs:515-520`, + `DbContextFactory.cs:610-612`). ## Opt-in by marker interface, pay only for what you use @@ -721,10 +721,10 @@ so a request that misses the fast path and the double-check is not counted twice non-generic holder around a [`KeyedSemaphoreStripe`](group-08-auth.md#keyedsemaphorestripe) so that every closed generic decorator shares one table rather than one per closed type (`CachingQueryDecorator.cs:226-250`). Its sibling [`CacheKeyLocks`](#cachekeylocks) -(`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:142`) does the same job +(`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:189`) does the same job for the default `ICacheService.GetOrCreateAsync` implementation, and is deliberately a *separate* table: different call sites over different keys, where sharing stripes would only widen the -unrelated-key collisions striping already tolerates (`ICacheService.cs:134-140`). Both are striped +unrelated-key collisions striping already tolerates (`ICacheService.cs:181-187`). Both are striped rather than one semaphore per key, and both are honest about the limit: the lock is per process, so across replicas stampede protection is at most one handler execution per instance, not one cluster-wide (`CachingQueryDecorator.cs:236-241`). @@ -777,8 +777,8 @@ paused holder can lose the lock without knowing (`IDistributedLock.cs:37-42`), a owner-scoped and idempotent (`IDistributedLock.cs:54-57`). No decorator takes it; its in-framework caller is the API idempotency filter, which needs its execute-then-store window to be exclusive across replicas ([`IdempotencyFilter`](group-12-api-hosting-mapping.md#idempotencyfilter), -`MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:148`, acquisition at -`IdempotencyFilter.cs:250`, +`MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:149`, acquisition at +`IdempotencyFilter.cs:251`, [ADR-017](https://ivanball.github.io/docs/adr/017-request-idempotency.html)), and the implementation ([`RedisDistributedLock`](group-14-module-system-composition.md#redisdistributedlock) or the [`InProcessDistributedLock`](group-14-module-system-composition.md#inprocessdistributedlock) fallback) @@ -898,7 +898,7 @@ whole pipeline survives a module being extracted into its own service unchanged The contract to memorize, because the rest of the system relies on it, has four clauses. On a **business failure** (a `Result` with `IsFailure`, no exception thrown) the transaction is **rolled back**, atomicity over partial persistence, and cache invalidation is skipped -(`DependencyInjection.cs:108-109`, enforced at `DbContextFactory.cs:582-588` and +(`DependencyInjection.cs:108-109`, enforced at `DbContextFactory.cs:598-604` and `CachingCommandDecorator.cs:61-63`). On an **exception** the transaction also rolls back and the exception propagates outward through every decorator, which records the outcome and tags the metric `exception`, leaving the stack to the boundary that finally handles it @@ -935,10 +935,10 @@ rules of the pipeline. - **Concept introduced, cross-replica mutual exclusion as an Application-layer abstraction.** `[Rubric §12, Performance & Scalability]` assesses whether a design still holds once the service scales out horizontally, and the XML doc opens with precisely that failure (`IDistributedLock.cs:6-13`): a `SemaphoreSlim` (or a striped one) serializes callers *inside one process*, so a service running more than one replica executes an "only one of these at a time" section once per replica. `[Rubric §29, Resilience, Reliability & Business Continuity]` assesses behavior under partial failure; this contract is documented as **best-effort, not a consensus protocol** (`IDistributedLock.cs:23-28`): a holder paused past its time-to-live loses the lock without being told, so the guarded section must stay correct (merely slower, or duplicated) when exclusion is lost. The doc states the usage rule bluntly: take the lock to *collapse duplicate work*, never as the only guard on a correctness invariant that persistence can enforce. `[Rubric §3, Clean Architecture]` assesses whether the core depends on abstractions while technology choices sit at the edge; the contract carries no transport type at all, so the StackExchange.Redis dependency stays in Infrastructure and callers here never see it. - **Walkthrough**: line 30 declares the interface; lines 59-63 declare its single member, `Task TryAcquireAsync(string key, TimeSpan ttl, TimeSpan wait, CancellationToken cancellationToken = default)`. Every parameter carries a contract the implementations must honour. `key` is the logical name that callers sharing one backing store have to agree on (`IDistributedLock.cs:36`). `ttl` is the **crash guard**: how long the lock survives with no explicit release, so a holder that dies mid-section cannot wedge the key; it must sit comfortably above the guarded section's expected duration, because work that outlives the TTL is no longer protected (`:37-42`). `wait` is how long to block for a current holder, and `TimeSpan.Zero` makes the call a single non-blocking attempt (`:43-46`). The token cancels *the wait*, not the work that follows it (`:47`). The return contract matters as much as the parameters: `null` means "still held elsewhere after `wait` elapsed", and the handle is meant to be disposed inside an `await using` so release happens even when the guarded work throws (`:48-53`). Release is **owner-scoped and idempotent** (`:54-58`): disposing a handle whose TTL already lapsed is a no-op, not a release of whatever holder now owns the key. Two remarks bound usage further: implementations are singletons and must be safe to call concurrently (`:17`), and the lock is **not reentrant**, so a caller that already holds `key` and asks for it again waits for itself and then fails to acquire (`:20-21`). - **Why it's built this way**: [ADR-017](https://ivanball.github.io/docs/adr/017-request-idempotency.html) records the change that introduced it. The idempotency filter's execute-then-store window was previously guarded only by a process-local striped semaphore, which stops serializing anything the moment a service runs more than one replica, and both deployed apps do. Putting the contract in `MMCA.Common.Application.Interfaces` rather than Infrastructure is what lets the API filter depend on "a lock" while the Redis-versus-process-local decision stays a composition-root concern. -- **Where it's used**: the Infrastructure composition root registers exactly one implementation inside `AddCaching` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:84-98`), choosing [RedisDistributedLock](group-14-module-system-composition.md#redisdistributedlock) when an `IConnectionMultiplexer` is resolvable (`:87-93`) and the warn-once [InProcessDistributedLock](group-14-module-system-composition.md#inprocessdistributedlock) otherwise (`:95-97`); the comment above the registration explains the pairing with the cache (`:80-83`). Two production consumers exist today. - - The framework's [IdempotencyFilter](group-12-api-hosting-mapping.md#idempotencyfilter) resolves it from request services (`MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:148`) and spans the double-check plus action plus cache-store window with a 30-second `ttl` (`LockTimeToLive`, `:97`) and a 5-second `wait` (`LockWait`, `:104`), calling `TryAcquireAsync` at `:249-251`. When that wait expires with nothing cached it answers a 409 in-flight-duplicate result instead of executing a second time (`:261-273`); when the lock call itself *throws*, it records a degraded metric and executes anyway rather than failing the request (`:253-259`). +- **Where it's used**: the Infrastructure composition root registers exactly one implementation inside `AddCaching` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:86-100`), choosing [RedisDistributedLock](group-14-module-system-composition.md#redisdistributedlock) when an `IConnectionMultiplexer` is resolvable (`:87-93`) and the warn-once [InProcessDistributedLock](group-14-module-system-composition.md#inprocessdistributedlock) otherwise (`:95-97`); the comment above the registration explains the pairing with the cache (`:80-83`). Two production consumers exist today. + - The framework's [IdempotencyFilter](group-12-api-hosting-mapping.md#idempotencyfilter) resolves it from request services (`MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:149`) and spans the double-check plus action plus cache-store window with a 30-second `ttl` (`LockTimeToLive`, `:97`) and a 5-second `wait` (`LockWait`, `:104`), calling `TryAcquireAsync` at `:249-251`. When that wait expires with nothing cached it answers a 409 in-flight-duplicate result instead of executing a second time (`:261-273`); when the lock call itself *throws*, it records a degraded metric and executes anyway rather than failing the request (`:253-259`). - MMCA.ADC's `ScoreEventSessionsInternalCommandHandler` takes the lock per event around an AI-scoring pass (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/ScoreEventSessionsInternalCommandHandler.cs:78-80`) with a 15-minute `ClaimTimeToLive` (`:53`) and a `ClaimWait` of `TimeSpan.Zero` (`:60`), so a second replica that cannot claim the event simply skips its pass (`:79-83`). The inline rationale (`:14-21`) is a good worked example of both halves of the contract: the handle's disposal releases on every exit path, and the TTL releases for a replica that never reaches an exit path at all. -- **Caveats / not-in-source**: `MMCA.Store/Source` has no `IDistributedLock` consumer today. The idempotency filter also resolves it with `GetService()` and falls back to the striped-semaphore path when the result is `null` (`IdempotencyFilter.cs:148-153`), even though `AddCaching` registers an implementation unconditionally, so that fallback is reachable only in a host that never calls `AddCaching` (or a test building its own provider). The framework's own registration comment states the other honest limit: a host with no Redis gets the process-local, warn-once implementation, so "cross-replica" exclusion degrades back to per-replica (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:82-83`). +- **Caveats / not-in-source**: `MMCA.Store/Source` has no `IDistributedLock` consumer today. The idempotency filter also resolves it with `GetService()` and falls back to the striped-semaphore path when the result is `null` (`IdempotencyFilter.cs:149-154`), even though `AddCaching` registers an implementation unconditionally, so that fallback is reachable only in a host that never calls `AddCaching` (or a test building its own provider). The framework's own registration comment states the other honest limit: a host with no Redis gets the process-local, warn-once implementation, so "cross-replica" exclusion degrades back to per-replica (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:82-83`). --- @@ -970,7 +970,7 @@ rules of the pipeline. - **Concept introduced, ambient scope state with an honest "unset" value.** `[Rubric §11, Security]` assesses whether data isolation is enforced structurally rather than remembered per query; every tenant-aware read filter, save interceptor and cache key reads this one object, so a handler cannot forget to scope itself. `[Rubric §12, Performance & Scalability]`: like the correlation id, the value is captured once at the edge and flows implicitly for the rest of the scope. The interesting design decision is the one the doc calls out (`ITenantContext.cs:10-15`): unlike the correlation id there is **no generated fallback**. An unresolved tenant is a meaningful state (a background service, a seeder, an admin flow) and reads as "see everything", so inventing a value would silently scope a system operation to a tenant that does not exist. - **Walkthrough**: line 28 declares `string? TenantId { get; }`, null until resolved. Line 31 declares `bool IsResolved { get; }`. Line 41 declares `void SetTenant(string tenantId)`, and its contract is the strict part: it throws `ArgumentException` on a null, empty or whitespace id (`:37`) and `InvalidOperationException` when a *different* tenant was already resolved for this scope (`:38-40`), while accepting the value it already holds (`:34`). The rationale is on `:16-20`: **one scope, one tenant**, because a scope whose tenant changed mid-flight has already read rows under the previous tenant and there is no honest way to reconcile that afterwards. The implementation matches exactly (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Context/TenantContext.cs:11-45`), where `IsResolved` is simply `TenantId is not null` (`:17`), the first `SetTenant` assigns (`:24-28`), a repeat of the same value returns quietly (`:32-35`), and anything else throws with a message that names both tenants (`:37-43`). - **Why it's built this way**: [ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html) records the multi-tenancy model. Putting the contract in Application, not Infrastructure, is what lets the Application-layer caching decorators scope their keys without referencing EF Core: [CachingCommandDecorator](#cachingcommanddecoratortcommand-tresult) and [CachingQueryDecorator](#cachingquerydecoratortquery-tresult) both take it as an **optional** primary-constructor parameter defaulting to `null` (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/CachingCommandDecorator.cs:37` and `.../CachingQueryDecorator.cs:45`), so a host that never resolves a tenant pays nothing. -- **Where it's used**: registered scoped in `AddServices()`, **not** in `AddMultiTenancy` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:294`). The comment above the line is the design statement (`:535-538`): everything that reads the context treats an unresolved tenant as "no tenancy", so an always-on registration costs one object per scope and removes a whole class of "works until someone forgets the opt-in" bug. `AddMultiTenancy(configuration)` is then only the *settings* half: it binds and validates [TenancySettings](group-07-persistence-ef-core.md#tenancysettings) and adds the per-source override validator (`:511-523`). The context is written at the edge by [TenantResolutionMiddleware](group-12-api-hosting-mapping.md#tenantresolutionmiddleware) from a claim or a header, and read by [DbContextFactory](group-07-persistence-ef-core.md#dbcontextfactory) for per-tenant routing and by both caching decorators through `TenantCacheKey.Scope`, which prefixes the key with `t:{tenantId}:` only when a tenant is actually resolved (`.../UseCases/Decorators/TenantCacheKey.cs:37-40`, called at `CachingCommandDecorator.cs:67` and `CachingQueryDecorator.cs:56`). +- **Where it's used**: registered scoped in `AddServices()`, **not** in `AddMultiTenancy` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:304`). The comment above the line is the design statement (`:535-538`): everything that reads the context treats an unresolved tenant as "no tenancy", so an always-on registration costs one object per scope and removes a whole class of "works until someone forgets the opt-in" bug. `AddMultiTenancy(configuration)` is then only the *settings* half: it binds and validates [TenancySettings](group-07-persistence-ef-core.md#tenancysettings) and adds the per-source override validator (`:511-523`). The context is written at the edge by [TenantResolutionMiddleware](group-12-api-hosting-mapping.md#tenantresolutionmiddleware) from a claim or a header, and read by [DbContextFactory](group-07-persistence-ef-core.md#dbcontextfactory) for per-tenant routing and by both caching decorators through `TenantCacheKey.Scope`, which prefixes the key with `t:{tenantId}:` only when a tenant is actually resolved (`.../UseCases/Decorators/TenantCacheKey.cs:37-40`, called at `CachingCommandDecorator.cs:67` and `CachingQueryDecorator.cs:56`). - **Caveats / not-in-source**: verified by source search, neither `MMCA.ADC/Source` nor `MMCA.Store/Source` mentions `ITenantContext` at all today. Multi-tenancy is a shipped, tested framework capability that no deployed app has opted into, so every scope in production runs unresolved and the tenant-scoped cache keys and query filters are no-ops there. --- @@ -1077,14 +1077,14 @@ rules of the pipeline. --- ### CacheKeyLocks -> MMCA.Common.Application · `MMCA.Common.Application.Interfaces` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:142` · Level 2 · class (static, internal) +> MMCA.Common.Application · `MMCA.Common.Application.Interfaces` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:189` · Level 2 · class (static, internal) - **What it is**: a two-line internal holder for the process-wide stripe table that the default `ICacheService.GetOrCreateAsync` implementation uses to keep concurrent misses on one key from all running the factory. - **Depends on**: [KeyedSemaphoreStripe](group-08-auth.md#keyedsemaphorestripe) (`MMCA.Common.Shared.Concurrency`, `using` at `ICacheService.cs:1`). Used only by [ICacheService](group-09-caching.md#icacheservice)'s default interface method. -- **Concept introduced, cache stampede protection and why the lock table is striped.** `[Rubric §12, Performance & Scalability]` assesses behavior under load, and this is the classic thundering-herd guard: on a cold key, N concurrent readers would otherwise all miss, all call the expensive factory, and all write the same value. The interesting part is the *shape* of the guard. A per-key semaphore table forces a bad choice, spelled out on `ICacheService.cs:134-141`: drop the entry on release and two callers can run concurrently, or never drop it and a parameterized cache key grows the table without bound. Striping sidesteps both by hashing keys onto a fixed number of semaphores ([KeyedSemaphoreStripe](group-08-auth.md#keyedsemaphorestripe)) and accepting that two unrelated keys occasionally share one. That is a fixed, bounded cost, and the stripes are never disposed because the table outlives every caller. -- **Walkthrough**: line 142 declares `internal static class CacheKeyLocks`; line 145 declares its only member, `internal static readonly KeyedSemaphoreStripe Locks = new()`. The consuming sequence is the double-checked idiom at `ICacheService.cs:99-124`: a null-check on the factory (`:105`), a lock-free `GetAsync` fast path that returns immediately on a hit (`:107-110`), then the stripe is taken (`:112`), then the key is re-read inside the stripe (`:114-118`) so the waiters see what the winner just wrote, and only a still-missing key runs the factory and stores it (`:120-122`). The class doc (`:127-133`) explains the non-generic holder: statics on a generic method's declaring type would already be shared, but a holder keeps the table addressable and matches the sibling [QueryCacheKeyLocks](#querycachekeylocks) in the caching decorator (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/CachingQueryDecorator.cs:247`, used at `:182` and `:189`). -- **Why it's built this way**: the two tables are separate **on purpose** (`ICacheService.cs:138-141`): these are different call sites over different keys, so sharing stripes would only widen the unrelated-key collisions striping already tolerates. Two limits of the mechanism are documented on the member it guards (`ICacheService.cs:80-97`) and matter more than the class itself. First, **caching there is unconditional**: whatever the factory returns is stored, including a failed [Result](group-01-result-error-handling.md#result) or a null-equivalent value, which is exactly why the caching decorators do NOT route through `GetOrCreateAsync` and keep their own read/execute/write sequence (`:81-86`). Second, **stampede protection is per process**: the stripe table is process-wide, so with several replicas over one shared cache the factory can still run once per replica; a cluster-wide guarantee would need an [IDistributedLock](#idistributedlock) and is deliberately not attempted here (`:87-91`). -- **Where it's used**: only by the default implementation of `ICacheService.GetOrCreateAsync` (`ICacheService.cs:112`). Backing stores with a native two-level primitive override the method and never touch this table (`:92-97`); [HybridCacheService](group-09-caching.md#hybridcacheservice) is the shipped example, forwarding straight to `HybridCache.GetOrCreateAsync` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/HybridCacheService.cs:238-248`). +- **Concept introduced, cache stampede protection and why the lock table is striped.** `[Rubric §12, Performance & Scalability]` assesses behavior under load, and this is the classic thundering-herd guard: on a cold key, N concurrent readers would otherwise all miss, all call the expensive factory, and all write the same value. The interesting part is the *shape* of the guard. A per-key semaphore table forces a bad choice, spelled out on `ICacheService.cs:181-188`: drop the entry on release and two callers can run concurrently, or never drop it and a parameterized cache key grows the table without bound. Striping sidesteps both by hashing keys onto a fixed number of semaphores ([KeyedSemaphoreStripe](group-08-auth.md#keyedsemaphorestripe)) and accepting that two unrelated keys occasionally share one. That is a fixed, bounded cost, and the stripes are never disposed because the table outlives every caller. +- **Walkthrough**: line 142 declares `internal static class CacheKeyLocks`; line 145 declares its only member, `internal static readonly KeyedSemaphoreStripe Locks = new()`. The consuming sequence is the double-checked idiom at `ICacheService.cs:145-171`: a null-check on the factory (`:105`), a lock-free `GetAsync` fast path that returns immediately on a hit (`:107-110`), then the stripe is taken (`:112`), then the key is re-read inside the stripe (`:114-118`) so the waiters see what the winner just wrote, and only a still-missing key runs the factory and stores it (`:120-122`). The class doc (`:127-133`) explains the non-generic holder: statics on a generic method's declaring type would already be shared, but a holder keeps the table addressable and matches the sibling [QueryCacheKeyLocks](#querycachekeylocks) in the caching decorator (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/CachingQueryDecorator.cs:247`, used at `:182` and `:189`). +- **Why it's built this way**: the two tables are separate **on purpose** (`ICacheService.cs:185-188`): these are different call sites over different keys, so sharing stripes would only widen the unrelated-key collisions striping already tolerates. Two limits of the mechanism are documented on the member it guards (`ICacheService.cs:126-143`) and matter more than the class itself. First, **caching there is unconditional**: whatever the factory returns is stored, including a failed [Result](group-01-result-error-handling.md#result) or a null-equivalent value, which is exactly why the caching decorators do NOT route through `GetOrCreateAsync` and keep their own read/execute/write sequence (`:81-86`). Second, **stampede protection is per process**: the stripe table is process-wide, so with several replicas over one shared cache the factory can still run once per replica; a cluster-wide guarantee would need an [IDistributedLock](#idistributedlock) and is deliberately not attempted here (`:87-91`). +- **Where it's used**: only by the default implementation of `ICacheService.GetOrCreateAsync` (`ICacheService.cs:159`). Backing stores with a native two-level primitive override the method and never touch this table (`:92-97`); [HybridCacheService](group-09-caching.md#hybridcacheservice) is the shipped example, forwarding straight to `HybridCache.GetOrCreateAsync` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/HybridCacheService.cs:285-295`). - **Caveats / not-in-source**: the type is `internal`, so it is not part of the public package surface and cannot be referenced or replaced from a consumer app; it is documented here because the behavior it produces is visible to anyone calling `GetOrCreateAsync`. --- @@ -1341,7 +1341,7 @@ rules of the pipeline. - `HandleAsync` (`:395-406`): creates the context **itself** with `CreateContext()` (`:399`), rather than letting the convenience overload create one, then calls the three-argument `MutateCoreAsync(UnitOfWork, command, context, ...)` (`:401`) so the same context instance is still in hand after the workflow returns. On failure it forwards the errors; on success it calls `BuildResult(result.Value!, command, context)` (`:403-405`). That is the mechanical reason this flavor exists as its own class: the other two never need the context back. - `BuildResult` (`:418`): the single abstract member, `protected abstract Result BuildResult(TEntity entity, TCommand command, MutationContext context)`. It is synchronous and returns a `Result`, so a payload that cannot be built is a failure value rather than an exception. It is called only on success, after the save, or after a `SkipSave` short circuit, where the aggregate is the one that was loaded and left untouched (`:408-413`). - **Why it's built this way**: it is one of the five extensions the 2026-08-30 revision of [ADR-099](https://ivanball.github.io/docs/adr/099-generic-write-side-entity-commands.html) added to close the shapes that still forced a hand-written handler, and it is additive: every existing subclass of the other two flavors compiles and behaves exactly as before. -- **Where it's used**: ADC's `UpdateEventHandler` is the reference case (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/Update/UpdateEventHandler.cs:22`). Its `MutateAsync` compares the incoming timezone against the **stored** one before `Event.Update` overwrites it, only pays for a session-existence lookup when the value actually moves, and writes the BR-131 warning flag into the context under a private key (`:43-57`); `BuildResult` then assembles the `UpdateEventResult` envelope from the aggregate plus that flag (`:80`). ADC also uses it for `AddRoomHandler` (`.../Events/UseCases/AddRoom/AddRoomHandler.cs:28`) and for `SetUserAvatarHandler` (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/SetUserAvatar/SetUserAvatarHandler.cs:32`). Exercised by [MutateEntityHandlerBaseTests](group-28-testing-infrastructure.md#per-project-test-rollup). +- **Where it's used**: ADC's `UpdateEventHandler` is the reference case (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/Update/UpdateEventHandler.cs:22`). Its `MutateAsync` compares the incoming timezone against the **stored** one before `Event.Update` overwrites it, only pays for a session-existence lookup when the value actually moves, and writes the BR-131 warning flag into the context under a private key (`:43-57`); `BuildResult` then assembles the `UpdateEventResult` envelope from the aggregate plus that flag (`:80`). ADC also uses it for `AddRoomHandler` (`.../Events/UseCases/AddRoom/AddRoomHandler.cs:28`) and for `SetUserAvatarHandler` (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/SetUserAvatar/SetUserAvatarHandler.cs:34`). Exercised by [MutateEntityHandlerBaseTests](group-28-testing-infrastructure.md#per-project-test-rollup). --- @@ -1626,19 +1626,19 @@ rules of the pipeline. - **Caveats / not-in-source**: **no query validator exists anywhere in the workspace today.** A repo-wide search for a FluentValidation validator closed over a query type finds only the framework's own unit tests (`MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Decorators/ValidatingQueryDecoratorTests.cs`), so in every deployed host this decorator constructs an empty `_validators` array and is a length-check pass-through. It is a live extension point with full test coverage and zero production adopters, which is worth knowing before assuming a query's inputs are checked for you. ### TransactionalCommandDecorator -> MMCA.Common.Application · `MMCA.Common.Application.UseCases.Decorators` · `MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:20` · Level 8 · class (sealed) +> MMCA.Common.Application · `MMCA.Common.Application.UseCases.Decorators` · `MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:22` · Level 8 · class (sealed) - **What it is**: the innermost standard command decorator, the one closest to the concrete handler. It wraps the handler in a database transaction only when the command opts in via [ITransactional](#itransactional); every other command passes straight through. - **Depends on**: [ITransactional](#itransactional) (the opt-in marker); [IUnitOfWork](group-07-persistence-ef-core.md#iunitofwork) (supplies `ExecuteInTransactionAsync`, `TransactionalCommandDecorator.cs:1`, `:20`); [ICommandHandler](#icommandhandlerin-tcommand-tresult). No BCL or NuGet dependency beyond `Task` and `CancellationToken`. - **Concept introduced, declarative transaction boundaries via a marker.** `[Rubric §2, Design Patterns]` assesses whether patterns are chosen deliberately rather than decoratively; this is the Decorator pattern in its plainest form, a type that *is* an `ICommandHandler` and holds an inner `ICommandHandler`, so the pipeline composes without any handler knowing it exists. `[Rubric §12, Performance & Scalability]` assesses whether concerns like transactions are handled once, centrally: no handler in either app calls `BeginTransaction`. `[Rubric §8, Data Architecture]` assesses whether transaction boundaries are deliberate: here the boundary is declared on the command type, a domain-adjacent artifact reviewable in a pull request, rather than buried in handler code. The class itself is tiny; nearly all of the behavior lives one layer down in [DbContextFactory](group-07-persistence-ef-core.md#dbcontextfactory), reached through [UnitOfWork](group-07-persistence-ef-core.md#unitofwork). - **Walkthrough** - - Primary constructor (`TransactionalCommandDecorator.cs:20-22`): takes the inner `ICommandHandler` and an [IUnitOfWork](group-07-persistence-ef-core.md#iunitofwork). There is no state and no `_field` capture; both parameters are used directly in the single method. - - `HandleAsync` (`TransactionalCommandDecorator.cs:25`), opt-out path (`:26-27`): `if (command is not ITransactional) return await inner.HandleAsync(command, cancellationToken)`. A command that has not opted in pays exactly one type test and never touches the unit of work, which is why the decorator can be registered unconditionally on every command handler in the host. - - Transactional path (`TransactionalCommandDecorator.cs:31-33`): `unitOfWork.ExecuteInTransactionAsync(ct => inner.HandleAsync(command, ct), cancellationToken)`. The handler call is passed as a delegate rather than awaited inline, which is what lets the layer below own begin, commit, rollback, and (crucially) *re-run* the delegate under an EF Core execution strategy. Note that the lambda forwards the strategy's own `ct`, not the captured outer token. + - Primary constructor (`TransactionalCommandDecorator.cs:22-24`): takes the inner `ICommandHandler` and an [IUnitOfWork](group-07-persistence-ef-core.md#iunitofwork). There is no state and no `_field` capture; both parameters are used directly in the single method. + - `HandleAsync` (`TransactionalCommandDecorator.cs:27`), opt-out path (`:26-27`): `if (command is not ITransactional) return await inner.HandleAsync(command, cancellationToken)`. A command that has not opted in pays exactly one type test and never touches the unit of work, which is why the decorator can be registered unconditionally on every command handler in the host. + - Transactional path (`TransactionalCommandDecorator.cs:33-35`): `unitOfWork.ExecuteInTransactionAsync(ct => inner.HandleAsync(command, ct), cancellationToken)`. The handler call is passed as a delegate rather than awaited inline, which is what lets the layer below own begin, commit, rollback, and (crucially) *re-run* the delegate under an EF Core execution strategy. Note that the lambda forwards the strategy's own `ct`, not the captured outer token. - **Why it's built this way**: opt-in via a marker means only handlers that mutate multiple aggregates, or save twice against one database, pay for a transaction; everything else keeps the single-`SaveChangesAsync` atomicity EF Core already gives. Because transactions are per physical data source and there is no two-phase commit, cross-source consistency is the outbox's job rather than this decorator's ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)). The composition itself, a Scrutor decorator chain over thin handlers with a load-bearing registration order, is [ADR-014](https://ivanball.github.io/docs/adr/014-cqrs-decorator-pipeline.html). - **Where it's used**: registered by `AddApplicationDecorators()` at `MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:134`, the **first** of the seven command registrations (`:129-135`). Scrutor's `TryDecorate` applies decorators in reverse registration order (`DependencyInjection.cs:57-58`), so registered-first means applied-innermost: the transaction opens closest to the handler and **inside** the timeout budget and cache invalidation. That is what makes "invalidate only after a committed mutation" true (`DependencyInjection.cs:101-102`) and what lets an expired budget cancel the transaction rather than leave it open (`:104-107`). - **Adoption, verified against source**: opt-in is deliberately narrow. Seven production commands implement the marker today: `SendPushNotificationCommand` in Common itself (`MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationCommand.cs:23`); four in ADC, [LinkUserToSpeakerCommand](group-18-conference-application.md#linkusertospeakercommand) (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Speakers/UseCases/LinkUser/LinkUserToSpeakerCommand.cs:13`), [UnlinkUserFromSpeakerCommand](group-18-conference-application.md#unlinkuserfromspeakercommand) (`.../Speakers/UseCases/UnlinkUser/UnlinkUserFromSpeakerCommand.cs:12`), [RefreshFromSessionizeCommand](group-18-conference-application.md#refreshfromsessionizecommand) (`.../Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeCommand.cs:13`), and [BatchAddSessionQuestionAnswersCommand](group-18-conference-application.md#batchaddsessionquestionanswerscommand) (`.../Sessions/UseCases/BatchAddSessionQuestionAnswers/BatchAddSessionQuestionAnswersCommand.cs:24`); and two in Store, `UploadProductImageCommand` (`MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Application/Products/UseCases/UploadImage/UploadProductImageCommand.cs:27`) and `ReorderProductImagesCommand` (`.../Products/UseCases/ReorderImages/ReorderProductImagesCommand.cs:22`). Four Store commands carry a doc comment stating they are deliberately **not** transactional because each performs a single save: `CheckOutCommand.cs:9`, `VerifyPaymentCommand.cs:11`, `ProcessPaymentWebhookCommand.cs:9`, and `BulkSetInventoryCommand.cs:10`, the last of which is asserted in a test (`MMCA.Store/Tests/Modules/Sales/MMCA.Store.Sales.Application.Tests/Inventory/BulkSetInventoryTests.cs:264`). The full inventory and the reasoning per command live under [ITransactional](#itransactional). -- **Caveats / not-in-source**: four behaviors a reader will attribute to this file actually live in [DbContextFactory](group-07-persistence-ef-core.md#dbcontextfactory), and the class doc here (`TransactionalCommandDecorator.cs:11-16`, which mentions only exceptions) is narrower than what the code below does. (1) A returned **failed** [Result](group-01-result-error-handling.md#result) rolls the transaction back exactly like an exception, atomicity over partial persistence (`DbContextFactory.cs:480-484`, `:563-570`). (2) The call is **re-entrant**: a nested `ExecuteInTransactionAsync` joins the ambient transaction instead of opening a second one, so an `ITransactional` command whose handler also opens a transaction no longer throws from EF (`DbContextFactory.cs:475-479`, `:503-511`). (3) The whole delegate runs under an EF execution strategy and may be **re-executed** on a transient failure, with the change tracker reset between attempts so a retry does not insert duplicates (`DbContextFactory.cs:485-497`, `:526-534`). (4) A failure of the **commit** itself is never retried and surfaces as [TransactionCommitAmbiguousException](group-07-persistence-ef-core.md#transactioncommitambiguousexception) (`DbContextFactory.cs:498-505`, `:536-541`). In-process domain event dispatch is deferred until after a successful commit and dropped on rollback (`DbContextFactory.cs:595-602`), via [DomainEventSaveChangesInterceptor](group-07-persistence-ef-core.md#domaineventsavechangesinterceptor). +- **Caveats / not-in-source**: four behaviors a reader will attribute to this file actually live in [DbContextFactory](group-07-persistence-ef-core.md#dbcontextfactory), and the class doc here (`TransactionalCommandDecorator.cs:11-18`, which mentions only exceptions) is narrower than what the code below does. (1) A returned **failed** [Result](group-01-result-error-handling.md#result) rolls the transaction back exactly like an exception, atomicity over partial persistence (`DbContextFactory.cs:496-500`, `:563-570`). (2) The call is **re-entrant**: a nested `ExecuteInTransactionAsync` joins the ambient transaction instead of opening a second one, so an `ITransactional` command whose handler also opens a transaction no longer throws from EF (`DbContextFactory.cs:491-495`, `:503-511`). (3) The whole delegate runs under an EF execution strategy and may be **re-executed** on a transient failure, with the change tracker reset between attempts so a retry does not insert duplicates (`DbContextFactory.cs:501-513`, `:526-534`). (4) A failure of the **commit** itself is never retried and surfaces as [TransactionCommitAmbiguousException](group-07-persistence-ef-core.md#transactioncommitambiguousexception) (`DbContextFactory.cs:514-521`, `:536-541`). In-process domain event dispatch is deferred until after a successful commit and dropped on rollback (`DbContextFactory.cs:614-621`), via [DomainEventSaveChangesInterceptor](group-07-persistence-ef-core.md#domaineventsavechangesinterceptor). --- @@ -1650,8 +1650,8 @@ rules of the pipeline. - **Concept introduced, one dual-source capability check shared by both transports and both CQRS sides.** `[Rubric §11, Security]` assesses where the capability check lives and whether the same request is evaluated the same way regardless of caller. The remarks explain the dual grant source (`AuthorizationGate.cs:39-45`): the permission check grants on **either** source, exactly as the HTTP policy handler does, a role the host's registry maps to the permission, or an `AuthClaimTypes.Permission` claim on the principal itself. The claim path is what carries a stored grant across a service boundary, since only the minting host reads the grant table; checking the registry alone would deny in one service what the endpoint policy allows in another. `[Rubric §1, SOLID]`: extracting the shared logic out of the two decorators (see the diff hunks below) removes a duplicated permission check that previously had to be kept in sync by hand across the command and query files. - **Walkthrough** - Permission gate (`AuthorizationGate.cs:52-62`): `request is IRequiresPermission requiresPermission` combined with `!permissionRegistry.HasPermission(currentUser.Roles, requiresPermission.Permission)` and `!currentUser.User.HasPermissionClaim(requiresPermission.Permission)`; only when **both** are false does it record `CqrsMetrics.RecordAuthorizationDenied(requestTypeName)` and return `Error.Forbidden("Authorization.PermissionDenied", ...)`. - - MFA gate (`AuthorizationGate.cs:64-74`): checked **second**, and the comment says why (`:64-65`): a caller who holds no capability at all is answered by the permission gate and never learns which use cases additionally demand a step-up. `request is IRequiresMfa && !currentUser.User.HasMultiFactor()` records the same denial metric and returns `Error.Forbidden("Authorization.MultiFactorRequired", ...)`. - - Pass-through (`AuthorizationGate.cs:76`): `return null` when neither marker denies, or when the request implements neither marker at all. + - MFA gate (`AuthorizationGate.cs:58-68`): checked **second**, and the comment says why (`:58-59`): a caller who holds no capability at all is answered by the permission gate and never learns which use cases additionally demand a step-up. `request is IRequiresMfa && !currentUser.User.HasMultiFactor()` records the same denial metric and returns `Error.Forbidden("Authorization.MultiFactorRequired", ...)`. + - Pass-through (`AuthorizationGate.cs:70`): `return null` when neither marker denies, or when the request implements neither marker at all. - **Why it's built this way**: the command and query authorization decorators previously each inlined the permission check; the 2026-08-18 revision of [ADR-014](https://ivanball.github.io/docs/adr/014-cqrs-decorator-pipeline.html) both added the [IRequiresMfa](#irequiresmfa) step-up gate and pulled the combined logic into this one internal static class so the two decorators stay thin callers rather than two copies of the same branching. [ADR-020](https://ivanball.github.io/docs/adr/020-permission-based-authorization.html) supplies the permission model and registry the first gate reads; [ADR-116](https://ivanball.github.io/docs/adr/116-identity-completions-opt-in.html) documents the `mfa` claim this class reads through `HasMultiFactor` for the second gate (`116-identity-completions-opt-in.md:73-83`). - **Where it's used**: called once each by [AuthorizationCommandDecorator](#authorizationcommanddecoratortcommand-tresult)`.HandleAsync` and [AuthorizationQueryDecorator](#authorizationquerydecoratortquery-tresult)`.HandleAsync` (`AuthorizationCommandDecorator.cs:1`, `AuthorizationQueryDecorator.cs:1`). Internal and static, so it is an implementation detail of the two decorators rather than something a consuming module resolves or calls directly. - **Caveats / not-in-source**: as with the two decorators that call it, no production command or query implements [IRequiresMfa](#irequiresmfa) today, so the second gate is exercised only by the decorators' own test suites; both gates remain reachable and fully covered extension points with zero production adopters. @@ -1660,14 +1660,14 @@ rules of the pipeline. > MMCA.Common.Application · `MMCA.Common.Application.UseCases.Decorators` · `MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/AuthorizationCommandDecorator.cs:30` · Level 10 · class (sealed) - **What it is**: the second-outermost command decorator. When a command implements [IRequiresPermission](#irequirespermission) and none of the caller's roles or permission claims grants that permission, or when it implements [IRequiresMfa](#irequiresmfa) and the caller has not stepped up, it short-circuits with a `Forbidden` failure before logging, cache invalidation, validation, the timeout budget, or the transaction. -- **Depends on**: [AuthorizationGate](#authorizationgate) (`AuthorizationCommandDecorator.cs:70`, the shared evaluator that now holds the branching); [ICurrentUserService](group-08-auth.md#icurrentuserservice) and [IPermissionRegistry](group-08-auth.md#ipermissionregistry) (both forwarded straight into `AuthorizationGate.Evaluate` rather than read locally); [ResultFailureFactory](#resultfailurefactory); [Error](group-01-result-error-handling.md#error); [ICommandHandler](#icommandhandlerin-tcommand-tresult). +- **Depends on**: [AuthorizationGate](#authorizationgate) (`AuthorizationCommandDecorator.cs:62`, the shared evaluator that now holds the branching); [ICurrentUserService](group-08-auth.md#icurrentuserservice) and [IPermissionRegistry](group-08-auth.md#ipermissionregistry) (both forwarded straight into `AuthorizationGate.Evaluate` rather than read locally); [ResultFailureFactory](#resultfailurefactory); [Error](group-01-result-error-handling.md#error); [ICommandHandler](#icommandhandlerin-tcommand-tresult). - **Concept introduced, authorization as a use-case concern rather than a transport concern.** `[Rubric §11, Security]` assesses where the capability check lives and whether it survives a change of transport. The class doc frames this decorator as **defense in depth, not a replacement** for the endpoint's `[Authorize]` policy (`AuthorizationCommandDecorator.cs:22-26`): moving the check next to the use case is what makes a command reached through gRPC, a scheduled job, or another module get checked the same way it is over HTTP. `[Rubric §13, Observability & Operability]` assesses the factoring: no handler injects a permission service, and every denial increments a counter (inside [AuthorizationGate](#authorizationgate)) so a permission denying far more traffic than expected is visible as a metric rather than as a support ticket. - **Walkthrough** - `_createFailure` (`AuthorizationCommandDecorator.cs:50`) and `CreateFailure()` (`:52-53`): the lazily-built [ResultFailureFactory](#resultfailurefactory) delegate, `_createFailure ??= ResultFailureFactory.Build()`. The remarks (`:36-45`) explain why the build is deferred to the first short-circuit rather than done in a static initializer: `ResultFailureFactory` supports only [Result](group-01-result-error-handling.md#result) and `Result` and throws otherwise (`ResultFailureFactory.cs:43-45`), and because Scrutor's `TryDecorate` is unconditional, an eager static initializer would turn an unsupported `TResult` into a `TypeInitializationException` at **resolve** time for a handler that never denies anything. One assignment per closed generic type, and a benign duplicate build under a race produces an equivalent delegate. - - `HandleAsync` (`AuthorizationCommandDecorator.cs:68-76`): calls `AuthorizationGate.Evaluate(command, currentUser, permissionRegistry, typeof(TCommand).Name)` (`:70`). A `null` result runs the inner handler directly (`:71-72`); otherwise it builds the failure factory and returns `createFailure([denial])` (`:74-75`). The decorator itself no longer type-tests the command or reads `IPermissionRegistry.HasPermission`; both marker checks, the claim fallback, and the metric now live in [AuthorizationGate](#authorizationgate). + - `HandleAsync` (`AuthorizationCommandDecorator.cs:60-68`): calls `AuthorizationGate.Evaluate(command, currentUser, permissionRegistry, typeof(TCommand).Name)` (`:62`). A `null` result runs the inner handler directly (`:63-64`); otherwise it builds the failure factory and returns `createFailure([denial])` (`:66-67`). The decorator itself no longer type-tests the command or reads `IPermissionRegistry.HasPermission`; both marker checks, the claim fallback, and the metric now live in [AuthorizationGate](#authorizationgate). - **Why it's built this way**: the placement is the argued part. It sits **directly inside** the feature gate and **outside** logging, cache invalidation, validation and the transaction (`DependencyInjection.cs:92-94`, class doc `:12-17`), so a denied command never starts a transaction, never invalidates the cache and never runs validation, while a feature that is off is still rejected first, because a disabled feature must not leak the existence of the permission that guards it (`DependencyInjection.cs:88-91`). [ADR-020](https://ivanball.github.io/docs/adr/020-permission-based-authorization.html) supplies the permission model and the registry; the 2026-08-18 revision of [ADR-014](https://ivanball.github.io/docs/adr/014-cqrs-decorator-pipeline.html) inserted this stage into both chains and, in a later revision, extracted the shared [AuthorizationGate](#authorizationgate) so the command and query decorators stopped carrying two copies of the same check. - **Where it's used**: registered by `AddApplicationDecorators()` (`DependencyInjection.cs:139`) as the sixth of seven command registrations (`:129-135`), therefore the second-outermost wrapper on every command handler in the host. Because the two authorization decorators are registered unconditionally and take an `IPermissionRegistry`, the same method first does `services.TryAddSingleton()` (`DependencyInjection.cs:123`, comment `:119-123`): a host that declared its grants via `AddAuthorizationPolicies()` or `AddPermissions(...)` keeps its own registry, and a host with no permission model still resolves every handler instead of failing activation. [UnconfiguredPermissionRegistry](group-08-auth.md#unconfiguredpermissionregistry) grants nothing (`UnconfiguredPermissionRegistry.cs:44-51`) and logs one warning the first time a permission is actually checked (`:49-60`), so the fallback fails closed and says so. Behavior is covered by [AuthorizationCommandDecoratorTests](group-28-testing-infrastructure.md#per-project-test-rollup) and, for the ordering, [CommandDecoratorPipelineTests](group-28-testing-infrastructure.md#per-project-test-rollup). -- **Caveats / not-in-source**: no production command implements [IRequiresPermission](#irequirespermission) today (the only implementers in the workspace are test fakes such as `GuardedCommand` at `MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Decorators/AuthorizationCommandDecoratorTests.cs:196`), so this decorator is a pass-through in every deployed host. One behavioral note for whoever adopts it first: because the decorator sits outside [LoggingCommandDecorator](#loggingcommanddecoratortcommand-tresult), a denial is **not** recorded in the `cqrs.command.duration` histogram and produces no correlated command log line, only the denial counter. The failure travels on the [Result](group-01-result-error-handling.md#result) channel rather than as an exception, and the framework's transport mappers already translate it: [ErrorHttpMapping](group-12-api-hosting-mapping.md#errorhttpmapping) maps `ErrorType.Forbidden` to HTTP 403 (`MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/ErrorHttpMapping.cs:27`) and [ResultGrpcExtensions](group-13-grpc-contracts.md#resultgrpcextensions) maps it to `StatusCode.PermissionDenied` (`MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:43`). +- **Caveats / not-in-source**: no production command implements [IRequiresPermission](#irequirespermission) today (the only implementers in the workspace are test fakes such as `GuardedCommand` at `MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Decorators/AuthorizationCommandDecoratorTests.cs:196`), so this decorator is a pass-through in every deployed host. One behavioral note for whoever adopts it first: because the decorator sits outside [LoggingCommandDecorator](#loggingcommanddecoratortcommand-tresult), a denial is **not** recorded in the `cqrs.command.duration` histogram and produces no correlated command log line, only the denial counter. The failure travels on the [Result](group-01-result-error-handling.md#result) channel rather than as an exception, and the framework's transport mappers already translate it: [ErrorHttpMapping](group-12-api-hosting-mapping.md#errorhttpmapping) maps `ErrorType.Forbidden` to HTTP 403 (`MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/ErrorHttpMapping.cs:27`) and [ResultGrpcExtensions](group-13-grpc-contracts.md#resultgrpcextensions) maps it to `StatusCode.PermissionDenied` (`MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:44`). --- @@ -1776,10 +1776,10 @@ rules of the pipeline. - **What it is**: a C# empty-body interface (`public interface ITransactional;`) that a **command** implements to opt in to database-transaction wrapping by [TransactionalCommandDecorator](#transactionalcommanddecoratortcommand-tresult). - **Depends on**: nothing (BCL only). -- **Concept introduced, marker interfaces as opt-in decorator switches.** `[Rubric §2, Design Patterns]` assesses the deliberate, idiomatic use of patterns; a **marker interface** carries no members, so its mere presence on a type is the signal. The decorator's whole dispatch is `if (command is not ITransactional)` followed by a pass-through (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:27-29`). Read that line carefully: the marker goes on the **command record**, not on the handler class, which is the same convention [IFeatureGated](#ifeaturegated), [ICacheInvalidating](#icacheinvalidating), [IHasTimeout](#ihastimeout) and [IRequiresPermission](#irequirespermission) follow. This is also `[Rubric §1, SOLID]` (open for extension): the pipeline gains new transactional commands with no change to any existing decorator. The semicolon-body syntax (`interface ITransactional;`, line 6) is the idiomatic zero-member declaration and is used consistently across the framework's markers, including [ICommand](#icommandtresult) and [IQuery](#iquerytresult). +- **Concept introduced, marker interfaces as opt-in decorator switches.** `[Rubric §2, Design Patterns]` assesses the deliberate, idiomatic use of patterns; a **marker interface** carries no members, so its mere presence on a type is the signal. The decorator's whole dispatch is `if (command is not ITransactional)` followed by a pass-through (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:29-31`). Read that line carefully: the marker goes on the **command record**, not on the handler class, which is the same convention [IFeatureGated](#ifeaturegated), [ICacheInvalidating](#icacheinvalidating), [IHasTimeout](#ihastimeout) and [IRequiresPermission](#irequirespermission) follow. This is also `[Rubric §1, SOLID]` (open for extension): the pipeline gains new transactional commands with no change to any existing decorator. The semicolon-body syntax (`interface ITransactional;`, line 6) is the idiomatic zero-member declaration and is used consistently across the framework's markers, including [ICommand](#icommandtresult) and [IQuery](#iquerytresult). - **Walkthrough**: the entire file body is `public interface ITransactional;` at line 6, under the doc comment on lines 3-5. No members; the type *is* the message. - **Why it's built this way**: keeping transaction scope opt-in spares single-statement commands the cost of an explicit begin and commit around a save that is already atomic. Commands that mutate multiple aggregates, or that save twice against one database, opt in. The behavior that comes with opting in is documented in [ADR-014](https://ivanball.github.io/docs/adr/014-cqrs-decorator-pipeline.html) and `MMCA.Common/CLAUDE.md`, and is worth knowing before you add the marker: exceptions **and** business failures (`Result.Failure`) both roll back, and in-process domain event dispatch is deferred until after a successful commit, so handlers never act on state that could still roll back. -- **Where it's used**: sparingly, and the restraint is deliberate. Verified by source search, exactly **4 implementers in `MMCA.ADC/Source`** (`RefreshFromSessionizeCommand.cs:13`, `BatchAddSessionQuestionAnswersCommand.cs:24`, `LinkUserToSpeakerCommand.cs:13`, `UnlinkUserFromSpeakerCommand.cs:12`) and exactly **2 in `MMCA.Store/Source`** (`UploadProductImageCommand.cs:27` and `ReorderProductImagesCommand.cs:22`, both of which save twice against the same database and document why on lines 10 and 11 respectively). Several Store commands a reader would expect to see here carry a doc comment saying the opposite: `CheckOutCommand.cs:9`, `BulkSetInventoryCommand.cs:10`, `VerifyPaymentCommand.cs:11` and `ProcessPaymentWebhookCommand.cs:9` are each explicitly annotated "Deliberately NOT `ITransactional`", because their handlers reach the database in a single save that is already atomic. Inspected at execution time by [TransactionalCommandDecorator](#transactionalcommanddecoratortcommand-tresult), the **innermost** command decorator, so it sits closest to the handler (see the registration note under [ICommandHandler](#icommandhandlerin-tcommand-tresult)). +- **Where it's used**: sparingly, and the restraint is deliberate. Verified by source search, exactly **4 implementers in `MMCA.ADC/Source`** (`RefreshFromSessionizeCommand.cs:13`, `BatchAddSessionQuestionAnswersCommand.cs:24`, `LinkUserToSpeakerCommand.cs:13`, `UnlinkUserFromSpeakerCommand.cs:12`) and exactly **2 in `MMCA.Store/Source`** (`UploadProductImageCommand.cs:27` and `ReorderProductImagesCommand.cs:24`, both of which save twice against the same database and document why on lines 10 and 11 respectively). Several Store commands a reader would expect to see here carry a doc comment saying the opposite: `CheckOutCommand.cs:9`, `BulkSetInventoryCommand.cs:11`, `VerifyPaymentCommand.cs:11` and `ProcessPaymentWebhookCommand.cs:9` are each explicitly annotated "Deliberately NOT `ITransactional`", because their handlers reach the database in a single save that is already atomic. Inspected at execution time by [TransactionalCommandDecorator](#transactionalcommanddecoratortcommand-tresult), the **innermost** command decorator, so it sits closest to the handler (see the registration note under [ICommandHandler](#icommandhandlerin-tcommand-tresult)). --- diff --git a/docs-src/onboarding/group-06-validation.md b/docs-src/onboarding/group-06-validation.md index 1fa6a288..e460c695 100644 --- a/docs-src/onboarding/group-06-validation.md +++ b/docs-src/onboarding/group-06-validation.md @@ -93,7 +93,7 @@ failure contract for every endpoint). `AbstractValidator` generic over the *parent* type, taking an `Expression>` selector in its constructor and declaring its rules in an expression-bodied constructor. Because they are generic-plus-selector, the same [`EmailRules`](#requiredstringrulest-optionalstringrulest-emailrulest-positiveintrulest-positivedecimalrulest-nonnegativeintrulest-requiredidrulest-tid-optionalpositiveidrulest-tid-passwordrulest-strongpasswordrulest) -(`CommonValidationRules.cs:64`) validates a value object, a request DTO, or a command; a module +(`CommonValidationRules.cs:65`) validates a value object, a request DTO, or a command; a module composes it with FluentValidation's `Include(...)` instead of rewriting "non-empty, valid format, max length" each time. The bounds are parameters, never literals in the rule: ADC's registration validator passes `UserInvariants.EmailMaxLength` into `EmailRules` and pairs it with @@ -101,7 +101,7 @@ validator passes `UserInvariants.EmailMaxLength` into `EmailRules`](#requiredstringrulest-optionalstringrulest-emailrulest-positiveintrulest-positivedecimalrulest-nonnegativeintrulest-requiredidrulest-tid-optionalpositiveidrulest-tid-passwordrulest-strongpasswordrulest) (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/Validation/RegisterRequestValidator.cs:16-19`). The two password rules are the one place a literal bound is intentional: both pin 8 and 128 -characters (`CommonValidationRules.cs:179-180`, `:193-194`), and +characters (`CommonValidationRules.cs:180-181`, `:193-194`), and [`StrongPasswordRules`](#requiredstringrulest-optionalstringrulest-emailrulest-positiveintrulest-positivedecimalrulest-nonnegativeintrulest-requiredidrulest-tid-optionalpositiveidrulest-tid-passwordrulest-strongpasswordrulest) adds four complexity `Matches` rules for uppercase, lowercase, digit, and non-alphanumeric (`CommonValidationRules.cs:195-198`). That pair is `[Rubric §11, Security]` territory: the framework offers a weak-by-default floor and a strong @@ -112,11 +112,11 @@ are what the whole file is optimizing for. **One field, one error code.** Every rule class takes an optional trailing `errorCode`, and [`OptionalErrorCodeExtensions.WithOptionalErrorCode`](#optionalerrorcodeextensions) -(`CommonValidationRules.cs:19`, the method at `:30-32`) is the internal helper that applies it: +(`CommonValidationRules.cs:20`, the method at `:30-32`) is the internal helper that applies it: it returns the rule unchanged when the code is `null`, so every existing call site that omits it behaves exactly as before, and calls FluentValidation's `WithErrorCode` when one is supplied. The code is applied to **every** rule the class declares for that field, so one field answers under one -code (`CommonValidationRules.cs:11-18`); a field whose separate bounds must answer under distinct +code (`CommonValidationRules.cs:12-19`); a field whose separate bounds must answer under distinct codes still writes its own rules. This is what lets modules subclass a framework rule instead of bypassing it: ADC's `SessionEventIdRules` derives from [`RequiredIdRules`](#requiredstringrulest-optionalstringrulest-emailrulest-positiveintrulest-positivedecimalrulest-nonnegativeintrulest-requiredidrulest-tid-optionalpositiveidrulest-tid-passwordrulest-strongpasswordrulest) and passes `"Session.EventId.Required"` @@ -127,17 +127,17 @@ and Store's `ProductCategoryIdRules` derives from (`MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Application/Products/Validation/ProductValidationRules.cs:47-51`). The two id rules also encode a deliberate difference in what "missing" means: [`RequiredIdRules`](#requiredstringrulest-optionalstringrulest-emailrulest-positiveintrulest-positivedecimalrulest-nonnegativeintrulest-requiredidrulest-tid-optionalpositiveidrulest-tid-passwordrulest-strongpasswordrulest) uses `NotEmpty`, which rejects both `0` for an -integer key and `Guid.Empty` for a GUID key (`CommonValidationRules.cs:133-139`, +integer key and `Guid.Empty` for a GUID key (`CommonValidationRules.cs:134-140`, `:147`), while [`OptionalPositiveIdRules`](#requiredstringrulest-optionalstringrulest-emailrulest-positiveintrulest-positivedecimalrulest-nonnegativeintrulest-requiredidrulest-tid-optionalpositiveidrulest-tid-passwordrulest-strongpasswordrulest) uses `GreaterThan(default(TId))` on a nullable and relies on FluentValidation skipping a comparison rule when the property is `null`, so "positive when provided" needs no `When` clause and no per-pass -recompiled selector (`CommonValidationRules.cs:154-158`, `:166`). +recompiled selector (`CommonValidationRules.cs:155-159`, `:166`). **One rule that shares its check with the domain.** [`AbsoluteUrlRules`](#absoluteurlrulest) -(`CommonValidationRules.cs:85`) is the exception to "rule sets are self-contained": besides the -length bound it calls `Must(BeAnAbsoluteHttpUrl)` (`CommonValidationRules.cs:90`), and that predicate +(`CommonValidationRules.cs:86`) is the exception to "rule sets are self-contained": besides the +length bound it calls `Must(BeAnAbsoluteHttpUrl)` (`CommonValidationRules.cs:91`), and that predicate delegates to [`CommonInvariants.EnsureUrlIsWellFormed`](group-02-domain-building-blocks.md#commoninvariants) -(`CommonValidationRules.cs:92-93`, the invariant at +(`CommonValidationRules.cs:93-94`, the invariant at `MMCA.Common/Source/Core/MMCA.Common.Domain/Invariants/CommonInvariants.cs:293-297`) and keeps only its `IsSuccess`. The validator and the domain invariant therefore answer identically, by construction rather than by convention. What it buys is concrete: a plain bounded-string treatment accepts @@ -242,7 +242,7 @@ contract the gate emits, and by the architecture fitness tests that keep the lay ### OptionalErrorCodeExtensions -> MMCA.Common.Application · `MMCA.Common.Application.Validation` · `MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:19` · Level 0 · class (internal static) +> MMCA.Common.Application · `MMCA.Common.Application.Validation` · `MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:20` · Level 0 · class (internal static) - **What it is**: a one-method internal helper that lets every reusable rule fragment in `CommonValidationRules.cs` accept an *optional* machine-readable error code without any of them @@ -260,7 +260,7 @@ contract the gate emits, and by the architecture fitness tests that keep the lay an `ErrorCode` meant for a program. Left alone, FluentValidation fills the code with the *validator's* name (`"NotEmptyValidator"`, `"MaximumLengthValidator"`), which is useless to a caller that wants to branch on "the question text was missing" rather than "some non-empty rule failed somewhere". The - class remarks (`CommonValidationRules.cs:11-18`) record the failure mode this helper exists to close: + class remarks (`CommonValidationRules.cs:12-19`) record the failure mode this helper exists to close: module validators used to skip the shared bases entirely and hand-write the rule chain for the single reason that the bases set a message but no code, so a validator needing a stable code had nothing to compose with. `[Rubric §9, API & Contract Design]` assesses whether the contract a client codes @@ -302,16 +302,16 @@ contract the gate emits, and by the architecture fitness tests that keep the lay | Type | File:Line | Rule chain (all with `.WithOptionalErrorCode(errorCode)` per rule) | |------|-----------|-------------------------------------------------------------------| - | `RequiredStringRules` | `CommonValidationRules.cs:41` | `NotEmpty()` + `MaximumLength(maxLength)` | - | `OptionalStringRules` | `CommonValidationRules.cs:53` | `MaximumLength(maxLength)` only (nullable selector, null passes) | - | `EmailRules` | `CommonValidationRules.cs:64` | `NotEmpty()` + `EmailAddress()` + `MaximumLength(maxLength)` | - | `PositiveIntRules` | `CommonValidationRules.cs:100` | `GreaterThan(0)` over `int` | - | `PositiveDecimalRules` | `CommonValidationRules.cs:111` | `GreaterThan(0)` over `decimal` | - | `NonNegativeIntRules` | `CommonValidationRules.cs:122` | `GreaterThanOrEqualTo(0)` over `int` | - | `RequiredIdRules` | `CommonValidationRules.cs:142` | `NotEmpty()` over `TId : notnull` (rejects `0` and `Guid.Empty`) | - | `OptionalPositiveIdRules` | `CommonValidationRules.cs:161` | `GreaterThan(default(TId))` over `TId?` (null passes) | - | `PasswordRules` | `CommonValidationRules.cs:174` | `NotEmpty()` + `MinimumLength(8)` + `MaximumLength(128)` | - | `StrongPasswordRules` | `CommonValidationRules.cs:188` | all of `PasswordRules` plus four `Matches(...)` regexes | + | `RequiredStringRules` | `CommonValidationRules.cs:42` | `NotEmpty()` + `MaximumLength(maxLength)` | + | `OptionalStringRules` | `CommonValidationRules.cs:54` | `MaximumLength(maxLength)` only (nullable selector, null passes) | + | `EmailRules` | `CommonValidationRules.cs:65` | `NotEmpty()` + `EmailAddress()` + `MaximumLength(maxLength)` | + | `PositiveIntRules` | `CommonValidationRules.cs:101` | `GreaterThan(0)` over `int` | + | `PositiveDecimalRules` | `CommonValidationRules.cs:112` | `GreaterThan(0)` over `decimal` | + | `NonNegativeIntRules` | `CommonValidationRules.cs:123` | `GreaterThanOrEqualTo(0)` over `int` | + | `RequiredIdRules` | `CommonValidationRules.cs:143` | `NotEmpty()` over `TId : notnull` (rejects `0` and `Guid.Empty`) | + | `OptionalPositiveIdRules` | `CommonValidationRules.cs:162` | `GreaterThan(default(TId))` over `TId?` (null passes) | + | `PasswordRules` | `CommonValidationRules.cs:175` | `NotEmpty()` + `MinimumLength(8)` + `MaximumLength(128)` | + | `StrongPasswordRules` | `CommonValidationRules.cs:191` | the same 8 to 128 band, read from `PasswordComplexity`, plus four `Matches(...)` over its generated regexes | The eleventh class in the file, [`AbsoluteUrlRules`](#absoluteurlrulest), is a Level 6 sibling (it reaches into a domain invariant) and is covered separately. @@ -319,8 +319,11 @@ contract the gate emits, and by the architecture fitness tests that keep the lay - **Depends on**: FluentValidation's `AbstractValidator` (NuGet, primer §3), `System.Linq.Expressions.Expression>` and `System.Globalization.CultureInfo` (BCL, `CommonValidationRules.cs:1-3`), and the file-local - [`OptionalErrorCodeExtensions`](#optionalerrorcodeextensions). No first-party dependencies beyond - that: these sit at the very bottom of the Application layer, which is why they carry no invariant + [`OptionalErrorCodeExtensions`](#optionalerrorcodeextensions). The one first-party dependency beyond + that is `StrongPasswordRules`'s use of + [`PasswordComplexity`](group-08-auth.md#passwordcomplexity) from `MMCA.Common.Shared.Auth` + (`using` at `CommonValidationRules.cs:5`), which is why it sits one Level above its nine siblings. + The rest sit at the very bottom of the Application layer, which is why they carry no invariant constants and take `maxLength` as an argument. Bridged onto commands automatically by [`CommandRequestValidator`](#commandrequestvalidatortcommand-trequest). @@ -374,10 +377,19 @@ contract the gate emits, and by the architecture fitness tests that keep the lay clause and no selector recompiled per validation pass. - `PasswordRules` (`:176-180`) takes only `(selector, errorCode)`: its messages are fixed strings, not parameterized by a field name. It enforces non-empty plus a length band of 8 to 128. - - `StrongPasswordRules` (`:190-198`) repeats that band and adds four `Matches(...)` calls whose - regex literals are inline in the source: `"[A-Z]"`, `"[a-z]"`, `"\\d"` and `"[^a-zA-Z\\d]"` for - uppercase, lowercase, digit and special character (`:195-198`). The doc comment on `PasswordRules` - (`:170-171`) points callers who need complexity at `StrongPasswordRules` instead. + - `StrongPasswordRules` (`:193-201`) keeps the same messages but takes nothing inline: the band + comes from `PasswordComplexity.MinimumLength` and `MaximumLength` (`:196-197`), and its four + `Matches(...)` calls pass the `Uppercase`, `Lowercase`, `Digit` and `SpecialCharacter` regex + properties (`:198-201`). Those are `[GeneratedRegex]` members over the Unicode classes `\p{Lu}`, + `\p{Ll}`, `\p{Nd}` and `[^\p{L}\p{Nd}]`, each with a 1000 ms match timeout + (`MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/PasswordComplexity.cs:27-43`), so an accented + or CJK letter counts as a letter, never as the special character. The doc comment (`:187-188`) + states the reason: `PasswordComplexity` is the one definition the client form attribute + [`PasswordComplexityAttribute`](group-15-common-ui-framework.md#passwordcomplexityattribute) also + evaluates, so server and client cannot disagree, and `PasswordRuleParityTests` + (`MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Ui/PasswordRuleParityTests.cs:36`) + compares the two verdicts. `PasswordRules` still hard-codes `8` and `128` (`:180-181`); its doc + comment (`:171-172`) points callers who need complexity at `StrongPasswordRules` instead. - **Why it's built this way**: these fragments are the DRY core of the validation story. Because they live in `MMCA.Common.Application` and are generic, both ADC and Store inherit identical, tested field @@ -697,7 +709,7 @@ contract the gate emits, and by the architecture fitness tests that keep the lay behaviour of this validator. ### AbsoluteUrlRules -> MMCA.Common.Application · `MMCA.Common.Application.Validation` · `MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:85` · Level 6 · class +> MMCA.Common.Application · `MMCA.Common.Application.Validation` · `MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:86` · Level 6 · class - **What it is**: a reusable fragment for an **optional URL** field. It applies two rules: a maximum length, and a check that any supplied value is an absolute `http` or `https` URI. A `null` or empty @@ -710,7 +722,7 @@ contract the gate emits, and by the architecture fitness tests that keep the lay what puts this fragment at Level 6 while its file-mates sit at Level 0. - **Concept introduced, delegating a validator predicate to the domain invariant.** `[Rubric §11, Security]` assesses whether untrusted input is constrained before it reaches a sink. - The rule's own doc comment states the threat plainly (`CommonValidationRules.cs:77-83`): a + The rule's own doc comment states the threat plainly (`CommonValidationRules.cs:78-84`): a length-only bound accepts `javascript:` and `data:` values, and those become executable the moment a link `href` or an image `src` renders them. The scheme check is therefore not cosmetic URL hygiene, it is the boundary control for stored script injection through a user-supplied link. @@ -722,13 +734,13 @@ contract the gate emits, and by the architecture fitness tests that keep the lay implementation of the scheme test, it calls the domain invariant, so the request-level answer and the entity-level answer cannot diverge. - **Walkthrough**: - - The constructor (`CommonValidationRules.cs:87-90`) takes + - The constructor (`CommonValidationRules.cs:88-91`) takes `(Expression> selector, string fieldName, int maxLength, string? errorCode = null)` and builds a single chain: `MaximumLength(maxLength)` with the interpolated "{fieldName} cannot be longer than {maxLength} characters" message, then `.Must(BeAnAbsoluteHttpUrl)` with "{fieldName} must be an absolute http or https URL". Each rule ends in `.WithOptionalErrorCode(errorCode)` (`:89`, `:90`), which returns the rule untouched when the code - is `null` (`CommonValidationRules.cs:30-32`), so every existing caller that omits it keeps + is `null` (`CommonValidationRules.cs:31-33`), so every existing caller that omits it keeps FluentValidation's default per-rule code. - The length message uses `string.Create(CultureInfo.InvariantCulture, ...)` while the scheme message is a plain interpolation: the first embeds a number and so pins the culture, the second embeds only @@ -849,7 +861,7 @@ contract the gate emits, and by the architecture fitness tests that keep the lay every broken rule in one response instead of one per round trip. The remaining three call sites are in [`AuthenticationServiceBase`](group-08-auth.md#authenticationservicebasetuser), which validates its request before touching the user store and passes the method name as `source`: - `nameof(LoginAsync)` (`MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:165`), + `nameof(LoginAsync)` (`MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:114`), `nameof(RegisterAsync)` (`:193`), and `nameof(RefreshTokenAsync)` (`:273`), each wrapping the result in `Result.Failure(...)`. Covered by [`ValidationFailureExtensionsTests`](group-28-testing-infrastructure.md#per-project-test-rollup). @@ -860,7 +872,7 @@ contract the gate emits, and by the architecture fitness tests that keep the lay by an unrelated extension in the gRPC layer, [`ResultGrpcExtensions`](group-13-grpc-contracts.md#resultgrpcextensions) declares an `extension(Metadata? trailers)` block with its own `ToErrors()` - (`MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:146` and `:165`) that decodes + (`MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:151` and `:165`) that decodes errors out of gRPC trailers. Different receiver, different assembly, no relationship to this one. ### CurrentUserServiceExtensions diff --git a/docs-src/onboarding/group-07-persistence-ef-core.md b/docs-src/onboarding/group-07-persistence-ef-core.md index 3a430d0a..4c21e921 100644 --- a/docs-src/onboarding/group-07-persistence-ef-core.md +++ b/docs-src/onboarding/group-07-persistence-ef-core.md @@ -5,7 +5,9 @@ aggregate and a row in a database. It is the single largest group in the guide b lot of load. One abstract [`ApplicationDbContext`](#applicationdbcontext) base with a sealed subclass per engine ([`SQLServerDbContext`](#sqlserverdbcontext), [`PostgreSQLDbContext`](#postgresqldbcontext), [`CosmosDbContext`](#cosmosdbcontext), -[`SqliteDbContext`](#sqlitedbcontext)); four EF Core save interceptors that turn a plain +[`SqliteDbContext`](#sqlitedbcontext)) and a stateless engine registry +([`IDataSourceEngine`](#idatasourceengine), [`DataSourceEngines`](#datasourceengines)) that answers every +engine-specific question as data; four EF Core save interceptors that turn a plain `SaveChangesAsync` into audit stamping, tenant enforcement, transactional domain-event capture, and a field-level change trail; a small repository family behind an interface-segregated contract ([`IReadRepository`](#ireadrepositorytentity-tidentifiertype), @@ -40,48 +42,50 @@ contracts whose implementations are composed in is an abstract primary-constructor class over EF's `DbContext`. It holds the cross-cutting model configuration every engine shares. It applies a global soft-delete query filter to every non-owned [`IAuditableEntity`](group-02-domain-building-blocks.md#iauditableentity) using a runtime-built -expression tree, registered as a **named** `"SoftDelete"` filter (`ApplicationDbContext.cs:448-460`, -name at `:469`); it applies a second named `"Tenant"` filter to every non-owned -[`ITenantEntity`](group-02-domain-building-blocks.md#itenantentity) (`ApplicationDbContext.cs:509-567`); -it configures the `RowVersion` optimistic-concurrency token, mapped as a SQL Server `rowversion` or as -a plain application-managed token on other providers (`ApplicationDbContext.cs:582-602`); and it maps +expression tree, registered as a **named** `"SoftDelete"` filter (`ApplicationDbContext.cs:453-467`, +name at `:474`); it applies a second named `"Tenant"` filter to every non-owned +[`ITenantEntity`](group-02-domain-building-blocks.md#itenantentity) (`ApplicationDbContext.cs:514-574`); +it configures the `RowVersion` optimistic-concurrency token by the engine's +[`RowVersionStrategy`](#rowversionstrategy), mapped as a SQL Server `rowversion` or as +a plain token the audit interceptor stamps on PostgreSQL and SQLite (`ApplicationDbContext.cs:576-601`, +strategy read at `:591`); and it maps the framework's own bookkeeping tables so every relational database carries its own -([`OutboxMessage`](group-04-events-outbox.md#outboxmessage) at `:658-716`, -[`InboxMessage`](group-04-events-outbox.md#inboxmessage) at `:718-733`, -[`InternalCommandMessage`](#internalcommandmessage) at `:748-790`, -[`ScheduledJobEntry`](group-14-module-system-composition.md#scheduledjobentry) at `:799-836`, -[`AuditTrailEntry`](#audittrailentry) at `:844-873`), each with the filtered indexes its poll path and -its retention sweep need (`IX_OutboxMessages_Pending` at `:691`, `IX_OutboxMessages_Processed` at -`:698`, the keyed-ordering index `IX_OutboxMessages_Ordering` at `:710`, -`IX_InboxMessages_MessageId` at `:726`, `IX_InboxMessages_ProcessedOn` at `:731`, -`IX_InternalCommands_Pending` at `:776`, `IX_InternalCommands_Processed` at `:783`, -`IX_InternalCommands_DeadLettered` at `:788`, -`IX_ScheduledJobs_NextRunOn` at `:833`, `IX_AuditTrailEntries_Entity` at `:865`, -`IX_AuditTrailEntries_ChangedOn` at `:871`). Four of the seven framework tables are **gated**: the +([`OutboxMessage`](group-04-events-outbox.md#outboxmessage) (indexes at `:694-713`), +[`InboxMessage`](group-04-events-outbox.md#inboxmessage) (indexes at `:729-734`), +[`InternalCommandMessage`](#internalcommandmessage) at `:751`, +[`ScheduledJobEntry`](group-14-module-system-composition.md#scheduledjobentry) at `:802`, +[`AuditTrailEntry`](#audittrailentry) at `:847`), each with the filtered indexes its poll path and +its retention sweep need (`IX_OutboxMessages_Pending` at `:694`, `IX_OutboxMessages_Processed` at +`:701`, the keyed-ordering index `IX_OutboxMessages_Ordering` at `:713`, +`IX_InboxMessages_MessageId` at `:729`, `IX_InboxMessages_ProcessedOn` at `:734`, +`IX_InternalCommands_Pending` at `:779`, `IX_InternalCommands_Processed` at `:786`, +`IX_InternalCommands_DeadLettered` at `:791`, +`IX_ScheduledJobs_NextRunOn` at `:836`, `IX_AuditTrailEntries_Entity` at `:868`, +`IX_AuditTrailEntries_ChangedOn` at `:874`). Four of the seven framework tables are **gated**: the job table is mapped only when `Scheduler:Enabled` is set AND this context targets the `Default` source -(jobs are host-scoped, `:322-325`), the trail table only when `AuditTrail:Enabled` is set, on every +(jobs are host-scoped, `:327-329`), the trail table only when `AuditTrail:Enabled` is set, on every relational source (a trail row must commit with the change it describes, and a transaction does not -span databases, `:327`), the refresh-session table only when `RefreshSessions:Enabled` is set AND -this context targets the source that setting names (`:332-334`, applied at `:889-897`), and the +span databases, `:332`), the refresh-session table only when `RefreshSessions:Enabled` is set AND +this context targets the source that setting names (`:336-339`, applied at `:892-900`), and the permission-grant table only when the host called `AddStoredPermissionGrants` AND this context targets -the source `Authentication:PermissionGrants:DataSourceName` names (`:338`, resolved at `:910-916`, -applied at `:933-941`). That fourth gate has no configuration flag of its own: the DI call itself is +the source `Authentication:PermissionGrants:DataSourceName` names (`:343`, resolved at `:913-918`, +applied at `:936-944`). That fourth gate has no configuration flag of its own: the DI call itself is the opt-in, expressed as the marker type [`PermissionGrantModelGate`](#permissiongrantmodelgate) (`.../Persistence/Auth/PermissionGrantModelGate.cs:19`, reasoning at `:6-12`). A host that opted into none of them keeps the model it had before those features shipped, so none of those tables ever appears in its migrations. The outbox, the inbox and the internal-command queue are deliberately **not** gated: they are mapped into every relational source whether or not this host drains them, so -flipping `InternalCommands:Enabled` is never a migration (`:735-748`). +flipping `InternalCommands:Enabled` is never a migration (`:737-751`). -Its `SaveChangesAsync(userId, ...)` overload (`ApplicationDbContext.cs:189-203`) is the one entry +Its `SaveChangesAsync(userId, ...)` overload (`ApplicationDbContext.cs:194-208`) is the one entry point handlers care about: it stashes the current user id in `CurrentSaveUserId` so the audit interceptor can read it, delegates to `base`, then clears it in a `finally` so a later internal save -cannot silently reuse the previous caller's identity (`:197-201`). The base also overrides both +cannot silently reuse the previous caller's identity (`:197`, `:206`). The base also overrides both `SaveChanges` overloads purely to run change detection once per save and suppress it for the rest, through the `DetectChangesOnce` helper and its [`DetectChangesScope`](#detectchangesscope) disposable -(`:209-215`, `:242`, `:269-282`): each interceptor's `ChangeTracker.Entries()` call would +(`:212-218`, `:245-249`, helper at `:274`): each interceptor's `ChangeTracker.Entries()` call would otherwise trigger a full `DetectChanges`, so a save paid three snapshot comparisons where one -suffices, and the previous auto-detect setting is restored on the way out (`:281`). +suffices, and the previous auto-detect setting is restored when the scope is disposed (`:274`). The design decision that shapes this whole group is stated in the base's own doc comment: **one context class per engine, one instance per physical data source** (`ApplicationDbContext.cs:34-40`). @@ -93,9 +97,12 @@ database name). To keep EF from silently reusing the first-built model for every [`DataSourceModelCacheKeyFactory`](#datasourcemodelcachekeyfactory) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/DataSourceModelCacheKeyFactory.cs:16`) keys EF's model cache by context type plus physical source name plus the design-time flag (`:19-22`), -and is installed by the base in `OnConfiguring` (`ApplicationDbContext.cs:345`). This is deliberately +and is installed by the base in `OnConfiguring` (`ApplicationDbContext.cs:350`). This is deliberately not a per-module context split: one sealed context per engine over the abstract base is -[ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)'s ruling. +[ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)'s ruling. Each instance +also exposes the engine it targets as an internal `Engine` property resolved through +[`DataSourceEngines`](#datasourceengines) (`ApplicationDbContext.cs:61`), which is how the base, the +interceptors and the factory ask an engine question without naming an engine. `SQLServerDbContext` adds the provider-specific touches: a per-environment command timeout read from [`PersistenceSettings`](#persistencesettings) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/PersistenceSettings.cs:10`, bound from @@ -114,9 +121,9 @@ carries a rule the rest of the group depends on: with retry-on-failure enabled a `BeginTransactionAsync` must run inside `Database.CreateExecutionStrategy().ExecuteAsync` (`:60-62`). [`SqliteDbContext`](#sqlitedbcontext) (`.../DbContexts/SqliteDbContext.cs:12`) is the same shape with `UseSqlite` and its own migrations assembly (`:24-33`), and -[`CosmosDbContext`](#cosmosdbcontext) (`.../DbContexts/CosmosDbContext.cs:14`) deliberately does not +[`CosmosDbContext`](#cosmosdbcontext) (`.../DbContexts/CosmosDbContext.cs:15`) deliberately does not call `base.OnModelCreating`, because the relational bookkeeping tables have no place in a document -store; it re-applies only the filters it needs (`:89-95`). +store; it re-applies only the filters it needs (`:139-145`). [`PostgreSQLDbContext`](#postgresqldbcontext) (`.../DbContexts/PostgreSQLDbContext.cs:23`) is the fourth engine class ([ADR-113](https://ivanball.github.io/docs/adr/113-postgresql-as-a-first-class-engine.html)), and its mapping conventions deliberately match `SQLServerDbContext` rather than PostgreSQL house @@ -133,35 +140,38 @@ without a zone (`:5-24`). One more base-context decision belongs here: (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SensitiveDataLoggingGate.cs:24`) decides whether EF may render parameter **values** into logs and exception messages, and the answer is an AND rather than a single switch: the host must both ask for it and report itself as Development -(`ApplicationDbContext.cs:340`, `:362-369`). A null `IHostEnvironment`, which is what design-time +(`ApplicationDbContext.cs:345`, `:367-371`). A null `IHostEnvironment`, which is what design-time tooling and a directly-constructed test context have, counts as not Development, so unknown fails closed (`SensitiveDataLoggingGate.cs:10-22`). That is [Rubric §11, Security] in one helper. ## SaveChanges as an interceptor pipeline The base context resolves its interceptors from DI in `OnConfiguring` -(`ApplicationDbContext.cs:285-317`), and **registration order is execution order**. The audit +(`ApplicationDbContext.cs:290-322`), and **registration order is execution order**. The audit interceptor runs first, the tenant interceptor between it and the domain-event interceptor (so the -outbox rows describe an entity whose tenant is already final, `:258-271`), and the change-trail -interceptor last, because it diffs the final values (`:273-281`). Two of the four are resolved with +outbox rows describe an entity whose tenant is already final, `:300-312`), and the change-trail +interceptor last, because it diffs the final values (`:314-322`). Two of the four are resolved with `GetService` rather than `GetRequiredService`: a directly-constructed test context or a host that never called `AddAuditTrail` must still build, and their absence has to read as "the feature is off" rather than fail every context construction. [`AuditSaveChangesInterceptor`](#auditsavechangesinterceptor) -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:22`) +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:30`) runs on `SavingChanges`: it walks every tracked [`IAuditableEntity`](group-02-domain-building-blocks.md#iauditableentity), stamps `CreatedOn/By` plus -`LastModifiedOn/By` on `Added` (`:56-60`), and on `Modified` marks the two `Created*` properties -unmodified before re-stamping `LastModified*` (`:67-71`), reading the timestamp from an injected +`LastModifiedOn/By` on `Added` (`:65-69`), and on `Modified` marks the two `Created*` properties +unmodified before re-stamping `LastModified*` (`:77-80`), reading the timestamp from an injected `TimeProvider` and the user id from `CurrentSaveUserId` (falling back to `default` as the -system-operation sentinel, `:49-50`). It also writes the soft-delete stamps, and it drives them off +system-operation sentinel, `:57-58`). It also writes the soft-delete stamps, and it drives them off the **transition** of the `IsDeleted` flag rather than its value: `DeletedOn/By` are written when the -flag goes false to true and cleared when it goes back (`:92-105`, with the original value read at -`:83-84`), so a later update to an already-deleted row keeps the stamps of the delete that produced +flag goes false to true and cleared when it goes back (`StampSoftDeleteTransition`, doc at `:111`, with the original value read at +`:107-109`), so a later update to an already-deleted row keeps the stamps of the delete that produced it, exactly as `CreatedOn/By` survive every update. This is why the domain declares audit fields with private setters and never writes them: the interceptor sets them centrally through -`entry.Property(...).CurrentValue`, bypassing setter visibility. That is the +`entry.Property(...).CurrentValue`, bypassing setter visibility. On an engine whose +[`RowVersionStrategy`](#rowversionstrategy) is `ClientStamped` (PostgreSQL, SQLite) it also writes a fresh +`RowVersion` on every insert and update, so the UPDATE's WHERE clause detects a concurrent writer where no +server-generated row version exists (`:59`, `:74`, `:83`, `StampRowVersion` at `:94-105`). That is the [Rubric §12, Performance & Scalability] payoff, one enforcement point instead of copy-paste in every handler. @@ -185,7 +195,7 @@ publishing every integration event twice. The captured state is parked in a (`:62`), so it is cleaned up automatically when the context is disposed. A third weak table (`:77`) holds a per-context capture exclusion set: `BeginCaptureExclusion` / `EndCaptureExclusion` (`:179-195`) let [`DbContextFactory`](#dbcontextfactory) name exactly the entries it hides from an -`IDENTITY_INSERT` round, so an event is never serialized and cleared a round before the insert that +explicit-key insert round, so an event is never serialized and cleared a round before the insert that justifies it. The exclusion is by instance rather than by entity state on purpose: a state-based filter would also drop events raised on an already-saved aggregate, which is how the identity module publishes its registration events (`:172-176`). @@ -209,8 +219,9 @@ throws, the interceptor logs a warning and signals the outbox to retry from the than losing the event (`:346-354`). The synchronous `SavedChanges` path cannot await a dispatcher at all, so it removes the captured events, signals the outbox, and leaves delivery entirely to it (`:124-137`). Two conditions take the everything-in-process branch instead: Cosmos DB has no relational -outbox table, so the base exposes a `SupportsOutbox` flag (`ApplicationDbContext.cs:172`) that -[`CosmosDbContext`](#cosmosdbcontext) overrides to `false` (`CosmosDbContext.cs:69`); and a host can +outbox table, so the interceptor asks the context's engine whether it is relational +(`DataSourceEngineCapabilities.IsRelational`, read at `:127` and `:236`), which is false only for +[`CosmosDbContext`](#cosmosdbcontext)'s engine; and a host can turn the outbox off outright, which the interceptor reads once from the message-bus options into `_outboxEnabled` (`:55`) and honors at the same branch (`:236`, `:269-275`). This split, atomic persistence plus best-effort immediate dispatch with a durable fallback, is the at-least-once contract @@ -229,7 +240,7 @@ kinship plainly: like `OutboxMessage` it is framework bookkeeping rather than do deliberately not an `IAuditableEntity`, carries no soft-delete flag (no global query filter applies to it), no audit stamps and no concurrency token; its concurrency control is an explicit claim lease (`:9-19`). [`InternalCommandScheduler`](#internalcommandscheduler) -(`.../InternalCommands/InternalCommandScheduler.cs:39`) writes that row on the **same** scoped +(`.../InternalCommands/InternalCommandScheduler.cs:34`) writes that row on the **same** scoped [`IDbContextFactory`](#idbcontextfactory) the calling handler's repositories use, which is the whole atomicity story: inside an `ITransactional` command the factory has already begun a transaction on every context it hands out, so the row commits with the aggregate change or rolls back with it, and @@ -238,7 +249,13 @@ also carries an [`InternalCommandOrigin`](#internalcommandorigin) (`.../InternalCommands/InternalCommandOrigin.cs:16`): the scheduling user, roles, tenant, correlation id and trace context, because without it a deferred command would run as an anonymous tenant-less system call, which is both an authorization hole (an `IRequiresPermission` command would be denied) -and an audit hole (its writes would carry the system sentinel, `:1-9`). +and an audit hole (its writes would carry the system sentinel, `:1-9`). The snapshot is taken by the scoped +[`InternalCommandOriginCapture`](#internalcommandorigincapture) +(`.../InternalCommands/InternalCommandOriginCapture.cs:21`, `Capture` at `:28-39`), which the scheduler +calls as it builds the row (`InternalCommandScheduler.cs:38`, `:86`). It is scoped like the request +services it reads, because the snapshot must describe this request's caller, and it flattens roles through +the same helper the outbox capture uses, so the two hops store one shape +(`InternalCommandOriginCapture.cs:13-17`). [`InternalCommandProcessor`](#internalcommandprocessor) (`.../InternalCommands/Processing/InternalCommandProcessor.cs:46`) is the drain: a background service @@ -328,7 +345,7 @@ audit-trail sweep does: that database has a queue table nothing else would drain Multi-tenancy ([ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html)) is two independent halves that meet in this group. The **read** half is the named `Tenant` query filter the base context applies to every non-owned [`ITenantEntity`](group-02-domain-building-blocks.md#itenantentity) -(`ApplicationDbContext.cs:509-568`). Three details make it work: the filter body embeds the context +(`ApplicationDbContext.cs:514-573`). Three details make it work: the filter body embeds the context instance as a constant typed as `ApplicationDbContext`, so EF rewrites it to the executing context and lifts `CurrentTenantId` into a SQL parameter, letting **one compiled model serve every tenant** (`:413-419`, `:434-436`); the predicate is `CurrentTenantId == null || e.TenantId == CurrentTenantId`, @@ -384,7 +401,7 @@ too (`:42-48`, `:56-61`, `:76-77`, `:106-107`). [`TenantResolutionStrategy`](#te cannot pick its own tenant), `Header` is for service-to-service calls behind a trusted gateway, and `Host` is **defined but not implemented**, present only so the configuration contract stays stable (`:8-27`). [`TenancySettingsValidator`](#tenancysettingsvalidator) -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:23`) +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:24`) is what makes that honest: registered with `ValidateOnStart`, it fails the boot when a resolution order names `Host` (`:54-65`) and checks each tenant's overrides against the resolved physical sources when [`IDataSourceResolver`](#idatasourceresolver) is registered (`:14-18`, `:39-42`), on the reasoning that @@ -402,7 +419,16 @@ sweeps expand their work list through [`TenantDataSourceTargets`](#tenantdatasou (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:51-81`), which emits the shared target for every source plus one extra [`TenantDataSourceTarget`](#tenantdatasourcetarget) (`:15`) per tenant that overrides a source, because -a tenant with its own database is invisible to the shared sweep (`:36-40`). +a tenant with its own database is invisible to the shared sweep (`:36-40`). The sweeps do not each +assemble that list themselves: [`FrameworkTableTargets`](#frameworktabletargets) +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/FrameworkTableTargets.cs:32`) +is the one answer to "which databases hold the framework's own relational tables on this host", shared +by the outbox and internal-command processors, both cleanup services, both operator surfaces and the +audit-trail sweep (`:14-19`). `Relational()` returns every relational source backing a registered +entity, deduplicated and expanded per tenant (`:43-44`), and its overload adds the source the caller's +own settings name as the table's home when that engine is relational (`:54-60`). It is recomputed per +call, so late-loading module assemblies are seen, and Cosmos sources are skipped because they host none +of those tables (`:21-22`). ## Recording what changed, the audit trail @@ -507,7 +533,7 @@ original values for optimistic concurrency on both the aggregate and any child i [`IRowVersioned`](group-02-domain-building-blocks.md#irowversioned) (`:75-93`, [ADR-035](https://ivanball.github.io/docs/adr/035-optimistic-concurrency.html)). Two set-based escape hatches sit beside the tracked path: `ExecuteDeleteAsync`, which the interface itself documents as -bypassing domain events, audit stamps, and soft-delete (`IRepository.cs:445-455`), and +bypassing domain events, audit stamps, and soft-delete (`IRepository.cs:447-457`), and `ExecuteUpdateAsync` (`:457-479`), the contention-proof conditional update whose guard predicate lets the database arbitrate two racing callers with no rowversion retry loop. The latter is described through the persistence-agnostic @@ -515,7 +541,7 @@ through the persistence-agnostic builder by [`UpdatePropertySetterBuilder`](#updatepropertysetterbuildertentity) (`.../Repositories/UpdatePropertySetterBuilder.cs:14`), which is what keeps EF Core out of the Application layer, and because `ExecuteUpdate` bypasses the interceptor pipeline the repository stamps -`LastModifiedOn/By` itself unless the caller assigned them (`EFRepository.cs:140-151`). +`LastModifiedOn/By` itself unless the caller assigned them (`EFRepository.cs:140-154`). The read repository does not compose queries by hand. [`SpecificationEvaluator`](#specificationevaluator) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/SpecificationEvaluator.cs:21`) @@ -534,7 +560,7 @@ string-include path and the specification path cannot drift (`:78-79`, requested sort property or fails validation (`:35`), orders by `(sortKey, Id)` with the identifier tie-break that makes the order total (`:50-51`), and builds the composite seek predicate against the last row of the previous page (`:109`), so `GetPageByCursorAsync` -(`IRepository.cs:316`, implemented at `EFReadRepository.cs:617`) seeks straight to the boundary instead +(`IRepository.cs:318`, implemented at `EFReadRepository.cs:617`) seeks straight to the boundary instead of counting past every skipped row. Exactly one sort key is supported, by design (`KeysetQueryBuilder.cs:17-20`). That is [Rubric §12, Performance and Scalability] expressed as a contract rather than as advice. @@ -547,58 +573,66 @@ builds a repository over a given context and conditionally wraps it in a MiniPro when `UseMiniProfiler` is on (`:34-38`, `:58-62`), adding timing without the base repository knowing, and it activates both through a cached compiled `ObjectFactory` rather than reflecting on every call (`:70-84`). [`DbContextFactory`](#dbcontextfactory) -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:46`) +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:48`) is the scoped coordinator: it caches one [`ApplicationDbContext`](#applicationdbcontext) per -[`DataSourceKey`](#datasourcekey) so every repository in a scope shares one change tracker (`:87-118`), -gives each new context a live tenant accessor rather than a copied value (`:129-140`), and enlists a -late-created context into an already-open transaction (`:107-108`). It is also the database-per-tenant +[`DataSourceKey`](#datasourcekey) so every repository in a scope shares one change tracker (`:98-131`), +gives each new context live scope accessors rather than copied values (`:114`, doc at `:136`), and enlists a +late-created context into an already-open transaction (`:116-119`). It is also the database-per-tenant routing point: when the scope's tenant overrides a source, the context is created against that tenant's connection string while keeping the **original** `DataSourceKey`, which is what lets one compiled model -serve every tenant's database (`:143-173`), and a cached routed context is refused to a second tenant -rather than silently serving the first tenant's rows (`:176-198`). Its save loop runs up to -`MaxSavePasses` (3, `:53`) passes over the cached contexts, because dispatching events in-process can -materialize a context for a source nobody had touched yet (`:237-251`), and it closes with a hard +serve every tenant's database (`ResolveTenantOverride`, `:162-168`), and a cached routed context is refused to a second tenant +rather than silently serving the first tenant's rows (`:123-124`, guard at `:196-210`), just as a shared +context created before the tenant resolved is refused to a tenant that overrides that source +(`:126-128`, `:228-231`). Its save loop runs up to +`MaxSavePasses` (3, `:63`) passes over the cached contexts, because dispatching events in-process can +materialize a context for a source nobody had touched yet (`:278-296`), and it closes with a hard assertion: any context still reporting `ChangeTracker.HasChanges()` when the unit of work returns throws -rather than silently discarding those changes (`:259-269`). +rather than silently discarding those changes (`:298-315`). -Because there can be more than one physical source in play, `ExecuteInTransactionAsync` (`:499-544`) +Because there can be more than one physical source in play, `ExecuteInTransactionAsync` (`:534`) runs the operation under the first transactional context's execution strategy, opens a transaction per -source, and commits them sequentially with no two-phase commit (`TryCommit` at `:621-660`); +source, and commits them sequentially with no two-phase commit (`TryCommit` at `:697`); cross-source consistency is the outbox's job, and the doc comment is explicit that a commit failure on -the second source leaves the first one committed (`:488-498`). The method is re-entrant: a nested call +the second source leaves the first one committed (`:522-532`). The method is re-entrant: a nested call joins the ambient transaction instead of opening a second one, so only the outermost call may begin, -commit, roll back, or flush (`:503-511`). A returned failed -[`Result`](group-01-result-error-handling.md#result) rolls back exactly like an exception (`:563-570`), +commit, roll back, or flush (`:538-546`). A returned failed +[`Result`](group-01-result-error-handling.md#result) rolls back exactly like an exception (`:598-604`), which is what makes [ADR-013](https://ivanball.github.io/docs/adr/013-result-pattern.html)'s Result-over-exceptions rule safe for partial persistence; rollback also drops the deferred event -dispatch (`:443-447`), the deferred flush runs only after every commit has succeeded (`:576-584`), and a +dispatch (`:600-603`), the deferred flush runs only after every commit has succeeded (`:614-621`), and a retry resets the change tracker first so the aborted attempt's `Added` entities are not inserted twice -(`ResetForRetry` at `:742-751`). `DbContextFactory` further carries the -`SET IDENTITY_INSERT` machinery ([`IdentityInsertGroup`](#identityinsertgroup) at `:405`, the per-table -save split at `:284-403`) for importing entities with explicit database-generated ids one table at a -time, and the `MigrateAsync` / `HasPendingMigrationsAsync` sweeps (`:686-739`) over every source whose -resolved [`PhysicalDataSource`](#physicaldatasource)`.UsesMigrations` says a migrations pipeline owns -its schema (`PhysicalDataSource.cs:41-47`, target selection at `DbContextFactory.cs:724-743`). +(`ResetForRetry` at `:814-821`, called at `:564`). Before committing, the unit also refuses to discard +work: anything still tracked but unsaved throws, except an internal-command row that was only +**enrolled** on the context while the transaction was open, which is saved inside the transaction so it +commits with the change that scheduled it (`FlushEnrolledCommandsBeforeCommitAsync`, `:649-680`, called +at `:608`). `DbContextFactory` further carries the engine-neutral half of the explicit-key insert +(`RequestExplicitKeyInsert` at `:321`, `SaveWithExplicitKeyInsertAsync` at `:329`): when one is requested +and the context's engine has an [`IExplicitKeyInsertDialect`](#iexplicitkeyinsertdialect), the save is +split into one [`ExplicitKeyInsertGroup`](#explicitkeyinsertgroup) round per table with the toggle on +(`:262-266`, `:292-294`), for importing entities with explicit database-generated ids. It also runs the +`MigrateAsync` / `HasPendingMigrationsAsync` sweeps (`:762`, `:798`) over every source whose resolved +[`PhysicalDataSource`](#physicaldatasource)`.IsMigrationTarget` holds: the source `UsesMigrations` under +its engine's [`MigrationPolicy`](#migrationpolicy), and it is not an optional source left without a +connection string (`PhysicalDataSource.cs:42-60`, target selection at `DbContextFactory.cs:782-795`). [`UnitOfWork`](#unitofwork) sits on top, resolving an entity's physical source through [`IDataSourceService`](#idatasourceservice), handing the matching context to the factory, and caching the resulting repository per closed generic interface type (`UnitOfWork.cs:33-66`). The physical creation itself runs through [`PhysicalDbContextFactory`](#physicaldbcontextfactory) -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/PhysicalDbContextFactory.cs:16`), -a singleton that switches on the key's engine to construct the right context class (`:41-48`) and whose +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/PhysicalDbContextFactory.cs:19`), +a singleton that hands construction to the key's engine through `IDataSourceEngine.CreateDbContext` (`:32`) and whose doc comment warns it must **never** be pooled, because each instance carries per-source constructor -state that pooling would smear across databases (`:10-14`). The interfaces +state that pooling would smear across databases (`:13-17`). The interfaces ([`IUnitOfWork`](#iunitofwork), [`IDbContextFactory`](#idbcontextfactory), [`IPhysicalDbContextFactory`](#iphysicaldbcontextfactory), [`IRepositoryFactory`](#irepositoryfactory)) keep the application layer talking to abstractions. Every member of that factory family has its own -section below, including the two types that exist only because of what the coordinator does: -[`IdentityInsertGroup`](#identityinsertgroup), the per-table batch the `SET IDENTITY_INSERT` loop saves -one at a time, and [`TransactionCommitAmbiguousException`](#transactioncommitambiguousexception) +section below, including the type that exists only because of what the coordinator does, +[`TransactionCommitAmbiguousException`](#transactioncommitambiguousexception) (`.../Factory/TransactionCommitAmbiguousException.cs:22`), which the commit path raises when the commit itself fails with an outcome nobody can vouch for, naming each physical source's outcome (committed, ambiguous, or rolled back) so the partial state is observable rather than inferred (`:57-70`, -`DbContextFactory.cs:667-672`). That exception is thrown **outside** the execution strategy on purpose -(`DbContextFactory.cs:554-560`), because the strategy walks an exception's whole inner chain to decide +`DbContextFactory.cs:697-722`). That exception is thrown **outside** the execution strategy on purpose +(`DbContextFactory.cs:575-576`), because the strategy walks an exception's whole inner chain to decide retriability and would otherwise re-run the operation on top of a possibly-durable commit. ## Routing an entity to its database @@ -611,25 +645,28 @@ entity resolves to a [`DataSourceKey`](#datasourcekey) one of Cosmos DB, SQLite, SQL Server, or PostgreSQL, and `Name` is a **physical** database name defaulting to `"Default"` (`DataSourceKey.cs:18-23`). Two layers compute this. [`DataSourceResolver`](#datasourceresolver) -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:15`), +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:16`), the [`IDataSourceResolver`](#idatasourceresolver) singleton (`.../DataSources/IDataSourceResolver.cs:15`), builds the logical-to-physical map once per engine from configuration and hands out the resolved -[`PhysicalDataSource`](#physicaldatasource) (`:93-107`, `:143-148`). Named sources with no connection +[`PhysicalDataSource`](#physicaldatasource) (`BuildEngineMap` at `:194`, `ResolveLogical` at `:92`, `GetPhysical` at `:182`). Named sources with no connection string, or whose connection identity equals the top-level one, **collapse onto the `Default` source**, so a host with no `DataSources` section behaves exactly like a single-database monolith -(`DataSourceResolver.cs:260-305`), and sources sharing a connection identity collapse onto one canonical -key named after their alphabetically-first member (`:343-370`, canonical name at `:353`). What the +(`DataSourceResolver.cs:294-344`), and sources sharing a connection identity collapse onto one canonical +key named after their alphabetically-first member (`:380-411`, canonical name at `:386`). What the `Default` source is built from is itself resolved rather than assumed: the top-level `ConnectionStrings` section first, and when it names nothing for this engine while `DataSources` names exactly one database on it, that database becomes `Default`, which is what keeps the framework's own -tables working in a host that declares its database only under `DataSources` (`:179-236`, captured in -the private [`DefaultSeed`](#defaultseed) record at `:177`). Identity is the connection string compared -ordinally, with the Cosmos database name appended because one account hosts many databases (`:467-476`). +tables working in a host that declares its database only under `DataSources` (`:219-285`, captured in +the private [`DefaultSeed`](#defaultseed) record at `:216`). Identity is the connection string compared +ordinally, with the database name appended on an engine whose `ConnectionIdentityIncludesDatabaseName` +is set (Cosmos, because one account hosts many databases, `:494-495`). The resolver fails fast when two logical names collapsing to one database declare conflicting -migrations assemblies (`:433-465`) and logs a warning when a separate SQL Server source falls back to -the Default migrations assembly (`:360-368`, message at `:499`). It also substitutes the host's own +migrations assemblies (`:464-480`) and logs a warning when a separate source on an `Always` +[`MigrationPolicy`](#migrationpolicy) engine (SQL Server) falls back to +the Default migrations assembly (`:393`, message at `:508`). It also substitutes the host's own engine for a request naming an engine the host does not configure, in a fixed relational-first -preference order (`:29-30`, applied at `:97` and `:109-129`), because every table the framework owns is relational and honoring +preference order ranked by each engine's `SubstitutionPriority` (`:35-36`, applied through +`SubstituteUnconfiguredEngine` at `:127`), because every table the framework owns is relational and honoring an unconfigured engine literally handed those components an empty connection string. What the resolver reads is two bound settings objects. @@ -675,29 +712,99 @@ use so the outbox processor's per-poll call allocates nothing (`:75-80`, `:157-1 registry reads the same attributes the model configuration reads, routing and model contents agree by construction, and configurations that implement a provider interface directly without the attributed base classes are deliberately skipped as legacy (`:163-176`). [`DataSourceService`](#datasourceservice) -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceService.cs:11`) is the thin +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceService.cs:12`) is the thin application-facing facade over [`IEntityDataSourceRegistry`](#ientitydatasourceregistry), and it answers the one question navigation loading needs: two entities support EF `.Include()` only when their physical -keys are equal and the engine is not Cosmos (`DataSourceService.cs:30-31`). +keys are equal and the engine is relational (`DataSourceService.cs:32`). + +## One registry answers every engine question + +Four engines would otherwise mean a four-way `switch` in every component that maps a key, quotes a +column, picks a migrations assembly or decides whether a source has an outbox. The framework keeps +exactly one place that knows engines apart. [`IDataSourceEngine`](#idatasourceengine) +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/IDataSourceEngine.cs:18`) +is the internal contract, and its doc comment states the goal: no call site branches on +[`DataSource`](#datasource) (`:9-16`). Its members fall into three groups. The descriptive ones say how +the engine is configured: its rank when the resolver substitutes it for an unconfigured engine (`:29`), +the open generic entity-configuration interface whose implementations belong to its model (`:32`), the +setting that names its migrations assembly (`:38`), whether connection identity includes the database +name (`:44`), and how to read its connection string and migrations assembly from the top-level section, +a named entry or a tenant override (`:49-69`). The capabilities are one value (`:74`). The behavior hooks +are the few places the engine has to act rather than answer: build its sealed context over one physical +source, never pooled (`:83-86`), apply its table or container mapping and key generation to one entity +(`:95-97`), add covering index columns (`:103`), quote a column and build the soft-delete index +predicate (`:108-113`), and expose an explicit-key insert dialect, or none (`:119`). + +[`DataSourceEngines`](#datasourceengines) (`.../Engines/DataSourceEngines.cs:19`) is the registry: a +static class holding the four engines (`:22-28`) in a frozen dictionary keyed by the shipped +`DataSource` value (`:30-31`), whose `For` throws on an unregistered value (`:40-43`). It is static +rather than a DI service on purpose: several consumers run where no container is reachable (the +model-building conventions, `EntityTypeConfiguration.ApplyEngineConventions`, `SoftDeleteFilterSql`, +`IndexBuilderExtensions`), and the engines are stateless facts about a closed enum only Common can +extend, so a DI extension point would buy no extensibility; adding a fifth engine is one class plus one +line (`:6-18`). The four implementations are +[`SQLServerDataSourceEngine`](#sqlserverdatasourceengine) (`.../Engines/SQLServerDataSourceEngine.cs:19`), +[`PostgreSQLDataSourceEngine`](#postgresqldatasourceengine) (`.../Engines/PostgreSQLDataSourceEngine.cs:17`), +[`SqliteDataSourceEngine`](#sqlitedatasourceengine) (`.../Engines/SqliteDataSourceEngine.cs:16`) and +[`CosmosDataSourceEngine`](#cosmosdatasourceengine) (`.../Engines/CosmosDataSourceEngine.cs:18`), with +substitution priorities 0, 1, 2 and 3 (`SQLServerDataSourceEngine.cs:29`, +`PostgreSQLDataSourceEngine.cs:27`, `SqliteDataSourceEngine.cs:26`, `CosmosDataSourceEngine.cs:28`), +which is the relational-first order the resolver substitutes in. + +What differs between engines is a [`DataSourceEngineCapabilities`](#datasourceenginecapabilities) record +(`.../Engines/DataSourceEngineCapabilities.cs:23`) of five values (`:24-28`). Only facts whose values +genuinely differ get a member, and everything that splits the engines exactly along the relational line +is the single `IsRelational` flag: same-source `.Include()`, transactions, raw parameterized SQL, +indexes, foreign keys whose delete behavior matters, `Any(predicate)` translation and the framework's +own relational tables (`:3-20`). That one flag is what the outbox capture, the transaction coordinator, +the conventions and the `.Include()` check read (`DomainEventSaveChangesInterceptor.cs:236`, +`DbContextFactory.cs:827-828`, `SoftDeleteUniqueIndexConvention.cs:43`, `DataSourceService.cs:32`). +[`MigrationPolicy`](#migrationpolicy) (`.../Engines/MigrationPolicy.cs:8`) decides whether a source is +migrated or created outright: `Never` for Cosmos, `WhenAssemblyConfigured` for SQLite and PostgreSQL, +and `Always` for SQL Server, where a named source with no assembly falls back to the Default source's +with a warning (`:10-20`; values at `SQLServerDataSourceEngine.cs:42`, `PostgreSQLDataSourceEngine.cs:40`, +`SqliteDataSourceEngine.cs:39`, `CosmosDataSourceEngine.cs:42`). +[`RowVersionStrategy`](#rowversionstrategy) (`.../Engines/RowVersionStrategy.cs:8`) decides the +concurrency token: `None` on Cosmos, `StoreGenerated` (a `rowversion`, never stamped) on SQL Server, and +`ClientStamped` on PostgreSQL and SQLite, where the audit interceptor writes it (`:10-17`). +`ConnectionStringRequired` is true only for SQL Server, so a SQL Server source with no connection string +is a startup failure rather than an optional source to skip (`DataSourceEngineCapabilities.cs:10-13`, +`SQLServerDataSourceEngine.cs:43`), and `NullsSortFirstAscending` is false only for PostgreSQL, which +keyset pagination has to know (`DataSourceEngineCapabilities.cs:21`, `PostgreSQLDataSourceEngine.cs:43`). + +The explicit-key insert shows the split between engine-specific and engine-neutral work most clearly. +[`IExplicitKeyInsertDialect`](#iexplicitkeyinsertdialect) (`.../Engines/IExplicitKeyInsertDialect.cs:13`) +is the engine half, and only an engine that refuses an explicit value for a store-generated key without a +session toggle has one: SQL Server, whose engine class implements the dialect itself +(`SQLServerDataSourceEngine.cs:19`, `:49`) and emits `SET IDENTITY_INSERT [schema].[table] ON` or `OFF` +(`:163`). Every other engine returns `null` and the save runs unchanged +(`PostgreSQLDataSourceEngine.cs:47`, `SqliteDataSourceEngine.cs:46`, `CosmosDataSourceEngine.cs:49`). The +dialect finds the added entries carrying explicit store-generated keys, grouped by table +(`IExplicitKeyInsertDialect.cs:21`), each group an [`ExplicitKeyInsertGroup`](#explicitkeyinsertgroup) +record of schema, table and entries (`.../Engines/ExplicitKeyInsertGroup.cs:12`), and builds the toggle +statement (`IExplicitKeyInsertDialect.cs:28`); grouping the rounds, hiding other tables' entries, +excluding their domain events and pinning the connection stay in [`DbContextFactory`](#dbcontextfactory) +(`:5-12`). Every engine type is internal, so a consumer never sees one: the public surface is still the +`DataSource` value on an attribute, and this registry is what turns that value into behavior. ## Three model-finalizing conventions and the identifier mapping The base context adds all three of its conventions in `ConfigureConventions` -(`ApplicationDbContext.cs:373-394`), and each exists because a cross-cutting policy above would +(`ApplicationDbContext.cs:378-399`), and each exists because a cross-cutting policy above would otherwise produce an invalid or surprising model. [`CrossDataSourceDegradeConvention`](#crossdatasourcedegradeconvention) -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:33`, -added at `ApplicationDbContext.cs:382`) is what lets routing be lazy and attribute-driven and still +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:34`, +added at `ApplicationDbContext.cs:387`) is what lets routing be lazy and attribute-driven and still produce a valid EF model. When a relationship's two ends live in different physical sources it removes the foreign key (a database cannot enforce an FK into another database), keeps the declared scalar FK columns plus a compensating index unless an existing index already covers them as a prefix, ignores the CLR navigation members, and drops the foreign entity types out of this database's model entirely -(`CrossDataSourceDegradeConvention.cs:38-89`, `:110-140`). It works through EF's **mutable** model API +(`CrossDataSourceDegradeConvention.cs:39-90`, `:110-140`). It works through EF's **mutable** model API rather than convention builders, because the soft-delete and concurrency helpers have already promoted every entity type to the Explicit configuration source (`:22-24`, `:44-46`), it eagerly drops the convention-created FK index before the coverage check so the column does not end up unindexed (`:126-131`), and it skips the compensating index on Cosmos, which auto-indexes everything and rejects -explicit index definitions (`:65`, `:118-121`). Runtime navigation across sources then flows through the +explicit index definitions, which the convention reads as the engine's `IsRelational` capability (`:66`). Runtime navigation across sources then flows through the [`INavigationPopulator`](group-11-navigation-populators.md#inavigationpopulatorin-tentity) batch-loading machinery in [Group 11](group-11-navigation-populators.md). Crucially, when every entity collapses onto one physical source (the monolith case) nothing is foreign and the convention returns @@ -706,7 +813,7 @@ the same codebase run as a monolith today and as split services later without a [Rubric §7, Microservices Readiness] claim. [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) -(`.../Conventions/SoftDeleteUniqueIndexConvention.cs:33`, added at `ApplicationDbContext.cs:387`) closes +(`.../Conventions/SoftDeleteUniqueIndexConvention.cs:34`, added at `ApplicationDbContext.cs:392`) closes a smaller but sharper hole. Soft-delete hides a row from queries, but a plain unique index still enforces uniqueness against it, so "deleting" a speaker would permanently block re-creating one with the same email. The convention adds an `IsDeleted = 0` filter to every unique index on a soft-deletable entity @@ -714,11 +821,11 @@ and **extends** rather than skips a hand-authored filter, appending its clause w already narrowed on something else keeps its own predicate and still stops enforcing uniqueness against soft-deleted rows; a filter that already constrains the soft-delete column is left exactly as it is, so the append is idempotent (`SoftDeleteUniqueIndexConvention.cs:36-81`). It is a no-op for Cosmos -(`:41-42`). The predicate text itself is not built inline: both this convention and the opt-in +(`:43`, any non-relational engine). The predicate text itself is not built inline: both this convention and the opt-in `HasSoftDeleteFilter` extension go through [`SoftDeleteFilterSql`](#softdeletefiltersql)`.Build` -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:27-36`), which -reads the column name from the model and quotes it per engine (brackets for SQL Server, double quotes for -SQLite, `null` for Cosmos), with a normalized comparison for the already-present check (`:53-56`), so the +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:17`, `Build` at `:35`), which +reads the column name from the model and asks the engine for the predicate and the quoting +(`IDataSourceEngine.BuildSoftDeleteFilter` and `QuoteColumn`, `:35`, `:70`; `null` where filtered indexes are unsupported, which is Cosmos), with a normalized comparison for the already-present check (`:57-58`), so the automatic and the hand-authored path can never disagree. [`IndexBuilderExtensions`](#indexbuilderextensions) (`.../Configuration/IndexBuilderExtensions.cs:10`) is that opt-in half, an `extension(IndexBuilder)` block for the case the convention deliberately leaves @@ -728,8 +835,8 @@ model finalization, after module configurations have declared their indexes and relationship discovery, which is why they can see the finished picture. The third convention is [`RestrictDeleteByDefaultConvention`](#restrictdeletebydefaultconvention) -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/RestrictDeleteByDefaultConvention.cs:41`, -added last of the three at `ApplicationDbContext.cs:394` so it never stamps a relationship the +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/RestrictDeleteByDefaultConvention.cs:42`, +added last of the three at `ApplicationDbContext.cs:399` so it never stamps a relationship the cross-source convention has already removed, [ADR-119](https://ivanball.github.io/docs/adr/119-restrict-delete-by-default.html)). EF's own default is the opposite posture: a required relationship cascades, so deleting a parent silently deletes its @@ -745,12 +852,12 @@ finalized model can be audited without re-running the convention, and `DeleteBeh reads exactly that annotation to assert every cascading relationship was opted into on purpose (`:21-31`). Like its siblings it is a no-op for Cosmos, which has no foreign key constraints to restrict and where stamping a decision the provider cannot enforce would only make the audit lie -(`:33-37`). +(`:33-37`, checked at `:66`). `ConfigureConventions` ends with the one piece that is not a convention at all: [`StronglyTypedIdModelConfiguration`](#stronglytypedidmodelconfiguration)`.Apply` (`.../Persistence/Conventions/StronglyTypedIdModelConfiguration.cs:21`, called at -`ApplicationDbContext.cs:396-404`, +`ApplicationDbContext.cs:401-409`, [ADR-115](https://ivanball.github.io/docs/adr/115-strongly-typed-identifiers-opt-in.html)). A host that calls `AddStronglyTypedIds` registers a `StronglyTypedIdRegistry`, and this declares one **pre-convention type mapping** per declared identifier, so every property of that type maps to the @@ -758,7 +865,7 @@ primitive it wraps: keys, cross-module scalar references, owned-type members and columns alike, on SQL Server, PostgreSQL, SQLite and Cosmos, because it runs on the one base context (`:7-20`). Absent the registry it is a no-op, which is the default posture: the primitive identifier aliases stay the identifier model and no existing entity changes -(`ApplicationDbContext.cs:396-404`). Pre-convention is the load-bearing word. The actual conversion is +(`ApplicationDbContext.cs:401-409`). Pre-convention is the load-bearing word. The actual conversion is [`StronglyTypedIdValueConverter`](#stronglytypedidvalueconvertertself-tvalue) (`.../Persistence/Conversions/StronglyTypedIdValueConverter.cs:28`), with [`NullableStronglyTypedIdValueConverter`](#nullablestronglytypedidvalueconvertertself-tvalue) @@ -773,7 +880,7 @@ wrapper is therefore neither a schema change nor a migration (`StronglyTypedIdVa Concrete entity configurations derive from the engine-aware [`EntityTypeConfiguration`](#entitytypeconfigurationtentity-tidentifiertype) -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:29`) +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:27`) or, more commonly, one of the fixed-engine shims like [`EntityTypeConfigurationSQLServer`](#entitytypeconfigurationsqlservertentity-tidentifiertype) (`.../EntityTypeConfigurationSQLServer.cs:17`), which is that base annotated @@ -784,12 +891,13 @@ or, more commonly, one of the fixed-engine shims like (`.../EntityTypeConfigurationCosmos.cs:18`) and [`EntityTypeConfigurationPostgreSQL`](#entitytypeconfigurationpostgresqltentity-tidentifiertype) (`.../EntityTypeConfigurationPostgreSQL.cs:22`) as its three siblings. The base reads the attribute off its own -runtime type and throws a clear error when it is missing (`EntityTypeConfiguration.cs:45-48`), then -applies the engine's conventions in `ApplyEngineConventions` (`:57-99`): SQL Server gets a table in a -module schema (`:65-72`), SQLite a plain table (`:74-81`), and Cosmos a per-module container with the -entity id as partition key (`:83-94`), each mapping key generation according to the entity's +runtime type and throws a clear error when it is missing (`EntityTypeConfiguration.cs:43-46`), then +applies the engine's conventions in `ApplyEngineConventions` (`:69-77`), which hands the mapping to the +engine's `ApplyKeyAndTableMapping` hook (`:77`): SQL Server gets a table in a +module schema (`SQLServerDataSourceEngine.cs:94-103`), SQLite a plain table, and Cosmos a per-module container with the +entity id as partition key (`CosmosDataSourceEngine.cs:86-99`), each mapping key generation according to the entity's [`EntityTypeExtensions`](group-02-domain-building-blocks.md#entitytypeextensions)`.IsIdValueGenerated` -marker (`:61`): `ValueGeneratedOnAdd` for database identity, `ValueGeneratedNever` otherwise, or the +marker (`SQLServerDataSourceEngine.cs:103`, `CosmosDataSourceEngine.cs:98-99`): `ValueGeneratedOnAdd` for database identity, `ValueGeneratedNever` otherwise, or the [`CosmosIntIdValueGenerator`](#cosmosintidvaluegenerator) for Cosmos, which has no server-side identity and increments a process-level counter seeded from the Unix timestamp (`.../ValueGenerators/CosmosIntIdValueGenerator.cs:16-25`). Because the engine is a single attribute and @@ -802,7 +910,7 @@ the base implements all four provider marker interfaces all over the common [`IEntityTypeConfigurationBase`](#ientitytypeconfigurationbasetentity-tidentifiertype)), **moving an entity between engines is a one-line attribute change with no configuration-body edits** -(`EntityTypeConfiguration.cs:11-25`). The shared +(`EntityTypeConfiguration.cs:9-23`). The shared [`EntityTypeConfigurationBase`](#entitytypeconfigurationbasetentity-tidentifiertype) (`.../EntityTypeConfigurationBase.cs:19`) handles the one universal concern: excluding the in-memory `DomainEvents` collection from mapping (`:25-32`). Value objects reach the database through this layer @@ -828,7 +936,7 @@ is not a schema change. Discovery runs through [`ModelBuilderExtensions`](#modelbuilderextensions)`.ApplyAllConfigurations` (`.../DbContexts/ModelBuilderExtensions.cs:10`, an `extension(ModelBuilder)` block at `:12`), which the base calls with an entity filter so each database's model receives only its own entities -(`ApplicationDbContext.cs:950-978`, filter application at `ModelBuilderExtensions.cs:57-60`), over the +(`ApplicationDbContext.cs:953-987`, filter application at `ModelBuilderExtensions.cs:57-60`), over the assemblies supplied by [`IEntityConfigurationAssemblyProvider`](#ientityconfigurationassemblyprovider) and its [`DefaultEntityConfigurationAssemblyProvider`](#defaultentityconfigurationassemblyprovider) implementation (`.../Persistence/DefaultEntityConfigurationAssemblyProvider.cs:12`), which scans loaded @@ -889,7 +997,7 @@ token hash as a fixed-width non-Unicode column, uniquely indexed because that is answers and because a collision across users would let one account's token validate against another's session (`:44-49`, `:60-66`), plus a `(UserId, RevokedAt)` index for the per-user family questions the session cap, reuse detection and sign-out-everywhere all ask (`:68-71`). The base context calls it from -the gated `ConfigureRefreshSessions` (`ApplicationDbContext.cs:889-897`) precisely because a downstream +the gated `ConfigureRefreshSessions` (`ApplicationDbContext.cs:892-900`) precisely because a downstream app runs on the sealed engine contexts and has no `OnModelCreating` of its own to override. [`EFRefreshSessionStore`](#efrefreshsessionstore) (`.../Auth/EFRefreshSessionStore.cs:30`) is the [`IRefreshSessionStore`](group-08-auth.md#irefreshsessionstore) implementation over that table; it @@ -1063,7 +1171,7 @@ survives a module being pulled out into its own service. - **Concept introduced, closing the gap between a shape-valid channel key and an authorized one.** `[Rubric §11, Security]` assesses whether an authorization decision that a hub cannot make structurally (any well-formed key like `event:42` passes the configured pattern) is pushed out to a pluggable check - rather than left implicit. The doc comment (`IChannelJoinAuthorizer.cs:27-34`) frames the contract as a + rather than left implicit. The doc comment (`IChannelJoinAuthorizer.cs:18-25`) frames the contract as a single yes/no gate: given the connection's principal and the requested `channelKey`, decide whether the connection may join. Because the parameter is optional and defaults to `null` on the consuming hub, an existing host that never registers an implementation keeps working unchanged and every authenticated @@ -1071,7 +1179,7 @@ survives a module being pulled out into its own service. user must register one deliberately. - **Walkthrough**: one method. - `CanJoinAsync(ClaimsPrincipal? user, string channelKey, CancellationToken cancellationToken = default)` - (`IChannelJoinAuthorizer.cs:35-38`): returns `true` to admit the connection to `channelKey`. `user` is the + (`IChannelJoinAuthorizer.cs:26-29`): returns `true` to admit the connection to `channelKey`. `user` is the connection's principal (the hub requires authentication, so this is never null in practice); `channelKey` is the caller-requested channel, already validated against the shape pattern before this check runs. - **Why it's built this way**: keeping the check as a single optional interface, rather than a required @@ -1087,7 +1195,7 @@ survives a module being pulled out into its own service. `LiveChannelJoinAuthorizer` (`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Live/LiveChannelJoinAuthorizer.cs:43-45`) is wired with `AddScoped()` - (`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:234`). + (`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:231`). ### INativePushSender > MMCA.Common.Application · `MMCA.Common.Application.Interfaces.Infrastructure.Notifications` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Notifications/INativePushSender.cs:10` · Level 0 · interface @@ -1121,9 +1229,9 @@ survives a module being pulled out into its own service. native push an opt-in capability rather than a hard dependency of every host. - **Where it's used**: injected into [`SendPushNotificationHandler`](group-10-notifications.md#sendpushnotificationhandler) - (`MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:31`) + (`MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:36`) alongside the SignalR sender. [`NullNativePushSender`](group-14-module-system-composition.md#nullnativepushsender) is registered by default with - `TryAddTransient` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:320`) and + `TryAddTransient` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:330`) and [`AzureNotificationHubNativePushSender`](group-14-module-system-composition.md#azurenotificationhubnativepushsender) replaces it when a hub is configured (`DependencyInjection.Notifications.cs:98`). @@ -1166,7 +1274,7 @@ survives a module being pulled out into its own service. (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Notifications/DevicesController.cs:27`) and passes the authenticated user id on both calls, `UpsertAsync` at `DevicesController.cs:44` and `DeleteAsync` at `DevicesController.cs:68`. [`NullPushDeviceRegistrar`](group-14-module-system-composition.md#nullpushdeviceregistrar) is the default - registration (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:321`); + registration (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:331`); [`AzureNotificationHubDeviceRegistrar`](group-14-module-system-composition.md#azurenotificationhubdeviceregistrar) replaces it when a hub is configured (`DependencyInjection.Notifications.cs:99`). @@ -1271,10 +1379,10 @@ survives a module being pulled out into its own service. [`CosmosDbContext`](#cosmosdbcontext) at `CosmosDbContext.cs:18`, [`SqliteDbContext`](#sqlitedbcontext) at `SqliteDbContext.cs:15`); consumed by [`EntityDataSourceRegistry`](#entitydatasourceregistry) to build the entity-to-source map (`EntityDataSourceRegistry.cs:22`) and by - [`PhysicalDbContextFactory`](#physicaldbcontextfactory) (`PhysicalDbContextFactory.cs:19`). The default + [`PhysicalDbContextFactory`](#physicaldbcontextfactory) (`PhysicalDbContextFactory.cs:22`). The default implementation [`DefaultEntityConfigurationAssemblyProvider`](#defaultentityconfigurationassemblyprovider) is registered with `TryAddSingleton` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:58`), and + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:59`), and [`DesignTimeDbContextHelper`](#designtimedbcontexthelper) substitutes its own nested `ExplicitAssemblyProvider` for `dotnet ef` runs (`DesignTimeDbContextHelper.cs:193` and `:185`). @@ -1312,7 +1420,7 @@ survives a module being pulled out into its own service. silently reaching list endpoints. - **Where it's used**: implemented by [`EFQueryableExecutor`](#efqueryableexecutor) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/EFQueryableExecutor.cs:11`), registered with - `TryAddSingleton` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:105`). Injected + `TryAddSingleton` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:111`). Injected into [`EntityQueryPipeline`](group-03-querying-specifications.md#entityquerypipeline) (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/EntityQueryPipeline.cs:13`) and into the framework's own notification handlers, for example @@ -1320,7 +1428,7 @@ survives a module being pulled out into its own service. (`GetMyNotificationsHandler.cs:18`). ### IRawSqlQueryExecutor -> MMCA.Common.Application · `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRawSqlQueryExecutor.cs:23` · Level 0 · interface +> MMCA.Common.Application · `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRawSqlQueryExecutor.cs:24` · Level 0 · interface - **What it is**: an escape hatch for interpolated raw SQL reads, materializing rows into a scalar or an unmapped DTO, for the cases a `Queryable`-based repository cannot express. @@ -1335,13 +1443,18 @@ survives a module being pulled out into its own service. translate. - **Walkthrough**: two methods, both generic in the row shape `T`. - `QueryAsync(FormattableString sql, CancellationToken cancellationToken = default)` - (`IRawSqlQueryExecutor.cs:29-31`): runs the statement and materializes every row into `T`, empty when + (`IRawSqlQueryExecutor.cs:31`): runs the statement and materializes every row into `T`, empty when none match. - `QuerySingleOrDefaultAsync(FormattableString sql, CancellationToken cancellationToken = default)` - (`IRawSqlQueryExecutor.cs:39-41`): runs a statement expected to select at most one row, throwing - `InvalidOperationException` when the statement selected more than one (`IRawSqlQueryExecutor.cs:35`). - - Both document `NotSupportedException` when the host's default data source is not a relational engine - (`IRawSqlQueryExecutor.cs:27`, `:36`), since raw SQL has no meaning against Cosmos DB. + (`IRawSqlQueryExecutor.cs:39`): runs a statement expected to select at most one row, throwing + `InvalidOperationException` when the statement selected more than one (`IRawSqlQueryExecutor.cs:38`). + - Neither method documents a runtime "not relational" failure. The type-level remarks + (`IRawSqlQueryExecutor.cs:18-21`) move that check to registration instead: the executor is registered + only when the host's default physical data source is on a relational engine, so a Cosmos-default host + (its own query language, no SQL command surface to parameterize) has no registration at all, and a + service that injects this interface there fails when the container is validated. The gate is + `DataSourceEngines.For(defaultEngine).Capabilities.IsRelational` in + `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:409-411`. - **Why it's built this way**: an interpolated `FormattableString` parameter, rather than a plain `string` plus a parameter array, keeps the parameterization automatic at the call site: a developer writes ordinary string interpolation and the implementation is responsible for turning each hole into a @@ -1349,7 +1462,8 @@ survives a module being pulled out into its own service. - **Where it's used**: implemented by [`EFRawSqlQueryExecutor`](#efrawsqlqueryexecutor) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/EFRawSqlQueryExecutor.cs:2`), registered - in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs`. Exercised by the + conditionally as scoped in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:411` + (covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/DependencyInjectionInfrastructureTests.cs`). Exercised by the architecture fitness base `MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Cqrs/RawSqlConventionTestsBase.cs` and its ADC concrete test `MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Cqrs/RawSqlConventionTests.cs`. @@ -1391,9 +1505,9 @@ survives a module being pulled out into its own service. which walks the inner-exception chain (`SqlServerUniqueConstraintViolationDetector.cs:48-68`) matching the two error numbers (`:34` and `:37`) with a message fallback for engines that report the same rejection in words (`:40`, `:43`, matched at `:63-64`). It is registered with `TryAddSingleton` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:115`), deliberately `TryAdd` so a + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:121`), deliberately `TryAdd` so a host on another engine can register its own implementation first and keep it - (`DependencyInjection.cs:112-114`). Consumers inject it into the `when` clause of a catch: + (`DependencyInjection.cs:118-120`). Consumers inject it into the `when` clause of a catch: [`GetOrCreateMyBadgeHandler`](group-22-engagement-module.md#getorcreatemybadgehandler) (`MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/CheckIns/UseCases/GetOrCreateMyBadge/GetOrCreateMyBadgeHandler.cs:21` and `:53`), @@ -1414,7 +1528,7 @@ survives a module being pulled out into its own service. *which* properties change and *to what*, and Infrastructure translates the description into the provider's set-based `UPDATE`. - **Depends on**: `System.Linq.Expressions` (BCL, `IUpdatePropertySetter.cs:1`) only. It is the parameter type of - [`IWriteRepository.ExecuteUpdateAsync`](#iwriterepositorytentity-tidentifiertype) (`IRepository.cs:476`), + [`IWriteRepository.ExecuteUpdateAsync`](#iwriterepositorytentity-tidentifiertype) (`IRepository.cs:478`), which the doc comment cross-references (`IUpdatePropertySetter.cs:7`). - **Concept introduced, describing a SET clause without leaking EF Core.** `[Rubric §3, Clean Architecture]` assesses whether the technology choice stays outside the inner layers; @@ -1443,7 +1557,7 @@ survives a module being pulled out into its own service. property names (`UpdatePropertySetterBuilder.cs:17` and `:64`) and exposes `SetsProperty` (`UpdatePropertySetterBuilder.cs:49`) so [`EFRepository`](#efrepositorytentity-tidentifiertype) can stamp `LastModifiedOn` and `LastModifiedBy` only when the caller did not - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:142-153`), + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:142-156`), keeping audit fields correct on a path that bypasses the save pipeline (`EFRepository.cs:19`). ### OutboxDeadLetter @@ -1474,10 +1588,10 @@ survives a module being pulled out into its own service. free, and keeping it in the Application layer beside its interface means an admin surface can render dead letters without referencing the EF entity or the Infrastructure assembly. - **Where it's used**: built by the Infrastructure implementation's projection - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:91`), from + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:86`), from rows matching the dead-letter predicate `ProcessedOn == null && RetryCount >= maxRetries` - (`OutboxAdministration.cs:87`), collected across sources and returned oldest first - (`OutboxAdministration.cs:106`). + (`OutboxAdministration.cs:82`), collected across sources and returned oldest first + (`OutboxAdministration.cs:101`). ### DataSourceKey > MMCA.Common.Application · `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/DataSourceKey.cs:15` · Level 1 · record struct (readonly) @@ -1593,15 +1707,15 @@ survives a module being pulled out into its own service. keeps a bad source name on the same rails as any other user error. The retention sweep that would otherwise be a dead letter's only exit is [`OutboxCleanupService`](group-04-events-outbox.md#outboxcleanupservice), whose own comment points at `IOutboxAdministration.ReplayDeadLettersAsync` as the alternative - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxCleanupService.cs:147`). + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxCleanupService.cs:142`). - **Where it's used**: implemented by [`OutboxAdministration`](group-04-events-outbox.md#outboxadministration) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:35-42`), - registered with `TryAddScoped` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:205-206`), + registered with `TryAddScoped` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:215-216`), scoped because it creates one child scope per data source it visits and holds no state of its own - (`DependencyInjection.cs:217-218`). The implementation caps one page at 500 rows so an admin call cannot ask - for the whole table at once (`OutboxAdministration.cs:45`), rejects a negative `skip` or an out-of-range - `take` as validation errors (`OutboxAdministration.cs:47-51`), and expresses replay as one set-based - `UPDATE` per target (`OutboxAdministration.cs:148-149`) rather than as loaded entities. + (`DependencyInjection.cs:227-228`). The implementation caps one page at 500 rows so an admin call cannot ask + for the whole table at once (`OutboxAdministration.cs:40`), rejects a negative `skip` or an out-of-range + `take` as validation errors (`OutboxAdministration.cs:42-46`), and expresses replay as one set-based + `UPDATE` per target (`OutboxAdministration.cs:143-144`) rather than as loaded entities. - **Caveats / not-in-source**: the framework registers the service but ships no controller over it. No `MMCA.Common.API` endpoint and no ADC or Store call site references `ListDeadLettersAsync`, `ReplayDeadLettersAsync`, or `CountPendingAsync` today; outside the registration the only callers in the @@ -1618,21 +1732,21 @@ survives a module being pulled out into its own service. - **Depends on**: [`AuditableBaseEntity`](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype) (the `TEntity` constraint, `IRepository.cs:81`), [`BaseLookup`](group-12-api-hosting-mapping.md#baselookuptidentifiertype) (the lookup - projection, from `MMCA.Common.Shared.DTOs`, `IRepository.cs:5` and `:222`), + projection, from `MMCA.Common.Shared.DTOs`, `IRepository.cs:5` and `:224`), [`ISpecification`](group-03-querying-specifications.md#ispecificationtentity-tidentifiertype) - (from `MMCA.Common.Domain.Interfaces`, `IRepository.cs:3`, first used at `:149`), + (from `MMCA.Common.Domain.Interfaces`, `IRepository.cs:3`, first used in a signature at `:151`), [`Result`](group-01-result-error-handling.md#result) with [`KeysetPageRequest`](group-01-result-error-handling.md#keysetpagerequest) and [`KeysetCollectionResult`](group-01-result-error-handling.md#keysetcollectionresultt) (from - `MMCA.Common.Shared.Abstractions`, `IRepository.cs:4`, used at `:316-317`), and `System.Linq.Expressions` + `MMCA.Common.Shared.Abstractions`, `IRepository.cs:4`, used at `:318-319`), and `System.Linq.Expressions` (`IRepository.cs:1`). - **Concept introduced, the ISP-split repository ladder.** `[Rubric §1, SOLID]` assesses whether clients depend only on the members they use. `IRepository.cs` defines a deliberate ladder of ever-wider interfaces so a handler declares exactly the surface it needs: [`IEntityReader`](#ientityreadertentity-tidentifiertype) (by-id lookups, `IRepository.cs:21`), `IEntityQuerier` (set-returning reads, this type, `:80`), [`IReadRepository`](#ireadrepositorytentity-tidentifiertype) (reader plus querier plus raw `IQueryable`, - `:330`), [`IWriteRepository`](#iwriterepositorytentity-tidentifiertype) (mutations, `:367`), and - [`IRepository`](#irepositorytentity-tidentifiertype) (read plus write, `:467`). The doc comment says it + `:332`), [`IWriteRepository`](#iwriterepositorytentity-tidentifiertype) (mutations, `:369`), and + [`IRepository`](#irepositorytentity-tidentifiertype) (read plus write, `:495`). The doc comment says it outright (`IRepository.cs:68-71`): prefer this over `IReadRepository` when a handler needs `GetAllAsync`, `GetProjectedAsync`, or `CountAsync`. `[Rubric §12, Performance & Scalability]` also applies: every member here exists so the *database* answers the question. `GetProjectedAsync` takes an @@ -1649,11 +1763,11 @@ survives a module being pulled out into its own service. each parameter that carries it (for example `IRepository.cs:99-103` and `:126-130`). That is enforced downstream, where [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype) passes a one-element filter-name array to EF's named `IgnoreQueryFilters` overload - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:40`, used at + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:41`, used at `:52`, `:81`, `:102`, `:198`, `:288`, `:365`, `:379`, and `:445-446`) rather than EF's parameterless form; its - own comment (`EFReadRepository.cs:37`) spells out that dropping both would let a caller asking to see deleted + own comment (`EFReadRepository.cs:38`) spells out that dropping both would let a caller asking to see deleted rows silently read every tenant's data. The two named filters come from - [`ApplicationDbContext`](#applicationdbcontext) (`ApplicationDbContext.cs:469` and `:391`). + [`ApplicationDbContext`](#applicationdbcontext) (`ApplicationDbContext.cs:474` and `:391`). - **Walkthrough** - `GetAllAsync(IEnumerable includes, where?, orderBy?, select?, asTracking, ignoreQueryFilters, CancellationToken)` (`IRepository.cs:85-92`): the general collection read with optional includes, filter, ordering, and same-type @@ -1662,63 +1776,63 @@ survives a module being pulled out into its own service. - `GetProjectedAsync(select, where?, asTracking, ignoreQueryFilters, CancellationToken)` (`IRepository.cs:105-110`): SQL-side projection to an arbitrary result type. - `FirstOrDefaultAsync(where, includes?, asTracking, ignoreQueryFilters, CancellationToken)` - (`IRepository.cs:133-138`): the single-row predicate read. The remarks (`IRepository.cs:115-122`) name the + (`IRepository.cs:134-139`): the single-row predicate read. The remarks (`IRepository.cs:115-122`) name the alternative it exists to remove, `GetAllAsync` followed by an in-memory `FirstOrDefault`, which materializes the whole matching set to keep one entity, and warn that no ordering is applied, so "first" is whatever the provider returns first. - `FirstOrDefaultAsync(ISpecification specification, CancellationToken)` - (`IRepository.cs:148-150`): the deterministic counterpart. Unlike the predicate overload it honors the + (`IRepository.cs:150-152`): the deterministic counterpart. Unlike the predicate overload it honors the specification's ORDERING, so "first" means what the specification says it means - (`IRepository.cs:140-144`). - - `CountByAsync(keySelector, where?, CancellationToken)` (`IRepository.cs:167-171`): a `GROUP BY` count, - returning one dictionary entry per key that has at least one row. The remarks (`IRepository.cs:156-161`) + (`IRepository.cs:141-145`). + - `CountByAsync(keySelector, where?, CancellationToken)` (`IRepository.cs:169-173`): a `GROUP BY` count, + returning one dictionary entry per key that has at least one row. The remarks (`IRepository.cs:158-163`) explain why the member has to exist at all: the Application layer references no EF Core, so a handler needing a grouped count has no `IQueryable` to group and would otherwise project every matching row out of - the database and group client-side. `TKey` is constrained `notnull` (`IRepository.cs:171`). - - `SumByAsync(keySelector, sumSelector, where?, CancellationToken)` (`IRepository.cs:184-189`): the - grouped `SUM`, the counterpart of `CountByAsync` (`IRepository.cs:173-177`). - - `FindIncludingDeletedAsync(where, includes?, asTracking, CancellationToken)` (`IRepository.cs:215-219`): - the resurrection read (BR-135, `IRepository.cs:198`), returning a named tuple of `Active` and `SoftDeleted` - matches. The remarks (`IRepository.cs:195-205`) give the whole rationale: a create handler whose natural key + the database and group client-side. `TKey` is constrained `notnull` (`IRepository.cs:173`). + - `SumByAsync(keySelector, sumSelector, where?, CancellationToken)` (`IRepository.cs:186-191`): the + grouped `SUM`, the counterpart of `CountByAsync` (`IRepository.cs:175-179`). + - `FindIncludingDeletedAsync(where, includes?, asTracking, CancellationToken)` (`IRepository.cs:217-221`): + the resurrection read (BR-135, `IRepository.cs:199`), returning a named tuple of `Active` and `SoftDeleted` + matches. The remarks (`IRepository.cs:197-208`) give the whole rationale: a create handler whose natural key already exists as a soft-deleted row must reactivate that row rather than insert a duplicate the unique index will reject, and it needs both halves of the answer in one round trip, since an active match is a conflict, a soft-deleted match is the row to bring back, and neither is a plain insert. The `asTracking` - parameter carries its own warning (`IRepository.cs:209-212`): a caller intending to reactivate wants `true`, + parameter carries its own warning (`IRepository.cs:211-214`): a caller intending to reactivate wants `true`, otherwise the reactivation saves nothing. See [ADR-005](https://ivanball.github.io/docs/adr/005-soft-delete-vs-erasure.html) for the soft-delete policy this read serves. - `GetAllForLookupAsync(string nameProperty, where?, asTracking, CancellationToken)` - (`IRepository.cs:222-226`): returns lightweight + (`IRepository.cs:224-228`): returns lightweight [`BaseLookup`](group-12-api-hosting-mapping.md#baselookuptidentifiertype) id/name pairs for dropdowns without materializing full entities. It has no `ignoreQueryFilters` parameter: a lookup list never offers deleted rows. - - `CountAsync(CancellationToken)` (`IRepository.cs:229`) and - `CountAsync(Expression> where, CancellationToken)` (`IRepository.cs:232-234`): total and + - `CountAsync(CancellationToken)` (`IRepository.cs:231`) and + `CountAsync(Expression> where, CancellationToken)` (`IRepository.cs:234-236`): total and predicated counts. - `CountAsync(ISpecification specification, CancellationToken)` - (`IRepository.cs:243-245`): the specification-shaped count. The doc comment (`IRepository.cs:236-239`) states + (`IRepository.cs:245-247`): the specification-shaped count. The doc comment (`IRepository.cs:238-241`) states that ordering and paging on the specification are ignored deliberately, since a count of "page 3 of the matches" is never what a caller means. - `ListAsync(ISpecification specification, CancellationToken)` - (`IRepository.cs:260-262`): runs a specification and returns the matching entities. The remarks - (`IRepository.cs:250-256`) draw the line between the two specification shapes: a plain `ISpecification` + (`IRepository.cs:262-264`): runs a specification and returns the matching entities. The remarks + (`IRepository.cs:252-258`) draw the line between the two specification shapes: a plain `ISpecification` contributes its `Criteria` only, while a [`QuerySpecification`](group-03-querying-specifications.md#queryspecificationtentity-tidentifiertype) also contributes includes, ordering, paging, tracking, and soft-delete scope, so one object describes the whole read instead of five loose arguments. - `ListAsync(specification, Expression> select, CancellationToken)` - (`IRepository.cs:279-282`): the projecting overload, so only the selected columns leave the database. The - remarks (`IRepository.cs:268-273`) pin the ordering rule: the projection is applied **after** the + (`IRepository.cs:281-284`): the projecting overload, so only the selected columns leave the database. The + remarks (`IRepository.cs:270-275`) pin the ordering rule: the projection is applied **after** the specification's ordering and paging, so a paged specification still pages over entity rows and projects only that page, and includes on the specification are redundant here but not harmful. - `AnyAsync(ISpecification specification, CancellationToken)` - (`IRepository.cs:291-293`): existence by specification, with ordering and paging ignored for the same reason - as the specification `CountAsync` (`IRepository.cs:285-286`). + (`IRepository.cs:293-295`): existence by specification, with ordering and paging ignored for the same reason + as the specification `CountAsync` (`IRepository.cs:287-288`). - `GetPageByCursorAsync(KeysetPageRequest request, ISpecification? specification = null, CancellationToken)` - (`IRepository.cs:316-319`): one keyset ("seek") page plus the cursor for the next one, and the only member + (`IRepository.cs:318-321`): one keyset ("seek") page plus the cursor for the next one, and the only member here that returns a [`Result`](group-01-result-error-handling.md#result), because an unknown sort column and a malformed cursor are caller errors rather than exceptions and must never come back as a silent first page - (`IRepository.cs:306-310`). The remarks (`IRepository.cs:299-311`) state the trade in full: one index seek + (`IRepository.cs:308-312`). The remarks (`IRepository.cs:301-313`) state the trade in full: one index seek regardless of scroll depth and no skipped or repeated rows, against no random page access and no total count, with exactly one sort key supported and `Id` as the tie-break. - **Why it's built this way**: splitting reads into a focused querier lets a handler signal its access pattern @@ -1727,9 +1841,9 @@ survives a module being pulled out into its own service. already depends on the querier from needing a second dependency to run a specification. - **Where it's used**: query handlers needing collections, aggregates, specifications, or a keyset page; folded into [`IReadRepository`](#ireadrepositorytentity-tidentifiertype) - (`IRepository.cs:331`) and implemented concretely by + (`IRepository.cs:333`) and implemented concretely by [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype) (for example - `GetProjectedAsync` at `EFReadRepository.cs:74`, `CountByAsync` at `:127`, `SumByAsync` at `:150`, the + `GetProjectedAsync` at `EFReadRepository.cs:75`, `CountByAsync` at `:127`, `SumByAsync` at `:150`, the projecting `ListAsync` at `:464`), with [`EFReadRepositoryDecorator`](#efreadrepositorydecoratortentity-tidentifiertype) wrapping every call in a MiniProfiler step (`EFReadRepositoryDecorator.cs:17`, and `:45-83` for the newer @@ -1741,7 +1855,11 @@ survives a module being pulled out into its own service. - **Caveats / not-in-source**: `GetProjectedAsync` carries `ignoreQueryFilters` **before** the cancellation token (`IRepository.cs:109-110`), so any caller or test double that passes arguments positionally has to account for it; the compiler catches positional callers, but a mock configured with a fixed argument count does not fail - until run time. + until run time. The two `FirstOrDefaultAsync` overloads are public overloads that both carry optional + parameters, which the RS0026 public-API analyzer rule forbids; each is annotated with a targeted + `[SuppressMessage("ApiDesign", "RS0026...")]` (`IRepository.cs:133` and `:149`) whose justification records + them as grandfathered: released after the v1.152 baseline while RS0026/RS0027 were off, so changing either + signature now would be a breaking change. A new overload on this interface does not inherit that exemption. ### IEntityReader > MMCA.Common.Application · `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:21` · Level 4 · interface @@ -1779,7 +1897,7 @@ survives a module being pulled out into its own service. reads clearly at the constructor and mocks in two lines in a test. - **Where it's used**: command handlers that load an aggregate before mutating it; folded into [`IReadRepository`](#ireadrepositorytentity-tidentifiertype) - (`IRepository.cs:331`). Note that the `GetByIdOrFailAsync` extension on + (`IRepository.cs:333`). Note that the `GetByIdOrFailAsync` extension on [`ReadRepositoryExtensions`](#readrepositoryextensions), which turns a miss into a [`Result`](group-01-result-error-handling.md#result) failure carrying `Error.NotFound`, hangs off `IReadRepository` rather than this interface and is implemented over `GetAllAsync` @@ -1788,67 +1906,67 @@ survives a module being pulled out into its own service. ### IReadRepository -> MMCA.Common.Application · `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:330` · Level 5 · interface +> MMCA.Common.Application · `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:332` · Level 5 · interface -- **What it is**: the full read surface over an entity, combining [`IEntityReader`](#ientityreadertentity-tidentifiertype) (by-id lookups) and [`IEntityQuerier`](#ientityqueriertentity-tidentifiertype) (collections, projections, specifications, grouped aggregates, and keyset pages), and adding four `IQueryable` properties for handlers that need raw LINQ (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:330-346`). -- **Depends on**: [`IEntityReader`](#ientityreadertentity-tidentifiertype) and [`IEntityQuerier`](#ientityqueriertentity-tidentifiertype), both declared in the same file and both listed as base interfaces (`IRepository.cs:331`); [`AuditableBaseEntity`](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype) as the `TEntity` constraint (`IRepository.cs:332`). Externals: BCL `IQueryable` only. No EF Core type appears anywhere in the declaration, which is what keeps this interface legal in the Application layer. -- **Concept, the composition point of the ISP ladder, and controlled `IQueryable` exposure.** `[Rubric §1, SOLID]` assesses whether clients depend only on the members they use. `IRepository.cs` defines a ladder of ever-wider interfaces so a handler declares exactly the surface it needs: [`IEntityReader`](#ientityreadertentity-tidentifiertype) (five members, `IRepository.cs:21`), [`IEntityQuerier`](#ientityqueriertentity-tidentifiertype) (fifteen members, `IRepository.cs:80`), this type (both of those plus four properties, twenty-four members in total, `IRepository.cs:330`), [`IWriteRepository`](#iwriterepositorytentity-tidentifiertype) (`IRepository.cs:367`), and [`IRepository`](#irepositorytentity-tidentifiertype) (read plus write, `IRepository.cs:493`). The doc comment records a migration stance rather than a ban (`IRepository.cs:322-327`): existing code should continue using this interface, and new handlers can depend on the focused sub-interfaces for better ISP compliance. +- **What it is**: the full read surface over an entity, combining [`IEntityReader`](#ientityreadertentity-tidentifiertype) (by-id lookups) and [`IEntityQuerier`](#ientityqueriertentity-tidentifiertype) (collections, projections, specifications, grouped aggregates, and keyset pages), and adding four `IQueryable` properties for handlers that need raw LINQ (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:332-348`). +- **Depends on**: [`IEntityReader`](#ientityreadertentity-tidentifiertype) and [`IEntityQuerier`](#ientityqueriertentity-tidentifiertype), both declared in the same file and both listed as base interfaces (`IRepository.cs:333`); [`AuditableBaseEntity`](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype) as the `TEntity` constraint (`IRepository.cs:334`). Externals: BCL `IQueryable` only. No EF Core type appears anywhere in the declaration, which is what keeps this interface legal in the Application layer. +- **Concept, the composition point of the ISP ladder, and controlled `IQueryable` exposure.** `[Rubric §1, SOLID]` assesses whether clients depend only on the members they use. `IRepository.cs` defines a ladder of ever-wider interfaces so a handler declares exactly the surface it needs: [`IEntityReader`](#ientityreadertentity-tidentifiertype) (five members, `IRepository.cs:21`), [`IEntityQuerier`](#ientityqueriertentity-tidentifiertype) (fifteen members, `IRepository.cs:80`), this type (both of those plus four properties, twenty-four members in total, `IRepository.cs:332`), [`IWriteRepository`](#iwriterepositorytentity-tidentifiertype) (`IRepository.cs:369`), and [`IRepository`](#irepositorytentity-tidentifiertype) (read plus write, `IRepository.cs:495`). The doc comment records a migration stance rather than a ban (`IRepository.cs:324-329`): existing code should continue using this interface, and new handlers can depend on the focused sub-interfaces for better ISP compliance. `[Rubric §12, Performance & Scalability]` assesses whether expensive query behavior is a deliberate choice. The four properties turn EF's tracking mode and query-splitting mode into a named decision at the call site: asking for `TableNoTrackingSplitQuery` is visible in review, where a plain `DbSet` would silently track every row and emit one cartesian join. - **Walkthrough**: this interface declares no methods of its own. Its whole body is four get-only `IQueryable` properties. - - `Table` (`IRepository.cs:336`): the base queryable with change tracking enabled, the shape a mutation path composes over. Implemented as the raw `DbSet` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:511`). - - `TableNoTracking` (`IRepository.cs:339`): the no-tracking queryable, documented as best for read-only queries and implemented as `Entities.AsNoTracking()` (`EFReadRepository.cs:514`). - - `TableNoTrackingSingleQuery` (`IRepository.cs:342`): no-tracking, pinned to a single SQL statement through `AsSingleQuery()` (`EFReadRepository.cs:517`). - - `TableNoTrackingSplitQuery` (`IRepository.cs:345`): no-tracking in split-query mode, which avoids the cartesian explosion that collection includes cause, through `AsSplitQuery()` (`EFReadRepository.cs:520`). It is the same concern [`IQueryableExecutor`](#iqueryableexecutor) addresses for callers that hold an `IQueryable` rather than a repository. + - `Table` (`IRepository.cs:338`): the base queryable with change tracking enabled, the shape a mutation path composes over. Implemented as the raw `DbSet` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:526`). + - `TableNoTracking` (`IRepository.cs:341`): the no-tracking queryable, documented as best for read-only queries and implemented as `Entities.AsNoTracking()` (`EFReadRepository.cs:529`). + - `TableNoTrackingSingleQuery` (`IRepository.cs:344`): no-tracking, pinned to a single SQL statement through `AsSingleQuery()` (`EFReadRepository.cs:532`). + - `TableNoTrackingSplitQuery` (`IRepository.cs:347`): no-tracking in split-query mode, which avoids the cartesian explosion that collection includes cause, through `AsSplitQuery()` (`EFReadRepository.cs:535`). It is the same concern [`IQueryableExecutor`](#iqueryableexecutor) addresses for callers that hold an `IQueryable` rather than a repository. - The twenty inherited members come from the two base interfaces and are walked through on their own sections: five point-lookup members on [`IEntityReader`](#ientityreadertentity-tidentifiertype), fifteen set-returning members on [`IEntityQuerier`](#ientityqueriertentity-tidentifiertype). - **Why it's built this way**: the framework needed one interface a query handler can take when it genuinely uses the whole read surface, without forcing every handler to take it. Keeping the composition free of new methods means the two halves stay independently declarable and independently mockable, and it gives the profiling decorator one surface to wrap. Naming the four queryables separately, rather than exposing a `DbSet`, is what makes tracking and split-query opt-in rather than accidental. The ladder itself is [ADR-055](https://ivanball.github.io/docs/adr/055-repository-and-specification-contract.html). -- **Where it's used**: obtained through [`IUnitOfWork.GetReadRepository()`](#iunitofwork) (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IUnitOfWork.cs:29`), which is the sanctioned way to get one; implemented by [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype) (`EFReadRepository.cs:20-24`) and wrapped in [`EFReadRepositoryDecorator`](#efreadrepositorydecoratortentity-tidentifiertype) (`EFReadRepositoryDecorator.cs:17`) by [`RepositoryFactory`](#repositoryfactory) only when MiniProfiler is enabled (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/Factory/RepositoryFactory.cs:55-63`). [`EntityQueryService`](group-03-querying-specifications.md#entityqueryservicetentity-tentitydto-tidentifiertype) derives its `Repository` property from the unit of work at construction (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:88`). It is also the receiver of the `GetByIdOrFailAsync` extension on [`ReadRepositoryExtensions`](#readrepositoryextensions), which turns a miss into a [`Result`](group-01-result-error-handling.md#result) failure carrying `Error.NotFound` and is implemented over `GetAllAsync` rather than `GetByIdAsync` (`MMCA.Common/Source/Core/MMCA.Common.Application/Extensions/ReadRepositoryExtensions.cs:12`, `:27`, `:34-44`), so a handler that wants that convenience must take this wider interface rather than [`IEntityReader`](#ientityreadertentity-tidentifiertype). +- **Where it's used**: obtained through [`IUnitOfWork.GetReadRepository()`](#iunitofwork) (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IUnitOfWork.cs:29`), which is the sanctioned way to get one; implemented by [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype) (`EFReadRepository.cs:21-25`) and wrapped in [`EFReadRepositoryDecorator`](#efreadrepositorydecoratortentity-tidentifiertype) (`EFReadRepositoryDecorator.cs:17`) by [`RepositoryFactory`](#repositoryfactory) only when MiniProfiler is enabled (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/Factory/RepositoryFactory.cs:55-63`). [`EntityQueryService`](group-03-querying-specifications.md#entityqueryservicetentity-tentitydto-tidentifiertype) derives its `Repository` property from the unit of work at construction (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:88`). It is also the receiver of the `GetByIdOrFailAsync` extension on [`ReadRepositoryExtensions`](#readrepositoryextensions), which turns a miss into a [`Result`](group-01-result-error-handling.md#result) failure carrying `Error.NotFound` and is implemented over `GetAllAsync` rather than `GetByIdAsync` (`MMCA.Common/Source/Core/MMCA.Common.Application/Extensions/ReadRepositoryExtensions.cs:12`, `:27`, `:34-44`), so a handler that wants that convenience must take this wider interface rather than [`IEntityReader`](#ientityreadertentity-tidentifiertype). - **Caveats / not-in-source**: a mutation path must not compose its query off `TableNoTracking` and then expect the save to persist, because one no-tracking source makes the whole composed query untracked. That constraint is not stated in this file; it follows from EF's tracking semantics. ### IWriteRepository -> MMCA.Common.Application · `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:367` · Level 5 · interface +> MMCA.Common.Application · `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:369` · Level 5 · interface -- **What it is**: the write half of the repository abstraction, over an aggregate root: `AddAsync`, `AddRangeAsync`, `UpdateAsync`, `UpdateRange`, two `SetOriginalRowVersion` overloads, `TouchConcurrencyToken`, `ExecuteDeleteAsync`, and `ExecuteUpdateAsync` (`IRepository.cs:367-480`). Nine members, and not one of them saves. -- **Depends on**: [`AuditableAggregateRootEntity`](group-02-domain-building-blocks.md#auditableaggregaterootentitytidentifiertype) as the `TEntity` constraint (`IRepository.cs:368`), [`IRowVersioned`](group-02-domain-building-blocks.md#irowversioned) for the child-concurrency overload (written by its qualified `Domain.Interfaces.IRowVersioned` name, `IRepository.cs:417`), and [`IUpdatePropertySetter`](#iupdatepropertysettertentity) for the set-based update builder (`IRepository.cs:478`). Externals: `System.Linq.Expressions` (`IRepository.cs:1`). -- **Concept introduced, writes enter through the aggregate root, and the repository never flushes.** `[Rubric §4, DDD]` assesses whether the aggregate boundary is an enforced rule rather than a naming convention. The constraint here is the narrower `AuditableAggregateRootEntity`, not the `AuditableBaseEntity` the read side accepts, and the doc comment says exactly why (`IRepository.cs:351-358`): a write enters the aggregate through its root so that the root's invariants are enforced and its domain events are collected, and a repository over a child entity would let a caller persist a change the root never saw. Reading a child directly stays harmless and stays supported through [`IReadRepository`](#ireadrepositorytentity-tidentifiertype). The rule is enforced by the compiler, not by review: asking for a write repository over a child entity does not compile. +- **What it is**: the write half of the repository abstraction, over an aggregate root: `AddAsync`, `AddRangeAsync`, `UpdateAsync`, `UpdateRange`, two `SetOriginalRowVersion` overloads, `TouchConcurrencyToken`, `ExecuteDeleteAsync`, and `ExecuteUpdateAsync` (`IRepository.cs:369-482`). Nine members, and not one of them saves. +- **Depends on**: [`AuditableAggregateRootEntity`](group-02-domain-building-blocks.md#auditableaggregaterootentitytidentifiertype) as the `TEntity` constraint (`IRepository.cs:370`), [`IRowVersioned`](group-02-domain-building-blocks.md#irowversioned) for the child-concurrency overload (written by its qualified `Domain.Interfaces.IRowVersioned` name, `IRepository.cs:419`), and [`IUpdatePropertySetter`](#iupdatepropertysettertentity) for the set-based update builder (`IRepository.cs:480`). Externals: `System.Linq.Expressions` (`IRepository.cs:1`). +- **Concept introduced, writes enter through the aggregate root, and the repository never flushes.** `[Rubric §4, DDD]` assesses whether the aggregate boundary is an enforced rule rather than a naming convention. The constraint here is the narrower `AuditableAggregateRootEntity`, not the `AuditableBaseEntity` the read side accepts, and the doc comment says exactly why (`IRepository.cs:353-360`): a write enters the aggregate through its root so that the root's invariants are enforced and its domain events are collected, and a repository over a child entity would let a caller persist a change the root never saw. Reading a child directly stays harmless and stays supported through [`IReadRepository`](#ireadrepositorytentity-tidentifiertype). The rule is enforced by the compiler, not by review: asking for a write repository over a child entity does not compile. - `[Rubric §8, Data Architecture]` assesses whether persistence is deliberate: one save boundary, one concurrency story. This interface has no `Save` and no `SaveChangesAsync`. The doc comment states the division (`IRepository.cs:359-363`): the repository stages changes and never flushes them, because persisting is the unit of work's job, so every repository touched in a scope is written as one unit under one audit stamp. A handler that mutates through a repository and then forgets [`IUnitOfWork.SaveChangesAsync`](#iunitofwork) has written nothing. + `[Rubric §8, Data Architecture]` assesses whether persistence is deliberate: one save boundary, one concurrency story. This interface has no `Save` and no `SaveChangesAsync`. The doc comment states the division (`IRepository.cs:361-365`): the repository stages changes and never flushes them, because persisting is the unit of work's job, so every repository touched in a scope is written as one unit under one audit stamp. A handler that mutates through a repository and then forgets [`IUnitOfWork.SaveChangesAsync`](#iunitofwork) has written nothing. - **Concept introduced, optimistic-concurrency wiring and change-tracking-bypass writes.** Five members carry the weight. - - `SetOriginalRowVersion(TEntity entity, byte[] rowVersion)` (`IRepository.cs:406`): plants the client's last-observed `RowVersion` as the tracked entity's *original* concurrency token, so the next save emits its `WHERE RowVersion = @original` and raises `DbUpdateConcurrencyException` when the row moved since the client read it (`IRepository.cs:399-403`). The token only ever arrives through `If-Match`, so `[SupportsIfMatch]` answers that exception as `412 Precondition Failed` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Concurrency/SupportsIfMatchAttribute.cs:130-138`, [ADR-035](https://ivanball.github.io/docs/adr/035-optimistic-concurrency.html)). The implementation sets `OriginalValue` on the tracked entry's `RowVersion` property and rejects a null token outright (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:75-84`). - - `SetOriginalRowVersion(Domain.Interfaces.IRowVersioned childEntity, byte[] rowVersion)` (`IRepository.cs:417`): the same protection for a tracked **child** of the aggregate, for example a `ProductVariant` under a `Product`. The doc comment explains why a second overload exists at all (`IRepository.cs:408-414`): the repository's `TEntity` is the root, so the typed overload cannot reach children, and this one accepts any [`IRowVersioned`](group-02-domain-building-blocks.md#irowversioned) entity instead ([ADR-035](https://ivanball.github.io/docs/adr/035-optimistic-concurrency.html)). It reaches the entry through an `(object)` cast (`EFRepository.cs:86-95`). - - `TouchConcurrencyToken(TEntity entity)` (`IRepository.cs:440`): forces the aggregate ROOT to take part in the next save when a conditional write left it untouched, so the precondition the caller sent is actually evaluated (SEC-Common-77). The remarks explain the gap it closes (`IRepository.cs:424-439`): `SetOriginalRowVersion` only stamps the tracked entry's ORIGINAL value, it does not make the entry dirty, so an applier that changes only child rows leaves the root `Unchanged`, EF emits no root `UPDATE`, no `WHERE RowVersion = @token` reaches the database, and the stale-token check silently does not fire, letting two administrators editing different children of the same aggregate from the same ETag both get `200` while the second silently discards the first's edit. Touching the root restores the `412` [ADR-035](https://ivanball.github.io/docs/adr/035-optimistic-concurrency.html) promises. It is declared with a default no-op body (`IRepository.cs:440-443`) so an existing implementer stays source- and binary-compatible; a repository that does not override it keeps the pre-hardening behaviour rather than failing to compile. `EFRepository` overrides it. - - `ExecuteDeleteAsync(Expression> where, CancellationToken)` (`IRepository.cs:453-455`): a set-based delete run directly in the database, one statement, no change tracker. The doc comment warns in capitals that it does **not** trigger domain events, audit stamps, or soft delete, and is for maintenance scenarios only (`IRepository.cs:445-452`). The implementation is a one-liner over the `DbSet` (`EFRepository.cs:118-124`). - - `ExecuteUpdateAsync(where, Action> setProperties, CancellationToken)` (`IRepository.cs:476-479`): a set-based `UPDATE ... SET ... WHERE ...` as one atomic statement. `[Rubric §12, Performance & Scalability]` applies alongside §8 here, and the long doc comment (`IRepository.cs:457-475`) is the teaching text for contention-proof conditional updates: guard the update inside `where` (the worked example is a stock decrement guarded by `AvailableQuantity >= @qty`), and then zero rows affected means the guard did not hold, so two racing callers can never both win and no rowversion retry loop is needed, because the database itself arbitrates. It also draws the exact boundaries: domain events are bypassed, global query filters (soft delete) DO apply to `where`, audit fields are NOT bypassed, and the statement runs on the ambient transaction when one is active, so a decrement rolls back with its caller. The audit guarantee is not something the database does; it is compensation code in the implementation, which stamps `LastModifiedOn` from the injected `TimeProvider` and `LastModifiedBy` from `ICurrentUserService` unless the caller assigned them explicitly, and rejects an empty setter list (`EFRepository.cs:135-153`, with the two optional constructor dependencies at `EFRepository.cs:23-27`). + - `SetOriginalRowVersion(TEntity entity, byte[] rowVersion)` (`IRepository.cs:408`): plants the client's last-observed `RowVersion` as the tracked entity's *original* concurrency token, so the next save emits its `WHERE RowVersion = @original` and raises `DbUpdateConcurrencyException` when the row moved since the client read it (`IRepository.cs:401-405`). The token only ever arrives through `If-Match`, so `[SupportsIfMatch]` answers that exception as `412 Precondition Failed`, and the doc comment itself names that status and the attribute (`IRepository.cs:403-404`; the child overload's comment likewise promises "the same stale-token 412 protection", `IRepository.cs:413`) (`MMCA.Common/Source/Presentation/MMCA.Common.API/Concurrency/SupportsIfMatchAttribute.cs:130-138`, [ADR-035](https://ivanball.github.io/docs/adr/035-optimistic-concurrency.html)). The implementation sets `OriginalValue` on the tracked entry's `RowVersion` property and rejects a null token outright (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:75-84`). + - `SetOriginalRowVersion(Domain.Interfaces.IRowVersioned childEntity, byte[] rowVersion)` (`IRepository.cs:419`): the same protection for a tracked **child** of the aggregate, for example a `ProductVariant` under a `Product`. The doc comment explains why a second overload exists at all (`IRepository.cs:410-416`): the repository's `TEntity` is the root, so the typed overload cannot reach children, and this one accepts any [`IRowVersioned`](group-02-domain-building-blocks.md#irowversioned) entity instead ([ADR-035](https://ivanball.github.io/docs/adr/035-optimistic-concurrency.html)). It reaches the entry through an `(object)` cast (`EFRepository.cs:86-95`). + - `TouchConcurrencyToken(TEntity entity)` (`IRepository.cs:442`): forces the aggregate ROOT to take part in the next save when a conditional write left it untouched, so the precondition the caller sent is actually evaluated (SEC-Common-77). The remarks explain the gap it closes (`IRepository.cs:426-441`): `SetOriginalRowVersion` only stamps the tracked entry's ORIGINAL value, it does not make the entry dirty, so an applier that changes only child rows leaves the root `Unchanged`, EF emits no root `UPDATE`, no `WHERE RowVersion = @token` reaches the database, and the stale-token check silently does not fire, letting two administrators editing different children of the same aggregate from the same ETag both get `200` while the second silently discards the first's edit. Touching the root restores the `412` [ADR-035](https://ivanball.github.io/docs/adr/035-optimistic-concurrency.html) promises. It is declared with a default no-op body (`IRepository.cs:442-445`) so an existing implementer stays source- and binary-compatible; a repository that does not override it keeps the pre-hardening behaviour rather than failing to compile. `EFRepository` overrides it. + - `ExecuteDeleteAsync(Expression> where, CancellationToken)` (`IRepository.cs:455-457`): a set-based delete run directly in the database, one statement, no change tracker. The doc comment warns in capitals that it does **not** trigger domain events, audit stamps, or soft delete, and is for maintenance scenarios only (`IRepository.cs:447-454`). The implementation is a one-liner over the `DbSet` (`EFRepository.cs:118-124`). + - `ExecuteUpdateAsync(where, Action> setProperties, CancellationToken)` (`IRepository.cs:478-481`): a set-based `UPDATE ... SET ... WHERE ...` as one atomic statement. `[Rubric §12, Performance & Scalability]` applies alongside §8 here, and the long doc comment (`IRepository.cs:459-477`) is the teaching text for contention-proof conditional updates: guard the update inside `where` (the worked example is a stock decrement guarded by `AvailableQuantity >= @qty`), and then zero rows affected means the guard did not hold, so two racing callers can never both win and no rowversion retry loop is needed, because the database itself arbitrates. It also draws the exact boundaries: domain events are bypassed, global query filters (soft delete) DO apply to `where`, audit fields are NOT bypassed, and the statement runs on the ambient transaction when one is active, so a decrement rolls back with its caller. The audit guarantee is not something the database does; it is compensation code in the implementation, which stamps `LastModifiedOn` from the injected `TimeProvider` and `LastModifiedBy` from `ICurrentUserService` unless the caller assigned them explicitly, and rejects an empty setter list (`EFRepository.cs:135-156`, with the two optional constructor dependencies at `EFRepository.cs:23-27`). - **Walkthrough**: the nine members in teaching order. - - `AddAsync(TEntity entity, CancellationToken)` (`IRepository.cs:375-377`) and `AddRangeAsync(IEnumerable entities, CancellationToken)` (`IRepository.cs:383-385`): single and batch inserts, staged on the change tracker. - - `UpdateAsync(TEntity entity, CancellationToken)` (`IRepository.cs:391-393`) and `UpdateRange(IEnumerable entities)` (`IRepository.cs:397`): mark tracked entities modified. `UpdateRange` is the one `void` member of the pair, since batch marking needs nothing awaited. - - The two `SetOriginalRowVersion` overloads (`IRepository.cs:406`, `:417`), `TouchConcurrencyToken` (`IRepository.cs:440`), and the two database-side operations (`IRepository.cs:453`, `:450`) described above. + - `AddAsync(TEntity entity, CancellationToken)` (`IRepository.cs:377-379`) and `AddRangeAsync(IEnumerable entities, CancellationToken)` (`IRepository.cs:385-387`): single and batch inserts, staged on the change tracker. + - `UpdateAsync(TEntity entity, CancellationToken)` (`IRepository.cs:393-395`) and `UpdateRange(IEnumerable entities)` (`IRepository.cs:399`): mark tracked entities modified. `UpdateRange` is the one `void` member of the pair, since batch marking needs nothing awaited. + - The two `SetOriginalRowVersion` overloads (`IRepository.cs:408`, `:419`), `TouchConcurrencyToken` (`IRepository.cs:442`), and the two database-side operations (`IRepository.cs:455`, `:478`) described above. - **Why it's built this way**: keeping writes in a focused interface means a query handler cannot accidentally acquire mutation methods, and the concurrency and set-based escape hatches are declared where a reader meets their warnings rather than buried in a concrete class. Constraining `TEntity` to the aggregate root mirrors the constraint on `IUnitOfWork.GetRepository` (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IUnitOfWork.cs:20`), which is how a handler is meant to obtain one, so the two agree by construction. Leaving `Save` off the interface entirely is what makes the single save boundary of [ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html) enforceable: with several physical databases in one host, "save" cannot mean "save this repository". -- **Where it's used**: command handlers that mutate aggregates, always through [`IUnitOfWork.GetRepository()`](#iunitofwork) (`IUnitOfWork.cs:19`); folded into [`IRepository`](#irepositorytentity-tidentifiertype) (`IRepository.cs:493`); implemented by [`EFRepository`](#efrepositorytentity-tidentifiertype) (`EFRepository.cs:23-29`), which derives from [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype) and adds only the write surface, and is wrapped in [`EFRepositoryDecorator`](#efrepositorydecoratortentity-tidentifiertype) when MiniProfiler is on (`RepositoryFactory.cs:31-41`). Both apps exercise the specialized members: in MMCA.Store, `ChangeVariantPriceHandler` calls the **child-typed** `SetOriginalRowVersion` so a race on one product variant conflicts even when the product row itself is untouched (`MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Application/Products/UseCases/ChangeVariantPrice/ChangeVariantPriceHandler.cs:45-47`, with the same pattern in `ChangeVariantSkuHandler.cs:69`); in MMCA.ADC, `ScoreEventSessionsHandler` uses `ExecuteDeleteAsync` to clear a session's previous AI scores before re-adding them (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/SessionScoringRunner.cs:28`, `:105-107`). +- **Where it's used**: command handlers that mutate aggregates, always through [`IUnitOfWork.GetRepository()`](#iunitofwork) (`IUnitOfWork.cs:19`); folded into [`IRepository`](#irepositorytentity-tidentifiertype) (`IRepository.cs:495`); implemented by [`EFRepository`](#efrepositorytentity-tidentifiertype) (`EFRepository.cs:23-29`), which derives from [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype) and adds only the write surface, and is wrapped in [`EFRepositoryDecorator`](#efrepositorydecoratortentity-tidentifiertype) when MiniProfiler is on (`RepositoryFactory.cs:31-41`). Both apps exercise the specialized members: in MMCA.Store, `ChangeVariantPriceHandler` calls the **child-typed** `SetOriginalRowVersion` so a race on one product variant conflicts even when the product row itself is untouched (`MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Application/Products/UseCases/ChangeVariantPrice/ChangeVariantPriceHandler.cs:45-47`, with the same pattern in `ChangeVariantSkuHandler.cs:69`); in MMCA.ADC, `ScoreEventSessionsHandler` uses `ExecuteDeleteAsync` to clear a session's previous AI scores before re-adding them (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/SessionScoringRunner.cs:28`, `:105-107`). - **Caveats / not-in-source**: `ExecuteDeleteAsync` and `ExecuteUpdateAsync` bypass the domain-event capture that [`DomainEventSaveChangesInterceptor`](#domaineventsavechangesinterceptor) performs on save, so anything downstream that reacts to an event will not observe those writes. The interface says so in its doc comments; nothing in the type system stops it, so it stays a review rule rather than a compiler rule. ### IRepository -> MMCA.Common.Application · `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:493` · Level 6 · interface +> MMCA.Common.Application · `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:495` · Level 6 · interface -- **What it is**: the combined read-write repository over an aggregate root, extending both [`IReadRepository`](#ireadrepositorytentity-tidentifiertype) and [`IWriteRepository`](#iwriterepositorytentity-tidentifiertype), so a command handler that reads an aggregate and then mutates it takes a single dependency (`IRepository.cs:493`). -- **Depends on**: [`IReadRepository`](#ireadrepositorytentity-tidentifiertype) and [`IWriteRepository`](#iwriterepositorytentity-tidentifiertype), both named as bases on `IRepository.cs:493`, and [`AuditableAggregateRootEntity`](group-02-domain-building-blocks.md#auditableaggregaterootentitytidentifiertype) as the `TEntity` constraint (`IRepository.cs:494`). -- **Concept, the top of the ISP ladder, and where the aggregate-root constraint wins.** `[Rubric §1, SOLID]` and `[Rubric §4, DDD]`. The interface is purely compositional: it declares no members of its own, only two base interfaces and two constraints (`IRepository.cs:493-495`). What matters is which constraint survives the composition. The read side accepts any [`AuditableBaseEntity`](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype) (`IRepository.cs:332`) and the write side only aggregate roots (`IRepository.cs:368`), so the combination inherits the narrower bound, and the doc comment states the consequence for the reader (`IRepository.cs:485-489`): a handler that only reads, and reads a child entity, depends on [`IReadRepository`](#ireadrepositorytentity-tidentifiertype) instead, which still accepts any auditable entity. -- **Walkthrough**: no members. The declaration is a semicolon-terminated interface with two bases and two generic constraints (`IRepository.cs:493-495`), the C# shorthand for an empty body. Everything a caller can do through it is walked through on the two base sections, and beneath them on [`IEntityReader`](#ientityreadertentity-tidentifiertype) and [`IEntityQuerier`](#ientityqueriertentity-tidentifiertype). +- **What it is**: the combined read-write repository over an aggregate root, extending both [`IReadRepository`](#ireadrepositorytentity-tidentifiertype) and [`IWriteRepository`](#iwriterepositorytentity-tidentifiertype), so a command handler that reads an aggregate and then mutates it takes a single dependency (`IRepository.cs:495`). +- **Depends on**: [`IReadRepository`](#ireadrepositorytentity-tidentifiertype) and [`IWriteRepository`](#iwriterepositorytentity-tidentifiertype), both named as bases on `IRepository.cs:495`, and [`AuditableAggregateRootEntity`](group-02-domain-building-blocks.md#auditableaggregaterootentitytidentifiertype) as the `TEntity` constraint (`IRepository.cs:496`). +- **Concept, the top of the ISP ladder, and where the aggregate-root constraint wins.** `[Rubric §1, SOLID]` and `[Rubric §4, DDD]`. The interface is purely compositional: it declares no members of its own, only two base interfaces and two constraints (`IRepository.cs:495-497`). What matters is which constraint survives the composition. The read side accepts any [`AuditableBaseEntity`](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype) (`IRepository.cs:334`) and the write side only aggregate roots (`IRepository.cs:370`), so the combination inherits the narrower bound, and the doc comment states the consequence for the reader (`IRepository.cs:487-491`): a handler that only reads, and reads a child entity, depends on [`IReadRepository`](#ireadrepositorytentity-tidentifiertype) instead, which still accepts any auditable entity. +- **Walkthrough**: no members. The declaration is a semicolon-terminated interface with two bases and two generic constraints (`IRepository.cs:495-497`), the C# shorthand for an empty body. Everything a caller can do through it is walked through on the two base sections, and beneath them on [`IEntityReader`](#ientityreadertentity-tidentifiertype) and [`IEntityQuerier`](#ientityqueriertentity-tidentifiertype). - **Why it's built this way**: keeping the combined interface empty means the read and write surfaces each stay independently usable and independently mockable, while a handler that genuinely needs both still takes one constructor parameter. Composition rather than a fresh member list is also what keeps the ladder honest: there is exactly one definition of "read" and one of "write" in the codebase, and the widest rung is a deliberate choice a reviewer can see at the constructor rather than a default. - **Where it's used**: obtained through [`IUnitOfWork.GetRepository()`](#iunitofwork) (`IUnitOfWork.cs:19-21`), which is the sanctioned way to get one; implemented by [`EFRepository`](#efrepositorytentity-tidentifiertype) (`EFRepository.cs:23-29`) and produced, optionally wrapped for profiling, by [`RepositoryFactory`](#repositoryfactory) (`RepositoryFactory.cs:26-42`). [`UnitOfWork`](#unitofwork) caches the instance per closed generic interface type, so `typeof(IRepository)` is the cache key for a whole scope (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:37`, `:43`). -- **Caveats / not-in-source**: `AddInfrastructure` does register the open generic `IRepository<,>` against `EFRepository<,>` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:122`), which makes constructor-injecting `IRepository<,>` look supported. It is not the intended path: `EFRepository` takes a bare `DbContext` constructor parameter (`EFRepository.cs:23-24`) and that registration file adds no `DbContext` service, so a direct injection sidesteps both the data-source resolution and the per-scope repository cache that `GetRepository` performs (`UnitOfWork.cs:37-45`). Ask the unit of work. +- **Caveats / not-in-source**: `AddInfrastructure` does register the open generic `IRepository<,>` against `EFRepository<,>` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:128`), which makes constructor-injecting `IRepository<,>` look supported. It is not the intended path: `EFRepository` takes a bare `DbContext` constructor parameter (`EFRepository.cs:23-24`) and that registration file adds no `DbContext` service, so a direct injection sidesteps both the data-source resolution and the per-scope repository cache that `GetRepository` performs (`UnitOfWork.cs:37-45`). Ask the unit of work. ### IUnitOfWork > MMCA.Common.Application · `MMCA.Common.Application.Interfaces.Infrastructure.Persistence` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IUnitOfWork.cs:10` · Level 7 · interface -- **What it is**: the one coordination point a handler uses to touch the database. It hands out typed repositories (read-write for aggregate roots, read-only for any entity), persists everything pending in one call, and exposes controlled transaction and identity-insert operations. The doc comment states the contract in two sentences (`IUnitOfWork.cs:5-9`): it coordinates persistence across multiple repositories within a single database context, and `SaveChangesAsync` persists all pending changes and dispatches domain events raised by tracked aggregates. +- **What it is**: the one coordination point a handler uses to touch the database. It hands out typed repositories (read-write for aggregate roots, read-only for any entity), persists everything pending in one call, and exposes controlled transaction and explicit-key-insert operations. The doc comment states the contract in two sentences (`IUnitOfWork.cs:5-9`): it coordinates persistence across multiple repositories within a single database context, and `SaveChangesAsync` persists all pending changes and dispatches domain events raised by tracked aggregates. - **Depends on**: [`AuditableAggregateRootEntity`](group-02-domain-building-blocks.md#auditableaggregaterootentitytidentifiertype) and [`AuditableBaseEntity`](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype) as the two constraint bounds (`IUnitOfWork.cs:1`, `:20`, `:30`); [`IRepository`](#irepositorytentity-tidentifiertype) and [`IReadRepository`](#ireadrepositorytentity-tidentifiertype) as the two return types (`IUnitOfWork.cs:19` and `:29`). Externals: BCL `IDisposable` and `IAsyncDisposable`, both declared as base interfaces (`IUnitOfWork.cs:10`). Notably absent: anything from `Microsoft.EntityFrameworkCore`, which is the point of the type. -- **Concept introduced, the Unit of Work as the Application layer's only persistence verb.** `[Rubric §8, Data Architecture]` assesses whether persistence is deliberate: one save boundary, one transaction story, explicit concurrency and audit handling rather than scattered `SaveChanges` calls. A unit of work is the scope inside which a caller sees a consistent view of the data and inside which all of its changes either land together or not at all. Here that scope is the DI scope: [`UnitOfWork`](#unitofwork) is registered `TryAddScoped` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:124`), it caches one repository per closed generic interface type (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:23`, `:37-45`), and every repository it hands out is bound to a context obtained from the same [`IDbContextFactory`](#idbcontextfactory) (`UnitOfWork.cs:41`), so two handlers in one request share one change tracker instead of racing two. +- **Concept introduced, the Unit of Work as the Application layer's only persistence verb.** `[Rubric §8, Data Architecture]` assesses whether persistence is deliberate: one save boundary, one transaction story, explicit concurrency and audit handling rather than scattered `SaveChanges` calls. A unit of work is the scope inside which a caller sees a consistent view of the data and inside which all of its changes either land together or not at all. Here that scope is the DI scope: [`UnitOfWork`](#unitofwork) is registered `TryAddScoped` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:130`), it caches one repository per closed generic interface type (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:23`, `:37-45`), and every repository it hands out is bound to a context obtained from the same [`IDbContextFactory`](#idbcontextfactory) (`UnitOfWork.cs:41`), so two handlers in one request share one change tracker instead of racing two. - `[Rubric §3, Clean Architecture]` assesses whether the inner layers declare intent while the outer layers own the technology. This interface lives in `MMCA.Common.Application` and names no EF type, which is exactly what lets the architecture fitness rule "Application must not depend on EF Core, use IRepository/IUnitOfWork" hold (`MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Layering/ArchitectureRules.Purity.cs:53-63`). The EF-shaped work (a context per physical source, execution strategies, identity-insert SQL) sits behind it in `MMCA.Common.Infrastructure`. + `[Rubric §3, Clean Architecture]` assesses whether the inner layers declare intent while the outer layers own the technology. This interface lives in `MMCA.Common.Application` and names no EF type, which is exactly what lets the architecture fitness rule "Application must not depend on EF Core, use IRepository/IUnitOfWork" hold (`MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Layering/ArchitectureRules.Purity.cs:53-63`). The EF-shaped work (a context per physical source, execution strategies, the engine-specific explicit-key insert SQL) sits behind it in `MMCA.Common.Infrastructure`. `[Rubric §6, CQRS & Event-Driven]` assesses whether events are raised and delivered from a single, reliable place. `SaveChangesAsync` is that place: the doc comment (`IUnitOfWork.cs:7-8`) ties persistence and domain-event dispatch into one operation, and the mechanism behind it is the interceptor plus outbox flow described on [`DomainEventSaveChangesInterceptor`](#domaineventsavechangesinterceptor). @@ -1856,19 +1974,19 @@ survives a module being pulled out into its own service. - **Walkthrough**: five members, in three groups. - `GetRepository()` (`IUnitOfWork.cs:19-21`): the read-write repository. Its constraint is `where TEntity : AuditableAggregateRootEntity` (`IUnitOfWork.cs:20`), so the DDD rule that the doc comment states in prose ("Only aggregate roots can be directly persisted", `IUnitOfWork.cs:14`) is enforced by the compiler. In the implementation the call resolves the entity's physical data source through [`IDataSourceService`](#idatasourceservice), fetches the matching context, builds the repository through [`IRepositoryFactory`](#irepositoryfactory), and caches it under the closed interface type (`UnitOfWork.cs:37-45`). - `GetReadRepository()` (`IUnitOfWork.cs:29-31`): the read-only repository, constrained only to [`AuditableBaseEntity`](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype) (`IUnitOfWork.cs:30`), so child entities are readable even though they are not independently writable. Same resolution path, different factory method (`UnitOfWork.cs:57-65`). - - `SaveChangesAsync(CancellationToken cancellationToken = default)` (`IUnitOfWork.cs:36`): persists everything and returns the number of state entries written (`IUnitOfWork.cs:35`). The implementation is a straight delegation (`UnitOfWork.cs:69-70`) to [`DbContextFactory`](#dbcontextfactory), which saves **every** cached context, not just one: it snapshots the contexts and loops in bounded passes (three, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:61`) so a context materialized by a domain event handler mid-save is still saved (`DbContextFactory.cs:256-270`), then throws if any tracked change is left behind rather than losing it silently (`DbContextFactory.cs:278-289`). - - `RequestIdentityInsert()` (`IUnitOfWork.cs:45`): a one-shot flag saying the next save may insert rows carrying explicit values for database-generated identity columns, for example records imported from an external system with their source ids intact (`IUnitOfWork.cs:38-44`). The implementation sets a boolean (`UnitOfWork.cs:73`, `DbContextFactory.cs:295`); the next `SaveChangesAsync` reads and immediately clears it (`DbContextFactory.cs:246-247`, which is what "automatically cleared after the save completes" means) and, for a SQL Server context, routes the save through `SaveWithIdentityInsertAsync` (`DbContextFactory.cs:266-268`), which groups the affected entries by table and wraps each group in `SET IDENTITY_INSERT ON/OFF` because SQL Server allows only one table per session to have it on (`DbContextFactory.cs:297-319`). - - `ExecuteInTransactionAsync(Func> operation, CancellationToken cancellationToken = default)` (`IUnitOfWork.cs:57-59`): the interface's only transaction-control member; the manual `Save()`, `BeginTransaction()`, `CommitTransaction()`, and `RollbackTransaction()` members it used to sit alongside are gone from the interface. Its doc comment (`IUnitOfWork.cs:47-53`) states the reason: the operation runs wrapped by the active execution strategy, so a retrying strategy such as `SqlServerRetryingExecutionStrategy` can retry the whole transaction as one retriable unit, committing on success and rolling back before an exception propagates. The implementation adds five behaviors worth knowing (`DbContextFactory.cs:518-563`, with the attempt runner at `:554-609`): a nested call joins the ambient transaction instead of opening a second one (`DbContextFactory.cs:529-530`); a returned failed [`Result`](group-01-result-error-handling.md#result) rolls back exactly like an exception (`DbContextFactory.cs:582-589`); each retry starts from a reset change tracker, so entities a failed attempt added are not inserted twice (`DbContextFactory.cs:491-497`, `:528-529`); deferred in-process domain events are flushed only after a successful commit (`DbContextFactory.cs:595-602`); and a failure of the **commit itself** is never retried, surfacing as [`TransactionCommitAmbiguousException`](#transactioncommitambiguousexception) thrown outside the strategy instead (`DbContextFactory.cs:555-560`). + - `SaveChangesAsync(CancellationToken cancellationToken = default)` (`IUnitOfWork.cs:36`): persists everything and returns the number of state entries written (`IUnitOfWork.cs:35`). The implementation is a straight delegation (`UnitOfWork.cs:69-70`) to [`DbContextFactory`](#dbcontextfactory), which saves **every** cached context, not just one: it snapshots the contexts and loops in bounded passes (three, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:63`) so a context materialized by a domain event handler mid-save is still saved (`DbContextFactory.cs:282-296`), then throws if any tracked change is left behind rather than losing it silently (`DbContextFactory.cs:304-315`). + - `RequestExplicitKeyInsert()` (`IUnitOfWork.cs:45`): a one-shot flag saying the next save may insert rows that carry explicit values for store-generated keys, for example rows imported from an external system with their source ids intact (`IUnitOfWork.cs:38-44`). The contract is engine-neutral: on an engine that refuses such a value unless it is switched on per table, the save pipeline does that around those inserts, and on every other engine the save runs unchanged (`IUnitOfWork.cs:41-42`). The implementation sets a boolean (`UnitOfWork.cs:73`, `DbContextFactory.cs:321`); the next `SaveChangesAsync` reads and immediately clears it (`DbContextFactory.cs:272-273`, which is what "automatically cleared after the save completes" means) and, for a context whose engine exposes an [`IExplicitKeyInsertDialect`](#iexplicitkeyinsertdialect) through `IDataSourceEngine.ExplicitKeyInsert` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/IDataSourceEngine.cs:119`), routes the save through `SaveWithExplicitKeyInsertAsync` (`DbContextFactory.cs:292-294`). Only [`SQLServerDataSourceEngine`](#sqlserverdatasourceengine) supplies a dialect (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SQLServerDataSourceEngine.cs:49`, the `SET IDENTITY_INSERT ON/OFF` toggle per `DbContextFactory.cs:263-266`); the SQLite, PostgreSQL and Cosmos engines return `null`, so their save is the plain one. With a dialect, the save asks it for the affected entries grouped by table as [`ExplicitKeyInsertGroup`](#explicitkeyinsertgroup) values (`DbContextFactory.cs:334`), pins one open connection for the whole save because the toggle is session state (`DbContextFactory.cs:342-352`, closed again at `:364-368`), and saves each group in its own round with the toggle on, because SQL Server allows only one table per session to have it on (`DbContextFactory.cs:323-328`, `SaveExplicitKeyGroupsAsync` at `:377`). + - `ExecuteInTransactionAsync(Func> operation, CancellationToken cancellationToken = default)` (`IUnitOfWork.cs:63-65`): the interface's only transaction-control member; the manual `Save()`, `BeginTransaction()`, `CommitTransaction()`, and `RollbackTransaction()` members it used to sit alongside are gone from the interface. Its doc comment (`IUnitOfWork.cs:47-59`) states the reason: the operation runs wrapped by the active execution strategy, so a retrying strategy such as `SqlServerRetryingExecutionStrategy` can retry the whole transaction as one retriable unit, committing on success and rolling back before an exception propagates. The same comment carries the save rule (`IUnitOfWork.cs:53-58`): the commit does **not** save the operation's work, so the operation must save before it returns; the one exception is an internal-command row scheduled after the last save, which is saved just before the commit, and any other change still tracked at that point throws `InvalidOperationException` and rolls the unit back rather than being silently discarded. The implementation adds six behaviors worth knowing (`DbContextFactory.cs:534-579`, with the attempt runner at `:589-647`): a nested call joins the ambient transaction instead of opening a second one (`DbContextFactory.cs:545-546`); a returned failed [`Result`](group-01-result-error-handling.md#result) rolls back exactly like an exception (`DbContextFactory.cs:598-605`); before committing, `FlushEnrolledCommandsBeforeCommitAsync` (`DbContextFactory.cs:608`, `:659-685`) saves the unit when every pending entry is an added [`InternalCommandMessage`](#internalcommandmessage) and otherwise throws the unsaved-changes exception (`DbContextFactory.cs:668-682`); each retry starts from a reset change tracker, so entities a failed attempt added are not inserted twice (`DbContextFactory.cs:563-564`, `ResetForRetry` at `:814-821`); deferred in-process domain events are flushed only after a successful commit (`DbContextFactory.cs:614-621`); and a failure of the **commit itself** is never retried, surfacing as [`TransactionCommitAmbiguousException`](#transactioncommitambiguousexception) thrown outside the strategy instead (`DbContextFactory.cs:571-576`). - The two base interfaces (`IUnitOfWork.cs:10`) matter at scope teardown: [`UnitOfWork`](#unitofwork) forwards both disposal paths to the context factory (`UnitOfWork.cs:81-107`), so the async path awaits `_dbContextFactory.DisposeAsync()` (`UnitOfWork.cs:91`) rather than blocking. - **Why it's built this way**: under [ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html) a single host may own several physical databases, so "save" cannot mean "call SaveChanges on the one context". Splitting the responsibility keeps that manageable: [`IDbContextFactory`](#idbcontextfactory) owns multi-source routing, saving, transactions and disposal, while `IUnitOfWork` is the narrow per-scope facade the Application layer is allowed to see, adding only repository resolution and caching on top (`UnitOfWork.cs:13`, `:23`). Exposing `ExecuteInTransactionAsync` as the interface's only transaction-control member, rather than the manual `BeginTransaction`/`CommitTransaction`/`RollbackTransaction`/`Save` set it replaced, is what makes the [ADR-014](https://ivanball.github.io/docs/adr/014-cqrs-decorator-pipeline.html) pipeline's transaction rules enforceable in one place: business failures roll back and post-commit event dispatch is deferred. Keeping the interface in Application rather than Infrastructure is the [ADR-013](https://ivanball.github.io/docs/adr/013-result-pattern.html) and Clean Architecture pairing, since a handler returns a `Result` and never sees an EF type on the way there. -- **Where it's used**: injected into command handlers across the framework and both apps. In MMCA.Common: [`TransactionalCommandDecorator`](group-05-cqrs-pipeline.md#transactionalcommanddecoratortcommand-tresult) takes it in its primary constructor (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:22`) and calls `ExecuteInTransactionAsync` for any command marked [`ITransactional`](group-05-cqrs-pipeline.md#itransactional) (`TransactionalCommandDecorator.cs:31`); [`DeleteEntityHandler`](group-05-cqrs-pipeline.md#deleteentityhandlertentity-tidentifiertype) takes it and resolves its write repository from it (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/DeleteEntityHandler.cs:37`, `:70`); [`EntityQueryService`](group-03-querying-specifications.md#entityqueryservicetentity-tentitydto-tidentifiertype) both holds it and derives its read repository from it (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:33`, `:43`, `:87`). In MMCA.ADC, [`RefreshFromSessionizeHandler`](group-18-conference-application.md#refreshfromsessionizehandler) is the live consumer of the identity-insert path, calling `RequestIdentityInsert()` immediately before the save because Sessionize imports preserve external ids into tables with IDENTITY columns (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeHandler.cs:168-169`). The single implementation is [`UnitOfWork`](#unitofwork) (`UnitOfWork.cs:13`), which is `internal sealed`, so consumers only ever see this interface. -- **Caveats / not-in-source**: `ExecuteInTransactionAsync` is not a distributed transaction: with several physical sources each gets its own transaction and commits are sequential and best effort, so a commit failure on the second source leaves the first already committed. The doc comment names that limitation and records that the thrown [`TransactionCommitAmbiguousException`](#transactioncommitambiguousexception) reports each source's outcome so the partial state is observable rather than inferred, with the outbox as the cross-source consistency mechanism (`DbContextFactory.cs:471-474`, `:487-497`). +- **Where it's used**: injected into command handlers across the framework and both apps. In MMCA.Common: [`TransactionalCommandDecorator`](group-05-cqrs-pipeline.md#transactionalcommanddecoratortcommand-tresult) takes it in its primary constructor (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:24`) and calls `ExecuteInTransactionAsync` for any command marked [`ITransactional`](group-05-cqrs-pipeline.md#itransactional) (`TransactionalCommandDecorator.cs:33`); [`DeleteEntityHandler`](group-05-cqrs-pipeline.md#deleteentityhandlertentity-tidentifiertype) takes it and resolves its write repository from it (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/DeleteEntityHandler.cs:37`, `:70`); [`EntityQueryService`](group-03-querying-specifications.md#entityqueryservicetentity-tentitydto-tidentifiertype) both holds it and derives its read repository from it (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:33`, `:43`, `:87`). In MMCA.ADC, [`RefreshFromSessionizeHandler`](group-18-conference-application.md#refreshfromsessionizehandler) is the live consumer of the explicit-key insert path, calling `RequestExplicitKeyInsert()` immediately before the save because Sessionize imports preserve external ids into tables with store-generated keys (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeHandler.cs:192-193`). The single implementation is [`UnitOfWork`](#unitofwork) (`UnitOfWork.cs:13`), which is `internal sealed`, so consumers only ever see this interface. +- **Caveats / not-in-source**: `ExecuteInTransactionAsync` is not a distributed transaction: with several physical sources each gets its own transaction and commits are sequential and best effort, so a commit failure on the second source leaves the first already committed. The doc comment names that limitation and records that the thrown [`TransactionCommitAmbiguousException`](#transactioncommitambiguousexception) names each source's outcome (committed, ambiguous, or rolled back) so the partial state is observable rather than inferred, and that the caller's replay is what reconciles it (`DbContextFactory.cs:522-532`). A witness row that would let a replay learn what landed is deliberately not built, since a single transactional source needs none (`DbContextFactory.cs:528-531`); the outbox delivers whatever the commit did make durable (`DbContextFactory.cs:519-520`). ### BlobNames > MMCA.Common.Application · `MMCA.Common.Application.Interfaces.Infrastructure.Storage` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Storage/BlobNames.cs:10` · Level 0 · class (public static) - **What it is**: a dependency-free static helper that reduces a user-supplied file name to a blob-safe - segment built only from `A-Z a-z 0-9 . _ -` (`BlobNames.cs:25-135`). + segment built only from `A-Z a-z 0-9 . _ -` (`BlobNames.cs:33-119`). - **Depends on**: nothing first-party; BCL `StringBuilder`. - **Concept introduced, file-name sanitization as an upload trust boundary.** `[Rubric §11, Security]` assesses whether untrusted input (here, a caller-chosen file name that becomes part of a storage path @@ -1885,7 +2003,7 @@ survives a module being pulled out into its own service. the extension alone is longer than the budget. - `KeepSafeCharacters(string fileName)` (`BlobNames.cs:95-117`): rewrites the name over the safe alphabet, collapsing any run of unsafe characters to one `-` and any run of dots to one `.`. - - `ToLowerAscii(string value)` (`BlobNames.cs:123-131`): a hand-rolled ASCII lower-case so the result + - `ToLowerAscii(string value)` (`BlobNames.cs:108-116`): a hand-rolled ASCII lower-case so the result is independent of the host's culture. - **Why it's built this way**: collapsing runs rather than dropping characters one-by-one keeps a long run of spaces or path separators from producing a long run of dashes, and preserving the extension @@ -1921,30 +2039,30 @@ survives a module being pulled out into its own service. > MMCA.Common.Application · `MMCA.Common.Application.Interfaces.Infrastructure.Storage` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Storage/FileUploadOptions.cs:13` · Level 0 · record (sealed) - **What it is**: an immutable options record carrying the response headers a blob upload should be - stored with, `ContentDisposition` and `CacheControl` (`FileUploadOptions.cs:221-227`), plus factory + stored with, `ContentDisposition` and `CacheControl` (`FileUploadOptions.cs:28-34`), plus factory methods that build a correctly-encoded `Content-Disposition` from a user-facing file name. - **Depends on**: nothing first-party; BCL `StringBuilder`, `Encoding.UTF8`. - **Concept introduced, RFC 6266/5987-correct `Content-Disposition` encoding.** `[Rubric §8, Data Architecture]` assesses whether a storage-layer concern (here, the HTTP headers a blob is served with) is centralized in one place rather than hand-built at each call site. The static `None` instance - (`FileUploadOptions.cs:214`) sets no headers, so a caller only reaches for `Attachment`/`Inline` when + (`FileUploadOptions.cs:21`) sets no headers, so a caller only reaches for `Attachment`/`Inline` when it actually wants a download disposition. - **Walkthrough**: two public factory methods and three private helpers. - - `Attachment(string fileName, string? cacheControl = null)` (`FileUploadOptions.cs:241-242`): builds + - `Attachment(string fileName, string? cacheControl = null)` (`FileUploadOptions.cs:48-49`): builds an `attachment` disposition, so the browser downloads the blob under `fileName` instead of rendering it. - - `Inline(string fileName, string? cacheControl = null)` (`FileUploadOptions.cs:252-253`): builds an + - `Inline(string fileName, string? cacheControl = null)` (`FileUploadOptions.cs:59-60`): builds an `inline` disposition, so the browser renders the blob when it can and otherwise downloads it under `fileName`. - `ForDisposition(string dispositionType, string fileName, string? cacheControl)` - (`FileUploadOptions.cs:255-278`): trims the name (falling back to `file` when blank), then emits both + (`FileUploadOptions.cs:62-85`): trims the name (falling back to `file` when blank), then emits both an ASCII `filename="..."` fallback and a percent-encoded UTF-8 `filename*=UTF-8''...` parameter in the same header, per RFC 6266. - - `ToAsciiFallback(string fileName)` (`FileUploadOptions.cs:284-299`) and `ToRfc5987(string fileName)` - (`FileUploadOptions.cs:305-322`): the two encodings behind the header; the ASCII fallback drops + - `ToAsciiFallback(string fileName)` (`FileUploadOptions.cs:91-106`) and `ToRfc5987(string fileName)` + (`FileUploadOptions.cs:112-129`): the two encodings behind the header; the ASCII fallback drops quotes, backslashes, semicolons and line breaks and replaces the rest of non-printable-ASCII with `_`, while the RFC 5987 form percent-encodes every UTF-8 byte outside the `attr-char` alphabet - (`FileUploadOptions.cs:328-332`). + (`FileUploadOptions.cs:135-139`). - **Why it's built this way**: emitting both an ASCII fallback and the UTF-8 `filename*` parameter in one header lets older clients that ignore `filename*` still get a usable name, while modern clients get the exact, non-ASCII-safe original; centralizing the encoding here keeps @@ -1952,7 +2070,7 @@ survives a module being pulled out into its own service. themselves. - **Where it's used**: the `options` parameter of [`IFileStorageService`](#ifilestorageservice)'s document-upload overload - (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Storage/IFileStorageService.cs:36`), + (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Storage/IFileStorageService.cs:37`), implemented by [`AzureBlobFileStorageService`](group-14-module-system-composition.md#azureblobfilestorageservice) and [`NullFileStorageService`](group-14-module-system-composition.md#nullfilestorageservice); called by @@ -1992,9 +2110,9 @@ survives a module being pulled out into its own service. that follows. - **Where it's used**: called by [`SetUserAvatarHandler`](group-24-identity-module.md#setuseravatarhandler) as the first gate on an upload - (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/SetUserAvatar/SetUserAvatarHandler.cs:54`) - before the bytes reach [`IImageProcessor`](#iimageprocessor) (`SetUserAvatarHandler.cs:76`) and then - [`IFileStorageService`](#ifilestorageservice) (`SetUserAvatarHandler.cs:90`). It has its own dedicated unit-test + (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/SetUserAvatar/SetUserAvatarHandler.cs:63`) + before the bytes reach [`IImageProcessor`](#iimageprocessor) (`SetUserAvatarHandler.cs:103`) and then + [`IFileStorageService`](#ifilestorageservice) (`SetUserAvatarHandler.cs:117`). It has its own dedicated unit-test class (`MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/ImageContentSnifferTests.cs`). - **Caveats / not-in-source**: sniffing establishes only that the *prefix* matches a known signature. It is not a decode, so a truncated or malformed body still passes here and is rejected later by @@ -2014,29 +2132,29 @@ survives a module being pulled out into its own service. the bytes to agree before it will return a MIME type, which [`ImageContentSniffer`](#imagecontentsniffer) does not need because an image has no user-visible extension trust decision to make. A zip-bomb guard (`MaxInspectedEntries = 4096`, - `DocumentContentSniffer.cs:365`) caps how many zip central-directory entries are inspected, and only - entry *names* are read, never entry streams (`DocumentContentSniffer.cs:479-481`), so a hostile archive + `DocumentContentSniffer.cs:61`) caps how many zip central-directory entries are inspected, and only + entry *names* are read, never entry streams (`DocumentContentSniffer.cs:175-177`), so a hostile archive costs nothing to refuse. - **Walkthrough**: a lookup table plus detection and signature-checking members. - - `KnownExtensions` (`DocumentContentSniffer.cs:372-383`): the one place extension, `DocumentFormats` + - `KnownExtensions` (`DocumentContentSniffer.cs:68-79`): the one place extension, `DocumentFormats` flag and canonical MIME type are tied together, compared case-insensitively. - `Detect(ReadOnlySpan content, string fileName, DocumentFormats allowed)` - (`DocumentContentSniffer.cs:395-410`): resolves the extension, then for Office Open XML formats + (`DocumentContentSniffer.cs:91-106`): resolves the extension, then for Office Open XML formats copies the span to an array (`ZipArchive` needs a seekable stream) and calls `IsOfficeOpenXml`; otherwise checks the format's byte signature directly. - `Detect(ReadOnlyMemory content, string fileName, DocumentFormats allowed)` - (`DocumentContentSniffer.cs:423-435`): the same contract without the defensive copy, for callers that + (`DocumentContentSniffer.cs:119-131`): the same contract without the defensive copy, for callers that already hold a `Memory`. - - `IsPdf` (`DocumentContentSniffer.cs:440-441`): leading bytes match `%PDF-`. - - `IsZip` (`DocumentContentSniffer.cs:446-447`): leading bytes match the zip local-file-header signature + - `IsPdf` (`DocumentContentSniffer.cs:136-137`): leading bytes match `%PDF-`. + - `IsZip` (`DocumentContentSniffer.cs:142-143`): leading bytes match the zip local-file-header signature `PK 03 04`. - - `IsOfficeOpenXml(ReadOnlyMemory content)` (`DocumentContentSniffer.cs:457-492`): opens the + - `IsOfficeOpenXml(ReadOnlyMemory content)` (`DocumentContentSniffer.cs:153-188`): opens the bytes as a `ZipArchive` and checks for a `[Content_Types].xml` entry, the marker every Office Open XML package carries at its root; a malformed or truncated archive answers `false` rather than - throwing (`DocumentContentSniffer.cs:483-491`). - - `IsPlainUtf8Text` (`DocumentContentSniffer.cs:501-515`): strips an optional UTF-8 BOM, then rejects + throwing (`DocumentContentSniffer.cs:179-187`). + - `IsPlainUtf8Text` (`DocumentContentSniffer.cs:197-211`): strips an optional UTF-8 BOM, then rejects empty content, any embedded NUL byte, or anything `Utf8.IsValid` rejects. - - `TryResolve`/`MatchesSignature`/`Extension` (`DocumentContentSniffer.cs:520-571`): the private glue + - `TryResolve`/`MatchesSignature`/`Extension` (`DocumentContentSniffer.cs:216-267`): the private glue that looks up the extension, checks it against `allowed`, and routes to the right signature check. - **Why it's built this way**: requiring extension and signature to agree stops both a renamed executable and a correctly-named file with forged content; reading only zip entry names (never opening entry @@ -2079,29 +2197,39 @@ survives a module being pulled out into its own service. (`IFileStorageService.cs:17`), and the content is read from the stream's current position (`IFileStorageService.cs:18`). - `UploadAsync(string blobName, Stream content, string contentType, FileUploadOptions options, CancellationToken cancellationToken = default)` - (`IFileStorageService.cs:36`): the overload a document upload wants, storing the response headers + (`IFileStorageService.cs:37`): the overload a document upload wants, storing the response headers carried by [`FileUploadOptions`](#fileuploadoptions) (a `Content-Disposition`, for example, is what makes a stored blob come back as a download under its original file name). It has no default body: every implementation must honor the headers directly rather than falling back to the three-argument - overload. + overload. It carries a `SuppressMessage` for analyzer `RS0026` (multiple public overloads with + optional parameters) (`IFileStorageService.cs:36`): the justification records it as a grandfathered + public overload shipped after the v1.152 public-API baseline, so changing its signature would be a + breaking change. - `DeleteAsync(string blobName, CancellationToken cancellationToken = default)` - (`IFileStorageService.cs:42`): deletes a blob; unknown names succeed (idempotent, - `IFileStorageService.cs:38`), which matches at-least-once cleanup semantics. + (`IFileStorageService.cs:43`): deletes a blob; unknown names succeed (idempotent, + `IFileStorageService.cs:39`), which matches at-least-once cleanup semantics. - **Why it's built this way**: an unconfigured default plus an `IsConfigured` gate means the framework ships avatar support without forcing every consumer to provision blob storage, and idempotent delete makes cleanup safe to retry after a partial failure. The headers-aware overload is a plain interface member with no - default implementation (`IFileStorageService.cs:36`), so every `IFileStorageService` implementation is + default implementation (`IFileStorageService.cs:37`), so every `IFileStorageService` implementation is forced to honor the response headers rather than silently dropping them via a forwarding default. - **Where it's used**: [`SetUserAvatarHandler`](group-24-identity-module.md#setuseravatarhandler) uploads the - normalized JPEG (`SetUserAvatarHandler.cs:29` for the injection, `:85` for the call), with + normalized JPEG (`SetUserAvatarHandler.cs:30` for the injection, `:117` for the call). Deletes run through + [`DeleteBlobInternalCommandHandlerBase`](group-14-module-system-composition.md#deleteblobinternalcommandhandlerbasetcommand), + which takes the service (`MMCA.Common/Source/Core/MMCA.Common.Application/InternalCommands/DeleteBlobInternalCommandHandlerBase.cs:29`) + and calls `DeleteAsync` (`DeleteBlobInternalCommandHandlerBase.cs:45`): + [`DeleteAvatarBlobInternalCommandHandler`](group-24-identity-module.md#deleteavatarblobinternalcommandhandler) + derives from it (`DeleteAvatarBlobInternalCommandHandler.cs:20`) and is the cleanup side scheduled by + [`SetUserAvatarHandler`](group-24-identity-module.md#setuseravatarhandler), [`RemoveUserAvatarHandler`](group-24-identity-module.md#removeuseravatarhandler) and - [`DeleteUserHandler`](group-24-identity-module.md#deleteuserhandler) on the cleanup side. + [`DeleteUserHandler`](group-24-identity-module.md#deleteuserhandler). [`UploadSessionAssetHandler`](group-18-conference-application.md#uploadsessionassethandler) uses the - headers-aware overload for document uploads, with `DeleteSessionAssetBlobInternalCommandHandler` on its - cleanup side - (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/SessionAssets/UseCases/DeleteSessionAssetBlob/DeleteSessionAssetBlobInternalCommandHandler.cs:1`). + headers-aware overload for document uploads (`UploadSessionAssetHandler.cs:166`), with + [`DeleteSessionAssetBlobInternalCommandHandler`](group-18-conference-application.md#deletesessionassetblobinternalcommandhandler) + on its cleanup side + (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/SessionAssets/UseCases/DeleteSessionAssetBlob/DeleteSessionAssetBlobInternalCommandHandler.cs:20`). [`NullFileStorageService`](group-14-module-system-composition.md#nullfilestorageservice) is the default registration - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:325`); + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:335`); [`AzureBlobFileStorageService`](group-14-module-system-composition.md#azureblobfilestorageservice) replaces it when configured (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:139`). @@ -2131,9 +2259,9 @@ survives a module being pulled out into its own service. [`IFileStorageService`](#ifilestorageservice); and a `Result` failure on undecodable input stops a bad upload before it ever reaches storage. - **Where it's used**: called by [`SetUserAvatarHandler`](group-24-identity-module.md#setuseravatarhandler) - between the sniffer and the upload (`SetUserAvatarHandler.cs:28` for the injection, `:71` for the call). + between the sniffer and the upload (`SetUserAvatarHandler.cs:29` for the injection, `:71` for the call). Implemented by [`ImageSharpImageProcessor`](group-14-module-system-composition.md#imagesharpimageprocessor), registered with `TryAddSingleton` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:326`); note this is the one member + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:336`); note this is the one member of the avatar trio with a real default implementation rather than a null object. ### EntityConfigurationOptions @@ -2160,9 +2288,9 @@ survives a module being pulled out into its own service. extension without the provider (or the context) taking a compile-time dependency on any specific module. The provider merges these with the name-scanned set and de-duplicates. - **Where it's used**: written through the `AddEntityConfigurationAssembly(Assembly)` extension - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:338-348`), which calls + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:348-358`), which calls `services.Configure` with a contains-check so an assembly is added at - most once (`DependencyInjection.cs:340-346`); `AddNotificationInfrastructure()` + most once (`DependencyInjection.cs:350-356`); `AddNotificationInfrastructure()` (`DependencyInjection.Notifications.cs:26-31`) is the one in-framework caller. It is read by [`DefaultEntityConfigurationAssemblyProvider`](#defaultentityconfigurationassemblyprovider). @@ -2190,9 +2318,9 @@ survives a module being pulled out into its own service. so a context built outside the full DI graph (the design-time provider behind `dotnet ef`, a test) still gets the documented default rather than a null reference. It then applies it with `sql.CommandTimeout(_persistenceSettings.CommandTimeoutSeconds)` (`SQLServerDbContext.cs:55`). - A second property, `EnableSensitiveDataLogging`, defaults to `false` (`PersistenceSettings.cs:117`) + A second property, `EnableSensitiveDataLogging`, defaults to `false` (`PersistenceSettings.cs:44`) and asks EF to render parameter VALUES into its logs and exception messages - (`EnableSensitiveDataLogging`, `PersistenceSettings.cs:97-99`); [`SensitiveDataLoggingGate`](#sensitivedatalogginggate) + (`EnableSensitiveDataLogging`, `PersistenceSettings.cs:25-26`); [`SensitiveDataLoggingGate`](#sensitivedatalogginggate) is the class that decides whether the request is actually honored. - **Why it's built this way**: a `[Range]`-validated option bound with `ValidateDataAnnotations()` and `ValidateOnStart()` turns a typo into a startup failure rather than a per-query surprise @@ -2200,19 +2328,19 @@ survives a module being pulled out into its own service. `EnableSensitiveDataLogging` is deliberately a request, not a switch: the doc comment is explicit that setting it `true` matters only when the host also reports the Development environment, and a host with no `IHostEnvironment` registered (design time, a directly-constructed test context) counts - as not Development (`PersistenceSettings.cs:101-108`), so the flag cannot by itself put parameter + as not Development (`PersistenceSettings.cs:29-34`), so the flag cannot by itself put parameter values (customer data, tokens, password hashes) into a log sink in Staging or Production ([ADR-122](https://ivanball.github.io/docs/adr/122-dev-only-relaxations-fail-closed.html)). The flag also does not by itself make EF log statements: the doc comment pairs it with setting `Logging:LogLevel:Microsoft.EntityFrameworkCore.Database.Command` to `Information` in - `appsettings.Development.json` only (`PersistenceSettings.cs:109-115`). + `appsettings.Development.json` only (`PersistenceSettings.cs:37-41`). - **Where it's used**: bound in the infrastructure registration - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:138-141`) and read by + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:144-147`) and read by [`SQLServerDbContext`](#sqlserverdbcontext) for the command timeout; no other engine context reads that property today. `EnableSensitiveDataLogging` is read by [`SensitiveDataLoggingGate`](#sensitivedatalogginggate), which [`ApplicationDbContext`](#applicationdbcontext) calls from `ConfigureSensitiveDataLogging` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:362-370`) + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:367-375`) so the guarantee applies on the shared base rather than per engine. ### ProfilingHelper @@ -2247,7 +2375,7 @@ survives a module being pulled out into its own service. (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepositoryDecorator.cs:33-111`, covering `GetAllAsync` through `CountAsync`). [`ApplicationDbContext`](#applicationdbcontext) opens its own MiniProfiler step directly rather - than through this helper (`ApplicationDbContext.cs:191`). Behavior is pinned by + than through this helper (`ApplicationDbContext.cs:196`). Behavior is pinned by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/ProfilingHelperTests.cs`. ### DefaultEntityConfigurationAssemblyProvider @@ -2280,9 +2408,9 @@ survives a module being pulled out into its own service. on the abstraction plus options keeps the persistence layer free of module references. - **Where it's used**: registered as the singleton [`IEntityConfigurationAssemblyProvider`](#ientityconfigurationassemblyprovider) via `TryAddSingleton` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:58`) and resolved by + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:59`) and resolved by [`ApplicationDbContext`](#applicationdbcontext), which iterates its assemblies inside - `ApplyConfigurationsForEntitiesInContext` (`ApplicationDbContext.cs:950,968`). Covered by + `ApplyConfigurationsForEntitiesInContext` (`ApplicationDbContext.cs:953,972`). Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DefaultEntityConfigurationAssemblyProviderTests.cs`. ### EfCoreConcurrencyConflictDetector @@ -2315,7 +2443,7 @@ survives a module being pulled out into its own service. wraps the original exception; checking only the outermost exception would miss that case. - **Where it's used**: registered as the singleton [`IConcurrencyConflictDetector`](#iconcurrencyconflictdetector) via `TryAddSingleton` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:120`). Covered by + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:126`). Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EfCoreConcurrencyConflictDetectorTests.cs`. ### EFQueryableExecutor @@ -2348,7 +2476,7 @@ survives a module being pulled out into its own service. class means every consumer gets the fallback for free and no handler references EF's static extension methods. - **Where it's used**: registered as the singleton [`IQueryableExecutor`](#iqueryableexecutor) - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:105`) and injected into + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:111`) and injected into Application-layer query code: [`EntityQueryPipeline`](group-03-querying-specifications.md#entityquerypipeline) (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/EntityQueryPipeline.cs:13`), the @@ -2400,8 +2528,9 @@ survives a module being pulled out into its own service. `[UseDatabase]` is present and then to `DataSourceKey.DefaultName` (`EntityDataSourceRegistry.cs:181`), and [`EntityTypeConfiguration`](#entitytypeconfigurationtentity-tidentifiertype) - uses it for the SQL table schema (`EntityTypeConfiguration.cs:74`, falling back to `dbo`) and the - Cosmos container name (`EntityTypeConfiguration.cs:95`, falling back to the entity type name). The + hands mapping to the engine (`EntityTypeConfiguration.cs:77`), which uses it for the SQL table schema + (`SQLServerDataSourceEngine.cs:101`, `PostgreSQLDataSourceEngine.cs:100`, falling back to `dbo`) and the + Cosmos container name (`CosmosDataSourceEngine.cs:95`, falling back to the entity type name). The Shared parser is separately exercised by `MMCA.Common/Tests/Core/MMCA.Common.Shared.Tests/Conventions/ModuleNameConventionsTests.cs`, and the persistence forwarder by @@ -2434,76 +2563,12 @@ survives a module being pulled out into its own service. left to right as "did the host ask, AND is it Development". - **Where it's used**: called from [`ApplicationDbContext`](#applicationdbcontext)'s `ConfigureSensitiveDataLogging` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:364-366`), + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:369-371`), which calls `optionsBuilder.EnableSensitiveDataLogging()` only when the gate returns `true` - (`ApplicationDbContext.cs:368`); the method sits on the shared base rather than in each engine's + (`ApplicationDbContext.cs:373`); the method sits on the shared base rather than in each engine's context class so the guarantee holds identically for every engine. Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/SensitiveDataLoggingGateTests.cs`. -### SoftDeleteFilterSql -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:16` · Level 1 · class (internal static) - -- **What it is**: the two-method internal static class that owns the `IsDeleted = 0` index predicate: - it builds the predicate in the identifier-quoting style of the target engine, and it recognises when - an index filter already carries that predicate. It is the single authority both the automatic - convention and the hand-authored opt-in call, so the two can never disagree. -- **Depends on**: [`DataSource`](#datasource) (the engine enum), - [`IAuditableEntity`](group-02-domain-building-blocks.md#iauditableentity) (for the - `nameof(IAuditableEntity.IsDeleted)` property name), and EF Core's - `Microsoft.EntityFrameworkCore.Metadata.IReadOnlyEntityType` plus the `GetColumnName()` relational - metadata extension. -- **Concept introduced, one predicate builder shared by a convention and an explicit extension.** - `[Rubric §8, Data Architecture]` (assesses whether soft-delete is honored by the schema, not just by - query filters) and `[Rubric §15, Best Practices & Code Quality]` (assesses whether one rule has one - implementation): a filtered unique index that hardcodes the literal `"[IsDeleted] = 0"` breaks in - two ways, on a model that renames the column with `HasColumnName` and on a provider that quotes - identifiers with double quotes rather than brackets. Reading the column name from the EF model and - branching on the engine fixes both, and putting that logic in one place means the automatic path - ([`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention)) and the opt-in path - ([`IndexBuilderExtensions`](#indexbuilderextensions)) emit byte-identical SQL - (`SoftDeleteFilterSql.cs:8-15`). -- **Walkthrough**: - - `Build(DataSource engine, IReadOnlyEntityType entityType)` (`SoftDeleteFilterSql.cs:27-51`) has - three steps. The **Cosmos short-circuit** (`:29-30`) returns `null` for `DataSource.CosmosDB`, - which has no filtered-index support; `null` is the contract for "leave the index untouched", and - both callers check it before doing anything (`SoftDeleteUniqueIndexConvention.cs:57-58`, - `IndexBuilderExtensions.cs:59-60`). **Column-name resolution** (`:32`) delegates to the private - `ColumnName` helper (`:57-59`), which looks the `IsDeleted` property up in the entity type and - takes its mapped column name, falling back to the CLR property name when the property is not in - the model, so a `HasColumnName` rename follows automatically. **Quoting and value spelling** - (`:34-49`, a `switch` over `DataSource`): SQL Server gets `[Column] = 0`; PostgreSQL gets - `"Column" = false`, because it maps the soft-delete flag to a real boolean column and refuses to - compare one with an integer, the one place the predicate differs by engine beyond quoting - (`SoftDeleteFilterSql.cs:41-44`); Sqlite and every other relational engine get `"Column" = 0`. The - Cosmos arm of the switch (`:46-47`) is unreachable, since the early return above already answered - `null`; it is listed anyway because the switch must name every `DataSource` member (IDE0072). - - `ContainsPredicate(string existingFilter, IReadOnlyEntityType entityType)` - (`SoftDeleteFilterSql.cs:67-74`) answers the idempotence question: does a filter already declared - on an index constrain the soft-delete column? Both the SQL Server/Sqlite integer spelling - (`{column} = 0`) and the PostgreSQL boolean spelling (`{column} = false`) are checked, each pushed - through `Normalize` (`:80-81`), which strips whitespace and the three identifier quoting styles - (`[`, `]`, `"` and a backtick), so a hand-authored `[IsDeleted] = 0`, a `"IsDeleted"=0`, the - PostgreSQL `"IsDeleted" = false` and the predicate `Build` produces all count as the same clause. - Without that normalization, and without checking both spellings, the convention could append its - own predicate to a filter that already had one, or append the integer and boolean spellings of the - same predicate to one another. -- **Why it's built this way**: `internal static` with a nullable return keeps the engine-capability - decision inside the builder rather than duplicated at each call site. The Cosmos `null` is a - deliberate signal instead of an exception, because both callers run over whole models where Cosmos - entities are simply skipped. Comparing on a normalized form rather than on the exact string is what - lets the two entry points, which do not agree on quoting, still recognise each other's output; the - PostgreSQL branch exists because a type-checked boolean column, not an untyped bit, is the PostgreSQL - mapping for the flag, so the SQL Server/Sqlite integer predicate would be a type error there. -- **Where it's used**: [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) calls - `Build` at model finalization and `ContainsPredicate` before combining, then joins with `AND` in the - same order the opt-in extension uses (`SoftDeleteUniqueIndexConvention.cs:56,74,79`), and - [`IndexBuilderExtensions`](#indexbuilderextensions) calls `Build` for a hand-authored index, - optionally joining an extra predicate (`IndexBuilderExtensions.cs:58-65`). -- **Caveats / not-in-source**: the plain double-quote-integer branch (`"Column" = 0`) is reached by any - relational engine that is neither SQL Server nor PostgreSQL nor Cosmos. Sqlite is the only such - engine registered today, so whether that quoting and value spelling suits a future provider is Not - determinable from source. - ### SqlServerUniqueConstraintViolationDetector > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SqlServerUniqueConstraintViolationDetector.cs:31` · Level 1 · class (sealed) @@ -2552,7 +2617,7 @@ survives a module being pulled out into its own service. own number check (`:19-25`). - **Where it's used**: registered as the singleton [`IUniqueConstraintViolationDetector`](#iuniqueconstraintviolationdetector) via `TryAddSingleton` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:115`), which the module + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:121`), which the module registrations rely on rather than shipping their own pair (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/DependencyInjection.cs:28`). The consumers are the handlers whose uniqueness pre-check can lose a race: @@ -2570,42 +2635,127 @@ survives a module being pulled out into its own service. describes; whether a real provider emits that wording for a non-unique failure is Not determinable from source. +### SoftDeleteFilterSql +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:17` · Level 11 · class (internal static) + +- **What it is**: the internal static class that owns the soft-delete index predicate for unique and + opted-in indexes. It resolves which column carries the flag, hands that column to the target engine + to spell the predicate, recognises when an index filter already carries that predicate, and quotes + a single column name the way the engine expects. It is the single authority both the automatic + convention and the hand-authored opt-in call, so the two can never disagree + (`SoftDeleteFilterSql.cs:9-16`). +- **Depends on**: [`DataSource`](#datasource) (the engine enum), + [`DataSourceEngines`](#datasourceengines) and [`IDataSourceEngine`](#idatasourceengine) (the + per-engine strategy that owns the quoting and the predicate spelling), + [`IAuditableEntity`](group-02-domain-building-blocks.md#iauditableentity) (for the + `nameof(IAuditableEntity.IsDeleted)` property name), and EF Core's + `Microsoft.EntityFrameworkCore.Metadata.IReadOnlyEntityType` plus the `GetColumnName()` relational + metadata extension. +- **Concept introduced, one predicate entry point shared by a convention and an explicit extension.** + `[Rubric §8, Data Architecture]` (assesses whether soft-delete is honored by the schema, not just by + query filters) and `[Rubric §15, Best Practices & Code Quality]` (assesses whether one rule has one + implementation): a filtered unique index that hardcodes the literal `"[IsDeleted] = 0"` breaks in + two ways, on a model that renames the column with `HasColumnName` and on a provider that quotes + identifiers with double quotes rather than brackets. Reading the column name from the EF model and + asking the engine strategy for the spelling fixes both, and putting that entry point in one place + means the automatic path ([`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention)) + and the opt-in path ([`IndexBuilderExtensions`](#indexbuilderextensions)) emit byte-identical SQL. + The engine-specific text itself no longer lives in this class: it is one member per engine on + [`IDataSourceEngine`](#idatasourceengine), so this class carries no `switch` over `DataSource`. +- **Walkthrough**: + - `Build(DataSource engine, IReadOnlyEntityType entityType)` (`SoftDeleteFilterSql.cs:34-35`) is + one expression: `DataSourceEngines.For(engine).BuildSoftDeleteFilter(ColumnName(entityType))`. + The private `ColumnName` helper (`:72-74`) looks the `IsDeleted` property up in the entity type + and takes its mapped column name, falling back to the CLR property name when the property is not + in the model, so a `HasColumnName` rename follows automatically. The engine then answers the + spelling: SQL Server `[Column] = 0` + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SQLServerDataSourceEngine.cs:117`), + PostgreSQL `"Column" = false` (`PostgreSQLDataSourceEngine.cs:116`), because it maps the flag to a + real boolean column and refuses to compare one with an integer, the only place the predicate + differs by engine beyond quoting (`SoftDeleteFilterSql.cs:28-33`), Sqlite `"Column" = 0` + (`SqliteDataSourceEngine.cs:115`), and Cosmos `null` (`CosmosDataSourceEngine.cs:117`), since it + has no filtered-index support. `null` is the contract for "leave the index untouched", and both + callers check it before doing anything (`SoftDeleteUniqueIndexConvention.cs:58-59`, + `IndexBuilderExtensions.cs:59-60`). + - `ContainsPredicate(string existingFilter, IReadOnlyEntityType entityType)` + (`SoftDeleteFilterSql.cs:52-59`) answers the idempotence question: does a filter already declared + on an index constrain the soft-delete column? Both the integer spelling (`{column} = 0`) and the + PostgreSQL boolean spelling (`{column} = false`) are checked, each pushed through `Normalize` + (`:77-78`), which strips whitespace and the three identifier quoting styles (`[`, `]`, `"` and a + backtick), so a hand-authored `[IsDeleted] = 0`, a `"IsDeleted"=0`, the PostgreSQL + `"IsDeleted" = false` and the predicate `Build` produces all count as the same clause. Without + that normalization, and without checking both spellings, the convention could append its own + predicate to a filter that already had one, or append the integer and boolean spellings of the + same predicate to one another. + - `QuoteColumn(DataSource engine, string column)` (`SoftDeleteFilterSql.cs:69-70`) forwards to + `DataSourceEngines.For(engine).QuoteColumn(column)`: brackets on SQL Server, the SQL-standard + double-quoted form on PostgreSQL (which rejects brackets) and SQLite (`:61-68`). It exists for + filtered-index predicates over columns other than `IsDeleted`, such as an `IS NOT NULL` clause. +- **Why it's built this way**: `internal static` with a nullable `Build` return keeps the + engine-capability decision out of the call sites; delegating the spelling to the engine strategy + keeps the per-engine text in one place per engine rather than in a `switch` here. The Cosmos `null` + is a deliberate signal instead of an exception, because both callers run over whole models where + Cosmos entities are simply skipped. Comparing on a normalized form rather than on the exact string + is what lets the two entry points, which do not agree on quoting, still recognise each other's + output. +- **Where it's used**: [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) calls + `Build` at model finalization and `ContainsPredicate` before combining + (`SoftDeleteUniqueIndexConvention.cs:57,75`), and + [`IndexBuilderExtensions`](#indexbuilderextensions) calls `Build` for a hand-authored index + (`IndexBuilderExtensions.cs:58`). `QuoteColumn` is called by + [`PushNotificationConfiguration`](#pushnotificationconfiguration) for its `DedupKey IS NOT NULL` + filter (`PushNotificationConfiguration.cs:73`) and by + [`ApplicationDbContext`](#applicationdbcontext) through a private wrapper + (`ApplicationDbContext.cs:618-619`) for the outbox and internal-command partial-index predicates + (`ApplicationDbContext.cs:664-665,754-755`). +- **Caveats / not-in-source**: a fifth engine would need its own + [`IDataSourceEngine`](#idatasourceengine) implementation to answer `BuildSoftDeleteFilter` and + `QuoteColumn`; how `DataSourceEngines.For` (`DataSourceEngines.cs:40`) treats an unmapped value is + not covered by this section. + ### EFRawSqlQueryExecutor -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/EFRawSqlQueryExecutor.cs:22` · Level 13 · class (internal sealed) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/EFRawSqlQueryExecutor.cs:28` · Level 13 · class (internal sealed) - **What it is**: the EF Core implementation of `IRawSqlQueryExecutor`, the Application-layer escape hatch for a query that is easier to express as literal SQL than as a specification. It builds a - parameterized `IQueryable` from an interpolated `FormattableString` over the resolved default - data source, then either lists or single-or-defaults it. + parameterized `IQueryable` from an interpolated `FormattableString` over the host's default + physical data source, then either lists or single-or-defaults it (`EFRawSqlQueryExecutor.cs:8-25`). - **Depends on**: `IRawSqlQueryExecutor` (the Application-layer contract it implements), [`IDbContextFactory`](#idbcontextfactory), [`IDataSourceResolver`](#idatasourceresolver), and `Microsoft.EntityFrameworkCore` (`Database.SqlQuery`, `EntityFrameworkQueryableExtensions`). -- **Concept introduced, an escape hatch that still refuses to run against the wrong engine.** `[Rubric - §3, Clean Architecture]` (the raw-SQL surface is still an abstraction, not a direct - `DbContext` reference from Application) and `[Rubric §8, Data Architecture]` (a relational-only - capability stays refused rather than silently mis-executed on Cosmos): the class resolves the - logical default source and, if that source's engine is Cosmos DB, throws - `NotSupportedException` with a message explaining the alternative (LINQ through - [`IQueryableExecutor`](#iqueryableexecutor), or moving the entity to a relational source) rather than - handing the caller a query that would fail deep inside the Cosmos provider. -- **Walkthrough**: a primary constructor takes - [`IDbContextFactory`](#idbcontextfactory) and [`IDataSourceResolver`](#idatasourceresolver) - (`EFRawSqlQueryExecutor.cs:24-26`). `QueryAsync(FormattableString sql, CancellationToken)` - (`:28-29`) and `QuerySingleOrDefaultAsync(...)` (`:32-33`) both call the private - `SqlQueryable(sql)` and materialize it with `ToListAsync`/`SingleOrDefaultAsync`. `SqlQueryable` - (`:41-56`) null-guards `sql` (`:44`), resolves the logical default source engine with - `dataSourceResolver.ResolveLogical(DataSource.SQLServer, DataSourceKey.DefaultName)` (`:46`), throws - the `NotSupportedException` when that resolved engine is `DataSource.CosmosDB` (`:47-53`), and - otherwise returns - `dbContextFactory.GetDbContext(dataSourceKey).Database.SqlQuery(sql)` (`:55`), EF's own - parameterized raw-SQL queryable, composable with further LINQ before materialization. -- **Why it's built this way**: `internal sealed`; the Cosmos check runs before the query is built so - the failure is an immediate, explanatory exception rather than a deep provider error. The - `FormattableString` parameter is the parameterization guarantee: interpolated values become SQL - parameters through EF's own handling, not string-concatenated SQL. -- **Where it's used**: registered as the scoped `IRawSqlQueryExecutor` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:110`). Covered by - `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs`. +- **Concept introduced, an escape hatch that is absent, not refused, on the wrong engine.** `[Rubric + §3, Clean Architecture]` (the raw-SQL surface is still an abstraction, not a direct `DbContext` + reference from Application) and `[Rubric §8, Data Architecture]` (a relational-only capability is + never silently mis-executed on Cosmos): the class itself carries no engine check. The engine gate + lives at registration: `AddRawSqlQueryExecutor` resolves the framework default engine and registers + the executor only when `DataSourceEngines.For(defaultEngine).Capabilities.IsRelational` + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:400-413`). A + Cosmos-default host therefore has no `IRawSqlQueryExecutor` at all, so a service that injects it + fails when the container validates rather than on its first statement + (`EFRawSqlQueryExecutor.cs:19-24`). +- **Walkthrough**: a primary constructor takes [`IDbContextFactory`](#idbcontextfactory) and + [`IDataSourceResolver`](#idatasourceresolver) (`EFRawSqlQueryExecutor.cs:28-30`). + `QueryAsync(FormattableString sql, CancellationToken)` (`:33-34`) and + `QuerySingleOrDefaultAsync(...)` (`:37-38`) both call the private `SqlQueryable(sql)` and + materialize it with `ToListAsync`/`SingleOrDefaultAsync`. `SqlQueryable` (`:46-52`) null-guards + `sql` (`:48`), resolves the default source with + `dataSourceResolver.ResolveLogical(DataSource.SQLServer, DataSourceKey.DefaultName)` (`:50`), the + same way the framework's own tables resolve theirs, letting the resolver substitute the engine a + single-engine host actually configured (`:13-15`), and returns + `dbContextFactory.GetDbContext(dataSourceKey).Database.SqlQuery(sql)` (`:51`), EF's own + parameterized raw-SQL queryable, composable with further LINQ before materialization. Because the + context comes from the scoped factory, the statement shares the caller's connection and any + transaction an `ITransactional` command opened (`:16-17`). +- **Why it's built this way**: `internal sealed`, with no engine branching in the body, so the + relational-only constraint is decided once, at composition, from the engine's declared + capabilities rather than re-checked on every call. The `FormattableString` parameter is the + parameterization guarantee: interpolated values become SQL parameters through EF's own handling, + not string-concatenated SQL ([ADR-125](https://ivanball.github.io/docs/adr/125-parameterized-sql-only.html)). +- **Where it's used**: registered as the scoped `IRawSqlQueryExecutor` via `TryAddScoped` + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:411`), the helper being invoked from the + infrastructure registration at `DependencyInjection.cs:116`. Covered by + `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs`, + and named by `MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/DataSourceBranchingFitnessTests.cs`. ### UnitOfWork > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:13` · Level 13 · class (internal sealed) @@ -2630,35 +2780,41 @@ survives a module being pulled out into its own service. service, and the repository factory, null-guarding the context factory and the repository factory into readonly fields (the data-source service is used directly from the primary-constructor parameter). - - **`_repositories`** (`UnitOfWork.cs:111`): a `Dictionary` keyed by the closed generic + - **`_repositories`** (`UnitOfWork.cs:23`): a `Dictionary` keyed by the closed generic repository interface (for example `IRepository`), so a repository is created at most once per entity type per scope. - - **`GetRepository()`** (`UnitOfWork.cs:121-134`): on a cache miss, resolves + - **`GetRepository()`** (`UnitOfWork.cs:33-46`): on a cache miss, resolves the entity's [`DataSourceKey`](#datasourcekey) via - `dataSourceService.GetDataSourceKey(typeof(TEntity))` (`UnitOfWork.cs:128`), asks the context - factory for the matching context (`:129`), and builds a read-write + `dataSourceService.GetDataSourceKey(typeof(TEntity))` (`UnitOfWork.cs:40`), asks the context + factory for the matching context (`:41`), and builds a read-write [`IRepository`](#irepositorytentity-tidentifiertype) through - [`IRepositoryFactory`](#irepositoryfactory) (`:130`); constrained to + [`IRepositoryFactory`](#irepositoryfactory) (`:42`); constrained to `AuditableAggregateRootEntity` so only aggregate roots get a mutable repository. - - **`GetReadRepository()`** (`UnitOfWork.cs:141-154`): the same resolution - but calls `CreateReadOnly` (`:150`) and accepts any `AuditableBaseEntity`, + - **`GetReadRepository()`** (`UnitOfWork.cs:53-66`): the same resolution + but calls `CreateReadOnly` (`:62`) and accepts any `AuditableBaseEntity`, returning [`IReadRepository`](#ireadrepositorytentity-tidentifiertype) for query handlers. - - **Save and transaction methods** (`UnitOfWork.cs:157-167`): `SaveChangesAsync`, - `RequestIdentityInsert`, and `ExecuteInTransactionAsync` all delegate straight to the context - factory, because in a multi-database scope the factory is what coordinates saving and transacting - across every context the scope touched. `Save`, `BeginTransaction`, `CommitTransaction`, and - `RollbackTransaction` were removed from this pass-through set. - - **Disposal** (`UnitOfWork.cs:169-195`): implements both `Dispose` and `DisposeAsync` over a - `volatile bool _disposed` flag (`UnitOfWork.cs:113`), disposing the context factory exactly once - and suppressing finalization on both paths. + - **Save and transaction methods** (`UnitOfWork.cs:69-79`): `SaveChangesAsync` (`:69-70`), + `RequestExplicitKeyInsert` (`:73`), and `ExecuteInTransactionAsync` (`:76-79`) all delegate + straight to the context factory, because in a multi-database scope the factory is what + coordinates saving and transacting across every context the scope touched. + `RequestExplicitKeyInsert` forwards to `IDbContextFactory.RequestExplicitKeyInsert()`, the + engine-neutral signal that the next save may carry explicit values for store-generated keys: + each context's engine decides whether those inserts need a per-table toggle (its + [`IExplicitKeyInsertDialect`](#iexplicitkeyinsertdialect)), an engine with none saves unchanged, + and the flag clears after the save + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/IDbContextFactory.cs:34-40`). + There is no synchronous `Save` and no manual begin/commit/rollback on this type. + - **Disposal** (`UnitOfWork.cs:81-107`): implements both `Dispose` (`:81-85`) and `DisposeAsync` + (`:87-95`) over a `volatile bool _disposed` flag (`UnitOfWork.cs:25`), disposing the context + factory exactly once and suppressing finalization on both paths. - **Why it's built this way**: the unit of work plus the factory hide the physical topology from handlers ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)), and per-scope repository caching guarantees a single change tracker per database. It is `internal sealed` because consumers only ever see the [`IUnitOfWork`](#iunitofwork) abstraction (dependency inversion). - **Where it's used**: registered as the scoped [`IUnitOfWork`](#iunitofwork) via `TryAddScoped` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:124`) and injected into + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:130`) and injected into virtually every command and query handler in Common and in both apps, and into the module seeders. ### AuditTrailEntry @@ -2704,7 +2860,7 @@ survives a module being pulled out into its own service. - `ChangedBy`, `ChangedOn`, `CorrelationId`, `TenantId` (`AuditTrailEntry.cs:80,83,91,99`): the provenance block. `ChangedBy` is null for a save that carried no identity (a background service, a seeder), `CorrelationId` is the ambient trace id, and `TenantId` comes from - `ApplicationDbContext.CurrentTenantId` at capture (`ApplicationDbContext.cs:141`). + `ApplicationDbContext.CurrentTenantId` at capture (`ApplicationDbContext.cs:154`). - **Row shape** (`AuditTrailEntry.cs:15-21`): a `Modified` save produces one row per property that actually changed, so a trail reads as a field-level history; an `Added` or `Deleted` save produces a single summary row with a null `PropertyName`, because the interesting fact there is the @@ -2716,19 +2872,19 @@ survives a module being pulled out into its own service. keeps the trail from becoming a second copy of a data subject's data that erasure would have to chase ([ADR-005](https://ivanball.github.io/docs/adr/005-soft-delete-vs-erasure.html)). - **Where it's used**: mapped by `ApplicationDbContext.ConfigureAuditTrail` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:844-873`) - to `dbo.AuditTrailEntries` (`ApplicationDbContext.cs:853`) with `EntityType` at 256 non-unicode + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:847-876`) + to `dbo.AuditTrailEntries` (`ApplicationDbContext.cs:856`) with `EntityType` at 256 non-unicode characters, `EntityKey` at 128, `PropertyName` at 128 non-unicode, `Operation` at 16 non-unicode and - both `CorrelationId` and `TenantId` at 64 (`ApplicationDbContext.cs:855-861`), plus two indexes: + both `CorrelationId` and `TenantId` at 64 (`ApplicationDbContext.cs:858-864`), plus two indexes: `IX_AuditTrailEntries_Entity` over `(EntityType, EntityKey, ChangedOn)` for the read path and - `IX_AuditTrailEntries_ChangedOn` for the retention sweep (`ApplicationDbContext.cs:862-871`). The + `IX_AuditTrailEntries_ChangedOn` for the retention sweep (`ApplicationDbContext.cs:865-874`). The mapping only happens when `AuditTrail:Enabled` is true: the context resolves that flag once in - `OnConfiguring` (`ApplicationDbContext.cs:327`) and `ConfigureAuditTrail` returns immediately when - it is false (`ApplicationDbContext.cs:846-849`), so a host that never opted in has exactly the model - it had before the trail shipped (`ApplicationDbContext.cs:71-75`). + `OnConfiguring` (`ApplicationDbContext.cs:332`) and `ConfigureAuditTrail` returns immediately when + it is false (`ApplicationDbContext.cs:849-852`), so a host that never opted in has exactly the model + it had before the trail shipped (`ApplicationDbContext.cs:84-88`). ### CaptureContext -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.AuditTrail` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:538` · Level 0 · record struct (private readonly, nested) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.AuditTrail` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:540` · Level 0 · record struct (private readonly, nested) - **What it is**: a four-field value carrying the provenance every trail row of one save shares: the acting user, the capture instant, the trace id, and the tenant. @@ -2742,7 +2898,7 @@ survives a module being pulled out into its own service. identical `ChangedOn`, which is what lets a reader group a save back together. - **Walkthrough**: the positional members are `ChangedBy` (nullable user id), `ChangedOn` (UTC instant), `CorrelationId` (nullable trace id) and `TenantId` (nullable), declared at - `AuditTrailSaveChangesInterceptor.cs:538-542`. It is constructed exactly once per capture, in + `AuditTrailSaveChangesInterceptor.cs:540-544`. It is constructed exactly once per capture, in `CaptureChanges` (`AuditTrailSaveChangesInterceptor.cs:190-194`), where the trace id and tenant are already truncated to their column widths through [`ColumnWidth.Truncate`](#columnwidth) (`MaxCorrelationIdLength` 64 and `MaxTenantIdLength` 64, @@ -2769,10 +2925,10 @@ survives a module being pulled out into its own service. `[Rubric §11, Security]` (whether stored permission grants are an explicit choice rather than an always-on table). `ApplicationDbContext.ResolvePermissionGrantGate` reads `serviceProvider.GetService() is not null` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:911`) + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:914`) and ANDs it with a check that this context's physical source name matches `Authentication:PermissionGrants:DataSourceName` - (`ApplicationDbContext.cs:912-915`). Registering the class IS the opt-in the context reads: no flag, + (`ApplicationDbContext.cs:915-918`). Registering the class IS the opt-in the context reads: no flag, no boolean setting, just whether the type was registered at all. This is the same mechanic [`RefreshSessionModelBuilderExtensions`](#refreshsessionmodelbuilderextensions)'s gate uses for the `RefreshSessions` table, applied to a second framework-owned table. @@ -2786,8 +2942,8 @@ survives a module being pulled out into its own service. answerable questions, since a host could bind the settings section without ever calling `AddStoredPermissionGrants`. - **Where it's used**: registered as a singleton by `AddStoredPermissionGrants` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:112`), read by - `ApplicationDbContext.ResolvePermissionGrantGate` (`ApplicationDbContext.cs:910-915`) and by + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:116`), read by + `ApplicationDbContext.ResolvePermissionGrantGate` (`ApplicationDbContext.cs:913-918`) and by `ConfigurePermissionGrants` which calls [`PermissionGrantModelBuilderExtensions`](#permissiongrantmodelbuilderextensions)`.ApplyPermissionGrantConfiguration` behind that gate. Covered by @@ -2812,8 +2968,8 @@ survives a module being pulled out into its own service. changed this, and when"). Most feature flags in this codebase decide whether code runs; this one also decides whether a table exists. `Enabled` is read in [`ApplicationDbContext`](#applicationdbcontext) with `GetService`, not `GetRequiredService`, and - cached in a field (`ApplicationDbContext.cs:81`, `:291`) that the model builder consults before - mapping the entity (`ApplicationDbContext.cs:844-849`), so a host that leaves it false has exactly + cached in a field (`ApplicationDbContext.cs:94`, `:291`) that the model builder consults before + mapping the entity (`ApplicationDbContext.cs:847-852`), so a host that leaves it false has exactly the model it had before the trail existed and its migrations never see an `AuditTrailEntries` table (`AuditTrailSettings.cs:10-15`). That is what makes an opt-in feature genuinely free for a host that does not want it: a mapped-but-empty table would still have to be migrated. The trail is @@ -2827,11 +2983,11 @@ survives a module being pulled out into its own service. - **Walkthrough**: one static field and three `init` properties. - `SectionName = "AuditTrail"` (`AuditTrailSettings.cs:19`), the binding key. - `Enabled` (`AuditTrailSettings.cs:26`), defaulting to `false`. Read by - [`ApplicationDbContext`](#applicationdbcontext) for the model gate (`ApplicationDbContext.cs:327`) - and re-checked by the cleanup job before it does any work (`AuditTrailCleanupJob.cs:71-74`). + [`ApplicationDbContext`](#applicationdbcontext) for the model gate (`ApplicationDbContext.cs:332`) + and re-checked by the cleanup job before it does any work (`AuditTrailCleanupJob.cs:68-71`). - `RetentionDays` (`AuditTrailSettings.cs:38`), `[Range(1, 3650)]` (`:37`), default `90`. [`AuditTrailCleanupJob`](#audittrailcleanupjob) turns it into a cutoff instant - (`AuditTrailCleanupJob.cs:76`) and purges from every relational source in use, skipping Cosmos + (`AuditTrailCleanupJob.cs:73`) and purges from every relational source in use, skipping Cosmos (`:79-81`) and expanding each source across the declared tenants (`:83-86`). - `DataSource` (`AuditTrailSettings.cs:46`), default `DataSource.SQLServer`. Note the asymmetry the doc calls out (`AuditTrailSettings.cs:40-45`): rows are WRITTEN to every relational source @@ -2855,12 +3011,12 @@ survives a module being pulled out into its own service. with the table on or off (`DesignTimeDbContextHelper.cs:165-166`). The recording itself is [`AuditTrailSaveChangesInterceptor`](#audittrailsavechangesinterceptor), registered as a singleton at `DependencyInjection.Jobs.cs:117` and resolved by the context with `GetService` so its absence reads - as "not registered" (`ApplicationDbContext.cs:314`). Covered by + as "not registered" (`ApplicationDbContext.cs:319`). Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailModelGateTests.cs` and `AddAuditTrailTests.cs`. ### PendingEntityKey -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.AuditTrail` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:547` · Level 1 · record (private sealed, nested) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.AuditTrail` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:549` · Level 1 · record (private sealed, nested) - **What it is**: a two-field pairing of a staged trail row with the entity entry whose primary key the database has not assigned yet. It exists only between `SavingChanges` and `SavedChanges`. @@ -2877,7 +3033,7 @@ survives a module being pulled out into its own service. whole design exists for. - **Walkthrough**: the positional members are `Row` (the tracked trail row whose `EntityKey` must be rewritten) and `Entry` (the audited entry whose key the database assigns), declared at - `AuditTrailSaveChangesInterceptor.cs:547`. Instances are produced by `CaptureEntry` only when the + `AuditTrailSaveChangesInterceptor.cs:549`. Instances are produced by `CaptureEntry` only when the entry is `Added` **and** `HasTemporaryKey(entry)` returns true (`AuditTrailSaveChangesInterceptor.cs:268-270`, with the temporary-key test walking every primary key property at `:449-466`). They are accumulated into a list and parked in a @@ -2921,7 +3077,7 @@ survives a module being pulled out into its own service. (`:57`, `:59-62`); the base class is what turns a thrown exception there into "log and keep looping" rather than a host crash. - **Why it's built this way**: registering it through `TryAddEnumerable` rather than `AddHostedService` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:126-129`) is what keeps + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:130-133`) is what keeps two modules calling `AddStoredPermissionGrants` from starting two refresh loops in one process, the same idempotent-registration discipline [`RefreshSessionCleanupService`](#refreshsessioncleanupservice) and @@ -2930,7 +3086,7 @@ survives a module being pulled out into its own service. a stored-grant feature that takes the host down on a transient database fault would be worse than the fallback it exists to layer over. - **Where it's used**: registered as a singleton `IHostedService` by `AddStoredPermissionGrants` - (`DependencyInjection.Auth.cs:128-129`), started and stopped by the generic host alongside every other + (`DependencyInjection.Auth.cs:132-133`), started and stopped by the generic host alongside every other `BackgroundService`. Its only collaborator is [`PermissionGrantCache`](#permissiongrantcache) through `IPermissionGrantCache.RefreshAsync`. @@ -2944,15 +3100,15 @@ survives a module being pulled out into its own service. - **Walkthrough** - Three public constants name the objects so tests and callers do not restate strings: `TableName = "RefreshSessions"` (`RefreshSessionModelBuilderExtensions.cs:19`), `TokenHashIndexName` (`:22`) and `UserIndexName` (`:25`). - The method null-guards, maps the table with the caller's schema defaulting to `dbo` (`RefreshSessionModelBuilderExtensions.cs:36-40`), and keys on `Id` (`:41`). - - `TokenHash` is `IsRequired`, `HasMaxLength(RefreshSession.TokenHashLength)`, `IsUnicode(false)`, `IsFixedLength()` (`RefreshSessionModelBuilderExtensions.cs:45-49`). The constant is 64 (`MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:34`), the length of a SHA-256 digest rendered as hex. The comment at `:43-44` gives the reason for the three facets: the value is always a 64-character hex digest, so a Unicode or variable-width column would double the index it has to fit in for nothing. + - `TokenHash` is `IsRequired`, `HasMaxLength(RefreshSession.TokenHashLength)`, `IsUnicode(false)`, `IsFixedLength()` (`RefreshSessionModelBuilderExtensions.cs:45-49`). The constant is 64 (`MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:42`), the length of a SHA-256 digest rendered as hex. The comment at `:43-44` gives the reason for the three facets: the value is always a 64-character hex digest, so a Unicode or variable-width column would double the index it has to fit in for nothing. - `ReplacedByTokenHash` gets the same three facets minus `IsRequired` (`RefreshSessionModelBuilderExtensions.cs:51-54`), because it is null until the session is rotated. - - `ReasonRevoked`, `IpAddress` and `UserAgent` take their lengths from the domain constants (64, 45 and 512 at `RefreshSession.cs:43`, `:37`, `:40`), the first two non-Unicode (`RefreshSessionModelBuilderExtensions.cs:56-58`). 45 is the length of a full IPv6 text form; `UserAgent` stays Unicode because a user-agent string is arbitrary client text. + - `ReasonRevoked`, `IpAddress` and `UserAgent` take their lengths from the domain constants (64, 45 and 512 at `RefreshSession.cs:51`, `:37`, `:40`), the first two non-Unicode (`RefreshSessionModelBuilderExtensions.cs:56-58`). 45 is the length of a full IPv6 text form; `UserAgent` stays Unicode because a user-agent string is arbitrary client text. - The unique index over `TokenHash` (`RefreshSessionModelBuilderExtensions.cs:64-66`) is the validation path: every refresh presents a token and must be answered by exactly one row. The comment at `:60-63` gives two reasons for the uniqueness, and both are security reasons rather than performance ones: a hash collision across users would let one account's token validate against another's session, and a double-insert of the same token becomes a database error instead of an ambiguity the reuse check has to resolve. - The composite index on `(UserId, RevokedAt)` (`RefreshSessionModelBuilderExtensions.cs:70-71`) serves the family question, "every live session for this user", which is asked on the per-user session cap, on reuse detection and on sign-out-everywhere. Without it each of those scans the table (`:68-69`). - The builder is returned for chaining (`RefreshSessionModelBuilderExtensions.cs:74`). - **Why it's built this way**: hashing the token rather than storing it, and rotating per device, is [ADR-097](https://ivanball.github.io/docs/adr/097-multi-device-refresh-sessions.html), which supersedes the single-plaintext-column storage model of [ADR-050](https://ivanball.github.io/docs/adr/050-jwt-refresh-token-rotation.html) while keeping its rotation and reuse-detection policy. The column shapes here are what make that model cheap: a fixed-width non-Unicode digest keeps the unique index narrow, and the two indexes are exactly the two questions the auth service asks. Nothing here is soft-deletable and nothing is audit-stamped, which is deliberate and is why the table needs its own mapping method rather than riding the module entity-configuration mechanism. -- **Where it's used**: called by [`ApplicationDbContext`](#applicationdbcontext) from `ConfigureRefreshSessions` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:889-897`) behind the two-part gate computed in the constructor: `RefreshSessions:Enabled` is true **and** this context's physical source name equals `RefreshSessions:DataSourceName` (`ApplicationDbContext.cs:331-334`). At design time the same gate is fed by [`DesignTimeDbContextOptions`](#designtimedbcontextoptions)`.EnableRefreshSessions`. Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Auth/RefreshSessionModelBuilderExtensionsTests.cs` and, for the gate itself, `.../Persistence/DbContexts/RefreshSessionModelGateTests.cs:89`. -- **Caveats / not-in-source**: the class doc says the consumer's Identity context calls this from its own `OnModelCreating` (`RefreshSessionModelBuilderExtensions.cs:12-13`), which describes an earlier arrangement. The base context now calls it directly behind the gate, and the doc on `ConfigureRefreshSessions` explains why (`ApplicationDbContext.cs:880-887`): downstream apps run on the sealed engine contexts and have no context class to override ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)). Calling it by hand remains supported for a host that does have its own context class; trust the context, not the older sentence. +- **Where it's used**: called by [`ApplicationDbContext`](#applicationdbcontext) from `ConfigureRefreshSessions` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:892-900`) behind the two-part gate computed in the constructor: `RefreshSessions:Enabled` is true **and** this context's physical source name equals `RefreshSessions:DataSourceName` (`ApplicationDbContext.cs:336-339`). At design time the same gate is fed by [`DesignTimeDbContextOptions`](#designtimedbcontextoptions)`.EnableRefreshSessions`. Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Auth/RefreshSessionModelBuilderExtensionsTests.cs` and, for the gate itself, `.../Persistence/DbContexts/RefreshSessionModelGateTests.cs:89`. +- **Caveats / not-in-source**: the class doc says the consumer's Identity context calls this from its own `OnModelCreating` (`RefreshSessionModelBuilderExtensions.cs:12-13`), which describes an earlier arrangement. The base context now calls it directly behind the gate, and the doc on `ConfigureRefreshSessions` explains why (`ApplicationDbContext.cs:883-890`): downstream apps run on the sealed engine contexts and have no context class to override ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)). Calling it by hand remains supported for a host that does have its own context class; trust the context, not the older sentence. ### PermissionGrantModelBuilderExtensions > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/PermissionGrantModelBuilderExtensions.cs:15` · Level 4 · class (public static) @@ -2960,7 +3116,7 @@ survives a module being pulled out into its own service. - **What it is**: the single place the `PermissionGrants` table is mapped. One extension method, `ApplyPermissionGrantConfiguration(this ModelBuilder, string? schema = "dbo")`, configures the `PermissionGrant` entity: table, key, column widths, and the unique index that makes a grant - idempotent (`PermissionGrantModelBuilderExtensions.cs:15-160`). + idempotent (`PermissionGrantModelBuilderExtensions.cs:15-64`). - **Depends on**: `PermissionGrant` from `MMCA.Common.Domain.Auth` (including its `RoleMaxLength`/`PermissionMaxLength` constants) and EF Core's `ModelBuilder`. - **Concept introduced, the same opt-in-table mapping shape as refresh sessions, applied to a second @@ -2970,7 +3126,7 @@ survives a module being pulled out into its own service. `Persistence.Auth` and follows the identical pattern: a callable extension method rather than an inline mapping in a context, so the table only appears in a host's model when something calls it. - **Walkthrough** - - Two public constants: `TableName = "PermissionGrants"` (`PermissionGrantModelBuilderExtensions.cs:114`) + - Two public constants: `TableName = "PermissionGrants"` (`PermissionGrantModelBuilderExtensions.cs:18`) and `RolePermissionIndexName = "IX_PermissionGrants_Role_Permission"` (`:117`). - The method null-guards, maps the table with the caller's schema defaulting to `dbo` (`:126-132`), and keys on `Id` (`:133`). @@ -2994,7 +3150,7 @@ survives a module being pulled out into its own service. a second lookup. - **Where it's used**: called by `ApplicationDbContext.ConfigurePermissionGrants` behind [`PermissionGrantModelGate`](#permissiongrantmodelgate)'s presence check plus the physical-source - match (`ApplicationDbContext.cs:910-925`). Covered by + match (`ApplicationDbContext.cs:913-928`). Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Auth/PermissionGrantModelBuilderExtensionsTests.cs` and `.../Persistence/DbContexts/PermissionGrantModelGateTests.cs`, and consumed by [`EFPermissionGrantStore`](#efpermissiongrantstore) through the `DbSet` the mapping @@ -3006,7 +3162,7 @@ survives a module being pulled out into its own service. - **What it is**: the in-memory snapshot of every role's stored permissions, keyed per role so a read never scans the whole grant table. It implements `IPermissionGrantCache` (the read side) and `IPermissionGrantCacheInvalidator` (the write-triggered refresh), and both resolve to the same - singleton instance (`PermissionGrantCache.cs:35-269`). + singleton instance (`PermissionGrantCache.cs:35-131`). - **Depends on**: `IMemoryCache`, `IServiceScopeFactory` (to resolve the scoped `IPermissionGrantStore` for a rebuild), `IOptions`, and BCL `FrozenSet`/`SemaphoreSlim`. @@ -3015,34 +3171,44 @@ survives a module being pulled out into its own service. lock-free) and `[Rubric §11, Security]` (a reader must never observe a half-built permission set). A `SemaphoreSlim` (`_rebuildLock`) serializes rebuilds so the interval refresh and a concurrent administration edit cannot interleave a write from one with the eviction pass of the other - (`PermissionGrantCache.cs:195`, doc at `:190-194`); `_cachedRoles` is `volatile` and replaced - wholesale on every rebuild rather than mutated, so a reader never sees a half-built set (`:201`, - doc at `:197-200`). + (`PermissionGrantCache.cs:50`, doc at `:46-49`); `_cachedRoles` is `volatile` and replaced + wholesale on every rebuild rather than mutated, so a reader never sees a half-built set (`:56`, + doc at `:52-55`). - **Walkthrough** - - `GetPermissions(role)` (`PermissionGrantCache.cs:204-207`): a plain `IMemoryCache.TryGetValue` - keyed by `CacheKey(role)`, returning `Empty` (a static `FrozenSet`, `:186`) for a blank + - `GetPermissions(role)` (`PermissionGrantCache.cs:59-62`): a plain `IMemoryCache.TryGetValue` + keyed by `CacheKey(role)`, returning `Empty` (a static `FrozenSet`, `:41`) for a blank role or a cache miss. No lock, no allocation on a miss. - - `RefreshAsync` (`:210-247`): takes the rebuild lock, opens a DI scope, resolves + - `RefreshAsync` (`:65-107`): takes the rebuild lock, opens a DI scope, resolves `IPermissionGrantStore`, reads every grant with `GetAllAsync`, groups by role (`StringComparer.OrdinalIgnoreCase`) into per-role `FrozenSet`s - (`StringComparer.Ordinal` for the permission names themselves), writes each role's entry with the - configured `CacheSeconds` lifetime (`:227-232`), then evicts roles that were cached a moment ago - but carry no grant now, in that order, so a role that still has grants is never briefly absent - (`:234-239`, comment at `:234-235`). `_cachedRoles` is replaced last (`:241`). - - `InvalidateAsync(role, ct)` (`:250-258`): the `role` argument narrows what a caller MEANS, not + (`StringComparer.Ordinal` for the permission names themselves), writes each role's entry + (`:89-92`), then evicts roles that were cached a moment ago but carry no grant now, in that order, + so a role that still has grants is never briefly absent (`:96-99`, comment at `:94-95`). + `_cachedRoles` is replaced last (`:101`). + - **Entry lifetime is three refresh intervals, not one** (`:87`, `CacheSeconds * 3`), for the two + reasons the comment gives (`:82-86`). The next rebuild starts one interval AFTER the previous one + finishes, so an entry living exactly one interval would expire before it is rewritten and every + cycle would open a window with no stored grants. And when the refresh keeps failing, the snapshot + must still stop answering eventually, so a revoke cannot grant forever on a dead database: three + intervals is that bound. + - `InvalidateAsync(role, ct)` (`:110-118`): the `role` argument narrows what a caller MEANS, not what is reloaded; `RefreshAsync` always reloads everything, and the comment explains why - (`:252-254`): a single query either way, and reloading everything keeps one code path that cannot + (`:112-114`): a single query either way, and reloading everything keeps one code path that cannot leave a second role stale because a caller named only the first. - - `Dispose` (`:266`) disposes only the semaphore; the cache entries belong to the host's + - `Dispose` (`:126`) disposes only the semaphore; the cache entries belong to the host's `IMemoryCache` and are deliberately left alone since this is a singleton whose disposal happens at - shutdown (doc at `:261-265`). + shutdown (doc at `:121-125`). + - `CacheKey(role)` (`:130`) upper-cases the role (`ToUpperInvariant`) before building + `permgrant:role:{ROLE}`. Role names compare case-insensitively everywhere they are read, while + `IMemoryCache` compares keys ordinally, so without the normalization a role stored as `Admin` and + read as `admin` would miss (comment at `:128-129`). - **Why it's built this way**: one instance answering both the read interface and the invalidator interface, rather than two collaborating types, is what guarantees an edit and the reads that follow it cannot end up looking at two different snapshots (the DI registration comment states this - directly, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:116-117`). + directly, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:120-121`). - **Where it's used**: registered as a singleton for all three roles (concrete type, and the two interfaces resolved from the same instance) by `AddStoredPermissionGrants` - (`DependencyInjection.Auth.cs:118-122`). Refreshed on an interval by + (`DependencyInjection.Auth.cs:122-126`). Refreshed on an interval by [`PermissionGrantRefreshService`](#permissiongrantrefreshservice) and read by `LayeredPermissionRegistry` and the stored-grant administration surface. @@ -3073,13 +3239,13 @@ survives a module being pulled out into its own service. here as a set. - **Opt in twice over** (`AuditTrailSaveChangesInterceptor.cs:33-37`): the host must call `AddAuditTrail` (the context resolves this interceptor with `GetService`, so its absence is a - silent no-op, `ApplicationDbContext.cs:311-316`) **and** set `AuditTrail:Enabled` so the table is + silent no-op, `ApplicationDbContext.cs:316-321`) **and** set `AuditTrail:Enabled` so the table is mapped. Then the entity must carry the marker. Nothing about the feature is on by default. - **Position in the pipeline** (`AuditTrailSaveChangesInterceptor.cs:26-32`): registered last, after [`AuditSaveChangesInterceptor`](#auditsavechangesinterceptor), the optional [`TenantSaveChangesInterceptor`](#tenantsavechangesinterceptor) and [`DomainEventSaveChangesInterceptor`](#domaineventsavechangesinterceptor) - (`ApplicationDbContext.cs:292-316`), so the values it diffs are final: the audit stamps are + (`ApplicationDbContext.cs:297-321`), so the values it diffs are final: the audit stamps are already written when it runs. - **Correlation is the trace id, not the scoped correlation context** (`AuditTrailSaveChangesInterceptor.cs:44-55`): this interceptor is a singleton and the only @@ -3090,13 +3256,13 @@ survives a module being pulled out into its own service. when a request carries no `X-Correlation-ID` header. A caller-supplied header value is therefore NOT recorded today. - **Tenant is read off the context** (`AuditTrailSaveChangesInterceptor.cs:56-60`), through the live - accessor the scoped context factory assigns (`ApplicationDbContext.cs:141`), so it does reach the + accessor the scoped context factory assigns (`ApplicationDbContext.cs:154`), so it does reach the interceptor where a scoped service would not. - **Walkthrough**: - **Constants** (`AuditTrailSaveChangesInterceptor.cs:65-87`): the three operation names, the four column widths (`MaxEntityTypeLength` 256, `MaxKeyLength` 128, `MaxCorrelationIdLength` 64, `MaxTenantIdLength` 64) and the `|` composite-key separator. They match the model mapping in - `ApplicationDbContext.ConfigureAuditTrail` exactly (`ApplicationDbContext.cs:855-861`). + `ApplicationDbContext.ConfigureAuditTrail` exactly (`ApplicationDbContext.cs:858-864`). - **Static state** (`AuditTrailSaveChangesInterceptor.cs:89-120`): two `ConditionalWeakTable`s keyed by context (pending key fix-ups, and a marker for "a capture staged rows but the save has not finished"), a `ConcurrentDictionary` caching the PII verdict per (declaring type, property name), @@ -3104,7 +3270,7 @@ survives a module being pulled out into its own service. CLR type**, not by the absence of the marker, which is what keeps the trail from recording its own rows in an unbounded feedback loop and keeps the outbox, inbox and job tables out of a history nobody asked for (`AuditTrailSaveChangesInterceptor.cs:108-120`). `IsFrameworkEntity(Type)` - (`:171`) is the `internal` window onto that set, which is both how `ShouldAudit` consults it and + (`:172`) is the `internal` window onto that set, which is both how `ShouldAudit` consults it and how a test can assert the exclusion list directly. - **The four overrides** (`AuditTrailSaveChangesInterceptor.cs:123-164`): `SavingChangesAsync` and `SavingChanges` both call `CaptureChanges`; `SavedChangesAsync` and `SavedChanges` both run the @@ -3112,10 +3278,10 @@ survives a module being pulled out into its own service. context passes straight through. - **`CaptureChanges`** (`AuditTrailSaveChangesInterceptor.cs:178-220`): the first statement is the cheap double gate, `context.Model.FindEntityType(typeof(AuditTrailEntry)) is null` - (`:179-185`), which covers both a host that never opted in and Cosmos (which skips relational + (`:181-184`), which covers both a host that never opted in and Cosmos (which skips relational tables), because `Set()` would throw for both. Then it discards an abandoned capture, builds one [`CaptureContext`](#capturecontext), snapshots - `ChangeTracker.Entries().Where(ShouldAudit).ToArray()` (`:197`, materialized before adding, + `ChangeTracker.Entries().Where(ShouldAudit).ToArray()` (`:198`, materialized before adding, because enumerating the tracker lazily while adding to it would throw), and captures each entry. - **`ShouldAudit`** (`AuditTrailSaveChangesInterceptor.cs:226-229`): the entity carries the marker, is not a framework bookkeeping type, and is in state `Added`, `Modified` or `Deleted`. @@ -3125,9 +3291,9 @@ survives a module being pulled out into its own service. - **`CaptureModifiedProperties`** (`AuditTrailSaveChangesInterceptor.cs:278-319`): one row per property that is `IsModified` **and** whose value actually differs. A property EF flagged as modified but whose value is unchanged (the whole-entity `Update` idiom) writes nothing - (`:272-276`). `PiiRedactor.HasPii(entry.Metadata.ClrType)` is checked once per entity so a type - with no personal data skips the per-property attribute lookup entirely (`:286`), and a PII - property records `PiiRedactor.RedactedToken` on both sides (`:309-310`). + (`:273-277`). `PiiRedactor.HasPii(entry.Metadata.ClrType)` is checked once per entity so a type + with no personal data skips the per-property attribute lookup entirely (`:287`), and a PII + property records `PiiRedactor.RedactedToken` on both sides (`:310-311`). - **`DiscardAbandonedCapture`** (`AuditTrailSaveChangesInterceptor.cs:340-361`): the retry-safety mechanic. An execution strategy that retries a failed save re-runs `SavingChanges` against a tracker that still holds the previous attempt's `Added` rows, so without this one transient SQL @@ -3143,12 +3309,16 @@ survives a module being pulled out into its own service. then brings the tracked instance and its snapshot in line so a later save does not re-issue the update, and the ordering there is load-bearing: writing `OriginalValue` must precede clearing `IsModified`, because clearing the flag reverts the current value to the original. - - **Helpers**: `AddRow` (`:325-331`, mutation is `Add` only, because the save runs with automatic + - **Helpers**: `AddRow` (`:326-332`, mutation is `Add` only, because the save runs with automatic change detection off), `HasTemporaryKey` (`:449-466`), `BuildEntityKey` (`:473-485`, a keyless - entity yields an empty string rather than throwing), `IsPiiProperty` (`:492-504`, a shadow - property has no `PropertyInfo` so it can never be personal data), `FormatValue` (`:510-511`, + entity yields an empty string rather than throwing), `IsPiiProperty` (`:493-507`, a shadow + property has no `PropertyInfo` so it can never be personal data; the verdict uses the static + `Attribute.IsDefined(propertyInfo, typeof(PiiAttribute), inherit: true)` at `:505`, because the + `PropertyInfo.IsDefined` instance method ignores `inherit` for properties, and only the static form + walks an override back to its base declaration, matching how `PiiRedactor` decides, comment at + `:503-504`), `FormatValue` (`:513-514`, `CultureInfo.InvariantCulture` on purpose, so a trail read years later or on a differently - localized replica shows the value that was written) and `ValuesEqual` (`:517-530`, byte arrays + localized replica shows the value that was written) and `ValuesEqual` (`:520-533`, byte arrays such as row versions are compared by content, since reference equality would report every save as a change). Column-width truncation itself is no longer a private method on this type: every call site (the correlation id and tenant id in `CaptureChanges` at `:193-194`, the entity type in @@ -3168,7 +3338,7 @@ survives a module being pulled out into its own service. (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Jobs.cs:115-117`, singleton for the same reason as the other two save interceptors: stateless, with per-save state in a `ConditionalWeakTable` keyed by context), added to the EF pipeline in - `ApplicationDbContext.OnConfiguring` (`ApplicationDbContext.cs:311-316`), and registered in the + `ApplicationDbContext.OnConfiguring` (`ApplicationDbContext.cs:316-321`), and registered in the design-time pipeline too so `dotnet ef` matches runtime ([`DesignTimeDbContextHelper`](#designtimedbcontexthelper), `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextHelper.cs:165-167`). @@ -3185,78 +3355,6 @@ survives a module being pulled out into its own service. introduced for `TenantId` (`AuditTrailSaveChangesInterceptor.cs:52-54`). Whether that will be done is Not determinable from source. -### AuditTrailCleanupJob -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.AuditTrail` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailCleanupJob.cs:47` · Level 13 · class (internal sealed partial) - -- **What it is**: the framework's own recurring job. It purges - [`AuditTrailEntry`](#audittrailentry) rows older than the configured retention window from every - relational data source in use, in batches. -- **Depends on**: [`IScheduledJob`](group-05-cqrs-pipeline.md#ischeduledjob) (the contract it - implements), [`IDbContextFactory`](#idbcontextfactory), - [`IEntityDataSourceRegistry`](#ientitydatasourceregistry), - [`AuditTrailSettings`](group-07-persistence-ef-core.md#audittrailsettings) and - [`TenancySettings`](group-07-persistence-ef-core.md#tenancysettings) via `IOptions<>`, - [`ITenantContext`](group-05-cqrs-pipeline.md#itenantcontext), - [`TenantDataSourceTargets`](#tenantdatasourcetargets) plus - [`TenantDataSourceTarget`](#tenantdatasourcetarget), [`DataSource`](#datasource), `TimeProvider`, - `IServiceScopeFactory` and `ILogger`. -- **Concept introduced, retention as a first-class part of a history feature.** `[Rubric §30, - Compliance, Privacy & Data Governance]` (assesses whether retained data has a bounded lifetime), - `[Rubric §31, Cost & FinOps]` (assesses whether unbounded growth is designed out) and `[Rubric §29, - Resilience & Business Continuity]` (a first sweep over a neglected table must not become one - enormous transaction). The class comment makes the argument (`AuditTrailCleanupJob.cs:17-21`): a - change trail grows monotonically and stores values that may describe a data subject, so a retention - window is not housekeeping, it is what keeps the table from being both a cost centre and a - data-protection liability. It also records the honest limitation - (`AuditTrailCleanupJob.cs:22-27`): `AddAuditTrail` registers the job, but a job with no runner never - executes, so the host must **also** call `AddScheduledJobs` and set `Scheduler:Enabled`. A host that - records the trail without the scheduler is fully supported and keeps recording; pruning is then the - operator's job. -- **Walkthrough**: - - **Constructor** (`AuditTrailCleanupJob.cs:47-54`): the last two parameters are optional. The scope - factory and the tenancy options default to `null`, because a host without tenancy never leaves the - job's own scope. `_settings` is snapshotted from `options.Value` (`:60`), and `RetentionDays` - defaults to 90 with a `[Range(1, 3650)]` guard - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSettings.cs:36-37`). - - **`Name` and `CronExpression`** (`AuditTrailCleanupJob.cs:62,66`): `"audit-trail-cleanup"`, daily - at `0 3 * * *`, chosen to sit off the daily peak for every time zone the framework runs in. - - **`ExecuteAsync`** (`AuditTrailCleanupJob.cs:69-82`): returns immediately when - `AuditTrail:Enabled` is false (`:71-74`), computes the cutoff as now minus `RetentionDays` - (`:76`), then sweeps every target - [`TenantDataSourceTargets.ExpandRelational`](#tenantdatasourcetargets) returns (`:78-81`); that - helper is what filters `DataSource.CosmosDB` out of the physical sources in use and expands the - rest into per-tenant targets, one call replacing what used to be inline `Where`/`Distinct` plus a - separate `Expand` call. - - **`PurgeTargetAsync`** (`AuditTrailCleanupJob.cs:89-106`): the shared database is swept on the - job's own scope; a tenant that keeps its own database gets a fresh scope from - [`TenantDataSourceTargets.CreateTenantScope`](#tenantdatasourcetargets) (`:100`), the shared - extension that sets [`ITenantContext`](group-05-cqrs-pipeline.md#itenantcontext) before the - context is asked for, because the scoped context factory binds one database per scope and the - job's scope is already bound to the shared one. - - **`PurgeWithFactoryAsync`** (`AuditTrailCleanupJob.cs:109-130`): resolves the target's context and - re-checks the model for the trail entity (`:125-128`). Cosmos is already excluded, but a - relational source can still lack the table when the host disabled the trail after writing it, so - the model, not the configuration, is the authority. - - **`PurgeSourceAsync`** (`AuditTrailCleanupJob.cs:136-173`): the batching loop. It reads up to - `BatchSize` (1000, `:58`) ids with `AsNoTracking`, ordered by `ChangedOn`, then deletes those ids - with `ExecuteDeleteAsync`. The comment at `:151-153` explains the two-step shape: every relational - provider translates an `IN` list, while a limited `DELETE` is not universally supported. The loop - ends when a page comes back empty (`:162-165`) or short (`:172-175`), and it re-checks the - cancellation token each turn (`:149`). No row is ever materialized as an entity. - - **Logging** (`AuditTrailCleanupJob.cs:175-176`): a source-generated `LoggerMessage` emitted only - when a sweep actually removed rows (`:131-135`), so a quiet night logs nothing. -- **Why it's built this way**: registering the job in `AddAuditTrail` rather than in - `AddScheduledJobs` keeps the two features independent, which the DI comment states outright - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Jobs.cs:121-123`): the trail can - be enabled without the scheduler and the scheduler without the trail. Set-based batched deletion is - the same discipline the outbox retention sweep uses, for the same reason. -- **Where it's used**: registered through `AddScheduledJob()` inside - `AddAuditTrail` (`DependencyInjection.Jobs.cs:124`, and that helper does a `TryAddScoped` of the concrete - type plus a `TryAddEnumerable` of `IScheduledJob` at `:75-76`), and executed by - [`ScheduledJobRunner`](group-14-module-system-composition.md#scheduledjobrunner) when the host runs - the scheduler. Covered by - `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailCleanupJobTests.cs`. - ### AuditTrailReader > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.AuditTrail` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailReader.cs:34` · Level 13 · class (internal sealed) @@ -3290,14 +3388,14 @@ survives a module being pulled out into its own service. environment, so "not enabled" must read as "no history", not as an exception. - **The query** (`AuditTrailReader.cs:60-80`): `AsNoTracking`, filtered on `EntityType` and `EntityKey` (exactly the leading columns of `IX_AuditTrailEntries_Entity`, - `ApplicationDbContext.cs:864-865`), ordered, skipped and taken, then projected column by column + `ApplicationDbContext.cs:867-868`), ordered, skipped and taken, then projected column by column into the DTO (`AuditTrailReader.cs:66-78`). `TenantId` is not projected, because the DTO has no such member: it declares `Id` through `CorrelationId` and stops there (`MMCA.Common/Source/Core/MMCA.Common.Application/Auditing/AuditTrailEntryDTO.cs:12-48`). - **Why it's built this way**: the DTO stops the persistence entity from leaking into the Application contract, and matching the query's predicate and sort to the shipped index is why that index carries the whole predicate plus the sort rather than just the key - (`ApplicationDbContext.cs:862-865`). + (`ApplicationDbContext.cs:865-868`). - **Where it's used**: registered as the scoped [`IAuditTrailReader`](group-05-cqrs-pipeline.md#iaudittrailreader) by `AddAuditTrail` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Jobs.cs:119`). Covered by @@ -3331,52 +3429,59 @@ survives a module being pulled out into its own service. - Having won, it mirrors the claim onto the tracked instance with `presented.Revoke(...)` and then copies current values over original values (`EFRefreshSessionStore.cs:142-143`). That second line is easy to miss and load-bearing: without it, the next `SaveChanges` would re-issue the same `UPDATE` as a tracked modification. Then the successor is added and saved inside the same transaction (`:145-146`). - Sharing one transaction between the claim and the successor insert is what stops a loser observing a half-finished rotation: its `UPDATE` blocks on the winner's row lock, re-evaluates the predicate after the winner commits, and the family revocation it then performs sees the committed successor. A nested call joins the ambient transaction, so an `ITransactional` caller is unaffected (`EFRefreshSessionStore.cs:100-106`). - **Why it's built this way**: every read here is tracked on purpose, and the class doc says why (`EFRefreshSessionStore.cs:24-28`): the caller revokes by mutating the instances this returns, so a no-tracking query would take those revocations and silently drop them at save time. That is the opposite of the usual read-path default in this codebase and worth remembering. Resolving the database through the registry first and the setting second means a single-database host needs no configuration at all (`RefreshSessions:DataSourceName` defaults to `Default` at `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/RefreshSessionSettings.cs:52`) while a multi-database host names the Identity source once. Pointing it at a database that does not map the table fails loudly on the first query rather than reading the wrong rows (`EFRefreshSessionStore.cs:21-22`). The policy is [ADR-097](https://ivanball.github.io/docs/adr/097-multi-device-refresh-sessions.html), building on [ADR-050](https://ivanball.github.io/docs/adr/050-jwt-refresh-token-rotation.html). -- **Where it's used**: registered as the scoped [`IRefreshSessionStore`](group-08-auth.md#irefreshsessionstore) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:166`), scoped deliberately, like the unit of work it shares a `DbContext` with, so a login and its session insert commit together (`:143-144`). The consumer is [`AuthenticationServiceBase`](group-08-auth.md#authenticationservicebasetuser), which holds it as `RefreshSessions` (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:81`, `:88`) and drives rotation, per-session revoke and sign-out-everywhere through it. [`DeleteUserHandlerBase`](group-14-module-system-composition.md#deleteuserhandlerbasetuser-tcommand) revokes a deleted user's sessions through the same store from its soft-delete tail (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:106`). +- **Where it's used**: registered as the scoped [`IRefreshSessionStore`](group-08-auth.md#irefreshsessionstore) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:172`), scoped deliberately, like the unit of work it shares a `DbContext` with, so a login and its session insert commit together (`:143-144`). The consumer is [`AuthenticationServiceBase`](group-08-auth.md#authenticationservicebasetuser), which holds it as `RefreshSessions` (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:81`, `:88`) and drives rotation, per-session revoke and sign-out-everywhere through it. [`DeleteUserHandlerBase`](group-14-module-system-composition.md#deleteuserhandlerbasetuser-tcommand) revokes a deleted user's sessions through the same store from its soft-delete tail (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:110`). ### EFPermissionGrantStore -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/EFPermissionGrantStore.cs:34` · Level 13 · class (internal sealed) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/EFPermissionGrantStore.cs:35` · Level 13 · class (internal sealed) - **What it is**: the EF Core implementation of `IPermissionGrantStore`, the persistence side of - stored permission grants (`EFPermissionGrantStore.cs:34-395`). It lists, grants and revokes rows in + stored permission grants (`EFPermissionGrantStore.cs:35-151`). It lists, grants and revokes rows in the `PermissionGrants` table. - **Depends on**: `IDbContextFactory` (context access and saving), [`IEntityDataSourceRegistry`](#ientitydatasourceregistry) and [`IDataSourceResolver`](#idatasourceresolver) (source resolution), - `IOptions`, and `TimeProvider`, all primary-constructor parameters - (`EFPermissionGrantStore.cs:290-295`). + `IOptions`, `TimeProvider`, and + [`IUniqueConstraintViolationDetector`](#iuniqueconstraintviolationdetector) (classifies the + unique-index failure a concurrent duplicate grant raises), all primary-constructor parameters + (`EFPermissionGrantStore.cs:35-41`). - **Concept introduced, check-then-act made harmless by a unique index, rather than a database-level conditional update.** `[Rubric §11, Security]` (assesses whether a duplicate grant is handled deliberately) and `[Rubric §8, Data Architecture]`. Unlike [`EFRefreshSessionStore`](#efrefreshsessionstore)'s rotation claim, granting a permission is idempotent by nature (a role either has a permission or it does not), so `GrantAsync` uses an ordinary check-then-act: query for an existing row, and only insert when none is found - (`EFPermissionGrantStore.cs:350-358`). The comment names why that race is harmless here - (`:346-349`): the unique index on `(Role, Permission)` is what makes a concurrent duplicate insert - fail at the database rather than corrupt the read, and the row the winner wrote says exactly what - the loser was trying to say. The check exists to make the ordinary duplicate free, not to be the - guarantee. + (`EFPermissionGrantStore.cs:96-117`). The comment names why that race is harmless here + (`:92-95`): the unique index on `(Role, Permission)` makes a concurrent duplicate insert fail at + the database, the loser catches that failure and answers success, because the row the winner wrote + says exactly what the loser was trying to say. The check exists to make the ordinary duplicate + free, not to be the guarantee. - **Walkthrough** - - `Context` (`EFPermissionGrantStore.cs:297`) resolves the context per access through the factory, - keyed on `ResolveDataSourceKey()`; `Grants` (`:299`) is the `DbSet` over it. - - `GetAllAsync` (`:302-308`) and `GetPermissionsAsync(role, ct)` (`:311-324`) are both `AsNoTracking` + - `Context` (`EFPermissionGrantStore.cs:43`) resolves the context per access through the factory, + keyed on `ResolveDataSourceKey()`; `Grants` (`:45`) is the `DbSet` over it. + - `GetAllAsync` (`:48-54`) and `GetPermissionsAsync(role, ct)` (`:57-70`) are both `AsNoTracking` reads, the first ordered by `Role` then `Permission` for the administration listing, the second filtered to one role and projected to bare permission strings for [`PermissionGrantCache`](#permissiongrantcache)'s rebuild. - - `GrantAsync(role, permission, grantedBy, ct)` (`:327-364`) constructs a `PermissionGrant` through - its `Create` factory, returns the factory's failure untouched on invalid input (`:339-342`), then + - `GrantAsync(role, permission, grantedBy, ct)` (`:73-120`) constructs a `PermissionGrant` through + its `Create` factory, returns the factory's failure untouched on invalid input (`:85-88`), then runs the check-then-act described above: an existing row returns success without writing anything - (`:355-358`), otherwise the grant is added and saved (`:360-363`). - - `RevokeAsync(role, permission, ct)` (`:367-384`) is a set-based `ExecuteDeleteAsync` filtered on - both columns (`:378-381`), never a load-then-remove: there is at most one row by the unique index, + (`:101-104`), otherwise the grant is added to the resolved context and saved (`:106-111`). A + `DbUpdateException` that the detector classifies as a unique-constraint violation is caught + (`:112`): the insert lost the race to a concurrent duplicate, so its entry is set to + `EntityState.Detached` (`:116`, comment at `:114-115`) so the scoped context does not retry the + failed insert on its next save, and the method still returns success. Any other database failure + propagates. + - `RevokeAsync(role, permission, ct)` (`:123-140`) is a set-based `ExecuteDeleteAsync` filtered on + both columns (`:134-137`), never a load-then-remove: there is at most one row by the unique index, nothing about it needs to be read, and removing zero rows is exactly the success an idempotent - revoke promises (comment at `:375-377`). - - `ResolveDataSourceKey` (`:389-394`) is the same two-leg routing shape as + revoke promises (comment at `:131-133`). + - `ResolveDataSourceKey` (`:145-150`) is the same two-leg routing shape as [`EFRefreshSessionStore`](#efrefreshsessionstore)'s: the entity registry first (so a consumer entity configuration for `PermissionGrant` routes it like any other entity), otherwise a key built from the resolver's engine for `Default` plus the configured `DataSourceName`. Only the engine goes through the resolver; the configured name is used verbatim, so a host that configures no SQL Server connection string gets the engine it does configure rather than a context over an empty - connection string (comment at `:386-388`). + connection string (comment at `:142-144`). - **Why it's built this way**: the store is intentionally the thinnest possible layer over the model [`PermissionGrantModelBuilderExtensions`](#permissiongrantmodelbuilderextensions) maps: every method is a direct translation of one `IPermissionGrantStore` operation into one query or one set-based @@ -3384,7 +3489,7 @@ survives a module being pulled out into its own service. [`PermissionGrantCache`](#permissiongrantcache) is the layer that owns performance and this one owns correctness against the database. - **Where it's used**: registered as the scoped `IPermissionGrantStore` by `AddStoredPermissionGrants` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:114`). Listed as a + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:118`). Listed as a sanctioned soft-delete exception alongside the other framework bookkeeping stores in both `MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Domain/SoftDeleteEnforcementTests.cs` and `MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/SoftDeleteEnforcementTests.cs`, @@ -3425,18 +3530,94 @@ survives a module being pulled out into its own service. - `ResolveDataSourceKey` (`RefreshSessionCleanupService.cs:140-145`) duplicates [`EFRefreshSessionStore`](#efrefreshsessionstore)'s resolution exactly, and the class doc says why (`:34-39`): the sweep must never visit a different database than the store reads. - The five log messages are source-generated `[LoggerMessage]` partials (`RefreshSessionCleanupService.cs:147-160`), which is why the class is `partial`. - **Why it's built this way**: retention bounds reuse detection, and the class doc is explicit about the trade (`RefreshSessionCleanupService.cs:24-32`). BR-206 catches a replayed refresh token by finding its revoked row and revoking the whole family; a rotation chain older than the window is gone, so a replay of a token that old reads as an unknown token and fails alone instead of signalling reuse. The default window of 30 days (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/RefreshSessionSettings.cs:72`) is far past the default refresh-token lifetime, so every token still capable of being replayed still has its row, and a host that shortens `RefreshSessions:RetentionDays` below `Jwt:RefreshTokenExpirationDays` is choosing to lose that signal. Unlike the outbox, sessions live in exactly one physical source, which is why the sweep resolves one database rather than iterating them (`:33-39`). The sweep is the retention half of the 2026-08-27 revision of [ADR-097](https://ivanball.github.io/docs/adr/097-multi-device-refresh-sessions.html). -- **Where it's used**: registered as a hosted service only when `RefreshSessions:Enabled` is true, read straight off configuration at registration time (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:171-174`). The comment there gives the reason for the conditional (`:151-153`): registering it unconditionally would start a sweep in every service of a modular host, all but one of which has no `RefreshSessions` table to sweep. Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Auth/RefreshSessionCleanupServiceTests.cs`, which exercises both disabled paths, the retention predicate, the unmapped-table warning and the registration gate (`:189`, `:201`, `:218`). +- **Where it's used**: registered as a hosted service only when `RefreshSessions:Enabled` is true, read straight off configuration at registration time (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:181-184`). The comment there gives the reason for the conditional (`:151-153`): registering it unconditionally would start a sweep in every service of a modular host, all but one of which has no `RefreshSessions` table to sweep. Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Auth/RefreshSessionCleanupServiceTests.cs`, which exercises both disabled paths, the retention predicate, the unmapped-table warning and the registration gate (`:189`, `:201`, `:218`). - **Caveats / not-in-source**: the default interval is 6 hours (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/RefreshSessionSettings.cs:80`, constrained to 1 through 168), and because the loop waits one full interval before its first sweep, a host that restarts more often than the configured interval never sweeps. Nothing in the service compensates for that. +### AuditTrailCleanupJob +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.AuditTrail` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailCleanupJob.cs:45` · Level 16 · class (internal sealed partial) + +- **What it is**: the framework's own recurring job. It purges + [`AuditTrailEntry`](#audittrailentry) rows older than the configured retention window from every + relational database that holds a trail table, in batches. +- **Depends on**: [`IScheduledJob`](group-05-cqrs-pipeline.md#ischeduledjob) (the contract it + implements), [`IDbContextFactory`](#idbcontextfactory), + [`FrameworkTableTargets`](#frameworktabletargets) (which databases hold the table), + [`AuditTrailSettings`](group-07-persistence-ef-core.md#audittrailsettings) via `IOptions<>`, + [`TenantDataSourceTargets`](#tenantdatasourcetargets) (for `CreateTenantScope`) plus + [`TenantDataSourceTarget`](#tenantdatasourcetarget), `TimeProvider`, `IServiceScopeFactory` and + `ILogger` (`AuditTrailCleanupJob.cs:45-51`). +- **Concept introduced, retention as a first-class part of a history feature.** `[Rubric §30, + Compliance, Privacy & Data Governance]` (assesses whether retained data has a bounded lifetime), + `[Rubric §31, Cost & FinOps]` (assesses whether unbounded growth is designed out) and `[Rubric §29, + Resilience & Business Continuity]` (a first sweep over a neglected table must not become one + enormous transaction). The class comment makes the argument (`AuditTrailCleanupJob.cs:16-20`): a + change trail grows monotonically and stores values that may describe a data subject, so a retention + window is not housekeeping, it is what keeps the table from being both a cost centre and a + data-protection liability. It also records the honest limitation + (`AuditTrailCleanupJob.cs:21-26`): `AddAuditTrail` registers the job, but a job with no runner never + executes, so the host must **also** call `AddScheduledJobs` and set `Scheduler:Enabled`. A host that + records the trail without the scheduler is fully supported and keeps recording; pruning is then the + operator's job. +- **Walkthrough**: + - **Constructor** (`AuditTrailCleanupJob.cs:45-51`): the last parameter, the scope factory, is + optional and defaults to `null`, because a host without tenancy never leaves the job's own scope. + Tenancy settings are no longer a parameter here: tenant discovery moved into + [`FrameworkTableTargets`](#frameworktabletargets). `_settings` is snapshotted from + `options.Value` (`:56`), and `RetentionDays` defaults to 90 with a `[Range(1, 3650)]` guard + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSettings.cs:36-37`). + - **`Name` and `CronExpression`** (`AuditTrailCleanupJob.cs:59,63`): `"audit-trail-cleanup"`, daily + at `0 3 * * *` (03:00 UTC), chosen to sit off the daily peak for every time zone the framework runs + in (`:62`). + - **`ExecuteAsync`** (`AuditTrailCleanupJob.cs:66-79`): returns immediately when + `AuditTrail:Enabled` is false (`:68-71`), computes the cutoff as now minus `RetentionDays` + (`:73`), then sweeps every target `tableTargets.Relational()` returns (`:75-78`). That call + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/FrameworkTableTargets.cs:43-44`) + takes every relational physical source backing a registered entity, deduplicates it and expands it + per tenant that keeps its own copy, skipping Cosmos; it is the one answer the outbox processor, the + internal-command processor, both cleanup services, both operator surfaces and this sweep share, + rather than each re-deriving it from the registry, the resolver and the tenancy settings + (`FrameworkTableTargets.cs:16-18`). + - **`PurgeTargetAsync`** (`AuditTrailCleanupJob.cs:86-103`): the shared database is swept on the + job's own scope; a tenant that keeps its own database gets a fresh scope from + [`TenantDataSourceTargets.CreateTenantScope`](#tenantdatasourcetargets) (`:97`), the shared + extension that sets [`ITenantContext`](group-05-cqrs-pipeline.md#itenantcontext) before the + context is asked for, because the scoped context factory binds one database per scope and the + job's scope is already bound to the shared one (doc at `:81-85`). + - **`PurgeWithFactoryAsync`** (`AuditTrailCleanupJob.cs:106-127`): resolves the target's context and + re-checks the model for the trail entity (`:116-119`, comment at `:114-115`). Cosmos is already + excluded, but a relational source can still lack the table when the host disabled the trail after + writing it, so the model, not the configuration, is the authority. + - **`PurgeSourceAsync`** (`AuditTrailCleanupJob.cs:133-170`): the batching loop. It reads up to + `BatchSize` (1000, `:54`) ids with `AsNoTracking`, ordered by `ChangedOn`, then deletes those ids + with `ExecuteDeleteAsync`. The comment at `:142-144` explains the two-step shape: every relational + provider translates an `IN` list, while a limited `DELETE` is not universally supported. The loop + ends when a page comes back empty (`:153-156`) or short (`:163-166`), and it re-checks the + cancellation token each turn (`:140`). No row is ever materialized as an entity. + - **Logging** (`AuditTrailCleanupJob.cs:172-173`): a source-generated `LoggerMessage` emitted only + when a sweep actually removed rows (`:122-126`), so a quiet night logs nothing. +- **Why it's built this way**: registering the job in `AddAuditTrail` rather than in + `AddScheduledJobs` keeps the two features independent, which the DI comment states outright + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Jobs.cs:121-123`): the trail can + be enabled without the scheduler and the scheduler without the trail. Set-based batched deletion is + the same discipline the outbox retention sweep uses, for the same reason, and taking its target list + from [`FrameworkTableTargets`](#frameworktabletargets) means this sweep visits exactly the databases + the other framework-table sweeps visit. +- **Where it's used**: registered through `AddScheduledJob()` inside + `AddAuditTrail` (`DependencyInjection.Jobs.cs:124`, and that helper does a `TryAddScoped` of the concrete + type plus a `TryAddEnumerable` of `IScheduledJob` at `:75-76`), and executed by + [`ScheduledJobRunner`](group-14-module-system-composition.md#scheduledjobrunner) when the host runs + the scheduler. Covered by + `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailCleanupJobTests.cs`. + ### ColumnWidth > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Conversions` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conversions/ColumnWidth.cs:4` · Level 0 · class (internal static) - **What it is**: an internal static helper with one method, `Truncate(string? value, int maxLength)`, that cuts a string to a column's maximum length while preserving `null` (`ColumnWidth.cs:4-12`). - **Depends on**: nothing beyond the BCL `string` indexer and range operator. -- **Concept introduced, a defensive width guard for audit and command persistence rather than a value-object mapping.** Both call sites write free-form diagnostic text (a trace ID, a CLR type name, a property key, a joined key string) into fixed-width columns where a value produced at runtime could exceed the column's declared size. Rather than let SQL reject the write or truncate silently at the provider, `Truncate` clips the string before it reaches EF: `value is null || value.Length <= maxLength ? value : value[..maxLength]` (`ColumnWidth.cs:31-32`). A `null` input passes straight through, so an absent value is never coerced into an empty string. +- **Concept introduced, a defensive width guard for audit and command persistence rather than a value-object mapping.** Both call sites write free-form diagnostic text (a trace ID, a CLR type name, a property key, a joined key string) into fixed-width columns where a value produced at runtime could exceed the column's declared size. Rather than let SQL reject the write or truncate silently at the provider, `Truncate` clips the string before it reaches EF: `value is null || value.Length <= maxLength ? value : value[..maxLength]` (`ColumnWidth.cs:10-11`). A `null` input passes straight through, so an absent value is never coerced into an empty string. - **Walkthrough** - - **`Truncate`** (`ColumnWidth.cs:31-32`): a single expression-bodied method; no allocation happens when the value already fits. + - **`Truncate`** (`ColumnWidth.cs:10-11`): a single expression-bodied method; no allocation happens when the value already fits. - **Why it's built this way**: the callers (an audit interceptor and internal command processing) run outside a validated domain type, so nothing upstream already guarantees the string fits its column; `ColumnWidth.Truncate` is the one place that guarantee is enforced before the row is written. - **Where it's used**: `AuditTrailSaveChangesInterceptor` truncates a correlation ID, tenant ID, entity type name, property name, and joined key string through it (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:193-194,244,309,485`); `ScheduledJobRunner` and `InternalCommandProcessor` call it for the same reason at their own persistence boundaries. @@ -3456,7 +3637,7 @@ survives a module being pulled out into its own service. > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Conversions` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conversions/UtcDateTimeConverter.cs:25` · Level 0 · class (internal sealed) -- **What it is**: an internal `ValueConverter` that normalizes every `DateTime` written to PostgreSQL to `DateTimeKind.Utc`, and stamps the same kind back onto values read from the column (`UtcDateTimeConverter.cs:25-73`). +- **What it is**: an internal `ValueConverter` that normalizes every `DateTime` written to PostgreSQL to `DateTimeKind.Utc`, and stamps the same kind back onto values read from the column (`UtcDateTimeConverter.cs:25-40`). - **Depends on**: only the BCL `DateTime`/`DateTimeKind` and EF Core's `ValueConverter` (`UtcDateTimeConverter.cs:1`, implied by the file's imports). - **Concept introduced, a provider-specific kind fixup that is not a value-object mapping.** Unlike the converters above it, this one maps `DateTime` to itself: the model and provider CLR types are identical, and the transformation is purely about the `Kind` flag Npgsql checks before it will accept a `timestamp with time zone` write. `[Rubric §8, Data Architecture]` covers exactly this: the framework's committed answer to "every `DateTime` is mapped `timestamp with time zone`... never the process-wide `Npgsql.EnableLegacyTimestampBehavior` switch" (see `MMCA.Common/CLAUDE.md`, Multi-Database Strategy) is a per-property converter rather than a global legacy-behavior opt-out, so the fix is scoped to PostgreSQL alone and does not alter how SQL Server, SQLite, or Cosmos treat `DateTime`. - **Walkthrough** @@ -3500,12 +3681,12 @@ survives a module being pulled out into its own service. > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Conversions` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conversions/EnumerationValueConverter.cs:33` · Level 4 · class (public sealed) - **What it is**: the shipped EF Core `ValueConverter` that stores a smart-enumeration member as its integer `Value` and rebuilds the member when a row is read back (`EnumerationValueConverter.cs:33`). It is the packaged replacement for the two lambdas every entity configuration would otherwise hand-roll to map an [`Enumeration`](group-02-domain-building-blocks.md#enumerationtenumeration) property. -- **Depends on**: [`Enumeration`](group-02-domain-building-blocks.md#enumerationtenumeration) (the generic constraint, `EnumerationValueConverter.cs:34`), its `Value` property (`MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:104`) and its static `FromValue` factory (`Enumeration.cs:120`); [`Result`](group-01-result-error-handling.md#result) indirectly, because `FromValue` returns one; EF Core's `ValueConverter` from `Microsoft.EntityFrameworkCore.Storage.ValueConversion` (NuGet, `EnumerationValueConverter.cs:1`). +- **Depends on**: [`Enumeration`](group-02-domain-building-blocks.md#enumerationtenumeration) (the generic constraint, `EnumerationValueConverter.cs:34`), its `Value` property (`MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:105`) and its static `FromValue` factory (`Enumeration.cs:121`); [`Result`](group-01-result-error-handling.md#result) indirectly, because `FromValue` returns one; EF Core's `ValueConverter` from `Microsoft.EntityFrameworkCore.Storage.ValueConversion` (NuGet, `EnumerationValueConverter.cs:1`). - **Concept introduced, mapping a smart enumeration onto the column a CLR enum already used.** A smart enumeration is a class, not a language `enum`, so the naive EF answer is `OwnsOne`: the member becomes an owned entity type with its own columns, which turns "replace the `enum` property with a richer type" into a schema change and a migration. `HasConversion` takes the other route. It keeps a single flat column and supplies a pair of expression trees: a **write leg** that turns the model value into the provider value, and a **read leg** that turns the provider value back. Because this converter's provider type is `int` (`EnumerationValueConverter.cs:33`), the backing column stays exactly the plain integer column a CLR enum was already persisted into, so adopting the smart enumeration in the domain changes nothing in the database. The class doc states this as the reason for the choice (`EnumerationValueConverter.cs:6-11`). `[Rubric §4, Domain-Driven Design]` assesses whether the domain models concepts as behavior-carrying types rather than primitives or bare enums; a framework-shipped converter removes the storage-cost argument against doing so. `[Rubric §8, Data Architecture]` assesses how deliberately the storage shape is chosen; the flat `int` column keeps indexes, existing rows, and prior migrations untouched. - **Walkthrough** - **Type parameters and constraint** (`EnumerationValueConverter.cs:33-34`): `ValueConverter` with `where TEnumeration : Enumeration`, the curiously-recurring constraint that makes `FromValue` resolve to the concrete member type rather than to the base. - - **Constructor, write leg** (`EnumerationValueConverter.cs:41`): `member => member.Value`, the member's declared stable integer (`Enumeration.cs:104`). No validation happens here: a member reference is valid by construction, since the only members that exist are the ones the type declares. - - **Constructor, read leg** (`EnumerationValueConverter.cs:42`): `value => Enumeration.FromValue(value).Value!`. `FromValue` looks the value up in the type's interned member dictionary and returns a success result (`Enumeration.cs:122-123`) or an invariant failure coded `Enumeration.UnknownValue` (`Enumeration.cs:125-129`). Because [`Result.Value`](group-01-result-error-handling.md#result) is declared nullable and simply returns `null` on failure rather than throwing (`MMCA.Common/Source/Core/MMCA.Common.Shared/Abstractions/Result.cs:206-207`), the null-forgiving `!` means a row carrying a value no member declares materializes a `null` reference for that property instead of failing materialization. + - **Constructor, write leg** (`EnumerationValueConverter.cs:41`): `member => member.Value`, the member's declared stable integer (`Enumeration.cs:105`). No validation happens here: a member reference is valid by construction, since the only members that exist are the ones the type declares. + - **Constructor, read leg** (`EnumerationValueConverter.cs:42`): `value => Enumeration.FromValue(value).Value!`. `FromValue` looks the value up in the type's interned member dictionary and returns a success result (`Enumeration.cs:123-124`) or an invariant failure coded `Enumeration.UnknownValue` (`Enumeration.cs:126-130`). Because [`Result.Value`](group-01-result-error-handling.md#result) is declared nullable and simply returns `null` on failure rather than throwing (`MMCA.Common/Source/Core/MMCA.Common.Shared/Abstractions/Result.cs:206-207`), the null-forgiving `!` means a row carrying a value no member declares materializes a `null` reference for that property instead of failing materialization. - **The read-leg contract, spelled out** (`EnumerationValueConverter.cs:23-30`): the read leg trusts the column. Every value the write leg can produce round-trips, because the write leg can only persist an already-declared member; the contract can only be broken by a value written outside EF (a manual script, a data fix, or a member deleted from the enumeration after rows were written). - **What it deliberately does not do** (`EnumerationValueConverter.cs:19-21`): requiredness, default value, and precision stay at the call site, because they differ per entity. The documented usage chains `.IsRequired()` next to the `HasConversion` call (`EnumerationValueConverter.cs:13-18`). - **Why it's built this way**: the domain wants a type that can carry names, ordering, and behavior; the database wants the same integer column it already has. `HasConversion` satisfies both, and shipping the converter from the framework means every consumer gets the same interning behavior on read rather than a per-configuration lambda that might allocate a fresh instance. @@ -3592,7 +3773,7 @@ survives a module being pulled out into its own service. - **Concept introduced, fail-fast configuration where the rule spans two sections.** `[Rubric §15, Best Practices & Code Quality]` assesses whether misconfiguration is caught early. `AddOptions().Bind(...).ValidateDataAnnotations().ValidateOnStart()` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:71-74`) is the pattern + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:72-75`) is the pattern every validated settings class uses, and [ADR-070](https://ivanball.github.io/docs/adr/070-fail-fast-configuration-contract.html) makes it the required form: annotations run during host startup rather than lazily on first resolution. @@ -3605,7 +3786,7 @@ survives a module being pulled out into its own service. property on one class, so the real invariant ("the host can reach SOME database") is expressed as an `IValidateOptions` implementation registered alongside the binding, [`ConnectionStringSettingsValidator`](#connectionstringsettingsvalidator) - (`DependencyInjection.cs:81-82`). `[Rubric §8, Data Architecture]`: the rule spans both configuration + (`DependencyInjection.cs:82-83`). `[Rubric §8, Data Architecture]`: the rule spans both configuration shapes precisely because the physical topology is a deployment decision, not a compile-time one. - **Walkthrough**: one static field and seven `{ get; init; }` strings. - `SectionName = "ConnectionStrings"` (`ConnectionStringSettings.cs:15`), reusing ASP.NET Core's own @@ -3637,15 +3818,15 @@ survives a module being pulled out into its own service. (`ConnectionStringSettingsValidator.cs:11-24`). - **Why it's built this way**: `init`-only properties make the bound settings immutable after startup, which [`DataSourceResolver`](#datasourceresolver) relies on, since it classifies every physical source - once in its constructor (`DataSourceResolver.cs:58-76`). Keeping the "some database" check at boot + once in its constructor (`DataSourceResolver.cs:64-77`). Keeping the "some database" check at boot rather than at first query is what stops a host from reporting healthy while unable to serve a request (`ConnectionStringSettingsValidator.cs:20-24`). - **Where it's used**: bound and validated in `AddInfrastructure` ([`DependencyInjection`](group-14-module-system-composition.md#dependencyinjection-1), - `DependencyInjection.cs:71-82`); consumed by [`DataSourceResolver`](#datasourceresolver) through - `IOptions` (`DataSourceResolver.cs:59`, `DataSourceResolver.cs:66`), which - reads it while seeding each engine's `Default` source (`DataSourceResolver.cs:201-236`) and while - applying the Cosmos database-name fallback (`DataSourceResolver.cs:256-257`). + `DependencyInjection.cs:72-83`); consumed by [`DataSourceResolver`](#datasourceresolver) through + `IOptions` (`DataSourceResolver.cs:65`, `DataSourceResolver.cs:72`), which + reads it while seeding each engine's `Default` source (`DataSourceResolver.cs:240-275`) and while + applying the Cosmos database-name fallback (`DataSourceResolver.cs:289-290`). - **Caveats**: the Cosmos database default was renamed from the application-specific `"AtlDevCon"` (the ADC conference database name) to the neutral `"MMCA"` (`ConnectionStringSettings.cs:25`); every default on this class is now framework-neutral. @@ -3675,7 +3856,7 @@ survives a module being pulled out into its own service. - `CosmosConnectionString` (`DataSourceEntrySettings.cs:22`) and `CosmosDatabaseName` (`DataSourceEntrySettings.cs:25`), the Cosmos pair; the database name falls back to the top-level `CosmosDatabaseName` when empty ([`DataSourceResolver`](#datasourceresolver) applies that fallback - at `DataSourceResolver.cs:256-257` and again at `DataSourceResolver.cs:284-286`). + at `DataSourceResolver.cs:289-290` and again at `DataSourceResolver.cs:317-319`). - `PostgreSQLConnectionString` (`DataSourceEntrySettings.cs:28`), the PostgreSQL connection string for this source ([ADR-113](https://ivanball.github.io/docs/adr/113-postgresql-as-a-first-class-engine.html)). - `PostgreSQLMigrationsAssembly` (`DataSourceEntrySettings.cs:35`), documented as falling back to the @@ -3697,7 +3878,7 @@ survives a module being pulled out into its own service. - The resolver reads the relational pair through one engine-keyed switch, `GetMigrationsAssembly` (`DataSourceResolver.cs:424-431`), stamps the SQLite or PostgreSQL value onto the [`PhysicalDataSource`](#physicaldatasource) via its object initializer, only for the matching engine - (`DataSourceResolver.cs:421-423`), and names the offending setting per engine when two logical names + (`DataSourceResolver.cs:454-456`), and names the offending setting per engine when two logical names collapse onto one database with conflicting values (`DataSourceResolver.cs:450-457`). - The XML doc carries a worked `appsettings.json` example for a `Conference` source (`DataSourceEntrySettings.cs:9-18`), which is the fastest way to see the intended shape. @@ -3707,24 +3888,24 @@ survives a module being pulled out into its own service. ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)). - **Where it's used**: bound as the value type of the dictionary that `AddInfrastructure` reads with `configuration.GetSection(DataSourcesSettings.SectionName).Get>()` - (`DependencyInjection.cs:85-87`); consumed by [`DataSourceResolver`](#datasourceresolver) when it - classifies logical names into physical sources (`DataSourceResolver.cs:273-287`) and when it tests - whether any entry names a database for an engine (`DataSourceResolver.cs:135-140`), and by + (`DependencyInjection.cs:86-88`); consumed by [`DataSourceResolver`](#datasourceresolver) when it + classifies logical names into physical sources (`DataSourceResolver.cs:306-320`) and when it tests + whether any entry names a database for an engine (`DataSourceResolver.cs:174-179`), and by [`ConnectionStringSettingsValidator`](#connectionstringsettingsvalidator) when it looks for any configured database (`ConnectionStringSettingsValidator.cs:68-74`). ### DefaultSeed -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:177` · Level 0 · record (sealed, private nested) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:216` · Level 0 · record (sealed, private nested) - **What it is**: a three-field carrier describing what the `Default` physical source of one engine is built from: `ConnectionString`, `MigrationsAssembly`, and `CosmosDatabaseName` - (`DataSourceResolver.cs:177`). It exists only inside [`DataSourceResolver`](#datasourceresolver), + (`DataSourceResolver.cs:216`). It exists only inside [`DataSourceResolver`](#datasourceresolver), where it is computed once per engine and then threaded through the four private methods that build that engine's map. - **Depends on**: nothing first-party. It holds three `string` values, all of which may legitimately be empty. It is produced from [`ConnectionStringSettings`](group-07-persistence-ef-core.md#connectionstringsettings) and [`DataSourcesSettings`](group-07-persistence-ef-core.md#datasourcessettings) by - `ResolveDefaultSeed` (`DataSourceResolver.cs:202-237`). + `ResolveDefaultSeed` (`DataSourceResolver.cs:241-276`). - **Concept introduced, the two-branch answer to "which database is Default".** `[Rubric §8, Data Architecture]` assesses whether the mapping from configuration to physical storage is explicit and has one owner; `[Rubric §15, Best Practices & Code Quality]` assesses whether a rule that several methods depend on @@ -3733,41 +3914,41 @@ survives a module being pulled out into its own service. answer even in a host that never wrote a top-level `ConnectionStrings` entry. The seed is that answer, and computing it into a record rather than passing three loose strings is what lets `ClassifyEntries`, `RegisterDefaultSource`, and `RegisterNamedSource` all agree on it - (`DataSourceResolver.cs:161-168`). + (`DataSourceResolver.cs:200-207`). - **Walkthrough** - - `ConnectionString` (`DataSourceResolver.cs:174`, `DataSourceResolver.cs:177`) is the connection the + - `ConnectionString` (`DataSourceResolver.cs:213`, `DataSourceResolver.cs:216`) is the connection the `Default` source uses, and is empty when the engine is unconfigured. Branch one of `ResolveDefaultSeed` takes it straight from the top-level section when that section names a - connection for the engine (`DataSourceResolver.cs:207-217`). Branch two applies only when the + connection for the engine (`DataSourceResolver.cs:246-256`). Branch two applies only when the top-level value is absent: the named `DataSources` entries are filtered down to the ones carrying a - connection for this engine (`DataSourceResolver.cs:219-221`), their connection identities are - counted distinctly (`DataSourceResolver.cs:223-226`), and when exactly ONE distinct database is - named that database becomes `Default` (`DataSourceResolver.cs:231-235`). With several distinct + connection for this engine (`DataSourceResolver.cs:258-260`), their connection identities are + counted distinctly (`DataSourceResolver.cs:262-265`), and when exactly ONE distinct database is + named that database becomes `Default` (`DataSourceResolver.cs:270-274`). With several distinct databases and no top-level value there is no single answer, so the seed stays empty - (`DataSourceResolver.cs:236`) and a genuinely multi-database host names the shared one by adding a + (`DataSourceResolver.cs:275`) and a genuinely multi-database host names the shared one by adding a `DataSources:Default` entry. - - `MigrationsAssembly` (`DataSourceResolver.cs:175`) is populated only on the top-level branch, and + - `MigrationsAssembly` (`DataSourceResolver.cs:214`) is populated only on the top-level branch, and only for SQL Server: `ConnectionStrings` carries no SQLite equivalent, and handing a SQLite `Default` source the SQL Server value in a mixed-engine host would scaffold the wrong schema - (`DataSourceResolver.cs:207-210`). Branch two deliberately leaves it empty - (`DataSourceResolver.cs:228-230`): every entry it considered has the seed's own connection + (`DataSourceResolver.cs:246-249`). Branch two deliberately leaves it empty + (`DataSourceResolver.cs:267-269`): every entry it considered has the seed's own connection identity, so those entries all collapse onto `Default` and contribute their declared assemblies through `AddExplicitMigrationsAssemblies`, conflicts included. A test pins that the single named entry's assembly still reaches the `Default` source that way (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/DataSourceResolverTests.cs:374`). - - `CosmosDatabaseName` (`DataSourceResolver.cs:176`) is the database name entries fall back to when - they declare none of their own, applied by `CosmosDatabaseNameOf` (`DataSourceResolver.cs:257-258`) - and again inline while classifying entries (`DataSourceResolver.cs:284-286`). + - `CosmosDatabaseName` (`DataSourceResolver.cs:215`) is the database name entries fall back to when + they declare none of their own, applied by `CosmosDatabaseNameOf` (`DataSourceResolver.cs:290-291`) + and again inline while classifying entries (`DataSourceResolver.cs:317-319`). - **Why it's built this way**: branch two cannot change an existing host's routing, because it fires - only where the top-level value is absent (`DataSourceResolver.cs:184-190`). That is the + only where the top-level value is absent (`DataSourceResolver.cs:223-229`). That is the additive-by-construction property the whole data-source layer is built on ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)): a host that declares its database only under `DataSources` gets a working `Default`, and a host that declares it the old way resolves exactly as it always did. - **Where it's used**: only within [`DataSourceResolver`](#datasourceresolver). `BuildEngineMap` - computes it (`DataSourceResolver.cs:161`) and passes it to `ClassifyEntries` - (`DataSourceResolver.cs:162`), `RegisterDefaultSource` (`DataSourceResolver.cs:163`), and - `RegisterNamedSource` (`DataSourceResolver.cs:167`). The two behaviors it decides are pinned by + computes it (`DataSourceResolver.cs:200`) and passes it to `ClassifyEntries` + (`DataSourceResolver.cs:201`), `RegisterDefaultSource` (`DataSourceResolver.cs:202`), and + `RegisterNamedSource` (`DataSourceResolver.cs:206`). The two behaviors it decides are pinned by `DataSourceResolverTests.cs:374` and `DataSourceResolverTests.cs:395`. - **Caveats / not-in-source**: a private nested type. It is inventoried because private nested types are, but nothing outside the resolver can name it, and it never leaves the constructor's call graph. @@ -3787,7 +3968,7 @@ survives a module being pulled out into its own service. `AddInfrastructure` builds the instance by hand from `configuration.GetSection(...).Get>()` and registers it as a singleton (`DataSourcesSettings.cs:8-11`, - `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:83-87`). With no options + `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:84-88`). With no options pipeline there is no `ValidateOnStart`, so the constructor becomes the fail-fast gate. `[Rubric §15, Best Practices & Code Quality]`: rejecting a reserved name at construction is the same guarantee `ValidateOnStart` would have given, implemented where the type can enforce it itself. `[Rubric §8, @@ -3812,11 +3993,11 @@ survives a module being pulled out into its own service. landing in the wrong database ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)). - **Where it's used**: constructed and registered as a singleton in `AddInfrastructure` - (`DependencyInjection.cs:85-87`), immediately before [`DataSourceResolver`](#datasourceresolver) and + (`DependencyInjection.cs:86-88`), immediately before [`DataSourceResolver`](#datasourceresolver) and [`EntityDataSourceRegistry`](#entitydatasourceregistry) (`DependencyInjection.cs:88-89`). It is - consumed by the resolver's constructor (`DataSourceResolver.cs:60`, `DataSourceResolver.cs:68-76`), by - its "is any database configured for this engine" test (`DataSourceResolver.cs:135-140`) and its - classification pass (`DataSourceResolver.cs:273-287`), and by + consumed by the resolver's constructor (`DataSourceResolver.cs:66`, `DataSourceResolver.cs:68-76`), by + its "is any database configured for this engine" test (`DataSourceResolver.cs:174-179`) and its + classification pass (`DataSourceResolver.cs:306-320`), and by [`ConnectionStringSettingsValidator`](#connectionstringsettingsvalidator), which takes it as an optional constructor dependency so that a container binding the settings without `AddInfrastructure` still validates (`ConnectionStringSettingsValidator.cs:26-30`, @@ -3847,7 +4028,7 @@ survives a module being pulled out into its own service. section and leaving the top-level section empty; the annotation failed such a host at startup even though every one of its entities resolved to a configured database. The registration comment in the composition root makes the same point at the call site - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:76-80`). + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:77-81`). `[Rubric §17, DevOps & Deployment]` assesses whether configuration is validated centrally rather than defensively at each read. The check runs once, at boot, under `ValidateOnStart`, so no downstream @@ -3899,8 +4080,8 @@ survives a module being pulled out into its own service. never touches SQL Server, without giving up the boot-time guarantee for the hosts that do. - **Where it's used**: registered by `AddInfrastructure` with `TryAddEnumerable(ServiceDescriptor.Singleton, ConnectionStringSettingsValidator>())` - (`DependencyInjection.cs:81-82`), immediately after the `ValidateOnStart` chain that binds the section - (`DependencyInjection.cs:71-74`); `TryAddEnumerable`, like the tenancy validator, because two modules + (`DependencyInjection.cs:82-83`), immediately after the `ValidateOnStart` chain that binds the section + (`DependencyInjection.cs:72-75`); `TryAddEnumerable`, like the tenancy validator, because two modules calling `AddInfrastructure` must not run the same validation twice. Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Settings/ConnectionStringSettingsValidatorTests.cs`, which drives the validator directly for each engine and for the named-source shapes, and end to end @@ -3932,7 +4113,7 @@ survives a module being pulled out into its own service. `TryGetDataSourceKey` is the non-throwing probe used where a miss is legitimate, for example when [`ApplicationDbContext`](#applicationdbcontext) decides whether an entity belongs in the model it is currently building - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:974-976`). + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:978-980`). - **Why it's built this way**: database-per-service ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)) needs every entity to resolve to exactly one physical source; deriving that map from configuration classes instead of from @@ -3940,13 +4121,13 @@ survives a module being pulled out into its own service. query. - **Where it's used**: implemented by [`EntityDataSourceRegistry`](#entitydatasourceregistry) and registered as a singleton in `AddInfrastructure` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:90`). Consumers include + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:92`). Consumers include [`DbContextFactory`](#dbcontextfactory) - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:48`), + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:50`), [`CrossDataSourceDegradeConvention`](#crossdatasourcedegradeconvention) - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:35`), + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:36`), [`DataSourceService`](#datasourceservice) - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceService.cs:11`), the + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceService.cs:12`), the [`OutboxProcessor`](group-04-events-outbox.md#outboxprocessor) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:61`), the [`OutboxCleanupService`](group-04-events-outbox.md#outboxcleanupservice) @@ -3954,72 +4135,9 @@ survives a module being pulled out into its own service. the [`AuditTrailCleanupJob`](#audittrailcleanupjob) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailCleanupJob.cs:49`), both model-building passes of [`ApplicationDbContext`](#applicationdbcontext) - (`ApplicationDbContext.cs:381`, `ApplicationDbContext.cs:966`), and the startup + (`ApplicationDbContext.cs:386`, `ApplicationDbContext.cs:962`), and the startup database-initialization path - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:53`). - -### PhysicalDataSource -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/PhysicalDataSource.cs:20` · Level 2 · record (sealed) - -- **What it is**: the fully-resolved connection information for one physical database. Four positional - members, `Key` (its engine plus name identity), `ConnectionString`, `MigrationsAssembly?` and - `CosmosDatabaseName` (`PhysicalDataSource.cs:20-24`), plus the computed `UsesMigrations` - (`PhysicalDataSource.cs:41-47`). -- **Depends on**: [`DataSourceKey`](#datasourcekey) and, through it, [`DataSource`](#datasource). -- **Concept, a logical name resolved into a real connection.** `[Rubric §8, Data Architecture]` covers - the step from a configured name like `DataSources:Conference` to an actual database. The record's doc - comment (`PhysicalDataSource.cs:5-9`) explains that it is produced by - [`IDataSourceResolver`](#idatasourceresolver) from the top-level `ConnectionStrings` section (the - `Default` source) plus the named `DataSources` entries. `MigrationsAssembly` is read from the - configuration key of the source's own engine, `SQLServerMigrationsAssembly`, - `SqliteMigrationsAssembly`, or `PostgreSQLMigrationsAssembly`, and is always null for Cosmos, which - migrates nothing (`PhysicalDataSource.cs:12-15`); `CosmosDatabaseName` is ignored for relational - engines (`PhysicalDataSource.cs:19`). A physical source belongs to exactly one engine, so one slot is - enough, and the resolver never hands one engine's assembly to another. Being a positional `record` - buys two things at once here: value equality, so two resolutions of the same source compare equal, - and non-destructive mutation, which is exactly how per-tenant routing is implemented. -- **Walkthrough** - - `Key` is the identity the rest of the stack routes on, and it is deliberately not recomputed when - the connection changes. [`DbContextFactory.ResolveTenantOverride`](#dbcontextfactory) clones the - shared source with `shared with { ConnectionString = ..., CosmosDatabaseName = ... }` and keeps the - original key, because the key is what EF's model cache is keyed on, so swapping only the connection - string is what lets one compiled model serve every tenant's database - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:162-187`, - [ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html)). - - `ConnectionString` may legitimately be empty. Both the startup initializer and `EnsureCreatedAsync` - treat an empty connection string as "this source is not configured in this host" and skip it rather - than failing (`DatabaseInitializationExtensions.cs:74`, `DbContextFactory.cs:221-222`). - - `MigrationsAssembly` is a single positional parameter rather than three engine-scoped record-body - properties, because at most one is ever populated: `BuildPhysicalSource` on - [`DataSourceResolver`](#datasourceresolver) already reads it from the right engine's setting before - constructing the record (`DataSourceResolver.cs:403-407`). - - `UsesMigrations` (`PhysicalDataSource.cs:41-47`) is the switch that decides `Migrate` versus - `EnsureCreated` for this one database. SQL Server always migrates, including the single-database - monolith whose `Default` source names no migrations assembly at all and lets EF look next to the - context (`PhysicalDataSource.cs:43`). PostgreSQL and SQLite share one rule rather than following the - SQL Server one: both migrate only once `MigrationsAssembly` is configured for them - (`PhysicalDataSource.cs:44`), because neither ships with a host that already depends on being - migrated, and a SQLite source wired by hand before that setting existed has no migrations to apply - and must keep being created outright. Cosmos never does: the provider has no migrations pipeline - (`PhysicalDataSource.cs:45`). All branches are pinned by - `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/PhysicalDataSourceTests.cs:21`, - `PhysicalDataSourceTests.cs:33`, `PhysicalDataSourceTests.cs:49`, and - `PhysicalDataSourceTests.cs:90`; PostgreSQL is additionally pinned by - `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.PostgreSQL.Tests/PostgreSQLPersistenceTests.cs`. -- **Where it's used**: produced by [`DataSourceResolver`](#datasourceresolver) through - `BuildPhysicalSource` (`DataSourceResolver.cs:409-423`) for both the Default source - (`DataSourceResolver.cs:330-335`) and named ones (`DataSourceResolver.cs:375-380`), and handed back - by `GetPhysical` (`DataSourceResolver.cs:143-148`); consumed by - [`PhysicalDbContextFactory`](#physicaldbcontextfactory), whose `Create(DataSourceKey)` resolves it - and whose `Create(DataSourceKey, PhysicalDataSource)` overload accepts an already-resolved one, which - is the entry point the tenant clone uses - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/PhysicalDbContextFactory.cs:37-40`). - [`DesignTimeDbContextHelper`](#designtimedbcontexthelper) resolves one for `dotnet ef` commands and - hands the same record to the design-time context - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextHelper.cs:140`), - and the startup initializer branches on both `ConnectionString` and `UsesMigrations` - (`DatabaseInitializationExtensions.cs:74`, `DatabaseInitializationExtensions.cs:79`, - `DatabaseInitializationExtensions.cs:148`). + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:64`). ### Snapshot > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/EntityDataSourceRegistry.cs:25` · Level 2 · record (sealed, private nested) @@ -4095,16 +4213,108 @@ survives a module being pulled out into its own service. context, because the tenant is what routes the scoped factory to that tenant's connection string. Every sweep now gets that branch for free through [`TenantDataSourceTargets.CreateTenantScope`](#tenantdatasourcetargets) - (`OutboxCleanupService.cs:103`, `AuditTrailCleanupJob.cs:100`). + (`OutboxCleanupService.cs:98`, `AuditTrailCleanupJob.cs:97`). - **Where it's used**: produced by [`TenantDataSourceTargets.Expand`](#tenantdatasourcetargets) (via `ExpandRelational`) and consumed as the loop variable of every host-owned sweep: [`OutboxProcessor`](group-04-events-outbox.md#outboxprocessor) - (`OutboxProcessor.cs:144`, iterated at `OutboxProcessor.cs:161`), + (`OutboxProcessor.cs:144`, iterated at `OutboxProcessor.cs:154`), [`OutboxCleanupService`](group-04-events-outbox.md#outboxcleanupservice) (`OutboxCleanupService.cs:197-198`), [`AuditTrailCleanupJob`](#audittrailcleanupjob) - (`AuditTrailCleanupJob.cs:78`, and as a parameter at `AuditTrailCleanupJob.cs:90` and - `AuditTrailCleanupJob.cs:111`), and the startup initializer - (`DatabaseInitializationExtensions.cs:137-138`). + (`AuditTrailCleanupJob.cs:78`, and as a parameter at `AuditTrailCleanupJob.cs:87` and + `AuditTrailCleanupJob.cs:108`), and the startup initializer + (`DatabaseInitializationExtensions.cs:188-189`). + +### PhysicalDataSource +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/PhysicalDataSource.cs:21` · Level 11 · record (sealed) + +- **What it is**: the fully-resolved connection information for one physical database. Four positional + members, `Key` (its engine plus name identity), `ConnectionString`, `MigrationsAssembly?` and + `CosmosDatabaseName` (`PhysicalDataSource.cs:21-25`), plus two computed properties: `UsesMigrations` + (`PhysicalDataSource.cs:42-48`) and `IsMigrationTarget` (`PhysicalDataSource.cs:57-60`). +- **Depends on**: [`DataSourceKey`](#datasourcekey) and, through it, [`DataSource`](#datasource); + [`DataSourceEngines`](#datasourceengines), looked up by `Key.Engine`, whose + [`DataSourceEngineCapabilities`](#datasourceenginecapabilities) supply the + [`MigrationPolicy`](#migrationpolicy) and the `ConnectionStringRequired` flag that both computed + properties read (`PhysicalDataSource.cs:42`, `PhysicalDataSource.cs:59`). +- **Concept, a logical name resolved into a real connection.** `[Rubric §8, Data Architecture]` covers + the step from a configured name like `DataSources:Conference` to an actual database. The record's doc + comment (`PhysicalDataSource.cs:6-10`) explains that it is produced by + [`IDataSourceResolver`](#idatasourceresolver) from the top-level `ConnectionStrings` section (the + `Default` source) plus the named `DataSources` entries. `MigrationsAssembly` is read from the + configuration key of the source's own engine, `SQLServerMigrationsAssembly`, + `SqliteMigrationsAssembly`, or `PostgreSQLMigrationsAssembly`, and is always null for Cosmos, which + migrates nothing (`PhysicalDataSource.cs:13-18`); `CosmosDatabaseName` is ignored for relational + engines (`PhysicalDataSource.cs:20`). A physical source belongs to exactly one engine, so one slot is + enough, and the resolver never hands one engine's assembly to another. Being a positional `record` + buys two things at once here: value equality, so two resolutions of the same source compare equal, + and non-destructive mutation, which is exactly how per-tenant routing is implemented. +- **Walkthrough** + - `Key` is the identity the rest of the stack routes on, and it is deliberately not recomputed when + the connection changes. [`DbContextFactory.ResolveTenantOverride`](#dbcontextfactory) clones the + shared source with `shared with { ConnectionString = ..., CosmosDatabaseName = ... }` and keeps the + original key, because the key is what EF's model cache is keyed on, so swapping only the connection + string is what lets one compiled model serve every tenant's database + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:168-193`, + [ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html)). + - `ConnectionString` may legitimately be empty. Both the startup initializer and `EnsureCreatedAsync` + treat an empty connection string as "this source is not configured in this host" and skip it rather + than failing (`DatabaseInitializationExtensions.cs:88`, `DbContextFactory.cs:246-247`). + - `MigrationsAssembly` is a single positional parameter rather than three engine-scoped record-body + properties, because at most one is ever populated: `BuildPhysicalSource` on + [`DataSourceResolver`](#datasourceresolver) already reads it from the right engine's setting, and + passes null for an engine whose migration policy is `Never` (`DataSourceResolver.cs:441-451`). + - `UsesMigrations` (`PhysicalDataSource.cs:42-48`) is the switch that decides `Migrate` versus + `EnsureCreated` for this one database. It names no engine: it switches on the + [`MigrationPolicy`](#migrationpolicy) that the engine's descriptor declares in its capabilities, + `DataSourceEngines.For(Key.Engine).Capabilities.Migrations` (`PhysicalDataSource.cs:42`). `Always` + migrates unconditionally (`PhysicalDataSource.cs:44`), which is SQL Server, including the + single-database monolith whose `Default` source names no migrations assembly at all and lets EF look + next to the context. `WhenAssemblyConfigured` migrates only once `MigrationsAssembly` is set + (`PhysicalDataSource.cs:45`), which is the rule PostgreSQL and SQLite share: neither ships with a host + that already depends on being migrated, and a SQLite source wired by hand before that setting existed + has no migrations to apply and must keep being created outright. `Never` is Cosmos, whose provider + has no migrations pipeline (`PhysicalDataSource.cs:46`), and an unrecognized policy falls to `false` + (`PhysicalDataSource.cs:47`). The per-engine reasoning is kept in the property's doc comment + (`PhysicalDataSource.cs:27-41`). All branches are pinned by + `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/PhysicalDataSourceTests.cs:21`, + `PhysicalDataSourceTests.cs:33`, `PhysicalDataSourceTests.cs:49`, `PhysicalDataSourceTests.cs:64`, + `PhysicalDataSourceTests.cs:78`, and `PhysicalDataSourceTests.cs:90`. + - `IsMigrationTarget` (`PhysicalDataSource.cs:57-60`) narrows `UsesMigrations` to the sources startup + actually migrates (or, under the `None` strategy, checks): a source that uses migrations and either + belongs to an engine whose capabilities set `ConnectionStringRequired` or has a non-empty connection + string. The doc comment (`PhysicalDataSource.cs:50-56`) names the consequence: only SQL Server stays a + target with an empty connection string, so that misconfiguration fails loudly at startup instead of + being skipped, while an optional SQLite source a host leaves unconfigured stays silently absent. One + rule, shared by the context factory's `GetMigrationTargets` + (`DbContextFactory.cs:782-788`) and the startup pending-migrations check + (`DatabaseInitializationExtensions.cs:312`). +- **Where it's used**: produced by [`DataSourceResolver`](#datasourceresolver) through + `BuildPhysicalSource` (`DataSourceResolver.cs:441-451`) for both the Default source + (`DataSourceResolver.cs:363-368`) and named ones (`DataSourceResolver.cs:408-413`), and handed back + by `GetPhysical` (`DataSourceResolver.cs:182-187`); consumed by + [`PhysicalDbContextFactory`](#physicaldbcontextfactory), whose `Create(DataSourceKey)` resolves it + and whose `Create(DataSourceKey, PhysicalDataSource)` overload accepts an already-resolved one, which + is the entry point the tenant clone uses + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/PhysicalDbContextFactory.cs:25-28`). + [`DesignTimeDbContextHelper`](#designtimedbcontexthelper) resolves one for `dotnet ef` commands and + hands the same record to the design-time context + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextHelper.cs:140`), + and the startup initializer branches on `ConnectionString`, `UsesMigrations` and `IsMigrationTarget` + (`DatabaseInitializationExtensions.cs:88`, `DatabaseInitializationExtensions.cs:93`, + `DatabaseInitializationExtensions.cs:199`, `DatabaseInitializationExtensions.cs:312`). + +### DataSourceService +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceService.cs:12` · Level 12 · class (public sealed) + +- **What it is**: the Application-facing facade over [`IEntityDataSourceRegistry`](#ientitydatasourceregistry). It answers two questions: which physical data source (engine plus database) does this entity live in, and can these two entities be joined with an EF `Include` (`DataSourceService.cs:6-12`). +- **Depends on**: [`IEntityDataSourceRegistry`](#ientitydatasourceregistry) as its single primary-constructor parameter (`DataSourceService.cs:12`), [`IDataSourceService`](#idatasourceservice) as the contract it implements, the [`DataSourceKey`](#datasourcekey) and [`DataSource`](#datasource) types it returns, and [`DataSourceEngines`](#datasourceengines) for the engine capability the include rule reads (`DataSourceService.cs:32`). +- **Concept introduced, routing as a first-class query.** `[Rubric §7, Microservices Readiness]` assesses whether "where does this entity actually live" is answerable at runtime rather than being baked into code, and `[Rubric §3, Clean Architecture]` assesses whether the Application layer can ask that question without referencing EF. Database-per-service ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)) means two entities written in the same module's code may or may not share a physical database depending on how the deployment is configured. Every consumer that needs to make a decision about that (which `DbContext` to open, whether a navigation can be eager-loaded or must go through a populator) asks this service. The class doc records an important property of the current design (`DataSourceService.cs:9-10`): the registry is built eagerly from configuration assemblies, so resolution no longer depends on an EF model having been built first, which is what makes it safe to consult from the Application layer at any point in startup. +- **Walkthrough** + - The four resolution members (`DataSourceService.cs:15-24`) are pure forwarders to the registry, in two pairs: `GetDataSourceKey` by `Type` and by full type name, and `GetDataSource` by the same two, each projecting `.Engine` off the resolved key. The name-based overloads exist because a caller holding only a metadata string (an EF entity type name, a message payload's type name) should not have to resolve a `Type` first. + - `HaveIncludeSupport(first, second)` (`DataSourceService.cs:31-32`) is the whole classification rule in one expression: the two keys must be equal **and** the engine's descriptor must declare itself relational, `DataSourceEngines.For(first.Engine).Capabilities.IsRelational`. The rule names no engine; Cosmos fails it because its capabilities say it is not relational. The remarks at `:27-30` state both halves: EF `Include` requires both entities in the same physical database on a relational engine, and Cosmos has no cross-document include. + - `HaveIncludeSupport(firstEntityFullName, secondEntityFullName)` (`DataSourceService.cs:35-38`) is the name-based overload, and it uses `TryGetDataSourceKey` rather than the throwing form: an entity the registry does not know about yields `false`, which degrades to "use the populator path" rather than to an exception during navigation classification. +- **Why it's built this way**: putting the include decision here rather than inside a repository is what lets [`NavigationMetadataProvider`](group-03-querying-specifications.md#navigationmetadataprovider) classify each navigation once and route it to either an EF `Include` or an `INavigationPopulator` ([ADR-002](https://ivanball.github.io/docs/adr/002-navigation-populators.html)) without knowing anything about deployment topology. Reading the relational flag off the engine descriptor keeps the same code correct on a non-relational store ([ADR-018](https://ivanball.github.io/docs/adr/018-polyglot-persistence.html), [ADR-130](https://ivanball.github.io/docs/adr/130-per-engine-data-source-strategy.html)). It is registered as a singleton (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:60`), which is safe because both the service and the registry behind it are immutable after construction. +- **Where it's used**: [`UnitOfWork`](#unitofwork) resolves an entity's key before choosing a context (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:13`, doc at `:29`); [`NavigationMetadataProvider`](group-03-querying-specifications.md#navigationmetadataprovider) uses it for navigation classification (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/NavigationMetadataProvider.cs:20`); in MMCA.Store, `InventoryAllocationService` (`MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Infrastructure/Services/InventoryAllocationService.cs:35`) and `ProductImageStorageService` (`MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Infrastructure/Services/ProductImageStorageService.cs:28`) both resolve a key before opening the right context. Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/DataSourceServiceTests.cs` and `.../DataSourceServiceAdditionalTests.cs`. ### IDataSourceResolver > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/IDataSourceResolver.cs:15` · Level 3 · interface @@ -4144,11 +4354,11 @@ survives a module being pulled out into its own service. rule, so no caller reimplements the defaulting logic and no caller can disagree about what `Default` means. - **Where it's used**: implemented by [`DataSourceResolver`](#datasourceresolver) and registered as a - singleton (`DependencyInjection.cs:103`); injected into + singleton (`DependencyInjection.cs:109`); injected into [`EntityDataSourceRegistry`](#entitydatasourceregistry) (`EntityDataSourceRegistry.cs:23`) to resolve each entity's derived logical name, into [`PhysicalDbContextFactory`](#physicaldbcontextfactory) and - [`DbContextFactory`](#dbcontextfactory) to open connections (`PhysicalDbContextFactory.cs:18`, - `DbContextFactory.cs:49`), into the inbox store and both event buses so each can locate its own + [`DbContextFactory`](#dbcontextfactory) to open connections (`PhysicalDbContextFactory.cs:21`, + `DbContextFactory.cs:51`), into the inbox store and both event buses so each can locate its own database ([`EfInboxStore`](group-04-events-outbox.md#efinboxstore) `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Inbox/EfInboxStore.cs:40`, [`InProcessEventBus`](group-04-events-outbox.md#inprocesseventbus) @@ -4162,37 +4372,28 @@ survives a module being pulled out into its own service. optionally into [`TenancySettingsValidator`](group-07-persistence-ef-core.md#tenancysettingsvalidator), which uses `ResolveLogical` to check that a tenant override is keyed by a name that actually round-trips as a physical source - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:23`, - `TenancySettingsValidator.cs:119`). - -### DataSourceService - -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceService.cs:11` · Level 3 · class (public sealed) - -- **What it is**: the Application-facing facade over [`IEntityDataSourceRegistry`](#ientitydatasourceregistry). It answers two questions: which physical data source (engine plus database) does this entity live in, and can these two entities be joined with an EF `Include` (`DataSourceService.cs:5-11`). -- **Depends on**: [`IEntityDataSourceRegistry`](#ientitydatasourceregistry) as its single primary-constructor parameter (`DataSourceService.cs:11`), [`IDataSourceService`](#idatasourceservice) as the contract it implements, and the [`DataSourceKey`](#datasourcekey) and [`DataSource`](#datasource) types it returns. -- **Concept introduced, routing as a first-class query.** `[Rubric §7, Microservices Readiness]` assesses whether "where does this entity actually live" is answerable at runtime rather than being baked into code, and `[Rubric §3, Clean Architecture]` assesses whether the Application layer can ask that question without referencing EF. Database-per-service ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)) means two entities written in the same module's code may or may not share a physical database depending on how the deployment is configured. Every consumer that needs to make a decision about that (which `DbContext` to open, whether a navigation can be eager-loaded or must go through a populator) asks this service. The class doc records an important property of the current design (`DataSourceService.cs:8-10`): the registry is built eagerly from configuration assemblies, so resolution no longer depends on an EF model having been built first, which is what makes it safe to consult from the Application layer at any point in startup. -- **Walkthrough** - - The four resolution members (`DataSourceService.cs:14-23`) are pure forwarders to the registry, in two pairs: `GetDataSourceKey` by `Type` and by full type name, and `GetDataSource` by the same two, each projecting `.Engine` off the resolved key. The name-based overloads exist because a caller holding only a metadata string (an EF entity type name, a message payload's type name) should not have to resolve a `Type` first. - - `HaveIncludeSupport(first, second)` (`DataSourceService.cs:30-31`) is the whole classification rule in one expression: the two keys must be equal **and** the engine must not be `DataSource.CosmosDB`. The remarks at `:27-30` state both halves: EF `Include` requires both entities in the same physical database on a relational engine, and Cosmos has no cross-document include. - - `HaveIncludeSupport(firstEntityFullName, secondEntityFullName)` (`DataSourceService.cs:34-37`) is the name-based overload, and it uses `TryGetDataSourceKey` rather than the throwing form: an entity the registry does not know about yields `false`, which degrades to "use the populator path" rather than to an exception during navigation classification. -- **Why it's built this way**: putting the include decision here rather than inside a repository is what lets [`NavigationMetadataProvider`](group-03-querying-specifications.md#navigationmetadataprovider) classify each navigation once and route it to either an EF `Include` or an `INavigationPopulator` ([ADR-002](https://ivanball.github.io/docs/adr/002-navigation-populators.html)) without knowing anything about deployment topology. The engine carve-out keeps the same code correct on a non-relational store ([ADR-018](https://ivanball.github.io/docs/adr/018-polyglot-persistence.html)). It is registered as a singleton (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:59`), which is safe because both the service and the registry behind it are immutable after construction. -- **Where it's used**: [`UnitOfWork`](#unitofwork) resolves an entity's key before choosing a context (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:13`, doc at `:29`); [`NavigationMetadataProvider`](group-03-querying-specifications.md#navigationmetadataprovider) uses it for navigation classification (`MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/NavigationMetadataProvider.cs:20`); in MMCA.Store, `InventoryAllocationService` (`MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Infrastructure/Services/InventoryAllocationService.cs:35`) and `ProductImageStorageService` (`MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Infrastructure/Services/ProductImageStorageService.cs:28`) both resolve a key before opening the right context. Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Services/DataSourceServiceTests.cs` and `.../DataSourceServiceAdditionalTests.cs`. + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:24`, + `TenancySettingsValidator.cs:120`). ### DataSourceResolver -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:15` · Level 4 · class (sealed, partial) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:16` · Level 13 · class (sealed, partial) - **What it is**: the singleton implementation of [`IDataSourceResolver`](#idatasourceresolver). It builds the logical-to-physical map once at construction from the connection-string settings and the named `DataSources` entries, validates migrations-assembly conflicts, works out which engine to substitute for engines the host does not configure, and then serves both lookups from in-memory - dictionaries. + dictionaries. Every engine-specific question it asks (connection string, migrations assembly, setting + name, migration policy, connection identity, substitution priority) is a lookup on the engine's + descriptor through [`DataSourceEngines`](#datasourceengines), so the class itself switches on no + engine. - **Depends on**: [`DataSource`](#datasource), [`DataSourceKey`](#datasourcekey), [`PhysicalDataSource`](#physicaldatasource), [`IDataSourceResolver`](#idatasourceresolver), its own nested [`DefaultSeed`](#defaultseed), [`ConnectionStringSettings`](group-07-persistence-ef-core.md#connectionstringsettings) (taken as `IOptions`), [`DataSourcesSettings`](group-07-persistence-ef-core.md#datasourcessettings) and [`DataSourceEntrySettings`](group-07-persistence-ef-core.md#datasourceentrysettings); + [`DataSourceEngines`](#datasourceengines), [`IDataSourceEngine`](#idatasourceengine) and + [`MigrationPolicy`](#migrationpolicy) for every per-engine answer; `ILogger` and the `[LoggerMessage]` source generator, which is why the class is `partial`. - **Concept introduced, eager and validated data-source resolution.** `[Rubric §8, Data Architecture]` (deliberate multi-database routing) and `[Rubric §7, Microservices Readiness]` @@ -4200,83 +4401,95 @@ survives a module being pulled out into its own service. The resolver realizes the collapse rule described on [`IDataSourceResolver`](#idatasourceresolver). Two guardrails are worth calling out. First, conflicting migrations-assembly declarations on logical names that collapse to the same physical - database throw at construction (`DataSourceResolver.cs:449-462`), a loud fail-fast rather than a + database throw at construction (`DataSourceResolver.cs:476-483`), a loud fail-fast rather than a silent pick. Second, `[Rubric §13, Observability & Operability]` shows up in the two source-generated - log methods: `LogMigrationsAssemblyFallback` (`DataSourceResolver.cs:499-500`) warns when a named SQL - Server source has no dedicated migrations assembly and therefore falls back to another database's, - whose snapshot describes a different schema, and `LogSubstituteEngine` - (`DataSourceResolver.cs:496-497`) states at startup that requests for an unconfigured engine are being + log methods: `LogMigrationsAssemblyFallback` (`DataSourceResolver.cs:508-509`) warns when a named + source on an engine that always migrates has no dedicated migrations assembly and therefore falls + back to another database's, whose snapshot describes a different schema, and `LogSubstituteEngine` + (`DataSourceResolver.cs:505-506`) states at startup that requests for an unconfigured engine are being served from another one. - **Walkthrough** - - State (`DataSourceResolver.cs:17-39`): `AllEngines` is the build order, CosmosDB, PostgreSQL, - Sqlite, SQLServer (`DataSourceResolver.cs:17-18`); `EnginePreference` is the substitution order, - SQL Server, PostgreSQL, SQLite, Cosmos DB, relational first because every table the framework owns - is relational, and PostgreSQL ahead of SQLite because a host that configures a server engine means - it to serve the framework's own tables (`DataSourceResolver.cs:20-30`). The lookups are - `_logicalToPhysical`, keyed by - `(engine, logical name)` (`DataSourceResolver.cs:33`), and `_physicalSources`, keyed by - [`DataSourceKey`](#datasourcekey) (`DataSourceResolver.cs:36`). `_configuredEngines` - (`DataSourceResolver.cs:39`) and `_substituteEngine` (`DataSourceResolver.cs:46`) carry the + - State (`DataSourceResolver.cs:18-52`): `FrameworkDefaultEngine` is `DataSource.SQLServer`, the + engine every framework-owned table asks for by default, documented as a policy constant rather than + an engine capability (`DataSourceResolver.cs:18-23`). `EnginePreference` is the substitution order, + built by ordering `DataSourceEngines.All` by each engine's `SubstitutionPriority`, lowest first + (`DataSourceResolver.cs:35-36`); its doc comment records the intended order, relational first + because every table the framework owns is relational, SQL Server ahead of the others, and PostgreSQL + ahead of SQLite because a host that configures a server engine means it to serve the framework's own + tables (`DataSourceResolver.cs:25-34`). The lookups are `_logicalToPhysical`, keyed by + `(engine, logical name)` (`DataSourceResolver.cs:39`), and `_physicalSources`, keyed by + [`DataSourceKey`](#datasourcekey) (`DataSourceResolver.cs:42`). `_configuredEngines` + (`DataSourceResolver.cs:45`) and `_substituteEngine` (`DataSourceResolver.cs:52`) carry the substitution decision. - - Constructor (`DataSourceResolver.cs:58-90`): null-guards its two settings arguments - (`DataSourceResolver.cs:63-64`), then loops all four engines calling `BuildEngineMap` and recording - which engines carry a connection string anywhere (`DataSourceResolver.cs:68-76`). It picks the - substitute as the first configured engine in preference order, or null when the host configures no - database at all (`DataSourceResolver.cs:78-81`), and logs once when the substitute is not SQL Server - (`DataSourceResolver.cs:83-89`). Every field is written only here, which is what makes the singleton + - Constructor (`DataSourceResolver.cs:64-89`): null-guards its two settings arguments + (`DataSourceResolver.cs:69-70`), then calls `BuildEngineMap` for every engine registered in + `DataSourceEngines.All` (`DataSourceResolver.cs:74-77`). It then computes the configured engines and + the substitute through the two static helpers `ConfiguredEngines` and `PickSubstituteEngine` + (`DataSourceResolver.cs:79-80`), and logs once when the substitute is not `FrameworkDefaultEngine` + (`DataSourceResolver.cs:82-88`). Every field is written only here, which is what makes the singleton safe to share without locking. - - `ResolveLogical` (`DataSourceResolver.cs:93-107`): substitutes the engine first - (`DataSourceResolver.cs:97`), then short-circuits the `Default` name case-insensitively - (`DataSourceResolver.cs:99-102`), then does a dictionary lookup whose miss returns - `DataSourceKey.Default(effectiveEngine)` (`DataSourceResolver.cs:104-106`), which is the monolith + - `ResolveLogical` (`DataSourceResolver.cs:92-106`): substitutes the engine first + (`DataSourceResolver.cs:96`), then short-circuits the `Default` name case-insensitively + (`DataSourceResolver.cs:98-101`), then does a dictionary lookup whose miss returns + `DataSourceKey.Default(effectiveEngine)` (`DataSourceResolver.cs:103-105`), which is the monolith default. - - `SubstituteUnconfiguredEngine` (`DataSourceResolver.cs:128-129`) is one line, and its doc comment - (`DataSourceResolver.cs:109-125`) carries the failure it removes: every engine choice the framework + - `SubstituteUnconfiguredEngine` (`DataSourceResolver.cs:127-128`) is one line, and its doc comment + (`DataSourceResolver.cs:108-124`) carries the failure it removes: every engine choice the framework makes for its own tables comes from a setting defaulting to SQL Server (`Outbox:DataSource`, `Scheduler:DataSource`, `AuditTrail:DataSource`), and honoring that literally in a SQLite-only host handed those components a source with an empty connection string, so their first query failed with - "The ConnectionString property has not been initialized". `HasAnyConnectionString` - (`DataSourceResolver.cs:135-140`) is what decides "configured", and it deliberately looks at named - entries as well as the top-level section. - - `GetPhysical` (`DataSourceResolver.cs:143-148`): a `_physicalSources` lookup that throws with an + "The ConnectionString property has not been initialized". + - `ResolveFrameworkDefaultEngine` (`DataSourceResolver.cs:139-150`) is an `internal static` answer to + the same question without building a resolver: the engine `ResolveLogical(SQLServer, "Default")` + would land on, which is `FrameworkDefaultEngine` when it is configured, otherwise the substitute, and + `FrameworkDefaultEngine` again when nothing is configured (`DataSourceResolver.cs:146-149`). + Registration code reads it to decide which services the default engine can back, raw SQL being + registered only where that engine is relational (`DataSourceResolver.cs:130-135`, + `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:407`). It shares + `ConfiguredEngines` (`DataSourceResolver.cs:153-158`) and `PickSubstituteEngine` + (`DataSourceResolver.cs:164-168`) with the constructor, so the two paths cannot disagree. + `HasAnyConnectionString` (`DataSourceResolver.cs:174-179`) is what decides "configured", and it + deliberately looks at named entries as well as the top-level section. + - `GetPhysical` (`DataSourceResolver.cs:182-187`): a `_physicalSources` lookup that throws with an actionable message when the key was not produced by `ResolveLogical`. - - `BuildEngineMap` (`DataSourceResolver.cs:155-169`): computes the [`DefaultSeed`](#defaultseed) - (`DataSourceResolver.cs:161`), splits the engine's named entries with `ClassifyEntries` - (`DataSourceResolver.cs:162`), then registers the Default source and one named source per group - (`DataSourceResolver.cs:163-168`). - - `ClassifyEntries` (`DataSourceResolver.cs:264-305`): computes a per-connection identity string via - `GetIdentity` (`DataSourceResolver.cs:473-476`), where a Cosmos identity appends the database name - because one account hosts many databases, relational engines use the connection string alone, and - the comparison is ordinal, so semantically-equal-but-textually-different connection strings - deliberately do not collapse. Entries with no connection string for the engine are skipped entirely - (`DataSourceResolver.cs:277-282`), because `ResolveLogical` already defaults on a map miss; entries - matching the seed's identity go to the collapsed list (`DataSourceResolver.cs:289-293`) and the rest - are grouped by identity (`DataSourceResolver.cs:295-302`). - - `RegisterDefaultSource` (`DataSourceResolver.cs:311-341`): registers the `Default` key for the + - `BuildEngineMap` (`DataSourceResolver.cs:194-208`): computes the [`DefaultSeed`](#defaultseed) + (`DataSourceResolver.cs:200`), splits the engine's named entries with `ClassifyEntries` + (`DataSourceResolver.cs:201`), then registers the Default source and one named source per group + (`DataSourceResolver.cs:202-207`). + - `ClassifyEntries` (`DataSourceResolver.cs:297-338`): computes a per-connection identity string via + `GetIdentity` (`DataSourceResolver.cs:494-497`), which appends the database name only for an engine + whose descriptor sets `ConnectionIdentityIncludesDatabaseName` (Cosmos, where one account hosts many + databases) and otherwise uses the connection string alone; the comparison is ordinal, so + semantically-equal-but-textually-different connection strings deliberately do not collapse. Entries + with no connection string for the engine are skipped entirely (`DataSourceResolver.cs:310-315`), + because `ResolveLogical` already defaults on a map miss; entries matching the seed's identity go to + the collapsed list (`DataSourceResolver.cs:322-326`) and the rest are grouped by identity + (`DataSourceResolver.cs:328-335`). + - `RegisterDefaultSource` (`DataSourceResolver.cs:344-374`): registers the `Default` key for the engine, letting entries that collapsed onto it contribute an explicit migrations assembly alongside - the seed's own (`DataSourceResolver.cs:318-328`), and maps each collapsed logical name onto that key - (`DataSourceResolver.cs:337-340`). - - `RegisterNamedSource` (`DataSourceResolver.cs:347-386`): names the physical key after the - alphabetically-first member (`Order(...).First()`, `DataSourceResolver.cs:353`) so routing is - deterministic regardless of configuration key order, then warns and falls back when a SQL Server - source declares no migrations assembly of its own (`DataSourceResolver.cs:360-368`). The group's - Cosmos database name comes from the canonical entry, falling back to the seed + the seed's own (`DataSourceResolver.cs:351-361`), and maps each collapsed logical name onto that key (`DataSourceResolver.cs:370-373`). - - `BuildPhysicalSource` (`DataSourceResolver.cs:409-423`): the migrations assembly was already read + - `RegisterNamedSource` (`DataSourceResolver.cs:380-419`): names the physical key after the + alphabetically-first member (`Order(...).First()`, `DataSourceResolver.cs:386`) so routing is + deterministic regardless of configuration key order, then, for an engine whose migration policy is + `Always` (SQL Server), warns and falls back to the seed's assembly when the source declares none of + its own (`DataSourceResolver.cs:393-401`). The group's Cosmos database name comes from the canonical + entry, falling back to the seed (`DataSourceResolver.cs:403-406`). + - `BuildPhysicalSource` (`DataSourceResolver.cs:441-451`): the migrations assembly was already read from the configuration key of this source's own engine via `GetMigrationsAssembly`, so building the - record is a straight positional construction with the assembly passed straight through, null only - for Cosmos (`DataSourceResolver.cs:414-421`); which is what stops a SQL Server assembly from being - handed to `UseSqlite` or `UseNpgsql`. - - `ResolveMigrationsAssembly` (`DataSourceResolver.cs:437-465`): returns null for Cosmos and when no - explicit value exists (`DataSourceResolver.cs:442-445`), and throws when logical names sharing a - database declare conflicting assemblies, naming the offending setting per engine (via a four-way - switch over `Sqlite`/`PostgreSQL`/`SQLServer`/`CosmosDB`) and every declaration in the message - (`DataSourceResolver.cs:447-461`). `GetMigrationsAssembly` (`DataSourceResolver.cs:424-431`) is the - per-engine reader that feeds it: SQLite, PostgreSQL and SQL Server have their own entry settings, - Cosmos migrates nothing. `TopLevelMigrationsAssembly` (`DataSourceResolver.cs:246-253`), used inside - `ResolveDefaultSeed`, is the same idea at the top-level-section layer: only SQL Server and PostgreSQL - have a declared top-level migrations assembly, so a mixed-engine host can never scaffold one - engine's schema from another's snapshot. + record is a straight positional construction with the assembly passed through, null only for an + engine whose policy is `MigrationPolicy.Never` (`DataSourceResolver.cs:450`); which is what stops a + SQL Server assembly from being handed to `UseSqlite` or `UseNpgsql`. + - `ResolveMigrationsAssembly` (`DataSourceResolver.cs:464-486`): returns null for a `Never` engine and + when no explicit value exists (`DataSourceResolver.cs:469-473`), and throws when logical names + sharing a database declare conflicting assemblies, naming the engine's own + `MigrationsAssemblySettingName` and every declaration in the message (`DataSourceResolver.cs:475-483`). + `GetMigrationsAssembly` (`DataSourceResolver.cs:457-458`), `TopLevelMigrationsAssembly` + (`DataSourceResolver.cs:286-287`) and the two `GetConnectionString` overloads + (`DataSourceResolver.cs:499-503`) are one-line delegations to the engine descriptor. The top-level + one, used inside `ResolveDefaultSeed`, keeps each engine on its own value: only SQL Server and + PostgreSQL declare a top-level migrations assembly, so a mixed-engine host can never scaffold one + engine's schema from another's snapshot (`DataSourceResolver.cs:278-282`). - **Why it's built this way**: resolving eagerly at construction turns a misconfiguration into a startup failure rather than a mid-request surprise, and the deterministic canonical-name rule keeps routing stable across configuration orderings @@ -4284,20 +4497,25 @@ survives a module being pulled out into its own service. comparison is the conservative choice: collapsing two connection strings that only look different would silently merge two databases. Engine substitution is scoped the same conservative way, since it can only fire for a request that could not have been served at all - (`DataSourceResolver.cs:121-124`). + (`DataSourceResolver.cs:120-123`). Pushing every per-engine answer onto the engine descriptor means a + new engine is a new descriptor, not a new arm in each switch here + ([ADR-130](https://ivanball.github.io/docs/adr/130-per-engine-data-source-strategy.html)). - **Where it's used**: registered as the singleton [`IDataSourceResolver`](#idatasourceresolver) - (`DependencyInjection.cs:103`); consumed by [`EntityDataSourceRegistry`](#entitydatasourceregistry), - the context factories, and [`DesignTimeDbContextHelper`](#designtimedbcontexthelper), which constructs - its own instance for `dotnet ef` commands and registers it in a hand-built container + (`DependencyInjection.cs:91`), with its static `ResolveFrameworkDefaultEngine` read during + registration (`DependencyInjection.cs:407`); consumed by + [`EntityDataSourceRegistry`](#entitydatasourceregistry), the context factories, and + [`DesignTimeDbContextHelper`](#designtimedbcontexthelper), which constructs its own instance for + `dotnet ef` commands and registers it in a hand-built container (`DesignTimeDbContextHelper.cs:131-134`, `DesignTimeDbContextHelper.cs:194`). Its rules are pinned by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/DataSourceResolverTests.cs:12`, including the alphabetically-first canonical name (`DataSourceResolverTests.cs:84`), the two Cosmos cases where the same account with a different database stays distinct while the same database collapses (`DataSourceResolverTests.cs:99`, `DataSourceResolverTests.cs:118`), the SQLite-only host being served the framework's SQL Server default from SQLite (`DataSourceResolverTests.cs:335`), the - polyglot host routing each engine to itself (`DataSourceResolverTests.cs:424`), the SQL-Server-only - host being unchanged (`DataSourceResolverTests.cs:413`), and the host with no database at all passing - the engine straight through (`DataSourceResolverTests.cs:479`). + SQL-Server-only host being unchanged (`DataSourceResolverTests.cs:413`), the polyglot host routing + each engine to itself (`DataSourceResolverTests.cs:424`), the host with no database at all passing + the engine straight through (`DataSourceResolverTests.cs:479`), and PostgreSQL being preferred over + SQLite as the substitute (`DataSourceResolverTests.cs:578`). ### EntityDataSourceRegistry > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/EntityDataSourceRegistry.cs:21` · Level 5 · class (sealed) @@ -4378,13 +4596,13 @@ survives a module being pulled out into its own service. [`ApplicationDbContext`](#applicationdbcontext) applies when filtering configurations into a model (`ApplicationDbContext.cs:961-976`). - **Where it's used**: registered as the singleton - [`IEntityDataSourceRegistry`](#ientitydatasourceregistry) (`DependencyInjection.cs:90`) and rebuilt by + [`IEntityDataSourceRegistry`](#ientitydatasourceregistry) (`DependencyInjection.cs:92`) and rebuilt by hand for design-time commands (`DesignTimeDbContextHelper.cs:135`, `DesignTimeDbContextHelper.cs:195`); consumed by [`CrossDataSourceDegradeConvention`](#crossdatasourcedegradeconvention), [`DataSourceService`](#datasourceservice), [`DbContextFactory`](#dbcontextfactory), both - [`ApplicationDbContext`](#applicationdbcontext) model passes (`ApplicationDbContext.cs:381`, - `ApplicationDbContext.cs:966`), and the outbox, audit-trail and migrations enumerations. Its behavior + [`ApplicationDbContext`](#applicationdbcontext) model passes (`ApplicationDbContext.cs:386`, + `ApplicationDbContext.cs:962`), and the outbox, audit-trail and migrations enumerations. Its behavior is pinned by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/EntityDataSourceRegistryTests.cs:15`, covering the agreeing and conflicting duplicate cases (`EntityDataSourceRegistryTests.cs:94`, @@ -4394,25 +4612,21 @@ survives a module being pulled out into its own service. source enumeration (`EntityDataSourceRegistryTests.cs:149`). ### TenantDataSourceTargets -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:42` · Level 5 · class (static) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:42` · Level 14 · class (static) - **What it is**: a static class that expands the physical data sources a background service owns into the [`TenantDataSourceTarget`](#tenantdatasourcetarget) units it must actually visit once - database-per-tenant is configured (`TenantDataSourceTargets.cs:25-51`), plus two convenience layers - built on top of that expansion: an overload pair that derives the source list itself from the entity - registry (`ExpandRelational`, `TenantDataSourceTargets.cs:60-76`, `TenantDataSourceTargets.cs:86-100`) - and a scope-creation helper (`CreateTenantScope`, `TenantDataSourceTargets.cs:110-118`). + database-per-tenant is configured (`Expand`, `TenantDataSourceTargets.cs:51-82`), plus a + scope-creation extension (`CreateTenantScope`, `TenantDataSourceTargets.cs:92-103`). Deciding which + sources to feed it is not its job: that lives on [`FrameworkTableTargets`](#frameworktabletargets). - **Depends on**: [`DataSourceKey`](#datasourcekey), [`TenantDataSourceTarget`](#tenantdatasourcetarget), [`TenancySettings`](group-07-persistence-ef-core.md#tenancysettings) with its nested [`TenantEntrySettings`](group-07-persistence-ef-core.md#tenantentrysettings) and [`TenantDataSourceOverrideSettings`](group-07-persistence-ef-core.md#tenantdatasourceoverridesettings), [`TenancySettingsValidator.ConnectionStringFor`](group-07-persistence-ef-core.md#tenancysettingsvalidator) - for the per-engine connection-string lookup - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:122`), - [`IEntityDataSourceRegistry`](#ientitydatasourceregistry) and [`IDataSourceResolver`](#idatasourceresolver) - for the `ExpandRelational` overloads, and `ITenantContext` (`group-05-cqrs-pipeline.md#itenantcontext`) - for `CreateTenantScope`. + for the per-engine connection-string lookup (`TenantDataSourceTargets.cs:73`), and `ITenantContext` + (`group-05-cqrs-pipeline.md#itenantcontext`) for `CreateTenantScope`. - **Concept introduced, why a per-tenant database is invisible to a shared sweep.** `[Rubric §8, Data Architecture]` and `[Rubric §29, Resilience & Business Continuity]` both apply, and the class remarks (`TenantDataSourceTargets.cs:29-41`) carry the reasoning. A shared-schema tenant needs nothing here: @@ -4444,53 +4658,478 @@ survives a module being pulled out into its own service. for a source this host does not own adds nothing, and an override that only declares, say, a SQLite connection adds nothing for a SQL Server source. Both cases have their own test (`TenantDataSourceTargetTests.cs:64`, `TenantDataSourceTargetTests.cs:78`). - - `ExpandRelational(IEntityDataSourceRegistry, TenancySettings?)` (`TenantDataSourceTargets.cs:60-76`) - is `Expand` composed with `RelationalSourcesInUse` - (`TenantDataSourceTargets.cs:127-128`): every relational physical source backing a registered entity, - Cosmos excluded because it has none of these tables, deduplicated. It is `internal`, recomputed on - every call by its own remark, which is deliberately cheap and tolerant of module assemblies that - finish loading after startup. - - The second `ExpandRelational` overload (`TenantDataSourceTargets.cs:86-100`) adds one more source on - top of that same relational set: the physical source the caller's own settings name as its table's - home, resolved through [`IDataSourceResolver.ResolveLogical`](#idatasourceresolver) - (`TenantDataSourceTargets.cs:95`), unless that configured engine is Cosmos - (`TenantDataSourceTargets.cs:93`). This is what an outbox or internal-command sweep uses: the entity - registry alone would miss the publish or scheduling table when it lives in a source no registered - entity happens to use. - `CreateTenantScope(this IServiceScopeFactory, TenantDataSourceTarget)` - (`TenantDataSourceTargets.cs:110-118`) null-guards the factory, creates a scope, and, when the - target's `TenantId` is set, calls `ITenantContext.SetTenant` on that scope before returning it. The - doc comment is explicit about ordering: call it before asking the scope for a context, because the + (`TenantDataSourceTargets.cs:92-103`) null-guards the factory, creates a scope, and, when the + target's `TenantId` is set, calls `ITenantContext.SetTenant` on that scope before returning it + (`TenantDataSourceTargets.cs:94-102`). The doc comment is explicit about ordering + (`TenantDataSourceTargets.cs:83-87`): call it before asking the scope for a context, because the tenant is what routes the scoped context factory to the tenant's connection string and is also what the query filter reads. - **Why it's built this way**: `Expand` is a pure function over settings, with no DI and no I/O, so every - sweep can share one expansion rule and each can unit-test its own target list without a database - (`TenantDataSourceTargetTests.cs:20`). Keeping the tenant loop outside the sweeps also keeps the - tenancy feature additive: a host with no `Tenancy` section gets byte-identical behavior to the - pre-tenancy code path (`TenantDataSourceTargetTests.cs:26`, `TenantDataSourceTargetTests.cs:36`). The - `ExpandRelational` overloads and `CreateTenantScope` centralize two things every sweep previously had - to get right on its own: which sources to enumerate, and the call-order rule between setting the - tenant and asking for the context. -- **Where it's used**: `Expand` and its `ExpandRelational` overloads back all of the host-owned sweeps: - [`OutboxProcessor`](group-04-events-outbox.md#outboxprocessor) (`OutboxProcessor.cs:213`), - [`OutboxCleanupService`](group-04-events-outbox.md#outboxcleanupservice) - (`OutboxCleanupService.cs:220`), [`AuditTrailCleanupJob`](#audittrailcleanupjob) - (`AuditTrailCleanupJob.cs:83`), + caller shares one expansion rule and each target list can be unit-tested without a database + (`TenantDataSourceTargetTests.cs:47`). Keeping the tenant loop outside the sweeps also keeps the + tenancy feature additive: a host with no `Tenancy` section gets one shared target per source and + nothing else (`TenantDataSourceTargetTests.cs:26`, `TenantDataSourceTargetTests.cs:36`). + `CreateTenantScope` centralizes the call-order rule between setting the tenant and asking for the + context, which every sweep would otherwise have to get right on its own. +- **Where it's used**: `Expand` is called by [`FrameworkTableTargets`](#frameworktabletargets), which + supplies the relational source set for the framework-table sweeps (`FrameworkTableTargets.cs:44`, + `FrameworkTableTargets.cs:63`), and by [`DatabaseInitializationExtensions`](group-12-api-hosting-mapping.md#databaseinitializationextensions), which filters the expansion down to `t.TenantId is not null` because the shared sources were already - initialized in the pass above it (`DatabaseInitializationExtensions.cs:137-138`), - `InternalCommandAdministration`, `InternalCommandCleanupService`, `InternalCommandProcessor`, - `OutboxAdministration`, and `OutboxCleanupService`. `CreateTenantScope` is the scope-creation call each - of those makes per target. Two of the sweeps are pinned against the same expansion in tests - (`TenantDataSourceTargetTests.cs:100`, `TenantDataSourceTargetTests.cs:118`). + initialized in the pass above it (`DatabaseInitializationExtensions.cs:188-189`). `CreateTenantScope` + is the per-target scope call of the startup initializer (`DatabaseInitializationExtensions.cs:191`), + [`OutboxProcessor`](group-04-events-outbox.md#outboxprocessor) (`OutboxProcessor.cs:181`, and per row + at `OutboxProcessor.cs:509`), [`OutboxCleanupService`](group-04-events-outbox.md#outboxcleanupservice) + (`OutboxCleanupService.cs:98`), `OutboxAdministration` (`OutboxAdministration.cs:217`), + [`InternalCommandProcessor`](#internalcommandprocessor) (`InternalCommandProcessor.cs:153`), + [`InternalCommandCleanupService`](#internalcommandcleanupservice) + (`InternalCommandCleanupService.cs:105`), + [`InternalCommandAdministration`](#internalcommandadministration) + (`InternalCommandAdministration.cs:261`) and [`AuditTrailCleanupJob`](#audittrailcleanupjob) + (`AuditTrailCleanupJob.cs:97`). Two of the sweeps are pinned against the same expansion in tests + (`TenantDataSourceTargetTests.cs:100`, `TenantDataSourceTargetTests.cs:116`). - **Caveats / not-in-source**: the expansion is driven purely by declared configuration. A tenant whose database exists but is not declared under `Tenancy:Tenants` produces no target, and nothing in this class detects that; the round-trip check that a source name is a real physical name lives in [`TenancySettingsValidator`](group-07-persistence-ef-core.md#tenancysettingsvalidator), not here. +### FrameworkTableTargets +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/FrameworkTableTargets.cs:32` · Level 15 · class (sealed) + +- **What it is**: a singleton that answers one question for every sweep over the framework's own + relational tables (`OutboxMessages`, `InternalCommands`, the audit trail): which physical databases, + and which per-tenant copies of them, does this host have to visit (`FrameworkTableTargets.cs:8-13`). + Three public members, `Relational()` (`FrameworkTableTargets.cs:43-44`), + `Relational(DataSource, string)` (`FrameworkTableTargets.cs:54-64`) and `Named(DataSource, string, + string?)` (`FrameworkTableTargets.cs:75-82`), all returning `IReadOnlyList`. +- **Depends on**: three primary-constructor parameters (`FrameworkTableTargets.cs:32-35`): + [`IEntityDataSourceRegistry`](#ientitydatasourceregistry) for the physical sources in use, + [`IDataSourceResolver`](#idatasourceresolver) to resolve the caller's configured home source, and an + optional `IOptions` ([`TenancySettings`](group-07-persistence-ef-core.md#tenancysettings)). + It delegates the per-tenant expansion to [`TenantDataSourceTargets.Expand`](#tenantdatasourcetargets) + and reads the relational flag from [`DataSourceEngines`](#datasourceengines). +- **Concept introduced, one answer for "where do the framework's tables live".** `[Rubric §8, Data + Architecture]` and `[Rubric §7, Microservices Readiness]`: with database-per-service + ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)) every relational + source has its own outbox and internal-command table, and with database-per-tenant + ([ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html)) each tenant that keeps + its own copy of a source has another. The class remarks (`FrameworkTableTargets.cs:14-24`) say why + this is one class: the outbox processor, the internal-command processor, both cleanup services, both + operator surfaces and the audit-trail sweep all ask the same question, so the answer is derived here + once rather than from the registry, the resolver and the tenancy settings inside each of them. +- **Walkthrough** + - `RelationalSourcesInUse` (`FrameworkTableTargets.cs:84-86`, private) is the base set: every + physical source the registry reports in use, filtered to engines whose capabilities declare them + relational. Cosmos sources drop out because they host none of these tables + (`FrameworkTableTargets.cs:21-22`). + - `Relational()` (`FrameworkTableTargets.cs:43-44`) is that set, deduplicated, then passed to + `TenantDataSourceTargets.Expand` with the bound tenancy settings, or null when tenancy was never + registered. It is what a sweep uses when its table has no configured home of its own. + - `Relational(homeEngine, homeDatabaseName)` (`FrameworkTableTargets.cs:54-64`) adds one more source: + the physical source the caller's own settings name as its table's home, resolved through + `IDataSourceResolver.ResolveLogical` (`FrameworkTableTargets.cs:60`), but only when that engine is + relational (`FrameworkTableTargets.cs:58`). This is what the outbox and internal-command sweeps use: + the entity registry alone would miss the publish or scheduling table when it lives in a source no + registered entity happens to use. + - `Named(homeEngine, homeDatabaseName, targetName)` (`FrameworkTableTargets.cs:75-82`) is the same + list narrowed to the one target an operator named, matched case-insensitively against + `TenantDataSourceTarget.ToString()`, the display form the operator surfaces report + (`FrameworkTableTargets.cs:79-81`). A null name keeps every target; a name matching nothing returns + an empty list. +- **Why it's built this way**: every caller recomputes the list per call, which the remarks call cheap + and tolerant of module assemblies that finish loading after startup (`FrameworkTableTargets.cs:21-22`), + so the class holds no cached state and is safe as a singleton + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:96`). The tenancy option + is defaulted to null (`FrameworkTableTargets.cs:35`), so a host without tenancy visits the shared + databases only (`FrameworkTableTargets.cs:27-31`). Testing the relational flag on the engine + descriptor rather than naming Cosmos keeps the rule correct for any engine registered in + `DataSourceEngines` + ([ADR-130](https://ivanball.github.io/docs/adr/130-per-engine-data-source-strategy.html)). +- **Where it's used**: injected into [`OutboxProcessor`](group-04-events-outbox.md#outboxprocessor) + (`OutboxProcessor.cs:58`, called at `OutboxProcessor.cs:142`), + [`OutboxCleanupService`](group-04-events-outbox.md#outboxcleanupservice) + (`OutboxCleanupService.cs:47`, called at `OutboxCleanupService.cs:193`), + [`InternalCommandProcessor`](#internalcommandprocessor) (`InternalCommandProcessor.cs:48`, called at + `InternalCommandProcessor.cs:115`), [`InternalCommandCleanupService`](#internalcommandcleanupservice) + (`InternalCommandCleanupService.cs:44`, called at `InternalCommandCleanupService.cs:161`), and + [`AuditTrailCleanupJob`](#audittrailcleanupjob) (`AuditTrailCleanupJob.cs:47`, which uses the + home-less `Relational()` at `AuditTrailCleanupJob.cs:75`). The two operator surfaces use `Named`: + `OutboxAdministration` (`OutboxAdministration.cs:36`, called at `OutboxAdministration.cs:203`) and + [`InternalCommandAdministration`](#internalcommandadministration) + (`InternalCommandAdministration.cs:38`, called at `InternalCommandAdministration.cs:247`). All paths + are under `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/`. Tests construct it in + `TenantDataSourceTargetTests.cs`, `OutboxProcessorTests.cs`, `OutboxProcessorExecuteAsyncTests.cs`, + `OutboxCleanupServiceTests.cs` and `AuditTrailCleanupJobTests.cs`. + +### ExplicitKeyInsertGroup +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/ExplicitKeyInsertGroup.cs:12` · Level 0 · record (internal sealed) + +- **What it is**: a three-member positional record, `(string Schema, string Table, IReadOnlyList Entries)` + (`ExplicitKeyInsertGroup.cs:12`). It names one batch of pending inserts that all target the same + table and all carry an explicit value for a store-generated key, which is exactly the unit an engine's + explicit-key toggle operates on (`ExplicitKeyInsertGroup.cs:5-8`). +- **Depends on**: EF Core's `EntityEntry` (`ExplicitKeyInsertGroup.cs:1`), and nothing first-party. +- **Concept introduced, importing rows that already have ids.** `[Rubric §8, Data Architecture]` + (assesses whether persistence mechanics are deliberate): a store-generated key normally means the + application never supplies the id, but an import from an external system must preserve the source's + ids. SQL Server only accepts that with `SET IDENTITY_INSERT ON`, one table at a time per + session (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:323-328`). + Grouping the affected entries by table is what turns that constraint into a loop. +- **Walkthrough**: no members beyond the positional ones. `Schema` and `Table` come from EF model + metadata, never user input, which is what makes the raw toggle statement safe to build by + concatenation (see [`IExplicitKeyInsertDialect`](#iexplicitkeyinsertdialect)). +- **Why it's built this way**: the record is engine-neutral and lives beside the engine strategies + rather than inside [`DbContextFactory`](#dbcontextfactory), because the engine produces the groups + ([`SQLServerDataSourceEngine`](#sqlserverdatasourceengine)`.FindGroups`) and the factory consumes them + ([ADR-130](https://ivanball.github.io/docs/adr/130-per-engine-data-source-strategy.html)). +- **Where it's used**: returned by [`IExplicitKeyInsertDialect`](#iexplicitkeyinsertdialect)`.FindGroups` + (`IExplicitKeyInsertDialect.cs:21`), built by the SQL Server engine + (`SQLServerDataSourceEngine.cs:158`), and consumed by `DbContextFactory.SaveExplicitKeyGroupsAsync` + (`DbContextFactory.cs:377-427`), which saves each group in its own round with the toggle on, hiding + the other groups' `Added` entries as `Unchanged` (`DbContextFactory.cs:390-396`) and excluding their + domain events from capture through + [`DomainEventSaveChangesInterceptor`](#domaineventsavechangesinterceptor)`.BeginCaptureExclusion` + (`DbContextFactory.cs:398-406`). + +### MigrationPolicy +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/MigrationPolicy.cs:8` · Level 0 · enum (internal) + +- **What it is**: how an engine decides whether one physical source is migrated (`Migrate`) or created + outright (`EnsureCreated`) at startup (`MigrationPolicy.cs:3-7`). +- **Depends on**: nothing first-party. +- **Concept**: one of the plain-value capabilities that replaced engine branching; the idea is taught on + [`DataSourceEngineCapabilities`](#datasourceenginecapabilities). `[Rubric §8, Data Architecture]` + applies: schema evolution policy is a per-engine fact, not a per-call-site decision. +- **Walkthrough**: three members. `Never` (`MigrationPolicy.cs:11`): no migrations pipeline (Cosmos). + `WhenAssemblyConfigured` (`:14`): migrated only once a migrations assembly is configured for the source + (SQLite, PostgreSQL). `Always` (`:20`): always migrated, even with no migrations assembly, and a named + source with none falls back to the Default source's assembly with a warning (SQL Server, + `MigrationPolicy.cs:16-19`). +- **Why it's built this way**: three values, not a boolean, because SQL Server's fallback behavior is + distinct from "migrate when configured". +- **Where it's used**: [`PhysicalDataSource`](#physicaldatasource)`.UsesMigrations` switches on it + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/PhysicalDataSource.cs:42-46`); + [`DataSourceResolver`](#datasourceresolver) reads `Always` for the Default-assembly fallback and + `Never` to drop migrations settings + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:393,450,470`). + Each engine declares its value in its `Capabilities` (for example `CosmosDataSourceEngine.cs:42`). + +### RowVersionStrategy +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/RowVersionStrategy.cs:8` · Level 0 · enum (internal) + +- **What it is**: how an engine maps and maintains the `RowVersion` optimistic-concurrency token + (`RowVersionStrategy.cs:3-7`). +- **Depends on**: nothing first-party. +- **Concept**: a capability value (see [`DataSourceEngineCapabilities`](#datasourceenginecapabilities)). + `[Rubric §8, Data Architecture]` applies: optimistic concurrency is uniform for callers while the + token's mechanics differ per engine. +- **Walkthrough**: three members. `None` (`RowVersionStrategy.cs:11`): no token configured (Cosmos). + `StoreGenerated` (`:14`): a server-generated `rowversion` mapped with `IsRowVersion()` and never + stamped (SQL Server). `ClientStamped` (`:17`): a plain concurrency token the audit interceptor stamps + on every insert and update (PostgreSQL, SQLite). +- **Why it's built this way**: only SQL Server has a native row-version column type, so the other + relational engines need the application to write the token; naming the strategy keeps that split in + one place instead of an engine comparison in two files. +- **Where it's used**: mapping in [`ApplicationDbContext`](#applicationdbcontext) + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:591`) + and stamping in [`AuditSaveChangesInterceptor`](#auditsavechangesinterceptor) + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:59`). + +### DataSourceEngineCapabilities +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/DataSourceEngineCapabilities.cs:23` · Level 1 · record (internal sealed) + +- **What it is**: what one engine can and cannot do, as plain values a call site reads instead of naming + an engine (`DataSourceEngineCapabilities.cs:3-8`). Five positional members + (`DataSourceEngineCapabilities.cs:23-28`). +- **Depends on**: [`MigrationPolicy`](#migrationpolicy), [`RowVersionStrategy`](#rowversionstrategy). +- **Concept introduced, capabilities instead of engine checks.** `[Rubric §1, SOLID]` (assesses + whether code is open for extension without edits scattered across consumers): a call site that asks + "is this engine relational?" keeps working when a fifth engine is added, while one that asks "is this + SQL Server?" must be found and edited. `[Rubric §8, Data Architecture]` applies too: engine + differences are recorded as data. The doc comment states the admission rule: only facts whose values + genuinely differ between engines get their own member, and everything that splits exactly along the + relational / non-relational line collapses into `IsRelational` (`DataSourceEngineCapabilities.cs:5-7`). +- **Walkthrough**: + - `Migrations` (`:24`): a [`MigrationPolicy`](#migrationpolicy). + - `ConnectionStringRequired` (`:25`): a source with no connection string is a startup + misconfiguration rather than an optional source to skip (SQL Server only, `:10-13`). + - `IsRelational` (`:26`): the bundle of relational-only features: `.Include()` within one physical + source, transactions, raw parameterized SQL, indexes, foreign keys with meaningful delete behavior, + `Any(predicate)` translation, and the framework's own relational tables (outbox, inbox, internal + commands, scheduled jobs) (`:14-19`). + - `NullsSortFirstAscending` (`:27`): keyset pagination depends on it (`:20`). + - `RowVersion` (`:28`): a [`RowVersionStrategy`](#rowversionstrategy). +- **Why it's built this way**: a fitness test forbids concrete engine branching outside the engine + strategies (`MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/DataSourceBranchingFitnessTests.cs:19,71`), + so a capability value is the sanctioned way to differ + ([ADR-130](https://ivanball.github.io/docs/adr/130-per-engine-data-source-strategy.html)). +- **Where it's used**: exposed as [`IDataSourceEngine`](#idatasourceengine)`.Capabilities` + (`IDataSourceEngine.cs:74`). `IsRelational` is read by the conventions + ([`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) at + `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:43`, + [`RestrictDeleteByDefaultConvention`](#restrictdeletebydefaultconvention) at + `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/RestrictDeleteByDefaultConvention.cs:66`, + [`CrossDataSourceDegradeConvention`](#crossdatasourcedegradeconvention) at + `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:66`), + by [`DataSourceService`](#datasourceservice) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceService.cs:32`) + and by [`DomainEventSaveChangesInterceptor`](#domaineventsavechangesinterceptor) + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/DomainEventSaveChangesInterceptor.cs:127,236`). + `ConnectionStringRequired` is read by [`PhysicalDataSource`](#physicaldatasource) + (`PhysicalDataSource.cs:59`), and `NullsSortFirstAscending` by + [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype), which defaults to + `true` when no engine is known + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:523,721`). + +### IExplicitKeyInsertDialect +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/IExplicitKeyInsertDialect.cs:13` · Level 1 · interface (internal) + +- **What it is**: the engine-specific half of an explicit-key insert. Only an engine that refuses an + explicit value for a store-generated key without a session toggle has one (SQL Server, through + `SET IDENTITY_INSERT`); every other engine returns `null` from + [`IDataSourceEngine`](#idatasourceengine)`.ExplicitKeyInsert` and the save runs unchanged + (`IExplicitKeyInsertDialect.cs:5-12`). +- **Depends on**: [`ExplicitKeyInsertGroup`](#explicitkeyinsertgroup); EF Core's `DbContext`. +- **Concept**: the strategy split taught on [`IDataSourceEngine`](#idatasourceengine) applied to one + behavior. The doc comment draws the line explicitly: the engine-neutral half (grouping rounds, hiding + other tables' entries, excluding their domain events, pinning the connection) stays in + [`DbContextFactory`](#dbcontextfactory) (`IExplicitKeyInsertDialect.cs:10-11`). +- **Walkthrough**: + - `FindGroups(DbContext)` (`IExplicitKeyInsertDialect.cs:21`): returns the added entries whose + single-column key is store-generated on this engine and carries an explicit (non-temporary) value, + one [`ExplicitKeyInsertGroup`](#explicitkeyinsertgroup) per table; empty when nothing needs the toggle. + - `BuildToggleSql(schema, table, enable)` (`:28`): the raw statement that switches explicit-key + insert on or off for one table. The parameters are documented as coming from EF model metadata, + never user input (`:24-25`). +- **Why it's built this way**: a nullable dialect property lets the factory express "this engine needs + no toggle" without an engine check + ([ADR-130](https://ivanball.github.io/docs/adr/130-per-engine-data-source-strategy.html)). The + toggle is session state, so the factory pins one connection for the whole save + (`DbContextFactory.cs:342-352`). +- **Where it's used**: implemented by [`SQLServerDataSourceEngine`](#sqlserverdatasourceengine), which + returns itself (`SQLServerDataSourceEngine.cs:19,49`). `DbContextFactory` takes the explicit-key path + only when a caller requested it and the context's engine has a dialect (`DbContextFactory.cs:292-294`), + then drives `FindGroups` and `BuildToggleSql` (`DbContextFactory.cs:334,414-427`). The request flag + is set through [`IUnitOfWork`](#iunitofwork)`.RequestExplicitKeyInsert` + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:73`); the one + first-party caller is ADC's Sessionize refresh + (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeHandler.cs:192`). + +### CosmosDataSourceEngine +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/CosmosDataSourceEngine.cs:18` · Level 11 · class (internal sealed) + +- **What it is**: the [`IDataSourceEngine`](#idatasourceengine) strategy for Azure Cosmos DB, the one + non-relational engine. +- **Depends on**: [`CosmosDbContext`](#cosmosdbcontext), + [`IEntityTypeConfigurationCosmos`](#ientitytypeconfigurationcosmostentity-tidentifiertype), + [`CosmosIntIdValueGenerator`](#cosmosintidvaluegenerator), [`NamespaceConventions`](#namespaceconventions), + [`ConnectionStringSettings`](#connectionstringsettings), [`DataSourceEntrySettings`](#datasourceentrysettings), + [`TenantDataSourceOverrideSettings`](#tenantdatasourceoverridesettings); EF Core Cosmos provider. +- **Concept**: see [`IDataSourceEngine`](#idatasourceengine). +- **Walkthrough**: + - Descriptive facts: `Engine` is `DataSource.CosmosDB` (`CosmosDataSourceEngine.cs:25`), + `SubstitutionPriority` 3, the last resort (`:28`), and `MigrationsAssemblySettingName` is `null` + because Cosmos migrates nothing (`:33-35`). `ConnectionIdentityIncludesDatabaseName` is `true` + (`:38`): one Cosmos account hosts many databases, so the connection string alone does not identify + the physical source. + - `Capabilities` (`:41-46`): `MigrationPolicy.Never`, connection string not required, not relational, + nulls sort first ascending, `RowVersionStrategy.None`. `ExplicitKeyInsert` is `null` (`:49`). + - Migrations-assembly readers return `string.Empty` (`:73,76`); `CreateDbContext` builds a + `CosmosDbContext` over static empty options, since all configuration happens in `OnConfiguring` + (`:20-22,79-83`). + - `ApplyKeyAndTableMapping` (`:86-102`): all of a module's entities share one container named after + the module (falling back to the entity name) with the entity `Id` as partition key (`:92-96`); a + generated int id uses `CosmosIntIdValueGenerator`, otherwise `ValueGeneratedNever` (`:98-101`). + - `IncludeColumns` keeps the SQL Server annotation and is unreachable in practice, since Cosmos never + maps the outbox or internal-command tables that call it (`:105-110`); `QuoteColumn` uses brackets + (`:114`) only for a filter that `BuildSoftDeleteFilter` then discards by returning `null` (`:117`). +- **Why it's built this way**: the Cosmos-specific mapping (container, partition key, client-side id + generation) that used to be engine branches now sits in one class + ([ADR-130](https://ivanball.github.io/docs/adr/130-per-engine-data-source-strategy.html)). +- **Where it's used**: registered in [`DataSourceEngines`](#datasourceengines) + (`DataSourceEngines.cs:24`), and allow-listed as an engine strategy by + `DataSourceBranchingFitnessTests.cs:27`. + +### DataSourceEngines +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/DataSourceEngines.cs:19` · Level 11 · class (internal static) + +- **What it is**: the one registry of database engines, keyed by the shipped [`DataSource`](#datasource) + value (`DataSourceEngines.cs:6`). +- **Depends on**: [`IDataSourceEngine`](#idatasourceengine) and its four implementations; BCL + `FrozenDictionary`. +- **Concept**: the lookup half of the strategy pattern taught on [`IDataSourceEngine`](#idatasourceengine). +- **Walkthrough**: `Registered` (`DataSourceEngines.cs:22-28`) is the array of the four engines in + registration order (Cosmos, PostgreSQL, SQLite, SQL Server). `ByEngine` (`:30-31`) freezes it into a + dictionary keyed by each engine's `Engine`. `All` (`:34`) exposes the array; `For(DataSource)` + (`:40-43`) returns the registered engine or throws `InvalidOperationException` naming the unimplemented + value. +- **Why it's built this way**: static rather than a DI service because several consumers run where no + container is reachable (model-building conventions, `EntityTypeConfiguration.ApplyEngineConventions`, + [`SoftDeleteFilterSql`](#softdeletefiltersql), [`IndexBuilderExtensions`](#indexbuilderextensions)), + and because engines are stateless facts about a closed enum that only Common can extend + (`DataSourceEngines.cs:8-12`). Adding a fifth engine is one new class plus one line in `Registered` + (`:14-16`). +- **Where it's used**: substitution order is `All` sorted by `SubstitutionPriority` in + [`DataSourceResolver`](#datasourceresolver) (`DataSourceResolver.cs:36`) and + [`TenancySettingsValidator`](#tenancysettingsvalidator) + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:29`); + `For` backs [`ApplicationDbContext`](#applicationdbcontext)`.Engine` (`ApplicationDbContext.cs:61`), + [`PhysicalDbContextFactory`](#physicaldbcontextfactory) + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/PhysicalDbContextFactory.cs:32`), + [`EntityTypeConfiguration`](#entitytypeconfigurationtentity-tidentifiertype) + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:77`), + [`SoftDeleteFilterSql`](#softdeletefiltersql) + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:35,70`), + [`FrameworkTableTargets`](#frameworktabletargets) + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/FrameworkTableTargets.cs:58,86`), + and infrastructure registration (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:409`). + +### IDataSourceEngine +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/IDataSourceEngine.cs:18` · Level 11 · interface (internal) + +- **What it is**: everything the framework needs to know about one database engine, so that no call + site branches on [`DataSource`](#datasource). Implementations are stateless singletons looked up + through [`DataSourceEngines`](#datasourceengines)`.For` (`IDataSourceEngine.cs:9-12`). +- **Depends on**: [`DataSource`](#datasource), [`DataSourceEngineCapabilities`](#datasourceenginecapabilities), + [`IExplicitKeyInsertDialect`](#iexplicitkeyinsertdialect), [`ApplicationDbContext`](#applicationdbcontext), + [`PhysicalDataSource`](#physicaldatasource), + [`IEntityConfigurationAssemblyProvider`](#ientityconfigurationassemblyprovider), the three settings + types ([`ConnectionStringSettings`](#connectionstringsettings), + [`DataSourceEntrySettings`](#datasourceentrysettings), + [`TenantDataSourceOverrideSettings`](#tenantdatasourceoverridesettings)); EF Core `EntityTypeBuilder` + and `IndexBuilder`. +- **Concept introduced, one strategy per engine.** `[Rubric §2, Design Patterns]` (assesses whether + patterns solve a real problem idiomatically): a Strategy keyed by an enum replaces `switch` and `if` + statements on the engine that were spread across resolver, conventions, configuration and factory + code. `[Rubric §1, SOLID]` (open/closed): a fifth engine is one class, not edits at every branch. + `[Rubric §34, Architecture Governance & Documentation]` (assesses whether architectural rules are + enforced, not just written down): `DataSourceBranchingFitnessTests` fails the build on concrete engine + branching outside the engine strategies, with the remedy "route engine-specific behavior through + IDataSourceEngine (ADR-130)" (`DataSourceBranchingFitnessTests.cs:19,71`). The interface groups its + members in three bands: DESCRIPTIVE facts, `Capabilities` (plain values) and BEHAVIOUR hooks, the few + places where the engine has to act rather than answer (`IDataSourceEngine.cs:13-15`). +- **Walkthrough**: + - **Identity**: `Engine` (`IDataSourceEngine.cs:21`), the enum value the engine answers for. + - **Descriptive** (`:23-69`): `SubstitutionPriority` (`:29`, lower wins when the resolver substitutes + a configured engine for an unconfigured one; it replaces the hand-ordered `EnginePreference` array, + `:25-28`), `EntityConfigurationInterface` (`:32`, the open generic configuration interface whose + implementations belong to this engine's model), `MigrationsAssemblySettingName` (`:38`, for error + messages, `null` when the engine migrates nothing), `ConnectionIdentityIncludesDatabaseName` + (`:44`), three `GetConnectionString` overloads for the top-level section, a named `DataSources` + entry and a tenant override (`:49,54,59`), and the two migrations-assembly readers (`:64,69`). + - **Capabilities** (`:74`): a [`DataSourceEngineCapabilities`](#datasourceenginecapabilities). + - **Behaviour** (`:76-119`): `CreateDbContext` builds the engine's sealed context over one physical + source, never pooled (`:83-86`); `ApplyKeyAndTableMapping` is the body of + `EntityTypeConfiguration.ApplyEngineConventions` (`:95-97`); `IncludeColumns` adds covering columns + with the engine's provider annotation (`:103`); `QuoteColumn` and `BuildSoftDeleteFilter` produce the + filtered-index predicate, the latter `null` where filtered indexes are unsupported (`:108,113`); + `ExplicitKeyInsert` is the optional [`IExplicitKeyInsertDialect`](#iexplicitkeyinsertdialect) (`:119`). +- **Why it's built this way**: recorded in + [ADR-130](https://ivanball.github.io/docs/adr/130-per-engine-data-source-strategy.html). It does not + replace the one-context-class-per-engine rule (ADR-006): each strategy creates its own sealed context. +- **Where it's used**: four implementations + ([`CosmosDataSourceEngine`](#cosmosdatasourceengine), [`PostgreSQLDataSourceEngine`](#postgresqldatasourceengine), + [`SqliteDataSourceEngine`](#sqlitedatasourceengine), [`SQLServerDataSourceEngine`](#sqlserverdatasourceengine)), + all held by [`DataSourceEngines`](#datasourceengines); see that section for the consumers. + [`ApplicationDbContext`](#applicationdbcontext) exposes the current context's engine as an + `internal` `Engine` property (`ApplicationDbContext.cs:61`), which interceptors and the factory read. + +### PostgreSQLDataSourceEngine +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/PostgreSQLDataSourceEngine.cs:17` · Level 11 · class (internal sealed) + +- **What it is**: the [`IDataSourceEngine`](#idatasourceengine) strategy for PostgreSQL (Npgsql). +- **Depends on**: [`PostgreSQLDbContext`](#postgresqldbcontext), + [`IEntityTypeConfigurationPostgreSQL`](#ientitytypeconfigurationpostgresqltentity-tidentifiertype), + [`NamespaceConventions`](#namespaceconventions), the settings types; Npgsql EF provider. +- **Concept**: see [`IDataSourceEngine`](#idatasourceengine). +- **Walkthrough**: + - Descriptive facts: `Engine` is `DataSource.PostgreSQL` (`PostgreSQLDataSourceEngine.cs:24`), + `SubstitutionPriority` 1 (`:27`), settings key `PostgreSQLMigrationsAssembly` (`:33`), connection + identity by connection string alone (`:36`). + - `Capabilities` (`:39-44`): `MigrationPolicy.WhenAssemblyConfigured`, connection string not + required, relational, nulls do **not** sort first ascending, `RowVersionStrategy.ClientStamped`. + `ExplicitKeyInsert` is `null` (`:47`): PostgreSQL accepts an explicit key without a toggle. + - It reads its own top-level migrations assembly (`:71-75`) and creates a `PostgreSQLDbContext` + (`:85-89`). + - `ApplyKeyAndTableMapping` (`:92-106`) is deliberately identical to SQL Server, table named after the + entity in the module schema (falling back to `dbo`), so an entity moves between the two engines by + changing its configuration base class alone (`:98-100`). + - The predicate differences: Npgsql's `IncludeProperties` annotation (`:109-110`), double-quoted + identifiers (`:113`) and a `"IsDeleted" = false` soft-delete filter (`:116`). +- **Why it's built this way**: [ADR-113](https://ivanball.github.io/docs/adr/113-postgresql-as-a-first-class-engine.html) + keeps the mapping shared with SQL Server; ADR-130 puts what differs in this one class. +- **Where it's used**: registered in [`DataSourceEngines`](#datasourceengines) (`DataSourceEngines.cs:25`); + allow-listed by `DataSourceBranchingFitnessTests.cs:28`. + +### SqliteDataSourceEngine +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SqliteDataSourceEngine.cs:16` · Level 11 · class (internal sealed) + +- **What it is**: the [`IDataSourceEngine`](#idatasourceengine) strategy for SQLite. +- **Depends on**: [`SqliteDbContext`](#sqlitedbcontext), + [`IEntityTypeConfigurationSqlite`](#ientitytypeconfigurationsqlitetentity-tidentifiertype), + the settings types; EF Core SQLite provider and the SQL Server index-builder extension. +- **Concept**: see [`IDataSourceEngine`](#idatasourceengine). +- **Walkthrough**: + - Descriptive facts: `Engine` is `DataSource.Sqlite` (`SqliteDataSourceEngine.cs:23`), + `SubstitutionPriority` 2 (`:26`), settings key `SqliteMigrationsAssembly` (`:32`), connection + identity by connection string alone (`:35`). + - `Capabilities` (`:38-43`): `MigrationPolicy.WhenAssemblyConfigured`, connection string not required, + relational, nulls sort first ascending, `RowVersionStrategy.ClientStamped`. `ExplicitKeyInsert` is + `null` (`:46`). + - `GetTopLevelMigrationsAssembly` always returns empty: `ConnectionStrings` carries no SQLite + migrations assembly, so a mixed-engine host can never hand SQLite the SQL Server snapshot (`:70-74`). + Only a named `DataSources` entry can supply one. + - `ApplyKeyAndTableMapping` (`:91-103`) maps to a table named after the entity with no schema (`:97`) + and a generated key as `ValueGeneratedOnAdd().UseIdentityColumn(1, 1)` (`:100`). + - `IncludeColumns` keeps the SQL Server annotation, which the SQLite provider ignores (`:106-108`); + double-quoted identifiers (`:112`) and a `"IsDeleted" = 0` filter (`:115`). +- **Why it's built this way**: see [ADR-130](https://ivanball.github.io/docs/adr/130-per-engine-data-source-strategy.html). +- **Where it's used**: registered in [`DataSourceEngines`](#datasourceengines) (`DataSourceEngines.cs:26`); + allow-listed by `DataSourceBranchingFitnessTests.cs:30`. + +### SQLServerDataSourceEngine +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DataSources.Engines` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SQLServerDataSourceEngine.cs:19` · Level 11 · class (internal sealed) + +- **What it is**: the [`IDataSourceEngine`](#idatasourceengine) strategy for SQL Server, and the only + engine that also implements [`IExplicitKeyInsertDialect`](#iexplicitkeyinsertdialect) + (`SQLServerDataSourceEngine.cs:19`). +- **Depends on**: [`SQLServerDbContext`](#sqlserverdbcontext), + [`IEntityTypeConfigurationSQLServer`](#ientitytypeconfigurationsqlservertentity-tidentifiertype), + [`ExplicitKeyInsertGroup`](#explicitkeyinsertgroup), [`NamespaceConventions`](#namespaceconventions), + the settings types; EF Core SQL Server provider. +- **Concept**: see [`IDataSourceEngine`](#idatasourceengine) and, for the explicit-key toggle, + [`ExplicitKeyInsertGroup`](#explicitkeyinsertgroup). +- **Walkthrough**: + - Descriptive facts: `Engine` is `DataSource.SQLServer` (`SQLServerDataSourceEngine.cs:26`), + `SubstitutionPriority` 0, the preferred substitute (`:29`), settings key + `SQLServerMigrationsAssembly` (`:35`), connection identity by connection string alone (`:38`). + - `Capabilities` (`:41-46`): `MigrationPolicy.Always`, connection string **required**, relational, + nulls sort first ascending, `RowVersionStrategy.StoreGenerated`. `ExplicitKeyInsert` returns `this` + (`:49`). + - Reads its top-level migrations assembly (`:73-77`) and creates a `SQLServerDbContext` (`:87-91`). + - `ApplyKeyAndTableMapping` (`:94-107`): table named after the entity in the module schema (falling + back to `dbo`), shared verbatim with PostgreSQL (`:100-101`). + - Bracket-quoted identifiers (`:114`) and a `[IsDeleted] = 0` filter (`:117`). + - `FindGroups` (`:120-159`) walks the change tracker for `Added` entries (`:128`), skips anything + without a single-property primary key (`:132-134`), keeps only keys whose SQL Server value-generation + strategy is `IdentityColumn` (`:137-141`), skips entries whose key is still an EF temporary value, + since only an explicitly set (imported) value needs the toggle (`:143-146`), and buckets survivors by + `(schema, table)` with `dbo` as the schema fallback (`:148-155`). + - `BuildToggleSql` (`:162-163`) concatenates `SET IDENTITY_INSERT [schema].[table] ON|OFF`; the + identifier cannot be parameterized, and the caller suppresses `S2077` with that justification + (`DbContextFactory.cs:413`). +- **Why it's built this way**: SQL Server is the default engine, so it alone requires a connection + string and migrates even with no configured assembly (falling back to the Default source's assembly, + `MigrationPolicy.cs:16-19`). Implementing the dialect on the engine itself keeps the identity-insert + rules (identity strategy, temporary values) next to the other SQL Server facts + ([ADR-130](https://ivanball.github.io/docs/adr/130-per-engine-data-source-strategy.html)). +- **Where it's used**: registered in [`DataSourceEngines`](#datasourceengines) (`DataSourceEngines.cs:27`); + allow-listed by `DataSourceBranchingFitnessTests.cs:29`; its dialect is driven by + [`DbContextFactory`](#dbcontextfactory) (`DbContextFactory.cs:292-294,334`). + ### DetectChangesScope -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DbContexts` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:279` · Level 0 · struct (private readonly, nested) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DbContexts` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:284` · Level 0 · struct (private readonly, nested) - **What it is**: a two-field disposable struct nested inside [`ApplicationDbContext`](#applicationdbcontext) whose only job is to put EF's @@ -4503,27 +5142,27 @@ survives a module being pulled out into its own service. EF's `ChangeTracker.Entries()` runs a full `DetectChanges` pass on every call and memoizes nothing. Interceptors scan the tracker during `SavingChanges` and EF then detects once more on its own before building the save, so a single save paid three `O(tracked entities x properties)` - snapshot comparisons where one suffices (`ApplicationDbContext.cs:252-259`). Turning detection off + snapshot comparisons where one suffices (`ApplicationDbContext.cs:257-264`). Turning detection off for the duration of the save is the optimization; a `using`-scoped struct is what makes turning it back on unforgettable, including on the exception path. - **Walkthrough**: the primary constructor `DetectChangesScope(ChangeTracker changeTracker, bool - previousSetting)` (`ApplicationDbContext.cs:279`) captures the tracker and the flag value that was in - force before suppression. `Dispose()` (`ApplicationDbContext.cs:281`) is a single expression-bodied + previousSetting)` (`ApplicationDbContext.cs:284`) captures the tracker and the flag value that was in + force before suppression. `Dispose()` (`ApplicationDbContext.cs:286`) is a single expression-bodied assignment that writes `previousSetting` back onto `changeTracker.AutoDetectChangesEnabled`. It is - created only by `DetectChangesOnce()` (`ApplicationDbContext.cs:269-277`), which reads the current + created only by `DetectChangesOnce()` (`ApplicationDbContext.cs:274-282`), which reads the current setting (`:235`), calls `ChangeTracker.DetectChanges()` once when detection was on (`:236-237`), sets the flag to `false` (`:239`), and hands back the scope (`:240`). - **Why it's built this way**: `readonly struct` means no heap allocation on a path that runs on every save, and `private` keeps the mechanism invisible to callers. Restoring the *previous* value rather than hardcoding `true` is the load-bearing detail: a caller that had deliberately disabled auto-detect keeps its choice and never gets an unexpected detection pass on the way out - (`ApplicationDbContext.cs:264-266`). Suppressing the remaining passes is safe because everything the + (`ApplicationDbContext.cs:269-271`). Suppressing the remaining passes is safe because everything the interceptors do afterwards bypasses detection anyway: the audit interceptor writes through - `entry.Property(...).CurrentValue` (`AuditSaveChangesInterceptor.cs:57-60`) and the domain-event + `entry.Property(...).CurrentValue` (`AuditSaveChangesInterceptor.cs:66-69`) and the domain-event interceptor adds outbox rows through `Add`, both of which take effect on the entry immediately. - **Where it's used**: both save overrides that EF funnels through, `SaveChangesAsync(bool, - CancellationToken)` (`ApplicationDbContext.cs:209-215`) and `SaveChanges(bool)` - (`ApplicationDbContext.cs:242-246`), open one with `using var detection = DetectChangesOnce()`. The + CancellationToken)` (`ApplicationDbContext.cs:214-220`) and `SaveChanges(bool)` + (`ApplicationDbContext.cs:247-251`), open one with `using var detection = DetectChangesOnce()`. The behavior is pinned by `SaveChangeDetectionTests` (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/SaveChangeDetectionTests.cs:31`, `:47`, `:64`, `:78`, `:90`), which asserts detection runs exactly once, that an untracked property @@ -4531,7 +5170,7 @@ survives a module being pulled out into its own service. save, and that a default context is left with detection enabled for the next caller. - **Caveats / not-in-source**: the remarks name **two** tracker-scanning interceptors ([`AuditSaveChangesInterceptor`](#auditsavechangesinterceptor) at - `AuditSaveChangesInterceptor.cs:52` and + `AuditSaveChangesInterceptor.cs:61` and [`DomainEventSaveChangesInterceptor`](#domaineventsavechangesinterceptor) at `DomainEventSaveChangesInterceptor.cs:221`), which are the two that are always registered. Two optional interceptors enumerate the tracker as well when a host opts into them @@ -4541,54 +5180,6 @@ survives a module being pulled out into its own service. `AuditTrailSaveChangesInterceptor.cs:198`), so the suppression saves strictly more than the comment claims; the comment is narrower than the code, not wrong about the mechanism. -### IdentityInsertGroup -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DbContexts.Factory` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:424` · Level 0 · record (private sealed, nested) - -- **What it is**: a three-member private record nested in [`DbContextFactory`](#dbcontextfactory), - `(string Schema, string Table, List Entries)` (`DbContextFactory.cs:424`). It names one - batch of pending inserts that all target the same table and all carry explicit identity values, which - is exactly the unit SQL Server's `SET IDENTITY_INSERT` operates on. -- **Depends on**: EF Core's `EntityEntry`, and nothing else. -- **Concept introduced, importing rows that already have ids.** `[Rubric §8, Data Architecture]` - (assesses whether persistence mechanics are deliberate): normally a database-generated identity - column means the application never supplies the id. An import from an external system (ADC's - Sessionize refresh) must preserve the source's ids, and SQL Server only allows that with - `SET IDENTITY_INSERT
ON`, one table at a time per session (`DbContextFactory.cs:297-302`). - Grouping the affected entries by table is what turns that constraint into a loop. -- **Walkthrough**: - - **`GetIdentityInsertGroups`** (`DbContextFactory.cs:381-422`) builds the list: it walks the change - tracker for `Added` entries (`:366-369`), skips anything without a single-property primary key - (`:372-374`), keeps only properties whose SQL Server value-generation strategy is - `IdentityColumn` (`:376-381`), and then skips entries whose id is still an EF **temporary** value - (`:386-387`), since a temporary value means the application did not set one. Survivors are bucketed - by `(schema, table)` with `"dbo"` as the schema fallback (`:389-402`). - - **`SaveWithIdentityInsertAsync`** (`DbContextFactory.cs:303-375`) consumes the groups: with none it - falls back to a plain save (`:290-291`); otherwise, per group, it flips every `Added` entry - belonging to the **other** groups to `Unchanged` so this round's batch touches one table only - (`:300-306`), runs `SET IDENTITY_INSERT [schema].[table] ON`, saves, and turns it `OFF` in a - `finally` (`:324-337`), then restores the hidden entries' states in an outer `finally` (`:340-346`). - Any remaining changes get a final ordinary save (`:350-353`). - - **Capture exclusion** (`DbContextFactory.cs:333-335`, `:342`): before each round it calls - [`DomainEventSaveChangesInterceptor`](#domaineventsavechangesinterceptor)`.BeginCaptureExclusion` - with exactly the hidden entities and ends the exclusion afterwards. The comment (`:308-313`) - explains the bug this prevents: capture serializes an aggregate's events to the outbox and clears - them, so without the exclusion a row written a round later would have published its event a round - early. It names the hidden entries explicitly rather than filtering by state, because "skip every - `Unchanged` aggregate" would also drop events legitimately raised on an already-saved aggregate, - which is how the identity module publishes registration events. -- **Why it's built this way**: the whole dance is confined to the SQL Server path of one private - method, guarded by an opt-in flag, so the normal save path pays nothing. The raw SQL is covered by a - justified `CA2100` suppression (`DbContextFactory.cs:242-243`) and an `S2077` pragma (`:323`, - `:338`), both stating that schema and table names come from EF model metadata rather than user input, - and `SET IDENTITY_INSERT` cannot take a parameterized identifier. -- **Where it's used**: only inside [`DbContextFactory`](#dbcontextfactory), reached when a caller has - invoked `RequestIdentityInsert()` (`:276`) before the save, which the save path reads and immediately - clears (`:227-228`). The one first-party caller is ADC's Sessionize refresh, which signals the unit of - work before saving imported entities - (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeHandler.cs:168`), - through [`IUnitOfWork`](#iunitofwork)`.RequestIdentityInsert` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:73`). - ### ModelBuilderExtensions > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DbContexts` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ModelBuilderExtensions.cs:10` · Level 0 · class (internal static) @@ -4627,7 +5218,7 @@ survives a module being pulled out into its own service. configurations depend on services without a parameterless-ctor constraint. - **Where it's used**: called from [`ApplicationDbContext.ApplyConfigurationsForEntitiesInContext`](#applicationdbcontext) - (`ApplicationDbContext.cs:970-976`), which passes the engine's configuration interface (for example + (`ApplicationDbContext.cs:974-980`), which passes the engine's configuration interface (for example [`IEntityTypeConfigurationSQLServer`](#ientitytypeconfigurationsqlservertentity-tidentifiertype)) and a filter that matches each entity's registry-resolved [`DataSourceKey`](#datasourcekey). @@ -4649,10 +5240,10 @@ survives a module being pulled out into its own service. connections) as transient, and EF decides retriability by walking an exception's **whole** inner chain, so any wrapper carrying the transient error would still be retried, re-running every write of an operation whose commit may already be durable, including its outbox rows - (`TransactionCommitAmbiguousException.cs:8-14`, `DbContextFactory.cs:555-558`). That is why the + (`TransactionCommitAmbiguousException.cs:8-14`, `DbContextFactory.cs:571-574`). That is why the commit failure is returned rather than thrown from `RunTransactionalAttemptAsync` and `TryCommit` - (`DbContextFactory.cs:568-572`, `:572-574`, `:617-620`) and only converted into this exception - **past** the strategy (`DbContextFactory.cs:559-560`). Second, semantically: "it failed" and "nobody + (`DbContextFactory.cs:584-588`, `:572-574`, `:617-620`) and only converted into this exception + **past** the strategy (`DbContextFactory.cs:575-576`). Second, semantically: "it failed" and "nobody can say whether it failed" call for different recovery, so the type itself is the signal. - **Concept introduced, naming the partial outcome.** `[Rubric §13, Observability & Operability]` (assesses whether an operator can tell what actually happened from what the system reports): commits @@ -4666,7 +5257,7 @@ survives a module being pulled out into its own service. set; `(Exception innerException)` (`:45-46`), which pairs the provider's failure with the default message; and the four-argument diagnostic one (`:57-69`), which additionally takes the committed, ambiguous and rolled-back sources and composes them into the message. That last one is the one - [`DbContextFactory`](#dbcontextfactory) actually constructs (`DbContextFactory.cs:665`); it + [`DbContextFactory`](#dbcontextfactory) actually constructs (`DbContextFactory.cs:722`); it null-coalesces both lists to empty (`:63`, `:66-68`) so a caller passing `null` gets an empty group rather than a second exception. - **`CommittedSources`** (`:76`), **`AmbiguousSource`** (`:85`), **`RolledBackSources`** (`:93`): the @@ -4688,11 +5279,11 @@ survives a module being pulled out into its own service. [`OutboxProcessor`](group-04-events-outbox.md#outboxprocessor) if the commit did land ([ADR-003](https://ivanball.github.io/docs/adr/003-outbox-dual-dispatch.html)); and the deferred in-process dispatch is dropped, so no handler acts on state that may not exist - (`DbContextFactory.cs:681-702`). The source comment records that a witness row (a marker written + (`DbContextFactory.cs:738-759`). The source comment records that a witness row (a marker written inside each source's transaction that a replay could read) would close the multi-source gap entirely, and that it is deliberately not built, because the single transactional source every host runs today - needs none (`DbContextFactory.cs:506-516`). -- **Where it's used**: thrown at `DbContextFactory.cs:560`, constructed at `:646`; pinned by + needs none (`DbContextFactory.cs:522-532`). +- **Where it's used**: thrown at `DbContextFactory.cs:576`, constructed at `:646`; pinned by `DbContextFactoryCommitAmbiguityTests` (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:73`, `:105`, `:129`, `:149`, `:168`), which drives a context whose execution strategy retries on **any** @@ -4723,6 +5314,8 @@ survives a module being pulled out into its own service. [`IEntityDataSourceRegistry`](#ientitydatasourceregistry), [`IEntityConfigurationAssemblyProvider`](#ientityconfigurationassemblyprovider), [`PhysicalDataSource`](#physicaldatasource), [`DataSourceKey`](#datasourcekey), + [`IDataSourceEngine`](#idatasourceengine) through [`DataSourceEngines`](#datasourceengines), + [`SoftDeleteFilterSql`](#softdeletefiltersql), [`IAuditableEntity`](group-02-domain-building-blocks.md#iauditableentity), [`ITenantEntity`](group-02-domain-building-blocks.md#itenantentity), [`AuditableBaseEntity`](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype), @@ -4747,40 +5340,52 @@ survives a module being pulled out into its own service. - **Concept introduced, named global query filters.** `[Rubric §11, Security]` and `[Rubric §30, Compliance, Privacy & Data Governance]` (both assess whether isolation of other parties' data is structural rather than per-query discipline): this class declares two filters by name, `SoftDelete` - (`ApplicationDbContext.cs:469`) and `Tenant` (`:391`). EF composes named filters with AND, so a + (`ApplicationDbContext.cs:474`) and `Tenant` (`:477`). EF composes named filters with AND, so a tenant-owned soft-deletable entity is filtered on both without either filter knowing about the other, and a caller asking to see deleted rows drops exactly `SoftDelete` and leaves `Tenant` in force - (`:383-387`, `:405-412`). Isolation therefore cannot be forgotten at a call site. + (`:469-474`, `:490-499`). Isolation therefore cannot be forgotten at a call site. - **Walkthrough**: - **Primary constructor** (`ApplicationDbContext.cs:46-51`): takes `DbContextOptions`, an `IServiceProvider`, an [`IEntityConfigurationAssemblyProvider`](#ientityconfigurationassemblyprovider), and the [`PhysicalDataSource`](#physicaldatasource) this instance targets, delegating to `DbContext(options)`. - - **`DataSourceKey`** (`:49`): exposes the `(engine, database name)` pair this context serves; - **`PhysicalSource`** (`:97`) exposes the resolved connection info to subclasses as `internal`. - - **Model-gate fields** (`:63`, `:76`, `:94`, `:116`): `_schedulerTableEnabled`, + - **`DataSourceKey`** (`:54`): exposes the `(engine, database name)` pair this context serves; + **`PhysicalSource`** exposes the resolved connection info to subclasses as `internal`. + - **`Engine`** (`:61`): `internal`, `DataSourceEngines.For(physicalDataSource.Key.Engine)`, the + [`IDataSourceEngine`](#idatasourceengine) this context targets. Its doc comment (`:56-60`) names + both halves of what it answers: what the engine can do (whether it hosts the framework's + relational tables, so the transactional outbox, which Cosmos DB does not) and how its SQL and keys + are shaped. Every engine branch in this class now reads `Engine.Capabilities` rather than + comparing a `DataSource` value, and the same property is what + [`DomainEventSaveChangesInterceptor`](#domaineventsavechangesinterceptor) tests + (`Engine.Capabilities.IsRelational`, `DomainEventSaveChangesInterceptor.cs:127`, `:236`) and what + [`DbContextFactory`](#dbcontextfactory) reads for transactions and explicit-key inserts. + - **`EngineAnnotation`** (`:67`): the `internal const` model annotation name `"MMCA:Engine"`, which + carries the engine of the model being built while entity configurations are applied (see + `ApplyConfigurationsForEntitiesInContext` below). + - **Model-gate fields** (`:81`, `:94`, `:112`, `:129`): `_schedulerTableEnabled`, `_auditTrailTableEnabled`, `_refreshSessionTableEnabled` and `_permissionGrantTableEnabled`, all four resolved in `OnConfiguring` and read by the model-building methods. Their remarks state the rule that keeps them correct: `OnModelCreating` must not depend on anything the model cache key does not cover, so the settings lookups happen once, up front, in the same place the interceptors are - resolved. The fourth gate (`:101-116`) is shaped like the refresh-session one but keyed on a + resolved. The fourth gate (`:129`) is shaped like the refresh-session one but keyed on a **registration** rather than a configuration flag: `AddStoredPermissionGrants(configuration)` registers `Auth.PermissionGrantModelGate`, which is the whole opt-in, and `Authentication:PermissionGrants:DataSourceName` (default `Default`) names the one database that - carries the rows, resolved by `ResolvePermissionGrantGate` (`:338`) and read by + carries the rows, resolved by `ResolvePermissionGrantGate` (`:913`) and read by [`ConfigurePermissionGrants`](#applicationdbcontext). - - **`TenantIdAccessor`** (`:134`) and **`CurrentTenantId`** (`:141`): an `internal Func?` + - **`TenantIdAccessor`** (`:147`) and **`CurrentTenantId`** (`:154`): an `internal Func?` assigned by the scoped [`DbContextFactory`](#dbcontextfactory) at context creation - (`DbContextFactory.cs:104`, `:134`), and the public property that invokes it. The remarks explain + (`DbContextFactory.cs:114`, `:151`), and the public property that invokes it. The remarks explain why it is an accessor and not a copied value: a context can be created before the request's tenant is resolved, and a copy taken at that moment would pin the context to the wrong answer for its whole life. `null` reads as "no tenant", which makes the `Tenant` filter inert for background services, seeders and admin flows. - - **`OutboxOriginAccessor`** (`:143-158`) and **`CurrentOutboxOrigin`** (`:160-164`): the same + - **`OutboxOriginAccessor`** (`:171`) and **`CurrentOutboxOrigin`** (`:177`): the same accessor shape, one step wider. `OutboxOriginAccessor` is an `internal Func?` assigned by the scoped [`DbContextFactory`](#dbcontextfactory) alongside the tenant accessor - (`DbContextFactory.cs:149-153`) and read once per save, before its capture loop, by + (`DbContextFactory.cs:155`) and read once per save, before its capture loop, by [`DomainEventSaveChangesInterceptor`](#domaineventsavechangesinterceptor) when it writes outbox rows. The remarks name the reason it is an accessor rather than an injected service: a context is built by the singleton `PhysicalDbContextFactory` and carries only the root provider, and the @@ -4789,40 +5394,34 @@ survives a module being pulled out into its own service. context created outside a scope, design time or a directly-constructed test context) reads as "nothing captured", and `CurrentOutboxOrigin` falls back to `default` in that case. - **`ValReturn`**: the nested keyless scalar holder, documented in its own section above. - - **`SupportsOutbox`** (`:172`): `internal virtual`, `true` by default; the Cosmos subclass overrides - it to `false`. Its doc comment states which contexts it means: the relational contexts, SQL Server, - PostgreSQL and SQLite (`:166-171`). Read by - [`DomainEventSaveChangesInterceptor`](#domaineventsavechangesinterceptor) - (`DomainEventSaveChangesInterceptor.cs:127`, `:235`). - - **`CurrentSaveUserId`** (`:143`): `internal` audit user id with a private setter, written by the + - **`CurrentSaveUserId`** (`:184`): `internal` audit user id with a private setter, written by the save overloads and read by [`AuditSaveChangesInterceptor`](#auditsavechangesinterceptor); `null` - marks a system operation and the interceptor resolves it to `default` - (`AuditSaveChangesInterceptor.cs:50`). - - **`SaveChangesAsync(userId, ct)`** (`:153-167`): the mutation entry point. Opens a MiniProfiler step - (`:155`), sets `CurrentSaveUserId`, calls `base.SaveChangesAsync`, and clears the id again in a - `finally` (`:161-166`) so a later plain `base.SaveChangesAsync` on the same instance (an internal + marks a system operation and the interceptor resolves it to `default`. + - **`SaveChangesAsync(userId, ct)`** (`:194`): the mutation entry point. Opens a MiniProfiler step, + sets `CurrentSaveUserId`, calls `base.SaveChangesAsync`, and clears the id again in a + `finally` so a later plain `base.SaveChangesAsync` on the same instance (an internal outbox write, for example) cannot silently reuse the previous caller's identity for its stamps. - - **`SaveChanges(userId)`** (`:189-200`): the synchronous counterpart with the same set/reset - discipline. Its doc comment records the behavioral difference (`:181-186`): the sync path cannot + - **`SaveChanges(userId)`** (`:230`): the synchronous counterpart with the same set/reset + discipline. Its doc comment records the behavioral difference: the sync path cannot dispatch events in-process, so captured events are delivered by the outbox processor instead. - - **Change-detection overrides** (`:173-179`, `:206-210`): both EF save overloads wrap the base call + - **Change-detection overrides** (`:214`, `:247`): both EF save overloads wrap the base call in `using var detection = DetectChangesOnce()`, the optimization taught under [`DetectChangesScope`](#detectchangesscope). - - **`OnConfiguring`** (`:249-306`): resolves the two always-present interceptors from DI (`:256-257`) + - **`OnConfiguring`** (`:290-353`): resolves the two always-present interceptors from DI (`:297-298`) and adds them, inserting [`TenantSaveChangesInterceptor`](#tenantsavechangesinterceptor) - **between** them when it is registered (`:264-271`). Registration order is execution order, and the - comment (`:259-263`) states why the tenant interceptor belongs in the middle: after the audit stamps + **between** them when it is registered (`:305-312`). Registration order is execution order, and the + comment (`:300-304`) states why the tenant interceptor belongs in the middle: after the audit stamps (it must not run against half-stamped entries) and before the domain-event interceptor serializes outbox rows, so those rows describe an entity whose tenant is already final. [`AuditTrailSaveChangesInterceptor`](#audittrailsavechangesinterceptor) is appended last when - present (`:278-281`), because it diffs the final values. Both optional interceptors are fetched with + present (`:319-322`), because it diffs the final values. Both optional interceptors are fetched with `GetService`, not `GetRequiredService`: a host that never opted in, a design-time context, or a directly constructed test context must still build. The method then resolves the four model gates, - including `_permissionGrantTableEnabled` (`:338`), calls - [`ConfigureSensitiveDataLogging`](#applicationdbcontext) (`:340`), and replaces EF's + including `_permissionGrantTableEnabled` (`:343`), calls + [`ConfigureSensitiveDataLogging`](#applicationdbcontext) (`:345`), and replaces EF's `IModelCacheKeyFactory` with [`DataSourceModelCacheKeyFactory`](#datasourcemodelcachekeyfactory) - (`:345`) so each database gets its own model. - - **`ConfigureSensitiveDataLogging`** (`:351-370`): a private helper called from `OnConfiguring`. It + (`:350`) so each database gets its own model. + - **`ConfigureSensitiveDataLogging`** (`:367-375`): a private helper called from `OnConfiguring`. It calls `optionsBuilder.EnableSensitiveDataLogging()` only when [`SensitiveDataLoggingGate`](#sensitivedatalogginggate)`.IsEnabled` says both [`PersistenceSettings`](group-07-persistence-ef-core.md#persistencesettings) asked for it and @@ -4830,67 +5429,75 @@ survives a module being pulled out into its own service. directly-constructed context (which registers neither) reads as "off". Living on the shared base rather than in each engine's context class is what keeps the guarantee from holding for SQL Server while a fourth engine quietly leaks it. - - **`ConfigureConventions`** (`:340-406`): adds four model-finalization conventions in a fixed order. + - **`ConfigureConventions`** (`:378-409`): adds four model-finalization conventions in a fixed order. [`CrossDataSourceDegradeConvention`](#crossdatasourcedegradeconvention) strips FK constraints and navigations between entities in different physical databases (a structural no-op in the collapsed-monolith case); [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) makes unique indexes on soft-deletable entities exclude deleted rows, so a soft-deleted row does not block re-creating the "same" record; [`RestrictDeleteByDefaultConvention`](#restrictdeletebydefaultconvention) - (`:385-406`) runs last of the three finalizing conventions, deliberately, so it never stamps a + (`:394-399`) runs last of the three finalizing conventions, deliberately, so it never stamps a relationship the cross-source convention has already removed, and records on every foreign key whether its delete behavior was chosen or inherited, inverting EF's own cascade default for a required relationship nobody configured; and, opt-in, `StronglyTypedIdModelConfiguration.Apply` - (`:385-406`) runs only when a `StronglyTypedIdRegistry` is registered (`AddStronglyTypedIds`, + (`:401-408`) runs only when a `StronglyTypedIdRegistry` is registered (`AddStronglyTypedIds`, ADR-115), mapping every declared wrapper type to the primitive it wraps on every engine because this runs on the one base context; absent the service it is a no-op and the primitive identifier aliases stay the identifier model. - **`Set()`**: a public override that forwards straight to `base.Set()` and adds no behavior of its own. - - **`OnModelCreating`** (`:414-428`): applies soft-delete filters, tenant filters and concurrency - tokens, registers the four keyless `ValReturn` views, then configures the outbox (now passed - `physicalDataSource.Key.Engine`), the inbox, the internal-command job queue + - **`OnModelCreating`** (`:416-444`): applies soft-delete filters, tenant filters and concurrency + tokens, registers the four keyless `ValReturn` views, then configures the outbox (passed + `physicalDataSource.Key.Engine`, `:423`), the inbox, the internal-command job queue (`ConfigureInternalCommands`, one table per relational source like the outbox), the scheduler table, the audit-trail table, the refresh-session table, and, last, the stored permission-grant table via `ConfigurePermissionGrants`, gated the same two-condition way as refresh sessions. - - **`ApplySoftDeleteFilters`** (`:367-381`): `protected static`; iterates every non-owned + - **`ApplySoftDeleteFilters`** (`:453-467`): `protected static`; iterates every non-owned `IAuditableEntity` type and builds an expression-tree - `HasQueryFilter("SoftDelete", e => e.IsDeleted == false)` (`:373-379`). Expression trees are + `HasQueryFilter("SoftDelete", e => e.IsDeleted == false)` (`:459-465`). Expression trees are required because the CLR type is only known at runtime; owned types are excluded because they inherit the parent filter. `[Rubric §5, Vertical Slice]` (a global filter removes per-query `Where(!IsDeleted)` boilerplate from every slice). - - **`ApplyTenantFilters`** (`:428-488`): `protected` (instance, because the filter body reads this + - **`ApplyTenantFilters`** (`:514-574`): `protected` (instance, because the filter body reads this context). For every non-owned [`ITenantEntity`](group-02-domain-building-blocks.md#itenantentity) - it configures the discriminator column as required, capped at `TenantIdMaxLength` of 64 (`:397`) - and non-Unicode (`:445-448`), indexes it for every engine except Cosmos (`:457-467`), and builds + it configures the discriminator column as required, capped at `TenantIdMaxLength` of 64 (`:483`) + and non-Unicode (`:531-534`), indexes it only when `Engine.Capabilities.IsRelational` (`:543-553`, + so every engine but Cosmos), and builds `HasQueryFilter("Tenant", e => CurrentTenantId == null || EF.Property(e, "TenantId") == - CurrentTenantId)` (`:469-486`). Three mechanisms are worth internalizing here. The filter embeds - **this context** as a constant typed as `ApplicationDbContext` (`:435`), which EF rewrites to the + CurrentTenantId)` (`:566-572`). Three mechanisms are worth internalizing here. The filter embeds + **this context** as a constant typed as `ApplicationDbContext` (`:521`), which EF rewrites to the executing context at query compile time and lifts `CurrentTenantId` into a SQL parameter, so two scopes on two tenants share one compiled model and still read disjoint rows. It reads the column - through `EF.Property` rather than a CLR member access (`:473-478`), which works for an explicitly + through `EF.Property` rather than a CLR member access (`:557-564`), which works for an explicitly implemented interface member and for a shadow property alike. And the supporting index follows the filter **composition**: an entity that is also an `IAuditableEntity` gets `(TenantId, IsDeleted)` because every read of it carries `TenantId = @tenant AND IsDeleted = 0`, while a tenant-only entity - keeps the single-column index (`:450-466`). - - **`ConfigureConcurrencyTokens`** (`:572`): `protected` (instance, because it reads - `Database.ProviderName`). It applies `IsRowVersion()` on SQL Server (database-generated - `rowversion`) or `IsConcurrencyToken()` on the other relational providers, PostgreSQL and SQLite - (application-managed, `:572`), to the `RowVersion` property of every non-owned auditable entity. EF - then includes the token in `UPDATE`/`DELETE` `WHERE` clauses and throws - `DbUpdateConcurrencyException` on conflicts. `[Rubric §8, Data Architecture]`. - - **`QuoteColumn`** and **`IncludeProperties`** (`:601-666`): two private static helpers the filtered - outbox/internal-command indexes below now route through, added once a second relational provider - joined SQL Server and SQLite. `QuoteColumn(DataSource engine, string column)` (`:601-621`) quotes a - column name the way the engine expects inside a filtered-index predicate string: SQL Server and - SQLite both accept the bracketed `[column]` form they have always produced, PostgreSQL rejects - brackets and needs the SQL-standard `"column"` form. `IncludeProperties` (`:622-666`) picks between - the SQL Server and PostgreSQL `IncludeProperties` extension overloads, which share one signature and - are ambiguous unqualified once both providers are referenced in the same project, by dispatching on - the engine of the model being built. - - **`ConfigureOutbox`** (`:528-563` region, now taking `DataSource engine`): maps `OutboxMessages` in - `dbo` with length/unicode constraints, plus three purpose-built filtered indexes, now built through - `QuoteColumn`/`IncludeProperties` so the same method body produces valid filter and include syntax + keeps the single-column index (`:536-553`). + - **`ConfigureConcurrencyTokens`** (`:588-608`): `protected` (instance, because it reads this + context's `Engine`). When `Engine.Capabilities.RowVersion` is + [`RowVersionStrategy`](#rowversionstrategy)`.StoreGenerated` (`:591`, SQL Server's `rowversion`) it + applies `IsRowVersion()`; on the other relational engines, PostgreSQL and SQLite, it applies + `IsConcurrencyToken()` (`:599-606`), to the `RowVersion` property of every non-owned auditable + entity. The doc comment (`:576-587`) names who manages the value on those engines: + [`AuditSaveChangesInterceptor`](#auditsavechangesinterceptor) writes a fresh value on every insert + and update (it checks `RowVersionStrategy.ClientStamped`, `AuditSaveChangesInterceptor.cs:59`), so + the `UPDATE`'s `WHERE` clause actually detects a concurrent writer. EF then includes the token in + `UPDATE`/`DELETE` `WHERE` clauses and throws `DbUpdateConcurrencyException` on conflicts. + `[Rubric §8, Data Architecture]`. + - **`QuoteColumn`** and **`IncludeColumns`** (`:610-648`): two private static helpers the filtered + outbox, internal-command and scheduler indexes below route through, each now a one-line delegation + to the engine. `QuoteColumn(DataSource engine, string column)` (`:618-619`) forwards to + [`SoftDeleteFilterSql`](#softdeletefiltersql)`.QuoteColumn`, and its doc comment (`:611-613`) states + the result: brackets on SQL Server, SQL-standard double quotes on PostgreSQL and SQLite. + `IncludeColumns` (`:644-648`) forwards to `DataSourceEngines.For(engine).IncludeColumns`, which picks + the provider's own `IncludeProperties` overload (the SQL Server and PostgreSQL extensions share one + signature and are ambiguous unqualified once both are referenced). Its remarks (`:631-643`) record + two deliberate choices: every engine other than PostgreSQL keeps the SQL Server annotation, SQLite + included because its provider ignores it, and the helper takes property names rather than a + selector so the scheduler table does not reflect as a `Persistence -> Scheduling` type reference. + - **`ConfigureOutbox`** (`:661`, taking `DataSource engine`): maps `OutboxMessages` in + `dbo` with length/unicode constraints, plus three purpose-built filtered indexes, built through + `QuoteColumn`/`IncludeColumns` so the same method body produces valid filter and include syntax on every relational engine. `IX_OutboxMessages_Pending` covers the poll path over `(ProcessedOn, OccurredOn)` filtered to the processed column being null and includes `RetryCount` and `LockedUntil` so the processor's extra predicates do not force a key lookup per candidate row; @@ -4899,41 +5506,48 @@ survives a module being pulled out into its own service. pending rows, answers the claim predicate's "is there an earlier unprocessed row with this key?" question with a seek instead of a scan per candidate row. `[Rubric §12, Performance & Scalability]`. - - **`ConfigureInbox`** (`:570-584`): maps `InboxMessages` in `dbo` with a unique - `IX_InboxMessages_MessageId` (the consumer-side idempotency key, `:576-578`) and an - `IX_InboxMessages_ProcessedOn` index so the age-based purge has something to seek (`:582-583`). - - **`ConfigureScheduler`** (`:594-619`): the first **gated** table. It returns immediately unless - `_schedulerTableEnabled` (`:596-599`), then maps + - **`ConfigureInbox`** (`:721`): maps `InboxMessages` in `dbo` with a unique + `IX_InboxMessages_MessageId` (the consumer-side idempotency key) and an + `IX_InboxMessages_ProcessedOn` index so the age-based purge has something to seek. + - **`ConfigureScheduler`** (`:802`): the first **gated** table. It returns immediately unless + `_schedulerTableEnabled`, then maps [`ScheduledJobEntry`](group-14-module-system-composition.md#scheduledjobentry) to `ScheduledJobs` in - `dbo` keyed by `JobName`, with `IX_ScheduledJobs_NextRunOn` including the lease columns - (`:615-617`). The index comment (`:610-614`) records the load-bearing decision: it is deliberately + `dbo` keyed by `JobName`, with `IX_ScheduledJobs_NextRunOn` including the lease columns through + `IncludeColumns` (`:831`). The index comment records the load-bearing decision: it is deliberately **not** filtered to unlocked rows, because the poll must also find rows whose lease has expired, which is how a dead replica's work is reclaimed. - - **`ConfigureAuditTrail`** (`:628-657`): the second gated table, mapping + - **`ConfigureAuditTrail`** (`:847`): the second gated table, mapping [`AuditTrailEntry`](#audittrailentry) to `AuditTrailEntries` in `dbo` with a read index over - `(EntityType, EntityKey, ChangedOn)` (`:648-649`) and a retention index over `ChangedOn` - (`:654-655`). Note the asymmetry with the scheduler, stated at `:69-73` and `:621-627`: the job + `(EntityType, EntityKey, ChangedOn)` and a retention index over `ChangedOn`. Note the asymmetry + with the scheduler, stated in the gate fields' remarks (`:81`, `:94`): the job table is host-scoped and lives in the `Default` source only, while the trail table belongs in **every** relational source, because a trail row must commit in the same transaction as the change it describes and a transaction does not span databases (the outbox precedent). - - **`ConfigureRefreshSessions`** (`:673-681`): the third gated table, and the one gated on a - **configurable** source rather than a fixed one. It returns unless `_refreshSessionTableEnabled` - (`:675-678`), which requires both `RefreshSessions:Enabled` and this context targeting the source - named by `RefreshSessions:DataSourceName` (`:295-298`); when it passes it simply calls - [`RefreshSessionModelBuilderExtensions`](#refreshsessionmodelbuilderextensions)`.ApplyRefreshSessionConfiguration` - (`:680`). The doc comment states why the mapping lives in the framework base rather than in a - consumer's context class (`:664-671`): under + - **`ConfigureRefreshSessions`** (`:892`): the third gated table, and the one gated on a + **configurable** source rather than a fixed one. It returns unless `_refreshSessionTableEnabled`, + which requires both `RefreshSessions:Enabled` and this context targeting the source + named by `RefreshSessions:DataSourceName` (`:336-339`); when it passes it simply calls + [`RefreshSessionModelBuilderExtensions`](#refreshsessionmodelbuilderextensions)`.ApplyRefreshSessionConfiguration`. + The doc comment states why the mapping lives in the framework base rather than in a + consumer's context class: under [ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html) downstream apps run on the sealed engine contexts and have no context class of their own to override, and [`RefreshSession`](group-08-auth.md#refreshsession) is not an `AuditableBaseEntity`, so the module entity-configuration mechanism does not reach it either. - - **`ApplyConfigurationsForEntitiesInContext`** (`:690-717`): the discovery method subclasses call - from their `OnModelCreating`. It maps the engine to its configuration interface (`:692-698`), - resolves [`IEntityDataSourceRegistry`](#ientitydatasourceregistry) (`:705`), then for each assembly - from the provider calls + - **`ApplyConfigurationsForEntitiesInContext`** (`:953-987`): the discovery method subclasses call + from their `OnModelCreating`. It asks the engine for its configuration interface + (`DataSourceEngines.For(dataSource).EntityConfigurationInterface`, `:955`) rather than switching on + the `DataSource` value, resolves [`IEntityDataSourceRegistry`](#ientitydatasourceregistry) (`:962`), + then for each assembly from the provider calls [`ModelBuilderExtensions.ApplyAllConfigurations`](#modelbuilderextensions) with a filter that keeps only entities whose registry-resolved key equals this `DataSourceKey`, or, for unregistered - entities, only when this context is the engine's `Default` source (`:709-715`). + entities, only when this context is the engine's `Default` source (`:974-980`). The whole pass runs + inside a `try`/`finally` that sets `EngineAnnotation` to this context's engine on the model first + (`:969`) and removes it afterwards (`:985`). The comment (`:964-968`) gives the reason: a + configuration declared for one engine can be applied to another engine's model when the host + substitutes an unconfigured engine, so it cannot trust its own attribute for SQL text and reads the + effective engine from the annotation instead; removing it keeps the finished model and every + migration snapshot unchanged. - **Why it's built this way**: [ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html) (database-per-service) requires the same context class per database; without a specialized model-cache key EF would build @@ -4953,13 +5567,17 @@ survives a module being pulled out into its own service. strongly typed identifiers apply only when a host opts in ([ADR-115](https://ivanball.github.io/docs/adr/115-strongly-typed-identifiers-opt-in.html)); and the restrict-by-default delete convention inverts EF's own cascade default for every non-owned foreign key - ([ADR-119](https://ivanball.github.io/docs/adr/119-restrict-delete-by-default.html)). The comment at - `:700-704` records one more deliberate fallback: an entity configured without the attributed base + ([ADR-119](https://ivanball.github.io/docs/adr/119-restrict-delete-by-default.html)). Every + engine-specific branch reads the engine object behind `Engine` rather than comparing `DataSource` + values inline + ([ADR-130](https://ivanball.github.io/docs/adr/130-per-engine-data-source-strategy.html)), so a fact + about an engine is stated once, on that engine. The comment above the registry lookup (`:957-961`) + records one more deliberate fallback: an entity configured without the attributed base classes lands in the `Default` model but is not routable through the unit of work. - **Where it's used**: inherited by the four concrete contexts below; created per source by - [`PhysicalDbContextFactory`](#physicaldbcontextfactory) (`PhysicalDbContextFactory.cs:46-49`), cached - per scope and given its tenant accessor by [`DbContextFactory`](#dbcontextfactory) - (`DbContextFactory.cs:104`), and consumed by the interceptors and the outbox processor. The model + [`PhysicalDbContextFactory`](#physicaldbcontextfactory) through the engine's `CreateDbContext` + (`PhysicalDbContextFactory.cs:32`), cached per scope and given its tenant accessor by + [`DbContextFactory`](#dbcontextfactory) (`DbContextFactory.cs:106-114`), and consumed by the interceptors and the outbox processor. The model gates are pinned by `SchedulerModelGateTests` (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Scheduling/SchedulerModelGateTests.cs:28`, `:38`, `:48`, `:59`), `AuditTrailModelGateTests` @@ -5000,11 +5618,11 @@ survives a module being pulled out into its own service. the key: the tenant. One compiled model serves every tenant, and the tenant value enters as a SQL parameter through the `Tenant` query filter instead (see [`ApplicationDbContext.ApplyTenantFilters`](#applicationdbcontext) and its remarks at - `ApplicationDbContext.cs:495-501`), which is what keeps a multi-tenant host from building one model + `ApplicationDbContext.cs:500-506`), which is what keeps a multi-tenant host from building one model per tenant. - **Where it's used**: registered in [`ApplicationDbContext.OnConfiguring`](#applicationdbcontext) via `optionsBuilder.ReplaceService()` - (`ApplicationDbContext.cs:345`), so every engine context inherits it. + (`ApplicationDbContext.cs:350`), so every engine context inherits it. ### CosmosDbContext > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DbContexts` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/CosmosDbContext.cs:15` · Level 12 · class (sealed) @@ -5022,19 +5640,30 @@ survives a module being pulled out into its own service. - **Walkthrough**: - **4-arg constructor** (`CosmosDbContext.cs:15-20`): forwards options, service provider, assembly provider, and physical source to the base. - - **Emulator detection** (`CosmosDbContext.cs:23-65`): checks the connection string for the well-known - emulator account-key prefix `"C2y6yDjf5"` (`:29`). The emulator path uses `ConnectionMode.Gateway` - and an `HttpClientFactory` whose handler installs - `DangerousAcceptAnyServerCertificateValidator` for the self-signed cert, guarded by a - `#pragma warning disable S4830` and a comment that this is safe only in local dev (`:41-52`). The - production path uses `ConnectionMode.Direct` with `MaxRequestsPerTcpConnection(20)` and - `MaxTcpConnectionsPerEndpoint(32)` (`:57-59`). The database name comes from - `PhysicalSource.CosmosDatabaseName` (`:34`). - - **`SupportsOutbox => false`** (`CosmosDbContext.cs:70`): overrides the base; Cosmos has no - relational outbox table, so domain events are dispatched in-process only. - - **`OnModelCreating`** (`CosmosDbContext.cs:73-103`): applies the Cosmos configurations (`:74`), then - `Ignore()` (`:77`) and, for the same relational-only reason, - `Ignore()` (`:80`), then removes every index from every entity type + - **Emulator handling in `OnConfiguring`** (`CosmosDbContext.cs:22-73`): two decisions, deliberately + separate (`:29-31`). The emulator account key alone (`UsesEmulatorKey`, `:32`) selects + `ConnectionMode.Gateway` (`:44`). Disabling certificate validation additionally requires a loopback + endpoint (`ShouldBypassCertificateValidation`, `:33`), because the key is published by Microsoft and + so never proves the endpoint is the local emulator; only then does an `HttpClientFactory` install + `DangerousAcceptAnyServerCertificateValidator` under a `#pragma warning disable S4830` + (`:46-61`). The production path uses `ConnectionMode.Direct` with + `MaxRequestsPerTcpConnection(20)` and `MaxTcpConnectionsPerEndpoint(32)` (`:66-68`). The database + name comes from `PhysicalSource.CosmosDatabaseName` (`:38`). + - **`UsesEmulatorKey`** (`:82-83`) and **`ShouldBypassCertificateValidation`** (`:98-99`): + `internal static` predicates. The first is an ordinal `Contains("C2y6yDjf5")`, the well-known prefix + of the emulator's default account key. The second requires that key AND a loopback + `AccountEndpoint`, judged by the private `HasLoopbackAccountEndpoint` (`:101-116`): the connection + string is parsed with `DbConnectionStringBuilder` and the endpoint with `Uri.TryCreate`, then tested + with `Uri.IsLoopback`, never by a string prefix, so a host such as `localhost.attacker.example` does + not qualify, and an unparseable string or endpoint keeps validation on (`:93-97`). + - **No outbox override**: the class no longer overrides anything to opt out of the outbox. Cosmos + is non-relational on its engine object, and + [`DomainEventSaveChangesInterceptor`](#domaineventsavechangesinterceptor) tests + `Engine.Capabilities.IsRelational` (`DomainEventSaveChangesInterceptor.cs:127`, `:236`), so domain + events from a Cosmos context are dispatched in-process only. + - **`OnModelCreating`** (`CosmosDbContext.cs:119-146`): applies the Cosmos configurations (`:121`), then + `Ignore()` (`:124`) and, for the same relational-only reason, + `Ignore()` (`:127`), then removes every index from every entity type because the provider does not support relational `HasIndex`/`HasFilter`, then calls `ApplySoftDeleteFilters` and `ApplyTenantFilters` directly. It deliberately does **not** call `base.OnModelCreating` because every table the base maps (outbox, inbox, internal commands, @@ -5045,124 +5674,33 @@ survives a module being pulled out into its own service. the entity configuration bodies engine-agnostic; stripping indexes lets one configuration body serve both SQL Server and Cosmos. Calling the two filter helpers directly rather than through the base is what keeps soft delete and tenancy in force on an engine that cannot run the rest of the base - pipeline (the tenant helper skips only its index for Cosmos, `ApplicationDbContext.cs:538-548`). + pipeline (the tenant helper skips its index on a non-relational engine, + `ApplicationDbContext.cs:543-553`). - **Where it's used**: instantiated per Cosmos source by - [`PhysicalDbContextFactory`](#physicaldbcontextfactory) when a data source resolves to the `CosmosDB` - engine (`PhysicalDbContextFactory.cs:49`). It is also the single fact behind - [`DbContextFactory`](#dbcontextfactory)`.SupportsTransactions`, which is literally - `context is not CosmosDbContext` (`DbContextFactory.cs:774-775`). -- **Caveats / not-in-source**: the certificate bypass is scoped by an ordinal substring match on the - emulator key prefix; whether any production connection string could contain that substring is Not - determinable from source. Per + [`CosmosDataSourceEngine`](#cosmosdatasourceengine)`.CreateDbContext` + (`CosmosDataSourceEngine.cs:79-83`), which [`PhysicalDbContextFactory`](#physicaldbcontextfactory) + reaches through [`DataSourceEngines`](#datasourceengines) when a data source resolves to the + `CosmosDB` engine (`PhysicalDbContextFactory.cs:32`). The emulator predicates are pinned by + `CosmosDbContextTests` + (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/CosmosDbContextTests.cs`). +- **Caveats / not-in-source**: Gateway mode is still selected by an ordinal substring match on the + emulator key prefix alone, though the certificate bypass now also requires a loopback endpoint. Per [ADR-018](https://ivanball.github.io/docs/adr/018-polyglot-persistence.html) the plumbing ships and is tested, but no host in this workspace configures a Cosmos source today. -### IDbContextFactory -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DbContexts.Factory` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/IDbContextFactory.cs:10` · Level 12 · interface +### PostgreSQLDbContext +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DbContexts` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/PostgreSQLDbContext.cs:23` · Level 12 · class (sealed) -- **What it is**: the framework's own context-factory contract, the scoped object that hands out one - [`ApplicationDbContext`](#applicationdbcontext) per physical [`DataSourceKey`](#datasourcekey) and - then coordinates saving, transactions, schema lifecycle, and disposal across every context a scope - touched (`IDbContextFactory.cs:5-10`). It is deliberately **not** EF Core's - `IDbContextFactory`: the two names collide, which is why consumers that need both add a - `using IDbContextFactory = ...DbContexts.Factory.IDbContextFactory;` alias - (`InProcessEventBus.cs:8`, `BrokerEventBus.cs:8`, `EfInboxStore.cs:8`) or fully qualify it - (`OutboxProcessor.cs:190`). -- **Depends on**: [`ApplicationDbContext`](#applicationdbcontext), [`DataSourceKey`](#datasourcekey), - and the BCL `IDisposable` plus `IAsyncDisposable` it extends (`IDbContextFactory.cs:10`). -- **Concept introduced, addressing a context by physical source rather than by type.** `[Rubric §8, - Data Architecture]` (assesses whether transaction boundaries and unit-of-work scope are deliberate, - and whether per-service data isolation is real): EF's own factory answers "give me a context of type - T". This one answers "give me the context for **this database**", which is the only question that - makes sense once [ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html) splits - storage along a `Name` axis and [ADR-018](https://ivanball.github.io/docs/adr/018-polyglot-persistence.html) - adds an orthogonal `Engine` axis. The interface also owns the honest statement of what a - multi-source save can and cannot promise, in the `ExecuteInTransactionAsync` doc comment - (`IDbContextFactory.cs:60-65`): each physical source gets its own transaction, commits are - sequential and best-effort, there is **no** two-phase commit, a failure mid-commit leaves earlier - sources committed, and the outbox is the cross-source consistency mechanism. -- **Walkthrough**: - - **`GetDbContext(DataSourceKey)`** (`IDbContextFactory.cs:16`): the only accessor, documented to - create the context if this scope does not already have one for that source. - - **`EnsureCreatedAsync`** (`:22`), **`MigrateAsync`** (`:81`), **`HasPendingMigrationsAsync`** - (`:87`): schema lifecycle across every source the host uses. The contract states the asymmetry: - `EnsureCreatedAsync` skips sources with no configured connection string (`:18-21`), while the two - migration members cover only the sources a migrations pipeline owns, which is every SQL Server - source plus any SQLite source with a configured `SqliteMigrationsAssembly`; Cosmos and - assembly-less SQLite are created by `EnsureCreatedAsync` instead (`:74-80`). - - **`SaveChangesAsync`** (`:27`) and **`SaveChanges`** (`:32`): save across all active contexts, the - async overload documented as carrying audit stamping and domain-event dispatch. - - **`RequestIdentityInsert`** (`:40`): a one-shot flag for the next save, documented as - automatically cleared once the save completes (see [`IdentityInsertGroup`](#identityinsertgroup)). - - **`BeginTransaction`** / **`CommitTransaction`** / **`RollbackTransaction`** (`:45`, `:50`, `:55`): - applied to every active context that supports transactions. - - **`ExecuteInTransactionAsync`** (`:70-72`): the member handlers actually reach, running - the operation under the active execution strategy so a retrying strategy retries the whole unit. -- **Why it's built this way**: the application layer already talks to - [`IUnitOfWork`](#iunitofwork); this second interface exists so the physical-topology coordination - (which databases, which transactions, which migrations) has a home that Infrastructure can implement - and tests can mock, without leaking EF Core upward. -- **Where it's used**: registered scoped as [`DbContextFactory`](#dbcontextfactory) - (`DependencyInjection.cs:104`). [`UnitOfWork`](#unitofwork) delegates its whole save and transaction - surface to it (`UnitOfWork.cs:70-79`), the startup path resolves it to create, migrate, or verify - databases - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:57`, - `:242`, and per tenant at `:144`), and the background and messaging paths resolve it per scope to - reach a specific source ([`OutboxProcessor`](group-04-events-outbox.md#outboxprocessor) at - `OutboxProcessor.cs:190`, `OutboxCleanupService.cs:105`, `OutboxAdministration.cs:235`, - `EfInboxStore.cs:39`, `InProcessEventBus.cs:34`, `BrokerEventBus.cs:32`, `ScheduledJobRunner.cs:214`, - `AuditTrailReader.cs:35`, `AuditTrailCleanupJob.cs:48`, `EFRefreshSessionStore.cs:31`, and - `RefreshSessionCleanupService.cs:112`). - -### IPhysicalDbContextFactory -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DbContexts.Factory` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/IPhysicalDbContextFactory.cs:15` · Level 12 · interface - -- **What it is**: the contract for constructing a **raw** context for a given physical data source. Its - doc comment states the split precisely: the engine selects the context class (SQL Server, Cosmos, - SQLite) and the source name selects the database (connection string, migrations assembly, EF model), - and contexts created here are neither scoped nor cached (`IPhysicalDbContextFactory.cs:6-13`). -- **Depends on**: [`ApplicationDbContext`](#applicationdbcontext) as the return type, - [`DataSourceKey`](#datasourcekey) as the addressing type, and - [`PhysicalDataSource`](#physicaldatasource) on the second overload - (`IPhysicalDbContextFactory.cs:22`, `:37`). -- **Concept introduced, construction split from lifetime.** `[Rubric §2, Design Patterns]` (assesses - whether patterns are applied where they earn their keep): this is the Abstract Factory half of the - pair. Deciding *which class to new up for which database* is a pure function of the key and needs no - per-request state, so it lives in a singleton; deciding *how long a context lives, when it saves, and - what transaction it is in* is per-request state and lives in the scoped - [`IDbContextFactory`](#idbcontextfactory) layered on top (`IPhysicalDbContextFactory.cs:10-13`). -- **Walkthrough**: two members. `Create(DataSourceKey key)` - (`IPhysicalDbContextFactory.cs:22`) returns a new instance targeting the database the resolver maps - that key to. `Create(DataSourceKey key, PhysicalDataSource physicalDataSource)` - (`IPhysicalDbContextFactory.cs:37`) is the database-per-tenant overload: the caller clones the - resolved [`PhysicalDataSource`](#physicaldatasource) with the tenant's connection string and passes - the **same** key, so EF's model cache still serves one compiled model per (context type, source name) - across every tenant (`IPhysicalDbContextFactory.cs:24-35`). -- **Why it's built this way**: keeping the construction decision behind a two-method interface is what - makes the scoped coordinator testable without a database (`[Rubric §14, Testability]`): the - commit-ambiguity tests hand [`DbContextFactory`](#dbcontextfactory) a - `Mock` that returns a SQLite in-memory context - (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:49-55`). - The two-overload shape is also what keeps tenancy - ([ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html)) a routing decision in - the scoped layer rather than a second factory. -- **Where it's used**: registered singleton as [`PhysicalDbContextFactory`](#physicaldbcontextfactory) - (`DependencyInjection.cs:105`); injected into [`DbContextFactory`](#dbcontextfactory) - (`DbContextFactory.cs:47`), which calls both overloads at `:94-96`. - -### PostgreSQLDbContext -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DbContexts` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/PostgreSQLDbContext.cs:23` · Level 12 · class (sealed) - -- **What it is**: the `sealed` [`ApplicationDbContext`](#applicationdbcontext) subclass targeting - PostgreSQL, the third relational engine alongside SQL Server and SQLite - (`PostgreSQLDbContext.cs:23-30`). One instance exists per physical PostgreSQL data source. -- **Depends on**: [`ApplicationDbContext`](#applicationdbcontext), - [`PhysicalDataSource`](#physicaldatasource), [`DataSource`](#datasource), - [`IEntityConfigurationAssemblyProvider`](#ientityconfigurationassemblyprovider), - [`PersistenceSettings`](group-07-persistence-ef-core.md#persistencesettings) via `IOptions<>`, - [`UtcDateTimeConverter`](#utcdatetimeconverter), and the Npgsql EF provider. `[Rubric §8, Data - Architecture]` (a third concrete context, same shape as SQL Server) and - `[ADR-113](https://ivanball.github.io/docs/adr/113-postgresql-as-a-first-class-engine.html)`. +- **What it is**: the `sealed` [`ApplicationDbContext`](#applicationdbcontext) subclass targeting + PostgreSQL, the third relational engine alongside SQL Server and SQLite + (`PostgreSQLDbContext.cs:23-30`). One instance exists per physical PostgreSQL data source. +- **Depends on**: [`ApplicationDbContext`](#applicationdbcontext), + [`PhysicalDataSource`](#physicaldatasource), [`DataSource`](#datasource), + [`IEntityConfigurationAssemblyProvider`](#ientityconfigurationassemblyprovider), + [`PersistenceSettings`](group-07-persistence-ef-core.md#persistencesettings) via `IOptions<>`, + [`UtcDateTimeConverter`](#utcdatetimeconverter), and the Npgsql EF provider. `[Rubric §8, Data + Architecture]` (a third concrete context, same shape as SQL Server) and + `[ADR-113](https://ivanball.github.io/docs/adr/113-postgresql-as-a-first-class-engine.html)`. - **Walkthrough**: - **`TimestampWithTimeZone`** (`PostgreSQLDbContext.cs:30`): `internal const string` holding `"timestamp with time zone"`, the PostgreSQL store type every `DateTime` maps to. Named as a @@ -5250,7 +5788,7 @@ survives a module being pulled out into its own service. which holds one SQLite connection open for the fixture's lifetime). `[Rubric §14, Testability]`. - **Where it's used**: instantiated per SQLite source by [`PhysicalDbContextFactory`](#physicaldbcontextfactory) when a data source resolves to the `Sqlite` - engine (`PhysicalDbContextFactory.cs:48`). Its migration behavior is pinned by + engine (`PhysicalDbContextFactory.cs:32` dispatches to `SqliteDataSourceEngine.cs:88`). Its migration behavior is pinned by `DbContextFactoryMigrationTargetTests` (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryMigrationTargetTests.cs:94`, `:106`, `:115`, `:126`), which asserts that a SQLite source with a migrations assembly is migrated, @@ -5315,7 +5853,7 @@ survives a module being pulled out into its own service. ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html), [ADR-009](https://ivanball.github.io/docs/adr/009-resilience-and-recovery-objectives.html)). - **Where it's used**: instantiated per SQL Server source by - [`PhysicalDbContextFactory`](#physicaldbcontextfactory) (`PhysicalDbContextFactory.cs:46`); built at + [`PhysicalDbContextFactory`](#physicaldbcontextfactory) (`PhysicalDbContextFactory.cs:32` dispatches to `SQLServerDataSourceEngine.cs:91`); built at design time by [`DesignTimeDbContextHelper`](#designtimedbcontexthelper)`.CreateSqlServer` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextHelper.cs:52`), which is what makes one migrations project per database possible. It is also the type @@ -5325,19 +5863,165 @@ survives a module being pulled out into its own service. - **Caveats / not-in-source**: whether any repository's CI actually runs the `has-pending-model-changes` gate the comment recommends is Not determinable from this file. +### IDbContextFactory +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DbContexts.Factory` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/IDbContextFactory.cs:10` · Level 12 · interface + +- **What it is**: the framework's own context-factory contract, the scoped object that hands out one + [`ApplicationDbContext`](#applicationdbcontext) per physical [`DataSourceKey`](#datasourcekey) and + then coordinates saving, transactions, schema lifecycle, and disposal across every context a scope + touched (`IDbContextFactory.cs:5-10`). It is deliberately **not** EF Core's + `IDbContextFactory`: the two names collide, which is why consumers that need both add a + `using IDbContextFactory = ...DbContexts.Factory.IDbContextFactory;` alias + (`InProcessEventBus.cs:8`, `BrokerEventBus.cs:8`, `EfInboxStore.cs:8`) or fully qualify it + (`OutboxProcessor.cs:183`). +- **Depends on**: [`ApplicationDbContext`](#applicationdbcontext), [`DataSourceKey`](#datasourcekey), + and the BCL `IDisposable` plus `IAsyncDisposable` it extends (`IDbContextFactory.cs:10`). +- **Concept introduced, addressing a context by physical source rather than by type.** `[Rubric §8, + Data Architecture]` (assesses whether transaction boundaries and unit-of-work scope are deliberate, + and whether per-service data isolation is real): EF's own factory answers "give me a context of type + T". This one answers "give me the context for **this database**", which is the only question that + makes sense once [ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html) splits + storage along a `Name` axis and [ADR-018](https://ivanball.github.io/docs/adr/018-polyglot-persistence.html) + adds an orthogonal `Engine` axis. The interface also owns the honest statement of what a + multi-source save can and cannot promise, in the `ExecuteInTransactionAsync` doc comment + (`IDbContextFactory.cs:60-65`): each physical source gets its own transaction, commits are + sequential and best-effort, there is **no** two-phase commit, a failure mid-commit leaves earlier + sources committed, and the outbox is the cross-source consistency mechanism. +- **Walkthrough**: + - **`GetDbContext(DataSourceKey)`** (`IDbContextFactory.cs:16`): the only accessor, documented to + create the context if this scope does not already have one for that source. + - **`EnsureCreatedAsync`** (`:22`), **`MigrateAsync`** (`:81`), **`HasPendingMigrationsAsync`** + (`:87`): schema lifecycle across every source the host uses. The contract states the asymmetry: + `EnsureCreatedAsync` skips sources with no configured connection string (`:18-21`), while the two + migration members cover only the sources a migrations pipeline owns, which is every SQL Server + source plus any SQLite source with a configured `SqliteMigrationsAssembly`; Cosmos and + assembly-less SQLite are created by `EnsureCreatedAsync` instead (`:74-80`). + - **`SaveChangesAsync`** (`:27`) and **`SaveChanges`** (`:32`): save across all active contexts, the + async overload documented as carrying audit stamping and domain-event dispatch. + - **`RequestExplicitKeyInsert`** (`:40`): a one-shot flag for the next save, signalling that it may + include entities carrying explicit values for store-generated keys. The contract is now + engine-neutral (`:34-39`): each context's engine decides whether those inserts need a per-table + toggle (its explicit-key insert dialect, [`IExplicitKeyInsertDialect`](#iexplicitkeyinsertdialect)), + an engine with none saves unchanged, and the flag is cleared once the save completes (see + [`ExplicitKeyInsertGroup`](#explicitkeyinsertgroup)). + - **`BeginTransaction`** / **`CommitTransaction`** / **`RollbackTransaction`** (`:45`, `:50`, `:55`): + applied to every active context that supports transactions. + - **`ExecuteInTransactionAsync`** (`:70-72`): the member handlers actually reach, running + the operation under the active execution strategy so a retrying strategy retries the whole unit. +- **Why it's built this way**: the application layer already talks to + [`IUnitOfWork`](#iunitofwork); this second interface exists so the physical-topology coordination + (which databases, which transactions, which migrations) has a home that Infrastructure can implement + and tests can mock, without leaking EF Core upward. +- **Where it's used**: registered scoped as [`DbContextFactory`](#dbcontextfactory) + (`DependencyInjection.cs:104`). [`UnitOfWork`](#unitofwork) delegates its whole save and transaction + surface to it (`UnitOfWork.cs:70-79`), the startup path resolves it to create, migrate, or verify + databases + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:68`, + `:242`, and per tenant at `:144`), and the background and messaging paths resolve it per scope to + reach a specific source ([`OutboxProcessor`](group-04-events-outbox.md#outboxprocessor) at + `OutboxProcessor.cs:183`, `OutboxCleanupService.cs:100`, `OutboxAdministration.cs:219`, + `EfInboxStore.cs:39`, `InProcessEventBus.cs:34`, `BrokerEventBus.cs:32`, `ScheduledJobRunner.cs:222`, + `AuditTrailReader.cs:35`, `AuditTrailCleanupJob.cs:46`, `EFRefreshSessionStore.cs:31`, and + `RefreshSessionCleanupService.cs:112`). + +### IPhysicalDbContextFactory +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DbContexts.Factory` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/IPhysicalDbContextFactory.cs:15` · Level 12 · interface + +- **What it is**: the contract for constructing a **raw** context for a given physical data source. Its + doc comment states the split precisely: the engine selects the context class (SQL Server, Cosmos, + SQLite) and the source name selects the database (connection string, migrations assembly, EF model), + and contexts created here are neither scoped nor cached (`IPhysicalDbContextFactory.cs:6-13`). +- **Depends on**: [`ApplicationDbContext`](#applicationdbcontext) as the return type, + [`DataSourceKey`](#datasourcekey) as the addressing type, and + [`PhysicalDataSource`](#physicaldatasource) on the second overload + (`IPhysicalDbContextFactory.cs:22`, `:37`). +- **Concept introduced, construction split from lifetime.** `[Rubric §2, Design Patterns]` (assesses + whether patterns are applied where they earn their keep): this is the Abstract Factory half of the + pair. Deciding *which class to new up for which database* is a pure function of the key and needs no + per-request state, so it lives in a singleton; deciding *how long a context lives, when it saves, and + what transaction it is in* is per-request state and lives in the scoped + [`IDbContextFactory`](#idbcontextfactory) layered on top (`IPhysicalDbContextFactory.cs:10-13`). +- **Walkthrough**: two members. `Create(DataSourceKey key)` + (`IPhysicalDbContextFactory.cs:22`) returns a new instance targeting the database the resolver maps + that key to. `Create(DataSourceKey key, PhysicalDataSource physicalDataSource)` + (`IPhysicalDbContextFactory.cs:37`) is the database-per-tenant overload: the caller clones the + resolved [`PhysicalDataSource`](#physicaldatasource) with the tenant's connection string and passes + the **same** key, so EF's model cache still serves one compiled model per (context type, source name) + across every tenant (`IPhysicalDbContextFactory.cs:24-35`). +- **Why it's built this way**: keeping the construction decision behind a two-method interface is what + makes the scoped coordinator testable without a database (`[Rubric §14, Testability]`): the + commit-ambiguity tests hand [`DbContextFactory`](#dbcontextfactory) a + `Mock` that returns a SQLite in-memory context + (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:49-55`). + The two-overload shape is also what keeps tenancy + ([ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html)) a routing decision in + the scoped layer rather than a second factory. +- **Where it's used**: registered singleton as [`PhysicalDbContextFactory`](#physicaldbcontextfactory) + (`DependencyInjection.cs:105`); injected into [`DbContextFactory`](#dbcontextfactory) + (`DbContextFactory.cs:49`), which calls both overloads at `:94-96`. + +### PhysicalDbContextFactory +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DbContexts.Factory` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/PhysicalDbContextFactory.cs:19` · Level 13 · class (sealed) + +- **What it is**: the singleton implementation of + [`IPhysicalDbContextFactory`](#iphysicaldbcontextfactory). It resolves the key's connection + information through [`IDataSourceResolver`](#idatasourceresolver) (or accepts it from the caller) and + asks the key's engine object to construct the matching context (`PhysicalDbContextFactory.cs:25-33`). +- **Depends on**: `IServiceProvider`, [`IDataSourceResolver`](#idatasourceresolver), and + [`IEntityConfigurationAssemblyProvider`](#ientityconfigurationassemblyprovider), all taken through a + primary constructor (`PhysicalDbContextFactory.cs:19-22`), plus + [`DataSourceEngines`](#datasourceengines) and the [`IDataSourceEngine`](#idatasourceengine) it + returns, whose `CreateDbContext` (`IDataSourceEngine.cs:83`) builds the + [`SQLServerDbContext`](#sqlserverdbcontext), [`PostgreSQLDbContext`](#postgresqldbcontext), + [`SqliteDbContext`](#sqlitedbcontext), or [`CosmosDbContext`](#cosmosdbcontext). +- **Concept introduced, the never-pool rule.** `[Rubric §8, Data Architecture]` and + `[Rubric §12, Performance & Scalability]` (which assesses whether performance work is deliberate + rather than reflexive): EF Core's `AddPooledDbContextFactory` is the standard way to avoid + re-allocating contexts, and it is explicitly forbidden here. The class comment says why + (`PhysicalDbContextFactory.cs:13-17`): each instance carries per-source constructor state (its + [`PhysicalDataSource`](#physicaldatasource)), so a pool would hand a context configured for one + database to a caller asking for another and silently point repositories at the wrong database. The + same warning is repeated at the registration site (`MMCA.Common.Infrastructure/DependencyInjection.cs:103-109`), which is where + someone optimizing DI would look first. Under database-per-tenant the rule is load-bearing twice + over, since a pooled context could then cross a tenant boundary rather than only a module one. +- **Walkthrough**: + - **No options of its own**: the static empty `DbContextOptions` objects now live one per engine + class (for example `SQLServerDataSourceEngine.cs:23`, `CosmosDataSourceEngine.cs:22`), each built + once and reused; provider, connection, interceptors, and model cache key are all set inside each + context's `OnConfiguring`, so those options exist only to satisfy the `DbContext` constructor. + - **`Create(DataSourceKey key)`** (`PhysicalDbContextFactory.cs:25`): a one-liner that resolves the + [`PhysicalDataSource`](#physicaldatasource) with `resolver.GetPhysical(key)` and forwards to the + two-argument overload, so the resolver path and the tenant path share one construction switch. + - **`Create(DataSourceKey key, PhysicalDataSource physicalDataSource)`** + (`PhysicalDbContextFactory.cs:28-33`): null-guards the supplied source (`:30`), then returns + `DataSourceEngines.For(key.Engine).CreateDbContext(serviceProvider, assemblyProvider, physical)` + (`:32`). The engine-to-class switch that used to live here is gone: each engine class constructs its + own context with its own options. +- **Why it's built this way**: this is the single point where the two storage axes meet, the `Engine` + axis of [ADR-018](https://ivanball.github.io/docs/adr/018-polyglot-persistence.html) picking the + class and the `Name` axis of [ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html) + picking the database, so adding an engine is a new engine class plus a context class rather than a + change anywhere in application code + ([ADR-130](https://ivanball.github.io/docs/adr/130-per-engine-data-source-strategy.html)). Taking the connection information as a parameter is what let + [ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html) add a third, per-tenant + axis without touching the switch. +- **Where it's used**: registered singleton (`DependencyInjection.cs:105`); + [`DbContextFactory.GetDbContext`](#dbcontextfactory) calls one overload or the other on every cache + miss (`DbContextFactory.cs:105-107`), which is the only first-party call site. + ### DbContextFactory -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DbContexts.Factory` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:46` · Level 13 · class (sealed) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DbContexts.Factory` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:48` · Level 13 · class (sealed) - **What it is**: the scoped implementation of [`IDbContextFactory`](#idbcontextfactory) and the busiest type in this group. It caches one [`ApplicationDbContext`](#applicationdbcontext) per physical [`DataSourceKey`](#datasourcekey) for the life of the scope, coordinates save, transaction, migration, and disposal across all of them, and is also the database-per-tenant routing point - (`DbContextFactory.cs:19-28`). + (`DbContextFactory.cs:21-30`). - **Depends on**: [`IPhysicalDbContextFactory`](#iphysicaldbcontextfactory), [`IEntityDataSourceRegistry`](#ientitydatasourceregistry), [`IDataSourceResolver`](#idatasourceresolver), and [`ICurrentUserService`](group-08-auth.md#icurrentuserservice), all null-guarded from the primary - constructor into readonly fields (`DbContextFactory.cs:46-66`), plus three parameters read directly + constructor into readonly fields (`DbContextFactory.cs:48-68`), plus three parameters read directly off the primary constructor: [`ITenantContext`](group-05-cqrs-pipeline.md#itenantcontext), `IOptions<`[`TenancySettings`](group-07-persistence-ef-core.md#tenancysettings)`>` @@ -5348,7 +6032,7 @@ survives a module being pulled out into its own service. `internal static` members of [`DomainEventSaveChangesInterceptor`](#domaineventsavechangesinterceptor) (`BeginCaptureExclusion`, `EndCaptureExclusion`, `DropDeferred`, `FlushDeferredAsync`, at - `DbContextFactory.cs:333`, `:342`, `:447`, and `:581`). + `DbContextFactory.cs:404`, `:342`, `:447`, and `:581`). - **Concept introduced, coordinating one logical save across several physical databases.** `[Rubric §8, Data Architecture]` (transaction boundaries, unit-of-work scope, per-service isolation) and `[Rubric §12, Performance & Scalability]` (transactions handled once in a shared mechanism, never @@ -5364,22 +6048,22 @@ survives a module being pulled out into its own service. own connection string for a source, and this class is where that declaration turns into a different database. The trick is that only the connection string changes: the [`DataSourceKey`](#datasourcekey) stays the same, which is what EF's model cache is keyed on, so one - compiled model serves every tenant (`DbContextFactory.cs:156-161`). + compiled model serves every tenant (`DbContextFactory.cs:162-167`). - **Walkthrough**: - - **State** (`DbContextFactory.cs:56-93`): `MaxSavePasses` (3, `:52`); `_dbContexts`, the per-scope + - **State** (`DbContextFactory.cs:58-95`): `MaxSavePasses` (3, `:52`); `_dbContexts`, the per-scope `Dictionary` that guarantees every repository in a scope shares one change tracker per database (`:62`); `_routedContextTenants`, recording which tenant each per-tenant-routed context was created for and holding only overridden sources (`:64-69`); - `_transactionActive` (`:75`); the one-shot `_identityInsertRequested` flag (`:82`); and a - `volatile bool _disposed` (`:84`). - - **`GetDbContext(DataSourceKey)`** (`DbContextFactory.cs:96-126`): throws if disposed (`:89`), then + `_transactionActive` (`:86`); the one-shot `_explicitKeyInsertRequested` flag (`:93`); and a + `volatile bool _disposed` (`:95`). + - **`GetDbContext(DataSourceKey)`** (`DbContextFactory.cs:98-132`): throws if disposed (`:89`), then on a cache miss asks `ResolveTenantOverride` for the tenant's own connection information and creates the context through whichever `Create` overload applies (`:93-96`), records the creating tenant when the source was routed (`:100-101`), attaches the scope's accessors (`:103`), and enlists the new context in an already-active transaction (`:107-108`), so a source first touched **inside** a transactional command still shares the boundary. On a cache **hit** it calls - `GuardRoutedTenantUnchanged` (`:113`). - - **`AttachScopeAccessors`** (`DbContextFactory.cs:140-154`, renamed from `AttachTenantAccessor`): + `GuardRoutedTenantUnchanged` (`:124`) and then `GuardSharedContextNotRoutable` (`:128`). + - **`AttachScopeAccessors`** (`DbContextFactory.cs:146-160`, renamed from `AttachTenantAccessor`): hands the context two delegates rather than copied values, because a context can be created before the request's tenant or principal is resolved and both the query filter and the outbox capture must read the answer that holds at query and save time rather than at construction time. It sets @@ -5390,31 +6074,37 @@ survives a module being pulled out into its own service. not once per row, so flattening the role claims costs one pass per save. It null-guards inside rather than at the call site so the null tolerance a mocked physical factory needs does not leak a maybe-null flow state back into the caller. - - **`ResolveTenantOverride`** (`DbContextFactory.cs:162-187`): returns `null` (source stays shared) + - **`ResolveTenantOverride`** (`DbContextFactory.cs:168-193`): returns `null` (source stays shared) unless there is a tenant, bound settings, an entry for that tenant, and an entry for this source name (`:145-151`); otherwise it takes the engine-appropriate connection string through [`TenancySettingsValidator`](group-07-persistence-ef-core.md#tenancysettingsvalidator)`.ConnectionStringFor` (`:153`), still returning `null` when the tenant overrides only a different engine (`:154-158`), and finally clones the shared [`PhysicalDataSource`](#physicaldatasource) with the tenant's connection string and Cosmos database name (`:160-167`). - - **`GuardRoutedTenantUnchanged`** (`DbContextFactory.cs:195-212`): if the cached context was routed + - **`GuardRoutedTenantUnchanged`** (`DbContextFactory.cs:201-218`): if the cached context was routed for a tenant and the scope's tenant has since changed, it throws with both tenant ids named (`:185-192`). The comment states the stakes (`:170-175`): serving that context to a second tenant would read and write the first tenant's data under the second tenant's filter value. - - **`GetSourcesInUse`** (`DbContextFactory.cs:232-233`): the union of every source backing a + - **`GuardSharedContextNotRoutable`** (`DbContextFactory.cs:226`): the mirror-image guard. A context + created **shared** (because a repository call ran before the tenant resolved) is refused once the + scope's tenant turns out to override that source: unless the context was itself routed or + `ResolveTenantOverride` now returns `null`, it throws an `InvalidOperationException` telling the + caller to resolve the tenant before the first repository call or use a fresh scope. Serving it + would read and write the shared database for a tenant that owns its own. + - **`GetSourcesInUse`** (`DbContextFactory.cs:257-258`): the union of every source backing a registered entity (from [`IEntityDataSourceRegistry`](#ientitydatasourceregistry)) and every source already materialized in this scope, which is what `EnsureCreatedAsync` (`:196-207`) and `GetMigrationTargets` (`:705-723`) iterate. `EnsureCreatedAsync` skips sources with an empty connection string (`:200-203`). - - **`GetMigrationTargets`** (`DbContextFactory.cs:724-742`): the shared filter behind `MigrateAsync` + - **`GetMigrationTargets`** (`DbContextFactory.cs:782-795`): the shared filter behind `MigrateAsync` (`:686-690`) and `HasPendingMigrationsAsync` (`:726-735`). It keeps a source only when its resolved [`PhysicalDataSource`](#physicaldatasource)`.UsesMigrations` is true (`:713-714`), then skips a non-SQL-Server target whose connection string is empty (`:716-717`). The asymmetry is deliberate and documented (`:697-702`): an optional SQLite source a test host leaves unconfigured stays silently absent, while a SQL Server source with no connection string still fails loudly at startup, because for SQL Server that is a misconfiguration rather than an option. - - **`SaveChangesAsync`** (`DbContextFactory.cs:244-292`): reads and immediately clears the - identity-insert flag (`:227-228`), then loops at most `MaxSavePasses` times over the contexts it + - **`SaveChangesAsync`** (`DbContextFactory.cs:270-318`): reads and immediately clears the + explicit-key-insert flag (`:272`), then loops at most `MaxSavePasses` times over the contexts it has not yet saved (`:237-251`), passing `_currentUserService.UserId` into each context's audit-aware `SaveChangesAsync` overload (`:247-249`). The re-loop exists because saving dispatches domain events in-process, and a handler that resolves a repository for a source nobody had touched @@ -5424,18 +6114,25 @@ survives a module being pulled out into its own service. silently lost. The comment at `:253-258` explains why the assertion reads the change tracker rather than the saved set: it must catch both a context materialized past the pass bound and a handler that dirtied an already-saved context. - - **`SaveChanges`** (`DbContextFactory.cs:427-435`): the synchronous path, a single pass over a + - **`SaveChanges`** (`DbContextFactory.cs:443-451`): the synchronous path, a single pass over a snapshot of the cached contexts with no re-loop. - - **Identity-insert path** (`DbContextFactory.cs:295`, `:284-403`): covered in - [`IdentityInsertGroup`](#identityinsertgroup) above. - - **`BeginTransaction` / `CommitTransaction` / `RollbackTransaction`** (`DbContextFactory.cs:437-467`): + - **Explicit-key insert path** (`DbContextFactory.cs:292-294`, `:329-440`): a context takes it only + when the flag is set AND `context.Engine.ExplicitKeyInsert` returns an + [`IExplicitKeyInsertDialect`](#iexplicitkeyinsertdialect) (SQL Server's `SET IDENTITY_INSERT`); + every other engine saves normally. `SaveWithExplicitKeyInsertAsync` (`:329`) asks the dialect for + the [`ExplicitKeyInsertGroup`](#explicitkeyinsertgroup)s and opens the connection itself when it is + not already open, because the toggle is session state and without an ambient transaction EF would + otherwise return the connection to the pool between the toggle and the `INSERT`; an + already-open connection is left as found. `SaveExplicitKeyGroupsAsync` (`:377`) then saves one + table per round with the toggle SQL built by `dialect.BuildToggleSql`. + - **`BeginTransaction` / `CommitTransaction` / `RollbackTransaction`** (`DbContextFactory.cs:453-483`): each filters to contexts that support transactions and, symmetrically, to those that do or do not already carry one (`:426`, `:433`, `:440`), because EF throws on a second `BeginTransaction` for the same connection and `GetDbContext` may already have enlisted a late-created context (`:422-425`). Rollback additionally calls `DomainEventSaveChangesInterceptor.DropDeferred` on every context (`:446-447`): the aggregate changes and their outbox rows just rolled back, so the deferred in-process dispatch must never run, and must not survive into a retry. - - **`ExecuteInTransactionAsync`** (`DbContextFactory.cs:518-563`): re-entrancy first, a + - **`ExecuteInTransactionAsync`** (`DbContextFactory.cs:534-579`): re-entrancy first, a nested call simply runs the operation on the ambient transaction (`:510-511`), because an inner commit would make the outer scope's earlier work durable ahead of its own decision (`:503-509`). Otherwise it picks the execution strategy from the first transaction-capable context, materializing @@ -5443,7 +6140,7 @@ survives a module being pulled out into its own service. rather than taken literally so a host with no SQL Server connection does not open a connection string that does not exist, `:513-518`), and runs the attempt under `strategy.ExecuteAsync` (`:526-534`), calling `ResetForRetry` before every attempt after the first (`:528-529`). - - **`RunTransactionalAttemptAsync`** (`DbContextFactory.cs:573-628`): one attempt, begin to commit. + - **`RunTransactionalAttemptAsync`** (`DbContextFactory.cs:589-647`): one attempt, begin to commit. A failed [`Result`](group-01-result-error-handling.md#result) rolls back and returns (`:563-570`), which is what makes [ADR-013](https://ivanball.github.io/docs/adr/013-result-pattern.html)'s Result-over-exceptions @@ -5452,7 +6149,7 @@ survives a module being pulled out into its own service. still materialize a new source (`:576-583`). A cancellation attempts a best-effort rollback and, if even that throws, clears the flag and drops every deferred dispatch by hand (`:587-603`); any other exception rolls back and rethrows (`:604-608`). - - **`TryCommit`** (`DbContextFactory.cs:640-672`) and **`AbandonAfterCommitFailure`** (`:662-683`): a + - **`TryCommit`** (`DbContextFactory.cs:697-729`) and **`AbandonAfterCommitFailure`** (`:662-683`): a commit failure is **returned, not thrown** (`:646`). `TryCommit` snapshots the enlisted contexts first, because that order *is* the commit order (`:625-630`), then commits them one by one, accumulating the successes; on a throw it names the failing source, treats everything past it in the @@ -5461,13 +6158,13 @@ survives a module being pulled out into its own service. `AbandonAfterCommitFailure` rolls back whatever has not committed yet, drops every deferred dispatch, and swallows secondary rollback failures so the commit ambiguity stays the reported failure (`:666-682`). - - **`ResetForRetry`** (`DbContextFactory.cs:761-768`): before the strategy re-runs the operation it + - **`ResetForRetry`** (`DbContextFactory.cs:814-821`): before the strategy re-runs the operation it drops deferred dispatch and calls `ChangeTracker.Clear()` on every context, so entities the aborted attempt added are not inserted a second time (with a duplicate outbox row per event). - - **`SupportsTransactions`** (`DbContextFactory.cs:774-775`): `context is not CosmosDbContext`, - the single place the Cosmos "no multi-document transactions" fact is encoded; - **`HasActiveTransaction`** (`:758-759`) is `Database.CurrentTransaction is not null`. - - **Disposal** (`DbContextFactory.cs:780-810`): `Dispose` and `DisposeAsync` both dispose every + - **`SupportsTransactions`** (`DbContextFactory.cs:827-828`): `context.Engine.Capabilities.IsRelational`, + so the Cosmos "no multi-document transactions" fact is read off the engine object rather than + a type test; **`HasActiveTransaction`** (`:830-831`) is `Database.CurrentTransaction is not null`. + - **Disposal** (`DbContextFactory.cs:833-863`): `Dispose` and `DisposeAsync` both dispose every cached context, clear the dictionary, set `_disposed`, and suppress finalization. - **Why it's built this way**: [ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html) makes "one scope, several databases" the normal case, so somebody has to own the cross-context @@ -5482,63 +6179,15 @@ survives a module being pulled out into its own service. section). Directly instantiated in tests, for example `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:55` and `:195`; the save-loop invariants are pinned by `DbContextFactorySaveIntegrityTests` - (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:81`, + (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:82`, `:101`, `:120`), which assert that a handler mutating an already-saved context throws naming that context, that an extra read-only context does not, and that a clean scope returns the written count. - **Caveats / not-in-source**: the `MaxSavePasses` bound of 3 is documented as "two passes cover the - realistic case, the third is slack" (`DbContextFactory.cs:56-60`); whether any production workload + realistic case, the third is slack" (`DbContextFactory.cs:58-62`); whether any production workload has ever needed the third pass is Not determinable from source. The routed-tenant guard also implies a usage rule the code can only enforce after the fact: a scope serves one tenant, and switching tenants means a fresh scope (`:185-192`). -### PhysicalDbContextFactory -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DbContexts.Factory` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/PhysicalDbContextFactory.cs:16` · Level 13 · class (sealed) - -- **What it is**: the singleton implementation of - [`IPhysicalDbContextFactory`](#iphysicaldbcontextfactory). It resolves the key's connection - information through [`IDataSourceResolver`](#idatasourceresolver) (or accepts it from the caller) and - constructs the matching engine context directly with `new` (`PhysicalDbContextFactory.cs:37-52`). -- **Depends on**: `IServiceProvider`, [`IDataSourceResolver`](#idatasourceresolver), and - [`IEntityConfigurationAssemblyProvider`](#ientityconfigurationassemblyprovider), all taken through a - primary constructor (`PhysicalDbContextFactory.cs:16-19`), plus the four context classes - [`SQLServerDbContext`](#sqlserverdbcontext), [`PostgreSQLDbContext`](#postgresqldbcontext), - [`SqliteDbContext`](#sqlitedbcontext), and [`CosmosDbContext`](#cosmosdbcontext). -- **Concept introduced, the never-pool rule.** `[Rubric §8, Data Architecture]` and - `[Rubric §12, Performance & Scalability]` (which assesses whether performance work is deliberate - rather than reflexive): EF Core's `AddPooledDbContextFactory` is the standard way to avoid - re-allocating contexts, and it is explicitly forbidden here. The class comment says why - (`PhysicalDbContextFactory.cs:10-14`): each instance carries per-source constructor state (its - [`PhysicalDataSource`](#physicaldatasource)), so a pool would hand a context configured for one - database to a caller asking for another and silently point repositories at the wrong database. The - same warning is repeated at the registration site (`MMCA.Common.Infrastructure/DependencyInjection.cs:97-103`), which is where - someone optimizing DI would look first. Under database-per-tenant the rule is load-bearing twice - over, since a pooled context could then cross a tenant boundary rather than only a module one. -- **Walkthrough**: - - **Four static empty options objects** (`PhysicalDbContextFactory.cs:24-38`, one added for - PostgreSQL): one `DbContextOptions` per engine, built once and reused. The comment above them - explains the emptiness: provider, connection, interceptors, and model cache key are all set inside - each context's `OnConfiguring`, so these options exist only to satisfy the `DbContext` constructor - and match what the previous `AddDbContextFactory()` registrations produced. - - **`Create(DataSourceKey key)`** (`PhysicalDbContextFactory.cs:41`): a one-liner that resolves the - [`PhysicalDataSource`](#physicaldatasource) with `resolver.GetPhysical(key)` and forwards to the - two-argument overload, so the resolver path and the tenant path share one construction switch. - - **`Create(DataSourceKey key, PhysicalDataSource physicalDataSource)`** - (`PhysicalDbContextFactory.cs:44-56`): null-guards the supplied source, then switches on - `key.Engine` to construct `SQLServerDbContext`, `PostgreSQLDbContext`, `SqliteDbContext`, or - `CosmosDbContext`, passing options, the service provider, the assembly provider, and the physical - source into each four-argument constructor. An unmapped engine throws an - `InvalidOperationException` naming the offending value. -- **Why it's built this way**: this is the single point where the two storage axes meet, the `Engine` - axis of [ADR-018](https://ivanball.github.io/docs/adr/018-polyglot-persistence.html) picking the - class and the `Name` axis of [ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html) - picking the database, so adding an engine is a new `case` plus a context class rather than a change - anywhere in application code. Taking the connection information as a parameter is what let - [ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html) add a third, per-tenant - axis without touching the switch. -- **Where it's used**: registered singleton (`DependencyInjection.cs:105`); - [`DbContextFactory.GetDbContext`](#dbcontextfactory) calls one overload or the other on every cache - miss (`DbContextFactory.cs:103-105`), which is the only first-party call site. - ### CrossTenantWriteException > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Interceptors` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/CrossTenantWriteException.cs:24` · Level 0 · class (sealed, exception) @@ -5747,7 +6396,7 @@ survives a module being pulled out into its own service. `IdentityModuleSeeder.cs:35-36`), which [`ModuleLoader`](group-14-module-system-composition.md#moduleloader) runs through `SeedAllAsync` at startup, after schema initialization - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:110`). + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:124`). There is no reflection-based discovery of `IDbSeeder` and no hosted service that drains a list of them. @@ -5764,13 +6413,14 @@ survives a module being pulled out into its own service. exactly what a disconnected, later execution needs to look like the scope that scheduled it. - **Walkthrough**: a plain positional `internal readonly record struct` with no members beyond its six constructor parameters. Value semantics are what let it be captured once by - `InternalCommandScheduler.CaptureOrigin` and copied cheaply onto the row. + `InternalCommandOriginCapture.Capture` and copied cheaply onto the row. - **Why it's built this way**: a `record struct` rather than a `class` because it is a short-lived value with no identity of its own, constructed once per scheduled command and read once when the row is built; `internal` because only this project's scheduling and message types touch it. -- **Where it's used**: built by [`InternalCommandScheduler`](#internalcommandscheduler)`.CaptureOrigin` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandScheduler.cs:138-149`) - and consumed by [`InternalCommandMessage`](#internalcommandmessage)`.FromCommand`, which copies its +- **Where it's used**: built by [`InternalCommandOriginCapture`](#internalcommandorigincapture)`.Capture` + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandOriginCapture.cs:28-39`), + which [`InternalCommandScheduler`](#internalcommandscheduler) calls while building the row + (`InternalCommandScheduler.cs:86`), and consumed by [`InternalCommandMessage`](#internalcommandmessage)`.FromCommand`, which copies its five restorable fields onto the row (`InternalCommandMessage.cs:149-154`). ### SeedAccount @@ -5837,7 +6487,7 @@ survives a module being pulled out into its own service. is not a general-purpose id converter) while still allowing every derived seeder to use it without an instance. Determinism, not uniqueness, is the property that matters: seeders must be idempotent across restarts, which is what production hosts rely on when they run the seeder on every boot - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:110`). + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:124`). - **Where it's used**: the base of every module seeder in both apps, for example ADC's [`ConferenceModuleDbSeeder`](group-19-conference-infrastructure.md#conferencemoduledbseeder) (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/DbContexts/Seeding/ConferenceModuleDbSeeder.cs:26`), @@ -5952,7 +6602,7 @@ survives a module being pulled out into its own service. understands the other. - **Where it's used**: added to the caller's `IDbContextFactory` context by [`InternalCommandScheduler`](#internalcommandscheduler)`.ScheduleAsync` - (`InternalCommandScheduler.cs:104-108`), so it commits atomically with the caller's aggregate change + (`InternalCommandScheduler.cs:97-101`), so it commits atomically with the caller's aggregate change inside a transaction, or saves immediately outside one; claimed, executed and stamped by `InternalCommandProcessor` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/InternalCommandProcessor.cs`, @@ -5967,6 +6617,42 @@ survives a module being pulled out into its own service. the outbox's own column of the same name through `AmbientOrigin.MaxRolesLength`); this type itself does not truncate, so an over-length role list is truncated by the caller before `FromCommand` sees it. +### InternalCommandOriginCapture +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.InternalCommands` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandOriginCapture.cs:21` · Level 9 · class (internal sealed) + +- **What it is**: the snapshot step of the internal-command queue: one method, `Capture()`, that reads + the ambient request context a scheduled command must carry onto its row (the scheduling principal and + roles, the tenant, the correlation id, the trace and span ids) and returns it as an + [`InternalCommandOrigin`](#internalcommandorigin) (`InternalCommandOriginCapture.cs:8-12`, `:21-44`). + The `InternalCommandProcessor` restores those values around the deferred execution. +- **Depends on**: `ICurrentUserService`, `ITenantContext` and `ICorrelationContext` through the primary + constructor (`:21-24`); [`InternalCommandOrigin`](#internalcommandorigin) as the value it builds; + `AmbientOrigin.FlattenRoles` for the roles; BCL `System.Diagnostics.Activity` for the trace and span + ids. +- **Concept introduced, capture is scoped because what it reads is scoped.** `[Rubric §13, Observability + & Operability]` assesses whether the identity and correlation of a request survive an asynchronous + hop. The snapshot has to describe the request that is scheduling the command, and all three services + it reads are request-scoped, so the capture is registered **scoped** too + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Jobs.cs:160`, rationale in the + remarks at `:13-17`). Pulling the capture out of the scheduler into its own type gives the snapshot + one owner with one dependency set, so [`InternalCommandScheduler`](#internalcommandscheduler) takes a + single `InternalCommandOriginCapture` argument instead of the three ambient services. +- **Walkthrough**: + - **`Capture()`** (`:32-43`): reads `Activity.Current` once (`:34`), then builds the + `InternalCommandOrigin` from `currentUserService.UserId`, the roles flattened through + `AmbientOrigin.FlattenRoles(currentUserService.Roles)`, `tenantContext.TenantId`, + `correlationContext.CorrelationId`, and the activity's `TraceId`/`SpanId` as strings, or `null` + when no activity is running (`:36-42`). +- **Why it's built this way**: roles go through the shared `AmbientOrigin` helper, which the outbox + capture uses as well, so the outbox hop and the internal-command hop store roles in the same shape + (`:15-16`). The class holds no state of its own; every value comes from the scoped services at the + moment `Capture()` runs. +- **Where it's used**: registered with `TryAddScoped` (`DependencyInjection.Jobs.cs:160`); called once + per scheduled command by [`InternalCommandScheduler`](#internalcommandscheduler) + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandScheduler.cs:86`); + constructed directly by `InternalCommandTestHarness` + (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandTestHarness.cs`). + ### CapturedState > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Interceptors` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/DomainEventSaveChangesInterceptor.cs:389` · Level 10 · record (private sealed, nested) @@ -5979,7 +6665,7 @@ survives a module being pulled out into its own service. [`IDomainEvent`](group-04-events-outbox.md#idomainevent), [`OutboxMessage`](group-04-events-outbox.md#outboxmessage). - **Concept introduced, why per-save state cannot live in a field.** The interceptor is registered - as a **singleton** (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:64`), + as a **singleton** (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:65`), and one singleton serves every context in every scope concurrently. Anything it remembers between `SavingChanges` and `SavedChanges` therefore has to be keyed by the context, not stored on the instance. That is exactly what this record is: the value side of a @@ -6012,16 +6698,21 @@ survives a module being pulled out into its own service. read on the synchronous path by `SavedChanges` (`:127-133`). ### AuditSaveChangesInterceptor -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Interceptors` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:22` · Level 11 · class (sealed) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Interceptors` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:30` · Level 11 · class (sealed) - **What it is**: the EF Core interceptor that stamps `CreatedOn`/`CreatedBy`, `LastModifiedOn`/`LastModifiedBy` and `DeletedOn`/`DeletedBy` on every [`IAuditableEntity`](group-02-domain-building-blocks.md#iauditableentity) entry immediately before - the write (`AuditSaveChangesInterceptor.cs:9-20`). It is the first of the interceptors the - framework installs and the reason no handler anywhere in ADC or Store sets an audit field by hand. -- **Depends on**: [`ApplicationDbContext`](#applicationdbcontext) (for `CurrentSaveUserId` and the - change tracker), [`IAuditableEntity`](group-02-domain-building-blocks.md#iauditableentity), and the - BCL `TimeProvider`, injected through the primary constructor (`:22`). + the write (`AuditSaveChangesInterceptor.cs:10-28`). On PostgreSQL and SQLite it also writes the + `RowVersion` optimistic-concurrency token, because neither engine generates one server-side + (`:21-27`). It is the first of the interceptors the framework installs and the reason no handler + anywhere in ADC or Store sets an audit field by hand. +- **Depends on**: [`ApplicationDbContext`](#applicationdbcontext) (for `CurrentSaveUserId`, the + engine capabilities and the change tracker), + [`IAuditableEntity`](group-02-domain-building-blocks.md#iauditableentity), `IRowVersioned` (only for + the `RowVersion` property name), [`RowVersionStrategy`](#rowversionstrategy) read off + [`DataSourceEngineCapabilities`](#datasourceenginecapabilities), and the BCL `TimeProvider`, injected + through the primary constructor (`:30`). - **Concept introduced, the EF `SaveChangesInterceptor` as the cross-cutting hook of the persistence layer.** `[Rubric §13, Observability & Operability]` assesses whether audit, correlation and logging are wired once centrally rather than repeated per handler. An EF `SaveChangesInterceptor` is a hook EF @@ -6033,60 +6724,72 @@ survives a module being pulled out into its own service. is engaged too, because "every row knows who wrote it and when" is a schema-level guarantee here, not a convention. - **Concept introduced, stamping a transition rather than a value.** The soft-delete stamps are not - driven by what `IsDeleted` *is* but by whether it *changed* during this save (`:14-18`). That + driven by what `IsDeleted` *is* but by whether it *changed* during this save (`:14-20`). That distinction is what makes the delete stamp behave like the creation stamp: it is written once, by the save that flipped the flag, and every later update of the same already-deleted row leaves it untouched. Reading the flag's value instead would rewrite `DeletedOn` on every subsequent write to a deleted row, which destroys the one fact the column exists to record. - **Walkthrough**: - - **`SavingChangesAsync`** (`:25-34`) and **`SavingChanges`** (`:37-45`): identical two-line + - **`SavingChangesAsync`** (`:33-42`) and **`SavingChanges`** (`:45-53`): identical two-line bodies. Each pattern-matches `eventData.Context` against `ApplicationDbContext`, calls `StampAuditFields`, then delegates to `base`. The type test is the guard: a context that is not the framework's own is left completely alone. - - **`StampAuditFields`** (`:47-81`): reads the clock once per save via - `timeProvider.GetUtcNow().UtcDateTime` (`:49`) so every row in one save carries the same instant, - and resolves the user once as `context.CurrentSaveUserId ?? default` (`:50`). `CurrentSaveUserId` + - **`StampAuditFields`** (`:55-92`): reads the clock once per save via + `timeProvider.GetUtcNow().UtcDateTime` (`:57`) so every row in one save carries the same instant, + and resolves the user once as `context.CurrentSaveUserId ?? default` (`:58`). `CurrentSaveUserId` is the nullable user id the context was handed for this save - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:179`, - set at `:156` and `:191`), so `default` is the sentinel for a system-originated write with no - user behind it. - - **The `Added` branch** (`:56-65`): sets all four creation and modification properties through + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:184`, + set at `:197` and `:232`), so `default` is the sentinel for a system-originated write with no + user behind it. It also decides once per save whether this engine needs a client-written row + version: `context.Engine.Capabilities.RowVersion == RowVersionStrategy.ClientStamped` (`:59`), + which is true for the PostgreSQL and SQLite engines + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/PostgreSQLDataSourceEngine.cs:44`, + `SqliteDataSourceEngine.cs:43`; the enum member is at `RowVersionStrategy.cs:17`). + - **The `Added` branch** (`:65-75`): sets all four creation and modification properties through `entry.Property(nameof(...)).CurrentValue`, going through the change tracker rather than the CLR setters so the fields can stay `init`-only or privately settable on the entity, then calls - `StampSoftDeleteTransition` with `wasDeleted: false`. A brand new row has no prior state, so an - entity inserted already soft-deleted still gets its delete stamp (`:62-64`). - - **The `Modified` branch** (`:66-73`): the interesting one. It sets `LastModifiedBy`/ + `StampSoftDeleteTransition` with `wasDeleted: false` and `StampRowVersion`. A brand new row has + no prior state, so an entity inserted already soft-deleted still gets its delete stamp + (`:71-74`). + - **The `Modified` branch** (`:76-84`): the interesting one. It sets `LastModifiedBy`/ `LastModifiedOn`, and explicitly marks `CreatedBy` and `CreatedOn` as `IsModified = false` - (`:67-68`). That is the invariant: an update can never rewrite creation provenance, even if the + (`:77-78`). That is the invariant: an update can never rewrite creation provenance, even if the caller mutated those properties on a tracked instance. It then runs the same soft-delete - transition check, this time against the flag's stored value (`:72`). - - **`Detached`, `Unchanged`, `Deleted` and the default** (`:74-78`): deliberate no-ops. Note what + transition check, this time against the flag's stored value (`:82`), and stamps a fresh row + version (`:83`). + - **`Detached`, `Unchanged`, `Deleted` and the default** (`:85-89`): deliberate no-ops. Note what `Deleted` means here: a **hard** delete, which the framework does not use for auditable entities. A soft delete arrives as `Modified`, because the entity only set a flag (see [ADR-005](https://ivanball.github.io/docs/adr/005-soft-delete-vs-erasure.html) for soft delete versus erasure). - - **`WasDeleted`** (`:84-85`): the flag as the database has it, read from the property's + - **`StampRowVersion`** (`:99-105`): when the engine is client-stamped and the entity's model has a + `RowVersion` property (`entry.Metadata.FindProperty`, `:101`), it writes + `Guid.NewGuid().ToByteArray()` as the property's current value (`:103`). Setting the current value + marks the property modified while EF keeps the loaded value as the original, so the UPDATE still + carries the old token in its WHERE clause and a concurrent writer is detected (`:94-98`). On SQL + Server the `rowversion` column is database-generated and this method writes nothing. + - **`WasDeleted`** (`:108-109`): the flag as the database has it, read from the property's `OriginalValue`. The `is true` test rather than a cast is what keeps an unset or shadow value from throwing. - - **`StampSoftDeleteTransition`** (`:92-106`): compares the current flag against the prior one and - returns immediately when they agree (`:98-102`), so no transition means no write at all. On a + - **`StampSoftDeleteTransition`** (`:116-130`): compares the current flag against the prior one and + returns immediately when they agree (`:123-126`), so no transition means no write at all. On a transition it writes both stamps in one direction: the resolved user and the save's instant on a - delete, and `null` on both columns on an undelete (`:104-105`). + delete, and `null` on both columns on an undelete (`:128-129`). - **Why it's built this way**: taking `TimeProvider` instead of calling `DateTime.UtcNow` makes the stamps deterministic under test (`[Rubric §14, Testability]`), which is what `AuditSaveChangesInterceptorTests` relies on (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/AuditSaveChangesInterceptorTests.cs:13`, - with the four soft-delete transitions pinned at `:128` (delete stamps written), `:144` (an update - after a delete keeps the original stamp), `:165` (undelete clears both), `:186` (an active entity - keeps them null) and `:195` (an insert that is already deleted is stamped)). The class is - registered as a singleton because it holds no per-save state at all - (`DependencyInjection.cs:61-63`), unlike its neighbours. + with the soft-delete transitions pinned at `:149` (delete stamps written), `:165` (an update + after a delete keeps the original stamp), `:186` (undelete clears both), `:207` (an active entity + keeps them null) and `:216` (an insert that is already deleted is stamped), and the client-stamped + row version on SQLite at `:76`). The class is registered as a singleton because it holds no + per-save state at all (`DependencyInjection.cs:64`). - **Where it's used**: resolved from DI and attached in `ApplicationDbContext.OnConfiguring` - (`ApplicationDbContext.cs:292`, added at `:266` or `:270`). It is always **first** in the + (`ApplicationDbContext.cs:297`, added at `:307` or `:311`). It is always **first** in the interceptor chain, which is what lets [`TenantSaveChangesInterceptor`](#tenantsavechangesinterceptor), the domain-event interceptor and the optional [`AuditTrailSaveChangesInterceptor`](#audittrailsavechangesinterceptor) assume the - audit stamps are already final when they run (`ApplicationDbContext.cs:295-299`, `:273-277`). + audit stamps are already final when they run (`ApplicationDbContext.cs:300-304`, `:314-318`). ### DeferredDispatch > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Interceptors` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/DomainEventSaveChangesInterceptor.cs:396` · Level 11 · record (private sealed, nested) @@ -6120,8 +6823,8 @@ survives a module being pulled out into its own service. - **Where it's used**: enqueued by `DispatchAndFinalizeAsync` when `context.Database.CurrentTransaction is not null` (`:308-314`); drained by `FlushDeferredAsync` after a successful commit - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:600`); - discarded wholesale by `DropDeferred` on every rollback path (`DbContextFactory.cs:466`, `:599`, + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:619`); + discarded wholesale by `DropDeferred` on every rollback path (`DbContextFactory.cs:482`, `:599`, `:668`, `:746`). ### DomainEventSaveChangesInterceptor @@ -6168,8 +6871,13 @@ survives a module being pulled out into its own service. monolith running the in-process bus writes no outbox rows at all and dispatches everything directly, while a host on a broker keeps the durable path. A host that resolves no options at all keeps the outbox (`:42-45`, `:55`). This is why the type has two behavioral axes rather than one: - `context.SupportsOutbox` (does this engine have the table) and `_outboxEnabled` (does this host - want it), and both must be true to take the durable branch (`:236`). + `context.Engine.Capabilities.IsRelational` (is this a relational engine, and so one with an outbox + table) and `_outboxEnabled` (does this host want it), and both must be true to take the durable + branch (`:236`). The engine axis is read off the context's + [`DataSourceEngineCapabilities`](#datasourceenginecapabilities): SQL Server, PostgreSQL and SQLite + declare `IsRelational: true`, Cosmos declares `false` + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SQLServerDataSourceEngine.cs:44`, + `PostgreSQLDataSourceEngine.cs:42`, `SqliteDataSourceEngine.cs:41`, `CosmosDataSourceEngine.cs:44`). - **Walkthrough** (this is the densest type in the group; read it as capture, then route, then defer): - **Two instance fields and three static weak tables** (`:53`, `:55`, `:62`, `:69`, `:77`). The @@ -6193,7 +6901,7 @@ survives a module being pulled out into its own service. - It reads the exclusion set (`:219`) and projects the tracked aggregate roots that have events and are not excluded into `AggregateCapture` values (`:221-225`), taking a snapshot copy of each event list, and returns early when nothing carried an event (`:227-228`). - - When `context.SupportsOutbox` and `_outboxEnabled` are both true (`:236`), it reads + - When `context.Engine.Capabilities.IsRelational` and `_outboxEnabled` are both true (`:236`), it reads `context.CurrentOutboxOrigin` **once** into a local (`:247`) rather than per event, then walks the flattened event list (`:248-265`): every event gets `OutboxMessage.FromDomainEvent(domainEvent, origin)` and is added to the outbox set, then the event is sorted. The comment states why a @@ -6222,7 +6930,8 @@ survives a module being pulled out into its own service. source-generated `LogDispatchError` (`:367-368`) and signals the processor so the unprocessed rows get retried (`:339-347`); the `finally` clears the events again, idempotently (`:348-352`). - **`SavedChanges`, the synchronous path** (`:124-138`): it cannot await the dispatcher, so it does - not try. For a host with the outbox on and an outbox-capable context it removes the state, clears + not try. For a host with the outbox on and a context whose engine is relational (the property + pattern `{ Engine.Capabilities.IsRelational: true }` at `:127`) it removes the state, clears the captured events (which is what stops a later async save from re-capturing and duplicating them) and signals the processor, leaving delivery entirely to the outbox. A context without outbox support, and a host running with the outbox off, keep the legacy no-op, because with no @@ -6237,10 +6946,10 @@ survives a module being pulled out into its own service. - **`BeginCaptureExclusion` / `EndCaptureExclusion`** (`:179-188`, `:195`): the narrow hook for `IDENTITY_INSERT` batching. `DbContextFactory` splits a save into one round per identity table and temporarily marks the other tables' entries `Unchanged` - (`DbContextFactory.cs:319-335`); those rows are not written this round, so capturing their events + (`DbContextFactory.cs:388-406`); those rows are not written this round, so capturing their events now would persist and clear an event ahead of the insert that justifies it. The exclusion set is built with `ReferenceEqualityComparer.Instance` (`:187`) and cleared in a `finally` - (`DbContextFactory.cs:359-365`). The remarks explain why exclusion is by instance and not by + (`DbContextFactory.cs:430-435`). The remarks explain why exclusion is by instance and not by entity state (`:172-176`): skipping every `Unchanged` aggregate would also drop events raised on an already-saved aggregate, which is how the identity module publishes its registration events. - **Why it's built this way**: @@ -6251,11 +6960,11 @@ survives a module being pulled out into its own service. decorator honored at the persistence layer: business failures roll back, and a rolled-back save must deliver nothing. The type is `partial` for the source-generated `[LoggerMessage]` (`:367-368`), and singleton-safe because every piece of per-save state lives in a weak table keyed by context. -- **Where it's used**: registered as a singleton (`DependencyInjection.cs:64`), attached last of the - save-time trio in `ApplicationDbContext.OnConfiguring` (`ApplicationDbContext.cs:293`, `:266`, - `:270`) so it sees final audit stamps and final tenant values; driven at transaction boundaries by +- **Where it's used**: registered as a singleton (`DependencyInjection.cs:65`), attached last of the + save-time trio in `ApplicationDbContext.OnConfiguring` (`ApplicationDbContext.cs:298`, `:307`, + `:311`) so it sees final audit stamps and final tenant values; driven at transaction boundaries by [`DbContextFactory`](#dbcontextfactory). Pinned by `DomainEventSaveChangesInterceptorTests` - (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:17`), + (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:19`), `DomainEventSaveChangesInterceptorOutboxRoutingTests` (`.../DomainEventSaveChangesInterceptorOutboxRoutingTests.cs:27`), `DomainEventSaveChangesInterceptorOutboxDisabledTests` (`.../DomainEventSaveChangesInterceptorOutboxDisabledTests.cs:21`) and `DomainEventCaptureExclusionTests` (`.../DomainEventCaptureExclusionTests.cs:26`). @@ -6278,10 +6987,10 @@ survives a module being pulled out into its own service. `[Rubric §11, Security]` assesses whether a boundary holds without caller cooperation. Tenancy in this framework is enforced twice, independently. On reads it is a **named** EF query filter, `"Tenant"`, applied in `ApplicationDbContext.ApplyTenantFilters` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:509-569`), - which composes by AND with the `"SoftDelete"` filter (`ApplicationDbContext.cs:486-494`) and embeds + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:514-574`), + which composes by AND with the `"SoftDelete"` filter (`ApplicationDbContext.cs:491-499`) and embeds the executing context as a constant so one cached model serves every tenant - (`ApplicationDbContext.cs:495-501`, `:435-436`). On writes it is this interceptor. The + (`ApplicationDbContext.cs:500-506`, `:435-436`). On writes it is this interceptor. The independence is the point and is called out in the remarks (`:29-34`): a caller who bypasses the read filter with EF's own parameterless `IgnoreQueryFilters()` can read across tenants, but still cannot write across them. `[Rubric §30, Compliance and Data Governance]` applies for the same @@ -6291,7 +7000,7 @@ survives a module being pulled out into its own service. audit interceptor, both routing to `ApplyTenant`. - **`ApplyTenant`** (`:64-94`): reads `context.CurrentTenantId` **once** per save (`:68`), because that property walks a live accessor into the scoped tenant context - (`ApplicationDbContext.cs:141`) and a save must be judged against a single value. It then + (`ApplicationDbContext.cs:154`) and a save must be judged against a single value. It then enumerates `ChangeTracker.Entries()` filtered by `!entry.Metadata.IsOwned()` (`:74-75`) and switches on state: `Added` to `StampOrVerifyInsert`, `Modified` and `Deleted` to `VerifyExistingRow` with the operation name, everything else a no-op. Owned types are excluded on @@ -6319,15 +7028,15 @@ survives a module being pulled out into its own service. order** (`:15-21`). This one sits deliberately between the audit and domain-event interceptors: the audit stamps are already written when it runs, and the outbox rows the domain-event interceptor adds afterwards describe an entity whose tenant is final. It is also **always registered and inert - by default** (`:22-28`, `DependencyInjection.cs:66-69`): it is a no-op for every entity that does + by default** (`:22-28`, `DependencyInjection.cs:67-70`): it is a no-op for every entity that does not carry `ITenantEntity`, which is every entity in a host that never adopted tenancy, so that host pays nothing while a host that does adopt tenancy can never accidentally leave the write guard off. `OnConfiguring` resolves it with `GetService` rather than `GetRequiredService` - (`ApplicationDbContext.cs:300`) so a directly-constructed test or design-time context still builds. + (`ApplicationDbContext.cs:305`) so a directly-constructed test or design-time context still builds. See [ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html). - **Where it's used**: registered as a singleton in `AddInfrastructure` - (`DependencyInjection.cs:69`) and attached second in the interceptor chain - (`ApplicationDbContext.cs:302`); exercised by `TenantSaveChangesInterceptorTests` + (`DependencyInjection.cs:70`) and attached second in the interceptor chain + (`ApplicationDbContext.cs:307`); exercised by `TenantSaveChangesInterceptorTests` (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/TenantSaveChangesInterceptorTests.cs:12`, including the owned-value carve-out at `:149` and the still-builds-without-it case at `:164`) and by the registration tests in @@ -6337,60 +7046,60 @@ survives a module being pulled out into its own service. is inert until an entity implements `ITenantEntity`. ### InternalCommandScheduler -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.InternalCommands` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandScheduler.cs:39` · Level 13 · class (internal sealed, partial) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.InternalCommands` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandScheduler.cs:34` · Level 13 · class (internal sealed, partial) - **What it is**: the write side of the internal-command queue, `IInternalCommandScheduler`'s implementation: it turns a command into an [`InternalCommandMessage`](#internalcommandmessage) row, enrolls it in the caller's transaction if one is open, and signals the processor when the work is - due immediately (`InternalCommandScheduler.cs:39-164`). + due immediately (`InternalCommandScheduler.cs:34-140`). - **Depends on**: `IDbContextFactory`, `IDataSourceResolver`, `IOptions`, - `ICurrentUserService`, `ITenantContext`, `ICorrelationContext`, `IInternalCommandSignal`, and - `ILogger`, all through the primary constructor (`:39-48`); - [`InternalCommandMessage`](#internalcommandmessage) as the row it builds, and + [`InternalCommandOriginCapture`](#internalcommandorigincapture), `IInternalCommandSignal`, + `ILogger` and `TimeProvider`, all through the primary constructor + (`:34-41`); [`InternalCommandMessage`](#internalcommandmessage) as the row it builds, and [`InternalCommandOrigin`](#internalcommandorigin) as the captured scheduling context it hands to - `FromCommand`; `Result` and `Error` for its return shape; `System.Diagnostics.Activity` for the - trace/span ids. + `FromCommand`; `Result` and `Error` for its return shape. - **Concept introduced, scheduling rides the caller's own save.** `[Rubric §6, CQRS and Event-Driven]` assesses whether deferred work is delivered reliably rather than optimistically, the same lens as the outbox. `ScheduleAsync` never opens a `SaveChanges` of its own when a transaction is already active: it resolves the caller's context through `dbContextFactory.GetDbContext(...)` and `Add`s the row onto - it (`:104-108`), so the queued command commits or rolls back exactly with the aggregate change that - scheduled it. Outside a transaction it saves immediately and signals the processor only when the work - is due now (`:111-130`), because enrolling into an open transaction and signalling immediately would - only buy a query against a row that has not committed yet. + it (`:97-102`), so the queued command commits or rolls back exactly with the aggregate change that + scheduled it. Inside a transaction it neither saves nor signals (`:104-112`): the caller's next save + writes the row, or the transactional pipeline saves it just before the commit when no save follows + (`:106-109`), and signalling then would only buy a query against a row that has not committed yet. + Outside a transaction it saves immediately and always signals the processor (`:116-121`). - **Walkthrough**: - - **`MaxRolesLength`** (`:55`): `internal const int`, aliased to `AmbientOrigin.MaxRolesLength` so the + - **`MaxRolesLength`** (`:48`): `internal const int`, aliased to `AmbientOrigin.MaxRolesLength` so the internal-command queue and the outbox agree on the column width for `UserRoles` without restating the number. - - **`ScheduleAsync(command, delay, cancellationToken)`** (`:64-71`): the delay-based overload, sugar + - **`ScheduleAsync(command, delay, cancellationToken)`** (`:57-64`): the delay-based overload, sugar over the `DateTimeOffset?` overload: a positive delay becomes `_timeProvider.GetUtcNow() + delay`, anything else passes `null` through. - - **`ScheduleAsync(command, runAt, cancellationToken)`** (`:74-131`), the primary path: - - Null-guards the command (`:79-82`). - - Normalizes a past `runAt` to "now" rather than rejecting it (`:88`): a caller computing a + - **`ScheduleAsync(command, runAt, cancellationToken)`** (`:67-125`), the primary path: + - Returns `NullCommandError` (a `Validation` error, `:50-51`) for a null command (`:72-75`). + - Normalizes a past `runAt` to "now" rather than rejecting it (`:81`): a caller computing a deadline that already slipped wants the work done immediately, not an error to handle. - Builds the row via `InternalCommandMessage.FromCommand(command, scheduledOn, now, - CaptureOrigin())` (`:90-102`) inside a `try`/`catch (NotSupportedException)`: `System.Text.Json` - reports an unserializable payload this way, and the catch turns it into a logged failure result - rather than letting it take down the handler that scheduled the command. + originCapture.Capture())` (`:83-95`, the call at `:86`) inside a `try`/`catch + (NotSupportedException)`: `System.Text.Json` reports an unserializable payload this way, and the + catch turns it into a logged failure result rather than letting it take down the handler that + scheduled the command. The origin snapshot itself is + [`InternalCommandOriginCapture`](#internalcommandorigincapture)'s job. - Resolves the context for `_settings.DataSource`/`DatabaseName` through - `dataSourceResolver.ResolveLogical` (`:104-105`) and adds the row with the standard - `VSTHRD103`-suppressed synchronous `Add` (`:108`). - - If `context.Database.CurrentTransaction is not null`, returns immediately without saving or - signalling (`:111-118`): the caller's own commit persists the row, and the processor discovers it - on its next poll. + `dataSourceResolver.ResolveLogical` (`:97-98`) and adds the row with the standard + `VSTHRD103`-suppressed synchronous `Add` (`:100-102`). + - If `context.Database.CurrentTransaction is not null`, logs `LogEnrolled` and returns immediately + without saving or signalling (`:104-112`); the processor discovers the committed row on its next + poll. - Otherwise calls `context.SaveChangesAsync` directly, deliberately **not** through a user id overload, because an `InternalCommandMessage` is neither auditable nor an aggregate root, so - there is nothing for the audit or domain-event interceptors to do here (`:122`); signals the - processor only when `scheduledOn <= now` (`:124-127`). - - **`CaptureOrigin()`** (`:138-149`, `private`): builds the - [`InternalCommandOrigin`](#internalcommandorigin) from `Activity.Current` for the trace/span ids and - from `currentUserService`/`tenantContext`/`correlationContext` for the rest, flattening roles - through the shared `AmbientOrigin.FlattenRoles` helper, the same one the outbox capture uses, so - both hops store roles in the same shape (`:141-144`). - - **`SerializationError(commandType)`** (`:151-154`): the `Error.Failure` factory for the + there is nothing for the audit or domain-event interceptors to do here (`:114-116`). It then + signals the processor whether or not the row is due yet (`:118-121`): a due row runs on the + wake, and a not-yet-due row costs one fetch but re-arms the processor's smart wait on its + `ScheduledOn`, which the processor would otherwise only learn at the next polling interval and + so run the command late. + - **`SerializationError(commandType)`** (`:127-130`): the `Error.Failure` factory for the not-serializable branch. - - **`LogEnrolled`, `LogScheduled`, `LogSerializationFailed`** (`:156-163`): source-generated + - **`LogEnrolled`, `LogScheduled`, `LogSerializationFailed`** (`:132-139`): source-generated `[LoggerMessage]` partials at Debug, Debug, and Error respectively. - **Why it's built this way**: `internal sealed partial` because it is the one implementation of `IInternalCommandScheduler` in the framework and callers depend on the interface, not the class; the @@ -6398,15 +7107,14 @@ survives a module being pulled out into its own service. [`DeferredDispatch`](#deferreddispatch) enforces for domain events, applied here to a queued command instead of an in-process dispatch. See [ADR-114](https://ivanball.github.io/docs/adr/114-internal-commands-durable-job-queue.html). -- **Where it's used**: registered in DI (`DependencyInjection.cs`, 1 reference); the entry point every - caller of `IInternalCommandScheduler.ScheduleAsync` goes through. Pinned by - `InternalCommandSchedulerTests` - (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandSchedulerTests.cs`) - and exercised through `InternalCommandTestHarness`. -- **Caveats / not-in-source**: `NullCommandError` returns a `Result.Failure` for a null command rather - than throwing, which is the one path in this type that does not match the null-guard-and-throw shape - used everywhere else in the class; that asymmetry is in the source as written and not explained - further in a comment. +- **Where it's used**: registered in DI (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Jobs.cs`, + 1 reference); the entry point every caller of `IInternalCommandScheduler.ScheduleAsync` goes through. + Pinned by `InternalCommandSchedulerTests` + (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandSchedulerTests.cs`), + exercised through `InternalCommandTestHarness`, and referenced by `DbContextFactoryTransactionTests`. +- **Caveats / not-in-source**: a null command returns `Result.Failure` with a `Validation` error + (`:50-51`, `:72-75`) rather than throwing `ArgumentNullException`; the source does not explain the + choice in a comment. ### IdentityModuleDbSeederBase > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DbContexts.Seeding` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Seeding/IdentityModuleDbSeederBase.cs:39` · Level 14 · class (abstract) @@ -6495,11 +7203,11 @@ survives a module being pulled out into its own service. > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/IInternalCommandSignal.cs:9` · Level 0 · interface - **What it is**: a wake-up abstraction that lets code schedule an internal command and immediately nudge the processor, instead of the processor relying only on a fixed polling interval. -- **Depends on**: nothing first-party; the contract is `Task WaitAsync(TimeSpan, CancellationToken)` plus a synchronous `Signal()` (`IInternalCommandSignal.cs:27,37`). -- **Concept introduced, the signal-based smart-wait pattern for the internal-commands job queue.** `[Rubric §12: Performance & Scalability]` (assesses whether the system avoids wasted work and unnecessary latency): a plain fixed-interval poll trades latency for idle cost either way, while a signal lets a processor sleep long and still react immediately when work shows up. The doc comment on `WaitAsync` (`IInternalCommandSignal.cs:30-37`) states its intended caller directly: `InternalCommandProcessor`, "in place of a plain polling delay." -- **Walkthrough**: `Signal()` (`IInternalCommandSignal.cs:27-28`) marks that due work is available; `WaitAsync(timeout, cancellationToken)` (`IInternalCommandSignal.cs:37`) waits for a signal or until `timeout` elapses, whichever comes first. +- **Depends on**: nothing first-party; the contract is `Task WaitAsync(TimeSpan, CancellationToken)` plus a synchronous `Signal()` (`IInternalCommandSignal.cs:12,21`). +- **Concept introduced, the signal-based smart-wait pattern for the internal-commands job queue.** `[Rubric §12: Performance & Scalability]` (assesses whether the system avoids wasted work and unnecessary latency): a plain fixed-interval poll trades latency for idle cost either way, while a signal lets a processor sleep long and still react immediately when work shows up. The doc comment on `WaitAsync` (`IInternalCommandSignal.cs:14-17`) states its intended caller directly: `InternalCommandProcessor`, "in place of a plain polling delay." +- **Walkthrough**: `Signal()` (`IInternalCommandSignal.cs:11-12`) marks that due work is available; `WaitAsync(timeout, cancellationToken)` (`IInternalCommandSignal.cs:21`) waits for a signal or until `timeout` elapses, whichever comes first. - **Why it's built this way**: an interface, not a concrete `SemaphoreSlim` dependency, so the processor, scheduler, and administration surface can share one wake mechanism through DI and a test harness can substitute a controllable fake. -- **Where it's used**: implemented by [`InternalCommandSignal`](#internalcommandsignal); consumed by `InternalCommandProcessor` (the wait loop), `InternalCommandScheduler`, and [`InternalCommandAdministration`](#internalcommandadministration) (it signals after a successful requeue, `InternalCommandAdministration.cs:756`); registered in `DependencyInjection.cs`. +- **Where it's used**: implemented by [`InternalCommandSignal`](#internalcommandsignal); consumed by `InternalCommandProcessor` (the wait loop), `InternalCommandScheduler`, and [`InternalCommandAdministration`](#internalcommandadministration) (it signals after a successful requeue, `InternalCommandAdministration.cs:192`); registered in `DependencyInjection.cs`. ### InternalCommandCycleResult > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/InternalCommandCycleResult.cs:17` · Level 0 · record struct @@ -6509,7 +7217,7 @@ survives a module being pulled out into its own service. - **Concept**: a plain readonly record struct used purely to avoid tuple sprawl across the processor's internal methods; no new pattern to teach beyond the record-struct convention used elsewhere in the framework. - **Walkthrough**: one line, two positional members: `HasMoreDueWork` drives whether [`InternalCommandProcessor`](#internalcommandprocessor) re-polls immediately; `EarliestUpcoming` feeds `ComputeWaitTime`. - **Why it's built this way**: `internal readonly record struct` keeps the per-cycle result on the stack rather than the heap, since it is produced and consumed once per poll cycle in a loop that can run continuously for the life of the host. -- **Where it's used**: returned by `InternalCommandProcessor.ProcessDueCommandsAsync` (`InternalCommandProcessor.cs:1103,1137`) and its private per-target helper, then read by `ExecuteAsync`'s wait loop. +- **Where it's used**: returned by `InternalCommandProcessor.ProcessDueCommandsAsync` (`InternalCommandProcessor.cs:125,140`) and its private per-target helper, then read by `ExecuteAsync`'s wait loop. ### InternalCommandsDisabledNoticeService > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandsDisabledNoticeService.cs:20` · Level 0 · class @@ -6517,8 +7225,8 @@ survives a module being pulled out into its own service. - **What it is**: an `IHostedService` that logs a single informational notice on startup for a host that writes `InternalCommands` rows but runs neither [`InternalCommandProcessor`](#internalcommandprocessor) nor [`InternalCommandCleanupService`](#internalcommandcleanupservice) (`InternalCommandsSettings.Enabled == false`). - **Depends on**: `ILogger`, the `[LoggerMessage]` source-generator pattern. - **Concept**: a "notice service" idiom that pairs a settings toggle with a one-time explanatory log line rather than staying silent about a deliberate configuration choice. `[Rubric §13: Observability & Operability]` (assesses whether an operator can tell WHY the system behaves a certain way): a replica that never drains its own queue is easy to mistake for a bug without this notice. -- **Walkthrough**: `StartAsync` (`InternalCommandsDisabledNoticeService.cs:83-87`) calls the generated `LogQueueDisabled` (`InternalCommandsDisabledNoticeService.cs:92-95`) once and returns; `StopAsync` (`InternalCommandsDisabledNoticeService.cs:90`) is a no-op. -- **Why it's built this way**: the log message itself (`InternalCommandsDisabledNoticeService.cs:94`) states the operational fact directly: scheduled work is executed only by a host that has `InternalCommands:Enabled=true` against the same databases, and no migration is needed to turn the queue on later because the `InternalCommands` table is already part of the model. +- **Walkthrough**: `StartAsync` (`InternalCommandsDisabledNoticeService.cs:24-28`) calls the generated `LogQueueDisabled` (`InternalCommandsDisabledNoticeService.cs:33-36`) once and returns; `StopAsync` (`InternalCommandsDisabledNoticeService.cs:31`) is a no-op. +- **Why it's built this way**: the log message itself (`InternalCommandsDisabledNoticeService.cs:35`) states the operational fact directly: scheduled work is executed only by a host that has `InternalCommands:Enabled=true` against the same databases, and no migration is needed to turn the queue on later because the `InternalCommands` table is already part of the model. - **Where it's used**: registered in `DependencyInjection.cs` (1 site), presumably conditioned on `InternalCommandsSettings.Enabled` being `false`. ### WakeUpSignal @@ -6527,7 +7235,7 @@ survives a module being pulled out into its own service. - **What it is**: the shared wake-up primitive behind both queue signals: an internal, single-slot `SemaphoreSlim(0, 1)` wrapper used purely as a flag, not a resource lock. - **Depends on**: `System.Threading.SemaphoreSlim`, `IDisposable`. - **Concept**: the semaphore is capped at one permit because a drain cycle empties a whole batch per pass, so a burst of signals carries no more information than a single one; extracting it here lets both queue signals share the exact same wake-up mechanics instead of each owning a copy. -- **Walkthrough**: `_semaphore = new(0, 1)` (`WakeUpSignal.cs:27`); `Signal()` (`WakeUpSignal.cs:30-40`) releases the semaphore and swallows `SemaphoreFullException`, because a wake-up already pending means "one batch drains everything: nothing to add"; `WaitAsync(timeout, cancellationToken)` (`WakeUpSignal.cs:46-56`) awaits the semaphore with the given timeout and re-throws `OperationCanceledException` only when the token itself requested cancellation, so shutdown propagates but a plain timeout returns normally; `Dispose()` (`WakeUpSignal.cs:59`) disposes the semaphore. +- **Walkthrough**: `_semaphore = new(0, 1)` (`WakeUpSignal.cs:27`); `Signal()` (`WakeUpSignal.cs:30-40`) releases the semaphore and swallows `SemaphoreFullException`, because a wake-up already pending means "one batch drains everything: nothing to add"; `WaitAsync(timeout, cancellationToken)` (`WakeUpSignal.cs:37-47`) awaits the semaphore with the given timeout and re-throws `OperationCanceledException` only when the token itself requested cancellation, so shutdown propagates but a plain timeout returns normally; `Dispose()` (`WakeUpSignal.cs:50`) disposes the semaphore. - **Why it's built this way**: pulling this out of [`InternalCommandSignal`](#internalcommandsignal) means the internal-commands queue and the outbox share one tested wake-up implementation instead of two independent copies of the same semaphore dance. - **Where it's used**: wrapped by [`InternalCommandSignal`](#internalcommandsignal) (2 sites) and by `OutboxSignal` (2 sites, `MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxSignal.cs`). @@ -6536,9 +7244,9 @@ survives a module being pulled out into its own service. - **What it is**: the static home of the OpenTelemetry `Meter` and every instrument the internal-commands job queue publishes: processed/failed/dead-letter counters, execution duration and scheduling-lag histograms, and pending-depth / oldest-due-age gauges. - **Depends on**: `System.Diagnostics.Metrics` (`Meter`, `Counter`, `Histogram`, `ObservableGauge`), `ConcurrentDictionary`. -- **Concept introduced, per-instance observable gauges for a replicated background worker.** `[Rubric §13: Observability & Operability]`: the doc remarks on `PendingDepthGauge` (`InternalCommandMetrics.cs:183-190`) spell out that each replica reports only what IT last observed, never a cluster total, because the poll predicate already excludes rows another replica currently holds under lease; a source whose database is unreachable contributes zero for that cycle instead of a stale value. -- **Walkthrough**: `MeterName` constant (`InternalCommandMetrics.cs:119`, `"MMCA.Common.InternalCommands"`) backs a single `Meter` (line 121); `ProcessedCounter`/`FailedCounter`/`DeadLetterCounter` (`InternalCommandMetrics.cs:137-159`) are tagged by `command_type` (and `reason` for the latter two); `DurationHistogram` and `LagHistogram` (`InternalCommandMetrics.cs:165-177`) measure execution time and scheduling lag in seconds; `PendingDepthGauge` (`InternalCommandMetrics.cs:191-195`) is backed by `_pendingDepth` (line 127), read via `Interlocked.Read`; `OldestDueAgeGauge` (`InternalCommandMetrics.cs:203-207`) is backed by a `ConcurrentDictionary` keyed by `data_source` (lines 133-134) and observed through `ObserveOldestDueAge` (`InternalCommandMetrics.cs:219-227`); `SetPendingDepth` (line 211) and `SetOldestDueAge` (lines 216-217) are the only writers, both called from [`InternalCommandProcessor`](#internalcommandprocessor). -- **Why it's built this way**: the counter/histogram/gauge split mirrors the questions an operator actually asks: throughput and failure rate (counters), how long work takes and how late it starts (histograms), and how deep and how stale the backlog currently is (gauges); the doc comment on `OldestDueAgeGauge` (`InternalCommandMetrics.cs:197-202`) explains it is the signal a "wedged queue" alert fires on, distinct from the lag histogram which only reports rows that DID run. +- **Concept introduced, per-instance observable gauges for a replicated background worker.** `[Rubric §13: Observability & Operability]`: the doc remarks on `PendingDepthGauge` (`InternalCommandMetrics.cs:84-91`) spell out that each replica reports only what IT last observed, never a cluster total, because the poll predicate already excludes rows another replica currently holds under lease; a source whose database is unreachable contributes zero for that cycle instead of a stale value. +- **Walkthrough**: `MeterName` constant (`InternalCommandMetrics.cs:20`, `"MMCA.Common.InternalCommands"`) backs a single `Meter` (line 22); `ProcessedCounter`/`FailedCounter`/`DeadLetterCounter` (`InternalCommandMetrics.cs:38-60`) are tagged by `command_type` (and `reason` for the latter two); `DurationHistogram` and `LagHistogram` (`InternalCommandMetrics.cs:66-78`) measure execution time and scheduling lag in seconds; `PendingDepthGauge` (`InternalCommandMetrics.cs:92-96`) is backed by `_pendingDepth` (line 28), read via `Interlocked.Read`; `OldestDueAgeGauge` (`InternalCommandMetrics.cs:104-108`) is backed by a `ConcurrentDictionary` keyed by `data_source` (lines 34-35) and observed through `ObserveOldestDueAge` (`InternalCommandMetrics.cs:120-128`); `SetPendingDepth` (line 112) and `SetOldestDueAge` (lines 117-118) are the only writers, both called from [`InternalCommandProcessor`](#internalcommandprocessor). +- **Why it's built this way**: the counter/histogram/gauge split mirrors the questions an operator actually asks: throughput and failure rate (counters), how long work takes and how late it starts (histograms), and how deep and how stale the backlog currently is (gauges); the doc comment on `OldestDueAgeGauge` (`InternalCommandMetrics.cs:98-103`) explains it is the signal a "wedged queue" alert fires on, distinct from the lag histogram which only reports rows that DID run. - **Where it's used**: `InternalCommandProcessor.cs` (8 sites, one poll cycle at a time); `MMCA.Common.Aspire`'s `OutboxPollFilterProcessor.cs` (1 site) reads `MeterName` to filter the paired suppressed telemetry. - **ADRs**: [ADR-041](https://ivanball.github.io/docs/adr/041-observability-and-telemetry.html), [ADR-114](https://ivanball.github.io/docs/adr/114-internal-commands-durable-job-queue.html). @@ -6547,9 +7255,9 @@ survives a module being pulled out into its own service. - **What it is**: a static, cached resolver between an `IInternalCommand` CLR type and the string a queued row stores for it, honoring an optional declared-name attribute override in both directions. - **Depends on**: `System.Reflection` (`Type.GetCustomAttribute`, `AppDomain.GetAssemblies`), `ConcurrentDictionary`, the first-party `InternalCommandNameAttribute` (not in this group). -- **Concept**: reflection-with-caching for a hot path. `[Rubric §12: Performance & Scalability]`: the doc comment on `DeclaredNameCache` (`InternalCommandNameResolver.cs:250-254`) is explicit that caching the `null` (no-attribute) case too keeps the common unannotated command to one reflection lookup per type per process, rather than one per scheduled row. -- **Walkthrough**: `GetDeclaredName` (`InternalCommandNameResolver.cs:264-267`) reads `InternalCommandNameAttribute` with `inherit: false` (a derived command cannot silently borrow its base's identity) via `GetOrAdd`; `GetStorageName` (`InternalCommandNameResolver.cs:276-280`) returns the declared name if present, else falls back through `AssemblyQualifiedName` → `FullName` → `Name`, the same fallback chain the outbox uses; `FindTypeByDeclaredName` (`InternalCommandNameResolver.cs:295-301`) is the reverse lookup for a stored name that is not a CLR type name: it lazily scans loaded, non-dynamic assemblies and checks `IsDefined` before `GetDeclaredName`, deliberately in that order, so only the handful of annotated types pay for constructing the attribute; `GetLoadableTypes` (`InternalCommandNameResolver.cs:310-320`) degrades to the types that DID load on a `ReflectionTypeLoadException`, so one bad type in an assembly cannot abort the scan. -- **Why it's built this way**: the lazy `Where`/`SelectMany`/`FirstOrDefault` chain stops at the first match instead of materializing every loaded type (`InternalCommandNameResolver.cs:288-292`), which matters because this reverse path is reached at most once per stored name (the caller, `InternalCommandMessage.ResolveCommandType`, caches the result). +- **Concept**: reflection-with-caching for a hot path. `[Rubric §12: Performance & Scalability]`: the doc comment on `DeclaredNameCache` (`InternalCommandNameResolver.cs:21-26`) is explicit that caching the `null` (no-attribute) case too keeps the common unannotated command to one reflection lookup per type per process, rather than one per scheduled row. +- **Walkthrough**: `GetDeclaredName` (`InternalCommandNameResolver.cs:35-38`) reads `InternalCommandNameAttribute` with `inherit: false` (a derived command cannot silently borrow its base's identity) via `GetOrAdd`; `GetStorageName` (`InternalCommandNameResolver.cs:47-51`) returns the declared name if present, else falls back through `AssemblyQualifiedName` → `FullName` → `Name`, the same fallback chain the outbox uses; `FindTypeByDeclaredName` (`InternalCommandNameResolver.cs:66-72`) is the reverse lookup for a stored name that is not a CLR type name: it lazily scans loaded, non-dynamic assemblies and checks `IsDefined` before `GetDeclaredName`, deliberately in that order, so only the handful of annotated types pay for constructing the attribute; `GetLoadableTypes` (`InternalCommandNameResolver.cs:81-91`) degrades to the types that DID load on a `ReflectionTypeLoadException`, so one bad type in an assembly cannot abort the scan. +- **Why it's built this way**: the lazy `Where`/`SelectMany`/`FirstOrDefault` chain stops at the first match instead of materializing every loaded type (`InternalCommandNameResolver.cs:59-63`), which matters because this reverse path is reached at most once per stored name (the caller, `InternalCommandMessage.ResolveCommandType`, caches the result). - **Where it's used**: `InternalCommandMessage.cs` (2 sites, plus 1 more). ### InternalCommandSignal @@ -6568,9 +7276,9 @@ survives a module being pulled out into its own service. - **What it is**: the bound options class for the `"InternalCommands"` configuration section: it controls enablement, batching, retry backoff, claim leasing, retention, and which data source new work is scheduled against. - **Depends on**: [`DataSource`](group-07-persistence-ef-core.md#datasource) and [`DataSourceKey`](group-07-persistence-ef-core.md#datasourcekey), `System.ComponentModel.DataAnnotations` `[Range]` validation. - **Concept**: the options pattern used throughout the framework, here tuned deliberately DIFFERENT from the outbox's own settings in two places the doc comments call out explicitly. `[Rubric §13: Observability & Operability]` assesses whether operational knobs are documented well enough to tune safely; every property here carries a rationale comment, not just a default. -- **Walkthrough**: `SectionName` (`InternalCommandsSettings.cs:397`, `"InternalCommands"`); `Enabled` (`InternalCommandsSettings.cs:406`, default `true`, the outbox's own posture: a host that schedules work must drain it); `BatchSize` (`InternalCommandsSettings.cs:409-410`, `[Range(1,1000)]`, default `50`); `MaxAttempts` (`InternalCommandsSettings.cs:417-418`, `[Range(1,20)]`, default `5`, a `Result.Failure` counts as an attempt exactly like a thrown exception); `PollingIntervalSeconds` (`InternalCommandsSettings.cs:427-428`, `[Range(1,3600)]`, default `2`, the fallback bound behind the smart wait); `ProcessingDelaySeconds` (`InternalCommandsSettings.cs:436-437`, `[Range(0,600)]`, default `0`: UNLIKE the outbox's `5`, because the outbox delay exists to bound a race with an in-process fast path that the job queue does not have); `LeaseSeconds` (`InternalCommandsSettings.cs:446-447`, `[Range(10,3600)]`, default `300`, how long one replica's claim on a row lasts before another replica may reclaim it); `RetryBackoffBaseSeconds` (`InternalCommandsSettings.cs:455-456`, `[Range(1,3600)]`, default `10`, attempt `n` waits `base * 2^(n-1)` with `[0.8, 1.2]` jitter); `MaxRetryBackoffSeconds` (`InternalCommandsSettings.cs:464-465`, `[Range(1,86400)]`, default `600`, independent of `LeaseSeconds` unlike the outbox, because a job queue wants a long lease for slow handlers but a short backoff ceiling); `RetentionDays` (`InternalCommandsSettings.cs:471-472`, `[Range(0,3650)]`, default `7`, `0` keeps completed rows forever); `DeadLetterRetentionDays` (`InternalCommandsSettings.cs:479-480`, `[Range(0,3650)]`, default `0` falls back to `RetentionDays`); `CleanupIntervalHours` (`InternalCommandsSettings.cs:486-487`, `[Range(1,168)]`, default `6`); `DataSource` (`InternalCommandsSettings.cs:495`, default `DataSource.SQLServer`, must be relational: Cosmos has no `InternalCommands` table); `DatabaseName` (`InternalCommandsSettings.cs:503`, default `DataSourceKey.DefaultName`). +- **Walkthrough**: `SectionName` (`InternalCommandsSettings.cs:18`, `"InternalCommands"`); `Enabled` (`InternalCommandsSettings.cs:27`, default `true`, the outbox's own posture: a host that schedules work must drain it, and a write-only replica sets it `false`); `BatchSize` (`InternalCommandsSettings.cs:30-31`, `[Range(1,1000)]`, default `50`); `MaxAttempts` (`InternalCommandsSettings.cs:38-39`, `[Range(1,20)]`, default `5`, a `Result.Failure` counts as an attempt exactly like a thrown exception); `PollingIntervalSeconds` (`InternalCommandsSettings.cs:48-49`, `[Range(1,3600)]`, default `2`, the fallback bound behind the smart wait, which is what bounds a row scheduled inside a transaction because that row raises no signal); `ProcessingDelaySeconds` (`InternalCommandsSettings.cs:57-58`, `[Range(0,600)]`, default `0`: UNLIKE the outbox's `5`, because the outbox delay exists to bound a race with an in-process fast path that the job queue does not have); `LeaseSeconds` (`InternalCommandsSettings.cs:68-69`, `[Range(10,3600)]`, default `300`, how long one replica's claim on a row lasts before another replica may reclaim it; the lease is renewed before each row of a claimed batch runs, so it bounds one handler, not the whole batch, per the doc comment at `InternalCommandsSettings.cs:65-66`); `RetryBackoffBaseSeconds` (`InternalCommandsSettings.cs:77-78`, `[Range(1,3600)]`, default `10`, attempt `n` waits `base * 2^(n-1)` with `[0.8, 1.2]` jitter, then capped); `MaxRetryBackoffSeconds` (`InternalCommandsSettings.cs:86-87`, `[Range(1,86400)]`, default `600`, independent of `LeaseSeconds` unlike the outbox, because a job queue wants a long lease for slow handlers but a short backoff ceiling); `RetentionDays` (`InternalCommandsSettings.cs:93-94`, `[Range(0,3650)]`, default `7`, `0` keeps completed rows forever); `DeadLetterRetentionDays` (`InternalCommandsSettings.cs:101-102`, `[Range(0,3650)]`, default `0` falls back to `RetentionDays`); `CleanupIntervalHours` (`InternalCommandsSettings.cs:108-109`, `[Range(1,168)]`, default `6`); `DataSource` (`InternalCommandsSettings.cs:117`, default `DataSource.SQLServer`, must be relational: Cosmos has no `InternalCommands` table, though the processor drains every relational source in use, not only this one); `DatabaseName` (`InternalCommandsSettings.cs:125`, default `DataSourceKey.DefaultName`, pointed at the source holding the aggregates that schedule work so the row and the aggregate change share one transaction). - **Why it's built this way**: every divergence from the outbox's settings is a deliberate consequence of the job queue having no in-process fast path to race against, documented inline rather than left implicit. -- **Where it's used**: read via `IOptions` across `DependencyInjection.cs` (5 sites), [`InternalCommandCleanupService`](#internalcommandcleanupservice) (4), `InternalCommandScheduler` (4), the test harness (3), [`InternalCommandAdministration`](#internalcommandadministration) (2), [`InternalCommandProcessor`](#internalcommandprocessor) (2), and one cleanup-service test. +- **Where it's used**: read via `IOptions` across `DependencyInjection.Jobs.cs` (5 sites), [`InternalCommandCleanupService`](#internalcommandcleanupservice) (4), `InternalCommandScheduler` (4), the test harness (3), [`InternalCommandAdministration`](#internalcommandadministration) (2), [`InternalCommandProcessor`](#internalcommandprocessor) (2), `DataSourceBranchingFitnessTests` (1), and one cleanup-service test. - **ADRs**: [ADR-114](https://ivanball.github.io/docs/adr/114-internal-commands-durable-job-queue.html). ### PollingLoop @@ -6578,8 +7286,8 @@ survives a module being pulled out into its own service. - **What it is**: the smart-wait polling loop extracted out of the outbox and internal-commands processors so both share one implementation: run cycles until cancelled, re-poll immediately when a cycle reports more work, otherwise wait on a signal bounded by a computed smart wait; plus the retry-backoff and per-source fan-out helpers both processors also share. - **Depends on**: `System.TimeProvider`, [`TenantDataSourceTarget`](group-07-persistence-ef-core.md#tenantdatasourcetarget). -- **Concept**: this is the smart-wait loop, backoff formula, and per-target fan-out originally described under `InternalCommandProcessor`/`OutboxProcessor`, now factored into one shared static class so the two processors cannot drift out of sync on the same shape. `[Rubric §12: Performance & Scalability]` still applies here directly: `ComputeWaitTime` is what turns a fixed poll into a smart wait, and `ComputeRetryBackoffSeconds`'s jitter (`PollingLoop.cs:358-360`) is what keeps a batch that failed together from retrying in lockstep against the same dependency. -- **Walkthrough**: `StartupDelay` (`PollingLoop.cs:24`, 5 seconds) and `MinimumWait` (`PollingLoop.cs:27`, 1 second) are the shared constants; `RunAsync` (`PollingLoop.cs:46-97`) waits `StartupDelay`, returns via `onNoTargets` when `hasTargets()` is false, then loops calling `runCycle`, re-polling immediately on `HasMoreWork` and otherwise awaiting `waitForSignal` for `ComputeWaitTime`'s result, with a cycle exception reported through `onCycleError` and treated as no pending work; `ComputeWaitTime` (`PollingLoop.cs:117-133`) returns the fallback `pollingInterval` when `earliestUpcoming` is null, otherwise `earliestUpcoming + processingDelay - utcNow`, floored at `MinimumWait` and capped at `pollingInterval`; `DrainAllAsync` (`PollingLoop.cs:148-184`, targets a generic `List` plus a `drainSource` delegate) aggregates `HasMoreWork` (OR), `EarliestUpcoming` (min) and a summed `PendingDepth` across targets, isolating one source's exception via `onSourceError` from the others; `ComputeRetryBackoffSeconds` (`PollingLoop.cs:196-210`) computes `base * 2^(attempts-1)`, clamps the exponent at `16` before `Math.Pow` (`PollingLoop.cs:201-203`, so a future settings change cannot overflow it), applies `[0.8, 1.2]` jitter BEFORE the cap so a capped backoff lands exactly at the ceiling, then caps at `capSeconds`. +- **Concept**: this is the smart-wait loop, backoff formula, and per-target fan-out originally described under `InternalCommandProcessor`/`OutboxProcessor`, now factored into one shared static class so the two processors cannot drift out of sync on the same shape. `[Rubric §12: Performance & Scalability]` still applies here directly: `ComputeWaitTime` is what turns a fixed poll into a smart wait, and `ComputeRetryBackoffSeconds`'s jitter (`PollingLoop.cs:193`) is what keeps a batch that failed together from retrying in lockstep against the same dependency. +- **Walkthrough**: `StartupDelay` (`PollingLoop.cs:18`, 5 seconds) and `MinimumWait` (`PollingLoop.cs:21`, 1 second) are the shared constants; `RunAsync` (`PollingLoop.cs:40-91`) waits `StartupDelay`, returns via `onNoTargets` when `hasTargets()` is false, then loops calling `runCycle`, re-polling immediately on `HasMoreWork` and otherwise awaiting `waitForSignal` for `ComputeWaitTime`'s result, with a cycle exception reported through `onCycleError` and treated as no pending work; `ComputeWaitTime` (`PollingLoop.cs:104-122`) returns the fallback `pollingInterval` when `earliestUpcoming` is null, otherwise `earliestUpcoming + processingDelay - utcNow`, floored at `MinimumWait` and capped at `pollingInterval`; `DrainAllAsync` (`PollingLoop.cs:135-171`, takes an `IReadOnlyList` (`PollingLoop.cs:136`) plus a `drainSource` delegate) aggregates `HasMoreWork` (OR), `EarliestUpcoming` (min) and a summed `PendingDepth` across targets, isolating one source's exception via `onSourceError` from the others; `ComputeRetryBackoffSeconds` (`PollingLoop.cs:183-197`) computes `base * 2^(attempts-1)`, clamps the exponent at `16` before `Math.Pow` (`PollingLoop.cs:188`, so a future settings change cannot overflow it), applies `[0.8, 1.2]` jitter BEFORE the cap so a capped backoff lands exactly at the ceiling, then caps at `capSeconds`. - **Why it's built this way**: the two callers (internal-commands, outbox) had nearly identical loop, wait-time, drain, and backoff code; extracting it here is a straight de-duplication, not a new abstraction over a different concept, so each caller keeps its own settings, signal, and per-source work delegate and passes them in as parameters. - **Where it's used**: `OutboxProcessor.cs` (7 sites) and [`InternalCommandProcessor`](#internalcommandprocessor) (`InternalCommandProcessor.cs`, 5 sites). @@ -6588,97 +7296,97 @@ survives a module being pulled out into its own service. - **What it is**: a static dispatcher that resolves and invokes the decorated `ICommandHandler` for a deserialized `IInternalCommand`, without the caller needing compile-time knowledge of the command's concrete type. - **Depends on**: `ICommandHandler` and `IInternalCommand` (Application layer, not in this group), `IServiceProvider`, `System.Reflection.MethodInfo`, `ConcurrentDictionary`. -- **Concept introduced, a compiled-reflection invoker cache.** `[Rubric §12: Performance & Scalability]`: building one delegate per command type costs a single `MakeGenericMethod` + `CreateDelegate` (`InternalCommandDispatcher.cs:526-530`); every subsequent row of that type calls the cached delegate directly instead of paying `MakeGenericMethod` or `MethodInfo.Invoke` again. -- **Walkthrough**: `Invokers` (`InternalCommandDispatcher.cs:531`) caches one `Func>` per command `Type`; `InvokeDefinition` (`InternalCommandDispatcher.cs:534-536`) reflectively resolves the private static generic `InvokeAsync` by `nameof`, guarded by an inline `#pragma warning disable S3011` explaining the target is this same class's own private method, so a rename cannot silently break it (`InternalCommandDispatcher.cs:533`); `ExecuteAsync` (`InternalCommandDispatcher.cs:552-559`) is the public entry point: `Invokers.GetOrAdd(command.GetType(), BuildInvoker)` then invokes; `BuildInvoker` (`InternalCommandDispatcher.cs:561-565`) does the one-time `MakeGenericMethod(commandType).CreateDelegate<...>()`; `InvokeAsync` (`InternalCommandDispatcher.cs:572-585`) resolves the handler via `GetService`, NOT `GetRequiredService` (line 578), so a command with no registered handler on this host resolves to `null` rather than throwing. -- **Why it's built this way**: the doc comment on `ExecuteAsync` (`InternalCommandDispatcher.cs:546-551`) states the rationale for the null-not-throw choice directly: a missing handler is a deployment fact (the owning module is disabled here, or the row was written by a different service), and the caller records that fact on the row instead of treating it as an exception to classify. +- **Concept introduced, a compiled-reflection invoker cache.** `[Rubric §12: Performance & Scalability]`: building one delegate per command type costs a single `MakeGenericMethod` + `CreateDelegate` (`InternalCommandDispatcher.cs:23-27`); every subsequent row of that type calls the cached delegate directly instead of paying `MakeGenericMethod` or `MethodInfo.Invoke` again. +- **Walkthrough**: `Invokers` (`InternalCommandDispatcher.cs:28`) caches one `Func>` per command `Type`; `InvokeDefinition` (`InternalCommandDispatcher.cs:31-33`) reflectively resolves the private static generic `InvokeAsync` by `nameof`, guarded by an inline `#pragma warning disable S3011` explaining the target is this same class's own private method, so a rename cannot silently break it (`InternalCommandDispatcher.cs:30`); `ExecuteAsync` (`InternalCommandDispatcher.cs:49-56`) is the public entry point: `Invokers.GetOrAdd(command.GetType(), BuildInvoker)` then invokes; `BuildInvoker` (`InternalCommandDispatcher.cs:58-62`) does the one-time `MakeGenericMethod(commandType).CreateDelegate<...>()`; `InvokeAsync` (`InternalCommandDispatcher.cs:69-82`) resolves the handler via `GetService`, NOT `GetRequiredService` (line 75), so a command with no registered handler on this host resolves to `null` rather than throwing. +- **Why it's built this way**: the doc comment on `ExecuteAsync` (`InternalCommandDispatcher.cs:43-48`) states the rationale for the null-not-throw choice directly: a missing handler is a deployment fact (the owning module is disabled here, or the row was written by a different service), and the caller records that fact on the row instead of treating it as an exception to classify. - **Where it's used**: [`InternalCommandProcessor`](#internalcommandprocessor) (`InternalCommandProcessor.cs`, 1 site) calls `ExecuteAsync` when it executes a claimed row. - **ADRs**: [ADR-114](https://ivanball.github.io/docs/adr/114-internal-commands-durable-job-queue.html). ### InternalCommandAdministration -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandAdministration.cs:37` · Level 13 · class +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandAdministration.cs:34` · Level 16 · class - **What it is**: the operator-facing administration surface for the internal-commands queue: count pending work, list and paginate dead letters, requeue them, and purge completed rows, fanned out across every relational data source (and tenant) this host owns. -- **Depends on**: `IServiceScopeFactory`, `IOptions` ([`InternalCommandsSettings`](#internalcommandssettings)), `IEntityDataSourceRegistry`, `IDataSourceResolver`, [`IInternalCommandSignal`](#iinternalcommandsignal), `TimeProvider`, `IOptions`, [`ApplicationDbContext`](group-07-persistence-ef-core.md#applicationdbcontext), `InternalCommandMessage`, [`TenantDataSourceTarget`](group-07-persistence-ef-core.md#tenantdatasourcetarget) / [`TenantDataSourceTargets`](group-07-persistence-ef-core.md#tenantdatasourcetargets), `Error`/`Result` (Domain layer). +- **Depends on**: `IServiceScopeFactory`, `IOptions` ([`InternalCommandsSettings`](#internalcommandssettings)), [`FrameworkTableTargets`](group-07-persistence-ef-core.md#frameworktabletargets), [`IInternalCommandSignal`](#iinternalcommandsignal), `TimeProvider`, [`IDbContextFactory`](group-07-persistence-ef-core.md#idbcontextfactory), [`ApplicationDbContext`](group-07-persistence-ef-core.md#applicationdbcontext), `InternalCommandMessage`, [`TenantDataSourceTarget`](group-07-persistence-ef-core.md#tenantdatasourcetarget), `Error`/`Result` (Domain layer). - **Concept**: the same operator-lever shape as [`IOutboxAdministration`](group-07-persistence-ef-core.md#ioutboxadministration): count / list-dead-letters / requeue / purge, fanned out per owned data source. `[Rubric §8: Data Architecture]` (assesses how a system handles database-per-service and multi-tenant fan-out): every operation here resolves its own target set rather than assuming a single database. `[Rubric §13: Observability & Operability]`: requeue and purge are explicit, logged operator actions, not silent background behavior. -- **Walkthrough**: `MaxPageSize` (`InternalCommandAdministration.cs:471`, `500`) bounds one page so an admin call cannot pull the whole table; `SkipError`/`TakeError`/`OlderThanError` (`InternalCommandAdministration.cs:473-480`) are pre-built `Error.Validation` instances; `CountPendingAsync` (`InternalCommandAdministration.cs:486-507`) sums a `LongCountAsync` per target using the SAME pending predicate as the processor's own poll; `ListDeadLettersAsync` (`InternalCommandAdministration.cs:510-566`) paginates across the MERGED result of every target: each target returns at most `skip + take` rows (ordered by `ScheduledOn` then `Id`), because "skip 50" must mean the same thing whether this host owns one database or four, then the merged list is re-ordered and re-paged in memory (`InternalCommandAdministration.cs:560-563`); `RequeueAsync` (`InternalCommandAdministration.cs:569-624`) resets `Attempts` to `0` and clears `DeadLetteredOn`/`ClaimedUntil`/`ClaimedBy` via `ExecuteUpdateAsync` (`InternalCommandAdministration.cs:599-605`) while deliberately preserving `LastError` ("the only evidence of WHY this row needed requeuing"), then calls `signal.Signal()` (`InternalCommandAdministration.cs:620`) to wake the processor immediately instead of waiting for the next poll; `PurgeProcessedAsync` (`InternalCommandAdministration.cs:627-662`) `ExecuteDeleteAsync`s processed rows older than a caller-supplied cutoff; `SelectTargets` (`InternalCommandAdministration.cs:674-686`) resolves its target list through the shared `TenantDataSourceTargets.ExpandRelational` helper (also used by [`InternalCommandCleanupService`](#internalcommandcleanupservice) and [`InternalCommandProcessor`](#internalcommandprocessor)), optionally narrowed to one name; `VisitAsync` (`InternalCommandAdministration.cs:693-704`) opens a fresh DI scope via the shared `scopeFactory.CreateTenantScope(target)` extension, which sets the tenant BEFORE the `IDbContextFactory` context is resolved, because the tenant is both what routes the scoped factory to the right database and what the soft-delete/tenant query filter reads; the constructor's `timeProvider` parameter is now a plain required parameter rather than an optional one defaulting to `TimeProvider.System` (`InternalCommandAdministration.cs:467,483`). -- **Why it's built this way**: the tenant-before-context ordering and the deliberate `LastError` preservation are both stated inline as design decisions, not incidental; `LogRequeued`/`LogPurged` (`InternalCommandAdministration.cs:706-710`) give operators a durable audit trail of manual interventions on the queue. -- **Where it's used**: registered in `DependencyInjection.cs` (1 site); referenced from `ApplicationDbContext.cs` (1); exercised by `InternalCommandAdministrationTests.cs` (4); `SoftDeleteEnforcementTests` in both `MMCA.Common.Architecture.Tests` and `MMCA.ADC.Architecture.Tests` (1 each) check its hard `ExecuteDeleteAsync`/`ExecuteUpdateAsync` calls against the restrict-delete-by-default convention. +- **Walkthrough**: the primary constructor (`InternalCommandAdministration.cs:34-40`) takes six required parameters, with target selection delegated to an injected `FrameworkTableTargets` rather than a registry, a resolver and optional tenancy options; `MaxPageSize` (`InternalCommandAdministration.cs:43`, `500`) bounds one page so an admin call cannot pull the whole table; `SkipError`/`TakeError`/`OlderThanError` (`InternalCommandAdministration.cs:45-52`) are pre-built `Error.Validation` instances; every operation returns `Error.NotFoundError` from `UnknownSourceError` (`InternalCommandAdministration.cs:236-239`) when the requested source name matches no owned target; `CountPendingAsync` (`InternalCommandAdministration.cs:58-79`) sums a `LongCountAsync` per target using the processor's pending predicate (`ProcessedOn` and `DeadLetteredOn` null, `Attempts < MaxAttempts`); `ListDeadLettersAsync` (`InternalCommandAdministration.cs:82-138`) paginates across the MERGED result of every target: each target returns at most `skip + take` rows (ordered by `ScheduledOn` then `Id`), because "skip 50" must mean the same thing whether this host owns one database or four, then the merged list is re-ordered and re-paged in memory (`InternalCommandAdministration.cs:132-135`); `RequeueAsync` (`InternalCommandAdministration.cs:141-196`) resets `Attempts` to `0` and clears `DeadLetteredOn`/`ClaimedUntil`/`ClaimedBy` via `ExecuteUpdateAsync` (`InternalCommandAdministration.cs:171-177`) while deliberately preserving `LastError` ("the only evidence of WHY this row needed requeuing"), then calls `signal.Signal()` (`InternalCommandAdministration.cs:192`) to wake the processor immediately instead of waiting for the next poll; `PurgeProcessedAsync` (`InternalCommandAdministration.cs:199-234`) `ExecuteDeleteAsync`s processed rows at or before a caller-supplied cutoff; `SelectTargets` (`InternalCommandAdministration.cs:246-247`) is a one-line call to `tableTargets.Named(DataSource, DatabaseName, dataSource)`, recomputed per call because module assemblies can register entities after startup, and optionally narrowed to one name; `VisitAsync` (`InternalCommandAdministration.cs:254-265`) opens a fresh DI scope via the shared `scopeFactory.CreateTenantScope(target)` extension (`InternalCommandAdministration.cs:261`), which sets the tenant BEFORE the `IDbContextFactory` context is resolved, because the tenant is both what routes the scoped factory to the right database and what the query filter reads. +- **Why it's built this way**: the tenant-before-context ordering and the deliberate `LastError` preservation are both stated inline as design decisions, not incidental; `LogRequeued`/`LogPurged` (`InternalCommandAdministration.cs:267-271`) give operators a durable audit trail of manual interventions on the queue. +- **Where it's used**: registered in `DependencyInjection.Jobs.cs` (1 site); referenced from `ApplicationDbContext.cs` (1); exercised by `InternalCommandAdministrationTests.cs` (4); `SoftDeleteEnforcementTests` in both `MMCA.Common.Architecture.Tests` and `MMCA.ADC.Architecture.Tests` (1 each) check its hard `ExecuteDeleteAsync`/`ExecuteUpdateAsync` calls against the restrict-delete-by-default convention. - **ADRs**: [ADR-119](https://ivanball.github.io/docs/adr/119-restrict-delete-by-default.html). ### InternalCommandCleanupService -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandCleanupService.cs:44` · Level 13 · class +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandCleanupService.cs:40` · Level 16 · class - **What it is**: a [`PeriodicBackgroundService`](group-14-module-system-composition.md#periodicbackgroundservice) that periodically purges completed and dead-lettered `InternalCommands` rows past their retention windows, across every relational data source (and tenant) this host owns. -- **Depends on**: [`PeriodicBackgroundService`](group-14-module-system-composition.md#periodicbackgroundservice), `IServiceScopeFactory`, `IOptions` ([`InternalCommandsSettings`](#internalcommandssettings)), `IEntityDataSourceRegistry`, `IDataSourceResolver`, `TimeProvider`, `IOptions`, [`ApplicationDbContext`](group-07-persistence-ef-core.md#applicationdbcontext), `InternalCommandMessage`, [`TenantDataSourceTarget`](group-07-persistence-ef-core.md#tenantdatasourcetarget) / [`TenantDataSourceTargets`](group-07-persistence-ef-core.md#tenantdatasourcetargets). +- **Depends on**: [`PeriodicBackgroundService`](group-14-module-system-composition.md#periodicbackgroundservice), `IServiceScopeFactory`, `IOptions` ([`InternalCommandsSettings`](#internalcommandssettings)), [`FrameworkTableTargets`](group-07-persistence-ef-core.md#frameworktabletargets), `TimeProvider`, [`IDbContextFactory`](group-07-persistence-ef-core.md#idbcontextfactory), [`ApplicationDbContext`](group-07-persistence-ef-core.md#applicationdbcontext), `InternalCommandMessage`, [`TenantDataSourceTarget`](group-07-persistence-ef-core.md#tenantdatasourcetarget). - **Concept**: the same "sweep every owned target, isolate one source's failure from the rest" shape as [`InternalCommandAdministration`](#internalcommandadministration)'s own purge path; cross-reference rather than re-teach the tenant-before-context ordering. `[Rubric §29: Resilience & Business Continuity]`: an unreachable database on one source does not stop the sweep of the others. The hour-scale loop mechanics themselves (interval wait, cancellation, error logging) now come from the shared `PeriodicBackgroundService` base rather than being hand-rolled here, so this section covers only what this type overrides. -- **Walkthrough**: the type now extends `PeriodicBackgroundService(timeProvider, logger)` (`InternalCommandCleanupService.cs:44-52`) instead of `BackgroundService` directly; it overrides `Interval` (`InternalCommandCleanupService.cs:55`, `TimeSpan.FromHours(CleanupIntervalHours)`), `StartupDelay` (`InternalCommandCleanupService.cs:57-61`, equal to `Interval`, so the first sweep still waits one full interval before competing with startup or migration work), `IsEnabled` (`InternalCommandCleanupService.cs:64-76`, `false` and logs `LogCleanupDisabled` when `RetentionDays <= 0`), `ExecuteCycleAsync` (`InternalCommandCleanupService.cs:79`, forwards straight to `PurgeAsync`), and `LogCycleFailure` (`InternalCommandCleanupService.cs:82`, forwards to `LogCleanupError`); `PurgeAsync` (`InternalCommandCleanupService.cs:89-133`, `internal` so a test can drive one sweep without advancing the hour-scale timer) computes `cutoff = now - RetentionDays` and a separate `deadLetterCutoff` using `DeadLetterRetentionDays` when set, else `RetentionDays` (`InternalCommandCleanupService.cs:91-97`); for each target from `GetTargets` (`InternalCommandCleanupService.cs:160-166`, now built through the shared `TenantDataSourceTargets.ExpandRelational` helper rather than its own inline source-resolution logic) it opens a scope via `scopeFactory.CreateTenantScope(target)` (`InternalCommandCleanupService.cs:105`, sets the tenant before the context is resolved), resolves the `ApplicationDbContext`, `ExecuteDeleteAsync`s processed rows past `cutoff` (`InternalCommandCleanupService.cs:110-113`, `LogPurged`), then calls `SweepDeadLettersAsync` (`InternalCommandCleanupService.cs:139-154`) which deletes rows keyed on `DeadLetteredOn < deadLetterCutoff` (`LogDeadLetterPurged`); a per-source exception is caught and logged via `LogSourcePurgeError` rather than aborting the whole sweep. +- **Walkthrough**: the type extends `PeriodicBackgroundService(timeProvider, logger)` (`InternalCommandCleanupService.cs:40-46`) and takes five required constructor parameters, with target selection delegated to an injected `FrameworkTableTargets`; it overrides `Interval` (`InternalCommandCleanupService.cs:55`, `TimeSpan.FromHours(CleanupIntervalHours)`), `StartupDelay` (`InternalCommandCleanupService.cs:61`, equal to `Interval`, so the first sweep waits one full interval before competing with startup or migration work), `IsEnabled` (`InternalCommandCleanupService.cs:64-76`, `false` and logs `LogCleanupDisabled` when `RetentionDays <= 0`), `ExecuteCycleAsync` (`InternalCommandCleanupService.cs:79`, forwards straight to `PurgeAsync`), and `LogCycleFailure` (`InternalCommandCleanupService.cs:82`, forwards to `LogCleanupError`); `PurgeAsync` (`InternalCommandCleanupService.cs:89-133`, `internal` so a test can drive one sweep without advancing the hour-scale timer) computes `cutoff = now - RetentionDays` and a separate `deadLetterCutoff` using `DeadLetterRetentionDays` when set, else `RetentionDays` (`InternalCommandCleanupService.cs:91-97`); for each target from `GetTargets` (`InternalCommandCleanupService.cs:160-161`, a one-line call to `tableTargets.Relational(DataSource, DatabaseName)`, the same set the processor drains) it opens a scope via `scopeFactory.CreateTenantScope(target)` (`InternalCommandCleanupService.cs:105`, sets the tenant before the context is resolved), resolves the `ApplicationDbContext` through `IDbContextFactory.GetDbContext(target.Source)`, `ExecuteDeleteAsync`s processed rows past `cutoff` (`InternalCommandCleanupService.cs:110-113`, `LogPurged`), then calls `SweepDeadLettersAsync` (`InternalCommandCleanupService.cs:139-154`) which deletes rows keyed on `DeadLetteredOn < deadLetterCutoff` (`LogDeadLetterPurged`); a per-source exception is caught and logged via `LogSourcePurgeError` rather than aborting the whole sweep. - **Why it's built this way**: keying the dead-letter sweep on `DeadLetteredOn` rather than a completion stamp reflects that a dead-lettered row never got one; the independent `DeadLetterRetentionDays` window lets operators keep abandoned commands around longer for diagnosis than ordinary completed ones; moving onto `PeriodicBackgroundService` retires a hand-rolled interval loop that was nearly identical to the outbox and internal-commands processors' own loops. -- **Where it's used**: registered as a hosted service alongside [`InternalCommandProcessor`](#internalcommandprocessor) in `DependencyInjection.cs` (1 site); its retention posture is what [`InternalCommandsDisabledNoticeService`](#internalcommandsdisablednoticeservice)'s log message calls out as the second job a `Enabled=false` host does not run; exercised directly by `InternalCommandCleanupServiceTests.cs` (4). +- **Where it's used**: registered as a hosted service alongside [`InternalCommandProcessor`](#internalcommandprocessor) in `DependencyInjection.Jobs.cs` (1 site); its retention posture is what [`InternalCommandsDisabledNoticeService`](#internalcommandsdisablednoticeservice)'s log message calls out as the second job a `Enabled=false` host does not run; exercised directly by `InternalCommandCleanupServiceTests.cs` (4). - **ADRs**: [ADR-114](https://ivanball.github.io/docs/adr/114-internal-commands-durable-job-queue.html). ### InternalCommandProcessor -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/InternalCommandProcessor.cs:46` · Level 13 · class +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/InternalCommandProcessor.cs:43` · Level 16 · class - **What it is**: the `BackgroundService` at the center of the internal-commands job queue: it drains due rows across every relational data source (and tenant) this host owns, executes each via [`InternalCommandDispatcher`](#internalcommanddispatcher), and records the outcome, using a smart wait instead of a fixed polling interval. -- **Depends on**: `BackgroundService`, [`PollingLoop`](#pollingloop), `IServiceScopeFactory`, `IOptions` ([`InternalCommandsSettings`](#internalcommandssettings)), [`IInternalCommandSignal`](#iinternalcommandsignal), `IEntityDataSourceRegistry`, `IDataSourceResolver`, `TimeProvider`, `IOptions`, [`InternalCommandCycleResult`](#internalcommandcycleresult), [`InternalCommandMetrics`](#internalcommandmetrics), [`InternalCommandDispatcher`](#internalcommanddispatcher), `InternalCommandMessage`, [`TenantDataSourceTarget`](group-07-persistence-ef-core.md#tenantdatasourcetarget) / [`TenantDataSourceTargets`](group-07-persistence-ef-core.md#tenantdatasourcetargets), [`ApplicationDbContext`](group-07-persistence-ef-core.md#applicationdbcontext), `System.Diagnostics.ActivitySource`. +- **Depends on**: `BackgroundService`, [`PollingLoop`](#pollingloop), `IServiceScopeFactory`, `IOptions` ([`InternalCommandsSettings`](#internalcommandssettings)), [`IInternalCommandSignal`](#iinternalcommandsignal), [`FrameworkTableTargets`](group-07-persistence-ef-core.md#frameworktabletargets), `TimeProvider`, [`InternalCommandCycleResult`](#internalcommandcycleresult), [`InternalCommandMetrics`](#internalcommandmetrics), [`InternalCommandDispatcher`](#internalcommanddispatcher), `InternalCommandMessage`, [`TenantDataSourceTarget`](group-07-persistence-ef-core.md#tenantdatasourcetarget), [`IDbContextFactory`](group-07-persistence-ef-core.md#idbcontextfactory), [`ApplicationDbContext`](group-07-persistence-ef-core.md#applicationdbcontext), `System.Diagnostics.ActivitySource`. - **Concept**: the smart-wait polling loop itself now lives in the shared [`PollingLoop`](#pollingloop) (cross-reference rather than re-teach): this type supplies its own settings, signal, and per-source work, and PollingLoop runs the loop, computes the wait, and aggregates targets. `[Rubric §12: Performance & Scalability]`: instead of a fixed poll interval, the loop computes how long until the earliest known upcoming row is due, floors that to avoid a hot loop, caps it at the configured fallback interval, and lets [`IInternalCommandSignal`](#iinternalcommandsignal) wake it early. `[Rubric §13: Observability & Operability]`: poll spans are emitted under their own suppressible activity so an idle fleet's constant polling does not dominate telemetry ingestion, and [`InternalCommandMetrics`](#internalcommandmetrics) is fed every cycle. -- **Walkthrough**: constants: `PollActivityName` (`InternalCommandProcessor.cs:1259`, must stay in sync with `OutboxPollFilterProcessor` in `MMCA.Common.Aspire`, which has no project reference here and so duplicates the string), `MaxErrorLength` (`InternalCommandProcessor.cs:1262`, `4000`, the column width `LastError` is truncated to), `PrincipalAuthenticationType` (`InternalCommandProcessor.cs:1268`, the auth type stamped on the identity rebuilt from a claimed row); the loop's own `MinimumWait`/`StartupDelay` constants and the hand-rolled `ExecuteAsync` loop body are gone: `ExecuteAsync` (`InternalCommandProcessor.cs:1277-1291`) now just calls `PollingLoop.RunAsync`, passing `GetTargets().Count > 0` as `hasTargets`, `LogNoRelationalSources` as `onNoTargets`, a lambda that runs `ProcessDueCommandsAsync` and maps its result to the `(HasMoreWork, EarliestUpcoming)` tuple `PollingLoop` expects, `LogCycleError` as `onCycleError`, the configured delay/interval seconds, and `signal.WaitAsync` as the wait delegate; `ComputeWaitTime` (`InternalCommandProcessor.cs:1303-1308`) is now a one-line forward to `PollingLoop.ComputeWaitTime`, kept as a wrapper so its own unit tests still compile against this type; `GetTargets` (`InternalCommandProcessor.cs:1316-1322`, the standalone `GetSources` helper is gone) now resolves directly through `TenantDataSourceTargets.ExpandRelational`, recomputed every cycle so a module assembly registered after startup is picked up; `ProcessDueCommandsAsync` (`InternalCommandProcessor.cs:1332-1348`, `internal` for direct test invocation) now forwards the per-target fan-out to `PollingLoop.DrainAllAsync`, supplying `ProcessSourceAsync` as the per-target delegate and `LogSourceError` as the per-source error handler, then publishes the aggregated depth through `InternalCommandMetrics.SetPendingDepth`; `ProcessSourceAsync` per target still opens a DI scope, sets the tenant before resolving the `ApplicationDbContext` (same ordering rule as the admin and cleanup types), fetches up to `BatchSize` runnable rows via `FetchCandidatesAsync`, derives the pending-depth gauge input from that same fetch via `CountPendingAsync` (no extra query unless the batch is saturated), publishes the oldest-due-age gauge from the fetch's own first row (no extra query), splits the ordered rows into a due prefix and an upcoming remainder, returns early with no claim when nothing is due, otherwise claims the due prefix under a fresh `claimToken` and executes each claimed row, reporting `HasMoreDueWork` when the due count exactly filled `BatchSize` and at least one row progressed (so a saturated batch triggers an immediate re-poll); `FetchCandidatesAsync` runs inside its own suppressible `Activity`, queries `AsNoTracking` rows with `ProcessedOn`/`DeadLetteredOn` null, `Attempts < MaxAttempts`, and no unexpired `ClaimedUntil`, ordered by `ScheduledOn` then `Id`; `CountPendingAsync` short-circuits to the fetched row count when it is below `BatchSize` (the fetch already saw the whole backlog) and pays for a `COUNT` query only when the batch is saturated. +- **Walkthrough**: the primary constructor (`InternalCommandProcessor.cs:43-49`) takes six required parameters, with target selection delegated to an injected `FrameworkTableTargets`; constants: `PollActivityName` (`InternalCommandProcessor.cs:57`, `"InternalCommandPoll"`, must stay in sync with `OutboxPollFilterProcessor` in `MMCA.Common.Aspire`, which has no project reference here and so duplicates the string), `MaxErrorLength` (`InternalCommandProcessor.cs:60`, `4000`, the column width `LastError` is truncated to), `PrincipalAuthenticationType` (`InternalCommandProcessor.cs:66`, `"InternalCommand"`, the auth type stamped on the identity rebuilt from a claimed row); `ExecuteAsync` (`InternalCommandProcessor.cs:75-89`) just calls `PollingLoop.RunAsync`, passing `GetTargets().Count > 0` as `hasTargets`, `LogNoRelationalSources` as `onNoTargets`, a lambda that runs `ProcessDueCommandsAsync` and maps its result to the `(HasMoreWork, EarliestUpcoming)` tuple `PollingLoop` expects, `LogCycleError` as `onCycleError`, the configured delay/interval seconds, and `signal.WaitAsync` as the wait delegate; `ComputeWaitTime` (`InternalCommandProcessor.cs:101-106`) is a one-line forward to `PollingLoop.ComputeWaitTime`, kept as a wrapper so its own unit tests still compile against this type; `GetTargets` (`InternalCommandProcessor.cs:114-115`) is a one-line call to `tableTargets.Relational(DataSource, DatabaseName)`, recomputed every cycle so a module assembly registered after startup is picked up; `ProcessDueCommandsAsync` (`InternalCommandProcessor.cs:125-141`, `internal` for direct test invocation) forwards the per-target fan-out to `PollingLoop.DrainAllAsync`, supplying `ProcessSourceAsync` as the per-target delegate and `LogSourceError` as the per-source error handler, then publishes the aggregated depth through `InternalCommandMetrics.SetPendingDepth`; `ProcessSourceAsync` (`InternalCommandProcessor.cs:147-219`) per target opens a DI scope via `CreateTenantScope` (`InternalCommandProcessor.cs:153`), so the tenant is set before the `ApplicationDbContext` is resolved through `IDbContextFactory` (same ordering rule as the admin and cleanup types), fetches up to `BatchSize` runnable rows via `FetchCandidatesAsync`, derives the pending-depth gauge input from that same fetch via `CountPendingAsync` (no extra query unless the batch is saturated), publishes the oldest-due-age gauge from the fetch's own first row (no extra query), splits the ordered rows into a due prefix and an upcoming remainder, returns early with no claim when nothing is due or when another replica claimed the whole prefix, otherwise claims the due prefix under a fresh `claimToken` and, for each claimed row, first calls `RenewClaimAsync` (`InternalCommandProcessor.cs:204`) and skips the row with `LogLeaseLostBeforeExecution` when the claim is no longer this replica's, else runs `ExecuteClaimedAsync`; it reports `HasMoreDueWork` when the due count exactly filled `BatchSize` and at least one row progressed (`InternalCommandProcessor.cs:216`, so a saturated batch triggers an immediate re-poll); `FetchCandidatesAsync` (`InternalCommandProcessor.cs:229-248`) runs inside its own suppressible `Activity`, queries `AsNoTracking` rows with `ProcessedOn`/`DeadLetteredOn` null, `Attempts < MaxAttempts`, and no unexpired `ClaimedUntil`, ordered by `ScheduledOn` then `Id`; `CountPendingAsync` (`InternalCommandProcessor.cs:256-278`) short-circuits to the fetched row count when it is below `BatchSize` (the fetch already saw the whole backlog) and pays for a `COUNT` query only when the batch is saturated; `ClaimDueAsync` (`InternalCommandProcessor.cs:286-327`) stamps `ClaimedUntil = now + LeaseSeconds` and `ClaimedBy = claimToken` on the due prefix in one guarded `ExecuteUpdateAsync`, and on a partial claim re-reads which ids carry this replica's token so only owned rows run; `RenewClaimAsync` (`InternalCommandProcessor.cs:334-348`) pushes `ClaimedUntil` forward by `LeaseSeconds` in a statement guarded on `ClaimedBy == claimToken` and returns whether a row was updated, which is what makes `LeaseSeconds` bound one handler rather than the whole batch; `ExecuteClaimedAsync` (`InternalCommandProcessor.cs:355-426`) dead-letters a row on the first attempt when its payload throws `JsonException` or `NotSupportedException` on deserialization (`InternalCommandProcessor.cs:369-378`, reason `payload_invalid`, logged by `LogPayloadInvalid`), when its type cannot be resolved (`type_unresolvable`), or when no handler is registered on this host (`handler_missing`), records lag and duration histograms around the invocation, completes the row on a successful `Result`, and otherwise hands off to `RecordFailedAttemptAsync` (`InternalCommandProcessor.cs:435-462`), which treats a `Result.Failure` and a thrown exception alike: dead-letter with `attempts_exhausted` once `MaxAttempts` is reached, else schedule a backoff retry. - **Why it's built this way**: the loop, wait-time computation, and per-target aggregation moved into [`PollingLoop`](#pollingloop) because the outbox processor had nearly identical code; keeping `ComputeWaitTime` as a thin forwarding wrapper on this type avoids a breaking change to its own existing unit tests. An un-suppressed poll span would swamp telemetry for an idle fleet, and a `COUNT` on every cycle would be wasted work in the (common) unsaturated case. Cite [ADR-114](https://ivanball.github.io/docs/adr/114-internal-commands-durable-job-queue.html) for the queue itself; the executed handler goes through the same decorated `ICommandHandler` pipeline any other command uses, per [ADR-014](https://ivanball.github.io/docs/adr/014-cqrs-decorator-pipeline.html). -- **Where it's used**: registered as a hosted service in `DependencyInjection.cs` (1 site); ADC's `ScoreEventSessionsInternalCommand` (`Conference.Application`, 1 site) is a concrete `IInternalCommand` this processor drains; `InternalCommandProcessorTests.cs` and the shared test harness exercise `ProcessDueCommandsAsync`/`ComputeWaitTime` directly; `OutboxPollFilterProcessor.cs` (`MMCA.Common.Aspire`, 2 sites) reads `PollActivityName` to filter the matching spans from export. -- **Caveats / not-in-source**: the claim-and-execute mechanics (`ClaimDueAsync`, `ExecuteClaimedAsync`) live further down the source file and were not re-read line-by-line for this section; `sed -n` over `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/InternalCommandProcessor.cs` covers them for anyone who needs the claim/backoff details. +- **Where it's used**: registered as a hosted service in `DependencyInjection.Jobs.cs` (1 site); ADC's `ScoreEventSessionsInternalCommand` (`Conference.Application`, 1 site) is a concrete `IInternalCommand` this processor drains; `InternalCommandProcessorTests.cs` and the shared test harness exercise `ProcessDueCommandsAsync`/`ComputeWaitTime` directly; `OutboxPollFilterProcessor.cs` (`MMCA.Common.Aspire`, 2 sites) reads `PollActivityName` to filter the matching spans from export. +- **Caveats / not-in-source**: `InvokeAsync`, `ScheduleRetryAsync`, `CompleteAsync` and `DeadLetterAsync` (from `InternalCommandProcessor.cs:464` onward, through the end of the type at `InternalCommandProcessor.cs:712`) were not re-read line-by-line for this section; read that range for the principal-restore and retry-write details. - **ADRs**: [ADR-014](https://ivanball.github.io/docs/adr/014-cqrs-decorator-pipeline.html), [ADR-114](https://ivanball.github.io/docs/adr/114-internal-commands-durable-job-queue.html). ### GroupedCount -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Repositories` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:185` · Level 0 · record (private sealed, nested) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Repositories` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:186` · Level 0 · record (private sealed, nested) -- **What it is**: a two-property carrier for one `GROUP BY` row: the grouping key and the number of rows carrying it. It is declared `private sealed record class` **inside** [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype), so it exists only for the lifetime of one query translation and is invisible to every caller (`EFReadRepository.cs:182-185`). -- **Depends on**: nothing first-party. `TKey` is the caller's grouping key type, constrained to `notnull` at the `CountByAsync` declaration (`EFReadRepository.cs:136`). -- **Concept introduced, a named projection target so the provider can translate the grouping.** `[Rubric §12, Performance and Scalability]` assesses whether aggregation happens in the database rather than after materialization. `CountByAsync` composes `query.GroupBy(keySelector).Select(group => new GroupedCount(group.Key, group.Count()))` (`EFReadRepository.cs:145-149`), so the `COUNT` and the `GROUP BY` are both pushed to SQL and only one row per key comes back. The reason a named type appears here at all rather than an anonymous type or a tuple is translatability: EF Core needs a constructor-shaped projection it can bind by parameter name, and a positional record gives it exactly that with a single line of code. The result is flattened into an `IReadOnlyDictionary` immediately afterwards (`:146`), so the record never escapes the method. +- **What it is**: a two-property carrier for one `GROUP BY` row: the grouping key and the number of rows carrying it. It is declared `private sealed record class` **inside** [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype), so it exists only for the lifetime of one query translation and is invisible to every caller (`EFReadRepository.cs:183-186`). +- **Depends on**: nothing first-party. `TKey` is the caller's grouping key type, constrained to `notnull` at the `CountByAsync` declaration (`EFReadRepository.cs:137`). +- **Concept introduced, a named projection target so the provider can translate the grouping.** `[Rubric §12, Performance and Scalability]` assesses whether aggregation happens in the database rather than after materialization. `CountByAsync` composes `query.GroupBy(keySelector).Select(group => new GroupedCount(group.Key, group.Count()))` (`EFReadRepository.cs:146-150`), so the `COUNT` and the `GROUP BY` are both pushed to SQL and only one row per key comes back. The reason a named type appears here at all rather than an anonymous type or a tuple is translatability: EF Core needs a constructor-shaped projection it can bind by parameter name, and a positional record gives it exactly that with a single line of code. The result is flattened into an `IReadOnlyDictionary` immediately afterwards (`:146`), so the record never escapes the method. - **Walkthrough** - - `Key` (`EFReadRepository.cs:185`): the grouping key, projected from `group.Key`. - - `Value` (`EFReadRepository.cs:185`): the `int` row count, projected from `group.Count()`. + - `Key` (`EFReadRepository.cs:186`): the grouping key, projected from `group.Key`. + - `Value` (`EFReadRepository.cs:186`): the `int` row count, projected from `group.Count()`. - The record has no methods of its own. Being a positional `record class`, it gets value equality and a `Deconstruct` free, neither of which the one call site uses; the shape is the whole point. - **Why it's built this way**: nesting it privately inside the repository keeps a purely mechanical translation helper out of the assembly's type surface, while still giving EF a real named type to bind to. It is the counterpart of [`GroupedSum`](#groupedsumtkey), and the two are deliberately separate rather than one generic `Grouped`, because the `int` and `decimal` aggregate shapes translate through different provider paths. -- **Where it's used**: only in `EFReadRepository.CountByAsync` (`EFReadRepository.cs:147`, flattened at `:146`). The public capability it backs is consumed in MMCA.ADC by `BookmarkCountService` (`MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/UserSessionBookmarks/Services/BookmarkCountService.cs:38`) and `GetAttendanceStatsHandler` (`MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/CheckIns/UseCases/GetAttendanceStats/GetAttendanceStatsHandler.cs:32`), and covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EFReadRepositoryReadSurfaceTests.cs`. +- **Where it's used**: only in `EFReadRepository.CountByAsync` (`EFReadRepository.cs:148`, flattened at `:146`). The public capability it backs is consumed in MMCA.ADC by `BookmarkCountService` (`MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/UserSessionBookmarks/Services/BookmarkCountService.cs:38`) and `GetAttendanceStatsHandler` (`MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/CheckIns/UseCases/GetAttendanceStats/GetAttendanceStatsHandler.cs:32`), and covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EFReadRepositoryReadSurfaceTests.cs`. ### GroupedSum -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Repositories` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:190` · Level 0 · record (private sealed, nested) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Repositories` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:191` · Level 0 · record (private sealed, nested) -- **What it is**: the summing twin of [`GroupedCount`](#groupedcounttkey): one `GROUP BY` row carrying the key and the `decimal` total for that key (`EFReadRepository.cs:187-190`). Same nesting, same privacy, same single-method lifetime. +- **What it is**: the summing twin of [`GroupedCount`](#groupedcounttkey): one `GROUP BY` row carrying the key and the `decimal` total for that key (`EFReadRepository.cs:188-191`). Same nesting, same privacy, same single-method lifetime. - **Depends on**: nothing first-party; see [`GroupedCount`](#groupedcounttkey) for the concept. -- **Concept**: introduced by [`GroupedCount`](#groupedcounttkey). What is worth reading here is the *element selector* on the grouping. `SumByAsync` calls `query.GroupBy(keySelector, sumSelector)` with two expressions and then `group.Sum()` with none (`EFReadRepository.cs:173-177`). The comment at `:165-167` explains the choice: choosing the summed column inside the grouping keeps the caller's expression tree intact all the way to the provider, whereas summing over the grouping with a separately supplied expression would need that tree spliced in by hand. `[Rubric §12, Performance and Scalability]` again: the whole aggregation is one statement. +- **Concept**: introduced by [`GroupedCount`](#groupedcounttkey). What is worth reading here is the *element selector* on the grouping. `SumByAsync` calls `query.GroupBy(keySelector, sumSelector)` with two expressions and then `group.Sum()` with none (`EFReadRepository.cs:174-178`). The comment at `:165-167` explains the choice: choosing the summed column inside the grouping keeps the caller's expression tree intact all the way to the provider, whereas summing over the grouping with a separately supplied expression would need that tree spliced in by hand. `[Rubric §12, Performance and Scalability]` again: the whole aggregation is one statement. - **Walkthrough** - - `Key` (`EFReadRepository.cs:190`): the grouping key. - - `Value` (`EFReadRepository.cs:190`): the `decimal` total, projected from `group.Sum()` over the element-selected column. - - Flattened to `IReadOnlyDictionary` at `EFReadRepository.cs:179`. + - `Key` (`EFReadRepository.cs:191`): the grouping key. + - `Value` (`EFReadRepository.cs:191`): the `decimal` total, projected from `group.Sum()` over the element-selected column. + - Flattened to `IReadOnlyDictionary` at `EFReadRepository.cs:180`. - **Why it's built this way**: identical rationale to its twin. `decimal` rather than a generic numeric parameter matches the framework's money and points columns, which are the values callers actually total. -- **Where it's used**: only in `EFReadRepository.SumByAsync` (`EFReadRepository.cs:175`). The capability is consumed in MMCA.ADC by `GetLeaderboardHandler` (`MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/Points/UseCases/GetLeaderboard/GetLeaderboardHandler.cs:55`) and covered by `EFReadRepositoryReadSurfaceTests.cs`. +- **Where it's used**: only in `EFReadRepository.SumByAsync` (`EFReadRepository.cs:176`). The capability is consumed in MMCA.ADC by `GetLeaderboardHandler` (`MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/Points/UseCases/GetLeaderboard/GetLeaderboardHandler.cs:55`) and covered by `EFReadRepositoryReadSurfaceTests.cs`. ### LookupRow -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Repositories` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:720` · Level 0 · class (internal sealed) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Repositories` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:735` · Level 0 · class (internal sealed) -- **What it is**: the intermediate row a non-string lookup projection materializes: the entity's identifier plus the name property carried in its own CLR type, rather than pre-formatted to a string (`EFReadRepository.cs:712-727`). Declared at file scope in `EFReadRepository.cs`, alongside the class it serves, rather than nested private the way [`GroupedCount`](#groupedcounttkey) and [`GroupedSum`](#groupedsumtkey) are: [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype)'s `ExecuteRawLookupAsync` is `public static` and needs `LookupRow` in its own signature, which a `private` nested type could not appear in. -- **Depends on**: nothing first-party. `TId` and `TName` are the caller's identifier and raw name types, closed over by [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype)'s `BuildLookupSelector` (`EFReadRepository.cs:353`) and `ExecuteRawLookupAsync` (`:282`). +- **What it is**: the intermediate row a non-string lookup projection materializes: the entity's identifier plus the name property carried in its own CLR type, rather than pre-formatted to a string (`EFReadRepository.cs:727-742`). Declared at file scope in `EFReadRepository.cs`, alongside the class it serves, rather than nested private the way [`GroupedCount`](#groupedcounttkey) and [`GroupedSum`](#groupedsumtkey) are: [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype)'s `ExecuteRawLookupAsync` is `public static` and needs `LookupRow` in its own signature, which a `private` nested type could not appear in. +- **Depends on**: nothing first-party. `TId` and `TName` are the caller's identifier and raw name types, closed over by [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype)'s `BuildLookupSelector` (`EFReadRepository.cs:354`) and `ExecuteRawLookupAsync` (`:282`). - **Concept, why the projection stops short of a string.** `[Rubric §12, Performance and Scalability]` assesses whether a translation failure is designed out rather than caught after the fact. `GetAllForLookupAsync`'s selector used to call `ToString()` inside the `Select` for a non-string name property, which has no server-side translation for a value-object property (an `Email`, say): appending it to the projection threw `InvalidOperationException` for the whole query and surfaced as an HTTP 500 on a property `QueryFieldService` had already approved as sortable. `LookupRow` is the fix: the projection stops at the raw value, in whatever CLR type EF materializes it through the property's own value converter, and the formatting to a display string happens in memory afterwards, in `ExecuteRawLookupAsync`, where `ToString()` is plain C# rather than an expression tree a provider has to translate. - **Walkthrough** - - `Id` (`EFReadRepository.cs:723`): the entity identifier, `TId`, defaulted to `default!` like the other lightweight projection carriers in this file. - - `Name` (`EFReadRepository.cs:726`): the raw name value, `TName?`, exactly as the provider materialized it, with no coalescing or formatting applied at this stage. -- **Why it's built this way**: a generic carrier rather than one record per name type is what lets `BuildLookupSelector` build the projection once per `(TEntity, TName)` pair by reflection (`Expression.MemberInit` over `Expression.New(rowType)`, `EFReadRepository.cs:353-357`) without a compile-time-known type for every possible name property; `sealed` and two auto-properties keep it a pure carrier with nothing else to reason about. -- **Where it's used**: only inside `EFReadRepository.cs`. `BuildLookupSelector` closes `LookupRow<,>` over `(TIdentifierType, nameAccess.Type)` for a non-string name property (`EFReadRepository.cs:353`); `ExecuteRawLookupAsync` takes the resulting `Expression>>` as its selector parameter (`:282`), projects, orders and caps with it, then formats each row's `Name` to the returned [`BaseLookup`](group-12-api-hosting-mapping.md#baselookuptidentifiertype) (`:292-296`). + - `Id` (`EFReadRepository.cs:738`): the entity identifier, `TId`, defaulted to `default!` like the other lightweight projection carriers in this file. + - `Name` (`EFReadRepository.cs:741`): the raw name value, `TName?`, exactly as the provider materialized it, with no coalescing or formatting applied at this stage. +- **Why it's built this way**: a generic carrier rather than one record per name type is what lets `BuildLookupSelector` build the projection once per `(TEntity, TName)` pair by reflection (`Expression.MemberInit` over `Expression.New(rowType)`, `EFReadRepository.cs:354-358`) without a compile-time-known type for every possible name property; `sealed` and two auto-properties keep it a pure carrier with nothing else to reason about. +- **Where it's used**: only inside `EFReadRepository.cs`. `BuildLookupSelector` closes `LookupRow<,>` over `(TIdentifierType, nameAccess.Type)` for a non-string name property (`EFReadRepository.cs:354`); `ExecuteRawLookupAsync` takes the resulting `Expression>>` as its selector parameter (`:282`), projects, orders and caps with it, then formats each row's `Name` to the returned [`BaseLookup`](group-12-api-hosting-mapping.md#baselookuptidentifiertype) (`:292-296`). - **Caveats / not-in-source**: no test file targets `LookupRow` by name; it is exercised indirectly through `EFReadRepositoryLookupProjectionTests.cs` (listed among [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype)'s usage sites) whenever a non-string name property is looked up. ### ValueHolder -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Repositories` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/KeysetQueryBuilder.cs:252` · Level 0 · class (private sealed, nested) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Repositories` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/KeysetQueryBuilder.cs:266` · Level 0 · class (private sealed, nested) -- **What it is**: a single-field carrier declared `private sealed class ValueHolder(T value)` inside [`KeysetQueryBuilder`](#keysetquerybuilder), whose only member is a get-only `Value` property assigned from the primary constructor (`KeysetQueryBuilder.cs:252-256`). +- **What it is**: a single-field carrier declared `private sealed class ValueHolder(T value)` inside [`KeysetQueryBuilder`](#keysetquerybuilder), whose only member is a get-only `Value` property assigned from the primary constructor (`KeysetQueryBuilder.cs:266-270`). - **Depends on**: nothing. `T` is the caller's boundary-value type, supplied by `KeysetQueryBuilder.Capture`. - **Concept introduced, making a literal look like a captured local so EF parameterizes it.** `[Rubric §12, Performance and Scalability]` assesses whether a hot query path reuses compiled plans instead of minting a fresh one per call. A bare `Expression.Constant` is translated by every EF provider as an inlined literal, which is wrong for a keyset cursor boundary: the value changes on every page of every cursor, so each page would get its own statement text, missing EF's compiled-query cache and filling the server's plan cache with thousands of single-use plans. Reading the value off a member of a constant *holder* instance is the same expression shape the C# compiler emits for a captured local, so EF's parameter extraction evaluates the subtree once and binds the result as a query parameter instead, leaving the statement text identical across pages. - **Walkthrough** - - `Value` (`KeysetQueryBuilder.cs:255`): the one carried value, assigned once from the constructor parameter, never mutated. + - `Value` (`KeysetQueryBuilder.cs:269`): the one carried value, assigned once from the constructor parameter, never mutated. - **Why it's built this way**: a whole class for one field is deliberate rather than minimal: `KeysetQueryBuilder.Capture` needs a `MemberExpression` reading a property off a `ConstantExpression`, and a generic single-property class is the smallest shape that produces one, closed over the boundary's declared type via `MakeGenericType`. - **Where it's used**: only by `KeysetQueryBuilder.Capture(value, type)`, which constructs one instance through `Activator.CreateInstance` and returns `Expression.Property` reading its `Value` member; both `BuildSeekPredicate` boundary constants (the identifier boundary and the sort-key boundary) go through `Capture` rather than a bare `Expression.Constant`. - **Caveats / not-in-source**: private and nested, so it has no existence outside `KeysetQueryBuilder`; the null-boundary comparisons deliberately bypass it and keep real `Expression.Constant(null, ...)` nodes, because `IS NULL` is a shape EF must see as a constant, not a parameterized value. @@ -6696,7 +7404,7 @@ survives a module being pulled out into its own service. - `Set(property, valueFactory)` (`UpdatePropertySetterBuilder.cs:31-40`) is the computed-value overload: the second argument is itself an expression over the entity, which is how `SetProperty(x => x.Count, x => x.Count + 1)` style updates are expressed. Same guards, same recording, same fluent return. - `IsEmpty` (`UpdatePropertySetterBuilder.cs:43`) reports whether anything was described at all. [`EFRepository`](#efrepositorytentity-tidentifiertype) turns a `true` here into an `ArgumentException` rather than issuing a no-op UPDATE (`EFRepository.cs:137-138`). - `SetsProperty(propertyName)` (`UpdatePropertySetterBuilder.cs:49`) is the audit-stamping guard: it answers "did the caller already assign this column?" so the automatic `LastModifiedOn` and `LastModifiedBy` stamp never overwrites an explicit value. - - `Apply(builder)` (`UpdatePropertySetterBuilder.cs:52-58`) is the replay: iterate the recorded actions, invoke each against EF's real setters builder. It is passed as a method group straight into EF (`EFRepository.cs:153`). + - `Apply(builder)` (`UpdatePropertySetterBuilder.cs:52-58`) is the replay: iterate the recorded actions, invoke each against EF's real setters builder. It is passed as a method group straight into EF (`EFRepository.cs:156`). - `TrackPropertyName` (`UpdatePropertySetterBuilder.cs:60-66`) only records a name when the selector body is a `MemberExpression`, which is the shape of a simple `e => e.Property` lambda. A more complex selector contributes no name, so `SetsProperty` answers `false` for it and the audit stamp is applied. That is the safe direction of the two. - **Why it's built this way**: recording rather than adapting live is what makes the type useful in two directions at once. The Application layer never sees EF, and the repository gets a chance to inspect and augment the described update before it is issued, which is exactly what the audit stamping in [`EFRepository`](#efrepositorytentity-tidentifiertype) needs. `internal sealed` keeps the class invisible outside the Infrastructure assembly: consumers only ever hold the interface. The repository plus specification contract this member belongs to is recorded in [ADR-055](https://ivanball.github.io/docs/adr/055-repository-and-specification-contract.html). - **Where it's used**: constructed once per call in `EFRepository.ExecuteUpdateAsync` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:135`), fed by the caller's `Action>` (`:114`), interrogated for the audit stamp (`:120`, `:126`), and finally replayed into EF (`:131`). The profiled path forwards the same delegate through [`EFRepositoryDecorator`](#efrepositorydecoratortentity-tidentifiertype) (`EFRepositoryDecorator.cs:58-63`). @@ -6707,7 +7415,7 @@ survives a module being pulled out into its own service. > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Repositories` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/QueryTags.cs:18` · Level 2 · class (internal static) - **What it is**: the shared helper that composes and applies EF Core query tags (`TagWith`), so a generated SQL statement can be traced back to the use case and the query-builder detail that produced it (`QueryTags.cs:18-68`). -- **Depends on**: [`QueryTagScope`](group-03-querying-specifications.md#querytagscope) for the ambient use-case half of the tag (`Tag`, `QueryTags.cs:139`), and EF Core's `IQueryable.TagWith` extension. No other framework types. +- **Depends on**: [`QueryTagScope`](group-03-querying-specifications.md#querytagscope) for the ambient use-case half of the tag (`Tag`, `QueryTags.cs:42`), and EF Core's `IQueryable.TagWith` extension. No other framework types. - **Concept introduced, a two-half tag composed from an ambient scope and a local detail.** `[Rubric §13, Observability and Operability]` assesses whether a slow query in a provider's query store can be traced back to the code that issued it. A tag has two independent halves: the ambient use case (set by [`QueryTagScope`](group-03-querying-specifications.md#querytagscope) around a handler) and a detail the query-building code itself knows, such as which specification or which keyset sort ran. `Compose` (`QueryTags.cs:55-67`) handles all four presence combinations, joining both halves with a space when both are known, using whichever one is known alone, and returning `null` when neither is (`:60-66`). The `null` case is deliberate rather than an omission: `Tag` (`:39-47`) checks for it explicitly because EF throws on an empty tag, so an untagged query is the correct outcome, not a bug to guard against (`:44-46`). - **Walkthrough** - `HandlerPrefix = "handler:"` (`QueryTags.cs:21`), `SpecificationPrefix = "spec:"` (`:24`), and `KeysetPrefix = "keyset:"` (`:27`) are the three `internal const` prefixes that make a tag's origin recognizable at a glance in a query store. @@ -6723,7 +7431,7 @@ survives a module being pulled out into its own service. - **What it is**: the one place a specification becomes an `IQueryable`. It always applies the specification's criteria, and when the specification is a [`QuerySpecification`](group-03-querying-specifications.md#queryspecificationtentity-tidentifiertype) it also applies that specification's includes, ordering, and paging (`SpecificationEvaluator.cs:11-21`). - **Depends on**: [`ISpecification`](group-03-querying-specifications.md#ispecificationtentity-tidentifiertype) and [`QuerySpecification`](group-03-querying-specifications.md#queryspecificationtentity-tidentifiertype) for the input shape, [`OrderExpression`](group-03-querying-specifications.md#orderexpression) for each ordering key, [`IBaseEntity`](group-02-domain-building-blocks.md#ibaseentitytidentifiertype) as the entity constraint (`SpecificationEvaluator.cs:44`), [`QueryTags`](#querytags) for tagging the query with the specification's type name, EF Core's `Include` and `AsSplitQuery` extensions, plus `System.Reflection` and `System.Collections.Concurrent` for the two caches (`:1-6`). -- **Concept introduced, the evaluator half of the specification pattern.** `[Rubric §2, Design Patterns]` assesses whether patterns are implemented completely rather than named; the specification pattern has two halves, a declarative object describing what to select and an evaluator that turns it into a provider query, and this class is the second half. `[Rubric §3, Clean Architecture]` also applies: the specification types live in the Domain layer and know nothing about EF, so all EF knowledge is concentrated here. The most important thing to internalize is stated in the class doc at `SpecificationEvaluator.cs:15-19`, and it is a boundary, not an omission. **Tracking and soft-delete scope are deliberately not applied here.** Those two choices select the *base* queryable (`Table` versus `TableNoTracking`, with or without the named soft-delete filter dropped), which only a repository holding the `DbContext` can do. The evaluator composes on top of whatever base it is handed, which is why `BaseQueryFor` lives in [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype) (`EFReadRepository.cs:560-569`) and not here. +- **Concept introduced, the evaluator half of the specification pattern.** `[Rubric §2, Design Patterns]` assesses whether patterns are implemented completely rather than named; the specification pattern has two halves, a declarative object describing what to select and an evaluator that turns it into a provider query, and this class is the second half. `[Rubric §3, Clean Architecture]` also applies: the specification types live in the Domain layer and know nothing about EF, so all EF knowledge is concentrated here. The most important thing to internalize is stated in the class doc at `SpecificationEvaluator.cs:15-19`, and it is a boundary, not an omission. **Tracking and soft-delete scope are deliberately not applied here.** Those two choices select the *base* queryable (`Table` versus `TableNoTracking`, with or without the named soft-delete filter dropped), which only a repository holding the `DbContext` can do. The evaluator composes on top of whatever base it is handed, which is why `BaseQueryFor` lives in [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype) (`EFReadRepository.cs:575-584`) and not here. - **Walkthrough** - `Apply(source, specification, applyShape = true)` (`SpecificationEvaluator.cs:40-69`) is the entry point. It applies `specification.Criteria`, then tags the resulting query through [`QueryTags`](#querytags) with `SpecificationPrefix + specification.GetType().Name` (`:52-54`), tagging first so the label survives whatever shape composes on top of it, because EF carries tags on the query rather than on the operator they were attached to (`:50-51`). It then returns early when either `applyShape` is false or the specification is not a `QuerySpecification` (`:56-57`). Otherwise it layers includes, then ordering, then `Skip` when it is a positive integer, then `Take` (`:59-66`). Skip before Take, ordering before both: that is the only order in which paging is meaningful. - The `applyShape` parameter earns its own doc paragraph (`SpecificationEvaluator.cs:30-35`) and it is a real correctness argument, not a micro-optimization. Aggregate reads such as count and exists pass `false`, because joining includes in to count rows costs a join per navigation, and counting "page 3 of the matches" is never what a caller means. @@ -6733,7 +7441,7 @@ survives a module being pulled out into its own service. - Two static caches keep the reflection cost off the hot path. `OrderingMethods` (`SpecificationEvaluator.cs:164-178`) resolves the four two-generic-argument, two-parameter `Queryable` overloads exactly once at type initialization; the per-call cost is a dictionary hit plus a `MakeGenericMethod`. `CollectionIncludeCache` (`:177`) is a `ConcurrentDictionary` keyed by `(entity type, include path)` memoizing the collection-navigation walk in `IsCollectionNavigationPath` (`:179-197`). - `IsCollectionNavigationPath` walks the path segment by segment. An unknown segment returns `false` and defers to EF's own include validation rather than guessing (`SpecificationEvaluator.cs:194-195`); a segment whose type is `IEnumerable` but not `string` returns `true` (`:190-191`). The `string` exclusion matters: `string` implements `IEnumerable` and would otherwise force every string-valued path into split-query mode. - **Why it's built this way**: `internal static` with no state beyond two caches makes it trivially safe to share across every repository instance in the process. The `` at `SpecificationEvaluator.cs:77-80` records the deliberate consolidation: `EFReadRepository.ApplyIncludes` delegates here rather than duplicating the logic, so the string-include path and the specification path cannot drift apart. The overall contract is [ADR-055](https://ivanball.github.io/docs/adr/055-repository-and-specification-contract.html); the expectation that a specification's criteria translate on more than one engine is [ADR-018](https://ivanball.github.io/docs/adr/018-polyglot-persistence.html). -- **Where it's used**: exclusively inside this group. [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype) calls `Apply` from `FirstOrDefaultAsync(specification)` (`EFReadRepository.cs:125-126`), from `CountAsync(specification)` with `applyShape: false` (`:348-349`), from both `ListAsync` overloads (`:457-458`, `:474-475`), and from `GetPageByCursorAsync` again with `applyShape: false` (`:516`); its `ApplyIncludes` is a thin forwarder to this class (`:426-429`). [`KeysetQueryBuilder`](#keysetquerybuilder) calls `ApplyOrderingStep` three times (`KeysetQueryBuilder.cs:77`, `:73`, `:74`). Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/SpecificationEvaluatorTests.cs` and `.../EFReadRepositorySpecificationTests.cs`. +- **Where it's used**: exclusively inside this group. [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype) calls `Apply` from `FirstOrDefaultAsync(specification)` (`EFReadRepository.cs:126-127`), from `CountAsync(specification)` with `applyShape: false` (`:348-349`), from both `ListAsync` overloads (`:457-458`, `:474-475`), and from `GetPageByCursorAsync` again with `applyShape: false` (`:516`); its `ApplyIncludes` is a thin forwarder to this class (`:426-429`). [`KeysetQueryBuilder`](#keysetquerybuilder) calls `ApplyOrderingStep` three times (`KeysetQueryBuilder.cs:77`, `:73`, `:74`). Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/SpecificationEvaluatorTests.cs` and `.../EFReadRepositorySpecificationTests.cs`. ### KeysetQueryBuilder @@ -6745,53 +7453,19 @@ survives a module being pulled out into its own service. - **Walkthrough** - `TryResolveSortProperty(sortColumn, out property)` (`KeysetQueryBuilder.cs:35-47`) returns `true` with a `null` property when no column was requested (paging by `Id` alone, `:39-40`), and otherwise does a case-insensitive public-instance property lookup (`:42-44`). A `false` return means the client named something that does not exist, which the caller turns into a validation failure rather than an exception. - `ApplyOrdering(query, sortProperty, descending)` (`KeysetQueryBuilder.cs:62-82`) first tags the query through [`QueryTags`](#querytags) with `KeysetPrefix + (sortProperty?.Name ?? "Id")` (`:71`), so every keyset page, specification-driven or not, names itself in the generated SQL. It then builds an `e => e.Id` selector from the shared parameter (`:73-74`), then either orders by `Id` alone in the requested direction (`:76-77`) or orders by the sort key in the requested direction and then by `Id` **always ascending** (`:79-81`). The identifier tie-break always ascends, which is exactly what the seek predicate assumes. - - `BuildSeekPredicate(sortProperty, sortValue, lastId, descending)` (`KeysetQueryBuilder.cs:109-166`) is the heart of the type, and it has four branches: - - **No sort key** (`:114-119`): the predicate is `Id > lastId`, flipped to `<` when the page descends, because there the identifier *is* the sort key. - - **Null boundary on a non-nullable key** (`:128-132`): impossible by construction, so it degrades to the plain id seek rather than emitting a comparison against null. - - **Null boundary on a nullable key** (`:134-142`): stated explicitly rather than left to SQL. Ascending, nulls sort first, so what remains is "any non-null value, or a null with a larger id"; descending, nulls sort last, so what remains is "a null with a larger id". The remarks at `:87-93` give exactly this reasoning, and the reason it is needed: SQL comparisons against NULL are unknown and would silently drop rows. - - **Normal boundary** (`:145-158`): the classic composite `sort > v OR (sort == v AND Id > lastId)` (`:146-148`), with the sort half flipped to `<` for a descending page. When the key is nullable and the page descends, an extra `sort == null OR (...)` is prepended (`:150-156`), because `null < v` is unknown and the nulls that sort last would otherwise vanish. - - Both boundary constants, the identifier (`:119`) and the sort key (`:152`), are built through the private `Capture(value, type)` (`:241-247`) rather than a bare `Expression.Constant`. `Capture` wraps the value in a [`ValueHolder`](#valueholdert) instance and returns a `MemberExpression` reading its `Value` property, which is the same shape the compiler emits for a captured local, so EF's parameter extraction binds it as a query parameter instead of inlining it as a literal. The null-boundary comparisons deliberately keep real `Expression.Constant(null, ...)` nodes instead, because `IS NULL` is a shape, not a value that changes per page. - - `ToInvariantString(value)` (`KeysetQueryBuilder.cs:175-185`) renders a value for the cursor. The four date and time types use the round-trip `"O"` format specifically so a cursor never loses sub-second precision and re-seeks onto the wrong row (`:161-165`, `:171-174`); strings pass through (`:175`); anything `IFormattable` gets invariant formatting (`:176`); everything else falls back to `ToString()` (`:177`). - - `TryFromInvariantString(targetType, text, out value)` (`KeysetQueryBuilder.cs:194-218`) is the inverse: unwrap `Nullable` (`:191`), short-circuit for `string` (`:192-196`), otherwise use `TypeDescriptor.GetConverter` and `ConvertFromInvariantString` (`:198-205`), catching only `FormatException`, `NotSupportedException`, and `ArgumentException` and turning them into `false` (`:207-210`). A malformed cursor is a validation result, never an exception escaping the repository. - - `Compare(left, right, greaterThan)` (`KeysetQueryBuilder.cs:269-303`) is the provider-translatability layer, and the remarks at `:216-223` explain why it cannot just call `Expression.GreaterThan`: that factory only exists for types that have the operator, which excludes `string`. So strings compare through `string.Compare(string, string)`, which EF translates into a native `>` or `<` (`:228-234`); types with a relational operator use it directly (`:236-241`); and anything else with an `IComparable` implementation (a `Guid` key, for instance) compares through `CompareTo` (`:243-257`), whose translation is provider-specific. A type with none of the three gets a `NotSupportedException` naming the type (`:246-248`), which is a loud failure at query-build time rather than a wrong result. - - `SupportsRelationalOperator` (`KeysetQueryBuilder.cs:305-314`) enumerates the primitive, enum, decimal, and date and time cases, and falls back to reflecting for a public static `op_GreaterThan`. `StringCompareMethod` and `ZeroConstant` (`:271-274`) are resolved once as statics. + - `BuildSeekPredicate(sortProperty, sortValue, lastId, descending, nullsSortFirstAscending = true)` (`KeysetQueryBuilder.cs:118-180`) is the heart of the type. The last parameter (`:123`) says where the engine puts nulls: SQL Server and SQLite sort them first ascending and last descending, PostgreSQL the reverse (`ASC NULLS LAST`, `DESC NULLS FIRST`), as the remarks at `:102-106` state. Before any null handling the method folds the engine and the page direction into one flag, `nullsFirst = nullsSortFirstAscending ? !descending : descending` (`:143-144`), meaning "null keys come before the non-null ones in this page's traversal order". It then has four branches: + - **No sort key** (`:131-136`): the predicate is `Id > lastId`, flipped to `<` when the page descends, because there the identifier *is* the sort key. + - **Null boundary on a non-nullable key** (`:148-152`): impossible by construction, so it degrades to the plain id seek rather than emitting a comparison against null. + - **Null boundary on a nullable key** (`:154-162`): stated explicitly rather than left to SQL. Where nulls come first in the traversal, what remains is "any non-null value, or a null with a larger id"; where nulls come last, what remains is "a null with a larger id" (`:156-160`). The remarks at `:94-101` give exactly this reasoning, and the reason it is needed: SQL comparisons against NULL are unknown and would silently drop rows. + - **Normal boundary** (`:165-179`): the classic composite `sort > v OR (sort == v AND Id > lastId)` (`:166-168`), with the sort half flipped to `<` for a descending page. When the key is nullable and nulls come last in the traversal (`!nullsFirst`), an extra `sort == null OR (...)` is prepended (`:170-177`), because a comparison against null is unknown and the nulls still ahead would otherwise vanish. On SQL Server and SQLite that is the descending page; on PostgreSQL it is the ascending one. + - Both boundary constants, the identifier (`:129`) and the sort key (`:165`), are built through the private `Capture(value, type)` (`:255-261`, reasoning in the remarks at `:234-251`) rather than a bare `Expression.Constant`. `Capture` wraps the value in a [`ValueHolder`](#valueholdert) instance and returns a `MemberExpression` reading its `Value` property, which is the same shape the compiler emits for a captured local, so EF's parameter extraction binds it as a query parameter instead of inlining it as a literal. The null-boundary comparisons deliberately keep real `Expression.Constant(null, ...)` nodes instead, because `IS NULL` is a shape, not a value that changes per page. + - `ToInvariantString(value)` (`KeysetQueryBuilder.cs:189-199`) renders a value for the cursor. The four date and time types use the round-trip `"O"` format specifically so a cursor never loses sub-second precision and re-seeks onto the wrong row (`:192-195`); strings pass through (`:196`); anything `IFormattable` gets invariant formatting (`:197`); everything else falls back to `ToString()` (`:198`). + - `TryFromInvariantString(targetType, text, out value)` (`KeysetQueryBuilder.cs:208-232`) is the inverse: unwrap `Nullable` (`:212`), short-circuit for `string` (`:213-217`), otherwise use `TypeDescriptor.GetConverter` and `ConvertFromInvariantString` (`:219-225`, returning `false` up front when the converter cannot read a string, `:220-221`), catching only `FormatException`, `NotSupportedException`, and `ArgumentException` and turning them into `false` (`:228-231`). A malformed cursor is a validation result, never an exception escaping the repository. + - `Compare(left, right, greaterThan)` (`KeysetQueryBuilder.cs:283-317`) is the provider-translatability layer, and the remarks at `:272-282` explain why it cannot just call `Expression.GreaterThan`: that factory only exists for types that have the operator, which excludes `string`. So strings compare through `string.Compare(string, string)`, which EF translates into a native `>` or `<` (`:287-293`); types with a relational operator use it directly (`:295-300`); and anything else with an `IComparable` implementation (a `Guid` key, for instance) compares through `CompareTo` (`:302-316`), whose translation is provider-specific. A type with none of the three gets a `NotSupportedException` naming the type (`:303-307`), which is a loud failure at query-build time rather than a wrong result. + - `SupportsRelationalOperator` (`KeysetQueryBuilder.cs:319-328`) enumerates the primitive, enum, decimal, date and time, and `TimeSpan` cases, and falls back to reflecting for a public static `op_GreaterThan`. `StringCompareMethod` and `ZeroConstant` (`:330-333`) are resolved once as statics. - **Why it's built this way**: keyset paging is declared as a repository-level capability, deliberately not part of the HTTP query contract of [ADR-034](https://ivanball.github.io/docs/adr/034-generic-entity-query-layer.html), and [ADR-055](https://ivanball.github.io/docs/adr/055-repository-and-specification-contract.html) records that split along with the cursor format. Building the predicate as an expression tree rather than as SQL text is what keeps the same code working on more than one provider ([ADR-018](https://ivanball.github.io/docs/adr/018-polyglot-persistence.html)); reusing [`SpecificationEvaluator`](#specificationevaluator)'s ordering step rather than reimplementing the ordering call means the keyset ORDER BY and the specification ORDER BY are produced by the same code. -- **Where it's used**: only by [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype)'s `GetPageByCursorAsync`, which calls `TryResolveSortProperty` (`EFReadRepository.cs:624`), `ApplyOrdering` (`:533`), `ToInvariantString` twice when encoding the next cursor (`:548-549`), and, through the private `TryBuildSeekPredicate`, `TryFromInvariantString` (`:570`, `:579`) and `BuildSeekPredicate` (`:584-585`). Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EFReadRepositoryKeysetPagingTests.cs`. -- **Caveats / not-in-source**: the `NotSupportedException` for an unorderable key type and the provider-specific translation of `CompareTo` are both real limits of the design, and neither is discoverable until a query is built for that key type. No first-party call site in MMCA.ADC or MMCA.Store calls `GetPageByCursorAsync` today (the only references outside MMCA.Common are the test-support fakes in `MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Support/TestSupport.cs` and its Identity sibling), so which key and sort types have actually been exercised against a real engine is established by the test suite rather than by production usage. - -### EFReadRepository - -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Repositories` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:20` · Level 6 · class (internal) - -- **What it is**: the EF Core implementation of [`IReadRepository`](#ireadrepositorytentity-tidentifiertype), the read half of the repository contract: get by id, get many, projected reads, first-or-default, grouped counts and sums, soft-delete-aware finds, lookups, counts, existence checks, specification-driven reads, and keyset pages, with no mutation surface at all (`EFReadRepository.cs:15-20`). -- **Depends on**: EF Core's `DbContext` and `DbSet`, taken as its one primary-constructor parameter (`EFReadRepository.cs:20-32`); [`IReadRepository`](#ireadrepositorytentity-tidentifiertype) as the contract (declared at `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:330`); [`AuditableBaseEntity`](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype) as the entity constraint (`:22`); [`SpecificationEvaluator`](#specificationevaluator) and [`KeysetQueryBuilder`](#keysetquerybuilder) as its two composition helpers; [`ApplicationDbContext`](#applicationdbcontext) for the soft-delete filter name (`:33`); `EntityQueryPipeline.MaxUnboundedResultLimit` for the lookup row ceiling; [`LookupRow`](#lookuprowtid-tname) as the intermediate projection shape for a non-string lookup name; and [`Result`](group-01-result-error-handling.md#result), [`Error`](group-01-result-error-handling.md#error), [`KeysetPageRequest`](group-01-result-error-handling.md#keysetpagerequest), [`KeysetCollectionResult`](group-01-result-error-handling.md#keysetcollectionresultt), [`KeysetCursor`](group-01-result-error-handling.md#keysetcursor), and [`BaseLookup`](group-12-api-hosting-mapping.md#baselookuptidentifiertype) for the shapes it returns. -- **Concept introduced, naming the query filter you drop.** `[Rubric §11, Security]` assesses whether isolation boundaries hold under every code path, and `[Rubric §8, Data Architecture]` assesses whether the query-filter design is deliberate. EF 10 gives global query filters **names**, and the framework registers two on the model: `SoftDelete` and `Tenant` ([ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html)). EF's parameterless `IgnoreQueryFilters()` drops *all* of them. That means a caller who only wanted to see soft-deleted rows would, with the parameterless call, also read across every tenant. So the repository declares a one-element array naming exactly the filter it is willing to drop (`EFReadRepository.cs:34-40`, resolving `ApplicationDbContext.SoftDeleteFilterName` from `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:469`) and every `ignoreQueryFilters: true` path passes it (`:52`, `:81`, `:102`, `:198`, `:288`, `:365`, `:379`, `:446`). This is the kind of detail worth copying wherever you add a filter-bypassing read. -- **Concept, tracked versus no-tracking as an explicit repository decision.** `[Rubric §12, Performance and Scalability]`. The four queryable properties at `EFReadRepository.cs:511-520` are the vocabulary: `Table` (tracked), `TableNoTracking`, `TableNoTrackingSingleQuery`, and `TableNoTrackingSplitQuery`. Read paths default to no-tracking to avoid change-tracker overhead, but two exceptions are load-bearing and both are commented in place, see the walkthrough below. -- **Walkthrough** - - `MaxLookupSelectorCacheEntries = 512` (`EFReadRepository.cs:30`) bounds the lookup-selector cache; past it a selector is built per request rather than cached, which the source calls correct and only slightly slower. - - The protected `_context` field is guarded at construction (`EFReadRepository.cs:32`), and `Entities` is a `virtual` property resolving `_context.Set()` (`:35`). Every member goes through one of those two, which is what makes the class subclassable by [`EFRepository`](#efrepositorytentity-tidentifiertype). - - A private `BaseQuery(asTracking)` helper now backs every read path that used to write out `asTracking ? Table : TableNoTracking` inline (`GetAllAsync`, `GetProjectedAsync`, `FirstOrDefaultAsync(where, ...)`, `FindIncludingDeletedAsync`, `GetAllForLookupAsync`, `GetByIdsAsync`, `GetByIdAsync(id, includes, asTracking)`); `CountByAsync` and `SumByAsync` call it with `asTracking: false` explicitly. Consolidating the ternary in one place is what keeps the tracked-versus-untracked decision consistent as new read members are added. - - `GetAllAsync` (`EFReadRepository.cs:45-71`) is the widest read: pick tracked or untracked (`:47-49`), optionally drop the named soft-delete filter (`:51-52`), apply includes (`:54`), then optional `where` (`:56-57`), then optional `orderBy` (`:59-60`), then materialize with or without a `select` (`:62-65`). Every parameter is optional, which is what makes it the general-purpose read the older query paths call. - - `GetProjectedAsync` (`EFReadRepository.cs:74-92`) is the projection-first read: no includes at all, because a projection selects its own columns, and the `Select` is applied in the database rather than after materialization (`:86`). - - `FirstOrDefaultAsync(where, includes, ...)` (`EFReadRepository.cs:95-114`) is the predicate overload, and the comment at `:107` records the point: the predicate goes into EF's `FirstOrDefaultAsync` so the database issues a `TOP 1`, rather than materializing a set and narrowing it in memory. - - `FirstOrDefaultAsync(specification)` (`EFReadRepository.cs:117-129`) is the specification overload, and unlike the aggregate reads it applies the **full** shape, ordering included. The comment at `:118-119` is the reason: "first" is only meaningful against a defined order, and the specification is where that order is declared. - - `CountByAsync` (`EFReadRepository.cs:132-152`) and `SumByAsync` (`:150-175`) are the two grouped aggregates. Both run untracked, apply the optional predicate, group in the database, and project into the private records [`GroupedCount`](#groupedcounttkey) (`:142`) and [`GroupedSum`](#groupedsumtkey) (`:170`) before flattening to a dictionary (`:146`, `:174`). The comment at `:165-167` explains why `SumByAsync` passes the sum selector as `GroupBy`'s *element* selector: it keeps the caller's expression tree intact all the way to the provider. - - `FindIncludingDeletedAsync` (`EFReadRepository.cs:193-219`) returns a named tuple of active and soft-deleted matches. The comment at `:196-197` is the design point: it drops the soft-delete filter and reads **once**, then partitions in memory (`:205-211`), because two separate queries would let a concurrent delete land between them and report the same row in neither half. - - `GetAllForLookupAsync` (`EFReadRepository.cs:222-261`) returns id and name pairs ordered by name, where "name" is a *runtime* property name. `GetOrBuildLookupSelector` (`:320-341`) now returns an untyped `LambdaExpression` rather than a fixed `Expression>>`, because `BuildLookupSelector` (`:343-374`) builds one of two projection shapes depending on the resolved property's CLR type: a string property still projects straight to `BaseLookup` with its name coalesced to empty (`:365-373`), while anything else projects to [`LookupRow`](#lookuprowtid-tname) carrying the raw value (`:349-363`), because appending `ToString()` to the projection has no server-side translation for a value-object property and used to throw `InvalidOperationException` for the whole query. `GetAllForLookupAsync` pattern-matches the returned `LambdaExpression` (`:240`): the string leg runs the original `Select`/`OrderBy`/`Take` inline (`:242-247`); the non-string leg resolves the raw name's CLR type by reflection (`:255`) and dispatches, through the cached `ExecuteRawLookupMethod` (`:267-268`), to the public static `ExecuteRawLookupAsync` (`:280-297`), which runs the same ordered, capped projection against `LookupRow` and formats each row's name in memory afterward (`:292-296`). Both legs keep `.Take(EntityQueryPipeline.MaxUnboundedResultLimit)` (`:245`, `:288`) BEFORE materialization, and the comment above the branch names why (`:235-239`, tagged SEC-Common-24): this path never enters `EntityQueryPipeline`, so the framework's row ceiling has to be applied here or a lookup stays the one unpaginated, uncapped read in the framework. The cache underneath is unchanged in behavior: `GetOrBuildLookupSelector` first resolves the property name to its CANONICAL spelling (`:322-326`, tagged SEC-Store-14) before keying `LookupSelectorCache` (`:313`), checks the cache (`:330-331`), builds uncached rather than admitting a new entry once `MaxLookupSelectorCacheEntries` is reached (`:335-336`), and otherwise memoizes through `GetOrAdd` (`:338-340`). Canonicalizing the name first is a fix, not a cosmetic change: reflection resolves a property name case-insensitively, so every case permutation of a real column used to mint its own never-evicted cache entry, 2^N spellings of an N-letter name, from an anonymous endpoint; resolving the `PropertyInfo` first collapses them onto one entry. - - `GetByIdsAsync` (`EFReadRepository.cs:377-399`) materializes the id sequence once, short-circuits on an empty set with an empty result (`:281-283`), and translates to a single `Contains` (in other words a SQL `IN`) rather than N round trips (`:293`). - - `GetByIdAsync(id)` (`EFReadRepository.cs:402-414`) carries the single most important comment in the file (`:303-307`) and it encodes two separate bug fixes. First, it is a **filtered query, not `FindAsync`**: `FindAsync` serves a tracked instance straight out of the identity map without evaluating the global soft-delete filter, so an entity soft-deleted earlier in the same scope came back as if it were live. Second, it queries `Table` (**tracked**) on purpose: [`EFRepository`](#efrepositorytentity-tidentifiertype) inherits this member and the generic delete and update handlers load through it, mutate the instance, and save, so a no-tracking query would turn those writes into silent no-ops. - - `GetByIdAsync(id, includes, asTracking)` (`EFReadRepository.cs:417-430`) is the include-carrying overload and defaults to no-tracking, because a caller asking for includes is normally reading for display. - - The three `CountAsync` overloads (`EFReadRepository.cs:433-457`) cover no predicate, an expression predicate, and a specification. The specification overload composes through [`SpecificationEvaluator`](#specificationevaluator) with `applyShape: false` (`:348-349`), so includes, ordering, and paging never reach a COUNT. - - The two `ExistsAsync` overloads (`EFReadRepository.cs:462-487`) both funnel into the private `AnyAsync` helper (`:394-400`), which is provider-aware. The remarks at `:387-393` are worth reading before you touch it: Cosmos gets `CountAsync` because its provider generates invalid SQL (an unresolved `root` identifier) when translating a predicated `AnyAsync` into a subquery; every other provider gets `AnyAsync`, which short-circuits at the first match. The cost of the workaround is stated honestly in the same remark: `CountAsync` reads every matching row, so on a wide predicate the Cosmos path is proportional to the number of matches. `IsCosmosProvider` (`:402-403`) is a provider-name test. - - `ApplyIncludes` (`EFReadRepository.cs:547-550`) is a `protected static` forwarder to [`SpecificationEvaluator`](#specificationevaluator), including the collection-navigation split-query auto-switch. The doc at `:417-425` says why: the logic lives once so the string-include path and the specification path cannot drift apart. - - `BaseQueryFor` (`EFReadRepository.cs:560-569`) is the boundary [`SpecificationEvaluator`](#specificationevaluator) refuses to cross. It reads `AsTracking` and `IgnoreQueryFilters` off a [`QuerySpecification`](group-03-querying-specifications.md#queryspecificationtentity-tidentifiertype) when the specification is one, and gives a plain `ISpecification` the untracked, filtered default. - - The two `ListAsync` overloads (`EFReadRepository.cs:572-600`) apply the specification to that base. In the projecting overload the comment at `:472-473` records the ordering rule: `Select` comes **last**, so ordering and paging run over entity rows and only the resulting page is projected. - - `AnyAsync(specification)` (`EFReadRepository.cs:603-614`) uses criteria only, through the same Cosmos-aware existence check the predicate overloads use (`:489-492`). - - `GetPageByCursorAsync` (`EFReadRepository.cs:617-674`) is the keyset page, and it is the one read that returns a [`Result`](group-01-result-error-handling.md#result) rather than a bare value, because two of its failures are caller errors rather than exceptions. An unknown sort column returns `Error.InvalidEntityField` with the column and type named (`:503-512`); a malformed cursor returns a validation error with code `Error.InvalidCursor` (`:520-528`). Between those it applies the optional specification with `applyShape: false` (`:514-516`), the seek predicate (`:530`), and the `(sortKey, Id)` ordering (`:533`). The `Take(request.PageSize + 1)` at `:537` is the next-page probe, and the comment at `:535-536` explains the choice: the extra row is never returned, it only says whether a next page exists, which is cheaper and more honest than a COUNT over the whole set. The probe row is trimmed (`:539-541`), and a next cursor is encoded from the last surviving row only when there is more (`:543-550`). - - `TryBuildSeekPredicate` (`EFReadRepository.cs:681-709`) is the private decode-and-build: reject a cursor that fails `KeysetCursor.TryDecode` (`:567`), reject an id segment that does not parse to `TIdentifierType` (`:570-574`), reject a sort segment that does not parse to the sort property's type (`:576-582`), and otherwise hand the parsed values to [`KeysetQueryBuilder`](#keysetquerybuilder) (`:584-585`). -- **Why it's built this way**: `internal` rather than public, and every member `virtual`, is what lets [`EFRepository`](#efrepositorytentity-tidentifiertype) extend it with writes while consumers hold only the interface. Concentrating the tracking and filter-scope decisions here and pushing pure composition into [`SpecificationEvaluator`](#specificationevaluator) and [`KeysetQueryBuilder`](#keysetquerybuilder) is what keeps this class about *policy* and those about *mechanism*. The contract and its evolution are recorded in [ADR-055](https://ivanball.github.io/docs/adr/055-repository-and-specification-contract.html); the interface segregation into [`IEntityReader`](#ientityreadertentity-tidentifiertype) and [`IEntityQuerier`](#ientityqueriertentity-tidentifiertype) beneath `IReadRepository` is what makes a read-only consumer unable to write at all (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:21`, `:80`, `:330`). -- **Where it's used**: constructed by [`RepositoryFactory`](#repositoryfactory)'s `CreateReadOnly` through a cached `ActivatorUtilities` factory taking the `DbContext` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/Factory/RepositoryFactory.cs:55-56`), optionally wrapped in [`EFReadRepositoryDecorator`](#efreadrepositorydecoratortentity-tidentifiertype) (`:58-63`). Application code reaches it through [`IUnitOfWork`](#iunitofwork)'s `GetReadRepository`, implemented at `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:53-66`. Covered by `EFReadRepositoryReadSurfaceTests.cs`, `EFReadRepositoryKeysetPagingTests.cs`, `EFReadRepositorySpecificationTests.cs`, `EFReadRepositoryGetByIdFilterTests.cs`, and `EFReadRepositoryProjectedFilterTests.cs` under `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/`. -- **Caveats / not-in-source**: `GetAllForLookupAsync` resolves `nameProperty` by reflection at runtime (`EFReadRepository.cs:324-326`), so an unknown name fails from the expression build rather than at compile time; the source contains no validation of that argument beyond what `Expression.Property` itself enforces. It is capped by `EntityQueryPipeline.MaxUnboundedResultLimit` on both the string and the raw-value leg (`:245`, `:288`), so unlike `FindIncludingDeletedAsync`, which still partitions in memory and materializes every matching row including the deleted ones with no upper bound on that set, a lookup can no longer read an unbounded table. +- **Where it's used**: only by [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype)'s `GetPageByCursorAsync`, which calls `TryResolveSortProperty` (`EFReadRepository.cs:639`), `ApplyOrdering` (`:669`), `ToInvariantString` twice when encoding the next cursor (`:684-685`), and, through the private `TryBuildSeekPredicate`, `TryFromInvariantString` (`:706`, `:715`) and `BuildSeekPredicate` (`:720-721`), passing `nullsSortFirstAscending` from the engine behind the context. Covered by `EFReadRepositoryKeysetPagingTests.cs` under `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Repositories/Read/`, and by `KeysetQueryBuilderSqlTests.cs` and `KeysetQueryBuilderNullOrderingTests.cs` under `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Repositories/`. +- **Caveats / not-in-source**: the `NotSupportedException` for an unorderable key type and the provider-specific translation of `CompareTo` are both real limits of the design, and neither is discoverable until a query is built for that key type. `nullsSortFirstAscending` defaults to `true` (`KeysetQueryBuilder.cs:123`), so a caller that omits it gets SQL Server and SQLite null placement, which is the wrong side of the boundary for PostgreSQL; the one production caller always passes the engine's value (`EFReadRepository.cs:720-721`). No first-party call site in MMCA.ADC or MMCA.Store calls `GetPageByCursorAsync` today (the only references outside MMCA.Common are the test-support fakes in `MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Support/TestSupport.cs` and its Identity sibling), so which key and sort types have actually been exercised against a real engine is established by the test suite rather than by production usage. ### EFReadRepositoryDecorator @@ -6815,7 +7489,7 @@ survives a module being pulled out into its own service. - **What it is**: the read-write half of the profiling decorator. It extends [`EFReadRepositoryDecorator`](#efreadrepositorydecoratortentity-tidentifiertype) and adds forwarders for the mutation members of [`IRepository`](#irepositorytentity-tidentifiertype): add, update, row-version, execute-delete, and execute-update (`EFRepositoryDecorator.cs:7-14`). - **Depends on**: [`IRepository`](#irepositorytentity-tidentifiertype), its base class [`EFReadRepositoryDecorator`](#efreadrepositorydecoratortentity-tidentifiertype), [`ProfilingHelper`](#profilinghelper), [`IRowVersioned`](group-02-domain-building-blocks.md#irowversioned) for the child-entity concurrency overload (`EFRepositoryDecorator.cs:45`), and [`IUpdatePropertySetter`](#iupdatepropertysettertentity) in the `ExecuteUpdateAsync` signature (`:56`). -- **Concept, decorating a derived contract by passing the same instance twice.** `[Rubric §1, SOLID]`. The declaration is the interesting line: `EFRepositoryDecorator(IRepository<...> inner)` deriving from `EFReadRepositoryDecorator<...>(inner)` while implementing `IRepository<...>` (`EFRepositoryDecorator.cs:14-18`). The single `inner` argument is handed to the base constructor as an `IReadRepository` **and** stored again in this class's own `_inner` as an `IRepository` (`:21`). One object, two typed references: the inherited read members forward through the base's field, the write members through this one. That works precisely because [`IRepository`](#irepositorytentity-tidentifiertype) extends [`IReadRepository`](#ireadrepositorytentity-tidentifiertype) and [`IWriteRepository`](#iwriterepositorytentity-tidentifiertype) (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:493`), so the read decorator can be reused verbatim rather than re-forwarded. +- **Concept, decorating a derived contract by passing the same instance twice.** `[Rubric §1, SOLID]`. The declaration is the interesting line: `EFRepositoryDecorator(IRepository<...> inner)` deriving from `EFReadRepositoryDecorator<...>(inner)` while implementing `IRepository<...>` (`EFRepositoryDecorator.cs:14-18`). The single `inner` argument is handed to the base constructor as an `IReadRepository` **and** stored again in this class's own `_inner` as an `IRepository` (`:21`). One object, two typed references: the inherited read members forward through the base's field, the write members through this one. That works precisely because [`IRepository`](#irepositorytentity-tidentifiertype) extends [`IReadRepository`](#ireadrepositorytentity-tidentifiertype) and [`IWriteRepository`](#iwriterepositorytentity-tidentifiertype) (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:495`), so the read decorator can be reused verbatim rather than re-forwarded. - **Walkthrough** - `ClassName` is `"EFRepository"` here (`EFRepositoryDecorator.cs:20`), so writes appear under the read-write repository's label while inherited reads keep the read repository's label. - Asynchronous writes follow the base's shape: `AddAsync` (`:23-25`), `AddRangeAsync` (`:27-29`), `UpdateAsync` (`:31-33`), `ExecuteDeleteAsync` (`:48-52`), and `ExecuteUpdateAsync` (`:54-59`). @@ -6825,28 +7499,62 @@ survives a module being pulled out into its own service. - `sealed`, unlike its base: nothing derives from it. - **Why it's built this way**: inheriting the read decorator rather than composing a second one avoids duplicating twenty-one forwarders, and keeps the two class-name labels distinct so a profile distinguishes a read issued through the write repository from one issued through a read-only repository. - **Where it's used**: applied by [`RepositoryFactory`](#repositoryfactory)'s `Create` when `UseMiniProfiler` is true (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/Factory/RepositoryFactory.cs:34-39`, documented at `:21-25`). Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EFRepositoryDecoratorTests.cs` and `.../EFRepositoryDecoratorAdditionalTests.cs`. -- **Caveats / not-in-source**: there is no `Save` or `SaveChangesAsync` forwarder here, and none is missing: flushing is [`IUnitOfWork`](#iunitofwork)'s job, not the repository's, so [`IWriteRepository`](#iwriterepositorytentity-tidentifiertype) declares no save member at all (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:367-480`). +- **Caveats / not-in-source**: there is no `Save` or `SaveChangesAsync` forwarder here, and none is missing: flushing is [`IUnitOfWork`](#iunitofwork)'s job, not the repository's, so [`IWriteRepository`](#iwriterepositorytentity-tidentifiertype) declares no save member at all (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:369-482`). + +### EFReadRepository + +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Repositories` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:21` · Level 12 · class (internal) + +- **What it is**: the EF Core implementation of [`IReadRepository`](#ireadrepositorytentity-tidentifiertype), the read half of the repository contract: get by id, get many, projected reads, first-or-default, grouped counts and sums, soft-delete-aware finds, lookups, counts, existence checks, specification-driven reads, and keyset pages, with no mutation surface at all (`EFReadRepository.cs:16-21`). +- **Depends on**: EF Core's `DbContext` and `DbSet`, taken as its one primary-constructor parameter (`EFReadRepository.cs:21-33`); [`IReadRepository`](#ireadrepositorytentity-tidentifiertype) as the contract (declared at `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:332`); [`AuditableBaseEntity`](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype) as the entity constraint (`:24`); [`SpecificationEvaluator`](#specificationevaluator), [`KeysetQueryBuilder`](#keysetquerybuilder), and [`QueryTags`](#querytags) as its composition helpers; [`ApplicationDbContext`](#applicationdbcontext) for the soft-delete filter name (`:41`) and for the engine behind the context, whose [`DataSourceEngineCapabilities`](#datasourceenginecapabilities) drive the existence check and keyset null ordering (`:513-514`); `EntityQueryPipeline.MaxUnboundedResultLimit` for the lookup row ceiling; [`LookupRow`](#lookuprowtid-tname) as the intermediate projection shape for a non-string lookup name; and [`Result`](group-01-result-error-handling.md#result), [`Error`](group-01-result-error-handling.md#error), [`KeysetPageRequest`](group-01-result-error-handling.md#keysetpagerequest), [`KeysetCollectionResult`](group-01-result-error-handling.md#keysetcollectionresultt), [`KeysetCursor`](group-01-result-error-handling.md#keysetcursor), and [`BaseLookup`](group-12-api-hosting-mapping.md#baselookuptidentifiertype) for the shapes it returns. +- **Concept introduced, naming the query filter you drop.** `[Rubric §11, Security]` assesses whether isolation boundaries hold under every code path, and `[Rubric §8, Data Architecture]` assesses whether the query-filter design is deliberate. EF 10 gives global query filters **names**, and the framework registers two on the model: `SoftDelete` and `Tenant` ([ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html)). EF's parameterless `IgnoreQueryFilters()` drops *all* of them. That means a caller who only wanted to see soft-deleted rows would, with the parameterless call, also read across every tenant. So the repository declares a one-element array naming exactly the filter it is willing to drop (`EFReadRepository.cs:35-41`, resolving `ApplicationDbContext.SoftDeleteFilterName` from `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:474`) and every `ignoreQueryFilters: true` path passes it (`:58`, `:87`, `:108`, `:204`, `:394`, `:471`, `:485`, `:582`). This is the kind of detail worth copying wherever you add a filter-bypassing read. +- **Concept, tracked versus no-tracking as an explicit repository decision.** `[Rubric §12, Performance and Scalability]`. The four queryable properties at `EFReadRepository.cs:526-535` are the vocabulary: `Table` (tracked), `TableNoTracking`, `TableNoTrackingSingleQuery`, and `TableNoTrackingSplitQuery`. Read paths default to no-tracking to avoid change-tracker overhead, but two exceptions are load-bearing and both are commented in place, see the walkthrough below. +- **Walkthrough** + - `MaxLookupSelectorCacheEntries = 512` (`EFReadRepository.cs:31`) bounds the lookup-selector cache; past it a selector is built per request rather than cached, which the source calls correct and only slightly slower. + - The protected `_context` field is guarded at construction (`EFReadRepository.cs:33`), and `Entities` is a `virtual` property resolving `_context.Set()` (`:43`). Every member goes through one of those two, which is what makes the class subclassable by [`EFRepository`](#efrepositorytentity-tidentifiertype). + - The private `BaseQuery(asTracking)` helper (`EFReadRepository.cs:550-551`) picks `Table` or `TableNoTracking` and tags the result through [`QueryTags`](#querytags) with the ambient use-case tag. It backs every specification-less read (`GetAllAsync`, `GetProjectedAsync`, `FirstOrDefaultAsync(where, ...)`, `FindIncludingDeletedAsync`, `GetAllForLookupAsync`, `GetByIdsAsync`, `GetByIdAsync(id, includes, asTracking)`, and a specification-less keyset page); `CountByAsync` and `SumByAsync` call it with `asTracking: false` explicitly. Its doc (`:537-547`) records why `Table` and `TableNoTracking` stay untagged: they are the public, overridable extension point a consumer composes its own queries on, and the specification path is tagged inside [`SpecificationEvaluator`](#specificationevaluator) instead, so no query is tagged twice. + - `GetAllAsync` (`EFReadRepository.cs:46-72`) is the widest read: pick tracked or untracked (`:55`), optionally drop the named soft-delete filter (`:57-58`), apply includes (`:60`), then optional `where` (`:62-63`), then optional `orderBy` (`:65-66`), then materialize with or without a `select` (`:68-71`). Every parameter is optional, which is what makes it the general-purpose read the older query paths call. + - `GetProjectedAsync` (`EFReadRepository.cs:75-93`) is the projection-first read: no includes at all, because a projection selects its own columns, and the `Select` is applied in the database rather than after materialization (`:92`). + - `FirstOrDefaultAsync(where, includes, ...)` (`EFReadRepository.cs:96-115`) is the predicate overload, and the comment at `:113` records the point: the predicate goes into EF's `FirstOrDefaultAsync` so the database issues a `TOP 1`, rather than materializing a set and narrowing it in memory. + - `FirstOrDefaultAsync(specification)` (`EFReadRepository.cs:118-130`) is the specification overload, and unlike the aggregate reads it applies the **full** shape, ordering included. The comment at `:124-125` is the reason: "first" is only meaningful against a defined order, and the specification is where that order is declared. + - `CountByAsync` (`EFReadRepository.cs:133-153`) and `SumByAsync` (`:156-181`) are the two grouped aggregates. Both run untracked, apply the optional predicate, group in the database, and project into the private records [`GroupedCount`](#groupedcounttkey) (`:148`) and [`GroupedSum`](#groupedsumtkey) (`:176`) before flattening to a dictionary (`:152`, `:180`). The comment at `:171-173` explains why `SumByAsync` passes the sum selector as `GroupBy`'s *element* selector: it keeps the caller's expression tree intact all the way to the provider. + - `FindIncludingDeletedAsync` (`EFReadRepository.cs:194-220`) returns a named tuple of active and soft-deleted matches. The comment at `:202-203` is the design point: it drops the soft-delete filter and reads **once**, then partitions in memory (`:211-217`), because two separate queries would let a concurrent delete land between them and report the same row in neither half. + - `GetAllForLookupAsync` (`EFReadRepository.cs:223-262`) returns id and name pairs ordered by name, where "name" is a *runtime* property name. `GetOrBuildLookupSelector` (`:321-342`) returns an untyped `LambdaExpression` rather than a fixed `Expression>>`, because `BuildLookupSelector` (`:344-375`) builds one of two projection shapes depending on the resolved property's CLR type: a string property projects straight to `BaseLookup` with its name coalesced to empty (`:366-374`), while anything else projects to [`LookupRow`](#lookuprowtid-tname) carrying the raw value (`:350-364`), because appending `ToString()` to the projection has no server-side translation for a value-object property and throws `InvalidOperationException` for the whole query (the comment at `:251-255`). `GetAllForLookupAsync` pattern-matches the returned `LambdaExpression` (`:241`): the string leg runs `Select`/`OrderBy`/`Take` inline (`:243-248`); the non-string leg resolves the raw name's CLR type by reflection (`:256`) and dispatches, through the cached `ExecuteRawLookupMethod` (`:268-269`), to the public static `ExecuteRawLookupAsync` (`:281-298`), which runs the same ordered, capped projection against `LookupRow` and formats each row's name in memory afterward (`:293-297`). Both legs keep `.Take(EntityQueryPipeline.MaxUnboundedResultLimit)` (`:246`, `:289`) BEFORE materialization, and the comment above the branch names why (`:236-240`, tagged SEC-Common-24): this path never enters `EntityQueryPipeline`, so the framework's row ceiling has to be applied here or a lookup stays the one unpaginated, uncapped read in the framework. Underneath, `GetOrBuildLookupSelector` first resolves the property name to its CANONICAL spelling (`:325-327`, the SEC-Store-14 remark at `:306-313`) before keying `LookupSelectorCache` (`:314`), checks the cache (`:331-332`), builds uncached rather than admitting a new entry once `MaxLookupSelectorCacheEntries` is reached (`:336-337`), and otherwise memoizes through `GetOrAdd` (`:339-341`). Canonicalizing the name first is a fix, not a cosmetic change: reflection resolves a property name case-insensitively, so every case permutation of a real column used to mint its own never-evicted cache entry, 2^N spellings of an N-letter name, from an anonymous endpoint; resolving the `PropertyInfo` first collapses them onto one entry. + - `GetByIdsAsync` (`EFReadRepository.cs:378-400`) materializes the id sequence once, short-circuits on an empty set with an empty result (`:387-389`), and translates to a single `Contains` (in other words a SQL `IN`) rather than N round trips (`:399`). + - `GetByIdAsync(id)` (`EFReadRepository.cs:403-415`) carries the single most important comment in the file (`:409-413`) and it encodes two separate bug fixes. First, it is a **filtered query, not `FindAsync`**: `FindAsync` serves a tracked instance straight out of the identity map without evaluating the global soft-delete filter, so an entity soft-deleted earlier in the same scope came back as if it were live. Second, it queries `Table` (**tracked**) on purpose: [`EFRepository`](#efrepositorytentity-tidentifiertype) inherits this member and the generic delete and update handlers load through it, mutate the instance, and save, so a no-tracking query would turn those writes into silent no-ops. + - `GetByIdAsync(id, includes, asTracking)` (`EFReadRepository.cs:418-431`) is the include-carrying overload and defaults to no-tracking, because a caller asking for includes is normally reading for display. + - The three `CountAsync` overloads (`EFReadRepository.cs:434-458`) cover no predicate, an expression predicate, and a specification. The specification overload composes through [`SpecificationEvaluator`](#specificationevaluator) with `applyShape: false` (`:455`), so includes, ordering, and paging never reach a COUNT. + - The two `ExistsAsync` overloads (`EFReadRepository.cs:463-488`) both funnel into the private `AnyAsync` helper (`:500-506`), which is engine-aware. The remarks at `:490-499` are worth reading before you touch it: the non-relational engine (Cosmos DB) gets `CountAsync` because its provider generates invalid SQL (an unresolved `root` identifier) when translating a predicated `AnyAsync` into a subquery; every relational engine gets `AnyAsync`, which short-circuits at the first match. The cost of the workaround is stated honestly in the same remark: `CountAsync` reads every matching row, so on a wide predicate the Cosmos path is proportional to the number of matches. The switch is a capability, not a provider-name test: `EngineCapabilities` (`:513-514`) reads [`DataSourceEngineCapabilities`](#datasourceenginecapabilities) off the [`ApplicationDbContext`](#applicationdbcontext)'s engine, and `TranslatesAny` (`:517`) is its `IsRelational` flag. A plain `DbContext` that is not a framework context (a directly constructed test double) yields `null`, which the doc at `:508-512` reads as a relational engine that sorts nulls first, so both `TranslatesAny` and `NullsSortFirstAscending` (`:523`) fall back to `true`. + - `ApplyIncludes` (`EFReadRepository.cs:562-565`) is a `protected static` forwarder to [`SpecificationEvaluator`](#specificationevaluator), including the collection-navigation split-query auto-switch. The doc at `:553-561` says why: the logic lives once so the string-include path and the specification path cannot drift apart. + - `BaseQueryFor` (`EFReadRepository.cs:575-584`) is the boundary [`SpecificationEvaluator`](#specificationevaluator) refuses to cross. It reads `AsTracking` and `IgnoreQueryFilters` off a [`QuerySpecification`](group-03-querying-specifications.md#queryspecificationtentity-tidentifiertype) when the specification is one, and gives a plain `ISpecification` the untracked, filtered default. + - The two `ListAsync` overloads (`EFReadRepository.cs:587-615`) apply the specification to that base. In the projecting overload the comment at `:608-609` records the ordering rule: `Select` comes **last**, so ordering and paging run over entity rows and only the resulting page is projected. + - `AnyAsync(specification)` (`EFReadRepository.cs:618-629`) uses criteria only, through the same engine-aware existence check the predicate overloads use (`:625-628`). + - `GetPageByCursorAsync` (`EFReadRepository.cs:632-689`) is the keyset page, and it is the one read that returns a [`Result`](group-01-result-error-handling.md#result) rather than a bare value, because two of its failures are caller errors rather than exceptions. An unknown sort column returns `Error.InvalidEntityField` with the column and type named (`:639-648`); a malformed cursor returns a validation error with code `Error.InvalidCursor` (`:654-664`). Between those it starts from `BaseQuery` or applies the optional specification with `applyShape: false` (`:650-652`), then the seek predicate (`:666`), and the `(sortKey, Id)` ordering (`:669`). The `Take(request.PageSize + 1)` at `:673` is the next-page probe, and the comment at `:671-672` explains the choice: the extra row is never returned, it only says whether a next page exists, which is cheaper and more honest than a COUNT over the whole set. The probe row is trimmed (`:675-677`), and a next cursor is encoded from the last surviving row only when there is more (`:679-686`). + - `TryBuildSeekPredicate` (`EFReadRepository.cs:696-724`) is the private decode-and-build, an instance method because it reads the engine's null ordering: reject a cursor that fails `KeysetCursor.TryDecode` (`:703-704`), reject an id segment that does not parse to `TIdentifierType` (`:706-710`), reject a sort segment that does not parse to the sort property's type (`:712-718`), and otherwise hand the parsed values to [`KeysetQueryBuilder`](#keysetquerybuilder) together with `nullsSortFirstAscending: NullsSortFirstAscending` (`:720-721`). That flag is what makes a nullable sort key page correctly on PostgreSQL, which sorts nulls last ascending where SQL Server and SQLite sort them first. +- **Why it's built this way**: `internal` rather than public, and every member `virtual`, is what lets [`EFRepository`](#efrepositorytentity-tidentifiertype) extend it with writes while consumers hold only the interface. Concentrating the tracking and filter-scope decisions here and pushing pure composition into [`SpecificationEvaluator`](#specificationevaluator) and [`KeysetQueryBuilder`](#keysetquerybuilder) is what keeps this class about *policy* and those about *mechanism*. The contract and its evolution are recorded in [ADR-055](https://ivanball.github.io/docs/adr/055-repository-and-specification-contract.html); the interface segregation into [`IEntityReader`](#ientityreadertentity-tidentifiertype) and [`IEntityQuerier`](#ientityqueriertentity-tidentifiertype) beneath `IReadRepository` is what makes a read-only consumer unable to write at all (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:21`, `:80`, `:332`). +- **Where it's used**: constructed by [`RepositoryFactory`](#repositoryfactory)'s `CreateReadOnly` through a cached `ActivatorUtilities` factory taking the `DbContext` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/Factory/RepositoryFactory.cs:55-56`), optionally wrapped in [`EFReadRepositoryDecorator`](#efreadrepositorydecoratortentity-tidentifiertype) (`:58-63`). Application code reaches it through [`IUnitOfWork`](#iunitofwork)'s `GetReadRepository`, implemented at `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:53-66`. Covered by `EFReadRepositoryReadSurfaceTests.cs`, `EFReadRepositoryKeysetPagingTests.cs`, `EFReadRepositorySpecificationTests.cs`, `EFReadRepositoryGetByIdFilterTests.cs`, `EFReadRepositoryProjectedFilterTests.cs`, `EFReadRepositoryLookupProjectionTests.cs`, and `EFReadRepositoryLookupSecurityTests.cs` under `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Repositories/Read/`. +- **Caveats / not-in-source**: `GetAllForLookupAsync` resolves `nameProperty` by reflection at runtime (`EFReadRepository.cs:325-327`), so an unknown name fails from the expression build rather than at compile time; the source contains no validation of that argument beyond what `Expression.Property` itself enforces. It is capped by `EntityQueryPipeline.MaxUnboundedResultLimit` on both the string and the raw-value leg (`:246`, `:289`), so unlike `FindIncludingDeletedAsync`, which still partitions in memory and materializes every matching row including the deleted ones with no upper bound on that set, a lookup can no longer read an unbounded table. ### EFRepository -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Repositories` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:23` · Level 9 · class (internal sealed) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Repositories` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:23` · Level 13 · class (internal sealed) -- **What it is**: the read-write EF Core repository. It extends [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype) with add, update, concurrency-token, and set-based delete and update operations, implementing [`IRepository`](#irepositorytentity-tidentifiertype) (`EFRepository.cs:10-29`). It stages changes and never saves: the class doc says so outright at `:12`. -- **Depends on**: the base read repository, constructed with the same `DbContext` (`EFRepository.cs:24`, `:27`); a required `TimeProvider` (`:25`) and one **optional** constructor dependency, [`ICurrentUserService`](group-08-auth.md#icurrentuserservice) (`:26`); [`UpdatePropertySetterBuilder`](#updatepropertysetterbuildertentity) for set-based updates (`:113`); [`AuditableAggregateRootEntity`](group-02-domain-building-blocks.md#auditableaggregaterootentitytidentifiertype) as the entity constraint (`:28`); and [`AuditableBaseEntity`](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype), [`IRowVersioned`](group-02-domain-building-blocks.md#irowversioned), and [`IAuditableEntity`](group-02-domain-building-blocks.md#iauditableentity) for the concurrency and audit member names. -- **Concept introduced, the two write paths and why one of them must stamp its own audit fields.** `[Rubric §8, Data Architecture]` assesses whether audit metadata is reliably captured, and `[Rubric §12, Performance & Scalability]` assesses whether a cross-cutting policy holds on every path rather than the common one. The framework's normal write path is change-tracked: load, mutate, save, and the save interceptors stamp `LastModifiedOn` and `LastModifiedBy` and dispatch domain events. The second path is set-based: `ExecuteUpdate` and `ExecuteDelete` issue one SQL statement over matching rows without loading or tracking them, which is far cheaper on a wide update but **bypasses the save pipeline entirely**, interceptors included. Rather than pretend the two paths are the same, this class closes the gap explicitly for audit fields on `ExecuteUpdateAsync` (`EFRepository.cs:140-151`). The class-level `` (`:17-22`) states what the required `TimeProvider` and the optional `ICurrentUserService` are for: both serve `ExecuteUpdateAsync`'s own audit stamping since that path bypasses the save pipeline, and without a user service (direct construction in tests) only the user stamp is skipped, since the clock has no fallback anymore. +- **What it is**: the read-write EF Core repository. It extends [`EFReadRepository`](#efreadrepositorytentity-tidentifiertype) with add, update, concurrency-token, and set-based delete and update operations, implementing [`IRepository`](#irepositorytentity-tidentifiertype) (`EFRepository.cs:10-29`). It stages changes and never saves: the class doc says so outright at `:13`. +- **Depends on**: the base read repository, constructed with the same `DbContext` (`EFRepository.cs:24`, `:27`); a required `TimeProvider` (`:25`) and one **optional** constructor dependency, [`ICurrentUserService`](group-08-auth.md#icurrentuserservice) (`:26`); [`UpdatePropertySetterBuilder`](#updatepropertysetterbuildertentity) for set-based updates (`:135`); [`AuditableAggregateRootEntity`](group-02-domain-building-blocks.md#auditableaggregaterootentitytidentifiertype) as the entity constraint (`:28`); and [`AuditableBaseEntity`](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype), [`IRowVersioned`](group-02-domain-building-blocks.md#irowversioned), and [`IAuditableEntity`](group-02-domain-building-blocks.md#iauditableentity) for the concurrency and audit member names. +- **Concept introduced, the two write paths and why one of them must stamp its own audit fields.** `[Rubric §8, Data Architecture]` assesses whether audit metadata is reliably captured, and `[Rubric §12, Performance & Scalability]` assesses whether a cross-cutting policy holds on every path rather than the common one. The framework's normal write path is change-tracked: load, mutate, save, and the save interceptors stamp `LastModifiedOn` and `LastModifiedBy` and dispatch domain events. The second path is set-based: `ExecuteUpdate` and `ExecuteDelete` issue one SQL statement over matching rows without loading or tracking them, which is far cheaper on a wide update but **bypasses the save pipeline entirely**, interceptors included. Rather than pretend the two paths are the same, this class closes the gap explicitly for audit fields on `ExecuteUpdateAsync` (`EFRepository.cs:140-154`). The class-level `` (`:17-22`) states what the required `TimeProvider` and the optional `ICurrentUserService` are for: both serve `ExecuteUpdateAsync`'s own audit stamping since that path bypasses the save pipeline. The code goes one step further than those remarks: with no current user (no user service, or a background or system scope with no user id), `LastModifiedBy` is stamped with the identifier type's `default` sentinel rather than left alone (`:148-154`), exactly as the save pipeline attributes a system save, because leaving the column untouched would keep crediting the change to the previous human editor. - **Concept, the optional dependency as a testability affordance.** `[Rubric §14, Testability]`. Only [`ICurrentUserService`](group-08-auth.md#icurrentuserservice) defaults to `null` (`EFRepository.cs:26`); `TimeProvider` is a required constructor argument (`:25`), so a test must pass one explicitly (typically `TimeProvider.System` or a fake), while production construction through `ActivatorUtilities` fills both from the container. Taking `TimeProvider` at all, rather than calling `DateTime.UtcNow`, is what makes the timestamp deterministic under test, which is exactly what `EFRepositoryAuditStampTests` exercises. - **Walkthrough** - `AddAsync` and `AddRangeAsync` (`EFRepository.cs:32-43`) are thin guarded forwarders to `Entities.AddAsync` and `AddRangeAsync`. Nothing is saved here; persistence happens at the unit of work's save. - - `UpdateAsync` (`EFRepository.cs:52-65`) has the one non-obvious body in the mutation set. It first asks the change tracker whether this key is already tracked, and if so copies values onto the tracked entry with `CurrentValues.SetValues` instead of attaching a second instance, which would throw an "already tracked" exception (`:44-50`, `:57-59`). The comment at `:55-56` explains the lookup choice: `Entities.Local.FindEntry(entity.Id)` is an O(1) key lookup against the identity map that never falls back to the database, replacing a linear scan of the `LocalView`. Untracked entities go through `Entities.Update`, which attaches and marks modified (`:61`). The method returns `Task.CompletedTask` (`:63`): it is asynchronous only for signature compatibility. + - `UpdateAsync` (`EFRepository.cs:52-65`) has the one non-obvious body in the mutation set. It first asks the change tracker whether this key is already tracked, and if so copies values onto the tracked entry with `CurrentValues.SetValues` instead of attaching a second instance, which would throw an "already tracked" exception (`:46-51`, `:58-60`). The comment at `:56-57` explains the lookup choice: `Entities.Local.FindEntry(entity.Id)` is an O(1) key lookup against the identity map that never falls back to the database, replacing a linear scan of the `LocalView`. Untracked entities go through `Entities.Update`, which attaches and marks modified (`:62`). The method returns `Task.CompletedTask` (`:64`): it is asynchronous only for signature compatibility. - `UpdateRange` (`EFRepository.cs:68-72`) is the guarded batch forwarder. - - The two `SetOriginalRowVersion` overloads (`EFRepository.cs:75-94`) implement optimistic concurrency by writing the client's known row version into the tracked entry's **original** value, so EF's generated UPDATE carries it in the WHERE clause and a concurrent modification raises a concurrency exception instead of silently winning. Both null-guard their arguments and then assign unconditionally (`:76-81`, `:87-92`). The second overload takes an [`IRowVersioned`](group-02-domain-building-blocks.md#irowversioned) child entity and casts to `object` for `Entry` (`:90`), which is how a child of the aggregate gets the same protection without being an aggregate root itself. + - The two `SetOriginalRowVersion` overloads (`EFRepository.cs:75-94`) implement optimistic concurrency by writing the client's known row version into the tracked entry's **original** value, so EF's generated UPDATE carries it in the WHERE clause and a concurrent modification raises a concurrency exception instead of silently winning. Both null-guard their arguments and then assign unconditionally (`:77-82`, `:88-93`). The second overload takes an [`IRowVersioned`](group-02-domain-building-blocks.md#irowversioned) child entity and casts to `object` for `Entry` (`:91`), which is how a child of the aggregate gets the same protection without being an aggregate root itself. - `TouchConcurrencyToken(entity)` (`EFRepository.cs:97-115`) forces a concurrency check on a root that a caller wants to protect even though nothing on it changed. It is a no-op unless the tracked entry's state is exactly `Unchanged`: a root the applier already modified emits its own UPDATE carrying the concurrency predicate already, and marking a property modified on a `Deleted` or `Detached` entry would be wrong rather than merely redundant. When the entry is unchanged, it marks the `LastModifiedOn` audit property `IsModified = true`, which is what puts the row in the generated UPDATE at all; the audit interceptor then fills in the real value, so the write is a genuine edit record rather than a no-op touch, and EF appends `WHERE RowVersion = @original` from the concurrency token. - - `ExecuteDeleteAsync` (`EFRepository.cs:118-124`) is the set-based delete: `Entities.Where(where).ExecuteDeleteAsync(...)`, returning the affected row count (`:101`). - - `ExecuteUpdateAsync` (`EFRepository.cs:127-154`) is the flagship. It guards both arguments (`:110-111`), constructs an [`UpdatePropertySetterBuilder`](#updatepropertysetterbuildertentity) (`:113`), lets the caller describe the assignments against the persistence-agnostic interface (`:114`), and throws `ArgumentException` when nothing was described (`:115-116`). Then it stamps: `LastModifiedOn` from `timeProvider.GetUtcNow().UtcDateTime` unless the caller already set it (`:120-124`), and `LastModifiedBy` from the current user unless the caller already set it and only when a user id is actually available (`:126-129`). Both guards go through `builder.SetsProperty`, so an explicit caller assignment always wins. Finally the recorded assignments are replayed into EF as a method group (`:131`). + - `ExecuteDeleteAsync` (`EFRepository.cs:118-124`) is the set-based delete: `Entities.Where(where).ExecuteDeleteAsync(...)`, returning the affected row count (`:123`). + - `ExecuteUpdateAsync` (`EFRepository.cs:127-157`) is the flagship. It guards both arguments (`:132-133`), constructs an [`UpdatePropertySetterBuilder`](#updatepropertysetterbuildertentity) (`:135`), lets the caller describe the assignments against the persistence-agnostic interface (`:136`), and throws `ArgumentException` when nothing was described (`:137-138`). Then it stamps: `LastModifiedOn` from `timeProvider.GetUtcNow().UtcDateTime` unless the caller already set it (`:142-146`), and `LastModifiedBy` unless the caller already set it (`:151-154`), with `currentUserService?.UserId ?? default`, so a scope with no current user records the default sentinel as the editor (the comment at `:148-150`). Both guards go through `builder.SetsProperty`, so an explicit caller assignment always wins. Finally the recorded assignments are replayed into EF as a method group (`:156`). - `sealed`, and the mutation members are non-virtual: this is the end of the inheritance chain. - **Why it's built this way**: keeping the write members on a subclass of the read repository rather than on a parallel type means the read behavior a write handler relies on (the tracked `GetByIdAsync`, most of all) is literally the same code a query handler runs. The contract is [ADR-055](https://ivanball.github.io/docs/adr/055-repository-and-specification-contract.html), and the split between staging here and flushing in [`IUnitOfWork`](#iunitofwork) is what makes one transaction span several repositories. -- **Where it's used**: constructed by [`RepositoryFactory`](#repositoryfactory)'s `Create` through the same cached `ActivatorUtilities` factory (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/Factory/RepositoryFactory.cs:31-32`), optionally wrapped in [`EFRepositoryDecorator`](#efrepositorydecoratortentity-tidentifiertype) (`:34-39`). Application code reaches it through [`IUnitOfWork`](#iunitofwork)'s `GetRepository` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:33`). Covered by `EFRepositoryAdditionalTests.cs`, `EFRepositoryAuditStampTests.cs`, and `EFRepositoryIntegrationTests.cs` under `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/`. -- **Caveats / not-in-source**: `ExecuteUpdateAsync` and `ExecuteDeleteAsync` bypass the save pipeline, so beyond the two audit columns stamped here they raise **no** domain events and run **no** other interceptor, the tenant guard included. The source stamps the audit fields and nothing else; whether a given set-based call site needed an event is a judgement the framework does not make for you. +- **Where it's used**: constructed by [`RepositoryFactory`](#repositoryfactory)'s `Create` through the same cached `ActivatorUtilities` factory (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/Factory/RepositoryFactory.cs:31-32`), optionally wrapped in [`EFRepositoryDecorator`](#efrepositorydecoratortentity-tidentifiertype) (`:34-39`). Application code reaches it through [`IUnitOfWork`](#iunitofwork)'s `GetRepository` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:33`). Covered by `EFRepositoryAdditionalTests.cs`, `EFRepositoryAuditStampTests.cs`, `EFRepositoryConcurrencyTouchTests.cs`, and `EFRepositoryIntegrationTests.cs` under `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Repositories/`. +- **Caveats / not-in-source**: `ExecuteUpdateAsync` and `ExecuteDeleteAsync` bypass the save pipeline, so beyond the two audit columns stamped here they raise **no** domain events and run **no** other interceptor, the tenant guard included. The source stamps the audit fields and nothing else; whether a given set-based call site needed an event is a judgement the framework does not make for you. The class-level remarks still say that without a user service "the user stamp is skipped" (`EFRepository.cs:20-21`), which no longer matches the body: `LastModifiedBy` is always stamped unless the caller set it, with `default` when no user id is available (`:151-154`). Trust the method body over the remark. ### CosmosIntIdValueGenerator @@ -6860,7 +7568,7 @@ survives a module being pulled out into its own service. - **`GeneratesTemporaryValues => false`** (`CosmosIntIdValueGenerator.cs:21`): the value this generator returns is the real stored key, not an EF placeholder to be replaced after the insert. That distinction matters elsewhere in this group: [`DbContextFactory`](#dbcontextfactory) reads the *temporary* flag on SQL Server keys to tell an application-supplied id from an EF-assigned one, and only the non-temporary ones need an `IDENTITY_INSERT` round (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:402-406`). - **`Next(EntityEntry entry)`** (`CosmosIntIdValueGenerator.cs:24-25`): `Interlocked.Increment(ref _seed)`. Lock-free and thread-safe, which is what you want on a member called once per inserted entity. The `entry` argument is ignored, so every Cosmos entity type in the process draws from the same counter. - **Why it's built this way**: the counter is deliberately process-local. A durable sequence would need a round trip to the database per insert, which is exactly the cost a Cosmos-shaped workload is trying to avoid, and the class remarks accept the trade-off explicitly (`CosmosIntIdValueGenerator.cs:11-15`). -- **Where it's used**: installed by the Cosmos branch of `EntityTypeConfiguration.ApplyEngineConventions` (`EntityTypeConfiguration.cs:99`) for every entity whose id is value-generated; pinned by `CosmosIntIdValueGeneratorTests` (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/ValueGenerators/CosmosIntIdValueGeneratorTests.cs:6`). +- **Where it's used**: installed by `CosmosDataSourceEngine.ApplyKeyAndTableMapping` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/CosmosDataSourceEngine.cs:98-99`), which `EntityTypeConfiguration.ApplyEngineConventions` reaches through `DataSourceEngines.For(engine)` (`EntityTypeConfiguration.cs:77`), for every entity whose id is value-generated; pinned by `CosmosIntIdValueGeneratorTests` (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/ValueGenerators/CosmosIntIdValueGeneratorTests.cs:6`). - **Caveats / not-in-source**: the class remarks state the limit plainly (`CosmosIntIdValueGenerator.cs:14`): two processes seeded within the same second, or two processes whose counters drift into each other, can mint the same id, and the suggested remedy is a `Guid` alias for entities that need true uniqueness. Whether any deployed host currently stores entities in Cosmos is Not determinable from source: SQL Server is the engine every host in this workspace configures. ### TenantDataSourceOverrideSettings @@ -6873,10 +7581,10 @@ survives a module being pulled out into its own service. - **Walkthrough** - Five nullable `{ get; init; }` strings, one per engine plus the Cosmos database name: `SQLServerConnectionString` (`TenancySettings.cs:141`), `PostgreSQLConnectionString` (`:144`), `SqliteConnectionString` (`:147`), `CosmosConnectionString` (`:150`). - `CosmosDatabaseName` (`:153`) is optional: when omitted the shared source's database name is kept, which is how one Cosmos account can serve per-tenant databases (`:149-152`). - - The read path is a record `with` clone, and it is the interesting part. `DbContextFactory.ResolveTenantOverride` bails out unless there is a resolved tenant, bound tenancy settings, an entry for that tenant, and an entry for that source name (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:164-170`), picks the connection string for the source's engine through `TenancySettingsValidator.ConnectionStringFor` (`:154`, resolver at `TenancySettingsValidator.cs:122-130`), and returns null when the tenant overrides this source on a different engine only (`DbContextFactory.cs:173-177`). Otherwise it clones the shared [`PhysicalDataSource`](#physicaldatasource) with the new connection string and, if supplied, the new Cosmos database name (`:161-168`). - - The clone keeps the ORIGINAL [`DataSourceKey`](#datasourcekey), and the comment above it explains why (`DbContextFactory.cs:156-161`): EF's model cache is keyed on that key, so replacing only the connection string is what lets one compiled model serve every tenant's database. + - The read path is a record `with` clone, and it is the interesting part. `DbContextFactory.ResolveTenantOverride` bails out unless there is a resolved tenant, bound tenancy settings, an entry for that tenant, and an entry for that source name (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:170-176`), picks the connection string for the source's engine through `TenancySettingsValidator.ConnectionStringFor` (`:154`, resolver at `TenancySettingsValidator.cs:123-124`), and returns null when the tenant overrides this source on a different engine only (`DbContextFactory.cs:179-183`). Otherwise it clones the shared [`PhysicalDataSource`](#physicaldatasource) with the new connection string and, if supplied, the new Cosmos database name (`:161-168`). + - The clone keeps the ORIGINAL [`DataSourceKey`](#datasourcekey), and the comment above it explains why (`DbContextFactory.cs:162-167`): EF's model cache is keyed on that key, so replacing only the connection string is what lets one compiled model serve every tenant's database. - **Why it's built this way**: `[Rubric §12, Performance & Scalability]` is the reason for the key-preserving clone. A per-tenant `DataSourceKey` would build and cache a separate EF model per tenant, which multiplies startup cost and memory by the tenant count for no schema difference. -- **Where it's used**: [`DbContextFactory`](#dbcontextfactory) at context-creation time (`DbContextFactory.cs:102-110`), and [`TenancySettingsValidator`](#tenancysettingsvalidator) at startup, which rejects an entry that declares no connection string at all (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:76-86`) and an entry whose key is not a real physical source name for the engine it declares (`:93-104`, with the round-trip test at `:117-119`). The second check exists because the alternative is a silent fall back to the shared database, which is precisely the failure database-per-tenant is bought to prevent. +- **Where it's used**: [`DbContextFactory`](#dbcontextfactory) at context-creation time (`DbContextFactory.cs:104-112`), and [`TenancySettingsValidator`](#tenancysettingsvalidator) at startup, which rejects an entry that declares no connection string at all (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:77-87`) and an entry whose key is not a real physical source name for the engine it declares (`:93-104`, with the round-trip test at `:117-119`). The second check exists because the alternative is a silent fall back to the shared database, which is precisely the failure database-per-tenant is bought to prevent. - **Caveats / not-in-source**: an override is keyed by **physical** source name (the name [`IDataSourceResolver`](#idatasourceresolver) produces), not the logical name a module uses (`TenancySettings.cs:123-128`). That distinction is invisible in a single-database host, where everything collapses onto `Default`. ### TenantResolutionStrategy @@ -6891,9 +7599,9 @@ survives a module being pulled out into its own service. - `Header = 1` (`:20`), read via `context.Request.Headers[settings.HeaderName].FirstOrDefault()` (`TenantResolutionMiddleware.cs:112`); the header defaults to `X-Tenant-Id` (`TenancySettings.cs:89`). - `Host = 2` (`:27`), which maps to `null` in the middleware switch (`TenantResolutionMiddleware.cs:113`) and is skipped rather than guessed at. - The order is read through `EffectiveResolutionOrder`, not the bound list: an empty `ResolutionOrder` falls back to the framework default pair (`TenancySettings.cs:76-77`). That indirection exists because the configuration binder ADDS to a pre-populated collection rather than replacing it, so a non-empty default would leave a host that configured its own order also running the framework's entries (`:42-48`). -- **Concept, a declared-but-unimplemented member that cannot silently no-op.** `[Rubric §9, API & Contract Design]`: the member exists so the configuration contract is stable when host-based resolution ships (`TenancySettings.cs:22-26`). `[Rubric §15, Best Practices & Code Quality]`: selecting it is not accepted quietly. [`TenancySettingsValidator`](#tenancysettingsvalidator) walks the effective resolution order and fails options validation with a message naming the value as defined but not implemented (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:54-65`), so the host refuses to boot instead of resolving nothing on every request. That is the fail-fast configuration contract ([ADR-070](https://ivanball.github.io/docs/adr/070-fail-fast-configuration-contract.html)) applied to a rule data annotations cannot express. +- **Concept, a declared-but-unimplemented member that cannot silently no-op.** `[Rubric §9, API & Contract Design]`: the member exists so the configuration contract is stable when host-based resolution ships (`TenancySettings.cs:22-26`). `[Rubric §15, Best Practices & Code Quality]`: selecting it is not accepted quietly. [`TenancySettingsValidator`](#tenancysettingsvalidator) walks the effective resolution order and fails options validation with a message naming the value as defined but not implemented (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:55-66`), so the host refuses to boot instead of resolving nothing on every request. That is the fail-fast configuration contract ([ADR-070](https://ivanball.github.io/docs/adr/070-fail-fast-configuration-contract.html)) applied to a rule data annotations cannot express. - **Why it's built this way**: shipping the enum member ahead of the implementation keeps the JSON contract additive, and pairing it with a boot-time rejection removes the only real risk of doing that. -- **Where it's used**: [`TenantResolutionMiddleware`](group-12-api-hosting-mapping.md#tenantresolutionmiddleware) (`TenantResolutionMiddleware.cs:111-113`), `TenancySettings.EffectiveResolutionOrder` (`TenancySettings.cs:76-77`), and [`TenancySettingsValidator`](#tenancysettingsvalidator) (`TenancySettingsValidator.cs:56-64`). +- **Where it's used**: [`TenantResolutionMiddleware`](group-12-api-hosting-mapping.md#tenantresolutionmiddleware) (`TenantResolutionMiddleware.cs:111-113`), `TenancySettings.EffectiveResolutionOrder` (`TenancySettings.cs:76-77`), and [`TenancySettingsValidator`](#tenancysettingsvalidator) (`TenancySettingsValidator.cs:57-65`). ### DesignTimeDbContextOptions @@ -6925,47 +7633,17 @@ survives a module being pulled out into its own service. - **Why it's built this way**: `private sealed` and nested means it exists only for the helper's own call path and cannot become a public extension point by accident. It is constructed from the caller's `ConfigurationAssemblies` list with a spread so later mutation of the options object cannot change the model (`DesignTimeDbContextHelper.cs:129`). - **Where it's used**: built once per design-time context and used three ways, registered as the DI-visible [`IEntityConfigurationAssemblyProvider`](#ientityconfigurationassemblyprovider) (`DesignTimeDbContextHelper.cs:193`), passed directly to the context constructor (`:57`, `:78`), and handed to the [`EntityDataSourceRegistry`](#entitydatasourceregistry) that decides which entities belong to which physical source (`:110`). -### RestrictDeleteByDefaultConvention - -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Conventions` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/RestrictDeleteByDefaultConvention.cs:41` · Level 1 · class (public sealed) - -- **What it is**: a model-finalizing convention that stamps every foreign key in the model with the source of its delete behavior, and restricts the ones nobody configured. It walks every declared foreign key once and defaults an unconfigured or convention-sourced cascade to `DeleteBehavior.Restrict`, unless the foreign key belongs to an owned-type relationship, whose cascade EF itself owns (`RestrictDeleteByDefaultConvention.cs:41-107`). -- **Depends on**: EF Core's `IModelFinalizingConvention`, `IConventionModelBuilder`, `IConventionForeignKey`, `ConfigurationSource` and `DeleteBehavior`, plus [`DataSource`](#datasource) (its one primary-constructor parameter, `RestrictDeleteByDefaultConvention.cs:41`). -- **Concept introduced, defaulting to the safe cascade instead of the convenient one.** `[Rubric §8, Data Architecture]` assesses whether the storage design prevents the failure modes its own conventions could otherwise create, and `[Rubric §15, Best Practices and Code Quality]` assesses whether a default is a deliberate choice rather than whatever the underlying framework happens to ship. EF Core's own convention cascades a required foreign key by default, which means a relationship nobody explicitly configured silently deletes dependents when the principal is deleted. This convention overrides that default: an unconfigured or convention-sourced delete behavior becomes `Restrict`, so a delete that would fan out across rows the domain never asked to remove fails loudly at the database instead of succeeding silently. -- **Walkthrough** - - `DeleteBehaviorSourceAnnotation`, `ExplicitSource`, `ConventionSource` and `OwnershipSource` (`RestrictDeleteByDefaultConvention.cs:188-197`) are the four public constants: the annotation name the convention stamps on every foreign key, and the three values it can carry, recording whether the behavior came from an explicit configuration call, from this convention, or from EF's ownership handling. - - `ProcessModelFinalizing` (`RestrictDeleteByDefaultConvention.cs:199-221`) null-guards the model builder, returns immediately for `DataSource.CosmosDB` (`:206-209`, the same engine carve-out [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) and [`CrossDataSourceDegradeConvention`](#crossdatasourcedegradeconvention) make), then materializes every declared foreign key across the model into a list before applying anything (`:213-215`). The comment names why: the cross-source convention runs first and may already have removed relationships, so nothing here should observe a collection mid-mutation. - - `Apply` (`RestrictDeleteByDefaultConvention.cs:228-247`) is the per-foreign-key decision. An ownership foreign key is stamped `OwnershipSource` and left untouched (`:230-234`): EF owns that cascade, and overriding it would fight the owned-type feature. Otherwise the foreign key's existing `ConfigurationSource` is read; `null` (nobody ever set one) or `ConfigurationSource.Convention` (EF's own required-FK rule produced the cascading default) are both treated as "inherited, and ours to replace": the behavior is set to `DeleteBehavior.Restrict` and stamped `ConventionSource` (`:239-244`). Anything else, meaning a configuration explicitly set a delete behavior, is stamped `ExplicitSource` and left exactly as configured (`:246`). -- **Why it's built this way**: the annotation is not cosmetic. Stamping the source of every delete behavior, rather than just flipping the ones that need it, gives a fitness test a byte-for-byte answer to "did this foreign key's cascade come from an explicit choice, from this convention, or from ownership", which is what [ADR-119](https://ivanball.github.io/docs/adr/119-restrict-delete-by-default.html) records as the decision: default every unconfigured relationship to `Restrict` rather than to EF's own cascading default, and make the source of that decision inspectable rather than implicit. Running at model finalizing, after every module's `IEntityTypeConfiguration` has declared its own relationships, is what lets the convention see the whole model without needing to know which modules exist, the same timing [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) uses for the same reason. -- **Where it's used**: registered by [`ApplicationDbContext`](#applicationdbcontext) in `ConfigureConventions` with the context's own engine, alongside the other model-finalizing conventions. Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Conventions/RestrictDeleteByDefaultConventionTests.cs` and by `MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Domain/DeleteBehaviorConventionTests.cs`, which subclasses the shared `DeleteBehaviorConventionTestsBase` and the `ArchitectureRules.DeleteBehavior` rule group so ADC's own model is checked against the same contract. - ### TenantEntrySettings > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Tenancy` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettings.cs:121` · Level 1 · class (sealed) - **What it is**: one declared tenant, bound from `Tenancy:Tenants:{tenantId}`. It holds exactly one member: that tenant's per-data-source connection overrides (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettings.cs:118-130`). - **Depends on**: [`TenantDataSourceOverrideSettings`](#tenantdatasourceoverridesettings), the value type of its dictionary (`TenancySettings.cs:129`, declared at `:138`). Held by [`TenancySettings`](#tenancysettings)`.Tenants` (`:115`). -- **Concept introduced, declaring a tenant is only required for the database-per-tenant case.** This is the single most important thing to read off this class, and it is stated in its own doc (`TenancySettings.cs:109-114`). A shared-schema tenant needs NO entry here at all, because its isolation comes from the global query filter and the [`TenantSaveChangesInterceptor`](#tenantsavechangesinterceptor), which `AddInfrastructure` registers unconditionally with a comment saying why (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:66-69`). Configuration is therefore only how a tenant is PROMOTED to its own database. `[Rubric §8, Data Architecture]` assesses how deliberately data is partitioned. The dictionary is keyed by PHYSICAL data source name (the name [`IDataSourceResolver`](#idatasourceresolver) produces, for example `Default` or `Conference`), and the key choice is load-bearing enough that [`TenancySettingsValidator`](#tenancysettingsvalidator) fails the boot on a key that does not round-trip. A tenant can override some sources and share others: a source with an entry is routed to the tenant's own database, a source without one stays shared (`TenancySettings.cs:123-128`). +- **Concept introduced, declaring a tenant is only required for the database-per-tenant case.** This is the single most important thing to read off this class, and it is stated in its own doc (`TenancySettings.cs:109-114`). A shared-schema tenant needs NO entry here at all, because its isolation comes from the global query filter and the [`TenantSaveChangesInterceptor`](#tenantsavechangesinterceptor), which `AddInfrastructure` registers unconditionally with a comment saying why (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:67-70`). Configuration is therefore only how a tenant is PROMOTED to its own database. `[Rubric §8, Data Architecture]` assesses how deliberately data is partitioned. The dictionary is keyed by PHYSICAL data source name (the name [`IDataSourceResolver`](#idatasourceresolver) produces, for example `Default` or `Conference`), and the key choice is load-bearing enough that [`TenancySettingsValidator`](#tenancysettingsvalidator) fails the boot on a key that does not round-trip. A tenant can override some sources and share others: a source with an entry is routed to the tenant's own database, a source without one stays shared (`TenancySettings.cs:123-128`). - **Walkthrough** - `DataSources` (`TenancySettings.cs:129`): a get-only `Dictionary` bound from `Tenancy:Tenants:{tenantId}:DataSources:{sourceName}`. Get-only is the correct shape for a bound dictionary, since the configuration binder populates an existing instance rather than assigning a new one. - **Why it's built this way**: keeping the per-tenant overrides one level below the tenant, rather than flattening connection strings onto the tenant entry, is what lets a single tenant be physically separated on one source while remaining shared on the others, which is the mixed model [ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html) records. -- **Where it's used**: read by [`DbContextFactory`](#dbcontextfactory)`.ResolveTenantOverride`, which looks up the current tenant then the requested source and clones the shared `PhysicalDataSource` with the tenant's connection string while keeping the ORIGINAL `DataSourceKey`, so one compiled EF model serves every tenant's database (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:162-187`). Also expanded by [`TenantDataSourceTargets`](#tenantdatasourcetargets)`.Expand`, which turns the shared sources plus every (tenant, overridden source) pair into the list the outbox and cleanup background services sweep (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:68-78`), and validated per entry by [`TenancySettingsValidator`](#tenancysettingsvalidator) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:71-106`). - -### IndexBuilderExtensions - -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Configuration` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/IndexBuilderExtensions.cs:10` · Level 2 · class (public static, extension container) - -- **What it is**: a single C# `extension(IndexBuilder)` block exposing one member, `HasSoftDeleteFilter(...)`, which attaches the `IsDeleted = 0` predicate to a hand-authored index in an entity type configuration (`IndexBuilderExtensions.cs:10-12`, member at `:50-64`). -- **Depends on**: [`SoftDeleteFilterSql`](#softdeletefiltersql) (the shared predicate builder, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:27`), the [`DataSource`](#datasource) engine enum, and EF Core's `Microsoft.EntityFrameworkCore.Metadata.Builders.IndexBuilder`. -- **Concept introduced, the filtered (partial) index under soft delete.** `[Rubric §8, Data Architecture]` assesses whether the storage design accounts for the consequences of its own conventions, and `[Rubric §12, Performance and Scalability]` assesses whether indexes match the queries they serve. Soft delete ([ADR-005](https://ivanball.github.io/docs/adr/005-soft-delete-vs-erasure.html)) means a "deleted" row is still physically present with `IsDeleted = 1`, and the global query filter on [`ApplicationDbContext`](#applicationdbcontext) hides it from every read. An index that does not carry the same predicate therefore indexes rows no query will ever return: the deleted rows still occupy index pages, and the optimizer's row estimates include them. The doc comment states this in one sentence (`IndexBuilderExtensions.cs:14-18`). Adding `WHERE [IsDeleted] = 0` to the index makes it a *filtered* index (SQL Server's term; SQLite calls the same thing a partial index) that matches the shape of every query the application actually issues. -- **Walkthrough** - - The extension receiver is the `IndexBuilder` returned by `builder.HasIndex(...)`, so the call chains directly off the index declaration (`IndexBuilderExtensions.cs:12`, usage sample at `:23-26`). - - `engine` defaults to `DataSource.SQLServer` (`IndexBuilderExtensions.cs:53`), which matches the majority case of a configuration deriving from [`EntityTypeConfigurationSQLServer`](#entitytypeconfigurationsqlservertentity-tidentifiertype); a SQLite or PostgreSQL configuration passes `DataSource.Sqlite` or `DataSource.PostgreSQL` explicitly. PostgreSQL needs it for a real reason, not just symmetry: its soft-delete flag is a genuine boolean column, so the predicate the engine produces is `"IsDeleted" = false` rather than SQL Server's `[IsDeleted] = 0` (`IndexBuilderExtensions.cs:37-43`). - - `additionalFilter` is the optional second predicate for an index that is already conditional on something else, for example `"[ExternalSessionId] IS NOT NULL"` (`IndexBuilderExtensions.cs:45-50`). The two are joined as `{additionalFilter} AND {filterSql}` in that exact order (`:62-65`), which is deliberate: it reproduces the SQL of the hand-authored literal the helper replaced, so switching to the helper does not force a migration. It is also the order [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) uses when it appends its own clause, so the two paths yield byte-identical SQL for the same pair of predicates. - - The body null-guards the receiver, asks [`SoftDeleteFilterSql`](#softdeletefiltersql) to build the predicate from the model's own metadata, and returns the builder unchanged when the builder returns `null` (`IndexBuilderExtensions.cs:56-60`). That `null` is the Cosmos no-op and the "this entity is not soft-deletable" case, so the call is always safe to write. -- **Why it's built this way**: the doc comment gives the rationale directly (`IndexBuilderExtensions.cs:27-29`). A literal `HasFilter("[IsDeleted] = 0")` hard-codes both the column name and SQL Server's bracket quoting; reading the column name from the model means a `HasColumnName` rename follows automatically, and delegating the quoting to the engine keeps the same configuration body valid on SQLite. This is the portability posture of [ADR-018](https://ivanball.github.io/docs/adr/018-polyglot-persistence.html). The division of labour with [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) is explicit: the convention covers every UNIQUE index automatically, and this extension point exists for the case the convention deliberately leaves alone, a hand-authored NON-unique index (`IndexBuilderExtensions.cs:20-22`). The pair of them is [ADR-095](https://ivanball.github.io/docs/adr/095-soft-delete-unique-indexes.html). -- **Where it's used**: entity configurations across both applications. In MMCA.Store: `MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Infrastructure/Persistence/EntityConfiguration/OrderConfiguration.cs:44,51,58` (the last one with `additionalFilter: "[StripeSessionId] IS NOT NULL"`), `MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Infrastructure/Persistence/EntityConfiguration/UserConfiguration.cs:69,83`, `.../EntityConfiguration/CustomerConfiguration.cs:82`, `MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Infrastructure/Persistence/EntityConfiguration/ProductConfiguration.cs:43`, `.../ProductImageConfiguration.cs:54`, `.../ProductVariantConfiguration.cs:46`, `.../CategoryConfiguration.cs:33`. In MMCA.ADC's Engagement module: `MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/CheckIns/CheckInConfiguration.cs:51,56,62,66,69`, `.../PointsEntryConfiguration.cs:48,52`, `.../LivePollVoteConfiguration.cs:37`, `.../LeaderboardOptInConfiguration.cs:35`, `.../SessionQuestionUpvoteConfiguration.cs:34`, `.../UserSessionBookmarkConfiguration.cs:34,39`. Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/IndexBuilderExtensionsTests.cs`. -- **Caveats / not-in-source**: the ordering constraint is called out in the doc comment (`IndexBuilderExtensions.cs:31-35`). Unlike the convention, which runs at model finalizing, this member reads the column name at the moment it is called, so a `HasColumnName` on the soft-delete property must be declared before the index. That only bites a model that renames the column. +- **Where it's used**: read by [`DbContextFactory`](#dbcontextfactory)`.ResolveTenantOverride`, which looks up the current tenant then the requested source and clones the shared `PhysicalDataSource` with the tenant's connection string while keeping the ORIGINAL `DataSourceKey`, so one compiled EF model serves every tenant's database (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:168-193`). Also expanded by [`TenantDataSourceTargets`](#tenantdatasourcetargets)`.Expand`, which turns the shared sources plus every (tenant, overridden source) pair into the list the outbox and cleanup background services sweep (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:68-78`), and validated per entry by [`TenancySettingsValidator`](#tenancysettingsvalidator) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:72-107`). ### NullDomainEventDispatcher @@ -6978,22 +7656,6 @@ survives a module being pulled out into its own service. - **Why it's built this way**: it is `private sealed` and nested so it can never be resolved by a production host by mistake. Registering it as the singleton `IDomainEventDispatcher` (`DesignTimeDbContextHelper.cs:146`) is what lets the design-time container build the same interceptor set the runtime builds, which is the property that keeps a scaffolded migration honest: the design-time pipeline differs from the runtime pipeline in nothing that touches the model. - **Where it's used**: registered once in `BuildDesignTimeServices` (`DesignTimeDbContextHelper.cs:146`); nowhere else in the codebase. -### SoftDeleteUniqueIndexConvention - -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Conventions` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:33` · Level 2 · class (public sealed) - -- **What it is**: an EF Core model-finalizing convention that puts the `IsDeleted = 0` predicate on every **unique** index of every soft-deletable entity type in the model, so a soft-deleted row stops occupying its unique slot (`SoftDeleteUniqueIndexConvention.cs:10-33`). -- **Depends on**: EF Core's `IModelFinalizingConvention`, `IConventionModelBuilder` and `IConventionEntityType`; [`SoftDeleteFilterSql`](#softdeletefiltersql) for the predicate text; the [`DataSource`](#datasource) engine enum, taken as its one primary-constructor parameter (`SoftDeleteUniqueIndexConvention.cs:33`); and [`IAuditableEntity`](group-02-domain-building-blocks.md#iauditableentity) as the marker for "this entity is soft-deletable". -- **Concept introduced, the model-finalizing convention as a cross-cutting model rule.** `[Rubric §6, CQRS & Event-Driven Design]` assesses whether policies that apply to every entity are expressed once rather than repeated per configuration, and `[Rubric §8, Data Architecture]` assesses whether the data model's invariants actually hold. EF exposes convention hooks that run while the model is being built; `IModelFinalizingConvention` is the last of them, which means it observes the model *after* every module's `IEntityTypeConfiguration` has declared its indexes. That timing is what makes a blanket rule possible: the convention does not need to know which modules exist or what they declared, it just walks the finished model. The bug it closes is a genuine soft-delete trap, stated in the doc comment (`SoftDeleteUniqueIndexConvention.cs:12-16`): soft-delete a speaker, and the unique index on email still blocks creating a new speaker with that email, because the row is invisible to the application but entirely visible to the database's uniqueness check. Adding the filter aligns what the database enforces with what the application shows. -- **Walkthrough** - - `ProcessModelFinalizing` (`SoftDeleteUniqueIndexConvention.cs:36-50`) null-guards the model builder, then returns immediately when the engine is `DataSource.CosmosDB` (`:42-43`): Cosmos has no filtered-index concept, so the convention is a documented no-op there (`:27-30`). - - The entity selection is two predicates (`SoftDeleteUniqueIndexConvention.cs:45-46`): the CLR type must be assignable to [`IAuditableEntity`](group-02-domain-building-blocks.md#iauditableentity), and the entity type must not be owned. Owned types share their owner's table and have no independent index story, so they are excluded. - - `ApplyFilterToUniqueIndexes` (`SoftDeleteUniqueIndexConvention.cs:52-81`) asks [`SoftDeleteFilterSql`](#softdeletefiltersql) for the predicate and bails when it is `null` (`:56-58`), then walks the entity's indexes, skipping every non-unique one (`:60-63`). - - For a unique index the convention branches three ways on the existing filter. No filter at all: set the soft-delete predicate (`SoftDeleteUniqueIndexConvention.cs:65-70`). A filter that already constrains the soft-delete column, whether a hand-authored literal or the output of `HasSoftDeleteFilter`: leave it exactly as it is, detected by `SoftDeleteFilterSql.ContainsPredicate` (`:74-75`, the helper at `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:52`). Anything else: **append** the clause as `{existingFilter} AND {filterSql}` (`:79`). - - That append is the load-bearing part, and the doc comment explains why it replaced the earlier "skip an index that already has a filter" behavior (`SoftDeleteUniqueIndexConvention.cs:17-26`). Skipping meant the exact partial-unique indexes a model bothered to hand-author (for example one narrowed on `[DedupKey] IS NOT NULL`) were the only ones a soft-deleted row could keep blocking. The `ContainsPredicate` check is what keeps appending idempotent: without it, a second model build would produce `... AND [IsDeleted] = 0 AND [IsDeleted] = 0`. -- **Why it's built this way**: the comment at `SoftDeleteUniqueIndexConvention.cs:54-55` names the reason the predicate comes from [`SoftDeleteFilterSql`](#softdeletefiltersql) rather than from a local string: it is the same builder the opt-in extension reaches, so the automatic path and the hand-authored path can never disagree about column name or identifier quoting, and the `AND` ordering at `:77-78` is chosen to match `HasSoftDeleteFilter(additionalFilter:)` byte for byte. Restricting the automatic behavior to unique indexes is a deliberate blast-radius choice: a unique index without the filter is a correctness bug under soft delete, while a non-unique index without it is only a performance question, and performance questions belong to whoever wrote the index. The whole decision, including the 2026-08-26 revision from skipping to appending, is [ADR-095](https://ivanball.github.io/docs/adr/095-soft-delete-unique-indexes.html); soft delete as a policy is [ADR-005](https://ivanball.github.io/docs/adr/005-soft-delete-vs-erasure.html); covering SQL Server and SQLite while skipping Cosmos is the engine-portability posture of [ADR-018](https://ivanball.github.io/docs/adr/018-polyglot-persistence.html). -- **Where it's used**: registered by [`ApplicationDbContext`](#applicationdbcontext) in `ConfigureConventions` with the context's own engine (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:387`), immediately after [`CrossDataSourceDegradeConvention`](#crossdatasourcedegradeconvention). The comment there records the ordering intent and the precedence rule: it runs at finalization, after module configurations have declared their indexes, and hand-authored index filters are respected (`ApplicationDbContext.cs:384-386`). Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Conventions/SoftDeleteUniqueIndexConventionTests.cs`. - ### TenancySettings > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Tenancy` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettings.cs:50` · Level 2 · class (sealed) @@ -7002,7 +7664,7 @@ survives a module being pulled out into its own service. - **Depends on**: [`TenantResolutionStrategy`](#tenantresolutionstrategy) (the strategy enum at the top of the same file, `:6-28`) and [`TenantEntrySettings`](#tenantentrysettings) (the value type of `Tenants`, `:115`). No externals, and deliberately no relational data annotations: the checks that matter here are relational, so they live in [`TenancySettingsValidator`](#tenancysettingsvalidator). - **Concept introduced, "empty means the default" for bound collections.** This is a genuine configuration-binder trap and the class documents it explicitly (`TenancySettings.cs:41-48`). The .NET configuration binder ADDS to a pre-populated collection rather than replacing it. If `ResolutionOrder` shipped pre-filled with `[Claim, Header]`, a host that configured its own order would end up running the framework's entries as well. The resolution is a pair of properties: the bound list starts empty (`:73`, `:103`), and the framework reads a computed `Effective*` projection that substitutes a private static default when the bound list is empty (`:76-77`, `:106-107`). `[Rubric §11, Security]` assesses where trust boundaries are drawn. The two implemented strategies are not equivalent and the enum says so: `Claim` is the trustworthy source because the claim was signed by the token issuer, so a caller cannot pick its own tenant (`:8-13`), while `Header` is intended for service-to-service calls behind a trusted gateway, and a public edge that honors it lets any caller name any tenant (`:15-20`). The default order tries `Claim` first (`:56-57`). `RequireTenant` defaults to `true` and is documented as failing closed, because with tenancy switched on an unscoped request would read across every tenant (`:91-96`). - `[Rubric §15, Best Practices & Code Quality]` assesses whether a new capability is additive for existing hosts. `Enabled` gates RESOLUTION, not isolation: the global query filter and the [`TenantSaveChangesInterceptor`](#tenantsavechangesinterceptor) are always registered and are inert whenever no tenant is resolved (`:35-40`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:66-69`, and the same point restated on `AddMultiTenancy` itself at `:505-509`). A host that never enables tenancy keeps exactly the behavior it had before tenancy shipped, and a host that does enable it can never accidentally leave the write-side guard off. + `[Rubric §15, Best Practices & Code Quality]` assesses whether a new capability is additive for existing hosts. `Enabled` gates RESOLUTION, not isolation: the global query filter and the [`TenantSaveChangesInterceptor`](#tenantsavechangesinterceptor) are always registered and are inert whenever no tenant is resolved (`:35-40`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:67-70`, and the same point restated on `AddMultiTenancy` itself at `:505-509`). A host that never enables tenancy keeps exactly the behavior it had before tenancy shipped, and a host that does enable it can never accidentally leave the write-side guard off. - **Walkthrough** - `SectionName = "Tenancy"` (`TenancySettings.cs:53`), the one public static field. - `DefaultResolutionOrder` (`:56-57`): private static `[Claim, Header]`. `DefaultExcludedPathPrefixes` (`:60-61`): private static `["/health", "/alive", "/.well-known"]`, the probe and discovery endpoints that must answer before any tenant exists. @@ -7013,82 +7675,29 @@ survives a module being pulled out into its own service. - `ExcludedPathPrefixes` (`:103`) plus `EffectiveExcludedPathPrefixes` (`:106-107`), the same projection shape. - `Tenants` (`:115`): a get-only `Dictionary` bound from `Tenancy:Tenants:{tenantId}`, needed only for database-per-tenant. - **Why it's built this way**: shared-schema isolation plus optional database-per-tenant promotion is the model [ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html) records, and this class is its whole operator-facing surface. Defaulting `Enabled` to false while keeping the filter and interceptor always-on is what makes the feature safe to ship into an existing host, and keeping every relational check in a separate `IValidateOptions` (rather than in attributes) is what lets the validator reach the resolved data sources. -- **Where it's used**: bound with `ValidateOnStart` in `AddMultiTenancy`, alongside the `TryAddEnumerable` registration of [`TenancySettingsValidator`](#tenancysettingsvalidator) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:266-278`). Read at request time by [`TenantResolutionMiddleware`](group-12-api-hosting-mapping.md#tenantresolutionmiddleware), which short-circuits when disabled or on an excluded path (`MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/TenantResolutionMiddleware.cs:62`), walks `EffectiveResolutionOrder` reading the claim or the header (`:107-118`), lets the request through unscoped when `RequireTenant` is off (`:75-81`), and otherwise answers `400 Bad Request` (`:83`). Read at persistence time by [`DbContextFactory`](#dbcontextfactory) for per-tenant connection routing (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:45`, `:144-168`), and by the background services through [`TenantDataSourceTargets`](#tenantdatasourcetargets)`.Expand` so they sweep every tenant database too (`OutboxProcessor.cs:64`, `OutboxCleanupService.cs:198`, `OutboxAdministration.cs:42`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:51-81`). Startup database initialization uses the same expansion to create each tenant's database (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:131`, `:138`), and the design-time helper supplies a default instance so `dotnet ef` needs no tenancy configuration (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextHelper.cs:156`). All of the runtime consumers take the options as NULLABLE, so a host that never called `AddMultiTenancy` resolves `null` and behaves exactly as before. - -### CrossDataSourceDegradeConvention - -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Conventions` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:33` · Level 3 · class (public sealed) - -- **What it is**: the model-finalizing convention that makes database-per-service work without forcing every module to write two versions of its entity configuration. It finds relationships whose two ends resolve to different physical databases and degrades them: the foreign key goes away, the navigation members are ignored, and entity types belonging to another database are removed from this model entirely (`CrossDataSourceDegradeConvention.cs:9-33`). -- **Depends on**: [`DataSourceKey`](#datasourcekey) (the context's own physical source) and [`IEntityDataSourceRegistry`](#ientitydatasourceregistry) (the entity-to-database map), both primary-constructor parameters (`CrossDataSourceDegradeConvention.cs:33-35`); EF Core's `IModelFinalizingConvention` plus the **mutable** metadata surface (`IMutableModel`, `IMutableEntityType`, `IMutableForeignKey`, `IMutableProperty`). -- **Concept introduced, degrading a relationship instead of forbidding it.** `[Rubric §7, Microservices Readiness]` assesses whether a module can be lifted into its own service without rewriting application code, and `[Rubric §8, Data Architecture]` assesses whether ownership boundaries in the data model are real. Under database-per-service ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)) an order row and a user row can live in different databases, and no database engine can enforce a foreign key across that line. The naive answers are both bad: forbid the navigation in the domain model (which distorts the domain to fit deployment), or keep the FK and hope the two entities always end up co-located (which breaks the moment they do not). This convention takes a third route. The configuration body is written once, as if everything were in one database, and the model builder subtracts what the current physical source cannot support. Three things are given up and each has a compensating mechanism: the FK constraint is replaced by an index over the surviving scalar columns, in-database navigation is replaced by the `INavigationPopulator` batch-loading path ([ADR-002](https://ivanball.github.io/docs/adr/002-navigation-populators.html)), and referential consistency is maintained by integration events rather than by the engine (`CrossDataSourceDegradeConvention.cs:12-21`). The payoff is stated at `:25-29`: when every entity resolves to the same source, nothing is foreign and the convention is a structural no-op, so the monolith model is identical to the single-database model. -- **Walkthrough** - - `ProcessModelFinalizing` opens by casting the convention model builder's metadata to `IMutableModel` (`CrossDataSourceDegradeConvention.cs:44-46`). The comment above the cast is load-bearing: cross-cutting helpers (soft-delete filters, concurrency tokens) have already promoted every entity type to the `Explicit` configuration source, and convention-sourced builder calls cannot override `Explicit`, so the mutable API is the only surface that can apply these changes (`:22-24`). - - `IsForeign` (`CrossDataSourceDegradeConvention.cs:91-94`) is the whole routing decision: look the CLR type's full name up in the registry, and call it foreign when the registry knows it and its key differs from this context's key. An unregistered type is never foreign. - - The foreign set is computed over non-owned entity types, and when it is empty the method returns at once (`CrossDataSourceDegradeConvention.cs:48-55`). That early return is the monolith fast path. - - **Step 1, degrade the FKs** (`CrossDataSourceDegradeConvention.cs:62-74`): for each local entity, every declared foreign key whose principal is foreign is passed to `DegradeForeignKey`. FKs declared on foreign dependents are not touched here because step 3 removes those entity types wholesale. Note that `addCompensatingIndex` is computed once as "engine is not Cosmos" (`:65`). - - `DegradeForeignKey` (`CrossDataSourceDegradeConvention.cs:107-138`) captures the non-shadow FK properties, removes the FK (which takes both navigations with it), and then rebuilds the index story. The subtle part is `:123-130`: EF's own `ForeignKeyIndexConvention` created an index for the FK, and its removal is processed by a **deferred** event that would fire after the coverage check below, silently leaving the column unindexed. So the convention drops that convention-sourced index eagerly itself, then checks coverage and adds a plain index only if nothing already covers the columns. - - `HasCoveringIndex` (`CrossDataSourceDegradeConvention.cs:140-144`) treats an index as covering when the FK columns are a **prefix** of its column list, in order, which is exactly the condition under which a composite index can serve a lookup on those columns. - - **Step 2, ignore the foreign members** (`CrossDataSourceDegradeConvention.cs:79-82`, implementation at `:151-165`): skip navigations pointing at foreign entities are removed, then every CLR property whose type (or collection element type) is foreign is added to the ignore list. The comment at `:76-78` explains why the second half is needed: removing a navigation leaves an unmapped CLR property of entity type behind, and EF's model validation rejects that. - - `UnwrapCollectionElementType` (`CrossDataSourceDegradeConvention.cs:171-174`) is the one-argument-generic unwrap that makes `ICollection` detectable as a collection of foreign entities. - - **Step 3, remove the foreign entity types** (`CrossDataSourceDegradeConvention.cs:85-88`). EF's relationship discovery pulls foreign types into the model as a side effect; this is where they leave it. -- **Why it's built this way**: the Cosmos carve-out on the compensating index is spelled out at `CrossDataSourceDegradeConvention.cs:100-105`. Cosmos auto-indexes every property and rejects explicit index definitions, so adding a compensating index would fail model validation, and skipping it is what keeps a configuration body carrying a cross-source relationship portable to Cosmos without edits ([ADR-018](https://ivanball.github.io/docs/adr/018-polyglot-persistence.html)). Doing all of this at model finalizing rather than at configuration time is what lets the same entity configuration serve the monolith and the extracted-service topology ([ADR-008](https://ivanball.github.io/docs/adr/008-service-extraction-topology.html)) with no per-topology branching in module code. -- **Where it's used**: registered by [`ApplicationDbContext`](#applicationdbcontext) in `ConfigureConventions` with the context's `DataSourceKey` and the resolved [`IEntityDataSourceRegistry`](#ientitydatasourceregistry) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:381-382`), ahead of [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention). Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/CrossDataSourceDegradeConventionTests.cs`. +- **Where it's used**: bound with `ValidateOnStart` in `AddMultiTenancy`, alongside the `TryAddEnumerable` registration of [`TenancySettingsValidator`](#tenancysettingsvalidator) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:276-288`). Read at request time by [`TenantResolutionMiddleware`](group-12-api-hosting-mapping.md#tenantresolutionmiddleware), which short-circuits when disabled or on an excluded path (`MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/TenantResolutionMiddleware.cs:62`), walks `EffectiveResolutionOrder` reading the claim or the header (`:107-118`), lets the request through unscoped when `RequireTenant` is off (`:75-81`), and otherwise answers `400 Bad Request` (`:83`). Read at persistence time by [`DbContextFactory`](#dbcontextfactory) for per-tenant connection routing (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:47`, `:144-168`), and by the background services through [`TenantDataSourceTargets`](#tenantdatasourcetargets)`.Expand` so they sweep every tenant database too (`OutboxProcessor.cs:64`, `OutboxCleanupService.cs:198`, `OutboxAdministration.cs:42`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:51-81`). Startup database initialization uses the same expansion to create each tenant's database (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:182`, `:138`), and the design-time helper supplies a default instance so `dotnet ef` needs no tenancy configuration (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextHelper.cs:156`). All of the runtime consumers take the options as NULLABLE, so a host that never called `AddMultiTenancy` resolves `null` and behaves exactly as before. ### TenancySettingsValidator -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Tenancy` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:23` · Level 4 · class (sealed, internal) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Tenancy` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:24` · Level 13 · class (sealed, internal) -- **What it is**: the startup validator for [`TenancySettings`](#tenancysettings). It rejects a resolution order naming an unimplemented strategy, and rejects a per-tenant data-source override that declares no connection string or names a source that does not exist (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:8-23`). -- **Depends on**: [`IDataSourceResolver`](#idatasourceresolver) as an OPTIONAL primary-constructor parameter (`TenancySettingsValidator.cs:23`), plus [`DataSource`](#datasource) and [`DataSourceKey`](#datasourcekey) (`:4`, `:27-28`), and the two settings shapes [`TenancySettings`](#tenancysettings) and [`TenantDataSourceOverrideSettings`](#tenantdatasourceoverridesettings). Externals: `Microsoft.Extensions.Options` for `IValidateOptions` and `ValidateOptionsResult` (`:2`), and `System.Globalization` for the `CultureInfo.InvariantCulture` message formatting (`:1`, `:79`). -- **Concept introduced, `IValidateOptions` when validation needs other services.** Data annotations and `IValidatableObject` are the right tools when a rule only involves the settings object itself. This class is the other half of the options-validation story: `IValidateOptions` is a DI-resolved service, so it can inject collaborators. Here that collaborator is the data-source resolver, which is the only thing that knows whether `Conference` is a real physical source in this host. Registration is `TryAddEnumerable(ServiceDescriptor.Singleton, TenancySettingsValidator>())` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:274-275`), with `TryAddEnumerable` because two modules calling `AddMultiTenancy` must not run the same validation twice (`DependencyInjection.cs:273`). The same shape is used by [`ConnectionStringSettingsValidator`](#connectionstringsettingsvalidator); these two are the framework's only custom `IValidateOptions` implementations. - `[Rubric §11, Security]` assesses whether misconfiguration can degrade silently. The class doc states the rationale outright: every failure here would otherwise surface as silent cross-tenant behavior at run time, which is exactly the class of bug tenancy exists to prevent, so it is worth a failed boot (`TenancySettingsValidator.cs:8-13`). The concrete danger is the override key: an unknown logical name resolves to `Default`, so a mistyped source name would quietly leave that tenant on the shared database instead of its own (`:112-116`). - `[Rubric §15, Best Practices & Code Quality]` assesses the quality of failure messages. Each message names the exact configuration path that is wrong and tells the operator what to do: the missing-connection-string message lists the four acceptable properties and notes that removing the entry keeps the source shared (`:78-85`); the unknown-source message explains that override keys are physical names (`:95-104`); the `Host` strategy message names the two supported values (`:60-63`). +- **What it is**: the startup validator for [`TenancySettings`](#tenancysettings). It rejects a resolution order naming an unimplemented strategy, and rejects a per-tenant data-source override that declares no connection string or names a source that does not exist (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:9-24`). +- **Depends on**: [`IDataSourceResolver`](#idatasourceresolver) as an OPTIONAL primary-constructor parameter (`TenancySettingsValidator.cs:24`), plus [`DataSource`](#datasource) and [`DataSourceKey`](#datasourcekey) (`:3-4`, `:28-29`, `:104`), the engine registry [`DataSourceEngines`](#datasourceengines) and its [`IDataSourceEngine`](#idatasourceengine) entries (`:5`, `:29`, `:124`), and the two settings shapes [`TenancySettings`](#tenancysettings) and [`TenantDataSourceOverrideSettings`](#tenantdatasourceoverridesettings). Externals: `Microsoft.Extensions.Options` for `IValidateOptions` and `ValidateOptionsResult` (`:2`), and `System.Globalization` for the `CultureInfo.InvariantCulture` message formatting (`:1`, `:80`). +- **Concept introduced, `IValidateOptions` when validation needs other services.** Data annotations and `IValidatableObject` are the right tools when a rule only involves the settings object itself. This class is the other half of the options-validation story: `IValidateOptions` is a DI-resolved service, so it can inject collaborators. Here that collaborator is the data-source resolver, which is the only thing that knows whether `Conference` is a real physical source in this host. Registration is `TryAddEnumerable(ServiceDescriptor.Singleton, TenancySettingsValidator>())` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:284-285`), with `TryAddEnumerable` because two modules calling `AddMultiTenancy` must not run the same validation twice (`DependencyInjection.cs:283`). The same shape is used by [`ConnectionStringSettingsValidator`](#connectionstringsettingsvalidator); these two are the framework's only custom `IValidateOptions` implementations. + `[Rubric §11, Security]` assesses whether misconfiguration can degrade silently. The class doc states the rationale outright: every failure here would otherwise surface as silent cross-tenant behavior at run time, which is exactly the class of bug tenancy exists to prevent, so it is worth a failed boot (`TenancySettingsValidator.cs:9-14`). The concrete danger is the override key: an unknown logical name resolves to `Default`, so a mistyped source name would quietly leave that tenant on the shared database instead of its own (`:113-117`). + `[Rubric §15, Best Practices & Code Quality]` assesses the quality of failure messages. Each message names the exact configuration path that is wrong and tells the operator what to do: the missing-connection-string message lists the four acceptable properties and notes that removing the entry keeps the source shared (`:79-85`); the unknown-source message explains that override keys are physical names (`:96-104`); the `Host` strategy message names the two supported values (`:61-63`). - **Walkthrough** - - `Engines` (`:27-28`): private static `[SQLServer, PostgreSQL, Sqlite, CosmosDB]`, the engines an override can carry a connection string for. - - `Validate(string? name, TenancySettings options)` (`:31-47`): null-guards the options, collects failures into a list, runs the resolution-order check once and the tenant check per declared tenant, then returns `ValidateOptionsResult.Success` or `Fail(failures)`. Note that it accumulates ALL failures rather than returning on the first, so one boot attempt reports the whole set. - - `ValidateResolutionOrder` (`:54-65`): walks `EffectiveResolutionOrder` (so the framework default is validated too, not just an explicitly configured order) and fails on `TenantResolutionStrategy.Host`. That enum member exists so the configuration contract is stable for when host-based resolution ships, but selecting it today must not read as "resolve nothing" (`:49-53`, and the enum's own doc at `TenancySettings.cs:22-26`). - - `ValidateTenant` (`:71-106`): for each `(sourceName, override)` pair, computes which engines the override actually declares a connection string for. Zero engines is a failure and the loop moves on (`:76-86`). If `resolver` is null the source-existence check is skipped entirely (`:88-91`), which is what makes the validator usable in a container that never registered persistence (`:14-18`). Otherwise every declared engine whose source name is unknown produces a failure (`:93-104`). - - `DeclaredEngines` (`:109-110`): a collection expression over `Engines` filtered by `ConnectionStringFor` being non-blank. - - `IsKnownPhysicalSource` (`:117-119`): the round-trip test. `Default` always passes; otherwise the name must survive `resolver.ResolveLogical(engine, sourceName).Name` unchanged, because an unknown logical name collapses onto `Default` and therefore comes back different. - - `ConnectionStringFor(DataSource, TenantDataSourceOverrideSettings)` (`:122-130`): `internal static`, a switch expression mapping engine to the matching connection-string property, now with a `PostgreSQL => over.PostgreSQLConnectionString` arm alongside SQL Server, Sqlite, and Cosmos. It is `internal` rather than private because it is reused outside validation, which is the detail worth noting next. -- **Why it's built this way**: the engine-to-property mapping is needed in three places (validation, target expansion, and connection routing), so it lives once here and the runtime paths call back into the validator's `ConnectionStringFor` rather than re-implementing the switch ([ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html)). Making the resolver optional rather than required is what keeps the validator constructible in a host that binds tenancy without registering `AddInfrastructure`, at the cost of skipping the source-existence check there; the strategy and connection-string checks still run. -- **Where it's used**: registered by `AddMultiTenancy` alongside `ValidateOnStart` on the options (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:266-278`). Its `ConnectionStringFor` helper is called at run time by [`TenantDataSourceTargets`](#tenantdatasourcetargets)`.Expand` when deciding whether a (tenant, source) pair is really overridden (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:73`) and by [`DbContextFactory`](#dbcontextfactory)`.ResolveTenantOverride` when cloning the physical source for a tenant (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:172`). Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/AddMultiTenancyTests.cs`, which asserts the single-registration shape (`:97-98`) and drives the validator both without a resolver (`:168`) and with one (`:206`). + - `Engines` (`:28-29`): private static, built from [`DataSourceEngines`](#datasourceengines)`.All` ordered by each engine's `SubstitutionPriority` and projected to its `Engine` value. With the shipped priorities (SQL Server `0`, PostgreSQL `1`, Sqlite `2`, Cosmos `3`: `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SQLServerDataSourceEngine.cs:29`, `PostgreSQLDataSourceEngine.cs:27`, `SqliteDataSourceEngine.cs:26`, `CosmosDataSourceEngine.cs:28`) that yields `[SQLServer, PostgreSQL, Sqlite, CosmosDB]`, the engines an override can carry a connection string for, in substitution-priority order (`:27`). The list is no longer hand-written, so a newly registered engine is validated without touching this class. + - `Validate(string? name, TenancySettings options)` (`:32-48`): null-guards the options, collects failures into a list, runs the resolution-order check once and the tenant check per declared tenant, then returns `ValidateOptionsResult.Success` or `Fail(failures)`. Note that it accumulates ALL failures rather than returning on the first, so one boot attempt reports the whole set. + - `ValidateResolutionOrder` (`:55-66`): walks `EffectiveResolutionOrder` (so the framework default is validated too, not just an explicitly configured order) and fails on `TenantResolutionStrategy.Host`. That enum member exists so the configuration contract is stable for when host-based resolution ships, but selecting it today must not read as "resolve nothing" (`:50-54`, and the enum's own doc at `TenancySettings.cs:22-26`). + - `ValidateTenant` (`:72-107`): for each `(sourceName, override)` pair, computes which engines the override actually declares a connection string for. Zero engines is a failure and the loop moves on (`:77-87`). If `resolver` is null the source-existence check is skipped entirely (`:89-92`), which is what makes the validator usable in a container that never registered persistence (`:15-19`). Otherwise every declared engine whose source name is unknown produces a failure (`:94-105`). + - `DeclaredEngines` (`:110-111`): a collection expression over `Engines` filtered by `ConnectionStringFor` being non-blank. + - `IsKnownPhysicalSource` (`:118-120`): the round-trip test. `Default` always passes; otherwise the name must survive `resolver.ResolveLogical(engine, sourceName).Name` unchanged, because an unknown logical name collapses onto `Default` and therefore comes back different. + - `ConnectionStringFor(DataSource, TenantDataSourceOverrideSettings)` (`:123-124`): `internal static`, a one-line delegation to `DataSourceEngines.For(engine).GetConnectionString(over)`, so each engine answers for its own override property (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/IDataSourceEngine.cs:59`). `For` throws `InvalidOperationException` for an unregistered engine rather than returning null (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/DataSourceEngines.cs:40-43`). It is `internal` rather than private because it is reused outside validation, which is the detail worth noting next. +- **Why it's built this way**: the engine-to-property mapping is needed in three places (validation, target expansion, and connection routing). The mapping itself now lives on each engine (`GetConnectionString(TenantDataSourceOverrideSettings)`, `IDataSourceEngine.cs:59`), and this validator's `ConnectionStringFor` is the single entry point the runtime paths call back into rather than resolving the engine themselves ([ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html)). Deriving `Engines` from the registry follows the same rule: `DataSourceEngines` documents that a fifth engine is one new class plus one registry line (`DataSourceEngines.cs:14-17`), and this validator picks it up with no edit. Making the resolver optional rather than required is what keeps the validator constructible in a host that binds tenancy without registering `AddInfrastructure`, at the cost of skipping the source-existence check there; the strategy and connection-string checks still run. +- **Where it's used**: registered by `AddMultiTenancy` alongside `ValidateOnStart` on the options (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:276-288`). Its `ConnectionStringFor` helper is called at run time by [`TenantDataSourceTargets`](#tenantdatasourcetargets)`.Expand` when deciding whether a (tenant, source) pair is really overridden (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:73`) and by [`DbContextFactory`](#dbcontextfactory)`.ResolveTenantOverride` when cloning the physical source for a tenant (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:178`). Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/AddMultiTenancyTests.cs`, which asserts the single-registration shape (`:97-98`) and drives the validator both without a resolver (`:168`) and with one (`:206`). - **Caveats / not-in-source**: the type is `internal`, so it is not part of the framework's public API and a consumer cannot subclass or replace it; a host needing extra tenancy rules registers its own additional `IValidateOptions`. -### EntityTypeBuilderExtensions - -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Configuration` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeBuilderExtensions.cs:13` · Level 5 · class (public static, extension container) - -- **What it is**: a generic `extension(EntityTypeBuilder)` block with two members. `OwnsMoney(...)` maps a [`Money`](group-02-domain-building-blocks.md#money) property as an owned type flattened into two columns on the owner's table: a decimal amount and an ISO 4217 currency code (`EntityTypeBuilderExtensions.cs:13`, `:21-23`, member at `:51-81`). `OwnsAddress(...)` does the same for an `Address` property, flattened into six columns (`:125-171`). -- **Depends on**: [`Money`](group-02-domain-building-blocks.md#money) and [`Currency`](group-02-domain-building-blocks.md#currency) from `MMCA.Common.Shared.ValueObjects`, `Address` and `AddressInvariants` from the same namespace, EF Core's `EntityTypeBuilder` and `OwnsOne`, and `System.Linq.Expressions`. -- **Concept introduced, the round-trip contract of a value converter.** `[Rubric §4, Domain-Driven Design]` assesses whether value objects survive the trip to storage intact, and `[Rubric §15, Best Practices and Code Quality]` assesses whether edge cases are handled where they arise. A value converter has two legs, write and read, and correctness means every value the write leg can produce is a value the read leg can materialize. The doc comment at `EntityTypeBuilderExtensions.cs:37-46` documents a real failure of that contract and its fix. An aggregate can seed a zero total with `Money.Zero()`, whose currency `Code` is the empty string, and leave it there; the write leg persists that empty code faithfully. On the way back, `Currency.FromCode("")` fails, and a bare `.Value!` turned those rows into a `null` Currency inside a `Money`, which is a materialization-time `NullReferenceException` waiting for the first read. The same applies to any code that has since dropped out of `Currency.All`. So the read leg coalesces to a sentinel instead. -- **Walkthrough** - - `NoCurrency` (`EntityTypeBuilderExtensions.cs:26`) is that sentinel, obtained as `Money.Zero().Currency`. The comment at `:14-18` explains the indirection: the "no currency" instance is internal to `MMCA.Common.Shared`, so a zero `Money` is the only public handle on it. - - The extension is generic over the owner with a `class` constraint (`EntityTypeBuilderExtensions.cs:28-29`), so it applies to any entity that owns a `Money`. - - The signature takes the navigation expression, the two column names, and a `required` flag defaulting to `true` (`EntityTypeBuilderExtensions.cs:58-62`). The parameter doc at `:44-49` is honest about the design rule behind that: `required` is the only facet that differs across the existing call sites, so it is the only one parameterized beyond the two column names. - - All four arguments are guarded, the strings with `ArgumentException.ThrowIfNullOrWhiteSpace` (`EntityTypeBuilderExtensions.cs:64-67`). - - `OwnsOne` maps the two members (`EntityTypeBuilderExtensions.cs:69-83`): `Amount` gets its column name and `IsRequired`; `Currency` gets the two-leg conversion (write `currency => currency.Code`, read `code => Currency.FromCode(code).Value ?? NoCurrency`), plus `HasMaxLength(3)`, `IsUnicode(false)`, its column name, and `IsRequired`. That is the ISO 4217 code shape exactly: three non-Unicode characters. - - `builder.Navigation(navigationExpression).IsRequired(required)` (`EntityTypeBuilderExtensions.cs:85`) is a separate call from the `OwnsOne` body because it configures the *navigation* rather than the owned entity's properties. The builder is returned for chaining (`:80`). - - `OwnsAddress(...)` (`EntityTypeBuilderExtensions.cs:125-171`) maps `Address`'s six fields (`AddressLine1`, `AddressLine2`, `City`, `State`, `ZipCode`, `Country`) to columns built by the private helper `AddressColumn(columnPrefix, propertyName)` (`:185-188`). Each column is `columnPrefix + propertyName`, except the redundant leading word `Address` is dropped from the two line properties first (`AddressPropertyPrefix`, `:19`), so the default `"Address"` prefix yields `AddressLine1`/`AddressLine2` rather than `AddressAddressLine1`/`AddressAddressLine2`, and `AddressCity`, `AddressState`, `AddressZipCode`, `AddressCountry` for the rest; a second address on the same owner passes a different `columnPrefix` (for example `"ShippingAddress"`) and gets `ShippingAddressLine1`, `ShippingAddressCity`, and so on. That is exactly the mapping the framework's own hand-written configurations already declared, so adopting the helper is a zero-diff model change: no migration, no snapshot churn. - - Max lengths on the six columns come from `AddressInvariants` (`EntityTypeBuilderExtensions.cs:138,144,149,154,159,164`), the same constants the `Address` value object validates against, so a column can never be shorter than what the domain accepts; every column is non-Unicode `varchar`, and only `AddressLine1` is required, matching the value object, whose other five fields are optional for international address formats. `required` on the navigation itself defaults to `false` (`:128`), matching the existing call sites where an owner may have no address at all. -- **Why it's built this way**: value objects are validated primitives ([ADR-068](https://ivanball.github.io/docs/adr/068-value-objects-as-validated-primitives.html)), which means construction is guarded but persistence must still round-trip every value that construction allowed, including the `Money` zero sentinel. Flattening `Money` into two columns and `Address` into six on the owner's table rather than a separate table keeps a price, a total, or a location a single-row read. Putting each mapping behind one extension member means the read-leg fallback for `Money`, and the column-naming and invariant-length rules for `Address`, are each written once and cannot be forgotten by the next configuration that maps one. -- **Where it's used**: `OwnsMoney` has three call sites, all in MMCA.Store's Sales and Catalog modules: `MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Infrastructure/Persistence/EntityConfiguration/OrderConfiguration.cs:26` (`Total`, with `required: false`), `.../OrderLineConfiguration.cs:28` (`UnitPrice`), and `MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Infrastructure/Persistence/EntityConfiguration/ProductVariantConfiguration.cs:31` (`Price`); covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/OwnsMoneyTests.cs`. `OwnsAddress` is covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/OwnsAddressTests.cs`. - -### StronglyTypedIdModelConfiguration - -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Conventions` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/StronglyTypedIdModelConfiguration.cs:21` · Level 6 · class (public static) - -- **What it is**: the single call that wires strongly typed identifiers (ADR-115) into EF Core's model, once per host. `Apply` walks the host's [`StronglyTypedIdRegistry`](group-08-auth.md#stronglytypedidregistry) and declares one pre-convention type mapping per identifier type it describes, so every property of that type, keys, cross-module scalar references, owned-type members and the properties of a framework table alike, maps to its wrapped primitive (`StronglyTypedIdModelConfiguration.cs:7-12`, `:29-51`). -- **Depends on**: EF Core's `ModelConfigurationBuilder`, [`StronglyTypedIdValueConverter`](#stronglytypedidvalueconvertertself-tvalue) and its comparer counterpart from `MMCA.Common.Infrastructure.Persistence.Conversions`, and `StronglyTypedIdRegistry` from `MMCA.Common.Shared.Identifiers`. -- **Concept introduced, pre-convention configuration versus a model-finalizing convention.** `[Rubric §8, Data Architecture]` assesses whether a cross-cutting mapping rule is applied at the point in the pipeline where it actually works, and `[Rubric §1, SOLID]` assesses whether the opt-in stays orthogonal to code that never adopts it. EF Core exposes two very different places to declare a type-wide rule: `ConfigureConventions` (pre-convention, runs before the provider's own conventions see the model) and a model-finalizing `IModelFinalizingConvention` like [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) (runs last). The class doc comment states why this one has to be the former (`StronglyTypedIdModelConfiguration.cs:14-18`): a converter attached at finalization arrives after the provider's value-generation conventions have already inspected the property, so a wrapped `int` key would silently lose its IDENTITY strategy. Declaring the mapping here means the provider CLR type is known before any of that runs, and a wrapped key generates exactly as its primitive did. -- **Walkthrough** - - `Apply(configurationBuilder, registry)` (`StronglyTypedIdModelConfiguration.cs:29`) null-guards both arguments, then iterates `registry.Describe()`, which yields the `(identifierType, valueType)` pairs the host declared (`:36`). - - For each pair it closes the two open generics reflectively: `StronglyTypedIdValueConverter<,>.MakeGenericType(identifierType, valueType)` and `StronglyTypedIdValueComparer<>.MakeGenericType(identifierType)` (`StronglyTypedIdModelConfiguration.cs:38-41`), then calls `configurationBuilder.Properties(identifierType).HaveConversion(converterType, comparerType)` (`:43-45`), the pre-convention API that applies the conversion to every property of that CLR type across the whole model, not just one entity's. - - `configured` counts the identifiers actually wired and is returned (`StronglyTypedIdModelConfiguration.cs:34,47,50`), giving the caller a number to log or assert against rather than a bare `void`. -- **Why it's built this way**: strongly typed identifiers sit on the same opt-in footing as smart enumerations (ADR-115): a wrapper is two lines, and adopting the whole feature is one DI call, `services.AddStronglyTypedIds(typeof(OrderId).Assembly)`. The EF half of that contract has to be equally uniform, one call from `ApplicationDbContext.ConfigureConventions`, which every engine context inherits, so SQL Server, PostgreSQL, SQLite and Cosmos all get the same mapping without an engine branch (`StronglyTypedIdModelConfiguration.cs:10-12`). Driving the mapping from the registry rather than from a hard-coded type list means a host that never wraps an identifier calls `Apply` over an empty registry and nothing changes. -- **Where it's used**: called once from [`ApplicationDbContext`](#applicationdbcontext)'s `ConfigureConventions` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs`). - ### DesignTimeDbContextHelper > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.DbContexts.Design` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextHelper.cs:43` · Level 13 · class (public static) @@ -7124,6 +7733,25 @@ survives a module being pulled out into its own service. - **Why it's built this way**: the alternative is an attribute-only scheme, where every configuration is annotated and discovery is a reflection sweep over attributes. Putting the engine in the type system instead means the scan is a plain `GetInterfaces()` match (`ModelBuilderExtensions.cs:48-50`), the compiler tells a consumer immediately when a configuration implements two engines' contracts, and the entity type is available as a generic argument rather than as something else to look up. - **Where it's used**: implemented by [`EntityTypeConfigurationBase`](#entitytypeconfigurationbasetentity-tidentifiertype) (`EntityTypeConfigurationBase.cs:19-20`) and extended by the three engine markers, [`IEntityTypeConfigurationSQLServer`](#ientitytypeconfigurationsqlservertentity-tidentifiertype), [`IEntityTypeConfigurationSqlite`](#ientitytypeconfigurationsqlitetentity-tidentifiertype), and [`IEntityTypeConfigurationCosmos`](#ientitytypeconfigurationcosmostentity-tidentifiertype). +### EntityTypeBuilderExtensions + +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Configuration` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeBuilderExtensions.cs:13` · Level 5 · class (public static, extension container) + +- **What it is**: a generic `extension(EntityTypeBuilder)` block with two members. `OwnsMoney(...)` maps a [`Money`](group-02-domain-building-blocks.md#money) property as an owned type flattened into two columns on the owner's table: a decimal amount and an ISO 4217 currency code (`EntityTypeBuilderExtensions.cs:13`, `:21-23`, member at `:51-81`). `OwnsAddress(...)` does the same for an `Address` property, flattened into six columns (`:125-171`). +- **Depends on**: [`Money`](group-02-domain-building-blocks.md#money) and [`Currency`](group-02-domain-building-blocks.md#currency) from `MMCA.Common.Shared.ValueObjects`, `Address` and `AddressInvariants` from the same namespace, EF Core's `EntityTypeBuilder` and `OwnsOne`, and `System.Linq.Expressions`. +- **Concept introduced, the round-trip contract of a value converter.** `[Rubric §4, Domain-Driven Design]` assesses whether value objects survive the trip to storage intact, and `[Rubric §15, Best Practices and Code Quality]` assesses whether edge cases are handled where they arise. A value converter has two legs, write and read, and correctness means every value the write leg can produce is a value the read leg can materialize. The doc comment at `EntityTypeBuilderExtensions.cs:37-46` documents a real failure of that contract and its fix. An aggregate can seed a zero total with `Money.Zero()`, whose currency `Code` is the empty string, and leave it there; the write leg persists that empty code faithfully. On the way back, `Currency.FromCode("")` fails, and a bare `.Value!` turned those rows into a `null` Currency inside a `Money`, which is a materialization-time `NullReferenceException` waiting for the first read. The same applies to any code that has since dropped out of `Currency.All`. So the read leg coalesces to a sentinel instead. +- **Walkthrough** + - `NoCurrency` (`EntityTypeBuilderExtensions.cs:26`) is that sentinel, obtained as `Money.Zero().Currency`. The comment at `:14-18` explains the indirection: the "no currency" instance is internal to `MMCA.Common.Shared`, so a zero `Money` is the only public handle on it. + - The extension is generic over the owner with a `class` constraint (`EntityTypeBuilderExtensions.cs:28-29`), so it applies to any entity that owns a `Money`. + - The signature takes the navigation expression, the two column names, and a `required` flag defaulting to `true` (`EntityTypeBuilderExtensions.cs:58-62`). The parameter doc at `:44-49` is honest about the design rule behind that: `required` is the only facet that differs across the existing call sites, so it is the only one parameterized beyond the two column names. + - All four arguments are guarded, the strings with `ArgumentException.ThrowIfNullOrWhiteSpace` (`EntityTypeBuilderExtensions.cs:64-67`). + - `OwnsOne` maps the two members (`EntityTypeBuilderExtensions.cs:69-83`): `Amount` gets its column name and `IsRequired`; `Currency` gets the two-leg conversion (write `currency => currency.Code`, read `code => Currency.FromCode(code).Value ?? NoCurrency`), plus `HasMaxLength(3)`, `IsUnicode(false)`, its column name, and `IsRequired`. That is the ISO 4217 code shape exactly: three non-Unicode characters. + - `builder.Navigation(navigationExpression).IsRequired(required)` (`EntityTypeBuilderExtensions.cs:85`) is a separate call from the `OwnsOne` body because it configures the *navigation* rather than the owned entity's properties. The builder is returned for chaining (`:80`). + - `OwnsAddress(...)` (`EntityTypeBuilderExtensions.cs:125-171`) maps `Address`'s six fields (`AddressLine1`, `AddressLine2`, `City`, `State`, `ZipCode`, `Country`) to columns built by the private helper `AddressColumn(columnPrefix, propertyName)` (`:185-188`). Each column is `columnPrefix + propertyName`, except the redundant leading word `Address` is dropped from the two line properties first (`AddressPropertyPrefix`, `:19`), so the default `"Address"` prefix yields `AddressLine1`/`AddressLine2` rather than `AddressAddressLine1`/`AddressAddressLine2`, and `AddressCity`, `AddressState`, `AddressZipCode`, `AddressCountry` for the rest; a second address on the same owner passes a different `columnPrefix` (for example `"ShippingAddress"`) and gets `ShippingAddressLine1`, `ShippingAddressCity`, and so on. That is exactly the mapping the framework's own hand-written configurations already declared, so adopting the helper is a zero-diff model change: no migration, no snapshot churn. + - Max lengths on the six columns come from `AddressInvariants` (`EntityTypeBuilderExtensions.cs:138,144,149,154,159,164`), the same constants the `Address` value object validates against, so a column can never be shorter than what the domain accepts; every column is non-Unicode `varchar`, and only `AddressLine1` is required, matching the value object, whose other five fields are optional for international address formats. `required` on the navigation itself defaults to `false` (`:128`), matching the existing call sites where an owner may have no address at all. +- **Why it's built this way**: value objects are validated primitives ([ADR-068](https://ivanball.github.io/docs/adr/068-value-objects-as-validated-primitives.html)), which means construction is guarded but persistence must still round-trip every value that construction allowed, including the `Money` zero sentinel. Flattening `Money` into two columns and `Address` into six on the owner's table rather than a separate table keeps a price, a total, or a location a single-row read. Putting each mapping behind one extension member means the read-leg fallback for `Money`, and the column-naming and invariant-length rules for `Address`, are each written once and cannot be forgotten by the next configuration that maps one. +- **Where it's used**: `OwnsMoney` has three call sites, all in MMCA.Store's Sales and Catalog modules: `MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Infrastructure/Persistence/EntityConfiguration/OrderConfiguration.cs:26` (`Total`, with `required: false`), `.../OrderLineConfiguration.cs:28` (`UnitPrice`), and `MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Infrastructure/Persistence/EntityConfiguration/ProductVariantConfiguration.cs:31` (`Price`); covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/OwnsMoneyTests.cs`. `OwnsAddress` is covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/OwnsAddressTests.cs`. + ### EntityTypeConfigurationBase > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfigurationBase.cs:19` · Level 5 · class (public abstract) @@ -7133,7 +7761,7 @@ survives a module being pulled out into its own service. - **Concept introduced, keeping a domain-only collection out of the model.** An aggregate root collects domain events in memory so the dispatcher can drain them after a save. That collection is not state: it has no column, no table, and no meaning after the transaction closes. EF Core's convention-based model builder does not know that and would happily try to map it, which fails outright or (worse) invents a shadow table. One `builder.Ignore` call at the root of the hierarchy makes the exclusion automatic for every entity in every consumer, rather than a rule each configuration author has to remember. `[Rubric §4, Domain-Driven Design]` assesses whether the aggregate's event mechanism stays a domain concern; ignoring the collection is what keeps events a domain construct rather than a persisted one. `[Rubric §9, API & Contract Design]` assesses whether concerns that apply everywhere are handled once in a shared place; this is the smallest possible example of that, applied at the base class every configuration inherits. - **Walkthrough** - **Declaration** (`EntityTypeConfigurationBase.cs:19-22`): abstract, generic over the entity and its identifier type, implementing the base interface. Being abstract means it is never discovered as a configuration itself: the scan skips abstract types (`ModelBuilderExtensions.cs:44`). - - **`Configure` is `virtual`** (`EntityTypeConfigurationBase.cs:25`): the derived engine-aware class overrides it and calls `base.Configure(builder)` first, so this rule always runs before engine conventions (see [`EntityTypeConfiguration`](#entitytypeconfigurationtentity-tidentifiertype), `EntityTypeConfiguration.cs:43`). + - **`Configure` is `virtual`** (`EntityTypeConfigurationBase.cs:25`): the derived engine-aware class overrides it and calls `base.Configure(builder)` first, so this rule always runs before engine conventions (see [`EntityTypeConfiguration`](#entitytypeconfigurationtentity-tidentifiertype), `EntityTypeConfiguration.cs:41`). - **The aggregate-root test** (`EntityTypeConfigurationBase.cs:29`): `typeof(IAggregateRoot).IsAssignableFrom(typeof(TEntity))`. The rule is applied only to aggregate roots, because only they carry the collection; a plain child entity configured through the same hierarchy is untouched. - **The exclusion** (`EntityTypeConfigurationBase.cs:31`): `builder.Ignore(nameof(AuditableAggregateRootEntity<>.DomainEvents))`. The unbound-generic `nameof` form is worth noticing: it names the member without having to close the generic, so the string stays refactor-safe. - **What this class deliberately leaves to someone else** (`EntityTypeConfigurationBase.cs:10-15`): the doc records that entity-to-data-source mapping is not registered here as a model-building side effect. [`EntityDataSourceRegistry`](#entitydatasourceregistry) derives that mapping eagerly from the configuration class's attributes ([`UseDataSourceAttribute`](group-14-module-system-composition.md#usedatasourceattribute) for the engine, [`UseDatabaseAttribute`](group-14-module-system-composition.md#usedatabaseattribute) or the module namespace for the database), which is what lets routing be known before any model is built. @@ -7150,8 +7778,8 @@ survives a module being pulled out into its own service. - **Walkthrough** - **`internal`** (`IEntityTypeConfigurationCosmos.cs:13`): unlike its parent, this interface is not part of the public API. Consumers are not meant to implement it directly; they derive from [`EntityTypeConfigurationCosmos`](#entitytypeconfigurationcosmostentity-tidentifiertype) (or annotate with [`UseDataSourceAttribute`](group-14-module-system-composition.md#usedatasourceattribute)), which implements it for them. - **`new void Configure(...)`** (`IEntityTypeConfigurationCosmos.cs:17`): the redeclaration the base interface exists to enable. -- **Caveats / not-in-source**: implementing this interface directly, without the attributed base class, is a supported but degraded path. [`EntityDataSourceRegistry`](#entitydatasourceregistry) skips configurations that carry no [`UseDataSourceAttribute`](group-14-module-system-composition.md#usedatasourceattribute) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/EntityDataSourceRegistry.cs:174-178`), so such an entity lands in the engine's Default model but is not routable through [`IUnitOfWork`](#iunitofwork); the code comments call this legacy behavior (`ApplicationDbContext.cs:961-965`). -- **Where it's used**: matched by `ApplicationDbContext.ApplyConfigurationsForEntitiesInContext` for the Cosmos engine (`ApplicationDbContext.cs:952-959`) and implemented by [`EntityTypeConfiguration`](#entitytypeconfigurationtentity-tidentifiertype) (`EntityTypeConfiguration.cs:34`). +- **Caveats / not-in-source**: implementing this interface directly, without the attributed base class, is a supported but degraded path. [`EntityDataSourceRegistry`](#entitydatasourceregistry) skips configurations that carry no [`UseDataSourceAttribute`](group-14-module-system-composition.md#usedatasourceattribute) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/EntityDataSourceRegistry.cs:174-178`), so such an entity lands in the engine's Default model but is not routable through [`IUnitOfWork`](#iunitofwork); the code comments call this legacy behavior (`ApplicationDbContext.cs:957-961`). +- **Where it's used**: matched by `ApplicationDbContext.ApplyConfigurationsForEntitiesInContext` for the Cosmos engine (`ApplicationDbContext.cs:952-959`) and implemented by [`EntityTypeConfiguration`](#entitytypeconfigurationtentity-tidentifiertype) (`EntityTypeConfiguration.cs:32`). ### IEntityTypeConfigurationPostgreSQL @@ -7160,7 +7788,7 @@ survives a module being pulled out into its own service. - **What it is**: the engine marker for PostgreSQL configurations (`IEntityTypeConfigurationPostgreSQL.cs:13`), the fourth member of the marker-interface family alongside Cosmos, Sqlite, and SQL Server; structurally identical to its siblings. - **Depends on**: [`IEntityTypeConfigurationBase`](#ientitytypeconfigurationbasetentity-tidentifiertype) (extends it, `IEntityTypeConfigurationPostgreSQL.cs:13`); [`AuditableBaseEntity`](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype) (constraint, `:14`); EF Core's `EntityTypeBuilder` (NuGet, `:1`). - **Concept reinforced**: engine selection by interface identity, taught at [`IEntityTypeConfigurationBase`](#ientitytypeconfigurationbasetentity-tidentifiertype). `DataSource.PostgreSQL` maps to `typeof(IEntityTypeConfigurationPostgreSQL<,>)` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:955`), the case added alongside the other three when PostgreSQL became a first-class engine ([ADR-113](https://ivanball.github.io/docs/adr/113-postgresql-as-a-first-class-engine.html)). -- **Where it's used**: matched by `PostgreSQLDbContext` through the shared discovery method, and implemented by [`EntityTypeConfiguration`](#entitytypeconfigurationtentity-tidentifiertype) (`EntityTypeConfiguration.cs:32`). +- **Where it's used**: matched by `PostgreSQLDbContext` through the shared discovery method, and implemented by [`EntityTypeConfiguration`](#entitytypeconfigurationtentity-tidentifiertype) (`EntityTypeConfiguration.cs:30`). ### IEntityTypeConfigurationSqlite @@ -7169,7 +7797,7 @@ survives a module being pulled out into its own service. - **What it is**: the engine marker for SQLite configurations (`IEntityTypeConfigurationSqlite.cs:13`), structurally identical to its Cosmos and SQL Server siblings. - **Depends on**: [`IEntityTypeConfigurationBase`](#ientitytypeconfigurationbasetentity-tidentifiertype) (`IEntityTypeConfigurationSqlite.cs:13`); [`AuditableBaseEntity`](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype) (`:14`); EF Core's `EntityTypeBuilder` (`:1`). - **Concept reinforced**: engine selection by interface identity, taught at [`IEntityTypeConfigurationBase`](#ientitytypeconfigurationbasetentity-tidentifiertype). `DataSource.Sqlite` maps to `typeof(IEntityTypeConfigurationSqlite<,>)` (`ApplicationDbContext.cs:956`). -- **Where it's used**: matched by [`SqliteDbContext`](#sqlitedbcontext) through the shared discovery method, implemented by [`EntityTypeConfiguration`](#entitytypeconfigurationtentity-tidentifiertype) (`EntityTypeConfiguration.cs:33`), and used directly by the framework's own tests as the scan target: the `ApplyAllConfigurations` tests pass `typeof(IEntityTypeConfigurationSqlite<,>)` as the interface to match (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/ModelBuilderExtensionsTests.cs:93`, `:148`), and two test types implement it directly to exercise the unattributed path (`ModelBuilderExtensionsTests.cs:108`, `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/EntityDataSourceRegistryTests.cs:233`). +- **Where it's used**: matched by [`SqliteDbContext`](#sqlitedbcontext) through the shared discovery method, implemented by [`EntityTypeConfiguration`](#entitytypeconfigurationtentity-tidentifiertype) (`EntityTypeConfiguration.cs:31`), and used directly by the framework's own tests as the scan target: the `ApplyAllConfigurations` tests pass `typeof(IEntityTypeConfigurationSqlite<,>)` as the interface to match (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/ModelBuilderExtensionsTests.cs:93`, `:148`), and two test types implement it directly to exercise the unattributed path (`ModelBuilderExtensionsTests.cs:108`, `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/EntityDataSourceRegistryTests.cs:233`). ### IEntityTypeConfigurationSQLServer @@ -7178,28 +7806,7 @@ survives a module being pulled out into its own service. - **What it is**: the engine marker for SQL Server configurations (`IEntityTypeConfigurationSQLServer.cs:13`). It is the one of the three that matters in production today, because every deployed entity in ADC and Store routes to SQL Server. - **Depends on**: [`IEntityTypeConfigurationBase`](#ientitytypeconfigurationbasetentity-tidentifiertype) (`IEntityTypeConfigurationSQLServer.cs:13`); [`AuditableBaseEntity`](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype) (`:14`); EF Core's `EntityTypeBuilder` (`:1`). - **Concept reinforced**: engine selection by interface identity. `DataSource.SQLServer` maps to `typeof(IEntityTypeConfigurationSQLServer<,>)` (`ApplicationDbContext.cs:957`); anything the switch does not recognize throws `InvalidOperationException` rather than silently building an empty model (`ApplicationDbContext.cs:958`). -- **Where it's used**: matched by [`SQLServerDbContext`](#sqlserverdbcontext) through `ApplyConfigurationsForEntitiesInContext`, and implemented by [`EntityTypeConfiguration`](#entitytypeconfigurationtentity-tidentifiertype) (`EntityTypeConfiguration.cs:31`), which is how all 28 ADC configurations and all 12 Store configurations reach it through the [`EntityTypeConfigurationSQLServer`](#entitytypeconfigurationsqlservertentity-tidentifiertype) shim. - -### EntityTypeConfiguration - -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:29` · Level 6 · class (public abstract) - -- **What it is**: the engine-aware configuration base and the busiest type in this family. It reads the target engine off a [`UseDataSourceAttribute`](group-14-module-system-composition.md#usedatasourceattribute) on the concrete configuration class (or on an inherited shim base) and applies that engine's table/container mapping plus key generation, so a consumer's `Configure` body only ever describes columns, indexes, and relationships (`EntityTypeConfiguration.cs:29-107`). -- **Depends on**: [`EntityTypeConfigurationBase`](#entitytypeconfigurationbasetentity-tidentifiertype) (base, `EntityTypeConfiguration.cs:30`); all four engine markers, [`IEntityTypeConfigurationSQLServer`](#ientitytypeconfigurationsqlservertentity-tidentifiertype), [`IEntityTypeConfigurationPostgreSQL`](#ientitytypeconfigurationpostgresqltentity-tidentifiertype), [`IEntityTypeConfigurationSqlite`](#ientitytypeconfigurationsqlitetentity-tidentifiertype), [`IEntityTypeConfigurationCosmos`](#ientitytypeconfigurationcosmostentity-tidentifiertype) (`:31-34`); [`UseDataSourceAttribute`](group-14-module-system-composition.md#usedatasourceattribute) and the [`DataSource`](#datasource) enum (`:4`, `:45`, `:59`); [`NamespaceConventions`](#namespaceconventions) (`:74`, `:95`); [`CosmosIntIdValueGenerator`](#cosmosintidvaluegenerator) (`:7`, `:99`); the `IsIdValueGenerated` extension property from [`EntityTypeExtensions`](group-02-domain-building-blocks.md#entitytypeextensions) (`:63`), which is a lookup for [`IdValueGeneratedAttribute`](group-02-domain-building-blocks.md#idvaluegeneratedattribute) (`MMCA.Common/Source/Core/MMCA.Common.Domain/Extensions/EntityTypeExtensions.cs:19`); `System.Reflection` and EF Core's `EntityTypeBuilder` (`:1-3`). -- **Concept introduced, one configuration body that is portable across storage engines.** The naive way to support four engines is four configuration classes per entity, or one class littered with `if (engine == ...)`. This class removes both. The engine is declared **once**, as an attribute, and everything that actually differs between engines is centralized in a single `switch` here: SQL Server and PostgreSQL share one branch and get a table in a module schema, SQLite gets a bare table (SQLite has no schemas), Cosmos gets a per-module container with the entity id as partition key (`EntityTypeConfiguration.cs:65-106`). Moving an entity from SQL Server to Cosmos, or from SQL Server to PostgreSQL, is therefore a one-line attribute change. Two other pieces of the framework complete the portability claim rather than this class alone: [`CosmosDbContext`](#cosmosdbcontext) strips every relational index from the built model, because the Cosmos provider rejects them and indexes every property itself (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/CosmosDbContext.cs:83-91`), and [`CrossDataSourceDegradeConvention`](#crossdatasourcedegradeconvention) removes FK constraints and navigations that would span physical sources while keeping the declared scalar FK column and a compensating index (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:9-21`). A dedicated test builds a Cosmos model offline from a configuration that keeps a filtered index and a cross-source relationship, and asserts the indexes are gone, the FK is gone, the scalar FK column survives, and the foreign principal is not in the model (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/CosmosConfigurationPortabilityTests.cs:70-76`). `[Rubric §8, Data Architecture]` assesses how deliberately the storage shape is chosen and how tightly the model is bound to one engine. `[Rubric §7, Microservices Readiness]` assesses whether a module can be lifted out without a rewrite; being able to re-point an entity's engine and database with attributes is a precondition for that ([ADR-018](https://ivanball.github.io/docs/adr/018-polyglot-persistence.html), [ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html), [ADR-113](https://ivanball.github.io/docs/adr/113-postgresql-as-a-first-class-engine.html)). `[Rubric §15, Best Practices & Code Quality]` assesses whether one decision lives in one place; the per-engine differences live in exactly one `switch`. -- **Concept introduced, discovery and routing that agree by construction.** This class implements **all four** engine marker interfaces (`EntityTypeConfiguration.cs:31-34`), so a configuration derived from it is discovered during every engine's model pass. That sounds wrong until you see the filter: `ApplyConfigurationsForEntitiesInContext` applies a discovered configuration only when [`EntityDataSourceRegistry`](#entitydatasourceregistry) says the entity's [`DataSourceKey`](#datasourcekey) equals this context instance's key (`ApplicationDbContext.cs:970-976`). Both sides read the same attributes: the registry derives the engine from [`UseDataSourceAttribute`](group-14-module-system-composition.md#usedatasourceattribute) and the logical database from [`UseDatabaseAttribute`](group-14-module-system-composition.md#usedatabaseattribute), falling back to the entity's module namespace and then to `Default` (`EntityDataSourceRegistry.cs:174-182`), while this class reads the same attribute for its conventions. Discovery is broad, routing is exact, and there is no second source of truth to drift. `[Rubric §1, SOLID]` assesses whether behavior is driven from one declaration; here the attribute is that declaration. -- **Walkthrough** - - **Declaration** (`EntityTypeConfiguration.cs:29-36`): abstract, extends the base class, implements the four markers, constrained to an audited entity with a non-null identifier type. - - **`Configure` override** (`EntityTypeConfiguration.cs:39-51`): null-guards the builder (`:41`), calls `base.Configure(builder)` so the `DomainEvents` exclusion runs first (`:43`), then reads the engine. - - **The attribute read and its failure mode** (`EntityTypeConfiguration.cs:45-48`): `GetType().GetCustomAttribute()?.DataSource` on the **runtime** type, which is the concrete configuration. `UseDataSourceAttribute` is declared with `Inherited = true` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/UseDataSourceAttribute.cs:12`), which is exactly why a class deriving from one of the shim bases inherits its engine without repeating the annotation. When the attribute is missing entirely the code throws `InvalidOperationException` naming the offending configuration class, rather than defaulting to an engine and mapping the entity somewhere surprising. - - **`ApplyEngineConventions(builder, engine)`, `protected static`** (`EntityTypeConfiguration.cs:59-107`): extracted as a static helper so the provider shims share the identical logic. It reads `typeof(TEntity).IsIdValueGenerated` once (`:63`), which is the presence test for [`IdValueGeneratedAttribute`](group-02-domain-building-blocks.md#idvaluegeneratedattribute) on the entity (`EntityTypeExtensions.cs:19`). - - **SQL Server / PostgreSQL branch** (`EntityTypeConfiguration.cs:72-80`): one `case` block deliberately shared by `DataSource.SQLServer` and `DataSource.PostgreSQL` (`:72-73`), documented inline as taking the SQL Server mapping unchanged so an entity moves between the two engines by changing its configuration base class and nothing else, with PostgreSQL house style (snake_case, the public schema) left to a consumer's own naming-convention plugin rather than a framework decision ([ADR-113](https://ivanball.github.io/docs/adr/113-postgresql-as-a-first-class-engine.html), `:67-71`). The shared branch: `ToTable(entityName, moduleName ?? "dbo")`, so the SQL schema is the module name derived from the entity namespace and unmoduled entities land in `dbo`; `HasKey(p => p.Id)`; then `ValueGeneratedOnAdd()` when the entity opts into generated ids, `ValueGeneratedNever()` otherwise. That last branch is what lets a domain factory assign an id itself without EF overwriting it. - - **SQLite branch** (`EntityTypeConfiguration.cs:82-89`): `ToTable(entityName)` with no schema, and the generated-id case adds `.UseIdentityColumn(1, 1)` on top of `ValueGeneratedOnAdd()`. - - **Cosmos branch** (`EntityTypeConfiguration.cs:91-102`): `ToContainer(moduleName ?? entityName).HasPartitionKey(p => p.Id)`, plus `HasValueGenerator()` for generated ids. The inline comment records the reasoning for one container per module (`:92-93`): all of a module's entities share a container so their relationships and the navigation populators still work, and the entity id doubles as the partition key. - - **The default arm** (`EntityTypeConfiguration.cs:104-105`): an unimplemented engine throws instead of silently producing an unmapped entity. - - **`NamespaceConventions.GetModuleName`** (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/NamespaceConventions.cs:20-21`): an internal one-line delegation to `ModuleNameConventions.GetModuleName`, documented as taking the namespace segment immediately preceding `Domain` (`MMCA.Store.Sales.Domain.Orders` yields `Sales`) and returning `null` when there is no `Domain` segment (`NamespaceConventions.cs:13-19`). The same helper feeds the logical database name in the registry (`EntityDataSourceRegistry.cs:181`), which is the point of it being shared: schema and database name can never drift apart. -- **Why it's built this way**: the framework's stated multi-database model is one context class per engine and one instance per database ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)), with the engine as an orthogonal axis ([ADR-018](https://ivanball.github.io/docs/adr/018-polyglot-persistence.html)). That only works if the mapping conventions for an engine live in one place that every context can call, which is this class. Keeping `ApplyEngineConventions` `static` and `protected` rather than inlining it in `Configure` is what allows the shims to exist as empty declarations, and it is why adding PostgreSQL as a fourth engine ([ADR-113](https://ivanball.github.io/docs/adr/113-postgresql-as-a-first-class-engine.html)) cost one `case` label rather than a new mapping method. -- **Where it's used**: extended by the four shim bases below ([`EntityTypeConfigurationSQLServer`](#entitytypeconfigurationsqlservertentity-tidentifiertype), [`EntityTypeConfigurationPostgreSQL`](#entitytypeconfigurationpostgresqltentity-tidentifiertype), [`EntityTypeConfigurationSqlite`](#entitytypeconfigurationsqlitetentity-tidentifiertype), [`EntityTypeConfigurationCosmos`](#entitytypeconfigurationcosmostentity-tidentifiertype)), and directly by any configuration that prefers to carry its own `[UseDataSource(...)]` annotation, as one portability test does (`CosmosConfigurationPortabilityTests.cs:101-103`). Its conventions are pinned against a real SQLite model in `SqliteConfig_Configure_SetsTableNameAndKey` and `SqliteConfig_Configure_SetsValueGeneratedNever_WhenNoAttribute` (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/EntityTypeConfigurationTests.cs:22`, `:37`). +- **Where it's used**: matched by [`SQLServerDbContext`](#sqlserverdbcontext) through `ApplyConfigurationsForEntitiesInContext`, and implemented by [`EntityTypeConfiguration`](#entitytypeconfigurationtentity-tidentifiertype) (`EntityTypeConfiguration.cs:29`), which is how all 28 ADC configurations and all 12 Store configurations reach it through the [`EntityTypeConfigurationSQLServer`](#entitytypeconfigurationsqlservertentity-tidentifiertype) shim. ### ReadRepositoryExtensions @@ -7211,13 +7818,49 @@ survives a module being pulled out into its own service. - **Walkthrough** - **`extension(IReadRepository repository)`** (`ReadRepositoryExtensions.cs:12-14`): a generic extension block whose receiver is the repository; the constraints mirror the interface exactly (`TEntity : AuditableBaseEntity`, `TIdentifierType : notnull`). - **`GetByIdOrFailAsync(id, source, includes, asTracking, cancellationToken)`** (`ReadRepositoryExtensions.cs:27-32`): note the parameters. `source` is a string the caller passes (typically its own type name) so the resulting error can name who produced it; `includes` and `asTracking` are passed straight through, and `asTracking` defaults to `true`, matching the command-handler case where the loaded entity is about to be modified. - - **The lookup** (`ReadRepositoryExtensions.cs:34-38`): it calls `GetAllAsync` with `where: e => e.Id.Equals(id)` rather than a keyed fetch. That is deliberate: `GetAllAsync` is the overload that takes the `includes` collection plus tracking (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:85-92`, declared on `IEntityQuerier`, which `IReadRepository` composes at `IRepository.cs:330-331`), so the helper participates in the full eager-loading pipeline. `includes ?? []` keeps the parameter optional. + - **The lookup** (`ReadRepositoryExtensions.cs:34-38`): it calls `GetAllAsync` with `where: e => e.Id.Equals(id)` rather than a keyed fetch. That is deliberate: `GetAllAsync` is the overload that takes the `includes` collection plus tracking (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:85-92`, declared on `IEntityQuerier`, which `IReadRepository` composes at `IRepository.cs:332-333`), so the helper participates in the full eager-loading pipeline. `includes ?? []` keeps the parameter optional. - **The failure branch** (`ReadRepositoryExtensions.cs:40-45`): `entities.FirstOrDefault()`, and when it is null, `Error.NotFound.WithSource(source).WithTarget(typeof(TEntity).Name)`. [`Error.NotFound`](group-01-result-error-handling.md#error) is the shared static instance (`MMCA.Common/Source/Core/MMCA.Common.Shared/Abstractions/Error.cs:23`) and the two `With*` calls return copies (`Error.cs:120`, `:126`), so the shared instance is never mutated and the caller gets an error that names both the caller and the entity type. - **The success branch** (`ReadRepositoryExtensions.cs:47`): `Result.Success(entity)`. - **Why it's built this way**: handlers in this codebase compose with [`Result`](group-01-result-error-handling.md#result), never exceptions, so a lookup that returns `null` forces every call site to translate. Doing the translation once, in the layer that owns the abstraction, keeps the error code, source, and target consistent across every module and keeps the 404 mapping at the API edge working off one well-known `ErrorType`. - **Where it's used**: the only current callers in the workspace are its own tests (`MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Extensions/ReadRepositoryExtensionsTests.cs:15`, `:40`); no ADC or Store handler calls it today, and handlers there still do the explicit null check. It is available to any handler that resolves a read repository through [`IUnitOfWork.GetReadRepository`](#iunitofwork). - **Caveats / not-in-source**: the method loads through `GetAllAsync`, so it materializes a collection and takes the first element rather than issuing a keyed `FindAsync`; whether that costs anything at the database depends on the provider's translation of the `Id.Equals(id)` predicate and is not determinable from source. +### IndexBuilderExtensions + +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Configuration` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/IndexBuilderExtensions.cs:10` · Level 2 · class (public static, extension container) + +- **What it is**: a single C# `extension(IndexBuilder)` block exposing one member, `HasSoftDeleteFilter(...)`, which attaches the `IsDeleted = 0` predicate to a hand-authored index in an entity type configuration (`IndexBuilderExtensions.cs:10-12`, member at `:50-64`). +- **Depends on**: [`SoftDeleteFilterSql`](#softdeletefiltersql) (the shared predicate builder, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:27`), the [`DataSource`](#datasource) engine enum, and EF Core's `Microsoft.EntityFrameworkCore.Metadata.Builders.IndexBuilder`. +- **Concept introduced, the filtered (partial) index under soft delete.** `[Rubric §8, Data Architecture]` assesses whether the storage design accounts for the consequences of its own conventions, and `[Rubric §12, Performance and Scalability]` assesses whether indexes match the queries they serve. Soft delete ([ADR-005](https://ivanball.github.io/docs/adr/005-soft-delete-vs-erasure.html)) means a "deleted" row is still physically present with `IsDeleted = 1`, and the global query filter on [`ApplicationDbContext`](#applicationdbcontext) hides it from every read. An index that does not carry the same predicate therefore indexes rows no query will ever return: the deleted rows still occupy index pages, and the optimizer's row estimates include them. The doc comment states this in one sentence (`IndexBuilderExtensions.cs:14-18`). Adding `WHERE [IsDeleted] = 0` to the index makes it a *filtered* index (SQL Server's term; SQLite calls the same thing a partial index) that matches the shape of every query the application actually issues. +- **Walkthrough** + - The extension receiver is the `IndexBuilder` returned by `builder.HasIndex(...)`, so the call chains directly off the index declaration (`IndexBuilderExtensions.cs:12`, usage sample at `:23-26`). + - `engine` defaults to `DataSource.SQLServer` (`IndexBuilderExtensions.cs:53`), which matches the majority case of a configuration deriving from [`EntityTypeConfigurationSQLServer`](#entitytypeconfigurationsqlservertentity-tidentifiertype); a SQLite or PostgreSQL configuration passes `DataSource.Sqlite` or `DataSource.PostgreSQL` explicitly. PostgreSQL needs it for a real reason, not just symmetry: its soft-delete flag is a genuine boolean column, so the predicate the engine produces is `"IsDeleted" = false` rather than SQL Server's `[IsDeleted] = 0` (`IndexBuilderExtensions.cs:37-43`). + - `additionalFilter` is the optional second predicate for an index that is already conditional on something else, for example `"[ExternalSessionId] IS NOT NULL"` (`IndexBuilderExtensions.cs:45-50`). The two are joined as `{additionalFilter} AND {filterSql}` in that exact order (`:62-65`), which is deliberate: it reproduces the SQL of the hand-authored literal the helper replaced, so switching to the helper does not force a migration. It is also the order [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) uses when it appends its own clause, so the two paths yield byte-identical SQL for the same pair of predicates. + - The body null-guards the receiver, asks [`SoftDeleteFilterSql`](#softdeletefiltersql) to build the predicate from the model's own metadata, and returns the builder unchanged when the builder returns `null` (`IndexBuilderExtensions.cs:56-60`). That `null` is the Cosmos no-op and the "this entity is not soft-deletable" case, so the call is always safe to write. +- **Why it's built this way**: the doc comment gives the rationale directly (`IndexBuilderExtensions.cs:27-29`). A literal `HasFilter("[IsDeleted] = 0")` hard-codes both the column name and SQL Server's bracket quoting; reading the column name from the model means a `HasColumnName` rename follows automatically, and delegating the quoting to the engine keeps the same configuration body valid on SQLite. This is the portability posture of [ADR-018](https://ivanball.github.io/docs/adr/018-polyglot-persistence.html). The division of labour with [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) is explicit: the convention covers every UNIQUE index automatically, and this extension point exists for the case the convention deliberately leaves alone, a hand-authored NON-unique index (`IndexBuilderExtensions.cs:20-22`). The pair of them is [ADR-095](https://ivanball.github.io/docs/adr/095-soft-delete-unique-indexes.html). +- **Where it's used**: entity configurations across both applications. In MMCA.Store: `MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Infrastructure/Persistence/EntityConfiguration/OrderConfiguration.cs:44,51,58` (the last one with `additionalFilter: "[StripeSessionId] IS NOT NULL"`), `MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Infrastructure/Persistence/EntityConfiguration/UserConfiguration.cs:69,83`, `.../EntityConfiguration/CustomerConfiguration.cs:82`, `MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Infrastructure/Persistence/EntityConfiguration/ProductConfiguration.cs:43`, `.../ProductImageConfiguration.cs:54`, `.../ProductVariantConfiguration.cs:46`, `.../CategoryConfiguration.cs:33`. In MMCA.ADC's Engagement module: `MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/CheckIns/CheckInConfiguration.cs:51,56,62,66,69`, `.../PointsEntryConfiguration.cs:48,52`, `.../LivePollVoteConfiguration.cs:37`, `.../LeaderboardOptInConfiguration.cs:35`, `.../SessionQuestionUpvoteConfiguration.cs:34`, `.../UserSessionBookmarkConfiguration.cs:34,39`. Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/IndexBuilderExtensionsTests.cs`. +- **Caveats / not-in-source**: the ordering constraint is called out in the doc comment (`IndexBuilderExtensions.cs:31-35`). Unlike the convention, which runs at model finalizing, this member reads the column name at the moment it is called, so a `HasColumnName` on the soft-delete property must be declared before the index. That only bites a model that renames the column. + +### EntityTypeConfiguration + +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:27` · Level 12 · class (public abstract) + +- **What it is**: the engine-aware configuration base and the busiest type in this family. It reads the target engine off a [`UseDataSourceAttribute`](group-14-module-system-composition.md#usedatasourceattribute) on the concrete configuration class (or on an inherited shim base) and hands that engine's table/container mapping plus key generation to the matching engine strategy, so a consumer's `Configure` body only ever describes columns, indexes, and relationships (`EntityTypeConfiguration.cs:27-79`). It also exposes `EffectiveEngine`, the engine of the model actually being built, for the few configurations that write engine-specific SQL text themselves (`:54-61`). +- **Depends on**: [`EntityTypeConfigurationBase`](#entitytypeconfigurationbasetentity-tidentifiertype) (base, `EntityTypeConfiguration.cs:28`); all four engine markers, [`IEntityTypeConfigurationSQLServer`](#ientitytypeconfigurationsqlservertentity-tidentifiertype), [`IEntityTypeConfigurationPostgreSQL`](#ientitytypeconfigurationpostgresqltentity-tidentifiertype), [`IEntityTypeConfigurationSqlite`](#ientitytypeconfigurationsqlitetentity-tidentifiertype), [`IEntityTypeConfigurationCosmos`](#ientitytypeconfigurationcosmostentity-tidentifiertype) (`:29-32`); `AuditableBaseEntity` as the entity constraint (`:4`, `:33`); [`UseDataSourceAttribute`](group-14-module-system-composition.md#usedatasourceattribute) and the [`DataSource`](#datasource) enum (`:3`, `:43`, `:61`, `:69`); [`DataSourceEngines`](#datasourceengines) (`:5`, `:77`), which returns the registered [`IDataSourceEngine`](#idatasourceengine) for an engine value (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/DataSourceEngines.cs:40-43`); the internal `EngineAnnotation` constant of [`ApplicationDbContext`](#applicationdbcontext) (`:48`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:67`); `System.Reflection` and EF Core's `EntityTypeBuilder` (`:1-2`). +- **Concept introduced, one configuration body that is portable across storage engines.** The naive way to support four engines is four configuration classes per entity, or one class littered with `if (engine == ...)`. This class removes both. The engine is declared **once**, as an attribute, and everything that actually differs between engines is owned by one strategy per engine: this class looks the strategy up with `DataSourceEngines.For(engine)` and calls its `ApplyKeyAndTableMapping` (`EntityTypeConfiguration.cs:77`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/IDataSourceEngine.cs:95`). [`SQLServerDataSourceEngine`](#sqlserverdatasourceengine) and [`PostgreSQLDataSourceEngine`](#postgresqldatasourceengine) apply the identical mapping, a table in a module schema (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SQLServerDataSourceEngine.cs:100-106`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/PostgreSQLDataSourceEngine.cs:98-101`); [`SqliteDataSourceEngine`](#sqlitedatasourceengine) gives a bare table, since SQLite has no schemas (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SqliteDataSourceEngine.cs:97-102`); [`CosmosDataSourceEngine`](#cosmosdatasourceengine) gives a per-module container with the entity id as partition key (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/CosmosDataSourceEngine.cs:92-101`). Moving an entity from SQL Server to Cosmos, or from SQL Server to PostgreSQL, is therefore a one-line attribute change. Two other pieces of the framework complete the portability claim rather than this class alone: [`CosmosDbContext`](#cosmosdbcontext) strips every relational index from the built model, because the Cosmos provider rejects them and indexes every property itself (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/CosmosDbContext.cs:129-137`), and [`CrossDataSourceDegradeConvention`](#crossdatasourcedegradeconvention) removes FK constraints and navigations that would span physical sources while keeping the declared scalar FK column and a compensating index (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:10-22`). A dedicated test builds a Cosmos model offline from a configuration that keeps a filtered index and a cross-source relationship, and asserts the indexes are gone, the FK is gone, the scalar FK column survives, and the foreign principal is not in the model (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/CosmosConfigurationPortabilityTests.cs:70-76`). `[Rubric §8, Data Architecture]` assesses how deliberately the storage shape is chosen and how tightly the model is bound to one engine. `[Rubric §7, Microservices Readiness]` assesses whether a module can be lifted out without a rewrite; being able to re-point an entity's engine and database with attributes is a precondition for that ([ADR-018](https://ivanball.github.io/docs/adr/018-polyglot-persistence.html), [ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html), [ADR-113](https://ivanball.github.io/docs/adr/113-postgresql-as-a-first-class-engine.html)). `[Rubric §15, Best Practices & Code Quality]` assesses whether one decision lives in one place; each engine's mapping lives in exactly one strategy, and this file names no concrete engine value at all. That last property is enforced: `DataSourceBranchingFitnessTests` scans `Source/**/*.cs` and fails on any concrete engine named outside an exact allow-list, which lists the four shim bases but not this class (`MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/DataSourceBranchingFitnessTests.cs:24-68`, `:71-80`; [ADR-130](https://ivanball.github.io/docs/adr/130-per-engine-data-source-strategy.html)). +- **Concept introduced, discovery and routing that agree by construction.** This class implements **all four** engine marker interfaces (`EntityTypeConfiguration.cs:29-32`), so a configuration derived from it is discovered during every engine's model pass. That sounds wrong until you see the filter: `ApplyConfigurationsForEntitiesInContext` applies a discovered configuration only when [`EntityDataSourceRegistry`](#entitydatasourceregistry) says the entity's [`DataSourceKey`](#datasourcekey) equals this context instance's key (`ApplicationDbContext.cs:978-980`). Both sides read the same attributes: the registry derives the engine from [`UseDataSourceAttribute`](group-14-module-system-composition.md#usedatasourceattribute) and the logical database from [`UseDatabaseAttribute`](group-14-module-system-composition.md#usedatabaseattribute), falling back to the entity's module namespace and then to `Default` (`EntityDataSourceRegistry.cs:180-182`), while this class reads the same attribute for its conventions. Discovery is broad, routing is exact, and there is no second source of truth to drift. `[Rubric §1, SOLID]` assesses whether behavior is driven from one declaration; here the attribute is that declaration. +- **Concept introduced, declared engine versus effective engine.** The declared engine and the engine of the model being built usually agree, but not always: when a host substitutes an unconfigured engine (the doc comment's example is a SQL Server configuration applied to a PostgreSQL-only host), the configuration's own attribute is the wrong input for hand-written SQL such as an index filter (`EntityTypeConfiguration.cs:54-60`). `ApplyConfigurationsForEntitiesInContext` therefore stamps the context's engine onto the model as an annotation for the duration of the configuration pass and removes it in a `finally`, so the finished model and every migration snapshot are unchanged (`ApplicationDbContext.cs:964-969`, `:983-986`). `Configure` copies that annotation into `EffectiveEngine`, falling back to the declared engine when no annotation is present (`EntityTypeConfiguration.cs:48-49`). The key and table mapping still uses the declared engine (`:51`); `EffectiveEngine` is for SQL text only. The framework's own consumers show the use: [`PushNotificationConfiguration`](#pushnotificationconfiguration) builds a filtered unique index whose predicate is quoted for `EffectiveEngine` through [`SoftDeleteFilterSql`](#softdeletefiltersql) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/Notifications/PushNotificationConfiguration.cs:72-73`), and [`UserNotificationConfiguration`](#usernotificationconfiguration) passes it to `HasSoftDeleteFilter` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/Notifications/UserNotificationConfiguration.cs:45`). +- **Walkthrough** + - **Declaration** (`EntityTypeConfiguration.cs:27-34`): abstract, extends the base class, implements the four markers, constrained to an audited entity with a non-null identifier type. + - **`Configure` override** (`EntityTypeConfiguration.cs:37-52`): null-guards the builder (`:39`), calls `base.Configure(builder)` so the `DomainEvents` exclusion runs first (`:41`), then reads the engine. + - **The attribute read and its failure mode** (`EntityTypeConfiguration.cs:43-46`): `GetType().GetCustomAttribute()?.DataSource` on the **runtime** type, which is the concrete configuration. `UseDataSourceAttribute` is declared with `Inherited = true` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/UseDataSourceAttribute.cs:12`), which is exactly why a class deriving from one of the shim bases inherits its engine without repeating the annotation. When the attribute is missing entirely the code throws `InvalidOperationException` naming the offending configuration class, rather than defaulting to an engine and mapping the entity somewhere surprising. + - **`EffectiveEngine`** (`EntityTypeConfiguration.cs:48-49`, `:61`): `protected` with a `private set`, assigned from the model's engine annotation or the declared engine, then `ApplyEngineConventions(builder, engine)` runs with the declared engine (`:51`). The doc comment states it is valid inside `Configure` after the base call (`:58-59`). + - **`ApplyEngineConventions(builder, engine)`, `protected static`** (`EntityTypeConfiguration.cs:69-78`): extracted as a static helper so the provider shims share the identical logic. After its own null guard (`:71`) it is a single delegation, `DataSourceEngines.For(engine).ApplyKeyAndTableMapping(builder)` (`:77`); the inline comment records that each engine owns its mapping, that PostgreSQL takes the SQL Server mapping unchanged ([ADR-113](https://ivanball.github.io/docs/adr/113-postgresql-as-a-first-class-engine.html)), and that Cosmos puts a module's entities in one container partitioned by id (`:73-76`). An engine value with no registered strategy throws `InvalidOperationException` ("DataSource ... not implemented") from `DataSourceEngines.For` instead of silently producing an unmapped entity (`DataSourceEngines.cs:40-43`). + - **What the strategies apply**: every engine calls `HasKey(p => p.Id)` and reads `typeof(TEntity).IsIdValueGenerated`, the presence test for [`IdValueGeneratedAttribute`](group-02-domain-building-blocks.md#idvaluegeneratedattribute) via [`EntityTypeExtensions`](group-02-domain-building-blocks.md#entitytypeextensions) (`MMCA.Common/Source/Core/MMCA.Common.Domain/Extensions/EntityTypeExtensions.cs:19`). SQL Server and PostgreSQL map `ToTable(entityName, moduleName ?? "dbo")`, so the SQL schema is the module name derived from the entity namespace and unmoduled entities land in `dbo`, then `ValueGeneratedOnAdd()` when the entity opts into generated ids and `ValueGeneratedNever()` otherwise (`SQLServerDataSourceEngine.cs:101-106`). That last branch is what lets a domain factory assign an id itself without EF overwriting it. SQLite maps `ToTable(entityName)` with no schema and adds `.UseIdentityColumn(1, 1)` in the generated-id case (`SqliteDataSourceEngine.cs:97-102`). Cosmos maps `ToContainer(moduleName ?? entityName).HasPartitionKey(p => p.Id)` and uses [`CosmosIntIdValueGenerator`](#cosmosintidvaluegenerator) for generated ids (`CosmosDataSourceEngine.cs:94-101`). + - **`NamespaceConventions.GetModuleName`** (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/NamespaceConventions.cs:20-21`): an internal one-line delegation to `ModuleNameConventions.GetModuleName`, documented as taking the namespace segment immediately preceding `Domain` (`MMCA.Store.Sales.Domain.Orders` yields `Sales`) and returning `null` when there is no `Domain` segment (`NamespaceConventions.cs:13-19`). The engine strategies use it for the schema and container name (`SQLServerDataSourceEngine.cs:101`, `CosmosDataSourceEngine.cs:95`), and the same helper feeds the logical database name in the registry (`EntityDataSourceRegistry.cs:181`), which is the point of it being shared: schema and database name can never drift apart. +- **Why it's built this way**: the framework's stated multi-database model is one context class per engine and one instance per database ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)), with the engine as an orthogonal axis ([ADR-018](https://ivanball.github.io/docs/adr/018-polyglot-persistence.html)). That only works if the mapping conventions for an engine live in one place that every context can call. This class is the single entry point and the engine strategy is that place ([ADR-130](https://ivanball.github.io/docs/adr/130-per-engine-data-source-strategy.html)), so adding an engine means one more strategy in the `DataSourceEngines` registry (`DataSourceEngines.cs:22-28`) rather than an edit here. Keeping `ApplyEngineConventions` `static` and `protected` rather than inlining it in `Configure` is what allows the shims to exist as empty declarations. +- **Where it's used**: extended by the four shim bases below ([`EntityTypeConfigurationSQLServer`](#entitytypeconfigurationsqlservertentity-tidentifiertype), [`EntityTypeConfigurationPostgreSQL`](#entitytypeconfigurationpostgresqltentity-tidentifiertype), [`EntityTypeConfigurationSqlite`](#entitytypeconfigurationsqlitetentity-tidentifiertype), [`EntityTypeConfigurationCosmos`](#entitytypeconfigurationcosmostentity-tidentifiertype)), and directly by any configuration that prefers to carry its own `[UseDataSource(...)]` annotation, as one portability test does (`CosmosConfigurationPortabilityTests.cs:101-103`). `EffectiveEngine` is read by the framework's notification configurations (`PushNotificationConfiguration.cs:72-73`, `UserNotificationConfiguration.cs:45`). Its conventions are pinned against a real SQLite model in `SqliteConfig_Configure_SetsTableNameAndKey` and `SqliteConfig_Configure_SetsValueGeneratedNever_WhenNoAttribute` (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/EntityTypeConfigurationTests.cs:22`, `:37`). + ### EntityTypeConfigurationCosmos > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfigurationCosmos.cs:18` · Level 7 · class (public abstract) @@ -7233,7 +7876,7 @@ survives a module being pulled out into its own service. - **What it is**: the PostgreSQL shim, `[UseDataSource(DataSource.PostgreSQL)]` over the engine-aware base (`EntityTypeConfigurationPostgreSQL.cs:21-25`). Its own doc records that the mapping it produces is the SQL Server one, not PostgreSQL house style: a table per entity inside the module schema with an identity key, so an entity moves between the two engines with a single base-class change and no configuration-body edits (`:6-17`). - **Depends on**: [`EntityTypeConfiguration`](#entitytypeconfigurationtentity-tidentifiertype) (base, `EntityTypeConfigurationPostgreSQL.cs:23`); [`UseDataSourceAttribute`](group-14-module-system-composition.md#usedatasourceattribute) and [`DataSource`](#datasource) (`:21`); [`AuditableBaseEntity`](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype) (`:24`). -- **Concept reinforced**: the attribute-as-base-class shim taught at [`EntityTypeConfigurationCosmos`](#entitytypeconfigurationcosmostentity-tidentifiertype). Unlike the Cosmos and SQLite shims it delegates to no engine-specific mapping code of its own: the engine-aware base's SQL Server branch handles `DataSource.SQLServer` and `DataSource.PostgreSQL` together in one `case` block, deliberately, so PostgreSQL takes the SQL Server table/key mapping unchanged (`EntityTypeConfiguration.cs:72-80`); PostgreSQL house style (snake_case identifiers, the `public` schema) is left to a consumer's own naming-convention plugin rather than built into the framework ([ADR-113](https://ivanball.github.io/docs/adr/113-postgresql-as-a-first-class-engine.html), `EntityTypeConfiguration.cs:67-71`). +- **Concept reinforced**: the attribute-as-base-class shim taught at [`EntityTypeConfigurationCosmos`](#entitytypeconfigurationcosmostentity-tidentifiertype). The engine-aware base hands the table/key mapping to the target engine (`DataSourceEngines.For(engine).ApplyKeyAndTableMapping`, `EntityTypeConfiguration.cs:73-77`), and the PostgreSQL engine's implementation is deliberately identical to SQL Server's: a table named for the entity in the module schema (falling back to `dbo`), the `Id` key, and store generation only when the identifier type is value-generated (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/PostgreSQLDataSourceEngine.cs:92-106`). PostgreSQL house style (snake_case identifiers, the `public` schema) is left to a consumer's own naming-convention plugin rather than built into the framework ([ADR-113](https://ivanball.github.io/docs/adr/113-postgresql-as-a-first-class-engine.html), `EntityTypeConfiguration.cs:73-76`). - **Where it's used**: exercised by the framework's own PostgreSQL test suite, `PostgreSQLPersistenceTests.cs`, `DesignTimeDbContextHelperTests.cs`, and `PostgreSQLDbContextModelTests.cs` (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.PostgreSQL.Tests/` and `MMCA.Common.Infrastructure.Tests/`). No production configuration in ADC, Store, or Helpdesk derives from it today, matching the state ADR-113 records: the engine is plumbed and tested, with no PostgreSQL entity shipped in either app yet. ### EntityTypeConfigurationSqlite @@ -7242,8 +7885,8 @@ survives a module being pulled out into its own service. - **What it is**: the SQLite shim, identical in shape to its Cosmos sibling: a body-less class carrying `[UseDataSource(DataSource.Sqlite)]` (`EntityTypeConfigurationSqlite.cs:16-20`). - **Depends on**: [`EntityTypeConfiguration`](#entitytypeconfigurationtentity-tidentifiertype) (base, `EntityTypeConfigurationSqlite.cs:18`); [`UseDataSourceAttribute`](group-14-module-system-composition.md#usedatasourceattribute) and [`DataSource`](#datasource) (`:1`, `:16`); [`AuditableBaseEntity`](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype) (`:19`). -- **Concept reinforced**: the attribute-as-base-class shim taught at [`EntityTypeConfigurationCosmos`](#entitytypeconfigurationcosmostentity-tidentifiertype). Its doc records the SQLite-specific mapping it delegates: table name plus an auto-increment key (`EntityTypeConfigurationSqlite.cs:7-12`), implemented in the engine-aware base at `EntityTypeConfiguration.cs:82-89`. -- **Where it's used**: no production configuration in the three repos derives from it, but it is the framework's own in-memory-and-file test engine and is used throughout the Common test suite: the database-initialization tests (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:271`, `:279`), the convention tests (`EntityTypeConfigurationTests.cs:58`), the multi-source integration tests (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/MultiSourceSqliteIntegrationTests.cs:240`, `:241`), and the registry tests (`EntityDataSourceRegistryTests.cs:217`, `:220`, including a deliberate duplicate-configuration pair at `:228` and `:231`). +- **Concept reinforced**: the attribute-as-base-class shim taught at [`EntityTypeConfigurationCosmos`](#entitytypeconfigurationcosmostentity-tidentifiertype). Its doc records the SQLite-specific mapping it delegates: table name plus an auto-increment key (`EntityTypeConfigurationSqlite.cs:7-12`), which the engine-aware base hands to the SQLite engine (`EntityTypeConfiguration.cs:77`), implemented at `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SqliteDataSourceEngine.cs:91-103`. +- **Where it's used**: no production configuration in the three repos derives from it, but it is the framework's own in-memory-and-file test engine and is used throughout the Common test suite: the database-initialization tests (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:321`, `:279`), the convention tests (`EntityTypeConfigurationTests.cs:58`), the multi-source integration tests (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/MultiSourceSqliteIntegrationTests.cs:240`, `:241`), and the registry tests (`EntityDataSourceRegistryTests.cs:217`, `:220`, including a deliberate duplicate-configuration pair at `:228` and `:231`). ### EntityTypeConfigurationSQLServer @@ -7254,57 +7897,104 @@ survives a module being pulled out into its own service. - **Concept reinforced, and what a consumer inherits by writing one base name.** Deriving from this class buys four behaviors without a line of code: the `DomainEvents` exclusion from [`EntityTypeConfigurationBase`](#entitytypeconfigurationbasetentity-tidentifiertype), a table named after the entity in a schema named after the module (`EntityTypeConfiguration.cs:74`), a primary key with the right generation policy for the entity's [`IdValueGeneratedAttribute`](group-02-domain-building-blocks.md#idvaluegeneratedattribute) (`:67-71`), and a [`DataSourceKey`](#datasourcekey) in [`EntityDataSourceRegistry`](#entitydatasourceregistry) that makes the entity routable through [`IUnitOfWork`](#iunitofwork) and [`DbContextFactory`](#dbcontextfactory). Adding a class-level [`UseDatabaseAttribute`](group-14-module-system-composition.md#usedatabaseattribute) on top overrides only the logical database, leaving the engine alone (`EntityDataSourceRegistry.cs:180-182`). `[Rubric §15, Best Practices & Code Quality]` assesses how much a consumer must know to add an entity correctly; here it is one base class name. - **Where it's used**: 28 configurations across ADC's Conference, Engagement, and Identity modules (for example `MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Infrastructure/Persistence/EntityConfiguration/UserConfiguration.cs:13` and `MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/LivePolls/LivePollConfiguration.cs:16`), 12 across Store's Catalog, Sales, and Identity modules, MMCA.Helpdesk's two Tickets configurations (`MMCA.Helpdesk/Source/Modules/Tickets/MMCA.Helpdesk.Tickets.Infrastructure/Persistence/EntityConfiguration/TicketConfiguration.cs`, `MMCA.Helpdesk/Source/Modules/Tickets/MMCA.Helpdesk.Tickets.Infrastructure/Persistence/EntityConfiguration/TicketCommentConfiguration.cs`), and the framework's own notification configurations, [`PushNotificationConfiguration`](#pushnotificationconfiguration) and [`UserNotificationConfiguration`](#usernotificationconfiguration). It is also the SQL Server half of the cross-engine portability test (`CosmosConfigurationPortabilityTests.cs:116`). +### StronglyTypedIdModelConfiguration + +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Conventions` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/StronglyTypedIdModelConfiguration.cs:21` · Level 6 · class (public static) + +- **What it is**: the single call that wires strongly typed identifiers (ADR-115) into EF Core's model, once per host. `Apply` walks the host's [`StronglyTypedIdRegistry`](group-08-auth.md#stronglytypedidregistry) and declares one pre-convention type mapping per identifier type it describes, so every property of that type, keys, cross-module scalar references, owned-type members and the properties of a framework table alike, maps to its wrapped primitive (`StronglyTypedIdModelConfiguration.cs:7-12`, `:29-51`). +- **Depends on**: EF Core's `ModelConfigurationBuilder`, [`StronglyTypedIdValueConverter`](#stronglytypedidvalueconvertertself-tvalue) and its comparer counterpart from `MMCA.Common.Infrastructure.Persistence.Conversions`, and `StronglyTypedIdRegistry` from `MMCA.Common.Shared.Identifiers`. +- **Concept introduced, pre-convention configuration versus a model-finalizing convention.** `[Rubric §8, Data Architecture]` assesses whether a cross-cutting mapping rule is applied at the point in the pipeline where it actually works, and `[Rubric §1, SOLID]` assesses whether the opt-in stays orthogonal to code that never adopts it. EF Core exposes two very different places to declare a type-wide rule: `ConfigureConventions` (pre-convention, runs before the provider's own conventions see the model) and a model-finalizing `IModelFinalizingConvention` like [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) (runs last). The class doc comment states why this one has to be the former (`StronglyTypedIdModelConfiguration.cs:14-18`): a converter attached at finalization arrives after the provider's value-generation conventions have already inspected the property, so a wrapped `int` key would silently lose its IDENTITY strategy. Declaring the mapping here means the provider CLR type is known before any of that runs, and a wrapped key generates exactly as its primitive did. +- **Walkthrough** + - `Apply(configurationBuilder, registry)` (`StronglyTypedIdModelConfiguration.cs:29`) null-guards both arguments, then iterates `registry.Describe()`, which yields the `(identifierType, valueType)` pairs the host declared (`:36`). + - For each pair it closes the two open generics reflectively: `StronglyTypedIdValueConverter<,>.MakeGenericType(identifierType, valueType)` and `StronglyTypedIdValueComparer<>.MakeGenericType(identifierType)` (`StronglyTypedIdModelConfiguration.cs:38-41`), then calls `configurationBuilder.Properties(identifierType).HaveConversion(converterType, comparerType)` (`:43-45`), the pre-convention API that applies the conversion to every property of that CLR type across the whole model, not just one entity's. + - `configured` counts the identifiers actually wired and is returned (`StronglyTypedIdModelConfiguration.cs:34,47,50`), giving the caller a number to log or assert against rather than a bare `void`. +- **Why it's built this way**: strongly typed identifiers sit on the same opt-in footing as smart enumerations (ADR-115): a wrapper is two lines, and adopting the whole feature is one DI call, `services.AddStronglyTypedIds(typeof(OrderId).Assembly)`. The EF half of that contract has to be equally uniform, one call from `ApplicationDbContext.ConfigureConventions`, which every engine context inherits, so SQL Server, PostgreSQL, SQLite and Cosmos all get the same mapping without an engine branch (`StronglyTypedIdModelConfiguration.cs:10-12`). Driving the mapping from the registry rather than from a hard-coded type list means a host that never wraps an identifier calls `Apply` over an empty registry and nothing changes. +- **Where it's used**: called once from [`ApplicationDbContext`](#applicationdbcontext)'s `ConfigureConventions` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs`). + ### IRepositoryFactory > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Repositories.Factory` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/Factory/IRepositoryFactory.cs:11` · Level 7 · interface (public) - **What it is**: a two-method contract that builds a repository over a **caller-supplied** `DbContext` (`IRepositoryFactory.cs:11-34`). `Create` returns a read-write [`IRepository`](#irepositorytentity-tidentifiertype), `CreateReadOnly` returns an [`IReadRepository`](#ireadrepositorytentity-tidentifiertype), and the class doc records the one behavior an implementation is expected to fold in: conditional MiniProfiler wrapping (`IRepositoryFactory.cs:7-10`). - **Depends on**: EF Core's `DbContext` as the single parameter of both methods (NuGet, `IRepositoryFactory.cs:1`, `:20`, `:31`); [`IRepository`](#irepositorytentity-tidentifiertype) and [`IReadRepository`](#ireadrepositorytentity-tidentifiertype) as return types (`:2`, `:19`, `:30`); [`AuditableAggregateRootEntity`](group-02-domain-building-blocks.md#auditableaggregaterootentitytidentifiertype) and [`AuditableBaseEntity`](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype) as the two entity constraints (`:3`, `:21`, `:32`). -- **Concept introduced, a factory for the argument DI cannot supply.** Plain constructor injection can hand a repository a `DbContext`, and the container does exactly that for the open-generic registration `TryAddScoped(typeof(IRepository<,>), typeof(EFRepository<,>))` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:122`). That registration can only ever bind **one** context, because DI resolves by type. This framework does not have one context: it has one context **instance per** [`DataSourceKey`](#datasourcekey), created and cached per scope by [`DbContextFactory`](#dbcontextfactory), and which instance an entity belongs to is a runtime lookup through [`IDataSourceService`](#idatasourceservice) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:40-41`). So the context stops being a dependency and becomes an argument, and an argument-taking creation step is a factory. This is the type-level reason the codebase forbids constructor-injecting `IRepository<,>` directly: that path silently binds whatever the container's single registration resolves, while everything routed through [`IUnitOfWork`](#iunitofwork) reaches this factory and gets the context its entity actually lives in. `[Rubric §2, Design Patterns]` assesses whether a pattern earns its place rather than decorating the code; here the factory exists because DI provably cannot express the requirement. `[Rubric §8, Data Architecture]` assesses how deliberately storage boundaries are drawn; per-source repository construction is what keeps database-per-service ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)) true at the level a handler touches. +- **Concept introduced, a factory for the argument DI cannot supply.** Plain constructor injection can hand a repository a `DbContext`, and the container does exactly that for the open-generic registration `TryAddScoped(typeof(IRepository<,>), typeof(EFRepository<,>))` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:128`). That registration can only ever bind **one** context, because DI resolves by type. This framework does not have one context: it has one context **instance per** [`DataSourceKey`](#datasourcekey), created and cached per scope by [`DbContextFactory`](#dbcontextfactory), and which instance an entity belongs to is a runtime lookup through [`IDataSourceService`](#idatasourceservice) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:40-41`). So the context stops being a dependency and becomes an argument, and an argument-taking creation step is a factory. This is the type-level reason the codebase forbids constructor-injecting `IRepository<,>` directly: that path silently binds whatever the container's single registration resolves, while everything routed through [`IUnitOfWork`](#iunitofwork) reaches this factory and gets the context its entity actually lives in. `[Rubric §2, Design Patterns]` assesses whether a pattern earns its place rather than decorating the code; here the factory exists because DI provably cannot express the requirement. `[Rubric §8, Data Architecture]` assesses how deliberately storage boundaries are drawn; per-source repository construction is what keeps database-per-service ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)) true at the level a handler touches. - **Walkthrough** - **`Create(DbContext dbContext)`** (`IRepositoryFactory.cs:19-22`): constrained to `TEntity : AuditableAggregateRootEntity` and `TIdentifierType : notnull`. Writes go through aggregate roots only, which is the DDD rule expressed in the signature rather than in a comment. - **`CreateReadOnly(DbContext dbContext)`** (`IRepositoryFactory.cs:30-33`): the same shape with a looser entity constraint, [`AuditableBaseEntity`](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype). Reads may target a child entity that is not itself an aggregate root, which is exactly the asymmetry [`IUnitOfWork`](#iunitofwork) exposes on its own `GetRepository` / `GetReadRepository` pair (`UnitOfWork.cs:33-35`, `:53-55`). - **What the interface deliberately does not say**: nothing about profiling, decoration, caching, or activation. Those are implementation choices, and [`RepositoryFactory`](#repositoryfactory) makes them all. - **Why it's built this way**: extracting the two lines from `UnitOfWork` into a contract means the unit of work never asks "is profiling on"; it asks for a repository and gets whichever composition the host configured. It also gives the test suite a substitution point that does not require a real context factory. -- **Where it's used**: registered scoped as `IRepositoryFactory -> RepositoryFactory` (`DependencyInjection.cs:123`) and pinned by `AddInfrastructure_RegistersIRepositoryFactory` (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/DependencyInjectionInfrastructureTests.cs:239-252`). Its only production consumer is [`UnitOfWork`](#unitofwork), which injects it (`UnitOfWork.cs:13`, `:16`) and calls `Create` (`:42`) and `CreateReadOnly` (`:62`) once per entity type, caching the result for the rest of the scope (`:38-44`, `:58-64`). +- **Where it's used**: registered scoped as `IRepositoryFactory -> RepositoryFactory` (`DependencyInjection.cs:129`) and pinned by `AddInfrastructure_RegistersIRepositoryFactory` (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/DependencyInjectionInfrastructureTests.cs:239-252`). Its only production consumer is [`UnitOfWork`](#unitofwork), which injects it (`UnitOfWork.cs:13`, `:16`) and calls `Create` (`:42`) and `CreateReadOnly` (`:62`) once per entity type, caching the result for the rest of the scope (`:38-44`, `:58-64`). + +### CrossDataSourceDegradeConvention + +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Conventions` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:34` · Level 11 · class (public sealed) + +- **What it is**: the model-finalizing convention that makes database-per-service work without forcing every module to write two versions of its entity configuration. It finds relationships whose two ends resolve to different physical databases and degrades them: the foreign key goes away, the navigation members are ignored, and entity types belonging to another database are removed from this model entirely (`CrossDataSourceDegradeConvention.cs:10-34`). +- **Depends on**: [`DataSourceKey`](#datasourcekey) (the context's own physical source) and [`IEntityDataSourceRegistry`](#ientitydatasourceregistry) (the entity-to-database map), both primary-constructor parameters (`CrossDataSourceDegradeConvention.cs:34-36`); [`DataSourceEngines`](#datasourceengines) and the `IsRelational` flag of [`DataSourceEngineCapabilities`](#datasourceenginecapabilities), read to decide whether a compensating index is allowed (`CrossDataSourceDegradeConvention.cs:66`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/DataSourceEngines.cs:40`); EF Core's `IModelFinalizingConvention` plus the **mutable** metadata surface (`IMutableModel`, `IMutableEntityType`, `IMutableForeignKey`, `IMutableProperty`). +- **Concept introduced, degrading a relationship instead of forbidding it.** `[Rubric §7, Microservices Readiness]` assesses whether a module can be lifted into its own service without rewriting application code, and `[Rubric §8, Data Architecture]` assesses whether ownership boundaries in the data model are real. Under database-per-service ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)) an order row and a user row can live in different databases, and no database engine can enforce a foreign key across that line. The naive answers are both bad: forbid the navigation in the domain model (which distorts the domain to fit deployment), or keep the FK and hope the two entities always end up co-located (which breaks the moment they do not). This convention takes a third route. The configuration body is written once, as if everything were in one database, and the model builder subtracts what the current physical source cannot support. Three things are given up and each has a compensating mechanism: the FK constraint is replaced by an index over the surviving scalar columns, in-database navigation is replaced by the `INavigationPopulator` batch-loading path ([ADR-002](https://ivanball.github.io/docs/adr/002-navigation-populators.html)), and referential consistency is maintained by integration events rather than by the engine (`CrossDataSourceDegradeConvention.cs:13-22`). The payoff is stated at `:26-30`: when every entity resolves to the same source, nothing is foreign and the convention is a structural no-op, so the monolith model is identical to the single-database model. +- **Walkthrough** + - `ProcessModelFinalizing` opens by casting the convention model builder's metadata to `IMutableModel` (`CrossDataSourceDegradeConvention.cs:45-47`). The class doc behind the cast is load-bearing: cross-cutting helpers (soft-delete filters, concurrency tokens) have already promoted every entity type to the `Explicit` configuration source, and convention-sourced builder calls cannot override `Explicit`, so the mutable API is the only surface that can apply these changes (`:23-25`). + - `IsForeign` (`CrossDataSourceDegradeConvention.cs:92-95`) is the whole routing decision: look the CLR type's full name up in the registry, and call it foreign when the registry knows it and its key differs from this context's key. An unregistered type is never foreign. + - The foreign set is computed over non-owned entity types, and when it is empty the method returns at once (`CrossDataSourceDegradeConvention.cs:49-56`). That early return is the monolith fast path. + - **Step 1, degrade the FKs** (`CrossDataSourceDegradeConvention.cs:63-75`): for each local entity, every declared foreign key whose principal is foreign is passed to `DegradeForeignKey`. FKs declared on foreign dependents are not touched here because step 3 removes those entity types wholesale. Note that `addCompensatingIndex` is computed once, as the context engine's `IsRelational` capability (`DataSourceEngines.For(contextKey.Engine).Capabilities.IsRelational`, `:66`), not as a comparison against one named engine. + - `DegradeForeignKey` (`CrossDataSourceDegradeConvention.cs:108-139`) captures the non-shadow FK properties, removes the FK (which takes both navigations with it), and returns early when no compensating index is wanted or no scalar column survives (`:119-122`). The subtle part is `:124-131`: EF's own `ForeignKeyIndexConvention` created an index for the FK, and its removal is processed by a **deferred** event that would fire after the coverage check below, silently leaving the column unindexed. So the convention drops that convention-sourced index eagerly itself, then checks coverage and adds a plain index only if nothing already covers the columns (`:133-138`). + - `HasCoveringIndex` (`CrossDataSourceDegradeConvention.cs:141-145`) treats an index as covering when the FK columns are a **prefix** of its column list, in order, which is exactly the condition under which a composite index can serve a lookup on those columns. + - **Step 2, ignore the foreign members** (`CrossDataSourceDegradeConvention.cs:80-83`, implementation at `:152-166`): skip navigations pointing at foreign entities are removed, then every CLR property whose type (or collection element type) is foreign is added to the ignore list. The comment at `:77-79` explains why the second half is needed: removing a navigation leaves an unmapped CLR property of entity type behind, and EF's model validation rejects that. + - `UnwrapCollectionElementType` (`CrossDataSourceDegradeConvention.cs:172-175`) is the one-argument-generic unwrap that makes `ICollection` detectable as a collection of foreign entities. + - **Step 3, remove the foreign entity types** (`CrossDataSourceDegradeConvention.cs:85-89`). EF's relationship discovery pulls foreign types into the model as a side effect; this is where they leave it. +- **Why it's built this way**: the carve-out on the compensating index is spelled out at `CrossDataSourceDegradeConvention.cs:101-106`. Cosmos, the one non-relational engine (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/DataSourceEngineCapabilities.cs:14-19`), auto-indexes every property and rejects explicit index definitions, so adding a compensating index would fail model validation, and skipping it is what keeps a configuration body carrying a cross-source relationship portable to Cosmos without edits ([ADR-018](https://ivanball.github.io/docs/adr/018-polyglot-persistence.html)). Reading the `IsRelational` capability rather than testing for `DataSource.CosmosDB` puts this decision on the same single flag that [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) and [`RestrictDeleteByDefaultConvention`](#restrictdeletebydefaultconvention) consult for their own engine guard, so the three conventions cannot disagree about which engines support indexes and constraints. Doing all of this at model finalizing rather than at configuration time is what lets the same entity configuration serve the monolith and the extracted-service topology ([ADR-008](https://ivanball.github.io/docs/adr/008-service-extraction-topology.html)) with no per-topology branching in module code. +- **Where it's used**: registered by [`ApplicationDbContext`](#applicationdbcontext) in `ConfigureConventions` with the context's `DataSourceKey` and the resolved [`IEntityDataSourceRegistry`](#ientitydatasourceregistry) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:383-387`), first of the three finalizing conventions, ahead of [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) (`:392`) and [`RestrictDeleteByDefaultConvention`](#restrictdeletebydefaultconvention) (`:399`). Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/CrossDataSourceDegradeConventionTests.cs`. + +### RestrictDeleteByDefaultConvention + +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Conventions` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/RestrictDeleteByDefaultConvention.cs:42` · Level 11 · class (public sealed) + +- **What it is**: a model-finalizing convention that stamps every foreign key in the model with the source of its delete behavior, and restricts the ones nobody configured. It walks every declared foreign key once and defaults an unconfigured or convention-sourced cascade to `DeleteBehavior.Restrict`, unless the foreign key belongs to an owned-type relationship, whose cascade EF itself owns (`RestrictDeleteByDefaultConvention.cs:42-108`). +- **Depends on**: EF Core's `IModelFinalizingConvention`, `IConventionModelBuilder`, `IConventionForeignKey`, `ConfigurationSource` and `DeleteBehavior`; [`DataSource`](#datasource) (its one primary-constructor parameter, `RestrictDeleteByDefaultConvention.cs:42`); and [`DataSourceEngines`](#datasourceengines) with the `IsRelational` flag of [`DataSourceEngineCapabilities`](#datasourceenginecapabilities) for its engine guard (`:66`). +- **Concept introduced, defaulting to the safe cascade instead of the convenient one.** `[Rubric §8, Data Architecture]` assesses whether the storage design prevents the failure modes its own conventions could otherwise create, and `[Rubric §15, Best Practices and Code Quality]` assesses whether a default is a deliberate choice rather than whatever the underlying framework happens to ship. EF Core's own convention cascades a required foreign key by default, which means a relationship nobody explicitly configured silently deletes dependents when the principal is deleted, below the aggregate's invariants, the soft-delete filter and the domain events (`RestrictDeleteByDefaultConvention.cs:14-21`). This convention overrides that default: an unconfigured or convention-sourced delete behavior becomes `Restrict`, so a delete that would fan out across rows the domain never asked to remove fails loudly at the database instead of succeeding silently, and a genuine cascade becomes an `.OnDelete(DeleteBehavior.Cascade)` written in the entity configuration. +- **Walkthrough** + - `DeleteBehaviorSourceAnnotation`, `ExplicitSource`, `ConventionSource` and `OwnershipSource` (`RestrictDeleteByDefaultConvention.cs:44-57`) are the four public constants: the annotation name the convention stamps on every foreign key (`MMCA:DeleteBehaviorSource`, `:48`), and the three values it can carry, recording whether the behavior came from an explicit configuration call, from this convention, or from EF's ownership handling. + - `ProcessModelFinalizing` (`RestrictDeleteByDefaultConvention.cs:60-81`) null-guards the model builder, returns immediately when the engine is not relational (`!DataSourceEngines.For(engine).Capabilities.IsRelational`, `:66-69`, the same capability check [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) and [`CrossDataSourceDegradeConvention`](#crossdatasourcedegradeconvention) make), then materializes every declared foreign key across the model into a list before applying anything (`:71-75`). The class doc gives the non-relational reason: Cosmos has no foreign-key constraints to restrict, and stamping a delete-behavior decision on a model that cannot enforce one would only make the audit lie (`:35-39`). The comment above the materialization names why it is a list: the cross-source convention runs first and may already have removed relationships, so nothing here should observe a collection mid-mutation. + - `Apply` (`RestrictDeleteByDefaultConvention.cs:88-107`) is the per-foreign-key decision. An ownership foreign key is stamped `OwnershipSource` and left untouched (`:90-94`): EF owns that cascade, and overriding it would fight the owned-type feature. Otherwise the foreign key's existing delete-behavior `ConfigurationSource` is read; `null` (nobody ever set one) or `ConfigurationSource.Convention` (EF's own required-FK rule produced the cascading default) are both treated as "inherited, and ours to replace": the behavior is set to `DeleteBehavior.Restrict` and stamped `ConventionSource` (`:96-104`). Anything else, meaning a configuration explicitly set a delete behavior, is stamped `ExplicitSource` and left exactly as configured (`:106`). +- **Why it's built this way**: the annotation is not cosmetic. Stamping the source of every delete behavior, rather than just flipping the ones that need it, gives a fitness test a byte-for-byte answer to "did this foreign key's cascade come from an explicit choice, from this convention, or from ownership", read straight off a finalized model without re-running the convention (`RestrictDeleteByDefaultConvention.cs:29-34`). That is what [ADR-119](https://ivanball.github.io/docs/adr/119-restrict-delete-by-default.html) records as the decision: default every unconfigured relationship to `Restrict` rather than to EF's own cascading default, and make the source of that decision inspectable rather than implicit. Running at model finalizing, after every module's `IEntityTypeConfiguration` has declared its own relationships, is what lets the convention see the whole model without needing to know which modules exist, the same timing [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) uses for the same reason. +- **Where it's used**: registered by [`ApplicationDbContext`](#applicationdbcontext) in `ConfigureConventions` with the context's own engine (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:399`), last of the three finalizing conventions so it never stamps a relationship the cross-source convention has already removed (`ApplicationDbContext.cs:394-398`). Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Conventions/RestrictDeleteByDefaultConventionTests.cs` and by `MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Domain/DeleteBehaviorConventionTests.cs`, which subclasses the shared `DeleteBehaviorConventionTestsBase` and the `ArchitectureRules.DeleteBehavior` rule group so ADC's own model is checked against the same contract. + +### SoftDeleteUniqueIndexConvention + +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Conventions` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:34` · Level 11 · class (public sealed) + +- **What it is**: an EF Core model-finalizing convention that puts the `IsDeleted = 0` predicate on every **unique** index of every soft-deletable entity type in the model, so a soft-deleted row stops occupying its unique slot (`SoftDeleteUniqueIndexConvention.cs:11-34`). +- **Depends on**: EF Core's `IModelFinalizingConvention`, `IConventionModelBuilder` and `IConventionEntityType`; [`SoftDeleteFilterSql`](#softdeletefiltersql) for the predicate text; the [`DataSource`](#datasource) engine enum, taken as its one primary-constructor parameter (`SoftDeleteUniqueIndexConvention.cs:34`); [`DataSourceEngines`](#datasourceengines) with the `IsRelational` flag of [`DataSourceEngineCapabilities`](#datasourceenginecapabilities) for its engine guard (`:43`); and [`IAuditableEntity`](group-02-domain-building-blocks.md#iauditableentity) as the marker for "this entity is soft-deletable". +- **Concept introduced, the model-finalizing convention as a cross-cutting model rule.** `[Rubric §6, CQRS & Event-Driven Design]` assesses whether policies that apply to every entity are expressed once rather than repeated per configuration, and `[Rubric §8, Data Architecture]` assesses whether the data model's invariants actually hold. EF exposes convention hooks that run while the model is being built; `IModelFinalizingConvention` is the last of them, which means it observes the model *after* every module's `IEntityTypeConfiguration` has declared its indexes. That timing is what makes a blanket rule possible: the convention does not need to know which modules exist or what they declared, it just walks the finished model. The bug it closes is a genuine soft-delete trap, stated in the doc comment (`SoftDeleteUniqueIndexConvention.cs:12-17`): soft-delete a speaker, and the unique index on email still blocks creating a new speaker with that email, because the row is invisible to the application but entirely visible to the database's uniqueness check. Adding the filter aligns what the database enforces with what the application shows. +- **Walkthrough** + - `ProcessModelFinalizing` (`SoftDeleteUniqueIndexConvention.cs:37-51`) null-guards the model builder, then returns immediately when the engine is not relational (`!DataSourceEngines.For(engine).Capabilities.IsRelational`, `:43-44`). The class doc names the coverage: SQL Server, PostgreSQL and SQLite all support partial or filtered indexes, and the convention is a no-op for Cosmos (`:28-31`). + - The entity selection is two predicates (`SoftDeleteUniqueIndexConvention.cs:46-47`): the CLR type must be assignable to [`IAuditableEntity`](group-02-domain-building-blocks.md#iauditableentity), and the entity type must not be owned. Owned types share their owner's table and have no independent index story, so they are excluded. + - `ApplyFilterToUniqueIndexes` (`SoftDeleteUniqueIndexConvention.cs:53-82`) asks [`SoftDeleteFilterSql`](#softdeletefiltersql) to build the predicate for this engine (`:57`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:34`) and bails when it is `null` (`:58-59`), then walks the entity's indexes, skipping every non-unique one (`:63-64`). The predicate text is engine-specific: `[IsDeleted] = 0` on SQL Server, `"IsDeleted" = 0` on SQLite, `"IsDeleted" = false` on PostgreSQL, whose flag is a real boolean (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SQLServerDataSourceEngine.cs:117`, `SqliteDataSourceEngine.cs:115`, `PostgreSQLDataSourceEngine.cs:116`). + - For a unique index the convention branches three ways on the existing filter. No filter at all: set the soft-delete predicate (`SoftDeleteUniqueIndexConvention.cs:67-71`). A filter that already constrains the soft-delete column, whether a hand-authored literal or the output of `HasSoftDeleteFilter`: leave it exactly as it is, detected by `SoftDeleteFilterSql.ContainsPredicate` (`:73-76`, the helper at `SoftDeleteFilterSql.cs:52`). Anything else: **append** the clause as `{existingFilter} AND {filterSql}` (`:80`). + - That append is the load-bearing part, and the doc comment explains why it replaced the earlier "skip an index that already has a filter" behavior (`SoftDeleteUniqueIndexConvention.cs:18-27`). Skipping meant the exact partial-unique indexes a model bothered to hand-author (for example one narrowed on `[DedupKey] IS NOT NULL`) were the only ones a soft-deleted row could keep blocking. The `ContainsPredicate` check is what keeps appending idempotent: without it, a second model build would produce `... AND [IsDeleted] = 0 AND [IsDeleted] = 0`. +- **Why it's built this way**: the comment at `SoftDeleteUniqueIndexConvention.cs:55-56` names the reason the predicate comes from [`SoftDeleteFilterSql`](#softdeletefiltersql) rather than from a local string: it is the same builder the opt-in extension reaches, so the automatic path and the hand-authored path can never disagree about column name or identifier quoting, and the `AND` ordering at `:78-79` is chosen to match `HasSoftDeleteFilter(additionalFilter:)` byte for byte. Restricting the automatic behavior to unique indexes is a deliberate blast-radius choice: a unique index without the filter is a correctness bug under soft delete, while a non-unique index without it is only a performance question, and performance questions belong to whoever wrote the index. Guarding on the `IsRelational` capability rather than on `DataSource.CosmosDB` keeps this convention aligned with [`CrossDataSourceDegradeConvention`](#crossdatasourcedegradeconvention) and [`RestrictDeleteByDefaultConvention`](#restrictdeletebydefaultconvention) on which engines have indexes at all. The whole decision, including the 2026-08-26 revision from skipping to appending, is [ADR-095](https://ivanball.github.io/docs/adr/095-soft-delete-unique-indexes.html); soft delete as a policy is [ADR-005](https://ivanball.github.io/docs/adr/005-soft-delete-vs-erasure.html); covering SQL Server, PostgreSQL and SQLite while skipping Cosmos is the engine-portability posture of [ADR-018](https://ivanball.github.io/docs/adr/018-polyglot-persistence.html). +- **Where it's used**: registered by [`ApplicationDbContext`](#applicationdbcontext) in `ConfigureConventions` with the context's own engine (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:392`), immediately after [`CrossDataSourceDegradeConvention`](#crossdatasourcedegradeconvention) and before [`RestrictDeleteByDefaultConvention`](#restrictdeletebydefaultconvention). The comment there records the ordering intent and the precedence rule: it runs at finalization, after module configurations have declared their indexes, and hand-authored index filters are respected (`ApplicationDbContext.cs:389-391`). [`UserNotificationConfiguration`](#usernotificationconfiguration) relies on it outright for its unique index, declaring no filter of its own. Covered by `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Conventions/SoftDeleteUniqueIndexConventionTests.cs`. ### PushNotificationConfiguration -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration.Notifications` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/Notifications/PushNotificationConfiguration.cs:16` · Level 8 · class (internal sealed) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration.Notifications` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/Notifications/PushNotificationConfiguration.cs:16` · Level 14 · class (internal sealed) -- **What it is**: the EF Core mapping for [`PushNotification`](group-10-notifications.md#pushnotification), the framework's own broadcast-notification aggregate (`PushNotificationConfiguration.cs:16-72`). It is one of only two entity configurations that ship **inside the framework** rather than in a consumer application, and it is where the dedup guarantee behind a retried send is actually enforced. -- **Depends on**: [`EntityTypeConfigurationSQLServer`](#entitytypeconfigurationsqlservertentity-tidentifiertype) as its base (`PushNotificationConfiguration.cs:17`); [`PushNotification`](group-10-notifications.md#pushnotification) and its `DedupKeyMaxLength` / `ScopeKeyMaxLength` constants (`:3`, `:47`, `:53`, declared at `MMCA.Common/Source/Core/MMCA.Common.Domain/Notifications/PushNotifications/PushNotification.cs:19`, `:22`); [`PushNotificationInvariants`](group-10-notifications.md#pushnotificationinvariants) for the title and body lengths (`:4`, `:29`, `:33`); [`PushNotificationStatus`](group-10-notifications.md#pushnotificationstatus) indirectly through the string conversion (`:43`); [`UseDatabaseAttribute`](group-14-module-system-composition.md#usedatabaseattribute) (`:15`); the `HasSoftDeleteFilter` member from [`IndexBuilderExtensions`](#indexbuilderextensions) (`:70`); EF Core's `EntityTypeBuilder` (NuGet, `:1-2`). -- **Concept introduced, a framework-owned entity that has to name its own home.** Every other configuration in this workspace lets convention pick the schema and the logical database: [`EntityTypeConfiguration`](#entitytypeconfigurationtentity-tidentifiertype) maps a SQL Server entity to a table in a schema named by [`NamespaceConventions`](#namespaceconventions), which is the namespace segment immediately preceding `Domain` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:74`). That rule is right for a consumer entity in `MMCA.ADC.Conference.Domain.Sessions` (schema `Conference`) and wrong here, because this entity lives in `MMCA.Common.Domain.Notifications.PushNotifications`, whose preceding segment is `Common`. The configuration therefore states both facts explicitly: `[UseDatabase("Notification")]` (`PushNotificationConfiguration.cs:15`) fixes the logical database that [`EntityDataSourceRegistry`](#entitydatasourceregistry) records, and `ToTable(nameof(PushNotification), "Notification")` (`:25`) overrides the auto-derived schema after the base call. The class doc states this reasoning in place (`:8-14`), including the consequence that matters for a small host: a host with no `DataSources:Notification` entry keeps these tables in its default database. `[Rubric §8, Data Architecture]` assesses whether the physical layout is a decision rather than an accident; this is a convention override made visible in two attributes on one class. `[Rubric §29, Resilience, Reliability & Business Continuity]` assesses whether shared capabilities carry their own infrastructure; the notification feature ships its schema with the framework instead of asking each consumer to re-declare it. -- **Concept introduced, letting the database arbitrate a duplicate send.** A "have I already sent this?" check in a handler is a check-then-act race: two retries of the same request can both read "no row" before either writes. The filtered unique index on `DedupKey` (`PushNotificationConfiguration.cs:68-70`) removes the race by moving arbitration into the engine, and the filter is what makes it usable: `IS NOT NULL` keeps the many sends that carry no key from colliding with each other (SQL Server treats NULLs as equal in a unique index), and `IsDeleted = 0` keeps a soft-deleted notification from squatting on its key forever. The comment block records both halves and the defect that produced the second one (`:55-67`). `[Rubric §12, Performance & Scalability]` assesses whether index choices are reasoned; the file argues **for** one index and **against** another in the same class. `[Rubric §29, Resilience & Business Continuity]` assesses behavior under retry; at-least-once delivery upstream is only safe because this index makes a repeated send idempotent at the storage layer. +- **What it is**: the EF Core mapping for [`PushNotification`](group-10-notifications.md#pushnotification), the framework's own broadcast-notification aggregate (`PushNotificationConfiguration.cs:16-75`). It is one of only two entity configurations that ship **inside the framework** rather than in a consumer application, and it is where the dedup guarantee behind a retried send is actually enforced. +- **Depends on**: [`EntityTypeConfigurationSQLServer`](#entitytypeconfigurationsqlservertentity-tidentifiertype) as its base (`PushNotificationConfiguration.cs:17`), including the inherited `EffectiveEngine` property (`:72-73`, declared at `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:61`); [`PushNotification`](group-10-notifications.md#pushnotification) and its `DedupKeyMaxLength` / `ScopeKeyMaxLength` constants (`:3`, `:47`, `:53`, declared at `MMCA.Common/Source/Core/MMCA.Common.Domain/Notifications/PushNotifications/PushNotification.cs:19`, `:22`); [`PushNotificationInvariants`](group-10-notifications.md#pushnotificationinvariants) for the title and body lengths (`:4`, `:29`, `:33`); [`PushNotificationStatus`](group-10-notifications.md#pushnotificationstatus) indirectly through the string conversion (`:43`); [`UseDatabaseAttribute`](group-14-module-system-composition.md#usedatabaseattribute) (`:15`); the `HasSoftDeleteFilter` member from [`IndexBuilderExtensions`](#indexbuilderextensions) (`:71`); `QuoteColumn` from [`SoftDeleteFilterSql`](#softdeletefiltersql) (`:73`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:69-70`); EF Core's `EntityTypeBuilder` (NuGet, `:1-2`). +- **Concept introduced, a framework-owned entity that has to name its own home.** Every other configuration in this workspace lets convention pick the schema and the logical database: a SQL Server entity is mapped by its engine to a table in a schema named by [`NamespaceConventions`](#namespaceconventions), which is the namespace segment immediately preceding `Domain` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SQLServerDataSourceEngine.cs:101`). That rule is right for a consumer entity in `MMCA.ADC.Conference.Domain.Sessions` (schema `Conference`) and wrong here, because this entity lives in `MMCA.Common.Domain.Notifications.PushNotifications`, whose preceding segment is `Common`. The configuration therefore states both facts explicitly: `[UseDatabase("Notification")]` (`PushNotificationConfiguration.cs:15`) fixes the logical database that [`EntityDataSourceRegistry`](#entitydatasourceregistry) records, and `ToTable(nameof(PushNotification), "Notification")` (`:25`) overrides the auto-derived schema after the base call. The class doc states this reasoning in place (`:8-14`), including the consequence that matters for a small host: a host with no `DataSources:Notification` entry keeps these tables in its default database. `[Rubric §8, Data Architecture]` assesses whether the physical layout is a decision rather than an accident; this is a convention override made visible in two attributes on one class. `[Rubric §29, Resilience, Reliability & Business Continuity]` assesses whether shared capabilities carry their own infrastructure; the notification feature ships its schema with the framework instead of asking each consumer to re-declare it. +- **Concept introduced, letting the database arbitrate a duplicate send.** A "have I already sent this?" check in a handler is a check-then-act race: two retries of the same request can both read "no row" before either writes. The filtered unique index on `DedupKey` (`PushNotificationConfiguration.cs:69-73`) removes the race by moving arbitration into the engine, and the filter is what makes it usable: `IS NOT NULL` keeps the many sends that carry no key from colliding with each other (SQL Server treats NULLs as equal in a unique index), and `IsDeleted = 0` keeps a soft-deleted notification from squatting on its key forever. The comment block records both halves, the defect that produced the second one, and why the filter is written for the engine of the model being built (`:55-68`). `[Rubric §12, Performance & Scalability]` assesses whether index choices are reasoned; the file argues **for** one index and **against** another in the same class. `[Rubric §29, Resilience & Business Continuity]` assesses behavior under retry; at-least-once delivery upstream is only safe because this index makes a repeated send idempotent at the storage layer. - **Walkthrough** - - **`base.Configure(builder)`** (`PushNotificationConfiguration.cs:22`): runs the inherited chain first. [`EntityTypeConfigurationBase`](#entitytypeconfigurationbasetentity-tidentifiertype) ignores the aggregate's in-memory `DomainEvents` collection (`EntityTypeConfigurationBase.cs:29-32`), then [`EntityTypeConfiguration`](#entitytypeconfigurationtentity-tidentifiertype) reads the engine off the `[UseDataSource(DataSource.SQLServer)]` carried by the shim base (`EntityTypeConfigurationSQLServer.cs:16`) and applies the SQL Server conventions: table plus schema, `HasKey(p => p.Id)`, and `ValueGeneratedOnAdd()` because the entity carries [`IdValueGeneratedAttribute`](group-02-domain-building-blocks.md#idvaluegeneratedattribute) (`EntityTypeConfiguration.cs:45-50`, `:65-72`, `PushNotification.cs:15`). + - **`base.Configure(builder)`** (`PushNotificationConfiguration.cs:22`): runs the inherited chain first. [`EntityTypeConfigurationBase`](#entitytypeconfigurationbasetentity-tidentifiertype) ignores the aggregate's in-memory `DomainEvents` collection (`EntityTypeConfigurationBase.cs:29-32`), then [`EntityTypeConfiguration`](#entitytypeconfigurationtentity-tidentifiertype) reads the declared engine off the `[UseDataSource(DataSource.SQLServer)]` carried by the shim base (`EntityTypeConfiguration.cs:43-46`, `EntityTypeConfigurationSQLServer.cs:16`), records `EffectiveEngine` as the engine annotated on the model being built, falling back to the declared one (`EntityTypeConfiguration.cs:48-49`), and hands the declared engine to its engine's `ApplyKeyAndTableMapping` (`:51`, `:68-77`). For SQL Server that is table plus schema, `HasKey(p => p.Id)`, and `ValueGeneratedOnAdd()` because the entity carries [`IdValueGeneratedAttribute`](group-02-domain-building-blocks.md#idvaluegeneratedattribute) (`SQLServerDataSourceEngine.cs:94-104`, `PushNotification.cs:15`). - **The schema override** (`PushNotificationConfiguration.cs:25`): `ToTable(nameof(PushNotification), "Notification")`, applied **after** the base call so it replaces the derived `Common` schema rather than being replaced by it. Ordering is load-bearing here. - **Required scalars** (`:27-39`): `Title` required with `HasMaxLength(PushNotificationInvariants.TitleMaxLength)` (200, `MMCA.Common/Source/Core/MMCA.Common.Domain/Notifications/PushNotifications/Invariants/PushNotificationInvariants.cs:13`), `Body` required at `BodyMaxLength` (2000, `PushNotificationInvariants.cs:16`), `SentByUserId` and `RecipientCount` required. The column widths are the same constants the domain validates against (`PushNotificationInvariants.cs:18-26`), so the database cannot be narrower than the invariant. - **`Status` stored as text** (`:41-44`): `HasConversion()` with `HasMaxLength(20)`. The CLR type is the `PushNotificationStatus` enum with three members, `Pending`, `Sent`, `Failed` (`MMCA.Common/Source/Core/MMCA.Common.Domain/Notifications/PushNotifications/PushNotificationStatus.cs:6-16`); persisting the name rather than the ordinal means reordering or inserting an enum member does not silently re-interpret existing rows. - **`DedupKey`** (`:46-47`): nullable, bounded by `PushNotification.DedupKeyMaxLength` (128, `PushNotification.cs:19`). The domain records that this is typically the `Idempotency-Key` header value and that the filtered unique index is what arbitrates the race (`PushNotification.cs:39-45`). - **`ScopeKey`** (`:52-53`): nullable, bounded by `ScopeKeyMaxLength` (128, `PushNotification.cs:22`), and deliberately **not** indexed. The comment gives the economics (`:49-51`): the scope filter runs after the primary-key join from [`UserNotification`](group-10-notifications.md#usernotification), over a table holding one row per send, so an index would cost writes without buying a read. - - **The filtered unique index** (`:68-70`): `HasIndex(p => p.DedupKey).IsUnique().HasSoftDeleteFilter(additionalFilter: "[DedupKey] IS NOT NULL")`. The helper composes the final predicate as `{additionalFilter} AND {softDeletePredicate}` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/IndexBuilderExtensions.cs:62-65`), and the soft-delete half comes from [`SoftDeleteFilterSql`](#softdeletefiltersql), which reads the actual column name out of the model and quotes it per engine (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:32-36`). The `engine` parameter is not passed, so the default `DataSource.SQLServer` applies (`IndexBuilderExtensions.cs:53`), which matches this configuration's engine base class. The result is `[DedupKey] IS NOT NULL AND [IsDeleted] = 0`, asserted verbatim by `DedupKeyIndex_FiltersOutSoftDeletedRows` (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/PushNotificationConfigurationTests.cs:26-30`). - - **Why the opt-in call is belt and braces, not a requirement** (`:62-67`): [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) stamps the soft-delete predicate onto every unique index of a soft-deletable entity, and it now **extends** a hand-authored filter instead of skipping it: an index that already declares a predicate gets `{existingFilter} AND {filterSql}` in that exact order (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:65-79`), and an index whose filter already constrains the soft-delete column is left untouched so a second model build cannot append the clause twice (`:72-75`). Because `HasSoftDeleteFilter` produces the same SQL in the same order from the same column name, the convention recognizes this index and stops, which is what the configuration's own comment says (`PushNotificationConfiguration.cs:64-67`). -- **Why it's built this way**: the two overrides exist because a framework-owned domain type cannot be named by the convention that names consumer domain types, and stating the target explicitly is cheaper than special-casing the convention. The soft-delete clause on the dedup index is a fix, not an original design: it closes a defect where a soft-deleted notification held its dedup key permanently, and ADC's migration for it is an explicit expand-contract drop and recreate, since a filtered index predicate cannot be altered in place (`MMCA.ADC/Source/Hosting/MMCA.ADC.Migrations.SqlServer.Notification/Migrations/20260804185520_CommonV1141PushNotificationDedupIndexSoftDeleteFilter.cs:13-31`). The migration comment argues the safety case explicitly: the new predicate is strictly more permissive, so a previous revision running against the new index still succeeds. -- **Where it's used**: discovered by assembly scan rather than by a direct reference. `AddNotificationInfrastructure()` registers this class's assembly as an entity-configuration source (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:26-31`, naming the type only to get its `Assembly` at `:29`), and the only production caller is ADC's Notification module (`MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.API/DependencyInjection.cs:33`), whose service points the logical `Notification` source at the `ADC_Notification` database (`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/appsettings.Development.json:37-41`). Store hosts no notification entities today: the only other callers of `AddNotificationInfrastructure` are Common's own DI tests. Behavior is pinned against a real model built from this exact configuration (`PushNotificationConfigurationTests.cs:76-83`): index uniqueness (`:22-24`), the composed filter (`:26-30`), the scope-key length and nullability (`:35-41`), and the deliberate absence of a scope-key index (`:43-49`). -- **Caveats / not-in-source**: the test double builds the model on SQLite (`PushNotificationConfigurationTests.cs:69-71`) while the configuration targets SQL Server, so the asserted filter string is the one this class hand-authors, not one a SQL Server model build rewrote. - -### UserNotificationConfiguration - -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration.Notifications` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/Notifications/UserNotificationConfiguration.cs:15` · Level 8 · class (internal sealed) - -- **What it is**: the EF Core mapping for [`UserNotification`](group-10-notifications.md#usernotification), the per-user inbox row that pairs a recipient with a broadcast (`UserNotificationConfiguration.cs:15-47`). It is the sibling of [`PushNotificationConfiguration`](#pushnotificationconfiguration) and shares its shape exactly: `internal sealed`, `[UseDatabase("Notification")]`, [`EntityTypeConfigurationSQLServer`](#entitytypeconfigurationsqlservertentity-tidentifiertype) base, and a `ToTable` that overrides the derived `Common` schema. -- **Depends on**: [`EntityTypeConfigurationSQLServer`](#entitytypeconfigurationsqlservertentity-tidentifiertype) (`UserNotificationConfiguration.cs:16`); [`UserNotification`](group-10-notifications.md#usernotification) (`:3`, `:24`); [`UseDatabaseAttribute`](group-14-module-system-composition.md#usedatabaseattribute) (`:14`); EF Core's `EntityTypeBuilder` (NuGet, `:1-2`). -- **Concept reinforced**: the schema-and-database override taught at [`PushNotificationConfiguration`](#pushnotificationconfiguration), with the identical doc comment stating why (`UserNotificationConfiguration.cs:7-13`). What is worth studying here instead is the **index pair**, which shows the unique and the non-unique filtered case side by side. `[Rubric §12, Performance & Scalability]` assesses whether hot read paths are backed by an index that matches the query's predicate; the unread lookup below is a textbook covering-filter case. -- **Walkthrough** - - **Base call and schema override** (`UserNotificationConfiguration.cs:21`, `:24`): same order and same reason as its sibling. - - **Scalars** (`:26-36`): `UserId` and `PushNotificationId` required, `IsRead` required with `HasDefaultValue(false)` so an inserted row is unread at the database level too, and `ReadOn` mapped with no facets, staying nullable because the domain declares it `DateTime?` (`MMCA.Common/Source/Core/MMCA.Common.Domain/Notifications/UserNotifications/UserNotification.cs:24`). - - **Uniqueness per recipient** (`:38-41`): `HasIndex(p => new { p.UserId, p.PushNotificationId }).IsUnique().HasFilter("[IsDeleted] = 0")`, one inbox row per user per notification among live rows. Note the literal predicate: this index would have received the same filter automatically from [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) (`SoftDeleteUniqueIndexConvention.cs:60-68`), because it is unique and the entity is soft-deletable, so the hand-written string is belt and braces. The convention detects that the declared filter already constrains the soft-delete column and leaves it exactly as written (`:72-75`). - - **The unread lookup** (`:43-45`): `HasIndex(p => new { p.UserId, p.IsRead }).HasFilter("[IsDeleted] = 0")`, non-unique, serving the per-user unread query behind the notification badge. This is the case the convention deliberately skips, since it continues past any index that is not unique (`SoftDeleteUniqueIndexConvention.cs:62-63`), so the filter here **is** required to keep soft-deleted rows out of the index. It is written as a literal rather than through `HasSoftDeleteFilter()` from [`IndexBuilderExtensions`](#indexbuilderextensions), which is the helper that reads the column name from the model instead (`IndexBuilderExtensions.cs:52-66`); the produced SQL is identical for this model. - - **No relationship to `PushNotification`** (`:29-30`): `PushNotificationId` is configured as a plain required scalar, with no `HasOne` or `WithMany` anywhere in the file, and the domain entity exposes no navigation property either, only the identifier (`UserNotification.cs:18`). The inbox row references the broadcast by value. -- **Why it's built this way**: modeling the reference as a bare scalar keeps the entity honest about what the database enforces, and it is the shape the framework can move without rework. Both notification tables carry `[UseDatabase("Notification")]` today, so they land in one database and a declared relationship would be legal; the moment a host routed them apart, [`CrossDataSourceDegradeConvention`](#crossdatasourcedegradeconvention) would strip the EF relationship and the FK constraint anyway and leave exactly this scalar behind (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:9-21`). That is the same rule ADC applies to every cross-module reference under database-per-service ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)). -- **Where it's used**: registered by the same assembly scan as its sibling (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:26-31`) and reached in production only through ADC's Notification module (`MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.API/DependencyInjection.cs:33`), where the `UserNotification` table lands in the `Notification` schema of `ADC_Notification` (`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/appsettings.Development.json:37-41`). -- **Caveats / not-in-source**: unlike its sibling, this configuration has no dedicated unit test in the Common suite; its mapping is exercised indirectly through the ADC Notification migrations and integration tier. + - **The filtered unique index** (`:69-73`): `HasIndex(p => p.DedupKey).IsUnique().HasSoftDeleteFilter(EffectiveEngine, additionalFilter: ...)`, where the additional filter is the `DedupKey` column quoted for `EffectiveEngine` by `SoftDeleteFilterSql.QuoteColumn` followed by `IS NOT NULL` (`:73`). The helper composes the final predicate as `{additionalFilter} AND {softDeletePredicate}` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/IndexBuilderExtensions.cs:62-65`), and the soft-delete half comes from [`SoftDeleteFilterSql`](#softdeletefiltersql), which reads the actual column name out of the model and builds the predicate for the given engine (`SoftDeleteFilterSql.cs:34`). The comment at `:58-61` gives the reason the engine is passed explicitly rather than left to the helper's `DataSource.SQLServer` default (`IndexBuilderExtensions.cs:53`): a host that configures only PostgreSQL or SQLite substitutes its engine and applies this SQL Server configuration to that model, and the Cosmos context strips relational indexes. On a SQL Server model the result is `[DedupKey] IS NOT NULL AND [IsDeleted] = 0`, asserted verbatim by `DedupKeyIndex_FiltersOutSoftDeletedRows` (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/PushNotificationConfigurationTests.cs:26-30`) and by `OnASqlServerHost_NotificationIndexFilters_KeepTheBracketedForm` (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/NotificationConfigurationEngineTests.cs:68`); on a PostgreSQL-only host it is `"DedupKey" IS NOT NULL AND "IsDeleted" = false` (`NotificationConfigurationEngineTests.cs:46`). + - **Why the opt-in call is belt and braces, not a requirement** (`:63-68`): [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) stamps the soft-delete predicate onto every unique index of a soft-deletable entity, and it **extends** a hand-authored filter instead of skipping it: an index that already declares a predicate gets `{existingFilter} AND {filterSql}` in that exact order (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:67-80`), and an index whose filter already constrains the soft-delete column is left untouched so a second model build cannot append the clause twice (`:73-76`). Because `HasSoftDeleteFilter` produces the same SQL in the same order from the same column name, the convention recognizes this index and stops, which is what the configuration's own comment says (`PushNotificationConfiguration.cs:66-68`). +- **Why it's built this way**: the two overrides exist because a framework-owned domain type cannot be named by the convention that names consumer domain types, and stating the target explicitly is cheaper than special-casing the convention. Writing the filter for `EffectiveEngine` rather than as a bracketed literal is what lets one SQL Server-declared configuration serve a PostgreSQL or SQLite host: the test that pins it states the failure it prevents, a bracketed SQL Server literal being a syntax error on Npgsql (`NotificationConfigurationEngineTests.cs:14-17`). The soft-delete clause on the dedup index is a fix, not an original design: it closes a defect where a soft-deleted notification held its dedup key permanently, and ADC's migration for it is an explicit expand-contract drop and recreate, since a filtered index predicate cannot be altered in place (`MMCA.ADC/Source/Hosting/MMCA.ADC.Migrations.SqlServer.Notification/Migrations/20260804185520_CommonV1141PushNotificationDedupIndexSoftDeleteFilter.cs:13-31`). The migration comment argues the safety case explicitly: the new predicate is strictly more permissive, so a previous revision running against the new index still succeeds. +- **Where it's used**: discovered by assembly scan rather than by a direct reference. `AddNotificationInfrastructure()` registers this class's assembly as an entity-configuration source (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:26-31`, naming the type only to get its `Assembly` at `:29`), and the only production caller is ADC's Notification module (`MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.API/DependencyInjection.cs:33`), whose service points the logical `Notification` source at the `ADC_Notification` database (`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/appsettings.Development.json:37-41`). Store hosts no notification entities today: the only other callers of `AddNotificationInfrastructure` are Common's own DI tests. Behavior is pinned against a real model built from this exact configuration (`PushNotificationConfigurationTests.cs:76-82`): index uniqueness (`:22-24`), the composed filter (`:26-30`), the scope-key length and nullability (`:35-41`), and the deliberate absence of a scope-key index (`:43-49`). The engine substitution is pinned separately by `NotificationConfigurationEngineTests`, which builds the full context over a PostgreSQL-only and a SQL Server `Notification` source (`NotificationConfigurationEngineTests.cs:27-47`, `:49-69`). +- **Caveats / not-in-source**: the `PushNotificationConfigurationTests` double builds the model on SQLite (`PushNotificationConfigurationTests.cs:69-71`) through a plain `DbContext` that carries no engine annotation, so `EffectiveEngine` falls back to the declared SQL Server engine (`EntityTypeConfiguration.cs:48-49`) and the asserted filter is the bracketed SQL Server form; the engine-substituted output is only asserted by `NotificationConfigurationEngineTests`. ### RepositoryFactory @@ -7315,7 +8005,7 @@ survives a module being pulled out into its own service. - **Concept introduced, reflective activation traded for a cached compiled factory.** `ActivatorUtilities.CreateInstance` is the usual way to build a type whose constructor mixes DI-resolved services with caller-supplied arguments, and it is what this class used to call. Its cost is per call: it matches the constructor by reflection every time and caches nothing, so a request touching four aggregates paid four reflective activations. `ActivatorUtilities.CreateFactory` does the matching **once** and returns an `ObjectFactory`, a compiled delegate that can be invoked repeatedly. The insight that makes caching safe is stated in the code (`RepositoryFactory.cs:74-79`): each **closed** repository type here always takes the same argument shape, so the delegate can be keyed by type alone with no risk of a shape mismatch. `[Rubric §12, Performance & Scalability]` assesses whether hot paths avoid repeated per-call work; repository creation is on every command and query, so a per-type one-time cost replaces a per-call one. `[Rubric §15, Best Practices & Code Quality]` assesses whether an optimization is justified in place rather than left as folklore; the doc comment names the exact scenario it fixes. - **Concept reinforced, decoration decided by configuration.** The decorator pattern itself is taught in the CQRS pipeline chapter. Here it appears in its simplest form and with a switch: `ApplicationSettings.UseMiniProfiler` (`MMCA.Common/Source/Core/MMCA.Common.Application/Settings/ApplicationSettings.cs:14`) decides whether the plain repository is returned or is passed as the inner instance of a timing decorator. When the flag is off there is no wrapper object and no indirection at all, so profiling costs nothing in a production host that leaves it off. `[Rubric §13, Observability & Operability]` assesses whether the system can be inspected without being rebuilt; per-repository timing is a configuration flip. - **Walkthrough** - - **Primary constructor** (`RepositoryFactory.cs:15-18`): captures the provider into `_serviceProvider` and, importantly, resolves `IOptions.Value` once into `_applicationSettings` rather than on every call. Nothing else is resolved at construction, so the class stays cheap to create per scope (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:123` registers it scoped). + - **Primary constructor** (`RepositoryFactory.cs:15-18`): captures the provider into `_serviceProvider` and, importantly, resolves `IOptions.Value` once into `_applicationSettings` rather than on every call. Nothing else is resolved at construction, so the class stays cheap to create per scope (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:129` registers it scoped). - **`Create(DbContext dbContext)`** (`:26-42`): builds `EFRepository` through `Factory(..., DbContextArg)(_serviceProvider, [dbContext])` (`:31-32`). Only the context is passed positionally; the repository's two remaining constructor parameters are optional, `TimeProvider?` and `ICurrentUserService?` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:23-27`), and come from the provider, with the class doc recording the fallback when they are absent: the system clock and no user stamp (`EFRepository.cs:17-22`). When `UseMiniProfiler` is true (`:34`) the instance is re-wrapped by activating `EFRepositoryDecorator` with an argument shape of `[typeof(IRepository)]` (`:36-38`), matching the decorator's single `inner` parameter (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepositoryDecorator.cs:14`). - **`CreateReadOnly(DbContext dbContext)`** (`:50-66`): the identical sequence against `EFReadRepository` and `EFReadRepositoryDecorator`, with the looser [`AuditableBaseEntity`](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype) constraint. - **`DbContextArg`** (`:68`): a single static `Type[]` holding `typeof(DbContext)`, shared by both creation paths so the common case allocates no argument-type array per call. @@ -7325,6 +8015,23 @@ survives a module being pulled out into its own service. - **Where it's used**: injected into [`UnitOfWork`](#unitofwork) (`UnitOfWork.cs:13`, `:16`) and called from `GetRepository` (`:42`) and `GetReadRepository` (`:62`). Because the unit of work caches one repository per entity type per scope (`:38-44`), the factory typically runs once per entity type per request, and the compiled delegate is reused for every later request in the process. All four composition outcomes are pinned directly against a real SQLite context: plain repository with the flag off, decorated with it on, and the same pair for the read side (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Repositories/RepositoryFactoryTests.cs:45-95`), plus two tests asserting the built instances are functional repositories (`:95-115`). Those tests construct the factory directly with `Options.Create(new ApplicationSettings { UseMiniProfiler = false })` (`:98`, `:100`), which is the shape the primary constructor takes. - **Caveats / not-in-source**: the cache is keyed by implementation type only, which is correct exactly as long as every call site for a given closed type passes the same argument shape. Both current call sites do, and the doc comment states that assumption (`RepositoryFactory.cs:74-79`), but nothing in the code enforces it: a future overload passing a different `argumentTypes` array for an already-cached type would silently reuse the first delegate. +### UserNotificationConfiguration + +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration.Notifications` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/Notifications/UserNotificationConfiguration.cs:15` · Level 14 · class (internal sealed) + +- **What it is**: the EF Core mapping for [`UserNotification`](group-10-notifications.md#usernotification), the per-user inbox row that pairs a recipient with a broadcast (`UserNotificationConfiguration.cs:15-47`). It is the sibling of [`PushNotificationConfiguration`](#pushnotificationconfiguration) and shares its shape exactly: `internal sealed`, `[UseDatabase("Notification")]`, [`EntityTypeConfigurationSQLServer`](#entitytypeconfigurationsqlservertentity-tidentifiertype) base, and a `ToTable` that overrides the derived `Common` schema. +- **Depends on**: [`EntityTypeConfigurationSQLServer`](#entitytypeconfigurationsqlservertentity-tidentifiertype) (`UserNotificationConfiguration.cs:16`), including the inherited `EffectiveEngine` property (`:45`, declared at `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:61`); [`UserNotification`](group-10-notifications.md#usernotification) (`:3`, `:24`); [`UseDatabaseAttribute`](group-14-module-system-composition.md#usedatabaseattribute) (`:14`); the `HasSoftDeleteFilter` member from [`IndexBuilderExtensions`](#indexbuilderextensions) (`:45`); [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention), which supplies the filter on its unique index (`:38-41`); EF Core's `EntityTypeBuilder` (NuGet, `:1-2`). +- **Concept reinforced**: the schema-and-database override taught at [`PushNotificationConfiguration`](#pushnotificationconfiguration), with the identical doc comment stating why (`UserNotificationConfiguration.cs:7-13`). What is worth studying here instead is the **index pair**, which shows the two routes a soft-delete filter takes onto an index side by side: the convention for the unique one, the opt-in helper for the non-unique one, neither written as an engine-specific literal. `[Rubric §12, Performance & Scalability]` assesses whether hot read paths are backed by an index that matches the query's predicate; the unread lookup below is a textbook covering-filter case. +- **Walkthrough** + - **Base call and schema override** (`UserNotificationConfiguration.cs:21`, `:24`): same order and same reason as its sibling. + - **Scalars** (`:26-36`): `UserId` and `PushNotificationId` required, `IsRead` required with `HasDefaultValue(false)` so an inserted row is unread at the database level too, and `ReadOn` mapped with no facets, staying nullable because the domain declares it `DateTime?` (`MMCA.Common/Source/Core/MMCA.Common.Domain/Notifications/UserNotifications/UserNotification.cs:24`). + - **Uniqueness per recipient** (`:38-41`): `HasIndex(p => new { p.UserId, p.PushNotificationId }).IsUnique()`, one inbox row per user per notification among live rows, with no filter declared here. The comment states where the predicate comes from (`:38-39`): [`SoftDeleteUniqueIndexConvention`](#softdeleteuniqueindexconvention) sets the soft-delete filter on a unique index of a soft-deletable entity that has none (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:67-71`), built for the context's own engine (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:392`), so the SQL comes out in the syntax of whichever engine the model is built for. + - **The unread lookup** (`:43-45`): `HasIndex(p => new { p.UserId, p.IsRead }).HasSoftDeleteFilter(EffectiveEngine)`, non-unique, serving the per-user unread query behind the notification badge. This is the case the convention deliberately skips, since it continues past any index that is not unique (`SoftDeleteUniqueIndexConvention.cs:63-64`), so the explicit call **is** required to keep soft-deleted rows out of the index. Passing `EffectiveEngine` rather than relying on the helper's `DataSource.SQLServer` default (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/IndexBuilderExtensions.cs:53`) is what makes the filter `[IsDeleted] = 0` on a SQL Server model and `"IsDeleted" = false` on a PostgreSQL-only host (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/NotificationConfigurationEngineTests.cs:67`, `:45`). + - **No relationship to `PushNotification`** (`:29-30`): `PushNotificationId` is configured as a plain required scalar, with no `HasOne` or `WithMany` anywhere in the file, and the domain entity exposes no navigation property either, only the identifier (`UserNotification.cs:18`). The inbox row references the broadcast by value. +- **Why it's built this way**: modeling the reference as a bare scalar keeps the entity honest about what the database enforces, and it is the shape the framework can move without rework. Both notification tables carry `[UseDatabase("Notification")]` today, so they land in one database and a declared relationship would be legal; the moment a host routed them apart, [`CrossDataSourceDegradeConvention`](#crossdatasourcedegradeconvention) would strip the EF relationship and the FK constraint anyway and leave exactly this scalar behind (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:10-22`). That is the same rule ADC applies to every cross-module reference under database-per-service ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)). Leaving both index filters to engine-aware code rather than a bracketed literal is what lets this SQL Server-declared configuration be applied to a PostgreSQL or SQLite model, where the bracketed form is a syntax error on Npgsql (`NotificationConfigurationEngineTests.cs:14-17`). +- **Where it's used**: registered by the same assembly scan as its sibling (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:26-31`) and reached in production only through ADC's Notification module (`MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.API/DependencyInjection.cs:33`), where the `UserNotification` table lands in the `Notification` schema of `ADC_Notification` (`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/appsettings.Development.json:37-41`). Its index filters are pinned by `NotificationConfigurationEngineTests`, which loads this configuration's assembly into a full context over a PostgreSQL-only and a SQL Server `Notification` source (`NotificationConfigurationEngineTests.cs:42`, `:64`) and asserts both indexes carry the engine's soft-delete predicate (`:45`, `:67`, exactly two indexes at `:73-74`). +- **Caveats / not-in-source**: that test pins only the two index filters; the scalar mapping (required columns, the `IsRead` default) has no dedicated assertion in the Common suite and is exercised indirectly through the ADC Notification migrations and integration tier. + --- [⬅ Validation](group-06-validation.md) • [Index](00-index.md) • [Authentication & Authorization ➡](group-08-auth.md) diff --git a/docs-src/onboarding/group-08-auth.md b/docs-src/onboarding/group-08-auth.md index c4d02b78..15fb4ea1 100644 --- a/docs-src/onboarding/group-08-auth.md +++ b/docs-src/onboarding/group-08-auth.md @@ -13,13 +13,16 @@ register / refresh workflow** ([`AuthenticationServiceBase`](#authenticat [`AuthenticationValidators`](#authenticationvalidators)); **multi-device refresh sessions** ([`RefreshSession`](#refreshsession), [`IRefreshSessionStore`](#irefreshsessionstore), [`RefreshSessionSettings`](#refreshsessionsettings), -[`RefreshSessionSummaryResponse`](#refreshsessionsummaryresponse), and the two workflow-private -helpers [`IssuedSession`](#issuedsession) and +[`RefreshSessionSummaryResponse`](#refreshsessionsummaryresponse), and the issuer that owns every +session decision, [`AuthSessionIssuer`](#authsessionissuer) / +[`IAuthSessionIssuer`](#iauthsessionissuer), with its two private helpers +[`IssuedSession`](#issuedsession) and [`SessionStampingTokenService`](#sessionstampingtokenservice)); **the contracts an app's `User` aggregate exposes to those shared workflows** ([`IAuthUser`](#iauthuser), [`IPasswordChangeableUser`](#ipasswordchangeableuser), [`IUserPreferences`](#iuserpreferences), [`IErasableUser`](#ierasableuser)); **password material** -([`PasswordHasher`](#passwordhasher) / [`IPasswordHasher`](#ipasswordhasher)); **brute-force and +([`PasswordHasher`](#passwordhasher) / [`IPasswordHasher`](#ipasswordhasher), and the one +complexity rule both server and client read, [`PasswordComplexity`](#passwordcomplexity)); **brute-force and rate-limit protection** ([`LoginProtectionService`](#loginprotectionservice) / [`ILoginProtectionService`](#iloginprotectionservice), [`LoginProtectionSettings`](#loginprotectionsettings)); **the forgot-password token lifecycle** @@ -95,38 +98,38 @@ The framework mints two credentials on every successful sign-in: a short-lived * JWT, 15 minutes by default, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwtSettings.cs:61`) and an opaque, random **refresh token** (64 bytes of `RandomNumberGenerator` output, Base64-encoded, valid 7 days by -default, `TokenService.cs:144-147`, `JwtSettings.cs:64`), both produced by +default, `TokenService.cs:155-158`, `JwtSettings.cs:64`), both produced by [`TokenService`](#tokenservice) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:26`). The access token carries a fixed claim spine: `sub`, `jti`, `iat`, plus name, email, and role -(`TokenService.cs:102-111`), and the app adds its own claims (for example `speaker_id` or -`customer_id`) through the `additionalClaims` parameter (`TokenService.cs:113-116`). `sub` is the +(`TokenService.cs:113-122`), and the app adds its own claims (for example `speaker_id` or +`customer_id`) through the `additionalClaims` parameter (`TokenService.cs:124-127`). `sub` is the single carrier of the user identifier: the duplicate custom claim that used to ride alongside it is gone, because two values that can disagree is two claim names every reader has to know -(`TokenService.cs:99-101`). The port [`ITokenService`](#itokenservice) +(`TokenService.cs:110-112`). The port [`ITokenService`](#itokenservice) (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Auth/ITokenService.cs:8`) publishes both lifetimes as default interface members pinned to the same 15-minute / 7-day baseline (`ITokenService.cs:33`, `ITokenService.cs:40`), so a hand-written test double reports the same expiry the production settings would, while the concrete service derives them from the bound settings -(`TokenService.cs:151`, `TokenService.cs:154`). +(`TokenService.cs:162`, `TokenService.cs:165`). The load-bearing design choice is a single configuration switch, [`JwtSettings`](#jwtsettings)`.SigningAlgorithm` -(`TokenService.cs:76`). It defaults to +(`TokenService.cs:87`). It defaults to [`JwtSigningAlgorithm`](#jwtsigningalgorithm)`.RS256` (`JwtSettings.cs:30`): the Identity service signs with an RSA private key and every other service validates against the matching public key, which it fetches over JWKS. A single-host monolith opts into `HS256` explicitly, where one symmetric Base64 secret both signs and validates because issuer -and validator are the same process (`TokenService.cs:76-87`, `TokenService.cs:206-218`). Asymmetric +and validator are the same process (`TokenService.cs:87-98`, `TokenService.cs:226-238`). Asymmetric is the default precisely because it is the shape that survives extraction: a compromised non-Identity service can verify tokens but cannot forge them. An issuer with no explicit public key configured derives one from its own private-key parameters so it can still self-validate during refresh -(`TokenService.cs:242-256`), and the key id from +(`TokenService.cs:262-276`), and the key id from [`JwksSettings`](#jwkssettings) travels into every RS256 token's `kid` header so a validator reading the published document selects the right key by name rather than -trying each in turn (`TokenService.cs:235-239`). Key material is materialized once in the constructor -and the owned `RSA` handles are disposed with the service (`TokenService.cs:37-38`, -`TokenService.cs:200-204`), so token operations never re-parse a PEM. The settings class enforces the +trying each in turn (`TokenService.cs:255-259`). Key material is materialized once in the constructor +and the owned `RSA` handles are disposed with the service (`TokenService.cs:39-40`, +`TokenService.cs:220-224`), so token operations never re-parse a PEM. The settings class enforces the pairing rather than trusting the host: it implements `IValidatableObject` and rejects an HS256 secret shorter than 32 characters or an RS256 configuration with no private key (`JwtSettings.cs:70-85`). @@ -154,60 +157,85 @@ pre-fetches that document as a startup warm-up task (`MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Warmup/OpenIdConnectMetadataWarmupTask.cs:21`) so the first authenticated request on a cold replica does not pay the discovery round trip. Validation pins the expected algorithm so an attacker cannot force an algorithm swap: `GetPrincipalFromExpiredToken` -sets `ValidAlgorithms` to the single configured value (`TokenService.cs:176`) and then re-checks the -token header after `ValidateToken` returns (`TokenService.cs:185-189`). Only the lifetime check is -skipped there (`TokenService.cs:171`), because the method exists to read claims out of an +sets `ValidAlgorithms` to the single configured value (`TokenService.cs:187`) and then re-checks the +token header after `ValidateToken` returns (`TokenService.cs:196-200`). Only the lifetime check is +skipped there (`TokenService.cs:182`), because the method exists to read claims out of an already-expired token during refresh. ## The shared authentication workflow Login, registration, refresh, revocation, and device listing are not re-implemented per app. They live once in [`AuthenticationServiceBase`](#authenticationservicebasetuser) -(`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:74`), an abstract +(`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:63`), an abstract base each app's Identity module seals over its concrete `User` aggregate. The base owns the sequence; the sealed subclass supplies the genuinely app-specific pieces through abstract and virtual hooks: `FindUntrackedByEmailAsync` and `EmailExistsAsync` (written against the concrete `User` so EF -translation is unchanged, `AuthenticationServiceBase.cs:585`, `AuthenticationServiceBase.cs:591`), -`CreateUser` (`AuthenticationServiceBase.cs:594`), `CreateAccessToken` -(`AuthenticationServiceBase.cs:597`), the two optional candidate gates -(`AuthenticationServiceBase.cs:691-696`), the post-commit `OnUserRegisteredAsync` -(`AuthenticationServiceBase.cs:702`), and the overridable "refresh user vanished" error -(`AuthenticationServiceBase.cs:710`, 401 by default because a token for a deleted user is -indistinguishable from an invalid one). Both token lifetimes are read from -[`ITokenService`](#itokenservice) with a defensive fallback to the 15-minute / 7-day baseline for a -misconfigured host or a test double (`AuthenticationServiceBase.cs:137-146`). - -`LoginAsync` (`AuthenticationServiceBase.cs:156`) shows the shape. It validates the request first, +translation is unchanged, `AuthenticationServiceBase.cs:438`, `AuthenticationServiceBase.cs:444`), +`CreateUser` (`AuthenticationServiceBase.cs:447`), `CreateAccessToken` +(`AuthenticationServiceBase.cs:450`), the two optional candidate gates +(`AuthenticationServiceBase.cs:555-560`), the post-commit `OnUserRegisteredAsync` +(`AuthenticationServiceBase.cs:566`), and the overridable "refresh user vanished" error +(`AuthenticationServiceBase.cs:574`, 401 by default because a token for a deleted user is +indistinguishable from an invalid one). What the base does *not* own is the session: it decides who is +signed in and hands everything about what they are given to +[`IAuthSessionIssuer`](#iauthsessionissuer), a constructor dependency +(`AuthenticationServiceBase.cs:40-43`, `AuthenticationServiceBase.cs:68`), so the workflow never +touches a session row. Even the token service a subclass mints through is the issuer's +(`AuthenticationServiceBase.cs:98`), which is how the `sid` and `mfa` claims reach an app's token +without the app's claim code knowing they exist (see the refresh-session section below). + +`LoginAsync` (`AuthenticationServiceBase.cs:105`) shows the shape. It validates the request first, then runs the [ADR-029](https://ivanball.github.io/docs/adr/029-authentication-brute-force-protection.html) -lockout check (`AuthenticationServiceBase.cs:168`), then does the **dual-fetch**: an untracked, -no-change-tracking query to verify the password cheaply (`AuthenticationServiceBase.cs:179`, -`AuthenticationServiceBase.cs:210`), and only on success a second *tracked* re-fetch of the instance +lockout check (`AuthenticationServiceBase.cs:117-122`), then does the **dual-fetch**: an untracked, +no-change-tracking query to verify the password cheaply (`AuthenticationServiceBase.cs:130`, +`AuthenticationServiceBase.cs:143`), and only on success a second *tracked* re-fetch of the instance the app's `CreateAccessToken` hook mints from, which is also what turns a race that deleted the -account between the two steps into a clean 404 (`AuthenticationServiceBase.cs:228-237`). The email is +account between the two steps into a clean 404 (`AuthenticationServiceBase.cs:177-186`). The email is normalized through the [`Email`](group-02-domain-building-blocks.md#email) value object before the query so the EF predicate compares same-typed converted values -(`AuthenticationServiceBase.cs:176`). Soft-deleted accounts fall out through EF global query filters -and return the same generic 401 as a wrong password (`AuthenticationServiceBase.cs:180-192`), so the -API never reveals whether an email exists, and a successful login clears the attempt counters -(`AuthenticationServiceBase.cs:240`) before handing off to the shared token-issue path -(`AuthenticationServiceBase.cs:245`). - -`RegisterAsync` (`AuthenticationServiceBase.cs:254`) rate-limits by source IP, rejects a duplicate +(`AuthenticationServiceBase.cs:126`). Soft-deleted accounts fall out through EF global query filters +and return the same generic 401 as a wrong password (`AuthenticationServiceBase.cs:128-148`), and so +does an account with no stored credential at all (the external-OAuth shape), which also pays one +throwaway key derivation so its 401 does not come back measurably faster than a real check +(`AuthenticationServiceBase.cs:132-141`, `AuthenticationServiceBase.cs:589-599`): the API never +reveals whether an email exists. The app's candidate gate, the email-confirmation gate and the +second-factor challenge all run *after* the password check (`AuthenticationServiceBase.cs:150-175`), +and a successful login clears the attempt counters (`AuthenticationServiceBase.cs:189`) before +handing off to the shared token-issue path (`AuthenticationServiceBase.cs:194`). + +`RegisterAsync` (`AuthenticationServiceBase.cs:203`) rate-limits by source IP, rejects a duplicate email as a conflict, hashes the password, saves, and only then runs the app's post-commit hook, counts -the registration, and opens the session (`AuthenticationServiceBase.cs:266-330`). The up-front email +the registration, and opens the session (`AuthenticationServiceBase.cs:215-279`). The up-front email check is a check-then-act, so two concurrent registrations for the same address both pass it and the loser only fails on the insert. The save is therefore wrapped in a deliberately broad catch that re-checks the address and, if it now exists, returns the *same* conflict the serialized path would -have produced, rethrowing anything else (`AuthenticationServiceBase.cs:291-319`); the shared failure -factory keeps the two paths indistinguishable to the caller (`AuthenticationServiceBase.cs:915`). The +have produced, rethrowing anything else (`AuthenticationServiceBase.cs:240-268`); the shared failure +factory keeps the two paths indistinguishable to the caller (`AuthenticationServiceBase.cs:606`). The catch is broad because the Application layer has no EF Core dependency by layer rule and cannot name `DbUpdateException`; the re-check is what narrows it, and it deliberately runs on `CancellationToken.None` so a cancelled save can still be classified -(`AuthenticationServiceBase.cs:313`). `RefreshTokenAsync` (`AuthenticationServiceBase.cs:334`) -extracts claims from the *expired* access token (signature still verified, only lifetime skipped, -`AuthenticationServiceBase.cs:347`), reads the identifier off `sub` through -[`ClaimsPrincipalExtensions`](#claimsprincipalextensions) (`AuthenticationServiceBase.cs:358`), and -then resolves the presented refresh token to its session row. Every failure path returns a +(`AuthenticationServiceBase.cs:262`). The password rule a registration is validated against is +defined exactly once, in [`PasswordComplexity`](#passwordcomplexity) +(`MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/PasswordComplexity.cs:18`): 8 to 128 UTF-16 code +units (`PasswordComplexity.cs:21`, `PasswordComplexity.cs:24`) with an uppercase letter, a lowercase +letter, a digit and a special character, each a Unicode-aware generated regex with a one-second match +timeout, so an accented or CJK letter counts as a letter and never as the special character +(`PasswordComplexity.cs:12-16`, `PasswordComplexity.cs:27-43`), combined in `Evaluate` +(`PasswordComplexity.cs:52-57`). It lives in Shared because both sides read it: the server's +`StrongPasswordRules` matches the same four patterns and the same ceiling +(`MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:197-201`), and +the client form attribute calls `Evaluate` +(`MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/PasswordComplexityAttribute.cs:24`), so the +two cannot give different verdicts for one input, which is the validation-parity point of rubric §24 +the type's own remarks cite (`PasswordComplexity.cs:5-10`). + +`RefreshTokenAsync` (`AuthenticationServiceBase.cs:283`) extracts claims from the *expired* access +token (signature still verified, only lifetime skipped, `AuthenticationServiceBase.cs:295-297`), reads +the identifier off `sub` through [`ClaimsPrincipalExtensions`](#claimsprincipalextensions) +(`AuthenticationServiceBase.cs:307`), carries the `mfa` claim the user already earned across the +rotation so a signed-in session is not quietly demoted every fifteen minutes +(`AuthenticationServiceBase.cs:327-332`), and then hands the presented refresh token to the issuer's +`RotateAsync` (`AuthenticationServiceBase.cs:337-343`). Every failure path returns a [`Result`](group-01-result-error-handling.md#result) rather than throwing, matching the framework-wide Result pattern (see [primer §2](00-primer.md#2-architectural-styles-this-codebase-commits-to)). @@ -245,32 +273,33 @@ A refresh token is no longer a column on the user row. Every issue opens its own [`RefreshSession`](#refreshsession) (`MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:31`), so signing in on a phone leaves a laptop signed in, and the store holds only the token's digest: `Create` hashes on the way in -so the plaintext never reaches a property (`RefreshSession.cs:112-145`), and `HashToken` is an +so the plaintext never reaches a property (`RefreshSession.cs:125-158`), and `HashToken` is an unsalted, deterministic SHA-256 rendered as 64 upper-case hex characters precisely because lookups are -*by* hash (`RefreshSession.cs:160-164`, width constant at `RefreshSession.cs:34`). The encoding is +*by* hash (`RefreshSession.cs:173-177`, width constant at `RefreshSession.cs:42`). The encoding is part of the contract, not an implementation detail: the type's own remarks give the byte-for-byte SQL -Server equivalent a consumer's data migration has to reproduce (`RefreshSession.cs:151-157`). The row +Server equivalent a consumer's data migration has to reproduce (`RefreshSession.cs:164-170`). The row is deliberately *not* an aggregate: no audit stamps, no soft-delete flag, no concurrency token, like `OutboxMessage` and `AuditTrailEntry`, because rows are only ever inserted or revoked and no global -query filter may hide a revoked row from the reuse check (`RefreshSession.cs:22-29`). `Revoke` is +query filter may hide a revoked row from the reuse check (`RefreshSession.cs:24-31`). `Revoke` is idempotent by refusal, so the first reason and instant recorded are the ones kept -(`RefreshSession.cs:174-189`), and the four reason constants (`Rotated`, `SignedOut`, `ReuseDetected`, -`SessionCapExceeded`, `RefreshSession.cs:46-55`) are what an operator reads afterwards. +(`RefreshSession.cs:187-202`), and the four reason constants (`Rotated`, `SignedOut`, `ReuseDetected`, +`SessionCapExceeded`, `RefreshSession.cs:54-63`) are what an operator reads afterwards. Rotation leaves a chain, and the chain is the security mechanism. Using a session revokes it and -records the successor in `ReplacedByTokenHash` (`RefreshSession.cs:79`), so presenting an +records the successor in `ReplacedByTokenHash` (`RefreshSession.cs:87`), so presenting an already-rotated token lands on a *revoked* row rather than on nothing: that is the [ADR-050](https://ivanball.github.io/docs/adr/050-jwt-refresh-token-rotation.html) reuse signal, and -the workflow answers it by revoking every live session the user holds -(`AuthenticationServiceBase.cs:742-749`, `AuthenticationServiceBase.cs:844-854`). The three +[`AuthSessionIssuer`](#authsessionissuer) answers it by revoking every live session the user holds +(`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:293-300`, +`AuthSessionIssuer.cs:378-388`). The three rejections behind the single generic error are deliberately different in what they *do* -(`AuthenticationServiceBase.cs:714-754`): an unknown hash, or one belonging to another account, is +(`AuthSessionIssuer.cs:263-305`): an unknown hash, or one belonging to another account, is failed alone, since revoking the family on it would let anyone holding one of this user's expired access tokens sign them out everywhere by posting a random string; a revoked row revokes the family; an expired row is an ordinary end of life, so that device re-authenticates while the others keep working. Two requests presenting the same still-live token are covered by the same rule: rotation is claimed atomically through [`IRefreshSessionStore`](#irefreshsessionstore)`.TryRotateAsync` -(`AuthenticationServiceBase.cs:824-838`), and the request that loses the claim is answered exactly +(`AuthSessionIssuer.cs:340-388`), and the request that loses the claim is answered exactly like a replay because a caller cannot tell the two apart. [`IRefreshSessionStore`](#irefreshsessionstore) @@ -289,35 +318,40 @@ conditional `ExecuteUpdateAsync` the database arbitrates (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/EFRefreshSessionStore.cs:108-133`). That is the [Rubric §8, Data Architecture] half of the story, and the store is registered scoped alongside the unit of work it shares a `DbContext` with, so a login and its session insert commit -together (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:160-166`). - -The workflow around the port is small and worth reading end to end. -`IssueTokensAsync` (`AuthenticationServiceBase.cs:554`) opens the session *before* it mints the access -token, because the token carries the session's id and a session only has an id once it has been -created (`AuthenticationServiceBase.cs:564-578`); `OpenSessionAsync` -(`AuthenticationServiceBase.cs:762`) mints the refresh token, builds the row, and first enforces the -per-user cap by revoking the oldest live sessions -(`AuthenticationServiceBase.cs:784`, `AuthenticationServiceBase.cs:864-877`), so one account cannot -grow the table without bound while a legitimate sign-in never fails. Both helpers return -[`IssuedSession`](#issuedsession) (`AuthenticationServiceBase.cs:924`), the private pair of "the -plaintext token, which exists nowhere else" and "the row id". The id reaches the client as the -standard `sid` claim, stamped by [`SessionStampingTokenService`](#sessionstampingtokenservice) -(`AuthenticationServiceBase.cs:936`), a pass-through `ITokenService` armed for the duration of the -app's `CreateAccessToken` call (`AuthenticationServiceBase.cs:618-651`, -`AuthenticationServiceBase.cs:792-802`). Doing it with a wrapper rather than by changing the hook's +together (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:166-172`). + +The workflow around the port lives in [`AuthSessionIssuer`](#authsessionissuer) +(`AuthSessionIssuer.cs:39`), not in the authentication base, and is small and worth reading end to +end. `IssueTokensAsync` (`AuthenticationServiceBase.cs:417`) hands the user's id and a minting callback +to the issuer's `IssueAsync` (`AuthSessionIssuer.cs:62`), which opens the session *before* it mints +the access token, because the token carries the session's id and a session only has an id once it has +been created (`AuthSessionIssuer.cs:73-86`). `OpenSessionAsync` (`AuthSessionIssuer.cs:312`) mints the +refresh token, builds the row, and, before staging the insert, enforces the per-user cap by revoking +the oldest live sessions (`AuthSessionIssuer.cs:334`, `AuthSessionIssuer.cs:407-429`), so one account +cannot grow the table without bound while a legitimate sign-in never fails. `OpenSessionAsync` and its +rotation twin `RotateSessionAsync` (`AuthSessionIssuer.cs:351`) both return +[`IssuedSession`](#issuedsession) (`AuthSessionIssuer.cs:436`), the private pair of "the plaintext +token, which exists nowhere else" and "the row id". The id reaches the client as the standard `sid` +claim, stamped by [`SessionStampingTokenService`](#sessionstampingtokenservice) +(`AuthSessionIssuer.cs:448`), a private pass-through `ITokenService` that the issuer exposes as its +`TokenService` (`AuthSessionIssuer.cs:50-53`) and arms through `MintForSession` for the duration of the +app's `CreateAccessToken` call (`AuthSessionIssuer.cs:130-145`, reached from +`AuthenticationServiceBase.cs:469-470`). Doing it with a wrapper rather than by changing the hook's signature is what makes the claim additive: every existing subclass keeps compiling and starts -emitting `sid` with no edit. `GetSessionsAsync` (`AuthenticationServiceBase.cs:481`) projects the +emitting `sid` with no edit. `GetSessionsAsync` (`AuthenticationServiceBase.cs:386`) delegates to the +issuer's `ListActiveAsync` (`AuthSessionIssuer.cs:191`), which projects the user's live sessions into [`RefreshSessionSummaryResponse`](#refreshsessionsummaryresponse) (`MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/Responses/RefreshSessionSummaryResponse.cs:23`), newest first, -flagging the caller's own device by comparing against the token's `sid` -(`AuthenticationServiceBase.cs:489-502`); the response deliberately omits the token hash and the +flagging the caller's own device by comparing against the session id the caller passes from the token's +`sid` (`AuthSessionIssuer.cs:199-212`); the response deliberately omits the token hash and the rotation link, since nothing a client does with a session needs anything but its id (`RefreshSessionSummaryResponse.cs:6-11`). `RevokeSessionByIdAsync` -(`AuthenticationServiceBase.cs:519`) signs one device out and treats an already-revoked row as a -success that writes nothing, because a device list clicked twice is the most ordinary duplicate in the -feature (`AuthenticationServiceBase.cs:532-537`), while `RevokeTokenAsync` -(`AuthenticationServiceBase.cs:416`) degrades to signing every device out when the presented token does -not identify a live session of this user's (`AuthenticationServiceBase.cs:429-447`). Those methods +(`AuthenticationServiceBase.cs:402`, delegating to `AuthSessionIssuer.cs:227`) signs one device out +and treats an already-revoked row as a success that writes nothing, because a device list clicked +twice is the most ordinary duplicate in the feature (`AuthSessionIssuer.cs:242-245`), while +`RevokeTokenAsync` (`AuthenticationServiceBase.cs:352`, delegating to `AuthSessionIssuer.cs:148`) +degrades to signing every device out when the presented token does not identify a live session of +this user's (`AuthSessionIssuer.cs:158-172`). Those methods surface on [`IAuthenticationService`](#iauthenticationservice) (`IAuthenticationService.cs:79`, `IAuthenticationService.cs:96`, `IAuthenticationService.cs:115`) and are exposed by [`AuthControllerBase`](group-12-api-hosting-mapping.md#authcontrollerbase) as `revoke`, `my-sessions`, @@ -341,7 +375,7 @@ swept, a replay of its token reads as an unknown token and fails alone (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/RefreshSessionCleanupService.cs:48`) is registered only when the flag is set, so a service with no `RefreshSessions` table never starts a sweep over a table it does not have -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:168-175`), and the mapping +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:178-185`), and the mapping itself is opt-in through [`RefreshSessionModelBuilderExtensions`](group-07-persistence-ef-core.md#refreshsessionmodelbuilderextensions)`.ApplyRefreshSessionConfiguration` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/RefreshSessionModelBuilderExtensions.cs:34`). @@ -459,15 +493,15 @@ implementation [`TotpTwoFactorService`](#totptwofactorservice) a Base32 shared secret (`TotpTwoFactorService.cs:40-41`), render the `otpauth://totp/...` provisioning URI an authenticator app is keyed from (`TotpTwoFactorService.cs:44`, `TotpTwoFactorService.cs:54`), verify a code inside the configured skew window (`TotpTwoFactorService.cs:58`), and generate and match -single-use recovery codes (`TotpTwoFactorService.cs:87`, `TotpTwoFactorService.cs:116`). It is +single-use recovery codes (`TotpTwoFactorService.cs:91`, `TotpTwoFactorService.cs:120`). It is deliberately stateless and persistence-free, so a data migration enrolling existing accounts can call it directly and every method is testable against a fixed clock (`ITwoFactorService.cs:10-15`). Recovery -codes travel as a [`RecoveryCodeSet`](#recoverycodeset) (`ITwoFactorService.cs:84`), plaintext and +codes travel as a [`RecoveryCodeSet`](#recoverycodeset) (`ITwoFactorService.cs:97`), plaintext and hashes paired positionally so a caller cannot store one set and display another, and the plaintext exists only in the returned value. Matching compares in fixed time through -`CryptographicOperations.FixedTimeEquals` with no early exit (`TotpTwoFactorService.cs:138`), and the +`CryptographicOperations.FixedTimeEquals` with no early exit (`TotpTwoFactorService.cs:142`), and the codes are Base32 rather than Base64 because that alphabet has no case ambiguity for a user reading one -off paper (`TotpTwoFactorService.cs:94-96`). +off paper (`TotpTwoFactorService.cs:98-100`). [`TwoFactorSettings`](#twofactorsettings) (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/TwoFactorSettings.cs:15`) binds from @@ -512,15 +546,15 @@ password check, for the same reason the account-state gate does: reaching it pro credential, so answering "this account needs a second factor" tells the owner something rather than telling an address sweeper which accounts are protected (`ITwoFactorAuthenticator.cs:19-21`). The shipped [`TwoFactorAuthenticator`](#twofactorauthenticator) -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs:25`) reads +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs:36`) reads the state and returns [`TwoFactorOutcome`](#twofactoroutcome)`.NotEnrolled` when there is no active -factor, so an unenrolled account's sign-in is unchanged (`TwoFactorAuthenticator.cs:40`, -`ITwoFactorAuthenticator.cs:47`); otherwise it verifies a TOTP code (`TwoFactorAuthenticator.cs:53`), -falls back to matching and spending a recovery code (`TwoFactorAuthenticator.cs:56`, -`TwoFactorAuthenticator.cs:71`), and fails with one of the two [`TwoFactorErrors`](#twofactorerrors) +factor, so an unenrolled account's sign-in is unchanged (`TwoFactorAuthenticator.cs:114`, +`ITwoFactorAuthenticator.cs:47`); otherwise it verifies a TOTP code (`TwoFactorAuthenticator.cs:114`), +falls back to matching and spending a recovery code (`TwoFactorAuthenticator.cs:70`, +`TwoFactorAuthenticator.cs:85`), and fails with one of the two [`TwoFactorErrors`](#twofactorerrors) codes: `Authentication.TwoFactorRequired` when a code was needed and none arrived, `Authentication.TwoFactorInvalid` when one arrived and did not verify -(`TwoFactorAuthenticator.cs:48`, `TwoFactorAuthenticator.cs:60`, +(`TwoFactorAuthenticator.cs:61`, `TwoFactorAuthenticator.cs:74`, `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/TwoFactorErrors.cs:18`, `TwoFactorErrors.cs:21`). Which method satisfied the challenge is what the issued token records: [`AuthClaimTypes`](#authclaimtypes) names the `mfa` claim and its two values, `otp` and `recovery` @@ -545,40 +579,40 @@ port is [`IPasswordResetTokenService`](#ipasswordresettokenservice) wide: `IssueAsync` mints a token for an address (`IPasswordResetTokenService.cs:23`) and `ValidateAndConsumeAsync` redeems it exactly once (`IPasswordResetTokenService.cs:36`). The implementation, [`PasswordResetTokenService`](#passwordresettokenservice) -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:26`), rides on +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:33`), rides on [`ICacheService`](group-09-caching.md#icacheservice) and buys four properties in a few lines each: - **One active token per email.** Issuing writes the same per-address key - (`PasswordResetTokenService.cs:40`, `PasswordResetTokenService.cs:77`), so requesting a new link + (`PasswordResetTokenService.cs:53`, `PasswordResetTokenService.cs:90`), so requesting a new link retires the previous one. - **Hashed at rest.** Only the Base64 of the token's SHA-256 is stored - (`PasswordResetTokenService.cs:44-45`, `PasswordResetTokenService.cs:71-77`), so a cache dump hands + (`PasswordResetTokenService.cs:57-58`, `PasswordResetTokenService.cs:84-90`), so a cache dump hands out no working reset links, and the comparison on redemption is constant time through - `CryptographicOperations.FixedTimeEquals` (`PasswordResetTokenService.cs:107`). + `CryptographicOperations.FixedTimeEquals` (`PasswordResetTokenService.cs:146`). - **An attempt cap.** A wrong token increments a counter on the record, and the record is discarded at - `MaxValidationAttempts` (`PasswordResetTokenService.cs:121-142`, default 5, + `MaxValidationAttempts` (`PasswordResetTokenService.cs:160-181`, default 5, `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/PasswordResetSettings.cs:36`). The rewrite after a wrong guess uses the record's *remaining* lifetime rather than a fresh one - (`PasswordResetTokenService.cs:135-141`), so guessing cannot extend the redeemable window. + (`PasswordResetTokenService.cs:174-180`), so guessing cannot extend the redeemable window. - **A per-email request throttle.** A counter carrying the window's TTL caps how often one address can - trigger an email (`PasswordResetTokenService.cs:55-66`, default 3 per 60 minutes, + trigger an email (`PasswordResetTokenService.cs:68-79`, default 3 per 60 minutes, `PasswordResetSettings.cs:40`, `PasswordResetSettings.cs:44`), and a successful redemption deletes - the token *and* that counter (`PasswordResetTokenService.cs:115-116`) so a legitimate reset does not + the token *and* that counter (`PasswordResetTokenService.cs:154-155`) so a legitimate reset does not leave the user throttled out of a later one. Keys are built from an `Email`-normalized identity, through the same [`EmailIdentity`](#emailidentity) helper and for the same reason [`LoginProtectionService`](#loginprotectionservice) does it -(`PasswordResetTokenService.cs:34-42`). The cached record, [`PasswordResetEntry`](#passwordresetentry) -(`PasswordResetTokenService.cs:160`), is deliberately all JSON primitives: cache values round-trip +(`PasswordResetTokenService.cs:47-55`). The cached record, [`PasswordResetEntry`](#passwordresetentry) +(`PasswordResetTokenService.cs:199`), is deliberately all JSON primitives: cache values round-trip through `System.Text.Json`, so a value object or a `byte[]` member would not survive a distributed -backing store (`PasswordResetTokenService.cs:151-155`). Token material is 32 random bytes, Base64Url -encoded (`PasswordResetTokenService.cs:30`, `PasswordResetTokenService.cs:68`), redeemable for +backing store (`PasswordResetTokenService.cs:190-194`). Token material is 32 random bytes, Base64Url +encoded (`PasswordResetTokenService.cs:39`, `PasswordResetTokenService.cs:81`), redeemable for `TokenLifetimeMinutes` (default 30, `PasswordResetSettings.cs:29`), and every rejection (unknown, expired, mismatched, attempt-capped) collapses into one generic failure -(`PasswordResetTokenService.cs:144-148`). The settings bind from the `PasswordReset` configuration +(`PasswordResetTokenService.cs:183-187`). The settings bind from the `PasswordReset` configuration section and the service is registered scoped in Infrastructure DI (`PasswordResetSettings.cs:13`, -`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:154-158`). +`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:160-164`). The workflow around the port lives in the group-14 handler bases, and it is where the anti-enumeration rule is enforced. @@ -626,16 +660,16 @@ mirrors the reset port, with `IssueAsync` minting a token for an address and acc (`IEmailConfirmationTokenService.cs:28`) and `ValidateAndConsumeAsync` redeeming it exactly once and returning the confirmed account's identifier (`IEmailConfirmationTokenService.cs:41`). The implementation [`EmailConfirmationTokenService`](#emailconfirmationtokenservice) -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:35`) rides on +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:36`) rides on [`ICacheService`](group-09-caching.md#icacheservice) under its own key prefix and buys the same four -properties: 32 random bytes Base64Url encoded (`EmailConfirmationTokenService.cs:39`, -`EmailConfirmationTokenService.cs:64`), only the token's SHA-256 stored -(`EmailConfirmationTokenService.cs:130`), a constant-time comparison on redemption -(`EmailConfirmationTokenService.cs:103`), and every rejection collapsed into one generic failure -(`EmailConfirmationTokenService.cs:155-157`). Its token and request-counter keys go through the same +properties: 32 random bytes Base64Url encoded (`EmailConfirmationTokenService.cs:41`, +`EmailConfirmationTokenService.cs:66`), only the token's SHA-256 stored +(`EmailConfirmationTokenService.cs:135`), a constant-time comparison on redemption +(`EmailConfirmationTokenService.cs:108`), and every rejection collapsed into one generic failure +(`EmailConfirmationTokenService.cs:160-162`). Its token and request-counter keys go through the same [`EmailIdentity`](#emailidentity) normalization as the reset keys -(`EmailConfirmationTokenService.cs:125`, `EmailConfirmationTokenService.cs:127`). The cached record is -[`EmailConfirmationEntry`](#emailconfirmationentry) (`EmailConfirmationTokenService.cs:169`), JSON +(`EmailConfirmationTokenService.cs:130`, `EmailConfirmationTokenService.cs:132`). The cached record is +[`EmailConfirmationEntry`](#emailconfirmationentry) (`EmailConfirmationTokenService.cs:174`), JSON primitives only for the same round-trip reason [`PasswordResetEntry`](#passwordresetentry) is. [`EmailConfirmationSettings`](#emailconfirmationsettings) @@ -811,20 +845,20 @@ defaults preserve the original `customer_id` / `Admin` / `id` behavior (`OwnerOrAdminFilterOptions.cs:16-31`, [ADR-033](https://ivanball.github.io/docs/adr/033-resource-ownership-authorization.html)), with [`OwnershipHelper`](#ownershiphelper) -(`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:10`) supplying both -the bypass-role check (`OwnershipHelper.cs:17`) and the query-scoping specification factory that +(`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:11`) supplying both +the bypass-role check (`OwnershipHelper.cs:18`) and the query-scoping specification factory that controllers use to narrow *collection* endpoints to the caller's own rows -(`OwnershipHelper.cs:34-67`). +(`OwnershipHelper.cs:35-68`). Compiled grants are the baseline, not the ceiling. A host that needs an operator to move a capability between roles without a redeploy calls `AddStoredPermissionGrants(configuration)` -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:104`), which binds +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:108`), which binds [`PermissionGrantSettings`](#permissiongrantsettings) (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Permissions/PermissionGrantSettings.cs:12`, -bound at `DependencyInjection.Auth.cs:106-109`), registers the EF-backed -[`IPermissionGrantStore`](#ipermissiongrantstore) (`DependencyInjection.Auth.cs:114`), and decorates the +bound at `DependencyInjection.Auth.cs:110-113`), registers the EF-backed +[`IPermissionGrantStore`](#ipermissiongrantstore) (`DependencyInjection.Auth.cs:118`), and decorates the compiled registry with [`LayeredPermissionRegistry`](#layeredpermissionregistry) -(`DependencyInjection.Auth.cs:135-146`). The layering rule is a union with **no deny row**: a role holds a +(`DependencyInjection.Auth.cs:139-150`). The layering rule is a union with **no deny row**: a role holds a permission when either the compiled registry or the stored grants say so (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Permissions/LayeredPermissionRegistry.cs:30`, `LayeredPermissionRegistry.cs:55-65`), so a bad edit can add a capability but can never take away one @@ -835,15 +869,15 @@ boundary over an in-memory snapshot, refreshed on demand (`IPermissionGrantCache `IPermissionGrantCache.cs:11-19`), and [`IPermissionGrantCacheInvalidator`](#ipermissiongrantcacheinvalidator) (`IPermissionGrantCache.cs:51`, `IPermissionGrantCache.cs:59`) is what an edit calls. The consequence a host accepts is that a grant -edit reaches other replicas within `CacheSeconds` (default 300, `PermissionGrantSettings.cs:23`), with -`DataSourceName` naming the one database that carries the rows (`PermissionGrantSettings.cs:31`), +edit reaches other replicas within `CacheSeconds` (default 300, `PermissionGrantSettings.cs:25`), with +`DataSourceName` naming the one database that carries the rows (`PermissionGrantSettings.cs:33`), exactly as [`RefreshSessionSettings`](#refreshsessionsettings) does. The row itself, [`PermissionGrant`](#permissiongrant) (`MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/PermissionGrant.cs:24`), is a two-column identity (`PermissionGrant.cs:39`, `PermissionGrant.cs:45`) trimmed and length-validated in a `Result`-returning factory (`PermissionGrant.cs:64`, widths at `PermissionGrant.cs:27` and `PermissionGrant.cs:30`), and its table is mapped only for the host that opted in, through a model gate -whose mere registration is the opt-in the context reads (`DependencyInjection.Auth.cs:112`). +whose mere registration is the opt-in the context reads (`DependencyInjection.Auth.cs:116`). The administration API sits on top of the same grants. [`IRoleAdministrationService`](#iroleadministrationservice) @@ -888,7 +922,7 @@ would be gated along with everything else (`FallbackAuthorizationRequirement.cs: the requirement is path-aware. [`FallbackAuthorizationOptions`](#fallbackauthorizationoptions) (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/Fallback/FallbackAuthorizationOptions.cs:14`) carries the `Enabled` switch (on by default, `FallbackAuthorizationOptions.cs:52`) and the editable -exempt-prefix list seeded from the framework defaults (`FallbackAuthorizationOptions.cs:61`); the +exempt-prefix list seeded from the framework defaults (`FallbackAuthorizationOptions.cs:64`); the handler is registered with `TryAddEnumerable` and the policy attached only while the switch is set (`AuthorizationExtensions.cs:80-81`, `AuthorizationExtensions.cs:86-89`). @@ -898,10 +932,10 @@ The final cluster solves a Blazor-specific problem: an interactive Blazor app ke browser memory, but a *cold* server-side render (a new tab, an F5, an external deep link) has no memory to read, so an `[Authorize]` page would bounce to `/login` before the interactive phase starts. The fix is a pair of HttpOnly cookies (`mmca_auth_access`, `mmca_auth_refresh`, -`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:17-18`) +`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:18-19`) seeded and cleared from JS through [`SessionCookieEndpoints`](#sessioncookieendpoints) -(`SessionCookieEndpoints.cs:15`, `SessionCookieEndpoints.cs:34-44`, request body -[`SessionCookieRequest`](#sessioncookierequest) at `SessionCookieEndpoints.cs:77`), written with one +(`SessionCookieEndpoints.cs:16`, `SessionCookieEndpoints.cs:34-44`, request body +[`SessionCookieRequest`](#sessioncookierequest) at `SessionCookieEndpoints.cs:92`), written with one shared set of attributes by [`SessionCookieJar`](#sessioncookiejar) (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieJar.cs:11`: `HttpOnly`, `Secure` outside Development, `SameSite=Lax`, `Path=/`, and a 7-day max age aligned to the @@ -926,33 +960,33 @@ registered by [`CookieSessionRefreshMiddlewareExtensions`](#cookiesessionrefresh at `CookieSessionRefreshMiddleware.cs:35`) runs *before* `UseAuthentication` on qualifying navigations (GET plus an `Accept` header containing `text/html`, `CookieSessionRefreshMiddleware.cs:28-31`) and delegates to [`CookieSessionRefresher`](#cookiesessionrefresher) -(`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:52`) through -the [`ICookieSessionRefresher`](#icookiesessionrefresher) port (`CookieSessionRefresher.cs:30`). The +(`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:74`) through +the [`ICookieSessionRefresher`](#icookiesessionrefresher) port (`CookieSessionRefresher.cs:32`). The refresher first tries to read a still-valid expiry out of the access cookie with a 30-second skew -allowance (`CookieSessionRefresher.cs:60`, `CookieSessionRefresher.cs:155-184`); failing that it +allowance (`CookieSessionRefresher.cs:84`, `CookieSessionRefresher.cs:155-184`); failing that it exchanges the refresh cookie at the API's `auth/refresh` endpoint server-to-server -(`CookieSessionRefresher.cs:128-131`), so the refresh token never reaches browser JS. It then writes +(`CookieSessionRefresher.cs:179-182`), so the refresh token never reaches browser JS. It then writes the rotated pair back as cookies and stashes the fresh access token on `HttpContext.Items` (`CookieSessionRefresher.cs:88-92`) so the *current* request's authentication reads the new token: [`CookieTokenReader`](#cookietokenreader) checks that item before falling back to the request cookie (`CookieTokenReader.cs:17`, `CookieTokenReader.cs:27-33`). Concurrent refreshes are collapsed into a single flight by a [`KeyedSemaphoreStripe`](#keyedsemaphorestripe) keyed on the refresh token plus a 10-second rotation-grace `IMemoryCache` entry keyed by the **old** refresh token -(`CookieSessionRefresher.cs:61`, `CookieSessionRefresher.cs:63`, `CookieSessionRefresher.cs:96-112`, -`CookieSessionRefresher.cs:145`), so a queued herd of requests cannot double-rotate. Striping rather +(`CookieSessionRefresher.cs:85`, `CookieSessionRefresher.cs:87`, `CookieSessionRefresher.cs:96-112`, +`CookieSessionRefresher.cs:198`), so a queued herd of requests cannot double-rotate. Striping rather than one process-wide lock is deliberate and stated in source: the lock is held across an outbound HTTP call, so a single semaphore serialized every unrelated user's cold navigation behind whichever refresh -was in flight (`CookieSessionRefresher.cs:45-50`); two unrelated tokens sharing a stripe is harmless +was in flight (`CookieSessionRefresher.cs:67-72`); two unrelated tokens sharing a stripe is harmless because the grace cache is re-checked per token after acquiring -(`CookieSessionRefresher.cs:105-109`). A transport failure is not cached and renders the request +(`CookieSessionRefresher.cs:155-159`). A transport failure is not cached and renders the request anonymously rather than throwing a 500 out of SSR (`CookieSessionRefresher.cs:118-123`, `CookieSessionRefresher.cs:148-152`). The same refresher backs the same-origin `POST /auth/session/token` endpoint the browser polls to hydrate its in-memory token -(`SessionCookieEndpoints.cs:50-65`), guarded by `SameSite=Lax` plus a `Sec-Fetch-Site` cross-site -rejection (`SessionCookieEndpoints.cs:53-56`, `SessionCookieEndpoints.cs:73-75`) and returning -[`SessionTokenResponse`](#sessiontokenresponse) (`CookieSessionRefresher.cs:21`), the browser-safe +(`SessionCookieEndpoints.cs:58-80`), guarded by `SameSite=Lax` plus a `Sec-Fetch-Site` cross-site +rejection (`SessionCookieEndpoints.cs:61-64`, `SessionCookieEndpoints.cs:88-90`) and returning +[`SessionTokenResponse`](#sessiontokenresponse) (`CookieSessionRefresher.cs:23`), the browser-safe projection of the internal [`SessionTokenResult`](#sessiontokenresult) -(`CookieSessionRefresher.cs:15`) that deliberately omits the refresh token. This whole cluster is +(`CookieSessionRefresher.cs:17`) that deliberately omits the refresh token. This whole cluster is [ADR-022](https://ivanball.github.io/docs/adr/022-browser-session-cookie-auth.html)'s server half; the client half across Blazor Server, WASM, and MAUI is [ADR-051](https://ivanball.github.io/docs/adr/051-client-auth-token-lifecycle.html). @@ -997,16 +1031,16 @@ which forces a choice between two defects: removing the entry on release opens a caller waits on a semaphore no longer in the table while another creates a fresh one, and never removing it lets caller-supplied keys grow the table without bound (`KeyedSemaphoreStripe.cs:7-16`). Its consumers today are -[`CookieSessionRefresher`](#cookiesessionrefresher) (above, `CookieSessionRefresher.cs:63`), +[`CookieSessionRefresher`](#cookiesessionrefresher) (above, `CookieSessionRefresher.cs:87`), the [`IdempotencyFilter`](group-12-api-hosting-mapping.md#idempotencyfilter) -(`MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:90`), +(`MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:91`), [`CachingQueryDecorator`](group-05-cqrs-pipeline.md#cachingquerydecoratortquery-tresult) (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/CachingQueryDecorator.cs:250`), [`MemoryCacheService`](group-09-caching.md#memorycacheservice) -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:38`), and the +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:45`), and the default `GetOrCreateAsync` lock table on [`ICacheService`](group-09-caching.md#icacheservice) -(`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:145`). +(`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:192`). [`InProcessDistributedLock`](group-14-module-system-composition.md#inprocessdistributedlock) is the deliberate exception: it keys on the exact key in a `ConcurrentDictionary` instead, because its contract has a *bounded* wait, and stripe false-sharing would turn that into a spurious @@ -1097,7 +1131,7 @@ and query binding through [`StronglyTypedIdTypeConverter`](#stron and the [`StronglyTypedIdTypeConverters`](#stronglytypedidtypeconverters) registrar (`MMCA.Common/Source/Core/MMCA.Common.Shared/Identifiers/StronglyTypedIdTypeConverter.cs:21`, `StronglyTypedIdTypeConverter.cs:82`, `StronglyTypedIdTypeConverter.cs:89`, -`StronglyTypedIdTypeConverter.cs:108`), object mapping through +`StronglyTypedIdTypeConverter.cs:119`), object mapping through [`StronglyTypedIdMappings`](#stronglytypedidmappingstself-tvalue), whose four *static* methods are the contract because Mapperly discovers them by signature on a closed generic type (`MMCA.Common/Source/Core/MMCA.Common.Shared/Identifiers/StronglyTypedIdMappings.cs:31`, @@ -1210,7 +1244,7 @@ live in later groups; this chapter is the engine those endpoints call into. (`AddAuthorizationPolicies(options => options.Enabled = false)`) rather than by omission (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/Fallback/FallbackAuthorizationOptions.cs:47-49`). `ExemptPathPrefixes` - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/Fallback/FallbackAuthorizationOptions.cs:61`) + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/Fallback/FallbackAuthorizationOptions.cs:64`) is a mutable `IList` seeded with `[.. DefaultExemptPathPrefixes]`, so a host appends its own static roots without losing the framework defaults; the doc comment is explicit that application endpoints belong on `[AllowAnonymous]`, not in this list, because that is what the anonymous-endpoint @@ -1219,6 +1253,14 @@ live in later groups; this chapter is the engine those endpoints call into. - **Why it's built this way**: matching is segment-based and case-insensitive against these prefixes (per the `ExemptPathPrefixes` doc comment), so a coarse prefix like `/_framework` covers everything beneath it without enumerating individual asset paths. +- **Caveats / not-in-source**: the exemption is purely path-based. An undecorated controller routed + under one of these prefixes would pass ungated, and the doc comment names + [`AnonymousEndpointTestsBase`](group-28-testing-infrastructure.md#anonymousendpointtestsbase)`.Endpoints_DeclareAnAuthorizationDecision` + as the gate that keeps such a controller from landing + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/Fallback/FallbackAuthorizationOptions.cs:59-62`). + That test is off by default, so a repo only gets this protection once it opts into the stricter + gate + (`MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Api/AnonymousEndpointTestsBase.cs:55-56,118`). - **Where it's used**: bound via `services.AddOptions()` and read by [`FallbackAuthorizationHandler`](#fallbackauthorizationhandler) inside [`AuthorizationExtensions.AddAuthorizationPolicies`](#authorizationextensions). @@ -1646,14 +1688,16 @@ live in later groups; this chapter is the engine those endpoints call into. (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/UnconfiguredPermissionRegistry.cs:68`). ### OwnershipHelper -> MMCA.Common.API · `MMCA.Common.API.Authorization` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:10` · Level 9 · class (static) +> MMCA.Common.API · `MMCA.Common.API.Authorization` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:11` · Level 9 · class (static) -- **What it is**: static helpers a controller calls to scope a query to the current user's own data, - returning a specification that filters by owner id, or `null` when the caller holds the privileged - bypass role and should see everything. +- **What it is**: static helpers a controller calls to enforce ownership: to scope a query to the + current user's own data (a specification that filters by owner id, or `null` when the caller holds + the privileged bypass role), to refuse a scoped read whose owner claim cannot be resolved, and to + check that the caller owns one specific record. - **Depends on**: [`ICurrentUserService`](#icurrentuserservice) (Application layer, imported at - `MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:1`). The - specification it hands back is typically a + `MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:1`), plus + [`Result`](group-01-result-error-handling.md#result) and [`Error`](group-01-result-error-handling.md#error) + for the two gate methods. The specification it hands back is typically a [`Specification`](group-03-querying-specifications.md#specificationtentity-tidentifiertype), though the helper itself never says so (see caveats). - **Concept introduced, ownership scoping at the query level, as distinct from the filter's gate.** @@ -1667,58 +1711,90 @@ live in later groups; this chapter is the engine those endpoints call into. for collection routes. - **Walkthrough** - `IsAdmin(ICurrentUserService, string bypassRole)` - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:17`): a + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:18`): a case-insensitive compare of the current user's `Role` against the bypass role - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:20`). + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:21`). `bypassRole` has no default argument: every caller supplies its own, normally sourced from [`OwnerOrAdminFilterOptions.BypassRole`](#owneroradminfilteroptions), so the framework itself declares no role names - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:13-16`). + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:14-17`). [`OwnerOrAdminFilter`](#owneroradminfilter) reuses this same method so the gate and the scoping agree on who bypasses. - `GetOwnershipSpecification(...)` - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:34`): the general + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:35`): the general form. It returns `null` for a bypass-role caller - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:45-48`, no scoping + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:46-49`, no scoping needed); otherwise it reads the owner id from the named claim via `currentUserService.GetClaimValue(claimType)` and, when present, calls the supplied `specFactory(id.Value)` to build the scoping specification - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:50-51`). Like + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:51-52`). Like `IsAdmin`, `bypassRole` is a required parameter with no default - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:38`). The + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:39`). The `where TId : struct, IParsable` constraint - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:40`) is what lets + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:41`) is what lets the claim string be parsed into a strongly-typed id. - `GetOwnershipSpecification(...)` - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:64`): the + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:65`): the convenience overload that fixes `TId` to `int` and the claim to `"customer_id"`, but still takes `bypassRole` as a required parameter and forwards it - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:67`). + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:68`). + - `RequireResolvableOwner(currentUserService, claimType, bypassRole, source, target)` + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:91`): the + fail-closed gate to call before a read scoped through `GetOwnershipSpecification`. It returns + `Result.Success()` when the caller passes `IsAdmin` or carries a parsable owner claim, and + otherwise a `Forbidden` failure + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:101-103`). The + doc comment frames a missing claim as an authorization answer (403), never an unscoped read and + never a 500 + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:73-78`). + - `ValidateOwnershipAsync(..., isOwnedBy, source, target, cancellationToken)` + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:133`): the + single-record gate. A bypass-role caller passes without a lookup + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:146-149`); an + unresolvable owner claim is refused with the same `Forbidden` failure and the lookup never runs + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:151-154`); + otherwise the private `CheckOwnershipAsync` + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:159`) awaits the + caller-supplied `isOwnedBy(ownerId, cancellationToken)` predicate (typically an `ExistsAsync` + query) and answers `Error.NotFound` with the given source and target when it is false. + - `AccessDenied(source, target)` + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:174`): the one + private factory both gates share, `Error.Forbidden("Error.Forbidden", "Access denied.", source, + target)`. - **Why it's built this way**: returning `null` for bypass-role callers (rather than a "match everything" specification) lets the caller skip filtering entirely on the privileged path; producing a specification rather than running the query keeps the helper in the API layer while the actual - filtering runs in the query pipeline. Removing the `"Admin"` default arguments (in favor of a required - parameter everywhere) matches the same move on - [`OwnerOrAdminFilterOptions.BypassRole`](#owneroradminfilteroptions): the framework no longer names a - default role anywhere in the ownership axis. -- **Where it's used**: called from MMCA.Store controller query actions that must isolate a caller's - data. `ShoppingCartsController` exposes both an `IsAdmin` property and a private - `GetOwnershipSpecification()` over it - (`MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/ShoppingCartsController.cs:64,66-68`), + filtering runs in the query pipeline. Every method takes `bypassRole` as a required parameter and + checks it through the same `IsAdmin`, so the scope and both gates agree on who is privileged, and, + like [`OwnerOrAdminFilterOptions.BypassRole`](#owneroradminfilteroptions), the framework names no + default role anywhere in the ownership axis. `ValidateOwnershipAsync` answers a non-owner with 404 + rather than 403 so the existence of another owner's record cannot be probed for + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:113-114`). +- **Where it's used**: MMCA.Store's `ShoppingCartsController` wraps `GetOwnershipSpecification` + and `RequireResolvableOwner` in private helpers + (`MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/ShoppingCartsController.cs:65-67,79-84`), and `OrdersController` does the same - (`MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/OrdersController.cs:62,65`). Its - `IsAdmin` method is also the bypass check inside [`OwnerOrAdminFilter`](#owneroradminfilter) + (`MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/OrdersController.cs:66-68,80-85`), + also routing its mutating endpoints through `ValidateOwnershipAsync` + (`MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/OrdersController.cs:386-390`). + In MMCA.ADC, `EventQuestionAnswersController` and `SessionQuestionAnswersController` scope by user + id with `GetOwnershipSpecification` and gate with `RequireResolvableOwner` + (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventQuestionAnswersController.cs:108,136`, + `MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionQuestionAnswersController.cs:108,136`). + `IsAdmin` also decides the export override `AllowUnscopedExport` on all four controllers (for + example + `MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/OrdersController.cs:249`) and is + the bypass check inside [`OwnerOrAdminFilter`](#owneroradminfilter) (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnerOrAdminFilter.cs:43`). - **Caveats / not-in-source**: two gaps are worth knowing. `TSpec` is an open generic with only a `class` constraint - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:39`), so the helper - does not itself require the returned type to be a specification: that contract is the caller's. And a - non-admin caller whose claim is missing or unparseable also gets `null` - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:51`), which means - no scoping, so callers must not read `null` as "admin". Store closes that in the controller with a - `RequireResolvableOwner()` gate that forbids the second case explicitly - (`MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/ShoppingCartsController.cs:80-90`); - the helper itself does not distinguish them. + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:40`), so the helper + does not itself require the returned type to be a specification: that contract is the caller's. And + `GetOwnershipSpecification` also returns `null` for a non-admin caller whose claim is missing or + unparseable + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:52`), which means + no scoping. It does not distinguish that case from the bypass role; `RequireResolvableOwner` does, + but only if the caller invokes it before the read, which nothing in the helper enforces. ### OwnerOrAdminFilter > MMCA.Common.API · `MMCA.Common.API.Authorization` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnerOrAdminFilter.cs:31` · Level 10 · class (sealed action filter) @@ -1818,8 +1894,80 @@ live in later groups; this chapter is the engine those endpoints call into. ([ADR-033](https://ivanball.github.io/docs/adr/033-resource-ownership-authorization.html) lists orders as that case, handled with a specification or an explicit per-id check instead). +### ISessionCookieStore +> MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/ISessionCookieStore.cs:14` · Level 0 · interface + +- **What it is**: the public port for writing and clearing the two HttpOnly session cookies from server + code that obtained the tokens itself, so it can store them without a browser round trip. +- **Depends on**: `HttpContext` (ASP.NET Core) only. Its one implementation is + [`SessionCookieStore`](#sessioncookiestore), a thin adapter over the internal + [`SessionCookieJar`](#sessioncookiejar) that applies [`SessionCookieSettings`](#sessioncookiesettings). +- **Concept introduced, a public door onto an internal cookie writer.** [Rubric §11, Security] + assesses whether cookie hardening has one definition. The jar is `internal`, so before this + interface only `MMCA.Common.API` itself could set the session cookies. The doc comment + (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/ISessionCookieStore.cs:7-13`) names + the new caller: the same-origin API proxy in `MMCA.Common.UI.Web`, which receives tokens from the + identity endpoint and must persist them with exactly the options (`HttpOnly`, `Secure` outside + Development, the configured `SameSite`, 7-day lifetime) that the session-cookie endpoints and the + server-side refresher already use. Exposing two methods instead of making the jar public keeps the + option-building in one place. +- **Walkthrough**: `void Write(HttpContext context, string accessToken, string refreshToken)` (`:19`) + appends both cookies; `void Clear(HttpContext context)` (`:23`) expires both. Neither returns + anything: the cookies are a side effect on `context.Response`. +- **Why it's built this way**: the proxy keeps every credential server-side + ([ADR-131](https://ivanball.github.io/docs/adr/131-same-origin-api-proxy.html)), so it is the one + writer of the cookies on a proxied host and needs a supported way to write them that cannot drift + from the endpoints' own writes. +- **Where it's used**: registered as a singleton by `AddServerAuthSessionCookie` + (`MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:191`); injected into + [`SameOriginApiProxyEndpoint`](group-15-common-ui-framework.md#sameoriginapiproxyendpoint) + (`MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpoint.cs:33`), + [`SameOriginProxyTransformer`](group-15-common-ui-framework.md#sameoriginproxytransformer) + (`MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginProxyTransformer.cs:39`) + and the handoff route of + [`SessionHandoffEndpoints`](group-15-common-ui-framework.md#sessionhandoffendpoints) + (`MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SessionHandoffEndpoints.cs:42`). + +### SessionClaimsToken +> MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionClaimsToken.cs:14` · Level 0 · class + +- **What it is**: a static helper that turns a signed access token into a claims-only copy: the same + payload under an unsecured header with an empty signature, readable by the browser but useless as a + credential. +- **Depends on**: BCL and NuGet only: `Base64Url` (`System.Buffers.Text`), `Encoding`, and + `JwtSecurityTokenHandler` (`System.IdentityModel.Tokens.Jwt`). Switched on by + [`SessionCookieSettings`](#sessioncookiesettings)`.ClaimsOnlyBrowserTokens`. +- **Concept introduced, the claims-only browser token.** [Rubric §11, Security] and [Rubric §26, + Front-End Security] both assess what an XSS payload could steal. The doc comment + (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionClaimsToken.cs:7-13`) states + the trade: the client can still read the user's claims and the expiry to render its authentication + state, but the token authorizes nothing, because every API validates signatures and rejects an + unsigned token. Script on the page therefore holds nothing worth exfiltrating, while the UI keeps + working off the same claims it always read. +- **Walkthrough**: `UnsecuredHeader` (`:17`) is computed once as the base64url (no padding) encoding of + `{"alg":"none","typ":"JWT"}`, as the comment at `:16` records. `Create(string? accessToken)` (`:25`) + returns `null` for a blank value or anything `JwtSecurityTokenHandler.CanReadToken` refuses + (`:27-30`), then splits on `.` and, for a three-segment compact JWT, returns + `{UnsecuredHeader}.{payload}.` (`:32-33`); any other shape is also `null`. The trailing dot keeps the + three-segment compact form with an empty signature. +- **Why it's built this way**: copying the payload segment verbatim (rather than re-serializing claims) + guarantees the browser sees exactly the claims and `exp` the API issued + ([ADR-131](https://ivanball.github.io/docs/adr/131-same-origin-api-proxy.html)). +- **Where it's used**: the `/auth/session/token` route of + [`SessionCookieEndpoints`](#sessioncookieendpoints) under claims-only mode + (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:73-75`), + the proxy's response rewrite + (`MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginProxyTransformer.cs:142`) + and its refresh answer + (`MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpoint.cs:369`). + Pinned by `MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/SessionClaimsTokenTests.cs`: + claims and expiry kept but signature dropped (`:20`), the result rejected by a signature-validating + API (`:37`), and `null` for non-JWT input (`:56`). +- **Caveats / not-in-source**: "authorizes nothing" is a property of each API's JWT bearer validation + (signature required), not of this type; the test at `:37` checks it against one validating handler. + ### SessionCookieRequest -> MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:77` · Level 0 · record +> MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:92` · Level 0 · record - **What it is**: the inbound body for `POST /auth/session-cookie`: the access and refresh token strings the browser hands back to the server so they can be re-issued as HttpOnly cookies. @@ -1834,13 +1982,68 @@ live in later groups; this chapter is the engine those endpoints call into. session-cookie scheme. - **Walkthrough**: the whole type is one line, `public sealed record SessionCookieRequest(string AccessToken, string RefreshToken)` - (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:77`). It + (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:92`). It is nested in the endpoint class it serves, so the contract sits next to its only route. - **Where it's used**: bound by the `POST` handler at `SessionCookieEndpoints.cs:34`, which passes both strings straight to [`SessionCookieJar`](#sessioncookiejar) (`:31`). +### SessionCookieSettings +> MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieSettings.cs:11` · Level 0 · class + +- **What it is**: the options object for both session cookies: which `SameSite` mode they carry and + whether the browser is ever handed a real access token. +- **Depends on**: `SameSiteMode` (ASP.NET Core) only. Read through `IOptions` by + [`SessionCookieEndpoints`](#sessioncookieendpoints), [`SessionCookieStore`](#sessioncookiestore) and + [`CookieSessionRefresher`](#cookiesessionrefresher); its claims-only switch selects + [`SessionClaimsToken`](#sessionclaimstoken). +- **Concept introduced, one options object for every cookie write.** [Rubric §11, Security] assesses + cookie hardening and credential exposure. A cookie delete only matches when it repeats the write's + attributes, so the seed, refresh, store and clear paths all read `SameSite` from this one object + instead of each choosing a value. +- **Walkthrough**: `SameSite` (`:19`) defaults to `SameSiteMode.Lax`; the doc comment (`:13-18`) + explains the same-origin API proxy raises it to `Strict` because the cookie then authenticates data + calls, not only server-side rendering. `ClaimsOnlyBrowserTokens` (`:29`) defaults to `false`; when + `true` (doc `:21-28`), `POST /auth/session/token` answers with a claims-only token instead of the real + access token, and `POST /auth/session-cookie` ignores posted tokens and answers `204` without + writing, because the server is then the only writer of the cookies. +- **Why it's built this way**: the defaults are the long-standing cookie behavior (the comment at + `DependencyInjection.cs:188-189` says so), so a host that never opts in keeps `Lax` cookies and real + browser tokens; the proxy tightens both settings in one place + ([ADR-131](https://ivanball.github.io/docs/adr/131-same-origin-api-proxy.html)). +- **Where it's used**: registered with defaults by `AddServerAuthSessionCookie` + (`MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:190`, comment at `:188-189`). + The proxy's registration in + [`SameOriginApiProxyServiceExtensions`](group-15-common-ui-framework.md#sameoriginapiproxyserviceextensions) + configures it (`MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyServiceExtensions.cs:69-74`), + copying `SameSite` from + [`SameOriginApiProxySettings`](group-15-common-ui-framework.md#sameoriginapiproxysettings)`.SessionCookieSameSite` + (default `Strict`, + `MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxySettings.cs:74`) + and setting `ClaimsOnlyBrowserTokens = true`. Claims-only behavior is pinned by + `MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/ClaimsOnlySessionCookieEndpointsTests.cs` + (`:27`, `:47`, `:62`). + +### SessionRefreshStatus +> MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionRefreshOutcome.cs:4` · Level 0 · enum + +- **What it is**: the three ways a validate-or-refresh attempt can end: `Refreshed = 0` (`:10`), + `Rejected = 1` (`:16`) and `Unavailable = 2` (`:23`). +- **Depends on**: nothing. Carried by [`SessionRefreshOutcome`](#sessionrefreshoutcome). +- **Concept introduced, a dead session is not the same as an unreachable identity endpoint.** + [Rubric §29, Resilience, Reliability & Business Continuity] assesses whether a transient fault is + treated as a permanent one. The doc comments draw the line: `Refreshed` means a usable access token + is available (still-valid cookie, or a rotated pair written back, `:6-9`); `Rejected` means no refresh + cookie or the identity endpoint refused it with 400, 401 or 403, so clearing the cookies is correct + (`:12-15`); `Unavailable` covers a 5xx, 429 or other non-refusal status, a timeout, a network failure + or an unreadable body, where the cookies may still hold a live session and must be kept for a retry + (`:18-22`). A caller that clears cookies on any failure would sign a user out over a gateway blip. +- **Where it's used**: produced by [`CookieSessionRefresher`](#cookiesessionrefresher) (its + `ClassifyFailure`, `CookieSessionRefresher.cs:118-121`) and branched on by the proxy's + `FailRefreshAsync` + (`MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpoint.cs:276`). + ### SessionTokenResponse -> MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:21` · Level 0 · record +> MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:23` · Level 0 · record - **What it is**: the JSON body returned by `POST /auth/session/token`: the access token and its UTC expiry, and nothing else. @@ -1851,7 +2054,7 @@ live in later groups; this chapter is the engine those endpoints call into. the access token, which the SPA holds in memory for its Bearer calls, and deliberately omits the refresh token, which stays exclusively in the HttpOnly cookie. The doc comment states the rule outright - (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:17-20`). + (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:19-22`). - **Walkthrough**: `public sealed record SessionTokenResponse(string AccessToken, DateTime AccessTokenExpiry)` (`:20`). It is the serialized projection of the internal [`SessionTokenResult`](#sessiontokenresult), which is why the two types carry the same two members @@ -1860,7 +2063,7 @@ live in later groups; this chapter is the engine those endpoints call into. (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:61`). ### SessionTokenResult -> MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:15` · Level 0 · record struct +> MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:17` · Level 0 · record struct - **What it is**: the internal carrier for a validated access token plus its UTC expiry, returned by the refresher. A `readonly record struct`, so the validate path allocates nothing to report success. @@ -1873,7 +2076,7 @@ live in later groups; this chapter is the engine those endpoints call into. `readonly record struct` is the light choice for a result produced on every qualifying navigation. - **Walkthrough**: `public readonly record struct SessionTokenResult(string AccessToken, DateTime AccessTokenExpiry)` - (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:15`), with + (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:17`), with the one-line summary at `:13` naming its provenance ("acquired from the session cookies"). - **Where it's used**: the return type of [`ICookieSessionRefresher.GetOrRefreshAsync`](#icookiesessionrefresher) (`:36`); constructed by @@ -1881,35 +2084,81 @@ live in later groups; this chapter is the engine those endpoints call into. rotation); unwrapped by [`SessionCookieEndpoints`](#sessioncookieendpoints) at `SessionCookieEndpoints.cs:61`. -### ICookieSessionRefresher -> MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:30` · Level 1 · interface +### SessionRefreshOutcome +> MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionRefreshOutcome.cs:32` · Level 9 · class + +- **What it is**: the result of + [`ICookieSessionRefresher`](#icookiesessionrefresher)`.ValidateOrRefreshAsync` and `.RefreshAsync`: + a [`SessionRefreshStatus`](#sessionrefreshstatus), the token when the status is `Refreshed`, and the + upstream's `Retry-After` hint when it is `Unavailable` (doc comment, + `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionRefreshOutcome.cs:26-31`). +- **Depends on**: [`SessionRefreshStatus`](#sessionrefreshstatus) and + [`SessionTokenResult`](#sessiontokenresult); `TimeSpan` from the BCL. +- **Concept**: a closed result type built only through named factories, so a caller cannot assemble an + inconsistent combination (a `Rejected` outcome carrying a token, say). It is the richer sibling of + the nullable `SessionTokenResult?` that `GetOrRefreshAsync` still returns: that shape cannot tell a + dead session from a transient failure, this one can. [Rubric §29, Resilience, Reliability & Business + Continuity] is the lens, as introduced at [`SessionRefreshStatus`](#sessionrefreshstatus). +- **Walkthrough**: the constructor is private (`:36-41`). `Status` (`:44`), `Session` (`:47`, set only + for `Refreshed`) and `RetryAfter` (`:53`, set only for `Unavailable` when the identity endpoint sent + one) are get-only. The factories are `Refreshed(SessionTokenResult session)` (`:58-59`), `Rejected()` + (`:63`), which returns one cached `RejectedOutcome` instance (`:34`) since it carries no data, and + `Unavailable(TimeSpan? retryAfter = null)` (`:68-69`). +- **Where it's used**: built throughout [`CookieSessionRefresher`](#cookiesessionrefresher) and + consumed by the proxy's `FailRefreshAsync` + (`MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpoint.cs:274-286`): + `Rejected` ends the session, while `Unavailable` keeps the cookies and answers `503` with a + `Retry-After` taken from `outcome.RetryAfter` or a short default + ([ADR-131](https://ivanball.github.io/docs/adr/131-same-origin-api-proxy.html)). -- **What it is**: the "validate-or-refresh over the HttpOnly session cookies" port. One method returns - a currently-valid access token for the request, rotating from the refresh cookie when the access - cookie has expired, or `null` when there is no valid session. -- **Depends on**: `HttpContext` (ASP.NET Core) and [`SessionTokenResult`](#sessiontokenresult). Its - only implementation is [`CookieSessionRefresher`](#cookiesessionrefresher). +### ICookieSessionRefresher +> MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:32` · Level 10 · interface + +- **What it is**: the "validate-or-refresh over the HttpOnly session cookies" port. It returns a + currently-valid access token for the request, rotating from the refresh cookie when the access + cookie has expired, in two shapes (a nullable token, or a + [`SessionRefreshOutcome`](#sessionrefreshoutcome) that separates a dead session from a transient + failure), plus a forced rotation for when an API rejected a still-valid-looking access token. +- **Depends on**: `HttpContext` (ASP.NET Core), [`SessionTokenResult`](#sessiontokenresult), + [`SessionRefreshOutcome`](#sessionrefreshoutcome) and [`SessionRefreshStatus`](#sessionrefreshstatus). + Its only implementation is [`CookieSessionRefresher`](#cookiesessionrefresher). - **Concept introduced, server-side refresh that browser script never sees.** [Rubric §11, Security] assesses where the long-lived credential lives. The type comment - (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:23-29`) is + (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:25-31`) is the contract in prose: if the access cookie's JWT is still valid it is returned as-is; otherwise the refresh cookie is exchanged at the API's `auth/refresh` endpoint server-to-server, so the refresh token never reaches browser JS; the rotated pair is written back as HttpOnly cookies; and the fresh access token is stashed on `HttpContext.Items` so the current request's SSR authentication can read it before the `Set-Cookie` takes effect on the next request. -- **Walkthrough**: `Task GetOrRefreshAsync(HttpContext context, CancellationToken - cancellationToken = default)` (`:36`). The nullable return is the whole vocabulary: a value means - "here is a good access token", `null` means "no session, treat this caller as anonymous". The doc - comment at `:31-35` flags that setting fresh cookies is a side effect of the call. -- **Why it's built this way**: one interface lets the SSR middleware and the `/auth/session/token` - endpoint share a single refresh path, so exactly one type decides validity and exactly one type - rotates ([ADR-022](https://ivanball.github.io/docs/adr/022-browser-session-cookie-auth.html)). It is - also what makes both callers trivially testable against a mock. -- **Where it's used**: injected into +- **Walkthrough**: three members. + `Task GetOrRefreshAsync(HttpContext context, CancellationToken cancellationToken + = default)` (`:39`) keeps the simple vocabulary: a value means "here is a good access token", `null` + means "no session, treat this caller as anonymous"; its doc comment (`:34-38`) flags that setting + fresh cookies is a side effect. `Task ValidateOrRefreshAsync(...)` (`:48`) is + the same operation with the failure kept apart: `Rejected` when there is no refresh cookie or the + identity endpoint refused it, `Unavailable` for 5xx, 429, timeout or network trouble, "so a caller + that clears cookies on failure does so only for a session that is really dead" (`:41-47`). + `Task RefreshAsync(...)` (`:59`) exchanges the refresh cookie even when the + access cookie still looks valid (the API rejected it: revoked, or signed with a rotated key), and is + single-flighted exactly like `GetOrRefreshAsync`, so concurrent callers holding the same refresh + cookie share one rotation (`:50-58`). +- **Why it's built this way**: one interface lets the SSR middleware, the `/auth/session/token` + endpoint and the same-origin proxy share a single refresh path, so exactly one type decides validity + and exactly one type rotates + ([ADR-022](https://ivanball.github.io/docs/adr/022-browser-session-cookie-auth.html), + [ADR-131](https://ivanball.github.io/docs/adr/131-same-origin-api-proxy.html)). Callers that render + anonymously on any failure keep the nullable method; callers that clear cookies use the outcome + methods. It is also what makes every caller testable against a mock. +- **Where it's used**: `GetOrRefreshAsync` by [`CookieSessionRefreshMiddleware`](#cookiesessionrefreshmiddleware) (which runs before - authentication on navigations) and resolved by the `/auth/session/token` handler - (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:51`). - Registered as a singleton at + authentication on navigations), by the `/auth/session/token` handler + (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:66`) and by + [`SessionHandoffEndpoints`](group-15-common-ui-framework.md#sessionhandoffendpoints) + (`MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SessionHandoffEndpoints.cs:31`); + `ValidateOrRefreshAsync` and `RefreshAsync` by + [`SameOriginApiProxyEndpoint`](group-15-common-ui-framework.md#sameoriginapiproxyendpoint) + (`MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpoint.cs:88`, + `:321`, `:358`). Registered as a singleton at `MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:186`. ### CookieSessionRefreshMiddleware @@ -1941,7 +2190,7 @@ live in later groups; this chapter is the engine those endpoints call into. itself cannot double-rotate a token ([ADR-022](https://ivanball.github.io/docs/adr/022-browser-session-cookie-auth.html)). - **Where it's used**: registered on both Blazor Server hosts immediately before `UseAuthentication()`, - `MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:234` (with `UseAuthentication()` on the very next + `MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:246` (with `UseAuthentication()` on the very next statement at `:140`) and `MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:178` (`:180`). Its gating rules are pinned one test per branch in `MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/CookieSessionRefreshMiddlewareTests.cs`: @@ -1953,7 +2202,7 @@ live in later groups; this chapter is the engine those endpoints call into. rather than failing loudly. ### SessionCookieEndpoints -> MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:15` · Level 2 · class +> MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:16` · Level 11 · class - **What it is**: the minimal-API mapper for the three session-cookie routes: `POST` and `DELETE /auth/session-cookie` (seed and clear the HttpOnly cookies at login and logout) and `POST @@ -1961,83 +2210,107 @@ live in later groups; this chapter is the engine those endpoints call into. access token). It also owns the two cookie-name constants and the cross-site guard. - **Depends on**: [`SessionCookieJar`](#sessioncookiejar), [`ICookieSessionRefresher`](#icookiesessionrefresher), - [`SessionCookieRequest`](#sessioncookierequest), [`SessionTokenResponse`](#sessiontokenresponse), and - ASP.NET Core routing plus `Results`. The cookies it writes are read back by - [`CookieTokenReader`](#cookietokenreader). + [`SessionCookieSettings`](#sessioncookiesettings) (via `IOptions`), + [`SessionClaimsToken`](#sessionclaimstoken), [`SessionCookieRequest`](#sessioncookierequest), + [`SessionTokenResponse`](#sessiontokenresponse), and ASP.NET Core routing plus `Results`. The + cookies it writes are read back by [`CookieTokenReader`](#cookietokenreader). - **Concept introduced, the cookie names are the shared contract.** `AccessTokenCookieName = "mmca_auth_access"` and `RefreshTokenCookieName = "mmca_auth_refresh"` - (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:17-18`) + (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:18-19`) are `public const`, and every other type in this feature (the jar, the reader, the refresher) references them instead of a string literal, so the names have exactly one definition. [Rubric §9, API & Contract Design] is the other lens: three tightly-scoped routes, all excluded from OpenAPI - (`ExcludeFromDescription` at `:31` and `:63`) because they are browser plumbing, not public API + (`ExcludeFromDescription` at `:32` and `:78`) because they are browser plumbing, not public API surface. - **Walkthrough**: the mapping method lives inside an `extension(IEndpointRouteBuilder endpoints)` - block (`:20`), the C# extension-member syntax this codebase uses for fluent registration + block (`:21`), the C# extension-member syntax this codebase uses for fluent registration ([primer §2](00-primer.md#2-architectural-styles-this-codebase-commits-to)), so hosts call - `app.MapSessionCookieEndpoints()`. Inside `MapSessionCookieEndpoints` (`:22-68`): a route group for + `app.MapSessionCookieEndpoints()`. Inside `MapSessionCookieEndpoints` (`:23-83`): a route group for `/auth/session-cookie` is created, excluded from description, and explicitly marked - `AllowAnonymous()` (`:30-32`); the comment above it (`:26-29`) spells out why the anonymity has to be + `AllowAnonymous()` (`:31-33`); the comment above it (`:27-30`) spells out why the anonymity has to be declared rather than left implicit: the `POST` seeds the cookie jar at login and the `DELETE` clears it at logout, so both run before or after a session exists, and the default fallback authorization policy (SEC-Common-16) requires an authenticated caller on any endpoint that does not declare - otherwise, which would make sign-in impossible on a Blazor host. The `POST` (`:34-38`) binds a - [`SessionCookieRequest`](#sessioncookierequest), calls `SessionCookieJar.Append` and returns `204`; - the `DELETE` (`:40-44`) calls `SessionCookieJar.Delete` and returns `204`; both `DisableAntiforgery()` - because there is no antiforgery token cookie to validate on these calls. The `/auth/session/token` - `POST` (`:50-65`) first rejects an obvious cross-site request with `403` (`:53-56`), then awaits - `refresher.GetOrRefreshAsync` (`:58`); a `null` result becomes a `401` JSON body - `{ error = "no_session" }` (`:60`), otherwise a [`SessionTokenResponse`](#sessiontokenresponse) is - serialized (`:61`). That route is `AllowAnonymous()` (`:64`) because it authenticates via the cookies - themselves. The private `IsCrossSite` (`:73-75`) inspects the `Sec-Fetch-Site` request header and - treats a missing header as allowed, which the comment at `:72` attributes to older browsers. -- **Why it's built this way**: CSRF is defended in depth rather than by antiforgery tokens. The comment - at `:71-72` spells it out: `POST`-only, `SameSite=Lax` on the cookies (which already blocks - cross-site cookie attachment), and the `Sec-Fetch-Site` check together stop a cross-site page from - driving these endpoints, which is what makes disabling antiforgery safe here + otherwise, which would make sign-in impossible on a Blazor host. The `POST` (`:35-46`) binds a + [`SessionCookieRequest`](#sessioncookierequest) plus `IOptions` and, unless + `ClaimsOnlyBrowserTokens` is set (`:40`), calls `SessionCookieJar.Append` with the configured + `SameSite` (`:42`); either way it returns `204` (`:45`). The comment at `:37-39` explains the + claims-only branch: on a proxied host the server is the only writer of the cookies and the browser + holds nothing worth posting, and answering `204` rather than an error keeps a client written for the + default mode signing in. The `DELETE` (`:48-52`) calls `SessionCookieJar.Delete` with the same + configured `SameSite` (`:50`) and returns `204`; both `DisableAntiforgery()` because there is no + antiforgery token cookie to validate on these calls. The `/auth/session/token` `POST` (`:58-80`) + first rejects an obvious cross-site request with `403` (`:61-64`), then awaits + `refresher.GetOrRefreshAsync` (`:66`); a `null` result becomes a `401` JSON body + `{ error = "no_session" }` (`:69`). Otherwise the browser token is the real access token, or, on a + claims-only host, `SessionClaimsToken.Create(...) ?? string.Empty` (`:73-75`, comment at `:72`), and a + [`SessionTokenResponse`](#sessiontokenresponse) is serialized (`:76`). That route is + `AllowAnonymous()` (`:79`) because it authenticates via the cookies themselves. The private + `IsCrossSite` (`:88-90`) inspects the `Sec-Fetch-Site` request header and treats a missing header as + allowed, which the comment at `:87` attributes to older browsers. +- **Why it's built this way**: CSRF is defended in depth rather than by antiforgery tokens. The comments + at `:57` and `:86-87` spell it out: `POST`-only, a `SameSite` cookie (which already blocks cross-site + cookie attachment; the comments say `Lax`, the default, and the same-origin proxy raises it to + `Strict` through [`SessionCookieSettings`](#sessioncookiesettings)), and the `Sec-Fetch-Site` check + together stop a cross-site page from driving these endpoints, which is what makes disabling + antiforgery safe here ([ADR-022](https://ivanball.github.io/docs/adr/022-browser-session-cookie-auth.html)). [Rubric §11, Security]. The group-level `AllowAnonymous()` is the same defense-in-depth posture applied to authentication instead of CSRF: it is a deliberate, commented opt-out of the fallback policy - (SEC-Common-16) rather than an oversight. + (SEC-Common-16) rather than an oversight. The claims-only branches are what let the same three routes + serve a proxied host without ever handing the browser a credential + ([ADR-131](https://ivanball.github.io/docs/adr/131-same-origin-api-proxy.html)). - **Where it's used**: mapped by both Blazor Server hosts, - `MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:255` and - `MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:194`. The routes are exercised end to end by + `MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:267` and + `MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:261`. The routes are exercised end to end by `MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/SessionCookieEndpointsTests.cs:125`, whose `CreateHostAsync` builds a real pipeline around the mapper; the cases that matter most are the cross-site `403` (`:60`), the no-session `401` (`:91`), and the assertion that a valid session returns - the access token but never the refresh token (`:104`). + the access token but never the refresh token (`:104`). The claims-only branches are covered by + `MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/ClaimsOnlySessionCookieEndpointsTests.cs`: + an unsigned claims copy instead of the credential (`:27`), script-supplied tokens ignored (`:47`), and + the delete using the configured `SameSite` (`:62`). ### SessionCookieJar -> MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieJar.cs:11` · Level 2 · class +> MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieJar.cs:11` · Level 11 · class - **What it is**: the one internal static helper that writes and clears the two HttpOnly auth cookies, - so the endpoints, the server-side refresher, and the SSR middleware all emit identical cookie + so the endpoints, the server-side refresher, and the public cookie store all emit identical cookie options. -- **Depends on**: `CookieOptions`, `HttpContext` and `IWebHostEnvironment` (ASP.NET Core) plus the - cookie-name constants on [`SessionCookieEndpoints`](#sessioncookieendpoints). +- **Depends on**: `CookieOptions`, `HttpContext`, `IWebHostEnvironment` and `SameSiteMode` (ASP.NET + Core) plus the cookie-name constants on [`SessionCookieEndpoints`](#sessioncookieendpoints). The + `SameSite` value its callers pass comes from [`SessionCookieSettings`](#sessioncookiesettings). - **Concept introduced, one place to build cookie options.** [Rubric §11, Security] assesses cookie hardening. Centralizing `BuildOptions` - (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieJar.cs:31-38`) means - every write is `HttpOnly = true` (`:33`), `Secure` outside Development (`:34`, + (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieJar.cs:37-44`) means + every write is `HttpOnly = true` (`:39`), `Secure` outside Development (`:40`, `!environment.IsDevelopment()`, so `http://localhost` dev still works while every deployed - environment forces HTTPS), `SameSite = SameSiteMode.Lax` (`:35`), and `Path = "/"` (`:36`). Drift - between the seed, refresh, and clear paths is structurally impossible because all three call this - method. The conditional `Secure` is the one thing an analyzer objects to, and the suppression carries - its justification inline (`:30` and `:39` bracket a scoped `#pragma warning disable S2092`), which is - the house style for an accepted deviation. + environment forces HTTPS), `SameSite` set to the caller's mode (`:41`), and `Path = "/"` (`:42`). + Drift between the seed, refresh, store and clear paths is structurally impossible because all of + them call this method. The conditional `Secure` is the one thing an analyzer objects to, and the + suppression carries its justification inline (`:36` and `:45` bracket a scoped + `#pragma warning disable S2092`), which is the house style for an accepted deviation. - **Walkthrough**: `Lifetime = TimeSpan.FromDays(7)` (`:14`) is aligned to the refresh-token lifetime - by the comment at `:13`, so a cookie never outlives the credential it carries. `Append` (`:16-21`) - builds options once and writes both cookies with that 7-day `MaxAge`. `Delete` (`:23-28`) rebuilds - the options with `TimeSpan.Zero`, which `:37` turns into a `null` `MaxAge`, and calls - `Cookies.Delete` for both names. + by the comment at `:13`, so a cookie never outlives the credential it carries. `Append` has two + overloads: the four-argument form (`:16-17`) forwards with `SameSiteMode.Lax`, and the five-argument + form (`:19-24`) builds options once with the given `SameSite` and writes both cookies with that 7-day + `MaxAge`. `Delete` mirrors it: the three-argument form (`:26-27`) forwards with `Lax`, and the + four-argument form (`:29-34`) rebuilds the options with `TimeSpan.Zero`, which `:43` turns into a + `null` `MaxAge`, and calls `Cookies.Delete` for both names. - **Why it's built this way**: a delete must send back the same `Path`, `SameSite` and `Secure` attributes as the original write or the browser will not match the cookie and will not clear it. - Sharing `BuildOptions` between `Append` and `Delete` guarantees that - ([ADR-022](https://ivanball.github.io/docs/adr/022-browser-session-cookie-auth.html)). + Sharing `BuildOptions` between `Append` and `Delete`, and having every caller pass the same + configured `SameSite`, guarantees that + ([ADR-022](https://ivanball.github.io/docs/adr/022-browser-session-cookie-auth.html)). The + `Lax`-defaulting overloads keep the long-standing call shape for callers with no settings in hand. - **Where it's used**: [`SessionCookieEndpoints`](#sessioncookieendpoints) (seed at - `SessionCookieEndpoints.cs:36`, clear at `:37`) and + `SessionCookieEndpoints.cs:42`, clear at `:50`), [`CookieSessionRefresher`](#cookiesessionrefresher) (rewrite after rotation, - `CookieSessionRefresher.cs:88`). The attributes it emits are asserted directly by + `CookieSessionRefresher.cs:137`) and [`SessionCookieStore`](#sessioncookiestore) + (`ISessionCookieStore.cs:33` and `:36`), all passing `SessionCookieSettings.SameSite`. Its + `BuildOptions` is also named as the precedent for the culture cookie's conditional `Secure` + suppression (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationExtensions.cs:124`). + The attributes it emits are asserted directly by `MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/SessionCookieJarTests.cs`. ### CookieSessionRefreshMiddlewareExtensions @@ -2057,7 +2330,7 @@ live in later groups; this chapter is the engine those endpoints call into. load-bearing rule in bold: register it immediately **before** `UseAuthentication()` on the Blazor Server (UI.Web) host. - **Where it's used**: the two Blazor Server hosts - (`MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:234`, + (`MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:246`, `MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:178`). Both the null guard and the pipeline wiring are covered directly at `MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/CookieSessionRefreshMiddlewareTests.cs:115` @@ -2099,85 +2372,141 @@ live in later groups; this chapter is the engine those endpoints call into. (`MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:180`), and covered by `MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/CookieTokenReaderTests.cs`. +### SessionCookieStore +> MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/ISessionCookieStore.cs:28` · Level 12 · class + +- **What it is**: the internal implementation of [`ISessionCookieStore`](#isessioncookiestore): a + two-line adapter that forwards to [`SessionCookieJar`](#sessioncookiejar) with the configured + `SameSite`. +- **Depends on**: `IWebHostEnvironment` and `IOptions` (primary constructor, + `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/ISessionCookieStore.cs:28-30`), + [`SessionCookieSettings`](#sessioncookiesettings) and [`SessionCookieJar`](#sessioncookiejar). +- **Concept**: the adapter half of the public-port-over-internal-helper split introduced at + [`ISessionCookieStore`](#isessioncookiestore). It adds no behavior of its own, which is the point: + every cookie option still comes from the jar's single `BuildOptions`. +- **Walkthrough**: `Write` (`:32-33`) calls `SessionCookieJar.Append(context, accessToken, + refreshToken, environment, settings.Value.SameSite)`; `Clear` (`:35-36`) calls + `SessionCookieJar.Delete(context, environment, settings.Value.SameSite)`. Reading `settings.Value` on + each call means writes and deletes always agree on `SameSite`. +- **Why it's built this way**: `internal sealed`, so consumers outside `MMCA.Common.API` depend on the + interface and the jar stays hidden; the one-line summary at `:27` names it "the + `ISessionCookieStore` over `SessionCookieJar`". +- **Where it's used**: registered as the singleton `ISessionCookieStore` by + `AddServerAuthSessionCookie` + (`MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:191`); see + [`ISessionCookieStore`](#isessioncookiestore) for the proxy types that consume it. + ### CookieSessionRefresher -> MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:52` · Level 4 · class +> MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:74` · Level 13 · class - **What it is**: the singleton implementation of [`ICookieSessionRefresher`](#icookiesessionrefresher). It validates the access cookie's JWT locally - and, when that fails, exchanges the refresh cookie at the API's `auth/refresh` endpoint - server-to-server, writes the rotated pair back as cookies, and single-flights concurrent refreshes so - a burst of requests rotates the token only once. -- **Depends on**: `IHttpClientFactory`, `IMemoryCache`, `IWebHostEnvironment` and - `ILogger` (primary constructor, - `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:52-56`); - [`KeyedSemaphoreStripe`](#keyedsemaphorestripe) (`:62`); [`SessionCookieJar`](#sessioncookiejar); + and, when that fails (or when a caller forces it), exchanges the refresh cookie at the API's + `auth/refresh` endpoint server-to-server, writes the rotated pair back as cookies, single-flights + concurrent refreshes so a burst of requests rotates the token only once, and classifies every + failure as a dead session or a transient one. +- **Depends on**: `IHttpClientFactory`, `IMemoryCache`, `IWebHostEnvironment`, + `ILogger`, `TimeProvider` and `IOptions` (primary + constructor, + `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:74-80`); + [`KeyedSemaphoreStripe`](#keyedsemaphorestripe) (`:87`); [`SessionCookieJar`](#sessioncookiejar); + [`SessionCookieSettings`](#sessioncookiesettings) for the cookie `SameSite`; + [`SessionRefreshOutcome`](#sessionrefreshoutcome) and [`SessionRefreshStatus`](#sessionrefreshstatus); [`CookieTokenReader`](#cookietokenreader) for the Items key; [`SessionCookieEndpoints`](#sessioncookieendpoints) for the cookie names; and the [`AuthenticationResponse`](#authenticationresponse) / [`RefreshTokenRequest`](#refreshtokenrequest) contracts from `MMCA.Common.Shared.Auth`. It reads token expiry with - `System.IdentityModel.Tokens.Jwt`. + `System.IdentityModel.Tokens.Jwt` and names `Polly.ExecutionRejectedException` in its catch filter. - **Concept introduced, single-flight refresh under a thundering herd.** [Rubric §12, Performance & Scalability] assesses behavior under concurrent load. When an access token expires, many queued navigations can arrive at once; rotating for each would burn the refresh token repeatedly and log the - user out. The type comment (`:39-50`) states the design: the lock is a **striped** + user out. The type comment (`:61-73`) states the design: the lock is a **striped** [`KeyedSemaphoreStripe`](#keyedsemaphorestripe) keyed by refresh token rather than one process-wide semaphore, because the lock is held across an outbound HTTP call and a single semaphore would serialize every unrelated user's cold navigation behind whichever refresh happened to be in flight. Two unrelated tokens can still land on the same one of the stripe's 256 lanes (`MMCA.Common/Source/Core/MMCA.Common.Shared/Concurrency/KeyedSemaphoreStripe.cs:25`), which the comment calls out as harmless precisely because the rotation-grace cache is re-checked per token - after acquiring. Alongside the lock, a 10-second `RotationGrace` (`:60`) caches the rotated pair - keyed by the OLD refresh token (`:144`), so a slightly-late sibling carrying the same expired pair + after acquiring. Alongside the lock, a 10-second `RotationGrace` (`:85`) caches the rotated pair + keyed by the OLD refresh token (`:198`), so a slightly-late sibling carrying the same expired pair gets the same result instead of rotating again. -- **Walkthrough**: `GetOrRefreshAsync` (`:64-93`) reads the access cookie (`:68`) and, if - `TryReadValidExpiry` passes, returns it untouched (`:69-72`). Otherwise it reads the refresh cookie - and returns `null` when there is none (`:74-78`). It calls `RefreshAsync` (`:80`), treats a missing or - blank access token as failure (`:81-84`), writes the rotated pair with `SessionCookieJar.Append` - (`:87`), stashes the fresh access token on `context.Items[CookieTokenReader.FreshAccessTokenItemKey]` - (`:91`, with the reason spelled out at `:89-90`), and returns the new - [`SessionTokenResult`](#sessiontokenresult) (`:92`). `RefreshAsync` (`:95-111`) is textbook - double-checked locking: a cache hit returns immediately (`:97-100`), otherwise it acquires the stripe - for this token (`:102`) and re-checks the cache before doing any work (`:105-108`). `CallRefreshAsync` - (`:113-152`) creates the named client (`:115`), POSTs a - [`RefreshTokenRequest`](#refreshtokenrequest) to the relative `auth/refresh` URI with - `CancellationToken.None` (`:127-130`) so that once the lock is held the rotation completes and writes - its cookies even if the triggering request was aborted (the reason is at `:125-126`), bails on a - non-success status (`:132-135`) or an empty access token (`:138-141`), and caches the - [`AuthenticationResponse`](#authenticationresponse) under the old refresh token for `RotationGrace` - (`:144`). `TryReadValidExpiry` (`:154-183`) rejects a blank token, refuses anything - `JwtSecurityTokenHandler` cannot read (`:162-166`), treats the token as expired when - `jwt.ValidTo <= DateTime.UtcNow + ClockSkew` (`:171`, with `ClockSkew` a 30-second margin at `:59`), - and swallows only `ArgumentException`/`FormatException` (`:179-182`). `CacheKey` (`:189`) builds the +- **Walkthrough**: `GetOrRefreshAsync` (`:89-90`) is a projection: it awaits `ValidateOrRefreshAsync` + and returns its `.Session`, so every failure becomes `null`. `ValidateOrRefreshAsync` (`:92-103`) + reads the access cookie (`:96`) and, if `TryReadValidExpiry` passes, returns a `Refreshed` outcome + around it untouched (`:97-100`); otherwise it delegates to `RefreshFromCookiesAsync` (`:102`). The + public `RefreshAsync` (`:105-111`) skips the validity check and goes straight to + `RefreshFromCookiesAsync` (`:110`), which is the forced rotation the interface promises. + `RefreshFromCookiesAsync` (`:123-143`) returns `Rejected` when there is no refresh cookie + (`:126-129`), calls the private `RefreshAsync` (`:131`) and passes its failure through + (`:132-135`), writes the rotated pair with `SessionCookieJar.Append` and the configured `SameSite` + (`:137`), stashes the fresh access token on + `context.Items[CookieTokenReader.FreshAccessTokenItemKey]` (`:141`, with the reason at `:139-140`), + and returns a `Refreshed` outcome around the new [`SessionTokenResult`](#sessiontokenresult) + (`:142`). The private `RefreshAsync` (`:145-162`) is textbook double-checked locking: a cache hit + returns immediately (`:148-151`), otherwise it acquires the stripe for this token (`:153`) and + re-checks the cache before doing any work (`:156-159`). `CallRefreshAsync` (`:164-210`) creates the + named client (`:166`), POSTs a [`RefreshTokenRequest`](#refreshtokenrequest) to the relative + `auth/refresh` URI with `CancellationToken.None` (`:179-182`) so that once the lock is held the + rotation completes and writes its cookies even if the triggering request was aborted (the reason is + at `:177-178`). A non-success status goes through `ClassifyFailure` (`:184-189`): `Rejected`, or + `Unavailable` carrying the parsed `Retry-After`. An empty access token is `Unavailable` (`:192-195`). + Success caches the [`AuthenticationResponse`](#authenticationresponse) under the old refresh token + for `RotationGrace` (`:198`). `ClassifyFailure` (`:118-121`) is `internal static`: 400, 401 and 403 + are the identity endpoint refusing the token, so `Rejected`; anything else (5xx, 429, 408, a 404 + from a misrouted gateway) says nothing about the token, so `Unavailable` (doc `:113-117`). + `ReadRetryAfter` (`:212-231`) accepts either form of the header, a delta (`:219-222`) or a date + measured against the injected `TimeProvider` (`:224-228`), and clamps a negative result to zero. + `TryReadValidExpiry` (`:233-262`) rejects a blank token, refuses anything `JwtSecurityTokenHandler` + cannot read (`:242-245`), treats the token as expired when + `jwt.ValidTo <= timeProvider.GetUtcNow().UtcDateTime + ClockSkew` (`:250`, with `ClockSkew` a + 30-second margin at `:84`), and swallows only `ArgumentException`/`FormatException` (`:258-261`); it + is an instance method because it reads the injected clock. `CacheKey` (`:268`) builds the `mmca:session-refresh:{refreshToken}` string that is both the cache key and the striping key; the - comment at `:185-188` explains it is `internal` rather than `private` so a concurrency test can pick + comment at `:264-267` explains it is `internal` rather than `private` so a concurrency test can pick two refresh tokens that do not collide on a stripe, which is a nice example of a testability - affordance that costs nothing at runtime. [Rubric §14, Testability]. -- **Concept, an SSR-safe failure mode.** [Rubric §29, Resilience & Business Continuity] and [Rubric - §13, Observability & Operability] apply to the outbound call. `CallRefreshAsync` wraps the POST in a - `try` whose filter narrows to `HttpRequestException`, `OperationCanceledException`, `JsonException` - and `NotSupportedException` (`:147`), logs one warning through the source-generated - `LogRefreshCallFailed` (`:149`, declared with `[LoggerMessage]` at `:191-192`, which is why the class - is `partial` at `:51`), and returns `null`. The comment at `:117-122` gives the reasoning: this code - runs during SSR, so an escaping exception would turn a signed-in user's navigation into a `500` - instead of an anonymous render. The failure is deliberately not cached (only a successful rotation - reaches `cache.Set` at `:144`), so the next navigation retries, and a missing `BaseAddress` raises - `InvalidOperationException` and is left to propagate because that is a host misconfiguration rather - than a runtime condition. + affordance that costs nothing at runtime. [Rubric §14, Testability]: the injected `TimeProvider` is + the same kind of affordance, letting tests move the clock instead of minting tokens around the wall + clock. +- **Concept, an SSR-safe failure mode that does not sign users out.** [Rubric §29, Resilience, + Reliability & Business Continuity] and [Rubric §13, Observability & Operability] apply to the + outbound call. `CallRefreshAsync` wraps the POST in a `try` whose filter narrows to + `HttpRequestException`, `OperationCanceledException`, `JsonException`, `NotSupportedException` and + `Polly.ExecutionRejectedException` (`:204-205`); the comment at `:201-203` explains the last one: the + resilience pipeline's own refusals (a timed-out attempt, an open circuit, a rate-limited call) throw + it, for example `TimeoutRejectedException` with the gateway down, which is neither transport + exception. The catch logs one warning through the source-generated `LogRefreshCallFailed` (`:207`, + declared with `[LoggerMessage]` at `:270-271`, which is why the class is `partial` at `:74`) and + returns `Unavailable` (`:208`). The comment at `:168-174` gives the reasoning: this code runs during + SSR, so an escaping exception would turn a signed-in user's navigation into a `500` instead of an + anonymous render; it is `Unavailable`, not `Rejected`, because nothing said the refresh token is + dead, so a caller that clears cookies must keep them. The failure is deliberately not cached (only a + successful rotation reaches `cache.Set` at `:198`), so the next navigation retries, and a missing + `BaseAddress` raises `InvalidOperationException` and is left to propagate because that is a host + misconfiguration rather than a runtime condition. - **Why it's built this way**: keying the grace cache by the OLD token is what lets a slightly-late sibling find the already-rotated pair, and striping the lock keeps one user's slow refresh from blocking everyone else's cold navigation. The server-to-server call is what keeps the refresh token off browser JS ([ADR-022](https://ivanball.github.io/docs/adr/022-browser-session-cookie-auth.html)). - [Rubric §11, Security]. + [Rubric §11, Security]. Separating `Rejected` from `Unavailable` is what lets the same-origin proxy + clear cookies only for a session that is really over and answer a transient identity-endpoint fault + with `503` plus `Retry-After` + ([ADR-131](https://ivanball.github.io/docs/adr/131-same-origin-api-proxy.html)). - **Where it's used**: resolved as [`ICookieSessionRefresher`](#icookiesessionrefresher) by - [`CookieSessionRefreshMiddleware`](#cookiesessionrefreshmiddleware) and by the - `/auth/session/token` endpoint. Its named `HttpClient`, `RefreshClientName = - "SessionCookieRefreshClient"` (`:57`), is configured with the API base address in - `AddServerAuthSessionCookie` + [`CookieSessionRefreshMiddleware`](#cookiesessionrefreshmiddleware), by the `/auth/session/token` + endpoint, and by the same-origin proxy + ([`SameOriginApiProxyEndpoint`](group-15-common-ui-framework.md#sameoriginapiproxyendpoint), + [`SessionHandoffEndpoints`](group-15-common-ui-framework.md#sessionhandoffendpoints)). Its named + `HttpClient`, `RefreshClientName = "SessionCookieRefreshClient"` (`:82`), is configured with the API + base address in `AddServerAuthSessionCookie` (`MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:182-183`), which also - registers the refresher as a singleton (`:171-172`) with an inline note that a shared instance across - requests is what makes single-flight work at all. The validate, rotate, grace-cache and failure paths - are covered by - `MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs`. + registers the refresher as a singleton (`:186`) with an inline note (`:185`) that a shared instance + across requests is what makes single-flight work at all, and registers `TimeProvider.System` with + `TryAdd` (`:196`) because a Blazor Web host calling only this method has no other clock registration + (comment `:193-195`). The validate, rotate, grace-cache and failure paths are covered by + `MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs`, + including refusal as `Rejected` (`:327`), undecidable statuses as `Unavailable` with no cookies + written (`:354`), resilience-pipeline rejections (`:396`), both `Retry-After` forms (`:420`, `:436`) + and expiry judged against the injected clock (`:61`). ### SessionCookieAuthenticationHandler > MMCA.Common.API · `MMCA.Common.API.SessionCookies` · `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieAuthenticationHandler.cs:24` · Level 4 · class @@ -2221,7 +2550,7 @@ live in later groups; this chapter is the engine those endpoints call into. clock through the base handler's `TimeProvider` rather than `DateTime.UtcNow` keeps the expiry check on the same injectable clock as the rest of the auth stack and its tests. [Rubric §14, Testability]. - **Where it's used**: registered as the `SessionCookie` scheme on both Blazor Server hosts, - `MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:102-103` and + `MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:103-104` and `MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:111-112`. Covered directly by `MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/SessionCookieAuthenticationHandlerTests.cs`, including the fresh-token-from-Items path (`:95`, which stashes the token under @@ -2248,29 +2577,19 @@ live in later groups; this chapter is the engine those endpoints call into. explicit `null` arguments are named, so the call site says what it is skipping. The doc comment (`:94-97`) directs callers to use it after `AddAuthentication(SessionCookieAuthenticationHandler.SchemeName)`. -- **Where it's used**: `MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:103` and +- **Where it's used**: `MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:104` and `MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:112`, chained onto the host's `AddAuthentication(SessionCookieAuthenticationHandler.SchemeName)` call on the preceding line. -### IssuedSession -> MMCA.Common.Application · `MMCA.Common.Application.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:924` · Level 0 · record (private sealed, nested) - -- **What it is**: the two-field result of opening or rotating a refresh session: the plaintext refresh token the client is handed, and the id of the session row it belongs to. It is a `private sealed record` nested inside [AuthenticationServiceBase](#authenticationservicebasetuser) (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:924`), not part of the framework's public surface. -- **Depends on**: nothing beyond the BCL (`string`, `Guid`). It is produced and consumed entirely inside its declaring class. -- **Concept introduced: the plaintext token exists in exactly one place, and it is a return value.** `[Rubric §11, Security]` assesses how a bearer credential is stored. [RefreshSession](#refreshsession) rows keep only a digest (`RefreshSession.HashToken`, used at `:349` and `:593` to *look up* by hash), so once a session is persisted the raw token cannot be recovered from the store at all. The type comment says exactly this (`:753-757`): the plaintext "exists nowhere else". Modelling the hand-off as a small record rather than an out-parameter or a tuple is what keeps that fact readable: every method that can produce a token returns `Result`, so the compiler shows you the complete list of places raw token material is in flight. `[Rubric §15, Best Practices & Code Quality]` also applies: a positional record gives value equality and immutability for free, and `Guid SessionId` names what would otherwise be an anonymous second tuple element. -- **Walkthrough**: one positional declaration, `IssuedSession(string RefreshToken, Guid SessionId)` (`:758`). `RefreshToken` is what goes back to the caller in the [AuthenticationResponse](#authenticationresponse); `SessionId` is what the access token's `sid` claim carries, which is why the session must be created before the token is minted (`:481-483`). -- **Why it's built this way**: the pairing is load-bearing rather than incidental. A caller that received only the token could not stamp `sid`, and a caller that received only the id could not answer the client. Returning both together removes the ordering mistake where a token is minted for a session that does not exist yet. -- **Where it's used**: returned by `OpenSessionAsync` (`:620`, constructed at `:645`) and `RotateAsync` (`:659`, constructed at `:698`); unwrapped by `IssueTokensAsync` (`:492-493`) and by `RefreshTokenAsync` (`:327-328`), each of which reads `SessionId` to mint the access token and `RefreshToken` to fill the response. - ### PasswordResetSettings > MMCA.Common.Application · `MMCA.Common.Application.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/PasswordResetSettings.cs:10` · Level 0 · class (sealed) - **What it is**: the bound options object for the forgot-password workflow: where the reset page lives, how long a token stays redeemable, how many wrong guesses a token tolerates, and how often one address may ask for a reset (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/PasswordResetSettings.cs:6-9`). - **Depends on**: `System.ComponentModel.DataAnnotations` for the range attributes and `System.Diagnostics.CodeAnalysis` for one scoped suppression (BCL, `:1-2`). Nothing first-party. Read by the implementation behind [IPasswordResetTokenService](#ipasswordresettokenservice) and by the shared [ForgotPasswordHandlerBase](group-14-module-system-composition.md#forgotpasswordhandlerbasetuser-tcommand). -- **Concept: validated options whose defaults keep an unconfigured host bootable.** `[Rubric §17, DevOps & Deployment]` assesses whether policy knobs are configuration rather than constants buried in a handler, and `[Rubric §11, Security]` assesses whether the security-relevant knobs (token lifetime, attempt cap, request throttle) are bounded rather than free-form. Every numeric member carries a `[Range]` attribute, and the host binds the section with `ValidateDataAnnotations().ValidateOnStart()` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:154-157`), so a typo such as `TokenLifetimeMinutes: 0` fails the host at startup instead of silently issuing tokens that are already expired. -- **Walkthrough**: `const string SectionName = "PasswordReset"` (`:13`) names the configuration section the host binds. `ResetUrl` (`:25`) defaults to `string.Empty` and is **deliberately not** `[Required]`: the doc comment (`:15-20`) records that a host which has not configured a UI base must still boot, and an empty value degrades to a token-only email the user pastes into the reset page by hand. That degradation is visible in the caller, which emits the bare token when the URL is blank and otherwise appends `?email=...&token=...` (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ForgotPassword/ForgotPasswordHandlerBase.cs:146-148`). The property carries a scoped `CA1056` suppression (`:21-24`) explaining why it is a `string` and not a `System.Uri`: it is bound from `PasswordReset__ResetUrl`, concatenated with a query string, and the empty default is not a valid `Uri`. The four numeric knobs follow: `TokenLifetimeMinutes` (`:29`, `[Range(1, 1440)]`, default 30), `MaxValidationAttempts` (`:36`, `[Range(1, 100)]`, default 5), `MaxRequestsPerEmail` (`:40`, `[Range(1, 100)]`, default 3), and `RequestWindowMinutes` (`:44`, `[Range(1, 1440)]`, default 60). All five members are `init`-only, so the bound instance is immutable afterwards. +- **Concept: validated options whose defaults keep an unconfigured host bootable.** `[Rubric §17, DevOps & Deployment]` assesses whether policy knobs are configuration rather than constants buried in a handler, and `[Rubric §11, Security]` assesses whether the security-relevant knobs (token lifetime, attempt cap, request throttle) are bounded rather than free-form. Every numeric member carries a `[Range]` attribute, and the host binds the section with `ValidateDataAnnotations().ValidateOnStart()` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:160-163`), so a typo such as `TokenLifetimeMinutes: 0` fails the host at startup instead of silently issuing tokens that are already expired. +- **Walkthrough**: `const string SectionName = "PasswordReset"` (`:13`) names the configuration section the host binds. `ResetUrl` (`:25`) defaults to `string.Empty` and is **deliberately not** `[Required]`: the doc comment (`:15-20`) records that a host which has not configured a UI base must still boot, and an empty value degrades to a token-only email the user pastes into the reset page by hand. That degradation is visible in the caller, which emits the bare token when the URL is blank and otherwise appends the escaped address and token as a URL **fragment**, `#email=...&token=...` (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ForgotPassword/ForgotPasswordHandlerBase.cs:153-155`). A fragment is never sent to the server in the HTTP request, which is the reason the doc comment gives (`:16-17`): the live token never reaches a server or proxy access log the way a query string would. The property carries a scoped `CA1056` suppression (`:21-24`) explaining why it is a `string` and not a `System.Uri`: it is bound from `PasswordReset__ResetUrl`, concatenated with a URL fragment, and the empty default is not a valid `Uri`. The four numeric knobs follow: `TokenLifetimeMinutes` (`:29`, `[Range(1, 1440)]`, default 30), `MaxValidationAttempts` (`:36`, `[Range(1, 100)]`, default 5), `MaxRequestsPerEmail` (`:40`, `[Range(1, 100)]`, default 3), and `RequestWindowMinutes` (`:44`, `[Range(1, 1440)]`, default 60). All five members are `init`-only, so the bound instance is immutable afterwards. - **Why it's built this way**: the defaults are a working policy on their own, so adopting the feature costs a registration call and no configuration at all, while the `[Range]` bounds plus `ValidateOnStart` make the one genuinely dangerous class of misconfiguration (a zero or negative lifetime, an unbounded attempt cap) unreachable. The decision to keep the whole reset credential in configuration and cache rather than in schema is [ADR-091](https://ivanball.github.io/docs/adr/091-cache-backed-password-reset.html). -- **Where it's used**: bound in the framework's Infrastructure registration (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:154-157`, immediately before the token service that reads it is registered at `:141`); consumed by [PasswordResetTokenService](#passwordresettokenservice) as a snapshot field (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:32`) for the request window, the throttle ceiling, the token lifetime, and the attempt cap; and exposed to the shared forgot-password handler as a protected `Settings` property (`ForgotPasswordHandlerBase.cs:49`) that states the expiry in the email body (`:125`) and renders the link (`:145-147`). +- **Where it's used**: bound in the framework's Infrastructure registration (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:160-163`, immediately before the token service that reads it is registered at `:164`); consumed by [PasswordResetTokenService](#passwordresettokenservice) as a snapshot field (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:45`) for the request window, the throttle ceiling, the token lifetime, and the attempt cap; and exposed to the shared forgot-password handler as a protected `Settings` property (`ForgotPasswordHandlerBase.cs:49`) that states the expiry in the email body (`:126`) and renders the fragment link (`:153-155`). ### RefreshSessionSettings > MMCA.Common.Application · `MMCA.Common.Application.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/RefreshSessionSettings.cs:9` · Level 0 · class (sealed) @@ -2280,13 +2599,13 @@ live in later groups; this chapter is the engine those endpoints call into. - **Concept introduced: a flag that places a table rather than switching a feature.** `[Rubric §8, Data Architecture]` assesses whether each table has exactly one owning database, and `[Rubric §7, Microservices Readiness]` assesses whether that ownership survives splitting a modular host into services. The doc comment on `Enabled` states the distinction precisely (`:20-23`): the flag "gates the model, not the workflow". The workflow always issues, rotates and revokes sessions; `Enabled` decides which host maps the table, runs its migrations, and sweeps it. In a modular host the service that owns identity sets it to `true` and every other service leaves it `false`, which is what keeps one table in one database instead of one per service. The `Scheduler:Enabled` precedent is named in the same comment as the pattern being followed. - **Walkthrough**: six members, all `init`-only. - `const string SectionName = "RefreshSessions"` (`:12`) names the configuration section. - - `Enabled` (`:25`) defaults to `false`. Two places read it: the model gate in [ApplicationDbContext](group-07-persistence-ef-core.md#applicationdbcontext), which enables the table only when the flag is set **and** the context instance's physical source name equals `DataSourceName` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:331-334`), and the hosted-service registration, which starts the retention sweep only when the flag is set (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:171-175`, whose comment explains that registering it unconditionally would start a sweep in every service of a modular host, all but one of which has no table to sweep). + - `Enabled` (`:25`) defaults to `false`. Two places read it: the model gate in [ApplicationDbContext](group-07-persistence-ef-core.md#applicationdbcontext), which enables the table only when the flag is set **and** the context instance's physical source name equals `DataSourceName` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:336-339`), and the hosted-service registration, which starts the retention sweep only when the flag is set (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:181-185`, whose comment explains that registering it unconditionally would start a sweep in every service of a modular host, all but one of which has no table to sweep). - `MaxActiveSessionsPerUser` (`:35`, `[Range(1, 1000)]`, default 10) caps live sessions per user. The comment (`:28-33`) records the deliberate behavior at the ceiling: signing in on device number cap + 1 **revokes the oldest live session rather than refusing the login**, so the table is bounded without a legitimate sign-in ever failing. - `DataSourceName` (`:52`, `[MinLength(1)]`, default `"Default"`) names the logical data source whose database holds the table. The comment (`:37-49`) is worth reading in full: the value answers two questions that must agree, which context *maps* the table and which context the shipped [IRefreshSessionStore](#irefreshsessionstore) reads and writes through, and naming a source that does not exist fails loudly on the first session query rather than reading the wrong database. It is ignored for routing when the consumer ships its own entity configuration for the session entity. - `RetentionDays` (`:72`, `[Range(0, 3650)]`, default 30) is measured from the instant a session died (its revocation, or its expiry when never revoked), so a live session is never a sweep candidate. The comment (`:59-66`) states the constraint that makes the number security-relevant: retention **bounds reuse detection**, because BR-206 catches a replayed refresh token by landing on its revoked row, and a swept row turns that replay into an unknown token that fails alone instead of revoking the family. Thirty days sits well past the seven-day refresh-token lifetime for exactly that reason. `0` keeps every row forever (`:67-69`). - `CleanupIntervalHours` (`:80`, `[Range(1, 168)]`, default 6) is how often the sweep runs, ignored when `RetentionDays` is `0`, and matches the outbox sweep cadence because the deadline is measured in days (`:74-77`). -- **Why it's built this way**: the same `AddOptions(...).Bind(...).ValidateDataAnnotations().ValidateOnStart()` treatment as every other settings class in the framework (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:162-165`) means an out-of-range cap or a negative retention window fails the host at startup, not at the first login. Defaulting `Enabled` to `false` is the safe direction for a multi-service host: a service that never opts in never grows a table it does not own. -- **Where it's used**: bound at `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:162-165`; injected as `IOptions` into [AuthenticationServiceBase](#authenticationservicebasetuser) (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:61`, read for the cap at `:115`), into [EFRefreshSessionStore](group-07-persistence-ef-core.md#efrefreshsessionstore) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/EFRefreshSessionStore.cs:34`), and into [RefreshSessionCleanupService](group-07-persistence-ef-core.md#refreshsessioncleanupservice) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/RefreshSessionCleanupService.cs:51`, snapshotted at `:54`). The design-time context helper supplies an instance so `dotnet ef` can build a model that includes the table (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextHelper.cs:173-174`). Each app's Identity service takes it as a required constructor dependency and passes it through, for example `MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:57`. +- **Why it's built this way**: the same `AddOptions(...).Bind(...).ValidateDataAnnotations().ValidateOnStart()` treatment as every other settings class in the framework (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:168-171`) means an out-of-range cap or a negative retention window fails the host at startup, not at the first login. Defaulting `Enabled` to `false` is the safe direction for a multi-service host: a service that never opts in never grows a table it does not own. +- **Where it's used**: bound at `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:168-171`; injected as `IOptions` into [AuthenticationServiceBase](#authenticationservicebasetuser) (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:61`, read for the cap at `:115`), into [EFRefreshSessionStore](group-07-persistence-ef-core.md#efrefreshsessionstore) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/EFRefreshSessionStore.cs:34`), and into [RefreshSessionCleanupService](group-07-persistence-ef-core.md#refreshsessioncleanupservice) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/RefreshSessionCleanupService.cs:51`, snapshotted at `:54`). The design-time context helper supplies an instance so `dotnet ef` can build a model that includes the table (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextHelper.cs:173-174`). Each app's Identity service takes it as a required constructor dependency and passes it through, for example `MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:57`. ### UserAdministrationQuery > MMCA.Common.Application · `MMCA.Common.Application.Auth.Administration` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Administration/IUserAdministrationService.cs:79` · Level 0 · record struct @@ -2298,21 +2617,6 @@ live in later groups; this chapter is the engine those endpoints call into. - **Why it's built this way**: a `record struct` rather than a class avoids a heap allocation for a value that is built once, passed to one call, and discarded, and the two optional filters default to `null` so a caller that wants an unfiltered page supplies only the paging pair. - **Where it's used**: built by `UsersAdminControllerBase` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Administration/UsersAdminControllerBase.cs`) from the incoming query string and passed to [IUserAdministrationService](#iuseradministrationservicetuserdto)`.ListAsync`; consumed by each app's `UserAdministrationService`, for example `MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/Administration/UserAdministrationService.cs`. -### SessionStampingTokenService -> MMCA.Common.Application · `MMCA.Common.Application.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:936` · Level 1 · class (private sealed, nested) - -- **What it is**: a pass-through [ITokenService](#itokenservice) that appends the current refresh session's `sid` claim and, when a second factor verified this request, an `mfa` claim to every access token minted while it is armed, and behaves as the plain inner service the rest of the time. It is a `private sealed class` nested inside [AuthenticationServiceBase](#authenticationservicebasetuser) (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:936`). -- **Depends on**: [ITokenService](#itokenservice) (the contract it implements and the inner instance it wraps, `:106`), [AuthClaimTypes](#authclaimtypes) for the `sid` and `mfa` claim names (`:142`, `:147`), and the BCL (`System.Security.Claims`, `System.Globalization.CultureInfo`). -- **Concept introduced: a decorator used to make a new claim additive.** `[Rubric §2, Design Patterns]` assesses idiomatic pattern use, and this is the Decorator pattern applied to a very specific compatibility problem. Access tokens now need to name the session they belong to (and, when relevant, the second factor that satisfied a step-up), but the claim set is produced by the app's own `CreateAccessToken` hook. The obvious fix, adding parameters to that hook, is a compile break in every consumer for claims the app has no decision to make about. Wrapping the token service instead means the base arms the wrapper around the hook call and the claims appear in tokens minted by subclasses that were never edited. `[Rubric §15, Best Practices & Code Quality]` is the payoff: an additive protocol change with a zero-line consumer diff, twice over. -- **Walkthrough**: a primary constructor takes the `inner` service (`:106`). - - `Guid? CurrentSessionId { get; set; }` (`:109`) is the session arming switch: a session id stamps, `null` mints unchanged. - - `string? CurrentMultiFactorMethod { get; set; }` (`:115`) is the second-factor arming switch, set only when a second factor really verified for this request. The remarks on the caller explain why plain mutable properties are safe here: the authentication service is resolved per request (scoped, like the unit of work it saves through) and one request issues one token pair at a time. - - `AccessTokenLifetime` (`:118`) and `RefreshTokenLifetime` (`:121`) forward straight to `inner`, so the lifetime the base reports is still the JWT settings' value. - - `GenerateAccessToken(...)` (`:124-151`) is the only member with behavior. When neither switch is armed it delegates verbatim (`:131-134`). Otherwise it copies the app's `additionalClaims` into a new `List` (`:136`, so the caller's sequence is never mutated), then independently appends `AuthClaimTypes.SessionId` formatted as `sessionId.ToString("D", CultureInfo.InvariantCulture)` when `CurrentSessionId` is set (`:138-143`, the `"D"` format being the canonical hyphenated Guid form `ClaimsPrincipalExtensions.FindSessionId` parses back, see [ClaimsPrincipalExtensions](#claimsprincipalextensions)) and `AuthClaimTypes.MultiFactor` set to the raw method string when `CurrentMultiFactorMethod` is set (`:145-148`), before delegating with the extended list (`:150`). The two claims are independent: a token can carry either, both, or neither. - - `GenerateRefreshToken()` (`:154`) and `GetPrincipalFromExpiredToken(string token)` (`:157-158`) are plain forwards. -- **Why it's built this way**: putting the stamping behind an `ITokenService` rather than inside the base's own method keeps the app hook's signature and semantics untouched while still guaranteeing both claims on the tokens the framework's own flows mint. The escape hatch still applies: an app that mints from its own injected `ITokenService` reference produces a valid token with neither claim, and can restore them by overriding `CreateAccessTokenForSession`. -- **Where it's used**: constructed once per authentication service instance (`:93`), surfaced to subclasses as the protected `TokenService` property (`:120`, whose remarks state that minting through the property is what puts `sid` on the token), and armed and disarmed around the hook call in `CreateAccessTokenForSession` (`:618-631`, with the `finally` guaranteeing both switches disarm even when the hook throws). `CurrentMultiFactorMethod` is set from the base's `_multiFactorMethod` field, armed in `LoginAsync` around `IssueTokensAsync` and in `RefreshTokenAsync` around the successor mint (see [AuthenticationServiceBase](#authenticationservicebasetuser)). - ### UnconfiguredPermissionRegistry > MMCA.Common.Application · `MMCA.Common.Application.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/UnconfiguredPermissionRegistry.cs:20` · Level 1 · class (internal sealed partial) @@ -2342,7 +2646,7 @@ live in later groups; this chapter is the engine those endpoints call into. All five take a `CancellationToken` with a `default` argument, per convention. - **Why it's built this way**: keeping the protection policy behind an interface lets the shared authentication workflow compose it in while the concrete cache mechanics stay in the implementation; the null-IP skip keeps the limiter from becoming an availability hazard ([ADR-029](https://ivanball.github.io/docs/adr/029-authentication-brute-force-protection.html)). -- **Where it's used**: injected into [AuthenticationServiceBase](#authenticationservicebasetuser) (constructor parameter at `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:78`), which calls all five across its login and registration flows: `CheckLockoutAsync` (`:131`), `IncrementFailedAttemptsAsync` on both the unknown-email and wrong-password branches (`:146`, `:161`), `ResetFailedAttemptsAsync` on success (`:178`), `CheckRegistrationRateLimitAsync` (`:197`) and `IncrementRegistrationCountAsync` (`:256`). The concrete, cache-backed [LoginProtectionService](#loginprotectionservice) (tuned by [LoginProtectionSettings](#loginprotectionsettings), bound at `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:148-151`) implements it, and the framework registers that pairing at `:135`. +- **Where it's used**: injected into [AuthenticationServiceBase](#authenticationservicebasetuser) (constructor parameter at `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:66`), which calls all five across its login and registration flows: `CheckLockoutAsync` (`:131`), `IncrementFailedAttemptsAsync` on both the unknown-email and wrong-password branches (`:146`, `:161`), `ResetFailedAttemptsAsync` on success (`:178`), `CheckRegistrationRateLimitAsync` (`:197`) and `IncrementRegistrationCountAsync` (`:256`). The concrete, cache-backed [LoginProtectionService](#loginprotectionservice) (tuned by [LoginProtectionSettings](#loginprotectionsettings), bound at `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:154-157`) implements it, and the framework registers that pairing at `:135`. ### IPasswordResetTokenService > MMCA.Common.Application · `MMCA.Common.Application.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/IPasswordResetTokenService.cs:10` · Level 3 · interface @@ -2355,8 +2659,8 @@ live in later groups; this chapter is the engine those endpoints call into. - **Walkthrough**: two members. - `Task> IssueAsync(string email, UserIdentifierType userId, CancellationToken cancellationToken = default)` (`:23`) returns the **raw** token to email, or a failure when the per-email request throttle has been exceeded. The doc comment (`:12-15`) states the replace semantics: issuing overwrites any token already outstanding for that address, so requesting a new link immediately stops the older one from working. The `userId` parameter is what the token resolves back to at redeem time, which is why the redeem call never has to trust an identifier supplied by the caller. - `Task> ValidateAndConsumeAsync(string email, string token, CancellationToken cancellationToken = default)` (`:36`) validates the presented token against the outstanding record and **consumes it on success**, so a token never redeems twice (`:25-28`), returning the account the token belongs to. -- **Why it's built this way**: taking `email` on both methods, rather than treating the token as self-describing, is what lets the implementation key its records by address and enforce the per-address throttle and the one-active-token rule at the same key. Returning `Result` rather than a boolean means the redeem handler gets the account identity from the token store itself. See [PasswordResetTokenService](#passwordresettokenservice) for the mechanics the port hides: a 32-byte random token (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:30`), only its digest stored, an attempt counter, and an address normalized through [Email](group-02-domain-building-blocks.md#email) before it becomes a cache key (`:34-45`) so `User@x.com` and `user@x.com` cannot hold independent tokens for one account. -- **Where it's used**: injected into the shared [ForgotPasswordHandlerBase](group-14-module-system-composition.md#forgotpasswordhandlerbasetuser-tcommand) (constructor parameter at `MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ForgotPassword/ForgotPasswordHandlerBase.cs:38`, called at `:72`, where a throttled issue is logged and still answered as success) and into [ResetPasswordHandlerBase](group-14-module-system-composition.md#resetpasswordhandlerbasetuser-tcommand) (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:44`, redeemed at `:61-62`). Both apps' sealed subclasses take the same dependency, for example ADC's [ForgotPasswordHandler](group-24-identity-module.md#forgotpasswordhandler) and [ResetPasswordHandler](group-24-identity-module.md#resetpasswordhandler). The framework registers the concrete as scoped at `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:158`. +- **Why it's built this way**: taking `email` on both methods, rather than treating the token as self-describing, is what lets the implementation key its records by address and enforce the per-address throttle and the one-active-token rule at the same key. Returning `Result` rather than a boolean means the redeem handler gets the account identity from the token store itself. See [PasswordResetTokenService](#passwordresettokenservice) for the mechanics the port hides: a 32-byte random token (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:39`), only its digest stored, an attempt counter, and an address normalized through [Email](group-02-domain-building-blocks.md#email) before it becomes a cache key (`:34-45`) so `User@x.com` and `user@x.com` cannot hold independent tokens for one account. +- **Where it's used**: injected into the shared [ForgotPasswordHandlerBase](group-14-module-system-composition.md#forgotpasswordhandlerbasetuser-tcommand) (constructor parameter at `MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ForgotPassword/ForgotPasswordHandlerBase.cs:38`, called at `:72`, where a throttled issue is logged and still answered as success) and into [ResetPasswordHandlerBase](group-14-module-system-composition.md#resetpasswordhandlerbasetuser-tcommand) (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:44`, redeemed at `:61-62`). Both apps' sealed subclasses take the same dependency, for example ADC's [ForgotPasswordHandler](group-24-identity-module.md#forgotpasswordhandler) and [ResetPasswordHandler](group-24-identity-module.md#resetpasswordhandler). The framework registers the concrete as scoped at `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:164`. ### IRoleAdministrationService > MMCA.Common.Application · `MMCA.Common.Application.Auth.Administration` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Administration/IRoleAdministrationService.cs:29` · Level 3 · interface @@ -2386,6 +2690,22 @@ live in later groups; this chapter is the engine those endpoints call into. - **Why it's built this way**: generic over `TUserDto` so each app's admin list can carry app-specific columns without the framework knowing about them, while the lock/unlock contract's remarks pin the one security-relevant behavior (session revocation on lock) as a documented obligation rather than leaving it to whichever app implements the interface first. - **Where it's used**: consumed by `UsersAdminControllerBase` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Administration/UsersAdminControllerBase.cs`); implemented by each app's `UserAdministrationService`, for example `MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/Administration/UserAdministrationService.cs`, and registered through `MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs`. +### SoftDeletedUserCache +> MMCA.Common.Application · `MMCA.Common.Application.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/SoftDeletedUserCache.cs:17` · Level 4 · class (static) + +- **What it is**: the shared cache contract for the **soft-deleted user marker** (BR-133): the key shape, the marker lifetime, and a one-call helper that writes it. The API middleware reads the marker on every authenticated request; the module that soft-deletes a user writes it (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/SoftDeletedUserCache.cs:6-9`). +- **Depends on**: [ICacheService](group-09-caching.md#icacheservice) from `MMCA.Common.Application.Interfaces` (`:2`), the `UserIdentifierType` alias, and `System.Globalization.CultureInfo` (BCL, `:1`). +- **Concept introduced: revoking a stateless credential without a per-request lookup.** `[Rubric §11, Security]` assesses whether a revoked principal actually loses access, and `[Rubric §12, Performance & Scalability]` assesses whether such a concern is factored so both ends share one definition. A JWT is a bearer credential: signature validation never asks "is this account still active?", so soft-deleting a user leaves an already-issued access token passing validation until it expires ([ADR-047](https://ivanball.github.io/docs/adr/047-soft-deleted-user-session-revocation.html)). The textbook fixes (a deny-list, or an account-status query on every request) reintroduce exactly the per-request state that stateless JWT was chosen to avoid. This type is the middle path: a short-lived cache marker written at deletion time and read cheaply on the hot path. + + The `remarks` (`:11-16`) explain why the constants live in the **Application** layer rather than next to the middleware that reads them: a downstream application deleting an account has to write the exact same key the middleware reads, and a private constant in the presentation layer is unreachable from an application-layer command handler. Same reasoning as [IdempotencyHeaders](#idempotencyheaders), applied one layer up. +- **Walkthrough**: three static members, no state. + - `MarkerDuration => TimeSpan.FromSeconds(30)` (`:29`). The remarks (`:22-28`) justify the number rather than leaving it magic: the marker only has to cover the window between the delete committing and the next token validation, because once it expires the validator query is the source of truth again and gives the same answer. Short-lived access tokens (15 minutes, the BR-205 default on [ITokenService](#itokenservice)) bound the rest of the exposure, so a longer marker would buy nothing and would keep stale entries alive for users who were never deleted. + - `KeyFor(UserIdentifierType userId)` (`:42-43`) builds `user:deleted:{userId}` through `string.Create(CultureInfo.InvariantCulture, ...)`. The remarks (`:36-41`) name the bug this prevents: an identifier renders differently under some cultures (digit shapes, group separators), so a culture-sensitive key would be written under one request's culture and missed under another, silently letting a deleted user keep making requests. This is a case where the analyzer rule about culture-invariant formatting is guarding a security property, not just a formatting nicety. + - `MarkDeletedAsync(ICacheService cache, UserIdentifierType userId, CancellationToken cancellationToken = default)` (`:53-61`) null-guards the cache (`:58`) and writes `true` under `KeyFor(userId)` for `MarkerDuration` (`:60`). It returns the task without awaiting, so there is no extra async state machine for a one-call passthrough. +- **Why it's built this way**: publishing the key shape and the TTL as framework API is what keeps the writer and the reader honest, and it is a precondition for the module boundary in [ADR-047](https://ivanball.github.io/docs/adr/047-soft-deleted-user-session-revocation.html): Identity owns the delete, every service hosts the middleware, and the only thing they share is a cache entry rather than a database. `[Rubric §7, Microservices Readiness]` applies directly: an extracted service can enforce the revocation without a reference to the Identity database. +- **Where it's used**: read by [SoftDeletedUserMiddleware](group-12-api-hosting-mapping.md#softdeletedusermiddleware), which builds the key (`MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/SoftDeletedUserMiddleware.cs:85`), short-circuits with 401 when the marker is `true` (`:102-105`), and on a miss falls back to the validator query (`:113-115`) and caches **that** answer, deleted or not, for the same `MarkerDuration` (`:132`). Written by the shared delete workflow itself, ahead of either app's post-commit tail: `DeleteUserHandlerBase.HandleAsync` calls `SoftDeletedUserCache.MarkDeletedAsync` right after the erasure commits (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:146-148`) and swallows a cache fault (`:146-149`) so a failed marker cannot turn a successful erasure into an error the caller would retry. +- **Caveats / not-in-source**: the marker is best effort on both ends by design. The middleware fails **open** on a cache outage, falling through to the validator query (`:95-100`) and proceeding if that is also unavailable (`:118-125`), and the writer logs and continues on a cache fault. The exposure that leaves is bounded by the access-token lifetime, which is the trade-off ADR-047 accepts explicitly. ADC's handler is the only writer in the source tree today; MMCA.Store soft-deletes users without writing the marker, so there the middleware's own validator-query fallback is what enforces BR-133. + ### IRefreshSessionStore > MMCA.Common.Application · `MMCA.Common.Application.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/IRefreshSessionStore.cs:21` · Level 4 · interface @@ -2402,33 +2722,17 @@ live in later groups; this chapter is the engine those endpoints call into. - `SaveChangesAsync(...)` (`:67`) persists staged inserts and revocations. - `TryRotateAsync(RefreshSession presented, RefreshSession successor, DateTime revokedAt, ...)` (`:95-113`) is the one exception to the no-update rule, and it ships a **default interface implementation**. It argument-guards both sessions (`:101-102`), revokes the presented session as `RefreshSession.ReasonRotated` linked to the successor's hash (`:104`), then stages and saves the successor (`:109-110`). The `bool` return is the whole point (`:73-79`): two requests presenting the same still-live token both read an un-revoked row, so a check-then-act rotation would mint two successors from one token and the presented row could never fire reuse detection again. Returning `false` tells the caller it lost the claim, which is indistinguishable from a replay and gets the same answer. The default body is atomic only per instance, which is all an in-memory or test store can offer; the shipped EF store replaces it with a conditional `ExecuteUpdateAsync` the database arbitrates (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/EFRefreshSessionStore.cs:108` and `:126`), as the comment at `:80-84` says. - **Why it's built this way**: hashed-at-rest, per-device session rows are what turn refresh-token rotation into something a user can inspect and revoke per device, and what let reuse detection revoke a whole family ([ADR-050](https://ivanball.github.io/docs/adr/050-jwt-refresh-token-rotation.html), BR-205/206). Keeping the contract in Application means the workflow that uses it is independent of EF, so an extracted Identity service can bring its own store. Making rotation a *claim* rather than a mutation is the difference between a race that mints two live tokens and a race one side of which is answered as a replay. -- **Where it's used**: registered as scoped against the EF implementation at `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:166`, with the comment (`:143-144`) noting the lifetime is deliberate: scoped, like the unit of work it shares a `DbContext` with, so a login and its session insert commit together. Consumed throughout [AuthenticationServiceBase](#authenticationservicebasetuser) (injected at `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:81`, exposed to subclasses at `:88`) for single-device sign-out (`:348-350`), session listing (`:404`), targeted revocation (`:441`), reuse resolution (`:592-594`), rotation (`:682-684`), family revocation (`:708`) and cap eviction (`:725`). - -### SoftDeletedUserCache -> MMCA.Common.Application · `MMCA.Common.Application.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/SoftDeletedUserCache.cs:17` · Level 4 · class (static) - -- **What it is**: the shared cache contract for the **soft-deleted user marker** (BR-133): the key shape, the marker lifetime, and a one-call helper that writes it. The API middleware reads the marker on every authenticated request; the module that soft-deletes a user writes it (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/SoftDeletedUserCache.cs:6-9`). -- **Depends on**: [ICacheService](group-09-caching.md#icacheservice) from `MMCA.Common.Application.Interfaces` (`:2`), the `UserIdentifierType` alias, and `System.Globalization.CultureInfo` (BCL, `:1`). -- **Concept introduced: revoking a stateless credential without a per-request lookup.** `[Rubric §11, Security]` assesses whether a revoked principal actually loses access, and `[Rubric §12, Performance & Scalability]` assesses whether such a concern is factored so both ends share one definition. A JWT is a bearer credential: signature validation never asks "is this account still active?", so soft-deleting a user leaves an already-issued access token passing validation until it expires ([ADR-047](https://ivanball.github.io/docs/adr/047-soft-deleted-user-session-revocation.html)). The textbook fixes (a deny-list, or an account-status query on every request) reintroduce exactly the per-request state that stateless JWT was chosen to avoid. This type is the middle path: a short-lived cache marker written at deletion time and read cheaply on the hot path. - - The `remarks` (`:11-16`) explain why the constants live in the **Application** layer rather than next to the middleware that reads them: a downstream application deleting an account has to write the exact same key the middleware reads, and a private constant in the presentation layer is unreachable from an application-layer command handler. Same reasoning as [IdempotencyHeaders](#idempotencyheaders), applied one layer up. -- **Walkthrough**: three static members, no state. - - `MarkerDuration => TimeSpan.FromSeconds(30)` (`:29`). The remarks (`:22-28`) justify the number rather than leaving it magic: the marker only has to cover the window between the delete committing and the next token validation, because once it expires the validator query is the source of truth again and gives the same answer. Short-lived access tokens (15 minutes, the BR-205 default on [ITokenService](#itokenservice)) bound the rest of the exposure, so a longer marker would buy nothing and would keep stale entries alive for users who were never deleted. - - `KeyFor(UserIdentifierType userId)` (`:42-43`) builds `user:deleted:{userId}` through `string.Create(CultureInfo.InvariantCulture, ...)`. The remarks (`:36-41`) name the bug this prevents: an identifier renders differently under some cultures (digit shapes, group separators), so a culture-sensitive key would be written under one request's culture and missed under another, silently letting a deleted user keep making requests. This is a case where the analyzer rule about culture-invariant formatting is guarding a security property, not just a formatting nicety. - - `MarkDeletedAsync(ICacheService cache, UserIdentifierType userId, CancellationToken cancellationToken = default)` (`:53-61`) null-guards the cache (`:58`) and writes `true` under `KeyFor(userId)` for `MarkerDuration` (`:60`). It returns the task without awaiting, so there is no extra async state machine for a one-call passthrough. -- **Why it's built this way**: publishing the key shape and the TTL as framework API is what keeps the writer and the reader honest, and it is a precondition for the module boundary in [ADR-047](https://ivanball.github.io/docs/adr/047-soft-deleted-user-session-revocation.html): Identity owns the delete, every service hosts the middleware, and the only thing they share is a cache entry rather than a database. `[Rubric §7, Microservices Readiness]` applies directly: an extracted service can enforce the revocation without a reference to the Identity database. -- **Where it's used**: read by [SoftDeletedUserMiddleware](group-12-api-hosting-mapping.md#softdeletedusermiddleware), which builds the key (`MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/SoftDeletedUserMiddleware.cs:85`), short-circuits with 401 when the marker is `true` (`:102-105`), and on a miss falls back to the validator query (`:113-115`) and caches **that** answer, deleted or not, for the same `MarkerDuration` (`:132`). Written by the shared delete workflow itself, ahead of either app's post-commit tail: `DeleteUserHandlerBase.HandleAsync` calls `SoftDeletedUserCache.MarkDeletedAsync` right after the erasure commits (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:142-144`) and swallows a cache fault (`:146-149`) so a failed marker cannot turn a successful erasure into an error the caller would retry. -- **Caveats / not-in-source**: the marker is best effort on both ends by design. The middleware fails **open** on a cache outage, falling through to the validator query (`:95-100`) and proceeding if that is also unavailable (`:118-125`), and the writer logs and continues on a cache fault. The exposure that leaves is bounded by the access-token lifetime, which is the trade-off ADR-047 accepts explicitly. ADC's handler is the only writer in the source tree today; MMCA.Store soft-deletes users without writing the marker, so there the middleware's own validator-query fallback is what enforces BR-133. +- **Where it's used**: registered as scoped against the EF implementation at `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:172`, with the comment (`:143-144`) noting the lifetime is deliberate: scoped, like the unit of work it shares a `DbContext` with, so a login and its session insert commit together. Consumed throughout [AuthenticationServiceBase](#authenticationservicebasetuser) (injected at `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:81`, exposed to subclasses at `:88`) for single-device sign-out (`:348-350`), session listing (`:404`), targeted revocation (`:441`), reuse resolution (`:592-594`), rotation (`:682-684`), family revocation (`:708`) and cap eviction (`:725`). ### AuthenticationValidators > MMCA.Common.Application · `MMCA.Common.Application.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationValidators.cs:16` · Level 5 · class (sealed) - **What it is**: a tiny **parameter object** that bundles the three FluentValidation validators the authentication workflow needs (login, registration, refresh) into one injectable dependency. - **Depends on**: FluentValidation's `IValidator` (NuGet, `:1`) over the request DTOs [LoginRequest](#loginrequest), [RegisterRequest](#registerrequest), and [RefreshTokenRequest](#refreshtokenrequest) (all in `MMCA.Common.Shared.Auth`, `:2`). -- **Concept introduced: the parameter object as a constructor-arity guardrail.** `[Rubric §1, SOLID]` assesses whether a class stays a single, cohesive responsibility rather than sprawling into a god class, and `[Rubric §15, Best Practices & Code Quality]` assesses whether cross-cutting dependencies are grouped so a class can grow without exploding its constructor. The doc comment (`:6-11`) states the exact motive: collapsing three closely-related dependencies into one keeps the app's `AuthenticationService` **below the application-service constructor-arity ceiling** (a god-class analyzer guardrail) without giving up per-request validation. Because the request DTOs already live in `MMCA.Common.Shared.Auth`, the bundle is app-agnostic, which is why it could be hoisted out of the apps into the framework. The pressure is real rather than theoretical: even with the bundle, ADC's subclass constructor takes ten parameters (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:49-58`). +- **Concept introduced: the parameter object as a constructor-arity guardrail.** `[Rubric §1, SOLID]` assesses whether a class stays a single, cohesive responsibility rather than sprawling into a god class, and `[Rubric §15, Best Practices & Code Quality]` assesses whether cross-cutting dependencies are grouped so a class can grow without exploding its constructor. The doc comment (`:6-11`) states the exact motive: collapsing three closely-related dependencies into one keeps the app's `AuthenticationService` **below the application-service constructor-arity ceiling** (a god-class analyzer guardrail) without giving up per-request validation. Because the request DTOs already live in `MMCA.Common.Shared.Auth`, the bundle is app-agnostic, which is why it could be hoisted out of the apps into the framework. The pressure is real rather than theoretical: even with the bundle, ADC's subclass constructor takes ten parameters (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:51-57`). - **Walkthrough**: a primary constructor takes the three `IValidator` instances (`:16-19`), and three get-only properties surface them by name: `Login` (`:22`), `Register` (`:25`), and `Refresh` (`:28`), each assigned from its matching constructor parameter. There is no logic here; the type exists purely to shrink the dependency footprint of its consumer. - **Why it's built this way**: a `sealed` grouping type with get-only properties is the cheapest way to fold three cohesive dependencies into one constructor slot, so the workflow base can validate each request shape without pushing its constructor over the arity limit; DI resolves the three underlying validators and composes them into this one object. Two of the three ([LoginRequestValidator](#loginrequestvalidator), [RefreshTokenRequestValidator](#refreshtokenrequestvalidator)) come from the framework assembly, while `IValidator` is satisfied by the app's own `RegisterRequestValidator`, so the bundle is the point where framework and app validation meet. -- **Where it's used**: injected into [AuthenticationServiceBase](#authenticationservicebasetuser) (constructor parameter at `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:80`), whose `LoginAsync`, `RegisterAsync`, and `RefreshTokenAsync` call `validators.Login` (`:124`), `validators.Register` (`:190`), and `validators.Refresh` (`:270`) respectively before doing any work. It is registered by each app's Identity module rather than by the framework, since one of its three dependencies is app-owned: `MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:36` and `MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Application/DependencyInjection.cs:42`, both `TryAddScoped()`. +- **Where it's used**: injected into [AuthenticationServiceBase](#authenticationservicebasetuser) (constructor parameter at `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:67`), whose `LoginAsync`, `RegisterAsync`, and `RefreshTokenAsync` call `validators.Login` (`:124`), `validators.Register` (`:190`), and `validators.Refresh` (`:270`) respectively before doing any work. It is registered by each app's Identity module rather than by the framework, since one of its three dependencies is app-owned: `MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:36` and `MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Application/DependencyInjection.cs:42`, both `TryAddScoped()`. ### IAuthenticationService > MMCA.Common.Application · `MMCA.Common.Application.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/IAuthenticationService.cs:12` · Level 5 · interface @@ -2463,31 +2767,40 @@ live in later groups; this chapter is the engine those endpoints call into. - **Where it's used**: the documented mechanism behind [IUserAdministrationService](#iuseradministrationservicetuserdto)'s `SetLockedAsync` obligation (see that type); also `ChangePasswordHandlerBase` (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs`) and `ResetPasswordHandlerBase` (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs`). ### AuthenticationServiceBase -> MMCA.Common.Application · `MMCA.Common.Application.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:74` · Level 8 · class (abstract) +> MMCA.Common.Application · `MMCA.Common.Application.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:63` · Level 8 · class (abstract) - **What it is**: the **shared authentication workflow** (login, registration, refresh-token rotation, per-device and global revocation, session listing) hoisted once into the framework, generic over the app's `User` aggregate. It realises [IAuthenticationService](#iauthenticationservice) and leaves the genuinely app-specific decisions to a small set of `abstract` and `virtual` hooks a sealed subclass overrides. -- **Depends on**: [IUnitOfWork](group-07-persistence-ef-core.md#iunitofwork) and [IRepository](group-07-persistence-ef-core.md#irepositorytentity-tidentifiertype) (persistence, G07), [ITokenService](#itokenservice), [IPasswordHasher](#ipasswordhasher), [ILoginProtectionService](#iloginprotectionservice), [AuthenticationValidators](#authenticationvalidators), [IRefreshSessionStore](#irefreshsessionstore), `IOptions<`[RefreshSessionSettings](#refreshsessionsettings)`>` (the eight required constructor parameters, `:75-82`), and two optional ones added since: an [ITwoFactorAuthenticator](#itwofactorauthenticator)`?` and `IOptions<`[EmailConfirmationSettings](#emailconfirmationsettings)`>?` (`:83-84`, both default `null` so every existing subclass keeps compiling untouched), the [IAuthUser](#iauthuser) credential contract plus [AuditableAggregateRootEntity](group-02-domain-building-blocks.md#auditableaggregaterootentitytidentifiertype) as the `TUser` constraint (`:85`) and, where a host adopts confirmation, [IEmailConfirmableUser](#iemailconfirmableuser) (matched in `CheckEmailConfirmed`), [RefreshSession](#refreshsession) (the session aggregate it creates and revokes), [Email](group-02-domain-building-blocks.md#email) (normalizing the login and register address), [ClaimsPrincipalExtensions](#claimsprincipalextensions) (`principal.GetUserId()` and, new, `principal.FindMultiFactorMethod()`), [Result](group-01-result-error-handling.md#result) and [Error](group-01-result-error-handling.md#error), the request and response DTOs, and the BCL `TimeProvider` (injected at `:79`, never `DateTime.UtcNow`, so the clock is testable). -- **Concept introduced: the Template Method that de-duplicates a whole vertical slice.** `[Rubric §2, Design Patterns]` assesses idiomatic pattern use: this is a textbook **Template Method**, the invariant sequence of an operation living in the base while the variable steps are deferred to subclass hooks. `[Rubric §15, Best Practices & Code Quality]` (DRY across services) and `[Rubric §1, SOLID]` also apply: the doc comment (`:14-19`) records that the app Identity modules previously duplicated this workflow at roughly 70 to 95 percent line-identity, so a fix to the lockout order or the rotation logic is written once. `[Rubric §11, Security]`: the base encodes the security posture directly, validate first, an [ILoginProtectionService](#iloginprotectionservice) lockout and rate-limit gate ([ADR-029](https://ivanball.github.io/docs/adr/029-authentication-brute-force-protection.html)), an untracked-then-tracked dual fetch ([ADR-004](https://ivanball.github.io/docs/adr/004-authentication-dual-fetch.html)), and refresh-token rotation with **reuse detection** ([ADR-050](https://ivanball.github.io/docs/adr/050-jwt-refresh-token-rotation.html), BR-205/206). `[Rubric §7, Microservices Readiness]`: the workflow depends only on ports, so it runs unchanged whether the Identity module is in-monolith or its own service. +- **Depends on**: [IUnitOfWork](group-07-persistence-ef-core.md#iunitofwork) and [IRepository](group-07-persistence-ef-core.md#irepositorytentity-tidentifiertype) (persistence, G07), [IPasswordHasher](#ipasswordhasher), [ILoginProtectionService](#iloginprotectionservice), [AuthenticationValidators](#authenticationvalidators) and [IAuthSessionIssuer](#iauthsessionissuer) (the five required constructor parameters, `:64-68`), and two optional ones: an [ITwoFactorAuthenticator](#itwofactorauthenticator)`?` and `IOptions<`[EmailConfirmationSettings](#emailconfirmationsettings)`>?` (`:69-70`, both default `null` so a subclass that adopts neither passes nothing), the [IAuthUser](#iauthuser) credential contract plus [AuditableAggregateRootEntity](group-02-domain-building-blocks.md#auditableaggregaterootentitytidentifiertype) as the `TUser` constraint (`:71`) and, where a host adopts confirmation, [IEmailConfirmableUser](#iemailconfirmableuser) (matched in `CheckEmailConfirmed`), [ITokenService](#itokenservice) (reached only through the issuer's `TokenService` property, `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/IAuthSessionIssuer.cs:34`), [TwoFactorOutcome](#twofactoroutcome), [TwoFactorErrors](#twofactorerrors) and [AuthClaimTypes](#authclaimtypes) (the `mfa` claim values), [Email](group-02-domain-building-blocks.md#email) (normalizing the login and register address), [ClaimsPrincipalExtensions](#claimsprincipalextensions) (`principal.GetUserId()` and `principal.FindMultiFactorMethod()`), [Result](group-01-result-error-handling.md#result) and [Error](group-01-result-error-handling.md#error), and the request and response DTOs. The base itself takes no session store, no session settings and no clock (`:63-70`): those belong to the issuer. +- **Concept introduced: the Template Method that de-duplicates a whole vertical slice.** `[Rubric §2, Design Patterns]` assesses idiomatic pattern use: this is a textbook **Template Method**, the invariant sequence of an operation living in the base while the variable steps are deferred to subclass hooks. `[Rubric §15, Best Practices & Code Quality]` (DRY across services) and `[Rubric §1, SOLID]` also apply: the doc comment (`:19-23`) records that the app Identity modules previously duplicated this workflow at roughly 70 to 95 percent line-identity, so a fix to the lockout order or the rotation logic is written once. `[Rubric §11, Security]`: the base encodes the security posture directly, validate first, an [ILoginProtectionService](#iloginprotectionservice) lockout and rate-limit gate ([ADR-029](https://ivanball.github.io/docs/adr/029-authentication-brute-force-protection.html)), an untracked-then-tracked dual fetch ([ADR-004](https://ivanball.github.io/docs/adr/004-authentication-dual-fetch.html)), and, through [IAuthSessionIssuer](#iauthsessionissuer), refresh-token rotation with **reuse detection** ([ADR-050](https://ivanball.github.io/docs/adr/050-jwt-refresh-token-rotation.html), BR-205/206). `[Rubric §7, Microservices Readiness]`: the workflow depends only on ports, so it runs unchanged whether the Identity module is in-monolith or its own service. - **The session model is the other concept to absorb before reading the code** (`:35-47`). Refresh tokens are not a column on the user: every issue opens its own [RefreshSession](#refreshsession) row, the store holds only `RefreshSession.HashToken` digests, and rotation revokes the presented session and links it to its successor. Presenting an already-rotated token therefore lands on a revoked row, which is the reuse signal that revokes the user's whole live family (BR-206). Two requests presenting the same live token at the same instant get the same treatment, because the rotation is claimed atomically through `IRefreshSessionStore.TryRotateAsync` and the loser is answered as a replay rather than handed a second successor. An expired session is **not** a reuse signal and fails alone. A per-user cap evicts the oldest live session on a new sign-in so one account cannot grow the table without bound. + **The division of labour is the other concept to absorb before reading the code** (`:39-44`): this class decides **who** is signed in, and [IAuthSessionIssuer](#iauthsessionissuer) decides **what they are handed**. Once a caller is proved, the multi-device refresh sessions (hashed at rest), BR-205 rotation with BR-206 reuse detection, the per-user session cap, and the `sid` and `mfa` claims on the minted access token are the issuer's job, so this workflow never touches a [RefreshSession](#refreshsession) row. Every session-shaped member below is a single delegation to the issuer; the reuse, replay, expiry and cap rules are taught on [AuthSessionIssuer](#authsessionissuer). - **Walkthrough** (members in teaching order): - - **Constructor and protected accessors** (`:74-86`): a primary constructor takes the eight required collaborators plus the two optional ones; a private field wraps the injected token service in a [SessionStampingTokenService](#sessionstampingtokenservice) (`:93`); a private `_multiFactorMethod` field (`:105`) is the base's own arming state for the second-factor claim, separate from and forwarded into the wrapper's `CurrentMultiFactorMethod`; protected read-only properties re-expose `UnitOfWork` (`:108`), the *wrapped* `TokenService` (`:120`, whose remarks explain that minting through this property is what puts `sid` on the token), `TimeProvider` (`:123`), `RefreshSessions` (`:126`), and a `Repository` resolved lazily as `unitOfWork.GetRepository()` (`:129`). - - **Lifetimes and cap**: `virtual` `AccessTokenLifetime` and `RefreshTokenLifetime` read through to [ITokenService](#itokenservice) (which derives them from `Jwt:AccessTokenExpirationMinutes` and `Jwt:RefreshTokenExpirationDays`), falling back to the BR-205 defaults of 15 minutes and 7 days on a non-positive value, meaning a hand-written test double or a misconfigured host. `virtual MaxActiveSessionsPerUser` reads `RefreshSessions:MaxActiveSessionsPerUser`. - - **`LoginAsync`** (`:156-251`): validate the request, check lockout (ADR-029 and BR-212), normalize the raw email into an [Email](group-02-domain-building-blocks.md#email) value object so the EF predicate compares same-typed converted values (an invalid address yields a null value object that simply matches no user, which is the invalid-credentials answer anyway). **Step 1** is an *untracked* fetch via the `FindUntrackedByEmailAsync` hook to verify credentials without change-tracker overhead. The null check now also calls `HasStoredCredential(untracked)` (`:186`, private helper at `:890`): an account with no stored password material, the shape an external-OAuth account carries (ADR-036), can never be reached by password login, and both branches answer the identical generic 401 with the identical `BurnPasswordVerificationCost(request.Password)` call (`:188`, private helper at `:898`) so the two cases cost the same time and are indistinguishable to a timing observer, before `IncrementFailedAttemptsAsync`. `passwordHasher.VerifyPassword` runs next and fails the same way on a wrong password. Only after the password check does the app gate (`ValidateLoginCandidateAsync`) run, deliberately reordered: reaching it now proves the caller owns the account, so its distinct status message (for example a deactivated-account rejection) is told to the account's owner rather than to anyone sweeping addresses, and running it before the password check (the old order) made account state readable with no credential at all. Two more gates follow the same rule, for the same reason: `CheckEmailConfirmed(untracked)` (`:215`, hook at `:641-651`), off unless the host both supplied `EmailConfirmationSettings.RequireConfirmedEmail` and the app's `User` implements [IEmailConfirmableUser](#iemailconfirmableuser), and `ChallengeSecondFactorAsync(untracked.Id, request.TwoFactorCode, cancellationToken)` (`:221-222`, hook at `:665-671`), which with no [ITwoFactorAuthenticator](#itwofactorauthenticator) registered answers `TwoFactorOutcome.NotEnrolled` outright at no extra cost. **Step 2** is a *tracked* re-fetch by id, purely about the instance the app's `CreateAccessToken` hook mints from, and the second lookup is what turns a race that deleted the account between the two steps into a clean 404. Then `ResetFailedAttemptsAsync`, and `_multiFactorMethod = MultiFactorMethodFor(secondFactor.Value)` (`:242`, private helper at `:679-688`, mapping a verified TOTP or recovery code to the `mfa` claim value and any unrecognized outcome to `null`) arms the field in a `try`/`finally` around `IssueTokensAsync` so the claim lands only on this response and disarms whether or not the call throws. - - **`RegisterAsync`** (`:254-331`): validate, IP rate-limit (ADR-029 and BR-213), reject a duplicate email through the `EmailExistsAsync` hook, hash the password, build the user through the `CreateUser` hook, `AddAsync` and `SaveChangesAsync`, run the `OnUserRegisteredAsync` post-commit hook to pick up the instance the first access token is minted from, increment the IP registration count, and open the session last: the session row carries the user id, which a store-generated key only has once the insert has run. + - **Constructor and protected accessors** (`:63-71`): a primary constructor takes the five required collaborators plus the two optional ones. A private `_multiFactorMethod` field (`:83`) is the base's own arming state for the second-factor claim, a plain field because the service is scoped and one request issues one token pair (remarks, `:79-82`); it is handed to the issuer at mint time. Protected read-only properties re-expose `UnitOfWork` (`:86`), `TokenService` (`:98`), which returns `sessionIssuer.TokenService`, the issuer's stamping instance, so minting through it is what puts `sid` on the token (remarks, `:88-97`), and a `Repository` resolved lazily as `unitOfWork.GetRepository()` (`:101-102`). Token lifetimes and the session cap are not members of this class: they are configuration read by the issuer. + - **`LoginAsync`** (`:105-200`): validate the request, check lockout (ADR-029 and BR-212), normalize the raw email into an [Email](group-02-domain-building-blocks.md#email) value object so the EF predicate compares same-typed converted values (an invalid address yields a null value object that simply matches no user, which is the invalid-credentials answer anyway). **Step 1** is an *untracked* fetch via the `FindUntrackedByEmailAsync` hook to verify credentials without change-tracker overhead. The null check now also calls `HasStoredCredential(untracked)` (`:135`, private helper at `:581-582`): an account with no stored password material, the shape an external-OAuth account carries (ADR-036), can never be reached by password login, and both branches answer the identical generic 401 with the identical `BurnPasswordVerificationCost(request.Password)` call (`:137`, private helper at `:589-599`) so the two cases cost the same time and are indistinguishable to a timing observer, before `IncrementFailedAttemptsAsync`. `passwordHasher.VerifyPassword` runs next and fails the same way on a wrong password. Only after the password check does the app gate (`ValidateLoginCandidateAsync`) run, deliberately reordered: reaching it now proves the caller owns the account, so its distinct status message (for example a deactivated-account rejection) is told to the account's owner rather than to anyone sweeping addresses, and running it before the password check (the old order) made account state readable with no credential at all. Two more gates follow the same rule, for the same reason: `CheckEmailConfirmed(untracked)` (`:164`, hook at `:480-490`), off unless the host both supplied `EmailConfirmationSettings.RequireConfirmedEmail` and the app's `User` implements [IEmailConfirmableUser](#iemailconfirmableuser), and `ChallengeSecondFactorCountingFailuresAsync(untracked.Id, request, cancellationToken)` (`:170-171`, private helper at `:522-535`). That helper runs the overridable `ChallengeSecondFactorAsync` hook (`:504-510`), which with no [ITwoFactorAuthenticator](#itwofactorauthenticator) registered answers `TwoFactorOutcome.NotEnrolled` outright at no extra cost, and counts a `TwoFactorErrors.TwoFactorInvalidCode` failure against the account through `IncrementFailedAttemptsAsync` exactly like a wrong password, so the lockout at the top of the method throttles code guessing per account. A missing code is the ordinary first leg of the challenge and is not counted (`:512-517`). **Step 2** is a *tracked* re-fetch by id, purely about the instance the app's `CreateAccessToken` hook mints from, and the second lookup is what turns a race that deleted the account between the two steps into a clean 404. Then `ResetFailedAttemptsAsync`, and `_multiFactorMethod = MultiFactorMethodFor(secondFactor.Value)` (`:191`, private helper at `:543-552`, mapping a verified TOTP or recovery code to the `mfa` claim value and any unrecognized outcome to `null`) arms the field in a `try`/`finally` around `IssueTokensAsync` so the claim lands only on this response and disarms whether or not the call throws. + - **`RegisterAsync`** (`:203-280`): validate, IP rate-limit (ADR-029 and BR-213), reject a duplicate email through the `EmailExistsAsync` hook, hash the password, build the user through the `CreateUser` hook, `AddAsync` and `SaveChangesAsync`, run the `OnUserRegisteredAsync` post-commit hook to pick up the instance the first access token is minted from, increment the IP registration count, and open the session last: the session row carries the user id, which a store-generated key only has once the insert has run. The save is wrapped in a deliberately **broad** `catch (Exception)` whose comment is the teaching material. The email lookup above is a check-then-act: two concurrent registrations for the same address both pass it, and the loser only fails on the insert, against the unique index every consumer puts on `Email` (ADC unfiltered, Store filtered on `IsDeleted`). Without the catch, that race surfaces as a generic 500 instead of the 409 a serialized pair would have produced. The catch cannot name `DbUpdateException`, because Application has no EF Core dependency by layer rule, so the **re-check is what narrows it**: if the address exists now, the concurrent registration is the cause and the caller gets the same conflict the serial path returns through the shared `EmailAlreadyExistsFailure()` helper; anything else rethrows untouched and still reaches the exception middleware. The re-check passes `CancellationToken.None` on purpose: it has to run even when the caller's token is what aborted the save, or a cancelled save could never be classified. - - **`RefreshTokenAsync`** (`:334-413`): validate, pull claims from the *expired* JWT via `tokenService.GetPrincipalFromExpiredToken` (signature still checked, only lifetime skipped), read the identifier with `principal.GetUserId()` (rides the standard `sub` claim, also accepts the `NameIdentifier` form the bearer handler maps it to, and parses through `IParsable` so the identifier alias can change shape without editing this file), load the tracked user, run the refresh app gate, resolve the session behind the presented token, rotate it, and answer with a token pair whose access token carries the **successor's** `sid`, a client's current-device marker following the rotation instead of pointing at the session the rotation just revoked. Since the second-factor addition, the step-up the user already performed is carried across the rotation too: `_multiFactorMethod = principal.FindMultiFactorMethod()` (`:398`) reads the `mfa` claim off the presented access token, whose signature was already validated, and arms it in a `try`/`finally` around the successor mint, the same disarm pattern as `LoginAsync`. Dropping it would quietly demote a signed-in session every access-token lifetime and make an `IRequiresMfa` use case unreachable without a fresh sign-in. - - **`RevokeTokenAsync`** (`:416-454`): load the user, and when a refresh token was supplied, look up its session by hash. Only a **live session belonging to this user** identifies the device to sign out; anything else (unknown token, another account's token, an already-revoked row) leaves the caller unidentifiable, so the request degrades to revoking every live session rather than reporting success for a revocation that reached nothing. - - **`RevokeAllSessionsAsync`** (`:457-472`): the unconditional form of the same thing. - - **`GetSessionsAsync`** (`:481-505`): reads the same "un-revoked sessions for this user" query the cap and family revocation use, then drops expired rows in memory with `IsActiveAt(now)` and orders newest first with `Id` as the tie-break. The remarks explain why the filtering is in memory: the store returns expired-but-unrevoked rows on purpose, and a device list must not offer a user a device that can no longer authenticate. - - **`RevokeSessionByIdAsync`** (`:519-543`): the ownership check *is* the store query, scoped to the user, so another account's id and a nonexistent id produce the same `NotFound`; an already-revoked session returns success without writing. - - **`IssueTokensAsync`** (`:554-578`): the shared open-and-respond used by login and registration, and reusable by an app-level external-login flow. It opens the session **before** minting the access token, because the token carries the session's id, saves, and returns the response. - - **The private mechanics**: `ResolveRotatableSessionAsync` (`:723`) is where the three rejections differ behind one identical error. An unknown hash, or one belonging to another account, is failed **alone**, because revoking the family on it would let anyone holding one of this user's expired access tokens sign them out everywhere by posting a random token. A **revoked** row means this exact token was already rotated away or signed out and has come back, which is the BR-206 reuse signal that revokes every live session. An **expired** row is an ordinary end of life and fails alone. `OpenSessionAsync` (`:762`) mints a token, creates the session, enforces the cap, and stages the insert, returning an [IssuedSession](#issuedsession). `RotateAsync` (`:801`) mints the successor and claims the rotation through `TryRotateAsync`; losing the claim is answered exactly like a replay. `RevokeLiveSessionsAsync` (`:844`) revokes without saving. `EnforceSessionCapAsync` (`:864`) revokes the oldest live sessions while the user is at or over the cap; expired-but-unrevoked rows do not count against the cap because they authenticate nobody, and age out through the retention sweep instead. `InvalidRefreshTokenError()` (`:883`) and `EmailAlreadyExistsFailure()` (`:915`) are the two shared failures that keep distinct internal paths indistinguishable to a caller. - - **The hooks**: four are `abstract`, so a subclass must supply them. `FindUntrackedByEmailAsync` (`:585`) and `EmailExistsAsync` (`:591`) are deliberately written against the app's concrete `User` so EF translates the predicate byte-for-byte as before, and the second explicitly leaves the app to decide whether soft-deleted accounts count (`ignoreQueryFilters: true` blocks re-registration of an erased address); `CreateUser` (`:594`) runs the app's domain factory; `CreateAccessToken` (`:597`) mints the app's claim set (for example `speaker_id` versus `customer_id`). Seven `virtual` members can be overridden, up from five: `CreateAccessTokenForSession` (`:618-631`) arms the stamping wrapper (both the session and, now, the multi-factor property) around the hook call; `CheckEmailConfirmed` (`:641-651`), new, is the email-confirmation sign-in gate, off unless the host supplied `EmailConfirmationSettings.RequireConfirmedEmail` and the app's `User` implements [IEmailConfirmableUser](#iemailconfirmableuser); `ChallengeSecondFactorAsync` (`:665-671`), new, runs the second-factor challenge and, with no [ITwoFactorAuthenticator](#itwofactorauthenticator) injected, answers `TwoFactorOutcome.NotEnrolled` with no extra query, which is what keeps the feature free for an app that has not adopted it; `ValidateLoginCandidateAsync` (`:691`) and `ValidateRefreshCandidateAsync` (`:695`) add extra gates such as a deactivated-account check; `OnUserRegisteredAsync` (`:702`) runs the post-commit side-effect; and `CreateRefreshUserMissingError` (`:710`) defaults the vanished-user case to 401 (a token for a missing user is indistinguishable from an invalid one) while letting an app return 404 where its public contract already promises it. -- **Why it's built this way**: the untracked-then-tracked dual fetch keeps the common credential-verification path off the change tracker (cheaper, and soft-deleted accounts fall out via EF query filters returning the generic 401) while still giving a tracked instance to mint from ([ADR-004](https://ivanball.github.io/docs/adr/004-authentication-dual-fetch.html)). Per-device session rows with hashed tokens, rotation, family revocation on reuse, and a per-user cap are the BR-205/206 model ([ADR-050](https://ivanball.github.io/docs/adr/050-jwt-refresh-token-rotation.html)). Password material flows through [IAuthUser](#iauthuser)'s `PasswordHash` and `PasswordSalt` ([ADR-032](https://ivanball.github.io/docs/adr/032-password-hashing.html)), and the whole workflow depends only on abstractions, so it is identical whether the module runs in-process or as an extracted service. -- **Where it's used**: subclassed by each app's sealed [AuthenticationService](group-24-identity-module.md#authenticationservice), for example `MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:48`, which binds `TUser = User`, adds the Attendee default role (BR-45) and the `speaker_id` claim (BR-209, built at `:275`), and re-lists `IAuthenticationService` (`:63`) so it can re-implement `RegisterAsync` and `ExternalLoginAsync` outright: ADC raises its registration side-effects inside one transactional unit rather than through the `OnUserRegisteredAsync` hook, because the identity column means the id does not exist until the first save (`AuthenticationService.cs:30-41`). MMCA.Store supplies its own subclass with a `customer_id` claim. Consumed by the Identity API controllers via the [IAuthenticationService](#iauthenticationservice) port. -- **Caveats / not-in-source**: `ExternalLoginAsync` is intentionally **not** overridden here: the base inherits the interface's default not-supported failure, and OAuth account linking stays in the app subclass because it is coupled to the app's `User` factory surface (doc comment, `:33-34`). + - **`RefreshTokenAsync`** (`:283-349`): validate, pull claims from the *expired* JWT via `TokenService.GetPrincipalFromExpiredToken` (`:297`, the issuer's instance; signature still checked, only lifetime skipped), read the identifier with `principal.GetUserId()` (rides the standard `sub` claim, also accepts the `NameIdentifier` form the bearer handler maps it to, and parses through `IParsable` so the identifier alias can change shape without editing this file), load the tracked user, run the refresh app gate, and hand the presented refresh token to `sessionIssuer.RotateAsync` (`:337-343`) together with a mint callback over `CreateAccessTokenForSession`. The issuer resolves the session behind the token (reuse detection included), rotates it, and answers with a token pair whose access token carries the **successor's** `sid`, a client's current-device marker following the rotation instead of pointing at the session the rotation just revoked. The step-up the user already performed is carried across the rotation too: `_multiFactorMethod = principal.FindMultiFactorMethod()` (`:332`) reads the `mfa` claim off the presented access token, whose signature was already validated, and arms it in a `try`/`finally` around the successor mint, the same disarm pattern as `LoginAsync`. Dropping it would quietly demote a signed-in session every access-token lifetime and make an `IRequiresMfa` use case unreachable without a fresh sign-in. + - **`RevokeTokenAsync`** (`:352-366`): load the user (a `NotFound` when absent), then delegate to `sessionIssuer.SignOutAsync(userId, refreshToken, ...)` (`:363`). Only a **live session belonging to this user** identifies the device to sign out; when the token is absent or identifies no live session of this user, the issuer revokes every live session instead (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/IAuthSessionIssuer.cs:85-94`), rather than reporting success for a revocation that reached nothing. + - **`RevokeAllSessionsAsync`** (`:369-382`): the unconditional form of the same thing, through `sessionIssuer.SignOutEverywhereAsync` (`:379`). + - **`GetSessionsAsync`** (`:386-394`): no user lookup, so a list is one call to `sessionIssuer.ListActiveAsync(userId, currentSessionId, ...)` (`:391`) wrapped in a success. The issuer lists live sessions newest first and leaves expired-but-unrevoked rows out, since a device list must not offer a device that can no longer authenticate (`IAuthSessionIssuer.cs:102-113`). + - **`RevokeSessionByIdAsync`** (`:402-406`): an expression-bodied delegation to `sessionIssuer.RevokeSessionAsync`. Ownership is checked by the issuer's store query, so another account's id and a nonexistent id produce the same `NotFound`; an already-revoked session is a success that writes nothing (`IAuthSessionIssuer.cs:115-124`). + - **`IssueTokensAsync`** (`:417-431`): the shared open-and-respond used by login and registration, and reusable by an app-level external-login flow. It null-guards the user and delegates to `sessionIssuer.IssueAsync(user.Id, sessionId => CreateAccessTokenForSession(user, sessionId), ...)` (`:425-430`): the access token is a callback because it carries the session's id, which exists only once the issuer has opened the session. + - **The private mechanics** are login-side only. `ChallengeSecondFactorCountingFailuresAsync` (`:522-535`) is the counting wrapper described under `LoginAsync`. `MultiFactorMethodFor` (`:543-552`) maps an unrecognized outcome to `null`, so an enum that grew cannot silently mint an `mfa` claim. `HasStoredCredential` (`:581-582`) requires both a non-empty hash and a non-empty salt. `BurnPasswordVerificationCost` (`:589-599`) runs one throwaway `VerifyPassword` against a zeroed 64-byte hash and 32-byte salt, skipping a blank password, so a branch that never reaches the real check still pays the key-derivation cost. `EmailAlreadyExistsFailure()` (`:606-608`) is the 409 shared by the up-front check and the race recovery so the two paths are indistinguishable to a caller. Session resolution, rotation, family revocation and the cap are private to [AuthSessionIssuer](#authsessionissuer). + - **The hooks**: four are `abstract`, so a subclass must supply them. `FindUntrackedByEmailAsync` (`:438`) and `EmailExistsAsync` (`:444`) are deliberately written against the app's concrete `User` so EF translates the predicate byte-for-byte as before, and the second explicitly leaves the app to decide whether soft-deleted accounts count (`ignoreQueryFilters: true` blocks re-registration of an erased address); `CreateUser` (`:447`) runs the app's domain factory; `CreateAccessToken` (`:450`) mints the app's claim set (for example `speaker_id` versus `customer_id`). Seven `virtual` members can be overridden: `CreateAccessTokenForSession` (`:469-470`) calls `sessionIssuer.MintForSession(sessionId, _multiFactorMethod, () => CreateAccessToken(user))`, so the issuer arms its stamping token service for the duration of the app hook and `sid` (plus `mfa` when this request verified a second factor) is appended to whatever claim set the app passes, which is what keeps the claims additive with no change to `CreateAccessToken`'s signature (remarks, `:456-465`); `CheckEmailConfirmed` (`:480-490`) is the email-confirmation sign-in gate, off unless the host supplied `EmailConfirmationSettings.RequireConfirmedEmail` and the app's `User` implements [IEmailConfirmableUser](#iemailconfirmableuser); `ChallengeSecondFactorAsync` (`:504-510`) runs the second-factor challenge and, with no [ITwoFactorAuthenticator](#itwofactorauthenticator) injected, answers `TwoFactorOutcome.NotEnrolled` with no extra query, which is what keeps the feature free for an app that has not adopted it; `ValidateLoginCandidateAsync` (`:555`) and `ValidateRefreshCandidateAsync` (`:559`) add extra gates such as a deactivated-account check; `OnUserRegisteredAsync` (`:566`) runs the post-commit side-effect; and `CreateRefreshUserMissingError` (`:574`) defaults the vanished-user case to 401 (a token for a missing user is indistinguishable from an invalid one) while letting an app return 404 where its public contract already promises it. +- **Why it's built this way**: the untracked-then-tracked dual fetch keeps the common credential-verification path off the change tracker (cheaper, and soft-deleted accounts fall out via EF query filters returning the generic 401) while still giving a tracked instance to mint from ([ADR-004](https://ivanball.github.io/docs/adr/004-authentication-dual-fetch.html)). Per-device session rows with hashed tokens, rotation, family revocation on reuse, and a per-user cap are the BR-205/206 model ([ADR-050](https://ivanball.github.io/docs/adr/050-jwt-refresh-token-rotation.html)), and keeping them behind [IAuthSessionIssuer](#iauthsessionissuer) gives the sign-in decision and the session mechanics one owner each (`:39-44`). Password material flows through [IAuthUser](#iauthuser)'s `PasswordHash` and `PasswordSalt` ([ADR-032](https://ivanball.github.io/docs/adr/032-password-hashing.html)), and the whole workflow depends only on abstractions, so it is identical whether the module runs in-process or as an extracted service. +- **Where it's used**: subclassed by each app's sealed [AuthenticationService](group-24-identity-module.md#authenticationservice), for example `MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:50`, which binds `TUser = User`, adds the Attendee default role (BR-45) and the `speaker_id` claim (BR-209, built at `:381-384`), passes the injected [IAuthSessionIssuer](#iauthsessionissuer) through to the base (`:56`, `:63`), and re-lists `IAuthenticationService` (`:66`) so it can re-implement `RegisterAsync` and `ExternalLoginAsync` outright: ADC raises its registration side-effects inside one transactional unit rather than through the `OnUserRegisteredAsync` hook, because the identity column means the id does not exist until the first save (`AuthenticationService.cs:30-42`). MMCA.Store supplies its own subclass with a `customer_id` claim. Consumed by the Identity API controllers via the [IAuthenticationService](#iauthenticationservice) port. +- **Caveats / not-in-source**: `ExternalLoginAsync` is intentionally **not** overridden here: the base inherits the interface's default not-supported failure, and OAuth account linking stays in the app subclass because it is coupled to the app's `User` factory surface (doc comment, `:37-38`). + +### IssuedSession +> MMCA.Common.Application · `MMCA.Common.Application.Auth.Sessions` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:436` · Level 0 · record (private sealed, nested) + +- **What it is**: the two-field result of opening or rotating a refresh session: the plaintext refresh token the client is handed, and the id of the session row it belongs to. It is a `private sealed record` nested inside [AuthSessionIssuer](#authsessionissuer) (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:436`), not part of the framework's public surface. +- **Depends on**: nothing beyond the BCL (`string`, `Guid`). It is produced and consumed entirely inside its declaring class. +- **Concept introduced: the plaintext token exists in exactly one place, and it is a return value.** `[Rubric §11, Security]` assesses how a bearer credential is stored. [RefreshSession](#refreshsession) rows keep only a digest (`RefreshSession.HashToken`, used at `AuthSessionIssuer.cs:155` and `:285` to *look up* by hash), so once a session is persisted the raw token cannot be recovered from the store at all. The type comment says exactly this (`AuthSessionIssuer.cs:431-435`): the plaintext "exists nowhere else, since the store keeps only its hash". Modelling the hand-off as a small record rather than an out-parameter or a tuple is what keeps that fact readable: both methods that can produce a token return `Result`, so the compiler shows you the complete list of places raw token material is in flight. `[Rubric §15, Best Practices & Code Quality]` also applies: a positional record gives value equality and immutability for free, and `Guid SessionId` names what would otherwise be an anonymous second tuple element. +- **Walkthrough**: one positional declaration, `IssuedSession(string RefreshToken, Guid SessionId)` (`AuthSessionIssuer.cs:436`). `RefreshToken` is what goes back to the caller in the [AuthenticationResponse](#authenticationresponse); `SessionId` is what the access token's `sid` claim carries, which is why the session must be opened before the token is minted (the comment at `AuthSessionIssuer.cs:73-74`). +- **Why it's built this way**: the pairing is load-bearing rather than incidental. A caller that received only the token could not stamp `sid`, and a caller that received only the id could not answer the client. Returning both together removes the ordering mistake where a token is minted for a session that does not exist yet. +- **Where it's used**: returned by `OpenSessionAsync` (`AuthSessionIssuer.cs:312`, constructed at `:337`) and `RotateSessionAsync` (`:350`, constructed at `:390`); unwrapped by `IssueAsync` (`:84-85`) and by `RotateAsync` (`:120-121`), each of which passes `SessionId` to the caller's `mintAccessToken` callback and puts `RefreshToken` into the response. ### EmailConfirmationSettings > MMCA.Common.Application · `MMCA.Common.Application.Auth.EmailConfirmation` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/EmailConfirmation/EmailConfirmationSettings.cs:10` · Level 0 · class @@ -2496,7 +2809,7 @@ live in later groups; this chapter is the engine those endpoints call into. - **Depends on**: nothing first-party; `System.ComponentModel.DataAnnotations` (`[Range]`) and `System.Diagnostics.CodeAnalysis` (`[SuppressMessage]`) validate the bound values. - **Concept introduced**: the config-bound settings-class shape (a `SectionName` constant plus `init`-only, range-validated properties with sane defaults) is the same one the sibling `PasswordResetSettings` uses elsewhere in this group; see it there for the general pattern rather than re-teaching it here. `[Rubric §11, Security]` (assesses whether authentication-adjacent behavior is safely configurable): every numeric knob here is range-constrained (`EmailConfirmationSettings.cs:43,50,54,58`), so a bad value in configuration fails validation instead of silently disabling throttling. - **Walkthrough**: `ConfirmationUrl` (`EmailConfirmationSettings.cs:40`) defaults to empty rather than being `[Required]`, deliberately, per its own doc comment (`EmailConfirmationSettings.cs:30-34`): an unconfigured host still boots and degrades to a token-only, paste-in-by-hand flow. `TokenLifetimeMinutes` (line 44) defaults to 1440 (one day). `MaxValidationAttempts` (line 51) caps wrong-token guesses at 5 before the record is discarded. `MaxRequestsPerEmail` (line 55) and `RequestWindowMinutes` (line 59) throttle re-issuance per address. `RequireConfirmedEmail` (line 70) defaults to `false`. -- **Why it's built this way**: the `RequireConfirmedEmail` default is called out as load-bearing in its own remarks (`EmailConfirmationSettings.cs:64-69`): turning it on locks out every account whose address was never confirmed, so a host backfills existing rows as confirmed before flipping it, and with it off the whole feature is additive. This is the opt-in shape recorded in [ADR-116](https://ivanball.github.io/docs/adr/116-identity-completions-opt-in.html). +- **Why it's built this way**: the `RequireConfirmedEmail` default is called out as load-bearing in its own remarks (`EmailConfirmationSettings.cs:49-54`): turning it on locks out every account whose address was never confirmed, so a host backfills existing rows as confirmed before flipping it, and with it off the whole feature is additive. This is the opt-in shape recorded in [ADR-116](https://ivanball.github.io/docs/adr/116-identity-completions-opt-in.html). - **Where it's used**: read by `AuthenticationServiceBase` and `EmailConfirmationTokenService` (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs`) and registered in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs`; ADC binds it via `MMCA.ADC.Identity.Application/Users/AuthenticationServiceSettings.cs` and consumes it in `SendEmailConfirmationHandler`. ### IPermissionGrantCache @@ -2504,7 +2817,7 @@ live in later groups; this chapter is the engine those endpoints call into. - **What it is**: the read side of the in-memory snapshot of stored (database-granted, as opposed to compiled-into-code) permissions, keyed by role. - **Depends on**: nothing first-party in its own signature; consumed alongside `IPermissionRegistry`. -- **Concept introduced**: the cache-in-front-of-a-store pattern used across this group for data that is read on every authorization check but changes rarely. `[Rubric §12, Performance & Scalability]` (assesses whether hot paths avoid a database round trip): `GetPermissions` (`IPermissionGrantCache.cs:99`) is synchronous and reads the already-loaded snapshot, so it stays off the authorization hot path's I/O. +- **Concept introduced**: the cache-in-front-of-a-store pattern used across this group for data that is read on every authorization check but changes rarely. `[Rubric §12, Performance & Scalability]` (assesses whether hot paths avoid a database round trip): `GetPermissions` (`IPermissionGrantCache.cs:29`) is synchronous and reads the already-loaded snapshot, so it stays off the authorization hot path's I/O. - **Walkthrough**: `GetPermissions(string role)` (line 99) returns the role's stored permissions, matched case-insensitively, empty when the role has none or the cache has not loaded yet. `RefreshAsync` (line 107) reloads the whole snapshot from the store; called at startup, on a configured interval, and explicitly by [IPermissionGrantCacheInvalidator](#ipermissiongrantcacheinvalidator) after a grant changes. - **Why it's built this way**: separating the read interface from the invalidation interface ([IPermissionGrantCacheInvalidator](#ipermissiongrantcacheinvalidator)) lets [LayeredPermissionRegistry](#layeredpermissionregistry), which only needs to read, depend on a narrower contract than the administration path that needs to force a reload. - **Where it's used**: implemented by `PermissionGrantCache` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/PermissionGrantCache.cs`), read by [LayeredPermissionRegistry](#layeredpermissionregistry) (`LayeredPermissionRegistry.cs`) and `StoredPermissionRoleAdministrationService`, and registered in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs`. @@ -2515,7 +2828,7 @@ live in later groups; this chapter is the engine those endpoints call into. - **What it is**: the write-triggering half of the cache: forces a reload of one role's grants (or every role) after an administrative change. - **Depends on**: nothing first-party in its own signature. - **Concept**: same cache-invalidation split introduced at [IPermissionGrantCache](#ipermissiongrantcache); no new concept here. -- **Walkthrough**: `InvalidateAsync(string? role = null, ...)` (`IPermissionGrantCache.cs:136`) invalidates the cached grants and reloads them; `null` means every role. +- **Walkthrough**: `InvalidateAsync(string? role = null, ...)` (`IPermissionGrantCache.cs:59`) invalidates the cached grants and reloads them; `null` means every role. - **Why it's built this way**: kept on a separate, narrower interface from the read side so a caller that only grants or revokes (the administration surface) depends on exactly the capability it needs. - **Where it's used**: implemented alongside `IPermissionGrantCache` by `PermissionGrantCache.cs`, called by `StoredPermissionRoleAdministrationService.cs` after a grant or revoke, and registered in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs`. @@ -2525,49 +2838,24 @@ live in later groups; this chapter is the engine those endpoints call into. - **What it is**: the options class bound to `Authentication:PermissionGrants`, controlling how long a cached grant snapshot is served, which logical data source owns the grant table, and which role names the administration surface lists. - **Depends on**: nothing first-party; `System.ComponentModel.DataAnnotations` (`[Range]`, `[Required]`). - **Concept**: the same config-bound settings shape as [EmailConfirmationSettings](#emailconfirmationsettings); no new concept. -- **Walkthrough**: `CacheSeconds` (`PermissionGrantSettings.cs:171`) defaults to 300 (five minutes) and its remarks (lines 165-169) spell out the trade-off: this is the bound on how stale another replica may be after an edit, since invalidation is per process. `DataSourceName` (line 179) defaults to `"Default"`, naming the logical data source a modular host points at its Identity database. `KnownRoles` (line 191) defaults to an empty list, and its remarks (lines 185-190) explain why it must be configured: neither `IPermissionRegistry` (which knows about a role but does not enumerate roles) nor the grant table (which only knows roles that already have a grant) can list roles on its own. -- **Why it's built this way**: a five-minute cache treats a permission grant as an administrative change rather than a per-request one, trading a shorter staleness window for more database reads if lowered. -- **Where it's used**: read by `PermissionGrantCache.cs`, `PermissionGrantRefreshService.cs`, `StoredPermissionRoleAdministrationService.cs`, and `DesignTimeDbContextHelper.cs`; registered in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs`. - -### RecoveryCodeSet -> MMCA.Common.Application · `MMCA.Common.Application.Auth.TwoFactor` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/ITwoFactorService.cs:84` · Level 0 · record - -- **What it is**: a two-field record pairing a freshly generated set of plaintext recovery codes with their stored hashes. -- **Depends on**: nothing first-party. -- **Concept**: a plaintext/hash pair returned once, at generation time only, so the plaintext never has to be persisted or re-derived. -- **Walkthrough**: `Codes` (`ITwoFactorService.cs:85`) is the plaintext shown to the user once; `Hashes` (line 86) is what the store keeps. -- **Why it's built this way**: keeping the plaintext out of any store means a database compromise cannot recover usable recovery codes, only their hashes. -- **Where it's used**: returned by [ITwoFactorService](#itwofactorservice).`GenerateRecoveryCodes()`, consumed by `TotpTwoFactorService.cs` and exercised in `MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs`. - -### TwoFactorOutcome -> MMCA.Common.Application · `MMCA.Common.Application.Auth.TwoFactor` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/ITwoFactorAuthenticator.cs:47` · Level 0 · enum - -- **What it is**: the result of a second-factor challenge: `NotEnrolled = 0`, `VerifiedTotp = 1`, `VerifiedRecoveryCode = 2` (`ITwoFactorAuthenticator.cs:237,240,243`). -- **Depends on**: nothing first-party. -- **Concept**: same explicitly-numbered, wire-stable outcome-enum shape used throughout the domain event taxonomy (see `DomainEntityState` in group-02); `NotEnrolled = 0` is the default/no-op value, matching that convention. -- **Walkthrough**: three values; `NotEnrolled` means the account has no active second factor so nothing was challenged, `VerifiedTotp` means a time-based code from the authenticator app verified, `VerifiedRecoveryCode` means a single-use recovery code verified and was spent. -- **Why it's built this way**: collapsing the challenge result to one enum lets `AuthenticationServiceBase` decide, in one switch, whether to continue sign-in unchanged (`NotEnrolled`) or stamp an `mfa` claim on the issued token (the two verified cases). -- **Where it's used**: returned by [ITwoFactorAuthenticator](#itwofactorauthenticator).`ChallengeAsync`, read by `AuthenticationServiceBase.cs` (7 sites) and `TwoFactorAuthenticator.cs` (7 sites), and by the disable/regenerate handler bases. - -### TwoFactorSettings -> MMCA.Common.Application · `MMCA.Common.Application.Auth.TwoFactor` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/TwoFactorSettings.cs:15` · Level 0 · class - -- **What it is**: the options class bound to `Authentication:TwoFactor`, controlling the TOTP parameters (issuer, digits, period, clock-skew window) and the recovery-code and secret sizing. -- **Depends on**: nothing first-party; `System.ComponentModel.DataAnnotations` (`[Range]`, `[Required]`, `[StringLength]`). -- **Concept**: the same config-bound settings shape as [EmailConfirmationSettings](#emailconfirmationsettings); no new concept. -- **Walkthrough**: `Issuer` (`TwoFactorSettings.cs:275`) defaults to `"MMCA"` and labels the account in the authenticator app. `Digits` (line 279) defaults to 6. `PeriodSeconds` (line 283) defaults to 30. `VerificationWindowSteps` (line 295) defaults to 1, and its remarks (lines 285-293) explain the trade-off: one step widens acceptance to roughly ninety seconds to tolerate clock skew, while a larger window multiplies how many codes are live at once for an attacker guessing. `RecoveryCodeCount` (line 299) defaults to 10. `RecoveryCodeByteLength` (line 306) defaults to 10 bytes (eighty bits). `SecretByteLength` (line 310) defaults to 20 bytes, the RFC 4226 recommendation. -- **Why it's built this way**: every size and window is a documented security/usability trade-off rather than an arbitrary constant, per [ADR-116](https://ivanball.github.io/docs/adr/116-identity-completions-opt-in.html). -- **Where it's used**: read by `TotpTwoFactorService.cs` (2 sites) and registered in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs`. - -### ITwoFactorService -> MMCA.Common.Application · `MMCA.Common.Application.Auth.TwoFactor` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/ITwoFactorService.cs:16` · Level 1 · interface +- **Walkthrough**: `SectionName` (`PermissionGrantSettings.cs:12`) is `"Authentication:PermissionGrants"`. `CacheSeconds` (line 25) defaults to 300 (five minutes), is range-validated to 5-3600 (line 24), and its remarks (lines 17-23) spell out two things. First the trade-off: this is the bound on how stale another replica may be after an edit, since invalidation is per process. Second the failure behavior: each cached entry lives three intervals, so the snapshot outlives the gap before the next reload and survives up to two failed reloads, after which it stops answering and stored grants fail closed. `DataSourceName` (line 33, `[Required]` at line 32) defaults to `"Default"`, naming the logical data source a modular host points at its Identity database. `KnownRoles` (line 45) defaults to an empty list, and its remarks (lines 39-44) explain why it must be configured: neither `IPermissionRegistry` (which answers questions about a role but does not list roles) nor the grant table (which only knows roles that already have a grant) can enumerate roles on its own; left empty, the surface still works but lists nothing until the first grant exists. +- **Why it's built this way**: a five-minute cache treats a permission grant as an administrative change rather than a per-request one, trading a shorter staleness window for more database reads if lowered. The three-interval entry lifetime means a transient store outage costs nothing for two reload cycles, while a longer one degrades to denying stored grants (compiled permissions are unaffected, see [LayeredPermissionRegistry](#layeredpermissionregistry)) rather than serving an arbitrarily old snapshot. +- **Where it's used**: read by `PermissionGrantCache.cs`, `PermissionGrantRefreshService.cs`, `EFPermissionGrantStore.cs`, `StoredPermissionRoleAdministrationService.cs`, `ApplicationDbContext.cs`, and `DesignTimeDbContextHelper.cs` (all under `MMCA.Common/Source/Core/MMCA.Common.Infrastructure`); registered in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs`; pinned by `PermissionGrantModelGateTests.cs` and `PermissionGrantCacheTests.cs` in `MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests`. -- **What it is**: the TOTP/authenticator-app abstraction: mints secrets, builds the provisioning URI, verifies codes, and generates/hashes/matches recovery codes. -- **Depends on**: [RecoveryCodeSet](#recoverycodeset) (return type of `GenerateRecoveryCodes`). -- **Concept introduced**: a service interface that isolates the cryptographic mechanics of TOTP (RFC 6238-style time-based codes) from the account-state orchestration that lives in [ITwoFactorAuthenticator](#itwofactorauthenticator). `[Rubric §1, SOLID]` (assesses single-responsibility separation): this interface only knows about codes and secrets, never about a user account or a store. -- **Walkthrough**: `GenerateSecret()` (`ITwoFactorService.cs:337`) mints a fresh Base32 secret. `BuildProvisioningUri(secret, accountName)` (line 352) builds the `otpauth://totp/...` URI an authenticator app scans as a QR code; it is deliberately returned as `string` rather than `System.Uri` (the `[SuppressMessage]` at lines 348-351 explains that round-tripping through `Uri` would re-normalize the percent-encoding the app parses). `VerifyCode(secret, code)` (line 361) checks a code within the configured window. `GenerateRecoveryCodes()` (line 368) returns a fresh [RecoveryCodeSet](#recoverycodeset). `HashRecoveryCode(code)` (line 376) hashes one plaintext code the way the store keeps it. `TryMatchRecoveryCode(code, storedHashes, out matchedHash)` (line 385) compares in fixed time to avoid a timing side-channel on which stored hash matched. -- **Why it's built this way**: keeping code verification, secret generation, and recovery-code hashing behind one narrow interface means [ITwoFactorAuthenticator](#itwofactorauthenticator) and the handler bases never touch a raw cryptographic primitive directly. -- **Where it's used**: implemented by `TotpTwoFactorService` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TotpTwoFactorService.cs`), consumed by the enrollment/regeneration handler bases in `MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/TwoFactor/` and by `TwoFactorAuthenticator.cs`, and registered in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs`. +### SessionStampingTokenService +> MMCA.Common.Application · `MMCA.Common.Application.Auth.Sessions` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:448` · Level 1 · class (private sealed, nested) + +- **What it is**: a pass-through [ITokenService](#itokenservice) that appends the current refresh session's `sid` claim and, when a second factor verified this request, an `mfa` claim to every access token minted while it is armed, and behaves as the plain inner service the rest of the time. It is a `private sealed class` nested inside [AuthSessionIssuer](#authsessionissuer) (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:448`). +- **Depends on**: [ITokenService](#itokenservice) (the contract it implements and the inner instance it wraps, `AuthSessionIssuer.cs:448`), [AuthClaimTypes](#authclaimtypes) for the `sid` and `mfa` claim names (`:484`, `:489`), and the BCL (`System.Security.Claims`, `System.Globalization.CultureInfo`). +- **Concept introduced: a decorator used to make a new claim additive.** `[Rubric §2, Design Patterns]` assesses idiomatic pattern use, and this is the Decorator pattern applied to a very specific compatibility problem. Access tokens need to name the session they belong to (and, when relevant, the second factor that satisfied a step-up), but the claim set is produced by the app's own `CreateAccessToken` hook. The obvious fix, adding parameters to that hook, is a compile break in every consumer for claims the app has no decision to make about; the type's own remarks say exactly this (`AuthSessionIssuer.cs:443-446`). Wrapping the token service instead means the issuer arms the wrapper around the hook call and the claims appear in tokens minted by subclasses that were never edited. `[Rubric §15, Best Practices & Code Quality]` is the payoff: an additive protocol change with a zero-line consumer diff, twice over. +- **Walkthrough**: a primary constructor takes the `inner` service (`AuthSessionIssuer.cs:448`). + - `Guid? CurrentSessionId { get; set; }` (`:451`) is the session arming switch: a session id stamps, `null` mints unchanged. + - `string? CurrentMultiFactorMethod { get; set; }` (`:457`) is the second-factor arming switch, set only when a second factor really verified for this request. The remarks on the arming method explain why plain mutable properties are safe here (`:126-129`): the issuer is resolved per request (scoped, like the store it saves through) and one request mints one token at a time. + - `AccessTokenLifetime` (`:460`) and `RefreshTokenLifetime` (`:463`) forward straight to `inner`, so the lifetime callers read is still the JWT settings' value. + - `GenerateAccessToken(...)` (`:466-493`) is the only member with behavior. When neither switch is armed it delegates verbatim (`:473-476`). Otherwise it copies the app's `additionalClaims` into a new `List` (`:478`, so the caller's sequence is never mutated), then independently appends `AuthClaimTypes.SessionId` formatted as `sessionId.ToString("D", CultureInfo.InvariantCulture)` when `CurrentSessionId` is set (`:480-485`, the `"D"` format being the canonical hyphenated Guid form `ClaimsPrincipalExtensions.FindSessionId` parses back, see [ClaimsPrincipalExtensions](#claimsprincipalextensions)) and `AuthClaimTypes.MultiFactor` set to the raw method string when `CurrentMultiFactorMethod` is set (`:487-490`), before delegating with the extended list (`:492`). The two claims are independent: a token can carry either, both, or neither. + - `GenerateRefreshToken()` (`:496`) and `GetPrincipalFromExpiredToken(string token)` (`:499-500`) are plain forwards. +- **Why it's built this way**: putting the stamping behind an `ITokenService` rather than inside the caller's own method keeps the app hook's signature and semantics untouched while still guaranteeing both claims on the tokens the framework's own flows mint. The escape hatch still applies: an app that mints from its own injected `ITokenService` reference produces a valid token with neither claim, and can restore them by overriding `CreateAccessTokenForSession` (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:469-470`). +- **Where it's used**: constructed once per issuer instance (`AuthSessionIssuer.cs:50`) and exposed as [IAuthSessionIssuer](#iauthsessionissuer)`.TokenService` (`:53`), which [AuthenticationServiceBase](#authenticationservicebasetuser) surfaces to subclasses as its protected `TokenService` property (`AuthenticationServiceBase.cs:98`, whose remarks at `:88-97` state that minting through the property is what puts `sid` on the token). Both switches are armed and disarmed by `AuthSessionIssuer.MintForSession` (`AuthSessionIssuer.cs:130-145`, with the `finally` at `:140-144` guaranteeing both disarm even when the hook throws), which the base calls from `CreateAccessTokenForSession` with its `_multiFactorMethod` field (`AuthenticationServiceBase.cs:469-470`). ### LayeredPermissionRegistry > MMCA.Common.Application · `MMCA.Common.Application.Auth.Permissions` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Permissions/LayeredPermissionRegistry.cs:30` · Level 1 · class @@ -2575,7 +2863,7 @@ live in later groups; this chapter is the engine those endpoints call into. - **What it is**: an `IPermissionRegistry` decorator that unions the compiled-into-code permission set with the stored (database-granted) set, so a role's effective permissions are "compiled OR granted", never a replacement of one by the other. - **Depends on**: `IPermissionRegistry` (the inner, compiled registry), [IPermissionGrantCache](#ipermissiongrantcache) (the stored-grant cache). - **Concept introduced**: the decorator pattern applied to a registry lookup. `[Rubric §2, Design Patterns]` (assesses whether a pattern is used idiomatically and solves a real problem): `LayeredPermissionRegistry` wraps `inner` without changing its contract, adding a second data source transparently to every caller of `IPermissionRegistry`. -- **Walkthrough**: the type is a primary-constructor class taking `inner` and `grants` (`LayeredPermissionRegistry.cs:406-408`). `GetPermissions(role)` (lines 411-428) reads both layers; when the stored set is empty it returns the compiled set as-is (no allocation), and only when both layers contribute does it materialize a `HashSet` union, per the comment at lines 421-423: this call is a reporting path (administration surface, diagnostics), not the hot authorization path. `HasPermission(roles, permission)` (lines 431-442) is that hot path: it short-circuits on the compiled registry first (`inner.HasPermission`), and only falls through to a per-role stored-grant lookup (`grants.GetPermissions(role).Contains(permission)`) when the compiled check fails; the caller's `roles` sequence is materialized once (line 438) because both passes would otherwise enumerate it twice. +- **Walkthrough**: the type is a primary-constructor class taking `inner` and `grants` (`LayeredPermissionRegistry.cs:30-32`). `GetPermissions(role)` (lines 35-52) reads both layers; when the stored set is empty it returns the compiled set as-is (no allocation), and only when both layers contribute does it materialize a `HashSet` union, per the comment at lines 45-47: this call is a reporting path (administration surface, diagnostics), not the hot authorization path. `HasPermission(roles, permission)` (lines 55-66) is that hot path: it short-circuits on the compiled registry first (`inner.HasPermission`), and only falls through to a per-role stored-grant lookup (`grants.GetPermissions(role).Contains(permission)`) when the compiled check fails; the caller's `roles` sequence is materialized once (line 62) because both passes would otherwise enumerate it twice. - **Why it's built this way**: separating the always-cheap compiled check from the stored-grant fallback keeps the common case (a permission granted in code) allocation-free and single-pass, per [ADR-116](https://ivanball.github.io/docs/adr/116-identity-completions-opt-in.html), which records the opt-in database-grant layer as additive on top of the existing compiled registry. - **Where it's used**: registered as the `IPermissionRegistry` implementation in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs` (3 sites) when database grants are enabled; exercised directly in `MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Auth/LayeredPermissionRegistryTests.cs`. @@ -2585,19 +2873,27 @@ live in later groups; this chapter is the engine those endpoints call into. - **What it is**: the static `Error`-factory class for the email-confirmation flow: one factory for a sign-in refused because the address is unconfirmed, one for every failing token redemption. - **Depends on**: `Error` (the Result-pattern error type taught in the primer). - **Concept**: the same named-code-plus-factory-method convention used by every `*Errors` class in this codebase (see [AuthErrorCodes](#autherrorcodes) for the general shape); no new concept here. -- **Walkthrough**: `EmailNotConfirmedCode` (`EmailConfirmationErrors.cs:466`) and `InvalidTokenCode` (line 469) are the two error codes. `EmailNotConfirmed(source)` (lines 479-482) returns `Error.Unauthorized`, distinct from `Auth.InvalidCredentials` so a UI can offer to resend the link. `InvalidToken(source)` (lines 487-490) is the single rejection every failing redemption (unknown, expired, mismatched, or attempt-capped token) collapses to. +- **Walkthrough**: `EmailNotConfirmedCode` (`EmailConfirmationErrors.cs:12`) and `InvalidTokenCode` (line 15) are the two error codes. `EmailNotConfirmed(source)` (lines 25-28) returns `Error.Unauthorized`, distinct from `Auth.InvalidCredentials` so a UI can offer to resend the link. `InvalidToken(source)` (lines 33-36) is the single rejection every failing redemption (unknown, expired, mismatched, or attempt-capped token) collapses to. - **Why it's built this way**: the doc comment on `EmailNotConfirmed` (lines 474-478) explains it is reachable only after the password has already been proved, so the message can safely tell the account owner what is wrong rather than help an address sweeper enumerate valid accounts; `InvalidToken` collapsing every failure mode to one message denies an attacker any signal about which reason a redemption failed for. - **Where it's used**: raised by `ConfirmEmailHandlerBase.cs` (2 sites) and `AuthenticationServiceBase.cs`, and by `EmailConfirmationTokenService.cs`; asserted against in `EmailConfirmationHandlerBaseTests.cs`, `EmailConfirmationTokenServiceTests.cs`, and ADC's `ConfirmEmailHandlerTests.cs`. -### TwoFactorErrors -> MMCA.Common.Application · `MMCA.Common.Application.Auth.TwoFactor` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/TwoFactorErrors.cs:15` · Level 2 · class +### IAuthSessionIssuer +> MMCA.Common.Application · `MMCA.Common.Application.Auth.Sessions` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/IAuthSessionIssuer.cs:26` · Level 3 · interface -- **What it is**: the static `Error`-factory class for the two-factor flow: challenge-required, challenge-invalid, not-enrolled, and enrollment-missing. -- **Depends on**: `Error`. -- **Concept**: same convention as [EmailConfirmationErrors](#emailconfirmationerrors); no new concept. -- **Walkthrough**: four codes (`TwoFactorErrors.cs:514,517,520,523`). `TwoFactorRequired(source)` (lines 528-531) is `Error.Unauthorized`, asking the caller to supply a code or a recovery code. `TwoFactorInvalid(source)` (lines 541-544) is also `Error.Unauthorized`; its remarks (lines 536-540) note it is deliberately the same message for a wrong TOTP code and a wrong recovery code, since telling them apart would leak whether a presented value was recovery-code shaped. `TwoFactorNotEnrolled(source)` (lines 549-552) and `TwoFactorEnrollmentMissing(source)` (lines 557-560) are both `Error.Conflict`, for an action attempted on an account with no active factor and on an enrollment that was never started, respectively. -- **Why it's built this way**: `Conflict` versus `Unauthorized` is used to distinguish "your account state does not support this action" from "your credentials did not verify", matching the semantics the rest of the auth surface uses for those two error kinds. -- **Where it's used**: raised by `ConfirmTwoFactorEnrollmentHandlerBase.cs` (2 sites), `TwoFactorAuthenticator.cs` (2 sites), `DisableTwoFactorHandlerBase.cs`, and `RegenerateRecoveryCodesHandlerBase.cs`; asserted in `TwoFactorHandlerBaseTests.cs`, `AuthenticationServiceIdentityCompletionsTests.cs`, and `TwoFactorAuthenticatorTests.cs`. +- **What it is**: the contract that issues, rotates and revokes the access/refresh token pair behind a signed-in device. Its summary (`IAuthSessionIssuer.cs:7-12`) lists what it owns: the multi-device [RefreshSession](#refreshsession) rows (hashed at rest), BR-205 rotation with BR-206 reuse detection, the per-user live-session cap, and the `sid`/`mfa` claims stamped on the access token minted for a session. +- **Depends on**: [ITokenService](#itokenservice), [AuthenticationResponse](#authenticationresponse), [RefreshSessionSummaryResponse](#refreshsessionsummaryresponse), `Result`/`Result` (the Result pattern taught in the primer), and the `UserIdentifierType` alias. +- **Concept introduced: splitting "who is signed in" from "what they are handed".** `[Rubric §1, SOLID]` assesses whether each type has one reason to change. The remarks (`IAuthSessionIssuer.cs:15-19`) draw the line explicitly: credentials, lockout, the second factor and the app's own gates stay with [AuthenticationServiceBase](#authenticationservicebasetuser), which calls the issuer only once a caller has been proved, and the base's own summary states the same split from the other side (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:40-43`: "This class decides who is signed in; IAuthSessionIssuer decides what they are handed"). The access token's claim set still belongs to the app, so the issuer takes it as a `Func mintAccessToken` callback: the session id the token must carry exists only once the session has been opened or rotated. `[Rubric §11, Security]` assesses credential lifecycle handling; the rotation and reuse rules are stated on the contract itself, not left to an implementation. +- **Walkthrough**: + - `TokenService` (`IAuthSessionIssuer.cs:34`): the [ITokenService](#itokenservice) an app mints through. While the issuer is minting for a session it appends `sid` (and `mfa` when a second factor verified); at any other time it is the plain registered service (doc at `:29-32`). + - `IssueAsync(userId, mintAccessToken, ipAddress, userAgent, ct)` (`:46`): opens a new session, evicting the oldest live one when the per-user cap is full, persists it and returns the pair; the user's other sessions are untouched (`:37-38`). + - `RotateAsync(userId, refreshToken, mintAccessToken, ipAddress, userAgent, ct)` (`:67`): BR-205 rotation. An unknown or expired token fails alone; an already-revoked token, or one a concurrent request rotated first, is the BR-206 reuse signal and revokes every live session the user holds. Every rejection carries the same `Auth.InvalidRefreshToken` error (`:54-58`). + - `MintForSession(sessionId, multiFactorMethod, mintAccessToken)` (`:83`): runs the callback with `TokenService` armed to stamp `sid` and, when not null, `mfa`. + - `SignOutAsync(userId, refreshToken, ct)` (`:94`): revokes the one live session behind the presented token when it belongs to the user; when the token is absent or identifies no live session of this user, every live session is revoked instead (`:86-88`). + - `SignOutEverywhereAsync(userId, ct)` (`:100`): revokes every live session and saves. + - `ListActiveAsync(userId, currentSessionId, ct)` (`:110`): live sessions newest first, with expired-but-unrevoked rows left out because a device list must not offer a device that can no longer authenticate (`:103-104`). + - `RevokeSessionAsync(userId, sessionId, ct)` (`:124`): another account's session id and a never-existing id both answer `Auth.SessionNotFound`; an already-revoked session is a success that writes nothing (`:116-118`). +- **Why it's built this way**: the remarks (`IAuthSessionIssuer.cs:22-23`) give the lifetime rule: scoped, like the session store it writes through, so a sign-in and its session insert share the request's unit of work. Keeping the session rules behind one interface means the authentication workflow "never touches a session row" (`AuthenticationServiceBase.cs:43`). The multi-device session model is recorded in [ADR-097](https://ivanball.github.io/docs/adr/097-multi-device-refresh-sessions.html). +- **Where it's used**: implemented by [AuthSessionIssuer](#authsessionissuer); registered `TryAddScoped` in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:176`; injected into [AuthenticationServiceBase](#authenticationservicebasetuser) (`AuthenticationServiceBase.cs:68`), which delegates to it from `TokenService` (`:98`), `RefreshTokenAsync` (`:337`), sign-out (`:363`, `:379`), session listing (`:391`) and revocation (`:406`), `IssueTokensAsync` (`:425`) and `CreateAccessTokenForSession` (`:470`). ADC's `AuthenticationService` takes it as a constructor argument and passes it to the base (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:56,63`). ### IEmailConfirmationTokenService > MMCA.Common.Application · `MMCA.Common.Application.Auth.EmailConfirmation` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/EmailConfirmation/IEmailConfirmationTokenService.cs:15` · Level 3 · interface @@ -2605,40 +2901,72 @@ live in later groups; this chapter is the engine those endpoints call into. - **What it is**: the token-lifecycle abstraction for email confirmation: issue a token for an address, validate and consume one presented back. - **Depends on**: `UserIdentifierType` (the module's identifier-type alias), `Result`. - **Concept**: an issue/validate-and-consume token contract, mirroring the shape `IPasswordResetTokenService` uses elsewhere in this group; no new concept. -- **Walkthrough**: `IssueAsync(email, userId, cancellationToken)` (`IEmailConfirmationTokenService.cs:594`) replaces any token already outstanding for that address, so there is one active token per address, and fails only when the per-address request throttle ([EmailConfirmationSettings](#emailconfirmationsettings).`MaxRequestsPerEmail`/`RequestWindowMinutes`) has been exceeded. `ValidateAndConsumeAsync(email, token, cancellationToken)` (line 607) validates against the outstanding token and consumes it on success so it never redeems twice, collapsing unknown/expired/mismatched/attempt-capped failures to the single [EmailConfirmationErrors](#emailconfirmationerrors).`InvalidToken` error. +- **Walkthrough**: `IssueAsync(email, userId, cancellationToken)` (`IEmailConfirmationTokenService.cs:28`) replaces any token already outstanding for that address, so there is one active token per address, and fails only when the per-address request throttle ([EmailConfirmationSettings](#emailconfirmationsettings).`MaxRequestsPerEmail`/`RequestWindowMinutes`) has been exceeded. `ValidateAndConsumeAsync(email, token, cancellationToken)` (line 41) validates against the outstanding token and consumes it on success so it never redeems twice, collapsing unknown/expired/mismatched/attempt-capped failures to the single [EmailConfirmationErrors](#emailconfirmationerrors).`InvalidToken` error. - **Why it's built this way**: consuming on success is what makes redemption single-use; collapsing every failure reason to one error, per its own doc comment (lines 603-606), denies an attacker signal about why a guess failed. - **Where it's used**: implemented by `EmailConfirmationTokenService` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs`), consumed by `SendEmailConfirmationHandlerBase.cs` and `ConfirmEmailHandlerBase.cs`, and by ADC's `SendEmailConfirmationHandler.cs`/`ConfirmEmailHandler.cs`; registered in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs`. -### ITwoFactorAuthenticator -> MMCA.Common.Application · `MMCA.Common.Application.Auth.TwoFactor` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/ITwoFactorAuthenticator.cs:24` · Level 3 · interface - -- **What it is**: the account-level orchestration for the second-factor challenge at sign-in, one level above [ITwoFactorService](#itwofactorservice)'s pure cryptographic operations. -- **Depends on**: `UserIdentifierType`, [TwoFactorOutcome](#twofactoroutcome), [TwoFactorErrors](#twofactorerrors), `Result`. -- **Concept**: the service-orchestrates-over-a-crypto-primitive layering also seen with [IEmailConfirmationTokenService](#iemailconfirmationtokenservice) versus its store; no new concept. -- **Walkthrough**: `ChallengeAsync(userId, code, cancellationToken)` (`ITwoFactorAuthenticator.cs:644-647`) returns [TwoFactorOutcome](#twofactoroutcome).`NotEnrolled` when the account has no active second factor (sign-in continues unchanged, no `mfa` claim), the verified outcome when a code satisfies the challenge, or a failure: [TwoFactorErrors](#twofactorerrors).`TwoFactorRequiredCode` when a code was needed and none arrived, [TwoFactorErrors](#twofactorerrors).`TwoFactorInvalidCode` when one arrived and did not verify (doc comment lines 636-643). -- **Why it's built this way**: returning an outcome enum rather than a bare boolean lets `AuthenticationServiceBase` distinguish "no second factor configured" from "verified", which matters for whether it stamps an `mfa` claim on the issued token. -- **Where it's used**: implemented by `TwoFactorAuthenticator` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs`), called from `AuthenticationServiceBase.cs` (2 sites) and registered in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs`. - -### ITwoFactorStore -> MMCA.Common.Application · `MMCA.Common.Application.Auth.TwoFactor` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/ITwoFactorStore.cs:24` · Level 3 · interface - -- **What it is**: the persistence abstraction for one account's two-factor state: enrollment lifecycle, recovery-code replacement, and single recovery-code consumption. -- **Depends on**: `UserIdentifierType`, `ITwoFactorUserState` (the read shape, taught elsewhere in this group), `Result`. -- **Concept**: a two-phase enrollment (start, then complete) so an abandoned setup cannot lock an account out; the same two-phase shape a reader has already seen in other enrollment-style flows in this group. -- **Walkthrough**: `GetAsync(userId, cancellationToken)` (`ITwoFactorStore.cs:678`) returns `null` only when the account itself does not exist; an account that never enrolled still returns a state with `IsTwoFactorEnabled = false`. `StartEnrollmentAsync(userId, secret, cancellationToken)` (line 688) stores a freshly minted secret WITHOUT activating the factor, per its remarks (lines 681-683), so an abandoned enrollment cannot lock the user out. `CompleteEnrollmentAsync(userId, recoveryCodeHashes, cancellationToken)` (lines 698-701) activates the factor and stores the first recovery-code hashes, called only after a code from the stored secret has verified. `DisableAsync(userId, cancellationToken)` (line 710) turns the factor off and clears the secret and recovery codes, so a later re-enrollment starts from a fresh secret. `ReplaceRecoveryCodesAsync(userId, recoveryCodeHashes, cancellationToken)` (lines 720-723) replaces the whole set, never appends, since regeneration exists to invalidate a list the user believes compromised. `ConsumeRecoveryCodeAsync(userId, recoveryCodeHash, cancellationToken)` (lines 737-740) removes one hash to spend it, and its remarks (lines 728-731) flag this as security-critical: it must persist before the sign-in it authorized is answered, or the same code could sign in twice. -- **Why it's built this way**: splitting `StartEnrollmentAsync`/`CompleteEnrollmentAsync` prevents a half-finished enrollment from ever becoming an active, un-verified second factor. -- **Where it's used**: implemented and called by `TwoFactorAuthenticator.cs` (2 sites) through the enrollment/disable/regenerate handler bases in `MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/TwoFactor/`, and registered in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs`. - ### IPermissionGrantStore > MMCA.Common.Application · `MMCA.Common.Application.Auth.Permissions` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Permissions/IPermissionGrantStore.cs:16` · Level 4 · interface - **What it is**: the persistence abstraction behind the stored-grant layer: read every grant, read one role's grants, grant a permission, revoke a permission. - **Depends on**: `PermissionGrant` (the persisted grant row), `Result`. - **Concept**: an idempotent grant/revoke pair, so a click repeated by an administration UI is not an error; no new concept beyond that idempotency guarantee. -- **Walkthrough**: `GetAllAsync(cancellationToken)` (`IPermissionGrantStore.cs:768`) reads every stored row, used to build the in-memory snapshot the authorization path reads. `GetPermissionsAsync(role, cancellationToken)` (line 776) reads one role's stored permissions for the administration surface. `GrantAsync(role, permission, grantedBy, cancellationToken)` (lines 787-791) grants a permission, succeeding and writing nothing when the role already has it stored (doc comment lines 778-780). `RevokeAsync(role, permission, cancellationToken)` (line 805) removes a stored grant, also idempotent; its remarks (lines 796-800) are explicit that this removes only the STORED grant, never a compiled-into-code one, which is exactly what keeps a data edit from disabling an endpoint the code guarantees. +- **Walkthrough**: `GetAllAsync(cancellationToken)` (`IPermissionGrantStore.cs:23`) reads every stored row, used to build the in-memory snapshot the authorization path reads. `GetPermissionsAsync(role, cancellationToken)` (line 31) reads one role's stored permissions for the administration surface. `GrantAsync(role, permission, grantedBy, cancellationToken)` (lines 42-46) grants a permission, succeeding and writing nothing when the role already has it stored (doc comment lines 33-36). `RevokeAsync(role, permission, cancellationToken)` (line 60) removes a stored grant, also idempotent; its remarks (lines 51-55) are explicit that this removes only the STORED grant, never a compiled-into-code one, which is exactly what keeps a data edit from disabling an endpoint the code guarantees. - **Why it's built this way**: the "stored grant is additive, never subtractive over compiled permissions" rule enforced here is the same invariant [LayeredPermissionRegistry](#layeredpermissionregistry).`HasPermission` implements: the compiled check always wins first, and revoking a stored grant can never take away a compiled-in permission. - **Where it's used**: implemented by `EFPermissionGrantStore` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/EFPermissionGrantStore.cs`), read by `PermissionGrantCache.cs` (2 sites) to build the [IPermissionGrantCache](#ipermissiongrantcache) snapshot, called by [IRoleAdministrationService](#iroleadministrationservice)'s implementation `StoredPermissionRoleAdministrationService.cs` (2 sites), and registered in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs`. +### AuthSessionIssuer +> MMCA.Common.Application · `MMCA.Common.Application.Auth.Sessions` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:39` · Level 6 · class (sealed) + +- **What it is**: the framework's [IAuthSessionIssuer](#iauthsessionissuer): multi-device refresh sessions, hashed at rest (`AuthSessionIssuer.cs:12-14`). Every issue opens its own [RefreshSession](#refreshsession), so signing in on a second device leaves the first one signed in. +- **Depends on**: [ITokenService](#itokenservice) (mints both tokens), [IRefreshSessionStore](#irefreshsessionstore) (the session rows), `IOptions<`[RefreshSessionSettings](#refreshsessionsettings)`>` (the per-user cap), `TimeProvider` (every session instant), all as primary-constructor parameters (`AuthSessionIssuer.cs:39-43`); [RefreshSession](#refreshsession), [AuthenticationResponse](#authenticationresponse), [RefreshSessionSummaryResponse](#refreshsessionsummaryresponse) and `Error`/`Result` from the primer. Its two private helpers are [IssuedSession](#issuedsession) and [SessionStampingTokenService](#sessionstampingtokenservice). +- **Concept introduced: refresh-token rotation with family revocation on reuse.** `[Rubric §11, Security]` assesses how bearer credentials are issued, stored and revoked. The class remarks (`AuthSessionIssuer.cs:17-26`) state the model: the store holds only `RefreshSession.HashToken` digests; rotation revokes the presented session and links it to its successor; presenting an already-rotated token lands on that revoked row, which is the reuse signal that revokes the user's whole live family (BR-206); two requests presenting the same live token at once are covered by the same rule because the rotation is claimed atomically through `IRefreshSessionStore.TryRotateAsync`; and an expired session is not a reuse signal. The practical effect: a stolen-and-replayed token costs the attacker the session and costs the user only a re-sign-in. +- **Walkthrough**: + - **Lifetimes** (`:55-59`): `AccessTokenLifetime` and `RefreshTokenLifetime` read the token service and fall back to 15 minutes and 7 days when the value is non-positive (a test double or a misconfigured host), so the expiry reported to clients matches the JWT's actual `exp` (`:29-32`). + - **`IssueAsync`** (`:62-87`): opens the session first (`OpenSessionAsync`, `:75`), saves (`:81`), and only then calls `mintAccessToken(opened.Value!.SessionId)` (`:84`); the comment at `:73-74` says why: the token carries the session's id in `sid`, and a session has an id only once created. + - **`OpenSessionAsync`** (`:312-338`): generates a refresh token (`:319`), creates the row through `RefreshSession.Create` with `now + RefreshTokenLifetime` (`:320-326`), runs `EnforceSessionCapAsync` (`:334`), stages the insert without saving (`:335`) and returns an [IssuedSession](#issuedsession) (`:337`). + - **`EnforceSessionCapAsync`** (`:416-429`): reads the user's un-revoked rows, keeps the ones active now, orders oldest first (`CreatedAt`, then `Id`), and revokes from the front with `ReasonSessionCap` until there is room for one more (`:425-428`). Its summary (`:407-415`) records the default cap of 10, the 1-1000 startup range, and that expired-but-unrevoked rows do not count and age out through `RefreshSessionCleanupService`. + - **`RotateAsync`** (`:90-123`): `ResolveRotatableSessionAsync` (`:101`), then `RotateSessionAsync` (`:109`), then the mint with the successor's id (`:120`), so the client's current-device marker follows the rotation (comment `:116-118`). + - **`ResolveRotatableSessionAsync`** (`:273-305`): a blank token, an unknown hash, or a row of another user fails alone (`:279-291`); a revoked row revokes every live session with `ReasonReuseDetected` and saves before failing (`:293-300`); an expired row fails alone (`:302-304`). Its summary (`:263-272`) explains why the unknown-hash case must not revoke the family: otherwise anyone holding one of this user's expired access tokens could sign them out everywhere by posting a random token. + - **`RotateSessionAsync`** (`:350-391`): builds the successor, then asks the store to claim the rotation via `TryRotateAsync` (`:374-376`). A lost claim means a concurrent request spent this token first, which is indistinguishable from a replay and gets the replay answer: family revoked, saved, `Auth.InvalidRefreshToken` (`:378-388`). Rotation replaces one session with one, so the cap is not re-evaluated (`:341`). + - **`InvalidRefreshTokenError`** (`:257-261`): the single `Error.Unauthorized("Auth.InvalidRefreshToken", ...)` every failing branch returns, so a caller cannot tell unknown from expired from replayed (`:253-256`). + - **`MintForSession`** (`:130-145`): arms the nested [SessionStampingTokenService](#sessionstampingtokenservice) with the session id and second-factor method, runs the callback, and disarms in a `finally`. + - **`SignOutAsync`** (`:148-174`): revokes only a live session of this user found by token hash (`:162-169`); anything else degrades to revoking every live session rather than reporting success for a revocation that reached nothing (comment `:158-161`, fallback `:172-173`). `SignOutEverywhereAsync` (`:177-182`) always revokes everything. + - **`ListActiveAsync`** (`:191-213`): one query for un-revoked rows, then an in-memory filter to the active ones, newest first, flagging the caller's own session (`:202-211`). + - **`RevokeSessionAsync`** (`:227-251`): the ownership check is the user-scoped `FindByIdAsync` query (`:232`), so another account's id and a missing id produce the same `Auth.SessionNotFound` (`:235-239`); an already-revoked row returns success without writing (`:242-245`), for the reasons in its remarks (`:216-226`). +- **Why it's built this way**: every timestamp comes from the injected `TimeProvider`, so rotation and expiry are testable without a clock, and every user-visible failure on the refresh path collapses to one error so the endpoint leaks nothing about which rule fired. Treating a lost concurrent rotation as a replay keeps one rule for one observable situation. The multi-device model, rotation and reuse detection are recorded in [ADR-097](https://ivanball.github.io/docs/adr/097-multi-device-refresh-sessions.html). +- **Where it's used**: registered as the scoped [IAuthSessionIssuer](#iauthsessionissuer) in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:176` (the comment at `:174-175` says the base delegates every session decision to it); consumed through that interface by [AuthenticationServiceBase](#authenticationservicebasetuser). Constructed directly in `AuthenticationServiceBaseTests.cs`, `AuthenticationServiceIdentityCompletionsTests.cs` and `RefreshSessionManagementTests.cs` (`MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Auth/`) and in ADC's `AuthenticationServiceTests.cs` (`MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/`). + +### RecoveryCodeSet +> MMCA.Common.Application · `MMCA.Common.Application.Auth.TwoFactor` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/ITwoFactorService.cs:97` · Level 0 · record + +- **What it is**: a two-field record pairing a freshly generated set of plaintext recovery codes with their stored hashes. +- **Depends on**: nothing first-party. +- **Concept**: a plaintext/hash pair returned once, at generation time only, so the plaintext never has to be persisted or re-derived. +- **Walkthrough**: `Codes` (`ITwoFactorService.cs:98`) is the plaintext shown to the user once; `Hashes` (line 86) is what the store keeps. +- **Why it's built this way**: keeping the plaintext out of any store means a database compromise cannot recover usable recovery codes, only their hashes. +- **Where it's used**: returned by [ITwoFactorService](#itwofactorservice).`GenerateRecoveryCodes()`, consumed by `TotpTwoFactorService.cs` and exercised in `MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs`. + +### TwoFactorOutcome +> MMCA.Common.Application · `MMCA.Common.Application.Auth.TwoFactor` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/ITwoFactorAuthenticator.cs:47` · Level 0 · enum + +- **What it is**: the result of a second-factor challenge: `NotEnrolled = 0`, `VerifiedTotp = 1`, `VerifiedRecoveryCode = 2` (`ITwoFactorAuthenticator.cs:50,53,56`). +- **Depends on**: nothing first-party. +- **Concept**: same explicitly-numbered, wire-stable outcome-enum shape used throughout the domain event taxonomy (see `DomainEntityState` in group-02); `NotEnrolled = 0` is the default/no-op value, matching that convention. +- **Walkthrough**: three values; `NotEnrolled` means the account has no active second factor so nothing was challenged, `VerifiedTotp` means a time-based code from the authenticator app verified, `VerifiedRecoveryCode` means a single-use recovery code verified and was spent. +- **Why it's built this way**: collapsing the challenge result to one enum lets `AuthenticationServiceBase` decide, in one switch, whether to continue sign-in unchanged (`NotEnrolled`) or stamp an `mfa` claim on the issued token (the two verified cases). +- **Where it's used**: returned by [ITwoFactorAuthenticator](#itwofactorauthenticator).`ChallengeAsync`, read by `AuthenticationServiceBase.cs` (7 sites) and `TwoFactorAuthenticator.cs` (7 sites), and by the disable/regenerate handler bases. + +### TwoFactorSettings +> MMCA.Common.Application · `MMCA.Common.Application.Auth.TwoFactor` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/TwoFactorSettings.cs:15` · Level 0 · class + +- **What it is**: the options class bound to `Authentication:TwoFactor`, controlling the TOTP parameters (issuer, digits, period, clock-skew window) and the recovery-code and secret sizing. +- **Depends on**: nothing first-party; `System.ComponentModel.DataAnnotations` (`[Range]`, `[Required]`, `[StringLength]`). +- **Concept**: the same config-bound settings shape as [EmailConfirmationSettings](#emailconfirmationsettings); no new concept. +- **Walkthrough**: `Issuer` (`TwoFactorSettings.cs:26`) defaults to `"MMCA"` and labels the account in the authenticator app. `Digits` (line 30) defaults to 6. `PeriodSeconds` (line 34) defaults to 30. `VerificationWindowSteps` (line 46) defaults to 1, and its remarks (lines 40-44) explain the trade-off: one step widens acceptance to roughly ninety seconds to tolerate clock skew, while a larger window multiplies how many codes are live at once for an attacker guessing. `RecoveryCodeCount` (line 50) defaults to 10. `RecoveryCodeByteLength` (line 57) defaults to 10 bytes (eighty bits). `SecretByteLength` (line 61) defaults to 20 bytes, the RFC 4226 recommendation. +- **Why it's built this way**: every size and window is a documented security/usability trade-off rather than an arbitrary constant, per [ADR-116](https://ivanball.github.io/docs/adr/116-identity-completions-opt-in.html). +- **Where it's used**: read by `TotpTwoFactorService.cs` (2 sites) and registered in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs`. + ### IPasswordHasher > MMCA.Common.Application · `MMCA.Common.Application.Interfaces.Infrastructure.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Auth/IPasswordHasher.cs:6` · Level 0 · interface @@ -2647,7 +2975,7 @@ live in later groups; this chapter is the engine those endpoints call into. - **Concept introduced: hash and salt kept apart.** `[Rubric §11, Security]` assesses credential handling. Returning the hash and the salt as two distinct `byte[]` members (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Auth/IPasswordHasher.cs:11`) rather than one concatenated blob keeps the storage contract explicit: the caller persists two columns, and `VerifyPassword` (`:18`) is unambiguous about what it re-derives and compares. Because the algorithm and its parameters live entirely behind this interface, they can be strengthened without touching a single Application handler ([ADR-032](https://ivanball.github.io/docs/adr/032-password-hashing.html) sets the current hashing policy, applied inside [PasswordHasher](#passwordhasher)). - **Walkthrough**: `(byte[] Hash, byte[] Salt) HashPassword(string password)` (`:11`) returns a named value tuple the caller stores as two fields. `bool VerifyPassword(string password, byte[] hash, byte[] salt)` (`:18`) re-derives from the supplied salt and compares. The interface declares no iteration count, algorithm identifier, or format version: every one of those is the concrete's business. - **Why it's built this way**: a two-method port is the `[Rubric §1, SOLID]` dependency-inversion story in miniature. Swapping the key-derivation function or raising the iteration count is an Infrastructure change, invisible to the register, login, and change-password use cases that only ever see this contract. -- **Where it's used**: constructor-injected into [AuthenticationServiceBase](#authenticationservicebasetuser) (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:77`), which calls `VerifyPassword` on the login path (`:159`) and `HashPassword` on registration (`:210`); into the shared [ChangePasswordHandlerBase](group-14-module-system-composition.md#changepasswordhandlerbasetuser-tcommand), which verifies the current password (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:79`) before hashing the new one (`:61`); into [ResetPasswordHandlerBase](group-14-module-system-composition.md#resetpasswordhandlerbasetuser-tcommand), which hashes the replacement after the token redeems (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:94`); and into the per-app Identity services, handlers and seeders that derive from those, for example ADC's [AuthenticationService](group-24-identity-module.md#authenticationservice) (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:51`), its `ChangePasswordHandler` (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ChangePassword/ChangePasswordHandler.cs:26`) and its module seeder (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/IdentityModuleSeeder.cs:34`, which needs the hasher because seed data carries plaintext credentials). +- **Where it's used**: constructor-injected into [AuthenticationServiceBase](#authenticationservicebasetuser) (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:65`), which calls `VerifyPassword` on the login path (`:159`) and `HashPassword` on registration (`:210`); into the shared [ChangePasswordHandlerBase](group-14-module-system-composition.md#changepasswordhandlerbasetuser-tcommand), which verifies the current password (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:95`) before hashing the new one (`:61`); into [ResetPasswordHandlerBase](group-14-module-system-composition.md#resetpasswordhandlerbasetuser-tcommand), which hashes the replacement after the token redeems (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:94`); and into the per-app Identity services, handlers and seeders that derive from those, for example ADC's [AuthenticationService](group-24-identity-module.md#authenticationservice) (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:52`), its `ChangePasswordHandler` (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ChangePassword/ChangePasswordHandler.cs:26`) and its module seeder (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/IdentityModuleSeeder.cs:34`, which needs the hasher because seed data carries plaintext credentials). ### ISoftDeletedUserValidator > MMCA.Common.Application · `MMCA.Common.Application.Interfaces.Infrastructure.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Auth/ISoftDeletedUserValidator.cs:7` · Level 0 · interface @@ -2664,9 +2992,60 @@ live in later groups; this chapter is the engine those endpoints call into. - **What it is**: the token-minting port called by the login and refresh use cases. It builds a signed JWT access token from explicit identity facts, generates an opaque refresh token, publishes the two token lifetimes, and recovers the `ClaimsPrincipal` from an expired-but-validly-signed access token. - **Depends on**: `System.Security.Claims` (BCL, `:1`) and the `UserIdentifierType` alias. Its Infrastructure adapter is [TokenService](#tokenservice), which signs with the RSA key surfaced by [IJwksProvider](#ijwksprovider); [SessionStampingTokenService](#sessionstampingtokenservice) is a second, internal implementation that decorates the first. - **Concept introduced: token creation as an Infrastructure detail.** `[Rubric §3, Clean Architecture]` assesses whether library-specific types stay out of the inner layers: the handlers call this contract and never see `System.IdentityModel.Tokens.Jwt`. `GetPrincipalFromExpiredToken` (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Auth/ITokenService.cs:48`) is the linchpin of the refresh flow: it validates the signature while deliberately ignoring lifetime, so an expired access token can still identify the user whose tokens are being rotated, returning `null` when the token is invalid (`:47`). -- **Walkthrough**: `GenerateAccessToken(UserIdentifierType userId, string email, string role, string fullName, IEnumerable? additionalClaims = null)` (`:17-22`) takes the minimum claim set as typed parameters rather than a ready-made principal, with an escape hatch for module-specific claims. `GenerateRefreshToken()` (`:26`) returns a cryptographically random base64 string. Two **default interface members** publish the lifetimes: `AccessTokenLifetime` (`:33`, defaulting to 15 minutes) and `RefreshTokenLifetime` (`:40`, defaulting to 7 days), both documented as the BR-205 baseline. The comments at `:28-32` and `:35-39` explain the split: the real implementation derives both from the bound JWT settings, so the expiry reported to a client matches the token's actual `exp`, while the defaults keep hand-written test doubles on the baseline instead of forcing every double to implement two more members. That derivation is visible in the concrete: `TimeSpan.FromMinutes(_jwtSettings.AccessTokenExpirationMinutes)` and `TimeSpan.FromDays(_jwtSettings.RefreshTokenExpirationDays)` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:151` and `:129`). `GetPrincipalFromExpiredToken(string token)` (`:48`) closes the set. +- **Walkthrough**: `GenerateAccessToken(UserIdentifierType userId, string email, string role, string fullName, IEnumerable? additionalClaims = null)` (`:17-22`) takes the minimum claim set as typed parameters rather than a ready-made principal, with an escape hatch for module-specific claims. `GenerateRefreshToken()` (`:26`) returns a cryptographically random base64 string. Two **default interface members** publish the lifetimes: `AccessTokenLifetime` (`:33`, defaulting to 15 minutes) and `RefreshTokenLifetime` (`:40`, defaulting to 7 days), both documented as the BR-205 baseline. The comments at `:28-32` and `:35-39` explain the split: the real implementation derives both from the bound JWT settings, so the expiry reported to a client matches the token's actual `exp`, while the defaults keep hand-written test doubles on the baseline instead of forcing every double to implement two more members. That derivation is visible in the concrete: `TimeSpan.FromMinutes(_jwtSettings.AccessTokenExpirationMinutes)` and `TimeSpan.FromDays(_jwtSettings.RefreshTokenExpirationDays)` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:162` and `:129`). `GetPrincipalFromExpiredToken(string token)` (`:48`) closes the set. - **Why it's built this way**: the explicit-parameter overload is a `[Rubric §11, Security]` guardrail. The token's contents are a deliberate list, not whatever claims happened to ride in on an inbound principal. Surfacing the lifetimes through the same port removes the duplication where a caller would hard-code an expiry that could drift from the signed `exp`. Note the consumer still guards: [AuthenticationServiceBase](#authenticationservicebasetuser) falls back to the same 15-minute and 7-day baselines when an implementation reports a non-positive lifetime (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:137-146`). -- **Where it's used**: injected into [AuthenticationServiceBase](#authenticationservicebasetuser) (`AuthenticationServiceBase.cs:76`), which reads the expired principal on refresh (`:278`), mints refresh tokens when opening and rotating sessions (`:627`, `:667`), and re-exposes a *wrapped* instance to subclasses through its `TokenService` property (`:82`). Each app's Identity service mints from that property, for example `MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:153-154` (access token plus the `speaker_id` claim). The rotated pair produced here is what [CookieSessionRefresher](#cookiesessionrefresher) later exchanges on the browser's behalf. +- **Where it's used**: injected into [AuthenticationServiceBase](#authenticationservicebasetuser) (`AuthenticationServiceBase.cs:76`), which reads the expired principal on refresh (`:278`), mints refresh tokens when opening and rotating sessions (`:627`, `:667`), and re-exposes a *wrapped* instance to subclasses through its `TokenService` property (`:82`). Each app's Identity service mints from that property, for example `MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:149-150` (access token plus the `speaker_id` claim). The rotated pair produced here is what [CookieSessionRefresher](#cookiesessionrefresher) later exchanges on the browser's behalf. + +### ITwoFactorService +> MMCA.Common.Application · `MMCA.Common.Application.Auth.TwoFactor` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/ITwoFactorService.cs:16` · Level 1 · interface + +- **What it is**: the TOTP/authenticator-app abstraction: mints secrets, builds the provisioning URI, verifies codes, and generates/hashes/matches recovery codes. +- **Depends on**: [RecoveryCodeSet](#recoverycodeset) (return type of `GenerateRecoveryCodes`). +- **Concept introduced**: a service interface that isolates the cryptographic mechanics of TOTP (RFC 6238-style time-based codes) from the account-state orchestration that lives in [ITwoFactorAuthenticator](#itwofactorauthenticator). `[Rubric §1, SOLID]` (assesses single-responsibility separation): this interface only knows about codes and secrets, never about a user account or a store. +- **Walkthrough**: `GenerateSecret()` (`ITwoFactorService.cs:22`) mints a fresh Base32 secret. `BuildProvisioningUri(secret, accountName)` (line 37) builds the `otpauth://totp/...` URI an authenticator app scans as a QR code; it is deliberately returned as `string` rather than `System.Uri` (the `[SuppressMessage]` at lines 33-36 explains that round-tripping through `Uri` would re-normalize the percent-encoding the app parses). `VerifyCode(secret, code)` (line 46) checks a code within the configured number of time steps on each side of the current one. The overload `VerifyCode(secret, code, out matchedStep)` (line 59) verifies exactly the same way and also reports which time step matched (Unix seconds divided by the period, or 0 when nothing matched), so a caller that keeps per-account state can refuse a replay of a code it already accepted inside the same window. `GenerateRecoveryCodes()` (line 66) returns a fresh [RecoveryCodeSet](#recoverycodeset). `HashRecoveryCode(code)` (line 74) hashes one plaintext code the way the store keeps it. `TryMatchRecoveryCode(code, storedHashes, out matchedHash)` (line 83) compares in fixed time to avoid a timing side-channel on which stored hash matched. +- **Why it's built this way**: keeping code verification, secret generation, and recovery-code hashing behind one narrow interface means [ITwoFactorAuthenticator](#itwofactorauthenticator) and the handler bases never touch a raw cryptographic primitive directly. Replay protection stays out of this interface too: the service only reports the matched step, and the stateful "accept each step once" rule lives in the caller. +- **Where it's used**: implemented by [TotpTwoFactorService](#totptwofactorservice) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TotpTwoFactorService.cs:61` for the step-reporting overload), consumed by the enrollment/regeneration handler bases in `MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/TwoFactor/` and by [TwoFactorAuthenticator](#twofactorauthenticator), which calls the step-reporting overload (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs:65`) and then rejects any step at or below the last one it cached for that user (`TwoFactorAuthenticator.cs:106`). Registered as a singleton in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:47`. + +### TwoFactorErrors +> MMCA.Common.Application · `MMCA.Common.Application.Auth.TwoFactor` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/TwoFactorErrors.cs:15` · Level 2 · class + +- **What it is**: the static `Error`-factory class for the two-factor flow: challenge-required, challenge-invalid, not-enrolled, and enrollment-missing. +- **Depends on**: `Error`. +- **Concept**: same convention as [EmailConfirmationErrors](#emailconfirmationerrors); no new concept. +- **Walkthrough**: four codes (`TwoFactorErrors.cs:18,21,24,27`). `TwoFactorRequired(source)` (lines 32-35) is `Error.Unauthorized`, asking the caller to supply a code or a recovery code. `TwoFactorInvalid(source)` (lines 45-48) is also `Error.Unauthorized`; its remarks (lines 40-44) note it is deliberately the same message for a wrong TOTP code and a wrong recovery code, since telling them apart would leak whether a presented value was recovery-code shaped. `TwoFactorNotEnrolled(source)` (lines 53-56) and `TwoFactorEnrollmentMissing(source)` (lines 61-64) are both `Error.Conflict`, for an action attempted on an account with no active factor and on an enrollment that was never started, respectively. +- **Why it's built this way**: `Conflict` versus `Unauthorized` is used to distinguish "your account state does not support this action" from "your credentials did not verify", matching the semantics the rest of the auth surface uses for those two error kinds. +- **Where it's used**: raised by `ConfirmTwoFactorEnrollmentHandlerBase.cs` (2 sites), `TwoFactorAuthenticator.cs` (2 sites), `DisableTwoFactorHandlerBase.cs`, and `RegenerateRecoveryCodesHandlerBase.cs`; asserted in `TwoFactorHandlerBaseTests.cs`, `AuthenticationServiceIdentityCompletionsTests.cs`, and `TwoFactorAuthenticatorTests.cs`. + +### ITwoFactorAuthenticator +> MMCA.Common.Application · `MMCA.Common.Application.Auth.TwoFactor` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/ITwoFactorAuthenticator.cs:24` · Level 3 · interface + +- **What it is**: the account-level orchestration for the second-factor challenge at sign-in, one level above [ITwoFactorService](#itwofactorservice)'s pure cryptographic operations. +- **Depends on**: `UserIdentifierType`, [TwoFactorOutcome](#twofactoroutcome), [TwoFactorErrors](#twofactorerrors), `Result`. +- **Concept**: the service-orchestrates-over-a-crypto-primitive layering also seen with [IEmailConfirmationTokenService](#iemailconfirmationtokenservice) versus its store; no new concept. +- **Walkthrough**: `ChallengeAsync(userId, code, cancellationToken)` (`ITwoFactorAuthenticator.cs:40-43`) returns [TwoFactorOutcome](#twofactoroutcome).`NotEnrolled` when the account has no active second factor (sign-in continues unchanged, no `mfa` claim), the verified outcome when a code satisfies the challenge, or a failure: [TwoFactorErrors](#twofactorerrors).`TwoFactorRequiredCode` when a code was needed and none arrived, [TwoFactorErrors](#twofactorerrors).`TwoFactorInvalidCode` when one arrived and did not verify (doc comment lines 32-39). +- **Why it's built this way**: returning an outcome enum rather than a bare boolean lets `AuthenticationServiceBase` distinguish "no second factor configured" from "verified", which matters for whether it stamps an `mfa` claim on the issued token. +- **Where it's used**: implemented by `TwoFactorAuthenticator` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs`), called from `AuthenticationServiceBase.cs` (2 sites) and registered in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs`. + +### ITwoFactorStore +> MMCA.Common.Application · `MMCA.Common.Application.Auth.TwoFactor` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/ITwoFactorStore.cs:24` · Level 3 · interface + +- **What it is**: the persistence abstraction for one account's two-factor state: enrollment lifecycle, recovery-code replacement, and single recovery-code consumption. +- **Depends on**: `UserIdentifierType`, `ITwoFactorUserState` (the read shape, taught elsewhere in this group), `Result`. +- **Concept**: a two-phase enrollment (start, then complete) so an abandoned setup cannot lock an account out; the same two-phase shape a reader has already seen in other enrollment-style flows in this group. +- **Walkthrough**: `GetAsync(userId, cancellationToken)` (`ITwoFactorStore.cs:34`) returns `null` only when the account itself does not exist; an account that never enrolled still returns a state with `IsTwoFactorEnabled = false`. `StartEnrollmentAsync(userId, secret, cancellationToken)` (line 44) stores a freshly minted secret WITHOUT activating the factor, per its summary (lines 36-39), so an abandoned enrollment cannot lock the user out. `CompleteEnrollmentAsync(userId, recoveryCodeHashes, cancellationToken)` (lines 54-57) activates the factor and stores the first recovery-code hashes, called only after a code from the stored secret has verified. `DisableAsync(userId, cancellationToken)` (line 66) turns the factor off and clears the secret and recovery codes, so a later re-enrollment starts from a fresh secret. `ReplaceRecoveryCodesAsync(userId, recoveryCodeHashes, cancellationToken)` (lines 76-79) replaces the whole set, never appends, since regeneration exists to invalidate a list the user believes compromised. `ConsumeRecoveryCodeAsync(userId, recoveryCodeHash, cancellationToken)` (lines 93-96) removes one hash to spend it, and its remarks (lines 84-88) flag this as security-critical: it must persist before the sign-in it authorized is answered, or the same code could sign in twice. +- **Why it's built this way**: splitting `StartEnrollmentAsync`/`CompleteEnrollmentAsync` prevents a half-finished enrollment from ever becoming an active, un-verified second factor. +- **Where it's used**: implemented and called by `TwoFactorAuthenticator.cs` (2 sites) through the enrollment/disable/regenerate handler bases in `MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/TwoFactor/`, and registered in `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs`. + +### ICurrentUserService +> MMCA.Common.Application · `MMCA.Common.Application.Interfaces.Infrastructure.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Auth/ICurrentUserService.cs:9` · Level 8 · interface + +- **What it is**: the Application layer's read-only window onto the authenticated caller: the raw `ClaimsPrincipal`, a strongly-typed `UserId`, the caller's first role, the full role set, a generic typed-claim reader, and a role-membership helper. It answers "who is calling?" without any handler ever touching `HttpContext`. +- **Depends on**: `System.Security.Claims` and `IParsable` (BCL, `:1`) plus the solution-wide `UserIdentifierType` alias (`:15`); see [primer §2](00-primer.md#2-architectural-styles-this-codebase-commits-to). Its adapter is [CurrentUserService](#currentuserservice) in Infrastructure. +- **Concept introduced: the caller-identity port with behavior on the interface.** `[Rubric §3, Clean Architecture]` assesses whether inner layers stay free of transport types, and `[Rubric §1, SOLID]` (interface segregation) whether a contract exposes only what its clients need. A handler must know the caller to run ownership checks and to stamp audit fields, but it must not depend on `IHttpContextAccessor`, which would drag ASP.NET Core into the Application project. This interface is that inversion, and the adapter is the only place the accessor appears (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Context/CurrentUserService.cs:17`, `:25`). What makes it worth studying is the use of **default interface members**: `Roles` (`:45-64`) and `IsInRole` (`:88-89`) ship real implementations on the contract, so every implementer and every hand-written test double inherits correct multi-role behavior instead of re-deriving it. +- **Walkthrough**: `ClaimsPrincipal User` (`:12`) exposes the full principal for advanced inspection. `UserIdentifierType? UserId` (`:15`) is the typed identifier, nullable because an unauthenticated request has no user. `string? Role` (`:22`) is documented as the **first** role claim only, with the remarks at `:18-21` steering callers to `Roles` or `IsInRole` for membership checks. `Roles` (`:45-64`) is the interesting member: it reads every role claim, accepting each claim type the JWT middleware may produce (`ClaimTypes.Role` when inbound claim mapping is on, or the raw `role` / `roles` claim when it is off, `:50-53`), falls back to a single-element list built from `Role` when the principal yields nothing (`:62`), and null-guards `User` even though the property is declared non-nullable (`:49`). The long remarks at `:27-44` justify both accommodations from the nature of a default interface member: it runs against *every* implementation, including a hand-written double or a mock that stubs only `Role`, where reading claims alone would have reported no roles and silently turned an authorization check into a denial, and dereferencing a null principal would have turned it into a `NullReferenceException`. Claims win when present, so a genuine multi-role principal is still read in full. `T? GetClaimValue(string claimType) where T : struct, IParsable` (`:73-74`) parses a named claim into any parsable value type and returns `null` when the claim is missing or unparseable, which is how a module reads its own claim (the doc names `speaker_id`, `:68`) without Common ever knowing that claim exists. `IsInRole(string roleName)` (`:88-89`) is `Roles.Any(role => string.Equals(role, roleName, StringComparison.OrdinalIgnoreCase))`. +- **Why it's built this way**: the remarks at `:82-87` record the reasoning behind `IsInRole` checking every claim rather than comparing against `Role`. Comparing against the first role alone matched only whichever role happened to be listed first, which is latent today because tokens carry a single role, and would have surfaced silently as an authorization denial the moment a second role was added. Typing `UserId` as the per-app alias instead of a generic parameter keeps the interface concrete and easy to mock while staying correct for each app. `[Rubric §11, Security]` and `[Rubric §15, Best Practices & Code Quality]` both apply. +- **Where it's used**: registered as scoped against [CurrentUserService](#currentuserservice) at `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:306`. It supplies the `Roles` set the CQRS authorization decorators check permissions against (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/AuthorizationCommandDecorator.cs:32`, and its query twin; see [group 05](group-05-cqrs-pipeline.md)); it is how audit fields get their actor, since [DbContextFactory](group-07-persistence-ef-core.md#dbcontextfactory) passes `_currentUserService.UserId` into every save (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:68`, used at `:248`, `:291`, `:330`, `:352` and `:414`); and it backs the ownership check in [OwnerOrAdminFilter](#owneroradminfilter) and the framework's account controllers. +- **Caveats / not-in-source**: `Role` deliberately reports only the first role claim; treat it as a display value and use `Roles` or `IsInRole` for any decision. ### ITwoFactorUserState > MMCA.Common.Domain · `MMCA.Common.Domain.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/ITwoFactorUserState.cs:27` · Level 0 · interface @@ -2691,7 +3070,7 @@ live in later groups; this chapter is the engine those endpoints call into. - Both sit inside a scoped `#pragma warning disable CA1819` (`:18`, restored on `:24`) that knowingly returns arrays, to mirror [IPasswordHasher](#ipasswordhasher)'s `byte[]` tuple and the EF-mapped `varbinary` columns rather than force a defensive copy on every read. The suppression's justification is written on the disable line itself, which is the convention this codebase uses everywhere it takes an analyzer exception. - There is no mutator. Writing new material is the separate capability [IPasswordChangeableUser](#ipasswordchangeableuser) adds, so an aggregate that only ever authenticates never exposes a way to change its own password. - **Why it's built this way**: keeping the contract in Domain and keeping it small is what makes the shared auth workflow reusable across Store and ADC (both `User` aggregates satisfy it) while each aggregate stays free to model everything else its own way. See [ADR-032](https://ivanball.github.io/docs/adr/032-password-hashing.html) for the password-material policy, [ADR-004](https://ivanball.github.io/docs/adr/004-authentication-dual-fetch.html) for the dual-fetch auth model this contract feeds, and [ADR-097](https://ivanball.github.io/docs/adr/097-multi-device-refresh-sessions.html) for the refresh-token move. -- **Where it's used**: it is half the generic constraint on the shared login and registration workflow, `where TUser : AuditableAggregateRootEntity, IAuthUser` (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:85`), which reads both properties on the login path (`:159`) and writes the pair on registration (`:210`). It is also the base of [IPasswordChangeableUser](#ipasswordchangeableuser), and the shape hand-written test doubles copy (`MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:946`). +- **Where it's used**: it is half the generic constraint on the shared login and registration workflow, `where TUser : AuditableAggregateRootEntity, IAuthUser` (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:71`), which reads both properties on the login path (`:159`) and writes the pair on registration (`:210`). It is also the base of [IPasswordChangeableUser](#ipasswordchangeableuser), and the shape hand-written test doubles copy (`MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:947`). - **Caveats / not-in-source**: the doc comment on `PasswordSalt` still says the salt's length selects the verify algorithm (`MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/IAuthUser.cs:22`). That was true while [PasswordHasher](#passwordhasher) also verified a legacy HMAC-SHA512 format; the current implementation has one algorithm and one salt size (`SaltSize = 32`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordHasher.cs:15`, with no legacy branch) per [ADR-102](https://ivanball.github.io/docs/adr/102-pbkdf2-only-password-hashing.html). The comment is stale; the code is the contract. ### IPasswordChangeableUser @@ -2702,7 +3081,7 @@ live in later groups; this chapter is the engine those endpoints call into. - **Concept: capability interfaces layered by workflow.** `[Rubric §1, SOLID]` assesses interface segregation, and this is the pattern applied twice over: a `User` that only ever authenticates satisfies [IAuthUser](#iauthuser); a `User` whose app offers self-service password change implements this one and gets `PasswordHash` and `PasswordSalt` along with it, because the workflow must verify the current credential before writing the new one (the XML comment states exactly this reason, `:7-9`). Inheritance here encodes a real dependency between capabilities rather than a taxonomy. `[Rubric §4, DDD]` also applies: the method returns [Result](group-01-result-error-handling.md#result), so the aggregate can refuse the change (an invariant failure) instead of the handler assuming success. - **Walkthrough**: one member, `Result ChangePassword(byte[] newPasswordHash, byte[] newPasswordSalt)` (`:19`). The aggregate receives already-hashed material, never a plaintext password: hashing is the handler's job via [IPasswordHasher](#ipasswordhasher), so no plaintext ever reaches the Domain layer or an EF change tracker. - **Why it's built this way**: keeping the hash-and-salt pair as the parameter shape mirrors [IAuthUser](#iauthuser)'s two properties and [IPasswordHasher](#ipasswordhasher)'s tuple return, so the whole chain from handler to aggregate speaks one vocabulary. See [ADR-032](https://ivanball.github.io/docs/adr/032-password-hashing.html). -- **Where it's used**: as the generic constraint `where TUser : AuditableAggregateRootEntity, IPasswordChangeableUser` on the shared change-password workflow (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:40`), which verifies the current password (`:55`), hashes the new one (`:61`), and calls `ChangePassword` with the result (`:62`). The forgot-password sibling, [ResetPasswordHandlerBase](group-14-module-system-composition.md#resetpasswordhandlerbasetuser-tcommand), calls the same member on the redeem path after [IPasswordResetTokenService](#ipasswordresettokenservice) has identified the account (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:94-95`). Both apps' [User](group-24-identity-module.md#user) aggregates declare it (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:34-35`, `MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Domain/Users/User.cs:29-30`), which is also how they pick up [IAuthUser](#iauthuser). +- **Where it's used**: as the generic constraint `where TUser : AuditableAggregateRootEntity, IPasswordChangeableUser` on the shared change-password workflow (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:49`), which verifies the current password (`:55`), hashes the new one (`:61`), and calls `ChangePassword` with the result (`:62`). The forgot-password sibling, [ResetPasswordHandlerBase](group-14-module-system-composition.md#resetpasswordhandlerbasetuser-tcommand), calls the same member on the redeem path after [IPasswordResetTokenService](#ipasswordresettokenservice) has identified the account (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:94-95`). Both apps' [User](group-24-identity-module.md#user) aggregates declare it (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:34-35`, `MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Domain/Users/User.cs:29-30`), which is also how they pick up [IAuthUser](#iauthuser). ### IUserPreferences > MMCA.Common.Domain · `MMCA.Common.Domain.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/IUserPreferences.cs:10` · Level 3 · interface @@ -2714,26 +3093,6 @@ live in later groups; this chapter is the engine those endpoints call into. - **Why it's built this way**: one replace method keeps the aggregate's invariant check in a single place, and pushing the merge into the workflow keeps the null-means-unchanged policy out of every app's `User`. Returning [Result](group-01-result-error-handling.md#result) lets the aggregate reject an unsupported culture or theme value. - **Where it's used**: the read workflow constrains `where TUser : AuditableBaseEntity, IUserPreferences` and projects both properties into a response (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/GetPreferences/GetUserPreferencesHandlerBase.cs:23`, `:44`); the write workflow constrains `where TUser : AuditableAggregateRootEntity, IUserPreferences` (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePreferences/ChangePreferencesHandlerBase.cs:26`). Both are cross-linked as [GetUserPreferencesHandlerBase](group-14-module-system-composition.md#getuserpreferenceshandlerbasetuser) and [ChangePreferencesHandlerBase](group-14-module-system-composition.md#changepreferenceshandlerbasetuser-tcommand), and both apps' [User](group-24-identity-module.md#user) aggregates implement the interface. -### RefreshSession -> MMCA.Common.Domain · `MMCA.Common.Domain.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:31` · Level 3 · class (sealed) - -- **What it is**: one refresh-token session, meaning a single device's right to mint access tokens for one user, held as a **hash** of the issued refresh token. A user has as many rows as they have signed-in devices, so signing in on a phone no longer signs the same account out of a laptop (BR-205/206, `MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:7-10`). -- **Depends on**: [Result](group-01-result-error-handling.md#result) and [Error](group-01-result-error-handling.md#error) (`:3`), the `UserIdentifierType` alias, and from the BCL `System.Security.Cryptography.SHA256`, `System.Text.Encoding`, and `Convert.ToHexString` (`:1-2`). Persisted by [EFRefreshSessionStore](group-07-persistence-ef-core.md#efrefreshsessionstore) behind [IRefreshSessionStore](#irefreshsessionstore), mapped by [RefreshSessionModelBuilderExtensions](group-07-persistence-ef-core.md#refreshsessionmodelbuilderextensions), swept by [RefreshSessionCleanupService](group-07-persistence-ef-core.md#refreshsessioncleanupservice), tuned by [RefreshSessionSettings](#refreshsessionsettings). -- **Concept introduced: a refresh token is a credential, so store its digest and make reuse detectable.** `[Rubric §11, Security]` assesses how a long-lived credential is stored, rotated, and revoked, and this one class carries three separate properties that are each worth understanding on their own. - - **Hash at rest.** The plaintext refresh token exists only in the response that hands it to the client; the row keeps `TokenHash` (`:63-64`), so a database read cannot mint tokens (`:11-15`). That forces one design consequence the comment calls out explicitly: because lookups are **by hash**, the digest must be unsalted and deterministic. A per-row salt would make the token unfindable. This is the opposite trade-off from [PasswordHasher](#passwordhasher), and legitimately so: a refresh token is 64 random bytes from a CSPRNG rather than a human-chosen password, so there is no dictionary to run against it and no value in slowing the digest down. - - **Rotation leaves a chain.** Using a session revokes it and records its successor in `ReplacedByTokenHash` (`:16-21`, `:75-79`). The point is not bookkeeping: presenting an already-rotated token lands on a **revoked row** rather than on nothing, and that difference is the signal that a token was replayed. A stolen-and-replayed token therefore triggers revocation of the whole family instead of failing quietly (BR-206 reuse detection, [ADR-050](https://ivanball.github.io/docs/adr/050-jwt-refresh-token-rotation.html)). - - **Framework bookkeeping, not an aggregate.** The class comment (`:22-29`) is explicit that this is a flat record like [OutboxMessage](group-04-events-outbox.md#outboxmessage) and [AuditTrailEntry](group-07-persistence-ef-core.md#audittrailentry): no audit stamps, no soft-delete flag, no concurrency token. The reason matters for `[Rubric §8, Data Architecture]`: rows are never deleted or edited except to be revoked, and **no global query filter may hide a revoked row**, because the reuse check depends on finding it. It is also mapped only where a consumer opts in (`ApplyRefreshSessionConfiguration`), since sessions belong to the Identity module's database rather than to every data source ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html) database-per-service). -- **Walkthrough** - - **Width and reason constants** (`MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:33-55`): `TokenHashLength = 64` (the width of a hex-encoded SHA-256 digest, `:34`), `IpAddressMaxLength = 45` (sized to fit an IPv4-mapped IPv6 literal, `:37`), `UserAgentMaxLength = 512` (`:40`), `ReasonRevokedMaxLength = 64` (`:43`), and the four revocation reasons `ReasonRotated` (`:46`), `ReasonSignedOut` (`:49`), `ReasonReuseDetected` (`:52`), and `ReasonSessionCap` (`:55`). Publishing the widths as `public const` on the Domain type is what lets the EF configuration derive every column width from the same numbers (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/RefreshSessionModelBuilderExtensions.cs:47`, `:52`, `:56-58`) rather than repeating magic numbers in a mapping file. - - **State** (`:57-92`): `Id` defaults to a fresh `Guid` (`:58`); `UserId`, `TokenHash`, `CreatedAt` and `ExpiresAt` are `required` and `init`-only (`:61-70`), so a session cannot be constructed without them and cannot be rewritten afterwards. The three mutable members carry `private set` and change only through `Revoke`: `RevokedAt` (`:73`), `ReplacedByTokenHash` (`:79`), `ReasonRevoked` (`:82`). `IpAddress` (`:89`) and `UserAgent` (`:92`) are optional `init`-only capture. The comment on `IpAddress` (`:84-88`) is a good example of documenting what a field is **not** for: it identifies a session in a "your devices" list and gives an audit trail for a revocation, and it is never part of a validation decision, so a mobile client changing networks is not signed out. - - **Derived state**: `IsRevoked => RevokedAt is not null` (`:95`) and `IsActiveAt(DateTime utcNow) => !IsRevoked && ExpiresAt > utcNow` (`:99`). Passing the instant in rather than reading a clock keeps the type free of ambient time, which is what makes it directly unit-testable (see [RefreshSessionTests](group-28-testing-infrastructure.md#per-project-test-rollup)). - - **`Create(...)`** (`:112-145`), the factory returning `Result` in the framework's standard shape (see the primer on factory methods and the [Result](group-01-result-error-handling.md#result) pattern). Two guards: a blank token fails with `RefreshSession.TokenRequired` (`:120-126`), and an expiry at or before creation fails with `RefreshSession.ExpiryInPast` (`:128-134`), both `Error.Validation`. On success it hashes the token on the way in (`:139`), so **the plaintext never reaches a property**, and truncates the two optional capture fields to their column widths (`:142-143`). Truncating in the factory rather than trusting the caller is what keeps an oversized `User-Agent` header from turning a login into a database error. - - **`HashToken(string refreshToken)`** (`:160-164`): `Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(refreshToken)))`, guarded by `ArgumentException.ThrowIfNullOrWhiteSpace` (`:162`). The `` (`:151-157`) is the one piece of this file to read twice: the encoding is **part of the contract, not an implementation detail**, because a consumer's data migration has to reproduce it exactly to carry existing tokens over. It even gives the T-SQL equivalent, `CONVERT(char(64), HASHBYTES('SHA2_256', CONVERT(varchar(max), Token)), 2)`, and explains both halves of why it matches: style 2 emits upper-case hex with no `0x` prefix, and the `varchar` conversion is what makes the hashed bytes UTF-8 rather than SQL Server's default UTF-16. `[Rubric §8, Data Architecture]` and `[Rubric §34, Architecture Governance & Documentation]` both apply here: a hash format that a migration must reproduce is a published contract, and it is documented as one. - - **`Revoke(DateTime revokedAt, string reason, string? replacedByTokenHash = null)`** (`:174-189`): the only mutator. It is **idempotent by refusal** (`:166-169`), returning `Error.Invariant("RefreshSession.AlreadyRevoked", ...)` when the session is already revoked (`:176-182`) rather than silently overwriting, so the first reason and instant recorded are the ones kept. That matters for forensics: a session revoked by reuse detection must not have that reason overwritten by a later sign-out. On success it stamps the instant, the truncated reason, and the successor hash (`:184-186`). - - `Truncate` (`:191-192`) is the shared private helper, returning the value unchanged when it is null, empty, or already short enough. -- **Why it's built this way**: [ADR-097](https://ivanball.github.io/docs/adr/097-multi-device-refresh-sessions.html) records the move from one plaintext refresh-token column on the user row to a session table, and [ADR-050](https://ivanball.github.io/docs/adr/050-jwt-refresh-token-rotation.html) the rotation-and-reuse-detection model the chain implements. Keeping the class free of audit stamps and soft-delete is deliberate rather than an omission, and keeping `Create`/`Revoke` as the only ways in and out means every row in the table was validated and every revoked row carries a reason. -- **Where it's used**: [AuthenticationServiceBase](#authenticationservicebasetuser) is the main consumer. It hashes a presented token to look the session up (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:432`, `:593`), creates one per login (`:628`) and per rotation (`:668`), revokes on sign-out (`:360`, `:367`, `:385`, `:456`), revokes the live family on reuse detection (`:603`, `:691`), and evicts the oldest session with `ReasonSessionCap` when a user exceeds `RefreshSessions:MaxActiveSessionsPerUser` (`:733`, documented at `:111`). Rotation itself is a claim rather than a plain mutation: [IRefreshSessionStore](#irefreshsessionstore)`.TryRotateAsync` revokes with `ReasonRotated` and links the successor (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/IRefreshSessionStore.cs:104`; the EF implementation does it as a conditional `ExecuteUpdate`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/EFRefreshSessionStore.cs:129`, `:142`). The table is mapped through `ApplyRefreshSessionConfiguration` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:896`), and the account-deletion path deliberately does **not** revoke sessions (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:103-107`): the refresh flow re-fetches the user through the soft-delete query filter, so an erased account's sessions stop working the moment the delete commits. - ### IEmailConfirmableUser > MMCA.Common.Domain · `MMCA.Common.Domain.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/IEmailConfirmableUser.cs:22` · Level 3 · interface @@ -2754,41 +3113,52 @@ live in later groups; this chapter is the engine those endpoints call into. - **Why it's built this way**: validating length and blankness in the factory rather than trusting the caller keeps an oversized or empty value from reaching the database as a constraint violation instead of a `[Rubric §4, DDD]`-shaped `Result` failure. Grouping the two comparison conventions on the type that stores the data, rather than leaving them implicit in the store or the registry, is what keeps every reader consistent. - **Where it's used**: written and read by [EFPermissionGrantStore](group-07-persistence-ef-core.md#efpermissiongrantstore) and [StoredPermissionRoleAdministrationService](#storedpermissionroleadministrationservice), mapped by [PermissionGrantModelBuilderExtensions](group-07-persistence-ef-core.md#permissiongrantmodelbuilderextensions), and its `RoleMaxLength`/`PermissionMaxLength` constants are cited by [SetUserRolesRequestValidator](#setuserrolesrequestvalidator) and [SetRolePermissionsRequestValidator](#setrolepermissionsrequestvalidator) (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Validation/AdministrationRequestValidators.cs:22-23`, `:41-42`) when validating admin requests that set roles or permissions. +### RefreshSession +> MMCA.Common.Domain · `MMCA.Common.Domain.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:39` · Level 4 · class (sealed) + +- **What it is**: one refresh-token session, meaning a single device's right to mint access tokens for one user, held as a **hash** of the issued refresh token. A user has as many rows as they have signed-in devices, so signing in on a phone no longer signs the same account out of a laptop (BR-205/206, `MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:9-12`). It implements [IAnonymizable](group-02-domain-building-blocks.md#ianonymizable) (`:39`) so the device metadata it captures has an erasure path. +- **Depends on**: [Result](group-01-result-error-handling.md#result) and [Error](group-01-result-error-handling.md#error) (`:5`), [PiiAttribute](group-02-domain-building-blocks.md#piiattribute) (`:3`), [IAnonymizable](group-02-domain-building-blocks.md#ianonymizable) (`:4`), the `UserIdentifierType` alias, and from the BCL `System.Security.Cryptography.SHA256`, `System.Text.Encoding`, and `Convert.ToHexString` (`:1-2`). Persisted by [EFRefreshSessionStore](group-07-persistence-ef-core.md#efrefreshsessionstore) behind [IRefreshSessionStore](#irefreshsessionstore), mapped by [RefreshSessionModelBuilderExtensions](group-07-persistence-ef-core.md#refreshsessionmodelbuilderextensions), swept by [RefreshSessionCleanupService](group-07-persistence-ef-core.md#refreshsessioncleanupservice), tuned by [RefreshSessionSettings](#refreshsessionsettings). +- **Concept introduced: a refresh token is a credential, so store its digest and make reuse detectable.** `[Rubric §11, Security]` assesses how a long-lived credential is stored, rotated, and revoked, and this one class carries four separate properties that are each worth understanding on their own. + - **Hash at rest.** The plaintext refresh token exists only in the response that hands it to the client; the row keeps `TokenHash` (`:72`), so a database read cannot mint tokens (`:14-16`). That forces one design consequence the comment calls out explicitly: because lookups are **by hash**, the digest must be unsalted and deterministic. A per-row salt would make the token unfindable. This is the opposite trade-off from [PasswordHasher](#passwordhasher), and legitimately so: a refresh token is 64 random bytes from a CSPRNG rather than a human-chosen password, so there is no dictionary to run against it and no value in slowing the digest down. + - **Rotation leaves a chain.** Using a session revokes it and records its successor in `ReplacedByTokenHash` (`:19-22`, `:83-87`). The point is not bookkeeping: presenting an already-rotated token lands on a **revoked row** rather than on nothing, and that difference is the signal that a token was replayed. A stolen-and-replayed token therefore triggers revocation of the whole family instead of failing quietly (BR-206 reuse detection, [ADR-050](https://ivanball.github.io/docs/adr/050-jwt-refresh-token-rotation.html)). + - **Framework bookkeeping, not an aggregate.** The class comment (`:24-31`) is explicit that this is a flat record like [OutboxMessage](group-04-events-outbox.md#outboxmessage) and [AuditTrailEntry](group-07-persistence-ef-core.md#audittrailentry): no audit stamps, no soft-delete flag, no concurrency token. The reason matters for `[Rubric §8, Data Architecture]`: rows are never deleted or edited except to be revoked, and **no global query filter may hide a revoked row**, because the reuse check depends on finding it. It is also mapped only where a consumer opts in (`ApplyRefreshSessionConfiguration`), since sessions belong to the Identity module's database rather than to every data source ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html) database-per-service). + - **Personal data is separable from the credential.** The class comment (`:32-37`) splits the row in two: `IpAddress` and `UserAgent` identify the data subject's device and network, so both carry `[Pii]` and `Anonymize` clears them for an erasure request ([ADR-005](https://ivanball.github.io/docs/adr/005-soft-delete-vs-erasure.html)), while the token hashes, timestamps and revocation chain carry no personal data and are kept. That split is what lets reuse detection keep working on an anonymized row. It also satisfies the framework's PII convention, under which any domain entity with a `[Pii]`-marked property must implement `IAnonymizable` (`MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Governance/ArchitectureRules.Governance.cs:10`). +- **Walkthrough** + - **Width and reason constants** (`MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:41-63`): `TokenHashLength = 64` (the width of a hex-encoded SHA-256 digest, `:42`), `IpAddressMaxLength = 45` (sized to fit an IPv4-mapped IPv6 literal, `:45`), `UserAgentMaxLength = 512` (`:48`), `ReasonRevokedMaxLength = 64` (`:51`), and the four revocation reasons `ReasonRotated` (`:54`), `ReasonSignedOut` (`:57`), `ReasonReuseDetected` (`:60`), and `ReasonSessionCap` (`:63`, value `"SessionCapExceeded"`). Publishing the widths as `public const` on the Domain type is what lets the EF configuration derive every column width from the same numbers (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/RefreshSessionModelBuilderExtensions.cs:47`, `:52`, `:56-58`) rather than repeating magic numbers in a mapping file. + - **State** (`:65-105`): `Id` defaults to a fresh `Guid` (`:66`); `UserId`, `TokenHash`, `CreatedAt` and `ExpiresAt` are `required` and `init`-only (`:69-78`), so a session cannot be constructed without them and cannot be rewritten afterwards. Three revocation members carry `private set` and change only through `Revoke`: `RevokedAt` (`:81`), `ReplacedByTokenHash` (`:87`), `ReasonRevoked` (`:90`). `IpAddress` (`:98`) and `UserAgent` (`:105`) are optional capture, each marked `[Pii]` (`:97`, `:104`) and also `private set`: they are written once by `Create` and afterwards only cleared by `Anonymize`, never set from outside. The `UserAgent` comment (`:100-103`) gives the reason for its marking: alongside the IP it fingerprints the data subject's device. The comment on `IpAddress` (`:92-96`) is a good example of documenting what a field is **not** for: it identifies a session in a "your devices" list and gives an audit trail for a revocation, and it is never part of a validation decision, so a mobile client changing networks is not signed out. + - **Derived state**: `IsRevoked => RevokedAt is not null` (`:108`) and `IsActiveAt(DateTime utcNow) => !IsRevoked && ExpiresAt > utcNow` (`:112`). Passing the instant in rather than reading a clock keeps the type free of ambient time, which is what makes it directly unit-testable (see [RefreshSessionTests](group-28-testing-infrastructure.md#per-project-test-rollup)). + - **`Create(...)`** (`:125-158`), the factory returning `Result` in the framework's standard shape (see the primer on factory methods and the [Result](group-01-result-error-handling.md#result) pattern). Two guards: a blank token fails with `RefreshSession.TokenRequired` (`:133-139`), and an expiry at or before creation fails with `RefreshSession.ExpiryInPast` (`:141-147`), both `Error.Validation`. On success it hashes the token on the way in (`:152`), so **the plaintext never reaches a property**, and truncates the two optional capture fields to their column widths (`:155-156`). Truncating in the factory rather than trusting the caller is what keeps an oversized `User-Agent` header from turning a login into a database error. + - **`HashToken(string refreshToken)`** (`:173-177`): `Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(refreshToken)))`, guarded by `ArgumentException.ThrowIfNullOrWhiteSpace` (`:175`). The `` (`:164-170`) is the one piece of this file to read twice: the encoding is **part of the contract, not an implementation detail**, because a consumer's data migration has to reproduce it exactly to carry existing tokens over. It even gives the T-SQL equivalent, `CONVERT(char(64), HASHBYTES('SHA2_256', CONVERT(varchar(max), Token)), 2)`, and explains both halves of why it matches: style 2 emits upper-case hex with no `0x` prefix, and the `varchar` conversion is what makes the hashed bytes UTF-8 rather than SQL Server's default UTF-16. `[Rubric §8, Data Architecture]` and `[Rubric §34, Architecture Governance & Documentation]` both apply here: a hash format that a migration must reproduce is a published contract, and it is documented as one. + - **`Revoke(DateTime revokedAt, string reason, string? replacedByTokenHash = null)`** (`:187-202`): the only mutator of session state. It is **idempotent by refusal** (`:180-181`), returning `Error.Invariant("RefreshSession.AlreadyRevoked", ...)` when the session is already revoked (`:189-195`) rather than silently overwriting, so the first reason and instant recorded are the ones kept. That matters for forensics: a session revoked by reuse detection must not have that reason overwritten by a later sign-out. On success it stamps the instant, the truncated reason, and the successor hash (`:197-199`). + - **`Anonymize()`** (`:210-215`): the [IAnonymizable](group-02-domain-building-blocks.md#ianonymizable) member. It nulls `IpAddress` and `UserAgent` and returns `Result.Success()`. Unlike `Revoke` it is **idempotent by acceptance** (`:204-209`): an already-anonymized session stays as it is and the call still succeeds, because erasing absent data is not an error. It deliberately leaves the session's validity untouched; revoking is the sign-out path's job, so anonymizing a row and ending it remain two separate decisions. + - `Truncate` (`:217-218`) is the shared private helper, returning the value unchanged when it is null, empty, or already short enough. +- **Why it's built this way**: [ADR-097](https://ivanball.github.io/docs/adr/097-multi-device-refresh-sessions.html) records the move from one plaintext refresh-token column on the user row to a session table, and [ADR-050](https://ivanball.github.io/docs/adr/050-jwt-refresh-token-rotation.html) the rotation-and-reuse-detection model the chain implements. Keeping the class free of audit stamps and soft-delete is deliberate rather than an omission, and keeping `Create`/`Revoke`/`Anonymize` as the only ways in and out means every row in the table was validated, every revoked row carries a reason, and personal data can be erased without breaking the chain. +- **Where it's used**: [AuthSessionIssuer](#authsessionissuer) is the main consumer. It hashes a presented token to look the session up (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:155`, `:285`), creates one per login (`:320`) and per rotation (`:360`), revokes on sign-out (`:166`, `:172`, `:180`) and on a by-id revoke from the device list (`:247`), revokes the live family on reuse detection (`:295`, `:383`), and evicts the oldest session with `ReasonSessionCap` when a user is at `RefreshSessions:MaxActiveSessionsPerUser` (`:427`, documented at `:409`; default 10 at `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/RefreshSessionSettings.cs:35`). Its `ListActiveAsync` filters with `IsActiveAt` and projects `IpAddress` and `UserAgent` into [RefreshSessionSummaryResponse](#refreshsessionsummaryresponse) for the "your devices" list (`AuthSessionIssuer.cs:202-211`). [RefreshSessionRevocation](#refreshsessionrevocation) also revokes with `ReasonSignedOut` (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/RefreshSessionRevocation.cs:47`). No code under `MMCA.Common/Source` calls `RefreshSession.Anonymize` today: the method is the erasure path the PII convention requires, available to an application's erasure flow. Rotation itself is a claim rather than a plain mutation: [IRefreshSessionStore](#irefreshsessionstore)`.TryRotateAsync` revokes with `ReasonRotated` and links the successor (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/IRefreshSessionStore.cs:104`; the EF implementation does it as a conditional `ExecuteUpdate`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/EFRefreshSessionStore.cs:129`, `:142`). The table is mapped through `ApplyRefreshSessionConfiguration` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:899`), and the account-deletion path deliberately does **not** revoke sessions (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:107-111`): the refresh flow re-fetches the user through the soft-delete query filter, so an erased account's sessions stop working the moment the delete commits. + ### IErasableUser > MMCA.Common.Domain · `MMCA.Common.Domain.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/IErasableUser.cs:30` · Level 4 · interface - **What it is**: the erasure surface an Identity module's `User` aggregate exposes to the shared [DeleteUserHandlerBase](group-14-module-system-composition.md#deleteuserhandlerbasetuser-tcommand) workflow: soft-delete the row, then irreversibly anonymize the personal data it still holds (`MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/IErasableUser.cs:6-9`). - **Depends on**: [IAnonymizable](group-02-domain-building-blocks.md#ianonymizable) (its base, contributing `Result Anonymize()`, `:1` and `:30`) and [Result](group-01-result-error-handling.md#result) (`:2`). - **Concept introduced: why a `Delete()` that already exists on the base entity is redeclared here.** This is the most instructive comment in the file and it is worth reading in full (`:11-29`). [AuditableBaseEntity](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype) already has a `Delete()`. But an app's `User` may **hide** it (`public new Result Delete()`) to couple account-specific behavior to deletion. A hidden method is not an override, and C# member lookup on a generic type parameter prefers the members of its **class** constraint, so a shared workflow writing `user.Delete()` would bind to the base implementation and silently skip the app's version. Because the app `User` lists this interface in its own base list, the interface map resolves to the most derived `Delete()` declared on the app type, so invoking it **through the interface** forces interface dispatch and reaches exactly the member the app intended. `[Rubric §1, SOLID]` (Liskov: the hidden method is exactly the substitutability hazard this closes) and `[Rubric §15, Best Practices & Code Quality]` both apply, and this is a case where a language rule, not a style preference, dictates the design. The second paragraph (`:25-28`) adds the compile-time guarantee: the base entity deliberately does **not** implement this interface, so a consumer that forgets to declare it fails the generic constraint at compile time rather than losing behavior at run time. -- **Walkthrough**: one declared member, `Result Delete()` (`:37`), documented as soft-delete plus whatever the app couples to deletion (`:32-34`), returning a failure when the account is already deleted (`:36`). Inherited from [IAnonymizable](group-02-domain-building-blocks.md#ianonymizable) is `Result Anonymize()`, which must be idempotent. The two-step order is visible in the caller: cast once to the interface (`IErasableUser erasable = user;`, `MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:114`, with the reason spelled out at `:88-92`), `erasable.Delete()` first (`:94`), the app's own tail hook next (`OnAfterSoftDeleteAsync`, `:101`), then `erasable.Anonymize()` (`:108`), each short-circuiting on failure. -- **Why it's built this way**: soft-delete alone hides a row but retains its personal data, so it does not satisfy an erasure request; anonymize-in-place overwrites the personal fields while keeping the row so foreign keys and the audit trail survive ([ADR-005](https://ivanball.github.io/docs/adr/005-soft-delete-vs-erasure.html)). Splitting the two into separate members lets the workflow run app-specific work between them, which the handler documents as the only point where an app can both read the personal data and know the delete succeeded (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:41-42`). `[Rubric §30, Compliance, Privacy & Data Governance]` assesses exactly this: an erasure path that does not destroy referential integrity. -- **Where it's used**: the generic constraint `where TUser : AuditableAggregateRootEntity, IErasableUser` on the shared delete-user workflow (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:62`), implemented by each app's [User](group-24-identity-module.md#user) aggregate (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:34-35`, `MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Domain/Users/User.cs:29-30`). +- **Walkthrough**: one declared member, `Result Delete()` (`:37`), documented as soft-delete plus whatever the app couples to deletion (`:32-34`), returning a failure when the account is already deleted (`:36`). Inherited from [IAnonymizable](group-02-domain-building-blocks.md#ianonymizable) is `Result Anonymize()`, which must be idempotent. The two-step order is visible in the caller: cast once to the interface (`IErasableUser erasable = user;`, `MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:118`, with the reason spelled out at `:88-92`), `erasable.Delete()` first (`:94`), the app's own tail hook next (`OnAfterSoftDeleteAsync`, `:101`), then `erasable.Anonymize()` (`:108`), each short-circuiting on failure. +- **Why it's built this way**: soft-delete alone hides a row but retains its personal data, so it does not satisfy an erasure request; anonymize-in-place overwrites the personal fields while keeping the row so foreign keys and the audit trail survive ([ADR-005](https://ivanball.github.io/docs/adr/005-soft-delete-vs-erasure.html)). Splitting the two into separate members lets the workflow run app-specific work between them, which the handler documents as the only point where an app can both read the personal data and know the delete succeeded (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:45-46`). `[Rubric §30, Compliance, Privacy & Data Governance]` assesses exactly this: an erasure path that does not destroy referential integrity. +- **Where it's used**: the generic constraint `where TUser : AuditableAggregateRootEntity, IErasableUser` on the shared delete-user workflow (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:66`), implemented by each app's [User](group-24-identity-module.md#user) aggregate (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:34-35`, `MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Domain/Users/User.cs:29-30`). - **Caveats / not-in-source**: the interface's own comment says an app typically hides `Delete()` to revoke the refresh token (`MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/IErasableUser.cs:16`, `:34`). That phrasing predates the move to [RefreshSession](#refreshsession) rows and no longer describes either app. ADC is the only consumer that hides the method, and its version calls `base.Delete()` and raises a `UserDeleted` domain event (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:443-452`), with the XML comment there stating outright that sessions are not touched and do not need to be. MMCA.Store does not hide `Delete()` at all. The load-bearing lesson (interface dispatch over a possibly-hidden base member) is unchanged; the example in the comment is stale. -### ICurrentUserService -> MMCA.Common.Application · `MMCA.Common.Application.Interfaces.Infrastructure.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Auth/ICurrentUserService.cs:9` · Level 8 · interface - -- **What it is**: the Application layer's read-only window onto the authenticated caller: the raw `ClaimsPrincipal`, a strongly-typed `UserId`, the caller's first role, the full role set, a generic typed-claim reader, and a role-membership helper. It answers "who is calling?" without any handler ever touching `HttpContext`. -- **Depends on**: `System.Security.Claims` and `IParsable` (BCL, `:1`) plus the solution-wide `UserIdentifierType` alias (`:15`); see [primer §2](00-primer.md#2-architectural-styles-this-codebase-commits-to). Its adapter is [CurrentUserService](#currentuserservice) in Infrastructure. -- **Concept introduced: the caller-identity port with behavior on the interface.** `[Rubric §3, Clean Architecture]` assesses whether inner layers stay free of transport types, and `[Rubric §1, SOLID]` (interface segregation) whether a contract exposes only what its clients need. A handler must know the caller to run ownership checks and to stamp audit fields, but it must not depend on `IHttpContextAccessor`, which would drag ASP.NET Core into the Application project. This interface is that inversion, and the adapter is the only place the accessor appears (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Context/CurrentUserService.cs:17`, `:25`). What makes it worth studying is the use of **default interface members**: `Roles` (`:45-64`) and `IsInRole` (`:88-89`) ship real implementations on the contract, so every implementer and every hand-written test double inherits correct multi-role behavior instead of re-deriving it. -- **Walkthrough**: `ClaimsPrincipal User` (`:12`) exposes the full principal for advanced inspection. `UserIdentifierType? UserId` (`:15`) is the typed identifier, nullable because an unauthenticated request has no user. `string? Role` (`:22`) is documented as the **first** role claim only, with the remarks at `:18-21` steering callers to `Roles` or `IsInRole` for membership checks. `Roles` (`:45-64`) is the interesting member: it reads every role claim, accepting each claim type the JWT middleware may produce (`ClaimTypes.Role` when inbound claim mapping is on, or the raw `role` / `roles` claim when it is off, `:50-53`), falls back to a single-element list built from `Role` when the principal yields nothing (`:62`), and null-guards `User` even though the property is declared non-nullable (`:49`). The long remarks at `:27-44` justify both accommodations from the nature of a default interface member: it runs against *every* implementation, including a hand-written double or a mock that stubs only `Role`, where reading claims alone would have reported no roles and silently turned an authorization check into a denial, and dereferencing a null principal would have turned it into a `NullReferenceException`. Claims win when present, so a genuine multi-role principal is still read in full. `T? GetClaimValue(string claimType) where T : struct, IParsable` (`:73-74`) parses a named claim into any parsable value type and returns `null` when the claim is missing or unparseable, which is how a module reads its own claim (the doc names `speaker_id`, `:68`) without Common ever knowing that claim exists. `IsInRole(string roleName)` (`:88-89`) is `Roles.Any(role => string.Equals(role, roleName, StringComparison.OrdinalIgnoreCase))`. -- **Why it's built this way**: the remarks at `:82-87` record the reasoning behind `IsInRole` checking every claim rather than comparing against `Role`. Comparing against the first role alone matched only whichever role happened to be listed first, which is latent today because tokens carry a single role, and would have surfaced silently as an authorization denial the moment a second role was added. Typing `UserId` as the per-app alias instead of a generic parameter keeps the interface concrete and easy to mock while staying correct for each app. `[Rubric §11, Security]` and `[Rubric §15, Best Practices & Code Quality]` both apply. -- **Where it's used**: registered as scoped against [CurrentUserService](#currentuserservice) at `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:296`. It supplies the `Roles` set the CQRS authorization decorators check permissions against (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/AuthorizationCommandDecorator.cs:32`, and its query twin; see [group 05](group-05-cqrs-pipeline.md)); it is how audit fields get their actor, since [DbContextFactory](group-07-persistence-ef-core.md#dbcontextfactory) passes `_currentUserService.UserId` into every save (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:66`, used at `:248`, `:291`, `:330`, `:352` and `:414`); and it backs the ownership check in [OwnerOrAdminFilter](#owneroradminfilter) and the framework's account controllers. -- **Caveats / not-in-source**: `Role` deliberately reports only the first role claim; treat it as a display value and use `Roles` or `IsInRole` for any decision. - ### EmailConfirmationEntry -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:169` · Level 0 · record (internal sealed) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:174` · Level 0 · record (internal sealed) -- **What it is**: the cached confirmation record behind the email-confirmation flow: what [EmailConfirmationTokenService](#emailconfirmationtokenservice) writes into the cache when a confirmation token is issued, and reads back when one is redeemed. It is `internal sealed`, declared as a second type at the bottom of its service's file (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:169-173`). +- **What it is**: the cached confirmation record behind the email-confirmation flow: what [EmailConfirmationTokenService](#emailconfirmationtokenservice) writes into the cache when a confirmation token is issued, and reads back when one is redeemed. It is `internal sealed`, declared as a second type at the bottom of its service's file (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:174-178`). - **Depends on**: nothing first-party except the `UserIdentifierType` alias (the per-module `global using` identifier alias, [ADR-048](https://ivanball.github.io/docs/adr/048-primitive-identifier-type-aliases.html)). Four positional parameters, all BCL primitives. - **Concept**: the same cache-DTO rule [PasswordResetEntry](#passwordresetentry) demonstrates: a value round-tripped through `System.Text.Json` in a distributed cache must be a JSON primitive, so the token digest travels as Base64 text (`TokenHashBase64`, `:170`) rather than as `byte[]`, and the expiry as Unix seconds (`ExpiresAtUnixSeconds`, `:173`) rather than as `DateTimeOffset`. `[Rubric §11, Security]` applies through the same member name convention: `TokenHashBase64`, never `Token`, so the record cannot hold the redeemable secret it guards. - **Walkthrough**: four members, the same shape as [PasswordResetEntry](#passwordresetentry): - - `string TokenHashBase64` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:170`): the SHA-256 digest of the issued token, Base64-encoded. Validation re-hashes the presented token and compares digests. + - `string TokenHashBase64` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:175`): the SHA-256 digest of the issued token, Base64-encoded. Validation re-hashes the presented token and compares digests. - `UserIdentifierType UserId` (`:171`): the account the token confirms. - `int FailedAttempts` (`:172`): wrong tokens presented against this record so far. - `long ExpiresAtUnixSeconds` (`:173`): when the record expires; a failed-attempt rewrite recaches with the **remaining** lifetime computed from this field so a wrong guess cannot extend how long the token stays redeemable (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:138-151`). -- **Why it's built this way**: being a `record` gives the `with` expression the attempt-counter rewrite relies on (`entry with { FailedAttempts = attempts }`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:150`). Being `internal` keeps the cache layout out of the package's public API. -- **Where it's used**: written by `IssueAsync` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:67-73`), read by `ValidateAndConsumeAsync` (`:85`), and rewritten by `RecordFailedAttemptAsync` (`:148-151`). It appears nowhere else. +- **Why it's built this way**: being a `record` gives the `with` expression the attempt-counter rewrite relies on (`entry with { FailedAttempts = attempts }`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:155`). Being `internal` keeps the cache layout out of the package's public API. +- **Where it's used**: written by `IssueAsync` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:69-75`), read by `ValidateAndConsumeAsync` (`:85`), and rewritten by `RecordFailedAttemptAsync` (`:148-151`). It appears nowhere else. ### IJwksProvider > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/IJwksProvider.cs:11` · Level 0 · interface @@ -2798,14 +3168,14 @@ live in later groups; this chapter is the engine those endpoints call into. - **Concept introduced: publishing a public key instead of sharing a secret.** `[Rubric §11, Security]` assesses key management and blast radius, and `[Rubric §7, Microservices Readiness]` assesses whether a module can be lifted out without a rewrite. In an extracted-service topology, symmetric HS256 would require every service to hold the same secret, so any one compromised service can mint tokens for all of them. The asymmetric alternative ([ADR-004](https://ivanball.github.io/docs/adr/004-authentication-dual-fetch.html)) keeps the RSA private key inside the Identity service and publishes only the public key at a well-known URL; peers fetch it and validate signatures without ever being able to sign. `IJwksProvider` is how the Identity API obtains that public key set to serve. - **Walkthrough**: a single synchronous member, `JsonWebKeySet GetJsonWebKeySet()` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/IJwksProvider.cs:19`). Synchronous is the deliberate shape because key material is resolved once and cached in-process by the implementation. The doc comment sets a contract that the implementation must honor: return an **empty** key set rather than throwing when no signing key is configured (`:13-17`), so `/.well-known/jwks.json` stays a valid, pollable URL even in a host where JWKS publishing is off. - **Why it's built this way**: an interface here lets tests inject a pre-built key set with no file IO, and the empty-set contract makes the endpoint safe to map unconditionally instead of behind a feature check. -- **Where it's used**: registered as `services.TryAddSingleton()` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:186`) immediately after the `JwksSettings` options binding (`:165-168`); the JWKS minimal-API endpoint calls it, and consuming services fetch the resulting document through `AddForwardedJwtBearer` at startup (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/IJwksProvider.cs:9`). +- **Where it's used**: registered as `services.TryAddSingleton()` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:196`) immediately after the `JwksSettings` options binding (`:165-168`); the JWKS minimal-API endpoint calls it, and consuming services fetch the resulting document through `AddForwardedJwtBearer` at startup (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/IJwksProvider.cs:9`). ### JwksSettings > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwksSettings.cs:17` · Level 0 · class (sealed) - **What it is**: the `Jwks` section that controls whether an Identity service publishes a JSON Web Key Set at `/.well-known/jwks.json`, and where its RSA public key comes from (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwksSettings.cs:5-9`). - **Depends on**: `System.ComponentModel.DataAnnotations` for `[StringLength]` (BCL, `:1`) only. Consumed by [RsaJwksProvider](#rsajwksprovider) and [TokenService](#tokenservice) through `IOptions`. -- **Concept introduced: key distribution as configuration.** `[Rubric §11, Security]` assesses how trust is established between services. In a single-process monolith the issuer and the validator can share one symmetric secret. Once a module is extracted, the validator must obtain the issuer's *public* key without sharing anything secret, which is what a JWKS document is for ([ADR-004](https://ivanball.github.io/docs/adr/004-authentication-dual-fetch.html), [ADR-008](https://ivanball.github.io/docs/adr/008-service-extraction-topology.html)). `[Rubric §7, Microservices Readiness]`: the framework ships the endpoint always and the key set empty, so nothing about a deployment changes until a host flips `Enabled`. The `kid` contract is the subtle part: `KeyId` is published as the JWK `kid` and must match the `kid` header on tokens the issuer signs (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwksSettings.cs:13-14`, restated on the property itself at `:28-32`), otherwise a validator holding a correct key set still cannot pick the right key. [TokenService](#tokenservice) closes that loop by taking these same options and stamping `KeyId` onto every RS256 token it signs (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:57-62`, `:57`, `:67`). +- **Concept introduced: key distribution as configuration.** `[Rubric §11, Security]` assesses how trust is established between services. In a single-process monolith the issuer and the validator can share one symmetric secret. Once a module is extracted, the validator must obtain the issuer's *public* key without sharing anything secret, which is what a JWKS document is for ([ADR-004](https://ivanball.github.io/docs/adr/004-authentication-dual-fetch.html), [ADR-008](https://ivanball.github.io/docs/adr/008-service-extraction-topology.html)). `[Rubric §7, Microservices Readiness]`: the framework ships the endpoint always and the key set empty, so nothing about a deployment changes until a host flips `Enabled`. The `kid` contract is the subtle part: `KeyId` is published as the JWK `kid` and must match the `kid` header on tokens the issuer signs (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwksSettings.cs:13-14`, restated on the property itself at `:28-32`), otherwise a validator holding a correct key set still cannot pick the right key. [TokenService](#tokenservice) closes that loop by taking these same options and stamping `KeyId` onto every RS256 token it signs (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:65-70`, `:57`, `:67`). - **Walkthrough**: - `SectionName = "Jwks"` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwksSettings.cs:20`). - `Enabled` (`:26`), defaulting to `false` with the rationale spelled out inline (`:22-25`): existing HMAC-only deployments must not start advertising an RSA key set by accident. @@ -2813,7 +3183,7 @@ live in later groups; this chapter is the engine those endpoints call into. - `RsaPublicKeyPem` (`:41`) and `RsaPublicKeyPath` (`:47`), documented as mutually exclusive (`:36-40`, `:43-46`); the path form exists for keys mounted as a secret rather than inlined in configuration. - The consuming logic, worth reading alongside: [RsaJwksProvider](#rsajwksprovider)`.BuildKeySet` returns an EMPTY `JsonWebKeySet` when `Enabled` is false (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/RsaJwksProvider.cs:29-32`) and again when neither PEM source resolves (`:36-39`); otherwise it imports the PEM, stamps `KeyId` onto the `RsaSecurityKey` (`:41-47`) and tags the JWK `use=sig`, `alg=RS256` (`:50-51`). `ResolvePem` prefers the inline value over the file (`:58-74`), and the key set is built once behind a `Lazy` in `PublicationOnly` mode (`:21-22`) so that one transient IO failure reading the PEM is retried rather than cached forever (`:17-21`). - **Why it's built this way**: default-off plus an empty key set means the endpoint is safe to map unconditionally, and two key sources cover both "inline it in configuration" and "mount it as a secret" without a second code path in the provider. -- **Where it's used**: bound with `.ValidateDataAnnotations().ValidateOnStart()` in `AddInfrastructure` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:182-185`), immediately followed by the [IJwksProvider](#ijwksprovider) registration (`:183`). [TokenService](#tokenservice) takes it as an optional constructor dependency and falls back to `new JwksSettings().KeyId` when it is absent (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:79`). +- **Where it's used**: bound with `.ValidateDataAnnotations().ValidateOnStart()` in `AddInfrastructure` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:192-195`), immediately followed by the [IJwksProvider](#ijwksprovider) registration (`:183`). [TokenService](#tokenservice) takes it as an optional constructor dependency and falls back to `new JwksSettings().KeyId` when it is absent (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:90`). ### JwtSigningAlgorithm > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwtSigningAlgorithm.cs:21` · Level 0 · enum @@ -2823,7 +3193,7 @@ live in later groups; this chapter is the engine those endpoints call into. - **Concept introduced: the deployment shape encoded as one configuration value.** `[Rubric §11, Security]` assesses key management: HS256 requires every validator to hold the *signing* key, which is acceptable only while issuer and validators share a process. RS256 splits the pair, the issuer holds the private key and peers validate against the JWKS endpoint, so no peer ever holds the signing key ([ADR-004](https://ivanball.github.io/docs/adr/004-authentication-dual-fetch.html)). `[Rubric §7, Microservices Readiness]`: making this a configuration value rather than a compile-time choice is what lets the same binaries run both topologies, and the type's own doc says RS256 is also the right choice for a monolith that intends to extract later, because the token format does not change when it does (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwtSigningAlgorithm.cs:8-12`). The operational consequence is stated just as plainly: switching a running deployment between the two invalidates every existing token, a hard cutover (`:17-18`). - **Walkthrough**: `HS256 = 0` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwtSigningAlgorithm.cs:24`) and `RS256 = 1` (`:27`), both with explicit ordinals. - The default is RS256, and where that default lives is worth being precise about. The enum's zero value is HS256, so a configuration binder that saw an *invalid* value would land there; but a host that simply omits `Jwt:SigningAlgorithm` never has the property set at all, and [JwtSettings](#jwtsettings)'s own initializer holds (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwtSettings.cs:30`). The default is a property initializer, not the enum ordinal. - - [TokenService](#tokenservice) branches on the value once, in its constructor, and caches the resulting credentials (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:76-86`), with the RSA and HMAC builders at `:194` and `:180`. Each builder throws a named `InvalidOperationException` when its key material is missing (`:184`, `:200`). + - [TokenService](#tokenservice) branches on the value once, in its constructor, and caches the resulting credentials (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:87-97`), with the RSA and HMAC builders at `:194` and `:180`. Each builder throws a named `InvalidOperationException` when its key material is missing (`:184`, `:200`). - The API layer branches on the same value when configuring in-process JWT bearer validation: `BuildValidationParameters` takes the RSA path for RS256 (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.Authentication.cs:209-232`) and, when the public key is absent, throws a message that points the reader at `AddForwardedJwtBearer` for services that fetch the key through JWKS at runtime instead (`:211-215`). - **Why it's built this way**: both members stay because they encode deployment shapes rather than a compatibility level (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwtSigningAlgorithm.cs:5-6`). A single-process monolith that will never be split skips RSA key management entirely; everything else gets the algorithm that survives extraction. - **Where it's used**: [JwtSettings.SigningAlgorithm](#jwtsettings) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwtSettings.cs:30`) and its conditional validation (`:72`, `:79`), [TokenService](#tokenservice), and `BuildValidationParameters` in the API startup extensions (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.Authentication.cs:207`). @@ -2837,22 +3207,22 @@ live in later groups; this chapter is the engine those endpoints call into. - **Walkthrough**: `const string SectionName = "LoginProtection"` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/LoginProtectionSettings.cs:12`) names the bound section. Two concerns follow. - Account lockout: `MaxFailedAttempts` (default 5, `[Range(1, 100)]`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/LoginProtectionSettings.cs:17-18`), `MaxLockoutSeconds` (default 300, `[Range(1, 3600)]`, `:23-24`), `FailedAttemptWindowMinutes` (default 30, `[Range(1, 1440)]`, `:30-31`). The window comment (`:26-29`) is load-bearing for understanding the service: the attempt counter resets by cache expiration, not by a sweep job. - Registration rate limiting: `MaxRegistrationsPerIpPerHour` (default 10, `[Range(1, 10000)]`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/LoginProtectionSettings.cs:36-37`) and `RegistrationRateLimitWindowMinutes` (default 60, `[Range(1, 1440)]`, `:42-43`). -- **Why it's built this way**: `sealed` with `init`-only properties gives an immutable options object. Every property carries a `[Range]`, and the registration wires `.ValidateDataAnnotations().ValidateOnStart()` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:148-151`), so an obviously unsafe value such as `MaxFailedAttempts = 0` fails the host at startup instead of quietly disabling lockout until someone notices in production. The `MaxLockoutSeconds` upper bound of 3600 is also what lets [LoginProtectionService](#loginprotectionservice) reason about its shift-clamp safely. -- **Where it's used**: bound and validated in `AddInfrastructure` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:148-151`) immediately before [LoginProtectionService](#loginprotectionservice) is registered (`:135`). Its sibling [PasswordResetSettings](#passwordresetsettings) is bound in exactly the same shape two lines later (`:137-140`), as is [RefreshSessionSettings](#refreshsessionsettings) (`:145-148`). +- **Why it's built this way**: `sealed` with `init`-only properties gives an immutable options object. Every property carries a `[Range]`, and the registration wires `.ValidateDataAnnotations().ValidateOnStart()` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:154-157`), so an obviously unsafe value such as `MaxFailedAttempts = 0` fails the host at startup instead of quietly disabling lockout until someone notices in production. The `MaxLockoutSeconds` upper bound of 3600 is also what lets [LoginProtectionService](#loginprotectionservice) reason about its shift-clamp safely. +- **Where it's used**: bound and validated in `AddInfrastructure` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:154-157`) immediately before [LoginProtectionService](#loginprotectionservice) is registered (`:135`). Its sibling [PasswordResetSettings](#passwordresetsettings) is bound in exactly the same shape two lines later (`:137-140`), as is [RefreshSessionSettings](#refreshsessionsettings) (`:145-148`). ### PasswordResetEntry -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:160` · Level 0 · record (internal sealed) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:199` · Level 0 · record (internal sealed) -- **What it is**: the cached reset record behind the forgot-password flow: what [PasswordResetTokenService](#passwordresettokenservice) writes into the cache when a reset token is issued, and reads back when one is redeemed. It is `internal sealed`, declared as a second type at the bottom of its service's file (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:160-164`). +- **What it is**: the cached reset record behind the forgot-password flow: what [PasswordResetTokenService](#passwordresettokenservice) writes into the cache when a reset token is issued, and reads back when one is redeemed. It is `internal sealed`, declared as a second type at the bottom of its service's file (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:199-203`). - **Depends on**: nothing first-party except the `UserIdentifierType` alias (the per-module `global using` identifier alias taught in the primer, [ADR-048](https://ivanball.github.io/docs/adr/048-primitive-identifier-type-aliases.html)). Four positional parameters, all BCL primitives. -- **Concept introduced: a cache DTO is constrained by its serializer, not by your domain.** `[Rubric §8, Data Architecture]` assesses whether each store is given a shape it can actually round-trip, and this four-line record is a compact lesson in that. The XML comment states the rule directly (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:151-155`): the cache round-trips values through `System.Text.Json`, so **every member is a JSON primitive**. A value object such as [Email](group-02-domain-building-blocks.md#email) or a raw `byte[]` here would not survive a distributed backing store, which is why the token digest is carried as Base64 text (`:172`) and the expiry as Unix seconds (`:175`) rather than as `byte[]` and `DateTimeOffset`. `[Rubric §11, Security]` also applies through one member name: `TokenHashBase64`, not `Token`. The record is structurally incapable of holding the secret it guards. +- **Concept introduced: a cache DTO is constrained by its serializer, not by your domain.** `[Rubric §8, Data Architecture]` assesses whether each store is given a shape it can actually round-trip, and this four-line record is a compact lesson in that. The XML comment states the rule directly (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:190-194`): the cache round-trips values through `System.Text.Json`, so **every member is a JSON primitive**. A value object such as [Email](group-02-domain-building-blocks.md#email) or a raw `byte[]` here would not survive a distributed backing store, which is why the token digest is carried as Base64 text (`:172`) and the expiry as Unix seconds (`:175`) rather than as `byte[]` and `DateTimeOffset`. `[Rubric §11, Security]` also applies through one member name: `TokenHashBase64`, not `Token`. The record is structurally incapable of holding the secret it guards. - **Walkthrough**: four members, in the order they matter. - - `string TokenHashBase64` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:161`): Base64 of the SHA-256 of the issued token, never the token itself (`:167`). Validation re-hashes the presented token and compares digests, so the cache never holds redeemable material. + - `string TokenHashBase64` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:200`): Base64 of the SHA-256 of the issued token, never the token itself (`:167`). Validation re-hashes the presented token and compares digests, so the cache never holds redeemable material. - `UserIdentifierType UserId` (`:173`): the account the token redeems to (`:168`). Storing the id in the record is what lets redemption resolve the user without a second lookup by email. - `int FailedAttempts` (`:174`): wrong tokens presented against this record so far (`:169`), the counter the attempt cap is enforced against. - `long ExpiresAtUnixSeconds` (`:175`): when the record expires (`:170`). This one exists for a specific reason explained at the rewrite site: when a failed attempt bumps the counter, the record is re-cached with the **remaining** lifetime computed from this field, so a wrong guess cannot extend how long the token stays redeemable (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:127`, `:146-152`). -- **Why it's built this way**: being a `record` gives the non-destructive `with` expression that the attempt-counter update relies on (`entry with { FailedAttempts = attempts }`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:139`), so the rewrite is a copy rather than a mutation. Being `internal` keeps a cache-layout detail out of the package's public API: nothing outside the Infrastructure assembly should be able to construct or read one. See [ADR-091](https://ivanball.github.io/docs/adr/091-cache-backed-password-reset.html) for why the reset lifecycle lives in the cache at all. -- **Where it's used**: written by `IssueAsync` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:71-77`), read by `ValidateAndConsumeAsync` (`:100`), and rewritten by `RecordFailedAttemptAsync` (`:148-152`). It appears nowhere else. +- **Why it's built this way**: being a `record` gives the non-destructive `with` expression that the attempt-counter update relies on (`entry with { FailedAttempts = attempts }`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:178`), so the rewrite is a copy rather than a mutation. Being `internal` keeps a cache-layout detail out of the package's public API: nothing outside the Infrastructure assembly should be able to construct or read one. See [ADR-091](https://ivanball.github.io/docs/adr/091-cache-backed-password-reset.html) for why the reset lifecycle lives in the cache at all. +- **Where it's used**: written by `IssueAsync` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:84-90`), read by `ValidateAndConsumeAsync` (`:100`), and rewritten by `RecordFailedAttemptAsync` (`:148-152`). It appears nowhere else. ### AuthenticationRequest > MMCA.Common.Shared · `MMCA.Common.Shared` · `MMCA.Common/Source/Core/MMCA.Common.Shared/AuthenticationRequest.cs:15` · Level 0 · record struct @@ -2881,7 +3251,7 @@ live in later groups; this chapter is the engine those endpoints call into. - `Validate(ValidationContext)` (`:70-85`): an iterator method with two independent checks. Under HS256, `SecretForKey.Length < 32` yields a failure naming `SecretForKey` (`:72-77`); under RS256, a null or whitespace `RsaPrivateKeyPem` yields a failure naming `RsaPrivateKeyPem` (`:79-84`). Note the asymmetry: the private key is enforced here, the public key is not, because a service that only validates fetches it through JWKS. - The in-process validator enforces the other half at wiring time instead: `BuildValidationParameters` throws when RS256 is selected with no `RsaPublicKeyPem`, and the message points at `AddForwardedJwtBearer` for services that should fetch the key at runtime (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.Authentication.cs:211-215`). - **Why it's built this way**: keeping the conditional rule in code next to the properties it constrains, rather than in the registration call, means every host that binds this section gets the same guarantee without repeating it. The algorithm switch is a hard cutover that invalidates every existing token (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwtSigningAlgorithm.cs:17-18`), so failing the boot on a half-configured section is much cheaper than discovering it at the first sign or the first validation. -- **Where it's used**: bound in `AddCommonAuthentication` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.Authentication.cs:146-149`), which then re-reads the section eagerly to build the token validation parameters at wiring time (`:154-157`); consumed by [TokenService](#tokenservice) through `IOptions`, which branches on the algorithm once in the constructor and caches the credentials (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:63-87`). +- **Where it's used**: bound in `AddCommonAuthentication` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.Authentication.cs:146-149`), which then re-reads the section eagerly to build the token validation parameters at wiring time (`:154-157`); consumed by [TokenService](#tokenservice) through `IOptions`, which branches on the algorithm once in the constructor and caches the credentials (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:71-98`). ### RsaJwksProvider > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/RsaJwksProvider.cs:14` · Level 1 · class (sealed) @@ -2894,7 +3264,7 @@ live in later groups; this chapter is the engine those endpoints call into. - `BuildKeySet(JwksSettings settings)` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/RsaJwksProvider.cs:27`) short-circuits to an empty `JsonWebKeySet` when `!settings.Enabled` (`:30-33`) or when the resolved PEM is blank (`:36-39`). Those are the two paths that satisfy the [IJwksProvider](#ijwksprovider) never-throw contract. - With a key present it imports the PEM into a disposable `RSA` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/RsaJwksProvider.cs:40-41`), exports **only** the public parameters (`ExportParameters(includePrivateParameters: false)`, `:44`) into an `RsaSecurityKey` tagged with the configured `KeyId` (`:44-47`), converts it with `JsonWebKeyConverter.ConvertFromRSASecurityKey` (`:49`), marks it `Use = "sig"` and `Alg = SecurityAlgorithms.RsaSha256` (`:50-51`) so consumers know the key's purpose and algorithm, and adds it to a fresh key set (`:53-55`). - `ResolvePem(JwksSettings settings)` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/RsaJwksProvider.cs:57`) prefers the inline `RsaPublicKeyPem` (`:60-63`) and otherwise reads `RsaPublicKeyPath` from disk with a synchronous `File.ReadAllText` (`:70`), justified in the comment because the read happens on the first request and its success is cached, while a failure is deliberately not cached (`:67-69`). With neither configured it returns `null` (`:73`), which is what lands `BuildKeySet` on the empty-set path. -- **Why it's built this way**: exporting only the public parameters guarantees the private key can never reach the JWKS document even by accident. The inline-PEM-or-path pair supports both secrets-manager injection (env var or config) and a volume-mounted key file, which are the two deployment shapes the framework's samples use. `sealed`, and registered singleton (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:186`) so the cache is process-wide. +- **Why it's built this way**: exporting only the public parameters guarantees the private key can never reach the JWKS document even by accident. The inline-PEM-or-path pair supports both secrets-manager injection (env var or config) and a volume-mounted key file, which are the two deployment shapes the framework's samples use. `sealed`, and registered singleton (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:196`) so the cache is process-wide. - **Where it's used**: the JWKS minimal-API endpoint calls `GetJsonWebKeySet()` per request; see [JwksEndpointExtensions](group-12-api-hosting-mapping.md#jwksendpointextensions). ### PasswordHasher @@ -2968,15 +3338,15 @@ live in later groups; this chapter is the engine those endpoints call into. and the executing primitive the same fact. The remaining shape (no per-app hasher, no algorithm parameter on the port) is what lets `[Rubric §11, Security]` be assessed once for both applications. - **Where it's used**: registered as `services.TryAddSingleton()` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:304`); the type is + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:314`); the type is stateless, so a singleton is safe. Consumers reach it through the port: [`AuthenticationServiceBase`](#authenticationservicebasetuser) takes it as a constructor - parameter (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:77`), + parameter (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:65`), verifies on login (`:159`, and a failure there increments the brute-force counter at `:160`) and hashes on registration (`:210`); [`ChangePasswordHandlerBase`](group-14-module-system-composition.md#changepasswordhandlerbasetuser-tcommand) verifies the current password then re-hashes the new one - (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:36`, + (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:44`, `:55`, `:61`); and [`ResetPasswordHandlerBase`](group-14-module-system-composition.md#resetpasswordhandlerbasetuser-tcommand) only hashes @@ -2989,32 +3359,37 @@ live in later groups; this chapter is the engine those endpoints call into. previously stored hashes, and nothing in this file or its call sites migrates them. ### TokenService -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:26` · Level 2 · class +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:27` · Level 2 · class (sealed partial) - **What it is**: the JWT issuer. It mints signed access tokens carrying the user id, email, role and display name (plus any extra claims a caller supplies), generates opaque random refresh tokens, projects both configured lifetimes as `TimeSpan`s, and re-reads an already-expired access token during the refresh flow. It signs with RSA-SHA256 or HMAC-SHA256, decided once at construction from - configuration (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:13-25`). -- **Depends on**: [`ITokenService`](#itokenservice) (the port it implements) and `IDisposable`; + configuration (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:87-97`). + The class is `partial` only so the source-generated `[LoggerMessage]` method at its foot can be + emitted (`:297-298`). +- **Depends on**: [`ITokenService`](#itokenservice) (the port it implements) and `IDisposable` + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:27`); [`JwtSettings`](group-08-auth.md#jwtsettings), [`JwtSigningAlgorithm`](group-08-auth.md#jwtsigningalgorithm) and [`JwksSettings`](group-08-auth.md#jwkssettings), all bound through - `IOptions` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:63-67`); - [`IPermissionRegistry`](#ipermissionregistry), a required constructor dependency (`:29`, `:65`) - that supplies the role-to-permission map baked into every access token. + `IOptions` (`:71-76`); + [`IPermissionRegistry`](#ipermissionregistry), a required constructor dependency (`:30`, `:73`) + that supplies the role-to-permission map baked into every access token; and an + `ILogger` (`:32`, `:75`) used for exactly one log line. Externals: `System.IdentityModel.Tokens.Jwt` (`JwtSecurityToken`, `JwtSecurityTokenHandler`), `Microsoft.IdentityModel.Tokens` (`SigningCredentials`, `SecurityKey`, `TokenValidationParameters`), - `System.Security.Cryptography` (`RSA`, `RandomNumberGenerator`), and `TimeProvider` for the clock. + `System.Security.Cryptography` (`RSA`, `RandomNumberGenerator`), `Microsoft.Extensions.Logging`, + and `TimeProvider` for the clock. - **Concept introduced: asymmetric issuance, and one deliberate hole in validation.** `[Rubric §11, Security]` assesses token issuance and algorithm-confusion defense, and two choices here are worth reading slowly. First, `GetPrincipalFromExpiredToken` sets `ValidateLifetime = false` - on purpose (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:171`) under - a narrowly scoped `#pragma warning disable CA5404` (`:170`, restored at `:172`) whose comment states + on purpose (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:182`) under + a narrowly scoped `#pragma warning disable CA5404` (`:181`, restored at `:183`) whose comment states why: the refresh flow's whole job is reading claims out of a token that has already expired. Every - other check stays on (`:167-169`). Second, the algorithm is pinned twice: `ValidAlgorithms` limits - validation to the one algorithm this instance was built for (`:176`), and the token header's `alg` is - re-compared with an ordinal string comparison *after* validation succeeds (`:185-186`). That pair is + other check stays on (`:178-180`). Second, the algorithm is pinned twice: `ValidAlgorithms` limits + validation to the one algorithm this instance was built for (`:187`), and the token header's `alg` is + re-compared with an ordinal string comparison *after* validation succeeds (`:196-200`). That pair is the defense against algorithm substitution, where an attacker takes the RSA *public* key (which is published on purpose) and presents it as an HMAC shared secret. `[Rubric §7, Microservices Readiness]` applies to the algorithm switch itself: RS256, the default, @@ -3022,114 +3397,127 @@ live in later groups; this chapter is the engine those endpoints call into. fetches the public key from the JWKS document that [`RsaJwksProvider`](#rsajwksprovider) publishes ([ADR-004](https://ivanball.github.io/docs/adr/004-authentication-dual-fetch.html)). HS256 remains available for a single-process monolith, where issuer and validator are the same host and a shared - secret costs nothing (`:16-23`). `[Rubric §14, Testability]` shows up in the constructor: the clock - is an injected, required `TimeProvider` with no `TimeProvider.System` fallback (`:66`, `:74`), - resolved from whatever DI registration adds it in the container's `AddServices` call, so a test - passes its own and can assert `iat`/`nbf`/`exp` without waiting for wall-clock time to pass. A third, - `[Rubric §20, Policy and Enforcement Points]` concept sits in `GenerateAccessToken` itself: every - access token now carries one [`AuthClaimTypes`](#authclaimtypes)`.Permission` claim per permission - [`IPermissionRegistry`](#ipermissionregistry) grants the token's role (`:118-130`), so a client can + secret costs nothing (`:93-97`). `[Rubric §14, Testability]` shows up in the constructor: the clock + is an injected, required `TimeProvider` with no `TimeProvider.System` fallback (`:74`, `:84`), + resolved from whatever DI registration adds it in the container's `AddServices` call (the parameter + doc says so at `:55-58`), so a test passes its own and can assert `iat`/`nbf`/`exp` without waiting + for wall-clock time to pass. A third, `[Rubric §20, Policy and Enforcement Points]` concept sits in + `GenerateAccessToken` itself: every access token carries one + [`AuthClaimTypes`](#authclaimtypes)`.Permission` claim per permission + [`IPermissionRegistry`](#ipermissionregistry) grants the token's role (`:129-141`), so a client can gate its own surface on a capability rather than a role name it would otherwise have to know. A host that declared no grants resolves [`UnconfiguredPermissionRegistry`](#unconfiguredpermissionregistry), - which grants nothing, and its tokens simply carry no permission claims (`:46-52`, the constructor's - own doc comment on the parameter). + which grants nothing, and its tokens simply carry no permission claims (`:48-54`, the constructor's + own doc comment on the parameter). A fourth, `[Rubric §13, Observability]`, is the split catch in + `GetPrincipalFromExpiredToken` (`:204-216`): ordinary rejections stay silent, but anything else is + logged before it is refused, so a fault in the validation path cannot hide behind an ordinary 401. - **Walkthrough** - - Fields (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:28-38`): the - settings snapshot, the injected `IPermissionRegistry` (`:29`), the `TimeProvider`, the - `SigningCredentials` used to sign, the `SecurityKey` and algorithm string used to validate, and two - nullable owned `RSA` handles. The comment at `:35-36` explains why `IDisposable` is on the class at - all: `RsaSecurityKey` does not own the `RSA` it wraps, so something has to. + - Fields (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:29-40`): the + settings snapshot, the injected `IPermissionRegistry` (`:30`), the `TimeProvider` (`:31`), the + `ILogger` (`:32`), the `SigningCredentials` used to sign, the `SecurityKey` and + algorithm string used to validate, and two nullable owned `RSA` handles (`:39-40`). The comment at + `:37-38` explains why `IDisposable` is on the class at all: `RsaSecurityKey` does not own the `RSA` + it wraps, so something has to. - The constructor - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:63-87`) takes - `IPermissionRegistry permissionRegistry` as a required parameter, null-guarded alongside - `jwtOptions` (`:69-70`) and assigned to `_permissionRegistry` (`:73`). `TimeProvider timeProvider` - is likewise a required parameter with no default (`:66`), assigned straight to `_timeProvider` - with no fallback (`:74`). It materializes all key + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:71-98`) takes + `IPermissionRegistry permissionRegistry` and `ILogger logger` as required + parameters, null-guarded alongside `jwtOptions` (`:78-80`) and assigned to `_permissionRegistry` + and `_logger` (`:83`, `:85`). `TimeProvider timeProvider` is likewise a required parameter with no + default (`:74`), assigned straight to `_timeProvider` with no fallback (`:84`); it is not + null-guarded. The logger's parameter doc states its whole job (`:59-64`): record the one + validation failure that is not an ordinary rejection. The constructor materializes all key material exactly once, so no token operation re-parses a PEM. For `JwtSigningAlgorithm.RS256` it - builds RSA credentials and pins `SecurityAlgorithms.RsaSha256` (`:78-80`); the key id it passes is - `jwksSettings?.Value.KeyId` falling back to a default `JwksSettings` instance (`:79`), which is how + builds RSA credentials and pins `SecurityAlgorithms.RsaSha256` (`:87-92`); the key id it passes is + `jwksSettings?.Value.KeyId` falling back to a default `JwksSettings` instance (`:90`), which is how the same `kid` ends up on both the token and the published JWK (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/RsaJwksProvider.cs:45`). Otherwise it - builds HMAC credentials and pins `HmacSha256` (`:84-85`). + builds HMAC credentials and pins `HmacSha256` (`:95-96`). - `GenerateAccessToken(userId, email, role, fullName, additionalClaims)` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:90-141`) reads the - clock once (`:97`), then builds six claims (`:103-111`): `sub` (the user id, formatted with + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:101-152`) reads the + clock once (`:108`), then builds six claims (`:114-122`): `sub` (the user id, formatted with `CultureInfo.InvariantCulture`), `jti` (a fresh GUID, so a token is individually identifiable), - `iat` as Unix seconds, and the standard name, email and role claims. The comment at `:99-102` is a + `iat` as Unix seconds, and the standard name, email and role claims. The comment at `:110-113` is a design note worth internalizing: `sub` is the *only* carrier of the user id. A duplicate custom claim used to ride alongside it, which meant two values that could disagree and two claim names every reader had to know; readers now go through [`ClaimsPrincipalExtensions`](#claimsprincipalextensions) instead. Caller-supplied claims are - appended (`:113-116`). Then, before the token is assembled, permission claims are added - (`:118-130`): `alreadyClaimed` collects any `Permission` claim value already present in `claims` - (`:122-125`), and every permission `_permissionRegistry.GetPermissions(role)` returns that is not - already in that set is appended, ordered ordinally for a deterministic token per role (`:127-130`). + appended (`:124-127`). Then, before the token is assembled, permission claims are added + (`:129-141`): `alreadyClaimed` collects any `Permission` claim value already present in `claims` + (`:133-136`), and every permission `_permissionRegistry.GetPermissions(role)` returns that is not + already in that set is appended, ordered ordinally for a deterministic token per role (`:138-141`). A `JwtSecurityToken` is then assembled with issuer, audience, `notBefore` from the injected clock and - `expires` at `now + AccessTokenExpirationMinutes`, and serialized. + `expires` at `now + AccessTokenExpirationMinutes` (`:143-149`), and serialized (`:151`). - `GenerateRefreshToken()` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:144-148`) returns 64 + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:155-159`) returns 64 CSPRNG bytes Base64-encoded. It is not a JWT and carries no claims: it is an opaque bearer string whose only property is being unguessable, and the store it is compared against is what gives it meaning. - `AccessTokenLifetime` and `RefreshTokenLifetime` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:151`, `:154`) project + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:162`, `:165`) project the configured minutes and days, so callers computing an expiry timestamp never re-read settings and never disagree with the token just minted. - `GetPrincipalFromExpiredToken(string token)` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:163-197`) builds the - validation parameters described above (`:165-177`), validates (`:183`), applies the + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:174-217`) builds the + validation parameters described above (`:176-188`), validates (`:194`), applies the post-validation `alg` re-check and returns `null` when the token is not a `JwtSecurityToken` or the - header disagrees (`:185-189`), and swallows every exception into `null` (`:193-196`). A malformed, + header disagrees (`:196-200`). The failure path is two catches. A `SecurityTokenException` or + `ArgumentException` (bad signature, wrong issuer or audience, malformed input) returns `null` + without a log line, because a client presenting a bad token is not an event (`:204-209`). Any + other exception is treated as a fault in the validation path: it is logged at warning level + through the source-generated `LogUnexpectedValidationFailure` (`:214`, declared at `:297-298`) and + still answered with `null` (`:210-216`), so the method fails closed either way. A malformed, forged, or wrong-issuer token therefore produces a plain "no principal" answer rather than a parser exception leaking to the caller. - - `Dispose()` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:200-204`) + - `Dispose()` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:220-224`) releases both owned `RSA` handles. - `BuildHmacCredentials` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:206-218`) throws - `InvalidOperationException` when `SecretForKey` is missing (`:208-212`) and Base64-decodes it into - a `SymmetricSecurityKey` used for both signing and validation (`:215-217`). + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:226-238`) throws + `InvalidOperationException` when `SecretForKey` is missing (`:228-232`) and Base64-decodes it into + a `SymmetricSecurityKey` used for both signing and validation (`:235-237`). - `BuildRsaCredentials` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:220-275`) throws when - `RsaPrivateKeyPem` is missing (`:224-228`), imports the private key (`:230-233`), and stamps the - key id on the signing key (`:239`) so every RS256 token carries a `kid` header. The comment at - `:235-238` gives the reason: a validator reading the published JWKS selects the key by name, and + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:240-295`) throws when + `RsaPrivateKeyPem` is missing (`:244-248`), imports the private key (`:250-253`), and stamps the + key id on the signing key (`:259`) so every RS256 token carries a `kid` header. The comment at + `:255-258` gives the reason: a validator reading the published JWKS selects the key by name, and without a `kid` it has to try every published key, which stops working the moment a rotation publishes two. The validation key prefers the configured `RsaPublicKeyPem` and otherwise derives - the public parameters from the private key (`:248-256`), so an issuer configured with only a - private key still validates its own tokens during refresh; it carries the same key id (`:261`). - Both nested `try`/`catch` blocks dispose the partially built `RSA` before rethrowing (`:264-268`, - `:270-274`), so a bad PEM does not leak a native handle. Because all of this runs in the + the public parameters from the private key (`:265-276`), so an issuer configured with only a + private key still validates its own tokens during refresh; it carries the same key id (`:281`). + Both nested `try`/`catch` blocks dispose the partially built `RSA` before rethrowing (`:284-288`, + `:290-294`), so a bad PEM does not leak a native handle. Because all of this runs in the constructor, missing or malformed key material fails at host startup, not on the first login. - **Why it's built this way**: [ADR-004](https://ivanball.github.io/docs/adr/004-authentication-dual-fetch.html) records the asymmetric-issuance rationale. The DI lifetime deserves its own read at the registration site - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:297-303`): the comment + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:307-313`): the comment there records that a scoped lifetime disposed the underlying `RSA` at end-of-request while the static `CryptoProviderCache` in `Microsoft.IdentityModel.Tokens` still held the cached `AsymmetricSignatureProvider` wrapping it, throwing `ObjectDisposedException` on the next RS256 sign. - Singleton is correct because the constructor depends only on singleton options and the service is - stateless afterwards. Baking permission claims into the token itself, rather than having a client - call back to look them up, is the same shape + Singleton is correct because the service is stateless after construction; the comment names only the + `IOptions` dependency, and the logger, clock and permission registry it also takes are + resolved once at that same construction. Baking permission claims into the token itself, rather than + having a client call back to look them up, is the same shape [ADR-020](https://ivanball.github.io/docs/adr/020-permission-based-authorization.html) establishes for permission-based authorization: the permission set travels with the credential. - **Where it's used**: registered as `services.TryAddSingleton()` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:303`), which resolves - [`IPermissionRegistry`](#ipermissionregistry) from the container alongside the JWT options, and - consumed through the port by [`AuthenticationServiceBase`](#authenticationservicebasetuser): the - lifetime feeds the response's expiry - (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:137-138`, with a - 15-minute floor when the setting is non-positive), the refresh flow reads the expired token (`:278`), - and refresh tokens are minted at `:627` and `:667`. That base also wraps this service in a private - `SessionStampingTokenService` pass-through - (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:936`) which - appends the [`AuthClaimTypes`](#authclaimtypes)`.SessionId` (`sid`) claim through the - `additionalClaims` parameter when a session is armed (`:782-800`, the claim added at `:797`), so - per-device session identity is layered on without this type knowing about sessions at all. The `sub` + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:313`), which resolves + [`IPermissionRegistry`](#ipermissionregistry) and the logger from the container alongside the JWT + options. Consumed through the port in two places. [`AuthSessionIssuer`](#authsessionissuer) mints + the tokens: its `AccessTokenLifetime` reads this service's lifetime with a 15-minute floor when the + setting is non-positive + (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:55-56`), and + refresh tokens are minted at `:319` and `:359`. The same file wraps this service in the private + [`SessionStampingTokenService`](#sessionstampingtokenservice) pass-through (`:448`), which appends the + [`AuthClaimTypes`](#authclaimtypes)`.SessionId` (`sid`) claim through the `additionalClaims` + parameter (`:484`), so per-device session identity is layered on without this type knowing about + sessions at all. [`AuthenticationServiceBase`](#authenticationservicebasetuser) reads the + expired token in the refresh flow + (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:297`). The `sub` claim it writes is what [`CurrentUserService`](#currentuserservice) and [`ClaimBasedUserIdProvider`](#claimbaseduseridprovider) read back. - **Caveats / not-in-source**: nothing here rotates or reloads key material. The keys are read once in the constructor - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:63-87`) and the + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:71-98`) and the instance is a singleton, so a key change takes a host restart. Refresh-token storage, rotation and revocation are not in this file either: it only produces the random string. @@ -3144,21 +3532,21 @@ live in later groups; this chapter is the engine those endpoints call into. - **Where it's used**: `LoginProtectionKey`-style builders in [LoginProtectionService](#loginprotectionservice) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/LoginProtectionService.cs`, 2 call sites), [PasswordResetTokenService](#passwordresettokenservice) (2 call sites) and [EmailConfirmationTokenService](#emailconfirmationtokenservice) (2 call sites); unit-tested directly by `EmailIdentityTests` (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Auth/EmailIdentityTests.cs`). ### EmailConfirmationTokenService -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:35` · Level 6 · class (sealed) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:36` · Level 6 · class (sealed) -- **What it is**: the [IEmailConfirmationTokenService](#iemailconfirmationtokenservice) implementation: the email-confirmation token lifecycle, issue a single-use token for an address, throttle how often one address can ask, hash the token at rest, cap wrong guesses, and consume the token on a successful redeem (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:35-37`). -- **Depends on**: `IEmailConfirmationTokenService` (the Application port it implements); [EmailConfirmationSettings](group-08-auth.md#emailconfirmationsettings) via `IOptions<>` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:37`, snapshotted at `:41`); [ICacheService](group-09-caching.md#icacheservice) (`:36`); [EmailConfirmationEntry](#emailconfirmationentry), its cached record; [Result](group-01-result-error-handling.md#result) and [Error](group-01-result-error-handling.md#error); [EmailIdentity](#emailidentity), the shared normalizer, at the two key builders (`:125`, `:127`); and from the BCL `SHA256`, `RandomNumberGenerator`, `CryptographicOperations`, and `System.Buffers.Text.Base64Url`. -- **Concept introduced: the same cache-backed token pattern, applied a third time.** `[Rubric §11, Security]` assesses credential issuance and redemption; `[Rubric §15, Best Practices & Code Quality]` assesses whether a proven idiom is reused or reinvented per flow. This type is structurally the same shape as [PasswordResetTokenService](#passwordresettokenservice): hash the token at rest, throttle issuance per address, cap validation attempts, and consume both the token and the request counter together on success. The `TokenKey` doc comment says so directly, citing [PasswordResetTokenService](#passwordresettokenservice) as the precedent for normalizing the cache key the same way [Email](group-02-domain-building-blocks.md#email) normalizes the lookup value (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:117-124`), and both key builders now call [EmailIdentity](#emailidentity)`.Normalize` instead of a private copy of the same helper (`:125`, `:127`). The same read-modify-write gap [LoginProtectionService](#loginprotectionservice) documents on its own throttle is inherited here too: the comment at the top of `IssueAsync` names it explicitly (`:49-50`). +- **What it is**: the [IEmailConfirmationTokenService](#iemailconfirmationtokenservice) implementation: the email-confirmation token lifecycle, issue a single-use token for an address, throttle how often one address can ask, hash the token at rest, cap wrong guesses, and consume the token on a successful redeem (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:12-25`). The class remarks add why its keys carry their own `emailconfirm:` prefix: a key shared with the reset service would make issuing a confirmation link silently invalidate an outstanding password-reset link for the same address (`:27-32`). +- **Depends on**: `IEmailConfirmationTokenService` (the Application port it implements, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:39`); [EmailConfirmationSettings](group-08-auth.md#emailconfirmationsettings) via `IOptions<>` (`:38`, snapshotted at `:43`); [ICacheService](group-09-caching.md#icacheservice) (`:37`); `TimeProvider` (`:39`), the clock for the expiry stamp and the remaining-lifetime check (`:35`); [EmailConfirmationEntry](#emailconfirmationentry), its cached record; [EmailConfirmationErrors](#emailconfirmationerrors) for the failure; [Result](group-01-result-error-handling.md#result) and [Error](group-01-result-error-handling.md#error); [EmailIdentity](#emailidentity), the shared normalizer, at the two key builders (`:130`, `:132`); and from the BCL `SHA256`, `RandomNumberGenerator`, `CryptographicOperations`, and `System.Buffers.Text.Base64Url`. +- **Concept introduced: the same cache-backed token pattern, applied a third time.** `[Rubric §11, Security]` assesses credential issuance and redemption; `[Rubric §15, Best Practices & Code Quality]` assesses whether a proven idiom is reused or reinvented per flow. This type is structurally the same shape as [PasswordResetTokenService](#passwordresettokenservice): hash the token at rest, throttle issuance per address, cap validation attempts, and consume both the token and the request counter together on success. The `TokenKey` doc comment says so directly, citing [PasswordResetTokenService](#passwordresettokenservice) as the precedent for normalizing the cache key the same way [Email](group-02-domain-building-blocks.md#email) normalizes the lookup value (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:122-129`), and both key builders call [EmailIdentity](#emailidentity)`.Normalize` instead of a private copy of the same helper (`:130`, `:132`). The same read-modify-write gap [LoginProtectionService](#loginprotectionservice) documents on its own throttle is inherited here too: the comment at the top of `IssueAsync` names it explicitly (`:51-52`). One place it departs from its sibling: redemption reads through `GetFromSharedStoreAsync` but takes no distributed lock, so the single-redemption guarantee [PasswordResetTokenService](#passwordresettokenservice) adds under [IDistributedLock](group-05-cqrs-pipeline.md#idistributedlock) is not present here (see Caveats). - **Walkthrough** - - `TokenByteLength = 32` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:39`) is the only constant; `_settings` is the snapshotted `EmailConfirmationSettings` (`:41`). - - `IssueAsync(string email, UserIdentifierType userId, CancellationToken)` (`:44-76`): increments the per-email request counter with the configured `RequestWindowMinutes` TTL (`:51-54`) and fails with `Authentication.ConfirmationThrottled` once the count exceeds `MaxRequestsPerEmail` (`:56-62`). It then mints 32 CSPRNG bytes rendered with `Base64Url.EncodeToString` (`:64`), builds an [EmailConfirmationEntry](#emailconfirmationentry) holding the Base64 digest of the token, the user id, a zero attempt count, and the absolute expiry as Unix seconds (`:67-71`), caches it under the token key with the configured lifetime (`:73`), and returns the raw token to the caller (`:75`); the raw token is never written anywhere else. - - `ValidateAndConsumeAsync(string email, string token, CancellationToken)` (`:79-115`): loads the entry and returns `InvalidToken()` when there is none (`:84-89`); a `FormatException` decoding the stored Base64 removes the unreadable record rather than leaving it to expire (`:91-101`); the comparison is `CryptographicOperations.FixedTimeEquals` over the two digests (`:103`), with `token ?? string.Empty` so a null token hashes rather than throwing; a mismatch records a failed attempt and returns the same generic failure (`:104-107`). On a match it removes **both** the token key and the address's request counter (`:109-112`), so a confirmed address is not left throttled out of a later legitimate request, and returns the entry's `UserId` (`:114`). - - `TokenKey`, `RequestKey`, `HashToken` (`:117-130`): `TokenKey` and `RequestKey` each call [EmailIdentity](#emailidentity)`.Normalize` (`:125`, `:127`), producing `emailconfirm:token:{normalized}` and `emailconfirm:req:{normalized}` keys; `HashToken` is the shared SHA-256 helper (`:129-130`). - - `RecordFailedAttemptAsync` (`:132-153`): computes `attempts = entry.FailedAttempts + 1` and the remaining lifetime from `ExpiresAtUnixSeconds` (`:137-138`). At `MaxValidationAttempts`, or once the remaining lifetime is non-positive, it deletes the record (`:140-144`); otherwise it rewrites the entry with `entry with { FailedAttempts = attempts }` at the **remaining** TTL, not a fresh one (`:146-152`). - - `InvalidToken()` (`:155-157`) is the single failure factory built from `EmailConfirmationErrors.InvalidToken`, so unknown, expired, mismatched and attempt-capped tokens collapse to one error rather than giving an oracle for which addresses have an outstanding confirmation. -- **Why it's built this way**: reusing the pattern [PasswordResetTokenService](#passwordresettokenservice) established, rather than a bespoke implementation, is what keeps a third token lifecycle (login lockout, password reset, email confirmation) auditable as one idiom instead of three. [ADR-116](https://ivanball.github.io/docs/adr/116-identity-completions-opt-in.html) frames identity-completion flows like this one as opt-in per host. + - `TokenByteLength = 32` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:41`) is the only constant; `_settings` is the snapshotted `EmailConfirmationSettings` (`:43`). The primary constructor takes `ICacheService`, `IOptions` and `TimeProvider` (`:36-39`). + - `IssueAsync(string email, UserIdentifierType userId, CancellationToken)` (`:46-78`): increments the per-email request counter with the configured `RequestWindowMinutes` TTL (`:53-56`) and fails with `Authentication.ConfirmationThrottled` once the count exceeds `MaxRequestsPerEmail` (`:58-64`). It then mints 32 CSPRNG bytes rendered with `Base64Url.EncodeToString` (`:66`), builds an [EmailConfirmationEntry](#emailconfirmationentry) holding the Base64 digest of the token, the user id, a zero attempt count, and the absolute expiry as Unix seconds computed from the injected clock (`:69-73`), caches it under the token key with the configured lifetime (`:75`), and returns the raw token to the caller (`:77`); the raw token is never written anywhere else. + - `ValidateAndConsumeAsync(string email, string token, CancellationToken)` (`:81-120`): loads the entry with `GetFromSharedStoreAsync` (`:90`), whose comment gives the reason (`:88-89`): a token consumed on another replica must be a miss here, never a stale local copy that would let it be redeemed twice. It returns `InvalidToken()` when there is none (`:91-94`); a `FormatException` decoding the stored Base64 removes the unreadable record rather than leaving it to expire (`:96-106`); the comparison is `CryptographicOperations.FixedTimeEquals` over the two digests (`:108`), with `token ?? string.Empty` so a null token hashes rather than throwing; a mismatch records a failed attempt and returns the same generic failure (`:108-112`). On a match it removes **both** the token key and the address's request counter (`:114-117`), so a confirmed address is not left throttled out of a later legitimate request, and returns the entry's `UserId` (`:119`). + - `TokenKey`, `RequestKey`, `HashToken` (`:122-135`): `TokenKey` and `RequestKey` each call [EmailIdentity](#emailidentity)`.Normalize` (`:130`, `:132`), producing `emailconfirm:token:{normalized}` and `emailconfirm:req:{normalized}` keys; `HashToken` is the shared SHA-256 helper (`:134-135`). + - `RecordFailedAttemptAsync` (`:137-158`): computes `attempts = entry.FailedAttempts + 1` and the remaining lifetime from `ExpiresAtUnixSeconds` against the injected clock (`:142-143`). At `MaxValidationAttempts`, or once the remaining lifetime is non-positive, it deletes the record (`:145-149`); otherwise it rewrites the entry with `entry with { FailedAttempts = attempts }` at the **remaining** TTL, not a fresh one (`:151-157`). + - `InvalidToken()` (`:160-162`) is the single failure factory built from `EmailConfirmationErrors.InvalidToken`, so unknown, expired, mismatched and attempt-capped tokens collapse to one error rather than giving an oracle for which addresses have an outstanding confirmation. +- **Why it's built this way**: reusing the pattern [PasswordResetTokenService](#passwordresettokenservice) established, rather than a bespoke implementation, is what keeps a third token lifecycle (login lockout, password reset, email confirmation) auditable as one idiom instead of three. [ADR-116](https://ivanball.github.io/docs/adr/116-identity-completions-opt-in.html) frames identity-completion flows like this one as opt-in per host, and [ADR-077](https://ivanball.github.io/docs/adr/077-hybridcache-substrate.html) is the cache substrate whose shared-store read the redemption relies on. Taking `TimeProvider` rather than reading `DateTimeOffset.UtcNow` lets a test move the clock past the expiry without waiting. - **Where it's used**: registered `services.TryAddScoped()` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:75`), directly after the `EmailConfirmationSettings` binding (`:69-70`). -- **Caveats / not-in-source**: the per-email request throttle is a read-modify-write on the distributed cache, the same gap [LoginProtectionService](#loginprotectionservice) documents on its own counters: concurrent requests can undercount, which loosens the throttle but never tightens it. The failed-attempt rewrite has the same property against the attempt cap. +- **Caveats / not-in-source**: the per-email request throttle is a read-modify-write on the distributed cache, the same gap [LoginProtectionService](#loginprotectionservice) documents on its own counters: concurrent requests can undercount, which loosens the throttle but never tightens it. The failed-attempt rewrite has the same property against the attempt cap. Redemption is not serialized: the shared-store read rules out a stale replica-local copy, but nothing in this file stops two concurrent redemptions that both read before either removes from both succeeding, which is the window [PasswordResetTokenService](#passwordresettokenservice) closes with a lock. ### LoginProtectionService > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/LoginProtectionService.cs:19` · Level 6 · class (sealed) @@ -3175,30 +3563,32 @@ live in later groups; this chapter is the engine those endpoints call into. - `ResetFailedAttemptsAsync` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/LoginProtectionService.cs:83-87`): removes both keys on a successful login (`:85-86`). - `CheckRegistrationRateLimitAsync(string? ipAddress, ...)` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/LoginProtectionService.cs:90-106`): a null or empty IP is unrestricted (`:92-95`); otherwise it compares the per-IP count against `MaxRegistrationsPerIpPerHour` and fails with `Auth.RegistrationRateLimitExceeded` (`:100-105`). - `IncrementRegistrationCountAsync` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/LoginProtectionService.cs:109-123`): no-ops on a missing IP (`:111-114`) and otherwise increments the per-IP counter with the `RegistrationRateLimitWindowMinutes` TTL (`:119-122`). The comment (`:116-118`) notes the TTL is refreshed on every write, so the window slides rather than staying anchored to the first registration, which only ever tightens the limit. -- **Why it's built this way**: reusing [ICacheService](group-09-caching.md#icacheservice) (Redis in production, in-memory fallback) instead of a bespoke store keeps the service thin and lets counters expire naturally by TTL rather than needing a sweep job; `IOptions<>` keeps every threshold configurable per environment ([ADR-029](https://ivanball.github.io/docs/adr/029-authentication-brute-force-protection.html)). Registered scoped (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:152`). -- **Where it's used**: injected into [AuthenticationServiceBase](#authenticationservicebasetuser) (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:78`), which calls all five members across its login and registration flows: the lockout check (`:131`), an increment on both the unknown-user and wrong-password branches (`:146`, `:161`), the reset on success (`:178`), and the registration rate-limit check and increment (`:197`, `:256`). Incrementing on the unknown-user branch as well as the wrong-password branch is what keeps the endpoint from becoming a user-enumeration oracle by timing or by lockout behavior. +- **Why it's built this way**: reusing [ICacheService](group-09-caching.md#icacheservice) (Redis in production, in-memory fallback) instead of a bespoke store keeps the service thin and lets counters expire naturally by TTL rather than needing a sweep job; `IOptions<>` keeps every threshold configurable per environment ([ADR-029](https://ivanball.github.io/docs/adr/029-authentication-brute-force-protection.html)). Registered scoped (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:158`). +- **Where it's used**: injected into [AuthenticationServiceBase](#authenticationservicebasetuser) (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:66`), which calls all five members across its login and registration flows: the lockout check (`:131`), an increment on both the unknown-user and wrong-password branches (`:146`, `:161`), the reset on success (`:178`), and the registration rate-limit check and increment (`:197`, `:256`). Incrementing on the unknown-user branch as well as the wrong-password branch is what keeps the endpoint from becoming a user-enumeration oracle by timing or by lockout behavior. - **Caveats / not-in-source**: the increment is documented in source as **not atomic** on the distributed cache today (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/LoginProtectionService.cs:55-63`). [DistributedCacheService](group-09-caching.md#distributedcacheservice)`.IncrementAsync` is a read-modify-write, because the Redis `INCR` it used to issue wrote a plain string key while `IDistributedCache` reads entries back as hashes, and the mismatch made the counter unreadable (`WRONGTYPE`). The accepted cost: genuinely parallel attempts can overwrite each other's increments, so a concurrent burst can stay under `MaxFailedAttempts`. Sequential guessing, which is what a credential-stuffing run against one account looks like, still trips the lockout. The comment names the two ways to close the gap (a Lua script that increments within the hash layout, or moving counters off `IDistributedCache`); neither is implemented today. ### PasswordResetTokenService -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:26` · Level 6 · class (sealed) - -- **What it is**: the [IPasswordResetTokenService](#ipasswordresettokenservice) implementation, and the whole forgot-password token lifecycle in one file: issue a single-use token for an address, throttle how often one address can ask, hash the token at rest, cap wrong guesses, and consume the token on a successful redeem (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:12-24`). -- **Depends on**: [IPasswordResetTokenService](#ipasswordresettokenservice) (the Application port, `:5`); [PasswordResetSettings](#passwordresetsettings) via `IOptions<>` (`:28`, snapshotted at `:32`); [ICacheService](group-09-caching.md#icacheservice) (`:6`, `:27`); [PasswordResetEntry](#passwordresetentry) (its cached record); [Result](group-01-result-error-handling.md#result) and [Error](group-01-result-error-handling.md#error) (`:7`); [EmailIdentity](#emailidentity), the shared normalizer, at the two key builders (`:40`, `:42`); and from the BCL `SHA256`, `RandomNumberGenerator`, `CryptographicOperations`, and `System.Buffers.Text.Base64Url` (`:1-3`). -- **Concept introduced: a reset token is a bearer credential, so treat it like a password.** `[Rubric §11, Security]` assesses credential issuance and redemption; `[Rubric §8, Data Architecture]` assesses picking the right store for the right lifetime. Four properties are designed in, and the class doc lists all four up front (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:15-24`). - - **Hashed at rest.** Only `SHA256.HashData(...)` of the token is stored (`:44-45`, `:72`), so a cache dump does not hand out working reset links. Like [RefreshSession](#refreshsession) and unlike a password, a reset token is high-entropy (32 random bytes, `:30`, `:68`) and short-lived, which is why a plain digest is sufficient here where [PasswordHasher](#passwordhasher) needs 600,000 PBKDF2 iterations: there is no dictionary to run against a 256-bit random value. - - **One active token per email.** The key is derived purely from the address (`:40`), so `SetAsync` overwrites (`:77`) and an older link stops working the moment a newer one is requested. - - **Attempt cap.** Wrong tokens are counted on the record and the record is discarded at `MaxValidationAttempts` (`:129-133`), which turns the token into a credential you cannot grind at. +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:33` · Level 6 · class (sealed) + +- **What it is**: the [IPasswordResetTokenService](#ipasswordresettokenservice) implementation, and the whole forgot-password token lifecycle in one file: issue a single-use token for an address, throttle how often one address can ask, hash the token at rest, cap wrong guesses, and consume the token exactly once on a successful redeem (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:12-28`). +- **Depends on**: [IPasswordResetTokenService](#ipasswordresettokenservice) (the Application port, `:5`, `:37`); [PasswordResetSettings](#passwordresetsettings) via `IOptions<>` (`:35`, snapshotted at `:45`); [ICacheService](group-09-caching.md#icacheservice) (`:6`, `:34`); [IDistributedLock](group-05-cqrs-pipeline.md#idistributedlock) (`:36`), which serializes redemptions of one token (`:31`); `TimeProvider` (`:37`), the clock for the expiry stamp and the remaining-lifetime check (`:32`); [PasswordResetEntry](#passwordresetentry) (its cached record); [Result](group-01-result-error-handling.md#result) and [Error](group-01-result-error-handling.md#error) (`:7`); [EmailIdentity](#emailidentity), the shared normalizer, at the two key builders (`:53`, `:55`); and from the BCL `SHA256`, `RandomNumberGenerator`, `CryptographicOperations`, and `System.Buffers.Text.Base64Url` (`:1-3`). +- **Concept introduced: a reset token is a bearer credential, so treat it like a password.** `[Rubric §11, Security]` assesses credential issuance and redemption; `[Rubric §8, Data Architecture]` assesses picking the right store for the right lifetime. Five properties are designed in, and the class doc lists all five up front (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:15-27`). + - **Hashed at rest.** Only `SHA256.HashData(...)` of the token is stored (`:57-58`, `:85`), so a cache dump does not hand out working reset links. Like [RefreshSession](#refreshsession) and unlike a password, a reset token is high-entropy (32 random bytes, `:39`, `:81`) and short-lived, which is why a plain digest is sufficient here where [PasswordHasher](#passwordhasher) needs 600,000 PBKDF2 iterations: there is no dictionary to run against a 256-bit random value. + - **One active token per email.** The key is derived purely from the address (`:53`), so `SetAsync` overwrites (`:90`) and an older link stops working the moment a newer one is requested. + - **Attempt cap.** Wrong tokens are counted on the record and the record is discarded at `MaxValidationAttempts` (`:168-172`), which turns the token into a credential you cannot grind at. + - **Single redemption.** The read, compare and remove run as one critical section under an [IDistributedLock](group-05-cqrs-pipeline.md#idistributedlock) on the token key (`:24-26`, `:103-117`), so two concurrent redemptions of one token cannot both succeed, and a redemption that cannot take the lock is answered as invalid rather than as a second success. The lookup itself reads the shared store, so a token consumed on another replica is a miss rather than a stale local copy (`:126-128`). - **No schema change, no sweeper.** The whole lifecycle rides [ICacheService](group-09-caching.md#icacheservice), so expiry is the cache TTL rather than a background job over a table ([ADR-091](https://ivanball.github.io/docs/adr/091-cache-backed-password-reset.html)). Compare [LoginProtectionService](#loginprotectionservice), which reaches the same conclusion for lockout counters. - **Walkthrough** - - Primary constructor takes `ICacheService cacheService` and `IOptions settings` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:26-28`), snapshotting `settings.Value` into `_settings` (`:32`). `TokenByteLength = 32` (`:30`) is the only other constant. - - `TokenKey` and `RequestKey` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:40-42`) each call [EmailIdentity](#emailidentity)`.Normalize(email)` rather than carrying a private normalizer, and the doc comment cites [LoginProtectionService](#loginprotectionservice) as the origin of the rule (`:34-39`): keys built from raw request input would give `User@x.com` and `user@x.com` independent tokens **and** independent request counters while resolving to one account. `TokenKey` produces `pwdreset:token:{normalized}` (`:40`) and `RequestKey` produces `pwdreset:req:{normalized}` (`:42`). `HashToken` is the shared SHA-256 helper (`:44-45`). - - `IssueAsync(string email, UserIdentifierType userId, CancellationToken)` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:48-80`): throttle first. It increments the per-email request counter with the `RequestWindowMinutes` TTL (`:55-58`) and fails with `Error.Unauthorized("Auth.ResetThrottled", ...)` once the count exceeds `MaxRequestsPerEmail` (`:60-66`). Only then does it mint the token: 32 CSPRNG bytes rendered with `Base64Url.EncodeToString` (`:68`, URL-safe because the token travels in a query string), builds a [PasswordResetEntry](#passwordresetentry) holding the Base64 digest, the user id, a zero attempt count and the absolute expiry as Unix seconds (`:71-75`), caches it under the token key with the configured lifetime (`:77`), and returns the **raw** token to the caller to email (`:79`). The raw token exists only in that return value: it is never written anywhere. - - `ValidateAndConsumeAsync(string email, string token, CancellationToken)` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:83-119`): loads the entry (`:88-89`) and returns `InvalidToken()` when there is none (`:90-93`). A `FormatException` decoding the stored Base64 removes the unreadable record rather than leaving it to expire (`:96-105`). The comparison is `CryptographicOperations.FixedTimeEquals` over the two digests (`:107`), the same timing-side-channel defense [PasswordHasher](#passwordhasher) uses, with `token ?? string.Empty` so a null token hashes rather than throwing. A mismatch records a failed attempt and returns the same generic failure (`:108-111`). On a match it removes **both** the token key and the address's request counter (`:115-116`), so a successful reset does not leave the user throttled out of a later legitimate request (`:113-114`), and returns the entry's `UserId` (`:118`). - - `RecordFailedAttemptAsync` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:121-142`): computes `attempts = entry.FailedAttempts + 1` and the remaining lifetime from `ExpiresAtUnixSeconds` (`:126-127`). At `MaxValidationAttempts`, or once the remaining lifetime is non-positive, it deletes the record (`:129-133`). Otherwise it rewrites the entry with `entry with { FailedAttempts = attempts }` and a TTL of the **remaining** seconds, not a fresh lifetime (`:135-141`), because a wrong guess must not be able to extend how long the token stays redeemable. - - `InvalidToken()` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:144-148`) is the single failure factory: unknown, expired, mismatched and attempt-capped all collapse to one `Auth.InvalidResetToken` error with one message. That uniformity is deliberate: distinct errors would make the endpoint an oracle for which addresses have an outstanding reset. -- **Why it's built this way**: [ADR-091](https://ivanball.github.io/docs/adr/091-cache-backed-password-reset.html) records the decision. It extends [ADR-029](https://ivanball.github.io/docs/adr/029-authentication-brute-force-protection.html) (the cache-backed protection idiom reused here) and sits beside [ADR-032](https://ivanball.github.io/docs/adr/032-password-hashing.html), which decided how a password is stored but not how a user who has lost one gets a new one. Keeping the token out of the database is what makes the feature additive: no migration, no new table, and nothing to reap. -- **Where it's used**: registered `services.TryAddScoped()` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:158`), directly after the `PasswordResetSettings` binding (`:137-140`). [ForgotPasswordHandlerBase](group-14-module-system-composition.md#forgotpasswordhandlerbasetuser-tcommand) calls `IssueAsync` and emails the resulting link (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ForgotPassword/ForgotPasswordHandlerBase.cs:73`); [ResetPasswordHandlerBase](group-14-module-system-composition.md#resetpasswordhandlerbasetuser-tcommand) calls `ValidateAndConsumeAsync` (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:76-78`) **before** the save, because leaving the token live until the write succeeds would open a replay window, and a token burned by a later invariant failure only costs the user one more reset request (`:58-60`). It is unit-tested by [PasswordResetTokenServiceTests](group-28-testing-infrastructure.md#per-project-test-rollup). -- **Caveats / not-in-source**: the per-email request throttle inherits [LoginProtectionService](#loginprotectionservice)'s non-atomic increment, and the source says so where it matters (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:53-54`): concurrent requests can undercount, which loosens the throttle but never tightens it. The failed-attempt rewrite is a read-modify-write too, so a burst of simultaneous wrong guesses can lose increments against the attempt cap; sequential guessing still trips it. + - Primary constructor takes `ICacheService cacheService`, `IOptions settings`, `IDistributedLock distributedLock` and `TimeProvider timeProvider` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:33-37`), snapshotting `settings.Value` into `_settings` (`:45`). `TokenByteLength = 32` (`:39`) is the one constant; two static timeouts govern the redeem lock, `RedeemLockTimeToLive` of 10 seconds (`:41`) and `RedeemLockWait` of 5 seconds (`:43`). + - `TokenKey` and `RequestKey` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:53-55`) each call [EmailIdentity](#emailidentity)`.Normalize(email)` rather than carrying a private normalizer, and the doc comment cites [LoginProtectionService](#loginprotectionservice) as the origin of the rule (`:47-52`): keys built from raw request input would give `User@x.com` and `user@x.com` independent tokens **and** independent request counters while resolving to one account. `TokenKey` produces `pwdreset:token:{normalized}` (`:53`) and `RequestKey` produces `pwdreset:req:{normalized}` (`:55`). `HashToken` is the shared SHA-256 helper (`:57-58`). + - `IssueAsync(string email, UserIdentifierType userId, CancellationToken)` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:61-93`): throttle first. It increments the per-email request counter with the `RequestWindowMinutes` TTL (`:68-71`) and fails with `Error.Unauthorized("Auth.ResetThrottled", ...)` once the count exceeds `MaxRequestsPerEmail` (`:73-79`). Only then does it mint the token: 32 CSPRNG bytes rendered with `Base64Url.EncodeToString` (`:81`, URL-safe because the token travels in a query string), builds a [PasswordResetEntry](#passwordresetentry) holding the Base64 digest, the user id, a zero attempt count and the absolute expiry as Unix seconds computed from the injected clock (`:84-88`), caches it under the token key with the configured lifetime (`:90`), and returns the **raw** token to the caller to email (`:92`). The raw token exists only in that return value: it is never written anywhere. + - `ValidateAndConsumeAsync(string email, string token, CancellationToken)` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:96-118`): builds the token key (`:101`), then tries to acquire the distributed lock `lock:{key}` with the 10-second TTL and a 5-second wait (`:106-108`). A null handle (contended or unavailable) returns `InvalidToken()` (`:109-112`); otherwise it runs `RedeemAsync` inside an `await using` over the handle, so the lock is released however the redemption ends (`:114-117`). The comment at `:103-105` states the reason: without the critical section, two callers that both read before either removes would both succeed. + - `RedeemAsync` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:120-158`): loads the entry with `GetFromSharedStoreAsync` (`:128`) and returns `InvalidToken()` when there is none (`:129-132`). A `FormatException` decoding the stored Base64 removes the unreadable record rather than leaving it to expire (`:134-144`). The comparison is `CryptographicOperations.FixedTimeEquals` over the two digests (`:146`), the same timing-side-channel defense [PasswordHasher](#passwordhasher) uses, with `token ?? string.Empty` so a null token hashes rather than throwing. A mismatch records a failed attempt and returns the same generic failure (`:146-150`). On a match it removes **both** the token key and the address's request counter (`:154-155`), so a successful reset does not leave the user throttled out of a later legitimate request (`:152-153`), and returns the entry's `UserId` (`:157`). + - `RecordFailedAttemptAsync` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:160-181`): computes `attempts = entry.FailedAttempts + 1` and the remaining lifetime from `ExpiresAtUnixSeconds` against the injected clock (`:165-166`). At `MaxValidationAttempts`, or once the remaining lifetime is non-positive, it deletes the record (`:168-172`). Otherwise it rewrites the entry with `entry with { FailedAttempts = attempts }` and a TTL of the **remaining** seconds, not a fresh lifetime (`:174-180`), because a wrong guess must not be able to extend how long the token stays redeemable. + - `InvalidToken()` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:183-187`) is the single failure factory: unknown, expired, mismatched, attempt-capped and lock-contended all collapse to one `Auth.InvalidResetToken` error with one message. That uniformity is deliberate: distinct errors would make the endpoint an oracle for which addresses have an outstanding reset. +- **Why it's built this way**: [ADR-091](https://ivanball.github.io/docs/adr/091-cache-backed-password-reset.html) records the decision. It extends [ADR-029](https://ivanball.github.io/docs/adr/029-authentication-brute-force-protection.html) (the cache-backed protection idiom reused here) and sits beside [ADR-032](https://ivanball.github.io/docs/adr/032-password-hashing.html), which decided how a password is stored but not how a user who has lost one gets a new one. Keeping the token out of the database is what makes the feature additive: no migration, no new table, and nothing to reap. The redeem lock is the [ADR-108](https://ivanball.github.io/docs/adr/108-distributed-lock-primitive.html) primitive, and the shared-store read leans on the [ADR-077](https://ivanball.github.io/docs/adr/077-hybridcache-substrate.html) cache substrate. Taking `TimeProvider` rather than reading `DateTimeOffset.UtcNow` lets a test move the clock past the expiry without waiting. +- **Where it's used**: registered `services.TryAddScoped()` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:164`), directly after the `PasswordResetSettings` binding (`:160-161`). [ForgotPasswordHandlerBase](group-14-module-system-composition.md#forgotpasswordhandlerbasetuser-tcommand) calls `IssueAsync` and emails the resulting link (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ForgotPassword/ForgotPasswordHandlerBase.cs:73`); [ResetPasswordHandlerBase](group-14-module-system-composition.md#resetpasswordhandlerbasetuser-tcommand) calls `ValidateAndConsumeAsync` (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:76-77`) **before** the save, because leaving the token live until the write succeeds would open a replay window, and a token burned by a later invariant failure only costs the user one more reset request (`:74-75`). It is unit-tested by [PasswordResetTokenServiceTests](group-28-testing-infrastructure.md#per-project-test-rollup). +- **Caveats / not-in-source**: the per-email request throttle inherits [LoginProtectionService](#loginprotectionservice)'s non-atomic increment, and the source says so where it matters (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:66-67`): concurrent requests can undercount, which loosens the throttle but never tightens it. The redeem lock serializes redemptions, so the failed-attempt rewrite (also a read-modify-write) no longer races another redemption of the same token on a host whose lock is shared; the lock's own cross-replica strength depends on which [IDistributedLock](group-05-cqrs-pipeline.md#idistributedlock) the host registers, which is not determinable from this file. ### AdministrationPermissions > MMCA.Common.Shared · `MMCA.Common.Shared.Auth.Permissions` · `MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/Permissions/AdministrationPermissions.cs:15` · Level 0 · class (static) @@ -3302,14 +3692,14 @@ live in later groups; this chapter is the engine those endpoints call into. - `Permission` is written by [TokenService](#tokenservice) itself, one claim per permission the injected `IPermissionRegistry` grants the token's role, sorted ordinally and deduplicated against any permission claim the caller already supplied - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:118-130`), and is read + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:129-141`), and is read by [PermissionAuthorizationHandler](#permissionauthorizationhandler), which checks `context.User.HasClaim(AuthClaimTypes.Permission, requirement.Permission)` before falling back to the registry (`MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/PermissionAuthorizationHandler.cs:30-31`), and by [ClaimsPrincipalExtensions](#claimsprincipalextensions)`.HasPermissionClaim`. - `Subject` is written by [TokenService](#tokenservice) as `JwtRegisteredClaimNames.Sub` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:105`, with the + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:116`, with the one-carrier rationale at `:88-91`) and read through [ClaimsPrincipalExtensions](#claimsprincipalextensions) by [CurrentUserService](#currentuserservice), [ClaimBasedUserIdProvider](#claimbaseduseridprovider), @@ -3317,8 +3707,8 @@ live in later groups; this chapter is the engine those endpoints call into. partitioner. - `SessionId` is stamped by the private [SessionStampingTokenService](#sessionstampingtokenservice) decorator inside - [AuthenticationServiceBase](#authenticationservicebasetuser) - (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:972`) and read + [AuthSessionIssuer](#authsessionissuer) + (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:448`) and read back by `FindSessionId` for the "my sessions" endpoint (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/AuthControllerBase.cs:187`). - `MultiFactor` and its two method values are stamped by the sign-in flow after a second factor @@ -3326,7 +3716,7 @@ live in later groups; this chapter is the engine those endpoints call into. [ClaimsPrincipalExtensions](#claimsprincipalextensions)`.HasMultiFactor`/`FindMultiFactorMethod` by any use case marked `IRequiresMfa`. - **Caveats / not-in-source**: the `Permission` claim is now written by every token the framework - mints (`TokenService.cs:118-130`), which supersedes the older behavior where no shipped token + mints (`TokenService.cs:129-141`), which supersedes the older behavior where no shipped token issuer wrote it; a reader depending on `Permission` being absent from real tokens would be wrong today. The registry lookup remains the authoritative check either way, since the claim path is additive. @@ -3443,6 +3833,58 @@ live in later groups; this chapter is the engine those endpoints call into. `UserListDTO` (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/UserListDTO.cs`). +### PasswordComplexity +> MMCA.Common.Shared · `MMCA.Common.Shared.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/PasswordComplexity.cs:18` · Level 0 · class (static partial) + +- **What it is**: the one definition of the strong-password rule: the two length bounds, four + source-generated character-class regexes, and an `Evaluate` predicate that combines them + (`MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/PasswordComplexity.cs:5-18`). +- **Depends on**: nothing first-party; BCL `System.Text.RegularExpressions` and the + `[GeneratedRegex]` source generator (`PasswordComplexity.cs:1,34`). +- **Concept introduced, one rule evaluated on both sides of the wire.** `[Rubric §24, Forms, + Validation & UX Safety]` assesses validation parity: the client form must give the verdict the + API would. The doc comment states the purpose directly (`PasswordComplexity.cs:5-10`): the server + validator ([StrongPasswordRules](group-06-validation.md#requiredstringrulest-optionalstringrulest-emailrulest-positiveintrulest-positivedecimalrulest-nonnegativeintrulest-requiredidrulest-tid-optionalpositiveidrulest-tid-passwordrulest-strongpasswordrulest) + in MMCA.Common.Application) and the client attribute + ([PasswordComplexityAttribute](group-15-common-ui-framework.md#passwordcomplexityattribute) in + MMCA.Common.UI) both read this type, so they cannot disagree on the same input. It lives in Shared + because Shared is the one project both of those layers may reference (UI depends on Shared only). + The character classes are Unicode-aware (`PasswordComplexity.cs:11-16`): an accented or CJK letter + counts as a letter, never as the "special" character. +- **Walkthrough**: `MinimumLength` is `8` (`PasswordComplexity.cs:28`) and `MaximumLength` is `128` + (`PasswordComplexity.cs:31`), both `const int` measured in UTF-16 code units, so they are usable in + attribute arguments. Four `public static partial Regex` properties are source-generated by + `[GeneratedRegex]`, each with a 1000 ms match timeout: `Uppercase` (`\p{Lu}`, + `PasswordComplexity.cs:34-35`), `Lowercase` (`\p{Ll}`, `:38-39`), `Digit` (`\p{Nd}`, `:42-43`), and + `SpecialCharacter` (`[^\p{L}\p{Nd}]`, anything that is neither a letter nor a decimal digit, + `:49-50`). `Evaluate(string? password)` (`PasswordComplexity.cs:52-57`) returns `true` only when the + input is non-null, at least `MinimumLength` long, and matches all four regexes. It deliberately does + **not** check `MaximumLength`: the upper bound is a separate rule with its own message on both + sides (`PasswordComplexity.cs:52-56`). +- **Why it's built this way**: a client rule and a server rule written separately are two copies of + one policy that drift independently. Exposing the regexes (not only `Evaluate`) lets the + server's FluentValidation chain keep one message per failed class, while the client attribute asks + the single yes/no question. Source-generated regexes avoid runtime regex construction, and the + match timeout bounds evaluation of untrusted input. +- **Where it's used**: `StrongPasswordRules` feeds both length constants and all four regexes into + its rule chain + (`MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:196-201`); + [PasswordComplexityAttribute](group-15-common-ui-framework.md#passwordcomplexityattribute) calls + `Evaluate`, leaving empty input to `[Required]` + (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/PasswordComplexityAttribute.cs:24`); + [RegisterModel](group-15-common-ui-framework.md#registermodel) and + [ResetPasswordModel](group-15-common-ui-framework.md#resetpasswordmodel) pair that attribute with + `[StringLength(PasswordComplexity.MaximumLength)]` to supply the separate upper-bound rule + (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/RegisterModel.cs:27-28`, + `MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/ResetPasswordModel.cs:25-26`); pinned by + `MMCA.Common/Tests/Core/MMCA.Common.Shared.Tests/Auth/PasswordComplexityTests.cs`. +- **Caveats / not-in-source**: the server's error messages still hard-code the numbers ("at least 8 + characters", "longer than 128 characters") rather than interpolating the constants + (`CommonValidationRules.cs:196-197`), and so does the attribute's default message + (`PasswordComplexityAttribute.cs:18`); changing a bound means editing those strings too. The + plain `PasswordRules` sibling in the same file still uses literal `8`/`128` + (`CommonValidationRules.cs:180-181`) and does not read this type. + ### ClaimsPrincipalExtensions > MMCA.Common.Shared · `MMCA.Common.Shared.Auth` · `MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/ClaimsPrincipalExtensions.cs:18` · Level 1 · class (static) @@ -3515,10 +3957,10 @@ live in later groups; this chapter is the engine those endpoints call into. value of the `MultiFactor` claim, or `null` when the principal carries none, for audit and UI copy such as "signed in with a recovery code". - **Why it's built this way**: [TokenService](#tokenservice) records the other half of the story in a - comment (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:99-102`): a + comment (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:110-113`): a duplicate custom user-id claim used to ride alongside `sub`, which meant two values that could disagree and two claim names every reader had to know. Collapsing the writer to one claim - (`TokenService.cs:105`) is only safe because one reader absorbs the mapping difference, which is this + (`TokenService.cs:116`) is only safe because one reader absorbs the mapping difference, which is this type. The `sid` half comes from [ADR-097](https://ivanball.github.io/docs/adr/097-multi-device-refresh-sessions.html). - **Where it's used**: broadly, and always instead of a hand-rolled claim lookup. @@ -3529,19 +3971,19 @@ live in later groups; this chapter is the engine those endpoints call into. (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Context/ClaimBasedUserIdProvider.cs:15`); the [IdempotencyFilter](group-12-api-hosting-mapping.md#idempotencyfilter) uses it to scope an idempotency key to a caller - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:487`); + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:541`); [CurrentUserTargetingContextAccessor](group-12-api-hosting-mapping.md#currentusertargetingcontextaccessor) uses it for feature-flag targeting (`MMCA.Common/Source/Presentation/MMCA.Common.API/FeatureManagement/CurrentUserTargetingContextAccessor.cs:17,86`); the opt-in "UserPolicy" rate-limit partition keys on `httpContext.User?.Identity?.Name` directly rather than on `GetUserId()` - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:171-175`); + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:172-177`); [AuthControllerBase](group-12-api-hosting-mapping.md#authcontrollerbase) uses `FindSessionId()` to tell the session list which row is the caller's own (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/AuthControllerBase.cs:187`); [AuthenticationServiceBase](#authenticationservicebasetuser) uses `GetUserId()` on the principal recovered from an expired access token during rotation - (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:355-358`); and + (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:304-307`); and [TestPrincipal](group-28-testing-infrastructure.md#testprincipal) writes `sub` precisely so test principals resolve the same way real ones do (`MMCA.Common/Source/Hosting/MMCA.Common.Testing.UI/Infrastructure/TestPrincipal.cs:19,27`); @@ -3907,7 +4349,7 @@ live in later groups; this chapter is the engine those endpoints call into. posted by [AuthUIService](group-15-common-ui-framework.md#authuiservice)'s `RequestPasswordResetAsync`, deliberately over a bearer-free client so a signed-in caller does not bind the reset to the current session - (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/AuthUIService.cs:204,212`). + (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/AuthUIService.cs:198,206`). - **Caveats / not-in-source**: both applications now wire this vertical. ADC has a [ForgotPasswordCommand](group-24-identity-module.md#forgotpasswordcommand) (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ForgotPassword/ForgotPasswordCommand.cs:12-13`) @@ -3944,7 +4386,7 @@ live in later groups; this chapter is the engine those endpoints call into. of the credential was wrong (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Validation/LoginRequestValidator.cs:6-10,15-20`); then handled by [AuthenticationServiceBase](#authenticationservicebasetuser)`.LoginAsync` - (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:156-157`), which + (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:105-106`), which is reached through [AuthControllerBase](group-12-api-hosting-mapping.md#authcontrollerbase)'s `POST login` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/AuthControllerBase.cs:69,76-77`). @@ -3973,10 +4415,10 @@ live in later groups; this chapter is the engine those endpoints call into. putting it in a URL acceptable. [OAuthControllerBase](group-12-api-hosting-mapping.md#oauthcontrollerbase)`.ExchangeAsync` rejects a blank code (`OAuthControllerBase.cs:185-188`), looks the code up in - [ICacheService](group-09-caching.md#icacheservice) (`OAuthControllerBase.cs:190-198`), and then - removes it so a replayed code cannot mint a second token pair (`OAuthControllerBase.cs:200-201`); an + [ICacheService](group-09-caching.md#icacheservice) (`OAuthControllerBase.cs:190-200`), and then + removes it so a replayed code cannot mint a second token pair (`OAuthControllerBase.cs:202-203`); an unknown, burned, or expired code all return the same HTTP 400 with a deliberately non-specific - message (`OAuthControllerBase.cs:206-209`). The action is also marked `[NonIdempotent]` with the + message (`OAuthControllerBase.cs:208-211`). The action is also marked `[NonIdempotent]` with the reason inline: replaying a stored response would defeat the burn and let a leaked code mint the same tokens again (`OAuthControllerBase.cs:178`). - **Where it's used**: the body of the OAuth `exchange` endpoint @@ -4003,9 +4445,9 @@ live in later groups; this chapter is the engine those endpoints call into. - **Where it's used**: shape-validated by [RefreshTokenRequestValidator](#refreshtokenrequestvalidator), handled by [AuthenticationServiceBase](#authenticationservicebasetuser)`.RefreshTokenAsync` - (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:334-335`), which + (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:283-284`), which rejects an unreadable token or a principal with no usable user id with an `Auth.InvalidToken` - failure before it ever looks at the refresh token (`AuthenticationServiceBase.cs:351-352,358-362`); + failure before it ever looks at the refresh token (`AuthenticationServiceBase.cs:300-301,307-311`); exposed by [AuthControllerBase](group-12-api-hosting-mapping.md#authcontrollerbase)'s `POST refresh` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/AuthControllerBase.cs:117,122-123`). @@ -4053,7 +4495,7 @@ live in later groups; this chapter is the engine those endpoints call into. the account's failed-attempt count so a user who reset *because* of a lockout is not still locked out (`ResetPasswordHandlerBase.cs:94-95,101,109-110`); posted by [AuthUIService](group-15-common-ui-framework.md#authuiservice)'s `ResetPasswordAsync` - (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/AuthUIService.cs:221,231`). ADC + (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/AuthUIService.cs:215,225`). ADC carries it in a [ResetPasswordCommand](group-24-identity-module.md#resetpasswordcommand) marked `ICacheInvalidating` (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ResetPassword/ResetPasswordCommand.cs:15-16`); @@ -4194,14 +4636,14 @@ live in later groups; this chapter is the engine those endpoints call into. while [`AuthenticationServiceBase`](#authenticationservicebasetuser) hands the whole request to an abstract `CreateUser(RegisterRequest request, byte[] passwordHash, byte[] passwordSalt)` that each app implements against its own `User` aggregate - (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:594`). Note + (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:447`). Note that `Password` is a plain `string` on the contract and never travels past the hashing call: `RegisterAsync` turns it into a hash and salt pair, and that pair, not the password, is what - reaches `CreateUser` and the aggregate (`AuthenticationServiceBase.cs:280-281`). + reaches `CreateUser` and the aggregate (`AuthenticationServiceBase.cs:229-230`). `[Rubric §11, Security]`. - **Where it's used**: [`AuthenticationServiceBase.RegisterAsync`](#authenticationservicebasetuser) - (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:254-255`), the + (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:203-204`), the `register` endpoint on [`AuthControllerBase`](group-12-api-hosting-mapping.md#authcontrollerbase), which binds it `[FromBody]` on an anonymous, rate-limited, idempotent POST (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/AuthControllerBase.cs:93-102`), and @@ -4210,7 +4652,7 @@ live in later groups; this chapter is the engine those endpoints call into. (`MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Application/Users/AuthenticationService.cs:52-60`), while ADC ignores it entirely and creates the user from email, names, hash, salt, and the default `UserRole.Attendee` - (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:143-150`). + (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:139-146`). ### AuthenticationResponse > MMCA.Common.Shared · `MMCA.Common.Shared.Auth.Responses` · `MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/Responses/AuthenticationResponse.cs:10` · Level 0 · record struct (readonly) @@ -4235,12 +4677,12 @@ live in later groups; this chapter is the engine those endpoints call into. [OAuthControllerBase](group-12-api-hosting-mapping.md#oauthcontrollerbase) therefore detects a missing exchange entry by testing `string.IsNullOrEmpty(response.AccessToken)`, with the reason written down at the call site - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/OAuthControllerBase.cs:192-195`). + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/OAuthControllerBase.cs:192-197`). - **Where it's used**: produced by [AuthenticationServiceBase](#authenticationservicebasetuser) from the shared `IssueTokensAsync` helper that login and registration both funnel through (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:183,263,474,494`) - and directly at the end of a rotation (`AuthenticationServiceBase.cs:329`); declared as the 200/201 + and directly at the end of a rotation (`AuthenticationServiceBase.cs:278`); declared as the 200/201 response type on the three [AuthControllerBase](group-12-api-hosting-mapping.md#authcontrollerbase) token endpoints (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/AuthControllerBase.cs:73,97,120`); @@ -4383,7 +4825,7 @@ live in later groups; this chapter is the engine those endpoints call into. [`EntityTypeConfiguration`](group-07-persistence-ef-core.md#entitytypeconfigurationtentity-tidentifiertype) uses for the SQL Server schema name (falling back to `dbo`, `.../Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:66`) and the Cosmos container - name (`EntityTypeConfiguration.cs:87`), and what + name (`.../DataSources/Engines/CosmosDataSourceEngine.cs:95`), and what [`EntityDataSourceRegistry`](group-07-persistence-ef-core.md#entitydatasourceregistry) uses to derive a logical database name when no `[UseDatabase]` attribute overrides it (`.../DataSources/EntityDataSourceRegistry.cs:181`). @@ -4433,14 +4875,14 @@ live in later groups; this chapter is the engine those endpoints call into. Security]` assesses what a response is allowed to expose, and `[Rubric §9, API & Contract Design]` assesses whether a contract carries exactly the fields its consumers need. The entity behind this row holds the material the refresh-token reuse check runs on: a `TokenHash` - (`MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:64`) and the rotation link - `ReplacedByTokenHash` (`RefreshSession.cs:79`). Both are deliberately **absent** from this response, + (`MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:72`) and the rotation link + `ReplacedByTokenHash` (`RefreshSession.cs:87`). Both are deliberately **absent** from this response, and the doc comment states the reasoning (`RefreshSessionSummaryResponse.cs:6-11`): shipping either would hand every caller a queryable index of another session's credentials-at-rest for no gain, since nothing a client does with a session needs anything but its id. The rule is enforced by a reflection assertion rather than by review habit: a test asserts the type's property names contain neither `TokenHash` nor `ReplacedByTokenHash` - (`MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Auth/RefreshSessionManagementTests.cs:200-203`). + (`MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Auth/RefreshSessionManagementTests.cs:201-204`). This is the read-model half of the "never project a secret" discipline; the same instinct is what keeps password hashes out of every user DTO. - **Walkthrough**: six positional parameters on a `sealed record` @@ -4468,15 +4910,15 @@ live in later groups; this chapter is the engine those endpoints call into. [AuthenticationServiceBase](#authenticationservicebasetuser)`.GetSessionsAsync`, which reads unrevoked sessions from [IRefreshSessionStore](#irefreshsessionstore), filters to those active at the current instant, orders newest-first, and projects each into this record - (`AuthenticationServiceBase.cs:481-504`); returned by the `GET my-sessions` endpoint on + (`AuthenticationServiceBase.cs:386-393`); returned by the `GET my-sessions` endpoint on [AuthControllerBase](group-12-api-hosting-mapping.md#authcontrollerbase), which supplies the caller's own session via `User.FindSessionId()` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/AuthControllerBase.cs:175-187`); fetched client-side by [AuthUIService](group-15-common-ui-framework.md#authuiservice)`.GetSessionsAsync` - (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/AuthUIService.cs:239,246`) and + (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/AuthUIService.cs:233,240`) and rendered by the [Sessions](group-15-common-ui-framework.md#sessions) page, which uses `IsCurrent` to disable the revoke action on the caller's own row - (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Sessions.razor.cs:38,108-110,179`). + (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Sessions.razor.cs:39,110-112,216`). - **Caveats / not-in-source**: `IpAddress` and `UserAgent` are whatever the client sent at issue time and are stored verbatim; nothing in this type or the projection validates, geolocates, or canonicalizes them, so a spoofed user-agent shows up as-is in the device list. @@ -4515,13 +4957,13 @@ live in later groups; this chapter is the engine those endpoints call into. because releasing a lock held in a remote store is I/O. - **Where it's used**: every caller of `AcquireAsync`, always inside a `using`: [`MemoryCacheService`](group-09-caching.md#memorycacheservice) at - `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:101,112,132`, + `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:111,122,142`, [`CookieSessionRefresher`](#cookiesessionrefresher) at - `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:103`, + `MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:153`, [`IdempotencyFilter`](group-12-api-hosting-mapping.md#idempotencyfilter) at - `MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:206`, and the + `MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:207`, and the [`ICacheService`](group-09-caching.md#icacheservice) `GetOrCreateAsync` default implementation at - `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:112`. + `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:159`. [`CachingQueryDecorator`](group-05-cqrs-pipeline.md#cachingquerydecoratortquery-tresult) is the one caller that names the type explicitly: its `TryAcquirePopulateLockAsync` returns `KeyedSemaphoreStripe.Releaser?` @@ -4800,7 +5242,7 @@ live in later groups; this chapter is the engine those endpoints call into. - `Width` is a get-only property (`:50`), exposed so tests can reason about collisions; one test computes the exact stripe index a key lands on, precisely so it "cannot flake on the one-in-`DefaultWidth` collision" - (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs:270,291`). + (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs:496,517`). - `AcquireAsync` (`:60`) resolves the stripe, awaits `WaitAsync(cancellationToken)` with `ConfigureAwait(false)` per [ADR-049](https://ivanball.github.io/docs/adr/049-library-configureawait-policy.html) (`:63`), and @@ -4821,18 +5263,18 @@ live in later groups; this chapter is the engine those endpoints call into. process-local lock only serializes duplicates that land on the same replica (`017-request-idempotency.md:91-93`), which is why [`IDistributedLock`](group-05-cqrs-pipeline.md#idistributedlock) is preferred when present - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:34-37`). + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:35-38`). - **Where it's used**: five holders, all `static` or instance fields that live for the lifetime of their owner, matching the remark that instances are "intended to be held in a static field for the process lifetime" and that stripes are never disposed (`KeyedSemaphoreStripe.cs:18-21`): - [`IdempotencyFilter`](group-12-api-hosting-mapping.md#idempotencyfilter) (`IdempotencyFilter.cs:90`), + [`IdempotencyFilter`](group-12-api-hosting-mapping.md#idempotencyfilter) (`IdempotencyFilter.cs:91`), [`CookieSessionRefresher`](#cookiesessionrefresher) - (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:63`), + (`MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:87`), [`MemoryCacheService`](group-09-caching.md#memorycacheservice) - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:38`), the + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:45`), the `CacheKeyLocks` holder behind [`ICacheService`](group-09-caching.md#icacheservice)'s `GetOrCreateAsync` default implementation - (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:142-145`), and the + (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:189-192`), and the `QueryCacheKeyLocks` holder behind [`CachingQueryDecorator`](group-05-cqrs-pipeline.md#cachingquerydecoratortquery-tresult) (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/CachingQueryDecorator.cs:247-250`). @@ -4948,9 +5390,9 @@ live in later groups; this chapter is the engine those endpoints call into. the error decision to the caller keeps the parser free of HTTP status opinions. - **Where it's used**: server side, [`EntityControllerBase`](group-12-api-hosting-mapping.md#entitycontrollerbasetentity-tentitydto-tidentifiertype)`.SetConcurrencyETag` - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:405`) writes + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:417`) writes `Response.Headers[ConcurrencyETag.ETagHeaderName] = ConcurrencyETag.Format(rowVersion)` - (`EntityControllerBase.cs:413`) after a successful by-id read (`EntityControllerBase.cs:370`), and + (`EntityControllerBase.cs:425`) after a successful by-id read (`EntityControllerBase.cs:382`), and the CRUD base does the same after a create (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/CrudEntityControllerBase.cs:112`); [`SupportsIfMatchAttribute`](group-12-api-hosting-mapping.md#supportsifmatchattribute) reads the @@ -4960,7 +5402,7 @@ live in later groups; this chapter is the engine those endpoints call into. [`EntityServiceBase`](group-15-common-ui-framework.md#entityservicebasetentitydto-tidentifiertype) formats the tag (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:199`) and attaches it - as `If-Match` (`EntityServiceBase.cs:394`), as do ADC's + as `If-Match` (`EntityServiceBase.cs:398`), as do ADC's [`EventService`](group-21-conference-ui.md#eventservice) (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Events/EventService.cs:29,41`), [`SessionQuestionUIService`](group-22-engagement-module.md#sessionquestionuiservice) @@ -5007,16 +5449,16 @@ live in later groups; this chapter is the engine those endpoints call into. - **Where it's used**: server side, [`IdempotencyFilter`](group-12-api-hosting-mapping.md#idempotencyfilter) re-exports the request header name as a public property - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:73`), reads it in - the one helper both filter stages share (`IdempotencyFilter.cs:165`), and appends the replay header - when it serves a cached response (`IdempotencyFilter.cs:383`); + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:74`), reads it in + the one helper both filter stages share (`IdempotencyFilter.cs:166`), and appends the replay header + when it serves a cached response (`IdempotencyFilter.cs:384`); [`NotificationsController`](group-10-notifications.md#notificationscontroller) reads the same request header directly (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Notifications/NotificationsController.cs:62`). Client side, [`EntityServiceBase`](group-15-common-ui-framework.md#entityservicebasetentitydto-tidentifiertype) attaches a generated key on retried writes - (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:386`), as do ADC's + (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:390`), as do ADC's [`SessionQuestionUIService`](group-22-engagement-module.md#sessionquestionuiservice) (`MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Services/SessionLive/SessionQuestionUIService.cs:73`) and [`LivePollUIService`](group-22-engagement-module.md#livepolluiservice) @@ -5029,7 +5471,7 @@ live in later groups; this chapter is the engine those endpoints call into. - **What it is**: four `const string` message-broker header names carried on every outbound message: the tenant the publishing scope was resolved to, the publishing user's identifier, that user's roles as a comma-separated list, and the correlation id of the interaction that produced the - message (`MessageHeaders.cs:25-38`). + message (`MessageHeaders.cs:25-34`). - **Depends on**: nothing; BCL-free constants only. - **Concept introduced, the cross-layer wire literal for a broker envelope.** Same placement rule [`ConcurrencyETag`](#concurrencyetag) and [`IdempotencyHeaders`](#idempotencyheaders) follow for an @@ -5095,7 +5537,8 @@ live in later groups; this chapter is the engine those endpoints call into. [ADR-115](https://ivanball.github.io/docs/adr/115-strongly-typed-identifiers-opt-in.html) is the governing record for the opt-in strongly typed identifier feature this class supports. - **Where it's used**: exclusively by [`StronglyTypedId`](#stronglytypedid).`TryParse` - (`StronglyTypedId.cs:258-259`); nothing outside the defining file references it directly. + (`StronglyTypedId.cs:67`) and `CanParseValues` (`StronglyTypedId.cs:87`); nothing outside the + defining file references it directly. ### IStronglyTypedId @@ -5197,7 +5640,7 @@ live in later groups; this chapter is the engine those endpoints call into. body (or a non-JSON body, or no body at all) and turns it back into the [`Error`](group-01-result-error-handling.md#error) list and failed [`Result`](group-01-result-error-handling.md#result) the server started from - (`MMCA.Common/Source/Core/MMCA.Common.Shared/Http/ProblemDetailsResultReader.cs:58-476`). + (`MMCA.Common/Source/Core/MMCA.Common.Shared/Http/ProblemDetailsResultReader.cs:58-478`). - **Depends on**: [`Error`](group-01-result-error-handling.md#error), [`Result`](group-01-result-error-handling.md#result) and [`ErrorType`](group-01-result-error-handling.md#errortype) from `MMCA.Common.Shared.Abstractions` @@ -5246,36 +5689,41 @@ live in later groups; this chapter is the engine those endpoints call into. `ReadValidationDictionary` (`:190-192`). Parsed errors win only when the list is non-empty (`:195-198`); otherwise it falls through to a synthesized error carrying `detail` or `title` (`:201`). - - `ResolveStatus` (`:419`) is a small but deliberate affordance: a caller that passes a non-positive - status gets the status read out of the body's own `status` member instead (`:426-430`), which is what + - `ResolveStatus` (`:421`) is a small but deliberate affordance: a caller that passes a non-positive + status gets the status read out of the body's own `status` member instead (`:428-432`), which is what makes the parser usable against a captured payload with no response object around it. - - `ReadErrorArray` (`:319`) maps each object element through `ReadErrorObject` and, notably, still - salvages a degraded array of plain strings (`:331-336`). `ReadErrorObject` (`:342`) reads `code`, + - `ReadErrorArray` (`:321`) maps each object element through `ReadErrorObject` and, notably, still + salvages a degraded array of plain strings (`:333-338`). `ReadErrorObject` (`:344`) reads `code`, `message`, `type`, `source` and `target`, with a three-step fallback for the message (`message`, then `code`, then the generic default) so an `Error` is never constructed with an empty - one (`:348-353`). - - `ReadValidationDictionary` (`:356`) handles the standard ASP.NET Core shape. The key becomes + one (`:350-355`). + - `ReadValidationDictionary` (`:358`) handles the standard ASP.NET Core shape. The key becomes `Validation.{propertyName}`, or bare `Validation` for an object-level rule with an empty key - (`:363-365`), and the property name is carried separately as `Target` (`:366`). A value may be an - array of messages or a single one, and both paths funnel through `AddValidationError` (`:368-378`), - which silently ignores non-string and blank entries (`:391-400`). - - `ParseErrorType` (`:403`) is the one place an inbound string becomes an enum, and it is + (`:365-367`), and the property name is carried separately as `Target` (`:368`). A value may be an + array of messages or a single one, and both paths funnel through `AddValidationError` (`:370-380`), + which silently ignores non-string and blank entries (`:393-402`). + - `ParseErrorType` (`:405`) is the one place an inbound string becomes an enum, and it is defensive in the right way: `Enum.TryParse` with `ignoreCase: true` **plus** `Enum.IsDefined` - (`:405-406`). Without the second check `TryParse` would happily accept an arbitrary numeric string and + (`:407-408`). Without the second check `TryParse` would happily accept an arbitrary numeric string and hand back an undefined enum value. - - `ToFailureResult` (`:212`) lifts the parsed errors into a failed `Result`. `ReadAsync` (`:223`) - short-circuits on a 2xx to `Result.Success()` (`:229-232`) and otherwise parses the body. - `ReadAsync` (`:257`) is the value-returning overload: a non-success status parses errors (`:269`), - a blank 2xx body is a *failure* coded `EmptyResponseCode` (`:272-279`), a body that deserializes to - `null` is the same failure with a different message (`:284-289`), and a `JsonException` becomes - `MalformedResponseCode` (`:292-295`). The "204 is a failure here" rule is stated in the doc with its + - `ToFailureResult` (`:212`) lifts the parsed errors into a failed `Result`. `ReadAsync` (`:224`) + short-circuits on a 2xx to `Result.Success()` (`:230-233`) and otherwise parses the body. + `ReadAsync` (`:259`) is the value-returning overload: a non-success status parses errors (`:271`), + a blank 2xx body is a *failure* coded `EmptyResponseCode` (`:274-281`), a body that deserializes to + `null` is the same failure with a different message (`:286-292`), and a `JsonException` becomes + `MalformedResponseCode` (`:294-297`). The "204 is a failure here" rule is stated in the doc with its escape hatch: use the non-generic overload for endpoints that legitimately answer without a body - (`:241-246`). - - `ReadBodyAsync` (`:298`) buffers the content as a string and swallows an `HttpRequestException` back - to `null` (`:311-316`), with the comment explaining the judgement: a truncated body should still + (`:242-247`). + - Both `ReadAsync` overloads carry a `SuppressMessage` for analyzer `RS0026` ("Do not add multiple + public overloads with optional parameters", `:223,258`). The justification records them as + grandfathered: released after the v1.152 public-API baseline while RS0026/RS0027 were off, so + reshaping either signature now would be a breaking change. The two-overload shape is therefore + frozen by the public API contract, not by preference. + - `ReadBodyAsync` (`:300`) buffers the content as a string and swallows an `HttpRequestException` back + to `null` (`:313-318`), with the comment explaining the judgement: a truncated body should still report the status-level failure rather than surface a transport exception "from a reader". - - `TryGetProperty` (`:444`) does case-insensitive member lookup, trying the exact name first and only - then enumerating (`:453-459`). The doc gives the reason (`:438-443`): the wire form is camelCase, but + - `TryGetProperty` (`:446`) does case-insensitive member lookup, trying the exact name first and only + then enumerating (`:455-461`). The doc gives the reason (`:440-445`): the wire form is camelCase, but a hand-assembled or differently-configured payload can be PascalCase, and a reader that understood only one "would silently drop every error field". - **Why it's built this way**: @@ -5288,7 +5736,7 @@ live in later groups; this chapter is the engine those endpoints call into. - **Where it's used**: it is the single funnel for every framework-shaped HTTP read on the client. [`EntityServiceBase`](group-15-common-ui-framework.md#entityservicebasetentitydto-tidentifiertype) uses both overloads - (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:339,367`, documented at + (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:343,371`, documented at `:304,321,347`), [`ChildEntityServiceBase`](group-15-common-ui-framework.md#childentityservicebase) uses all three call shapes @@ -5417,21 +5865,29 @@ live in later groups; this chapter is the engine those endpoints call into. - **What it is**: the registration surface for [`StronglyTypedIdTypeConverter`](#stronglytypedidtypeconvertertself-tvalue): register one identifier type explicitly, or scan an assembly and register every identifier type it declares - (`StronglyTypedIdTypeConverter.cs:82-155`). + (`StronglyTypedIdTypeConverter.cs:82-166`). - **Depends on**: [`StronglyTypedId`](#stronglytypedid).`GetValueType`/`IsStronglyTypedId` for detection; [`StronglyTypedIdTypeConverter`](#stronglytypedidtypeconvertertself-tvalue), attached via `TypeDescriptor.AddAttributes`. - **Concept introduced**: none new; the assembly-scan companion to the per-type converter. -- **Walkthrough**: `Register(Type)` (`:600`) resolves the wrapped value type or throws - `ArgumentException` naming the offending type (`:604-607`), builds the closed +- **Walkthrough**: `Register(Type)` (`:89`) resolves the wrapped value type or throws + `ArgumentException` naming the offending type (`:93-96`), builds the closed `StronglyTypedIdTypeConverter<,>` and attaches it with - `TypeDescriptor.AddAttributes(identifierType, new TypeConverterAttribute(converterType))` (`:611`). - `RegisterAll(params Assembly[])` (`:619`) calls `Register` for every candidate `GetIdentifierTypes` - yields and returns the count. `GetIdentifierTypes(Assembly)` (`:643`) tolerates a partially loadable + `TypeDescriptor.AddAttributes(identifierType, new TypeConverterAttribute(converterType))` (`:100`). + For a value-type identifier it then calls `TypeDescriptor.Refresh` on the closed `Nullable` + (`:108-111`). The comment gives the failure this prevents (`:102-107`): MVC binds an optional + `TId?` query-string parameter through `Nullable`'s converter, and `TypeDescriptor` caches that + `NullableConverter` with whatever underlying converter it saw first. If anything resolved it before + registration (another host in the process, a library scanning types), the cached converter still + wraps the default struct converter, which cannot read a string, so MVC treats `TId?` as a complex + type and the request answers 500. Refreshing drops the cache so the next lookup sees the converter + just registered. + `RegisterAll(params Assembly[])` (`:119`) calls `Register` for every candidate `GetIdentifierTypes` + yields and returns the count. `GetIdentifierTypes(Assembly)` (`:143`) tolerates a partially loadable assembly: it catches `ReflectionTypeLoadException` and falls back to the partial `Types` array - (`:649-656`), the doc comment noting this matches the tolerance the architecture map's + (`:149-156`), the doc comment noting this matches the tolerance the architecture map's loadable-types helper applies elsewhere, then filters to non-generic value types satisfying - `IsStronglyTypedId` (`:658-664`). + `IsStronglyTypedId` (`:158-164`). - **Why it's built this way**: [ADR-115](https://ivanball.github.io/docs/adr/115-strongly-typed-identifiers-opt-in.html). - **Where it's used**: called from `MMCA.Common.Infrastructure`'s `DependencyInjection` @@ -5698,8 +6154,8 @@ live in later groups; this chapter is the engine those endpoints call into. - **Where it's used**: the framework side is [`DataExportControllerBase`](group-12-api-hosting-mapping.md#dataexportcontrollerbasetquery), which carries `[FeatureGate(PrivacyFeatures.DataExport)]` on the class - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:58`) - with the rationale in the same file's remarks (`DataExportControllerBase.cs:52-53`). Both apps + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:60`) + with the rationale in the same file's remarks (`DataExportControllerBase.cs:54-55`). Both apps subclass that base with a thin, route-only controller: [`UsersDataExportController`](group-24-identity-module.md#usersdataexportcontroller) (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersDataExportController.cs:26-35`, @@ -5745,59 +6201,6 @@ live in later groups; this chapter is the engine those endpoints call into. [`AuthenticationValidators`](#authenticationvalidators) (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationValidators.cs:19,28`). -### ResetPasswordRequestValidator - -> MMCA.Common.Application · `MMCA.Common.Application.Auth.Validation` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Validation/ResetPasswordRequestValidator.cs:12` · Level 1 · class - -- **What it is**: the validator for [`ResetPasswordRequest`](#resetpasswordrequest): address shape on - `Email`, presence on `Token`, and the shared strong-password policy on `NewPassword` - (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Validation/ResetPasswordRequestValidator.cs:16-23`). -- **Depends on**: [`ResetPasswordRequest`](#resetpasswordrequest); - [`StrongPasswordRules`](group-06-validation.md#requiredstringrulest-optionalstringrulest-emailrulest-positiveintrulest-positivedecimalrulest-nonnegativeintrulest-requiredidrulest-tid-optionalpositiveidrulest-tid-passwordrulest-strongpasswordrulest); `FluentValidation`'s - `AbstractValidator` and its `Include` composition. -- **Concept introduced, composing a rule set with `Include`.** `[Rubric §11, Security]` assesses whether - a policy holds on every path that can change the guarded value, and `[Rubric §1, SOLID]` the - single-responsibility split that makes that possible. A password-complexity policy is only a policy if - *every* write path enforces it; if registration demands an uppercase letter and reset does not, reset - is a documented downgrade route. FluentValidation's `Include` merges another validator's rules for the - same model type into this one, so the policy can live in exactly one class and be pulled into each - writer. The doc comment states the intent: the new password goes through "the same - `StrongPasswordRules` the registration and change-password requests use, so a reset cannot be a way - around the complexity policy" (`ResetPasswordRequestValidator.cs:8-10`). `StrongPasswordRules` is - generic over the containing model and takes a selector expression, which is what lets one rule set - attach to a differently-shaped request each time - (`MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:188-199`): it - enforces non-empty, 8 to 128 characters, and one each of uppercase, lowercase, digit, and - non-alphanumeric. -- **Walkthrough**: three statements in a block-bodied constructor - (`ResetPasswordRequestValidator.cs:14-24`). `Email` gets `NotEmpty().EmailAddress()` (`:16-18`), - matching the forgot-password half so the two steps agree on what an address is. `Token` gets - `NotEmpty()` with a reset-specific message (`:20-21`); no format check, because the token's validity is - a lookup, not a shape. Then - `Include(new StrongPasswordRules(x => x.NewPassword))` (`:23`) grafts the seven - policy rules onto the `NewPassword` field. Note the contrast with the weaker sibling - [`PasswordRules`](group-06-validation.md#requiredstringrulest-optionalstringrulest-emailrulest-positiveintrulest-positivedecimalrulest-nonnegativeintrulest-requiredidrulest-tid-optionalpositiveidrulest-tid-passwordrulest-strongpasswordrulest) (`CommonValidationRules.cs:174-181`), which - enforces length only; reset deliberately takes the strong one. -- **Why it's built this way**: the reset flow is - [ADR-091](https://ivanball.github.io/docs/adr/091-cache-backed-password-reset.html), and the hashing - the accepted password ends up under is - [ADR-102](https://ivanball.github.io/docs/adr/102-pbkdf2-only-password-hashing.html) (which supersedes - ADR-032). Neither is this validator's concern, which is the point: it only decides whether the - candidate is policy-compliant. -- **Where it's used**: registered by the assembly scan - (`MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:48`) and reached through - [`CommandRequestValidator`](group-06-validation.md#commandrequestvalidatortcommand-trequest) - for any command implementing `ICommandWithRequest`, the constraint - [`ResetPasswordHandlerBase`](group-14-module-system-composition.md#resetpasswordhandlerbasetuser-tcommand) - declares - (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:50`). - The request arrives at - [`PasswordResetAuthControllerBase`](group-12-api-hosting-mapping.md#passwordresetauthcontrollerbasetforgotpasswordcommand-tresetpasswordcommand) - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/PasswordResetAuthControllerBase.cs:108`), - which is why a policy failure surfaces as the documented `400` - (`PasswordResetAuthControllerBase.cs:104`) while a bad token collapses to `401` - (`PasswordResetAuthControllerBase.cs:97,105`). - ### SendEmailConfirmationRequestValidator > MMCA.Common.Application · `MMCA.Common.Application.Auth.Validation` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Validation/EmailConfirmationRequestValidators.cs:11` · Level 1 · class @@ -5872,7 +6275,7 @@ live in later groups; this chapter is the engine those endpoints call into. the degradation path logs the exception but hands the subject a generic reason (`ExportUserDataHandlerBase.cs:188-196`); and the controller serializes to bytes and returns a file rather than an `ObjectResult` - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:101-109`). + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:103-113`). `[Rubric §9, API & Contract Design]`: `FormatVersion` versions "the export document shape itself (not the app's data)" (`UserDataExportDTO.cs:18-19`), so a consumer parsing an old file can detect an envelope change rather than guess at it. @@ -5903,9 +6306,9 @@ live in later groups; this chapter is the engine those endpoints call into. `GeneratedOn` from an injected `TimeProvider` rather than a static clock (`ExportUserDataHandlerBase.cs:113`). [`DataExportControllerBase`](group-12-api-hosting-mapping.md#dataexportcontrollerbasetquery) - declares it as the 200 response type (`DataExportControllerBase.cs:78`) and derives the download file + declares it as the 200 response type (`DataExportControllerBase.cs:80`) and derives the download file name from the package's own `GeneratedOn` so the file name and the document can never disagree - (`DataExportControllerBase.cs:109,124-134`). Both apps subclass the handler + (`DataExportControllerBase.cs:113,128-138`). Both apps subclass the handler (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ExportUserData/ExportUserDataHandler.cs:35`, `MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Application/Users/UseCases/ExportUserData/ExportUserDataHandler.cs:39`) and expose it through their own @@ -5913,13 +6316,66 @@ live in later groups; this chapter is the engine those endpoints call into. (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersDataExportController.cs:27`, `MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.API/Controllers/UsersDataExportController.cs:29`). +### ResetPasswordRequestValidator + +> MMCA.Common.Application · `MMCA.Common.Application.Auth.Validation` · `MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Validation/ResetPasswordRequestValidator.cs:12` · Level 1 · class + +- **What it is**: the validator for [`ResetPasswordRequest`](#resetpasswordrequest): address shape on + `Email`, presence on `Token`, and the shared strong-password policy on `NewPassword` + (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Validation/ResetPasswordRequestValidator.cs:16-23`). +- **Depends on**: [`ResetPasswordRequest`](#resetpasswordrequest); + [`StrongPasswordRules`](group-06-validation.md#requiredstringrulest-optionalstringrulest-emailrulest-positiveintrulest-positivedecimalrulest-nonnegativeintrulest-requiredidrulest-tid-optionalpositiveidrulest-tid-passwordrulest-strongpasswordrulest); `FluentValidation`'s + `AbstractValidator` and its `Include` composition. +- **Concept introduced, composing a rule set with `Include`.** `[Rubric §11, Security]` assesses whether + a policy holds on every path that can change the guarded value, and `[Rubric §1, SOLID]` the + single-responsibility split that makes that possible. A password-complexity policy is only a policy if + *every* write path enforces it; if registration demands an uppercase letter and reset does not, reset + is a documented downgrade route. FluentValidation's `Include` merges another validator's rules for the + same model type into this one, so the policy can live in exactly one class and be pulled into each + writer. The doc comment states the intent: the new password goes through "the same + `StrongPasswordRules` the registration and change-password requests use, so a reset cannot be a way + around the complexity policy" (`ResetPasswordRequestValidator.cs:8-10`). `StrongPasswordRules` is + generic over the containing model and takes a selector expression, which is what lets one rule set + attach to a differently-shaped request each time + (`MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:191-202`): it + enforces non-empty, 8 to 128 characters, and one each of uppercase, lowercase, digit, and + non-alphanumeric. +- **Walkthrough**: three statements in a block-bodied constructor + (`ResetPasswordRequestValidator.cs:14-24`). `Email` gets `NotEmpty().EmailAddress()` (`:16-18`), + matching the forgot-password half so the two steps agree on what an address is. `Token` gets + `NotEmpty()` with a reset-specific message (`:20-21`); no format check, because the token's validity is + a lookup, not a shape. Then + `Include(new StrongPasswordRules(x => x.NewPassword))` (`:23`) grafts the seven + policy rules onto the `NewPassword` field. Note the contrast with the weaker sibling + [`PasswordRules`](group-06-validation.md#requiredstringrulest-optionalstringrulest-emailrulest-positiveintrulest-positivedecimalrulest-nonnegativeintrulest-requiredidrulest-tid-optionalpositiveidrulest-tid-passwordrulest-strongpasswordrulest) (`CommonValidationRules.cs:175-182`), which + enforces length only; reset deliberately takes the strong one. +- **Why it's built this way**: the reset flow is + [ADR-091](https://ivanball.github.io/docs/adr/091-cache-backed-password-reset.html), and the hashing + the accepted password ends up under is + [ADR-102](https://ivanball.github.io/docs/adr/102-pbkdf2-only-password-hashing.html) (which supersedes + ADR-032). Neither is this validator's concern, which is the point: it only decides whether the + candidate is policy-compliant. +- **Where it's used**: registered by the assembly scan + (`MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:48`) and reached through + [`CommandRequestValidator`](group-06-validation.md#commandrequestvalidatortcommand-trequest) + for any command implementing `ICommandWithRequest`, the constraint + [`ResetPasswordHandlerBase`](group-14-module-system-composition.md#resetpasswordhandlerbasetuser-tcommand) + declares + (`MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:50`). + The request arrives at + [`PasswordResetAuthControllerBase`](group-12-api-hosting-mapping.md#passwordresetauthcontrollerbasetforgotpasswordcommand-tresetpasswordcommand) + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/PasswordResetAuthControllerBase.cs:108`), + which is why a policy failure surfaces as the documented `400` + (`PasswordResetAuthControllerBase.cs:104`) while a bad token collapses to `401` + (`PasswordResetAuthControllerBase.cs:97,105`). + ### TotpTwoFactorService > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Auth.TwoFactor` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TotpTwoFactorService.cs:35` · Level 2 · class - **What it is**: the RFC 6238 TOTP implementation of `ITwoFactorService`: secret generation, provisioning-URI construction, code verification, and recovery-code generation/hashing/matching - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TotpTwoFactorService.cs:35-155`). + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TotpTwoFactorService.cs:35-159`). - **Depends on**: [`ITwoFactorService`](group-08-auth.md#itwofactorservice) (the contract it implements); `TwoFactorSettings` (`MMCA.Common.Shared`) injected as `IOptions`; `RecoveryCodeSet` (`MMCA.Common.Shared`) as its return shape; `OtpDotNet` (`Base32Encoding`, `KeyGeneration`, `Totp`, @@ -5929,46 +6385,54 @@ live in later groups; this chapter is the engine those endpoints call into. of.** `[Rubric §11, Security]` assesses whether the codebase avoids timing side channels around secret comparison. `TryMatchRecoveryCode` never exits its scan early on a match: the loop "runs to the end so the answer takes the same time whichever code in the list was presented" - (`TotpTwoFactorService.cs:136-137`), and each comparison itself goes through - `CryptographicOperations.FixedTimeEquals` rather than `==` or `SequenceEqual` (`:138`). A byte-for-byte + (`TotpTwoFactorService.cs:140-141`), and each comparison itself goes through + `CryptographicOperations.FixedTimeEquals` rather than `==` or `SequenceEqual` (`:142`). A byte-for-byte comparison that stops at the first differing byte leaks how many leading bytes an attacker guessed correctly across repeated attempts; a fixed-time comparison over a full linear scan removes that channel entirely. -- **Walkthrough**: `internal sealed class` constructed with `IOptions settings` - (`:35`), caching `.Value` once (`:37`). +- **Walkthrough**: `internal sealed class` with a primary constructor taking + `IOptions settings` (`:35`), caching `.Value` once (`:37`). - `GenerateSecret()` returns a Base32-encoded random key sized by `_settings.SecretByteLength` - (`:40-41`). Base32 rather than Base64: "the alphabet has no case ambiguity and no characters a user - has to guess at while copying a code off paper" (`:251-252`), the same rationale repeated at the - recovery-code site. - - `BuildProvisioningUri` URL-escapes the issuer and account name and formats the standard `otpauth://` - URI a QR code encodes, with algorithm, digit count, and period read from settings (`:44-56`). - - `VerifyCode` decodes the stored Base32 secret, catching `ArgumentException` and returning `false` - rather than throwing when the stored value is not valid Base32: "a stored secret that is not Base32 - can never mint a code, so it verifies nothing. That is a data fault, not a caller fault" - (`:229-231`). It then builds a `Totp` with the configured step, hash mode, and digit count, and - checks the normalized code against a verification window of `_settings.VerificationWindowSteps` steps - on each side of now (`:215-240`), tolerating clock drift between the server and the user's - authenticator app. + (`:40-41`). + - `BuildProvisioningUri` rejects a blank secret or account name, URL-escapes the issuer and account + name, and formats the standard `otpauth://` URI a QR code encodes: the algorithm is fixed at `SHA1`, + while digit count and period are read from settings (`:44-55`). + - `VerifyCode` comes in two overloads. The two-argument form is a one-line forward to the + three-argument form, discarding the step (`:58`). The three-argument form reports the matched TOTP + time step through `out long matchedStep`, initialized to `0` so every early `false` leaves it + defined (`:61-63`). It returns `false` for a blank secret or code (`:64-67`), then decodes the + stored Base32 secret, catching `ArgumentException` and returning `false` rather than throwing when + the stored value is not valid Base32: "a stored secret that is not Base32 can never mint a code, so + it verifies nothing. That is a data fault, not a caller fault" (`:74-80`). It then builds a `Totp` + with the configured period, SHA-1 hash mode, and digit count (`:82`), and checks the normalized code + against a verification window of `_settings.VerificationWindowSteps` steps on each side of now, + writing the step the code matched into `matchedStep` (`:83-87`). The window tolerates clock drift + between the server and the user's authenticator app; the reported step is what lets + [`TwoFactorAuthenticator`](#twofactorauthenticator) refuse a replay of the same code inside that + window. - `GenerateRecoveryCodes` produces `_settings.RecoveryCodeCount` codes, each a Base32-encoded random - byte string with the `=` padding trimmed, alongside their SHA-256 hashes computed by - `HashRecoveryCode` (`:244-262`). Only the hashes and the caller-facing codes are returned; nothing - persists the plaintext codes here. - - `HashRecoveryCode` normalizes the code (strips non-alphanumerics, upper-cases) then SHA-256-hashes it - (`:265-270`). - - `TryMatchRecoveryCode` normalizes and hashes the presented code once, then fixed-time-compares it - against every stored hash, returning the matched hash (not the code) through an `out` parameter - (`:273-302`). + byte string of `_settings.RecoveryCodeByteLength` bytes with the `=` padding trimmed, alongside + their SHA-256 hashes computed by `HashRecoveryCode` (`:91-109`). Base32 rather than Base64: "the + alphabet has no case ambiguity and no characters a user has to guess at while copying a code off + paper" (`:98-99`). Only the hashes and the caller-facing codes are returned as a `RecoveryCodeSet` + (`:108`); nothing persists the plaintext codes here. + - `HashRecoveryCode` normalizes the code (keeps letters and digits, upper-cases) then SHA-256-hashes + it to an upper-case hex string (`:112-117`). + - `TryMatchRecoveryCode` normalizes and hashes the presented code once, skips `null` stored entries, + then fixed-time-compares it against every stored hash, returning the matched hash (not the code) + through an `out` parameter (`:120-149`). - `NormalizeCode` is the shared helper both verification paths route through: it strips whitespace and separators a user types or a password manager inserts and upper-cases what remains, "so a code is - matched the way it was generated" (`:304-311`). + matched the way it was generated" (`:151-158`). - **Why it's built this way**: two-factor authentication is opt-in ([ADR-116](https://ivanball.github.io/docs/adr/116-identity-completions-opt-in.html)), registered by `AddTwoFactorAuthentication(config)`; `MMCA.Common/CLAUDE.md` notes Otp.NET is scoped to Infrastructure only, which is why this type, rather than anything in Application, is what actually calls into the library. - **Where it's used**: registered in DI - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs`) as the `ITwoFactorService` - implementation behind `AddTwoFactorAuthentication(config)`, and consumed by + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:47`, a + `TryAddSingleton`) as the `ITwoFactorService` implementation behind `AddTwoFactorAuthentication(config)` + (`DependencyInjection.Auth.cs:39`), and consumed by [`TwoFactorAuthenticator`](#twofactorauthenticator) for both TOTP and recovery-code verification. - **Caveats / not-in-source**: `internal sealed`, so it is reached only through the `ITwoFactorService` abstraction; a consumer cannot construct or type-check against this class directly. @@ -6032,14 +6496,16 @@ live in later groups; this chapter is the engine those endpoints call into. ### TwoFactorAuthenticator -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Auth.TwoFactor` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs:25` · Level 4 · class +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Auth.TwoFactor` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs:36` · Level 4 · class - **What it is**: the `ITwoFactorAuthenticator` implementation that turns a raw code plus a user's stored two-factor state into one of four challenge outcomes - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs:25-73`). + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs:36-116`). - **Depends on**: [`ITwoFactorService`](group-08-auth.md#itwofactorservice) (code verification and recovery-code matching); [`ITwoFactorStore`](group-08-auth.md#itwofactorstore) (loading state and - consuming a recovery code); [`TwoFactorErrors`](group-08-auth.md#twofactorerrors); + consuming a recovery code); [`ICacheService`](group-09-caching.md#icacheservice) (remembering the + last accepted TOTP time step per account); [`TwoFactorSettings`](group-08-auth.md#twofactorsettings) + via `IOptions` (period and window size); [`TwoFactorErrors`](group-08-auth.md#twofactorerrors); [`TwoFactorOutcome`](group-08-auth.md#twofactoroutcome); the framework's `Result`; the `UserIdentifierType` alias. - **Concept introduced, a challenge is answered with an outcome enum, not a bare boolean.** `[Rubric §1, @@ -6049,30 +6515,45 @@ live in later groups; this chapter is the engine those endpoints call into. `VerifiedRecoveryCode`. Collapsing those five states into one boolean would force the caller to re-derive which one happened from side effects, and a login path that cannot tell "not enrolled" from "denied" cannot decide whether to prompt for enrollment or reject the sign-in. -- **Walkthrough**: `internal sealed class` taking `ITwoFactorService` and `ITwoFactorStore` as primary - constructor parameters (`:25-27`), implementing `ChallengeAsync(userId, code, cancellationToken)`. - - Loads the user's `state` from the store first (`:34`). If the state is missing or +- **Walkthrough**: `internal sealed class` taking `ITwoFactorService`, `ITwoFactorStore`, + `ICacheService`, and `IOptions` as primary constructor parameters (`:36-40`), + implementing `ChallengeAsync(userId, code, cancellationToken)` (`:43-86`). + - Loads the user's `state` from the store first (`:48`). If the state is missing or `IsTwoFactorEnabled` is false, the challenge succeeds trivially with `NotEnrolled`: "an account that never enrolled, and an account whose row vanished between the password check and here, are both - 'nothing to challenge'" (`:36-38`). - - An enrolled account with no presented code fails with `TwoFactorErrors.TwoFactorRequired` (`:44-47`). - - The code is checked first against the stored TOTP secret via `ITwoFactorService.VerifyCode`; a match - returns `VerifiedTotp` (`:49-52`). + 'nothing to challenge'" (`:50-56`). + - An enrolled account with no presented code fails with `TwoFactorErrors.TwoFactorRequired` (`:58-62`). + - The code is checked first against the stored TOTP secret via the `out long matchedStep` overload of + `ITwoFactorService.VerifyCode`; a match is not accepted outright but handed to + `AcceptTimeStepOnceAsync` with the step it matched (`:64-68`). - Failing that, it is checked against the stored recovery-code hashes via `ITwoFactorService.TryMatchRecoveryCode`; no match fails with `TwoFactorErrors.TwoFactorInvalid` - (`:54-59`). + (`:70-75`). - A recovery-code match is then **consumed**, not just verified: `store.ConsumeRecoveryCodeAsync` persists the redemption, and if that persistence fails, the whole challenge is answered as a failure rather than a success: "letting the sign-in through would hand out a code that is still live" - (`:65-69`). Only a successfully persisted consumption returns `VerifiedRecoveryCode`. + (`:77-85`). Only a successfully persisted consumption returns `VerifiedRecoveryCode`. + - The private `AcceptTimeStepOnceAsync` makes a TOTP code single use (`:96-114`). It keys the account + as `twofactor:laststep:{userId}` (`:101`) and reads the last accepted step with + `GetFromSharedStoreAsync`, deliberately bypassing a local copy: "a step accepted on another replica + must be seen here, or a replayed code would pass against a stale local copy inside its window" + (`:103-104`). A matched step not newer than the stored one fails with + `TwoFactorErrors.TwoFactorInvalid` (`:106-109`). Otherwise the step is written back with a lifetime + of `PeriodSeconds * (2 * VerificationWindowSteps + 2)` seconds, long enough to outlast every step the + verification window could still accept, and the challenge returns `VerifiedTotp` (`:111-114`). - **Why it's built this way**: this is the runtime half of the opt-in two-factor feature - ([ADR-116](https://ivanball.github.io/docs/adr/116-identity-completions-opt-in.html)); the recovery - code's one-time-use property is only real if consumption and the outcome it produces are atomic, which - is why the failure path is checked before the outcome is returned rather than after. + ([ADR-116](https://ivanball.github.io/docs/adr/116-identity-completions-opt-in.html)). Both kinds of + code are made one-time inside the challenge rather than left to the caller. A recovery code's + one-time-use property is only real if consumption and the outcome it produces are atomic, which is why + the failure path is checked before the outcome is returned rather than after. A TOTP code would + otherwise stay valid for its whole verification window, so a code seen over someone's shoulder could be + replayed; remembering the last accepted step closes that window + (`TwoFactorAuthenticator.cs` type remarks, `:25-29`). - **Where it's used**: registered in DI - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs`) as the - `ITwoFactorAuthenticator` behind `AddTwoFactorAuthentication(config)`; the resolved instance is passed - to `AuthenticationServiceBase`, which answers `Authentication.TwoFactorRequired` or mints the `mfa` + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:50`, a `TryAddScoped`) + as the `ITwoFactorAuthenticator` behind `AddTwoFactorAuthentication(config)`; the resolved instance is + passed to [`AuthenticationServiceBase`](group-08-auth.md#authenticationservicebasetuser), which + answers `Authentication.TwoFactorRequired` or mints the `mfa` claim depending on the outcome (`MMCA.Common/CLAUDE.md`, "Identity completions" section). ### StoredPermissionRoleAdministrationService @@ -6082,7 +6563,7 @@ live in later groups; this chapter is the engine those endpoints call into. - **What it is**: the `IRoleAdministrationService` implementation that lets an operator list roles, read or replace a role's stored permissions, and view the compiled permission catalog, layered over the code-compiled permission registry - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/Administration/StoredPermissionRoleAdministrationService.cs:45-229`). + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/Administration/StoredPermissionRoleAdministrationService.cs:45-237`). - **Depends on**: [`IPermissionRegistry`](group-08-auth.md#ipermissionregistry) (the compiled grants); [`IPermissionCatalog`](group-08-auth.md#ipermissioncatalog) (the closed list of declared permissions); [`IPermissionGrantStore`](group-08-auth.md#ipermissiongrantstore) (persisted grants); @@ -6102,28 +6583,31 @@ live in later groups; this chapter is the engine those endpoints call into. through a host's own permission registry. - **Walkthrough**: constructed with the registry, catalog, store, cache, invalidator, and settings as primary constructor parameters (`:45-51`). - - `ListRolesAsync` groups all stored grants by role, case-insensitively (`:60-64`), unions the result + - `ListRolesAsync` groups all stored grants by role, case-insensitively (`:59-64`), unions the result with the compiled and configured role universes via the private `RoleUniverse` helper (`:66`), and returns one `RolePermissionsResponse` per role pairing its compiled permissions (`CompiledPermissions`) - with its stored ones (`:68-77`). + with its stored ones (`:68-76`). - `GetCatalogAsync` returns every known role alongside the full compiled `catalog.Permissions` deliberately, not whatever happens to be stored: "widening it with whatever happens to be stored - would let one typo legitimize itself" (`:88-90`). + would let one typo legitimize itself" (`:87-89`). - `GetRoleAsync` treats a role as not found only when it has no stored grants, no compiled permissions, and is named in neither `KnownRoles` nor the catalog's role list, so "a role the host has never named ... does not exist as far as this surface is concerned" rather than reporting it as an empty, - plausible-looking role (`:107-110`). - - `SetStoredPermissionsAsync` trims and de-duplicates the desired set (`:135-137`), rejects + plausible-looking role (`:105-114`). + - `SetStoredPermissionsAsync` trims and de-duplicates the desired set (`:133-135`), rejects `ManageRoles` as above, rejects any permission absent from `catalog.Permissions` with the unknown - names listed in the error (`:151-163`), then diffs the desired set against the currently stored one: + names listed in the error (`:148-160`), then diffs the desired set against the currently stored one: grants what is newly desired, revokes what was dropped, short-circuiting on the first store failure - either way (`:168-187`), and finally invalidates the role's permission cache (`:189`) before - returning the fresh `RolePermissionsResponse`. + either way (`:162-190`). The two loops sit in a `try` whose `finally` invalidates the role's + permission cache on every exit, failure and exception included (`:168-194`): each grant and revoke + "commits on its own, so a failure or a throw part-way through leaves earlier rows already written", + and invalidating only on success would leave the process "serving the pre-edit snapshot until the + next refresh" (`:165-167`). On success it returns the fresh `RolePermissionsResponse` (`:196-198`). - The private `RoleUniverse` helper unions the compiled catalog's roles, the configured `KnownRoles`, - and whatever roles the store's own rows name, case-insensitively and sorted (`:216-224`). + and whatever roles the store's own rows name, case-insensitively and sorted (`:208-215`). - The private `CompiledPermissions` helper reads "through the SAME registry the authorization path uses" and subtracts what the cache already reports, so the two lists a role response carries stay - disjoint (`:238-244,246-251`). + disjoint (`:223-236`). - **Why it's built this way**: [ADR-116](https://ivanball.github.io/docs/adr/116-identity-completions-opt-in.html) documents `AddStoredPermissionGrants(config)` as the opt-in that layers a `PermissionGrant` table over the @@ -6132,8 +6616,9 @@ live in later groups; this chapter is the engine those endpoints call into. [`LayeredPermissionRegistry`](group-08-auth.md#layeredpermissionregistry) "without changing the decorators' contract", the principle this service's `ManageRoles` refusal exists to protect. - **Where it's used**: registered in DI - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs`) behind - `AddStoredPermissionGrants(config)` as the `IRoleAdministrationService` implementation; consumed + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:128`, a + `TryAddScoped`) behind `AddStoredPermissionGrants(config)` (`DependencyInjection.Auth.cs:108`) as the + `IRoleAdministrationService` implementation; consumed through `RolesAdminControllerBase`, which is itself gated on `AdministrationPermissions.ManageRoles` (`MMCA.Common/CLAUDE.md`, "Identity completions" section). - **Caveats / not-in-source**: `internal sealed`, so it is reached only through the @@ -6153,7 +6638,7 @@ live in later groups; this chapter is the engine those endpoints call into. is centralized once. SignalR keys its user-targeted sends on whatever string an `IUserIdProvider` returns; the built-in provider reads `ClaimTypes.NameIdentifier`. This framework mints the user id only into `sub` (see [`TokenService`](#tokenservice), - `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:105`), and whether that + `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:116`), and whether that survives as `sub` or arrives mapped to `NameIdentifier` depends on which handler authenticated the connection. Routing through the shared extension is what makes both shapes resolve identically, so a consumer that changes its inbound claim mapping does not silently start delivering zero @@ -6171,7 +6656,7 @@ live in later groups; this chapter is the engine those endpoints call into. claim value is used verbatim on this side, and the sender formats with `CultureInfo.InvariantCulture` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Notifications/Push/SignalRPushNotificationSender.cs:19`), matching the invariant formatting the token writer used - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:105`). Keeping the whole + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:116`). Keeping the whole provider to one delegating line is what makes that three-way agreement checkable at a glance. - **Where it's used**: registered as `services.TryAddSingleton()` @@ -6202,7 +6687,7 @@ live in later groups; this chapter is the engine those endpoints call into. `[Rubric §12, Performance & Scalability]` explains the `Lazy` fields (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Context/CurrentUserService.cs:19`, `:21`): because the service is registered scoped - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:296`), the claim walk + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:306`), the claim walk happens at most once per request no matter how many handlers, filters and save operations ask. `[Rubric §27, i18n]` covers the trap most codebases miss: claim values are machine-written under `CultureInfo.InvariantCulture`, so they must be *read* invariantly too, or a request running under a @@ -6245,11 +6730,11 @@ live in later groups; this chapter is the engine those endpoints call into. (`MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/ClaimsPrincipalExtensions.cs:9-16`). - **Where it's used**: registered as `services.TryAddScoped()` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:296`). The + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:306`). The highest-traffic consumer is [`DbContextFactory`](group-07-persistence-ef-core.md#dbcontextfactory), which takes it as a constructor dependency - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:50`, + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:52`, `:57`) and passes `UserId` into every save so audit fields are stamped with the acting user (`:248`, `:291`, `:330`, `:352`, `:414`); [`EFRepository`](group-07-persistence-ef-core.md#efrepositorytentity-tidentifiertype) diff --git a/docs-src/onboarding/group-09-caching.md b/docs-src/onboarding/group-09-caching.md index f329216a..c38d0625 100644 --- a/docs-src/onboarding/group-09-caching.md +++ b/docs-src/onboarding/group-09-caching.md @@ -21,26 +21,36 @@ namespace, plus how they plug into that pipeline. (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:10`) is a textbook Clean Architecture port/adapter split (see [primer §1](00-primer.md#1-the-big-picture)): the interface lives in `MMCA.Common.Application`, all three implementations live in `MMCA.Common.Infrastructure`, and -application code compiles against the interface alone. It declares six members. Four are abstract: +application code compiles against the interface alone. It declares eight members. Four are abstract: `GetAsync` returning `T?` with `null` for a miss (`ICacheService.cs:17`), `SetAsync` with an -optional `TimeSpan?` TTL (`ICacheService.cs:26`), `RemoveAsync` for one key (`ICacheService.cs:36`), -and `RemoveByPrefixAsync` for bulk eviction by key prefix (`ICacheService.cs:42`). Two are **default -interface members** with working bodies, so each one shipped without breaking an implementer: -`IncrementAsync` (`ICacheService.cs:59`) is a read-modify-write counter (`ICacheService.cs:61-64`) that -gives the [ADR-029](https://ivanball.github.io/docs/adr/029-authentication-brute-force-protection.html) +optional `TimeSpan?` TTL (`ICacheService.cs:72`), `RemoveAsync` for one key (`ICacheService.cs:82`), +and `RemoveByPrefixAsync` for bulk eviction by key prefix (`ICacheService.cs:88`). Four are **default +interface members** with working bodies, so each one shipped without breaking an implementer. +`TryGetAsync` (`ICacheService.cs:34`) reports presence separately from the value, because for a +value-type `T` a `GetAsync` miss answers `default(T)` and cannot be told apart from a cached `0` or +`false` (`ICacheService.cs:19-23`); the default infers presence from a non-null value +(`ICacheService.cs:36-37`), and the shipped stores that can do better override it. +`GetFromSharedStoreAsync` (`ICacheService.cs:62`) reads past any process-local copy, for +single-use records (an OAuth exchange code, a password-reset or email-confirmation token, a +second-factor time step) whose consumption on one replica must be visible on every other one at once +(`ICacheService.cs:40-61`); its default simply calls `GetAsync`, which is exact for a store with no +local tier. `IncrementAsync` (`ICacheService.cs:105`) is a read-modify-write counter +(`ICacheService.cs:107-110`) that gives the +[ADR-029](https://ivanball.github.io/docs/adr/029-authentication-brute-force-protection.html) brute-force and rate-limit counters one entry point instead of scattered get-then-set pairs, and -`GetOrCreateAsync` (`ICacheService.cs:99`) folds read, per-key stripe, double-check, factory and -write into one call (`ICacheService.cs:104-124`) using the process-wide `CacheKeyLocks` table -(`ICacheService.cs:142-146`), cross-referenced in +`GetOrCreateAsync` (`ICacheService.cs:145`) folds read, per-key stripe, double-check, factory and +write into one call (`ICacheService.cs:150-171`), taking presence from `TryGetAsync` on both checks so +a cached `default(T)` counts as a hit (`ICacheService.cs:153-165`), using the process-wide +`CacheKeyLocks` table (`ICacheService.cs:189-193`), cross-referenced in [Group 5](group-05-cqrs-pipeline.md#cachekeylocks). Its XML doc is explicit about two limits that matter: it caches whatever the factory returned, failed [`Result`](group-01-result-error-handling.md#result) included, which is exactly why the caching decorators do not route through it, and its stampede -protection is per process (`ICacheService.cs:80-97`). `RemoveByPrefixAsync` is the load-bearing member: +protection is per process (`ICacheService.cs:126-143`). `RemoveByPrefixAsync` is the load-bearing member: it is what lets a single write evict every cached read it could have staled, and it is why the backends had to be built rather than used off the shelf (`IMemoryCache` has no key enumeration, `IDistributedCache` has no prefix delete). [Rubric §3, Clean Architecture] assesses whether dependencies point inward and infrastructure stays replaceable; this is that rule in one file, since the only thing Application knows -about caching is six method signatures. +about caching is eight method signatures. **Backend selection happens once, at the composition root.** `AddCaching(IConfiguration?)` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:26`, called from @@ -55,18 +65,19 @@ the one knob the Application-layer query decorator needs (`DependencyInjection.C that layer because it cannot reference Infrastructure). Without configuration both settings objects are still registered at their defaults (`DependencyInjection.Caching.cs:53-57`), so `IOptions` always resolves. [`ICacheService`](#icacheservice) itself is registered through `TryAddSingleton` with a factory that -probes the container (`DependencyInjection.Caching.cs:59-78`). If an `IDistributedCache` is registered **and +probes the container (`DependencyInjection.Caching.cs:59-80`). If an `IDistributedCache` is registered **and it is not the default `MemoryDistributedCache`** (`DependencyInjection.Caching.cs:62`), meaning a real out-of-process store such as the Redis cache Aspire wires, the factory builds a [`DistributedCacheService`](#distributedcacheservice) with whatever `IConnectionMultiplexer` and `ILogger` it can resolve plus the bound key namespace and cache settings (`DependencyInjection.Caching.cs:64-73`); otherwise it falls back to a -[`MemoryCacheService`](#memorycacheservice) over the registered `IMemoryCache` -(`DependencyInjection.Caching.cs:77`). The same method registers the -[`IDistributedLock`](group-05-cqrs-pipeline.md#idistributedlock) that the API idempotency filter pairs -with the cache, [Redis-backed](group-14-module-system-composition.md#redisdistributedlock) when a +[`MemoryCacheService`](#memorycacheservice) over the registered `IMemoryCache`, handed the same bound +cache settings (`DependencyInjection.Caching.cs:77-79`). The same method registers the +[`IDistributedLock`](group-05-cqrs-pipeline.md#idistributedlock) that both in-framework callers pair +with the cache, the API idempotency filter and password-reset token redemption +(`DependencyInjection.Caching.cs:82-85`), [Redis-backed](group-14-module-system-composition.md#redisdistributedlock) when a multiplexer is present and [process-local](group-14-module-system-composition.md#inprocessdistributedlock) -otherwise (`DependencyInjection.Caching.cs:84-98`). A single-process monolith therefore caches in-process for +otherwise (`DependencyInjection.Caching.cs:86-100`). A single-process monolith therefore caches in-process for free, and the identical application code uses Redis the moment a distributed cache is present: no flag, no per-environment branch in a handler. This is the same "abstraction in Application, transport chosen at the edge" pattern the message bus and gRPC clients use, which is what [Rubric §7, Microservices @@ -75,64 +86,76 @@ Readiness] looks for (can a module move to its own process without a code change without touching business code). **A third substrate, opt in and explicit.** `AddCommonHybridCache(Action?)` -(`DependencyInjection.Caching.cs:137`) calls `AddHybridCache()` (`DependencyInjection.Caching.cs:139`) and then +(`DependencyInjection.Caching.cs:139`) calls `AddHybridCache()` (`DependencyInjection.Caching.cs:141`) and then configures `HybridCacheOptions` through the options pipeline rather than through the -`AddHybridCache` callback (`DependencyInjection.Caching.cs:145-159`), because the TTL policy now comes from +`AddHybridCache` callback (`DependencyInjection.Caching.cs:147-161`), because the TTL policy now comes from the bound [`CacheSettings`](#cachesettings) and the callback has no service provider to read it from; the framework sets `Expiration` from `DefaultDuration` and `LocalCacheExpiration` from `LocalCacheDuration` (falling back to -`HybridCacheService.LocalCacheDefault`) at `DependencyInjection.Caching.cs:152-156`, and the host's own hook -runs last so it can override anything (`DependencyInjection.Caching.cs:158`). The swap of the cache +`HybridCacheService.LocalCacheDefault`) at `DependencyInjection.Caching.cs:154-158`, and the host's own hook +runs last so it can override anything (`DependencyInjection.Caching.cs:160`). The swap of the cache implementation is deliberately `RemoveAll()` followed by `AddSingleton` -(`DependencyInjection.Caching.cs:163-177`) rather than `TryAdd`, so the call wins whether it runs before or +(`DependencyInjection.Caching.cs:165-179`) rather than `TryAdd`, so the call wins whether it runs before or after `AddInfrastructure`; the source is equally explicit that this also removes a host's own bespoke `ICacheService`, so calling it is a statement that the two-level cache is the cache -(`DependencyInjection.Caching.cs:126-129`). All seven deployed service hosts call it, inside the same "is Redis -configured" branch that registers the distributed cache: ADC Conference at +(`DependencyInjection.Caching.cs:128-131`). `AddCommonHybridCache` has no guard of its own, so the +framework also ships the guarded form, `AddCommonHybridCacheWhenRedisConfigured(IConfiguration, string)` +(`DependencyInjection.Caching.cs:200`), which calls it only when the `redis` connection string (the +default `connectionName`) is non-empty (`DependencyInjection.Caching.cs:206-209`) and otherwise leaves +the registration untouched. All seven deployed service hosts call that guarded form unconditionally, +right after `AddRedisCaching()`: ADC Conference at `MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:203` and Store Catalog at -`MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:94`, with ADC Engagement, Identity -and Notification and Store Sales and Identity alongside them. Without Redis the branch does not run and +`MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:108`, with ADC Engagement, Identity +and Notification and Store Sales and Identity alongside them. Without Redis the guard declines and the host keeps the auto-selected substrate, which is the point: an L1 in front of an in-process L2 buys -nothing ([ADR-077](https://ivanball.github.io/docs/adr/077-hybridcache-substrate.html)). +nothing (`DependencyInjection.Caching.cs:189-191`, +[ADR-077](https://ivanball.github.io/docs/adr/077-hybridcache-substrate.html)). **The in-process adapter.** [`MemoryCacheService`](#memorycacheservice) -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:18`) wraps -`IMemoryCache` and carries one side structure: a `ConcurrentDictionary` of live keys -(`MemoryCacheService.cs:31`), because `IMemoryCache` cannot enumerate its own keys and without that -shadow index `RemoveByPrefixAsync` (`MemoryCacheService.cs:128-138`) would be impossible. Keeping the +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:19`) wraps +`IMemoryCache`, takes the bound [`CacheSettings`](#cachesettings) (framework defaults when none is +passed, `MemoryCacheService.cs:25`) so an entry written without an expiration gets the same +`DefaultDuration` every other store applies (`MemoryCacheService.cs:84`), and carries one side structure: a `ConcurrentDictionary` of live keys +(`MemoryCacheService.cs:38`), because `IMemoryCache` cannot enumerate its own keys and without that +shadow index `RemoveByPrefixAsync` (`MemoryCacheService.cs:138-148`) would be impossible. Keeping the cache and the index in agreement is the whole design. Every mutation takes that key's stripe from a per-instance [`KeyedSemaphoreStripe`](group-08-auth.md#keyedsemaphorestripe) -(`MemoryCacheService.cs:38`) and touches the cache before the table -(`MemoryCacheService.cs:101-105`), because ordering alone cannot close the window: track-then-write +(`MemoryCacheService.cs:45`) and touches the cache before the table +(`MemoryCacheService.cs:111-115`), because ordering alone cannot close the window: track-then-write lets a concurrent removal drop the record between the steps, and write-then-track lets a removal run entirely between them, both leaving a live entry nothing can find. The post-eviction callback is deliberately lock-free, since `IMemoryCache` queues it to the thread pool, and it removes the record only while the record is still its own, comparing the entry token by reference and skipping -`EvictionReason.Replaced` outright (`MemoryCacheService.cs:93-99`). `GetAsync` also matches on the -stored object rather than using the generic `TryGetValue` overload (`MemoryCacheService.cs:46`), so -a key reused under a different `T` surfaces as a clean miss instead of an `InvalidCastException`. +`EvictionReason.Replaced` outright (`MemoryCacheService.cs:103-109`). `GetAsync` also matches on the +stored object rather than using the generic `TryGetValue` overload (`MemoryCacheService.cs:53`), so +a key reused under a different `T` surfaces as a clean miss instead of an `InvalidCastException`, and +it overrides `TryGetAsync` with the same type-matched lookup so presence is real rather than inferred +from a non-null value (`MemoryCacheService.cs:62-64`). **The out-of-process adapter.** [`DistributedCacheService`](#distributedcacheservice) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/DistributedCacheService.cs:19`) -serializes values to UTF-8 JSON via `System.Text.Json` (`DistributedCacheService.cs:153-157`) and +serializes values to UTF-8 JSON via `System.Text.Json` (`DistributedCacheService.cs:161-165`) and stores them through `IDistributedCache`, applying the bound TTL default when the caller supplies none -(`DistributedCacheService.cs:37`, `DistributedCacheService.cs:64`). Prefix eviction is where it earns +(`DistributedCacheService.cs:37`, `DistributedCacheService.cs:72`). It overrides `TryGetAsync` with a +real presence check on the stored bytes (`DistributedCacheService.cs:48-53`) and keeps the default +`GetFromSharedStoreAsync`, which is already exact here because the store has no process-local tier. +Prefix eviction is where it earns its keep: when an `IConnectionMultiplexer` is available it hands the namespace-qualified pattern and a raw `KeyDeleteAsync` to [`RedisPrefixScanner`](#redisprefixscanner) -(`DistributedCacheService.cs:116-122`), resolving the `IDatabase` lazily on the first delete -(`DistributedCacheService.cs:114`, `DistributedCacheService.cs:119`). When no multiplexer is +(`DistributedCacheService.cs:124-130`), resolving the `IDatabase` lazily on the first delete +(`DistributedCacheService.cs:122`, `DistributedCacheService.cs:127`). When no multiplexer is registered, prefix eviction cannot run at all, and rather than failing silently the class logs a -warning **once**, guarded by an `Interlocked.Exchange` flag (`DistributedCacheService.cs:73`, -`DistributedCacheService.cs:107-108`) and naming the fix (`AddRedisClient`), because a permanently dead +warning **once**, guarded by an `Interlocked.Exchange` flag (`DistributedCacheService.cs:81`, +`DistributedCacheService.cs:115-116`) and naming the fix (`AddRedisClient`), because a permanently dead invalidation is a steady state that must not flood the log on every command; the anomalous "multiplexer with no servers" case and a per-server failure each get their own message -(`DistributedCacheService.cs:120-121`). All three are compile-time `LoggerMessage` sources -(`DistributedCacheService.cs:159-166`). That warn-once-versus-warn-always split is a small but real +(`DistributedCacheService.cs:128-129`). All three are compile-time `LoggerMessage` sources +(`DistributedCacheService.cs:167-174`). That warn-once-versus-warn-always split is a small but real [Rubric §13, Observability and Operability] decision: §13 assesses whether an operator can tell what the system is doing, and a cache whose invalidation quietly does nothing is exactly the failure mode that hides from dashboards. The class also **overrides** `IncrementAsync` -(`DistributedCacheService.cs:145-151`) while keeping the same non-atomic read-modify-write shape, and -the comment above it (`DistributedCacheService.cs:126-144`) is worth reading: Redis `INCR` would be +(`DistributedCacheService.cs:153-159`) while keeping the same non-atomic read-modify-write shape, and +the comment above it (`DistributedCacheService.cs:134-152`) is worth reading: Redis `INCR` would be atomic but writes a Redis *string*, while `StackExchangeRedisCache` stores every entry as a Redis *hash*, so an `INCR`-written counter makes the next read fail with `WRONGTYPE`. Readability of the counter wins over atomicity, and [ADR-026](https://ivanball.github.io/docs/adr/026-caching-strategy.html) @@ -156,30 +179,47 @@ The delete itself is a caller-supplied callback and the log messages stay with t removing keys differently. **The two-level cache.** [`HybridCacheService`](#hybridcacheservice) -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/HybridCacheService.cs:36`) puts an +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/HybridCacheService.cs:44`) puts an in-process L1 in front of the registered distributed L2 and lets the platform's `HybridCache` supply serialization, L1 promotion and stampede protection. Its structural decision is the **disjoint -keyspace**: every key is written as `{prefix}hc:{key}` (`HybridCacheService.cs:47`, -`HybridCacheService.cs:260`), so the payload layout `HybridCache` writes can never meet the UTF-8 JSON +keyspace**: every key is written as `{prefix}hc:{key}` (`HybridCacheService.cs:55`, +`HybridCacheService.cs:307`), so the payload layout `HybridCache` writes can never meet the UTF-8 JSON [`DistributedCacheService`](#distributedcacheservice) writes at one key. That is the `WRONGTYPE` lesson from `IncrementAsync` generalized: an entry in the other format is simply a clean miss, including while a rolling deploy runs both builds (`HybridCacheService.cs:18-28`). `RemoveByPrefixAsync` consequently runs the scanner over that one keyspace, `{namespace}hc:{prefix}*`, and deletes each match through `HybridCache.RemoveAsync` rather than by raw key, so this process's L1 copy goes with the L2 -entry (`HybridCacheService.cs:173-179`); a missing multiplexer warns once exactly as it does on the -single-level adapter (`HybridCacheService.cs:163-171`). Reads are fail-soft: a fault is logged, +entry (`HybridCacheService.cs:220-226`); a missing multiplexer warns once exactly as it does on the +single-level adapter (`HybridCacheService.cs:210-218`). Reads are fail-soft: a fault is logged, answered as a miss, and the offending entry is dropped best-effort so the next write repopulates it -(`HybridCacheService.cs:103-122`, `HybridCacheService.cs:290-300`). `IncrementAsync` is the one member -that bypasses L1 on **both** legs (`HybridCacheService.cs:71-76`, `HybridCacheService.cs:206-227`), and -the reasoning is a [Rubric §11, Security] point rather than a performance one: a counter cached -per replica would let one process read its own stale count and write it back, so a brute-force limiter -could be held near its starting value indefinitely by a steady stream of attempts against a single -replica. Its faults are deliberately not swallowed either, since a counter that silently reads zero -resets the limit it exists to enforce (`HybridCacheService.cs:201-204`). `GetOrCreateAsync` overrides -the interface default with `HybridCache`'s own implementation (`HybridCacheService.cs:238-255`). +(`HybridCacheService.cs:114-133`, `HybridCacheService.cs:337-347`). Two members bypass L1 entirely, +and both share one options instance that disables the L1 read and the L1 write +(`SharedStoreReadOptions`, `HybridCacheService.cs:82-87`), because both are values whose correctness +depends on every replica seeing the same thing; the reasoning is a [Rubric §11, Security] point rather +than a performance one. `GetFromSharedStoreAsync` overrides the interface default so a single-use +record (an OAuth exchange code, a password-reset or email-confirmation token, the last accepted +second-factor time step) consumed on one replica is a miss on every other replica at once rather than +usable a second time for up to the local expiration (`HybridCacheService.cs:35-42`, +`HybridCacheService.cs:150-169`); it stays fail-soft like `GetAsync`, and for such a record a miss is a +refusal, the safe direction. Its callers are +[`OAuthControllerBase`](group-12-api-hosting-mapping.md#oauthcontrollerbase) +(`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/OAuthControllerBase.cs:196`), +[`PasswordResetTokenService`](group-08-auth.md#passwordresettokenservice) +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:128`), +[`EmailConfirmationTokenService`](group-08-auth.md#emailconfirmationtokenservice) +(`.../Auth/EmailConfirmationTokenService.cs:90`) and +[`TwoFactorAuthenticator`](group-08-auth.md#twofactorauthenticator) +(`.../Auth/TwoFactor/TwoFactorAuthenticator.cs:105`). `IncrementAsync` bypasses L1 on **both** legs +(`HybridCacheService.cs:253-274`): a counter cached per replica would let one process read its own +stale count and write it back, so a brute-force limiter could be held near its starting value +indefinitely by a steady stream of attempts against a single replica. Its faults are deliberately not +swallowed, unlike the reads, since a counter that silently reads zero resets the limit it exists to +enforce (`HybridCacheService.cs:248-251`). The class does not override `TryGetAsync`, so presence there +is the interface default inferred from a non-null value. `GetOrCreateAsync` overrides +the interface default with `HybridCache`'s own implementation (`HybridCacheService.cs:285-302`). Replica L1 staleness after an invalidation is bounded by the local expiration, the shorter of the entry's TTL and `Cache:LocalCacheDuration` (30 seconds by default, -`HybridCacheService.cs:54`, `HybridCacheService.cs:269-280`), not by the eviction, and the source names +`HybridCacheService.cs:62`, `HybridCacheService.cs:316-327`), not by the eviction, and the source names that as the accepted cost of the L1 hit rate. **The key namespace and the TTL policy.** [`CacheKeyPrefixOptions`](#cachekeyprefixoptions) @@ -203,7 +243,7 @@ application name, and falls back again to the literal `app` keys at `ApplicationNamespace.cs:31` and `ApplicationNamespace.cs:34`), so the resolved prefix is never empty and isolation is automatic rather than opt-in (`ApplicationNamespace.cs:45-48`). `Qualify` (`CacheKeyPrefix.cs:91-92`) prepends it. The same namespace instance is handed to the Redis distributed -lock registered beside the cache (`DependencyInjection.Caching.cs:91-92`), so one application's cache entries +lock registered beside the cache (`DependencyInjection.Caching.cs:93-94`), so one application's cache entries and its lock keys carry one prefix. Both Redis-capable adapters honor it and apply it *inside* the adapter rather than through `RedisCacheOptions.InstanceName`; the rationale in the source (`CacheKeyPrefix.cs:16-25`) is precise, since `InstanceName` is prepended below this abstraction where @@ -235,9 +275,10 @@ the order is documented at tests the query for [`IQueryCacheable`](group-05-cqrs-pipeline.md#iquerycacheable) and passes straight through when it is absent (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/CachingQueryDecorator.cs:64-65`). -When present it scopes the key to the resolved tenant through -[`TenantCacheKey`](group-05-cqrs-pipeline.md#tenantcachekey) (`CachingQueryDecorator.cs:57-58`, so two -tenants can never share an entry), takes a lock-free fast path on a hit +When present it scopes the key to the target user for a caller-scoped query through +[`UserCacheKey`](group-05-cqrs-pipeline.md#usercachekey) and then to the resolved tenant through +[`TenantCacheKey`](group-05-cqrs-pipeline.md#tenantcachekey) (`CachingQueryDecorator.cs:58-59`, so two +tenants or two users can never share an entry), takes a lock-free fast path on a hit (`CachingQueryDecorator.cs:74-79`), and on a miss acquires a per-key stripe from the process-wide [`QueryCacheKeyLocks`](group-05-cqrs-pipeline.md#querycachekeylocks) (`CachingQueryDecorator.cs:185`), re-checks (`CachingQueryDecorator.cs:100-105`), records the miss on @@ -288,8 +329,8 @@ and evict it across replicas through the [`OutputCacheEvictionRequested`](group-04-events-outbox.md#outputcacheevictionrequested) integration event and its [`OutputCacheEvictionHandler`](group-12-api-hosting-mapping.md#outputcacheevictionhandler). Both adopters back that edge with Redis when Redis is configured -(`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:193`; -`MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:104`), so the two tiers ride the same +(`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:195`; +`MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:100`), so the two tiers ride the same Redis instance from opposite ends: Tier 1 through `IDistributedCache` or `HybridCache`, Tier 2 through the output-cache store. ADR-026 also records an optional **third tier on the client**, [`IUiReadCache`](group-15-common-ui-framework.md#iuireadcache), a per-circuit read-through cache over @@ -302,10 +343,10 @@ confuse them with Tier 1 when you meet `[OutputCache]` on a controller. Redis is live in the deployed services: every service host registers the Aspire Redis integration through [`RedisCachingExtensions`](group-16-aspire-orchestration.md#rediscachingextensions), whose `AddRedisCaching()` brings the `IConnectionMultiplexer` the SCAN needs along with the distributed cache -(`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:183`; -`MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:86`), and the hybrid substrate is -registered inside the same connection-string conditional -(`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:201-204`). Write-side adoption is +(`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:185`; +`MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:89`), and the hybrid substrate is +registered through the framework's connection-string guard +(`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:203`). Write-side adoption is broad: about forty types across ADC's Conference, Engagement and Identity modules implement [`ICacheInvalidating`](group-05-cqrs-pipeline.md#icacheinvalidating), for example `MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Categories/UseCases/UpdateCategoryItem/UpdateCategoryItemCommand.cs:18` @@ -320,14 +361,17 @@ consumers are not decorators at all: (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/LoginProtectionService.cs:19`) injects [`ICacheService`](#icacheservice) for its brute-force and rate-limit counters (`LoginProtectionService.cs:64`, `LoginProtectionService.cs:119`), covered in -[Group 8, Authentication and Authorization](group-08-auth.md), and +[Group 8, Authentication and Authorization](group-08-auth.md), the password-reset and +email-confirmation token services, which count requests through `IncrementAsync` +(`PasswordResetTokenService.cs:68`, `EmailConfirmationTokenService.cs:53`) and redeem their single-use +records through `GetFromSharedStoreAsync` as described above, and [`IdempotencyFilter`](group-12-api-hosting-mapping.md#idempotencyfilter) resolves it per request to store and replay responses -(`MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:140`). The key +(`MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:141`). The key namespace is live in both deployed applications, and the two pin it differently: ADC's four service hosts all set `Cache:KeyPrefix` to `adc:` alongside `Application:Namespace` `adc`, because they share one Redis and one Service Bus namespace and must agree on the keyspace they already share -(`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/appsettings.json:44`, rationale at `:30-42`), +(`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/appsettings.json:44-45`, rationale at `:30-42`), while each Store service pins its own (`MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/appsettings.json:45`, rationale at `:29-33`). Each of those values restates what the per-application default would already have produced, so the @@ -363,12 +407,12 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin [HybridCacheService](#hybridcacheservice) and the Redis distributed lock. It exists so several services sharing one Redis instance cannot read each other's entries by accidentally choosing the same key. Leaving it unset no longer means "no prefix": the empty value is a signal to fall back to - the framework's own per-application default (`CacheKeyPrefix.cs:106-109`). + the framework's own per-application default (`CacheKeyPrefix.cs:37-40`). - **Depends on**: nothing first-party for the options object itself; it is a plain POCO bound by `Microsoft.Extensions.Options` / `IConfiguration` (BCL). Its value is turned into behavior by [CacheKeyNamespace](#cachekeynamespace), which is what the cache adapters actually hold, and which now falls back to [ApplicationNamespace](group-14-module-system-composition.md#applicationnamespace) when this option - is unset (`CacheKeyPrefix.cs:106-109`). + is unset (`CacheKeyPrefix.cs:37-40`). - **Concept introduced, keyspace isolation for a shared cache, on by default.** `[Rubric §7, Microservices Readiness]` assesses whether a module keeps working, and keeps its data to itself, once it is lifted into its own process next to its siblings. A cache instance is exactly the kind of @@ -376,7 +420,7 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin for free has to be re-created explicitly. This option is one of two ways to get it: set it explicitly to pin a shared keyspace across hosts of the *same* application, or leave it unset and let [ApplicationNamespace](group-14-module-system-composition.md#applicationnamespace) derive a distinct keyspace per - application automatically (`CacheKeyPrefix.cs:105-109`). `[Rubric §11, Security]` assesses whether the + application automatically (`CacheKeyPrefix.cs:37-40`). `[Rubric §11, Security]` assesses whether the system prevents data reaching a caller who should not see it; SEC-Common-53 (`CacheKeyPrefix.cs:90-92`) records that this option used to default to no prefix at all, so two applications sharing one Redis silently shared one keyspace for both cache entries and distributed @@ -388,7 +432,7 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin populate-lock knob ([QueryCachePipelineSettings](group-14-module-system-composition.md#querycachepipelinesettings)), all three bound side by side in `AddCaching` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:39-57`). - - `KeyPrefix` (`CacheKeyPrefix.cs:46`, doc at `CacheKeyPrefix.cs:105-109`), `string` with + - `KeyPrefix` (`CacheKeyPrefix.cs:46`, doc at `CacheKeyPrefix.cs:36-45`), `string` with `{ get; init; }` and a default of `string.Empty`. The property default is unchanged, but its meaning changed: an empty value now tells [CacheKeyNamespace](#cachekeynamespace)`.From(IServiceProvider)` to resolve the framework's @@ -409,13 +453,13 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin `services.Configure(configuration.GetSection(CacheKeyPrefixOptions.SectionName))`, and only when a non-null `IConfiguration` was passed (`DependencyInjection.Caching.cs:39`). `AddInfrastructure` always passes one - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:134`), so a host composing through the + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:140`), so a host composing through the normal entry point gets the binding; a test calling the parameterless `AddCaching()` overload does not. The bound options are no longer read directly at the call sites; instead [CacheKeyNamespace](#cachekeynamespace)`.From(IServiceProvider)` resolves them (and the per-application fallback) once per registration, at three sites: the distributed cache factory - (`DependencyInjection.Caching.cs:67`), the Redis lock factory (`DependencyInjection.Caching.cs:91`) and - the opt-in hybrid factory (`DependencyInjection.Caching.cs:169`). + (`DependencyInjection.Caching.cs:67`), the Redis lock factory (`DependencyInjection.Caching.cs:93`) and + the opt-in hybrid factory (`DependencyInjection.Caching.cs:171`). - **Caveats / not-in-source**: [MemoryCacheService](#memorycacheservice) never sees the prefix, because a per-process keyspace is private by construction and a prefix would add nothing (`CacheKeyPrefix.cs:26-29`). The prior caveat here, that no checked-in `appsettings*.json` sets @@ -472,7 +516,7 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin (`026-caching-strategy.md:62`). - **Where it's used**: [DistributedCacheService](#distributedcacheservice)`.SetAsync` calls `CacheOptions.Create(expiration ?? _settings.DefaultDuration)` for every write - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/DistributedCacheService.cs:64`), which + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/DistributedCacheService.cs:72`), which is the only first-party call site of `Create`. `DefaultDuration` seeds [CacheSettings](group-09-caching.md#cachesettings)`.DefaultDuration` (`CacheSettings.cs:32`), which is what both distributed adapters actually read at runtime. @@ -546,9 +590,9 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin of cascading, and here a failing node costs you the freshness of the keys it holds, nothing more. - **Where it's used**: exactly two call sites, one per Redis-capable adapter: [DistributedCacheService](#distributedcacheservice)`.RemoveByPrefixAsync` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/DistributedCacheService.cs:116-122`) and + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/DistributedCacheService.cs:124-130`) and [HybridCacheService](#hybridcacheservice)`.RemoveByPrefixAsync` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/HybridCacheService.cs:173-179`). + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/HybridCacheService.cs:220-226`). Exercised against a real Redis by [DistributedCacheServiceRedisTests](group-28-testing-infrastructure.md#per-project-test-rollup) and [HybridCacheServiceRedisTests](group-28-testing-infrastructure.md#per-project-test-rollup), @@ -573,39 +617,39 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin `[Rubric §15, Best Practices & Code Quality]` assesses whether the code avoids incidental complexity and defensive noise. Rather than making every call site ask "is a prefix configured?", the unconfigured case is represented by a real instance, `None`, whose `Qualify` returns the key - unchanged. There is one branch (`CacheKeyPrefix.cs:266-267`) instead of a null check at every use. + unchanged. There is one branch (`CacheKeyPrefix.cs:91-92`) instead of a null check at every use. `[Rubric §11, Security]` assesses whether the system prevents data reaching a caller who should not see it: the composition-root factory, `From(IServiceProvider)`, is what turns an unset `Cache:KeyPrefix` into a non-empty, per-application prefix (SEC-Common-53, - `CacheKeyPrefix.cs:240-245`) rather than into `None`, so cross-application key collisions are closed + `CacheKeyPrefix.cs:65-69`) rather than into `None`, so cross-application key collisions are closed by construction rather than by a host remembering to configure a prefix. `[Rubric §14, Testability]` assesses whether behavior can be exercised without standing up the world: because the type is a plain object that both adapters take as an optional constructor parameter, a unit test passes `new CacheKeyNamespace("svc:")` directly and asserts on the qualified key with no configuration system involved - (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Caching/DistributedCacheServiceTests.cs:425`, - and the same shape throughout `HybridCacheServiceTests.cs:78-95`). -- **Walkthrough**: primary constructor `CacheKeyNamespace(string prefix)` (`CacheKeyPrefix.cs:225`). - - `None` (`CacheKeyPrefix.cs:228`), a `static` property initialised to `new(string.Empty)`. One + (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Caching/DistributedCacheServiceTests.cs:450`, + and the same shape throughout `HybridCacheServiceTests.cs:79-96`). +- **Walkthrough**: primary constructor `CacheKeyNamespace(string prefix)` (`CacheKeyPrefix.cs:50`). + - `None` (`CacheKeyPrefix.cs:53`), a `static` property initialised to `new(string.Empty)`. One shared, immutable instance meaning "leave keys alone"; still used directly wherever a caller wants no prefix at all (for example [MemoryCacheService](#memorycacheservice)'s registration), but no longer what an unconfigured `Cache:KeyPrefix` resolves to through `From(IServiceProvider)`. - - `Prefix` (`CacheKeyPrefix.cs:231`), get-only, initialised with `prefix ?? string.Empty`, so a null + - `Prefix` (`CacheKeyPrefix.cs:56`), get-only, initialised with `prefix ?? string.Empty`, so a null argument degrades to the no-op prefix rather than throwing later inside `string.Concat`. - - `From(IOptions? options)` (`CacheKeyPrefix.cs:234-238`), the original + - `From(IOptions? options)` (`CacheKeyPrefix.cs:59-63`), the original factory: tolerates a **null options object**, reads `options?.Value.KeyPrefix`, and returns `None` when that is null or empty. It is still the plain, options-only overload used directly by tests; production DI code no longer calls it. - - `From(IServiceProvider serviceProvider)` (`CacheKeyPrefix.cs:248-263`), the overload the DI + - `From(IServiceProvider serviceProvider)` (`CacheKeyPrefix.cs:73-88`), the overload the DI factories now call. It null-checks `serviceProvider`, reads the bound `CacheKeyPrefixOptions` off it, and returns `new CacheKeyNamespace(configured)` when `Cache:KeyPrefix` is set - (`CacheKeyPrefix.cs:252-256`); otherwise it resolves + (`CacheKeyPrefix.cs:77-81`); otherwise it resolves [ApplicationNamespace](group-14-module-system-composition.md#applicationnamespace)`.Resolve` against the container's `IConfiguration` and `IHostEnvironment` and returns - `new CacheKeyNamespace($"{applicationNamespace}:")` (`CacheKeyPrefix.cs:258-262`). Because + `new CacheKeyNamespace($"{applicationNamespace}:")` (`CacheKeyPrefix.cs:83-87`). Because `ApplicationNamespace.Resolve` never returns empty (`ApplicationNamespace.cs:47-48`), this overload never returns `None`. - - `Qualify(string key)` (`CacheKeyPrefix.cs:266-267`), expression-bodied: returns `key` unchanged when + - `Qualify(string key)` (`CacheKeyPrefix.cs:91-92`), expression-bodied: returns `key` unchanged when `Prefix.Length == 0`, otherwise `string.Concat(Prefix, key)`. No separator is inserted for an explicit prefix, so a caller-configured `Cache:KeyPrefix` must carry its own delimiter (`"conference:"`, not `"conference"`); the per-application fallback path adds the `:` itself. @@ -623,10 +667,10 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin constructor argument: to [DistributedCacheService](#distributedcacheservice) (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:67`), to [RedisDistributedLock](group-14-module-system-composition.md#redisdistributedlock) - (`DependencyInjection.Caching.cs:91`), and to [HybridCacheService](#hybridcacheservice) in the opt-in - hybrid path (`DependencyInjection.Caching.cs:169`). Inside each adapter it lands in a `_keys` field + (`DependencyInjection.Caching.cs:93`), and to [HybridCacheService](#hybridcacheservice) in the opt-in + hybrid path (`DependencyInjection.Caching.cs:171`). Inside each adapter it lands in a `_keys` field with a `?? CacheKeyNamespace.None` fallback (`DistributedCacheService.cs:30`, - `HybridCacheService.cs:82`). The in-process branch of `AddCaching()` constructs + `HybridCacheService.cs:93`). The in-process branch of `AddCaching()` constructs [MemoryCacheService](#memorycacheservice) with no namespace at all (`DependencyInjection.Caching.cs:76-77`), and the comment above it says why: the keyspace is private to the process, so neither the explicit prefix nor the per-application fallback is needed there. @@ -677,12 +721,12 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin - **Walkthrough**: one static field and three `init` properties. - `SectionName = "Cache"` (`CacheSettings.cs:25`). - `DefaultDuration` (`:32`), the absolute TTL applied when a caller supplies no expiration. - [HybridCacheService](#hybridcacheservice) uses it as the fallback TTL (`HybridCacheService.cs:271`) + [HybridCacheService](#hybridcacheservice) uses it as the fallback TTL (`HybridCacheService.cs:318`) and [DistributedCacheService](#distributedcacheservice) does the same - (`DistributedCacheService.cs:64`). + (`DistributedCacheService.cs:72`). - `LocalCacheDuration` (`:42`), nullable, the ceiling on the L1 copy of a two-level entry so a replica that never sees an invalidation still re-reads L2 within the window. Null keeps the - built-in 30-second ceiling (`HybridCacheService.cs:54`), and the effective L1 lifetime is the + built-in 30-second ceiling (`HybridCacheService.cs:62`), and the effective L1 lifetime is the shorter of the ceiling and the entry's own TTL (`:271-272`). The single-level cache services have no L1 and ignore it. - `PopulateLockTimeout` (`:57`), defaulting to `Timeout.InfiniteTimeSpan`: waiters block until the @@ -690,7 +734,7 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin waiter proceed uncached, and the remarks note that zero or a negative value means no bound, exactly like the default (`:50-56`). - Both cache services take the options as an OPTIONAL constructor parameter and fall back to a fresh - instance (`HybridCacheService.cs:41`, `:89`; `DistributedCacheService.cs:24`, `:37`), so a service + instance (`HybridCacheService.cs:49`, `:89`; `DistributedCacheService.cs:24`, `:37`), so a service constructed outside the container still gets the framework defaults. - **Why it's built this way**: registration guarantees `IOptions` always resolves. When configuration is available the section is bound and validated @@ -702,7 +746,7 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin The same values are then projected into `HybridCache`'s own option type through the options pipeline rather than the `AddHybridCache` callback, because the callback has no service provider to read the bound section from, and the host's own hook still runs last so it can override anything the framework set - (`DependencyInjection.Caching.cs:145-159`, + (`DependencyInjection.Caching.cs:147-161`, [ADR-077](https://ivanball.github.io/docs/adr/077-hybridcache-substrate.html)). - **Where it's used**: [DistributedCacheService](#distributedcacheservice) and [HybridCacheService](#hybridcacheservice) receive it through `IOptions` @@ -713,15 +757,16 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin ### ICacheService > MMCA.Common.Application · `MMCA.Common.Application.Interfaces` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:10` · Level 3 · interface -- **What it is**: the Application layer's cache port. Get by key, set with an optional TTL, remove by - exact key, remove every key matching a prefix, increment a counter, and get-or-create with stampede - protection. It hides whether the backing store is Redis, a SQL distributed cache, an in-process - `IMemoryCache`, or a two-level `HybridCache`. -- **Depends on**: BCL types at the signature level (`Task`, `CancellationToken`, `TimeSpan?`) plus - [KeyedSemaphoreStripe](group-08-auth.md#keyedsemaphorestripe) from `MMCA.Common.Shared.Concurrency`, - which the default `GetOrCreateAsync` body uses through the +- **What it is**: the Application layer's cache port. Get by key, look a key up and report presence + separately from the value, read straight from the shared backing store, set with an optional TTL, + remove by exact key, remove every key matching a prefix, increment a counter, and get-or-create with + stampede protection. It hides whether the backing store is Redis, a SQL distributed cache, an + in-process `IMemoryCache`, or a two-level `HybridCache`. +- **Depends on**: BCL types at the signature level (`Task`, `CancellationToken`, `TimeSpan?`, value + tuples) plus [KeyedSemaphoreStripe](group-08-auth.md#keyedsemaphorestripe) from + `MMCA.Common.Shared.Concurrency`, which the default `GetOrCreateAsync` body uses through the [CacheKeyLocks](group-05-cqrs-pipeline.md#cachekeylocks) holder (`ICacheService.cs:1`, - `ICacheService.cs:142-146`). Implemented by [MemoryCacheService](#memorycacheservice), + `ICacheService.cs:188-192`). Implemented by [MemoryCacheService](#memorycacheservice), [DistributedCacheService](#distributedcacheservice) and [HybridCacheService](#hybridcacheservice). - **Concept introduced, dependency inversion for infrastructure.** `[Rubric §3, Clean Architecture]` assesses whether business code depends on abstractions while concrete technology sits at the edges. @@ -729,96 +774,129 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin decorators and the auth services never see `StackExchange.Redis` or `Microsoft.Extensions.Caching`; they program against this interface and the container decides which adapter they get. **Second concept, the default interface member as a non-breaking extension point.** `[Rubric §15, - Best Practices & Code Quality]` assesses whether the codebase can absorb change without a ripple. Two members here - ship with bodies, `IncrementAsync` (`ICacheService.cs:59`) and `GetOrCreateAsync` - (`ICacheService.cs:99`). Every existing implementer keeps compiling and inherits working behavior, + Best Practices & Code Quality]` assesses whether the codebase can absorb change without a ripple. Four + members here ship with bodies: `TryGetAsync` (`ICacheService.cs:34`), `GetFromSharedStoreAsync` + (`ICacheService.cs:62`), `IncrementAsync` (`ICacheService.cs:105`) and `GetOrCreateAsync` + (`ICacheService.cs:145`). Every existing implementer keeps compiling and inherits working behavior, while a store with a better primitive overrides. That is how the [ADR-029](https://ivanball.github.io/docs/adr/029-authentication-brute-force-protection.html) - counters, and later the whole + counters, the whole [ADR-077](https://ivanball.github.io/docs/adr/077-hybridcache-substrate.html) two-level substrate, - were added to a *published package contract* without a breaking change. `[Rubric §12, Performance & - Scalability]`: `RemoveByPrefixAsync` (`ICacheService.cs:42`) is the member that makes *scoped* - invalidation possible, so one mutation evicts a whole family of cached query results without - enumerating individual keys. + and later the shared-store read for single-use records were added to a *published package contract* + without a breaking change. `[Rubric §12, Performance & Scalability]`: `RemoveByPrefixAsync` + (`ICacheService.cs:88`) is the member that makes *scoped* invalidation possible, so one mutation + evicts a whole family of cached query results without enumerating individual keys. - **Walkthrough**: members in declaration order. - `Task GetAsync(string key, CancellationToken)` (`ICacheService.cs:17`), returns `default` / `null` on a miss. + - `async Task<(bool Found, T? Value)> TryGetAsync(string key, CancellationToken)` + (`ICacheService.cs:34-38`), a **default implementation**. It exists for a value-type `T`, where + `GetAsync` answers a miss with `default(T)` (`0`, `false`, `Guid.Empty`) that cannot be told apart + from a cached `default(T)` (`ICacheService.cs:19-24`). The default body infers presence from a + non-null `GetAsync` result (`ICacheService.cs:36-37`), which is exact for reference and nullable + types; [MemoryCacheService](#memorycacheservice) and + [DistributedCacheService](#distributedcacheservice) override it with a real presence check. + - `Task GetFromSharedStoreAsync(string key, CancellationToken)` (`ICacheService.cs:62-63`), + a **default implementation** that simply calls `GetAsync`. It reads from the shared backing store, + bypassing any process-local copy, and is meant for single-use records (an OAuth exchange code, a + password-reset or email-confirmation token, a second-factor time step) whose consumption on one + replica must be visible to every other replica at once (`ICacheService.cs:40-43`). The remarks give + the usage rule: read the record through this member, then remove it; everything read many times + stays on `GetAsync` (`ICacheService.cs:50-54`). The default is exact for a store with no + process-local tier; [HybridCacheService](#hybridcacheservice) overrides it + (`ICacheService.cs:56-59`). - `Task SetAsync(string key, T value, TimeSpan? expiration = null, CancellationToken)` - (`ICacheService.cs:26-30`). A null `expiration` means "use the implementation's default TTL", - resolved from [CacheSettings](group-09-caching.md#cachesettings) on both - distributed paths (whose own default is [CacheOptions](#cacheoptions)`.DefaultDuration`). - - `Task RemoveAsync(string key, CancellationToken)` (`ICacheService.cs:36`), single-key eviction. - - `Task RemoveByPrefixAsync(string prefix, CancellationToken)` (`ICacheService.cs:42`), bulk eviction + (`ICacheService.cs:72-76`). A null `expiration` means "use the implementation's default TTL", + resolved from [CacheSettings](group-09-caching.md#cachesettings) on all three shipped stores + (whose own default is [CacheOptions](#cacheoptions)`.DefaultDuration`). + - `Task RemoveAsync(string key, CancellationToken)` (`ICacheService.cs:82`), single-key eviction. + - `Task RemoveByPrefixAsync(string prefix, CancellationToken)` (`ICacheService.cs:88`), bulk eviction of every key starting with `prefix`. This is what [CachingCommandDecorator](group-05-cqrs-pipeline.md#cachingcommanddecoratortcommand-tresult) invokes after a successful mutation. - `async Task IncrementAsync(string key, TimeSpan expiration, CancellationToken)` - (`ICacheService.cs:59-65`), a **default implementation**: read the current value as `long?` (0 on a + (`ICacheService.cs:105-111`), a **default implementation**: read the current value as `long?` (0 on a miss), add one, write it back with `expiration`, return the new value. The doc - (`ICacheService.cs:44-58`) is explicit about why it exists: rate-limit and brute-force counters + (`ICacheService.cs:90-104`) is explicit about why it exists: rate-limit and brute-force counters ([ADR-029](https://ivanball.github.io/docs/adr/029-authentication-brute-force-protection.html)) built from a raw `GetAsync` + `SetAsync` pair let concurrent requests overwrite each other's increments and undercount, so the read-modify-write is at least centralised in one auditable place, and a store with a native counter primitive can override. - `async Task GetOrCreateAsync(string key, Func> factory, TimeSpan? - expiration = null, CancellationToken)` (`ICacheService.cs:99-124`), the second default - implementation and the more interesting one. It null-guards the factory (`ICacheService.cs:105`), - takes a **lock-free fast path on a hit** (`ICacheService.cs:108-110`), and only on a miss acquires + expiration = null, CancellationToken)` (`ICacheService.cs:145-171`), the last default + implementation and the most interesting one. It null-guards the factory (`ICacheService.cs:151`), + takes a **lock-free fast path on a hit** (`ICacheService.cs:155-157`), and only on a miss acquires the key's stripe from [CacheKeyLocks](group-05-cqrs-pipeline.md#cachekeylocks) - (`ICacheService.cs:112`), re-reads under the lock (`ICacheService.cs:116-118`, the double-check that + (`ICacheService.cs:159`), re-reads under the lock (`ICacheService.cs:163-165`, the double-check that lets waiters see the value the winner just wrote), then runs the factory and stores its result - (`ICacheService.cs:120-121`). That is the same read-through-with-stampede-protection shape + (`ICacheService.cs:167-168`). Both reads go through `TryGetAsync`, not a null test, so a cached + `default(T)` of a value type counts as a hit and a miss still runs the factory + (`ICacheService.cs:153-154`). That is the same read-through-with-stampede-protection shape [CachingQueryDecorator](group-05-cqrs-pipeline.md#cachingquerydecoratortquery-tresult) applies to cacheable queries, made available to any caller. - - [CacheKeyLocks](group-05-cqrs-pipeline.md#cachekeylocks) (`ICacheService.cs:142-146`), the + - [CacheKeyLocks](group-05-cqrs-pipeline.md#cachekeylocks) (`ICacheService.cs:188-192`), the non-generic holder for the fixed-width [KeyedSemaphoreStripe](group-08-auth.md#keyedsemaphorestripe) that the default `GetOrCreateAsync` uses. It is deliberately a **separate** table from the query decorator's `QueryCacheKeyLocks` - (`ICacheService.cs:137-140`): different call sites over different keys, and sharing stripes would + (`ICacheService.cs:184-186`): different call sites over different keys, and sharing stripes would only widen the unrelated-key collisions striping already tolerates. - **Why it's built this way**: keeping the port in `MMCA.Common.Application` rather than Infrastructure is what lets the CQRS decorators, which also live in Application, depend on caching without dragging a Redis reference into the business layers. The optional `TimeSpan? expiration` lets callers override - the global TTL without a second overload. The two defaulted members follow the precedent + the global TTL without a second overload. The four defaulted members follow the precedent [ADR-077](https://ivanball.github.io/docs/adr/077-hybridcache-substrate.html) names explicitly: a default interface member is how this package grows a capability that no consumer has to react to. - Note the honest boundary the `GetOrCreateAsync` remarks draw (`ICacheService.cs:80-98`): caching is + `GetFromSharedStoreAsync` makes the single-use-record rule a named member rather than a convention, so + a caller states "this read must not be answered from a replica's own memory" at the call site. + Note the honest boundary the `GetOrCreateAsync` remarks draw (`ICacheService.cs:126-144`): caching is **unconditional** there, so a failed [Result](group-01-result-error-handling.md#result) would be cached, which is exactly why the caching decorators do NOT route through this member and keep their own read/execute/write sequence. - **Where it's used**: both caching decorators take `ICacheService` by constructor injection (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/CachingQueryDecorator.cs:45`, - `.../CachingCommandDecorator.cs:33`). Outside the pipeline, + `.../CachingCommandDecorator.cs:35`). Outside the pipeline, [LoginProtectionService](group-08-auth.md#loginprotectionservice) calls `IncrementAsync` for failed logins and per-IP registrations - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/LoginProtectionService.cs:64` and `:130`); - [PasswordResetTokenService](group-08-auth.md#passwordresettokenservice) uses it for both the - per-email request counter and the token entries themselves - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:55`, `:88`, - `:100`); [SoftDeletedUserCache](group-08-auth.md#softdeletedusercache) writes the soft-deleted marker - with `SetAsync` (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/SoftDeletedUserCache.cs:60`), + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/LoginProtectionService.cs:64` and `:119`); + [PasswordResetTokenService](group-08-auth.md#passwordresettokenservice) uses it for the per-email + request counter, the token write and the token read, the last through `GetFromSharedStoreAsync` + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:68`, `:90`, + `:128`); [EmailConfirmationTokenService](group-08-auth.md#emailconfirmationtokenservice) follows the + same shape + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:53`, `:75`, + `:90`); [TwoFactorAuthenticator](group-08-auth.md#twofactorauthenticator) reads the last accepted + time step through `GetFromSharedStoreAsync` and writes the new one with `SetAsync` + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs:105` + and `:113`); [SoftDeletedUserCache](group-08-auth.md#softdeletedusercache) writes the soft-deleted + marker with `SetAsync` (`MMCA.Common/Source/Core/MMCA.Common.Application/Auth/SoftDeletedUserCache.cs:60`), read back by [SoftDeletedUserMiddleware](group-12-api-hosting-mapping.md#softdeletedusermiddleware) (`MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/SoftDeletedUserMiddleware.cs:91`); [IdempotencyFilter](group-12-api-hosting-mapping.md#idempotencyfilter) resolves it per request - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:140`) to store and - replay the cached response record (`IdempotencyFilter.cs:360` and `:435`, default expiration 24 hours - at `:80`); and [OAuthControllerBase](group-12-api-hosting-mapping.md#oauthcontrollerbase) takes it as + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:141`) to store and + replay the cached response record (`IdempotencyFilter.cs:361` and `:450`, default expiration 24 hours + at `:81`); and [OAuthControllerBase](group-12-api-hosting-mapping.md#oauthcontrollerbase) takes it as a constructor dependency - (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/OAuthControllerBase.cs:37`). Exactly - one implementation is live per host: `AddCaching()` registers one via `TryAddSingleton` + (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/OAuthControllerBase.cs:37`), writing the + exchange-code entry with `SetAsync` (`OAuthControllerBase.cs:142`) and redeeming it through + `GetFromSharedStoreAsync` (`OAuthControllerBase.cs:196`). Exactly one implementation is live per + host: `AddCaching()` registers one via `TryAddSingleton` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:59`), and - `AddCommonHybridCache()` replaces it (`DependencyInjection.Caching.cs:163-164`). The default + `AddCommonHybridCache()` replaces it (`DependencyInjection.Caching.cs:165-166`). The default `GetOrCreateAsync` body is covered by [CacheServiceGetOrCreateTests](group-28-testing-infrastructure.md#per-project-test-rollup). - **Caveats / not-in-source**: `IncrementAsync` is **not atomic** on any shipped implementation. The default body is a read-modify-write, and both distributed adapters override it with the same shape rather than Redis `INCR`, for the storage-format reason spelled out in the [DistributedCacheService](#distributedcacheservice) section. Stampede protection in - `GetOrCreateAsync` is likewise **per process** (`ICacheService.cs:88-91`): with several replicas over + `GetOrCreateAsync` is likewise **per process** (`ICacheService.cs:134-136`): with several replicas over one shared cache the factory can still run once per replica, and a cluster-wide guarantee would need - a distributed lock, which is deliberately not attempted here. And `GetOrCreateAsync` has no - first-party caller outside tests today: it is a published extension point plus the member - [HybridCacheService](#hybridcacheservice) overrides. + a distributed lock, which is deliberately not attempted here. `GetOrCreateAsync` has no first-party + caller outside tests today: it is a published extension point plus the member + [HybridCacheService](#hybridcacheservice) overrides. And `TryGetAsync` is overridden only by + [MemoryCacheService](#memorycacheservice) and [DistributedCacheService](#distributedcacheservice): + [HybridCacheService](#hybridcacheservice) inherits the null-inference default, which is harmless for + its own `GetOrCreateAsync` (overridden, so it never calls `TryGetAsync`) but means a direct + `TryGetAsync` of a value type on that store cannot distinguish a miss from a cached `default(T)`. ### DistributedCacheService > MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Caching` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/DistributedCacheService.cs:19` · Level 4 · class (internal sealed partial) @@ -860,31 +938,36 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin - `GetAsync` (`DistributedCacheService.cs:40-45`), fetches the raw `byte[]` via `cache.GetAsync(_keys.Qualify(key), ...)`; returns `default` on null (a miss), else `Deserialize`. - - `SetAsync` (`DistributedCacheService.cs:53-66`), serializes to bytes and writes with + - `TryGetAsync` (`DistributedCacheService.cs:48-53`), an **override** of the + [ICacheService](#icacheservice) default that reads the same raw bytes and decides presence from + them: `null` bytes are `(false, default)`, anything else is `(true, Deserialize(bytes))` + (`DistributedCacheService.cs:52`). Presence is therefore a property of the stored entry, not of the + deserialized value, so a cached `0` or `false` is a hit. + - `SetAsync` (`DistributedCacheService.cs:61-74`), serializes to bytes and writes with `cache.SetAsync(_keys.Qualify(key), bytes, CacheOptions.Create(expiration ?? - _settings.DefaultDuration), ...)` (`DistributedCacheService.cs:64`). That single line is where the + _settings.DefaultDuration), ...)` (`DistributedCacheService.cs:72`). That single line is where the caller's optional `TimeSpan?`, the configured default and the hard-coded framework default all collapse into one `DistributedCacheEntryOptions`. - - `RemoveAsync` (`DistributedCacheService.cs:69-70`), expression-bodied passthrough on the qualified + - `RemoveAsync` (`DistributedCacheService.cs:77-78`), expression-bodied passthrough on the qualified key. - - `_noMultiplexerWarned` (`DistributedCacheService.cs:73`), an `int` flag flipped once via + - `_noMultiplexerWarned` (`DistributedCacheService.cs:81`), an `int` flag flipped once via `Interlocked.Exchange` so the missing-multiplexer warning fires exactly once per process rather than on every mutating command. - - `RemoveByPrefixAsync` (`DistributedCacheService.cs:100-123`). If `connectionMultiplexer` is null - (`DistributedCacheService.cs:102`) it logs the no-op once, guarded by `Interlocked.Exchange(ref - _noMultiplexerWarned, 1) == 0` (`DistributedCacheService.cs:107-108`), and returns; entries then + - `RemoveByPrefixAsync` (`DistributedCacheService.cs:108-131`). If `connectionMultiplexer` is null + (`DistributedCacheService.cs:110`) it logs the no-op once, guarded by `Interlocked.Exchange(ref + _noMultiplexerWarned, 1) == 0` (`DistributedCacheService.cs:115-116`), and returns; entries then expire on TTL alone. Otherwise it delegates the whole scan to [RedisPrefixScanner](#redisprefixscanner)`.RemoveMatchingAsync` - (`DistributedCacheService.cs:116-122`), passing the namespaced pattern - `$"{_keys.Qualify(prefix)}*"` (`DistributedCacheService.cs:118`, note the prefix is namespaced too, + (`DistributedCacheService.cs:124-130`), passing the namespaced pattern + `$"{_keys.Qualify(prefix)}*"` (`DistributedCacheService.cs:126`, note the prefix is namespaced too, which is the entire reason the namespace lives here rather than in `RedisCacheOptions.InstanceName`), a raw `KeyDeleteAsync` as the per-key delete over a lazily - resolved `IDatabase` (`DistributedCacheService.cs:114` and `:120`, so a host whose multiplexer + resolved `IDatabase` (`DistributedCacheService.cs:127` and `:122`, so a host whose multiplexer reports no scannable server never asks for a database), and its own two log hooks - (`DistributedCacheService.cs:120-121`). - - `IncrementAsync` (`DistributedCacheService.cs:145-151`), an **override** of the + (`DistributedCacheService.cs:128-129`). + - `IncrementAsync` (`DistributedCacheService.cs:153-159`), an **override** of the [ICacheService](#icacheservice) default that keeps the same read-modify-write shape. The remarks - (`DistributedCacheService.cs:126-144`) are the important read. Redis `INCR` would be atomic, which + (`DistributedCacheService.cs:133-152`) are the important read. Redis `INCR` would be atomic, which is what the member was added for, but `INCR` writes a Redis **string** while `StackExchangeRedisCache` stores every entry as a Redis **hash** (`absexp` / `sldexp` / `data`, read back with `HMGET`). Mixing the two at one key makes the next read fail with `WRONGTYPE`, which @@ -892,11 +975,11 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin [ADR-029](https://ivanball.github.io/docs/adr/029-authentication-brute-force-protection.html) case). A counter has to live in the same storage format as the reads that consult it, so readability was chosen over atomicity. - - `Deserialize` / `Serialize` (`DistributedCacheService.cs:153-157`), private static JSON + - `Deserialize` / `Serialize` (`DistributedCacheService.cs:161-165`), private static JSON helpers: `SerializeToUtf8Bytes(value)` and `Deserialize(bytes)!` (null-forgiving, since the BCL signature is nominally nullable). - `LogPrefixEvictionNoMultiplexer` / `LogPrefixEvictionNoServer` / `LogPrefixEvictionServerFailed` - (`DistributedCacheService.cs:159-166`), `[LoggerMessage]` `Warning`-level partial methods. The + (`DistributedCacheService.cs:167-174`), `[LoggerMessage]` `Warning`-level partial methods. The first names the fix explicitly ("Register a Redis client (AddRedisClient) to enable prefix eviction"), and the third states the blast radius ("the remaining servers are still processed, so entries on this one are bounded only by their TTL"), which is the difference between a log line and @@ -910,11 +993,13 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin than throwing, and the 30-second TTL becomes the staleness backstop. Warn-once keeps that degradation from being invisible without flooding the log. Lifting the scan into [RedisPrefixScanner](#redisprefixscanner) came with the two-level cache: two adapters that evict - differently should still share one eviction algorithm. `internal sealed partial`: `partial` for the - generated log methods, `internal sealed` because it is only ever resolved through the - [ICacheService](#icacheservice) registration. + differently should still share one eviction algorithm. The class has no process-local tier, so it + keeps the [ICacheService](#icacheservice) default for `GetFromSharedStoreAsync`: every read already + comes from the shared store. `internal sealed partial`: `partial` for the generated log methods, + `internal sealed` because it is only ever resolved through the [ICacheService](#icacheservice) + registration. - **Where it's used**: selected by `AddCaching()` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:59-78`). The + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:59-80`). The `TryAddSingleton` factory builds this implementation **only when** an `IDistributedCache` is present and is not the default `MemoryDistributedCache` (`DependencyInjection.Caching.cs:62`), resolving the optional `IConnectionMultiplexer` @@ -928,17 +1013,20 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin [DistributedCacheServiceTests](group-28-testing-infrastructure.md#per-project-test-rollup) and, against a real Redis, [DistributedCacheServiceRedisTests](group-28-testing-infrastructure.md#per-project-test-rollup). -- **Caveats / not-in-source**: all seven deployed service hosts call `AddCommonHybridCache()` inside a - Redis-conditional block (for example - `MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:201-204`, - `MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:94`), so this adapter is not the - one the container hands out in those hosts: it is the default for any host that registers a real - distributed cache and does *not* opt in. `IncrementAsync` here is not atomic (see the walkthrough); +- **Caveats / not-in-source**: all seven deployed service hosts call + `AddCommonHybridCacheWhenRedisConfigured`, which registers the two-level cache only when the `redis` + connection string is present + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:199-211`; for + example `MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:203`, + `MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:108`), so this adapter is not the + one the container hands out in those hosts when Redis is configured: it is the default for any host + that registers a real distributed cache and does *not* opt in. `IncrementAsync` here is not atomic + (see the walkthrough); [ADR-026](https://ivanball.github.io/docs/adr/026-caching-strategy.html) records the possible undercount as accepted rather than outstanding. ### HybridCacheService -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Caching` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/HybridCacheService.cs:36` · Level 4 · class (internal sealed partial) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Caching` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/HybridCacheService.cs:44` · Level 4 · class (internal sealed partial) - **What it is**: the two-level implementation of [ICacheService](#icacheservice), backed by `Microsoft.Extensions.Caching.Hybrid.HybridCache`: an in-process L1 in front of the host's registered @@ -955,13 +1043,13 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin Architecture]` assesses whether stored data has one owner and one shape; `[Rubric §15, Best Practices & Code Quality]` assesses whether a change can be rolled out without a coordinated flag day. Every key this service writes carries a `hc:` segment inside the configured prefix, `{prefix}hc:{key}` - (`HybridCacheService.cs:20`, `:48`, `:261`), which is the structural form of the `WRONGTYPE` lesson + (`HybridCacheService.cs:20`, `:55`, `:307`), which is the structural form of the `WRONGTYPE` lesson recorded on [DistributedCacheService](#distributedcacheservice)`.IncrementAsync`. `HybridCache` writes its own payload layout, not the UTF-8 JSON the older adapter writes, so letting the two meet at one key would reproduce that production failure at *every* key rather than at one counter. With the keyspaces disjoint, an entry written by the other service is simply invisible to this one (a clean miss) and vice versa, so two hosts can share one Redis without either being able to read a - payload it cannot parse (`HybridCacheService.cs:19-28`). + payload it cannot parse (`HybridCacheService.cs:19-27`). [ADR-077](https://ivanball.github.io/docs/adr/077-hybridcache-substrate.html) adds the case the code comment does not spell out, a rolling deploy where both builds serve traffic against one Redis (`077-hybridcache-substrate.md:54-56`), records the rejected alternative, a payload discriminator in @@ -969,86 +1057,101 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin (`077-hybridcache-substrate.md:58-61`); it also states the consequence this code implements, that prefix eviction "scans the `hc:` keyspace and nothing else" (`077-hybridcache-substrate.md:66-67`). `[Rubric §12, Performance & Scalability]`: the whole point of - L1 is removing a network hop and a JSON deserialize from every read of a small hot value. -- **Walkthrough**: primary constructor (`HybridCacheService.cs:36-41`), the same shape as the + L1 is removing a network hop and a JSON deserialize from every read of a small hot value. **Second + concept, knowing which reads must skip the fast tier.** `[Rubric §11, Security]`: an L1 copy is only + safe for a value that tolerates being stale on one replica for the local expiration. A counter and a + single-use record (an OAuth exchange code, a password-reset or email-confirmation token, the last + accepted second-factor time step) do not, so both read from L2 alone (`HybridCacheService.cs:35-42`). +- **Walkthrough**: primary constructor (`HybridCacheService.cs:44-49`), the same shape as the distributed adapter but over `HybridCache hybrid`. - - `KeyspaceSegment` (`HybridCacheService.cs:47`), `internal const string` = `"hc:"`, applied *inside* + - `KeyspaceSegment` (`HybridCacheService.cs:55`), `internal const string` = `"hc:"`, applied *inside* the configured namespace. - - `LocalCacheDefault` (`HybridCacheService.cs:54`), `internal static readonly TimeSpan` = 30 seconds. + - `LocalCacheDefault` (`HybridCacheService.cs:62`), `internal static readonly TimeSpan` = 30 seconds. It is both the default L1 lifetime and the **ceiling** applied to every entry, and `AddCommonHybridCache` seeds `HybridCacheOptions` from the same field when the host configured no `Cache:LocalCacheDuration` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:155`). - - `ReadOnlyOptions` (`HybridCacheService.cs:62-65`) and `CounterReadOptions` - (`HybridCacheService.cs:71-76`), two static option objects. The first sets + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:157`). + - `ReadOnlyOptions` (`HybridCacheService.cs:70-73`) and `SharedStoreReadOptions` + (`HybridCacheService.cs:82-87`), two static option objects. The first sets `HybridCacheEntryFlags.DisableUnderlyingData`, which tells `HybridCache` not to invoke the factory and not to write anything, so a miss stays a miss while an L2 hit is still promoted into L1. The - second adds `DisableLocalCacheRead | DisableLocalCacheWrite` for the counter path. - - `_keys` (`HybridCacheService.cs:82`), `_settings` (`HybridCacheService.cs:89`) and - `_noMultiplexerWarned` (`HybridCacheService.cs:92`), identical in role to their counterparts on + second adds `DisableLocalCacheRead | DisableLocalCacheWrite`, a read answered by L2 alone with no + L1 promotion, shared by the two reads whose correctness depends on every replica seeing the same + value: the counter read in `IncrementAsync` and `GetFromSharedStoreAsync` + (`HybridCacheService.cs:75-81`). + - `_keys` (`HybridCacheService.cs:93`), `_settings` (`HybridCacheService.cs:100`) and + `_noMultiplexerWarned` (`HybridCacheService.cs:103`), identical in role to their counterparts on [DistributedCacheService](#distributedcacheservice). - - `GetAsync` (`HybridCacheService.cs:103-122`), calls `hybrid.GetOrCreateAsync` with a `static` - no-op factory and `ReadOnlyOptions` (`HybridCacheService.cs:109-114`), which is how you perform a + - `GetAsync` (`HybridCacheService.cs:114-133`), calls `hybrid.GetOrCreateAsync` with a `static` + no-op factory and `ReadOnlyOptions` (`HybridCacheService.cs:120-125`), which is how you perform a plain read through an API whose primary shape is get-or-create. It is **fail-soft** - (`HybridCacheService.cs:95-102`): any exception that is not `OperationCanceledException` is logged - at warning and answered as a miss (`HybridCacheService.cs:116-121`), and the offending entry is + (`HybridCacheService.cs:106-113`): any exception that is not `OperationCanceledException` is logged + at warning and answered as a miss (`HybridCacheService.cs:127-132`), and the offending entry is dropped best-effort through `SelfHealAsync` so the next write repopulates it instead of the process failing the same read forever. - - `SetAsync` (`HybridCacheService.cs:132-137`), one call to `hybrid.SetAsync` with the options + - `GetFromSharedStoreAsync` (`HybridCacheService.cs:150-169`), an **override** of the + [ICacheService](#icacheservice) default. It is the same no-op-factory read as `GetAsync`, but with + `SharedStoreReadOptions` (`HybridCacheService.cs:159`), so the answer always comes from L2 and + nothing is promoted into this replica's memory: a removal on another replica is seen immediately, + which is what keeps a single-use record single-use (`HybridCacheService.cs:136-141`). It is + fail-soft exactly like `GetAsync` (`HybridCacheService.cs:163-168`); for a single-use record a miss + is a refusal, which is the safe direction (`HybridCacheService.cs:143-147`). + - `SetAsync` (`HybridCacheService.cs:179-184`), one call to `hybrid.SetAsync` with the options `WriteOptions(expiration)` builds. - - `RemoveAsync` (`HybridCacheService.cs:141-142`), `hybrid.RemoveAsync`, which clears this process's + - `RemoveAsync` (`HybridCacheService.cs:188-189`), `hybrid.RemoveAsync`, which clears this process's L1 copy along with the L2 entry. - - `RemoveByPrefixAsync` (`HybridCacheService.cs:161-180`). After the same warn-once no-multiplexer - guard (`HybridCacheService.cs:163-171`) it runs [RedisPrefixScanner](#redisprefixscanner) once, - over this service's own pattern `$"{HybridKey(prefix)}*"` (`HybridCacheService.cs:175`), which is + - `RemoveByPrefixAsync` (`HybridCacheService.cs:208-227`). After the same warn-once no-multiplexer + guard (`HybridCacheService.cs:210-218`) it runs [RedisPrefixScanner](#redisprefixscanner) once, + over this service's own pattern `$"{HybridKey(prefix)}*"` (`HybridCacheService.cs:222`), which is the one keyspace this service writes and therefore the one it evicts. The per-key delete routes back through `hybrid.RemoveAsync` rather than a raw `KeyDeleteAsync` - (`HybridCacheService.cs:176`): a raw delete would clear L2 and leave this process's own L1 copy - serving the value it just invalidated (`HybridCacheService.cs:151-155`). - - `IncrementAsync` (`HybridCacheService.cs:206-227`), a read-modify-write like the distributed + (`HybridCacheService.cs:223`): a raw delete would clear L2 and leave this process's own L1 copy + serving the value it just invalidated (`HybridCacheService.cs:199-201`). + - `IncrementAsync` (`HybridCacheService.cs:253-274`), a read-modify-write like the distributed adapter's, and deliberately **not** routed through this class's own `GetAsync` / `SetAsync` because - both legs must bypass L1 (`HybridCacheService.cs:210-215` reads with `CounterReadOptions`, - `:220-225` writes with the two disable flags). The reason - (`HybridCacheService.cs:192-200`) is the sharpest argument in this group: a counter is the one + both legs must bypass L1 (`HybridCacheService.cs:257-262` reads with `SharedStoreReadOptions`, + `:266-271` writes with the two disable flags). The reason + (`HybridCacheService.cs:240-247`) is the sharpest argument in this group: a counter is the one value whose correctness depends on every replica seeing the same number, so an L1 copy would let a process read its own stale count and write it back, and a brute-force counter could then be held near its starting value indefinitely by a steady stream of attempts against one replica. That is a security control silently weakened by a cache optimization, so `[Rubric §11, Security]` is the category that decided this member, not §12. Faults are also **not** swallowed here, unlike - `GetAsync` (`HybridCacheService.cs:201-204`): a counter that silently reads as zero would reset the + `GetAsync` (`HybridCacheService.cs:249-250`): a counter that silently reads as zero would reset the limit it exists to enforce. - - `GetOrCreateAsync` (`HybridCacheService.cs:238-255`), an override of the interface default that + - `GetOrCreateAsync` (`HybridCacheService.cs:285-302`), an override of the interface default that hands the work to `HybridCache`'s own primitive, which folds the double-check and the stampede protection into one call and additionally deduplicates concurrent callers before they reach L2. The - factory is passed as **state** rather than captured (`HybridCacheService.cs:248-251`), so the + factory is passed as **state** rather than captured (`HybridCacheService.cs:293-301`), so the delegate stays `static` and no closure is allocated per call. - - `HybridKey` (`HybridCacheService.cs:260`), `WriteOptions` (`HybridCacheService.cs:269-280`) and - `SelfHealAsync` (`HybridCacheService.cs:290-300`), the private helpers. `WriteOptions` is where + - `HybridKey` (`HybridCacheService.cs:307`), `WriteOptions` (`HybridCacheService.cs:316-327`) and + `SelfHealAsync` (`HybridCacheService.cs:337-347`), the private helpers. `WriteOptions` is where both dials meet: `ttl = expiration ?? _settings.DefaultDuration` - (`HybridCacheService.cs:271`), `localCeiling = _settings.LocalCacheDuration ?? LocalCacheDefault` - (`HybridCacheService.cs:272`), and `LocalCacheExpiration = ttl < localCeiling ? ttl : localCeiling` - (`HybridCacheService.cs:277`), so a long-lived entry does not sit in another replica's memory for + (`HybridCacheService.cs:318`), `localCeiling = _settings.LocalCacheDuration ?? LocalCacheDefault` + (`HybridCacheService.cs:319`), and `LocalCacheExpiration = ttl < localCeiling ? ttl : localCeiling` + (`HybridCacheService.cs:324`), so a long-lived entry does not sit in another replica's memory for its whole TTL after an invalidation that process never saw. - **Why it's built this way**: [ADR-077](https://ivanball.github.io/docs/adr/077-hybridcache-substrate.html) is the record. Opt-in rather than default keeps the release non-breaking: a host that never calls `AddCommonHybridCache` gets a byte-identical registration to before, and a memory-only host would gain nothing from an L1 in front of an L1 anyway - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:110-116`). The + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:112-118`). The registration deliberately uses `RemoveAll` + `Add` rather than `TryAdd` so it wins in either call - order (`DependencyInjection.Caching.cs:161-164`), with the honest warning that `RemoveAll` does not + order (`DependencyInjection.Caching.cs:163-166`), with the honest warning that `RemoveAll` does not distinguish the framework's registration from a host's own custom - [ICacheService](#icacheservice) (`DependencyInjection.Caching.cs:125-130`). `[Rubric §29, Resilience]` + [ICacheService](#icacheservice) (`DependencyInjection.Caching.cs:127-132`). `[Rubric §29, Resilience]` shows up in the fail-soft read: the cache is an optimization, never the system of record, so an unreadable entry costs a database round trip rather than a failed request. - **Where it's used**: registered only by `AddCommonHybridCache` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:137-180`), which all - seven deployed service hosts call inside a `GetConnectionString("redis")` conditional - (`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:203`, - `MMCA.ADC.Engagement.Service/Program.cs:113`, `MMCA.ADC.Identity.Service/Program.cs:135`, - `MMCA.ADC.Notification.Service/Program.cs:119`, - `MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:94`, - `MMCA.Store.Sales.Service/Program.cs:111`, `MMCA.Store.Identity.Service/Program.cs:100`). Everything + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:139-182`), which all + seven deployed service hosts reach through `AddCommonHybridCacheWhenRedisConfigured`, the guard that + calls it only when the `redis` connection string is configured + (`DependencyInjection.Caching.cs:199-211`): `MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:203`, + `MMCA.ADC.Engagement.Service/Program.cs:113`, `MMCA.ADC.Identity.Service/Program.cs:134`, + `MMCA.ADC.Notification.Service/Program.cs:116`, + `MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:108`, + `MMCA.Store.Sales.Service/Program.cs:113`, `MMCA.Store.Identity.Service/Program.cs:102`. Everything downstream still talks to [ICacheService](#icacheservice) and is unaware. Covered by [HybridCacheServiceTests](group-28-testing-infrastructure.md#per-project-test-rollup), the registration semantics by @@ -1062,27 +1165,33 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin [CachingCommandDecorator](group-05-cqrs-pipeline.md#cachingcommanddecoratortcommand-tresult) already performs (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/CachingCommandDecorator.cs:45` - and `:79`). Because the keyspaces are disjoint, a host that switches substrate starts cold: entries + and `:81`). It does not apply to counters or to reads made through `GetFromSharedStoreAsync`, which + never touch L1; a caller that reads a single-use record through plain `GetAsync` instead still gets + the L1 window, so the protection depends on the call site choosing the right member. `TryGetAsync` + is not overridden here, so it uses the [ICacheService](#icacheservice) null-inference default. + Because the keyspaces are disjoint, a host that switches substrate starts cold: entries another [ICacheService](#icacheservice) implementation wrote are invisible here and age out on their own TTL, which ADR-077 records as a cost rather than a correctness problem (`077-hybridcache-substrate.md:61-65`). ### MemoryCacheService -> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Caching` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:18` · Level 4 · class (internal sealed) +> MMCA.Common.Infrastructure · `MMCA.Common.Infrastructure.Caching` · `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:19` · Level 4 · class (internal sealed) - **What it is**: the in-process implementation of [ICacheService](#icacheservice), backed by `IMemoryCache`. Because `IMemoryCache` exposes no way to enumerate its keys, this service maintains its own `ConcurrentDictionary` tracking table so it can honor `RemoveByPrefixAsync`, the one capability the BCL memory cache lacks. The cache and that table are two structures that have to agree, so every mutation of a key runs under that key's lock stripe - (`MemoryCacheService.cs:8-17`). + (`MemoryCacheService.cs:9-18`). - **Depends on**: `IMemoryCache` / `MemoryCacheEntryOptions` and `ConcurrentDictionary` - (both BCL), plus [KeyedSemaphoreStripe](group-08-auth.md#keyedsemaphorestripe) from - `MMCA.Common.Shared.Concurrency` (`MemoryCacheService.cs:4`, `:38`) for the per-key mutual exclusion. - Implements [ICacheService](#icacheservice) and overrides **neither** default member, so it inherits - the non-atomic `IncrementAsync` and the stripe-plus-double-check `GetOrCreateAsync`. It takes no - [CacheKeyNamespace](#cachekeynamespace) and no - [CacheSettings](group-09-caching.md#cachesettings) at all. + (both BCL), [KeyedSemaphoreStripe](group-08-auth.md#keyedsemaphorestripe) from + `MMCA.Common.Shared.Concurrency` (`MemoryCacheService.cs:5`, `:45`) for the per-key mutual exclusion, + and the optional [CacheSettings](group-09-caching.md#cachesettings) (`MemoryCacheService.cs:19`, + `:25`) for the default TTL. Implements [ICacheService](#icacheservice) and overrides `TryGetAsync` + but **none** of the other default members, so it inherits the non-atomic `IncrementAsync`, the + stripe-plus-double-check `GetOrCreateAsync` and the `GetAsync`-backed `GetFromSharedStoreAsync` + (exact here, since there is no shared store beyond this process). It takes no + [CacheKeyNamespace](#cachekeynamespace). - **Concept introduced, a shadow index to back-fill a missing API.** `[Rubric §12, Performance & Scalability]` assesses cheap reads and sound invalidation; an in-process cache is the lowest-latency option available but is *not shared* across instances, so it is correct for a single-process monolith @@ -1094,56 +1203,64 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin distributed adapters, so swapping backends changes nothing for callers. **Second concept, an invariant that write ordering cannot buy you.** `[Rubric §15, Best Practices & Code Quality]` assesses everyday craftsmanship, including whether comments explain *why* rather than *what*. The `SetAsync` - remarks (`MemoryCacheService.cs:55-63`) are the worked example: with two structures to update, + remarks (`MemoryCacheService.cs:67-75`) are the worked example: with two structures to update, track-then-write lets a concurrent removal drop the tracking record between the two steps, and write-then-track lets a removal run entirely between them; both leave a live entry nothing can find. Neither order closes the window, so the class buys the invariant with mutual exclusion instead, and says so in the code rather than leaving the next reader to rediscover it. -- **Walkthrough**: primary constructor injection (`MemoryCacheService.cs:18`), `IMemoryCache cache`. - - `_keys` (`MemoryCacheService.cs:31`), `new ConcurrentDictionary? settings = null` optional. + - `_settings` (`MemoryCacheService.cs:25`), the bound `Cache` section or `new CacheSettings()` when a + host built the service without one. It supplies the expiration of an entry written without one, as + every other store does (`MemoryCacheService.cs:21-24`). + - `_keys` (`MemoryCacheService.cs:38`), `new ConcurrentDictionary(StringComparer.Ordinal)`. The value is **load-bearing**: it is the tracking token of the cache entry the record belongs to, a plain `object` compared by reference - (`MemoryCacheService.cs:20-30`). It exists so a post-eviction callback, which necessarily runs + (`MemoryCacheService.cs:27-37`). It exists so a post-eviction callback, which necessarily runs after its own entry may already have been superseded, can remove only its OWN record and never the record of a newer live entry. `Ordinal` comparison matches the ordinal prefix test below. - - `_keyLocks` (`MemoryCacheService.cs:38`), a + - `_keyLocks` (`MemoryCacheService.cs:45`), a [KeyedSemaphoreStripe](group-08-auth.md#keyedsemaphorestripe) serializing the paired mutation of the cache and `_keys` for one key. It is **per instance rather than static** - (`MemoryCacheService.cs:33-37`): the tracking table belongs to this service instance, so two + (`MemoryCacheService.cs:40-44`): the tracking table belongs to this service instance, so two instances have nothing to serialize against each other. - - `GetAsync` (`MemoryCacheService.cs:41-52`), calls `cache.TryGetValue(key, out var stored)` and - then **type-checks the stored object** with `stored is T typed` (`MemoryCacheService.cs:46`) before + - `GetAsync` (`MemoryCacheService.cs:48-59`), calls `cache.TryGetValue(key, out var stored)` and + then **type-checks the stored object** with `stored is T typed` (`MemoryCacheService.cs:53`) before returning it, wrapped in `Task.FromResult` (there is no real async work; `IMemoryCache` is synchronous). It takes no lock: it touches only the cache. The pattern match is deliberate - (`MemoryCacheService.cs:43-45`): the generic `TryGetValue` overload performs an unchecked + (`MemoryCacheService.cs:50-52`): the generic `TryGetValue` overload performs an unchecked `(T)stored` cast and throws `InvalidCastException` when a key is reused under a different `T`, so matching on the stored object turns a type mismatch (or a stored null) into a clean miss. - - `SetAsync` (`MemoryCacheService.cs:64-106`), the method that establishes the invariant the class - rests on. It builds `MemoryCacheEntryOptions` (`MemoryCacheService.cs:70`) and sets - `AbsoluteExpirationRelativeToNow` **only when** `expiration.HasValue` - (`MemoryCacheService.cs:72-75`), so there is no 30-second floor on this path: an unset TTL means no - time-based expiry, unlike the distributed paths. It mints this entry's identity, `var token = new - object()` (`MemoryCacheService.cs:78`), and registers the post-eviction callback - (`MemoryCacheService.cs:93-99`) with that token as the callback **state**. The callback body - **skips `EvictionReason.Replaced`** (`MemoryCacheService.cs:97`) and removes through the + - `TryGetAsync` (`MemoryCacheService.cs:62-64`), an **override** of the + [ICacheService](#icacheservice) default with the same type-checked lookup, returning `(true, + typed)` on a match and `(false, default)` otherwise. Presence comes from `TryGetValue` itself, so a + cached `0` or `false` is a hit, which is what lets the inherited `GetOrCreateAsync` cache a + value-type `default(T)` instead of re-running its factory. + - `SetAsync` (`MemoryCacheService.cs:76-116`), the method that establishes the invariant the class + rests on. It builds `MemoryCacheEntryOptions` with `AbsoluteExpirationRelativeToNow = expiration ?? + _settings.DefaultDuration` (`MemoryCacheService.cs:82-85`), so an entry written without a TTL + expires on the configured default exactly as on the distributed paths. It mints this entry's + identity, `var token = new object()` (`MemoryCacheService.cs:88`), and registers the post-eviction + callback (`MemoryCacheService.cs:103-109`) with that token as the callback **state**. The callback + body **skips `EvictionReason.Replaced`** (`MemoryCacheService.cs:106`) and removes through the `KeyValuePair` overload, `_keys.TryRemove(new KeyValuePair(evictedKey.ToString()!, - state!))`, which deletes the record only while the tracked value is still this entry's own token. - The comment (`MemoryCacheService.cs:80-92`) explains the shape: the callback stays deliberately - **lock-free** because `IMemoryCache` queues it to the thread pool, and waiting on a stripe from a - pool thread would stall the pool behind whichever caller holds it; running lock-free means it can - land when the key already carries a newer live entry, so the token check is what stops it - untracking an entry that is still cached (live but invisible to `RemoveByPrefixAsync`, clearable - only by its TTL). Only then does the write happen, under the key's stripe: `using (await - _keyLocks.AcquireAsync(key, cancellationToken)...)` (`MemoryCacheService.cs:101`), `cache.Set(key, - value, options)` (`MemoryCacheService.cs:103`), `_keys[key] = token` - (`MemoryCacheService.cs:104`). - - `RemoveAsync` (`MemoryCacheService.cs:110-117`), the same stripe and the same order - (`MemoryCacheService.cs:109`): acquire (`:112`), `cache.Remove` (`:114`), then `_keys.TryRemove(key, - out _)` (`:115`). - - `RemoveByPrefixAsync` (`MemoryCacheService.cs:128-138`), iterates `_keys.Keys.Where(k => - k.StartsWith(prefix, StringComparison.Ordinal))` (`MemoryCacheService.cs:130`) and removes each key - from both stores under its own stripe (`MemoryCacheService.cs:132-136`). Two details from the - remarks (`MemoryCacheService.cs:120-127`): the candidate list is a **snapshot**, because + state!))` (`MemoryCacheService.cs:107`), which deletes the record only while the tracked value is + still this entry's own token. The comment (`MemoryCacheService.cs:90-102`) explains the shape: the + callback stays deliberately **lock-free** because `IMemoryCache` queues it to the thread pool, and + waiting on a stripe from a pool thread would stall the pool behind whichever caller holds it; + running lock-free means it can land when the key already carries a newer live entry, so the token + check is what stops it untracking an entry that is still cached (live but invisible to + `RemoveByPrefixAsync`, clearable only by its TTL). Only then does the write happen, under the key's + stripe: `using (await _keyLocks.AcquireAsync(key, cancellationToken)...)` + (`MemoryCacheService.cs:111`), `cache.Set(key, value, options)` (`MemoryCacheService.cs:113`), + `_keys[key] = token` (`MemoryCacheService.cs:114`). + - `RemoveAsync` (`MemoryCacheService.cs:120-127`), the same stripe and the same order + (`MemoryCacheService.cs:119`): acquire (`:122`), `cache.Remove` (`:124`), then `_keys.TryRemove(key, + out _)` (`:125`). + - `RemoveByPrefixAsync` (`MemoryCacheService.cs:138-148`), iterates `_keys.Keys.Where(k => + k.StartsWith(prefix, StringComparison.Ordinal))` (`MemoryCacheService.cs:140`) and removes each key + from both stores under its own stripe (`MemoryCacheService.cs:142-146`). Two details from the + remarks (`MemoryCacheService.cs:130-137`): the candidate list is a **snapshot**, because `ConcurrentDictionary.Keys` already copies, so it is enumerated outside every lock; and each stripe is released before the next one is taken, never accumulated across the loop, because distinct keys can map to the same stripe and to different stripes in a different relative order, so holding @@ -1156,23 +1273,30 @@ are catalogued in [Group 27, Testing and Quality Infrastructure](group-28-testin entries that are still live. The stripe then covers what neither guard can, the window between the two writes that any single-threaded reading of the code hides. Striping rather than a semaphore per key is a bounded-memory choice made once in - [KeyedSemaphoreStripe](group-08-auth.md#keyedsemaphorestripe) and reused here. The class is `internal - sealed` because it is only ever resolved through the [ICacheService](#icacheservice) registration. + [KeyedSemaphoreStripe](group-08-auth.md#keyedsemaphorestripe) and reused here. Taking + [CacheSettings](group-09-caching.md#cachesettings) gives all three stores one TTL policy, so a + caller's omitted expiration means the same thing whichever adapter the host resolved. The class is + `internal sealed` because it is only ever resolved through the [ICacheService](#icacheservice) + registration. - **Where it's used**: the fallback branch of `AddCaching()` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:77`). `AddCaching()` - always calls `AddMemoryCache()` first (`DependencyInjection.Caching.cs:28`), so when no real distributed - cache is registered this is the [ICacheService](#icacheservice) the container hands out, and a host - with no Redis behaves as a single-instance cached monolith with the full interface intact. Consumed - through the interface by both CQRS caching decorators, + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:77-79`), which + passes the optional `IOptions` (`DependencyInjection.Caching.cs:79`). `AddCaching()` + always calls `AddMemoryCache()` first (`DependencyInjection.Caching.cs:28`), so when no real + distributed cache is registered this is the [ICacheService](#icacheservice) the container hands out, + and a host with no Redis behaves as a single-instance cached monolith with the full interface intact. + Consumed through the interface by both CQRS caching decorators, [LoginProtectionService](group-08-auth.md#loginprotectionservice), [PasswordResetTokenService](group-08-auth.md#passwordresettokenservice), [SoftDeletedUserCache](group-08-auth.md#softdeletedusercache) and [IdempotencyFilter](group-12-api-hosting-mapping.md#idempotencyfilter). Unit-tested by [MemoryCacheServiceTests](group-28-testing-infrastructure.md#per-project-test-rollup), which pins the - concurrency behavior deterministically rather than racing for it: the test takes the key's own stripe - first, then asserts that a `SetAsync` and a `RemoveByPrefixAsync` both park on it - (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Caching/MemoryCacheServiceTests.cs:190`) - and that `RemoveAsync` waits on the same stripe as `SetAsync` (`MemoryCacheServiceTests.cs:221`). + default-TTL behavior + (`MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Caching/MemoryCacheServiceTests.cs:26`), the + value-type `GetOrCreateAsync` miss that depends on `TryGetAsync` (`MemoryCacheServiceTests.cs:40`), + and the concurrency behavior deterministically rather than racing for it: the test takes the key's + own stripe first, then asserts that a `SetAsync` and a `RemoveByPrefixAsync` both park on it + (`MemoryCacheServiceTests.cs:218`) and that `RemoveAsync` waits on the same stripe as `SetAsync` + (`MemoryCacheServiceTests.cs:251`). - **Caveats / not-in-source**: the cache is per-process, so two replicas hold independent and potentially divergent copies until each entry's TTL or an explicit eviction reconciles them. That is why the distributed adapters exist for scaled-out deployments, and why diff --git a/docs-src/onboarding/group-10-notifications.md b/docs-src/onboarding/group-10-notifications.md index 858be45b..b3d3490a 100644 --- a/docs-src/onboarding/group-10-notifications.md +++ b/docs-src/onboarding/group-10-notifications.md @@ -15,9 +15,9 @@ the same one that runs through the whole codebase: application and domain code t forwarder) is chosen at the composition root, and a default is always registered so nothing has to be configured for DI to resolve. [`NullPushNotificationSender`](#nullpushnotificationsender) and [`NullLiveChannelPublisher`](#nulllivechannelpublisher) are no-ops -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:315-316`), +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:325-326`), `IEmailSender` defaults to the real [`SmtpEmailSender`](#smtpemailsender) -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:314`), and +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:324`), and `INotificationRecipientProvider` gets its default in the Application layer (`MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/DependencyInjection.cs:75`). @@ -27,7 +27,7 @@ they have different durability guarantees: 1. **The durable in-app inbox.** Every send writes one [`UserNotification`](#usernotification) row per recipient, so a user who was offline at send time still sees the message when they next open their inbox - (`MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:115-123`). + (`MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:124-132`). This is the persistent half of the two-channel model ([ADR-024](https://ivanball.github.io/docs/adr/024-push-notifications.html)). 2. **The transient SignalR push.** [`IPushNotificationSender`](#ipushnotificationsender) fans the same message out to any connections the recipient has open right now via the @@ -156,13 +156,13 @@ write and the recipient writes would leave a committed row carrying the key that delivered, and every retry of that key would then report success forever (`SendPushNotificationCommand.cs:11-19`). Inside that transaction the handler runs a deliberate ordering -(`MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:36-167`): +(`MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:41-176`): - **Dedup lookup first** (lines 39-50). When a key is present the handler requeries for an existing [`PushNotification`](#pushnotification) with that key and, on a hit, returns it mapped to a DTO without sending anything again. The lookup goes through `unitOfWork.GetReadRepository<...>()` rather than an injected repository, so it reads the same - data source the write below targets (`SendPushNotificationHandler.cs:174-183`). + data source the write below targets (`SendPushNotificationHandler.cs:194-203`). - **Resolve recipients** through [`INotificationRecipientProvider`](#inotificationrecipientprovider), failing early with a `PushNotification.NoRecipients` validation error when the set is empty (lines 52-62). @@ -263,7 +263,7 @@ accept the optional `scope` query parameter, length-checked against content newest-first, applies the scope predicate to the `PushNotification` side only when one was supplied, and clamps paging through [`PagingMath`](group-03-querying-specifications.md#pagingmath) with a 500-row page ceiling - (`MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/UserNotifications/UseCases/GetInbox/GetMyNotificationsHandler.cs:21,32,39-59`). + (`MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/UserNotifications/UseCases/GetInbox/GetMyNotificationsHandler.cs:21,32,39-61`). - The **unread count** ([`GetUnreadNotificationCountQuery`](#getunreadnotificationcountquery) / [`GetUnreadNotificationCountHandler`](#getunreadnotificationcounthandler)), served `[ResponseCache(NoStore = true)]` so the bell badge is never stale @@ -282,7 +282,7 @@ accept the optional `scope` query parameter, length-checked against **tracked** `Table` rather than `TableNoTracking`: an `AsNoTracking` source anywhere in a composed EF query switches the whole query to no-tracking, the `UserNotification` rows would come back untracked, and the `MarkAsRead` mutations would silently never be saved - (`MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/UserNotifications/UseCases/MarkAllRead/MarkAllNotificationsReadHandler.cs:30-45`) + (`MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/UserNotifications/UseCases/MarkAllRead/MarkAllNotificationsReadHandler.cs:37-46`) ([Rubric §8, Data Architecture]). [`UserNotification`](#usernotification)`.MarkAsRead` is **idempotent** and takes the read timestamp @@ -294,7 +294,7 @@ The organizer-facing history read is the fifth use case and lives on the other c [`GetNotificationHistoryHandler`](#getnotificationhistoryhandler) pages the [`PushNotification`](#pushnotification) audit rows newest-first under the same 500-row clamp and maps them through [`PushNotificationDTOMapper`](#pushnotificationdtomapper) -(`MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/GetHistory/GetNotificationHistoryHandler.cs:21,30-42`). +(`MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/GetHistory/GetNotificationHistoryHandler.cs:21,30-43`). List reads that go through the generic query service take a different path again: [`PushNotificationDTOProjector`](#pushnotificationdtoprojector) wraps the Mapperly-generated [`PushNotificationDTOProjection`](#pushnotificationdtoprojection), and merely registering it switches @@ -326,7 +326,7 @@ it exists is that **shape is not entitlement**: the pattern proves a key is well caller may read what is published to it, and the keys are consecutive integers, so enumerating them costs nothing. A host that publishes anything channel-scoped which is not public to every signed-in user has to register one. ADC does, per connection in the Notification host -(`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:234`), and the implementation is +(`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:231`), and the implementation is [`LiveChannelJoinAuthorizer`](#livechanneljoinauthorizer): it refuses an unauthenticated principal, admits the privileged read audience without a lookup (`ConferenceReadAudience.PrivilegedRoles`, the same Organizer / ContentEditor pair that reads the unfiltered catalog through REST), and otherwise @@ -359,7 +359,7 @@ The live channel is where the extracted-service topology shows through ([Rubric §7, Microservices Readiness]). In a monolith the default [`NullLiveChannelPublisher`](#nulllivechannelpublisher) is registered, and a host that maps the hub swaps in the real [`SignalRLiveChannelPublisher`](#signalrlivechannelpublisher) -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:316`, +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:326`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:66`). In extracted ADC, Engagement is a *different* process from the one that owns the WebSocket, so Engagement's live layer depends on [`ILiveChannelPublisher`](#ilivechannelpublisher) as usual but the composition root `Replace`s the @@ -369,7 +369,7 @@ adapter forwards each event over gRPC with a tight 2-second deadline to the Noti [`LiveChannelGrpcService`](#livechannelgrpcservice), which then delegates to the local [`SignalRLiveChannelPublisher`](#signalrlivechannelpublisher), the only host whose `IHubContext` can reach connected clients -(`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:22-38`). +(`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:31-63`). Both the adapter and the whole live path are **best-effort by contract** ([ADR-039](https://ivanball.github.io/docs/adr/039-live-channel-push.html)): every transport, resolution, or broken-circuit failure is logged and swallowed, never thrown, so a publishing command can never fail because Notification is slow or down @@ -382,19 +382,19 @@ Serving both a WebSocket and an h2c gRPC ingress from one host is the mixed-endp named `grpc` (8081 in the container, 5996 locally) is declared in the `Kestrel:Endpoints` config section and resolved by peers as `_grpc.notification` through the `services__notification__grpc__0` entry -(`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:59-74`). The host maps the hub -itself at `/hubs/notifications` via `MapNotificationHub()` (`Program.cs:285-289`, the path coming +(`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:58-73`). The host maps the hub +itself at `/hubs/notifications` via `MapNotificationHub()` (`Program.cs:272-276`, the path coming from `PushNotificationSettings.HubPath`, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Notifications/Push/PushNotificationSettings.cs:17`) and both -gRPC services on that dedicated endpoint (`Program.cs:298,306`). The two gRPC surfaces are **not** +gRPC services on that dedicated endpoint (`Program.cs:285,293`). The two gRPC surfaces are **not** protected alike, and the difference is deliberate: the live-channel ingress is mapped -`.AllowAnonymous()` (`Program.cs:298`) because it is reachable only on the internal service network +`.AllowAnonymous()` (`Program.cs:285`) because it is reachable only on the internal service network and its caller (Engagement's [`LiveChannelPublishProcessor`](group-22-engagement-module.md#livechannelpublishprocessor) background drain) has no HttpContext and forwards no bearer -(`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:13-20`), +(`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:16-23`), while the export rpc is mapped with `.RequireAuthorization()` because its response carries personal -data keyed by a raw user id (`Program.cs:302-306`). +data keyed by a raw user id (`Program.cs:289-293`). ## The module host, native-device registration, and the privacy export @@ -585,7 +585,7 @@ without any of the four ever taking the others down, and without a retried reque - **Why it's built this way**: keeping the interface in Application (and the SMTP dependency in Infrastructure) is what lets a test host register a no-op sender and production register [SmtpEmailSender](#smtpemailsender). Registration is `TryAddTransient()` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:314`), so the `TryAdd` lets + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:324`), so the `TryAdd` lets a host pre-register its own sender and win. - **Where it's used**: the framework's own consumer is the password-reset flow: [ForgotPasswordHandlerBase](group-14-module-system-composition.md#forgotpasswordhandlerbasetuser-tcommand) @@ -626,7 +626,7 @@ without any of the four ever taking the others down, and without a retried reque `[Rubric §7, Microservices Readiness]` assesses whether cross-boundary calls go through abstractions that can be re-homed onto a network transport: in MMCA.ADC this exact interface is served over gRPC by the Notification host's [LiveChannelGrpcService](#livechannelgrpcservice) - (`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:22`) and + (`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:31`) and consumed through a scoped adapter that `Replace`s the local registration in publishing services (`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Contracts/DependencyInjection.cs:48`), so the boundary already survives extraction ([ADR-007](https://ivanball.github.io/docs/adr/007-grpc-extraction.html), @@ -639,7 +639,7 @@ without any of the four ever taking the others down, and without a retried reque of the business of knowing each live event's schema; the presentation and UI layers agree on the contract. Non-delivery to absent clients is the intended semantics, not a gap. The default registration is the inert [NullLiveChannelPublisher](#nulllivechannelpublisher) - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:316`), replaced by + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:326`), replaced by [SignalRLiveChannelPublisher](#signalrlivechannelpublisher) when a host opts into the SignalR wiring (same file, line 632). - **Where it's used**: ADC's conference-day live layer does **not** inject it into command handlers. @@ -691,7 +691,7 @@ without any of the four ever taking the others down, and without a retried reque so the `TryAdd` finds the slot already taken. - **Where it's used**: [SendPushNotificationHandler](#sendpushnotificationhandler) takes it as a primary constructor dependency - (`MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:29`), + (`MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:34`), resolves the audience, then hands it to [IPushNotificationSender](#ipushnotificationsender). Until an app registers its own provider, [NullNotificationRecipientProvider](#nullnotificationrecipientprovider) returns an empty audience. @@ -722,14 +722,14 @@ without any of the four ever taking the others down, and without a retried reque - **Why it's built this way**: three targeting methods rather than one "audience" parameter keeps each call site's intent explicit and lets the SignalR implementation map user-targeting to hub groups directly. The default registration is the no-op - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:315`) so a host with no + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:325`) so a host with no real-time transport still resolves the port; the SignalR wiring replaces it with `AddTransient` (same file, line 631), a deliberate override rather than a `TryAdd`. - **Where it's used**: [SendPushNotificationHandler](#sendpushnotificationhandler) fans a message out - through this port (`SendPushNotificationHandler.cs:30`) after persisting the + through this port (`SendPushNotificationHandler.cs:35`) after persisting the [PushNotification](#pushnotification) record and its per-user [UserNotification](#usernotification) rows. That handler also carries a second, separate delivery leg, - `INativePushSender` (`SendPushNotificationHandler.cs:31`), which is the OS-level channel of + `INativePushSender` (`SendPushNotificationHandler.cs:36`), which is the OS-level channel of [ADR-044](https://ivanball.github.io/docs/adr/044-native-push-delivery.html) rather than part of this port. @@ -748,7 +748,7 @@ without any of the four ever taking the others down, and without a retried reque That is a decision, not an omission: the Transactional decorator only opens a transaction for a command that implements [ITransactional](group-05-cqrs-pipeline.md#itransactional) and otherwise passes it straight through - (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:27-29`), + (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:29-31`), so marking notifications read runs on the handler's own single `SaveChangesAsync` rather than an ambient transaction. `[Rubric §6, CQRS & Event-Driven]` assesses whether the write path is modeled as explicit messages with explicit cross-cutting opt-ins; the decorator pipeline is described in @@ -934,7 +934,7 @@ without any of the four ever taking the others down, and without a retried reque Architecture]`: the handler expresses the join as a LINQ `IQueryable` but never calls EF's `ToListAsync` or `CountAsync` directly, delegating those to [IQueryableExecutor](group-07-persistence-ef-core.md#iqueryableexecutor) so Application stays EF-free. - `[Rubric §11, Security]`: the `where un.UserId == query.UserId` clause (line 48) is the entire tenancy + `[Rubric §11, Security]`: the `where un.UserId == query.UserId` clause (line 50) is the entire tenancy boundary for an inbox, and the value comes from the resolved caller rather than from client input (`NotificationInboxController.cs:49,55`). The scope filter is explicitly **not** part of that boundary: the comment above it (lines 36-38) calls scope "a view filter, not a security boundary". @@ -950,21 +950,24 @@ without any of the four ever taking the others down, and without a retried reque `pn.ScopeKey == null || pn.ScopeKey == scopeKey` predicate when a scope is supplied (lines 39-44, the local `string scopeKey` on line 42 giving the expression tree a non-nullable capture); build the LINQ query-syntax join of `UserNotification` to that source on `un.PushNotificationId equals pn.Id`, - filtered to `query.UserId`, ordered by `pn.CreatedOn` descending, projected into `UserNotificationDTO` - (lines 46-59, mapping id, push id, title, body, `IsRead`, `ReadOn`, and `SentOn = pn.CreatedOn`); count - the joined set (line 61); page it with `Skip(skip).Take(take)` and materialize (lines 63-65); wrap - total, page size and floored page number into - [PaginationMetadata](group-01-result-error-handling.md#paginationmetadata) (line 69) and return a + filtered to `query.UserId`, ordered by `pn.CreatedOn` descending then `un.Id` descending (line 51), + projected into `UserNotificationDTO` (lines 48-61, mapping id, push id, title, body, `IsRead`, `ReadOn`, + and `SentOn = pn.CreatedOn`); count the joined set (line 63); page it with `Skip(skip).Take(take)` and + materialize (lines 65-67); wrap total, page size and floored page number into + [PaginationMetadata](group-01-result-error-handling.md#paginationmetadata) (line 71) and return a successful [PagedCollectionResult](group-01-result-error-handling.md#pagedcollectionresultt) - (line 70). + (line 72). - **Why it's built this way**: both repositories are read through `TableNoTracking`, which is correct for a read (no change-tracking overhead since nothing is saved). Server-side projection into the DTO means only the needed columns cross the wire, and the count runs against the *same* joined expression so the total is consistent with the page. Note the count and the page are two round trips against one composed `IQueryable`, the standard cost of a paged read. The scope predicate is applied to the push-notification side *before* the join rather than after it, so an unscoped read composes exactly the query it always - did. The metadata reports the clamped `take` and the floored page number (line 69) so the response - describes the page actually served: the comment on lines 67-68 makes that explicit, and it matters + did. The `un.Id` tie-break after `CreatedOn` makes the sort a total order: without it, two notifications + created in the same instant have no defined relative order, so they could swap places between two + `OFFSET` pages and one would repeat while the other vanished (the comment on lines 46-47 states exactly + that). The metadata reports the clamped `take` and the floored page number (line 71) so the response + describes the page actually served: the comment on lines 69-70 makes that explicit, and it matters because the `[Range]` attributes at the API boundary (`NotificationInboxController.cs:44-45`) do not protect a direct in-process caller. - **Where it's used**: injected as a closed `IQueryHandler` into [InboxController](#inboxcontroller) @@ -1010,45 +1013,56 @@ without any of the four ever taking the others down, and without a retried reque [NotificationBell](group-15-common-ui-framework.md#notificationbell) badge. ### MarkAllNotificationsReadHandler -> MMCA.Common.Application · `MMCA.Common.Application.Notifications.UserNotifications.UseCases.MarkAllRead` · `MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/UserNotifications/UseCases/MarkAllRead/MarkAllNotificationsReadHandler.cs:12` · Level 8 · class +> MMCA.Common.Application · `MMCA.Common.Application.Notifications.UserNotifications.UseCases.MarkAllRead` · `MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/UserNotifications/UseCases/MarkAllRead/MarkAllNotificationsReadHandler.cs:18` · Level 8 · class - **What it is**: the command handler that clears a user's unread notifications (all of them, or just the - ones in a scope): it loads the tracked unread rows, calls the domain method on each, and saves once. -- **Depends on**: [IUnitOfWork](group-07-persistence-ef-core.md#iunitofwork), - [IQueryableExecutor](group-07-persistence-ef-core.md#iqueryableexecutor) and the BCL `TimeProvider` - (primary constructor, lines 12-15). Implements + ones in a scope) with one set-based `UPDATE`: it builds a predicate over the user's unread rows and + hands it to the repository's `ExecuteUpdateAsync`, never loading a row. +- **Depends on**: [IUnitOfWork](group-07-persistence-ef-core.md#iunitofwork), resolved as + [IRepository](group-07-persistence-ef-core.md#irepositorytentity-tidentifiertype) + via `GetRepository`, and the BCL `TimeProvider` (primary constructor, lines 18-20). Implements [ICommandHandler](group-05-cqrs-pipeline.md#icommandhandlerin-tcommand-tresult)`` - and drives [UserNotification](#usernotification)'s `MarkAsRead`. -- **Concept introduced**: **write handlers read through `Table`, not `TableNoTracking`, and one - no-tracking source poisons the whole composed query.** The two inbox reads in this part use - `TableNoTracking`; this one uses `repository.Table` (line 24) precisely because the entities must stay - attached to the change tracker for the subsequent `SaveChangesAsync` to see their mutations. The - scoped join goes further and uses the **tracked** `Table` on the [PushNotification](#pushnotification) - side too (line 42), and the comment on lines 35-40 explains why that is load-bearing: in EF Core an - `AsNoTracking` source anywhere in a composed query switches the *whole* query to no-tracking, so the - `UserNotification` rows would come back untracked and the `MarkAsRead` mutations would never be - persisted, making a scoped read-all a silent no-op. Projecting `select un` (line 44) means only - `UserNotification` instances are materialized, so no `PushNotification` is tracked by the join. - `[Rubric §4, DDD]` assesses whether state changes go through the aggregate rather than around it: the - handler never assigns `IsRead` itself, it calls `notification.MarkAsRead(readOnUtc)` (line 54) and the - entity owns the transition, including the already-read early return - (`MMCA.Common/Source/Core/MMCA.Common.Domain/Notifications/UserNotifications/UserNotification.cs:58-67`). - `[Rubric §14, Testability]`: the read timestamp comes from an injected `TimeProvider` - (line 15, used at line 51) rather than `DateTime.UtcNow`, which is what makes the clock substitutable - in a unit test; the domain XML doc states that intent directly (`UserNotification.cs:53-57`). -- **Walkthrough**: get the [UserNotification](#usernotification) repository (line 22); compose the tracked - unread query for this user (lines 24-25); apply the same conditional scope join the count handler uses, - over tracked tables (lines 30-45); materialize through the executor (lines 47-49); take one UTC instant - for the whole batch (line 51) so every row in one call reports the same read time; loop and call - `MarkAsRead` (lines 52-55); persist **only if something changed**, guarded by `if (unread.Count > 0)` - (lines 57-60); return `Result.Success()` (line 62). -- **Why it's built this way**: the `Count > 0` guard makes a repeated "mark all read" a genuine no-op at - the database, which matters because the UI can fire it on every inbox open. This is a load-then-save - loop rather than a set-based `ExecuteUpdate`, which keeps the transition inside the entity (and lets - `MarkAsRead` stay the single place the invariant lives) at the cost of materializing the unread rows. - Since [MarkAllNotificationsReadCommand](#markallnotificationsreadcommand) does not implement - [ITransactional](group-05-cqrs-pipeline.md#itransactional), the single `SaveChangesAsync` is the atomic - unit; see [Group 05](group-05-cqrs-pipeline.md) for the decorator order. + and writes the same two columns [UserNotification](#usernotification)'s `MarkAsRead` owns. +- **Concept introduced**: **a set-based write that deliberately bypasses the change tracker.** The type's + `` (lines 13-17) carry the argument: an inbox can hold an unbounded number of unread rows, and + `MarkAsRead` only sets `IsRead` and `ReadOn` and raises no domain event + (`MMCA.Common/Source/Core/MMCA.Common.Domain/Notifications/UserNotifications/UserNotification.cs:58-67`), + so issuing `UPDATE ... SET IsRead = 1, ReadOn = @now WHERE ...` loses nothing that loading and tracking + each row would have kept. The repository contract spells out the cost of the bypass: no domain events, + global query filters still apply to the predicate, and the update runs on the ambient transaction when + one is active + (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:460-481`). + Audit fields are not lost either: because `ExecuteUpdate` skips the save pipeline's audit interceptor, + `EFRepository` stamps `LastModifiedOn` and `LastModifiedBy` itself unless the caller set them + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:140-154`). + `[Rubric §12, Performance & Scalability]`: one statement regardless of inbox size, with no + materialization. `[Rubric §4, DDD]` assesses whether state changes go through the aggregate rather than + around it: here they go around it on purpose, and the invariant `MarkAsRead` protects (an already-read + row keeps its original `ReadOn`, `UserNotification.cs:60-63`) is preserved by the `!un.IsRead` term in + the predicate, so a read row never matches and its timestamp is never overwritten. + `[Rubric §14, Testability]`: the read timestamp comes from an injected `TimeProvider` (line 20, used at + line 48) rather than `DateTime.UtcNow`, which is what makes the clock substitutable in a unit test. +- **Walkthrough**: get the [UserNotification](#usernotification) write repository via `GetRepository` + (line 27); form the base predicate `un.UserId == command.UserId && !un.IsRead` as an + `Expression>` (lines 29-30); when and only when a non-blank `ScopeKey` + arrived (line 37), copy it into a local (line 39), take the [PushNotification](#pushnotification) + `Table` (line 40) and replace the predicate with one that adds a correlated + `pushNotifications.Any(pn => pn.Id == un.PushNotificationId && (pn.ScopeKey == null || pn.ScopeKey == scopeKey))` + test (lines 42-45), which EF translates to an `EXISTS` subquery; take one UTC instant for the whole + batch (line 48) so every row in one call reports the same read time; call + `repository.ExecuteUpdateAsync` with the predicate and setters for `IsRead = true` and + `ReadOn = readOnUtc` (lines 50-55); return `Result.Success()` (line 57). +- **Why it's built this way**: the scope condition mirrors the unread count for the two reasons the + comment on lines 32-36 gives: a scoped client must not mark rows it cannot see as read, and a no-scope + command keeps the legacy predicate exactly as it was rather than inheriting `PushNotification`'s + soft-delete global query filter. Expressing the scope as an `EXISTS` subquery rather than a join keeps + the update targeted at `UserNotification` alone while the push-notification filter applies to the + subquery exactly as it did to the former join (same comment). A repeated "mark all read" is a cheap + no-op at the database: the predicate matches zero rows and nothing changes, which matters because the + UI can fire it on every inbox open. The returned row count is discarded, since the command reports + success either way. Since [MarkAllNotificationsReadCommand](#markallnotificationsreadcommand) does not + implement [ITransactional](group-05-cqrs-pipeline.md#itransactional), the single `UPDATE` statement is + the atomic unit; see [Group 05](group-05-cqrs-pipeline.md) for the decorator order. - **Where it's used**: injected into [InboxController](#inboxcontroller) as `ICommandHandler` (`NotificationInboxController.cs:33`) and invoked by the `PUT read-all` action (`NotificationInboxController.cs:123`), which returns 204 on @@ -1082,13 +1096,15 @@ without any of the four ever taking the others down, and without a retried reque repository itself for the total via `repository.CountAsync` (line 33, note this one goes through the repository, not the queryable executor, because there is no composed predicate to count); page `TableNoTracking` ordered by - `CreatedOn` descending with `Skip`/`Take` and materialize the entities through the executor - (lines 35-40); run them through `dtoMapper.MapToDTOs` (line 42); build + `CreatedOn` descending then `Id` descending with `Skip`/`Take` and materialize the entities through the + executor (lines 35-41); run them through `dtoMapper.MapToDTOs` (line 43); build [PaginationMetadata](group-01-result-error-handling.md#paginationmetadata) from the total, the clamped - `take` and the floored page number (line 46); return a successful - [PagedCollectionResult](group-01-result-error-handling.md#pagedcollectionresultt) (line 48). -- **Why it's built this way**: history has no per-user state, so there is nothing to join; a dedicated - mapper (versus an inline projection) is used because + `take` and the floored page number (line 47); return a successful + [PagedCollectionResult](group-01-result-error-handling.md#pagedcollectionresultt) (line 49). +- **Why it's built this way**: the `ThenByDescending(n => n.Id)` tie-break (line 38) makes the order + total, so two notifications sent in the same instant cannot repeat or skip across `OFFSET` pages, the + same guarantee the inbox handler gets from its `un.Id` tie-break. History has no per-user state, so + there is nothing to join; a dedicated mapper (versus an inline projection) is used because [PushNotificationDTO](#pushnotificationdto) is a richer contract reused across the push endpoints, and centralizing that mapping keeps the shape consistent (see [ADR-001](https://ivanball.github.io/docs/adr/001-manual-dto-mapping.html) on manual/Mapperly mapping). @@ -1530,7 +1546,7 @@ without any of the four ever taking the others down, and without a retried reque [DisabledUserNotificationExportService](#disabledusernotificationexportservice) instead. ### DependencyInjection -> MMCA.ADC.Notification.Application · `MMCA.ADC.Notification.Application` · `MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.Application/DependencyInjection.cs:12` · Level 9 · class (static) +> MMCA.ADC.Notification.Application · `MMCA.ADC.Notification.Application` · `MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.Application/DependencyInjection.cs:13` · Level 9 · class (static) - **What it is**: the Notification module's application-layer DI composition. Its single extension method `AddModuleNotificationApplication` wires ADC's recipient policy and export service, then pulls @@ -1542,58 +1558,82 @@ without any of the four ever taking the others down, and without a retried reque [UserNotificationExportService](#usernotificationexportservice), and the framework's `AddNotificationApplicationServices()` registration. - **Concept, the `extension(IServiceCollection)` registration block.** DI wiring here uses the C# - preview extension-member syntax (`DependencyInjection.cs:14`), which lets a library add methods + preview extension-member syntax (`DependencyInjection.cs:15`), which lets a library add methods directly to `IServiceCollection` (taught in the [primer](00-primer.md#2-architectural-styles-this-codebase-commits-to)). `[Rubric §3, Clean Architecture]` is visible in the split of responsibility: this method registers the module's *app-specific* choices (attendees as recipients, the export service) and then calls the - framework's `AddNotificationApplicationServices()` (line 31) for the reusable handlers, mapper, + framework's `AddNotificationApplicationServices()` (line 32) for the reusable handlers, mapper, validator, and entity query service, so shared and app-specific wiring stay separate and only the app-specific half needs review when policy changes. -- **Walkthrough**: `AddModuleNotificationApplication` (lines 19-34) discards the unused - `applicationSettings` with `_ = applicationSettings;` (line 21, commented "Reserved for future use", +- **Walkthrough**: `AddModuleNotificationApplication` (lines 20-35) discards the unused + `applicationSettings` with `_ = applicationSettings;` (line 22, commented "Reserved for future use", which also keeps the analyzers quiet about an unused parameter on a fixed signature), registers [AttendeeNotificationRecipientProvider](#attendeenotificationrecipientprovider) as the scoped - `INotificationRecipientProvider` (line 24), registers + `INotificationRecipientProvider` (line 25), registers [UserNotificationExportService](#usernotificationexportservice) as the scoped - `IUserNotificationExportService` for the privacy export (line 28), calls the framework's - `AddNotificationApplicationServices()` (line 31), and returns the collection for chaining (line 33). + `IUserNotificationExportService` for the privacy export (line 29), calls the framework's + `AddNotificationApplicationServices()` (line 32), and returns the collection for chaining (line 34). + Both module registrations use `TryAddScoped` (lines 25 and 29), so they apply only when no + implementation of that interface is registered yet: a registration made before this method runs + (a host override or a test double) wins, and calling the method twice does not stack a second + descriptor. - **Where it's used**: called first thing by the API-layer [DependencyInjection](#dependencyinjection-1)`.AddNotificationModule` (`MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.API/DependencyInjection.cs:32`), which is itself driven by [NotificationModule](#notificationmodule)`.Register`. ### LiveChannelGrpcService -> MMCA.ADC.Notification.Service · `MMCA.ADC.Notification.Service.Grpc` · `MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:22` · Level 1 · class (sealed) +> MMCA.ADC.Notification.Service · `MMCA.ADC.Notification.Service.Grpc` · `MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:31` · Level 1 · class (sealed) - **What it is**: the gRPC **server** endpoint that other services call to fan an ephemeral "live" event out to connected clients. It implements the generated `LiveChannelPushService.LiveChannelPushServiceBase` and delegates each call to the framework's [ILiveChannelPublisher](#ilivechannelpublisher). - **Depends on**: [ILiveChannelPublisher](#ilivechannelpublisher) (injected via the primary - constructor, `MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:22`; + constructor, `MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:31`; in this host it resolves to [SignalRLiveChannelPublisher](#signalrlivechannelpublisher), registered by `AddPushNotifications` because Notification is the host that maps the SignalR [NotificationHub](#notificationhub)), the generated `LiveChannelPushService` base (compiled from the - `.Contracts` `.proto`, `LiveChannelGrpcService.cs:23`), and `Grpc.Core.ServerCallContext`. + `.Contracts` `.proto`, `LiveChannelGrpcService.cs:32`), and `Grpc.Core.ServerCallContext`. - **Concept introduced, the live-channel ingress ([ADR-039](https://ivanball.github.io/docs/adr/039-live-channel-push.html)).** `[Rubric §6, CQRS & Event-Driven]` assesses whether state changes travel as events; `[Rubric §7, Microservices Readiness]` assesses whether cross-process collaboration rides typed transports. The conference-day live layer (LivePolls, SessionQuestions) lives in the Engagement service, but only the Notification host owns the SignalR `IHubContext` that can reach browsers. So Engagement calls THIS gRPC endpoint **post-commit** to hand off an ephemeral event, which the service passes to the local publisher that fans it out over - [NotificationHub](#notificationhub) (doc comment `LiveChannelGrpcService.cs:7-12`). This is the server + [NotificationHub](#notificationhub) (doc comment `LiveChannelGrpcService.cs:10-15`). This is the server half; its client half is [LiveChannelPublisherGrpcAdapter](#livechannelpublishergrpcadapter). -- **Walkthrough**: the single `PushToChannel` override (`LiveChannelGrpcService.cs:26`) null-guards - `request` and `context` (`LiveChannelGrpcService.cs:30-31`), then awaits +- **Walkthrough**: the single `PushToChannel` override (`LiveChannelGrpcService.cs:47`) null-guards + `request` and `context` (`LiveChannelGrpcService.cs:51-52`), validates the three fields + (`LiveChannelGrpcService.cs:54-56`), then awaits `publisher.PublishAsync(request.ChannelKey, request.EventName, request.PayloadJson, context.CancellationToken)` - (`LiveChannelGrpcService.cs:33-35`) and returns an empty `PushToChannelResponse` - (`LiveChannelGrpcService.cs:37`). The channel key, event name, and payload are opaque strings: the - transport relays the event rather than modeling it. + (`LiveChannelGrpcService.cs:58-60`) and returns an empty `PushToChannelResponse` + (`LiveChannelGrpcService.cs:62`). The transport still relays the event rather than modeling it, but it + no longer accepts arbitrary strings: the class doc (`LiveChannelGrpcService.cs:24-29`) calls this the + "bounded payloads" compensating control for an unauthenticated ingress. + - Three caps (`LiveChannelGrpcService.cs:34-36`): `MaxChannelKeyLength` = **200**, + `MaxEventNameLength` = **100**, `MaxPayloadBytes` = **64 KiB** (`64 * 1024`). + - `AllowedChars` (`LiveChannelGrpcService.cs:43-44`), a `SearchValues` over + `abcdefghijklmnopqrstuvwxyz0123456789:.-`, is the one character set shared by channel keys and event + names; its doc comment (`LiveChannelGrpcService.cs:38-42`) notes it covers every producer format, + [NotificationScopeKey](#notificationscopekey) keys such as `event:1` and the live-layer event names + such as `poll.results-changed`. + - `ValidateName` (`LiveChannelGrpcService.cs:65-81`) rejects a blank value, one over its cap, and one + containing any character outside `AllowedChars` (`ContainsAnyExcept`, `LiveChannelGrpcService.cs:77`). + `ValidatePayload` (`LiveChannelGrpcService.cs:83-89`) rejects a payload whose UTF-8 byte count + exceeds the cap; a null payload passes. + - Every refusal is an `RpcException` with `StatusCode.InvalidArgument` built by `InvalidArgument` + (`LiveChannelGrpcService.cs:91-92`), thrown before the publisher is reached, with messages formatted + under `CultureInfo.InvariantCulture`. `[Rubric §11, Security]`: input bounding is what limits the + blast radius of a surface that cannot authenticate its caller. - **Why it's built this way (security posture)**: there is deliberately **no `[Authorize]`**, and the - endpoint is mapped without `RequireAuthorization` - (`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:275`). `[Rubric §11, Security]`: - the doc comment (`LiveChannelGrpcService.cs:13-20`) gives two reasons. First, this surface is reachable + endpoint is mapped with an explicit `.AllowAnonymous()` + (`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:285`). The comment above it + (`Program.cs:281-284`) says why the opt-out is stated rather than implied: the framework's fallback + authorization policy gates any endpoint that declares nothing (SEC-Common-16), which would have silenced + the whole live layer. `[Rubric §11, Security]`: + the doc comment (`LiveChannelGrpcService.cs:16-23`) gives two reasons. First, this surface is reachable only on the internal service network (a dedicated internal port in Azure Container Apps, never routed by the Gateway), the same posture as the other internal gRPC services (it names `BookmarkCountsGrpcService`). Second, authorization is **not addable** here: the publishing caller is @@ -1601,10 +1641,10 @@ without any of the four ever taking the others down, and without a retried reque background drain, which runs with no `HttpContext` and therefore forwards no bearer token. Transport-wise it rides the [ADR-012](https://ivanball.github.io/docs/adr/012-grpc-host-transport.html) mixed-endpoint profile: Notification keeps its default endpoint `Http1AndHttp2` for SignalR WebSockets - (`Program.cs:75`) and serves this h2c gRPC ingress on a dedicated `Http2`-only endpoint named `grpc` + (`Program.cs:73`) and serves this h2c gRPC ingress on a dedicated `Http2`-only endpoint named `grpc` (port 8081 in the container, 5996 locally), declared in the `Kestrel:Endpoints` config section rather - than in code; the full reasoning is spelled out at `Program.cs:59-74`. -- **Where it's used**: mapped by the Notification service's `Program.cs` (`Program.cs:275`); invoked by + than in code; the full reasoning is spelled out at `Program.cs:58-72`. +- **Where it's used**: mapped by the Notification service's `Program.cs` (`Program.cs:285`); invoked by [LiveChannelPublisherGrpcAdapter](#livechannelpublishergrpcadapter) running inside Engagement. --- @@ -1660,7 +1700,7 @@ without any of the four ever taking the others down, and without a retried reque [NullLiveChannelPublisher](#nulllivechannelpublisher) default (`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Contracts/DependencyInjection.cs:48`). The one caller today is the Engagement service's composition root - (`MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:283`). + (`MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:285`). --- @@ -1693,7 +1733,7 @@ without any of the four ever taking the others down, and without a retried reque best-effort by design). - **Where it's used**: registered as the framework default with `services.TryAddTransient()` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:316`) so the port is always + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:326`) so the port is always resolvable; `AddPushNotifications` adds the SignalR implementation over it (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:66`), and in ADC Engagement's composition root `services.Replace(...)` overwrites it with the [LiveChannelPublisherGrpcAdapter](#livechannelpublishergrpcadapter) @@ -1724,7 +1764,7 @@ without any of the four ever taking the others down, and without a retried reque `AddPushNotifications()`. The send handler always calls the port; whether anything reaches a browser is a composition-root decision. - **Where it's used**: registered as the default `IPushNotificationSender` by `AddInfrastructure` - (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:315`); superseded by the + (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:325`); superseded by the SignalR sender in any host that calls `AddPushNotifications` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:65`). @@ -1754,11 +1794,14 @@ without any of the four ever taking the others down, and without a retried reque plus one body member. - `Request` (`SendPushNotificationCommand.cs:22`) is the validated DTO; `SentByUserId` (`SendPushNotificationCommand.cs:23`) is the audit/authorization context. - - `DedupKey` (`SendPushNotificationCommand.cs:32`), an `init`-only `string?`, is the optional + - `DedupKey` (`SendPushNotificationCommand.cs:35`), an `init`-only `string?`, is the optional deduplication key, typically the caller's `Idempotency-Key` header. Its doc comment - (`SendPushNotificationCommand.cs:25-31`) states the contract: when present, a send whose key has + (`SendPushNotificationCommand.cs:25-34`) states the contract: when present, a send whose key has already been seen returns the existing notification instead of creating a second one and sending - again; when null (the default every existing caller gets) the send behaves exactly as before. + again; when null (the default every existing caller gets) the send behaves exactly as before. The + key is **scoped to the sender** (`SendPushNotificationCommand.cs:31-33`): the handler stores and + looks up a SHA-256 of `{SentByUserId}:{key}`, never the raw client key, so two senders reusing one + client key never collide, and the stored value always fits the column. `[Rubric §29, Resilience & Business Continuity]` assesses whether a retry is safe: this property is what makes a retried broadcast at-most-once at the delivery level (see the matching logic in [SendPushNotificationHandler](#sendpushnotificationhandler)). It complements, and is distinct from, @@ -1803,7 +1846,7 @@ without any of the four ever taking the others down, and without a retried reque `[Rubric §17, DevOps & Deployment]`: host, port, credentials, and the default from/to addresses come from configuration bound at startup by `AddInfrastructure` (`services.AddOptions().Bind(configuration.GetSection(SmtpSettings.SectionName))`, - `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:92-93`), never hard-coded, + `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:98-99`), never hard-coded, so the same code targets a real relay in production and a local SMTP container in development. - `[Rubric §11, Security]` (SEC-Common-54): TLS is no longer read verbatim from `SmtpSettings.EnableSsl`. The container-preferred constructor resolves it through @@ -1844,7 +1887,7 @@ without any of the four ever taking the others down, and without a retried reque would be a breaking public-API change. Unlike push, email has **no** null-object default: `SmtpEmailSender` itself is what `AddInfrastructure` registers as `IEmailSender` (`services.TryAddTransient()`, - `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:314`), so a host that never + `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:324`), so a host that never configures SMTP settings still resolves a real sender that will fail at send time rather than silently no-op. - **Where it's used**: injected as `IEmailSender` by callers (the port, not this class); the transient @@ -1911,40 +1954,40 @@ without any of the four ever taking the others down, and without a retried reque drifting on a magic string. - `ChannelKeyMatchTimeout` (1 second) and the `ChannelKeyRegexCache` (`StringComparer.Ordinal`) back the validation helper. - - `ConnectionsPerUser` (`NotificationHub.cs:264`): a static `ConcurrentDictionary`, + - `ConnectionsPerUser` (`NotificationHub.cs:51`): a static `ConcurrentDictionary`, `StringComparer.Ordinal`, tracking live connection count per user identifier on this replica. - - `OnConnectedAsync` (`NotificationHub.cs:267-285`): when `MaxConnectionsPerUser` is positive and + - `OnConnectedAsync` (`NotificationHub.cs:54-72`): when `MaxConnectionsPerUser` is positive and `Context.UserIdentifier` is set, atomically increments the count with `AddOrUpdate` - (`:274`); if the new count exceeds the cap, it gives the slot straight back via `Decrement` - (`:279`, so a refused connection cannot keep counting against the user and lock it out once the + (`:61`); if the new count exceeds the cap, it gives the slot straight back via `Decrement` + (`:66`, so a refused connection cannot keep counting against the user and lock it out once the aborted ones drain) and throws `HubException("Too many concurrent connections for this account.")` - (`:280`); otherwise it calls `base.OnConnectedAsync()` (`:284`). - - `OnDisconnectedAsync` (`NotificationHub.cs:288-297`): decrements the caller's count, then calls + (`:67`); otherwise it calls `base.OnConnectedAsync()` (`:71`). + - `OnDisconnectedAsync` (`NotificationHub.cs:75-84`): decrements the caller's count, then calls `base.OnDisconnectedAsync(exception)`. - - `Decrement(string userId)` (`NotificationHub.cs:303-322`): a lock-free loop over + - `Decrement(string userId)` (`NotificationHub.cs:90-109`): a lock-free loop over `TryGetValue`/`TryRemove`/`TryUpdate` that removes the entry entirely at zero, so the dictionary tracks only users who are actually connected rather than everyone who ever was. - - `JoinChannelAsync(string channelKey)` (`NotificationHub.cs:332`): attributed + - `JoinChannelAsync(string channelKey)` (`NotificationHub.cs:119`): attributed `[HubMethodName(JoinChannelMethod)]`, it validates the key via `EnsureValidChannelKey` - (`:334`), then awaits `EnsureAuthorizedForChannelAsync(channelKey)` (`:335`) before calling - `Groups.AddToGroupAsync(Context.ConnectionId, channelKey, Context.ConnectionAborted)` (`:340-341`). - - `LeaveChannelAsync(string channelKey)` (`NotificationHub.cs:349`): + (`:121`), then awaits `EnsureAuthorizedForChannelAsync(channelKey)` (`:122`) before calling + `Groups.AddToGroupAsync(Context.ConnectionId, channelKey, Context.ConnectionAborted)` (`:127-128`). + - `LeaveChannelAsync(string channelKey)` (`NotificationHub.cs:136`): `[HubMethodName(LeaveChannelMethod)]`, validates then `Groups.RemoveFromGroupAsync(...)` with the same - connection token (`:354-355`). Leaving is not re-checked against the authorizer. - - `EnsureAuthorizedForChannelAsync` (`NotificationHub.cs:362-377`): a no-op when `joinAuthorizer` is + connection token (`:141-142`). Leaving is not re-checked against the authorizer. + - `EnsureAuthorizedForChannelAsync` (`NotificationHub.cs:149-164`): a no-op when `joinAuthorizer` is `null`; otherwise awaits `joinAuthorizer.CanJoinAsync(Context.User, channelKey, Context.ConnectionAborted)` and throws `HubException("Not authorized for this channel.")` on a `false` result, so a caller not entitled to the channel is refused before the group membership is created and never receives a single published payload. - Both `JoinChannelAsync`/`LeaveChannelAsync` take **no `CancellationToken` parameter**, and the comment - at `NotificationHub.cs:337-339` explains why: a hub method signature is the client-visible RPC + at `NotificationHub.cs:124-126` explains why: a hub method signature is the client-visible RPC contract bound by SignalR's dispatcher, so the cancellation token comes from the connection (`Context.ConnectionAborted`) instead, and the repo's `CancellationTokenConventionTests` carry an explicit exemption for it. `[Rubric §15, Best Practices & Code Quality]`: the convention is enforced by a test, and the deviation is documented at the deviation site. - - `EnsureValidChannelKey` (`NotificationHub.cs:379`): `GetOrAdd`s the cached `Regex` for + - `EnsureValidChannelKey` (`NotificationHub.cs:166`): `GetOrAdd`s the cached `Regex` for `settings.Value.ChannelKeyPattern`; an empty or non-matching key throws - `HubException("Invalid channel key.")` (`:387`), which SignalR surfaces to the caller rather than + `HubException("Invalid channel key.")` (`:174`), which SignalR surfaces to the caller rather than tearing down the connection. - **Why it's built this way**: routing delivery through `IHubContext` instead of hub instance methods lets the framework construct and send messages from anywhere (background senders, a gRPC ingress) @@ -1962,7 +2005,7 @@ without any of the four ever taking the others down, and without a retried reque path is `/hubs/notifications` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Notifications/Push/PushNotificationSettings.cs:17`), and in ADC the Notification service calls it at - `MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:289`. It is driven by + `MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:276`. It is driven by [SignalRPushNotificationSender](#signalrpushnotificationsender) (per-user notification delivery) and [SignalRLiveChannelPublisher](#signalrlivechannelpublisher) (ephemeral channel events), both via `IHubContext`. `AddPushNotifications` registers the `IUserIdProvider` @@ -2134,7 +2177,7 @@ without any of the four ever taking the others down, and without a retried reque --- ### SendPushNotificationHandler -> MMCA.Common.Application · `MMCA.Common.Application.Notifications.PushNotifications.UseCases.Send` · `MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:27` · Level 9 · class (sealed partial) +> MMCA.Common.Application · `MMCA.Common.Application.Notifications.PushNotifications.UseCases.Send` · `MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:32` · Level 9 · class (sealed partial) - **What it is**: the command handler for the push-notification broadcast. It short-circuits duplicate sends by deduplication key, resolves recipients, persists a sender-side audit aggregate plus one inbox @@ -2147,7 +2190,7 @@ without any of the four ever taking the others down, and without a retried reque by [ADR-044](https://ivanball.github.io/docs/adr/044-native-push-delivery.html)), [PushNotificationDTOMapper](#pushnotificationdtomapper) (the success-payload mapper), and `ILogger<>` (all injected via the primary constructor, - `MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:27-33`); + `MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:32-38`); the persisted aggregates are [PushNotification](#pushnotification) and [UserNotification](#usernotification). Implements [ICommandHandler](group-05-cqrs-pipeline.md#icommandhandlerin-tcommand-tresult) @@ -2158,72 +2201,79 @@ without any of the four ever taking the others down, and without a retried reque separately performs two best-effort real-time pushes (SignalR, then native OS push). These are different reliability tiers on purpose: the aggregate and inbox rows are the **durable** record a recipient can retrieve later, while the SignalR and native pushes are the **immediate** deliveries that - an offline user may miss. The class doc (`SendPushNotificationHandler.cs:17-25`) states the atomicity + an offline user may miss. The class doc (`SendPushNotificationHandler.cs:20-30`) states the atomicity contract that ties the durable half together: all three saves are one unit because [SendPushNotificationCommand](#sendpushnotificationcommand) is `ITransactional`. - **Walkthrough** (teaching order) - 1. **deduplication gate** (`SendPushNotificationHandler.cs:40-51`): the command's + 1. **deduplication gate** (`SendPushNotificationHandler.cs:45-60`): the command's [DedupKey](#sendpushnotificationcommand) is normalized so whitespace counts as absent - (`SendPushNotificationHandler.cs:42`, "a blank header cannot claim the single empty key"); when a - key is present, `FindByDedupKeyAsync` looks for an already-persisted notification and, on a hit, + (`SendPushNotificationHandler.cs:49-51`, "a blank header cannot claim the single empty key"), and a + present key is replaced by its sender-scoped hash (see `SenderScopedDedupKey` below), so another + caller reusing the same client key can neither suppress this send nor be handed this sender's + notification (comment `SendPushNotificationHandler.cs:46-48`); when a key is present, `FindByDedupKeyAsync` looks for an already-persisted notification and, on a hit, logs and returns the existing one mapped to a DTO **without sending again** - (`SendPushNotificationHandler.cs:45-50`). - 2. **resolve recipients** (`SendPushNotificationHandler.cs:54-55`) via the app-specific + (`SendPushNotificationHandler.cs:54-59`). + 2. **resolve recipients** (`SendPushNotificationHandler.cs:63-64`) via the app-specific [INotificationRecipientProvider](#inotificationrecipientprovider) (in ADC, [AttendeeNotificationRecipientProvider](#attendeenotificationrecipientprovider)). An empty set short-circuits to a `Result.Failure` with an [Error](group-01-result-error-handling.md#error) - `Validation` code `PushNotification.NoRecipients` (`SendPushNotificationHandler.cs:57-63`), before + `Validation` code `PushNotification.NoRecipients` (`SendPushNotificationHandler.cs:66-72`), before any rows are written. - 3. **create the audit aggregate** (`SendPushNotificationHandler.cs:66-72`) via + 3. **create the audit aggregate** (`SendPushNotificationHandler.cs:75-81`) via `PushNotification.Create(title, body, sentByUserId, recipientIds.Count, dedupKey, scopeKey)`; - propagate errors on failure (`SendPushNotificationHandler.cs:73-76`), then add to the repository - (`SendPushNotificationHandler.cs:79-80`). This is where the aggregate's + propagate errors on failure (`SendPushNotificationHandler.cs:82-85`), then add to the repository + (`SendPushNotificationHandler.cs:88-89`). This is where the aggregate's [PushNotificationCreated](#pushnotificationcreated) domain event is captured to the outbox ([ADR-003](https://ivanball.github.io/docs/adr/003-outbox-dual-dispatch.html)). - 4. **first save, with a race requery** (`SendPushNotificationHandler.cs:82-113`): the save is wrapped + 4. **first save, with a race requery** (`SendPushNotificationHandler.cs:91-122`): the save is wrapped in a `try/catch (Exception)` under a justified `#pragma warning disable CA1031` - (`SendPushNotificationHandler.cs:86-88`). The long comment - (`SendPushNotificationHandler.cs:90-101`) is the teaching point: the dedup lookup in step 1 is a + (`SendPushNotificationHandler.cs:95-97`). The long comment + (`SendPushNotificationHandler.cs:99-110`) is the teaching point: the dedup lookup in step 1 is a check-then-act, so two concurrent retries of the same send both pass it and the loser only fails here, on the insert, against the filtered unique index on `DedupKey`. The catch requeries by key - with `CancellationToken.None` (`SendPushNotificationHandler.cs:104`, so a cancelled save can still + with `CancellationToken.None` (`SendPushNotificationHandler.cs:113`, so a cancelled save can still be classified) and, if a winner now exists, returns it - (`SendPushNotificationHandler.cs:105-109`); anything else rethrows untouched - (`SendPushNotificationHandler.cs:112`) so a genuine persistence fault still reaches the exception + (`SendPushNotificationHandler.cs:114-118`); anything else rethrows untouched + (`SendPushNotificationHandler.cs:121`) so a genuine persistence fault still reaches the exception middleware. The broad catch is deliberate: Application has no EF Core dependency under the layer rule, so `DbUpdateException` is not a type this file can name, and the requery is what narrows it. Same shape as [EfInboxStore](group-04-events-outbox.md#efinboxstore)'s `MessageId` unique-index handling. - 5. **durable inbox** (`SendPushNotificationHandler.cs:115-123`): one + 5. **durable inbox** (`SendPushNotificationHandler.cs:124-132`): one `UserNotification.Create(recipientId, notification.Id)` row per recipient, added and saved. This is what lets a user retrieve a notification they missed while offline. - 6. **best-effort SignalR delivery** (`SendPushNotificationHandler.cs:125-143`): + 6. **best-effort SignalR delivery** (`SendPushNotificationHandler.cs:134-152`): `pushNotificationSender.SendToUsersAsync(...)` inside a `try/catch` with its own justified `CA1031` - suppression (`SendPushNotificationHandler.cs:137-139`). A delivery failure is **non-fatal**: success - calls `notification.MarkAsSent()` plus an info log (`SendPushNotificationHandler.cs:134-135`), + suppression (`SendPushNotificationHandler.cs:146-148`). A delivery failure is **non-fatal**: success + calls `notification.MarkAsSent()` plus an info log (`SendPushNotificationHandler.cs:143-144`), failure calls `notification.MarkAsFailed()` plus an error log - (`SendPushNotificationHandler.cs:141-142`); the failure becomes recorded *status*, not a thrown + (`SendPushNotificationHandler.cs:150-151`); the failure becomes recorded *status*, not a thrown exception. - 7. **best-effort native push** (`SendPushNotificationHandler.cs:145-162`, + 7. **best-effort native push** (`SendPushNotificationHandler.cs:154-171`, [ADR-044](https://ivanball.github.io/docs/adr/044-native-push-delivery.html)): `nativePushSender.SendToUsersAsync(...)` in a third `try/catch` with its own suppression - (`SendPushNotificationHandler.cs:157-159`). This is the OS-level channel that can reach devices - whose app is backgrounded or killed (comment `SendPushNotificationHandler.cs:145-148`). It is + (`SendPushNotificationHandler.cs:166-168`). This is the OS-level channel that can reach devices + whose app is backgrounded or killed (comment `SendPushNotificationHandler.cs:154-157`). It is **purely additive**: the SignalR leg above already decided the audit status, so a native-push - failure only logs a warning (`LogNativePushFailed`, `SendPushNotificationHandler.cs:161`) and never + failure only logs a warning (`LogNativePushFailed`, `SendPushNotificationHandler.cs:170`) and never touches `Status`. The default [NullNativePushSender](group-14-module-system-composition.md#nullnativepushsender) keeps this a no-op until a notification hub is configured. - 8. **persist final status** (`SendPushNotificationHandler.cs:164`) and **return** the mapped DTO - (`SendPushNotificationHandler.cs:166`). - - `FindByDedupKeyAsync` (`SendPushNotificationHandler.cs:174-183`) resolves the read repository from - the unit of work (`unitOfWork.GetReadRepository<...>()`, `SendPushNotificationHandler.cs:176`), never + 8. **persist final status** (`SendPushNotificationHandler.cs:173`) and **return** the mapped DTO + (`SendPushNotificationHandler.cs:175`). + - `SenderScopedDedupKey` (`SendPushNotificationHandler.cs:185-187`) derives the stored key as + `Convert.ToHexString(SHA256.HashData(...))` over the invariant-culture string + `{sentByUserId}:{clientKey}`, so the persisted value is always **64** hex characters (within the + column) whatever the client sent (doc comment `SendPushNotificationHandler.cs:178-184`). `[Rubric §11, + Security]`: the client key alone no longer identifies a notification across senders. + - `FindByDedupKeyAsync` (`SendPushNotificationHandler.cs:194-203`) resolves the read repository from + the unit of work (`unitOfWork.GetReadRepository<...>()`, `SendPushNotificationHandler.cs:196`), never an injected [IRepository](group-07-persistence-ef-core.md#irepositorytentity-tidentifiertype), so the lookup runs against the same data source as the write (its doc comment says exactly that, - `SendPushNotificationHandler.cs:169-173`). + `SendPushNotificationHandler.cs:189-193`). - Five source-generated `[LoggerMessage]` methods close the file - (`SendPushNotificationHandler.cs:185-198`): sent (Information), delivery-failed (Error), + (`SendPushNotificationHandler.cs:205-218`): sent (Information), delivery-failed (Error), native-failed (Warning), dedup hit (Information), and dedup race requery (Information). `[Rubric §13, Observability]`. - **Why it's built this way**: shipping the whole feature in the *framework* means both ADC and Store get @@ -2241,11 +2291,13 @@ without any of the four ever taking the others down, and without a retried reque ADC by the Notification DI facade); the real-time legs land on connected clients through [NotificationHub](#notificationhub) and, for native push, through the configured OS notification hub. - **Caveats / not-in-source**: the three `SaveChangesAsync` calls - (`SendPushNotificationHandler.cs:84`, `:122`, `:163`) are separate saves, not separate transactions: + (`SendPushNotificationHandler.cs:93`, `:132`, `:173`) are separate saves, not separate transactions: atomicity comes from the ambient transaction the Transactional decorator opens because the command implements `ITransactional`, so the decorator, not this file, is where the commit happens. A - consequence the class doc accepts explicitly (`SendPushNotificationHandler.cs:17-25`) is that both - sender calls run inside that transaction. There is still no automatic redelivery of a failed push: the + consequence the class doc accepts explicitly (`SendPushNotificationHandler.cs:27-29`) is that both + sender calls run inside that unit, so a transient fault on the final status save re-runs them under + the execution strategy: delivery is **at-least-once** for the live channels (SignalR and native) and + exactly-once for the inbox rows. There is still no automatic redelivery of a failed push: the outcome is recorded, not re-attempted. --- @@ -2262,7 +2314,9 @@ without any of the four ever taking the others down, and without a retried reque `MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/UserNotificationExportGrpcService.cs:27`), the generated `UserNotificationExportService.UserNotificationExportServiceBase` (compiled from the `.Contracts` `.proto`, `UserNotificationExportGrpcService.cs:28`), and `Grpc.Core.ServerCallContext`. - Uses `System.Globalization.CultureInfo` for invariant-culture date formatting, and projects into + Formats timestamps through the framework's + [GrpcWireFormat](group-13-grpc-contracts.md#grpcwireformat) (`MMCA.Common.Grpc`, imported at + `UserNotificationExportGrpcService.cs:4`), and projects into [UserNotificationExportItemDTO](#usernotificationexportitemdto) shapes on the far side. - **Concept introduced, cross-service data-subject export over internal gRPC.** `[Rubric §30, Compliance, Privacy & Data Governance]` assesses whether the system can satisfy a subject-access @@ -2272,8 +2326,10 @@ without any of the four ever taking the others down, and without a retried reque that slice without a cross-database query. `[Rubric §7, Microservices Readiness]` and `[Rubric §9, API & Contract Design]`: the export contract is a versioned `.proto` shared through the `.Contracts` package, the same extraction pattern as the live-channel ingress. `[Rubric §27, i18n]`: timestamps are - serialized with the round-trip `"O"` format under `CultureInfo.InvariantCulture` - (`UserNotificationExportGrpcService.cs:47` and `:51`) so the export is locale-stable. + serialized with the round-trip `"O"` format under `CultureInfo.InvariantCulture` by + `GrpcWireFormat.FormatUtc` + (`MMCA.Common/Source/Presentation/MMCA.Common.Grpc/GrpcWireFormat.cs:42-43`, called at + `UserNotificationExportGrpcService.cs:47` and `:49`) so the export is locale-stable. - **Walkthrough**: the single `GetUserNotificationExport` override (`UserNotificationExportGrpcService.cs:31`) null-guards `request` and `context` (`UserNotificationExportGrpcService.cs:35-36`), then awaits @@ -2281,18 +2337,22 @@ without any of the four ever taking the others down, and without a retried reque (`UserNotificationExportGrpcService.cs:38-40`) to get the in-process items. It builds a `GetUserNotificationExportResponse` (`UserNotificationExportGrpcService.cs:42`) and `AddRange`s a projection of each item into a `UserNotificationExportItem` - (`UserNotificationExportGrpcService.cs:43-56`): `NotificationId`, `Title`, `SentOn`, `IsRead`, `ReadOn` - (`string.Empty` when the notification is unread, `UserNotificationExportGrpcService.cs:49-51`), and - `ScopeKey` (also `string.Empty` when null, `UserNotificationExportGrpcService.cs:55`, because proto3 - has no null string, per the comment at `:53-54`). Both timestamps pass through - `DateTime.SpecifyKind(..., DateTimeKind.Utc)` before `ToString("O", ...)`: the doc comment - (`UserNotificationExportGrpcService.cs:20-25`) explains why, SQL Server hands back - `DateTimeKind.Unspecified` values and the `"O"` format omits the `Z` marker for that kind, so the - stamp only restores the marker the wire contract promises (the stored values are already UTC). + (`UserNotificationExportGrpcService.cs:43-54`): `NotificationId`, `Title`, `SentOn` via + `GrpcWireFormat.FormatUtc` (`UserNotificationExportGrpcService.cs:47`), `IsRead`, `ReadOn` via + `GrpcWireFormat.FormatUtcOrEmpty` (`string.Empty` when the notification is unread, + `UserNotificationExportGrpcService.cs:49`; `GrpcWireFormat.cs:51-52`), and `ScopeKey` (also + `string.Empty` when null, `UserNotificationExportGrpcService.cs:53`, because proto3 has no null + string, per the comment at `:51-52`). `FormatUtc` stamps `DateTimeKind.Utc` with + `DateTime.SpecifyKind` before `ToString("O", ...)`: the class doc + (`UserNotificationExportGrpcService.cs:20-25`, restated once for every contract in + `GrpcWireFormat.cs:11-17`) explains why, SQL Server hands back `DateTimeKind.Unspecified` values and + the `"O"` format omits the `Z` marker for that kind, so the stamp only restores the marker the wire + contract promises (the stored values are already UTC). Writing the format once in the framework means + this server and its client adapter cannot drift apart. - **Why it's built this way (security posture)**: unlike [LiveChannelGrpcService](#livechannelgrpcservice), this endpoint **requires authorization**: it is mapped with `.RequireAuthorization()` - (`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:306`) and the class doc says why + (`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:293`) and the class doc says why (`UserNotificationExportGrpcService.cs:14-19`). `[Rubric §11, Security]`, `[Rubric §30, Compliance, Privacy & Data Governance]`: internal-only ingress is **not sufficient** here because the response carries personal data keyed by a raw `UserId`, so the calling service forwards the end user's JWT via @@ -2301,7 +2361,7 @@ without any of the four ever taking the others down, and without a retried reque endpoint as the live-channel ingress ([ADR-012](https://ivanball.github.io/docs/adr/012-grpc-host-transport.html) mixed-endpoint profile, `UserNotificationExportGrpcService.cs:11-13`). -- **Where it's used**: mapped by the Notification service's `Program.cs` (`Program.cs:306`); the wire is +- **Where it's used**: mapped by the Notification service's `Program.cs` (`Program.cs:293`); the wire is dialed by its client half [UserNotificationExportServiceGrpcAdapter](#usernotificationexportservicegrpcadapter), which runs inside the Identity service's export aggregator and stitches this Notification slice into the full @@ -2326,7 +2386,8 @@ without any of the four ever taking the others down, and without a retried reque constructor, `MMCA.ADC/Source/Services/MMCA.ADC.Notification.Contracts/UserNotificationExportServiceGrpcAdapter.cs:17-18`); [UserNotificationExportItemDTO](#usernotificationexportitemdto) (the returned shape); the - `UserIdentifierType` alias; `System.Globalization` for invariant parsing. Its server counterpart is + `UserIdentifierType` alias; [GrpcWireFormat](group-13-grpc-contracts.md#grpcwireformat) for parsing + the wire timestamps. Its server counterpart is [UserNotificationExportGrpcService](#usernotificationexportgrpcservice). - **Concept**: the mirror image of [LiveChannelPublisherGrpcAdapter](#livechannelpublishergrpcadapter)'s error policy, and the contrast is @@ -2349,20 +2410,25 @@ without any of the four ever taking the others down, and without a retried reque with a `GetUserNotificationExportRequest { UserId = userId }`, the deadline, and the caller's token (`:30-36`). No `try/catch`: failures surface. - The projection (`:38-49`) maps each wire item back into a - [UserNotificationExportItemDTO](#usernotificationexportitemdto) with a collection expression. Two - fields undo the proto3 encoding: `ReadOn` becomes `null` when the string is empty (`:44`), and - `ScopeKey` likewise (`:48`), with the comment at `:46-47` recording that a peer replica predating the - field and a genuinely unscoped notification both arrive as the empty string and mean the same thing. - - `ParseRoundtripUtc(value)` (`:59-63`) is the counterpart of the server's `"O"` formatting: - `DateTime.Parse` under `CultureInfo.InvariantCulture` with - `DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal`. The comment block (`:52-58`) is - worth reading in full: those two flags defend against a peer replica that still emits the - marker-less form during a rolling deploy (without them a suffix-less value parses as - `Kind=Unspecified`, and `AssumeUniversal` alone yields `Kind=Local`), while a `Z`-suffixed value - takes the same path and keeps its instant. `DateTimeStyles.RoundtripKind` is deliberately absent - because `DateTime.Parse` rejects it alongside either flag with an `ArgumentException`, and its job - (preserving a non-UTC kind) is the opposite of what this contract wants. `[Rubric §15, Best Practices - & Code Quality]`: a subtle BCL interaction is documented at the point of use. + [UserNotificationExportItemDTO](#usernotificationexportitemdto) with a collection expression. + `SentOn` is read by `GrpcWireFormat.ParseUtc` (`:42`). Two fields undo the proto3 encoding: `ReadOn` + becomes `null` when the string is empty through `GrpcWireFormat.ParseUtcOrNull` (`:44`), and + `ScopeKey` likewise by an inline length check (`:48`), with the comment at `:46-47` recording that a + peer replica predating the field and a genuinely unscoped notification both arrive as the empty + string and mean the same thing. + - The parsing itself lives in the framework, the counterpart of the server's `"O"` formatting: + `ParseUtc` is `DateTime.Parse` under `CultureInfo.InvariantCulture` with + `DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal` + (`MMCA.Common/Source/Presentation/MMCA.Common.Grpc/GrpcWireFormat.cs:37`, `:61-62`). The comment + there (`GrpcWireFormat.cs:32-36`) is worth reading in full: those two flags defend against a peer + replica that still emits the marker-less form during a rolling deploy (without them a suffix-less + value parses as `Kind=Unspecified`, and `AssumeUniversal` alone yields `Kind=Local`), while a + `Z`-suffixed value takes the same path and keeps its instant. `DateTimeStyles.RoundtripKind` is + deliberately absent because `DateTime.Parse` rejects it alongside either flag with an + `ArgumentException`, and its job (preserving a non-UTC kind) is the opposite of what this contract + wants. A malformed value throws `FormatException` (`GrpcWireFormat.cs:24-25`), which this adapter + lets propagate like any other failure. `[Rubric §15, Best Practices & Code Quality]`: a subtle BCL + interaction is documented once, beside the shared helper both ends call. - **Why it's built this way**: keeping the class `internal` (`:17`) means nothing outside the `.Contracts` package can bind to the concretion; consumers get it only through the DI helper below, always behind the interface. The rolling-deploy tolerance in the parser and the two empty-string mappings are what @@ -2373,7 +2439,7 @@ without any of the four ever taking the others down, and without a retried reque `services.Replace(...)` (`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Contracts/DependencyInjection.cs:84`); the one caller today is the Identity service's composition root - (`MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:317`), whose + (`MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:316`), whose [ExportUserDataHandler](group-24-identity-module.md#exportuserdatahandler) consumes the interface. --- @@ -2439,9 +2505,9 @@ heading.)* best-effort per section, so if this peer stays unreachable after the resilience pipeline the Identity handler marks the Notifications section unavailable instead of failing the whole export. - **Where it's used**: `AddNotificationLiveChannelClient` is called by the Engagement service's - application pipeline (`MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:283`); + application pipeline (`MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:285`); `AddNotificationUserExportClient` by the Identity service's - (`MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:317`). The matching AppHost references + (`MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:316`). The matching AppHost references that inject the `services__notification__grpc__0` entry are `engagementService.WithReference(notificationService)` (`MMCA.ADC/Source/Hosting/MMCA.ADC.AppHost/Program.cs:282`) and @@ -2982,7 +3048,7 @@ heading.)* response, but only while the cached entry survives. So the action ALSO reads the raw header itself and carries it into the domain as the command's `DedupKey` (lines 60-69), where a key that has already been seen returns the existing notification instead of sending a second time - (`MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationCommand.cs:25-32`). + (`MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationCommand.cs:25-35`). The filter protects the response; the `DedupKey` protects delivery when the cache is cold, evicted, or degraded. See [ADR-024](https://ivanball.github.io/docs/adr/024-push-notifications.html). `[Rubric §29, Resilience & Business Continuity]` assesses whether a retried or replayed request can diff --git a/docs-src/onboarding/group-11-navigation-populators.md b/docs-src/onboarding/group-11-navigation-populators.md index ea4d51b8..4f1d5b36 100644 --- a/docs-src/onboarding/group-11-navigation-populators.md +++ b/docs-src/onboarding/group-11-navigation-populators.md @@ -192,13 +192,13 @@ Two architectural threads are worth holding onto as you read the per-type sectio subsystem is *why* the database-per-service split is feasible without rewriting query code (`[Rubric §7, Microservices Readiness]`): when a relationship's ends move to different sources, the EF model's [`CrossDataSourceDegradeConvention`](group-07-persistence-ef-core.md#crossdatasourcedegradeconvention) -(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:33`) +(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:34`) drops the relationship and ignores the CLR navigation members, routing runtime navigation "through the existing `INavigationPopulator` batch-loading machinery instead" -(`CrossDataSourceDegradeConvention.cs:15-17`), the metadata provider starts reporting that navigation +(`CrossDataSourceDegradeConvention.cs:16-18`), the metadata provider starts reporting that navigation as unsupported, and the populator path picks it up automatically. The application code that issued the query never changes, and when every entity resolves to the same physical source the convention is a -structural no-op (`CrossDataSourceDegradeConvention.cs:25-29`). Second, the design is a clean +structural no-op (`CrossDataSourceDegradeConvention.cs:26-30`). Second, the design is a clean illustration of keeping policy out of the domain: the *what* (a `[Navigation]` marker) lives in Domain, the *whether* (supported versus unsupported) is computed in Application from an Infrastructure capability check, and the *how* (batch SQL) is a static helper. Three responsibilities, three layers, diff --git a/docs-src/onboarding/group-12-api-hosting-mapping.md b/docs-src/onboarding/group-12-api-hosting-mapping.md index afb210f7..533cef47 100644 --- a/docs-src/onboarding/group-12-api-hosting-mapping.md +++ b/docs-src/onboarding/group-12-api-hosting-mapping.md @@ -59,7 +59,7 @@ registers the Problem Details service and the five exception handlers in most-sp (`:154-158`), `AddServerAuthSessionCookie` (`DependencyInjection.cs:174`) registers the Blazor Server host's SSR cookie reader ([`CookieTokenReader`](group-08-auth.md#cookietokenreader), `:180`) plus the singleton [`CookieSessionRefresher`](group-08-auth.md#cookiesessionrefresher) (`:186`, singleton so its -in-flight map is shared across requests), and `AddModuleHealthChecks` (`DependencyInjection.cs:202`) +in-flight map is shared across requests), and `AddModuleHealthChecks` (`DependencyInjection.cs:212`) turns [`ModuleLoader`](group-14-module-system-composition.md#moduleloader) discovery results into `module-{Name}` health checks, tagged `module` so `/health?tag=module` filters them (Healthy for enabled modules at `:206-212`, Degraded for the disabled ones from `:214`). @@ -71,7 +71,7 @@ carries the identical builder-side setup every service shares: header-based API `api-version` header (`AddCommonApiVersioning`, line 37, reader at line 46, v1.0 assumed when the header is absent at line 44, [ADR-046](https://ivanball.github.io/docs/adr/046-http-api-versioning.html)), rate limiting -(`AddCommonRateLimiting`, three overloads at `WebApplicationBuilderExtensions.RateLimiting.cs:326`, +(`AddCommonRateLimiting`, three overloads at `WebApplicationBuilderExtensions.RateLimiting.cs:328`, `:344`, and `:362`), Brotli and Gzip compression at `CompressionLevel.Fastest` (`AddCommonResponseCompression`, line 62, both providers pinned to `Fastest` at lines 71 and 76 because these are dynamic per-request payloads on fractional @@ -113,7 +113,7 @@ sequence of `Use...` calls. (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationExtensions.cs:48`) and its `Action` overload (`WebApplicationExtensions.cs:60`) both route through one private `ApplyPipeline` that seeds the defaults, lets the host adjust them, validates the result, and -only then applies each step (`WebApplicationExtensions.cs:153-160`). The steps themselves are +only then applies each step (`WebApplicationExtensions.cs:168-175`). The steps themselves are [`MiddlewarePipelineStep`](#middlewarepipelinestep) records (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/Pipeline/MiddlewarePipelineStep.cs:21`), each a stable `Name` plus an `Action Configure` delegate, both null-validated on construction @@ -191,7 +191,7 @@ global limiter is active on every request and rejects with 429 above [`RateLimitingSettings`](#ratelimitingsettings)`.GlobalPermitLimit` (default 300 requests per minute, `MMCA.Common/Source/Presentation/MMCA.Common.API/RateLimiting/RateLimitingSettings.cs:40`) **per authenticated user**: `GlobalRateLimitPartition` -(`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:136`) +(`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:137`) routes anonymous traffic to `AnonymousPartition` (line 58), and health, liveness, `/.well-known/*`, and gRPC traffic bypass the limiter outright (`IsRateLimitBypassed`, lines 42-46). The gRPC arm of that exemption is keyed on the **routed endpoint** rather than on the request's `Content-Type` @@ -228,7 +228,7 @@ and `:102`), so every consumer inherits spray protection by construction, while automatic and Blazor Server circuits issue it server-side from one shared host IP. A consumer that inherits the base without calling `AddCommonRateLimiting` fails at startup on an unregistered policy, which is the loud failure rather than the silent one. Two knobs sit on top of that baseline, both -reachable only through the `IConfiguration` overload (`WebApplicationBuilderExtensions.RateLimiting.cs:344`) that +reachable only through the `IConfiguration` overload (`WebApplicationBuilderExtensions.RateLimiting.cs:346`) that binds the `RateLimiting` section. `Algorithm` (`RateLimitingSettings.cs:53`) selects the [`RateLimitAlgorithm`](#ratelimitalgorithm) enum (`MMCA.Common/Source/Presentation/MMCA.Common.API/RateLimiting/RateLimitAlgorithm.cs:8`): `FixedWindow` @@ -239,7 +239,7 @@ the cost of one counter per segment. And `Distributed` (`RateLimitingSettings.cs in-memory counter for [`RedisFixedWindowRateLimiter`](#redisfixedwindowratelimiter) (`MMCA.Common/Source/Presentation/MMCA.Common.API/RateLimiting/RedisFixedWindowRateLimiter.cs:37`), so a limit means the same thing behind a load balancer as it does on one node. All three choices funnel -through one private factory, `CreateLimitedPartition` (`WebApplicationBuilderExtensions.RateLimiting.cs:205`), which +through one private factory, `CreateLimitedPartition` (`WebApplicationBuilderExtensions.RateLimiting.cs:207`), which takes the Redis path only when an `IConnectionMultiplexer` is actually registered and otherwise falls through to the in-memory limiters rather than failing startup (lines 214-236). The Redis limiter is worth reading for its three deliberate compromises: it stores one `INCR` counter per partition per @@ -253,7 +253,7 @@ never itself become the reason a healthy request is rejected. [`RedisRateLimitLease`](#redisratelimitlease) (`RedisFixedWindowRateLimiter.cs:169`) is the two-instance lease type it hands out, `Acquired` and `Rejected` as shared statics (lines 172 and 175) so a permitted request allocates nothing. The `auth-ip` policy stays per-instance whatever -`Distributed` says (`allowDistributed: false`, `WebApplicationBuilderExtensions.RateLimiting.cs:291`): per-account +`Distributed` says (`allowDistributed: false`, `WebApplicationBuilderExtensions.RateLimiting.cs:293`): per-account lockout already backs it, and a login throttle that fails open on a Redis outage is a worse trade than one that stays local ([ADR-019](https://ivanball.github.io/docs/adr/019-rate-limiting.html) for the layering, and @@ -263,14 +263,14 @@ once at the edge. **Correlation, tenancy, and the soft-deleted-user gate: three ambient facts established once.** [`CorrelationIdMiddleware`](#correlationidmiddleware) -(`MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/CorrelationIdMiddleware.cs:15`) reads the -`X-Correlation-ID` request header (the constant lives at `CorrelationIdMiddleware.cs:18`), falling back +(`MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/CorrelationIdMiddleware.cs:18`) reads the +`X-Correlation-ID` request header (the constant lives at `CorrelationIdMiddleware.cs:21`), falling back to the current W3C trace id then ASP.NET's `TraceIdentifier` (lines 32-34), writes it onto the scoped [`ICorrelationContext`](#icorrelationcontext) (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICorrelationContext.cs:8`, implemented by [`CorrelationContext`](#correlationcontext), which self-seeds a GUID when no middleware ever sets one, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Context/CorrelationContext.cs:12`), and echoes it -back through `Response.OnStarting` (`CorrelationIdMiddleware.cs:37-41`). That single id is what the +back through `Response.OnStarting` (`CorrelationIdMiddleware.cs:44-48`). That single id is what the CQRS logging decorators read (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/LoggingCommandDecorator.cs:17` and `:24`) and stamp onto every log scope through the @@ -316,7 +316,7 @@ access-token lifetime. All three middlewares are [Rubric §13, Observability & O ways. Thrown exceptions are caught by the handler chain registered in `AddCommonExceptionHandlers` (`DependencyInjection.cs:154-158`), evaluated most-specific-first: [`OperationCanceledExceptionHandler`](#operationcanceledexceptionhandler) (499 Client Closed Request, -`OperationCanceledExceptionHandler.cs:32`), [`DomainExceptionHandler`](#domainexceptionhandler) (400, +`OperationCanceledExceptionHandler.cs:37`), [`DomainExceptionHandler`](#domainexceptionhandler) (400, `DomainExceptionHandler.cs:32`), [`DbUpdateExceptionHandler`](#dbupdateexceptionhandler) (409 at line 33 with a deliberately generic detail so database schema names never leak, `DbUpdateExceptionHandler.cs:35-37`), [`ValidationExceptionHandler`](#validationexceptionhandler) (400 @@ -434,7 +434,7 @@ for two minutes at `OAuthControllerBase.cs:50`, keeps tokens out of the redirect [ADR-036](https://ivanball.github.io/docs/adr/036-external-oauth-login.html) and [ADR-043](https://ivanball.github.io/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html)); [`DataExportControllerBase`](#dataexportcontrollerbasetquery) -(`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:59`), +(`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:61`), the data-subject access and portability endpoint ([ADR-076](https://ivanball.github.io/docs/adr/076-data-subject-export.html)), which serves `GET {userId}/export` (line 77) as a real file download rather than an inline body (returned through @@ -475,20 +475,20 @@ subclass, and inherits a fully paged, filterable, exportable, error-mapped REST [Rubric §30, Compliance & Data Governance] covered by the DSAR base. **Export is a route, not a content negotiation.** `EntityControllerBase.ExportAsync` -(`EntityControllerBase.cs:245-249`) streams the same filtered collection the paged endpoint serves as +(`EntityControllerBase.cs:252-257`) streams the same filtered collection the paged endpoint serves as an RFC 4180 CSV download, page-looping the query service server-side ([ADR-078](https://ivanball.github.io/docs/adr/078-csv-export-endpoint.html)). It is a distinct path rather than an `Accept: text/csv` variant for two reasons stated in the source: the public output-cache policy varies by query string but not by `Accept`, so a cached JSON body could be replayed to a CSV request, and `AddAPI` sets `ReturnHttpNotAcceptable = false`, so a negotiation miss would fall -back to JSON silently instead of returning 406 (`EntityControllerBase.cs:196-201`). The work is split +back to JSON silently instead of returning 406 (`EntityControllerBase.cs:199-204`). The work is split in two. The controller keeps the HTTP concerns (the action, the row scope, the file name, and the headers) and hands the format to [`EntityCsvExporter`](#entitycsvexportertentitydto) (`MMCA.Common/Source/Presentation/MMCA.Common.API/Export/EntityCsvExporter.cs:17`, an internal static class whose own summary draws that line at `:9-14`), which owns which DTO properties can be exported, how a page of rows becomes CSV records, and the paging loop that streams every page under a row -ceiling (`WriteAsync`, line 105, invoked from `EntityControllerBase.cs:267`). Its record-writing +ceiling (`WriteAsync`, line 105, invoked from `EntityControllerBase.cs:279`). Its record-writing primitive is [`CsvWriter`](#csvwriter) (`MMCA.Common/Source/Presentation/MMCA.Common.API/Export/CsvWriter.cs:34`), a deliberately hand-written internal helper: it quotes only when RFC 4180 requires it (`WriteField`, line 145, with @@ -503,24 +503,24 @@ at `:197-200`); value objects and other class-typed properties are deliberately invariant `ToString` is exactly the cell a reader expects (`:26-28`). And a caller who names one of those dropped properties in `fields=` gets an `Error.InvalidEntityField` validation failure rather than a quietly missing column (`ValidateFields`, `EntityCsvExporter.cs:60`, called first thing in the action -at `EntityControllerBase.cs:257-259`). Then the exporter opens a `StreamWriter` over `Response.Body` +at `EntityControllerBase.cs:265-267`). Then the exporter opens a `StreamWriter` over `Response.Body` (`EntityCsvExporter.cs:121`) but commits nothing until the first page has succeeded: headers, BOM, and the header row go out only then (`:144-151`), and a first-page failure comes back as a plain failed `Result` (`:130-131`), which is what keeps the "a failure on page one still returns Problem Details" -path honest (`EntityControllerBase.cs:288-289`). A failure on a later page can no longer change the +path honest (`EntityControllerBase.cs:300-301`). A failure on a later page can no longer change the status, so the exporter logs it through the controller (`LogExportPageFailure`, -`EntityControllerBase.cs:593`) and ends the file with an explicit `# export incomplete after N rows` +`EntityControllerBase.cs:646`) and ends the file with an explicit `# export incomplete after N rows` line (`EntityCsvExporter.cs:133-137`, text at `:321-322`) rather than letting it pass for a complete export of fewer rows. The row ceiling is announced up front through the `X-Export-Row-Limit` header -(constant at `EntityControllerBase.cs:469`, default 100,000 at line 460, overridable per host through +(constant at `EntityControllerBase.cs:481`, default 100,000 at line 460, overridable per host through `MaxExportRows` at lines 76-83, written alongside the `Content-Disposition` attachment name in `BeginExportResponse` at lines 571-572, the name being a UTC `{controller}-{yyyyMMddTHHmmssZ}.csv` built at lines 582-585) with a `# export truncated at N rows` notice written as a final body line -(`EntityCsvExporter.cs:180-183`, text at `:315-316`), because headers are frozen the moment the first +(`EntityCsvExporter.cs:179-182`, text at `:315-316`), because headers are frozen the moment the first byte flushes; when the ceiling lands exactly on a page boundary the exporter consults the page's `TotalItemCount` instead of issuing one more query just to find out (`:169-175`). Row scoping is a shared hook rather than an export-only one: `GetReadSpecificationAsync` -(`EntityControllerBase.cs:531-533`) is what all five read actions apply (both `GetAllAsync` +(`EntityControllerBase.cs:571-573`) is what all five read actions apply (both `GetAllAsync` overloads, the lookup, `GetByIdAsync`, and the export, `:497-499`), resolved once per request so one instance filters every page of an export loop (`:527-528`). It is asynchronous because row scoping is rarely a pure function of the principal (`:507-512`), it returns `GetExportSpecification()`, itself @@ -535,7 +535,7 @@ and [Rubric §11, Security] pairing worth reading closely. [`IdempotentAttribute`](#idempotentattribute) (`MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotentAttribute.cs:16`), a one-line `ServiceFilterAttribute` that resolves the scoped [`IdempotencyFilter`](#idempotencyfilter) -(`MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:67`) from DI +(`MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:68`) from DI ([ADR-017](https://ivanball.github.io/docs/adr/017-request-idempotency.html)). The filter runs at two MVC stages. As an `IAsyncResourceFilter` it runs before model binding, the last point at which the body can be made replayable, and calls `EnableBuffering` only when an `Idempotency-Key` header is @@ -614,7 +614,7 @@ back (line 62), with the header names and the `*` wildcard as constants (`:27`, the honest strength, because a strong tag promises byte-for-byte equality of the representation, which this cannot promise when the same row version renders differently under a `fields=` projection (`ConcurrencyETag.cs:13`). The read side emits it automatically: `EntityControllerBase.GetByIdAsync` -calls `SetConcurrencyETag` (`EntityControllerBase.cs:370`), which resolves the DTO's `RowVersion` +calls `SetConcurrencyETag` (`EntityControllerBase.cs:382`), which resolves the DTO's `RowVersion` property once per closed controller type (line 379, so a DTO with no token costs no per-request reflection), reads the token off either the typed DTO or the camelCase dictionary a `fields=` projection produces (`ReadRowVersion`, lines 422-431), and writes the header at line 413; the method @@ -780,7 +780,7 @@ the caller's inbound `Authorization` header onto outgoing HTTP calls, unless one flows through a service-to-service hop without any handler threading the token by hand: the HTTP twin of [`JwtForwardingClientInterceptor`](group-13-grpc-contracts.md#jwtforwardingclientinterceptor). [`DatabaseInitializationExtensions`](#databaseinitializationextensions) -(`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:20`) +(`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:28`) initializes every physical data source the host owns: `InitializeDatabaseAsync` (line 34) validates the strategy **before** touching anything (`EnsureKnownStrategy`, called at line 46 and declared at line 180, which accepts only `Migrate` or `None` and otherwise throws naming the valid values through @@ -799,7 +799,7 @@ strategy per tenant that keeps its own copy of a source, each in a fresh scope w [ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html)), and finishes by running the enabled modules' seeders on the default scope only (line 110). Five smaller startup helpers round out the host: [`OpenApiEndpointExtensions`](#openapiendpointextensions) -(`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/Endpoints/OpenApiEndpointExtensions.cs:22`) maps the +(`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/Endpoints/OpenApiEndpointExtensions.cs:25`) maps the per-version OpenAPI document (`MapCommonOpenApi`, lines 34-38) and the optional Scalar reference UI (`MapCommonScalarUi`, lines 52-56), both outside Production only, [`ApiParameterDescriptorBackfillProvider`](#apiparameterdescriptorbackfillprovider) @@ -813,7 +813,7 @@ paired [`StronglyTypedIdSchemaTransformer`](#stronglytypedidschematransformer) (`MMCA.Common/Source/Presentation/MMCA.Common.API/OpenApi/StronglyTypedIdSchemaTransformer.cs:24`) and [`StronglyTypedIdParameterTransformer`](#stronglytypedidparametertransformer) (same file, line 74), both registered by `AddCommonOpenApi` through a `ConfigureAll` that reaches every versioned document -(`WebApplicationBuilderExtensions.cs:100-104`) and both inert in a host that declares no wrappers: the +(`WebApplicationBuilderExtensions.cs:102-106`) and both inert in a host that declares no wrappers: the schema half clears the object schema the default generator produces for a one-property struct and rewrites it as the wrapped primitive's type and format (`:38-41`, mapped at `:46` and `:51`) so the document matches the bare-primitive wire shape, and the operation half does the same for route and @@ -913,7 +913,7 @@ code stays modules and domain logic, never plumbing. - **Concept introduced (auth-header-tolerant output caching).** The framework UI attaches a Bearer token to *every* outgoing API request, including reads of `[AllowAnonymous]` endpoints whose payload is identical for every caller. Under the default policy those reads bypass the output cache for any signed-in user and land on the database each time (`PublicEndpointOutputCachePolicy.cs:15-20`). `[Rubric §12, Performance & Scalability]` assesses whether hot read paths avoid redundant work; this policy is a direct performance lever, letting public reads share one cached entry across authenticated and anonymous callers. `[Rubric §11, Security]` assesses trust boundaries; the class docs are explicit that a cached response is served verbatim to every subsequent caller, so it must be applied only to identity-independent payloads, and the `bypassRoles` mechanism exists precisely so a privileged role that receives an elevated payload (for example administrators seeing unpublished rows) is never served or stored from the shared cache (`:32-38`). A second SECURITY remark covers multi-tenancy (`:43-48`): the tenant-vary rule below is a backstop, not a license, an endpoint whose payload depends on the caller (rather than only on the tenant) still must not use this policy. This is the edge tier of the two-tier caching model ([ADR-026](https://ivanball.github.io/docs/adr/026-caching-strategy.html)); the authenticated-read decision itself is [ADR-040](https://ivanball.github.io/docs/adr/040-authenticated-output-caching-for-public-reads.html). - **Walkthrough**: a `TenantVaryByKey = "t"` constant (`:50`) mirrors the `t:{tenantId}` prefix the caching CQRS decorators use, and three instance fields hold the rest of the config, `_expiration`, `_bypassRoles`, `_tags` (`:52-54`). Two constructors: the `params string[] tags` overload delegates to the full one with an empty bypass-roles array, and the primary constructor guards its inputs (`ThrowIfLessThanOrEqual(expiration, TimeSpan.Zero)`, null checks on both arrays). All three interface methods are explicitly implemented, so they are reachable only through `IOutputCachePolicy`. `CacheRequestAsync` (`:81`) computes `attemptOutputCaching` as "is a GET/HEAD request" AND "is not a bypassed caller" (`:86-87`), enables output caching, sets `AllowCacheLookup`/`AllowCacheStorage` to that flag, allows locking, sets the expiration (`:88-92`), and (matching the built-in default) varies the cache key by every query-string parameter via `CacheVaryByRules.QueryKeys = "*"` (`:96`). It then resolves the tenant from `context.HttpContext.RequestServices` and, when one is present, stamps it into `CacheVaryByRules.VaryByValues[TenantVaryByKey]` (`:98-106`), an unresolved tenant, single-tenant host or background call, adds nothing; then copies the eviction tags in. `ServeFromCacheAsync` is a no-op returning `ValueTask.CompletedTask`. `ServeResponseAsync` refuses to store any response that set a cookie or returned a non-200 status, the same guard the built-in default applies. Two private helpers close it out: `IsCacheableRequest` (`:134-135`, GET or HEAD) and `IsBypassedCaller` (`:141-142`, `Array.Exists(_bypassRoles, user.HasRole)`). `HasRole` is deliberate, not `ClaimsPrincipal.IsInRole`: `IsInRole` only matches the identity's own role claim type, so against a provider that emits bare `role`/`roles` claims the permission handler would grant the elevated payload while this bypass check missed it, and that response would be stored under the shared public key (`:137-140`). - **Why it's built this way**: it mirrors the built-in default policy minus exactly one behavior, the authenticated-request bail-out, so its caching, query-key variance, and cookie/status guards stay identical to what developers already expect. Bypass roles get the default behavior back (no lookup, no storage), which keeps elevated payloads out of the shared cache without disabling caching for everyone. Stamping the resolved tenant into the vary key means a multi-tenant host's shared cache entry per path+query never crosses tenants, since the same path and query mean different rows per tenant (`:98-101`). A raw `IOutputCachePolicy` implementation inherits none of the default policy's behavior, so every guard is re-implemented here. -- **Where it's used**: registered as a named policy by [OutputCacheOptionsExtensions](#outputcacheoptionsextensions) and referenced from controller actions via `[OutputCache(PolicyName = ...)]`. Store's Catalog service registers four such policies with no bypass roles (`MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:149-154`); ADC's Conference service registers ten, most of them with an admin bypass-roles array (`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:268-297`), including two short 60-second policies for the fast-moving `NowNextCache` (`:231`) and `BookmarkCountsCache` (`:243`). The tags passed here are the same strings [OutputCacheEvictionHandler](#outputcacheevictionhandler) evicts by. +- **Where it's used**: registered as a named policy by [OutputCacheOptionsExtensions](#outputcacheoptionsextensions) and referenced from controller actions via `[OutputCache(PolicyName = ...)]`. Store's Catalog service registers four such policies with no bypass roles (`MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:149-154`); ADC's Conference service registers ten, most of them with an admin bypass-roles array (`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:267-296`), including two short 60-second policies for the fast-moving `NowNextCache` (`:231`) and `BookmarkCountsCache` (`:243`). The tags passed here are the same strings [OutputCacheEvictionHandler](#outputcacheevictionhandler) evicts by. ### OutputCacheOptionsExtensions > MMCA.Common.API · `MMCA.Common.API.Caching` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Caching/OutputCacheOptionsExtensions.cs:6` · Level 2 · class (static) @@ -923,7 +923,7 @@ code stays modules and domain logic, never plumbing. - **Concept**: this is a thin fluent facade over `OutputCacheOptions.AddPolicy`, using a C# `extension(OutputCacheOptions options)` block (`OutputCacheOptionsExtensions.cs:8`) so the policy registration reads as a first-class option on the options object. See the [DI registration extension(T) convention](00-primer.md#2-architectural-styles-this-codebase-commits-to). `[Rubric §9, API & Contract Design]` is relevant: the helper gives callers a self-documenting, named entry point instead of hand-constructing the policy at each call site. - **Walkthrough**: two overloads of `AddPublicEndpointPolicy`. The first (`OutputCacheOptionsExtensions.cs:20-21`) takes `name`, `expiration`, and `params string[] tags` and registers `new PublicEndpointOutputCachePolicy(expiration, tags)`. The second (`:34-35`) adds a `string[] bypassRoles` parameter before the `params string[] tags` and forwards to the three-argument policy constructor, for endpoints whose payload is identical for every caller except one privileged role. Both are expression-bodied and return `void`, mutating the options in place. - **Why it's built this way**: keeping the policy construction behind a named helper means the "apply only to `[AllowAnonymous]`, identity-independent endpoints" guidance travels with the API surface (see the doc comments at `:10-16` and `:23-29`) instead of being re-derived at each registration. -- **Where it's used**: called during host composition where the app configures `AddOutputCache(...)`; the registered `name` is then referenced by `[OutputCache(PolicyName = ...)]` on controller actions. Store's Catalog service uses the two-argument form (`MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:149-154`); ADC's Conference service mostly uses the bypass-roles form (`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:268-297`). +- **Where it's used**: called during host composition where the app configures `AddOutputCache(...)`; the registered `name` is then referenced by `[OutputCache(PolicyName = ...)]` on controller actions. Store's Catalog service uses the two-argument form (`MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:149-154`); ADC's Conference service mostly uses the bypass-roles form (`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:267-296`). ### ModuleControllerFeatureProvider > MMCA.Common.API · `MMCA.Common.API` · `MMCA.Common/Source/Presentation/MMCA.Common.API/ModuleControllerFeatureProvider.cs:28` · Level 2 · class (sealed) @@ -945,7 +945,7 @@ code stays modules and domain logic, never plumbing. - **Cancellation is not swallowed** (`:25-28`): an `OperationCanceledException` from host shutdown propagates, so MassTransit sees the shutdown rather than an acked message. This is the same `when (ex is not OperationCanceledException)` discipline used across the framework's cache-touching code. - **Walkthrough**: the primary constructor takes `IOutputCacheStore outputCacheStore` and `ILogger logger` (`:32-34`). `HandleAsync(OutputCacheEvictionRequested integrationEvent, CancellationToken)` (`:38`) null-guards the event (`:42`), then loops the tags (`:44`), skipping blank entries (`:46-49`). Each tag goes through `outputCacheStore.EvictByTagAsync(tag, cancellationToken)` inside its own `try` (`:51-53`) followed by a Debug log (`:54`). The catch (`:56-62`) is deliberately broad, with the comment noting that CA1031/S2221 are suggestions here because an eviction store that throws must not turn a coherence hint into a dead-lettered message; it records the failure on [OutputCacheMetrics](#outputcachemetrics) (`:60`) and logs a Warning naming the tag and stating that responses carrying it stay cached until their own TTL expires (`:61`, template at `:71-74`). - **Why it's built this way**: `partial` plus two `[LoggerMessage]` declarations (`:66-74`) gives source-generated, allocation-free logging at Debug for the success path and Warning for the failure path. Keeping the handler free of any broker type is what lets the same class serve both the in-process dispatcher and the MassTransit consumer, which is the extraction property the module system is built around ([ADR-007](https://ivanball.github.io/docs/adr/007-grpc-extraction.html), [ADR-008](https://ivanball.github.io/docs/adr/008-service-extraction-topology.html)). -- **Where it's used**: registered by [OutputCacheEvictionExtensions](#outputcacheevictionextensions)`.AddOutputCacheEvictionHandler()`. The broker half is `RegisterOutputCacheEvictionConsumer()` inside the host's `AddBrokerMessaging` configuration, which wires the generic `IntegrationEventConsumer` onto this handler; ADC's Conference service calls both (`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:303` and `:352`). The tags it evicts are the ones passed to [OutputCacheOptionsExtensions](#outputcacheoptionsextensions)`.AddPublicEndpointPolicy`. Framework coverage is `OutputCacheEvictionHandlerTests` (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Caching/OutputCacheEvictionHandlerTests.cs`). +- **Where it's used**: registered by [OutputCacheEvictionExtensions](#outputcacheevictionextensions)`.AddOutputCacheEvictionHandler()`. The broker half is `RegisterOutputCacheEvictionConsumer()` inside the host's `AddBrokerMessaging` configuration, which wires the generic `IntegrationEventConsumer` onto this handler; ADC's Conference service calls both (`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:302` and `:352`). The tags it evicts are the ones passed to [OutputCacheOptionsExtensions](#outputcacheoptionsextensions)`.AddPublicEndpointPolicy`. Framework coverage is `OutputCacheEvictionHandlerTests` (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Caching/OutputCacheEvictionHandlerTests.cs`). ### OutputCacheEvictionExtensions > MMCA.Common.API · `MMCA.Common.API.Caching` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Caching/OutputCacheEvictionExtensions.cs:27` · Level 5 · class (static, two extension blocks) @@ -963,21 +963,21 @@ code stays modules and domain logic, never plumbing. - `extension(IOutputCacheStore store)` (`:36`) holds the two verbs. `EvictTagsAsync(CancellationToken cancellationToken, params string[] tags)` (`:49`) null-guards `store` and `tags` (`:51-52`) and awaits `EvictByTagAsync` for each tag in the order given (`:54-57`). `TryEvictTagsAsync(ILogger logger, params string[] tags)` (`:78`) adds a `logger` guard (`:80-82`) and routes each eviction through `BestEffort.ExecuteAsync` with the prefixed operation name, the caller's logger, the eviction as the action, and `CancellationToken.None` (`:86-90`). - `extension(IServiceCollection services)` (`:95`) holds `AddOutputCacheEvictionHandler()` (`:111`). It null-guards `services` (`:113`), calls `services.TryAddEnumerable(ServiceDescriptor.Singleton, OutputCacheEvictionHandler>())` (`:115-117`), and returns the collection for chaining (`:119`). The doc records the one prerequisite (`:100-101`): `AddOutputCache()` must have been called, since it supplies the singleton `IOutputCacheStore` the handler evicts through. - **Why it's built this way**: the class doc states the pairing rule plainly (`:12-16`): this is the DI half, `RegisterOutputCacheEvictionConsumer()` on the MassTransit bus configurator is the broker half, and a host that wants the behavior calls both. Splitting them is what keeps the handler broker-agnostic, since the DI registration has no messaging dependency at all. Housing the eviction verbs in the same class keeps the write-side helper and the read-side coherence path in one file. -- **Where it's used**: ADC's Conference service calls `services.AddOutputCacheEvictionHandler()` (`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:303`) alongside the broker registration (`:352`). ADC's Conference controllers use the throwing form after a write, for example `SpeakersController` (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:305`) and `SpeakerCategoryItemsController`, which evicts three tags in one call (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerCategoryItemsController.cs:178`); Store's Catalog controllers use the best-effort form, for example `ProductsController` (`MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.API/Controllers/ProductsController.cs:242`) and `CategoriesController` (`MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.API/Controllers/CategoriesController.cs:168`). Framework coverage: `OutputCacheEvictTagsTests` pins both verbs, including the `CancellationToken.None` rule and the swallow-versus-throw split (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Caching/OutputCacheEvictTagsTests.cs:59-113`), and `OutputCacheEvictionHandlerTests` pins the double-registration behavior (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Caching/OutputCacheEvictionHandlerTests.cs:107-114`). +- **Where it's used**: ADC's Conference service calls `services.AddOutputCacheEvictionHandler()` (`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:302`) alongside the broker registration (`:352`). ADC's Conference controllers use the throwing form after a write, for example `SpeakersController` (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:314`) and `SpeakerCategoryItemsController`, which evicts three tags in one call (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerCategoryItemsController.cs:186`); Store's Catalog controllers use the best-effort form, for example `ProductsController` (`MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.API/Controllers/ProductsController.cs:242`) and `CategoriesController` (`MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.API/Controllers/CategoriesController.cs:168`). Framework coverage: `OutputCacheEvictTagsTests` pins both verbs, including the `CancellationToken.None` rule and the swallow-versus-throw split (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Caching/OutputCacheEvictTagsTests.cs:59-113`), and `OutputCacheEvictionHandlerTests` pins the double-registration behavior (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Caching/OutputCacheEvictionHandlerTests.cs:107-114`). ### DependencyInjection -> MMCA.Common.API · `MMCA.Common.API` · `MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:26` · Level 11 · class (static) +> MMCA.Common.API · `MMCA.Common.API` · `MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:26` · Level 14 · class (static) - **What it is**: the primary DI entry point for the `MMCA.Common.API` layer. Using a C# `extension(IServiceCollection services)` block (`DependencyInjection.cs:28`) it adds six methods to `IServiceCollection`: `AddAPI`, `AddErrorLocalization`, `AddErrorResources`, `AddCommonExceptionHandlers`, `AddServerAuthSessionCookie`, and `AddModuleHealthChecks`. -- **Depends on**: a broad slice of the API layer plus feature management and localization (`DependencyInjection.cs:1-19`). Notable first-party types wired here: [CurrencyJsonConverter](#currencyjsonconverter), the Shared-layer [EnumerationJsonConverterFactory](group-02-domain-building-blocks.md#enumerationjsonconverterfactory) (aliased because the Shared and API namespaces both surface converters), and `StronglyTypedIdJsonConverterFactory` (ADR-115 strongly typed identifiers, serializing wrapper types as their bare primitive), [UnhandledResultFailureFilter](#unhandledresultfailurefilter), [IdempotencyFilter](#idempotencyfilter) and [IdempotencySettings](#idempotencysettings), [OwnerOrAdminFilter](group-08-auth.md#owneroradminfilter) and its `OwnerOrAdminFilterOptions`, [ModuleControllerFeatureProvider](#modulecontrollerfeatureprovider), [CurrentUserTargetingContextAccessor](#currentusertargetingcontextaccessor) and [DisabledFeatureHandler](#disabledfeaturehandler), [IErrorLocalizer](#ierrorlocalizer)/[ErrorLocalizer](#errorlocalizer), [ErrorResources](#errorresources)/[ErrorResourceSource](#errorresourcesource), the exception handlers ([OperationCanceledExceptionHandler](#operationcanceledexceptionhandler), [DomainExceptionHandler](#domainexceptionhandler), [DbUpdateExceptionHandler](#dbupdateexceptionhandler), [ValidationExceptionHandler](#validationexceptionhandler), [GlobalExceptionHandler](#globalexceptionhandler)), [CookieTokenReader](group-08-auth.md#cookietokenreader) and [ICookieSessionRefresher](group-08-auth.md#icookiesessionrefresher)/[CookieSessionRefresher](group-08-auth.md#cookiesessionrefresher), and [ModuleLoader](group-14-module-system-composition.md#moduleloader)/[ModulesSettings](group-14-module-system-composition.md#modulessettings). Externals: `Microsoft.FeatureManagement` (including its `IDisabledFeaturesHandler` contract), `Microsoft.Extensions.Localization`, ASP.NET Core MVC/ProblemDetails/HealthChecks. -- **Concept introduced (layered DI wiring at the API edge).** `[Rubric §3, Clean Architecture]` assesses whether each layer registers only its own concerns; this class wires controllers, JSON/XML formatters, filters, feature management, exception handlers, and health checks, all API-layer edges, and reaches down to Application only for `ModulesSettings`/`ModuleLoader` (`:16-17`). `[Rubric §13, Observability & Operability]` and `[Rubric §17, DevOps]` both apply through `AddModuleHealthChecks` (`:188`), which projects module state into `/health` checks tagged `module` so `/health?tag=module` reports each module's status (`:180-181`). `[Rubric §9, API & Contract Design]` is relevant: every parameter is optional and defaulted so a host wires only what it needs. +- **Depends on**: a broad slice of the API layer plus feature management and localization (`DependencyInjection.cs:1-19`). Notable first-party types wired here: [CurrencyJsonConverter](#currencyjsonconverter), the Shared-layer [EnumerationJsonConverterFactory](group-02-domain-building-blocks.md#enumerationjsonconverterfactory) (aliased because the Shared and API namespaces both surface converters), and `StronglyTypedIdJsonConverterFactory` (ADR-115 strongly typed identifiers, serializing wrapper types as their bare primitive), [UnhandledResultFailureFilter](#unhandledresultfailurefilter), [IdempotencyFilter](#idempotencyfilter) and [IdempotencySettings](#idempotencysettings), [OwnerOrAdminFilter](group-08-auth.md#owneroradminfilter) and its `OwnerOrAdminFilterOptions`, [ModuleControllerFeatureProvider](#modulecontrollerfeatureprovider), [CurrentUserTargetingContextAccessor](#currentusertargetingcontextaccessor) and [DisabledFeatureHandler](#disabledfeaturehandler), [IErrorLocalizer](#ierrorlocalizer)/[ErrorLocalizer](#errorlocalizer), [ErrorResources](#errorresources)/[ErrorResourceSource](#errorresourcesource), the exception handlers ([OperationCanceledExceptionHandler](#operationcanceledexceptionhandler), [DomainExceptionHandler](#domainexceptionhandler), [DbUpdateExceptionHandler](#dbupdateexceptionhandler), [ValidationExceptionHandler](#validationexceptionhandler), [GlobalExceptionHandler](#globalexceptionhandler)), [CookieTokenReader](group-08-auth.md#cookietokenreader) and [ICookieSessionRefresher](group-08-auth.md#icookiesessionrefresher)/[CookieSessionRefresher](group-08-auth.md#cookiesessionrefresher), the session-cookie options and store ([SessionCookieSettings](group-08-auth.md#sessioncookiesettings), [ISessionCookieStore](group-08-auth.md#isessioncookiestore)/[SessionCookieStore](group-08-auth.md#sessioncookiestore), from `MMCA.Common.API.SessionCookies`, `:15`), and [ModuleLoader](group-14-module-system-composition.md#moduleloader)/[ModulesSettings](group-14-module-system-composition.md#modulessettings). Externals: `Microsoft.FeatureManagement` (including its `IDisabledFeaturesHandler` contract), `Microsoft.Extensions.Localization`, ASP.NET Core MVC/ProblemDetails/HealthChecks. +- **Concept introduced (layered DI wiring at the API edge).** `[Rubric §3, Clean Architecture]` assesses whether each layer registers only its own concerns; this class wires controllers, JSON/XML formatters, filters, feature management, exception handlers, and health checks, all API-layer edges, and reaches down to Application only for `ModulesSettings`/`ModuleLoader` (`:16-17`). `[Rubric §13, Observability & Operability]` and `[Rubric §17, DevOps]` both apply through `AddModuleHealthChecks` (`:212`), which projects module state into `/health` checks tagged `module` so `/health?tag=module` reports each module's status (`:204-205`). `[Rubric §9, API & Contract Design]` is relevant: every parameter is optional and defaulted so a host wires only what it needs. - **Walkthrough** - - `AddAPI(ModulesSettings? modulesSettings = null, IConfiguration? configuration = null)` (`DependencyInjection.cs:45`) registers controllers with `ReturnHttpNotAcceptable = false` and the [UnhandledResultFailureFilter](#unhandledresultfailurefilter) global filter (`:47-50`), adds three JSON converters (`:52-65`) and the XML DataContract formatters (`:67`). The second converter is the load-bearing one for enumerations: concrete enumerations do not inherit the base class's `[JsonConverter]` attribute because `System.Text.Json` resolves it with `inherit: false`, so the factory is registered once here and every `Enumeration` serializes by `Name` across the whole API surface (`:56-59`). The third, `StronglyTypedIdJsonConverterFactory` (`:61-65`), does the same job for ADR-115 strongly typed identifiers, so wrapper types serialize as the bare primitive they wrap without the attribute being repeated on every wrapper a consumer declares; in a host that declares no wrappers the factory converts nothing. It then conditionally registers [ModuleControllerFeatureProvider](#modulecontrollerfeatureprovider) when `modulesSettings` is non-null (`:69-73`), conditionally binds [IdempotencySettings](#idempotencysettings) from the config section with data-annotation validation on start (`:75-81`), and registers the scoped [IdempotencyFilter](#idempotencyfilter) and [OwnerOrAdminFilter](group-08-auth.md#owneroradminfilter) (scoped because they depend on scoped services such as [ICacheService](group-09-caching.md#icacheservice) and [ICurrentUserService](group-08-auth.md#icurrentuserservice), `:83-85`). Right after that it binds `OwnerOrAdminFilterOptions` with data-annotation validation but deliberately **not** `ValidateOnStart` (`:87-92`): `BypassRole` is required with no default, the framework knows no role names, so validating at startup would fail every host that never applies the filter; validating on first resolve puts the message in front of the host that actually uses it. Feature management comes next and is worth reading closely: `AddHttpContextAccessor()` is called first because it is `TryAdd`-based and therefore safe to repeat, then `AddFeatureManagement().WithTargeting()` registers the feature manager plus the built-in Percentage/TimeWindow/Targeting filters **and** supplies the targeting audience from the current request's principal, which is what makes a percentage rollout sticky per user instead of random per request; the accessor is a singleton, which is exactly why it reads `IHttpContextAccessor` rather than the scoped `ICurrentUserService`. Finally the singleton [DisabledFeatureHandler](#disabledfeaturehandler) is bound to `IDisabledFeaturesHandler` and `AddErrorLocalization()` is called. - - `AddErrorLocalization()` (`:107`) registers ASP.NET localization (`:109`), the singleton [IErrorLocalizer](#ierrorlocalizer) via `TryAddSingleton` so a host can substitute its own (`:110`), and the framework's own [ErrorResources](#errorresources) source (`:111`); `AddErrorResources()` (`:122`) adds a module's resource anchor as another [ErrorResourceSource](#errorresourcesource) built from an `IStringLocalizerFactory` (`:124-125`). This is the [ADR-027](https://ivanball.github.io/docs/adr/027-multi-locale-i18n.html) edge error-localization extension point, keyed by `Error.Code`, and modules add their translations additively (`:115-121`). - - `AddCommonExceptionHandlers()` (`:135`) registers ProblemDetails (adding a `requestId` extension from `TraceIdentifier`, `:137-139`) then five `IExceptionHandler`s in specificity order (`:140-144`): `OperationCanceled`, `DomainException`, `DbUpdate`, `Validation`, and [GlobalExceptionHandler](#globalexceptionhandler) as the catch-all. ASP.NET Core invokes them in registration order and stops at the first that handles the exception, hence most-specific first and the 500 fallback last (`:131-132`). - - `AddServerAuthSessionCookie(string apiBaseAddress)` (`:160`) wires the SSR-prerender auth path: it guards the address (`:162`), then adds `HttpContextAccessor`, memory cache, the scoped [CookieTokenReader](group-08-auth.md#cookietokenreader) (`:164-166`), a named `HttpClient` pointed at the internal API base address (`:168-169`), and the [CookieSessionRefresher](group-08-auth.md#cookiesessionrefresher) as a **singleton** (`:172`). The singleton is load-bearing: its in-flight map must be shared across requests for single-flight refresh to work (`:171`). The doc comment is explicit that the address is the internal endpoint, not the browser-facing one (`:155-158`). - - `AddModuleHealthChecks(ModuleLoader moduleLoader)` (`:188`) adds one health check per module, `Healthy` for each enabled module (`:192-198`) and `Degraded` for each disabled one (`:200-207`), named `module-{Name}` and tagged `module`. It must run after [ModuleLoader](group-14-module-system-composition.md#moduleloader)'s `DiscoverAndRegister` (`:183-186`). -- **Why it's built this way**: bundling the API-edge concerns behind small, defaulted extension methods lets each host opt into exactly the surface it needs (a JWT-only test host skips `AddServerAuthSessionCookie`; a monolith with no disabled modules passes a null `modulesSettings`; a host with no idempotency configuration passes a null `configuration` and keeps the defaults). The exception-handler ordering, the enumeration and strongly-typed-id converter factories, the `OwnerOrAdminFilterOptions` validate-on-resolve (not on-start) choice, the targeting accessor's singleton lifetime, and the refresher's singleton lifetime are the non-obvious, correctness-critical choices, and each carries an inline comment. Error localization is registered automatically by `AddAPI` so modules only add their own resources additively ([ADR-027](https://ivanball.github.io/docs/adr/027-multi-locale-i18n.html)). + - `AddAPI(ModulesSettings? modulesSettings = null, IConfiguration? configuration = null)` (`DependencyInjection.cs:45`) registers controllers with `ReturnHttpNotAcceptable = false` and the [UnhandledResultFailureFilter](#unhandledresultfailurefilter) global filter (`:47-50`), adds three JSON converters (`:52-65`) and the XML DataContract formatters (`:67`). The second converter is the load-bearing one for enumerations: concrete enumerations do not inherit the base class's `[JsonConverter]` attribute because `System.Text.Json` resolves it with `inherit: false`, so the factory is registered once here and every `Enumeration` serializes by `Name` across the whole API surface (`:56-59`). The third, `StronglyTypedIdJsonConverterFactory` (`:61-65`), does the same job for ADR-115 strongly typed identifiers, so wrapper types serialize as the bare primitive they wrap without the attribute being repeated on every wrapper a consumer declares; in a host that declares no wrappers the factory converts nothing. It then conditionally registers [ModuleControllerFeatureProvider](#modulecontrollerfeatureprovider) when `modulesSettings` is non-null (`:69-73`), conditionally binds [IdempotencySettings](#idempotencysettings) from the config section with data-annotation validation on start (`:75-81`), and registers the scoped [IdempotencyFilter](#idempotencyfilter) and [OwnerOrAdminFilter](group-08-auth.md#owneroradminfilter) (scoped because they depend on scoped services such as [ICacheService](group-09-caching.md#icacheservice) and [ICurrentUserService](group-08-auth.md#icurrentuserservice), `:83-85`). Right after that it binds `OwnerOrAdminFilterOptions` with data-annotation validation but deliberately **not** `ValidateOnStart` (`:87-92`): `BypassRole` is required with no default, the framework knows no role names, so validating at startup would fail every host that never applies the filter; validating on first resolve puts the message in front of the host that actually uses it. Feature management comes next (`:94-107`) and is worth reading closely: `AddHttpContextAccessor()` is called first because it is `TryAdd`-based and therefore safe to repeat, then `AddFeatureManagement().WithTargeting()` registers the feature manager plus the built-in Percentage/TimeWindow/Targeting filters **and** supplies the targeting audience from the current request's principal, which is what makes a percentage rollout sticky per user instead of random per request; the accessor is a singleton, which is exactly why it reads `IHttpContextAccessor` rather than the scoped `ICurrentUserService`. Finally the singleton [DisabledFeatureHandler](#disabledfeaturehandler) is bound to `IDisabledFeaturesHandler` and `AddErrorLocalization()` is called (`:110`). + - `AddErrorLocalization()` (`:121`) registers ASP.NET localization (`:123`), the singleton [IErrorLocalizer](#ierrorlocalizer) via `TryAddSingleton` so a host can substitute its own (`:124`), and the framework's own [ErrorResources](#errorresources) source (`:125`); `AddErrorResources()` (`:136`) adds a module's resource anchor as another [ErrorResourceSource](#errorresourcesource) built from an `IStringLocalizerFactory` (`:138-139`). This is the [ADR-027](https://ivanball.github.io/docs/adr/027-multi-locale-i18n.html) edge error-localization extension point, keyed by `Error.Code`, and modules add their translations additively (`:116-118`). + - `AddCommonExceptionHandlers()` (`:149`) registers ProblemDetails (adding a `requestId` extension from `TraceIdentifier`, `:151-153`) then five `IExceptionHandler`s in specificity order (`:154-158`): `OperationCanceled`, `DomainException`, `DbUpdate`, `Validation`, and [GlobalExceptionHandler](#globalexceptionhandler) as the catch-all. ASP.NET Core invokes them in registration order and stops at the first that handles the exception, hence most-specific first and the 500 fallback last (`:145-146`). + - `AddServerAuthSessionCookie(string apiBaseAddress)` (`:174`) wires the SSR-prerender auth path: it guards the address (`:176`), then adds `HttpContextAccessor`, memory cache, the scoped [CookieTokenReader](group-08-auth.md#cookietokenreader) (`:178-180`), a named `HttpClient` pointed at the internal API base address (`:182-183`), and the [CookieSessionRefresher](group-08-auth.md#cookiesessionrefresher) as a **singleton** (`:186`). The singleton is load-bearing: its in-flight map must be shared across requests for single-flight refresh to work (`:185`). It then registers the [SessionCookieSettings](group-08-auth.md#sessioncookiesettings) options with no configuration binding, so SameSite and the claims-only browser token keep their default behavior unless the same-origin API proxy in `MMCA.Common.UI.Web` tightens them on opt-in ([ADR-131](https://ivanball.github.io/docs/adr/131-same-origin-api-proxy.html)), and the singleton [ISessionCookieStore](group-08-auth.md#isessioncookiestore)/[SessionCookieStore](group-08-auth.md#sessioncookiestore) via `TryAddSingleton` (`:188-191`). Last it registers `TimeProvider.System` via `TryAddSingleton` (`:193-196`): the refresher judges access-token expiry against the injected clock, and a Blazor Web host that calls only this method has no `AddServices` registration to supply one, while `TryAdd` never displaces a clock the host registered itself. The doc comment is explicit that the address is the internal endpoint, not the browser-facing one (`:169-171`). + - `AddModuleHealthChecks(ModuleLoader moduleLoader)` (`:212`) adds one health check per module, `Healthy` for each enabled module (`:216-222`) and `Degraded` for each disabled one (`:224-231`), named `module-{Name}` and tagged `module`. It must run after [ModuleLoader](group-14-module-system-composition.md#moduleloader)'s `DiscoverAndRegister` (`:208-209`). +- **Why it's built this way**: bundling the API-edge concerns behind small, defaulted extension methods lets each host opt into exactly the surface it needs (a JWT-only test host skips `AddServerAuthSessionCookie`; a monolith with no disabled modules passes a null `modulesSettings`; a host with no idempotency configuration passes a null `configuration` and keeps the defaults). The exception-handler ordering, the enumeration and strongly-typed-id converter factories, the `OwnerOrAdminFilterOptions` validate-on-resolve (not on-start) choice, the targeting accessor's singleton lifetime, the refresher's singleton lifetime, and the `TryAdd` fallback clock for hosts that call only `AddServerAuthSessionCookie` are the non-obvious, correctness-critical choices, and each carries an inline comment. Error localization is registered automatically by `AddAPI` so modules only add their own resources additively ([ADR-027](https://ivanball.github.io/docs/adr/027-multi-locale-i18n.html)). - **Where it's used**: called from every service host's composition (`Program.cs` of the ADC/Store/Helpdesk API hosts and the integration-test hosts) to wire the shared API layer; `AddApplicationDecorators()` still runs last in the overall sequence (see `MMCA.Common/CLAUDE.md` DI ordering note). - **Caveats / not-in-source**: the relative ordering of `AddAPI` against `AddInfrastructure`/`AddApplication` in a given host is not fixed by this file; only `AddApplicationDecorators()` last is load-bearing. @@ -996,7 +996,7 @@ code stays modules and domain logic, never plumbing. - `WriteField(field, writer)` (`CsvWriter.cs:145`) is the only private member: a clean field is written as-is (`CsvWriter.cs:147-151`), and a field containing any `MustQuote` character is wrapped in quotes with embedded quotes doubled (`CsvWriter.cs:153-155`). - **Why it's built this way**: the writer takes a `TextWriter` rather than returning a string, because the whole point of the export endpoint is that no full result set exists in memory at any moment. Keeping the type `internal static` means it is a framework implementation detail, not a public surface a consumer can bind to and then be broken by. - **Caveats / not-in-source**: the writer deliberately does **not** neutralize spreadsheet formula injection. A cell whose value opens with `=`, `+`, `-`, or `@` is written verbatim, and the type doc records the reasoning (`CsvWriter.cs:27-32`): CSV is treated as a data-faithful format here and prefixing would corrupt legitimate negative numbers, so a host that opens untrusted exports in a spreadsheet is expected to import them as text. [ADR-078](https://ivanball.github.io/docs/adr/078-csv-export-endpoint.html) records the same decision in the same direction, and names the price (a known spreadsheet risk carried by every consumer). -- **Where it's used**: only by [EntityCsvExporter](#entitycsvexportertentitydto)`.WriteAsync`, which takes the encoding at `EntityCsvExporter.cs:121`, writes the BOM and header at `EntityCsvExporter.cs:148-149`, and each data row via `WritePage` (`EntityCsvExporter.cs:217`), reached in turn by [EntityControllerBase](#entitycontrollerbasetentity-tentitydto-tidentifiertype)`.ExportAsync` (`EntityControllerBase.cs:267`). +- **Where it's used**: only by [EntityCsvExporter](#entitycsvexportertentitydto)`.WriteAsync`, which takes the encoding at `EntityCsvExporter.cs:121`, writes the BOM and header at `EntityCsvExporter.cs:148-149`, and each data row via `WritePage` (`EntityCsvExporter.cs:225`), reached in turn by [EntityControllerBase](#entitycontrollerbasetentity-tentitydto-tidentifiertype)`.ExportAsync` (`EntityControllerBase.cs:279`). ### ServiceInfoResponse > MMCA.Common.API · `MMCA.Common.API.Controllers` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/ServiceInfoControllerBase.cs:51` · Level 0 · record (sealed, nested) @@ -1071,82 +1071,86 @@ code stays modules and domain logic, never plumbing. > MMCA.Common.API · `MMCA.Common.API.Export` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Export/EntityCsvExporter.cs:17` · Level 4 · class (internal static) - **What it is**: the page-looping CSV export engine [EntityControllerBase](#entitycontrollerbasetentity-tentitydto-tidentifiertype)`.ExportAsync` delegates to: it validates a `fields=` request, walks the paged query service page by page, and streams RFC 4180 CSV to a caller-supplied stream. Closed over the DTO type alone (`EntityCsvExporter.cs:17`), so its exclusion lists and column resolution are computed once per DTO shape without needing the entity or identifier type at all. -- **Depends on**: [CsvWriter](#csvwriter) (byte order mark, header, rows), [Error](group-01-result-error-handling.md#error), [PagedCollectionResult](group-01-result-error-handling.md#pagedcollectionresultt), [QueryFieldService](group-03-querying-specifications.md#queryfieldservice) (`ShapeData`, the shaped-row fallback), `System.Reflection`, `System.Text.Json` (`JsonNamingPolicy.CamelCase`), and `System.Globalization`. +- **Depends on**: [CsvWriter](#csvwriter) (byte order mark, header, rows), [Error](group-01-result-error-handling.md#error), [PagedCollectionResult](group-01-result-error-handling.md#pagedcollectionresultt), [PaginationMetadata](group-01-result-error-handling.md#paginationmetadata) (`TotalItemCount`, the last-page and cap-boundary arbiter), [QueryFieldService](group-03-querying-specifications.md#queryfieldservice) (`ShapeData`, the shaped-row fallback), `System.Reflection`, `System.Text.Json` (`JsonNamingPolicy.CamelCase`), and `System.Globalization`. - **Concept introduced: extracting a generic algorithm out of the controller that only invokes it.** `[Rubric §1, SOLID]` covers Single Responsibility: the page loop, the column resolution, and the exclusion lists are export mechanics independent of HTTP, so hoisting them out of `EntityControllerBase` lets `ExportAsync` shrink to validate, delegate, and translate the result (class doc, `EntityCsvExporter.cs:10-14`). `[Rubric §15, Best Practices & Code Quality]`: `WriteAsync` takes the fetch as a delegate (`Func>>>`) rather than the query service itself, so the exporter carries no dependency on `IEntityQueryService` and could stream any paged source a caller adapts to that shape. - **Walkthrough** - - The three exclusion sets, `UnexportablePropertyNames` (`EntityCsvExporter.cs:30-36`), `UnexportableColumns` (`:42-43`), and `UnexportableFields` (`:49-50`), are `static readonly` fields computed once per closed `EntityCsvExporter`, since a DTO's shape cannot change at runtime: the property names that cannot render a faithful scalar CSV cell (binary concurrency tokens and every collection type except `string`, `IsExportableType` at `:197-200`), their camelCase column-name form, and a case-insensitive lookup set for `fields=` matching. + - The three exclusion sets, `UnexportablePropertyNames` (`EntityCsvExporter.cs:30-36`), `UnexportableColumns` (`:42-43`), and `UnexportableFields` (`:49-50`), are `static readonly` fields computed once per closed `EntityCsvExporter`, since a DTO's shape cannot change at runtime: the property names that cannot render a faithful scalar CSV cell (binary concurrency tokens and every collection type except `string`, `IsExportableType` at `:205-208`), their camelCase column-name form, and a case-insensitive lookup set for `fields=` matching. - `ValidateFields(fields)` (`:60`) rejects a `fields=` request naming one of those properties as an `Error.InvalidEntityField` failure (`:69-73`), before a single byte of the body is written; an empty exclusion set short-circuits to `null` (`:62-63`). - - `WriteAsync(body, fetchPageAsync, pageSize, maxExportRows, fields, beginResponse, onFailureAfterStart, cancellationToken)` (`:105`) is the page loop: constructing the `StreamWriter` (`:121`) sends nothing, so a first-page failure still returns `Result.Failure` with nothing written (`:128-130`) for the caller to turn into Problem Details. On the first successful page it resolves columns, calls `beginResponse()`, and writes the BOM and header (`:144-151`); each page is written by `WritePage` (`:217`) and flushed. Three termination conditions mirror the pre-extraction controller loop: a short page ends the export (`:166-167`), a page that stopped mid-page marks it truncated (`:159-163`), and a cap landing exactly on a page boundary consults `PaginationMetadata.TotalItemCount` rather than issuing a wasted page (`:171-175`). A mid-stream failure after the header started calls `onFailureAfterStart` and writes the incomplete marker instead of returning failure (`:128-138`). - - `ResolveColumns` (`:252`) takes the shaped keys of the first row when there is one, or falls back to the DTO's own properties in declaration order, filtered by `fields`, for an empty result, so an export of nothing still owes a header row. - - `ShapeRow` (`:282`) and `BuildCells` (`:293`) normalize a row (typed DTO or already-shaped dynamic object) to a camelCase-keyed dictionary and then project it onto the resolved column order, substituting null for a column a row does not carry. - - `TruncationMarker` (`:315`) and `IncompleteMarker` (`:321`) format the two trailing marker lines with `CultureInfo.InvariantCulture`. `ParseFields` (`:307`) splits a `fields=` value into a case-insensitive set. -- **Why it's built this way**: generic over `TEntityDTO` alone, because export needs only the DTO shape to compute its exclusion lists and resolve columns; the fetch delegate carries everything else the caller's specific entity/query pipeline needs. `internal static` keeps it a framework implementation detail behind `ExportAsync`, not a public surface a consumer can bind to. -- **Caveats / not-in-source**: this type has no HTTP awareness of its own; the authorization symmetry and no-output-caching caveats belong to [EntityControllerBase](#entitycontrollerbasetentity-tentitydto-tidentifiertype), which is the only caller. -- **Where it's used**: only by [EntityControllerBase](#entitycontrollerbasetentity-tentitydto-tidentifiertype)`.ExportAsync`, which calls `ValidateFields` (`EntityControllerBase.cs:257`) and `WriteAsync` (`EntityControllerBase.cs:267-286`). + - `WriteAsync(body, fetchPageAsync, pageSize, maxExportRows, fields, beginResponse, onFailureAfterStart, cancellationToken)` (`:105`) is the page loop: constructing the `StreamWriter` (`:121`) sends nothing, so a first-page failure still returns `Result.Failure` with nothing written (`:128-131`) for the caller to turn into Problem Details. On the first successful page it resolves columns, calls `beginResponse()`, and writes the BOM and header (`:144-151`); each page is written by `WritePage` (`:225`) and flushed. Three termination conditions follow: a page that stopped mid-page marks the export truncated (`:159-163`), `IsLastPage` ends it on the last page (`:165-166`), and a cap landing exactly on a page boundary consults `PaginationMetadata.TotalItemCount` rather than issuing a wasted page (`:170-174`). A mid-stream failure after the header started calls `onFailureAfterStart` and writes the incomplete marker instead of returning failure (`:136-138`). + - `IsLastPage(itemCount, pageSize, rowsWritten, metadata)` (`:196-197`) does not trust a short page on its own: an empty page ends the export, but a short non-empty page ends it only when `rowsWritten` has reached `TotalItemCount`. The query pipeline clamps every page to its own ceiling (`EntityQueryPipeline.MaxUnboundedResultLimit = 1000`, `MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/EntityQueryPipeline.cs:23`), so a full clamped page can come back shorter than a larger requested page size, and ending there would cut the export short with no marker line (doc, `:190-195`). + - `ResolveColumns` (`:260`) takes the shaped keys of the first row when there is one, or falls back to the DTO's own properties in declaration order, filtered by `fields`, for an empty result, so an export of nothing still owes a header row. + - `ShapeRow` (`:290`) and `BuildCells` (`:301`) normalize a row (typed DTO or already-shaped dynamic object) to a camelCase-keyed dictionary and then project it onto the resolved column order, substituting null for a column a row does not carry. + - `TruncationMarker` (`:323`) and `IncompleteMarker` (`:329`) format the two trailing marker lines with `CultureInfo.InvariantCulture`. `ParseFields` (`:315`) splits a `fields=` value into a case-insensitive set. +- **Why it's built this way**: generic over `TEntityDTO` alone, because export needs only the DTO shape to compute its exclusion lists and resolve columns; the fetch delegate carries everything else the caller's specific entity/query pipeline needs. `internal static` keeps it a framework implementation detail behind `ExportAsync`, not a public surface a consumer can bind to. The last-page test reads the total rather than the page length because the page length is a property of the pipeline's clamp, not of the data. +- **Caveats / not-in-source**: this type has no HTTP awareness of its own; the authorization symmetry, the fail-closed row scoping, and the no-output-caching caveats belong to [EntityControllerBase](#entitycontrollerbasetentity-tentitydto-tidentifiertype), which is the only caller. +- **Where it's used**: only by [EntityControllerBase](#entitycontrollerbasetentity-tentitydto-tidentifiertype)`.ExportAsync`, which calls `ValidateFields` (`EntityControllerBase.cs:265`) and `WriteAsync` (`EntityControllerBase.cs:279-298`). Unit coverage is `EntityCsvExporterTests` (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Export/EntityCsvExporterTests.cs`). ### EntityControllerBase > MMCA.Common.API · `MMCA.Common.API.Controllers` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:34` · Level 6 · class (abstract) -- **What it is**: the generic read-only controller that gives any entity five working REST endpoints (`GET /`, `GET /paged`, `GET /export`, `GET /lookup`, `GET /{id}`) with filtering, sorting, pagination, field projection, one shared row-scoping hook, and an `ETag` on the by-id read, by delegating to the [IEntityQueryService](group-03-querying-specifications.md#ientityqueryservicetentity-tentitydto-tidentifiertype) pipeline. -- **Depends on**: [ApiControllerBase](#apicontrollerbase) (base), [IEntityControllerBase](#ientitycontrollerbasetentitydto-tidentifiertype) (implements), [IEntityQueryService](group-03-querying-specifications.md#ientityqueryservicetentity-tentitydto-tidentifiertype), [AuditableBaseEntity](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype) (constraint), [ApplicationSettings](group-14-module-system-composition.md#applicationsettings) (through `IOptions`), [CollectionResult](group-01-result-error-handling.md#collectionresultt), [PagedCollectionResult](group-01-result-error-handling.md#pagedcollectionresultt), [BaseLookup](#baselookuptidentifiertype), [QueryFilterModelBinder](#queryfiltermodelbinder), [Error](group-01-result-error-handling.md#error), [EntityCsvExporter](#entitycsvexportertentitydto), [ConcurrencyETag](group-08-auth.md#concurrencyetag), [QueryFieldService](group-03-querying-specifications.md#queryfieldservice), and [Specification](group-03-querying-specifications.md#specificationtentity-tidentifiertype); ASP.NET Core MVC, `Asp.Versioning`, `System.Text.Json`, `TimeProvider`, and `ILogger`. +- **What it is**: the generic read-only controller that gives any entity five working REST endpoints (`GET /`, `GET /paged`, `GET /export`, `GET /lookup`, `GET /{id}`) with filtering, sorting, pagination, field projection, one shared row-scoping hook, a fail-closed export, and an `ETag` on the by-id read, by delegating to the [IEntityQueryService](group-03-querying-specifications.md#ientityqueryservicetentity-tentitydto-tidentifiertype) pipeline. +- **Depends on**: [ApiControllerBase](#apicontrollerbase) (base), [IEntityControllerBase](#ientitycontrollerbasetentitydto-tidentifiertype) (implements), [IEntityQueryService](group-03-querying-specifications.md#ientityqueryservicetentity-tentitydto-tidentifiertype), [AuditableBaseEntity](group-02-domain-building-blocks.md#auditablebaseentitytidentifiertype) (constraint), [ApplicationSettings](group-14-module-system-composition.md#applicationsettings) (through `IOptions`), [CollectionResult](group-01-result-error-handling.md#collectionresultt), [PagedCollectionResult](group-01-result-error-handling.md#pagedcollectionresultt), [BaseLookup](#baselookuptidentifiertype), [QueryFilterModelBinder](#queryfiltermodelbinder), [Error](group-01-result-error-handling.md#error), [EntityCsvExporter](#entitycsvexportertentitydto), [ConcurrencyETag](group-08-auth.md#concurrencyetag), [QueryFieldService](group-03-querying-specifications.md#queryfieldservice), and [Specification](group-03-querying-specifications.md#specificationtentity-tidentifiertype); `EntityQueryPipeline.MaxUnboundedResultLimit` for the page-size clamp; ASP.NET Core MVC, `Asp.Versioning`, `System.Text.Json`, `TimeProvider`, and `ILogger`. - **Concept introduced: generic controller bases that eliminate CRUD boilerplate.** `[Rubric §9, API & Contract Design]` covers uniform endpoint conventions; `[Rubric §1, SOLID]` covers the Open/Closed side, since a new entity controller extends this base rather than re-writing the endpoints. The class-level `[ApiController]`, `[Route("[controller]")]`, `[ApiVersion("1.0")]` (`EntityControllerBase.cs:31-33`) plus the three generic constraints (`EntityControllerBase.cs:41-43`) turn the type parameters into the contract: name the entity, the DTO, and the identifier alias, and the routes, the versioning, and the query behavior follow ([ADR-034](https://ivanball.github.io/docs/adr/034-generic-entity-query-layer.html)). - **Walkthrough** - - Primary constructor (`EntityControllerBase.cs:34-39`) takes the query service and an `ILogger`, both null-guarded into the `QueryService` (`:47`) and `Logger` (`:52`) protected properties. - - `MaxPageSize` (`:58-65`) resolves `IOptions` per-request from `HttpContext.RequestServices`, falling back to 500 (`:63`). Per-request resolution means a settings change takes effect without a restart. `MaxExportRows` (`:78-86`) does the same for the export ceiling, with the extra rule that a configured value of zero or less is treated as unconfigured (`:83-84`), because a cap of zero would silently serve every caller a header-only file (`:73-77`). `EntityName` (`:91`) is `typeof(TEntity).Name`, used in log messages. - - `GetAllAsync` unpaged (`:109`, `[HttpGet]` at `:106`): resolves the read specification (`:115`), then delegates to the query service with `pageNumber: 1` and `pageSize: MaxPageSize` (`:122-123`), so even the "all" endpoint is capped, then either `HandleFailure` or `Ok`. - - `GetAllAsync` paged (`:157`, `[HttpGet("paged")]` at `:153`): clamps with `Math.Min(pageSize, MaxPageSize)` (`:168`), and on success serializes [PaginationMetadata](group-01-result-error-handling.md#paginationmetadata) into the `X-Pagination` response header (`:187`) rather than mixing it into the body, `[Rubric §9]` again, and `[Rubric §12, Performance & Scalability]` for the clamp. - - `ExportAsync` (`:249`, `[HttpGet("export")]` at `:245`) is covered as its own concept below. - - `GetAllForLookupAsync` (`:374`, `[HttpGet("lookup")]` at `:371`): returns `CollectionResult>`, a lightweight id/label pair, with a `[Required]` `nameProperty` (`:375`) choosing the label. - - `GetByIdAsync` (`:415`, `[HttpGet("{id}")]` at `:410`): `includeFKs` defaults to `true` for the single-entity case (`:417`), and on success it calls `SetConcurrencyETag(result.Value)` **before** returning (`:436-437`). - - `HandleFailure` override (`:505-519`): logs the first error at Warning, guarded by `Logger.IsEnabled` (`:508`), before delegating to [ApiControllerBase](#apicontrollerbase)`.HandleFailure`, so the read path gets observability (`[Rubric §13, Observability & Operability]`) without changing the response mapping. Note it logs the *first* error while the base ranks the status by the most severe one: the log line is a breadcrumb, not the status decision. - - Every read action passes `asTracking: false` to the query service (for example `:124`), because a read endpoint never mutates what it loaded. -- **Concept introduced: one row-scoping hook for every read the controller serves.** `[Rubric §11, Security]` assesses whether an authorization decision can be reproduced consistently across every path that returns the same rows; `[Rubric §15, Best Practices & Code Quality]` assesses whether that decision lives in one place. `GetReadSpecificationAsync(cancellationToken)` (`:597-599`) returns the [Specification](group-03-querying-specifications.md#specificationtentity-tidentifiertype) applied by **all five** read actions: both `GetAllAsync` overloads, `GetAllForLookupAsync`, `GetByIdAsync`, and `ExportAsync` (each resolves it at `:115`, `:170`, `:378`, `:422`, and `:264`). Four properties are worth internalizing, all stated on the hook's own doc (`:562-596`). - - **It is asynchronous because row scoping usually is** (`:572-579`). Scope is rarely a pure function of the current principal: it comes from a query handler, a claim lookup that hits a store, a tenancy read. A synchronous hook forced such a controller to override all five actions by hand purely to get an `await` in before the query. An override with nothing to await returns `ValueTask.FromResult(...)` and allocates nothing. - - **It narrows, it never replaces** (`:580-586`). The query service ANDs the specification with the caller's `filters` dictionary and `fields` projection, so a caller can only narrow what the specification already allows; a filter naming excluded rows yields an empty page rather than leaking them (`:137-142`). - - **A rejected row is a 404, not a 403** (`:587-591`, restated on `GetByIdAsync` at `:404-409`). The specification narrows the query, so the row is simply absent. Answering "forbidden" would confirm the id exists and turn a scoped read into an existence oracle a caller could walk. - - **The lookup endpoint carries the scope as a predicate** (`:364-370`). `GetAllForLookupAsync` has no specification parameter, so the scope travels as `specification?.Criteria` (`:382`); a null specification passes a null predicate, the unscoped query this endpoint always issued. A dropdown that lists what the list endpoint hides would be an oracle of its own. - `GetExportSpecification()` (`:626`) is the synchronous half: it returns `null` by default and is what `GetReadSpecificationAsync` returns by default (`:599`). A controller that can build its scope without awaiting overrides this one and gets all five actions scoped; a controller that needs an `await` overrides the async hook instead, and then this one is no longer consulted (`:606-613`). The remarks are blunt about the consequence of overriding neither on a row-scoped controller (`:614-619`): it hands every caller the whole table in one request. See [ADR-033](https://ivanball.github.io/docs/adr/033-resource-ownership-authorization.html) for the ownership model this hook reproduces in the query. -- **Concept introduced: handing the client a precondition token on the way out.** `[Rubric §8, Data Architecture]` and `[Rubric §9, API & Contract Design]`. `SetConcurrencyETag(object? dto)` (`:471`) emits the read's concurrency token as a weak `ETag` so a client can hand it straight back as an `If-Match` precondition on the next write (see [SupportsIfMatchAttribute](#supportsifmatchattribute)) instead of round-tripping it through the request body. Three details make it cheap and honest. - - `RowVersionProperty` (`:445-449`) is resolved **once per closed controller type** because a DTO's shape cannot change at runtime: the first public instance `byte[]` property named exactly `RowVersion`. For a DTO with no concurrency token it is null, which makes the whole method a no-op with no per-request reflection at all (`:473-474`). - - `ReadRowVersion` (`:488`) handles both shapes a served row can take: a typed DTO, read through the cached `PropertyInfo` (`:490-491`), or a **shaped dictionary** keyed by JSON names when the caller asked for a field projection, probed under both `rowVersion` and `RowVersion` (`:493-496`). - - A DTO without a token gets no header at all (`:476-477`). The remark says why (`:458-463`): absent is the correct answer for a resource that has no version to condition on, and a fabricated tag would invite preconditions the write side cannot honour. The method is `protected` rather than private (`:464-469`) so a derived controller serving a row from a custom read action emits the identical header instead of re-implementing the reflection and the base64 format. -- **Concept introduced: streaming a bulk extract from a paged read.** `[Rubric §12, Performance & Scalability]` assesses whether a large response is bounded and whether memory grows with the result set; `[Rubric §11, Security]` covers who may pull a whole table in one request. `ExportAsync` answers the "export what you filtered" request without any new query path ([ADR-078](https://ivanball.github.io/docs/adr/078-csv-export-endpoint.html)). The action itself is now a thin adapter over [EntityCsvExporter](#entitycsvexportertentitydto), which owns the page loop, the column resolution, and the CSV format; this controller keeps only the HTTP concerns. - - **A dedicated route, not content negotiation** (`[HttpGet("export")]`, `:245`). The remarks name the two behaviors that make an `Accept: text/csv` formatter wrong here (`:196-203`): the public output-cache policy varies by query string but not by `Accept`, so a cached JSON body could be replayed to a CSV request, and `AddAPI` sets `ReturnHttpNotAcceptable = false` (`MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:49`), so a negotiation miss falls back to JSON silently instead of returning 406. A distinct path has neither failure mode. - - **Field validation before a byte moves** (`:257-259`): `EntityCsvExporter.ValidateFields` ([EntityCsvExporter](#entitycsvexportertentitydto)`.ValidateFields`) rejects a `fields=` request naming a property the CSV cannot render, as an `Error.InvalidEntityField` failure, rather than quietly dropping the column. - - **Row scoping through the shared hook**: the specification is resolved once, before the first page (`:262`), so the same closed-over instance filters every page the fetch delegate passed to `WriteAsync` reads, and it is the *same* hook the list endpoints read, so an export can no longer drift wider than the list it mirrors (`:228-235`, doc-comment range). - - **The page loop lives in the exporter now**: `ExportAsync` builds a fetch delegate that calls `QueryService.GetAllAsync` (`:267-280`) at `pageSize = Math.Max(1, MaxPageSize)` (`:265`) and hands it, `beginResponse`, and `onFailureAfterStart` to `EntityCsvExporter.WriteAsync` (`:267`); the loop, its three termination conditions, and the truncation bookkeeping are documented on that type. - - **Headers first, then body** (`BeginExportResponse`, `:566`): content type `text/csv; charset=utf-8` (`CsvContentType`, `:463`, set at `:570`), a `Content-Disposition` attachment whose file name is `{controller}-{yyyyMMdd'T'HHmmss'Z'}.csv` built invariantly from an injected `TimeProvider` (`:568`, `:571`, `BuildExportFileName` at `:582-585`, prefix at `:481-486`), and `X-Export-Row-Limit` (`ExportRowLimitHeaderName`, `:469`, appended at `:572`) so a client can tell "exactly at the limit" from "coincidentally that many rows". - - **Truncation rides in the body.** Headers freeze the moment the first body byte flushes, so a truncated export ends with a `# export truncated at N rows` record (marker built by [EntityCsvExporter](#entitycsvexportertentitydto)`.TruncationMarker`) and a mid-stream query failure ends with an incomplete marker plus a Warning log (`LogExportPageFailure`, `:593-604`, wired as `onFailureAfterStart` at `:285`). A failure on the FIRST page, before anything was written, still returns Problem Details through `HandleFailure` (`:289`), which is exactly what "constructing the writer sends nothing" protects on the exporter side. + - Primary constructor (`EntityControllerBase.cs:34-39`) takes the query service and an `ILogger`, both null-guarded into the `QueryService` (`:45`) and `Logger` (`:50`) protected properties. + - `MaxPageSize` (`:59-66`) resolves `IOptions` per-request from `HttpContext.RequestServices`, falling back to 500, and clamps the result to the range 1 to `EntityQueryPipeline.MaxUnboundedResultLimit` (`:64`; the ceiling is 1000 at `MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/EntityQueryPipeline.cs:23`). The pipeline caps every page at that ceiling anyway, so the clamp keeps the pagination metadata and the export loop from assuming a page size the pipeline does not serve (`:53-58`). Per-request resolution means a settings change takes effect without a restart. `MaxExportRows` (`:79-87`) does the same for the export ceiling, with the extra rule that a configured value of zero or less is treated as unconfigured (`:84-85`), because a cap of zero would silently serve every caller a header-only file (`:75-77`). `EntityName` (`:92`) is `typeof(TEntity).Name`, used in log messages and in the export refusal. + - `GetAllAsync` unpaged (`:110`, `[HttpGet]` at `:107`): resolves the read specification (`:116`), then delegates to the query service with `pageNumber: 1` and `pageSize: MaxPageSize` (`:123-124`), so even the "all" endpoint is capped, then either `HandleFailure` or `Ok`. + - `GetAllAsync` paged (`:158`, `[HttpGet("paged")]` at `:154`): clamps with `Math.Min(pageSize, MaxPageSize)` (`:169`), and on success serializes [PaginationMetadata](group-01-result-error-handling.md#paginationmetadata) into the `X-Pagination` response header (`:188`) rather than mixing it into the body, `[Rubric §9]` again, and `[Rubric §12, Performance & Scalability]` for the clamp. + - `ExportAsync` (`:257`, `[HttpGet("export")]` at `:252`) is covered as its own concept below. + - `GetAllForLookupAsync` (`:320`, `[HttpGet("lookup")]` at `:317`): returns `CollectionResult>`, a lightweight id/label pair, with a `[Required]` `nameProperty` (`:321`) choosing the label. + - `GetByIdAsync` (`:361`, `[HttpGet("{id}")]` at `:356`): `includeFKs` defaults to `true` for the single-entity case (`:363`), and on success it calls `SetConcurrencyETag(result.Value)` **before** returning (`:382-383`). + - `HandleFailure` override (`:451-465`): logs the first error at Warning, guarded by `Logger.IsEnabled` (`:454`), before delegating to [ApiControllerBase](#apicontrollerbase)`.HandleFailure`, so the read path gets observability (`[Rubric §13, Observability & Operability]`) without changing the response mapping. Note it logs the *first* error while the base ranks the status by the most severe one: the log line is a breadcrumb, not the status decision. + - Every read action passes `asTracking: false` to the query service (for example `:125`), because a read endpoint never mutates what it loaded. +- **Concept introduced: one row-scoping hook for every read the controller serves.** `[Rubric §11, Security]` assesses whether an authorization decision can be reproduced consistently across every path that returns the same rows; `[Rubric §15, Best Practices & Code Quality]` assesses whether that decision lives in one place. `GetReadSpecificationAsync(cancellationToken)` (`:571-573`) returns the [Specification](group-03-querying-specifications.md#specificationtentity-tidentifiertype) applied by **all five** read actions: both `GetAllAsync` overloads, `GetAllForLookupAsync`, `GetByIdAsync`, and `ExportAsync` (each resolves it at `:116`, `:171`, `:324`, `:368`, and `:270`). Four properties are worth internalizing, all stated on the hook's own doc (`:535-570`). + - **It is asynchronous because row scoping usually is** (`:547-552`). Scope is rarely a pure function of the current principal: it comes from a query handler, a claim lookup that hits a store, a tenancy read. A synchronous hook forced such a controller to override all five actions by hand purely to get an `await` in before the query. An override with nothing to await returns `ValueTask.FromResult(...)` and allocates nothing. + - **It narrows, it never replaces** (`:555-559`). The query service ANDs the specification with the caller's `filters` dictionary and `fields` projection, so a caller can only narrow what the specification already allows; a filter naming excluded rows yields an empty page rather than leaking them (`:139-142`). + - **A rejected row is a 404, not a 403** (`:562-564`, restated on `GetByIdAsync` at `:350-355`). The specification narrows the query, so the row is simply absent. Answering "forbidden" would confirm the id exists and turn a scoped read into an existence oracle a caller could walk. + - **The lookup endpoint carries the scope as a predicate** (`:311-316`). `GetAllForLookupAsync` has no specification parameter, so the scope travels as `specification?.Criteria` (`:328`); a null specification passes a null predicate, the unscoped query this endpoint always issued. A dropdown that lists what the list endpoint hides would be an oracle of its own. + `GetExportSpecification()` (`:601`) is the synchronous half: it returns `null` by default and is what `GetReadSpecificationAsync` returns by default (`:573`). A controller that can build its scope without awaiting overrides this one and gets all five actions scoped; a controller that needs an `await` overrides the async hook instead, and then this one is no longer consulted (`:583-587`). For the four JSON reads a null specification still means unscoped; for the export it means refused (next concept), so a row-scoped controller that overrides neither hook gets a 403 on `/export` rather than handing every caller the whole table (`:590-593`). See [ADR-033](https://ivanball.github.io/docs/adr/033-resource-ownership-authorization.html) for the ownership model this hook reproduces in the query. +- **Concept introduced: a bulk read that fails closed when no scope was declared.** `[Rubric §11, Security]` assesses whether the default on a sensitive path is the safe one. `ExportAsync` is the one read that can hand a caller a whole table in one request, so it does not treat a null specification as "unscoped": when the hook resolves to `null` and `AllowUnscopedExport` is `false`, it returns before any query runs (`:273-274`) with the `Error.Forbidden` built by `UnscopedExportRefused` (`:608-613`), which `HandleFailure` turns into a 403 Problem Details carrying the code `ExportRowScopeRequiredErrorCode = "Export.RowScopeRequired"` (`:487`; the 403 is declared on the action at `:255`). The error message names the three ways out: override `GetReadSpecificationAsync`, override `GetExportSpecification`, or opt in. + - `AllowUnscopedExport` (`protected virtual bool`, default `false`, `:508`) is the deliberate opt-in. Its doc (`:489-507`) limits it to controllers whose rows every caller allowed to reach the endpoint may see (reference data, a catalog, an admin-only table already behind a role gate), and notes it is read per request, so it may depend on the principal: return `null` from the read hook for an administrator and a specification for everyone else, and opt in only for the administrator. + - A non-null specification never consults the property (`:504-505`): a scoped export needs no opt-in. The effect is that a controller which forgot to scope its export serves no rows rather than every row (`:231-238`). +- **Concept introduced: handing the client a precondition token on the way out.** `[Rubric §8, Data Architecture]` and `[Rubric §9, API & Contract Design]`. `SetConcurrencyETag(object? dto)` (`:417`) emits the read's concurrency token as a weak `ETag` so a client can hand it straight back as an `If-Match` precondition on the next write (see [SupportsIfMatchAttribute](#supportsifmatchattribute)) instead of round-tripping it through the request body. Three details make it cheap and honest. + - `RowVersionProperty` (`:391-395`) is resolved **once per closed controller type** because a DTO's shape cannot change at runtime: the first public instance `byte[]` property named exactly `RowVersion`. For a DTO with no concurrency token it is null, which makes the whole method a no-op with no per-request reflection at all (`:419-420`). + - `ReadRowVersion` (`:434`) handles both shapes a served row can take: a typed DTO, read through the cached `PropertyInfo` (`:436-437`), or a **shaped dictionary** keyed by JSON names when the caller asked for a field projection, probed under both `rowVersion` and `RowVersion` (`:439-442`). + - A DTO without a token gets no header at all (`:422-423`). The remark says why (`:406-408`): absent is the correct answer for a resource that has no version to condition on, and a fabricated tag would invite preconditions the write side cannot honour. The method is `protected` rather than private (`:411-414`) so a derived controller serving a row from a custom read action emits the identical header instead of re-implementing the reflection and the base64 format. +- **Concept introduced: streaming a bulk extract from a paged read.** `[Rubric §12, Performance & Scalability]` assesses whether a large response is bounded and whether memory grows with the result set; `[Rubric §11, Security]` covers who may pull a whole table in one request. `ExportAsync` answers the "export what you filtered" request without any new query path ([ADR-078](https://ivanball.github.io/docs/adr/078-csv-export-endpoint.html)). The action itself is a thin adapter over [EntityCsvExporter](#entitycsvexportertentitydto), which owns the page loop, the column resolution, and the CSV format; this controller keeps only the HTTP concerns. + - **A dedicated route, not content negotiation** (`[HttpGet("export")]`, `:252`). The remarks name the two behaviors that make an `Accept: text/csv` formatter wrong here (`:199-204`): the public output-cache policy varies by query string but not by `Accept`, so a cached JSON body could be replayed to a CSV request, and `AddAPI` sets `ReturnHttpNotAcceptable = false` (`MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:49`), so a negotiation miss falls back to JSON silently instead of returning 406. A distinct path has neither failure mode. + - **Field validation before a byte moves** (`:265-267`): [EntityCsvExporter](#entitycsvexportertentitydto)`.ValidateFields` rejects a `fields=` request naming a property the CSV cannot render, as an `Error.InvalidEntityField` failure, rather than quietly dropping the column. + - **Row scoping through the shared hook, fail-closed**: the specification is resolved once, before the first page (`:270`), and refused when null without the opt-in (`:273-274`, previous concept), so the same closed-over instance filters every page the fetch delegate reads, and it is the *same* hook the list endpoints read, so an export cannot drift wider than the list it mirrors. + - **The page loop lives in the exporter**: `ExportAsync` builds a fetch delegate that calls `QueryService.GetAllAsync` (`:281-292`) at `pageSize = Math.Max(1, MaxPageSize)` (`:277`) and hands it, `beginResponse`, and `onFailureAfterStart` to `EntityCsvExporter.WriteAsync` (`:279`); the loop, its termination conditions, and the truncation bookkeeping are documented on that type. + - **Headers first, then body** (`BeginExportResponse`, `:619`): content type `text/csv; charset=utf-8` (`CsvContentType`, `:475`, set at `:623`), a `Content-Disposition` attachment whose file name is `{controller}-{yyyyMMdd'T'HHmmss'Z'}.csv` built invariantly from an injected `TimeProvider` (`:621`, `:624`, `BuildExportFileName` at `:635-638`, prefix `ExportFileNamePrefix` at `:520-533`), and `X-Export-Row-Limit` (`ExportRowLimitHeaderName`, `:481`, appended at `:625`) so a client can tell "exactly at the limit" from "coincidentally that many rows". + - **Truncation rides in the body.** Headers freeze the moment the first body byte flushes, so a truncated export ends with a `# export truncated at N rows` record (marker built by [EntityCsvExporter](#entitycsvexportertentitydto)`.TruncationMarker`) and a mid-stream query failure ends with an incomplete marker plus a Warning log (`LogExportPageFailure`, `:646-658`, wired as `onFailureAfterStart` at `:297`). A failure on the FIRST page, before anything was written, still returns Problem Details through `HandleFailure` (`:301`), which is exactly what "constructing the writer sends nothing" protects on the exporter side. - **Column resolution is derived, not invented**: `EntityCsvExporter.ResolveColumns` takes the shaped keys of the first row, so the CSV columns for a given `fields=` request are the same camelCase JSON names the JSON endpoints emit; an empty result still gets a header row built from the DTO's own properties in declaration order. `UnexportablePropertyNames` on that type drops the properties that cannot render a faithful scalar cell, `byte[]`/`ReadOnlyMemory` concurrency tokens and every collection type except `string`, computed once per closed DTO type. Value objects and other class-typed properties are deliberately NOT dropped, because a record or value object has a meaningful invariant `ToString`. See [EntityCsvExporter](#entitycsvexportertentitydto) for the full walkthrough. -- **Why it's built this way**: the controller stays thin. All filtering/sorting/paging lives in [IEntityQueryService](group-03-querying-specifications.md#ientityqueryservicetentity-tentitydto-tidentifiertype), and manual DTO mapping ([ADR-001](https://ivanball.github.io/docs/adr/001-manual-dto-mapping.html)) keeps entities off the wire. The controller only translates HTTP concerns: query strings, headers, status codes. The export reuses the paged read wholesale precisely so it cannot disagree with the grid it was launched from, and the row ceiling (`DefaultMaxExportRows = 100_000`, `:526`, matching `ApplicationSettings.MaxExportRows`'s own default) is a number an operator can reason about rather than an unbounded connection hold. -- **Caveats / not-in-source**: the export carries no attributes of its own. It inherits whatever `[Authorize]` or `[FeatureGate]` the derived controller declares, so a controller that exposes `paged` anonymously exposes `export` anonymously; the symmetry is deliberate and documented (`:205-211`). The export is also not output-cached. -- **Where it's used**: the base for every read-only module controller, for example ADC's child-collection controllers `SessionSpeakersController` (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionSpeakersController.cs:56`) and `CategoryItemsController` (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:70`). Extended by [AggregateRootEntityControllerBase](#aggregaterootentitycontrollerbasetentity-tentitydto-tidentifiertype-tcreaterequest) for entities that also create and delete. The scoping hooks are widely overridden today: the async `GetReadSpecificationAsync` by ADC's `SpeakersController` (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:95`), `SessionsController` (`SessionsController.cs:75`), `RoomsController` (`RoomsController.cs:120`) and their child controllers, and by Store's owner-scoped `ShoppingCartsController` (`MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/ShoppingCartsController.cs:206`); the synchronous `GetExportSpecification` by ADC's `EventsController` (`EventsController.cs:69`) and `SessionQuestionAnswersController` (`SessionQuestionAnswersController.cs:107`) and by Store's `OrdersController` (`MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/OrdersController.cs:244`). Store's `CustomersController` deliberately overrides neither and records why (its list endpoints are Admin-only rather than row-scoped, so there is no ownership specification to reproduce, `MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.API/Controllers/CustomersController.cs:86-100`). Framework coverage lives in `EntityControllerBaseTests`, `EntityControllerBaseExportTests`, `EntityControllerBaseETagTests`, and `EntityControllerBaseReadSpecificationTests` (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Controllers/`), the last of which drives both hooks through purpose-built doubles (`EntityControllerBaseReadSpecificationTests.cs:356` and `:377`). +- **Why it's built this way**: the controller stays thin. All filtering/sorting/paging lives in [IEntityQueryService](group-03-querying-specifications.md#ientityqueryservicetentity-tentitydto-tidentifiertype), and manual DTO mapping ([ADR-001](https://ivanball.github.io/docs/adr/001-manual-dto-mapping.html)) keeps entities off the wire. The controller only translates HTTP concerns: query strings, headers, status codes. The export reuses the paged read wholesale precisely so it cannot disagree with the grid it was launched from, and the row ceiling (`DefaultMaxExportRows = 100_000`, `:472`, matching `ApplicationSettings.MaxExportRows`'s own default) is a number an operator can reason about rather than an unbounded connection hold. The fail-closed default puts the cost of a forgotten scope on the controller author (a 403 in their own tests) instead of on the data. +- **Caveats / not-in-source**: the export carries no authorization attributes of its own. It inherits whatever `[Authorize]` or `[FeatureGate]` the derived controller declares, so a controller that exposes `paged` anonymously exposes `export` anonymously once it scopes the rows or opts in; the symmetry is deliberate and documented (`:207-211`). The fail-closed rule applies to `ExportAsync` only: the four JSON reads still run unscoped on a null specification. The export is also not output-cached. +- **Where it's used**: the base for every read-only module controller, for example ADC's child-collection controllers `SessionSpeakersController` (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionSpeakersController.cs:56`) and `CategoryItemsController` (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:70`). Extended by [AggregateRootEntityControllerBase](#aggregaterootentitycontrollerbasetentity-tentitydto-tidentifiertype-tcreaterequest) for entities that also create and delete. The scoping hooks are widely overridden: the async `GetReadSpecificationAsync` by ADC's `SpeakersController` (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:96`), `SessionsController` (`SessionsController.cs:75`), `RoomsController` (`RoomsController.cs:120`) and their child controllers, and by Store's owner-scoped `ShoppingCartsController` (`MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/ShoppingCartsController.cs:200`); the synchronous `GetExportSpecification` by ADC's `EventsController` (`EventsController.cs:69`) and `SessionQuestionAnswersController` (`SessionQuestionAnswersController.cs:107`) and by Store's `OrdersController` (`MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/OrdersController.cs:241`). `AllowUnscopedExport` is overridden two ways. Unconditional `true` on reference and catalog data: ADC's `ConferenceCategoriesController` (`ConferenceCategoriesController.cs:47`), `QuestionsController` (`QuestionsController.cs:46`) and `CategoryItemsController` (`CategoryItemsController.cs:73`), and Store's `ProductsController` (`MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.API/Controllers/ProductsController.cs:51`), `CategoriesController` (`CategoriesController.cs:48`), `InventoryItemsController` (`InventoryItemsController.cs:43`) and `CustomersController` (`MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.API/Controllers/CustomersController.cs:48`), whose export sits behind `CustomersManage` and whose list endpoints are Admin-only rather than row-scoped (`:90-104`). Per principal, where the read hook returns `null` to a privileged caller: ADC's `SpeakersController` (`SpeakersController.cs:106`), `SessionsController` (`SessionsController.cs:93`), `RoomsController` (`RoomsController.cs:138`), `EventsController` (`EventsController.cs:77`), `SessionQuestionAnswersController` (`SessionQuestionAnswersController.cs:119`) and their siblings, and Store's `OrdersController` (`OrdersController.cs:249`) and `ShoppingCartsController` (`ShoppingCartsController.cs:209`). Framework coverage lives in `EntityControllerBaseTests`, `EntityControllerBaseExportTests` (opt-in doubles at `EntityControllerBaseExportTests.cs:534` and `:652`), `EntityControllerBaseETagTests`, and `EntityControllerBaseReadSpecificationTests` (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Controllers/`), the last of which drives both hooks through purpose-built doubles (`EntityControllerBaseReadSpecificationTests.cs:358` and `:377`). ### OAuthControllerBase > MMCA.Common.API · `MMCA.Common.API.Controllers` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/OAuthControllerBase.cs:35` · Level 6 · class (abstract) - **What it is**: the base controller for external OAuth2 sign-in (Google, GitHub, Apple). It runs the challenge/callback/complete/exchange dance so a browser or native head can log in through a provider and receive a local JWT pair without ever exposing tokens in a redirect URL. -- **Depends on**: [IAuthenticationService](group-08-auth.md#iauthenticationservice) (`ExternalLoginAsync`), [ICacheService](group-09-caching.md#icacheservice), `IConfiguration`, [ExternalAuthExtensions](#externalauthextensions) (the scheme constant, `OAuthControllerBase.cs:42`), [NonIdempotentAttribute](#nonidempotentattribute), [AuthenticationResponse](group-08-auth.md#authenticationresponse), [OAuthCodeExchangeRequest](group-08-auth.md#oauthcodeexchangerequest), and [Error](group-01-result-error-handling.md#error); the Google, GitHub, and Apple OAuth packages and `System.Security.Cryptography`. -- **Concept introduced: the code-exchange OAuth completion pattern.** `[Rubric §11, Security]` assesses how credentials move through the system; the design's whole point is that the redirect after a successful provider login carries only a single-use opaque code, never the access/refresh tokens, so tokens never land in the address bar, browser history, the `Referer` header, or upstream access logs (`OAuthControllerBase.cs:286-288`). `[Rubric §7, Microservices Readiness]`: the base is hoisted from the app hosts so every service reuses the identical flow, with the sealed subclass supplying only `[Route("auth/oauth")]` and versioning. See [ADR-036](https://ivanball.github.io/docs/adr/036-external-oauth-login.html). +- **Depends on**: [IAuthenticationService](group-08-auth.md#iauthenticationservice) (`ExternalLoginAsync`), [ICacheService](group-09-caching.md#icacheservice) (`SetAsync`, `GetFromSharedStoreAsync`, `RemoveAsync`), `IConfiguration`, [ExternalAuthExtensions](#externalauthextensions) (the scheme constant, `OAuthControllerBase.cs:40`), [NonIdempotentAttribute](#nonidempotentattribute), [AuthenticationResponse](group-08-auth.md#authenticationresponse), [OAuthCodeExchangeRequest](group-08-auth.md#oauthcodeexchangerequest), and [Error](group-01-result-error-handling.md#error); the Google, GitHub, and Apple OAuth packages and `System.Security.Cryptography`. +- **Concept introduced: the code-exchange OAuth completion pattern.** `[Rubric §11, Security]` assesses how credentials move through the system; the design's whole point is that the redirect after a successful provider login carries only a single-use opaque code, never the access/refresh tokens, so tokens never land in the address bar, browser history, the `Referer` header, or upstream access logs (`OAuthControllerBase.cs:138-140`). `[Rubric §7, Microservices Readiness]`: the base is hoisted from the app hosts so every service reuses the identical flow, with the sealed subclass supplying only `[Route("auth/oauth")]` and versioning, and the burn check reads the shared cache store so the single-use guarantee holds across replicas. See [ADR-036](https://ivanball.github.io/docs/adr/036-external-oauth-login.html). - **Walkthrough** - - `OAuthExchangeCodePrefix` and a 2-minute `OAuthExchangeCodeLifetime` (`OAuthControllerBase.cs:193-198`) namespace and time-box the server-side token stash; the short TTL matches the single redirect-then-POST round trip. A second constant, `ClientStateItemKey = "clientState"` (`:197`), names a separate `AuthenticationProperties.Items` slot for the client's own opaque per-attempt value, deliberately not called `"state"` because that name belongs to the OAuth handler's own protocol value (`:195-196`). - - `GoogleLogin` (`:207`), `GitHubLogin` (`:217`), and `AppleLogin` (`:230`) each now take an optional `state` query parameter alongside `returnUrl` and carry `[AllowAnonymous]` directly (`:206`, `:216`, `:229`); all three call `ChallengeProvider` (`:449`), which stashes both `returnUrl` and the client's `state` under `ClientStateItemKey` in `AuthenticationProperties.Items` and sets `RedirectUri = "/auth/oauth/complete"` (`:451-463`). Apple is the newest of the three and needs no special action here: its callback arrives as a cross-site form POST (`response_mode=form_post` is forced by the name/email scopes) that the middleware handles at `/auth/callback/apple` like any other provider (`:221-224`). - - `CompleteAsync` (`:248`) also carries `[AllowAnonymous]` now (`:246`). After the middleware handles the provider callback, it reads the external cookie (`:251`), redirects to `/login?error=oauth_failed` when the ticket did not survive (`:253-258`), reads both the stashed `returnUrl` and `clientState` via `ReadChallengeState` (`:263`, `:299-300`), extracts provider claims (`ExtractClaims`, `:266`), calls `ExternalLoginAsync` to find or create the local user and mint tokens (`:273-274`), signs out the temporary external cookie (`:284`), then mints a 32-byte hex `exchangeCode` (`:289`), stashes the token pair in the cache under it (`:290-291`), and redirects with the code and, when the client sent one, the echoed `state` (`:293`, URL built by `BuildSuccessRedirectUrl` at `:302-316`). - - `ReadChallengeState` (`:299-300`) is a safe-lookup helper (`GetString` is `TryGetValue` under the hood): the challenge normally stashes both `returnUrl` and `clientState`, but a ticket minted without them (custom challenge, provider round-trip edge) falls back to `"/"` and `null` instead of throwing on the `Items` indexer. - - `BuildSuccessRedirectUrl` (`:302-316`) appends `&state=` (URL-escaped) only when `clientState` is non-empty (`:309-311`), for both the web redirect and the native `AppendQuery` branch (`:313-315`), so callers that never sent a `state` see no change to the redirect shape. - - Name handling is defensive: `ExtractName` (`:372`) prefers `GivenName`/`Surname` claims and otherwise splits the `Name` claim, falling back to `("User", "")` when there is no usable space-separated name (`:386-400`), so a provider that returns only a display name still yields a creatable local account. That fallback matters most for Apple, which returns name claims on the *first* authorization only. - - Native heads ([ADR-043](https://ivanball.github.io/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html)): `GetAllowedMobileReturnUrl` (`:424`) returns the stashed `returnUrl` as the redirect target only when it is an absolute URI whose custom scheme is listed in `OAuth:AllowedReturnUrlSchemes`; http/https never match (`:426-428`), so the allowlist cannot become an open redirect, and a missing or empty section (or a test double returning `null` from `GetSection`) means "no allowlist", the exact pre-ADR-043 behavior (`:433-437`). Failures route to the same surface through `RedirectError` (`:412-415`), so a native window closes on an error instead of stranding on a web login page. - - `ExchangeAsync` (`:329`): `[HttpPost("exchange")]` with `[NonIdempotent(...)]` and `[AllowAnonymous]` (`:326-327`); the UI swaps the code for the real [AuthenticationResponse](group-08-auth.md#authenticationresponse) out-of-band. Because that response is a struct, a cache miss yields a default value rather than `null`, so the miss is detected via an empty `AccessToken` (`:340-346`). The code is then removed (`:349`), making it single-use so a leaked or replayed code cannot mint a second token pair. Both failure paths return the same opaque 400 "Invalid sign-in code" (`InvalidCode`, `:354-360`). -- **Why it's built this way**: carrying tokens in a redirect is the classic OAuth token-leak vector; the single-use code plus a short-lived server-side stash closes it while keeping the client flow a plain redirect and one POST. The `[NonIdempotent]` justification on `ExchangeAsync` (`:326`) records why this one endpoint must stay outside the replay contract: replaying the stored response would defeat the burn, letting a leaked code mint the same tokens again for the whole retention window. The `AppendQuery` helper (`:440`) deliberately uses `OriginalString` rather than `ToString()`, because `Uri` normalization appends a trailing slash to authority-only URIs (e.g. `myapp://oauth-complete`) and native authenticator callback matching can be exact (`:442-444`). The client-supplied `state` is carried opaquely: the server never interprets or trusts it, only stores and echoes it back so the client can bind the completion to the flow it started (`:458-461`). -- **Caveats / not-in-source**: the provider scheme registration and the concrete `ExternalLoginAsync` implementation live outside this base ([ExternalAuthExtensions](#externalauthextensions) and the app's [IAuthenticationService](group-08-auth.md#iauthenticationservice)); this file assumes both are wired. The complete and exchange actions are hidden from OpenAPI with `[ApiExplorerSettings(IgnoreApi = true)]` (`:247`, `:328`) because they are browser redirects, not a documented client contract. -- **Where it's used**: subclassed by each app's sealed OAuth controller, today ADC's `OAuthController` (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/OAuthController.cs:20`), which adds only the class-level routing and versioning attributes. `ExchangeAsync` is one of the endpoints the framework's anonymous-endpoint architecture gate lists by name (`MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Api/AnonymousEndpointTests.cs:31`), so its `[AllowAnonymous]` is an approved exception rather than an oversight. Framework coverage is `OAuthControllerBaseTests` (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Controllers/OAuthControllerBaseTests.cs`). + - `OAuthExchangeCodePrefix` and a 2-minute `OAuthExchangeCodeLifetime` (`OAuthControllerBase.cs:45-50`) namespace and time-box the server-side token stash; the short TTL matches the single redirect-then-POST round trip. A second constant, `ClientStateItemKey = "clientState"` (`:49`), names a separate `AuthenticationProperties.Items` slot for the client's own opaque per-attempt value, deliberately not called `"state"` because that name belongs to the OAuth handler's own protocol value (`:47-48`). + - `GoogleLogin` (`:59`), `GitHubLogin` (`:69`), and `AppleLogin` (`:82`) each take an optional `state` query parameter alongside `returnUrl` and carry `[AllowAnonymous]` directly (`:58`, `:68`, `:81`); all three call `ChallengeProvider` (`:303`), which stashes both `returnUrl` and the client's `state` under `ClientStateItemKey` in `AuthenticationProperties.Items` and sets `RedirectUri = "/auth/oauth/complete"` (`:305-317`). Apple needs no special action here: its callback arrives as a cross-site form POST (`response_mode=form_post` is forced by the name/email scopes) that the middleware handles at `/auth/callback/apple` like any other provider (`:73-76`). + - `CompleteAsync` (`:100`) carries `[AllowAnonymous]` (`:98`). After the middleware handles the provider callback, it reads the external cookie (`:103`), redirects to `/login?error=oauth_failed` when the ticket did not survive (`:105-110`), reads both the stashed `returnUrl` and `clientState` via `ReadChallengeState` (`:115`, `:151-152`), extracts provider claims (`ExtractClaims`, `:118`), calls `ExternalLoginAsync` to find or create the local user and mint tokens (`:125-126`), signs out the temporary external cookie (`:136`), then mints a 32-byte hex `exchangeCode` (`:141`), stashes the token pair in the cache under it (`:142-143`), and redirects with the code and, when the client sent one, the echoed `state` (`:145`, URL built by `BuildSuccessRedirectUrl` at `:154-168`). + - `ReadChallengeState` (`:151-152`) is a safe-lookup helper (`GetString` is `TryGetValue` under the hood): the challenge normally stashes both `returnUrl` and `clientState`, but a ticket minted without them (custom challenge, provider round-trip edge) falls back to `"/"` and `null` instead of throwing on the `Items` indexer. + - `BuildSuccessRedirectUrl` (`:154-168`) appends `&state=` (URL-escaped) only when `clientState` is non-empty (`:161-163`), for both the web redirect and the native `AppendQuery` branch (`:165-167`), so callers that never sent a `state` see no change to the redirect shape. + - Name handling is defensive: `ExtractName` (`:226`) prefers `GivenName`/`Surname` claims and otherwise splits the `Name` claim through `SplitFullName`, falling back to `("User", "")` when there is no usable space-separated name (`:240-251`), so a provider that returns only a display name still yields a creatable local account. That fallback matters most for Apple, which returns name claims on the *first* authorization only. + - Native heads ([ADR-043](https://ivanball.github.io/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html)): `GetAllowedMobileReturnUrl` (`:278`) returns the stashed `returnUrl` as the redirect target only when it is an absolute URI whose custom scheme is listed in `OAuth:AllowedReturnUrlSchemes`; http/https never match (`:280-282`), so the allowlist cannot become an open redirect, and a missing or empty section (or a test double returning `null` from `GetSection`) means "no allowlist", the exact pre-ADR-043 behavior (`:287-290`). Failures route to the same surface through `RedirectError` (`:266-269`), so a native window closes on an error instead of stranding on a web login page. + - `ExchangeAsync` (`:181`): `[HttpPost("exchange")]` with `[NonIdempotent(...)]` and `[AllowAnonymous]` (`:177-179`); the UI swaps the code for the real [AuthenticationResponse](group-08-auth.md#authenticationresponse) out-of-band. The lookup goes through `ICacheService.GetFromSharedStoreAsync` (`:196`) rather than the ordinary read: a code already burned on another replica must be a miss here, not a stale local copy that would mint a second token pair (`:194-195`). Because the response is a struct, a cache miss yields a default value rather than `null`, so the miss is detected via an empty `AccessToken` (`:197-200`). The code is then removed (`:203`), making it single-use so a leaked or replayed code cannot mint a second token pair. Both failure paths return the same opaque 400 "Invalid sign-in code" (`InvalidCode`, `:208-214`). +- **Why it's built this way**: carrying tokens in a redirect is the classic OAuth token-leak vector; the single-use code plus a short-lived server-side stash closes it while keeping the client flow a plain redirect and one POST. Reading the stash from the shared store keeps "single-use" true when the API runs on more than one replica, where a per-node copy could otherwise outlive the burn. The `[NonIdempotent]` justification on `ExchangeAsync` (`:178`) records why this one endpoint must stay outside the replay contract: replaying the stored response would defeat the burn, letting a leaked code mint the same tokens again for the whole retention window. The `AppendQuery` helper (`:294`) deliberately uses `OriginalString` rather than `ToString()`, because `Uri` normalization appends a trailing slash to authority-only URIs (e.g. `myapp://oauth-complete`) and native authenticator callback matching can be exact (`:296-298`). The client-supplied `state` is carried opaquely: the server never interprets or trusts it, only stores and echoes it back so the client can bind the completion to the flow it started (`:312-314`). +- **Caveats / not-in-source**: the provider scheme registration and the concrete `ExternalLoginAsync` implementation live outside this base ([ExternalAuthExtensions](#externalauthextensions) and the app's [IAuthenticationService](group-08-auth.md#iauthenticationservice)); this file assumes both are wired. How `GetFromSharedStoreAsync` bypasses a local tier is the cache implementation's concern ([ICacheService](group-09-caching.md#icacheservice)), not this file's. The complete and exchange actions are hidden from OpenAPI with `[ApiExplorerSettings(IgnoreApi = true)]` (`:99`, `:180`) because they are browser redirects, not a documented client contract. +- **Where it's used**: subclassed by each app's sealed OAuth controller, today ADC's `OAuthController` (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/OAuthController.cs:20`), which adds only the class-level routing and versioning attributes. `ExchangeAsync` is one of the endpoints the framework's anonymous-endpoint architecture gate lists by name (`MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Api/AnonymousEndpointTests.cs:31`), so its `[AllowAnonymous]` is an approved exception rather than an oversight. Framework coverage is `OAuthControllerBaseTests` (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Controllers/Auth/OAuthControllerBaseTests.cs`), and ADC exercises the exchange end to end in `OAuthExchangeTests` (`MMCA.ADC/Tests/Integration/MMCA.ADC.Identity.IntegrationTests/Auth/OAuthExchangeTests.cs`). ### AggregateRootEntityControllerBase > MMCA.Common.API · `MMCA.Common.API.Controllers` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/AggregateRootEntityControllerBase.cs:28` · Level 7 · class (abstract) - **What it is**: the read-plus-create-plus-delete tier of the controller hierarchy. It extends [EntityControllerBase](#entitycontrollerbasetentity-tentitydto-tidentifiertype) (the read endpoints) by adding a `CreateAsync` (POST) and a `DeleteAsync` (DELETE) for aggregate-root entities. - **Depends on**: [EntityControllerBase](#entitycontrollerbasetentity-tentitydto-tidentifiertype) (base), [IAggregateRootEntityControllerBase](#iaggregaterootentitycontrollerbasetentitydto-tidentifiertype-tcreaterequest) (implements), [ICommandHandler](group-05-cqrs-pipeline.md#icommandhandlerin-tcommand-tresult) (create and delete handlers), [DeleteEntityCommand](group-05-cqrs-pipeline.md#deleteentitycommandtentity-tidentifiertype), [AuditableAggregateRootEntity](group-02-domain-building-blocks.md#auditableaggregaterootentitytidentifiertype) (constraint), [ICreateRequest](group-05-cqrs-pipeline.md#icreaterequest) (constraint), [IdempotentAttribute](#idempotentattribute); ASP.NET Core MVC and `Asp.Versioning`. -- **Concept introduced: idempotent creation guarded at the endpoint.** `[Rubric §9, API & Contract Design]` assesses safe mutation; `CreateAsync` carries `[Idempotent]` (`AggregateRootEntityControllerBase.cs:60`), which wires [IdempotencyFilter](#idempotencyfilter) so a retried POST carrying the same `Idempotency-Key` replays the original 201 (flagged `X-Idempotent-Replay: true`, `IdempotencyFilter.cs:40`) instead of creating a duplicate aggregate, exactly what mobile and flaky-network clients need. A duplicate that arrives while the first request is still running and cannot take the lock within the 5-second `LockWait` (`IdempotencyFilter.cs:104`, awaited at `IdempotencyFilter.cs:250`) is answered with 409 Conflict rather than a replay (`IdempotencyFilter.cs:304-309`). Because the fitness rule reads attributes with `inherit: true`, every concrete controller that inherits this action satisfies its POST idempotency-intent obligation through this base (see [NonIdempotentAttribute](#nonidempotentattribute)). `[Rubric §1, SOLID]`: the four constraints (`AggregateRootEntityControllerBase.cs:41-44`, notably `TEntity : AuditableAggregateRootEntity`) enforce at compile time that only aggregate roots reach this create/delete surface. +- **Concept introduced: idempotent creation guarded at the endpoint.** `[Rubric §9, API & Contract Design]` assesses safe mutation; `CreateAsync` carries `[Idempotent]` (`AggregateRootEntityControllerBase.cs:60`), which wires [IdempotencyFilter](#idempotencyfilter) so a retried POST carrying the same `Idempotency-Key` replays the original 201 (flagged `X-Idempotent-Replay: true`, `IdempotencyFilter.cs:41`) instead of creating a duplicate aggregate, exactly what mobile and flaky-network clients need. A duplicate that arrives while the first request is still running and cannot take the lock within the 5-second `LockWait` (`IdempotencyFilter.cs:105`, awaited at `IdempotencyFilter.cs:251`) is answered with 409 Conflict rather than a replay (`IdempotencyFilter.cs:305-310`). Because the fitness rule reads attributes with `inherit: true`, every concrete controller that inherits this action satisfies its POST idempotency-intent obligation through this base (see [NonIdempotentAttribute](#nonidempotentattribute)). `[Rubric §1, SOLID]`: the four constraints (`AggregateRootEntityControllerBase.cs:41-44`, notably `TEntity : AuditableAggregateRootEntity`) enforce at compile time that only aggregate roots reach this create/delete surface. - **Walkthrough** - Primary constructor (`AggregateRootEntityControllerBase.cs:28-39`): four parameters, where `queryService` and `logger` are forwarded to the [EntityControllerBase](#entitycontrollerbasetentity-tentitydto-tidentifiertype) base (`:38`), plus `createHandler` and `deleteHandler`. The `logger` is typed `ILogger>`, not of this class, because `ILogger` is not covariant and the base ctor requires that exact type; the `#pragma warning disable S6672` (`:35-37`) is a justified, narrowly-scoped suppression documenting exactly that (`[Rubric §15, Best Practices]`). - `CreateHandler` property (`:48`): `protected`, so a derived controller that overrides `CreateAsync` to build a more specific command can still reach the handler. `deleteHandler` stays a captured constructor parameter, used directly at `:93`, because nothing overrides delete today. @@ -1174,21 +1178,20 @@ code stays modules and domain logic, never plumbing. > MMCA.Common.API · `MMCA.Common.API.Controllers` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/AuthControllerBase.cs:43` · Level 15 · class (abstract) - **What it is**: the abstract base for password-based authentication and session management: login, register, refresh, revoke-everywhere, list-my-devices, and revoke-one-device. A downstream module (Identity) inherits it and adds the route prefix, version attribute, and any module-specific endpoints. -- **Depends on**: [ApiControllerBase](#apicontrollerbase) (base), [IAuthenticationService](group-08-auth.md#iauthenticationservice) and [ICurrentUserService](group-08-auth.md#icurrentuserservice) (injected), [LoginRequest](group-08-auth.md#loginrequest), [RegisterRequest](group-08-auth.md#registerrequest), [RefreshTokenRequest](group-08-auth.md#refreshtokenrequest), [AuthenticationResponse](group-08-auth.md#authenticationresponse), [RefreshSessionSummaryResponse](group-08-auth.md#refreshsessionsummaryresponse), [ClaimsPrincipalExtensions](group-08-auth.md#claimsprincipalextensions) (`FindSessionId`), [IdempotentAttribute](#idempotentattribute) and [NonIdempotentAttribute](#nonidempotentattribute), and the `RateLimitPolicyAuthIp` constant on [WebApplicationBuilderExtensions](#webapplicationbuilderextensions); `Microsoft.AspNetCore.RateLimiting` for `[EnableRateLimiting]`. -- **Concept introduced: a secure-by-default base, not just a shared one.** `[Rubric §9, API & Contract Design]` assesses uniform endpoint conventions and `[Rubric §1, SOLID]` the Open/Closed angle (the base provides `virtual` endpoints, a derived controller overrides only what differs), but the load-bearing idea here is `[Rubric §11, Security]`: anti-spray throttling is **on by default**. `LoginAsync` and `RegisterAsync` carry `[EnableRateLimiting(WebApplicationBuilderExtensions.RateLimitPolicyAuthIp)]` (`AuthControllerBase.cs:72` and `:94`), so any consumer inheriting this base gets per-IP protection without opting in. The class doc (`AuthControllerBase.cs:20-29`) records why: the earlier arrangement shipped the policy in the framework and left each app to attach it, and an app that simply inherited these actions silently had no spray protection at all, because the global limiter deliberately no-ops for anonymous traffic and account lockout is per-email ([ADR-019](https://ivanball.github.io/docs/adr/019-rate-limiting.html), [ADR-029](https://ivanball.github.io/docs/adr/029-authentication-brute-force-protection.html)). Every action otherwise shares the same Result-to-ActionResult shape: call the service, check `result.IsFailure`, return `HandleFailure(result.Errors)` or the success result. None carries business logic; they are thin HTTP adapters over [IAuthenticationService](group-08-auth.md#iauthenticationservice). -- **Concept introduced: every POST states its idempotency intent.** This base is the framework's worked example of the [NonIdempotentAttribute](#nonidempotentattribute) rule, and reading its actions together is the fastest way to internalize the distinction. `RegisterAsync` is `[Idempotent]` (`:92`): a retried registration should replay the original 201 rather than fail on a duplicate email. The others carry `[NonIdempotent("...")]` with a written harm: login issues a token pair, so a replayed response would hand a retrying client the tokens minted for an earlier call and extend the lifetime of credentials the caller may already have discarded (`:68`); refresh rotates the refresh token, so a replay would return a token the rotation has already invalidated and hand the client dead credentials (`:116`); and revocation must reach the store on every call, since a replayed 204 would report success for a revoke that never ran and leave a refresh token live (`:143`) or a device signed in (`:206`) after the user asked otherwise. +- **Depends on**: [ApiControllerBase](#apicontrollerbase) (base), [IAuthenticationService](group-08-auth.md#iauthenticationservice) and [ICurrentUserService](group-08-auth.md#icurrentuserservice) (injected), [LoginRequest](group-08-auth.md#loginrequest), [RegisterRequest](group-08-auth.md#registerrequest), [RefreshTokenRequest](group-08-auth.md#refreshtokenrequest), [AuthenticationResponse](group-08-auth.md#authenticationresponse), [RefreshSessionSummaryResponse](group-08-auth.md#refreshsessionsummaryresponse), [ClaimsPrincipalExtensions](group-08-auth.md#claimsprincipalextensions) (`FindSessionId`), [NonIdempotentAttribute](#nonidempotentattribute), and the `RateLimitPolicyAuthIp` constant on [WebApplicationBuilderExtensions](#webapplicationbuilderextensions); `Microsoft.AspNetCore.RateLimiting` for `[EnableRateLimiting]`. +- **Concept introduced: a secure-by-default base, not just a shared one.** `[Rubric §9, API & Contract Design]` assesses uniform endpoint conventions and `[Rubric §1, SOLID]` the Open/Closed angle (the base provides `virtual` endpoints, a derived controller overrides only what differs), but the load-bearing idea here is `[Rubric §11, Security]`: anti-spray throttling is **on by default**. `LoginAsync` and `RegisterAsync` carry `[EnableRateLimiting(WebApplicationBuilderExtensions.RateLimitPolicyAuthIp)]` (`AuthControllerBase.cs:72` and `:96`), so any consumer inheriting this base gets per-IP protection without opting in. The class doc (`AuthControllerBase.cs:21-28`) records why: the earlier arrangement shipped the policy in the framework and left each app to attach it, and an app that simply inherited these actions silently had no spray protection at all, because the global limiter deliberately no-ops for anonymous traffic and account lockout is per-email ([ADR-019](https://ivanball.github.io/docs/adr/019-rate-limiting.html), [ADR-029](https://ivanball.github.io/docs/adr/029-authentication-brute-force-protection.html)). Every action otherwise shares the same Result-to-ActionResult shape: call the service, check `result.IsFailure`, return `HandleFailure(result.Errors)` or the success result. None carries business logic; they are thin HTTP adapters over [IAuthenticationService](group-08-auth.md#iauthenticationservice). +- **Concept introduced: every POST states its idempotency intent, and here every one opts out.** This base is the framework's worked example of the [NonIdempotentAttribute](#nonidempotentattribute) rule: all five POSTs carry `[NonIdempotent("...")]` with a written harm, because each one either mints credentials or must reach the store. Login issues a token pair, so a replayed response would hand a retrying client the tokens minted for an earlier call and extend the lifetime of credentials the caller may already have discarded (`:70`). Register issues a token pair too, so a replay would hand back tokens minted for an earlier call and keep serving them after the session was revoked; a retried registration instead hits the email-exists 409, which the justification names as the correct answer (`:94`, the 409 declared at `:100`). Refresh rotates the refresh token, so a replay would return a token the rotation has already invalidated and hand the client dead credentials (`:118`). Revocation must reach the store on every call, since a replayed 204 would report success for a revoke that never ran and leave a refresh token live (`:145`) or a device signed in (`:208`) after the user asked otherwise. - **Walkthrough** - - Primary constructor (`:41-43`) exposes `AuthenticationService` and `CurrentUserService` as `protected` properties (`:46` and `:49`) so derived controllers can reach them for extra endpoints. - - `ClientIpAddress` (`:56`) and `ClientUserAgent` (`:62`) are the two request facts every write action forwards to the service. The IP is recorded on the refresh session and drives the registration rate limit, and behind a proxy it is the forwarded value only when the host configured `UseForwardedHeaders` (`:51-55`). The user agent is purely informational, so a device list can name the session a user is looking at; nothing validates against it (`:58-61`). - - `LoginAsync` (`:74`): `[HttpPost("login")]`, `[NonIdempotent]`, `[AllowAnonymous]`, throttled per IP (`:67-70`); calls `LoginAsync(request, ClientIpAddress, ClientUserAgent, ...)` (`:78-80`) and returns `Ok` or `HandleFailure`. The `[ProducesResponseType]` attributes (`:71-73`) feed the OpenAPI contract and include the 429 the limiter can produce. - - `RegisterAsync` (`:99`): `[HttpPost("register")]`, `[Idempotent]`, anonymous and throttled (`:91-94`); returns `StatusCode(StatusCodes.Status201Created, ...)` (`:109`), correctly 201 Created for a new account rather than 200. It is `virtual` so a module can override it to inject extra context (`:88-89`). - - `RefreshAsync` (`:120`): `[AllowAnonymous]` (`:117`), since exchanging an expired token pair is pre-authentication, and deliberately **not** throttled (`:28-34`): refresh is automatic and periodic rather than user-initiated, Blazor Server circuits issue it server-side so every Server-circuit user shares the UI host's IP, and refresh tokens are high-entropy, so brute force is not the threat password spraying is. - - `RevokeAsync` (`:147`): `[Authorize]` (`:144`); reads `CurrentUserService.UserId`, returns `Unauthorized()` if null (`:149-151`) as a defensive guard even though `[Authorize]` should already prevent a null id, then calls `RevokeAllSessionsAsync` and returns `NoContent()` (`:153-159`). The endpoint carries no body, so it cannot name the device it is called from: it signs the user out everywhere, which is what a caller with no way to identify its own session should get (`:135-140`). - - `GetMySessionsAsync` (`:177`): `[HttpGet("my-sessions")]` plus `[Authorize]` (`:173-174`), one row per live refresh session, newest first. The "this is the device you are on" flag comes from the access token's own `sid` claim, read with `User.FindSessionId()` (`:185`), so no client state is involved and nothing has to send a refresh token to a read endpoint; a token minted before `sid` shipped simply flags no row (`:166-170`). - - `RevokeSessionAsync` (`:211`): `[HttpPost("revoke/{sessionId:guid}")]` (`:205`), the per-device counterpart. The session is named in the route rather than by its refresh token, so a client can sign out a device it does not hold the token for, which is the whole point of a device list. Ownership is enforced in the store query, so another account's session id answers 404 exactly as a nonexistent one does, and revoking an already-revoked session answers 204 because a device list is where duplicate clicks come from (`:196-202`). -- **Why it's built this way**: `[Rubric §15, Best Practices & Code Quality]`: adding a new token flow means changing one base, not N module controllers; keeping the methods `virtual` (rather than the class open-ended) keeps the override surface intentional. The rate-limit default is deliberately a *loud* dependency: a consumer that inherits this base without calling `AddCommonRateLimiting()` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:326`, which registers the `"auth-ip"` policy at `:395-397`, constant defined at `:23`, with an `authIpPermitLimit` default of 30 requests per minute per IP at `:326`) fails at startup on an unregistered policy rather than silently serving unthrottled logins (`AuthControllerBase.cs:37-41`). -- **Caveats / not-in-source**: the per-IP policy partitions on `Connection.RemoteIpAddress` (`WebApplicationBuilderExtensions.RateLimiting.cs:280`) and deliberately does **not** limit when that address is null (in-process `TestServer`, integration tests): `AuthIpRateLimitPartition` returns `RateLimitPartition.GetNoLimiter("__unknown-ip")` in that case (`WebApplicationBuilderExtensions.RateLimiting.cs:283`), a fail-open posture matching the global limiter and documented at `WebApplicationBuilderExtensions.RateLimiting.cs:387-394`. -- **Where it's used**: the base of every app's Identity `AuthController`, reached today through [UserAccountAuthControllerBase](#useraccountauthcontrollerbasetchangepasswordcommand-tchangepreferencescommand), which both ADC (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/AuthController.cs:30`) and Store (`MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.API/Controllers/AuthController.cs:27`) extend. Password *recovery* is deliberately NOT on this chain: it ships as the sibling [PasswordResetAuthControllerBase](#passwordresetauthcontrollerbasetforgotpasswordcommand-tresetpasswordcommand). The framework's own coverage drives the base through a minimal test double, `TestAuthController` (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Controllers/Auth/AuthControllerBaseTests.cs:339`), with the throttling asserted separately in `AuthControllerBaseRateLimitTests`. + - Primary constructor (`:43-45`) exposes `AuthenticationService` and `CurrentUserService` as `protected` properties (`:48` and `:51`) so derived controllers can reach them for extra endpoints. + - `ClientIpAddress` (`:58`) and `ClientUserAgent` (`:64`) are the two request facts every write action forwards to the service. The IP is recorded on the refresh session and drives the registration rate limit, and behind a proxy it is the forwarded value only when the host configured `UseForwardedHeaders` (`:53-57`). The user agent is purely informational, so a device list can name the session a user is looking at; nothing validates against it (`:60-63`). + - `LoginAsync` (`:76`): `[HttpPost("login")]`, `[NonIdempotent]`, `[AllowAnonymous]`, throttled per IP (`:69-72`); calls `LoginAsync(request, ClientIpAddress, ClientUserAgent, ...)` (`:80-82`) and returns `Ok` or `HandleFailure`. The `[ProducesResponseType]` attributes (`:73-75`) feed the OpenAPI contract and include the 429 the limiter can produce. + - `RegisterAsync` (`:101`): `[HttpPost("register")]`, `[NonIdempotent]`, anonymous and throttled (`:93-96`); returns `StatusCode(StatusCodes.Status201Created, ...)` (`:111`), correctly 201 Created for a new account rather than 200. It is `virtual` so a module can override it to inject extra context (`:90-91`). + - `RefreshAsync` (`:122`): `[AllowAnonymous]` (`:119`), since exchanging an expired token pair is pre-authentication, and deliberately **not** throttled (`:31-35`): refresh is automatic and periodic rather than user-initiated, Blazor Server circuits issue it server-side so every Server-circuit user shares the UI host's IP, and refresh tokens are high-entropy, so brute force is not the threat password spraying is. + - `RevokeAsync` (`:149`): `[Authorize]` (`:146`); reads `CurrentUserService.UserId`, returns `Unauthorized()` if null (`:151-153`) as a defensive guard even though `[Authorize]` should already prevent a null id, then calls `RevokeAllSessionsAsync` and returns `NoContent()` (`:155-161`). The endpoint carries no body, so it cannot name the device it is called from: it signs the user out everywhere, which is what a caller with no way to identify its own session should get (`:137-142`). + - `GetMySessionsAsync` (`:179`): `[HttpGet("my-sessions")]` plus `[Authorize]` (`:175-176`), one row per live refresh session, newest first. The "this is the device you are on" flag comes from the access token's own `sid` claim, read with `User.FindSessionId()` (`:187`), so no client state is involved and nothing has to send a refresh token to a read endpoint; a token minted before `sid` shipped simply flags no row (`:168-172`). + - `RevokeSessionAsync` (`:213`): `[HttpPost("revoke/{sessionId:guid}")]` (`:207`), the per-device counterpart. The session is named in the route rather than by its refresh token, so a client can sign out a device it does not hold the token for, which is the whole point of a device list. Ownership is enforced in the store query, so another account's session id answers 404 exactly as a nonexistent one does, and revoking an already-revoked session answers 204 because a device list is where duplicate clicks come from (`:198-204`). +- **Why it's built this way**: `[Rubric §15, Best Practices & Code Quality]`: adding a new token flow means changing one base, not N module controllers; keeping the methods `virtual` (rather than the class open-ended) keeps the override surface intentional. The rate-limit default is deliberately a *loud* dependency: a consumer that inherits this base without calling `AddCommonRateLimiting()` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:328`, which registers the `"auth-ip"` policy at `:395-397`, constant defined at `:23`, with an `authIpPermitLimit` default of 30 requests per minute per IP at `:326`) fails at startup on an unregistered policy rather than silently serving unthrottled logins (`AuthControllerBase.cs:38-40`). +- **Caveats / not-in-source**: the per-IP policy partitions on `Connection.RemoteIpAddress` (`WebApplicationBuilderExtensions.RateLimiting.cs:282`) and deliberately does **not** limit when that address is null (in-process `TestServer`, integration tests): `AuthIpRateLimitPartition` returns `RateLimitPartition.GetNoLimiter("__unknown-ip")` in that case (`WebApplicationBuilderExtensions.RateLimiting.cs:285`), a fail-open posture matching the global limiter and documented at `WebApplicationBuilderExtensions.RateLimiting.cs:393-400`.- **Where it's used**: the base of every app's Identity `AuthController`, reached today through [UserAccountAuthControllerBase](#useraccountauthcontrollerbasetchangepasswordcommand-tchangepreferencescommand), which both ADC (`MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/AuthController.cs:41`) and Store (`MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.API/Controllers/AuthController.cs:34`) extend. Password *recovery* is deliberately NOT on this chain: it ships as the sibling [PasswordResetAuthControllerBase](#passwordresetauthcontrollerbasetforgotpasswordcommand-tresetpasswordcommand). `RegisterAsync` is one of the anonymous endpoints the framework's architecture gate lists by name (`MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Api/AnonymousEndpointTests.cs:27`). The framework's own coverage drives the base through a minimal test double, `TestAuthController` (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Controllers/Auth/AuthControllerBaseTests.cs:350`), with the throttling asserted separately in `AuthControllerBaseRateLimitTests`. ### PasswordResetAuthControllerBase > MMCA.Common.API · `MMCA.Common.API.Controllers` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/PasswordResetAuthControllerBase.cs:43` · Level 15 · class (abstract) @@ -1247,17 +1250,17 @@ code stays modules and domain logic, never plumbing. - The two conflict shapes are separated by a tag rather than by separate counters: `ConflictKindTag = "kind"` (`IdempotencyMetrics.cs:32`) carries either `ConflictKindBodyMismatch = "body_mismatch"` (`IdempotencyMetrics.cs:24`) or `ConflictKindInFlight = "in_flight"` (`IdempotencyMetrics.cs:29`), so one time series can be split or summed. - The three helpers are the only recording surface: `RecordReplayed()` (`IdempotencyMetrics.cs:52`), `RecordConflict(string kind)`, which attaches the tag as a `KeyValuePair` (`IdempotencyMetrics.cs:61-62`), and `RecordDegraded()` (`IdempotencyMetrics.cs:68`). - **Why it's built this way**: `internal static` keeps the instruments off the package's public API while still letting the filter in the same assembly record them. A host exports these by registering the meter, which the Aspire service defaults do with the name as a *literal*, because `MMCA.Common.Aspire` has no reference to `MMCA.Common.API`; the duplication is called out in the type doc (`IdempotencyMetrics.cs:8-10`). [OutputCacheMetrics](#outputcachemetrics) uses the identical pattern for the eviction consumer. -- **Where it's used**: every recording site is in [IdempotencyFilter](#idempotencyfilter): `RecordDegraded` on a faulted lock acquisition (`IdempotencyFilter.cs:255`), a failed cache read (`IdempotencyFilter.cs:364`) and a failed cache write (`IdempotencyFilter.cs:439`); `RecordConflict` for an in-flight duplicate (`IdempotencyFilter.cs:267`) and a body mismatch (`IdempotencyFilter.cs:374`); `RecordReplayed` on every served replay (`IdempotencyFilter.cs:380`). +- **Where it's used**: every recording site is in [IdempotencyFilter](#idempotencyfilter): `RecordDegraded` on a faulted lock acquisition (`IdempotencyFilter.cs:256`), a failed cache read (`IdempotencyFilter.cs:365`) and a failed cache write (`IdempotencyFilter.cs:454`); `RecordConflict` for an in-flight duplicate (`IdempotencyFilter.cs:268`) and a body mismatch (`IdempotencyFilter.cs:375`); `RecordReplayed` on every served replay (`IdempotencyFilter.cs:381`). ### IdempotencyRecord -> MMCA.Common.API · `MMCA.Common.API.Idempotency` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyRecord.cs:14` · Level 0 · record (sealed, positional) +> MMCA.Common.API · `MMCA.Common.API.Idempotency` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyRecord.cs:21` · Level 0 · record (sealed, positional) -- **What it is**: the cached snapshot of an idempotent action's response: the HTTP status code, the JSON-serialized body, and the hash of the request body that produced it. It is what [IdempotencyFilter](#idempotencyfilter) writes after the first successful execution and replays for every duplicate carrying the same key *and* the same payload. -- **Depends on**: nothing beyond the BCL; a three-parameter positional `record`, all three parameters required. -- **Concept**: introduced fully by [IdempotencyFilter](#idempotencyfilter); this is the value it persists. Two properties of the shape are load-bearing. First, only status and body are captured, no headers, which is why the replay path re-adds `X-Idempotent-Replay` itself (`IdempotencyFilter.cs:383`) and why a replayed 201 does not carry the original `Location` header. Second, `RequestBodyHash` is **non-nullable**, so there is no "no hash stored" state to fall through: every record carries one and a key replayed with a different payload is always rejected rather than served someone else's response (parameter doc, `IdempotencyRecord.cs:9-13`). A body-less request hashes the empty payload rather than storing nothing. `[Rubric §9, API & Contract Design]` assesses whether a persisted contract states its invariants in its shape rather than in a comment; making the hash required is what removes the unchecked path. -- **Walkthrough**: `IdempotencyRecord(int StatusCode, string ResponseBody, string RequestBodyHash)` (`IdempotencyRecord.cs:14`). `StatusCode` is the original response's code, defaulting to 200 when an `ObjectResult` carried none (`IdempotencyFilter.cs:453`). `ResponseBody` is non-nullable: an `ObjectResult` stores `objectResult.Value` serialized with `JsonSerializerOptions.Web` (`IdempotencyFilter.cs:456-459`), while a body-less 2xx such as the 204 from `NoContent()` stores `string.Empty` (`IdempotencyFilter.cs:468`), which is the signal the replay path uses to answer with a bare status code instead of a JSON content result. `RequestBodyHash` is the lowercase hex SHA-256 of the request body (`IdempotencyFilter.cs:188-192`). -- **Why it's built this way**: keeping the record to three primitives makes it provider-agnostic, so the same artifact round-trips through the in-memory cache or Redis with no serializer coupling. Distinguishing "empty body" from "no body" via the empty string (rather than a nullable) keeps the JSON shape stable across both cases, and the comment at `IdempotencyFilter.cs:463-465` names that convention at the one place a body-less result is stored. -- **Where it's used**: read by [IdempotencyFilter](#idempotencyfilter) through [ICacheService](group-09-caching.md#icacheservice) (`IdempotencyFilter.cs:360`), built by its `BuildRecord` (`IdempotencyFilter.cs:448`), and written back with the configured expiration (`IdempotencyFilter.cs:435`). +- **What it is**: the cached snapshot of an idempotent action's response: the HTTP status code, the JSON-serialized body, the hash of the request body that produced it, and the two response headers a client may act on (`Location` and `ETag`). It is what [IdempotencyFilter](#idempotencyfilter) writes after the first successful execution and replays for every duplicate carrying the same key *and* the same payload. +- **Depends on**: nothing beyond the BCL; a five-parameter positional `record` whose first three parameters are required and whose last two are optional and nullable. +- **Concept**: introduced fully by [IdempotencyFilter](#idempotencyfilter); this is the value it persists. Two properties of the shape are load-bearing. First, the record captures the status, the body and exactly two headers, not the whole header collection: `Location` so a replayed `201 Created` still says where the resource is, and `ETag` so a replayed response still carries its concurrency token (parameter docs, `IdempotencyRecord.cs:14-20`; replay at `IdempotencyFilter.cs:386-392`). The replay path adds `X-Idempotent-Replay` itself (`IdempotencyFilter.cs:384`). Second, `RequestBodyHash` is **non-nullable**, so there is no "no hash stored" state to fall through: every record carries one and a key replayed with a different payload is always rejected rather than served someone else's response (parameter doc, `IdempotencyRecord.cs:9-13`). A body-less request hashes the empty payload rather than storing nothing. `[Rubric §9, API & Contract Design]` assesses whether a persisted contract states its invariants in its shape rather than in a comment; making the hash required is what removes the unchecked path. +- **Walkthrough**: `IdempotencyRecord(int StatusCode, string ResponseBody, string RequestBodyHash, string? Location = null, string? ETag = null)` (`IdempotencyRecord.cs:21-26`). `StatusCode` is the original response's code, defaulting to 200 when an `ObjectResult` carried none (`IdempotencyFilter.cs:476`). `ResponseBody` is non-nullable: an `ObjectResult` stores `objectResult.Value` serialized with the host's MVC JSON options (falling back to `JsonSerializerOptions.Web`), and stores `string.Empty` when the value is null (`IdempotencyFilter.cs:435-436` and `:481`), while a body-less 2xx such as the 204 from `NoContent()` also stores `string.Empty` (`IdempotencyFilter.cs:495`); the empty string is the signal the replay path uses to answer with a bare status code instead of a JSON content result. `RequestBodyHash` is the lowercase hex SHA-256 of the request body (`IdempotencyFilter.cs:190-193`). `Location` is resolved only for an `ObjectResult` (`IdempotencyFilter.cs:487`) and is `null` when the response carried none; `ETag` is whatever the action wrote to the response headers, `null` when empty (`IdempotencyFilter.cs:468-469`). The parameter doc notes that `Location` is also `null` for records written before the field existed (`IdempotencyRecord.cs:16`), which is why both new parameters are optional. +- **Why it's built this way**: keeping the record to primitives makes it provider-agnostic, so the same artifact round-trips through the in-memory cache or Redis with no serializer coupling. Adding the two headers as trailing optional parameters keeps a record cached before they existed deserializable, with the replay simply skipping a header it does not have (`IdempotencyFilter.cs:388-392`). Distinguishing "empty body" from "no body" via the empty string (rather than a nullable) keeps the JSON shape stable across both cases, and the comment at `IdempotencyFilter.cs:490-492` names that convention at the place a body-less status result is stored. +- **Where it's used**: read by [IdempotencyFilter](#idempotencyfilter) through [ICacheService](group-09-caching.md#icacheservice) (`IdempotencyFilter.cs:361`), built by its `BuildRecord` (`IdempotencyFilter.cs:463`), and written back with the configured expiration (`IdempotencyFilter.cs:450`). ### IdempotencySettings > MMCA.Common.API · `MMCA.Common.API.Idempotency` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencySettings.cs:9` · Level 0 · class (sealed) @@ -1267,7 +1270,7 @@ code stays modules and domain logic, never plumbing. - **Concept**: the standard options pattern (see the [primer](00-primer.md)). `[Rubric §17, DevOps & Deployment]` assesses whether a cross-cutting concern is configurable without becoming mandatory; the whole section is optional because the single property has a default, so a host that never mentions idempotency still behaves correctly (type doc, `IdempotencySettings.cs:5-8`). - **Walkthrough**: `SectionName` is a `public static readonly string` equal to `"Idempotency"` (`IdempotencySettings.cs:12`), so the binding key is a symbol rather than a magic string at the call site. `CacheExpirationHours` defaults to 24 (`IdempotencySettings.cs:16`) and is constrained by `[Range(1, 168)]` (`IdempotencySettings.cs:15`), one hour to one week. The property is `init`-only, so the value is fixed once bound. - **Why it's built this way**: `AddAPI` binds the section only when the caller passes an `IConfiguration`, and when it does it chains `.ValidateDataAnnotations().ValidateOnStart()` (`MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:77-80`), so an out-of-range `CacheExpirationHours` fails the host at startup rather than at the first idempotent POST. `[Rubric §15, Best Practices & Code Quality]`: fail fast, loudly, at composition time. [RateLimitingSettings](#ratelimitingsettings) follows the same shape one directory over. -- **Where it's used**: resolved as `IOptions` inside [IdempotencyFilter](#idempotencyfilter) at store time (`IdempotencyFilter.cs:427-431`). Note the deliberate `GetService`, not `GetRequiredService`: when the options are absent (the `AddAPI` overload called with no configuration) the filter falls back to its hard-coded 24-hour `DefaultExpiration` (`IdempotencyFilter.cs:80`) instead of throwing. +- **Where it's used**: resolved as `IOptions` inside [IdempotencyFilter](#idempotencyfilter) at store time (`IdempotencyFilter.cs:442-446`). Note the deliberate `GetService`, not `GetRequiredService`: when the options are absent (the `AddAPI` overload called with no configuration) the filter falls back to its hard-coded 24-hour `DefaultExpiration` (`IdempotencyFilter.cs:81`) instead of throwing. ### IErrorLocalizer > MMCA.Common.API · `MMCA.Common.API.Localization` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Localization/IErrorLocalizer.cs:9` · Level 0 · interface @@ -1297,7 +1300,7 @@ code stays modules and domain logic, never plumbing. - **Concept introduced: feature gating at the HTTP edge.** `[Rubric §9, API & Contract Design]` assesses whether every response, success or refusal, follows one uniform contract; this handler makes the disabled-feature path match the [ApiControllerBase](#apicontrollerbase)`.HandleFailure` shape rather than leaking a framework default. `[Rubric §6, CQRS & Event-Driven Design]` covers concerns applied uniformly across endpoints, and feature flags are exactly that. Note the split: this class gates *controller actions* decorated with `[FeatureGate]`, while [FeatureGateCommandDecorator](group-05-cqrs-pipeline.md#featuregatecommanddecoratortcommand-tresult) gates *CQRS handlers* one layer deeper. The two surfaces cover the two entry points into a gated capability, which is precisely the dual-surface enforcement [ADR-031](https://ivanball.github.io/docs/adr/031-feature-flag-management.html) records. - **Walkthrough**: `HandleDisabledFeatures(features, context)` (`DisabledFeatureHandler.cs:16`) sets `context.Result` to an `ObjectResult` wrapping a `ProblemDetails` with `Status = 404` and a fixed title/detail ("Feature not available", `DisabledFeatureHandler.cs:18-23`), and also sets the outer `StatusCode = 404` (`DisabledFeatureHandler.cs:25`) so the response code and the body agree. It returns `Task.CompletedTask` (`DisabledFeatureHandler.cs:28`): the work is synchronous, there is nothing to await. - **Why it's built this way**: the payload deliberately does not name the disabled feature. An anonymous caller learns only that the endpoint is unavailable, not which flag is off, so the flag set is not enumerable from outside. The `features` parameter is available but unused for that reason. -- **Where it's used**: registered as the app's `IDisabledFeaturesHandler` singleton inside `AddAPI` (`MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:107`, immediately after `services.AddFeatureManagement().WithTargeting()` on `DependencyInjection.cs:105-106`); invoked by `Microsoft.FeatureManagement.Mvc` whenever a `[FeatureGate]` action is hit with its flag disabled. In the framework itself that covers [DataExportControllerBase](#dataexportcontrollerbasetquery) while `PrivacyFeatures.DataExport` is off (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:58`) and the three notification controllers gated on `NotificationFeatures.PushNotifications` (`Controllers/Notifications/DevicesController.cs:23`, `NotificationInboxController.cs:27`, `NotificationsController.cs:28`). +- **Where it's used**: registered as the app's `IDisabledFeaturesHandler` singleton inside `AddAPI` (`MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:107`, immediately after `services.AddFeatureManagement().WithTargeting()` on `DependencyInjection.cs:105-106`); invoked by `Microsoft.FeatureManagement.Mvc` whenever a `[FeatureGate]` action is hit with its flag disabled. In the framework itself that covers [DataExportControllerBase](#dataexportcontrollerbasetquery) while `PrivacyFeatures.DataExport` is off (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:60`) and the three notification controllers gated on `NotificationFeatures.PushNotifications` (`Controllers/Notifications/DevicesController.cs:23`, `NotificationInboxController.cs:27`, `NotificationsController.cs:28`). ### ErrorLocalizer > MMCA.Common.API · `MMCA.Common.API.Localization` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Localization/ErrorLocalizer.cs:11` · Level 1 · class (sealed, internal) @@ -1310,35 +1313,36 @@ code stays modules and domain logic, never plumbing. - **Where it's used**: registered by `AddErrorLocalization()` (`DependencyInjection.cs:121-127`, which also calls `services.AddLocalization()` at `:109`), and that method is called by `AddAPI` (`DependencyInjection.cs:110`); reached at runtime only through the [IErrorLocalizer](#ierrorlocalizer) handle that [ApiControllerBase](#apicontrollerbase), [UnhandledResultFailureFilter](#unhandledresultfailurefilter) and [SupportsIfMatchAttribute](#supportsifmatchattribute) pass into [ErrorHttpMapping](#errorhttpmapping). ### IdempotencyFilter -> MMCA.Common.API · `MMCA.Common.API.Idempotency` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:67` · Level 4 · class (sealed, partial) +> MMCA.Common.API · `MMCA.Common.API.Idempotency` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:68` · Level 4 · class (sealed, partial) -- **What it is**: the ASP.NET Core filter that gives write operations client-driven idempotency. A client attaches an `Idempotency-Key` header; the first successful response for that key is cached and every subsequent request carrying the same key gets the stored response back, without re-running the action. It implements *both* `IAsyncActionFilter` and `IAsyncResourceFilter` (`IdempotencyFilter.cs:68`), which is the detail that makes body-aware deduplication possible. -- **Depends on**: [ICacheService](group-09-caching.md#icacheservice) and [IDistributedLock](group-05-cqrs-pipeline.md#idistributedlock), both resolved per request from `RequestServices` (`IdempotencyFilter.cs:140` and `IdempotencyFilter.cs:148`); [IdempotencyRecord](#idempotencyrecord); [IdempotencySettings](#idempotencysettings) via `IOptions<>`; [IdempotencyMetrics](#idempotencymetrics); [IdempotencyHeaders](group-08-auth.md#idempotencyheaders) for the two header names; [KeyedSemaphoreStripe](group-08-auth.md#keyedsemaphorestripe) as the no-distributed-lock fallback; [ClaimsPrincipalExtensions](group-08-auth.md#claimsprincipalextensions) for the caller's identity. Externals: `SHA256`, `Encoding`, `System.Text.Json`, and `ILogger` with source-generated `[LoggerMessage]` partials. +- **What it is**: the ASP.NET Core filter that gives write operations client-driven idempotency. A client attaches an `Idempotency-Key` header; the first successful response for that key is cached and every subsequent request carrying the same key gets the stored response back, without re-running the action. It implements *both* `IAsyncActionFilter` and `IAsyncResourceFilter` (`IdempotencyFilter.cs:69`), which is the detail that makes body-aware deduplication possible. +- **Depends on**: [ICacheService](group-09-caching.md#icacheservice) and [IDistributedLock](group-05-cqrs-pipeline.md#idistributedlock), both resolved per request from `RequestServices` (`IdempotencyFilter.cs:141` and `IdempotencyFilter.cs:149`); [IdempotencyRecord](#idempotencyrecord); [IdempotencySettings](#idempotencysettings) via `IOptions<>`; [IdempotencyMetrics](#idempotencymetrics); [IdempotencyHeaders](group-08-auth.md#idempotencyheaders) for the two header names; [KeyedSemaphoreStripe](group-08-auth.md#keyedsemaphorestripe) as the no-distributed-lock fallback; [ClaimsPrincipalExtensions](group-08-auth.md#claimsprincipalextensions) for the caller's identity. Externals: `SHA256`, `Encoding`, `System.Text.Json` (with the host's MVC `IOptions` resolved per request when present), `IUrlHelperFactory` for reconstructing a created result's `Location`, and `ILogger` with source-generated `[LoggerMessage]` partials. - **Concept introduced: idempotent mutation as a lock plus a caller-scoped key plus a body binding.** `[Rubric §29, Resilience & Business Continuity]` assesses surviving client retries without duplicating side effects; `[Rubric §9, API & Contract Design]` covers safe retry semantics on non-safe verbs; `[Rubric §11, Security]` applies because the key derivation is a trust boundary, not just a cache detail; `[Rubric §13, Observability & Operability]` applies because every degraded path is counted rather than silent. Three ideas compose here. - 1. **Double-check locking** (doc comment, `IdempotencyFilter.cs:22-32`): read the cache with no lock (the common fast path); on a miss take the lock for this key; re-read, because a concurrent duplicate may have finished while this one waited; only then execute and store. Without the lock, two near-simultaneous retries of a slow create both miss and both execute. - 2. **The lock must be visible to every replica.** A per-process stripe only serializes duplicates that land on the same instance, and both deployed apps run more than one (`IdempotencyFilter.cs:33-38`, restated at `IdempotencyFilter.cs:219-223`), so the primary path uses an [IDistributedLock](group-05-cqrs-pipeline.md#idistributedlock) and the stripe is only the fallback for a host that registers none. - 3. **The key alone is not enough.** A stored response is bound to the request body that produced it, so a key reused with a *different* payload is refused (422) rather than replayed, which would otherwise silently swallow a genuinely new write (`IdempotencyFilter.cs:43-51`). + 1. **Double-check locking** (doc comment, `IdempotencyFilter.cs:23-33`): read the cache with no lock (the common fast path); on a miss take the lock for this key; re-read, because a concurrent duplicate may have finished while this one waited; only then execute and store. Without the lock, two near-simultaneous retries of a slow create both miss and both execute. + 2. **The lock must be visible to every replica.** A per-process stripe only serializes duplicates that land on the same instance, and both deployed apps run more than one (`IdempotencyFilter.cs:34-39`, restated at `IdempotencyFilter.cs:221-224`), so the primary path uses an [IDistributedLock](group-05-cqrs-pipeline.md#idistributedlock) and the stripe is only the fallback for a host that registers none. + 3. **The key alone is not enough.** A stored response is bound to the request body that produced it, so a key reused with a *different* payload is refused (422) rather than replayed, which would otherwise silently swallow a genuinely new write (`IdempotencyFilter.cs:44-52`). - **Walkthrough** - - Constants and shared state: `IdempotencyKeyHeader` delegates to `IdempotencyHeaders.IdempotencyKey` (`IdempotencyFilter.cs:73`, defined at `MMCA.Common/Source/Core/MMCA.Common.Shared/Http/IdempotencyHeaders.cs:19` as `"Idempotency-Key"`); `CacheKeyPrefix` returns `"idempotency:"` (`IdempotencyFilter.cs:75`); `DefaultExpiration` of 24 hours (`IdempotencyFilter.cs:80`); `LockTimeToLive` of 30 seconds and `LockWait` of 5 seconds (`IdempotencyFilter.cs:97` and `IdempotencyFilter.cs:104`), sized so a slow action finishes under its own lock while a dead replica does not block a retry for long; and `EmptyBodyHash`, the SHA-256 of zero bytes (`IdempotencyFilter.cs:110-111`). - - `KeyLocks` (`IdempotencyFilter.cs:90`) is a static [KeyedSemaphoreStripe](group-08-auth.md#keyedsemaphorestripe). The comment above it (`IdempotencyFilter.cs:82-89`) gives the reason for striping over one-semaphore-per-key: the key embeds a caller-supplied value, so a per-key table either grows without bound or needs an eager removal that races (a removal between another request's lookup and its wait lets a third request create a fresh semaphore, and both then execute concurrently). - - **Resource stage.** `OnResourceExecutionAsync` (`IdempotencyFilter.cs:119`) runs before model binding, the last point at which the body can still be made replayable. It calls `Request.EnableBuffering()` only when the header is present (`IdempotencyFilter.cs:121-122`), so ordinary traffic on an `[Idempotent]` action pays nothing, then awaits `next()` (`IdempotencyFilter.cs:124`). - - **Action stage.** `OnActionExecutionAsync` (`IdempotencyFilter.cs:128`): no header means straight through to `next()` with nothing else resolved (`IdempotencyFilter.cs:131-136`), so idempotency is opt-in per request as well as per action. Otherwise it derives the cache key (`IdempotencyFilter.cs:138`), hashes the body (`:139`), resolves [ICacheService](group-09-caching.md#icacheservice) (`:140`), and tries the lock-free replay (`:143`). On a miss it picks a lock strategy: `GetService()` returning null routes to the process-stripe path (`:148-153`), otherwise the distributed path (`:155`). The comment at `:146-147` states the invariant that shapes both paths: the lock has to span execute-and-store, so a duplicate cannot slip in between the action finishing and its response reaching the cache. - - `ReadIdempotencyKey` (`IdempotencyFilter.cs:163-170`) treats a missing *or* blank header as absent, and both stages call it so they agree on what "has a key" means. - - `ComputeRequestBodyHashAsync` (`IdempotencyFilter.cs:182-193`): a stream that cannot seek was never buffered, so it takes `EmptyBodyHash` rather than throwing (`:185-186`), which leaves such a request deduplicated on its key alone. Otherwise it rewinds to 0, hashes with `SHA256.HashDataAsync`, and rewinds *again* (`:188-190`) because model binding still has to read the whole body. - - `ExecuteUnderProcessLockAsync` (`IdempotencyFilter.cs:199`): acquires the stripe honoring `RequestAborted` inside a `using` so release survives a throw (`:206`), re-checks the cache (`:209`), then executes and stores (`:212`). - - `ExecuteUnderDistributedLockAsync` (`IdempotencyFilter.cs:238`) is where the interesting policy lives, and it separates three outcomes that a naive implementation conflates (rationale in the remarks, `:219-236`). - - The lock backend *faults*: counted on `idempotency.degraded`, logged, and the action runs unguarded (`:253-259`). There is no holder to wait for, so refusing would turn a cache blip into a write outage. - - The wait *expires* with the lock still held elsewhere: the holder is executing this key right now, so it logs the timeout and re-checks the cache first, and only if still nothing is stored does it record an `in_flight` conflict and answer with `InFlightDuplicateResult()` (`:261-273`). That helper (`:301-310`) is a 409 `ProblemDetails` titled "Request in progress" telling the client to retry with the same key: retryable and honest, where executing would be the duplicate write. - - The lock is *acquired*: `await using` on the handle (`:275`), double-check (`:278`), then execute and store (`:281`). - - `ExecuteAndStoreAsync` (`IdempotencyFilter.cs:286-295`) is the two-line shared tail of all three paths: await `next()`, then `TryStoreAsync` the executed result. - - `TryReplayAsync` (`IdempotencyFilter.cs:351`) serves both the fast path and every double-check, returning whether it short-circuited. A cache read that throws is reported as "nothing stored" after counting a degradation (`:358-367`). A stored record whose `RequestBodyHash` differs from this request's hash (an ordinal comparison, `:372`) yields a `body_mismatch` conflict and `BodyMismatchResult()` (`:372-378`), a 422 whose remarks explain the status choice: 409 is already spent on "still in flight", which is retry-with-the-same-key, while key reuse with a different body is not retryable until the client picks a new key (`:317-331`). On a genuine hit it counts the replay (`:380`), appends `X-Idempotent-Replay: true` (`:383`, name from `IdempotencyHeaders.cs:25`), and short-circuits with a bare `StatusCodeResult` when the stored body is empty or a `ContentResult` with `application/json` otherwise (`:384-391`). That split is what makes a replayed 204 look like the original 204 instead of a 204 carrying a content type. - - `TryStoreAsync` (`IdempotencyFilter.cs:416`) builds the record and returns early when it is not cacheable (`:423-425`), reads the expiration from [IdempotencySettings](#idempotencysettings) when registered and `DefaultExpiration` otherwise (`:427-431`), and swallows a failing `SetAsync` after counting it (`:433-441`): the action already ran, so failing here would push the client into the very retry the filter exists to deduplicate (`:410-414`). - - `BuildRecord` (`IdempotencyFilter.cs:448`) decides what is cacheable. An `ObjectResult` with a 2xx status stores its value serialized with `JsonSerializerOptions.Web` (`:452-461`; the `VSTHRD103` suppression at `:454` documents that serializing to a string is correctly synchronous). A `StatusCodeResult` with a 2xx status, which is what `NoContent()`, `OkResult` and `StatusCode(int)` produce, stores the empty string (`:466-469`). Everything else, including every non-2xx and every redirect or file result, returns `null` and is not stored (`:471-472`); `IsSuccess` is the `>= 200 and < 300` test (`:476`). - - `BuildCacheKey` (`IdempotencyFilter.cs:485-499`) is the security-relevant part: the subject is the caller's user-id claim via `FindUserIdValue()` (`:487`, `MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/ClaimsPrincipalExtensions.cs:26`, which reads `sub` and falls back to the mapped `NameIdentifier`) or, unauthenticated, `anon:{remote address}` (`:488`); the route is the attribute route template falling back to the request path (`:490-492`); those plus the HTTP method and the client key are joined with a newline (`:495`, a character valid in none of the components, so the tuple cannot be forged), SHA-256 hashed (`:496`), and emitted as `idempotency:{lowercase hex}` (`:498`). - - Logging is entirely source-generated `[LoggerMessage]` partials with instance forwarders (`IdempotencyFilter.cs:501-552`): one Information message for a served replay (`:501-507`) and five Warnings covering body mismatch (`:509-514`), in-flight duplicate (`:516-521`), lock-wait timeout (`:523-528`), cache-read failure (`:530-536`) and cache-store failure (`:538-544`), plus lock unavailability (`:546-552`). `[Rubric §13, Observability & Operability]`: each degraded path has both a counter and a log line naming the cache key. -- **Why it's built this way**: keying on the bare client value would make the key space global, so two callers who happened to pick the same value would share an entry and one user's serialized body could be replayed to another; with services sharing one cache instance that collision reaches across endpoints and services (SECURITY note, `IdempotencyFilter.cs:59-65`; [ADR-017](https://ivanball.github.io/docs/adr/017-request-idempotency.html)). Hashing also bounds the stored key length regardless of what a client sends (`:478-484`). Non-2xx results are deliberately not stored (`IdempotencyFilter.cs:400-403`) because replaying a failure for the whole retention window would mean a client retrying after a transient 500 keeps receiving that 500 for 24 hours instead of the retry actually executing. The 204 case is explicitly covered rather than skipped (`:405-409`): skipping it left every command answering `NoContent()` with nothing stored, so the body-less writes, the ones most likely to be retried, were the ones idempotency did not actually cover. And the whole cache-and-lock layer is treated as best-effort infrastructure (`IdempotencyFilter.cs:52-58`): deduplication is an optimization over an at-least-once client retry, so a Redis outage must degrade dedup, not every write endpoint carrying the attribute. -- **Caveats / not-in-source**: the replay restores only the status code and the JSON body. Response headers the original action set (a 201's `Location`, for example) are not captured by [IdempotencyRecord](#idempotencyrecord) and are therefore not reproduced; only `X-Idempotent-Replay` is added. -- **Where it's used**: registered scoped in `AddAPI` because it depends on scoped services (`MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:84`, comment at `:76`), and attached to actions through [IdempotentAttribute](#idempotentattribute). In the framework itself that is the create endpoint on [AggregateRootEntityControllerBase](#aggregaterootentitycontrollerbasetentity-tentitydto-tidentifiertype-tcreaterequest) (`AggregateRootEntityControllerBase.cs:60`), the update endpoint on [CrudEntityControllerBase](#crudentitycontrollerbasetentity-tentitydto-tidentifiertype-tcreaterequest-tupdaterequest) (`CrudEntityControllerBase.cs:89`, alongside `[SupportsIfMatch]`), the register endpoint on [AuthControllerBase](#authcontrollerbase) (`AuthControllerBase.cs:94`), both actions of [PasswordResetAuthControllerBase](#passwordresetauthcontrollerbasetforgotpasswordcommand-tresetpasswordcommand) (`PasswordResetAuthControllerBase.cs:76` and `:100`), and the send endpoint of [NotificationsController](group-10-notifications.md#notificationscontroller) (`NotificationsController.cs:44`). Downstream, ADC marks its Engagement writes (check-ins, bookmarks, live polls, session questions) and its Conference collection writes, and Store marks its cart, order and catalog writes. + - Constants and shared state: `IdempotencyKeyHeader` delegates to `IdempotencyHeaders.IdempotencyKey` (`IdempotencyFilter.cs:74`, defined at `MMCA.Common/Source/Core/MMCA.Common.Shared/Http/IdempotencyHeaders.cs:19` as `"Idempotency-Key"`); `CacheKeyPrefix` returns `"idempotency:"` (`IdempotencyFilter.cs:76`); `DefaultExpiration` of 24 hours (`IdempotencyFilter.cs:81`); `LockTimeToLive` of 30 seconds and `LockWait` of 5 seconds (`IdempotencyFilter.cs:98` and `IdempotencyFilter.cs:105`), sized so a slow action finishes under its own lock while a dead replica does not block a retry for long; and `EmptyBodyHash`, the SHA-256 of zero bytes (`IdempotencyFilter.cs:111-112`). + - `KeyLocks` (`IdempotencyFilter.cs:91`) is a static [KeyedSemaphoreStripe](group-08-auth.md#keyedsemaphorestripe). The comment above it (`IdempotencyFilter.cs:83-90`) gives the reason for striping over one-semaphore-per-key: the key embeds a caller-supplied value, so a per-key table either grows without bound or needs an eager removal that races (a removal between another request's lookup and its wait lets a third request create a fresh semaphore, and both then execute concurrently). + - **Resource stage.** `OnResourceExecutionAsync` (`IdempotencyFilter.cs:120`) runs before model binding, the last point at which the body can still be made replayable. It calls `Request.EnableBuffering()` only when the header is present (`IdempotencyFilter.cs:122-123`), so ordinary traffic on an `[Idempotent]` action pays nothing, then awaits `next()` (`IdempotencyFilter.cs:125`). + - **Action stage.** `OnActionExecutionAsync` (`IdempotencyFilter.cs:129`): no header means straight through to `next()` with nothing else resolved (`IdempotencyFilter.cs:132-137`), so idempotency is opt-in per request as well as per action. Otherwise it derives the cache key (`IdempotencyFilter.cs:139`), hashes the body (`:140`), resolves [ICacheService](group-09-caching.md#icacheservice) (`:141`), and tries the lock-free replay (`:144-145`). On a miss it picks a lock strategy: `GetService()` returning null routes to the process-stripe path (`:149-154`), otherwise the distributed path (`:156-157`). The comment at `:147-148` states the invariant that shapes both paths: the lock has to span execute-and-store, so a duplicate cannot slip in between the action finishing and its response reaching the cache. + - `ReadIdempotencyKey` (`IdempotencyFilter.cs:164-171`) treats a missing *or* blank header as absent, and both stages call it so they agree on what "has a key" means. + - `ComputeRequestBodyHashAsync` (`IdempotencyFilter.cs:183-194`): a stream that cannot seek was never buffered, so it takes `EmptyBodyHash` rather than throwing (`:186-187`), which leaves such a request deduplicated on its key alone. Otherwise it rewinds to 0, hashes with `SHA256.HashDataAsync`, and rewinds *again* (`:189-191`) because model binding still has to read the whole body. + - `ExecuteUnderProcessLockAsync` (`IdempotencyFilter.cs:200`): acquires the stripe honoring `RequestAborted` inside a `using` so release survives a throw (`:207`), re-checks the cache (`:210`), then executes and stores (`:213`). + - `ExecuteUnderDistributedLockAsync` (`IdempotencyFilter.cs:239`) is where the interesting policy lives, and it separates three outcomes that a naive implementation conflates (rationale in the remarks, `:220-238`). + - The lock backend *faults*: counted on `idempotency.degraded`, logged, and the action runs unguarded (`:254-260`). There is no holder to wait for, so refusing would turn a cache blip into a write outage. + - The wait *expires* with the lock still held elsewhere: the holder is executing this key right now, so it logs the timeout and re-checks the cache first, and only if still nothing is stored does it record an `in_flight` conflict and answer with `InFlightDuplicateResult()` (`:262-274`). That helper (`:302-311`) is a 409 `ProblemDetails` titled "Request in progress" telling the client to retry with the same key: retryable and honest, where executing would be the duplicate write. + - The lock is *acquired*: `await using` on the handle (`:276`), double-check (`:279`), then execute and store (`:282`). + - `ExecuteAndStoreAsync` (`IdempotencyFilter.cs:287-296`) is the two-line shared tail of all three paths: await `next()`, then `TryStoreAsync` the executed result. + - `TryReplayAsync` (`IdempotencyFilter.cs:352`) serves both the fast path and every double-check, returning whether it short-circuited. A cache read that throws is reported as "nothing stored" after counting a degradation (`:359-367`). A stored record whose `RequestBodyHash` differs from this request's hash (an ordinal comparison, `:373`) yields a `body_mismatch` conflict and `BodyMismatchResult()` (`:373-379`), a 422 whose remarks explain the status choice: 409 is already spent on "still in flight", which is retry-with-the-same-key, while key reuse with a different body is not retryable until the client picks a new key (`:318-322`). On a genuine hit it counts the replay (`:381`), appends `X-Idempotent-Replay: true` (`:384`, name from `IdempotencyHeaders.cs:25`), restores the stored `Location` and `ETag` headers when the record carries them (`:386-392`, so a replayed 201 still says where the resource is and a replayed response still carries its concurrency token), and short-circuits with a bare `StatusCodeResult` when the stored body is empty or a `ContentResult` with `application/json` otherwise (`:394-401`). That split is what makes a replayed 204 look like the original 204 instead of a 204 carrying a content type. + - `TryStoreAsync` (`IdempotencyFilter.cs:426`) resolves the host's MVC `JsonOptions` and falls back to `JsonSerializerOptions.Web` only when none is registered (`:433-436`): the replayed body has to be shaped exactly like the original, which MVC wrote with the host's converters. It then builds the record and returns early when it is not cacheable (`:438-440`), reads the expiration from [IdempotencySettings](#idempotencysettings) when registered and `DefaultExpiration` otherwise (`:442-446`), and swallows a failing `SetAsync` after counting it (`:448-456`): the action already ran, so failing here would push the client into the very retry the filter exists to deduplicate (`:421-424`). + - `BuildRecord` (`IdempotencyFilter.cs:463`) decides what is cacheable. It first reads any `ETag` the action wrote to the response, storing `null` when it is empty (`:468-469`). An `ObjectResult` with a non-2xx status returns `null` (`:476-478`); a 2xx one stores its value serialized with the resolved options, or the empty string when the value is null so an `Accepted()` with no payload replays as a body-less status rather than a JSON `null` (`:473-474` and `:481`; the `VSTHRD103` suppression at `:480` documents that serializing to a string is correctly synchronous), together with the resolved `Location` and the `ETag` (`:483-488`). A `StatusCodeResult` with a 2xx status, which is what `NoContent()`, `OkResult` and `StatusCode(int)` produce, stores the empty string plus the `ETag` (`:493-496`). Everything else, including every redirect or file result, returns `null` and is not stored (`:498-499`); `IsSuccess` is the `>= 200 and < 300` test (`:530`). + - `ResolveLocation` (`IdempotencyFilter.cs:507-528`) exists because a created result writes its `Location` when the RESULT executes, after this filter has already run, so the filter computes it the same way the result will (`:503-506`): a `CreatedResult` gives its `Location` directly (`:511-512`), a `CreatedAtRouteResult` goes through `IUrlHelperFactory` and `Link` (`:514-517`), a `CreatedAtActionResult` through `IUrlHelperFactory` and `Action` with the request's scheme and host (`:519-523`), and any other `ObjectResult` yields `null` (`:525-526`). + - `BuildCacheKey` (`IdempotencyFilter.cs:539-553`) is the security-relevant part: the subject is the caller's user-id claim via `FindUserIdValue()` (`:541`, `MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/ClaimsPrincipalExtensions.cs:26`, which reads `sub` and falls back to the mapped `NameIdentifier`) or, unauthenticated, `anon:{remote address}` (`:542`); the route is the attribute route template falling back to the request path (`:544-546`); those plus the HTTP method and the client key are joined with a newline (`:548-549`, a character valid in none of the components, so the tuple cannot be forged), SHA-256 hashed (`:550`), and emitted as `idempotency:{lowercase hex}` (`:552`). + - Logging is entirely source-generated `[LoggerMessage]` partials with instance forwarders (`IdempotencyFilter.cs:555-606`): one Information message for a served replay (`:555-561`) and six Warnings covering body mismatch (`:563-568`), in-flight duplicate (`:570-575`), lock-wait timeout (`:577-582`), cache-read failure (`:584-590`), cache-store failure (`:592-598`) and lock unavailability (`:600-606`). `[Rubric §13, Observability & Operability]`: each degraded path has both a counter and a log line naming the cache key. +- **Why it's built this way**: keying on the bare client value would make the key space global, so two callers who happened to pick the same value would share an entry and one user's serialized body could be replayed to another; with services sharing one cache instance that collision reaches across endpoints and services (SECURITY note, `IdempotencyFilter.cs:60-66`; [ADR-017](https://ivanball.github.io/docs/adr/017-request-idempotency.html)). Hashing also bounds the stored key length regardless of what a client sends (`:532-538`). Non-2xx results are deliberately not stored (`IdempotencyFilter.cs:410-413`) because replaying a failure for the whole retention window would mean a client retrying after a transient 500 keeps receiving that 500 for 24 hours instead of the retry actually executing. The 204 case is explicitly covered rather than skipped (`:416-419`): skipping it left every command answering `NoContent()` with nothing stored, so the body-less writes, the ones most likely to be retried, were the ones idempotency did not actually cover. And the whole cache-and-lock layer is treated as best-effort infrastructure (`IdempotencyFilter.cs:53-59`): deduplication is an optimization over an at-least-once client retry, so a Redis outage must degrade dedup, not every write endpoint carrying the attribute. +- **Caveats / not-in-source**: the replay restores the status code, the JSON body, and the `Location` and `ETag` headers stored on [IdempotencyRecord](#idempotencyrecord) (`IdempotencyFilter.cs:386-392`); any other header the original action set is not captured and is therefore not reproduced, and `X-Idempotent-Replay` is added. `Location` is reconstructed only for `CreatedResult`, `CreatedAtRouteResult` and `CreatedAtActionResult`, and for the two URL-helper cases only when an `IUrlHelperFactory` is registered (`IdempotencyFilter.cs:515` and `:521`); a `Location` the action set on the response by hand is not captured. A record cached before these fields existed replays without them. +- **Where it's used**: registered scoped in `AddAPI` because it depends on scoped services (`MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:84`, comment at `:76`), and attached to actions through [IdempotentAttribute](#idempotentattribute). In the framework itself that is the create endpoint on [AggregateRootEntityControllerBase](#aggregaterootentitycontrollerbasetentity-tentitydto-tidentifiertype-tcreaterequest) (`AggregateRootEntityControllerBase.cs:60`), the update endpoint on [CrudEntityControllerBase](#crudentitycontrollerbasetentity-tentitydto-tidentifiertype-tcreaterequest-tupdaterequest) (`CrudEntityControllerBase.cs:89`, alongside `[SupportsIfMatch]`), both actions of [PasswordResetAuthControllerBase](#passwordresetauthcontrollerbasetforgotpasswordcommand-tresetpasswordcommand) (`PasswordResetAuthControllerBase.cs:76` and `:100`), and the send endpoint of [NotificationsController](group-10-notifications.md#notificationscontroller) (`NotificationsController.cs:44`). The token-issuing and revoking actions of [AuthControllerBase](#authcontrollerbase), register included, opt out explicitly with [NonIdempotentAttribute](#nonidempotentattribute) (`AuthControllerBase.cs:70`, `:94`, `:118`, `:145`, `:208`). Downstream, ADC marks its Engagement writes (check-ins, bookmarks, live polls, session questions) and its Conference collection writes, and Store marks its cart, order and catalog writes. ### IdempotentAttribute > MMCA.Common.API · `MMCA.Common.API.Idempotency` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotentAttribute.cs:16` · Level 5 · class (sealed) @@ -1347,7 +1351,7 @@ code stays modules and domain logic, never plumbing. - **Depends on**: `ServiceFilterAttribute` from ASP.NET Core MVC; resolves [IdempotencyFilter](#idempotencyfilter) from DI. - **Concept introduced: service filters (DI-resolved action filters).** `[Rubric §2, Design Patterns]` covers the filter idiom and how the instance is obtained. A plain `[TypeFilter]` constructs the filter itself, which would confine it to constructor arguments MVC can supply; `ServiceFilterAttribute` (`IdempotentAttribute.cs:16`) resolves it from the container instead, which is what lets the filter be registered `AddScoped` and reach scoped services such as [ICacheService](group-09-caching.md#icacheservice) (remarks, `IdempotentAttribute.cs:10-14`). `[Rubric §15, Best Practices & Code Quality]`: `[AttributeUsage(AttributeTargets.Method)]` (`IdempotentAttribute.cs:15`) makes misapplication at class level a compile error rather than a silent no-op. - **Walkthrough**: the entire type is one line, `public sealed class IdempotentAttribute() : ServiceFilterAttribute(typeof(IdempotencyFilter))` (`IdempotentAttribute.cs:16`); the primary constructor forwards the filter type to the base. The doc notes the filter must be registered in DI, which `AddAPI` does (`MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:84`); without that registration, resolution fails at request time rather than at startup. It also states the no-op contract: an action carrying the attribute but reached by a request with no `Idempotency-Key` header executes normally (`IdempotentAttribute.cs:5-9`). -- **Why it's built this way**: opt-in per action is the [ADR-017](https://ivanball.github.io/docs/adr/017-request-idempotency.html) decision. Nothing is deduplicated unless the action declares it, so adding the attribute is additive and safe, and the client still decides per request whether to send a key at all (a keyless request short-circuits at `IdempotencyFilter.cs:131-136`). The flip side, that a POST which *should* be idempotent but is missing the attribute gets no protection, is closed by a fitness function rather than by inventory discipline: see [NonIdempotentAttribute](#nonidempotentattribute), whose `PostActionsDeclareIdempotencyIntent` rule requires every POST action to carry one of the two attributes (`MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Cqrs/ArchitectureRules.Idempotency.cs:44`). Because that rule reads attributes with `inherit: true`, a concrete controller deriving from one of the framework bases already satisfies it through the base's method (`ArchitectureRules.Idempotency.cs:30-36`). +- **Why it's built this way**: opt-in per action is the [ADR-017](https://ivanball.github.io/docs/adr/017-request-idempotency.html) decision. Nothing is deduplicated unless the action declares it, so adding the attribute is additive and safe, and the client still decides per request whether to send a key at all (a keyless request short-circuits at `IdempotencyFilter.cs:132-137`). The flip side, that a POST which *should* be idempotent but is missing the attribute gets no protection, is closed by a fitness function rather than by inventory discipline: see [NonIdempotentAttribute](#nonidempotentattribute), whose `PostActionsDeclareIdempotencyIntent` rule requires every POST action to carry one of the two attributes (`MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Cqrs/ArchitectureRules.Idempotency.cs:44`). Because that rule reads attributes with `inherit: true`, a concrete controller deriving from one of the framework bases already satisfies it through the base's method (`ArchitectureRules.Idempotency.cs:30-36`). - **Where it's used**: applied in the framework to the create endpoint of [AggregateRootEntityControllerBase](#aggregaterootentitycontrollerbasetentity-tentitydto-tidentifiertype-tcreaterequest) (`AggregateRootEntityControllerBase.cs:60`), the update endpoint of [CrudEntityControllerBase](#crudentitycontrollerbasetentity-tentitydto-tidentifiertype-tcreaterequest-tupdaterequest) (`CrudEntityControllerBase.cs:89`), [AuthControllerBase](#authcontrollerbase)`.RegisterAsync` (`AuthControllerBase.cs:94`), the forgot-password and reset-password actions of [PasswordResetAuthControllerBase](#passwordresetauthcontrollerbasetforgotpasswordcommand-tresetpasswordcommand) (`PasswordResetAuthControllerBase.cs:76`, `:100`) and [NotificationsController](group-10-notifications.md#notificationscontroller)`.SendAsync` (`NotificationsController.cs:44`); and in the apps to the ADC Conference/Engagement/Identity and Store Sales/Catalog write endpoints listed under [IdempotencyFilter](#idempotencyfilter). ### CurrentUserTargetingContextAccessor @@ -1356,7 +1360,7 @@ code stays modules and domain logic, never plumbing. - **What it is**: the `ITargetingContextAccessor` that supplies the audience (a user id plus that user's groups) which the feature-management `Targeting` filter evaluates, read from the current HTTP request's principal. - **Depends on**: `Microsoft.FeatureManagement.FeatureFilters.ITargetingContextAccessor` and `TargetingContext`, `Microsoft.AspNetCore.Http.IHttpContextAccessor`, `System.Security.Claims`, and [ClaimsPrincipalExtensions](group-08-auth.md#claimsprincipalextensions) for the user-id read (`CurrentUserTargetingContextAccessor.cs:1-4`). Registered by `AddAPI`; a sibling of [DisabledFeatureHandler](#disabledfeaturehandler) in the same folder. - **Concept introduced: a percentage rollout that is sticky per user rather than random per request.** A `Percentage` feature filter with no targeting rolls a die on every evaluation, so the same user sees the feature on one request and off the next: unusable for a UI. The `Targeting` filter fixes that by **hashing the audience's user id**, which makes the answer deterministic for a given user across requests and across instances, and this accessor is what supplies that id (`:10-14`). `[Rubric §29, Resilience, Reliability & Business Continuity]` assesses whether a cross-cutting toggle is applied uniformly; `[Rubric §11, Security]` and `[Rubric §17, DevOps]` both bear on the rollout being an operational lever rather than a deploy. See [ADR-031](https://ivanball.github.io/docs/adr/031-feature-flag-management.html). - Two source decisions are worth carrying. First, the user id is the standard `sub` claim `TokenService` emits, read through [ClaimsPrincipalExtensions](group-08-auth.md#claimsprincipalextensions) so the `ClaimTypes.NameIdentifier` form the bearer handler maps it to resolves identically, the same read `CurrentUserService` and [IdempotencyFilter](#idempotencyfilter) perform (`:15-19`, and `IdempotencyFilter.cs:487`); the principal's name is the fallback for a token carrying neither. Second, the accessor is a **singleton** (that is the lifetime `WithTargeting` gives it), so it cannot take the scoped `ICurrentUserService`; it reads `IHttpContextAccessor` instead and re-derives the roles itself, which the doc calls out explicitly (`:22-24`). + Two source decisions are worth carrying. First, the user id is the standard `sub` claim `TokenService` emits, read through [ClaimsPrincipalExtensions](group-08-auth.md#claimsprincipalextensions) so the `ClaimTypes.NameIdentifier` form the bearer handler maps it to resolves identically, the same read `CurrentUserService` and [IdempotencyFilter](#idempotencyfilter) perform (`:15-19`, and `IdempotencyFilter.cs:541`); the principal's name is the fallback for a token carrying neither. Second, the accessor is a **singleton** (that is the lifetime `WithTargeting` gives it), so it cannot take the scoped `ICurrentUserService`; it reads `IHttpContextAccessor` instead and re-derives the roles itself, which the doc calls out explicitly (`:22-24`). - **Walkthrough**: `GetContextAsync()` (`:63`) reads `httpContextAccessor.HttpContext?.User` (`:65`). An unauthenticated or absent principal yields an **empty** context, `UserId = null` and `Groups = []` (`:67-74`), so a targeted feature is simply off for anonymous callers unless the audience opts everyone in (`:26-29`); the method never returns null, because a feature filter must not be able to fail a request (`:57-61`). For an authenticated caller it collects the role claims, accepting each claim type the JWT middleware may produce: the standard `ClaimTypes.Role` URI when inbound claim mapping is on, or the raw `role` / `roles` claim when it is off (`:76-82`). Finally it builds the `TargetingContext` with `user.FindUserIdValue() ?? user.Identity.Name` (`:86`) and those groups (`:87`). - **Why it's built this way**: accepting three role claim types is not defensive padding, it is the concrete consequence of ASP.NET Core's inbound claim mapping being configurable; matching only `ClaimTypes.Role` would silently drop every group when a host turns mapping off, and a group-targeted rollout would then behave as an ungrouped one. The class doc carries a worked `FeatureManagement` configuration example (`:30-51`) showing a rollout that always includes the Organizer role, includes 25 percent of everyone else, and pins two named users, which is the fastest way to see what the context is actually feeding. - **Where it's used**: registered inside `AddAPI` as `services.AddFeatureManagement().WithTargeting()` (`MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:105-106`), immediately after `AddHttpContextAccessor()` (`:90`, which is `TryAdd`-based and therefore safe to call here as well as elsewhere, the reasoning recorded at `:84-89`). From there it is consumed only by the `Targeting` feature filter, whose verdicts reach the HTTP edge through `[FeatureGate]` and [DisabledFeatureHandler](#disabledfeaturehandler), and the CQRS layer through [FeatureGateCommandDecorator](group-05-cqrs-pipeline.md#featuregatecommanddecoratortcommand-tresult). @@ -1372,7 +1376,7 @@ code stays modules and domain logic, never plumbing. - `OnProvidersExecuting` (`ApiParameterDescriptorBackfillProvider.cs:49-53`) is intentionally empty, and the comment says why (`:51-52`): the descriptions this guard repairs do not exist yet at that point. - `OnProvidersExecuted` (`ApiParameterDescriptorBackfillProvider.cs:56`) null-guards the context (`:58`), flattens `context.Results.SelectMany(description => description.ParameterDescriptions)` (`:60`) and applies `parameter.ParameterDescriptor ??= new ParameterDescriptor { ... }` (`:65-71`). The `??=` is the load-bearing operator: an existing descriptor (a `ControllerParameterDescriptor`, which carries the `ParameterInfo` that XML-comment lookups prefer) is never replaced, and the comment states that rule explicitly (`:62-64`). The synthesized replacement takes `parameter.Name ?? string.Empty` (`:67`), and the type falls back through `parameter.Type`, then `ModelMetadata?.ModelType`, then `typeof(string)` (`:68-70`). - **Why it's built this way**: the class remarks (`ApiParameterDescriptorBackfillProvider.cs:12-42`) document the exact failure it guards. MVC leaves `ParameterDescriptor` null for a route-template token with no matching action parameter (normal for `[Route("api/v{version:apiVersion}/orders")]` or `[Route("api/{tenant}/orders")]`), while `Asp.Versioning.OpenApi` 10.2.1 reads `arg.ParameterDescriptor.Name` without a null check (`:21-27`). The fix is deliberately general (any unbound route token, not just the version token) so a `{tenant}` or `{region}` segment is covered by the same guard (`:28-33`). -- **Where it's used**: registered once through the private `AddApiParameterDescriptorBackfill()` helper (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.cs:116-118`), which uses `TryAddEnumerable` so calling both `AddCommonApiVersioning()` (`WebApplicationBuilderExtensions.cs:37`, which calls the helper at `:261`) and `AddCommonOpenApi()` (`:403`, calling at `:406`) still yields exactly one instance. The de-duplication rationale is on the helper's own doc (`:411-417`). +- **Where it's used**: registered once through the private `AddApiParameterDescriptorBackfill()` helper (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.cs:118-120`), which uses `TryAddEnumerable` so calling both `AddCommonApiVersioning()` (`WebApplicationBuilderExtensions.cs:37`, which calls the helper at `:261`) and `AddCommonOpenApi()` (`:403`, calling at `:406`) still yields exactly one instance. The de-duplication rationale is on the helper's own doc (`:411-417`). ### DbUpdateExceptionHandler > MMCA.Common.API · `MMCA.Common.API.Middleware` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/DbUpdateExceptionHandler.cs:17` · Level 0 · class (sealed) @@ -1390,15 +1394,15 @@ code stays modules and domain logic, never plumbing. - **Caveats / not-in-source**: the class doc comment (`DbUpdateExceptionHandler.cs:12-13`) still says "the inner exception message is included in the detail because it typically contains the database-level constraint name". The code does **not** do that: it writes the generic string at `:37`. Trust the code; the comment is stale. ### OperationCanceledExceptionHandler -> MMCA.Common.API · `MMCA.Common.API.Middleware` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/OperationCanceledExceptionHandler.cs:16` · Level 0 · class (sealed) +> MMCA.Common.API · `MMCA.Common.API.Middleware` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/OperationCanceledExceptionHandler.cs:18` · Level 0 · class (sealed) -- **What it is**: the handler that maps `OperationCanceledException` (a client that hung up mid-request) to HTTP **499 Client Closed Request** instead of letting it inflate the 500 rate. +- **What it is**: the handler that maps an `OperationCanceledException` caused by the client hanging up mid-request (`HttpContext.RequestAborted` is cancelled) to HTTP **499 Client Closed Request** instead of letting it inflate the 500 rate. Any other cancellation is deliberately left alone. - **Depends on**: `IProblemDetailsService`, `ILogger`; chain concept at [DbUpdateExceptionHandler](#dbupdateexceptionhandler). -- **Concept**: `[Rubric §13, Observability & Operability]` assesses whether the signals operators alert on distinguish real faults from normal client behavior. 499 is a non-standard nginx-origin code (class comment, `OperationCanceledExceptionHandler.cs:8-13`) that monitoring stacks read as "the caller gave up", so cancellations stop looking like server errors on a dashboard. -- **Walkthrough**: `TryHandleAsync` (`OperationCanceledExceptionHandler.cs:22`) type-tests and returns `false` for anything else (`:27-28`), logs at `LogWarning` with a client-disconnected message (`:30`), sets `StatusCodes.Status499ClientClosedRequest` (`:32`), and still builds a problem document titled `"Operation Canceled Exception"` (`:33-43`) which it writes through `TryWriteAsync` (`:45`). Note that `Status499ClientClosedRequest` is a real constant on ASP.NET Core's `StatusCodes`, so the non-standard code is spelled symbolically, not as a magic number. -- **Why it's built this way**: it is registered **first** in the chain (`MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:154`). That position matters: a cancellation propagating out of a `CancellationToken` would otherwise be claimed by nothing until [GlobalExceptionHandler](#globalexceptionhandler) turned it into a 500. It is also why every `catch` in the cache-touching middleware and limiters filters `OperationCanceledException` out explicitly rather than swallowing it (for example `SoftDeletedUserMiddleware.cs:93` and `RedisFixedWindowRateLimiter.cs:147`). -- **Where it's used**: `AddCommonExceptionHandlers()` (`DependencyInjection.cs:154`); it fires for request-abort propagation from any handler that honors its `CancellationToken`. -- **Caveats / not-in-source**: whether the 499 body actually reaches a disconnected client is not determinable from this file; the write is attempted unconditionally at `:45`. +- **Concept**: `[Rubric §13, Observability & Operability]` assesses whether the signals operators alert on distinguish real faults from normal client behavior. 499 is a non-standard nginx-origin code (class comment, `OperationCanceledExceptionHandler.cs:8-12`) that monitoring stacks read as "the caller gave up", so client cancellations stop looking like server errors on a dashboard. The same comment draws the other half of the line (`:13-14`): a cancellation the client did not cause (a downstream `HttpClient` timeout, an internal token) IS a server error and must still count as one. +- **Walkthrough**: `TryHandleAsync` (`OperationCanceledExceptionHandler.cs:24`) applies a two-part guard (`:29-33`): it returns `false` unless the exception is an `OperationCanceledException` **and** `httpContext.RequestAborted.IsCancellationRequested` is true. Past the guard it logs at `LogWarning` with a client-disconnected message (`:35`), sets `StatusCodes.Status499ClientClosedRequest` (`:37`), and still builds a problem document titled `"Operation Canceled Exception"` (`:38-48`) which it writes through `TryWriteAsync` (`:50`). Note that `Status499ClientClosedRequest` is a real constant on ASP.NET Core's `StatusCodes`, so the non-standard code is spelled symbolically, not as a magic number. +- **Why it's built this way**: it is registered **first** in the chain (`MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:154`). That position matters: a client abort propagating out of a `CancellationToken` would otherwise be claimed by nothing until [GlobalExceptionHandler](#globalexceptionhandler) turned it into a 500. The `RequestAborted` check is the reverse protection: an `OperationCanceledException` thrown while the client is still connected (a timeout inside the request) falls through to [GlobalExceptionHandler](#globalexceptionhandler) and is answered and logged as the 500 it is, rather than being hidden as a 499 Warning. It is also why every `catch` in the cache-touching middleware and limiters filters `OperationCanceledException` out explicitly rather than swallowing it (for example `SoftDeletedUserMiddleware.cs:93` and `RedisFixedWindowRateLimiter.cs:147`). +- **Where it's used**: `AddCommonExceptionHandlers()` (`DependencyInjection.cs:154`); it fires for request-abort propagation from any handler that honors its `CancellationToken`, and only while `RequestAborted` is signalled. +- **Caveats / not-in-source**: whether the 499 body actually reaches a disconnected client is not determinable from this file; once the guard passes, the write is attempted unconditionally at `:50`. ### QueryFilterModelBinder > MMCA.Common.API · `MMCA.Common.API.ModelBinders` · `MMCA.Common/Source/Presentation/MMCA.Common.API/ModelBinders/QueryFilterModelBinder.cs:24` · Level 0 · class (sealed) @@ -1414,7 +1418,7 @@ code stays modules and domain logic, never plumbing. - A second pass removes any entry still missing an operator or a value (`:74-80`): incomplete filters are silently discarded, never a 400. - Finally `ModelBindingResult.Success(filters)` (`:82`) and `Task.CompletedTask` (`:83`): the binder is synchronous work behind an async signature, so it allocates no task machinery. - **Why it's built this way**: silent discard plus case-insensitive matching makes the grammar forgiving for hand-built UI query strings, while the hard cap keeps a malicious caller from turning the query string into a reflection amplifier. -- **Where it's used**: applied per parameter with `[ModelBinder(typeof(QueryFilterModelBinder))]` on the list actions of [IEntityControllerBase](#ientitycontrollerbasetentitydto-tidentifiertype) (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/IEntityControllerBase.cs:51`) and [EntityControllerBase](#entitycontrollerbasetentity-tentitydto-tidentifiertype) (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:163` for the paged read and `:256` for the CSV export). +- **Where it's used**: applied per parameter with `[ModelBinder(typeof(QueryFilterModelBinder))]` on the list actions of [IEntityControllerBase](#ientitycontrollerbasetentitydto-tidentifiertype) (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/IEntityControllerBase.cs:51`) and [EntityControllerBase](#entitycontrollerbasetentity-tentitydto-tidentifiertype) (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:166` for the paged read and `:256` for the CSV export). ### ValidationExceptionHandler > MMCA.Common.API · `MMCA.Common.API.Middleware` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/ValidationExceptionHandler.cs:17` · Level 0 · class (sealed) @@ -1454,7 +1458,7 @@ code stays modules and domain logic, never plumbing. - **What it is**: the single table that maps domain [ErrorType](group-01-result-error-handling.md#errortype) values to HTTP status codes, the ranking rule that picks one status for a whole failure, and the builder for the `errors` extension array that problem documents carry. - **Depends on**: [Error](group-01-result-error-handling.md#error), [ErrorType](group-01-result-error-handling.md#errortype), [ErrorTypeSeverity](group-01-result-error-handling.md#errortypeseverity), [IErrorLocalizer](#ierrorlocalizer); `System.Collections.Frozen` and ASP.NET `StatusCodes` (`ErrorHttpMapping.cs:1-4`). -- **Concept: the Result-to-HTTP translation table.** This is where the [Result](group-01-result-error-handling.md#result) pattern meets the HTTP contract. `[Rubric §9, API & Contract Design]` assesses whether error responses are consistent across the whole surface: because both [ApiControllerBase](#apicontrollerbase) and [UnhandledResultFailureFilter](#unhandledresultfailurefilter) call into this one class, they cannot drift (the class doc says exactly that, `ErrorHttpMapping.cs:8-13`). `[Rubric §7, Microservices Readiness]` applies to the ranking: the severity order itself lives in `MMCA.Common.Shared` (`MMCA.Common/Source/Core/MMCA.Common.Shared/Abstractions/ErrorTypeSeverity.cs:30`) so the gRPC edge classifies the same aggregate identically (`ErrorHttpMapping.cs:45-46`, and see `MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:117`). `[Rubric §27, i18n]` applies through the localizer parameter. +- **Concept: the Result-to-HTTP translation table.** This is where the [Result](group-01-result-error-handling.md#result) pattern meets the HTTP contract. `[Rubric §9, API & Contract Design]` assesses whether error responses are consistent across the whole surface: because both [ApiControllerBase](#apicontrollerbase) and [UnhandledResultFailureFilter](#unhandledresultfailurefilter) call into this one class, they cannot drift (the class doc says exactly that, `ErrorHttpMapping.cs:8-13`). `[Rubric §7, Microservices Readiness]` applies to the ranking: the severity order itself lives in `MMCA.Common.Shared` (`MMCA.Common/Source/Core/MMCA.Common.Shared/Abstractions/ErrorTypeSeverity.cs:30`) so the gRPC edge classifies the same aggregate identically (`ErrorHttpMapping.cs:45-46`, and see `MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:122`). `[Rubric §27, i18n]` applies through the localizer parameter. - **Walkthrough** - `ErrorTypeToStatusCode` (`ErrorHttpMapping.cs:20-31`): a `FrozenDictionary` with `Validation` to 400, `Invariant` to 400, `NotFound` to 404, `Conflict` to 409, `Unauthorized` to 401, `Forbidden` to 403, `UnprocessableEntity` to 422, `Failure` to 400, and `Unexpected` to 500. `FrozenDictionary` is the right structure for a table built once at startup and read on every failed request: it trades slower construction for the fastest lookups (`:16-19`). - `GetStatusCode(ErrorType)` (`:37-38`) uses `GetValueOrDefault(..., Status400BadRequest)`, so a future error type falls back to 400 rather than throwing inside an error path. @@ -1475,7 +1479,7 @@ code stays modules and domain logic, never plumbing. - For a match, it clears `schema.Properties` and `schema.Required` (`:39-40`), which erases the object shape the reflection-based generator would otherwise have built, then sets `schema.Type = MapType(valueType)` and `schema.Format = MapFormat(valueType)` (`:41-42`). - `MapType` (`:47-50`, `internal static`) returns `JsonSchemaType.Integer` for `int`/`long`, else `JsonSchemaType.String`. `MapFormat` (`:52-61`) returns `"int32"`, `"int64"`, `"uuid"` (for `Guid`), or `null`, matching the OpenAPI format vocabulary a client generator already understands. - **Why it's built this way**: both mapping methods are `internal static` rather than private, so [StronglyTypedIdParameterTransformer](#stronglytypedidparametertransformer) reuses the identical type/format decision for route and query parameters instead of duplicating the switch; the two transformers can never disagree about how one wrapper renders. -- **Where it's used**: registered unconditionally inside `AddCommonOpenApi()` via `options.AddSchemaTransformer(new StronglyTypedIdSchemaTransformer())` on every `OpenApiOptions` instance (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.cs:102`, inside the `ConfigureAll` block at `:502-506`, whose comment states it is inert in a host that declares no wrappers, `:498-501`). `ConfigureAll` rather than a single named document is deliberate: `AddOpenApi()` creates one `OpenApiOptions` per discovered API version, and the wire shape is identical in every one. +- **Where it's used**: registered unconditionally inside `AddCommonOpenApi()` via `options.AddSchemaTransformer(new StronglyTypedIdSchemaTransformer())` on every `OpenApiOptions` instance (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.cs:104`, inside the `ConfigureAll` block at `:502-506`, whose comment states it is inert in a host that declares no wrappers, `:498-501`). `ConfigureAll` rather than a single named document is deliberate: `AddOpenApi()` creates one `OpenApiOptions` per discovered API version, and the wire shape is identical in every one. ### TenantResolutionMiddleware > MMCA.Common.API · `MMCA.Common.API.Middleware` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/TenantResolutionMiddleware.cs:36` · Level 3 · class (sealed) @@ -1500,13 +1504,13 @@ code stays modules and domain logic, never plumbing. - **What it is**: an `IOpenApiOperationTransformer` that rewrites the per-operation parameter schema for a strongly typed identifier bound from a route segment or a query string, the operation-level counterpart to [StronglyTypedIdSchemaTransformer](#stronglytypedidschematransformer). - **Depends on**: `Microsoft.AspNetCore.OpenApi.IOpenApiOperationTransformer`, `Microsoft.OpenApi.Models.OpenApiParameter`/`OpenApiSchema`; [StronglyTypedIdSchemaTransformer](#stronglytypedidschematransformer)`.MapType`/`.MapFormat` for the type/format decision, and `StronglyTypedId.TryDescribe` to recognize a wrapper. - **Concept**: a component-level schema and a parameter-level schema are generated by two different ASP.NET Core OpenAPI extension points, so fixing the component schema alone leaves an operation's own parameter list still describing the wrapper's object shape; the framework installs one transformer of each kind so both descriptions agree. `[Rubric §9, API & Contract Design]` applies for the same reason as the sibling type: a client generator reading only the operation's parameters (the common case for a route or query parameter) needs the primitive shape too. -- **Walkthrough**: `TransformAsync(OpenApiOperation, OpenApiOperationTransformerContext, CancellationToken)` (`StronglyTypedIdSchemaTransformer.cs:85-117`): - - Null-guards `operation` and `context` (`:90-91`), then exits early when `operation.Parameters is null` (`:93-94`). - - Iterates `context.Description.ParameterDescriptions` (`:96`), one entry per bound parameter on the action. For each, it resolves the effective type as `described.ModelMetadata?.ModelType ?? described.Type` (`:98`) and skips it when that type is null or is not a strongly typed identifier per `StronglyTypedId.TryDescribe` (`:99-100`). - - Matches the description back to the operation's `OpenApiParameter` by name: `operation.Parameters.OfType().FirstOrDefault(p => string.Equals(p.Name, described.Name, StringComparison.Ordinal))` (`:102-104`), and skips a miss (`:106-107`). - - Replaces `parameter.Schema` outright with a new `OpenApiSchema { Type = StronglyTypedIdSchemaTransformer.MapType(valueType), Format = StronglyTypedIdSchemaTransformer.MapFormat(valueType) }` (`:109-113`), rather than mutating the existing schema in place, since the wrapper's original parameter schema carries nothing worth preserving. +- **Walkthrough**: `TransformAsync(OpenApiOperation, OpenApiOperationTransformerContext, CancellationToken)` (`StronglyTypedIdSchemaTransformer.cs:77-109`): + - Null-guards `operation` and `context` (`:82-83`), then exits early when `operation.Parameters is null` (`:85-86`). + - Iterates `context.Description.ParameterDescriptions` (`:88`), one entry per bound parameter on the action. For each, it resolves the effective type as `described.ModelMetadata?.ModelType ?? described.Type` (`:90`) and skips it when that type is null or is not a strongly typed identifier per `StronglyTypedId.TryDescribe` (`:91-92`). + - Matches the description back to the operation's `OpenApiParameter` by name: `operation.Parameters.OfType().FirstOrDefault(p => string.Equals(p.Name, described.Name, StringComparison.Ordinal))` (`:94-96`), and skips a miss (`:98-99`). + - Replaces `parameter.Schema` outright with a new `OpenApiSchema { Type = StronglyTypedIdSchemaTransformer.MapType(valueType), Format = StronglyTypedIdSchemaTransformer.MapFormat(valueType) }` (`:101-105`), rather than mutating the existing schema in place, since the wrapper's original parameter schema carries nothing worth preserving. - **Why it's built this way**: reusing the sibling transformer's `MapType`/`MapFormat` instead of re-deriving the mapping keeps the component schema and every parameter schema for the same wrapper type identical by construction; there is no separate table to fall out of sync. -- **Where it's used**: registered unconditionally alongside its sibling inside `AddCommonOpenApi()` via `options.AddOperationTransformer(new StronglyTypedIdParameterTransformer())` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.cs:103`), inside the same `ConfigureAll` block (`:502-506`) that installs [StronglyTypedIdSchemaTransformer](#stronglytypedidschematransformer). +- **Where it's used**: registered unconditionally alongside its sibling inside `AddCommonOpenApi()` via `options.AddOperationTransformer(new StronglyTypedIdParameterTransformer())` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.cs:105`), inside the same `ConfigureAll` block (`:502-506`) that installs [StronglyTypedIdSchemaTransformer](#stronglytypedidschematransformer). ### UnhandledResultFailureFilter > MMCA.Common.API · `MMCA.Common.API.Middleware` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/UnhandledResultFailureFilter.cs:22` · Level 4 · class (sealed, partial) @@ -1526,19 +1530,21 @@ code stays modules and domain logic, never plumbing. - **Where it's used**: added as a global MVC filter inside `AddAPI(...)` (`MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:50`), so it applies to every controller action in every host. ### CorrelationIdMiddleware -> MMCA.Common.API · `MMCA.Common.API.Middleware` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/CorrelationIdMiddleware.cs:15` · Level 9 · class (sealed) +> MMCA.Common.API · `MMCA.Common.API.Middleware` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/CorrelationIdMiddleware.cs:18` · Level 9 · class (sealed) - **What it is**: convention-based middleware that establishes one correlation ID per request, publishes it on the scoped [ICorrelationContext](#icorrelationcontext), and echoes it back to the caller in the `X-Correlation-ID` response header. - **Depends on**: [ICorrelationContext](#icorrelationcontext) (injected per invoke, so it is resolved from the request scope) and `System.Diagnostics.Activity` (`CorrelationIdMiddleware.cs:1-3`). - **Concept**: distributed trace correlation is introduced at [ICorrelationContext](#icorrelationcontext); this is the type that populates it. `[Rubric §13, Observability & Operability]` assesses whether one logical operation can be reconstructed across log entries and services: with this middleware second in the pipeline, every later log line, outbox row and downstream call can carry the same ID. - **Walkthrough** - - `HeaderName = "X-Correlation-ID"` (`CorrelationIdMiddleware.cs:18`) is a public `const`, so clients, tests and downstream code all name the header from one place. - - `InvokeAsync(HttpContext, ICorrelationContext)` (`:27`): the second parameter is **per-invoke injected**, which is how convention-based middleware consumes a scoped service from a singleton middleware instance. Both arguments are null-guarded (`:29-30`). - - The resolution waterfall (`:32-34`): the inbound `X-Correlation-ID` header wins, else `Activity.Current?.TraceId` (the W3C trace ID that OpenTelemetry propagates), else ASP.NET's `HttpContext.TraceIdentifier`. A caller-supplied ID is therefore honored, and the ID always exists. - - `correlationContext.SetCorrelationId(correlationId)` (`:36`) publishes it, then `context.Response.OnStarting(...)` (`:37-41`) registers a callback that writes the response header. Writing it in `OnStarting` rather than after `await next(...)` is the only safe way: once the response has begun, header writes throw. - - `await next(context)` (`:43`) continues the pipeline. -- **Why it's built this way**: accepting the client's ID makes cross-system correlation possible when the caller already has a trace, and falling back to `Activity.Current` means the correlation ID and the OpenTelemetry trace ID are the same value in a normally instrumented host. -- **Where it's used**: it is the second step of the default pipeline, right after the exception handler (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/Pipeline/MiddlewarePipelineBuilder.cs:38-40`, following `ExceptionHandler` at `:34-36`), so everything downstream (including the exception handlers above) logs under a known ID. The step is named `MiddlewarePipelineStepNames.CorrelationId` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/Pipeline/MiddlewarePipelineStepNames.cs:19`), which is the handle a host uses to insert its own middleware around it. The CQRS logging decorators read the same context. + - `HeaderName = "X-Correlation-ID"` (`CorrelationIdMiddleware.cs:21`) is a public `const`, so clients, tests and downstream code all name the header from one place. + - `MaxLength = 64` (`:24`) is an `internal const`: the width of the persisted correlation columns (its doc comment at `:23`). + - `InvokeAsync(HttpContext, ICorrelationContext)` (`:33`): the second parameter is **per-invoke injected**, which is how convention-based middleware consumes a scoped service from a singleton middleware instance. Both arguments are null-guarded (`:35-36`). + - The resolution waterfall (`:38-41`): an inbound `X-Correlation-ID` header wins when it is not null, empty or whitespace (`string.IsNullOrWhiteSpace`, `:39`), and is passed through `Truncate` (`:41`); otherwise `Activity.Current?.TraceId` (the W3C trace ID that OpenTelemetry propagates), else ASP.NET's `HttpContext.TraceIdentifier` (`:40`). A caller-supplied ID is therefore honored, a blank one is treated as absent, and the ID always exists. + - `Truncate` (`:56`) cuts a caller value longer than `MaxLength` to its first 64 characters with a range slice and returns shorter values unchanged. + - `correlationContext.SetCorrelationId(correlationId)` (`:43`) publishes it, then `context.Response.OnStarting(...)` (`:44-48`) registers a callback that writes the response header. Writing it in `OnStarting` rather than after `await next(...)` is the only safe way: once the response has begun, header writes throw. + - `await next(context)` (`:50`) continues the pipeline. +- **Why it's built this way**: accepting the client's ID makes cross-system correlation possible when the caller already has a trace, and falling back to `Activity.Current` means the correlation ID and the OpenTelemetry trace ID are the same value in a normally instrumented host. Truncating at the boundary rather than at the database means the id echoed in the response header is exactly the one stored (class comment, `:13-15`), and treating a blank header as absent keeps an empty value from displacing the trace-ID fallback. +- **Where it's used**: it is the second step of the default pipeline, right after the exception handler (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/Pipeline/MiddlewarePipelineBuilder.cs:38-40`, following `ExceptionHandler` at `:34-36`), so everything downstream (including the exception handlers above) logs under a known ID. The step is named `MiddlewarePipelineStepNames.CorrelationId` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/Pipeline/MiddlewarePipelineStepNames.cs:20`), which is the handle a host uses to insert its own middleware around it. The CQRS logging decorators read the same context. ### SoftDeletedUserMiddleware > MMCA.Common.API · `MMCA.Common.API.Middleware` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/SoftDeletedUserMiddleware.cs:31` · Level 9 · class (sealed, partial) @@ -1607,7 +1613,7 @@ code stays modules and domain logic, never plumbing. - `FixedWindow` (`RateLimitAlgorithm.cs:15`) is the default and the cheapest to run: the whole allowance becomes available again at the window boundary, which lets a caller spend the allowance twice across a boundary, once at the end of one window and once at the start of the next (`:10-14`). - `SlidingWindow` (`:22`) divides the one-minute window into `SegmentsPerWindow` segments and returns each segment's permits as it ages out, smoothing that boundary burst away at the cost of tracking one counter per segment (`:17-21`). - **Why it's built this way**: an enum rather than a boolean keeps the configuration self-describing in `appsettings.json` (`"Algorithm": "SlidingWindow"`) and leaves room for a third algorithm without a breaking rename. The distributed Redis path is deliberately **not** an algorithm value: it is the separate `Distributed` flag on [RateLimitingSettings](#ratelimitingsettings), because it changes *where* the counter lives rather than how it replenishes, and [RedisFixedWindowRateLimiter](#redisfixedwindowratelimiter) implements only the fixed window. -- **Where it's used**: [RateLimitingSettings](#ratelimitingsettings)`.Algorithm` defaults it to `FixedWindow` (`MMCA.Common/Source/Presentation/MMCA.Common.API/RateLimiting/RateLimitingSettings.cs:53`), and `CreateLimitedPartition` branches on it (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:237`) to build either a `SlidingWindowRateLimiterOptions` partition (`:239-246`) or a `FixedWindowRateLimiterOptions` one (`:249-255`). +- **Where it's used**: [RateLimitingSettings](#ratelimitingsettings)`.Algorithm` defaults it to `FixedWindow` (`MMCA.Common/Source/Presentation/MMCA.Common.API/RateLimiting/RateLimitingSettings.cs:53`), and `CreateLimitedPartition` branches on it (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:239`) to build either a `SlidingWindowRateLimiterOptions` partition (`:239-246`) or a `FixedWindowRateLimiterOptions` one (`:249-255`). ### RedisRateLimitLease > MMCA.Common.API · `MMCA.Common.API.RateLimiting` · `MMCA.Common/Source/Presentation/MMCA.Common.API/RateLimiting/RedisFixedWindowRateLimiter.cs:169` · Level 0 · class (internal sealed) @@ -1627,7 +1633,7 @@ code stays modules and domain logic, never plumbing. - **Concept (a UI-specific entry point, ordered first).** The doc comment (`CommonForwardedHeadersExtensions.cs:14-17`) states the ordering contract directly: call it **FIRST** in a server-rendered UI host's pipeline, before anything reads the scheme, the host, or the client address (security headers, rate limiting, HTTPS redirection, antiforgery). `[Rubric §13, Observability & Operability]` and `[Rubric §29, Resilience & Business Continuity]` both apply: a UI host that reads the wrong scheme before forwarded headers are adopted can mis-evaluate an HTTPS-only cookie or antiforgery check. - **Walkthrough**: `UseCommonUiForwardedHeaders(ForwardedHeaders? headers = null)` (`:24-25`) is a one-line `extension` member that calls `app.UseForwardedHeaders(CommonForwardedHeaders.Create(headers))`. - **Why it's built this way**: a named extension on `IApplicationBuilder` rather than a bare call to `CommonForwardedHeaders.Create()` plus `UseForwardedHeaders` gives UI hosts one call to place first, matching the naming convention of the service-host pipeline members on [WebApplicationExtensions](#webapplicationextensions). [CommonForwardedHeaders](#commonforwardedheaders)'s own doc comment cross-references this member (`CommonForwardedHeaders.cs:9`) so a reader lands on the UI-host entry point from either type. -- **Where it's used**: the ADC Blazor UI host calls it first in its pipeline, before `UseCommonMiddlewarePipeline`-equivalent steps (`MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:192`). +- **Where it's used**: the ADC Blazor UI host calls it first in its pipeline, before `UseCommonMiddlewarePipeline`-equivalent steps (`MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:204`). - **Caveats / not-in-source**: nothing enforces the "call it first" contract; a host that places it later gets whatever scheme and host the raw connection reports until this call runs. ### RateLimitingSettings @@ -1642,7 +1648,7 @@ code stays modules and domain logic, never plumbing. - `Algorithm` (`:53`) defaults to `RateLimitAlgorithm.FixedWindow`, which is what the framework has always used; `SegmentsPerWindow` (`:62`, `[Range(1, 60)]`, default 4) is the number of segments the one-minute window is divided into when the algorithm is `SlidingWindow`, and is ignored otherwise (`:55-60`). - `Distributed` (`:72`, default `false`) is the interesting one. Its doc (`:64-71`) states the whole contract: it makes the global limiter and the `"UserPolicy"` limiter count against a shared Redis counter instead of per-instance memory, so a limit means the same thing behind a load balancer as it does on one node; it requires an `IConnectionMultiplexer` in the container, and when none is registered the limiters **silently fall back** to the in-memory behavior rather than failing startup; and the `auth-ip` policy stays in memory either way, because per-account login protection already backs it. - `HubPathPrefixes` (`:91`, default `["/hubs"]`) and `AnonymousHubPermitLimit` (`:99`, `[Range(1, 1_000_000)]`, default 60) are a pair added for SEC-ADC-25 (`RateLimitingSettings.cs:75-91`, `:93-99`). The global limiter exempts anonymous callers everywhere else on purpose (public reads are output-cached, server-rendered Blazor traffic shares one host IP); a real-time hub is the exception, because a gateway that bypasses `/hubs` at the edge (ADR-024 requires it, since the hub authenticates from a query-string token the edge cannot read) left anonymous `/hubs/*/negotiate` traffic metered nowhere. `HubPathPrefixes` names which paths get that anonymous-IP metering, and `AnonymousHubPermitLimit` is deliberately generous, since one browser tab reconnecting behind a flaky network legitimately negotiates several times a minute. -- **Why it's built this way**: bundling the knobs into one bound object is what let `AddCommonRateLimiting` grow a configuration overload without changing the five-parameter one, and the "same defaults" rule means the two overloads are observationally identical for a host that configures nothing (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:326-334` builds an instance from the old parameters). The fall-back-rather-than-fail choice on `Distributed` is deliberate: a rate limiter that refuses to start is a worse outage than one that limits per instance. The `AuthIpPermitLimit` default of 30 is itself argued in the overload's parameter doc (`WebApplicationBuilderExtensions.RateLimiting.cs:318-324`): Blazor Server circuits issue the login call server-side, so every Server-circuit user shares the UI host's IP and a tighter cap would throttle real logins. +- **Why it's built this way**: bundling the knobs into one bound object is what let `AddCommonRateLimiting` grow a configuration overload without changing the five-parameter one, and the "same defaults" rule means the two overloads are observationally identical for a host that configures nothing (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:328-336` builds an instance from the old parameters). The fall-back-rather-than-fail choice on `Distributed` is deliberate: a rate limiter that refuses to start is a worse outage than one that limits per instance. The `AuthIpPermitLimit` default of 30 is itself argued in the overload's parameter doc (`WebApplicationBuilderExtensions.RateLimiting.cs:320-326`): Blazor Server circuits issue the login call server-side, so every Server-circuit user shares the UI host's IP and a tighter cap would throttle real logins. - **Where it's used**: bound in `AddCommonRateLimiting(IConfiguration)` with `configuration.GetSection(RateLimitingSettings.SectionName).Get() ?? new RateLimitingSettings()` (`WebApplicationBuilderExtensions.RateLimiting.cs:348-349`), then threaded through every partition selector (`GlobalRateLimitPartition` at `:136`, `UserPolicyRateLimitPartition` at `:171`, `AuthIpRateLimitPartition` at `:278`) and into `CreateLimitedPartition` (`:205-256`), which reads `Distributed` (`:214`), `Algorithm` (`:237`), and `SegmentsPerWindow` (`:242`). `HubPathPrefixes` and `AnonymousHubPermitLimit` are read by `AnonymousPartition` (`WebApplicationBuilderExtensions.RateLimiting.cs:58-68`) and `IsAnonymousHubRequest` (`:78-88`, internal so `InternalsVisibleTo` lets it be unit-tested directly): an unauthenticated request that matches a hub prefix is metered per client IP through `CreateLimitedPartition` with `redisScope: "hub"` and `allowDistributed: true` (`:60-67`); every other anonymous request keeps taking `RateLimitPartition.GetNoLimiter("__anonymous")` (`:68`). ### RedisFixedWindowRateLimiter @@ -1662,8 +1668,8 @@ code stays modules and domain logic, never plumbing. - `GetStatistics()` (`:101`) returns `null` on purpose: the counter lives in Redis and reporting it would cost a round trip per call for a diagnostic the middleware does not require (`:96-100`). - `AttemptAcquireCore(int)` (`:111`) stamps the last-used timestamp and always permits (`:113-114`). The doc (`:103-108`) is explicit that the ASP.NET Core middleware uses the asynchronous path exclusively, so this exists only to satisfy the base contract, and blocking a request thread on a Redis round trip would be strictly worse than the fail-open posture the limiter already takes. - `AcquireAsyncCore` (`:118`) is the real path: stamp the timestamp (`:122`), reject outright when `permitCount > _permitLimit` (`:124-127`), compute the window as `GetUtcNow().ToUnixTimeSeconds() / 60` (`:129`) and build the invariant-culture key (`:130`), `StringIncrementAsync` (`:135`), and, when `count <= permitCount` (meaning this increment created the key), set a 65-second TTL (`:142`). That skew past the window length covers clock drift between instances, since an early-expiring key would hand the partition a fresh allowance inside the same window (`:139-141`). The decision is the final compare (`:145`). The catch (`:147-155`) excludes `OperationCanceledException`, logs at most once per window via `Interlocked.Exchange` on the static field (`:149-152`), and returns `Acquired`. -- **Why it's built this way**: `partial` plus `[LoggerMessage]` (`:158-161`) gives a source-generated, allocation-free warning that names the partition and states plainly that requests are permitted uncounted until Redis recovers. The partition key arrives already scoped by the caller (for example `global:alice`, `:58-61`) so two policies limiting the same user never share one counter; that scoping is applied in `CreateLimitedPartition` as `$"{redisScope}:{key}"` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:229`). -- **Where it's used**: constructed only inside `CreateLimitedPartition` (`WebApplicationBuilderExtensions.RateLimiting.cs:227-229`), and only when `allowDistributed && settings.Distributed` is true (`:214`) **and** an `IConnectionMultiplexer` resolves (`:220-222`); the logger falls back to `NullLogger` when the request has no service provider (`:224-225`, which is also why `RequestServices` is read through a nullable local, `:216-219`). When no multiplexer is registered the code falls through to the in-memory limiters rather than failing startup (`:232-234`). The `auth-ip` policy passes `allowDistributed: false` (`WebApplicationBuilderExtensions.RateLimiting.cs:291`), so login throttling never depends on Redis. +- **Why it's built this way**: `partial` plus `[LoggerMessage]` (`:158-161`) gives a source-generated, allocation-free warning that names the partition and states plainly that requests are permitted uncounted until Redis recovers. The partition key arrives already scoped by the caller (for example `global:alice`, `:58-61`) so two policies limiting the same user never share one counter; that scoping is applied in `CreateLimitedPartition` as `$"{redisScope}:{key}"` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:231`). +- **Where it's used**: constructed only inside `CreateLimitedPartition` (`WebApplicationBuilderExtensions.RateLimiting.cs:229-231`), and only when `allowDistributed && settings.Distributed` is true (`:214`) **and** an `IConnectionMultiplexer` resolves (`:220-222`); the logger falls back to `NullLogger` when the request has no service provider (`:224-225`, which is also why `RequestServices` is read through a nullable local, `:216-219`). When no multiplexer is registered the code falls through to the in-memory limiters rather than failing startup (`:232-234`). The `auth-ip` policy passes `allowDistributed: false` (`WebApplicationBuilderExtensions.RateLimiting.cs:293`), so login throttling never depends on Redis. ### MiniProfilerExtensions > MMCA.Common.API · `MMCA.Common.API.Startup` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/MiniProfilerExtensions.cs:9` · Level 1 · class (static, extension block) @@ -1699,8 +1705,8 @@ code stays modules and domain logic, never plumbing. - **Depends on**: [NotificationHub](group-10-notifications.md#notificationhub) (Infrastructure), [PushNotificationSettings](group-14-module-system-composition.md#pushnotificationsettings), and `IOptions`. - **Concept (conditional real-time endpoint mapping).** `[Rubric §6, CQRS & Event-Driven]`: the SignalR hub is the real-time delivery arm of the notification pipeline, so mapping it behind a settings gate means a host that does not push notifications simply never opens the endpoint, and the same `Program.cs` line is safe in every host. - **Walkthrough**: `MapNotificationHub()` (`SignalRExtensions.cs:22`) resolves `IOptions` through `GetService()`, which returns null when nothing registered it, and takes `?.Value` (`:24`). Only when `settings is { Enabled: true }` does it call `MapHub(settings.HubPath)` (`:25-28`). The doc comment (`:16-21`) notes it must run after `UseCommonMiddlewarePipeline()` so authentication and routing are already in place. -- **Why it's built this way**: `GetService` rather than `GetRequiredService`, plus the property-pattern guard, is what makes the call unconditionally safe; it matches the same "always call, no-op if not applicable" convention as the JWKS and OIDC mappers. The hub path is also why the JWT bearer options carry an `access_token` query-string fallback for `/hubs` on both authentication paths (`WebApplicationBuilderExtensions.cs:605-618` and `:556-569`): a WebSocket cannot send an `Authorization` header. -- **Where it's used**: the ADC Notification service maps it after the shared pipeline (`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:289`, with the pipeline itself at `:244`); that host reads the hub path from configuration rather than hard-coding it, which the comment there records (`:257-260`). +- **Why it's built this way**: `GetService` rather than `GetRequiredService`, plus the property-pattern guard, is what makes the call unconditionally safe; it matches the same "always call, no-op if not applicable" convention as the JWKS and OIDC mappers. The hub path is also why the JWT bearer options carry an `access_token` query-string fallback for `/hubs` on both authentication paths (`WebApplicationBuilderExtensions.Authentication.cs:111-124` and `:162-175`): a WebSocket cannot send an `Authorization` header. +- **Where it's used**: the ADC Notification service maps it after the shared pipeline (`MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:276`, with the pipeline itself at `:244`); that host reads the hub path from configuration rather than hard-coding it, which the comment there records (`:257-260`). ### ModuleHostExtensions > MMCA.Common.API · `MMCA.Common.API.Startup` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/ModuleHostExtensions.cs:22` · Level 4 · class (static, extension block) @@ -1741,28 +1747,28 @@ code stays modules and domain logic, never plumbing. - `CorsPolicyAllowSpecificOrigins` / `CorsPolicyAllowAll` (`WebApplicationBuilderExtensions.cs:25`, `:28`): the two policy names the pipeline chooses between by environment. - `AddCommonApiVersioning()` (`WebApplicationBuilderExtensions.cs:37`): header-based versioning through the `api-version` reader with `AssumeDefaultVersionWhenUnspecified` and `ReportApiVersions` (`:44-46`), the API explorer group format `'v'VVV` and `SubstituteApiVersionInUrl` (`:48-51`), then the backfill guard (`:54`). The comment (`:39-41`) records that `DefaultApiVersion` is deliberately not set because 1.0 is already the framework default and restating it trips AV0011/AV0024. See [ADR-046](https://ivanball.github.io/docs/adr/046-http-api-versioning.html). - `AddCommonResponseCompression()` (`WebApplicationBuilderExtensions.cs:62`): Brotli plus Gzip, enabled for HTTPS, both at `CompressionLevel.Fastest` (`:64-76`); the comment (`:72-74`) justifies Fastest for gzip too on fractional-vCPU hosts serving dynamic payloads. - - `AddCommonOpenApi()` (`WebApplicationBuilderExtensions.cs:91`): `services.AddApiVersioning().AddOpenApi()` (`:93`) plus the backfill guard (`:94`). It also registers [StronglyTypedIdSchemaTransformer](#stronglytypedidschematransformer) and [StronglyTypedIdParameterTransformer](#stronglytypedidparametertransformer) on every discovered API version through `services.ConfigureAll` (ADR-115, `:100-104`), rather than a named document, because `AddOpenApi` above creates one `OpenApiOptions` per discovered version and the wire shape is identical in all of them; the comment (`:96-99`) notes this is inert in a host that declares no strongly typed identifier wrappers. The comment on the base call (`:86-87`) notes the parameterless `AddApiVersioning()` only returns the builder, so options configured by `AddCommonApiVersioning` accumulate independently of call order. Pair it with `MapCommonOpenApi()` on [OpenApiEndpointExtensions](#openapiendpointextensions). - - `AddApiParameterDescriptorBackfill()` (`WebApplicationBuilderExtensions.cs:116`, private) registers [ApiParameterDescriptorBackfillProvider](#apiparameterdescriptorbackfillprovider) via `TryAddEnumerable` (`:117-118`), which de-duplicates on implementation type, so calling both `AddCommonApiVersioning` and `AddCommonOpenApi` installs the guard exactly once. - - `AddCommonCors(IConfiguration)` (`WebApplicationBuilderExtensions.cs:124`): the restrictive production policy takes its origins from `Cors:AllowedOrigins` and allowlists four headers and five methods with `AllowCredentials` (`:128-134`); the development any-origin policy sits under a justified `#pragma warning disable S5122` explaining it is only ever selected when the environment is Development (`:136-141`). See [ADR-082](https://ivanball.github.io/docs/adr/082-two-tier-cors-posture.html). + - `AddCommonOpenApi()` (`WebApplicationBuilderExtensions.cs:95`) registers **no OpenAPI document of its own**: it installs the backfill guard (`:97`) and registers [StronglyTypedIdSchemaTransformer](#stronglytypedidschematransformer) and [StronglyTypedIdParameterTransformer](#stronglytypedidparametertransformer) through `services.ConfigureAll` (ADR-115, `:102-106`), rather than on a named document, so whatever document names the host registers are all covered; the comment (`:99-101`) notes this is inert in a host that declares no strongly typed identifier wrappers. The host must make the `services.AddOpenApi()` call in its own project (either order relative to this one), because the OpenAPI XML-comment source generator attaches the host's controller summaries by intercepting the `AddOpenApi` call sites of the project being compiled, and a registration made inside this assembly would carry none of them (doc comment `:82-87`). Pair it with `MapCommonOpenApi()` on [OpenApiEndpointExtensions](#openapiendpointextensions), which the same doc comment says fails at startup outside Production when no `v1` document was registered (`:87-89`). + - `AddApiParameterDescriptorBackfill()` (`WebApplicationBuilderExtensions.cs:118`, private) registers [ApiParameterDescriptorBackfillProvider](#apiparameterdescriptorbackfillprovider) via `TryAddEnumerable` (`:119-120`), which de-duplicates on implementation type, so calling both `AddCommonApiVersioning` and `AddCommonOpenApi` installs the guard exactly once (`:111-117`). + - `AddCommonCors(IConfiguration)` (`WebApplicationBuilderExtensions.cs:126`): the restrictive production policy takes its origins from `Cors:AllowedOrigins` and allowlists four headers and five methods with `AllowCredentials` (`:130-136`); the development any-origin policy sits under a justified `#pragma warning disable S5122` explaining it is only ever selected when the environment is Development (`:138-143`). See [ADR-082](https://ivanball.github.io/docs/adr/082-two-tier-cors-posture.html). - `RateLimitPolicyAuthIp` (`WebApplicationBuilderExtensions.RateLimiting.cs:23`): the named `"auth-ip"` policy for anonymous authentication attempts. Its doc comment (`:16-22`) states why it exists: the global limiter deliberately no-ops for anonymous traffic and per-account lockout is per-email, which would leave a password spray (one password, many emails) from a single source unthrottled. - `IsRateLimitBypassed(HttpContext)` (`WebApplicationBuilderExtensions.RateLimiting.cs:42`) exempts `/health`, `/alive` and `/.well-known/*` (`:43-45`), then a mapped gRPC endpoint through `IsGrpcEndpoint` (`:46`), all legitimately high-frequency. The gRPC arm used to key on the request's `Content-Type` header; the remark (`:27-36`, SEC-Common-44) records why it no longer does: a caller-supplied `Content-Type: application/grpc` header is unverifiable and used to hand any authenticated account the no-limiter partition, switching off its 300/min cap entirely, while routed endpoint metadata comes from the server's own `MapGrpcService` registrations and cannot be forged. It is `internal` rather than private specifically so the exemption logic is unit-testable through `InternalsVisibleTo` instead of only under a request flood (`:37-40`). - `AnonymousPartition(HttpContext, RateLimitingSettings)` (`:58`, private) is the partition an unauthenticated request now counts against: `RateLimitPartition.GetNoLimiter("__anonymous")` everywhere except a real-time hub path, where `IsAnonymousHubRequest` routes it through `CreateLimitedPartition` keyed on the client IP with `redisScope: "hub"` and `settings.AnonymousHubPermitLimit` (`:59-68`). The remark (SEC-ADC-25, `:52-57`) is the reason: a gateway that bypasses `/hubs` at the edge (ADR-024 requires it, because the hub authenticates from a query-string token the edge cannot read) left an anonymous negotiate flood metered nowhere at all. - `IsAnonymousHubRequest(HttpContext, RateLimitingSettings)` (`:78`) null-guards both arguments (`:80-81`) and matches the request path against `settings.HubPathPrefixes` (`:85-87`); `internal` for the same `InternalsVisibleTo` reason. - `GrpcServerMetadataNamespace` (`:95`, the literal `"Grpc.AspNetCore.Server"`) and `IsGrpcEndpoint(HttpContext)` (`:103`) together are the routed-metadata check: matched by namespace name so MMCA.Common.API needs no package reference on the gRPC server stack, which only the extracted service hosts take (`:90-93`), returning true only when the resolved endpoint carries metadata from that namespace (`:105-121`). - - `GlobalRateLimitPartition` has two overloads. The permit-count one (`:128-129`) simply wraps its argument in a [RateLimitingSettings](#ratelimitingsettings) and delegates; the settings one (`:136`) holds the logic: a `NoLimiter` for bypassed infrastructure (`:138-141`) and, for unauthenticated callers, a delegate to `AnonymousPartition` rather than an unconditional `NoLimiter` (`:143-146`), otherwise a partition keyed by `Identity.Name`, then the user-id claim, then the remote IP, then the literal `"authenticated"` (`:148-151`), built through `CreateLimitedPartition` with `redisScope: "global"`, `queueLimit: 0` and `allowDistributed: true` (`:153-160`). - - `UserPolicyRateLimitPartition` (`:171`) is the same shape for the opt-in `"UserPolicy"` limiter: one bucket per authenticated user, falling back to the client IP and then a shared anonymous bucket (`:173-175`), with `redisScope: "user"` and the configured queue limit (`:177-184`). It was extracted from the inline lambda it used to be so the key selection is unit-testable (`:167-169`). - - `CreateLimitedPartition` (`:205`) is the single place a partition is built, and reading it is the fastest way to understand the whole limiter. When `allowDistributed && settings.Distributed` it resolves `IConnectionMultiplexer` through a **nullable** local, because `HttpContext.RequestServices` is declared non-nullable but is genuinely null outside a request pipeline such as a bare `DefaultHttpContext` in a unit test (`:214-220`). With a connection it returns a partition whose factory builds a [RedisFixedWindowRateLimiter](#redisfixedwindowratelimiter) over `$"{redisScope}:{key}"` (`:227-229`), falling back to a null logger when none is registered (`:224-225`). Without a connection it deliberately **falls through** to the in-memory limiters rather than failing startup, so a host that turns the flag on before wiring Redis degrades to per-instance limits instead of losing rate limiting altogether (`:232-234`). The in-memory branch honours [RateLimitAlgorithm](#ratelimitalgorithm): a sliding window with `SegmentsPerWindow` segments (`:237-247`) or the default fixed window (`:249-255`), both over `TimeSpan.FromMinutes(1)` with `QueueProcessingOrder.OldestFirst`. - - `AuthIpRateLimitPartition` (`:269` permit-count overload, `:278` settings overload) partitions the `"auth-ip"` policy on the client IP and returns **no limiter at all** when the IP is unattributable (`:282-283`). The remark (`:262-267`) explains the choice: failing open on a null IP beats collapsing every such request into one shared bucket, which would throttle the in-process `TestServer` and the integration tier to a standstill. It passes `allowDistributed: false` (`:291`), so login throttling stays per-instance whatever `Distributed` says, because per-account login protection already backs it and a login throttle that fails open on a Redis outage is a worse trade than one that stays local (`:200-203`). - - `AddCommonRateLimiting` has **three** overloads (all `WebApplicationBuilderExtensions.RateLimiting.cs`). The permit-count one (`:326`) keeps the defaults `permitLimit: 100, queueLimit: 2, perUserPermitLimit: 30, globalPermitLimit: 300, authIpPermitLimit: 30` and simply builds a [RateLimitingSettings](#ratelimitingsettings) from them (`:326-334`). The `IConfiguration` one (`:344`) binds the `RateLimiting` section, falling back to a default instance when the section is absent (`:348-349`). The settings one (`:362`) does the work: rejection status 429 (`:368`), the always-on `GlobalLimiter` (`:370-371`), the opt-in `"FixedPolicy"` (`:376-383`, `allowDistributed: false`), `"UserPolicy"` (`:385`), and `auth-ip` (`:395-397`). Two comments carry the reasoning: `"FixedPolicy"` keeps its name whichever algorithm is configured, because it is referenced by name from `[EnableRateLimiting]` attributes in three repos and renaming it on a settings change would silently unlimit every endpoint using it (`:373-375`); and the `auth-ip` policy takes the client IP from `Connection.RemoteIpAddress`, which the shared pipeline has already resolved from `X-Forwarded-For` because `UseForwardedHeaders` runs before `UseRateLimiter` (`:387-394`). The doc comment on the permit-count overload (`:296-325`) explains why anonymous traffic is deliberately unlimited and why `authIpPermitLimit` is 30 rather than a tighter 10: Blazor Server circuits issue the login call server-side, so every Server-circuit user shares the UI host's IP. See [ADR-019](https://ivanball.github.io/docs/adr/019-rate-limiting.html). + - `GlobalRateLimitPartition` has two overloads. The permit-count one (`:129-130`) simply wraps its argument in a [RateLimitingSettings](#ratelimitingsettings) and delegates; the settings one (`:137`) holds the logic: a `NoLimiter` for bypassed infrastructure (`:139-142`) and, for unauthenticated callers, a delegate to `AnonymousPartition` rather than an unconditional `NoLimiter` (`:144-147`), otherwise a partition keyed by the user-id (subject) claim, then `Identity.Name`, then the remote IP, then the literal `"authenticated"` (`:149-152`), built through `CreateLimitedPartition` with `redisScope: "global"`, `queueLimit: 0` and `allowDistributed: true` (`:154-161`). The subject claim leads because it is unique per account, while the name claim carries the full name, which two users can share and would then pool into one bucket (`:124-126`). + - `UserPolicyRateLimitPartition` (`:172`) is the same shape for the opt-in `"UserPolicy"` limiter: one bucket per authenticated user (subject claim, then name), falling back to the client IP and then a shared anonymous bucket (`:174-177`), with `redisScope: "user"` and the configured queue limit (`:179-186`). It was extracted from the inline lambda it used to be so the key selection is unit-testable (`:168-171`). + - `CreateLimitedPartition` (`:207`) is the single place a partition is built, and reading it is the fastest way to understand the whole limiter. When `allowDistributed && settings.Distributed` it resolves `IConnectionMultiplexer` through a **nullable** local, because `HttpContext.RequestServices` is declared non-nullable but is genuinely null outside a request pipeline such as a bare `DefaultHttpContext` in a unit test (`:216-222`). With a connection it returns a partition whose factory builds a [RedisFixedWindowRateLimiter](#redisfixedwindowratelimiter) over `$"{redisScope}:{key}"` (`:229-231`), falling back to a null logger when none is registered (`:226-227`). Without a connection it deliberately **falls through** to the in-memory limiters rather than failing startup, so a host that turns the flag on before wiring Redis degrades to per-instance limits instead of losing rate limiting altogether (`:234-236`). The in-memory branch honours [RateLimitAlgorithm](#ratelimitalgorithm): a sliding window with `SegmentsPerWindow` segments (`:239-249`) or the default fixed window (`:251-257`), both over `TimeSpan.FromMinutes(1)` with `QueueProcessingOrder.OldestFirst`. + - `AuthIpRateLimitPartition` (`:271` permit-count overload, `:280` settings overload) partitions the `"auth-ip"` policy on the client IP and returns **no limiter at all** when the IP is unattributable (`:284-285`). The remark (`:264-269`) explains the choice: failing open on a null IP beats collapsing every such request into one shared bucket, which would throttle the in-process `TestServer` and the integration tier to a standstill. It passes `allowDistributed: false` (`:293`), so login throttling stays per-instance whatever `Distributed` says, because per-account login protection already backs it and a login throttle that fails open on a Redis outage is a worse trade than one that stays local (`:202-205`). + - `AddCommonRateLimiting` has **three** overloads (all `WebApplicationBuilderExtensions.RateLimiting.cs`). The permit-count one (`:328`) keeps the defaults `permitLimit: 100, queueLimit: 2, perUserPermitLimit: 30, globalPermitLimit: 300, authIpPermitLimit: 30` and simply builds a [RateLimitingSettings](#ratelimitingsettings) from them (`:328-336`). The `IConfiguration` one (`:346`) null-guards the configuration (`:348`), registers `RateLimitingSettings` as options bound to the `RateLimiting` section with data-annotation validation on start, so a `[Range]` violation fails at startup rather than building a limiter that throws on every authenticated request (`:350-353`), and then reads the same section into the settings it passes on, falling back to a default instance when the section is absent (`:355-357`). The settings one (`:368`) does the work: rejection status 429 (`:374`), the always-on `GlobalLimiter` (`:376-377`), the opt-in `"FixedPolicy"` (`:382-389`, `allowDistributed: false`), `"UserPolicy"` (`:391`), and `auth-ip` (`:401-403`). Two comments carry the reasoning: `"FixedPolicy"` keeps its name whichever algorithm is configured, because it is referenced by name from `[EnableRateLimiting]` attributes in three repos and renaming it on a settings change would silently unlimit every endpoint using it (`:379-381`); and the `auth-ip` policy takes the client IP from `Connection.RemoteIpAddress`, which the shared pipeline has already resolved from `X-Forwarded-For` because `UseForwardedHeaders` runs before `UseRateLimiter` (`:393-400`). The doc comment on the permit-count overload (`:298-327`) explains why anonymous traffic is deliberately unlimited and why `authIpPermitLimit` is 30 rather than a tighter 10: Blazor Server circuits issue the login call server-side, so every Server-circuit user shares the UI host's IP. See [ADR-019](https://ivanball.github.io/docs/adr/019-rate-limiting.html). - `AddForwardedJwtBearer(authority, audience, configuration, environment, requireHttpsMetadata = null)` (`WebApplicationBuilderExtensions.Authentication.cs:51`) is the **extracted-service** mode. It validates all four required arguments (`:58-61`), then resolves the metadata posture in three steps: the explicit argument when it is not null, then `RequireHttpsMetadataConfigKey`, then `true` everywhere except Development (`:63-65`). When the resolved value is `false` outside Development it registers [InsecureJwtMetadataWarningStartupFilter](#insecurejwtmetadatawarningstartupfilter) so the deviation is logged once at startup (`:67-71`), and finally delegates to the private `AddForwardedJwtBearerCore` (`:73`). Its `authority` argument is normally the result of [JwtAuthorityExtensions](#jwtauthorityextensions)`.GetRequiredJwtAuthority()`. - `RequireHttpsMetadataConfigKey` (`:24`, value `"Authentication:JwtBearer:RequireHttpsMetadata"`): the one configuration key that can override the secure-by-default metadata posture. Its comment (`:18-23`) narrows the legitimate use to an authority that is genuinely plain HTTP (an internal-ingress h2c service URL) and asks for the justification to be recorded beside the setting. - `AddForwardedJwtBearerCore` (`:76`, private) does the JWT wiring: `Authority`, `Audience` and `RequireHttpsMetadata` (`:84-86`), validation parameters that deliberately leave `ValidIssuer` unset so the middleware takes the issuer from the discovery document (`:94-99`), and `ValidAlgorithms = [RsaSha256]` as defense against an algorithm-confusion swap (`:101-107`). It also installs the SignalR `access_token` query-string fallback for `/hubs` (`:110-124`) and then calls `AddAuthorizationPolicies()` (`:127`). - `AddCommonAuthentication(IConfiguration)` (`:144`) is the **in-process** mode: it binds [JwtSettings](group-08-auth.md#jwtsettings) with data-annotation validation on start (`:146-149`), builds validation parameters through `BuildValidationParameters` (`:157`), wires the same `/hubs` access-token fallback (`:162-175`), and adds the authorization policies (`:178`). - `GetValidatedSigningKey(string)` (`:188`, `internal static`, outside the extension block) decodes the Base64 HMAC key and throws when it is under 256 bits (`:190-195`), so a too-short secret fails at startup rather than weakening every token. - `BuildValidationParameters(JwtSettings)` (`:207`, also `internal static`) branches on [JwtSigningAlgorithm](group-08-auth.md#jwtsigningalgorithm): RS256 requires `RsaPublicKeyPem` and throws a message that points at `AddForwardedJwtBearer` when it is missing (`:211-215`), imports the PEM into an `RSA` held for the app lifetime (`:217-220`, with a justified CA2000 suppression) and pins RS256 (`:230`); the default HS256 path builds a `SymmetricSecurityKey` from the validated secret and pins HmacSha256 (`:234-247`). -- **Why it's built this way**: two authentication entry points are the framework's monolith-to-microservice hinge ([ADR-004](https://ivanball.github.io/docs/adr/004-authentication-dual-fetch.html)): the monolith or the issuing Identity service validates in process against a local key, while an extracted service validates against the issuer's published JWKS with no shared secret. The explicit `ValidAlgorithms` pin on **both** paths is deliberate defense in depth rather than trust in the token header. Taking `IConfiguration` and `IHostEnvironment` as required arguments on `AddForwardedJwtBearer` is what makes "HTTPS metadata unless you say otherwise" the default a host cannot forget: the insecure value stays reachable for the deployments that need it, but only through a named key and never silently. On the limiter side, factoring every partition through `CreateLimitedPartition` is what let the Redis counter and the sliding window arrive without touching a single partition-key rule: the policy names, the bypass list and every key are identical whatever the settings say, and only the permit counts, the algorithm and the counter's location change (`WebApplicationBuilderExtensions.RateLimiting.cs:354-358`). Keying the gRPC exemption and the hub exemption on server-controlled state (routed endpoint metadata, configured hub path prefixes) rather than caller-supplied headers is the same defense-in-depth posture applied to the exemption list itself, not just to the authenticated-caller partitions. -- **Where it's used**: every ADC and Store service host calls the builder-side registrations (`AddCommonCors`, `AddCommonApiVersioning`, `AddCommonRateLimiting`, `AddCommonResponseCompression`) at startup. Identity hosts take the in-process mode while the other services take the forwarded mode, passing `builder.Configuration` and `builder.Environment` so the framework resolves the metadata posture (`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:327-331`, `MMCA.Store/Source/Services/MMCA.Store.Sales.Service/Program.cs:179-183`). The `"auth-ip"` policy is applied by attribute on the login and register actions of [AuthControllerBase](#authcontrollerbase) (`AuthControllerBase.cs:72` and `:94`), so every consumer inheriting that base gets it without opting in. The internal partition helpers are exercised directly by `WebApplicationBuilderExtensionsTests`, `RateLimitPartitionTests` and `RateLimitAlgorithmSelectionTests`; the metadata resolution by `ForwardedJwtBearerSecurityTests`. -- **Caveats / not-in-source**: the five permit-limit defaults are framework defaults only. What a given deployment actually enforces is whatever the host passes or configures under `RateLimiting`, and that configuration value is not determinable from this file. Likewise `Distributed` only takes effect when the host also registers an `IConnectionMultiplexer`; whether it does is host composition, not this file. +- **Why it's built this way**: two authentication entry points are the framework's monolith-to-microservice hinge ([ADR-004](https://ivanball.github.io/docs/adr/004-authentication-dual-fetch.html)): the monolith or the issuing Identity service validates in process against a local key, while an extracted service validates against the issuer's published JWKS with no shared secret. The explicit `ValidAlgorithms` pin on **both** paths is deliberate defense in depth rather than trust in the token header. Taking `IConfiguration` and `IHostEnvironment` as required arguments on `AddForwardedJwtBearer` is what makes "HTTPS metadata unless you say otherwise" the default a host cannot forget: the insecure value stays reachable for the deployments that need it, but only through a named key and never silently. On the limiter side, factoring every partition through `CreateLimitedPartition` is what let the Redis counter and the sliding window arrive without touching a single partition-key rule: the policy names, the bypass list and every key are identical whatever the settings say, and only the permit counts, the algorithm and the counter's location change (`WebApplicationBuilderExtensions.RateLimiting.cs:360-365`). Keying the gRPC exemption and the hub exemption on server-controlled state (routed endpoint metadata, configured hub path prefixes) rather than caller-supplied headers is the same defense-in-depth posture applied to the exemption list itself, not just to the authenticated-caller partitions. +- **Where it's used**: every ADC and Store service host calls the builder-side registrations (`AddCommonCors`, `AddCommonApiVersioning`, `AddCommonRateLimiting`, `AddCommonResponseCompression`) at startup. Identity hosts take the in-process mode while the other services take the forwarded mode, passing `builder.Configuration` and `builder.Environment` so the framework resolves the metadata posture (`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:327-331`, `MMCA.Store/Source/Services/MMCA.Store.Sales.Service/Program.cs:179-183`). Each ADC service host pairs its own `services.AddOpenApi()` with `AddCommonOpenApi()` (`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:317-318`). The `"auth-ip"` policy is applied by attribute on the login and register actions of [AuthControllerBase](#authcontrollerbase) (`AuthControllerBase.cs:72` and `:96`), so every consumer inheriting that base gets it without opting in. The internal partition helpers are exercised directly by `WebApplicationBuilderExtensionsTests`, `RateLimitPartitionTests` and `RateLimitAlgorithmSelectionTests`; the metadata resolution by `ForwardedJwtBearerSecurityTests`. +- **Caveats / not-in-source**: the five permit-limit defaults are framework defaults only. What a given deployment actually enforces is whatever the host passes or configures under `RateLimiting`, and that configuration value is not determinable from this file. Likewise `Distributed` only takes effect when the host also registers an `IConnectionMultiplexer`; whether it does is host composition, not this file. The doc comment on the permit-count `AddCommonRateLimiting` overload still describes the gRPC bypass as `application/grpc` traffic (`WebApplicationBuilderExtensions.RateLimiting.cs:305-307`); the code keys it on routed endpoint metadata (`IsGrpcEndpoint`, `:103`), so trust the code over that sentence. ### WebApplicationExtensions > MMCA.Common.API · `MMCA.Common.API.Startup` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationExtensions.cs:16` · Level 10 · class (static, extension block) @@ -1771,34 +1777,37 @@ code stays modules and domain logic, never plumbing. - **Depends on**: [MiddlewarePipelineBuilder](#middlewarepipelinebuilder) (which owns the step list) and [SupportedCultures](#supportedcultures); ASP.NET localization and cookie primitives. - **Concept (one canonical, ordered pipeline, applied through one private helper).** `[Rubric §13, Observability & Operability]` and `[Rubric §13, Observability & Operability]`: middleware order is behavior, not taste. Correlation must be established before anything downstream logs, and authentication must run before the rate limiter so the per-user partition sees a principal at all. Centralizing the order means a host cannot get it wrong ([ADR-079](https://ivanball.github.io/docs/adr/079-shared-http-middleware-pipeline.html)). `[Rubric §27, i18n]` applies through the localization wiring ([ADR-027](https://ivanball.github.io/docs/adr/027-multi-locale-i18n.html)). - **Walkthrough** - - Two internal constants, `PreForwardedSchemeKey` (`WebApplicationExtensions.cs:24`) and `PreForwardedHostKey` (`:33`), name the `HttpContext.Items` slots that the pipeline's `PreForwardedCapture` step writes **before** `UseForwardedHeaders` rewrites scheme and host. The comment on the host key (`:24-32`) records why: Aspire/DCP injects an `X-Forwarded-Host` pointing at the canonical launchSettings URL, which internal callers cannot reach. - - `UseCommonMiddlewarePipeline()` (`:46`) is a one-liner: `ApplyPipeline(app, configure: null)`. Its doc comment (`:37-45`) states the contract in one sentence worth keeping: the order is data, not prose, named by [MiddlewarePipelineStepNames](#middlewarepipelinestepnames) and frozen by the `MiddlewarePipelineOrderTestsBase` fitness function. - - `UseCommonMiddlewarePipeline(Action configure)` (`:58`) is the scoped escape hatch: it null-guards the delegate (`:60`) and routes through the same helper (`:61`). The XML doc declares both failure modes, `ArgumentNullException` and the `InvalidOperationException` an invariant violation raises (`:56-57`). - - `UseCommonRequestLocalization()` (`:71`) builds the supported list from [SupportedCultures](#supportedcultures)`.All` (`:73`), appends the pseudo-locale in **Development only** (`:78-81`), and sets the default plus both supported and supported-UI culture lists (`:84-87`). It is itself the `RequestLocalization` step of the default pipeline, and Blazor UI hosts call it explicitly before `MapRazorComponents` so SSR prerender runs under the right culture (`:64-70`). - - `MapCultureEndpoint()` (`:102`) is now a one-line default that calls the new `MapCultureEndpoint(bool httpOnly)` overload with `httpOnly: false`, the value it always used. `MapCultureEndpoint(bool httpOnly)` (`:115`) does the real work: it maps the anonymous `GET /culture/set?culture=&redirectUri=` that the culture switcher calls, honors only allowlisted cultures and the pseudo-locale only in Development (`:119`, `:122`), writes the standard ASP.NET culture cookie with the caller-chosen `HttpOnly` (`:128-136`, with `Secure` conditional on the environment and both `S3330`/`S2092` deviations justified inline at `:124`), then local-redirects (`:140-141`) to force a full reload. The `httpOnly` parameter (`:108-114`) exists so a Server-only host that never runs WASM can pass `true` and keep the cookie out of script reach; a host that serves a WebAssembly client must keep the parameterless overload's `false`, because the WASM runtime reads the cookie directly on startup (`MmcaCultureBootstrap.SetBrowserCultureAsync`). - - `ApplyPipeline` (`:138`, private) is the whole application step: seed the defaults (`:140`), let the host's delegate adjust them if there is one (`:141`), then `foreach` over `builder.Build()` invoking each step's `Configure` in order (`:143-146`). Because both public overloads route through here, the zero-argument path is exactly the validated default pipeline (`:133-137`). + - Two internal constants, `PreForwardedSchemeKey` (`WebApplicationExtensions.cs:24`) and `PreForwardedHostKey` (`:35`), name the `HttpContext.Items` slots that the pipeline's `PreForwardedCapture` step writes **before** `UseForwardedHeaders` rewrites scheme and host. The comment on the host key (`:26-34`) records why: Aspire/DCP injects an `X-Forwarded-Host` pointing at the canonical launchSettings URL, which internal callers cannot reach. + - `UseCommonMiddlewarePipeline()` (`:48`) is a one-liner: `ApplyPipeline(app, configure: null)`. Its doc comment (`:39-47`) states the contract in one sentence worth keeping: the order is data, not prose, named by [MiddlewarePipelineStepNames](#middlewarepipelinestepnames) and frozen by the `MiddlewarePipelineOrderTestsBase` fitness function. + - `UseCommonMiddlewarePipeline(Action configure)` (`:60`) is the scoped escape hatch: it null-guards the delegate (`:62`) and routes through the same helper (`:63`). The XML doc declares both failure modes, `ArgumentNullException` and the `InvalidOperationException` an invariant violation raises (`:58-59`). + - `UseCommonRequestLocalization()` (`:73`) builds the supported list from [SupportedCultures](#supportedcultures)`.All` (`:75`), appends the pseudo-locale in **Development only** (`:80-83`), and sets the default plus both supported and supported-UI culture lists (`:86-89`). It is itself the `RequestLocalization` step of the default pipeline, and Blazor UI hosts call it explicitly before `MapRazorComponents` so SSR prerender runs under the right culture (`:66-72`). + - `MapCultureEndpoint()` (`:102`) is a one-line default that calls the `MapCultureEndpoint(bool httpOnly)` overload with `httpOnly: false`. `MapCultureEndpoint(bool httpOnly)` (`:115`) does the real work: it maps the anonymous `GET /culture/set?culture=&redirectUri=` that the culture switcher calls, honors only allowlisted cultures and the pseudo-locale only in Development (`:119`, `:122`), writes the standard ASP.NET culture cookie with the caller-chosen `HttpOnly` (`:125-136`, with `Secure` conditional on the environment and both `S3330`/`S2092` deviations justified inline at `:124`), then local-redirects (`:143-144`) to force a full reload. The redirect target passes through `IsLocalRedirectTarget` first and falls back to `/` when the requested target is missing or not local (`:140-143`): `Results.LocalRedirect` throws at execution for a non-local URL, which on this anonymous endpoint would surface as a 500 rather than a redirect. The `httpOnly` parameter (`:108-114`) exists so a Server-only host that never runs WASM can pass `true` and keep the cookie out of script reach; a host that serves a WebAssembly client must keep the parameterless overload's `false`, because the WASM runtime reads the cookie directly on startup (`MmcaCultureBootstrap.SetBrowserCultureAsync`). + - `IsLocalRedirectTarget(string?)` (`:158`, private) applies the rule `LocalRedirect` itself enforces, ahead of it (`:151-157`): a non-blank value with a single leading `/` that is not followed by a second `/` or a `\`, since browsers treat either of those as a protocol-relative URL to another host (`:159-161`). + - `ApplyPipeline` (`:168`, private) is the whole application step: seed the defaults (`:170`), let the host's delegate adjust them if there is one (`:171`), then `foreach` over `builder.Build()` invoking each step's `Configure` in order (`:173-176`). Because both public overloads route through here, the zero-argument path is exactly the validated default pipeline (`:163-167`). - **Why it's built this way**: pushing the step list out into [MiddlewarePipelineBuilder](#middlewarepipelinebuilder) and keeping only `ApplyPipeline` here is what lets the order be inspected and asserted while the entry point stays a single line in a host's `Program.cs`. The `configure`-then-`Build` sequence is deliberate: the host mutates first and the invariants are checked last, so a customization is judged on its result rather than on the order the host happened to make its edits. -- **Where it's used**: called once per service host after `app.Build()`, for example `MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:444`, `MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:268`, `MMCA.Store/Source/Services/MMCA.Store.Sales.Service/Program.cs:278`, and `MMCA.Helpdesk/Source/Hosts/MMCA.Helpdesk.Web/Program.cs:130`. The Blazor UI hosts instead call the localization and culture-endpoint members directly (`MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:220` and `:169`). +- **Where it's used**: called once per service host after `app.Build()`, for example `MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:444`, `MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:262`, `MMCA.Store/Source/Services/MMCA.Store.Sales.Service/Program.cs:299`, and `MMCA.Helpdesk/Source/Hosts/MMCA.Helpdesk.Web/Program.cs:142`. The Blazor UI hosts instead call the localization and culture-endpoint members directly (`MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:232` and `:271`). - **Caveats / not-in-source**: a host that maps additional endpoints (SignalR hubs, minimal-API endpoints, app-association documents) does so after this call; the framework cannot enforce that ordering, it only documents it on the members that require it (for example [SignalRExtensions](#signalrextensions)`.MapNotificationHub`, `MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/SignalRExtensions.cs:16-21`). Note also that `MMCA.Common.UI` ships a *different* `WebApplicationExtensions` (see [WebApplicationExtensions](group-15-common-ui-framework.md#webapplicationextensions) in the UI framework chapter); the two share a name and nothing else. ### DatabaseInitializationExtensions -> MMCA.Common.API · `MMCA.Common.API.Startup` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:20` · Level 13 · class (static, extension block) +> MMCA.Common.API · `MMCA.Common.API.Startup` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:28` · Level 15 · class (static, two extension blocks) -- **What it is**: the shared startup routine every downstream host calls once after `app.Build()`. It walks each **physical data source** the host actually uses, creates or migrates that schema according to the configured strategy, repeats the pass for every tenant that keeps its own copy of a source, and finally runs each enabled module's seeder. -- **Depends on**: [IEntityDataSourceRegistry](group-07-persistence-ef-core.md#ientitydatasourceregistry), [IDataSourceResolver](group-07-persistence-ef-core.md#idatasourceresolver), [IDbContextFactory](group-07-persistence-ef-core.md#idbcontextfactory), [DataSourceKey](group-07-persistence-ef-core.md#datasourcekey) and its [DataSource](group-07-persistence-ef-core.md#datasource) engine enum, [PhysicalDataSource](group-07-persistence-ef-core.md#physicaldatasource), [TenantDataSourceTargets](group-07-persistence-ef-core.md#tenantdatasourcetargets) / [TenantDataSourceTarget](group-07-persistence-ef-core.md#tenantdatasourcetarget), [ITenantContext](group-05-cqrs-pipeline.md#itenantcontext), [TenancySettings](group-07-persistence-ef-core.md#tenancysettings), [ApplicationSettings](group-14-module-system-composition.md#applicationsettings), and [ModuleLoader](group-14-module-system-composition.md#moduleloader). Externally it uses only `IServiceProvider` scoping, `IOptions`, and EF Core's `DatabaseFacade` (`MigrateAsync`, `EnsureCreatedAsync`, `GetPendingMigrationsAsync`). +- **What it is**: the shared startup routine every downstream host calls once after `app.Build()`, either directly or through a `WebApplication` wrapper that skips it during build-time OpenAPI generation. It walks each **physical data source** the host actually uses, creates or migrates that schema according to the configured strategy, repeats the pass for every tenant that keeps its own copy of a source, and finally runs each enabled module's seeder. +- **Depends on**: [IEntityDataSourceRegistry](group-07-persistence-ef-core.md#ientitydatasourceregistry), [IDataSourceResolver](group-07-persistence-ef-core.md#idatasourceresolver), [IDbContextFactory](group-07-persistence-ef-core.md#idbcontextfactory), [DataSourceKey](group-07-persistence-ef-core.md#datasourcekey) and its [DataSource](group-07-persistence-ef-core.md#datasource) engine enum, [PhysicalDataSource](group-07-persistence-ef-core.md#physicaldatasource), [TenantDataSourceTargets](group-07-persistence-ef-core.md#tenantdatasourcetargets) / [TenantDataSourceTarget](group-07-persistence-ef-core.md#tenantdatasourcetarget), [ITenantContext](group-05-cqrs-pipeline.md#itenantcontext), [TenancySettings](group-07-persistence-ef-core.md#tenancysettings), [ApplicationSettings](group-14-module-system-composition.md#applicationsettings), [ModuleLoader](group-14-module-system-composition.md#moduleloader), and [ModuleHostContext](#modulehostcontext) for the `WebApplication` wrapper. Externally it uses only `IServiceProvider` scoping, `IOptions`, the host's environment and configuration, and EF Core's `DatabaseFacade` (`MigrateAsync`, `EnsureCreatedAsync`, `GetPendingMigrationsAsync`). - **Concept (strategy-driven, per-source database initialization).** `[Rubric §8, Data Architecture]` assesses deliberate persistence decisions including migrations: because of database-per-service ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)) a host may own several physical databases at once, so "initialize the database" is really a loop over sources, and polyglot persistence ([ADR-018](https://ivanball.github.io/docs/adr/018-polyglot-persistence.html)) means those sources do not all have a migrations pipeline. `[Rubric §17, DevOps]` assesses how schema reaches an environment: the single `DatabaseInitStrategy` string is the switch between a development host that self-migrates on boot and a production host whose migrations are applied by the deployment pipeline and which must therefore refuse to start if it finds itself behind. `[Rubric §13, Observability & Operability]`: every failure path here throws a message that names the offending value or the exact pending migrations plus the command to run, so a bad boot is diagnosable from the startup log alone. -- **Walkthrough**: the entire public surface is one extension member on `IServiceProvider` (`DatabaseInitializationExtensions.cs:22`), declared with C# `extension(T)` syntax like the rest of the framework's DI and startup helpers. - - `InitializeDatabaseAsync(applicationSettings, moduleLoader, cancellationToken)` (`:35-38`) starts by null-guarding all three inputs (`:40-42`). - - **Strategy is validated first** (`:47`). `EnsureKnownStrategy` (`:182`) accepts only the ordinal strings `"Migrate"` and `"None"` (`:184-185`) and otherwise throws `UnknownStrategy` (`:187`), whose message names the valid values (`:194-195`). The comment (`:44-46`) gives the reason for doing it up front: a misspelled strategy is a configuration mistake, and the host must stop before the code below has already created the migrationless sources. - - It then opens a scope (`:49`) and **warms the entity data-source registry**: resolving `IEntityDataSourceRegistry` and calling `GetPhysicalSourcesInUse()` (`:54-56`) scans the configuration assemblies once so entity-to-database routing is deterministic before the first repository call, replacing a lazy model-building side effect (`:51-53`). - - **The migrationless pass** (`:70-87`) covers Cosmos, PostgreSQL and SQLite sources in use (`:71`). A source with an empty connection string is skipped (`:75-78`), since integration tests may omit one; a source where `PhysicalDataSource.UsesMigrations` is true is also skipped (`:80-83`); everything left is created outright with `EnsureCreatedAsync` (`:85-86`). Two comments carry the load here. The first (`:60-65`) says this is the **only** path that creates such a source: without it the first repository call fails. The second (`:67-69`) explains the `UsesMigrations` exclusion: running `EnsureCreated` against a PostgreSQL or SQLite source that does have a migrations assembly writes the tables with no `__EFMigrationsHistory` row, after which every migration is both pending and un-appliable because its `CREATE TABLE` hits an existing table. `UsesMigrations` itself is the per-engine rule on the source record (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/PhysicalDataSource.cs:41-46`): SQL Server always, PostgreSQL and SQLite only with a configured migrations assembly (`PostgreSQLMigrationsAssembly` / `SqliteMigrationsAssembly`), Cosmos never. - - **The strategy switch** (`:92-102`) has exactly two live branches. `"Migrate"` delegates to `IDbContextFactory.MigrateAsync` (`:95`), which applies pending migrations for every migration-owned source in use (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/IDbContextFactory.cs:74-81`). `"None"` calls `ThrowIfPendingMigrationsAsync` (`:98`). The `default` arm rethrows `UnknownStrategy` (`:101`), unreachable in practice because of the guard at `:47` but kept so the switch is total. - - **The tenant pass** runs next (`:104-105`) and module seeding last (`:111`). Seeding deliberately runs on the default scope only; the comment (`:107-110`) states the rationale: a seeder writes reference data the application needs to boot, no module declares which of its seeders are tenant-scoped, and running one twice against a shared database is worse than not running it per tenant at all. - - `InitializeTenantDatabasesAsync` (`:125`) resolves `IOptions` with `GetService` rather than `GetRequiredService` (`:132`) and returns immediately when tenancy was never registered or no tenants are configured (`:133-136`). Otherwise it expands the sources into targets via `TenantDataSourceTargets.Expand` and keeps only the ones with a tenant id (`:138-139`), because `Expand` also emits the shared, tenant-less target for each source, which the pass above already handled. For each target it opens a **fresh scope through `IServiceScopeFactory.CreateTenantScope(target)`** (`:140`), the shared helper (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:134`) every other per-tenant background pass (the outbox processor, the internal-command processor, the audit-trail and outbox cleanup jobs) also calls; it creates the scope and, when the target carries a tenant id, calls `ITenantContext.SetTenant` on it before returning (`TenantDataSourceTargets.cs:138-142`), so the tenant is always set *before* anything asks that scope for a context. The remark (`:120-124`) is still the reason a fresh scope is required at all: the scoped context factory binds one physical database per source for the life of a scope, so reusing the outer scope would keep handing back the shared database. - - Per tenant it reads `UsesMigrations` off the *shared* resolved source (`:150`), on the grounds stated at `:147-149`: a tenant's copy is the same schema on a different connection, the migrations assembly is declared once on the source, and a per-tenant override only replaces the connection string. It then applies the same strategy (`:152-171`): under `"Migrate"` a migrated source gets `MigrateAsync` and anything else gets `EnsureCreatedAsync` (`:155-162`), under `"None"` it defers to `ThrowIfTenantPendingMigrationsAsync` (`:166`). - - **The two production rails.** `ThrowIfTenantPendingMigrationsAsync` (`:201`) no-ops for a non-migrated source (`:207-210`), otherwise queries `GetPendingMigrationsAsync` (`:212`) and, when anything is pending, throws naming the tenant target, the migrations, and the `dotnet ef database update` remedy (`:218-221`). `ThrowIfPendingMigrationsAsync` (`:241`) short-circuits on `IDbContextFactory.HasPendingMigrationsAsync` (`:247-250`), then builds a per-source breakdown by re-querying each key that passes `IsMigrationTarget` (`:252-261`) before throwing (`:263-265`). `IsMigrationTarget` (`:231-233`) mirrors the factory's own rule (a source a migrations pipeline owns, minus an optional non-SQL-Server source left without a connection string) so the breakdown names exactly the sources the factory checked, rather than reporting an empty list (`:224-228`). -- **Why it's built this way**: one shared init path keeps every service host consistent, so adding a database engine or a tenant changes this file rather than five `Program.cs` files. PostgreSQL joining the migrationless pass alongside Cosmos and SQLite ([ADR-113](https://ivanball.github.io/docs/adr/113-postgresql-as-a-first-class-engine.html)) is exactly that: a host that names no `PostgreSQLMigrationsAssembly` gets the same `EnsureCreatedAsync` treatment a migrations-less SQLite source already got, with no change to the strategy switch below it. The `"None"` strategy is the deploy-time guarantee that an app never serves traffic against an un-migrated database when migrations are the pipeline's job, and validating the strategy string before any side effect keeps a typo from silently degrading into "schema untouched, first query fails". The tenant pass exists because nothing else ever opens a per-tenant database ([ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html)), so without it such a database is never created and never migrated (`:116-118`). -- **Where it's used**: called once per host after `app.Build()`, before the middleware pipeline is wired: `MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:440`, `MMCA.Store/Source/Services/MMCA.Store.Sales.Service/Program.cs:275`, and `MMCA.Helpdesk/Source/Hosts/MMCA.Helpdesk.Web/Program.cs:127` are representative; the ADC and Store service hosts pass `moduleHost.ApplicationSettings` and `moduleHost.ModuleLoader` off [ModuleHostContext](#modulehostcontext) (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/ModuleHostContext.cs:52`). The migrationless-engine loop and the strategy contract are covered by `DatabaseInitializationExtensionsTests` (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:42`, `:95`, `:143`, `:184`), which exercise SQLite create, SQLite migrate, the `"None"` failure message, and the unknown-strategy guard. +- **Walkthrough**: the public surface is two extension members in two `extension(T)` blocks, one on `IServiceProvider` (`DatabaseInitializationExtensions.cs:33`) and one on `WebApplication` (`:128`), declared with C# `extension(T)` syntax like the rest of the framework's DI and startup helpers. The class carries a justified `CA1708` suppression because multiple extension blocks in one static class trip that rule on compiler-generated grouping members (`:24-27`). + - `InitializeDatabaseAsync(applicationSettings, moduleLoader, cancellationToken)` (`:45-48`) starts by null-guarding all three inputs (`:50-52`). + - **Strategy is validated first** (`:57`). `EnsureKnownStrategy` (`:231`) accepts only the ordinal strings `"Migrate"` and `"None"` (`:233-234`) and otherwise throws `UnknownStrategy` (`:236`), whose message names the valid values (`:243-244`). The comment (`:54-56`) gives the reason for doing it up front: a misspelled strategy is a configuration mistake, and the host must stop before the code below has already created the migrationless sources. + - It then opens a scope (`:59`) and **warms the entity data-source registry**: resolving `IEntityDataSourceRegistry` and calling `GetPhysicalSourcesInUse()` (`:64-66`) scans the configuration assemblies once so entity-to-database routing is deterministic before the first repository call, replacing a lazy model-building side effect (`:61-63`). + - **The migrationless pass** (`:84-100`) walks every source in use with no engine filter (`:84`). A source with an empty connection string is skipped (`:88-91`), since integration tests may omit one; a source where `PhysicalDataSource.UsesMigrations` is true is also skipped (`:93-96`); everything left is created outright with `EnsureCreatedAsync` (`:98-99`). The comments carry the load here. The first (`:70-76`) says this is the **only** path that creates such a source (without it the first repository call fails) and that the per-tenant pass repeats it for a tenant's own copy under either strategy. The second (`:78-80`) explains the `UsesMigrations` exclusion: running `EnsureCreated` against a PostgreSQL or SQLite source that does have a migrations assembly writes the tables with no `__EFMigrationsHistory` row, after which every migration is both pending and un-appliable because its `CREATE TABLE` hits an existing table. The third (`:82-83`) is why no engine filter is needed: a SQL Server source always answers `UsesMigrations` and is skipped, so only a migration-less source reaches `EnsureCreated`. `UsesMigrations` itself is the per-engine rule on the source record, read from the engine's `MigrationPolicy` capability (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/PhysicalDataSource.cs:42-48`): `Always` (SQL Server), `WhenAssemblyConfigured` (PostgreSQL and SQLite, true only with a configured migrations assembly), `Never` (Cosmos), per the doc comment above it (`PhysicalDataSource.cs:36-39`). + - **The strategy switch** (`:105-115`) has exactly two live branches. `"Migrate"` delegates to `IDbContextFactory.MigrateAsync` (`:108`), which applies pending migrations for every migration-owned source in use (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/IDbContextFactory.cs:74-81`). `"None"` calls `ThrowIfPendingMigrationsAsync` (`:111`). The `default` arm rethrows `UnknownStrategy` (`:114`), unreachable in practice because of the guard at `:57` but kept so the switch is total. + - **The tenant pass** runs next (`:117-118`) and module seeding last (`:124`). Seeding deliberately runs on the default scope only; the comment (`:120-123`) states the rationale: a seeder writes reference data the application needs to boot, no module declares which of its seeders are tenant-scoped, and running one twice against a shared database is worse than not running it per tenant at all. + - `InitializeDatabaseUnlessDesignTimeAsync(moduleHost, cancellationToken)` (`:146`) is the `WebApplication` wrapper the service hosts call. It null-guards both the app and the [ModuleHostContext](#modulehostcontext) (`:150-151`), returns a completed task without touching any database when the environment is Development **and** the `MmcaOpenApiDesignTime` configuration switch (`OpenApiDesignTimeKey`, `:31`) is true (`:153-156`), and otherwise forwards to `InitializeDatabaseAsync` with `moduleHost.ApplicationSettings` and `moduleHost.ModuleLoader` (`:158-161`). The remarks (`:134-141`) give the reason: build-time OpenAPI generation (`MmcaGenerateOpenApiDocument`) starts the host with no database reachable, and the schema step is the one startup action that throws rather than logs when it cannot connect. Honouring the switch only in Development means a host in any other environment can never be talked out of initializing its schema ([ADR-122](https://ivanball.github.io/docs/adr/122-dev-only-relaxations-fail-closed.html)). + - `InitializeTenantDatabasesAsync` (`:175`) resolves `IOptions` with `GetService` rather than `GetRequiredService` (`:182`) and returns immediately when tenancy was never registered or no tenants are configured (`:183-186`). Otherwise it expands the sources into targets via `TenantDataSourceTargets.Expand` and keeps only the ones with a tenant id (`:188-189`), because `Expand` also emits the shared, tenant-less target for each source, which the pass above already handled. For each target it opens a **fresh scope through `IServiceScopeFactory.CreateTenantScope(target)`** (`:191`), the shared helper (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:92`) every other per-tenant background pass (the outbox processor, the internal-command processor, the audit-trail and outbox cleanup jobs) also calls; it creates the scope and, when the target carries a tenant id, calls `ITenantContext.SetTenant` on it before returning (`TenantDataSourceTargets.cs:99`), so the tenant is always set *before* anything asks that scope for a context. The remark (`:170-174`) is still the reason a fresh scope is required at all: the scoped context factory binds one physical database per source for the life of a scope, so reusing the outer scope would keep handing back the shared database. + - Per tenant it reads `UsesMigrations` off the *shared* resolved source (`:199`), on the grounds stated at `:196-198`: a tenant's copy is the same schema on a different connection, the migrations assembly is declared once on the source, and a per-tenant override only replaces the connection string. It then applies the same strategy (`:201-220`): under `"Migrate"` a migrated source gets `MigrateAsync` and anything else gets `EnsureCreatedAsync` (`:204-211`), under `"None"` it defers to `ApplyNoneStrategyToTenantAsync` (`:215`). + - `ApplyNoneStrategyToTenantAsync` (`:251`, private) is the tenant-side `"None"` branch. A migration-less tenant copy (Cosmos, or PostgreSQL/SQLite with no migrations assembly) is created with `EnsureCreatedAsync` (`:257-261`), mirroring the shared migrationless pass, because nothing else ever creates it (`:246-250`); every other copy goes to `ThrowIfTenantPendingMigrationsAsync` (`:263`). + - **The two production rails.** `ThrowIfTenantPendingMigrationsAsync` (`:271`) no-ops for a non-migrated source (`:277-280`), otherwise queries `GetPendingMigrationsAsync` (`:282`) and, when anything is pending, throws naming the tenant target, the migrations, and the `dotnet ef database update` remedy (`:288-291`). `ThrowIfPendingMigrationsAsync` (`:300`) short-circuits on `IDbContextFactory.HasPendingMigrationsAsync` (`:306-309`), then builds a per-source breakdown by re-querying each key whose resolved source answers `PhysicalDataSource.IsMigrationTarget` (`:312-320`) before throwing (`:322-324`). `IsMigrationTarget` now lives on the source record (`PhysicalDataSource.cs:57-60`): a source that `UsesMigrations` and either belongs to an engine whose sources require a connection string (SQL Server, so a missing one fails loudly at startup) or has one configured. Its doc comment (`PhysicalDataSource.cs:50-56`) states that the context factory and this initializer share that one rule, so the breakdown names exactly the sources the factory checked rather than an empty list (`:294-299`). +- **Why it's built this way**: one shared init path keeps every service host consistent, so adding a database engine or a tenant changes this file rather than five `Program.cs` files. PostgreSQL joining the migrationless pass alongside Cosmos and SQLite ([ADR-113](https://ivanball.github.io/docs/adr/113-postgresql-as-a-first-class-engine.html)) is exactly that: a host that names no `PostgreSQLMigrationsAssembly` gets the same `EnsureCreatedAsync` treatment a migrations-less SQLite source already got, with no change to the strategy switch below it. The `"None"` strategy is the deploy-time guarantee that an app never serves traffic against an un-migrated database when migrations are the pipeline's job, and validating the strategy string before any side effect keeps a typo from silently degrading into "schema untouched, first query fails". The tenant pass exists because nothing else ever opens a per-tenant database ([ADR-073](https://ivanball.github.io/docs/adr/073-multi-tenancy-model.html)), so without it such a database is never created and never migrated (`:165-169`). The `"None"` tenant branch creating a migration-less copy keeps that true under either strategy, matching what the shared pass already did. Skipping initialization for build-time OpenAPI generation through a Development-only switch, rather than a flag any environment can set, keeps the one exemption from becoming a way to boot production against an untouched schema. +- **Where it's used**: called once per host after `app.Build()`, before the middleware pipeline is wired. The ADC and Store service hosts call the wrapper, `await app.InitializeDatabaseUnlessDesignTimeAsync(moduleHost)` (`MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:441`, `MMCA.Store/Source/Services/MMCA.Store.Sales.Service/Program.cs:296`), which reads `ApplicationSettings` and `ModuleLoader` off [ModuleHostContext](#modulehostcontext) (`MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/ModuleHostContext.cs:44`, `:54`); Helpdesk calls `InitializeDatabaseAsync` directly with its own settings and loader (`MMCA.Helpdesk/Source/Hosts/MMCA.Helpdesk.Web/Program.cs:139`). The migrationless-engine loop and the strategy contract are covered by `DatabaseInitializationExtensionsTests` (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:43`, `:97`, `:145`, `:183`, `:233`), which exercise SQLite create, SQLite migrate, the `"None"` failure message, the `"None"` creation of a migration-less tenant copy, and the unknown-strategy guard. - **Caveats / not-in-source**: which strategy a given deployment runs is configuration, not code. The framework default is `"Migrate"` (`MMCA.Common/Source/Core/MMCA.Common.Application/Settings/ApplicationSettings.cs:43`); what any environment overrides it to lives in that app's settings and cannot be read here. The tenant pass is also only as complete as `TenancySettings`: `Expand` emits a tenant target only when the tenant declares an override connection string for that source, so a tenant that shares the default database is initialized by the shared pass and never appears in the per-tenant loop. ### SupportedCultures @@ -1834,7 +1843,7 @@ code stays modules and domain logic, never plumbing. - **Concept introduced (optimistic concurrency, header-only).** `[Rubric §8, Data Architecture]` assesses deliberate persistence: transactions, migrations, soft-delete, audit, and **concurrency control**. SQL Server's `rowversion` is a token the database changes on every update of a row. A read DTO carries the current `RowVersion` so the client can state a precondition on the next write, and the persistence layer can then refuse a conflicting concurrent edit instead of silently overwriting it. The doc comment (`IConcurrencyAware.cs:3-7`) spells out the full round trip: DTO to `ETag` to `If-Match` to `SetOriginalRowVersion`. `[Rubric §9, API & Contract Design]` applies too, because the precondition is expressed in HTTP's own vocabulary rather than in a body field. - **Walkthrough**: one property, `byte[] RowVersion { get; init; }` (`IConcurrencyAware.cs:19`), and it is **not nullable**. The remark (`IConcurrencyAware.cs:9-13`) is the reason: the token is the `If-Match` header's whole content, so it is never optional. A DTO read from a persisted aggregate always has one (`AuditableBaseEntity.RowVersion` is non-null), and a write that states no precondition is refused with `428 Precondition Required` rather than falling back to last-write-wins. The same remark states the other half of the current design: **update requests carry no token**, because the precondition travels in the header alone. Note the `[SuppressMessage("Performance", "CA1819")]` on `IConcurrencyAware.cs:18`: exposing a `byte[]` property normally trips the "properties should not return arrays" analyzer rule, but it is required to round-trip the EF token, and the suppression is *justified inline* (`[Rubric §15, Best Practices]`: suppressions are tracked and explained, not blanket-disabled). - **Why it's built this way**: [ADR-035](https://ivanball.github.io/docs/adr/035-optimistic-concurrency.html) puts the precondition on the resource read rather than on each request model, so a module adds conditional writes by making its read DTO concurrency-aware plus tagging the action, and no request record has to loosen its immutability to receive a token. Keeping the contract in `Shared` means the same interface is visible to the Blazor client that must echo the tag and to the Infrastructure repository that consumes the bytes. -- **Where it's used**: implemented by the read DTOs across the apps: ADC's `SessionDTO` (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Sessions/SessionDTO.cs:15`) and `LivePollDTO` (`MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Shared/LivePolls/LivePollDTO.cs:8`), Store's `OrderDTO` (`MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Shared/Orders/OrderDTO.cs:9`) and `InventoryItemDTO` (`MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Shared/Inventory/InventoryItemDTO.cs:9`), and Helpdesk's `TicketDTO` (`MMCA.Helpdesk/Source/Modules/Tickets/MMCA.Helpdesk.Tickets.Shared/Tickets/TicketDTO.cs:10`). On the server, [EntityControllerBase](#entitycontrollerbasetentity-tentitydto-tidentifiertype)`.SetConcurrencyETag` tests a returned DTO for this interface and writes the `ETag` response header (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:405-413`, called from `:436`). On the client, [EntityServiceBase](group-15-common-ui-framework.md#entityservicebasetentitydto-tidentifiertype)`.ConcurrencyTagOf` pattern-matches the same interface to build the outgoing `If-Match` value, treating an empty array as no token (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:197-201`). +- **Where it's used**: implemented by the read DTOs across the apps: ADC's `SessionDTO` (`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Sessions/SessionDTO.cs:15`) and `LivePollDTO` (`MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Shared/LivePolls/LivePollDTO.cs:8`), Store's `OrderDTO` (`MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Shared/Orders/OrderDTO.cs:9`) and `InventoryItemDTO` (`MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Shared/Inventory/InventoryItemDTO.cs:9`), and Helpdesk's `TicketDTO` (`MMCA.Helpdesk/Source/Modules/Tickets/MMCA.Helpdesk.Tickets.Shared/Tickets/TicketDTO.cs:10`). On the server, [EntityControllerBase](#entitycontrollerbasetentity-tentitydto-tidentifiertype)`.SetConcurrencyETag` tests a returned DTO for this interface and writes the `ETag` response header (`MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:417-425`, called from `:436`). On the client, [EntityServiceBase](group-15-common-ui-framework.md#entityservicebasetentitydto-tidentifiertype)`.ConcurrencyTagOf` pattern-matches the same interface to build the outgoing `If-Match` value, treating an empty array as no token (`MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:197-201`). - **Caveats / not-in-source**: the "update requests carry no token" half is a convention this interface cannot enforce by itself; it is pinned by the shipped fitness rule `UpdateRequests_ShouldNotImplement_IConcurrencyAware` (`MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Domain/ConcurrencyConventionTestsBase.cs:14`), which each consumer app subclasses. ### JwtForwardingDelegatingHandler @@ -1847,8 +1856,8 @@ code stays modules and domain logic, never plumbing. - `SendAsync` (`:22`) guards the request (`:24`), then returns straight to `base.SendAsync` in two cases. First, when `request.Headers.Authorization` is already set (`:27-30`), because a previous handler or the caller stated its own credentials and must win. Second, when there is no inbound header to copy (`:32-36`): `httpContextAccessor.HttpContext?.Request?.Headers.Authorization.ToString()` is null-conditional the whole way, so a background processor or an outbox dispatch with no ambient request simply passes through untouched. - When there is a value, it normalizes the scheme (`:40-43`): an inbound `"Bearer "` has the prefix sliced off case-insensitively, and anything else is treated as the parameter itself. The result is assigned as a structured `AuthenticationHeaderValue(BearerScheme, token)` (`:45`) rather than as a raw string, so the outgoing header is always well-formed `Bearer ` with the constant scheme from `:19`. - **Why it's built this way**: the no-op-without-`HttpContext` behavior (`:13-14`) is what makes the handler safe to attach unconditionally: background work uses its own credentials and is not accidentally given the last request's token. Slicing and re-forming the scheme instead of copying the string verbatim means a malformed or scheme-less inbound value still leaves the client with a valid header. -- **Where it's used**: wired by the framework's typed-service-client helper, `AddTypedServiceClient` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:141`), which calls `AddHttpContextAccessor()` and `TryAddTransient()` (`:147-148`) and then chains `.AddHttpMessageHandler()` onto the typed `HttpClient` (`:151-153`) alongside Aspire service discovery and the standard resilience handler (`:156`). -- **Caveats / not-in-source**: **no shipped app calls `AddTypedServiceClient` today.** The helper's own doc scopes it to HTTP contracts that do not warrant a gRPC binding (webhook receivers, public REST endpoints, third-party wrappers) and points at `MMCA.Common.Grpc.AddTypedGrpcClient` as the preferred service-to-service path (`DependencyInjection.Messaging.cs:131-134`), which is the path the cross-service calls in ADC and Store actually take. This handler is therefore live framework code on a currently unused registration path. +- **Where it's used**: wired by the framework's typed-service-client helper, `AddTypedServiceClient` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:152`), which calls `AddHttpContextAccessor()` and `TryAddTransient()` (`:147-148`) and then chains `.AddHttpMessageHandler()` onto the typed `HttpClient` (`:151-153`) alongside Aspire service discovery and the standard resilience handler (`:156`). +- **Caveats / not-in-source**: **no shipped app calls `AddTypedServiceClient` today.** The helper's own doc scopes it to HTTP contracts that do not warrant a gRPC binding (webhook receivers, public REST endpoints, third-party wrappers) and points at `MMCA.Common.Grpc.AddTypedGrpcClient` as the preferred service-to-service path (`DependencyInjection.Messaging.cs:136-139`), which is the path the cross-service calls in ADC and Store actually take. This handler is therefore live framework code on a currently unused registration path. ### AppAssociationOptions > MMCA.Common.API · `MMCA.Common.API.Startup.Endpoints` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/Endpoints/AppAssociationOptions.cs:9` · Level 0 · class (sealed) @@ -1862,7 +1871,7 @@ code stays modules and domain logic, never plumbing. - `AppleAppId` (`AppAssociationOptions.cs:21`, `required`): the `TeamID.BundleID` value used by both the `webcredentials` and the `applinks` sections. - `AppleAppLinkComponents` (`AppAssociationOptions.cs:28`, defaults to `[]`): the URL patterns (for example `"/conference/*"`) that each become a `{ "/": pattern }` component; the comment (`:23-27`) notes these should mirror the app's shared Blazor routes, because identical URLs on web and device is the Blazor Hybrid payoff (no route-translation table). - **Why it's built this way**: `required init` gives compile-checked construction plus immutability once bound (see the primer on [required/init immutability](00-primer.md#2-architectural-styles-this-codebase-commits-to)), which matches the lifetime: a host builds one instance at startup and the endpoint reads it for the process lifetime. Defaulting the two collections to `[]` means a host that ships only one platform still constructs a valid document for the other. See [ADR-043](https://ivanball.github.io/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html) for the deep-link decision this serves. -- **Where it's used**: constructed inline by the ADC Blazor web host and passed straight to the mapper, with the Android and Apple identifiers read from the `AppAssociation` configuration section (with in-code fallbacks) and the applinks patterns hard-coded to the app's routes (`MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:266-277`). The comment there (`:180-183`) records a trap worth reading: the Release Android head overrides `ApplicationId` to `ivanball.AtlDevCon`, so that is the package Digital Asset Links must name, not the Debug-only id. +- **Where it's used**: constructed inline by the ADC Blazor web host and passed straight to the mapper, with the Android and Apple identifiers read from the `AppAssociation` configuration section (with in-code fallbacks) and the applinks patterns hard-coded to the app's routes (`MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:279-290`). The comment there (`:180-183`) records a trap worth reading: the Release Android head overrides `ApplicationId` to `ivanball.AtlDevCon`, so that is the package Digital Asset Links must name, not the Debug-only id. - **Caveats / not-in-source**: the type performs no validation. Whether a fingerprint or bundle id is the *correct* one for the shipped app is only observable at install time on the device. ### MiddlewarePipelineStep @@ -1899,16 +1908,18 @@ code stays modules and domain logic, never plumbing. - **Where it's used**: [MiddlewarePipelineBuilder](#middlewarepipelinebuilder)`.CreateDefault()` names every seeded step with these constants; `Build()` names them again in its four invariant checks; and `MiddlewarePipelineOrderTestsBase.ExpectedStepNames` (`MMCA.Common/Source/Hosting/MMCA.Common.Testing/Conformance/MiddlewarePipelineOrderTestsBase.cs:38-58`) lists all eighteen as the frozen expected order, which each app's `MiddlewarePipelineOrderTests` subclasses. ### OpenApiEndpointExtensions -> MMCA.Common.API · `MMCA.Common.API.Startup.Endpoints` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/Endpoints/OpenApiEndpointExtensions.cs:22` · Level 0 · class (static, extension block) +> MMCA.Common.API · `MMCA.Common.API.Startup.Endpoints` · `MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/Endpoints/OpenApiEndpointExtensions.cs:25` · Level 0 · class (static, extension block) - **What it is**: two `extension(WebApplication app)` mapping helpers that expose the generated OpenAPI document and an optional interactive reference UI, both **outside Production only**. -- **Depends on**: `Scalar.AspNetCore` (NuGet) for the reference UI and `Asp.Versioning`'s `WithDocumentPerVersion()` convention. It pairs with `AddCommonOpenApi()` on [WebApplicationBuilderExtensions](#webapplicationbuilderextensions), which registers the generator. -- **Concept introduced (the OpenAPI document as a dev/CI artifact, not a public surface).** `[Rubric §9, API & Contract Design]` assesses whether an API has a machine-readable contract and whether that contract is guarded against silent drift. The doc comment (`OpenApiEndpointExtensions.cs:7-21`) is explicit that the guarding happens at **two levels**: the framework-owned part of the generated document (the versioned naming convention, the unbound-route-token backfill, the generated `ProblemDetails` error schema) is diffed against a committed baseline in-repo by `OpenApiBaselineTests` (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/OpenApi/OpenApiBaselineTests.cs`), which fails on any change until the baseline is regenerated deliberately in the same pull request, while each consumer's concrete API surface stays the concern of the contract-snapshot tests in that host's integration tier. Mapping outside Production is the security posture `[Rubric §11, Security]`: these are internal services reached through the Gateway, which does not route the endpoint. +- **Depends on**: `Microsoft.AspNetCore.OpenApi` for `MapOpenApi()` and the keyed `IOpenApiDocumentProvider` it probes, and `Scalar.AspNetCore` (NuGet) for the reference UI. It pairs with the host's own `services.AddOpenApi()`, which registers the `v1` document, plus `AddCommonOpenApi()` on [WebApplicationBuilderExtensions](#webapplicationbuilderextensions), which only configures the registered documents and registers none of its own (doc comment `OpenApiEndpointExtensions.cs:13-14`, `:37-38`). +- **Concept introduced (the OpenAPI document as a dev/CI artifact, not a public surface).** `[Rubric §9, API & Contract Design]` assesses whether an API has a machine-readable contract and whether that contract is guarded against silent drift. The doc comment (`OpenApiEndpointExtensions.cs:10-24`) is explicit that the guarding happens at **two levels**: the framework-owned part of the generated document (the unbound-route-token backfill, the generated `ProblemDetails` error schema) is diffed against a committed baseline in-repo by `OpenApiBaselineTests` (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/OpenApi/OpenApiBaselineTests.cs`), which fails on any change until the baseline is regenerated deliberately in the same pull request, while each consumer's concrete API surface stays the concern of that host's committed document and its contract-snapshot tests. Mapping outside Production is the security posture `[Rubric §11, Security]`: these are internal services reached through the Gateway, which does not route the endpoint. - **Walkthrough** - - `MapCommonOpenApi()` (`OpenApiEndpointExtensions.cs:34`) calls the built-in `MapOpenApi()` only when `!app.Environment.IsProduction()` (`:36-39`) and chains `.WithDocumentPerVersion()`, which applies the API-versioning convention so the route resolves one document per discovered API version (`/openapi/v1.json` for v1.0, doc comment `:26-33`). It is a no-op in Production and returns `app` for chaining (`:41`). - - `MapCommonScalarUi()` (`OpenApiEndpointExtensions.cs:52`) is the opt-in developer convenience: it calls `MapScalarApiReference()` outside Production (`:54-57`), rendering `/scalar/{documentName}`. Assets ship inside the `Scalar.AspNetCore` package rather than a CDN (`:49-50`), so it works offline and in CI. -- **Why it's built this way**: one shared pair of helpers keeps every service's OpenAPI story identical and enforces the "internal spec, not public surface" convention in one place instead of per host. The version-aware document mapping is what keeps the route stable as versions accumulate ([ADR-046](https://ivanball.github.io/docs/adr/046-http-api-versioning.html)). -- **Where it's used**: inside this workspace the only caller is the framework's own probe host, `OpenApiProbeHost` (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/OpenApi/OpenApiProbeHost.cs:40` registers, `:59` maps), which is what `OpenApiBaselineTests` and the [ApiParameterDescriptorBackfillProvider](#apiparameterdescriptorbackfillprovider) tests boot. The ADC and Store service hosts today call the stock ASP.NET pair directly instead. This is the framework offering a convention ahead of the consumers adopting it; `OpenApiContractTestsBase` (`MMCA.Common/Source/Hosting/MMCA.Common.Testing/Conformance/OpenApiContractTestsBase.cs:22`) is the base a consumer subclasses once it does. + - `DefaultDocumentName` (`OpenApiEndpointExtensions.cs:28`) is the private constant `"v1"`: the document name plain `AddOpenApi()` registers and this endpoint serves. + - `MapCommonOpenApi()` (`OpenApiEndpointExtensions.cs:60`) does nothing in Production and returns `app` for chaining (`:77`). Outside Production (`:62`) it first resolves `IServiceProviderIsKeyedService` and checks that a keyed `IOpenApiDocumentProvider` exists for `"v1"` (`:64-65`); if not, it throws `InvalidOperationException` telling the host to call `services.AddOpenApi()` in the host project itself, because that is the call the OpenAPI XML-comment generator intercepts to attach the host's summaries (`:67-71`). A host that calls only the framework method therefore fails at startup (and at build-time document generation) instead of silently serving no document (doc comment `:36-41`). It then maps the document with `app.MapOpenApi().AllowAnonymous()` (`:74`). The anonymous declaration is load-bearing: the framework's fallback authorization policy gates every endpoint that states nothing and `/openapi` is not one of its exempt prefixes, so an undeclared mapping would answer 401 to the contract-snapshot tests and API tooling (doc comment `:43-50`). + - The method carries a `[SuppressMessage("Usage", "AV0030:Missing WithDocumentPerVersion")]` (`:56-59`). The justification records why: the analyzer fires because `AddCommonApiVersioning`'s `AddApiExplorer` call lives in this assembly, but no versioned OpenAPI registration exists (`Asp.Versioning.OpenApi` is not referenced, so `WithDocumentPerVersion()` is not available). The host serves the single plain `v1` document. + - `MapCommonScalarUi()` (`OpenApiEndpointExtensions.cs:89`) is the opt-in developer convenience: it calls `MapScalarApiReference()` outside Production (`:91-94`), rendering `/scalar/{documentName}`, and requires `services.AddOpenApi()` + `AddCommonOpenApi()` + `MapCommonOpenApi()` (doc comment `:83-84`). Assets ship inside the `Scalar.AspNetCore` package rather than a CDN (`:86-87`), so it works offline and in CI. +- **Why it's built this way**: one shared pair of helpers keeps every service's OpenAPI story identical and enforces the "internal spec, not public surface" convention in one place instead of per host. The registration guard turns a missing host `AddOpenApi()` call (which would otherwise lose the XML-comment summaries or serve nothing) into a startup failure, and the explicit `AllowAnonymous()` keeps the contract reachable before a client holds a token without widening the fallback policy's exempt list. API versioning itself is [ADR-046](https://ivanball.github.io/docs/adr/046-http-api-versioning.html). +- **Where it's used**: every ADC and Store service host maps the document through it (for example `MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:451` and `MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:322`), and the ADC integration tiers subclass `OpenApiContractTestsBase` (`MMCA.Common/Source/Hosting/MMCA.Common.Testing/Conformance/OpenApiContractTestsBase.cs:22`), for example `MMCA.ADC/Tests/Integration/MMCA.ADC.Conference.IntegrationTests/Contract/OpenApiContractTests.cs:15`. In the framework, `OpenApiProbeHost` (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/OpenApi/OpenApiProbeHost.cs:40-41` registers, `:61` maps) is what `OpenApiBaselineTests` boots, and `MapCommonOpenApiAuthorizationTests` (`MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/OpenApi/MapCommonOpenApiAuthorizationTests.cs:23`) pins the anonymous declaration and the Production no-op. ### ICorrelationContext > MMCA.Common.Application · `MMCA.Common.Application.Interfaces` · `MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICorrelationContext.cs:8` · Level 0 · interface @@ -1918,7 +1929,7 @@ code stays modules and domain logic, never plumbing. - **Concept introduced (request correlation as an injected ambient value).** `[Rubric §13, Observability & Operability]` assesses whether one logical request can be reassembled from disjoint logs; a **correlation ID** is the value that makes that possible. `[Rubric §3, Clean Architecture]` is the reason this interface exists at all: handlers, decorators and interceptors need the ID, but they must not reach for `HttpContext`, so the contract sits in Application and the holder sits in Infrastructure, keeping the dependency arrow pointing inward. `[Rubric §13, Observability & Operability]`: nothing in the business path sets or threads the ID, it is populated once at the edge and read wherever it is needed. - **Walkthrough**: two members. `string CorrelationId { get; }` (`ICorrelationContext.cs:11`) is read-only to consumers. `void SetCorrelationId(string correlationId)` (`ICorrelationContext.cs:15`) is the single write path, on the same interface, so the edge component that populates the value can do it through the DI-resolved instance without a second, internal interface. The doc comment (`ICorrelationContext.cs:3-6`) states the intended lifecycle: set by middleware from the `X-Correlation-ID` header or generated, then carried through the handler pipeline in structured-logging scopes. - **Why it's built this way**: a two-member interface is small enough that a test or a background host can supply its own holder, and putting the setter here (rather than on the concrete class) keeps middleware depending on the abstraction. [ITenantContext](group-05-cqrs-pipeline.md#itenantcontext) is deliberately modelled on it, and says so: one scoped instance per request, populated once at the edge (`MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ITenantContext.cs:11`). -- **Where it's used**: registered as `TryAddScoped()` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:288`). Populated by [CorrelationIdMiddleware](#correlationidmiddleware), which takes it as a method-injected parameter of `InvokeAsync` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/CorrelationIdMiddleware.cs:27`). Consumed by [LoggingCommandDecorator](group-05-cqrs-pipeline.md#loggingcommanddecoratortcommand-tresult) (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/LoggingCommandDecorator.cs:19`) and [LoggingQueryDecorator](group-05-cqrs-pipeline.md#loggingquerydecoratortquery-tresult) (`.../LoggingQueryDecorator.cs:16`), which wrap the ID into their log scope for the duration of the pipeline. +- **Where it's used**: registered as `TryAddScoped()` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:298`). Populated by [CorrelationIdMiddleware](#correlationidmiddleware), which takes it as a method-injected parameter of `InvokeAsync` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/CorrelationIdMiddleware.cs:33`). Consumed by [LoggingCommandDecorator](group-05-cqrs-pipeline.md#loggingcommanddecoratortcommand-tresult) (`MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/LoggingCommandDecorator.cs:19`) and [LoggingQueryDecorator](group-05-cqrs-pipeline.md#loggingquerydecoratortquery-tresult) (`.../LoggingQueryDecorator.cs:16`), which wrap the ID into their log scope for the duration of the pipeline. - **Caveats / not-in-source**: not every component can use it. The audit-trail interceptor is a singleton and records the ambient `Activity` trace id instead of this scoped value, and says so in its own comment (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailEntry.cs:89`). ### CorrelationContext @@ -1928,7 +1939,7 @@ code stays modules and domain logic, never plumbing. - **Depends on**: [ICorrelationContext](#icorrelationcontext) (Level 0, `MMCA.Common.Application.Interfaces`), the abstraction it implements (`CorrelationContext.cs:1`, `:9`). Uses BCL `Guid` only. - **Concept (where the correlation value actually lives).** The pattern itself is introduced at [ICorrelationContext](#icorrelationcontext); this is its one shipped implementation. `[Rubric §13, Observability & Operability]`: the eager default is what makes correlation always-on, so a log line emitted from a path with no HTTP request still carries an ID. `[Rubric §3, Clean Architecture]`: the concrete holder sits in Infrastructure while every consumer depends on the Application interface. - **Walkthrough**: `CorrelationId` (`CorrelationContext.cs:12`) is `{ get; private set; }`, initialized eagerly to `Guid.NewGuid().ToString("N")` so a value always exists even if no middleware runs (a background processor, a test path, a gRPC call). `SetCorrelationId(string)` (`CorrelationContext.cs:15-19`) overwrites it, guarding the input with `ArgumentException.ThrowIfNullOrWhiteSpace` (`:17`) so a blank header can never wipe the ID. The private setter means the only write path is that one guarded method. -- **Why it's built this way**: a scoped holder with an eager default keeps correlation cheap and unconditional: every code path has an ID without a null check, and inbound requests still adopt the caller's ID for cross-service tracing. The `"N"` GUID format (32 hex digits, no hyphens) keeps the value compact in log lines and headers. Registration is `TryAddScoped` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:288`): scoped, so one instance lives per request, and `TryAdd`, so a host that wants its own implementation can register it first and win. +- **Why it's built this way**: a scoped holder with an eager default keeps correlation cheap and unconditional: every code path has an ID without a null check, and inbound requests still adopt the caller's ID for cross-service tracing. The `"N"` GUID format (32 hex digits, no hyphens) keeps the value compact in log lines and headers. Registration is `TryAddScoped` (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:298`): scoped, so one instance lives per request, and `TryAdd`, so a host that wants its own implementation can register it first and win. - **Where it's used**: [CorrelationIdMiddleware](#correlationidmiddleware) resolves it per request and calls `SetCorrelationId` with the inbound `X-Correlation-ID` header, falling back to the current `Activity` trace ID and then to `HttpContext.TraceIdentifier` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/CorrelationIdMiddleware.cs:32-36`), then echoes the value back on the response through `OnStarting` (`:37-41`). Downstream, [LoggingCommandDecorator](group-05-cqrs-pipeline.md#loggingcommanddecoratortcommand-tresult) and [LoggingQueryDecorator](group-05-cqrs-pipeline.md#loggingquerydecoratortquery-tresult) take it as a constructor dependency and wrap the ID into their log scope for the full pipeline duration. ### BaseLookup @@ -1937,9 +1948,9 @@ code stays modules and domain logic, never plumbing. - **What it is**: a minimal DTO for dropdown and autocomplete lookups: just `Id` and `Name`. - **Depends on**: [IBaseDTO](#ibasedtotidentifiertype) (Level 0), which it implements (`BaseLookup.cs:8`). - **Concept (right-sized response shapes).** `[Rubric §9, API & Contract Design]` assesses whether responses are shaped to their consumer rather than dumping full entities. Instead of returning a full entity DTO to populate a ` - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -260,7 +260,7 @@

Level distribution

0960998
1614631
2391404
3365376
4445446
5380378
6200208
7159156
8244210
9287277
10268263
11104123
128870
13157198
1484146
15172200
162235
172337
187077
19

-

Cycles (SCC size > 1): 46

+

Cycles (SCC size > 1): 47

@@ -287,11 +287,6 @@

Cycles (SCC size > 1): 46

- - - - - @@ -441,48 +436,48 @@

Cycles (SCC size > 1): 46

- - + + - + - + - + - + - + - + - + - + - + - + - + - - - + + + @@ -492,12 +487,22 @@

Cycles (SCC size > 1): 46

- + - + + + + + + - + + + + + +
2 2API:SessionCookieEndpoints, API:SessionCookieJar
22 AI:ContentPolicyGuardrail, AI:ContentPolicySettings
118Infrastructure:AuditTrailSaveChangesInterceptor, Infrastructure:ApplicationDbContext, Infrastructure:DataSourceModelCacheKeyFactory, Infrastructure:AuditSaveChangesInterceptor, Infrastructure:DomainEventSaveChangesInterceptor, Infrastructure:DeferredDispatch, Infrastructure:TenantSaveChangesInterceptor, Infrastructure:OutboxFinalizer23Infrastructure:SoftDeleteFilterSql, Infrastructure:AuditTrailSaveChangesInterceptor, Infrastructure:CrossDataSourceDegradeConvention, Infrastructure:RestrictDeleteByDefaultConvention, Infrastructure:SoftDeleteUniqueIndexConvention, Infrastructure:PhysicalDataSource, Infrastructure:ApplicationDbContext, Infrastructure:CosmosDbContext, Infrastructure:DataSourceModelCacheKeyFactory, Infrastructure:PostgreSQLDbContext, Infrastructure:SqliteDbContext, Infrastructure:SQLServerDbContext, Infrastructure:AuditSaveChangesInterceptor, Infrastructure:DomainEventSaveChangesInterceptor, Infrastructure:DeferredDispatch, Infrastructure:TenantSaveChangesInterceptor, Infrastructure:CosmosDataSourceEngine, Infrastructure:DataSourceEngines, Infrastructure:IDataSourceEngine, Infrastructure:PostgreSQLDataSourceEngine, Infrastructure:SqliteDataSourceEngine, Infrastructure:SQLServerDataSourceEngine, Infrastructure:OutboxFinalizer
1211 2Tests:GateTestContext, Tests:GateTestContextAPI:SessionCookieEndpoints, API:SessionCookieJar
12 2Tests:AuditTrailTestContext, Tests:FailingSaveInterceptorTests:GateTestContext, Tests:GateTestContext
1213 2Tests:MidSaveContextCreatingDbContext, Tests:ReentrantSaveInterceptorTests:EventScopeFitnessTests, Tests:FakeConsumerMap
1213 2Tests:CommitFailingDbContext, Tests:FailingDatabaseFacadeTests:EventUpcasterFitnessTests, Tests:UpcasterTestMap
1213 2Tests:FailingSaveInterceptor, Tests:OutboxRoutingTestDbContextTests:AuditTrailTestContext, Tests:FailingSaveInterceptor
13 2Tests:EventScopeFitnessTests, Tests:FakeConsumerMapTests:MidSaveContextCreatingDbContext, Tests:ReentrantSaveInterceptor
13 2Tests:EventUpcasterFitnessTests, Tests:UpcasterTestMapTests:CommitFailingDbContext, Tests:FailingDatabaseFacade
143Tests:CosmosConfigurationPortabilityTests, Tests:FixedAssemblyProvider, Tests:MultiSourceSqliteIntegrationTests132Tests:FailingSaveInterceptor, Tests:OutboxRoutingTestDbContext
14
14 2Tests:DatabaseInitializationExtensionsTests, Tests:FixedAssemblyProviderTests:PostgreSQLDbContextModelTests, Tests:FixedAssemblyProvider
14153Tests:CosmosConfigurationPortabilityTests, Tests:FixedAssemblyProvider, Tests:MultiSourceSqliteIntegrationTests
15 2Tests:PostgreSQLDbContextModelTests, Tests:FixedAssemblyProviderTests:SQLServerPersistenceTests, Tests:FixedAssemblyProvider
152Tests:DatabaseInitializationExtensionsTests, Tests:FixedAssemblyProvider

Manifest (by level, then assembly)

@@ -1416,6 +1421,13 @@

Manifest (by level, then assembly)

0 + FixedTimeProvider + MMCA.ADC.Conference.UI.Tests + 0 + (none) + + + 0 EventFeedbackPage MMCA.ADC.E2E.Tests 0 @@ -2354,6 +2366,13 @@

Manifest (by level, then assembly)

0 + JwtAudience + MMCA.ADC.Identity.Shared + 0 + (none) + + + 0 RoleNames MMCA.ADC.Identity.Shared 0 @@ -2452,6 +2471,13 @@

Manifest (by level, then assembly)

0 + GrpcCalls + MMCA.ADC.Services.Tests + 0 + (none) + + + 0 NowNextSession MMCA.ADC.UI 0 @@ -2690,6 +2716,13 @@

Manifest (by level, then assembly)

0 + ISessionCookieStore + MMCA.Common.API + 0 + (none) + + + 0 JwtAuthorityExtensions MMCA.Common.API 0 @@ -2795,6 +2828,13 @@

Manifest (by level, then assembly)

0 + SessionClaimsToken + MMCA.Common.API + 0 + (none) + + + 0 SessionCookieRequest MMCA.Common.API 0 @@ -2802,6 +2842,20 @@

Manifest (by level, then assembly)

0 + SessionCookieSettings + MMCA.Common.API + 0 + (none) + + + 0 + SessionRefreshStatus + MMCA.Common.API + 0 + (none) + + + 0 SessionTokenResponse MMCA.Common.API 0 @@ -2837,6 +2891,13 @@

Manifest (by level, then assembly)

0 + ExportRow + MMCA.Common.API.Tests + 0 + (none) + + + 0 FakeCategoriesController MMCA.Common.API.Tests 0 @@ -2851,6 +2912,13 @@

Manifest (by level, then assembly)

0 + MapCommonOpenApiAuthorizationTests + MMCA.Common.API.Tests + 0 + (none) + + + 0 NextDelegateSpy MMCA.Common.API.Tests 0 @@ -2935,6 +3003,13 @@

Manifest (by level, then assembly)

0 + Wrapped + MMCA.Common.API.Tests + 0 + (none) + + + 0 AmbientScope MMCA.Common.Application 0 @@ -3544,13 +3619,6 @@

Manifest (by level, then assembly)

0 - StrongPasswordRules<T> - MMCA.Common.Application - 0 - (none) - - - 0 TwoFactorOutcome MMCA.Common.Application 0 @@ -3579,13 +3647,6 @@

Manifest (by level, then assembly)

0 - UserUseCaseLog - MMCA.Common.Application - 0 - (none) - - - 0 AddOrderLineCommand MMCA.Common.Application.Tests 0 @@ -3838,6 +3899,13 @@

Manifest (by level, then assembly)

0 + ScanFailingAssembly + MMCA.Common.Application.Tests + 0 + (none) + + + 0 ScopedProbe MMCA.Common.Application.Tests 0 @@ -4048,6 +4116,13 @@

Manifest (by level, then assembly)

0 + EngineHit + MMCA.Common.Architecture.Tests + 0 + (none) + + + 0 ExemptableFixtureService MMCA.Common.Architecture.Tests 0 @@ -4181,6 +4256,13 @@

Manifest (by level, then assembly)

0 + PasswordProbe + MMCA.Common.Architecture.Tests + 0 + (none) + + + 0 PurgeTicketsCommand MMCA.Common.Architecture.Tests 0 @@ -4223,6 +4305,13 @@

Manifest (by level, then assembly)

0 + ResourceEntry + MMCA.Common.Architecture.Tests + 0 + (none) + + + 0 RethrowingFixture MMCA.Common.Architecture.Tests 0 @@ -4230,6 +4319,13 @@

Manifest (by level, then assembly)

0 + SwitchExpressionFixture + MMCA.Common.Architecture.Tests + 0 + (none) + + + 0 ThinFixtureController MMCA.Common.Architecture.Tests 0 @@ -4244,6 +4340,13 @@

Manifest (by level, then assembly)

0 + TodayReadingFixture + MMCA.Common.Architecture.Tests + 0 + (none) + + + 0 TwoMemberClockFixture MMCA.Common.Architecture.Tests 0 @@ -4482,6 +4585,13 @@

Manifest (by level, then assembly)

0 + LiveMetricsConfigurationTests + MMCA.Common.Aspire.Tests + 0 + (none) + + + 0 ProbeAttempt MMCA.Common.Aspire.Tests 0 @@ -4496,6 +4606,13 @@

Manifest (by level, then assembly)

0 + ServiceDefaultsRetryTests + MMCA.Common.Aspire.Tests + 0 + (none) + + + 0 SourceCollectingConfigurationManager MMCA.Common.Aspire.Tests 0 @@ -4741,6 +4858,13 @@

Manifest (by level, then assembly)

0 + PiiBase + MMCA.Common.Domain.Tests + 0 + (none) + + + 0 Subject MMCA.Common.Domain.Tests 0 @@ -4811,6 +4935,13 @@

Manifest (by level, then assembly)

0 + GrpcWireFormat + MMCA.Common.Grpc + 0 + (none) + + + 0 JwtForwardingClientInterceptor MMCA.Common.Grpc 0 @@ -4818,6 +4949,13 @@

Manifest (by level, then assembly)

0 + AuthenticateResultFeatureStub + MMCA.Common.Grpc.Tests + 0 + (none) + + + 0 CountingFailureHandler MMCA.Common.Grpc.Tests 0 @@ -4986,35 +5124,35 @@

Manifest (by level, then assembly)

0 - FileStorageSettings + ExplicitKeyInsertGroup MMCA.Common.Infrastructure 0 (none) 0 - GroupedCount<TKey> + FileStorageSettings MMCA.Common.Infrastructure 0 (none) 0 - GroupedSum<TKey> + GroupedCount<TKey> MMCA.Common.Infrastructure 0 (none) 0 - IDbSeeder + GroupedSum<TKey> MMCA.Common.Infrastructure 0 (none) 0 - IdentityInsertGroup + IDbSeeder MMCA.Common.Infrastructure 0 (none) @@ -5140,6 +5278,13 @@

Manifest (by level, then assembly)

0 + MigrationPolicy + MMCA.Common.Infrastructure + 0 + (none) + + + 0 ModelBuilderExtensions MMCA.Common.Infrastructure 0 @@ -5231,6 +5376,13 @@

Manifest (by level, then assembly)

0 + RowVersionStrategy + MMCA.Common.Infrastructure + 0 + (none) + + + 0 ScheduledJobEntry MMCA.Common.Infrastructure 0 @@ -5392,6 +5544,13 @@

Manifest (by level, then assembly)

0 + CountingAllocator + MMCA.Common.Infrastructure.Tests + 0 + (none) + + + 0 DrillResult MMCA.Common.Infrastructure.Tests 0 @@ -5427,6 +5586,27 @@

Manifest (by level, then assembly)

0 + IFakeContract + MMCA.Common.Infrastructure.Tests + 0 + (none) + + + 0 + ImageFrameBoundCollection + MMCA.Common.Infrastructure.Tests + 0 + (none) + + + 0 + ManualClock + MMCA.Common.Infrastructure.Tests + 0 + (none) + + + 0 ManualClock MMCA.Common.Infrastructure.Tests 0 @@ -5455,6 +5635,13 @@

Manifest (by level, then assembly)

0 + PiiBaseThing + MMCA.Common.Infrastructure.Tests + 0 + (none) + + + 0 PlainThing MMCA.Common.Infrastructure.Tests 0 @@ -5553,6 +5740,20 @@

Manifest (by level, then assembly)

0 + LoadResults + MMCA.Common.LoadTests + 0 + (none) + + + 0 + PagedQuery + MMCA.Common.LoadTests + 0 + (none) + + + 0 AdministrationPermissions MMCA.Common.Shared 0 @@ -5791,6 +5992,13 @@

Manifest (by level, then assembly)

0 + PasswordComplexity + MMCA.Common.Shared + 0 + (none) + + + 0 PermissionCatalogResponse MMCA.Common.Shared 0 @@ -6407,13 +6615,6 @@

Manifest (by level, then assembly)

0 - IsAuthenticatedAuthorizationService - MMCA.Common.Testing.UI - 0 - (none) - - - 0 MarkupSnapshotResult MMCA.Common.Testing.UI 0 @@ -6827,13 +7028,6 @@

Manifest (by level, then assembly)

0 - MmcaClientConfigBootstrap - MMCA.Common.UI - 0 - (none) - - - 0 MudTranslations MMCA.Common.UI 0 @@ -6862,13 +7056,6 @@

Manifest (by level, then assembly)

0 - PasswordComplexityAttribute - MMCA.Common.UI - 0 - (none) - - - 0 PendingAttempt MMCA.Common.UI 0 @@ -6925,42 +7112,35 @@

Manifest (by level, then assembly)

0 - RegisterModel - MMCA.Common.UI - 0 - (none) - - - 0 - ResetPasswordModel + ReturnUrlProtector MMCA.Common.UI 0 (none) 0 - ReturnUrlProtector + RoleAdminEditResources MMCA.Common.UI 0 (none) 0 - RoleAdminEditResources + RoleAdminListResources MMCA.Common.UI 0 (none) 0 - RoleAdminListResources + RoutePaths MMCA.Common.UI 0 (none) 0 - RoutePaths + SameOriginProxyHeaders MMCA.Common.UI 0 (none) @@ -7044,6 +7224,13 @@

Manifest (by level, then assembly)

0 + InpProbe + MMCA.Common.UI.E2E.Tests + 0 + (none) + + + 0 GalleryFakeAuthenticationHandler MMCA.Common.UI.Gallery 0 @@ -7086,6 +7273,13 @@

Manifest (by level, then assembly)

0 + CapturingHandler + MMCA.Common.UI.Tests + 0 + (none) + + + 0 ChildModel MMCA.Common.UI.Tests 0 @@ -7114,6 +7308,13 @@

Manifest (by level, then assembly)

0 + GatedGetHandler + MMCA.Common.UI.Tests + 0 + (none) + + + 0 KeyedModel MMCA.Common.UI.Tests 0 @@ -7128,6 +7329,13 @@

Manifest (by level, then assembly)

0 + PipePair + MMCA.Common.UI.Tests + 0 + (none) + + + 0 RecordingNavigationManager MMCA.Common.UI.Tests 0 @@ -7149,6 +7357,13 @@

Manifest (by level, then assembly)

0 + StubHandler + MMCA.Common.UI.Tests + 0 + (none) + + + 0 StubHttpClientFactory MMCA.Common.UI.Tests 0 @@ -7212,6 +7427,48 @@

Manifest (by level, then assembly)

0 + HandoffBody + MMCA.Common.UI.Web + 0 + (none) + + + 0 + ProxyResponseMode + MMCA.Common.UI.Web + 0 + (none) + + + 0 + SameOriginApiProxyMarker + MMCA.Common.UI.Web + 0 + (none) + + + 0 + SameOriginApiProxySettings + MMCA.Common.UI.Web + 0 + (none) + + + 0 + SameOriginProxyInvoker + MMCA.Common.UI.Web + 0 + (none) + + + 0 + TokenPair + MMCA.Common.UI.Web + 0 + (none) + + + 0 TrustedCallerHandler MMCA.Common.UI.Web 0 @@ -7226,6 +7483,20 @@

Manifest (by level, then assembly)

0 + Jwt + MMCA.Common.UI.Web.Tests + 0 + (none) + + + 0 + SeenRequest + MMCA.Common.UI.Web.Tests + 0 + (none) + + + 0 SentRequest MMCA.Common.UI.Web.Tests 0 @@ -7709,6 +7980,13 @@

Manifest (by level, then assembly)

1 + RecordingCacheStore + MMCA.ADC.Conference.UI.Tests + 1 + ILocalCacheStore + + + 1 VenueMapLinksTests MMCA.ADC.Conference.UI.Tests 1 @@ -8003,13 +8281,6 @@

Manifest (by level, then assembly)

1 - ChangePasswordRequestValidator - MMCA.ADC.Identity.Application - 2 - ChangePasswordRequest, StrongPasswordRules<T> - - - 1 ConfirmEmailCommand MMCA.ADC.Identity.Application 2 @@ -8080,6 +8351,13 @@

Manifest (by level, then assembly)

1 + JwtAudienceTests + MMCA.ADC.Identity.Shared.Tests + 1 + JwtAudience + + + 1 AttendeeNotificationRecipientProvider MMCA.ADC.Notification.Application 2 @@ -8290,13 +8568,6 @@

Manifest (by level, then assembly)

1 - ICookieSessionRefresher - MMCA.Common.API - 1 - SessionTokenResult - - - 1 JwksEndpointExtensions MMCA.Common.API 1 @@ -8395,6 +8666,13 @@

Manifest (by level, then assembly)

1 + HostRegistrationProbeController + MMCA.Common.API.Tests + 1 + Route + + + 1 IdempotencySettingsTests MMCA.Common.API.Tests 1 @@ -8416,6 +8694,13 @@

Manifest (by level, then assembly)

1 + OperationCanceledExceptionHandlerTests + MMCA.Common.API.Tests + 1 + OperationCanceledExceptionHandler + + + 1 PlainDTO MMCA.Common.API.Tests 1 @@ -8451,6 +8736,13 @@

Manifest (by level, then assembly)

1 + SessionClaimsTokenTests + MMCA.Common.API.Tests + 1 + SessionClaimsToken + + + 1 StubErrorLocalizer MMCA.Common.API.Tests 1 @@ -8528,6 +8820,13 @@

Manifest (by level, then assembly)

1 + WrappedAsStringConverter + MMCA.Common.API.Tests + 1 + Wrapped + + + 1 BestEffort MMCA.Common.Application 2 @@ -8752,13 +9051,6 @@

Manifest (by level, then assembly)

1 - ResetPasswordRequestValidator - MMCA.Common.Application - 2 - ResetPasswordRequest, StrongPasswordRules<T> - - - 1 SendEmailConfirmationRequestValidator MMCA.Common.Application 1 @@ -8787,6 +9079,13 @@

Manifest (by level, then assembly)

1 + StrongPasswordRules<T> + MMCA.Common.Application + 1 + PasswordComplexity + + + 1 TenantCacheKey MMCA.Common.Application 1 @@ -9270,6 +9569,13 @@

Manifest (by level, then assembly)

1 + EditorRequiredParameterConventionTests + MMCA.Common.Architecture.Tests + 1 + UISharedAssemblyReference + + + 1 FixtureBadFeatures MMCA.Common.Architecture.Tests 1 @@ -9585,6 +9891,13 @@

Manifest (by level, then assembly)

1 + PiiOverride + MMCA.Common.Domain.Tests + 1 + PiiBase + + + 1 GatewayHealthCheckDefaults MMCA.Common.Gateway 2 @@ -9620,6 +9933,13 @@

Manifest (by level, then assembly)

1 + GrpcWireFormatTests + MMCA.Common.Grpc.Tests + 1 + GrpcWireFormat + + + 1 ResilienceCircuitBreakerFaultInjectionTests MMCA.Common.Grpc.Tests 1 @@ -9662,6 +9982,13 @@

Manifest (by level, then assembly)

1 + DataSourceEngineCapabilities + MMCA.Common.Infrastructure + 2 + MigrationPolicy, RowVersionStrategy + + + 1 DataSourcesSettings MMCA.Common.Infrastructure 1 @@ -9718,6 +10045,13 @@

Manifest (by level, then assembly)

1 + IExplicitKeyInsertDialect + MMCA.Common.Infrastructure + 1 + ExplicitKeyInsertGroup + + + 1 InProcessDistributedLock MMCA.Common.Infrastructure 2 @@ -9837,13 +10171,6 @@

Manifest (by level, then assembly)

1 - RestrictDeleteByDefaultConvention - MMCA.Common.Infrastructure - 1 - DataSource - - - 1 RsaJwksProvider MMCA.Common.Infrastructure 2 @@ -9872,13 +10199,6 @@

Manifest (by level, then assembly)

1 - SoftDeleteFilterSql - MMCA.Common.Infrastructure - 2 - DataSource, IAuditableEntity - - - 1 SqlServerUniqueConstraintViolationDetector MMCA.Common.Infrastructure 1 @@ -9914,6 +10234,13 @@

Manifest (by level, then assembly)

1 + NoTenantContext + MMCA.Common.Infrastructure.SQLServer.Tests + 1 + ITenantContext + + + 1 AuditedThing MMCA.Common.Infrastructure.Tests 1 @@ -10026,6 +10353,13 @@

Manifest (by level, then assembly)

1 + FakeContract + MMCA.Common.Infrastructure.Tests + 1 + IFakeContract + + + 1 FakeGrantCache MMCA.Common.Infrastructure.Tests 1 @@ -10236,6 +10570,13 @@

Manifest (by level, then assembly)

1 + OverridingPiiThing + MMCA.Common.Infrastructure.Tests + 2 + IAuditedEntity, PiiBaseThing + + + 1 PersistenceSettingsTests MMCA.Common.Infrastructure.Tests 1 @@ -10320,6 +10661,13 @@

Manifest (by level, then assembly)

1 + LoadItemDTO + MMCA.Common.LoadTests + 1 + IBaseDTO<TIdentifierType> + + + 1 BaseLookup<TIdentifierType> MMCA.Common.Shared 1 @@ -10474,6 +10822,13 @@

Manifest (by level, then assembly)

1 + PasswordComplexityTests + MMCA.Common.Shared.Tests + 1 + PasswordComplexity + + + 1 SupportedCulturesTests MMCA.Common.Shared.Tests 1 @@ -10530,13 +10885,6 @@

Manifest (by level, then assembly)

1 - SqlServerIntegrationTestFixtureBase<TEntryPoint> - MMCA.Common.Testing - 1 - IIntegrationTestFixture - - - 1 LayerRef MMCA.Common.Testing.Architecture 1 @@ -10789,13 +11137,6 @@

Manifest (by level, then assembly)

1 - DirectApiTokenRefresher - MMCA.Common.UI - 4 - AuthenticationResponse, ISecureTokenStore, ITokenRefresher, RefreshTokenRequest - - - 1 EndpointCultureApplier MMCA.Common.UI 1 @@ -11069,6 +11410,13 @@

Manifest (by level, then assembly)

1 + PasswordComplexityAttribute + MMCA.Common.UI + 1 + PasswordComplexity + + + 1 PseudoStringLocalizer MMCA.Common.UI 2 @@ -11076,6 +11424,20 @@

Manifest (by level, then assembly)

1 + RegisterModel + MMCA.Common.UI + 1 + PasswordComplexity + + + 1 + ResetPasswordModel + MMCA.Common.UI + 1 + PasswordComplexity + + + 1 ResxMudLocalizer MMCA.Common.UI 1 @@ -11083,6 +11445,13 @@

Manifest (by level, then assembly)

1 + SameOriginProxyRequestHandler + MMCA.Common.UI + 1 + SameOriginProxyHeaders + + + 1 SameOriginProxyTokenRefresher MMCA.Common.UI 1 @@ -11370,24 +11739,24 @@

Manifest (by level, then assembly)

1 - LatestLoadGuardTests + InMemoryHubServer MMCA.Common.UI.Tests 1 - LatestLoadGuard + PipePair 1 - LazyJsModuleTests + LatestLoadGuardTests MMCA.Common.UI.Tests 1 - LazyJsModule + LatestLoadGuard 1 - MmcaClientConfigBootstrapTests + LazyJsModuleTests MMCA.Common.UI.Tests - 2 - MmcaClientConfigBootstrap, ScriptedHandler + 1 + LazyJsModule 1 @@ -11503,6 +11872,20 @@

Manifest (by level, then assembly)

1 + SameOriginApiProxySettingsValidator + MMCA.Common.UI.Web + 1 + SameOriginApiProxySettings + + + 1 + SessionHandoffProtector + MMCA.Common.UI.Web + 1 + TokenPair + + + 1 UiRateLimitingExtensions MMCA.Common.UI.Web 1 @@ -11524,6 +11907,13 @@

Manifest (by level, then assembly)

1 + FakeGateway + MMCA.Common.UI.Web.Tests + 3 + AuthenticationResponse, Jwt, SeenRequest + + + 1 Mocks MMCA.Common.UI.Web.Tests 2 @@ -11734,6 +12124,13 @@

Manifest (by level, then assembly)

2 + PublicReadAudience + MMCA.ADC.Conference.UI + 1 + ConferenceReadAudience + + + 2 QuestionFormModel MMCA.ADC.Conference.UI 1 @@ -12168,6 +12565,13 @@

Manifest (by level, then assembly)

2 + ChangePasswordRequestValidator + MMCA.ADC.Identity.Application + 2 + ChangePasswordRequest, StrongPasswordRules<T> + + + 2 ExportUserDataQuery MMCA.ADC.Identity.Application 1 @@ -12182,13 +12586,6 @@

Manifest (by level, then assembly)

2 - ChangePasswordRequestValidatorTests - MMCA.ADC.Identity.Application.Tests - 2 - ChangePasswordRequest, ChangePasswordRequestValidator - - - 2 LoginRequestValidatorTests MMCA.ADC.Identity.Application.Tests 2 @@ -12266,6 +12663,20 @@

Manifest (by level, then assembly)

2 + LiveChannelGrpcServiceTests + MMCA.ADC.Services.Tests + 5 + FakeServerCallContext, ILiveChannelPublisher, LiveChannelGrpcService, LivePollChannel, SessionQuestionChannel + + + 2 + LiveChannelPublisherGrpcAdapterTests + MMCA.ADC.Services.Tests + 2 + GrpcCalls, LiveChannelPublisherGrpcAdapter + + + 2 AiProviderValidator MMCA.Common.AI 2 @@ -12301,13 +12712,6 @@

Manifest (by level, then assembly)

2 - PiiRedactionGuardrail - MMCA.Common.AI - 3 - GuardrailVerdict, IChatGuardrail, IChatRequestRedactor - - - 2 AnthropicAiProviderFactory MMCA.Common.AI.Anthropic 2 @@ -12406,13 +12810,6 @@

Manifest (by level, then assembly)

2 - CookieSessionRefreshMiddleware - MMCA.Common.API - 1 - ICookieSessionRefresher - - - 2 IEntityControllerBase<TEntityDTO, TIdentifierType> MMCA.Common.API 5 @@ -12441,20 +12838,6 @@

Manifest (by level, then assembly)

2 - SessionCookieEndpoints - MMCA.Common.API - 4 - ICookieSessionRefresher, SessionCookieJar, SessionCookieRequest, SessionTokenResponse - - - 2 - SessionCookieJar - MMCA.Common.API - 1 - SessionCookieEndpoints - - - 2 ApiParameterDescriptorBackfillProviderTests MMCA.Common.API.Tests 4 @@ -12490,6 +12873,13 @@

Manifest (by level, then assembly)

2 + HostRegistrationProbeFeatureProvider + MMCA.Common.API.Tests + 1 + HostRegistrationProbeController + + + 2 JwksEndpointTests MMCA.Common.API.Tests 4 @@ -12539,13 +12929,6 @@

Manifest (by level, then assembly)

2 - StubRefresher - MMCA.Common.API.Tests - 2 - ICookieSessionRefresher, SessionTokenResult - - - 2 TestChangePasswordCommand MMCA.Common.API.Tests 2 @@ -12658,6 +13041,13 @@

Manifest (by level, then assembly)

2 + ResetPasswordRequestValidator + MMCA.Common.Application + 2 + ResetPasswordRequest, StrongPasswordRules<T> + + + 2 SafeDomainEventHandler<TDomainEvent> MMCA.Common.Application 2 @@ -12812,13 +13202,6 @@

Manifest (by level, then assembly)

2 - ResetPasswordRequestValidatorTests - MMCA.Common.Application.Tests - 2 - ResetPasswordRequest, ResetPasswordRequestValidator - - - 2 StubTwoFactorService MMCA.Common.Application.Tests 2 @@ -12938,6 +13321,13 @@

Manifest (by level, then assembly)

2 + PasswordRuleParityTests + MMCA.Common.Architecture.Tests + 3 + PasswordComplexityAttribute, PasswordProbe, StrongPasswordRules<T> + + + 2 SharedCodeErrors MMCA.Common.Architecture.Tests 1 @@ -13115,8 +13505,8 @@

Manifest (by level, then assembly)

2 PiiRedactorTests MMCA.Common.Domain.Tests - 3 - NoPii, PiiRedactor, Subject + 4 + NoPii, PiiOverride, PiiRedactor, Subject 2 @@ -13157,8 +13547,8 @@

Manifest (by level, then assembly)

2 JwtForwardingClientInterceptorTests MMCA.Common.Grpc.Tests - 5 - FakeRequest, FakeResponse, FakeStreamReader, FakeStreamWriter, JwtForwardingClientInterceptor + 6 + AuthenticateResultFeatureStub, FakeRequest, FakeResponse, FakeStreamReader, FakeStreamWriter, JwtForwardingClientInterceptor 2 @@ -13204,13 +13594,6 @@

Manifest (by level, then assembly)

2 - IndexBuilderExtensions - MMCA.Common.Infrastructure - 2 - DataSource, SoftDeleteFilterSql - - - 2 InternalCommandsSettings MMCA.Common.Infrastructure 2 @@ -13239,13 +13622,6 @@

Manifest (by level, then assembly)

2 - PhysicalDataSource - MMCA.Common.Infrastructure - 2 - DataSource, DataSourceKey - - - 2 QueryTags MMCA.Common.Infrastructure 1 @@ -13274,13 +13650,6 @@

Manifest (by level, then assembly)

2 - SoftDeleteUniqueIndexConvention - MMCA.Common.Infrastructure - 3 - DataSource, IAuditableEntity, SoftDeleteFilterSql - - - 2 TenancySettings MMCA.Common.Infrastructure 2 @@ -13330,6 +13699,20 @@

Manifest (by level, then assembly)

2 + RecordingDomainEventDispatcher + MMCA.Common.Infrastructure.SQLServer.Tests + 2 + IDomainEvent, IDomainEventDispatcher + + + 2 + SqlThingShipped + MMCA.Common.Infrastructure.SQLServer.Tests + 2 + BaseDomainEvent, IIntegrationEvent + + + 2 ApplicationNamespaceTests MMCA.Common.Infrastructure.Tests 3 @@ -13589,6 +13972,13 @@

Manifest (by level, then assembly)

2 + TypedServiceClientRegistrationTests + MMCA.Common.Infrastructure.Tests + 2 + FakeContract, IFakeContract + + + 2 UseDataSourceAttributeTests MMCA.Common.Infrastructure.Tests 2 @@ -13596,6 +13986,13 @@

Manifest (by level, then assembly)

2 + Measured + MMCA.Common.LoadTests + 1 + PagedCollectionResult<T> + + + 2 ErrorTypeSeverity MMCA.Common.Shared 2 @@ -13715,6 +14112,13 @@

Manifest (by level, then assembly)

2 + SqlServerIntegrationTestFixtureBase<TEntryPoint> + MMCA.Common.Testing + 2 + CrossServiceFixtureBase, IIntegrationTestFixture + + + 2 IArchitectureMap MMCA.Common.Testing.Architecture 2 @@ -13794,8 +14198,8 @@

Manifest (by level, then assembly)

2 BunitComponentTestBase MMCA.Common.Testing.UI - 5 - IsAuthenticatedAuthorizationService, ListPageQueryStateService, ListPageStateService, MudProviderHandles, MutableAuthenticationStateProvider + 4 + ListPageQueryStateService, ListPageStateService, MudProviderHandles, MutableAuthenticationStateProvider 2 @@ -13876,6 +14280,13 @@

Manifest (by level, then assembly)

2 + DirectApiTokenRefresher + MMCA.Common.UI + 5 + AuthDelegatingHandler, AuthenticationResponse, ISecureTokenStore, ITokenRefresher, RefreshTokenRequest + + + 2 IDeepLinkDispatcher MMCA.Common.UI 1 @@ -13883,6 +14294,13 @@

Manifest (by level, then assembly)

2 + IdempotentReadRetry + MMCA.Common.UI + 1 + AuthenticatedServiceBase + + + 2 IUIModule MMCA.Common.UI 1 @@ -13890,6 +14308,20 @@

Manifest (by level, then assembly)

2 + LocalizedDataAnnotationsValidator + MMCA.Common.UI + 2 + DataAnnotationsModelValidator, SharedResource + + + 2 + MmcaClientConfigBootstrap + MMCA.Common.UI + 1 + ApiSettings + + + 2 MMCATheme MMCA.Common.UI 2 @@ -13913,8 +14345,8 @@

Manifest (by level, then assembly)

2 NotificationHubService MMCA.Common.UI - 5 - ApiSettings, ChannelReferenceCounter, ChannelSubscription, ITokenStorageService, State + 6 + ApiSettings, ChannelReferenceCounter, ChannelSubscription, ITokenStorageService, SameOriginProxyHeaders, State 2 @@ -14060,8 +14492,8 @@

Manifest (by level, then assembly)

2 ApiUserPreferenceWriterTests MMCA.Common.UI.Tests - 4 - ApiUserPreferenceWriter, ITokenStorageService, StubHttpClientFactory, StubHttpMessageHandler + 5 + ApiUserPreferenceWriter, ITokenStorageService, Jwt, StubHttpClientFactory, StubHttpMessageHandler 2 @@ -14079,6 +14511,13 @@

Manifest (by level, then assembly)

2 + BrowserMapNavigationServiceTests + MMCA.Common.UI.Tests + 2 + BrowserMapNavigationService, IExternalLinkService + + + 2 CapturingHttpMessageHandlerTests MMCA.Common.UI.Tests 1 @@ -14100,6 +14539,13 @@

Manifest (by level, then assembly)

2 + JsFetchSessionCookieSyncTests + MMCA.Common.UI.Tests + 1 + JsFetchSessionCookieSync + + + 2 JwtAuthenticationStateProviderTests MMCA.Common.UI.Tests 2 @@ -14121,6 +14567,13 @@

Manifest (by level, then assembly)

2 + MauiBackNavigationBridgeTests + MMCA.Common.UI.Tests + 2 + BackNavigationResult, MauiBackNavigationBridge + + + 2 Mocks MMCA.Common.UI.Tests 2 @@ -14156,6 +14609,20 @@

Manifest (by level, then assembly)

2 + PasswordComplexityAttributeTests + MMCA.Common.UI.Tests + 2 + PasswordComplexityAttribute, RegisterModel + + + 2 + PolicyProbe + MMCA.Common.UI.Tests + 2 + AuthenticatedServiceBase, ITokenStorageService + + + 2 ProbePage MMCA.Common.UI.Tests 2 @@ -14228,8 +14695,22 @@

Manifest (by level, then assembly)

2 ClientConfigEndpointExtensions MMCA.Common.UI.Web + 4 + ApiSettings, ClientConfigBuilder, SameOriginApiProxyMarker, SameOriginApiProxySettings + + + 2 + HandoffSessionCookieSync + MMCA.Common.UI.Web + 2 + ISessionCookieSync, SessionHandoffProtector + + + 2 + HandoffTokenRefresher + MMCA.Common.UI.Web 2 - ApiSettings, ClientConfigBuilder + ITokenRefresher, SessionHandoffProtector 2 @@ -14709,6 +15190,13 @@

Manifest (by level, then assembly)

3 + PublicReadAudienceTests + MMCA.ADC.Conference.UI.Tests + 3 + ConferenceReadAudience, PublicReadAudience, RoleNames + + + 3 E2ETestCollection MMCA.ADC.E2E.Tests 2 @@ -14884,6 +15372,13 @@

Manifest (by level, then assembly)

3 + ChangePasswordRequestValidatorTests + MMCA.ADC.Identity.Application.Tests + 2 + ChangePasswordRequest, ChangePasswordRequestValidator + + + 3 ThrowingExportSection MMCA.ADC.Identity.Application.Tests 2 @@ -14961,10 +15456,10 @@

Manifest (by level, then assembly)

3 - GuardrailServiceCollectionExtensions + PiiRedactionGuardrail MMCA.Common.AI - 5 - ContentPolicyGuardrail, ContentPolicySettings, IChatGuardrail, IChatRequestRedactor, PiiRedactionGuardrail + 4 + GuardrailVerdict, IChatGuardrail, IChatRequestRedactor, Result 3 @@ -15024,13 +15519,6 @@

Manifest (by level, then assembly)

3 - GuardrailRegistrationTests - MMCA.Common.AI.Tests - 7 - AiSettings, BoundedChatClient, GuardrailChatClient, IChatGuardrail, IChatRequestRedactor, PiiRedactionGuardrail, StubChatClient - - - 3 RecordedResponsesTests MMCA.Common.AI.Tests 3 @@ -15045,13 +15533,6 @@

Manifest (by level, then assembly)

3 - RequestRedactionTests - MMCA.Common.AI.Tests - 6 - GuardrailChatClient, PiiRedactionGuardrail, RecordingGuardrail, StubChatClient, SuffixRedactor, UppercaseRedactor - - - 3 StreamedGuardrailTests MMCA.Common.AI.Tests 6 @@ -15073,20 +15554,6 @@

Manifest (by level, then assembly)

3 - CookieSessionRefreshMiddlewareExtensions - MMCA.Common.API - 1 - CookieSessionRefreshMiddleware - - - 3 - CookieTokenReader - MMCA.Common.API - 1 - SessionCookieEndpoints - - - 3 ErrorHttpMapping MMCA.Common.API 4 @@ -15129,6 +15596,13 @@

Manifest (by level, then assembly)

3 + AddCommonOpenApiHostRegistrationTests + MMCA.Common.API.Tests + 1 + HostRegistrationProbeFeatureProvider + + + 3 ModuleHostExtensionsTests MMCA.Common.API.Tests 3 @@ -15164,20 +15638,6 @@

Manifest (by level, then assembly)

3 - SessionCookieEndpointsTests - MMCA.Common.API.Tests - 6 - ICookieSessionRefresher, SessionCookieEndpoints, SessionCookieRequest, SessionTokenResponse, SessionTokenResult, StubRefresher - - - 3 - SessionCookieJarTests - MMCA.Common.API.Tests - 2 - SessionCookieEndpoints, SessionCookieJar - - - 3 DomainEventDispatcher MMCA.Common.Application 6 @@ -15192,6 +15652,13 @@

Manifest (by level, then assembly)

3 + IAuthSessionIssuer + MMCA.Common.Application + 4 + AuthenticationResponse, ITokenService, RefreshSessionSummaryResponse, Result + + + 3 ICacheService MMCA.Common.Application 1 @@ -15418,8 +15885,8 @@

Manifest (by level, then assembly)

3 ModuleLoaderTests MMCA.Common.Application.Tests - 10 - ApplicationSettings, FakeCycleModuleOne, FakeCycleModuleTwo, FakeModuleTracker, FakeRemoteContractRealAdapter, FakeRemoteContractStub, IFakeRemoteContract, ModuleLoader, ModuleSettings, ModulesSettings + 12 + ApplicationSettings, FakeCycleModuleOne, FakeCycleModuleTwo, FakeModuleAlpha, FakeModuleTracker, FakeRemoteContractRealAdapter, FakeRemoteContractStub, IFakeRemoteContract, ModuleLoader, ModuleSettings, ModulesSettings, ScanFailingAssembly 3 @@ -15486,6 +15953,13 @@

Manifest (by level, then assembly)

3 + ResetPasswordRequestValidatorTests + MMCA.Common.Application.Tests + 2 + ResetPasswordRequest, ResetPasswordRequestValidator + + + 3 RetiredEvent MMCA.Common.Application.Tests 1 @@ -15703,6 +16177,55 @@

Manifest (by level, then assembly)

3 + FixtureCountEvent + MMCA.Common.Architecture.Tests + 1 + BaseIntegrationEvent + + + 3 + FixtureLabelEvent + MMCA.Common.Architecture.Tests + 1 + BaseIntegrationEvent + + + 3 + FixtureNamedEvent + MMCA.Common.Architecture.Tests + 1 + BaseIntegrationEvent + + + 3 + FixtureNullableCountEvent + MMCA.Common.Architecture.Tests + 1 + BaseIntegrationEvent + + + 3 + FixtureNullableLabelEvent + MMCA.Common.Architecture.Tests + 1 + BaseIntegrationEvent + + + 3 + FixtureShapedBase + MMCA.Common.Architecture.Tests + 1 + BaseIntegrationEvent + + + 3 + FixtureTallyEvent + MMCA.Common.Architecture.Tests + 1 + BaseIntegrationEvent + + + 3 GetFixtureEntityHandlerBase<TQuery> MMCA.Common.Architecture.Tests 2 @@ -15941,13 +16464,6 @@

Manifest (by level, then assembly)

3 - RefreshSession - MMCA.Common.Domain - 2 - Error, Result - - - 3 BaseDomainEventTests MMCA.Common.Domain.Tests 1 @@ -16039,20 +16555,6 @@

Manifest (by level, then assembly)

3 - CrossDataSourceDegradeConvention - MMCA.Common.Infrastructure - 3 - DataSource, DataSourceKey, IEntityDataSourceRegistry - - - 3 - DataSourceService - MMCA.Common.Infrastructure - 4 - DataSource, DataSourceKey, IDataSourceService, IEntityDataSourceRegistry - - - 3 EventUpcasterStartupValidator MMCA.Common.Infrastructure 2 @@ -16060,13 +16562,6 @@

Manifest (by level, then assembly)

3 - IDataSourceResolver - MMCA.Common.Infrastructure - 3 - DataSource, DataSourceKey, PhysicalDataSource - - - 3 InProcessMessageBus MMCA.Common.Infrastructure 3 @@ -16137,13 +16632,6 @@

Manifest (by level, then assembly)

3 - ExecutionLog - MMCA.Common.Infrastructure.Tests - 2 - RecordedExecution, Result - - - 3 FixedSourcesRegistry MMCA.Common.Infrastructure.Tests 2 @@ -16235,13 +16723,6 @@

Manifest (by level, then assembly)

3 - PhysicalDataSourceTests - MMCA.Common.Infrastructure.Tests - 3 - DataSource, DataSourceKey, PhysicalDataSource - - - 3 RecordingHandler<TEvent> MMCA.Common.Infrastructure.Tests 2 @@ -16319,13 +16800,6 @@

Manifest (by level, then assembly)

3 - TestPhysicalDataSources - MMCA.Common.Infrastructure.Tests - 3 - DataSource, DataSourceKey, PhysicalDataSource - - - 3 ThrowingHandler<TEvent> MMCA.Common.Infrastructure.Tests 2 @@ -16368,6 +16842,20 @@

Manifest (by level, then assembly)

3 + CountingMessageBus + MMCA.Common.LoadTests + 2 + IIntegrationEvent, IMessageBus + + + 3 + LoadIntegrationEvent + MMCA.Common.LoadTests + 1 + BaseIntegrationEvent + + + 3 Address MMCA.Common.Shared 4 @@ -16508,6 +16996,13 @@

Manifest (by level, then assembly)

3 + LateRegisteredId + MMCA.Common.Shared.Tests + 1 + IStronglyTypedId<TSelf, TValue> + + + 3 LineId MMCA.Common.Shared.Tests 2 @@ -16713,8 +17208,15 @@

Manifest (by level, then assembly)

3 MobileInfiniteScrollList<TItem> MMCA.Common.UI + 2 + Result, SharedResource + + 3 - IToastService, Result, SharedResource + PagedReadAll + MMCA.Common.UI + 2 + PagedCollectionResult<T>, Result 3 @@ -16753,6 +17255,20 @@

Manifest (by level, then assembly)

3 + BrowserExternalLinkServiceTests + MMCA.Common.UI.Tests + 2 + BrowserExternalLinkService, CapabilitiesJsModule + + + 3 + BunitComponentTestBaseAuthorizationTests + MMCA.Common.UI.Tests + 2 + BunitComponentTestBase, TestPrincipal + + + 3 BunitComponentTestBaseFacadeTests MMCA.Common.UI.Tests 5 @@ -16783,8 +17299,22 @@

Manifest (by level, then assembly)

3 DirectApiTokenRefresherTests MMCA.Common.UI.Tests - 6 - AuthenticationResponse, DirectApiTokenRefresher, ISecureTokenStore, Mocks, StubHttpClientFactory, StubHttpMessageHandler + 7 + AuthDelegatingHandler, AuthenticationResponse, DirectApiTokenRefresher, ISecureTokenStore, Mocks, StubHttpClientFactory, StubHttpMessageHandler + + + 3 + IdempotentReadRetryTests + MMCA.Common.UI.Tests + 3 + IdempotentReadRetry, PolicyProbe, StubHandler + + + 3 + MmcaClientConfigBootstrapTests + MMCA.Common.UI.Tests + 2 + MmcaClientConfigBootstrap, ScriptedHandler 3 @@ -16795,6 +17325,20 @@

Manifest (by level, then assembly)

3 + OtherModule + MMCA.Common.UI.Tests + 2 + IUIModule, NavItem + + + 3 + SameOriginProxyClientTests + MMCA.Common.UI.Tests + 7 + ApiSettings, CapturingHandler, ITokenStorageService, MmcaClientConfigBootstrap, NotificationHubService, SameOriginProxyHeaders, StubTokenStorageService + + + 3 StubUiModule MMCA.Common.UI.Tests 2 @@ -16802,6 +17346,13 @@

Manifest (by level, then assembly)

3 + TokenRefreshPipelineTests + MMCA.Common.UI.Tests + 9 + AuthDelegatingHandler, AuthenticationResponse, DirectApiTokenRefresher, ISecureTokenStore, ISessionCookieSync, ITokenRefresher, ITokenStorageService, StubHttpMessageHandler, WasmTokenStorageService + + + 3 UiHttpServiceHarnessTests MMCA.Common.UI.Tests 1 @@ -16822,6 +17373,13 @@

Manifest (by level, then assembly)

BlazorCircuitLimitExtensions, BlazorCircuitLimitSettings, BoundedCircuitHandler + 3 + SessionHandoffServicesTests + MMCA.Common.UI.Web.Tests + 3 + HandoffSessionCookieSync, HandoffTokenRefresher, SessionHandoffProtector + + 4 AdcAppHostCollection MMCA.ADC.AppHost.SmokeTests @@ -16837,6 +17395,13 @@

Manifest (by level, then assembly)

4 + MobileHostParityTests + MMCA.ADC.Architecture.Tests + 1 + ArchitectureMapBase + + + 4 DependencyInjection MMCA.ADC.Conference.API 2 @@ -16916,8 +17481,8 @@

Manifest (by level, then assembly)

4 CategoryItemLookupService MMCA.ADC.Conference.UI - 9 - CategoryItemDTO, CategoryItemInfo, CollectionResult<T>, ConferenceCategoryDTO, HttpResultExecutor, ICategoryItemLookupService, PagedCollectionResult<T>, ProblemDetailsResultReader, Result + 11 + CategoryItemDTO, CategoryItemInfo, CollectionResult<T>, ConferenceCategoryDTO, HttpResultExecutor, ICategoryItemLookupService, IdempotentReadRetry, PagedCollectionResult<T>, PagedReadAll, ProblemDetailsResultReader, Result 4 @@ -16951,8 +17516,8 @@

Manifest (by level, then assembly)

4 EventLookupService MMCA.ADC.Conference.UI - 7 - EventDTO, EventInfo, HttpResultExecutor, IEventLookupService, PagedCollectionResult<T>, ProblemDetailsResultReader, Result + 9 + EventDTO, EventInfo, HttpResultExecutor, IdempotentReadRetry, IEventLookupService, PagedCollectionResult<T>, PagedReadAll, ProblemDetailsResultReader, Result 4 @@ -17035,15 +17600,15 @@

Manifest (by level, then assembly)

4 OrganizerEventFeedbackService MMCA.ADC.Conference.UI - 8 - AuthenticatedServiceBase, EventQuestionAnswerDTO, HttpResultExecutor, IOrganizerEventFeedbackUIService, ITokenStorageService, PagedCollectionResult<T>, ProblemDetailsResultReader, Result + 9 + AuthenticatedServiceBase, EventQuestionAnswerDTO, HttpResultExecutor, IOrganizerEventFeedbackUIService, ITokenStorageService, PagedCollectionResult<T>, PagedReadAll, ProblemDetailsResultReader, Result 4 OrganizerSessionFeedbackService MMCA.ADC.Conference.UI - 8 - AuthenticatedServiceBase, HttpResultExecutor, IOrganizerSessionFeedbackUIService, ITokenStorageService, PagedCollectionResult<T>, ProblemDetailsResultReader, Result, SessionQuestionAnswerDTO + 9 + AuthenticatedServiceBase, HttpResultExecutor, IOrganizerSessionFeedbackUIService, ITokenStorageService, PagedCollectionResult<T>, PagedReadAll, ProblemDetailsResultReader, Result, SessionQuestionAnswerDTO 4 @@ -17098,8 +17663,8 @@

Manifest (by level, then assembly)

4 SpeakerLookupService MMCA.ADC.Conference.UI - 7 - HttpResultExecutor, ISpeakerLookupService, PagedCollectionResult<T>, ProblemDetailsResultReader, Result, SpeakerDTO, SpeakerInfo + 9 + HttpResultExecutor, IdempotentReadRetry, ISpeakerLookupService, PagedCollectionResult<T>, PagedReadAll, ProblemDetailsResultReader, Result, SpeakerDTO, SpeakerInfo 4 @@ -17117,6 +17682,13 @@

Manifest (by level, then assembly)

4 + RoomCreateModelTests + MMCA.ADC.Conference.UI.Tests + 1 + RoomCreateModel + + + 4 AccessibilityTests MMCA.ADC.E2E.Tests 42 @@ -17441,8 +18013,8 @@

Manifest (by level, then assembly)

4 NowNextService MMCA.ADC.Engagement.UI - 5 - HttpResultExecutor, INowNextService, NowNextSnapshot, ProblemDetailsResultReader, Result + 6 + HttpResultExecutor, IdempotentReadRetry, INowNextService, NowNextSnapshot, ProblemDetailsResultReader, Result 4 @@ -17476,8 +18048,8 @@

Manifest (by level, then assembly)

4 SessionLookupService MMCA.ADC.Engagement.UI - 7 - HttpResultExecutor, ISessionLookupService, PagedCollectionResult<T>, ProblemDetailsResultReader, Result, SessionDTO, SessionInfo + 8 + HttpResultExecutor, IdempotentReadRetry, ISessionLookupService, PagedCollectionResult<T>, ProblemDetailsResultReader, Result, SessionDTO, SessionInfo 4 @@ -17490,8 +18062,8 @@

Manifest (by level, then assembly)

4 SessionReminderCoordinator MMCA.ADC.Engagement.UI - 6 - IDevicePreferences, ILiveEventUIService, ILocalNotificationService, ISessionLookupService, LocalNotificationRequest, SessionReminderPlanner + 7 + IDevicePreferences, ILiveEventUIService, ILocalNotificationService, ISessionLookupService, LocalNotificationRequest, SessionReminder, SessionReminderPlanner 4 @@ -17614,6 +18186,13 @@

Manifest (by level, then assembly)

4 + SelfHttpOutputCacheWarmupTaskTests + MMCA.ADC.Services.Tests + 2 + PagedReadAll, SelfHttpOutputCacheWarmupTask + + + 4 App MMCA.ADC.UI 1 @@ -17621,17 +18200,31 @@

Manifest (by level, then assembly)

4 - ApiControllerBase - MMCA.Common.API - 3 - Error, ErrorHttpMapping, IErrorLocalizer + GuardrailServiceCollectionExtensions + MMCA.Common.AI + 5 + ContentPolicyGuardrail, ContentPolicySettings, IChatGuardrail, IChatRequestRedactor, PiiRedactionGuardrail 4 - CookieSessionRefresher + GuardrailRegistrationTests + MMCA.Common.AI.Tests + 7 + AiSettings, BoundedChatClient, GuardrailChatClient, IChatGuardrail, IChatRequestRedactor, PiiRedactionGuardrail, StubChatClient + + + 4 + RequestRedactionTests + MMCA.Common.AI.Tests + 6 + GuardrailChatClient, PiiRedactionGuardrail, RecordingGuardrail, StubChatClient, SuffixRedactor, UppercaseRedactor + + + 4 + ApiControllerBase MMCA.Common.API - 8 - AuthenticationResponse, CookieTokenReader, ICookieSessionRefresher, KeyedSemaphoreStripe, RefreshTokenRequest, SessionCookieEndpoints, SessionCookieJar, SessionTokenResult + 3 + Error, ErrorHttpMapping, IErrorLocalizer 4 @@ -17644,8 +18237,8 @@

Manifest (by level, then assembly)

4 EntityCsvExporter<TEntityDTO> MMCA.Common.API - 5 - CsvWriter, Error, PagedCollectionResult<T>, QueryFieldService, Result + 6 + CsvWriter, Error, PagedCollectionResult<T>, PaginationMetadata, QueryFieldService, Result 4 @@ -17670,13 +18263,6 @@

Manifest (by level, then assembly)

4 - SessionCookieAuthenticationHandler - MMCA.Common.API - 1 - CookieTokenReader - - - 4 StronglyTypedIdParameterTransformer MMCA.Common.API 2 @@ -17698,24 +18284,24 @@

Manifest (by level, then assembly)

4 - CookieSessionRefreshMiddlewareTests + CurrencyJsonConverterTests MMCA.Common.API.Tests - 5 - CookieSessionRefreshMiddleware, CookieSessionRefreshMiddlewareExtensions, ICookieSessionRefresher, NextDelegateSpy, SessionTokenResult + 1 + Currency 4 - CookieTokenReaderTests + DesignTimeDatabaseInitializationTests MMCA.Common.API.Tests - 2 - CookieTokenReader, SessionCookieEndpoints + 1 + ModuleHostContext 4 - CurrencyJsonConverterTests + ErrorHttpMappingTests MMCA.Common.API.Tests - 1 - Currency + 2 + ErrorHttpMapping, ErrorType 4 @@ -17798,8 +18384,8 @@

Manifest (by level, then assembly)

4 BeginTwoFactorEnrollmentHandlerBase<TCommand> MMCA.Common.Application - 9 - Error, ICommandHandler<in TCommand, TResult>, ITwoFactorService, ITwoFactorStore, ITwoFactorUserState, IUserScopedRequest, Result, TwoFactorSetupResponse, UserUseCaseLog + 8 + Error, ICommandHandler<in TCommand, TResult>, ITwoFactorService, ITwoFactorStore, ITwoFactorUserState, IUserScopedRequest, Result, TwoFactorSetupResponse 4 @@ -17826,8 +18412,8 @@

Manifest (by level, then assembly)

4 ConfirmTwoFactorEnrollmentHandlerBase<TCommand> MMCA.Common.Application - 9 - ICommandHandler<in TCommand, TResult>, ITwoFactorService, ITwoFactorStore, IUserScopedCommand<out TRequest>, Result, TwoFactorCodeRequest, TwoFactorErrors, TwoFactorRecoveryCodesResponse, UserUseCaseLog + 8 + ICommandHandler<in TCommand, TResult>, ITwoFactorService, ITwoFactorStore, IUserScopedCommand<out TRequest>, Result, TwoFactorCodeRequest, TwoFactorErrors, TwoFactorRecoveryCodesResponse 4 @@ -17840,8 +18426,8 @@

Manifest (by level, then assembly)

4 DisableTwoFactorHandlerBase<TCommand> MMCA.Common.Application - 9 - ICommandHandler<in TCommand, TResult>, ITwoFactorAuthenticator, ITwoFactorStore, IUserScopedCommand<out TRequest>, Result, TwoFactorCodeRequest, TwoFactorErrors, TwoFactorOutcome, UserUseCaseLog + 8 + ICommandHandler<in TCommand, TResult>, ITwoFactorAuthenticator, ITwoFactorStore, IUserScopedCommand<out TRequest>, Result, TwoFactorCodeRequest, TwoFactorErrors, TwoFactorOutcome 4 @@ -17943,13 +18529,6 @@

Manifest (by level, then assembly)

4 - IRefreshSessionStore - MMCA.Common.Application - 1 - RefreshSession - - - 4 QueryFilterService MMCA.Common.Application 12 @@ -17959,8 +18538,8 @@

Manifest (by level, then assembly)

4 RegenerateRecoveryCodesHandlerBase<TCommand> MMCA.Common.Application - 11 - ICommandHandler<in TCommand, TResult>, ITwoFactorAuthenticator, ITwoFactorService, ITwoFactorStore, IUserScopedCommand<out TRequest>, Result, TwoFactorCodeRequest, TwoFactorErrors, TwoFactorOutcome, TwoFactorRecoveryCodesResponse, UserUseCaseLog + 10 + ICommandHandler<in TCommand, TResult>, ITwoFactorAuthenticator, ITwoFactorService, ITwoFactorStore, IUserScopedCommand<out TRequest>, Result, TwoFactorCodeRequest, TwoFactorErrors, TwoFactorOutcome, TwoFactorRecoveryCodesResponse 4 @@ -18447,6 +19026,13 @@

Manifest (by level, then assembly)

4 + DataSourceBranchingFitnessTests + MMCA.Common.Architecture.Tests + 3 + ArchitectureAssert, ArchitectureMapBase, EngineHit + + + 4 DriftedTests MMCA.Common.Architecture.Tests 2 @@ -18559,6 +19145,13 @@

Manifest (by level, then assembly)

4 + FixtureShapedEvent + MMCA.Common.Architecture.Tests + 1 + FixtureShapedBase + + + 4 ModuleConformanceTestsBaseTests MMCA.Common.Architecture.Tests 2 @@ -18566,6 +19159,13 @@

Manifest (by level, then assembly)

4 + PluralSentenceResourceTests + MMCA.Common.Architecture.Tests + 3 + ArchitectureAssert, ArchitectureMapBase, ResourceEntry + + + 4 StaleAllowListTests MMCA.Common.Architecture.Tests 2 @@ -18622,6 +19222,13 @@

Manifest (by level, then assembly)

4 + RefreshSession + MMCA.Common.Domain + 3 + Error, IAnonymizable, Result + + + 4 SpecificationExtensions MMCA.Common.Domain 5 @@ -18699,13 +19306,6 @@

Manifest (by level, then assembly)

4 - RefreshSessionTests - MMCA.Common.Domain.Tests - 2 - RefreshSession, Result - - - 4 TestEntity MMCA.Common.Domain.Tests 2 @@ -18811,13 +19411,6 @@

Manifest (by level, then assembly)

4 - DataSourceResolver - MMCA.Common.Infrastructure - 8 - ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourcesSettings, DefaultSeed, IDataSourceResolver, PhysicalDataSource - - - 4 DistributedCacheService MMCA.Common.Infrastructure 5 @@ -18869,8 +19462,8 @@

Manifest (by level, then assembly)

4 MemoryCacheService MMCA.Common.Infrastructure - 2 - ICacheService, KeyedSemaphoreStripe + 3 + CacheSettings, ICacheService, KeyedSemaphoreStripe 4 @@ -18902,13 +19495,6 @@

Manifest (by level, then assembly)

4 - RefreshSessionModelBuilderExtensions - MMCA.Common.Infrastructure - 1 - RefreshSession - - - 4 SpecificationEvaluator MMCA.Common.Infrastructure 5 @@ -18916,17 +19502,10 @@

Manifest (by level, then assembly)

4 - TenancySettingsValidator - MMCA.Common.Infrastructure - 7 - DataSource, DataSourceKey, IDataSourceResolver, TenancySettings, TenantDataSourceOverrideSettings, TenantEntrySettings, TenantResolutionStrategy - - - 4 TwoFactorAuthenticator MMCA.Common.Infrastructure - 6 - ITwoFactorAuthenticator, ITwoFactorService, ITwoFactorStore, Result, TwoFactorErrors, TwoFactorOutcome + 8 + ICacheService, ITwoFactorAuthenticator, ITwoFactorService, ITwoFactorStore, Result, TwoFactorErrors, TwoFactorOutcome, TwoFactorSettings 4 @@ -18951,13 +19530,6 @@

Manifest (by level, then assembly)

4 - DefaultDataSourceResolver - MMCA.Common.Infrastructure.Tests - 4 - DataSource, DataSourceKey, IDataSourceResolver, PhysicalDataSource - - - 4 DependencyInjectionPushNotificationsTests MMCA.Common.Infrastructure.Tests 5 @@ -19021,13 +19593,6 @@

Manifest (by level, then assembly)

4 - FixedEngineResolver - MMCA.Common.Infrastructure.Tests - 4 - DataSource, DataSourceKey, IDataSourceResolver, PhysicalDataSource - - - 4 Parent MMCA.Common.Infrastructure.Tests 2 @@ -19821,8 +20386,8 @@

Manifest (by level, then assembly)

4 NotificationHubServiceTests MMCA.Common.UI.Tests - 5 - ApiSettings, CapturingLogger, ConcurrencyTrackingTokenStorage, ITokenStorageService, NotificationHubService + 6 + ApiSettings, CapturingLogger, ConcurrencyTrackingTokenStorage, InMemoryHubServer, ITokenStorageService, NotificationHubService 4 @@ -19840,6 +20405,13 @@

Manifest (by level, then assembly)

4 + PagedReadAllTests + MMCA.Common.UI.Tests + 5 + Error, PagedCollectionResult<T>, PagedReadAll, PaginationMetadata, Result + + + 4 PageStateScopeTests MMCA.Common.UI.Tests 1 @@ -19930,13 +20502,6 @@

Manifest (by level, then assembly)

BunitTestBase - 4 - ServerTokenStorageService - MMCA.Common.UI.Web - 5 - CookieTokenReader, ISessionCookieSync, ITokenRefresher, ITokenStorageService, JwtTokenInfo - - 5 AdcAppHostSmokeTests MMCA.ADC.AppHost.SmokeTests @@ -20274,6 +20839,34 @@

Manifest (by level, then assembly)

5 + SessionAssetServiceTests + MMCA.ADC.Conference.UI.Tests + 4 + CapturingHttpMessageHandler, HttpTestDoubles, SessionAssetDTO, SessionAssetService + + + 5 + SessionCreateModelTests + MMCA.ADC.Conference.UI.Tests + 1 + SessionCreateModel + + + 5 + SessionEditModelTests + MMCA.ADC.Conference.UI.Tests + 2 + SessionDTO, SessionEditModel + + + 5 + SpeakerLookupServiceTests + MMCA.ADC.Conference.UI.Tests + 6 + CapturingHttpMessageHandler, HttpTestDoubles, PagedCollectionResult<T>, PaginationMetadata, SpeakerDTO, SpeakerLookupService + + + 5 AuthorizationTests MMCA.ADC.E2E.Tests 2 @@ -20554,13 +21147,6 @@

Manifest (by level, then assembly)

5 - SessionCookieAuthenticationExtensions - MMCA.Common.API - 1 - SessionCookieAuthenticationHandler - - - 5 UsersAdminControllerBase<TUserDto> MMCA.Common.API 6 @@ -20575,6 +21161,13 @@

Manifest (by level, then assembly)

5 + EntityCsvExporterTests + MMCA.Common.API.Tests + 5 + EntityCsvExporter<TEntityDTO>, ExportRow, PagedCollectionResult<T>, PaginationMetadata, Result + + + 5 ExportShapeTestDTO MMCA.Common.API.Tests 2 @@ -20591,8 +21184,8 @@

Manifest (by level, then assembly)

5 IdempotencyFilterTests MMCA.Common.API.Tests - 8 - AuthClaimTypes, ICacheService, IdempotencyFilter, IdempotencyRecord, IDistributedLock, NonSeekableStream, Result, TrackingHandle + 10 + AuthClaimTypes, ICacheService, IdempotencyFilter, IdempotencyRecord, IDistributedLock, NonSeekableStream, Result, TrackingHandle, Wrapped, WrappedAsStringConverter 5 @@ -20617,13 +21210,6 @@

Manifest (by level, then assembly)

5 - RefresherHarness - MMCA.Common.API.Tests - 3 - CookieSessionRefresher, StubHttpClientFactory, StubHttpMessageHandler - - - 5 RoundTripController MMCA.Common.API.Tests 2 @@ -20631,13 +21217,6 @@

Manifest (by level, then assembly)

5 - SessionCookieAuthenticationHandlerTests - MMCA.Common.API.Tests - 4 - CookieTokenReader, FakeTimeProvider, SessionCookieAuthenticationHandler, SessionCookieEndpoints - - - 5 SupportsIfMatchAttributeTests MMCA.Common.API.Tests 3 @@ -20729,6 +21308,13 @@

Manifest (by level, then assembly)

5 + IRefreshSessionStore + MMCA.Common.Application + 1 + RefreshSession + + + 5 IWriteRepository<TEntity, TIdentifierType> MMCA.Common.Application 3 @@ -20750,13 +21336,6 @@

Manifest (by level, then assembly)

5 - RefreshSessionRevocation - MMCA.Common.Application - 2 - IRefreshSessionStore, RefreshSession - - - 5 UpdateEntityCommand<TEntity, TUpdateRequest, TIdentifierType> MMCA.Common.Application 4 @@ -20836,8 +21415,8 @@

Manifest (by level, then assembly)

5 DateTimeFilterStrategyTests MMCA.Common.Application.Tests - 2 - Item, QueryFilterService + 3 + DateTimeFilterStrategy, Item, QueryFilterService 5 @@ -21025,8 +21604,8 @@

Manifest (by level, then assembly)

5 StringFilterStrategyTests MMCA.Common.Application.Tests - 2 - Item, QueryFilterService + 3 + Item, QueryFilterService, StringFilterStrategy 5 @@ -21506,6 +22085,13 @@

Manifest (by level, then assembly)

5 + RefreshSessionTests + MMCA.Common.Domain.Tests + 2 + RefreshSession, Result + + + 5 TestAggregate MMCA.Common.Domain.Tests 5 @@ -21534,13 +22120,6 @@

Manifest (by level, then assembly)

5 - EntityDataSourceRegistry - MMCA.Common.Infrastructure - 10 - DataSource, DataSourceKey, IDataSourceResolver, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IEntityTypeConfigurationBase<TEntity, TIdentifierType>, NamespaceConventions, Snapshot, UseDatabaseAttribute, UseDataSourceAttribute - - - 5 EntityTypeBuilderExtensions MMCA.Common.Infrastructure 4 @@ -21618,6 +22197,13 @@

Manifest (by level, then assembly)

5 + RefreshSessionModelBuilderExtensions + MMCA.Common.Infrastructure + 1 + RefreshSession + + + 5 ScopedUserOverride MMCA.Common.Infrastructure 1 @@ -21632,13 +22218,6 @@

Manifest (by level, then assembly)

5 - TenantDataSourceTargets - MMCA.Common.Infrastructure - 8 - DataSource, DataSourceKey, IDataSourceResolver, IEntityDataSourceRegistry, ITenantContext, TenancySettings, TenancySettingsValidator, TenantDataSourceTarget - - - 5 PgThing MMCA.Common.Infrastructure.PostgreSQL.Tests 3 @@ -21655,8 +22234,15 @@

Manifest (by level, then assembly)

5 HybridCacheServiceRedisTests MMCA.Common.Infrastructure.Redis.Tests + 3 + DistributedCacheService, HybridCacheService, ICacheService + + + 5 + SqlThing + MMCA.Common.Infrastructure.SQLServer.Tests 2 - DistributedCacheService, HybridCacheService + AuditableAggregateRootEntity<TIdentifierType>, SqlThingShipped 5 @@ -21681,27 +22267,6 @@

Manifest (by level, then assembly)

5 - DataSourceResolverTests - MMCA.Common.Infrastructure.Tests - 9 - AuditTrailSettings, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourceResolver, DataSourcesSettings, OutboxSettings, SchedulerSettings - - - 5 - DataSourceServiceAdditionalTests - MMCA.Common.Infrastructure.Tests - 7 - DataSource, DataSourceKey, DataSourceService, FakeEntity, FakeEntity, IEntityDataSourceRegistry, UnregisteredEntity - - - 5 - DataSourceServiceTests - MMCA.Common.Infrastructure.Tests - 6 - DataSource, DataSourceKey, DataSourceService, FakeEntity, FakeEntity, IEntityDataSourceRegistry - - - 5 DegradeCustomer MMCA.Common.Infrastructure.Tests 2 @@ -21765,6 +22330,13 @@

Manifest (by level, then assembly)

5 + ExecutionLog + MMCA.Common.Infrastructure.Tests + 3 + RecordedExecution, RecordingCommand, Result + + + 5 FakeAggregate MMCA.Common.Infrastructure.Tests 1 @@ -21823,8 +22395,15 @@

Manifest (by level, then assembly)

5 HybridCacheServiceTests MMCA.Common.Infrastructure.Tests - 8 - CacheKeyNamespace, CacheOptions, CacheSettings, DistributedCacheService, FaultingHybridCache, HybridCacheService, RecordingDistributedCache, RecordingHybridCache + 9 + CacheKeyNamespace, CacheOptions, CacheSettings, DistributedCacheService, FaultingHybridCache, HybridCacheService, ICacheService, RecordingDistributedCache, RecordingHybridCache + + + 5 + ImageSharpImageProcessorFrameBoundTests + MMCA.Common.Infrastructure.Tests + 3 + CountingAllocator, ImageFrameBoundCollection, ImageSharpImageProcessor 5 @@ -21844,8 +22423,8 @@

Manifest (by level, then assembly)

5 MemoryCacheServiceTests MMCA.Common.Infrastructure.Tests - 3 - EvictionSignalingMemoryCache, KeyedSemaphoreStripe, MemoryCacheService + 5 + CacheSettings, EvictionSignalingMemoryCache, ICacheService, KeyedSemaphoreStripe, MemoryCacheService 5 @@ -21891,13 +22470,6 @@

Manifest (by level, then assembly)

5 - RefreshSessionModelBuilderExtensionsTests - MMCA.Common.Infrastructure.Tests - 4 - CustomSchemaContext, RefreshSession, RefreshSessionModelBuilderExtensions, RefreshSessionOnlyContext - - - 5 RegistryDuplicate MMCA.Common.Infrastructure.Tests 1 @@ -22005,8 +22577,8 @@

Manifest (by level, then assembly)

5 TwoFactorAuthenticatorTests MMCA.Common.Infrastructure.Tests - 10 - Error, ErrorType, FakeTwoFactorStore, RecoveryCodeSet, Result, TotpTwoFactorService, TwoFactorAuthenticator, TwoFactorErrors, TwoFactorOutcome, TwoFactorSettings + 12 + Error, ErrorType, FakeCacheService, FakeTwoFactorStore, ICacheService, RecoveryCodeSet, Result, TotpTwoFactorService, TwoFactorAuthenticator, TwoFactorErrors, TwoFactorOutcome, TwoFactorSettings 5 @@ -22024,6 +22596,13 @@

Manifest (by level, then assembly)

5 + LoadItem + MMCA.Common.LoadTests + 1 + AuditableAggregateRootEntity<TIdentifierType> + + + 5 StronglyTypedIdRegistry MMCA.Common.Shared 2 @@ -22089,8 +22668,8 @@

Manifest (by level, then assembly)

5 StronglyTypedIdTypeConverterTests MMCA.Common.Shared.Tests - 5 - LineId, OrderId, SkuId, StronglyTypedIdTypeConverter<TSelf, TValue>, StronglyTypedIdTypeConverters + 6 + LateRegisteredId, LineId, OrderId, SkuId, StronglyTypedIdTypeConverter<TSelf, TValue>, StronglyTypedIdTypeConverters 5 @@ -22101,13 +22680,6 @@

Manifest (by level, then assembly)

5 - InMemoryRefreshSessionStore - MMCA.Common.Testing - 2 - IRefreshSessionStore, RefreshSession - - - 5 AggregateConventionTestsBase MMCA.Common.Testing.Architecture 2 @@ -22570,33 +23142,12 @@

Manifest (by level, then assembly)

5 - DependencyInjection - MMCA.Common.UI.Web - 11 - ApiSettings, BlazorCspPolicyProvider, BlazorCspSettings, BlazorCspSettingsValidator, GatewayRateLimitingSettings, ICspPolicyProvider, IFormFactor, ITokenStorageService, ServerTokenStorageService, TrustedCallerHandler, WebFormFactor - - - 5 ClientConfigEndpointTests MMCA.Common.UI.Web.Tests 3 ApiSettings, ClientConfigEndpointExtensions, Email - 5 - ServerTokenStorageServiceTests - MMCA.Common.UI.Web.Tests - 6 - CookieTokenReader, ISessionCookieSync, ITokenRefresher, Mocks, ServerTokenStorageService, SessionCookieEndpoints - - - 5 - WebFormFactorTests - MMCA.Common.UI.Web.Tests - 5 - ICspPolicyProvider, IFormFactor, ITokenStorageService, ServerTokenStorageService, WebFormFactor - - 6 AnonymousEndpointTests MMCA.ADC.Architecture.Tests @@ -22810,8 +23361,8 @@

Manifest (by level, then assembly)

6 SessionCreate MMCA.ADC.Conference.UI - 15 - ConferenceRoutePaths, DataAnnotationsModelValidator, ErrorMessages, EventInfo, EventLookupService, IEventLookupService, IRoomUIService, ISessionUIService, IToastService, ModelValidation, Result, RoomDTO, RoomService, SessionCreateModel, SessionService + 16 + ConferenceRoutePaths, DataAnnotationsModelValidator, ErrorMessages, EventInfo, EventLookupService, IEventLookupService, IRoomUIService, ISessionUIService, IToastService, ModelValidation, Result, RoomDTO, RoomService, SessionCreateModel, SessionFormModel, SessionService 6 @@ -22857,6 +23408,20 @@

Manifest (by level, then assembly)

6 + PublicSessionScheduleServiceTests + MMCA.ADC.Conference.UI.Tests + 6 + IConnectivityStatusService, ISessionUIService, PublicSessionScheduleService, RecordingCacheStore, SessionDTO, SessionSchedulePageRequest + + + 6 + RoomServiceTests + MMCA.ADC.Conference.UI.Tests + 4 + CapturingHttpMessageHandler, HttpTestDoubles, RoomDTO, RoomService + + + 6 SessionSelectionServiceTests MMCA.ADC.Conference.UI.Tests 9 @@ -22864,6 +23429,13 @@

Manifest (by level, then assembly)

6 + SpeakerEditModelTests + MMCA.ADC.Conference.UI.Tests + 2 + SpeakerDTO, SpeakerEditModel + + + 6 AttendeeBadgeInvariants MMCA.ADC.Engagement.Domain 2 @@ -23130,13 +23702,6 @@

Manifest (by level, then assembly)

6 - CookieSessionRefresherTests - MMCA.Common.API.Tests - 7 - AuthenticationResponse, CookieSessionRefresher, CookieTokenReader, KeyedSemaphoreStripe, RefresherHarness, SessionCookieEndpoints, SessionTokenResult - - - 6 EdgeErrorLocalizationTests MMCA.Common.API.Tests 4 @@ -23188,8 +23753,8 @@

Manifest (by level, then assembly)

6 RateLimitPartitionTests MMCA.Common.API.Tests - 2 - AuthClaimTypes, WebApplicationBuilderExtensions + 3 + AuthClaimTypes, RateLimitingSettings, WebApplicationBuilderExtensions 6 @@ -23228,6 +23793,13 @@

Manifest (by level, then assembly)

6 + AuthSessionIssuer + MMCA.Common.Application + 12 + AuthenticationResponse, Error, IAuthenticationService, IAuthSessionIssuer, IRefreshSessionStore, IssuedSession, ITokenService, RefreshSession, RefreshSessionSettings, RefreshSessionSummaryResponse, Result, SessionStampingTokenService + + + 6 IEntityUpdateCommandApplier<TEntity, TUpdateRequest, TIdentifierType, in TCommand> MMCA.Common.Application 4 @@ -23256,6 +23828,13 @@

Manifest (by level, then assembly)

6 + RefreshSessionRevocation + MMCA.Common.Application + 2 + IRefreshSessionStore, RefreshSession + + + 6 AddressValidationRulesTests MMCA.Common.Application.Tests 11 @@ -23347,6 +23926,13 @@

Manifest (by level, then assembly)

6 + RecordingSetter + MMCA.Common.Application.Tests + 2 + IUpdatePropertySetter<TEntity>, UserNotification + + + 6 RenameOrderByOwnerCommand MMCA.Common.Application.Tests 3 @@ -23426,8 +24012,8 @@

Manifest (by level, then assembly)

6 IntegrationEventContractTestsBaseTests MMCA.Common.Architecture.Tests - 3 - ArchitectureRules, FixtureMap, ProbeTests + 4 + ArchitectureRules, FixtureMap, IntegrationEventContractTestsBase, ProbeTests 6 @@ -23564,13 +24150,6 @@

Manifest (by level, then assembly)

6 - EFReadRepository<TEntity, TIdentifierType> - MMCA.Common.Infrastructure - 17 - AuditableBaseEntity<TIdentifierType>, BaseLookup<TIdentifierType>, EntityQueryPipeline, Error, GroupedCount<TKey>, GroupedSum<TKey>, IReadRepository<TEntity, TIdentifierType>, ISpecification<TEntity, TIdentifierType>, KeysetCollectionResult<T>, KeysetCursor, KeysetPageRequest, KeysetQueryBuilder, LookupRow<TId, TName>, QuerySpecification<TEntity, TIdentifierType>, QueryTags, Result, SpecificationEvaluator - - - 6 EFReadRepositoryDecorator<TEntity, TIdentifierType> MMCA.Common.Infrastructure 8 @@ -23585,13 +24164,6 @@

Manifest (by level, then assembly)

6 - EntityTypeConfiguration<TEntity, TIdentifierType> - MMCA.Common.Infrastructure - 10 - AuditableBaseEntity<TIdentifierType>, CosmosIntIdValueGenerator, DataSource, EntityTypeConfigurationBase<TEntity, TIdentifierType>, IEntityTypeConfigurationCosmos<TEntity, TIdentifierType>, IEntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType>, IEntityTypeConfigurationSqlite<TEntity, TIdentifierType>, IEntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, NamespaceConventions, UseDataSourceAttribute - - - 6 LoginProtectionService MMCA.Common.Infrastructure 6 @@ -23601,8 +24173,8 @@

Manifest (by level, then assembly)

6 PasswordResetTokenService MMCA.Common.Infrastructure - 7 - EmailIdentity, Error, ICacheService, IPasswordResetTokenService, PasswordResetEntry, PasswordResetSettings, Result + 8 + EmailIdentity, Error, ICacheService, IDistributedLock, IPasswordResetTokenService, PasswordResetEntry, PasswordResetSettings, Result 6 @@ -23657,8 +24229,8 @@

Manifest (by level, then assembly)

6 DistributedCacheServiceTests MMCA.Common.Infrastructure.Tests - 3 - CacheKeyNamespace, DistributedCacheService, WarningCountingLogger + 4 + CacheKeyNamespace, DistributedCacheService, ICacheService, WarningCountingLogger 6 @@ -23767,6 +24339,13 @@

Manifest (by level, then assembly)

6 + PermissionGrantCacheTests + MMCA.Common.Infrastructure.Tests + 5 + IPermissionGrantStore, ManualClock, PermissionGrant, PermissionGrantCache, PermissionGrantSettings + + + 6 PhoneNumberValueConverterTests MMCA.Common.Infrastructure.Tests 3 @@ -23788,6 +24367,13 @@

Manifest (by level, then assembly)

6 + RefreshSessionModelBuilderExtensionsTests + MMCA.Common.Infrastructure.Tests + 4 + CustomSchemaContext, RefreshSession, RefreshSessionModelBuilderExtensions, RefreshSessionOnlyContext + + + 6 RegistryUnattributedConfiguration MMCA.Common.Infrastructure.Tests 2 @@ -23797,8 +24383,8 @@

Manifest (by level, then assembly)

6 StoredPermissionRoleAdministrationServiceTests MMCA.Common.Infrastructure.Tests - 8 - AdministrationPermissions, ErrorType, FakeGrantCache, FakeGrantStore, IPermissionGrantCacheInvalidator, PermissionGrantSettings, PermissionRegistryBuilder, StoredPermissionRoleAdministrationService + 11 + AdministrationPermissions, Error, ErrorType, FakeGrantCache, FakeGrantStore, IPermissionGrantCacheInvalidator, IPermissionGrantStore, PermissionGrantSettings, PermissionRegistryBuilder, Result, StoredPermissionRoleAdministrationService 6 @@ -23858,6 +24444,20 @@

Manifest (by level, then assembly)

6 + LoadItemMapper + MMCA.Common.LoadTests + 3 + IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType>, LoadItem, LoadItemDTO + + + 6 + LoadItemNavigationPopulator + MMCA.Common.LoadTests + 3 + INavigationPopulator<in TEntity>, LoadItem, NavigationMetadata + + + 6 EnumerationSerializationTests MMCA.Common.Shared.Tests 5 @@ -23872,6 +24472,13 @@

Manifest (by level, then assembly)

6 + InMemoryRefreshSessionStore + MMCA.Common.Testing + 2 + IRefreshSessionStore, RefreshSession + + + 6 AuthUIService MMCA.Common.UI 19 @@ -23902,8 +24509,8 @@

Manifest (by level, then assembly)

6 Sessions MMCA.Common.UI - 7 - IAuthUIService, IToastService, RefreshSessionSummaryResponse, Result, RoutePaths, SharedResource, UserAgentSummary + 8 + IAppDialogService, IAuthUIService, IToastService, RefreshSessionSummaryResponse, Result, RoutePaths, SharedResource, UserAgentSummary 6 @@ -23930,8 +24537,8 @@

Manifest (by level, then assembly)

6 EntityServiceBaseCachingTests MMCA.Common.UI.Tests - 14 - BaseLookup<TIdentifierType>, CollectionResult<T>, FakeTimeProvider, ITokenStorageService, IUiReadCache, PagedCollectionResult<T>, PaginationMetadata, Result, StubHttpClientFactory, StubHttpMessageHandler, UiReadCache, UiReadCacheOptions, WidgetDto, WidgetService + 15 + BaseLookup<TIdentifierType>, CollectionResult<T>, FakeTimeProvider, GatedGetHandler, ITokenStorageService, IUiReadCache, PagedCollectionResult<T>, PaginationMetadata, Result, StubHttpClientFactory, StubHttpMessageHandler, UiReadCache, UiReadCacheOptions, WidgetDto, WidgetService 6 @@ -23965,8 +24572,8 @@

Manifest (by level, then assembly)

6 PushNotificationServiceTests MMCA.Common.UI.Tests - 12 - ErrorType, ITokenStorageService, Mocks, PagedCollectionResult<T>, PaginationMetadata, ProblemDetailsResultReader, PushNotificationDTO, PushNotificationService, SendPushNotificationRequest, StubHttpClientFactory, StubHttpMessageHandler, StubScopeProvider + 13 + ErrorType, IdempotencyHeaders, ITokenStorageService, Mocks, PagedCollectionResult<T>, PaginationMetadata, ProblemDetailsResultReader, PushNotificationDTO, PushNotificationService, SendPushNotificationRequest, StubHttpClientFactory, StubHttpMessageHandler, StubScopeProvider 6 @@ -24441,8 +25048,8 @@

Manifest (by level, then assembly)

7 CategoryTests MMCA.ADC.Conference.Domain.Tests - 3 - Category, CategoryChanged, DomainEntityState + 4 + Category, CategoryChanged, CategoryInvariants, DomainEntityState 7 @@ -24565,6 +25172,13 @@

Manifest (by level, then assembly)

7 + SessionCreateTests + MMCA.ADC.Conference.UI.Tests + 9 + BunitTestBase, Error, EventInfo, IEventLookupService, IRoomUIService, ISessionUIService, Result, SessionCreate, SessionDTO + + + 7 SessionSelectionAiScoresTests MMCA.ADC.Conference.UI.Tests 6 @@ -24623,8 +25237,8 @@

Manifest (by level, then assembly)

7 TestSupport MMCA.ADC.Engagement.Application.Tests - 5 - AuditableAggregateRootEntity<TIdentifierType>, AuditableBaseEntity<TIdentifierType>, BaseEntity<TIdentifierType>, IReadRepository<TEntity, TIdentifierType>, IRepository<TEntity, TIdentifierType> + 10 + AuditableAggregateRootEntity<TIdentifierType>, AuditableBaseEntity<TIdentifierType>, BaseEntity<TIdentifierType>, EventLiveInfo, IEventLiveValidationService, IReadRepository<TEntity, TIdentifierType>, IRepository<TEntity, TIdentifierType>, QuestionModerationDefault, Result, SessionLiveInfo 7 @@ -24796,6 +25410,13 @@

Manifest (by level, then assembly)

7 + DefaultExportTestController + MMCA.Common.API.Tests + 4 + EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>, ExportTestDTO, ExportTestEntity, IEntityQueryService<TEntity, TEntityDTO, TIdentifierType> + + + 7 ExportShapeTestController MMCA.Common.API.Tests 4 @@ -24957,34 +25578,6 @@

Manifest (by level, then assembly)

7 - EntityTypeConfigurationCosmos<TEntity, TIdentifierType> - MMCA.Common.Infrastructure - 3 - AuditableBaseEntity<TIdentifierType>, DataSource, EntityTypeConfiguration<TEntity, TIdentifierType> - - - 7 - EntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType> - MMCA.Common.Infrastructure - 3 - AuditableBaseEntity<TIdentifierType>, DataSource, EntityTypeConfiguration<TEntity, TIdentifierType> - - - 7 - EntityTypeConfigurationSqlite<TEntity, TIdentifierType> - MMCA.Common.Infrastructure - 3 - AuditableBaseEntity<TIdentifierType>, DataSource, EntityTypeConfiguration<TEntity, TIdentifierType> - - - 7 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> - MMCA.Common.Infrastructure - 3 - AuditableBaseEntity<TIdentifierType>, DataSource, EntityTypeConfiguration<TEntity, TIdentifierType> - - - 7 IRepositoryFactory MMCA.Common.Infrastructure 4 @@ -25008,8 +25601,8 @@

Manifest (by level, then assembly)

7 EmailConfirmationTokenServiceTests MMCA.Common.Infrastructure.Tests - 7 - EmailConfirmationEntry, EmailConfirmationErrors, EmailConfirmationSettings, EmailConfirmationTokenService, ErrorType, FakeConfirmationCacheService, Result + 9 + EmailConfirmationEntry, EmailConfirmationErrors, EmailConfirmationSettings, EmailConfirmationTokenService, ErrorType, FakeConfirmationCacheService, FakeTimeProvider, ICacheService, Result 7 @@ -25036,15 +25629,8 @@

Manifest (by level, then assembly)

7 PasswordResetTokenServiceTests MMCA.Common.Infrastructure.Tests - 6 - ErrorType, FakeCacheService, PasswordResetEntry, PasswordResetSettings, PasswordResetTokenService, Result - - - 7 - PortableThingConfiguration - MMCA.Common.Infrastructure.Tests - 3 - DataSource, EntityTypeConfiguration<TEntity, TIdentifierType>, PortableThing + 9 + ErrorType, FakeCacheService, FakeTimeProvider, ICacheService, InProcessDistributedLock, PasswordResetEntry, PasswordResetSettings, PasswordResetTokenService, Result 7 @@ -25064,8 +25650,8 @@

Manifest (by level, then assembly)

7 DependencyInjection MMCA.Common.UI - 46 - ApiSettings, ApiUserPreferenceReader, ApiUserPreferenceWriter, AuthDelegatingHandler, AuthUIService, CultureDelegatingHandler, DefaultOAuthUISettings, EmailConfirmationUIService, EndpointCultureApplier, HttpResilienceDefaults, IAppDialogService, IAuthUIService, ICultureApplier, IEmailConfirmationUIService, IEntityService<TEntityDTO, TIdentifierType>, IFormFactor, InvariantMudLocalizationInterceptor, IOAuthUISettings, IPublicLinkBuilder, IRoleAdminUIService …(+26) + 47 + ApiSettings, ApiUserPreferenceReader, ApiUserPreferenceWriter, AuthDelegatingHandler, AuthUIService, CultureDelegatingHandler, DefaultOAuthUISettings, EmailConfirmationUIService, EndpointCultureApplier, HttpResilienceDefaults, IAppDialogService, IAuthUIService, ICultureApplier, IEmailConfirmationUIService, IEntityService<TEntityDTO, TIdentifierType>, IFormFactor, InvariantMudLocalizationInterceptor, IOAuthUISettings, IPublicLinkBuilder, IRoleAdminUIService …(+27) 7 @@ -25078,8 +25664,8 @@

Manifest (by level, then assembly)

7 AuthUIServiceTests MMCA.Common.UI.Tests - 14 - AuthenticationResponse, AuthResponse, AuthUIService, ErrorType, HttpResultExecutor, ILocalCacheStore, IPushRegistrationService, ITokenRefresher, ITokenStorageService, JwtAuthenticationStateProvider, LoginRequest, RegisterRequest, StubHttpClientFactory, StubHttpMessageHandler + 15 + AuthenticationResponse, AuthResponse, AuthUIService, ErrorType, HttpResultExecutor, ILocalCacheStore, IPushRegistrationService, ITokenRefresher, ITokenStorageService, Jwt, JwtAuthenticationStateProvider, LoginRequest, RegisterRequest, StubHttpClientFactory, StubHttpMessageHandler 7 @@ -25106,8 +25692,8 @@

Manifest (by level, then assembly)

7 SessionsTests MMCA.Common.UI.Tests - 9 - BunitTestBase, Error, IAuthUIService, IToastService, RefreshSessionSummaryResponse, Result, Sessions, TestPrincipal, ToastSeverity + 10 + BunitTestBase, Error, IAppDialogService, IAuthUIService, IToastService, RefreshSessionSummaryResponse, Result, Sessions, TestPrincipal, ToastSeverity 8 @@ -25295,8 +25881,8 @@

Manifest (by level, then assembly)

8 RefreshFromSessionizeCommand MMCA.ADC.Conference.Application - 5 - ConferenceFeatures, Event, ICacheInvalidating, IFeatureGated, ITransactional + 4 + ConferenceFeatures, Event, ICacheInvalidating, IFeatureGated 8 @@ -25652,85 +26238,8 @@

Manifest (by level, then assembly)

8 SpeakerTests MMCA.ADC.Conference.Domain.Tests - 4 - DomainEntityState, Speaker, SpeakerCategoryItemChanged, SpeakerChanged - - - 8 - CategoryItemConfiguration - MMCA.ADC.Conference.Infrastructure - 3 - CategoryInvariants, CategoryItem, EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> - - - 8 - ConferenceCategoryConfiguration - MMCA.ADC.Conference.Infrastructure - 3 - Category, CategoryInvariants, EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> - - - 8 - EventConfiguration - MMCA.ADC.Conference.Infrastructure - 4 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, Event, EventInvariants, NullableEmailValueConverter - - - 8 - EventQuestionAnswerConfiguration - MMCA.ADC.Conference.Infrastructure - 3 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, EventInvariants, EventQuestionAnswer - - - 8 - EventSpeakerConfiguration - MMCA.ADC.Conference.Infrastructure - 2 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, EventSpeaker - - - 8 - QuestionConfiguration - MMCA.ADC.Conference.Infrastructure - 3 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, Question, QuestionInvariants - - - 8 - RoomConfiguration - MMCA.ADC.Conference.Infrastructure - 3 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, EventInvariants, Room - - - 8 - SessionAiScoreConfiguration - MMCA.ADC.Conference.Infrastructure - 2 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SessionAiScore - - - 8 - SpeakerCategoryItemConfiguration - MMCA.ADC.Conference.Infrastructure - 2 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SpeakerCategoryItem - - - 8 - SpeakerConfiguration - MMCA.ADC.Conference.Infrastructure - 4 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, NullableEmailValueConverter, Speaker, SpeakerInvariants - - - 8 - SpeakerQuestionAnswerConfiguration - MMCA.ADC.Conference.Infrastructure - 3 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SpeakerInvariants, SpeakerQuestionAnswer + 5 + DomainEntityState, Speaker, SpeakerCategoryItemChanged, SpeakerChanged, SpeakerInvariants 8 @@ -25765,7 +26274,7 @@

Manifest (by level, then assembly)

PublicEventDetail MMCA.ADC.Conference.UI 13 - ConferenceReadAudience, ConferenceRoutePaths, Event, EventDTO, EventService, IClipboardService, IEventUIService, IGeocodingService, IGeolocationService, IMapNavigationService, IToastService, LatestLoadGuard, ToastSeverity + ConferenceRoutePaths, Event, EventDTO, EventService, IClipboardService, IEventUIService, IGeocodingService, IGeolocationService, IMapNavigationService, IToastService, LatestLoadGuard, PublicReadAudience, ToastSeverity 8 @@ -25855,8 +26364,22 @@

Manifest (by level, then assembly)

8 ToggleUpvoteHandler MMCA.ADC.Engagement.Application - 9 - Error, ICommandHandler<in TCommand, TResult>, IEntityReader<TEntity, TIdentifierType>, IRepository<TEntity, TIdentifierType>, IUnitOfWork, Result, SessionQuestion, SessionQuestionUpvote, ToggleUpvoteCommand + 11 + Error, ICommandHandler<in TCommand, TResult>, IEntityReader<TEntity, TIdentifierType>, IEventLiveValidationService, IRepository<TEntity, TIdentifierType>, IUniqueConstraintViolationDetector, IUnitOfWork, Result, SessionQuestion, SessionQuestionUpvote, ToggleUpvoteCommand + + + 8 + UserDeletedBadgeHandler + MMCA.ADC.Engagement.Application + 4 + AttendeeBadge, IUnitOfWork, ScopedIntegrationEventHandlerBase<TIntegrationEvent>, UserDeleted + + + 8 + UserDeletedBookmarksHandler + MMCA.ADC.Engagement.Application + 4 + IUnitOfWork, ScopedIntegrationEventHandlerBase<TIntegrationEvent>, UserDeleted, UserSessionBookmark 8 @@ -25867,6 +26390,20 @@

Manifest (by level, then assembly)

8 + UserDeletedSessionQuestionsHandler + MMCA.ADC.Engagement.Application + 5 + IUnitOfWork, ScopedIntegrationEventHandlerBase<TIntegrationEvent>, SessionQuestion, SessionQuestionUpvote, UserDeleted + + + 8 + UserDeletedVotesHandler + MMCA.ADC.Engagement.Application + 4 + IUnitOfWork, LivePollVote, ScopedIntegrationEventHandlerBase<TIntegrationEvent>, UserDeleted + + + 8 UserSessionBookmarkDTOMapper MMCA.ADC.Engagement.Application 3 @@ -25979,55 +26516,6 @@

Manifest (by level, then assembly)

8 - AttendeeBadgeConfiguration - MMCA.ADC.Engagement.Infrastructure - 2 - AttendeeBadge, EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> - - - 8 - LeaderboardOptInConfiguration - MMCA.ADC.Engagement.Infrastructure - 2 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, LeaderboardOptIn - - - 8 - LivePollVoteConfiguration - MMCA.ADC.Engagement.Infrastructure - 2 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, LivePollVote - - - 8 - PointsEntryConfiguration - MMCA.ADC.Engagement.Infrastructure - 3 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, PointsEntry, PointsSubjectKeys - - - 8 - SessionQuestionConfiguration - MMCA.ADC.Engagement.Infrastructure - 3 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SessionQuestion, SessionQuestionInvariants - - - 8 - SessionQuestionUpvoteConfiguration - MMCA.ADC.Engagement.Infrastructure - 2 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SessionQuestionUpvote - - - 8 - UserSessionBookmarkConfiguration - MMCA.ADC.Engagement.Infrastructure - 2 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, UserSessionBookmark - - - 8 PointsSettingsTests MMCA.ADC.Engagement.Shared.Tests 5 @@ -26238,13 +26726,6 @@

Manifest (by level, then assembly)

8 - UserConfiguration - MMCA.ADC.Identity.Infrastructure - 4 - EmailValueConverter, EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, User, UserInvariants - - - 8 SeederMocks MMCA.ADC.Identity.Infrastructure.Tests 4 @@ -26296,8 +26777,8 @@

Manifest (by level, then assembly)

8 EntityControllerBaseExportTests MMCA.Common.API.Tests - 15 - ApplicationSettings, EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>, Error, ExportTestController, ExportTestDTO, ExportTestEntity, FakeTimeProvider, IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, InlineSpecification<TEntity, TIdentifierType>, PagedCollectionResult<T>, PaginationMetadata, Result, ScopedExportTestController, Specification<TEntity, TIdentifierType>, SpecificationHonoringQueryService + 16 + ApplicationSettings, DefaultExportTestController, EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>, Error, ExportTestController, ExportTestDTO, ExportTestEntity, FakeTimeProvider, IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, InlineSpecification<TEntity, TIdentifierType>, PagedCollectionResult<T>, PaginationMetadata, Result, ScopedExportTestController, Specification<TEntity, TIdentifierType>, SpecificationHonoringQueryService 8 @@ -26315,20 +26796,6 @@

Manifest (by level, then assembly)

8 - InitTestMigratedWidgetConfiguration - MMCA.Common.API.Tests - 2 - EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, InitTestMigratedWidget - - - 8 - InitTestWidgetConfiguration - MMCA.Common.API.Tests - 2 - EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, InitTestWidget - - - 8 OAuthControllerBaseTests MMCA.Common.API.Tests 10 @@ -26352,29 +26819,29 @@

Manifest (by level, then assembly)

8 AuthenticationServiceBase<TUser> MMCA.Common.Application - 29 - AuditableAggregateRootEntity<TIdentifierType>, AuthClaimTypes, AuthenticationResponse, AuthenticationValidators, AuthErrorCodes, Email, EmailConfirmationErrors, EmailConfirmationSettings, Error, IAuthenticationService, IAuthUser, IEmailConfirmableUser, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, IRepository<TEntity, TIdentifierType>, IssuedSession, ITokenService, ITwoFactorAuthenticator, IUnitOfWork …(+9) + 27 + AuditableAggregateRootEntity<TIdentifierType>, AuthClaimTypes, AuthenticationResponse, AuthenticationValidators, AuthErrorCodes, Email, EmailConfirmationErrors, EmailConfirmationSettings, Error, IAuthenticationService, IAuthSessionIssuer, IAuthUser, IEmailConfirmableUser, ILoginProtectionService, IPasswordHasher, IRepository<TEntity, TIdentifierType>, ITokenService, ITwoFactorAuthenticator, IUnitOfWork, LoginRequest …(+7) 8 ChangePasswordHandlerBase<TUser, TCommand> MMCA.Common.Application 12 - AuditableAggregateRootEntity<TIdentifierType>, ChangePasswordRequest, Error, ICommandHandler<in TCommand, TResult>, IPasswordChangeableUser, IPasswordHasher, IRefreshSessionStore, IUnitOfWork, IUserScopedCommand<out TRequest>, RefreshSessionRevocation, Result, UserUseCaseLog + AuditableAggregateRootEntity<TIdentifierType>, ChangePasswordRequest, Error, ICommandHandler<in TCommand, TResult>, ILoginProtectionService, IPasswordChangeableUser, IPasswordHasher, IRefreshSessionStore, IUnitOfWork, IUserScopedCommand<out TRequest>, RefreshSessionRevocation, Result 8 ChangePreferencesHandlerBase<TUser, TCommand> MMCA.Common.Application - 9 - AuditableAggregateRootEntity<TIdentifierType>, ChangePreferencesRequest, Error, ICommandHandler<in TCommand, TResult>, IUnitOfWork, IUserPreferences, IUserScopedCommand<out TRequest>, Result, UserUseCaseLog + 8 + AuditableAggregateRootEntity<TIdentifierType>, ChangePreferencesRequest, Error, ICommandHandler<in TCommand, TResult>, IUnitOfWork, IUserPreferences, IUserScopedCommand<out TRequest>, Result 8 ConfirmEmailHandlerBase<TUser, TCommand> MMCA.Common.Application - 10 - AuditableAggregateRootEntity<TIdentifierType>, ConfirmEmailRequest, EmailConfirmationErrors, ICommandHandler<in TCommand, TResult>, ICommandWithRequest<out TRequest>, IEmailConfirmableUser, IEmailConfirmationTokenService, IUnitOfWork, Result, UserUseCaseLog + 9 + AuditableAggregateRootEntity<TIdentifierType>, ConfirmEmailRequest, EmailConfirmationErrors, ICommandHandler<in TCommand, TResult>, ICommandWithRequest<out TRequest>, IEmailConfirmableUser, IEmailConfirmationTokenService, IUnitOfWork, Result 8 @@ -26401,8 +26868,8 @@

Manifest (by level, then assembly)

8 DeleteUserHandlerBase<TUser, TCommand> MMCA.Common.Application - 11 - AuditableAggregateRootEntity<TIdentifierType>, Error, ICacheService, ICommandHandler<in TCommand, TResult>, IErasableUser, IUnitOfWork, IUserOwnedRequest, Result, SoftDeletedUserCache, UserOwnershipRule, UserUseCaseLog + 10 + AuditableAggregateRootEntity<TIdentifierType>, Error, ICacheService, ICommandHandler<in TCommand, TResult>, IErasableUser, IUnitOfWork, IUserOwnedRequest, Result, SoftDeletedUserCache, UserOwnershipRule 8 @@ -26415,15 +26882,15 @@

Manifest (by level, then assembly)

8 ExportUserDataHandlerBase<TUser, TQuery> MMCA.Common.Application - 13 - AuditableAggregateRootEntity<TIdentifierType>, Error, IQueryHandler<in TQuery, TResult>, IUnitOfWork, IUserDataExportSection, IUserOwnedRequest, Result, Subject, UserDataExportDTO, UserDataExportSectionDefaults, UserDataExportSectionDTO, UserOwnershipRule, UserUseCaseLog + 12 + AuditableAggregateRootEntity<TIdentifierType>, Error, IQueryHandler<in TQuery, TResult>, IUnitOfWork, IUserDataExportSection, IUserOwnedRequest, Result, Subject, UserDataExportDTO, UserDataExportSectionDefaults, UserDataExportSectionDTO, UserOwnershipRule 8 ForgotPasswordHandlerBase<TUser, TCommand> MMCA.Common.Application - 11 - AuditableAggregateRootEntity<TIdentifierType>, Email, ForgotPasswordRequest, ICommandHandler<in TCommand, TResult>, ICommandWithRequest<out TRequest>, IEmailSender, IPasswordResetTokenService, IUnitOfWork, PasswordResetSettings, Result, UserUseCaseLog + 10 + AuditableAggregateRootEntity<TIdentifierType>, Email, ForgotPasswordRequest, ICommandHandler<in TCommand, TResult>, ICommandWithRequest<out TRequest>, IEmailSender, IPasswordResetTokenService, IUnitOfWork, PasswordResetSettings, Result 8 @@ -26464,8 +26931,8 @@

Manifest (by level, then assembly)

8 MarkAllNotificationsReadHandler MMCA.Common.Application - 7 - ICommandHandler<in TCommand, TResult>, IQueryableExecutor, IUnitOfWork, MarkAllNotificationsReadCommand, PushNotification, Result, UserNotification + 6 + ICommandHandler<in TCommand, TResult>, IUnitOfWork, MarkAllNotificationsReadCommand, PushNotification, Result, UserNotification 8 @@ -26499,15 +26966,15 @@

Manifest (by level, then assembly)

8 ResetPasswordHandlerBase<TUser, TCommand> MMCA.Common.Application - 14 - AuditableAggregateRootEntity<TIdentifierType>, Error, ICommandHandler<in TCommand, TResult>, ICommandWithRequest<out TRequest>, ILoginProtectionService, IPasswordChangeableUser, IPasswordHasher, IPasswordResetTokenService, IRefreshSessionStore, IUnitOfWork, RefreshSessionRevocation, ResetPasswordRequest, Result, UserUseCaseLog + 13 + AuditableAggregateRootEntity<TIdentifierType>, Error, ICommandHandler<in TCommand, TResult>, ICommandWithRequest<out TRequest>, ILoginProtectionService, IPasswordChangeableUser, IPasswordHasher, IPasswordResetTokenService, IRefreshSessionStore, IUnitOfWork, RefreshSessionRevocation, ResetPasswordRequest, Result 8 SendEmailConfirmationHandlerBase<TUser, TCommand> MMCA.Common.Application - 12 - AuditableAggregateRootEntity<TIdentifierType>, Email, EmailConfirmationSettings, ICommandHandler<in TCommand, TResult>, ICommandWithRequest<out TRequest>, IEmailConfirmableUser, IEmailConfirmationTokenService, IEmailSender, IUnitOfWork, Result, SendEmailConfirmationRequest, UserUseCaseLog + 11 + AuditableAggregateRootEntity<TIdentifierType>, Email, EmailConfirmationSettings, ICommandHandler<in TCommand, TResult>, ICommandWithRequest<out TRequest>, IEmailConfirmableUser, IEmailConfirmationTokenService, IEmailSender, IUnitOfWork, Result, SendEmailConfirmationRequest 8 @@ -26623,55 +27090,6 @@

Manifest (by level, then assembly)

8 - PushNotificationConfiguration - MMCA.Common.Infrastructure - 3 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, PushNotification, PushNotificationInvariants - - - 8 - UserNotificationConfiguration - MMCA.Common.Infrastructure - 2 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, UserNotification - - - 8 - PgThingConfiguration - MMCA.Common.Infrastructure.PostgreSQL.Tests - 2 - EntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType>, PgThing - - - 8 - DesignAlphaEntityConfiguration - MMCA.Common.Infrastructure.Tests - 2 - DesignAlphaEntity, EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> - - - 8 - DesignBetaEntityConfiguration - MMCA.Common.Infrastructure.Tests - 2 - DesignBetaEntity, EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> - - - 8 - DesignPostgreSQLEntityConfiguration - MMCA.Common.Infrastructure.Tests - 2 - DesignPostgreSQLEntity, EntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType> - - - 8 - DesignSqliteEntityConfiguration - MMCA.Common.Infrastructure.Tests - 2 - DesignSqliteEntity, EntityTypeConfigurationSqlite<TEntity, TIdentifierType> - - - 8 EFRepositoryDecoratorAdditionalTests MMCA.Common.Infrastructure.Tests 3 @@ -26693,20 +27111,6 @@

Manifest (by level, then assembly)

8 - MultiSourceCustomerConfiguration - MMCA.Common.Infrastructure.Tests - 2 - EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, MultiSourceCustomer - - - 8 - MultiSourceOrderConfiguration - MMCA.Common.Infrastructure.Tests - 2 - EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, MultiSourceOrder - - - 8 NotificationTestDbContext MMCA.Common.Infrastructure.Tests 2 @@ -26714,20 +27118,6 @@

Manifest (by level, then assembly)

8 - PortablePrincipalConfiguration - MMCA.Common.Infrastructure.Tests - 2 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, PortablePrincipal - - - 8 - PostgresThingConfiguration - MMCA.Common.Infrastructure.Tests - 2 - EntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType>, PostgresThing - - - 8 ProjectionTestDbContext MMCA.Common.Infrastructure.Tests 1 @@ -26735,41 +27125,6 @@

Manifest (by level, then assembly)

8 - RegistryDuplicateConfigurationA - MMCA.Common.Infrastructure.Tests - 2 - EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, RegistryDuplicate - - - 8 - RegistryDuplicateConfigurationB - MMCA.Common.Infrastructure.Tests - 2 - EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, RegistryDuplicate - - - 8 - RegistryInvoiceConfiguration - MMCA.Common.Infrastructure.Tests - 2 - EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, RegistryInvoice - - - 8 - RegistryOrderConfiguration - MMCA.Common.Infrastructure.Tests - 2 - EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, RegistryOrder - - - 8 - RegistrySqlServerEntityConfiguration - MMCA.Common.Infrastructure.Tests - 2 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, RegistrySqlServerEntity - - - 8 SeederMocks MMCA.Common.Infrastructure.Tests 4 @@ -26777,20 +27132,6 @@

Manifest (by level, then assembly)

8 - SqliteTestEntityConfig - MMCA.Common.Infrastructure.Tests - 2 - EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, SqliteTestEntity - - - 8 - SqlServerThingConfiguration - MMCA.Common.Infrastructure.Tests - 2 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SqlServerThing - - - 8 TestConnectionContext MMCA.Common.Infrastructure.Tests 2 @@ -26805,6 +27146,13 @@

Manifest (by level, then assembly)

8 + NotificationPageGate + MMCA.Common.UI + 6 + IUIModule, LayoutSettings, NotificationInbox, NotificationList, NotificationSend, NotificationUIModule + + + 8 GalleryAuthenticationStateProvider MMCA.Common.UI.Gallery 1 @@ -26885,7 +27233,7 @@

Manifest (by level, then assembly)

SpeakersController MMCA.ADC.Conference.API 24 - AggregateRootEntityControllerBase<TEntity, TEntityDTO, TIdentifierType, TCreateRequest>, BaseLookup<TIdentifierType>, CollectionResult<T>, ConferencePermissions, DeleteEntityCommand<TEntity, TIdentifierType>, Error, GetPublicSpeakerFilterQuery, GetSpeakersByEventFilterQuery, ICommandHandler<in TCommand, TResult>, ICurrentUserService, IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, IQueryHandler<in TQuery, TResult>, PagedCollectionResult<T>, QueryFilterModelBinder, Result, RoleNames, Route, Speaker, SpeakerCreateRequest, SpeakerDTO …(+4) + AggregateRootEntityControllerBase<TEntity, TEntityDTO, TIdentifierType, TCreateRequest>, BaseLookup<TIdentifierType>, CollectionResult<T>, ConferencePermissions, DeleteEntityCommand<TEntity, TIdentifierType>, Error, GetPublicSpeakerFilterQuery, GetSpeakersByEventFilterQuery, ICommandHandler<in TCommand, TResult>, ICurrentUserService, IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, IPermissionRegistry, IQueryHandler<in TQuery, TResult>, PagedCollectionResult<T>, QueryFilterModelBinder, Result, Route, Speaker, SpeakerCreateRequest, SpeakerDTO …(+4) 9 @@ -26898,15 +27246,15 @@

Manifest (by level, then assembly)

9 CategoryItemsControllerTests MMCA.ADC.Conference.API.Tests - 12 - AddCategoryItemCommand, AddCategoryItemRequest, CategoryItem, CategoryItemDTO, CategoryItemsController, Error, ICommandHandler<in TCommand, TResult>, IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, RemoveCategoryItemCommand, Result, UpdateCategoryItemCommand, UpdateCategoryItemRequest + 14 + AddCategoryItemCommand, AddCategoryItemRequest, CategoryItem, CategoryItemDTO, CategoryItemsController, Error, ICommandHandler<in TCommand, TResult>, IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, ISpecification<TEntity, TIdentifierType>, PagedCollectionResult<T>, RemoveCategoryItemCommand, Result, UpdateCategoryItemCommand, UpdateCategoryItemRequest 9 ConferenceCategoriesControllerTests MMCA.ADC.Conference.API.Tests - 12 - Category, ConferenceCategoriesController, ConferenceCategoryCreateRequest, ConferenceCategoryDTO, ConferenceCategoryUpdateRequest, DeleteEntityCommand<TEntity, TIdentifierType>, Error, ICommandHandler<in TCommand, TResult>, IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, Result, SupportsIfMatchAttribute, UpdateEntityCommand<TEntity, TUpdateRequest, TIdentifierType> + 14 + Category, ConferenceCategoriesController, ConferenceCategoryCreateRequest, ConferenceCategoryDTO, ConferenceCategoryUpdateRequest, DeleteEntityCommand<TEntity, TIdentifierType>, Error, ICommandHandler<in TCommand, TResult>, IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, ISpecification<TEntity, TIdentifierType>, PagedCollectionResult<T>, Result, SupportsIfMatchAttribute, UpdateEntityCommand<TEntity, TUpdateRequest, TIdentifierType> 9 @@ -27442,6 +27790,13 @@

Manifest (by level, then assembly)

9 + UserDeletedFeedbackHandler + MMCA.ADC.Conference.Application + 5 + Event, IUnitOfWork, ScopedIntegrationEventHandlerBase<TIntegrationEvent>, Session, UserDeleted + + + 9 AddCategoryItemCommandValidatorTests MMCA.ADC.Conference.Application.Tests 3 @@ -27645,13 +28000,6 @@

Manifest (by level, then assembly)

9 - ActivityConfiguration - MMCA.ADC.Conference.Infrastructure - 3 - Activity, ActivityInvariants, EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> - - - 9 ConferenceModuleDbSeeder MMCA.ADC.Conference.Infrastructure 14 @@ -27659,55 +28007,6 @@

Manifest (by level, then assembly)

9 - PartnerConfiguration - MMCA.ADC.Conference.Infrastructure - 3 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, Partner, PartnerInvariants - - - 9 - SessionAssetConfiguration - MMCA.ADC.Conference.Infrastructure - 5 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, Event, Session, SessionAsset, SessionAssetInvariants - - - 9 - SessionCategoryItemConfiguration - MMCA.ADC.Conference.Infrastructure - 2 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SessionCategoryItem - - - 9 - SessionConfiguration - MMCA.ADC.Conference.Infrastructure - 3 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, Session, SessionInvariants - - - 9 - SessionQuestionAnswerConfiguration - MMCA.ADC.Conference.Infrastructure - 3 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SessionInvariants, SessionQuestionAnswer - - - 9 - SessionSpeakerConfiguration - MMCA.ADC.Conference.Infrastructure - 2 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SessionSpeaker - - - 9 - SponsorConfiguration - MMCA.ADC.Conference.Infrastructure - 3 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, Sponsor, SponsorInvariants - - - 9 ConferenceTestDbContext MMCA.ADC.Conference.Infrastructure.Tests 14 @@ -27788,7 +28087,7 @@

Manifest (by level, then assembly)

PublicEventList MMCA.ADC.Conference.UI 13 - ConferenceReadAudience, ConferenceRoutePaths, CurrentEventSelector, DataGridListPageBase<TDto>, EventDTO, EventInfo, EventLookupService, EventService, IEventLookupService, IEventUIService, ListPageActions, MobileInfiniteScrollList<TItem>, Result + ConferenceRoutePaths, CurrentEventSelector, DataGridListPageBase<TDto>, EventDTO, EventInfo, EventLookupService, EventService, IEventLookupService, IEventUIService, ListPageActions, MobileInfiniteScrollList<TItem>, PublicReadAudience, Result 9 @@ -27808,8 +28107,8 @@

Manifest (by level, then assembly)

9 SessionDetail MMCA.ADC.Conference.UI - 26 - CategoryItemInfo, CategoryItemLookupService, ConferenceRoutePaths, DataAnnotationsModelValidator, ErrorMessages, EventLookupService, ICategoryItemLookupService, IEventLookupService, IRoomUIService, ISessionCategoryItemUIService, ISessionSpeakerUIService, ISessionUIService, ISpeakerLookupService, IToastService, ModelValidation, Result, RoomService, Session, SessionCategoryItemService, SessionDTO …(+6) + 27 + CategoryItemInfo, CategoryItemLookupService, ConferenceRoutePaths, DataAnnotationsModelValidator, ErrorMessages, EventLookupService, ICategoryItemLookupService, IEventLookupService, IRoomUIService, ISessionCategoryItemUIService, ISessionSpeakerUIService, ISessionUIService, ISpeakerLookupService, IToastService, ModelValidation, Result, RoomService, Session, SessionCategoryItemService, SessionDTO …(+7) 9 @@ -27822,8 +28121,8 @@

Manifest (by level, then assembly)

9 SpeakerDashboard MMCA.ADC.Conference.UI - 12 - CurrentEventSelector, Email, EventInfo, EventLookupService, IEventLookupService, ISpeakerDashboardUIService, ISpeakerUIService, IToastService, SessionDTO, SessionFeedbackDTO, SpeakerDTO, SpeakerService + 11 + CurrentEventSelector, EventInfo, EventLookupService, IEventLookupService, ISpeakerDashboardUIService, ISpeakerUIService, IToastService, SessionDTO, SessionFeedbackDTO, SpeakerDTO, SpeakerService 9 @@ -27850,8 +28149,8 @@

Manifest (by level, then assembly)

9 EventDetailTests MMCA.ADC.Conference.UI.Tests - 6 - BunitTestBase, EventDetail, EventDTO, IEventUIService, QuestionModerationDefault, Result + 8 + BunitTestBase, Error, EventDetail, EventDTO, IEventUIService, QuestionModerationDefault, Result, SessionizeRefreshOutcome 9 @@ -27885,8 +28184,8 @@

Manifest (by level, then assembly)

9 RoomDetailTests MMCA.ADC.Conference.UI.Tests - 6 - BunitTestBase, EventInfo, IEventLookupService, IRoomUIService, RoomDetail, RoomDTO + 7 + BunitTestBase, EventInfo, IEventLookupService, IRoomUIService, Result, RoomDetail, RoomDTO 9 @@ -27962,8 +28261,8 @@

Manifest (by level, then assembly)

9 GetSessionQuestionsHandler MMCA.ADC.Engagement.Application - 10 - GetSessionQuestionsQuery, IQueryableExecutor, IQueryHandler<in TQuery, TResult>, IUnitOfWork, QuestionStatus, Result, SessionQuestion, SessionQuestionDTO, SessionQuestionUpvote, SessionQuestionViewBuilder + 11 + GetSessionQuestionsQuery, IEventLiveValidationService, IQueryableExecutor, IQueryHandler<in TQuery, TResult>, IUnitOfWork, QuestionStatus, Result, SessionQuestion, SessionQuestionDTO, SessionQuestionUpvote, SessionQuestionViewBuilder 9 @@ -28051,20 +28350,6 @@

Manifest (by level, then assembly)

9 - LivePollConfiguration - MMCA.ADC.Engagement.Infrastructure - 3 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, LivePoll, LivePollInvariants - - - 9 - LivePollOptionConfiguration - MMCA.ADC.Engagement.Infrastructure - 3 - EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, LivePollInvariants, LivePollOption - - - 9 BookmarkCountsGrpcService MMCA.ADC.Engagement.Service 2 @@ -28116,8 +28401,8 @@

Manifest (by level, then assembly)

9 ChangePasswordHandler MMCA.ADC.Identity.Application - 6 - ChangePasswordCommand, ChangePasswordHandlerBase<TUser, TCommand>, IPasswordHasher, IRefreshSessionStore, IUnitOfWork, User + 7 + ChangePasswordCommand, ChangePasswordHandlerBase<TUser, TCommand>, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, IUnitOfWork, User 9 @@ -28205,13 +28490,6 @@

Manifest (by level, then assembly)

9 - IdentityTestDbContext - MMCA.ADC.Identity.Infrastructure.Tests - 4 - DataSource, SoftDeleteUniqueIndexConvention, User, UserConfiguration - - - 9 AttendeesGrpcService MMCA.ADC.Identity.Service 2 @@ -28235,15 +28513,15 @@

Manifest (by level, then assembly)

9 UserNotificationExportServiceGrpcAdapter MMCA.ADC.Notification.Contracts - 3 - IUserNotificationExportService, UserNotificationExportItemDTO, UserNotificationExportService + 4 + GrpcWireFormat, IUserNotificationExportService, UserNotificationExportItemDTO, UserNotificationExportService 9 UserNotificationExportGrpcService MMCA.ADC.Notification.Service - 2 - IUserNotificationExportService, UserNotificationExportService + 3 + GrpcWireFormat, IUserNotificationExportService, UserNotificationExportService 9 @@ -28298,8 +28576,15 @@

Manifest (by level, then assembly)

9 OwnershipHelper MMCA.Common.API - 1 - ICurrentUserService + 3 + Error, ICurrentUserService, Result + + + 9 + SessionRefreshOutcome + MMCA.Common.API + 3 + Session, SessionRefreshStatus, SessionTokenResult 9 @@ -28424,8 +28709,8 @@

Manifest (by level, then assembly)

9 ConfirmableAuthenticationService MMCA.Common.Application.Tests - 15 - AuthenticationServiceBase<TUser>, AuthenticationValidators, ConfirmableAuthUser, Email, EmailConfirmationSettings, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, ITokenService, ITwoFactorAuthenticator, IUnitOfWork, RefreshSessionSettings, RegisterRequest, Result, TokenService + 16 + AuthenticationServiceBase<TUser>, AuthenticationValidators, AuthSessionIssuer, ConfirmableAuthUser, Email, EmailConfirmationSettings, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, ITokenService, ITwoFactorAuthenticator, IUnitOfWork, RefreshSessionSettings, RegisterRequest, Result, TokenService 9 @@ -28478,13 +28763,6 @@

Manifest (by level, then assembly)

9 - MarkAllNotificationsReadHandlerTests - MMCA.Common.Application.Tests - 10 - FixedTimeProvider, HandlerMocks, IQueryableExecutor, IRepository<TEntity, TIdentifierType>, IUnitOfWork, MarkAllNotificationsReadCommand, MarkAllNotificationsReadHandler, PushNotification, Result, UserNotification - - - 9 MarkNotificationReadHandlerTests MMCA.Common.Application.Tests 9 @@ -28529,8 +28807,8 @@

Manifest (by level, then assembly)

9 SessionAwareAuthenticationService MMCA.Common.Application.Tests - 13 - AuthenticationServiceBase<TUser>, AuthenticationValidators, Email, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, ITokenService, IUnitOfWork, RefreshSessionSettings, RegisterRequest, Result, TestAuthUser, TokenService + 14 + AuthenticationServiceBase<TUser>, AuthenticationValidators, AuthSessionIssuer, Email, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, ITokenService, IUnitOfWork, RefreshSessionSettings, RegisterRequest, Result, TestAuthUser, TokenService 9 @@ -28557,15 +28835,15 @@

Manifest (by level, then assembly)

9 TestAuthenticationService MMCA.Common.Application.Tests - 12 - AuthenticationServiceBase<TUser>, AuthenticationValidators, Email, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, ITokenService, IUnitOfWork, RefreshSessionSettings, RegisterRequest, Result, TestAuthUser + 13 + AuthenticationServiceBase<TUser>, AuthenticationValidators, AuthSessionIssuer, Email, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, ITokenService, IUnitOfWork, RefreshSessionSettings, RegisterRequest, Result, TestAuthUser 9 TestChangePasswordHandler MMCA.Common.Application.Tests - 6 - ChangePasswordHandlerBase<TUser, TCommand>, IPasswordHasher, IRefreshSessionStore, IUnitOfWork, TestChangePasswordCommand, TestIdentityUser + 7 + ChangePasswordHandlerBase<TUser, TCommand>, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, IUnitOfWork, TestChangePasswordCommand, TestIdentityUser 9 @@ -28702,17 +28980,17 @@

Manifest (by level, then assembly)

9 - EFRepository<TEntity, TIdentifierType> + ImpersonatingCurrentUserService MMCA.Common.Infrastructure - 9 - AuditableAggregateRootEntity<TIdentifierType>, AuditableBaseEntity<TIdentifierType>, EFReadRepository<TEntity, TIdentifierType>, IAuditableEntity, ICurrentUserService, IRepository<TEntity, TIdentifierType>, IRowVersioned, IUpdatePropertySetter<TEntity>, UpdatePropertySetterBuilder<TEntity> + 2 + ICurrentUserService, ScopedUserOverride 9 - ImpersonatingCurrentUserService + InternalCommandOriginCapture MMCA.Common.Infrastructure - 2 - ICurrentUserService, ScopedUserOverride + 6 + Activity, AmbientOrigin, ICorrelationContext, ICurrentUserService, InternalCommandOrigin, ITenantContext 9 @@ -28730,6 +29008,13 @@

Manifest (by level, then assembly)

9 + FixedCurrentUserService + MMCA.Common.Infrastructure.SQLServer.Tests + 1 + ICurrentUserService + + + 9 AnonymousCurrentUserService MMCA.Common.Infrastructure.Tests 1 @@ -28786,13 +29071,6 @@

Manifest (by level, then assembly)

9 - PushNotificationTestDbContext - MMCA.Common.Infrastructure.Tests - 1 - PushNotificationConfiguration - - - 9 RecordingCommandHandler MMCA.Common.Infrastructure.Tests 7 @@ -28807,13 +29085,6 @@

Manifest (by level, then assembly)

9 - SqliteTestDbContext - MMCA.Common.Infrastructure.Tests - 2 - SqliteTestEntity, SqliteTestEntityConfig - - - 9 StubCurrentUserService MMCA.Common.Infrastructure.Tests 1 @@ -28827,6 +29098,13 @@

Manifest (by level, then assembly)

GalleryAuthenticationStateProvider, GalleryFakeAuthenticationHandler, GalleryUIModule, IAuthUIService, INotificationInboxUIService, INotificationScopeProvider, IPushNotificationUIService, ITokenRefresher, ITokenStorageService, IUIModule, NoOpAuthUIService, NotificationState, NullNotificationScopeProvider, NullTokenRefresher, NullTokenStorageService, StubNotificationInboxUIService, StubPushNotificationUIService, SupportedCultures + 9 + NotificationPageGateTests + MMCA.Common.UI.Tests + 7 + LayoutSettings, NotificationInbox, NotificationList, NotificationPageGate, NotificationSend, NotificationUIModule, OtherModule + + 10 ActivitiesController MMCA.ADC.Conference.API @@ -28844,8 +29122,8 @@

Manifest (by level, then assembly)

10 EventQuestionAnswersController MMCA.ADC.Conference.API - 25 - AddEventQuestionAnswerCommand, AddEventQuestionAnswerRequest, BaseLookup<TIdentifierType>, BatchAddEventQuestionAnswersCommand, BatchAddEventQuestionAnswersRequest, BatchEventQuestionAnswerItem, CollectionResult<T>, EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>, Error, EventQuestionAnswer, EventQuestionAnswerDTO, ICommandHandler<in TCommand, TResult>, ICurrentUserService, IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, OwnedByUserSpecification<TEntity, TIdentifierType>, OwnershipHelper, PagedCollectionResult<T>, QueryFilterModelBinder, RemoveEventQuestionAnswerCommand, Result …(+5) + 24 + AddEventQuestionAnswerCommand, AddEventQuestionAnswerRequest, BaseLookup<TIdentifierType>, BatchAddEventQuestionAnswersCommand, BatchAddEventQuestionAnswersRequest, BatchEventQuestionAnswerItem, CollectionResult<T>, EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>, EventQuestionAnswer, EventQuestionAnswerDTO, ICommandHandler<in TCommand, TResult>, ICurrentUserService, IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, OwnedByUserSpecification<TEntity, TIdentifierType>, OwnershipHelper, PagedCollectionResult<T>, QueryFilterModelBinder, RemoveEventQuestionAnswerCommand, Result, RoleNames …(+4) 10 @@ -28872,8 +29150,8 @@

Manifest (by level, then assembly)

10 SessionQuestionAnswersController MMCA.ADC.Conference.API - 25 - AddSessionQuestionAnswerCommand, AddSessionQuestionAnswerRequest, BaseLookup<TIdentifierType>, BatchAddSessionQuestionAnswersCommand, BatchAddSessionQuestionAnswersRequest, BatchSessionQuestionAnswerItem, CollectionResult<T>, EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>, Error, ICommandHandler<in TCommand, TResult>, ICurrentUserService, IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, OwnedByUserSpecification<TEntity, TIdentifierType>, OwnershipHelper, PagedCollectionResult<T>, QueryFilterModelBinder, RemoveSessionQuestionAnswerCommand, Result, RoleNames, Route …(+5) + 24 + AddSessionQuestionAnswerCommand, AddSessionQuestionAnswerRequest, BaseLookup<TIdentifierType>, BatchAddSessionQuestionAnswersCommand, BatchAddSessionQuestionAnswersRequest, BatchSessionQuestionAnswerItem, CollectionResult<T>, EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>, ICommandHandler<in TCommand, TResult>, ICurrentUserService, IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, OwnedByUserSpecification<TEntity, TIdentifierType>, OwnershipHelper, PagedCollectionResult<T>, QueryFilterModelBinder, RemoveSessionQuestionAnswerCommand, Result, RoleNames, Route, SessionQuestionAnswer …(+4) 10 @@ -28914,8 +29192,8 @@

Manifest (by level, then assembly)

10 QuestionsControllerTests MMCA.ADC.Conference.API.Tests - 12 - DeleteEntityCommand<TEntity, TIdentifierType>, Error, ICommandHandler<in TCommand, TResult>, IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, Question, QuestionCreateRequest, QuestionDTO, QuestionsController, QuestionUpdateRequest, Result, SupportsIfMatchAttribute, UpdateQuestionCommand + 14 + DeleteEntityCommand<TEntity, TIdentifierType>, Error, ICommandHandler<in TCommand, TResult>, IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, ISpecification<TEntity, TIdentifierType>, PagedCollectionResult<T>, Question, QuestionCreateRequest, QuestionDTO, QuestionsController, QuestionUpdateRequest, Result, SupportsIfMatchAttribute, UpdateQuestionCommand 10 @@ -28949,8 +29227,8 @@

Manifest (by level, then assembly)

10 SpeakersControllerTests MMCA.ADC.Conference.API.Tests - 23 - AndSpecification<TEntity, TIdentifierType>, BaseLookup<TIdentifierType>, DeleteEntityCommand<TEntity, TIdentifierType>, Error, GetPublicSpeakerFilterQuery, GetSpeakersByEventFilterQuery, ICommandHandler<in TCommand, TResult>, ICurrentUserService, IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, InlineSpecification<TEntity, TIdentifierType>, IQueryHandler<in TQuery, TResult>, ISpecification<TEntity, TIdentifierType>, PagedCollectionResult<T>, Result, RoleNames, Speaker, SpeakerCreateRequest, SpeakerDTO, SpeakersController, SpeakerUpdateRequest …(+3) + 25 + AndSpecification<TEntity, TIdentifierType>, BaseLookup<TIdentifierType>, DeleteEntityCommand<TEntity, TIdentifierType>, Error, GetPublicSpeakerFilterQuery, GetSpeakersByEventFilterQuery, ICommandHandler<in TCommand, TResult>, ICurrentUserService, IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, InlineSpecification<TEntity, TIdentifierType>, IPermissionRegistry, IQueryHandler<in TQuery, TResult>, ISpecification<TEntity, TIdentifierType>, PagedCollectionResult<T>, PermissionRegistry, Result, RoleNames, Speaker, SpeakerCreateRequest, SpeakerDTO …(+5) 10 @@ -29047,8 +29325,8 @@

Manifest (by level, then assembly)

10 CategorySyncStrategy MMCA.ADC.Conference.Application - 7 - Category, ISessionizeSyncStrategy, SessionizeCategory, SessionizeCategoryItem, SessionizeSyncContext, SessionizeSyncResult, SessionizeSyncWarnings + 8 + Category, ISessionizeSyncStrategy, Result, SessionizeCategory, SessionizeCategoryItem, SessionizeSyncContext, SessionizeSyncResult, SessionizeSyncWarnings 10 @@ -29194,8 +29472,8 @@

Manifest (by level, then assembly)

10 QuestionSyncStrategy MMCA.ADC.Conference.Application - 5 - ISessionizeSyncStrategy, Question, QuestionInvariants, SessionizeSyncContext, SessionizeSyncResult + 6 + ISessionizeSyncStrategy, Question, QuestionInvariants, SessionizeSyncContext, SessionizeSyncResult, SessionizeSyncWarnings 10 @@ -29311,17 +29589,10 @@

Manifest (by level, then assembly)

10 - SpeakerQuestionAnswerNavigationPopulator - MMCA.ADC.Conference.Application - 5 - DeclarativeNavigationPopulator<TEntity>, FKNavigationDescriptor<TEntity, TChild, TChildId>, IUnitOfWork, Speaker, SpeakerQuestionAnswer - - - 10 SpeakerSyncStrategy MMCA.ADC.Conference.Application - 9 - EventSpeaker, ISessionizeSyncStrategy, SessionizeLink, SessionizeQuestionAnswer, SessionizeSpeaker, SessionizeSyncContext, SessionizeSyncResult, SessionizeSyncWarnings, Speaker + 10 + CommonInvariants, EventSpeaker, ISessionizeSyncStrategy, SessionizeQuestionAnswer, SessionizeSpeaker, SessionizeSyncContext, SessionizeSyncResult, SessionizeSyncWarnings, Speaker, SpeakerInvariants 10 @@ -29367,13 +29638,6 @@

Manifest (by level, then assembly)

10 - UpdateEventQuestionAnswerHandler - MMCA.ADC.Conference.Application - 9 - Error, Event, EventQuestionAnswer, ICurrentUserService, IUnitOfWork, MutateEntityHandlerBase<TCommand, TEntity, TIdentifierType>, Result, RoleNames, UpdateEventQuestionAnswerCommand - - - 10 UpdateRoomHandler MMCA.ADC.Conference.Application 5 @@ -29395,13 +29659,6 @@

Manifest (by level, then assembly)

10 - UpdateSessionQuestionAnswerHandler - MMCA.ADC.Conference.Application - 9 - Error, ICurrentUserService, IUnitOfWork, MutateEntityHandlerBase<TCommand, TEntity, TIdentifierType>, Result, RoleNames, Session, SessionQuestionAnswer, UpdateSessionQuestionAnswerCommand - - - 10 ActivityDTOMapperTests MMCA.ADC.Conference.Application.Tests 2 @@ -29591,6 +29848,13 @@

Manifest (by level, then assembly)

10 + SpeakerCreateRequestMapperTests + MMCA.ADC.Conference.Application.Tests + 2 + SpeakerCreateRequest, SpeakerCreateRequestMapper + + + 10 SpeakerCreateRequestValidatorTests MMCA.ADC.Conference.Application.Tests 4 @@ -29640,6 +29904,13 @@

Manifest (by level, then assembly)

10 + UserDeletedFeedbackHandlerTests + MMCA.ADC.Conference.Application.Tests + 9 + Event, EventQuestionAnswer, InMemoryRepository<TEntity, TIdentifierType>, IUnitOfWork, RecordingUnitOfWork, Session, SessionQuestionAnswer, UserDeleted, UserDeletedFeedbackHandler + + + 10 UserRegisteredHandlerTests MMCA.ADC.Conference.Application.Tests 11 @@ -29703,13 +29974,6 @@

Manifest (by level, then assembly)

10 - ConferenceEntityConfigurationTests - MMCA.ADC.Conference.Infrastructure.Tests - 35 - Category, CategoryInvariants, CategoryItem, CategoryItemConfiguration, ConferenceCategoryConfiguration, ConferenceTestDbContext, Event, EventConfiguration, EventInvariants, EventQuestionAnswer, EventQuestionAnswerConfiguration, EventSpeaker, EventSpeakerConfiguration, Question, QuestionConfiguration, QuestionInvariants, Room, RoomConfiguration, Session, SessionCategoryItem …(+15) - - - 10 ConferenceModuleDbSeederTests MMCA.ADC.Conference.Infrastructure.Tests 6 @@ -29748,7 +30012,7 @@

Manifest (by level, then assembly)

PublicSessionList MMCA.ADC.Conference.UI 18 - BookmarkService, ConferenceReadAudience, CurrentEventDefaults, DataGridListPageBase<TDto>, EventDTO, EventService, IEventUIService, IPublicSessionScheduleService, ISessionBookmarkUIService, ISpeakerLookupService, PublicScheduleRoomOptions, PublicSessionListFilterState, PublicSessionListView, Result, RoomDTO, SessionDTO, SessionSchedulePageRequest, SpeakerInfo + BookmarkService, CurrentEventDefaults, DataGridListPageBase<TDto>, EventDTO, EventService, IEventUIService, IPublicSessionScheduleService, ISessionBookmarkUIService, ISpeakerLookupService, PublicReadAudience, PublicScheduleRoomOptions, PublicSessionListFilterState, PublicSessionListView, Result, RoomDTO, SessionDTO, SessionSchedulePageRequest, SpeakerInfo 10 @@ -29762,7 +30026,7 @@

Manifest (by level, then assembly)

PublicSpeakerList MMCA.ADC.Conference.UI 5 - ConferenceReadAudience, EventFilteredListPageBase<TDto>, ISpeakerUIService, SpeakerDTO, SpeakerService + EventFilteredListPageBase<TDto>, ISpeakerUIService, PublicReadAudience, SpeakerDTO, SpeakerService 10 @@ -29845,8 +30109,8 @@

Manifest (by level, then assembly)

10 PublicActivityListTests MMCA.ADC.Conference.UI.Tests - 6 - ActivityDTO, BunitTestBase, EventInfo, IActivityUIService, IEventLookupService, PublicActivityList + 8 + ActivityDTO, BunitTestBase, Error, EventInfo, IActivityUIService, IEventLookupService, PublicActivityList, Result 10 @@ -29899,6 +30163,13 @@

Manifest (by level, then assembly)

10 + SessionDetailScheduleTests + MMCA.ADC.Conference.UI.Tests + 17 + BunitTestBase, CategoryItemInfo, EventInfo, ICategoryItemLookupService, IEventLookupService, IRoomUIService, ISessionCategoryItemUIService, ISessionSpeakerUIService, ISessionUIService, ISpeakerLookupService, Result, RoleNames, RoomDTO, SessionDetail, SessionDTO, SpeakerInfo, TestPrincipal + + + 10 SessionDetailStaleLoadTests MMCA.ADC.Conference.UI.Tests 18 @@ -29992,8 +30263,8 @@

Manifest (by level, then assembly)

10 CastVoteHandler MMCA.ADC.Engagement.Application - 10 - CastVoteCommand, Error, ICommandHandler<in TCommand, TResult>, IEntityReader<TEntity, TIdentifierType>, IUnitOfWork, LivePoll, LivePollResultsBuilder, LivePollResultsDTO, LivePollVote, Result + 12 + CastVoteCommand, Error, ICommandHandler<in TCommand, TResult>, IEntityReader<TEntity, TIdentifierType>, IEventLiveValidationService, IUniqueConstraintViolationDetector, IUnitOfWork, LivePoll, LivePollResultsBuilder, LivePollResultsDTO, LivePollVote, Result 10 @@ -30020,8 +30291,8 @@

Manifest (by level, then assembly)

10 GetOpenPollsHandler MMCA.ADC.Engagement.Application - 10 - Error, GetOpenPollsQuery, IEntityQuerier<TEntity, TIdentifierType>, IQueryHandler<in TQuery, TResult>, IUnitOfWork, LivePoll, LivePollResultsBuilder, LivePollResultsDTO, LivePollStatus, Result + 11 + Error, GetOpenPollsQuery, IEntityQuerier<TEntity, TIdentifierType>, IEventLiveValidationService, IQueryHandler<in TQuery, TResult>, IUnitOfWork, LivePoll, LivePollResultsBuilder, LivePollResultsDTO, LivePollStatus, Result 10 @@ -30095,13 +30366,6 @@

Manifest (by level, then assembly)

10 - EngagementTestDbContext - MMCA.ADC.Engagement.Infrastructure.Tests - 12 - LivePoll, LivePollConfiguration, LivePollOption, LivePollOptionConfiguration, LivePollVote, LivePollVoteConfiguration, SessionQuestion, SessionQuestionConfiguration, SessionQuestionUpvote, SessionQuestionUpvoteConfiguration, UserSessionBookmark, UserSessionBookmarkConfiguration - - - 10 CheckInScopeNamesTests MMCA.ADC.Engagement.Shared.Tests 2 @@ -30172,13 +30436,6 @@

Manifest (by level, then assembly)

10 - IdentityEntityConfigurationTests - MMCA.ADC.Identity.Infrastructure.Tests - 3 - IdentityTestDbContext, User, UserInvariants - - - 10 DependencyInjection MMCA.ADC.Notification.Contracts 5 @@ -30186,6 +30443,27 @@

Manifest (by level, then assembly)

10 + AttendeesGrpcServiceTests + MMCA.ADC.Services.Tests + 5 + AttendeeQueryService, AttendeesGrpcService, FakeServerCallContext, GrpcCalls, IAttendeeQueryService + + + 10 + BookmarkCountsGrpcServiceTests + MMCA.ADC.Services.Tests + 6 + BookmarkCountService, BookmarkCountServiceGrpcAdapter, BookmarkCountsGrpcService, FakeServerCallContext, GrpcCalls, IBookmarkCountService + + + 10 + SessionBookmarkValidationServiceGrpcAdapterTests + MMCA.ADC.Services.Tests + 4 + Error, GrpcCalls, ISessionBookmarkValidationService, SessionBookmarkValidationService + + + 10 UserNotificationExportGrpcServiceTests MMCA.ADC.Services.Tests 4 @@ -30249,6 +30527,13 @@

Manifest (by level, then assembly)

10 + ICookieSessionRefresher + MMCA.Common.API + 2 + SessionRefreshOutcome, SessionTokenResult + + + 10 MiddlewarePipelineBuilder MMCA.Common.API 8 @@ -30312,6 +30597,13 @@

Manifest (by level, then assembly)

10 + OwnershipHelperGateTests + MMCA.Common.API.Tests + 4 + Error, ICurrentUserService, OwnershipHelper, Result + + + 10 OwnershipHelperTests MMCA.Common.API.Tests 3 @@ -30377,8 +30669,8 @@

Manifest (by level, then assembly)

10 ChangePasswordHandlerBaseTests MMCA.Common.Application.Tests - 13 - ChangePasswordRequest, Error, ErrorType, HandlerMocks, IPasswordHasher, IRefreshSessionStore, IRepository<TEntity, TIdentifierType>, IUnitOfWork, RefreshSession, Result, TestChangePasswordCommand, TestChangePasswordHandler, TestIdentityUser + 14 + ChangePasswordRequest, Error, ErrorType, HandlerMocks, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, IRepository<TEntity, TIdentifierType>, IUnitOfWork, RefreshSession, Result, TestChangePasswordCommand, TestChangePasswordHandler, TestIdentityUser 10 @@ -30606,13 +30898,6 @@

Manifest (by level, then assembly)

10 - RepositoryFactory - MMCA.Common.Infrastructure - 10 - ApplicationSettings, AuditableAggregateRootEntity<TIdentifierType>, AuditableBaseEntity<TIdentifierType>, EFReadRepository<TEntity, TIdentifierType>, EFReadRepositoryDecorator<TEntity, TIdentifierType>, EFRepository<TEntity, TIdentifierType>, EFRepositoryDecorator<TEntity, TIdentifierType>, IReadRepository<TEntity, TIdentifierType>, IRepository<TEntity, TIdentifierType>, IRepositoryFactory - - - 10 CurrentUserServiceAdditionalTests MMCA.Common.Infrastructure.Tests 3 @@ -30627,41 +30912,6 @@

Manifest (by level, then assembly)

10 - EFRepositoryAdditionalTests - MMCA.Common.Infrastructure.Tests - 3 - EFRepository<TEntity, TIdentifierType>, TestDbContext, TestEntity - - - 10 - EFRepositoryConcurrencyTouchTests - MMCA.Common.Infrastructure.Tests - 3 - EFRepository<TEntity, TIdentifierType>, TestDbContext, TestEntity - - - 10 - EFRepositoryIntegrationTests - MMCA.Common.Infrastructure.Tests - 7 - EFReadRepository<TEntity, TIdentifierType>, EFRepository<TEntity, TIdentifierType>, FakeTimeProvider, ICurrentUserService, TestChildEntity, TestDbContext, TestEntity - - - 10 - EntityTypeConfigurationTests - MMCA.Common.Infrastructure.Tests - 2 - SqliteTestDbContext, SqliteTestEntity - - - 10 - MarkAllNotificationsReadHandlerTrackingTests - MMCA.Common.Infrastructure.Tests - 10 - EFQueryableExecutor, EFRepository<TEntity, TIdentifierType>, IUnitOfWork, MarkAllNotificationsReadCommand, MarkAllNotificationsReadHandler, NotificationTestDbContext, PushNotification, Result, SeededIds, UserNotification - - - 10 OutboxMessageTests MMCA.Common.Infrastructure.Tests 7 @@ -30669,13 +30919,6 @@

Manifest (by level, then assembly)

10 - PushNotificationConfigurationTests - MMCA.Common.Infrastructure.Tests - 2 - PushNotification, PushNotificationTestDbContext - - - 10 PushNotificationProjectionTranslationTests MMCA.Common.Infrastructure.Tests 5 @@ -30993,8 +31236,8 @@

Manifest (by level, then assembly)

11 EventLiveValidationServiceGrpcAdapter MMCA.ADC.Conference.Contracts - 8 - EventLiveInfo, EventLiveValidationService, IEventLiveValidationService, QuestionModerationDefault, Result, RoomSessionInfo, SessionLiveInfo, SponsorLiveInfo + 9 + Error, EventLiveInfo, EventLiveValidationService, IEventLiveValidationService, QuestionModerationDefault, Result, RoomSessionInfo, SessionLiveInfo, SponsorLiveInfo 11 @@ -31021,8 +31264,15 @@

Manifest (by level, then assembly)

11 PublicSessionListEventFilterTests MMCA.ADC.Conference.UI.Tests + 12 + BunitTestBase, Error, Event, EventDTO, IEventUIService, ISessionUIService, ISpeakerLookupService, PublicSessionList, Result, RoleNames, SpeakerInfo, TestPrincipal + + + 11 + PublicSessionListMyScheduleTests + MMCA.ADC.Conference.UI.Tests 11 - BunitTestBase, Event, EventDTO, IEventUIService, ISessionUIService, ISpeakerLookupService, PublicSessionList, Result, RoleNames, SpeakerInfo, TestPrincipal + BunitTestBase, Error, EventDTO, IEventUIService, ISessionBookmarkUIService, ISessionUIService, ISpeakerLookupService, PublicSessionList, Result, SpeakerInfo, TestPrincipal 11 @@ -31061,6 +31311,13 @@

Manifest (by level, then assembly)

11 + SessionListCurrentEventClockTests + MMCA.ADC.Conference.UI.Tests + 12 + BunitTestBase, Event, EventDTO, FixedTimeProvider, IEventUIService, ISessionUIService, ISpeakerLookupService, Result, RoleNames, SessionList, SpeakerInfo, TestPrincipal + + + 11 SessionListEventFilterTests MMCA.ADC.Conference.UI.Tests 11 @@ -31124,20 +31381,6 @@

Manifest (by level, then assembly)

11 - CheckInConfiguration - MMCA.ADC.Engagement.Infrastructure - 2 - CheckIn, EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> - - - 11 - EngagementEntityConfigurationTests - MMCA.ADC.Engagement.Infrastructure.Tests - 9 - EngagementTestDbContext, LivePoll, LivePollInvariants, LivePollOption, LivePollVote, SessionQuestion, SessionQuestionInvariants, SessionQuestionUpvote, UserSessionBookmark - - - 11 EngagementUIModule MMCA.ADC.Engagement.UI 6 @@ -31187,6 +31430,20 @@

Manifest (by level, then assembly)

11 + EventLiveValidationServiceGrpcAdapterTests + MMCA.ADC.Services.Tests + 5 + EventLiveValidationService, IEventLiveValidationService, QuestionModerationDefault, Result, SessionLiveInfo + + + 11 + SessionBookmarksGrpcServiceTests + MMCA.ADC.Services.Tests + 6 + Error, FakeServerCallContext, ISessionBookmarkValidationService, Result, ResultFailureException, SessionBookmarksGrpcService + + + 11 MauiProgram MMCA.ADC.UI 14 @@ -31194,10 +31451,24 @@

Manifest (by level, then assembly)

11 - DependencyInjection + CookieSessionRefreshMiddleware MMCA.Common.API - 25 - CookieSessionRefresher, CookieTokenReader, CurrencyJsonConverter, CurrentUserTargetingContextAccessor, DbUpdateExceptionHandler, DisabledFeatureHandler, DomainExceptionHandler, EnumerationJsonConverterFactory, ErrorLocalizer, ErrorResources, ErrorResourceSource, GlobalExceptionHandler, ICookieSessionRefresher, IdempotencyFilter, IdempotencySettings, IErrorLocalizer, ModuleControllerFeatureProvider, ModuleLoader, ModulesSettings, OperationCanceledExceptionHandler …(+5) + 1 + ICookieSessionRefresher + + + 11 + SessionCookieEndpoints + MMCA.Common.API + 6 + ICookieSessionRefresher, SessionClaimsToken, SessionCookieJar, SessionCookieRequest, SessionCookieSettings, SessionTokenResponse + + + 11 + SessionCookieJar + MMCA.Common.API + 1 + SessionCookieEndpoints 11 @@ -31229,6 +31500,13 @@

Manifest (by level, then assembly)

11 + StubRefresher + MMCA.Common.API.Tests + 3 + ICookieSessionRefresher, SessionRefreshOutcome, SessionTokenResult + + + 11 TestDataExportController MMCA.Common.API.Tests 6 @@ -31358,14 +31636,14 @@

Manifest (by level, then assembly)

ApplicationDbContext MMCA.Common.Infrastructure 36 - AuditableBaseEntity<TIdentifierType>, AuditSaveChangesInterceptor, AuditTrailEntry, AuditTrailSaveChangesInterceptor, AuditTrailSettings, CrossDataSourceDegradeConvention, DataSource, DataSourceKey, DataSourceModelCacheKeyFactory, DetectChangesScope, DomainEventSaveChangesInterceptor, IAuditableEntity, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IEntityTypeConfigurationCosmos<TEntity, TIdentifierType>, IEntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType>, IEntityTypeConfigurationSqlite<TEntity, TIdentifierType>, IEntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, InboxMessage, InternalCommandMessage …(+16) + AuditableBaseEntity<TIdentifierType>, AuditSaveChangesInterceptor, AuditTrailEntry, AuditTrailSaveChangesInterceptor, AuditTrailSettings, CrossDataSourceDegradeConvention, DataSource, DataSourceEngines, DataSourceKey, DataSourceModelCacheKeyFactory, DetectChangesScope, DomainEventSaveChangesInterceptor, IAuditableEntity, IDataSourceEngine, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, InboxMessage, InternalCommandMessage, ITenantEntity, OutboxMessage …(+16) 11 AuditSaveChangesInterceptor MMCA.Common.Infrastructure - 2 - ApplicationDbContext, IAuditableEntity + 4 + ApplicationDbContext, IAuditableEntity, IRowVersioned, RowVersionStrategy 11 @@ -31376,6 +31654,34 @@

Manifest (by level, then assembly)

11 + CosmosDataSourceEngine + MMCA.Common.Infrastructure + 17 + ApplicationDbContext, AuditableBaseEntity<TIdentifierType>, ConnectionStringSettings, CosmosDbContext, CosmosIntIdValueGenerator, DataSource, DataSourceEngineCapabilities, DataSourceEntrySettings, IDataSourceEngine, IEntityConfigurationAssemblyProvider, IEntityTypeConfigurationCosmos<TEntity, TIdentifierType>, IExplicitKeyInsertDialect, MigrationPolicy, NamespaceConventions, PhysicalDataSource, RowVersionStrategy, TenantDataSourceOverrideSettings + + + 11 + CosmosDbContext + MMCA.Common.Infrastructure + 6 + ApplicationDbContext, DataSource, IEntityConfigurationAssemblyProvider, InternalCommandMessage, OutboxMessage, PhysicalDataSource + + + 11 + CrossDataSourceDegradeConvention + MMCA.Common.Infrastructure + 3 + DataSourceEngines, DataSourceKey, IEntityDataSourceRegistry + + + 11 + DataSourceEngines + MMCA.Common.Infrastructure + 6 + CosmosDataSourceEngine, DataSource, IDataSourceEngine, PostgreSQLDataSourceEngine, SqliteDataSourceEngine, SQLServerDataSourceEngine + + + 11 DataSourceModelCacheKeyFactory MMCA.Common.Infrastructure 1 @@ -31397,6 +31703,13 @@

Manifest (by level, then assembly)

11 + IDataSourceEngine + MMCA.Common.Infrastructure + 10 + ApplicationDbContext, AuditableBaseEntity<TIdentifierType>, ConnectionStringSettings, DataSource, DataSourceEngineCapabilities, DataSourceEntrySettings, IEntityConfigurationAssemblyProvider, IExplicitKeyInsertDialect, PhysicalDataSource, TenantDataSourceOverrideSettings + + + 11 OutboxFinalizer MMCA.Common.Infrastructure 2 @@ -31404,17 +31717,80 @@

Manifest (by level, then assembly)

11 - TenantSaveChangesInterceptor + PhysicalDataSource MMCA.Common.Infrastructure 3 - ApplicationDbContext, CrossTenantWriteException, ITenantEntity + DataSourceEngines, DataSourceKey, MigrationPolicy 11 - RepositoryFactoryTests - MMCA.Common.Infrastructure.Tests - 13 - ApplicationSettings, EFReadRepository<TEntity, TIdentifierType>, EFReadRepositoryDecorator<TEntity, TIdentifierType>, EFRepository<TEntity, TIdentifierType>, EFRepositoryDecorator<TEntity, TIdentifierType>, FakeAggregate, FakeAggregate, FakeEntity, FakeEntity, IReadRepository<TEntity, TIdentifierType>, IRepository<TEntity, TIdentifierType>, RepositoryFactory, TestDbContext + PostgreSQLDataSourceEngine + MMCA.Common.Infrastructure + 16 + ApplicationDbContext, AuditableBaseEntity<TIdentifierType>, ConnectionStringSettings, DataSource, DataSourceEngineCapabilities, DataSourceEntrySettings, IDataSourceEngine, IEntityConfigurationAssemblyProvider, IEntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType>, IExplicitKeyInsertDialect, MigrationPolicy, NamespaceConventions, PhysicalDataSource, PostgreSQLDbContext, RowVersionStrategy, TenantDataSourceOverrideSettings + + + 11 + PostgreSQLDbContext + MMCA.Common.Infrastructure + 6 + ApplicationDbContext, DataSource, IEntityConfigurationAssemblyProvider, PersistenceSettings, PhysicalDataSource, UtcDateTimeConverter + + + 11 + RestrictDeleteByDefaultConvention + MMCA.Common.Infrastructure + 2 + DataSource, DataSourceEngines + + + 11 + SoftDeleteFilterSql + MMCA.Common.Infrastructure + 3 + DataSource, DataSourceEngines, IAuditableEntity + + + 11 + SoftDeleteUniqueIndexConvention + MMCA.Common.Infrastructure + 4 + DataSource, DataSourceEngines, IAuditableEntity, SoftDeleteFilterSql + + + 11 + SqliteDataSourceEngine + MMCA.Common.Infrastructure + 15 + ApplicationDbContext, AuditableBaseEntity<TIdentifierType>, ConnectionStringSettings, DataSource, DataSourceEngineCapabilities, DataSourceEntrySettings, IDataSourceEngine, IEntityConfigurationAssemblyProvider, IEntityTypeConfigurationSqlite<TEntity, TIdentifierType>, IExplicitKeyInsertDialect, MigrationPolicy, PhysicalDataSource, RowVersionStrategy, SqliteDbContext, TenantDataSourceOverrideSettings + + + 11 + SqliteDbContext + MMCA.Common.Infrastructure + 4 + ApplicationDbContext, DataSource, IEntityConfigurationAssemblyProvider, PhysicalDataSource + + + 11 + SQLServerDataSourceEngine + MMCA.Common.Infrastructure + 17 + ApplicationDbContext, AuditableBaseEntity<TIdentifierType>, ConnectionStringSettings, DataSource, DataSourceEngineCapabilities, DataSourceEntrySettings, ExplicitKeyInsertGroup, IDataSourceEngine, IEntityConfigurationAssemblyProvider, IEntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, IExplicitKeyInsertDialect, MigrationPolicy, NamespaceConventions, PhysicalDataSource, RowVersionStrategy, SQLServerDbContext, TenantDataSourceOverrideSettings + + + 11 + SQLServerDbContext + MMCA.Common.Infrastructure + 5 + ApplicationDbContext, DataSource, IEntityConfigurationAssemblyProvider, PersistenceSettings, PhysicalDataSource + + + 11 + TenantSaveChangesInterceptor + MMCA.Common.Infrastructure + 3 + ApplicationDbContext, CrossTenantWriteException, ITenantEntity 11 @@ -31504,8 +31880,8 @@

Manifest (by level, then assembly)

12 UserEngagementExportServiceGrpcAdapter MMCA.ADC.Engagement.Contracts - 9 - CheckInScope, IUserEngagementExportService, PointsActivityType, UserEngagementBookmarkExportDTO, UserEngagementCheckInExportDTO, UserEngagementExportDTO, UserEngagementExportService, UserEngagementPointsEntryExportDTO, UserEngagementSubmittedQuestionExportDTO + 10 + CheckInScope, GrpcWireFormat, IUserEngagementExportService, PointsActivityType, UserEngagementBookmarkExportDTO, UserEngagementCheckInExportDTO, UserEngagementExportDTO, UserEngagementExportService, UserEngagementPointsEntryExportDTO, UserEngagementSubmittedQuestionExportDTO 12 @@ -31518,8 +31894,8 @@

Manifest (by level, then assembly)

12 UserEngagementExportGrpcService MMCA.ADC.Engagement.Service - 3 - IUserEngagementExportService, LeaderboardOptIn, UserEngagementExportService + 4 + GrpcWireFormat, IUserEngagementExportService, LeaderboardOptIn, UserEngagementExportService 12 @@ -31558,6 +31934,13 @@

Manifest (by level, then assembly)

12 + EventLiveValidationGrpcServiceTests + MMCA.ADC.Services.Tests + 13 + Error, EventLiveInfo, EventLiveValidationGrpcService, EventLiveValidationService, FakeServerCallContext, GrpcCalls, IEventLiveValidationService, QuestionModerationDefault, Result, ResultFailureException, RoomSessionInfo, SessionLiveInfo, SponsorLiveInfo + + + 12 App MMCA.ADC.UI 1 @@ -31567,8 +31950,8 @@

Manifest (by level, then assembly)

12 AppDelegate MMCA.ADC.UI - 2 - IDeepLinkDispatcher, MauiProgram + 3 + DeepLinkDispatcher, IDeepLinkDispatcher, MauiProgram 12 @@ -31579,6 +31962,27 @@

Manifest (by level, then assembly)

12 + CookieSessionRefreshMiddlewareExtensions + MMCA.Common.API + 1 + CookieSessionRefreshMiddleware + + + 12 + CookieTokenReader + MMCA.Common.API + 1 + SessionCookieEndpoints + + + 12 + SessionCookieStore + MMCA.Common.API + 3 + ISessionCookieStore, SessionCookieJar, SessionCookieSettings + + + 12 AdministrationControllerBaseTests MMCA.Common.API.Tests 19 @@ -31586,6 +31990,13 @@

Manifest (by level, then assembly)

12 + ClaimsOnlySessionCookieEndpointsTests + MMCA.Common.API.Tests + 7 + ICookieSessionRefresher, SessionCookieEndpoints, SessionCookieRequest, SessionCookieSettings, SessionTokenResponse, SessionTokenResult, StubRefresher + + + 12 DataExportControllerBaseTests MMCA.Common.API.Tests 13 @@ -31593,6 +32004,20 @@

Manifest (by level, then assembly)

12 + SessionCookieEndpointsTests + MMCA.Common.API.Tests + 6 + ICookieSessionRefresher, SessionCookieEndpoints, SessionCookieRequest, SessionTokenResponse, SessionTokenResult, StubRefresher + + + 12 + SessionCookieJarTests + MMCA.Common.API.Tests + 2 + SessionCookieEndpoints, SessionCookieJar + + + 12 ChildEntityHandlerBaseTests MMCA.Common.Application.Tests 8 @@ -31607,6 +32032,20 @@

Manifest (by level, then assembly)

12 + FrameworkModels + MMCA.Common.Architecture.Tests + 12 + AuditSaveChangesInterceptor, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, IEntityDataSourceRegistry, NoDomainEventDispatcher, NoEntityDataSources, NoModuleAssemblies, NoOutboxSignal, PhysicalDataSource, SqliteDbContext, SQLServerDbContext + + + 12 + FrameworkModuleArchitectureMap + MMCA.Common.Architecture.Tests + 7 + ApplicationDbContext, ArchitectureMapBase, BaseEntity<TIdentifierType>, DomainEventDispatcher, Layer, LayerRef, Result + + + 12 FrameworkSanityTests MMCA.Common.Architecture.Tests 7 @@ -31684,80 +32123,59 @@

Manifest (by level, then assembly)

12 - CosmosDbContext + DataSourceService MMCA.Common.Infrastructure - 6 - ApplicationDbContext, DataSource, IEntityConfigurationAssemblyProvider, InternalCommandMessage, OutboxMessage, PhysicalDataSource + 5 + DataSource, DataSourceEngines, DataSourceKey, IDataSourceService, IEntityDataSourceRegistry 12 - IDbContextFactory + EFReadRepository<TEntity, TIdentifierType> MMCA.Common.Infrastructure - 2 - ApplicationDbContext, DataSourceKey + 19 + ApplicationDbContext, AuditableBaseEntity<TIdentifierType>, BaseLookup<TIdentifierType>, DataSourceEngineCapabilities, EntityQueryPipeline, Error, GroupedCount<TKey>, GroupedSum<TKey>, IReadRepository<TEntity, TIdentifierType>, ISpecification<TEntity, TIdentifierType>, KeysetCollectionResult<T>, KeysetCursor, KeysetPageRequest, KeysetQueryBuilder, LookupRow<TId, TName>, QuerySpecification<TEntity, TIdentifierType>, QueryTags, Result, SpecificationEvaluator 12 - IPhysicalDbContextFactory + EntityTypeConfiguration<TEntity, TIdentifierType> MMCA.Common.Infrastructure - 3 - ApplicationDbContext, DataSourceKey, PhysicalDataSource + 9 + AuditableBaseEntity<TIdentifierType>, DataSource, DataSourceEngines, EntityTypeConfigurationBase<TEntity, TIdentifierType>, IEntityTypeConfigurationCosmos<TEntity, TIdentifierType>, IEntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType>, IEntityTypeConfigurationSqlite<TEntity, TIdentifierType>, IEntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, UseDataSourceAttribute 12 - PostgreSQLDbContext + IDataSourceResolver MMCA.Common.Infrastructure - 6 - ApplicationDbContext, DataSource, IEntityConfigurationAssemblyProvider, PersistenceSettings, PhysicalDataSource, UtcDateTimeConverter + 3 + DataSource, DataSourceKey, PhysicalDataSource 12 - SqliteDbContext + IDbContextFactory MMCA.Common.Infrastructure - 4 - ApplicationDbContext, DataSource, IEntityConfigurationAssemblyProvider, PhysicalDataSource + 2 + ApplicationDbContext, DataSourceKey 12 - SQLServerDbContext + IndexBuilderExtensions MMCA.Common.Infrastructure - 5 - ApplicationDbContext, DataSource, IEntityConfigurationAssemblyProvider, PersistenceSettings, PhysicalDataSource - - - 12 - AddMultiTenancyTests - MMCA.Common.Infrastructure.Tests - 11 - ConnectionStringSettings, DataSourceResolver, DataSourcesSettings, ITenantContext, TenancySettings, TenancySettingsValidator, TenantContext, TenantDataSourceOverrideSettings, TenantEntrySettings, TenantResolutionStrategy, TenantSaveChangesInterceptor - - - 12 - AdminTestContext - MMCA.Common.Infrastructure.Tests - 10 - ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NoAssemblies, OutboxMessage, TestPhysicalDataSources - - - 12 - AuditTrailTestContext - MMCA.Common.Infrastructure.Tests - 16 - ApplicationDbContext, AuditedThing, AuditSaveChangesInterceptor, AuditTrailEntry, AuditTrailSaveChangesInterceptor, CompositeKeyThing, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FailingSaveInterceptor, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, PlainThing, TestPhysicalDataSources + 2 + DataSource, SoftDeleteFilterSql 12 - CleanupTestContext - MMCA.Common.Infrastructure.Tests - 13 - ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, InboxMessage, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, NullAssemblyProvider, OutboxMessage, TestPhysicalDataSources + IPhysicalDbContextFactory + MMCA.Common.Infrastructure + 3 + ApplicationDbContext, DataSourceKey, PhysicalDataSource 12 - CommitFailingDbContext + CosmosDbContextTests MMCA.Common.Infrastructure.Tests - 13 - ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FailingDatabaseFacade, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, OutboxMessage, TestAggregate, TestPhysicalDataSources + 1 + CosmosDbContext 12 @@ -31768,48 +32186,6 @@

Manifest (by level, then assembly)

12 - DeleteBehaviorTestDbContext - MMCA.Common.Infrastructure.Tests - 14 - ApplicationDbContext, AuditSaveChangesInterceptor, CascadingChild, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NoModuleAssemblies, NoModuleAssemblies, OptionalChild, Parent, RequiredChild, TestPhysicalDataSources - - - 12 - DetectionTestDbContext - MMCA.Common.Infrastructure.Tests - 12 - ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources, Widget, Widget - - - 12 - ExclusionTestDbContext - MMCA.Common.Infrastructure.Tests - 9 - ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, ExclusionAggregate, IEntityDataSourceRegistry, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources - - - 12 - FailingDatabaseFacade - MMCA.Common.Infrastructure.Tests - 2 - AlwaysRetryExecutionStrategy, CommitFailingDbContext - - - 12 - FailingSaveInterceptor - MMCA.Common.Infrastructure.Tests - 1 - AuditTrailTestContext - - - 12 - FailingSaveInterceptor - MMCA.Common.Infrastructure.Tests - 1 - OutboxRoutingTestDbContext - - - 12 GateContext<TCase> MMCA.Common.Infrastructure.Tests 3 @@ -31831,31 +32207,10 @@

Manifest (by level, then assembly)

12 - InboxTestDbContext - MMCA.Common.Infrastructure.Tests - 4 - ApplicationDbContext, IEntityConfigurationAssemblyProvider, InboxMessage, TestPhysicalDataSources - - - 12 IntegrityTestDbContext MMCA.Common.Infrastructure.Tests - 12 - ApplicationDbContext, AuditSaveChangesInterceptor, DataSourceKey, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IntegrityAggregate, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, PhysicalDataSource - - - 12 - InternalCommandTestContext - MMCA.Common.Infrastructure.Tests - 11 - ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, InternalCommandMessage, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources - - - 12 - MidSaveContextCreatingDbContext - MMCA.Common.Infrastructure.Tests - 10 - ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IOutboxSignal, ReentrantSaveInterceptor, TestPhysicalDataSources + 13 + ApplicationDbContext, AuditSaveChangesInterceptor, DataSourceKey, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IntegrityAggregate, IOutboxSignal, MessageBusSettings, NullAssemblyProvider, NullAssemblyProvider, PhysicalDataSource 12 @@ -31873,34 +32228,6 @@

Manifest (by level, then assembly)

12 - NoSessionTableContext - MMCA.Common.Infrastructure.Tests - 6 - ApplicationDbContext, NullAssemblyProvider, NullAssemblyProvider, NullAssemblyProvider, OutboxMessage, TestPhysicalDataSources - - - 12 - OrderingTestContext - MMCA.Common.Infrastructure.Tests - 10 - ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NoAssemblies, OutboxMessage, TestPhysicalDataSources - - - 12 - OutboxRoutingTestDbContext - MMCA.Common.Infrastructure.Tests - 11 - ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FailingSaveInterceptor, IEntityDataSourceRegistry, NullAssemblyProvider, NullAssemblyProvider, OutboxMessage, TestAggregate, TestPhysicalDataSources - - - 12 - OutboxTestDbContext - MMCA.Common.Infrastructure.Tests - 4 - ApplicationDbContext, IEntityConfigurationAssemblyProvider, OutboxMessage, TestPhysicalDataSources - - - 12 Participant MMCA.Common.Infrastructure.Tests 2 @@ -31908,45 +32235,10 @@

Manifest (by level, then assembly)

12 - QueryShapeTestDbContext - MMCA.Common.Infrastructure.Tests - 11 - ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, Product, TestPhysicalDataSources - - - 12 - ReentrantSaveInterceptor - MMCA.Common.Infrastructure.Tests - 1 - MidSaveContextCreatingDbContext - - - 12 - RestoreTestDbContext - MMCA.Common.Infrastructure.Tests - 4 - ApplicationDbContext, IEntityConfigurationAssemblyProvider, OutboxMessage, TestPhysicalDataSources - - - 12 - SchedulerTestContext - MMCA.Common.Infrastructure.Tests - 10 - ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, ScheduledJobEntry, TestPhysicalDataSources - - - 12 - SessionCleanupTestContext - MMCA.Common.Infrastructure.Tests - 5 - ApplicationDbContext, NullAssemblyProvider, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources - - - 12 - SoftDeleteTestDbContext + PhysicalDataSourceTests MMCA.Common.Infrastructure.Tests - 11 - ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, SoftDeletableEntity, SoftDeleteTestDbContext, TestPhysicalDataSources + 3 + DataSource, DataSourceKey, PhysicalDataSource 12 @@ -31957,73 +32249,10 @@

Manifest (by level, then assembly)

12 - StampTestDbContext - MMCA.Common.Infrastructure.Tests - 11 - ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, StampedEntity, TestPhysicalDataSources - - - 12 - TenantTestContext - MMCA.Common.Infrastructure.Tests - 17 - ApplicationDbContext, AuditSaveChangesInterceptor, AuditTrailEntry, AuditTrailSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, PlainThing, TenantOnlyThing, TenantSaveChangesInterceptor, TenantThing, TestPhysicalDataSources, TrailedTenantThing - - - 12 - TestApplicationDbContext - MMCA.Common.Infrastructure.Tests - 10 - ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IOutboxSignal, TestEntity, TestPhysicalDataSources - - - 12 - TestAuditDbContext - MMCA.Common.Infrastructure.Tests - 11 - ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, TestAuditEntity, TestPhysicalDataSources - - - 12 - TestDomainEventDbContext - MMCA.Common.Infrastructure.Tests - 9 - ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IEntityDataSourceRegistry, NullAssemblyProvider, NullAssemblyProvider, TestAggregate, TestPhysicalDataSources - - - 12 - TestNonOutboxContext - MMCA.Common.Infrastructure.Tests - 9 - ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, TestPhysicalDataSources - - - 12 - TestOutboxContext - MMCA.Common.Infrastructure.Tests - 10 - ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, OutboxMessage, TestPhysicalDataSources - - - 12 - TransactionTestDbContext - MMCA.Common.Infrastructure.Tests - 12 - ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, OutboxMessage, TestAggregate, TestPhysicalDataSources - - - 12 - UniqueIndexTestDbContext - MMCA.Common.Infrastructure.Tests - 15 - AlreadySoftDeleteFilteredEntity, ApplicationDbContext, AuditSaveChangesInterceptor, BracketQuotedFilterEntity, DataSource, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FilteredIndexEntity, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources, UniqueNamedEntity - - - 12 - WidgetContext + TestPhysicalDataSources MMCA.Common.Infrastructure.Tests - 10 - ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NoModuleAssemblies, TestPhysicalDataSources, Widget + 3 + DataSource, DataSourceKey, PhysicalDataSource 12 @@ -32034,6 +32263,13 @@

Manifest (by level, then assembly)

12 + CreateMigrationProofTable + MMCA.Common.Infrastructure.Tests.MigrationsFixture + 1 + SqliteDbContext + + + 12 MiddlewarePipelineOrderTests MMCA.Common.Testing.Tests 1 @@ -32047,6 +32283,20 @@

Manifest (by level, then assembly)

E2ETestConfiguration, GalleryE2ECollection, GalleryHostFixture, PlaywrightFixture + 12 + SameOriginProxyTransformer + MMCA.Common.UI.Web + 5 + ISessionCookieStore, ProxyResponseMode, SameOriginProxyHeaders, SessionClaimsToken, SessionCookieEndpoints + + + 12 + ProxyHost + MMCA.Common.UI.Web.Tests + 4 + ApiSettings, FakeGateway, SameOriginProxyInvoker, SessionCookieEndpoints + + 13 AiDependencyIsolationTests MMCA.ADC.Architecture.Tests @@ -32342,13 +32592,6 @@

Manifest (by level, then assembly)

13 - RefreshSessionModelGateTests - MMCA.ADC.Identity.Infrastructure.Tests - 19 - AuditSaveChangesInterceptor, CaseConference, CaseIdentity, CaseWrongSource, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourceResolver, DataSourcesSettings, DomainEventSaveChangesInterceptor, GateContext<TCase>, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IOutboxSignal, PhysicalDataSource, RefreshSession, RefreshSessionSettings - - - 13 UserEngagementExportGrpcServiceTests MMCA.ADC.Services.Tests 8 @@ -32370,10 +32613,31 @@

Manifest (by level, then assembly)

13 - DatabaseInitializationExtensions + CookieSessionRefresher MMCA.Common.API 11 - ApplicationSettings, DataSource, DataSourceKey, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, ModuleLoader, PhysicalDataSource, TenancySettings, TenantDataSourceTarget, TenantDataSourceTargets + AuthenticationResponse, CookieTokenReader, ICookieSessionRefresher, KeyedSemaphoreStripe, RefreshTokenRequest, SessionCookieEndpoints, SessionCookieJar, SessionCookieSettings, SessionRefreshOutcome, SessionRefreshStatus, SessionTokenResult + + + 13 + SessionCookieAuthenticationHandler + MMCA.Common.API + 1 + CookieTokenReader + + + 13 + CookieSessionRefreshMiddlewareTests + MMCA.Common.API.Tests + 5 + CookieSessionRefreshMiddleware, CookieSessionRefreshMiddlewareExtensions, ICookieSessionRefresher, NextDelegateSpy, SessionTokenResult + + + 13 + CookieTokenReaderTests + MMCA.Common.API.Tests + 2 + CookieTokenReader, SessionCookieEndpoints 13 @@ -32407,8 +32671,8 @@

Manifest (by level, then assembly)

13 ClockReadTests MMCA.Common.Architecture.Tests - 11 - ArchitectureRules, AsyncClockReadingFixture, ClockReadTestsBase, CommonArchitectureMap, FixtureAssemblyMap, IArchitectureMap, InjectedClockFixture, LambdaClockReadingFixture, OffsetNowReadingFixture, TwoMemberClockFixture, UtcNowReadingFixture + 12 + ArchitectureRules, AsyncClockReadingFixture, ClockReadTestsBase, CommonArchitectureMap, FixtureAssemblyMap, IArchitectureMap, InjectedClockFixture, LambdaClockReadingFixture, OffsetNowReadingFixture, TodayReadingFixture, TwoMemberClockFixture, UtcNowReadingFixture 13 @@ -32426,6 +32690,13 @@

Manifest (by level, then assembly)

13 + DeleteBehaviorConventionTests + MMCA.Common.Architecture.Tests + 3 + ArchitectureRules, DeleteBehaviorConventionTestsBase, FrameworkModels + + + 13 DomainPurityTests MMCA.Common.Architecture.Tests 3 @@ -32440,6 +32711,13 @@

Manifest (by level, then assembly)

13 + EntityConventionTests + MMCA.Common.Architecture.Tests + 3 + EntityConventionTestsBase, FrameworkModuleArchitectureMap, IArchitectureMap + + + 13 EventScopeFitnessTests MMCA.Common.Architecture.Tests 3 @@ -32482,10 +32760,10 @@

Manifest (by level, then assembly)

13 - FrameworkModels + FrameworkConstructorDependencyTests MMCA.Common.Architecture.Tests - 12 - AuditSaveChangesInterceptor, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, IEntityDataSourceRegistry, NoDomainEventDispatcher, NoEntityDataSources, NoModuleAssemblies, NoOutboxSignal, PhysicalDataSource, SqliteDbContext, SQLServerDbContext + 4 + CommonArchitectureMap, ConstructorDependencyCountTestsBase, IArchitectureMap, Layer 13 @@ -32503,6 +32781,13 @@

Manifest (by level, then assembly)

13 + ImmutabilityTests + MMCA.Common.Architecture.Tests + 3 + FrameworkModuleArchitectureMap, IArchitectureMap, ImmutabilityTestsBase + + + 13 IntegrationEventPayloadPurityRuleTests MMCA.Common.Architecture.Tests 7 @@ -32547,8 +32832,15 @@

Manifest (by level, then assembly)

13 PiiConventionTests MMCA.Common.Architecture.Tests + 4 + CommonArchitectureMap, IArchitectureMap, Layer, PiiConventionTestsBase + + + 13 + Probe + MMCA.Common.Architecture.Tests 3 - CommonArchitectureMap, IArchitectureMap, PiiConventionTestsBase + CommonArchitectureMap, DataResidencyTestsBase, IArchitectureMap 13 @@ -32608,6 +32900,13 @@

Manifest (by level, then assembly)

13 + TenantEntityConventionTests + MMCA.Common.Architecture.Tests + 3 + CommonArchitectureMap, ITenantEntity, Layer + + + 13 UIArchitectureConventionTests MMCA.Common.Architecture.Tests 3 @@ -32629,13 +32928,6 @@

Manifest (by level, then assembly)

13 - AuditTrailCleanupJob - MMCA.Common.Infrastructure - 8 - AuditTrailEntry, AuditTrailSettings, IDbContextFactory, IEntityDataSourceRegistry, IScheduledJob, TenancySettings, TenantDataSourceTarget, TenantDataSourceTargets - - - 13 AuditTrailReader MMCA.Common.Infrastructure 7 @@ -32650,17 +32942,10 @@

Manifest (by level, then assembly)

13 - DbContextFactory - MMCA.Common.Infrastructure - 22 - AmbientOrigin, ApplicationDbContext, CosmosDbContext, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, Entry, ICorrelationContext, ICurrentUserService, IDataSourceResolver, IDbContextFactory, IdentityInsertGroup, IEntityDataSourceRegistry, IPhysicalDbContextFactory, ITenantContext, OutboxOrigin, PhysicalDataSource, Result, SQLServerDbContext, TenancySettings …(+2) - - - 13 - DesignTimeDbContextHelper + DataSourceResolver MMCA.Common.Infrastructure - 28 - AuditSaveChangesInterceptor, AuditTrailSaveChangesInterceptor, AuditTrailSettings, DataSource, DataSourceKey, DataSourceResolver, DataSourcesSettings, DesignTimeDbContextOptions, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, ExplicitAssemblyProvider, IDataSourceResolver, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IOutboxSignal, NullDomainEventDispatcher, OutboxSignal, PermissionGrantModelGate, PermissionGrantSettings …(+8) + 10 + ConnectionStringSettings, DataSource, DataSourceEngines, DataSourceEntrySettings, DataSourceKey, DataSourcesSettings, DefaultSeed, IDataSourceResolver, MigrationPolicy, PhysicalDataSource 13 @@ -32673,8 +32958,8 @@

Manifest (by level, then assembly)

13 EFPermissionGrantStore MMCA.Common.Infrastructure - 10 - ApplicationDbContext, DataSource, DataSourceKey, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, IPermissionGrantStore, PermissionGrant, PermissionGrantSettings, Result + 11 + ApplicationDbContext, DataSource, DataSourceKey, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, IPermissionGrantStore, IUniqueConstraintViolationDetector, PermissionGrant, PermissionGrantSettings, Result 13 @@ -32692,66 +32977,66 @@

Manifest (by level, then assembly)

13 - InProcessEventBus + EFRepository<TEntity, TIdentifierType> MMCA.Common.Infrastructure 9 - IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IEventBus, IIntegrationEvent, MessageBusSettings, OutboxFinalizer, OutboxMessage, OutboxSettings + AuditableAggregateRootEntity<TIdentifierType>, AuditableBaseEntity<TIdentifierType>, EFReadRepository<TEntity, TIdentifierType>, IAuditableEntity, ICurrentUserService, IRepository<TEntity, TIdentifierType>, IRowVersioned, IUpdatePropertySetter<TEntity>, UpdatePropertySetterBuilder<TEntity> 13 - InternalCommandAdministration + EntityDataSourceRegistry MMCA.Common.Infrastructure - 14 - ApplicationDbContext, Error, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, IInternalCommandAdministration, IInternalCommandSignal, InternalCommandDeadLetter, InternalCommandMessage, InternalCommandsSettings, Result, TenancySettings, TenantDataSourceTarget, TenantDataSourceTargets + 10 + DataSource, DataSourceKey, IDataSourceResolver, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IEntityTypeConfigurationBase<TEntity, TIdentifierType>, NamespaceConventions, Snapshot, UseDatabaseAttribute, UseDataSourceAttribute 13 - InternalCommandCleanupService + EntityTypeConfigurationCosmos<TEntity, TIdentifierType> MMCA.Common.Infrastructure - 10 - ApplicationDbContext, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, InternalCommandMessage, InternalCommandsSettings, PeriodicBackgroundService, TenancySettings, TenantDataSourceTarget, TenantDataSourceTargets + 3 + AuditableBaseEntity<TIdentifierType>, DataSource, EntityTypeConfiguration<TEntity, TIdentifierType> 13 - InternalCommandProcessor + EntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType> MMCA.Common.Infrastructure - 19 - Activity, AmbientOrigin, ApplicationDbContext, ColumnWidth, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, IInternalCommand, IInternalCommandSignal, InternalCommandCycleResult, InternalCommandDispatcher, InternalCommandMessage, InternalCommandMetrics, InternalCommandsSettings, PollingLoop, Result, TenancySettings, TenantDataSourceTarget, TenantDataSourceTargets + 3 + AuditableBaseEntity<TIdentifierType>, DataSource, EntityTypeConfiguration<TEntity, TIdentifierType> 13 - InternalCommandScheduler + EntityTypeConfigurationSqlite<TEntity, TIdentifierType> MMCA.Common.Infrastructure - 15 - Activity, AmbientOrigin, Error, ICorrelationContext, ICurrentUserService, IDataSourceResolver, IDbContextFactory, IInternalCommand, IInternalCommandScheduler, IInternalCommandSignal, InternalCommandMessage, InternalCommandOrigin, InternalCommandsSettings, ITenantContext, Result + 3 + AuditableBaseEntity<TIdentifierType>, DataSource, EntityTypeConfiguration<TEntity, TIdentifierType> 13 - OutboxAdministration + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> MMCA.Common.Infrastructure - 14 - ApplicationDbContext, Error, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, IOutboxAdministration, IOutboxSignal, OutboxDeadLetter, OutboxMessage, OutboxSettings, Result, TenancySettings, TenantDataSourceTarget, TenantDataSourceTargets + 3 + AuditableBaseEntity<TIdentifierType>, DataSource, EntityTypeConfiguration<TEntity, TIdentifierType> 13 - OutboxCleanupService + InProcessEventBus MMCA.Common.Infrastructure - 12 - ApplicationDbContext, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, InboxMessage, MessageBusSettings, OutboxMessage, OutboxSettings, PeriodicBackgroundService, TenancySettings, TenantDataSourceTarget, TenantDataSourceTargets + 9 + IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IEventBus, IIntegrationEvent, MessageBusSettings, OutboxFinalizer, OutboxMessage, OutboxSettings 13 - OutboxProcessor + InternalCommandScheduler MMCA.Common.Infrastructure - 21 - Activity, ApplicationDbContext, BrokerMetrics, BrokerResilienceDefaults, DataSourceKey, Event, IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IEntityDataSourceRegistry, IIntegrationEvent, IMessageBus, IOutboxSignal, OutboxCycleResult, OutboxMessage, OutboxMetrics, OutboxSettings, PollingLoop, TenancySettings, TenantDataSourceTarget …(+1) + 11 + AmbientOrigin, Error, IDataSourceResolver, IDbContextFactory, IInternalCommand, IInternalCommandScheduler, IInternalCommandSignal, InternalCommandMessage, InternalCommandOriginCapture, InternalCommandsSettings, Result 13 PhysicalDbContextFactory MMCA.Common.Infrastructure - 11 - ApplicationDbContext, CosmosDbContext, DataSource, DataSourceKey, IDataSourceResolver, IEntityConfigurationAssemblyProvider, IPhysicalDbContextFactory, PhysicalDataSource, PostgreSQLDbContext, SqliteDbContext, SQLServerDbContext + 7 + ApplicationDbContext, DataSourceEngines, DataSourceKey, IDataSourceResolver, IEntityConfigurationAssemblyProvider, IPhysicalDbContextFactory, PhysicalDataSource 13 @@ -32769,6 +33054,13 @@

Manifest (by level, then assembly)

13 + TenancySettingsValidator + MMCA.Common.Infrastructure + 8 + DataSource, DataSourceEngines, DataSourceKey, IDataSourceResolver, TenancySettings, TenantDataSourceOverrideSettings, TenantEntrySettings, TenantResolutionStrategy + + + 13 UnitOfWork MMCA.Common.Infrastructure 8 @@ -32776,38 +33068,38 @@

Manifest (by level, then assembly)

13 - ApplicationDbContextTenantFilterTests + AdminTestContext MMCA.Common.Infrastructure.Tests - 8 - ApplicationDbContext, EFReadRepository<TEntity, TIdentifierType>, IAuditableEntity, PlainThing, TenantDetail, TenantOnlyThing, TenantTestContext, TenantThing + 10 + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NoAssemblies, OutboxMessage, TestPhysicalDataSources 13 - ApplicationDbContextTests + AuditTrailModelGateTests MMCA.Common.Infrastructure.Tests - 4 - ApplicationDbContext, DataSource, TestApplicationDbContext, TestEntity + 3 + AuditTrailEntry, DataSourceKey, GateTestContext 13 - AuditSaveChangesInterceptorTests + AuditTrailTestContext MMCA.Common.Infrastructure.Tests - 4 - AuditSaveChangesInterceptor, FakeTimeProvider, TestAuditDbContext, TestAuditEntity + 17 + ApplicationDbContext, AuditedThing, AuditSaveChangesInterceptor, AuditTrailEntry, AuditTrailSaveChangesInterceptor, CompositeKeyThing, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FailingSaveInterceptor, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, OverridingPiiThing, PlainThing, TestPhysicalDataSources 13 - AuditTrailModelGateTests + CleanupTestContext MMCA.Common.Infrastructure.Tests - 3 - AuditTrailEntry, DataSourceKey, GateTestContext + 13 + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, InboxMessage, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, NullAssemblyProvider, OutboxMessage, TestPhysicalDataSources 13 - AuditTrailSaveChangesInterceptorTests + CommitFailingDbContext MMCA.Common.Infrastructure.Tests 13 - AuditedThing, AuditTrailEntry, AuditTrailSaveChangesInterceptor, AuditTrailTestContext, AuditTrailTestHarness, CompositeKeyThing, Email, FakeTimeProvider, InboxMessage, OutboxMessage, PiiRedactor, PlainThing, ScheduledJobEntry + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FailingDatabaseFacade, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, OutboxMessage, TestAggregate, TestPhysicalDataSources 13 @@ -32818,38 +33110,45 @@

Manifest (by level, then assembly)

13 - DependencyInjectionAdditionalTests + DataSourceServiceAdditionalTests + MMCA.Common.Infrastructure.Tests + 7 + DataSource, DataSourceKey, DataSourceService, FakeEntity, FakeEntity, IEntityDataSourceRegistry, UnregisteredEntity + + + 13 + DataSourceServiceTests MMCA.Common.Infrastructure.Tests 6 - EntityConfigurationOptions, IDataSourceService, IDbContextFactory, IQueryableExecutor, IRepositoryFactory, IUnitOfWork + DataSource, DataSourceKey, DataSourceService, FakeEntity, FakeEntity, IEntityDataSourceRegistry 13 - DomainEventCaptureExclusionTests + DefaultDataSourceResolver MMCA.Common.Infrastructure.Tests - 7 - DomainEventSaveChangesInterceptor, ExclusionAggregate, ExclusionEvent, ExclusionTestDbContext, IDomainEvent, IDomainEventDispatcher, IOutboxSignal + 4 + DataSource, DataSourceKey, IDataSourceResolver, PhysicalDataSource 13 - DomainEventSaveChangesInterceptorOutboxRoutingTests + DeleteBehaviorTestDbContext MMCA.Common.Infrastructure.Tests - 11 - DomainEventSaveChangesInterceptor, FakeTimeProvider, IDomainEvent, IDomainEventDispatcher, IOutboxSignal, OutboxMessage, OutboxRoutingTestDbContext, TestAggregate, TestIntegrationEvent, TestLocalEvent, TestOrderedEvent + 14 + ApplicationDbContext, AuditSaveChangesInterceptor, CascadingChild, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NoModuleAssemblies, NoModuleAssemblies, OptionalChild, Parent, RequiredChild, TestPhysicalDataSources 13 - DomainEventSaveChangesInterceptorTests + DependencyInjectionAdditionalTests MMCA.Common.Infrastructure.Tests - 7 - DomainEventSaveChangesInterceptor, IDomainEvent, IDomainEventDispatcher, IOutboxSignal, TestAggregate, TestDomainEvent, TestDomainEventDbContext + 6 + EntityConfigurationOptions, IDataSourceService, IDbContextFactory, IQueryableExecutor, IRepositoryFactory, IUnitOfWork 13 - EFReadRepositoryGetByIdFilterTests + DetectionTestDbContext MMCA.Common.Infrastructure.Tests - 4 - EFReadRepository<TEntity, TIdentifierType>, SoftDeletableTestEntity, SoftDeleteTestDbContext, SoftDeleteTestDbContext + 12 + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources, Widget, Widget 13 @@ -32895,6 +33194,55 @@

Manifest (by level, then assembly)

13 + ExclusionTestDbContext + MMCA.Common.Infrastructure.Tests + 9 + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, ExclusionAggregate, IEntityDataSourceRegistry, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources + + + 13 + FailingDatabaseFacade + MMCA.Common.Infrastructure.Tests + 2 + AlwaysRetryExecutionStrategy, CommitFailingDbContext + + + 13 + FailingSaveInterceptor + MMCA.Common.Infrastructure.Tests + 1 + AuditTrailTestContext + + + 13 + FailingSaveInterceptor + MMCA.Common.Infrastructure.Tests + 1 + OutboxRoutingTestDbContext + + + 13 + FixedEngineResolver + MMCA.Common.Infrastructure.Tests + 4 + DataSource, DataSourceKey, IDataSourceResolver, PhysicalDataSource + + + 13 + GrantTestContext + MMCA.Common.Infrastructure.Tests + 10 + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources + + + 13 + InboxTestDbContext + MMCA.Common.Infrastructure.Tests + 4 + ApplicationDbContext, IEntityConfigurationAssemblyProvider, InboxMessage, TestPhysicalDataSources + + + 13 InternalCommandModelTests MMCA.Common.Infrastructure.Tests 6 @@ -32902,6 +33250,20 @@

Manifest (by level, then assembly)

13 + InternalCommandTestContext + MMCA.Common.Infrastructure.Tests + 11 + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, InternalCommandMessage, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources + + + 13 + KeysetQueryBuilderNullOrderingTests + MMCA.Common.Infrastructure.Tests + 3 + KeysetQueryBuilder, SpecificationTestDbContext, SpecTestEntity + + + 13 KeysetQueryBuilderSqlTests MMCA.Common.Infrastructure.Tests 3 @@ -32909,6 +33271,13 @@

Manifest (by level, then assembly)

13 + MidSaveContextCreatingDbContext + MMCA.Common.Infrastructure.Tests + 10 + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IOutboxSignal, ReentrantSaveInterceptor, TestPhysicalDataSources + + + 13 Mocks MMCA.Common.Infrastructure.Tests 4 @@ -32923,6 +33292,34 @@

Manifest (by level, then assembly)

13 + NoSessionTableContext + MMCA.Common.Infrastructure.Tests + 6 + ApplicationDbContext, NullAssemblyProvider, NullAssemblyProvider, NullAssemblyProvider, OutboxMessage, TestPhysicalDataSources + + + 13 + OrderingTestContext + MMCA.Common.Infrastructure.Tests + 10 + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NoAssemblies, OutboxMessage, TestPhysicalDataSources + + + 13 + OutboxRoutingTestDbContext + MMCA.Common.Infrastructure.Tests + 11 + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FailingSaveInterceptor, IEntityDataSourceRegistry, NullAssemblyProvider, NullAssemblyProvider, OutboxMessage, TestAggregate, TestPhysicalDataSources + + + 13 + OutboxTestDbContext + MMCA.Common.Infrastructure.Tests + 4 + ApplicationDbContext, IEntityConfigurationAssemblyProvider, OutboxMessage, TestPhysicalDataSources + + + 13 PermissionGrantModelGateTests MMCA.Common.Infrastructure.Tests 19 @@ -32930,10 +33327,24 @@

Manifest (by level, then assembly)

13 - QueryParameterizationTests + PortableThingConfiguration MMCA.Common.Infrastructure.Tests 3 - QueryFieldService, QueryFilterService, QueryShapeTestDbContext + DataSource, EntityTypeConfiguration<TEntity, TIdentifierType>, PortableThing + + + 13 + QueryShapeTestDbContext + MMCA.Common.Infrastructure.Tests + 11 + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, Product, TestPhysicalDataSources + + + 13 + ReentrantSaveInterceptor + MMCA.Common.Infrastructure.Tests + 1 + MidSaveContextCreatingDbContext 13 @@ -32944,45 +33355,45 @@

Manifest (by level, then assembly)

13 - RestrictDeleteByDefaultConventionTests + RestoreTestDbContext MMCA.Common.Infrastructure.Tests - 6 - CascadingChild, DeleteBehaviorTestDbContext, OptionalChild, Parent, RequiredChild, RestrictDeleteByDefaultConvention + 4 + ApplicationDbContext, IEntityConfigurationAssemblyProvider, OutboxMessage, TestPhysicalDataSources 13 - SaveChangeDetectionTests + SchedulerModelGateTests MMCA.Common.Infrastructure.Tests 3 - DetectionTestDbContext, Widget, Widget + DataSourceKey, GateTestContext, ScheduledJobEntry 13 - SchedulerModelGateTests + SchedulerTestContext MMCA.Common.Infrastructure.Tests - 3 - DataSourceKey, GateTestContext, ScheduledJobEntry + 10 + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, ScheduledJobEntry, TestPhysicalDataSources 13 - SingleContextFactory + SessionCleanupTestContext MMCA.Common.Infrastructure.Tests - 3 - ApplicationDbContext, DataSourceKey, IDbContextFactory + 5 + ApplicationDbContext, NullAssemblyProvider, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources 13 - SoftDeleteQueryFilterTests + SingleContextFactory MMCA.Common.Infrastructure.Tests - 2 - SoftDeletableEntity, SoftDeleteTestDbContext + 3 + ApplicationDbContext, DataSourceKey, IDbContextFactory 13 - SoftDeleteUniqueIndexConventionTests + SoftDeleteTestDbContext MMCA.Common.Infrastructure.Tests - 5 - AlreadySoftDeleteFilteredEntity, BracketQuotedFilterEntity, FilteredIndexEntity, UniqueIndexTestDbContext, UniqueNamedEntity + 11 + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, SoftDeletableEntity, SoftDeleteTestDbContext, TestPhysicalDataSources 13 @@ -33000,10 +33411,73 @@

Manifest (by level, then assembly)

13 - TenantSaveChangesInterceptorTests + StampTestDbContext MMCA.Common.Infrastructure.Tests - 5 - CrossTenantWriteException, PlainThing, TenantTestContext, TenantThing, TrailedTenantThing + 11 + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, StampedEntity, TestPhysicalDataSources + + + 13 + TenantTestContext + MMCA.Common.Infrastructure.Tests + 17 + ApplicationDbContext, AuditSaveChangesInterceptor, AuditTrailEntry, AuditTrailSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, PlainThing, TenantOnlyThing, TenantSaveChangesInterceptor, TenantThing, TestPhysicalDataSources, TrailedTenantThing + + + 13 + TestApplicationDbContext + MMCA.Common.Infrastructure.Tests + 10 + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IOutboxSignal, TestEntity, TestPhysicalDataSources + + + 13 + TestAuditDbContext + MMCA.Common.Infrastructure.Tests + 11 + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, TestAuditEntity, TestPhysicalDataSources + + + 13 + TestDomainEventDbContext + MMCA.Common.Infrastructure.Tests + 9 + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IEntityDataSourceRegistry, NullAssemblyProvider, NullAssemblyProvider, TestAggregate, TestPhysicalDataSources + + + 13 + TestNonOutboxContext + MMCA.Common.Infrastructure.Tests + 9 + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, TestPhysicalDataSources + + + 13 + TestOutboxContext + MMCA.Common.Infrastructure.Tests + 10 + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, OutboxMessage, TestPhysicalDataSources + + + 13 + TransactionTestDbContext + MMCA.Common.Infrastructure.Tests + 14 + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, InternalCommandMessage, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, NullAssemblyProvider, OutboxMessage, TestAggregate, TestPhysicalDataSources + + + 13 + UniqueIndexTestDbContext + MMCA.Common.Infrastructure.Tests + 15 + AlreadySoftDeleteFilteredEntity, ApplicationDbContext, AuditSaveChangesInterceptor, BracketQuotedFilterEntity, DataSource, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FilteredIndexEntity, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NullAssemblyProvider, NullAssemblyProvider, TestPhysicalDataSources, UniqueNamedEntity + + + 13 + WidgetContext + MMCA.Common.Infrastructure.Tests + 10 + ApplicationDbContext, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NoModuleAssemblies, TestPhysicalDataSources, Widget 13 @@ -33035,10 +33509,10 @@

Manifest (by level, then assembly)

13 - CreateMigrationProofTable - MMCA.Common.Infrastructure.Tests.MigrationsFixture - 1 - SqliteDbContext + LoadStack + MMCA.Common.LoadTests + 5 + ApplicationDbContext, DataSource, DataSourceKey, IDataSourceResolver, IDbContextFactory 13 @@ -33142,15 +33616,71 @@

Manifest (by level, then assembly)

13 WebVitalsE2ETests MMCA.Common.UI.E2E.Tests + 6 + GalleryAxeTestBase, GalleryHostFixture, InpProbe, PlaywrightFixture, WebVitalsBudget, WebVitalsSample + + + 13 + SameOriginApiProxyEndpoint + MMCA.Common.UI.Web + 11 + ICookieSessionRefresher, ISessionCookieStore, ProxyResponseMode, SameOriginApiProxySettings, SameOriginProxyHeaders, SameOriginProxyInvoker, SameOriginProxyTransformer, SessionClaimsToken, SessionCookieEndpoints, SessionRefreshOutcome, SessionRefreshStatus + + + 13 + ServerTokenStorageService + MMCA.Common.UI.Web 5 - GalleryAxeTestBase, GalleryHostFixture, PlaywrightFixture, WebVitalsBudget, WebVitalsSample + CookieTokenReader, ISessionCookieSync, ITokenRefresher, ITokenStorageService, JwtTokenInfo - 14 - ServiceModels - MMCA.ADC.Architecture.Tests + 13 + SameOriginApiProxyAuthFlowTests + MMCA.Common.UI.Web.Tests + 7 + FakeGateway, Jwt, LoginRequest, ProxyHost, SessionCookieEndpoints, SessionHandoffProtector, SessionTokenResponse + + + 13 + SameOriginApiProxyCsrfTests + MMCA.Common.UI.Web.Tests 3 - DataSourceEntrySettings, DefaultEntityConfigurationAssemblyProvider, DesignTimeDbContextHelper + FakeGateway, Jwt, ProxyHost + + + 13 + SameOriginApiProxyHubTests + MMCA.Common.UI.Web.Tests + 5 + Address, FakeGateway, Jwt, ProxyHost, SessionCookieEndpoints + + + 13 + SameOriginApiProxyOptInTests + MMCA.Common.UI.Web.Tests + 10 + FakeGateway, HandoffSessionCookieSync, HandoffTokenRefresher, ISessionCookieSync, ITokenRefresher, JsFetchSessionCookieSync, ProxyHost, SameOriginApiProxySettings, SameOriginProxyTokenRefresher, SessionCookieSettings + + + 13 + SameOriginApiProxyOriginTests + MMCA.Common.UI.Web.Tests + 3 + FakeGateway, Jwt, ProxyHost + + + 13 + SameOriginApiProxyRefreshOutcomeTests + MMCA.Common.UI.Web.Tests + 3 + FakeGateway, Jwt, ProxyHost + + + 13 + SameOriginApiProxyTokenTests + MMCA.Common.UI.Web.Tests + 4 + FakeGateway, Jwt, ProxyHost, SessionCookieEndpoints 14 @@ -33189,6 +33719,13 @@

Manifest (by level, then assembly)

14 + UpdateEventQuestionAnswerHandler + MMCA.ADC.Conference.Application + 12 + Error, Event, EventQuestionAnswer, EventQuestionAnswerRules, ICurrentUserService, IUnitOfWork, MutateEntityHandlerBase<TCommand, TEntity, TIdentifierType>, Question, Result, RoleNames, UnitOfWork, UpdateEventQuestionAnswerCommand + + + 14 UpdateQuestionHandler MMCA.ADC.Conference.Application 12 @@ -33196,6 +33733,13 @@

Manifest (by level, then assembly)

14 + UpdateSessionQuestionAnswerHandler + MMCA.ADC.Conference.Application + 13 + Error, Event, ICurrentUserService, IUnitOfWork, MutateEntityHandlerBase<TCommand, TEntity, TIdentifierType>, Question, Result, RoleNames, Session, SessionQuestionAnswer, SessionQuestionAnswerRules, UnitOfWork, UpdateSessionQuestionAnswerCommand + + + 14 CategorySyncStrategyTests MMCA.ADC.Conference.Application.Tests 10 @@ -33226,8 +33770,141 @@

Manifest (by level, then assembly)

14 SpeakerSyncStrategyTests MMCA.ADC.Conference.Application.Tests - 11 - Event, EventSpeaker, IReadRepository<TEntity, TIdentifierType>, IRepository<TEntity, TIdentifierType>, IUnitOfWork, SessionizeResponse, SessionizeSpeaker, SessionizeSyncContext, Speaker, SpeakerSyncStrategy, UnitOfWork + 12 + Event, EventSpeaker, IReadRepository<TEntity, TIdentifierType>, IRepository<TEntity, TIdentifierType>, IUnitOfWork, SessionizeLink, SessionizeResponse, SessionizeSpeaker, SessionizeSyncContext, Speaker, SpeakerSyncStrategy, UnitOfWork + + + 14 + ActivityConfiguration + MMCA.ADC.Conference.Infrastructure + 3 + Activity, ActivityInvariants, EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> + + + 14 + CategoryItemConfiguration + MMCA.ADC.Conference.Infrastructure + 3 + CategoryInvariants, CategoryItem, EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> + + + 14 + ConferenceCategoryConfiguration + MMCA.ADC.Conference.Infrastructure + 3 + Category, CategoryInvariants, EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> + + + 14 + EventConfiguration + MMCA.ADC.Conference.Infrastructure + 4 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, Event, EventInvariants, NullableEmailValueConverter + + + 14 + EventQuestionAnswerConfiguration + MMCA.ADC.Conference.Infrastructure + 3 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, EventInvariants, EventQuestionAnswer + + + 14 + EventSpeakerConfiguration + MMCA.ADC.Conference.Infrastructure + 2 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, EventSpeaker + + + 14 + PartnerConfiguration + MMCA.ADC.Conference.Infrastructure + 3 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, Partner, PartnerInvariants + + + 14 + QuestionConfiguration + MMCA.ADC.Conference.Infrastructure + 3 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, Question, QuestionInvariants + + + 14 + RoomConfiguration + MMCA.ADC.Conference.Infrastructure + 3 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, EventInvariants, Room + + + 14 + SessionAiScoreConfiguration + MMCA.ADC.Conference.Infrastructure + 2 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SessionAiScore + + + 14 + SessionAssetConfiguration + MMCA.ADC.Conference.Infrastructure + 5 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, Event, Session, SessionAsset, SessionAssetInvariants + + + 14 + SessionCategoryItemConfiguration + MMCA.ADC.Conference.Infrastructure + 2 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SessionCategoryItem + + + 14 + SessionConfiguration + MMCA.ADC.Conference.Infrastructure + 3 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, Session, SessionInvariants + + + 14 + SessionQuestionAnswerConfiguration + MMCA.ADC.Conference.Infrastructure + 3 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SessionInvariants, SessionQuestionAnswer + + + 14 + SessionSpeakerConfiguration + MMCA.ADC.Conference.Infrastructure + 2 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SessionSpeaker + + + 14 + SpeakerCategoryItemConfiguration + MMCA.ADC.Conference.Infrastructure + 2 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SpeakerCategoryItem + + + 14 + SpeakerConfiguration + MMCA.ADC.Conference.Infrastructure + 4 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, NullableEmailValueConverter, Speaker, SpeakerInvariants + + + 14 + SpeakerQuestionAnswerConfiguration + MMCA.ADC.Conference.Infrastructure + 3 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SpeakerInvariants, SpeakerQuestionAnswer + + + 14 + SponsorConfiguration + MMCA.ADC.Conference.Infrastructure + 3 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, Sponsor, SponsorInvariants 14 @@ -33273,6 +33950,76 @@

Manifest (by level, then assembly)

14 + AttendeeBadgeConfiguration + MMCA.ADC.Engagement.Infrastructure + 2 + AttendeeBadge, EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> + + + 14 + CheckInConfiguration + MMCA.ADC.Engagement.Infrastructure + 2 + CheckIn, EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> + + + 14 + LeaderboardOptInConfiguration + MMCA.ADC.Engagement.Infrastructure + 2 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, LeaderboardOptIn + + + 14 + LivePollConfiguration + MMCA.ADC.Engagement.Infrastructure + 3 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, LivePoll, LivePollInvariants + + + 14 + LivePollOptionConfiguration + MMCA.ADC.Engagement.Infrastructure + 3 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, LivePollInvariants, LivePollOption + + + 14 + LivePollVoteConfiguration + MMCA.ADC.Engagement.Infrastructure + 2 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, LivePollVote + + + 14 + PointsEntryConfiguration + MMCA.ADC.Engagement.Infrastructure + 3 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, PointsEntry, PointsSubjectKeys + + + 14 + SessionQuestionConfiguration + MMCA.ADC.Engagement.Infrastructure + 3 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SessionQuestion, SessionQuestionInvariants + + + 14 + SessionQuestionUpvoteConfiguration + MMCA.ADC.Engagement.Infrastructure + 2 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SessionQuestionUpvote + + + 14 + UserSessionBookmarkConfiguration + MMCA.ADC.Engagement.Infrastructure + 2 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, UserSessionBookmark + + + 14 EngagementTestWebApplicationFactory MMCA.ADC.Engagement.IntegrationTests 9 @@ -33317,8 +34064,8 @@

Manifest (by level, then assembly)

14 AuthenticationService MMCA.ADC.Identity.Application - 21 - AuthenticationResponse, AuthenticationServiceBase<TUser>, AuthenticationValidators, Email, EmailConfirmationSettings, Error, IAuthenticationService, IExternalLoginEmailVerifier, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, ITokenService, IUnitOfWork, RefreshSessionSettings, RegisterRequest, Result, TokenService, UnitOfWork, User, UserRegistered …(+1) + 19 + AuthenticationResponse, AuthenticationServiceBase<TUser>, AuthenticationValidators, Email, EmailConfirmationSettings, Error, IAuthenticationService, IAuthSessionIssuer, IExternalLoginEmailVerifier, ILoginProtectionService, IPasswordHasher, IUnitOfWork, RegisterRequest, Result, TokenService, UnitOfWork, User, UserRegistered, UserRole 14 @@ -33336,6 +34083,20 @@

Manifest (by level, then assembly)

14 + UserConfiguration + MMCA.ADC.Identity.Infrastructure + 4 + EmailValueConverter, EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, User, UserInvariants + + + 14 + RefreshSessionModelGateTests + MMCA.ADC.Identity.Infrastructure.Tests + 19 + AuditSaveChangesInterceptor, CaseConference, CaseIdentity, CaseWrongSource, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourceResolver, DataSourcesSettings, DomainEventSaveChangesInterceptor, GateContext<TCase>, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IOutboxSignal, PhysicalDataSource, RefreshSession, RefreshSessionSettings + + + 14 IdentityTestWebApplicationFactory MMCA.ADC.Identity.IntegrationTests 6 @@ -33357,17 +34118,45 @@

Manifest (by level, then assembly)

14 - DatabaseInitializationExtensionsTests + DependencyInjection + MMCA.Common.API + 28 + CookieSessionRefresher, CookieTokenReader, CurrencyJsonConverter, CurrentUserTargetingContextAccessor, DbUpdateExceptionHandler, DisabledFeatureHandler, DomainExceptionHandler, EnumerationJsonConverterFactory, ErrorLocalizer, ErrorResources, ErrorResourceSource, GlobalExceptionHandler, ICookieSessionRefresher, IdempotencyFilter, IdempotencySettings, IErrorLocalizer, ISessionCookieStore, ModuleControllerFeatureProvider, ModuleLoader, ModulesSettings …(+8) + + + 14 + SessionCookieAuthenticationExtensions + MMCA.Common.API + 1 + SessionCookieAuthenticationHandler + + + 14 + InitTestMigratedWidgetConfiguration MMCA.Common.API.Tests - 27 - ApplicationSettings, AuditSaveChangesInterceptor, ConnectionStringSettings, CreateMigrationProofTable, DataSource, DataSourceEntrySettings, DataSourceResolver, DataSourcesSettings, DbContextFactory, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, FixedAssemblyProvider, ICurrentUserService, IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, InitTestMigratedWidget, InitTestWidget …(+7) + 2 + EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, InitTestMigratedWidget 14 - FixedAssemblyProvider + InitTestWidgetConfiguration MMCA.Common.API.Tests 2 - DatabaseInitializationExtensionsTests, IEntityConfigurationAssemblyProvider + EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, InitTestWidget + + + 14 + RefresherHarness + MMCA.Common.API.Tests + 4 + CookieSessionRefresher, SessionCookieSettings, StubHttpClientFactory, StubHttpMessageHandler + + + 14 + SessionCookieAuthenticationHandlerTests + MMCA.Common.API.Tests + 4 + CookieTokenReader, FakeTimeProvider, SessionCookieAuthenticationHandler, SessionCookieEndpoints 14 @@ -33378,6 +34167,13 @@

Manifest (by level, then assembly)

14 + Harness + MMCA.Common.Application.Tests + 8 + IRepository<TEntity, TIdentifierType>, IUnitOfWork, IUpdatePropertySetter<TEntity>, MarkAllNotificationsReadHandler, PushNotification, RecordingSetter, UnitOfWork, UserNotification + + + 14 TestRetryingRenameHandler MMCA.Common.Application.Tests 7 @@ -33385,17 +34181,24 @@

Manifest (by level, then assembly)

14 - DeleteBehaviorConventionTests + DataResidencyTestsBaseTests MMCA.Common.Architecture.Tests - 3 - ArchitectureRules, DeleteBehaviorConventionTestsBase, FrameworkModels + 1 + Probe 14 - DependencyInjection + DbContextFactory + MMCA.Common.Infrastructure + 22 + AmbientOrigin, ApplicationDbContext, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, Entry, ExplicitKeyInsertGroup, ICorrelationContext, ICurrentUserService, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, IExplicitKeyInsertDialect, InternalCommandMessage, IPhysicalDbContextFactory, ITenantContext, OutboxOrigin, PhysicalDataSource, Result, TenancySettings …(+2) + + + 14 + DesignTimeDbContextHelper MMCA.Common.Infrastructure - 166 - ApplicationNamespace, AuditSaveChangesInterceptor, AuditTrailCleanupJob, AuditTrailReader, AuditTrailSaveChangesInterceptor, AuditTrailSettings, AzureBlobFileStorageService, AzureNotificationHubDeviceRegistrar, AzureNotificationHubNativePushSender, BrokerEventBus, BrokerMessageBus, CacheKeyNamespace, CacheKeyPrefixOptions, CacheSettings, ClaimBasedUserIdProvider, ClassReference, ConnectionStringSettings, ConnectionStringSettingsValidator, CorrelationContext, CurrentUserService …(+146) + 28 + AuditSaveChangesInterceptor, AuditTrailSaveChangesInterceptor, AuditTrailSettings, DataSource, DataSourceKey, DataSourceResolver, DataSourcesSettings, DesignTimeDbContextOptions, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, ExplicitAssemblyProvider, IDataSourceResolver, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IOutboxSignal, NullDomainEventDispatcher, OutboxSignal, PermissionGrantModelGate, PermissionGrantSettings …(+8) 14 @@ -33406,6 +34209,34 @@

Manifest (by level, then assembly)

14 + PushNotificationConfiguration + MMCA.Common.Infrastructure + 4 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, PushNotification, PushNotificationInvariants, SoftDeleteFilterSql + + + 14 + RepositoryFactory + MMCA.Common.Infrastructure + 10 + ApplicationSettings, AuditableAggregateRootEntity<TIdentifierType>, AuditableBaseEntity<TIdentifierType>, EFReadRepository<TEntity, TIdentifierType>, EFReadRepositoryDecorator<TEntity, TIdentifierType>, EFRepository<TEntity, TIdentifierType>, EFRepositoryDecorator<TEntity, TIdentifierType>, IReadRepository<TEntity, TIdentifierType>, IRepository<TEntity, TIdentifierType>, IRepositoryFactory + + + 14 + TenantDataSourceTargets + MMCA.Common.Infrastructure + 5 + DataSourceKey, ITenantContext, TenancySettings, TenancySettingsValidator, TenantDataSourceTarget + + + 14 + UserNotificationConfiguration + MMCA.Common.Infrastructure + 2 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, UserNotification + + + 14 FixedAssemblyProvider MMCA.Common.Infrastructure.PostgreSQL.Tests 2 @@ -33413,6 +34244,13 @@

Manifest (by level, then assembly)

14 + PgThingConfiguration + MMCA.Common.Infrastructure.PostgreSQL.Tests + 2 + EntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType>, PgThing + + + 14 PostgreSQLPersistenceTests MMCA.Common.Infrastructure.PostgreSQL.Tests 20 @@ -33420,10 +34258,17 @@

Manifest (by level, then assembly)

14 - AddAuditTrailTests + SqlThingConfiguration + MMCA.Common.Infrastructure.SQLServer.Tests + 2 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SqlThing + + + 14 + AddMultiTenancyTests MMCA.Common.Infrastructure.Tests - 6 - AuditTrailCleanupJob, AuditTrailReader, AuditTrailSaveChangesInterceptor, AuditTrailSettings, IAuditTrailReader, IScheduledJob + 11 + ConnectionStringSettings, DataSourceResolver, DataSourcesSettings, ITenantContext, TenancySettings, TenancySettingsValidator, TenantContext, TenantDataSourceOverrideSettings, TenantEntrySettings, TenantResolutionStrategy, TenantSaveChangesInterceptor 14 @@ -33434,129 +34279,136 @@

Manifest (by level, then assembly)

14 - BrokerEventBusTests + ApplicationDbContextTenantFilterTests MMCA.Common.Infrastructure.Tests - 19 - ApplicationDbContext, AuditSaveChangesInterceptor, BrokerEventBus, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IEntityDataSourceRegistry, IIntegrationEvent, IOutboxSignal, Mocks, OutboxMessage, OutboxSettings, TestIntegrationEvent, TestNonOutboxContext, TestOutboxContext + 8 + ApplicationDbContext, EFReadRepository<TEntity, TIdentifierType>, IAuditableEntity, PlainThing, TenantDetail, TenantOnlyThing, TenantTestContext, TenantThing 14 - BrokerMessageBusTests + ApplicationDbContextTests MMCA.Common.Infrastructure.Tests - 9 - BrokerMessageBus, ICorrelationContext, ICurrentUserService, IIntegrationEvent, ITenantContext, MessageHeaders, Mocks, OtherIntegrationEvent, TestIntegrationEvent + 4 + ApplicationDbContext, DataSource, TestApplicationDbContext, TestEntity 14 - CosmosConfigurationPortabilityTests + AuditSaveChangesInterceptorTests MMCA.Common.Infrastructure.Tests - 17 - AuditSaveChangesInterceptor, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceResolver, DataSourcesSettings, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, FixedAssemblyProvider, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, OutboxSignal, PhysicalDbContextFactory, PortablePrincipal, PortableThing + 4 + AuditSaveChangesInterceptor, FakeTimeProvider, TestAuditDbContext, TestAuditEntity 14 - CronosNextOccurrenceTests + AuditTrailSaveChangesInterceptorTests MMCA.Common.Infrastructure.Tests - 1 - ScheduledJobRunner + 14 + AuditedThing, AuditTrailEntry, AuditTrailSaveChangesInterceptor, AuditTrailTestContext, AuditTrailTestHarness, CompositeKeyThing, Email, FakeTimeProvider, InboxMessage, OutboxMessage, OverridingPiiThing, PiiRedactor, PlainThing, ScheduledJobEntry 14 - DbContextFactoryAdditionalTests + BrokerEventBusTests MMCA.Common.Infrastructure.Tests - 10 - DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, ICurrentUserService, IEntityDataSourceRegistry, IPhysicalDbContextFactory, ITenantContext, MidSaveContextCreatingDbContext, TenancySettings + 19 + ApplicationDbContext, AuditSaveChangesInterceptor, BrokerEventBus, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IEntityDataSourceRegistry, IIntegrationEvent, IOutboxSignal, Mocks, OutboxMessage, OutboxSettings, TestIntegrationEvent, TestNonOutboxContext, TestOutboxContext 14 - DbContextFactoryCommitAmbiguityTests + BrokerMessageBusTests MMCA.Common.Infrastructure.Tests - 16 - CommitFailingDbContext, DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, ICurrentUserService, IDomainEvent, IDomainEventDispatcher, IEntityDataSourceRegistry, IPhysicalDbContextFactory, ITenantContext, Result, TenancySettings, TestAggregate, TestLocalEvent, TransactionCommitAmbiguousException + 9 + BrokerMessageBus, ICorrelationContext, ICurrentUserService, IIntegrationEvent, ITenantContext, MessageHeaders, Mocks, OtherIntegrationEvent, TestIntegrationEvent 14 - DbContextFactoryMigrationTargetTests + CronosNextOccurrenceTests MMCA.Common.Infrastructure.Tests - 22 - AuditSaveChangesInterceptor, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourceResolver, DataSourcesSettings, DbContextFactory, DomainEventSaveChangesInterceptor, FixedSourcesRegistry, FixedSourcesRegistry, ICurrentUserService, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, ITenantContext, NoConfigurationAssemblyProvider, NoConfigurationAssemblyProvider, OutboxSignal …(+2) + 1 + ScheduledJobRunner 14 - DbContextFactorySaveIntegrityTests + DataSourceResolverTests MMCA.Common.Infrastructure.Tests - 14 - DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, ICurrentUserService, IDomainEvent, IDomainEventDispatcher, IEntityDataSourceRegistry, IntegrityAggregate, IntegrityEvent, IntegrityTestDbContext, IPhysicalDbContextFactory, ITenantContext, TenancySettings + 9 + AuditTrailSettings, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourceResolver, DataSourcesSettings, OutboxSettings, SchedulerSettings 14 - DbContextFactoryTenantTests + DependencyInjectionBrokerMessagingTests MMCA.Common.Infrastructure.Tests - 16 - ApplicationDbContext, DataSource, DataSourceKey, DbContextFactory, ICurrentUserService, IDataSourceResolver, IEntityDataSourceRegistry, IPhysicalDbContextFactory, ITenantContext, MutableTenantContext, PhysicalDataSource, TenancySettings, TenantContext, TenantDataSourceOverrideSettings, TenantEntrySettings, TenantTestContext + 6 + EfInboxStore, IInboxStore, InboxDisabledWarningService, MessageBusSettings, NoOpInboxStore, OrderPlacedConsumer 14 - DbContextFactoryTests + DependencyInjectionTests MMCA.Common.Infrastructure.Tests - 10 - ApplicationDbContext, DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, ICurrentUserService, IEntityDataSourceRegistry, IPhysicalDbContextFactory, ITenantContext, TenancySettings + 23 + CorrelationContext, CurrentUserService, DistributedCacheService, EntityConfigurationOptions, ICacheService, ICorrelationContext, ICurrentUserService, IDistributedLock, IEmailSender, IEventBus, ILiveChannelPublisher, InProcessDistributedLock, InProcessEventBus, IPasswordHasher, IPushNotificationSender, ITokenService, MemoryCacheService, NullLiveChannelPublisher, NullPushNotificationSender, PasswordHasher …(+3) 14 - DbContextFactoryTransactionTests + DesignAlphaEntityConfiguration MMCA.Common.Infrastructure.Tests - 17 - DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, Error, ICurrentUserService, IDomainEvent, IDomainEventDispatcher, IEntityDataSourceRegistry, IPhysicalDbContextFactory, ITenantContext, OutboxMessage, Result, TenancySettings, TestAggregate, TestLocalEvent, TransactionTestDbContext + 2 + DesignAlphaEntity, EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> 14 - DependencyInjectionBrokerMessagingTests + DesignBetaEntityConfiguration MMCA.Common.Infrastructure.Tests - 6 - EfInboxStore, IInboxStore, InboxDisabledWarningService, MessageBusSettings, NoOpInboxStore, OrderPlacedConsumer + 2 + DesignBetaEntity, EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> 14 - DependencyInjectionInfrastructureTests + DesignPostgreSQLEntityConfiguration MMCA.Common.Infrastructure.Tests - 15 - AuditSaveChangesInterceptor, ConnectionStringSettings, DomainEventSaveChangesInterceptor, EntityConfigurationOptions, IDataSourceService, IEntityConfigurationAssemblyProvider, IQueryableExecutor, IRepository<TEntity, TIdentifierType>, IRepositoryFactory, IUniqueConstraintViolationDetector, IUnitOfWork, OutboxProcessor, OutboxSettings, SmtpSettings, SqlServerUniqueConstraintViolationDetector + 2 + DesignPostgreSQLEntity, EntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType> 14 - DependencyInjectionOutboxGateTests + DesignSqliteEntityConfiguration MMCA.Common.Infrastructure.Tests - 3 - OutboxCleanupService, OutboxDisabledNoticeService, OutboxProcessor + 2 + DesignSqliteEntity, EntityTypeConfigurationSqlite<TEntity, TIdentifierType> 14 - DependencyInjectionTests + DomainEventCaptureExclusionTests MMCA.Common.Infrastructure.Tests - 23 - CorrelationContext, CurrentUserService, DistributedCacheService, EntityConfigurationOptions, ICacheService, ICorrelationContext, ICurrentUserService, IDistributedLock, IEmailSender, IEventBus, ILiveChannelPublisher, InProcessDistributedLock, InProcessEventBus, IPasswordHasher, IPushNotificationSender, ITokenService, MemoryCacheService, NullLiveChannelPublisher, NullPushNotificationSender, PasswordHasher …(+3) + 8 + DomainEventSaveChangesInterceptor, ExclusionAggregate, ExclusionEvent, ExclusionTestDbContext, IDomainEvent, IDomainEventDispatcher, IOutboxSignal, MessageBusSettings 14 - DesignTimeDbContextHelperTests + DomainEventSaveChangesInterceptorOutboxRoutingTests MMCA.Common.Infrastructure.Tests - 12 - ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DesignAlphaEntity, DesignBetaEntity, DesignPostgreSQLEntity, DesignSqliteEntity, DesignTimeDbContextHelper, DesignTimeDbContextOptions, PermissionGrant, RefreshSession + 11 + DomainEventSaveChangesInterceptor, FakeTimeProvider, IDomainEvent, IDomainEventDispatcher, IOutboxSignal, OutboxMessage, OutboxRoutingTestDbContext, TestAggregate, TestIntegrationEvent, TestLocalEvent, TestOrderedEvent 14 - DomainEventSaveChangesInterceptorOutboxDisabledTests + DomainEventSaveChangesInterceptorTests MMCA.Common.Infrastructure.Tests - 11 - DomainEventSaveChangesInterceptor, DomainEventSaveChangesInterceptorOutboxRoutingTests, IDomainEvent, IDomainEventDispatcher, IOutboxSignal, MessageBusSettings, OutboxMessage, OutboxRoutingTestDbContext, TestAggregate, TestIntegrationEvent, TestLocalEvent + 8 + DomainEventSaveChangesInterceptor, IDomainEvent, IDomainEventDispatcher, IOutboxSignal, MessageBusSettings, TestAggregate, TestDomainEvent, TestDomainEventDbContext 14 EfInboxStoreTests MMCA.Common.Infrastructure.Tests - 16 - ApplicationDbContext, AuditSaveChangesInterceptor, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, EfInboxStore, EmptyEntityDataSourceRegistry, IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, InboxMessage, InboxTestDbContext, IOutboxSignal, OutboxSettings + 17 + ApplicationDbContext, AuditSaveChangesInterceptor, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, EfInboxStore, EmptyEntityDataSourceRegistry, FakeTimeProvider, IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, InboxMessage, InboxTestDbContext, IOutboxSignal, OutboxSettings + + + 14 + EFPermissionGrantStoreTests + MMCA.Common.Infrastructure.Tests + 10 + ApplicationDbContext, DataSourceKey, EFPermissionGrantStore, EmptyEntityDataSourceRegistry, GrantTestContext, IDataSourceResolver, IDbContextFactory, PermissionGrant, PermissionGrantSettings, SqlServerUniqueConstraintViolationDetector 14 @@ -33567,17 +34419,31 @@

Manifest (by level, then assembly)

14 - EFRepositoryAuditStampTests + EFReadRepositoryGetByIdFilterTests MMCA.Common.Infrastructure.Tests - 15 - DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, EFRepository<TEntity, TIdentifierType>, ICurrentUserService, IDataSourceService, IEntityDataSourceRegistry, IPhysicalDbContextFactory, IRepositoryFactory, ITenantContext, StampedEntity, StampTestDbContext, TenancySettings, UnitOfWork + 4 + EFReadRepository<TEntity, TIdentifierType>, SoftDeletableTestEntity, SoftDeleteTestDbContext, SoftDeleteTestDbContext 14 - FixedAssemblyProvider + EFRepositoryAdditionalTests MMCA.Common.Infrastructure.Tests 3 - CosmosConfigurationPortabilityTests, IEntityConfigurationAssemblyProvider, MultiSourceSqliteIntegrationTests + EFRepository<TEntity, TIdentifierType>, TestDbContext, TestEntity + + + 14 + EFRepositoryConcurrencyTouchTests + MMCA.Common.Infrastructure.Tests + 3 + EFRepository<TEntity, TIdentifierType>, TestDbContext, TestEntity + + + 14 + EFRepositoryIntegrationTests + MMCA.Common.Infrastructure.Tests + 8 + EFReadRepository<TEntity, TIdentifierType>, EFRepository<TEntity, TIdentifierType>, FakeTimeProvider, IAuditableEntity, ICurrentUserService, TestChildEntity, TestDbContext, TestEntity 14 @@ -33609,73 +34475,101 @@

Manifest (by level, then assembly)

14 - InternalCommandTestHarness + MarkAllNotificationsReadHandlerTrackingTests MMCA.Common.Infrastructure.Tests - 28 - AnonymousCurrentUserService, ApplicationDbContext, CorrelationContext, DataSource, DataSourceKey, DefaultDataSourceResolver, EmptyEntityDataSourceRegistry, ExecutionLog, FakeTimeProvider, ICommandHandler<in TCommand, TResult>, ICorrelationContext, ICurrentUserService, IDbContextFactory, IInternalCommand, IInternalCommandSignal, ImpersonatingCurrentUserService, InternalCommandMessage, InternalCommandProcessor, InternalCommandScheduler, InternalCommandsSettings …(+8) + 9 + EFRepository<TEntity, TIdentifierType>, IUnitOfWork, MarkAllNotificationsReadCommand, MarkAllNotificationsReadHandler, NotificationTestDbContext, PushNotification, Result, SeededIds, UserNotification 14 - MigrationApplyProofTests + MultiSourceCustomerConfiguration + MMCA.Common.Infrastructure.Tests + 2 + EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, MultiSourceCustomer + + + 14 + MultiSourceOrderConfiguration + MMCA.Common.Infrastructure.Tests + 2 + EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, MultiSourceOrder + + + 14 + PortablePrincipalConfiguration + MMCA.Common.Infrastructure.Tests + 2 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, PortablePrincipal + + + 14 + PostgreSQLDbContextModelTests MMCA.Common.Infrastructure.Tests 21 - AuditSaveChangesInterceptor, ConnectionStringSettings, CreateMigrationProofTable, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourceResolver, DataSourcesSettings, DbContextFactory, DomainEventSaveChangesInterceptor, FixedSourcesRegistry, ICurrentUserService, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, ITenantContext, NoConfigurationAssemblyProvider, OutboxSignal, PhysicalDbContextFactory …(+1) + ApplicationDbContext, AuditSaveChangesInterceptor, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceResolver, DataSourcesSettings, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, FixedAssemblyProvider, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, OutboxMessage, OutboxSignal, PhysicalDbContextFactory, PostgreSQLDbContext, PostgresThing, SqlServerThing …(+1) 14 - Mocks + PostgresThingConfiguration + MMCA.Common.Infrastructure.Tests + 2 + EntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType>, PostgresThing + + + 14 + QueryParameterizationTests MMCA.Common.Infrastructure.Tests 3 - IDataSourceResolver, IEntityDataSourceRegistry, OutboxCleanupService + QueryFieldService, QueryFilterService, QueryShapeTestDbContext 14 - MultiSourceSqliteIntegrationTests + RegistryDuplicateConfigurationA MMCA.Common.Infrastructure.Tests - 28 - ApplicationSettings, AuditSaveChangesInterceptor, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceResolver, DataSourceService, DataSourcesSettings, DbContextFactory, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, FixedAssemblyProvider, ICurrentUserService, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, ITenantContext, MultiSourceCustomer, MultiSourceOrder …(+8) + 2 + EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, RegistryDuplicate 14 - OutboxAdministrationTests + RegistryDuplicateConfigurationB MMCA.Common.Infrastructure.Tests - 12 - AdminTestContext, DataSource, DataSourceKey, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, IOutboxSignal, OutboxAdministration, OutboxDeadLetter, OutboxMessage, OutboxSettings, Payload + 2 + EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, RegistryDuplicate 14 - OutboxProcessorContextRestoreTests + RegistryInvoiceConfiguration MMCA.Common.Infrastructure.Tests - 26 - AuditSaveChangesInterceptor, CapturingMessageBus, CorrelationContext, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, ICorrelationContext, ICurrentUserService, IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IMessageBus, IOutboxSignal, ITenantContext, OutboxMessage, OutboxOrigin, OutboxProcessor …(+6) + 2 + EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, RegistryInvoice 14 - OutboxProcessorOrderingTests + RegistryOrderConfiguration MMCA.Common.Infrastructure.Tests - 16 - DataSource, DataSourceKey, FakeTimeProvider, IDataSourceResolver, IDbContextFactory, IDomainEvent, IDomainEventDispatcher, IEntityDataSourceRegistry, IMessageBus, IOutboxSignal, OrderedTestEvent, OrderingTestContext, OutboxMessage, OutboxProcessor, OutboxSettings, Payload + 2 + EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, RegistryOrder 14 - OutboxProcessorTests + RegistrySqlServerEntityConfiguration MMCA.Common.Infrastructure.Tests - 25 - AuditSaveChangesInterceptor, BrokerResilienceDefaults, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FakeTimeProvider, IDataSourceResolver, IDbContextFactory, IDomainEvent, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IIntegrationEvent, IMessageBus, IOutboxSignal, OutboxCycleResult, OutboxMessage, OutboxProcessor, OutboxSettings …(+5) + 2 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, RegistrySqlServerEntity 14 - OutboxProcessorWaitTests + RestrictDeleteByDefaultConventionTests MMCA.Common.Infrastructure.Tests - 1 - OutboxProcessor + 6 + CascadingChild, DeleteBehaviorTestDbContext, OptionalChild, Parent, RequiredChild, RestrictDeleteByDefaultConvention 14 - PostgreSQLDbContextModelTests + SaveChangeDetectionTests MMCA.Common.Infrastructure.Tests - 21 - ApplicationDbContext, AuditSaveChangesInterceptor, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceResolver, DataSourcesSettings, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, FixedAssemblyProvider, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, OutboxMessage, OutboxSignal, PhysicalDbContextFactory, PostgreSQLDbContext, PostgresThing, SqlServerThing …(+1) + 3 + DetectionTestDbContext, Widget, Widget 14 @@ -33693,6 +34587,34 @@

Manifest (by level, then assembly)

14 + SoftDeleteQueryFilterTests + MMCA.Common.Infrastructure.Tests + 2 + SoftDeletableEntity, SoftDeleteTestDbContext + + + 14 + SoftDeleteUniqueIndexConventionTests + MMCA.Common.Infrastructure.Tests + 5 + AlreadySoftDeleteFilteredEntity, BracketQuotedFilterEntity, FilteredIndexEntity, UniqueIndexTestDbContext, UniqueNamedEntity + + + 14 + SqliteTestEntityConfig + MMCA.Common.Infrastructure.Tests + 2 + EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, SqliteTestEntity + + + 14 + SqlServerThingConfiguration + MMCA.Common.Infrastructure.Tests + 2 + EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SqlServerThing + + + 14 StronglyTypedIdPersistenceTests MMCA.Common.Infrastructure.Tests 15 @@ -33707,10 +34629,10 @@

Manifest (by level, then assembly)

14 - TenantDataSourceTargetTests + TenantSaveChangesInterceptorTests MMCA.Common.Infrastructure.Tests - 14 - DataSource, DataSourceKey, IDataSourceResolver, IEntityDataSourceRegistry, IOutboxSignal, MessageBusSettings, OutboxCleanupService, OutboxProcessor, OutboxSettings, TenancySettings, TenantDataSourceOverrideSettings, TenantDataSourceTarget, TenantDataSourceTargets, TenantEntrySettings + 5 + CrossTenantWriteException, PlainThing, TenantTestContext, TenantThing, TrailedTenantThing 14 @@ -33728,17 +34650,66 @@

Manifest (by level, then assembly)

14 + LoadItemConfiguration + MMCA.Common.LoadTests + 2 + EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, LoadItem + + + 14 + PagedQueryFixture + MMCA.Common.LoadTests + 11 + EntityQueryService<TEntity, TEntityDTO, TIdentifierType>, IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType>, IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, INavigationPopulator<in TEntity>, LoadItem, LoadItemDTO, LoadItemMapper, LoadItemNavigationPopulator, LoadStack, PagedCollectionResult<T>, PagedQuery + + + 14 HandlerTestBase<THandler> MMCA.Common.Testing 6 AuditableAggregateRootEntity<TIdentifierType>, AuditableBaseEntity<TIdentifierType>, IReadRepository<TEntity, TIdentifierType>, IRepository<TEntity, TIdentifierType>, IUnitOfWork, UnitOfWork + 14 + DependencyInjection + MMCA.Common.UI.Web + 11 + ApiSettings, BlazorCspPolicyProvider, BlazorCspSettings, BlazorCspSettingsValidator, GatewayRateLimitingSettings, ICspPolicyProvider, IFormFactor, ITokenStorageService, ServerTokenStorageService, TrustedCallerHandler, WebFormFactor + + + 14 + SameOriginApiProxyServiceExtensions + MMCA.Common.UI.Web + 12 + ApiSettings, HandoffSessionCookieSync, HandoffTokenRefresher, ISessionCookieSync, ITokenRefresher, SameOriginApiProxyEndpoint, SameOriginApiProxyMarker, SameOriginApiProxySettings, SameOriginApiProxySettingsValidator, SameOriginProxyInvoker, SessionCookieSettings, SessionHandoffProtector + + + 14 + SessionHandoffEndpoints + MMCA.Common.UI.Web + 5 + HandoffBody, ICookieSessionRefresher, ISessionCookieStore, SameOriginApiProxyEndpoint, SessionHandoffProtector + + + 14 + ServerTokenStorageServiceTests + MMCA.Common.UI.Web.Tests + 6 + CookieTokenReader, ISessionCookieSync, ITokenRefresher, Mocks, ServerTokenStorageService, SessionCookieEndpoints + + + 14 + WebFormFactorTests + MMCA.Common.UI.Web.Tests + 5 + ICspPolicyProvider, IFormFactor, ITokenStorageService, ServerTokenStorageService, WebFormFactor + + 15 - DeleteBehaviorConventionTests + ServiceModels MMCA.ADC.Architecture.Tests 3 - ArchitectureRules, DeleteBehaviorConventionTestsBase, ServiceModels + DataSourceEntrySettings, DefaultEntityConfigurationAssemblyProvider, DesignTimeDbContextHelper 15 @@ -33751,8 +34722,8 @@

Manifest (by level, then assembly)

15 DependencyInjection MMCA.ADC.Conference.Application - 86 - Activity, ActivityCreateRequest, ActivityDTO, ActivityNavigationPopulator, ActivityUpdateRequest, ApplicationSettings, Category, CategoryItem, CategoryItemDTO, CategoryItemNavigationPopulator, ClassReference, ClassReference, ConferenceCategoryCreateRequest, ConferenceCategoryDTO, ConferenceCategoryEntityQueryService, ConferenceCategoryNavigationPopulator, ConferenceCategoryUpdateRequest, DeleteConferenceCategoryHandler, DeleteEntityCommand<TEntity, TIdentifierType>, DeleteEntityHandler<TEntity, TIdentifierType> …(+66) + 84 + Activity, ActivityCreateRequest, ActivityDTO, ActivityNavigationPopulator, ActivityUpdateRequest, ApplicationSettings, Category, CategoryItem, CategoryItemDTO, CategoryItemNavigationPopulator, ClassReference, ClassReference, ConferenceCategoryCreateRequest, ConferenceCategoryDTO, ConferenceCategoryEntityQueryService, ConferenceCategoryNavigationPopulator, ConferenceCategoryUpdateRequest, DeleteConferenceCategoryHandler, DeleteEntityCommand<TEntity, TIdentifierType>, DeleteEntityHandler<TEntity, TIdentifierType> …(+64) 15 @@ -33945,6 +34916,13 @@

Manifest (by level, then assembly)

15 + DeleteConferenceCategoryHandlerTests + MMCA.ADC.Conference.Application.Tests + 6 + Category, DeleteConferenceCategoryHandler, DeleteEntityCommand<TEntity, TIdentifierType>, HandlerTestBase<THandler>, IRepository<TEntity, TIdentifierType>, UnitOfWork + + + 15 DeleteEventHandlerTests MMCA.ADC.Conference.Application.Tests 15 @@ -34143,8 +35121,8 @@

Manifest (by level, then assembly)

15 RefreshFromSessionizeHandlerTests MMCA.ADC.Conference.Application.Tests - 12 - Error, ErrorType, Event, IConcurrencyConflictDetector, ICurrentUserService, IRepository<TEntity, TIdentifierType>, ISessionizeService, IUnitOfWork, RefreshFromSessionizeCommand, RefreshFromSessionizeHandler, Result, SessionizeResponse + 14 + Error, ErrorType, Event, IConcurrencyConflictDetector, ICurrentUserService, IRepository<TEntity, TIdentifierType>, ISessionizeService, ITransactional, IUnitOfWork, RefreshFromSessionizeCommand, RefreshFromSessionizeHandler, RefreshFromSessionizeResultDTO, Result, SessionizeResponse 15 @@ -34262,8 +35240,8 @@

Manifest (by level, then assembly)

15 SessionScoringRunnerTests MMCA.ADC.Conference.Application.Tests - 13 - AuditableBaseEntity<TIdentifierType>, HandlerTestBase<THandler>, IAiScoringService, IRepository<TEntity, TIdentifierType>, Session, SessionAiScore, SessionBuilder, SessionScoringInput, SessionScoringResult, SessionScoringRunner, SessionStatuses, Speaker, UnitOfWork + 14 + AuditableBaseEntity<TIdentifierType>, HandlerTestBase<THandler>, IAiScoringService, IRepository<TEntity, TIdentifierType>, IUnitOfWork, Session, SessionAiScore, SessionBuilder, SessionScoringInput, SessionScoringResult, SessionScoringRunner, SessionStatuses, Speaker, UnitOfWork 15 @@ -34295,13 +35273,6 @@

Manifest (by level, then assembly)

15 - SpeakerQuestionAnswerNavigationPopulatorTests - MMCA.ADC.Conference.Application.Tests - 6 - HandlerTestBase<THandler>, INavigationPopulator<in TEntity>, NavigationMetadata, SpeakerQuestionAnswer, SpeakerQuestionAnswerNavigationPopulator, UnitOfWork - - - 15 SponsorNavigationPopulatorTests MMCA.ADC.Conference.Application.Tests 6 @@ -34339,8 +35310,8 @@

Manifest (by level, then assembly)

15 UpdateEventQuestionAnswerHandlerTests MMCA.ADC.Conference.Application.Tests - 9 - ErrorType, Event, HandlerTestBase<THandler>, ICurrentUserService, IRepository<TEntity, TIdentifierType>, RoleNames, UnitOfWork, UpdateEventQuestionAnswerCommand, UpdateEventQuestionAnswerHandler + 10 + ErrorType, Event, HandlerTestBase<THandler>, ICurrentUserService, IRepository<TEntity, TIdentifierType>, Question, RoleNames, UnitOfWork, UpdateEventQuestionAnswerCommand, UpdateEventQuestionAnswerHandler 15 @@ -34358,10 +35329,17 @@

Manifest (by level, then assembly)

15 + UpdateSessionAssetHandlerTests + MMCA.ADC.Conference.Application.Tests + 13 + ErrorType, HandlerTestBase<THandler>, IRepository<TEntity, TIdentifierType>, Session, SessionAsset, SessionAssetAccessService, SessionAssetDTOMapper, SessionAssetFixtures, SessionAssetUpdateRequest, SessionBuilder, UnitOfWork, UpdateSessionAssetCommand, UpdateSessionAssetHandler + + + 15 UpdateSessionQuestionAnswerHandlerTests MMCA.ADC.Conference.Application.Tests - 10 - ErrorType, Event, HandlerTestBase<THandler>, ICurrentUserService, IRepository<TEntity, TIdentifierType>, RoleNames, Session, UnitOfWork, UpdateSessionQuestionAnswerCommand, UpdateSessionQuestionAnswerHandler + 11 + ErrorType, Event, HandlerTestBase<THandler>, ICurrentUserService, IRepository<TEntity, TIdentifierType>, Question, RoleNames, Session, UnitOfWork, UpdateSessionQuestionAnswerCommand, UpdateSessionQuestionAnswerHandler 15 @@ -34379,6 +35357,13 @@

Manifest (by level, then assembly)

15 + ConferenceEntityConfigurationTests + MMCA.ADC.Conference.Infrastructure.Tests + 35 + Category, CategoryInvariants, CategoryItem, CategoryItemConfiguration, ConferenceCategoryConfiguration, ConferenceTestDbContext, Event, EventConfiguration, EventInvariants, EventQuestionAnswer, EventQuestionAnswerConfiguration, EventSpeaker, EventSpeakerConfiguration, Question, QuestionConfiguration, QuestionInvariants, Room, RoomConfiguration, Session, SessionCategoryItem …(+15) + + + 15 ConferenceIntegrationTestFixture MMCA.ADC.Conference.IntegrationTests 4 @@ -34409,8 +35394,8 @@

Manifest (by level, then assembly)

15 CastVoteHandlerTests MMCA.ADC.Engagement.Application.Tests - 12 - CastVoteCommand, CastVoteHandler, ErrorType, FakeTimeProvider, HandlerMocks, HandlerTestBase<THandler>, InMemoryQueryableExecutor, IReadRepository<TEntity, TIdentifierType>, LivePoll, LivePollResultsBuilder, LivePollVote, UnitOfWork + 14 + CastVoteCommand, CastVoteHandler, ErrorType, FakeTimeProvider, HandlerMocks, HandlerTestBase<THandler>, InMemoryQueryableExecutor, IReadRepository<TEntity, TIdentifierType>, IUniqueConstraintViolationDetector, LivePoll, LivePollResultsBuilder, LivePollVote, TestSupport, UnitOfWork 15 @@ -34442,6 +35427,13 @@

Manifest (by level, then assembly)

15 + DeleteLivePollHandlerTests + MMCA.ADC.Engagement.Application.Tests + 6 + DeleteEntityCommand<TEntity, TIdentifierType>, DeleteLivePollHandler, HandlerTestBase<THandler>, IRepository<TEntity, TIdentifierType>, LivePoll, UnitOfWork + + + 15 EventFeedbackSubmittedPointsHandlerTests MMCA.ADC.Engagement.Application.Tests 9 @@ -34493,8 +35485,8 @@

Manifest (by level, then assembly)

15 GetOpenPollsHandlerTests MMCA.ADC.Engagement.Application.Tests - 11 - CountingQueryableExecutor, ErrorType, GetOpenPollsHandler, GetOpenPollsQuery, HandlerTestBase<THandler>, InMemoryQueryableExecutor, IQueryableExecutor, LivePoll, LivePollResultsBuilder, LivePollVote, UnitOfWork + 12 + CountingQueryableExecutor, ErrorType, GetOpenPollsHandler, GetOpenPollsQuery, HandlerTestBase<THandler>, InMemoryQueryableExecutor, IQueryableExecutor, LivePoll, LivePollResultsBuilder, LivePollVote, TestSupport, UnitOfWork 15 @@ -34528,8 +35520,8 @@

Manifest (by level, then assembly)

15 GetSessionQuestionsHandlerTests MMCA.ADC.Engagement.Application.Tests - 9 - GetSessionQuestionsHandler, GetSessionQuestionsQuery, HandlerTestBase<THandler>, InMemoryQueryableExecutor, QuestionStatus, SessionQuestion, SessionQuestionUpvote, SessionQuestionViewBuilder, UnitOfWork + 10 + GetSessionQuestionsHandler, GetSessionQuestionsQuery, HandlerTestBase<THandler>, InMemoryQueryableExecutor, QuestionStatus, SessionQuestion, SessionQuestionUpvote, SessionQuestionViewBuilder, TestSupport, UnitOfWork 15 @@ -34619,8 +35611,22 @@

Manifest (by level, then assembly)

15 ToggleUpvoteHandlerTests MMCA.ADC.Engagement.Application.Tests - 11 - ErrorType, FakeTimeProvider, HandlerMocks, HandlerTestBase<THandler>, IRepository<TEntity, TIdentifierType>, QuestionStatus, SessionQuestion, SessionQuestionUpvote, ToggleUpvoteCommand, ToggleUpvoteHandler, UnitOfWork + 13 + ErrorType, FakeTimeProvider, HandlerMocks, HandlerTestBase<THandler>, IRepository<TEntity, TIdentifierType>, IUniqueConstraintViolationDetector, QuestionStatus, SessionQuestion, SessionQuestionUpvote, TestSupport, ToggleUpvoteCommand, ToggleUpvoteHandler, UnitOfWork + + + 15 + UserDeletedBadgeHandlerTests + MMCA.ADC.Engagement.Application.Tests + 7 + AttendeeBadge, HandlerTestBase<THandler>, IRepository<TEntity, TIdentifierType>, TestSupport, UnitOfWork, UserDeleted, UserDeletedBadgeHandler + + + 15 + UserDeletedBookmarksHandlerTests + MMCA.ADC.Engagement.Application.Tests + 7 + HandlerTestBase<THandler>, IRepository<TEntity, TIdentifierType>, TestSupport, UnitOfWork, UserDeleted, UserDeletedBookmarksHandler, UserSessionBookmark 15 @@ -34631,6 +35637,27 @@

Manifest (by level, then assembly)

15 + UserDeletedSessionQuestionsHandlerTests + MMCA.ADC.Engagement.Application.Tests + 10 + HandlerTestBase<THandler>, IRepository<TEntity, TIdentifierType>, Question, QuestionStatus, SessionQuestion, SessionQuestionUpvote, TestSupport, UnitOfWork, UserDeleted, UserDeletedSessionQuestionsHandler + + + 15 + UserDeletedVotesHandlerTests + MMCA.ADC.Engagement.Application.Tests + 7 + HandlerTestBase<THandler>, IRepository<TEntity, TIdentifierType>, LivePollVote, TestSupport, UnitOfWork, UserDeleted, UserDeletedVotesHandler + + + 15 + EngagementTestDbContext + MMCA.ADC.Engagement.Infrastructure.Tests + 12 + LivePoll, LivePollConfiguration, LivePollOption, LivePollOptionConfiguration, LivePollVote, LivePollVoteConfiguration, SessionQuestion, SessionQuestionConfiguration, SessionQuestionUpvote, SessionQuestionUpvoteConfiguration, UserSessionBookmark, UserSessionBookmarkConfiguration + + + 15 EngagementIntegrationTestFixture MMCA.ADC.Engagement.IntegrationTests 4 @@ -34675,15 +35702,15 @@

Manifest (by level, then assembly)

15 AuthenticationServiceTests MMCA.ADC.Identity.Application.Tests - 24 - AuthClaimTypes, AuthenticationResponse, AuthenticationService, AuthenticationValidators, EmailConfirmationSettings, Error, ErrorType, IExternalLoginEmailVerifier, ILoginProtectionService, InMemoryRefreshSessionStore, IPasswordHasher, IRepository<TEntity, TIdentifierType>, ITokenService, IUnitOfWork, LoginRequest, RefreshSession, RefreshSessionSettings, RefreshTokenRequest, RegisterRequest, Result …(+4) + 25 + AuthClaimTypes, AuthenticationResponse, AuthenticationService, AuthenticationValidators, AuthSessionIssuer, EmailConfirmationSettings, Error, ErrorType, IExternalLoginEmailVerifier, ILoginProtectionService, InMemoryRefreshSessionStore, IPasswordHasher, IRepository<TEntity, TIdentifierType>, ITokenService, IUnitOfWork, LoginRequest, RefreshSession, RefreshSessionSettings, RefreshTokenRequest, RegisterRequest …(+5) 15 ChangePasswordHandlerTests MMCA.ADC.Identity.Application.Tests - 11 - ChangePasswordCommand, ChangePasswordHandler, ChangePasswordRequest, ErrorType, HandlerTestBase<THandler>, IPasswordHasher, IRefreshSessionStore, IRepository<TEntity, TIdentifierType>, UnitOfWork, User, UserRole + 13 + ChangePasswordCommand, ChangePasswordHandler, ChangePasswordRequest, ErrorType, HandlerTestBase<THandler>, ILoginProtectionService, IPasswordHasher, IRefreshSessionStore, IRepository<TEntity, TIdentifierType>, Result, UnitOfWork, User, UserRole 15 @@ -34764,6 +35791,13 @@

Manifest (by level, then assembly)

15 + IdentityTestDbContext + MMCA.ADC.Identity.Infrastructure.Tests + 4 + DataSource, SoftDeleteUniqueIndexConvention, User, UserConfiguration + + + 15 IdentityIntegrationTestFixture MMCA.ADC.Identity.IntegrationTests 4 @@ -34834,6 +35868,13 @@

Manifest (by level, then assembly)

15 + DatabaseInitializationExtensions + MMCA.Common.API + 10 + ApplicationSettings, DataSourceKey, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, ModuleHostContext, ModuleLoader, TenancySettings, TenantDataSourceTarget, TenantDataSourceTargets + + + 15 PasswordResetAuthControllerBase<TForgotPasswordCommand, TResetPasswordCommand> MMCA.Common.API 9 @@ -34841,6 +35882,34 @@

Manifest (by level, then assembly)

15 + CookieSessionRefresherTests + MMCA.Common.API.Tests + 10 + AuthenticationResponse, CookieSessionRefresher, CookieTokenReader, FakeTimeProvider, KeyedSemaphoreStripe, RefresherHarness, SessionCookieEndpoints, SessionRefreshOutcome, SessionRefreshStatus, SessionTokenResult + + + 15 + DatabaseInitializationExtensionsTests + MMCA.Common.API.Tests + 30 + ApplicationSettings, AuditSaveChangesInterceptor, ConnectionStringSettings, CreateMigrationProofTable, DataSource, DataSourceEntrySettings, DataSourceResolver, DataSourcesSettings, DbContextFactory, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, FixedAssemblyProvider, ICurrentUserService, IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, InitTestMigratedWidget, InitTestWidget …(+10) + + + 15 + FixedAssemblyProvider + MMCA.Common.API.Tests + 2 + DatabaseInitializationExtensionsTests, IEntityConfigurationAssemblyProvider + + + 15 + MarkAllNotificationsReadHandlerTests + MMCA.Common.Application.Tests + 6 + FixedTimeProvider, Harness, MarkAllNotificationsReadCommand, PushNotification, Result, UserNotification + + + 15 MutateAttemptScopeTests MMCA.Common.Application.Tests 5 @@ -34855,10 +35924,24 @@

Manifest (by level, then assembly)

15 - AuditTrailCleanupJobTests - MMCA.Common.Infrastructure.Tests - 13 - ApplicationDbContext, AuditedThing, AuditTrailCleanupJob, AuditTrailEntry, AuditTrailSettings, AuditTrailTestContext, AuditTrailTestHarness, DataSource, DataSourceKey, FakeTimeProvider, IDbContextFactory, IEntityDataSourceRegistry, SchedulerTestHarness + FrameworkTableTargets + MMCA.Common.Infrastructure + 8 + DataSource, DataSourceEngines, DataSourceKey, IDataSourceResolver, IEntityDataSourceRegistry, TenancySettings, TenantDataSourceTarget, TenantDataSourceTargets + + + 15 + FixedAssemblyProvider + MMCA.Common.Infrastructure.SQLServer.Tests + 2 + IEntityConfigurationAssemblyProvider, SQLServerPersistenceTests + + + 15 + SQLServerPersistenceTests + MMCA.Common.Infrastructure.SQLServer.Tests + 23 + ApplicationDbContext, AuditSaveChangesInterceptor, ConnectionStringSettings, DataSource, DataSourceKey, DataSourceResolver, DataSourcesSettings, DbContextFactory, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, FixedAssemblyProvider, FixedCurrentUserService, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, NoTenantContext, OutboxMessage, OutboxSignal, PhysicalDbContextFactory …(+3) 15 @@ -34869,52 +35952,108 @@

Manifest (by level, then assembly)

15 - EntityDataSourceRegistryTests + CosmosConfigurationPortabilityTests MMCA.Common.Infrastructure.Tests + 17 + AuditSaveChangesInterceptor, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceResolver, DataSourcesSettings, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, FixedAssemblyProvider, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, OutboxSignal, PhysicalDbContextFactory, PortablePrincipal, PortableThing + + 15 - ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourceResolver, DataSourcesSettings, EntityDataSourceRegistry, FixedAssemblyProvider, NamespaceConventions, PushNotification, RegistryDuplicate, RegistryInvoice, RegistryOrder, RegistrySqlServerEntity, RegistryUnattributed + DbContextFactoryAdditionalTests + MMCA.Common.Infrastructure.Tests + 10 + DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, ICurrentUserService, IEntityDataSourceRegistry, IPhysicalDbContextFactory, ITenantContext, MidSaveContextCreatingDbContext, TenancySettings 15 - InternalCommandAdministrationTests + DbContextFactoryCommitAmbiguityTests MMCA.Common.Infrastructure.Tests - 11 - DataSourceKey, DefaultDataSourceResolver, EmptyEntityDataSourceRegistry, FakeTimeProvider, IDbContextFactory, IInternalCommandSignal, InternalCommandAdministration, InternalCommandMessage, InternalCommandTestContext, InternalCommandTestHarness, RecordingCommand + 16 + CommitFailingDbContext, DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, ICurrentUserService, IDomainEvent, IDomainEventDispatcher, IEntityDataSourceRegistry, IPhysicalDbContextFactory, ITenantContext, Result, TenancySettings, TestAggregate, TestLocalEvent, TransactionCommitAmbiguousException 15 - InternalCommandCleanupServiceTests + DbContextFactoryMigrationTargetTests + MMCA.Common.Infrastructure.Tests + 22 + AuditSaveChangesInterceptor, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourceResolver, DataSourcesSettings, DbContextFactory, DomainEventSaveChangesInterceptor, FixedSourcesRegistry, FixedSourcesRegistry, ICurrentUserService, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, ITenantContext, NoConfigurationAssemblyProvider, NoConfigurationAssemblyProvider, OutboxSignal …(+2) + + + 15 + DbContextFactorySaveIntegrityTests + MMCA.Common.Infrastructure.Tests + 14 + DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, ICurrentUserService, IDomainEvent, IDomainEventDispatcher, IEntityDataSourceRegistry, IntegrityAggregate, IntegrityEvent, IntegrityTestDbContext, IPhysicalDbContextFactory, ITenantContext, TenancySettings + + + 15 + DbContextFactoryTenantTests + MMCA.Common.Infrastructure.Tests + 16 + ApplicationDbContext, DataSource, DataSourceKey, DbContextFactory, ICurrentUserService, IDataSourceResolver, IEntityDataSourceRegistry, IPhysicalDbContextFactory, ITenantContext, MutableTenantContext, PhysicalDataSource, TenancySettings, TenantContext, TenantDataSourceOverrideSettings, TenantEntrySettings, TenantTestContext + + + 15 + DbContextFactoryTests + MMCA.Common.Infrastructure.Tests + 10 + ApplicationDbContext, DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, ICurrentUserService, IEntityDataSourceRegistry, IPhysicalDbContextFactory, ITenantContext, TenancySettings + + + 15 + DesignTimeDbContextHelperTests MMCA.Common.Infrastructure.Tests 12 - DataSource, DataSourceKey, DefaultDataSourceResolver, EmptyEntityDataSourceRegistry, FakeTimeProvider, IDbContextFactory, InternalCommandCleanupService, InternalCommandMessage, InternalCommandsSettings, InternalCommandTestContext, InternalCommandTestHarness, RecordingCommand + ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DesignAlphaEntity, DesignBetaEntity, DesignPostgreSQLEntity, DesignSqliteEntity, DesignTimeDbContextHelper, DesignTimeDbContextOptions, PermissionGrant, RefreshSession 15 - InternalCommandProcessorTests + DomainEventSaveChangesInterceptorOutboxDisabledTests MMCA.Common.Infrastructure.Tests 11 - Error, ExecutionLog, FakeTimeProvider, IInternalCommandSignal, InternalCommandMessage, InternalCommandProcessor, InternalCommandTestContext, InternalCommandTestHarness, RecordingCommand, Result, StubCurrentUserService + DomainEventSaveChangesInterceptor, DomainEventSaveChangesInterceptorOutboxRoutingTests, IDomainEvent, IDomainEventDispatcher, IOutboxSignal, MessageBusSettings, OutboxMessage, OutboxRoutingTestDbContext, TestAggregate, TestIntegrationEvent, TestLocalEvent 15 - InternalCommandSchedulerTests + EFRepositoryAuditStampTests MMCA.Common.Infrastructure.Tests - 9 - FakeTimeProvider, ICurrentUserService, IInternalCommandSignal, InternalCommandMessage, InternalCommandScheduler, InternalCommandTestContext, InternalCommandTestHarness, RecordingCommand, StubCurrentUserService + 15 + DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, EFRepository<TEntity, TIdentifierType>, ICurrentUserService, IDataSourceService, IEntityDataSourceRegistry, IPhysicalDbContextFactory, IRepositoryFactory, ITenantContext, StampedEntity, StampTestDbContext, TenancySettings, UnitOfWork 15 - OutboxCleanupServiceTests + FixedAssemblyProvider MMCA.Common.Infrastructure.Tests - 17 - ApplicationDbContext, CleanupTestContext, DataSource, DataSourceKey, FakeClockLoop, FakeTimeProvider, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, InboxMessage, MessageBusSettings, Mocks, Mocks, OutboxCleanupService, OutboxMessage, OutboxSettings, Payload + 3 + CosmosConfigurationPortabilityTests, IEntityConfigurationAssemblyProvider, MultiSourceSqliteIntegrationTests 15 - OutboxProcessorExecuteAsyncTests + MigrationApplyProofTests MMCA.Common.Infrastructure.Tests - 9 - DataSource, DataSourceKey, DependencyInjection, FakeTimeProvider, IDataSourceResolver, IEntityDataSourceRegistry, IOutboxSignal, OutboxProcessor, OutboxSettings + 21 + AuditSaveChangesInterceptor, ConnectionStringSettings, CreateMigrationProofTable, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourceResolver, DataSourcesSettings, DbContextFactory, DomainEventSaveChangesInterceptor, FixedSourcesRegistry, ICurrentUserService, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, ITenantContext, NoConfigurationAssemblyProvider, OutboxSignal, PhysicalDbContextFactory …(+1) + + + 15 + MultiSourceSqliteIntegrationTests + MMCA.Common.Infrastructure.Tests + 28 + ApplicationSettings, AuditSaveChangesInterceptor, ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceResolver, DataSourceService, DataSourcesSettings, DbContextFactory, DomainEventSaveChangesInterceptor, EntityDataSourceRegistry, FixedAssemblyProvider, ICurrentUserService, IDataSourceResolver, IDomainEventDispatcher, IEntityDataSourceRegistry, IOutboxSignal, ITenantContext, MultiSourceCustomer, MultiSourceOrder …(+8) + + + 15 + NotificationConfigurationEngineTests + MMCA.Common.Infrastructure.Tests + 7 + ApplicationDbContext, ConnectionStringSettings, DataSourceEntrySettings, DesignTimeDbContextHelper, PushNotification, UserNotification, UserNotificationConfiguration + + + 15 + PushNotificationTestDbContext + MMCA.Common.Infrastructure.Tests + 1 + PushNotificationConfiguration 15 @@ -34925,6 +36064,20 @@

Manifest (by level, then assembly)

15 + RepositoryFactoryTests + MMCA.Common.Infrastructure.Tests + 13 + ApplicationSettings, EFReadRepository<TEntity, TIdentifierType>, EFReadRepositoryDecorator<TEntity, TIdentifierType>, EFRepository<TEntity, TIdentifierType>, EFRepositoryDecorator<TEntity, TIdentifierType>, FakeAggregate, FakeAggregate, FakeEntity, FakeEntity, IReadRepository<TEntity, TIdentifierType>, IRepository<TEntity, TIdentifierType>, RepositoryFactory, TestDbContext + + + 15 + SqliteTestDbContext + MMCA.Common.Infrastructure.Tests + 2 + SqliteTestEntity, SqliteTestEntityConfig + + + 15 TestIdentityModuleDbSeeder MMCA.Common.Infrastructure.Tests 8 @@ -34932,12 +36085,33 @@

Manifest (by level, then assembly)

15 + PagedQueryLoadTests + MMCA.Common.LoadTests + 7 + LoadItem, LoadItemDTO, LoadResults, Measured, PagedCollectionResult<T>, PagedQuery, PagedQueryFixture + + + 15 HandlerTestBaseTests MMCA.Common.Testing.Tests 5 FakeHandler, HandlerTestBase<THandler>, TestAggregate, TestChildEntity, UnitOfWork + 15 + SameOriginApiProxyEndpointExtensions + MMCA.Common.UI.Web + 8 + HandoffSessionCookieSync, HandoffTokenRefresher, ISessionCookieSync, ITokenRefresher, SameOriginApiProxyEndpoint, SameOriginApiProxyMarker, SameOriginApiProxySettings, SessionHandoffEndpoints + + + 16 + DeleteBehaviorConventionTests + MMCA.ADC.Architecture.Tests + 3 + ArchitectureRules, DeleteBehaviorConventionTestsBase, ServiceModels + + 16 AddRoomHandlerTests MMCA.ADC.Conference.Application.Tests @@ -34974,6 +36148,13 @@

Manifest (by level, then assembly)

16 + EngagementEntityConfigurationTests + MMCA.ADC.Engagement.Infrastructure.Tests + 9 + EngagementTestDbContext, LivePoll, LivePollInvariants, LivePollOption, LivePollVote, SessionQuestion, SessionQuestionInvariants, SessionQuestionUpvote, UserSessionBookmark + + + 16 EngagementIntegrationTestCollection MMCA.ADC.Engagement.IntegrationTests 1 @@ -35009,6 +36190,13 @@

Manifest (by level, then assembly)

16 + IdentityEntityConfigurationTests + MMCA.ADC.Identity.Infrastructure.Tests + 3 + IdentityTestDbContext, User, UserInvariants + + + 16 IdentityModuleDbSeederTests MMCA.ADC.Identity.Infrastructure.Tests 6 @@ -35072,6 +36260,69 @@

Manifest (by level, then assembly)

16 + AuditTrailCleanupJob + MMCA.Common.Infrastructure + 6 + AuditTrailEntry, AuditTrailSettings, FrameworkTableTargets, IDbContextFactory, IScheduledJob, TenantDataSourceTarget + + + 16 + InternalCommandAdministration + MMCA.Common.Infrastructure + 11 + ApplicationDbContext, Error, FrameworkTableTargets, IDbContextFactory, IInternalCommandAdministration, IInternalCommandSignal, InternalCommandDeadLetter, InternalCommandMessage, InternalCommandsSettings, Result, TenantDataSourceTarget + + + 16 + InternalCommandCleanupService + MMCA.Common.Infrastructure + 7 + ApplicationDbContext, FrameworkTableTargets, IDbContextFactory, InternalCommandMessage, InternalCommandsSettings, PeriodicBackgroundService, TenantDataSourceTarget + + + 16 + InternalCommandProcessor + MMCA.Common.Infrastructure + 16 + Activity, AmbientOrigin, ApplicationDbContext, ColumnWidth, FrameworkTableTargets, IDbContextFactory, IInternalCommand, IInternalCommandSignal, InternalCommandCycleResult, InternalCommandDispatcher, InternalCommandMessage, InternalCommandMetrics, InternalCommandsSettings, PollingLoop, Result, TenantDataSourceTarget + + + 16 + OutboxAdministration + MMCA.Common.Infrastructure + 11 + ApplicationDbContext, Error, FrameworkTableTargets, IDbContextFactory, IOutboxAdministration, IOutboxSignal, OutboxDeadLetter, OutboxMessage, OutboxSettings, Result, TenantDataSourceTarget + + + 16 + OutboxCleanupService + MMCA.Common.Infrastructure + 9 + ApplicationDbContext, FrameworkTableTargets, IDbContextFactory, InboxMessage, MessageBusSettings, OutboxMessage, OutboxSettings, PeriodicBackgroundService, TenantDataSourceTarget + + + 16 + OutboxProcessor + MMCA.Common.Infrastructure + 19 + Activity, ApplicationDbContext, BrokerMetrics, BrokerResilienceDefaults, ColumnWidth, DataSourceKey, Event, FrameworkTableTargets, IDbContextFactory, IDomainEventDispatcher, IIntegrationEvent, IMessageBus, IOutboxSignal, OutboxCycleResult, OutboxMessage, OutboxMetrics, OutboxSettings, PollingLoop, TenantDataSourceTarget + + + 16 + EntityDataSourceRegistryTests + MMCA.Common.Infrastructure.Tests + 15 + ConnectionStringSettings, DataSource, DataSourceEntrySettings, DataSourceKey, DataSourceResolver, DataSourcesSettings, EntityDataSourceRegistry, FixedAssemblyProvider, NamespaceConventions, PushNotification, RegistryDuplicate, RegistryInvoice, RegistryOrder, RegistrySqlServerEntity, RegistryUnattributed + + + 16 + EntityTypeConfigurationTests + MMCA.Common.Infrastructure.Tests + 2 + SqliteTestDbContext, SqliteTestEntity + + + 16 IdentityModuleDbSeederBaseTests MMCA.Common.Infrastructure.Tests 7 @@ -35079,6 +36330,13 @@

Manifest (by level, then assembly)

16 + PushNotificationConfigurationTests + MMCA.Common.Infrastructure.Tests + 2 + PushNotification, PushNotificationTestDbContext + + + 16 RotationHarness MMCA.Common.Infrastructure.Tests 9 @@ -35221,8 +36479,8 @@

Manifest (by level, then assembly)

17 AuthControllerBaseTests MMCA.Common.API.Tests - 13 - AuthClaimTypes, AuthControllerBase, AuthenticationResponse, Error, IAuthenticationService, ICurrentUserService, LoginRequest, NonIdempotentAttribute, RefreshSessionSummaryResponse, RefreshTokenRequest, RegisterRequest, Result, TestAuthController + 14 + AuthClaimTypes, AuthControllerBase, AuthenticationResponse, Error, IAuthenticationService, ICurrentUserService, IdempotentAttribute, LoginRequest, NonIdempotentAttribute, RefreshSessionSummaryResponse, RefreshTokenRequest, RegisterRequest, Result, TestAuthController 17 @@ -35240,6 +36498,41 @@

Manifest (by level, then assembly)

17 + DependencyInjection + MMCA.Common.Infrastructure + 173 + ApplicationNamespace, AuditSaveChangesInterceptor, AuditTrailCleanupJob, AuditTrailReader, AuditTrailSaveChangesInterceptor, AuditTrailSettings, AuthSessionIssuer, AzureBlobFileStorageService, AzureNotificationHubDeviceRegistrar, AzureNotificationHubNativePushSender, BrokerEventBus, BrokerMessageBus, CacheKeyNamespace, CacheKeyPrefixOptions, CacheSettings, ClaimBasedUserIdProvider, ClassReference, ConnectionStringSettings, ConnectionStringSettingsValidator, CorrelationContext …(+153) + + + 17 + AddAuditTrailTests + MMCA.Common.Infrastructure.Tests + 6 + AuditTrailCleanupJob, AuditTrailReader, AuditTrailSaveChangesInterceptor, AuditTrailSettings, IAuditTrailReader, IScheduledJob + + + 17 + AuditTrailCleanupJobTests + MMCA.Common.Infrastructure.Tests + 15 + ApplicationDbContext, AuditedThing, AuditTrailCleanupJob, AuditTrailEntry, AuditTrailSettings, AuditTrailTestContext, AuditTrailTestHarness, DataSource, DataSourceKey, FakeTimeProvider, FrameworkTableTargets, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, SchedulerTestHarness + + + 17 + DependencyInjectionInfrastructureTests + MMCA.Common.Infrastructure.Tests + 16 + AuditSaveChangesInterceptor, ConnectionStringSettings, DomainEventSaveChangesInterceptor, EntityConfigurationOptions, IDataSourceService, IEntityConfigurationAssemblyProvider, IQueryableExecutor, IRawSqlQueryExecutor, IRepository<TEntity, TIdentifierType>, IRepositoryFactory, IUniqueConstraintViolationDetector, IUnitOfWork, OutboxProcessor, OutboxSettings, SmtpSettings, SqlServerUniqueConstraintViolationDetector + + + 17 + DependencyInjectionOutboxGateTests + MMCA.Common.Infrastructure.Tests + 3 + OutboxCleanupService, OutboxDisabledNoticeService, OutboxProcessor + + + 17 EFRefreshSessionStoreFindByIdTests MMCA.Common.Infrastructure.Tests 2 @@ -35253,6 +36546,69 @@

Manifest (by level, then assembly)

Participant, RefreshSession, RotationHarness + 17 + InternalCommandTestHarness + MMCA.Common.Infrastructure.Tests + 30 + AnonymousCurrentUserService, ApplicationDbContext, CorrelationContext, DataSource, DataSourceKey, DefaultDataSourceResolver, EmptyEntityDataSourceRegistry, ExecutionLog, FakeTimeProvider, FrameworkTableTargets, ICommandHandler<in TCommand, TResult>, ICorrelationContext, ICurrentUserService, IDbContextFactory, IInternalCommand, IInternalCommandSignal, ImpersonatingCurrentUserService, InternalCommandMessage, InternalCommandOriginCapture, InternalCommandProcessor …(+10) + + + 17 + Mocks + MMCA.Common.Infrastructure.Tests + 3 + IDataSourceResolver, IEntityDataSourceRegistry, OutboxCleanupService + + + 17 + OutboxAdministrationTests + MMCA.Common.Infrastructure.Tests + 13 + AdminTestContext, DataSource, DataSourceKey, FrameworkTableTargets, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, IOutboxSignal, OutboxAdministration, OutboxDeadLetter, OutboxMessage, OutboxSettings, Payload + + + 17 + OutboxProcessorContextRestoreTests + MMCA.Common.Infrastructure.Tests + 27 + AuditSaveChangesInterceptor, CapturingMessageBus, CorrelationContext, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FrameworkTableTargets, ICorrelationContext, ICurrentUserService, IDataSourceResolver, IDbContextFactory, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IMessageBus, IOutboxSignal, ITenantContext, OutboxMessage, OutboxOrigin …(+7) + + + 17 + OutboxProcessorOrderingTests + MMCA.Common.Infrastructure.Tests + 17 + DataSource, DataSourceKey, FakeTimeProvider, FrameworkTableTargets, IDataSourceResolver, IDbContextFactory, IDomainEvent, IDomainEventDispatcher, IEntityDataSourceRegistry, IMessageBus, IOutboxSignal, OrderedTestEvent, OrderingTestContext, OutboxMessage, OutboxProcessor, OutboxSettings, Payload + + + 17 + OutboxProcessorTests + MMCA.Common.Infrastructure.Tests + 26 + AuditSaveChangesInterceptor, BrokerResilienceDefaults, DataSource, DataSourceKey, DomainEventSaveChangesInterceptor, EmptyEntityDataSourceRegistry, FakeTimeProvider, FrameworkTableTargets, IDataSourceResolver, IDbContextFactory, IDomainEvent, IDomainEventDispatcher, IEntityConfigurationAssemblyProvider, IEntityDataSourceRegistry, IIntegrationEvent, IMessageBus, IOutboxSignal, OutboxCycleResult, OutboxMessage, OutboxProcessor …(+6) + + + 17 + OutboxProcessorWaitTests + MMCA.Common.Infrastructure.Tests + 1 + OutboxProcessor + + + 17 + TenantDataSourceTargetTests + MMCA.Common.Infrastructure.Tests + 15 + DataSource, DataSourceKey, FrameworkTableTargets, IDataSourceResolver, IEntityDataSourceRegistry, IOutboxSignal, MessageBusSettings, OutboxCleanupService, OutboxProcessor, OutboxSettings, TenancySettings, TenantDataSourceOverrideSettings, TenantDataSourceTarget, TenantDataSourceTargets, TenantEntrySettings + + + 17 + OutboxThroughputLoadTests + MMCA.Common.LoadTests + 7 + CountingMessageBus, IMessageBus, LoadIntegrationEvent, LoadResults, LoadStack, OutboxMessage, OutboxProcessor + + 18 AnonymousAccessDeniedTests MMCA.ADC.Conference.IntegrationTests @@ -35743,6 +37099,55 @@

Manifest (by level, then assembly)

AuthenticationResponse, ChangePasswordRequest, ChangePreferencesRequest, Error, GetUserPreferencesQuery, IAuthenticationService, ICommandHandler<in TCommand, TResult>, ICurrentUserService, IQueryHandler<in TQuery, TResult>, LoginRequest, Result, TestChangePasswordCommand, TestChangePreferencesCommand, TestUserAccountAuthController, UserPreferencesResponse + 18 + DbContextFactoryTransactionTests + MMCA.Common.Infrastructure.Tests + 23 + DataSource, DataSourceKey, DbContextFactory, DefaultDataSourceResolver, Error, FakeTimeProvider, ICurrentUserService, IDomainEvent, IDomainEventDispatcher, IEntityDataSourceRegistry, IInternalCommandSignal, InternalCommandMessage, InternalCommandScheduler, InternalCommandTestHarness, IPhysicalDbContextFactory, ITenantContext, OutboxMessage, RecordingCommand, Result, TenancySettings …(+3) + + + 18 + InternalCommandAdministrationTests + MMCA.Common.Infrastructure.Tests + 12 + DataSourceKey, DefaultDataSourceResolver, EmptyEntityDataSourceRegistry, FakeTimeProvider, FrameworkTableTargets, IDbContextFactory, IInternalCommandSignal, InternalCommandAdministration, InternalCommandMessage, InternalCommandTestContext, InternalCommandTestHarness, RecordingCommand + + + 18 + InternalCommandCleanupServiceTests + MMCA.Common.Infrastructure.Tests + 13 + DataSource, DataSourceKey, DefaultDataSourceResolver, EmptyEntityDataSourceRegistry, FakeTimeProvider, FrameworkTableTargets, IDbContextFactory, InternalCommandCleanupService, InternalCommandMessage, InternalCommandsSettings, InternalCommandTestContext, InternalCommandTestHarness, RecordingCommand + + + 18 + InternalCommandProcessorTests + MMCA.Common.Infrastructure.Tests + 11 + Error, ExecutionLog, FakeTimeProvider, IInternalCommandSignal, InternalCommandMessage, InternalCommandProcessor, InternalCommandTestContext, InternalCommandTestHarness, RecordingCommand, Result, StubCurrentUserService + + + 18 + InternalCommandSchedulerTests + MMCA.Common.Infrastructure.Tests + 9 + FakeTimeProvider, ICurrentUserService, IInternalCommandSignal, InternalCommandMessage, InternalCommandScheduler, InternalCommandTestContext, InternalCommandTestHarness, RecordingCommand, StubCurrentUserService + + + 18 + OutboxCleanupServiceTests + MMCA.Common.Infrastructure.Tests + 18 + ApplicationDbContext, CleanupTestContext, DataSource, DataSourceKey, FakeClockLoop, FakeTimeProvider, FrameworkTableTargets, IDataSourceResolver, IDbContextFactory, IEntityDataSourceRegistry, InboxMessage, MessageBusSettings, Mocks, Mocks, OutboxCleanupService, OutboxMessage, OutboxSettings, Payload + + + 18 + OutboxProcessorExecuteAsyncTests + MMCA.Common.Infrastructure.Tests + 10 + DataSource, DataSourceKey, DependencyInjection, FakeTimeProvider, FrameworkTableTargets, IDataSourceResolver, IEntityDataSourceRegistry, IOutboxSignal, OutboxProcessor, OutboxSettings + + 19 JwksDiscoveryTests MMCA.ADC.Identity.IntegrationTests diff --git a/docs/onboarding/00-group-taxonomy.html b/docs/onboarding/00-group-taxonomy.html index 586b1628..91ed47f6 100644 --- a/docs/onboarding/00-group-taxonomy.html +++ b/docs/onboarding/00-group-taxonomy.html @@ -6,21 +6,21 @@ Phase 1b - Functional Group Taxonomy · MMCA · Ivan Ball-llovera - + - + - + @@ -145,7 +145,7 @@

Onboarding guide

Phase 1b - Functional Group Taxonomy

-

This is the primary axis of the guide. Every one of the 5,034 distinct first-party type +

This is the primary axis of the guide. Every one of the 5,234 distinct first-party type nodes from 00-inventory.md is assigned to exactly one functional group - its primary home: the capability or cross-cutting concern it most exists to serve. A type used across many groups (e.g. Result<T>, the entity base) lives in the one foundational group that @@ -227,7 +227,7 @@

The groups (ordered)

G04 Domain & Integration Events + Outbox Dual-Dispatch
group-04-events-outbox.md 38 - L0-L13 + L0-L16 Event contracts, the domain-event dispatcher, the transactional outbox/inbox, and the in-process + broker message buses. @@ -247,15 +247,15 @@

The groups (ordered)

G07 Persistence & EF Core
group-07-persistence-ef-core.md - 165 - L0-L14 + 177 + L0-L16 The single SQLServerDbContext over the abstract ApplicationDbContext, interceptors, repositories, specifications evaluation, data-source routing (database-per-service), conventions, value generators, encryption, factories and design-time. G08 Authentication & Authorization
group-08-auth.md - 153 - L0-L10 + 162 + L0-L14 JWT/JWKS dual-fetch token validation, current-user/claims, password hashing, cookie sessions, and policy/authorization plumbing. @@ -289,22 +289,22 @@

The groups (ordered)

G13 gRPC & Inter-Service Contracts
group-13-grpc-contracts.md - 6 + 7 L0-L4 Typed gRPC clients/servers, interceptors, Result-over-the-wire, and the ServiceContract marker for synchronous inter-service calls (ADR-007). G14 Module System, Composition & Configuration
group-14-module-system-composition.md - 88 - L0-L14 + 87 + L0-L17 IModule discovery + Kahn-ordered ModuleLoader, the DI composition roots, assembly markers, data-source/database attributes, and options/settings binding. G15 Common UI Framework (MudBlazor components, theme, base pages)
group-15-common-ui-framework.md - 153 - L0-L8 + 174 + L0-L15 Reusable Blazor building blocks: the data-grid list page base, theme, common pages/services, and UI extensions shared by every consumer app. @@ -332,7 +332,7 @@

The groups (ordered)

G19 ADC Conference - Infrastructure & Persistence
group-19-conference-infrastructure.md 29 - L0-L12 + L0-L14 The Conference module DbContext registration, EF entity configurations, database seeding, and infrastructure services. @@ -345,28 +345,28 @@

The groups (ordered)

G21 ADC Conference - UI
group-21-conference-ui.md - 162 + 163 L0-L10 The Conference Blazor pages (events, sessions, speakers, categories, questions, rooms, feedback, public, session-selection) and their UI services. G22 ADC Engagement Module (Session Bookmarks)
group-22-engagement-module.md - 194 - L0-L13 + 196 + L0-L14 The Engagement bounded context end-to-end: bookmark aggregate, use cases, persistence, API/contracts/service, and feedback UI. G26 ADC Engagement Live Layer (Real-Time Polls & Session Q&A)
group-23-engagement-live-layer.md - 85 + 87 L0-L14 Real-time audience interaction in the Engagement bounded context: event-wide live polls with voting and moderated per-session Q&A with upvoting, over the SignalR hub-channel transport (ADR-039) and the cross-service gRPC live-channel adapter. G23 ADC Identity Module (Users, Profiles, GDPR Export/Erasure)
group-24-identity-module.md - 107 + 108 L0-L17 The Identity bounded context end-to-end: the User aggregate, change-password/delete/export use cases, persistence, API/contracts/service, and profile/user UI. @@ -394,12 +394,12 @@

The groups (ordered)

G25 Testing & Quality Infrastructure
group-28-testing-infrastructure.md - 2771 + 2923 L0-L19 All test projects + the reusable Testing/Testing.E2E/Testing.UI bases, architecture-fitness tests, and the component Gallery harness; individual [Fact]s are rolled up by project (logged exception). -

Reconciliation: 2263 production types across 27 groups + 2771 test/testing types in G25 = 5034 (matches the inventory's distinct-node count). No type appears twice; none dropped.

+

Reconciliation: 2311 production types across 27 groups + 2923 test/testing types in G25 = 5234 (matches the inventory's distinct-node count). No type appears twice; none dropped.


Group membership

G01 - Result & Error Handling

@@ -1232,19 +1232,19 @@

G04 - Domain &am MMCA.Common.Infrastructure.Messaging - 13 + 16 OutboxAdministration class MMCA.Common.Infrastructure.Persistence.Outbox.Administration - 13 + 16 OutboxCleanupService class MMCA.Common.Infrastructure.Persistence.Outbox.Administration - 13 + 16 OutboxProcessor class MMCA.Common.Infrastructure.Persistence.Outbox.Processing @@ -1704,14 +1704,14 @@

G06 - Validation

MMCA.Common.Application.Validation - 0 - StrongPasswordRules<T> + 1 + CommandRequestValidator<TCommand, TRequest> class MMCA.Common.Application.Validation 1 - CommandRequestValidator<TCommand, TRequest> + StrongPasswordRules<T> class MMCA.Common.Application.Validation @@ -1778,7 +1778,7 @@

G06 - Validation

G07 - Persistence & EF Core

-

group-07-persistence-ef-core.md | 165 types | The single SQLServerDbContext over the abstract ApplicationDbContext, interceptors, repositories, specifications evaluation, data-source routing (database-per-service), conventions, value generators, encryption, factories and design-time.

+

group-07-persistence-ef-core.md | 177 types | The single SQLServerDbContext over the abstract ApplicationDbContext, interceptors, repositories, specifications evaluation, data-source routing (database-per-service), conventions, value generators, encryption, factories and design-time.

@@ -1875,6 +1875,12 @@

G07 - Persistence & EF Core

+ + + + + + @@ -1911,12 +1917,6 @@

G07 - Persistence & EF Core

- - - - - - @@ -1989,6 +1989,12 @@

G07 - Persistence & EF Core

+ + + + + + @@ -2019,6 +2025,12 @@

G07 - Persistence & EF Core

+ + + + + + @@ -2073,6 +2085,12 @@

G07 - Persistence & EF Core

+ + + + + + @@ -2127,6 +2145,12 @@

G07 - Persistence & EF Core

+ + + + + + @@ -2163,24 +2187,12 @@

G07 - Persistence & EF Core

- - - - - - - - - - - - @@ -2223,12 +2235,6 @@

G07 - Persistence & EF Core

- - - - - - @@ -2241,12 +2247,6 @@

G07 - Persistence & EF Core

- - - - - - @@ -2259,12 +2259,6 @@

G07 - Persistence & EF Core

- - - - - - @@ -2277,24 +2271,6 @@

G07 - Persistence & EF Core

- - - - - - - - - - - - - - - - - - @@ -2337,12 +2313,6 @@

G07 - Persistence & EF Core

- - - - - - @@ -2391,23 +2361,11 @@

G07 - Persistence & EF Core

- - - - - - - - - - - - @@ -2415,12 +2373,6 @@

G07 - Persistence & EF Core

- - - - - - @@ -2499,15 +2451,9 @@

G07 - Persistence & EF Core

- - - - - - - + - + @@ -2517,12 +2463,6 @@

G07 - Persistence & EF Core

- - - - - - @@ -2547,30 +2487,6 @@

G07 - Persistence & EF Core

- - - - - - - - - - - - - - - - - - - - - - - - @@ -2582,22 +2498,10 @@

G07 - Persistence & EF Core

- - - - - - - - - - - - - + - + @@ -2606,12 +2510,6 @@

G07 - Persistence & EF Core

- - - - - - @@ -2631,6 +2529,30 @@

G07 - Persistence & EF Core

+ + + + + + + + + + + + + + + + + + + + + + + + @@ -2649,69 +2571,129 @@

G07 - Persistence & EF Core

- + + + + + + + + + + + + + - + - - + + - - - - + + + + - - - - + + + + - - + + - + - + + + + + + + - + + + + + + + - - + + - + - - + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + + + + + + + @@ -2733,21 +2715,39 @@

G07 - Persistence & EF Core

- + - + - + - + - + - + + + + + + + + + + + + + + + + + + + @@ -2765,24 +2765,96 @@

G07 - Persistence & EF Core

- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + - - + + - +
0ExplicitKeyInsertGrouprecordMMCA.Common.Infrastructure.Persistence.DataSources.Engines
0 FileUploadOptions record MMCA.Common.Application.Interfaces.Infrastructure.Storage
0IdentityInsertGrouprecordMMCA.Common.Infrastructure.Persistence.DbContexts.Factory
0 IEntityConfigurationAssemblyProvider interface MMCA.Common.Application.Interfaces.Infrastructure.Persistence
0MigrationPolicyenumMMCA.Common.Infrastructure.Persistence.DataSources.Engines
0 ModelBuilderExtensions class MMCA.Common.Infrastructure.Persistence.DbContexts
0RowVersionStrategyenumMMCA.Common.Infrastructure.Persistence.DataSources.Engines
0 SeedAccount record MMCA.Common.Infrastructure.Persistence.DbContexts.Seeding
1DataSourceEngineCapabilitiesrecordMMCA.Common.Infrastructure.Persistence.DataSources.Engines
1 DataSourceKey record struct MMCA.Common.Application.Interfaces.Infrastructure.Persistence
1IExplicitKeyInsertDialectinterfaceMMCA.Common.Infrastructure.Persistence.DataSources.Engines
1 InternalCommandMetrics class MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing
1RestrictDeleteByDefaultConventionclassMMCA.Common.Infrastructure.Persistence.Conventions
1 SensitiveDataLoggingGate class MMCA.Common.Infrastructure.Persistence
1SoftDeleteFilterSqlclassMMCA.Common.Infrastructure.Persistence
1 SqlServerUniqueConstraintViolationDetector class MMCA.Common.Infrastructure.Persistence
2IndexBuilderExtensionsclassMMCA.Common.Infrastructure.Persistence.Configuration
2 InternalCommandsSettings class MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration
2PhysicalDataSourcerecordMMCA.Common.Infrastructure.Persistence.DataSources
2 QueryTags class MMCA.Common.Infrastructure.Persistence.Repositories
2SoftDeleteUniqueIndexConventionclassMMCA.Common.Infrastructure.Persistence.Conventions
2 TenancySettings class MMCA.Common.Infrastructure.Persistence.Tenancy
3CrossDataSourceDegradeConventionclassMMCA.Common.Infrastructure.Persistence.Conventions
3DataSourceServiceclassMMCA.Common.Infrastructure.Persistence.DataSources
3IDataSourceResolverinterfaceMMCA.Common.Infrastructure.Persistence.DataSources
3 IFileStorageService interface MMCA.Common.Application.Interfaces.Infrastructure.Storage
4DataSourceResolverclassMMCA.Common.Infrastructure.Persistence.DataSources
4 EnumerationValueConverter<TEnumeration> class MMCA.Common.Infrastructure.Persistence.Conversions
4RefreshSessionModelBuilderExtensionsclassMMCA.Common.Infrastructure.Persistence.Auth
4 SpecificationEvaluator class MMCA.Common.Infrastructure.Persistence.Repositories
4TenancySettingsValidatorclassMMCA.Common.Infrastructure.Persistence.Tenancy
5 EmailValueConverter class
5EntityDataSourceRegistryclassMMCA.Common.Infrastructure.Persistence.DataSources
5 EntityTypeBuilderExtensions class MMCA.Common.Infrastructure.Persistence.Configuration
5TenantDataSourceTargetsclassMMCA.Common.Infrastructure.Persistence.DataSources
6EFReadRepository<TEntity, TIdentifierType>RefreshSessionModelBuilderExtensions classMMCA.Common.Infrastructure.Persistence.RepositoriesMMCA.Common.Infrastructure.Persistence.Auth
6
6EntityTypeConfiguration<TEntity, TIdentifierType>classMMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration
6 IRepository<TEntity, TIdentifierType> interface MMCA.Common.Application.Interfaces.Infrastructure.Persistence
7EntityTypeConfigurationCosmos<TEntity, TIdentifierType>classMMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration
7EntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType>classMMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration
7EntityTypeConfigurationSqlite<TEntity, TIdentifierType>classMMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration
7EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>classMMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration
7 IRepositoryFactory interface MMCA.Common.Infrastructure.Persistence.Repositories.Factory MMCA.Common.Application.Interfaces.Infrastructure.Persistence
8PushNotificationConfigurationclassMMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration.Notifications
8UserNotificationConfigurationclassMMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration.Notifications
9EFRepository<TEntity, TIdentifierType>InternalCommandOriginCapture classMMCA.Common.Infrastructure.Persistence.RepositoriesMMCA.Common.Infrastructure.Persistence.InternalCommands
10 MMCA.Common.Infrastructure.Persistence.Interceptors
10RepositoryFactoryclassMMCA.Common.Infrastructure.Persistence.Repositories.Factory
11 ApplicationDbContext class
11CosmosDataSourceEngineclassMMCA.Common.Infrastructure.Persistence.DataSources.Engines
11CosmosDbContextclassMMCA.Common.Infrastructure.Persistence.DbContexts
11CrossDataSourceDegradeConventionclassMMCA.Common.Infrastructure.Persistence.Conventions
11DataSourceEnginesclassMMCA.Common.Infrastructure.Persistence.DataSources.Engines
11 DataSourceModelCacheKeyFactory class MMCA.Common.Infrastructure.Persistence.DbContexts
11TenantSaveChangesInterceptorIDataSourceEngineinterfaceMMCA.Common.Infrastructure.Persistence.DataSources.Engines
11PhysicalDataSourcerecordMMCA.Common.Infrastructure.Persistence.DataSources
11PostgreSQLDataSourceEngine classMMCA.Common.Infrastructure.Persistence.InterceptorsMMCA.Common.Infrastructure.Persistence.DataSources.Engines
12CosmosDbContext11PostgreSQLDbContext class MMCA.Common.Infrastructure.Persistence.DbContexts
12IDbContextFactoryinterfaceMMCA.Common.Infrastructure.Persistence.DbContexts.Factory11RestrictDeleteByDefaultConventionclassMMCA.Common.Infrastructure.Persistence.Conventions
12IPhysicalDbContextFactoryinterfaceMMCA.Common.Infrastructure.Persistence.DbContexts.Factory11SoftDeleteFilterSqlclassMMCA.Common.Infrastructure.Persistence
12PostgreSQLDbContext11SoftDeleteUniqueIndexConvention classMMCA.Common.Infrastructure.Persistence.DbContextsMMCA.Common.Infrastructure.Persistence.Conventions
1211SqliteDataSourceEngineclassMMCA.Common.Infrastructure.Persistence.DataSources.Engines
11 SqliteDbContext class MMCA.Common.Infrastructure.Persistence.DbContexts
1211SQLServerDataSourceEngineclassMMCA.Common.Infrastructure.Persistence.DataSources.Engines
11 SQLServerDbContext class MMCA.Common.Infrastructure.Persistence.DbContexts
13AuditTrailCleanupJob11TenantSaveChangesInterceptor classMMCA.Common.Infrastructure.Persistence.AuditTrailMMCA.Common.Infrastructure.Persistence.Interceptors
13AuditTrailReader12DataSourceService classMMCA.Common.Infrastructure.Persistence.AuditTrailMMCA.Common.Infrastructure.Persistence.DataSources
13DbContextFactory12EFReadRepository<TEntity, TIdentifierType>classMMCA.Common.Infrastructure.Persistence.Repositories
12EntityTypeConfiguration<TEntity, TIdentifierType>classMMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration
12IDataSourceResolverinterfaceMMCA.Common.Infrastructure.Persistence.DataSources
12IDbContextFactoryinterfaceMMCA.Common.Infrastructure.Persistence.DbContexts.Factory
12IndexBuilderExtensions classMMCA.Common.Infrastructure.Persistence.Configuration
12IPhysicalDbContextFactoryinterface MMCA.Common.Infrastructure.Persistence.DbContexts.Factory
13DesignTimeDbContextHelperAuditTrailReader classMMCA.Common.Infrastructure.Persistence.DbContexts.DesignMMCA.Common.Infrastructure.Persistence.AuditTrail
13DataSourceResolverclassMMCA.Common.Infrastructure.Persistence.DataSources
13
13InternalCommandAdministrationEFRepository<TEntity, TIdentifierType> classMMCA.Common.Infrastructure.Persistence.InternalCommands.AdministrationMMCA.Common.Infrastructure.Persistence.Repositories
13InternalCommandCleanupServiceEntityDataSourceRegistry classMMCA.Common.Infrastructure.Persistence.InternalCommands.AdministrationMMCA.Common.Infrastructure.Persistence.DataSources
13InternalCommandProcessorEntityTypeConfigurationCosmos<TEntity, TIdentifierType> classMMCA.Common.Infrastructure.Persistence.InternalCommands.ProcessingMMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration
13EntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType>classMMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration
13EntityTypeConfigurationSqlite<TEntity, TIdentifierType>classMMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration
13EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>classMMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration
13 13 RefreshSessionCleanupService classMMCA.Common.Infrastructure.Persistence.AuthMMCA.Common.Infrastructure.Persistence.Auth
13TenancySettingsValidatorclassMMCA.Common.Infrastructure.Persistence.Tenancy
13UnitOfWorkclassMMCA.Common.Infrastructure.Persistence
14DbContextFactoryclassMMCA.Common.Infrastructure.Persistence.DbContexts.Factory
14DesignTimeDbContextHelperclassMMCA.Common.Infrastructure.Persistence.DbContexts.Design
14IdentityModuleDbSeederBase<TUser>classMMCA.Common.Infrastructure.Persistence.DbContexts.Seeding
14PushNotificationConfigurationclassMMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration.Notifications
14RepositoryFactoryclassMMCA.Common.Infrastructure.Persistence.Repositories.Factory
14TenantDataSourceTargetsclassMMCA.Common.Infrastructure.Persistence.DataSources
14UserNotificationConfigurationclassMMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration.Notifications
15FrameworkTableTargetsclassMMCA.Common.Infrastructure.Persistence.DataSources
16AuditTrailCleanupJobclassMMCA.Common.Infrastructure.Persistence.AuditTrail
16InternalCommandAdministrationclassMMCA.Common.Infrastructure.Persistence.InternalCommands.Administration
13UnitOfWork16InternalCommandCleanupService classMMCA.Common.Infrastructure.PersistenceMMCA.Common.Infrastructure.Persistence.InternalCommands.Administration
14IdentityModuleDbSeederBase<TUser>16InternalCommandProcessor classMMCA.Common.Infrastructure.Persistence.DbContexts.SeedingMMCA.Common.Infrastructure.Persistence.InternalCommands.Processing

G08 - Authentication & Authorization

-

group-08-auth.md | 153 types | JWT/JWKS dual-fetch token validation, current-user/claims, password hashing, cookie sessions, and policy/authorization plumbing.

+

group-08-auth.md | 162 types | JWT/JWKS dual-fetch token validation, current-user/claims, password hashing, cookie sessions, and policy/authorization plumbing.

@@ -2945,6 +3017,12 @@

G08 - Authentication & Authoriz

+ + + + + + @@ -2953,7 +3031,7 @@

G08 - Authentication & Authoriz

- + @@ -3023,6 +3101,12 @@

G08 - Authentication & Authoriz

+ + + + + + @@ -3107,12 +3191,30 @@

G08 - Authentication & Authoriz

+ + + + + + + + + + + + + + + + + + @@ -3233,12 +3335,6 @@

G08 - Authentication & Authoriz

- - - - - - @@ -3311,12 +3407,6 @@

G08 - Authentication & Authoriz

- - - - - - @@ -3331,7 +3421,7 @@

G08 - Authentication & Authoriz

- + @@ -3359,12 +3449,6 @@

G08 - Authentication & Authoriz

- - - - - - @@ -3389,15 +3473,9 @@

G08 - Authentication & Authoriz

- - - - - - - + - + @@ -3431,15 +3509,9 @@

G08 - Authentication & Authoriz

- - - - - - - - - + + + @@ -3515,12 +3587,6 @@

G08 - Authentication & Authoriz

- - - - - - @@ -3545,12 +3611,6 @@

G08 - Authentication & Authoriz

- - - - - - @@ -3563,9 +3623,9 @@

G08 - Authentication & Authoriz

- - - + + + @@ -3575,12 +3635,6 @@

G08 - Authentication & Authoriz

- - - - - - @@ -3635,18 +3689,12 @@

G08 - Authentication & Authoriz

- - + + - - - - - - @@ -3659,6 +3707,12 @@

G08 - Authentication & Authoriz

+ + + + + + @@ -3676,6 +3730,12 @@

G08 - Authentication & Authoriz

+ + + + + + @@ -3706,11 +3766,77 @@

G08 - Authentication & Authoriz

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
0ISessionCookieStoreinterfaceMMCA.Common.API.SessionCookies
0 ISoftDeletedUserValidator interface MMCA.Common.Application.Interfaces.Infrastructure.Auth0 IssuedSession recordMMCA.Common.Application.AuthMMCA.Common.Application.Auth.Sessions
0
0PasswordComplexityclassMMCA.Common.Shared.Auth
0 PasswordResetEntry record MMCA.Common.Infrastructure.Auth
0SessionClaimsTokenclassMMCA.Common.API.SessionCookies
0 SessionCookieRequest record MMCA.Common.API.SessionCookies
0SessionCookieSettingsclassMMCA.Common.API.SessionCookies
0SessionRefreshStatusenumMMCA.Common.API.SessionCookies
0 SessionTokenResponse record MMCA.Common.API.SessionCookies
1ICookieSessionRefresherinterfaceMMCA.Common.API.SessionCookies
1 IcsCalendarBuilder class MMCA.Common.Shared.Calendars
1ResetPasswordRequestValidatorclassMMCA.Common.Application.Auth.Validation
1 RsaJwksProvider class MMCA.Common.Infrastructure.Auth1 SessionStampingTokenService classMMCA.Common.Application.AuthMMCA.Common.Application.Auth.Sessions
1
2CookieSessionRefreshMiddlewareclassMMCA.Common.API.SessionCookies
2 EmailConfirmationErrors class MMCA.Common.Application.Auth.EmailConfirmation
2SessionCookieEndpointsclassMMCA.Common.API.SessionCookies
2SessionCookieJarResetPasswordRequestValidator classMMCA.Common.API.SessionCookiesMMCA.Common.Application.Auth.Validation
2
3CookieSessionRefreshMiddlewareExtensionsclassMMCA.Common.API.SessionCookies
3CookieTokenReaderclassMMCA.Common.API.SessionCookiesIAuthSessionIssuerinterfaceMMCA.Common.Application.Auth.Sessions
3
3RefreshSessionclassMMCA.Common.Domain.Auth
3 RoleValue class MMCA.Common.Shared.Auth
4CookieSessionRefresherclassMMCA.Common.API.SessionCookies
4 IErasableUser interface MMCA.Common.Domain.Auth
4IRefreshSessionStoreinterfaceMMCA.Common.Application.AuthRefreshSessionclassMMCA.Common.Domain.Auth
4
4SessionCookieAuthenticationHandlerclassMMCA.Common.API.SessionCookies
4 SetRolePermissionsRequestValidator class MMCA.Common.Application.Auth.Validation
5RefreshSessionRevocationclassIRefreshSessionStoreinterface MMCA.Common.Application.Auth
5SessionCookieAuthenticationExtensionsclassMMCA.Common.API.SessionCookies
5 StoredPermissionRoleAdministrationService class MMCA.Common.Infrastructure.Auth.Administration
6AuthSessionIssuerclassMMCA.Common.Application.Auth.Sessions
6 EmailConfirmationTokenService class MMCA.Common.Infrastructure.AuthMMCA.Common.Infrastructure.Auth
6RefreshSessionRevocationclassMMCA.Common.Application.Auth
8 AuthenticationServiceBase<TUser> classMMCA.Common.API.Authorization
9SessionRefreshOutcomeclassMMCA.Common.API.SessionCookies
10ICookieSessionRefresherinterfaceMMCA.Common.API.SessionCookies
10 OwnerOrAdminFilter class MMCA.Common.API.Authorization
11CookieSessionRefreshMiddlewareclassMMCA.Common.API.SessionCookies
11SessionCookieEndpointsclassMMCA.Common.API.SessionCookies
11SessionCookieJarclassMMCA.Common.API.SessionCookies
12CookieSessionRefreshMiddlewareExtensionsclassMMCA.Common.API.SessionCookies
12CookieTokenReaderclassMMCA.Common.API.SessionCookies
12SessionCookieStoreclassMMCA.Common.API.SessionCookies
13CookieSessionRefresherclassMMCA.Common.API.SessionCookies
13SessionCookieAuthenticationHandlerclassMMCA.Common.API.SessionCookies
14SessionCookieAuthenticationExtensionsclassMMCA.Common.API.SessionCookies

G09 - Caching

@@ -4746,22 +4872,22 @@

G12 - API MMCA.Common.API.Startup - 11 + 14 DependencyInjection class MMCA.Common.API - 13 - DatabaseInitializationExtensions + 15 + AuthControllerBase class - MMCA.Common.API.Startup + MMCA.Common.API.Controllers 15 - AuthControllerBase + DatabaseInitializationExtensions class - MMCA.Common.API.Controllers + MMCA.Common.API.Startup 15 @@ -4778,7 +4904,7 @@

G12 - API

G13 - gRPC & Inter-Service Contracts

-

group-13-grpc-contracts.md | 6 types | Typed gRPC clients/servers, interceptors, Result-over-the-wire, and the ServiceContract marker for synchronous inter-service calls (ADR-007).

+

group-13-grpc-contracts.md | 7 types | Typed gRPC clients/servers, interceptors, Result-over-the-wire, and the ServiceContract marker for synchronous inter-service calls (ADR-007).

@@ -4791,6 +4917,12 @@

G13 - gRPC & Inter-Service Cont

+ + + + + + @@ -4828,7 +4960,7 @@

G13 - gRPC & Inter-Service Cont

0GrpcWireFormatclassMMCA.Common.Grpc
0 JwtForwardingClientInterceptor class MMCA.Common.Grpc.Interceptors

G14 - Module System, Composition & Configuration

-

group-14-module-system-composition.md | 88 types | IModule discovery + Kahn-ordered ModuleLoader, the DI composition roots, assembly markers, data-source/database attributes, and options/settings binding.

+

group-14-module-system-composition.md | 87 types | IModule discovery + Kahn-ordered ModuleLoader, the DI composition roots, assembly markers, data-source/database attributes, and options/settings binding.

@@ -4861,13 +4993,13 @@

G14 - Module System, Com

- + - + @@ -4885,19 +5017,19 @@

G14 - Module System, Com

- + - + - + @@ -5038,12 +5170,6 @@

G14 - Module System, Com

- - - - - - @@ -5350,7 +5476,7 @@

G14 - Module System, Com

- + @@ -5362,7 +5488,7 @@

G14 - Module System, Com

- + @@ -5370,7 +5496,7 @@

G14 - Module System, Com

0 AssemblyReference classMMCA.Common.DomainMMCA.Common.Infrastructure
0 AssemblyReference classMMCA.Common.InfrastructureMMCA.Common.Domain
00 ClassReference classMMCA.Common.ApplicationMMCA.Common.Infrastructure
0 ClassReference classMMCA.Common.DomainMMCA.Common.Application
0 ClassReference classMMCA.Common.InfrastructureMMCA.Common.Domain
0MMCA.Common.Application.Users.UseCases.ExportUserData
0UserUseCaseLogclassMMCA.Common.Application.Users
1 AzureNotificationHubNativePushSender classMMCA.Common.Application
1312 CreateMigrationProofTable class MMCA.Common.Infrastructure.Tests.MigrationsFixtureMMCA.Common.Infrastructure.Scheduling
1417 DependencyInjection class MMCA.Common.Infrastructure

G15 - Common UI Framework (MudBlazor components, theme, base pages)

-

group-15-common-ui-framework.md | 153 types | Reusable Blazor building blocks: the data-grid list page base, theme, common pages/services, and UI extensions shared by every consumer app.

+

group-15-common-ui-framework.md | 174 types | Reusable Blazor building blocks: the data-grid list page base, theme, common pages/services, and UI extensions shared by every consumer app.

@@ -5467,6 +5593,12 @@

G15 - C

+ + + + + + @@ -5593,12 +5725,6 @@

G15 - C

- - - - - - @@ -5623,12 +5749,6 @@

G15 - C

- - - - - - @@ -5647,6 +5767,12 @@

G15 - C

+ + + + + + @@ -5659,21 +5785,9 @@

G15 - C

- - - - - - - - - - - - - + - + @@ -5701,6 +5815,30 @@

G15 - C

+ + + + + + + + + + + + + + + + + + + + + + + + @@ -5719,6 +5857,12 @@

G15 - C

+ + + + + + @@ -5845,12 +5989,6 @@

G15 - C

- - - - - - @@ -5959,24 +6097,60 @@

G15 - C

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -6043,12 +6217,48 @@

G15 - C

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -6133,6 +6343,12 @@

G15 - C

+ + + + + + @@ -6204,18 +6420,6 @@

G15 - C

- - - - - - - - - - - - @@ -6299,6 +6503,54 @@

G15 - C

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
0HandoffBodyrecordMMCA.Common.UI.Web.SameOriginProxy
0 IApiSettings interface MMCA.Common.UI.Common.Settings
0MmcaClientConfigBootstrapclassMMCA.Common.UI.Common.Settings
0 MudTranslations class MMCA.Common.UI.Resources
0PasswordComplexityAttributeclassMMCA.Common.UI.Pages.Auth
0 PendingAttempt record MMCA.Common.UI.Services.Auth.OAuth
0ProxyResponseModeenumMMCA.Common.UI.Web.SameOriginProxy
0 PseudoLocalizer class MMCA.Common.UI.Globalization
0RatingStarsclassMMCA.Common.UI.Components.Ratings
0RegisterModelclassMMCA.Common.UI.Pages.Auth
0ResetPasswordModelRatingStars classMMCA.Common.UI.Pages.AuthMMCA.Common.UI.Components.Ratings
0
0SameOriginApiProxyMarkerclassMMCA.Common.UI.Web.SameOriginProxy
0SameOriginApiProxySettingsclassMMCA.Common.UI.Web.SameOriginProxy
0SameOriginProxyHeadersclassMMCA.Common.UI.Services.Auth
0SameOriginProxyInvokerclassMMCA.Common.UI.Web.SameOriginProxy
0 SharedResource class MMCA.Common.UI.Resources
0TokenPairrecordMMCA.Common.UI.Web.SameOriginProxy
0 TrustedCallerHandler class MMCA.Common.UI.Web.Security
1DirectApiTokenRefresherclassMMCA.Common.UI.Services.Auth.Tokens
1 EndpointCultureApplier class MMCA.Common.UI.Services.Culture
1PasswordComplexityAttributeclassMMCA.Common.UI.Pages.Auth
1 PseudoStringLocalizer class MMCA.Common.UI.Globalization
1RegisterModelclassMMCA.Common.UI.Pages.Auth
1ResetPasswordModelclassMMCA.Common.UI.Pages.Auth
1 ResxMudLocalizer class MMCA.Common.UI.Globalization
1SameOriginApiProxySettingsValidatorclassMMCA.Common.UI.Web.SameOriginProxy
1SameOriginProxyRequestHandlerclassMMCA.Common.UI.Services.Auth
1 SameOriginProxyTokenRefresher class MMCA.Common.UI.Services.Auth.Tokens
1SessionHandoffProtectorclassMMCA.Common.UI.Web.SameOriginProxy
1 ThemeService class MMCA.Common.UI.Theme
2DirectApiTokenRefresherclassMMCA.Common.UI.Services.Auth.Tokens
2HandoffSessionCookieSyncclassMMCA.Common.UI.Web.SameOriginProxy
2HandoffTokenRefresherclassMMCA.Common.UI.Web.SameOriginProxy
2IdempotentReadRetryclassMMCA.Common.UI.Services.Api
2 IUIModule interface MMCA.Common.UI.Common.Interfaces
2LocalizedDataAnnotationsValidatorclassMMCA.Common.UI.Validation
2MmcaClientConfigBootstrapclassMMCA.Common.UI.Common.Settings
2 MMCATheme class MMCA.Common.UI.Theme
3PagedReadAllclassMMCA.Common.UI.Services.Api
3 ResultUiExtensions class MMCA.Common.UI.CommonMMCA.Common.UI.Services.Administration
4ServerTokenStorageServiceclassMMCA.Common.UI.Web.Services
5DependencyInjectionclassMMCA.Common.UI.Web
5 IAuthUIService interfaceclass MMCA.Common.UI.Notifications
8NotificationPageGateclassMMCA.Common.UI.Notifications
12SameOriginProxyTransformerclassMMCA.Common.UI.Web.SameOriginProxy
13SameOriginApiProxyEndpointclassMMCA.Common.UI.Web.SameOriginProxy
13ServerTokenStorageServiceclassMMCA.Common.UI.Web.Services
14DependencyInjectionclassMMCA.Common.UI.Web
14SameOriginApiProxyServiceExtensionsclassMMCA.Common.UI.Web.SameOriginProxy
14SessionHandoffEndpointsclassMMCA.Common.UI.Web.SameOriginProxy
15SameOriginApiProxyEndpointExtensionsclassMMCA.Common.UI.Web.SameOriginProxy

G16 - Aspire Orchestration & Service Defaults

@@ -9006,6 +9258,12 @@

G18 - ADC Conference - Ap MMCA.ADC.Conference.Application.SessionAssets.UseCases.UploadFile + 9 + UserDeletedFeedbackHandler + class + MMCA.ADC.Conference.Application.Users.IntegrationEventHandlers + + 10 ActivityCreateRequestMapper class @@ -9307,12 +9565,6 @@

G18 - ADC Conference - Ap 10 - SpeakerQuestionAnswerNavigationPopulator - class - MMCA.ADC.Conference.Application.Speakers - - - 10 SpeakerSyncStrategy class MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize @@ -9355,12 +9607,6 @@

G18 - ADC Conference - Ap 10 - UpdateEventQuestionAnswerHandler - class - MMCA.ADC.Conference.Application.Events.UseCases.UpdateEventQuestionAnswer - - - 10 UpdateRoomHandler class MMCA.ADC.Conference.Application.Events.UseCases.UpdateRoom @@ -9378,12 +9624,6 @@

G18 - ADC Conference - Ap MMCA.ADC.Conference.Application.Sessions.UseCases.UpdateSessionQuestionAnswer - 10 - UpdateSessionQuestionAnswerHandler - class - MMCA.ADC.Conference.Application.Sessions.UseCases.UpdateSessionQuestionAnswer - - 11 GetPublicActivityFilterHandler class @@ -9517,11 +9757,23 @@

G18 - ADC Conference - Ap 14 + UpdateEventQuestionAnswerHandler + class + MMCA.ADC.Conference.Application.Events.UseCases.UpdateEventQuestionAnswer + + + 14 UpdateQuestionHandler class MMCA.ADC.Conference.Application.Questions.UseCases.Update + 14 + UpdateSessionQuestionAnswerHandler + class + MMCA.ADC.Conference.Application.Sessions.UseCases.UpdateSessionQuestionAnswer + + 15 AddRoomHandler class @@ -9608,130 +9860,130 @@

G19 - ADC Conference MMCA.ADC.Conference.Infrastructure - 8 + 9 + ConferenceModuleDbSeeder + class + MMCA.ADC.Conference.Infrastructure.Persistence.DbContexts.Seeding + + + 12 + ModuleApplicationDbContext + class + MMCA.ADC.Conference.Infrastructure.Persistence.DbContexts + + + 14 + ActivityConfiguration + class + MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Activities + + + 14 CategoryItemConfiguration class MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Categories - 8 + 14 ConferenceCategoryConfiguration class MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Categories - 8 + 14 EventConfiguration class MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Events - 8 + 14 EventQuestionAnswerConfiguration class MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Events - 8 + 14 EventSpeakerConfiguration class MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Events - 8 + 14 + PartnerConfiguration + class + MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Partners + + + 14 QuestionConfiguration class MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Questions - 8 + 14 RoomConfiguration class MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Events - 8 + 14 SessionAiScoreConfiguration class MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Sessions - 8 - SpeakerCategoryItemConfiguration - class - MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Speakers - - - 8 - SpeakerConfiguration - class - MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Speakers - - - 8 - SpeakerQuestionAnswerConfiguration - class - MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Speakers - - - 9 - ActivityConfiguration - class - MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Activities - - - 9 - ConferenceModuleDbSeeder - class - MMCA.ADC.Conference.Infrastructure.Persistence.DbContexts.Seeding - - - 9 - PartnerConfiguration - class - MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Partners - - - 9 + 14 SessionAssetConfiguration class MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.SessionAssets - 9 + 14 SessionCategoryItemConfiguration class MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Sessions - 9 + 14 SessionConfiguration class MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Sessions - 9 + 14 SessionQuestionAnswerConfiguration class MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Sessions - 9 + 14 SessionSpeakerConfiguration class MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Sessions - 9 - SponsorConfiguration + 14 + SpeakerCategoryItemConfiguration class - MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Sponsors + MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Speakers - 12 - ModuleApplicationDbContext + 14 + SpeakerConfiguration class - MMCA.ADC.Conference.Infrastructure.Persistence.DbContexts + MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Speakers + + + 14 + SpeakerQuestionAnswerConfiguration + class + MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Speakers + + + 14 + SponsorConfiguration + class + MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Sponsors

G20 - ADC Conference - API, gRPC Contracts & Service Host

@@ -10062,7 +10314,7 @@

G20 - ADC Confe

G21 - ADC Conference - UI

-

group-21-conference-ui.md | 162 types | The Conference Blazor pages (events, sessions, speakers, categories, questions, rooms, feedback, public, session-selection) and their UI services.

+

group-21-conference-ui.md | 163 types | The Conference Blazor pages (events, sessions, speakers, categories, questions, rooms, feedback, public, session-selection) and their UI services.

@@ -10237,6 +10489,12 @@

G21 - ADC Conference - UI

+ + + + + + @@ -11048,7 +11306,7 @@

G21 - ADC Conference - UI

2PublicReadAudienceclassMMCA.ADC.Conference.UI.Pages.Public
2 QuestionFormModel class MMCA.ADC.Conference.UI.Pages.Questions

G22 - ADC Engagement Module (Session Bookmarks)

-

group-22-engagement-module.md | 194 types | The Engagement bounded context end-to-end: bookmark aggregate, use cases, persistence, API/contracts/service, and feedback UI.

+

group-22-engagement-module.md | 196 types | The Engagement bounded context end-to-end: bookmark aggregate, use cases, persistence, API/contracts/service, and feedback UI.

@@ -11075,13 +11333,13 @@

G22 - ADC Engagement Modu

- + - + @@ -11135,19 +11393,19 @@

G22 - ADC Engagement Modu

- + - + - + @@ -11687,13 +11945,13 @@

G22 - ADC Engagement Modu

- + - + @@ -11931,12 +12189,6 @@

G22 - ADC Engagement Modu

- - - - - - @@ -11967,33 +12219,15 @@

G22 - ADC Engagement Modu

- - - - - - - - - - - - - - - - - - - + - + - + - + @@ -12003,12 +12237,6 @@

G22 - ADC Engagement Modu

- - - - - - @@ -12063,18 +12291,6 @@

G22 - ADC Engagement Modu

- - - - - - - - - - - - @@ -12129,12 +12345,6 @@

G22 - ADC Engagement Modu

- - - - - - @@ -12173,13 +12383,13 @@

G22 - ADC Engagement Modu

- + - + @@ -12223,10 +12433,70 @@

G22 - ADC Engagement Modu

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
0 AssemblyReference classMMCA.ADC.Engagement.ApplicationMMCA.ADC.Engagement.Domain
0 AssemblyReference classMMCA.ADC.Engagement.DomainMMCA.ADC.Engagement.Application
00 ClassReference classMMCA.ADC.Engagement.ApplicationMMCA.ADC.Engagement.Infrastructure
0 ClassReference classMMCA.ADC.Engagement.InfrastructureMMCA.ADC.Engagement.API
0 ClassReference classMMCA.ADC.Engagement.APIMMCA.ADC.Engagement.Application
04 DependencyInjection classMMCA.ADC.Engagement.APIMMCA.ADC.Engagement.Infrastructure
4 DependencyInjection classMMCA.ADC.Engagement.InfrastructureMMCA.ADC.Engagement.API
4
8AttendeeBadgeConfigurationclassMMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.CheckIns
8 BookmarkCountService class MMCA.ADC.Engagement.Application.UserSessionBookmarks.Services
8LeaderboardOptInConfigurationclassMMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.Points
8LivePollVoteConfigurationclassMMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.LivePolls
8PointsEntryConfigurationclassMMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.Points
8SessionQuestionConfigurationUserDeletedBadgeHandler classMMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.SessionQuestionsMMCA.ADC.Engagement.Application.CheckIns.IntegrationEventHandlers
8SessionQuestionUpvoteConfigurationUserDeletedBookmarksHandler classMMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.SessionQuestionsMMCA.ADC.Engagement.Application.UserSessionBookmarks.IntegrationEventHandlers
8
8UserSessionBookmarkConfigurationclassMMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.UserSessionBookmarks
8 UserSessionBookmarkDTOMapper class MMCA.ADC.Engagement.Application.UserSessionBookmarks.DTOs
9LivePollConfigurationclassMMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.LivePolls
9LivePollOptionConfigurationclassMMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.LivePolls
9 LivePollVotingController class MMCA.ADC.Engagement.API.Controllers
11CheckInConfigurationclassMMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.CheckIns
11 CheckInDTOMapper class MMCA.ADC.Engagement.Application.CheckIns.DTOs12 DependencyInjection classMMCA.ADC.Engagement.UIMMCA.ADC.Engagement.Application
12 DependencyInjection classMMCA.ADC.Engagement.ApplicationMMCA.ADC.Engagement.UI
12class MMCA.ADC.Engagement.Contracts
14AttendeeBadgeConfigurationclassMMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.CheckIns
14CheckInConfigurationclassMMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.CheckIns
14LeaderboardOptInConfigurationclassMMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.Points
14LivePollConfigurationclassMMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.LivePolls
14LivePollOptionConfigurationclassMMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.LivePolls
14LivePollVoteConfigurationclassMMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.LivePolls
14PointsEntryConfigurationclassMMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.Points
14SessionQuestionConfigurationclassMMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.SessionQuestions
14SessionQuestionUpvoteConfigurationclassMMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.SessionQuestions
14UserSessionBookmarkConfigurationclassMMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.UserSessionBookmarks

G26 - ADC Engagement Live Layer (Real-Time Polls & Session Q&A)

-

group-23-engagement-live-layer.md | 85 types | Real-time audience interaction in the Engagement bounded context: event-wide live polls with voting and moderated per-session Q&A with upvoting, over the SignalR hub-channel transport (ADR-039) and the cross-service gRPC live-channel adapter.

+

group-23-engagement-live-layer.md | 87 types | Real-time audience interaction in the Engagement bounded context: event-wide live polls with voting and moderated per-session Q&A with upvoting, over the SignalR hub-channel transport (ADR-039) and the cross-service gRPC live-channel adapter.

@@ -12622,6 +12892,18 @@

G26 - ADC E

+ + + + + + + + + + + + @@ -12750,7 +13032,7 @@

G26 - ADC E

MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.ToggleUpvote
8UserDeletedSessionQuestionsHandlerclassMMCA.ADC.Engagement.Application.SessionQuestions.IntegrationEventHandlers
8UserDeletedVotesHandlerclassMMCA.ADC.Engagement.Application.LivePolls.IntegrationEventHandlers
9 DeleteLivePollHandler class

G23 - ADC Identity Module (Users, Profiles, GDPR Export/Erasure)

-

group-24-identity-module.md | 107 types | The Identity bounded context end-to-end: the User aggregate, change-password/delete/export use cases, persistence, API/contracts/service, and profile/user UI.

+

group-24-identity-module.md | 108 types | The Identity bounded context end-to-end: the User aggregate, change-password/delete/export use cases, persistence, API/contracts/service, and profile/user UI.

@@ -12765,7 +13047,7 @@

G23 - ADC I

- + @@ -12777,7 +13059,7 @@

G23 - ADC I

- + @@ -12853,6 +13135,12 @@

G23 - ADC I

+ + + + + + @@ -12925,12 +13213,6 @@

G23 - ADC I

- - - - - - @@ -12991,6 +13273,12 @@

G23 - ADC I

+ + + + + + @@ -13243,12 +13531,6 @@

G23 - ADC I

- - - - - - @@ -13362,6 +13644,12 @@

G23 - ADC I

+ + + + + + @@ -14322,28 +14610,28 @@

G28 - Common AI Integration

- - + + - + - + - + - + - - + + - + @@ -14360,7 +14648,7 @@

G28 - Common AI Integration

0 AssemblyReference classMMCA.ADC.Identity.DomainMMCA.ADC.Identity.Infrastructure
00 AssemblyReference classMMCA.ADC.Identity.InfrastructureMMCA.ADC.Identity.Domain
0
0JwtAudienceclassMMCA.ADC.Identity.Shared.Authorization
0 RemoveUserAvatarCommand record MMCA.ADC.Identity.Application.Users.UseCases.RemoveUserAvatar
1ChangePasswordRequestValidatorclassMMCA.ADC.Identity.Application.Users.Validation
1 ConfirmEmailCommand record MMCA.ADC.Identity.Application.Users.UseCases.ConfirmEmail
2ChangePasswordRequestValidatorclassMMCA.ADC.Identity.Application.Users.Validation
2 ExportUserDataQuery record MMCA.ADC.Identity.Application.Users.UseCases.ExportUserData
8UserConfigurationclassMMCA.ADC.Identity.Infrastructure.Persistence.EntityConfiguration
8 UserDTOMapper class MMCA.ADC.Identity.Application.Users.DTOsMMCA.ADC.Identity.Application.Users.UseCases.SendEmailConfirmation
14UserConfigurationclassMMCA.ADC.Identity.Infrastructure.Persistence.EntityConfiguration
15 DependencyInjection class MMCA.Common.AI.OpenAI
2PiiRedactionGuardrail3AnthropicAiServiceCollectionExtensions classMMCA.Common.AI.GuardrailsMMCA.Common.AI.Anthropic
3AnthropicAiServiceCollectionExtensionsOpenAiServiceCollectionExtensions classMMCA.Common.AI.AnthropicMMCA.Common.AI.OpenAI
3GuardrailServiceCollectionExtensionsPiiRedactionGuardrail class MMCA.Common.AI.Guardrails
3OpenAiServiceCollectionExtensions4GuardrailServiceCollectionExtensions classMMCA.Common.AI.OpenAIMMCA.Common.AI.Guardrails
9

G25 - Testing & Quality Infrastructure

-

group-28-testing-infrastructure.md | 2771 types | All test projects + the reusable Testing/Testing.E2E/Testing.UI bases, architecture-fitness tests, and the component Gallery harness; individual [Fact]s are rolled up by project (logged exception).

+

group-28-testing-infrastructure.md | 2923 types | All test projects + the reusable Testing/Testing.E2E/Testing.UI bases, architecture-fitness tests, and the component Gallery harness; individual [Fact]s are rolled up by project (logged exception).

Rolled up by project (individual [Fact]s not sectioned - logged exception). Reusable test infrastructure assemblies (sectioned in full in the chapter) are marked (infra).

@@ -14375,8 +14663,8 @@

G25 - Testing & Quality Infra MMCA.ADC.Architecture.Tests (infra) - 56 - L0-L15 + 57 + L0-L16 @@ -14387,7 +14675,7 @@

G25 - Testing & Quality Infra MMCA.ADC.Conference.Application.Tests - 193 + 196 L0-L16 @@ -14400,7 +14688,7 @@

G25 - Testing & Quality Infra MMCA.ADC.Conference.Infrastructure.Tests 11 - L0-L10 + L0-L15 @@ -14423,8 +14711,8 @@

G25 - Testing & Quality Infra MMCA.ADC.Conference.UI.Tests - 66 - L1-L11 + 81 + L0-L11 @@ -14447,7 +14735,7 @@

G25 - Testing & Quality Infra MMCA.ADC.Engagement.Application.Tests - 65 + 70 L1-L15 @@ -14460,7 +14748,7 @@

G25 - Testing & Quality Infra MMCA.ADC.Engagement.Infrastructure.Tests 4 - L1-L11 + L1-L16 @@ -14519,8 +14807,8 @@

G25 - Testing & Quality Infra MMCA.ADC.Identity.Shared.Tests - 2 - L2-L4 + 3 + L1-L4 @@ -14555,7 +14843,7 @@

G25 - Testing & Quality Infra MMCA.ADC.Services.Tests - 7 + 17 L0-L13 @@ -14573,19 +14861,19 @@

G25 - Testing & Quality Infra MMCA.Common.API.Tests - 155 + 170 L0-L18 MMCA.Common.Application.Tests - 398 + 401 L0-L16 MMCA.Common.Architecture.Tests (infra) - 230 + 254 L0-L14 @@ -14597,7 +14885,7 @@

G25 - Testing & Quality Infra MMCA.Common.Aspire.Tests - 50 + 52 L0-L13 @@ -14609,7 +14897,7 @@

G25 - Testing & Quality Infra MMCA.Common.Domain.Tests - 62 + 64 L0-L8 @@ -14621,7 +14909,7 @@

G25 - Testing & Quality Infra MMCA.Common.Grpc.Tests - 16 + 18 L0-L4 @@ -14638,14 +14926,26 @@

G25 - Testing & Quality Infra + MMCA.Common.Infrastructure.SQLServer.Tests + 8 + L1-L15 + + + MMCA.Common.Infrastructure.Tests - 499 + 514 + L0-L18 + + + + MMCA.Common.LoadTests + 14 L0-L17 MMCA.Common.Shared.Tests - 58 + 60 L0-L6 @@ -14693,14 +14993,14 @@

G25 - Testing & Quality Infra MMCA.Common.Testing.UI (infra) - 19 + 18 L0-L5 MMCA.Common.UI.E2E.Tests - 20 - L2-L13 + 21 + L0-L13 @@ -14711,14 +15011,14 @@

G25 - Testing & Quality Infra MMCA.Common.UI.Tests - 152 - L0-L8 + 170 + L0-L9 MMCA.Common.UI.Web.Tests - 12 - L0-L5 + 24 + L0-L14 diff --git a/docs/onboarding/00-inventory.html b/docs/onboarding/00-inventory.html index 24a2c313..28a2d98b 100644 --- a/docs/onboarding/00-inventory.html +++ b/docs/onboarding/00-inventory.html @@ -149,11 +149,11 @@

Phase 0: Type Inventory

Generated mechanically by a Roslyn syntactic parse of every in-scope .cs file under MMCA.Common/Source, MMCA.Common/Tests, MMCA.ADC/Source, MMCA.ADC/Tests.

    -
  • Files scanned: 3860 (in-scope 3724, generated/excluded 136)
  • -
  • Type declaration rows (including partial-class fragments): 5200
  • -
  • Distinct type nodes (partials collapsed): 5034
  • -
  • extension(T) blocks: 115
  • -
  • Source HEADs: MMCA.Common 834a8fa, MMCA.ADC 3a32fd34
  • +
  • Files scanned: 4000 (in-scope 3862, generated/excluded 138)
  • +
  • Type declaration rows (including partial-class fragments): 5402
  • +
  • Distinct type nodes (partials collapsed): 5234
  • +
  • extension(T) blocks: 118
  • +
  • Source HEADs: MMCA.Common cd0026df, MMCA.ADC 024e4a98

Counts by kind

@@ -165,23 +165,23 @@

Counts by kind

- + - + - + - - + + - - + + @@ -210,7 +210,7 @@

Counts by assembly (distinct nodes)

- + @@ -226,7 +226,7 @@

Counts by assembly (distinct nodes)

- + @@ -270,11 +270,11 @@

Counts by assembly (distinct nodes)

- + - + @@ -294,11 +294,11 @@

Counts by assembly (distinct nodes)

- + - + @@ -394,11 +394,11 @@

Counts by assembly (distinct nodes)

- + - + @@ -446,7 +446,7 @@

Counts by assembly (distinct nodes)

- + @@ -482,23 +482,23 @@

Counts by assembly (distinct nodes)

- + - + - + - + - + @@ -514,7 +514,7 @@

Counts by assembly (distinct nodes)

- + @@ -526,7 +526,7 @@

Counts by assembly (distinct nodes)

- + @@ -538,15 +538,15 @@

Counts by assembly (distinct nodes)

- + - + - + @@ -557,20 +557,28 @@

Counts by assembly (distinct nodes)2

+ + + + - + + + + + - + - + @@ -602,15 +610,15 @@

Counts by assembly (distinct nodes)

- + - + - + @@ -622,15 +630,15 @@

Counts by assembly (distinct nodes)

- + - + - +
class41454315
record706728
interface257262
record struct45enum47
enum43record struct46
delegate
MMCA.ADC.Architecture.Tests5657
MMCA.ADC.Conference.API
MMCA.ADC.Conference.Application.Tests193196
MMCA.ADC.Conference.Contracts
MMCA.ADC.Conference.UI162163
MMCA.ADC.Conference.UI.Tests6681
MMCA.ADC.CrossService.IntegrationTests
MMCA.ADC.Engagement.Application8791
MMCA.ADC.Engagement.Application.Tests6570
MMCA.ADC.Engagement.Contracts
MMCA.ADC.Identity.Shared1920
MMCA.ADC.Identity.Shared.Tests23
MMCA.ADC.Identity.UI
MMCA.ADC.Services.Tests717
MMCA.ADC.UI
MMCA.Common.API108114
MMCA.Common.API.Tests155170
MMCA.Common.Application273274
MMCA.Common.Application.Tests398401
MMCA.Common.Architecture.Tests230254
MMCA.Common.Aspire
MMCA.Common.Aspire.Tests5052
MMCA.Common.Benchmarks
MMCA.Common.Domain.Tests6264
MMCA.Common.Gateway
MMCA.Common.Grpc56
MMCA.Common.Grpc.Tests1618
MMCA.Common.Infrastructure238250
MMCA.Common.Infrastructure.PostgreSQL.Tests
MMCA.Common.Infrastructure.SQLServer.Tests8
MMCA.Common.Infrastructure.Tests499514
MMCA.Common.Infrastructure.Tests.MigrationsFixture 1
MMCA.Common.LoadTests14
MMCA.Common.Shared104105
MMCA.Common.Shared.Tests5860
MMCA.Common.Testing
MMCA.Common.Testing.UI1918
MMCA.Common.UI207213
MMCA.Common.UI.E2E.Tests2021
MMCA.Common.UI.Gallery
MMCA.Common.UI.Tests152170
MMCA.Common.UI.Web1429
MMCA.Common.UI.Web.Tests1224

Full inventory

@@ -1037,6 +1045,13 @@

Full inventory

MMCA.ADC.Architecture.Tests/Ui/LocalizedTextConventionTests.cs:14 + MobileHostParityTests + class + MMCA.ADC.Architecture.Tests + MMCA.ADC.Architecture.Tests.Ui + MMCA.ADC.Architecture.Tests/Ui/MobileHostParityTests.cs:16 + + SortableColumnConventionTests class MMCA.ADC.Architecture.Tests @@ -1405,14 +1420,14 @@

Full inventory

class MMCA.ADC.Conference.API.Tests MMCA.ADC.Conference.API.Tests.Controllers.Categories - MMCA.ADC.Conference.API.Tests/Controllers/Categories/CategoryItemsControllerTests.cs:19 + MMCA.ADC.Conference.API.Tests/Controllers/Categories/CategoryItemsControllerTests.cs:20 ConferenceCategoriesControllerTests class MMCA.ADC.Conference.API.Tests MMCA.ADC.Conference.API.Tests.Controllers.Categories - MMCA.ADC.Conference.API.Tests/Controllers/Categories/ConferenceCategoriesControllerTests.cs:20 + MMCA.ADC.Conference.API.Tests/Controllers/Categories/ConferenceCategoriesControllerTests.cs:21 EventLifecycleControllerTests @@ -1461,7 +1476,7 @@

Full inventory

class MMCA.ADC.Conference.API.Tests MMCA.ADC.Conference.API.Tests.Controllers.Questions - MMCA.ADC.Conference.API.Tests/Controllers/Questions/QuestionsControllerTests.cs:20 + MMCA.ADC.Conference.API.Tests/Controllers/Questions/QuestionsControllerTests.cs:21 SessionAssetsControllerTests @@ -1531,7 +1546,7 @@

Full inventory

class MMCA.ADC.Conference.API.Tests MMCA.ADC.Conference.API.Tests.Controllers.Speakers - MMCA.ADC.Conference.API.Tests/Controllers/Speakers/SpeakersControllerTests.cs:28 + MMCA.ADC.Conference.API.Tests/Controllers/Speakers/SpeakersControllerTests.cs:30 SpeakerSessionsControllerTests @@ -2238,7 +2253,7 @@

Full inventory

class MMCA.ADC.Conference.Application MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize - MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/CategorySyncStrategy.cs:12 + MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/CategorySyncStrategy.cs:13 ISessionizeSyncStrategy @@ -2259,7 +2274,7 @@

Full inventory

record MMCA.ADC.Conference.Application MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize - MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeCommand.cs:13 + MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeCommand.cs:15 RefreshFromSessionizeHandler @@ -2308,7 +2323,7 @@

Full inventory

class MMCA.ADC.Conference.Application MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize - MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/SpeakerSyncStrategy.cs:14 + MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/SpeakerSyncStrategy.cs:16 RemoveEventQuestionAnswerCommand @@ -2420,7 +2435,7 @@

Full inventory

class MMCA.ADC.Conference.Application MMCA.ADC.Conference.Application.Events.UseCases.UpdateEventQuestionAnswer - MMCA.ADC.Conference.Application/Events/UseCases/UpdateEventQuestionAnswer/UpdateEventQuestionAnswerHandler.cs:21 + MMCA.ADC.Conference.Application/Events/UseCases/UpdateEventQuestionAnswer/UpdateEventQuestionAnswerHandler.cs:22 UpdateRoomCommand @@ -2896,7 +2911,7 @@

Full inventory

class MMCA.ADC.Conference.Application MMCA.ADC.Conference.Application.SessionAssets.UseCases.UploadFile - MMCA.ADC.Conference.Application/SessionAssets/UseCases/UploadFile/UploadSessionAssetHandler.cs:35 + MMCA.ADC.Conference.Application/SessionAssets/UseCases/UploadFile/UploadSessionAssetHandler.cs:37 ISessionAssetFieldsRequest @@ -3295,7 +3310,7 @@

Full inventory

class MMCA.ADC.Conference.Application MMCA.ADC.Conference.Application.Sessions.UseCases.DecisionSupport.ScoreEventSessions - MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/SessionScoringRunner.cs:17 + MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/SessionScoringRunner.cs:23 SpeakerInfo @@ -3512,7 +3527,7 @@

Full inventory

class MMCA.ADC.Conference.Application MMCA.ADC.Conference.Application.Sessions.UseCases.UpdateSessionQuestionAnswer - MMCA.ADC.Conference.Application/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerHandler.cs:21 + MMCA.ADC.Conference.Application/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerHandler.cs:23 ISessionFieldsRequest @@ -3613,13 +3628,6 @@

Full inventory

MMCA.ADC.Conference.Application/Speakers/SpeakerNavigationPopulator.cs:11 - SpeakerQuestionAnswerNavigationPopulator - class - MMCA.ADC.Conference.Application - MMCA.ADC.Conference.Application.Speakers - MMCA.ADC.Conference.Application/Speakers/SpeakerQuestionAnswerNavigationPopulator.cs:11 - - SpeakerDeletedHandler class MMCA.ADC.Conference.Application @@ -4068,6 +4076,13 @@

Full inventory

MMCA.ADC.Conference.Application/Sponsors/Validation/SponsorValidationRules.cs:56 + UserDeletedFeedbackHandler + class + MMCA.ADC.Conference.Application + MMCA.ADC.Conference.Application.Users.IntegrationEventHandlers + MMCA.ADC.Conference.Application/Users/IntegrationEventHandlers/UserDeletedFeedbackHandler.cs:23 + + UserRegisteredHandler class MMCA.ADC.Conference.Application @@ -4187,6 +4202,13 @@

Full inventory

MMCA.ADC.Conference.Application.Tests/Categories/UseCases/CreateConferenceCategoryHandlerTests.cs:13 + DeleteConferenceCategoryHandlerTests + class + MMCA.ADC.Conference.Application.Tests + MMCA.ADC.Conference.Application.Tests.Categories.UseCases + MMCA.ADC.Conference.Application.Tests/Categories/UseCases/DeleteConferenceCategoryHandlerTests.cs:17 + + RemoveCategoryItemHandlerTests class MMCA.ADC.Conference.Application.Tests @@ -4422,7 +4444,7 @@

Full inventory

class MMCA.ADC.Conference.Application.Tests MMCA.ADC.Conference.Application.Tests.Events.UseCases.RefreshFromSessionize - MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeHandlerTests.cs:16 + MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeHandlerTests.cs:17 RoomSyncStrategyTests @@ -4485,7 +4507,7 @@

Full inventory

class MMCA.ADC.Conference.Application.Tests MMCA.ADC.Conference.Application.Tests.Events.UseCases.UpdateEventQuestionAnswer - MMCA.ADC.Conference.Application.Tests/Events/UseCases/UpdateEventQuestionAnswer/UpdateEventQuestionAnswerHandlerTests.cs:13 + MMCA.ADC.Conference.Application.Tests/Events/UseCases/UpdateEventQuestionAnswer/UpdateEventQuestionAnswerHandlerTests.cs:14 UpdateRoomHandlerTests @@ -4782,6 +4804,13 @@

Full inventory

MMCA.ADC.Conference.Application.Tests/SessionAssets/UseCases/GetSessionAssetsHandlerTests.cs:13 + UpdateSessionAssetHandlerTests + class + MMCA.ADC.Conference.Application.Tests + MMCA.ADC.Conference.Application.Tests.SessionAssets.UseCases + MMCA.ADC.Conference.Application.Tests/SessionAssets/UseCases/UpdateSessionAssetHandlerTests.cs:21 + + UploadSessionAssetCommandMarkerTests class MMCA.ADC.Conference.Application.Tests @@ -4793,7 +4822,7 @@

Full inventory

class MMCA.ADC.Conference.Application.Tests MMCA.ADC.Conference.Application.Tests.SessionAssets.UseCases - MMCA.ADC.Conference.Application.Tests/SessionAssets/UseCases/UploadSessionAssetHandlerTests.cs:17 + MMCA.ADC.Conference.Application.Tests/SessionAssets/UseCases/UploadSessionAssetHandlerTests.cs:18 SessionAssetLinkRequestValidatorTests @@ -4982,7 +5011,7 @@

Full inventory

class MMCA.ADC.Conference.Application.Tests MMCA.ADC.Conference.Application.Tests.Sessions.UseCases.DecisionSupport - MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/DecisionSupport/SessionScoringRunnerTests.cs:16 + MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/DecisionSupport/SessionScoringRunnerTests.cs:17 SessionSimilarityCalculatorTests @@ -5101,7 +5130,7 @@

Full inventory

class MMCA.ADC.Conference.Application.Tests MMCA.ADC.Conference.Application.Tests.Sessions.UseCases.UpdateSessionQuestionAnswer - MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerHandlerTests.cs:14 + MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerHandlerTests.cs:15 AddSessionCategoryItemCommandValidatorTests @@ -5195,13 +5224,6 @@

Full inventory

MMCA.ADC.Conference.Application.Tests/Speakers/SpeakerNavigationPopulatorTests.cs:9 - SpeakerQuestionAnswerNavigationPopulatorTests - class - MMCA.ADC.Conference.Application.Tests - MMCA.ADC.Conference.Application.Tests.Speakers - MMCA.ADC.Conference.Application.Tests/Speakers/SpeakerQuestionAnswerNavigationPopulatorTests.cs:9 - - Mocks record MMCA.ADC.Conference.Application.Tests @@ -5251,6 +5273,13 @@

Full inventory

MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/Create/CreateSpeakerHandlerTests.cs:14 + SpeakerCreateRequestMapperTests + class + MMCA.ADC.Conference.Application.Tests + MMCA.ADC.Conference.Application.Tests.Speakers.UseCases.Create + MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/Create/SpeakerCreateRequestMapperTests.cs:7 + + GetPublicSpeakerCategoryItemFilterHandlerTests class MMCA.ADC.Conference.Application.Tests @@ -5419,6 +5448,13 @@

Full inventory

MMCA.ADC.Conference.Application.Tests/Users/IntegrationEventHandlers/UserRegisteredHandlerTests.cs:19 + UserDeletedFeedbackHandlerTests + class + MMCA.ADC.Conference.Application.Tests + MMCA.ADC.Conference.Application.Tests.Users.IntegrationEventHandlers + MMCA.ADC.Conference.Application.Tests/Users/IntegrationEventHandlers/UserDeletedFeedbackHandlerTests.cs:16 + + UserRegisteredHandlerTests class MMCA.ADC.Conference.Application.Tests @@ -7215,7 +7251,7 @@

Full inventory

class MMCA.ADC.Conference.UI MMCA.ADC.Conference.UI - MMCA.ADC.Conference.UI/DependencyInjection.cs:18 + MMCA.ADC.Conference.UI/DependencyInjection.cs:19 ActivityCreate @@ -7425,7 +7461,7 @@

Full inventory

enum MMCA.ADC.Conference.UI MMCA.ADC.Conference.UI.Pages.Home - MMCA.ADC.Conference.UI/Pages/Home/ADCHome.razor.cs:90 + MMCA.ADC.Conference.UI/Pages/Home/ADCHome.razor.cs:93 KeynoteSpeakerInfo @@ -7484,6 +7520,13 @@

Full inventory

MMCA.ADC.Conference.UI/Pages/Partners/PartnerList.razor.cs:19 + PublicReadAudience + class + MMCA.ADC.Conference.UI + MMCA.ADC.Conference.UI.Pages.Public + MMCA.ADC.Conference.UI/Pages/Public/PublicReadAudience.cs:10 + + PublicScheduleRoomOptions class MMCA.ADC.Conference.UI @@ -7509,14 +7552,14 @@

Full inventory

class MMCA.ADC.Conference.UI MMCA.ADC.Conference.UI.Pages.Public.Events - MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventDetail.razor.cs:19 + MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventDetail.razor.cs:18 PublicEventList class MMCA.ADC.Conference.UI MMCA.ADC.Conference.UI.Pages.Public.Events - MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventList.razor.cs:31 + MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventList.razor.cs:30 VenueMapLinks @@ -7537,7 +7580,7 @@

Full inventory

class MMCA.ADC.Conference.UI MMCA.ADC.Conference.UI.Pages.Public.Sessions - MMCA.ADC.Conference.UI/Pages/Public/Sessions/PublicSessionList.razor.cs:30 + MMCA.ADC.Conference.UI/Pages/Public/Sessions/PublicSessionList.razor.cs:29 PublicSessionListFilterBar @@ -7572,7 +7615,7 @@

Full inventory

class MMCA.ADC.Conference.UI MMCA.ADC.Conference.UI.Pages.Public.Speakers - MMCA.ADC.Conference.UI/Pages/Public/Speakers/PublicSpeakerList.razor.cs:33 + MMCA.ADC.Conference.UI/Pages/Public/Speakers/PublicSpeakerList.razor.cs:32 PublicSponsorList @@ -7635,7 +7678,7 @@

Full inventory

class MMCA.ADC.Conference.UI MMCA.ADC.Conference.UI.Pages.Rooms - MMCA.ADC.Conference.UI/Pages/Rooms/RoomCreateModel.cs:12 + MMCA.ADC.Conference.UI/Pages/Rooms/RoomCreateModel.cs:11 RoomDetail @@ -8125,7 +8168,7 @@

Full inventory

class MMCA.ADC.Conference.UI MMCA.ADC.Conference.UI.Services.Feedback - MMCA.ADC.Conference.UI/Services/Feedback/OrganizerFeedbackService.cs:66 + MMCA.ADC.Conference.UI/Services/Feedback/OrganizerFeedbackService.cs:70 IPartnerUIService @@ -8342,7 +8385,7 @@

Full inventory

class MMCA.ADC.Conference.UI.Tests MMCA.ADC.Conference.UI.Tests - MMCA.ADC.Conference.UI.Tests/BunitTestBase.cs:65 + MMCA.ADC.Conference.UI.Tests/BunitTestBase.cs:69 ManagementRouteAuthorizationTests @@ -8496,7 +8539,7 @@

Full inventory

class MMCA.ADC.Conference.UI.Tests MMCA.ADC.Conference.UI.Tests.Pages.Public - MMCA.ADC.Conference.UI.Tests/Pages/Public/PublicActivityListTests.cs:17 + MMCA.ADC.Conference.UI.Tests/Pages/Public/PublicActivityListTests.cs:19 PublicEventDetailTests @@ -8513,11 +8556,18 @@

Full inventory

MMCA.ADC.Conference.UI.Tests/Pages/Public/PublicEventListRedirectTests.cs:35 + PublicReadAudienceTests + class + MMCA.ADC.Conference.UI.Tests + MMCA.ADC.Conference.UI.Tests.Pages.Public + MMCA.ADC.Conference.UI.Tests/Pages/Public/PublicReadAudienceTests.cs:16 + + PublicSponsorListTests class MMCA.ADC.Conference.UI.Tests MMCA.ADC.Conference.UI.Tests.Pages.Public - MMCA.ADC.Conference.UI.Tests/Pages/Public/PublicSponsorListTests.cs:19 + MMCA.ADC.Conference.UI.Tests/Pages/Public/PublicSponsorListTests.cs:20 VenueMapLinksTests @@ -8555,6 +8605,13 @@

Full inventory

MMCA.ADC.Conference.UI.Tests/Pages/Public/Sessions/PublicSessionListEventFilterTests.cs:23 + PublicSessionListMyScheduleTests + class + MMCA.ADC.Conference.UI.Tests + MMCA.ADC.Conference.UI.Tests.Pages.Public.Sessions + MMCA.ADC.Conference.UI.Tests/Pages/Public/Sessions/PublicSessionListMyScheduleTests.cs:21 + + PublicSessionListRoomFilterTests class MMCA.ADC.Conference.UI.Tests @@ -8618,11 +8675,18 @@

Full inventory

MMCA.ADC.Conference.UI.Tests/Pages/Questions/QuestionDetailTests.cs:18 + RoomCreateModelTests + class + MMCA.ADC.Conference.UI.Tests + MMCA.ADC.Conference.UI.Tests.Pages.Rooms + MMCA.ADC.Conference.UI.Tests/Pages/Rooms/RoomCreateModelTests.cs:7 + + RoomDetailTests class MMCA.ADC.Conference.UI.Tests MMCA.ADC.Conference.UI.Tests.Pages.Rooms - MMCA.ADC.Conference.UI.Tests/Pages/Rooms/RoomDetailTests.cs:17 + MMCA.ADC.Conference.UI.Tests/Pages/Rooms/RoomDetailTests.cs:18 SessionAssetsDownloadListTests @@ -8639,6 +8703,27 @@

Full inventory

MMCA.ADC.Conference.UI.Tests/Pages/SessionAssets/SessionAssetsPanelTests.cs:19 + FixedTimeProvider + class + MMCA.ADC.Conference.UI.Tests + MMCA.ADC.Conference.UI.Tests.Pages.Sessions + MMCA.ADC.Conference.UI.Tests/Pages/Sessions/SessionListCurrentEventClockTests.cs:91 + + + SessionCreateModelTests + class + MMCA.ADC.Conference.UI.Tests + MMCA.ADC.Conference.UI.Tests.Pages.Sessions + MMCA.ADC.Conference.UI.Tests/Pages/Sessions/SessionCreateModelTests.cs:7 + + + SessionCreateTests + class + MMCA.ADC.Conference.UI.Tests + MMCA.ADC.Conference.UI.Tests.Pages.Sessions + MMCA.ADC.Conference.UI.Tests/Pages/Sessions/SessionCreateTests.cs:21 + + SessionDetailCategoryChipTests class MMCA.ADC.Conference.UI.Tests @@ -8653,6 +8738,13 @@

Full inventory

MMCA.ADC.Conference.UI.Tests/Pages/Sessions/SessionDetailRoomCacheTests.cs:26 + SessionDetailScheduleTests + class + MMCA.ADC.Conference.UI.Tests + MMCA.ADC.Conference.UI.Tests.Pages.Sessions + MMCA.ADC.Conference.UI.Tests/Pages/Sessions/SessionDetailScheduleTests.cs:25 + + SessionDetailStaleLoadTests class MMCA.ADC.Conference.UI.Tests @@ -8660,6 +8752,20 @@

Full inventory

MMCA.ADC.Conference.UI.Tests/Pages/Sessions/SessionDetailStaleLoadTests.cs:26 + SessionEditModelTests + class + MMCA.ADC.Conference.UI.Tests + MMCA.ADC.Conference.UI.Tests.Pages.Sessions + MMCA.ADC.Conference.UI.Tests/Pages/Sessions/SessionEditModelTests.cs:8 + + + SessionListCurrentEventClockTests + class + MMCA.ADC.Conference.UI.Tests + MMCA.ADC.Conference.UI.Tests.Pages.Sessions + MMCA.ADC.Conference.UI.Tests/Pages/Sessions/SessionListCurrentEventClockTests.cs:23 + + SessionListEventFilterTests class MMCA.ADC.Conference.UI.Tests @@ -8737,6 +8843,13 @@

Full inventory

MMCA.ADC.Conference.UI.Tests/Pages/Speakers/SpeakerDetailTests.cs:24 + SpeakerEditModelTests + class + MMCA.ADC.Conference.UI.Tests + MMCA.ADC.Conference.UI.Tests.Pages.Speakers + MMCA.ADC.Conference.UI.Tests/Pages/Speakers/SpeakerEditModelTests.cs:8 + + SpeakerQrTests class MMCA.ADC.Conference.UI.Tests @@ -8779,6 +8892,34 @@

Full inventory

MMCA.ADC.Conference.UI.Tests/Services/OrganizerSessionFeedbackServiceTests.cs:14 + PublicSessionScheduleServiceTests + class + MMCA.ADC.Conference.UI.Tests + MMCA.ADC.Conference.UI.Tests.Services + MMCA.ADC.Conference.UI.Tests/Services/PublicSessionScheduleServiceTests.cs:15 + + + RecordingCacheStore + class + MMCA.ADC.Conference.UI.Tests + MMCA.ADC.Conference.UI.Tests.Services + MMCA.ADC.Conference.UI.Tests/Services/PublicSessionScheduleServiceTests.cs:79 + + + RoomServiceTests + class + MMCA.ADC.Conference.UI.Tests + MMCA.ADC.Conference.UI.Tests.Services + MMCA.ADC.Conference.UI.Tests/Services/RoomServiceTests.cs:10 + + + SessionAssetServiceTests + class + MMCA.ADC.Conference.UI.Tests + MMCA.ADC.Conference.UI.Tests.Services + MMCA.ADC.Conference.UI.Tests/Services/SessionAssetServiceTests.cs:12 + + SessionSelectionServiceTests class MMCA.ADC.Conference.UI.Tests @@ -8793,6 +8934,13 @@

Full inventory

MMCA.ADC.Conference.UI.Tests/Services/SpeakerDashboardServiceTests.cs:20 + SpeakerLookupServiceTests + class + MMCA.ADC.Conference.UI.Tests + MMCA.ADC.Conference.UI.Tests.Services + MMCA.ADC.Conference.UI.Tests/Services/SpeakerLookupServiceTests.cs:13 + + BookmarkCountGrpcTests class MMCA.ADC.CrossService.IntegrationTests @@ -9745,6 +9893,13 @@

Full inventory

MMCA.ADC.Engagement.Application/CheckIns/DTOs/CheckInDTOMapper.cs:12 + UserDeletedBadgeHandler + class + MMCA.ADC.Engagement.Application + MMCA.ADC.Engagement.Application.CheckIns.IntegrationEventHandlers + MMCA.ADC.Engagement.Application/CheckIns/IntegrationEventHandlers/UserDeletedBadgeHandler.cs:22 + + CheckInProcessor class MMCA.ADC.Engagement.Application @@ -9885,6 +10040,13 @@

Full inventory

MMCA.ADC.Engagement.Application/LivePolls/DTOs/LivePollDTOMapper.cs:13 + UserDeletedVotesHandler + class + MMCA.ADC.Engagement.Application + MMCA.ADC.Engagement.Application.LivePolls.IntegrationEventHandlers + MMCA.ADC.Engagement.Application/LivePolls/IntegrationEventHandlers/UserDeletedVotesHandler.cs:21 + + LivePollAuthorization class MMCA.ADC.Engagement.Application @@ -9931,7 +10093,7 @@

Full inventory

class MMCA.ADC.Engagement.Application MMCA.ADC.Engagement.Application.LivePolls.UseCases.CastVote - MMCA.ADC.Engagement.Application/LivePolls/UseCases/CastVote/CastVoteHandler.cs:19 + MMCA.ADC.Engagement.Application/LivePolls/UseCases/CastVote/CastVoteHandler.cs:20 CloseLivePollCommand @@ -10001,7 +10163,7 @@

Full inventory

class MMCA.ADC.Engagement.Application MMCA.ADC.Engagement.Application.LivePolls.UseCases.GetOpenPolls - MMCA.ADC.Engagement.Application/LivePolls/UseCases/GetOpenPolls/GetOpenPollsHandler.cs:15 + MMCA.ADC.Engagement.Application/LivePolls/UseCases/GetOpenPolls/GetOpenPollsHandler.cs:21 GetOpenPollsQuery @@ -10085,7 +10247,7 @@

Full inventory

class MMCA.ADC.Engagement.Application MMCA.ADC.Engagement.Application.Points.IntegrationEventHandlers - MMCA.ADC.Engagement.Application/Points/IntegrationEventHandlers/UserDeletedPointsHandler.cs:37 + MMCA.ADC.Engagement.Application/Points/IntegrationEventHandlers/UserDeletedPointsHandler.cs:38 IPointsAwarder @@ -10172,6 +10334,13 @@

Full inventory

MMCA.ADC.Engagement.Application/SessionQuestions/DomainEventHandlers/SessionQuestionUpvoteChangedHandler.cs:39 + UserDeletedSessionQuestionsHandler + class + MMCA.ADC.Engagement.Application + MMCA.ADC.Engagement.Application.SessionQuestions.IntegrationEventHandlers + MMCA.ADC.Engagement.Application/SessionQuestions/IntegrationEventHandlers/UserDeletedSessionQuestionsHandler.cs:22 + + SessionQuestionViewBuilder class MMCA.ADC.Engagement.Application @@ -10197,7 +10366,7 @@

Full inventory

class MMCA.ADC.Engagement.Application MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.GetSessionQuestions - MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/GetSessionQuestions/GetSessionQuestionsHandler.cs:26 + MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/GetSessionQuestions/GetSessionQuestionsHandler.cs:27 GetSessionQuestionsQuery @@ -10260,7 +10429,7 @@

Full inventory

class MMCA.ADC.Engagement.Application MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.ToggleUpvote - MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/ToggleUpvote/ToggleUpvoteHandler.cs:17 + MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/ToggleUpvote/ToggleUpvoteHandler.cs:18 UserSessionBookmarkCacheEvictionHandler @@ -10277,6 +10446,13 @@

Full inventory

MMCA.ADC.Engagement.Application/UserSessionBookmarks/DTOs/UserSessionBookmarkDTOMapper.cs:12 + UserDeletedBookmarksHandler + class + MMCA.ADC.Engagement.Application + MMCA.ADC.Engagement.Application.UserSessionBookmarks.IntegrationEventHandlers + MMCA.ADC.Engagement.Application/UserSessionBookmarks/IntegrationEventHandlers/UserDeletedBookmarksHandler.cs:22 + + BookmarkCountService class MMCA.ADC.Engagement.Application @@ -10326,6 +10502,13 @@

Full inventory

MMCA.ADC.Engagement.Application/UserSessionBookmarks/UseCases/GetUserBookmarks/GetUserBookmarksQuery.cs:8 + UserDeletedBadgeHandlerTests + class + MMCA.ADC.Engagement.Application.Tests + MMCA.ADC.Engagement.Application.Tests.CheckIns.IntegrationEventHandlers + MMCA.ADC.Engagement.Application.Tests/CheckIns/IntegrationEventHandlers/UserDeletedBadgeHandlerTests.cs:14 + + CheckInAttendeeHandlerTests class MMCA.ADC.Engagement.Application.Tests @@ -10435,14 +10618,14 @@

Full inventory

class MMCA.ADC.Engagement.Application.Tests MMCA.ADC.Engagement.Application.Tests.Helpers - MMCA.ADC.Engagement.Application.Tests/Helpers/TestSupport.cs:16 + MMCA.ADC.Engagement.Application.Tests/Helpers/TestSupport.cs:18 TestSupport class MMCA.ADC.Engagement.Application.Tests MMCA.ADC.Engagement.Application.Tests.Helpers - MMCA.ADC.Engagement.Application.Tests/Helpers/TestSupport.cs:44 + MMCA.ADC.Engagement.Application.Tests/Helpers/TestSupport.cs:46 LiveChannelPublishQueueTests @@ -10473,6 +10656,13 @@

Full inventory

MMCA.ADC.Engagement.Application.Tests/LivePolls/DTOs/LivePollDTOMapperTests.cs:9 + UserDeletedVotesHandlerTests + class + MMCA.ADC.Engagement.Application.Tests + MMCA.ADC.Engagement.Application.Tests.LivePolls.IntegrationEventHandlers + MMCA.ADC.Engagement.Application.Tests/LivePolls/IntegrationEventHandlers/UserDeletedVotesHandlerTests.cs:14 + + LivePollOptionNavigationPopulatorTests class MMCA.ADC.Engagement.Application.Tests @@ -10508,6 +10698,13 @@

Full inventory

MMCA.ADC.Engagement.Application.Tests/LivePolls/UseCases/CreateLivePollHandlerTests.cs:14 + DeleteLivePollHandlerTests + class + MMCA.ADC.Engagement.Application.Tests + MMCA.ADC.Engagement.Application.Tests.LivePolls.UseCases + MMCA.ADC.Engagement.Application.Tests/LivePolls/UseCases/DeleteLivePollHandlerTests.cs:18 + + GetEventPollsHandlerTests class MMCA.ADC.Engagement.Application.Tests @@ -10540,7 +10737,7 @@

Full inventory

record MMCA.ADC.Engagement.Application.Tests MMCA.ADC.Engagement.Application.Tests.LivePolls.UseCases - MMCA.ADC.Engagement.Application.Tests/LivePolls/UseCases/CastVoteHandlerTests.cs:169 + MMCA.ADC.Engagement.Application.Tests/LivePolls/UseCases/CastVoteHandlerTests.cs:201 HandlerMocks @@ -10708,7 +10905,7 @@

Full inventory

record MMCA.ADC.Engagement.Application.Tests MMCA.ADC.Engagement.Application.Tests.Points.UseCases - MMCA.ADC.Engagement.Application.Tests/Points/UseCases/SetLeaderboardParticipationHandlerTests.cs:297 + MMCA.ADC.Engagement.Application.Tests/Points/UseCases/SetLeaderboardParticipationHandlerTests.cs:316 SetLeaderboardParticipationHandlerTests @@ -10732,6 +10929,13 @@

Full inventory

MMCA.ADC.Engagement.Application.Tests/SessionQuestions/DomainEventHandlers/SessionQuestionUpvoteChangedHandlerTests.cs:25 + UserDeletedSessionQuestionsHandlerTests + class + MMCA.ADC.Engagement.Application.Tests + MMCA.ADC.Engagement.Application.Tests.SessionQuestions.IntegrationEventHandlers + MMCA.ADC.Engagement.Application.Tests/SessionQuestions/IntegrationEventHandlers/UserDeletedSessionQuestionsHandlerTests.cs:15 + + GetModerationQueueHandlerTests class MMCA.ADC.Engagement.Application.Tests @@ -10771,7 +10975,7 @@

Full inventory

record MMCA.ADC.Engagement.Application.Tests MMCA.ADC.Engagement.Application.Tests.SessionQuestions.UseCases - MMCA.ADC.Engagement.Application.Tests/SessionQuestions/UseCases/ToggleUpvoteHandlerTests.cs:246 + MMCA.ADC.Engagement.Application.Tests/SessionQuestions/UseCases/ToggleUpvoteHandlerTests.cs:277 ModerateQuestionHandlerTests @@ -10823,6 +11027,13 @@

Full inventory

MMCA.ADC.Engagement.Application.Tests/UserSessionBookmarks/DTOs/UserSessionBookmarkDTOMapperTests.cs:7 + UserDeletedBookmarksHandlerTests + class + MMCA.ADC.Engagement.Application.Tests + MMCA.ADC.Engagement.Application.Tests.UserSessionBookmarks.IntegrationEventHandlers + MMCA.ADC.Engagement.Application.Tests/UserSessionBookmarks/IntegrationEventHandlers/UserDeletedBookmarksHandlerTests.cs:14 + + BookmarkCountServiceTests class MMCA.ADC.Engagement.Application.Tests @@ -10890,7 +11101,7 @@

Full inventory

class MMCA.ADC.Engagement.Contracts MMCA.ADC.Engagement.Contracts - MMCA.ADC.Engagement.Contracts/UserEngagementExportServiceGrpcAdapter.cs:18 + MMCA.ADC.Engagement.Contracts/UserEngagementExportServiceGrpcAdapter.cs:19 AssemblyReference @@ -11478,7 +11689,7 @@

Full inventory

class MMCA.ADC.Engagement.Service MMCA.ADC.Engagement.Service.Grpc - MMCA.ADC.Engagement.Service/Grpc/UserEngagementExportGrpcService.cs:23 + MMCA.ADC.Engagement.Service/Grpc/UserEngagementExportGrpcService.cs:24 EngagementFeatures @@ -12276,14 +12487,14 @@

Full inventory

class MMCA.ADC.Engagement.UI MMCA.ADC.Engagement.UI.Services.HappeningNow - MMCA.ADC.Engagement.UI/Services/HappeningNow/SessionReminderCoordinator.cs:20 + MMCA.ADC.Engagement.UI/Services/HappeningNow/SessionReminderCoordinator.cs:25 SessionReminderPlanner class MMCA.ADC.Engagement.UI MMCA.ADC.Engagement.UI.Services.HappeningNow - MMCA.ADC.Engagement.UI/Services/HappeningNow/SessionReminderPlanner.cs:31 + MMCA.ADC.Engagement.UI/Services/HappeningNow/SessionReminderPlanner.cs:39 AttendeeLookupService @@ -12969,7 +13180,7 @@

Full inventory

class MMCA.ADC.Identity.Application MMCA.ADC.Identity.Application.Speakers.IntegrationEventHandlers - MMCA.ADC.Identity.Application/Speakers/IntegrationEventHandlers/SpeakerLinkedToUserHandler.cs:27 + MMCA.ADC.Identity.Application/Speakers/IntegrationEventHandlers/SpeakerLinkedToUserHandler.cs:32 SpeakerUnlinkedFromUserHandler @@ -12990,7 +13201,7 @@

Full inventory

class MMCA.ADC.Identity.Application MMCA.ADC.Identity.Application.Users - MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:48 + MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:50 IExternalLoginEmailVerifier @@ -13235,7 +13446,7 @@

Full inventory

class MMCA.ADC.Identity.Application MMCA.ADC.Identity.Application.Users.UseCases.SetUserAvatar - MMCA.ADC.Identity.Application/Users/UseCases/SetUserAvatar/SetUserAvatarHandler.cs:26 + MMCA.ADC.Identity.Application/Users/UseCases/SetUserAvatar/SetUserAvatarHandler.cs:27 ChangePasswordRequestValidator @@ -13305,14 +13516,14 @@

Full inventory

class MMCA.ADC.Identity.Application.Tests MMCA.ADC.Identity.Application.Tests.Users - MMCA.ADC.Identity.Application.Tests/Users/AuthenticationServiceTests.cs:31 + MMCA.ADC.Identity.Application.Tests/Users/AuthenticationServiceTests.cs:32 ServiceMocks record MMCA.ADC.Identity.Application.Tests MMCA.ADC.Identity.Application.Tests.Users - MMCA.ADC.Identity.Application.Tests/Users/AuthenticationServiceTests.cs:892 + MMCA.ADC.Identity.Application.Tests/Users/AuthenticationServiceTests.cs:930 SoftDeletedUserValidatorTests @@ -13466,7 +13677,7 @@

Full inventory

class MMCA.ADC.Identity.Application.Tests MMCA.ADC.Identity.Application.Tests.Users.UseCases.SetUserAvatar - MMCA.ADC.Identity.Application.Tests/Users/UseCases/SetUserAvatar/SetUserAvatarHandlerTests.cs:20 + MMCA.ADC.Identity.Application.Tests/Users/UseCases/SetUserAvatar/SetUserAvatarHandlerTests.cs:21 ChangePasswordRequestValidatorTests @@ -13669,7 +13880,7 @@

Full inventory

class MMCA.ADC.Identity.Infrastructure.Tests MMCA.ADC.Identity.Infrastructure.Tests.Persistence - MMCA.ADC.Identity.Infrastructure.Tests/Persistence/IdentityEntityConfigurationTests.cs:209 + MMCA.ADC.Identity.Infrastructure.Tests/Persistence/IdentityEntityConfigurationTests.cs:223 RefreshSessionModelGateTests @@ -13980,6 +14191,13 @@

Full inventory

MMCA.ADC.Identity.Shared/Authorization/IdentityPermissions.cs:10 + JwtAudience + class + MMCA.ADC.Identity.Shared + MMCA.ADC.Identity.Shared.Authorization + MMCA.ADC.Identity.Shared/Authorization/JwtAudience.cs:14 + + RoleNames class MMCA.ADC.Identity.Shared @@ -14106,6 +14324,13 @@

Full inventory

MMCA.ADC.Identity.Shared.Tests/Authorization/IdentityPermissionGrantsTests.cs:14 + JwtAudienceTests + class + MMCA.ADC.Identity.Shared.Tests + MMCA.ADC.Identity.Shared.Tests.Authorization + MMCA.ADC.Identity.Shared.Tests/Authorization/JwtAudienceTests.cs:11 + + DisabledAttendeeQueryServiceTests class MMCA.ADC.Identity.Shared.Tests @@ -14292,7 +14517,7 @@

Full inventory

class MMCA.ADC.Notification.Application MMCA.ADC.Notification.Application - MMCA.ADC.Notification.Application/DependencyInjection.cs:12 + MMCA.ADC.Notification.Application/DependencyInjection.cs:13 UserNotificationExportService @@ -14418,7 +14643,7 @@

Full inventory

class MMCA.ADC.Notification.Service MMCA.ADC.Notification.Service.Grpc - MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:22 + MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:31 UserNotificationExportGrpcService @@ -14484,6 +14709,13 @@

Full inventory

MMCA.ADC.ServiceBusEmulator.IntegrationTests/Infrastructure/ServiceBusEmulatorFixture.cs:22 + AttendeesGrpcServiceTests + class + MMCA.ADC.Services.Tests + MMCA.ADC.Services.Tests.Attendees + MMCA.ADC.Services.Tests/Attendees/AttendeesGrpcServiceTests.cs:23 + + TokenPermissionGrantsTests class MMCA.ADC.Services.Tests @@ -14491,6 +14723,27 @@

Full inventory

MMCA.ADC.Services.Tests/Authorization/TokenPermissionGrantsTests.cs:33 + BookmarkCountsGrpcServiceTests + class + MMCA.ADC.Services.Tests + MMCA.ADC.Services.Tests.Bookmarks + MMCA.ADC.Services.Tests/Bookmarks/BookmarkCountsGrpcServiceTests.cs:17 + + + SessionBookmarksGrpcServiceTests + class + MMCA.ADC.Services.Tests + MMCA.ADC.Services.Tests.Bookmarks + MMCA.ADC.Services.Tests/Bookmarks/SessionBookmarksGrpcServiceTests.cs:18 + + + SessionBookmarkValidationServiceGrpcAdapterTests + class + MMCA.ADC.Services.Tests + MMCA.ADC.Services.Tests.Bookmarks + MMCA.ADC.Services.Tests/Bookmarks/SessionBookmarkValidationServiceGrpcAdapterTests.cs:23 + + UserEngagementExportGrpcServiceTests class MMCA.ADC.Services.Tests @@ -14526,6 +14779,34 @@

Full inventory

MMCA.ADC.Services.Tests/Helpers/FakeServerCallContext.cs:10 + GrpcCalls + class + MMCA.ADC.Services.Tests + MMCA.ADC.Services.Tests.Helpers + MMCA.ADC.Services.Tests/Helpers/GrpcCalls.cs:9 + + + EventLiveValidationGrpcServiceTests + class + MMCA.ADC.Services.Tests + MMCA.ADC.Services.Tests.Live + MMCA.ADC.Services.Tests/Live/EventLiveValidationGrpcServiceTests.cs:23 + + + EventLiveValidationServiceGrpcAdapterTests + class + MMCA.ADC.Services.Tests + MMCA.ADC.Services.Tests.Live + MMCA.ADC.Services.Tests/Live/EventLiveValidationServiceGrpcAdapterTests.cs:24 + + + LiveChannelGrpcServiceTests + class + MMCA.ADC.Services.Tests + MMCA.ADC.Services.Tests.Live + MMCA.ADC.Services.Tests/Live/LiveChannelGrpcServiceTests.cs:27 + + LiveChannelJoinAuthorizerTests class MMCA.ADC.Services.Tests @@ -14533,6 +14814,20 @@

Full inventory

MMCA.ADC.Services.Tests/Live/LiveChannelJoinAuthorizerTests.cs:18 + LiveChannelPublisherGrpcAdapterTests + class + MMCA.ADC.Services.Tests + MMCA.ADC.Services.Tests.Live + MMCA.ADC.Services.Tests/Live/LiveChannelPublisherGrpcAdapterTests.cs:16 + + + SelfHttpOutputCacheWarmupTaskTests + class + MMCA.ADC.Services.Tests + MMCA.ADC.Services.Tests.Warmup + MMCA.ADC.Services.Tests/Warmup/SelfHttpOutputCacheWarmupTaskTests.cs:15 + + App class MMCA.ADC.UI @@ -14775,7 +15070,7 @@

Full inventory

interface MMCA.Common.AI MMCA.Common.AI.Chat - MMCA.Common.AI/Chat/IChatGuardrail.cs:20 + MMCA.Common.AI/Chat/IChatGuardrail.cs:21 PromptTaggingChatClient @@ -14796,7 +15091,7 @@

Full inventory

class MMCA.Common.AI MMCA.Common.AI.Guardrails - MMCA.Common.AI/Guardrails/ChatToolPolicy.cs:23 + MMCA.Common.AI/Guardrails/ChatToolPolicy.cs:24 ContentPolicyGuardrail @@ -15027,14 +15322,14 @@

Full inventory

class MMCA.Common.AI.Tests MMCA.Common.AI.Tests.Evaluation - MMCA.Common.AI.Tests/Evaluation/ReplayChatClientTests.cs:124 + MMCA.Common.AI.Tests/Evaluation/ReplayChatClientTests.cs:145 MissingRecordingHarness class MMCA.Common.AI.Tests MMCA.Common.AI.Tests.Evaluation - MMCA.Common.AI.Tests/Evaluation/ReplayChatClientTests.cs:135 + MMCA.Common.AI.Tests/Evaluation/ReplayChatClientTests.cs:156 PinHarness @@ -15146,7 +15441,7 @@

Full inventory

class MMCA.Common.AI.Tests MMCA.Common.AI.Tests.Guardrails - MMCA.Common.AI.Tests/Guardrails/RequestRedactionTests.cs:163 + MMCA.Common.AI.Tests/Guardrails/RequestRedactionTests.cs:199 RequestRedactionTests @@ -15167,21 +15462,21 @@

Full inventory

class MMCA.Common.AI.Tests MMCA.Common.AI.Tests.Guardrails - MMCA.Common.AI.Tests/Guardrails/RequestRedactionTests.cs:157 + MMCA.Common.AI.Tests/Guardrails/RequestRedactionTests.cs:193 ToolPolicyTests class MMCA.Common.AI.Tests MMCA.Common.AI.Tests.Guardrails - MMCA.Common.AI.Tests/Guardrails/ToolPolicyTests.cs:15 + MMCA.Common.AI.Tests/Guardrails/ToolPolicyTests.cs:16 UppercaseRedactor class MMCA.Common.AI.Tests MMCA.Common.AI.Tests.Guardrails - MMCA.Common.AI.Tests/Guardrails/RequestRedactionTests.cs:151 + MMCA.Common.AI.Tests/Guardrails/RequestRedactionTests.cs:187 AdapterFactoryTests @@ -15272,7 +15567,7 @@

Full inventory

class MMCA.Common.API MMCA.Common.API.Authorization - MMCA.Common.API/Authorization/OwnershipHelper.cs:10 + MMCA.Common.API/Authorization/OwnershipHelper.cs:11 PermissionAuthorizationHandler @@ -15496,7 +15791,7 @@

Full inventory

class MMCA.Common.API MMCA.Common.API.Controllers.Privacy - MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:59 + MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:61 CsvWriter @@ -15531,7 +15826,7 @@

Full inventory

class MMCA.Common.API MMCA.Common.API.Idempotency - MMCA.Common.API/Idempotency/IdempotencyFilter.cs:67 + MMCA.Common.API/Idempotency/IdempotencyFilter.cs:68 IdempotencyMetrics @@ -15545,7 +15840,7 @@

Full inventory

record MMCA.Common.API MMCA.Common.API.Idempotency - MMCA.Common.API/Idempotency/IdempotencyRecord.cs:14 + MMCA.Common.API/Idempotency/IdempotencyRecord.cs:21 IdempotencySettings @@ -15601,7 +15896,7 @@

Full inventory

class MMCA.Common.API MMCA.Common.API.Middleware - MMCA.Common.API/Middleware/CorrelationIdMiddleware.cs:15 + MMCA.Common.API/Middleware/CorrelationIdMiddleware.cs:18 DbUpdateExceptionHandler @@ -15636,7 +15931,7 @@

Full inventory

class MMCA.Common.API MMCA.Common.API.Middleware - MMCA.Common.API/Middleware/OperationCanceledExceptionHandler.cs:16 + MMCA.Common.API/Middleware/OperationCanceledExceptionHandler.cs:18 SoftDeletedUserMiddleware @@ -15741,7 +16036,7 @@

Full inventory

class MMCA.Common.API MMCA.Common.API.SessionCookies - MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:52 + MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:74 CookieSessionRefreshMiddleware @@ -15769,7 +16064,21 @@

Full inventory

interface MMCA.Common.API MMCA.Common.API.SessionCookies - MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:30 + MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:32 + + + ISessionCookieStore + interface + MMCA.Common.API + MMCA.Common.API.SessionCookies + MMCA.Common.API/SessionCookies/ISessionCookieStore.cs:14 + + + SessionClaimsToken + class + MMCA.Common.API + MMCA.Common.API.SessionCookies + MMCA.Common.API/SessionCookies/SessionClaimsToken.cs:14 SessionCookieAuthenticationExtensions @@ -15790,7 +16099,7 @@

Full inventory

class MMCA.Common.API MMCA.Common.API.SessionCookies - MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:15 + MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:16 SessionCookieJar @@ -15804,21 +16113,49 @@

Full inventory

record MMCA.Common.API MMCA.Common.API.SessionCookies - MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:77 + MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:92 + + + SessionCookieSettings + class + MMCA.Common.API + MMCA.Common.API.SessionCookies + MMCA.Common.API/SessionCookies/SessionCookieSettings.cs:11 + + + SessionCookieStore + class + MMCA.Common.API + MMCA.Common.API.SessionCookies + MMCA.Common.API/SessionCookies/ISessionCookieStore.cs:28 + + + SessionRefreshOutcome + class + MMCA.Common.API + MMCA.Common.API.SessionCookies + MMCA.Common.API/SessionCookies/SessionRefreshOutcome.cs:32 + + + SessionRefreshStatus + enum + MMCA.Common.API + MMCA.Common.API.SessionCookies + MMCA.Common.API/SessionCookies/SessionRefreshOutcome.cs:4 SessionTokenResponse record MMCA.Common.API MMCA.Common.API.SessionCookies - MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:21 + MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:23 SessionTokenResult record struct MMCA.Common.API MMCA.Common.API.SessionCookies - MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:15 + MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:17 CommonForwardedHeaders @@ -15839,7 +16176,7 @@

Full inventory

class MMCA.Common.API MMCA.Common.API.Startup - MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:20 + MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:28 MiniProfilerExtensions @@ -15944,7 +16281,7 @@

Full inventory

class MMCA.Common.API MMCA.Common.API.Startup.Endpoints - MMCA.Common.API/Startup/Endpoints/OpenApiEndpointExtensions.cs:22 + MMCA.Common.API/Startup/Endpoints/OpenApiEndpointExtensions.cs:25 MiddlewarePipelineBuilder @@ -16017,6 +16354,13 @@

Full inventory

MMCA.Common.API.Tests/Authorization/OwnerOrAdminFilterTests.cs:15 + OwnershipHelperGateTests + class + MMCA.Common.API.Tests + MMCA.Common.API.Tests.Authorization + MMCA.Common.API.Tests/Authorization/OwnershipHelperGateTests.cs:18 + + OwnershipHelperTests class MMCA.Common.API.Tests @@ -16105,14 +16449,14 @@

Full inventory

class MMCA.Common.API.Tests MMCA.Common.API.Tests.Controllers - MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:343 + MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:345 BothHooksReadController class MMCA.Common.API.Tests MMCA.Common.API.Tests.Controllers - MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:381 + MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:383 CrudEntityControllerBaseTests @@ -16122,6 +16466,13 @@

Full inventory

MMCA.Common.API.Tests/Controllers/CrudEntityControllerBaseTests.cs:20 + DefaultExportTestController + class + MMCA.Common.API.Tests + MMCA.Common.API.Tests.Controllers + MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:538 + + EntityControllerBaseETagTests class MMCA.Common.API.Tests @@ -16133,7 +16484,7 @@

Full inventory

class MMCA.Common.API.Tests MMCA.Common.API.Tests.Controllers - MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:652 + MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:699 EntityControllerBaseExportTests @@ -16161,42 +16512,42 @@

Full inventory

record MMCA.Common.API.Tests MMCA.Common.API.Tests.Controllers - MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:641 + MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:688 ExportShapeTestController class MMCA.Common.API.Tests MMCA.Common.API.Tests.Controllers - MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:603 + MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:647 ExportShapeTestDTO record MMCA.Common.API.Tests MMCA.Common.API.Tests.Controllers - MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:625 + MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:672 ExportTestController class MMCA.Common.API.Tests MMCA.Common.API.Tests.Controllers - MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:494 + MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:529 ExportTestDTO record MMCA.Common.API.Tests MMCA.Common.API.Tests.Controllers - MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:610 + MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:657 ExportTestEntity class MMCA.Common.API.Tests MMCA.Common.API.Tests.Controllers - MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:608 + MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:655 PlainDTO @@ -16231,21 +16582,21 @@

Full inventory

record MMCA.Common.API.Tests MMCA.Common.API.Tests.Controllers - MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:316 + MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:318 ReadScopeEntity class MMCA.Common.API.Tests MMCA.Common.API.Tests.Controllers - MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:313 + MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:315 RecordingQueryService class MMCA.Common.API.Tests MMCA.Common.API.Tests.Controllers - MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:400 + MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:402 RoundTripController @@ -16259,21 +16610,21 @@

Full inventory

class MMCA.Common.API.Tests MMCA.Common.API.Tests.Controllers - MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:503 + MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:547 SpecificationHonoringQueryService class MMCA.Common.API.Tests MMCA.Common.API.Tests.Controllers - MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:517 + MMCA.Common.API.Tests/Controllers/EntityControllerBaseExportTests.cs:561 SyncScopedReadController class MMCA.Common.API.Tests MMCA.Common.API.Tests.Controllers - MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:370 + MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:372 TestAggDTO @@ -16357,7 +16708,7 @@

Full inventory

class MMCA.Common.API.Tests MMCA.Common.API.Tests.Controllers - MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:329 + MMCA.Common.API.Tests/Controllers/EntityControllerBaseReadSpecificationTests.cs:331 VersionedDTO @@ -16455,14 +16806,14 @@

Full inventory

class MMCA.Common.API.Tests MMCA.Common.API.Tests.Controllers.Auth - MMCA.Common.API.Tests/Controllers/Auth/OAuthControllerBaseTests.cs:643 + MMCA.Common.API.Tests/Controllers/Auth/OAuthControllerBaseTests.cs:664 TestAuthController class MMCA.Common.API.Tests MMCA.Common.API.Tests.Controllers.Auth - MMCA.Common.API.Tests/Controllers/Auth/AuthControllerBaseTests.cs:339 + MMCA.Common.API.Tests/Controllers/Auth/AuthControllerBaseTests.cs:350 TestChangePasswordCommand @@ -16490,7 +16841,7 @@

Full inventory

class MMCA.Common.API.Tests MMCA.Common.API.Tests.Controllers.Auth - MMCA.Common.API.Tests/Controllers/Auth/OAuthControllerBaseTests.cs:653 + MMCA.Common.API.Tests/Controllers/Auth/OAuthControllerBaseTests.cs:674 TestPasswordResetController @@ -16584,6 +16935,20 @@

Full inventory

MMCA.Common.API.Tests/Export/CsvWriterTests.cs:8 + EntityCsvExporterTests + class + MMCA.Common.API.Tests + MMCA.Common.API.Tests.Export + MMCA.Common.API.Tests/Export/EntityCsvExporterTests.cs:13 + + + ExportRow + record + MMCA.Common.API.Tests + MMCA.Common.API.Tests.Export + MMCA.Common.API.Tests/Export/EntityCsvExporterTests.cs:55 + + CurrentUserTargetingContextAccessorTests class MMCA.Common.API.Tests @@ -16623,42 +16988,56 @@

Full inventory

class MMCA.Common.API.Tests MMCA.Common.API.Tests.Idempotency - MMCA.Common.API.Tests/Idempotency/IdempotencyFilterTests.cs:954 + MMCA.Common.API.Tests/Idempotency/IdempotencyFilterTests.cs:1065 TrackingHandle class MMCA.Common.API.Tests MMCA.Common.API.Tests.Idempotency - MMCA.Common.API.Tests/Idempotency/IdempotencyFilterTests.cs:992 + MMCA.Common.API.Tests/Idempotency/IdempotencyFilterTests.cs:1103 + + + Wrapped + record + MMCA.Common.API.Tests + MMCA.Common.API.Tests.Idempotency + MMCA.Common.API.Tests/Idempotency/IdempotencyFilterTests.cs:657 + + + WrappedAsStringConverter + class + MMCA.Common.API.Tests + MMCA.Common.API.Tests.Idempotency + MMCA.Common.API.Tests/Idempotency/IdempotencyFilterTests.cs:659 OrderProbe class MMCA.Common.API.Tests MMCA.Common.API.Tests.Identifiers - MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:208 + MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:222 ProbeControllerFeatureProvider class MMCA.Common.API.Tests MMCA.Common.API.Tests.Identifiers - MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:186 + MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:200 ProbeOrderId record struct MMCA.Common.API.Tests MMCA.Common.API.Tests.Identifiers - MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:194 + MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:208 ProbeSkuId record struct MMCA.Common.API.Tests MMCA.Common.API.Tests.Identifiers - MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:201 + MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:215 StronglyTypedIdApiTests @@ -16672,7 +17051,7 @@

Full inventory

class MMCA.Common.API.Tests MMCA.Common.API.Tests.Identifiers - MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:221 + MMCA.Common.API.Tests/Identifiers/StronglyTypedIdApiTests.cs:235 CurrencyJsonConverterTests @@ -16717,6 +17096,13 @@

Full inventory

MMCA.Common.API.Tests/Middleware/CorrelationIdMiddlewareTests.cs:10 + ErrorHttpMappingTests + class + MMCA.Common.API.Tests + MMCA.Common.API.Tests.Middleware + MMCA.Common.API.Tests/Middleware/ErrorHttpMappingTests.cs:12 + + ExceptionHandlerTests class MMCA.Common.API.Tests @@ -16724,6 +17110,13 @@

Full inventory

MMCA.Common.API.Tests/Middleware/ExceptionHandlerTests.cs:14 + OperationCanceledExceptionHandlerTests + class + MMCA.Common.API.Tests + MMCA.Common.API.Tests.Middleware + MMCA.Common.API.Tests/Middleware/OperationCanceledExceptionHandlerTests.cs:14 + + SoftDeletedUserMiddlewareTests class MMCA.Common.API.Tests @@ -16742,7 +17135,7 @@

Full inventory

class MMCA.Common.API.Tests MMCA.Common.API.Tests.Middleware - MMCA.Common.API.Tests/Middleware/ExceptionHandlerTests.cs:411 + MMCA.Common.API.Tests/Middleware/ExceptionHandlerTests.cs:414 UnhandledResultFailureFilterTests @@ -16759,6 +17152,13 @@

Full inventory

MMCA.Common.API.Tests/ModelBinders/QueryFilterModelBinderTests.cs:9 + AddCommonOpenApiHostRegistrationTests + class + MMCA.Common.API.Tests + MMCA.Common.API.Tests.OpenApi + MMCA.Common.API.Tests/OpenApi/AddCommonOpenApiHostRegistrationTests.cs:36 + + ApiParameterDescriptorBackfillProviderTests class MMCA.Common.API.Tests @@ -16766,6 +17166,27 @@

Full inventory

MMCA.Common.API.Tests/OpenApi/ApiParameterDescriptorBackfillProviderTests.cs:31 + HostRegistrationProbeController + class + MMCA.Common.API.Tests + MMCA.Common.API.Tests.OpenApi + MMCA.Common.API.Tests/OpenApi/AddCommonOpenApiHostRegistrationTests.cs:163 + + + HostRegistrationProbeFeatureProvider + class + MMCA.Common.API.Tests + MMCA.Common.API.Tests.OpenApi + MMCA.Common.API.Tests/OpenApi/AddCommonOpenApiHostRegistrationTests.cs:150 + + + MapCommonOpenApiAuthorizationTests + class + MMCA.Common.API.Tests + MMCA.Common.API.Tests.OpenApi + MMCA.Common.API.Tests/OpenApi/MapCommonOpenApiAuthorizationTests.cs:23 + + OpenApiBaselineTests class MMCA.Common.API.Tests @@ -16784,7 +17205,7 @@

Full inventory

class MMCA.Common.API.Tests MMCA.Common.API.Tests.OpenApi - MMCA.Common.API.Tests/OpenApi/OpenApiProbeHost.cs:66 + MMCA.Common.API.Tests/OpenApi/OpenApiProbeHost.cs:67 ProblemDetailsProbeController @@ -16812,7 +17233,7 @@

Full inventory

class MMCA.Common.API.Tests MMCA.Common.API.Tests.RateLimiting - MMCA.Common.API.Tests/RateLimiting/RateLimitingSettingsTests.cs:13 + MMCA.Common.API.Tests/RateLimiting/RateLimitingSettingsTests.cs:16 RedisFixedWindowRateLimiterTests @@ -16822,11 +17243,18 @@

Full inventory

MMCA.Common.API.Tests/RateLimiting/RedisFixedWindowRateLimiterTests.cs:16 + ClaimsOnlySessionCookieEndpointsTests + class + MMCA.Common.API.Tests + MMCA.Common.API.Tests.SessionCookies + MMCA.Common.API.Tests/SessionCookies/ClaimsOnlySessionCookieEndpointsTests.cs:24 + + CookieSessionRefresherTests class MMCA.Common.API.Tests MMCA.Common.API.Tests.SessionCookies - MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs:24 + MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs:25 CookieSessionRefreshMiddlewareTests @@ -16854,7 +17282,14 @@

Full inventory

class MMCA.Common.API.Tests MMCA.Common.API.Tests.SessionCookies - MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs:335 + MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs:563 + + + SessionClaimsTokenTests + class + MMCA.Common.API.Tests + MMCA.Common.API.Tests.SessionCookies + MMCA.Common.API.Tests/SessionCookies/SessionClaimsTokenTests.cs:15 SessionCookieAuthenticationHandlerTests @@ -16882,14 +17317,21 @@

Full inventory

class MMCA.Common.API.Tests MMCA.Common.API.Tests.SessionCookies - MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs:364 + MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs:594 StubHttpMessageHandler class MMCA.Common.API.Tests MMCA.Common.API.Tests.SessionCookies - MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs:370 + MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs:600 + + + StubRefresher + class + MMCA.Common.API.Tests + MMCA.Common.API.Tests.SessionCookies + MMCA.Common.API.Tests/SessionCookies/ClaimsOnlySessionCookieEndpointsTests.cs:94 StubRefresher @@ -16917,7 +17359,14 @@

Full inventory

class MMCA.Common.API.Tests MMCA.Common.API.Tests.Startup - MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:33 + MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:34 + + + DesignTimeDatabaseInitializationTests + class + MMCA.Common.API.Tests + MMCA.Common.API.Tests.Startup + MMCA.Common.API.Tests/Startup/DesignTimeDatabaseInitializationTests.cs:23 EndpointFeatureStub @@ -16938,35 +17387,35 @@

Full inventory

class MMCA.Common.API.Tests MMCA.Common.API.Tests.Startup - MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:259 + MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:309 InitTestMigratedWidget class MMCA.Common.API.Tests MMCA.Common.API.Tests.Startup - MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:273 + MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:323 InitTestMigratedWidgetConfiguration class MMCA.Common.API.Tests MMCA.Common.API.Tests.Startup - MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:279 + MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:329 InitTestWidget class MMCA.Common.API.Tests MMCA.Common.API.Tests.Startup - MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:265 + MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:315 InitTestWidgetConfiguration class MMCA.Common.API.Tests MMCA.Common.API.Tests.Startup - MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:271 + MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:321 ModuleHostExtensionsTests @@ -17120,7 +17569,7 @@

Full inventory

class MMCA.Common.Application MMCA.Common.Application.Auth - MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:74 + MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:63 AuthenticationValidators @@ -17158,13 +17607,6 @@

Full inventory

MMCA.Common.Application/Auth/IRefreshSessionStore.cs:21 - IssuedSession - record - MMCA.Common.Application - MMCA.Common.Application.Auth - MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:924 - - PasswordResetSettings class MMCA.Common.Application @@ -17186,13 +17628,6 @@

Full inventory

MMCA.Common.Application/Auth/RefreshSessionSettings.cs:9 - SessionStampingTokenService - class - MMCA.Common.Application - MMCA.Common.Application.Auth - MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:936 - - SoftDeletedUserCache class MMCA.Common.Application @@ -17284,6 +17719,34 @@

Full inventory

MMCA.Common.Application/Auth/Permissions/PermissionGrantSettings.cs:9 + AuthSessionIssuer + class + MMCA.Common.Application + MMCA.Common.Application.Auth.Sessions + MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:39 + + + IAuthSessionIssuer + interface + MMCA.Common.Application + MMCA.Common.Application.Auth.Sessions + MMCA.Common.Application/Auth/Sessions/IAuthSessionIssuer.cs:26 + + + IssuedSession + record + MMCA.Common.Application + MMCA.Common.Application.Auth.Sessions + MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:436 + + + SessionStampingTokenService + class + MMCA.Common.Application + MMCA.Common.Application.Auth.Sessions + MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:448 + + ITwoFactorAuthenticator interface MMCA.Common.Application @@ -17309,7 +17772,7 @@

Full inventory

record MMCA.Common.Application MMCA.Common.Application.Auth.TwoFactor - MMCA.Common.Application/Auth/TwoFactor/ITwoFactorService.cs:84 + MMCA.Common.Application/Auth/TwoFactor/ITwoFactorService.cs:97 TwoFactorErrors @@ -17435,7 +17898,7 @@

Full inventory

class MMCA.Common.Application MMCA.Common.Application.Interfaces - MMCA.Common.Application/Interfaces/ICacheService.cs:142 + MMCA.Common.Application/Interfaces/ICacheService.cs:189 IAuditTrailReader @@ -17680,21 +18143,21 @@

Full inventory

interface MMCA.Common.Application MMCA.Common.Application.Interfaces.Infrastructure.Persistence - MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRawSqlQueryExecutor.cs:23 + MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRawSqlQueryExecutor.cs:24 IReadRepository<TEntity, TIdentifierType> interface MMCA.Common.Application MMCA.Common.Application.Interfaces.Infrastructure.Persistence - MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:330 + MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:332 IRepository<TEntity, TIdentifierType> interface MMCA.Common.Application MMCA.Common.Application.Interfaces.Infrastructure.Persistence - MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:493 + MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:495 IUniqueConstraintViolationDetector @@ -17722,7 +18185,7 @@

Full inventory

interface MMCA.Common.Application MMCA.Common.Application.Interfaces.Infrastructure.Persistence - MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:367 + MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:369 OutboxDeadLetter @@ -17932,7 +18395,7 @@

Full inventory

class MMCA.Common.Application MMCA.Common.Application.Modules - MMCA.Common.Application/Modules/ModuleLoader.cs:15 + MMCA.Common.Application/Modules/ModuleLoader.cs:16 DependencyInjection @@ -17988,7 +18451,7 @@

Full inventory

class MMCA.Common.Application MMCA.Common.Application.Notifications.PushNotifications.UseCases.Send - MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:27 + MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:32 SendPushNotificationRequestValidator @@ -18037,7 +18500,7 @@

Full inventory

class MMCA.Common.Application MMCA.Common.Application.Notifications.UserNotifications.UseCases.MarkAllRead - MMCA.Common.Application/Notifications/UserNotifications/UseCases/MarkAllRead/MarkAllNotificationsReadHandler.cs:12 + MMCA.Common.Application/Notifications/UserNotifications/UseCases/MarkAllRead/MarkAllNotificationsReadHandler.cs:18 MarkNotificationReadCommand @@ -18639,7 +19102,7 @@

Full inventory

class MMCA.Common.Application MMCA.Common.Application.UseCases.Decorators - MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:20 + MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:22 UserCacheKey @@ -18754,18 +19217,11 @@

Full inventory

MMCA.Common.Application/Users/UserOwnershipRule.cs:21 - UserUseCaseLog - class - MMCA.Common.Application - MMCA.Common.Application.Users - MMCA.Common.Application/Users/UserUseCaseLog.cs:11 - - ChangePasswordHandlerBase<TUser, TCommand> class MMCA.Common.Application MMCA.Common.Application.Users.UseCases.ChangePassword - MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:34 + MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:42 ChangePreferencesHandlerBase<TUser, TCommand> @@ -18779,7 +19235,7 @@

Full inventory

class MMCA.Common.Application MMCA.Common.Application.Users.UseCases.DeleteUser - MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:58 + MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:62 ConfirmEmailHandlerBase<TUser, TCommand> @@ -18884,7 +19340,7 @@

Full inventory

class MMCA.Common.Application MMCA.Common.Application.Validation - MMCA.Common.Application/Validation/CommonValidationRules.cs:85 + MMCA.Common.Application/Validation/CommonValidationRules.cs:86 AddressLine1Rules<T> @@ -18933,70 +19389,70 @@

Full inventory

class MMCA.Common.Application MMCA.Common.Application.Validation - MMCA.Common.Application/Validation/CommonValidationRules.cs:64 + MMCA.Common.Application/Validation/CommonValidationRules.cs:65 NonNegativeIntRules<T> class MMCA.Common.Application MMCA.Common.Application.Validation - MMCA.Common.Application/Validation/CommonValidationRules.cs:122 + MMCA.Common.Application/Validation/CommonValidationRules.cs:123 OptionalErrorCodeExtensions class MMCA.Common.Application MMCA.Common.Application.Validation - MMCA.Common.Application/Validation/CommonValidationRules.cs:19 + MMCA.Common.Application/Validation/CommonValidationRules.cs:20 OptionalPositiveIdRules<T, TId> class MMCA.Common.Application MMCA.Common.Application.Validation - MMCA.Common.Application/Validation/CommonValidationRules.cs:161 + MMCA.Common.Application/Validation/CommonValidationRules.cs:162 OptionalStringRules<T> class MMCA.Common.Application MMCA.Common.Application.Validation - MMCA.Common.Application/Validation/CommonValidationRules.cs:53 + MMCA.Common.Application/Validation/CommonValidationRules.cs:54 PasswordRules<T> class MMCA.Common.Application MMCA.Common.Application.Validation - MMCA.Common.Application/Validation/CommonValidationRules.cs:174 + MMCA.Common.Application/Validation/CommonValidationRules.cs:175 PositiveDecimalRules<T> class MMCA.Common.Application MMCA.Common.Application.Validation - MMCA.Common.Application/Validation/CommonValidationRules.cs:111 + MMCA.Common.Application/Validation/CommonValidationRules.cs:112 PositiveIntRules<T> class MMCA.Common.Application MMCA.Common.Application.Validation - MMCA.Common.Application/Validation/CommonValidationRules.cs:100 + MMCA.Common.Application/Validation/CommonValidationRules.cs:101 RequiredIdRules<T, TId> class MMCA.Common.Application MMCA.Common.Application.Validation - MMCA.Common.Application/Validation/CommonValidationRules.cs:142 + MMCA.Common.Application/Validation/CommonValidationRules.cs:143 RequiredStringRules<T> class MMCA.Common.Application MMCA.Common.Application.Validation - MMCA.Common.Application/Validation/CommonValidationRules.cs:41 + MMCA.Common.Application/Validation/CommonValidationRules.cs:42 StateRules<T> @@ -19010,7 +19466,7 @@

Full inventory

class MMCA.Common.Application MMCA.Common.Application.Validation - MMCA.Common.Application/Validation/CommonValidationRules.cs:188 + MMCA.Common.Application/Validation/CommonValidationRules.cs:191 ZipCodeRules<T> @@ -19255,14 +19711,14 @@

Full inventory

class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Auth - MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:28 + MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:29 AuthenticationServiceIdentityCompletionsTests class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Auth - MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:33 + MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:34 AuthenticationValidatorsTests @@ -19276,14 +19732,14 @@

Full inventory

class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Auth - MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:327 + MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:355 ConfirmableAuthUser class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Auth - MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:306 + MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:334 FakeGrantCache @@ -19297,28 +19753,28 @@

Full inventory

class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Auth - MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:299 + MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:327 FixedTimeProvider class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Auth - MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:936 + MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:937 FixedTimeProvider class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Auth - MMCA.Common.Application.Tests/Auth/RefreshSessionManagementTests.cs:420 + MMCA.Common.Application.Tests/Auth/RefreshSessionManagementTests.cs:421 Harness class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Auth - MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:202 + MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:229 LayeredPermissionRegistryTests @@ -19332,28 +19788,28 @@

Full inventory

class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Auth - MMCA.Common.Application.Tests/Auth/RefreshSessionManagementTests.cs:30 + MMCA.Common.Application.Tests/Auth/RefreshSessionManagementTests.cs:31 ServiceMocks record MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Auth - MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:868 + MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:869 ServiceMocks record MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Auth - MMCA.Common.Application.Tests/Auth/RefreshSessionManagementTests.cs:293 + MMCA.Common.Application.Tests/Auth/RefreshSessionManagementTests.cs:294 SessionAwareAuthenticationService class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Auth - MMCA.Common.Application.Tests/Auth/RefreshSessionManagementTests.cs:430 + MMCA.Common.Application.Tests/Auth/RefreshSessionManagementTests.cs:431 SoftDeletedUserCacheTests @@ -19367,21 +19823,21 @@

Full inventory

class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Auth - MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:954 + MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:955 TestAuthUser class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Auth - MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:946 + MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:947 TwoFactorStub class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Auth - MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:366 + MMCA.Common.Application.Tests/Auth/AuthenticationServiceIdentityCompletionsTests.cs:391 ForgotPasswordRequestValidatorTests @@ -20186,105 +20642,112 @@

Full inventory

class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Modules - MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:344 + MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:417 FakeCycleModuleOne class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Modules - MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:358 + MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:431 FakeCycleModuleTwo class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Modules - MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:370 + MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:443 FakeModuleAlpha class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Modules - MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:298 + MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:371 FakeModuleAlphaSeeder class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Modules - MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:307 + MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:380 FakeModuleBravo class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Modules - MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:288 + MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:361 FakeModuleCharlie class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Modules - MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:278 + MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:351 FakeModuleTracker class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Modules - MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:260 + MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:333 FakeRemoteContractRealAdapter class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Modules - MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:274 + MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:347 FakeRemoteContractStub class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Modules - MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:271 + MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:344 FakeStrictModule class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Modules - MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:319 + MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:392 FakeStubbedModule class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Modules - MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:332 + MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:405 IFakeRemoteContract interface MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Modules - MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:268 + MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:341 ModuleLoaderTests class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Modules - MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:19 + MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:20 + + + ScanFailingAssembly + class + MMCA.Common.Application.Tests + MMCA.Common.Application.Tests.Modules + MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:325 FixedTimeProvider class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Notifications - MMCA.Common.Application.Tests/Notifications/MarkAllNotificationsReadHandlerTests.cs:161 + MMCA.Common.Application.Tests/Notifications/MarkAllNotificationsReadHandlerTests.cs:135 FixedTimeProvider @@ -20326,28 +20789,28 @@

Full inventory

record MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Notifications - MMCA.Common.Application.Tests/Notifications/MarkAllNotificationsReadHandlerTests.cs:166 + MMCA.Common.Application.Tests/Notifications/MarkNotificationReadHandlerTests.cs:72 HandlerMocks record MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Notifications - MMCA.Common.Application.Tests/Notifications/MarkNotificationReadHandlerTests.cs:72 + MMCA.Common.Application.Tests/Notifications/SendPushNotificationHandlerTests.cs:366 - HandlerMocks - record + Harness + class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Notifications - MMCA.Common.Application.Tests/Notifications/SendPushNotificationHandlerTests.cs:316 + MMCA.Common.Application.Tests/Notifications/MarkAllNotificationsReadHandlerTests.cs:141 MarkAllNotificationsReadHandlerTests class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Notifications - MMCA.Common.Application.Tests/Notifications/MarkAllNotificationsReadHandlerTests.cs:11 + MMCA.Common.Application.Tests/Notifications/MarkAllNotificationsReadHandlerTests.cs:18 MarkNotificationReadHandlerTests @@ -20378,6 +20841,13 @@

Full inventory

MMCA.Common.Application.Tests/Notifications/PushNotificationDTOProjectorTests.cs:16 + RecordingSetter + class + MMCA.Common.Application.Tests + MMCA.Common.Application.Tests.Notifications + MMCA.Common.Application.Tests/Notifications/MarkAllNotificationsReadHandlerTests.cs:207 + + SendPushNotificationHandlerTests class MMCA.Common.Application.Tests @@ -20655,7 +21125,7 @@

Full inventory

class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Services.Filtering - MMCA.Common.Application.Tests/Services/Filtering/QueryFilterServiceTests.cs:265 + MMCA.Common.Application.Tests/Services/Filtering/QueryFilterServiceTests.cs:286 Widget @@ -21586,7 +22056,7 @@

Full inventory

class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Users - MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:319 + MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:326 ForgotPasswordHandlerBaseTests @@ -21607,7 +22077,7 @@

Full inventory

record MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Users - MMCA.Common.Application.Tests/Users/ChangePasswordHandlerBaseTests.cs:165 + MMCA.Common.Application.Tests/Users/ChangePasswordHandlerBaseTests.cs:213 HandlerMocks @@ -21670,7 +22140,7 @@

Full inventory

class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Users - MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:354 + MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:361 ResetPasswordHandlerBaseTests @@ -21691,7 +22161,7 @@

Full inventory

record MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Users - MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:421 + MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:428 StubTwoFactorService @@ -21705,7 +22175,7 @@

Full inventory

class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Users - MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:428 + MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:435 TestChangePasswordCommand @@ -21719,7 +22189,7 @@

Full inventory

class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Users - MMCA.Common.Application.Tests/Users/ChangePasswordHandlerBaseTests.cs:196 + MMCA.Common.Application.Tests/Users/ChangePasswordHandlerBaseTests.cs:254 TestChangePreferencesCommand @@ -21754,7 +22224,7 @@

Full inventory

class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Users - MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:436 + MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:443 TestDeleteUserCommand @@ -21775,7 +22245,7 @@

Full inventory

class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Users - MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:440 + MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:447 TestExportUserDataHandler @@ -21831,7 +22301,7 @@

Full inventory

class MMCA.Common.Application.Tests MMCA.Common.Application.Tests.Users - MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:444 + MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs:451 TestResetPasswordCommand @@ -22219,6 +22689,13 @@

Full inventory

MMCA.Common.Architecture.Tests/ClockReadFixtures/ClockReadFixtures.cs:12 + TodayReadingFixture + class + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.ClockReadFixtures + MMCA.Common.Architecture.Tests/ClockReadFixtures/ClockReadFixtures.cs:48 + + TwoMemberClockFixture class MMCA.Common.Architecture.Tests @@ -22433,7 +22910,7 @@

Full inventory

class MMCA.Common.Architecture.Tests MMCA.Common.Architecture.Tests.Contracts - MMCA.Common.Architecture.Tests/Contracts/IntegrationEventContractTestsBaseTests.cs:124 + MMCA.Common.Architecture.Tests/Contracts/IntegrationEventContractTestsBaseTests.cs:254 FixtureModuleMap @@ -22482,7 +22959,7 @@

Full inventory

class MMCA.Common.Architecture.Tests MMCA.Common.Architecture.Tests.Contracts - MMCA.Common.Architecture.Tests/Contracts/IntegrationEventContractTestsBaseTests.cs:134 + MMCA.Common.Architecture.Tests/Contracts/IntegrationEventContractTestsBaseTests.cs:264 ProtoContractFitnessTests @@ -22499,6 +22976,62 @@

Full inventory

MMCA.Common.Architecture.Tests/Contracts/EventUpcasterFitnessTests.cs:74 + FixtureCountEvent + record + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents + MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:33 + + + FixtureLabelEvent + record + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents + MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:42 + + + FixtureNamedEvent + record + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents + MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:55 + + + FixtureNullableCountEvent + record + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents + MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:37 + + + FixtureNullableLabelEvent + record + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents + MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:47 + + + FixtureShapedBase + record + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents + MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:10 + + + FixtureShapedEvent + record + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents + MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:22 + + + FixtureTallyEvent + record + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents + MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:29 + + AbstractFitnessControllerBase class MMCA.Common.Architecture.Tests @@ -22688,6 +23221,13 @@

Full inventory

MMCA.Common.Architecture.Tests/Cqrs/IdempotencyFitnessTests.cs:94 + FrameworkConstructorDependencyTests + class + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.Cqrs.ConstructorDependencies + MMCA.Common.Architecture.Tests/Cqrs/ConstructorDependencies/FrameworkConstructorDependencyTests.cs:25 + + FixtureAssemblyMap class MMCA.Common.Architecture.Tests @@ -22804,14 +23344,14 @@

Full inventory

class MMCA.Common.Architecture.Tests MMCA.Common.Architecture.Tests.Domain - MMCA.Common.Architecture.Tests/Domain/ClockReadTests.cs:78 + MMCA.Common.Architecture.Tests/Domain/ClockReadTests.cs:84 FixtureAssemblyMap class MMCA.Common.Architecture.Tests MMCA.Common.Architecture.Tests.Domain - MMCA.Common.Architecture.Tests/Domain/DomainThrowFitnessTests.cs:138 + MMCA.Common.Architecture.Tests/Domain/DomainThrowFitnessTests.cs:149 FixtureAssemblyMap @@ -22933,6 +23473,34 @@

Full inventory

MMCA.Common.Architecture.Tests/Domain/StronglyTypedIdFitnessTests.cs:11 + EntityConventionTests + class + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.Domain.EntityModel + MMCA.Common.Architecture.Tests/Domain/EntityModel/EntityConventionTests.cs:12 + + + FrameworkModuleArchitectureMap + class + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.Domain.EntityModel + MMCA.Common.Architecture.Tests/Domain/EntityModel/FrameworkModuleArchitectureMap.cs:16 + + + ImmutabilityTests + class + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.Domain.EntityModel + MMCA.Common.Architecture.Tests/Domain/EntityModel/ImmutabilityTests.cs:11 + + + TenantEntityConventionTests + class + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.Domain.EntityModel + MMCA.Common.Architecture.Tests/Domain/EntityModel/TenantEntityConventionTests.cs:20 + + BadgeGranter class MMCA.Common.Architecture.Tests @@ -23038,6 +23606,13 @@

Full inventory

MMCA.Common.Architecture.Tests/DomainThrowFixtures/DomainThrowFixtures.cs:76 + SwitchExpressionFixture + class + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.DomainThrowFixtures + MMCA.Common.Architecture.Tests/DomainThrowFixtures/DomainThrowFixtures.cs:115 + + TicketDomainException class MMCA.Common.Architecture.Tests @@ -23108,6 +23683,20 @@

Full inventory

MMCA.Common.Architecture.Tests/FeatureFlagFixtures/FeatureFlagFixtures.cs:11 + DataResidencyTestsBaseTests + class + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.Governance + MMCA.Common.Architecture.Tests/Governance/DataResidencyTestsBaseTests.cs:10 + + + DataSourceBranchingFitnessTests + class + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.Governance + MMCA.Common.Architecture.Tests/Governance/DataSourceBranchingFitnessTests.cs:17 + + DataSubjectSample class MMCA.Common.Architecture.Tests @@ -23122,6 +23711,13 @@

Full inventory

MMCA.Common.Architecture.Tests/Governance/DependencyVersionTests.cs:9 + EngineHit + record + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.Governance + MMCA.Common.Architecture.Tests/Governance/DataSourceBranchingFitnessTests.cs:149 + + FeatureFlagLifecycleRuleTests class MMCA.Common.Architecture.Tests @@ -23175,7 +23771,7 @@

Full inventory

class MMCA.Common.Architecture.Tests MMCA.Common.Architecture.Tests.Governance - MMCA.Common.Architecture.Tests/Governance/PiiConventionTests.cs:13 + MMCA.Common.Architecture.Tests/Governance/PiiConventionTests.cs:20 PiiErasureContractFitnessTests @@ -23185,6 +23781,13 @@

Full inventory

MMCA.Common.Architecture.Tests/Governance/PiiErasureContractFitnessTests.cs:19 + Probe + class + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.Governance + MMCA.Common.Architecture.Tests/Governance/DataResidencyTestsBaseTests.cs:24 + + SampleDeploymentObservabilityTests class MMCA.Common.Architecture.Tests @@ -23493,6 +24096,13 @@

Full inventory

MMCA.Common.Architecture.Tests/StronglyTypedIdFixtures/StronglyTypedIdFixtures.cs:40 + EditorRequiredParameterConventionTests + class + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.Ui + MMCA.Common.Architecture.Tests/Ui/EditorRequiredParameterConventionTests.cs:26 + + LocalizationResourceTests class MMCA.Common.Architecture.Tests @@ -23514,6 +24124,34 @@

Full inventory

MMCA.Common.Architecture.Tests/Ui/NavigationContractTests.cs:19 + PasswordProbe + record + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.Ui + MMCA.Common.Architecture.Tests/Ui/PasswordRuleParityTests.cs:54 + + + PasswordRuleParityTests + class + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.Ui + MMCA.Common.Architecture.Tests/Ui/PasswordRuleParityTests.cs:21 + + + PluralSentenceResourceTests + class + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.Ui + MMCA.Common.Architecture.Tests/Ui/PluralSentenceResourceTests.cs:15 + + + ResourceEntry + record + MMCA.Common.Architecture.Tests + MMCA.Common.Architecture.Tests.Ui + MMCA.Common.Architecture.Tests/Ui/PluralSentenceResourceTests.cs:70 + + SortableColumnFitnessTests class MMCA.Common.Architecture.Tests @@ -23637,7 +24275,7 @@

Full inventory

class MMCA.Common.Aspire MMCA.Common.Aspire - MMCA.Common.Aspire/Extensions.cs:18 + MMCA.Common.Aspire/Extensions.cs:19 Extensions @@ -24158,6 +24796,13 @@

Full inventory

MMCA.Common.Aspire.Tests/Logging/SerilogHostExtensionsTests.cs:175 + ServiceDefaultsRetryTests + class + MMCA.Common.Aspire.Tests + MMCA.Common.Aspire.Tests.Resilience + MMCA.Common.Aspire.Tests/Resilience/ServiceDefaultsRetryTests.cs:18 + + SecurityHeadersMiddlewareTests class MMCA.Common.Aspire.Tests @@ -24186,6 +24831,13 @@

Full inventory

MMCA.Common.Aspire.Tests/Telemetry/AiTelemetryExportTests.cs:20 + LiveMetricsConfigurationTests + class + MMCA.Common.Aspire.Tests + MMCA.Common.Aspire.Tests.Telemetry + MMCA.Common.Aspire.Tests/Telemetry/LiveMetricsConfigurationTests.cs:20 + + MetricsInstrumentationToggleTests class MMCA.Common.Aspire.Tests @@ -24463,7 +25115,7 @@

Full inventory

class MMCA.Common.Domain MMCA.Common.Domain.Auth - MMCA.Common.Domain/Auth/RefreshSession.cs:31 + MMCA.Common.Domain/Auth/RefreshSession.cs:39 BaseDomainEvent @@ -24575,7 +25227,7 @@

Full inventory

interface MMCA.Common.Domain MMCA.Common.Domain.Interfaces - MMCA.Common.Domain/Interfaces/IHasOrderingKey.cs:24 + MMCA.Common.Domain/Interfaces/IHasOrderingKey.cs:29 IIntegrationEvent @@ -24666,7 +25318,7 @@

Full inventory

class MMCA.Common.Domain MMCA.Common.Domain.Privacy - MMCA.Common.Domain/Privacy/PiiRedactor.cs:123 + MMCA.Common.Domain/Privacy/PiiRedactor.cs:126 AndSpecification<TEntity, TIdentifierType> @@ -25040,6 +25692,20 @@

Full inventory

MMCA.Common.Domain.Tests/Privacy/PiiRedactorTests.cs:27 + PiiBase + class + MMCA.Common.Domain.Tests + MMCA.Common.Domain.Tests.Privacy + MMCA.Common.Domain.Tests/Privacy/PiiRedactorTests.cs:32 + + + PiiOverride + class + MMCA.Common.Domain.Tests + MMCA.Common.Domain.Tests.Privacy + MMCA.Common.Domain.Tests/Privacy/PiiRedactorTests.cs:38 + + PiiRedactorTests class MMCA.Common.Domain.Tests @@ -25355,11 +26021,18 @@

Full inventory

MMCA.Common.Grpc/DependencyInjection.cs:25 + GrpcWireFormat + class + MMCA.Common.Grpc + MMCA.Common.Grpc + MMCA.Common.Grpc/GrpcWireFormat.cs:30 + + ResultGrpcExtensions class MMCA.Common.Grpc MMCA.Common.Grpc - MMCA.Common.Grpc/ResultGrpcExtensions.cs:29 + MMCA.Common.Grpc/ResultGrpcExtensions.cs:30 ResultFailureException @@ -25380,7 +26053,14 @@

Full inventory

class MMCA.Common.Grpc MMCA.Common.Grpc.Interceptors - MMCA.Common.Grpc/Interceptors/JwtForwardingClientInterceptor.cs:19 + MMCA.Common.Grpc/Interceptors/JwtForwardingClientInterceptor.cs:27 + + + AuthenticateResultFeatureStub + class + MMCA.Common.Grpc.Tests + MMCA.Common.Grpc.Tests + MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:337 CountingFailureHandler @@ -25408,21 +26088,21 @@

Full inventory

class MMCA.Common.Grpc.Tests MMCA.Common.Grpc.Tests - MMCA.Common.Grpc.Tests/ResilienceHandlerTests.cs:19 + MMCA.Common.Grpc.Tests/ResilienceHandlerTests.cs:23 FakeRequest class MMCA.Common.Grpc.Tests MMCA.Common.Grpc.Tests - MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:280 + MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:343 FakeResponse class MMCA.Common.Grpc.Tests MMCA.Common.Grpc.Tests - MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:282 + MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:345 FakeServerCallContext @@ -25436,14 +26116,14 @@

Full inventory

class MMCA.Common.Grpc.Tests MMCA.Common.Grpc.Tests - MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:284 + MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:347 FakeStreamWriter class MMCA.Common.Grpc.Tests MMCA.Common.Grpc.Tests - MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:291 + MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:354 GrpcResultExceptionInterceptorTests @@ -25453,11 +26133,18 @@

Full inventory

MMCA.Common.Grpc.Tests/GrpcResultExceptionInterceptorTests.cs:24 + GrpcWireFormatTests + class + MMCA.Common.Grpc.Tests + MMCA.Common.Grpc.Tests + MMCA.Common.Grpc.Tests/GrpcWireFormatTests.cs:18 + + JwtForwardingClientInterceptorTests class MMCA.Common.Grpc.Tests MMCA.Common.Grpc.Tests - MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:19 + MMCA.Common.Grpc.Tests/JwtForwardingClientInterceptorTests.cs:21 ResilienceCircuitBreakerFaultInjectionTests @@ -25471,7 +26158,7 @@

Full inventory

class MMCA.Common.Grpc.Tests MMCA.Common.Grpc.Tests - MMCA.Common.Grpc.Tests/ResilienceHandlerTests.cs:15 + MMCA.Common.Grpc.Tests/ResilienceHandlerTests.cs:19 ResultFailureExceptionTests @@ -25527,7 +26214,7 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure - MMCA.Common.Infrastructure/DependencyInjection.cs:46 + MMCA.Common.Infrastructure/DependencyInjection.cs:47 DependencyInjection @@ -25541,7 +26228,7 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure - MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:15 + MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:16 DependencyInjection @@ -25569,14 +26256,14 @@

Full inventory

record MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Auth - MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:169 + MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:174 EmailConfirmationTokenService class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Auth - MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:35 + MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:36 EmailIdentity @@ -25639,14 +26326,14 @@

Full inventory

record MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Auth - MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:160 + MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:199 PasswordResetTokenService class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Auth - MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:26 + MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:33 RsaJwksProvider @@ -25660,7 +26347,7 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Auth - MMCA.Common.Infrastructure/Auth/TokenService.cs:26 + MMCA.Common.Infrastructure/Auth/TokenService.cs:27 StoredPermissionRoleAdministrationService @@ -25681,7 +26368,7 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Auth.TwoFactor - MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs:25 + MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs:36 CacheKeyNamespace @@ -25723,14 +26410,14 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Caching - MMCA.Common.Infrastructure/Caching/HybridCacheService.cs:36 + MMCA.Common.Infrastructure/Caching/HybridCacheService.cs:44 MemoryCacheService class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Caching - MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:18 + MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:19 RedisPrefixScanner @@ -26066,7 +26753,7 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence - MMCA.Common.Infrastructure/Persistence/EFRawSqlQueryExecutor.cs:22 + MMCA.Common.Infrastructure/Persistence/EFRawSqlQueryExecutor.cs:28 EntityConfigurationOptions @@ -26108,7 +26795,7 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence - MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:16 + MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:17 SqlServerUniqueConstraintViolationDetector @@ -26129,7 +26816,7 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.AuditTrail - MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailCleanupJob.cs:47 + MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailCleanupJob.cs:45 AuditTrailEntry @@ -26164,21 +26851,21 @@

Full inventory

record struct MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.AuditTrail - MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:538 + MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:540 PendingEntityKey record MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.AuditTrail - MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:547 + MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:549 EFPermissionGrantStore class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.Auth - MMCA.Common.Infrastructure/Persistence/Auth/EFPermissionGrantStore.cs:34 + MMCA.Common.Infrastructure/Persistence/Auth/EFPermissionGrantStore.cs:35 EFRefreshSessionStore @@ -26248,7 +26935,7 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration - MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:29 + MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:27 EntityTypeConfigurationBase<TEntity, TIdentifierType> @@ -26339,21 +27026,21 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.Conventions - MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:33 + MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:34 RestrictDeleteByDefaultConvention class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.Conventions - MMCA.Common.Infrastructure/Persistence/Conventions/RestrictDeleteByDefaultConvention.cs:41 + MMCA.Common.Infrastructure/Persistence/Conventions/RestrictDeleteByDefaultConvention.cs:42 SoftDeleteUniqueIndexConvention class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.Conventions - MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:33 + MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:34 StronglyTypedIdModelConfiguration @@ -26465,14 +27152,14 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.DataSources - MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:15 + MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:16 DataSourceService class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.DataSources - MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceService.cs:11 + MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceService.cs:12 DataSourcesSettings @@ -26486,7 +27173,7 @@

Full inventory

record MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.DataSources - MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:177 + MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:216 EntityDataSourceRegistry @@ -26496,6 +27183,13 @@

Full inventory

MMCA.Common.Infrastructure/Persistence/DataSources/EntityDataSourceRegistry.cs:21 + FrameworkTableTargets + class + MMCA.Common.Infrastructure + MMCA.Common.Infrastructure.Persistence.DataSources + MMCA.Common.Infrastructure/Persistence/DataSources/FrameworkTableTargets.cs:32 + + IDataSourceResolver interface MMCA.Common.Infrastructure @@ -26514,7 +27208,7 @@

Full inventory

record MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.DataSources - MMCA.Common.Infrastructure/Persistence/DataSources/PhysicalDataSource.cs:20 + MMCA.Common.Infrastructure/Persistence/DataSources/PhysicalDataSource.cs:21 Snapshot @@ -26538,6 +27232,83 @@

Full inventory

MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:42 + CosmosDataSourceEngine + class + MMCA.Common.Infrastructure + MMCA.Common.Infrastructure.Persistence.DataSources.Engines + MMCA.Common.Infrastructure/Persistence/DataSources/Engines/CosmosDataSourceEngine.cs:18 + + + DataSourceEngineCapabilities + record + MMCA.Common.Infrastructure + MMCA.Common.Infrastructure.Persistence.DataSources.Engines + MMCA.Common.Infrastructure/Persistence/DataSources/Engines/DataSourceEngineCapabilities.cs:23 + + + DataSourceEngines + class + MMCA.Common.Infrastructure + MMCA.Common.Infrastructure.Persistence.DataSources.Engines + MMCA.Common.Infrastructure/Persistence/DataSources/Engines/DataSourceEngines.cs:19 + + + ExplicitKeyInsertGroup + record + MMCA.Common.Infrastructure + MMCA.Common.Infrastructure.Persistence.DataSources.Engines + MMCA.Common.Infrastructure/Persistence/DataSources/Engines/ExplicitKeyInsertGroup.cs:12 + + + IDataSourceEngine + interface + MMCA.Common.Infrastructure + MMCA.Common.Infrastructure.Persistence.DataSources.Engines + MMCA.Common.Infrastructure/Persistence/DataSources/Engines/IDataSourceEngine.cs:18 + + + IExplicitKeyInsertDialect + interface + MMCA.Common.Infrastructure + MMCA.Common.Infrastructure.Persistence.DataSources.Engines + MMCA.Common.Infrastructure/Persistence/DataSources/Engines/IExplicitKeyInsertDialect.cs:13 + + + MigrationPolicy + enum + MMCA.Common.Infrastructure + MMCA.Common.Infrastructure.Persistence.DataSources.Engines + MMCA.Common.Infrastructure/Persistence/DataSources/Engines/MigrationPolicy.cs:8 + + + PostgreSQLDataSourceEngine + class + MMCA.Common.Infrastructure + MMCA.Common.Infrastructure.Persistence.DataSources.Engines + MMCA.Common.Infrastructure/Persistence/DataSources/Engines/PostgreSQLDataSourceEngine.cs:17 + + + RowVersionStrategy + enum + MMCA.Common.Infrastructure + MMCA.Common.Infrastructure.Persistence.DataSources.Engines + MMCA.Common.Infrastructure/Persistence/DataSources/Engines/RowVersionStrategy.cs:8 + + + SqliteDataSourceEngine + class + MMCA.Common.Infrastructure + MMCA.Common.Infrastructure.Persistence.DataSources.Engines + MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SqliteDataSourceEngine.cs:16 + + + SQLServerDataSourceEngine + class + MMCA.Common.Infrastructure + MMCA.Common.Infrastructure.Persistence.DataSources.Engines + MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SQLServerDataSourceEngine.cs:19 + + ApplicationDbContext class MMCA.Common.Infrastructure @@ -26563,7 +27334,7 @@

Full inventory

struct MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.DbContexts - MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:279 + MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:284 ModelBuilderExtensions @@ -26626,7 +27397,7 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.DbContexts.Factory - MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:46 + MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:48 IDbContextFactory @@ -26636,13 +27407,6 @@

Full inventory

MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/IDbContextFactory.cs:10 - IdentityInsertGroup - record - MMCA.Common.Infrastructure - MMCA.Common.Infrastructure.Persistence.DbContexts.Factory - MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:424 - - IPhysicalDbContextFactory interface MMCA.Common.Infrastructure @@ -26654,7 +27418,7 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.DbContexts.Factory - MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/PhysicalDbContextFactory.cs:16 + MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/PhysicalDbContextFactory.cs:19 TransactionCommitAmbiguousException @@ -26745,7 +27509,7 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.Interceptors - MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:22 + MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:30 CapturedState @@ -26797,25 +27561,32 @@

Full inventory

MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandOrigin.cs:16 + InternalCommandOriginCapture + class + MMCA.Common.Infrastructure + MMCA.Common.Infrastructure.Persistence.InternalCommands + MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandOriginCapture.cs:21 + + InternalCommandScheduler class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.InternalCommands - MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandScheduler.cs:39 + MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandScheduler.cs:34 InternalCommandAdministration class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration - MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandAdministration.cs:37 + MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandAdministration.cs:34 InternalCommandCleanupService class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration - MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandCleanupService.cs:44 + MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandCleanupService.cs:40 InternalCommandsDisabledNoticeService @@ -26871,7 +27642,7 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing - MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/InternalCommandProcessor.cs:46 + MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/InternalCommandProcessor.cs:43 InternalCommandSignal @@ -26899,14 +27670,14 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.Outbox.Administration - MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:35 + MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:32 OutboxCleanupService class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.Outbox.Administration - MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxCleanupService.cs:45 + MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxCleanupService.cs:42 OutboxDisabledNoticeService @@ -26962,7 +27733,7 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.Outbox.Processing - MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:56 + MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:53 OutboxSignal @@ -26990,7 +27761,7 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.Repositories - MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:20 + MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:21 EFReadRepositoryDecorator<TEntity, TIdentifierType> @@ -27018,14 +27789,14 @@

Full inventory

record MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.Repositories - MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:185 + MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:186 GroupedSum<TKey> record MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.Repositories - MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:190 + MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:191 KeysetQueryBuilder @@ -27039,7 +27810,7 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.Repositories - MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:720 + MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:735 QueryTags @@ -27067,7 +27838,7 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.Repositories - MMCA.Common.Infrastructure/Persistence/Repositories/KeysetQueryBuilder.cs:252 + MMCA.Common.Infrastructure/Persistence/Repositories/KeysetQueryBuilder.cs:266 IRepositoryFactory @@ -27095,7 +27866,7 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Persistence.Tenancy - MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:23 + MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:24 TenantDataSourceOverrideSettings @@ -27130,7 +27901,7 @@

Full inventory

record MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Scheduling - MMCA.Common.Infrastructure/Scheduling/ScheduledJobRunner.cs:447 + MMCA.Common.Infrastructure/Scheduling/ScheduledJobRunner.cs:455 ScheduledJobEntry @@ -27186,7 +27957,7 @@

Full inventory

class MMCA.Common.Infrastructure MMCA.Common.Infrastructure.Storage - MMCA.Common.Infrastructure/Storage/ImageSharpImageProcessor.cs:15 + MMCA.Common.Infrastructure/Storage/ImageSharpImageProcessor.cs:16 NullFileStorageService @@ -27256,7 +28027,63 @@

Full inventory

class MMCA.Common.Infrastructure.Redis.Tests MMCA.Common.Infrastructure.Redis.Tests - MMCA.Common.Infrastructure.Redis.Tests/HybridCacheServiceRedisTests.cs:35 + MMCA.Common.Infrastructure.Redis.Tests/HybridCacheServiceRedisTests.cs:36 + + + FixedAssemblyProvider + class + MMCA.Common.Infrastructure.SQLServer.Tests + MMCA.Common.Infrastructure.SQLServer.Tests + MMCA.Common.Infrastructure.SQLServer.Tests/SQLServerPersistenceTests.cs:223 + + + FixedCurrentUserService + class + MMCA.Common.Infrastructure.SQLServer.Tests + MMCA.Common.Infrastructure.SQLServer.Tests + MMCA.Common.Infrastructure.SQLServer.Tests/SQLServerPersistenceTests.cs:239 + + + NoTenantContext + class + MMCA.Common.Infrastructure.SQLServer.Tests + MMCA.Common.Infrastructure.SQLServer.Tests + MMCA.Common.Infrastructure.SQLServer.Tests/SQLServerPersistenceTests.cs:251 + + + RecordingDomainEventDispatcher + class + MMCA.Common.Infrastructure.SQLServer.Tests + MMCA.Common.Infrastructure.SQLServer.Tests + MMCA.Common.Infrastructure.SQLServer.Tests/SQLServerPersistenceTests.cs:233 + + + SQLServerPersistenceTests + class + MMCA.Common.Infrastructure.SQLServer.Tests + MMCA.Common.Infrastructure.SQLServer.Tests + MMCA.Common.Infrastructure.SQLServer.Tests/SQLServerPersistenceTests.cs:44 + + + SqlThing + class + MMCA.Common.Infrastructure.SQLServer.Tests + MMCA.Common.Infrastructure.SQLServer.Tests + MMCA.Common.Infrastructure.SQLServer.Tests/SQLServerPersistenceTests.cs:261 + + + SqlThingConfiguration + class + MMCA.Common.Infrastructure.SQLServer.Tests + MMCA.Common.Infrastructure.SQLServer.Tests + MMCA.Common.Infrastructure.SQLServer.Tests/SQLServerPersistenceTests.cs:273 + + + SqlThingShipped + record + MMCA.Common.Infrastructure.SQLServer.Tests + MMCA.Common.Infrastructure.SQLServer.Tests + MMCA.Common.Infrastructure.SQLServer.Tests/SQLServerPersistenceTests.cs:270 DependencyInjectionAdditionalTests @@ -27326,7 +28153,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Auth - MMCA.Common.Infrastructure.Tests/Auth/EmailConfirmationTokenServiceTests.cs:19 + MMCA.Common.Infrastructure.Tests/Auth/EmailConfirmationTokenServiceTests.cs:21 EmailIdentityTests @@ -27347,21 +28174,28 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Auth - MMCA.Common.Infrastructure.Tests/Auth/PasswordResetTokenServiceTests.cs:222 + MMCA.Common.Infrastructure.Tests/Auth/PasswordResetTokenServiceTests.cs:296 + + + FakeCacheService + class + MMCA.Common.Infrastructure.Tests + MMCA.Common.Infrastructure.Tests.Auth + MMCA.Common.Infrastructure.Tests/Auth/TwoFactorAuthenticatorTests.cs:181 FakeConfirmationCacheService class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Auth - MMCA.Common.Infrastructure.Tests/Auth/EmailConfirmationTokenServiceTests.cs:214 + MMCA.Common.Infrastructure.Tests/Auth/EmailConfirmationTokenServiceTests.cs:259 FakeTwoFactorStore class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Auth - MMCA.Common.Infrastructure.Tests/Auth/TwoFactorAuthenticatorTests.cs:137 + MMCA.Common.Infrastructure.Tests/Auth/TwoFactorAuthenticatorTests.cs:212 LoginProtectionServiceTests @@ -27389,7 +28223,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Auth - MMCA.Common.Infrastructure.Tests/Auth/PasswordResetTokenServiceTests.cs:18 + MMCA.Common.Infrastructure.Tests/Auth/PasswordResetTokenServiceTests.cs:22 RsaJwksProviderTests @@ -27403,14 +28237,14 @@

Full inventory

record MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Auth - MMCA.Common.Infrastructure.Tests/Auth/TwoFactorAuthenticatorTests.cs:203 + MMCA.Common.Infrastructure.Tests/Auth/TwoFactorAuthenticatorTests.cs:278 TokenServiceTests class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Auth - MMCA.Common.Infrastructure.Tests/Auth/TokenServiceTests.cs:13 + MMCA.Common.Infrastructure.Tests/Auth/TokenServiceTests.cs:16 TotpTwoFactorServiceTests @@ -27424,21 +28258,21 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Auth - MMCA.Common.Infrastructure.Tests/Auth/TwoFactorAuthenticatorTests.cs:16 + MMCA.Common.Infrastructure.Tests/Auth/TwoFactorAuthenticatorTests.cs:19 FakeGrantCache class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Auth.Administration - MMCA.Common.Infrastructure.Tests/Auth/Administration/StoredPermissionRoleAdministrationServiceTests.cs:257 + MMCA.Common.Infrastructure.Tests/Auth/Administration/StoredPermissionRoleAdministrationServiceTests.cs:297 FakeGrantStore class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Auth.Administration - MMCA.Common.Infrastructure.Tests/Auth/Administration/StoredPermissionRoleAdministrationServiceTests.cs:206 + MMCA.Common.Infrastructure.Tests/Auth/Administration/StoredPermissionRoleAdministrationServiceTests.cs:246 StoredPermissionRoleAdministrationServiceTests @@ -27452,7 +28286,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Caching - MMCA.Common.Infrastructure.Tests/Caching/AddCommonHybridCacheTests.cs:18 + MMCA.Common.Infrastructure.Tests/Caching/AddCommonHybridCacheTests.cs:19 CacheOptionsTests @@ -27480,14 +28314,14 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Caching - MMCA.Common.Infrastructure.Tests/Caching/HybridCacheServiceTests.cs:485 + MMCA.Common.Infrastructure.Tests/Caching/HybridCacheServiceTests.cs:509 HybridCacheServiceTests class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Caching - MMCA.Common.Infrastructure.Tests/Caching/HybridCacheServiceTests.cs:23 + MMCA.Common.Infrastructure.Tests/Caching/HybridCacheServiceTests.cs:24 ManualClock @@ -27501,21 +28335,21 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Caching - MMCA.Common.Infrastructure.Tests/Caching/MemoryCacheServiceTests.cs:10 + MMCA.Common.Infrastructure.Tests/Caching/MemoryCacheServiceTests.cs:12 RecordingDistributedCache class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Caching - MMCA.Common.Infrastructure.Tests/Caching/HybridCacheServiceTests.cs:380 + MMCA.Common.Infrastructure.Tests/Caching/HybridCacheServiceTests.cs:404 RecordingHybridCache class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Caching - MMCA.Common.Infrastructure.Tests/Caching/HybridCacheServiceTests.cs:435 + MMCA.Common.Infrastructure.Tests/Caching/HybridCacheServiceTests.cs:459 SignalingEntry @@ -27529,7 +28363,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Caching - MMCA.Common.Infrastructure.Tests/Caching/DistributedCacheServiceTests.cs:117 + MMCA.Common.Infrastructure.Tests/Caching/DistributedCacheServiceTests.cs:142 InProcessDistributedLockTests @@ -27665,6 +28499,20 @@

Full inventory

MMCA.Common.Infrastructure.Tests/Messaging/BrokerMessageBusTests.cs:25 + FakeContract + class + MMCA.Common.Infrastructure.Tests + MMCA.Common.Infrastructure.Tests.Messaging + MMCA.Common.Infrastructure.Tests/Messaging/TypedServiceClientRegistrationTests.cs:35 + + + IFakeContract + interface + MMCA.Common.Infrastructure.Tests + MMCA.Common.Infrastructure.Tests.Messaging + MMCA.Common.Infrastructure.Tests/Messaging/TypedServiceClientRegistrationTests.cs:31 + + InProcessEventBusOutboxTests class MMCA.Common.Infrastructure.Tests @@ -27711,21 +28559,21 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Messaging - MMCA.Common.Infrastructure.Tests/Messaging/BrokerEventBusTests.cs:333 + MMCA.Common.Infrastructure.Tests/Messaging/BrokerEventBusTests.cs:330 NullAssemblyProvider class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Messaging - MMCA.Common.Infrastructure.Tests/Messaging/InProcessEventBusOutboxTests.cs:190 + MMCA.Common.Infrastructure.Tests/Messaging/InProcessEventBusOutboxTests.cs:188 NullAssemblyProvider class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Messaging - MMCA.Common.Infrastructure.Tests/Messaging/InProcessEventBusTests.cs:153 + MMCA.Common.Infrastructure.Tests/Messaging/InProcessEventBusTests.cs:152 OtherIntegrationEvent @@ -27823,14 +28671,14 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Messaging - MMCA.Common.Infrastructure.Tests/Messaging/BrokerEventBusTests.cs:293 + MMCA.Common.Infrastructure.Tests/Messaging/BrokerEventBusTests.cs:292 TestNonOutboxContext class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Messaging - MMCA.Common.Infrastructure.Tests/Messaging/InProcessEventBusTests.cs:118 + MMCA.Common.Infrastructure.Tests/Messaging/InProcessEventBusTests.cs:119 TestOutboxContext @@ -27847,6 +28695,13 @@

Full inventory

MMCA.Common.Infrastructure.Tests/Messaging/InProcessEventBusOutboxTests.cs:145 + TypedServiceClientRegistrationTests + class + MMCA.Common.Infrastructure.Tests + MMCA.Common.Infrastructure.Tests.Messaging + MMCA.Common.Infrastructure.Tests/Messaging/TypedServiceClientRegistrationTests.cs:12 + + EventUpcasterStartupValidatorTests class MMCA.Common.Infrastructure.Tests @@ -28103,7 +28958,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence - MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:176 + MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:167 FixedSourcesRegistry @@ -28117,7 +28972,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence - MMCA.Common.Infrastructure.Tests/Persistence/MarkAllNotificationsReadHandlerTrackingTests.cs:24 + MMCA.Common.Infrastructure.Tests/Persistence/MarkAllNotificationsReadHandlerTrackingTests.cs:22 MigrationApplyProofTests @@ -28152,21 +29007,21 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence - MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:222 + MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:211 NotificationTestDbContext class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence - MMCA.Common.Infrastructure.Tests/Persistence/MarkAllNotificationsReadHandlerTrackingTests.cs:162 + MMCA.Common.Infrastructure.Tests/Persistence/MarkAllNotificationsReadHandlerTrackingTests.cs:160 NullAssemblyProvider class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence - MMCA.Common.Infrastructure.Tests/Persistence/SoftDeleteQueryFilterTests.cs:114 + MMCA.Common.Infrastructure.Tests/Persistence/SoftDeleteQueryFilterTests.cs:112 ProfilingHelperTests @@ -28194,7 +29049,7 @@

Full inventory

record MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence - MMCA.Common.Infrastructure.Tests/Persistence/MarkAllNotificationsReadHandlerTrackingTests.cs:152 + MMCA.Common.Infrastructure.Tests/Persistence/MarkAllNotificationsReadHandlerTrackingTests.cs:150 SensitiveDataLoggingGateTests @@ -28208,7 +29063,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence - MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:128 + MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:119 SoftDeletableEntity @@ -28264,21 +29119,21 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence - MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:114 + MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:105 WidgetContext class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence - MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:184 + MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:175 WidgetRow class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence - MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:120 + MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs:111 AddAuditTrailTests @@ -28327,7 +29182,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail - MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:74 + MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:89 AuditTrailTestHarness @@ -28341,14 +29196,14 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail - MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:61 + MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:76 FailingSaveInterceptor class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail - MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:173 + MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:194 GateTestContext @@ -28369,16 +29224,30 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail - MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:187 + MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:208 - PlainThing + OverridingPiiThing + class + MMCA.Common.Infrastructure.Tests + MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail + MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:60 + + + PiiBaseThing class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:53 + PlainThing + class + MMCA.Common.Infrastructure.Tests + MMCA.Common.Infrastructure.Tests.Persistence.AuditTrail + MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailTestHarness.cs:68 + + CustomSchemaContext class MMCA.Common.Infrastructure.Tests @@ -28386,6 +29255,13 @@

Full inventory

MMCA.Common.Infrastructure.Tests/Persistence/Auth/RefreshSessionModelBuilderExtensionsTests.cs:124 + EFPermissionGrantStoreTests + class + MMCA.Common.Infrastructure.Tests + MMCA.Common.Infrastructure.Tests.Persistence.Auth + MMCA.Common.Infrastructure.Tests/Persistence/Auth/EFPermissionGrantStoreTests.cs:29 + + EFRefreshSessionStoreFindByIdTests class MMCA.Common.Infrastructure.Tests @@ -28428,6 +29304,20 @@

Full inventory

MMCA.Common.Infrastructure.Tests/Persistence/Auth/PermissionGrantModelBuilderExtensionsTests.cs:161 + GrantTestContext + class + MMCA.Common.Infrastructure.Tests + MMCA.Common.Infrastructure.Tests.Persistence.Auth + MMCA.Common.Infrastructure.Tests/Persistence/Auth/EFPermissionGrantStoreTests.cs:92 + + + ManualClock + class + MMCA.Common.Infrastructure.Tests + MMCA.Common.Infrastructure.Tests.Persistence.Auth + MMCA.Common.Infrastructure.Tests/Persistence/Auth/PermissionGrantCacheTests.cs:79 + + NoSessionTableContext class MMCA.Common.Infrastructure.Tests @@ -28439,6 +29329,13 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Auth + MMCA.Common.Infrastructure.Tests/Persistence/Auth/EFPermissionGrantStoreTests.cs:128 + + + NullAssemblyProvider + class + MMCA.Common.Infrastructure.Tests + MMCA.Common.Infrastructure.Tests.Persistence.Auth MMCA.Common.Infrastructure.Tests/Persistence/Auth/RefreshSessionCleanupServiceTests.cs:493 @@ -28449,6 +29346,13 @@

Full inventory

MMCA.Common.Infrastructure.Tests/Persistence/Auth/EFRefreshSessionStoreRotationTests.cs:96 + PermissionGrantCacheTests + class + MMCA.Common.Infrastructure.Tests + MMCA.Common.Infrastructure.Tests.Persistence.Auth + MMCA.Common.Infrastructure.Tests/Persistence/Auth/PermissionGrantCacheTests.cs:18 + + PermissionGrantModelBuilderExtensionsTests class MMCA.Common.Infrastructure.Tests @@ -28610,6 +29514,13 @@

Full inventory

MMCA.Common.Infrastructure.Tests/Persistence/Configuration/OwnsMoneyTests.cs:178 + NotificationConfigurationEngineTests + class + MMCA.Common.Infrastructure.Tests + MMCA.Common.Infrastructure.Tests.Persistence.Configuration + MMCA.Common.Infrastructure.Tests/Persistence/Configuration/NotificationConfigurationEngineTests.cs:23 + + OwnsAddressTests class MMCA.Common.Infrastructure.Tests @@ -28803,14 +29714,14 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Conventions - MMCA.Common.Infrastructure.Tests/Persistence/Conventions/RestrictDeleteByDefaultConventionTests.cs:221 + MMCA.Common.Infrastructure.Tests/Persistence/Conventions/RestrictDeleteByDefaultConventionTests.cs:219 NullAssemblyProvider class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Conventions - MMCA.Common.Infrastructure.Tests/Persistence/Conventions/SoftDeleteUniqueIndexConventionTests.cs:223 + MMCA.Common.Infrastructure.Tests/Persistence/Conventions/SoftDeleteUniqueIndexConventionTests.cs:221 OptionalChild @@ -28901,7 +29812,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Conversions - MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:237 + MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:229 OrderId @@ -28943,21 +29854,21 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Conversions - MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:106 + MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:104 WrappedIdContextServices class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Conversions - MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:190 + MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:182 WrappedIdPostgresContext class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Conversions - MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:156 + MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:150 WrappedIdSqliteContext @@ -28971,7 +29882,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Conversions - MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:131 + MMCA.Common.Infrastructure.Tests/Persistence/Conversions/WrappedIdSqliteContext.cs:127 WrappedOrder @@ -29328,7 +30239,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.DbContexts - MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:345 + MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:343 ApplicationDbContextTests @@ -29415,6 +30326,13 @@

Full inventory

MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:276 + CosmosDbContextTests + class + MMCA.Common.Infrastructure.Tests + MMCA.Common.Infrastructure.Tests.Persistence.DbContexts + MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/CosmosDbContextTests.cs:12 + + DbContextFactoryAdditionalTests class MMCA.Common.Infrastructure.Tests @@ -29440,7 +30358,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.DbContexts - MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:31 + MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:32 DbContextFactoryTests @@ -29454,7 +30372,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.DbContexts - MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:33 + MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:37 EmptyAssemblyProvider @@ -29468,7 +30386,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.DbContexts - MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:331 + MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:329 FixedAssemblyProvider @@ -29503,21 +30421,21 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.DbContexts - MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:135 + MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:136 IntegrityEvent record MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.DbContexts - MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:133 + MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:134 IntegrityTestDbContext class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.DbContexts - MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:140 + MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:141 MidSaveContextCreatingDbContext @@ -29538,21 +30456,21 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.DbContexts - MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:352 + MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:350 NullAssemblyProvider class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.DbContexts - MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:190 + MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:191 NullAssemblyProvider class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.DbContexts - MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:289 + MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:391 PermissionGrantModelGateTests @@ -29629,21 +30547,21 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.DbContexts - MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:235 + MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:330 TestApplicationDbContext class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.DbContexts - MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/ApplicationDbContextTests.cs:93 + MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/ApplicationDbContextTests.cs:86 TestEntity class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.DbContexts - MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/ApplicationDbContextTests.cs:88 + MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/ApplicationDbContextTests.cs:81 TestLocalEvent @@ -29657,14 +30575,14 @@

Full inventory

record MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.DbContexts - MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:233 + MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:328 TransactionTestDbContext class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.DbContexts - MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:240 + MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:335 DbSeederTests @@ -29720,7 +30638,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Inbox - MMCA.Common.Infrastructure.Tests/Persistence/Inbox/EfInboxStoreTests.cs:27 + MMCA.Common.Infrastructure.Tests/Persistence/Inbox/EfInboxStoreTests.cs:28 InboxDisabledWarningServiceTests @@ -29734,7 +30652,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Inbox - MMCA.Common.Infrastructure.Tests/Persistence/Inbox/EfInboxStoreTests.cs:304 + MMCA.Common.Infrastructure.Tests/Persistence/Inbox/EfInboxStoreTests.cs:322 RecordingLogger @@ -29762,7 +30680,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Interceptors - MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:26 + MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:28 DomainEventSaveChangesInterceptorOutboxDisabledTests @@ -29783,28 +30701,28 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Interceptors - MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:17 + MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:19 ExclusionAggregate class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Interceptors - MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:142 + MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:146 ExclusionEvent record MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Interceptors - MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:140 + MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:144 ExclusionTestDbContext class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Interceptors - MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:147 + MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:151 FailingSaveInterceptor @@ -29818,42 +30736,42 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Interceptors - MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/AuditSaveChangesInterceptorTests.cs:223 + MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/AuditSaveChangesInterceptorTests.cs:244 NullAssemblyProvider class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Interceptors - MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/AuditSaveChangesInterceptorTests.cs:287 + MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/AuditSaveChangesInterceptorTests.cs:306 NullAssemblyProvider class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Interceptors - MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:184 + MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:186 NullAssemblyProvider class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Interceptors - MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorOutboxRoutingTests.cs:376 + MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorOutboxRoutingTests.cs:374 NullAssemblyProvider class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Interceptors - MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:219 + MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:225 NullAssemblyProvider class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Interceptors - MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/SaveChangeDetectionTests.cs:147 + MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/SaveChangeDetectionTests.cs:145 OutboxRoutingTestDbContext @@ -29881,35 +30799,35 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Interceptors - MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:174 + MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:182 TestAuditDbContext class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Interceptors - MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/AuditSaveChangesInterceptorTests.cs:238 + MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/AuditSaveChangesInterceptorTests.cs:259 TestAuditEntity class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Interceptors - MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/AuditSaveChangesInterceptorTests.cs:232 + MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/AuditSaveChangesInterceptorTests.cs:253 TestDomainEvent record MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Interceptors - MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:172 + MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:180 TestDomainEventDbContext class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Interceptors - MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:179 + MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:187 TestIntegrationEvent @@ -29986,14 +30904,14 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands - MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandModelTests.cs:128 + MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandModelTests.cs:141 NullAssemblyProvider class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands - MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandModelTests.cs:160 + MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandModelTests.cs:173 NullAssemblyProvider @@ -30014,21 +30932,21 @@

Full inventory

record MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands - MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandTestHarness.cs:275 + MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandTestHarness.cs:278 RecordingCommandHandler class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands - MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandTestHarness.cs:285 + MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandTestHarness.cs:288 RecordingCommandValidator class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands - MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandTestHarness.cs:278 + MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandTestHarness.cs:281 StubCurrentUserService @@ -30042,14 +30960,14 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands.Administration - MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/Administration/InternalCommandAdministrationTests.cs:22 + MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/Administration/InternalCommandAdministrationTests.cs:23 InternalCommandCleanupServiceTests class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.InternalCommands.Administration - MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/Administration/InternalCommandCleanupServiceTests.cs:21 + MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/Administration/InternalCommandCleanupServiceTests.cs:22 InternalCommandProcessorTests @@ -30098,14 +31016,14 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Administration - MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Administration/OutboxAdministrationTests.cs:238 + MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Administration/OutboxAdministrationTests.cs:237 CleanupTestContext class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Administration - MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Administration/OutboxCleanupServiceTests.cs:559 + MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Administration/OutboxCleanupServiceTests.cs:557 Mocks @@ -30119,14 +31037,14 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Administration - MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Administration/OutboxAdministrationTests.cs:278 + MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Administration/OutboxAdministrationTests.cs:275 NullAssemblyProvider class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Administration - MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Administration/OutboxCleanupServiceTests.cs:616 + MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Administration/OutboxCleanupServiceTests.cs:612 OutboxAdministrationTests @@ -30147,35 +31065,35 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing - MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:192 + MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:220 NoAssemblies class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing - MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorOrderingTests.cs:292 + MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorOrderingTests.cs:289 Observation record MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing - MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:186 + MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:214 OrderedTestEvent class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing - MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorOrderingTests.cs:238 + MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorOrderingTests.cs:237 OrderingTestContext class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing - MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorOrderingTests.cs:252 + MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorOrderingTests.cs:251 OutboxProcessorContextRestoreTests @@ -30224,42 +31142,42 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing - MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorTests.cs:1100 + MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorTests.cs:1131 OverrideOnlyCurrentUserService class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing - MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:225 + MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:253 RestoreTestDbContext class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing - MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:248 + MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:276 TestDomainEvent class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing - MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorTests.cs:1076 + MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorTests.cs:1107 TestIntegrationEvent class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing - MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:178 + MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorContextRestoreTests.cs:206 TestIntegrationEvent class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Outbox.Processing - MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorTests.cs:1088 + MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorTests.cs:1119 EFRepositoryAdditionalTests @@ -30301,7 +31219,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Repositories - MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryIntegrationTests.cs:13 + MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryIntegrationTests.cs:14 FakeAggregate @@ -30332,6 +31250,13 @@

Full inventory

MMCA.Common.Infrastructure.Tests/Persistence/Repositories/RepositoryFactoryTests.cs:124 + KeysetQueryBuilderNullOrderingTests + class + MMCA.Common.Infrastructure.Tests + MMCA.Common.Infrastructure.Tests.Persistence.Repositories + MMCA.Common.Infrastructure.Tests/Persistence/Repositories/KeysetQueryBuilderNullOrderingTests.cs:22 + + KeysetQueryBuilderSqlTests class MMCA.Common.Infrastructure.Tests @@ -30343,7 +31268,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Repositories - MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryAuditStampTests.cs:199 + MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryAuditStampTests.cs:197 RepositoryFactoryTests @@ -30371,7 +31296,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Repositories - MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryIntegrationTests.cs:570 + MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryIntegrationTests.cs:588 TestDbContext @@ -30385,7 +31310,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Repositories - MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryIntegrationTests.cs:575 + MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryIntegrationTests.cs:593 TestDbContext @@ -30406,7 +31331,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Repositories - MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryIntegrationTests.cs:559 + MMCA.Common.Infrastructure.Tests/Persistence/Repositories/EFRepositoryIntegrationTests.cs:577 AllSpecification @@ -30630,7 +31555,7 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Specifications - MMCA.Common.Infrastructure.Tests/Persistence/Specifications/QueryParameterizationTests.cs:149 + MMCA.Common.Infrastructure.Tests/Persistence/Specifications/QueryParameterizationTests.cs:147 OrderedSpecification @@ -30742,14 +31667,14 @@

Full inventory

class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Tenancy - MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/DbContextFactoryTenantTests.cs:247 + MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/DbContextFactoryTenantTests.cs:264 NullAssemblyProvider class MMCA.Common.Infrastructure.Tests MMCA.Common.Infrastructure.Tests.Persistence.Tenancy - MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/TenantTestHarness.cs:200 + MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/TenantTestHarness.cs:198 PlainThing @@ -31011,6 +31936,13 @@

Full inventory

MMCA.Common.Infrastructure.Tests/SqlClient/SqlClientEntraAuthenticationTests.cs:14 + CountingAllocator + class + MMCA.Common.Infrastructure.Tests + MMCA.Common.Infrastructure.Tests.Storage + MMCA.Common.Infrastructure.Tests/Storage/ImageSharpImageProcessorFrameBoundTests.cs:71 + + FileUploadOptionsOverloadTests class MMCA.Common.Infrastructure.Tests @@ -31018,6 +31950,20 @@

Full inventory

MMCA.Common.Infrastructure.Tests/Storage/FileUploadOptionsOverloadTests.cs:13 + ImageFrameBoundCollection + class + MMCA.Common.Infrastructure.Tests + MMCA.Common.Infrastructure.Tests.Storage + MMCA.Common.Infrastructure.Tests/Storage/ImageSharpImageProcessorFrameBoundTests.cs:91 + + + ImageSharpImageProcessorFrameBoundTests + class + MMCA.Common.Infrastructure.Tests + MMCA.Common.Infrastructure.Tests.Storage + MMCA.Common.Infrastructure.Tests/Storage/ImageSharpImageProcessorFrameBoundTests.cs:25 + + ImageSharpImageProcessorTests class MMCA.Common.Infrastructure.Tests @@ -31053,6 +31999,104 @@

Full inventory

MMCA.Common.Infrastructure.Tests.MigrationsFixture/CreateMigrationProofTable.cs:24 + Measured + record + MMCA.Common.LoadTests + MMCA.Common.LoadTests + MMCA.Common.LoadTests/PagedQueryLoadTests.cs:201 + + + OutboxThroughputLoadTests + class + MMCA.Common.LoadTests + MMCA.Common.LoadTests + MMCA.Common.LoadTests/OutboxThroughputLoadTests.cs:20 + + + PagedQueryLoadTests + class + MMCA.Common.LoadTests + MMCA.Common.LoadTests + MMCA.Common.LoadTests/PagedQueryLoadTests.cs:13 + + + CountingMessageBus + class + MMCA.Common.LoadTests + MMCA.Common.LoadTests.Support + MMCA.Common.LoadTests/Support/CountingMessageBus.cs:20 + + + LoadIntegrationEvent + record + MMCA.Common.LoadTests + MMCA.Common.LoadTests.Support + MMCA.Common.LoadTests/Support/CountingMessageBus.cs:11 + + + LoadItem + class + MMCA.Common.LoadTests + MMCA.Common.LoadTests.Support + MMCA.Common.LoadTests/Support/LoadItems.cs:16 + + + LoadItemConfiguration + class + MMCA.Common.LoadTests + MMCA.Common.LoadTests.Support + MMCA.Common.LoadTests/Support/LoadItems.cs:52 + + + LoadItemDTO + record + MMCA.Common.LoadTests + MMCA.Common.LoadTests.Support + MMCA.Common.LoadTests/Support/LoadItems.cs:40 + + + LoadItemMapper + class + MMCA.Common.LoadTests + MMCA.Common.LoadTests.Support + MMCA.Common.LoadTests/Support/LoadItems.cs:55 + + + LoadItemNavigationPopulator + class + MMCA.Common.LoadTests + MMCA.Common.LoadTests.Support + MMCA.Common.LoadTests/Support/LoadItems.cs:67 + + + LoadResults + class + MMCA.Common.LoadTests + MMCA.Common.LoadTests.Support + MMCA.Common.LoadTests/Support/LoadResults.cs:11 + + + LoadStack + class + MMCA.Common.LoadTests + MMCA.Common.LoadTests.Support + MMCA.Common.LoadTests/Support/LoadStack.cs:25 + + + PagedQuery + record + MMCA.Common.LoadTests + MMCA.Common.LoadTests.Support + MMCA.Common.LoadTests/Support/PagedQueryFixture.cs:103 + + + PagedQueryFixture + class + MMCA.Common.LoadTests + MMCA.Common.LoadTests.Support + MMCA.Common.LoadTests/Support/PagedQueryFixture.cs:16 + + AuthenticationRequest record struct MMCA.Common.Shared @@ -31172,6 +32216,13 @@

Full inventory

MMCA.Common.Shared/Auth/ClaimsPrincipalExtensions.cs:18 + PasswordComplexity + class + MMCA.Common.Shared + MMCA.Common.Shared.Auth + MMCA.Common.Shared/Auth/PasswordComplexity.cs:18 + + RoleValue class MMCA.Common.Shared @@ -31694,21 +32745,21 @@

Full inventory

class MMCA.Common.Shared MMCA.Common.Shared.ValueObjects - MMCA.Common.Shared/ValueObjects/Enumeration.cs:76 + MMCA.Common.Shared/ValueObjects/Enumeration.cs:77 EnumerationConverter<TEnumeration> class MMCA.Common.Shared MMCA.Common.Shared.ValueObjects - MMCA.Common.Shared/ValueObjects/Enumeration.cs:229 + MMCA.Common.Shared/ValueObjects/Enumeration.cs:230 EnumerationJsonConverterFactory class MMCA.Common.Shared MMCA.Common.Shared.ValueObjects - MMCA.Common.Shared/ValueObjects/Enumeration.cs:200 + MMCA.Common.Shared/ValueObjects/Enumeration.cs:201 ValueObject @@ -31851,6 +32902,13 @@

Full inventory

MMCA.Common.Shared.Tests/Auth/ClaimsPrincipalExtensionsTests.cs:12 + PasswordComplexityTests + class + MMCA.Common.Shared.Tests + MMCA.Common.Shared.Tests.Auth + MMCA.Common.Shared.Tests/Auth/PasswordComplexityTests.cs:12 + + RoleValueTests class MMCA.Common.Shared.Tests @@ -31991,6 +33049,13 @@

Full inventory

MMCA.Common.Shared.Tests/Identifiers/TestIdentifiers.cs:17 + LateRegisteredId + record struct + MMCA.Common.Shared.Tests + MMCA.Common.Shared.Tests.Identifiers + MMCA.Common.Shared.Tests/Identifiers/StronglyTypedIdTypeConverterTests.cs:98 + + LineId record struct MMCA.Common.Shared.Tests @@ -32450,7 +33515,7 @@

Full inventory

class MMCA.Common.Testing.Architecture MMCA.Common.Testing.Architecture - MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:5 + MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:6 ArchitectureRules @@ -32723,7 +33788,7 @@

Full inventory

class MMCA.Common.Testing.Architecture MMCA.Common.Testing.Architecture - MMCA.Common.Testing.Architecture/Bases/Cqrs/ConstructorDependencyCountTestsBase.cs:20 + MMCA.Common.Testing.Architecture/Bases/Cqrs/ConstructorDependencyCountTestsBase.cs:26 ContractImplementationTestsBase @@ -32996,14 +34061,14 @@

Full inventory

record MMCA.Common.Testing.Architecture MMCA.Common.Testing.Architecture - MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:297 + MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:310 ProtoScopeKind enum MMCA.Common.Testing.Architecture MMCA.Common.Testing.Architecture - MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:286 + MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:299 QueryHandlerReadRepositoryTestsBase @@ -33087,7 +34152,7 @@

Full inventory

class MMCA.Common.Testing.Architecture MMCA.Common.Testing.Architecture - MMCA.Common.Testing.Architecture/Bases/Ui/StateManagementConventionTestsBase.cs:17 + MMCA.Common.Testing.Architecture/Bases/Ui/StateManagementConventionTestsBase.cs:18 StronglyTypedIdTestsBase @@ -33108,7 +34173,7 @@

Full inventory

class MMCA.Common.Testing.Aspire MMCA.Common.Testing.Aspire.Fixtures - MMCA.Common.Testing.Aspire/Fixtures/AppHostFixtureBase.cs:38 + MMCA.Common.Testing.Aspire/Fixtures/AppHostFixtureBase.cs:41 AppHostFixtureBase<TAppHost> @@ -33535,7 +34600,7 @@

Full inventory

class MMCA.Common.Testing.Tests MMCA.Common.Testing.Tests.Conformance - MMCA.Common.Testing.Tests/Conformance/MmcaGatewayHardeningTestsBaseTests.cs:97 + MMCA.Common.Testing.Tests/Conformance/MmcaGatewayHardeningTestsBaseTests.cs:99 SampleGatewayHardeningTests @@ -33556,7 +34621,7 @@

Full inventory

class MMCA.Common.Testing.Tests MMCA.Common.Testing.Tests.Fixtures - MMCA.Common.Testing.Tests/Fixtures/CrossServiceFixtureBaseTests.cs:107 + MMCA.Common.Testing.Tests/Fixtures/CrossServiceFixtureBaseTests.cs:119 OverridingFixture @@ -33689,14 +34754,14 @@

Full inventory

record MMCA.Common.Testing.UI MMCA.Common.Testing.UI - MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:158 + MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:189 CapturingHttpMessageHandler class MMCA.Common.Testing.UI MMCA.Common.Testing.UI - MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:19 + MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:20 ErrorSummaryExtensions @@ -33720,13 +34785,6 @@

Full inventory

MMCA.Common.Testing.UI/Infrastructure/HttpTestDoubles.cs:12 - IsAuthenticatedAuthorizationService - class - MMCA.Common.Testing.UI - MMCA.Common.Testing.UI - MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:176 - - MarkupSnapshot class MMCA.Common.Testing.UI @@ -33745,21 +34803,21 @@

Full inventory

record MMCA.Common.Testing.UI MMCA.Common.Testing.UI - MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:157 + MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:162 MutableAuthenticationStateProvider class MMCA.Common.Testing.UI MMCA.Common.Testing.UI - MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:162 + MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:167 Route record MMCA.Common.Testing.UI MMCA.Common.Testing.UI - MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:139 + MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:170 StubTokenStorageService @@ -33815,7 +34873,7 @@

Full inventory

class MMCA.Common.UI MMCA.Common.UI - MMCA.Common.UI/DependencyInjection.cs:288 + MMCA.Common.UI/DependencyInjection.cs:304 BreakpointConstants @@ -33934,7 +34992,7 @@

Full inventory

class MMCA.Common.UI MMCA.Common.UI.Common.Settings - MMCA.Common.UI/Common/Settings/MmcaClientConfigBootstrap.cs:24 + MMCA.Common.UI/Common/Settings/MmcaClientConfigBootstrap.cs:28 NotificationBellOptions @@ -33962,7 +35020,7 @@

Full inventory

class MMCA.Common.UI MMCA.Common.UI.Components.Capabilities - MMCA.Common.UI/Components/Capabilities/BiometricGate.razor.cs:17 + MMCA.Common.UI/Components/Capabilities/BiometricGate.razor.cs:18 ApiFileDownloadButton @@ -33983,7 +35041,7 @@

Full inventory

class MMCA.Common.UI MMCA.Common.UI.Components.Lists - MMCA.Common.UI/Components/Lists/MobileInfiniteScrollList.razor.cs:21 + MMCA.Common.UI/Components/Lists/MobileInfiniteScrollList.razor.cs:20 NotificationBell @@ -34077,6 +35135,13 @@

Full inventory

MMCA.Common.UI/Notifications/DependencyInjection.cs:12 + NotificationPageGate + class + MMCA.Common.UI + MMCA.Common.UI.Notifications + MMCA.Common.UI/Notifications/NotificationPageGate.cs:12 + + NotificationUIModule class MMCA.Common.UI @@ -34151,35 +35216,35 @@

Full inventory

class MMCA.Common.UI MMCA.Common.UI.Pages.Auth - MMCA.Common.UI/Pages/Auth/ForgotPasswordModel.cs:9 + MMCA.Common.UI/Pages/Auth/ForgotPasswordModel.cs:13 LoginModel class MMCA.Common.UI MMCA.Common.UI.Pages.Auth - MMCA.Common.UI/Pages/Auth/LoginModel.cs:9 + MMCA.Common.UI/Pages/Auth/LoginModel.cs:13 PasswordComplexityAttribute class MMCA.Common.UI MMCA.Common.UI.Pages.Auth - MMCA.Common.UI/Pages/Auth/PasswordComplexityAttribute.cs:12 + MMCA.Common.UI/Pages/Auth/PasswordComplexityAttribute.cs:15 RegisterModel class MMCA.Common.UI MMCA.Common.UI.Pages.Auth - MMCA.Common.UI/Pages/Auth/RegisterModel.cs:9 + MMCA.Common.UI/Pages/Auth/RegisterModel.cs:14 ResetPasswordModel class MMCA.Common.UI MMCA.Common.UI.Pages.Auth - MMCA.Common.UI/Pages/Auth/ResetPasswordModel.cs:10 + MMCA.Common.UI/Pages/Auth/ResetPasswordModel.cs:15 Sessions @@ -34399,11 +35464,25 @@

Full inventory

MMCA.Common.UI/Services/Api/HttpResultExecutor.cs:31 + IdempotentReadRetry + class + MMCA.Common.UI + MMCA.Common.UI.Services.Api + MMCA.Common.UI/Services/Api/IdempotentReadRetry.cs:18 + + + PagedReadAll + class + MMCA.Common.UI + MMCA.Common.UI.Services.Api + MMCA.Common.UI/Services/Api/PagedReadAll.cs:19 + + AuthDelegatingHandler class MMCA.Common.UI MMCA.Common.UI.Services.Auth - MMCA.Common.UI/Services/Auth/AuthDelegatingHandler.cs:10 + MMCA.Common.UI/Services/Auth/AuthDelegatingHandler.cs:12 AuthUIService @@ -34445,7 +35524,7 @@

Full inventory

class MMCA.Common.UI MMCA.Common.UI.Services.Auth - MMCA.Common.UI/Services/Auth/JsFetchSessionCookieSync.cs:11 + MMCA.Common.UI/Services/Auth/JsFetchSessionCookieSync.cs:13 JwtAuthenticationStateProvider @@ -34455,6 +35534,20 @@

Full inventory

MMCA.Common.UI/Services/Auth/JwtAuthenticationStateProvider.cs:13 + SameOriginProxyHeaders + class + MMCA.Common.UI + MMCA.Common.UI.Services.Auth + MMCA.Common.UI/Services/Auth/SameOriginProxyHeaders.cs:11 + + + SameOriginProxyRequestHandler + class + MMCA.Common.UI + MMCA.Common.UI.Services.Auth + MMCA.Common.UI/Services/Auth/SameOriginProxyRequestHandler.cs:11 + + UserAgentSummary class MMCA.Common.UI @@ -34494,14 +35587,14 @@

Full inventory

record MMCA.Common.UI MMCA.Common.UI.Services.Auth.OAuth - MMCA.Common.UI/Services/Auth/OAuth/OAuthFlowStateStore.cs:89 + MMCA.Common.UI/Services/Auth/OAuth/OAuthFlowStateStore.cs:92 DirectApiTokenRefresher class MMCA.Common.UI MMCA.Common.UI.Services.Auth.Tokens - MMCA.Common.UI/Services/Auth/Tokens/DirectApiTokenRefresher.cs:19 + MMCA.Common.UI/Services/Auth/Tokens/DirectApiTokenRefresher.cs:25 ISecureTokenStore @@ -34949,7 +36042,7 @@

Full inventory

class MMCA.Common.UI MMCA.Common.UI.Services.Capabilities.Navigation - MMCA.Common.UI/Services/Capabilities/Navigation/DeepLinkDispatcher.cs:9 + MMCA.Common.UI/Services/Capabilities/Navigation/DeepLinkDispatcher.cs:11 DeepLinkRouteEventArgs @@ -35103,7 +36196,7 @@

Full inventory

class MMCA.Common.UI MMCA.Common.UI.Services.Notifications - MMCA.Common.UI/Services/Notifications/NotificationHubService.cs:412 + MMCA.Common.UI/Services/Notifications/NotificationHubService.cs:490 INotificationInboxUIService @@ -35131,7 +36224,7 @@

Full inventory

class MMCA.Common.UI MMCA.Common.UI.Services.Notifications - MMCA.Common.UI/Services/Notifications/NotificationHubService.cs:26 + MMCA.Common.UI/Services/Notifications/NotificationHubService.cs:34 NotificationInboxService @@ -35246,6 +36339,13 @@

Full inventory

MMCA.Common.UI/Validation/IModelValidator.cs:13 + LocalizedDataAnnotationsValidator + class + MMCA.Common.UI + MMCA.Common.UI.Validation + MMCA.Common.UI/Validation/LocalizedDataAnnotationsValidator.cs:21 + + ModelValidation class MMCA.Common.UI @@ -35379,6 +36479,13 @@

Full inventory

MMCA.Common.UI.E2E.Tests/Layout/StickySidebarE2ETests.cs:23 + InpProbe + record + MMCA.Common.UI.E2E.Tests + MMCA.Common.UI.E2E.Tests.WebVitals + MMCA.Common.UI.E2E.Tests/WebVitals/WebVitalsE2ETests.cs:146 + + WebVitalsBudgetTests class MMCA.Common.UI.E2E.Tests @@ -35390,7 +36497,7 @@

Full inventory

class MMCA.Common.UI.E2E.Tests MMCA.Common.UI.E2E.Tests.WebVitals - MMCA.Common.UI.E2E.Tests/WebVitals/WebVitalsE2ETests.cs:15 + MMCA.Common.UI.E2E.Tests/WebVitals/WebVitalsE2ETests.cs:17 GalleryHost @@ -35656,7 +36763,7 @@

Full inventory

class MMCA.Common.UI.Maui MMCA.Common.UI.Maui.Capabilities.Media - MMCA.Common.UI.Maui/Capabilities/Media/MauiTextToSpeechService.cs:12 + MMCA.Common.UI.Maui/Capabilities/Media/MauiTextToSpeechService.cs:14 MauiLocalNotificationService @@ -35712,7 +36819,7 @@

Full inventory

class MMCA.Common.UI.Maui MMCA.Common.UI.Maui.Services - MMCA.Common.UI.Maui/Services/MauiTokenStorageService.cs:19 + MMCA.Common.UI.Maui/Services/MauiTokenStorageService.cs:25 BunitTestBase @@ -35782,7 +36889,7 @@

Full inventory

class MMCA.Common.UI.Tests MMCA.Common.UI.Tests.Components.Capabilities - MMCA.Common.UI.Tests/Components/Capabilities/BiometricGateTests.cs:22 + MMCA.Common.UI.Tests/Components/Capabilities/BiometricGateTests.cs:23 DeepLinkListenerTests @@ -35803,7 +36910,7 @@

Full inventory

class MMCA.Common.UI.Tests MMCA.Common.UI.Tests.Components.Capabilities - MMCA.Common.UI.Tests/Components/Capabilities/BiometricGateTests.cs:172 + MMCA.Common.UI.Tests/Components/Capabilities/BiometricGateTests.cs:204 FakeConnectivityService @@ -35817,7 +36924,7 @@

Full inventory

class MMCA.Common.UI.Tests MMCA.Common.UI.Tests.Components.Capabilities - MMCA.Common.UI.Tests/Components/Capabilities/BiometricGateTests.cs:188 + MMCA.Common.UI.Tests/Components/Capabilities/BiometricGateTests.cs:220 FakeExternalLinkService @@ -35901,7 +37008,7 @@

Full inventory

class MMCA.Common.UI.Tests MMCA.Common.UI.Tests.Components.Lists - MMCA.Common.UI.Tests/Components/Lists/MobileInfiniteScrollListTests.cs:12 + MMCA.Common.UI.Tests/Components/Lists/MobileInfiniteScrollListTests.cs:13 NotificationBellHost @@ -35929,7 +37036,7 @@

Full inventory

class MMCA.Common.UI.Tests MMCA.Common.UI.Tests.Components.Notifications - MMCA.Common.UI.Tests/Components/Notifications/NotificationBellTests.cs:357 + MMCA.Common.UI.Tests/Components/Notifications/NotificationBellTests.cs:397 EmptyStateTests @@ -36072,6 +37179,13 @@

Full inventory

MMCA.Common.UI.Tests/Globalization/StringLocalizerPluralExtensionsTests.cs:13 + BunitComponentTestBaseAuthorizationTests + class + MMCA.Common.UI.Tests + MMCA.Common.UI.Tests.Infrastructure + MMCA.Common.UI.Tests/Infrastructure/BunitComponentTestBaseAuthorizationTests.cs:13 + + BunitComponentTestBaseFacadeTests class MMCA.Common.UI.Tests @@ -36146,7 +37260,21 @@

Full inventory

class MMCA.Common.UI.Tests MMCA.Common.UI.Tests.Layout - MMCA.Common.UI.Tests/Layout/NavMenuTests.cs:290 + MMCA.Common.UI.Tests/Layout/NavMenuTests.cs:308 + + + NotificationPageGateTests + class + MMCA.Common.UI.Tests + MMCA.Common.UI.Tests.Notifications + MMCA.Common.UI.Tests/Notifications/NotificationPageGateTests.cs:16 + + + OtherModule + class + MMCA.Common.UI.Tests + MMCA.Common.UI.Tests.Notifications + MMCA.Common.UI.Tests/Notifications/NotificationPageGateTests.cs:46 ForbiddenTests @@ -36240,11 +37368,18 @@

Full inventory

MMCA.Common.UI.Tests/Pages/Auth/ConfirmEmailPageTests.cs:9 + PasswordComplexityAttributeTests + class + MMCA.Common.UI.Tests + MMCA.Common.UI.Tests.Pages.Auth + MMCA.Common.UI.Tests/Pages/Auth/PasswordComplexityAttributeTests.cs:12 + + RegisterFormTests class MMCA.Common.UI.Tests MMCA.Common.UI.Tests.Pages.Auth - MMCA.Common.UI.Tests/Pages/Auth/RegisterFormTests.cs:21 + MMCA.Common.UI.Tests/Pages/Auth/RegisterFormTests.cs:22 SessionsTests @@ -36366,6 +37501,13 @@

Full inventory

MMCA.Common.UI.Tests/Services/ApiClientRegistrationTests.cs:18 + CapturingHandler + class + MMCA.Common.UI.Tests + MMCA.Common.UI.Tests.Services + MMCA.Common.UI.Tests/Services/SameOriginProxyClientTests.cs:141 + + LazyJsModuleTests class MMCA.Common.UI.Tests @@ -36398,7 +37540,14 @@

Full inventory

class MMCA.Common.UI.Tests MMCA.Common.UI.Tests.Services - MMCA.Common.UI.Tests/Services/ListPageQueryStateServiceTests.cs:264 + MMCA.Common.UI.Tests/Services/ListPageQueryStateServiceTests.cs:278 + + + SameOriginProxyClientTests + class + MMCA.Common.UI.Tests + MMCA.Common.UI.Tests.Services + MMCA.Common.UI.Tests/Services/SameOriginProxyClientTests.cs:26 WasmFormFactorTests @@ -36443,6 +37592,13 @@

Full inventory

MMCA.Common.UI.Tests/Services/Api/EntityServiceBaseTests.cs:27 + GatedGetHandler + class + MMCA.Common.UI.Tests + MMCA.Common.UI.Tests.Services.Api + MMCA.Common.UI.Tests/Services/Api/EntityServiceBaseCachingTests.cs:340 + + HttpResultExecutorTests class MMCA.Common.UI.Tests @@ -36450,6 +37606,13 @@

Full inventory

MMCA.Common.UI.Tests/Services/Api/HttpResultExecutorTests.cs:15 + IdempotentReadRetryTests + class + MMCA.Common.UI.Tests + MMCA.Common.UI.Tests.Services.Api + MMCA.Common.UI.Tests/Services/Api/IdempotentReadRetryTests.cs:16 + + MembershipDto record MMCA.Common.UI.Tests @@ -36478,6 +37641,20 @@

Full inventory

MMCA.Common.UI.Tests/Services/Api/EntityServiceBaseTests.cs:39 + PagedReadAllTests + class + MMCA.Common.UI.Tests + MMCA.Common.UI.Tests.Services.Api + MMCA.Common.UI.Tests/Services/Api/PagedReadAllTests.cs:14 + + + PolicyProbe + class + MMCA.Common.UI.Tests + MMCA.Common.UI.Tests.Services.Api + MMCA.Common.UI.Tests/Services/Api/IdempotentReadRetryTests.cs:72 + + ScriptedHandler class MMCA.Common.UI.Tests @@ -36485,6 +37662,13 @@

Full inventory

MMCA.Common.UI.Tests/Services/Api/EntityServiceBaseIdempotencyRetryTests.cs:46 + StubHandler + class + MMCA.Common.UI.Tests + MMCA.Common.UI.Tests.Services.Api + MMCA.Common.UI.Tests/Services/Api/IdempotentReadRetryTests.cs:79 + + TrackingHttpResponseMessage class MMCA.Common.UI.Tests @@ -36552,7 +37736,7 @@

Full inventory

class MMCA.Common.UI.Tests MMCA.Common.UI.Tests.Services.Auth - MMCA.Common.UI.Tests/Services/Auth/DirectApiTokenRefresherTests.cs:17 + MMCA.Common.UI.Tests/Services/Auth/DirectApiTokenRefresherTests.cs:18 EmailConfirmationUIServiceTests @@ -36566,7 +37750,14 @@

Full inventory

class MMCA.Common.UI.Tests MMCA.Common.UI.Tests.Services.Auth - MMCA.Common.UI.Tests/Services/Auth/OAuthFlowStateStoreTests.cs:99 + MMCA.Common.UI.Tests/Services/Auth/OAuthFlowStateStoreTests.cs:115 + + + JsFetchSessionCookieSyncTests + class + MMCA.Common.UI.Tests + MMCA.Common.UI.Tests.Services.Auth + MMCA.Common.UI.Tests/Services/Auth/JsFetchSessionCookieSyncTests.cs:13 JwtAuthenticationStateProviderTests @@ -36580,7 +37771,7 @@

Full inventory

record MMCA.Common.UI.Tests MMCA.Common.UI.Tests.Services.Auth - MMCA.Common.UI.Tests/Services/Auth/DirectApiTokenRefresherTests.cs:19 + MMCA.Common.UI.Tests/Services/Auth/DirectApiTokenRefresherTests.cs:20 Mocks @@ -36611,6 +37802,13 @@

Full inventory

MMCA.Common.UI.Tests/Services/Auth/TokenHydrationWarmupTests.cs:13 + TokenRefreshPipelineTests + class + MMCA.Common.UI.Tests + MMCA.Common.UI.Tests.Services.Auth + MMCA.Common.UI.Tests/Services/Auth/TokenRefreshPipelineTests.cs:20 + + UserAgentSummaryTests class MMCA.Common.UI.Tests @@ -36653,6 +37851,27 @@

Full inventory

MMCA.Common.UI.Tests/Services/Capabilities/DeepLinkRouteShapeTests.cs:13 + BrowserMapNavigationServiceTests + class + MMCA.Common.UI.Tests + MMCA.Common.UI.Tests.Services.Capabilities.Geo + MMCA.Common.UI.Tests/Services/Capabilities/Geo/BrowserMapNavigationServiceTests.cs:13 + + + BrowserExternalLinkServiceTests + class + MMCA.Common.UI.Tests + MMCA.Common.UI.Tests.Services.Capabilities.Interop + MMCA.Common.UI.Tests/Services/Capabilities/Interop/BrowserExternalLinkServiceTests.cs:14 + + + MauiBackNavigationBridgeTests + class + MMCA.Common.UI.Tests + MMCA.Common.UI.Tests.Services.Navigation + MMCA.Common.UI.Tests/Services/Navigation/MauiBackNavigationBridgeTests.cs:12 + + NavigationPublicLinkBuilderTests class MMCA.Common.UI.Tests @@ -36671,21 +37890,28 @@

Full inventory

class MMCA.Common.UI.Tests MMCA.Common.UI.Tests.Services.Notifications - MMCA.Common.UI.Tests/Services/Notifications/NotificationHubServiceTests.cs:286 + MMCA.Common.UI.Tests/Services/Notifications/NotificationHubServiceTests.cs:347 ChannelReferenceCounterTests class MMCA.Common.UI.Tests MMCA.Common.UI.Tests.Services.Notifications - MMCA.Common.UI.Tests/Services/Notifications/NotificationHubServiceTests.cs:320 + MMCA.Common.UI.Tests/Services/Notifications/NotificationHubServiceTests.cs:381 ConcurrencyTrackingTokenStorage class MMCA.Common.UI.Tests MMCA.Common.UI.Tests.Services.Notifications - MMCA.Common.UI.Tests/Services/Notifications/NotificationHubServiceTests.cs:241 + MMCA.Common.UI.Tests/Services/Notifications/NotificationHubServiceTests.cs:302 + + + InMemoryHubServer + class + MMCA.Common.UI.Tests + MMCA.Common.UI.Tests.Services.Notifications + MMCA.Common.UI.Tests/Services/Notifications/InMemoryHubServer.cs:17 Mocks @@ -36723,6 +37949,13 @@

Full inventory

MMCA.Common.UI.Tests/Services/Notifications/NotificationStateTests.cs:13 + PipePair + class + MMCA.Common.UI.Tests + MMCA.Common.UI.Tests.Services.Notifications + MMCA.Common.UI.Tests/Services/Notifications/InMemoryHubServer.cs:108 + + PushNotificationServiceTests class MMCA.Common.UI.Tests @@ -36867,7 +38100,7 @@

Full inventory

class MMCA.Common.UI.Web MMCA.Common.UI.Web.ClientConfig - MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs:15 + MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs:17 BlazorCircuitLimitExtensions @@ -36888,7 +38121,7 @@

Full inventory

class MMCA.Common.UI.Web MMCA.Common.UI.Web.Hardening - MMCA.Common.UI.Web/Hardening/BoundedCircuitHandler.cs:39 + MMCA.Common.UI.Web/Hardening/BoundedCircuitHandler.cs:43 UiRateLimitingExtensions @@ -36905,6 +38138,111 @@

Full inventory

MMCA.Common.UI.Web/Hardening/UiRateLimitingSettings.cs:33 + HandoffBody + record + MMCA.Common.UI.Web + MMCA.Common.UI.Web.SameOriginProxy + MMCA.Common.UI.Web/SameOriginProxy/SessionHandoffEndpoints.cs:64 + + + HandoffSessionCookieSync + class + MMCA.Common.UI.Web + MMCA.Common.UI.Web.SameOriginProxy + MMCA.Common.UI.Web/SameOriginProxy/HandoffSessionServices.cs:39 + + + HandoffTokenRefresher + class + MMCA.Common.UI.Web + MMCA.Common.UI.Web.SameOriginProxy + MMCA.Common.UI.Web/SameOriginProxy/HandoffSessionServices.cs:14 + + + ProxyResponseMode + enum + MMCA.Common.UI.Web + MMCA.Common.UI.Web.SameOriginProxy + MMCA.Common.UI.Web/SameOriginProxy/SameOriginProxyTransformer.cs:14 + + + SameOriginApiProxyEndpoint + class + MMCA.Common.UI.Web + MMCA.Common.UI.Web.SameOriginProxy + MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpoint.cs:29 + + + SameOriginApiProxyEndpointExtensions + class + MMCA.Common.UI.Web + MMCA.Common.UI.Web.SameOriginProxy + MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpointExtensions.cs:15 + + + SameOriginApiProxyMarker + class + MMCA.Common.UI.Web + MMCA.Common.UI.Web.SameOriginProxy + MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyServiceExtensions.cs:92 + + + SameOriginApiProxyServiceExtensions + class + MMCA.Common.UI.Web + MMCA.Common.UI.Web.SameOriginProxy + MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyServiceExtensions.cs:37 + + + SameOriginApiProxySettings + class + MMCA.Common.UI.Web + MMCA.Common.UI.Web.SameOriginProxy + MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxySettings.cs:21 + + + SameOriginApiProxySettingsValidator + class + MMCA.Common.UI.Web + MMCA.Common.UI.Web.SameOriginProxy + MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxySettingsValidator.cs:14 + + + SameOriginProxyInvoker + class + MMCA.Common.UI.Web + MMCA.Common.UI.Web.SameOriginProxy + MMCA.Common.UI.Web/SameOriginProxy/SameOriginProxyInvoker.cs:19 + + + SameOriginProxyTransformer + class + MMCA.Common.UI.Web + MMCA.Common.UI.Web.SameOriginProxy + MMCA.Common.UI.Web/SameOriginProxy/SameOriginProxyTransformer.cs:34 + + + SessionHandoffEndpoints + class + MMCA.Common.UI.Web + MMCA.Common.UI.Web.SameOriginProxy + MMCA.Common.UI.Web/SameOriginProxy/SessionHandoffEndpoints.cs:15 + + + SessionHandoffProtector + class + MMCA.Common.UI.Web + MMCA.Common.UI.Web.SameOriginProxy + MMCA.Common.UI.Web/SameOriginProxy/SessionHandoffProtector.cs:16 + + + TokenPair + record + MMCA.Common.UI.Web + MMCA.Common.UI.Web.SameOriginProxy + MMCA.Common.UI.Web/SameOriginProxy/SessionHandoffProtector.cs:75 + + BlazorCspPolicyProvider class MMCA.Common.UI.Web @@ -36958,7 +38296,7 @@

Full inventory

class MMCA.Common.UI.Web.Tests MMCA.Common.UI.Web.Tests.Hardening - MMCA.Common.UI.Web.Tests/Hardening/BoundedCircuitHandlerTests.cs:19 + MMCA.Common.UI.Web.Tests/Hardening/BoundedCircuitHandlerTests.cs:20 UiRateLimitingTests @@ -36968,6 +38306,90 @@

Full inventory

MMCA.Common.UI.Web.Tests/Hardening/UiRateLimitingTests.cs:18 + FakeGateway + class + MMCA.Common.UI.Web.Tests + MMCA.Common.UI.Web.Tests.SameOriginProxy + MMCA.Common.UI.Web.Tests/SameOriginProxy/ProxyTestHarness.cs:33 + + + Jwt + class + MMCA.Common.UI.Web.Tests + MMCA.Common.UI.Web.Tests.SameOriginProxy + MMCA.Common.UI.Web.Tests/SameOriginProxy/ProxyTestHarness.cs:246 + + + ProxyHost + class + MMCA.Common.UI.Web.Tests + MMCA.Common.UI.Web.Tests.SameOriginProxy + MMCA.Common.UI.Web.Tests/SameOriginProxy/ProxyTestHarness.cs:148 + + + SameOriginApiProxyAuthFlowTests + class + MMCA.Common.UI.Web.Tests + MMCA.Common.UI.Web.Tests.SameOriginProxy + MMCA.Common.UI.Web.Tests/SameOriginProxy/SameOriginApiProxyAuthFlowTests.cs:20 + + + SameOriginApiProxyCsrfTests + class + MMCA.Common.UI.Web.Tests + MMCA.Common.UI.Web.Tests.SameOriginProxy + MMCA.Common.UI.Web.Tests/SameOriginProxy/SameOriginApiProxyCsrfTests.cs:12 + + + SameOriginApiProxyHubTests + class + MMCA.Common.UI.Web.Tests + MMCA.Common.UI.Web.Tests.SameOriginProxy + MMCA.Common.UI.Web.Tests/SameOriginProxy/SameOriginApiProxyHubTests.cs:29 + + + SameOriginApiProxyOptInTests + class + MMCA.Common.UI.Web.Tests + MMCA.Common.UI.Web.Tests.SameOriginProxy + MMCA.Common.UI.Web.Tests/SameOriginProxy/SameOriginApiProxyOptInTests.cs:21 + + + SameOriginApiProxyOriginTests + class + MMCA.Common.UI.Web.Tests + MMCA.Common.UI.Web.Tests.SameOriginProxy + MMCA.Common.UI.Web.Tests/SameOriginProxy/SameOriginApiProxyOriginTests.cs:14 + + + SameOriginApiProxyRefreshOutcomeTests + class + MMCA.Common.UI.Web.Tests + MMCA.Common.UI.Web.Tests.SameOriginProxy + MMCA.Common.UI.Web.Tests/SameOriginProxy/SameOriginApiProxyRefreshOutcomeTests.cs:14 + + + SameOriginApiProxyTokenTests + class + MMCA.Common.UI.Web.Tests + MMCA.Common.UI.Web.Tests.SameOriginProxy + MMCA.Common.UI.Web.Tests/SameOriginProxy/SameOriginApiProxyTokenTests.cs:15 + + + SeenRequest + record + MMCA.Common.UI.Web.Tests + MMCA.Common.UI.Web.Tests.SameOriginProxy + MMCA.Common.UI.Web.Tests/SameOriginProxy/ProxyTestHarness.cs:26 + + + SessionHandoffServicesTests + class + MMCA.Common.UI.Web.Tests + MMCA.Common.UI.Web.Tests.SameOriginProxy + MMCA.Common.UI.Web.Tests/SameOriginProxy/SessionHandoffServicesTests.cs:14 + + BlazorCspPolicyProviderTests class MMCA.Common.UI.Web.Tests @@ -37087,7 +38509,7 @@

extension(T) blocks

IServiceCollection services MMCA.ADC.Conference.UI - MMCA.ADC.Conference.UI/DependencyInjection.cs:20 + MMCA.ADC.Conference.UI/DependencyInjection.cs:21 IPage page @@ -37157,7 +38579,7 @@

extension(T) blocks

IServiceCollection services MMCA.ADC.Notification.Application - MMCA.ADC.Notification.Application/DependencyInjection.cs:14 + MMCA.ADC.Notification.Application/DependencyInjection.cs:15 IServiceCollection services @@ -37232,7 +38654,7 @@

extension(T) blocks

IEndpointRouteBuilder endpoints MMCA.Common.API - MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:20 + MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:21 IConfiguration configuration @@ -37247,7 +38669,12 @@

extension(T) blocks

IServiceProvider services MMCA.Common.API - MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:22 + MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:33 + + + WebApplication app + MMCA.Common.API + MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:128 IEndpointRouteBuilder endpoints @@ -37267,7 +38694,7 @@

extension(T) blocks

WebApplication app MMCA.Common.API - MMCA.Common.API/Startup/Endpoints/OpenApiEndpointExtensions.cs:24 + MMCA.Common.API/Startup/Endpoints/OpenApiEndpointExtensions.cs:30 IServiceCollection services @@ -37297,7 +38724,7 @@

extension(T) blocks

IServiceCollection services MMCA.Common.API - MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:294 + MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:296 WebApplication app @@ -37342,7 +38769,7 @@

extension(T) blocks

IServiceCollection services MMCA.Common.Aspire - MMCA.Common.Aspire/Extensions.cs:105 + MMCA.Common.Aspire/Extensions.cs:113 WebApplication app @@ -37362,12 +38789,12 @@

extension(T) blocks

IServiceCollection services MMCA.Common.Aspire - MMCA.Common.Aspire/Gateway/GatewayRateLimitingExtensions.cs:240 + MMCA.Common.Aspire/Gateway/GatewayRateLimitingExtensions.cs:258 IApplicationBuilder app MMCA.Common.Aspire - MMCA.Common.Aspire/Gateway/GatewayRateLimitingExtensions.cs:297 + MMCA.Common.Aspire/Gateway/GatewayRateLimitingExtensions.cs:315 IServiceCollection services @@ -37432,7 +38859,7 @@

extension(T) blocks

IServiceCollection services MMCA.Common.Gateway - MMCA.Common.Gateway/RateLimiting/GatewayRoutePolicyExtensions.cs:29 + MMCA.Common.Gateway/RateLimiting/GatewayRoutePolicyExtensions.cs:40 IServiceCollection services @@ -37442,27 +38869,27 @@

extension(T) blocks

ErrorType errorType MMCA.Common.Grpc - MMCA.Common.Grpc/ResultGrpcExtensions.cs:49 + MMCA.Common.Grpc/ResultGrpcExtensions.cs:50 Result result MMCA.Common.Grpc - MMCA.Common.Grpc/ResultGrpcExtensions.cs:60 + MMCA.Common.Grpc/ResultGrpcExtensions.cs:61 IReadOnlyList<Error> errors MMCA.Common.Grpc - MMCA.Common.Grpc/ResultGrpcExtensions.cs:98 + MMCA.Common.Grpc/ResultGrpcExtensions.cs:99 Metadata? trailers MMCA.Common.Grpc - MMCA.Common.Grpc/ResultGrpcExtensions.cs:146 + MMCA.Common.Grpc/ResultGrpcExtensions.cs:151 RpcException exception MMCA.Common.Grpc - MMCA.Common.Grpc/ResultGrpcExtensions.cs:196 + MMCA.Common.Grpc/ResultGrpcExtensions.cs:202 IServiceCollection services @@ -37477,7 +38904,7 @@

extension(T) blocks

IServiceCollection services MMCA.Common.Infrastructure - MMCA.Common.Infrastructure/DependencyInjection.cs:48 + MMCA.Common.Infrastructure/DependencyInjection.cs:49 IServiceCollection services @@ -37487,7 +38914,7 @@

extension(T) blocks

IServiceCollection services MMCA.Common.Infrastructure - MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:17 + MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:18 IServiceCollection services @@ -37607,7 +39034,7 @@

extension(T) blocks

IEndpointRouteBuilder endpoints MMCA.Common.UI.Web - MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs:23 + MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs:25 IServiceCollection services @@ -37622,16 +39049,26 @@

extension(T) blocks

IServiceCollection services MMCA.Common.UI.Web - MMCA.Common.UI.Web/Hardening/UiRateLimitingExtensions.cs:136 + MMCA.Common.UI.Web/Hardening/UiRateLimitingExtensions.cs:152 IApplicationBuilder app MMCA.Common.UI.Web - MMCA.Common.UI.Web/Hardening/UiRateLimitingExtensions.cs:181 + MMCA.Common.UI.Web/Hardening/UiRateLimitingExtensions.cs:197 + + + IEndpointRouteBuilder endpoints + MMCA.Common.UI.Web + MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpointExtensions.cs:17 + + + IServiceCollection services + MMCA.Common.UI.Web + MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyServiceExtensions.cs:39

Generated / excluded artifacts (no type sections written)

-

136 files excluded as generated (EF migrations, snapshots, *.g.cs, AssemblyInfo).

+

138 files excluded as generated (EF migrations, snapshots, *.g.cs, AssemblyInfo).

@@ -37972,6 +39409,12 @@

Generated / excl

+ + + + + + diff --git a/docs/onboarding/00-primer.html b/docs/onboarding/00-primer.html index 5fb33410..b7e58885 100644 --- a/docs/onboarding/00-primer.html +++ b/docs/onboarding/00-primer.html @@ -1009,6 +1009,26 @@

The decision records (ADRs) t

+ + + + + + + + + + + + + + + + + + + +
MMCA.ADC.Migrations.SqlServer.Identity/Migrations/20260913044149_AddPermissionGrants.Designer.cs
MMCA.ADC.Migrations.SqlServer.Identity/Migrations/20260930161158_UserNamesUnicode.cs
MMCA.ADC.Migrations.SqlServer.Identity/Migrations/20260930161158_UserNamesUnicode.Designer.cs
MMCA.ADC.Migrations.SqlServer.Identity/Migrations/SQLServerDbContextModelSnapshot.cs
The actor model is not adopted: per-entity state stays in the owning module's database behind optimistic concurrency, read pressure stays on the caching tiers, and live fan-out stays on the notification hub, with no actor runtime as a hosting layer g07/g09/g10
128Time as an input: Domain and Application code never reads the ambient clock (DateTime.UtcNow, Now, Today, DateTimeOffset.UtcNow, Now); handlers inject TimeProvider and pass the instant into the domain, BaseDomainEvent.DateOccurred is the single exemption, and an IL-scanning fitness base (ClockReadTestsBase) enforces itg28
129The tactical aggregate contract: entities are sealed classes over BaseEntity / AuditableBaseEntity / AuditableAggregateRootEntity, built only through a Create returning Result<T>, with private setters, child access through the root and invariants in static *Invariants classes; EntityConventionTestsBase gates construction, encapsulation and placementg28
130Per-engine data source strategy: one internal IDataSourceEngine per engine (SQL Server, PostgreSQL, SQLite, Cosmos DB) behind the static DataSourceEngines.For(DataSource) registry, so call sites read engine facts instead of branching on the DataSource enum, with DataSourceBranchingFitnessTests as the gateg07/g28
131Same-origin API proxy (backend-for-frontend): an opt-in AddCommonSameOriginApiProxy plus MapCommonSameOriginApiProxy serves /api/** on the UI host's own origin and forwards to the gateway through YARP, attaching the bearer from the HttpOnly session cookie so no browser script ever holds an API tokeng15/g08/g12

The canonical index for the full set can be found at https://ivanball.github.io/docs/adr/.


diff --git a/docs/onboarding/99-coverage-audit.html b/docs/onboarding/99-coverage-audit.html index e910d6ca..4d7fb148 100644 --- a/docs/onboarding/99-coverage-audit.html +++ b/docs/onboarding/99-coverage-audit.html @@ -1473,6 +1473,63 @@

1. Coverage reconciliation

  • Governance events: none (no new group, no classifier rule). The primer ADR table already matches the 127-record index.
  • +
    +

    Regeneration note (re-verified against current source, 2026-10-02 full drift sweep). Regenerated + at MMCA.Common cd0026df + MMCA.ADC 024e4a98 (both clean; prior pass 834a8fa / + 3a32fd34, Common releases v1.212.0 through v1.218.1 in between). Net change: +192 distinct nodes (5,042 to 5,234), + 205 added and 13 removed by name, 186 relocated (same type, new file:line), + 581 body-only, 0 regrouped; classify.ps1 reports 0 (after the approved MMCA.Common.LoadTests rule below; the first run refused 14) unmapped and the per-group counts + sum to 5,234. Individually-sectioned types 2,704 to 2,776, roll-ups 2,330 to 2,458, + ### sections 2,647 to 2,720, cycles 46 to 47, + edges 19,214 by namespace / 786 by unique-name fallback / 101 dropped.

    +
      +
    • Per-group movement (26 of 28 groups moved; the rest are unchanged):
        +
      • G02 Domain Building Blocks (Entities, Value Objects, Aggregates) (members 36): body 3, lineShift 5. PiiRedactor now finds [Pii] through Attribute.IsDefined with inherit: true so an override sees the base declaration, DateRange.Overlaps became inclusive at both ends, and AuditableAggregateRootEntity.SetItems always copies into a fresh list; these are the Common bug-hunt 2026-09-30 and 1.218.0 remediation waves (0cf2a909, e9c28d15), anchored by MMCA.Common/Source/Core/MMCA.Common.Domain/Privacy/PiiRedactor.cs:122.
      • +
      • G03 Querying: Specifications, Filtering & the Entity Query Service (members 42): body 4. StringFilterStrategy.CanParseValue now overrides for the IN operator only and QueryFilterService gained validation hooks, with the three new RS0026 SuppressMessage attributes shifting QueryFieldService anchors; the filter fix is Common 1.213.1 (11434c80, server-mapped filter applied verbatim), anchored by MMCA.Common/Source/Core/MMCA.Common.Application/Services/Filtering/StringFilterStrategy.cs:26.
      • +
      • G04 Domain & Integration Events + Outbox Dual-Dispatch (members 38): body 6. OutboxProcessor, OutboxCleanupService, OutboxAdministration and the event buses now take FrameworkTableTargets and gate on context.Engine.Capabilities.IsRelational instead of ApplicationDbContext.SupportsOutbox, and EfInboxStore takes TimeProvider; this is the 1.218.0 constructor-narrowing (ceiling 7) and per-engine contract plus the 1.216.0 TimeProvider change, anchored by MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:58.
      • +
      • G05 CQRS: Commands, Queries & the Decorator Pipeline (members 61): lineShift 2. No type body changed; TransactionalCommandDecorator and CacheKeyLocks only moved lines (CacheKeyLocks because ICacheService gained TryGetAsync and GetFromSharedStoreAsync), reason for the decorator shift not stated in source, anchored by MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:22.
      • +
      • G06 Validation (members 22): body 1, lineShift 11. StrongPasswordRules<T> now takes its lengths and four character classes from the new PasswordComplexity type in Common.Shared (Level 0 to 1), and the other CommonValidationRules fragments shifted one line; the stated driver is the 1.218.0 password-rule parity gate (3 non-ASCII rows), anchored by MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:191.
      • +
      • G07 Persistence & EF Core (members 177): added 13, removed 1, body 39, lineShift 10. Per-engine strategy replaces enum branching: IDataSourceEngine, DataSourceEngines, DataSourceEngineCapabilities, MigrationPolicy, RowVersionStrategy, IExplicitKeyInsertDialect, ExplicitKeyInsertGroup, four engine classes and FrameworkTableTargets were added and IdentityInsertGroup removed (RequestIdentityInsert became RequestExplicitKeyInsert), so adding an engine no longer needs a hand hunt for branches (ADR-130, e9c28d15), anchored by MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/IDataSourceEngine.cs:18.
      • +
      • G08 Authentication & Authorization (members 162): added 9, body 21, lineShift 6. Refresh-session issue, rotation, reuse detection and session cap moved out of AuthenticationServiceBase (10 to 7 constructor dependencies) into the new IAuthSessionIssuer/AuthSessionIssuer, the SessionCookieStore family (ISessionCookieStore, SessionCookieSettings, SessionClaimsToken, SessionRefreshOutcome) was added for the same-origin proxy, and PasswordComplexity was added (e9c28d15, ADR-131), anchored by MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:39.
      • +
      • G09 Caching (members 9): body 4. ICacheService gained GetFromSharedStoreAsync so single-use auth records (OAuth code, reset and confirmation tokens, last 2FA step) skip the local L1 tier, plus TryGetAsync for value-type misses, and MemoryCacheService now applies Cache:DefaultDuration; reasons are a record redeemable from another replica's L1 for 30s (0439838b) and the bug-hunt items L47 and L57 (0cf2a909), anchored by MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:62.
      • +
      • G10 Notifications (Push + In-App Inbox + Email) (members 59): body 9. MarkAllNotificationsReadHandler became one set-based ExecuteUpdateAsync (bug-hunt L49), and ADC LiveChannelGrpcService now rejects blank or over-long channel keys, event names, bad characters and payloads over 64 KiB so the anonymous internal surface is bounded (9b423f97), anchored by MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/UserNotifications/UseCases/MarkAllRead/MarkAllNotificationsReadHandler.cs:50.
      • +
      • G12 API Hosting, Middleware, Idempotency & DTO/Contract Mapping (members 88): body 14. AuthControllerBase login and register are [NonIdempotent] so token pairs never enter the replay cache (M137), EntityControllerBase.ExportAsync is fail-closed unless AllowUnscopedExport is set (403 Export.RowScopeRequired, 1.218.0 privacy item #30), and startup gained InitializeDatabaseUnlessDesignTimeAsync, anchored by MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:273.
      • +
      • G13 gRPC & Inter-Service Contracts (members 7): added 1, body 3. GrpcWireFormat was added so UTC DateTime and invariant decimal wire strings are written and parsed in one tested place, replacing nine service and adapter copies in ADC and Store (0439838b), anchored by MMCA.Common/Source/Presentation/MMCA.Common.Grpc/GrpcWireFormat.cs:30.
      • +
      • G14 Module System, Composition & Configuration (members 87): removed 1, body 19, lineShift 1. UserUseCaseLog was removed and each Users handler base now declares its own private partial [LoggerMessage] methods (event ids unchanged, 46 verified identical), ModuleLoader keeps loadable types and logs Error on a ReflectionTypeLoadException instead of a Warning, and ScheduledJobRunner stamps under its own 5-second token (1.218.0 slices refactor, 0439838b, M119), anchored by MMCA.Common/Source/Core/MMCA.Common.Application/Modules/ModuleLoader.cs:81.
      • +
      • G15 Common UI Framework (MudBlazor components, theme, base pages) (members 174): added 21, body 42, lineShift 3. The same-origin API proxy family was added to MMCA.Common.UI.Web (SameOriginApiProxyEndpoint, SameOriginProxyInvoker, SessionHandoffEndpoints, HandoffTokenRefresher and others) so access and refresh tokens stay in HttpOnly cookies (ADR-131), PagedReadAll moved in from ADC, IdempotentReadRetry and NotificationPageGate were added, and the bundled Bootstrap stylesheet was dropped, anchored by MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpoint.cs:29.
      • +
      • G16 Aspire Orchestration & Service Defaults (members 65): body 5. Service-default HTTP retry now skips POST and PATCH (a timed-out attempt may still be running; L80), and a Telemetry:DisableAspNetCoreMetrics knob and Live Metrics off-switch were added for idle-billing cost (d4824b36, ADC e882f40e), anchored by MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:56.
      • +
      • G17 ADC Conference - Domain Model & Module Contracts (members 111): body 5. Conference domain invariants tightened: Speaker optional fields and Category type are length-checked (M139), question entity and type compared ordinally (M143), speaker link URLs must be absolute http(s) (M161), from the ADC bug-hunt remediation (495c3167), anchored by MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/SpeakerInvariants.cs:16.
      • +
      • G18 ADC Conference - Application & Use Cases (members 358): added 1, removed 1, body 15. SpeakerQuestionAnswerNavigationPopulator was removed, UserDeletedFeedbackHandler was added so erasure also deletes Conference feedback answers (L106), UpdateEventQuestionAnswerHandler now applies the create-path rules via EnsureAnswerIsValid (M140), and RefreshFromSessionizeCommand is no longer ITransactional so the throttle stamp commits alone (L96), all from ADC 495c3167, anchored by MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/UpdateEventQuestionAnswer/UpdateEventQuestionAnswerHandler.cs:80.
      • +
      • G19 ADC Conference - Infrastructure & Persistence (members 29): body 1. SessionScoringService body changed only in comments (the PII guardrail is now composed by AddConferenceAiGuardrails and the fail-closed path is described as a direct construction over a bare chat client), from the ADR-audit comment fix (3478eb27), anchored by MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Sessions/Scoring/SessionScoringService.cs:42.
      • +
      • G20 ADC Conference - API, gRPC Contracts & Service Host (members 52): body 20. Every Conference controller that returns a null read scope now sets AllowUnscopedExport (privileged-reader predicate, Organizer check or true for reference data) to match Common 1.218.0's fail-closed CSV export (024e4a98), and EventLiveValidationServiceGrpcAdapter returns a failure Result for a malformed speaker id instead of throwing (9b423f97), anchored by MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:138.
      • +
      • G21 ADC Conference - UI (members 163): added 1, body 40, lineShift 1. Public pages now share PublicReadAudience.IsPrivilegedReaderAsync (cancellation propagates, no bare catch), the Conference and Engagement UI read time through TimeProvider, session create and edit enforce end-after-start (BR-122), and the AI scoring panel counts this run's scores, from ADC 9b423f97, 495c3167 and 85ef8c18, anchored by MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/PublicReadAudience.cs:18.
      • +
      • G22 ADC Engagement Module (Session Bookmarks) (members 196): added 2, body 13, lineShift 1. UserDeletedBookmarksHandler and UserDeletedBadgeHandler were added so UserDeleted erases session bookmarks and the attendee badge (L106), and SetLeaderboardParticipationHandler plus BookmarkService bodies changed for the terminal erased opt-in (L100) and cancellation-aware retry (L104), from ADC 495c3167, anchored by MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/CheckIns/IntegrationEventHandlers/UserDeletedBadgeHandler.cs:22.
      • +
      • G26 ADC Engagement Live Layer (Real-Time Polls & Session Q&A) (members 87): added 2, body 5. UserDeletedVotesHandler and UserDeletedSessionQuestionsHandler were added to the UserDeleted fan-out (L106), CastVote and ToggleUpvote treat a lost first-insert unique-index race as success (M146), the poll and question reads apply the published-scope gate (L105), and HappeningNow reads time through TimeProvider, from ADC 495c3167 and 9b423f97, anchored by MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/LivePolls/UseCases/CastVote/CastVoteHandler.cs:58.
      • +
      • G23 ADC Identity Module (Users, Profiles, GDPR Export/Erasure) (members 108): added 1, body 4, lineShift 1. AuthenticationService now takes IAuthSessionIssuer in place of ITokenService, TimeProvider, IRefreshSessionStore and RefreshSessionSettings, JwtAudience was added for a fail-fast JWT audience, and user name and device columns widened to nvarchar (M148), from ADC 024e4a98, 9b423f97 and 495c3167, anchored by MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:56.
      • +
      • G24 ADC Application Host, UI Shell & Cross-Module Composition (members 17): body 1. AppDelegate (iOS) now asks DeepLinkDispatcher.IsAppRelativeRoute before publishing a universal link, because Publish throws on a non-app-relative route and an exception out of a UIKit delegate callback terminates the app (SEC-Common-88, mirrors MainActivity), from ADC 495c3167 and 46271321, anchored by MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/Platforms/iOS/AppDelegate.cs:20.
      • +
      • G27 Device Capability Abstraction Layer (Native Contracts, MAUI, Browser & Fallback Adapters) (members 103): body 7, lineShift 2. IExternalLinkService.OpenAsync now returns Task<bool> so map navigation reports the real outcome (L70), BrowserExternalLinkService explains why noopener hides a blocked tab, and MauiPushRegistrationService serializes registration passes (L79), from the Common bug-hunt wave C-5 (0cf2a909), anchored by MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Capabilities/Interop/IExternalLinkService.cs:23.
      • +
      • G28 Common AI Integration (members 32): body 4, lineShift 1. PiiRedactionGuardrail now scrubs tool results, tool-call string arguments and reasoning text (M136), ChatToolPolicy fails closed on unrecognised consequential-marker shapes (M135), AiSettings rejects Ai:Timeout above one hour at startup (L90), and ReplayChatClient records under a lock (L84), from the Common bug-hunt wave C-6 (0cf2a909), anchored by MMCA.Common/Source/Core/MMCA.Common.AI/Guardrails/PiiRedactionGuardrail.cs:108.
      • +
      • G25 Testing & Quality Infrastructure (members 2923): added 154, removed 10, body 296, lineShift 142. New gates landed in Common 1.218.0 (FrameworkConstructorDependencyTests with a 7-dependency ceiling, PiiConventionTests, PasswordRuleParityTests, EditorRequiredParameterConventionTests, DataSourceBranchingFitnessTests), a weekly MMCA.Common.LoadTests tier (OutboxThroughputLoadTests, PagedQueryLoadTests) and a sqlserver-integration tier were added, and many existing test bodies moved with the bug-hunt, same-origin proxy and gRPC adapter changes, anchored by MMCA.Common/Tests/Performance/MMCA.Common.LoadTests/OutboxThroughputLoadTests.cs:20.
      • +
      +
    • +
    • Handled mechanically (no re-authoring): citation remaps 3,589 over 212 part file(s), 405 flagged for a human; 18 more fixed by the residual baseline-text match; 0 relocations; repack moved 609 sections, 19 flagged; fixlinks rewrote 4 anchors.
    • +
    • Authoring pass: 162 units re-authored (13 overview, 1 rollup, 148 sections) across 25 groups, all against real source + with path:line citations. Spot-checks (evidence-verifier): first pass 5 CONFIRMED (g07, g09, g13, g14, g15 overviews) and 1 DRIFTED (g08 overview still placed the refresh-session workflow in AuthenticationServiceBase.cs, re-anchored to MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:39). A short-form cite scan then found 299 File.cs:N cites past end-of-file in 56 parts (198 in parts re-authored this pass), which verify.ps1 did not check; a 70-unit citation fix pass took that to 0. Its spot-checks: 4 CONFIRMED (g04, g08, g23, g28 overviews), 2 DRIFTED in group-02 (a non-existent Email implicit operator, stale Store Order.cs cites), both corrected by hand against MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Contact/Email.cs:44 and MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Domain/Orders/Order.cs:220.
    • +
    • Outside the type pipeline: 5 of 5 devops-* chapters refreshed (devops-aspire, devops-cicd, devops-iac, devops-runbooks, devops-testing), + each because a file it cites has newer commits than the chapter.
        +
      • devops-aspire: MMCA.ADC/.github/workflows/cross-service-tests.yml: 1 commit(s) [9b423f97 2026-10-01 Conference-day hardening, tighter deploy gates, shared Common code; bump MMCA.Common to v1.216.0 (#233)] | MMCA.ADC/.github/workflows/deploy.yml: 3 commit(s) [024e4a98 2026-10-01 Adopt MMCA.Common 1.218.1 (remediation wave) (#236); 2140ebd1 2026-10-01 chore(deps): Bump the github-actions group across 1 directory with 4 updates (#231)] | MMCA.ADC/infra/main.bicep: 1 commit(s) [e882f40e 2026-09-29 chore(infra): idle-billing cost settings (Live Metrics off, ASP.NET Core metrics drop, 120s gateway probes) (#226)] | MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/appsettings.json: 1 commit(s) [e882f40e 2026-09-29 chore(infra): idle-billing cost settings (Live Metrics off, ASP.NET Core metrics drop, 120s gateway probes) (#226)] | MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/Dockerfile: 1 commit(s) [9b423f97 2026-10-01 Conference-day hardening, tighter deploy gates, shared Common code; bump MMCA.Common to v1.216.0 (#233)] | MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Dockerfile: 1 commit(s) [9b423f97 2026-10-01 Conference-day hardening, tighter deploy gates, shared Common code; bump MMCA.Common to v1.216.0 (#233)]
      • +
      • devops-cicd: MMCA.ADC/.github/workflows/claude-code-review.yml: 1 commit(s) [2140ebd1 2026-10-01 chore(deps): Bump the github-actions group across 1 directory with 4 updates (#231)] | MMCA.ADC/.github/workflows/claude.yml: 1 commit(s) [2140ebd1 2026-10-01 chore(deps): Bump the github-actions group across 1 directory with 4 updates (#231)] | MMCA.ADC/.github/workflows/cost-guard.yml: 1 commit(s) [2140ebd1 2026-10-01 chore(deps): Bump the github-actions group across 1 directory with 4 updates (#231)] | MMCA.ADC/.github/workflows/cross-service-tests.yml: 1 commit(s) [9b423f97 2026-10-01 Conference-day hardening, tighter deploy gates, shared Common code; bump MMCA.Common to v1.216.0 (#233)] | MMCA.ADC/.github/workflows/deploy.yml: 3 commit(s) [024e4a98 2026-10-01 Adopt MMCA.Common 1.218.1 (remediation wave) (#236); 2140ebd1 2026-10-01 chore(deps): Bump the github-actions group across 1 directory with 4 updates (#231)] | MMCA.ADC/.github/workflows/dr-drill.yml: 2 commit(s) [2140ebd1 2026-10-01 chore(deps): Bump the github-actions group across 1 directory with 4 updates (#231); 9b423f97 2026-10-01 Conference-day hardening, tighter deploy gates, shared Common code; bump MMCA.Common to v1.216.0 (#233)] | MMCA.ADC/.github/workflows/e2e.yml: 1 commit(s) [9b423f97 2026-10-01 Conference-day hardening, tighter deploy gates, shared Common code; bump MMCA.Common to v1.216.0 (#233)] | MMCA.ADC/.github/workflows/load-test.yml: 1 commit(s) [2140ebd1 2026-10-01 chore(deps): Bump the github-actions group across 1 directory with 4 updates (#231)] | MMCA.Common/.github/workflows/ci.yml: 2 commit(s) [1f4df71f 2026-10-01 docs: fix stale source comments found by the 2026-10-01 ADR audit (#468); 0439838b 2026-10-01 feat: auth-cache hardening, tighter gates, shared consumer code (v1.216.0) (#463)] | MMCA.Common/.github/workflows/claude-code-review.yml: 2 commit(s) [0439838b 2026-10-01 feat: auth-cache hardening, tighter gates, shared consumer code (v1.216.0) (#463); 693038e7 2026-09-30 ci: Bump anthropics/claude-code-action from 1.0.231 to 1.0.235 (#446)] | MMCA.Common/.github/workflows/claude.yml: 2 commit(s) [0439838b 2026-10-01 feat: auth-cache hardening, tighter gates, shared consumer code (v1.216.0) (#463); 693038e7 2026-09-30 ci: Bump anthropics/claude-code-action from 1.0.231 to 1.0.235 (#446)] | MMCA.Common/.github/workflows/release.yml: 1 commit(s) [0439838b 2026-10-01 feat: auth-cache hardening, tighter gates, shared consumer code (v1.216.0) (#463)]
      • +
      • devops-iac: MMCA.ADC/infra/main.bicep: 1 commit(s) [e882f40e 2026-09-29 chore(infra): idle-billing cost settings (Live Metrics off, ASP.NET Core metrics drop, 120s gateway probes) (#226)] | MMCA.Common/samples/deployment/main.bicep: 1 commit(s) [e9c28d15 2026-10-01 feat!: remediation backlog wave (1.218.0) (#469)]
      • +
      • devops-runbooks: MMCA.ADC/.github/workflows/dr-drill.yml: 2 commit(s) [2140ebd1 2026-10-01 chore(deps): Bump the github-actions group across 1 directory with 4 updates (#231); 9b423f97 2026-10-01 Conference-day hardening, tighter deploy gates, shared Common code; bump MMCA.Common to v1.216.0 (#233)]
      • +
      • devops-testing: MMCA.ADC/.github/workflows/cross-service-tests.yml: 1 commit(s) [9b423f97 2026-10-01 Conference-day hardening, tighter deploy gates, shared Common code; bump MMCA.Common to v1.216.0 (#233)] | MMCA.ADC/.github/workflows/deploy.yml: 3 commit(s) [024e4a98 2026-10-01 Adopt MMCA.Common 1.218.1 (remediation wave) (#236); 2140ebd1 2026-10-01 chore(deps): Bump the github-actions group across 1 directory with 4 updates (#231)] | MMCA.ADC/.github/workflows/e2e.yml: 1 commit(s) [9b423f97 2026-10-01 Conference-day hardening, tighter deploy gates, shared Common code; bump MMCA.Common to v1.216.0 (#233)] | MMCA.Common/.github/workflows/ci.yml: 2 commit(s) [1f4df71f 2026-10-01 docs: fix stale source comments found by the 2026-10-01 ADR audit (#468); 0439838b 2026-10-01 feat: auth-cache hardening, tighter gates, shared consumer code (v1.216.0) (#463)] | test-count drift: MMCA.Common.Shared.Tests chapter 58 vs inventory 60; MMCA.Common.Domain.Tests chapter 62 vs inventory 64; MMCA.Common.Application.Tests chapter 398 vs inventory 401; MMCA.Common.Infrastructure.Tests chapter 499 vs inventory 514; MMCA.Common.API.Tests chapter 155 vs inventory 170; MMCA.Common.Grpc.Tests chapter 16 vs inventory 18; MMCA.Common.UI.Tests chapter 152 vs inventory 170; MMCA.Common.UI.Web.Tests chapter 12 vs inventory 24; MMCA.Common.Aspire.Tests chapter 50 vs inventory 52; MMCA.Common.Architecture.Tests chapter 230 vs inventory 254; MMCA.Common.UI.E2E.Tests chapter 20 vs inventory 21; MMCA.ADC.Identity.Shared.Tests chapter 2 vs inventory 3; MMCA.ADC.Conference.Application.Tests chapter 193 vs inventory 196; MMCA.ADC.Conference.UI.Tests chapter 66 vs inventory 81; MMCA.ADC.Engagement.Application.Tests chapter 65 vs inventory 70; MMCA.ADC.Architecture.Tests chapter 56 vs inventory 57; MMCA.ADC.Services.Tests chapter 7 vs inventory 17
      • +
      • Coverage gaps (operational files no chapter mentions): MMCA.Common/.github/workflows/load-tests.yml, MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Fixtures/observability-main.bicep. load-tests.yml is now covered in devops-testing; observability-main.bicep is an architecture-test fixture, not an operational file, and stays uncovered by design.
      • +
      +
    • +
    • CONCEPT-MAPS.md: 1 mechanical mismatch(es) corrected. The flagged number ("four AI packages" vs the 22-package total) was a checker false positive and stands (FACTS.md lists exactly four AI packages). Non-numeric drift fixed: map 8 gains the per-engine IDataSourceEngine strategy and the PostgreSQL shim (ADR-130), map 9 moves session issuance and rotation to AuthSessionIssuer, map 11 adds the same-origin proxy.
    • +
    • Verification: verify.ps1: 0 missing, rubric 34/34, 0 rollup, 0 citation (7,971 full-path cites), 0 anchor (25,310 links), and the new short-cite check 0. verify.ps1 gained check 5 (short-form File.cs:line past end-of-file, exit bit 16) with a test-verify.ps1 negative case (38/38 pass).
    • +
    • Governance event: a classifier rule, no new group. The new MMCA.Common.LoadTests assembly (14 types, e.g. MMCA.Common/Tests/Performance/MMCA.Common.LoadTests/OutboxThroughputLoadTests.cs:20) routes to G25 Testing & Quality Infrastructure beside MMCA.Common.Benchmarks (user-approved 2026-10-01; Tools/invtool/classify.ps1:268).
    • +
    +

    2. Exceptions log (every deliberate omission, with reason)

    2.1 Generated / scaffolded code, not sectioned (134 files)

    diff --git a/docs/onboarding/CONCEPT-MAPS.html b/docs/onboarding/CONCEPT-MAPS.html index 37f99579..3e7d562c 100644 --- a/docs/onboarding/CONCEPT-MAPS.html +++ b/docs/onboarding/CONCEPT-MAPS.html @@ -575,7 +575,12 @@

    CosmosDbContext) over the abstract ApplicationDbContext, one instance per database. Each entity is engine-agnostic; a single [UseDataSource(engine)] attribute on its config class picks SQL Server, PostgreSQL (ADR-113), Cosmos, or SQLite. Cross-source relationships auto-degrade; the outbox - is the cross-source consistency mechanism. Each service self-applies its EF migrations at boot + is the cross-source consistency mechanism. Engine facts (relational or not, migrations, row-version + strategy, explicit-key inserts) come from one internal IDataSourceEngine per engine, looked up + through the static DataSourceEngines.For(DataSource) registry, so call sites read capabilities + instead of branching on the engine + (ADR-130). Each + service self-applies its EF migrations at boot (ADR-030).

    flowchart TD
         ENTITY["Domain entity (plain class, no persistence choice)"]
    @@ -585,10 +590,13 @@ 

    subgraph SHIMS["Engine shim base classes (one token = one engine)"] SQL["…SQLServer&lt;T,Id&gt;<br/>(all prod configs today)"] + PG["...PostgreSQL&lt;T,Id&gt;<br/>(first-class engine, ADR-113, staged)"] COS["…Cosmos&lt;T,Id&gt;<br/>(shipped + tested, staged)"] LITE["…Sqlite&lt;T,Id&gt;<br/>(fast integration tests, staged)"] end + ENG["DataSourceEngines.For: one internal IDataSourceEngine<br/>per engine, capabilities not branches (ADR-130)"] + subgraph DBS["Database-per-service (ADR-006)"] CTX1["SQLServerDbContext instance: Conference DB + outbox"] CTX2["SQLServerDbContext instance: Engagement DB + outbox"] @@ -600,13 +608,15 @@

    MIG["Startup sole-migrator: DatabaseInitStrategy=Migrate (ADR-030)"] ENTITY --> CFG --> ATTR --> REG - REG --> SQL & COS & LITE + REG --> SQL & PG & COS & LITE SQL --> DBS DBS -->|"FK dropped across sources → batch loaders"| XSPEC MIG -.-> DBS + ENG -.->|"engine facts"| REG + ENG -.-> MIG classDef p fill:#fef7e0,stroke:#f9ab00,color:#111 - class ENTITY,CFG,ATTR,REG,XSPEC,MIG p

    + class ENTITY,CFG,ATTR,REG,XSPEC,MIG,ENG p

    9. Authentication & Authorization stack

    The auth concern (G08) spans token validation, session cookies, federated sign-in, password hashing, @@ -616,11 +626,11 @@

    9. Authentication & Authoriza subgraph AUTHN["Authentication"] JWT["JWT bearer validation"] JWKS["JWKS discovery + fallback fetch (ADR-004)"] - COOKIE["HttpOnly session cookie + non-validating SSR scheme (ADR-022)"] + COOKIE["HttpOnly session cookie + non-validating SSR scheme (ADR-022);<br/>ISessionCookieStore writes them server-side for the proxy (ADR-131)"] HASH["PBKDF2-HMAC-SHA512, 32-byte salt, 600k iters:<br/>one algorithm, no legacy branch (ADR-102)"] LOGIN["ILoginProtectionService: lockout + per-IP cap (ADR-029)"] EXT["External OAuth: Google / GitHub behind a short-lived<br/>ExternalLogin cookie + single-use code (ADR-036/043)"] - ROT["AuthenticationServiceBase: 15-min access token +<br/>one refresh session per device, SHA-256 at rest,<br/>rotated with reuse detection (ADR-097);<br/>ITokenRefresher per head (ADR-051)"] + ROT["AuthenticationServiceBase decides who is signed in;<br/>IAuthSessionIssuer: 15-min access token +<br/>one refresh session per device, SHA-256 at rest,<br/>rotated with reuse detection (ADR-097);<br/>ITokenRefresher per head (ADR-051)"] RESET["Forgot/reset password: cache-backed single-use<br/>hashed token + email leg (ADR-091)"] end @@ -694,7 +704,11 @@

    11. UI, write-once re G26). Culture cookie + IStringLocalizer drive i18n (ADR-027); ThemeService drives day/dark (ADR-028). Each module's UI plugs into one shared shell by contributing navigation and routes through IUIModule - (ADR-067).

    + (ADR-067). A Blazor + Web host can opt in to the same-origin API proxy in MMCA.Common.UI.Web, which serves /api/** on + the UI host's own origin and forwards to the gateway through YARP with the bearer read from the + HttpOnly session cookie + (ADR-131).

    flowchart TD
         PAGE["Razor component authored once<br/>(per-module .UI library, e.g. EventList.razor)"]
         COMMONUI["MMCA.Common.UI: MudBlazor building blocks,<br/>DataGridListPageBase + EntityServiceBase client<br/>data access, common pages/services (G15, ADR-094)"]
    @@ -718,6 +732,7 @@ 

    11. UI, write-once re CSP["Security headers + pluggable CSP (ADR-023)"] RM["Render mode: InteractiveAuto on the root router,<br/>prerender on (ADR-056)"] CAP["Device capability contracts in MMCA.Common.UI:<br/>browser-JS, inert fallback and MAUI-native adapters<br/>chosen per host at DI time (ADR-042, G26)"] + BFF["Opt-in same-origin API proxy, MMCA.Common.UI.Web:<br/>/api/** forwarded to the gateway via YARP,<br/>bearer from the HttpOnly cookie (ADR-131, G15)"] PAGE --> COMMONUI COMMONUI --> WEB @@ -732,9 +747,10 @@

    11. UI, write-once re COMMONUI --> CAP CAP -.->|"browser + fallback adapters"| WEB CAP -.->|"MMCA.Common.UI.Maui adapters"| MAUI + BFF -.->|"browser API calls"| WEB classDef u fill:#f3e8fd,stroke:#a142f4,color:#111 - class PAGE,COMMONUI,SHELL,WEB,MAUI,CAP u

    + class PAGE,COMMONUI,SHELL,WEB,MAUI,CAP,BFF u

    12. ADC business modules, bounded contexts end-to-end

    Each ADC module is a vertical slice through all layers. Conference is large enough to split across diff --git a/docs/onboarding/devops-aspire.html b/docs/onboarding/devops-aspire.html index 16e29f8e..443a5008 100644 --- a/docs/onboarding/devops-aspire.html +++ b/docs/onboarding/devops-aspire.html @@ -224,8 +224,8 @@

    Infrastructure containers

    legacy single AtlDevCon database is deliberately not provisioned here, and it is gone in Azure too. It was exported to the bacpac blob sql-archive/AtlDevCon-20260902.bacpac and dropped on 2026-09-02, so the template no longer declares it and the blob, restorable with az sql db import, is the rollback - source of record (MMCA.ADC/infra/main.bicep:871-880). These four databases are the entire application - data estate (main.bicep:885-897). Database-per-service is the topology everywhere, not a + source of record (MMCA.ADC/infra/main.bicep:890-899). These four databases are the entire application + data estate (main.bicep:904-916). Database-per-service is the topology everywhere, not a local-development shortcut.

    Redis is also persistent (Program.cs:44-45), used by service hosts for distributed output caching and ICacheService. All four service hosts receive WithReference(redis) and WaitFor(redis).

    @@ -357,8 +357,8 @@

    The check all resolve from it, and the logical name collapses onto Default: one SQLServerDbContext instance, one EF change tracker, one migrations set per service. The Azure side matches exactly: the Bicep hands Identity only DataSources__Identity__SQLServerConnectionString - (MMCA.ADC/infra/main.bicep:1665) plus its migrations assembly (main.bicep:1666) and - Outbox__DatabaseName (main.bicep:1667), with no top-level ConnectionStrings__SQLServerConnectionString + (MMCA.ADC/infra/main.bicep:1686) plus its migrations assembly (main.bicep:1687) and + Outbox__DatabaseName (main.bicep:1688), with no top-level ConnectionStrings__SQLServerConnectionString anywhere. The WaitFor(database) (Extensions.cs:492) ensures the service process does not start until SQL Server is healthy.

    Polyglot siblings (ADR-018). Two more helpers wire the non-SQL engines for the staged Conference @@ -462,7 +462,7 @@

    gRPC cross-service references

    drift from the outbound-HTTP path (Core/MMCA.Common.Shared/Resilience/GrpcResilienceDefaults.cs:15-24), while its circuit-breaker shape is stated explicitly (failure ratio 0.5 at line 27, minimum throughput 10 at line 30, break duration 10 seconds at line 33) because an east-west gRPC call addresses a peer - directly and bypasses the gateway's active health checks, so the breaker is the only thing that notices + directly and bypasses the gateway's destination health checks, so the breaker is the only thing that notices a peer going bad. The h2c choice is not a shortcut: Aspire's endpoint discovery does not reliably produce a services__{name}__https__0 key for project resources, so the resolver falls back to http regardless of the requested scheme, and the target must therefore serve HTTP/2 on its cleartext endpoint @@ -515,7 +515,7 @@

    Why the ga or every cross-service call fails validation on the issuer claim. The appsettings default hardcodes the pinned dev port for a standalone F5 run, so deriving the value from the gateway resource means an Aspire run cannot mint wrong-issuer tokens if that port ever moves (Program.cs:368-374). Production sets - the equivalent from the gateway's ACA FQDN (MMCA.ADC/infra/main.bicep:1690).

    + the equivalent from the gateway's ACA FQDN (MMCA.ADC/infra/main.bicep:1711).

    [Rubric §11, Security] assesses how credentials and tokens flow through the system. No symmetric JWT secret is shared between services; each non-Identity service fetches the RSA public key dynamically. Routing that fetch through the gateway rather than hitting Identity directly keeps the internal topology @@ -541,7 +541,7 @@

    The Blazor UI

    AppHost knows:

    • Api__ApiEndpoint and Api__WasmApiEndpoint on the UI (Program.cs:450-451). These are both halves of - the split infra/main.bicep:2479 and 2481 make in production, derived from the gateway resource + the split infra/main.bicep:2516 and 2518 make in production, derived from the gateway resource instead of hardcoded. The WASM one is what /client-config hands the browser (and what the Blazor CSP pins connect-src to), so it must be an externally reachable URL: a browser cannot resolve an Aspire service-discovery name. The server-side one is overridden here because exactly one consumer is @@ -617,8 +617,8 @@

      Framework host exten owns. A non-integer probe port throws at startup rather than silently producing no listener, since the platform would then probe a closed port and the revision would never come up (KestrelEndpointExtensions.cs:72-76). In Azure the key is 8081 on Identity, Conference and - Engagement (main.bicep:1661, 1886, 2024) and 8082 on Notification (main.bicep:2174), because the - ADR-012 mixed profile already owns 8080 for ingress and 8081 for gRPC there (main.bicep:2134-2139, 2232-2233). + Engagement (main.bicep:1682, 1909, 2049) and 8082 on Notification (main.bicep:2201), because the + ADR-012 mixed profile already owns 8080 for ingress and 8081 for gRPC there (main.bicep:2159-2164, 2259-2260). Locally the key is absent on every host, which is exactly why the AppHost has to use the h2c probe instead.

    • AddCommonKeyVaultConfiguration() @@ -812,7 +812,7 @@

      ConfigureOpenTelemetry<TBuilder
    • OutboxPollFilterProcessor is added to the tracing pipeline (Common.Aspire/Extensions.cs:246) before the exporters, so its OnEnd runs first (comment at Extensions.cs:241-245).

    • -
    • Four cost knobs ([Rubric §31, Cost and FinOps]), three off by default and one on. +

    • Five cost knobs ([Rubric §31, Cost and FinOps]), four off by default and one on. Telemetry:DisableHttpClientMetrics (Extensions.cs:148-169) and Telemetry:DisableRuntimeMetrics (Extensions.cs:175-188) each drop a metric family. Both branches are more than "skip the instrumentation call", and the comments explain why (Extensions.cs:150-159 and 177-179): a deployed @@ -829,15 +829,27 @@

      ConfigureOpenTelemetry<TBuilder (TryGetTraceSampleRatio, Extensions.cs:448-461; the same defensive shape in IsInstrumentationDisabled, Extensions.cs:471-472). The fourth, Telemetry:FilterProbeTelemetry, is the one that defaults on and is covered on its own below - (IsProbeTelemetryFilterEnabled, Extensions.cs:483-484). ADC's production Bicep sets the first three - explicitly: Telemetry__TracesSampleRatio=0.25 (MMCA.ADC/infra/main.bicep:252-255), - Telemetry__DisableHttpClientMetrics=true (main.bicep:284-287) and - Telemetry__DisableRuntimeMetrics=true (main.bicep:288-291), the last two measured at roughly 65% of - AppMetrics ingestion, about 290 MB of a 500 MB daily stream (main.bicep:278-283). It also stretches - the OTel export cadence with the standard OTEL_METRIC_EXPORT_INTERVAL=300000 (main.bicep:299-302): - the exporter ships cumulative aggregates, so a 5x longer interval drops roughly 80% of the remaining - datapoints while five-minute alert windows keep the same signal (main.bicep:293-298). An unset host - keeps every metric family, samples every trace and exports on the SDK's 60 second default.

      + (IsProbeTelemetryFilterEnabled, Extensions.cs:483-484). A fifth, + Telemetry:DisableAspNetCoreMetrics, drops the ASP.NET Core meter family (http.server.*, + kestrel.*, aspnetcore.*, signalr.server.*) with the same whole-provider Drop View, matched on + the Microsoft.AspNetCore. meter-name prefix, for the same reason: the distro adds those meters on its + own (ConfigureAspNetCoreMetrics, MMCA.Common.Aspire/Extensions.Telemetry.cs:344-370). ADC's + production Bicep sets four of the five explicitly: Telemetry__TracesSampleRatio=0.25 + (MMCA.ADC/infra/main.bicep:252-255), Telemetry__DisableHttpClientMetrics=true (main.bicep:284-287) + and Telemetry__DisableRuntimeMetrics=true (main.bicep:288-291), those two measured at roughly 65% of + AppMetrics ingestion, about 290 MB of a 500 MB daily stream (main.bicep:278-283), and + Telemetry__DisableAspNetCoreMetrics=true (main.bicep:297-300), whose family was 73% of workspace + ingestion over 2026-09-22..28 with no alert reading it, since request latency and failures alert off + AppRequests (main.bicep:293-296). Alongside them it sets the distro's own + AzureMonitor__EnableLiveMetrics=false (main.bicep:307-310): Live Metrics is on by default and keeps + every replica talking to the Live Metrics service with nobody watching, which held every app just + above the Container Apps idle line so that 64% of vCPU-seconds billed at the active rate + (main.bicep:302-306). All six container apps carry both entries (for example main.bicep:1675-1676 + on Identity and 2371-2372 on the gateway). It also stretches the OTel export cadence with the + standard OTEL_METRIC_EXPORT_INTERVAL=300000 (main.bicep:318-321): the exporter ships cumulative + aggregates, so a 5x longer interval drops roughly 80% of the remaining datapoints while five-minute + alert windows keep the same signal (main.bicep:312-317). An unset host keeps every metric family, + samples every trace and exports on the SDK's 60 second default.

    • MapDefaultEndpoints

      @@ -923,7 +935,7 @@

      OutboxPollFilterProcessor

      that does not take the persistence stack (OutboxPollFilterProcessor.cs:17-22).

      [Rubric §31, Cost and FinOps] assesses whether observability costs are controlled. Suppressing poll spans on a 300 s polling interval in production (Outbox__PollingIntervalSeconds=300 on all four ADC - container apps, MMCA.ADC/infra/main.bicep:1672, 1893, 2031, 2183) eliminates the majority of + container apps, MMCA.ADC/infra/main.bicep:1693, 1916, 2056, 2210) eliminates the majority of idle-process telemetry ingestion. The framework makes this the default for every consumer; individual services do not need to configure it.

      Probe telemetry filtering

      @@ -1108,7 +1120,7 @@

      The six Dockerfiles

      the same base images. None build the AppHost: it is a local-only orchestration artifact, never deployed.

      Common structure

      Stage base (first FROM in all six): mcr.microsoft.com/dotnet/aspnet:10.0 pinned by digest - (@sha256:1fe8...1497, Gateway.Dockerfile:4), with WORKDIR /app and EXPOSE 8080 / EXPOSE 8081 + (@sha256:2d58...746f, Gateway.Dockerfile:4), with WORKDIR /app and EXPOSE 8080 / EXPOSE 8081 (Gateway.Dockerfile:5-7). This is the runtime-only image; it has no SDK tools, minimizing the attack surface of the final image. The digest is the point of that FROM line: the tag still names the channel, but the digest is what resolves, so a re-tagged :10.0 cannot silently change the runtime under a @@ -1192,33 +1204,33 @@

      Four service Dockerfiles

    [Rubric §17, DevOps and Deployment] continues: having one Dockerfile per deployable means each image is independently versioned and deployed. CI declares all six as a six-way parallel matrix - (.github/workflows/deploy.yml:1186-1207), gated as a whole on the changes job classifying the diff as - code (deploy.yml:1183), and each leg additionally carries a changed column from that job's per-image - dirty map (deploy.yml:1192, 1195, 1198, 1201, 1204, 1207). A leg whose image is clean skips the build and - push (deploy.yml:1227-1228) and re-tags the last :latest to this sha instead - (deploy.yml:1261-1274), a registry-side az acr import manifest copy that pulls and pushes no layer - (deploy.yml:1257-1258), so it still concludes success: the comment above the job - (deploy.yml:1174-1178) records why that matters, since deploy gates on the job-level - needs.build-images.result == 'success' equality (deploy.yml:1354) and a skipped leg would turn the + (.github/workflows/deploy.yml:1095-1116), gated as a whole on the changes job classifying the diff as + code (deploy.yml:1086), and each leg additionally carries a changed column from that job's per-image + dirty map (deploy.yml:1101, 1104, 1107, 1110, 1113, 1116). A leg whose image is clean skips the build and + push (deploy.yml:1136-1137) and re-tags the last :latest to this sha instead + (deploy.yml:1170-1183), a registry-side az acr import manifest copy that pulls and pushes no layer + (deploy.yml:1166-1167), so it still concludes success: the comment above the job + (deploy.yml:1077-1081) records why that matters, since deploy gates on the job-level + needs.build-images.result == 'success' equality (deploy.yml:1263) and a skipped leg would turn the whole job skipped and silently cancel the deploy. The UseAppHost=false publish flag strips the native executable wrapper; the Docker entrypoint invokes the DLL directly via the already-present runtime in the base image.

    -

    The same clean leg then copies the fresh sha tag back onto :latest (deploy.yml:1283-1287). That +

    The same clean leg then copies the fresh sha tag back onto :latest (deploy.yml:1192-1196). That second copy exists because of the registry's own housekeeping: the daily ACR purge task keeps only the three most recently updated tags per repository and deletes anything older than three days, and minting a sha tag never touches :latest, so after a few deploys that leave an image clean the very tag the re-tag reads from would age out, get purged, and fail the next clean leg, which blocks the whole deploy - through the same success equality (deploy.yml:1275-1282). Copying the identical digest moves only the + through the same success equality (deploy.yml:1184-1191). Copying the identical digest moves only the tag's timestamp; the build-and-push leg already pushes :latest on every build, so both legs keep it alive.

    Each leg ends with a container-image CVE scan of the exact tag this deploy will roll out: Trivy against {acr}/{image}:{sha}, table format, CRITICAL,HIGH, ignore-unfixed: true, exit-code: 1 - (deploy.yml:1297-1305). It exists because the supply-chain job's CycloneDX SBOM and + (deploy.yml:1206-1214). It exists because the supply-chain job's CycloneDX SBOM and dotnet list --vulnerable cover the managed NuGet graph only, so nothing else inspects the base layer of - any of the six images (deploy.yml:1289-1291). It is non-gating (continue-on-error: true, - deploy.yml:1298): findings are printed in the step log rather than reddening every deploy. Note the + any of the six images (deploy.yml:1198-1200). It is non-gating (continue-on-error: true, + deploy.yml:1207): findings are printed in the step log rather than reddening every deploy. Note the honest residue, the comment justifies that choice by the images building on a floating aspnet base - (deploy.yml:1293-1296), while the six Dockerfiles now pin that base by digest + (deploy.yml:1202-1205), while the six Dockerfiles now pin that base by digest (Gateway.Dockerfile:4): the stated precondition for flipping continue-on-error to false is met on the pinning half, and the step is still non-gating.

    [Rubric §32, Dependency and Supply-Chain] assesses whether what ships is known, pinned and checked. The @@ -1239,23 +1251,23 @@

    Local-to-cloud parity

    SQL Server container (persistent) - Azure SQL Server; the same four databases (ADC_Identity, ADC_Conference, ADC_Engagement, ADC_Notification, main.bicep:892-897), each Basic 5 DTU / 2 GB (main.bicep:907-915) and carrying a service tag equal to the owning container app's, so one tag-grouped cost report splits compute and storage per service (main.bicep:170-174, 903-905), with weekly/monthly/yearly long-term retention on top of Basic-tier PITR (main.bicep:919-935). The three databases that hold an audit-trail table (ADC_Identity, ADC_Conference, ADC_Engagement; Notification has no audited entities) also get a database-level audit policy recording every UPDATE or DELETE on dbo.AuditTrailEntries outside the database (main.bicep:962-966, 984-1001), because the services connect with the server admin login and could otherwise rewrite the ADR-075 trail without a record (main.bicep:940-948). The legacy AtlDevCon database is declared in neither place (main.bicep:871-880) + Azure SQL Server; the same four databases (ADC_Identity, ADC_Conference, ADC_Engagement, ADC_Notification, main.bicep:911-916), each Basic 5 DTU / 2 GB (main.bicep:926-934) and carrying a service tag equal to the owning container app's, so one tag-grouped cost report splits compute and storage per service (main.bicep:170-174, 922-924), with weekly/monthly/yearly long-term retention on top of Basic-tier PITR (main.bicep:938-954). The three databases that hold an audit-trail table (ADC_Identity, ADC_Conference, ADC_Engagement; Notification has no audited entities) also get a database-level audit policy recording every UPDATE or DELETE on dbo.AuditTrailEntries outside the database (main.bicep:981-985, 1003-1020), because the services connect with the server admin login and could otherwise rewrite the ADR-075 trail without a record (main.bicep:959-967). The legacy AtlDevCon database is declared in neither place (main.bicep:890-899) Redis container (persistent) - Azure Managed Redis (Microsoft.Cache/redisEnterprise, Balanced B0, no HA, main.bicep:1364-1375), OSS-cluster policy and volatile-LRU eviction (main.bicep:1383, 1386), injected as ConnectionStrings__redis from Key Vault (main.bicep:1687) + Azure Managed Redis (Microsoft.Cache/redisEnterprise, Balanced B0, no HA, main.bicep:1383-1394), OSS-cluster policy and volatile-LRU eviction (main.bicep:1402, 1405), injected as ConnectionStrings__redis from Key Vault (main.bicep:1708) RabbitMQ container (persistent, management plugin), or the Service Bus emulator container under ADC_BROKER=servicebus - Azure Service Bus (Standard tier, main.bicep:1016-1027; Basic lacks the topics MassTransit needs, main.bicep:1011-1012), with one per-service SAS rule rather than one credential for the whole fleet (main.bicep:1029-1031) + Azure Service Bus (Standard tier, main.bicep:1035-1046; Basic lacks the topics MassTransit needs, main.bicep:1030-1031), with one per-service SAS rule rather than one credential for the whole fleet (main.bicep:1048-1050) MailDev container (fixed ports 1080/1025) - Not provisioned; a real SMTP relay via Smtp__Host / Smtp__Port / Smtp__From (main.bicep:1733 for Identity, 2211 for Notification) + Not provisioned; a real SMTP relay via Smtp__Host / Smtp__Port / Smtp__From (main.bicep:1754 for Identity, 2238 for Notification) MessageBus__Provider=RabbitMq (AppHost default) - MessageBus__Provider=AzureServiceBus (Bicep env var on all four services, main.bicep:1712, 1920, 2060, 2209) + MessageBus__Provider=AzureServiceBus (Bicep env var on all four services, main.bicep:1733, 1943, 2085, 2236) Aspire dashboard (OTLP) @@ -1263,27 +1275,27 @@

    Local-to-cloud parity

    WithSQLServerDataSource injects one connection-string env var - Bicep injects the same one plus DataSources__{Module}__SQLServerMigrationsAssembly and Outbox__DatabaseName (main.bicep:1665-1667) + Bicep injects the same one plus DataSources__{Module}__SQLServerMigrationsAssembly and Outbox__DatabaseName (main.bicep:1686-1688) h2c health probe from the AppHost (WithH2cHealthCheck) - Dedicated HTTP/1.1 probe listener via HealthProbe__Port, 8081 on the three h2c services (main.bicep:1661, 1886, 2024) and 8082 on Notification (main.bicep:2174), because the ACA platform probes speak HTTP/1.1 + Dedicated HTTP/1.1 probe listener via HealthProbe__Port, 8081 on the three h2c services (main.bicep:1682, 1909, 2049) and 8082 on Notification (main.bicep:2201), because the ACA platform probes speak HTTP/1.1 WaitFor gates on /alive; only the UI gates on /health/ready - The ACA probe block splits the same two paths by job: startup and liveness on /alive, readiness on /health/ready, all three against the probe port (main.bicep:1794-1818). Readiness polls every 30 s rather than 10 s, because the DB-aware check issues a SELECT 1 per probe and neither the probe request nor its dependency row is sampled (main.bicep:1788-1793) + The ACA probe block splits the same two paths by job: startup and liveness on /alive, readiness on /health/ready, all three against the probe port (main.bicep:1815-1839). Readiness polls every 30 s rather than 10 s, because the DB-aware check issues a SELECT 1 per probe and neither the probe request nor its dependency row is sampled (main.bicep:1809-1814) Outbox poll interval: framework default 2 s - Outbox__PollingIntervalSeconds=300 on every service (main.bicep:1672, 1893, 2031, 2183); Identity, Conference and Engagement, the three hosts that call AddScheduledJobs, also slow the runner's idle wake to Scheduler__PollingIntervalSeconds=300 (main.bicep:1683, 1901, 2034). Internal commands (ADR-114) deliberately poll faster at InternalCommands__PollingIntervalSeconds=60 (main.bicep:1678), because only a row enrolled in a transaction cannot be signalled + Outbox__PollingIntervalSeconds=300 on every service (main.bicep:1693, 1916, 2056, 2210); Identity, Conference and Engagement, the three hosts that call AddScheduledJobs, also slow the runner's idle wake to Scheduler__PollingIntervalSeconds=300 (main.bicep:1704, 1924, 2059). Internal commands (ADR-114) deliberately poll faster at InternalCommands__PollingIntervalSeconds=60 (main.bicep:1699), because only a row enrolled in a transaction cannot be signalled Telemetry knobs at their defaults (sample everything, all metric families on, probe traces filtered) - Telemetry__TracesSampleRatio=0.25, Telemetry__DisableHttpClientMetrics=true, Telemetry__DisableRuntimeMetrics=true, OTEL_METRIC_EXPORT_INTERVAL=300000 (main.bicep:252-255, 284-291, 299-302). Telemetry__FilterProbeTelemetry is set nowhere, because its default is already the production behavior + Telemetry__TracesSampleRatio=0.25, Telemetry__DisableHttpClientMetrics=true, Telemetry__DisableRuntimeMetrics=true, Telemetry__DisableAspNetCoreMetrics=true, OTEL_METRIC_EXPORT_INTERVAL=300000, plus the distro switch AzureMonitor__EnableLiveMetrics=false (main.bicep:252-255, 284-291, 297-300, 307-310, 318-321). Telemetry__FilterProbeTelemetry is set nowhere, because its default is already the production behavior Gateway: YARP request-routing lines at Information (Gateway/appsettings.json:6) - Logging__LogLevel__Yarp=Warning on the gateway container and no other (main.bicep:267-276, 2341), because YARP's two lines per proxied request duplicated AppRequests and AppDependencies in the console-log stream; Warning keeps the forwarder's error lines + Logging__LogLevel__Yarp=Warning on the gateway container and no other (main.bicep:267-276, 2368), because YARP's two lines per proxied request duplicated AppRequests and AppDependencies in the console-log stream; Warning keeps the forwarder's error lines

    The transport switch (RabbitMq to AzureServiceBus) is entirely environment-driven. No code path @@ -1318,11 +1330,11 @@

    The YARP Gateway's role

    cluster, so adding or repointing a route is an appsettings edit plus a matching entry in RouteMapTests (which pins the whole table), never a redeploy of hand-written MapForwarder calls.

    The route table

    -

    Gateway/appsettings.json:80-260 declares 33 routes across 5 clusters (clusters at - appsettings.json:262-300), every destination an in-cluster service-discovery name over cleartext, never a +

    Gateway/appsettings.json:78-258 declares 33 routes across 5 clusters (clusters at + appsettings.json:260-298), every destination an in-cluster service-discovery name over cleartext, never a public URL:

      -
    • Eight Identity routes to cluster identity (http://identity, appsettings.json:90-135, 263-271): +
    • Eight Identity routes to cluster identity (http://identity, appsettings.json:88-133, 261-269): /Auth/login and /Auth/register (90-103), the /Auth/{**catch-all} that carries everything else (106-110), /Users (111-115), /UserClaims (116-120), /Admin/Users (121-125), /Admin/Roles (126-130) and /.well-known/* (131-135). Only the two credential-submission routes carry @@ -1332,16 +1344,16 @@

      The route table

      /CategoryItems, /SessionSpeakers, /EventSpeakers, /SessionCategoryItems, /SessionQuestionAnswers, /EventQuestionAnswers, /SpeakerCategoryItems, /SessionSelection, /Questions, /Sponsors, /Partners, /Activities, /SessionAssets) to cluster conference - (appsettings.json:136-225, 272-280). /Partners is the newest of them - (conference-partners, appsettings.json:211-215), and it is the shape every new aggregate takes at the + (appsettings.json:134-223, 270-278). /Partners is the newest of them + (conference-partners, appsettings.json:209-213), and it is the shape every new aggregate takes at the edge: one prefix route, "AuthorizationPolicy": "anonymous", the same conference cluster, no forwarder config of its own, and a matching row in the test that pins the table (Tests/Hosts/MMCA.ADC.Gateway.Tests/RouteMapTests.cs:162). Nothing in the gateway image or its Program.cs changed to carry it.
    • Five Engagement prefixes (/Bookmarks, /CheckIns, /LivePolls, /Points, /SessionQuestions) to - cluster engagement (appsettings.json:226-250, 281-289).
    • + cluster engagement (appsettings.json:224-248, 279-287).
    • /Notifications to cluster notification-rest and /hubs/* to cluster notification-hub - (appsettings.json:251-260, 290-299). Both point at the same http://notification destination; they are + (appsettings.json:249-258, 288-297). Both point at the same http://notification destination; they are two clusters because a cluster is what carries the forwarder request config, and these two need different ones.
    @@ -1352,12 +1364,12 @@

    The route table

    the ClientIp partition keyed every user's refresh on the UI replica's own container IP: one shared 30-per-minute bucket for the whole site. ADR-019 deliberately leaves RefreshAsync unthrottled at the service layer for exactly that shared-IP reason, and the gateway policy was silently undoing it - (appsettings.json:81-89). Login and register are split out and keep the tight policy; everything else + (appsettings.json:79-87). Login and register are split out and keep the tight policy; everything else under /Auth (refresh, logout, forgot/reset password, the OAuth callbacks) stays on the edge global - limiter alone (appsettings.json:104-105).

    + limiter alone (appsettings.json:102-103).

    Every route states "AuthorizationPolicy": "anonymous". A proxied route carries no authorization metadata of its own, so the anonymous posture of the whole table used to be an absence rather than a - statement, with nothing for a reviewer or a fitness test to read (SEC-Common-16, appsettings.json:74-79, + statement, with nothing for a reviewer or a fitness test to read (SEC-Common-16, appsettings.json:72-77, Gateway/Program.cs:98-106). Declaring it per route is what makes a route added without a policy fail closed at the edge. builder.Services.AddAuthorization() (Program.cs:112) plus app.UseAuthorization() (Program.cs:218) evaluate it, with no UseAuthentication above them on purpose: the gateway @@ -1370,7 +1382,7 @@

    Three forwarder profiles, on two a comment enumerates them, Gateway/Program.cs:15-31).

    The identity, conference and engagement clusters each state "Version": "2.0" with "VersionPolicy": "RequestVersionExact" in their own HttpRequest block - (appsettings.json:267-270, 276-279, 285-288). Exact is load-bearing: on cleartext there is no ALPN to + (appsettings.json:265-268, 274-277, 283-286). Exact is load-bearing: on cleartext there is no ALPN to negotiate, so RequestVersionOrLower silently downgrades to HTTP/1.1 and the Http2-only backend rejects it. That pair stays per-cluster rather than moving into the shared defaults, because which clusters speak h2c is a per-cluster fact, not a default (Program.cs:42-43).

    @@ -1407,13 +1419,15 @@

    What AddMmcaGateway adds o switch to flip (Program.cs:45-47).
  • Passive destination health checks on every cluster that declares none, so YARP ejects a failing destination instead of continuing to balance onto it, and it is free: it watches the responses the - gateway is already forwarding (Program.cs:140-142, defaults at GatewaySettings.cs:121-122). ADC then - turns the active probe on in configuration, at a 30 second interval, with the path (/alive) and - timeout (5 s) coming from the package defaults (appsettings.json:58-62). The comment above it - (appsettings.json:52-57) explains why it pays for out-of-band traffic: passive checks only demote a - destination after real traffic has already failed against it, so a restarting service keeps absorbing - requests until enough of them error, whereas an active probe takes a destination out of rotation - before a client request lands on it and puts it back once it answers again.
  • + gateway is already forwarding (Program.cs:140-142, defaults at GatewaySettings.cs:121-122). ADC + keeps the active probe explicitly off in configuration + (MmcaGateway:HealthCheckDefaults:Active:Enabled = false, appsettings.json:57-60), and the comment + above it (appsettings.json:52-56) gives two reasons. Every cluster fronts exactly one Container Apps + internal address, so an active probe could only ever eject the sole destination, turning a slow + request into a 503, while the platform already routes only to ready replicas. And each probe is an + HTTP request that bills an otherwise idle downstream replica at the Container Apps active rate, eight + times the idle rate ([Rubric §31, Cost and FinOps]). Passive checks cost no extra traffic, so they + are the only destination health signal the gateway runs.
  • X-MMCA-Route / X-MMCA-Cluster trace headers on every proxied request, with any inbound value stripped first so a downstream can trust them (GatewaySettings.cs:181-184), plus the named per-route rate-limiter policies routes reference by name.
  • @@ -1453,7 +1467,7 @@

    The gateway's own pipeline

    long-lived and its negotiate/reconnect traffic must not be throttled (appsettings.json:21-25, defaults in MMCA.Common.Aspire/Gateway/GatewayRateLimitingSettings.cs:59, 63, 73); health probes and /.well-known are always exempt (Program.cs:69-73). The auth-tight route policy is - 30 requests per 60 seconds partitioned on client IP with no queue (appsettings.json:64-70).

    + 30 requests per 60 seconds partitioned on client IP with no queue (appsettings.json:62-68).

    One exemption is configured but inert until a secret arrives. GatewayRateLimiting:TrustedCallerHeaderName names the header (X-Internal-Caller-Key, appsettings.json:32) and only the name: the secret itself arrives as GatewayRateLimiting__TrustedCallerSecret from Key Vault, and the exemption is off until it @@ -1466,9 +1480,9 @@

    The gateway's own pipeline

    starve Container Apps' own probe), and without a cache that exemption turns one anonymous request into four live internal GETs against a 0.25 vCPU gateway. MMCA.Common.Aspire now serves both paths from a single-flight CachedHealthReportProvider whose window is HealthChecks:CacheSeconds, pinned to 10 here - (appsettings.json:40-41, rationale at Program.cs:90-96). Ten seconds is one third of the 30 second - readiness period on the gateway's Container Apps probe, so a real outage is still seen within one probe - interval (appsettings.json:34-39).

    + (appsettings.json:40-41, rationale at Program.cs:90-96). The Container Apps readiness probe does not read + this report (it targets /alive, see below), so its consumers are the availability web test and + operators, and ten seconds keeps a real outage visible almost immediately (appsettings.json:34-39).

    Readiness that reflects the edge's job

    AddGatewayDownstreamHealthChecks("identity", "conference", "engagement", "notification") (Gateway/Program.cs:88) registers one downstream-{name} check per service, each GETting /alive @@ -1476,19 +1490,29 @@

    Readiness that reflects the edge& (MMCA.Common.Aspire/Gateway/GatewayHealthCheckExtensions.cs:130, 211). Tagging matters: a downstream outage pulls the gateway out of the load balancer without ever failing liveness, because restarting the gateway fixes nothing about a downstream being down (Gateway/Program.cs:76-79).

    +

    Production does not point the platform's readiness probe at that aggregate. The gateway container app + probes /alive for startup, liveness and readiness, all on port 8080 (MMCA.ADC/infra/main.bicep:2408-2434), + and the comment on the readiness entry records why (main.bicep:2423-2429). /health/ready fans out to + every downstream's /alive, so a 30 second probe there was an HTTP request on every service every 30 + seconds, which alone keeps an idle replica off the Container Apps idle rate; and one failed downstream + marked the only gateway replica unready and took the whole site down, healthy routes included. The + full aggregate stays on /health for the availability web test and its alert. No cold-revision + protection is lost, because the gateway registers no JwtBearer scheme, so the OIDC metadata warm-up task + AddServiceDefaults would otherwise wait on has nothing to fetch. This is the gateway's exception to the + service pattern in the parity table, where readiness stays on /health/ready.

    One call covers all four because the probe no longer needs to be told which protocol each downstream speaks. DownstreamProbeVersion.Auto is the default (GatewayHealthCheckExtensions.cs:59); it negotiates per downstream and latches the answer for the life of the process, so the three h2c REST services latch HTTP/2 on their first poll and Notification answers HTTP_1_1_REQUIRED once and latches HTTP/1.1. The fallback is a one-time cost per downstream, not a per-poll one (Gateway/Program.cs:80-87).

    -

    That active probing has an observable cost, and the gateway's appsettings.json pays it down at the - logging layer rather than by probing less (appsettings.json:2-16): three categories (Polly retry +

    Whatever still probes that aggregate has an observable cost, and the gateway's appsettings.json pays it + down at the logging layer (appsettings.json:2-15): at Information three categories (Polly retry attempts, the per-request System.Net.Http.HttpClient logs for the gateway-downstream-* clients, and - Yarp.ReverseProxy.Health) wrote roughly 300k stdout lines a day for traffic no user made, so they sit - at Warning while YARP's own request-routing lines stay at Information (appsettings.json:6). Production - goes one step further and caps YARP too: the Bicep sets Logging__LogLevel__Yarp=Warning on the gateway - container and no other host (main.bicep:267-276, 2341), because at Information YARP writes two lines per + the YARP health monitor, Yarp.ReverseProxy.Health) write a line per probe for traffic no user made, so + they sit at Warning while YARP's own request-routing lines stay at Information (appsettings.json:6, + 13). Production goes one step further and caps YARP too: the Bicep sets Logging__LogLevel__Yarp=Warning + on the gateway container and no other host (main.bicep:267-276, 2368), because at Information YARP writes two lines per proxied request that Container Apps ships to Log Analytics, measured at about 177k lines and 77 MB a week, the largest console-log stream in the workspace and all of it already recorded as AppRequests and AppDependencies. Warning keeps the forwarder's error lines, so a failed proxy hop still logs.

    @@ -1618,11 +1642,11 @@

    The AppHost composition smoke test

    It needs a Docker daemon and it is the slowest thing in the repo per assertion, so per ADR-098 it is probational and non-gating. The apphost-smoke job in the nightly cross-service-tests.yml runs it with - continue-on-error: true and a 30-minute timeout (.github/workflows/cross-service-tests.yml:204-209), - trusts the dev certificate first (line 271), sets the MMCA_APPHOST_TESTS: "1" opt-in (line 285), and + continue-on-error: true and a 30-minute timeout (.github/workflows/cross-service-tests.yml:208-213), + trusts the dev certificate first (line 275), sets the MMCA_APPHOST_TESTS: "1" opt-in (line 289), and passes --minimum-expected-tests 1 so a run where every test silently skipped cannot pass (lines - 286-288). The cross-service-freshness deploy gate does not look at this job at all, so it can never block - a deploy (lines 194-199).

    + 290-292). The cross-service-freshness deploy gate does not look at this job at all, so it can never block + a deploy (lines 198-203). Before building, the job also asserts that every MMCA.Common.* entry in the project's committed packages.lock.json resolves the single central pin (lines 229-265), because that out-of-solution lock is covered by no gating restore and a pin bump that forgot it once left it a version behind while the job stayed green (lines 230-233). It cannot simply restore --locked-mode like the two gating tiers above it (lines 96-99, 175-176): an Aspire AppHost lock carries a RID-specific Aspire.Dashboard.Sdk.<rid> entry, so locked mode fails with NU1004 on the Linux runner regardless of drift (lines 235-238).

    [Rubric §14, Testability and Test Strategy] assesses whether the test suite covers the risks the system actually carries. An orchestration file is a genuine failure surface with no compiler covering it, and the answer here is proportionate: one shared stack, one narrow claim per wiring contract, skips that name @@ -1659,7 +1683,7 @@

    Rubric category index for this c §17 DevOps & Deployment - Persistent container lifetimes shared by the inner loop and the Aspire-driven E2E CI run; one Dockerfile per deployable behind the six-way build-images matrix with per-image dirty gating (deploy.yml:1186-1207); the parity table's environment-driven local-to-cloud mapping + Persistent container lifetimes shared by the inner loop and the Aspire-driven E2E CI run; one Dockerfile per deployable behind the six-way build-images matrix with per-image dirty gating (deploy.yml:1095-1116); the parity table's environment-driven local-to-cloud mapping §29 Resilience & Business Continuity @@ -1667,11 +1691,11 @@

    Rubric category index for this c §31 Cost / FinOps - The four telemetry knobs and the metric export interval; OutboxPollFilterProcessor and ProbeTelemetryFilterProcessor suppressing the two highest-volume classes of span nobody asked for; the gateway's probe-log trim (Gateway/appsettings.json:2-16) and its production-only YARP log floor (main.bicep:267-276, 2341); the 30 s readiness cadence in the ACA probe block (main.bicep:1788-1793) + The five telemetry knobs, the Live Metrics switch (main.bicep:307-310) and the metric export interval; OutboxPollFilterProcessor and ProbeTelemetryFilterProcessor suppressing the two highest-volume classes of span nobody asked for; the gateway's probe-log trim (Gateway/appsettings.json:2-16) and its production-only YARP log floor (main.bicep:267-276, 2368); the 30 s readiness cadence in the ACA probe block (main.bicep:1809-1814); the gateway's YARP active probe switched off (Gateway/appsettings.json:52-60) and its readiness probe on /alive rather than the downstream fan-out (main.bicep:2423-2432) §32 Dependency & Supply-Chain - Digest-pinned aspnet and sdk base images in all six Dockerfiles (Gateway.Dockerfile:4, 10); --locked-mode restore in five of the six (Gateway.Dockerfile:32, the UI's documented exception at UI.Web.Dockerfile:36-38); the per-image Trivy scan of the exact tag being rolled out, non-gating today (deploy.yml:1297-1305) + Digest-pinned aspnet and sdk base images in all six Dockerfiles (Gateway.Dockerfile:4, 10); --locked-mode restore in five of the six (Gateway.Dockerfile:32, the UI's documented exception at UI.Web.Dockerfile:36-38); the per-image Trivy scan of the exact tag being rolled out, non-gating today (deploy.yml:1206-1214) §33 Developer Experience @@ -1692,13 +1716,12 @@

    Not determinable from source

    in MMCA.Common.Infrastructure, outside the files this chapter walks. The emulator resource's own doc comment (ServiceBusEmulatorResource.cs:18-24) is the only statement of that contract cited here.
  • Every ingestion figure quoted in the cost sections (roughly 65% of AppMetrics, about 290 MB of a - 500 MB daily stream, 100% of AppRequests rows from probes, the gateway's roughly 300k stdout lines a - day, the YARP forwarder's roughly 177k console lines a week) comes from the inline comment that records the measurement, not from a workspace query this - chapter ran: MMCA.ADC/infra/main.bicep:267-272 and 278-283, - MMCA.Common.Aspire/Telemetry/ProbeTelemetryFilter.cs:8-11 - and MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/appsettings.json:2-6.
  • + 500 MB daily stream, 100% of AppRequests rows from probes, the ASP.NET Core meter family at 73% of + ingestion, Live Metrics holding 64% of vCPU-seconds on the active rate, the YARP forwarder's roughly 177k console lines a week) comes from the inline comment that records the measurement, not from a workspace query this + chapter ran: MMCA.ADC/infra/main.bicep:267-272, 278-283, 293-296 and 302-306, + and MMCA.Common.Aspire/Telemetry/ProbeTelemetryFilter.cs:8-11.
  • That the AtlDevCon database is actually gone from the Azure SQL server is asserted by the template's - comment (main.bicep:871-880), which is explicit that Incremental-mode Bicep never deleted it and an + comment (main.bicep:890-899), which is explicit that Incremental-mode Bicep never deleted it and an operator did, after the template stopped declaring it. The template can only prove the declaration is absent, not the server's current state.
  • diff --git a/docs/onboarding/devops-cicd.html b/docs/onboarding/devops-cicd.html index e667ad4a..d2da29e2 100644 --- a/docs/onboarding/devops-cicd.html +++ b/docs/onboarding/devops-cicd.html @@ -177,7 +177,7 @@

    What it is

    (ci.yml:174), a ui-e2e cross-browser matrix for real-browser accessibility and render-smoke testing (ci.yml:245), a performance-smoke benchmark gate (ci.yml:352), a coverage job that merges the coverage tiers and enforces a floor (ci.yml:399), three canaries that catch failure modes the - solution build cannot see: consumer-source-build (ci.yml:469), package-consumption + solution build cannot see: consumer-source-build (ci.yml:475), package-consumption (ci.yml:669) and sample-deployment-validate (ci.yml:795), and three engine-or-orchestrator tiers for the components whose behavior only a real server can falsify: redis-integration (ci.yml:813), postgresql-integration (ci.yml:854) and the advisory apphost-testing (ci.yml:889).

    @@ -232,7 +232,11 @@

    Job: changes, the

    Job: build-and-test

    Runs on: ubuntu-latest (ci.yml:89). The Ubuntu runner matters: the Linux file system is case-sensitive, so path-casing bugs that Windows masks are caught in CI. This is a deliberate choice - documented in MMCA.Common/CLAUDE.md ("CI runs on Ubuntu, file paths are case-sensitive").

    + documented in MMCA.Common/CLAUDE.md ("CI runs on Ubuntu, file paths are case-sensitive"). + The job is bounded at timeout-minutes: 15 (ci.yml:92), about twice the slowest of four recent code PR + runs per the comment (ci.yml:90-91), so a hung restore or test host fails the job instead of holding a + runner for GitHub's 6-hour default. The coverage job gets the same treatment at 5 minutes + (ci.yml:405-408).

    Step 1, Checkout with full history (ci.yml:91-94):

    - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
       with:
    @@ -484,7 +488,7 @@ 

    Job: coverage

    The three canaries, and why a framework needs them

    The remaining jobs all exist for the same reason: a green solution build does not prove the framework works for anyone who is not the framework.

    -

    consumer-source-build (ci.yml:469-657) is a cross-repo pre-merge canary, and it now proves two +

    consumer-source-build (ci.yml:475-691) is a cross-repo pre-merge canary, and it now proves two different things. It checks out MMCA.Helpdesk as a sibling directory (ci.yml:486-525) and builds and tests it against this PR's framework source, so a breaking public-API change fails here rather than surfacing after a release and a lockstep sweep. Helpdesk is the ideal canary precisely because it is @@ -501,9 +505,16 @@

    The three canaries, a against MMCA.Common main, so neither repo can adapt first while the canary pins the other's main: a mutual deadlock, resolved by letting one PR name its counterpart branch (ci.yml:493-497).

    Second, the job runs the consumer's real EF migrations against a real SQL Server. An ephemeral - mcr.microsoft.com/mssql/server:2022-latest container starts before the build so it warms up while the - solution compiles (ci.yml:547-555), go-sqlcmd is installed as a single static binary rather than the - mssql-tools deb (ci.yml:573-577), a 30 x 5s poll waits on a real SELECT 1 rather than on Docker's + SQL Server container starts before the build so it warms up while the solution compiles + (ci.yml:557-566). Its image is the job-level SQLSERVER_IMAGE, pinned to one cumulative update, + mcr.microsoft.com/mssql/server:2022-CU27-ubuntu-22.04, rather than 2022-latest (ci.yml:490-495): + this is a required gate, so a new CU must not change what it runs without a reviewed diff, and the + comment ties the tag to the sqlserver-integration tier, which pins the same one ("bump both"). + go-sqlcmd is installed as a single static binary rather than the mssql-tools deb, and is itself pinned + (ci.yml:582-601): a fixed SQLCMD_VERSION (v1.10.0) is downloaded to $RUNNER_TEMP and checked + against SQLCMD_SHA256 with sha256sum -c before sudo tar extracts it, because piping + releases/latest straight into a root tar ran whatever the newest upload was, unchecked. Version and + digest are bumped together. A 30 x 5s poll waits on a real SELECT 1 rather than on Docker's notion of "running" (ci.yml:581-594), and dotnet ef database update applies the Tickets migrations with the same dotnet-ef 10.0.8 the consumers deploy with (ci.yml:598-623). The reason is stated in the comment (ci.yml:602-610): migrations add and the model-drift gate never open a connection, so @@ -514,8 +525,8 @@

    The three canaries, a (Tickets.Ticket) and a framework table (dbo.OutboxMessages) exist. The framework table is the half that belongs to MMCA.Common, so a change that stops the framework's own tables reaching a consumer's schema fails right here rather than at a consumer's deploy. The job's timeout was raised to 30 minutes to - pay for the container, the poll and the apply (ci.yml:473-475), and the throwaway SA password is inline - rather than a secret so the gate still runs from a fork (ci.yml:476-480).

    + pay for the container, the poll and the apply (ci.yml:479-481), and the throwaway SA password is inline + rather than a secret so the gate still runs from a fork (ci.yml:483-486).

    [Rubric §8, Data Architecture] is served in a way no build-only canary can reach: the framework's migration path is exercised end to end, on a real engine, by a real consumer.

    package-consumption (ci.yml:669-755) closes the gap that the previous job cannot: source-mode @@ -556,40 +567,59 @@

    The three canaries, a A real what-if or deploy stays a consumer-side concern, since ADC's and Store's deploy.yml are the production-proven versions.

    Job: redis-integration

    -

    The last job (ci.yml:813-852) runs MMCA.Common.Infrastructure.Redis.Tests against a real Redis via +

    redis-integration (ci.yml:837-879) runs MMCA.Common.Infrastructure.Redis.Tests against a real Redis via Testcontainers, which Ubuntu runners support with no extra setup since they ship a Docker daemon. Like the E2E and benchmark projects it lives outside MMCA.Common.slnx so the fast solution-wide unit loop never requires Docker, and is therefore built and run by path.

    -

    The comment (ci.yml:818-822) states the falsifiability argument better than a summary can: - DistributedCacheService is the one place where the storage format matters, and a +

    The comment (ci.yml:842-846) states the falsifiability argument better than a summary can: + DistributedCacheService is the one place where the storage format matters, and a Mock<IDistributedCache> cannot express it. Redis keys are typed, so a counter written as a string and read back as a hash round-trips perfectly against a mock and answers WRONGTYPE against a server. A test that cannot fail against a mock is not a test of the thing you care about.

    -

    Its heavy step is code-guarded like every other job (ci.yml:845-852) so a docs-only PR does not pull a +

    Its heavy step is code-guarded like every other job (ci.yml:869-879) so a docs-only PR does not pull a Redis image, while the job itself still runs and posts its context green, keeping it safe to add to branch - protection.

    -

    Jobs: postgresql-integration and apphost-testing

    -

    postgresql-integration (ci.yml:854-887) is the Redis argument applied to the second database engine. + protection. The test step carries --minimum-expected-tests 15 (ci.yml:874-879), and the comment says + why the number is exactly 15: it is the [Fact] count in the project (5 DistributedCacheService plus 10 + HybridCacheService), so a tier that silently discovers fewer tests fails, and adding a test means raising + the floor in the same PR. The same exact-count floor applies to the two database tiers below.

    +

    Jobs: postgresql-integration, sqlserver-integration and apphost-testing

    +

    postgresql-integration (ci.yml:881-916) is the Redis argument applied to the second database engine. The PostgreSQL provider is the one place where the SQL the framework emits matters, and a model - assertion cannot express it: the comment is specific about the failure class (ci.yml:859-864), a + assertion cannot express it: the comment is specific about the failure class (ci.yml:886-891), a partial-index predicate written the SQL Server way ([ProcessedOn] IS NULL), a soft-delete predicate comparing a boolean to 0, and a DateTime whose Kind is not UTC all build a perfectly valid EF model and are rejected by the server. The job runs MMCA.Common.Infrastructure.PostgreSQL.Tests against a real - PostgreSQL over Testcontainers (ci.yml:880-887), built and run by path for the same reason the Redis - tier is: the project sits outside MMCA.Common.slnx so the fast unit loop never requires Docker.

    -

    apphost-testing (ci.yml:889-946) is the only tier that starts a real orchestrator. It boots the sample + PostgreSQL over Testcontainers (ci.yml:907-916), built and run by path for the same reason the Redis + tier is: the project sits outside MMCA.Common.slnx so the fast unit loop never requires Docker. Its floor + is --minimum-expected-tests 7, the [Fact] count in PostgreSQLPersistenceTests.cs (ci.yml:913-916).

    +

    sqlserver-integration (ci.yml:918-956) completes the set for the default engine. The comment + (ci.yml:925-932) names what neither a mock nor SQLite can express on SQL Server, which is session + state and server-generated values: SET IDENTITY_INSERT holds only on the session that ran it, a + rowversion is issued by the server on every write, and the outbox row must reach the database in the + same SaveChanges as its aggregate. The job runs the shipped + SQLServerDbContext and + DbContextFactory against a real SQL Server over + Testcontainers. Its timeout is 20 minutes, the PostgreSQL tier's 15 plus headroom for the larger image pull + and slower engine start (ci.yml:921-924), and the image tag is the same pinned 2022-CU27-ubuntu-22.04 + the consumer-source-build canary uses, which is why the canary's comment says to bump both + (ci.yml:490-495). The heavy step is code-guarded with the job still posting green (ci.yml:948-951), + the project is outside MMCA.Common.slnx and run by path, and the floor is + --minimum-expected-tests 3, the [Fact] count in SQLServerPersistenceTests.cs (ci.yml:952-956). + It is not a required check yet: the comment records that promoting it is a branch-protection setting, + not a workflow change (ci.yml:931-932).

    +

    apphost-testing (ci.yml:958-1015) is the only tier that starts a real orchestrator. It boots the sample AppHost through Aspire.Hosting.Testing and closes the one layer nothing else executes, the AppHost - wiring itself (ci.yml:899-902): the solution build never runs an AppHost, and every in-process test tier + wiring itself (ci.yml:968-971): the solution build never runs an AppHost, and every in-process test tier boots hosts directly through WebApplicationFactory, bypassing the orchestration, so a renamed resource, an unresolvable reference or a WaitFor cycle is invisible everywhere else. Three properties are worth - carrying away. It is advisory, continue-on-error: true (ci.yml:907), because it is the slowest + carrying away. It is advisory, continue-on-error: true (ci.yml:976), because it is the slowest thing in the repository per assertion and its failure modes on a shared runner are not proven yet; the - comment states the exit condition rather than leaving it open (ci.yml:894-897), promote it once it has a + comment states the exit condition rather than leaving it open (ci.yml:963-966), promote it once it has a green streak, delete it if it proves to be a flake generator. It trusts the ASP.NET Core development - certificate as an explicit job step (ci.yml:923-932), because a resource launched with the https + certificate as an explicit job step (ci.yml:992-1000), because a resource launched with the https profile answers its health probe over TLS terminated by that certificate: untrusted on a fresh runner, every probe fails with UntrustedRoot, the resource never turns healthy, and every WaitFor edge into it - waits out the budget. And the test step opts in through MMCA_APPHOST_TESTS (ci.yml:933-946), the + waits out the budget. And the test step opts in through MMCA_APPHOST_TESTS (ci.yml:1001-1015), the environment gate the fixture reads, so a developer machine and every other CI job skip the collection with a named reason instead of paying for an orchestrator.

    [Rubric §14, Testability & Test Strategy] is served by both jobs the way the Redis tier serves it: each @@ -654,11 +684,15 @@

    Job: publish

    token only has write access to Packages, not to repo contents, issues, or deployments, and the public registry is reached with no stored API key at all.

    The release environment and the merged-main assertion. Publishing to nuget.org is irreversible, a - version can never be withdrawn (ADR-053), so the job declares environment: release (release.yml:11-15) + version can never be withdrawn (ADR-053), so the job declares environment: release (release.yml:15-18) and waits on that environment's protection rules (a required reviewer, and a deployment policy limited to v* tags) before it runs at all. The first step after checkout then refuses to publish a tag that is not reachable from origin/main (release.yml:35-46): it fetches the branch tip and fails unless - git merge-base --is-ancestor places the tagged commit on merged main. A v* tag is the one ref pushed + git merge-base --is-ancestor places the tagged commit on merged main. The job is bounded at + timeout-minutes: 15 (release.yml:12-14), about twice the slowest recent tag run, so a hung restore, + test or push fails instead of holding a runner for six hours. publish-maui deliberately keeps 40 + minutes (release.yml:146-149): it publishes after the main package set is already on nuget.org, so a + timeout there would leave a half-published release. A v* tag is the one ref pushed directly, outside the branch-protection pull-request flow, and this workflow re-runs only restore, build, test and SBOM, so the ancestry check is what makes the checks that ran on the merged pull request (the FACTS drift gate, the vulnerability audit, the Helpdesk consumer canary, the package-consumption canary, @@ -676,18 +710,24 @@

    Job: publish

    GITHUB_REF_NAME is the full tag name (e.g. v1.52.0). The #v parameter expansion strips the leading v, yielding 1.52.0. This string is then passed to the build and pack steps as an explicit version override.

    -

    Step 5, Build with explicit version (release.yml:69-70):

    +

    Step 5, Build with explicit version (release.yml:72-73):

    dotnet build MMCA.Common.slnx -c Release --no-restore -p:MinVerSkip=true -p:Version=${{ steps.version.outputs.VERSION }}

    -p:MinVerSkip=true disables MinVer's git-tag-based version derivation and -p:Version=... injects the tag-derived version directly. This pattern avoids a subtle race: if MinVer ran here, it would derive the version from the tag, which should be the same value, but in edge cases (e.g. detached HEAD, retagged commit) the two sources could diverge. Making the version explicit from the start removes the ambiguity.

    -

    Step 6, Test (release.yml:72-73):

    -
    dotnet test --solution MMCA.Common.slnx -c Release --no-build
    -

    Tests run again (no --minimum-expected-tests floor here, the release workflow is not the primary test - gate; CI already covered this). This is a belt-and-suspenders pass to ensure the tagged commit is green - before packaging.

    -

    Step 7, Pack (release.yml:75-76):

    +

    Step 5b, Audit dependencies (release.yml:75-83): the same + ./.github/actions/nuget-vulnerability-audit composite action that ci.yml build-and-test runs, with + the same accept-list (NuGetAuditSuppress in Directory.Build.props) and the same fail-closed + behavior, re-run on the tag build. The comment (release.yml:75-78) gives the reason: the graph that + gets packed is restored from the lock files here, and an advisory published between the PR run and the + tag must stop a nuget.org push that can never be withdrawn.

    +

    Step 6, Test (release.yml:85-87):

    +
    dotnet test --solution MMCA.Common.slnx -c Release --no-build --minimum-expected-tests 2000
    +

    Tests run again on the tagged commit, with the same 2000-test floor as ci.yml build-and-test. The + comment (release.yml:86-87) states why the floor is repeated here: a discovery or filter regression that + silently drops thousands of tests must fail the release, not publish behind a near-empty green run.

    +

    Step 7, Pack (release.yml:89-90):

    dotnet pack MMCA.Common.slnx -c Release --no-build -o ./nupkgs -p:MinVerSkip=true -p:PackageVersion=${{ steps.version.outputs.VERSION }}

    dotnet pack over the entire solution packs every packable project it contains (Source/**) in one command. -p:PackageVersion sets the NuGet package version metadata. -o ./nupkgs collects all @@ -2129,11 +2169,18 @@

    The automated Claude
  • Action pinning. Both repositories pin the third-party action to a commit SHA (MMCA.ADC/.github/workflows/claude-code-review.yml:41-43, MMCA.ADC/.github/workflows/claude.yml:35-37, - MMCA.Common/.github/workflows/claude-code-review.yml:41, - MMCA.Common/.github/workflows/claude.yml:35), with the comment stating the supply-chain argument: a + MMCA.Common/.github/workflows/claude-code-review.yml:45, + MMCA.Common/.github/workflows/claude.yml:39), with the comment stating the supply-chain argument: a mutable tag can be repointed at malicious code, a SHA cannot, and Dependabot's github-actions - ecosystem bumps it. The two sit on different revisions of the same action, the expected steady state - when each repository's Dependabot bumps it independently.
  • + ecosystem bumps it. Common's pin is annotated v1.0.235, ADC's only v1: the two sit on different + revisions of the same action, the expected steady state when each repository's Dependabot bumps it + independently. +
  • Checkout credentials. Common's checkout step sets persist-credentials: false + (MMCA.Common/.github/workflows/claude-code-review.yml:36-40, + MMCA.Common/.github/workflows/claude.yml:30-34), and the comment says why nothing is lost: the + action removes the checkout token anyway and authenticates git with its own app token, and the workflow + token is read-only in any case. That brings the Claude pair in line with every other Common job, none of + which leaves a token on disk.
  • [Rubric §34, Architecture Governance & Documentation] is served: with a single maintainer and no second human reviewer, an automated reviewer on every code PR is what keeps "reviewed" from meaning "self-merged diff --git a/docs/onboarding/devops-iac.html b/docs/onboarding/devops-iac.html index dce46703..0c5dd573 100644 --- a/docs/onboarding/devops-iac.html +++ b/docs/onboarding/devops-iac.html @@ -539,7 +539,7 @@

    infra/main.bic

    main.bicep declares every application-layer Azure resource: Application Insights, five SLO scheduled query rules (one of them the AI-scoring token ceiling, main.bicep:420-432) and their action group, three operational scheduled query rules, a log-ingestion-cap rule, a Gateway - availability web test and its severity-1 alert, a saved SLO workbook (main.bicep:704-724), the + availability web test and its severity-1 alert, a saved SLO workbook (main.bicep:721-742), the monthly cost budget, SQL Server with the four per-service databases, Service Bus, references to the manually provisioned Notification Hub, the blob storage account with its two declared containers (public avatars and the private DataProtection key ring), an Azure Managed Redis instance, the @@ -663,46 +663,65 @@

    Application Insights (main. APPLICATIONINSIGHTS_CONNECTION_STRING is present (main.bicep:201-205 comment), so the Common framework automatically routes OpenTelemetry spans, logs, and metrics to Azure Monitor in production with no service-level code change.

    -

    Five more shared env entries ride along with the connection string on every app, and all of them - are cost controls on a pay-per-GB workspace:

    +

    Seven more shared env entries ride along with the connection string on every app, and all of them + are cost controls, six on the pay-per-GB workspace and one on the Container Apps compute bill:

      -
    • Telemetry__TracesSampleRatio: '0.25' (main.bicep:230-233), head-based trace sampling that keeps +
    • Telemetry__TracesSampleRatio: '0.25' (main.bicep:252-255), head-based trace sampling that keeps 25% of traces. ParentBased sampling in MMCA.Common.Aspire keeps a sampled-in trace intact across service boundaries, so a kept trace is still end-to-end rather than a fragment.
    • -
    • Logging__OpenTelemetry__LogLevel__Default: 'Warning' (main.bicep:241-244), the floor for what the +
    • Logging__OpenTelemetry__LogLevel__Default: 'Warning' (main.bicep:263-266), the floor for what the OpenTelemetry logging provider ships to Azure Monitor. Serilog still writes Information to stdout (container logs), but only Warning and above bills against the workspace. The value is set explicitly because OpenTelemetry is the ProviderAlias of OpenTelemetryLoggerProvider, so the key gates that provider only, and because the service hosts register Serilog as one provider alongside OpenTelemetry instead of calling UseSerilog(), which would replace the ILoggerFactory and drop every application log line before it could reach App Insights.
    • -
    • Telemetry__DisableHttpClientMetrics: 'true' (main.bicep:252-255) and - Telemetry__DisableRuntimeMetrics: 'true' (main.bicep:256-259), which drop the two +
    • Telemetry__DisableHttpClientMetrics: 'true' (main.bicep:284-287) and + Telemetry__DisableRuntimeMetrics: 'true' (main.bicep:288-291), which drop the two highest-volume instrument groups from the AppMetrics stream. The comment records the - measurement that motivated them (main.bicep:246-251): the http.client.* connection gauges + measurement that motivated them (main.bicep:278-283): the http.client.* connection gauges plus the dotnet.* runtime instruments were about 65% of AppMetrics ingestion between 2026-08-03 and 2026-08-09, roughly 290 MB/day of a roughly 500 MB/day stream, while http.server.request.duration and the MMCA.Common meters carry the operational signal. Both keys are read by MMCA.Common.Aspire's ConfigureOpenTelemetry, and the outbound-dependency latency the client metrics would have shown is still captured as (sampled) AppDependencies traces, so this trims volume rather than visibility.
    • -
    • OTEL_METRIC_EXPORT_INTERVAL: '300000' (main.bicep:267-270) is the second stage of the same - cost control, and it works on cadence rather than on instrument selection. AppMetrics remained +
    • Telemetry__DisableAspNetCoreMetrics: 'true' (main.bicep:297-300) drops a third instrument + group, and the largest one: the ASP.NET Core meter family (http.server.*, kestrel.*, + aspnetcore.*, signalr.server.*) was 73% of workspace ingestion between 2026-09-22 and + 2026-09-28, and no alert reads it (main.bicep:293-296). Request latency and failure alerting + queries AppRequests, the request telemetry rather than the metric stream, so this trims volume + without blinding an alert; it does mean the http.server.request.duration histogram named in the + bullet above no longer ships either. The key is read by MMCA.Common.Aspire's cost knob, which + drops the whole Microsoft.AspNetCore.* meter family through a View + (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.Telemetry.cs:344-354).
    • +
    • AzureMonitor__EnableLiveMetrics: 'false' (main.bicep:307-310) is the one entry aimed at + compute rather than ingestion. Live Metrics is on by default in the Azure Monitor distro and keeps + every replica talking to the Live Metrics service whether or not anyone has the blade open. On + Container Apps that chatter decides the bill (main.bicep:302-306): measured on 2026-09-28, every + app sat right on the idle-billing line (about 0.011 cores against the 0.01 threshold, about 950 B/s + inbound against 1,000), so 64% of vCPU-seconds billed at the active rate, eight times the idle + rate. The key lives under AzureMonitor, not Telemetry, because the distro binds its own + AzureMonitor configuration section, and MMCA.Common pins that the key actually reaches it + (MMCA.Common/Tests/Hosting/MMCA.Common.Aspire.Tests/Telemetry/LiveMetricsConfigurationTests.cs:20), + so a distro change that stopped honoring it fails a test instead of silently restoring the chatter.
    • +
    • OTEL_METRIC_EXPORT_INTERVAL: '300000' (main.bicep:318-321) is the second stage of the + AppMetrics cost control, and it works on cadence rather than on instrument selection. AppMetrics remained about 63% of workspace ingestion after the two instrument groups above were dropped (measured - 2026-08-01 to 2026-08-22, main.bicep:261-266). The exporter ships cumulative aggregates, so + 2026-08-01 to 2026-08-22, main.bicep:311-316). The exporter ships cumulative aggregates, so stretching the export interval from the SDK default of 60s to 300s drops roughly 80% of the remaining datapoints without losing the signal: every alert rule in this template evaluates over a 15-minute window, so a 5-minute export cadence still lands datapoints in every window. This is the standard OpenTelemetry SDK env var, read by the periodic exporting metric reader rather than by any MMCA.Common code.
    -

    Every one of the six apps gets all five: Identity (main.bicep:1651-1656), Conference - (:1878-1883), Engagement (:2016-2021), Notification (:2162-2167), Gateway (:2338-2344), - UI (:2471-2476). They are declared once as Bicep variables and spliced into each env array by +

    Every one of the six apps gets all seven: Identity (main.bicep:1671-1677), Conference + (:1900-1906), Engagement (:2040-2046), Notification (:2188-2194), Gateway (:2366-2373), + UI (:2507-2513). They are declared once as Bicep variables and spliced into each env array by name, which is what keeps a cost decision from being applied to five apps and forgotten on the sixth.

    The Gateway carries one more, and it is the only per-host entry in the set: - Logging__LogLevel__Yarp: 'Warning' (yarpLogLevelEnv, main.bicep:267-276, spliced at :2341). + Logging__LogLevel__Yarp: 'Warning' (yarpLogLevelEnv, main.bicep:267-276, spliced at :2368). YARP writes two Information lines per proxied request (HttpForwarder events 9 and 56) to stdout, which Container Apps ships to Log Analytics as ContainerAppConsoleLogs_CL. The comment records the measurement behind it: about 177k lines and 77 MB per week between 2026-09-13 and 2026-09-19, the @@ -837,7 +856,7 @@

    SLO alerts as code (m (OPERATIONS.md:17, :31, :50, :63, :111), and that pairing is enforced by a framework fitness test rather than by discipline: ObservabilityConventionTestsBase parses this template between the literal anchors var sloAlertSpecs and resource sloAlerts - (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/ObservabilityConventionTestsBase.cs:109-110) + (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/ObservabilityConventionTestsBase.cs:135-136) and fails the build in both directions. ADC raises the base class's floor of three discovered specs (ObservabilityConventionTestsBase.cs:39) to five (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Governance/ObservabilityConventionTests.cs:14), @@ -992,12 +1011,29 @@

    AI-scoring token-ceilin subscription spend, cost-guard.yml bounds a surge left un-reverted, and this bounds the one meter that neither of them can see, with the envelope itself expressed as a reviewable Bicep parameter rather than as an assumption inside the scoring code.

    -

    SLO workbook (main.bicep:704-724)

    +

    SLO workbook (main.bicep:721-742)

    A saved Azure Monitor workbook renders the same three SLO signals plus exceptions, grouped per service by AppRoleName (which is the OTEL_SERVICE_NAME value). It is bound to the Log Analytics workspace and embeds workbooks/adc-slo-workbook.json at compile time via loadTextContent - (main.bicep:620), so the visualization cannot diverge from the alerts by being maintained - somewhere else, and the JSON stays independently validatable as a file.

    + (main.bicep:739), so the visualization cannot diverge from the alerts by being maintained + somewhere else, and the JSON stays independently validatable as a file. No test holds it in place, + though: ADC's ObservabilityConventionTests subclass raises only the spec floor and does not opt + into the base class's RequireWorkbook switch (ObservabilityConventionTestsBase.cs:58, + MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Governance/ObservabilityConventionTests.cs:7-15), + so deleting this resource would not fail the build.

    +

    MMCA.Common's reference sample reaches the same goal by another route + (MMCA.Common/samples/deployment/main.bicep:286-351): it has no workbook JSON file at all and + generates the workbook from its own sloAlertSpecs, one KQL table tile per spec that runs that + spec's own query against the workspace the rules scope to (:290-305), behind a time-range + parameter defaulting to the 15-minute window the rules evaluate (:323). Adding an alert therefore + adds its tile, and a tile cannot drift from the query that pages (:286-289). The resource name is a + GUID derived from the resource group and prefix (:339-340), because a workbook's name must be a + GUID and deriving it keeps redeploys updating the same workbook. Unlike ADC, the sample's + architecture test opts into RequireWorkbook + (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/SampleDeploymentObservabilityTests.cs:24), + so MonitoringWorkbookOrDashboard_IsProvisioned_WhenRequired (ObservabilityConventionTestsBase.cs:66-77) + fails if the sample stops declaring a Microsoft.Insights/workbooks or Microsoft.Portal/dashboards + resource.

    Cost budget (main.bicep:725-757)

    resource costBudget 'Microsoft.Consumption/budgets@2023-11-01' = if (enableBudget) {
       properties: {
    @@ -1703,11 +1739,12 @@ 

    Probes on a dedicated HTTP/1.1 li GOAWAY HTTP_1_1_REQUIRED, which would fail the liveness check and cause a reboot loop. Rather than degrading the three h2c services to port-only tcpSocket probes, each service opens a dedicated HTTP/1.1 probe listener that is not exposed via ingress: HealthProbe__Port: '8081' - on Identity, Conference and Engagement (main.bicep:1212, :1419, :1542) and '8082' on - Notification (main.bicep:1688, because 8080 and 8081 are already the ADR-012 pair). ACA probes may + on Identity, Conference and Engagement (main.bicep:1682, :1909, :2049) and '8082' on + Notification (main.bicep:2201, because 8080 and 8081 are already the ADR-012 pair). ACA probes may target a port that ingress does not publish, so all six apps use httpGet probes and all six carry - the same three (main.bicep:1326-1351 Identity, :1460-1485 Conference, :1587-1612 Engagement, - :1742-1767 Notification, :1861-1886 Gateway, :1983-2008 UI):

    + the same three (main.bicep:1817-1833 Identity, :1968-1984 Conference, :2101-2117 Engagement, + :2266-2282 Notification, :2409-2432 Gateway, :2558-2574 UI), with one deliberate difference in + where the Gateway's readiness probe points:

    @@ -1731,19 +1768,30 @@

    Probes on a dedicated HTTP/1.1 li

    - + - +
    readiness/health/ready/health/ready (Gateway: /alive) initialDelaySeconds: 3, periodSeconds: 30, failureThreshold: 3warmup gate plus the DB-aware AddSqlServer checkwarmup gate plus the DB-aware AddSqlServer check (Gateway: self-only, see below)
    -

    The liveness/readiness split is the load-bearing part (main.bicep:1313-1319): /alive checks the +

    The liveness/readiness split is the load-bearing part (main.bicep:1806-1808): /alive checks the process only, so a database outage does not trigger a restart loop, while /health/ready fails when a replica cannot reach its database, pulling it out of rotation instead of letting it serve 500s. Readiness is also gated on WarmupHostedService completing (OIDC discovery fetched), so ACA holds - back user traffic until the replica is warm. Gateway and UI probe their own 8080 (main.bicep:1861-1886, - :1983-2008) because their Kestrel accepts HTTP/1.1 directly.

    + back user traffic until the replica is warm. Gateway and UI probe their own 8080 (main.bicep:2409-2432, + :2558-2574) because their Kestrel accepts HTTP/1.1 directly.

    +

    The Gateway's readiness probe targets /alive, not /health/ready (main.bicep:2423-2432). + On the Gateway, /health/ready is an aggregate that fans out to every downstream's /alive + (AddGatewayDownstreamHealthChecks), so probing it every 30 seconds put an HTTP request on every + service every 30 seconds. The comment records two costs of that. On the bill, a request every 30 + seconds is on its own enough to keep an otherwise idle downstream replica off the Container Apps + idle rate (the same idle-line problem the Live Metrics switch above addresses). On availability, one + failed downstream marked the only Gateway replica unready and took the whole site down, healthy + routes included. The switch costs no cold-revision protection: the Gateway registers no JwtBearer + scheme, so the only warm-up task AddServiceDefaults gives it (OIDC metadata) has nothing to wait + for. The full downstream aggregate is still served on /health, which is what the availability web + test and its alert probe.

    Readiness runs every 30 seconds, not every 10, and that is a telemetry-cost decision - (main.bicep:1320-1325). The DB-aware readiness check issues a SQL SELECT 1 per probe, and + (main.bicep:1809-1814). The DB-aware readiness check issues a SQL SELECT 1 per probe, and neither the probe request nor its dependency row is sampled, so a 10-second period cost 360 request rows plus 360 dependency rows per app per hour of App Insights ingestion, on six apps, forever. failureThreshold stays at 3, so the honest trade is stated in the comment: an unhealthy replica @@ -1781,7 +1829,7 @@

    Service Discovery (serv services__engagement__http__0 = http://localhost:<assigned-port>. The application code calls AddHttpForwarderWithServiceDiscovery() or AddTypedGrpcClient<T>(serviceName) in both environments and resolves the endpoint from that env var key.

    -

    Identity Service specifics (main.bicep:1603-1827)

    +

    Identity Service specifics (main.bicep:1624-1850)

    Identity is the JWT issuer and JWKS endpoint. Its JWT configuration (main.bicep:1233-1237):

    { name: 'Jwt__SigningAlgorithm',   value: 'RS256' }
     { name: 'Jwt__Issuer',            value: 'https://${prefix}-gateway.${...defaultDomain}' }
    @@ -1838,7 +1886,7 @@ 

    Identity Service specific the post-login redirect target is provider-independent.

    Identity is sized at 0.25 CPU / 0.5 Gi (main.bicep:1198). JWT operations are CPU-cheap once the key is loaded; the bottleneck is typically network I/O to SQL.

    -

    Conference Service specifics (main.bicep:1828-1976)

    +

    Conference Service specifics (main.bicep:1851-2001)

    Conference carries the heaviest surface of the four services: seventeen API controllers (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/), an AI scoring path (Anthropic API), and the role of read-heavy entry point for the event/session catalog. It ran at @@ -1862,7 +1910,7 @@

    Conference Service spec

    This is the union() + conditional array pattern used throughout main.bicep to keep optional secrets and env vars out of the resource definition when not configured, rather than passing empty strings to the container.

    -

    Notification Service specifics (main.bicep:2108-2280)

    +

    Notification Service specifics (main.bicep:2135-2313)

    Notification differs from the other three back-end services in four ways:

    1. transport: 'http' instead of 'http2', SignalR WebSocket requires an HTTP/1.1 Upgrade @@ -1882,32 +1930,42 @@

      Notification Service it, SignalR connections made during warmup would fail because the JWT validator is not yet initialized. Its replica cap is no longer the outlier it once was: see the shared scale discussion above.

      -

      Gateway specifics (main.bicep:2281-2427)

      +

      Gateway specifics (main.bicep:2314-2465)

      Gateway is the sole externally-reachable back-end entry point (external: true, - allowInsecure: false, main.bicep:1806-1811). It is a pure YARP reverse proxy: no DbContext, no - JWT issuing, no module. Its env configuration is service-discovery entries, CORS, and one optional - rate-limiter key:

      + allowInsecure: false, main.bicep:2328-2333). It is a pure YARP reverse proxy: no DbContext, no + JWT issuing, no module. Its env configuration is service-discovery entries, CORS, the vault + reference, and two optional rate-limiter keys:

      { name: 'Cors__AllowedOrigins__0', value: 'https://${prefix}-ui.${...defaultDomain}' }
      -

      CORS is scoped to exactly the UI's FQDN (main.bicep:1843), not a wildcard. Gateway was right-sized - alongside Conference on 2026-09-02 and now runs at 0.25 CPU / 0.5 Gi (main.bicep:2334); its - comment records the easier half of that decision (main.bicep:1824-1830), a 190 to 235 MB working - set comfortably inside the new limit because pure YARP forwarding holds no DbContext. It uses the - readiness gate at main.bicep:1878-1885 because its warmup involves establishing connections to all - back-end services. It is also the target of the availability web test described above, and the only - app with no KeyVault__Uri.

      -

      Its one conditional secret is the ADR-088 synthetic-traffic bypass. When - hasSyntheticTrafficSecret is true the app declares a synthetic-traffic-secret Key Vault - reference (main.bicep:1815-1817) and receives - GatewayRateLimiting__SyntheticTrafficSecret as a secretRef (main.bicep:1858). A request +

      CORS is scoped to exactly the UI's FQDN (main.bicep:2375), not a wildcard. Gateway was right-sized + alongside Conference on 2026-09-02 and now runs at 0.25 CPU / 0.5 Gi (main.bicep:2361); its + comment records the easier half of that decision (main.bicep:2354-2360), a 190 to 235 MB working + set comfortably inside the new limit because pure YARP forwarding holds no DbContext. Its readiness + probe is the one exception to the fleet's /health/ready convention: it targets /alive + (main.bicep:2423-2432), for the cost and blast-radius reasons given in the probe section above. It + is also the target of the availability web test described above, which is where the full downstream + aggregate on /health is still checked. Like the other five deployables it reads Key Vault + configuration at startup through KeyVault__Uri plus AZURE_CLIENT_ID (main.bicep:2385-2391); the + client id is there because the app carries only the user-assigned identity and the ACA identity + endpoint needs it named, or DefaultAzureCredential would fail the startup vault read.

      +

      Its two conditional secrets are both rate-limiter keys, unioned rather than kept as one conditional + array because either can be present on its own (main.bicep:2335-2347). The first is the ADR-088 + synthetic-traffic bypass. When hasSyntheticTrafficSecret is true the app declares a + synthetic-traffic-secret Key Vault reference (main.bicep:2339-2341) and receives + GatewayRateLimiting__SyntheticTrafficSecret as a secretRef (main.bicep:2397). A request presenting that value in the X-Synthetic-Traffic-Key header skips both chained edge limiters, so - the monthly k6 run measures backend capacity instead of the per-IP window. Absent, the bypass is - off and every request stays rate limited, which is the correct default for a public entry point.

      -

      The template also records the transport contract the Gateway holds up (main.bicep:1843-1846): + the monthly k6 run measures backend capacity instead of the per-IP window. The second is the + trusted-internal-caller exemption (main.bicep:2344-2346, env at :2404): a request carrying that + value in the X-Internal-Caller-Key header takes the no-limiter partition on both chained edge + limiters (main.bicep:2398-2403). It exists for the UI host, whose server-side token refresh calls + all leave from one container address, which the per-IP window would otherwise collapse into a single + partition and throttle for the whole site. Absent, each key's exemption is off and every request + stays rate limited, which is the correct default for a public entry point.

      +

      The template also records the transport contract the Gateway holds up (main.bicep:2376-2379): ForwardHttp2 defaults to true in the gateway code and YARP uses VersionPolicy=RequestVersionExact, so it sends the HTTP/2 preface to the three h2c-prior-knowledge backends whose ACA ingress is transport: http2. That pairing is why the ingress choice on those three services and the forwarder policy here cannot be changed independently.

      -

      UI specifics (main.bicep:2428-2567)

      +

      UI specifics (main.bicep:2466-2604)

      UI is the other externally-reachable app (external: true, main.bicep:1928), the one app with secrets: [] (main.bicep:1938) and sized at 0.25 CPU / 0.5 Gi (main.bicep:1945). Three non-obvious configuration points:

      @@ -1936,7 +1994,7 @@

      UI specifics (main.bicep:2428-256

      The UI receives only the OAuth client ids when a provider is configured, one per provider including Apple (main.bicep:1971-1979); every client secret stays on Identity, which is the app that completes the exchange.

      -

      Outputs (main.bicep:2568-2573)

      +

      Outputs (main.bicep:2605-2610)

      output acrLoginServer     string = acr.properties.loginServer
       output gatewayFqdn        string = gatewayApp.properties.configuration.ingress.fqdn
       output uiFqdn             string = uiApp.properties.configuration.ingress.fqdn
      diff --git a/docs/onboarding/devops-runbooks.html b/docs/onboarding/devops-runbooks.html
      index 3be88918..9d2709a8 100644
      --- a/docs/onboarding/devops-runbooks.html
      +++ b/docs/onboarding/devops-runbooks.html
      @@ -249,10 +249,10 @@ 

      Walkthrough

      (OAUTH_GITHUB_CLIENT_ID, OAUTH_GITHUB_CLIENT_SECRET, ANTHROPIC_API_KEY, SMTP_PASSWORD) and optional variables (SMTP_HOST, SMTP_PORT, SMTP_FROM, SMTP_USERNAME). There is no HS256 fallback signing key in that list: RS256 is the only signing path, which is why both RSA PEMs are - listed as required and deploy.yml:1416-1419 fails the run when either is missing.

      + listed as required and deploy.yml:1329-1332 fails the run when either is missing.

      One gap to know about before your first deploy. The checklist never mentions ALERT_EMAIL, but main.bicep:115-117 declares alertEmailAddress as a required parameter with @minLength(3) and - no default (alerts that notify nobody are silent failures), and deploy.yml:1408-1411 refuses the + no default (alerts that notify nobody are silent failures), and deploy.yml:1321-1324 refuses the run with an actionable error when vars.ALERT_EMAIL is unset. Follow the printed checklist exactly and the first deploy stops there. Set ALERT_EMAIL as well.


      @@ -390,19 +390,19 @@

      Three passages that are created and visible in Azure Monitor even without the variable, "they just don't email". That state is no longer reachable: alertEmailAddress is a required parameter with @minLength(3) and no default (main.bicep:115-117), the action group's email receiver is unconditional - (main.bicep:269-286), and deploy.yml:1408-1411 fails the deploy before bicep validation when + (main.bicep:269-286), and deploy.yml:1321-1324 fails the deploy before bicep validation when vars.ALERT_EMAIL is unset. An alert that notifies nobody is now impossible in a deployed environment by construction, which is the stronger version of what the runbook was aiming at.

      Deploy rollback description (DISASTER-RECOVERY.md:165-167). The runbook describes the post-deploy smoke gate as Gateway /health plus /.well-known/jwks.json plus the UI root. The live gate is broader in both dimensions: a revision-activation gate that requires the newest revision of every app to report Healthy, Running and 100% traffic weight, followed by six probes that reach - every service through the Gateway (deploy.yml:1607-1628 for the reasoning, - deploy.yml:1707-1717 for the probes). The activation gate exists because the HTTP probes alone + every service through the Gateway (deploy.yml:1520-1541 for the reasoning, + deploy.yml:1620-1630 for the probes). The activation gate exists because the HTTP probes alone cannot prove the new code is serving: a healthy Gateway keeps answering from the previous backend revision when the new one never goes ready, which is exactly how a readiness regression stayed - hidden for four days (deploy.yml:1616-1619). The rollback mechanism the runbook names, - az containerapp revision copy, is still what runs (deploy.yml:1761).

      + hidden for four days (deploy.yml:1529-1532). The rollback mechanism the runbook names, + az containerapp revision copy, is still what runs (deploy.yml:1674).

      Recovery objectives

      The DR file targets three failure scenarios (DISASTER-RECOVERY.md:10-14):

      @@ -543,12 +543,12 @@

      Restore drill

      (DISASTER-RECOVERY.md:206-208) records that rows naming AtlDevCon are kept as history now that the archive is gone, rather than being edited out.

      The ledger stays honest because it is gated, not remembered: dr-freshness fails a deploy when the - newest successful dr-drill.yml run is older than 8 days (deploy.yml:823, + newest successful dr-drill.yml run is older than 8 days (deploy.yml:899, DISASTER-RECOVERY.md:222-224). The rotation itself is prose here but arithmetic in the workflow, which is the source of truth. The next section walks it.


      dr-drill.yml and dr-restore-drill.ps1, the ADR-009 restore drill

      -

      Files: MMCA.ADC/.github/workflows/dr-drill.yml (112 lines), +

      Files: MMCA.ADC/.github/workflows/dr-drill.yml (120 lines), MMCA.ADC/scripts/dr-restore-drill.ps1 (101 lines)

      What it is. The automation behind the drill requirement above: the workflow picks a target database and logs in to Azure via OIDC, the PowerShell script does the restore, the timing, the @@ -565,30 +565,45 @@

      dr-drill.

      The job holds id-token: write plus contents: read and nothing else (dr-drill.yml:35-37), which is the least privilege an OIDC login needs, and it logs in with the same three AZURE_* secrets - azure-setup.sh printed (dr-drill.yml:53-58). Both third-party actions it uses are pinned to a + azure-setup.sh printed (dr-drill.yml:61-66). Both third-party actions it uses are pinned to a full commit SHA with the human-readable version parked in a trailing comment: - actions/checkout@3d3c42e5... (dr-drill.yml:51) and azure/login@7ddb5af1... - (dr-drill.yml:54).

      + actions/checkout@3d3c42e5... (dr-drill.yml:59) and azure/login@a641126d... + (dr-drill.yml:62).

      [Rubric §32, Dependency & Supply-Chain] assesses whether third-party dependencies are pinned, scanned, and cannot change underfoot. A Git tag is mutable, so @v3 in a job that mints an Azure OIDC token means a repointed tag could run new code with production credentials; the SHA pin removes - that path, and the # v7.0.1 / # v3.0.2 comments keep the pin readable and updatable.

      + that path, and the # v7.0.1 / # v3.1.0 comments keep the pin readable and updatable.

      +

      Why the drill shares the deploy's concurrency group. The workflow joins the prod-azure group + with cancel-in-progress: false (dr-drill.yml:43-45). Its comment gives the reason + (dr-drill.yml:39-42): the drill creates and deletes a database on the production SQL server, and + deploy.yml declares that every workflow mutating production Azure state joins that same group + (deploy.yml:39-47), so a drill and a deploy (which migrates those very databases) never run at the + same time. Never cancelling an in-flight drill matches the deploy side, for the reason the cleanup + sweep below exists: a drill killed mid-restore never reaches the script's own cleanup. The cost is + queueing. A push to main that lands during a drill waits behind it, for at most the drill's + 60-minute timeout. And because GitHub Actions keeps only one pending run per concurrency group, a + scheduled drill still waiting behind a deploy can be displaced by a newer pending deploy; the 8-day + dr-freshness window, wider than the weekly cadence, is what absorbs one lost run, and a manual + dispatch restores the proof.

      +

      [Rubric §17, DevOps & Deployment] assesses whether deployments are safe and repeatable. Serializing + every production-mutating workflow on one group turns "do not drill during a migration" from an + operator habit into a property of the pipeline.

      Scheduled runs rotate across the four live per-service databases by ISO week number modulo 4 - (dr-drill.yml:60-79, the arithmetic at dr-drill.yml:72-75), so each live database gets a recovery + (dr-drill.yml:68-87, the arithmetic at dr-drill.yml:80-83), so each live database gets a recovery proof roughly monthly. Which branch runs is decided purely by whether the dispatch input is present - (dr-drill.yml:66), which is what lets one job serve both triggers. The chosen database is echoed - into the step summary before the drill starts (dr-drill.yml:79), so a reader of a failed run knows + (dr-drill.yml:74), which is what lets one job serve both triggers. The chosen database is echoed + into the step summary before the drill starts (dr-drill.yml:87), so a reader of a failed run knows immediately which database was under test. Note how the input is read: the step declares DISPATCH_DATABASE: ${{ inputs.source_database }} as an environment variable - (dr-drill.yml:62-63) and the shell tests "${DISPATCH_DATABASE:-}" (dr-drill.yml:66) instead of + (dr-drill.yml:70-71) and the shell tests "${DISPATCH_DATABASE:-}" (dr-drill.yml:74) instead of expanding the ${{ }} expression inline, and the whole script runs under set -euo pipefail - (dr-drill.yml:65) so an unset variable or a failed az call stops the step rather than silently + (dr-drill.yml:73) so an unset variable or a failed az call stops the step rather than silently rotating to the wrong database.

      How the inputs reach the PowerShell script. The drill step sets three variables in its own env block, DRILL_RESOURCE_GROUP from vars.AZURE_RESOURCE_GROUP, DRILL_SOURCE_DATABASE from the rotation step's output, and DRILL_RESTORE_POINT_MINUTES_AGO from the dispatch input with a literal - '10' fallback for scheduled runs (dr-drill.yml:85-88), then passes those variables to the script - (dr-drill.yml:89-94). The workflow's own comment states the reason (dr-drill.yml:83-84): values + '10' fallback for scheduled runs (dr-drill.yml:93-96), then passes those variables to the script + (dr-drill.yml:97-102). The workflow's own comment states the reason (dr-drill.yml:91-92): values reach the script through the environment rather than being interpolated into the command line, so a dispatch input cannot inject extra PowerShell arguments. This matters more here than in most jobs, because the step runs with an Azure session that holds Contributor on the production resource group.

      @@ -597,8 +612,8 @@

      dr-drill. hostile input value becomes code; routing every input through env and quoting it turns the same value back into data. The type: choice input (dr-drill.yml:20-26) already constrains the database name to four options, so this is defence in depth rather than the only control.

      -

      Why the job gets 60 minutes. The job carries a 60-minute timeout-minutes (dr-drill.yml:49), - and the comment above it records why it is not 30 (dr-drill.yml:42-48): the 2026-09-14 scheduled +

      Why the job gets 60 minutes. The job carries a 60-minute timeout-minutes (dr-drill.yml:57), + and the comment above it records why it is not 30 (dr-drill.yml:50-56): the 2026-09-14 scheduled run (run 34838047421) was killed at the old 30-minute limit and recorded no freshness proof. A measured PITR restore of an ADC_* database takes minutes, but Azure SQL queues the restore on the platform side and its duration varies from run to run, so 60 leaves headroom for a slow one (MMCA.Store @@ -608,22 +623,22 @@

      dr-drill. walked below).

      The sweep that runs even when the drill does not finish. A job cancelled at its timeout-minutes is killed mid-step, so the script's own finally cleanup never runs. The workflow - comment records the incident that exposed this (dr-drill.yml:96-100): the same 2026-09-14 + comment records the incident that exposed this (dr-drill.yml:104-108): the same 2026-09-14 scheduled run was cancelled mid-restore and left ADC_Engagement-drill behind, and the weekly rotation meant the next run targeted a different database, so the script's name-specific stale-copy check would not have removed it either; the copy sat at Basic-tier cost for five days. The last step, Remove leftover drill copies, therefore runs - under if: always() (dr-drill.yml:101-102), which fires on success, failure and cancellation alike. + under if: always() (dr-drill.yml:109-110), which fires on success, failure and cancellation alike. It resolves the server by the same adc-prod-sql- name prefix the script uses, exits 0 with a - workflow warning when no server matches (dr-drill.yml:107-108), and deletes every database whose - name ends in -drill (dr-drill.yml:109-112), not just the copy this run created. The suffix + workflow warning when no server matches (dr-drill.yml:115-116), and deletes every database whose + name ends in -drill (dr-drill.yml:117-120), not just the copy this run created. The suffix filter is what keeps the sweep safe on a production server: the live ADC_* databases never end in -drill, so the only names it can match are throwaway copies. One consequence for a local run: a copy kept with the script's -KeepCopy switch for a manual row-count check (dr-restore-drill.ps1:83-88 honors it) is deleted by the next workflow run, so finish that check before Monday 06:00 UTC. It reads the resource group through - env like the drill step (dr-drill.yml:103-104) and runs under set -euo pipefail - (dr-drill.yml:106).

      + env like the drill step (dr-drill.yml:111-112) and runs under set -euo pipefail + (dr-drill.yml:114).

      AtlDevCon is gone from both ends of this pair. The dispatch choice input now offers only the four live databases (dr-drill.yml:22-26), and the script's own -SourceDatabase default moved from AtlDevCon to ADC_Identity (dr-restore-drill.ps1:26). Both files record why: the archive was @@ -637,9 +652,9 @@

      dr-drill.

      Walkthrough of the script

      Parameters (dr-restore-drill.ps1:23-30). -ResourceGroup (default acc-rg), -SourceDatabase (default ADC_Identity), -RestorePointMinutesAgo (default 10), -KeepCopy, and - -SummaryPath (the workflow passes $env:GITHUB_STEP_SUMMARY, dr-drill.yml:81-94). The workflow + -SummaryPath (the workflow passes $env:GITHUB_STEP_SUMMARY, dr-drill.yml:89-102). The workflow never leans on a default: it passes an explicit -SourceDatabase from the rotation step and an - explicit -ResourceGroup from vars.AZURE_RESOURCE_GROUP (dr-drill.yml:86-87, :91-92), so the script's + explicit -ResourceGroup from vars.AZURE_RESOURCE_GROUP (dr-drill.yml:94-95, :99-100), so the script's own defaults only matter to a local CLI run.

      Server discovery (dr-restore-drill.ps1:40-44). Queries by name prefix (adc-prod-sql-*) rather than hard-coding the resource-token suffix, and throws a clear "Did you run 'az login'?" error when @@ -650,7 +665,7 @@

      Walkthrough of the script

      drill on a name collision, and the freshness gate would then start blocking deploys for a reason that has nothing to do with recoverability. The check only covers the database being drilled this run, which under the weekly rotation is usually a different one, so the workflow's always() sweep - (dr-drill.yml:101-112) is what catches a copy left by an earlier run against another database.

      + (dr-drill.yml:109-120) is what catches a copy left by an earlier run against another database.

      Restore and timing (dr-restore-drill.ps1:59-66). A stopwatch brackets az sql db restore, and $LASTEXITCODE is checked explicitly (dr-restore-drill.ps1:65) because a failing az call does not by itself throw in PowerShell. That elapsed time IS the measured RTO reported in the drill row; @@ -661,7 +676,7 @@

      Walkthrough of the script

      Cleanup (dr-restore-drill.ps1:83-88). The delete sits in a finally block, so the throwaway copy is removed even when the restore or the verification threw. A finally block cannot run in a process that GitHub Actions kills at timeout-minutes, though, which is why the workflow backs it - with the always() sweep described above (dr-drill.yml:96-112): between them, a weekly drill + with the always() sweep described above (dr-drill.yml:104-120): between them, a weekly drill does not accrete paid databases.

      Result row and exit code (dr-restore-drill.ps1:90-101). Prints the markdown table row (date, source, PASS/FAIL, minutes, note) to both the console and the job summary through the Write-Line @@ -671,20 +686,30 @@

      Walkthrough of the script

      What it does not do, and the gate that makes it matter

      The drill never touches a live database and it does not gate production directly. The link to production is the age of its newest successful run: deploy.yml's dr-freshness job - (deploy.yml:815-866) reads the Actions API for the latest successful dr-drill.yml run - (deploy.yml:851-853) and fails the deploy when that run is older than the 8-day window - (deploy.yml:823, compared at deploy.yml:862-865), or when there is no successful run at all - (deploy.yml:854-857). The comment at deploy.yml:849-850 records why the gate can trust the run - list at face value: dr-drill.yml has no skip-if-unchanged guard, so every successful run really - performed a PITR restore. The job itself asks for only actions: read plus contents: read - (deploy.yml:819-821) and runs on a 5-minute timeout (deploy.yml:817), because it is one API read - and no restore cost per deploy.

      + (deploy.yml:883-902) is a single step that calls the shared MMCA.Common composite action + ivanball/MMCA.Common/.github/actions/freshness-gate@main (deploy.yml:895) with + workflow: dr-drill.yml and window-days: '8' (deploy.yml:898-899). The job names no + required-jobs, so the action runs in its success mode: it reads the newest dr-drill.yml run with + status=success (MMCA.Common/.github/actions/freshness-gate/action.yml:184-187), fails the deploy + when there is none (action.yml:188-191), and otherwise fails it when that run's age in whole days + is greater than the window (action.yml:133-143). The comment at deploy.yml:892-894 records why + the gate can trust that run at face value: dr-drill.yml has no skip-if-unchanged guard, so every + successful run really performed a PITR restore. The job itself asks for only actions: read plus + contents: read (deploy.yml:887-889) and runs on a 5-minute timeout (deploy.yml:885), because + it is one API read and no restore cost per deploy.

      +

      Why a shared action rather than inline bash: the DR, load, cross-service and cross-browser gates all + call the same action (deploy.yml:895, :920, :949, :1001), and so does MMCA.Store's deploy + (action.yml:6-8), so the staleness arithmetic and the break-glass rules live in one place and a fix + lands for every gate at once instead of in several drifting copies. The reference is the MMCA.Common + main branch rather than a SHA pin like the drill's third-party actions: it is first-party code, + and that branch only moves through a PR.

      dr-freshness sits in deploy.needs alongside load-freshness, cross-service-freshness and - cross-browser-freshness (deploy.yml:1310), and the deploy's condition requires all four to have - concluded success (deploy.yml:1349-1352). Break-glass exists but is deliberately expensive to - use: the skip_freshness_gates input is honored only with a non-empty skip_justification, and the - run refuses without one (deploy.yml:834-838), then writes the justification into the step summary - and raises a workflow warning (deploy.yml:839-846).

      + cross-browser-freshness (deploy.yml:1219), and the deploy's condition requires all four to have + concluded success (deploy.yml:1258-1261). Break-glass exists but is deliberately expensive to + use: the job forwards the skip_freshness_gates and skip_justification dispatch inputs + (deploy.yml:13-18) to the action (deploy.yml:901-902), which refuses to skip without a non-empty + justification (action.yml:94-97) and, when it does skip, writes the justification into the step + summary and raises a workflow warning (action.yml:98-106).

      The operational consequence for an on-call reader: a red or skipped weekly drill blocks the next production deploy. If a deploy fails on dr-freshness, the fix is to re-run dr-drill.yml (and fix it first if it genuinely failed), not to bypass it. The gate's full mechanics, the break-glass @@ -810,16 +835,16 @@

      The one gap the gate cannot see

      two things to lean on: the alert's own description, which names the first triage step (check /health/ready on the named app, an untagged infrastructure health check gating readiness being the usual cause, main.bicep:417), and the deploy-side story, the activation gate and rollback at - deploy.yml:1607-1628, described in the CI/CD chapter.

      + deploy.yml:1520-1541, described in the CI/CD chapter.

      Recovery moves

      OPERATIONS.md:150-163 is the fast reference: roll a bad revision back with az containerapp revision list and revision copy, follow DISASTER-RECOVERY.md for a database restore, re-run the referenced workflow when a freshness gate blocks a deploy, and revert a conference-day surge when cost-guard.yml fails. Its freshness quick-reference (OPERATIONS.md:158-161) names all three windows, and each one matches the workflow that enforces it: dr-freshness 8 days - (deploy.yml:823), load-freshness 35 days (deploy.yml:881), cross-service-freshness 5 days - (deploy.yml:943). Those numbers live in two places, so treat the workflow as the source of truth + (deploy.yml:899), load-freshness 35 days (deploy.yml:924), cross-service-freshness 5 days + (deploy.yml:955). Those numbers live in two places, so treat the workflow as the source of truth and re-check the runbook line whenever a window moves: deploy.yml now enforces a fourth window the - runbook's quick reference does not name, cross-browser-freshness at 10 days (deploy.yml:1048), + runbook's quick reference does not name, cross-browser-freshness at 10 days (deploy.yml:1006), which is the drift this pairing produces whenever a gate is added and the prose is updated separately.

      Why there is no Aspire dashboard in production

      @@ -855,8 +880,8 @@

      infra/SQL-M

      [Rubric §11, Security] assesses credential hardening. All three stages have been run in ADC production. The template default is still false (main.bicep:36), because that is what makes a fresh environment start on password auth and each stage independently deployable, but the deployed - value comes from a repository variable, not the default: deploy.yml:1400 reads - vars.USE_MANAGED_IDENTITY_SQL and deploy.yml:1569-1574 rewrites the parameter to a literal JSON + value comes from a repository variable, not the default: deploy.yml:1313 reads + vars.USE_MANAGED_IDENTITY_SQL and deploy.yml:1482-1487 rewrites the parameter to a literal JSON true when it is set. All three variables are set in ivanball/ADC (USE_MANAGED_IDENTITY_SQL, SQL_AAD_ADMIN_LOGIN and SQL_AAD_ADMIN_OID, all dated 2026-06-28), so the deployed apps use passwordless Active Directory Managed Identity connection strings. The ADC scorecard records the @@ -878,7 +903,7 @@

      Why staged, and the three stages

      is independently deployable and reversible.

      1. Stage 1, add the Entra admin (SQL-MANAGED-IDENTITY.md:50-54). Set the SQL_AAD_ADMIN_LOGIN - and SQL_AAD_ADMIN_OID repository variables; deploy.yml:1561-1568 folds them into the bicep + and SQL_AAD_ADMIN_OID repository variables; deploy.yml:1474-1481 folds them into the bicep parameter file, and main.bicep:624-633 provisions the AAD admin only when the object id is non-empty. Additive, zero app impact.
      2. Stage 2, grant the identity in each database (SQL-MANAGED-IDENTITY.md:56-68). Connect to @@ -1165,7 +1190,7 @@

        The operational artifacts in (main.bicep:635-647, POST-CUTOVER-atldevcon-downgrade.md:79-121). Three practical consequences follow for an operator. The four live ADC_* databases are the entire estate covered by PITR and LTR (main.bicep:681-685). The weekly drill rotates over exactly those four and cannot target anything - else (dr-drill.yml:22-26, :72-75). And recovering pre-cutover data is an az sql db import into + else (dr-drill.yml:22-26, :80-83). And recovering pre-cutover data is an az sql db import into a new database name, not a point-in-time restore.

        Cross-links:

          @@ -1173,7 +1198,7 @@

          The operational artifacts in policies, the SLO alerts and workbook, and the Service Bus namespace.
        • CI/CD chapter: deploy.yml carries the revision-activation and smoke gates, the rollback, and the four recency gates (dr-freshness, load-freshness, - cross-service-freshness, cross-browser-freshness, deploy.yml:1310).
        • + cross-service-freshness, cross-browser-freshness, deploy.yml:1219).
        • ADR-006, the decision to adopt database-per-service, its trade-offs, and the CrossDataSourceDegradeConvention that removes cross-database FKs.
        • ADR-009, the resilience and recovery objectives framework, including the requirement @@ -1222,11 +1247,11 @@

          Rubric tag summary

      - + - + @@ -1239,10 +1264,10 @@

      Not determinable from source

    2. Whether the newest weekly drill is green: the drill ledger is maintained through 2026-08-10 (DISASTER-RECOVERY.md:210-217), but dr-drill.yml runs every Monday and a run reaches the ledger only when an operator pastes the printed row back into DISASTER-RECOVERY.md. Any drill newer than - the last ledger row exists only in the Actions history, which is exactly what dr-freshness queries - (deploy.yml:851-853); it cannot be read from the repository.
    3. + the last ledger row exists only in the Actions history, which is exactly what dr-freshness queries + (deploy.yml:895-899, which reads MMCA.Common/.github/actions/freshness-gate/action.yml:184-187); it cannot be read from the repository.
    4. The live values of the three SQL managed-identity repository variables: main.bicep:36 shows - the safe default (false) and deploy.yml:1400 shows that the deployed value comes from + the safe default (false) and deploy.yml:1313 shows that the deployed value comes from vars.USE_MANAGED_IDENTITY_SQL. The variable's current value is repository configuration, not source. Check Settings, then Secrets and variables, then Actions.
    5. Whether the archive bacpac is still present and readable: the blob path and storage account are diff --git a/docs/onboarding/devops-testing.html b/docs/onboarding/devops-testing.html index c62768a5..6e9f5f83 100644 --- a/docs/onboarding/devops-testing.html +++ b/docs/onboarding/devops-testing.html @@ -150,7 +150,7 @@

      Testing Architecture & S

      Chapter scope note. The tier chapters (tier-00 through the sweep) document every type in the production codebase one by one. Test types are the logged exception: this chapter covers - the 2,621 types that live in test projects, grouped by project purpose and foundational + the 2,774 types that live in test projects, grouped by project purpose and foundational infrastructure, not written as one section per [Fact]. Individual test methods are cited only as worked examples. Cross-reference the tier chapters for the production types being tested. The counts come from the Roslyn inventory (00-inventory.md:25-135), which scans @@ -220,10 +220,10 @@

      Solution files: slnx vs. slnf

      the full Aspire stack running. None of that is available in the fast build job, so each is gated by its own job with its own prerequisites.

      Read the gating carefully, it is easy to state wrongly. ADC's integration-tests job is - pull-request-only (MMCA.ADC/.github/workflows/deploy.yml:651, with the reasoning at - deploy.yml:646-650) and is not in the deploy job's needs list (deploy.yml:1310). Its - sibling build-and-test is pull-request-only for the same stated reason (deploy.yml:214-217). The - comment under the needs list spells it out (deploy.yml:1311-1313): with strict branch protection + pull-request-only (MMCA.ADC/.github/workflows/deploy.yml:702, with the reasoning at + deploy.yml:697-701) and is not in the deploy job's needs list (deploy.yml:1219). Its + sibling build-and-test is pull-request-only for the same stated reason (deploy.yml:222-225). The + comment under the needs list spells it out (deploy.yml:1220-1222): with strict branch protection the PR validated the exact merge tree, so those jobs are required PR checks rather than push-time deploy gates, and they are not re-run on the merge push. The required contexts are exactly four, build-and-test, supply-chain, integration-tests and coverage, as the repo's own contributing @@ -232,16 +232,16 @@

      Solution files: slnx vs. slnf

      (dr-freshness, load-freshness, cross-service-freshness, cross-browser-freshness), foundation, build-images, the always-on ai-eval-gate (section 7), and then exactly one of two complementary test gates: the chromium e2e-gate on a UI diff, or - backend-test-gate on every other code diff (deploy.yml:1310, condition at deploy.yml:1342-1357). - backend-test-gate (deploy.yml:448) exists because those PR-only checks plus a ui-scoped + backend-test-gate on every other code diff (deploy.yml:1219, condition at deploy.yml:1251-1266). + backend-test-gate (deploy.yml:499) exists because those PR-only checks plus a ui-scoped e2e-gate composed into a hole: a backend-only push to main ran no tests at all before rolling out, with the post-deploy smoke gate as the only backstop, which is detection after the rollout - rather than prevention (deploy.yml:432-436). Its if is the exact complement of e2e-gate's - (deploy.yml:450 versus deploy.yml:804), so one of the two always runs on a code deploy, at zero + rather than prevention (deploy.yml:486-487). Its if is the exact complement of e2e-gate's + (deploy.yml:501 versus deploy.yml:872), so one of the two always runs on a code deploy, at zero added minutes on a UI deploy and one CI.slnf pass on a backend-only one. It deliberately runs - MMCA.ADC.CI.slnf and skips coverage collection (deploy.yml:444-447, run step at - deploy.yml:476): coverage is a review-time regression signal, not a rollout gate. It restores with - --locked-mode against the committed lock files (deploy.yml:466), so the gate cannot silently + MMCA.ADC.CI.slnf and skips coverage collection (deploy.yml:495-498, run step at + deploy.yml:527): coverage is a review-time regression signal, not a rollout gate. It restores with + --locked-mode against the committed lock files (deploy.yml:517), so the gate cannot silently resolve a different graph than the PR validated. [Rubric §17, DevOps & Deployment]: §17 assesses how consistently CI/CD enforces quality gates; the two-filter pattern is how the build stays fast on every push while the SQL-dependent tier still has @@ -259,22 +259,23 @@

      Solution files: slnx vs. slnf

      MMCA.ADC.WebAPI.Tests, whose middleware coverage was consolidated upstream into MMCA.Common.API.Tests). MMCA.Store.Integration.slnf is the same shape over Catalog, Sales and Identity (MMCA.Store/MMCA.Store.Integration.slnf:5-7).

      -

      MMCA.Common.slnx (MMCA.Common/MMCA.Common.slnx:1-61) includes sixteen of the seventeen published - packages (MMCA.Common/FACTS.md:19-38): the meta package MMCA.Common (line 8), four Core - (.Shared, .Domain, .Application, .Infrastructure, lines 11-14), four Presentation (.API, - .Grpc, .UI, .UI.Web, lines 17-20), and seven Hosting (.Aspire, .Aspire.Hosting, .Gateway, - .Testing, .Testing.Architecture, .Testing.E2E, .Testing.UI, lines 23-29), plus fourteen - test projects (lines 33-52). The seventeenth package, MMCA.Common.UI.Maui, sits outside the +

      MMCA.Common.slnx holds the framework's source projects (the published package list itself is owned + by MMCA.Common/FACTS.md): the meta package MMCA.Common (MMCA.Common/MMCA.Common.slnx:8), seven + Core (.AI, .AI.Anthropic, .AI.OpenAI, .Shared, .Domain, .Application, .Infrastructure, + lines 11-17), four Presentation (.API, .Grpc, .UI, .UI.Web, lines 20-23), and nine Hosting + (.Aspire, .Aspire.Hosting, .Gateway, .Testing, .Testing.Architecture, .AI.Testing, + .Testing.Aspire, .Testing.E2E, .Testing.UI, lines 26-34), plus sixteen test projects + (lines 38-59). The MAUI package, MMCA.Common.UI.Maui, sits outside the .slnx on purpose: its four MAUI target frameworks cannot build on the ubuntu runners the solution's CI uses, so it is built and packed by a dedicated windows job - (MMCA.Common/.github/workflows/ci.yml:199, rationale at ci.yml:170-173, build step at - ci.yml:236, - ADR-042). Six test + (MMCA.Common/.github/workflows/ci.yml:177, rationale at ci.yml:171-176, build step at + ci.yml:241, + ADR-042). Eight test projects are also intentionally absent from the .slnx:

      • Tests/Presentation/MMCA.Common.UI.Gallery, a backend-less Blazor host that renders the real login and register pages, a UI-primitives showcase and the notification pages; it exists solely to - give Playwright something to hit (ci.yml:240-243).
      • + give Playwright something to hit (ci.yml:243-245).
      • Tests/Presentation/MMCA.Common.UI.E2E.Tests, the axe-core + render-smoke suite that hits the Gallery.
      • Tests/Core/MMCA.Common.Infrastructure.Redis.Tests, which runs DistributedCacheService against a @@ -282,19 +283,26 @@

        Solution files: slnx vs. slnf

      • Tests/Performance/MMCA.Common.Benchmarks, the BenchmarkDotNet suite behind the ADR-060 performance gate (see section 7).
      • Tests/Core/MMCA.Common.Infrastructure.PostgreSQL.Tests, which runs the shipped - PostgreSQLDbContext against a real PostgreSQL via Testcontainers (ci.yml:887).
      • + PostgreSQLDbContext against a real PostgreSQL via Testcontainers (ci.yml:916). +
      • Tests/Core/MMCA.Common.Infrastructure.SQLServer.Tests, which runs the shipped + SQLServerDbContext and DbContextFactory against a real SQL Server via Testcontainers + (ci.yml:925-931, run step at ci.yml:956).
      • Tests/Hosting/MMCA.Common.Testing.Aspire.AppHostTests, which boots a sample AppHost through Aspire.Hosting.Testing; its two companion sample projects are out of the .slnx with it - (ci.yml:944-946).
      • + (ci.yml:1013-1015). +
      • Tests/Performance/MMCA.Common.LoadTests, the weekly load tier, which runs from its own workflow + rather than from ci.yml (MMCA.Common/.github/workflows/load-tests.yml:10-12, section 7).

      The exclusions are not all for the same reason, and the comments say which is which. The gallery pair and the benchmark suite are out so dotnet test --solution MMCA.Common.slnx stays fast (no browser - install, no benchmark wall clock: ci.yml:243 and ci.yml:351); the Redis, PostgreSQL and AppHost - projects are out because they need a Docker daemon or a real orchestrator that the fast unit loop - must not require (ci.yml:813, ci.yml:859-864, ci.yml:894-897). Each runs in its own CI job, + install, no benchmark wall clock: ci.yml:246-247 and ci.yml:354); the Redis, PostgreSQL, SQL Server + and AppHost projects are out because they need a Docker daemon or a real orchestrator that the + fast unit loop must not require (ci.yml:874-875, ci.yml:912-913, ci.yml:952-953, + ci.yml:1013-1014), and the load tier is out because a timing tier on shared runners is a trend + signal rather than a merge gate (load-tests.yml:10-12). Each runs in its own CI job or workflow, built by csproj path, for example dotnet test --project Tests/Presentation/MMCA.Common.UI.E2E.Tests/MMCA.Common.UI.E2E.Tests.csproj - (ci.yml:321) and the Redis tier at ci.yml:852. + (ci.yml:324) and the Redis tier at ci.yml:879. [Rubric §28, Front-End Testing & Quality]: §28 assesses whether UI components have automated tests; the Gallery + E2E split is the mechanism that adds browser-level coverage without slowing the primary test loop.

      @@ -313,16 +321,18 @@

      Microsoft Testing Platform (M
    6. Exit code 8, if a test project discovers zero tests MTP exits 8, not 0. Every CI dotnet test call passes --minimum-expected-tests with a floor sized to the tier it runs, so a discovery regression is a visible failure rather than a silent skip: 2000 for the whole - MMCA.Common solution (MMCA.Common/.github/workflows/ci.yml:183), 1 for the browser tier - (ci.yml:321), 1 for the AppHost tier (ci.yml:946), 40 for the cross-repo Helpdesk canary - (ci.yml:569), and 1 for four of - ADC's five test invocations (MMCA.ADC/.github/workflows/deploy.yml:338,476,532,718). The + MMCA.Common solution (MMCA.Common/.github/workflows/ci.yml:161), 1 for the browser tier + (ci.yml:324), 1 for the AppHost tier (ci.yml:1015), 40 for the cross-repo Helpdesk canary + (ci.yml:580), the exact [Fact] count of each Testcontainers tier (15 for Redis at + ci.yml:876-879, 7 for PostgreSQL at ci.yml:914-916, 3 for SQL Server at ci.yml:954-956), + and 1 for four of + ADC's five test invocations (MMCA.ADC/.github/workflows/deploy.yml:346,527,583,781). The backend-test-gate step comment states the reasoning in one line: a filter or discovery breakage - that runs zero tests must fail here, not report a vacuous pass (deploy.yml:474-475). The one + that runs zero tests must fail here, not report a vacuous pass (deploy.yml:525-526). The one invocation deliberately without a floor is the paid live judge of ai-eval-gate - (deploy.yml:535): without AI_API_KEY (mapped from the ANTHROPIC_API_KEY repository secret, - deploy.yml:545-547) every case skips itself dynamically, and a zero-run there must not red a - deploy on a repo whose secret is absent (deploy.yml:536-538).

      + (deploy.yml:586): without AI_API_KEY (mapped from the ANTHROPIC_API_KEY repository secret, + deploy.yml:596-598) every case skips itself dynamically, and a zero-run there must not red a + deploy on a repo whose secret is absent (deploy.yml:587-589).

    7. Filter syntax differs. You pass a -- separator and then MTP's own filter flags:

      # Run a single test class
      @@ -351,7 +361,7 @@ 

      2. Test project layout

      The inventory below is drawn from 00-inventory.md:25-135 (test-assembly counts) and the solution files above. Counts are distinct types per project as reported by the Roslyn inventory scan, not [Fact] counts.

      -

      MMCA.Common, 1,716 test types across 16 in-solution projects + 49 across 6 out-of-solution

      +

      MMCA.Common, 1,811 test types across 16 in-solution projects + 72 across 8 out-of-solution

      Unit, Core layer

    8. §31 Cost/FinOpsPOST-CUTOVER-atldevcon-downgrade.md (S0 to Basic, then archive-and-drop on a measured 0 DTU); the 2026-09-02 alert-cadence changes (main.bicep:345-349, :464-469); the thinned telemetry stream documented in OPERATIONS.md:176-207; the dr-drill.yml always() sweep of leftover -drill copies (dr-drill.yml:96-112), so a drill cancelled at its timeout cannot leave a billed database behindPOST-CUTOVER-atldevcon-downgrade.md (S0 to Basic, then archive-and-drop on a measured 0 DTU); the 2026-09-02 alert-cadence changes (main.bicep:345-349, :464-469); the thinned telemetry stream documented in OPERATIONS.md:176-207; the dr-drill.yml always() sweep of leftover -drill copies (dr-drill.yml:104-120), so a drill cancelled at its timeout cannot leave a billed database behind
      §32 Dependency & Supply-Chaindr-drill.yml (actions/checkout and azure/login pinned to full commit SHAs with the version in a trailing comment, dr-drill.yml:51, :54)dr-drill.yml (actions/checkout and azure/login pinned to full commit SHAs with the version in a trailing comment, dr-drill.yml:59, :62)
      §34 Architecture Governance
      @@ -363,22 +373,22 @@

      - + - + - + - +
      MMCA.Common.API.Tests155170 Tests for ApiControllerBase, exception handlers, idempotency filter, the shared middleware pipeline, JWKS endpoint (also the consolidated home of the ADC/Store middleware coverage), session-cookie auth, CSV export and tenant resolution
      MMCA.Common.Grpc.Tests1618 Tests for GrpcResultExceptionInterceptor, JwtForwardingClientInterceptor, Result to RpcException mapping
      MMCA.Common.UI.Tests152170 bUnit component tests for shared Blazor components (login/register forms, nav, theming, notification pages)
      MMCA.Common.UI.Web.Tests1224 The Blazor Web host layer: ServerTokenStorageService, BlazorCspPolicyProvider, WebFormFactor, the client-config endpoint (ClientConfig/ClientConfigEndpointTests), the trusted-caller header (Security/TrustedCaller*Tests) and the UI-host hardening kit (Hardening/BoundedCircuitHandlerTests, Hardening/UiRateLimitingTests)
      @@ -433,7 +443,7 @@

      MMCA.Common.Architecture.Tests - 230 + 254 Thin subclasses of the shared bases plus the Common-only *FitnessTests family, alongside CommonArchitectureMap and the fake modules, drifted probes and fixtures the adversarial suites drive (see section 4) -

      Out-of-solution (each run by its own dedicated CI job)

      +

      Out-of-solution (each run by its own dedicated CI job or workflow)

      @@ -488,7 +498,7 @@

      - + - + + + + + + + + + + +
      MMCA.Common.Infrastructure.PostgreSQL.Tests 7PostgreSQLPersistenceTests drives the shipped PostgreSQLDbContext against a real PostgreSQL container, with its own PgThing fixture aggregate, events and a recording dispatcher (MMCA.Common/.github/workflows/ci.yml:880)PostgreSQLPersistenceTests drives the shipped PostgreSQLDbContext against a real PostgreSQL container, with its own PgThing fixture aggregate, events and a recording dispatcher (MMCA.Common/.github/workflows/ci.yml:916)
      MMCA.Common.Testing.Aspire.AppHostTests 3SampleAppHostFixture, SampleAppHostCollection and SampleAppHostTests: a sample AppHost booted through the shipped MMCA.Common.Testing.Aspire fixtures (ci.yml:946)SampleAppHostFixture, SampleAppHostCollection and SampleAppHostTests: a sample AppHost booted through the shipped MMCA.Common.Testing.Aspire fixtures (ci.yml:1015)
      MMCA.Common.Infrastructure.SQLServer.Tests8The shipped SQLServerDbContext and DbContextFactory against a real SQL Server container: session-scoped SET IDENTITY_INSERT, server-issued rowversions and the outbox row landing in the same SaveChanges as its aggregate (ci.yml:925-929)
      MMCA.Common.LoadTests14The weekly load tier: OutboxThroughputLoadTests, PagedQueryLoadTests and the Support/ stack (LoadStack, PagedQueryFixture, LoadItem, CountingMessageBus, LoadResults) over a temp-file SQLite database, run by load-tests.yml rather than ci.yml (section 7)
      -

      The last two rows are the tiers that landed after the previous inventory pass; the current scan covers - them, so the totals below cover all 22 MMCA.Common test projects. Both run outside every solution - file, each from its own CI job, and both are described in section 7. Keeping them out of the .slnx - is deliberate: a tier that needs Docker or a trusted development certificate must not be able to break - a plain dotnet build of the solution for an engineer who has neither.

      -

      MMCA.ADC, 853 test types across 32 in-solution projects + 3 across 1 out-of-solution

      +

      The current scan covers every row above, so the totals below cover all 24 MMCA.Common test projects. + Every out-of-solution row runs from its own CI job, or in the load tier's case its own weekly + workflow, and each is described in section 5 or section 7. Keeping them out of the .slnx is + deliberate: a tier that needs Docker, a trusted development certificate or a wall-clock budget must + not be able to break or slow a plain dotnet build of the solution for an engineer who has none of + those.

      +

      MMCA.ADC, 888 test types across 32 in-solution projects + 3 across 1 out-of-solution

      Unit, per-module, per-layer (Identity module)

      @@ -539,7 +560,7 @@

      - + @@ -595,11 +616,11 @@ - + @@ -674,8 +695,8 @@

      - + @@ -780,24 +801,24 @@

      Test-type totals

        -
      • MMCA.Common: the 16 in-solution projects sum to 58 + 62 + 398 + 499 + 1 + 37 + 155 + 16 + 152 + - 12 + 50 + 4 + 8 + 7 + 27 + 230 = 1,716; the 6 out-of-solution projects add 11 + 20 + 2 + 6 + 7 + - 3 = 49, for 1,765.
      • -
      • MMCA.ADC: 71 (Identity) + 349 (Conference, incl. the 10-type scoring evaluation suite) + 133 (Engagement) + 5 (Notification) + 56 - (architecture) + 108 (four integration projects) + 16 (two Testcontainers tiers) + 9 (Gateway) + 7 - (Services) + 7 (UI.Web) + 92 (E2E) = 853 in-solution, plus the 3-type AppHost smoke project = - 856.
      • -
      • Combined test projects: 2,621. Separately, the five shipped testing packages contribute - another 154 types (MMCA.Common.Testing 25, .Testing.Architecture 68, .Testing.Aspire 10, - .Testing.E2E 32, .Testing.UI 19): those are shipped product, not tests, which is why they are +
      • MMCA.Common: the 16 in-solution projects sum to 60 + 64 + 401 + 514 + 1 + 37 + 170 + 18 + 170 + + 24 + 52 + 4 + 8 + 7 + 27 + 254 = 1,811; the 8 out-of-solution projects add 11 + 21 + 2 + 6 + 7 + + 3 + 8 + 14 = 72, for 1,883.
      • +
      • MMCA.ADC: 72 (Identity) + 367 (Conference, incl. the 10-type scoring evaluation suite) + 138 (Engagement) + 5 (Notification) + 57 + (architecture) + 108 (four integration projects) + 16 (two Testcontainers tiers) + 9 (Gateway) + 17 + (Services) + 7 (UI.Web) + 92 (E2E) = 888 in-solution, plus the 3-type AppHost smoke project = + 891.
      • +
      • Combined test projects: 2,774. Separately, the five shipped testing packages contribute + another 153 types (MMCA.Common.Testing 25, .Testing.Architecture 68, .Testing.Aspire 10, + .Testing.E2E 32, .Testing.UI 18): those are shipped product, not tests, which is why they are counted apart. The inventory also lists MMCA.Common.AI.Testing (5 types, MMCA.Common/Source/Hosting/MMCA.Common.AI.Testing/), which ships beside the AI package rather - than in this family and is not in the 154.
      • + than in this family and is not in the 153.

      [Rubric §14, Testability & Test Strategy]: §14 assesses the breadth and meaningfulness of the test suite across all layers; the project layout above, unit per layer, arch per repo, @@ -1365,7 +1386,7 @@

      MMCA.Common.Testing.Aspire

      MMCA.Common consumes the package from both sides: MMCA.Common.Testing.Aspire.Tests (8 types, in-solution) unit-tests the preconditions and probes, and the out-of-solution MMCA.Common.Testing.Aspire.AppHostTests (3 types) boots a sample AppHost through the fixtures in its - own CI job (MMCA.Common/.github/workflows/ci.yml:939, section 7).

      + own CI job (MMCA.Common/.github/workflows/ci.yml:958, section 7).

      [Rubric §33, Developer Experience]: §33 assesses how much harness an engineer has to build before writing the test they actually wanted. Shipping the AppHost boot, the readiness wait and the environment gates as a referenced package means a consumer AppHost gets a composition test by @@ -1424,7 +1445,7 @@

      NetArchTest.eNhancedEdition, t

      The walkthroughs below describe what each rule enforces (and the count of facts it produces); the rule implementations live in the shared package's ArchitectureRules.* + *TestsBase files, not in the per-repo test class.

      -

      MMCA.Common.Architecture.Tests, 230 types

      +

      MMCA.Common.Architecture.Tests, 254 types

      Located at MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/, filed into the same per-area folders as the rule library (Api/, Contracts/, Cqrs/, Domain/, Governance/, Layering/, Ui/) plus the *Fixtures/ trees the adversarial suites drive. Beside the test @@ -1574,7 +1595,7 @@

      DependencyVersionTests

      Supply-Chain]: §32 assesses whether dependency versions are tracked, pinned, and protected against accidental bumps; this is the build-time gate for the most dangerous version upgrades in the codebase. (See the primer 00-primer.md#nuget-lock-files--pinned-audited-sources for context.)

      -

      MMCA.ADC.Architecture.Tests, 56 types

      +

      MMCA.ADC.Architecture.Tests, 57 types

      Located at MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/: 54 fitness-function classes plus AdcArchitectureMap and the ServiceModels fixture (Domain/DeleteBehaviorConventionTests.cs:70). Like the Common project, most are thin subclasses of the shared *TestsBase classes from MMCA.Common.Testing.Architecture, referenced as a NuGet package @@ -1862,14 +1883,14 @@

      The compile-time layer r

      The runtime rules above have a compile-time counterpart that ships inside the packages rather than in a test project. MMCA.Common.Shared carries a buildTransitive targets file that gives any consumer project named {App}.{Module}.{Layer} the layer rules at ResolveProjectReferences, with no opt-in - (MMCA.Common/.github/workflows/ci.yml:759-761): MMCA0001 for a forbidden project reference and - MMCA0002 for a forbidden package reference. The proof runs in package-consumption (ci.yml:669), + (MMCA.Common/.github/workflows/ci.yml:783-785): MMCA0001 for a forbidden project reference and + MMCA0002 for a forbidden package reference. The proof runs in package-consumption (ci.yml:693), the job that consumes the freshly packed local feed, in the step "Layer rules ship with the packages - (compile-time probes)" (ci.yml:757). It builds one positive probe and two negative ones, and each - negative probe must both fail and fail for its named code (ci.yml:783, ci.yml:786). The negatives + (compile-time probes)" (ci.yml:781). It builds one positive probe and two negative ones, and each + negative probe must both fail and fail for its named code (ci.yml:801, ci.yml:807, ci.yml:810). The negatives carry the weight: a passing probe on its own would also pass if the targets file were silently missing from the package, so only a build refused with the expected code proves the rules arrived - (ci.yml:762-763).

      + (ci.yml:786-787).


      5. Integration and E2E strategy

      Per-service integration tests (SQL-gated)

      @@ -1938,11 +1959,11 @@

      Testcontainers tiers: where t

      Neither is in deploy.needs, because the gating jobs have no Docker daemon. Both instead run on a weekday-nightly schedule (cross-service-tests.yml:27, jobs cross-service at - line 78 and servicebus-emulator-smoke at line 156) and their recency gates the deploy through - cross-service-freshness (MMCA.ADC/.github/workflows/deploy.yml:933): the deploy fails if the last + line 78 and servicebus-emulator-smoke at line 159) and their recency gates the deploy through + cross-service-freshness (MMCA.ADC/.github/workflows/deploy.yml:936): the deploy fails if the last successful nightly proving both tiers is stale, and it counts a run only when both required - broker jobs actually succeeded rather than skipped (deploy.yml:994). The emulator smoke has been authoritative rather than advisory since - 2026-08-31 (cross-service-tests.yml:126-135), and the same comment records the standing rule: if it + broker jobs actually succeeded rather than skipped (deploy.yml:944-948, through the shared freshness-gate action). The emulator smoke has been authoritative rather than advisory since + 2026-08-31 (cross-service-tests.yml:132-141), and the same comment records the standing rule: if it goes red, fix it or dispatch a green run, do not re-add continue-on-error to unblock a deploy. The window was widened from 3 to 5 days when the nightly moved to weekdays plus skip-if-unchanged, so a legitimate weekend or holiday gap does not red the gate. Both jobs also sit @@ -1951,22 +1972,22 @@

      Testcontainers tiers: where t on the freshness of the evidence rather than on the run itself. MMCA.Store mirrors the arrangement with MMCA.Store.CrossService.IntegrationTests and MMCA.Store.ServiceBusEmulator.IntegrationTests behind its own cross-service-freshness gate (MMCA.Store/.github/workflows/deploy.yml:716).

      -

      A third nightly job, apphost-smoke (cross-service-tests.yml:204), boots the real Aspire stack and - asserts its composition. It is continue-on-error: true (line 209) and, unlike the emulator smoke +

      A third nightly job, apphost-smoke (cross-service-tests.yml:208), boots the real Aspire stack and + asserts its composition. It is continue-on-error: true (line 213) and, unlike the emulator smoke above it, the cross-service-freshness deploy gate does not look at it at all, so nothing it does can ever gate a deploy.

      Three of its steps are worth reading, because each one is a precondition the job has to create for itself. A lock-drift guard compares every MMCA.Common.* entry in the project's committed - packages.lock.json against the single central pin and fails on a mismatch (cross-service-tests.yml:225): + packages.lock.json against the single central pin and fails on a mismatch (cross-service-tests.yml:229): this project sits outside every solution, so no gating restore covers its lock, and one lock really did sit at 1.176.0 while the repo pinned 1.177.0. It deliberately does not use --locked-mode, because an Aspire AppHost's lock carries a RID-specific Aspire.Dashboard.Sdk.<rid> entry that makes locked mode fail with NU1004 on this runner whether or not anything drifted. A dev-certificate trust - step follows the build (cross-service-tests.yml:270-271): Notification launches with the https + step follows the build (cross-service-tests.yml:274-275): Notification launches with the https profile, so its stock /alive probe negotiates TLS against the ASP.NET development certificate, and on a fresh runner every probe fails with UntrustedRoot until the certificate is trusted, leaving the - gateway's WaitFor(notification) edge waiting out the budget. The run step sets MMCA_APPHOST_TESTS - (cross-service-tests.yml:273), the framework's ADR-117 opt-in: without it the fixture never boots an + gateway's WaitFor(notification) edge waiting out the budget. The run step sets MMCA_APPHOST_TESTS + (cross-service-tests.yml:278, env at line 289), the framework's ADR-117 opt-in: without it the fixture never boots an orchestrator and every test skips with a named reason, which is exactly what a developer machine and every other CI job should get. The ephemeral RS256 keypair Identity needs is no longer minted by an openssl step here: AppHostFixtureBase mints it and exports it through the same E2E_JWT_* channel @@ -1978,7 +1999,7 @@

      Testcontainers tiers: where t continue-on-error only after it earns a track record, or deleted if it proves to be a flake generator. That is what an honestly staged new gate looks like.

      MMCA.Common unit-level infrastructure tests

      -

      MMCA.Common.Infrastructure.Tests (499 types) uses SQLite-backed EnsureCreated contexts for +

      MMCA.Common.Infrastructure.Tests (514 types) uses SQLite-backed EnsureCreated contexts for tests that need a real EF pipeline, with MMCA.Common.Infrastructure.Tests.MigrationsFixture beside it as the real migrations assembly those tests point EF at. SQLite avoids the SQL Server dependency entirely, which is why MMCA.Common builds and tests without any SQL Server or Docker in the local @@ -1988,22 +2009,27 @@

      MMCA.Common unit-level infra is the thing under test, SQLite is not enough: MMCA.Common.Infrastructure.Redis.Tests exists precisely because a counter written as a string and read back as a hash round-trips fine against a mocked IDistributedCache and answers WRONGTYPE against a real server, so that one project runs Redis - via Testcontainers, out of the slnx, in its own CI job (MMCA.Common/.github/workflows/ci.yml:806,845). - The same argument now has a second instance: postgresql-integration (ci.yml:854) runs the shipped + via Testcontainers, out of the slnx, in its own CI job (MMCA.Common/.github/workflows/ci.yml:837,869). + The same argument now has a second instance: postgresql-integration (ci.yml:881) runs the shipped PostgreSQLDbContext against a real PostgreSQL container because the SQL the framework emits is what matters there, and a model assertion cannot express it (a partial-index predicate written the SQL Server way, a soft-delete predicate comparing a boolean to 0, or a non-UTC DateTime Kind all build - a valid EF model and are rejected by the server, ci.yml:859-864). A third, apphost-testing - (ci.yml:889), boots a sample AppHost through Aspire.Hosting.Testing to execute the one layer + a valid EF model and are rejected by the server, ci.yml:886-891). A third, sqlserver-integration + (ci.yml:918), runs the shipped SQLServerDbContext and DbContextFactory against a real SQL Server + because there the session state and server-generated values matter, and neither a mock nor SQLite + can express them: SET IDENTITY_INSERT holds only on the session that ran it, a rowversion is issued + by the server on every write, and the outbox row must reach the database in the same SaveChanges as + its aggregate (ci.yml:925-929). It is not a required check yet; promoting it is a branch-protection + setting (ci.yml:930-931). A fourth, apphost-testing (ci.yml:958), boots a sample AppHost through Aspire.Hosting.Testing to execute the one layer nothing else runs, the AppHost wiring itself: the solution build never runs an AppHost and every in-process tier boots hosts directly through WebApplicationFactory, so a renamed resource, an - unresolvable reference or a WaitFor cycle is invisible everywhere else (ci.yml:899-902). It is - advisory (continue-on-error, ci.yml:907) on purpose: it is the only tier that starts a real + unresolvable reference or a WaitFor cycle is invisible everywhere else (ci.yml:968-971). It is + advisory (continue-on-error, ci.yml:976) on purpose: it is the only tier that starts a real orchestrator, it is the slowest thing in the repo per assertion, and its shared-runner failure modes are not yet proven, so it reds the job for visibility without failing the run until it earns a green streak. Like the ADC smoke it trusts nothing about the runner: it trusts the development certificate - itself (ci.yml:923) and gates on MMCA_APPHOST_TESTS with a --minimum-expected-tests floor so a - silently skipped tier is visible rather than a green no-op (ci.yml:946).

      + itself (ci.yml:992) and gates on MMCA_APPHOST_TESTS with a --minimum-expected-tests floor so a + silently skipped tier is visible rather than a green no-op (ci.yml:1015).

      E2E tests

      MMCA.ADC.E2E.Tests (92 types) and MMCA.Common.UI.E2E.Tests (20 types) require either the full Aspire stack (dotnet run --project Source/Hosting/MMCA.ADC.AppHost) or the Gallery @@ -2026,12 +2052,12 @@

      E2E tests

      When a run does go red, the evidence is in the processes' own logs, not the AppHost's. The Collect service logs step runs on always() and sweeps the working tree, $HOME and /tmp for every MMCAADC*.txt rolling Serilog file, then flattens them into artifacts/service-logs/ - (e2e.yml:334-349). The glob covers the four services and the UI host (MMCAADCUIWeb<date>.txt), - and the UI host is in the sweep because it runs the Blazor Server circuit (lines 336-337). The search + (e2e.yml:339-352). The glob covers the four services and the UI host (MMCAADCUIWeb<date>.txt), + and the UI host is in the sweep because it runs the Blazor Server circuit (lines 341-342). The search is that broad because the directory each process writes to depends on the working directory Aspire launches it from, and it is needed at all because the AppHost orchestrator log carries only DCP and - dashboard chatter, never the services' own output (lines 338-341). The bundle is uploaded only on - failure() with a 3-day retention (lines 355-365): a green run needs no offline triage, and each + dashboard chatter, never the services' own output (lines 343-346). The bundle is uploaded only on + failure() with a 3-day retention (the upload step at line 360): a green run needs no offline triage, and each browser's diagnostics bundle runs to roughly 350 MB.

      The cross-browser matrix runs the same suite once per engine by varying E2E_BROWSER: chromium, firefox, webkit. Mobile is covered by responsive layout testing (grid to card at a @@ -2273,6 +2299,12 @@

      7. The tiers and the gates that

      + + + + + + @@ -2303,6 +2335,12 @@

      7. The tiers and the gates that

      + + + + + + @@ -2316,30 +2354,30 @@

      7. The tiers and the gates that

      MMCA.ADC.Conference.Application.Tests193196 Handler tests for the Conference controllers' use cases (bulk), including the Sponsors/ create, update, public-filter and mapper tests, the Partners/ and SessionAssets/ subtrees, and the batch event question-answer handler and validator
      MMCA.ADC.Engagement.Application.Tests6570 Bookmark, feedback and live-layer handlers, plus the CheckIns/ subtree (attendee, manual, room and sponsor-visit check-in, badge issue, attendance stats) and the Points/ subtree (awarder, leaderboard, my-points, organizer overview, and the domain/integration-event points handlers)
      MMCA.ADC.Services.Tests717 The gRPC export services and their adapters, with a FakeServerCallContext
      Nothing until it is added to branch protection; the job is written to be promotable
      Real-engine persistence (SQL Server)DockerCommon sqlserver-integrationNothing until it is added to branch protection (MMCA.Common/.github/workflows/ci.yml:930-931)
      AppHost orchestration Dev HTTPS certificate, MMCA_APPHOST_TESTS Common apphost-testing, continue-on-errorMerge
      Load at volume (outbox drain, paged queries)none, temp-file SQLiteCommon load-tests.yml, weekly cron plus manual dispatchNothing, deliberately: a trend signal, not a merge gate
      Cross-repo consumer canary ephemeral SQL Server Common consumer-source-build, building Helpdesk against this PR's framework source

      The accessibility gate (ADR-063)

      -

      MMCA.Common's ui-e2e job (MMCA.Common/.github/workflows/ci.yml:270) builds the out-of-slnx - gallery plus E2E project (ci.yml:284) and runs the axe scans across a chromium, firefox, webkit - matrix (ci.yml:253-254), one engine per leg via E2E_BROWSER (ci.yml:318), with - fail-fast: false (ci.yml:252) so each engine reports independently. All three are required merge +

      MMCA.Common's ui-e2e job (MMCA.Common/.github/workflows/ci.yml:248) builds the out-of-slnx + gallery plus E2E project (ci.yml:287) and runs the axe scans across a chromium, firefox, webkit + matrix (ci.yml:256-257), one engine per leg via E2E_BROWSER (ci.yml:321), with + fail-fast: false (ci.yml:255) so each engine reports independently. All three are required merge checks, three of the eight enumerated in MMCA.Common/CONTRIBUTING.md:60-71 (firefox was promoted - 2026-07-12 and webkit 2026-07-16 after 11 consecutive green main runs, ci.yml:256-258 and + 2026-07-12 and webkit 2026-07-16 after 11 consecutive green main runs, ci.yml:258-260 and CONTRIBUTING.md:63-65). Only the chromium leg collects coverage; the other two run the same command - plain, so the merged report is not engine-dependent (ci.yml:321-326). That file also names the live ruleset as authoritative over its own copy + plain, so the merged report is not engine-dependent (ci.yml:318, run step at ci.yml:322). That file also names the live ruleset as authoritative over its own copy (CONTRIBUTING.md:75-77), which is the right instinct for any list of gates.

      -

      The deployed apps gate the deploy instead: ADC's e2e-gate (MMCA.ADC/.github/workflows/deploy.yml:793) +

      The deployed apps gate the deploy instead: ADC's e2e-gate (MMCA.ADC/.github/workflows/deploy.yml:861) calls the reusable e2e.yml (line 805) with browsers: '["chromium"]' (line 807), and the deploy - job waits on it (deploy.yml:1310,1355). Store's is the same shape at + job waits on it (deploy.yml:1219,1264). Store's is the same shape at MMCA.Store/.github/workflows/deploy.yml:584,594,945.

      The deploy gate is ui-scoped and may legitimately skip. Both apps gate e2e-gate on a ui change - filter (MMCA.ADC/.github/workflows/deploy.yml:804), and the deploy job's condition accepts + filter (MMCA.ADC/.github/workflows/deploy.yml:872), and the deploy job's condition accepts success or skipped for that need while requiring success from every unconditional one - (deploy.yml:1355). That asymmetry is deliberate and was learned the hard way: under default + (deploy.yml:1264). That asymmetry is deliberate and was learned the hard way: under default success() semantics a legitimately skipped e2e-gate cascaded into a skipped deploy, so a - green run shipped nothing (deploy.yml:1329-1331).

      + green run shipped nothing (deploy.yml:1238-1240).

      On ADC the cost of that fix is now bounded rather than open-ended. backend-test-gate - (deploy.yml:450) carries the exact complementary condition and the same success-or-skipped - allowance (deploy.yml:1356), so a skipped e2e-gate means a run backend-test-gate and the + (deploy.yml:501) carries the exact complementary condition and the same success-or-skipped + allowance (deploy.yml:1265), so a skipped e2e-gate means a run backend-test-gate and the invariant "no production deploy without test execution" holds without making either gate - unconditional (deploy.yml:799-803, deploy.yml:1334-1340). What a + unconditional (deploy.yml:867-871, deploy.yml:1243-1249). What a backend-only ADC deploy still ships without is a browser scan: axe did not run on that commit, and the post-deploy smoke gate is the backstop for anything the unit tier cannot see. On MMCA.Store, which has no backend-test-gate, the original exposure remains: a backend-only deploy runs no test tier at @@ -2352,12 +2390,12 @@

      The cross-engine freshness ga hole of the same shape the broker tier once had: the nightly e2e.yml matrix is fail-fast: false with continue-on-error on the non-chromium legs, so those legs could stay red for weeks without blocking a single deploy. The proof was produced and then discarded - (MMCA.ADC/.github/workflows/deploy.yml:1017-1022). cross-browser-freshness (deploy.yml:1039) - closes it the way cross-service-freshness (deploy.yml:933) closes the broker one: it does not run + (MMCA.ADC/.github/workflows/deploy.yml:966-970). cross-browser-freshness (deploy.yml:987) + closes it the way cross-service-freshness (deploy.yml:936) closes the broker one: it does not run the engines, it refuses to deploy unless a recent run passed on each of them.

        -
      • Push-only (deploy.yml:1042), like every other freshness gate: a PR is not a rollout.
      • -
      • A 10-day window (deploy.yml:1048). The two engines alternate on separate weekly crons +
      • Push-only (deploy.yml:990), like every other freshness gate: a PR is not a rollout.
      • +
      • A 10-day window (deploy.yml:1006). The two engines alternate on separate weekly crons (Monday firefox, Thursday webkit), so the window is a 7-day cadence plus slack for a skipped night (the nightly's should-run guard skips a night with no new commits) and for a manual re-run landing late.
      • @@ -2367,13 +2405,16 @@

        The cross-engine freshness ga an engine's red need not red the run, another job's red can red a run in which this engine passed, and the should-run guard can conclude a run success with every leg skipped. Each engine is resolved independently, because their proofs normally come from two different runs, and the older - of the two decides the gate (the per-engine job lookup is at deploy.yml:1085-1098, the - "no run found" failure at deploy.yml:1100). + of the two decides the gate (the step at deploy.yml:995-1000 hands this to MMCA.Common's shared freshness-gate + composite action, deploy.yml:1001, in its per-job mode, + MMCA.Common/.github/actions/freshness-gate/action.yml:44-46, which fails when either engine is + missing or stale).
      • Break-glass is loud, not silent. A workflow_dispatch skip requires a justification; without one the step fails rather than waving the deploy through, and with one it writes a warning and a - step-summary block naming what was not verified (deploy.yml:1064-1070). The skip path exits success + step-summary entry (MMCA.Common/.github/actions/freshness-gate/action.yml:93-106). That logic lives + in the shared action rather than in this file, so ADC and Store run one copy of it (action.yml:7-8). The skip path exits success inside the step, which is why deploy's condition can demand success from this need - unconditionally (deploy.yml:1352).
      • + unconditionally (deploy.yml:1261).

      [Rubric §22, Responsive & Cross-Browser]: §22 assesses whether the app is verified on more than one rendering engine; this gate is what makes the nightly cross-engine matrix enforced coverage rather @@ -2382,11 +2423,11 @@

      The AI scoring evaluation gate (TD

      The AI session scorer is the one place in this repo where behavior can change with no code change: a prompt edit, a model deprecation or a provider-side contract change all move the numbers an organizer uses to accept or decline talks, and every unit test in CI.slnf stays green through - all three (MMCA.ADC/.github/workflows/deploy.yml:479-482). ai-eval-gate (deploy.yml:500) is the + all three (MMCA.ADC/.github/workflows/deploy.yml:530-533). ai-eval-gate (deploy.yml:551) is the behavioral check, and it is split into two tiers by cost.

      Tier 1, golden replay plus prompt contract, always. The step runs the evaluation project with --filter-not-trait "Category=AiEval.Live" and a --minimum-expected-tests 1 floor - (deploy.yml:526-532). No API key and no network: seven recorded proposals in + (deploy.yml:577-583). No API key and no network: seven recorded proposals in Tests/Modules/Conference/MMCA.ADC.Conference.Scoring.Evaluation.Tests/Golden/ are replayed through the real SessionScoringService. The harness is the framework's: GoldenReplayTests (GoldenReplayTests.cs:35) derives from GoldenReplayTestsBase and drives a ReplayChatClient, @@ -2429,22 +2470,22 @@

      The AI scoring evaluation gate (TD calls to the configured provider for each golden proposal, asserting the overall lands in the case's band. It composes the same governed AddMmcaChatClient chain as the host and names no vendor type (LiveJudgeTests.cs:26, LiveJudgeTests.cs:112). The deploy - runs it only when needs.changes.outputs.scoring == 'true' (deploy.yml:535), and that filter + runs it only when needs.changes.outputs.scoring == 'true' (deploy.yml:586), and that filter is deliberately narrow: unlike code and ui it does not fail safe to true on an unrecognized path, because a false positive here spends money on every unrelated deploy while the key-free tier - already runs unconditionally (deploy.yml:154-158, the three matched paths at deploy.yml:160-162). + already runs unconditionally (deploy.yml:162-166, the three matched paths at deploy.yml:168-170). The whole-diff fail-safe still applies one level up: when the push range cannot be determined the - classifier sets scoring=true along with everything else (deploy.yml:95-101, deploy.yml:109-115). + classifier sets scoring=true along with everything else (deploy.yml:99-104, deploy.yml:113-119). Without AI_API_KEY (LiveJudgeTests.cs:41) each case calls Assert.Skip (LiveJudgeTests.cs:69), reported as skipped and never as passed, which is why that step alone - carries no --minimum-expected-tests floor (deploy.yml:536-538). The variable name is + carries no --minimum-expected-tests floor (deploy.yml:587-589). The variable name is provider-neutral to match the Ai:ApiKey configuration key, while the repository secret that feeds - it keeps its ANTHROPIC_API_KEY name because it holds an Anthropic credential (deploy.yml:545-547).

      + it keeps its ANTHROPIC_API_KEY name because it holds an Anthropic credential (deploy.yml:596-598).

      Two choices are worth naming. The gate runs on the same code condition as the CI.slnf tiers rather than the ui/backend split, because the replay tier is cheap and its whole point is catching - what the other two gates cannot see (deploy.yml:496-499, condition at deploy.yml:502); and the + what the other two gates cannot see (deploy.yml:547-550, condition at deploy.yml:553); and the live bands are wide on purpose, because a judge model is not deterministic and a flaky gate gets - ignored (deploy.yml:493-494). + ignored (deploy.yml:544-545). [Rubric §14, Testability & Test Strategy]: §14 assesses whether the suite meaningfully covers the system's real behavior; a deterministic replay of recorded provider responses is how a non-deterministic dependency becomes testable at all, and the corpus-shape fact is how the tier @@ -2460,13 +2501,13 @@

      The performance-regression gate (ADR-060). The answer splits the baseline by measurement stability.

        -
      • A dedicated job measures, then verifies. performance-smoke (MMCA.Common/.github/workflows/ci.yml:377), - named "Performance gate (BenchmarkDotNet Short + baseline verify)" (ci.yml:353), runs the - suite with --filter "*" --job Short --exporters json (ci.yml:385) and then runs build/perfgate - over the exported artifacts (ci.yml:387). --job Short is 3 warmup plus 3 iterations, chosen to +
      • A dedicated job measures, then verifies. performance-smoke (MMCA.Common/.github/workflows/ci.yml:355), + named "Performance gate (BenchmarkDotNet Short + baseline verify)" (ci.yml:356), runs the + suite with --filter "*" --job Short --exporters json (ci.yml:388) and then runs build/perfgate + over the exported artifacts (ci.yml:397). --job Short is 3 warmup plus 3 iterations, chosen to produce real measurements inside a bounded budget; --filter "*" is required because - BenchmarkDotNet otherwise prompts for a selection and would hang the runner (ci.yml:381-384).
      • -
      • The baseline is a committed JSON file, Tests/Performance/perf-baseline.json (ci.yml:387), + BenchmarkDotNet otherwise prompts for a selection and would hang the runner (ci.yml:384-387).
      • +
      • The baseline is a committed JSON file, Tests/Performance/perf-baseline.json (ci.yml:397), not a stored previous run. A gate comparing against the previous run ratchets silently: every PR is only slightly worse than the last, and the sum is invisible. A committed number is a line a reviewer questions in the same PR as the code that spends it.
      • @@ -2483,7 +2524,7 @@

        The performance-regression gate with an instruction to keep [MemoryDiagnoser] on the suite. Vacuity is the failure mode a benchmark gate actually has: a renamed method or a filter selecting nothing would leave a gate that passes while measuring nothing, which is worse than no gate because it reads as evidence - (the verifier invocation is ci.yml:387; the rules it enforces live in MMCA.Common/build/perfgate). + (the verifier invocation is ci.yml:397; the rules it enforces live in MMCA.Common/build/perfgate).

      The job is a required merge gate, not advisory: it is the eighth of the eight contexts listed in MMCA.Common/CONTRIBUTING.md:68-71, which also names raising a ceiling to silence a red gate as @@ -2491,24 +2532,74 @@

      The performance-regression gate

      This gate is MMCA.Common only. The harness, the baseline and the verifier exist in that repo and nowhere else. ADC and Store have no benchmark suite and no perfgate; their performance artifact is a periodic k6 load test against deployed read endpoints whose recency is gated by load-freshness - (MMCA.ADC/.github/workflows/deploy.yml:873), not a pull-request gate. Helpdesk has neither. + (MMCA.ADC/.github/workflows/deploy.yml:909), not a pull-request gate. Helpdesk has neither. Consumers inherit the framework's bounded hot paths through the released packages, not the gate over their own code.

      +

      The weekly load tier (load-tests.yml)

      +

      The benchmark gate answers "did a hot path regress", per pull request, without trusting wall-clock + time. It cannot answer "does the persistence stack hold up at realistic volume", which needs real + rows, a real background processor and real timings. MMCA.Common answers that second question in a + separate workflow, MMCA.Common/.github/workflows/load-tests.yml, over + Tests/Performance/MMCA.Common.LoadTests (14 types, 00-inventory.md:120; the types are rolled up in + group-28-testing-infrastructure.md).

      +
        +
      • Three scenarios, each a correctness assertion plus a timing bound (load-tests.yml:3-8). + OutboxProcessor_Drains10000Messages_ExactlyOnce_AboveThroughputFloor enqueues 10,000 messages + and lets the real OutboxProcessor, resolved from AddInfrastructure as a hosted service, + drain them, asserting exactly-once delivery and a floor of 500 messages per second + (MMCA.Common/Tests/Performance/MMCA.Common.LoadTests/OutboxThroughputLoadTests.cs:22,30,36,63). + PagedQueryLoadTests seeds 100,000 rows (Support/PagedQueryFixture.cs:18), bounds a first page, + a deep page and a filtered, sorted page at 30, 45 and 65 ms, and runs 50 concurrent paged reads + against a 750 ms p95 ceiling (PagedQueryLoadTests.cs:23-25,27,31). Each ceiling carries the local + measurements it was set from in a trailing comment, at about 3x headroom (load-tests.yml:7-8), so + a reviewer moving a number can see what it was measured against.
      • +
      • The database is a temp-file SQLite with connection pooling off. LoadStack points the + framework's real persistence and outbox wiring at a file database (Support/LoadStack.cs:62,66) + and turns Microsoft.Data.Sqlite pooling off, with the flake rate that motivated it recorded in + the comment above the setting (LoadStack.cs:56). No Docker is needed, so the tier runs on a stock + runner.
      • +
      • Schedule plus manual dispatch, never a pull request. The cron is Sunday 04:17 UTC, off-peak and + off the top of the hour where scheduled runs queue up (load-tests.yml:14-17). The header states + why it is neither in ci.yml nor a required check (load-tests.yml:10-12): the project sits + outside MMCA.Common.slnx, so the solution-wide dotnet test --solution never discovers it, and a + timing tier on shared runners is a trend signal, not a merge gate. It is ADR-060's refusal to gate + on absolute latency applied from the other side: where absolute ceilings exist, they stay off the + merge path.
      • +
      • The job keeps ci.yml's posture. Read-only contents permission (load-tests.yml:19-21), a + non-cancelling concurrency group (load-tests.yml:26-28), a 20-minute timeout that bounds a hung + processor or restore against a local run of about 15 seconds plus the build (load-tests.yml:34-35), + full history for MinVer (load-tests.yml:40), the same lock-file cache key as ci.yml + (load-tests.yml:45-49), a --locked-mode restore (load-tests.yml:51-52) and a build by csproj + path, like the other out-of-solution tiers (load-tests.yml:54-56). The run step executes the + self-hosted test executable directly (load-tests.yml:61) with MMCA_LOAD_RESULTS_DIR pointing at + the artifact folder (load-tests.yml:60, read by Support/LoadResults.cs:16).
      • +
      • A missing scenario fails. Each scenario writes its JSON summary before asserting, and the + "Verify every scenario reported" step requires exactly three of them (load-tests.yml:63-69), so a + discovery or filter regression cannot pass as a no-op: the same anti-vacuity rule the perfgate and + every --minimum-expected-tests floor enforce.
      • +
      • The results are kept as a trend. The summaries upload on always(), so a failing run still + leaves its numbers, with a 90-day retention (load-tests.yml:71-78) so runs can be compared over + time.
      • +
      +

      [Rubric §12, Performance & Efficiency]: §12 assesses whether performance is measured at realistic + volume rather than assumed; this tier measures the outbox drain and paged reads at production-like + row counts on a schedule, and keeps the absolute numbers it produces out of the merge path, where + runner noise would turn them into a flaky gate.

      The two cross-repo gates, and MMCA.Helpdesk's own CI

      Two gates run a different repo's code than the one being changed, and both exist because a green build in one repo has repeatedly proved nothing about the other.

      MMCA.Common's consumer canary (consumer-source-build, - MMCA.Common/.github/workflows/ci.yml:494, named "Consumer source build (Helpdesk)" at ci.yml:470) - checks MMCA.Helpdesk out as a sibling (ci.yml:515) and builds it against this PR's framework - source (ci.yml:557), so a breaking public-API change reds here rather than after a release plus a + MMCA.Common/.github/workflows/ci.yml:475, named "Consumer source build (Helpdesk)" at ci.yml:476) + checks MMCA.Helpdesk out as a sibling (ci.yml:526) and builds it against this PR's framework + source (ci.yml:568), so a breaking public-API change reds here rather than after a release plus a lockstep sweep. Helpdesk is the right canary precisely because it is minimal: no database and no GitHub Packages token, and its committed local.props already swaps the MMCA.Common.* package references for project references into ../MMCA.Common/Source. The job also proves the framework's migration path end to end, starting an ephemeral SQL Server - (ci.yml:547), applying the consumer's real EF migrations to it (ci.yml:611) and then asserting the - migrations were recorded and the schema exists (ci.yml:630), and it runs the seed's suite with a - floor of 40 (ci.yml:569). The same-name-branch resolution step described below has its Common-side - twin here (ci.yml:498).

      + (ci.yml:558), applying the consumer's real EF migrations to it (ci.yml:635) and then asserting the + migrations were recorded and the schema exists (ci.yml:654), and it runs the seed's suite with a + floor of 40 (ci.yml:580). The same-name-branch resolution step described below has its Common-side + twin here (ci.yml:509).

      MMCA.Helpdesk's own ci.yml is the other half of that pair, and it is deliberately small. It runs on pull requests and pushes to main (MMCA.Helpdesk/.github/workflows/ci.yml:3-7) with one build-and-test job (line 14) that checks MMCA.Common out as a source companion (line 39) and runs @@ -2541,32 +2632,32 @@

      Coverage floors

      Coverage is enforced as a floor, not reported as a number, and every floor is scoped so it measures the code it claims to measure.

        -
      • MMCA.Common: the coverage job (MMCA.Common/.github/workflows/ci.yml:424) merges the tiers - with ReportGenerator (ci.yml:416) and fails if the unit tier drops below 68.3% line coverage - (ci.yml:445). It gates the +
      • MMCA.Common: the coverage job (MMCA.Common/.github/workflows/ci.yml:402) merges the tiers + with ReportGenerator (ci.yml:422) and fails if the unit tier drops below 68.3% line coverage + (ci.yml:462). It gates the unit tier rather than the merged report because the gallery E2E tier dilutes it, and it excludes generated code (*.generated.cs / *.g.cs) because source generators emit large uncovered files that otherwise tank the number: 45.3% raw versus 61.9% hand-written on the run that prompted the - filter (ci.yml:439-444, assembly and file filters applied at ci.yml:453). It runs only when + filter (ci.yml:445-450, assembly and file filters applied at ci.yml:459). It runs only when build-and-test succeeded, so an upstream failure does - not add a confusing secondary coverage failure (ci.yml:448).
      • + not add a confusing secondary coverage failure (ci.yml:452-454).
      • MMCA.ADC: two floors run inside build-and-test, both PR-only. The global unit-tier floor sits - at 55.5% (MMCA.ADC/.github/workflows/deploy.yml:373), measured over ADC's own code only. + at 55.5% (MMCA.ADC/.github/workflows/deploy.yml:386), measured over ADC's own code only. The comment above it is the part worth reading: the raw cobertura also instruments the consumed framework assemblies (tested in their own repo, near 0% here), plus the service hosts and the protobuf-dominated contracts assemblies, and leaving them in deflated the number to something that measured nothing (26.8% raw, then 52.8% versus 62.5% filtered after the service-host assemblies appeared). The assembly filter is what makes the floor mean what it says, and it is one line: +MMCA.ADC.*;-*.Tests;-MMCA.ADC.*.Service;-MMCA.ADC.*.Contracts - (deploy.yml:371). A second, tighter floor gates Application-layer branch coverage at 77.5% - (deploy.yml:405) over +MMCA.ADC.*.Application only (deploy.yml:396), because the repo-wide + (deploy.yml:384). A second, tighter floor gates Application-layer branch coverage at 77.5% + (deploy.yml:422) over +MMCA.ADC.*.Application only (deploy.yml:413), because the repo-wide average is dominated by UI, Infrastructure and generated code, and because the business decisions - it wants to measure are branches rather than lines (deploy.yml:381-385); the floor sits about two + it wants to measure are branches rather than lines (deploy.yml:392-398); the floor sits about two points under the 79.7% branch coverage measured on the full unit tier, so ordinary churn does not - trip it while a real regression does (deploy.yml:387-390). It carries its own + trip it while a real regression does (deploy.yml:400-403). It carries its own anti-vacuity guard: if the filtered report covers fewer than four assemblies (Conference, Engagement, Identity, Notification) the step fails outright, on the grounds that a filter matching - nothing would make the floor pass vacuously (deploy.yml:398-401).
      • + nothing would make the floor pass vacuously (deploy.yml:414-418).
      • MMCA.Store: the equivalent unit-tier floor sits at 51.6% (MMCA.Store/.github/workflows/deploy.yml:255).
      • MMCA.Helpdesk has no coverage floor at all, and that is consistent with what it is. The seed's @@ -2606,7 +2697,7 @@

        Quick referen §12 Performance & Efficiency - §7 the ADR-060 benchmark baseline gate + §7 the ADR-060 benchmark baseline gate and the weekly load tier §13 Observability & Operability diff --git a/docs/onboarding/group-01-result-error-handling.html b/docs/onboarding/group-01-result-error-handling.html index f1583a46..5cfb739f 100644 --- a/docs/onboarding/group-01-result-error-handling.html +++ b/docs/onboarding/group-01-result-error-handling.html @@ -289,7 +289,7 @@

        Severity, not position, clas ErrorHttpMapping.GetStatusCode(IReadOnlyList<Error>) (MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/ErrorHttpMapping.cs:50-51) and the gRPC side from ToRpcException - (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:117). Every error still + (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:122). Every error still travels in the payload; ranking selects the status only. This is the 2026-08-26 and 2026-08-27 revision of ADR-013, and it is [Rubric §9, API & Contract Design] in the small: the meaning of a failure must not depend on the order @@ -349,19 +349,19 @@

        How a failure becomes an HTTP re

        And how the same failure crosses a service boundary, or reaches the browser

        On the gRPC side, ResultGrpcExtensions carries a mirror of that table, ErrorType to Grpc.Core.StatusCode, in its own FrozenDictionary - (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:35-47, with Unexpected - mapping to StatusCode.Internal at ResultGrpcExtensions.cs:46), reachable as an extension member - ToGrpcStatusCode() (ResultGrpcExtensions.cs:56). A service implementation guards with a single - result.ThrowIfFailure() (ResultGrpcExtensions.cs:69) or takes the value with UnwrapOrThrow() - (ResultGrpcExtensions.cs:86), both raising a + (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:36-48, with Unexpected + mapping to StatusCode.Internal at ResultGrpcExtensions.cs:47), reachable as an extension member + ToGrpcStatusCode() (ResultGrpcExtensions.cs:57). A service implementation guards with a single + result.ThrowIfFailure() (ResultGrpcExtensions.cs:70) or takes the value with UnwrapOrThrow() + (ResultGrpcExtensions.cs:87), both raising a ResultFailureException that the server interceptor GrpcResultExceptionInterceptor (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/Interceptors/GrpcResultExceptionInterceptor.cs:19) - translates through ToRpcException (ResultGrpcExtensions.cs:112): the status comes from - ErrorTypeSeverity.MostSevere (ResultGrpcExtensions.cs:116-117) and every error is still written + translates through ToRpcException (ResultGrpcExtensions.cs:117): the status comes from + ErrorTypeSeverity.MostSevere (ResultGrpcExtensions.cs:121-122) and every error is still written into the trailers as error-{i}-code, error-{i}-message and error-{i}-type entries, plus -source / -target when present, so the far side can rebuild the list - (ResultGrpcExtensions.cs:124-140). See + (ResultGrpcExtensions.cs:129-145). See ADR-007 and Chapter 13.

        The browser gets the same treatment from the other direction. Every HTTP-typed client service in @@ -455,13 +455,13 @@

        Keyset pa source.

        The consumer is the repository layer: EFReadRepository<TEntity, TIdentifierType>.GetPageByCursorAsync - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:617) + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:632) returns a Result<KeysetCollectionResult<TEntity>>, turning an unknown sort column - (EFReadRepository.cs:624-633) or a malformed cursor (EFReadRepository.cs:641-649) into a Result + (EFReadRepository.cs:639-648) or a malformed cursor (EFReadRepository.cs:656-664) into a Result failure rather than an exception, fetching PageSize + 1 rows so the extra row acts as a next-page - probe instead of paying for a COUNT (EFReadRepository.cs:656-662), and encoding the last kept row + probe instead of paying for a COUNT (EFReadRepository.cs:671-677), and encoding the last kept row into the next cursor via KeysetQueryBuilder - (EFReadRepository.cs:667-670). Per + (EFReadRepository.cs:682-685). Per ADR-055 this is a repository-level capability only: it is deliberately not exposed on the HTTP query contract of ADR-034, which still offers @@ -541,8 +541,8 @@

        KeysetCursor

      • TryFromBase64Url (KeysetPagination.cs:195-214) holds the subtle bit and says so in a comment: Base64Url.TryDecodeFromChars throws on an invalid character instead of returning false, and a client-supplied cursor is precisely the input that will contain one, so the method calls Base64Url.IsValid first (KeysetPagination.cs:202-206). An empty string short-circuits to a successful empty decode (KeysetPagination.cs:199-200), which is how a cursor with no sort value round-trips.

    9. -
    10. Why it's built this way: the version prefix plus the total-failure Try contract means a malformed or stale cursor becomes a validation failure the caller can see, never a silent reset to the first page. EFReadRepository<TEntity, TIdentifierType> turns a false return into Error.Validation("Error.InvalidCursor", ...) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:643-648). This is the keyset half of ADR-055.
    11. -
    12. Where it's used: Encode is called once per page by EFReadRepository<TEntity, TIdentifierType> with the last row's sort value and id, both rendered invariantly by KeysetQueryBuilder (EFReadRepository.cs:668-670); TryDecode is called from the same class's TryBuildSeekPredicate to rebuild the seek predicate (EFReadRepository.cs:688).
    13. +
    14. Why it's built this way: the version prefix plus the total-failure Try contract means a malformed or stale cursor becomes a validation failure the caller can see, never a silent reset to the first page. EFReadRepository<TEntity, TIdentifierType> turns a false return into Error.Validation("Error.InvalidCursor", ...) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:658-663). This is the keyset half of ADR-055.
    15. +
    16. Where it's used: Encode is called once per page by EFReadRepository<TEntity, TIdentifierType> with the last row's sort value and id, both rendered invariantly by KeysetQueryBuilder (EFReadRepository.cs:683-685); TryDecode is called from the same class's TryBuildSeekPredicate to rebuild the seek predicate (EFReadRepository.cs:703).
    17. KeysetPageRequest

      @@ -560,8 +560,8 @@

      KeysetPageRequest

    18. [DataContract] on the record and [DataMember(Order = 1..4)] on the four properties (KeysetPagination.cs:19,58,66,70,74) pin the wire order.
    19. -
    20. Why it's built this way: exactly one sort key is supported, with the entity's Id as the tie-break, because that is what keeps the seek predicate a single composable comparison. A null SortColumn keys the page on Id alone (stated on the contract at MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:306-309). The sort column must name a real public property: EFReadRepository<TEntity, TIdentifierType> resolves it through KeysetQueryBuilder and returns an Error.InvalidEntityField copy carrying the offending column name when it does not (EFReadRepository.cs:624-633), so an unknown name is a validation failure rather than a silently ignored parameter. See ADR-055.
    21. -
    22. Where it's used: the sole parameter object of GetPageByCursorAsync, declared on IEntityQuerier<TEntity, TIdentifierType> (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:316-319, inherited by IReadRepository<TEntity, TIdentifierType> at IRepository.cs:330), implemented by EFReadRepository<TEntity, TIdentifierType> (EFReadRepository.cs:617-674) and forwarded by EFReadRepositoryDecorator<TEntity, TIdentifierType> (EFReadRepositoryDecorator.cs:151-152).
    23. +
    24. Why it's built this way: exactly one sort key is supported, with the entity's Id as the tie-break, because that is what keeps the seek predicate a single composable comparison. A null SortColumn keys the page on Id alone (stated on the contract at MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:308-311). The sort column must name a real public property: EFReadRepository<TEntity, TIdentifierType> resolves it through KeysetQueryBuilder and returns an Error.InvalidEntityField copy carrying the offending column name when it does not (EFReadRepository.cs:639-648), so an unknown name is a validation failure rather than a silently ignored parameter. See ADR-055.
    25. +
    26. Where it's used: the sole parameter object of GetPageByCursorAsync, declared on IEntityQuerier<TEntity, TIdentifierType> (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:318-321, inherited by IReadRepository<TEntity, TIdentifierType> at IRepository.cs:332), implemented by EFReadRepository<TEntity, TIdentifierType> (EFReadRepository.cs:632-689) and forwarded by EFReadRepositoryDecorator<TEntity, TIdentifierType> (EFReadRepositoryDecorator.cs:151-152).
    27. Caveats / not-in-source: MaxPageSize and MaxUnboundedResultLimit are two independent constants that happen to be equal. Nothing in source ties them together, so a change to one does not move the other.
    28. PaginationMetadata

      @@ -580,7 +580,7 @@

      PaginationMetadata

    29. Why it's built this way: [DataContract] / [DataMember] / [IgnoreDataMember] make the wire shape explicit and stable, so only the three primary values travel and the rest are recomputed on the other side. Constructor-plus-init validation makes an invalid instance unconstructable, which is the same "validate at the boundary of the type" discipline the value objects of Chapter 2 use.
    30. -
    31. Where it's used: embedded in PagedCollectionResult<T>; serialized whole into the X-Pagination response header by EntityControllerBase<TEntity, TEntityDTO, TIdentifierType> (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:185); the CSV export endpoint on the same base class does not use this type at all, since truncation there is decided by EntityCsvExporter<TEntityDTO>.WriteAsync comparing rows written against the MaxExportRows ceiling (EntityControllerBase.cs:264-286). Built by EntityQueryService<TEntity, TEntityDTO, TIdentifierType> in one private helper (MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:608, called at :332).
    32. +
    33. Where it's used: embedded in PagedCollectionResult<T>; serialized whole into the X-Pagination response header by EntityControllerBase<TEntity, TEntityDTO, TIdentifierType> (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:188); the CSV export endpoint on the same base class does not use this type at all, since truncation there is decided by EntityCsvExporter<TEntityDTO>.WriteAsync comparing rows written against the MaxExportRows ceiling (EntityControllerBase.cs:276-298). Built by EntityQueryService<TEntity, TEntityDTO, TIdentifierType> in one private helper (MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:608, called at :332).
    34. PropertyReader

      @@ -621,7 +621,7 @@

      Error

    35. Three pre-built static readonly singletons for the ubiquitous cases (Error.cs:23,26,29): Error.NotFound, Error.AlreadyDeleted, and Error.InvalidEntityField. Each is itself built through a factory method, so the singletons cannot drift from the factory contract.
    36. Nine factory methods, one per ErrorType member (Error.cs:37,46,55,64,73,82,91,100,114): Validation, Invariant, NotFoundError, Conflict, Unauthorized, Forbidden, UnprocessableEntity, Failure, and Unexpected. Each hard-codes the matching ErrorType, so a caller cannot pair the wrong classification with a code. Two details worth carrying: the factory is NotFoundError rather than NotFound because the static field already owns that name and C# forbids a field and a method sharing one; and Unexpected carries a written usage policy on the factory itself (Error.cs:103-108), namely that it is for a genuine server-side fault the caller cannot fix by changing the request, with business-rule violations routed to Invariant or Failure.
    37. Two with-expression helpers (Error.cs:120-121,126-127): WithSource(string) and WithTarget(string) each return a copy with one component replaced, which is how a generic framework error gets enriched with caller context without being mutated.
    38. -
    39. The singletons are records, so they are also freely refinable at the call site: EFReadRepository<TEntity, TIdentifierType> takes Error.InvalidEntityField with { Message = ..., Source = ..., Target = ... } to report an unknown keyset sort column (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:626-632), keeping the shared Code while specializing everything else.
    40. +
    41. The singletons are records, so they are also freely refinable at the call site: EFReadRepository<TEntity, TIdentifierType> takes Error.InvalidEntityField with { Message = ..., Source = ..., Target = ... } to report an unknown keyset sort column (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:641-647), keeping the shared Code while specializing everything else.
    42. Why it's built this way: modeling an expected failure as data rather than as control flow is the whole point of ADR-013. The pre-built singletons keep the common cases allocation-free, and the factory methods fix the Code to ErrorType pairing at the point of creation so no mapping table is needed anywhere else in the codebase.
    43. @@ -641,8 +641,8 @@

      KeysetCollectionResult<T>

    44. One added property: string? NextCursor { get; init; } tagged [DataMember(Order = 2)] so it serializes after Items (KeysetPagination.cs:106-107). Null means this page is the last one. The doc comment instructs consumers to treat it as opaque and warns that its encoding is versioned (KeysetPagination.cs:102-105).
    45. -
    46. Why it's built this way: nullable-means-last is what lets the repository skip a count entirely. It fetches PageSize + 1 rows as a next-page probe, drops the extra row, and only then emits a cursor (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:656-673); the comment there states the trade explicitly, that a one-row probe is cheaper and more honest than a COUNT over the whole set (EFReadRepository.cs:657-658). See ADR-055.
    47. -
    48. Where it's used: the success payload of GetPageByCursorAsync, declared on IEntityQuerier<TEntity, TIdentifierType> as Task<Result<KeysetCollectionResult<TEntity>>> (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:316) and returned by EFReadRepository<TEntity, TIdentifierType> (EFReadRepository.cs:673). Note the composition: the outcome is a Result, the payload is this envelope, which is the standard pairing throughout the codebase.
    49. +
    50. Why it's built this way: nullable-means-last is what lets the repository skip a count entirely. It fetches PageSize + 1 rows as a next-page probe, drops the extra row, and only then emits a cursor (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:671-688); the comment there states the trade explicitly, that a one-row probe is cheaper and more honest than a COUNT over the whole set (EFReadRepository.cs:672-673). See ADR-055.
    51. +
    52. Where it's used: the success payload of GetPageByCursorAsync, declared on IEntityQuerier<TEntity, TIdentifierType> as Task<Result<KeysetCollectionResult<TEntity>>> (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:318) and returned by EFReadRepository<TEntity, TIdentifierType> (EFReadRepository.cs:688). Note the composition: the outcome is a Result, the payload is this envelope, which is the standard pairing throughout the codebase.
    53. PagedCollectionResult<T>

      @@ -659,7 +659,7 @@

      PagedCollectionResult<T>

    54. Why it's built this way: required plus a null-checking constructor covers both construction routes (object initializer and direct constructor call), and the explicit Order = 2 keeps the wire shape deterministic across the base/derived split, which member-declaration order alone would not guarantee.
    55. -
    56. Where it's used: the payload of every offset-paged read, and a genuinely shared contract rather than a server-only shape. The controller surface declares Task<ActionResult<PagedCollectionResult<TEntityDTO>>> (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/IEntityControllerBase.cs:43, implemented at EntityControllerBase.cs:155) and copies the metadata into the X-Pagination header (EntityControllerBase.cs:185), while the Blazor client deserializes the very same type (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:73,111), so there is no hand-written mirror DTO to drift. EntityQueryService<TEntity, TEntityDTO, TIdentifierType> builds it (EntityQueryService.cs:374-378), and the notification read handlers in MMCA.Common.Application return it too.
    57. +
    58. Where it's used: the payload of every offset-paged read, and a genuinely shared contract rather than a server-only shape. The controller surface declares Task<ActionResult<PagedCollectionResult<TEntityDTO>>> (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/IEntityControllerBase.cs:43, implemented at EntityControllerBase.cs:158) and copies the metadata into the X-Pagination header (EntityControllerBase.cs:188), while the Blazor client deserializes the very same type (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:73,111), so there is no hand-written mirror DTO to drift. EntityQueryService<TEntity, TEntityDTO, TIdentifierType> builds it (EntityQueryService.cs:374-378), and the notification read handlers in MMCA.Common.Application return it too.
    59. ErrorTypeSeverity

      @@ -683,7 +683,7 @@

      ErrorTypeSeverity

    60. Why it's built this way: ADR-013 records the decision and its history. Only the status is ranked: every error still travels in the payload, in the Problem Details errors array on HTTP (MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/ErrorHttpMapping.cs:61-69) and in the gRPC trailers on the other edge. The ranking answers exactly one question, "what is this failure, in one word", and answers it the same way on both transports.

    61. -
    62. Where it's used: HTTP resolves a status through it, ErrorHttpMapping.GetStatusCode(IReadOnlyList<Error>) calling MostSevere (ErrorHttpMapping.cs:50-51) on behalf of ApiControllerBase.HandleFailure (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/ApiControllerBase.cs:48). gRPC does the same in ResultGrpcExtensions.ToRpcException (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:117, falling back to Internal for the impossible empty case). The UI uses the other half of the type: ResultUiExtensions.LocalizedErrorMessages orders the messages it shows a user by Rank descending, so the most severe error is read first (MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/ResultUiExtensions.cs:155).

      +
    63. Where it's used: HTTP resolves a status through it, ErrorHttpMapping.GetStatusCode(IReadOnlyList<Error>) calling MostSevere (ErrorHttpMapping.cs:50-51) on behalf of ApiControllerBase.HandleFailure (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/ApiControllerBase.cs:48). gRPC does the same in ResultGrpcExtensions.ToRpcException (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:122, falling back to Internal for the impossible empty case). The UI uses the other half of the type: ResultUiExtensions.LocalizedErrorMessages orders the messages it shows a user by Rank descending, so the most severe error is read first (MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/ResultUiExtensions.cs:155).

    64. Result

      @@ -761,7 +761,7 @@

      ResultJsonConverterFactory

    65. WriteErrors (ResultJsonConverterFactory.cs:146-153) is the shared writer: it returns without writing anything when the result is a success, and otherwise emits the errors array through the ambient options.
    66. -
    67. Why it's built this way: the Result types deliberately keep internal constructors and get-only properties (the construction discipline described under Result), and the doc comment states the consequence plainly (ResultJsonConverterFactory.cs:7-14): default reflection-based deserialization cannot rehydrate them, so a purpose-built factory is what makes the type round-trippable for the distributed query cache. That cache path is real: CachingQueryDecorator<TQuery, TResult> reads and writes whole handler results (a Result<...> in practice) through ICacheService (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/CachingQueryDecorator.cs:43-48), and DistributedCacheService writes and reads those values as UTF-8 JSON via JsonSerializer (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/DistributedCacheService.cs:154,157). Centralizing the token bookkeeping in ReadObject / WriteErrors and the callback shape in PropertyReader keeps the two concrete converters small and structurally parallel.
    68. +
    69. Why it's built this way: the Result types deliberately keep internal constructors and get-only properties (the construction discipline described under Result), and the doc comment states the consequence plainly (ResultJsonConverterFactory.cs:7-14): default reflection-based deserialization cannot rehydrate them, so a purpose-built factory is what makes the type round-trippable for the distributed query cache. That cache path is real: CachingQueryDecorator<TQuery, TResult> reads and writes whole handler results (a Result<...> in practice) through ICacheService (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/CachingQueryDecorator.cs:43-48), and DistributedCacheService writes and reads those values as UTF-8 JSON via JsonSerializer (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/DistributedCacheService.cs:162,165). Centralizing the token bookkeeping in ReadObject / WriteErrors and the callback shape in PropertyReader keeps the two concrete converters small and structurally parallel.
    70. Where it's used: attached to Result and Result<T> by attribute, so System.Text.Json picks it up automatically wherever a result is serialized, most consequentially on the cache path of Chapter 9 and anywhere a result is written to an HTTP or gRPC payload.
    71. Caveats / not-in-source: the doc comment names Redis specifically (ResultJsonConverterFactory.cs:11-12), but the code path is IDistributedCache-shaped and DistributedCacheService documents Redis only as an example backing store (DistributedCacheService.cs:11). The converter is backing-store agnostic; which store a given host runs is a composition-time choice, not visible here.
    72. diff --git a/docs/onboarding/group-02-domain-building-blocks.html b/docs/onboarding/group-02-domain-building-blocks.html index 827ec8a9..2f939ae4 100644 --- a/docs/onboarding/group-02-domain-building-blocks.html +++ b/docs/onboarding/group-02-domain-building-blocks.html @@ -232,10 +232,10 @@

      The entity chain, one capabili are stamped centrally by AuditSaveChangesInterceptor, which walks ChangeTracker.Entries<IAuditableEntity>() and assigns through entry.Property(...).CurrentValue - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:52-60), - freezes CreatedOn/By as unmodified on updates (AuditSaveChangesInterceptor.cs:67-68), and writes or + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:61-69), + freezes CreatedOn/By as unmodified on updates (AuditSaveChangesInterceptor.cs:77-78), and writes or clears DeletedOn/By only when the soft-delete flag actually transitions - (AuditSaveChangesInterceptor.cs:98-105). Undelete() is deliberately protected + (AuditSaveChangesInterceptor.cs:122-129). Undelete() is deliberately protected (AuditableBaseEntity.cs:89): reversing a soft delete is a per-entity business decision, not a capability the base hands out. The class declares both IAuditableEntity and IRowVersioned (AuditableBaseEntity.cs:13); the latter exists so a repository can @@ -258,25 +258,25 @@

      The entity chain, one capabili
      • SetItems<TChildEntity> replaces a child collection through an overridable ValidateSetItems hook, so a root can veto (say) removing a shipped order line - (AuditableAggregateRootEntity.cs:60-90).
      • + (AuditableAggregateRootEntity.cs:60-92).
      • GetChildOrNotFound<TChild, TChildId> finds an active, non-soft-deleted child by id or returns an Error.NotFound failure - (AuditableAggregateRootEntity.cs:103-120).
      • + (AuditableAggregateRootEntity.cs:105-122).
      • RemoveChildOrNotFound<TChild, TChildId> is that lookup followed by the child's own Delete(), short-circuiting on either failure and handing the deleted child back rather than consuming it, because which domain event a removal raises is aggregate vocabulary the framework must not invent - (AuditableAggregateRootEntity.cs:156-178).
      • + (AuditableAggregateRootEntity.cs:158-180).
      • RestoreChild<TChild, TChildId> brings a soft-deleted child back (BR-135). It takes the child as an instance rather than an id, because a soft-deleted row is excluded by the global query filter and is not reachable through the loaded collection: the caller resolves it with an ignoreQueryFilters read and hands it in. The helper enforces only the "must actually be soft-deleted" rule, calls Reactivate(), and re-adds the child only when the collection does not already carry it - (AuditableAggregateRootEntity.cs:212-249, the duplicate guard at :243). Its TChild is + (AuditableAggregateRootEntity.cs:214-251, the duplicate guard at :243). Its TChild is constrained to IReactivatable, which is exactly how the type system expresses "resurrection is opt-in".
      • DeleteChildren<TChild, TChildId> cascades a soft delete across a child collection, skipping already-deleted children so re-deleting a parent stays idempotent, and combining the failures into one - result (AuditableAggregateRootEntity.cs:273-292).
      • + result (AuditableAggregateRootEntity.cs:275-294).

      RemoveDomainEvents is worth pausing on: it takes out exactly the events the persistence layer captured, matched by reference equality (AuditableAggregateRootEntity.cs:43), because two @@ -292,9 +292,9 @@

      Three opt-in markers beside the c read-only string TenantId (ITenantEntity.cs:39), and marking an entity with it buys two behaviors at once. On reads, a named Tenant global query filter is applied alongside the existing SoftDelete filter (ApplyTenantFilters at - MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:509 - and entity.HasQueryFilter(TenantFilterName, filter) at ApplicationDbContext.cs:567, with the filter - name constant at ApplicationDbContext.cs:472 and the soft-delete filter at ApplicationDbContext.cs:460); + MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:514 + and entity.HasQueryFilter(TenantFilterName, filter) at ApplicationDbContext.cs:572, with the filter + name constant at ApplicationDbContext.cs:477 and the soft-delete filter at ApplicationDbContext.cs:465); named filters compose with AND, so a tenant sees neither another tenant's rows nor soft-deleted ones. On writes, TenantSaveChangesInterceptor stamps the value on insert and refuses a cross-tenant save, which is why the property is read-only on @@ -302,7 +302,7 @@

      Three opt-in markers beside the c 64-character string on purpose, because it arrives from a claim, a header, or configuration, all of which are strings (ITenantEntity.cs:22-25). Adopting tenancy is three things together: marking entities, calling AddMultiTenancy(configuration) - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:266), and setting + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:276), and setting Tenancy:Enabled (ITenantEntity.cs:26-31); a host that never resolves a tenant behaves exactly as it did before (ADR-073).

      IAuditedEntity @@ -378,21 +378,21 @@

      How a domain event leaves an agg :80), and a versioned name leaves room for the upcasting path (ADR-090).

      IHasOrderingKey - (MMCA.Common/Source/Core/MMCA.Common.Domain/Interfaces/IHasOrderingKey.cs:24) is the opt-in ordering + (MMCA.Common/Source/Core/MMCA.Common.Domain/Interfaces/IHasOrderingKey.cs:29) is the opt-in ordering contract: an event returns a key naming the entity whose stream must stay sequential, typically the - aggregate id, or null to opt that individual instance out (IHasOrderingKey.cs:26-30). The outbox + aggregate id, or null to opt that individual instance out (IHasOrderingKey.cs:31-35). The outbox copies the value onto the row it writes (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/OutboxMessage.cs:152) and the processor refuses to claim a row while an earlier unprocessed, non-dead-lettered row carries the same key in the same data source - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:474-475, + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:463-464, with an in-batch guard at :516), so ordering holds across batches and across scaled-out replicas rather than only within one batch. Read the doc comment before adopting it, because the trade-off is explicit: this is head-of-line blocking by design, so keys must be as NARROW as the requirement really is (one key per aggregate serializes that aggregate, a constant key serializes the whole outbox), and a dead-lettered row stops blocking so one poison event cannot freeze its key forever (IHasOrderingKey.cs:15-22). That pairing of a domain-declared intent with an indexed infrastructure - predicate (ApplicationDbContext.cs:559-561) is a compact [Rubric §12, Performance & Scalability] and + predicate (ApplicationDbContext.cs:564-566) is a compact [Rubric §12, Performance & Scalability] and [Rubric §29, Resilience & Business Continuity] example.

      Value objects, invalid instances cannot exist

      The second family models concepts with no identity: two Money(10, USD) are equal because their @@ -474,19 +474,19 @@

      Value objects, invalid ins near-identical: a validated start/end pair with Overlaps, Contains, Deconstruct, and a length/duration accessor (LengthInDays at DateRange.cs:38, Duration at DateTimeRange.cs:39); Create rejects end < start (DateRange.cs:30-35, DateTimeRange.cs:31-36). Read the boundary - rules carefully: Contains is inclusive on both ends (DateRange.cs:55-56) while Overlaps - compares half-open (DateRange.cs:46-50). + rules carefully: Contains is inclusive on both ends (DateRange.cs:56-57) while Overlaps + compares half-open (DateRange.cs:47-51).

      Smart enumerations, a closed set that can carry behavior

      Enumeration<TEnumeration> - (MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:76) is the answer to a + (MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:77) is the answer to a recurring shape a CLR enum handles badly: a closed set of named members that need behavior hanging off them (policies, rates, display rules) instead of a switch statement somewhere else (Enumeration.cs:13-18). Members are declared as public static readonly fields on the derived type and discovered by reflection over that type's own declared fields on first use, then frozen into a ReadOnlyCollection plus two FrozenDictionary lookups keyed by value and by name - (Enumeration.cs:79-87, Enumeration.cs:170); All, FromValue, and FromName read from those - (Enumeration.cs:110, :115, :136). The two resolvers return + (Enumeration.cs:80-88, Enumeration.cs:171); All, FromValue, and FromName read from those + (Enumeration.cs:111, :115, :136). The two resolvers return Result<TEnumeration> with Enumeration.UnknownValue / Enumeration.UnknownName codes rather than throwing, which is the same contract every value-object factory in this group honors. Plain CLR enums stay the default, and this base is the documented opt-in @@ -498,18 +498,18 @@

      Smart enumerati forces every ValueObject derivative to be a sealed record in the Shared layer, which would forbid the static-member idiom this type exists for (Enumeration.cs:30-33). It also does not implement IEquatable<T>, for the same S4035 reason BaseEntity<TIdentifierType> - does not; equality is a type-guarded Equals(object?) override instead (Enumeration.cs:42-47, - Enumeration.cs:157). On the wire, EnumerationJsonConverterFactory - (Enumeration.cs:200) walks the base chain to confirm a type is the self-referencing closed type and no - further derivative (Enumeration.cs:203-204, :213-222), then builds the private nested - EnumerationConverter<TEnumeration> (Enumeration.cs:229), which + does not; equality is a type-guarded Equals(object?) override instead (Enumeration.cs:43-48, + Enumeration.cs:158). On the wire, EnumerationJsonConverterFactory + (Enumeration.cs:201) walks the base chain to confirm a type is the self-referencing closed type and no + further derivative (Enumeration.cs:204-205, :213-222), then builds the private nested + EnumerationConverter<TEnumeration> (Enumeration.cs:230), which writes the member's Name and reads it back through FromName, throwing JsonException on a non-string - token or an unknown name (Enumeration.cs:232-247) exactly the way CurrencyJsonConverter does, so the + token or an unknown name (Enumeration.cs:233-248) exactly the way CurrencyJsonConverter does, so the non-MVC paths (cache, outbox, integration events, typed HttpClient calls) fail the same way MVC model binding does. Note the registration gotcha the doc comment calls out: System.Text.Json reads [JsonConverter] off the type being converted without walking base types, so a concrete enumeration either repeats the attribute or the host registers the factory once in JsonSerializerOptions.Converters - (Enumeration.cs:49-54). This is a [Rubric §9, API & Contract Design] and [Rubric §15, Best Practices & + (Enumeration.cs:50-55). This is a [Rubric §9, API & Contract Design] and [Rubric §15, Best Practices & Code Quality] decision: one serialization shape, chosen once, with the trade-off written down where the next reader will find it.

      Governance markers, metadata that other layers act on

      @@ -520,7 +520,7 @@

      Governance markers Three mechanisms rely on the marker today. First, an architecture fitness test asserts that any entity declaring a [Pii] property also implements IAnonymizable, so every piece of personal data has an erasure path (PiiConventionTests, driven by the shared PiiConventionTestsBase, - at MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/PiiConventionTests.cs:13 over the rule + at MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/PiiConventionTests.cs:20 over the rule body at MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Governance/ArchitectureRules.Governance.cs:11-17; the scan is structurally vacuous inside the framework itself because no data-subject type lives in @@ -530,9 +530,9 @@

      Governance markers "[REDACTED]" token (PiiRedactor.cs:27, PiiRedactor.cs:42-53), offering Redact (a property map), RedactToString (a single-line rendering, PiiRedactor.cs:65), and HasPii (a type probe, PiiRedactor.cs:98). Its per-type reflection metadata is cached in a ConcurrentDictionary of - RedactableProperty descriptors (PiiRedactor.cs:31, PiiRedactor.cs:112-121, - the descriptor itself at PiiRedactor.cs:123), and a property getter that throws is caught and rendered - as "[unreadable]" so a logging call site can never be broken by redaction (PiiRedactor.cs:129-140). + RedactableProperty descriptors (PiiRedactor.cs:31, PiiRedactor.cs:112-124, + the descriptor itself at PiiRedactor.cs:126), and a property getter that throws is caught and rendered + as "[unreadable]" so a logging call site can never be broken by redaction (PiiRedactor.cs:132-143). Third, the audit trail consumes both halves: AuditTrailSaveChangesInterceptor calls PiiRedactor.HasPii per entity type and writes PiiRedactor.RedactedToken on both sides of a @@ -615,8 +615,8 @@

      DomainEntityState

      usage note spells out the convention (EntityChangedEvent.cs:10-13): raise Added from factories, Updated from mutation methods, Deleted from Delete(). Aggregates follow it literally (for example Category at - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/Category.cs:72,95,111 - for the root and Category.cs:144,176,194 for its child items), and handlers short-circuit on it + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/Category.cs:73,97,113 + for the root and Category.cs:146,178,196 for its child items), and handlers short-circuit on it (for example MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Speakers/DomainEventHandlers/SpeakerDeletedHandler.cs:29 and @@ -673,7 +673,7 @@

      DomainHelper

      [Parameter] string route value into the module's identifier alias, for example MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Speakers/SpeakerDetail.razor.cs:98, .../Pages/Session/SessionDetail.razor.cs:104, .../Pages/Event/EventDetail.razor.cs:86, and - MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Pages/Feedback/EventFeedback.razor.cs:254; + MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Pages/Feedback/EventFeedback.razor.cs:284; Store's detail pages import the same namespace (for example MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.UI/Pages/Product/ProductDetail.razor.cs:4). Unit-covered by DomainHelperTests @@ -868,7 +868,7 @@

      IAuditedEntity

      TicketComment deliberately does NOT carry it, and says so (MMCA.Helpdesk/Source/Modules/Tickets/MMCA.Helpdesk.Tickets.Domain/Tickets/TicketComment.cs:12,16). The opting-in hosts are the three ADC services - (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:239, + (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:238, MMCA.ADC.Conference.Service/Program.cs:350, MMCA.ADC.Engagement.Service/Program.cs:198) and the Helpdesk web host (MMCA.Helpdesk/Source/Hosts/MMCA.Helpdesk.Web/Program.cs:78).
    73. Caveats / not-in-source: retention is not automatic. AuditTrailSettings.RetentionDays defaults @@ -935,12 +935,12 @@

      IdValueGeneratedAttribute

      IHasOrderingKey

      -

      MMCA.Common.Domain · MMCA.Common.Domain.Interfaces · MMCA.Common/Source/Core/MMCA.Common.Domain/Interfaces/IHasOrderingKey.cs:24 · Level 0 · interface

      +

      MMCA.Common.Domain · MMCA.Common.Domain.Interfaces · MMCA.Common/Source/Core/MMCA.Common.Domain/Interfaces/IHasOrderingKey.cs:29 · Level 0 · interface

      • What it is: an opt-in contract for a domain or integration event that must be delivered in order relative to other events sharing the same key. One member, string? OrderingKey - (IHasOrderingKey.cs:30), returning a value that identifies the entity whose event stream must stay + (IHasOrderingKey.cs:35), returning a value that identifies the entity whose event stream must stay sequential, typically the aggregate id.
      • Depends on: nothing first-party. Implemented on an event record, most often a BaseIntegrationEvent.
      • @@ -971,16 +971,16 @@

        IHasOrderingKey

      • Why it's built this way: ordering is enforced at claim time rather than at fetch time, which is what makes it survive batching and scale-out - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:363-370). + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:352-359). Making it opt-in per event keeps the unordered fast path free: a batch containing no keyed row runs exactly the query it always ran, with no subquery for the optimizer to prove away - (OutboxProcessor.cs:394-399). The decision is recorded in + (OutboxProcessor.cs:383-388). The decision is recorded in ADR-003 (003-outbox-dual-dispatch.md:142-157).
      • Where it's used: OutboxMessage copies the key onto the row it writes (OutboxMessage.cs:86 for the column, :115 inside FromDomainEvent at :98). OutboxProcessor enforces it in two places: - SelectOrderedCandidates (OutboxProcessor.cs:431-448) keeps at most one row per key in this - cycle's candidate set (:516), and FilterUnblocked (OutboxProcessor.cs:468-478) adds the + SelectOrderedCandidates (OutboxProcessor.cs:420-437) keeps at most one row per key in this + cycle's candidate set (:516), and FilterUnblocked (OutboxProcessor.cs:457-467) adds the NOT EXISTS predicate to the claim update itself, so a second replica racing the same key loses on the row rather than on a check made before the race (:550-554; the retry-count conjunct at :552 is what lets a dead-lettered predecessor stop blocking). The storage side is configured on @@ -988,7 +988,7 @@

        IHasOrderingKey

        non-Unicode column (.../Persistence/DbContexts/ApplicationDbContext.cs:538) and the filtered IX_OutboxMessages_Ordering index over (OrderingKey, OccurredOn), which stays empty for hosts that never declare a key (ApplicationDbContext.cs:554-562). Covered by OutboxProcessorOrderingTests - (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorOrderingTests.cs:101,181,197) + (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Outbox/Processing/OutboxProcessorOrderingTests.cs:100,180,196) and OutboxMessageTests (.../OutboxMessageTests.cs:111,119).
      • Caveats / not-in-source: no event in ADC, Store or Helpdesk implements this interface today; the only implementors in the workspace are test doubles @@ -997,7 +997,7 @@

        IHasOrderingKey

        tested, not exercised by an application event. Ordering is also not total under a timestamp tie: the predecessor test is on OccurredOn alone, so two rows sharing a key and an exact timestamp are ordered within a cycle by Id but neither blocks the other in SQL, which the code states as a - deliberate non-guarantee (OutboxProcessor.cs:372-377).
      • + deliberate non-guarantee (OutboxProcessor.cs:361-366).

      IRowVersioned

      @@ -1014,13 +1014,13 @@

      IRowVersioned

      client sends back the token it last read, and the UPDATE includes it in the WHERE clause. If someone else changed the row in between, zero rows match, EF Core raises DbUpdateConcurrencyException, and the API maps that to 409 Conflict - (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:399-403). + (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:401-405). The interesting design point is why the token needs its own interface at all: the repository's - aggregate-typed overload SetOriginalRowVersion(TEntity, byte[]) (IRepository.cs:406) can only + aggregate-typed overload SetOriginalRowVersion(TEntity, byte[]) (IRepository.cs:408) can only reach the aggregate root, because TEntity is the root type. A child entity edit (a ProductVariant under a Product) would otherwise need a second generic parameter for the child's own identifier type. IRowVersioned erases that identifier type: the child overload - (IRepository.cs:417) accepts any IRowVersioned, so child-level edits get the same stale-token + (IRepository.cs:419) accepts any IRowVersioned, so child-level edits get the same stale-token protection as the root. The doc comment states this rationale and cites ADR-035 (IRowVersioned.cs:3-10).
    74. Walkthrough: one getter, byte[] RowVersion (IRowVersioned.cs:15), wrapped in a scoped @@ -1037,7 +1037,7 @@

      IRowVersioned

      RowVersion property is a private-set byte[] defaulting to [] (AuditableBaseEntity.cs:53), so every auditable entity (aggregate roots and their children) satisfies it. Consumed by IRepository<TEntity, TIdentifierType> - (IRepository.cs:417) and implemented in + (IRepository.cs:419) and implemented in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:86-93, which casts the child to object, walks to _context.Entry(...).Property(nameof(AuditableBaseEntity<>.RowVersion)) and assigns OriginalValue; @@ -1079,8 +1079,8 @@

      ITenantEntity

      claim, a header, or configuration, all of which are strings, so a stronger domain type would only add a conversion at every boundary without adding a guarantee. The cap is enforced at the model (TenantIdMaxLength = 64 at - MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:478, - applied via IsRequired().HasMaxLength(...).IsUnicode(false) at ApplicationDbContext.cs:526-529).
    75. + MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:483, + applied via IsRequired().HasMaxLength(...).IsUnicode(false) at ApplicationDbContext.cs:531-534).
    76. Marking is host-gated in practice (ITenantEntity.cs:26-31): a host that never resolves a tenant behaves exactly as it did before. Adopting tenancy is marking entities, calling AddMultiTenancy(configuration), and setting Tenancy:Enabled.
    77. @@ -1095,7 +1095,7 @@

      ITenantEntity

      (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/TenantSaveChangesInterceptor.cs:29-34).
    78. Where it's used: the read side is ApplicationDbContext.ApplyTenantFilters - (ApplicationDbContext.cs:509-569, called from OnModelCreating at :334), which selects every + (ApplicationDbContext.cs:514-574, called from OnModelCreating at :334), which selects every non-owned ITenantEntity type (:439-441), indexes the discriminator on non-Cosmos engines, widening it to (TenantId, IsDeleted) when the entity is also auditable (:457-466), and installs the named filter CurrentTenantId == null || EF.Property<string>(e, "TenantId") == CurrentTenantId @@ -1108,7 +1108,7 @@

      ITenantEntity

      CrossTenantWriteException rather than writing a row nobody can read (:101-110), and a declared-versus-current mismatch is rejected the same way (:123). The host gate is AddMultiTenancy - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:266) plus + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:276) plus TenancySettings (Tenancy:Enabled at MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettings.cs:67, claim-then-header resolution order at TenancySettings.cs:57). The only entities marked in the workspace apps today @@ -1119,7 +1119,7 @@

      ITenantEntity

      today.
    79. Caveats / not-in-source: Tenancy:Enabled gates resolution, not isolation. The filter and the interceptor are always registered and are inert whenever no tenant is resolved - (TenancySettings.cs:37-39, and the registration note at DependencyInjection.cs:290), so an + (TenancySettings.cs:37-39, and the registration note at DependencyInjection.cs:300), so an untenanted code path (a job, a seeder) reads every tenant's rows by design. That is the documented behavior, not an oversight, but it means "tenant safety" is a property of the request pipeline resolving a tenant, not of the entity marker alone.
    80. @@ -1175,7 +1175,7 @@

      PiiAttribute

      PiiConventionTestsBase (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/PiiConventionTestsBase.cs:7) that just passes its IArchitectureMap: - MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/PiiConventionTests.cs:13 (the scan + MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/PiiConventionTests.cs:20 (the scan is structurally vacuous today, the framework Domain ships no data-subject type), MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Governance/PiiConventionTests.cs:3, and MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/PiiConventionTests.cs:3. The framework @@ -1197,7 +1197,7 @@

      PiiAttribute

      RedactableProperty

      -

      MMCA.Common.Domain · MMCA.Common.Domain.Privacy · MMCA.Common/Source/Core/MMCA.Common.Domain/Privacy/PiiRedactor.cs:123 · Level 0 · class (private sealed, nested)

      +

      MMCA.Common.Domain · MMCA.Common.Domain.Privacy · MMCA.Common/Source/Core/MMCA.Common.Domain/Privacy/PiiRedactor.cs:126 · Level 0 · class (private sealed, nested)

      • What it is: PiiRedactor's private sealed nested cached-metadata helper, one @@ -1206,16 +1206,16 @@

        RedactableProperty

        the redactor.
      • Depends on: System.Reflection.PropertyInfo (BCL); constructed by PiiRedactor.
      • Walkthrough: a primary-constructor class - RedactableProperty(string name, bool isPii, PropertyInfo info) (PiiRedactor.cs:123) exposing - Name (PiiRedactor.cs:125), the precomputed IsPii flag (PiiRedactor.cs:127), and - Read(object target) (PiiRedactor.cs:129), which calls info.GetValue(target) and catches + RedactableProperty(string name, bool isPii, PropertyInfo info) (PiiRedactor.cs:126) exposing + Name (PiiRedactor.cs:128), the precomputed IsPii flag (PiiRedactor.cs:130), and + Read(object target) (PiiRedactor.cs:132), which calls info.GetValue(target) and catches TargetInvocationException to return UnreadableToken rather than propagate, the inline comment - noting that a throwing getter must never break a logging call site (PiiRedactor.cs:131-139).
      • + noting that a throwing getter must never break a logging call site (PiiRedactor.cs:134-142).
      • Why it's built this way: precomputing the IsPii flag and holding the PropertyInfo once per type (cached in PiiRedactor.Cache) means redaction never re-evaluates the [Pii] reflection check on the hot path, it just reads the cached flag and (for non-PII members) invokes the captured getter.
      • Where it's used: produced and consumed entirely within PiiRedactor - (GetProperties, PiiRedactor.cs:112-121); it has no independent consumers.
      • + (GetProperties, PiiRedactor.cs:112-124); it has no independent consumers.

      IAggregateRoot

      @@ -1275,7 +1275,7 @@

      PiiRedactor

    81. What it is: a static helper that produces a log- and telemetry-safe view of any object by masking every property marked with PiiAttribute, replacing each PII value with the literal [REDACTED]. It is the redaction half of the PiiAttribute contract.
    82. -
    83. Depends on: PiiAttribute (the marker it reads, PiiRedactor.cs:6,119); BCL +
    84. Depends on: PiiAttribute (the marker it reads, PiiRedactor.cs:6,122); BCL only (System.Reflection, System.Collections.Concurrent, System.Collections.ObjectModel, System.Text, System.Globalization).
    85. Concept introduced, value-erasing PII redaction for logs/telemetry. [Rubric §13, Observability & Operability] (assesses keeping personal data out of structured logs) and [Rubric §30, Compliance, Privacy & Data Governance] (assesses a real data-minimization control, not just an @@ -1311,8 +1311,14 @@

      PiiRedactor

    86. GetProperties(Type) (PiiRedactor.cs:112): the cache filler. Cache.GetOrAdd runs a static lambda that reflects public, instance, readable, non-indexer properties and builds a RedactableProperty for each, recording whether it carries the marker via - p.IsDefined(typeof(PiiAttribute), inherit: false) (PiiRedactor.cs:112-121). The inherit: false - mirrors PiiAttribute's Inherited = false.
    87. + Attribute.IsDefined(p, typeof(PiiAttribute), inherit: true) (PiiRedactor.cs:112-124, the check + at line 122). The static Attribute.IsDefined is used rather than the PropertyInfo instance + method because the instance method ignores inherit for properties, while the static one walks an + override back to its base declaration (PiiRedactor.cs:119-121): a derived type that overrides a + [Pii] property without repeating the marker stays redacted (asserted at PiiRedactorTests.cs:51). + This matches PiiAttribute's Inherited = true + (MMCA.Common/Source/Core/MMCA.Common.Domain/Attributes/PiiAttribute.cs:18), and + AuditTrailSaveChangesInterceptor resolves the marker the same way (AuditTrailSaveChangesInterceptor.cs:504).
    88. Why it's built this way: a static pure helper has no DI dependency, so it can be called from @@ -1324,7 +1330,7 @@

      PiiRedactor

      calls HasPii once per changed entity type (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:287) and writes RedactedToken into both the OldValue and NewValue columns of a [Pii] property's - change row (:309-310), which is how the change history avoids becoming a second copy of personal + change row (:310-311), which is how the change history avoids becoming a second copy of personal data (ADR-075); that behavior is asserted in AuditTrailSaveChangesInterceptorTests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailSaveChangesInterceptorTests.cs:180-181). @@ -1333,14 +1339,14 @@

      PiiRedactor

      end to end (composed with IAnonymizable) by PiiErasureContractFitnessTests (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/PiiErasureContractFitnessTests.cs:19).
    89. Caveats / not-in-source: Redact and RedactToString have no production call site; only - tests invoke them (PiiRedactorTests.cs:35,46,53,58,68 and PiiErasureContractFitnessTests.cs:29,42,46,69), + tests invoke them (PiiRedactorTests.cs:51,56,67,74,79,89 and PiiErasureContractFitnessTests.cs:29,42,46,69), so the log-side control is ready and tested but opt-in per call site rather than an automatic pipeline stage. Redaction is also shallow (one level), as the remarks state (PiiRedactor.cs:19): a non-PII property whose value is itself an object with nested [Pii] members is read and emitted as-is, not recursively masked. Only public instance properties are inspected (PiiRedactor.cs:115), so fields and non-public members are ignored. A property getter that throws TargetInvocationException yields [unreadable] instead of crashing the log call - (PiiRedactor.cs:135-139).
    90. + (PiiRedactor.cs:138-142).

      IAnonymizable

      @@ -1415,14 +1421,14 @@

      IReactivatable

      interface teeth: the aggregate helper AuditableAggregateRootEntity<TIdentifierType>.RestoreChild<TChild, TChildId> constrains its child to where TChild : AuditableBaseEntity<TChildId>, IReactivatable - (MMCA.Common/Source/Core/MMCA.Common.Domain/Entities/AuditableAggregateRootEntity.cs:212-218). A + (MMCA.Common/Source/Core/MMCA.Common.Domain/Entities/AuditableAggregateRootEntity.cs:214-220). A child that does not implement the interface simply cannot be passed to the helper: resurrection is a business decision per entity, not a capability the base class hands out to every soft-deletable row.
    91. Why it's built this way: RestoreChild shows the payoff. It checks only the framework-level rule - ("this candidate is soft-deleted", AuditableAggregateRootEntity.cs:223-231), delegates the entity's + ("this candidate is soft-deleted", AuditableAggregateRootEntity.cs:225-233), delegates the entity's own rule to child.Reactivate() and propagates its failure verbatim (:233-237), then re-adds the child to the aggregate's collection only when it is not already there, because a caller who resolved the child through an ignoreQueryFilters read holds an instance the loaded collection never @@ -1718,7 +1724,7 @@

      EmailInvariants

      Enumeration<TEnumeration>

      -

      MMCA.Common.Shared · MMCA.Common.Shared.ValueObjects · MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:76 · Level 3 · class (abstract, generic)

      +

      MMCA.Common.Shared · MMCA.Common.Shared.ValueObjects · MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:77 · Level 3 · class (abstract, generic)

      • What it is: the abstract base for a smart enumeration: a closed set of named, integer-valued @@ -1728,48 +1734,48 @@

        Enumeration<TEnumeration>

      • Depends on: Error, Result, and EnumerationJsonConverterFactory (mutual: the base carries - [JsonConverter(typeof(EnumerationJsonConverterFactory))] at Enumeration.cs:71 while the factory + [JsonConverter(typeof(EnumerationJsonConverterFactory))] at Enumeration.cs:72 while the factory is constrained on Enumeration<T>). Externals: System.Collections.Frozen, System.Collections.ObjectModel, System.Reflection, System.Text.Json.
      • Concept introduced, the self-referencing generic (curiously recurring) constraint. [Rubric §4, DDD] (a closed domain vocabulary that carries behaviour) and [Rubric §1, SOLID] (open for extension: adding a member is a field, not a new case in every switch). The declaration is abstract class Enumeration<TEnumeration> where TEnumeration : Enumeration<TEnumeration> - (Enumeration.cs:76-77). The type parameter is the concrete type itself, which is what lets All, + (Enumeration.cs:77-78). The type parameter is the concrete type itself, which is what lets All, FromValue and FromName be per-enumeration and strongly typed: Priority.FromValue(2) returns Result<Priority> with no type argument written by hand, and each closed type gets its own static lookup tables (static fields on a generic type are per-constructed-type). The - CA1000 suppression (Enumeration.cs:72-75) exists for exactly this and states the reasoning: a + CA1000 suppression (Enumeration.cs:73-76) exists for exactly this and states the reasoning: a non-generic sibling would return the base type and force a cast at every call site.
      • -
      • Concept introduced, lazy reflection frozen into a lookup. [Rubric §12, Performance & Scalability]. Three Lazy<T> statics (Enumeration.cs:79-87) build the member set once per closed +
      • Concept introduced, lazy reflection frozen into a lookup. [Rubric §12, Performance & Scalability]. Three Lazy<T> statics (Enumeration.cs:80-88) build the member set once per closed type on first touch: MembersLazy runs DiscoverMembers, ByValueLazy and ByNameLazy project it into FrozenDictionary instances (the name dictionary using StringComparer.OrdinalIgnoreCase). FrozenDictionary is the right structure for a build-once, read-forever table: construction is more expensive, lookups are faster than Dictionary. ToFrozenDictionary also throws ArgumentException on a duplicate key, which turns two members sharing a Value or a Name into a fail-fast at first - use rather than a silent shadowing bug (Enumeration.cs:35-40).
      • + use rather than a silent shadowing bug (Enumeration.cs:35-41).
      • Walkthrough
          -
        • protected Enumeration(int value, string name) (Enumeration.cs:92): the only constructor; +
        • protected Enumeration(int value, string name) (Enumeration.cs:93): the only constructor; derived types keep theirs private and expose members as static fields.
        • -
        • Name (Enumeration.cs:100) and Value (Enumeration.cs:104): getter-only, tagged +
        • Name (Enumeration.cs:101) and Value (Enumeration.cs:105): getter-only, tagged [DataMember(Order = 1)] and [DataMember(Order = 2)] under the class-level [DataContract] - (Enumeration.cs:70). The split is intentional and documented: Value is the persisted + (Enumeration.cs:71). The split is intentional and documented: Value is the persisted representation, Name is the serialized/display one.
        • -
        • All (Enumeration.cs:110): IReadOnlyCollection<TEnumeration>, ordered by Value, cached for +
        • All (Enumeration.cs:111): IReadOnlyCollection<TEnumeration>, ordered by Value, cached for the lifetime of the closed type.
        • -
        • FromValue(int value) (Enumeration.cs:120): TryGetValue on the frozen by-value map, else +
        • FromValue(int value) (Enumeration.cs:121): TryGetValue on the frozen by-value map, else Error.Invariant(code: "Enumeration.UnknownValue", ...) naming the concrete type in the message - (Enumeration.cs:125-129).
        • -
        • FromName(string name) (Enumeration.cs:141): the case-insensitive twin, null-coalescing the - argument to string.Empty first (Enumeration.cs:143), else + (Enumeration.cs:126-130).
        • +
        • FromName(string name) (Enumeration.cs:142): the case-insensitive twin, null-coalescing the + argument to string.Empty first (Enumeration.cs:144), else Error.Invariant(code: "Enumeration.UnknownName", ...).
        • -
        • ToString() (Enumeration.cs:154): returns Name.
        • -
        • Equals(object?) (Enumeration.cs:157-160) and GetHashCode() (Enumeration.cs:163): +
        • ToString() (Enumeration.cs:155): returns Name.
        • +
        • Equals(object?) (Enumeration.cs:158-161) and GetHashCode() (Enumeration.cs:164): type-guarded equality, GetType() == other.GetType() && Value == other.Value, hashed as HashCode.Combine(GetType(), Value). The class deliberately does not implement - IEquatable<T>: the remark at Enumeration.cs:41-47 cites Sonar S4035 (an unsealed + IEquatable<T>: the remark at Enumeration.cs:42-48 cites Sonar S4035 (an unsealed IEquatable<T> breaks the equality contract for subclasses) and leaves that to a sealed derived type. [Rubric §15, Best Practices & Code Quality].
        • -
        • DiscoverMembers() (Enumeration.cs:170-179): reflects over +
        • DiscoverMembers() (Enumeration.cs:171-180): reflects over BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly, keeps fields that are IsInitOnly (that is, readonly) and assignable to TEnumeration, reads their values, orders by Value and freezes to a ReadOnlyCollection. DeclaredOnly is the load-bearing flag: a derived @@ -1797,18 +1803,18 @@

          Enumeration<TEnumeration>

          types in the workspace are the fixtures in MMCA.Common.Shared.Tests/ValueObjects/EnumerationTests.cs and EnumerationSerializationTests.cs, plus MMCA.Common.Infrastructure.Tests/Persistence/Conversions/EnumerationValueConverterTests.cs. Treat - the Priority sample in the doc comment (Enumeration.cs:55-67) as the usage template.
        • + the Priority sample in the doc comment (Enumeration.cs:56-68) as the usage template.

        EnumerationConverter<TEnumeration>

        -

        MMCA.Common.Shared · MMCA.Common.Shared.ValueObjects · MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:229 · Level 3 · class (private nested, sealed)

        +

        MMCA.Common.Shared · MMCA.Common.Shared.ValueObjects · MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:230 · Level 3 · class (private nested, sealed)

        • What it is: the actual JsonConverter<TEnumeration> for one closed enumeration type. It is a private nested class inside - EnumerationJsonConverterFactory (Enumeration.cs:229-248), + EnumerationJsonConverterFactory (Enumeration.cs:230-249), constrained the same way as the base: where TEnumeration : Enumeration<TEnumeration> - (Enumeration.cs:230).
        • + (Enumeration.cs:231).
        • Depends on: Enumeration<TEnumeration> (it calls Enumeration<TEnumeration>.FromName) and System.Text.Json.
        • Concept, the generic worker behind a converter factory. [Rubric §2, Design Patterns] @@ -1817,54 +1823,54 @@

          EnumerationConverter<TEnumeration&g to obtain one is through CreateConverter, which guarantees the generic argument is a legal closed enumeration.

        • Walkthrough
            -
          • Read (Enumeration.cs:232): rejects a non-string token with +
          • Read (Enumeration.cs:233): rejects a non-string token with throw new JsonException($"{typeof(TEnumeration).Name} must be a string.") - (Enumeration.cs:234-235), reads the string (Enumeration.cs:237), resolves it through - Enumeration<TEnumeration>.FromName(name) (Enumeration.cs:239) and throws a naming - JsonException when that fails (Enumeration.cs:240-241). Identical failure behaviour to + (Enumeration.cs:235-236), reads the string (Enumeration.cs:238), resolves it through + Enumeration<TEnumeration>.FromName(name) (Enumeration.cs:240) and throws a naming + JsonException when that fails (Enumeration.cs:241-242). Identical failure behaviour to CurrencyJsonConverter, which is deliberate.
          • -
          • Write (Enumeration.cs:246-247): writer.WriteStringValue(value.Name). The wire shape is the +
          • Write (Enumeration.cs:247-248): writer.WriteStringValue(value.Name). The wire shape is the member name, never the integer, so a JSON payload stays readable and a renumbering is not a breaking API change (the integer is the persistence representation, handled by EnumerationValueConverter<TEnumeration>).
        • Where it's used: instantiated reflectively by - EnumerationJsonConverterFactory.CreateConverter (Enumeration.cs:207-209). It has no other + EnumerationJsonConverterFactory.CreateConverter (Enumeration.cs:208-210). It has no other caller and no public surface.

        EnumerationJsonConverterFactory

        -

        MMCA.Common.Shared · MMCA.Common.Shared.ValueObjects · MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:200 · Level 3 · class (sealed)

        +

        MMCA.Common.Shared · MMCA.Common.Shared.ValueObjects · MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:201 · Level 3 · class (sealed)

        • What it is: a JsonConverterFactory that hands System.Text.Json a EnumerationConverter<TEnumeration> for any concrete smart enumeration, so every member serializes as its Name.
        • Depends on: Enumeration<TEnumeration> (mutual: the base type - carries [JsonConverter(typeof(EnumerationJsonConverterFactory))] at Enumeration.cs:71), + carries [JsonConverter(typeof(EnumerationJsonConverterFactory))] at Enumeration.cs:72), EnumerationConverter<TEnumeration>, and System.Text.Json.Serialization.
        • Concept introduced, why an open generic needs a factory. [Rubric §9, API & Contract Design]. A JsonConverter<T> is closed over one T; there is no way to write one converter that serves Priority, Severity and every future enumeration. JsonConverterFactory is the System.Text.Json extension point for exactly that: CanConvert answers "is this type mine?" and CreateConverter builds the closed converter on demand. There is a second, subtler reason the - factory has to exist at all, documented at Enumeration.cs:48-54 and again at Enumeration.cs:189-193: + factory has to exist at all, documented at Enumeration.cs:49-55 and again at Enumeration.cs:190-194: System.Text.Json reads [JsonConverter] off the type it is converting without walking base types, so the attribute on Enumeration<T> does not reach Priority. A host therefore either repeats the attribute on each concrete type or registers this factory once. AddAPI takes the second route (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:59, with the inline comment explaining the inherit: false behaviour).
        • Walkthrough
            -
          • CanConvert(Type typeToConvert) (Enumeration.cs:203-204): +
          • CanConvert(Type typeToConvert) (Enumeration.cs:204-205): GetEnumerationArgument(typeToConvert) == typeToConvert. Read that carefully: it is true only when the type is the type argument of its own Enumeration<T> base, that is, only for the self-referencing closed type. A class deriving further from a concrete enumeration is left to the - default converter rather than being silently serialized as its base (Enumeration.cs:211-217).
          • -
          • CreateConverter(...) (Enumeration.cs:207-209): + default converter rather than being silently serialized as its base (Enumeration.cs:212-218).
          • +
          • CreateConverter(...) (Enumeration.cs:208-210): Activator.CreateInstance(typeof(EnumerationConverter<>).MakeGenericType(typeToConvert)) cast to JsonConverter. Reflection runs once per type; System.Text.Json caches the resulting converter.
          • -
          • GetEnumerationArgument(Type?) (Enumeration.cs:218-227): walks type.BaseType upward looking +
          • GetEnumerationArgument(Type?) (Enumeration.cs:219-228): walks type.BaseType upward looking for a generic type whose definition is typeof(Enumeration<>), returning its single generic argument, or null at the top of the chain.
          @@ -1873,12 +1879,12 @@

          EnumerationJsonConverterFactory

          uniform name-based JSON for every enumeration across the whole API surface, including the non-MVC paths (cache entries, outbox payloads, integration events, typed HttpClient calls) that never see MVC model binding. The HandleNull default of false is left alone on purpose - (Enumeration.cs:194-198) so nullable members still deserialize to null.
        • + (Enumeration.cs:195-199) so nullable members still deserialize to null.
        • Where it's used: registered in AddAPI (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:59), named in that method's doc comment alongside CurrencyJsonConverter (.../DependencyInjection.cs:30-31); also reachable via the [JsonConverter] attribute on - Enumeration<TEnumeration> (Enumeration.cs:71) for a member typed as + Enumeration<TEnumeration> (Enumeration.cs:72) for a member typed as the base itself.

        PhoneNumberInvariants

        @@ -1978,12 +1984,12 @@

        AuditableBaseEntity<TIdentifierTy CategoryItem, SessionSpeaker; Store's OrderLine, ShoppingCartItem). The stamping side is implemented by AuditSaveChangesInterceptor - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:104-105) + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:128-129) and covered by AuditableBaseEntityTests and AuditableBaseEntityAdditionalTests.

      • Caveats / not-in-source: the delete stamps are written only on a transition of the flag - (AuditSaveChangesInterceptor.cs:98-102), so updating an already-deleted row keeps the stamps of + (AuditSaveChangesInterceptor.cs:122-126), so updating an already-deleted row keeps the stamps of the delete that produced it rather than refreshing them.

      Email

      @@ -1996,13 +2002,12 @@

      Email

      constructor exists for JSON round-tripping only.
    92. Depends on: ValueObject (Level 0), EmailInvariants (Level 3), Result<T> (Level 2).
    93. -
    94. Concept introduced, normalization at construction plus implicit conversion. [Rubric §4, Domain-Driven Design] (rich value objects with invariant-protected construction). Three ideas +
    95. Concept introduced, normalization at construction. [Rubric §4, Domain-Driven Design] (rich value objects with invariant-protected construction). Three ideas combine here: (1) the [JsonConstructor]-tagged private constructor (Email.cs:22-23) keeps ad-hoc construction out while letting System.Text.Json rehydrate; (2) Create validates and - normalizes, returning Result<Email> instead of throwing; (3) - public static implicit operator string(Email email) (Email.cs:45) lets an Email drop into a - string position without a cast, a pragmatic bridge for code that has not adopted the value object - yet. The #pragma warning disable CA1308 around ToLowerInvariant (Email.cs:38-40) is a scoped + normalizes, returning Result<Email> instead of throwing; (3) there is no implicit conversion to + string, so leaving the value object is always explicit, through Value or ToString() + (Email.cs:44). The #pragma warning disable CA1308 around ToLowerInvariant (Email.cs:38-40) is a scoped suppression with its justification on the same line ("Email addresses are conventionally lowercase per RFC 5321"). [Rubric §15, Best Practices & Code Quality] (suppressions are narrow and explained, never blanket).
    96. @@ -2014,7 +2019,7 @@

      Email

      (Email.cs:34), propagates result.Errors on failure (Email.cs:36), and only then lowercases (Email.cs:39). Order matters: validation runs on the trimmed input, normalization on the validated value. -
    97. implicit operator string (Email.cs:45) and ToString() (Email.cs:48): both return Value.
    98. +
    99. ToString() (Email.cs:44): returns Value; there is no implicit conversion to string.
    100. Why it's built this way: normalizing once at construction means the rest of the system can @@ -2046,7 +2051,7 @@

      PhoneNumber

    101. Depends on: ValueObject (Level 0), PhoneNumberInvariants (Level 3), Result<T> (Level 2).
    102. -
    103. Concept: the same private-constructor + static-factory + implicit-conversion shape taught under +
    104. Concept: the same private-constructor + static-factory shape taught under Email; this section cross-references rather than repeating it. One difference worth noting: there is no case normalization (a phone number has no case), and Create validates the raw string then stores value.Trim() (PhoneNumber.cs:32,36), whereas Email.Create trims @@ -2057,8 +2062,8 @@

      PhoneNumber

      [DataContract] at PhoneNumber.cs:15); the [JsonConstructor] private constructor (PhoneNumber.cs:22-23); Create(string value) (PhoneNumber.cs:30) delegating to PhoneNumberInvariants.EnsurePhoneNumberIsValid and returning Result.Failure<PhoneNumber> with - the propagated errors (PhoneNumber.cs:34); the implicit operator string - (PhoneNumber.cs:41) and ToString() (PhoneNumber.cs:44).
    105. + the propagated errors (PhoneNumber.cs:34); and ToString() (PhoneNumber.cs:40), which + returns Value (there is no implicit conversion to string).
    106. Why it's built this way: as with Email, the remarks (PhoneNumber.cs:7-14) specify HasConversion rather than OwnsOne so the column stays nvarchar, and point at the shipped PhoneNumberValueConverter @@ -2104,34 +2109,37 @@

      AuditableAggregateRootEntit two structurally equal events raised separately are still two distinct occurrences (:41-42). An empty input returns early (:44-47).

    107. SetItems<TChildEntity>(List<TChildEntity>, IEnumerable<TChildEntity>) - (AuditableAggregateRootEntity.cs:60-74): materializes the incoming sequence once to avoid - double enumeration (:69), calls the validation hook, then Clear() + AddRange() on the - same list instance (:72-73). Never replacing the list reference is what keeps EF change - tracking able to see the adds and removes.
    108. -
    109. ValidateSetItems<TChildEntity> (AuditableAggregateRootEntity.cs:85-90): protected virtual, + (AuditableAggregateRootEntity.cs:60-76): always materializes the incoming sequence into a + fresh List<TChildEntity> (:71), which both avoids double enumeration and guards the + case where the caller passes the backing collection itself (or a read-only view over it): an + in-place reuse would be emptied by the Clear() before AddRange could read it (:69-70). + It then calls the validation hook and runs Clear() + AddRange() on the same list + instance (:74-75). Never replacing the list reference is what keeps EF change tracking able + to see the adds and removes.
    110. +
    111. ValidateSetItems<TChildEntity> (AuditableAggregateRootEntity.cs:87-92): protected virtual, empty by default. The extension point for rules such as "a fulfilled order line cannot be removed".
    112. -
    113. GetChildOrNotFound<TChild, TChildId> (AuditableAggregateRootEntity.cs:103-120): a +
    114. GetChildOrNotFound<TChild, TChildId> (AuditableAggregateRootEntity.cs:105-122): a FirstOrDefault over the in-memory collection matching on Id.Equals(childId) && !c.IsDeleted - (:110), returning Error.NotFound with source and target rather than throwing or returning + (:112), returning Error.NotFound with source and target rather than throwing or returning null.
    115. -
    116. RemoveChildOrNotFound<TChild, TChildId> (AuditableAggregateRootEntity.cs:156-178): the +
    117. RemoveChildOrNotFound<TChild, TChildId> (AuditableAggregateRootEntity.cs:158-180): the lookup above followed by the child's own Delete(), short-circuiting on either failure. The deleted child comes back in the result rather than being consumed here, because which domain event a removal raises is aggregate vocabulary and therefore the caller's decision - (:129-145 shows the intended call shape).
    118. -
    119. RestoreChild<TChild, TChildId> (AuditableAggregateRootEntity.cs:212-249): constrained - where TChild : AuditableBaseEntity<TChildId>, IReactivatable (:217). It takes the child as an + (:135-146 shows the intended call shape).
    120. +
    121. RestoreChild<TChild, TChildId> (AuditableAggregateRootEntity.cs:214-251): constrained + where TChild : AuditableBaseEntity<TChildId>, IReactivatable (:219). It takes the child as an instance, not an id, because a soft-deleted row is hidden by the global query filter and is not reachable through the loaded collection: the caller resolves it with an ignoreQueryFilters - read (:183-189). It rejects a candidate that is not soft-deleted using an error code the - caller supplies (:225-231), calls Reactivate(), and re-adds the child only when the - collection does not already carry that id (:243-246).
    122. -
    123. DeleteChildren<TChild, TChildId> (AuditableAggregateRootEntity.cs:273-292): cascades a soft - delete across a child collection, skipping children that are already deleted (:283-286) so + read (:186-190). It rejects a candidate that is not soft-deleted using an error code the + caller supplies (:225-233), calls Reactivate(), and re-adds the child only when the + collection does not already carry that id (:245-248).
    124. +
    125. DeleteChildren<TChild, TChildId> (AuditableAggregateRootEntity.cs:275-294): cascades a soft + delete across a child collection, skipping children that are already deleted (:285-288) so re-deleting a parent is idempotent, and combining the rest with - Result.Combine (:291). The results list is - allocated lazily (:279, :288), so a childless cascade allocates nothing.
    126. + Result.Combine (:293). The results list is + allocated lazily (:281, :290), so a childless cascade allocates nothing.
    127. Why it's built this way: every one of these helpers replaces a loop that each aggregate used @@ -2140,17 +2148,17 @@

      AuditableAggregateRootEntit event, which error code), which is why RemoveChildOrNotFound and RestoreChild hand the child back instead of raising an event themselves, and why RestoreChild takes notDeletedErrorCode as a parameter for the same reason GetChildOrNotFound takes source - (AuditableAggregateRootEntity.cs:201-206). Ownership checks and field re-validation stay in the + (AuditableAggregateRootEntity.cs:203-208). Ownership checks and field re-validation stay in the calling method and run before the helper, so a rejected restore leaves the child untouched - (:190-195).

    128. + (:193-196).
    129. Where it's used: base class for every aggregate root in the consumers. ADC's Event uses three DeleteChildren calls in one Result.Combine (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/Event.cs:361-363), RestoreChild for room reinstatement (Event.cs:496) and RemoveChildOrNotFound for room - removal (Event.cs:486); Session cascades to its speakers and question answers - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:311-312) and + removal (Event.cs:513); Session cascades to its speakers, question answers and category items + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:311-313) and Category to its items - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/Category.cs:107). The + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/Category.cs:109). The event queue is drained by DomainEventSaveChangesInterceptor, which calls RemoveDomainEvents per captured entry @@ -2234,24 +2242,26 @@

      DateRange

      dates: a validation problem, not corrupted internal state.
    130. LengthInDays (DateRange.cs:38): End.DayNumber - Start.DayNumber, avoiding TimeSpan arithmetic on DateOnly.
    131. -
    132. Overlaps(DateRange other) (DateRange.cs:46): ArgumentNullException.ThrowIfNull(other) then - the standard half-open formula Start < other.End && End > other.Start (DateRange.cs:48-49).
    133. -
    134. Contains(DateOnly instant) (DateRange.cs:55): inclusive on both ends, +
    135. Overlaps(DateRange other) (DateRange.cs:47): ArgumentNullException.ThrowIfNull(other) then + the inclusive formula Start <= other.End && End >= other.Start (DateRange.cs:49-50). Both + ranges are treated as inclusive on both ends, consistent with Contains, so two ranges that + share only their boundary day overlap and a single-day range overlaps itself.
    136. +
    137. Contains(DateOnly instant) (DateRange.cs:56): inclusive on both ends, instant >= Start && instant <= End.
    138. -
    139. Deconstruct (DateRange.cs:61): enables var (start, end) = dateRange.
    140. +
    141. Deconstruct (DateRange.cs:62): enables var (start, end) = dateRange.
    142. Why it's built this way: DateOnly rather than DateTime signals that the concept carries no time-of-day and no time zone; wrapping the pair in a type makes swapping start and end at a call site impossible.
    143. Where it's used: no production entity in ADC or Store holds a DateRange today; it is a shipped - framework primitive covered by MMCA.Common.Shared.Tests/ValueObjects/DateRangeTests.cs. The ADC + framework primitive covered by MMCA.Common.Shared.Tests/ValueObjects/Time/DateRangeTests.cs. The ADC Conference Event enforces the same rule over two loose DateOnly parameters instead, via EventInvariants.EnsureDateRangeIsValid - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:114, - called from Event.cs:179 and Event.cs:252), with the request-side counterpart + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:112, + called from Event.cs:198 and Event.cs:274), with the request-side counterpart EventDateRangeRules<T> - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/Validation/EventValidationRules.cs:93). + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/Validation/EventValidationRules.cs:146). That is the honest state of the code: the primitive exists, the app has not adopted it.
    144. DateTimeRange

      @@ -2377,9 +2387,10 @@

      Money

      which produces the amount column plus ISO-code column mapping together with the currency round-trip fallback every hand-rolled OwnsOne block would otherwise have to repeat.
    145. Where it's used: the Store Sales Order aggregate holds public Money Total - (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Domain/Orders/Order.cs:37), seeds it with - Money.Zero() (Order.cs:90), takes Money UnitPrice on its line-item tuple (Order.cs:105) and - accumulates with Money.Add(order.Total, unitPrice * quantity) (Order.cs:122), the exact + (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Domain/Orders/Order.cs:58), seeds it with + Money.Zero() (Order.cs:171), takes an OrderLinePricing Pricing on its line-item tuple + (Order.cs:194) and accumulates with Money.Add(order.Total, pricing.UnitPrice * quantity) + (Order.cs:220), the exact combination of the None identity, the * operator and the Result-safe Add described above. The Catalog module carries prices the same way, and the UI formats values through MoneyExtensions.
    146. diff --git a/docs/onboarding/group-03-querying-specifications.html b/docs/onboarding/group-03-querying-specifications.html index 33ade2a7..a1f27c20 100644 --- a/docs/onboarding/group-03-querying-specifications.html +++ b/docs/onboarding/group-03-querying-specifications.html @@ -162,18 +162,18 @@

      The Specification patte

      Two members round the family out for polyglot persistence (ADR-018). InlineSpecification<TEntity, TIdentifierType> (Specification.cs:45) wraps an already-composed Criteria expression as a first-class specification (Specification.cs:51-52), for predicates built at runtime where no hand-written class exists. The static CrossSourceSpecification (MMCA.Common/Source/Core/MMCA.Common.Application/Specifications/CrossSourceSpecification.cs:22) builds the cross-source filter: when a dependent entity references a principal that lives in a different physical data source (database-per-service, ADR-006), a navigating predicate like s => s.Event.IsPublished cannot be translated, so BuildAsync (CrossSourceSpecification.cs:39) first projects the matching principal keys from the principal's own source through the read repository's GetProjectedAsync (CrossSourceSpecification.cs:55-56), materializes them once (CrossSourceSpecification.cs:60), and returns an InlineSpecification (CrossSourceSpecification.cs:62) whose body is an Enumerable.Contains(keys, dependent.ForeignKey) call that translates to IN or ARRAY_CONTAINS (CrossSourceSpecification.cs:74-79). An optional local predicate on the dependent's own columns is rebound onto the foreign-key selector's parameter by the shared ParameterReplacer (CrossSourceSpecification.cs:86) and ANDed in (CrossSourceSpecification.cs:87), again without Expression.Invoke so the combined predicate stays translatable on every provider (CrossSourceSpecification.cs:83-85). The doc comment is explicit about the limit: the keys are materialized and embedded in the predicate, so the shape fits bounded principal sets (CrossSourceSpecification.cs:17-20). ADC uses it in production on both of its Session reads, each passing PublicSessionStatusSpecification.StatusCriteria as the local predicate: GetPublicSessionFilterHandler returns the specification as a query result (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/GetPublicSessionFilter/GetPublicSessionFilterHandler.cs:29-34), and PublicConferenceVisibility uses the same criteria to resolve the visible session ids so a session hidden from the session list cannot stay reachable through a speaker or junction read (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Common/PublicConferenceVisibility.cs:63-68). The convention this exists to serve is guarded by an opt-in fitness rule, ArchitectureRules.SpecificationsDoNotNavigateToOtherEntities (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.Specifications.cs:24), which analyzes only the parameterless specifications it can instantiate (ArchitectureRules.Specifications.cs:37-38) and is exposed to repos as the single-fact base SpecificationConventionTestsBase (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Domain/SpecificationConventionTestsBase.cs:10, the fact at SpecificationConventionTestsBase.cs:15-16), which is [Rubric §14, Testability] applied to an architectural rule.

      QuerySpecification, a whole read in one object

      A plain specification is only a predicate, which leaves includes, ordering, paging, and tracking to be threaded through every layer as loose arguments. QuerySpecification<TEntity, TIdentifierType> (MMCA.Common/Source/Core/MMCA.Common.Domain/Specifications/QuerySpecification.cs:38) carries them instead. State is exposed read-only (OrderBy at QuerySpecification.cs:54, IncludePaths at QuerySpecification.cs:60, Skip and Take at QuerySpecification.cs:63 and QuerySpecification.cs:66, AsTracking at QuerySpecification.cs:72, IgnoreQueryFilters at QuerySpecification.cs:82) and assembled through protected builder methods a derived specification calls from its constructor: AddOrderBy (QuerySpecification.cs:90), AddInclude (QuerySpecification.cs:102, which ignores blanks and duplicates at QuerySpecification.cs:104-108), ApplyPaging (QuerySpecification.cs:117, both values floored at zero at QuerySpecification.cs:119-120), WithTracking (QuerySpecification.cs:127), and WithSoftDeleted (QuerySpecification.cs:133). Two design notes are worth carrying forward. The base chain stays QuerySpecification over Specification on purpose, because the fitness rule above keys on that base-type prefix and on a property literally named Criteria (QuerySpecification.cs:29-34). And WithSoftDeleted drops the named SoftDelete global query filter and only that one, so a specification asking for deleted rows can never reach another tenant's data (QuerySpecification.cs:74-81). Each ordering key is an OrderExpression (QuerySpecification.cs:150), a record of an untyped LambdaExpression plus a descending flag, declared top-level rather than nested inside the generic class because a nested type is a different type per closed generic, which would stop the repository evaluator from handling an ordering list generically (QuerySpecification.cs:140-144).

      -

      That object is consumed on the persistence side, not by the pipeline in this chapter. The repository's ListAsync(specification) (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:260) takes any ISpecification, and SpecificationEvaluator (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/SpecificationEvaluator.cs:21) applies the Criteria always (SpecificationEvaluator.cs:46) and the rest only when the instance is a QuerySpecification (SpecificationEvaluator.cs:56-66). Tracking and soft-delete scope are deliberately not applied there: those choose the base queryable, which only the repository can do, in EFReadRepository<TEntity, TIdentifierType>'s BaseQueryFor (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:560-568, rationale at EFReadRepository.cs:554-559), with the concrete reads at EFReadRepository.cs:572 (ListAsync), EFReadRepository.cs:585 (the projecting overload, which selects last so a paged specification pages the rows it means to, EFReadRepository.cs:593-597), and EFReadRepository.cs:117 (FirstOrDefaultAsync, which keeps the full shape because "first" is only meaningful against a defined order, EFReadRepository.cs:123-124). Aggregate reads (count, exists) pass applyShape: false so counting does not join in includes or count "page 3 of the matches" (SpecificationEvaluator.cs:30-35, the call sites at EFReadRepository.cs:454 and EFReadRepository.cs:637). Includes go through one shared helper that also opts the query into split-query mode whenever any include targets a collection navigation (SpecificationEvaluator.cs:85, the decision at SpecificationEvaluator.cs:101), so the string-include path and the specification path cannot drift apart (SpecificationEvaluator.cs:77-80, the delegation at EFReadRepository.cs:547-550).

      +

      That object is consumed on the persistence side, not by the pipeline in this chapter. The repository's ListAsync(specification) (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:262) takes any ISpecification, and SpecificationEvaluator (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/SpecificationEvaluator.cs:21) applies the Criteria always (SpecificationEvaluator.cs:46) and the rest only when the instance is a QuerySpecification (SpecificationEvaluator.cs:56-66). Tracking and soft-delete scope are deliberately not applied there: those choose the base queryable, which only the repository can do, in EFReadRepository<TEntity, TIdentifierType>'s BaseQueryFor (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:575-583, rationale at EFReadRepository.cs:569-574), with the concrete reads at EFReadRepository.cs:587 (ListAsync), EFReadRepository.cs:600 (the projecting overload, which selects last so a paged specification pages the rows it means to, EFReadRepository.cs:608-612), and EFReadRepository.cs:118 (FirstOrDefaultAsync, which keeps the full shape because "first" is only meaningful against a defined order, EFReadRepository.cs:124-125). Aggregate reads (count, exists) pass applyShape: false so counting does not join in includes or count "page 3 of the matches" (SpecificationEvaluator.cs:30-35, the call sites at EFReadRepository.cs:455 and EFReadRepository.cs:652). Includes go through one shared helper that also opts the query into split-query mode whenever any include targets a collection navigation (SpecificationEvaluator.cs:85, the decision at SpecificationEvaluator.cs:101), so the string-include path and the specification path cannot drift apart (SpecificationEvaluator.cs:77-80, the delegation at EFReadRepository.cs:562-565).

      Dynamic filtering, one Strategy per CLR type

      User filters arrive as a Dictionary<string, (string Operator, string Value)>, property name to operator key plus raw string value, parsed from the query string by QueryFilterModelBinder at the API edge, which caps a single request at MaxFilters = 50 distinct properties (MMCA.Common/Source/Presentation/MMCA.Common.API/ModelBinders/QueryFilterModelBinder.cs:34, enforced at QueryFilterModelBinder.cs:61, where surplus entries are dropped rather than rejected). Turning ("Name", "CONTAINS", "blazor") into a .Where() clause depends entirely on the property's CLR type, so instead of one large switch each type gets an IFilterStrategy (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Filtering/IFilterStrategy.cs:6) declaring an Apply method (IFilterStrategy.cs:17), the operator set it supports (IFilterStrategy.cs:24, where the default SupportedOperators is null, meaning operator validation is skipped for custom strategies), and a CanParseValue predicate that defaults to true (IFilterStrategy.cs:44). That last member exists because Apply fails open: a strategy that cannot parse a value silently returns the query unfiltered, so ?filter=id:equals:abc returned the whole result set instead of no matches (IFilterStrategy.cs:32-38). Validating the value up front turns that into a 400, and the default of true keeps a custom strategy behaving exactly as before until it opts in.

      The seven built-ins each override SupportedOperators with a FrozenSet: StringFilterStrategy (StringFilterStrategy.cs:12, operators at StringFilterStrategy.cs:14-18: CONTAINS, NOT CONTAINS, EQUALS, NOT EQUALS, STARTS WITH, ENDS WITH, IS EMPTY, IS NOT EMPTY, IN), the numeric trio IntFilterStrategy (IntFilterStrategy.cs:15), LongFilterStrategy (LongFilterStrategy.cs:14), and DecimalFilterStrategy (DecimalFilterStrategy.cs:14), which share one operator set (equality, the four comparisons, IN, an inclusive BETWEEN range, and the two presence checks, at IntFilterStrategy.cs:17-22, LongFilterStrategy.cs:16-21, and DecimalFilterStrategy.cs:16-21, all parsing invariant-culture), DateTimeFilterStrategy (DateTimeFilterStrategy.cs:13: IS, IS NOT, IS AFTER, IS ON OR AFTER, IS BEFORE, IS ON OR BEFORE, the two presence checks, IN, and BETWEEN at DateTimeFilterStrategy.cs:17-22, parsed with CultureInfo.InvariantCulture at DateTimeFilterStrategy.cs:15), BoolFilterStrategy (BoolFilterStrategy.cs:12: IS plus the two presence checks, BoolFilterStrategy.cs:14-17), and GuidFilterStrategy (GuidFilterStrategy.cs:13: EQUALS, NOT EQUALS, IN, and the two presence checks at GuidFilterStrategy.cs:15-18; GUIDs have no ordering, so no comparisons). Every value-typed strategy implements CanParseValue by delegating to one shared rule (IntFilterStrategy.cs:25, LongFilterStrategy.cs:24, DecimalFilterStrategy.cs:24, DateTimeFilterStrategy.cs:25, BoolFilterStrategy.cs:20, GuidFilterStrategy.cs:21); StringFilterStrategy declares none, because any string parses. That shared rule lives in the internal FilterValueParser (FilterValueParser.cs:8): CanParse (FilterValueParser.cs:53) says presence checks ignore the value, IN needs at least one parseable item, BETWEEN needs exactly two bounds, and every other operator needs the single scalar to parse (FilterValueParser.cs:58-64). BETWEEN gets its own stricter check (FilterValueParser.cs:76) that keeps empty and unparseable segments in play, because dropping them let "5,abc,10" and "5,,10" validate as a two-bound range and the strategies then applied a pair the caller never asked for (FilterValueParser.cs:70-75). The same class decodes the lists at apply time: ParseList<T> skips unparseable entries rather than failing the request (FilterValueParser.cs:17, the if (parse(part) is { } parsed) guard at FilterValueParser.cs:26), and ParseStringList splits on comma, trimming and dropping empty entries (FilterValueParser.cs:34). An eighth strategy is never registered up front because it cannot be: StronglyTypedIdFilterStrategy<TSelf, TValue> (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Filtering/StronglyTypedIdFilterStrategy.cs:24) is internal sealed and generic over an identifier wrapper and its wrapped primitive (StronglyTypedIdFilterStrategy.cs:25-26), so one closed strategy exists per identifier type a model declares. The client still sends the primitive (filters[Id].value=42); the strategy parses it into the wrapper and compares wrapper to wrapper, so EF Core's value converter turns both sides into the same primitive column and adopting a wrapper (ADR-115) does not change the filter DSL a client speaks (StronglyTypedIdFilterStrategy.cs:10-13, the parse at StronglyTypedIdFilterStrategy.cs:49-50). Only the equality family is supported, EQUALS, NOT EQUALS, IN, and the two presence checks (StronglyTypedIdFilterStrategy.cs:28-31, applied at StronglyTypedIdFilterStrategy.cs:38-47): a record struct declares == and != and nothing else, so there is no > to build a range from, and an unsupported operator is a 400 from validation rather than a silently widened result set (StronglyTypedIdFilterStrategy.cs:14-20). The non-generic companion StronglyTypedIdFilterStrategy.TryCreate (StronglyTypedIdFilterStrategy.cs:79) closes the generic at runtime from StronglyTypedId.TryDescribe (StronglyTypedIdFilterStrategy.cs:81-85), which is what lets a consumer adopt wrappers without calling RegisterStrategy once per identifier.

      Every clause is built through System.Linq.Dynamic.Core string predicates with parameter placeholders (@0), never string-concatenated values, and every call site passes the one shared DynamicQueryConfig.Parameterized parsing config (DynamicQueryConfig.cs:18, the instance at DynamicQueryConfig.cs:21-24). That flag is not cosmetic: Dynamic LINQ defaults UseParameterizedNamesInDynamicQuery to false, which turns each @0 into a ConstantExpression that EF inlines, so one filter value produces one distinct SQL string, one SQL Server plan-cache entry per value, and an EF compiled-query cache miss on every request (DynamicQueryConfig.cs:8-16). With the flag on the value is reached through a member access and EF parameterizes it, and QueryParameterizationTests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Specifications/QueryParameterizationTests.cs:26) is the regression guard, the only test in the suite that inspects the emitted SQL. This is [Rubric §12, Performance & Scalability] hiding inside a one-property config object.

      -

      The static QueryFilterService (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Filtering/QueryFilterService.cs:22) is the registry and dispatcher. It seeds a ConcurrentDictionary<Type, IFilterStrategy> with the built-ins, registering both the value type and its Nullable<> form (QueryFilterService.cs:32-48), keeps a dedicated string instance used whenever the resolved value type is string, and exposes RegisterStrategy so a module can add a custom type without touching framework code (QueryFilterService.cs:54, QueryFilterService.cs:62, the open/closed principle made literal, [Rubric §1, SOLID]). ResolveStrategy (QueryFilterService.cs:396) is the three-step dispatch behind both phases: the string instance for string (QueryFilterService.cs:398-399), an explicitly registered strategy next (QueryFilterService.cs:401-402), and the strongly-typed-identifier fallback last, memoized beside the built-ins on first use so the dictionary grows with the number of CLR types the model declares, never with client input (QueryFilterService.cs:404-406, rationale at QueryFilterService.cs:389-394). Reflection is memoized per (entity type, property name) but hits only (QueryFilterService.cs:30, LookupProperty at QueryFilterService.cs:343): the probed names come from the client's query string, so caching misses would let any caller grow a never-evicted static dictionary simply by filtering on names that do not exist, while the request still gets a clean 400 (QueryFilterService.cs:322-329). One shared resolver, ResolvePropertyInfo (QueryFilterService.cs:331), backs both phases so they cannot disagree about what resolves; when they did, a plain rename entry passed validation and was then silently dropped, returning an unfiltered 200 (QueryFilterService.cs:315-321). A dotted path like "Category.Name" is walked segment by segment to its leaf type by ResolveFilterValueType (QueryFilterService.cs:368), so the leaf's own strategy validates the operator instead of every nested path defaulting to the string strategy (QueryFilterService.cs:247-252). A path whose leaf cannot be reached is failed closed rather than guessed at: validation rejects it as an unknown property (QueryFilterService.cs:257-265) and application skips it (QueryFilterService.cs:128-130), because letting Dynamic LINQ meet an unexpressible filter turns a bad request into a 500 (QueryFilterService.cs:361-367).

      -

      What a client may name at all is decided before any of that, and the allow-list is the response contract, not the entity. QueryFieldContract (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/QueryFieldContract.cs:34) is a frozen, case-insensitive set of names: For<TContract>() reflects a DTO's public instance properties once per type and caches them (QueryFieldContract.cs:56, QueryFieldContract.cs:64, the cache at QueryFieldContract.cs:44, the set built at QueryFieldContract.cs:70-73), and ForNames narrows it to an explicit, server-authored list for a DTO that declares a field the response redacts per role (QueryFieldContract.cs:83). AllowsClientKey admits a name only when the contract declares it and it carries no dot, so a caller can neither address a column no DTO exposes nor walk the object graph (QueryFieldContract.cs:103-106), while the static IsWithinNavigationDepth caps any resolved path, server-authored ones included, at MaxNavigationDepth = 3 segments (QueryFieldContract.cs:42, QueryFieldContract.cs:115-137). The reasoning is recorded on the type (QueryFieldContract.cs:12-32): an entity carries columns the response never does (audit fields, credential material, columns a mapper redacts per role), ordering a public list by one of them leaks its total order and a STARTS WITH filter over one turns a list endpoint into a character-by-character oracle, and an unbounded dotted path is another LEFT JOIN per segment in the emitted SQL. Map entries stay the escape hatch, because the server wrote them. In the filter service that gate is IsAdmissibleKey (QueryFilterService.cs:200): depth first, then map-authored or contract-declared (QueryFilterService.cs:206-209, rationale at QueryFilterService.cs:186-198). This is [Rubric §11, Security] in one small type.

      -

      The two phases and their ordering are the rest of the security story. ValidateFilters (the contract-aware overload at QueryFilterService.cs:166, with the contract-less signature kept for callers whose filter keys are server-authored at QueryFilterService.cs:150) runs before the query and returns a Result carrying every Error it found: Filter.Property.NotFound three times over, for a key the contract refuses (QueryFilterService.cs:225-231), a property the entity does not have (QueryFilterService.cs:237-242), and a dotted path whose leaf does not resolve (QueryFilterService.cs:259-265); then Filter.Type.NotSupported (QueryFilterService.cs:271-276), Filter.Operator.NotSupported (QueryFilterService.cs:419-424), and Filter.Value.Invalid (QueryFilterService.cs:304-308), the last suppressed when the operator itself was already rejected so one mistake does not produce two errors (QueryFilterService.cs:297-300). A bad filter is therefore a validation failure, not a SQL exception and not a silently widened result set. ApplyFilters (QueryFilterService.cs:99, the contract-less overload at QueryFilterService.cs:78) then builds the actual .Where() chain, resolving the DTO name through the property map first (QueryFilterService.cs:111-112), running the same admissibility gate so a caller that skipped validation cannot reach the Dynamic LINQ builder with a refused key (QueryFilterService.cs:117-118, the note at QueryFilterService.cs:114-116), and skipping any property it cannot resolve (QueryFilterService.cs:122-123). Strategy dispatch plus allow-listing untrusted input against the response contract is [Rubric §2, Design Patterns] and [Rubric §11, Security] together.

      +

      The static QueryFilterService (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Filtering/QueryFilterService.cs:22) is the registry and dispatcher. It seeds a ConcurrentDictionary<Type, IFilterStrategy> with the built-ins, registering both the value type and its Nullable<> form (QueryFilterService.cs:32-48), keeps a dedicated string instance used whenever the resolved value type is string, and exposes RegisterStrategy so a module can add a custom type without touching framework code (QueryFilterService.cs:54, QueryFilterService.cs:62, the open/closed principle made literal, [Rubric §1, SOLID]). ResolveStrategy (QueryFilterService.cs:406) is the three-step dispatch behind both phases: the string instance for string (QueryFilterService.cs:408-409), an explicitly registered strategy next (QueryFilterService.cs:411-412), and the strongly-typed-identifier fallback last, memoized beside the built-ins on first use so the dictionary grows with the number of CLR types the model declares, never with client input (QueryFilterService.cs:414-416, rationale at QueryFilterService.cs:399-404). Reflection is memoized per (entity type, property name) but hits only (QueryFilterService.cs:30, LookupProperty at QueryFilterService.cs:352): the probed names come from the client's query string, so caching misses would let any caller grow a never-evicted static dictionary simply by filtering on names that do not exist, while the request still gets a clean 400 (QueryFilterService.cs:331-338). One shared resolver, ResolvePropertyInfo (QueryFilterService.cs:340), backs both phases so they cannot disagree about what resolves; when they did, a plain rename entry passed validation and was then silently dropped, returning an unfiltered 200 (QueryFilterService.cs:324-330). A dotted path like "Category.Name" is walked segment by segment to its leaf type by ResolveFilterValueType (QueryFilterService.cs:378), so the leaf's own strategy validates the operator instead of every nested path defaulting to the string strategy (QueryFilterService.cs:256-261). A path whose leaf cannot be reached is failed closed rather than guessed at: validation rejects it as an unknown property (QueryFilterService.cs:266-274) and application skips it (QueryFilterService.cs:128-130), because letting Dynamic LINQ meet an unexpressible filter turns a bad request into a 500 (QueryFilterService.cs:371-377).

      +

      What a client may name at all is decided before any of that, and the allow-list is the response contract, not the entity. QueryFieldContract (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/QueryFieldContract.cs:34) is a frozen, case-insensitive set of names: For<TContract>() reflects a DTO's public instance properties once per type and caches them (QueryFieldContract.cs:56, QueryFieldContract.cs:64, the cache at QueryFieldContract.cs:44, the set built at QueryFieldContract.cs:70-73), and ForNames narrows it to an explicit, server-authored list for a DTO that declares a field the response redacts per role (QueryFieldContract.cs:83). AllowsClientKey admits a name only when the contract declares it and it carries no dot, so a caller can neither address a column no DTO exposes nor walk the object graph (QueryFieldContract.cs:103-106), while the static IsWithinNavigationDepth caps any resolved path, server-authored ones included, at MaxNavigationDepth = 3 segments (QueryFieldContract.cs:42, QueryFieldContract.cs:115-137). The reasoning is recorded on the type (QueryFieldContract.cs:12-32): an entity carries columns the response never does (audit fields, credential material, columns a mapper redacts per role), ordering a public list by one of them leaks its total order and a STARTS WITH filter over one turns a list endpoint into a character-by-character oracle, and an unbounded dotted path is another LEFT JOIN per segment in the emitted SQL. Map entries stay the escape hatch, because the server wrote them. In the filter service that gate is IsAdmissibleKey (QueryFilterService.cs:209): depth first, then map-authored or contract-declared (QueryFilterService.cs:215-218, rationale at QueryFilterService.cs:195-207). This is [Rubric §11, Security] in one small type.

      +

      The two phases and their ordering are the rest of the security story. ValidateFilters (the contract-aware overload at QueryFilterService.cs:175, with the contract-less signature kept for callers whose filter keys are server-authored at QueryFilterService.cs:159) runs before the query and returns a Result carrying every Error it found: Filter.Property.NotFound three times over, for a key the contract refuses (QueryFilterService.cs:234-240), a property the entity does not have (QueryFilterService.cs:246-251), and a dotted path whose leaf does not resolve (QueryFilterService.cs:268-274); then Filter.Type.NotSupported (QueryFilterService.cs:280-285), Filter.Operator.NotSupported (QueryFilterService.cs:429-434), and Filter.Value.Invalid (QueryFilterService.cs:313-317), the last suppressed when the operator itself was already rejected so one mistake does not produce two errors (QueryFilterService.cs:306-309). A bad filter is therefore a validation failure, not a SQL exception and not a silently widened result set. ApplyFilters (QueryFilterService.cs:99, the contract-less overload at QueryFilterService.cs:78) then builds the actual .Where() chain, resolving the DTO name through the property map first (QueryFilterService.cs:111-112), running the same admissibility gate so a caller that skipped validation cannot reach the Dynamic LINQ builder with a refused key (QueryFilterService.cs:117-118, the note at QueryFilterService.cs:114-116), and skipping any property it cannot resolve (QueryFilterService.cs:122-123). Strategy dispatch plus allow-listing untrusted input against the response contract is [Rubric §2, Design Patterns] and [Rubric §11, Security] together.

      Sorting, sparse fieldsets, and paging arithmetic

      -

      QueryFieldService (MMCA.Common/Source/Core/MMCA.Common.Application/Services/QueryFieldService.cs:16) owns the rest of read shaping. ApplySorting (QueryFieldService.cs:183, with a contract-less overload that passes fieldContract: null for server-authored callers at QueryFieldService.cs:155-162) resolves a DTO sort name through the server-authored map, and for a name the map does not cover it consults the response contract before the entity is reflected over at all: a column the DTO does not declare resolves to nothing (ResolveSortExpression at QueryFieldService.cs:226, the contract gate at QueryFieldService.cs:239-242, the entity lookup that only runs past it at QueryFieldService.cs:245-247), and the query falls back to the optional default sort instead (QueryFieldService.cs:201-207). Whatever the source, the resolved path is refused past QueryFieldContract.MaxNavigationDepth (QueryFieldService.cs:250). That guard is deliberate and documented on the overload (QueryFieldService.cs:219-224): a client-supplied string can never reach Dynamic LINQ to order by nested paths, nor order a public page by a column the response never carries. The same contract gates fields: ValidateSingleField checks the server-authored map first, capping its depth, then the response contract, and only then the entity's own property set (QueryFieldService.cs:462, the map branch at QueryFieldService.cs:471-483, the contract branch at QueryFieldService.cs:485-498), reached through a third Validate<TEntity> overload that takes the contract (QueryFieldService.cs:423). Map entries, being server-authored, may be expressions: ADC sorts speakers by FullName through an entry that concatenates first and last name (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Speakers/SpeakerEntityQueryService.cs:30, wired in by overriding the map at SpeakerEntityQueryService.cs:34). The method also takes an optional tieBreakProperty appended as a final ascending key (QueryFieldService.cs:190, applied by BuildOrdering at QueryFieldService.cs:258, and used alone when no valid sort column was given at QueryFieldService.cs:209-211). It exists because Skip/Take over a non-total ORDER BY is undefined: rows sharing a sort value can come back in a different order per statement, so the same row appears on two consecutive pages while another appears on neither, from data that never changed (QueryFieldService.cs:139-153). The append is repeated-key aware, skipped when the caller already sorted by that very column (QueryFieldService.cs:263-266).

      -

      ApplyFieldSelection (QueryFieldService.cs:278) builds a MemberInit Select expression so a fields=name,bio request pulls only those columns from the database ([Rubric §12, Performance & Scalability]), restricted to writable properties because the projection needs setters (QueryFieldService.cs:331-340, the CanWrite filter at QueryFieldService.cs:336). The compiled lambda is cached per (entity type, normalized field set) (QueryFieldService.cs:286, cache at QueryFieldService.cs:329), and a null is cached on purpose to record "this field set projects nothing writable" so the miss is not recomputed per request (QueryFieldService.cs:318-320). ShapeData and ShapeCollectionData (QueryFieldService.cs:75, QueryFieldService.cs:96) produce the wire shape: an ExpandoObject (or a list of them) holding only the requested fields under camelCase keys. To make that cheap on large result sets the service caches a per-type array of PropertyAccessor (QueryFieldService.cs:42), a private readonly record struct bundling each property's name, its precomputed camelCase key, and a compiled Func<object, object?> getter built with Expression.Lambda(...).Compile() rather than PropertyInfo.GetValue (QueryFieldService.cs:46, built at QueryFieldService.cs:48-65); the field-filtered subset is cached again per field set (QueryFieldService.cs:579, QueryFieldService.cs:585), under an order- and case-insensitive key so name,id and Id, Name share one entry (QueryFieldService.cs:616-617).

      -

      Both field-set caches are bounded, and the reason is the same one that shapes the filter cache. Their key is half client-supplied, so an entity with N properties admits up to 2^N distinct subsets and a caller could grow either dictionary by permuting the list (QueryFieldService.cs:18-38). The cap is a const int MaxCacheEntries = 512 per cache (QueryFieldService.cs:39), with deliberately no LRU: past the cap ApplyFieldSelection skips server-side projection rather than admitting another key (QueryFieldService.cs:297-298, and the response is unchanged because shaping still trims the payload), and GetShapedAccessors filters per request instead (QueryFieldService.cs:603-604), which is a scan over an already-compiled accessor array rather than an expression rebuild. Validation mirrors the filter side: Validate<TEntity> rejects unknown field names and (when shaping) read-only properties (QueryFieldService.cs:366 and the map-aware overload at QueryFieldService.cs:401, shared body at QueryFieldService.cs:434), and ValidateSortDirection accepts only asc or desc (QueryFieldService.cs:529). Paging arithmetic is small enough to look trivial and is not, which is why it has its own type: PagingMath.Clamp (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/PagingMath.cs:32) clamps the page size into [1, maxPageSize] and the page number to at least 1 (PagingMath.cs:37-38), computes the offset in 64-bit (PagingMath.cs:40), and returns (0, 0) for a page beyond the reachable offset range, materializing the empty page that page genuinely holds (PagingMath.cs:42). A 32-bit (pageNumber - 1) * pageSize overflows and wraps negative near int.MaxValue, and SQL Server rejects a negative OFFSET outright, so the request surfaced as a 500 instead of an empty page (PagingMath.cs:8-12). Every paginating caller routes through here rather than open-coding the multiply, because the arithmetic previously lived only inside the pipeline and the handlers that paginate their own queryable each re-derived it in 32-bit (PagingMath.cs:14-17).

      +

      QueryFieldService (MMCA.Common/Source/Core/MMCA.Common.Application/Services/QueryFieldService.cs:16) owns the rest of read shaping. ApplySorting (QueryFieldService.cs:184, with a contract-less overload that passes fieldContract: null for server-authored callers at QueryFieldService.cs:155-162) resolves a DTO sort name through the server-authored map, and for a name the map does not cover it consults the response contract before the entity is reflected over at all: a column the DTO does not declare resolves to nothing (ResolveSortExpression at QueryFieldService.cs:227, the contract gate at QueryFieldService.cs:240-243, the entity lookup that only runs past it at QueryFieldService.cs:246-248), and the query falls back to the optional default sort instead (QueryFieldService.cs:202-208). Whatever the source, the resolved path is refused past QueryFieldContract.MaxNavigationDepth (QueryFieldService.cs:251). That guard is deliberate and documented on the overload (QueryFieldService.cs:220-225): a client-supplied string can never reach Dynamic LINQ to order by nested paths, nor order a public page by a column the response never carries. The same contract gates fields: ValidateSingleField checks the server-authored map first, capping its depth, then the response contract, and only then the entity's own property set (QueryFieldService.cs:465, the map branch at QueryFieldService.cs:474-486, the contract branch at QueryFieldService.cs:488-501), reached through a third Validate<TEntity> overload that takes the contract (QueryFieldService.cs:426). Map entries, being server-authored, may be expressions: ADC sorts speakers by FullName through an entry that concatenates first and last name (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Speakers/SpeakerEntityQueryService.cs:30, wired in by overriding the map at SpeakerEntityQueryService.cs:34). The method also takes an optional tieBreakProperty appended as a final ascending key (QueryFieldService.cs:191, applied by BuildOrdering at QueryFieldService.cs:259, and used alone when no valid sort column was given at QueryFieldService.cs:210-212). It exists because Skip/Take over a non-total ORDER BY is undefined: rows sharing a sort value can come back in a different order per statement, so the same row appears on two consecutive pages while another appears on neither, from data that never changed (QueryFieldService.cs:139-153). The append is repeated-key aware, skipped when the caller already sorted by that very column (QueryFieldService.cs:264-267).

      +

      ApplyFieldSelection (QueryFieldService.cs:279) builds a MemberInit Select expression so a fields=name,bio request pulls only those columns from the database ([Rubric §12, Performance & Scalability]), restricted to writable properties because the projection needs setters (QueryFieldService.cs:332-341, the CanWrite filter at QueryFieldService.cs:337). The compiled lambda is cached per (entity type, normalized field set) (QueryFieldService.cs:287, cache at QueryFieldService.cs:330), and a null is cached on purpose to record "this field set projects nothing writable" so the miss is not recomputed per request (QueryFieldService.cs:319-321). ShapeData and ShapeCollectionData (QueryFieldService.cs:75, QueryFieldService.cs:96) produce the wire shape: an ExpandoObject (or a list of them) holding only the requested fields under camelCase keys. To make that cheap on large result sets the service caches a per-type array of PropertyAccessor (QueryFieldService.cs:42), a private readonly record struct bundling each property's name, its precomputed camelCase key, and a compiled Func<object, object?> getter built with Expression.Lambda(...).Compile() rather than PropertyInfo.GetValue (QueryFieldService.cs:46, built at QueryFieldService.cs:48-65); the field-filtered subset is cached again per field set (QueryFieldService.cs:582, QueryFieldService.cs:588), under an order- and case-insensitive key so name,id and Id, Name share one entry (QueryFieldService.cs:619-620).

      +

      Both field-set caches are bounded, and the reason is the same one that shapes the filter cache. Their key is half client-supplied, so an entity with N properties admits up to 2^N distinct subsets and a caller could grow either dictionary by permuting the list (QueryFieldService.cs:18-38). The cap is a const int MaxCacheEntries = 512 per cache (QueryFieldService.cs:39), with deliberately no LRU: past the cap ApplyFieldSelection skips server-side projection rather than admitting another key (QueryFieldService.cs:298-299, and the response is unchanged because shaping still trims the payload), and GetShapedAccessors filters per request instead (QueryFieldService.cs:606-607), which is a scan over an already-compiled accessor array rather than an expression rebuild. Validation mirrors the filter side: Validate<TEntity> rejects unknown field names and (when shaping) read-only properties (QueryFieldService.cs:368 and the map-aware overload at QueryFieldService.cs:404, shared body at QueryFieldService.cs:437), and ValidateSortDirection accepts only asc or desc (QueryFieldService.cs:532). Paging arithmetic is small enough to look trivial and is not, which is why it has its own type: PagingMath.Clamp (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/PagingMath.cs:32) clamps the page size into [1, maxPageSize] and the page number to at least 1 (PagingMath.cs:37-38), computes the offset in 64-bit (PagingMath.cs:40), and returns (0, 0) for a page beyond the reachable offset range, materializing the empty page that page genuinely holds (PagingMath.cs:42). A 32-bit (pageNumber - 1) * pageSize overflows and wraps negative near int.MaxValue, and SQL Server rejects a negative OFFSET outright, so the request surfaced as a 500 instead of an empty page (PagingMath.cs:8-12). Every paginating caller routes through here rather than open-coding the multiply, because the arithmetic previously lived only inside the pipeline and the handlers that paginate their own queryable each re-derived it in 32-bit (PagingMath.cs:14-17).

      The pipeline: two entity paths plus projection pushdown

      IEntityQueryPipeline (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/IEntityQueryPipeline.cs:10) is the execution contract, implemented by the sealed EntityQueryPipeline (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/EntityQueryPipeline.cs:13), which talks to the database through the IQueryableExecutor abstraction rather than referencing EF Core from the Application layer ([Rubric §3, Clean Architecture]). Its inputs are bundled into EntityQueryParameters<TEntity> (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/EntityQueryParameters.cs:11), an immutable record carrying the specification Criteria, the dynamic Filters, sort column and direction, Fields, page number and size, the two include flags, the DTO-to-entity property map (defaulting to an empty FrozenDictionary, EntityQueryParameters.cs:44), and the response FieldContract the filter and sort resolvers gate client names against (EntityQueryParameters.cs:52). The pipeline itself never inspects the contract: it threads it into QueryFilterService.ApplyFilters and QueryFieldService.ApplySorting on every path it runs (EntityQueryPipeline.cs:77, EntityQueryPipeline.cs:87, EntityQueryPipeline.cs:152, EntityQueryPipeline.cs:182, EntityQueryPipeline.cs:235), so the projection path and the two entity paths enforce one allow-list.

      ExecuteAsync (EntityQueryPipeline.cs:39) runs a shared front half, ApplyIncludesCriteriaAndFilters (EntityQueryPipeline.cs:120): add every supported navigation as an .Include() (EntityQueryPipeline.cs:134-135), force AsSplitQuery() when a child collection is among them (EntityQueryPipeline.cs:141-142), then apply the specification criteria and the dynamic filters before materializing anything (EntityQueryPipeline.cs:147-151) so the data source does as much of the work as possible. The comment above the split-query switch records the hard-won reason, annotated R24/§8: paginating a single-query collection include truncates child rows because EF applies Skip/Take to the JOIN-expanded set, so list reads returned empty child collections while by-id reads worked (EntityQueryPipeline.cs:137-140). It then branches on whether any requested navigation is unsupported (EntityQueryPipeline.cs:53). Path 1, server-side includes (EntityQueryPipeline.cs:218): sort (EntityQueryPipeline.cs:229), count before paging (EntityQueryPipeline.cs:243), Skip/Take (EntityQueryPipeline.cs:244), field-selection Select (EntityQueryPipeline.cs:254), materialize (EntityQueryPipeline.cs:255). Path 2, manual navigation (EntityQueryPipeline.cs:162), taken when a requested navigation crosses a physical data source and cannot be joined: sort and page at the database first (EntityQueryPipeline.cs:176, EntityQueryPipeline.cs:189), materialize the page (EntityQueryPipeline.cs:198), then invoke the navigationPopulator callback to batch-load those navigations in a second query (EntityQueryPipeline.cs:201), the INavigationPopulator<in TEntity> extension point of ADR-002, and apply field selection in memory afterwards (EntityQueryPipeline.cs:210). Both paths pass the entity key as the sort tie-break, and only when the read is paginated (EntityQueryPipeline.cs:36, passed at EntityQueryPipeline.cs:180 and EntityQueryPipeline.cs:232): an unpaginated read materializes one capped set in one statement, so it cannot suffer the split-across-pages incoherence, and adding an ORDER BY there would charge every unsorted list read for a sort nobody asked for (EntityQueryPipeline.cs:30-35).

      @@ -182,12 +182,12 @@

      The pipeline: tw

      The query service, the public face

      IEntityQueryService<TEntity, TEntityDTO, TIdentifierType> (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Mapping/IEntityQueryService.cs:19) and its concrete EntityQueryService<TEntity, TEntityDTO, TIdentifierType> (MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:32) are what controllers and handlers inject. The service is constructed from IUnitOfWork, the metadata provider, the pipeline, an IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType> (ADR-001), and an INavigationPopulator<TEntity> (EntityQueryService.cs:32-37), and it resolves its IReadRepository<TEntity, TIdentifierType> from the unit of work through a virtual property (EntityQueryService.cs:88). A second, six-argument constructor adds the optional IEntityDTOProjector (EntityQueryService.cs:70-78): it is a second constructor rather than an optional parameter because the Microsoft DI container has no notion of an optional dependency, so with two overloads it picks the longer one when a projector is registered and the shorter one when it is not (EntityQueryService.cs:52-62). Two virtual properties decide what a client may name against that service. FieldContract (EntityQueryService.cs:123) defaults to QueryFieldContract.For<TEntityDTO>(), so sort columns, filter keys, and fields resolve against the response contract rather than the entity; it is handed to the validators and packed into the pipeline parameters (EntityQueryService.cs:299-301, EntityQueryService.cs:331, EntityQueryService.cs:580). A subclass narrows it with QueryFieldContract.ForNames for a DTO that declares a field the mapper redacts per role, and returning null restores resolution against the entity as a deliberate, documented decision rather than a convenience (EntityQueryService.cs:109-122). LookupNameContract (EntityQueryService.cs:135) defaults to the same contract and gates GetAllForLookupAsync's nameProperty, which projects the named column verbatim for every row, unpaginated: an existence check against the entity was enough to dump a column the DTO never exposes (EntityQueryService.cs:393-401, rationale at EntityQueryService.cs:390-392 and EntityQueryService.cs:126-134). Neither property is overridden anywhere in MMCA.ADC/Source today: every ADC read runs on the DTO-derived default.

      GetAllAsync (EntityQueryService.cs:283, with a six-parameter convenience overload at EntityQueryService.cs:262) is the four-step orchestration. (1) Validate every parameter up front with Result.Combine over the fields, sort-column, sort-direction, and filter validators, so a bad fields fails before any database hit (EntityQueryService.cs:297-302), re-stamping each error with the operation and entity name (EntityQueryService.cs:305-311). (2) Build the query: ask the metadata provider which includes are supported (EntityQueryService.cs:317), pack everything into EntityQueryParameters (EntityQueryService.cs:319-332), and pick Repository.Table or TableNoTracking from the asTracking flag (EntityQueryService.cs:334). (3) Execute on one of two branches, chosen by CanProject (EntityQueryService.cs:541-544): a registered projector, no tracking request, and no cross-source includes routes to ExecuteProjectedAsync and the mapper is never involved (EntityQueryService.cs:339-349); otherwise ExecuteAsync runs and DTOMapper.MapToDTOs converts the materialized entities (EntityQueryService.cs:352-360). Field shaping deliberately does not disqualify projection, because shaping runs after materialization over whatever object the pipeline produced (EntityQueryService.cs:536-539). (4) Shape and wrap: shape only when a field subset was requested, otherwise return the typed DTOs as-is to avoid a per-row ExpandoObject allocation and boxing (EntityQueryService.cs:370-372); both forms serialize to the same camelCase JSON, which is why the return type is PagedCollectionResult<object> rather than a typed collection (PagedCollectionResult<T>, and the contract note at IEntityQueryService.cs:12-14). The PaginationMetadata comes from BuildPaginationMetadata (EntityQueryService.cs:368, method at EntityQueryService.cs:608), whose job is to describe what the pipeline actually did rather than what the caller asked for: an unpaginated call reports the true total with the page size floored to the rows actually returnable, Math.Min(total, MaxUnboundedResultLimit), on page 1 (EntityQueryService.cs:622-625), and a paginated call reports Math.Clamp(pageSize, 1, MaxUnboundedResultLimit) on Math.Max(pageNumber, 1) (EntityQueryService.cs:632-635), mirroring exactly the floor and ceiling PagingMath applied. The clamp is recomputed here rather than read back from PagingMath, because that helper's (0, 0) sentinel for an unreachable page would otherwise advertise PageSize = 0 for a perfectly valid page size (EntityQueryService.cs:601-606).

      -

      The by-id path has a fast lane worth knowing. GetEntityByIdAsync (EntityQueryService.cs:428) validates the fields (EntityQueryService.cs:438), then tries TryGetByIdFastPathAsync (EntityQueryService.cs:154), which issues a single keyed TOP 1 WHERE Id = @id through the repository's include overload (EntityQueryService.cs:170). TryGetFastPathIncludes (EntityQueryService.cs:196) decides eligibility: a field projection, a specification, or a non-default idField disqualifies the request (EntityQueryService.cs:206-211), and so do unsupported (cross-source) navigations, since only the pipeline's populator can batch-load those (EntityQueryService.cs:219-222, rationale at EntityQueryService.cs:190-194). Requested includes do not disqualify it: the repository's include overload applies the same Include calls and auto-applies AsSplitQuery for a child collection (EFReadRepository.cs:417-430, delegating the split decision to SpecificationEvaluator.cs:101), and disqualifying on includes left the fast path unreachable for every entity that declares a navigation, because the REST by-id action defaults includeFKs to true (EntityQueryService.cs:182-188). The string id is converted with a TypeConverter cached per identifier type (EntityQueryService.cs:233, cache at EntityQueryService.cs:142), and the read runs on the filtered TableNoTracking (EFReadRepository.cs:387), so soft-delete query filters still apply, unlike FindAsync (EntityQueryService.cs:148-152, and the same trap documented at EFReadRepository.cs:408-413). Anything else falls through to the pipeline with a synthetic Id EQUALS <value> filter and returns Error.NotFound when the page comes back empty. GetByIdAsync (EntityQueryService.cs:489) layers DTO mapping and the same shape-only-if-fields rule on top; GetAllForLookupAsync (EntityQueryService.cs:384) returns lightweight BaseLookup<TIdentifierType> id/name pairs for dropdowns; ExistsAsync (EntityQueryService.cs:519) delegates straight to the repository. The class is built for extension over modification ([Rubric §1, SOLID]): Repository (EntityQueryService.cs:88), DTOToEntityPropertyMap (EntityQueryService.cs:101), and every query method are virtual, so a module subclass such as SpeakerEntityQueryService (SpeakerEntityQueryService.cs:15) overrides one behavior (SpeakerEntityQueryService.cs:34) without reimplementing the engine.

      +

      The by-id path has a fast lane worth knowing. GetEntityByIdAsync (EntityQueryService.cs:428) validates the fields (EntityQueryService.cs:438), then tries TryGetByIdFastPathAsync (EntityQueryService.cs:154), which issues a single keyed TOP 1 WHERE Id = @id through the repository's include overload (EntityQueryService.cs:170). TryGetFastPathIncludes (EntityQueryService.cs:196) decides eligibility: a field projection, a specification, or a non-default idField disqualifies the request (EntityQueryService.cs:206-211), and so do unsupported (cross-source) navigations, since only the pipeline's populator can batch-load those (EntityQueryService.cs:219-222, rationale at EntityQueryService.cs:190-194). Requested includes do not disqualify it: the repository's include overload applies the same Include calls and auto-applies AsSplitQuery for a child collection (EFReadRepository.cs:418-431, delegating the split decision to SpecificationEvaluator.cs:101), and disqualifying on includes left the fast path unreachable for every entity that declares a navigation, because the REST by-id action defaults includeFKs to true (EntityQueryService.cs:182-188). The string id is converted with a TypeConverter cached per identifier type (EntityQueryService.cs:233, cache at EntityQueryService.cs:142), and the read runs on the filtered TableNoTracking (EFReadRepository.cs:388), so soft-delete query filters still apply, unlike FindAsync (EntityQueryService.cs:148-152, and the same trap documented at EFReadRepository.cs:409-414). Anything else falls through to the pipeline with a synthetic Id EQUALS <value> filter and returns Error.NotFound when the page comes back empty. GetByIdAsync (EntityQueryService.cs:489) layers DTO mapping and the same shape-only-if-fields rule on top; GetAllForLookupAsync (EntityQueryService.cs:384) returns lightweight BaseLookup<TIdentifierType> id/name pairs for dropdowns; ExistsAsync (EntityQueryService.cs:519) delegates straight to the repository. The class is built for extension over modification ([Rubric §1, SOLID]): Repository (EntityQueryService.cs:88), DTOToEntityPropertyMap (EntityQueryService.cs:101), and every query method are virtual, so a module subclass such as SpeakerEntityQueryService (SpeakerEntityQueryService.cs:15) overrides one behavior (SpeakerEntityQueryService.cs:34) without reimplementing the engine.

      End to end, one list request

      -

      The request reaches a read controller, EntityControllerBase<TEntity, TEntityDTO, TIdentifierType> (Group 12), which resolves MaxPageSize per request from ApplicationSettings, falling back to 500 when unset (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:56-61), clamps the requested page size to it (EntityControllerBase.cs:166), binds ?filter= through QueryFilterModelBinder (EntityControllerBase.cs:163), and asks its own GetReadSpecificationAsync hook for a server-authored authorization scope (EntityControllerBase.cs:168). It calls IEntityQueryService.GetAllAsync (EntityControllerBase.cs:170). The service validates fields, sort, and filters (an early failure short-circuits to an error result), classifies the requested includes, and packages an EntityQueryParameters. EntityQueryPipeline then takes the projection path when a projector is registered and the read qualifies, or one of the two entity paths otherwise, applying the specification criteria plus the dynamic filters as translated, parameterized WHERE clauses, sorting with the key tie-break when paginating, counting, paging through PagingMath, projecting the requested columns, materializing, and batch-loading any cross-source navigations. The service maps to DTOs (or skips mapping entirely on the projected path), shapes only if a field subset was asked for, and returns a Result<PagedCollectionResult<object>> that the controller unwraps into the HTTP body plus an X-Pagination header carrying the serialized metadata (EntityControllerBase.cs:185). One pipeline, every entity, validated input, server-side execution, and a clean extension point for navigations that cross a service boundary ([Rubric §6, CQRS & Event-Driven] on the read side, [Rubric §9, API & Contract Design] for the uniform query contract).

      +

      The request reaches a read controller, EntityControllerBase<TEntity, TEntityDTO, TIdentifierType> (Group 12), which resolves MaxPageSize per request from ApplicationSettings, falling back to 500 when unset (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:56-61), clamps the requested page size to it (EntityControllerBase.cs:169), binds ?filter= through QueryFilterModelBinder (EntityControllerBase.cs:166), and asks its own GetReadSpecificationAsync hook for a server-authored authorization scope (EntityControllerBase.cs:171). It calls IEntityQueryService.GetAllAsync (EntityControllerBase.cs:173). The service validates fields, sort, and filters (an early failure short-circuits to an error result), classifies the requested includes, and packages an EntityQueryParameters. EntityQueryPipeline then takes the projection path when a projector is registered and the read qualifies, or one of the two entity paths otherwise, applying the specification criteria plus the dynamic filters as translated, parameterized WHERE clauses, sorting with the key tie-break when paginating, counting, paging through PagingMath, projecting the requested columns, materializing, and batch-loading any cross-source navigations. The service maps to DTOs (or skips mapping entirely on the projected path), shapes only if a field subset was asked for, and returns a Result<PagedCollectionResult<object>> that the controller unwraps into the HTTP body plus an X-Pagination header carrying the serialized metadata (EntityControllerBase.cs:188). One pipeline, every entity, validated input, server-side execution, and a clean extension point for navigations that cross a service boundary ([Rubric §6, CQRS & Event-Driven] on the read side, [Rubric §9, API & Contract Design] for the uniform query contract).

      Query tags, naming the use case behind a statement

      Two types in the Services/Query folder serve the operator reading a query store rather than the read path itself. QueryTagScope (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/QueryTagScope.cs:20) is a static holder over an AsyncLocal<string?> (QueryTagScope.cs:22). Begin(handlerName) publishes the name of the CQRS use case the current flow is executing and returns a handle that restores the previous value on dispose, so a nested scope (an internal command executed inside another handler) unwinds correctly (QueryTagScope.cs:40-53); a null, empty, or whitespace name leaves the ambient value untouched, so no call site needs a guard (QueryTagScope.cs:44-47); and Current reads the innermost open scope, or null when none is open, which is the normal state for a background service, a seeder, or a directly constructed repository in a test (QueryTagScope.cs:28). The handle is the private AmbientScope (QueryTagScope.cs:61), a one-line IDisposable that assigns the captured previous value back (QueryTagScope.cs:63). The outermost scope of a request, whose previous value is null, is served by a single shared static instance, so opening a scope on the hot path allocates nothing at all and only a genuinely nested scope pays for an object (QueryTagScope.cs:49-52, the shared handle at QueryTagScope.cs:59).

      -

      The two ends are the decorator pipeline and the EF repositories. The logging decorators open the scope around each handler (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/LoggingQueryDecorator.cs:30 and LoggingCommandDecorator.cs:31), and QueryTags composes the ambient name with the query builder's own detail into the string the repository passes to EF Core's TagWith (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/QueryTags.cs:42, used on the specification path at SpecificationEvaluator.cs:38 and the include path at EFReadRepository.cs:524), turning an anonymous statement in a DBA's query store into a SQL comment naming the handler and the specification that asked for it. The value flows with the ExecutionContext, which is what lets a repository several awaits below the decorator read it without every signature in between growing a parameter, and nothing branches on it: a missing scope costs a less specific comment, never a behavior change (QueryTagScope.cs:6-18). It lives in the Application layer rather than Infrastructure because both ends need it and Application is the highest layer Infrastructure is allowed to see (QueryTagScope.cs:15-17). This is [Rubric §13, Observability & Operability] bought for one AsyncLocal and one using.

      +

      The two ends are the decorator pipeline and the EF repositories. The logging decorators open the scope around each handler (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/LoggingQueryDecorator.cs:30 and LoggingCommandDecorator.cs:31), and QueryTags composes the ambient name with the query builder's own detail into the string the repository passes to EF Core's TagWith (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/QueryTags.cs:42, used on the specification path at SpecificationEvaluator.cs:38 and the include path at EFReadRepository.cs:539), turning an anonymous statement in a DBA's query store into a SQL comment naming the handler and the specification that asked for it. The value flows with the ExecutionContext, which is what lets a repository several awaits below the decorator read it without every signature in between growing a parameter, and nothing branches on it: a missing scope costs a less specific comment, never a behavior change (QueryTagScope.cs:6-18). It lives in the Application layer rather than Infrastructure because both ends need it and Application is the highest layer Infrastructure is allowed to see (QueryTagScope.cs:15-17). This is [Rubric §13, Observability & Operability] bought for one AsyncLocal and one using.

      Also filed here: best-effort dispatch and the upcaster registry

      Four types in this group are not part of the read path at all; they are co-located in MMCA.Common.Application/Services and are grouped by that folder. BestEffort (MMCA.Common/Source/Core/MMCA.Common.Application/Services/BestEffort.cs:25) runs a side effect that must never fail its caller (cache eviction after a committed command, a fire-and-forget notification, an eviction broadcast onto the bus): ExecuteAsync awaits the action and turns any non-cancellation failure into exactly one Warning plus one metric increment instead of an exception that would roll back or 500 an operation whose real work already succeeded (BestEffort.cs:45, the swallow at BestEffort.cs:65-71). Cancellation is explicitly not swallowed: when the caller's own token is the reason the action stopped, the OperationCanceledException is rethrown so a host shutdown unwinds promptly (BestEffort.cs:59-64, rationale at BestEffort.cs:11-17). The two companions carry the telemetry: BestEffortLog (BestEffort.cs:79) is the source-generated Warning message, kept separate so the public helper need not be partial (BestEffort.cs:75-78), and BestEffortMetrics (BestEffort.cs:99) owns the MMCA.Common.BestEffort meter and its besteffort.dispatch.failed counter, tagged by a low-cardinality operation name (BestEffort.cs:102-115). It is its own meter rather than a counter folded into the CQRS metrics so an operator can drop or keep it independently of the RED metrics (BestEffort.cs:92-97). Callers span the framework and both apps: the framework's own output-cache eviction (MMCA.Common/Source/Presentation/MMCA.Common.API/Caching/OutputCacheEvictionExtensions.cs:86) and ADC's live broadcasts and cache-eviction handlers (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/UserSessionBookmarks/DomainEventHandlers/UserSessionBookmarkCacheEvictionHandler.cs:68, MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Live/LiveChannelPublishProcessor.cs:45, MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/Submit/SubmitQuestionHandler.cs:206). This is [Rubric §13, Observability & Operability] (a quietly broken side effect becomes a metric, not a line in a log nobody reads) and [Rubric §29, Resilience & Business Continuity] (a non-essential failure degrades instead of propagating).

      The fourth co-located type is EventUpcasterRegistry (MMCA.Common/Source/Core/MMCA.Common.Application/Services/EventUpcasterRegistry.cs:30), the default IEventUpcasterRegistry, which belongs to the integration-event story rather than to querying. It indexes every registered IEventUpcaster by its source contract and rejects a duplicate source, a self-mapping upcaster, or a chain cycle at construction time, throwing an InvalidOperationException that names the offenders (EventUpcasterRegistry.cs:50-82, the cycle check at EventUpcasterRegistry.cs:148-154); it precomputes each chain's terminal type once, because the graph is static after DI is built (EventUpcasterRegistry.cs:133); and it preserves the event envelope across every hop by stamping MessageId and DateOccurred from the pre-hop instance onto the upcasted one through cached PropertyInfo handles (EventUpcasterRegistry.cs:36, EventUpcasterRegistry.cs:169), so consumer-side inbox deduplication stays keyed on the id the producer published.

      @@ -521,18 +521,18 @@

      QueryFieldService

      GetAccessors<TEntity> (:48-65), which compiles one Expression.Lambda<Func<object, object?>> per property (:56-60) and pre-computes the camelCase name (:61). -
    147. ProjectionCache ((Type, string Fields) -> LambdaExpression?, :280) so the MemberInit - tree is built once per (entity, field set) instead of per request. Its remarks (:262-279) +
    148. ProjectionCache ((Type, string Fields) -> LambdaExpression?, :330) so the MemberInit + tree is built once per (entity, field set) instead of per request. Its remarks (:315-329) explain that a null value is cached deliberately: it records "this field set projects nothing writable", so the miss is not recomputed on every subsequent request.
    149. -
    150. ShapedAccessorCache ((Type, string Fields) -> PropertyAccessor[], :465) so a repeated +
    151. ShapedAccessorCache ((Type, string Fields) -> PropertyAccessor[], :582) so a repeated fields= request reuses the filtered accessor array instead of re-filtering per call.
    152. -

      The keys of the last two are normalized by NormalizeFieldsKey (:502-503), which uppercases and +

      The keys of the last two are normalized by NormalizeFieldsKey (:619-620), which uppercases and sorts the field names so "name,id" and "Id, Name" share one entry rather than multiplying the cache by however many spellings callers happen to send. The hot-path GetOrAdd calls pass static - lambdas and thread state through the overload's extra argument (:49, :51, :251-254, - :492-495), so no closure is allocated per call.

      + lambdas and thread state through the overload's extra argument (:49, :51, :301-304, + :609-612), so no closure is allocated per call.

    153. Concept introduced, the client-keyed cache cap. [Rubric §11, Security] (assesses whether a caller can grow process-lifetime state at will) and [Rubric §12] both apply to @@ -549,50 +549,51 @@

      QueryFieldService

    154. ShapeData<TEntity>(entity, fields) (:75-87) and ShapeCollectionData<TEntity>(entities, fields) (:96-117): resolve accessors through GetShapedAccessors (:77, :100), then fill an ExpandoObject keyed by CamelCaseName - (:83, :110). An empty field list means all properties (GetShapedAccessors, :476-477).
    155. -
    156. ApplySorting<TEntity> has two overloads (:155-162, :183-212). The five-argument overload is - a thin wrapper that calls the six-argument one with fieldContract: null (:162), which resolves - the sort column through ResolveSortExpression (:192), then emits - query.OrderBy(Filtering.DynamicQueryConfig.Parameterized, BuildOrdering(...)) (:196-198). When - no valid sort column survives it falls back to the optional defaultSort lambda (:201-206) and, - failing that, to the tie-break key alone (:209-211).
    157. -
    158. ResolveSortExpression<TEntity> (:226-251) is the security-relevant half, hardened by a + (:83, :110). An empty field list means all properties (GetShapedAccessors, :593-594).
    159. +
    160. ApplySorting<TEntity> has two overloads (:155-162, :184-213). The six-parameter overload is + a thin wrapper that calls the seven-parameter one with fieldContract: null (:162), which resolves + the sort column through ResolveSortExpression (:193), then emits + query.OrderBy(Filtering.DynamicQueryConfig.Parameterized, BuildOrdering(...)) (:197-199). When + no valid sort column survives it falls back to the optional defaultSort lambda (:202-208) and, + failing that, to the tie-break key alone (:210-212).
    161. +
    162. ResolveSortExpression<TEntity> (:227-252) is the security-relevant half, hardened by a QueryFieldContract parameter (SEC-ADC-09). A server-authored map entry - still wins outright (:235-238); an unmapped name is refused immediately when a fieldContract - was supplied and does not declare it (:239-242, AllowsClientKey), and otherwise (no contract, + still wins outright (:236-239); an unmapped name is refused immediately when a fieldContract + was supplied and does not declare it (:240-243, AllowsClientKey), and otherwise (no contract, the pre-hardening path) is accepted only when reflection finds a real public property of the - entity, matched with BindingFlags.IgnoreCase (:244-248). Either way the resolved path is then - refused past QueryFieldContract.MaxNavigationDepth segments (:250, SEC-Store-13). Anything + entity, matched with BindingFlags.IgnoreCase (:244-249). Either way the resolved path is then + refused past QueryFieldContract.MaxNavigationDepth segments (:251, SEC-Store-13). Anything refused returns null and never reaches Dynamic LINQ.
    163. -
    164. BuildOrdering (:258-267) turns the resolved expression plus "asc"/"desc" into the Dynamic +
    165. BuildOrdering (:259-268) turns the resolved expression plus "asc"/"desc" into the Dynamic LINQ clause and appends ", {tieBreakProperty} ascending" unless the caller already sorted by that very column, because repeating a key in an ORDER BY is redundant and some providers reject it outright.
    166. -
    167. ApplyFieldSelection<TEntity> (:278-309): returns the query untouched for an empty field list, +
    168. ApplyFieldSelection<TEntity> (:279-310): returns the query untouched for an empty field list, otherwise pulls the compiled projection out of ProjectionCache on the lock-free TryGetValue hit path, skips projection entirely once the cache is at its cap, - and applies the lambda as query.Select(...). BuildProjection<TEntity> (:331-353) + and applies the lambda as query.Select(...). BuildProjection<TEntity> (:332-354) is the builder: it keeps only writable properties that match the field set (p.CanWrite), since EF cannot translate a MemberInit that assigns a read-only member, returns null when nothing survives, and otherwise builds new TEntity { Prop = e.Prop, ... } so the projection is pushed into the SQL SELECT.
    169. -
    170. Three Validate overloads, each thinner than the next: Validate<TEntity>(fields, allowWriteableFields) (:366-367), the map-aware Validate<TEntity>(fields, dtoToEntityPropertyMap, allowWriteableFields = false) (:401-405, calls the shared body with - fieldContract: null), and the fully hardened Validate<TEntity>(fields, dtoToEntityPropertyMap, allowWriteableFields, fieldContract) (:423-428, SEC-ADC-09). All three delegate to the shared - ValidateFields (:434-456), which loops the requested field set through - ValidateSingleField (:462-522) per name. ValidateSingleField checks, in order: a - map entry wins unconditionally and is never reflected over (:471-483), except that a mapped +
    171. Three Validate overloads, each thinner than the next: Validate<TEntity>(fields, allowWriteableFields) (:368-369), the map-aware Validate<TEntity>(fields, dtoToEntityPropertyMap, allowWriteableFields = false) (:404-408, calls the shared body with + fieldContract: null), and the fully hardened Validate<TEntity>(fields, dtoToEntityPropertyMap, allowWriteableFields, fieldContract) (:426-431, SEC-ADC-09). All three delegate to the shared + ValidateFields (:437-459), which loops the requested field set through + ValidateSingleField (:465-525) per name. ValidateSingleField checks, in order: a + map entry wins unconditionally and is never reflected over (:474-486), except that a mapped path deeper than QueryFieldContract.MaxNavigationDepth is now rejected too - (:473-480, SEC-Store-13); an unmapped name is refused when a fieldContract was supplied and - does not declare it (:489-498, the RESPONSE contract, not the entity, decides what a client may + (:476-483, SEC-Store-13); an unmapped name is refused when a fieldContract was supplied and + does not declare it (:492-501, the RESPONSE contract, not the entity, decides what a client may name); and only then does it fall through to the entity-reflection check, existence - (:500-512) plus, when allowWriteableFields is false, not read-only (:514-521). All + (:503-515) plus, when allowWriteableFields is false, not read-only (:517-524). All offenders accumulate into one aggregate Result.
    172. -
    173. ValidateSortDirection (:529-548): accepts only "asc", "desc", or null/empty, and returns +
    174. ValidateSortDirection (:532-551): accepts only "asc", "desc", or null/empty, and returns an Error.Validation("Error.InvalidSortDirection", ...) failure otherwise.
    175. -
    176. Private helpers: ParseFields (:550-554) splits the comma list into a case-insensitive - HashSet; GetProperties<TEntity> (:556-559) reads through PropertiesCache; - FilterAccessorsByFields (:561-567) narrows an accessor array; GetShapedAccessors - (:585-599+) is the cached front door to it.
    177. +
    178. Private helpers: ParseFields (:553-557) splits the comma list into a case-insensitive + HashSet; GetProperties<TEntity> (:559-562) reads through PropertiesCache; + FilterAccessorsByFields (:564-570) narrows an accessor array; GetShapedAccessors + (:588-613) is the cached front door to it, filtering per request instead of caching once + ShapedAccessorCache is at its cap (:606-607).
    179. Concept introduced, gating client-named sort/filter/lookup columns on the response contract, not @@ -624,11 +625,16 @@

      QueryFieldService

      ShapeCollectionData and ShapeData are called after mapping (EntityQueryService.cs:372, :515); ApplySorting and ApplyFieldSelection are called inside EntityQueryPipeline - (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/EntityQueryPipeline.cs:81, :175, - :208, :227, :251).

      + (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/EntityQueryPipeline.cs:81, :176, + :210, :229, :254).

    180. Caveats / not-in-source: the class is sealed but every member is static, so it is never - instantiated or injected; treat it as a static utility despite the shape.

      + instantiated or injected; treat it as a static utility despite the shape. Three public overloads + carry a SuppressMessage for the public-API analyzer's optional-parameter rules: the seven-parameter + ApplySorting for RS0026 (:183) and the two older Validate overloads for RS0027 (:367, + :403). Each justification records a grandfathered overload whose signature cannot change + without a breaking change (the RS0026/RS0027 baseline), so the overload set is frozen as shipped + rather than collapsed.


    181. @@ -853,10 +859,10 @@

      DynamicQueryConfig

      runs with the default (constant-folding) config. Keeping it internal means no consumer can pass a different config into the built-in strategies.
    182. Where it's used: passed as the first argument to every query.Where(...) in all seven built-in - strategies (for example IntFilterStrategy.cs:31, StringFilterStrategy.cs:23, + strategies (for example IntFilterStrategy.cs:31, StringFilterStrategy.cs:32, DateTimeFilterStrategy.cs:32) and to every ordering call in QueryFieldService.ApplySorting - (MMCA.Common/Source/Core/MMCA.Common.Application/Services/QueryFieldService.cs:197, :177, + (MMCA.Common/Source/Core/MMCA.Common.Application/Services/QueryFieldService.cs:198, :177, :182, which qualify it as Filtering.DynamicQueryConfig.Parameterized because that class sits in the parent namespace).
    183. Caveats / not-in-source: internal, so a consumer writing a custom @@ -917,14 +923,14 @@

      FilterValueParser

      List<T> rather than an array matters downstream: LINQ Dynamic binds it as the receiver of a Contains call. Putting CanParse here rather than in each strategy is what keeps the validation rule and the application rule from drifting apart, which is exactly the class of bug - QueryFilterService documents at QueryFilterService.cs:284-288.
    184. + QueryFilterService documents at QueryFilterService.cs:293-297.
    185. Where it's used: every value strategy that supports IN or BETWEEN routes through ParseList: DateTimeFilterStrategy (DateTimeFilterStrategy.cs:59, :66), DecimalFilterStrategy (DecimalFilterStrategy.cs:55, :62), GuidFilterStrategy (GuidFilterStrategy.cs:38), IntFilterStrategy (IntFilterStrategy.cs:56, :63), and LongFilterStrategy (LongFilterStrategy.cs:55, :62); - StringFilterStrategy (StringFilterStrategy.cs:45) calls + StringFilterStrategy (StringFilterStrategy.cs:54) calls ParseStringList. CanParse backs the CanParseValue override on all six value strategies (BoolFilterStrategy.cs:21, DateTimeFilterStrategy.cs:26, DecimalFilterStrategy.cs:25, GuidFilterStrategy.cs:22, IntFilterStrategy.cs:26, LongFilterStrategy.cs:25).
    186. @@ -981,7 +987,7 @@

      IFilterStrategy

      out-of-tree implementer.
    187. Where it's used: implemented by the seven built-in strategies below; the registry, the dispatch, and the up-front validation all live in QueryFilterService - (QueryFilterService.cs:280, :195 for the two validation hooks).
    188. + (QueryFilterService.cs:289, :195 for the two validation hooks).
      @@ -1006,8 +1012,8 @@

      IFilterStrategy

      widened response, the six value strategies override CanParseValue by delegating to FilterValueParser.CanParse with their own parse function, so QueryFilterService rejects the request before it executes. - StringFilterStrategy is the one that does not override it: every raw - string is a valid string, so the interface default of true is already correct. + StringFilterStrategy overrides it more narrowly: every raw string is + a valid string, so only an IN list with no usable item is refused.
    189. IN is set membership. The strategies that support IN decode a comma list through FilterValueParser and emit @0.Contains({property}), with the parsed list as the receiver @0 and the entity property as the argument, the shape LINQ Dynamic turns @@ -1108,31 +1114,40 @@

      DateTimeFilterStrategy

    190. Depends on: IFilterStrategy, FilterValueParser, DynamicQueryConfig; System.Globalization, System.Collections.Frozen, System.Linq.Dynamic.Core.
    191. -
    192. Concept introduced, culture-invariant parsing of untrusted input. FormatProvider is a static - CultureInfo.InvariantCulture (:15) used by every DateTime.TryParse call, so "2026-07-21" - parses identically regardless of the server's locale. That is the single-locale, culture-safe - posture described in primer §4, and it is what - keeps a filter from meaning different things on two hosts in the same cluster.
    193. +
    194. Concept introduced, culture-invariant and zone-invariant parsing of untrusted input. + FormatProvider is a static CultureInfo.InvariantCulture (:15), so "2026-07-21" parses + identically regardless of the server's locale. That is the single-locale, culture-safe posture + described in primer §4. The second half is + the time zone: ParseDateTime (:80-81) passes + DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, so an offset or Z in the + value is honoured and normalized to UTC, a bare value is taken as UTC, and the result is always a + DateTimeKind.Utc instant. Its doc comment (:72-79) states the reason: that is how the framework + stores every DateTime, and the host's local zone never enters the result, so the same filter + selects the same rows on every server in the cluster.
    195. Walkthrough: SupportedOperators (:17-22) is the ten-entry frozen set. CanParseValue (:25-26) delegates to FilterValueParser.CanParse with the ParseDateTime - method group. Apply<T> (:28-47) parses inside each temporal arm via a when clause, for - example - "IS AFTER" when DateTime.TryParse(value, FormatProvider, DateTimeStyles.None, out var dt) => query.Where(DynamicQueryConfig.Parameterized, $"{property} > @0", dt) (:35-36). A failed parse - means the when guard is false and the arm does not match. The two null operators need no value - (:43-44). The _ => arm routes to ApplyInOrRange (:46), which dispatches IN to ApplyIn - and BETWEEN to ApplyBetween (:49-55). ApplyIn (:57-61) parses through + method group. Apply<T> (:28-47) parses inside each temporal arm via a when clause over + the same helper, for example + "IS AFTER" when ParseDateTime(value) is { } dt => query.Where(DynamicQueryConfig.Parameterized, $"{property} > @0", dt) (:35-36). A failed parse + yields null, the property pattern does not match, and the arm is skipped. The two null operators + need no value (:43-44). The _ => arm routes to ApplyInOrRange (:46), which dispatches IN + to ApplyIn and BETWEEN to ApplyBetween (:49-55). ApplyIn (:57-61) parses through FilterValueParser.ParseList with ParseDateTime (:59) and emits @0.Contains({property}) (:60); ApplyBetween (:63-70) requires exactly two bounds (:67) - and emits {property} >= @0 && {property} <= @1 (:68). ParseDateTime (:72-73) is the shared - culture-invariant TryParse.
    196. + and emits {property} >= @0 && {property} <= @1 (:68). ParseDateTime (:80-81, internal static) is therefore the single parse path for validation, the scalar arms, IN and BETWEEN.
    197. Why it's built this way: parsing per arm keeps the value-taking and the value-free operators in - one switch without a pre-parse that would reject IS EMPTY for having no parsable value; splitting - IN/BETWEEN into a helper keeps the main switch under the analyzers' cyclomatic-complexity - ceiling (the inline comment at :45 records the reason).
    198. + one switch without a pre-parse that would reject IS EMPTY for having no parsable value; routing + every arm through one helper means validation and application cannot disagree on the parse + styles; splitting IN/BETWEEN into a helper keeps the main switch under the analyzers' + cyclomatic-complexity ceiling (the inline comment at :45 records the reason).
    199. Where it's used: registered against typeof(DateTime) and typeof(DateTime?) in - QueryFilterService (QueryFilterService.cs:42-43), as two instances.
    200. -
    201. Caveats / not-in-source: nothing here normalizes to UTC: the parsed value is used as given - (DateTimeStyles.None), so the caller is responsible for supplying a value in the column's kind.
    202. + QueryFilterService (QueryFilterService.cs:42-43), as two instances. + Behavior is pinned by + MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Services/Filtering/DateTimeFilterStrategyTests.cs. +
    203. Caveats / not-in-source: a bare value is read as UTC, not as the caller's local time, so a + client filtering on a local wall-clock time must send an explicit offset. Whether a given column + actually holds UTC values depends on how the entity was written, which is not visible from this + class.

    204. DecimalFilterStrategy

      @@ -1275,37 +1290,43 @@

      StringFilterStrategy

    205. Concept introduced, method-call expressions in LINQ Dynamic. Where the value strategies emit operator comparisons, the text operators emit method calls on the property: {property}.Contains(@0), !{property}.Contains(@0), {property}.StartsWith(@0), - {property}.EndsWith(@0) (:23-28), and string.IsNullOrEmpty({property}) / - !string.IsNullOrEmpty({property}) for the presence checks (:37-38). LINQ Dynamic parses the + {property}.EndsWith(@0) (:32-37), and string.IsNullOrEmpty({property}) / + !string.IsNullOrEmpty({property}) for the presence checks (:46-47). LINQ Dynamic parses the string into an expression tree, and EF Core then translates those calls into LIKE predicates, so the match still runs in the database. [Rubric §12, Performance & Scalability]: CONTAINS translates to a leading-wildcard LIKE, which cannot use a normal B-tree index, that is a known cost of the convenience, not a defect of this class.
    206. -
    207. Walkthrough: SupportedOperators (:14-18) is the nine-entry frozen set. This is the only - built-in strategy that does not override CanParseValue, so it inherits the interface default - of true (IFilterStrategy.cs:44): every raw string is a usable string value, so there is nothing - to reject. Apply<T> (:20-30) handles the six value-taking text operators, then delegates the - rest to ApplyPresenceOrSet (:34-41); the inline comment (:32-33) records why, the split keeps - each method under the cyclomatic-complexity ceiling the analyzers enforce as errors. - ApplyPresenceOrSet covers IS EMPTY, IS NOT EMPTY, and routes IN to ApplyIn (:43-47), - which uses FilterValueParser.ParseStringList (:45) and emits the same +
    208. Walkthrough: SupportedOperators (:14-18) is the nine-entry frozen set. CanParseValue + (:26-27) is overridden, but narrowly: it returns true for every operator except IN, and for + IN it requires FilterValueParser.ParseStringList to yield at least one + item. The remarks (:21-25) record why only IN has a value shape to reject: an IN list with no + usable item would otherwise fail open and return the unfiltered set, where every value-typed + strategy refuses the same input; every other string operator accepts any value, including the + empty string for EQUALS. Unlike the six value strategies it does not delegate to + FilterValueParser.CanParse, because a string has no scalar parse to fail. Apply<T> (:29-39) + handles the six value-taking text operators, then delegates the rest to ApplyPresenceOrSet + (:43-50); the inline comment (:41-42) records why, the split keeps each method under the + cyclomatic-complexity ceiling the analyzers enforce as errors. ApplyPresenceOrSet covers + IS EMPTY, IS NOT EMPTY, and routes IN to ApplyIn (:52-56), which uses + FilterValueParser.ParseStringList (:54) and emits the same @0.Contains({property}) receiver-inverted form as the other IN strategies.
    209. Why it's built this way: the class doc comment (:6-11) still describes this strategy as the one used "for nested property paths (e.g. Category.Name) regardless of the target type, since LINQ Dynamic evaluates the full path as a string expression." Trust the code, not that doc comment: it describes an arrangement the code no longer has. QueryFilterService.ResolveFilterValueType - (QueryFilterService.cs:368-387) walks a dotted path to its leaf and returns the leaf's own type, - and ResolveStrategy (:303-306) hands back this strategy only when that resolved type is - typeof(string). A path whose leaf cannot be walked no longer falls back here at all: it now fails - closed (ResolveFilterValueType returns null, and both callers reject or skip the filter, - QueryFilterService.cs:257-265 and :98-99). So "Category.Id" on a Guid key gets + (QueryFilterService.cs:378-397) walks a dotted path to its leaf and returns the leaf's own type, + and ResolveStrategy (QueryFilterService.cs:408-409) hands back this strategy only when that + resolved type is typeof(string). A path whose leaf cannot be walked does not fall back here at + all: it fails closed (ResolveFilterValueType returns null, and both callers reject or skip the + filter, QueryFilterService.cs:266-274 and :129-130). So "Category.Id" on a Guid key gets GuidFilterStrategy's operator set, and "Category.Nonexistent" gets a 400 rather than this strategy's operator set.
    210. Where it's used: registered against typeof(string) in QueryFilterService (QueryFilterService.cs:35), and held a second time as the dedicated StringStrategy field (QueryFilterService.cs:54) that ResolveStrategy returns for - any resolved value type equal to typeof(string) (:303-306). Behavior is pinned by + any resolved value type equal to typeof(string) (QueryFilterService.cs:408-409). Behavior is + pinned by MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Services/Filtering/StringFilterStrategyTests.cs.
    211. Caveats / not-in-source: nothing here escapes LIKE wildcards, so a % inside a CONTAINS value reaches the database as part of the pattern. The value is still a bound parameter (rule 1 of @@ -1355,11 +1376,11 @@

      StronglyTypedIdFilterStrategy recognize the property type, then builds the closed generic through Activator.CreateInstance(typeof(StronglyTypedIdFilterStrategy<,>).MakeGenericType(identifierType, valueType)) (:81-84). QueryFilterService.ResolveStrategy calls TryCreate only after its own Strategies table misses, and memoizes the result with - Strategies.GetOrAdd (QueryFilterService.cs:404-406), so the reflection cost is paid once per + Strategies.GetOrAdd (QueryFilterService.cs:414-416), so the reflection cost is paid once per identifier type, not once per request. That is what lets a consumer adopt services.AddStronglyTypedIds(...) without a startup RegisterStrategy call per identifier.

    212. Where it's used: created on demand inside - QueryFilterService.ResolveStrategy (QueryFilterService.cs:396-407) + QueryFilterService.ResolveStrategy (QueryFilterService.cs:406-417) whenever a filter's resolved value type is a strongly typed identifier with no explicitly registered strategy. Behavior is pinned by MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Services/Filtering/StronglyTypedIdFilterStrategyTests.cs.
    213. @@ -1408,38 +1429,50 @@

      QueryFilterService

      disappear, returning an unfiltered 200. [Rubric §9, API & Contract Design]: the alternative failure, letting Dynamic LINQ blow up at query-build time, turns a bad request into a 500.
    214. Concept introduced, gating an unmapped filter key against the response contract rather than the - entity (SEC-Common-25, SEC-ADC-09, SEC-Store-13). Both public entry points now come in a pair: the + entity (SEC-Common-25, SEC-ADC-09, SEC-Store-13). Both public entry points come in a pair: the two-argument ApplyFilters/ValidateFilters overloads forward to the three-argument ones with - fieldContract: null (:78-84, :150-153), preserving the historical entity-reflection behaviour + fieldContract: null (:78-82, :159-162), preserving the historical entity-reflection behaviour for a caller whose filter keys are all server-authored. The three-argument overloads - (:99-103, :166-169) accept a QueryFieldContract? - and route every key through IsAdmissibleKey (:200-210) before it reaches reflection: a + (:99-103, :175-178) accept a QueryFieldContract? + and route every key through IsAdmissibleKey (:209-218) before it reaches reflection: a server-mapped key is trusted for what it names but is still capped at - QueryFieldContract.IsWithinNavigationDepth (:206), since every extra dotted segment past + QueryFieldContract.IsWithinNavigationDepth (:215), since every extra dotted segment past MaxNavigationDepth is another LEFT JOIN, the join-explosion load a self-referencing navigation repeated hundreds of times produces; a key the map did not author is client input and must both - pass that depth check and be declared filterable by fieldContract.AllowsClientKey (:209), so a + pass that depth check and be declared filterable by fieldContract.AllowsClientKey (:218), so a caller can neither walk the object graph nor address a column no DTO exposes. ApplyFilters drops a - key that fails the gate (:117-118); ValidateFilters, through ValidateSingleFilter (:223-232), + key that fails the gate (:117-118); ValidateFilters, through ValidateSingleFilter (:232-240), turns it into the same Filter.Property.NotFound error an unknown property gets, so the two paths agree on what a caller may reach. [Rubric §11, Security]: this closes a hole the entity-reflection path left open, an unmapped key could address any public entity property, including one the response DTO never carries.
    215. +
    216. Concept introduced, case-insensitive key resolution with a canonical member name. A filter key + resolves against the entity case-insensitively: LookupProperty passes BindingFlags.IgnoreCase + (:358-359, the comment naming the sort column, the field contract and the filter dictionary as + the precedents). Resolving loosely is only half of it: the Dynamic LINQ expression must then name a + member that actually exists, so ApplyFilters picks a memberPath (:134-141). A flat client key + is applied under the property's declared name (propertyInfo.Name), so the emitted member access + never depends on how the client cased it; a server-authored map entry, or any dotted path, is + applied verbatim. The comment records why the server-authored case must not be substituted: an + entry may be an expression rather than a member (ADC maps Speaker "FullName" to a + FirstName + " " + LastName concatenation because the entity's own FullName is unmapped), and + replacing it with the property name produces a query EF cannot translate.
    217. Walkthrough
      • PropertyCache (:30): ConcurrentDictionary<(Type EntityType, string PropertyName), PropertyInfo>, so a reflected lookup is paid once per entity/property pair. Read the doc comment - (:24-28) and LookupProperty<TEntity> (:343-354) together: only successful lookups are - memoized (if (resolved is not null) PropertyCache[key] = resolved;, :350-351). Caching + (:24-29) and LookupProperty<TEntity> (:352-364) together: only successful lookups are + memoized (if (resolved is not null) PropertyCache[key] = resolved;, :360-361). Caching misses too would let any caller grow a process-lifetime static dictionary without bound simply by filtering on names that do not exist, and because the request still gets a clean 400 the growth - would be invisible in error metrics. A miss now costs one reflection lookup, bounded per request + would be invisible in error metrics. A miss costs one reflection lookup, bounded per request by MaxFilters = 50 in QueryFilterModelBinder. This is an - unbounded-memory-growth defense, [Rubric §11, Security] and [Rubric §12, Performance & Scalability].
      • + unbounded-memory-growth defense, [Rubric §11, Security] and [Rubric §12, Performance & Scalability]. The cache key is the name as probed (:354), so two casings of one property are + two entries pointing at the same PropertyInfo.
      • StringStrategy (:54): a dedicated StringFilterStrategy instance held apart from the table, returned by ResolveStrategy whenever the resolved value type is typeof(string) (its doc comment at :50-53 names both cases: a flat string property, or a nested path whose leaf is one).
      • -
      • ApplyFilters<TEntity> (:99-140, the fieldContract-accepting overload; :78-84 is the +
      • ApplyFilters<TEntity> (:99-149, the fieldContract-accepting overload; :78-82 is the two-argument forwarder): for each (property, (op, value)) it translates the DTO name through dtoToEntityPropertyMap (:111-112), gates the resolved key through IsAdmissibleKey (:117-118), resolves the PropertyInfo through ResolvePropertyInfo (:120) and silently @@ -1449,45 +1482,45 @@

        QueryFilterService

        rejects all of these cases, so reaching here means the caller never validated, and applying a filter the query cannot express is worse than dropping it. Only then does it uppercase the operator once (:132, the caller-side half of the IFilterStrategy - contract), resolve the strategy from the value type (:134), and apply it when one exists - (:135-136).
      • -
      • ValidateFilters<TEntity> (:166-184, the fieldContract-accepting overload; :150-153 is the - two-argument forwarder): returns success for a null/empty filter map (:173-174), otherwise runs - ValidateSingleFilter per entry with fieldContract threaded through (:178-179) and collects - all errors into one Result (:181-183), so a + contract), choose the memberPath (:139-141), resolve the strategy from the value type + (:143), and apply it under that member path when one exists (:144-145).
      • +
      • ValidateFilters<TEntity> (:175-193, the fieldContract-accepting overload; :159-162 is the + two-argument forwarder): returns success for a null/empty filter map (:182-183), otherwise runs + ValidateSingleFilter per entry with fieldContract threaded through (:187-188) and collects + all errors into one Result (:190-192), so a client sees every problem at once rather than one per round trip.
      • -
      • ValidateSingleFilter<TEntity> (:212-, continuing past the excerpt shown here) is a five-gate - ladder, each gate returning early so one filter never yields two errors about the same mistake. - Gate zero: IsAdmissibleKey fails, Filter.Property.NotFound naming the key as not filterable - (:223-232). Gate one: reflection cannot resolve the property at all, the same code, a different - message. Gate two: the property resolves but the dotted path's leaf does not, again +
      • ValidateSingleFilter<TEntity> (:221-291) is a five-gate ladder, each gate returning early so + one filter never yields two errors about the same mistake. Gate zero: IsAdmissibleKey fails, + Filter.Property.NotFound naming the key as not filterable (:232-240). Gate one: reflection + cannot resolve the property at all (:244), the same code, a different message. Gate two: the + property resolves but the dotted path's leaf does not (:266-274), again Filter.Property.NotFound naming the path. Gate three: no strategy is registered for the resolved - value type, Filter.Type.NotSupported. Gate four: it delegates to ValidateOperatorSupported for - Filter.Operator.NotSupported and ValidateValueParseable (:289-) for Filter.Value.Invalid. - Every error carries source: nameof(ValidateFilters) so the origin is identifiable in a - problem-details payload.
      • -
      • ValidateValueParseable (:289-) is the guard behind rule 3 of the family preamble: without it + value type (:278), Filter.Type.NotSupported. Gate four: it delegates to + ValidateOperatorSupported for Filter.Operator.NotSupported and ValidateValueParseable for + Filter.Value.Invalid (:289-290). Every error carries source: nameof(ValidateFilters) so the + origin is identifiable in a problem-details payload.
      • +
      • ValidateValueParseable (:298-) is the guard behind rule 3 of the family preamble: without it the strategy returns the query unfiltered, so a malformed value widened the response to the full result set instead of narrowing it to no matches. It deliberately stays silent when the operator itself is already invalid, so one bad filter does not produce two errors describing the - same mistake.
      • -
      • ResolvePropertyInfo (:331-338): probes the DTO-facing name first, then the mapped entity name - (its root segment for a dotted path, :333-335). The doc comment records why both the apply path + same mistake; otherwise it asks strategy.CanParseValue (:311).
      • +
      • ResolvePropertyInfo (:340-347): probes the DTO-facing name first, then the mapped entity name + (its root segment for a dotted path, :342-344). The doc comment records why both the apply path and the validate path share it: they used to disagree on the fallback order, so a plain rename entry such as ["Name"] = "Title" passed validation and was then silently dropped, returning an unfiltered result set with a 200.
      • -
      • ResolveFilterValueType<TEntity> (:368-387): for a flat property this is the property's own - type (:370-371); for a dotted path it walks each segment to the leaf's type (:373-386), - starting from the path's own root rather than from the already-resolved property (:375-377, +
      • ResolveFilterValueType<TEntity> (:378-397): for a flat property this is the property's own + type (:380-381); for a dotted path it walks each segment to the leaf's type (:383-396), + starting from the path's own root rather than from the already-resolved property (:385-387, because for a nested path the DTO-facing name and the path root need not agree). The walk stops the moment a segment is not a public instance property of the preceding segment's type - (:379-384), and the remarks spell out that the null result is a fail-closed signal, not a + (:389-394), and the remarks spell out that the null result is a fail-closed signal, not a fallback: validation rejects it as an unknown property and application skips it.
      • -
      • ResolveStrategy (:396-407): typeof(string) maps to StringStrategy, an explicitly - registered type is a TryGetValue hit on the table (:401-402); otherwise it asks +
      • ResolveStrategy (:406-417): typeof(string) maps to StringStrategy (:408-409), an + explicitly registered type is a TryGetValue hit on the table (:411-412); otherwise it asks StronglyTypedIdFilterStrategy.TryCreate to build a strategy for a strongly typed identifier column and, when that succeeds, memoizes it into - Strategies with GetOrAdd (:404-406), so an unregistered, non-identifier type is the only case + Strategies with GetOrAdd (:414-416), so an unregistered, non-identifier type is the only case that still yields null and fires gate three of the validation ladder.
    218. @@ -1496,27 +1529,37 @@

      QueryFilterService

      resolution to first use per process while refusing to memoize client-controlled misses; gating an unmapped key against the response contract closes the same class of over-exposure the property cache and the nested-path fail-closed rule already close, just at the key-name layer instead of the - type layer; and resolving a nested path to its leaf type is what lets one operator vocabulary apply + type layer; resolving case-insensitively but emitting the declared name keeps the key matching + rule consistent with sorting and the field contract without letting client casing reach the + expression; and resolving a nested path to its leaf type is what lets one operator vocabulary apply uniformly to flat and nested properties without the string strategy standing in for everything.
    219. Where it's used: ValidateFilters is called from EntityQueryService.GetAllAsync - (MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:267); + (MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:301); ApplyFilters is called inside EntityQueryPipeline on both of its paths, - before materialization - (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/EntityQueryPipeline.cs:77 for the - projected path and :151 for the entity path). The filter map itself is produced at the API edge by + before materialization + (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/EntityQueryPipeline.cs:77 and + :152). All three calls use the three-argument overload with the caller's FieldContract. The + filter map itself is produced at the API edge by QueryFilterModelBinder. Its own behavior is pinned by four test classes under MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Services/Filtering/ (QueryFilterServiceTests, QueryFilterServiceValidateTests, QueryFilterServicePropertyCacheTests, StronglyTypedIdFilterStrategyTests) plus MMCA.Common.Application.Tests/Services/Query/QueryFieldContractSecurityTests.cs, on top of the - seven per-strategy suites in the same folder.
    220. + seven per-strategy suites in the same folder. Downstream, ADC pins its public schedule's Room filter + through the same two calls + (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/SessionRoomFilterTests.cs:16), + so a renamed or retyped Session.RoomId fails a test rather than silently dropping the filter.
    221. Caveats / not-in-source: this is a static class, not a DI-resolved service, so RegisterStrategy mutates process-global state and is only safe at startup; there is no deregistration and no per-tenant table. Nullable variants are registered as separate instances rather than a shared one (:35-47), which costs a few extra objects but keeps the table declaration - flat. ResolveFilterValueType walks only public instance properties (BindingFlags.Public | BindingFlags.Instance), so a path through a non-public member is unreachable by design.
    222. + flat. ResolveFilterValueType walks only public instance properties (BindingFlags.Public | BindingFlags.Instance), so a path through a non-public member is unreachable by design. The + case-insensitive lookup covers the root segment only: the segment walk past it (:392) does not + pass BindingFlags.IgnoreCase, so a dotted path needs every segment after the first cased as + declared, and because a dotted path is applied verbatim (:139) its casing reaches the Dynamic + LINQ expression unchanged.

      AmbientScope

      @@ -1535,7 +1578,7 @@

      AmbientScope

      try/finally at each call site, lets QueryTagScope.Begin hand back a uniform IDisposable regardless of whether the scope is nested; the outermost, by far the most common, case never allocates one at all (see QueryTagScope's shared OutermostScope handle). -
    223. Where it's used: constructed only inside QueryTagScope.Begin (QueryTagScope.cs:333) when a +
    224. Where it's used: constructed only inside QueryTagScope.Begin (QueryTagScope.cs:52) when a scope is already open; not referenced anywhere else.

    225. @@ -1692,7 +1735,7 @@

      EntityQueryParameters<TEntity>

      filtering and sorting can use DTO-facing names even when they differ from the entity's own properties; it defaults to FrozenDictionary<string, string>.Empty so callers that do not need remapping can omit it. -
    226. QueryFieldContract? FieldContract (EntityQueryParameters.cs:280), the response contract a +
    227. QueryFieldContract? FieldContract (EntityQueryParameters.cs:52), the response contract a client-supplied sort column or filter key must belong to when DTOToEntityPropertyMap does not cover it (SEC-Common-25, SEC-ADC-09). null leaves the pipeline resolving names against the entity, the pre-hardening behavior, which is only correct for a caller whose keys are server-authored rather @@ -2107,7 +2150,7 @@

      ISpecification<TEntity, TIdent IEntityQueryService<TEntity, TEntityDTO, TIdentifierType> (for example MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Mapping/IEntityQueryService.cs:40), and of the repository's specification-driven reads - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:572).

    228. + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:587).

      Specification<TEntity, TIdentifierType>

      @@ -2252,7 +2295,7 @@

      AndSpecification<TEntity, TI through the fluent SpecificationExtensions.And rather than the constructor. ADC's SessionsController ANDs the public-session filter with the speaker-scoped filter when a SpeakerId filter is present - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:125-127, + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:133-135, the composite then passed as the specification argument at :187) and SpeakersController does the same for its own public/filtered pair (.../Controllers/SpeakersController.cs:171-173); ADC's @@ -2386,7 +2429,7 @@

      QuerySpecification<TEntity deleted rows into scope drops the named SoftDelete filter and only that one, so the tenant filter stays in force and a specification asking for deleted rows can never reach another tenant's data (:74-82, enforced repository-side at - MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:566-568). + MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:581-583).
    229. Walkthrough: state is exposed read-only and assembled through protected builders a derived specification calls from its constructor.
      • Backing fields _orderBy and _includePaths (:43-44) with the read-only projections @@ -2418,8 +2461,8 @@

        QuerySpecification<TEntity a plain ISpecification against this type (SpecificationEvaluator.cs:56) and, when it matches, applies the includes (:51), the ordering chain (:52) and the Skip/Take window (:54-58); aggregate reads pass applyShape: false so a count never joins in includes or pages (:29-34, with - the call sites at EFReadRepository.cs:454 and :516). The tracking and soft-delete flags are - consumed one level up, in EFReadRepository.BaseQueryFor (EFReadRepository.cs:560-569), which + the call sites at EFReadRepository.cs:455 and :516). The tracking and soft-delete flags are + consumed one level up, in EFReadRepository.BaseQueryFor (EFReadRepository.cs:575-584), which chooses Table vs TableNoTracking (:443) and drops the named soft-delete filter (:445-447). Behavior is pinned by MMCA.Common/Tests/Core/MMCA.Common.Domain.Tests/Specifications/QuerySpecificationTests.cs:14, @@ -2537,7 +2580,7 @@

        SpecificationExtensions

      • Where it's used: And is the spelling every production composition in the workspace uses. ADC's SessionsController writes publicSpecification.And(speakerResult.Value!) - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:127), + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:135), SpeakersController writes publicSpecification.And(filterResult.Value!) (.../Controllers/SpeakersController.cs:173), and PublicConferenceVisibility chains @@ -2633,7 +2676,7 @@

        IEntityQueryServi generic controller base (G12), for example ADC's SessionsController, which passes its specification as the specification argument at - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:148 + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:156 and :187.


      diff --git a/docs/onboarding/group-04-events-outbox.html b/docs/onboarding/group-04-events-outbox.html index dc9a2647..b24ca8d3 100644 --- a/docs/onboarding/group-04-events-outbox.html +++ b/docs/onboarding/group-04-events-outbox.html @@ -262,7 +262,7 @@

      Raising and capturing relational source owns its own OutboxMessages table, never a shared one (ADR-006; the table, its origin columns, and its three filtered indexes, pending, processed, and ordering, are configured in - MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:664-711, with the inbox's + MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:667-714, with the inbox's table and unique index at ApplicationDbContext.cs:721-731; see the primer on database-per-service).

      Two details of the capture exist to stop duplicate rows. The interceptor snapshots exactly the events @@ -349,7 +349,7 @@

      Who the delivery runs as: (OutboxMessage.cs:125-131).

      Capture is one read per save, not one per event. The interceptor is a singleton and cannot reach a scoped service, so DbContextFactory attaches a - live accessor to the scoped context (DbContextFactory.cs:149-151) and the interceptor reads it once + live accessor to the scoped context (DbContextFactory.cs:155-157) and the interceptor reads it once through CurrentOutboxOrigin (ApplicationDbContext.cs:158,164, DomainEventSaveChangesInterceptor.cs:242-247); the two event buses do the same once per batch (InProcessEventBus.cs:87-89, BrokerEventBus.cs:79-81). One save is one scope's work, so every row @@ -385,7 +385,7 @@

      The safety net: how t that the same event may be delivered more than once, so handlers must be idempotent. That is not a wart; it is the documented contract and a healthy discipline regardless.

      The processor never blindly polls on a fixed clock. Its loop is the shared PollingLoop - (MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:108-122): after a five second startup delay + (MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:106-120): after a five second startup delay (MMCA.Common.Infrastructure/Persistence/Polling/PollingLoop.cs:18,51) it drains every outbox target once per cycle and aggregates the per-target results into an OutboxCycleResult (OutboxProcessor.cs:157-174), then waits on @@ -445,8 +445,8 @@

      Failu broker publish throwing) increments the row's RetryCount, records LastError, and re-leases the row for an explicit exponential backoff rather than leaving this cycle's claim on it (OutboxProcessor.cs:668-680). The backoff is RetryBackoffBaseSeconds * 2^(n-1) (base 10s, - OutboxSettings.cs:99) multiplied by a random jitter factor in [0.8, 1.2] and capped at the lease - (OutboxProcessor.cs:767-781); the jitter is what stops fifty rows that failed together on one + OutboxSettings.cs:99) multiplied by a random jitter factor in [0.8, 1.2] and capped at the lease + (OutboxProcessor.cs:680-681, delegating to PollingLoop.cs:183-196); the jitter is what stops fifty rows that failed together on one dependency outage from retrying in lockstep against that same dependency. The poll query only ever selects rows with RetryCount < MaxRetries (5 by default, OutboxProcessor.cs:431, OutboxSettings.cs:21), so once a row exhausts its retries it stops being fetched, stalls unprocessed @@ -480,18 +480,18 @@

      Failu OutboxPollFilterProcessor (G16) suppresses from telemetry export, so a fleet of idle services polling around the clock does not flood Application Insights, and the per-message success line is Debug for the same reason - (OutboxProcessor.cs:847-851). Each dispatched message also re-parents an OutboxProcess consumer + (OutboxProcessor.cs:747-751). Each dispatched message also re-parents an OutboxProcess consumer activity onto the trace context stored on its row, so the broker hop stays linked to the request that - raised the event (OutboxProcessor.cs:808-831).

      + raised the event (OutboxProcessor.cs:708-730).

      A sibling OutboxCleanupService keeps the tables bounded. Every CleanupIntervalHours (default 6, OutboxSettings.cs:73) it purges processed rows older than - RetentionDays (default 7, OutboxCleanupService.cs:95,109, OutboxSettings.cs:65), then purges + RetentionDays (default 7, OutboxCleanupService.cs:90,104, OutboxSettings.cs:65), then purges dead-lettered rows on their own DeadLetterRetentionDays window, falling back to RetentionDays when - that setting is left at its default of zero (OutboxCleanupService.cs:150-159, OutboxSettings.cs:108), + that setting is left at its default of zero (OutboxCleanupService.cs:145-154, OutboxSettings.cs:108), since those rows never get a ProcessedOn and would otherwise accumulate forever inside the pending index that every poll re-scans. When the inbox is enabled it purges processed inbox rows on the same - cutoff (OutboxCleanupService.cs:57,174-181). Setting RetentionDays to 0 disables the sweep entirely - (OutboxCleanupService.cs:73-85). Because payloads may contain personal data, this sweep is also part of + cutoff (OutboxCleanupService.cs:52,169-176). Setting RetentionDays to 0 disables the sweep entirely + (OutboxCleanupService.cs:68-80). Because payloads may contain personal data, this sweep is also part of the privacy posture of ADR-005. Both background services take an injected TimeProvider (OutboxProcessor.cs:63, OutboxCleanupService.cs:52-54) so tests can drive an hour-scale loop deterministically instead of @@ -500,7 +500,7 @@

      Failu is the scoped EF-backed admin surface behind IOutboxAdministration: it lists dead letters with merged paging across every target and a hard 500-row page cap - (MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:45,56), counts pending rows + (MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:40,51), counts pending rows (OutboxAdministration.cs:174), and replays dead letters as one set-based UPDATE per target followed by a Signal() so the processor picks them up immediately rather than on the next interval (OutboxAdministration.cs:115,146,167). It visits the same tenant-expanded targets in the same @@ -513,7 +513,7 @@

      The plugga IMessageBus, both defined in Application, so neither ever sees MassTransit). Infrastructure supplies two interchangeable implementations of each, selected by registration:

        -
      • Monolith mode (the defaults, MMCA.Common.Infrastructure/DependencyInjection.cs:305,311). +
      • Monolith mode (the defaults, MMCA.Common.Infrastructure/DependencyInjection.cs:315,321). InProcessEventBus writes the events to the outbox in one save, dispatches them in-process, and marks them processed through the same OutboxFinalizer path as the interceptor (MMCA.Common.Infrastructure/Messaging/InProcessEventBus.cs:76-103), falling back to a @@ -604,7 +604,7 @@

        Consuming (IInboxStore.cs:19-22). What TryBeginAsync adds is staging: EfInboxStore does not write the InboxMessage row after the handlers run, it stages it into the same scoped ApplicationDbContext the handlers write through - (MMCA.Common.Infrastructure/Persistence/Inbox/EfInboxStore.cs:49,61-68). A handler's own + (MMCA.Common.Infrastructure/Persistence/Inbox/EfInboxStore.cs:50,62-69). A handler's own SaveChangesAsync therefore commits the dedup row in the same transaction as its mutations, closing by construction the window where a crash between "handler committed" and "inbox written" reprocessed the whole event; CompleteAsync saves the staged row only when nothing else already did, which covers @@ -755,8 +755,8 @@

        IInboxStore

        (IntegrationEventConsumer.cs:122). Implemented by EfInboxStore and NoOpInboxStore.
      • Caveats / not-in-source: both registrations live inside AddBrokerMessaging - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:41), which returns - early when the configured provider is in-process (DependencyInjection.Messaging.cs:50-53), so a monolith + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:42), which returns + early when the configured provider is in-process (DependencyInjection.Messaging.cs:51-54), so a monolith host that never calls it has no IInboxStore in the container at all. That is consistent (nothing consumes the port without a broker consumer) but it does mean the inbox posture is a broker-mode decision, not a container-wide one.
      • @@ -800,12 +800,12 @@

        InboxDisabledWarningService

        startup log next to the rest of the boot sequence, where an operator reads it.
      • Where it's used: added by AddBrokerMessaging inside the else branch of the IsInboxEnabled check, immediately after the NoOpInboxStore registration - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:112-117, the + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:117-122, the AddHostedService call at line 117). Covered by InboxDisabledWarningServiceTests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Inbox/InboxDisabledWarningServiceTests.cs:11), which is what makes a log-only class testable at all ([Rubric §14, Testability]).
      • Caveats / not-in-source: because the whole registration sits inside AddBrokerMessaging, which - returns early for the in-process provider (DependencyInjection.Messaging.cs:50-53), a monolith host never + returns early for the in-process provider (DependencyInjection.Messaging.cs:51-54), a monolith host never sees this warning. That is correct (in-process dispatch does not redeliver) but it does mean the absence of the line is not by itself evidence that dedup is on.
      @@ -832,13 +832,13 @@

      InboxMessage

      InboxMessage.cs:14) is the event's own id, the deduplication key. EventType (required, InboxMessage.cs:17) is retained for diagnostics. ProcessedOn (InboxMessage.cs:20) is the UTC timestamp stamped at staging time. The shape only makes sense together with its EF configuration - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:718-732): - the table is dbo.InboxMessages (ApplicationDbContext.cs:721), EventType is capped at 500 - non-Unicode characters (ApplicationDbContext.cs:723), MessageId carries a unique index named - IX_InboxMessages_MessageId (ApplicationDbContext.cs:724-726), and ProcessedOn carries a - second, non-unique index IX_InboxMessages_ProcessedOn (ApplicationDbContext.cs:730-731) purely + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:721-735): + the table is dbo.InboxMessages (ApplicationDbContext.cs:724), EventType is capped at 500 + non-Unicode characters (ApplicationDbContext.cs:726), MessageId carries a unique index named + IX_InboxMessages_MessageId (ApplicationDbContext.cs:727-729), and ProcessedOn carries a + second, non-unique index IX_InboxMessages_ProcessedOn (ApplicationDbContext.cs:733-734) purely so the age-based retention purge has something to seek instead of scanning the table - (ApplicationDbContext.cs:728-729, [Rubric §12, Performance & Scalability]).
    230. + (ApplicationDbContext.cs:731-732, [Rubric §12, Performance & Scalability]).
    231. Why it's built this way: separating Id (the PK for EF internals) from MessageId (the business dedup key with the unique index) follows the surrogate-key convention used elsewhere in the codebase, and the unique index is what turns a racing duplicate delivery into a catchable @@ -848,15 +848,15 @@

      InboxMessage

      ADR-021 governs the mechanism, and the row lives in the consumer's own database (ADR-006).
    232. Where it's used: staged, saved, and queried by EfInboxStore - (EfInboxStore.cs:55,120); purged by OutboxCleanupService when the inbox - is enabled (OutboxCleanupService.cs:174-189, gated on _inboxEnabled at - OutboxCleanupService.cs:57 and called at OutboxCleanupService.cs:119-122); configured on every - relational context by ApplicationDbContext.ConfigureInbox (ApplicationDbContext.cs:718, called + (EfInboxStore.cs:56,121); purged by OutboxCleanupService when the inbox + is enabled (OutboxCleanupService.cs:169-184, gated on _inboxEnabled at + OutboxCleanupService.cs:52 and called at OutboxCleanupService.cs:114-117); configured on every + relational context by ApplicationDbContext.ConfigureInbox (ApplicationDbContext.cs:721, called from line 347) (G07).
    233. Caveats / not-in-source: the type itself has no first-party reference (it is a plain POCO), so the links to IInboxStore/IDomainEvent above are conceptual, not compile dependencies. The configuration is skipped by the Cosmos context, which overrides OnModelCreating - (ApplicationDbContext.cs:714-716). There is also no tenant column: a tenant with its own database + (ApplicationDbContext.cs:717-719). There is also no tenant column: a tenant with its own database gets its own InboxMessages table instead (see OutboxCleanupService).
    234. [Rubric §10, Messaging & Integration Architecture] applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores.

      @@ -879,7 +879,7 @@

      OutboxDisabledNoticeService

      (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Messaging/MessageBusSettings.cs:175), and in that mode neither OutboxProcessor nor OutboxCleanupService is registered - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:207-215). The delivery + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:217-225). The delivery guarantee changes with it: events reach their handlers synchronously inside the raising process, and a crash between the commit and the dispatch loses them. The class doc states that this is the right trade for a single-process application and the wrong one to discover from an absent hosted service @@ -897,7 +897,7 @@

      OutboxDisabledNoticeService

      is the default posture of an in-process host rather than an opt-out of a safety feature, and a warning on every small application's startup would train operators to ignore the category.
    235. Where it's used: registered by AddInfrastructure in the else branch of the outbox gate - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:212-215), so a host + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:222-225), so a host either gets the two outbox background services or gets this notice, never both and never neither.
    236. [Rubric §10, Messaging & Integration Architecture] applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores.

      @@ -928,10 +928,10 @@

      OutboxOrigin

      (an unauthenticated background job, for instance). Keeping it a record struct rather than a class avoids a heap allocation on the hot save path for what is, per row, four small fields copied once.
    237. Where it's used: constructed by DbContextFactory.AttachScopeAccessors - (DbContextFactory.cs:149-153) from ICurrentUserService, AmbientOrigin.FlattenRoles, the tenant + (DbContextFactory.cs:155-159) from ICurrentUserService, AmbientOrigin.FlattenRoles, the tenant context and the correlation context, and exposed through ApplicationDbContext.OutboxOriginAccessor/CurrentOutboxOrigin - (ApplicationDbContext.cs:158,164); read by OutboxMessage.FromDomainEvent to + (ApplicationDbContext.cs:171,177); read by OutboxMessage.FromDomainEvent to populate the row's four capture columns.
    238. Caveats / not-in-source: the struct itself does not restore anything, it only carries the values; the restore side (putting the captured user/tenant/correlation back onto a delivery scope) is the @@ -962,12 +962,12 @@

      IIntegrationEvent

    239. Why it's built this way: making integration events a subtype of domain events means one outbox mechanism serves both, and the routing decision is a single is IIntegrationEvent pattern match in the processor - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:540), + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:532), with no parallel capture pipeline to keep in step.
    240. Where it's used: implemented by integration events across modules and by BaseIntegrationEvent, which adds the SchemaVersion convention; routed by the OutboxProcessor to IMessageBus - (OutboxProcessor.cs:540); published by InProcessEventBus and + (OutboxProcessor.cs:532); published by InProcessEventBus and BrokerEventBus; consumed via IntegrationEventConsumer<TEvent>, whose type parameter is constrained to class, IIntegrationEvent (IntegrationEventConsumer.cs:43).
    241. @@ -999,13 +999,13 @@

      NoOpInboxStore

      (MessageBusSettings.IsInboxEnabled, MessageBusSettings.cs:141), so this store is the deliberate opt-out path for a host that cannot query the InboxMessages table yet, not a quiet default. Note that it is registered as a singleton while EfInboxStore is scoped - (DependencyInjection.Messaging.cs:108,112): a stateless no-op needs no per-request lifetime, an EF-backed + (DependencyInjection.Messaging.cs:109,117)
      : a stateless no-op needs no per-request lifetime, an EF-backed store that stages rows in the scope's unit of work does. The same else branch also registers - InboxDisabledWarningService (DependencyInjection.Messaging.cs:117), so + InboxDisabledWarningService (DependencyInjection.Messaging.cs:122), so choosing the Null Object is never silent (ADR-021).
    242. Where it's used: registered as IInboxStore inside AddBrokerMessaging on the else branch of settings.IsInboxEnabled, that is when MessageBus:EnableInbox=false is set - explicitly (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:106-118); + explicitly (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:107-123); consumed by IntegrationEventConsumer<TEvent>.
    243. [Rubric §10, Messaging & Integration Architecture] applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores.

      @@ -1028,7 +1028,7 @@

      OutboxSettings

    244. Concept introduced, options binding with a static SectionName. Note the convention that runs through every settings class in the framework: public static readonly string SectionName = "Outbox"; (OutboxSettings.cs:13) is the single source of truth for the section name, referenced at the bind - call instead of duplicating the literal (DependencyInjection.cs:144). The properties are + call instead of duplicating the literal (DependencyInjection.cs:150). The properties are init-only, so once materialized from configuration they are immutable for the process lifetime.

      [Rubric §6, CQRS & Event-Driven] assesses how reliably state changes turn into dispatched events. This is the knob set for the at-least-once outbox @@ -1040,7 +1040,7 @@

      OutboxSettings

      [Rubric §29, Resilience & Business Continuity] assesses behavior under replication and repeated failure. Three properties carry the weight. LeaseSeconds (:82) claims a batch for a replica so concurrent replicas never double-dispatch, and expires so a dead replica's rows become claimable - again (:75-81, applied at OutboxProcessor.cs:386). RetryBackoffBaseSeconds (:99) makes the + again (:75-81, applied at OutboxProcessor.cs:375). RetryBackoffBaseSeconds (:99) makes the retry cadence explicit: attempt n waits base * 2^(n-1), multiplied by a jitter factor in [0.8, 1.2] so rows that failed together do not retry in lockstep, then capped at LeaseSeconds (:84-89, implemented at OutboxProcessor.cs:675-681). The remark is worth reading as a design @@ -1056,36 +1056,36 @@

      OutboxSettings

      IEventBus are written, defaulting to the top-level connection strings so single-database behavior is preserved. It is a per-write target, not a global switch: the doc is explicit that the PROCESSOR still drains the outbox table of every relational physical source in use - (:53-56, and see OutboxProcessor.cs:136-142).

      + (:53-56, and see OutboxProcessor.cs:134-140).

    245. Walkthrough: one static field then eleven init properties, nine of them [Range]-validated.

      • SectionName (OutboxSettings.cs:13): static readonly "Outbox", the bind key.
      • BatchSize (:16-17): [Range(1, 1000)], default 50; messages per cycle, used both to size the - fetch (OutboxProcessor.cs:350) and to decide whether more eligible work remains - (OutboxProcessor.cs:263, :361).
      • + fetch (OutboxProcessor.cs:339) and to decide whether more eligible work remains + (OutboxProcessor.cs:252, :361).
      • MaxRetries (:20-21): [Range(1, 20)], default 5; attempts before a message is treated as - dead-lettered and excluded from the poll (OutboxProcessor.cs:293, :424, :669). The first + dead-lettered and excluded from the poll (OutboxProcessor.cs:282, :424, :669). The first failure is only re-scheduled when MaxRetries > 1, so 1 is honored as "the host asked for no - retries at all" (OutboxProcessor.cs:657).
      • + retries at all" (OutboxProcessor.cs:656).
      • PollingIntervalSeconds (:30-31): [Range(1, 3600)], default 2; the fallback interval.
      • ProcessingDelaySeconds (:39-40): [Range(0, 600)], default 5; the eligibility delay, applied - as a cutoff on the message timestamp (OutboxProcessor.cs:196, :275).
      • + as a cutoff on the message timestamp (OutboxProcessor.cs:187, :275).
      • DataSource (:48): default DataSource.SQLServer; must be a relational provider (SQL Server or SQLite), since the outbox is a table.
      • DatabaseName (:57): default DataSourceKey.DefaultName; the logical source name paired with DataSource.
      • RetentionDays (:64-65): [Range(0, 3650)], default 7; days a PROCESSED message is kept - before purge, with 0 disabling purging entirely (OutboxCleanupService.cs:77, cutoff at :94).
      • + before purge, with 0 disabling purging entirely (OutboxCleanupService.cs:72, cutoff at :94).
      • CleanupIntervalHours (:72-73): [Range(1, 168)], default 6; the purge sweep cadence, ignored - when RetentionDays is 0 (OutboxCleanupService.cs:70).
      • + when RetentionDays is 0 (OutboxCleanupService.cs:65).
      • LeaseSeconds (:81-82): [Range(10, 3600)], default 300; the batch claim window.
      • RetryBackoffBaseSeconds (:98-99): [Range(1, 3600)], default 10; the exponential-backoff base described above.
      • DeadLetterRetentionDays (:107-108): [Range(0, 3650)], default 0, which falls back to RetentionDays. Set it higher to keep exhausted payloads around for diagnosis and manual replay; the cleanup service resolves the fallback explicitly before computing its cutoff - (OutboxCleanupService.cs:155-157).
      • + (OutboxCleanupService.cs:150-152).
    246. Why it's built this way: the defaults encode the framework's out-of-the-box posture @@ -1098,9 +1098,9 @@

      OutboxSettings

      (ADR-070).

    247. Where it's used: bound with .ValidateDataAnnotations().ValidateOnStart() in AddInfrastructure - (DependencyInjection.cs:143-146). Consumed by OutboxProcessor - (OutboxProcessor.cs:59, :66) and OutboxCleanupService - (OutboxCleanupService.cs:48, :57) for batching, retry pacing and retention; by both event buses + (DependencyInjection.cs:149-152). Consumed by OutboxProcessor + (OutboxProcessor.cs:56, :66) and OutboxCleanupService + (OutboxCleanupService.cs:45, :57) for batching, retry pacing and retention; by both event buses to pick the write target when publishing an integration event (InProcessEventBus InProcessEventBus.cs:37, :78; BrokerEventBus BrokerEventBus.cs:35, :67); and by @@ -1203,7 +1203,7 @@

      OutboxMessage

      principal a synchronous dispatch would have seen instead of running anonymously; and the lease pair moves scale-out safety from a deployment convention (minReplicas: 1) into the data model. The EF configuration completes the picture - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:528-563): + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:533-568): table dbo.OutboxMessages (line 531), bounded columns for EventType (500, non-Unicode, line 533), LastError (4000, line 535) and OrderingKey (200, line 538), and three filtered indexes, each with its reason written above it: IX_OutboxMessages_Pending (line 545) whose included RetryCount @@ -1238,8 +1238,9 @@

      EfInboxStore

    248. Depends on: IDbContextFactory, IDataSourceResolver, IOptions<OutboxSettings> (to find the - publish-target source) and ILogger<EfInboxStore>, all via primary constructor - (EfInboxStore.cs:38-42); the InboxMessage entity; resolves an + publish-target source), ILogger<EfInboxStore> and a TimeProvider (the clock that stamps + ProcessedOn), all via primary constructor (EfInboxStore.cs:38-43); the + InboxMessage entity; resolves an ApplicationDbContext; EF Core's EntityEntry<T> and EntityState for the staging bookkeeping.
    249. Concept introduced, staging the dedup row into the handler's transaction. @@ -1257,51 +1258,52 @@

      EfInboxStore

      source is back to two transactions, and delivery is then at-least-once again, which is the contract handlers are written against anyway.
    250. Walkthrough
        -
      • _staged (EfInboxStore.cs:49) is a plain Dictionary<Guid, EntityEntry<InboxMessage>> of rows +
      • _staged (EfInboxStore.cs:50) is a plain Dictionary<Guid, EntityEntry<InboxMessage>> of rows opened but not yet closed out. The comment justifies the non-concurrent collection - (EfInboxStore.cs:44-48): the store is scoped per consumed message, so it holds one entry in + (EfInboxStore.cs:45-49): the store is scoped per consumed message, so it holds one entry in practice and is never touched from two threads.
      • -
      • AlreadyProcessedAsync (EfInboxStore.cs:52-58) resolves the context and issues a single +
      • AlreadyProcessedAsync (EfInboxStore.cs:53-59) resolves the context and issues a single AnyAsync for an InboxMessage with the given MessageId - (EfInboxStore.cs:55-57), which the unique index turns into an index seek.
      • -
      • TryBeginAsync (EfInboxStore.cs:61-68) short-circuits to false when the message was already - processed (lines 63-64), otherwise stages a row into _staged and returns true (lines 66-67).
      • -
      • Stage (EfInboxStore.cs:116-127) resolves the context and Adds a new - InboxMessage stamped DateTime.UtcNow (lines 120-125), with a scoped - VSTHRD103 suppression noting that EF's DbSet.Add is intentionally synchronous because it is an - in-memory operation (lines 119 and 126). Note it returns the EntityEntry, which is the handle the - rest of the class reads state from.
      • -
      • CompleteAsync (EfInboxStore.cs:71-89) removes the staged entry and inspects its state - (lines 73-78). Added still means no handler saved, so the row is persisted now (line 80); + (EfInboxStore.cs:56-58), which the unique index turns into an index seek.
      • +
      • TryBeginAsync (EfInboxStore.cs:62-69) short-circuits to false when the message was already + processed (lines 64-65), otherwise stages a row into _staged and returns true (lines 67-68).
      • +
      • Stage (EfInboxStore.cs:117-128) resolves the context and Adds a new + InboxMessage (lines 121-126) whose ProcessedOn comes from the injected clock, + timeProvider.GetUtcNow().UtcDateTime (line 125), rather than the ambient DateTime.UtcNow, so a + test can pin the stamp. A scoped VSTHRD103 suppression notes that EF's DbSet.Add is + intentionally synchronous because it is an in-memory operation (lines 120 and 127). Note it + returns the EntityEntry, which is the handle the rest of the class reads state from.
      • +
      • CompleteAsync (EfInboxStore.cs:72-90) removes the staged entry and inspects its state + (lines 74-79). Added still means no handler saved, so the row is persisted now (line 81); anything else means a handler's own SaveChangesAsync already committed it atomically with its - mutations, which is the whole point of staging, so there is nothing left to write (lines 75-77). + mutations, which is the whole point of staging, so there is nothing left to write (lines 76-78). When there is no staged entry at all (a caller that skipped TryBeginAsync, or a second CompleteAsync), it falls back to the stage-then-save path so the message is still recorded - (lines 86-88).
      • -
      • Abandon (EfInboxStore.cs:92-110) is the failure branch. No staged row means nothing to undo, - return true (lines 94-95). A staged entry whose state is no longer Added means a handler + (lines 87-89).
      • +
      • Abandon (EfInboxStore.cs:93-111) is the failure branch. No staged row means nothing to undo, + return true (lines 95-96). A staged entry whose state is no longer Added means a handler committed the row before a later handler failed: the store logs a Warning and returns false - (lines 97-103), and the comment says plainly that the redelivery will be skipped as a duplicate so + (lines 98-104), and the comment says plainly that the redelivery will be skipped as a duplicate so the handlers that had not run yet never will, which is the one case where this design loses work a pure after-the-fact inbox would have retried. Otherwise the entry is detached rather than left - Added (line 108), because the context is cached for the whole scope and a surviving Added row - would be re-attempted by any later save on that scope (lines 106-107).
      • -
      • MarkProcessedAsync (EfInboxStore.cs:113-114) is now a thin stage-and-save, kept because it is + Added (line 109), because the context is cached for the whole scope and a surviving Added row + would be re-attempted by any later save on that scope (lines 107-108).
      • +
      • MarkProcessedAsync (EfInboxStore.cs:114-115) is a thin stage-and-save, kept because it is the abstract member of the port.
      • -
      • SaveStagedAsync (EfInboxStore.cs:129-158) saves and then handles the race. Its - catch (DbUpdateException) (line 140) does three things in order. First it detaches the rejected - entry (line 146), for the same scope-caching reason, and the comment names the identical idiom in +
      • SaveStagedAsync (EfInboxStore.cs:130-159) saves and then handles the race. Its + catch (DbUpdateException) (line 141) does three things in order. First it detaches the rejected + entry (line 147), for the same scope-caching reason, and the comment names the identical idiom in DomainEventSaveChangesInterceptor - (lines 142-145). Second it re-queries through AlreadyProcessedAsync and rethrows when the row - is still absent (lines 153-154): only a concurrent duplicate delivery tripping the unique index is + (lines 143-146). Second it re-queries through AlreadyProcessedAsync and rethrows when the row + is still absent (lines 154-155): only a concurrent duplicate delivery tripping the unique index is safe to absorb, and the comment is explicit that re-querying beats sniffing provider-specific error codes because the check must hold for SQL Server and SQLite alike, and that swallowing any other - write failure would ack a message whose inbox row was never written (lines 148-152). Third, and - only then, it logs the absorbed duplicate at Debug (line 156, source-generated at lines 166-167).
      • -
      • ResolveContext (EfInboxStore.cs:160-164) routes to the configured outbox data source by + write failure would ack a message whose inbox row was never written (lines 149-153). Third, and + only then, it logs the absorbed duplicate at Debug (line 157, source-generated at lines 167-168).
      • +
      • ResolveContext (EfInboxStore.cs:161-165) routes to the configured outbox data source by resolving OutboxSettings.DataSource/DatabaseName through - IDataSourceResolver (line 162) and asking - the factory for that context (line 163), so the inbox lands in the same database as the outbox.
      • + IDataSourceResolver (line 163) and asking + the factory for that context (line 164), so the inbox lands in the same database as the outbox.
    251. Why it's built this way: dedup by MessageId (the IDomainEvent member @@ -1316,12 +1318,12 @@

      EfInboxStore

      of work.
    252. Where it's used: registered as the scoped IInboxStore whenever MessageBusSettings.IsInboxEnabled resolves true, which for a broker transport is the default - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:106-108); driven by + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:107-109); driven by IntegrationEventConsumer<TEvent> around handler invocation (IntegrationEventConsumer.cs:81,101,122); its rows are purged by - OutboxCleanupService (OutboxCleanupService.cs:174-189). Exercised + OutboxCleanupService (OutboxCleanupService.cs:169-184). Exercised directly by EfInboxStoreTests - (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Inbox/EfInboxStoreTests.cs:27).
    253. + (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Inbox/EfInboxStoreTests.cs:28).
    254. Caveats / not-in-source: the inbox key is the event's [EventName] identity when it declares one and its short type name otherwise, resolved by the caller, not by this store (EventNameResolver, called at IntegrationEventConsumer.cs:70). Whether a @@ -1331,7 +1333,7 @@

      EfInboxStore

      [Rubric §10, Messaging & Integration Architecture] applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores.

      OutboxAdministration

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Outbox.Administration · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:35 · Level 13 · class (public sealed partial)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Outbox.Administration · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:32 · Level 16 · class (public sealed partial)

      • What it is: the EF-backed operator surface over the outbox tables this host owns. It lists dead @@ -1340,11 +1342,9 @@

        OutboxAdministration

        OutboxCleanupService sweeps.
      • Depends on: IServiceScopeFactory, ILogger<OutboxAdministration>, IOptions<OutboxSettings>, - IEntityDataSourceRegistry, - IDataSourceResolver, - IOutboxSignal and an optional - IOptions<TenancySettings>, all via - primary constructor (OutboxAdministration.cs:35-42); implements + FrameworkTableTargets and + IOutboxSignal, all via primary constructor (OutboxAdministration.cs:32-37); + implements IOutboxAdministration and projects OutboxDeadLetter; resolves an IDbContextFactory and, for a tenant target, an @@ -1365,46 +1365,50 @@

        OutboxAdministration

        operator decides about a replay depends on reading it (IOutboxAdministration.cs:68-72), a [Rubric §30, Compliance, Privacy & Data Governance] choice.
      • Walkthrough
          -
        • Guards and paging. MaxPageSize is 500 (OutboxAdministration.cs:45), so an admin call cannot +
        • Guards and paging. MaxPageSize is 500 (OutboxAdministration.cs:40), so an admin call cannot ask for the whole table at once, and the two validation errors are preallocated Error.Validation - values (lines 47-51).
        • -
        • ListDeadLettersAsync (OutboxAdministration.cs:56-112) validates skip and take (lines 62-66), + values (lines 42-46).
        • +
        • ListDeadLettersAsync (OutboxAdministration.cs:51-107) validates skip and take (lines 57-61), resolves its targets and fails with a NotFound-shaped error when a named source is not owned by - this host (lines 68-70, 197-200), then queries each target for unprocessed rows whose RetryCount + this host (lines 63-65, 192-195), then queries each target for unprocessed rows whose RetryCount has reached MaxRetries, ordered by OccurredOn then Id, projected straight into - OutboxDeadLetter under AsNoTracking (lines 86-100). Two details are commented in place: the + OutboxDeadLetter under AsNoTracking (lines 81-95). Two details are commented in place: the source name is materialized outside the query because inside the projection it would be a method - call EF has to translate (lines 77-79), and each target returns at most skip + take rows because + call EF has to translate (lines 72-74), and each target returns at most skip + take rows because paging is applied across the merged result, so "skip 50" means the same thing whether the host - owns one database or four (lines 81-83, merged at lines 106-109).
        • -
        • ReplayDeadLettersAsync (OutboxAdministration.cs:115-171) is expressed as one set-based + owns one database or four (lines 76-78, merged at lines 101-104).
        • +
        • ReplayDeadLettersAsync (OutboxAdministration.cs:110-166) is expressed as one set-based ExecuteUpdateAsync per target rather than as loaded entities, because an operator replaying a backlog is replaying thousands of rows and none of the values written depend on the row's current - state (class doc, lines 21-25). The update resets RetryCount to zero, which is what returns the + state (class doc, lines 22-24). The update resets RetryCount to zero, which is what returns the row to the poll's predicate, and clears LockedUntil and LockToken so it is claimable on the very - next cycle instead of after LeaseSeconds (lines 146-151). LastError survives on purpose: the + next cycle instead of after LeaseSeconds (lines 141-146). LastError survives on purpose: the comment calls it the record of why this row needed replaying, and a replay that erased it would - destroy the only evidence (lines 142-145). An optional id filter narrows the scope (lines 137-140), - each non-empty target logs at Warning (lines 155-158, LogReplayed at 247-248), and when anything + destroy the only evidence (lines 137-140). An optional id filter narrows the scope (lines 132-135), + each non-empty target logs at Warning (lines 150-153, LogReplayed at 223-224), and when anything was replayed it calls IOutboxSignal.Signal() rather than leaving the work to a - polling interval deployed environments set as high as 300 seconds (lines 163-168).
        • -
        • CountPendingAsync (OutboxAdministration.cs:174-195) sums LongCountAsync over unprocessed rows + polling interval deployed environments set as high as 300 seconds (lines 158-163).
        • +
        • CountPendingAsync (OutboxAdministration.cs:169-190) sums LongCountAsync over unprocessed rows with RetryCount < MaxRetries across every selected target. Its interface doc draws the line against the gauge (IOutboxAdministration.cs:56-61): this counts the tables at the moment of the call and includes rows currently under a claim lease, where outbox.pending.depth reports what the processor last observed.
        • -
        • Target selection and scoping. SelectTargets (OutboxAdministration.cs:207-219) now delegates - the same set the two background services use to a single shared helper, - TenantDataSourceTargets.ExpandRelational - (every relational physical source in use, minus Cosmos, plus the configured publish target, expanded - per tenant), rather than assembling the source list inline, and optionally filters to one name - case-insensitively (lines 216-218). It is recomputed per call for the same reason the processor - recomputes it per cycle: module assemblies can register entities after startup (doc, lines 202-205). - VisitAsync<T> (lines 226-237) runs the work for one target in its own DI scope, created through - the same shared scopeFactory.CreateTenantScope(target) extension +
        • Target selection and scoping. SelectTargets (OutboxAdministration.cs:202-203) is a one-line + call to the injected + FrameworkTableTargets.Named, passing the + outbox's own DataSource/DatabaseName and the optional operator-supplied name. That shared type + answers the target question once for every framework-table sweeper: every relational physical + source in use (Cosmos skipped), plus the configured publish target when its engine is relational, + expanded per tenant that keeps its own copy + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/FrameworkTableTargets.cs:54-64), + then optionally narrowed to one target matched case-insensitively against its display name + (FrameworkTableTargets.cs:75-82). It is recomputed per call for the same reason the processor + recomputes it per cycle: module assemblies can register entities after startup (doc, lines 197-201). + VisitAsync<T> (lines 210-221) runs the work for one target in its own DI scope, created through + the shared scopeFactory.CreateTenantScope(target) extension (TenantDataSourceTargets) that sets the tenant before the context is asked for, because the tenant is what routes the scoped factory to - that tenant's database and is also what the query filter reads (line 233).
        • + that tenant's database and is also what the query filter reads (line 217).
      • Why it's built this way: reusing the processor's exact target expansion means an operator screen @@ -1416,15 +1420,16 @@

        OutboxAdministration

      • Where it's used: registered scoped as IOutboxAdministration by AddInfrastructure - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:203-206), with the + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:227-230), with the comment explaining the lifetime: scoped, because it creates one child scope per data source it visits - and holds no state of its own. The framework ships no endpoint for it; a host exposes it from an admin + and holds no state of its own. Its FrameworkTableTargets dependency is registered with + TryAddSingleton (DependencyInjection.cs:96). The framework ships no endpoint for it; a host exposes it from an admin endpoint, a support command or a scheduled job (IOutboxAdministration.cs:10-14).

      [Rubric §10, Messaging & Integration Architecture] applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores.

      OutboxCleanupService

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Outbox.Administration · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxCleanupService.cs:45 · Level 13 · class (public sealed partial, PeriodicBackgroundService)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Outbox.Administration · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxCleanupService.cs:42 · Level 16 · class (public sealed partial, PeriodicBackgroundService)

      • What it is: the periodic sweeper that purges spent outbox rows (both processed rows and @@ -1434,19 +1439,17 @@

        OutboxCleanupService

      • Depends on: IServiceScopeFactory, ILogger<OutboxCleanupService>, IOptions<OutboxSettings>, IOptions<MessageBusSettings>, - IEntityDataSourceRegistry, - IDataSourceResolver, a required + FrameworkTableTargets (which decides the + databases, including per-tenant copies, that hold the tables it sweeps) and a required TimeProvider (forwarded straight to the PeriodicBackgroundService base - it now extends) and an optional - IOptions<TenancySettings> - (OutboxCleanupService.cs:45-54); resolves an + it extends) (OutboxCleanupService.cs:42-49); resolves an IDbContextFactory and, for a tenant target, an ITenantContext per sweep, both through the shared scopeFactory.CreateTenantScope(target) helper - (TenantDataSourceTargets); expands its - work list through the same type into - TenantDataSourceTarget values; operates on + (TenantDataSourceTargets); its work list + is a list of TenantDataSourceTarget + values; operates on the OutboxMessage and InboxMessage entities.
      • Concept introduced, retention as a privacy and storage control (plus a clock injection point). [Rubric §30, Compliance, Privacy & Data Governance] assesses bounded retention of data that may @@ -1455,54 +1458,60 @@

        OutboxCleanupService

        whether time-driven code can be tested. The OutboxProcessor only ever sets ProcessedOn, and a message that exhausts MaxRetries keeps ProcessedOn null forever, so without this sweep the outbox, which stores serialized event payloads that may contain personal data, grows - without bound and dead rows linger in the pending index every poll re-scans (class doc, lines 16-24, - citing ADR-003 and ADR-005). The TimeProvider is now a required constructor parameter rather than an + without bound and dead rows linger in the pending index every poll re-scans (class doc, lines 15-30, + citing ADR-003 and ADR-005 at line 21). The TimeProvider is now a required constructor parameter rather than an optional one defaulting to TimeProvider.System, because the base PeriodicBackgroundService owns the clock all its waits go through; a private _timeProvider field re-reads the same value under this type's own name only because capturing the base constructor's own parameter into this type's state - would be a compiler error, CS9107 (comment, OutboxCleanupService.cs:59-60). A test can still drive + would be a compiler error, CS9107 (comment, OutboxCleanupService.cs:54-55). A test can still drive the hour-scale sweep loop deterministically instead of waiting real hours, now through the base's injected clock.
      • Walkthrough
        • The fixed-interval loop itself (delay, cycle, retry-on-failure, log-and-continue) moved to the shared PeriodicBackgroundService base; this type now supplies only the four hooks the base calls. Interval returns - CleanupIntervalHours hours (OutboxCleanupService.cs:64, default 6, OutboxSettings.cs:73) and - StartupDelay reuses it verbatim (lines 66-70), so the first sweep still waits one full interval + CleanupIntervalHours hours (OutboxCleanupService.cs:59, default 6, OutboxSettings.cs:73) and + StartupDelay reuses it verbatim (lines 61-65), so the first sweep still waits one full interval before running, which keeps cleanup off the critical path of startup or migration work. IsEnabled - (lines 72-85) is the documented off switch: it returns false, and logs, when RetentionDays <= 0 - (line 77). ExecuteCycleAsync delegates straight to PurgeAsync (line 88), and LogCycleFailure - overrides the base's generic message with this service's own event id (line 91, LogCleanupError - at 217-218).
        • -
        • PurgeAsync (OutboxCleanupService.cs:93-135) computes the cutoff from - _timeProvider.GetUtcNow().UtcDateTime minus RetentionDays (line 95, default 7, - OutboxSettings.cs:65), then walks GetRelationalTargets() (line 97). For each target it opens a - scope through the shared scopeFactory.CreateTenantScope(target) helper (line 103), which sets the + (lines 67-80) is the documented off switch: it returns false, and logs, when RetentionDays <= 0 + (line 72). ExecuteCycleAsync delegates straight to PurgeAsync (line 83), and LogCycleFailure + overrides the base's generic message with this service's own event id (line 86, LogCleanupError + at 207-208).
        • +
        • PurgeAsync (OutboxCleanupService.cs:88-130) computes the cutoff from + _timeProvider.GetUtcNow().UtcDateTime minus RetentionDays (line 90, default 7, + OutboxSettings.cs:65), then walks GetRelationalTargets() (line 92). For each target it opens a + scope through the shared scopeFactory.CreateTenantScope(target) helper (line 98), which sets the tenant on the scope before the context is asked for, because the tenant is what routes the scoped factory to that tenant's database. It then deletes processed rows older than the cutoff with - ExecuteDeleteAsync, a set-based SQL DELETE with no entity materialization (lines 108-111), - logging at Information when anything went (lines 113-116).
        • -
        • The dead-letter sweep (SweepDeadLettersAsync, OutboxCleanupService.cs:150-172) is the - second, separate pass, and its doc is worth reading in full (lines 137-149): dead-lettered rows keep + ExecuteDeleteAsync, a set-based SQL DELETE with no entity materialization (lines 103-106), + logging at Information when anything went (lines 108-111).
        • +
        • The dead-letter sweep (SweepDeadLettersAsync, OutboxCleanupService.cs:145-167) is the + second, separate pass, and its doc is worth reading in full (lines 132-144): dead-lettered rows keep ProcessedOn null forever, so the processor's poll excludes them (RetryCount < MaxRetries) but the processed sweep never reaches them either, and they accumulate inside the pending index. They are purged on their own window, DeadLetterRetentionDays falling back to RetentionDays when it is 0 - (lines 155-157, and 0 is the default, OutboxSettings.cs:108), keyed on OccurredOn since they - have no ProcessedOn (lines 161-164). This permanently abandons an undelivered event, which is why - the deletion logs at Warning (line 170, LogDeadLetterPurged at 211-212) while the processed - purge logs at Information (LogPurged at 208-209), and why the doc points at + (lines 150-152, and 0 is the default, OutboxSettings.cs:108), keyed on OccurredOn since they + have no ProcessedOn (lines 156-159). This permanently abandons an undelivered event, which is why + the deletion logs at Warning (line 165, LogDeadLetterPurged at 201-202) while the processed + purge logs at Information (LogPurged at 198-199), and why the doc points at OutboxAdministration as the thing to use before the window closes.
        • -
        • Inbox rows are purged only when the inbox is enabled (lines 120-123, the flag captured once at - construction from MessageBusSettings.IsInboxEnabled, line 57), delegating to PurgeInboxAsync - (lines 174-189), which deletes InboxMessage rows with ProcessedOn < cutoff.
        • +
        • Inbox rows are purged only when the inbox is enabled (lines 115-118, the flag captured once at + construction from MessageBusSettings.IsInboxEnabled, line 52), delegating to PurgeInboxAsync + (lines 169-184), which deletes InboxMessage rows with ProcessedOn < cutoff.
        • A single unreachable database does not stop the others: the per-target catch logs and moves on - (lines 129-133), while a real cancellation is rethrown (lines 125-128). GetRelationalTargets - (lines 197-203) now delegates target expansion to - TenantDataSourceTargets.ExpandRelational - (the same relational sources the processor drains against the shared database, plus one extra per - tenant that keeps its own copy), which is the only reason a per-tenant database's outbox and inbox - tables ever get swept (ADR-073).
        • + (lines 124-128), while a real cancellation is rethrown (lines 120-123). GetRelationalTargets + (lines 192-193) is a one-line call to the injected + FrameworkTableTargets.Relational with the + outbox's own DataSource/DatabaseName: every relational source backing a registered entity plus + the configured publish target, against the shared database, plus one extra unit per tenant that + keeps its own copy + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/FrameworkTableTargets.cs:54-64). + That per-tenant expansion is the only reason a per-tenant database's outbox and inbox tables ever + get swept (ADR-073), and because + the processor, both operator surfaces and the other cleanup services ask the same type, the sweep + cannot visit a different set of databases than the one being drained + (FrameworkTableTargets.cs:16-18).
      • Why it's built this way: bounded retention keeps both storage cost and PII exposure in check; @@ -1512,11 +1521,11 @@

        OutboxCleanupService

        this same sweep, gated on the inbox flag, rather than adding a second housekeeping service.
      • Where it's used: registered as a hosted service alongside the OutboxProcessor, inside the same IsOutboxEnabled gate - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:207-211), so a host with + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:217-221), so a host with the outbox disabled runs neither and gets OutboxDisabledNoticeService instead.
      • Caveats / not-in-source: the inbox purge uses the outbox RetentionDays cutoff - (OutboxCleanupService.cs:122), not a separate inbox window, so shortening outbox retention shortens + (OutboxCleanupService.cs:117), not a separate inbox window, so shortening outbox retention shortens the dedup memory with it.

      [Rubric §10, Messaging & Integration Architecture] applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores.

      @@ -1550,7 +1559,7 @@

      IOutboxSignal

      injection point, and it keeps the SemaphoreSlim detail (including its one-permit cap) out of every call site.
    255. Where it's used: registered as a singleton by AddInfrastructure - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:195). Signal() is + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:205). Signal() is called by DomainEventSaveChangesInterceptor on all three of its paths @@ -1558,9 +1567,9 @@

      IOutboxSignal

      by BrokerEventBus after writing its outbox batch (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Messaging/BrokerEventBus.cs:94), and by OutboxAdministration after a replay - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:167). + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:162)
      . WaitAsync is awaited by the OutboxProcessor loop - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:157), + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:150), with the duration computed from OutboxCycleResult.
    256. [Rubric §10, Messaging & Integration Architecture] applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores.

      @@ -1592,10 +1601,10 @@

      OutboxCycleResult

      the Infrastructure layer where the only two participants live.
    257. Where it's used: returned by OutboxProcessor.ProcessPendingMessagesAsync after aggregating the per-target results - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:174), - produced per source by ProcessSourceAsync (OutboxProcessor.cs:220,229,261-265), and consumed by + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:167), + produced per source by ProcessSourceAsync (OutboxProcessor.cs:211,220,250-254), and consumed by ExecuteAsync to either continue immediately (OutboxProcessor.cs:143-147) or wait for the - duration ComputeWaitTime derives from it (OutboxProcessor.cs:152-157).
    258. + duration ComputeWaitTime derives from it (OutboxProcessor.cs:145-150).

      [Rubric §10, Messaging & Integration Architecture] applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores.

      OutboxMetrics

      @@ -1661,16 +1670,16 @@

      OutboxMetrics

      lock-free while the processor writes them from its own loop. Making the depth a gauge fed by the cycle rather than an independent query means the steady state pays no extra database round-trip: see CountPendingAsync, which derives the depth from the fetch itself unless the batch came back - saturated (OutboxProcessor.cs:276-297). + saturated (OutboxProcessor.cs:265-286).
    259. Where it's used: DeadLetterCounter on both dead-letter paths (OutboxProcessor.cs:667-670 for an unresolvable type, :712-715 for exhausted retries); ProcessedCounter and - DispatchLagHistogram on the success path (OutboxProcessor.cs:562,567-569); SetOldestPendingAge - per source right after its fetch (OutboxProcessor.cs:204-206); and SetPendingDepth once per - cycle after every target has been drained (OutboxProcessor.cs:172).
    260. + DispatchLagHistogram on the success path (OutboxProcessor.cs:557,562-564); SetOldestPendingAge + per source right after its fetch (OutboxProcessor.cs:195-197); and SetPendingDepth once per + cycle after every target has been drained (OutboxProcessor.cs:165).
    261. Caveats / not-in-source: the circuit-open signal the processor emits alongside these lives on a different meter, BrokerMetrics.CircuitOpenCounter, not on - OutboxMetrics (OutboxProcessor.cs:606-608).
    262. + OutboxMetrics (OutboxProcessor.cs:601-603).

      [Rubric §10, Messaging & Integration Architecture] applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores.

      EventNameResolver

      @@ -1771,7 +1780,7 @@

      IDomainEventDispatcher

      calls DispatchAsync for the immediate in-process reactions (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/DomainEventSaveChangesInterceptor.cs:337); the background OutboxProcessor routes non-integration events through it - (OutboxProcessor.cs:554), as do InProcessMessageBus + (OutboxProcessor.cs:549), as do InProcessMessageBus (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Messaging/InProcessMessageBus.cs:25,32) and InProcessEventBus (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Messaging/InProcessEventBus.cs:83,100). @@ -1836,7 +1845,7 @@

      OutboxSignal

      the "at-least-once is fine, duplicates are absorbed" instinct that runs through this whole group in one place rather than two.
    263. Where it's used: registered as the singleton IOutboxSignal by AddInfrastructure - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:195). Its callers are + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:205). Its callers are listed under IOutboxSignal. Note the registration is unconditional, above the outbox gate, so the producers can signal without checking whether a processor exists.
    264. @@ -2062,8 +2071,8 @@

      SafeDomainEventHandler<TDomainEve attempts (default 5, OutboxSettings, MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxSettings.cs:21; the retry-count check that stops - fetching an exhausted row is OutboxProcessor.cs:291 and the dead-letter branch is - OutboxProcessor.cs:615). [Rubric §13, Observability & Operability]: the one job the base class + fetching an exhausted row is OutboxProcessor.cs:280 and the dead-letter branch is + OutboxProcessor.cs:610). [Rubric §13, Observability & Operability]: the one job the base class keeps is the error line naming the concrete handler and the event type, so an operator can tell which handler failed for which event without every subclass hand-rolling that context.
    265. Concept introduced, batch redelivery. The consequence subclasses have to design for is in the @@ -2212,7 +2221,7 @@

      ScopedIntegrationEve ADC's UserRegisteredHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Users/IntegrationEventHandlers/UserRegisteredHandler.cs:57), SpeakerLinkedToUserHandler - (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Speakers/IntegrationEventHandlers/SpeakerLinkedToUserHandler.cs:30), + (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Speakers/IntegrationEventHandlers/SpeakerLinkedToUserHandler.cs:35), SpeakerUnlinkedFromUserHandler (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Speakers/IntegrationEventHandlers/SpeakerUnlinkedFromUserHandler.cs:30), the Engagement points handlers (AttendeeCheckedInPointsHandler, @@ -2267,7 +2276,7 @@

      OutboxFinalizer

      ADR-003's in-process dispatch stays cheap; the durability net is the background OutboxProcessor, which deliberately does not use this helper (it stamps ProcessedOn on tracked rows and issues one - ordinary SaveChangesAsync per source, OutboxProcessor.cs:257, because it must persist + ordinary SaveChangesAsync per source, OutboxProcessor.cs:246, because it must persist RetryCount, LastError and lease changes in the same save).
    266. Where it's used: called by DomainEventSaveChangesInterceptor @@ -2278,7 +2287,7 @@

      OutboxFinalizer

      [Rubric §10, Messaging & Integration Architecture] applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores.

      OutboxProcessor

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Outbox.Processing · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:56 · Level 13 · class (public sealed partial, BackgroundService)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Outbox.Processing · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:53 · Level 16 · class (public sealed partial, BackgroundService)

      • What it is: the background service that drains every outbox table the host owns, claims rows under @@ -2288,20 +2297,20 @@

        OutboxProcessor

      • Depends on: IServiceScopeFactory, ILogger<OutboxProcessor>, IOptions<OutboxSettings>, IOutboxSignal, - IEntityDataSourceRegistry, - IDataSourceResolver, a required - TimeProvider (resolved from the TryAddSingleton(TimeProvider.System) registration in - AddInfrastructure, DependencyInjection.cs:313, rather than defaulted in the constructor) and an - optional IOptions<TenancySettings> - (OutboxProcessor.cs:56-64); the shared PollingLoop + FrameworkTableTargets (registered once as a + singleton by AddInfrastructure, DependencyInjection.cs:96) and a required TimeProvider (resolved + from the TryAddSingleton(TimeProvider.System) registration, DependencyInjection.cs:323, rather than + defaulted in the constructor) (OutboxProcessor.cs:53-59); the shared PollingLoop (MMCA.Common.Infrastructure.Persistence.Polling), the loop, smart-wait and per-source drain engine - this processor and InternalCommandProcessor both run on; per scope - IDbContextFactory, - IDomainEventDispatcher, IMessageBus and, for a tenant - target, ITenantContext (lines 275-283); the - OutboxMessage entity, OutboxMetrics, - OutboxCycleResult, - TenantDataSourceTargets, + this processor and InternalCommandProcessor both run on; per cycle scope + IDbContextFactory (line 183), and per row scope + IMessageBus or IDomainEventDispatcher (lines 534, 548), + both scopes opened through CreateTenantScope + (TenantDataSourceTargets, + TenantDataSourceTargets.cs:92); the OutboxMessage entity, + OutboxMetrics, OutboxCycleResult, + TenantDataSourceTarget, + ColumnWidth, BrokerResilienceDefaults, BrokerMetrics and AmbientOrigin; externally Polly @@ -2315,192 +2324,204 @@

        OutboxProcessor

        claim lease expires, not immediately on restart, because the claim is persisted before dispatch and the poll skips leased rows. Take the rest a layer at a time.
      • Walkthrough
          -
        • The loop, extracted. ExecuteAsync (OutboxProcessor.cs:108-122) no longer contains the loop - body: it is a one-expression call into PollingLoop.RunAsync - (MMCA.Common.Infrastructure.Persistence.Polling.PollingLoop.cs:40-91), passing _timeProvider, +
        • The loop, extracted. ExecuteAsync (OutboxProcessor.cs:106-120) contains no loop body: it is + a one-expression call into PollingLoop.RunAsync + (Persistence/Polling/PollingLoop.cs:40-91), passing _timeProvider, whether this host owns any target (GetOutboxTargets().Count > 0), LogOutboxDisabled for the no-target case, a cycle delegate that calls ProcessPendingMessagesAsync and projects its result to (HasMoreWork, EarliestUpcoming), LogProcessingError, the configured delay and interval seconds, and outboxSignal.WaitAsync itself as the wait function (MMCA.Common.Infrastructure.Persistence.Polling.PollingLoop, - Persistence/Polling/PollingLoop.cs:40-91). PollingLoop.RunAsync runs the mechanics this section - used to describe inline: it waits StartupDelay (a shared 5-second constant, PollingLoop.cs:18, - matching the internal command processor's own delay) so the application finishes - initializing, returns immediately when hasTargets() is false, and otherwise loops until cancelled, - treating a cancellation as a clean stop and any other cycle exception as a reported, non-fatal error - (PollingLoop.cs:51-74). A cycle with more work re-polls immediately; otherwise it awaits the - injected waitForSignal for whichever comes first of a signal, the smart wait, or the fallback - interval (PollingLoop.cs:76-90). The remark on ExecuteAsync says this in one line - (OutboxProcessor.cs:107): the loop, startup delay and smart wait are all the shared PollingLoop - now.
        • -
        • The smart wait. OutboxProcessor.ComputeWaitTime (OutboxProcessor.cs:129-134) is a thin + Persistence/Polling/PollingLoop.cs:40-91). PollingLoop.RunAsync runs the mechanics: it waits + StartupDelay (a shared 5-second constant, PollingLoop.cs:18, matching the internal command + processor's own delay) so the application finishes initializing, returns immediately when + hasTargets() is false, and otherwise loops until cancelled, treating a cancellation as a clean stop + and any other cycle exception as a reported, non-fatal error (PollingLoop.cs:51-74). A cycle with + more work re-polls immediately; otherwise it awaits the injected waitForSignal for whichever comes + first of a signal, the smart wait, or the fallback interval (PollingLoop.cs:76-90). The remark + on ExecuteAsync says this in one line (OutboxProcessor.cs:105): the loop, startup delay and smart + wait are the shared PollingLoop.
        • +
        • The smart wait. OutboxProcessor.ComputeWaitTime (OutboxProcessor.cs:127-132) is a thin forward to PollingLoop.ComputeWaitTime (PollingLoop.cs:104-122), kept as a named static method - only so existing unit tests keep calling it as before. It returns the full polling interval when - nothing is upcoming; otherwise it waits until the earliest upcoming row becomes ready, its - OccurredOn plus ProcessingDelaySeconds (delay default 5, OutboxSettings.cs:40), floored at - MinimumWait of 1 second so an overdue row cannot hot-loop the processor (PollingLoop.cs:21) and - capped at the polling interval. Its doc keeps the same subtle rule (PollingLoop.cs:96-97): - failed-but-already-ready rows never shorten the wait, which throttles a permanently failing message - instead of letting it drive the loop. This is why a deployed host can set a long poll interval - without adding latency: real messages wake it by signal or smart wait, and the slow fallback only - cuts idle database chatter and telemetry cost.
        • -
        • Which databases. GetOutboxTargets (OutboxProcessor.cs:143-149) no longer computes the owned - sources itself; it forwards straight to - TenantDataSourceTargets.ExpandRelational, - passing entityDataSourceRegistry, dataSourceResolver, _settings.DataSource, - _settings.DatabaseName and tenancyOptions?.Value. The method's own doc still states the same - contract this section previously split across two methods (OutboxProcessor.cs:136-142): every - relational source this host owns (every source backing a registered entity plus the configured - publish target; Cosmos has no outbox table) against the shared database, plus one extra unit per - tenant that keeps its own copy of a source, because a tenant database has its own OutboxMessages - table that nothing else would drain + so existing unit tests keep calling it. It returns the full polling interval when nothing is + upcoming; otherwise it waits until the earliest upcoming row becomes ready, its OccurredOn plus + ProcessingDelaySeconds (delay default 5, OutboxSettings.cs:40), floored at MinimumWait of 1 + second so an overdue row cannot hot-loop the processor (PollingLoop.cs:21) and capped at the + polling interval. Its doc keeps the subtle rule (PollingLoop.cs:96-97): failed-but-already-ready + rows never shorten the wait, which throttles a permanently failing message instead of letting it + drive the loop. This is why a deployed host can set a long poll interval without adding latency: + real messages wake it by signal or smart wait, and the slow fallback only cuts idle database chatter + and telemetry cost.
        • +
        • Which databases. GetOutboxTargets (OutboxProcessor.cs:141-142) asks the injected + FrameworkTableTargets one question, + tableTargets.Relational(_settings.DataSource, _settings.DatabaseName), the overload that adds the + caller's configured home source to every relational source in use and then expands per tenant + (FrameworkTableTargets.cs:54-64). That type exists so the outbox processor, the internal-command + processor, both cleanup services, both operator surfaces and the audit-trail sweep all share one + answer rather than each re-deriving it from the registry, the resolver and the tenancy settings + (FrameworkTableTargets.cs:16-18). The method's own doc states the contract + (OutboxProcessor.cs:134-140): every relational source this host owns (every source backing a + registered entity plus the configured publish target; Cosmos has no outbox table) against the shared + database, plus one extra unit per tenant that keeps its own copy of a source, because a tenant + database has its own OutboxMessages table that nothing else would drain (ADR-006; - ADR-073). A host therefore still - only touches its own databases, never racing another service for its rows; the source-enumeration - step this used to name as GetOutboxSources now lives inside ExpandRelational itself.
        • -
        • Aggregating a cycle. ProcessPendingMessagesAsync (OutboxProcessor.cs:157-175) also delegates, - to PollingLoop.DrainAllAsync (PollingLoop.cs:135-171), passing GetOutboxTargets(), a delegate - that calls ProcessSourceAsync per target and projects its tuple to + ADR-073). A host therefore only + touches its own databases, never racing another service for its rows.
        • +
        • Aggregating a cycle. ProcessPendingMessagesAsync (OutboxProcessor.cs:150-168) delegates to + PollingLoop.DrainAllAsync (PollingLoop.cs:135-171), passing GetOutboxTargets(), a delegate that + calls ProcessSourceAsync per target and projects its tuple to (HasMoreWork, EarliestUpcoming, PendingDepth), and LogSourceProcessingError. DrainAllAsync ORs the HasMoreWork flags, keeps the earliest upcoming timestamp across all targets, and sums the observed backlog; one unreachable database must not starve the others, so a per-target failure is reported and skipped while a real cancellation propagates (PollingLoop.cs:145-167). Back in ProcessPendingMessagesAsync, the summed depth is published through - OutboxMetrics.SetPendingDepth (OutboxProcessor.cs:172), so a target that threw + OutboxMetrics.SetPendingDepth (OutboxProcessor.cs:165), so a target that threw contributes zero and an outage reads as a drop rather than a stale plateau (comment, - OutboxProcessor.cs:170-171).
        • -
        • Draining one target. ProcessSourceAsync (OutboxProcessor.cs:181-266) now opens its scope - through scopeFactory.CreateTenantScope(target) (OutboxProcessor.cs:188) in one call, rather than - calling CreateScope() and then separately resolving ITenantContext.SetTenant when the target had - a tenant; the comment on the call site (line 187) notes the tenant is set before the context is asked - for, which is what CreateTenantScope now guarantees internally. It gets the context for that source - and resolves the dispatcher and message bus. It fetches a candidate - batch (line 288), derives the backlog depth (line 289) and publishes the oldest-pending age - from the batch's own first row (lines 294-296). Then it splits the ordered batch: the eligible - prefix is everything with OccurredOn before the ProcessingDelaySeconds cutoff (lines 286, - 300-304), and the first row past it becomes earliestPending (line 306). Nothing eligible means an - early return carrying only the wait information (lines 308-310). Otherwise it claims the prefix - (line 313), returns early if another replica claimed all of it between fetch and claim (lines - 316-319), dispatches (lines 327-329), and saves with a plain DbContext.SaveChangesAsync (line - 347). The comment above that save is worth noting (lines 343-346): no user id is passed, so the - audit interceptor stamps its system sentinel, and although the EF interceptors still run there is - nothing for them to capture because OutboxMessage is not an aggregate root. It returns a - (OutboxCycleResult, long PendingDepth) tuple (lines 351-355) so the caller can sum the depth.
        • -
        • Fetching. FetchCandidatesAsync (OutboxProcessor.cs:335-353) selects rows that are - unprocessed, under MaxRetries, and not under another replica's unexpired lease (lines 421-423), - ordered by OccurredOn then Id and capped at BatchSize (lines 424-426, default 50, + OutboxProcessor.cs:163-164).
        • +
        • Draining one target. ProcessSourceAsync (OutboxProcessor.cs:174-255) opens the cycle scope + through scopeFactory.CreateTenantScope(target) (line 181; the comment at line 180 notes the tenant + is set before the context is asked for) and gets the context for that source (lines 183-184). It + resolves no dispatcher or message bus: delivery happens on a separate scope per row (see below). It + fetches a candidate batch (line 189), derives the backlog depth (line 190) and publishes the + oldest-pending age from the batch's own first row (lines 195-197). Then it splits the ordered batch: + the eligible prefix is everything with OccurredOn before the ProcessingDelaySeconds cutoff (lines + 187, 201-205), and the first row past it becomes earliestPending (line 207). Nothing eligible means + an early return carrying only the wait information (lines 209-212). Otherwise it claims the + prefix (line 214), returns early if another replica claimed all of it between fetch and claim (lines + 217-221), dispatches with the target itself (line 228), and saves with a plain + DbContext.SaveChangesAsync (line 246). The comment above that save is worth noting (lines 242-245): + no user id is passed, so the audit interceptor stamps its system sentinel, and although the EF + interceptors still run there is nothing for them to capture because OutboxMessage is not an + aggregate root. It returns a (OutboxCycleResult, long PendingDepth) tuple (lines 250-254) so the + caller can sum the depth.
        • +
        • Fetching. FetchCandidatesAsync (OutboxProcessor.cs:324-342) selects rows that are + unprocessed, under MaxRetries, and not under another replica's unexpired lease (lines 334-336), + ordered by OccurredOn then Id and capped at BatchSize (lines 337-339, default 50, OutboxSettings.cs:17). There is deliberately no OccurredOn cutoff in SQL (doc, lines - 402-410): pending rows are fetched too so the caller can smart-wait, and ordering by OccurredOn + 315-323): pending rows are fetched too so the caller can smart-wait, and ordering by OccurredOn guarantees eligible rows sort before pending ones, which is what stops a full batch from starving - eligible work. The query runs inside an explicit OutboxPoll activity (lines 417-418; the name - constant PollActivityName is at line 73) that the Aspire + eligible work. The query runs inside an explicit OutboxPoll activity (line 330; the name constant + PollActivityName is at line 70) that the Aspire OutboxPollFilterProcessor suppresses from telemetry export along with its SqlClient child span; the string is deliberately duplicated - there because Aspire has no project reference back to Infrastructure (comment, lines 67-72).
        • -
        • Backlog depth almost for free. CountPendingAsync (OutboxProcessor.cs:276-297) returns the + there because Aspire has no project reference back to Infrastructure (comment, lines 64-69).
        • +
        • Backlog depth almost for free. CountPendingAsync (OutboxProcessor.cs:265-286) returns the fetched count directly whenever the batch came back short, because a short batch is the whole - backlog (lines 359-362). Only a saturated batch, exactly the state an operator alerts on, pays for a + backlog (lines 272-275). Only a saturated batch, exactly the state an operator alerts on, pays for a LongCountAsync, and that query runs inside its own OutboxPoll activity so it is suppressed like - the poll itself (lines 364-372). The predicate mirrors the fetch (lines 368-370), so the gauge counts + the poll itself (lines 277-278). The predicate mirrors the fetch (lines 281-283), so the gauge counts the rows this processor considers workable.
        • -
        • Claiming: how scale-out is made safe. ClaimEligibleAsync (OutboxProcessor.cs:378-422) mints - a lockToken and a leaseUntil of now plus LeaseSeconds (lines 461-462, default 300, - OutboxSettings.cs:82), narrows the prefix (line 463), then issues one conditional - ExecuteUpdateAsync setting LockedUntil and LockToken (lines 477-481). A claim of zero rows - means another replica took the whole prefix (lines 483-484); a full claim returns the candidates - as-is (lines 486-487); a partial claim re-queries which ids carry this replica's token and - processes only those (lines 490-497). The doc states the property this buys (lines 431-437): two +
        • Claiming: how scale-out is made safe. ClaimEligibleAsync (OutboxProcessor.cs:367-411) mints + a lockToken and a leaseUntil of now plus LeaseSeconds (lines 374-375, default 300, + OutboxSettings.cs:82), narrows the prefix (line 376), then issues one conditional + ExecuteUpdateAsync setting LockedUntil and LockToken (lines 390-394). A claim of zero rows + means another replica took the whole prefix (lines 396-397); a full claim returns the candidates + as-is (lines 399-400); a partial claim re-queries which ids carry this replica's token and + processes only those (lines 402-410). The doc states the property this buys (lines 345-350): two replicas can never dispatch the same message, and a replica that dies mid-batch releases its rows implicitly when the lease expires. That is scale-out safety by construction rather than by a minReplicas: 1 deployment convention.
        • Ordered delivery, enforced inside the claim. This is the piece that is easy to get wrong, and - the doc explains why it lives here rather than after the fetch (lines 438-446): enforcing it in the + the doc explains why it lives here rather than after the fetch (lines 352-358): enforcing it in the claim is what makes it survive batching and scale-out. Three pieces cooperate. - SelectOrderedCandidates (OutboxProcessor.cs:431-447) narrows the eligible prefix to every + SelectOrderedCandidates (OutboxProcessor.cs:420-436) narrows the eligible prefix to every unkeyed row plus the first row of each ordering key, which is what stops one cycle from - dispatching two events of a key in parallel. FilterClaimable (lines 543-549) is the shared - predicate (these ids, still unprocessed, not leased). FilterUnblocked (lines 558-568) adds the + dispatching two events of a key in parallel. FilterClaimable (lines 442-448) is the shared + predicate (these ids, still unprocessed, not leased). FilterUnblocked (lines 457-467) adds the ordering guard as a correlated NOT EXISTS: a keyed row is refused while any earlier unprocessed, non-dead-lettered row shares its key, evaluated by the database at the instant of the update, so a second replica racing the same key loses on the row rather than on a check it made before the race - started. Which of the two runs is decided per batch (lines 470-475): a batch with no keyed row runs + started. Which of the two runs is decided per batch (lines 383-388): a batch with no keyed row runs exactly the query it always ran, so hosts that never declare an ordering key pay nothing for the feature, not even a subquery the optimizer has to prove away. Two documented consequences: a predecessor still blocks while it is retrying, which is the head-of-line blocking IHasOrderingKey documents, but once it exhausts its retries it stops blocking, so a poison event cannot freeze its key forever (lines - 443-445); and the predecessor test is on OccurredOn alone, so two rows sharing a key and an exact + 356-358); and the predecessor test is on OccurredOn alone, so two rows sharing a key and an exact timestamp are ordered by Id within a cycle but neither blocks the other in SQL, because Guid has - no order that .NET and every provider agree on (remarks, lines 448-453).
        • -
        • Restoring the request's identity, per row. DispatchMessagesAsync now also takes the cycle's - IServiceProvider scopeServices (OutboxProcessor.cs:503), passed as scope.ServiceProvider from - the ProcessSourceAsync call site (lines 322-324). Before anything else runs for a row, Context.AmbientOrigin.Restore writes that - row's captured UserId, UserRoles, TenantId and CorrelationId onto the scope, tagged with the - authentication type OutboxPrincipalAuthenticationType ("Outbox", line 81) so the rebuilt identity - reads IsAuthenticated true and names the hop it came back from (lines 605-613). It is overwritten - again for the next row (doc, lines 570-573), so one row's identity can never answer for another's: - BrokerMessageBus reads the restored values through the same scoped services when it stamps its - publish headers, and the in-process path (InProcessMessageBus to - IDomainEventDispatcher) gets the right ambient context for free without - either transport reaching back into the row itself (remarks, lines 570-578).
        • -
        • Dispatching. DispatchMessagesAsync (OutboxProcessor.cs:500-637) walks the claimed batch - inside a per-message activity (line 607). A row whose payload will not deserialize goes to - HandleUnresolvableType (lines 621-625). Otherwise an IIntegrationEvent is + no order that .NET and every provider agree on (remarks, lines 362-365).
        • +
        • A fresh scope per row, and the request's identity restored onto it. DispatchMessagesAsync + takes the claimed rows and the batch's + TenantDataSourceTarget + (OutboxProcessor.cs:490-493), and for each row opens its own rowScope through + scopeFactory.CreateTenantScope(target) (line 509): a tenant-owned target keeps its tenant, while + the shared target starts unresolved so the restore can set this row's tenant or leave it unset + (comment, lines 507-508). The doc gives the reason (remarks, lines 476-485): the tenant context + refuses a change once resolved, so on one shared scope every later row of a shared-target batch ran + under the first row's tenant. Before anything else runs, Context.AmbientOrigin.Restore writes that + row's captured UserId, UserRoles, TenantId and CorrelationId onto rowScope.ServiceProvider, + tagged with the authentication type OutboxPrincipalAuthenticationType ("Outbox", line 76) so the + rebuilt identity reads IsAuthenticated true and names the hop it came back from (lines 514-520). + The message bus and the dispatcher are resolved from that row scope too (lines 534, 548), so + BrokerMessageBus reads the restored values when it stamps its publish headers, and the in-process + path (InProcessMessageBus to IDomainEventDispatcher) gets the right + ambient context for free, without either transport reaching back into the row. Only delivery moves + to the row scope: the cycle scope's context keeps tracking the rows for the batch save (lines + 484-485).
        • +
        • Dispatching. DispatchMessagesAsync (OutboxProcessor.cs:490-632) walks the claimed batch + inside a per-message activity (line 505). A row whose payload will not deserialize goes to + HandleUnresolvableType (lines 522-527). Otherwise an IIntegrationEvent is published through IMessageBus and a pure domain event goes to - IDomainEventDispatcher (lines 630-645). On success the row is stamped - (lines 647-649), ProcessedCounter is incremented (line 652) and DispatchLagHistogram records the + IDomainEventDispatcher (lines 532-550). On success the row is stamped + (lines 552-554), ProcessedCounter is incremented (line 557) and DispatchLagHistogram records the seconds between OccurredOn and ProcessedOn, clamped at zero because the two timestamps come from - different hosts and clock skew must not publish a negative duration (lines 657-659). The per-message + different hosts and clock skew must not publish a negative duration (lines 559-564). The per-message success log is deliberately Debug, not Information, and the comment prices the difference: it would otherwise be the single noisiest line in steady state, a real telemetry-ingestion cost, while - failures stay loud (lines 852-854).
        • + failures stay loud (lines 747-751).
        • Dead-lettering an unresolvable type, with one grace attempt. HandleUnresolvableType - (OutboxProcessor.cs:651-673) treats the first failure to resolve as transient and retries it - through the normal backoff path, because the assembly declaring the type may simply not be loaded - yet, a module assembly resolved lazily or a host still coming up, and a name that resolves one cycle - later was never a dead letter (doc, lines 730-734). Only the second attempt is terminal, which is - also the point at which an operator has already had a Warning naming the row (lines 747-754, - LogTypeUnresolvableRetry at 864-865, whose message names the fix: give the event an - EventName). A host that set MaxRetries - to 1 asked for no retries at all, so that case skips the grace attempt rather than scheduling one - the poll's filter would never pick up (lines 745-746). The terminal path stamps ProcessedOn, - increments the dead-letter counter with reason=type_unresolvable and logs at Error (lines 756-762).
        • + (OutboxProcessor.cs:650-673) records LastError (line 652) and treats the first failure to + resolve as transient, retrying it through the normal backoff path, because the assembly declaring + the type may simply not be loaded yet, a module assembly resolved lazily or a host still coming up, + and a name that resolves one cycle later was never a dead letter (doc, lines 635-639). Only the + second attempt is terminal, which is also the point at which an operator has already had a Warning + naming the row (lines 656-663, LogTypeUnresolvableRetry at 759-760, whose message names the fix: + give the event an EventName). A host that + set MaxRetries to 1 asked for no retries at all, so that case skips the grace attempt rather than + scheduling one the poll's filter would never pick up (lines 654-655). The terminal path dead-letters + the row the way exhausted retries do: it sets RetryCount to MaxRetries and clears LockedUntil, + leaving ProcessedOn null (lines 665-666), increments the dead-letter counter with + reason=type_unresolvable and logs at Error (lines 667-671). The doc names the effect (lines + 640-643): the row leaves the poll but stays listed and replayable by the outbox administration and + is kept for the dead-letter retention window, instead of being purged as delivered.
        • The broker circuit breaker. Only the broker hop is wrapped. _brokerPublishPipeline - (OutboxProcessor.cs:104) is a Polly ResiliencePipeline built by BuildBrokerPublishPipeline - (lines 795-806) from + (OutboxProcessor.cs:102) is a Polly ResiliencePipeline built by BuildBrokerPublishPipeline + (lines 690-701) from BrokerResilienceDefaults (failure - ratio, minimum throughput, sampling and break durations, lines 799-802), and the integration-event - branch executes the publish through it (lines 636-640). Three deliberate choices sit in the doc + ratio, minimum throughput, sampling and break durations, lines 694-697), and the integration-event + branch executes the publish through it (lines 540-544). Three deliberate choices sit in the doc comments. It guards the publish call only, never the database calls, because a breaker on those - would open exactly when the processor most needs to persist retry state (lines 101-103). It carries + would open exactly when the processor most needs to persist retry state (lines 90-92). It carries no retry strategy, because the outbox already owns retry through RetryCount and - ComputeRetryBackoffSeconds (lines 104-105). And it is an instance field rather than a static + ComputeRetryBackoffSeconds (lines 93-94). And it is an instance field rather than a static one, so breaker state cannot leak across the many processors a test assembly constructs in parallel - (lines 106-111). OperationCanceledException is excluded from the handled set (line 804), because - a host shutdown cancelling a batch is not evidence that the broker is unhealthy (doc, lines 789-794). - The in-process dispatcher branch is left unwrapped on purpose: it is a direct method call into the - same process, so a breaker there would only add a way to reject work that would have succeeded - (comment, lines 632-635).
        • + (lines 95-100). OperationCanceledException is excluded from the handled set (lines 698-699), + because a host shutdown cancelling a batch is not evidence that the broker is unhealthy (doc, lines + 683-689). The in-process dispatcher branch is left unwrapped on purpose: it is a direct method call + into the same process, so a breaker there would only add a way to reject work that would have + succeeded (comment, lines 536-539).
        • Failure handling. A cancellation during dispatch is rethrown rather than treated as a delivery - failure, and the comment explains the bug that guard prevents (lines 665-668): falling into the + failure, and the comment explains the bug that guard prevents (lines 568-575): falling into the generic handler would increment RetryCount and stamp LastError on this message and, since every later await fails the same way, on the whole remainder of the batch, so a graceful restart could - dead-letter messages that were never attempted. A genuine exception bumps RetryCount (line 673), - records LastError (line 674) and re-leases the row for an explicit backoff (lines 682-683); - the comment notes that simply keeping the original claim made every retry wait the full - LeaseSeconds no matter what the polling interval or a signal said, turning the retry cadence into - an accident of the lease (lines 676-681). A BrokenCircuitException follows that same failure path - but is counted separately on + dead-letter messages that were never attempted. A genuine exception bumps RetryCount (line 578), + records LastError truncated to the 4000-character column width (MaxErrorLength, line 79, through + ColumnWidth.Truncate, line 579) and re-leases + the row for an explicit backoff (lines 587-588); the comment notes that simply keeping the original + claim made every retry wait the full LeaseSeconds no matter what the polling interval or a signal + said, turning the retry cadence into an accident of the lease (lines 581-586). A + BrokenCircuitException follows that same failure path but is counted separately on BrokerMetrics.CircuitOpenCounter (lines - 693-699) and logged once per batch through a local latch (lines 603, 701-705), because an open + 598-604) and logged once per batch through a local latch (lines 501, 606-610), because an open circuit rejects every remaining row in the same instant, and "the broker refused 50 messages" and - "we did not try, the broker is known-dead" are different operational facts (comment, lines 687-692). + "we did not try, the broker is known-dead" are different operational facts (comment, lines 592-597). When RetryCount reaches MaxRetries (5 by default, OutboxSettings.cs:21) the dead-letter counter - is incremented with reason=retries_exhausted and it logs at Error (lines 707-716); the row then + is incremented with reason=retries_exhausted and it logs at Error (lines 612-622); the row then leaves the poll through the RetryCount filter and is eventually purged by - OutboxCleanupService, unless an operator replays it first through + OutboxCleanupService after the dead-letter retention window (comment, lines + 614-616), unless an operator replays it first through OutboxAdministration.
        • -
        • Backoff. OutboxProcessor.ComputeRetryBackoffSeconds (OutboxProcessor.cs:680-681) is now a +
        • Backoff. OutboxProcessor.ComputeRetryBackoffSeconds (OutboxProcessor.cs:680-681) is a one-line forward to PollingLoop.ComputeRetryBackoffSeconds (PollingLoop.cs:183-197), passing _settings.RetryBackoffBaseSeconds and _settings.LeaseSeconds as the base and cap. The shared implementation is RetryBackoffBaseSeconds * 2^(retryCount - 1) (default base 10, @@ -2514,18 +2535,18 @@

          OutboxProcessor

          shared schedule. The S2245/CA5394 suppression (PollingLoop.cs:192,194) is justified inline: the randomness feeds no security, token, key or cryptographic decision.
        • Graceful shutdown. If cancellation lands mid-batch, ProcessSourceAsync calls - TryPersistStampsOnCancellationAsync (lines 331-340, implemented at 402-414) before rethrowing, so + TryPersistStampsOnCancellationAsync (lines 230-240, implemented at 301-313) before rethrowing, so messages already delivered keep their ProcessedOn instead of being redelivered when their lease - expires. Two constraints are deliberate (doc, lines 389-401): its own try/catch, because a failure + expires. Two constraints are deliberate (doc, lines 288-300): its own try/catch, because a failure here must never replace the propagating OperationCanceledException the loop uses to recognize - shutdown; and its own 5-second token (ShutdownSaveTimeout, line 97) rather than + shutdown; and its own 5-second token (ShutdownSaveTimeout, line 86) rather than CancellationToken.None, so an uncancellable save against a dead connection cannot hold host shutdown open until the command timeout. A failure there logs at Warning and says plainly that the - delivered messages will be redelivered when the lease expires (lines 846-847).
        • + delivered messages will be redelivered when the lease expires (lines 741-742).
        • Trace continuity. StartOutboxActivity (OutboxProcessor.cs:708-730) rebuilds the original - request's ActivityContext from the row's TraceId/SpanId (lines 820-823) and starts a + request's ActivityContext from the row's TraceId/SpanId (lines 715-718) and starts a Consumer-kind OutboxProcess activity tagged with the message id, event type and data source - (lines 825-832), returning null when no trace context was captured (lines 815-818), so traces span + (lines 720-727), returning null when no trace context was captured (lines 710-713), so traces span the asynchronous hop.
      • @@ -2539,11 +2560,12 @@

        OutboxProcessor

        unresolvable types stops one poison message from blocking the queue, the progress requirement on re-poll (see OutboxCycleResult) prevents a fully-failing batch from hot-spinning, the circuit breaker keeps a known-dead broker from being hammered once per row per - cycle, the lease-plus-token pair is what makes running more than one replica safe, and the ordering - guard inside the claim is what makes ordered delivery survive both batching and scale-out. + cycle, the lease-plus-token pair is what makes running more than one replica safe, the ordering + guard inside the claim is what makes ordered delivery survive both batching and scale-out, and the + per-row scope is what lets one batch deliver rows belonging to different tenants.
      • Where it's used: registered as a hosted service by AddInfrastructure whenever the transport enables the outbox - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:207-211), so every + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:217-221), so every broker-backed service host runs exactly one. The producer side is the two IEventBus implementations (InProcessEventBus and BrokerEventBus) plus the SaveChanges capture in @@ -2593,10 +2615,10 @@

        IMessageBus

        The MassTransit v8 pin is a separate constraint enforced by the dependency-version fitness test (v9 requires a commercial licence); see the primer's external-stack section.
      • Where it's used: implemented by InProcessMessageBus, the default - scoped registration (MMCA.Common.Infrastructure/DependencyInjection.cs:311, with the rationale + scoped registration (MMCA.Common.Infrastructure/DependencyInjection.cs:321, with the rationale comment at DependencyInjection.cs:551-555), and by BrokerMessageBus, which Replaces that registration inside AddBrokerMessaging - (MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:93). At runtime it is resolved per cycle by + (MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:94). At runtime it is resolved per cycle by the background OutboxProcessor (MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:193) and invoked for every integration-event row it drains (OutboxProcessor.cs:590-600).
      • @@ -2682,9 +2704,9 @@

        BrokerMessageBus

        identity headers, and a publish with nothing to stamp pays no extra allocation for the pipe (comment, BrokerMessageBus.cs:59-61).
      • Where it's used: swapped in for the default in-process registration by AddBrokerMessaging - through services.Replace (MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:93), which returns + through services.Replace (MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:94), which returns early without touching the container when MessageBus:Provider is InProcess - (MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:50-53; see + (MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:51-54; see MessageBusSettings). It is driven at runtime by the OutboxProcessor, which resolves IMessageBus per cycle (MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:193) and calls it inside a @@ -2781,7 +2803,7 @@

        DomainEventDispatcher

        after the outbox rows are written (MMCA.Common.Infrastructure/Persistence/Interceptors/DomainEventSaveChangesInterceptor.cs:337), by the background OutboxProcessor when re-dispatching persisted domain events - (MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:554), and by both in-process + (MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:549), and by both in-process buses (InProcessMessageBus, InProcessEventBus).
      @@ -2817,12 +2839,12 @@

      InProcessMessageBus

      latency. When the outbox is enabled, the OutboxProcessor still supplies the at-least-once safety net around this bus, because the processor is what invokes it.
    267. Where it's used: registered as the default scoped IMessageBus in AddServices - (MMCA.Common.Infrastructure/DependencyInjection.cs:311, with the rationale comment at + (MMCA.Common.Infrastructure/DependencyInjection.cs:321, with the rationale comment at DependencyInjection.cs:551-555), and therefore the bus resolved by OutboxProcessor in monolith mode (MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:193). It is replaced by BrokerMessageBus in broker-mode service hosts - (MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:93).
    268. + (MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:94).

      [Rubric §10, Messaging & Integration Architecture] applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores.

      IntegrationEventConsumer<TEvent>

      @@ -2935,7 +2957,7 @@

      IntegrationEventConsumer<TEvent>IInboxStore is registered. AddBrokerMessaging registers EfInboxStore when MessageBusSettings.IsInboxEnabled resolves true, which it does by default for a broker transport - (MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:106-117, + (MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:107-122, MMCA.Common.Infrastructure/Messaging/MessageBusSettings.cs:141); an explicit MessageBus:EnableInbox=false opts down to NoOpInboxStore, where the inbox calls do nothing and every redelivery re-runs the handlers, a posture announced once at startup by @@ -2971,7 +2993,7 @@

      OutputCacheEvictionRequested

      framework-shipped integration events are the framework's own contract, gated by its conventions and public API baseline, so consumer residency rules and frozen snapshots neither police nor churn on them - (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Events.cs:59-66). + (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Events.cs:130-137).
    269. Walkthrough: the type carries one member and one attribute. [EventName("Common.OutputCacheEvictionRequested.v1")] (OutputCacheEvictionRequested.cs:28) pins the stable wire and storage identity, which is what the @@ -3088,7 +3110,7 @@

      IntegrationEventConsumerExtensionson means the safe posture is the one you get by not thinking about it, [Rubric §15, Best Practices & Code Quality].

    270. Where it's used: inside the configureConsumers callback passed to AddBrokerMessaging - (MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:87) in each broker-mode service's + (MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:88) in each broker-mode service's Program.cs, for example x.RegisterIntegrationEventConsumer<SpeakerLinkedToUser>().
    271. [Rubric §10, Messaging & Integration Architecture] applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores.

      @@ -3126,8 +3148,9 @@

      BrokerEventBus

      (BrokerEventBus.cs:51-52). PublishBatchAsync (BrokerEventBus.cs:65-95) resolves the outbox's logical data source through IDataSourceResolver - (BrokerEventBus.cs:67) and gets its context (BrokerEventBus.cs:68). If !context.SupportsOutbox - (BrokerEventBus.cs:70, Cosmos for example) it throws an InvalidOperationException naming the + (BrokerEventBus.cs:67) and gets its context (BrokerEventBus.cs:68). The outbox gate is the + context's engine capability, !context.Engine.Capabilities.IsRelational (BrokerEventBus.cs:70): + when the resolved engine is not relational (Cosmos for example) it throws an InvalidOperationException naming the misconfigured Outbox:DataSource and Outbox:DatabaseName rather than silently dropping the events (BrokerEventBus.cs:75-76, with the rationale at BrokerEventBus.cs:72-74). Otherwise it reads the scope's ambient OutboxOrigin once for the whole batch, @@ -3153,11 +3176,11 @@

      BrokerEventBus

      at the first publish rather than lose events quietly, and the same constraint is checked once at startup as well: EnsureOutboxAvailableForProvider rejects MessageBus:EnableOutbox=false under a broker transport with a message that names this class as the reason - (MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:58, + (MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:59, DependencyInjection.Messaging.cs:181-185).
    272. Where it's used: registered as the scoped IEventBus when AddBrokerMessaging runs, replacing InProcessEventBus - (MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:99, with the explanatory comment at + (MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:100, with the explanatory comment at DependencyInjection.Messaging.cs:95-98). Every IEventBus injection in application code resolves this implementation in broker mode.
    273. @@ -3209,8 +3232,9 @@

      InProcessEventBus

      single overload wraps one event (InProcessEventBus.cs:50), and the batch overload coerces the sequence to an array once and returns early when empty (InProcessEventBus.cs:58-60).
    274. PublishBatchAsync resolves the outbox target (InProcessEventBus.cs:78) and its context - (InProcessEventBus.cs:79). If !context.SupportsOutbox || !_outboxEnabled - (InProcessEventBus.cs:81) it dispatches directly with no outbox persistence and returns + (InProcessEventBus.cs:79). If the resolved engine is not relational or the outbox is + off, !context.Engine.Capabilities.IsRelational || !_outboxEnabled (InProcessEventBus.cs:81), it + dispatches directly with no outbox persistence and returns (InProcessEventBus.cs:83-84).
    275. Otherwise it reads the scope's ambient OutboxOrigin once for the whole batch, context.CurrentOutboxOrigin (InProcessEventBus.cs:87-89), because the batch is one @@ -3229,15 +3253,16 @@

      InProcessEventBus

      SaveChangesAsync closes the dual-write gap, and dispatching immediately afterward gives synchronous in-process reactions without giving up the durable retry path. Finishing through OutboxFinalizer rather than a second full save keeps the hottest write path - down to one extra statement, [Rubric §12, Performance & Scalability]. The SupportsOutbox fast - path keeps the framework usable on a store without an outbox table (dispatch-only) rather than + down to one extra statement, [Rubric §12, Performance & Scalability]. The non-relational + (Engine.Capabilities.IsRelational) fast path keeps the framework usable on a store without an + outbox table (dispatch-only) rather than failing, which is the deliberate opposite of the choice BrokerEventBus makes: with no local consumers, a silent dispatch-only fallback in broker mode would drop the event entirely, so that class throws instead.
    276. Where it's used: the default scoped IEventBus registration - (MMCA.Common.Infrastructure/DependencyInjection.cs:305), superseded by + (MMCA.Common.Infrastructure/DependencyInjection.cs:315), superseded by BrokerEventBus once AddBrokerMessaging is called - (MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:99).
    277. + (MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:100).

      [Rubric §10, Messaging & Integration Architecture] applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores.


      diff --git a/docs/onboarding/group-05-cqrs-pipeline.html b/docs/onboarding/group-05-cqrs-pipeline.html index 4b3cb6bb..f367b96e 100644 --- a/docs/onboarding/group-05-cqrs-pipeline.html +++ b/docs/onboarding/group-05-cqrs-pipeline.html @@ -324,7 +324,7 @@

      MmcaApplicationPipelineBuilder, then AddApplicationDecorators() (DependencyInjection.cs:209-215). Seven production service hosts compose it that way (for example - MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:407 and + MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:409 and MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:233), while MMCA.Helpdesk's web host still writes the hand-composed form and ends with the decorators call (MMCA.Helpdesk/Source/Hosts/MMCA.Helpdesk.Web/Program.cs:120). Alongside it, @@ -516,13 +516,13 @@

      Why this exact order, a call, in DbContextFactory ([Rubric §8, Data Architecture]), and it is worth reading: a returned failed Result rolls the transaction back, exactly like an exception - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:582-588); + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:598-604); the call is re-entrant, so a nested transaction joins the ambient one and only the outermost call - begins, commits, or rolls back (DbContextFactory.cs:529-530); in-process domain event dispatch is - deferred until after a successful commit and dropped on rollback (DbContextFactory.cs:597-602); + begins, commits, or rolls back (DbContextFactory.cs:545-546); in-process domain event dispatch is + deferred until after a successful commit and dropped on rollback (DbContextFactory.cs:616-621); and a failure of the commit itself is never retried, surfacing as - TransactionCommitAmbiguousException instead (DbContextFactory.cs:499-504, - DbContextFactory.cs:591-593). + TransactionCommitAmbiguousException instead (DbContextFactory.cs:515-520, + DbContextFactory.cs:610-612).

      Opt-in by marker interface, pay only for what you use

      The pipeline is registered for every handler, but most decorators are dormant unless the use case @@ -832,10 +832,10 @@

      Tenant scoping and the two lock non-generic holder around a KeyedSemaphoreStripe so that every closed generic decorator shares one table rather than one per closed type (CachingQueryDecorator.cs:226-250). Its sibling CacheKeyLocks - (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:142) does the same job + (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:189) does the same job for the default ICacheService.GetOrCreateAsync implementation, and is deliberately a separate table: different call sites over different keys, where sharing stripes would only widen the - unrelated-key collisions striping already tolerates (ICacheService.cs:134-140). Both are striped + unrelated-key collisions striping already tolerates (ICacheService.cs:181-187). Both are striped rather than one semaphore per key, and both are honest about the limit: the lock is per process, so across replicas stampede protection is at most one handler execution per instance, not one cluster-wide (CachingQueryDecorator.cs:236-241).

      @@ -882,8 +882,8 @@

      The other Applicati owner-scoped and idempotent (IDistributedLock.cs:54-57). No decorator takes it; its in-framework caller is the API idempotency filter, which needs its execute-then-store window to be exclusive across replicas (IdempotencyFilter, - MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:148, acquisition at - IdempotencyFilter.cs:250, + MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:149, acquisition at + IdempotencyFilter.cs:251, ADR-017), and the implementation (RedisDistributedLock or the InProcessDistributedLock fallback) @@ -995,7 +995,7 @@

      Where this fits, and the

      The contract to memorize, because the rest of the system relies on it, has four clauses. On a business failure (a Result with IsFailure, no exception thrown) the transaction is rolled back, atomicity over partial persistence, and cache invalidation is skipped - (DependencyInjection.cs:108-109, enforced at DbContextFactory.cs:582-588 and + (DependencyInjection.cs:108-109, enforced at DbContextFactory.cs:598-604 and CachingCommandDecorator.cs:61-63). On an exception the transaction also rolls back and the exception propagates outward through every decorator, which records the outcome and tags the metric exception, leaving the stack to the boundary that finally handles it @@ -1034,12 +1034,12 @@

      IDistributedLock

    278. Concept introduced, cross-replica mutual exclusion as an Application-layer abstraction. [Rubric §12, Performance & Scalability] assesses whether a design still holds once the service scales out horizontally, and the XML doc opens with precisely that failure (IDistributedLock.cs:6-13): a SemaphoreSlim (or a striped one) serializes callers inside one process, so a service running more than one replica executes an "only one of these at a time" section once per replica. [Rubric §29, Resilience, Reliability & Business Continuity] assesses behavior under partial failure; this contract is documented as best-effort, not a consensus protocol (IDistributedLock.cs:23-28): a holder paused past its time-to-live loses the lock without being told, so the guarded section must stay correct (merely slower, or duplicated) when exclusion is lost. The doc states the usage rule bluntly: take the lock to collapse duplicate work, never as the only guard on a correctness invariant that persistence can enforce. [Rubric §3, Clean Architecture] assesses whether the core depends on abstractions while technology choices sit at the edge; the contract carries no transport type at all, so the StackExchange.Redis dependency stays in Infrastructure and callers here never see it.
    279. Walkthrough: line 30 declares the interface; lines 59-63 declare its single member, Task<IAsyncDisposable?> TryAcquireAsync(string key, TimeSpan ttl, TimeSpan wait, CancellationToken cancellationToken = default). Every parameter carries a contract the implementations must honour. key is the logical name that callers sharing one backing store have to agree on (IDistributedLock.cs:36). ttl is the crash guard: how long the lock survives with no explicit release, so a holder that dies mid-section cannot wedge the key; it must sit comfortably above the guarded section's expected duration, because work that outlives the TTL is no longer protected (:37-42). wait is how long to block for a current holder, and TimeSpan.Zero makes the call a single non-blocking attempt (:43-46). The token cancels the wait, not the work that follows it (:47). The return contract matters as much as the parameters: null means "still held elsewhere after wait elapsed", and the handle is meant to be disposed inside an await using so release happens even when the guarded work throws (:48-53). Release is owner-scoped and idempotent (:54-58): disposing a handle whose TTL already lapsed is a no-op, not a release of whatever holder now owns the key. Two remarks bound usage further: implementations are singletons and must be safe to call concurrently (:17), and the lock is not reentrant, so a caller that already holds key and asks for it again waits for itself and then fails to acquire (:20-21).
    280. Why it's built this way: ADR-017 records the change that introduced it. The idempotency filter's execute-then-store window was previously guarded only by a process-local striped semaphore, which stops serializing anything the moment a service runs more than one replica, and both deployed apps do. Putting the contract in MMCA.Common.Application.Interfaces rather than Infrastructure is what lets the API filter depend on "a lock" while the Redis-versus-process-local decision stays a composition-root concern.
    281. -
    282. Where it's used: the Infrastructure composition root registers exactly one implementation inside AddCaching (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:84-98), choosing RedisDistributedLock when an IConnectionMultiplexer is resolvable (:87-93) and the warn-once InProcessDistributedLock otherwise (:95-97); the comment above the registration explains the pairing with the cache (:80-83). Two production consumers exist today.
        -
      • The framework's IdempotencyFilter resolves it from request services (MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:148) and spans the double-check plus action plus cache-store window with a 30-second ttl (LockTimeToLive, :97) and a 5-second wait (LockWait, :104), calling TryAcquireAsync at :249-251. When that wait expires with nothing cached it answers a 409 in-flight-duplicate result instead of executing a second time (:261-273); when the lock call itself throws, it records a degraded metric and executes anyway rather than failing the request (:253-259).
      • +
      • Where it's used: the Infrastructure composition root registers exactly one implementation inside AddCaching (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:86-100), choosing RedisDistributedLock when an IConnectionMultiplexer is resolvable (:87-93) and the warn-once InProcessDistributedLock otherwise (:95-97); the comment above the registration explains the pairing with the cache (:80-83). Two production consumers exist today.
          +
        • The framework's IdempotencyFilter resolves it from request services (MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:149) and spans the double-check plus action plus cache-store window with a 30-second ttl (LockTimeToLive, :97) and a 5-second wait (LockWait, :104), calling TryAcquireAsync at :249-251. When that wait expires with nothing cached it answers a 409 in-flight-duplicate result instead of executing a second time (:261-273); when the lock call itself throws, it records a degraded metric and executes anyway rather than failing the request (:253-259).
        • MMCA.ADC's ScoreEventSessionsInternalCommandHandler takes the lock per event around an AI-scoring pass (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/ScoreEventSessionsInternalCommandHandler.cs:78-80) with a 15-minute ClaimTimeToLive (:53) and a ClaimWait of TimeSpan.Zero (:60), so a second replica that cannot claim the event simply skips its pass (:79-83). The inline rationale (:14-21) is a good worked example of both halves of the contract: the handle's disposal releases on every exit path, and the TTL releases for a replica that never reaches an exit path at all.
      • -
      • Caveats / not-in-source: MMCA.Store/Source has no IDistributedLock consumer today. The idempotency filter also resolves it with GetService<IDistributedLock>() and falls back to the striped-semaphore path when the result is null (IdempotencyFilter.cs:148-153), even though AddCaching registers an implementation unconditionally, so that fallback is reachable only in a host that never calls AddCaching (or a test building its own provider). The framework's own registration comment states the other honest limit: a host with no Redis gets the process-local, warn-once implementation, so "cross-replica" exclusion degrades back to per-replica (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:82-83).
      • +
      • Caveats / not-in-source: MMCA.Store/Source has no IDistributedLock consumer today. The idempotency filter also resolves it with GetService<IDistributedLock>() and falls back to the striped-semaphore path when the result is null (IdempotencyFilter.cs:149-154), even though AddCaching registers an implementation unconditionally, so that fallback is reachable only in a host that never calls AddCaching (or a test building its own provider). The framework's own registration comment states the other honest limit: a host with no Redis gets the process-local, warn-once implementation, so "cross-replica" exclusion degrades back to per-replica (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:82-83).

      IScheduledJob

      @@ -1077,7 +1077,7 @@

      ITenantContext

    283. Concept introduced, ambient scope state with an honest "unset" value. [Rubric §11, Security] assesses whether data isolation is enforced structurally rather than remembered per query; every tenant-aware read filter, save interceptor and cache key reads this one object, so a handler cannot forget to scope itself. [Rubric §12, Performance & Scalability]: like the correlation id, the value is captured once at the edge and flows implicitly for the rest of the scope. The interesting design decision is the one the doc calls out (ITenantContext.cs:10-15): unlike the correlation id there is no generated fallback. An unresolved tenant is a meaningful state (a background service, a seeder, an admin flow) and reads as "see everything", so inventing a value would silently scope a system operation to a tenant that does not exist.
    284. Walkthrough: line 28 declares string? TenantId { get; }, null until resolved. Line 31 declares bool IsResolved { get; }. Line 41 declares void SetTenant(string tenantId), and its contract is the strict part: it throws ArgumentException on a null, empty or whitespace id (:37) and InvalidOperationException when a different tenant was already resolved for this scope (:38-40), while accepting the value it already holds (:34). The rationale is on :16-20: one scope, one tenant, because a scope whose tenant changed mid-flight has already read rows under the previous tenant and there is no honest way to reconcile that afterwards. The implementation matches exactly (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Context/TenantContext.cs:11-45), where IsResolved is simply TenantId is not null (:17), the first SetTenant assigns (:24-28), a repeat of the same value returns quietly (:32-35), and anything else throws with a message that names both tenants (:37-43).
    285. Why it's built this way: ADR-073 records the multi-tenancy model. Putting the contract in Application, not Infrastructure, is what lets the Application-layer caching decorators scope their keys without referencing EF Core: CachingCommandDecorator<TCommand, TResult> and CachingQueryDecorator<TQuery, TResult> both take it as an optional primary-constructor parameter defaulting to null (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/CachingCommandDecorator.cs:37 and .../CachingQueryDecorator.cs:45), so a host that never resolves a tenant pays nothing.
    286. -
    287. Where it's used: registered scoped in AddServices(), not in AddMultiTenancy (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:294). The comment above the line is the design statement (:535-538): everything that reads the context treats an unresolved tenant as "no tenancy", so an always-on registration costs one object per scope and removes a whole class of "works until someone forgets the opt-in" bug. AddMultiTenancy(configuration) is then only the settings half: it binds and validates TenancySettings and adds the per-source override validator (:511-523). The context is written at the edge by TenantResolutionMiddleware from a claim or a header, and read by DbContextFactory for per-tenant routing and by both caching decorators through TenantCacheKey.Scope, which prefixes the key with t:{tenantId}: only when a tenant is actually resolved (.../UseCases/Decorators/TenantCacheKey.cs:37-40, called at CachingCommandDecorator.cs:67 and CachingQueryDecorator.cs:56).
    288. +
    289. Where it's used: registered scoped in AddServices(), not in AddMultiTenancy (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:304). The comment above the line is the design statement (:535-538): everything that reads the context treats an unresolved tenant as "no tenancy", so an always-on registration costs one object per scope and removes a whole class of "works until someone forgets the opt-in" bug. AddMultiTenancy(configuration) is then only the settings half: it binds and validates TenancySettings and adds the per-source override validator (:511-523). The context is written at the edge by TenantResolutionMiddleware from a claim or a header, and read by DbContextFactory for per-tenant routing and by both caching decorators through TenantCacheKey.Scope, which prefixes the key with t:{tenantId}: only when a tenant is actually resolved (.../UseCases/Decorators/TenantCacheKey.cs:37-40, called at CachingCommandDecorator.cs:67 and CachingQueryDecorator.cs:56).
    290. Caveats / not-in-source: verified by source search, neither MMCA.ADC/Source nor MMCA.Store/Source mentions ITenantContext at all today. Multi-tenancy is a shipped, tested framework capability that no deployed app has opted into, so every scope in production runs unresolved and the tenant-scoped cache keys and query filters are no-ops there.

    291. @@ -1192,15 +1192,15 @@

      CqrsContractMismatch


      CacheKeyLocks

      -

      MMCA.Common.Application · MMCA.Common.Application.Interfaces · MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:142 · Level 2 · class (static, internal)

      +

      MMCA.Common.Application · MMCA.Common.Application.Interfaces · MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:189 · Level 2 · class (static, internal)

      • What it is: a two-line internal holder for the process-wide stripe table that the default ICacheService.GetOrCreateAsync<T> implementation uses to keep concurrent misses on one key from all running the factory.
      • Depends on: KeyedSemaphoreStripe (MMCA.Common.Shared.Concurrency, using at ICacheService.cs:1). Used only by ICacheService's default interface method.
      • -
      • Concept introduced, cache stampede protection and why the lock table is striped. [Rubric §12, Performance & Scalability] assesses behavior under load, and this is the classic thundering-herd guard: on a cold key, N concurrent readers would otherwise all miss, all call the expensive factory, and all write the same value. The interesting part is the shape of the guard. A per-key semaphore table forces a bad choice, spelled out on ICacheService.cs:134-141: drop the entry on release and two callers can run concurrently, or never drop it and a parameterized cache key grows the table without bound. Striping sidesteps both by hashing keys onto a fixed number of semaphores (KeyedSemaphoreStripe) and accepting that two unrelated keys occasionally share one. That is a fixed, bounded cost, and the stripes are never disposed because the table outlives every caller.
      • -
      • Walkthrough: line 142 declares internal static class CacheKeyLocks; line 145 declares its only member, internal static readonly KeyedSemaphoreStripe Locks = new(). The consuming sequence is the double-checked idiom at ICacheService.cs:99-124: a null-check on the factory (:105), a lock-free GetAsync fast path that returns immediately on a hit (:107-110), then the stripe is taken (:112), then the key is re-read inside the stripe (:114-118) so the waiters see what the winner just wrote, and only a still-missing key runs the factory and stores it (:120-122). The class doc (:127-133) explains the non-generic holder: statics on a generic method's declaring type would already be shared, but a holder keeps the table addressable and matches the sibling QueryCacheKeyLocks in the caching decorator (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/CachingQueryDecorator.cs:247, used at :182 and :189).
      • -
      • Why it's built this way: the two tables are separate on purpose (ICacheService.cs:138-141): these are different call sites over different keys, so sharing stripes would only widen the unrelated-key collisions striping already tolerates. Two limits of the mechanism are documented on the member it guards (ICacheService.cs:80-97) and matter more than the class itself. First, caching there is unconditional: whatever the factory returns is stored, including a failed Result or a null-equivalent value, which is exactly why the caching decorators do NOT route through GetOrCreateAsync and keep their own read/execute/write sequence (:81-86). Second, stampede protection is per process: the stripe table is process-wide, so with several replicas over one shared cache the factory can still run once per replica; a cluster-wide guarantee would need an IDistributedLock and is deliberately not attempted here (:87-91).
      • -
      • Where it's used: only by the default implementation of ICacheService.GetOrCreateAsync<T> (ICacheService.cs:112). Backing stores with a native two-level primitive override the method and never touch this table (:92-97); HybridCacheService is the shipped example, forwarding straight to HybridCache.GetOrCreateAsync (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/HybridCacheService.cs:238-248).
      • +
      • Concept introduced, cache stampede protection and why the lock table is striped. [Rubric §12, Performance & Scalability] assesses behavior under load, and this is the classic thundering-herd guard: on a cold key, N concurrent readers would otherwise all miss, all call the expensive factory, and all write the same value. The interesting part is the shape of the guard. A per-key semaphore table forces a bad choice, spelled out on ICacheService.cs:181-188: drop the entry on release and two callers can run concurrently, or never drop it and a parameterized cache key grows the table without bound. Striping sidesteps both by hashing keys onto a fixed number of semaphores (KeyedSemaphoreStripe) and accepting that two unrelated keys occasionally share one. That is a fixed, bounded cost, and the stripes are never disposed because the table outlives every caller.
      • +
      • Walkthrough: line 142 declares internal static class CacheKeyLocks; line 145 declares its only member, internal static readonly KeyedSemaphoreStripe Locks = new(). The consuming sequence is the double-checked idiom at ICacheService.cs:145-171: a null-check on the factory (:105), a lock-free GetAsync fast path that returns immediately on a hit (:107-110), then the stripe is taken (:112), then the key is re-read inside the stripe (:114-118) so the waiters see what the winner just wrote, and only a still-missing key runs the factory and stores it (:120-122). The class doc (:127-133) explains the non-generic holder: statics on a generic method's declaring type would already be shared, but a holder keeps the table addressable and matches the sibling QueryCacheKeyLocks in the caching decorator (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/CachingQueryDecorator.cs:247, used at :182 and :189).
      • +
      • Why it's built this way: the two tables are separate on purpose (ICacheService.cs:185-188): these are different call sites over different keys, so sharing stripes would only widen the unrelated-key collisions striping already tolerates. Two limits of the mechanism are documented on the member it guards (ICacheService.cs:126-143) and matter more than the class itself. First, caching there is unconditional: whatever the factory returns is stored, including a failed Result or a null-equivalent value, which is exactly why the caching decorators do NOT route through GetOrCreateAsync and keep their own read/execute/write sequence (:81-86). Second, stampede protection is per process: the stripe table is process-wide, so with several replicas over one shared cache the factory can still run once per replica; a cluster-wide guarantee would need an IDistributedLock and is deliberately not attempted here (:87-91).
      • +
      • Where it's used: only by the default implementation of ICacheService.GetOrCreateAsync<T> (ICacheService.cs:159). Backing stores with a native two-level primitive override the method and never touch this table (:92-97); HybridCacheService is the shipped example, forwarding straight to HybridCache.GetOrCreateAsync (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/HybridCacheService.cs:285-295).
      • Caveats / not-in-source: the type is internal, so it is not part of the public package surface and cannot be referenced or replaced from a consumer app; it is documented here because the behavior it produces is visible to anyone calling GetOrCreateAsync.

      @@ -1490,7 +1490,7 @@

      ADR-099 added to close the shapes that still forced a hand-written handler, and it is additive: every existing subclass of the other two flavors compiles and behaves exactly as before. -
    292. Where it's used: ADC's UpdateEventHandler is the reference case (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/Update/UpdateEventHandler.cs:22). Its MutateAsync compares the incoming timezone against the stored one before Event.Update overwrites it, only pays for a session-existence lookup when the value actually moves, and writes the BR-131 warning flag into the context under a private key (:43-57); BuildResult then assembles the UpdateEventResult envelope from the aggregate plus that flag (:80). ADC also uses it for AddRoomHandler (.../Events/UseCases/AddRoom/AddRoomHandler.cs:28) and for SetUserAvatarHandler (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/SetUserAvatar/SetUserAvatarHandler.cs:32). Exercised by MutateEntityHandlerBaseTests.
    293. +
    294. Where it's used: ADC's UpdateEventHandler is the reference case (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/Update/UpdateEventHandler.cs:22). Its MutateAsync compares the incoming timezone against the stored one before Event.Update overwrites it, only pays for a session-existence lookup when the value actually moves, and writes the BR-131 warning flag into the context under a private key (:43-57); BuildResult then assembles the UpdateEventResult envelope from the aggregate plus that flag (:80). ADC also uses it for AddRoomHandler (.../Events/UseCases/AddRoom/AddRoomHandler.cs:28) and for SetUserAvatarHandler (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/SetUserAvatar/SetUserAvatarHandler.cs:34). Exercised by MutateEntityHandlerBaseTests.

    295. CqrsMetrics

      @@ -1815,22 +1815,22 @@

      ValidatingQueryDecorator<TQue

      TransactionalCommandDecorator<TCommand, TResult>

      -

      MMCA.Common.Application · MMCA.Common.Application.UseCases.Decorators · MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:20 · Level 8 · class (sealed)

      +

      MMCA.Common.Application · MMCA.Common.Application.UseCases.Decorators · MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:22 · Level 8 · class (sealed)

      • What it is: the innermost standard command decorator, the one closest to the concrete handler. It wraps the handler in a database transaction only when the command opts in via ITransactional; every other command passes straight through.
      • Depends on: ITransactional (the opt-in marker); IUnitOfWork (supplies ExecuteInTransactionAsync, TransactionalCommandDecorator.cs:1, :20); ICommandHandler<in TCommand, TResult>. No BCL or NuGet dependency beyond Task and CancellationToken.
      • Concept introduced, declarative transaction boundaries via a marker. [Rubric §2, Design Patterns] assesses whether patterns are chosen deliberately rather than decoratively; this is the Decorator pattern in its plainest form, a type that is an ICommandHandler and holds an inner ICommandHandler, so the pipeline composes without any handler knowing it exists. [Rubric §12, Performance & Scalability] assesses whether concerns like transactions are handled once, centrally: no handler in either app calls BeginTransaction. [Rubric §8, Data Architecture] assesses whether transaction boundaries are deliberate: here the boundary is declared on the command type, a domain-adjacent artifact reviewable in a pull request, rather than buried in handler code. The class itself is tiny; nearly all of the behavior lives one layer down in DbContextFactory, reached through UnitOfWork.
      • Walkthrough
          -
        • Primary constructor (TransactionalCommandDecorator.cs:20-22): takes the inner ICommandHandler<TCommand, TResult> and an IUnitOfWork. There is no state and no _field capture; both parameters are used directly in the single method.
        • -
        • HandleAsync (TransactionalCommandDecorator.cs:25), opt-out path (:26-27): if (command is not ITransactional) return await inner.HandleAsync(command, cancellationToken). A command that has not opted in pays exactly one type test and never touches the unit of work, which is why the decorator can be registered unconditionally on every command handler in the host.
        • -
        • Transactional path (TransactionalCommandDecorator.cs:31-33): unitOfWork.ExecuteInTransactionAsync(ct => inner.HandleAsync(command, ct), cancellationToken). The handler call is passed as a delegate rather than awaited inline, which is what lets the layer below own begin, commit, rollback, and (crucially) re-run the delegate under an EF Core execution strategy. Note that the lambda forwards the strategy's own ct, not the captured outer token.
        • +
        • Primary constructor (TransactionalCommandDecorator.cs:22-24): takes the inner ICommandHandler<TCommand, TResult> and an IUnitOfWork. There is no state and no _field capture; both parameters are used directly in the single method.
        • +
        • HandleAsync (TransactionalCommandDecorator.cs:27), opt-out path (:26-27): if (command is not ITransactional) return await inner.HandleAsync(command, cancellationToken). A command that has not opted in pays exactly one type test and never touches the unit of work, which is why the decorator can be registered unconditionally on every command handler in the host.
        • +
        • Transactional path (TransactionalCommandDecorator.cs:33-35): unitOfWork.ExecuteInTransactionAsync(ct => inner.HandleAsync(command, ct), cancellationToken). The handler call is passed as a delegate rather than awaited inline, which is what lets the layer below own begin, commit, rollback, and (crucially) re-run the delegate under an EF Core execution strategy. Note that the lambda forwards the strategy's own ct, not the captured outer token.
      • Why it's built this way: opt-in via a marker means only handlers that mutate multiple aggregates, or save twice against one database, pay for a transaction; everything else keeps the single-SaveChangesAsync atomicity EF Core already gives. Because transactions are per physical data source and there is no two-phase commit, cross-source consistency is the outbox's job rather than this decorator's (ADR-006). The composition itself, a Scrutor decorator chain over thin handlers with a load-bearing registration order, is ADR-014.
      • Where it's used: registered by AddApplicationDecorators() at MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:134, the first of the seven command registrations (:129-135). Scrutor's TryDecorate applies decorators in reverse registration order (DependencyInjection.cs:57-58), so registered-first means applied-innermost: the transaction opens closest to the handler and inside the timeout budget and cache invalidation. That is what makes "invalidate only after a committed mutation" true (DependencyInjection.cs:101-102) and what lets an expired budget cancel the transaction rather than leave it open (:104-107).
      • Adoption, verified against source: opt-in is deliberately narrow. Seven production commands implement the marker today: SendPushNotificationCommand in Common itself (MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationCommand.cs:23); four in ADC, LinkUserToSpeakerCommand (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Speakers/UseCases/LinkUser/LinkUserToSpeakerCommand.cs:13), UnlinkUserFromSpeakerCommand (.../Speakers/UseCases/UnlinkUser/UnlinkUserFromSpeakerCommand.cs:12), RefreshFromSessionizeCommand (.../Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeCommand.cs:13), and BatchAddSessionQuestionAnswersCommand (.../Sessions/UseCases/BatchAddSessionQuestionAnswers/BatchAddSessionQuestionAnswersCommand.cs:24); and two in Store, UploadProductImageCommand (MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Application/Products/UseCases/UploadImage/UploadProductImageCommand.cs:27) and ReorderProductImagesCommand (.../Products/UseCases/ReorderImages/ReorderProductImagesCommand.cs:22). Four Store commands carry a doc comment stating they are deliberately not transactional because each performs a single save: CheckOutCommand.cs:9, VerifyPaymentCommand.cs:11, ProcessPaymentWebhookCommand.cs:9, and BulkSetInventoryCommand.cs:10, the last of which is asserted in a test (MMCA.Store/Tests/Modules/Sales/MMCA.Store.Sales.Application.Tests/Inventory/BulkSetInventoryTests.cs:264). The full inventory and the reasoning per command live under ITransactional.
      • -
      • Caveats / not-in-source: four behaviors a reader will attribute to this file actually live in DbContextFactory, and the class doc here (TransactionalCommandDecorator.cs:11-16, which mentions only exceptions) is narrower than what the code below does. (1) A returned failed Result rolls the transaction back exactly like an exception, atomicity over partial persistence (DbContextFactory.cs:480-484, :563-570). (2) The call is re-entrant: a nested ExecuteInTransactionAsync joins the ambient transaction instead of opening a second one, so an ITransactional command whose handler also opens a transaction no longer throws from EF (DbContextFactory.cs:475-479, :503-511). (3) The whole delegate runs under an EF execution strategy and may be re-executed on a transient failure, with the change tracker reset between attempts so a retry does not insert duplicates (DbContextFactory.cs:485-497, :526-534). (4) A failure of the commit itself is never retried and surfaces as TransactionCommitAmbiguousException (DbContextFactory.cs:498-505, :536-541). In-process domain event dispatch is deferred until after a successful commit and dropped on rollback (DbContextFactory.cs:595-602), via DomainEventSaveChangesInterceptor.
      • +
      • Caveats / not-in-source: four behaviors a reader will attribute to this file actually live in DbContextFactory, and the class doc here (TransactionalCommandDecorator.cs:11-18, which mentions only exceptions) is narrower than what the code below does. (1) A returned failed Result rolls the transaction back exactly like an exception, atomicity over partial persistence (DbContextFactory.cs:496-500, :563-570). (2) The call is re-entrant: a nested ExecuteInTransactionAsync joins the ambient transaction instead of opening a second one, so an ITransactional command whose handler also opens a transaction no longer throws from EF (DbContextFactory.cs:491-495, :503-511). (3) The whole delegate runs under an EF execution strategy and may be re-executed on a transient failure, with the change tracker reset between attempts so a retry does not insert duplicates (DbContextFactory.cs:501-513, :526-534). (4) A failure of the commit itself is never retried and surfaces as TransactionCommitAmbiguousException (DbContextFactory.cs:514-521, :536-541). In-process domain event dispatch is deferred until after a successful commit and dropped on rollback (DbContextFactory.cs:614-621), via DomainEventSaveChangesInterceptor.

      AuthorizationGate

      @@ -1843,8 +1843,8 @@

      AuthorizationGate

    296. Concept introduced, one dual-source capability check shared by both transports and both CQRS sides. [Rubric §11, Security] assesses where the capability check lives and whether the same request is evaluated the same way regardless of caller. The remarks explain the dual grant source (AuthorizationGate.cs:39-45): the permission check grants on either source, exactly as the HTTP policy handler does, a role the host's registry maps to the permission, or an AuthClaimTypes.Permission claim on the principal itself. The claim path is what carries a stored grant across a service boundary, since only the minting host reads the grant table; checking the registry alone would deny in one service what the endpoint policy allows in another. [Rubric §1, SOLID]: extracting the shared logic out of the two decorators (see the diff hunks below) removes a duplicated permission check that previously had to be kept in sync by hand across the command and query files.
    297. Walkthrough
      • Permission gate (AuthorizationGate.cs:52-62): request is IRequiresPermission requiresPermission combined with !permissionRegistry.HasPermission(currentUser.Roles, requiresPermission.Permission) and !currentUser.User.HasPermissionClaim(requiresPermission.Permission); only when both are false does it record CqrsMetrics.RecordAuthorizationDenied(requestTypeName) and return Error.Forbidden("Authorization.PermissionDenied", ...).
      • -
      • MFA gate (AuthorizationGate.cs:64-74): checked second, and the comment says why (:64-65): a caller who holds no capability at all is answered by the permission gate and never learns which use cases additionally demand a step-up. request is IRequiresMfa && !currentUser.User.HasMultiFactor() records the same denial metric and returns Error.Forbidden("Authorization.MultiFactorRequired", ...).
      • -
      • Pass-through (AuthorizationGate.cs:76): return null when neither marker denies, or when the request implements neither marker at all.
      • +
      • MFA gate (AuthorizationGate.cs:58-68): checked second, and the comment says why (:58-59): a caller who holds no capability at all is answered by the permission gate and never learns which use cases additionally demand a step-up. request is IRequiresMfa && !currentUser.User.HasMultiFactor() records the same denial metric and returns Error.Forbidden("Authorization.MultiFactorRequired", ...).
      • +
      • Pass-through (AuthorizationGate.cs:70): return null when neither marker denies, or when the request implements neither marker at all.
    298. Why it's built this way: the command and query authorization decorators previously each inlined the permission check; the 2026-08-18 revision of ADR-014 both added the IRequiresMfa step-up gate and pulled the combined logic into this one internal static class so the two decorators stay thin callers rather than two copies of the same branching. ADR-020 supplies the permission model and registry the first gate reads; ADR-116 documents the mfa claim this class reads through HasMultiFactor for the second gate (116-identity-completions-opt-in.md:73-83).
    299. @@ -1857,16 +1857,16 @@

      AuthorizationCommandDecor

      • What it is: the second-outermost command decorator. When a command implements IRequiresPermission and none of the caller's roles or permission claims grants that permission, or when it implements IRequiresMfa and the caller has not stepped up, it short-circuits with a Forbidden failure before logging, cache invalidation, validation, the timeout budget, or the transaction.
      • -
      • Depends on: AuthorizationGate (AuthorizationCommandDecorator.cs:70, the shared evaluator that now holds the branching); ICurrentUserService and IPermissionRegistry (both forwarded straight into AuthorizationGate.Evaluate rather than read locally); ResultFailureFactory; Error; ICommandHandler<in TCommand, TResult>.
      • +
      • Depends on: AuthorizationGate (AuthorizationCommandDecorator.cs:62, the shared evaluator that now holds the branching); ICurrentUserService and IPermissionRegistry (both forwarded straight into AuthorizationGate.Evaluate rather than read locally); ResultFailureFactory; Error; ICommandHandler<in TCommand, TResult>.
      • Concept introduced, authorization as a use-case concern rather than a transport concern. [Rubric §11, Security] assesses where the capability check lives and whether it survives a change of transport. The class doc frames this decorator as defense in depth, not a replacement for the endpoint's [Authorize] policy (AuthorizationCommandDecorator.cs:22-26): moving the check next to the use case is what makes a command reached through gRPC, a scheduled job, or another module get checked the same way it is over HTTP. [Rubric §13, Observability & Operability] assesses the factoring: no handler injects a permission service, and every denial increments a counter (inside AuthorizationGate) so a permission denying far more traffic than expected is visible as a metric rather than as a support ticket.
      • Walkthrough
        • _createFailure (AuthorizationCommandDecorator.cs:50) and CreateFailure() (:52-53): the lazily-built ResultFailureFactory delegate, _createFailure ??= ResultFailureFactory.Build<TResult>(). The remarks (:36-45) explain why the build is deferred to the first short-circuit rather than done in a static initializer: ResultFailureFactory supports only Result and Result<T> and throws otherwise (ResultFailureFactory.cs:43-45), and because Scrutor's TryDecorate is unconditional, an eager static initializer would turn an unsupported TResult into a TypeInitializationException at resolve time for a handler that never denies anything. One assignment per closed generic type, and a benign duplicate build under a race produces an equivalent delegate.
        • -
        • HandleAsync (AuthorizationCommandDecorator.cs:68-76): calls AuthorizationGate.Evaluate(command, currentUser, permissionRegistry, typeof(TCommand).Name) (:70). A null result runs the inner handler directly (:71-72); otherwise it builds the failure factory and returns createFailure([denial]) (:74-75). The decorator itself no longer type-tests the command or reads IPermissionRegistry.HasPermission; both marker checks, the claim fallback, and the metric now live in AuthorizationGate.
        • +
        • HandleAsync (AuthorizationCommandDecorator.cs:60-68): calls AuthorizationGate.Evaluate(command, currentUser, permissionRegistry, typeof(TCommand).Name) (:62). A null result runs the inner handler directly (:63-64); otherwise it builds the failure factory and returns createFailure([denial]) (:66-67). The decorator itself no longer type-tests the command or reads IPermissionRegistry.HasPermission; both marker checks, the claim fallback, and the metric now live in AuthorizationGate.
      • Why it's built this way: the placement is the argued part. It sits directly inside the feature gate and outside logging, cache invalidation, validation and the transaction (DependencyInjection.cs:92-94, class doc :12-17), so a denied command never starts a transaction, never invalidates the cache and never runs validation, while a feature that is off is still rejected first, because a disabled feature must not leak the existence of the permission that guards it (DependencyInjection.cs:88-91). ADR-020 supplies the permission model and the registry; the 2026-08-18 revision of ADR-014 inserted this stage into both chains and, in a later revision, extracted the shared AuthorizationGate so the command and query decorators stopped carrying two copies of the same check.
      • Where it's used: registered by AddApplicationDecorators() (DependencyInjection.cs:139) as the sixth of seven command registrations (:129-135), therefore the second-outermost wrapper on every command handler in the host. Because the two authorization decorators are registered unconditionally and take an IPermissionRegistry, the same method first does services.TryAddSingleton<IPermissionRegistry, UnconfiguredPermissionRegistry>() (DependencyInjection.cs:123, comment :119-123): a host that declared its grants via AddAuthorizationPolicies() or AddPermissions(...) keeps its own registry, and a host with no permission model still resolves every handler instead of failing activation. UnconfiguredPermissionRegistry grants nothing (UnconfiguredPermissionRegistry.cs:44-51) and logs one warning the first time a permission is actually checked (:49-60), so the fallback fails closed and says so. Behavior is covered by AuthorizationCommandDecoratorTests and, for the ordering, CommandDecoratorPipelineTests.
      • -
      • Caveats / not-in-source: no production command implements IRequiresPermission today (the only implementers in the workspace are test fakes such as GuardedCommand at MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Decorators/AuthorizationCommandDecoratorTests.cs:196), so this decorator is a pass-through in every deployed host. One behavioral note for whoever adopts it first: because the decorator sits outside LoggingCommandDecorator<TCommand, TResult>, a denial is not recorded in the cqrs.command.duration histogram and produces no correlated command log line, only the denial counter. The failure travels on the Result channel rather than as an exception, and the framework's transport mappers already translate it: ErrorHttpMapping maps ErrorType.Forbidden to HTTP 403 (MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/ErrorHttpMapping.cs:27) and ResultGrpcExtensions maps it to StatusCode.PermissionDenied (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:43).
      • +
      • Caveats / not-in-source: no production command implements IRequiresPermission today (the only implementers in the workspace are test fakes such as GuardedCommand at MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Decorators/AuthorizationCommandDecoratorTests.cs:196), so this decorator is a pass-through in every deployed host. One behavioral note for whoever adopts it first: because the decorator sits outside LoggingCommandDecorator<TCommand, TResult>, a denial is not recorded in the cqrs.command.duration histogram and produces no correlated command log line, only the denial counter. The failure travels on the Result channel rather than as an exception, and the framework's transport mappers already translate it: ErrorHttpMapping maps ErrorType.Forbidden to HTTP 403 (MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/ErrorHttpMapping.cs:27) and ResultGrpcExtensions maps it to StatusCode.PermissionDenied (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:44).

      AuthorizationQueryDecorator<TQuery, TResult>

      @@ -1985,10 +1985,10 @@

      ITransactional

      • What it is: a C# empty-body interface (public interface ITransactional;) that a command implements to opt in to database-transaction wrapping by TransactionalCommandDecorator<TCommand, TResult>.
      • Depends on: nothing (BCL only).
      • -
      • Concept introduced, marker interfaces as opt-in decorator switches. [Rubric §2, Design Patterns] assesses the deliberate, idiomatic use of patterns; a marker interface carries no members, so its mere presence on a type is the signal. The decorator's whole dispatch is if (command is not ITransactional) followed by a pass-through (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:27-29). Read that line carefully: the marker goes on the command record, not on the handler class, which is the same convention IFeatureGated, ICacheInvalidating, IHasTimeout and IRequiresPermission follow. This is also [Rubric §1, SOLID] (open for extension): the pipeline gains new transactional commands with no change to any existing decorator. The semicolon-body syntax (interface ITransactional;, line 6) is the idiomatic zero-member declaration and is used consistently across the framework's markers, including ICommand<TResult> and IQuery<TResult>.
      • +
      • Concept introduced, marker interfaces as opt-in decorator switches. [Rubric §2, Design Patterns] assesses the deliberate, idiomatic use of patterns; a marker interface carries no members, so its mere presence on a type is the signal. The decorator's whole dispatch is if (command is not ITransactional) followed by a pass-through (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:29-31). Read that line carefully: the marker goes on the command record, not on the handler class, which is the same convention IFeatureGated, ICacheInvalidating, IHasTimeout and IRequiresPermission follow. This is also [Rubric §1, SOLID] (open for extension): the pipeline gains new transactional commands with no change to any existing decorator. The semicolon-body syntax (interface ITransactional;, line 6) is the idiomatic zero-member declaration and is used consistently across the framework's markers, including ICommand<TResult> and IQuery<TResult>.
      • Walkthrough: the entire file body is public interface ITransactional; at line 6, under the doc comment on lines 3-5. No members; the type is the message.
      • Why it's built this way: keeping transaction scope opt-in spares single-statement commands the cost of an explicit begin and commit around a save that is already atomic. Commands that mutate multiple aggregates, or that save twice against one database, opt in. The behavior that comes with opting in is documented in ADR-014 and MMCA.Common/CLAUDE.md, and is worth knowing before you add the marker: exceptions and business failures (Result.Failure) both roll back, and in-process domain event dispatch is deferred until after a successful commit, so handlers never act on state that could still roll back.
      • -
      • Where it's used: sparingly, and the restraint is deliberate. Verified by source search, exactly 4 implementers in MMCA.ADC/Source (RefreshFromSessionizeCommand.cs:13, BatchAddSessionQuestionAnswersCommand.cs:24, LinkUserToSpeakerCommand.cs:13, UnlinkUserFromSpeakerCommand.cs:12) and exactly 2 in MMCA.Store/Source (UploadProductImageCommand.cs:27 and ReorderProductImagesCommand.cs:22, both of which save twice against the same database and document why on lines 10 and 11 respectively). Several Store commands a reader would expect to see here carry a doc comment saying the opposite: CheckOutCommand.cs:9, BulkSetInventoryCommand.cs:10, VerifyPaymentCommand.cs:11 and ProcessPaymentWebhookCommand.cs:9 are each explicitly annotated "Deliberately NOT ITransactional", because their handlers reach the database in a single save that is already atomic. Inspected at execution time by TransactionalCommandDecorator<TCommand, TResult>, the innermost command decorator, so it sits closest to the handler (see the registration note under ICommandHandler<in TCommand, TResult>).
      • +
      • Where it's used: sparingly, and the restraint is deliberate. Verified by source search, exactly 4 implementers in MMCA.ADC/Source (RefreshFromSessionizeCommand.cs:13, BatchAddSessionQuestionAnswersCommand.cs:24, LinkUserToSpeakerCommand.cs:13, UnlinkUserFromSpeakerCommand.cs:12) and exactly 2 in MMCA.Store/Source (UploadProductImageCommand.cs:27 and ReorderProductImagesCommand.cs:24, both of which save twice against the same database and document why on lines 10 and 11 respectively). Several Store commands a reader would expect to see here carry a doc comment saying the opposite: CheckOutCommand.cs:9, BulkSetInventoryCommand.cs:11, VerifyPaymentCommand.cs:11 and ProcessPaymentWebhookCommand.cs:9 are each explicitly annotated "Deliberately NOT ITransactional", because their handlers reach the database in a single save that is already atomic. Inspected at execution time by TransactionalCommandDecorator<TCommand, TResult>, the innermost command decorator, so it sits closest to the handler (see the registration note under ICommandHandler<in TCommand, TResult>).

      IEventUpcaster

      diff --git a/docs/onboarding/group-06-validation.html b/docs/onboarding/group-06-validation.html index 5b759119..c8749d3a 100644 --- a/docs/onboarding/group-06-validation.html +++ b/docs/onboarding/group-06-validation.html @@ -235,7 +235,7 @@

      6. Validation

      AbstractValidator<T> generic over the parent type, taking an Expression<Func<T, ...>> selector in its constructor and declaring its rules in an expression-bodied constructor. Because they are generic-plus-selector, the same EmailRules<T> - (CommonValidationRules.cs:64) validates a value object, a request DTO, or a command; a module + (CommonValidationRules.cs:65) validates a value object, a request DTO, or a command; a module composes it with FluentValidation's Include(...) instead of rewriting "non-empty, valid format, max length" each time. The bounds are parameters, never literals in the rule: ADC's registration validator passes UserInvariants.EmailMaxLength into EmailRules<RegisterRequest> and pairs it with @@ -243,7 +243,7 @@

      6. Validation

      RequiredStringRules<T> (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/Validation/RegisterRequestValidator.cs:16-19). The two password rules are the one place a literal bound is intentional: both pin 8 and 128 - characters (CommonValidationRules.cs:179-180, :193-194), and + characters (CommonValidationRules.cs:180-181, :193-194), and StrongPasswordRules<T> adds four complexity Matches rules for uppercase, lowercase, digit, and non-alphanumeric (CommonValidationRules.cs:195-198). That pair is [Rubric §11, Security] territory: the framework offers a weak-by-default floor and a strong @@ -253,11 +253,11 @@

      6. Validation

      are what the whole file is optimizing for.

      One field, one error code. Every rule class takes an optional trailing errorCode, and OptionalErrorCodeExtensions.WithOptionalErrorCode - (CommonValidationRules.cs:19, the method at :30-32) is the internal helper that applies it: + (CommonValidationRules.cs:20, the method at :30-32) is the internal helper that applies it: it returns the rule unchanged when the code is null, so every existing call site that omits it behaves exactly as before, and calls FluentValidation's WithErrorCode when one is supplied. The code is applied to every rule the class declares for that field, so one field answers under one - code (CommonValidationRules.cs:11-18); a field whose separate bounds must answer under distinct + code (CommonValidationRules.cs:12-19); a field whose separate bounds must answer under distinct codes still writes its own rules. This is what lets modules subclass a framework rule instead of bypassing it: ADC's SessionEventIdRules<T> derives from RequiredIdRules<T, TId> and passes "Session.EventId.Required" @@ -268,16 +268,16 @@

      6. Validation

      (MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Application/Products/Validation/ProductValidationRules.cs:47-51). The two id rules also encode a deliberate difference in what "missing" means: RequiredIdRules<T, TId> uses NotEmpty, which rejects both 0 for an - integer key and Guid.Empty for a GUID key (CommonValidationRules.cs:133-139, + integer key and Guid.Empty for a GUID key (CommonValidationRules.cs:134-140, :147), while OptionalPositiveIdRules<T, TId> uses GreaterThan(default(TId)) on a nullable and relies on FluentValidation skipping a comparison rule when the property is null, so "positive when provided" needs no When clause and no per-pass - recompiled selector (CommonValidationRules.cs:154-158, :166).

      + recompiled selector (CommonValidationRules.cs:155-159, :166).

      One rule that shares its check with the domain. AbsoluteUrlRules<T> - (CommonValidationRules.cs:85) is the exception to "rule sets are self-contained": besides the - length bound it calls Must(BeAnAbsoluteHttpUrl) (CommonValidationRules.cs:90), and that predicate + (CommonValidationRules.cs:86) is the exception to "rule sets are self-contained": besides the + length bound it calls Must(BeAnAbsoluteHttpUrl) (CommonValidationRules.cs:91), and that predicate delegates to CommonInvariants.EnsureUrlIsWellFormed - (CommonValidationRules.cs:92-93, the invariant at + (CommonValidationRules.cs:93-94, the invariant at MMCA.Common/Source/Core/MMCA.Common.Domain/Invariants/CommonInvariants.cs:293-297) and keeps only its IsSuccess. The validator and the domain invariant therefore answer identically, by construction rather than by convention. What it buys is concrete: a plain bounded-string treatment accepts @@ -377,7 +377,7 @@

      6. Validation

      contract the gate emits, and by the architecture fitness tests that keep the layering honest.

      OptionalErrorCodeExtensions

      -

      MMCA.Common.Application · MMCA.Common.Application.Validation · MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:19 · Level 0 · class (internal static)

      +

      MMCA.Common.Application · MMCA.Common.Application.Validation · MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:20 · Level 0 · class (internal static)

      • What it is: a one-method internal helper that lets every reusable rule fragment in @@ -396,7 +396,7 @@

        OptionalErrorCodeExtensions

        an ErrorCode meant for a program. Left alone, FluentValidation fills the code with the validator's name ("NotEmptyValidator", "MaximumLengthValidator"), which is useless to a caller that wants to branch on "the question text was missing" rather than "some non-empty rule failed somewhere". The - class remarks (CommonValidationRules.cs:11-18) record the failure mode this helper exists to close: + class remarks (CommonValidationRules.cs:12-19) record the failure mode this helper exists to close: module validators used to skip the shared bases entirely and hand-write the rule chain for the single reason that the bases set a message but no code, so a validator needing a stable code had nothing to compose with. [Rubric §9, API & Contract Design] assesses whether the contract a client codes @@ -447,53 +447,53 @@

        AbsoluteUrlRules<T>, is a Level 6 sibling @@ -502,8 +502,11 @@

        CommonValidationRules.cs:1-3), and the file-local - OptionalErrorCodeExtensions. No first-party dependencies beyond - that: these sit at the very bottom of the Application layer, which is why they carry no invariant + OptionalErrorCodeExtensions. The one first-party dependency beyond + that is StrongPasswordRules<T>'s use of + PasswordComplexity from MMCA.Common.Shared.Auth + (using at CommonValidationRules.cs:5), which is why it sits one Level above its nine siblings. + The rest sit at the very bottom of the Application layer, which is why they carry no invariant constants and take maxLength as an argument. Bridged onto commands automatically by CommandRequestValidator<TCommand, TRequest>.

      • @@ -558,10 +561,19 @@

        (:176-180) takes only (selector, errorCode): its messages are fixed strings, not parameterized by a field name. It enforces non-empty plus a length band of 8 to 128. -
      • StrongPasswordRules<T> (:190-198) repeats that band and adds four Matches(...) calls whose - regex literals are inline in the source: "[A-Z]", "[a-z]", "\\d" and "[^a-zA-Z\\d]" for - uppercase, lowercase, digit and special character (:195-198). The doc comment on PasswordRules - (:170-171) points callers who need complexity at StrongPasswordRules<T> instead.
      • +
      • StrongPasswordRules<T> (:193-201) keeps the same messages but takes nothing inline: the band + comes from PasswordComplexity.MinimumLength and MaximumLength (:196-197), and its four + Matches(...) calls pass the Uppercase, Lowercase, Digit and SpecialCharacter regex + properties (:198-201). Those are [GeneratedRegex] members over the Unicode classes \p{Lu}, + \p{Ll}, \p{Nd} and [^\p{L}\p{Nd}], each with a 1000 ms match timeout + (MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/PasswordComplexity.cs:27-43), so an accented + or CJK letter counts as a letter, never as the special character. The doc comment (:187-188) + states the reason: PasswordComplexity is the one definition the client form attribute + PasswordComplexityAttribute also + evaluates, so server and client cannot disagree, and PasswordRuleParityTests + (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Ui/PasswordRuleParityTests.cs:36) + compares the two verdicts. PasswordRules<T> still hard-codes 8 and 128 (:180-181); its doc + comment (:171-172) points callers who need complexity at StrongPasswordRules<T> instead.
    300. Why it's built this way: these fragments are the DRY core of the validation story. Because they @@ -922,7 +934,7 @@

      AddressValidator

      AbsoluteUrlRules<T>

      -

      MMCA.Common.Application · MMCA.Common.Application.Validation · MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:85 · Level 6 · class

      +

      MMCA.Common.Application · MMCA.Common.Application.Validation · MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:86 · Level 6 · class

      • What it is: a reusable fragment for an optional URL field. It applies two rules: a maximum @@ -936,7 +948,7 @@

        AbsoluteUrlRules<T>

        what puts this fragment at Level 6 while its file-mates sit at Level 0.
      • Concept introduced, delegating a validator predicate to the domain invariant. [Rubric §11, Security] assesses whether untrusted input is constrained before it reaches a sink. - The rule's own doc comment states the threat plainly (CommonValidationRules.cs:77-83): a + The rule's own doc comment states the threat plainly (CommonValidationRules.cs:78-84): a length-only bound accepts javascript: and data: values, and those become executable the moment a link href or an image src renders them. The scheme check is therefore not cosmetic URL hygiene, it is the boundary control for stored script injection through a user-supplied link. @@ -948,13 +960,13 @@

        AbsoluteUrlRules<T>

        implementation of the scheme test, it calls the domain invariant, so the request-level answer and the entity-level answer cannot diverge.
      • Walkthrough:
          -
        • The constructor (CommonValidationRules.cs:87-90) takes +
        • The constructor (CommonValidationRules.cs:88-91) takes (Expression<Func<T, string?>> selector, string fieldName, int maxLength, string? errorCode = null) and builds a single chain: MaximumLength(maxLength) with the interpolated "{fieldName} cannot be longer than {maxLength} characters" message, then .Must(BeAnAbsoluteHttpUrl) with "{fieldName} must be an absolute http or https URL". Each rule ends in .WithOptionalErrorCode(errorCode) (:89, :90), which returns the rule untouched when the code - is null (CommonValidationRules.cs:30-32), so every existing caller that omits it keeps + is null (CommonValidationRules.cs:31-33), so every existing caller that omits it keeps FluentValidation's default per-rule code.
        • The length message uses string.Create(CultureInfo.InvariantCulture, ...) while the scheme message is a plain interpolation: the first embeds a number and so pins the culture, the second embeds only @@ -1078,7 +1090,7 @@

          ValidationFailureExtensions

          every broken rule in one response instead of one per round trip. The remaining three call sites are in AuthenticationServiceBase<TUser>, which validates its request before touching the user store and passes the method name as source: - nameof(LoginAsync) (MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:165), + nameof(LoginAsync) (MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:114), nameof(RegisterAsync) (:193), and nameof(RefreshTokenAsync) (:273), each wrapping the result in Result.Failure<AuthenticationResponse>(...). Covered by ValidationFailureExtensionsTests.

          @@ -1089,7 +1101,7 @@

          ValidationFailureExtensions

          by an unrelated extension in the gRPC layer, ResultGrpcExtensions declares an extension(Metadata? trailers) block with its own ToErrors() - (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:146 and :165) that decodes + (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:151 and :165) that decodes errors out of gRPC trailers. Different receiver, different assembly, no relationship to this one.

        diff --git a/docs/onboarding/group-07-persistence-ef-core.html b/docs/onboarding/group-07-persistence-ef-core.html index 155cc039..17148751 100644 --- a/docs/onboarding/group-07-persistence-ef-core.html +++ b/docs/onboarding/group-07-persistence-ef-core.html @@ -152,7 +152,9 @@

        7. Persistence & EF Core

        lot of load. One abstract ApplicationDbContext base with a sealed subclass per engine (SQLServerDbContext, PostgreSQLDbContext, CosmosDbContext, - SqliteDbContext); four EF Core save interceptors that turn a plain + SqliteDbContext) and a stateless engine registry + (IDataSourceEngine, DataSourceEngines) that answers every + engine-specific question as data; four EF Core save interceptors that turn a plain SaveChangesAsync into audit stamping, tenant enforcement, transactional domain-event capture, and a field-level change trail; a small repository family behind an interface-segregated contract (IReadRepository<TEntity, TIdentifierType>, @@ -185,47 +187,49 @@

        One bas is an abstract primary-constructor class over EF's DbContext. It holds the cross-cutting model configuration every engine shares. It applies a global soft-delete query filter to every non-owned IAuditableEntity using a runtime-built - expression tree, registered as a named "SoftDelete" filter (ApplicationDbContext.cs:448-460, - name at :469); it applies a second named "Tenant" filter to every non-owned - ITenantEntity (ApplicationDbContext.cs:509-567); - it configures the RowVersion optimistic-concurrency token, mapped as a SQL Server rowversion or as - a plain application-managed token on other providers (ApplicationDbContext.cs:582-602); and it maps + expression tree, registered as a named "SoftDelete" filter (ApplicationDbContext.cs:453-467, + name at :474); it applies a second named "Tenant" filter to every non-owned + ITenantEntity (ApplicationDbContext.cs:514-574); + it configures the RowVersion optimistic-concurrency token by the engine's + RowVersionStrategy, mapped as a SQL Server rowversion or as + a plain token the audit interceptor stamps on PostgreSQL and SQLite (ApplicationDbContext.cs:576-601, + strategy read at :591); and it maps the framework's own bookkeeping tables so every relational database carries its own - (OutboxMessage at :658-716, - InboxMessage at :718-733, - InternalCommandMessage at :748-790, - ScheduledJobEntry at :799-836, - AuditTrailEntry at :844-873), each with the filtered indexes its poll path and - its retention sweep need (IX_OutboxMessages_Pending at :691, IX_OutboxMessages_Processed at - :698, the keyed-ordering index IX_OutboxMessages_Ordering at :710, - IX_InboxMessages_MessageId at :726, IX_InboxMessages_ProcessedOn at :731, - IX_InternalCommands_Pending at :776, IX_InternalCommands_Processed at :783, - IX_InternalCommands_DeadLettered at :788, - IX_ScheduledJobs_NextRunOn at :833, IX_AuditTrailEntries_Entity at :865, - IX_AuditTrailEntries_ChangedOn at :871). Four of the seven framework tables are gated: the + (OutboxMessage (indexes at :694-713), + InboxMessage (indexes at :729-734), + InternalCommandMessage at :751, + ScheduledJobEntry at :802, + AuditTrailEntry at :847), each with the filtered indexes its poll path and + its retention sweep need (IX_OutboxMessages_Pending at :694, IX_OutboxMessages_Processed at + :701, the keyed-ordering index IX_OutboxMessages_Ordering at :713, + IX_InboxMessages_MessageId at :729, IX_InboxMessages_ProcessedOn at :734, + IX_InternalCommands_Pending at :779, IX_InternalCommands_Processed at :786, + IX_InternalCommands_DeadLettered at :791, + IX_ScheduledJobs_NextRunOn at :836, IX_AuditTrailEntries_Entity at :868, + IX_AuditTrailEntries_ChangedOn at :874). Four of the seven framework tables are gated: the job table is mapped only when Scheduler:Enabled is set AND this context targets the Default source - (jobs are host-scoped, :322-325), the trail table only when AuditTrail:Enabled is set, on every + (jobs are host-scoped, :327-329), the trail table only when AuditTrail:Enabled is set, on every relational source (a trail row must commit with the change it describes, and a transaction does not - span databases, :327), the refresh-session table only when RefreshSessions:Enabled is set AND - this context targets the source that setting names (:332-334, applied at :889-897), and the + span databases, :332), the refresh-session table only when RefreshSessions:Enabled is set AND + this context targets the source that setting names (:336-339, applied at :892-900), and the permission-grant table only when the host called AddStoredPermissionGrants AND this context targets - the source Authentication:PermissionGrants:DataSourceName names (:338, resolved at :910-916, - applied at :933-941). That fourth gate has no configuration flag of its own: the DI call itself is + the source Authentication:PermissionGrants:DataSourceName names (:343, resolved at :913-918, + applied at :936-944). That fourth gate has no configuration flag of its own: the DI call itself is the opt-in, expressed as the marker type PermissionGrantModelGate (.../Persistence/Auth/PermissionGrantModelGate.cs:19, reasoning at :6-12). A host that opted into none of them keeps the model it had before those features shipped, so none of those tables ever appears in its migrations. The outbox, the inbox and the internal-command queue are deliberately not gated: they are mapped into every relational source whether or not this host drains them, so - flipping InternalCommands:Enabled is never a migration (:735-748).

        -

        Its SaveChangesAsync(userId, ...) overload (ApplicationDbContext.cs:189-203) is the one entry + flipping InternalCommands:Enabled is never a migration (:737-751).

        +

        Its SaveChangesAsync(userId, ...) overload (ApplicationDbContext.cs:194-208) is the one entry point handlers care about: it stashes the current user id in CurrentSaveUserId so the audit interceptor can read it, delegates to base, then clears it in a finally so a later internal save - cannot silently reuse the previous caller's identity (:197-201). The base also overrides both + cannot silently reuse the previous caller's identity (:197, :206). The base also overrides both SaveChanges overloads purely to run change detection once per save and suppress it for the rest, - through the DetectChangesOnce helper and its DetectChangesScope disposable - (:209-215, :242, :269-282): each interceptor's ChangeTracker.Entries<T>() call would + through the DetectChangesOnce helper and its DetectChangesScope disposable + (:212-218, :245-249, helper at :274): each interceptor's ChangeTracker.Entries<T>() call would otherwise trigger a full DetectChanges, so a save paid three snapshot comparisons where one - suffices, and the previous auto-detect setting is restored on the way out (:281).

        + suffices, and the previous auto-detect setting is restored when the scope is disposed (:274).

        The design decision that shapes this whole group is stated in the base's own doc comment: one context class per engine, one instance per physical data source (ApplicationDbContext.cs:34-40). The same SQLServerDbContext class @@ -236,9 +240,12 @@

        One bas DataSourceModelCacheKeyFactory (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/DataSourceModelCacheKeyFactory.cs:16) keys EF's model cache by context type plus physical source name plus the design-time flag (:19-22), - and is installed by the base in OnConfiguring (ApplicationDbContext.cs:345). This is deliberately + and is installed by the base in OnConfiguring (ApplicationDbContext.cs:350). This is deliberately not a per-module context split: one sealed context per engine over the abstract base is - ADR-006's ruling. + ADR-006's ruling. Each instance + also exposes the engine it targets as an internal Engine property resolved through + DataSourceEngines (ApplicationDbContext.cs:61), which is how the base, the + interceptors and the factory ask an engine question without naming an engine. SQLServerDbContext adds the provider-specific touches: a per-environment command timeout read from PersistenceSettings (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/PersistenceSettings.cs:10, bound from @@ -257,9 +264,9 @@

        One bas BeginTransactionAsync must run inside Database.CreateExecutionStrategy().ExecuteAsync (:60-62). SqliteDbContext (.../DbContexts/SqliteDbContext.cs:12) is the same shape with UseSqlite and its own migrations assembly (:24-33), and - CosmosDbContext (.../DbContexts/CosmosDbContext.cs:14) deliberately does not + CosmosDbContext (.../DbContexts/CosmosDbContext.cs:15) deliberately does not call base.OnModelCreating, because the relational bookkeeping tables have no place in a document - store; it re-applies only the filters it needs (:89-95). + store; it re-applies only the filters it needs (:139-145). PostgreSQLDbContext (.../DbContexts/PostgreSQLDbContext.cs:23) is the fourth engine class (ADR-113), and its mapping conventions deliberately match SQLServerDbContext rather than PostgreSQL house @@ -276,32 +283,35 @@

        One bas (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SensitiveDataLoggingGate.cs:24) decides whether EF may render parameter values into logs and exception messages, and the answer is an AND rather than a single switch: the host must both ask for it and report itself as Development - (ApplicationDbContext.cs:340, :362-369). A null IHostEnvironment, which is what design-time + (ApplicationDbContext.cs:345, :367-371). A null IHostEnvironment, which is what design-time tooling and a directly-constructed test context have, counts as not Development, so unknown fails closed (SensitiveDataLoggingGate.cs:10-22). That is [Rubric §11, Security] in one helper.

        SaveChanges as an interceptor pipeline

        The base context resolves its interceptors from DI in OnConfiguring - (ApplicationDbContext.cs:285-317), and registration order is execution order. The audit + (ApplicationDbContext.cs:290-322), and registration order is execution order. The audit interceptor runs first, the tenant interceptor between it and the domain-event interceptor (so the - outbox rows describe an entity whose tenant is already final, :258-271), and the change-trail - interceptor last, because it diffs the final values (:273-281). Two of the four are resolved with + outbox rows describe an entity whose tenant is already final, :300-312), and the change-trail + interceptor last, because it diffs the final values (:314-322). Two of the four are resolved with GetService rather than GetRequiredService: a directly-constructed test context or a host that never called AddAuditTrail must still build, and their absence has to read as "the feature is off" rather than fail every context construction.

        AuditSaveChangesInterceptor - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:22) + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:30) runs on SavingChanges: it walks every tracked IAuditableEntity, stamps CreatedOn/By plus - LastModifiedOn/By on Added (:56-60), and on Modified marks the two Created* properties - unmodified before re-stamping LastModified* (:67-71), reading the timestamp from an injected + LastModifiedOn/By on Added (:65-69), and on Modified marks the two Created* properties + unmodified before re-stamping LastModified* (:77-80), reading the timestamp from an injected TimeProvider and the user id from CurrentSaveUserId (falling back to default as the - system-operation sentinel, :49-50). It also writes the soft-delete stamps, and it drives them off + system-operation sentinel, :57-58). It also writes the soft-delete stamps, and it drives them off the transition of the IsDeleted flag rather than its value: DeletedOn/By are written when the - flag goes false to true and cleared when it goes back (:92-105, with the original value read at - :83-84), so a later update to an already-deleted row keeps the stamps of the delete that produced + flag goes false to true and cleared when it goes back (StampSoftDeleteTransition, doc at :111, with the original value read at + :107-109), so a later update to an already-deleted row keeps the stamps of the delete that produced it, exactly as CreatedOn/By survive every update. This is why the domain declares audit fields with private setters and never writes them: the interceptor sets them centrally through - entry.Property(...).CurrentValue, bypassing setter visibility. That is the + entry.Property(...).CurrentValue, bypassing setter visibility. On an engine whose + RowVersionStrategy is ClientStamped (PostgreSQL, SQLite) it also writes a fresh + RowVersion on every insert and update, so the UPDATE's WHERE clause detects a concurrent writer where no + server-generated row version exists (:59, :74, :83, StampRowVersion at :94-105). That is the [Rubric §12, Performance & Scalability] payoff, one enforcement point instead of copy-paste in every handler.

        DomainEventSaveChangesInterceptor @@ -324,7 +334,7 @@

        SaveChanges as an interceptor pi (:62), so it is cleaned up automatically when the context is disposed. A third weak table (:77) holds a per-context capture exclusion set: BeginCaptureExclusion / EndCaptureExclusion (:179-195) let DbContextFactory name exactly the entries it hides from an - IDENTITY_INSERT round, so an event is never serialized and cleared a round before the insert that + explicit-key insert round, so an event is never serialized and cleared a round before the insert that justifies it. The exclusion is by instance rather than by entity state on purpose: a state-based filter would also drop events raised on an already-saved aggregate, which is how the identity module publishes its registration events (:172-176).

        @@ -347,8 +357,9 @@

        SaveChanges as an interceptor pi than losing the event (:346-354). The synchronous SavedChanges path cannot await a dispatcher at all, so it removes the captured events, signals the outbox, and leaves delivery entirely to it (:124-137). Two conditions take the everything-in-process branch instead: Cosmos DB has no relational - outbox table, so the base exposes a SupportsOutbox flag (ApplicationDbContext.cs:172) that - CosmosDbContext overrides to false (CosmosDbContext.cs:69); and a host can + outbox table, so the interceptor asks the context's engine whether it is relational + (DataSourceEngineCapabilities.IsRelational, read at :127 and :236), which is false only for + CosmosDbContext's engine; and a host can turn the outbox off outright, which the interceptor reads once from the message-bus options into _outboxEnabled (:55) and honors at the same branch (:236, :269-275). This split, atomic persistence plus best-effort immediate dispatch with a durable fallback, is the at-least-once contract @@ -365,7 +376,7 @@

        Deferred work, the internal-co deliberately not an IAuditableEntity, carries no soft-delete flag (no global query filter applies to it), no audit stamps and no concurrency token; its concurrency control is an explicit claim lease (:9-19). InternalCommandScheduler - (.../InternalCommands/InternalCommandScheduler.cs:39) writes that row on the same scoped + (.../InternalCommands/InternalCommandScheduler.cs:34) writes that row on the same scoped IDbContextFactory the calling handler's repositories use, which is the whole atomicity story: inside an ITransactional command the factory has already begun a transaction on every context it hands out, so the row commits with the aggregate change or rolls back with it, and @@ -374,7 +385,13 @@

        Deferred work, the internal-co (.../InternalCommands/InternalCommandOrigin.cs:16): the scheduling user, roles, tenant, correlation id and trace context, because without it a deferred command would run as an anonymous tenant-less system call, which is both an authorization hole (an IRequiresPermission command would be denied) - and an audit hole (its writes would carry the system sentinel, :1-9).

        + and an audit hole (its writes would carry the system sentinel, :1-9). The snapshot is taken by the scoped + InternalCommandOriginCapture + (.../InternalCommands/InternalCommandOriginCapture.cs:21, Capture at :28-39), which the scheduler + calls as it builds the row (InternalCommandScheduler.cs:38, :86). It is scoped like the request + services it reads, because the snapshot must describe this request's caller, and it flattens roles through + the same helper the outbox capture uses, so the two hops store one shape + (InternalCommandOriginCapture.cs:13-17).

        InternalCommandProcessor (.../InternalCommands/Processing/InternalCommandProcessor.cs:46) is the drain: a background service that claims due rows with a lease, executes each in a fresh DI scope, and records the outcome. Every @@ -459,7 +476,7 @@

        The tenant boundary, r

        Multi-tenancy (ADR-073) is two independent halves that meet in this group. The read half is the named Tenant query filter the base context applies to every non-owned ITenantEntity - (ApplicationDbContext.cs:509-568). Three details make it work: the filter body embeds the context + (ApplicationDbContext.cs:514-573). Three details make it work: the filter body embeds the context instance as a constant typed as ApplicationDbContext, so EF rewrites it to the executing context and lifts CurrentTenantId into a SQL parameter, letting one compiled model serve every tenant (:413-419, :434-436); the predicate is CurrentTenantId == null || e.TenantId == CurrentTenantId, @@ -513,7 +530,7 @@

        The tenant boundary, r cannot pick its own tenant), Header is for service-to-service calls behind a trusted gateway, and Host is defined but not implemented, present only so the configuration contract stays stable (:8-27). TenancySettingsValidator - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:23) + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:24) is what makes that honest: registered with ValidateOnStart, it fails the boot when a resolution order names Host (:54-65) and checks each tenant's overrides against the resolved physical sources when IDataSourceResolver is registered (:14-18, :39-42), on the reasoning that @@ -530,7 +547,16 @@

        The tenant boundary, r (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:51-81), which emits the shared target for every source plus one extra TenantDataSourceTarget (:15) per tenant that overrides a source, because - a tenant with its own database is invisible to the shared sweep (:36-40).

        + a tenant with its own database is invisible to the shared sweep (:36-40). The sweeps do not each + assemble that list themselves: FrameworkTableTargets + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/FrameworkTableTargets.cs:32) + is the one answer to "which databases hold the framework's own relational tables on this host", shared + by the outbox and internal-command processors, both cleanup services, both operator surfaces and the + audit-trail sweep (:14-19). Relational() returns every relational source backing a registered + entity, deduplicated and expanded per tenant (:43-44), and its overload adds the source the caller's + own settings name as the table's home when that engine is relational (:54-60). It is recomputed per + call, so late-loading module assemblies are seen, and Cosmos sources are skipped because they host none + of those tables (:21-22).

        Recording what changed, the audit trail

        AuditTrailSaveChangesInterceptor (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:63) @@ -631,7 +657,7 @@

        Repositories, specific IRowVersioned (:75-93, ADR-035). Two set-based escape hatches sit beside the tracked path: ExecuteDeleteAsync, which the interface itself documents as - bypassing domain events, audit stamps, and soft-delete (IRepository.cs:445-455), and + bypassing domain events, audit stamps, and soft-delete (IRepository.cs:447-457), and ExecuteUpdateAsync (:457-479), the contention-proof conditional update whose guard predicate lets the database arbitrate two racing callers with no rowversion retry loop. The latter is described through the persistence-agnostic @@ -639,7 +665,7 @@

        Repositories, specific builder by UpdatePropertySetterBuilder<TEntity> (.../Repositories/UpdatePropertySetterBuilder.cs:14), which is what keeps EF Core out of the Application layer, and because ExecuteUpdate bypasses the interceptor pipeline the repository stamps - LastModifiedOn/By itself unless the caller assigned them (EFRepository.cs:140-151).

        + LastModifiedOn/By itself unless the caller assigned them (EFRepository.cs:140-154).

        The read repository does not compose queries by hand. SpecificationEvaluator (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/SpecificationEvaluator.cs:21) turns an ISpecification<TEntity, TIdentifierType> @@ -657,7 +683,7 @@

        Repositories, specific requested sort property or fails validation (:35), orders by (sortKey, Id) with the identifier tie-break that makes the order total (:50-51), and builds the composite seek predicate against the last row of the previous page (:109), so GetPageByCursorAsync - (IRepository.cs:316, implemented at EFReadRepository.cs:617) seeks straight to the boundary instead + (IRepository.cs:318, implemented at EFReadRepository.cs:617) seeks straight to the boundary instead of counting past every skipped row. Exactly one sort key is supported, by design (KeysetQueryBuilder.cs:17-20). That is [Rubric §12, Performance and Scalability] expressed as a contract rather than as advice.

        @@ -669,56 +695,64 @@

        Repositories, specific when UseMiniProfiler is on (:34-38, :58-62), adding timing without the base repository knowing, and it activates both through a cached compiled ObjectFactory rather than reflecting on every call (:70-84). DbContextFactory - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:46) + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:48) is the scoped coordinator: it caches one ApplicationDbContext per - DataSourceKey so every repository in a scope shares one change tracker (:87-118), - gives each new context a live tenant accessor rather than a copied value (:129-140), and enlists a - late-created context into an already-open transaction (:107-108). It is also the database-per-tenant + DataSourceKey so every repository in a scope shares one change tracker (:98-131), + gives each new context live scope accessors rather than copied values (:114, doc at :136), and enlists a + late-created context into an already-open transaction (:116-119). It is also the database-per-tenant routing point: when the scope's tenant overrides a source, the context is created against that tenant's connection string while keeping the original DataSourceKey, which is what lets one compiled model - serve every tenant's database (:143-173), and a cached routed context is refused to a second tenant - rather than silently serving the first tenant's rows (:176-198). Its save loop runs up to - MaxSavePasses (3, :53) passes over the cached contexts, because dispatching events in-process can - materialize a context for a source nobody had touched yet (:237-251), and it closes with a hard + serve every tenant's database (ResolveTenantOverride, :162-168), and a cached routed context is refused to a second tenant + rather than silently serving the first tenant's rows (:123-124, guard at :196-210), just as a shared + context created before the tenant resolved is refused to a tenant that overrides that source + (:126-128, :228-231). Its save loop runs up to + MaxSavePasses (3, :63) passes over the cached contexts, because dispatching events in-process can + materialize a context for a source nobody had touched yet (:278-296), and it closes with a hard assertion: any context still reporting ChangeTracker.HasChanges() when the unit of work returns throws - rather than silently discarding those changes (:259-269).

        -

        Because there can be more than one physical source in play, ExecuteInTransactionAsync (:499-544) + rather than silently discarding those changes (:298-315).

        +

        Because there can be more than one physical source in play, ExecuteInTransactionAsync (:534) runs the operation under the first transactional context's execution strategy, opens a transaction per - source, and commits them sequentially with no two-phase commit (TryCommit at :621-660); + source, and commits them sequentially with no two-phase commit (TryCommit at :697); cross-source consistency is the outbox's job, and the doc comment is explicit that a commit failure on - the second source leaves the first one committed (:488-498). The method is re-entrant: a nested call + the second source leaves the first one committed (:522-532). The method is re-entrant: a nested call joins the ambient transaction instead of opening a second one, so only the outermost call may begin, - commit, roll back, or flush (:503-511). A returned failed - Result rolls back exactly like an exception (:563-570), + commit, roll back, or flush (:538-546). A returned failed + Result rolls back exactly like an exception (:598-604), which is what makes ADR-013's Result-over-exceptions rule safe for partial persistence; rollback also drops the deferred event - dispatch (:443-447), the deferred flush runs only after every commit has succeeded (:576-584), and a + dispatch (:600-603), the deferred flush runs only after every commit has succeeded (:614-621), and a retry resets the change tracker first so the aborted attempt's Added entities are not inserted twice - (ResetForRetry at :742-751). DbContextFactory further carries the - SET IDENTITY_INSERT machinery (IdentityInsertGroup at :405, the per-table - save split at :284-403) for importing entities with explicit database-generated ids one table at a - time, and the MigrateAsync / HasPendingMigrationsAsync sweeps (:686-739) over every source whose - resolved PhysicalDataSource.UsesMigrations says a migrations pipeline owns - its schema (PhysicalDataSource.cs:41-47, target selection at DbContextFactory.cs:724-743).

        + (ResetForRetry at :814-821, called at :564). Before committing, the unit also refuses to discard + work: anything still tracked but unsaved throws, except an internal-command row that was only + enrolled on the context while the transaction was open, which is saved inside the transaction so it + commits with the change that scheduled it (FlushEnrolledCommandsBeforeCommitAsync, :649-680, called + at :608). DbContextFactory further carries the engine-neutral half of the explicit-key insert + (RequestExplicitKeyInsert at :321, SaveWithExplicitKeyInsertAsync at :329): when one is requested + and the context's engine has an IExplicitKeyInsertDialect, the save is + split into one ExplicitKeyInsertGroup round per table with the toggle on + (:262-266, :292-294), for importing entities with explicit database-generated ids. It also runs the + MigrateAsync / HasPendingMigrationsAsync sweeps (:762, :798) over every source whose resolved + PhysicalDataSource.IsMigrationTarget holds: the source UsesMigrations under + its engine's MigrationPolicy, and it is not an optional source left without a + connection string (PhysicalDataSource.cs:42-60, target selection at DbContextFactory.cs:782-795).

        UnitOfWork sits on top, resolving an entity's physical source through IDataSourceService, handing the matching context to the factory, and caching the resulting repository per closed generic interface type (UnitOfWork.cs:33-66). The physical creation itself runs through PhysicalDbContextFactory - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/PhysicalDbContextFactory.cs:16), - a singleton that switches on the key's engine to construct the right context class (:41-48) and whose + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/PhysicalDbContextFactory.cs:19), + a singleton that hands construction to the key's engine through IDataSourceEngine.CreateDbContext (:32) and whose doc comment warns it must never be pooled, because each instance carries per-source constructor - state that pooling would smear across databases (:10-14). The interfaces + state that pooling would smear across databases (:13-17). The interfaces (IUnitOfWork, IDbContextFactory, IPhysicalDbContextFactory, IRepositoryFactory) keep the application layer talking to abstractions. Every member of that factory family has its own - section below, including the two types that exist only because of what the coordinator does: - IdentityInsertGroup, the per-table batch the SET IDENTITY_INSERT loop saves - one at a time, and TransactionCommitAmbiguousException + section below, including the type that exists only because of what the coordinator does, + TransactionCommitAmbiguousException (.../Factory/TransactionCommitAmbiguousException.cs:22), which the commit path raises when the commit itself fails with an outcome nobody can vouch for, naming each physical source's outcome (committed, ambiguous, or rolled back) so the partial state is observable rather than inferred (:57-70, - DbContextFactory.cs:667-672). That exception is thrown outside the execution strategy on purpose - (DbContextFactory.cs:554-560), because the strategy walks an exception's whole inner chain to decide + DbContextFactory.cs:697-722). That exception is thrown outside the execution strategy on purpose + (DbContextFactory.cs:575-576), because the strategy walks an exception's whole inner chain to decide retriability and would otherwise re-run the operation on top of a possibly-durable commit.

        Routing an entity to its database

        The heart of ADR-006 is that every @@ -729,25 +763,28 @@

        Routing an entity to its database

        Name is a physical database name defaulting to "Default" (DataSourceKey.cs:18-23). Two layers compute this. DataSourceResolver - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:15), + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:16), the IDataSourceResolver singleton (.../DataSources/IDataSourceResolver.cs:15), builds the logical-to-physical map once per engine from configuration and hands out the resolved - PhysicalDataSource (:93-107, :143-148). Named sources with no connection + PhysicalDataSource (BuildEngineMap at :194, ResolveLogical at :92, GetPhysical at :182). Named sources with no connection string, or whose connection identity equals the top-level one, collapse onto the Default source, so a host with no DataSources section behaves exactly like a single-database monolith - (DataSourceResolver.cs:260-305), and sources sharing a connection identity collapse onto one canonical - key named after their alphabetically-first member (:343-370, canonical name at :353). What the + (DataSourceResolver.cs:294-344), and sources sharing a connection identity collapse onto one canonical + key named after their alphabetically-first member (:380-411, canonical name at :386). What the Default source is built from is itself resolved rather than assumed: the top-level ConnectionStrings section first, and when it names nothing for this engine while DataSources names exactly one database on it, that database becomes Default, which is what keeps the framework's own - tables working in a host that declares its database only under DataSources (:179-236, captured in - the private DefaultSeed record at :177). Identity is the connection string compared - ordinally, with the Cosmos database name appended because one account hosts many databases (:467-476). + tables working in a host that declares its database only under DataSources (:219-285, captured in + the private DefaultSeed record at :216). Identity is the connection string compared + ordinally, with the database name appended on an engine whose ConnectionIdentityIncludesDatabaseName + is set (Cosmos, because one account hosts many databases, :494-495). The resolver fails fast when two logical names collapsing to one database declare conflicting - migrations assemblies (:433-465) and logs a warning when a separate SQL Server source falls back to - the Default migrations assembly (:360-368, message at :499). It also substitutes the host's own + migrations assemblies (:464-480) and logs a warning when a separate source on an Always + MigrationPolicy engine (SQL Server) falls back to + the Default migrations assembly (:393, message at :508). It also substitutes the host's own engine for a request naming an engine the host does not configure, in a fixed relational-first - preference order (:29-30, applied at :97 and :109-129), because every table the framework owns is relational and honoring + preference order ranked by each engine's SubstitutionPriority (:35-36, applied through + SubstituteUnconfiguredEngine at :127), because every table the framework owns is relational and honoring an unconfigured engine literally handed those components an empty connection string.

        What the resolver reads is two bound settings objects. ConnectionStringSettings @@ -791,27 +828,92 @@

        Routing an entity to its database

        (:163-176). DataSourceService - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceService.cs:11) is the thin + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceService.cs:12) is the thin application-facing facade over IEntityDataSourceRegistry, and it answers the one question navigation loading needs: two entities support EF .Include() only when their physical - keys are equal and the engine is not Cosmos (DataSourceService.cs:30-31).

        + keys are equal and the engine is relational (DataSourceService.cs:32).

        +

        One registry answers every engine question

        +

        Four engines would otherwise mean a four-way switch in every component that maps a key, quotes a + column, picks a migrations assembly or decides whether a source has an outbox. The framework keeps + exactly one place that knows engines apart. IDataSourceEngine + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/IDataSourceEngine.cs:18) + is the internal contract, and its doc comment states the goal: no call site branches on + DataSource (:9-16). Its members fall into three groups. The descriptive ones say how + the engine is configured: its rank when the resolver substitutes it for an unconfigured engine (:29), + the open generic entity-configuration interface whose implementations belong to its model (:32), the + setting that names its migrations assembly (:38), whether connection identity includes the database + name (:44), and how to read its connection string and migrations assembly from the top-level section, + a named entry or a tenant override (:49-69). The capabilities are one value (:74). The behavior hooks + are the few places the engine has to act rather than answer: build its sealed context over one physical + source, never pooled (:83-86), apply its table or container mapping and key generation to one entity + (:95-97), add covering index columns (:103), quote a column and build the soft-delete index + predicate (:108-113), and expose an explicit-key insert dialect, or none (:119).

        +

        DataSourceEngines (.../Engines/DataSourceEngines.cs:19) is the registry: a + static class holding the four engines (:22-28) in a frozen dictionary keyed by the shipped + DataSource value (:30-31), whose For throws on an unregistered value (:40-43). It is static + rather than a DI service on purpose: several consumers run where no container is reachable (the + model-building conventions, EntityTypeConfiguration.ApplyEngineConventions, SoftDeleteFilterSql, + IndexBuilderExtensions), and the engines are stateless facts about a closed enum only Common can + extend, so a DI extension point would buy no extensibility; adding a fifth engine is one class plus one + line (:6-18). The four implementations are + SQLServerDataSourceEngine (.../Engines/SQLServerDataSourceEngine.cs:19), + PostgreSQLDataSourceEngine (.../Engines/PostgreSQLDataSourceEngine.cs:17), + SqliteDataSourceEngine (.../Engines/SqliteDataSourceEngine.cs:16) and + CosmosDataSourceEngine (.../Engines/CosmosDataSourceEngine.cs:18), with + substitution priorities 0, 1, 2 and 3 (SQLServerDataSourceEngine.cs:29, + PostgreSQLDataSourceEngine.cs:27, SqliteDataSourceEngine.cs:26, CosmosDataSourceEngine.cs:28), + which is the relational-first order the resolver substitutes in.

        +

        What differs between engines is a DataSourceEngineCapabilities record + (.../Engines/DataSourceEngineCapabilities.cs:23) of five values (:24-28). Only facts whose values + genuinely differ get a member, and everything that splits the engines exactly along the relational line + is the single IsRelational flag: same-source .Include(), transactions, raw parameterized SQL, + indexes, foreign keys whose delete behavior matters, Any(predicate) translation and the framework's + own relational tables (:3-20). That one flag is what the outbox capture, the transaction coordinator, + the conventions and the .Include() check read (DomainEventSaveChangesInterceptor.cs:236, + DbContextFactory.cs:827-828, SoftDeleteUniqueIndexConvention.cs:43, DataSourceService.cs:32). + MigrationPolicy (.../Engines/MigrationPolicy.cs:8) decides whether a source is + migrated or created outright: Never for Cosmos, WhenAssemblyConfigured for SQLite and PostgreSQL, + and Always for SQL Server, where a named source with no assembly falls back to the Default source's + with a warning (:10-20; values at SQLServerDataSourceEngine.cs:42, PostgreSQLDataSourceEngine.cs:40, + SqliteDataSourceEngine.cs:39, CosmosDataSourceEngine.cs:42). + RowVersionStrategy (.../Engines/RowVersionStrategy.cs:8) decides the + concurrency token: None on Cosmos, StoreGenerated (a rowversion, never stamped) on SQL Server, and + ClientStamped on PostgreSQL and SQLite, where the audit interceptor writes it (:10-17). + ConnectionStringRequired is true only for SQL Server, so a SQL Server source with no connection string + is a startup failure rather than an optional source to skip (DataSourceEngineCapabilities.cs:10-13, + SQLServerDataSourceEngine.cs:43), and NullsSortFirstAscending is false only for PostgreSQL, which + keyset pagination has to know (DataSourceEngineCapabilities.cs:21, PostgreSQLDataSourceEngine.cs:43).

        +

        The explicit-key insert shows the split between engine-specific and engine-neutral work most clearly. + IExplicitKeyInsertDialect (.../Engines/IExplicitKeyInsertDialect.cs:13) + is the engine half, and only an engine that refuses an explicit value for a store-generated key without a + session toggle has one: SQL Server, whose engine class implements the dialect itself + (SQLServerDataSourceEngine.cs:19, :49) and emits SET IDENTITY_INSERT [schema].[table] ON or OFF + (:163). Every other engine returns null and the save runs unchanged + (PostgreSQLDataSourceEngine.cs:47, SqliteDataSourceEngine.cs:46, CosmosDataSourceEngine.cs:49). The + dialect finds the added entries carrying explicit store-generated keys, grouped by table + (IExplicitKeyInsertDialect.cs:21), each group an ExplicitKeyInsertGroup + record of schema, table and entries (.../Engines/ExplicitKeyInsertGroup.cs:12), and builds the toggle + statement (IExplicitKeyInsertDialect.cs:28); grouping the rounds, hiding other tables' entries, + excluding their domain events and pinning the connection stay in DbContextFactory + (:5-12). Every engine type is internal, so a consumer never sees one: the public surface is still the + DataSource value on an attribute, and this registry is what turns that value into behavior.

        Three model-finalizing conventions and the identifier mapping

        The base context adds all three of its conventions in ConfigureConventions - (ApplicationDbContext.cs:373-394), and each exists because a cross-cutting policy above would + (ApplicationDbContext.cs:378-399), and each exists because a cross-cutting policy above would otherwise produce an invalid or surprising model. CrossDataSourceDegradeConvention - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:33, - added at ApplicationDbContext.cs:382) is what lets routing be lazy and attribute-driven and still + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:34, + added at ApplicationDbContext.cs:387) is what lets routing be lazy and attribute-driven and still produce a valid EF model. When a relationship's two ends live in different physical sources it removes the foreign key (a database cannot enforce an FK into another database), keeps the declared scalar FK columns plus a compensating index unless an existing index already covers them as a prefix, ignores the CLR navigation members, and drops the foreign entity types out of this database's model entirely - (CrossDataSourceDegradeConvention.cs:38-89, :110-140). It works through EF's mutable model API + (CrossDataSourceDegradeConvention.cs:39-90, :110-140). It works through EF's mutable model API rather than convention builders, because the soft-delete and concurrency helpers have already promoted every entity type to the Explicit configuration source (:22-24, :44-46), it eagerly drops the convention-created FK index before the coverage check so the column does not end up unindexed (:126-131), and it skips the compensating index on Cosmos, which auto-indexes everything and rejects - explicit index definitions (:65, :118-121). Runtime navigation across sources then flows through the + explicit index definitions, which the convention reads as the engine's IsRelational capability (:66). Runtime navigation across sources then flows through the INavigationPopulator<in TEntity> batch-loading machinery in Group 11. Crucially, when every entity collapses onto one physical source (the monolith case) nothing is foreign and the convention returns @@ -819,19 +921,19 @@

        Three mod the same codebase run as a monolith today and as split services later without a rewrite, the core [Rubric §7, Microservices Readiness] claim.

        SoftDeleteUniqueIndexConvention - (.../Conventions/SoftDeleteUniqueIndexConvention.cs:33, added at ApplicationDbContext.cs:387) closes + (.../Conventions/SoftDeleteUniqueIndexConvention.cs:34, added at ApplicationDbContext.cs:392) closes a smaller but sharper hole. Soft-delete hides a row from queries, but a plain unique index still enforces uniqueness against it, so "deleting" a speaker would permanently block re-creating one with the same email. The convention adds an IsDeleted = 0 filter to every unique index on a soft-deletable entity and extends rather than skips a hand-authored filter, appending its clause with AND so an index already narrowed on something else keeps its own predicate and still stops enforcing uniqueness against soft-deleted rows; a filter that already constrains the soft-delete column is left exactly as it is, so - the append is idempotent (SoftDeleteUniqueIndexConvention.cs:36-81). It is a no-op for Cosmos - (:41-42). The predicate text itself is not built inline: both this convention and the opt-in - HasSoftDeleteFilter extension go through SoftDeleteFilterSql.Build - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:27-36), which - reads the column name from the model and quotes it per engine (brackets for SQL Server, double quotes for - SQLite, null for Cosmos), with a normalized comparison for the already-present check (:53-56), so the + the append is idempotent (SoftDeleteUniqueIndexConvention.cs:36-81). It is a no-op for Cosmos + (:43, any non-relational engine). The predicate text itself is not built inline: both this convention and the opt-in + HasSoftDeleteFilter extension go through SoftDeleteFilterSql.Build + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:17, Build at :35), which + reads the column name from the model and asks the engine for the predicate and the quoting + (IDataSourceEngine.BuildSoftDeleteFilter and QuoteColumn, :35, :70; null where filtered indexes are unsupported, which is Cosmos), with a normalized comparison for the already-present check (:57-58), so the automatic and the hand-authored path can never disagree. IndexBuilderExtensions (.../Configuration/IndexBuilderExtensions.cs:10) is that opt-in half, an extension(IndexBuilder) block for the case the convention deliberately leaves @@ -840,8 +942,8 @@

        Three mod model finalization, after module configurations have declared their indexes and after EF's own relationship discovery, which is why they can see the finished picture.

        The third convention is RestrictDeleteByDefaultConvention - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/RestrictDeleteByDefaultConvention.cs:41, - added last of the three at ApplicationDbContext.cs:394 so it never stamps a relationship the + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/RestrictDeleteByDefaultConvention.cs:42, + added last of the three at ApplicationDbContext.cs:399 so it never stamps a relationship the cross-source convention has already removed, ADR-119). EF's own default is the opposite posture: a required relationship cascades, so deleting a parent silently deletes its @@ -856,12 +958,12 @@

        Three mod finalized model can be audited without re-running the convention, and DeleteBehaviorConventionTestsBase reads exactly that annotation to assert every cascading relationship was opted into on purpose (:21-31). Like its siblings it is a no-op for Cosmos, which has no foreign key constraints to - restrict and where stamping a decision the provider cannot enforce would only make the audit lie - (:33-37).

        + restrict and where stamping a decision the provider cannot enforce would only make the audit lie + (:33-37, checked at :66).

        ConfigureConventions ends with the one piece that is not a convention at all: StronglyTypedIdModelConfiguration.Apply (.../Persistence/Conventions/StronglyTypedIdModelConfiguration.cs:21, called at - ApplicationDbContext.cs:396-404, + ApplicationDbContext.cs:401-409, ADR-115). A host that calls AddStronglyTypedIds registers a StronglyTypedIdRegistry, and this declares one pre-convention type mapping per declared identifier, so every property of that type maps to the @@ -869,7 +971,7 @@

        Three mod columns alike, on SQL Server, PostgreSQL, SQLite and Cosmos, because it runs on the one base context (:7-20). Absent the registry it is a no-op, which is the default posture: the primitive identifier aliases stay the identifier model and no existing entity changes - (ApplicationDbContext.cs:396-404). Pre-convention is the load-bearing word. The actual conversion is + (ApplicationDbContext.cs:401-409). Pre-convention is the load-bearing word. The actual conversion is StronglyTypedIdValueConverter<TSelf, TValue> (.../Persistence/Conversions/StronglyTypedIdValueConverter.cs:28), with NullableStronglyTypedIdValueConverter<TSelf, TValue> @@ -882,7 +984,7 @@

        Three mod

        Entity configuration and engine portability

        Concrete entity configurations derive from the engine-aware EntityTypeConfiguration<TEntity, TIdentifierType> - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:29) + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:27) or, more commonly, one of the fixed-engine shims like EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> (.../EntityTypeConfigurationSQLServer.cs:17), which is that base annotated @@ -893,12 +995,13 @@

        Entity configuration and en (.../EntityTypeConfigurationCosmos.cs:18) and EntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType> (.../EntityTypeConfigurationPostgreSQL.cs:22) as its three siblings. The base reads the attribute off its own - runtime type and throws a clear error when it is missing (EntityTypeConfiguration.cs:45-48), then - applies the engine's conventions in ApplyEngineConventions (:57-99): SQL Server gets a table in a - module schema (:65-72), SQLite a plain table (:74-81), and Cosmos a per-module container with the - entity id as partition key (:83-94), each mapping key generation according to the entity's + runtime type and throws a clear error when it is missing (EntityTypeConfiguration.cs:43-46), then + applies the engine's conventions in ApplyEngineConventions (:69-77), which hands the mapping to the + engine's ApplyKeyAndTableMapping hook (:77): SQL Server gets a table in a + module schema (SQLServerDataSourceEngine.cs:94-103), SQLite a plain table, and Cosmos a per-module container with the + entity id as partition key (CosmosDataSourceEngine.cs:86-99), each mapping key generation according to the entity's EntityTypeExtensions.IsIdValueGenerated - marker (:61): ValueGeneratedOnAdd for database identity, ValueGeneratedNever otherwise, or the + marker (SQLServerDataSourceEngine.cs:103, CosmosDataSourceEngine.cs:98-99): ValueGeneratedOnAdd for database identity, ValueGeneratedNever otherwise, or the CosmosIntIdValueGenerator for Cosmos, which has no server-side identity and increments a process-level counter seeded from the Unix timestamp (.../ValueGenerators/CosmosIntIdValueGenerator.cs:16-25). Because the engine is a single attribute and @@ -911,7 +1014,7 @@

        Entity configuration and en all over the common IEntityTypeConfigurationBase<TEntity, TIdentifierType>), moving an entity between engines is a one-line attribute change with no configuration-body edits - (EntityTypeConfiguration.cs:11-25). The shared + (EntityTypeConfiguration.cs:9-23). The shared EntityTypeConfigurationBase<TEntity, TIdentifierType> (.../EntityTypeConfigurationBase.cs:19) handles the one universal concern: excluding the in-memory DomainEvents collection from mapping (:25-32). Value objects reach the database through this layer @@ -936,7 +1039,7 @@

        Entity configuration and en

        Discovery runs through ModelBuilderExtensions.ApplyAllConfigurations (.../DbContexts/ModelBuilderExtensions.cs:10, an extension(ModelBuilder) block at :12), which the base calls with an entity filter so each database's model receives only its own entities - (ApplicationDbContext.cs:950-978, filter application at ModelBuilderExtensions.cs:57-60), over the + (ApplicationDbContext.cs:953-987, filter application at ModelBuilderExtensions.cs:57-60), over the assemblies supplied by IEntityConfigurationAssemblyProvider and its DefaultEntityConfigurationAssemblyProvider implementation (.../Persistence/DefaultEntityConfigurationAssemblyProvider.cs:12), which scans loaded @@ -994,7 +1097,7 @@

        Two answers and because a collision across users would let one account's token validate against another's session (:44-49, :60-66), plus a (UserId, RevokedAt) index for the per-user family questions the session cap, reuse detection and sign-out-everywhere all ask (:68-71). The base context calls it from - the gated ConfigureRefreshSessions (ApplicationDbContext.cs:889-897) precisely because a downstream + the gated ConfigureRefreshSessions (ApplicationDbContext.cs:892-900) precisely because a downstream app runs on the sealed engine contexts and has no OnModelCreating of its own to override. EFRefreshSessionStore (.../Auth/EFRefreshSessionStore.cs:30) is the IRefreshSessionStore implementation over that table; it @@ -1161,7 +1264,7 @@

        IChannelJoinAuthorizer

      • Concept introduced, closing the gap between a shape-valid channel key and an authorized one. [Rubric §11, Security] assesses whether an authorization decision that a hub cannot make structurally (any well-formed key like event:42 passes the configured pattern) is pushed out to a pluggable check - rather than left implicit. The doc comment (IChannelJoinAuthorizer.cs:27-34) frames the contract as a + rather than left implicit. The doc comment (IChannelJoinAuthorizer.cs:18-25) frames the contract as a single yes/no gate: given the connection's principal and the requested channelKey, decide whether the connection may join. Because the parameter is optional and defaults to null on the consuming hub, an existing host that never registers an implementation keeps working unchanged and every authenticated @@ -1169,7 +1272,7 @@

        IChannelJoinAuthorizer

        user must register one deliberately.
      • Walkthrough: one method.
        • CanJoinAsync(ClaimsPrincipal? user, string channelKey, CancellationToken cancellationToken = default) - (IChannelJoinAuthorizer.cs:35-38): returns true to admit the connection to channelKey. user is the + (IChannelJoinAuthorizer.cs:26-29): returns true to admit the connection to channelKey. user is the connection's principal (the hub requires authentication, so this is never null in practice); channelKey is the caller-requested channel, already validated against the shape pattern before this check runs.
        @@ -1187,7 +1290,7 @@

        IChannelJoinAuthorizer

        LiveChannelJoinAuthorizer (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Live/LiveChannelJoinAuthorizer.cs:43-45) is wired with AddScoped<IChannelJoinAuthorizer, LiveChannelJoinAuthorizer>() - (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:234).
      • + (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:231)
        .

      INativePushSender

      @@ -1225,9 +1328,9 @@

      INativePushSender

      native push an opt-in capability rather than a hard dependency of every host.
    301. Where it's used: injected into SendPushNotificationHandler - (MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:31) + (MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:36) alongside the SignalR sender. NullNativePushSender is registered by default with - TryAddTransient (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:320) and + TryAddTransient (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:330) and AzureNotificationHubNativePushSender replaces it when a hub is configured (DependencyInjection.Notifications.cs:98).
    302. @@ -1274,7 +1377,7 @@

      IPushDeviceRegistrar

      (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Notifications/DevicesController.cs:27) and passes the authenticated user id on both calls, UpsertAsync at DevicesController.cs:44 and DeleteAsync at DevicesController.cs:68. NullPushDeviceRegistrar is the default - registration (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:321); + registration (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:331); AzureNotificationHubDeviceRegistrar replaces it when a hub is configured (DependencyInjection.Notifications.cs:99). @@ -1385,10 +1488,10 @@

      IEntityConfigurationAssemblyProvid CosmosDbContext at CosmosDbContext.cs:18, SqliteDbContext at SqliteDbContext.cs:15); consumed by EntityDataSourceRegistry to build the entity-to-source map (EntityDataSourceRegistry.cs:22) and by - PhysicalDbContextFactory (PhysicalDbContextFactory.cs:19). The default + PhysicalDbContextFactory (PhysicalDbContextFactory.cs:22). The default implementation DefaultEntityConfigurationAssemblyProvider is registered with TryAddSingleton - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:58), and + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:59), and DesignTimeDbContextHelper substitutes its own nested ExplicitAssemblyProvider for dotnet ef runs (DesignTimeDbContextHelper.cs:193 and :185). @@ -1430,7 +1533,7 @@

      IQueryableExecutor

      silently reaching list endpoints.
    303. Where it's used: implemented by EFQueryableExecutor (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/EFQueryableExecutor.cs:11), registered with - TryAddSingleton (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:105). Injected + TryAddSingleton (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:111). Injected into EntityQueryPipeline (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/EntityQueryPipeline.cs:13) and into the framework's own notification handlers, for example @@ -1439,7 +1542,7 @@

      IQueryableExecutor

      IRawSqlQueryExecutor

      -

      MMCA.Common.Application · MMCA.Common.Application.Interfaces.Infrastructure.Persistence · MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRawSqlQueryExecutor.cs:23 · Level 0 · interface

      +

      MMCA.Common.Application · MMCA.Common.Application.Interfaces.Infrastructure.Persistence · MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRawSqlQueryExecutor.cs:24 · Level 0 · interface

      • What it is: an escape hatch for interpolated raw SQL reads, materializing rows into a scalar or an @@ -1455,13 +1558,18 @@

        IRawSqlQueryExecutor

        translate.
      • Walkthrough: two methods, both generic in the row shape T.
        • QueryAsync<T>(FormattableString sql, CancellationToken cancellationToken = default) - (IRawSqlQueryExecutor.cs:29-31): runs the statement and materializes every row into T, empty when + (IRawSqlQueryExecutor.cs:31): runs the statement and materializes every row into T, empty when none match.
        • QuerySingleOrDefaultAsync<T>(FormattableString sql, CancellationToken cancellationToken = default) - (IRawSqlQueryExecutor.cs:39-41): runs a statement expected to select at most one row, throwing - InvalidOperationException when the statement selected more than one (IRawSqlQueryExecutor.cs:35).
        • -
        • Both document NotSupportedException when the host's default data source is not a relational engine - (IRawSqlQueryExecutor.cs:27, :36), since raw SQL has no meaning against Cosmos DB.
        • + (IRawSqlQueryExecutor.cs:39): runs a statement expected to select at most one row, throwing + InvalidOperationException when the statement selected more than one (IRawSqlQueryExecutor.cs:38). +
        • Neither method documents a runtime "not relational" failure. The type-level remarks + (IRawSqlQueryExecutor.cs:18-21) move that check to registration instead: the executor is registered + only when the host's default physical data source is on a relational engine, so a Cosmos-default host + (its own query language, no SQL command surface to parameterize) has no registration at all, and a + service that injects this interface there fails when the container is validated. The gate is + DataSourceEngines.For(defaultEngine).Capabilities.IsRelational in + MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:409-411.
      • Why it's built this way: an interpolated FormattableString parameter, rather than a plain string @@ -1471,7 +1579,8 @@

        IRawSqlQueryExecutor

      • Where it's used: implemented by EFRawSqlQueryExecutor (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/EFRawSqlQueryExecutor.cs:2), registered - in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs. Exercised by the + conditionally as scoped in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:411 + (covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/DependencyInjectionInfrastructureTests.cs). Exercised by the architecture fitness base MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Cqrs/RawSqlConventionTestsBase.cs and its ADC concrete test MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Cqrs/RawSqlConventionTests.cs.
      • @@ -1515,9 +1624,9 @@

        IUniqueConstraintViolationDetector (SqlServerUniqueConstraintViolationDetector.cs:48-68) matching the two error numbers (:34 and :37) with a message fallback for engines that report the same rejection in words (:40, :43, matched at :63-64). It is registered with TryAddSingleton - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:115), deliberately TryAdd so a + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:121), deliberately TryAdd so a host on another engine can register its own implementation first and keep it - (DependencyInjection.cs:112-114). Consumers inject it into the when clause of a catch: + (DependencyInjection.cs:118-120). Consumers inject it into the when clause of a catch: GetOrCreateMyBadgeHandler (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/CheckIns/UseCases/GetOrCreateMyBadge/GetOrCreateMyBadgeHandler.cs:21 and :53), @@ -1540,7 +1649,7 @@

        IUpdatePropertySetter<TEntity>

        which properties change and to what, and Infrastructure translates the description into the provider's set-based UPDATE.
      • Depends on: System.Linq.Expressions (BCL, IUpdatePropertySetter.cs:1) only. It is the parameter type of - IWriteRepository.ExecuteUpdateAsync (IRepository.cs:476), + IWriteRepository.ExecuteUpdateAsync (IRepository.cs:478), which the doc comment cross-references (IUpdatePropertySetter.cs:7).
      • Concept introduced, describing a SET clause without leaking EF Core. [Rubric §3, Clean Architecture] assesses whether the technology choice stays outside the inner layers; @@ -1571,7 +1680,7 @@

        IUpdatePropertySetter<TEntity>

        property names (UpdatePropertySetterBuilder.cs:17 and :64) and exposes SetsProperty (UpdatePropertySetterBuilder.cs:49) so EFRepository<TEntity, TIdentifierType> can stamp LastModifiedOn and LastModifiedBy only when the caller did not - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:142-153), + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:142-156), keeping audit fields correct on a path that bypasses the save pipeline (EFRepository.cs:19).

      OutboxDeadLetter

      @@ -1604,10 +1713,10 @@

      OutboxDeadLetter

      free, and keeping it in the Application layer beside its interface means an admin surface can render dead letters without referencing the EF entity or the Infrastructure assembly.
    304. Where it's used: built by the Infrastructure implementation's projection - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:91), from + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:86), from rows matching the dead-letter predicate ProcessedOn == null && RetryCount >= maxRetries - (OutboxAdministration.cs:87), collected across sources and returned oldest first - (OutboxAdministration.cs:106).
    305. + (OutboxAdministration.cs:82), collected across sources and returned oldest first + (OutboxAdministration.cs:101).

      DataSourceKey

      @@ -1735,15 +1844,15 @@

      IOutboxAdministration

      keeps a bad source name on the same rails as any other user error. The retention sweep that would otherwise be a dead letter's only exit is OutboxCleanupService, whose own comment points at IOutboxAdministration.ReplayDeadLettersAsync as the alternative - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxCleanupService.cs:147). + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxCleanupService.cs:142).
    306. Where it's used: implemented by OutboxAdministration (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Administration/OutboxAdministration.cs:35-42), - registered with TryAddScoped (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:205-206), + registered with TryAddScoped (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:215-216), scoped because it creates one child scope per data source it visits and holds no state of its own - (DependencyInjection.cs:217-218). The implementation caps one page at 500 rows so an admin call cannot ask - for the whole table at once (OutboxAdministration.cs:45), rejects a negative skip or an out-of-range - take as validation errors (OutboxAdministration.cs:47-51), and expresses replay as one set-based - UPDATE per target (OutboxAdministration.cs:148-149) rather than as loaded entities.
    307. + (DependencyInjection.cs:227-228). The implementation caps one page at 500 rows so an admin call cannot ask + for the whole table at once (OutboxAdministration.cs:40), rejects a negative skip or an out-of-range + take as validation errors (OutboxAdministration.cs:42-46), and expresses replay as one set-based + UPDATE per target (OutboxAdministration.cs:143-144) rather than as loaded entities.
    308. Caveats / not-in-source: the framework registers the service but ships no controller over it. No MMCA.Common.API endpoint and no ADC or Store call site references ListDeadLettersAsync, ReplayDeadLettersAsync, or CountPendingAsync today; outside the registration the only callers in the @@ -1762,21 +1871,21 @@

      IEntityQuerier<TEntity, TIdent
    309. Depends on: AuditableBaseEntity<TIdentifierType> (the TEntity constraint, IRepository.cs:81), BaseLookup<TIdentifierType> (the lookup - projection, from MMCA.Common.Shared.DTOs, IRepository.cs:5 and :222), + projection, from MMCA.Common.Shared.DTOs, IRepository.cs:5 and :224), ISpecification<TEntity, TIdentifierType> - (from MMCA.Common.Domain.Interfaces, IRepository.cs:3, first used at :149), + (from MMCA.Common.Domain.Interfaces, IRepository.cs:3, first used in a signature at :151), Result with KeysetPageRequest and KeysetCollectionResult<T> (from - MMCA.Common.Shared.Abstractions, IRepository.cs:4, used at :316-317), and System.Linq.Expressions + MMCA.Common.Shared.Abstractions, IRepository.cs:4, used at :318-319), and System.Linq.Expressions (IRepository.cs:1).
    310. Concept introduced, the ISP-split repository ladder. [Rubric §1, SOLID] assesses whether clients depend only on the members they use. IRepository.cs defines a deliberate ladder of ever-wider interfaces so a handler declares exactly the surface it needs: IEntityReader (by-id lookups, IRepository.cs:21), IEntityQuerier (set-returning reads, this type, :80), IReadRepository (reader plus querier plus raw IQueryable, - :330), IWriteRepository (mutations, :367), and - IRepository (read plus write, :467). The doc comment says it + :332), IWriteRepository (mutations, :369), and + IRepository (read plus write, :495). The doc comment says it outright (IRepository.cs:68-71): prefer this over IReadRepository when a handler needs GetAllAsync, GetProjectedAsync, or CountAsync. [Rubric §12, Performance & Scalability] also applies: every member here exists so the database answers the question. GetProjectedAsync<TResult> takes an @@ -1793,11 +1902,11 @@

      IEntityQuerier<TEntity, TIdent each parameter that carries it (for example IRepository.cs:99-103 and :126-130). That is enforced downstream, where EFReadRepository<TEntity, TIdentifierType> passes a one-element filter-name array to EF's named IgnoreQueryFilters overload - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:40, used at + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:41, used at :52, :81, :102, :198, :288, :365, :379, and :445-446) rather than EF's parameterless form; its - own comment (EFReadRepository.cs:37) spells out that dropping both would let a caller asking to see deleted + own comment (EFReadRepository.cs:38) spells out that dropping both would let a caller asking to see deleted rows silently read every tenant's data. The two named filters come from - ApplicationDbContext (ApplicationDbContext.cs:469 and :391).

    311. + ApplicationDbContext (ApplicationDbContext.cs:474 and :391).

    312. Walkthrough
      • GetAllAsync(IEnumerable<string> includes, where?, orderBy?, select?, asTracking, ignoreQueryFilters, CancellationToken) (IRepository.cs:85-92): the general collection read with optional includes, filter, ordering, and same-type @@ -1806,63 +1915,63 @@

        IEntityQuerier<TEntity, TIdent
      • GetProjectedAsync<TResult>(select, where?, asTracking, ignoreQueryFilters, CancellationToken) (IRepository.cs:105-110): SQL-side projection to an arbitrary result type.
      • FirstOrDefaultAsync(where, includes?, asTracking, ignoreQueryFilters, CancellationToken) - (IRepository.cs:133-138): the single-row predicate read. The remarks (IRepository.cs:115-122) name the + (IRepository.cs:134-139): the single-row predicate read. The remarks (IRepository.cs:115-122) name the alternative it exists to remove, GetAllAsync followed by an in-memory FirstOrDefault, which materializes the whole matching set to keep one entity, and warn that no ordering is applied, so "first" is whatever the provider returns first.
      • FirstOrDefaultAsync(ISpecification<TEntity, TIdentifierType> specification, CancellationToken) - (IRepository.cs:148-150): the deterministic counterpart. Unlike the predicate overload it honors the + (IRepository.cs:150-152): the deterministic counterpart. Unlike the predicate overload it honors the specification's ORDERING, so "first" means what the specification says it means - (IRepository.cs:140-144).
      • -
      • CountByAsync<TKey>(keySelector, where?, CancellationToken) (IRepository.cs:167-171): a GROUP BY count, - returning one dictionary entry per key that has at least one row. The remarks (IRepository.cs:156-161) + (IRepository.cs:141-145).
      • +
      • CountByAsync<TKey>(keySelector, where?, CancellationToken) (IRepository.cs:169-173): a GROUP BY count, + returning one dictionary entry per key that has at least one row. The remarks (IRepository.cs:158-163) explain why the member has to exist at all: the Application layer references no EF Core, so a handler needing a grouped count has no IQueryable to group and would otherwise project every matching row out of - the database and group client-side. TKey is constrained notnull (IRepository.cs:171).
      • -
      • SumByAsync<TKey>(keySelector, sumSelector, where?, CancellationToken) (IRepository.cs:184-189): the - grouped SUM, the counterpart of CountByAsync (IRepository.cs:173-177).
      • -
      • FindIncludingDeletedAsync(where, includes?, asTracking, CancellationToken) (IRepository.cs:215-219): - the resurrection read (BR-135, IRepository.cs:198), returning a named tuple of Active and SoftDeleted - matches. The remarks (IRepository.cs:195-205) give the whole rationale: a create handler whose natural key + the database and group client-side. TKey is constrained notnull (IRepository.cs:173).
      • +
      • SumByAsync<TKey>(keySelector, sumSelector, where?, CancellationToken) (IRepository.cs:186-191): the + grouped SUM, the counterpart of CountByAsync (IRepository.cs:175-179).
      • +
      • FindIncludingDeletedAsync(where, includes?, asTracking, CancellationToken) (IRepository.cs:217-221): + the resurrection read (BR-135, IRepository.cs:199), returning a named tuple of Active and SoftDeleted + matches. The remarks (IRepository.cs:197-208) give the whole rationale: a create handler whose natural key already exists as a soft-deleted row must reactivate that row rather than insert a duplicate the unique index will reject, and it needs both halves of the answer in one round trip, since an active match is a conflict, a soft-deleted match is the row to bring back, and neither is a plain insert. The asTracking - parameter carries its own warning (IRepository.cs:209-212): a caller intending to reactivate wants true, + parameter carries its own warning (IRepository.cs:211-214): a caller intending to reactivate wants true, otherwise the reactivation saves nothing. See ADR-005 for the soft-delete policy this read serves.
      • GetAllForLookupAsync(string nameProperty, where?, asTracking, CancellationToken) - (IRepository.cs:222-226): returns lightweight + (IRepository.cs:224-228): returns lightweight BaseLookup<TIdentifierType> id/name pairs for dropdowns without materializing full entities. It has no ignoreQueryFilters parameter: a lookup list never offers deleted rows.
      • -
      • CountAsync(CancellationToken) (IRepository.cs:229) and - CountAsync(Expression<Func<TEntity, bool>> where, CancellationToken) (IRepository.cs:232-234): total and +
      • CountAsync(CancellationToken) (IRepository.cs:231) and + CountAsync(Expression<Func<TEntity, bool>> where, CancellationToken) (IRepository.cs:234-236): total and predicated counts.
      • CountAsync(ISpecification<TEntity, TIdentifierType> specification, CancellationToken) - (IRepository.cs:243-245): the specification-shaped count. The doc comment (IRepository.cs:236-239) states + (IRepository.cs:245-247): the specification-shaped count. The doc comment (IRepository.cs:238-241) states that ordering and paging on the specification are ignored deliberately, since a count of "page 3 of the matches" is never what a caller means.
      • ListAsync(ISpecification<TEntity, TIdentifierType> specification, CancellationToken) - (IRepository.cs:260-262): runs a specification and returns the matching entities. The remarks - (IRepository.cs:250-256) draw the line between the two specification shapes: a plain ISpecification + (IRepository.cs:262-264): runs a specification and returns the matching entities. The remarks + (IRepository.cs:252-258) draw the line between the two specification shapes: a plain ISpecification contributes its Criteria only, while a QuerySpecification<TEntity, TIdentifierType> also contributes includes, ordering, paging, tracking, and soft-delete scope, so one object describes the whole read instead of five loose arguments.
      • ListAsync<TResult>(specification, Expression<Func<TEntity, TResult>> select, CancellationToken) - (IRepository.cs:279-282): the projecting overload, so only the selected columns leave the database. The - remarks (IRepository.cs:268-273) pin the ordering rule: the projection is applied after the + (IRepository.cs:281-284): the projecting overload, so only the selected columns leave the database. The + remarks (IRepository.cs:270-275) pin the ordering rule: the projection is applied after the specification's ordering and paging, so a paged specification still pages over entity rows and projects only that page, and includes on the specification are redundant here but not harmful.
      • AnyAsync(ISpecification<TEntity, TIdentifierType> specification, CancellationToken) - (IRepository.cs:291-293): existence by specification, with ordering and paging ignored for the same reason - as the specification CountAsync (IRepository.cs:285-286).
      • + (IRepository.cs:293-295): existence by specification, with ordering and paging ignored for the same reason + as the specification CountAsync (IRepository.cs:287-288).

      • GetPageByCursorAsync(KeysetPageRequest request, ISpecification<TEntity, TIdentifierType>? specification = null, CancellationToken) - (IRepository.cs:316-319): one keyset ("seek") page plus the cursor for the next one, and the only member + (IRepository.cs:318-321): one keyset ("seek") page plus the cursor for the next one, and the only member here that returns a Result, because an unknown sort column and a malformed cursor are caller errors rather than exceptions and must never come back as a silent first page - (IRepository.cs:306-310). The remarks (IRepository.cs:299-311) state the trade in full: one index seek + (IRepository.cs:308-312). The remarks (IRepository.cs:301-313) state the trade in full: one index seek regardless of scroll depth and no skipped or repeated rows, against no random page access and no total count, with exactly one sort key supported and Id as the tie-break.
      @@ -1873,9 +1982,9 @@

      IEntityQuerier<TEntity, TIdent already depends on the querier from needing a second dependency to run a specification.

    313. Where it's used: query handlers needing collections, aggregates, specifications, or a keyset page; folded into IReadRepository<TEntity, TIdentifierType> - (IRepository.cs:331) and implemented concretely by + (IRepository.cs:333) and implemented concretely by EFReadRepository<TEntity, TIdentifierType> (for example - GetProjectedAsync at EFReadRepository.cs:74, CountByAsync at :127, SumByAsync at :150, the + GetProjectedAsync at EFReadRepository.cs:75, CountByAsync at :127, SumByAsync at :150, the projecting ListAsync at :464), with EFReadRepositoryDecorator<TEntity, TIdentifierType> wrapping every call in a MiniProfiler step (EFReadRepositoryDecorator.cs:17, and :45-83 for the newer @@ -1887,7 +1996,11 @@

      IEntityQuerier<TEntity, TIdent
    314. Caveats / not-in-source: GetProjectedAsync carries ignoreQueryFilters before the cancellation token (IRepository.cs:109-110), so any caller or test double that passes arguments positionally has to account for it; the compiler catches positional callers, but a mock configured with a fixed argument count does not fail - until run time.
    315. + until run time. The two FirstOrDefaultAsync overloads are public overloads that both carry optional + parameters, which the RS0026 public-API analyzer rule forbids; each is annotated with a targeted + [SuppressMessage("ApiDesign", "RS0026...")] (IRepository.cs:133 and :149) whose justification records + them as grandfathered: released after the v1.152 baseline while RS0026/RS0027 were off, so changing either + signature now would be a breaking change. A new overload on this interface does not inherit that exemption.

    316. IEntityReader<TEntity, TIdentifierType>

      @@ -1929,7 +2042,7 @@

      IEntityReader<TEntity, TIdentif reads clearly at the constructor and mocks in two lines in a test.
    317. Where it's used: command handlers that load an aggregate before mutating it; folded into IReadRepository<TEntity, TIdentifierType> - (IRepository.cs:331). Note that the GetByIdOrFailAsync extension on + (IRepository.cs:333). Note that the GetByIdOrFailAsync extension on ReadRepositoryExtensions, which turns a miss into a Result failure carrying Error.NotFound, hangs off IReadRepository rather than this interface and is implemented over GetAllAsync @@ -1938,91 +2051,91 @@

      IEntityReader<TEntity, TIdentif

      IReadRepository<TEntity, TIdentifierType>

      -

      MMCA.Common.Application · MMCA.Common.Application.Interfaces.Infrastructure.Persistence · MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:330 · Level 5 · interface

      +

      MMCA.Common.Application · MMCA.Common.Application.Interfaces.Infrastructure.Persistence · MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:332 · Level 5 · interface

        -
      • What it is: the full read surface over an entity, combining IEntityReader<TEntity, TIdentifierType> (by-id lookups) and IEntityQuerier<TEntity, TIdentifierType> (collections, projections, specifications, grouped aggregates, and keyset pages), and adding four IQueryable<TEntity> properties for handlers that need raw LINQ (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:330-346).

        +
      • What it is: the full read surface over an entity, combining IEntityReader<TEntity, TIdentifierType> (by-id lookups) and IEntityQuerier<TEntity, TIdentifierType> (collections, projections, specifications, grouped aggregates, and keyset pages), and adding four IQueryable<TEntity> properties for handlers that need raw LINQ (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:332-348).

      • -
      • Depends on: IEntityReader<TEntity, TIdentifierType> and IEntityQuerier<TEntity, TIdentifierType>, both declared in the same file and both listed as base interfaces (IRepository.cs:331); AuditableBaseEntity<TIdentifierType> as the TEntity constraint (IRepository.cs:332). Externals: BCL IQueryable<T> only. No EF Core type appears anywhere in the declaration, which is what keeps this interface legal in the Application layer.

        +
      • Depends on: IEntityReader<TEntity, TIdentifierType> and IEntityQuerier<TEntity, TIdentifierType>, both declared in the same file and both listed as base interfaces (IRepository.cs:333); AuditableBaseEntity<TIdentifierType> as the TEntity constraint (IRepository.cs:334). Externals: BCL IQueryable<T> only. No EF Core type appears anywhere in the declaration, which is what keeps this interface legal in the Application layer.

      • -
      • Concept, the composition point of the ISP ladder, and controlled IQueryable exposure. [Rubric §1, SOLID] assesses whether clients depend only on the members they use. IRepository.cs defines a ladder of ever-wider interfaces so a handler declares exactly the surface it needs: IEntityReader (five members, IRepository.cs:21), IEntityQuerier (fifteen members, IRepository.cs:80), this type (both of those plus four properties, twenty-four members in total, IRepository.cs:330), IWriteRepository (IRepository.cs:367), and IRepository (read plus write, IRepository.cs:493). The doc comment records a migration stance rather than a ban (IRepository.cs:322-327): existing code should continue using this interface, and new handlers can depend on the focused sub-interfaces for better ISP compliance.

        +
      • Concept, the composition point of the ISP ladder, and controlled IQueryable exposure. [Rubric §1, SOLID] assesses whether clients depend only on the members they use. IRepository.cs defines a ladder of ever-wider interfaces so a handler declares exactly the surface it needs: IEntityReader (five members, IRepository.cs:21), IEntityQuerier (fifteen members, IRepository.cs:80), this type (both of those plus four properties, twenty-four members in total, IRepository.cs:332), IWriteRepository (IRepository.cs:369), and IRepository (read plus write, IRepository.cs:495). The doc comment records a migration stance rather than a ban (IRepository.cs:324-329): existing code should continue using this interface, and new handlers can depend on the focused sub-interfaces for better ISP compliance.

        [Rubric §12, Performance & Scalability] assesses whether expensive query behavior is a deliberate choice. The four properties turn EF's tracking mode and query-splitting mode into a named decision at the call site: asking for TableNoTrackingSplitQuery is visible in review, where a plain DbSet would silently track every row and emit one cartesian join.

      • Walkthrough: this interface declares no methods of its own. Its whole body is four get-only IQueryable<TEntity> properties.

          -
        • Table (IRepository.cs:336): the base queryable with change tracking enabled, the shape a mutation path composes over. Implemented as the raw DbSet (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:511).
        • -
        • TableNoTracking (IRepository.cs:339): the no-tracking queryable, documented as best for read-only queries and implemented as Entities.AsNoTracking() (EFReadRepository.cs:514).
        • -
        • TableNoTrackingSingleQuery (IRepository.cs:342): no-tracking, pinned to a single SQL statement through AsSingleQuery() (EFReadRepository.cs:517).
        • -
        • TableNoTrackingSplitQuery (IRepository.cs:345): no-tracking in split-query mode, which avoids the cartesian explosion that collection includes cause, through AsSplitQuery() (EFReadRepository.cs:520). It is the same concern IQueryableExecutor addresses for callers that hold an IQueryable rather than a repository.
        • +
        • Table (IRepository.cs:338): the base queryable with change tracking enabled, the shape a mutation path composes over. Implemented as the raw DbSet (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:526).
        • +
        • TableNoTracking (IRepository.cs:341): the no-tracking queryable, documented as best for read-only queries and implemented as Entities.AsNoTracking() (EFReadRepository.cs:529).
        • +
        • TableNoTrackingSingleQuery (IRepository.cs:344): no-tracking, pinned to a single SQL statement through AsSingleQuery() (EFReadRepository.cs:532).
        • +
        • TableNoTrackingSplitQuery (IRepository.cs:347): no-tracking in split-query mode, which avoids the cartesian explosion that collection includes cause, through AsSplitQuery() (EFReadRepository.cs:535). It is the same concern IQueryableExecutor addresses for callers that hold an IQueryable rather than a repository.
        • The twenty inherited members come from the two base interfaces and are walked through on their own sections: five point-lookup members on IEntityReader, fifteen set-returning members on IEntityQuerier.
      • Why it's built this way: the framework needed one interface a query handler can take when it genuinely uses the whole read surface, without forcing every handler to take it. Keeping the composition free of new methods means the two halves stay independently declarable and independently mockable, and it gives the profiling decorator one surface to wrap. Naming the four queryables separately, rather than exposing a DbSet, is what makes tracking and split-query opt-in rather than accidental. The ladder itself is ADR-055.

      • -
      • Where it's used: obtained through IUnitOfWork.GetReadRepository<TEntity, TIdentifierType>() (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IUnitOfWork.cs:29), which is the sanctioned way to get one; implemented by EFReadRepository<TEntity, TIdentifierType> (EFReadRepository.cs:20-24) and wrapped in EFReadRepositoryDecorator<TEntity, TIdentifierType> (EFReadRepositoryDecorator.cs:17) by RepositoryFactory only when MiniProfiler is enabled (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/Factory/RepositoryFactory.cs:55-63). EntityQueryService<TEntity, TEntityDTO, TIdentifierType> derives its Repository property from the unit of work at construction (MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:88). It is also the receiver of the GetByIdOrFailAsync extension on ReadRepositoryExtensions, which turns a miss into a Result failure carrying Error.NotFound and is implemented over GetAllAsync rather than GetByIdAsync (MMCA.Common/Source/Core/MMCA.Common.Application/Extensions/ReadRepositoryExtensions.cs:12, :27, :34-44), so a handler that wants that convenience must take this wider interface rather than IEntityReader.

        +
      • Where it's used: obtained through IUnitOfWork.GetReadRepository<TEntity, TIdentifierType>() (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IUnitOfWork.cs:29), which is the sanctioned way to get one; implemented by EFReadRepository<TEntity, TIdentifierType> (EFReadRepository.cs:21-25) and wrapped in EFReadRepositoryDecorator<TEntity, TIdentifierType> (EFReadRepositoryDecorator.cs:17) by RepositoryFactory only when MiniProfiler is enabled (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/Factory/RepositoryFactory.cs:55-63). EntityQueryService<TEntity, TEntityDTO, TIdentifierType> derives its Repository property from the unit of work at construction (MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:88). It is also the receiver of the GetByIdOrFailAsync extension on ReadRepositoryExtensions, which turns a miss into a Result failure carrying Error.NotFound and is implemented over GetAllAsync rather than GetByIdAsync (MMCA.Common/Source/Core/MMCA.Common.Application/Extensions/ReadRepositoryExtensions.cs:12, :27, :34-44), so a handler that wants that convenience must take this wider interface rather than IEntityReader.

      • Caveats / not-in-source: a mutation path must not compose its query off TableNoTracking and then expect the save to persist, because one no-tracking source makes the whole composed query untracked. That constraint is not stated in this file; it follows from EF's tracking semantics.

      IWriteRepository<TEntity, TIdentifierType>

      -

      MMCA.Common.Application · MMCA.Common.Application.Interfaces.Infrastructure.Persistence · MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:367 · Level 5 · interface

      +

      MMCA.Common.Application · MMCA.Common.Application.Interfaces.Infrastructure.Persistence · MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:369 · Level 5 · interface

        -
      • What it is: the write half of the repository abstraction, over an aggregate root: AddAsync, AddRangeAsync, UpdateAsync, UpdateRange, two SetOriginalRowVersion overloads, TouchConcurrencyToken, ExecuteDeleteAsync, and ExecuteUpdateAsync (IRepository.cs:367-480). Nine members, and not one of them saves.

        +
      • What it is: the write half of the repository abstraction, over an aggregate root: AddAsync, AddRangeAsync, UpdateAsync, UpdateRange, two SetOriginalRowVersion overloads, TouchConcurrencyToken, ExecuteDeleteAsync, and ExecuteUpdateAsync (IRepository.cs:369-482). Nine members, and not one of them saves.

      • -
      • Depends on: AuditableAggregateRootEntity<TIdentifierType> as the TEntity constraint (IRepository.cs:368), IRowVersioned for the child-concurrency overload (written by its qualified Domain.Interfaces.IRowVersioned name, IRepository.cs:417), and IUpdatePropertySetter<TEntity> for the set-based update builder (IRepository.cs:478). Externals: System.Linq.Expressions (IRepository.cs:1).

        +
      • Depends on: AuditableAggregateRootEntity<TIdentifierType> as the TEntity constraint (IRepository.cs:370), IRowVersioned for the child-concurrency overload (written by its qualified Domain.Interfaces.IRowVersioned name, IRepository.cs:419), and IUpdatePropertySetter<TEntity> for the set-based update builder (IRepository.cs:480). Externals: System.Linq.Expressions (IRepository.cs:1).

      • -
      • Concept introduced, writes enter through the aggregate root, and the repository never flushes. [Rubric §4, DDD] assesses whether the aggregate boundary is an enforced rule rather than a naming convention. The constraint here is the narrower AuditableAggregateRootEntity<TIdentifierType>, not the AuditableBaseEntity<TIdentifierType> the read side accepts, and the doc comment says exactly why (IRepository.cs:351-358): a write enters the aggregate through its root so that the root's invariants are enforced and its domain events are collected, and a repository over a child entity would let a caller persist a change the root never saw. Reading a child directly stays harmless and stays supported through IReadRepository<TEntity, TIdentifierType>. The rule is enforced by the compiler, not by review: asking for a write repository over a child entity does not compile.

        -

        [Rubric §8, Data Architecture] assesses whether persistence is deliberate: one save boundary, one concurrency story. This interface has no Save and no SaveChangesAsync. The doc comment states the division (IRepository.cs:359-363): the repository stages changes and never flushes them, because persisting is the unit of work's job, so every repository touched in a scope is written as one unit under one audit stamp. A handler that mutates through a repository and then forgets IUnitOfWork.SaveChangesAsync has written nothing.

        +
      • Concept introduced, writes enter through the aggregate root, and the repository never flushes. [Rubric §4, DDD] assesses whether the aggregate boundary is an enforced rule rather than a naming convention. The constraint here is the narrower AuditableAggregateRootEntity<TIdentifierType>, not the AuditableBaseEntity<TIdentifierType> the read side accepts, and the doc comment says exactly why (IRepository.cs:353-360): a write enters the aggregate through its root so that the root's invariants are enforced and its domain events are collected, and a repository over a child entity would let a caller persist a change the root never saw. Reading a child directly stays harmless and stays supported through IReadRepository<TEntity, TIdentifierType>. The rule is enforced by the compiler, not by review: asking for a write repository over a child entity does not compile.

        +

        [Rubric §8, Data Architecture] assesses whether persistence is deliberate: one save boundary, one concurrency story. This interface has no Save and no SaveChangesAsync. The doc comment states the division (IRepository.cs:361-365): the repository stages changes and never flushes them, because persisting is the unit of work's job, so every repository touched in a scope is written as one unit under one audit stamp. A handler that mutates through a repository and then forgets IUnitOfWork.SaveChangesAsync has written nothing.

      • Concept introduced, optimistic-concurrency wiring and change-tracking-bypass writes. Five members carry the weight.

          -
        • SetOriginalRowVersion(TEntity entity, byte[] rowVersion) (IRepository.cs:406): plants the client's last-observed RowVersion as the tracked entity's original concurrency token, so the next save emits its WHERE RowVersion = @original and raises DbUpdateConcurrencyException when the row moved since the client read it (IRepository.cs:399-403). The token only ever arrives through If-Match, so [SupportsIfMatch] answers that exception as 412 Precondition Failed (MMCA.Common/Source/Presentation/MMCA.Common.API/Concurrency/SupportsIfMatchAttribute.cs:130-138, ADR-035). The implementation sets OriginalValue on the tracked entry's RowVersion property and rejects a null token outright (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:75-84).
        • -
        • SetOriginalRowVersion(Domain.Interfaces.IRowVersioned childEntity, byte[] rowVersion) (IRepository.cs:417): the same protection for a tracked child of the aggregate, for example a ProductVariant under a Product. The doc comment explains why a second overload exists at all (IRepository.cs:408-414): the repository's TEntity is the root, so the typed overload cannot reach children, and this one accepts any IRowVersioned entity instead (ADR-035). It reaches the entry through an (object) cast (EFRepository.cs:86-95).
        • -
        • TouchConcurrencyToken(TEntity entity) (IRepository.cs:440): forces the aggregate ROOT to take part in the next save when a conditional write left it untouched, so the precondition the caller sent is actually evaluated (SEC-Common-77). The remarks explain the gap it closes (IRepository.cs:424-439): SetOriginalRowVersion only stamps the tracked entry's ORIGINAL value, it does not make the entry dirty, so an applier that changes only child rows leaves the root Unchanged, EF emits no root UPDATE, no WHERE RowVersion = @token reaches the database, and the stale-token check silently does not fire, letting two administrators editing different children of the same aggregate from the same ETag both get 200 while the second silently discards the first's edit. Touching the root restores the 412 ADR-035 promises. It is declared with a default no-op body (IRepository.cs:440-443) so an existing implementer stays source- and binary-compatible; a repository that does not override it keeps the pre-hardening behaviour rather than failing to compile. EFRepository<TEntity, TIdentifierType> overrides it.
        • -
        • ExecuteDeleteAsync(Expression<Func<TEntity, bool>> where, CancellationToken) (IRepository.cs:453-455): a set-based delete run directly in the database, one statement, no change tracker. The doc comment warns in capitals that it does not trigger domain events, audit stamps, or soft delete, and is for maintenance scenarios only (IRepository.cs:445-452). The implementation is a one-liner over the DbSet (EFRepository.cs:118-124).
        • -
        • ExecuteUpdateAsync(where, Action<IUpdatePropertySetter<TEntity>> setProperties, CancellationToken) (IRepository.cs:476-479): a set-based UPDATE ... SET ... WHERE ... as one atomic statement. [Rubric §12, Performance & Scalability] applies alongside §8 here, and the long doc comment (IRepository.cs:457-475) is the teaching text for contention-proof conditional updates: guard the update inside where (the worked example is a stock decrement guarded by AvailableQuantity >= @qty), and then zero rows affected means the guard did not hold, so two racing callers can never both win and no rowversion retry loop is needed, because the database itself arbitrates. It also draws the exact boundaries: domain events are bypassed, global query filters (soft delete) DO apply to where, audit fields are NOT bypassed, and the statement runs on the ambient transaction when one is active, so a decrement rolls back with its caller. The audit guarantee is not something the database does; it is compensation code in the implementation, which stamps LastModifiedOn from the injected TimeProvider and LastModifiedBy from ICurrentUserService unless the caller assigned them explicitly, and rejects an empty setter list (EFRepository.cs:135-153, with the two optional constructor dependencies at EFRepository.cs:23-27).
        • +
        • SetOriginalRowVersion(TEntity entity, byte[] rowVersion) (IRepository.cs:408): plants the client's last-observed RowVersion as the tracked entity's original concurrency token, so the next save emits its WHERE RowVersion = @original and raises DbUpdateConcurrencyException when the row moved since the client read it (IRepository.cs:401-405). The token only ever arrives through If-Match, so [SupportsIfMatch] answers that exception as 412 Precondition Failed, and the doc comment itself names that status and the attribute (IRepository.cs:403-404; the child overload's comment likewise promises "the same stale-token 412 protection", IRepository.cs:413) (MMCA.Common/Source/Presentation/MMCA.Common.API/Concurrency/SupportsIfMatchAttribute.cs:130-138, ADR-035). The implementation sets OriginalValue on the tracked entry's RowVersion property and rejects a null token outright (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:75-84).
        • +
        • SetOriginalRowVersion(Domain.Interfaces.IRowVersioned childEntity, byte[] rowVersion) (IRepository.cs:419): the same protection for a tracked child of the aggregate, for example a ProductVariant under a Product. The doc comment explains why a second overload exists at all (IRepository.cs:410-416): the repository's TEntity is the root, so the typed overload cannot reach children, and this one accepts any IRowVersioned entity instead (ADR-035). It reaches the entry through an (object) cast (EFRepository.cs:86-95).
        • +
        • TouchConcurrencyToken(TEntity entity) (IRepository.cs:442): forces the aggregate ROOT to take part in the next save when a conditional write left it untouched, so the precondition the caller sent is actually evaluated (SEC-Common-77). The remarks explain the gap it closes (IRepository.cs:426-441): SetOriginalRowVersion only stamps the tracked entry's ORIGINAL value, it does not make the entry dirty, so an applier that changes only child rows leaves the root Unchanged, EF emits no root UPDATE, no WHERE RowVersion = @token reaches the database, and the stale-token check silently does not fire, letting two administrators editing different children of the same aggregate from the same ETag both get 200 while the second silently discards the first's edit. Touching the root restores the 412 ADR-035 promises. It is declared with a default no-op body (IRepository.cs:442-445) so an existing implementer stays source- and binary-compatible; a repository that does not override it keeps the pre-hardening behaviour rather than failing to compile. EFRepository<TEntity, TIdentifierType> overrides it.
        • +
        • ExecuteDeleteAsync(Expression<Func<TEntity, bool>> where, CancellationToken) (IRepository.cs:455-457): a set-based delete run directly in the database, one statement, no change tracker. The doc comment warns in capitals that it does not trigger domain events, audit stamps, or soft delete, and is for maintenance scenarios only (IRepository.cs:447-454). The implementation is a one-liner over the DbSet (EFRepository.cs:118-124).
        • +
        • ExecuteUpdateAsync(where, Action<IUpdatePropertySetter<TEntity>> setProperties, CancellationToken) (IRepository.cs:478-481): a set-based UPDATE ... SET ... WHERE ... as one atomic statement. [Rubric §12, Performance & Scalability] applies alongside §8 here, and the long doc comment (IRepository.cs:459-477) is the teaching text for contention-proof conditional updates: guard the update inside where (the worked example is a stock decrement guarded by AvailableQuantity >= @qty), and then zero rows affected means the guard did not hold, so two racing callers can never both win and no rowversion retry loop is needed, because the database itself arbitrates. It also draws the exact boundaries: domain events are bypassed, global query filters (soft delete) DO apply to where, audit fields are NOT bypassed, and the statement runs on the ambient transaction when one is active, so a decrement rolls back with its caller. The audit guarantee is not something the database does; it is compensation code in the implementation, which stamps LastModifiedOn from the injected TimeProvider and LastModifiedBy from ICurrentUserService unless the caller assigned them explicitly, and rejects an empty setter list (EFRepository.cs:135-156, with the two optional constructor dependencies at EFRepository.cs:23-27).
      • Walkthrough: the nine members in teaching order.

          -
        • AddAsync(TEntity entity, CancellationToken) (IRepository.cs:375-377) and AddRangeAsync(IEnumerable<TEntity> entities, CancellationToken) (IRepository.cs:383-385): single and batch inserts, staged on the change tracker.
        • -
        • UpdateAsync(TEntity entity, CancellationToken) (IRepository.cs:391-393) and UpdateRange(IEnumerable<TEntity> entities) (IRepository.cs:397): mark tracked entities modified. UpdateRange is the one void member of the pair, since batch marking needs nothing awaited.
        • -
        • The two SetOriginalRowVersion overloads (IRepository.cs:406, :417), TouchConcurrencyToken (IRepository.cs:440), and the two database-side operations (IRepository.cs:453, :450) described above.
        • +
        • AddAsync(TEntity entity, CancellationToken) (IRepository.cs:377-379) and AddRangeAsync(IEnumerable<TEntity> entities, CancellationToken) (IRepository.cs:385-387): single and batch inserts, staged on the change tracker.
        • +
        • UpdateAsync(TEntity entity, CancellationToken) (IRepository.cs:393-395) and UpdateRange(IEnumerable<TEntity> entities) (IRepository.cs:399): mark tracked entities modified. UpdateRange is the one void member of the pair, since batch marking needs nothing awaited.
        • +
        • The two SetOriginalRowVersion overloads (IRepository.cs:408, :419), TouchConcurrencyToken (IRepository.cs:442), and the two database-side operations (IRepository.cs:455, :478) described above.
      • Why it's built this way: keeping writes in a focused interface means a query handler cannot accidentally acquire mutation methods, and the concurrency and set-based escape hatches are declared where a reader meets their warnings rather than buried in a concrete class. Constraining TEntity to the aggregate root mirrors the constraint on IUnitOfWork.GetRepository (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IUnitOfWork.cs:20), which is how a handler is meant to obtain one, so the two agree by construction. Leaving Save off the interface entirely is what makes the single save boundary of ADR-006 enforceable: with several physical databases in one host, "save" cannot mean "save this repository".

      • -
      • Where it's used: command handlers that mutate aggregates, always through IUnitOfWork.GetRepository<TEntity, TIdentifierType>() (IUnitOfWork.cs:19); folded into IRepository<TEntity, TIdentifierType> (IRepository.cs:493); implemented by EFRepository<TEntity, TIdentifierType> (EFRepository.cs:23-29), which derives from EFReadRepository<TEntity, TIdentifierType> and adds only the write surface, and is wrapped in EFRepositoryDecorator<TEntity, TIdentifierType> when MiniProfiler is on (RepositoryFactory.cs:31-41). Both apps exercise the specialized members: in MMCA.Store, ChangeVariantPriceHandler calls the child-typed SetOriginalRowVersion so a race on one product variant conflicts even when the product row itself is untouched (MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Application/Products/UseCases/ChangeVariantPrice/ChangeVariantPriceHandler.cs:45-47, with the same pattern in ChangeVariantSkuHandler.cs:69); in MMCA.ADC, ScoreEventSessionsHandler uses ExecuteDeleteAsync to clear a session's previous AI scores before re-adding them (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/SessionScoringRunner.cs:28, :105-107).

        +
      • Where it's used: command handlers that mutate aggregates, always through IUnitOfWork.GetRepository<TEntity, TIdentifierType>() (IUnitOfWork.cs:19); folded into IRepository<TEntity, TIdentifierType> (IRepository.cs:495); implemented by EFRepository<TEntity, TIdentifierType> (EFRepository.cs:23-29), which derives from EFReadRepository<TEntity, TIdentifierType> and adds only the write surface, and is wrapped in EFRepositoryDecorator<TEntity, TIdentifierType> when MiniProfiler is on (RepositoryFactory.cs:31-41). Both apps exercise the specialized members: in MMCA.Store, ChangeVariantPriceHandler calls the child-typed SetOriginalRowVersion so a race on one product variant conflicts even when the product row itself is untouched (MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Application/Products/UseCases/ChangeVariantPrice/ChangeVariantPriceHandler.cs:45-47, with the same pattern in ChangeVariantSkuHandler.cs:69); in MMCA.ADC, ScoreEventSessionsHandler uses ExecuteDeleteAsync to clear a session's previous AI scores before re-adding them (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/SessionScoringRunner.cs:28, :105-107).

      • Caveats / not-in-source: ExecuteDeleteAsync and ExecuteUpdateAsync bypass the domain-event capture that DomainEventSaveChangesInterceptor performs on save, so anything downstream that reacts to an event will not observe those writes. The interface says so in its doc comments; nothing in the type system stops it, so it stays a review rule rather than a compiler rule.

      IRepository<TEntity, TIdentifierType>

      -

      MMCA.Common.Application · MMCA.Common.Application.Interfaces.Infrastructure.Persistence · MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:493 · Level 6 · interface

      +

      MMCA.Common.Application · MMCA.Common.Application.Interfaces.Infrastructure.Persistence · MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:495 · Level 6 · interface

        -
      • What it is: the combined read-write repository over an aggregate root, extending both IReadRepository<TEntity, TIdentifierType> and IWriteRepository<TEntity, TIdentifierType>, so a command handler that reads an aggregate and then mutates it takes a single dependency (IRepository.cs:493).
      • -
      • Depends on: IReadRepository<TEntity, TIdentifierType> and IWriteRepository<TEntity, TIdentifierType>, both named as bases on IRepository.cs:493, and AuditableAggregateRootEntity<TIdentifierType> as the TEntity constraint (IRepository.cs:494).
      • -
      • Concept, the top of the ISP ladder, and where the aggregate-root constraint wins. [Rubric §1, SOLID] and [Rubric §4, DDD]. The interface is purely compositional: it declares no members of its own, only two base interfaces and two constraints (IRepository.cs:493-495). What matters is which constraint survives the composition. The read side accepts any AuditableBaseEntity<TIdentifierType> (IRepository.cs:332) and the write side only aggregate roots (IRepository.cs:368), so the combination inherits the narrower bound, and the doc comment states the consequence for the reader (IRepository.cs:485-489): a handler that only reads, and reads a child entity, depends on IReadRepository instead, which still accepts any auditable entity.
      • -
      • Walkthrough: no members. The declaration is a semicolon-terminated interface with two bases and two generic constraints (IRepository.cs:493-495), the C# shorthand for an empty body. Everything a caller can do through it is walked through on the two base sections, and beneath them on IEntityReader and IEntityQuerier.
      • +
      • What it is: the combined read-write repository over an aggregate root, extending both IReadRepository<TEntity, TIdentifierType> and IWriteRepository<TEntity, TIdentifierType>, so a command handler that reads an aggregate and then mutates it takes a single dependency (IRepository.cs:495).
      • +
      • Depends on: IReadRepository<TEntity, TIdentifierType> and IWriteRepository<TEntity, TIdentifierType>, both named as bases on IRepository.cs:495, and AuditableAggregateRootEntity<TIdentifierType> as the TEntity constraint (IRepository.cs:496).
      • +
      • Concept, the top of the ISP ladder, and where the aggregate-root constraint wins. [Rubric §1, SOLID] and [Rubric §4, DDD]. The interface is purely compositional: it declares no members of its own, only two base interfaces and two constraints (IRepository.cs:495-497). What matters is which constraint survives the composition. The read side accepts any AuditableBaseEntity<TIdentifierType> (IRepository.cs:334) and the write side only aggregate roots (IRepository.cs:370), so the combination inherits the narrower bound, and the doc comment states the consequence for the reader (IRepository.cs:487-491): a handler that only reads, and reads a child entity, depends on IReadRepository instead, which still accepts any auditable entity.
      • +
      • Walkthrough: no members. The declaration is a semicolon-terminated interface with two bases and two generic constraints (IRepository.cs:495-497), the C# shorthand for an empty body. Everything a caller can do through it is walked through on the two base sections, and beneath them on IEntityReader and IEntityQuerier.
      • Why it's built this way: keeping the combined interface empty means the read and write surfaces each stay independently usable and independently mockable, while a handler that genuinely needs both still takes one constructor parameter. Composition rather than a fresh member list is also what keeps the ladder honest: there is exactly one definition of "read" and one of "write" in the codebase, and the widest rung is a deliberate choice a reviewer can see at the constructor rather than a default.
      • Where it's used: obtained through IUnitOfWork.GetRepository<TEntity, TIdentifierType>() (IUnitOfWork.cs:19-21), which is the sanctioned way to get one; implemented by EFRepository<TEntity, TIdentifierType> (EFRepository.cs:23-29) and produced, optionally wrapped for profiling, by RepositoryFactory (RepositoryFactory.cs:26-42). UnitOfWork caches the instance per closed generic interface type, so typeof(IRepository<Order, int>) is the cache key for a whole scope (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:37, :43).
      • -
      • Caveats / not-in-source: AddInfrastructure does register the open generic IRepository<,> against EFRepository<,> (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:122), which makes constructor-injecting IRepository<,> look supported. It is not the intended path: EFRepository<TEntity, TIdentifierType> takes a bare DbContext constructor parameter (EFRepository.cs:23-24) and that registration file adds no DbContext service, so a direct injection sidesteps both the data-source resolution and the per-scope repository cache that GetRepository performs (UnitOfWork.cs:37-45). Ask the unit of work.
      • +
      • Caveats / not-in-source: AddInfrastructure does register the open generic IRepository<,> against EFRepository<,> (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:128), which makes constructor-injecting IRepository<,> look supported. It is not the intended path: EFRepository<TEntity, TIdentifierType> takes a bare DbContext constructor parameter (EFRepository.cs:23-24) and that registration file adds no DbContext service, so a direct injection sidesteps both the data-source resolution and the per-scope repository cache that GetRepository performs (UnitOfWork.cs:37-45). Ask the unit of work.

      IUnitOfWork

      MMCA.Common.Application · MMCA.Common.Application.Interfaces.Infrastructure.Persistence · MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IUnitOfWork.cs:10 · Level 7 · interface

        -
      • What it is: the one coordination point a handler uses to touch the database. It hands out typed repositories (read-write for aggregate roots, read-only for any entity), persists everything pending in one call, and exposes controlled transaction and identity-insert operations. The doc comment states the contract in two sentences (IUnitOfWork.cs:5-9): it coordinates persistence across multiple repositories within a single database context, and SaveChangesAsync persists all pending changes and dispatches domain events raised by tracked aggregates.

        +
      • What it is: the one coordination point a handler uses to touch the database. It hands out typed repositories (read-write for aggregate roots, read-only for any entity), persists everything pending in one call, and exposes controlled transaction and explicit-key-insert operations. The doc comment states the contract in two sentences (IUnitOfWork.cs:5-9): it coordinates persistence across multiple repositories within a single database context, and SaveChangesAsync persists all pending changes and dispatches domain events raised by tracked aggregates.

      • Depends on: AuditableAggregateRootEntity<TIdentifierType> and AuditableBaseEntity<TIdentifierType> as the two constraint bounds (IUnitOfWork.cs:1, :20, :30); IRepository<TEntity, TIdentifierType> and IReadRepository<TEntity, TIdentifierType> as the two return types (IUnitOfWork.cs:19 and :29). Externals: BCL IDisposable and IAsyncDisposable, both declared as base interfaces (IUnitOfWork.cs:10). Notably absent: anything from Microsoft.EntityFrameworkCore, which is the point of the type.

      • -
      • Concept introduced, the Unit of Work as the Application layer's only persistence verb. [Rubric §8, Data Architecture] assesses whether persistence is deliberate: one save boundary, one transaction story, explicit concurrency and audit handling rather than scattered SaveChanges calls. A unit of work is the scope inside which a caller sees a consistent view of the data and inside which all of its changes either land together or not at all. Here that scope is the DI scope: UnitOfWork is registered TryAddScoped (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:124), it caches one repository per closed generic interface type (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:23, :37-45), and every repository it hands out is bound to a context obtained from the same IDbContextFactory (UnitOfWork.cs:41), so two handlers in one request share one change tracker instead of racing two.

        -

        [Rubric §3, Clean Architecture] assesses whether the inner layers declare intent while the outer layers own the technology. This interface lives in MMCA.Common.Application and names no EF type, which is exactly what lets the architecture fitness rule "Application must not depend on EF Core, use IRepository/IUnitOfWork" hold (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Layering/ArchitectureRules.Purity.cs:53-63). The EF-shaped work (a context per physical source, execution strategies, identity-insert SQL) sits behind it in MMCA.Common.Infrastructure.

        +
      • Concept introduced, the Unit of Work as the Application layer's only persistence verb. [Rubric §8, Data Architecture] assesses whether persistence is deliberate: one save boundary, one transaction story, explicit concurrency and audit handling rather than scattered SaveChanges calls. A unit of work is the scope inside which a caller sees a consistent view of the data and inside which all of its changes either land together or not at all. Here that scope is the DI scope: UnitOfWork is registered TryAddScoped (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:130), it caches one repository per closed generic interface type (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:23, :37-45), and every repository it hands out is bound to a context obtained from the same IDbContextFactory (UnitOfWork.cs:41), so two handlers in one request share one change tracker instead of racing two.

        +

        [Rubric §3, Clean Architecture] assesses whether the inner layers declare intent while the outer layers own the technology. This interface lives in MMCA.Common.Application and names no EF type, which is exactly what lets the architecture fitness rule "Application must not depend on EF Core, use IRepository/IUnitOfWork" hold (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Layering/ArchitectureRules.Purity.cs:53-63). The EF-shaped work (a context per physical source, execution strategies, the engine-specific explicit-key insert SQL) sits behind it in MMCA.Common.Infrastructure.

        [Rubric §6, CQRS & Event-Driven] assesses whether events are raised and delivered from a single, reliable place. SaveChangesAsync is that place: the doc comment (IUnitOfWork.cs:7-8) ties persistence and domain-event dispatch into one operation, and the mechanism behind it is the interceptor plus outbox flow described on DomainEventSaveChangesInterceptor.

        [Rubric §14, Testability] assesses whether the abstraction a handler depends on can be replaced without infrastructure. Because a handler asks this interface for its repositories rather than receiving them, one Mock<IUnitOfWork> substitutes the entire data layer; the framework ships that scaffold as HandlerTestBase<THandler>, which pre-stubs SaveChangesAsync to return 1 (MMCA.Common/Source/Hosting/MMCA.Common.Testing/Support/HandlerTestBase.cs:42) and wires each registered repository mock into both GetRepository and GetReadRepository (HandlerTestBase.cs:61-62).

      • @@ -2030,17 +2143,17 @@

        IUnitOfWork

        • GetRepository<TEntity, TIdentifierType>() (IUnitOfWork.cs:19-21): the read-write repository. Its constraint is where TEntity : AuditableAggregateRootEntity<TIdentifierType> (IUnitOfWork.cs:20), so the DDD rule that the doc comment states in prose ("Only aggregate roots can be directly persisted", IUnitOfWork.cs:14) is enforced by the compiler. In the implementation the call resolves the entity's physical data source through IDataSourceService, fetches the matching context, builds the repository through IRepositoryFactory, and caches it under the closed interface type (UnitOfWork.cs:37-45).
        • GetReadRepository<TEntity, TIdentifierType>() (IUnitOfWork.cs:29-31): the read-only repository, constrained only to AuditableBaseEntity<TIdentifierType> (IUnitOfWork.cs:30), so child entities are readable even though they are not independently writable. Same resolution path, different factory method (UnitOfWork.cs:57-65).
        • -
        • SaveChangesAsync(CancellationToken cancellationToken = default) (IUnitOfWork.cs:36): persists everything and returns the number of state entries written (IUnitOfWork.cs:35). The implementation is a straight delegation (UnitOfWork.cs:69-70) to DbContextFactory, which saves every cached context, not just one: it snapshots the contexts and loops in bounded passes (three, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:61) so a context materialized by a domain event handler mid-save is still saved (DbContextFactory.cs:256-270), then throws if any tracked change is left behind rather than losing it silently (DbContextFactory.cs:278-289).
        • -
        • RequestIdentityInsert() (IUnitOfWork.cs:45): a one-shot flag saying the next save may insert rows carrying explicit values for database-generated identity columns, for example records imported from an external system with their source ids intact (IUnitOfWork.cs:38-44). The implementation sets a boolean (UnitOfWork.cs:73, DbContextFactory.cs:295); the next SaveChangesAsync reads and immediately clears it (DbContextFactory.cs:246-247, which is what "automatically cleared after the save completes" means) and, for a SQL Server context, routes the save through SaveWithIdentityInsertAsync (DbContextFactory.cs:266-268), which groups the affected entries by table and wraps each group in SET IDENTITY_INSERT ON/OFF because SQL Server allows only one table per session to have it on (DbContextFactory.cs:297-319).
        • -
        • ExecuteInTransactionAsync<TResult>(Func<CancellationToken, Task<TResult>> operation, CancellationToken cancellationToken = default) (IUnitOfWork.cs:57-59): the interface's only transaction-control member; the manual Save(), BeginTransaction(), CommitTransaction(), and RollbackTransaction() members it used to sit alongside are gone from the interface. Its doc comment (IUnitOfWork.cs:47-53) states the reason: the operation runs wrapped by the active execution strategy, so a retrying strategy such as SqlServerRetryingExecutionStrategy can retry the whole transaction as one retriable unit, committing on success and rolling back before an exception propagates. The implementation adds five behaviors worth knowing (DbContextFactory.cs:518-563, with the attempt runner at :554-609): a nested call joins the ambient transaction instead of opening a second one (DbContextFactory.cs:529-530); a returned failed Result rolls back exactly like an exception (DbContextFactory.cs:582-589); each retry starts from a reset change tracker, so entities a failed attempt added are not inserted twice (DbContextFactory.cs:491-497, :528-529); deferred in-process domain events are flushed only after a successful commit (DbContextFactory.cs:595-602); and a failure of the commit itself is never retried, surfacing as TransactionCommitAmbiguousException thrown outside the strategy instead (DbContextFactory.cs:555-560).
        • +
        • SaveChangesAsync(CancellationToken cancellationToken = default) (IUnitOfWork.cs:36): persists everything and returns the number of state entries written (IUnitOfWork.cs:35). The implementation is a straight delegation (UnitOfWork.cs:69-70) to DbContextFactory, which saves every cached context, not just one: it snapshots the contexts and loops in bounded passes (three, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:63) so a context materialized by a domain event handler mid-save is still saved (DbContextFactory.cs:282-296), then throws if any tracked change is left behind rather than losing it silently (DbContextFactory.cs:304-315).
        • +
        • RequestExplicitKeyInsert() (IUnitOfWork.cs:45): a one-shot flag saying the next save may insert rows that carry explicit values for store-generated keys, for example rows imported from an external system with their source ids intact (IUnitOfWork.cs:38-44). The contract is engine-neutral: on an engine that refuses such a value unless it is switched on per table, the save pipeline does that around those inserts, and on every other engine the save runs unchanged (IUnitOfWork.cs:41-42). The implementation sets a boolean (UnitOfWork.cs:73, DbContextFactory.cs:321); the next SaveChangesAsync reads and immediately clears it (DbContextFactory.cs:272-273, which is what "automatically cleared after the save completes" means) and, for a context whose engine exposes an IExplicitKeyInsertDialect through IDataSourceEngine.ExplicitKeyInsert (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/IDataSourceEngine.cs:119), routes the save through SaveWithExplicitKeyInsertAsync (DbContextFactory.cs:292-294). Only SQLServerDataSourceEngine supplies a dialect (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SQLServerDataSourceEngine.cs:49, the SET IDENTITY_INSERT ON/OFF toggle per DbContextFactory.cs:263-266); the SQLite, PostgreSQL and Cosmos engines return null, so their save is the plain one. With a dialect, the save asks it for the affected entries grouped by table as ExplicitKeyInsertGroup values (DbContextFactory.cs:334), pins one open connection for the whole save because the toggle is session state (DbContextFactory.cs:342-352, closed again at :364-368), and saves each group in its own round with the toggle on, because SQL Server allows only one table per session to have it on (DbContextFactory.cs:323-328, SaveExplicitKeyGroupsAsync at :377).
        • +
        • ExecuteInTransactionAsync<TResult>(Func<CancellationToken, Task<TResult>> operation, CancellationToken cancellationToken = default) (IUnitOfWork.cs:63-65): the interface's only transaction-control member; the manual Save(), BeginTransaction(), CommitTransaction(), and RollbackTransaction() members it used to sit alongside are gone from the interface. Its doc comment (IUnitOfWork.cs:47-59) states the reason: the operation runs wrapped by the active execution strategy, so a retrying strategy such as SqlServerRetryingExecutionStrategy can retry the whole transaction as one retriable unit, committing on success and rolling back before an exception propagates. The same comment carries the save rule (IUnitOfWork.cs:53-58): the commit does not save the operation's work, so the operation must save before it returns; the one exception is an internal-command row scheduled after the last save, which is saved just before the commit, and any other change still tracked at that point throws InvalidOperationException and rolls the unit back rather than being silently discarded. The implementation adds six behaviors worth knowing (DbContextFactory.cs:534-579, with the attempt runner at :589-647): a nested call joins the ambient transaction instead of opening a second one (DbContextFactory.cs:545-546); a returned failed Result rolls back exactly like an exception (DbContextFactory.cs:598-605); before committing, FlushEnrolledCommandsBeforeCommitAsync (DbContextFactory.cs:608, :659-685) saves the unit when every pending entry is an added InternalCommandMessage and otherwise throws the unsaved-changes exception (DbContextFactory.cs:668-682); each retry starts from a reset change tracker, so entities a failed attempt added are not inserted twice (DbContextFactory.cs:563-564, ResetForRetry at :814-821); deferred in-process domain events are flushed only after a successful commit (DbContextFactory.cs:614-621); and a failure of the commit itself is never retried, surfacing as TransactionCommitAmbiguousException thrown outside the strategy instead (DbContextFactory.cs:571-576).
        • The two base interfaces (IUnitOfWork.cs:10) matter at scope teardown: UnitOfWork forwards both disposal paths to the context factory (UnitOfWork.cs:81-107), so the async path awaits _dbContextFactory.DisposeAsync() (UnitOfWork.cs:91) rather than blocking.
      • Why it's built this way: under ADR-006 a single host may own several physical databases, so "save" cannot mean "call SaveChanges on the one context". Splitting the responsibility keeps that manageable: IDbContextFactory owns multi-source routing, saving, transactions and disposal, while IUnitOfWork is the narrow per-scope facade the Application layer is allowed to see, adding only repository resolution and caching on top (UnitOfWork.cs:13, :23). Exposing ExecuteInTransactionAsync as the interface's only transaction-control member, rather than the manual BeginTransaction/CommitTransaction/RollbackTransaction/Save set it replaced, is what makes the ADR-014 pipeline's transaction rules enforceable in one place: business failures roll back and post-commit event dispatch is deferred. Keeping the interface in Application rather than Infrastructure is the ADR-013 and Clean Architecture pairing, since a handler returns a Result and never sees an EF type on the way there.

      • -
      • Where it's used: injected into command handlers across the framework and both apps. In MMCA.Common: TransactionalCommandDecorator<TCommand, TResult> takes it in its primary constructor (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:22) and calls ExecuteInTransactionAsync for any command marked ITransactional (TransactionalCommandDecorator.cs:31); DeleteEntityHandler<TEntity, TIdentifierType> takes it and resolves its write repository from it (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/DeleteEntityHandler.cs:37, :70); EntityQueryService<TEntity, TEntityDTO, TIdentifierType> both holds it and derives its read repository from it (MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:33, :43, :87). In MMCA.ADC, RefreshFromSessionizeHandler is the live consumer of the identity-insert path, calling RequestIdentityInsert() immediately before the save because Sessionize imports preserve external ids into tables with IDENTITY columns (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeHandler.cs:168-169). The single implementation is UnitOfWork (UnitOfWork.cs:13), which is internal sealed, so consumers only ever see this interface.

        +
      • Where it's used: injected into command handlers across the framework and both apps. In MMCA.Common: TransactionalCommandDecorator<TCommand, TResult> takes it in its primary constructor (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:24) and calls ExecuteInTransactionAsync for any command marked ITransactional (TransactionalCommandDecorator.cs:33); DeleteEntityHandler<TEntity, TIdentifierType> takes it and resolves its write repository from it (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/DeleteEntityHandler.cs:37, :70); EntityQueryService<TEntity, TEntityDTO, TIdentifierType> both holds it and derives its read repository from it (MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:33, :43, :87). In MMCA.ADC, RefreshFromSessionizeHandler is the live consumer of the explicit-key insert path, calling RequestExplicitKeyInsert() immediately before the save because Sessionize imports preserve external ids into tables with store-generated keys (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeHandler.cs:192-193). The single implementation is UnitOfWork (UnitOfWork.cs:13), which is internal sealed, so consumers only ever see this interface.

      • -
      • Caveats / not-in-source: ExecuteInTransactionAsync is not a distributed transaction: with several physical sources each gets its own transaction and commits are sequential and best effort, so a commit failure on the second source leaves the first already committed. The doc comment names that limitation and records that the thrown TransactionCommitAmbiguousException reports each source's outcome so the partial state is observable rather than inferred, with the outbox as the cross-source consistency mechanism (DbContextFactory.cs:471-474, :487-497).

        +
      • Caveats / not-in-source: ExecuteInTransactionAsync is not a distributed transaction: with several physical sources each gets its own transaction and commits are sequential and best effort, so a commit failure on the second source leaves the first already committed. The doc comment names that limitation and records that the thrown TransactionCommitAmbiguousException names each source's outcome (committed, ambiguous, or rolled back) so the partial state is observable rather than inferred, and that the caller's replay is what reconciles it (DbContextFactory.cs:522-532). A witness row that would let a replay learn what landed is deliberately not built, since a single transactional source needs none (DbContextFactory.cs:528-531); the outbox delivers whatever the commit did make durable (DbContextFactory.cs:519-520).

      BlobNames

      @@ -2049,7 +2162,7 @@

      BlobNames

      • What it is: a dependency-free static helper that reduces a user-supplied file name to a blob-safe - segment built only from A-Z a-z 0-9 . _ - (BlobNames.cs:25-135).
      • + segment built only from A-Z a-z 0-9 . _ - (BlobNames.cs:33-119).
      • Depends on: nothing first-party; BCL StringBuilder.
      • Concept introduced, file-name sanitization as an upload trust boundary. [Rubric §11, Security] assesses whether untrusted input (here, a caller-chosen file name that becomes part of a storage path @@ -2066,7 +2179,7 @@

        BlobNames

        the extension alone is longer than the budget.
      • KeepSafeCharacters(string fileName) (BlobNames.cs:95-117): rewrites the name over the safe alphabet, collapsing any run of unsafe characters to one - and any run of dots to one ..
      • -
      • ToLowerAscii(string value) (BlobNames.cs:123-131): a hand-rolled ASCII lower-case so the result +
      • ToLowerAscii(string value) (BlobNames.cs:108-116): a hand-rolled ASCII lower-case so the result is independent of the host's culture.
      @@ -2108,30 +2221,30 @@

      FileUploadOptions

      • What it is: an immutable options record carrying the response headers a blob upload should be - stored with, ContentDisposition and CacheControl (FileUploadOptions.cs:221-227), plus factory + stored with, ContentDisposition and CacheControl (FileUploadOptions.cs:28-34), plus factory methods that build a correctly-encoded Content-Disposition from a user-facing file name.
      • Depends on: nothing first-party; BCL StringBuilder, Encoding.UTF8.
      • Concept introduced, RFC 6266/5987-correct Content-Disposition encoding. [Rubric §8, Data Architecture] assesses whether a storage-layer concern (here, the HTTP headers a blob is served with) is centralized in one place rather than hand-built at each call site. The static None instance - (FileUploadOptions.cs:214) sets no headers, so a caller only reaches for Attachment/Inline when + (FileUploadOptions.cs:21) sets no headers, so a caller only reaches for Attachment/Inline when it actually wants a download disposition.
      • Walkthrough: two public factory methods and three private helpers.
          -
        • Attachment(string fileName, string? cacheControl = null) (FileUploadOptions.cs:241-242): builds +
        • Attachment(string fileName, string? cacheControl = null) (FileUploadOptions.cs:48-49): builds an attachment disposition, so the browser downloads the blob under fileName instead of rendering it.
        • -
        • Inline(string fileName, string? cacheControl = null) (FileUploadOptions.cs:252-253): builds an +
        • Inline(string fileName, string? cacheControl = null) (FileUploadOptions.cs:59-60): builds an inline disposition, so the browser renders the blob when it can and otherwise downloads it under fileName.
        • ForDisposition(string dispositionType, string fileName, string? cacheControl) - (FileUploadOptions.cs:255-278): trims the name (falling back to file when blank), then emits both + (FileUploadOptions.cs:62-85): trims the name (falling back to file when blank), then emits both an ASCII filename="..." fallback and a percent-encoded UTF-8 filename*=UTF-8''... parameter in the same header, per RFC 6266.
        • -
        • ToAsciiFallback(string fileName) (FileUploadOptions.cs:284-299) and ToRfc5987(string fileName) - (FileUploadOptions.cs:305-322): the two encodings behind the header; the ASCII fallback drops +
        • ToAsciiFallback(string fileName) (FileUploadOptions.cs:91-106) and ToRfc5987(string fileName) + (FileUploadOptions.cs:112-129): the two encodings behind the header; the ASCII fallback drops quotes, backslashes, semicolons and line breaks and replaces the rest of non-printable-ASCII with _, while the RFC 5987 form percent-encodes every UTF-8 byte outside the attr-char alphabet - (FileUploadOptions.cs:328-332).
        • + (FileUploadOptions.cs:135-139).
      • Why it's built this way: emitting both an ASCII fallback and the UTF-8 filename* parameter in one @@ -2141,7 +2254,7 @@

        FileUploadOptions

        themselves.
      • Where it's used: the options parameter of IFileStorageService's document-upload overload - (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Storage/IFileStorageService.cs:36), + (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Storage/IFileStorageService.cs:37), implemented by AzureBlobFileStorageService and NullFileStorageService; called by @@ -2185,9 +2298,9 @@

        ImageContentSniffer

        that follows.
      • Where it's used: called by SetUserAvatarHandler as the first gate on an upload - (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/SetUserAvatar/SetUserAvatarHandler.cs:54) - before the bytes reach IImageProcessor (SetUserAvatarHandler.cs:76) and then - IFileStorageService (SetUserAvatarHandler.cs:90). It has its own dedicated unit-test + (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/SetUserAvatar/SetUserAvatarHandler.cs:63) + before the bytes reach IImageProcessor (SetUserAvatarHandler.cs:103) and then + IFileStorageService (SetUserAvatarHandler.cs:117). It has its own dedicated unit-test class (MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/ImageContentSnifferTests.cs).
      • Caveats / not-in-source: sniffing establishes only that the prefix matches a known signature. It is not a decode, so a truncated or malformed body still passes here and is rejected later by @@ -2209,29 +2322,29 @@

        DocumentContentSniffer

        the bytes to agree before it will return a MIME type, which ImageContentSniffer does not need because an image has no user-visible extension trust decision to make. A zip-bomb guard (MaxInspectedEntries = 4096, - DocumentContentSniffer.cs:365) caps how many zip central-directory entries are inspected, and only - entry names are read, never entry streams (DocumentContentSniffer.cs:479-481), so a hostile archive + DocumentContentSniffer.cs:61) caps how many zip central-directory entries are inspected, and only + entry names are read, never entry streams (DocumentContentSniffer.cs:175-177), so a hostile archive costs nothing to refuse.
      • Walkthrough: a lookup table plus detection and signature-checking members.
          -
        • KnownExtensions (DocumentContentSniffer.cs:372-383): the one place extension, DocumentFormats +
        • KnownExtensions (DocumentContentSniffer.cs:68-79): the one place extension, DocumentFormats flag and canonical MIME type are tied together, compared case-insensitively.
        • Detect(ReadOnlySpan<byte> content, string fileName, DocumentFormats allowed) - (DocumentContentSniffer.cs:395-410): resolves the extension, then for Office Open XML formats + (DocumentContentSniffer.cs:91-106): resolves the extension, then for Office Open XML formats copies the span to an array (ZipArchive needs a seekable stream) and calls IsOfficeOpenXml; otherwise checks the format's byte signature directly.
        • Detect(ReadOnlyMemory<byte> content, string fileName, DocumentFormats allowed) - (DocumentContentSniffer.cs:423-435): the same contract without the defensive copy, for callers that + (DocumentContentSniffer.cs:119-131): the same contract without the defensive copy, for callers that already hold a Memory<byte>.
        • -
        • IsPdf (DocumentContentSniffer.cs:440-441): leading bytes match %PDF-.
        • -
        • IsZip (DocumentContentSniffer.cs:446-447): leading bytes match the zip local-file-header signature +
        • IsPdf (DocumentContentSniffer.cs:136-137): leading bytes match %PDF-.
        • +
        • IsZip (DocumentContentSniffer.cs:142-143): leading bytes match the zip local-file-header signature PK 03 04.
        • -
        • IsOfficeOpenXml(ReadOnlyMemory<byte> content) (DocumentContentSniffer.cs:457-492): opens the +
        • IsOfficeOpenXml(ReadOnlyMemory<byte> content) (DocumentContentSniffer.cs:153-188): opens the bytes as a ZipArchive and checks for a [Content_Types].xml entry, the marker every Office Open XML package carries at its root; a malformed or truncated archive answers false rather than - throwing (DocumentContentSniffer.cs:483-491).
        • -
        • IsPlainUtf8Text (DocumentContentSniffer.cs:501-515): strips an optional UTF-8 BOM, then rejects + throwing (DocumentContentSniffer.cs:179-187).
        • +
        • IsPlainUtf8Text (DocumentContentSniffer.cs:197-211): strips an optional UTF-8 BOM, then rejects empty content, any embedded NUL byte, or anything Utf8.IsValid rejects.
        • -
        • TryResolve/MatchesSignature/Extension (DocumentContentSniffer.cs:520-571): the private glue +
        • TryResolve/MatchesSignature/Extension (DocumentContentSniffer.cs:216-267): the private glue that looks up the extension, checks it against allowed, and routes to the right signature check.
      • @@ -2278,31 +2391,41 @@

        IFileStorageService

        (IFileStorageService.cs:17), and the content is read from the stream's current position (IFileStorageService.cs:18).
      • UploadAsync(string blobName, Stream content, string contentType, FileUploadOptions options, CancellationToken cancellationToken = default) - (IFileStorageService.cs:36): the overload a document upload wants, storing the response headers + (IFileStorageService.cs:37): the overload a document upload wants, storing the response headers carried by FileUploadOptions (a Content-Disposition, for example, is what makes a stored blob come back as a download under its original file name). It has no default body: every implementation must honor the headers directly rather than falling back to the three-argument - overload.
      • + overload. It carries a SuppressMessage for analyzer RS0026 (multiple public overloads with + optional parameters) (IFileStorageService.cs:36): the justification records it as a grandfathered + public overload shipped after the v1.152 public-API baseline, so changing its signature would be a + breaking change.
      • DeleteAsync(string blobName, CancellationToken cancellationToken = default) - (IFileStorageService.cs:42): deletes a blob; unknown names succeed (idempotent, - IFileStorageService.cs:38), which matches at-least-once cleanup semantics.
      • + (IFileStorageService.cs:43): deletes a blob; unknown names succeed (idempotent, + IFileStorageService.cs:39), which matches at-least-once cleanup semantics.
    318. Why it's built this way: an unconfigured default plus an IsConfigured gate means the framework ships avatar support without forcing every consumer to provision blob storage, and idempotent delete makes cleanup safe to retry after a partial failure. The headers-aware overload is a plain interface member with no - default implementation (IFileStorageService.cs:36), so every IFileStorageService implementation is + default implementation (IFileStorageService.cs:37), so every IFileStorageService implementation is forced to honor the response headers rather than silently dropping them via a forwarding default.
    319. Where it's used: SetUserAvatarHandler uploads the - normalized JPEG (SetUserAvatarHandler.cs:29 for the injection, :85 for the call), with + normalized JPEG (SetUserAvatarHandler.cs:30 for the injection, :117 for the call). Deletes run through + DeleteBlobInternalCommandHandlerBase<TCommand>, + which takes the service (MMCA.Common/Source/Core/MMCA.Common.Application/InternalCommands/DeleteBlobInternalCommandHandlerBase.cs:29) + and calls DeleteAsync (DeleteBlobInternalCommandHandlerBase.cs:45): + DeleteAvatarBlobInternalCommandHandler + derives from it (DeleteAvatarBlobInternalCommandHandler.cs:20) and is the cleanup side scheduled by + SetUserAvatarHandler, RemoveUserAvatarHandler and - DeleteUserHandler on the cleanup side. + DeleteUserHandler. UploadSessionAssetHandler uses the - headers-aware overload for document uploads, with DeleteSessionAssetBlobInternalCommandHandler on its - cleanup side - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/SessionAssets/UseCases/DeleteSessionAssetBlob/DeleteSessionAssetBlobInternalCommandHandler.cs:1). + headers-aware overload for document uploads (UploadSessionAssetHandler.cs:166), with + DeleteSessionAssetBlobInternalCommandHandler + on its cleanup side + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/SessionAssets/UseCases/DeleteSessionAssetBlob/DeleteSessionAssetBlobInternalCommandHandler.cs:20). NullFileStorageService is the default registration - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:325); + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:335); AzureBlobFileStorageService replaces it when configured (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:139).
    320. @@ -2334,9 +2457,9 @@

      IImageProcessor

      IFileStorageService; and a Result failure on undecodable input stops a bad upload before it ever reaches storage.
    321. Where it's used: called by SetUserAvatarHandler - between the sniffer and the upload (SetUserAvatarHandler.cs:28 for the injection, :71 for the call). + between the sniffer and the upload (SetUserAvatarHandler.cs:29 for the injection, :71 for the call). Implemented by ImageSharpImageProcessor, registered with TryAddSingleton - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:326); note this is the one member + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:336); note this is the one member of the avatar trio with a real default implementation rather than a null object.
    322. EntityConfigurationOptions

      @@ -2364,9 +2487,9 @@

      EntityConfigurationOptions

      extension without the provider (or the context) taking a compile-time dependency on any specific module. The provider merges these with the name-scanned set and de-duplicates.
    323. Where it's used: written through the AddEntityConfigurationAssembly(Assembly) extension - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:338-348), which calls + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:348-358), which calls services.Configure<EntityConfigurationOptions> with a contains-check so an assembly is added at - most once (DependencyInjection.cs:340-346); AddNotificationInfrastructure() + most once (DependencyInjection.cs:350-356); AddNotificationInfrastructure() (DependencyInjection.Notifications.cs:26-31) is the one in-framework caller. It is read by DefaultEntityConfigurationAssemblyProvider.
    324. @@ -2394,9 +2517,9 @@

      PersistenceSettings

      so a context built outside the full DI graph (the design-time provider behind dotnet ef, a test) still gets the documented default rather than a null reference. It then applies it with sql.CommandTimeout(_persistenceSettings.CommandTimeoutSeconds) (SQLServerDbContext.cs:55). - A second property, EnableSensitiveDataLogging, defaults to false (PersistenceSettings.cs:117) + A second property, EnableSensitiveDataLogging, defaults to false (PersistenceSettings.cs:44) and asks EF to render parameter VALUES into its logs and exception messages - (EnableSensitiveDataLogging, PersistenceSettings.cs:97-99); SensitiveDataLoggingGate + (EnableSensitiveDataLogging, PersistenceSettings.cs:25-26); SensitiveDataLoggingGate is the class that decides whether the request is actually honored.
    325. Why it's built this way: a [Range]-validated option bound with ValidateDataAnnotations() and ValidateOnStart() turns a typo into a startup failure rather than a per-query surprise @@ -2404,19 +2527,19 @@

      PersistenceSettings

      EnableSensitiveDataLogging is deliberately a request, not a switch: the doc comment is explicit that setting it true matters only when the host also reports the Development environment, and a host with no IHostEnvironment registered (design time, a directly-constructed test context) counts - as not Development (PersistenceSettings.cs:101-108), so the flag cannot by itself put parameter + as not Development (PersistenceSettings.cs:29-34), so the flag cannot by itself put parameter values (customer data, tokens, password hashes) into a log sink in Staging or Production (ADR-122). The flag also does not by itself make EF log statements: the doc comment pairs it with setting Logging:LogLevel:Microsoft.EntityFrameworkCore.Database.Command to Information in - appsettings.Development.json only (PersistenceSettings.cs:109-115).
    326. + appsettings.Development.json only (PersistenceSettings.cs:37-41).
    327. Where it's used: bound in the infrastructure registration - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:138-141) and read by + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:144-147) and read by SQLServerDbContext for the command timeout; no other engine context reads that property today. EnableSensitiveDataLogging is read by SensitiveDataLoggingGate, which ApplicationDbContext calls from ConfigureSensitiveDataLogging - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:362-370) + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:367-375) so the guarantee applies on the shared base rather than per engine.
    328. ProfilingHelper

      @@ -2454,7 +2577,7 @@

      ProfilingHelper

      (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepositoryDecorator.cs:33-111, covering GetAllAsync through CountAsync). ApplicationDbContext opens its own MiniProfiler step directly rather - than through this helper (ApplicationDbContext.cs:191). Behavior is pinned by + than through this helper (ApplicationDbContext.cs:196). Behavior is pinned by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/ProfilingHelperTests.cs.

      DefaultEntityConfigurationAssemblyProvider

      @@ -2488,9 +2611,9 @@

      DefaultEntityConfigurationAs on the abstraction plus options keeps the persistence layer free of module references.
    329. Where it's used: registered as the singleton IEntityConfigurationAssemblyProvider via TryAddSingleton - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:58) and resolved by + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:59) and resolved by ApplicationDbContext, which iterates its assemblies inside - ApplyConfigurationsForEntitiesInContext (ApplicationDbContext.cs:950,968). Covered by + ApplyConfigurationsForEntitiesInContext (ApplicationDbContext.cs:953,972). Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DefaultEntityConfigurationAssemblyProviderTests.cs.
    330. EfCoreConcurrencyConflictDetector

      @@ -2524,7 +2647,7 @@

      EfCoreConcurrencyConflictDetector

    331. Where it's used: registered as the singleton IConcurrencyConflictDetector via TryAddSingleton - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:120). Covered by + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:126). Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EfCoreConcurrencyConflictDetectorTests.cs.
    332. EFQueryableExecutor

      @@ -2561,7 +2684,7 @@

      EFQueryableExecutor

      class means every consumer gets the fallback for free and no handler references EF's static extension methods.
    333. Where it's used: registered as the singleton IQueryableExecutor - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:105) and injected into + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:111) and injected into Application-layer query code: EntityQueryPipeline (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/EntityQueryPipeline.cs:13), the @@ -2614,8 +2737,9 @@

      NamespaceConventions

      [UseDatabase] is present and then to DataSourceKey.DefaultName (EntityDataSourceRegistry.cs:181), and EntityTypeConfiguration<TEntity, TIdentifierType> - uses it for the SQL table schema (EntityTypeConfiguration.cs:74, falling back to dbo) and the - Cosmos container name (EntityTypeConfiguration.cs:95, falling back to the entity type name). The + hands mapping to the engine (EntityTypeConfiguration.cs:77), which uses it for the SQL table schema + (SQLServerDataSourceEngine.cs:101, PostgreSQLDataSourceEngine.cs:100, falling back to dbo) and the + Cosmos container name (CosmosDataSourceEngine.cs:95, falling back to the entity type name). The Shared parser is separately exercised by MMCA.Common/Tests/Core/MMCA.Common.Shared.Tests/Conventions/ModuleNameConventionsTests.cs, and the persistence forwarder by @@ -2649,80 +2773,12 @@

      SensitiveDataLoggingGate

      left to right as "did the host ask, AND is it Development".
    334. Where it's used: called from ApplicationDbContext's ConfigureSensitiveDataLogging - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:364-366), + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:369-371), which calls optionsBuilder.EnableSensitiveDataLogging() only when the gate returns true - (ApplicationDbContext.cs:368); the method sits on the shared base rather than in each engine's + (ApplicationDbContext.cs:373); the method sits on the shared base rather than in each engine's context class so the guarantee holds identically for every engine. Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/SensitiveDataLoggingGateTests.cs.
    335. -

      SoftDeleteFilterSql

      -
      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:16 · Level 1 · class (internal static)

      -
      -
        -
      • What it is: the two-method internal static class that owns the IsDeleted = 0 index predicate: - it builds the predicate in the identifier-quoting style of the target engine, and it recognises when - an index filter already carries that predicate. It is the single authority both the automatic - convention and the hand-authored opt-in call, so the two can never disagree.
      • -
      • Depends on: DataSource (the engine enum), - IAuditableEntity (for the - nameof(IAuditableEntity.IsDeleted) property name), and EF Core's - Microsoft.EntityFrameworkCore.Metadata.IReadOnlyEntityType plus the GetColumnName() relational - metadata extension.
      • -
      • Concept introduced, one predicate builder shared by a convention and an explicit extension. - [Rubric §8, Data Architecture] (assesses whether soft-delete is honored by the schema, not just by - query filters) and [Rubric §15, Best Practices & Code Quality] (assesses whether one rule has one - implementation): a filtered unique index that hardcodes the literal "[IsDeleted] = 0" breaks in - two ways, on a model that renames the column with HasColumnName and on a provider that quotes - identifiers with double quotes rather than brackets. Reading the column name from the EF model and - branching on the engine fixes both, and putting that logic in one place means the automatic path - (SoftDeleteUniqueIndexConvention) and the opt-in path - (IndexBuilderExtensions) emit byte-identical SQL - (SoftDeleteFilterSql.cs:8-15).
      • -
      • Walkthrough:
          -
        • Build(DataSource engine, IReadOnlyEntityType entityType) (SoftDeleteFilterSql.cs:27-51) has - three steps. The Cosmos short-circuit (:29-30) returns null for DataSource.CosmosDB, - which has no filtered-index support; null is the contract for "leave the index untouched", and - both callers check it before doing anything (SoftDeleteUniqueIndexConvention.cs:57-58, - IndexBuilderExtensions.cs:59-60). Column-name resolution (:32) delegates to the private - ColumnName helper (:57-59), which looks the IsDeleted property up in the entity type and - takes its mapped column name, falling back to the CLR property name when the property is not in - the model, so a HasColumnName rename follows automatically. Quoting and value spelling - (:34-49, a switch over DataSource): SQL Server gets [Column] = 0; PostgreSQL gets - "Column" = false, because it maps the soft-delete flag to a real boolean column and refuses to - compare one with an integer, the one place the predicate differs by engine beyond quoting - (SoftDeleteFilterSql.cs:41-44); Sqlite and every other relational engine get "Column" = 0. The - Cosmos arm of the switch (:46-47) is unreachable, since the early return above already answered - null; it is listed anyway because the switch must name every DataSource member (IDE0072).
        • -
        • ContainsPredicate(string existingFilter, IReadOnlyEntityType entityType) - (SoftDeleteFilterSql.cs:67-74) answers the idempotence question: does a filter already declared - on an index constrain the soft-delete column? Both the SQL Server/Sqlite integer spelling - ({column} = 0) and the PostgreSQL boolean spelling ({column} = false) are checked, each pushed - through Normalize (:80-81), which strips whitespace and the three identifier quoting styles - ([, ], " and a backtick), so a hand-authored [IsDeleted] = 0, a "IsDeleted"=0, the - PostgreSQL "IsDeleted" = false and the predicate Build produces all count as the same clause. - Without that normalization, and without checking both spellings, the convention could append its - own predicate to a filter that already had one, or append the integer and boolean spellings of the - same predicate to one another.
        • -
        -
      • -
      • Why it's built this way: internal static with a nullable return keeps the engine-capability - decision inside the builder rather than duplicated at each call site. The Cosmos null is a - deliberate signal instead of an exception, because both callers run over whole models where Cosmos - entities are simply skipped. Comparing on a normalized form rather than on the exact string is what - lets the two entry points, which do not agree on quoting, still recognise each other's output; the - PostgreSQL branch exists because a type-checked boolean column, not an untyped bit, is the PostgreSQL - mapping for the flag, so the SQL Server/Sqlite integer predicate would be a type error there.
      • -
      • Where it's used: SoftDeleteUniqueIndexConvention calls - Build at model finalization and ContainsPredicate before combining, then joins with AND in the - same order the opt-in extension uses (SoftDeleteUniqueIndexConvention.cs:56,74,79), and - IndexBuilderExtensions calls Build for a hand-authored index, - optionally joining an extra predicate (IndexBuilderExtensions.cs:58-65).
      • -
      • Caveats / not-in-source: the plain double-quote-integer branch ("Column" = 0) is reached by any - relational engine that is neither SQL Server nor PostgreSQL nor Cosmos. Sqlite is the only such - engine registered today, so whether that quoting and value spelling suits a future provider is Not - determinable from source.
      • -

      SqlServerUniqueConstraintViolationDetector

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SqlServerUniqueConstraintViolationDetector.cs:31 · Level 1 · class (sealed)

      @@ -2773,7 +2829,7 @@

      SqlServerUniqueConstraintVio own number check (:19-25).
    336. Where it's used: registered as the singleton IUniqueConstraintViolationDetector via TryAddSingleton - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:115), which the module + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:121), which the module registrations rely on rather than shipping their own pair (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/DependencyInjection.cs:28). The consumers are the handlers whose uniqueness pre-check can lose a race: @@ -2791,43 +2847,132 @@

      SqlServerUniqueConstraintVio describes; whether a real provider emits that wording for a non-unique failure is Not determinable from source.

    337. +

      SoftDeleteFilterSql

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:17 · Level 11 · class (internal static)

      +
      +
        +
      • What it is: the internal static class that owns the soft-delete index predicate for unique and + opted-in indexes. It resolves which column carries the flag, hands that column to the target engine + to spell the predicate, recognises when an index filter already carries that predicate, and quotes + a single column name the way the engine expects. It is the single authority both the automatic + convention and the hand-authored opt-in call, so the two can never disagree + (SoftDeleteFilterSql.cs:9-16).
      • +
      • Depends on: DataSource (the engine enum), + DataSourceEngines and IDataSourceEngine (the + per-engine strategy that owns the quoting and the predicate spelling), + IAuditableEntity (for the + nameof(IAuditableEntity.IsDeleted) property name), and EF Core's + Microsoft.EntityFrameworkCore.Metadata.IReadOnlyEntityType plus the GetColumnName() relational + metadata extension.
      • +
      • Concept introduced, one predicate entry point shared by a convention and an explicit extension. + [Rubric §8, Data Architecture] (assesses whether soft-delete is honored by the schema, not just by + query filters) and [Rubric §15, Best Practices & Code Quality] (assesses whether one rule has one + implementation): a filtered unique index that hardcodes the literal "[IsDeleted] = 0" breaks in + two ways, on a model that renames the column with HasColumnName and on a provider that quotes + identifiers with double quotes rather than brackets. Reading the column name from the EF model and + asking the engine strategy for the spelling fixes both, and putting that entry point in one place + means the automatic path (SoftDeleteUniqueIndexConvention) + and the opt-in path (IndexBuilderExtensions) emit byte-identical SQL. + The engine-specific text itself no longer lives in this class: it is one member per engine on + IDataSourceEngine, so this class carries no switch over DataSource.
      • +
      • Walkthrough:
          +
        • Build(DataSource engine, IReadOnlyEntityType entityType) (SoftDeleteFilterSql.cs:34-35) is + one expression: DataSourceEngines.For(engine).BuildSoftDeleteFilter(ColumnName(entityType)). + The private ColumnName helper (:72-74) looks the IsDeleted property up in the entity type + and takes its mapped column name, falling back to the CLR property name when the property is not + in the model, so a HasColumnName rename follows automatically. The engine then answers the + spelling: SQL Server [Column] = 0 + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SQLServerDataSourceEngine.cs:117), + PostgreSQL "Column" = false (PostgreSQLDataSourceEngine.cs:116), because it maps the flag to a + real boolean column and refuses to compare one with an integer, the only place the predicate + differs by engine beyond quoting (SoftDeleteFilterSql.cs:28-33), Sqlite "Column" = 0 + (SqliteDataSourceEngine.cs:115), and Cosmos null (CosmosDataSourceEngine.cs:117), since it + has no filtered-index support. null is the contract for "leave the index untouched", and both + callers check it before doing anything (SoftDeleteUniqueIndexConvention.cs:58-59, + IndexBuilderExtensions.cs:59-60).
        • +
        • ContainsPredicate(string existingFilter, IReadOnlyEntityType entityType) + (SoftDeleteFilterSql.cs:52-59) answers the idempotence question: does a filter already declared + on an index constrain the soft-delete column? Both the integer spelling ({column} = 0) and the + PostgreSQL boolean spelling ({column} = false) are checked, each pushed through Normalize + (:77-78), which strips whitespace and the three identifier quoting styles ([, ], " and a + backtick), so a hand-authored [IsDeleted] = 0, a "IsDeleted"=0, the PostgreSQL + "IsDeleted" = false and the predicate Build produces all count as the same clause. Without + that normalization, and without checking both spellings, the convention could append its own + predicate to a filter that already had one, or append the integer and boolean spellings of the + same predicate to one another.
        • +
        • QuoteColumn(DataSource engine, string column) (SoftDeleteFilterSql.cs:69-70) forwards to + DataSourceEngines.For(engine).QuoteColumn(column): brackets on SQL Server, the SQL-standard + double-quoted form on PostgreSQL (which rejects brackets) and SQLite (:61-68). It exists for + filtered-index predicates over columns other than IsDeleted, such as an IS NOT NULL clause.
        • +
        +
      • +
      • Why it's built this way: internal static with a nullable Build return keeps the + engine-capability decision out of the call sites; delegating the spelling to the engine strategy + keeps the per-engine text in one place per engine rather than in a switch here. The Cosmos null + is a deliberate signal instead of an exception, because both callers run over whole models where + Cosmos entities are simply skipped. Comparing on a normalized form rather than on the exact string + is what lets the two entry points, which do not agree on quoting, still recognise each other's + output.
      • +
      • Where it's used: SoftDeleteUniqueIndexConvention calls + Build at model finalization and ContainsPredicate before combining + (SoftDeleteUniqueIndexConvention.cs:57,75), and + IndexBuilderExtensions calls Build for a hand-authored index + (IndexBuilderExtensions.cs:58). QuoteColumn is called by + PushNotificationConfiguration for its DedupKey IS NOT NULL + filter (PushNotificationConfiguration.cs:73) and by + ApplicationDbContext through a private wrapper + (ApplicationDbContext.cs:618-619) for the outbox and internal-command partial-index predicates + (ApplicationDbContext.cs:664-665,754-755).
      • +
      • Caveats / not-in-source: a fifth engine would need its own + IDataSourceEngine implementation to answer BuildSoftDeleteFilter and + QuoteColumn; how DataSourceEngines.For (DataSourceEngines.cs:40) treats an unmapped value is + not covered by this section.
      • +

      EFRawSqlQueryExecutor

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/EFRawSqlQueryExecutor.cs:22 · Level 13 · class (internal sealed)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/EFRawSqlQueryExecutor.cs:28 · Level 13 · class (internal sealed)

      • What it is: the EF Core implementation of IRawSqlQueryExecutor, the Application-layer escape hatch for a query that is easier to express as literal SQL than as a specification. It builds a - parameterized IQueryable<T> from an interpolated FormattableString over the resolved default - data source, then either lists or single-or-defaults it.
      • + parameterized IQueryable<T> from an interpolated FormattableString over the host's default + physical data source, then either lists or single-or-defaults it (EFRawSqlQueryExecutor.cs:8-25).
      • Depends on: IRawSqlQueryExecutor (the Application-layer contract it implements), IDbContextFactory, IDataSourceResolver, and Microsoft.EntityFrameworkCore (Database.SqlQuery<T>, EntityFrameworkQueryableExtensions).
      • -
      • Concept introduced, an escape hatch that still refuses to run against the wrong engine. [Rubric §3, Clean Architecture] (the raw-SQL surface is still an abstraction, not a direct - DbContext reference from Application) and [Rubric §8, Data Architecture] (a relational-only - capability stays refused rather than silently mis-executed on Cosmos): the class resolves the - logical default source and, if that source's engine is Cosmos DB, throws - NotSupportedException with a message explaining the alternative (LINQ through - IQueryableExecutor, or moving the entity to a relational source) rather than - handing the caller a query that would fail deep inside the Cosmos provider.
      • -
      • Walkthrough: a primary constructor takes - IDbContextFactory and IDataSourceResolver - (EFRawSqlQueryExecutor.cs:24-26). QueryAsync<T>(FormattableString sql, CancellationToken) - (:28-29) and QuerySingleOrDefaultAsync<T>(...) (:32-33) both call the private - SqlQueryable<T>(sql) and materialize it with ToListAsync/SingleOrDefaultAsync. SqlQueryable<T> - (:41-56) null-guards sql (:44), resolves the logical default source engine with - dataSourceResolver.ResolveLogical(DataSource.SQLServer, DataSourceKey.DefaultName) (:46), throws - the NotSupportedException when that resolved engine is DataSource.CosmosDB (:47-53), and - otherwise returns - dbContextFactory.GetDbContext(dataSourceKey).Database.SqlQuery<T>(sql) (:55), EF's own - parameterized raw-SQL queryable, composable with further LINQ before materialization.
      • -
      • Why it's built this way: internal sealed; the Cosmos check runs before the query is built so - the failure is an immediate, explanatory exception rather than a deep provider error. The - FormattableString parameter is the parameterization guarantee: interpolated values become SQL - parameters through EF's own handling, not string-concatenated SQL.
      • -
      • Where it's used: registered as the scoped IRawSqlQueryExecutor - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:110). Covered by - MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs.
      • +
      • Concept introduced, an escape hatch that is absent, not refused, on the wrong engine. [Rubric §3, Clean Architecture] (the raw-SQL surface is still an abstraction, not a direct DbContext + reference from Application) and [Rubric §8, Data Architecture] (a relational-only capability is + never silently mis-executed on Cosmos): the class itself carries no engine check. The engine gate + lives at registration: AddRawSqlQueryExecutor resolves the framework default engine and registers + the executor only when DataSourceEngines.For(defaultEngine).Capabilities.IsRelational + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:400-413). A + Cosmos-default host therefore has no IRawSqlQueryExecutor at all, so a service that injects it + fails when the container validates rather than on its first statement + (EFRawSqlQueryExecutor.cs:19-24).
      • +
      • Walkthrough: a primary constructor takes IDbContextFactory and + IDataSourceResolver (EFRawSqlQueryExecutor.cs:28-30). + QueryAsync<T>(FormattableString sql, CancellationToken) (:33-34) and + QuerySingleOrDefaultAsync<T>(...) (:37-38) both call the private SqlQueryable<T>(sql) and + materialize it with ToListAsync/SingleOrDefaultAsync. SqlQueryable<T> (:46-52) null-guards + sql (:48), resolves the default source with + dataSourceResolver.ResolveLogical(DataSource.SQLServer, DataSourceKey.DefaultName) (:50), the + same way the framework's own tables resolve theirs, letting the resolver substitute the engine a + single-engine host actually configured (:13-15), and returns + dbContextFactory.GetDbContext(dataSourceKey).Database.SqlQuery<T>(sql) (:51), EF's own + parameterized raw-SQL queryable, composable with further LINQ before materialization. Because the + context comes from the scoped factory, the statement shares the caller's connection and any + transaction an ITransactional command opened (:16-17).
      • +
      • Why it's built this way: internal sealed, with no engine branching in the body, so the + relational-only constraint is decided once, at composition, from the engine's declared + capabilities rather than re-checked on every call. The FormattableString parameter is the + parameterization guarantee: interpolated values become SQL parameters through EF's own handling, + not string-concatenated SQL (ADR-125).
      • +
      • Where it's used: registered as the scoped IRawSqlQueryExecutor via TryAddScoped + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:411), the helper being invoked from the + infrastructure registration at DependencyInjection.cs:116. Covered by + MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EFRawSqlQueryExecutorTests.cs, + and named by MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/DataSourceBranchingFitnessTests.cs.

      UnitOfWork

      @@ -2853,28 +2998,34 @@

      UnitOfWork

      service, and the repository factory, null-guarding the context factory and the repository factory into readonly fields (the data-source service is used directly from the primary-constructor parameter). -
    338. _repositories (UnitOfWork.cs:111): a Dictionary<Type, object> keyed by the closed generic +
    339. _repositories (UnitOfWork.cs:23): a Dictionary<Type, object> keyed by the closed generic repository interface (for example IRepository<Order, int>), so a repository is created at most once per entity type per scope.
    340. -
    341. GetRepository<TEntity, TIdentifierType>() (UnitOfWork.cs:121-134): on a cache miss, resolves +
    342. GetRepository<TEntity, TIdentifierType>() (UnitOfWork.cs:33-46): on a cache miss, resolves the entity's DataSourceKey via - dataSourceService.GetDataSourceKey(typeof(TEntity)) (UnitOfWork.cs:128), asks the context - factory for the matching context (:129), and builds a read-write + dataSourceService.GetDataSourceKey(typeof(TEntity)) (UnitOfWork.cs:40), asks the context + factory for the matching context (:41), and builds a read-write IRepository<TEntity, TIdentifierType> through - IRepositoryFactory (:130); constrained to + IRepositoryFactory (:42); constrained to AuditableAggregateRootEntity<TIdentifierType> so only aggregate roots get a mutable repository.
    343. -
    344. GetReadRepository<TEntity, TIdentifierType>() (UnitOfWork.cs:141-154): the same resolution - but calls CreateReadOnly (:150) and accepts any AuditableBaseEntity<TIdentifierType>, +
    345. GetReadRepository<TEntity, TIdentifierType>() (UnitOfWork.cs:53-66): the same resolution + but calls CreateReadOnly (:62) and accepts any AuditableBaseEntity<TIdentifierType>, returning IReadRepository<TEntity, TIdentifierType> for query handlers.
    346. -
    347. Save and transaction methods (UnitOfWork.cs:157-167): SaveChangesAsync, - RequestIdentityInsert, and ExecuteInTransactionAsync all delegate straight to the context - factory, because in a multi-database scope the factory is what coordinates saving and transacting - across every context the scope touched. Save, BeginTransaction, CommitTransaction, and - RollbackTransaction were removed from this pass-through set.
    348. -
    349. Disposal (UnitOfWork.cs:169-195): implements both Dispose and DisposeAsync over a - volatile bool _disposed flag (UnitOfWork.cs:113), disposing the context factory exactly once - and suppressing finalization on both paths.
    350. +
    351. Save and transaction methods (UnitOfWork.cs:69-79): SaveChangesAsync (:69-70), + RequestExplicitKeyInsert (:73), and ExecuteInTransactionAsync (:76-79) all delegate + straight to the context factory, because in a multi-database scope the factory is what + coordinates saving and transacting across every context the scope touched. + RequestExplicitKeyInsert forwards to IDbContextFactory.RequestExplicitKeyInsert(), the + engine-neutral signal that the next save may carry explicit values for store-generated keys: + each context's engine decides whether those inserts need a per-table toggle (its + IExplicitKeyInsertDialect), an engine with none saves unchanged, + and the flag clears after the save + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/IDbContextFactory.cs:34-40). + There is no synchronous Save and no manual begin/commit/rollback on this type.
    352. +
    353. Disposal (UnitOfWork.cs:81-107): implements both Dispose (:81-85) and DisposeAsync + (:87-95) over a volatile bool _disposed flag (UnitOfWork.cs:25), disposing the context + factory exactly once and suppressing finalization on both paths.
    354. Why it's built this way: the unit of work plus the factory hide the physical topology from @@ -2882,7 +3033,7 @@

      UnitOfWork

      per-scope repository caching guarantees a single change tracker per database. It is internal sealed because consumers only ever see the IUnitOfWork abstraction (dependency inversion).
    355. Where it's used: registered as the scoped IUnitOfWork via TryAddScoped - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:124) and injected into + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:130) and injected into virtually every command and query handler in Common and in both apps, and into the module seeders.
    356. AuditTrailEntry

      @@ -2929,7 +3080,7 @@

      AuditTrailEntry

    357. ChangedBy, ChangedOn, CorrelationId, TenantId (AuditTrailEntry.cs:80,83,91,99): the provenance block. ChangedBy is null for a save that carried no identity (a background service, a seeder), CorrelationId is the ambient trace id, and TenantId comes from - ApplicationDbContext.CurrentTenantId at capture (ApplicationDbContext.cs:141).
    358. + ApplicationDbContext.CurrentTenantId at capture (ApplicationDbContext.cs:154).
    359. Row shape (AuditTrailEntry.cs:15-21): a Modified save produces one row per property that actually changed, so a trail reads as a field-level history; an Added or Deleted save produces a single summary row with a null PropertyName, because the interesting fact there is the @@ -2943,20 +3094,20 @@

      AuditTrailEntry

      keeps the trail from becoming a second copy of a data subject's data that erasure would have to chase (ADR-005).
    360. Where it's used: mapped by ApplicationDbContext.ConfigureAuditTrail - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:844-873) - to dbo.AuditTrailEntries (ApplicationDbContext.cs:853) with EntityType at 256 non-unicode + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:847-876) + to dbo.AuditTrailEntries (ApplicationDbContext.cs:856) with EntityType at 256 non-unicode characters, EntityKey at 128, PropertyName at 128 non-unicode, Operation at 16 non-unicode and - both CorrelationId and TenantId at 64 (ApplicationDbContext.cs:855-861), plus two indexes: + both CorrelationId and TenantId at 64 (ApplicationDbContext.cs:858-864), plus two indexes: IX_AuditTrailEntries_Entity over (EntityType, EntityKey, ChangedOn) for the read path and - IX_AuditTrailEntries_ChangedOn for the retention sweep (ApplicationDbContext.cs:862-871). The + IX_AuditTrailEntries_ChangedOn for the retention sweep (ApplicationDbContext.cs:865-874). The mapping only happens when AuditTrail:Enabled is true: the context resolves that flag once in - OnConfiguring (ApplicationDbContext.cs:327) and ConfigureAuditTrail returns immediately when - it is false (ApplicationDbContext.cs:846-849), so a host that never opted in has exactly the model - it had before the trail shipped (ApplicationDbContext.cs:71-75).
    361. + OnConfiguring (ApplicationDbContext.cs:332) and ConfigureAuditTrail returns immediately when + it is false (ApplicationDbContext.cs:849-852), so a host that never opted in has exactly the model + it had before the trail shipped (ApplicationDbContext.cs:84-88).

      CaptureContext

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.AuditTrail · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:538 · Level 0 · record struct (private readonly, nested)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.AuditTrail · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:540 · Level 0 · record struct (private readonly, nested)

      • What it is: a four-field value carrying the provenance every trail row of one save shares: @@ -2970,7 +3121,7 @@

        CaptureContext

        identical ChangedOn, which is what lets a reader group a save back together.
      • Walkthrough: the positional members are ChangedBy (nullable user id), ChangedOn (UTC instant), CorrelationId (nullable trace id) and TenantId (nullable), declared at - AuditTrailSaveChangesInterceptor.cs:538-542. It is constructed exactly once per capture, in + AuditTrailSaveChangesInterceptor.cs:540-544. It is constructed exactly once per capture, in CaptureChanges (AuditTrailSaveChangesInterceptor.cs:190-194), where the trace id and tenant are already truncated to their column widths through ColumnWidth.Truncate (MaxCorrelationIdLength 64 and MaxTenantIdLength 64, @@ -2999,10 +3150,10 @@

        PermissionGrantModelGate

        [Rubric §11, Security] (whether stored permission grants are an explicit choice rather than an always-on table). ApplicationDbContext.ResolvePermissionGrantGate reads serviceProvider.GetService<PermissionGrantModelGate>() is not null - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:911) + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:914) and ANDs it with a check that this context's physical source name matches Authentication:PermissionGrants:DataSourceName - (ApplicationDbContext.cs:912-915). Registering the class IS the opt-in the context reads: no flag, + (ApplicationDbContext.cs:915-918). Registering the class IS the opt-in the context reads: no flag, no boolean setting, just whether the type was registered at all. This is the same mechanic RefreshSessionModelBuilderExtensions's gate uses for the RefreshSessions table, applied to a second framework-owned table.
      • @@ -3016,8 +3167,8 @@

        PermissionGrantModelGate

        answerable questions, since a host could bind the settings section without ever calling AddStoredPermissionGrants.
      • Where it's used: registered as a singleton by AddStoredPermissionGrants - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:112), read by - ApplicationDbContext.ResolvePermissionGrantGate (ApplicationDbContext.cs:910-915) and by + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:116), read by + ApplicationDbContext.ResolvePermissionGrantGate (ApplicationDbContext.cs:913-918) and by ConfigurePermissionGrants which calls PermissionGrantModelBuilderExtensions.ApplyPermissionGrantConfiguration behind that gate. Covered by @@ -3045,8 +3196,8 @@

        AuditTrailSettings

        changed this, and when"). Most feature flags in this codebase decide whether code runs; this one also decides whether a table exists. Enabled is read in ApplicationDbContext with GetService, not GetRequiredService, and - cached in a field (ApplicationDbContext.cs:81, :291) that the model builder consults before - mapping the entity (ApplicationDbContext.cs:844-849), so a host that leaves it false has exactly + cached in a field (ApplicationDbContext.cs:94, :291) that the model builder consults before + mapping the entity (ApplicationDbContext.cs:847-852), so a host that leaves it false has exactly the model it had before the trail existed and its migrations never see an AuditTrailEntries table (AuditTrailSettings.cs:10-15). That is what makes an opt-in feature genuinely free for a host that does not want it: a mapped-but-empty table would still have to be migrated. The trail is @@ -3061,11 +3212,11 @@

        AuditTrailSettings

        • SectionName = "AuditTrail" (AuditTrailSettings.cs:19), the binding key.
        • Enabled (AuditTrailSettings.cs:26), defaulting to false. Read by - ApplicationDbContext for the model gate (ApplicationDbContext.cs:327) - and re-checked by the cleanup job before it does any work (AuditTrailCleanupJob.cs:71-74).
        • + ApplicationDbContext for the model gate (ApplicationDbContext.cs:332) + and re-checked by the cleanup job before it does any work (AuditTrailCleanupJob.cs:68-71).
        • RetentionDays (AuditTrailSettings.cs:38), [Range(1, 3650)] (:37), default 90. AuditTrailCleanupJob turns it into a cutoff instant - (AuditTrailCleanupJob.cs:76) and purges from every relational source in use, skipping Cosmos + (AuditTrailCleanupJob.cs:73) and purges from every relational source in use, skipping Cosmos (:79-81) and expanding each source across the declared tenants (:83-86).
        • DataSource (AuditTrailSettings.cs:46), default DataSource.SQLServer. Note the asymmetry the doc calls out (AuditTrailSettings.cs:40-45): rows are WRITTEN to every relational source @@ -3092,14 +3243,14 @@

          AuditTrailSettings

          with the table on or off (DesignTimeDbContextHelper.cs:165-166). The recording itself is AuditTrailSaveChangesInterceptor, registered as a singleton at DependencyInjection.Jobs.cs:117 and resolved by the context with GetService so its absence reads - as "not registered" (ApplicationDbContext.cs:314). Covered by + as "not registered" (ApplicationDbContext.cs:319). Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailModelGateTests.cs and AddAuditTrailTests.cs.

        PendingEntityKey

        -

        MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.AuditTrail · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:547 · Level 1 · record (private sealed, nested)

        +

        MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.AuditTrail · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:549 · Level 1 · record (private sealed, nested)

        • What it is: a two-field pairing of a staged trail row with the entity entry whose primary key @@ -3117,7 +3268,7 @@

          PendingEntityKey

          whole design exists for.
        • Walkthrough: the positional members are Row (the tracked trail row whose EntityKey must be rewritten) and Entry (the audited entry whose key the database assigns), declared at - AuditTrailSaveChangesInterceptor.cs:547. Instances are produced by CaptureEntry only when the + AuditTrailSaveChangesInterceptor.cs:549. Instances are produced by CaptureEntry only when the entry is Added and HasTemporaryKey(entry) returns true (AuditTrailSaveChangesInterceptor.cs:268-270, with the temporary-key test walking every primary key property at :449-466). They are accumulated into a list and parked in a @@ -3162,7 +3313,7 @@

          PermissionGrantRefreshService

          (:57, :59-62); the base class is what turns a thrown exception there into "log and keep looping" rather than a host crash.
        • Why it's built this way: registering it through TryAddEnumerable rather than AddHostedService - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:126-129) is what keeps + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:130-133) is what keeps two modules calling AddStoredPermissionGrants from starting two refresh loops in one process, the same idempotent-registration discipline RefreshSessionCleanupService and @@ -3171,7 +3322,7 @@

          PermissionGrantRefreshService

          a stored-grant feature that takes the host down on a transient database fault would be worse than the fallback it exists to layer over.
        • Where it's used: registered as a singleton IHostedService by AddStoredPermissionGrants - (DependencyInjection.Auth.cs:128-129), started and stopped by the generic host alongside every other + (DependencyInjection.Auth.cs:132-133), started and stopped by the generic host alongside every other BackgroundService. Its only collaborator is PermissionGrantCache through IPermissionGrantCache.RefreshAsync.
        @@ -3186,17 +3337,17 @@

        RefreshSessionModelBuilderExtensio
      • Walkthrough
        • Three public constants name the objects so tests and callers do not restate strings: TableName = "RefreshSessions" (RefreshSessionModelBuilderExtensions.cs:19), TokenHashIndexName (:22) and UserIndexName (:25).
        • The method null-guards, maps the table with the caller's schema defaulting to dbo (RefreshSessionModelBuilderExtensions.cs:36-40), and keys on Id (:41).
        • -
        • TokenHash is IsRequired, HasMaxLength(RefreshSession.TokenHashLength), IsUnicode(false), IsFixedLength() (RefreshSessionModelBuilderExtensions.cs:45-49). The constant is 64 (MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:34), the length of a SHA-256 digest rendered as hex. The comment at :43-44 gives the reason for the three facets: the value is always a 64-character hex digest, so a Unicode or variable-width column would double the index it has to fit in for nothing.
        • +
        • TokenHash is IsRequired, HasMaxLength(RefreshSession.TokenHashLength), IsUnicode(false), IsFixedLength() (RefreshSessionModelBuilderExtensions.cs:45-49). The constant is 64 (MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:42), the length of a SHA-256 digest rendered as hex. The comment at :43-44 gives the reason for the three facets: the value is always a 64-character hex digest, so a Unicode or variable-width column would double the index it has to fit in for nothing.
        • ReplacedByTokenHash gets the same three facets minus IsRequired (RefreshSessionModelBuilderExtensions.cs:51-54), because it is null until the session is rotated.
        • -
        • ReasonRevoked, IpAddress and UserAgent take their lengths from the domain constants (64, 45 and 512 at RefreshSession.cs:43, :37, :40), the first two non-Unicode (RefreshSessionModelBuilderExtensions.cs:56-58). 45 is the length of a full IPv6 text form; UserAgent stays Unicode because a user-agent string is arbitrary client text.
        • +
        • ReasonRevoked, IpAddress and UserAgent take their lengths from the domain constants (64, 45 and 512 at RefreshSession.cs:51, :37, :40), the first two non-Unicode (RefreshSessionModelBuilderExtensions.cs:56-58). 45 is the length of a full IPv6 text form; UserAgent stays Unicode because a user-agent string is arbitrary client text.
        • The unique index over TokenHash (RefreshSessionModelBuilderExtensions.cs:64-66) is the validation path: every refresh presents a token and must be answered by exactly one row. The comment at :60-63 gives two reasons for the uniqueness, and both are security reasons rather than performance ones: a hash collision across users would let one account's token validate against another's session, and a double-insert of the same token becomes a database error instead of an ambiguity the reuse check has to resolve.
        • The composite index on (UserId, RevokedAt) (RefreshSessionModelBuilderExtensions.cs:70-71) serves the family question, "every live session for this user", which is asked on the per-user session cap, on reuse detection and on sign-out-everywhere. Without it each of those scans the table (:68-69).
        • The builder is returned for chaining (RefreshSessionModelBuilderExtensions.cs:74).
      • Why it's built this way: hashing the token rather than storing it, and rotating per device, is ADR-097, which supersedes the single-plaintext-column storage model of ADR-050 while keeping its rotation and reuse-detection policy. The column shapes here are what make that model cheap: a fixed-width non-Unicode digest keeps the unique index narrow, and the two indexes are exactly the two questions the auth service asks. Nothing here is soft-deletable and nothing is audit-stamped, which is deliberate and is why the table needs its own mapping method rather than riding the module entity-configuration mechanism.
      • -
      • Where it's used: called by ApplicationDbContext from ConfigureRefreshSessions (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:889-897) behind the two-part gate computed in the constructor: RefreshSessions:Enabled is true and this context's physical source name equals RefreshSessions:DataSourceName (ApplicationDbContext.cs:331-334). At design time the same gate is fed by DesignTimeDbContextOptions.EnableRefreshSessions. Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Auth/RefreshSessionModelBuilderExtensionsTests.cs and, for the gate itself, .../Persistence/DbContexts/RefreshSessionModelGateTests.cs:89.
      • -
      • Caveats / not-in-source: the class doc says the consumer's Identity context calls this from its own OnModelCreating (RefreshSessionModelBuilderExtensions.cs:12-13), which describes an earlier arrangement. The base context now calls it directly behind the gate, and the doc on ConfigureRefreshSessions explains why (ApplicationDbContext.cs:880-887): downstream apps run on the sealed engine contexts and have no context class to override (ADR-006). Calling it by hand remains supported for a host that does have its own context class; trust the context, not the older sentence.
      • +
      • Where it's used: called by ApplicationDbContext from ConfigureRefreshSessions (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:892-900) behind the two-part gate computed in the constructor: RefreshSessions:Enabled is true and this context's physical source name equals RefreshSessions:DataSourceName (ApplicationDbContext.cs:336-339). At design time the same gate is fed by DesignTimeDbContextOptions.EnableRefreshSessions. Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Auth/RefreshSessionModelBuilderExtensionsTests.cs and, for the gate itself, .../Persistence/DbContexts/RefreshSessionModelGateTests.cs:89.
      • +
      • Caveats / not-in-source: the class doc says the consumer's Identity context calls this from its own OnModelCreating (RefreshSessionModelBuilderExtensions.cs:12-13), which describes an earlier arrangement. The base context now calls it directly behind the gate, and the doc on ConfigureRefreshSessions explains why (ApplicationDbContext.cs:883-890): downstream apps run on the sealed engine contexts and have no context class to override (ADR-006). Calling it by hand remains supported for a host that does have its own context class; trust the context, not the older sentence.

      PermissionGrantModelBuilderExtensions

      @@ -3206,7 +3357,7 @@

      PermissionGrantModelBuilderExtens
    362. What it is: the single place the PermissionGrants table is mapped. One extension method, ApplyPermissionGrantConfiguration(this ModelBuilder, string? schema = "dbo"), configures the PermissionGrant entity: table, key, column widths, and the unique index that makes a grant - idempotent (PermissionGrantModelBuilderExtensions.cs:15-160).
    363. + idempotent (PermissionGrantModelBuilderExtensions.cs:15-64).
    364. Depends on: PermissionGrant from MMCA.Common.Domain.Auth (including its RoleMaxLength/PermissionMaxLength constants) and EF Core's ModelBuilder.
    365. Concept introduced, the same opt-in-table mapping shape as refresh sessions, applied to a second @@ -3216,7 +3367,7 @@

      PermissionGrantModelBuilderExtens Persistence.Auth and follows the identical pattern: a callable extension method rather than an inline mapping in a context, so the table only appears in a host's model when something calls it.

    366. Walkthrough
        -
      • Two public constants: TableName = "PermissionGrants" (PermissionGrantModelBuilderExtensions.cs:114) +
      • Two public constants: TableName = "PermissionGrants" (PermissionGrantModelBuilderExtensions.cs:18) and RolePermissionIndexName = "IX_PermissionGrants_Role_Permission" (:117).
      • The method null-guards, maps the table with the caller's schema defaulting to dbo (:126-132), and keys on Id (:133).
      • @@ -3242,7 +3393,7 @@

        PermissionGrantModelBuilderExtens a second lookup.
      • Where it's used: called by ApplicationDbContext.ConfigurePermissionGrants behind PermissionGrantModelGate's presence check plus the physical-source - match (ApplicationDbContext.cs:910-925). Covered by + match (ApplicationDbContext.cs:913-928). Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Auth/PermissionGrantModelBuilderExtensionsTests.cs and .../Persistence/DbContexts/PermissionGrantModelGateTests.cs, and consumed by EFPermissionGrantStore through the DbSet<PermissionGrant> the mapping @@ -3256,7 +3407,7 @@

        PermissionGrantCache

      • What it is: the in-memory snapshot of every role's stored permissions, keyed per role so a read never scans the whole grant table. It implements IPermissionGrantCache (the read side) and IPermissionGrantCacheInvalidator (the write-triggered refresh), and both resolve to the same - singleton instance (PermissionGrantCache.cs:35-269).
      • + singleton instance (PermissionGrantCache.cs:35-131).
      • Depends on: IMemoryCache, IServiceScopeFactory (to resolve the scoped IPermissionGrantStore for a rebuild), IOptions<PermissionGrantSettings>, and BCL FrozenSet/SemaphoreSlim.
      • @@ -3265,36 +3416,46 @@

        PermissionGrantCache

        lock-free) and [Rubric §11, Security] (a reader must never observe a half-built permission set). A SemaphoreSlim (_rebuildLock) serializes rebuilds so the interval refresh and a concurrent administration edit cannot interleave a write from one with the eviction pass of the other - (PermissionGrantCache.cs:195, doc at :190-194); _cachedRoles is volatile and replaced - wholesale on every rebuild rather than mutated, so a reader never sees a half-built set (:201, - doc at :197-200). + (PermissionGrantCache.cs:50, doc at :46-49); _cachedRoles is volatile and replaced + wholesale on every rebuild rather than mutated, so a reader never sees a half-built set (:56, + doc at :52-55).
      • Walkthrough
          -
        • GetPermissions(role) (PermissionGrantCache.cs:204-207): a plain IMemoryCache.TryGetValue - keyed by CacheKey(role), returning Empty (a static FrozenSet<string>, :186) for a blank +
        • GetPermissions(role) (PermissionGrantCache.cs:59-62): a plain IMemoryCache.TryGetValue + keyed by CacheKey(role), returning Empty (a static FrozenSet<string>, :41) for a blank role or a cache miss. No lock, no allocation on a miss.
        • -
        • RefreshAsync (:210-247): takes the rebuild lock, opens a DI scope, resolves +
        • RefreshAsync (:65-107): takes the rebuild lock, opens a DI scope, resolves IPermissionGrantStore, reads every grant with GetAllAsync, groups by role (StringComparer.OrdinalIgnoreCase) into per-role FrozenSet<string>s - (StringComparer.Ordinal for the permission names themselves), writes each role's entry with the - configured CacheSeconds lifetime (:227-232), then evicts roles that were cached a moment ago - but carry no grant now, in that order, so a role that still has grants is never briefly absent - (:234-239, comment at :234-235). _cachedRoles is replaced last (:241).
        • -
        • InvalidateAsync(role, ct) (:250-258): the role argument narrows what a caller MEANS, not + (StringComparer.Ordinal for the permission names themselves), writes each role's entry + (:89-92), then evicts roles that were cached a moment ago but carry no grant now, in that order, + so a role that still has grants is never briefly absent (:96-99, comment at :94-95). + _cachedRoles is replaced last (:101).
        • +
        • Entry lifetime is three refresh intervals, not one (:87, CacheSeconds * 3), for the two + reasons the comment gives (:82-86). The next rebuild starts one interval AFTER the previous one + finishes, so an entry living exactly one interval would expire before it is rewritten and every + cycle would open a window with no stored grants. And when the refresh keeps failing, the snapshot + must still stop answering eventually, so a revoke cannot grant forever on a dead database: three + intervals is that bound.
        • +
        • InvalidateAsync(role, ct) (:110-118): the role argument narrows what a caller MEANS, not what is reloaded; RefreshAsync always reloads everything, and the comment explains why - (:252-254): a single query either way, and reloading everything keeps one code path that cannot + (:112-114): a single query either way, and reloading everything keeps one code path that cannot leave a second role stale because a caller named only the first.
        • -
        • Dispose (:266) disposes only the semaphore; the cache entries belong to the host's +
        • Dispose (:126) disposes only the semaphore; the cache entries belong to the host's IMemoryCache and are deliberately left alone since this is a singleton whose disposal happens at - shutdown (doc at :261-265).
        • + shutdown (doc at :121-125). +
        • CacheKey(role) (:130) upper-cases the role (ToUpperInvariant) before building + permgrant:role:{ROLE}. Role names compare case-insensitively everywhere they are read, while + IMemoryCache compares keys ordinally, so without the normalization a role stored as Admin and + read as admin would miss (comment at :128-129).
      • Why it's built this way: one instance answering both the read interface and the invalidator interface, rather than two collaborating types, is what guarantees an edit and the reads that follow it cannot end up looking at two different snapshots (the DI registration comment states this - directly, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:116-117).
      • + directly, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:120-121).
      • Where it's used: registered as a singleton for all three roles (concrete type, and the two interfaces resolved from the same instance) by AddStoredPermissionGrants - (DependencyInjection.Auth.cs:118-122). Refreshed on an interval by + (DependencyInjection.Auth.cs:122-126). Refreshed on an interval by PermissionGrantRefreshService and read by LayeredPermissionRegistry and the stored-grant administration surface.
      @@ -3325,13 +3486,13 @@

      AuditTrailSaveChangesInterceptor

      here as a set.
      • Opt in twice over (AuditTrailSaveChangesInterceptor.cs:33-37): the host must call AddAuditTrail (the context resolves this interceptor with GetService, so its absence is a - silent no-op, ApplicationDbContext.cs:311-316) and set AuditTrail:Enabled so the table is + silent no-op, ApplicationDbContext.cs:316-321) and set AuditTrail:Enabled so the table is mapped. Then the entity must carry the marker. Nothing about the feature is on by default.
      • Position in the pipeline (AuditTrailSaveChangesInterceptor.cs:26-32): registered last, after AuditSaveChangesInterceptor, the optional TenantSaveChangesInterceptor and DomainEventSaveChangesInterceptor - (ApplicationDbContext.cs:292-316), so the values it diffs are final: the audit stamps are + (ApplicationDbContext.cs:297-321), so the values it diffs are final: the audit stamps are already written when it runs.
      • Correlation is the trace id, not the scoped correlation context (AuditTrailSaveChangesInterceptor.cs:44-55): this interceptor is a singleton and the only @@ -3342,7 +3503,7 @@

        AuditTrailSaveChangesInterceptor

        when a request carries no X-Correlation-ID header. A caller-supplied header value is therefore NOT recorded today.
      • Tenant is read off the context (AuditTrailSaveChangesInterceptor.cs:56-60), through the live - accessor the scoped context factory assigns (ApplicationDbContext.cs:141), so it does reach the + accessor the scoped context factory assigns (ApplicationDbContext.cs:154), so it does reach the interceptor where a scoped service would not.
    367. @@ -3350,7 +3511,7 @@

      AuditTrailSaveChangesInterceptor

    368. Constants (AuditTrailSaveChangesInterceptor.cs:65-87): the three operation names, the four column widths (MaxEntityTypeLength 256, MaxKeyLength 128, MaxCorrelationIdLength 64, MaxTenantIdLength 64) and the | composite-key separator. They match the model mapping in - ApplicationDbContext.ConfigureAuditTrail exactly (ApplicationDbContext.cs:855-861).
    369. + ApplicationDbContext.ConfigureAuditTrail exactly (ApplicationDbContext.cs:858-864).
    370. Static state (AuditTrailSaveChangesInterceptor.cs:89-120): two ConditionalWeakTables keyed by context (pending key fix-ups, and a marker for "a capture staged rows but the save has not finished"), a ConcurrentDictionary caching the PII verdict per (declaring type, property name), @@ -3358,7 +3519,7 @@

      AuditTrailSaveChangesInterceptor

      CLR type
      , not by the absence of the marker, which is what keeps the trail from recording its own rows in an unbounded feedback loop and keeps the outbox, inbox and job tables out of a history nobody asked for (AuditTrailSaveChangesInterceptor.cs:108-120). IsFrameworkEntity(Type) - (:171) is the internal window onto that set, which is both how ShouldAudit consults it and + (:172) is the internal window onto that set, which is both how ShouldAudit consults it and how a test can assert the exclusion list directly.
    371. The four overrides (AuditTrailSaveChangesInterceptor.cs:123-164): SavingChangesAsync and SavingChanges both call CaptureChanges; SavedChangesAsync and SavedChanges both run the @@ -3366,10 +3527,10 @@

      AuditTrailSaveChangesInterceptor

      context passes straight through.
    372. CaptureChanges (AuditTrailSaveChangesInterceptor.cs:178-220): the first statement is the cheap double gate, context.Model.FindEntityType(typeof(AuditTrailEntry)) is null - (:179-185), which covers both a host that never opted in and Cosmos (which skips relational + (:181-184), which covers both a host that never opted in and Cosmos (which skips relational tables), because Set<AuditTrailEntry>() would throw for both. Then it discards an abandoned capture, builds one CaptureContext, snapshots - ChangeTracker.Entries().Where(ShouldAudit).ToArray() (:197, materialized before adding, + ChangeTracker.Entries().Where(ShouldAudit).ToArray() (:198, materialized before adding, because enumerating the tracker lazily while adding to it would throw), and captures each entry.
    373. ShouldAudit (AuditTrailSaveChangesInterceptor.cs:226-229): the entity carries the marker, is not a framework bookkeeping type, and is in state Added, Modified or Deleted.
    374. @@ -3379,9 +3540,9 @@

      AuditTrailSaveChangesInterceptor

    375. CaptureModifiedProperties (AuditTrailSaveChangesInterceptor.cs:278-319): one row per property that is IsModified and whose value actually differs. A property EF flagged as modified but whose value is unchanged (the whole-entity Update idiom) writes nothing - (:272-276). PiiRedactor.HasPii(entry.Metadata.ClrType) is checked once per entity so a type - with no personal data skips the per-property attribute lookup entirely (:286), and a PII - property records PiiRedactor.RedactedToken on both sides (:309-310).
    376. + (:273-277). PiiRedactor.HasPii(entry.Metadata.ClrType) is checked once per entity so a type + with no personal data skips the per-property attribute lookup entirely (:287), and a PII + property records PiiRedactor.RedactedToken on both sides (:310-311).
    377. DiscardAbandonedCapture (AuditTrailSaveChangesInterceptor.cs:340-361): the retry-safety mechanic. An execution strategy that retries a failed save re-runs SavingChanges against a tracker that still holds the previous attempt's Added rows, so without this one transient SQL @@ -3397,12 +3558,16 @@

      AuditTrailSaveChangesInterceptor

      then brings the tracked instance and its snapshot in line so a later save does not re-issue the update, and the ordering there is load-bearing: writing OriginalValue must precede clearing IsModified, because clearing the flag reverts the current value to the original.
    378. -
    379. Helpers: AddRow (:325-331, mutation is Add only, because the save runs with automatic +
    380. Helpers: AddRow (:326-332, mutation is Add only, because the save runs with automatic change detection off), HasTemporaryKey (:449-466), BuildEntityKey (:473-485, a keyless - entity yields an empty string rather than throwing), IsPiiProperty (:492-504, a shadow - property has no PropertyInfo so it can never be personal data), FormatValue (:510-511, + entity yields an empty string rather than throwing), IsPiiProperty (:493-507, a shadow + property has no PropertyInfo so it can never be personal data; the verdict uses the static + Attribute.IsDefined(propertyInfo, typeof(PiiAttribute), inherit: true) at :505, because the + PropertyInfo.IsDefined instance method ignores inherit for properties, and only the static form + walks an override back to its base declaration, matching how PiiRedactor decides, comment at + :503-504), FormatValue (:513-514, CultureInfo.InvariantCulture on purpose, so a trail read years later or on a differently - localized replica shows the value that was written) and ValuesEqual (:517-530, byte arrays + localized replica shows the value that was written) and ValuesEqual (:520-533, byte arrays such as row versions are compared by content, since reference equality would report every save as a change). Column-width truncation itself is no longer a private method on this type: every call site (the correlation id and tenant id in CaptureChanges at :193-194, the entity type in @@ -3424,7 +3589,7 @@

      AuditTrailSaveChangesInterceptor

      (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Jobs.cs:115-117, singleton for the same reason as the other two save interceptors: stateless, with per-save state in a ConditionalWeakTable keyed by context), added to the EF pipeline in - ApplicationDbContext.OnConfiguring (ApplicationDbContext.cs:311-316), and registered in the + ApplicationDbContext.OnConfiguring (ApplicationDbContext.cs:316-321), and registered in the design-time pipeline too so dotnet ef matches runtime (DesignTimeDbContextHelper, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextHelper.cs:165-167). @@ -3441,80 +3606,6 @@

      AuditTrailSaveChangesInterceptor

      introduced for TenantId (AuditTrailSaveChangesInterceptor.cs:52-54). Whether that will be done is Not determinable from source.
    381. -

      AuditTrailCleanupJob

      -
      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.AuditTrail · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailCleanupJob.cs:47 · Level 13 · class (internal sealed partial)

      -
      -
        -
      • What it is: the framework's own recurring job. It purges - AuditTrailEntry rows older than the configured retention window from every - relational data source in use, in batches.
      • -
      • Depends on: IScheduledJob (the contract it - implements), IDbContextFactory, - IEntityDataSourceRegistry, - AuditTrailSettings and - TenancySettings via IOptions<>, - ITenantContext, - TenantDataSourceTargets plus - TenantDataSourceTarget, DataSource, TimeProvider, - IServiceScopeFactory and ILogger<T>.
      • -
      • Concept introduced, retention as a first-class part of a history feature. [Rubric §30, Compliance, Privacy & Data Governance] (assesses whether retained data has a bounded lifetime), - [Rubric §31, Cost & FinOps] (assesses whether unbounded growth is designed out) and [Rubric §29, Resilience & Business Continuity] (a first sweep over a neglected table must not become one - enormous transaction). The class comment makes the argument (AuditTrailCleanupJob.cs:17-21): a - change trail grows monotonically and stores values that may describe a data subject, so a retention - window is not housekeeping, it is what keeps the table from being both a cost centre and a - data-protection liability. It also records the honest limitation - (AuditTrailCleanupJob.cs:22-27): AddAuditTrail registers the job, but a job with no runner never - executes, so the host must also call AddScheduledJobs and set Scheduler:Enabled. A host that - records the trail without the scheduler is fully supported and keeps recording; pruning is then the - operator's job.
      • -
      • Walkthrough:
          -
        • Constructor (AuditTrailCleanupJob.cs:47-54): the last two parameters are optional. The scope - factory and the tenancy options default to null, because a host without tenancy never leaves the - job's own scope. _settings is snapshotted from options.Value (:60), and RetentionDays - defaults to 90 with a [Range(1, 3650)] guard - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSettings.cs:36-37).
        • -
        • Name and CronExpression (AuditTrailCleanupJob.cs:62,66): "audit-trail-cleanup", daily - at 0 3 * * *, chosen to sit off the daily peak for every time zone the framework runs in.
        • -
        • ExecuteAsync (AuditTrailCleanupJob.cs:69-82): returns immediately when - AuditTrail:Enabled is false (:71-74), computes the cutoff as now minus RetentionDays - (:76), then sweeps every target - TenantDataSourceTargets.ExpandRelational returns (:78-81); that - helper is what filters DataSource.CosmosDB out of the physical sources in use and expands the - rest into per-tenant targets, one call replacing what used to be inline Where/Distinct plus a - separate Expand call.
        • -
        • PurgeTargetAsync (AuditTrailCleanupJob.cs:89-106): the shared database is swept on the - job's own scope; a tenant that keeps its own database gets a fresh scope from - TenantDataSourceTargets.CreateTenantScope (:100), the shared - extension that sets ITenantContext before the - context is asked for, because the scoped context factory binds one database per scope and the - job's scope is already bound to the shared one.
        • -
        • PurgeWithFactoryAsync (AuditTrailCleanupJob.cs:109-130): resolves the target's context and - re-checks the model for the trail entity (:125-128). Cosmos is already excluded, but a - relational source can still lack the table when the host disabled the trail after writing it, so - the model, not the configuration, is the authority.
        • -
        • PurgeSourceAsync (AuditTrailCleanupJob.cs:136-173): the batching loop. It reads up to - BatchSize (1000, :58) ids with AsNoTracking, ordered by ChangedOn, then deletes those ids - with ExecuteDeleteAsync. The comment at :151-153 explains the two-step shape: every relational - provider translates an IN list, while a limited DELETE is not universally supported. The loop - ends when a page comes back empty (:162-165) or short (:172-175), and it re-checks the - cancellation token each turn (:149). No row is ever materialized as an entity.
        • -
        • Logging (AuditTrailCleanupJob.cs:175-176): a source-generated LoggerMessage emitted only - when a sweep actually removed rows (:131-135), so a quiet night logs nothing.
        • -
        -
      • -
      • Why it's built this way: registering the job in AddAuditTrail rather than in - AddScheduledJobs keeps the two features independent, which the DI comment states outright - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Jobs.cs:121-123): the trail can - be enabled without the scheduler and the scheduler without the trail. Set-based batched deletion is - the same discipline the outbox retention sweep uses, for the same reason.
      • -
      • Where it's used: registered through AddScheduledJob<AuditTrailCleanupJob>() inside - AddAuditTrail (DependencyInjection.Jobs.cs:124, and that helper does a TryAddScoped of the concrete - type plus a TryAddEnumerable of IScheduledJob at :75-76), and executed by - ScheduledJobRunner when the host runs - the scheduler. Covered by - MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailCleanupJobTests.cs.
      • -

      AuditTrailReader

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.AuditTrail · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailReader.cs:34 · Level 13 · class (internal sealed)

      @@ -3549,7 +3640,7 @@

      AuditTrailReader

      environment, so "not enabled" must read as "no history", not as an exception.
    382. The query (AuditTrailReader.cs:60-80): AsNoTracking, filtered on EntityType and EntityKey (exactly the leading columns of IX_AuditTrailEntries_Entity, - ApplicationDbContext.cs:864-865), ordered, skipped and taken, then projected column by column + ApplicationDbContext.cs:867-868), ordered, skipped and taken, then projected column by column into the DTO (AuditTrailReader.cs:66-78). TenantId is not projected, because the DTO has no such member: it declares Id through CorrelationId and stops there (MMCA.Common/Source/Core/MMCA.Common.Application/Auditing/AuditTrailEntryDTO.cs:12-48).
    383. @@ -3558,7 +3649,7 @@

      AuditTrailReader

    384. Why it's built this way: the DTO stops the persistence entity from leaking into the Application contract, and matching the query's predicate and sort to the shipped index is why that index carries the whole predicate plus the sort rather than just the key - (ApplicationDbContext.cs:862-865).
    385. + (ApplicationDbContext.cs:865-868).
    386. Where it's used: registered as the scoped IAuditTrailReader by AddAuditTrail (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Jobs.cs:119). Covered by @@ -3595,54 +3686,61 @@

      EFRefreshSessionStore

    387. Why it's built this way: every read here is tracked on purpose, and the class doc says why (EFRefreshSessionStore.cs:24-28): the caller revokes by mutating the instances this returns, so a no-tracking query would take those revocations and silently drop them at save time. That is the opposite of the usual read-path default in this codebase and worth remembering. Resolving the database through the registry first and the setting second means a single-database host needs no configuration at all (RefreshSessions:DataSourceName defaults to Default at MMCA.Common/Source/Core/MMCA.Common.Application/Auth/RefreshSessionSettings.cs:52) while a multi-database host names the Identity source once. Pointing it at a database that does not map the table fails loudly on the first query rather than reading the wrong rows (EFRefreshSessionStore.cs:21-22). The policy is ADR-097, building on ADR-050.
    388. -
    389. Where it's used: registered as the scoped IRefreshSessionStore (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:166), scoped deliberately, like the unit of work it shares a DbContext with, so a login and its session insert commit together (:143-144). The consumer is AuthenticationServiceBase<TUser>, which holds it as RefreshSessions (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:81, :88) and drives rotation, per-session revoke and sign-out-everywhere through it. DeleteUserHandlerBase<TUser, TCommand> revokes a deleted user's sessions through the same store from its soft-delete tail (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:106).
    390. +
    391. Where it's used: registered as the scoped IRefreshSessionStore (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:172), scoped deliberately, like the unit of work it shares a DbContext with, so a login and its session insert commit together (:143-144). The consumer is AuthenticationServiceBase<TUser>, which holds it as RefreshSessions (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:81, :88) and drives rotation, per-session revoke and sign-out-everywhere through it. DeleteUserHandlerBase<TUser, TCommand> revokes a deleted user's sessions through the same store from its soft-delete tail (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:110).
    392. EFPermissionGrantStore

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Auth · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/EFPermissionGrantStore.cs:34 · Level 13 · class (internal sealed)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Auth · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/EFPermissionGrantStore.cs:35 · Level 13 · class (internal sealed)

      • What it is: the EF Core implementation of IPermissionGrantStore, the persistence side of - stored permission grants (EFPermissionGrantStore.cs:34-395). It lists, grants and revokes rows in + stored permission grants (EFPermissionGrantStore.cs:35-151). It lists, grants and revokes rows in the PermissionGrants table.
      • Depends on: IDbContextFactory (context access and saving), IEntityDataSourceRegistry and IDataSourceResolver (source resolution), - IOptions<PermissionGrantSettings>, and TimeProvider, all primary-constructor parameters - (EFPermissionGrantStore.cs:290-295).
      • + IOptions<PermissionGrantSettings>, TimeProvider, and + IUniqueConstraintViolationDetector (classifies the + unique-index failure a concurrent duplicate grant raises), all primary-constructor parameters + (EFPermissionGrantStore.cs:35-41).
      • Concept introduced, check-then-act made harmless by a unique index, rather than a database-level conditional update. [Rubric §11, Security] (assesses whether a duplicate grant is handled deliberately) and [Rubric §8, Data Architecture]. Unlike EFRefreshSessionStore's rotation claim, granting a permission is idempotent by nature (a role either has a permission or it does not), so GrantAsync uses an ordinary check-then-act: query for an existing row, and only insert when none is found - (EFPermissionGrantStore.cs:350-358). The comment names why that race is harmless here - (:346-349): the unique index on (Role, Permission) is what makes a concurrent duplicate insert - fail at the database rather than corrupt the read, and the row the winner wrote says exactly what - the loser was trying to say. The check exists to make the ordinary duplicate free, not to be the - guarantee.
      • + (EFPermissionGrantStore.cs:96-117). The comment names why that race is harmless here + (:92-95): the unique index on (Role, Permission) makes a concurrent duplicate insert fail at + the database, the loser catches that failure and answers success, because the row the winner wrote + says exactly what the loser was trying to say. The check exists to make the ordinary duplicate + free, not to be the guarantee.
      • Walkthrough
          -
        • Context (EFPermissionGrantStore.cs:297) resolves the context per access through the factory, - keyed on ResolveDataSourceKey(); Grants (:299) is the DbSet<PermissionGrant> over it.
        • -
        • GetAllAsync (:302-308) and GetPermissionsAsync(role, ct) (:311-324) are both AsNoTracking +
        • Context (EFPermissionGrantStore.cs:43) resolves the context per access through the factory, + keyed on ResolveDataSourceKey(); Grants (:45) is the DbSet<PermissionGrant> over it.
        • +
        • GetAllAsync (:48-54) and GetPermissionsAsync(role, ct) (:57-70) are both AsNoTracking reads, the first ordered by Role then Permission for the administration listing, the second filtered to one role and projected to bare permission strings for PermissionGrantCache's rebuild.
        • -
        • GrantAsync(role, permission, grantedBy, ct) (:327-364) constructs a PermissionGrant through - its Create factory, returns the factory's failure untouched on invalid input (:339-342), then +
        • GrantAsync(role, permission, grantedBy, ct) (:73-120) constructs a PermissionGrant through + its Create factory, returns the factory's failure untouched on invalid input (:85-88), then runs the check-then-act described above: an existing row returns success without writing anything - (:355-358), otherwise the grant is added and saved (:360-363).
        • -
        • RevokeAsync(role, permission, ct) (:367-384) is a set-based ExecuteDeleteAsync filtered on - both columns (:378-381), never a load-then-remove: there is at most one row by the unique index, + (:101-104), otherwise the grant is added to the resolved context and saved (:106-111). A + DbUpdateException that the detector classifies as a unique-constraint violation is caught + (:112): the insert lost the race to a concurrent duplicate, so its entry is set to + EntityState.Detached (:116, comment at :114-115) so the scoped context does not retry the + failed insert on its next save, and the method still returns success. Any other database failure + propagates.
        • +
        • RevokeAsync(role, permission, ct) (:123-140) is a set-based ExecuteDeleteAsync filtered on + both columns (:134-137), never a load-then-remove: there is at most one row by the unique index, nothing about it needs to be read, and removing zero rows is exactly the success an idempotent - revoke promises (comment at :375-377).
        • -
        • ResolveDataSourceKey (:389-394) is the same two-leg routing shape as + revoke promises (comment at :131-133).
        • +
        • ResolveDataSourceKey (:145-150) is the same two-leg routing shape as EFRefreshSessionStore's: the entity registry first (so a consumer entity configuration for PermissionGrant routes it like any other entity), otherwise a key built from the resolver's engine for Default plus the configured DataSourceName. Only the engine goes through the resolver; the configured name is used verbatim, so a host that configures no SQL Server connection string gets the engine it does configure rather than a context over an empty - connection string (comment at :386-388).
        • + connection string (comment at :142-144).
      • Why it's built this way: the store is intentionally the thinnest possible layer over the model @@ -3652,7 +3750,7 @@

        EFPermissionGrantStore

        PermissionGrantCache is the layer that owns performance and this one owns correctness against the database.
      • Where it's used: registered as the scoped IPermissionGrantStore by AddStoredPermissionGrants - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:114). Listed as a + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:118). Listed as a sanctioned soft-delete exception alongside the other framework bookkeeping stores in both MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Domain/SoftDeleteEnforcementTests.cs and MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/SoftDeleteEnforcementTests.cs, @@ -3696,9 +3794,87 @@

        RefreshSessionCleanupService

    393. Why it's built this way: retention bounds reuse detection, and the class doc is explicit about the trade (RefreshSessionCleanupService.cs:24-32). BR-206 catches a replayed refresh token by finding its revoked row and revoking the whole family; a rotation chain older than the window is gone, so a replay of a token that old reads as an unknown token and fails alone instead of signalling reuse. The default window of 30 days (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/RefreshSessionSettings.cs:72) is far past the default refresh-token lifetime, so every token still capable of being replayed still has its row, and a host that shortens RefreshSessions:RetentionDays below Jwt:RefreshTokenExpirationDays is choosing to lose that signal. Unlike the outbox, sessions live in exactly one physical source, which is why the sweep resolves one database rather than iterating them (:33-39). The sweep is the retention half of the 2026-08-27 revision of ADR-097.
    394. -
    395. Where it's used: registered as a hosted service only when RefreshSessions:Enabled is true, read straight off configuration at registration time (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:171-174). The comment there gives the reason for the conditional (:151-153): registering it unconditionally would start a sweep in every service of a modular host, all but one of which has no RefreshSessions table to sweep. Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Auth/RefreshSessionCleanupServiceTests.cs, which exercises both disabled paths, the retention predicate, the unmapped-table warning and the registration gate (:189, :201, :218).
    396. +
    397. Where it's used: registered as a hosted service only when RefreshSessions:Enabled is true, read straight off configuration at registration time (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:181-184). The comment there gives the reason for the conditional (:151-153): registering it unconditionally would start a sweep in every service of a modular host, all but one of which has no RefreshSessions table to sweep. Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Auth/RefreshSessionCleanupServiceTests.cs, which exercises both disabled paths, the retention predicate, the unmapped-table warning and the registration gate (:189, :201, :218).
    398. Caveats / not-in-source: the default interval is 6 hours (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/RefreshSessionSettings.cs:80, constrained to 1 through 168), and because the loop waits one full interval before its first sweep, a host that restarts more often than the configured interval never sweeps. Nothing in the service compensates for that.
    399. +

      AuditTrailCleanupJob

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.AuditTrail · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailCleanupJob.cs:45 · Level 16 · class (internal sealed partial)

      +
      +
        +
      • What it is: the framework's own recurring job. It purges + AuditTrailEntry rows older than the configured retention window from every + relational database that holds a trail table, in batches.
      • +
      • Depends on: IScheduledJob (the contract it + implements), IDbContextFactory, + FrameworkTableTargets (which databases hold the table), + AuditTrailSettings via IOptions<>, + TenantDataSourceTargets (for CreateTenantScope) plus + TenantDataSourceTarget, TimeProvider, IServiceScopeFactory and + ILogger<T> (AuditTrailCleanupJob.cs:45-51).
      • +
      • Concept introduced, retention as a first-class part of a history feature. [Rubric §30, Compliance, Privacy & Data Governance] (assesses whether retained data has a bounded lifetime), + [Rubric §31, Cost & FinOps] (assesses whether unbounded growth is designed out) and [Rubric §29, Resilience & Business Continuity] (a first sweep over a neglected table must not become one + enormous transaction). The class comment makes the argument (AuditTrailCleanupJob.cs:16-20): a + change trail grows monotonically and stores values that may describe a data subject, so a retention + window is not housekeeping, it is what keeps the table from being both a cost centre and a + data-protection liability. It also records the honest limitation + (AuditTrailCleanupJob.cs:21-26): AddAuditTrail registers the job, but a job with no runner never + executes, so the host must also call AddScheduledJobs and set Scheduler:Enabled. A host that + records the trail without the scheduler is fully supported and keeps recording; pruning is then the + operator's job.
      • +
      • Walkthrough:
          +
        • Constructor (AuditTrailCleanupJob.cs:45-51): the last parameter, the scope factory, is + optional and defaults to null, because a host without tenancy never leaves the job's own scope. + Tenancy settings are no longer a parameter here: tenant discovery moved into + FrameworkTableTargets. _settings is snapshotted from + options.Value (:56), and RetentionDays defaults to 90 with a [Range(1, 3650)] guard + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSettings.cs:36-37).
        • +
        • Name and CronExpression (AuditTrailCleanupJob.cs:59,63): "audit-trail-cleanup", daily + at 0 3 * * * (03:00 UTC), chosen to sit off the daily peak for every time zone the framework runs + in (:62).
        • +
        • ExecuteAsync (AuditTrailCleanupJob.cs:66-79): returns immediately when + AuditTrail:Enabled is false (:68-71), computes the cutoff as now minus RetentionDays + (:73), then sweeps every target tableTargets.Relational() returns (:75-78). That call + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/FrameworkTableTargets.cs:43-44) + takes every relational physical source backing a registered entity, deduplicates it and expands it + per tenant that keeps its own copy, skipping Cosmos; it is the one answer the outbox processor, the + internal-command processor, both cleanup services, both operator surfaces and this sweep share, + rather than each re-deriving it from the registry, the resolver and the tenancy settings + (FrameworkTableTargets.cs:16-18).
        • +
        • PurgeTargetAsync (AuditTrailCleanupJob.cs:86-103): the shared database is swept on the + job's own scope; a tenant that keeps its own database gets a fresh scope from + TenantDataSourceTargets.CreateTenantScope (:97), the shared + extension that sets ITenantContext before the + context is asked for, because the scoped context factory binds one database per scope and the + job's scope is already bound to the shared one (doc at :81-85).
        • +
        • PurgeWithFactoryAsync (AuditTrailCleanupJob.cs:106-127): resolves the target's context and + re-checks the model for the trail entity (:116-119, comment at :114-115). Cosmos is already + excluded, but a relational source can still lack the table when the host disabled the trail after + writing it, so the model, not the configuration, is the authority.
        • +
        • PurgeSourceAsync (AuditTrailCleanupJob.cs:133-170): the batching loop. It reads up to + BatchSize (1000, :54) ids with AsNoTracking, ordered by ChangedOn, then deletes those ids + with ExecuteDeleteAsync. The comment at :142-144 explains the two-step shape: every relational + provider translates an IN list, while a limited DELETE is not universally supported. The loop + ends when a page comes back empty (:153-156) or short (:163-166), and it re-checks the + cancellation token each turn (:140). No row is ever materialized as an entity.
        • +
        • Logging (AuditTrailCleanupJob.cs:172-173): a source-generated LoggerMessage emitted only + when a sweep actually removed rows (:122-126), so a quiet night logs nothing.
        • +
        +
      • +
      • Why it's built this way: registering the job in AddAuditTrail rather than in + AddScheduledJobs keeps the two features independent, which the DI comment states outright + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Jobs.cs:121-123): the trail can + be enabled without the scheduler and the scheduler without the trail. Set-based batched deletion is + the same discipline the outbox retention sweep uses, for the same reason, and taking its target list + from FrameworkTableTargets means this sweep visits exactly the databases + the other framework-table sweeps visit.
      • +
      • Where it's used: registered through AddScheduledJob<AuditTrailCleanupJob>() inside + AddAuditTrail (DependencyInjection.Jobs.cs:124, and that helper does a TryAddScoped of the concrete + type plus a TryAddEnumerable of IScheduledJob at :75-76), and executed by + ScheduledJobRunner when the host runs + the scheduler. Covered by + MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/AuditTrail/AuditTrailCleanupJobTests.cs.
      • +

      ColumnWidth

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Conversions · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conversions/ColumnWidth.cs:4 · Level 0 · class (internal static)

      @@ -3706,9 +3882,9 @@

      ColumnWidth

      • What it is: an internal static helper with one method, Truncate(string? value, int maxLength), that cuts a string to a column's maximum length while preserving null (ColumnWidth.cs:4-12).
      • Depends on: nothing beyond the BCL string indexer and range operator.
      • -
      • Concept introduced, a defensive width guard for audit and command persistence rather than a value-object mapping. Both call sites write free-form diagnostic text (a trace ID, a CLR type name, a property key, a joined key string) into fixed-width columns where a value produced at runtime could exceed the column's declared size. Rather than let SQL reject the write or truncate silently at the provider, Truncate clips the string before it reaches EF: value is null || value.Length <= maxLength ? value : value[..maxLength] (ColumnWidth.cs:31-32). A null input passes straight through, so an absent value is never coerced into an empty string.
      • +
      • Concept introduced, a defensive width guard for audit and command persistence rather than a value-object mapping. Both call sites write free-form diagnostic text (a trace ID, a CLR type name, a property key, a joined key string) into fixed-width columns where a value produced at runtime could exceed the column's declared size. Rather than let SQL reject the write or truncate silently at the provider, Truncate clips the string before it reaches EF: value is null || value.Length <= maxLength ? value : value[..maxLength] (ColumnWidth.cs:10-11). A null input passes straight through, so an absent value is never coerced into an empty string.
      • Walkthrough
          -
        • Truncate (ColumnWidth.cs:31-32): a single expression-bodied method; no allocation happens when the value already fits.
        • +
        • Truncate (ColumnWidth.cs:10-11): a single expression-bodied method; no allocation happens when the value already fits.
      • Why it's built this way: the callers (an audit interceptor and internal command processing) run outside a validated domain type, so nothing upstream already guarantees the string fits its column; ColumnWidth.Truncate is the one place that guarantee is enforced before the row is written.
      • @@ -3734,7 +3910,7 @@

        UtcDateTimeConverter

        MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Conversions · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conversions/UtcDateTimeConverter.cs:25 · Level 0 · class (internal sealed)

        -
      • What it is: an internal ValueConverter<DateTime, DateTime> that normalizes every DateTime written to PostgreSQL to DateTimeKind.Utc, and stamps the same kind back onto values read from the column (UtcDateTimeConverter.cs:25-73).
      • +
      • What it is: an internal ValueConverter<DateTime, DateTime> that normalizes every DateTime written to PostgreSQL to DateTimeKind.Utc, and stamps the same kind back onto values read from the column (UtcDateTimeConverter.cs:25-40).
      • Depends on: only the BCL DateTime/DateTimeKind and EF Core's ValueConverter<TModel, TProvider> (UtcDateTimeConverter.cs:1, implied by the file's imports).
      • Concept introduced, a provider-specific kind fixup that is not a value-object mapping. Unlike the converters above it, this one maps DateTime to itself: the model and provider CLR types are identical, and the transformation is purely about the Kind flag Npgsql checks before it will accept a timestamp with time zone write. [Rubric §8, Data Architecture] covers exactly this: the framework's committed answer to "every DateTime is mapped timestamp with time zone... never the process-wide Npgsql.EnableLegacyTimestampBehavior switch" (see MMCA.Common/CLAUDE.md, Multi-Database Strategy) is a per-property converter rather than a global legacy-behavior opt-out, so the fix is scoped to PostgreSQL alone and does not alter how SQL Server, SQLite, or Cosmos treat DateTime.
      • Walkthrough
          @@ -3787,12 +3963,12 @@

          EnumerationValueConverter<TEnu

      • What it is: the shipped EF Core ValueConverter<TEnumeration, int> that stores a smart-enumeration member as its integer Value and rebuilds the member when a row is read back (EnumerationValueConverter.cs:33). It is the packaged replacement for the two lambdas every entity configuration would otherwise hand-roll to map an Enumeration<TEnumeration> property.
      • -
      • Depends on: Enumeration<TEnumeration> (the generic constraint, EnumerationValueConverter.cs:34), its Value property (MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:104) and its static FromValue factory (Enumeration.cs:120); Result<T> indirectly, because FromValue returns one; EF Core's ValueConverter<TModel, TProvider> from Microsoft.EntityFrameworkCore.Storage.ValueConversion (NuGet, EnumerationValueConverter.cs:1).
      • +
      • Depends on: Enumeration<TEnumeration> (the generic constraint, EnumerationValueConverter.cs:34), its Value property (MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:105) and its static FromValue factory (Enumeration.cs:121); Result<T> indirectly, because FromValue returns one; EF Core's ValueConverter<TModel, TProvider> from Microsoft.EntityFrameworkCore.Storage.ValueConversion (NuGet, EnumerationValueConverter.cs:1).
      • Concept introduced, mapping a smart enumeration onto the column a CLR enum already used. A smart enumeration is a class, not a language enum, so the naive EF answer is OwnsOne: the member becomes an owned entity type with its own columns, which turns "replace the enum property with a richer type" into a schema change and a migration. HasConversion takes the other route. It keeps a single flat column and supplies a pair of expression trees: a write leg that turns the model value into the provider value, and a read leg that turns the provider value back. Because this converter's provider type is int (EnumerationValueConverter.cs:33), the backing column stays exactly the plain integer column a CLR enum was already persisted into, so adopting the smart enumeration in the domain changes nothing in the database. The class doc states this as the reason for the choice (EnumerationValueConverter.cs:6-11). [Rubric §4, Domain-Driven Design] assesses whether the domain models concepts as behavior-carrying types rather than primitives or bare enums; a framework-shipped converter removes the storage-cost argument against doing so. [Rubric §8, Data Architecture] assesses how deliberately the storage shape is chosen; the flat int column keeps indexes, existing rows, and prior migrations untouched.
      • Walkthrough
        • Type parameters and constraint (EnumerationValueConverter.cs:33-34): ValueConverter<TEnumeration, int> with where TEnumeration : Enumeration<TEnumeration>, the curiously-recurring constraint that makes FromValue resolve to the concrete member type rather than to the base.
        • -
        • Constructor, write leg (EnumerationValueConverter.cs:41): member => member.Value, the member's declared stable integer (Enumeration.cs:104). No validation happens here: a member reference is valid by construction, since the only members that exist are the ones the type declares.
        • -
        • Constructor, read leg (EnumerationValueConverter.cs:42): value => Enumeration<TEnumeration>.FromValue(value).Value!. FromValue looks the value up in the type's interned member dictionary and returns a success result (Enumeration.cs:122-123) or an invariant failure coded Enumeration.UnknownValue (Enumeration.cs:125-129). Because Result<T>.Value is declared nullable and simply returns null on failure rather than throwing (MMCA.Common/Source/Core/MMCA.Common.Shared/Abstractions/Result.cs:206-207), the null-forgiving ! means a row carrying a value no member declares materializes a null reference for that property instead of failing materialization.
        • +
        • Constructor, write leg (EnumerationValueConverter.cs:41): member => member.Value, the member's declared stable integer (Enumeration.cs:105). No validation happens here: a member reference is valid by construction, since the only members that exist are the ones the type declares.
        • +
        • Constructor, read leg (EnumerationValueConverter.cs:42): value => Enumeration<TEnumeration>.FromValue(value).Value!. FromValue looks the value up in the type's interned member dictionary and returns a success result (Enumeration.cs:123-124) or an invariant failure coded Enumeration.UnknownValue (Enumeration.cs:126-130). Because Result<T>.Value is declared nullable and simply returns null on failure rather than throwing (MMCA.Common/Source/Core/MMCA.Common.Shared/Abstractions/Result.cs:206-207), the null-forgiving ! means a row carrying a value no member declares materializes a null reference for that property instead of failing materialization.
        • The read-leg contract, spelled out (EnumerationValueConverter.cs:23-30): the read leg trusts the column. Every value the write leg can produce round-trips, because the write leg can only persist an already-declared member; the contract can only be broken by a value written outside EF (a manual script, a data fix, or a member deleted from the enumeration after rows were written).
        • What it deliberately does not do (EnumerationValueConverter.cs:19-21): requiredness, default value, and precision stay at the call site, because they differ per entity. The documented usage chains .IsRequired() next to the HasConversion call (EnumerationValueConverter.cs:13-18).
        @@ -3899,7 +4075,7 @@

        ConnectionStringSettings

      • Concept introduced, fail-fast configuration where the rule spans two sections. [Rubric §15, Best Practices & Code Quality] assesses whether misconfiguration is caught early. AddOptions<T>().Bind(...).ValidateDataAnnotations().ValidateOnStart() - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:71-74) is the pattern + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:72-75) is the pattern every validated settings class uses, and ADR-070 makes it the required form: annotations run during host startup rather than lazily on first resolution. @@ -3911,7 +4087,7 @@

        ConnectionStringSettings

        property on one class, so the real invariant ("the host can reach SOME database") is expressed as an IValidateOptions<T> implementation registered alongside the binding, ConnectionStringSettingsValidator - (DependencyInjection.cs:81-82). [Rubric §8, Data Architecture]: the rule spans both configuration + (DependencyInjection.cs:82-83). [Rubric §8, Data Architecture]: the rule spans both configuration shapes precisely because the physical topology is a deployment decision, not a compile-time one.

      • Walkthrough: one static field and seven { get; init; } strings.

        @@ -3947,16 +4123,16 @@

        ConnectionStringSettings

      • Why it's built this way: init-only properties make the bound settings immutable after startup, which DataSourceResolver relies on, since it classifies every physical source - once in its constructor (DataSourceResolver.cs:58-76). Keeping the "some database" check at boot + once in its constructor (DataSourceResolver.cs:64-77). Keeping the "some database" check at boot rather than at first query is what stops a host from reporting healthy while unable to serve a request (ConnectionStringSettingsValidator.cs:20-24).

      • Where it's used: bound and validated in AddInfrastructure (DependencyInjection, - DependencyInjection.cs:71-82); consumed by DataSourceResolver through - IOptions<ConnectionStringSettings> (DataSourceResolver.cs:59, DataSourceResolver.cs:66), which - reads it while seeding each engine's Default source (DataSourceResolver.cs:201-236) and while - applying the Cosmos database-name fallback (DataSourceResolver.cs:256-257).

        + DependencyInjection.cs:72-83); consumed by DataSourceResolver through + IOptions<ConnectionStringSettings> (DataSourceResolver.cs:65, DataSourceResolver.cs:72), which + reads it while seeding each engine's Default source (DataSourceResolver.cs:240-275) and while + applying the Cosmos database-name fallback (DataSourceResolver.cs:289-290).

      • Caveats: the Cosmos database default was renamed from the application-specific "AtlDevCon" (the ADC conference database name) to the neutral "MMCA" (ConnectionStringSettings.cs:25); every default @@ -3990,7 +4166,7 @@

        DataSourceEntrySettings

      • CosmosConnectionString (DataSourceEntrySettings.cs:22) and CosmosDatabaseName (DataSourceEntrySettings.cs:25), the Cosmos pair; the database name falls back to the top-level CosmosDatabaseName when empty (DataSourceResolver applies that fallback - at DataSourceResolver.cs:256-257 and again at DataSourceResolver.cs:284-286).
      • + at DataSourceResolver.cs:289-290 and again at DataSourceResolver.cs:317-319).
      • PostgreSQLConnectionString (DataSourceEntrySettings.cs:28), the PostgreSQL connection string for this source (ADR-113).
      • PostgreSQLMigrationsAssembly (DataSourceEntrySettings.cs:35), documented as falling back to the @@ -4012,7 +4188,7 @@

        DataSourceEntrySettings

      • The resolver reads the relational pair through one engine-keyed switch, GetMigrationsAssembly (DataSourceResolver.cs:424-431), stamps the SQLite or PostgreSQL value onto the PhysicalDataSource via its object initializer, only for the matching engine - (DataSourceResolver.cs:421-423), and names the offending setting per engine when two logical names + (DataSourceResolver.cs:454-456), and names the offending setting per engine when two logical names collapse onto one database with conflicting values (DataSourceResolver.cs:450-457).
      • The XML doc carries a worked appsettings.json example for a Conference source (DataSourceEntrySettings.cs:9-18), which is the fastest way to see the intended shape.
      • @@ -4024,26 +4200,26 @@

        DataSourceEntrySettings

        (ADR-006).
      • Where it's used: bound as the value type of the dictionary that AddInfrastructure reads with configuration.GetSection(DataSourcesSettings.SectionName).Get<Dictionary<string, DataSourceEntrySettings>>() - (DependencyInjection.cs:85-87); consumed by DataSourceResolver when it - classifies logical names into physical sources (DataSourceResolver.cs:273-287) and when it tests - whether any entry names a database for an engine (DataSourceResolver.cs:135-140), and by + (DependencyInjection.cs:86-88); consumed by DataSourceResolver when it + classifies logical names into physical sources (DataSourceResolver.cs:306-320) and when it tests + whether any entry names a database for an engine (DataSourceResolver.cs:174-179), and by ConnectionStringSettingsValidator when it looks for any configured database (ConnectionStringSettingsValidator.cs:68-74).

      DefaultSeed

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:177 · Level 0 · record (sealed, private nested)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:216 · Level 0 · record (sealed, private nested)

      • What it is: a three-field carrier describing what the Default physical source of one engine is built from: ConnectionString, MigrationsAssembly, and CosmosDatabaseName - (DataSourceResolver.cs:177). It exists only inside DataSourceResolver, + (DataSourceResolver.cs:216). It exists only inside DataSourceResolver, where it is computed once per engine and then threaded through the four private methods that build that engine's map.
      • Depends on: nothing first-party. It holds three string values, all of which may legitimately be empty. It is produced from ConnectionStringSettings and DataSourcesSettings by - ResolveDefaultSeed (DataSourceResolver.cs:202-237).
      • + ResolveDefaultSeed (DataSourceResolver.cs:241-276).
      • Concept introduced, the two-branch answer to "which database is Default". [Rubric §8, Data Architecture] assesses whether the mapping from configuration to physical storage is explicit and has one owner; [Rubric §15, Best Practices & Code Quality] assesses whether a rule that several methods depend on is stated once instead of recomputed at each use. Default is the logical name every framework-owned @@ -4051,43 +4227,43 @@

        DefaultSeed

        answer even in a host that never wrote a top-level ConnectionStrings entry. The seed is that answer, and computing it into a record rather than passing three loose strings is what lets ClassifyEntries, RegisterDefaultSource, and RegisterNamedSource all agree on it - (DataSourceResolver.cs:161-168).
      • + (DataSourceResolver.cs:200-207).
      • Walkthrough
          -
        • ConnectionString (DataSourceResolver.cs:174, DataSourceResolver.cs:177) is the connection the +
        • ConnectionString (DataSourceResolver.cs:213, DataSourceResolver.cs:216) is the connection the Default source uses, and is empty when the engine is unconfigured. Branch one of ResolveDefaultSeed takes it straight from the top-level section when that section names a - connection for the engine (DataSourceResolver.cs:207-217). Branch two applies only when the + connection for the engine (DataSourceResolver.cs:246-256). Branch two applies only when the top-level value is absent: the named DataSources entries are filtered down to the ones carrying a - connection for this engine (DataSourceResolver.cs:219-221), their connection identities are - counted distinctly (DataSourceResolver.cs:223-226), and when exactly ONE distinct database is - named that database becomes Default (DataSourceResolver.cs:231-235). With several distinct + connection for this engine (DataSourceResolver.cs:258-260), their connection identities are + counted distinctly (DataSourceResolver.cs:262-265), and when exactly ONE distinct database is + named that database becomes Default (DataSourceResolver.cs:270-274). With several distinct databases and no top-level value there is no single answer, so the seed stays empty - (DataSourceResolver.cs:236) and a genuinely multi-database host names the shared one by adding a + (DataSourceResolver.cs:275) and a genuinely multi-database host names the shared one by adding a DataSources:Default entry.
        • -
        • MigrationsAssembly (DataSourceResolver.cs:175) is populated only on the top-level branch, and +
        • MigrationsAssembly (DataSourceResolver.cs:214) is populated only on the top-level branch, and only for SQL Server: ConnectionStrings carries no SQLite equivalent, and handing a SQLite Default source the SQL Server value in a mixed-engine host would scaffold the wrong schema - (DataSourceResolver.cs:207-210). Branch two deliberately leaves it empty - (DataSourceResolver.cs:228-230): every entry it considered has the seed's own connection + (DataSourceResolver.cs:246-249). Branch two deliberately leaves it empty + (DataSourceResolver.cs:267-269): every entry it considered has the seed's own connection identity, so those entries all collapse onto Default and contribute their declared assemblies through AddExplicitMigrationsAssemblies, conflicts included. A test pins that the single named entry's assembly still reaches the Default source that way (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/DataSourceResolverTests.cs:374).
        • -
        • CosmosDatabaseName (DataSourceResolver.cs:176) is the database name entries fall back to when - they declare none of their own, applied by CosmosDatabaseNameOf (DataSourceResolver.cs:257-258) - and again inline while classifying entries (DataSourceResolver.cs:284-286).
        • +
        • CosmosDatabaseName (DataSourceResolver.cs:215) is the database name entries fall back to when + they declare none of their own, applied by CosmosDatabaseNameOf (DataSourceResolver.cs:290-291) + and again inline while classifying entries (DataSourceResolver.cs:317-319).
      • Why it's built this way: branch two cannot change an existing host's routing, because it fires - only where the top-level value is absent (DataSourceResolver.cs:184-190). That is the + only where the top-level value is absent (DataSourceResolver.cs:223-229). That is the additive-by-construction property the whole data-source layer is built on (ADR-006): a host that declares its database only under DataSources gets a working Default, and a host that declares it the old way resolves exactly as it always did.
      • Where it's used: only within DataSourceResolver. BuildEngineMap - computes it (DataSourceResolver.cs:161) and passes it to ClassifyEntries - (DataSourceResolver.cs:162), RegisterDefaultSource (DataSourceResolver.cs:163), and - RegisterNamedSource (DataSourceResolver.cs:167). The two behaviors it decides are pinned by + computes it (DataSourceResolver.cs:200) and passes it to ClassifyEntries + (DataSourceResolver.cs:201), RegisterDefaultSource (DataSourceResolver.cs:202), and + RegisterNamedSource (DataSourceResolver.cs:206). The two behaviors it decides are pinned by DataSourceResolverTests.cs:374 and DataSourceResolverTests.cs:395.
      • Caveats / not-in-source: a private nested type. It is inventoried because private nested types are, but nothing outside the resolver can name it, and it never leaves the constructor's call graph.
      • @@ -4109,7 +4285,7 @@

        DataSourcesSettings

        AddInfrastructure builds the instance by hand from configuration.GetSection(...).Get<Dictionary<string, DataSourceEntrySettings>>() and registers it as a singleton (DataSourcesSettings.cs:8-11, - MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:83-87). With no options + MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:84-88). With no options pipeline there is no ValidateOnStart, so the constructor becomes the fail-fast gate. [Rubric §15, Best Practices & Code Quality]: rejecting a reserved name at construction is the same guarantee ValidateOnStart would have given, implemented where the type can enforce it itself. [Rubric §8, Data Architecture]: the reserved-name rule protects a real invariant, Default is configured through the top-level ConnectionStrings section, and letting someone also declare a DataSources:Default @@ -4134,11 +4310,11 @@

        DataSourcesSettings

        landing in the wrong database (ADR-006).
      • Where it's used: constructed and registered as a singleton in AddInfrastructure - (DependencyInjection.cs:85-87), immediately before DataSourceResolver and + (DependencyInjection.cs:86-88), immediately before DataSourceResolver and EntityDataSourceRegistry (DependencyInjection.cs:88-89). It is - consumed by the resolver's constructor (DataSourceResolver.cs:60, DataSourceResolver.cs:68-76), by - its "is any database configured for this engine" test (DataSourceResolver.cs:135-140) and its - classification pass (DataSourceResolver.cs:273-287), and by + consumed by the resolver's constructor (DataSourceResolver.cs:66, DataSourceResolver.cs:68-76), by + its "is any database configured for this engine" test (DataSourceResolver.cs:174-179) and its + classification pass (DataSourceResolver.cs:306-320), and by ConnectionStringSettingsValidator, which takes it as an optional constructor dependency so that a container binding the settings without AddInfrastructure still validates (ConnectionStringSettingsValidator.cs:26-30, @@ -4173,7 +4349,7 @@

        ConnectionStringSettingsValidator

        - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:76-80)
        .

        + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:77-81)
        .

        [Rubric §17, DevOps & Deployment] assesses whether configuration is validated centrally rather than defensively at each read. The check runs once, at boot, under ValidateOnStart, so no downstream component has to ask "did anyone configure a database".

        @@ -4227,8 +4403,8 @@

        ConnectionStringSettingsValidator

      • Where it's used: registered by AddInfrastructure with TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions<ConnectionStringSettings>, ConnectionStringSettingsValidator>()) - (DependencyInjection.cs:81-82), immediately after the ValidateOnStart chain that binds the section - (DependencyInjection.cs:71-74); TryAddEnumerable, like the tenancy validator, because two modules + (DependencyInjection.cs:82-83), immediately after the ValidateOnStart chain that binds the section + (DependencyInjection.cs:72-75); TryAddEnumerable, like the tenancy validator, because two modules calling AddInfrastructure must not run the same validation twice. Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Settings/ConnectionStringSettingsValidatorTests.cs, which drives the validator directly for each engine and for the named-source shapes, and end to end @@ -4264,7 +4440,7 @@

        IEntityDataSourceRegistry

        TryGetDataSourceKey is the non-throwing probe used where a miss is legitimate, for example when ApplicationDbContext decides whether an entity belongs in the model it is currently building - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:974-976).
      • + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:978-980).
      • Why it's built this way: database-per-service (ADR-006) needs every entity to resolve to exactly one physical source; deriving that map from configuration classes instead of from @@ -4272,13 +4448,13 @@

        IEntityDataSourceRegistry

        query.
      • Where it's used: implemented by EntityDataSourceRegistry and registered as a singleton in AddInfrastructure - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:90). Consumers include + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:92). Consumers include DbContextFactory - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:48), + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:50), CrossDataSourceDegradeConvention - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:35), + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:36), DataSourceService - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceService.cs:11), the + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceService.cs:12), the OutboxProcessor (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:61), the OutboxCleanupService @@ -4286,76 +4462,9 @@

        IEntityDataSourceRegistry

        the AuditTrailCleanupJob (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailCleanupJob.cs:49), both model-building passes of ApplicationDbContext - (ApplicationDbContext.cs:381, ApplicationDbContext.cs:966), and the startup + (ApplicationDbContext.cs:386, ApplicationDbContext.cs:962)
        , and the startup database-initialization path - (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:53).
      • -
      -

      PhysicalDataSource

      -
      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/PhysicalDataSource.cs:20 · Level 2 · record (sealed)

      -
      -
        -
      • What it is: the fully-resolved connection information for one physical database. Four positional - members, Key (its engine plus name identity), ConnectionString, MigrationsAssembly? and - CosmosDatabaseName (PhysicalDataSource.cs:20-24), plus the computed UsesMigrations - (PhysicalDataSource.cs:41-47).
      • -
      • Depends on: DataSourceKey and, through it, DataSource.
      • -
      • Concept, a logical name resolved into a real connection. [Rubric §8, Data Architecture] covers - the step from a configured name like DataSources:Conference to an actual database. The record's doc - comment (PhysicalDataSource.cs:5-9) explains that it is produced by - IDataSourceResolver from the top-level ConnectionStrings section (the - Default source) plus the named DataSources entries. MigrationsAssembly is read from the - configuration key of the source's own engine, SQLServerMigrationsAssembly, - SqliteMigrationsAssembly, or PostgreSQLMigrationsAssembly, and is always null for Cosmos, which - migrates nothing (PhysicalDataSource.cs:12-15); CosmosDatabaseName is ignored for relational - engines (PhysicalDataSource.cs:19). A physical source belongs to exactly one engine, so one slot is - enough, and the resolver never hands one engine's assembly to another. Being a positional record - buys two things at once here: value equality, so two resolutions of the same source compare equal, - and non-destructive mutation, which is exactly how per-tenant routing is implemented.
      • -
      • Walkthrough
          -
        • Key is the identity the rest of the stack routes on, and it is deliberately not recomputed when - the connection changes. DbContextFactory.ResolveTenantOverride clones the - shared source with shared with { ConnectionString = ..., CosmosDatabaseName = ... } and keeps the - original key, because the key is what EF's model cache is keyed on, so swapping only the connection - string is what lets one compiled model serve every tenant's database - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:162-187, - ADR-073).
        • -
        • ConnectionString may legitimately be empty. Both the startup initializer and EnsureCreatedAsync - treat an empty connection string as "this source is not configured in this host" and skip it rather - than failing (DatabaseInitializationExtensions.cs:74, DbContextFactory.cs:221-222).
        • -
        • MigrationsAssembly is a single positional parameter rather than three engine-scoped record-body - properties, because at most one is ever populated: BuildPhysicalSource on - DataSourceResolver already reads it from the right engine's setting before - constructing the record (DataSourceResolver.cs:403-407).
        • -
        • UsesMigrations (PhysicalDataSource.cs:41-47) is the switch that decides Migrate versus - EnsureCreated for this one database. SQL Server always migrates, including the single-database - monolith whose Default source names no migrations assembly at all and lets EF look next to the - context (PhysicalDataSource.cs:43). PostgreSQL and SQLite share one rule rather than following the - SQL Server one: both migrate only once MigrationsAssembly is configured for them - (PhysicalDataSource.cs:44), because neither ships with a host that already depends on being - migrated, and a SQLite source wired by hand before that setting existed has no migrations to apply - and must keep being created outright. Cosmos never does: the provider has no migrations pipeline - (PhysicalDataSource.cs:45). All branches are pinned by - MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/PhysicalDataSourceTests.cs:21, - PhysicalDataSourceTests.cs:33, PhysicalDataSourceTests.cs:49, and - PhysicalDataSourceTests.cs:90; PostgreSQL is additionally pinned by - MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.PostgreSQL.Tests/PostgreSQLPersistenceTests.cs.
        • -
        -
      • -
      • Where it's used: produced by DataSourceResolver through - BuildPhysicalSource (DataSourceResolver.cs:409-423) for both the Default source - (DataSourceResolver.cs:330-335) and named ones (DataSourceResolver.cs:375-380), and handed back - by GetPhysical (DataSourceResolver.cs:143-148); consumed by - PhysicalDbContextFactory, whose Create(DataSourceKey) resolves it - and whose Create(DataSourceKey, PhysicalDataSource) overload accepts an already-resolved one, which - is the entry point the tenant clone uses - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/PhysicalDbContextFactory.cs:37-40). - DesignTimeDbContextHelper resolves one for dotnet ef commands and - hands the same record to the design-time context - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextHelper.cs:140), - and the startup initializer branches on both ConnectionString and UsesMigrations - (DatabaseInitializationExtensions.cs:74, DatabaseInitializationExtensions.cs:79, - DatabaseInitializationExtensions.cs:148).
      • + (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:64).

      Snapshot

      @@ -4436,16 +4545,116 @@

      TenantDataSourceTarget

      context, because the tenant is what routes the scoped factory to that tenant's connection string. Every sweep now gets that branch for free through TenantDataSourceTargets.CreateTenantScope - (OutboxCleanupService.cs:103, AuditTrailCleanupJob.cs:100). + (OutboxCleanupService.cs:98, AuditTrailCleanupJob.cs:97).
    400. Where it's used: produced by TenantDataSourceTargets.Expand (via ExpandRelational) and consumed as the loop variable of every host-owned sweep: OutboxProcessor - (OutboxProcessor.cs:144, iterated at OutboxProcessor.cs:161), + (OutboxProcessor.cs:144, iterated at OutboxProcessor.cs:154), OutboxCleanupService (OutboxCleanupService.cs:197-198), AuditTrailCleanupJob - (AuditTrailCleanupJob.cs:78, and as a parameter at AuditTrailCleanupJob.cs:90 and - AuditTrailCleanupJob.cs:111), and the startup initializer - (DatabaseInitializationExtensions.cs:137-138).
    401. + (AuditTrailCleanupJob.cs:78, and as a parameter at AuditTrailCleanupJob.cs:87 and + AuditTrailCleanupJob.cs:108), and the startup initializer + (DatabaseInitializationExtensions.cs:188-189). + +

      PhysicalDataSource

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/PhysicalDataSource.cs:21 · Level 11 · record (sealed)

      +
      +
        +
      • What it is: the fully-resolved connection information for one physical database. Four positional + members, Key (its engine plus name identity), ConnectionString, MigrationsAssembly? and + CosmosDatabaseName (PhysicalDataSource.cs:21-25), plus two computed properties: UsesMigrations + (PhysicalDataSource.cs:42-48) and IsMigrationTarget (PhysicalDataSource.cs:57-60).
      • +
      • Depends on: DataSourceKey and, through it, DataSource; + DataSourceEngines, looked up by Key.Engine, whose + DataSourceEngineCapabilities supply the + MigrationPolicy and the ConnectionStringRequired flag that both computed + properties read (PhysicalDataSource.cs:42, PhysicalDataSource.cs:59).
      • +
      • Concept, a logical name resolved into a real connection. [Rubric §8, Data Architecture] covers + the step from a configured name like DataSources:Conference to an actual database. The record's doc + comment (PhysicalDataSource.cs:6-10) explains that it is produced by + IDataSourceResolver from the top-level ConnectionStrings section (the + Default source) plus the named DataSources entries. MigrationsAssembly is read from the + configuration key of the source's own engine, SQLServerMigrationsAssembly, + SqliteMigrationsAssembly, or PostgreSQLMigrationsAssembly, and is always null for Cosmos, which + migrates nothing (PhysicalDataSource.cs:13-18); CosmosDatabaseName is ignored for relational + engines (PhysicalDataSource.cs:20). A physical source belongs to exactly one engine, so one slot is + enough, and the resolver never hands one engine's assembly to another. Being a positional record + buys two things at once here: value equality, so two resolutions of the same source compare equal, + and non-destructive mutation, which is exactly how per-tenant routing is implemented.
      • +
      • Walkthrough
          +
        • Key is the identity the rest of the stack routes on, and it is deliberately not recomputed when + the connection changes. DbContextFactory.ResolveTenantOverride clones the + shared source with shared with { ConnectionString = ..., CosmosDatabaseName = ... } and keeps the + original key, because the key is what EF's model cache is keyed on, so swapping only the connection + string is what lets one compiled model serve every tenant's database + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:168-193, + ADR-073).
        • +
        • ConnectionString may legitimately be empty. Both the startup initializer and EnsureCreatedAsync + treat an empty connection string as "this source is not configured in this host" and skip it rather + than failing (DatabaseInitializationExtensions.cs:88, DbContextFactory.cs:246-247).
        • +
        • MigrationsAssembly is a single positional parameter rather than three engine-scoped record-body + properties, because at most one is ever populated: BuildPhysicalSource on + DataSourceResolver already reads it from the right engine's setting, and + passes null for an engine whose migration policy is Never (DataSourceResolver.cs:441-451).
        • +
        • UsesMigrations (PhysicalDataSource.cs:42-48) is the switch that decides Migrate versus + EnsureCreated for this one database. It names no engine: it switches on the + MigrationPolicy that the engine's descriptor declares in its capabilities, + DataSourceEngines.For(Key.Engine).Capabilities.Migrations (PhysicalDataSource.cs:42). Always + migrates unconditionally (PhysicalDataSource.cs:44), which is SQL Server, including the + single-database monolith whose Default source names no migrations assembly at all and lets EF look + next to the context. WhenAssemblyConfigured migrates only once MigrationsAssembly is set + (PhysicalDataSource.cs:45), which is the rule PostgreSQL and SQLite share: neither ships with a host + that already depends on being migrated, and a SQLite source wired by hand before that setting existed + has no migrations to apply and must keep being created outright. Never is Cosmos, whose provider + has no migrations pipeline (PhysicalDataSource.cs:46), and an unrecognized policy falls to false + (PhysicalDataSource.cs:47). The per-engine reasoning is kept in the property's doc comment + (PhysicalDataSource.cs:27-41). All branches are pinned by + MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/PhysicalDataSourceTests.cs:21, + PhysicalDataSourceTests.cs:33, PhysicalDataSourceTests.cs:49, PhysicalDataSourceTests.cs:64, + PhysicalDataSourceTests.cs:78, and PhysicalDataSourceTests.cs:90.
        • +
        • IsMigrationTarget (PhysicalDataSource.cs:57-60) narrows UsesMigrations to the sources startup + actually migrates (or, under the None strategy, checks): a source that uses migrations and either + belongs to an engine whose capabilities set ConnectionStringRequired or has a non-empty connection + string. The doc comment (PhysicalDataSource.cs:50-56) names the consequence: only SQL Server stays a + target with an empty connection string, so that misconfiguration fails loudly at startup instead of + being skipped, while an optional SQLite source a host leaves unconfigured stays silently absent. One + rule, shared by the context factory's GetMigrationTargets + (DbContextFactory.cs:782-788) and the startup pending-migrations check + (DatabaseInitializationExtensions.cs:312).
        • +
        +
      • +
      • Where it's used: produced by DataSourceResolver through + BuildPhysicalSource (DataSourceResolver.cs:441-451) for both the Default source + (DataSourceResolver.cs:363-368) and named ones (DataSourceResolver.cs:408-413), and handed back + by GetPhysical (DataSourceResolver.cs:182-187); consumed by + PhysicalDbContextFactory, whose Create(DataSourceKey) resolves it + and whose Create(DataSourceKey, PhysicalDataSource) overload accepts an already-resolved one, which + is the entry point the tenant clone uses + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/PhysicalDbContextFactory.cs:25-28). + DesignTimeDbContextHelper resolves one for dotnet ef commands and + hands the same record to the design-time context + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextHelper.cs:140), + and the startup initializer branches on ConnectionString, UsesMigrations and IsMigrationTarget + (DatabaseInitializationExtensions.cs:88, DatabaseInitializationExtensions.cs:93, + DatabaseInitializationExtensions.cs:199, DatabaseInitializationExtensions.cs:312).
      • +
      +

      DataSourceService

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceService.cs:12 · Level 12 · class (public sealed)

      +
      +
        +
      • What it is: the Application-facing facade over IEntityDataSourceRegistry. It answers two questions: which physical data source (engine plus database) does this entity live in, and can these two entities be joined with an EF Include (DataSourceService.cs:6-12).
      • +
      • Depends on: IEntityDataSourceRegistry as its single primary-constructor parameter (DataSourceService.cs:12), IDataSourceService as the contract it implements, the DataSourceKey and DataSource types it returns, and DataSourceEngines for the engine capability the include rule reads (DataSourceService.cs:32).
      • +
      • Concept introduced, routing as a first-class query. [Rubric §7, Microservices Readiness] assesses whether "where does this entity actually live" is answerable at runtime rather than being baked into code, and [Rubric §3, Clean Architecture] assesses whether the Application layer can ask that question without referencing EF. Database-per-service (ADR-006) means two entities written in the same module's code may or may not share a physical database depending on how the deployment is configured. Every consumer that needs to make a decision about that (which DbContext to open, whether a navigation can be eager-loaded or must go through a populator) asks this service. The class doc records an important property of the current design (DataSourceService.cs:9-10): the registry is built eagerly from configuration assemblies, so resolution no longer depends on an EF model having been built first, which is what makes it safe to consult from the Application layer at any point in startup.
      • +
      • Walkthrough
          +
        • The four resolution members (DataSourceService.cs:15-24) are pure forwarders to the registry, in two pairs: GetDataSourceKey by Type and by full type name, and GetDataSource by the same two, each projecting .Engine off the resolved key. The name-based overloads exist because a caller holding only a metadata string (an EF entity type name, a message payload's type name) should not have to resolve a Type first.
        • +
        • HaveIncludeSupport(first, second) (DataSourceService.cs:31-32) is the whole classification rule in one expression: the two keys must be equal and the engine's descriptor must declare itself relational, DataSourceEngines.For(first.Engine).Capabilities.IsRelational. The rule names no engine; Cosmos fails it because its capabilities say it is not relational. The remarks at :27-30 state both halves: EF Include requires both entities in the same physical database on a relational engine, and Cosmos has no cross-document include.
        • +
        • HaveIncludeSupport(firstEntityFullName, secondEntityFullName) (DataSourceService.cs:35-38) is the name-based overload, and it uses TryGetDataSourceKey rather than the throwing form: an entity the registry does not know about yields false, which degrades to "use the populator path" rather than to an exception during navigation classification.
        • +
        +
      • +
      • Why it's built this way: putting the include decision here rather than inside a repository is what lets NavigationMetadataProvider classify each navigation once and route it to either an EF Include or an INavigationPopulator (ADR-002) without knowing anything about deployment topology. Reading the relational flag off the engine descriptor keeps the same code correct on a non-relational store (ADR-018, ADR-130). It is registered as a singleton (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:60), which is safe because both the service and the registry behind it are immutable after construction.
      • +
      • Where it's used: UnitOfWork resolves an entity's key before choosing a context (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:13, doc at :29); NavigationMetadataProvider uses it for navigation classification (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/NavigationMetadataProvider.cs:20); in MMCA.Store, InventoryAllocationService (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Infrastructure/Services/InventoryAllocationService.cs:35) and ProductImageStorageService (MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Infrastructure/Services/ProductImageStorageService.cs:28) both resolve a key before opening the right context. Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/DataSourceServiceTests.cs and .../DataSourceServiceAdditionalTests.cs.

      IDataSourceResolver

      @@ -4487,11 +4696,11 @@

      IDataSourceResolver

      rule, so no caller reimplements the defaulting logic and no caller can disagree about what Default means.
    402. Where it's used: implemented by DataSourceResolver and registered as a - singleton (DependencyInjection.cs:103); injected into + singleton (DependencyInjection.cs:109); injected into EntityDataSourceRegistry (EntityDataSourceRegistry.cs:23) to resolve each entity's derived logical name, into PhysicalDbContextFactory and - DbContextFactory to open connections (PhysicalDbContextFactory.cs:18, - DbContextFactory.cs:49), into the inbox store and both event buses so each can locate its own + DbContextFactory to open connections (PhysicalDbContextFactory.cs:21, + DbContextFactory.cs:51), into the inbox store and both event buses so each can locate its own database (EfInboxStore MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Inbox/EfInboxStore.cs:40, InProcessEventBus @@ -4505,42 +4714,30 @@

      IDataSourceResolver

      optionally into TenancySettingsValidator, which uses ResolveLogical to check that a tenant override is keyed by a name that actually round-trips as a physical source - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:23, - TenancySettingsValidator.cs:119).
    403. - -

      DataSourceService

      -
      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceService.cs:11 · Level 3 · class (public sealed)

      -
      -
        -
      • What it is: the Application-facing facade over IEntityDataSourceRegistry. It answers two questions: which physical data source (engine plus database) does this entity live in, and can these two entities be joined with an EF Include (DataSourceService.cs:5-11).
      • -
      • Depends on: IEntityDataSourceRegistry as its single primary-constructor parameter (DataSourceService.cs:11), IDataSourceService as the contract it implements, and the DataSourceKey and DataSource types it returns.
      • -
      • Concept introduced, routing as a first-class query. [Rubric §7, Microservices Readiness] assesses whether "where does this entity actually live" is answerable at runtime rather than being baked into code, and [Rubric §3, Clean Architecture] assesses whether the Application layer can ask that question without referencing EF. Database-per-service (ADR-006) means two entities written in the same module's code may or may not share a physical database depending on how the deployment is configured. Every consumer that needs to make a decision about that (which DbContext to open, whether a navigation can be eager-loaded or must go through a populator) asks this service. The class doc records an important property of the current design (DataSourceService.cs:8-10): the registry is built eagerly from configuration assemblies, so resolution no longer depends on an EF model having been built first, which is what makes it safe to consult from the Application layer at any point in startup.
      • -
      • Walkthrough
          -
        • The four resolution members (DataSourceService.cs:14-23) are pure forwarders to the registry, in two pairs: GetDataSourceKey by Type and by full type name, and GetDataSource by the same two, each projecting .Engine off the resolved key. The name-based overloads exist because a caller holding only a metadata string (an EF entity type name, a message payload's type name) should not have to resolve a Type first.
        • -
        • HaveIncludeSupport(first, second) (DataSourceService.cs:30-31) is the whole classification rule in one expression: the two keys must be equal and the engine must not be DataSource.CosmosDB. The remarks at :27-30 state both halves: EF Include requires both entities in the same physical database on a relational engine, and Cosmos has no cross-document include.
        • -
        • HaveIncludeSupport(firstEntityFullName, secondEntityFullName) (DataSourceService.cs:34-37) is the name-based overload, and it uses TryGetDataSourceKey rather than the throwing form: an entity the registry does not know about yields false, which degrades to "use the populator path" rather than to an exception during navigation classification.
        • -
        -
      • -
      • Why it's built this way: putting the include decision here rather than inside a repository is what lets NavigationMetadataProvider classify each navigation once and route it to either an EF Include or an INavigationPopulator (ADR-002) without knowing anything about deployment topology. The engine carve-out keeps the same code correct on a non-relational store (ADR-018). It is registered as a singleton (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:59), which is safe because both the service and the registry behind it are immutable after construction.
      • -
      • Where it's used: UnitOfWork resolves an entity's key before choosing a context (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:13, doc at :29); NavigationMetadataProvider uses it for navigation classification (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/NavigationMetadataProvider.cs:20); in MMCA.Store, InventoryAllocationService (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Infrastructure/Services/InventoryAllocationService.cs:35) and ProductImageStorageService (MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Infrastructure/Services/ProductImageStorageService.cs:28) both resolve a key before opening the right context. Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Services/DataSourceServiceTests.cs and .../DataSourceServiceAdditionalTests.cs.
      • + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:24, + TenancySettingsValidator.cs:120).

      DataSourceResolver

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:15 · Level 4 · class (sealed, partial)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:16 · Level 13 · class (sealed, partial)

      • What it is: the singleton implementation of IDataSourceResolver. It builds the logical-to-physical map once at construction from the connection-string settings and the named DataSources entries, validates migrations-assembly conflicts, works out which engine to substitute for engines the host does not configure, and then serves both lookups from in-memory - dictionaries.
      • + dictionaries. Every engine-specific question it asks (connection string, migrations assembly, setting + name, migration policy, connection identity, substitution priority) is a lookup on the engine's + descriptor through DataSourceEngines, so the class itself switches on no + engine.
      • Depends on: DataSource, DataSourceKey, PhysicalDataSource, IDataSourceResolver, its own nested DefaultSeed, ConnectionStringSettings (taken as IOptions<T>), DataSourcesSettings and DataSourceEntrySettings; + DataSourceEngines, IDataSourceEngine and + MigrationPolicy for every per-engine answer; ILogger<T> and the [LoggerMessage] source generator, which is why the class is partial.
      • Concept introduced, eager and validated data-source resolution. [Rubric §8, Data Architecture] (deliberate multi-database routing) and [Rubric §7, Microservices Readiness] @@ -4548,83 +4745,95 @@

        DataSourceResolver

        The resolver realizes the collapse rule described on IDataSourceResolver. Two guardrails are worth calling out. First, conflicting migrations-assembly declarations on logical names that collapse to the same physical - database throw at construction (DataSourceResolver.cs:449-462), a loud fail-fast rather than a + database throw at construction (DataSourceResolver.cs:476-483), a loud fail-fast rather than a silent pick. Second, [Rubric §13, Observability & Operability] shows up in the two source-generated - log methods: LogMigrationsAssemblyFallback (DataSourceResolver.cs:499-500) warns when a named SQL - Server source has no dedicated migrations assembly and therefore falls back to another database's, - whose snapshot describes a different schema, and LogSubstituteEngine - (DataSourceResolver.cs:496-497) states at startup that requests for an unconfigured engine are being + log methods: LogMigrationsAssemblyFallback (DataSourceResolver.cs:508-509) warns when a named + source on an engine that always migrates has no dedicated migrations assembly and therefore falls + back to another database's, whose snapshot describes a different schema, and LogSubstituteEngine + (DataSourceResolver.cs:505-506) states at startup that requests for an unconfigured engine are being served from another one.
      • Walkthrough
          -
        • State (DataSourceResolver.cs:17-39): AllEngines is the build order, CosmosDB, PostgreSQL, - Sqlite, SQLServer (DataSourceResolver.cs:17-18); EnginePreference is the substitution order, - SQL Server, PostgreSQL, SQLite, Cosmos DB, relational first because every table the framework owns - is relational, and PostgreSQL ahead of SQLite because a host that configures a server engine means - it to serve the framework's own tables (DataSourceResolver.cs:20-30). The lookups are - _logicalToPhysical, keyed by - (engine, logical name) (DataSourceResolver.cs:33), and _physicalSources, keyed by - DataSourceKey (DataSourceResolver.cs:36). _configuredEngines - (DataSourceResolver.cs:39) and _substituteEngine (DataSourceResolver.cs:46) carry the +
        • State (DataSourceResolver.cs:18-52): FrameworkDefaultEngine is DataSource.SQLServer, the + engine every framework-owned table asks for by default, documented as a policy constant rather than + an engine capability (DataSourceResolver.cs:18-23). EnginePreference is the substitution order, + built by ordering DataSourceEngines.All by each engine's SubstitutionPriority, lowest first + (DataSourceResolver.cs:35-36); its doc comment records the intended order, relational first + because every table the framework owns is relational, SQL Server ahead of the others, and PostgreSQL + ahead of SQLite because a host that configures a server engine means it to serve the framework's own + tables (DataSourceResolver.cs:25-34). The lookups are _logicalToPhysical, keyed by + (engine, logical name) (DataSourceResolver.cs:39), and _physicalSources, keyed by + DataSourceKey (DataSourceResolver.cs:42). _configuredEngines + (DataSourceResolver.cs:45) and _substituteEngine (DataSourceResolver.cs:52) carry the substitution decision.
        • -
        • Constructor (DataSourceResolver.cs:58-90): null-guards its two settings arguments - (DataSourceResolver.cs:63-64), then loops all four engines calling BuildEngineMap and recording - which engines carry a connection string anywhere (DataSourceResolver.cs:68-76). It picks the - substitute as the first configured engine in preference order, or null when the host configures no - database at all (DataSourceResolver.cs:78-81), and logs once when the substitute is not SQL Server - (DataSourceResolver.cs:83-89). Every field is written only here, which is what makes the singleton +
        • Constructor (DataSourceResolver.cs:64-89): null-guards its two settings arguments + (DataSourceResolver.cs:69-70), then calls BuildEngineMap for every engine registered in + DataSourceEngines.All (DataSourceResolver.cs:74-77). It then computes the configured engines and + the substitute through the two static helpers ConfiguredEngines and PickSubstituteEngine + (DataSourceResolver.cs:79-80), and logs once when the substitute is not FrameworkDefaultEngine + (DataSourceResolver.cs:82-88). Every field is written only here, which is what makes the singleton safe to share without locking.
        • -
        • ResolveLogical (DataSourceResolver.cs:93-107): substitutes the engine first - (DataSourceResolver.cs:97), then short-circuits the Default name case-insensitively - (DataSourceResolver.cs:99-102), then does a dictionary lookup whose miss returns - DataSourceKey.Default(effectiveEngine) (DataSourceResolver.cs:104-106), which is the monolith +
        • ResolveLogical (DataSourceResolver.cs:92-106): substitutes the engine first + (DataSourceResolver.cs:96), then short-circuits the Default name case-insensitively + (DataSourceResolver.cs:98-101), then does a dictionary lookup whose miss returns + DataSourceKey.Default(effectiveEngine) (DataSourceResolver.cs:103-105), which is the monolith default.
        • -
        • SubstituteUnconfiguredEngine (DataSourceResolver.cs:128-129) is one line, and its doc comment - (DataSourceResolver.cs:109-125) carries the failure it removes: every engine choice the framework +
        • SubstituteUnconfiguredEngine (DataSourceResolver.cs:127-128) is one line, and its doc comment + (DataSourceResolver.cs:108-124) carries the failure it removes: every engine choice the framework makes for its own tables comes from a setting defaulting to SQL Server (Outbox:DataSource, Scheduler:DataSource, AuditTrail:DataSource), and honoring that literally in a SQLite-only host handed those components a source with an empty connection string, so their first query failed with - "The ConnectionString property has not been initialized". HasAnyConnectionString - (DataSourceResolver.cs:135-140) is what decides "configured", and it deliberately looks at named - entries as well as the top-level section.
        • -
        • GetPhysical (DataSourceResolver.cs:143-148): a _physicalSources lookup that throws with an + "The ConnectionString property has not been initialized".
        • +
        • ResolveFrameworkDefaultEngine (DataSourceResolver.cs:139-150) is an internal static answer to + the same question without building a resolver: the engine ResolveLogical(SQLServer, "Default") + would land on, which is FrameworkDefaultEngine when it is configured, otherwise the substitute, and + FrameworkDefaultEngine again when nothing is configured (DataSourceResolver.cs:146-149). + Registration code reads it to decide which services the default engine can back, raw SQL being + registered only where that engine is relational (DataSourceResolver.cs:130-135, + MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:407). It shares + ConfiguredEngines (DataSourceResolver.cs:153-158) and PickSubstituteEngine + (DataSourceResolver.cs:164-168) with the constructor, so the two paths cannot disagree. + HasAnyConnectionString (DataSourceResolver.cs:174-179) is what decides "configured", and it + deliberately looks at named entries as well as the top-level section.
        • +
        • GetPhysical (DataSourceResolver.cs:182-187): a _physicalSources lookup that throws with an actionable message when the key was not produced by ResolveLogical.
        • -
        • BuildEngineMap (DataSourceResolver.cs:155-169): computes the DefaultSeed - (DataSourceResolver.cs:161), splits the engine's named entries with ClassifyEntries - (DataSourceResolver.cs:162), then registers the Default source and one named source per group - (DataSourceResolver.cs:163-168).
        • -
        • ClassifyEntries (DataSourceResolver.cs:264-305): computes a per-connection identity string via - GetIdentity (DataSourceResolver.cs:473-476), where a Cosmos identity appends the database name - because one account hosts many databases, relational engines use the connection string alone, and - the comparison is ordinal, so semantically-equal-but-textually-different connection strings - deliberately do not collapse. Entries with no connection string for the engine are skipped entirely - (DataSourceResolver.cs:277-282), because ResolveLogical already defaults on a map miss; entries - matching the seed's identity go to the collapsed list (DataSourceResolver.cs:289-293) and the rest - are grouped by identity (DataSourceResolver.cs:295-302).
        • -
        • RegisterDefaultSource (DataSourceResolver.cs:311-341): registers the Default key for the +
        • BuildEngineMap (DataSourceResolver.cs:194-208): computes the DefaultSeed + (DataSourceResolver.cs:200), splits the engine's named entries with ClassifyEntries + (DataSourceResolver.cs:201), then registers the Default source and one named source per group + (DataSourceResolver.cs:202-207).
        • +
        • ClassifyEntries (DataSourceResolver.cs:297-338): computes a per-connection identity string via + GetIdentity (DataSourceResolver.cs:494-497), which appends the database name only for an engine + whose descriptor sets ConnectionIdentityIncludesDatabaseName (Cosmos, where one account hosts many + databases) and otherwise uses the connection string alone; the comparison is ordinal, so + semantically-equal-but-textually-different connection strings deliberately do not collapse. Entries + with no connection string for the engine are skipped entirely (DataSourceResolver.cs:310-315), + because ResolveLogical already defaults on a map miss; entries matching the seed's identity go to + the collapsed list (DataSourceResolver.cs:322-326) and the rest are grouped by identity + (DataSourceResolver.cs:328-335).
        • +
        • RegisterDefaultSource (DataSourceResolver.cs:344-374): registers the Default key for the engine, letting entries that collapsed onto it contribute an explicit migrations assembly alongside - the seed's own (DataSourceResolver.cs:318-328), and maps each collapsed logical name onto that key - (DataSourceResolver.cs:337-340).
        • -
        • RegisterNamedSource (DataSourceResolver.cs:347-386): names the physical key after the - alphabetically-first member (Order(...).First(), DataSourceResolver.cs:353) so routing is - deterministic regardless of configuration key order, then warns and falls back when a SQL Server - source declares no migrations assembly of its own (DataSourceResolver.cs:360-368). The group's - Cosmos database name comes from the canonical entry, falling back to the seed + the seed's own (DataSourceResolver.cs:351-361), and maps each collapsed logical name onto that key (DataSourceResolver.cs:370-373).
        • -
        • BuildPhysicalSource (DataSourceResolver.cs:409-423): the migrations assembly was already read +
        • RegisterNamedSource (DataSourceResolver.cs:380-419): names the physical key after the + alphabetically-first member (Order(...).First(), DataSourceResolver.cs:386) so routing is + deterministic regardless of configuration key order, then, for an engine whose migration policy is + Always (SQL Server), warns and falls back to the seed's assembly when the source declares none of + its own (DataSourceResolver.cs:393-401). The group's Cosmos database name comes from the canonical + entry, falling back to the seed (DataSourceResolver.cs:403-406).
        • +
        • BuildPhysicalSource (DataSourceResolver.cs:441-451): the migrations assembly was already read from the configuration key of this source's own engine via GetMigrationsAssembly, so building the - record is a straight positional construction with the assembly passed straight through, null only - for Cosmos (DataSourceResolver.cs:414-421); which is what stops a SQL Server assembly from being - handed to UseSqlite or UseNpgsql.
        • -
        • ResolveMigrationsAssembly (DataSourceResolver.cs:437-465): returns null for Cosmos and when no - explicit value exists (DataSourceResolver.cs:442-445), and throws when logical names sharing a - database declare conflicting assemblies, naming the offending setting per engine (via a four-way - switch over Sqlite/PostgreSQL/SQLServer/CosmosDB) and every declaration in the message - (DataSourceResolver.cs:447-461). GetMigrationsAssembly (DataSourceResolver.cs:424-431) is the - per-engine reader that feeds it: SQLite, PostgreSQL and SQL Server have their own entry settings, - Cosmos migrates nothing. TopLevelMigrationsAssembly (DataSourceResolver.cs:246-253), used inside - ResolveDefaultSeed, is the same idea at the top-level-section layer: only SQL Server and PostgreSQL - have a declared top-level migrations assembly, so a mixed-engine host can never scaffold one - engine's schema from another's snapshot.
        • + record is a straight positional construction with the assembly passed through, null only for an + engine whose policy is MigrationPolicy.Never (DataSourceResolver.cs:450); which is what stops a + SQL Server assembly from being handed to UseSqlite or UseNpgsql. +
        • ResolveMigrationsAssembly (DataSourceResolver.cs:464-486): returns null for a Never engine and + when no explicit value exists (DataSourceResolver.cs:469-473), and throws when logical names + sharing a database declare conflicting assemblies, naming the engine's own + MigrationsAssemblySettingName and every declaration in the message (DataSourceResolver.cs:475-483). + GetMigrationsAssembly (DataSourceResolver.cs:457-458), TopLevelMigrationsAssembly + (DataSourceResolver.cs:286-287) and the two GetConnectionString overloads + (DataSourceResolver.cs:499-503) are one-line delegations to the engine descriptor. The top-level + one, used inside ResolveDefaultSeed, keeps each engine on its own value: only SQL Server and + PostgreSQL declare a top-level migrations assembly, so a mixed-engine host can never scaffold one + engine's schema from another's snapshot (DataSourceResolver.cs:278-282).
      • Why it's built this way: resolving eagerly at construction turns a misconfiguration into a startup @@ -4634,20 +4843,25 @@

        DataSourceResolver

        comparison is the conservative choice: collapsing two connection strings that only look different would silently merge two databases. Engine substitution is scoped the same conservative way, since it can only fire for a request that could not have been served at all - (DataSourceResolver.cs:121-124).
      • + (DataSourceResolver.cs:120-123). Pushing every per-engine answer onto the engine descriptor means a + new engine is a new descriptor, not a new arm in each switch here + (ADR-130).
      • Where it's used: registered as the singleton IDataSourceResolver - (DependencyInjection.cs:103); consumed by EntityDataSourceRegistry, - the context factories, and DesignTimeDbContextHelper, which constructs - its own instance for dotnet ef commands and registers it in a hand-built container + (DependencyInjection.cs:91), with its static ResolveFrameworkDefaultEngine read during + registration (DependencyInjection.cs:407); consumed by + EntityDataSourceRegistry, the context factories, and + DesignTimeDbContextHelper, which constructs its own instance for + dotnet ef commands and registers it in a hand-built container (DesignTimeDbContextHelper.cs:131-134, DesignTimeDbContextHelper.cs:194). Its rules are pinned by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/DataSourceResolverTests.cs:12, including the alphabetically-first canonical name (DataSourceResolverTests.cs:84), the two Cosmos cases where the same account with a different database stays distinct while the same database collapses (DataSourceResolverTests.cs:99, DataSourceResolverTests.cs:118), the SQLite-only host being served the framework's SQL Server default from SQLite (DataSourceResolverTests.cs:335), the - polyglot host routing each engine to itself (DataSourceResolverTests.cs:424), the SQL-Server-only - host being unchanged (DataSourceResolverTests.cs:413), and the host with no database at all passing - the engine straight through (DataSourceResolverTests.cs:479).
      • + SQL-Server-only host being unchanged (DataSourceResolverTests.cs:413), the polyglot host routing + each engine to itself (DataSourceResolverTests.cs:424), the host with no database at all passing + the engine straight through (DataSourceResolverTests.cs:479), and PostgreSQL being preferred over + SQLite as the substitute (DataSourceResolverTests.cs:578).

      EntityDataSourceRegistry

      @@ -4732,13 +4946,13 @@

      EntityDataSourceRegistry

      ApplicationDbContext applies when filtering configurations into a model (ApplicationDbContext.cs:961-976).
    404. Where it's used: registered as the singleton - IEntityDataSourceRegistry (DependencyInjection.cs:90) and rebuilt by + IEntityDataSourceRegistry (DependencyInjection.cs:92) and rebuilt by hand for design-time commands (DesignTimeDbContextHelper.cs:135, DesignTimeDbContextHelper.cs:195); consumed by CrossDataSourceDegradeConvention, DataSourceService, DbContextFactory, both - ApplicationDbContext model passes (ApplicationDbContext.cs:381, - ApplicationDbContext.cs:966), and the outbox, audit-trail and migrations enumerations. Its behavior + ApplicationDbContext model passes (ApplicationDbContext.cs:386, + ApplicationDbContext.cs:962), and the outbox, audit-trail and migrations enumerations. Its behavior is pinned by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/EntityDataSourceRegistryTests.cs:15, covering the agreeing and conflicting duplicate cases (EntityDataSourceRegistryTests.cs:94, @@ -4749,26 +4963,22 @@

      EntityDataSourceRegistry

      TenantDataSourceTargets

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:42 · Level 5 · class (static)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:42 · Level 14 · class (static)

      • What it is: a static class that expands the physical data sources a background service owns into the TenantDataSourceTarget units it must actually visit once - database-per-tenant is configured (TenantDataSourceTargets.cs:25-51), plus two convenience layers - built on top of that expansion: an overload pair that derives the source list itself from the entity - registry (ExpandRelational, TenantDataSourceTargets.cs:60-76, TenantDataSourceTargets.cs:86-100) - and a scope-creation helper (CreateTenantScope, TenantDataSourceTargets.cs:110-118).
      • + database-per-tenant is configured (Expand, TenantDataSourceTargets.cs:51-82), plus a + scope-creation extension (CreateTenantScope, TenantDataSourceTargets.cs:92-103). Deciding which + sources to feed it is not its job: that lives on FrameworkTableTargets.
      • Depends on: DataSourceKey, TenantDataSourceTarget, TenancySettings with its nested TenantEntrySettings and TenantDataSourceOverrideSettings, TenancySettingsValidator.ConnectionStringFor - for the per-engine connection-string lookup - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:122), - IEntityDataSourceRegistry and IDataSourceResolver - for the ExpandRelational overloads, and ITenantContext (group-05-cqrs-pipeline.md#itenantcontext) - for CreateTenantScope.
      • + for the per-engine connection-string lookup (TenantDataSourceTargets.cs:73), and ITenantContext + (group-05-cqrs-pipeline.md#itenantcontext) for CreateTenantScope.
      • Concept introduced, why a per-tenant database is invisible to a shared sweep. [Rubric §8, Data Architecture] and [Rubric §29, Resilience & Business Continuity] both apply, and the class remarks (TenantDataSourceTargets.cs:29-41) carry the reasoning. A shared-schema tenant needs nothing here: its rows live in the shared database that the null-tenant target already drains, and the outbox @@ -4799,56 +5009,519 @@

        TenantDataSourceTargets

        for a source this host does not own adds nothing, and an override that only declares, say, a SQLite connection adds nothing for a SQL Server source. Both cases have their own test (TenantDataSourceTargetTests.cs:64, TenantDataSourceTargetTests.cs:78).
      • -
      • ExpandRelational(IEntityDataSourceRegistry, TenancySettings?) (TenantDataSourceTargets.cs:60-76) - is Expand composed with RelationalSourcesInUse - (TenantDataSourceTargets.cs:127-128): every relational physical source backing a registered entity, - Cosmos excluded because it has none of these tables, deduplicated. It is internal, recomputed on - every call by its own remark, which is deliberately cheap and tolerant of module assemblies that - finish loading after startup.
      • -
      • The second ExpandRelational overload (TenantDataSourceTargets.cs:86-100) adds one more source on - top of that same relational set: the physical source the caller's own settings name as its table's - home, resolved through IDataSourceResolver.ResolveLogical - (TenantDataSourceTargets.cs:95), unless that configured engine is Cosmos - (TenantDataSourceTargets.cs:93). This is what an outbox or internal-command sweep uses: the entity - registry alone would miss the publish or scheduling table when it lives in a source no registered - entity happens to use.
      • CreateTenantScope(this IServiceScopeFactory, TenantDataSourceTarget) - (TenantDataSourceTargets.cs:110-118) null-guards the factory, creates a scope, and, when the - target's TenantId is set, calls ITenantContext.SetTenant on that scope before returning it. The - doc comment is explicit about ordering: call it before asking the scope for a context, because the + (TenantDataSourceTargets.cs:92-103) null-guards the factory, creates a scope, and, when the + target's TenantId is set, calls ITenantContext.SetTenant on that scope before returning it + (TenantDataSourceTargets.cs:94-102). The doc comment is explicit about ordering + (TenantDataSourceTargets.cs:83-87): call it before asking the scope for a context, because the tenant is what routes the scoped context factory to the tenant's connection string and is also what the query filter reads.
    405. Why it's built this way: Expand is a pure function over settings, with no DI and no I/O, so every - sweep can share one expansion rule and each can unit-test its own target list without a database - (TenantDataSourceTargetTests.cs:20). Keeping the tenant loop outside the sweeps also keeps the - tenancy feature additive: a host with no Tenancy section gets byte-identical behavior to the - pre-tenancy code path (TenantDataSourceTargetTests.cs:26, TenantDataSourceTargetTests.cs:36). The - ExpandRelational overloads and CreateTenantScope centralize two things every sweep previously had - to get right on its own: which sources to enumerate, and the call-order rule between setting the - tenant and asking for the context.
    406. -
    407. Where it's used: Expand and its ExpandRelational overloads back all of the host-owned sweeps: - OutboxProcessor (OutboxProcessor.cs:213), - OutboxCleanupService - (OutboxCleanupService.cs:220), AuditTrailCleanupJob - (AuditTrailCleanupJob.cs:83), + caller shares one expansion rule and each target list can be unit-tested without a database + (TenantDataSourceTargetTests.cs:47). Keeping the tenant loop outside the sweeps also keeps the + tenancy feature additive: a host with no Tenancy section gets one shared target per source and + nothing else (TenantDataSourceTargetTests.cs:26, TenantDataSourceTargetTests.cs:36). + CreateTenantScope centralizes the call-order rule between setting the tenant and asking for the + context, which every sweep would otherwise have to get right on its own.
    408. +
    409. Where it's used: Expand is called by FrameworkTableTargets, which + supplies the relational source set for the framework-table sweeps (FrameworkTableTargets.cs:44, + FrameworkTableTargets.cs:63), and by DatabaseInitializationExtensions, which filters the expansion down to t.TenantId is not null because the shared sources were already - initialized in the pass above it (DatabaseInitializationExtensions.cs:137-138), - InternalCommandAdministration, InternalCommandCleanupService, InternalCommandProcessor, - OutboxAdministration, and OutboxCleanupService. CreateTenantScope is the scope-creation call each - of those makes per target. Two of the sweeps are pinned against the same expansion in tests - (TenantDataSourceTargetTests.cs:100, TenantDataSourceTargetTests.cs:118).
    410. + initialized in the pass above it (DatabaseInitializationExtensions.cs:188-189). CreateTenantScope + is the per-target scope call of the startup initializer (DatabaseInitializationExtensions.cs:191), + OutboxProcessor (OutboxProcessor.cs:181, and per row + at OutboxProcessor.cs:509), OutboxCleanupService + (OutboxCleanupService.cs:98), OutboxAdministration (OutboxAdministration.cs:217), + InternalCommandProcessor (InternalCommandProcessor.cs:153), + InternalCommandCleanupService + (InternalCommandCleanupService.cs:105), + InternalCommandAdministration + (InternalCommandAdministration.cs:261) and AuditTrailCleanupJob + (AuditTrailCleanupJob.cs:97). Two of the sweeps are pinned against the same expansion in tests + (TenantDataSourceTargetTests.cs:100, TenantDataSourceTargetTests.cs:116).
    411. Caveats / not-in-source: the expansion is driven purely by declared configuration. A tenant whose database exists but is not declared under Tenancy:Tenants produces no target, and nothing in this class detects that; the round-trip check that a source name is a real physical name lives in TenancySettingsValidator, not here.
    412. +

      FrameworkTableTargets

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/FrameworkTableTargets.cs:32 · Level 15 · class (sealed)

      +
      +
        +
      • What it is: a singleton that answers one question for every sweep over the framework's own + relational tables (OutboxMessages, InternalCommands, the audit trail): which physical databases, + and which per-tenant copies of them, does this host have to visit (FrameworkTableTargets.cs:8-13). + Three public members, Relational() (FrameworkTableTargets.cs:43-44), + Relational(DataSource, string) (FrameworkTableTargets.cs:54-64) and Named(DataSource, string, string?) (FrameworkTableTargets.cs:75-82), all returning IReadOnlyList<TenantDataSourceTarget>.
      • +
      • Depends on: three primary-constructor parameters (FrameworkTableTargets.cs:32-35): + IEntityDataSourceRegistry for the physical sources in use, + IDataSourceResolver to resolve the caller's configured home source, and an + optional IOptions<TenancySettings> (TenancySettings). + It delegates the per-tenant expansion to TenantDataSourceTargets.Expand + and reads the relational flag from DataSourceEngines.
      • +
      • Concept introduced, one answer for "where do the framework's tables live". [Rubric §8, Data Architecture] and [Rubric §7, Microservices Readiness]: with database-per-service + (ADR-006) every relational + source has its own outbox and internal-command table, and with database-per-tenant + (ADR-073) each tenant that keeps + its own copy of a source has another. The class remarks (FrameworkTableTargets.cs:14-24) say why + this is one class: the outbox processor, the internal-command processor, both cleanup services, both + operator surfaces and the audit-trail sweep all ask the same question, so the answer is derived here + once rather than from the registry, the resolver and the tenancy settings inside each of them.
      • +
      • Walkthrough
          +
        • RelationalSourcesInUse (FrameworkTableTargets.cs:84-86, private) is the base set: every + physical source the registry reports in use, filtered to engines whose capabilities declare them + relational. Cosmos sources drop out because they host none of these tables + (FrameworkTableTargets.cs:21-22).
        • +
        • Relational() (FrameworkTableTargets.cs:43-44) is that set, deduplicated, then passed to + TenantDataSourceTargets.Expand with the bound tenancy settings, or null when tenancy was never + registered. It is what a sweep uses when its table has no configured home of its own.
        • +
        • Relational(homeEngine, homeDatabaseName) (FrameworkTableTargets.cs:54-64) adds one more source: + the physical source the caller's own settings name as its table's home, resolved through + IDataSourceResolver.ResolveLogical (FrameworkTableTargets.cs:60), but only when that engine is + relational (FrameworkTableTargets.cs:58). This is what the outbox and internal-command sweeps use: + the entity registry alone would miss the publish or scheduling table when it lives in a source no + registered entity happens to use.
        • +
        • Named(homeEngine, homeDatabaseName, targetName) (FrameworkTableTargets.cs:75-82) is the same + list narrowed to the one target an operator named, matched case-insensitively against + TenantDataSourceTarget.ToString(), the display form the operator surfaces report + (FrameworkTableTargets.cs:79-81). A null name keeps every target; a name matching nothing returns + an empty list.
        • +
        +
      • +
      • Why it's built this way: every caller recomputes the list per call, which the remarks call cheap + and tolerant of module assemblies that finish loading after startup (FrameworkTableTargets.cs:21-22), + so the class holds no cached state and is safe as a singleton + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:96). The tenancy option + is defaulted to null (FrameworkTableTargets.cs:35), so a host without tenancy visits the shared + databases only (FrameworkTableTargets.cs:27-31). Testing the relational flag on the engine + descriptor rather than naming Cosmos keeps the rule correct for any engine registered in + DataSourceEngines + (ADR-130).
      • +
      • Where it's used: injected into OutboxProcessor + (OutboxProcessor.cs:58, called at OutboxProcessor.cs:142), + OutboxCleanupService + (OutboxCleanupService.cs:47, called at OutboxCleanupService.cs:193), + InternalCommandProcessor (InternalCommandProcessor.cs:48, called at + InternalCommandProcessor.cs:115), InternalCommandCleanupService + (InternalCommandCleanupService.cs:44, called at InternalCommandCleanupService.cs:161), and + AuditTrailCleanupJob (AuditTrailCleanupJob.cs:47, which uses the + home-less Relational() at AuditTrailCleanupJob.cs:75). The two operator surfaces use Named: + OutboxAdministration (OutboxAdministration.cs:36, called at OutboxAdministration.cs:203) and + InternalCommandAdministration + (InternalCommandAdministration.cs:38, called at InternalCommandAdministration.cs:247). All paths + are under MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/. Tests construct it in + TenantDataSourceTargetTests.cs, OutboxProcessorTests.cs, OutboxProcessorExecuteAsyncTests.cs, + OutboxCleanupServiceTests.cs and AuditTrailCleanupJobTests.cs.
      • +
      +

      ExplicitKeyInsertGroup

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources.Engines · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/ExplicitKeyInsertGroup.cs:12 · Level 0 · record (internal sealed)

      +
      +
        +
      • What it is: a three-member positional record, (string Schema, string Table, IReadOnlyList<EntityEntry> Entries) + (ExplicitKeyInsertGroup.cs:12). It names one batch of pending inserts that all target the same + table and all carry an explicit value for a store-generated key, which is exactly the unit an engine's + explicit-key toggle operates on (ExplicitKeyInsertGroup.cs:5-8).
      • +
      • Depends on: EF Core's EntityEntry (ExplicitKeyInsertGroup.cs:1), and nothing first-party.
      • +
      • Concept introduced, importing rows that already have ids. [Rubric §8, Data Architecture] + (assesses whether persistence mechanics are deliberate): a store-generated key normally means the + application never supplies the id, but an import from an external system must preserve the source's + ids. SQL Server only accepts that with SET IDENTITY_INSERT <table> ON, one table at a time per + session (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:323-328). + Grouping the affected entries by table is what turns that constraint into a loop.
      • +
      • Walkthrough: no members beyond the positional ones. Schema and Table come from EF model + metadata, never user input, which is what makes the raw toggle statement safe to build by + concatenation (see IExplicitKeyInsertDialect).
      • +
      • Why it's built this way: the record is engine-neutral and lives beside the engine strategies + rather than inside DbContextFactory, because the engine produces the groups + (SQLServerDataSourceEngine.FindGroups) and the factory consumes them + (ADR-130).
      • +
      • Where it's used: returned by IExplicitKeyInsertDialect.FindGroups + (IExplicitKeyInsertDialect.cs:21), built by the SQL Server engine + (SQLServerDataSourceEngine.cs:158), and consumed by DbContextFactory.SaveExplicitKeyGroupsAsync + (DbContextFactory.cs:377-427), which saves each group in its own round with the toggle on, hiding + the other groups' Added entries as Unchanged (DbContextFactory.cs:390-396) and excluding their + domain events from capture through + DomainEventSaveChangesInterceptor.BeginCaptureExclusion + (DbContextFactory.cs:398-406).
      • +
      +

      MigrationPolicy

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources.Engines · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/MigrationPolicy.cs:8 · Level 0 · enum (internal)

      +
      +
        +
      • What it is: how an engine decides whether one physical source is migrated (Migrate) or created + outright (EnsureCreated) at startup (MigrationPolicy.cs:3-7).
      • +
      • Depends on: nothing first-party.
      • +
      • Concept: one of the plain-value capabilities that replaced engine branching; the idea is taught on + DataSourceEngineCapabilities. [Rubric §8, Data Architecture] + applies: schema evolution policy is a per-engine fact, not a per-call-site decision.
      • +
      • Walkthrough: three members. Never (MigrationPolicy.cs:11): no migrations pipeline (Cosmos). + WhenAssemblyConfigured (:14): migrated only once a migrations assembly is configured for the source + (SQLite, PostgreSQL). Always (:20): always migrated, even with no migrations assembly, and a named + source with none falls back to the Default source's assembly with a warning (SQL Server, + MigrationPolicy.cs:16-19).
      • +
      • Why it's built this way: three values, not a boolean, because SQL Server's fallback behavior is + distinct from "migrate when configured".
      • +
      • Where it's used: PhysicalDataSource.UsesMigrations switches on it + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/PhysicalDataSource.cs:42-46); + DataSourceResolver reads Always for the Default-assembly fallback and + Never to drop migrations settings + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceResolver.cs:393,450,470). + Each engine declares its value in its Capabilities (for example CosmosDataSourceEngine.cs:42).
      • +
      +

      RowVersionStrategy

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources.Engines · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/RowVersionStrategy.cs:8 · Level 0 · enum (internal)

      +
      +
        +
      • What it is: how an engine maps and maintains the RowVersion optimistic-concurrency token + (RowVersionStrategy.cs:3-7).
      • +
      • Depends on: nothing first-party.
      • +
      • Concept: a capability value (see DataSourceEngineCapabilities). + [Rubric §8, Data Architecture] applies: optimistic concurrency is uniform for callers while the + token's mechanics differ per engine.
      • +
      • Walkthrough: three members. None (RowVersionStrategy.cs:11): no token configured (Cosmos). + StoreGenerated (:14): a server-generated rowversion mapped with IsRowVersion() and never + stamped (SQL Server). ClientStamped (:17): a plain concurrency token the audit interceptor stamps + on every insert and update (PostgreSQL, SQLite).
      • +
      • Why it's built this way: only SQL Server has a native row-version column type, so the other + relational engines need the application to write the token; naming the strategy keeps that split in + one place instead of an engine comparison in two files.
      • +
      • Where it's used: mapping in ApplicationDbContext + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:591) + and stamping in AuditSaveChangesInterceptor + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:59).
      • +
      +

      DataSourceEngineCapabilities

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources.Engines · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/DataSourceEngineCapabilities.cs:23 · Level 1 · record (internal sealed)

      +
      +
        +
      • What it is: what one engine can and cannot do, as plain values a call site reads instead of naming + an engine (DataSourceEngineCapabilities.cs:3-8). Five positional members + (DataSourceEngineCapabilities.cs:23-28).
      • +
      • Depends on: MigrationPolicy, RowVersionStrategy.
      • +
      • Concept introduced, capabilities instead of engine checks. [Rubric §1, SOLID] (assesses + whether code is open for extension without edits scattered across consumers): a call site that asks + "is this engine relational?" keeps working when a fifth engine is added, while one that asks "is this + SQL Server?" must be found and edited. [Rubric §8, Data Architecture] applies too: engine + differences are recorded as data. The doc comment states the admission rule: only facts whose values + genuinely differ between engines get their own member, and everything that splits exactly along the + relational / non-relational line collapses into IsRelational (DataSourceEngineCapabilities.cs:5-7).
      • +
      • Walkthrough:
          +
        • Migrations (:24): a MigrationPolicy.
        • +
        • ConnectionStringRequired (:25): a source with no connection string is a startup + misconfiguration rather than an optional source to skip (SQL Server only, :10-13).
        • +
        • IsRelational (:26): the bundle of relational-only features: .Include() within one physical + source, transactions, raw parameterized SQL, indexes, foreign keys with meaningful delete behavior, + Any(predicate) translation, and the framework's own relational tables (outbox, inbox, internal + commands, scheduled jobs) (:14-19).
        • +
        • NullsSortFirstAscending (:27): keyset pagination depends on it (:20).
        • +
        • RowVersion (:28): a RowVersionStrategy.
        • +
        +
      • +
      • Why it's built this way: a fitness test forbids concrete engine branching outside the engine + strategies (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/DataSourceBranchingFitnessTests.cs:19,71), + so a capability value is the sanctioned way to differ + (ADR-130).
      • +
      • Where it's used: exposed as IDataSourceEngine.Capabilities + (IDataSourceEngine.cs:74). IsRelational is read by the conventions + (SoftDeleteUniqueIndexConvention at + MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:43, + RestrictDeleteByDefaultConvention at + MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/RestrictDeleteByDefaultConvention.cs:66, + CrossDataSourceDegradeConvention at + MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:66), + by DataSourceService (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceService.cs:32) + and by DomainEventSaveChangesInterceptor + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/DomainEventSaveChangesInterceptor.cs:127,236). + ConnectionStringRequired is read by PhysicalDataSource + (PhysicalDataSource.cs:59), and NullsSortFirstAscending by + EFReadRepository<TEntity, TIdentifierType>, which defaults to + true when no engine is known + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:523,721).
      • +
      +

      IExplicitKeyInsertDialect

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources.Engines · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/IExplicitKeyInsertDialect.cs:13 · Level 1 · interface (internal)

      +
      +
        +
      • What it is: the engine-specific half of an explicit-key insert. Only an engine that refuses an + explicit value for a store-generated key without a session toggle has one (SQL Server, through + SET IDENTITY_INSERT); every other engine returns null from + IDataSourceEngine.ExplicitKeyInsert and the save runs unchanged + (IExplicitKeyInsertDialect.cs:5-12).
      • +
      • Depends on: ExplicitKeyInsertGroup; EF Core's DbContext.
      • +
      • Concept: the strategy split taught on IDataSourceEngine applied to one + behavior. The doc comment draws the line explicitly: the engine-neutral half (grouping rounds, hiding + other tables' entries, excluding their domain events, pinning the connection) stays in + DbContextFactory (IExplicitKeyInsertDialect.cs:10-11).
      • +
      • Walkthrough:
          +
        • FindGroups(DbContext) (IExplicitKeyInsertDialect.cs:21): returns the added entries whose + single-column key is store-generated on this engine and carries an explicit (non-temporary) value, + one ExplicitKeyInsertGroup per table; empty when nothing needs the toggle.
        • +
        • BuildToggleSql(schema, table, enable) (:28): the raw statement that switches explicit-key + insert on or off for one table. The parameters are documented as coming from EF model metadata, + never user input (:24-25).
        • +
        +
      • +
      • Why it's built this way: a nullable dialect property lets the factory express "this engine needs + no toggle" without an engine check + (ADR-130). The + toggle is session state, so the factory pins one connection for the whole save + (DbContextFactory.cs:342-352).
      • +
      • Where it's used: implemented by SQLServerDataSourceEngine, which + returns itself (SQLServerDataSourceEngine.cs:19,49). DbContextFactory takes the explicit-key path + only when a caller requested it and the context's engine has a dialect (DbContextFactory.cs:292-294), + then drives FindGroups and BuildToggleSql (DbContextFactory.cs:334,414-427). The request flag + is set through IUnitOfWork.RequestExplicitKeyInsert + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:73); the one + first-party caller is ADC's Sessionize refresh + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeHandler.cs:192).
      • +
      +

      CosmosDataSourceEngine

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources.Engines · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/CosmosDataSourceEngine.cs:18 · Level 11 · class (internal sealed)

      +
      +
        +
      • What it is: the IDataSourceEngine strategy for Azure Cosmos DB, the one + non-relational engine.
      • +
      • Depends on: CosmosDbContext, + IEntityTypeConfigurationCosmos<TEntity, TIdentifierType>, + CosmosIntIdValueGenerator, NamespaceConventions, + ConnectionStringSettings, DataSourceEntrySettings, + TenantDataSourceOverrideSettings; EF Core Cosmos provider.
      • +
      • Concept: see IDataSourceEngine.
      • +
      • Walkthrough:
          +
        • Descriptive facts: Engine is DataSource.CosmosDB (CosmosDataSourceEngine.cs:25), + SubstitutionPriority 3, the last resort (:28), and MigrationsAssemblySettingName is null + because Cosmos migrates nothing (:33-35). ConnectionIdentityIncludesDatabaseName is true + (:38): one Cosmos account hosts many databases, so the connection string alone does not identify + the physical source.
        • +
        • Capabilities (:41-46): MigrationPolicy.Never, connection string not required, not relational, + nulls sort first ascending, RowVersionStrategy.None. ExplicitKeyInsert is null (:49).
        • +
        • Migrations-assembly readers return string.Empty (:73,76); CreateDbContext builds a + CosmosDbContext over static empty options, since all configuration happens in OnConfiguring + (:20-22,79-83).
        • +
        • ApplyKeyAndTableMapping (:86-102): all of a module's entities share one container named after + the module (falling back to the entity name) with the entity Id as partition key (:92-96); a + generated int id uses CosmosIntIdValueGenerator, otherwise ValueGeneratedNever (:98-101).
        • +
        • IncludeColumns keeps the SQL Server annotation and is unreachable in practice, since Cosmos never + maps the outbox or internal-command tables that call it (:105-110); QuoteColumn uses brackets + (:114) only for a filter that BuildSoftDeleteFilter then discards by returning null (:117).
        • +
        +
      • +
      • Why it's built this way: the Cosmos-specific mapping (container, partition key, client-side id + generation) that used to be engine branches now sits in one class + (ADR-130).
      • +
      • Where it's used: registered in DataSourceEngines + (DataSourceEngines.cs:24), and allow-listed as an engine strategy by + DataSourceBranchingFitnessTests.cs:27.
      • +
      +

      DataSourceEngines

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources.Engines · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/DataSourceEngines.cs:19 · Level 11 · class (internal static)

      +
      +
        +
      • What it is: the one registry of database engines, keyed by the shipped DataSource + value (DataSourceEngines.cs:6).
      • +
      • Depends on: IDataSourceEngine and its four implementations; BCL + FrozenDictionary.
      • +
      • Concept: the lookup half of the strategy pattern taught on IDataSourceEngine.
      • +
      • Walkthrough: Registered (DataSourceEngines.cs:22-28) is the array of the four engines in + registration order (Cosmos, PostgreSQL, SQLite, SQL Server). ByEngine (:30-31) freezes it into a + dictionary keyed by each engine's Engine. All (:34) exposes the array; For(DataSource) + (:40-43) returns the registered engine or throws InvalidOperationException naming the unimplemented + value.
      • +
      • Why it's built this way: static rather than a DI service because several consumers run where no + container is reachable (model-building conventions, EntityTypeConfiguration.ApplyEngineConventions, + SoftDeleteFilterSql, IndexBuilderExtensions), + and because engines are stateless facts about a closed enum that only Common can extend + (DataSourceEngines.cs:8-12). Adding a fifth engine is one new class plus one line in Registered + (:14-16).
      • +
      • Where it's used: substitution order is All sorted by SubstitutionPriority in + DataSourceResolver (DataSourceResolver.cs:36) and + TenancySettingsValidator + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:29); + For backs ApplicationDbContext.Engine (ApplicationDbContext.cs:61), + PhysicalDbContextFactory + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/PhysicalDbContextFactory.cs:32), + EntityTypeConfiguration<TEntity, TIdentifierType> + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:77), + SoftDeleteFilterSql + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:35,70), + FrameworkTableTargets + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/FrameworkTableTargets.cs:58,86), + and infrastructure registration (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:409).
      • +
      +

      IDataSourceEngine

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources.Engines · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/IDataSourceEngine.cs:18 · Level 11 · interface (internal)

      +
      +
        +
      • What it is: everything the framework needs to know about one database engine, so that no call + site branches on DataSource. Implementations are stateless singletons looked up + through DataSourceEngines.For (IDataSourceEngine.cs:9-12).
      • +
      • Depends on: DataSource, DataSourceEngineCapabilities, + IExplicitKeyInsertDialect, ApplicationDbContext, + PhysicalDataSource, + IEntityConfigurationAssemblyProvider, the three settings + types (ConnectionStringSettings, + DataSourceEntrySettings, + TenantDataSourceOverrideSettings); EF Core EntityTypeBuilder + and IndexBuilder.
      • +
      • Concept introduced, one strategy per engine. [Rubric §2, Design Patterns] (assesses whether + patterns solve a real problem idiomatically): a Strategy keyed by an enum replaces switch and if + statements on the engine that were spread across resolver, conventions, configuration and factory + code. [Rubric §1, SOLID] (open/closed): a fifth engine is one class, not edits at every branch. + [Rubric §34, Architecture Governance & Documentation] (assesses whether architectural rules are + enforced, not just written down): DataSourceBranchingFitnessTests fails the build on concrete engine + branching outside the engine strategies, with the remedy "route engine-specific behavior through + IDataSourceEngine (ADR-130)" (DataSourceBranchingFitnessTests.cs:19,71). The interface groups its + members in three bands: DESCRIPTIVE facts, Capabilities (plain values) and BEHAVIOUR hooks, the few + places where the engine has to act rather than answer (IDataSourceEngine.cs:13-15).
      • +
      • Walkthrough:
          +
        • Identity: Engine (IDataSourceEngine.cs:21), the enum value the engine answers for.
        • +
        • Descriptive (:23-69): SubstitutionPriority (:29, lower wins when the resolver substitutes + a configured engine for an unconfigured one; it replaces the hand-ordered EnginePreference array, + :25-28), EntityConfigurationInterface (:32, the open generic configuration interface whose + implementations belong to this engine's model), MigrationsAssemblySettingName (:38, for error + messages, null when the engine migrates nothing), ConnectionIdentityIncludesDatabaseName + (:44), three GetConnectionString overloads for the top-level section, a named DataSources + entry and a tenant override (:49,54,59), and the two migrations-assembly readers (:64,69).
        • +
        • Capabilities (:74): a DataSourceEngineCapabilities.
        • +
        • Behaviour (:76-119): CreateDbContext builds the engine's sealed context over one physical + source, never pooled (:83-86); ApplyKeyAndTableMapping<TEntity, TIdentifierType> is the body of + EntityTypeConfiguration.ApplyEngineConventions (:95-97); IncludeColumns adds covering columns + with the engine's provider annotation (:103); QuoteColumn and BuildSoftDeleteFilter produce the + filtered-index predicate, the latter null where filtered indexes are unsupported (:108,113); + ExplicitKeyInsert is the optional IExplicitKeyInsertDialect (:119).
        • +
        +
      • +
      • Why it's built this way: recorded in + ADR-130. It does not + replace the one-context-class-per-engine rule (ADR-006): each strategy creates its own sealed context.
      • +
      • Where it's used: four implementations + (CosmosDataSourceEngine, PostgreSQLDataSourceEngine, + SqliteDataSourceEngine, SQLServerDataSourceEngine), + all held by DataSourceEngines; see that section for the consumers. + ApplicationDbContext exposes the current context's engine as an + internal Engine property (ApplicationDbContext.cs:61), which interceptors and the factory read.
      • +
      +

      PostgreSQLDataSourceEngine

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources.Engines · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/PostgreSQLDataSourceEngine.cs:17 · Level 11 · class (internal sealed)

      +
      +
        +
      • What it is: the IDataSourceEngine strategy for PostgreSQL (Npgsql).
      • +
      • Depends on: PostgreSQLDbContext, + IEntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType>, + NamespaceConventions, the settings types; Npgsql EF provider.
      • +
      • Concept: see IDataSourceEngine.
      • +
      • Walkthrough:
          +
        • Descriptive facts: Engine is DataSource.PostgreSQL (PostgreSQLDataSourceEngine.cs:24), + SubstitutionPriority 1 (:27), settings key PostgreSQLMigrationsAssembly (:33), connection + identity by connection string alone (:36).
        • +
        • Capabilities (:39-44): MigrationPolicy.WhenAssemblyConfigured, connection string not + required, relational, nulls do not sort first ascending, RowVersionStrategy.ClientStamped. + ExplicitKeyInsert is null (:47): PostgreSQL accepts an explicit key without a toggle.
        • +
        • It reads its own top-level migrations assembly (:71-75) and creates a PostgreSQLDbContext + (:85-89).
        • +
        • ApplyKeyAndTableMapping (:92-106) is deliberately identical to SQL Server, table named after the + entity in the module schema (falling back to dbo), so an entity moves between the two engines by + changing its configuration base class alone (:98-100).
        • +
        • The predicate differences: Npgsql's IncludeProperties annotation (:109-110), double-quoted + identifiers (:113) and a "IsDeleted" = false soft-delete filter (:116).
        • +
        +
      • +
      • Why it's built this way: ADR-113 + keeps the mapping shared with SQL Server; ADR-130 puts what differs in this one class.
      • +
      • Where it's used: registered in DataSourceEngines (DataSourceEngines.cs:25); + allow-listed by DataSourceBranchingFitnessTests.cs:28.
      • +
      +

      SqliteDataSourceEngine

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources.Engines · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SqliteDataSourceEngine.cs:16 · Level 11 · class (internal sealed)

      +
      +
        +
      • What it is: the IDataSourceEngine strategy for SQLite.
      • +
      • Depends on: SqliteDbContext, + IEntityTypeConfigurationSqlite<TEntity, TIdentifierType>, + the settings types; EF Core SQLite provider and the SQL Server index-builder extension.
      • +
      • Concept: see IDataSourceEngine.
      • +
      • Walkthrough:
          +
        • Descriptive facts: Engine is DataSource.Sqlite (SqliteDataSourceEngine.cs:23), + SubstitutionPriority 2 (:26), settings key SqliteMigrationsAssembly (:32), connection + identity by connection string alone (:35).
        • +
        • Capabilities (:38-43): MigrationPolicy.WhenAssemblyConfigured, connection string not required, + relational, nulls sort first ascending, RowVersionStrategy.ClientStamped. ExplicitKeyInsert is + null (:46).
        • +
        • GetTopLevelMigrationsAssembly always returns empty: ConnectionStrings carries no SQLite + migrations assembly, so a mixed-engine host can never hand SQLite the SQL Server snapshot (:70-74). + Only a named DataSources entry can supply one.
        • +
        • ApplyKeyAndTableMapping (:91-103) maps to a table named after the entity with no schema (:97) + and a generated key as ValueGeneratedOnAdd().UseIdentityColumn(1, 1) (:100).
        • +
        • IncludeColumns keeps the SQL Server annotation, which the SQLite provider ignores (:106-108); + double-quoted identifiers (:112) and a "IsDeleted" = 0 filter (:115).
        • +
        +
      • +
      • Why it's built this way: see ADR-130.
      • +
      • Where it's used: registered in DataSourceEngines (DataSourceEngines.cs:26); + allow-listed by DataSourceBranchingFitnessTests.cs:30.
      • +
      +

      SQLServerDataSourceEngine

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DataSources.Engines · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SQLServerDataSourceEngine.cs:19 · Level 11 · class (internal sealed)

      +
      +
        +
      • What it is: the IDataSourceEngine strategy for SQL Server, and the only + engine that also implements IExplicitKeyInsertDialect + (SQLServerDataSourceEngine.cs:19).
      • +
      • Depends on: SQLServerDbContext, + IEntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, + ExplicitKeyInsertGroup, NamespaceConventions, + the settings types; EF Core SQL Server provider.
      • +
      • Concept: see IDataSourceEngine and, for the explicit-key toggle, + ExplicitKeyInsertGroup.
      • +
      • Walkthrough:
          +
        • Descriptive facts: Engine is DataSource.SQLServer (SQLServerDataSourceEngine.cs:26), + SubstitutionPriority 0, the preferred substitute (:29), settings key + SQLServerMigrationsAssembly (:35), connection identity by connection string alone (:38).
        • +
        • Capabilities (:41-46): MigrationPolicy.Always, connection string required, relational, + nulls sort first ascending, RowVersionStrategy.StoreGenerated. ExplicitKeyInsert returns this + (:49).
        • +
        • Reads its top-level migrations assembly (:73-77) and creates a SQLServerDbContext (:87-91).
        • +
        • ApplyKeyAndTableMapping (:94-107): table named after the entity in the module schema (falling + back to dbo), shared verbatim with PostgreSQL (:100-101).
        • +
        • Bracket-quoted identifiers (:114) and a [IsDeleted] = 0 filter (:117).
        • +
        • FindGroups (:120-159) walks the change tracker for Added entries (:128), skips anything + without a single-property primary key (:132-134), keeps only keys whose SQL Server value-generation + strategy is IdentityColumn (:137-141), skips entries whose key is still an EF temporary value, + since only an explicitly set (imported) value needs the toggle (:143-146), and buckets survivors by + (schema, table) with dbo as the schema fallback (:148-155).
        • +
        • BuildToggleSql (:162-163) concatenates SET IDENTITY_INSERT [schema].[table] ON|OFF; the + identifier cannot be parameterized, and the caller suppresses S2077 with that justification + (DbContextFactory.cs:413).
        • +
        +
      • +
      • Why it's built this way: SQL Server is the default engine, so it alone requires a connection + string and migrates even with no configured assembly (falling back to the Default source's assembly, + MigrationPolicy.cs:16-19). Implementing the dialect on the engine itself keeps the identity-insert + rules (identity strategy, temporary values) next to the other SQL Server facts + (ADR-130).
      • +
      • Where it's used: registered in DataSourceEngines (DataSourceEngines.cs:27); + allow-listed by DataSourceBranchingFitnessTests.cs:29; its dialect is driven by + DbContextFactory (DbContextFactory.cs:292-294,334).
      • +

      DetectChangesScope

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DbContexts · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:279 · Level 0 · struct (private readonly, nested)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DbContexts · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:284 · Level 0 · struct (private readonly, nested)

      • What it is: a two-field disposable struct nested inside @@ -4860,25 +5533,25 @@

        DetectChangesScope

        EF's ChangeTracker.Entries<T>() runs a full DetectChanges pass on every call and memoizes nothing. Interceptors scan the tracker during SavingChanges and EF then detects once more on its own before building the save, so a single save paid three O(tracked entities x properties) - snapshot comparisons where one suffices (ApplicationDbContext.cs:252-259). Turning detection off + snapshot comparisons where one suffices (ApplicationDbContext.cs:257-264). Turning detection off for the duration of the save is the optimization; a using-scoped struct is what makes turning it back on unforgettable, including on the exception path.
      • -
      • Walkthrough: the primary constructor DetectChangesScope(ChangeTracker changeTracker, bool previousSetting) (ApplicationDbContext.cs:279) captures the tracker and the flag value that was in - force before suppression. Dispose() (ApplicationDbContext.cs:281) is a single expression-bodied +
      • Walkthrough: the primary constructor DetectChangesScope(ChangeTracker changeTracker, bool previousSetting) (ApplicationDbContext.cs:284) captures the tracker and the flag value that was in + force before suppression. Dispose() (ApplicationDbContext.cs:286) is a single expression-bodied assignment that writes previousSetting back onto changeTracker.AutoDetectChangesEnabled. It is - created only by DetectChangesOnce() (ApplicationDbContext.cs:269-277), which reads the current + created only by DetectChangesOnce() (ApplicationDbContext.cs:274-282), which reads the current setting (:235), calls ChangeTracker.DetectChanges() once when detection was on (:236-237), sets the flag to false (:239), and hands back the scope (:240).
      • Why it's built this way: readonly struct means no heap allocation on a path that runs on every save, and private keeps the mechanism invisible to callers. Restoring the previous value rather than hardcoding true is the load-bearing detail: a caller that had deliberately disabled auto-detect keeps its choice and never gets an unexpected detection pass on the way out - (ApplicationDbContext.cs:264-266). Suppressing the remaining passes is safe because everything the + (ApplicationDbContext.cs:269-271). Suppressing the remaining passes is safe because everything the interceptors do afterwards bypasses detection anyway: the audit interceptor writes through - entry.Property(...).CurrentValue (AuditSaveChangesInterceptor.cs:57-60) and the domain-event + entry.Property(...).CurrentValue (AuditSaveChangesInterceptor.cs:66-69) and the domain-event interceptor adds outbox rows through Add, both of which take effect on the entry immediately.
      • -
      • Where it's used: both save overrides that EF funnels through, SaveChangesAsync(bool, CancellationToken) (ApplicationDbContext.cs:209-215) and SaveChanges(bool) - (ApplicationDbContext.cs:242-246), open one with using var detection = DetectChangesOnce(). The +
      • Where it's used: both save overrides that EF funnels through, SaveChangesAsync(bool, CancellationToken) (ApplicationDbContext.cs:214-220) and SaveChanges(bool) + (ApplicationDbContext.cs:247-251), open one with using var detection = DetectChangesOnce(). The behavior is pinned by SaveChangeDetectionTests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/SaveChangeDetectionTests.cs:31, :47, :64, :78, :90), which asserts detection runs exactly once, that an untracked property @@ -4886,7 +5559,7 @@

        DetectChangesScope

        save, and that a default context is left with detection enabled for the next caller.
      • Caveats / not-in-source: the remarks name two tracker-scanning interceptors (AuditSaveChangesInterceptor at - AuditSaveChangesInterceptor.cs:52 and + AuditSaveChangesInterceptor.cs:61 and DomainEventSaveChangesInterceptor at DomainEventSaveChangesInterceptor.cs:221), which are the two that are always registered. Two optional interceptors enumerate the tracker as well when a host opts into them @@ -4896,58 +5569,6 @@

        DetectChangesScope

        AuditTrailSaveChangesInterceptor.cs:198), so the suppression saves strictly more than the comment claims; the comment is narrower than the code, not wrong about the mechanism.
      -

      IdentityInsertGroup

      -
      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DbContexts.Factory · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:424 · Level 0 · record (private sealed, nested)

      -
      -
        -
      • What it is: a three-member private record nested in DbContextFactory, - (string Schema, string Table, List<EntityEntry> Entries) (DbContextFactory.cs:424). It names one - batch of pending inserts that all target the same table and all carry explicit identity values, which - is exactly the unit SQL Server's SET IDENTITY_INSERT operates on.
      • -
      • Depends on: EF Core's EntityEntry, and nothing else.
      • -
      • Concept introduced, importing rows that already have ids. [Rubric §8, Data Architecture] - (assesses whether persistence mechanics are deliberate): normally a database-generated identity - column means the application never supplies the id. An import from an external system (ADC's - Sessionize refresh) must preserve the source's ids, and SQL Server only allows that with - SET IDENTITY_INSERT <table> ON, one table at a time per session (DbContextFactory.cs:297-302). - Grouping the affected entries by table is what turns that constraint into a loop.
      • -
      • Walkthrough:
          -
        • GetIdentityInsertGroups (DbContextFactory.cs:381-422) builds the list: it walks the change - tracker for Added entries (:366-369), skips anything without a single-property primary key - (:372-374), keeps only properties whose SQL Server value-generation strategy is - IdentityColumn (:376-381), and then skips entries whose id is still an EF temporary value - (:386-387), since a temporary value means the application did not set one. Survivors are bucketed - by (schema, table) with "dbo" as the schema fallback (:389-402).
        • -
        • SaveWithIdentityInsertAsync (DbContextFactory.cs:303-375) consumes the groups: with none it - falls back to a plain save (:290-291); otherwise, per group, it flips every Added entry - belonging to the other groups to Unchanged so this round's batch touches one table only - (:300-306), runs SET IDENTITY_INSERT [schema].[table] ON, saves, and turns it OFF in a - finally (:324-337), then restores the hidden entries' states in an outer finally (:340-346). - Any remaining changes get a final ordinary save (:350-353).
        • -
        • Capture exclusion (DbContextFactory.cs:333-335, :342): before each round it calls - DomainEventSaveChangesInterceptor.BeginCaptureExclusion - with exactly the hidden entities and ends the exclusion afterwards. The comment (:308-313) - explains the bug this prevents: capture serializes an aggregate's events to the outbox and clears - them, so without the exclusion a row written a round later would have published its event a round - early. It names the hidden entries explicitly rather than filtering by state, because "skip every - Unchanged aggregate" would also drop events legitimately raised on an already-saved aggregate, - which is how the identity module publishes registration events.
        • -
        -
      • -
      • Why it's built this way: the whole dance is confined to the SQL Server path of one private - method, guarded by an opt-in flag, so the normal save path pays nothing. The raw SQL is covered by a - justified CA2100 suppression (DbContextFactory.cs:242-243) and an S2077 pragma (:323, - :338), both stating that schema and table names come from EF model metadata rather than user input, - and SET IDENTITY_INSERT cannot take a parameterized identifier.
      • -
      • Where it's used: only inside DbContextFactory, reached when a caller has - invoked RequestIdentityInsert() (:276) before the save, which the save path reads and immediately - clears (:227-228). The one first-party caller is ADC's Sessionize refresh, which signals the unit of - work before saving imported entities - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeHandler.cs:168), - through IUnitOfWork.RequestIdentityInsert - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:73).
      • -

      ModelBuilderExtensions

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DbContexts · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ModelBuilderExtensions.cs:10 · Level 0 · class (internal static)

      @@ -4988,7 +5609,7 @@

      ModelBuilderExtensions

      configurations depend on services without a parameterless-ctor constraint.
    413. Where it's used: called from ApplicationDbContext.ApplyConfigurationsForEntitiesInContext - (ApplicationDbContext.cs:970-976), which passes the engine's configuration interface (for example + (ApplicationDbContext.cs:974-980), which passes the engine's configuration interface (for example IEntityTypeConfigurationSQLServer<TEntity, TIdentifierType>) and a filter that matches each entity's registry-resolved DataSourceKey.
    414. @@ -5011,10 +5632,10 @@

      TransactionCommitAmbiguousException connections) as transient, and EF decides retriability by walking an exception's whole inner chain, so any wrapper carrying the transient error would still be retried, re-running every write of an operation whose commit may already be durable, including its outbox rows - (TransactionCommitAmbiguousException.cs:8-14, DbContextFactory.cs:555-558). That is why the + (TransactionCommitAmbiguousException.cs:8-14, DbContextFactory.cs:571-574). That is why the commit failure is returned rather than thrown from RunTransactionalAttemptAsync and TryCommit - (DbContextFactory.cs:568-572, :572-574, :617-620) and only converted into this exception - past the strategy (DbContextFactory.cs:559-560). Second, semantically: "it failed" and "nobody + (DbContextFactory.cs:584-588, :572-574, :617-620) and only converted into this exception + past the strategy (DbContextFactory.cs:575-576). Second, semantically: "it failed" and "nobody can say whether it failed" call for different recovery, so the type itself is the signal.
    415. Concept introduced, naming the partial outcome. [Rubric §13, Observability & Operability] (assesses whether an operator can tell what actually happened from what the system reports): commits @@ -5028,7 +5649,7 @@

      TransactionCommitAmbiguousException set; (Exception innerException) (:45-46), which pairs the provider's failure with the default message; and the four-argument diagnostic one (:57-69), which additionally takes the committed, ambiguous and rolled-back sources and composes them into the message. That last one is the one - DbContextFactory actually constructs (DbContextFactory.cs:665); it + DbContextFactory actually constructs (DbContextFactory.cs:722); it null-coalesces both lists to empty (:63, :66-68) so a caller passing null gets an empty group rather than a second exception.

    416. CommittedSources (:76), AmbiguousSource (:85), RolledBackSources (:93): the @@ -5052,11 +5673,11 @@

      TransactionCommitAmbiguousException OutboxProcessor if the commit did land (ADR-003); and the deferred in-process dispatch is dropped, so no handler acts on state that may not exist - (DbContextFactory.cs:681-702). The source comment records that a witness row (a marker written + (DbContextFactory.cs:738-759). The source comment records that a witness row (a marker written inside each source's transaction that a replay could read) would close the multi-source gap entirely, and that it is deliberately not built, because the single transactional source every host runs today - needs none (DbContextFactory.cs:506-516).

    417. -
    418. Where it's used: thrown at DbContextFactory.cs:560, constructed at :646; pinned by + needs none (DbContextFactory.cs:522-532).
    419. +
    420. Where it's used: thrown at DbContextFactory.cs:576, constructed at :646; pinned by DbContextFactoryCommitAmbiguityTests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:73, :105, :129, :149, :168), which drives a context whose execution strategy retries on any @@ -5089,6 +5710,8 @@

      ApplicationDbContext

      IEntityDataSourceRegistry, IEntityConfigurationAssemblyProvider, PhysicalDataSource, DataSourceKey, + IDataSourceEngine through DataSourceEngines, + SoftDeleteFilterSql, IAuditableEntity, ITenantEntity, AuditableBaseEntity<TIdentifierType>, @@ -5112,40 +5735,52 @@

      ApplicationDbContext

      live in the interceptor pipeline rather than inline in every handler.
    421. Concept introduced, named global query filters. [Rubric §11, Security] and [Rubric §30, Compliance, Privacy & Data Governance] (both assess whether isolation of other parties' data is structural rather than per-query discipline): this class declares two filters by name, SoftDelete - (ApplicationDbContext.cs:469) and Tenant (:391). EF composes named filters with AND, so a + (ApplicationDbContext.cs:474) and Tenant (:477). EF composes named filters with AND, so a tenant-owned soft-deletable entity is filtered on both without either filter knowing about the other, and a caller asking to see deleted rows drops exactly SoftDelete and leaves Tenant in force - (:383-387, :405-412). Isolation therefore cannot be forgotten at a call site.
    422. + (:469-474, :490-499). Isolation therefore cannot be forgotten at a call site.
    423. Walkthrough:
      • Primary constructor (ApplicationDbContext.cs:46-51): takes DbContextOptions, an IServiceProvider, an IEntityConfigurationAssemblyProvider, and the PhysicalDataSource this instance targets, delegating to DbContext(options).
      • -
      • DataSourceKey (:49): exposes the (engine, database name) pair this context serves; - PhysicalSource (:97) exposes the resolved connection info to subclasses as internal.
      • -
      • Model-gate fields (:63, :76, :94, :116): _schedulerTableEnabled, +
      • DataSourceKey (:54): exposes the (engine, database name) pair this context serves; + PhysicalSource exposes the resolved connection info to subclasses as internal.
      • +
      • Engine (:61): internal, DataSourceEngines.For(physicalDataSource.Key.Engine), the + IDataSourceEngine this context targets. Its doc comment (:56-60) names + both halves of what it answers: what the engine can do (whether it hosts the framework's + relational tables, so the transactional outbox, which Cosmos DB does not) and how its SQL and keys + are shaped. Every engine branch in this class now reads Engine.Capabilities rather than + comparing a DataSource value, and the same property is what + DomainEventSaveChangesInterceptor tests + (Engine.Capabilities.IsRelational, DomainEventSaveChangesInterceptor.cs:127, :236) and what + DbContextFactory reads for transactions and explicit-key inserts.
      • +
      • EngineAnnotation (:67): the internal const model annotation name "MMCA:Engine", which + carries the engine of the model being built while entity configurations are applied (see + ApplyConfigurationsForEntitiesInContext below).
      • +
      • Model-gate fields (:81, :94, :112, :129): _schedulerTableEnabled, _auditTrailTableEnabled, _refreshSessionTableEnabled and _permissionGrantTableEnabled, all four resolved in OnConfiguring and read by the model-building methods. Their remarks state the rule that keeps them correct: OnModelCreating must not depend on anything the model cache key does not cover, so the settings lookups happen once, up front, in the same place the interceptors are - resolved. The fourth gate (:101-116) is shaped like the refresh-session one but keyed on a + resolved. The fourth gate (:129) is shaped like the refresh-session one but keyed on a registration rather than a configuration flag: AddStoredPermissionGrants(configuration) registers Auth.PermissionGrantModelGate, which is the whole opt-in, and Authentication:PermissionGrants:DataSourceName (default Default) names the one database that - carries the rows, resolved by ResolvePermissionGrantGate (:338) and read by + carries the rows, resolved by ResolvePermissionGrantGate (:913) and read by ConfigurePermissionGrants.
      • -
      • TenantIdAccessor (:134) and CurrentTenantId (:141): an internal Func<string?>? +
      • TenantIdAccessor (:147) and CurrentTenantId (:154): an internal Func<string?>? assigned by the scoped DbContextFactory at context creation - (DbContextFactory.cs:104, :134), and the public property that invokes it. The remarks explain + (DbContextFactory.cs:114, :151), and the public property that invokes it. The remarks explain why it is an accessor and not a copied value: a context can be created before the request's tenant is resolved, and a copy taken at that moment would pin the context to the wrong answer for its whole life. null reads as "no tenant", which makes the Tenant filter inert for background services, seeders and admin flows.
      • -
      • OutboxOriginAccessor (:143-158) and CurrentOutboxOrigin (:160-164): the same +
      • OutboxOriginAccessor (:171) and CurrentOutboxOrigin (:177): the same accessor shape, one step wider. OutboxOriginAccessor is an internal Func<Outbox.OutboxOrigin>? assigned by the scoped DbContextFactory alongside the tenant accessor - (DbContextFactory.cs:149-153) and read once per save, before its capture loop, by + (DbContextFactory.cs:155) and read once per save, before its capture loop, by DomainEventSaveChangesInterceptor when it writes outbox rows. The remarks name the reason it is an accessor rather than an injected service: a context is built by the singleton PhysicalDbContextFactory and carries only the root provider, and the @@ -5154,40 +5789,34 @@

        ApplicationDbContext

        context created outside a scope, design time or a directly-constructed test context) reads as "nothing captured", and CurrentOutboxOrigin falls back to default in that case.
      • ValReturn<T>: the nested keyless scalar holder, documented in its own section above.
      • -
      • SupportsOutbox (:172): internal virtual, true by default; the Cosmos subclass overrides - it to false. Its doc comment states which contexts it means: the relational contexts, SQL Server, - PostgreSQL and SQLite (:166-171). Read by - DomainEventSaveChangesInterceptor - (DomainEventSaveChangesInterceptor.cs:127, :235).
      • -
      • CurrentSaveUserId (:143): internal audit user id with a private setter, written by the +
      • CurrentSaveUserId (:184): internal audit user id with a private setter, written by the save overloads and read by AuditSaveChangesInterceptor; null - marks a system operation and the interceptor resolves it to default - (AuditSaveChangesInterceptor.cs:50).
      • -
      • SaveChangesAsync(userId, ct) (:153-167): the mutation entry point. Opens a MiniProfiler step - (:155), sets CurrentSaveUserId, calls base.SaveChangesAsync, and clears the id again in a - finally (:161-166) so a later plain base.SaveChangesAsync on the same instance (an internal + marks a system operation and the interceptor resolves it to default.
      • +
      • SaveChangesAsync(userId, ct) (:194): the mutation entry point. Opens a MiniProfiler step, + sets CurrentSaveUserId, calls base.SaveChangesAsync, and clears the id again in a + finally so a later plain base.SaveChangesAsync on the same instance (an internal outbox write, for example) cannot silently reuse the previous caller's identity for its stamps.
      • -
      • SaveChanges(userId) (:189-200): the synchronous counterpart with the same set/reset - discipline. Its doc comment records the behavioral difference (:181-186): the sync path cannot +
      • SaveChanges(userId) (:230): the synchronous counterpart with the same set/reset + discipline. Its doc comment records the behavioral difference: the sync path cannot dispatch events in-process, so captured events are delivered by the outbox processor instead.
      • -
      • Change-detection overrides (:173-179, :206-210): both EF save overloads wrap the base call +
      • Change-detection overrides (:214, :247): both EF save overloads wrap the base call in using var detection = DetectChangesOnce(), the optimization taught under DetectChangesScope.
      • -
      • OnConfiguring (:249-306): resolves the two always-present interceptors from DI (:256-257) +
      • OnConfiguring (:290-353): resolves the two always-present interceptors from DI (:297-298) and adds them, inserting TenantSaveChangesInterceptor - between them when it is registered (:264-271). Registration order is execution order, and the - comment (:259-263) states why the tenant interceptor belongs in the middle: after the audit stamps + between them when it is registered (:305-312). Registration order is execution order, and the + comment (:300-304) states why the tenant interceptor belongs in the middle: after the audit stamps (it must not run against half-stamped entries) and before the domain-event interceptor serializes outbox rows, so those rows describe an entity whose tenant is already final. AuditTrailSaveChangesInterceptor is appended last when - present (:278-281), because it diffs the final values. Both optional interceptors are fetched with + present (:319-322), because it diffs the final values. Both optional interceptors are fetched with GetService, not GetRequiredService: a host that never opted in, a design-time context, or a directly constructed test context must still build. The method then resolves the four model gates, - including _permissionGrantTableEnabled (:338), calls - ConfigureSensitiveDataLogging (:340), and replaces EF's + including _permissionGrantTableEnabled (:343), calls + ConfigureSensitiveDataLogging (:345), and replaces EF's IModelCacheKeyFactory with DataSourceModelCacheKeyFactory - (:345) so each database gets its own model.
      • -
      • ConfigureSensitiveDataLogging (:351-370): a private helper called from OnConfiguring. It + (:350) so each database gets its own model.
      • +
      • ConfigureSensitiveDataLogging (:367-375): a private helper called from OnConfiguring. It calls optionsBuilder.EnableSensitiveDataLogging() only when SensitiveDataLoggingGate.IsEnabled says both PersistenceSettings asked for it and @@ -5195,107 +5824,122 @@

        ApplicationDbContext

        directly-constructed context (which registers neither) reads as "off". Living on the shared base rather than in each engine's context class is what keeps the guarantee from holding for SQL Server while a fourth engine quietly leaks it.
      • -
      • ConfigureConventions (:340-406): adds four model-finalization conventions in a fixed order. +
      • ConfigureConventions (:378-409): adds four model-finalization conventions in a fixed order. CrossDataSourceDegradeConvention strips FK constraints and navigations between entities in different physical databases (a structural no-op in the collapsed-monolith case); SoftDeleteUniqueIndexConvention makes unique indexes on soft-deletable entities exclude deleted rows, so a soft-deleted row does not block re-creating the "same" record; RestrictDeleteByDefaultConvention - (:385-406) runs last of the three finalizing conventions, deliberately, so it never stamps a + (:394-399) runs last of the three finalizing conventions, deliberately, so it never stamps a relationship the cross-source convention has already removed, and records on every foreign key whether its delete behavior was chosen or inherited, inverting EF's own cascade default for a required relationship nobody configured; and, opt-in, StronglyTypedIdModelConfiguration.Apply - (:385-406) runs only when a StronglyTypedIdRegistry is registered (AddStronglyTypedIds, + (:401-408) runs only when a StronglyTypedIdRegistry is registered (AddStronglyTypedIds, ADR-115), mapping every declared wrapper type to the primitive it wraps on every engine because this runs on the one base context; absent the service it is a no-op and the primitive identifier aliases stay the identifier model.
      • Set<TEntity>(): a public override that forwards straight to base.Set<TEntity>() and adds no behavior of its own.
      • -
      • OnModelCreating (:414-428): applies soft-delete filters, tenant filters and concurrency - tokens, registers the four keyless ValReturn<T> views, then configures the outbox (now passed - physicalDataSource.Key.Engine), the inbox, the internal-command job queue +
      • OnModelCreating (:416-444): applies soft-delete filters, tenant filters and concurrency + tokens, registers the four keyless ValReturn<T> views, then configures the outbox (passed + physicalDataSource.Key.Engine, :423), the inbox, the internal-command job queue (ConfigureInternalCommands, one table per relational source like the outbox), the scheduler table, the audit-trail table, the refresh-session table, and, last, the stored permission-grant table via ConfigurePermissionGrants, gated the same two-condition way as refresh sessions.
      • -
      • ApplySoftDeleteFilters (:367-381): protected static; iterates every non-owned +
      • ApplySoftDeleteFilters (:453-467): protected static; iterates every non-owned IAuditableEntity type and builds an expression-tree - HasQueryFilter("SoftDelete", e => e.IsDeleted == false) (:373-379). Expression trees are + HasQueryFilter("SoftDelete", e => e.IsDeleted == false) (:459-465). Expression trees are required because the CLR type is only known at runtime; owned types are excluded because they inherit the parent filter. [Rubric §5, Vertical Slice] (a global filter removes per-query Where(!IsDeleted) boilerplate from every slice).
      • -
      • ApplyTenantFilters (:428-488): protected (instance, because the filter body reads this +
      • ApplyTenantFilters (:514-574): protected (instance, because the filter body reads this context). For every non-owned ITenantEntity - it configures the discriminator column as required, capped at TenantIdMaxLength of 64 (:397) - and non-Unicode (:445-448), indexes it for every engine except Cosmos (:457-467), and builds - HasQueryFilter("Tenant", e => CurrentTenantId == null || EF.Property<string>(e, "TenantId") == CurrentTenantId) (:469-486). Three mechanisms are worth internalizing here. The filter embeds - this context as a constant typed as ApplicationDbContext (:435), which EF rewrites to the + it configures the discriminator column as required, capped at TenantIdMaxLength of 64 (:483) + and non-Unicode (:531-534), indexes it only when Engine.Capabilities.IsRelational (:543-553, + so every engine but Cosmos), and builds + HasQueryFilter("Tenant", e => CurrentTenantId == null || EF.Property<string>(e, "TenantId") == CurrentTenantId) (:566-572). Three mechanisms are worth internalizing here. The filter embeds + this context as a constant typed as ApplicationDbContext (:521), which EF rewrites to the executing context at query compile time and lifts CurrentTenantId into a SQL parameter, so two scopes on two tenants share one compiled model and still read disjoint rows. It reads the column - through EF.Property rather than a CLR member access (:473-478), which works for an explicitly + through EF.Property rather than a CLR member access (:557-564), which works for an explicitly implemented interface member and for a shadow property alike. And the supporting index follows the filter composition: an entity that is also an IAuditableEntity gets (TenantId, IsDeleted) because every read of it carries TenantId = @tenant AND IsDeleted = 0, while a tenant-only entity - keeps the single-column index (:450-466).
      • -
      • ConfigureConcurrencyTokens (:572): protected (instance, because it reads - Database.ProviderName). It applies IsRowVersion() on SQL Server (database-generated - rowversion) or IsConcurrencyToken() on the other relational providers, PostgreSQL and SQLite - (application-managed, :572), to the RowVersion property of every non-owned auditable entity. EF - then includes the token in UPDATE/DELETE WHERE clauses and throws - DbUpdateConcurrencyException on conflicts. [Rubric §8, Data Architecture].
      • -
      • QuoteColumn and IncludeProperties (:601-666): two private static helpers the filtered - outbox/internal-command indexes below now route through, added once a second relational provider - joined SQL Server and SQLite. QuoteColumn(DataSource engine, string column) (:601-621) quotes a - column name the way the engine expects inside a filtered-index predicate string: SQL Server and - SQLite both accept the bracketed [column] form they have always produced, PostgreSQL rejects - brackets and needs the SQL-standard "column" form. IncludeProperties (:622-666) picks between - the SQL Server and PostgreSQL IncludeProperties extension overloads, which share one signature and - are ambiguous unqualified once both providers are referenced in the same project, by dispatching on - the engine of the model being built.
      • -
      • ConfigureOutbox (:528-563 region, now taking DataSource engine): maps OutboxMessages in - dbo with length/unicode constraints, plus three purpose-built filtered indexes, now built through - QuoteColumn/IncludeProperties so the same method body produces valid filter and include syntax + keeps the single-column index (:536-553).
      • +
      • ConfigureConcurrencyTokens (:588-608): protected (instance, because it reads this + context's Engine). When Engine.Capabilities.RowVersion is + RowVersionStrategy.StoreGenerated (:591, SQL Server's rowversion) it + applies IsRowVersion(); on the other relational engines, PostgreSQL and SQLite, it applies + IsConcurrencyToken() (:599-606), to the RowVersion property of every non-owned auditable + entity. The doc comment (:576-587) names who manages the value on those engines: + AuditSaveChangesInterceptor writes a fresh value on every insert + and update (it checks RowVersionStrategy.ClientStamped, AuditSaveChangesInterceptor.cs:59), so + the UPDATE's WHERE clause actually detects a concurrent writer. EF then includes the token in + UPDATE/DELETE WHERE clauses and throws DbUpdateConcurrencyException on conflicts. + [Rubric §8, Data Architecture].
      • +
      • QuoteColumn and IncludeColumns (:610-648): two private static helpers the filtered + outbox, internal-command and scheduler indexes below route through, each now a one-line delegation + to the engine. QuoteColumn(DataSource engine, string column) (:618-619) forwards to + SoftDeleteFilterSql.QuoteColumn, and its doc comment (:611-613) states + the result: brackets on SQL Server, SQL-standard double quotes on PostgreSQL and SQLite. + IncludeColumns (:644-648) forwards to DataSourceEngines.For(engine).IncludeColumns, which picks + the provider's own IncludeProperties overload (the SQL Server and PostgreSQL extensions share one + signature and are ambiguous unqualified once both are referenced). Its remarks (:631-643) record + two deliberate choices: every engine other than PostgreSQL keeps the SQL Server annotation, SQLite + included because its provider ignores it, and the helper takes property names rather than a + selector so the scheduler table does not reflect as a Persistence -> Scheduling type reference.
      • +
      • ConfigureOutbox (:661, taking DataSource engine): maps OutboxMessages in + dbo with length/unicode constraints, plus three purpose-built filtered indexes, built through + QuoteColumn/IncludeColumns so the same method body produces valid filter and include syntax on every relational engine. IX_OutboxMessages_Pending covers the poll path over (ProcessedOn, OccurredOn) filtered to the processed column being null and includes RetryCount and LockedUntil so the processor's extra predicates do not force a key lookup per candidate row; IX_OutboxMessages_Processed covers the retention sweep over rows the pending index deliberately excludes; and IX_OutboxMessages_Ordering over (OrderingKey, OccurredOn), filtered to keyed pending rows, answers the claim predicate's "is there an earlier unprocessed row with this key?" question with a seek instead of a scan per candidate row. [Rubric §12, Performance & Scalability].
      • -
      • ConfigureInbox (:570-584): maps InboxMessages in dbo with a unique - IX_InboxMessages_MessageId (the consumer-side idempotency key, :576-578) and an - IX_InboxMessages_ProcessedOn index so the age-based purge has something to seek (:582-583).
      • -
      • ConfigureScheduler (:594-619): the first gated table. It returns immediately unless - _schedulerTableEnabled (:596-599), then maps +
      • ConfigureInbox (:721): maps InboxMessages in dbo with a unique + IX_InboxMessages_MessageId (the consumer-side idempotency key) and an + IX_InboxMessages_ProcessedOn index so the age-based purge has something to seek.
      • +
      • ConfigureScheduler (:802): the first gated table. It returns immediately unless + _schedulerTableEnabled, then maps ScheduledJobEntry to ScheduledJobs in - dbo keyed by JobName, with IX_ScheduledJobs_NextRunOn including the lease columns - (:615-617). The index comment (:610-614) records the load-bearing decision: it is deliberately + dbo keyed by JobName, with IX_ScheduledJobs_NextRunOn including the lease columns through + IncludeColumns (:831). The index comment records the load-bearing decision: it is deliberately not filtered to unlocked rows, because the poll must also find rows whose lease has expired, which is how a dead replica's work is reclaimed.
      • -
      • ConfigureAuditTrail (:628-657): the second gated table, mapping +
      • ConfigureAuditTrail (:847): the second gated table, mapping AuditTrailEntry to AuditTrailEntries in dbo with a read index over - (EntityType, EntityKey, ChangedOn) (:648-649) and a retention index over ChangedOn - (:654-655). Note the asymmetry with the scheduler, stated at :69-73 and :621-627: the job + (EntityType, EntityKey, ChangedOn) and a retention index over ChangedOn. Note the asymmetry + with the scheduler, stated in the gate fields' remarks (:81, :94): the job table is host-scoped and lives in the Default source only, while the trail table belongs in every relational source, because a trail row must commit in the same transaction as the change it describes and a transaction does not span databases (the outbox precedent).
      • -
      • ConfigureRefreshSessions (:673-681): the third gated table, and the one gated on a - configurable source rather than a fixed one. It returns unless _refreshSessionTableEnabled - (:675-678), which requires both RefreshSessions:Enabled and this context targeting the source - named by RefreshSessions:DataSourceName (:295-298); when it passes it simply calls - RefreshSessionModelBuilderExtensions.ApplyRefreshSessionConfiguration - (:680). The doc comment states why the mapping lives in the framework base rather than in a - consumer's context class (:664-671): under +
      • ConfigureRefreshSessions (:892): the third gated table, and the one gated on a + configurable source rather than a fixed one. It returns unless _refreshSessionTableEnabled, + which requires both RefreshSessions:Enabled and this context targeting the source + named by RefreshSessions:DataSourceName (:336-339); when it passes it simply calls + RefreshSessionModelBuilderExtensions.ApplyRefreshSessionConfiguration. + The doc comment states why the mapping lives in the framework base rather than in a + consumer's context class: under ADR-006 downstream apps run on the sealed engine contexts and have no context class of their own to override, and RefreshSession is not an AuditableBaseEntity, so the module entity-configuration mechanism does not reach it either.
      • -
      • ApplyConfigurationsForEntitiesInContext (:690-717): the discovery method subclasses call - from their OnModelCreating. It maps the engine to its configuration interface (:692-698), - resolves IEntityDataSourceRegistry (:705), then for each assembly - from the provider calls +
      • ApplyConfigurationsForEntitiesInContext (:953-987): the discovery method subclasses call + from their OnModelCreating. It asks the engine for its configuration interface + (DataSourceEngines.For(dataSource).EntityConfigurationInterface, :955) rather than switching on + the DataSource value, resolves IEntityDataSourceRegistry (:962), + then for each assembly from the provider calls ModelBuilderExtensions.ApplyAllConfigurations with a filter that keeps only entities whose registry-resolved key equals this DataSourceKey, or, for unregistered - entities, only when this context is the engine's Default source (:709-715).
      • + entities, only when this context is the engine's Default source (:974-980). The whole pass runs + inside a try/finally that sets EngineAnnotation to this context's engine on the model first + (:969) and removes it afterwards (:985). The comment (:964-968) gives the reason: a + configuration declared for one engine can be applied to another engine's model when the host + substitutes an unconfigured engine, so it cannot trust its own attribute for SQL text and reads the + effective engine from the annotation instead; removing it keeps the finished model and every + migration snapshot unchanged.
    424. Why it's built this way: @@ -5317,13 +5961,17 @@

      ApplicationDbContext

      strongly typed identifiers apply only when a host opts in (ADR-115); and the restrict-by-default delete convention inverts EF's own cascade default for every non-owned foreign key - (ADR-119). The comment at - :700-704 records one more deliberate fallback: an entity configured without the attributed base + (ADR-119). Every + engine-specific branch reads the engine object behind Engine rather than comparing DataSource + values inline + (ADR-130), so a fact + about an engine is stated once, on that engine. The comment above the registry lookup (:957-961) + records one more deliberate fallback: an entity configured without the attributed base classes lands in the Default model but is not routable through the unit of work.
    425. Where it's used: inherited by the four concrete contexts below; created per source by - PhysicalDbContextFactory (PhysicalDbContextFactory.cs:46-49), cached - per scope and given its tenant accessor by DbContextFactory - (DbContextFactory.cs:104), and consumed by the interceptors and the outbox processor. The model + PhysicalDbContextFactory through the engine's CreateDbContext + (PhysicalDbContextFactory.cs:32), cached per scope and given its tenant accessor by + DbContextFactory (DbContextFactory.cs:106-114), and consumed by the interceptors and the outbox processor. The model gates are pinned by SchedulerModelGateTests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Scheduling/SchedulerModelGateTests.cs:28, :38, :48, :59), AuditTrailModelGateTests @@ -5364,11 +6012,11 @@

      DataSourceModelCacheKeyFactory

      the key: the tenant. One compiled model serves every tenant, and the tenant value enters as a SQL parameter through the Tenant query filter instead (see ApplicationDbContext.ApplyTenantFilters and its remarks at - ApplicationDbContext.cs:495-501), which is what keeps a multi-tenant host from building one model + ApplicationDbContext.cs:500-506), which is what keeps a multi-tenant host from building one model per tenant.
    426. Where it's used: registered in ApplicationDbContext.OnConfiguring via optionsBuilder.ReplaceService<IModelCacheKeyFactory, DataSourceModelCacheKeyFactory>() - (ApplicationDbContext.cs:345), so every engine context inherits it.
    427. + (ApplicationDbContext.cs:350), so every engine context inherits it.

      CosmosDbContext

      @@ -5388,19 +6036,30 @@

      CosmosDbContext

    428. Walkthrough:
      • 4-arg constructor (CosmosDbContext.cs:15-20): forwards options, service provider, assembly provider, and physical source to the base.
      • -
      • Emulator detection (CosmosDbContext.cs:23-65): checks the connection string for the well-known - emulator account-key prefix "C2y6yDjf5" (:29). The emulator path uses ConnectionMode.Gateway - and an HttpClientFactory whose handler installs - DangerousAcceptAnyServerCertificateValidator for the self-signed cert, guarded by a - #pragma warning disable S4830 and a comment that this is safe only in local dev (:41-52). The - production path uses ConnectionMode.Direct with MaxRequestsPerTcpConnection(20) and - MaxTcpConnectionsPerEndpoint(32) (:57-59). The database name comes from - PhysicalSource.CosmosDatabaseName (:34).
      • -
      • SupportsOutbox => false (CosmosDbContext.cs:70): overrides the base; Cosmos has no - relational outbox table, so domain events are dispatched in-process only.
      • -
      • OnModelCreating (CosmosDbContext.cs:73-103): applies the Cosmos configurations (:74), then - Ignore<OutboxMessage>() (:77) and, for the same relational-only reason, - Ignore<InternalCommandMessage>() (:80), then removes every index from every entity type +
      • Emulator handling in OnConfiguring (CosmosDbContext.cs:22-73): two decisions, deliberately + separate (:29-31). The emulator account key alone (UsesEmulatorKey, :32) selects + ConnectionMode.Gateway (:44). Disabling certificate validation additionally requires a loopback + endpoint (ShouldBypassCertificateValidation, :33), because the key is published by Microsoft and + so never proves the endpoint is the local emulator; only then does an HttpClientFactory install + DangerousAcceptAnyServerCertificateValidator under a #pragma warning disable S4830 + (:46-61). The production path uses ConnectionMode.Direct with + MaxRequestsPerTcpConnection(20) and MaxTcpConnectionsPerEndpoint(32) (:66-68). The database + name comes from PhysicalSource.CosmosDatabaseName (:38).
      • +
      • UsesEmulatorKey (:82-83) and ShouldBypassCertificateValidation (:98-99): + internal static predicates. The first is an ordinal Contains("C2y6yDjf5"), the well-known prefix + of the emulator's default account key. The second requires that key AND a loopback + AccountEndpoint, judged by the private HasLoopbackAccountEndpoint (:101-116): the connection + string is parsed with DbConnectionStringBuilder and the endpoint with Uri.TryCreate, then tested + with Uri.IsLoopback, never by a string prefix, so a host such as localhost.attacker.example does + not qualify, and an unparseable string or endpoint keeps validation on (:93-97).
      • +
      • No outbox override: the class no longer overrides anything to opt out of the outbox. Cosmos + is non-relational on its engine object, and + DomainEventSaveChangesInterceptor tests + Engine.Capabilities.IsRelational (DomainEventSaveChangesInterceptor.cs:127, :236), so domain + events from a Cosmos context are dispatched in-process only.
      • +
      • OnModelCreating (CosmosDbContext.cs:119-146): applies the Cosmos configurations (:121), then + Ignore<OutboxMessage>() (:124) and, for the same relational-only reason, + Ignore<InternalCommandMessage>() (:127), then removes every index from every entity type because the provider does not support relational HasIndex/HasFilter, then calls ApplySoftDeleteFilters and ApplyTenantFilters directly. It deliberately does not call base.OnModelCreating because every table the base maps (outbox, inbox, internal commands, @@ -5413,119 +6072,23 @@

        CosmosDbContext

        the entity configuration bodies engine-agnostic; stripping indexes lets one configuration body serve both SQL Server and Cosmos. Calling the two filter helpers directly rather than through the base is what keeps soft delete and tenancy in force on an engine that cannot run the rest of the base - pipeline (the tenant helper skips only its index for Cosmos, ApplicationDbContext.cs:538-548).
      • + pipeline (the tenant helper skips its index on a non-relational engine, + ApplicationDbContext.cs:543-553).
      • Where it's used: instantiated per Cosmos source by - PhysicalDbContextFactory when a data source resolves to the CosmosDB - engine (PhysicalDbContextFactory.cs:49). It is also the single fact behind - DbContextFactory.SupportsTransactions, which is literally - context is not CosmosDbContext (DbContextFactory.cs:774-775).
      • -
      • Caveats / not-in-source: the certificate bypass is scoped by an ordinal substring match on the - emulator key prefix; whether any production connection string could contain that substring is Not - determinable from source. Per + CosmosDataSourceEngine.CreateDbContext + (CosmosDataSourceEngine.cs:79-83), which PhysicalDbContextFactory + reaches through DataSourceEngines when a data source resolves to the + CosmosDB engine (PhysicalDbContextFactory.cs:32). The emulator predicates are pinned by + CosmosDbContextTests + (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/CosmosDbContextTests.cs).
      • +
      • Caveats / not-in-source: Gateway mode is still selected by an ordinal substring match on the + emulator key prefix alone, though the certificate bypass now also requires a loopback endpoint. Per ADR-018 the plumbing ships and is tested, but no host in this workspace configures a Cosmos source today.
      -

      IDbContextFactory

      +

      PostgreSQLDbContext

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DbContexts.Factory · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/IDbContextFactory.cs:10 · Level 12 · interface

      -
      -
        -
      • What it is: the framework's own context-factory contract, the scoped object that hands out one - ApplicationDbContext per physical DataSourceKey and - then coordinates saving, transactions, schema lifecycle, and disposal across every context a scope - touched (IDbContextFactory.cs:5-10). It is deliberately not EF Core's - IDbContextFactory<TContext>: the two names collide, which is why consumers that need both add a - using IDbContextFactory = ...DbContexts.Factory.IDbContextFactory; alias - (InProcessEventBus.cs:8, BrokerEventBus.cs:8, EfInboxStore.cs:8) or fully qualify it - (OutboxProcessor.cs:190).
      • -
      • Depends on: ApplicationDbContext, DataSourceKey, - and the BCL IDisposable plus IAsyncDisposable it extends (IDbContextFactory.cs:10).
      • -
      • Concept introduced, addressing a context by physical source rather than by type. [Rubric §8, Data Architecture] (assesses whether transaction boundaries and unit-of-work scope are deliberate, - and whether per-service data isolation is real): EF's own factory answers "give me a context of type - T". This one answers "give me the context for this database", which is the only question that - makes sense once ADR-006 splits - storage along a Name axis and ADR-018 - adds an orthogonal Engine axis. The interface also owns the honest statement of what a - multi-source save can and cannot promise, in the ExecuteInTransactionAsync doc comment - (IDbContextFactory.cs:60-65): each physical source gets its own transaction, commits are - sequential and best-effort, there is no two-phase commit, a failure mid-commit leaves earlier - sources committed, and the outbox is the cross-source consistency mechanism.
      • -
      • Walkthrough:
          -
        • GetDbContext(DataSourceKey) (IDbContextFactory.cs:16): the only accessor, documented to - create the context if this scope does not already have one for that source.
        • -
        • EnsureCreatedAsync (:22), MigrateAsync (:81), HasPendingMigrationsAsync - (:87): schema lifecycle across every source the host uses. The contract states the asymmetry: - EnsureCreatedAsync skips sources with no configured connection string (:18-21), while the two - migration members cover only the sources a migrations pipeline owns, which is every SQL Server - source plus any SQLite source with a configured SqliteMigrationsAssembly; Cosmos and - assembly-less SQLite are created by EnsureCreatedAsync instead (:74-80).
        • -
        • SaveChangesAsync (:27) and SaveChanges (:32): save across all active contexts, the - async overload documented as carrying audit stamping and domain-event dispatch.
        • -
        • RequestIdentityInsert (:40): a one-shot flag for the next save, documented as - automatically cleared once the save completes (see IdentityInsertGroup).
        • -
        • BeginTransaction / CommitTransaction / RollbackTransaction (:45, :50, :55): - applied to every active context that supports transactions.
        • -
        • ExecuteInTransactionAsync<TResult> (:70-72): the member handlers actually reach, running - the operation under the active execution strategy so a retrying strategy retries the whole unit.
        • -
        -
      • -
      • Why it's built this way: the application layer already talks to - IUnitOfWork; this second interface exists so the physical-topology coordination - (which databases, which transactions, which migrations) has a home that Infrastructure can implement - and tests can mock, without leaking EF Core upward.
      • -
      • Where it's used: registered scoped as DbContextFactory - (DependencyInjection.cs:104). UnitOfWork delegates its whole save and transaction - surface to it (UnitOfWork.cs:70-79), the startup path resolves it to create, migrate, or verify - databases - (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:57, - :242, and per tenant at :144), and the background and messaging paths resolve it per scope to - reach a specific source (OutboxProcessor at - OutboxProcessor.cs:190, OutboxCleanupService.cs:105, OutboxAdministration.cs:235, - EfInboxStore.cs:39, InProcessEventBus.cs:34, BrokerEventBus.cs:32, ScheduledJobRunner.cs:214, - AuditTrailReader.cs:35, AuditTrailCleanupJob.cs:48, EFRefreshSessionStore.cs:31, and - RefreshSessionCleanupService.cs:112).
      • -
      -

      IPhysicalDbContextFactory

      -
      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DbContexts.Factory · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/IPhysicalDbContextFactory.cs:15 · Level 12 · interface

      -
      -
        -
      • What it is: the contract for constructing a raw context for a given physical data source. Its - doc comment states the split precisely: the engine selects the context class (SQL Server, Cosmos, - SQLite) and the source name selects the database (connection string, migrations assembly, EF model), - and contexts created here are neither scoped nor cached (IPhysicalDbContextFactory.cs:6-13).
      • -
      • Depends on: ApplicationDbContext as the return type, - DataSourceKey as the addressing type, and - PhysicalDataSource on the second overload - (IPhysicalDbContextFactory.cs:22, :37).
      • -
      • Concept introduced, construction split from lifetime. [Rubric §2, Design Patterns] (assesses - whether patterns are applied where they earn their keep): this is the Abstract Factory half of the - pair. Deciding which class to new up for which database is a pure function of the key and needs no - per-request state, so it lives in a singleton; deciding how long a context lives, when it saves, and - what transaction it is in is per-request state and lives in the scoped - IDbContextFactory layered on top (IPhysicalDbContextFactory.cs:10-13).
      • -
      • Walkthrough: two members. Create(DataSourceKey key) - (IPhysicalDbContextFactory.cs:22) returns a new instance targeting the database the resolver maps - that key to. Create(DataSourceKey key, PhysicalDataSource physicalDataSource) - (IPhysicalDbContextFactory.cs:37) is the database-per-tenant overload: the caller clones the - resolved PhysicalDataSource with the tenant's connection string and passes - the same key, so EF's model cache still serves one compiled model per (context type, source name) - across every tenant (IPhysicalDbContextFactory.cs:24-35).
      • -
      • Why it's built this way: keeping the construction decision behind a two-method interface is what - makes the scoped coordinator testable without a database ([Rubric §14, Testability]): the - commit-ambiguity tests hand DbContextFactory a - Mock<IPhysicalDbContextFactory> that returns a SQLite in-memory context - (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:49-55). - The two-overload shape is also what keeps tenancy - (ADR-073) a routing decision in - the scoped layer rather than a second factory.
      • -
      • Where it's used: registered singleton as PhysicalDbContextFactory - (DependencyInjection.cs:105); injected into DbContextFactory - (DbContextFactory.cs:47), which calls both overloads at :94-96.
      • -
      -

      PostgreSQLDbContext

      -
      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DbContexts · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/PostgreSQLDbContext.cs:23 · Level 12 · class (sealed)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DbContexts · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/PostgreSQLDbContext.cs:23 · Level 12 · class (sealed)

      • What it is: the sealed ApplicationDbContext subclass targeting @@ -5624,7 +6187,7 @@

        SqliteDbContext

        which holds one SQLite connection open for the fixture's lifetime). [Rubric §14, Testability].
      • Where it's used: instantiated per SQLite source by PhysicalDbContextFactory when a data source resolves to the Sqlite - engine (PhysicalDbContextFactory.cs:48). Its migration behavior is pinned by + engine (PhysicalDbContextFactory.cs:32 dispatches to SqliteDataSourceEngine.cs:88). Its migration behavior is pinned by DbContextFactoryMigrationTargetTests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryMigrationTargetTests.cs:94, :106, :115, :126), which asserts that a SQLite source with a migrations assembly is migrated, @@ -5691,7 +6254,7 @@

        SQLServerDbContext

        (ADR-006, ADR-009).
      • Where it's used: instantiated per SQL Server source by - PhysicalDbContextFactory (PhysicalDbContextFactory.cs:46); built at + PhysicalDbContextFactory (PhysicalDbContextFactory.cs:32 dispatches to SQLServerDataSourceEngine.cs:91); built at design time by DesignTimeDbContextHelper.CreateSqlServer (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextHelper.cs:52), which is what makes one migrations project per database possible. It is also the type @@ -5701,21 +6264,176 @@

        SQLServerDbContext

      • Caveats / not-in-source: whether any repository's CI actually runs the has-pending-model-changes gate the comment recommends is Not determinable from this file.
      +

      IDbContextFactory

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DbContexts.Factory · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/IDbContextFactory.cs:10 · Level 12 · interface

      +
      +
        +
      • What it is: the framework's own context-factory contract, the scoped object that hands out one + ApplicationDbContext per physical DataSourceKey and + then coordinates saving, transactions, schema lifecycle, and disposal across every context a scope + touched (IDbContextFactory.cs:5-10). It is deliberately not EF Core's + IDbContextFactory<TContext>: the two names collide, which is why consumers that need both add a + using IDbContextFactory = ...DbContexts.Factory.IDbContextFactory; alias + (InProcessEventBus.cs:8, BrokerEventBus.cs:8, EfInboxStore.cs:8) or fully qualify it + (OutboxProcessor.cs:183).
      • +
      • Depends on: ApplicationDbContext, DataSourceKey, + and the BCL IDisposable plus IAsyncDisposable it extends (IDbContextFactory.cs:10).
      • +
      • Concept introduced, addressing a context by physical source rather than by type. [Rubric §8, Data Architecture] (assesses whether transaction boundaries and unit-of-work scope are deliberate, + and whether per-service data isolation is real): EF's own factory answers "give me a context of type + T". This one answers "give me the context for this database", which is the only question that + makes sense once ADR-006 splits + storage along a Name axis and ADR-018 + adds an orthogonal Engine axis. The interface also owns the honest statement of what a + multi-source save can and cannot promise, in the ExecuteInTransactionAsync doc comment + (IDbContextFactory.cs:60-65): each physical source gets its own transaction, commits are + sequential and best-effort, there is no two-phase commit, a failure mid-commit leaves earlier + sources committed, and the outbox is the cross-source consistency mechanism.
      • +
      • Walkthrough:
          +
        • GetDbContext(DataSourceKey) (IDbContextFactory.cs:16): the only accessor, documented to + create the context if this scope does not already have one for that source.
        • +
        • EnsureCreatedAsync (:22), MigrateAsync (:81), HasPendingMigrationsAsync + (:87): schema lifecycle across every source the host uses. The contract states the asymmetry: + EnsureCreatedAsync skips sources with no configured connection string (:18-21), while the two + migration members cover only the sources a migrations pipeline owns, which is every SQL Server + source plus any SQLite source with a configured SqliteMigrationsAssembly; Cosmos and + assembly-less SQLite are created by EnsureCreatedAsync instead (:74-80).
        • +
        • SaveChangesAsync (:27) and SaveChanges (:32): save across all active contexts, the + async overload documented as carrying audit stamping and domain-event dispatch.
        • +
        • RequestExplicitKeyInsert (:40): a one-shot flag for the next save, signalling that it may + include entities carrying explicit values for store-generated keys. The contract is now + engine-neutral (:34-39): each context's engine decides whether those inserts need a per-table + toggle (its explicit-key insert dialect, IExplicitKeyInsertDialect), + an engine with none saves unchanged, and the flag is cleared once the save completes (see + ExplicitKeyInsertGroup).
        • +
        • BeginTransaction / CommitTransaction / RollbackTransaction (:45, :50, :55): + applied to every active context that supports transactions.
        • +
        • ExecuteInTransactionAsync<TResult> (:70-72): the member handlers actually reach, running + the operation under the active execution strategy so a retrying strategy retries the whole unit.
        • +
        +
      • +
      • Why it's built this way: the application layer already talks to + IUnitOfWork; this second interface exists so the physical-topology coordination + (which databases, which transactions, which migrations) has a home that Infrastructure can implement + and tests can mock, without leaking EF Core upward.
      • +
      • Where it's used: registered scoped as DbContextFactory + (DependencyInjection.cs:104). UnitOfWork delegates its whole save and transaction + surface to it (UnitOfWork.cs:70-79), the startup path resolves it to create, migrate, or verify + databases + (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:68, + :242, and per tenant at :144), and the background and messaging paths resolve it per scope to + reach a specific source (OutboxProcessor at + OutboxProcessor.cs:183, OutboxCleanupService.cs:100, OutboxAdministration.cs:219, + EfInboxStore.cs:39, InProcessEventBus.cs:34, BrokerEventBus.cs:32, ScheduledJobRunner.cs:222, + AuditTrailReader.cs:35, AuditTrailCleanupJob.cs:46, EFRefreshSessionStore.cs:31, and + RefreshSessionCleanupService.cs:112).
      • +
      +

      IPhysicalDbContextFactory

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DbContexts.Factory · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/IPhysicalDbContextFactory.cs:15 · Level 12 · interface

      +
      +
        +
      • What it is: the contract for constructing a raw context for a given physical data source. Its + doc comment states the split precisely: the engine selects the context class (SQL Server, Cosmos, + SQLite) and the source name selects the database (connection string, migrations assembly, EF model), + and contexts created here are neither scoped nor cached (IPhysicalDbContextFactory.cs:6-13).
      • +
      • Depends on: ApplicationDbContext as the return type, + DataSourceKey as the addressing type, and + PhysicalDataSource on the second overload + (IPhysicalDbContextFactory.cs:22, :37).
      • +
      • Concept introduced, construction split from lifetime. [Rubric §2, Design Patterns] (assesses + whether patterns are applied where they earn their keep): this is the Abstract Factory half of the + pair. Deciding which class to new up for which database is a pure function of the key and needs no + per-request state, so it lives in a singleton; deciding how long a context lives, when it saves, and + what transaction it is in is per-request state and lives in the scoped + IDbContextFactory layered on top (IPhysicalDbContextFactory.cs:10-13).
      • +
      • Walkthrough: two members. Create(DataSourceKey key) + (IPhysicalDbContextFactory.cs:22) returns a new instance targeting the database the resolver maps + that key to. Create(DataSourceKey key, PhysicalDataSource physicalDataSource) + (IPhysicalDbContextFactory.cs:37) is the database-per-tenant overload: the caller clones the + resolved PhysicalDataSource with the tenant's connection string and passes + the same key, so EF's model cache still serves one compiled model per (context type, source name) + across every tenant (IPhysicalDbContextFactory.cs:24-35).
      • +
      • Why it's built this way: keeping the construction decision behind a two-method interface is what + makes the scoped coordinator testable without a database ([Rubric §14, Testability]): the + commit-ambiguity tests hand DbContextFactory a + Mock<IPhysicalDbContextFactory> that returns a SQLite in-memory context + (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:49-55). + The two-overload shape is also what keeps tenancy + (ADR-073) a routing decision in + the scoped layer rather than a second factory.
      • +
      • Where it's used: registered singleton as PhysicalDbContextFactory + (DependencyInjection.cs:105); injected into DbContextFactory + (DbContextFactory.cs:49), which calls both overloads at :94-96.
      • +
      +

      PhysicalDbContextFactory

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DbContexts.Factory · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/PhysicalDbContextFactory.cs:19 · Level 13 · class (sealed)

      +
      +
        +
      • What it is: the singleton implementation of + IPhysicalDbContextFactory. It resolves the key's connection + information through IDataSourceResolver (or accepts it from the caller) and + asks the key's engine object to construct the matching context (PhysicalDbContextFactory.cs:25-33).
      • +
      • Depends on: IServiceProvider, IDataSourceResolver, and + IEntityConfigurationAssemblyProvider, all taken through a + primary constructor (PhysicalDbContextFactory.cs:19-22), plus + DataSourceEngines and the IDataSourceEngine it + returns, whose CreateDbContext (IDataSourceEngine.cs:83) builds the + SQLServerDbContext, PostgreSQLDbContext, + SqliteDbContext, or CosmosDbContext.
      • +
      • Concept introduced, the never-pool rule. [Rubric §8, Data Architecture] and + [Rubric §12, Performance & Scalability] (which assesses whether performance work is deliberate + rather than reflexive): EF Core's AddPooledDbContextFactory is the standard way to avoid + re-allocating contexts, and it is explicitly forbidden here. The class comment says why + (PhysicalDbContextFactory.cs:13-17): each instance carries per-source constructor state (its + PhysicalDataSource), so a pool would hand a context configured for one + database to a caller asking for another and silently point repositories at the wrong database. The + same warning is repeated at the registration site (MMCA.Common.Infrastructure/DependencyInjection.cs:103-109), which is where + someone optimizing DI would look first. Under database-per-tenant the rule is load-bearing twice + over, since a pooled context could then cross a tenant boundary rather than only a module one.
      • +
      • Walkthrough:
          +
        • No options of its own: the static empty DbContextOptions<T> objects now live one per engine + class (for example SQLServerDataSourceEngine.cs:23, CosmosDataSourceEngine.cs:22), each built + once and reused; provider, connection, interceptors, and model cache key are all set inside each + context's OnConfiguring, so those options exist only to satisfy the DbContext constructor.
        • +
        • Create(DataSourceKey key) (PhysicalDbContextFactory.cs:25): a one-liner that resolves the + PhysicalDataSource with resolver.GetPhysical(key) and forwards to the + two-argument overload, so the resolver path and the tenant path share one construction switch.
        • +
        • Create(DataSourceKey key, PhysicalDataSource physicalDataSource) + (PhysicalDbContextFactory.cs:28-33): null-guards the supplied source (:30), then returns + DataSourceEngines.For(key.Engine).CreateDbContext(serviceProvider, assemblyProvider, physical) + (:32). The engine-to-class switch that used to live here is gone: each engine class constructs its + own context with its own options.
        • +
        +
      • +
      • Why it's built this way: this is the single point where the two storage axes meet, the Engine + axis of ADR-018 picking the + class and the Name axis of ADR-006 + picking the database, so adding an engine is a new engine class plus a context class rather than a + change anywhere in application code + (ADR-130). Taking the connection information as a parameter is what let + ADR-073 add a third, per-tenant + axis without touching the switch.
      • +
      • Where it's used: registered singleton (DependencyInjection.cs:105); + DbContextFactory.GetDbContext calls one overload or the other on every cache + miss (DbContextFactory.cs:105-107), which is the only first-party call site.
      • +

      DbContextFactory

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DbContexts.Factory · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:46 · Level 13 · class (sealed)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DbContexts.Factory · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:48 · Level 13 · class (sealed)

      • What it is: the scoped implementation of IDbContextFactory and the busiest type in this group. It caches one ApplicationDbContext per physical DataSourceKey for the life of the scope, coordinates save, transaction, migration, and disposal across all of them, and is also the database-per-tenant routing point - (DbContextFactory.cs:19-28).
      • + (DbContextFactory.cs:21-30).
      • Depends on: IPhysicalDbContextFactory, IEntityDataSourceRegistry, IDataSourceResolver, and ICurrentUserService, all null-guarded from the primary - constructor into readonly fields (DbContextFactory.cs:46-66), plus three parameters read directly + constructor into readonly fields (DbContextFactory.cs:48-68), plus three parameters read directly off the primary constructor: ITenantContext, IOptions<TenancySettings> @@ -5725,7 +6443,7 @@

        DbContextFactory

        internal static members of DomainEventSaveChangesInterceptor (BeginCaptureExclusion, EndCaptureExclusion, DropDeferred, FlushDeferredAsync, at - DbContextFactory.cs:333, :342, :447, and :581).
      • + DbContextFactory.cs:404, :342, :447, and :581).
      • Concept introduced, coordinating one logical save across several physical databases. [Rubric §8, Data Architecture] (transaction boundaries, unit-of-work scope, per-service isolation) and [Rubric §12, Performance & Scalability] (transactions handled once in a shared mechanism, never @@ -5741,22 +6459,22 @@

        DbContextFactory

        own connection string for a source, and this class is where that declaration turns into a different database. The trick is that only the connection string changes: the DataSourceKey stays the same, which is what EF's model cache is keyed on, so one - compiled model serves every tenant (DbContextFactory.cs:156-161).
      • + compiled model serves every tenant (DbContextFactory.cs:162-167).
      • Walkthrough:
          -
        • State (DbContextFactory.cs:56-93): MaxSavePasses (3, :52); _dbContexts, the per-scope +
        • State (DbContextFactory.cs:58-95): MaxSavePasses (3, :52); _dbContexts, the per-scope Dictionary<DataSourceKey, ApplicationDbContext> that guarantees every repository in a scope shares one change tracker per database (:62); _routedContextTenants, recording which tenant each per-tenant-routed context was created for and holding only overridden sources (:64-69); - _transactionActive (:75); the one-shot _identityInsertRequested flag (:82); and a - volatile bool _disposed (:84).
        • -
        • GetDbContext(DataSourceKey) (DbContextFactory.cs:96-126): throws if disposed (:89), then + _transactionActive (:86); the one-shot _explicitKeyInsertRequested flag (:93); and a + volatile bool _disposed (:95).
        • +
        • GetDbContext(DataSourceKey) (DbContextFactory.cs:98-132): throws if disposed (:89), then on a cache miss asks ResolveTenantOverride for the tenant's own connection information and creates the context through whichever Create overload applies (:93-96), records the creating tenant when the source was routed (:100-101), attaches the scope's accessors (:103), and enlists the new context in an already-active transaction (:107-108), so a source first touched inside a transactional command still shares the boundary. On a cache hit it calls - GuardRoutedTenantUnchanged (:113).
        • -
        • AttachScopeAccessors (DbContextFactory.cs:140-154, renamed from AttachTenantAccessor): + GuardRoutedTenantUnchanged (:124) and then GuardSharedContextNotRoutable (:128).
        • +
        • AttachScopeAccessors (DbContextFactory.cs:146-160, renamed from AttachTenantAccessor): hands the context two delegates rather than copied values, because a context can be created before the request's tenant or principal is resolved and both the query filter and the outbox capture must read the answer that holds at query and save time rather than at construction time. It sets @@ -5765,31 +6483,37 @@

          DbContextFactory

          not once per row, so flattening the role claims costs one pass per save. It null-guards inside rather than at the call site so the null tolerance a mocked physical factory needs does not leak a maybe-null flow state back into the caller.
        • -
        • ResolveTenantOverride (DbContextFactory.cs:162-187): returns null (source stays shared) +
        • ResolveTenantOverride (DbContextFactory.cs:168-193): returns null (source stays shared) unless there is a tenant, bound settings, an entry for that tenant, and an entry for this source name (:145-151); otherwise it takes the engine-appropriate connection string through TenancySettingsValidator.ConnectionStringFor (:153), still returning null when the tenant overrides only a different engine (:154-158), and finally clones the shared PhysicalDataSource with the tenant's connection string and Cosmos database name (:160-167).
        • -
        • GuardRoutedTenantUnchanged (DbContextFactory.cs:195-212): if the cached context was routed +
        • GuardRoutedTenantUnchanged (DbContextFactory.cs:201-218): if the cached context was routed for a tenant and the scope's tenant has since changed, it throws with both tenant ids named (:185-192). The comment states the stakes (:170-175): serving that context to a second tenant would read and write the first tenant's data under the second tenant's filter value.
        • -
        • GetSourcesInUse (DbContextFactory.cs:232-233): the union of every source backing a +
        • GuardSharedContextNotRoutable (DbContextFactory.cs:226): the mirror-image guard. A context + created shared (because a repository call ran before the tenant resolved) is refused once the + scope's tenant turns out to override that source: unless the context was itself routed or + ResolveTenantOverride now returns null, it throws an InvalidOperationException telling the + caller to resolve the tenant before the first repository call or use a fresh scope. Serving it + would read and write the shared database for a tenant that owns its own.
        • +
        • GetSourcesInUse (DbContextFactory.cs:257-258): the union of every source backing a registered entity (from IEntityDataSourceRegistry) and every source already materialized in this scope, which is what EnsureCreatedAsync (:196-207) and GetMigrationTargets (:705-723) iterate. EnsureCreatedAsync skips sources with an empty connection string (:200-203).
        • -
        • GetMigrationTargets (DbContextFactory.cs:724-742): the shared filter behind MigrateAsync +
        • GetMigrationTargets (DbContextFactory.cs:782-795): the shared filter behind MigrateAsync (:686-690) and HasPendingMigrationsAsync (:726-735). It keeps a source only when its resolved PhysicalDataSource.UsesMigrations is true (:713-714), then skips a non-SQL-Server target whose connection string is empty (:716-717). The asymmetry is deliberate and documented (:697-702): an optional SQLite source a test host leaves unconfigured stays silently absent, while a SQL Server source with no connection string still fails loudly at startup, because for SQL Server that is a misconfiguration rather than an option.
        • -
        • SaveChangesAsync (DbContextFactory.cs:244-292): reads and immediately clears the - identity-insert flag (:227-228), then loops at most MaxSavePasses times over the contexts it +
        • SaveChangesAsync (DbContextFactory.cs:270-318): reads and immediately clears the + explicit-key-insert flag (:272), then loops at most MaxSavePasses times over the contexts it has not yet saved (:237-251), passing _currentUserService.UserId into each context's audit-aware SaveChangesAsync overload (:247-249). The re-loop exists because saving dispatches domain events in-process, and a handler that resolves a repository for a source nobody had touched @@ -5799,18 +6523,25 @@

          DbContextFactory

          silently lost. The comment at :253-258 explains why the assertion reads the change tracker rather than the saved set: it must catch both a context materialized past the pass bound and a handler that dirtied an already-saved context.
        • -
        • SaveChanges (DbContextFactory.cs:427-435): the synchronous path, a single pass over a +
        • SaveChanges (DbContextFactory.cs:443-451): the synchronous path, a single pass over a snapshot of the cached contexts with no re-loop.
        • -
        • Identity-insert path (DbContextFactory.cs:295, :284-403): covered in - IdentityInsertGroup above.
        • -
        • BeginTransaction / CommitTransaction / RollbackTransaction (DbContextFactory.cs:437-467): +
        • Explicit-key insert path (DbContextFactory.cs:292-294, :329-440): a context takes it only + when the flag is set AND context.Engine.ExplicitKeyInsert returns an + IExplicitKeyInsertDialect (SQL Server's SET IDENTITY_INSERT); + every other engine saves normally. SaveWithExplicitKeyInsertAsync (:329) asks the dialect for + the ExplicitKeyInsertGroups and opens the connection itself when it is + not already open, because the toggle is session state and without an ambient transaction EF would + otherwise return the connection to the pool between the toggle and the INSERT; an + already-open connection is left as found. SaveExplicitKeyGroupsAsync (:377) then saves one + table per round with the toggle SQL built by dialect.BuildToggleSql.
        • +
        • BeginTransaction / CommitTransaction / RollbackTransaction (DbContextFactory.cs:453-483): each filters to contexts that support transactions and, symmetrically, to those that do or do not already carry one (:426, :433, :440), because EF throws on a second BeginTransaction for the same connection and GetDbContext may already have enlisted a late-created context (:422-425). Rollback additionally calls DomainEventSaveChangesInterceptor.DropDeferred on every context (:446-447): the aggregate changes and their outbox rows just rolled back, so the deferred in-process dispatch must never run, and must not survive into a retry.
        • -
        • ExecuteInTransactionAsync<TResult> (DbContextFactory.cs:518-563): re-entrancy first, a +
        • ExecuteInTransactionAsync<TResult> (DbContextFactory.cs:534-579): re-entrancy first, a nested call simply runs the operation on the ambient transaction (:510-511), because an inner commit would make the outer scope's earlier work durable ahead of its own decision (:503-509). Otherwise it picks the execution strategy from the first transaction-capable context, materializing @@ -5818,7 +6549,7 @@

          DbContextFactory

          rather than taken literally so a host with no SQL Server connection does not open a connection string that does not exist, :513-518), and runs the attempt under strategy.ExecuteAsync (:526-534), calling ResetForRetry before every attempt after the first (:528-529).
        • -
        • RunTransactionalAttemptAsync (DbContextFactory.cs:573-628): one attempt, begin to commit. +
        • RunTransactionalAttemptAsync (DbContextFactory.cs:589-647): one attempt, begin to commit. A failed Result rolls back and returns (:563-570), which is what makes ADR-013's Result-over-exceptions @@ -5827,7 +6558,7 @@

          DbContextFactory

          still materialize a new source (:576-583). A cancellation attempts a best-effort rollback and, if even that throws, clears the flag and drops every deferred dispatch by hand (:587-603); any other exception rolls back and rethrows (:604-608).
        • -
        • TryCommit (DbContextFactory.cs:640-672) and AbandonAfterCommitFailure (:662-683): a +
        • TryCommit (DbContextFactory.cs:697-729) and AbandonAfterCommitFailure (:662-683): a commit failure is returned, not thrown (:646). TryCommit snapshots the enlisted contexts first, because that order is the commit order (:625-630), then commits them one by one, accumulating the successes; on a throw it names the failing source, treats everything past it in the @@ -5836,13 +6567,13 @@

          DbContextFactory

          AbandonAfterCommitFailure rolls back whatever has not committed yet, drops every deferred dispatch, and swallows secondary rollback failures so the commit ambiguity stays the reported failure (:666-682).
        • -
        • ResetForRetry (DbContextFactory.cs:761-768): before the strategy re-runs the operation it +
        • ResetForRetry (DbContextFactory.cs:814-821): before the strategy re-runs the operation it drops deferred dispatch and calls ChangeTracker.Clear() on every context, so entities the aborted attempt added are not inserted a second time (with a duplicate outbox row per event).
        • -
        • SupportsTransactions (DbContextFactory.cs:774-775): context is not CosmosDbContext, - the single place the Cosmos "no multi-document transactions" fact is encoded; - HasActiveTransaction (:758-759) is Database.CurrentTransaction is not null.
        • -
        • Disposal (DbContextFactory.cs:780-810): Dispose and DisposeAsync both dispose every +
        • SupportsTransactions (DbContextFactory.cs:827-828): context.Engine.Capabilities.IsRelational, + so the Cosmos "no multi-document transactions" fact is read off the engine object rather than + a type test; HasActiveTransaction (:830-831) is Database.CurrentTransaction is not null.
        • +
        • Disposal (DbContextFactory.cs:833-863): Dispose and DisposeAsync both dispose every cached context, clear the dictionary, set _disposed, and suppress finalization.
      • @@ -5859,67 +6590,15 @@

        DbContextFactory

        section). Directly instantiated in tests, for example MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:55 and :195; the save-loop invariants are pinned by DbContextFactorySaveIntegrityTests - (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:81, + (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:82, :101, :120), which assert that a handler mutating an already-saved context throws naming that context, that an extra read-only context does not, and that a clean scope returns the written count.
      • Caveats / not-in-source: the MaxSavePasses bound of 3 is documented as "two passes cover the - realistic case, the third is slack" (DbContextFactory.cs:56-60); whether any production workload + realistic case, the third is slack" (DbContextFactory.cs:58-62); whether any production workload has ever needed the third pass is Not determinable from source. The routed-tenant guard also implies a usage rule the code can only enforce after the fact: a scope serves one tenant, and switching tenants means a fresh scope (:185-192).
      -

      PhysicalDbContextFactory

      -
      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DbContexts.Factory · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/PhysicalDbContextFactory.cs:16 · Level 13 · class (sealed)

      -
      -
        -
      • What it is: the singleton implementation of - IPhysicalDbContextFactory. It resolves the key's connection - information through IDataSourceResolver (or accepts it from the caller) and - constructs the matching engine context directly with new (PhysicalDbContextFactory.cs:37-52).
      • -
      • Depends on: IServiceProvider, IDataSourceResolver, and - IEntityConfigurationAssemblyProvider, all taken through a - primary constructor (PhysicalDbContextFactory.cs:16-19), plus the four context classes - SQLServerDbContext, PostgreSQLDbContext, - SqliteDbContext, and CosmosDbContext.
      • -
      • Concept introduced, the never-pool rule. [Rubric §8, Data Architecture] and - [Rubric §12, Performance & Scalability] (which assesses whether performance work is deliberate - rather than reflexive): EF Core's AddPooledDbContextFactory is the standard way to avoid - re-allocating contexts, and it is explicitly forbidden here. The class comment says why - (PhysicalDbContextFactory.cs:10-14): each instance carries per-source constructor state (its - PhysicalDataSource), so a pool would hand a context configured for one - database to a caller asking for another and silently point repositories at the wrong database. The - same warning is repeated at the registration site (MMCA.Common.Infrastructure/DependencyInjection.cs:97-103), which is where - someone optimizing DI would look first. Under database-per-tenant the rule is load-bearing twice - over, since a pooled context could then cross a tenant boundary rather than only a module one.
      • -
      • Walkthrough:
          -
        • Four static empty options objects (PhysicalDbContextFactory.cs:24-38, one added for - PostgreSQL): one DbContextOptions<T> per engine, built once and reused. The comment above them - explains the emptiness: provider, connection, interceptors, and model cache key are all set inside - each context's OnConfiguring, so these options exist only to satisfy the DbContext constructor - and match what the previous AddDbContextFactory<T>() registrations produced.
        • -
        • Create(DataSourceKey key) (PhysicalDbContextFactory.cs:41): a one-liner that resolves the - PhysicalDataSource with resolver.GetPhysical(key) and forwards to the - two-argument overload, so the resolver path and the tenant path share one construction switch.
        • -
        • Create(DataSourceKey key, PhysicalDataSource physicalDataSource) - (PhysicalDbContextFactory.cs:44-56): null-guards the supplied source, then switches on - key.Engine to construct SQLServerDbContext, PostgreSQLDbContext, SqliteDbContext, or - CosmosDbContext, passing options, the service provider, the assembly provider, and the physical - source into each four-argument constructor. An unmapped engine throws an - InvalidOperationException naming the offending value.
        • -
        -
      • -
      • Why it's built this way: this is the single point where the two storage axes meet, the Engine - axis of ADR-018 picking the - class and the Name axis of ADR-006 - picking the database, so adding an engine is a new case plus a context class rather than a change - anywhere in application code. Taking the connection information as a parameter is what let - ADR-073 add a third, per-tenant - axis without touching the switch.
      • -
      • Where it's used: registered singleton (DependencyInjection.cs:105); - DbContextFactory.GetDbContext calls one overload or the other on every cache - miss (DbContextFactory.cs:103-105), which is the only first-party call site.
      • -

      CrossTenantWriteException

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Interceptors · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/CrossTenantWriteException.cs:24 · Level 0 · class (sealed, exception)

      @@ -6134,7 +6813,7 @@

      IDbSeeder

      IdentityModuleSeeder.cs:35-36), which ModuleLoader runs through SeedAllAsync at startup, after schema initialization - (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:110). + (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:124). There is no reflection-based discovery of IDbSeeder and no hosted service that drains a list of them.
    429. @@ -6153,13 +6832,14 @@

      InternalCommandOrigin

      exactly what a disconnected, later execution needs to look like the scope that scheduled it.
    430. Walkthrough: a plain positional internal readonly record struct with no members beyond its six constructor parameters. Value semantics are what let it be captured once by - InternalCommandScheduler.CaptureOrigin and copied cheaply onto the row.
    431. + InternalCommandOriginCapture.Capture and copied cheaply onto the row.
    432. Why it's built this way: a record struct rather than a class because it is a short-lived value with no identity of its own, constructed once per scheduled command and read once when the row is built; internal because only this project's scheduling and message types touch it.
    433. -
    434. Where it's used: built by InternalCommandScheduler.CaptureOrigin - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandScheduler.cs:138-149) - and consumed by InternalCommandMessage.FromCommand, which copies its +
    435. Where it's used: built by InternalCommandOriginCapture.Capture + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandOriginCapture.cs:28-39), + which InternalCommandScheduler calls while building the row + (InternalCommandScheduler.cs:86), and consumed by InternalCommandMessage.FromCommand, which copies its five restorable fields onto the row (InternalCommandMessage.cs:149-154).
    436. SeedAccount

      @@ -6232,7 +6912,7 @@

      DbSeeder

      is not a general-purpose id converter) while still allowing every derived seeder to use it without an instance. Determinism, not uniqueness, is the property that matters: seeders must be idempotent across restarts, which is what production hosts rely on when they run the seeder on every boot - (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:110). + (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:124).
    437. Where it's used: the base of every module seeder in both apps, for example ADC's ConferenceModuleDbSeeder (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/DbContexts/Seeding/ConferenceModuleDbSeeder.cs:26), @@ -6354,7 +7034,7 @@

      InternalCommandMessage

      understands the other.
    438. Where it's used: added to the caller's IDbContextFactory context by InternalCommandScheduler.ScheduleAsync - (InternalCommandScheduler.cs:104-108), so it commits atomically with the caller's aggregate change + (InternalCommandScheduler.cs:97-101), so it commits atomically with the caller's aggregate change inside a transaction, or saves immediately outside one; claimed, executed and stamped by InternalCommandProcessor (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/InternalCommandProcessor.cs, @@ -6369,6 +7049,45 @@

      InternalCommandMessage

      the outbox's own column of the same name through AmbientOrigin.MaxRolesLength); this type itself does not truncate, so an over-length role list is truncated by the caller before FromCommand sees it.
    439. +

      InternalCommandOriginCapture

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.InternalCommands · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandOriginCapture.cs:21 · Level 9 · class (internal sealed)

      +
      +
        +
      • What it is: the snapshot step of the internal-command queue: one method, Capture(), that reads + the ambient request context a scheduled command must carry onto its row (the scheduling principal and + roles, the tenant, the correlation id, the trace and span ids) and returns it as an + InternalCommandOrigin (InternalCommandOriginCapture.cs:8-12, :21-44). + The InternalCommandProcessor restores those values around the deferred execution.
      • +
      • Depends on: ICurrentUserService, ITenantContext and ICorrelationContext through the primary + constructor (:21-24); InternalCommandOrigin as the value it builds; + AmbientOrigin.FlattenRoles for the roles; BCL System.Diagnostics.Activity for the trace and span + ids.
      • +
      • Concept introduced, capture is scoped because what it reads is scoped. [Rubric §13, Observability & Operability] assesses whether the identity and correlation of a request survive an asynchronous + hop. The snapshot has to describe the request that is scheduling the command, and all three services + it reads are request-scoped, so the capture is registered scoped too + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Jobs.cs:160, rationale in the + remarks at :13-17). Pulling the capture out of the scheduler into its own type gives the snapshot + one owner with one dependency set, so InternalCommandScheduler takes a + single InternalCommandOriginCapture argument instead of the three ambient services.
      • +
      • Walkthrough:
          +
        • Capture() (:32-43): reads Activity.Current once (:34), then builds the + InternalCommandOrigin from currentUserService.UserId, the roles flattened through + AmbientOrigin.FlattenRoles(currentUserService.Roles), tenantContext.TenantId, + correlationContext.CorrelationId, and the activity's TraceId/SpanId as strings, or null + when no activity is running (:36-42).
        • +
        +
      • +
      • Why it's built this way: roles go through the shared AmbientOrigin helper, which the outbox + capture uses as well, so the outbox hop and the internal-command hop store roles in the same shape + (:15-16). The class holds no state of its own; every value comes from the scoped services at the + moment Capture() runs.
      • +
      • Where it's used: registered with TryAddScoped (DependencyInjection.Jobs.cs:160); called once + per scheduled command by InternalCommandScheduler + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandScheduler.cs:86); + constructed directly by InternalCommandTestHarness + (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandTestHarness.cs).
      • +

      CapturedState

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Interceptors · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/DomainEventSaveChangesInterceptor.cs:389 · Level 10 · record (private sealed, nested)

      @@ -6383,7 +7102,7 @@

      CapturedState

      IDomainEvent, OutboxMessage.
    440. Concept introduced, why per-save state cannot live in a field. The interceptor is registered - as a singleton (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:64), + as a singleton (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:65), and one singleton serves every context in every scope concurrently. Anything it remembers between SavingChanges and SavedChanges therefore has to be keyed by the context, not stored on the instance. That is exactly what this record is: the value side of a @@ -6418,17 +7137,22 @@

      CapturedState

      AuditSaveChangesInterceptor

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Interceptors · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:22 · Level 11 · class (sealed)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Interceptors · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Interceptors/AuditSaveChangesInterceptor.cs:30 · Level 11 · class (sealed)

      • What it is: the EF Core interceptor that stamps CreatedOn/CreatedBy, LastModifiedOn/LastModifiedBy and DeletedOn/DeletedBy on every IAuditableEntity entry immediately before - the write (AuditSaveChangesInterceptor.cs:9-20). It is the first of the interceptors the - framework installs and the reason no handler anywhere in ADC or Store sets an audit field by hand.
      • -
      • Depends on: ApplicationDbContext (for CurrentSaveUserId and the - change tracker), IAuditableEntity, and the - BCL TimeProvider, injected through the primary constructor (:22).
      • + the write (AuditSaveChangesInterceptor.cs:10-28). On PostgreSQL and SQLite it also writes the + RowVersion optimistic-concurrency token, because neither engine generates one server-side + (:21-27). It is the first of the interceptors the framework installs and the reason no handler + anywhere in ADC or Store sets an audit field by hand. +
      • Depends on: ApplicationDbContext (for CurrentSaveUserId, the + engine capabilities and the change tracker), + IAuditableEntity, IRowVersioned (only for + the RowVersion property name), RowVersionStrategy read off + DataSourceEngineCapabilities, and the BCL TimeProvider, injected + through the primary constructor (:30).
      • Concept introduced, the EF SaveChangesInterceptor as the cross-cutting hook of the persistence layer. [Rubric §13, Observability & Operability] assesses whether audit, correlation and logging are wired once centrally rather than repeated per handler. An EF SaveChangesInterceptor is a hook EF @@ -6440,62 +7164,74 @@

        AuditSaveChangesInterceptor

        is engaged too, because "every row knows who wrote it and when" is a schema-level guarantee here, not a convention.
      • Concept introduced, stamping a transition rather than a value. The soft-delete stamps are not - driven by what IsDeleted is but by whether it changed during this save (:14-18). That + driven by what IsDeleted is but by whether it changed during this save (:14-20). That distinction is what makes the delete stamp behave like the creation stamp: it is written once, by the save that flipped the flag, and every later update of the same already-deleted row leaves it untouched. Reading the flag's value instead would rewrite DeletedOn on every subsequent write to a deleted row, which destroys the one fact the column exists to record.
      • Walkthrough:
          -
        • SavingChangesAsync (:25-34) and SavingChanges (:37-45): identical two-line +
        • SavingChangesAsync (:33-42) and SavingChanges (:45-53): identical two-line bodies. Each pattern-matches eventData.Context against ApplicationDbContext, calls StampAuditFields, then delegates to base. The type test is the guard: a context that is not the framework's own is left completely alone.
        • -
        • StampAuditFields (:47-81): reads the clock once per save via - timeProvider.GetUtcNow().UtcDateTime (:49) so every row in one save carries the same instant, - and resolves the user once as context.CurrentSaveUserId ?? default (:50). CurrentSaveUserId +
        • StampAuditFields (:55-92): reads the clock once per save via + timeProvider.GetUtcNow().UtcDateTime (:57) so every row in one save carries the same instant, + and resolves the user once as context.CurrentSaveUserId ?? default (:58). CurrentSaveUserId is the nullable user id the context was handed for this save - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:179, - set at :156 and :191), so default is the sentinel for a system-originated write with no - user behind it.
        • -
        • The Added branch (:56-65): sets all four creation and modification properties through + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:184, + set at :197 and :232), so default is the sentinel for a system-originated write with no + user behind it. It also decides once per save whether this engine needs a client-written row + version: context.Engine.Capabilities.RowVersion == RowVersionStrategy.ClientStamped (:59), + which is true for the PostgreSQL and SQLite engines + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/PostgreSQLDataSourceEngine.cs:44, + SqliteDataSourceEngine.cs:43; the enum member is at RowVersionStrategy.cs:17).
        • +
        • The Added branch (:65-75): sets all four creation and modification properties through entry.Property(nameof(...)).CurrentValue, going through the change tracker rather than the CLR setters so the fields can stay init-only or privately settable on the entity, then calls - StampSoftDeleteTransition with wasDeleted: false. A brand new row has no prior state, so an - entity inserted already soft-deleted still gets its delete stamp (:62-64).
        • -
        • The Modified branch (:66-73): the interesting one. It sets LastModifiedBy/ + StampSoftDeleteTransition with wasDeleted: false and StampRowVersion. A brand new row has + no prior state, so an entity inserted already soft-deleted still gets its delete stamp + (:71-74).
        • +
        • The Modified branch (:76-84): the interesting one. It sets LastModifiedBy/ LastModifiedOn, and explicitly marks CreatedBy and CreatedOn as IsModified = false - (:67-68). That is the invariant: an update can never rewrite creation provenance, even if the + (:77-78). That is the invariant: an update can never rewrite creation provenance, even if the caller mutated those properties on a tracked instance. It then runs the same soft-delete - transition check, this time against the flag's stored value (:72).
        • -
        • Detached, Unchanged, Deleted and the default (:74-78): deliberate no-ops. Note what + transition check, this time against the flag's stored value (:82), and stamps a fresh row + version (:83).
        • +
        • Detached, Unchanged, Deleted and the default (:85-89): deliberate no-ops. Note what Deleted means here: a hard delete, which the framework does not use for auditable entities. A soft delete arrives as Modified, because the entity only set a flag (see ADR-005 for soft delete versus erasure).
        • -
        • WasDeleted (:84-85): the flag as the database has it, read from the property's +
        • StampRowVersion (:99-105): when the engine is client-stamped and the entity's model has a + RowVersion property (entry.Metadata.FindProperty, :101), it writes + Guid.NewGuid().ToByteArray() as the property's current value (:103). Setting the current value + marks the property modified while EF keeps the loaded value as the original, so the UPDATE still + carries the old token in its WHERE clause and a concurrent writer is detected (:94-98). On SQL + Server the rowversion column is database-generated and this method writes nothing.
        • +
        • WasDeleted (:108-109): the flag as the database has it, read from the property's OriginalValue. The is true test rather than a cast is what keeps an unset or shadow value from throwing.
        • -
        • StampSoftDeleteTransition (:92-106): compares the current flag against the prior one and - returns immediately when they agree (:98-102), so no transition means no write at all. On a +
        • StampSoftDeleteTransition (:116-130): compares the current flag against the prior one and + returns immediately when they agree (:123-126), so no transition means no write at all. On a transition it writes both stamps in one direction: the resolved user and the save's instant on a - delete, and null on both columns on an undelete (:104-105).
        • + delete, and null on both columns on an undelete (:128-129).
      • Why it's built this way: taking TimeProvider instead of calling DateTime.UtcNow makes the stamps deterministic under test ([Rubric §14, Testability]), which is what AuditSaveChangesInterceptorTests relies on (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/AuditSaveChangesInterceptorTests.cs:13, - with the four soft-delete transitions pinned at :128 (delete stamps written), :144 (an update - after a delete keeps the original stamp), :165 (undelete clears both), :186 (an active entity - keeps them null) and :195 (an insert that is already deleted is stamped)). The class is - registered as a singleton because it holds no per-save state at all - (DependencyInjection.cs:61-63), unlike its neighbours.
      • + with the soft-delete transitions pinned at :149 (delete stamps written), :165 (an update + after a delete keeps the original stamp), :186 (undelete clears both), :207 (an active entity + keeps them null) and :216 (an insert that is already deleted is stamped), and the client-stamped + row version on SQLite at :76). The class is registered as a singleton because it holds no + per-save state at all (DependencyInjection.cs:64).
      • Where it's used: resolved from DI and attached in ApplicationDbContext.OnConfiguring - (ApplicationDbContext.cs:292, added at :266 or :270). It is always first in the + (ApplicationDbContext.cs:297, added at :307 or :311). It is always first in the interceptor chain, which is what lets TenantSaveChangesInterceptor, the domain-event interceptor and the optional AuditTrailSaveChangesInterceptor assume the - audit stamps are already final when they run (ApplicationDbContext.cs:295-299, :273-277).
      • + audit stamps are already final when they run (ApplicationDbContext.cs:300-304, :314-318).

      DeferredDispatch

      @@ -6533,8 +7269,8 @@

      DeferredDispatch

    441. Where it's used: enqueued by DispatchAndFinalizeAsync when context.Database.CurrentTransaction is not null (:308-314); drained by FlushDeferredAsync after a successful commit - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:600); - discarded wholesale by DropDeferred on every rollback path (DbContextFactory.cs:466, :599, + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:619); + discarded wholesale by DropDeferred on every rollback path (DbContextFactory.cs:482, :599, :668, :746).
    442. DomainEventSaveChangesInterceptor

      @@ -6583,8 +7319,13 @@

      DomainEventSaveChangesInterceptor

      (:42-45, :55). This is why the type has two behavioral axes rather than one: - context.SupportsOutbox (does this engine have the table) and _outboxEnabled (does this host - want it), and both must be true to take the durable branch (:236).
    443. + context.Engine.Capabilities.IsRelational (is this a relational engine, and so one with an outbox + table) and _outboxEnabled (does this host want it), and both must be true to take the durable + branch (:236). The engine axis is read off the context's + DataSourceEngineCapabilities: SQL Server, PostgreSQL and SQLite + declare IsRelational: true, Cosmos declares false + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SQLServerDataSourceEngine.cs:44, + PostgreSQLDataSourceEngine.cs:42, SqliteDataSourceEngine.cs:41, CosmosDataSourceEngine.cs:44).
    444. Walkthrough (this is the densest type in the group; read it as capture, then route, then defer):
      • Two instance fields and three static weak tables (:53, :55, :62, :69, :77). The @@ -6608,7 +7349,7 @@

        DomainEventSaveChangesInterceptor

        It reads the exclusion set (:219) and projects the tracked aggregate roots that have events and are not excluded into AggregateCapture values (:221-225), taking a snapshot copy of each event list, and returns early when nothing carried an event (:227-228).
      • -
      • When context.SupportsOutbox and _outboxEnabled are both true (:236), it reads +
      • When context.Engine.Capabilities.IsRelational and _outboxEnabled are both true (:236), it reads context.CurrentOutboxOrigin once into a local (:247) rather than per event, then walks the flattened event list (:248-265): every event gets OutboxMessage.FromDomainEvent(domainEvent, origin) and is added to the outbox set, then the event is sorted. The comment states why a single read is correct rather than a shortcut (:242-246): the interceptor is a singleton and the @@ -6638,7 +7379,8 @@

        DomainEventSaveChangesInterceptor

        LogDispatchError (:367-368) and signals the processor so the unprocessed rows get retried (:339-347); the finally clears the events again, idempotently (:348-352).
      • SavedChanges, the synchronous path (:124-138): it cannot await the dispatcher, so it does - not try. For a host with the outbox on and an outbox-capable context it removes the state, clears + not try. For a host with the outbox on and a context whose engine is relational (the property + pattern { Engine.Capabilities.IsRelational: true } at :127) it removes the state, clears the captured events (which is what stops a later async save from re-capturing and duplicating them) and signals the processor, leaving delivery entirely to the outbox. A context without outbox support, and a host running with the outbox off, keep the legacy no-op, because with no @@ -6653,10 +7395,10 @@

        DomainEventSaveChangesInterceptor

        BeginCaptureExclusion / EndCaptureExclusion (:179-188, :195): the narrow hook for IDENTITY_INSERT batching. DbContextFactory splits a save into one round per identity table and temporarily marks the other tables' entries Unchanged - (DbContextFactory.cs:319-335); those rows are not written this round, so capturing their events + (DbContextFactory.cs:388-406); those rows are not written this round, so capturing their events now would persist and clear an event ahead of the insert that justifies it. The exclusion set is built with ReferenceEqualityComparer.Instance (:187) and cleared in a finally - (DbContextFactory.cs:359-365). The remarks explain why exclusion is by instance and not by + (DbContextFactory.cs:430-435). The remarks explain why exclusion is by instance and not by entity state (:172-176): skipping every Unchanged aggregate would also drop events raised on an already-saved aggregate, which is how the identity module publishes its registration events.
      @@ -6669,11 +7411,11 @@

      DomainEventSaveChangesInterceptor

      partial
      for the source-generated [LoggerMessage] (:367-368), and singleton-safe because every piece of per-save state lives in a weak table keyed by context.
    445. -
    446. Where it's used: registered as a singleton (DependencyInjection.cs:64), attached last of the - save-time trio in ApplicationDbContext.OnConfiguring (ApplicationDbContext.cs:293, :266, - :270) so it sees final audit stamps and final tenant values; driven at transaction boundaries by +
    447. Where it's used: registered as a singleton (DependencyInjection.cs:65), attached last of the + save-time trio in ApplicationDbContext.OnConfiguring (ApplicationDbContext.cs:298, :307, + :311) so it sees final audit stamps and final tenant values; driven at transaction boundaries by DbContextFactory. Pinned by DomainEventSaveChangesInterceptorTests - (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:17), + (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Interceptors/DomainEventSaveChangesInterceptorTests.cs:19), DomainEventSaveChangesInterceptorOutboxRoutingTests (.../DomainEventSaveChangesInterceptorOutboxRoutingTests.cs:27), DomainEventSaveChangesInterceptorOutboxDisabledTests (.../DomainEventSaveChangesInterceptorOutboxDisabledTests.cs:21) and DomainEventCaptureExclusionTests (.../DomainEventCaptureExclusionTests.cs:26).
    448. @@ -6698,10 +7440,10 @@

      TenantSaveChangesInterceptor

      [Rubric §11, Security] assesses whether a boundary holds without caller cooperation. Tenancy in this framework is enforced twice, independently. On reads it is a named EF query filter, "Tenant", applied in ApplicationDbContext.ApplyTenantFilters - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:509-569), - which composes by AND with the "SoftDelete" filter (ApplicationDbContext.cs:486-494) and embeds + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:514-574), + which composes by AND with the "SoftDelete" filter (ApplicationDbContext.cs:491-499) and embeds the executing context as a constant so one cached model serves every tenant - (ApplicationDbContext.cs:495-501, :435-436). On writes it is this interceptor. The + (ApplicationDbContext.cs:500-506, :435-436). On writes it is this interceptor. The independence is the point and is called out in the remarks (:29-34): a caller who bypasses the read filter with EF's own parameterless IgnoreQueryFilters() can read across tenants, but still cannot write across them. [Rubric §30, Compliance and Data Governance] applies for the same @@ -6711,7 +7453,7 @@

      TenantSaveChangesInterceptor

      audit interceptor, both routing to ApplyTenant.
    449. ApplyTenant (:64-94): reads context.CurrentTenantId once per save (:68), because that property walks a live accessor into the scoped tenant context - (ApplicationDbContext.cs:141) and a save must be judged against a single value. It then + (ApplicationDbContext.cs:154) and a save must be judged against a single value. It then enumerates ChangeTracker.Entries<ITenantEntity>() filtered by !entry.Metadata.IsOwned() (:74-75) and switches on state: Added to StampOrVerifyInsert, Modified and Deleted to VerifyExistingRow with the operation name, everything else a no-op. Owned types are excluded on @@ -6741,15 +7483,15 @@

      TenantSaveChangesInterceptor

      order (:15-21). This one sits deliberately between the audit and domain-event interceptors: the audit stamps are already written when it runs, and the outbox rows the domain-event interceptor adds afterwards describe an entity whose tenant is final. It is also always registered and inert - by default (:22-28, DependencyInjection.cs:66-69): it is a no-op for every entity that does + by default
      (:22-28, DependencyInjection.cs:67-70): it is a no-op for every entity that does not carry ITenantEntity, which is every entity in a host that never adopted tenancy, so that host pays nothing while a host that does adopt tenancy can never accidentally leave the write guard off. OnConfiguring resolves it with GetService rather than GetRequiredService - (ApplicationDbContext.cs:300) so a directly-constructed test or design-time context still builds. + (ApplicationDbContext.cs:305) so a directly-constructed test or design-time context still builds. See ADR-073.
    450. Where it's used: registered as a singleton in AddInfrastructure - (DependencyInjection.cs:69) and attached second in the interceptor chain - (ApplicationDbContext.cs:302); exercised by TenantSaveChangesInterceptorTests + (DependencyInjection.cs:70) and attached second in the interceptor chain + (ApplicationDbContext.cs:307); exercised by TenantSaveChangesInterceptorTests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/TenantSaveChangesInterceptorTests.cs:12, including the owned-value carve-out at :149 and the still-builds-without-it case at :164) and by the registration tests in @@ -6760,62 +7502,61 @@

      TenantSaveChangesInterceptor

      InternalCommandScheduler

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.InternalCommands · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandScheduler.cs:39 · Level 13 · class (internal sealed, partial)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.InternalCommands · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandScheduler.cs:34 · Level 13 · class (internal sealed, partial)

      • What it is: the write side of the internal-command queue, IInternalCommandScheduler's implementation: it turns a command into an InternalCommandMessage row, enrolls it in the caller's transaction if one is open, and signals the processor when the work is - due immediately (InternalCommandScheduler.cs:39-164).
      • + due immediately (InternalCommandScheduler.cs:34-140).
      • Depends on: IDbContextFactory, IDataSourceResolver, IOptions<InternalCommandsSettings>, - ICurrentUserService, ITenantContext, ICorrelationContext, IInternalCommandSignal, and - ILogger<InternalCommandScheduler>, all through the primary constructor (:39-48); - InternalCommandMessage as the row it builds, and + InternalCommandOriginCapture, IInternalCommandSignal, + ILogger<InternalCommandScheduler> and TimeProvider, all through the primary constructor + (:34-41); InternalCommandMessage as the row it builds, and InternalCommandOrigin as the captured scheduling context it hands to - FromCommand; Result<Guid> and Error for its return shape; System.Diagnostics.Activity for the - trace/span ids.
      • + FromCommand; Result<Guid> and Error for its return shape.
      • Concept introduced, scheduling rides the caller's own save. [Rubric §6, CQRS and Event-Driven] assesses whether deferred work is delivered reliably rather than optimistically, the same lens as the outbox. ScheduleAsync never opens a SaveChanges of its own when a transaction is already active: it resolves the caller's context through dbContextFactory.GetDbContext(...) and Adds the row onto - it (:104-108), so the queued command commits or rolls back exactly with the aggregate change that - scheduled it. Outside a transaction it saves immediately and signals the processor only when the work - is due now (:111-130), because enrolling into an open transaction and signalling immediately would - only buy a query against a row that has not committed yet.
      • + it (:97-102), so the queued command commits or rolls back exactly with the aggregate change that + scheduled it. Inside a transaction it neither saves nor signals (:104-112): the caller's next save + writes the row, or the transactional pipeline saves it just before the commit when no save follows + (:106-109), and signalling then would only buy a query against a row that has not committed yet. + Outside a transaction it saves immediately and always signals the processor (:116-121).
      • Walkthrough:
          -
        • MaxRolesLength (:55): internal const int, aliased to AmbientOrigin.MaxRolesLength so the +
        • MaxRolesLength (:48): internal const int, aliased to AmbientOrigin.MaxRolesLength so the internal-command queue and the outbox agree on the column width for UserRoles without restating the number.
        • -
        • ScheduleAsync(command, delay, cancellationToken) (:64-71): the delay-based overload, sugar +
        • ScheduleAsync(command, delay, cancellationToken) (:57-64): the delay-based overload, sugar over the DateTimeOffset? overload: a positive delay becomes _timeProvider.GetUtcNow() + delay, anything else passes null through.
        • -
        • ScheduleAsync(command, runAt, cancellationToken) (:74-131), the primary path:
            -
          • Null-guards the command (:79-82).
          • -
          • Normalizes a past runAt to "now" rather than rejecting it (:88): a caller computing a +
          • ScheduleAsync(command, runAt, cancellationToken) (:67-125), the primary path:
              +
            • Returns NullCommandError (a Validation error, :50-51) for a null command (:72-75).
            • +
            • Normalizes a past runAt to "now" rather than rejecting it (:81): a caller computing a deadline that already slipped wants the work done immediately, not an error to handle.
            • -
            • Builds the row via InternalCommandMessage.FromCommand(command, scheduledOn, now, CaptureOrigin()) (:90-102) inside a try/catch (NotSupportedException): System.Text.Json - reports an unserializable payload this way, and the catch turns it into a logged failure result - rather than letting it take down the handler that scheduled the command.
            • +
            • Builds the row via InternalCommandMessage.FromCommand(command, scheduledOn, now, originCapture.Capture()) (:83-95, the call at :86) inside a try/catch (NotSupportedException): System.Text.Json reports an unserializable payload this way, and the + catch turns it into a logged failure result rather than letting it take down the handler that + scheduled the command. The origin snapshot itself is + InternalCommandOriginCapture's job.
            • Resolves the context for _settings.DataSource/DatabaseName through - dataSourceResolver.ResolveLogical (:104-105) and adds the row with the standard - VSTHRD103-suppressed synchronous Add (:108).
            • -
            • If context.Database.CurrentTransaction is not null, returns immediately without saving or - signalling (:111-118): the caller's own commit persists the row, and the processor discovers it - on its next poll.
            • + dataSourceResolver.ResolveLogical (:97-98) and adds the row with the standard + VSTHRD103-suppressed synchronous Add (:100-102). +
            • If context.Database.CurrentTransaction is not null, logs LogEnrolled and returns immediately + without saving or signalling (:104-112); the processor discovers the committed row on its next + poll.
            • Otherwise calls context.SaveChangesAsync directly, deliberately not through a user id overload, because an InternalCommandMessage is neither auditable nor an aggregate root, so - there is nothing for the audit or domain-event interceptors to do here (:122); signals the - processor only when scheduledOn <= now (:124-127).
            • + there is nothing for the audit or domain-event interceptors to do here (:114-116). It then + signals the processor whether or not the row is due yet (:118-121): a due row runs on the + wake, and a not-yet-due row costs one fetch but re-arms the processor's smart wait on its + ScheduledOn, which the processor would otherwise only learn at the next polling interval and + so run the command late.
          • -
          • CaptureOrigin() (:138-149, private): builds the - InternalCommandOrigin from Activity.Current for the trace/span ids and - from currentUserService/tenantContext/correlationContext for the rest, flattening roles - through the shared AmbientOrigin.FlattenRoles helper, the same one the outbox capture uses, so - both hops store roles in the same shape (:141-144).
          • -
          • SerializationError(commandType) (:151-154): the Error.Failure factory for the +
          • SerializationError(commandType) (:127-130): the Error.Failure factory for the not-serializable branch.
          • -
          • LogEnrolled, LogScheduled, LogSerializationFailed (:156-163): source-generated +
          • LogEnrolled, LogScheduled, LogSerializationFailed (:132-139): source-generated [LoggerMessage] partials at Debug, Debug, and Error respectively.
        • @@ -6825,15 +7566,14 @@

          InternalCommandScheduler

          DeferredDispatch enforces for domain events, applied here to a queued command instead of an in-process dispatch. See ADR-114. -
        • Where it's used: registered in DI (DependencyInjection.cs, 1 reference); the entry point every - caller of IInternalCommandScheduler.ScheduleAsync goes through. Pinned by - InternalCommandSchedulerTests - (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandSchedulerTests.cs) - and exercised through InternalCommandTestHarness.
        • -
        • Caveats / not-in-source: NullCommandError returns a Result.Failure for a null command rather - than throwing, which is the one path in this type that does not match the null-guard-and-throw shape - used everywhere else in the class; that asymmetry is in the source as written and not explained - further in a comment.
        • +
        • Where it's used: registered in DI (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Jobs.cs, + 1 reference); the entry point every caller of IInternalCommandScheduler.ScheduleAsync goes through. + Pinned by InternalCommandSchedulerTests + (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/InternalCommands/InternalCommandSchedulerTests.cs), + exercised through InternalCommandTestHarness, and referenced by DbContextFactoryTransactionTests.
        • +
        • Caveats / not-in-source: a null command returns Result.Failure with a Validation error + (:50-51, :72-75) rather than throwing ArgumentNullException; the source does not explain the + choice in a comment.

        IdentityModuleDbSeederBase<TUser>

        @@ -6927,11 +7667,11 @@

        IInternalCommandSignal

        • What it is: a wake-up abstraction that lets code schedule an internal command and immediately nudge the processor, instead of the processor relying only on a fixed polling interval.
        • -
        • Depends on: nothing first-party; the contract is Task WaitAsync(TimeSpan, CancellationToken) plus a synchronous Signal() (IInternalCommandSignal.cs:27,37).
        • -
        • Concept introduced, the signal-based smart-wait pattern for the internal-commands job queue. [Rubric §12: Performance & Scalability] (assesses whether the system avoids wasted work and unnecessary latency): a plain fixed-interval poll trades latency for idle cost either way, while a signal lets a processor sleep long and still react immediately when work shows up. The doc comment on WaitAsync (IInternalCommandSignal.cs:30-37) states its intended caller directly: InternalCommandProcessor, "in place of a plain polling delay."
        • -
        • Walkthrough: Signal() (IInternalCommandSignal.cs:27-28) marks that due work is available; WaitAsync(timeout, cancellationToken) (IInternalCommandSignal.cs:37) waits for a signal or until timeout elapses, whichever comes first.
        • +
        • Depends on: nothing first-party; the contract is Task WaitAsync(TimeSpan, CancellationToken) plus a synchronous Signal() (IInternalCommandSignal.cs:12,21).
        • +
        • Concept introduced, the signal-based smart-wait pattern for the internal-commands job queue. [Rubric §12: Performance & Scalability] (assesses whether the system avoids wasted work and unnecessary latency): a plain fixed-interval poll trades latency for idle cost either way, while a signal lets a processor sleep long and still react immediately when work shows up. The doc comment on WaitAsync (IInternalCommandSignal.cs:14-17) states its intended caller directly: InternalCommandProcessor, "in place of a plain polling delay."
        • +
        • Walkthrough: Signal() (IInternalCommandSignal.cs:11-12) marks that due work is available; WaitAsync(timeout, cancellationToken) (IInternalCommandSignal.cs:21) waits for a signal or until timeout elapses, whichever comes first.
        • Why it's built this way: an interface, not a concrete SemaphoreSlim dependency, so the processor, scheduler, and administration surface can share one wake mechanism through DI and a test harness can substitute a controllable fake.
        • -
        • Where it's used: implemented by InternalCommandSignal; consumed by InternalCommandProcessor (the wait loop), InternalCommandScheduler, and InternalCommandAdministration (it signals after a successful requeue, InternalCommandAdministration.cs:756); registered in DependencyInjection.cs.
        • +
        • Where it's used: implemented by InternalCommandSignal; consumed by InternalCommandProcessor (the wait loop), InternalCommandScheduler, and InternalCommandAdministration (it signals after a successful requeue, InternalCommandAdministration.cs:192); registered in DependencyInjection.cs.

        InternalCommandCycleResult

        @@ -6943,7 +7683,7 @@

        InternalCommandCycleResult

      • Concept: a plain readonly record struct used purely to avoid tuple sprawl across the processor's internal methods; no new pattern to teach beyond the record-struct convention used elsewhere in the framework.
      • Walkthrough: one line, two positional members: HasMoreDueWork drives whether InternalCommandProcessor re-polls immediately; EarliestUpcoming feeds ComputeWaitTime.
      • Why it's built this way: internal readonly record struct keeps the per-cycle result on the stack rather than the heap, since it is produced and consumed once per poll cycle in a loop that can run continuously for the life of the host.
      • -
      • Where it's used: returned by InternalCommandProcessor.ProcessDueCommandsAsync (InternalCommandProcessor.cs:1103,1137) and its private per-target helper, then read by ExecuteAsync's wait loop.
      • +
      • Where it's used: returned by InternalCommandProcessor.ProcessDueCommandsAsync (InternalCommandProcessor.cs:125,140) and its private per-target helper, then read by ExecuteAsync's wait loop.

      InternalCommandsDisabledNoticeService

      @@ -6953,8 +7693,8 @@

      InternalCommandsDisabledNoticeSer
    451. What it is: an IHostedService that logs a single informational notice on startup for a host that writes InternalCommands rows but runs neither InternalCommandProcessor nor InternalCommandCleanupService (InternalCommandsSettings.Enabled == false).
    452. Depends on: ILogger<InternalCommandsDisabledNoticeService>, the [LoggerMessage] source-generator pattern.
    453. Concept: a "notice service" idiom that pairs a settings toggle with a one-time explanatory log line rather than staying silent about a deliberate configuration choice. [Rubric §13: Observability & Operability] (assesses whether an operator can tell WHY the system behaves a certain way): a replica that never drains its own queue is easy to mistake for a bug without this notice.
    454. -
    455. Walkthrough: StartAsync (InternalCommandsDisabledNoticeService.cs:83-87) calls the generated LogQueueDisabled (InternalCommandsDisabledNoticeService.cs:92-95) once and returns; StopAsync (InternalCommandsDisabledNoticeService.cs:90) is a no-op.
    456. -
    457. Why it's built this way: the log message itself (InternalCommandsDisabledNoticeService.cs:94) states the operational fact directly: scheduled work is executed only by a host that has InternalCommands:Enabled=true against the same databases, and no migration is needed to turn the queue on later because the InternalCommands table is already part of the model.
    458. +
    459. Walkthrough: StartAsync (InternalCommandsDisabledNoticeService.cs:24-28) calls the generated LogQueueDisabled (InternalCommandsDisabledNoticeService.cs:33-36) once and returns; StopAsync (InternalCommandsDisabledNoticeService.cs:31) is a no-op.
    460. +
    461. Why it's built this way: the log message itself (InternalCommandsDisabledNoticeService.cs:35) states the operational fact directly: scheduled work is executed only by a host that has InternalCommands:Enabled=true against the same databases, and no migration is needed to turn the queue on later because the InternalCommands table is already part of the model.
    462. Where it's used: registered in DependencyInjection.cs (1 site), presumably conditioned on InternalCommandsSettings.Enabled being false.
    463. WakeUpSignal

      @@ -6965,7 +7705,7 @@

      WakeUpSignal

    464. What it is: the shared wake-up primitive behind both queue signals: an internal, single-slot SemaphoreSlim(0, 1) wrapper used purely as a flag, not a resource lock.
    465. Depends on: System.Threading.SemaphoreSlim, IDisposable.
    466. Concept: the semaphore is capped at one permit because a drain cycle empties a whole batch per pass, so a burst of signals carries no more information than a single one; extracting it here lets both queue signals share the exact same wake-up mechanics instead of each owning a copy.
    467. -
    468. Walkthrough: _semaphore = new(0, 1) (WakeUpSignal.cs:27); Signal() (WakeUpSignal.cs:30-40) releases the semaphore and swallows SemaphoreFullException, because a wake-up already pending means "one batch drains everything: nothing to add"; WaitAsync(timeout, cancellationToken) (WakeUpSignal.cs:46-56) awaits the semaphore with the given timeout and re-throws OperationCanceledException only when the token itself requested cancellation, so shutdown propagates but a plain timeout returns normally; Dispose() (WakeUpSignal.cs:59) disposes the semaphore.
    469. +
    470. Walkthrough: _semaphore = new(0, 1) (WakeUpSignal.cs:27); Signal() (WakeUpSignal.cs:30-40) releases the semaphore and swallows SemaphoreFullException, because a wake-up already pending means "one batch drains everything: nothing to add"; WaitAsync(timeout, cancellationToken) (WakeUpSignal.cs:37-47) awaits the semaphore with the given timeout and re-throws OperationCanceledException only when the token itself requested cancellation, so shutdown propagates but a plain timeout returns normally; Dispose() (WakeUpSignal.cs:50) disposes the semaphore.
    471. Why it's built this way: pulling this out of InternalCommandSignal means the internal-commands queue and the outbox share one tested wake-up implementation instead of two independent copies of the same semaphore dance.
    472. Where it's used: wrapped by InternalCommandSignal (2 sites) and by OutboxSignal (2 sites, MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxSignal.cs).
    473. @@ -6976,9 +7716,9 @@

      InternalCommandMetrics

      • What it is: the static home of the OpenTelemetry Meter and every instrument the internal-commands job queue publishes: processed/failed/dead-letter counters, execution duration and scheduling-lag histograms, and pending-depth / oldest-due-age gauges.
      • Depends on: System.Diagnostics.Metrics (Meter, Counter<T>, Histogram<T>, ObservableGauge<T>), ConcurrentDictionary<TKey,TValue>.
      • -
      • Concept introduced, per-instance observable gauges for a replicated background worker. [Rubric §13: Observability & Operability]: the doc remarks on PendingDepthGauge (InternalCommandMetrics.cs:183-190) spell out that each replica reports only what IT last observed, never a cluster total, because the poll predicate already excludes rows another replica currently holds under lease; a source whose database is unreachable contributes zero for that cycle instead of a stale value.
      • -
      • Walkthrough: MeterName constant (InternalCommandMetrics.cs:119, "MMCA.Common.InternalCommands") backs a single Meter (line 121); ProcessedCounter/FailedCounter/DeadLetterCounter (InternalCommandMetrics.cs:137-159) are tagged by command_type (and reason for the latter two); DurationHistogram and LagHistogram (InternalCommandMetrics.cs:165-177) measure execution time and scheduling lag in seconds; PendingDepthGauge (InternalCommandMetrics.cs:191-195) is backed by _pendingDepth (line 127), read via Interlocked.Read; OldestDueAgeGauge (InternalCommandMetrics.cs:203-207) is backed by a ConcurrentDictionary<string,double> keyed by data_source (lines 133-134) and observed through ObserveOldestDueAge (InternalCommandMetrics.cs:219-227); SetPendingDepth (line 211) and SetOldestDueAge (lines 216-217) are the only writers, both called from InternalCommandProcessor.
      • -
      • Why it's built this way: the counter/histogram/gauge split mirrors the questions an operator actually asks: throughput and failure rate (counters), how long work takes and how late it starts (histograms), and how deep and how stale the backlog currently is (gauges); the doc comment on OldestDueAgeGauge (InternalCommandMetrics.cs:197-202) explains it is the signal a "wedged queue" alert fires on, distinct from the lag histogram which only reports rows that DID run.
      • +
      • Concept introduced, per-instance observable gauges for a replicated background worker. [Rubric §13: Observability & Operability]: the doc remarks on PendingDepthGauge (InternalCommandMetrics.cs:84-91) spell out that each replica reports only what IT last observed, never a cluster total, because the poll predicate already excludes rows another replica currently holds under lease; a source whose database is unreachable contributes zero for that cycle instead of a stale value.
      • +
      • Walkthrough: MeterName constant (InternalCommandMetrics.cs:20, "MMCA.Common.InternalCommands") backs a single Meter (line 22); ProcessedCounter/FailedCounter/DeadLetterCounter (InternalCommandMetrics.cs:38-60) are tagged by command_type (and reason for the latter two); DurationHistogram and LagHistogram (InternalCommandMetrics.cs:66-78) measure execution time and scheduling lag in seconds; PendingDepthGauge (InternalCommandMetrics.cs:92-96) is backed by _pendingDepth (line 28), read via Interlocked.Read; OldestDueAgeGauge (InternalCommandMetrics.cs:104-108) is backed by a ConcurrentDictionary<string,double> keyed by data_source (lines 34-35) and observed through ObserveOldestDueAge (InternalCommandMetrics.cs:120-128); SetPendingDepth (line 112) and SetOldestDueAge (lines 117-118) are the only writers, both called from InternalCommandProcessor.
      • +
      • Why it's built this way: the counter/histogram/gauge split mirrors the questions an operator actually asks: throughput and failure rate (counters), how long work takes and how late it starts (histograms), and how deep and how stale the backlog currently is (gauges); the doc comment on OldestDueAgeGauge (InternalCommandMetrics.cs:98-103) explains it is the signal a "wedged queue" alert fires on, distinct from the lag histogram which only reports rows that DID run.
      • Where it's used: InternalCommandProcessor.cs (8 sites, one poll cycle at a time); MMCA.Common.Aspire's OutboxPollFilterProcessor.cs (1 site) reads MeterName to filter the paired suppressed telemetry.
      • ADRs: ADR-041, ADR-114.
      @@ -6989,9 +7729,9 @@

      InternalCommandNameResolver

      • What it is: a static, cached resolver between an IInternalCommand CLR type and the string a queued row stores for it, honoring an optional declared-name attribute override in both directions.
      • Depends on: System.Reflection (Type.GetCustomAttribute, AppDomain.GetAssemblies), ConcurrentDictionary<TKey,TValue>, the first-party InternalCommandNameAttribute (not in this group).
      • -
      • Concept: reflection-with-caching for a hot path. [Rubric §12: Performance & Scalability]: the doc comment on DeclaredNameCache (InternalCommandNameResolver.cs:250-254) is explicit that caching the null (no-attribute) case too keeps the common unannotated command to one reflection lookup per type per process, rather than one per scheduled row.
      • -
      • Walkthrough: GetDeclaredName (InternalCommandNameResolver.cs:264-267) reads InternalCommandNameAttribute with inherit: false (a derived command cannot silently borrow its base's identity) via GetOrAdd; GetStorageName (InternalCommandNameResolver.cs:276-280) returns the declared name if present, else falls back through AssemblyQualifiedName → FullName → Name, the same fallback chain the outbox uses; FindTypeByDeclaredName (InternalCommandNameResolver.cs:295-301) is the reverse lookup for a stored name that is not a CLR type name: it lazily scans loaded, non-dynamic assemblies and checks IsDefined before GetDeclaredName, deliberately in that order, so only the handful of annotated types pay for constructing the attribute; GetLoadableTypes (InternalCommandNameResolver.cs:310-320) degrades to the types that DID load on a ReflectionTypeLoadException, so one bad type in an assembly cannot abort the scan.
      • -
      • Why it's built this way: the lazy Where/SelectMany/FirstOrDefault chain stops at the first match instead of materializing every loaded type (InternalCommandNameResolver.cs:288-292), which matters because this reverse path is reached at most once per stored name (the caller, InternalCommandMessage.ResolveCommandType, caches the result).
      • +
      • Concept: reflection-with-caching for a hot path. [Rubric §12: Performance & Scalability]: the doc comment on DeclaredNameCache (InternalCommandNameResolver.cs:21-26) is explicit that caching the null (no-attribute) case too keeps the common unannotated command to one reflection lookup per type per process, rather than one per scheduled row.
      • +
      • Walkthrough: GetDeclaredName (InternalCommandNameResolver.cs:35-38) reads InternalCommandNameAttribute with inherit: false (a derived command cannot silently borrow its base's identity) via GetOrAdd; GetStorageName (InternalCommandNameResolver.cs:47-51) returns the declared name if present, else falls back through AssemblyQualifiedName → FullName → Name, the same fallback chain the outbox uses; FindTypeByDeclaredName (InternalCommandNameResolver.cs:66-72) is the reverse lookup for a stored name that is not a CLR type name: it lazily scans loaded, non-dynamic assemblies and checks IsDefined before GetDeclaredName, deliberately in that order, so only the handful of annotated types pay for constructing the attribute; GetLoadableTypes (InternalCommandNameResolver.cs:81-91) degrades to the types that DID load on a ReflectionTypeLoadException, so one bad type in an assembly cannot abort the scan.
      • +
      • Why it's built this way: the lazy Where/SelectMany/FirstOrDefault chain stops at the first match instead of materializing every loaded type (InternalCommandNameResolver.cs:59-63), which matters because this reverse path is reached at most once per stored name (the caller, InternalCommandMessage.ResolveCommandType, caches the result).
      • Where it's used: InternalCommandMessage.cs (2 sites, plus 1 more).

      InternalCommandSignal

      @@ -7014,9 +7754,9 @@

      InternalCommandsSettings

    474. What it is: the bound options class for the "InternalCommands" configuration section: it controls enablement, batching, retry backoff, claim leasing, retention, and which data source new work is scheduled against.
    475. Depends on: DataSource and DataSourceKey, System.ComponentModel.DataAnnotations [Range] validation.
    476. Concept: the options pattern used throughout the framework, here tuned deliberately DIFFERENT from the outbox's own settings in two places the doc comments call out explicitly. [Rubric §13: Observability & Operability] assesses whether operational knobs are documented well enough to tune safely; every property here carries a rationale comment, not just a default.
    477. -
    478. Walkthrough: SectionName (InternalCommandsSettings.cs:397, "InternalCommands"); Enabled (InternalCommandsSettings.cs:406, default true, the outbox's own posture: a host that schedules work must drain it); BatchSize (InternalCommandsSettings.cs:409-410, [Range(1,1000)], default 50); MaxAttempts (InternalCommandsSettings.cs:417-418, [Range(1,20)], default 5, a Result.Failure counts as an attempt exactly like a thrown exception); PollingIntervalSeconds (InternalCommandsSettings.cs:427-428, [Range(1,3600)], default 2, the fallback bound behind the smart wait); ProcessingDelaySeconds (InternalCommandsSettings.cs:436-437, [Range(0,600)], default 0: UNLIKE the outbox's 5, because the outbox delay exists to bound a race with an in-process fast path that the job queue does not have); LeaseSeconds (InternalCommandsSettings.cs:446-447, [Range(10,3600)], default 300, how long one replica's claim on a row lasts before another replica may reclaim it); RetryBackoffBaseSeconds (InternalCommandsSettings.cs:455-456, [Range(1,3600)], default 10, attempt n waits base * 2^(n-1) with [0.8, 1.2] jitter); MaxRetryBackoffSeconds (InternalCommandsSettings.cs:464-465, [Range(1,86400)], default 600, independent of LeaseSeconds unlike the outbox, because a job queue wants a long lease for slow handlers but a short backoff ceiling); RetentionDays (InternalCommandsSettings.cs:471-472, [Range(0,3650)], default 7, 0 keeps completed rows forever); DeadLetterRetentionDays (InternalCommandsSettings.cs:479-480, [Range(0,3650)], default 0 falls back to RetentionDays); CleanupIntervalHours (InternalCommandsSettings.cs:486-487, [Range(1,168)], default 6); DataSource (InternalCommandsSettings.cs:495, default DataSource.SQLServer, must be relational: Cosmos has no InternalCommands table); DatabaseName (InternalCommandsSettings.cs:503, default DataSourceKey.DefaultName).
    479. +
    480. Walkthrough: SectionName (InternalCommandsSettings.cs:18, "InternalCommands"); Enabled (InternalCommandsSettings.cs:27, default true, the outbox's own posture: a host that schedules work must drain it, and a write-only replica sets it false); BatchSize (InternalCommandsSettings.cs:30-31, [Range(1,1000)], default 50); MaxAttempts (InternalCommandsSettings.cs:38-39, [Range(1,20)], default 5, a Result.Failure counts as an attempt exactly like a thrown exception); PollingIntervalSeconds (InternalCommandsSettings.cs:48-49, [Range(1,3600)], default 2, the fallback bound behind the smart wait, which is what bounds a row scheduled inside a transaction because that row raises no signal); ProcessingDelaySeconds (InternalCommandsSettings.cs:57-58, [Range(0,600)], default 0: UNLIKE the outbox's 5, because the outbox delay exists to bound a race with an in-process fast path that the job queue does not have); LeaseSeconds (InternalCommandsSettings.cs:68-69, [Range(10,3600)], default 300, how long one replica's claim on a row lasts before another replica may reclaim it; the lease is renewed before each row of a claimed batch runs, so it bounds one handler, not the whole batch, per the doc comment at InternalCommandsSettings.cs:65-66); RetryBackoffBaseSeconds (InternalCommandsSettings.cs:77-78, [Range(1,3600)], default 10, attempt n waits base * 2^(n-1) with [0.8, 1.2] jitter, then capped); MaxRetryBackoffSeconds (InternalCommandsSettings.cs:86-87, [Range(1,86400)], default 600, independent of LeaseSeconds unlike the outbox, because a job queue wants a long lease for slow handlers but a short backoff ceiling); RetentionDays (InternalCommandsSettings.cs:93-94, [Range(0,3650)], default 7, 0 keeps completed rows forever); DeadLetterRetentionDays (InternalCommandsSettings.cs:101-102, [Range(0,3650)], default 0 falls back to RetentionDays); CleanupIntervalHours (InternalCommandsSettings.cs:108-109, [Range(1,168)], default 6); DataSource (InternalCommandsSettings.cs:117, default DataSource.SQLServer, must be relational: Cosmos has no InternalCommands table, though the processor drains every relational source in use, not only this one); DatabaseName (InternalCommandsSettings.cs:125, default DataSourceKey.DefaultName, pointed at the source holding the aggregates that schedule work so the row and the aggregate change share one transaction).
    481. Why it's built this way: every divergence from the outbox's settings is a deliberate consequence of the job queue having no in-process fast path to race against, documented inline rather than left implicit.
    482. -
    483. Where it's used: read via IOptions<InternalCommandsSettings> across DependencyInjection.cs (5 sites), InternalCommandCleanupService (4), InternalCommandScheduler (4), the test harness (3), InternalCommandAdministration (2), InternalCommandProcessor (2), and one cleanup-service test.
    484. +
    485. Where it's used: read via IOptions<InternalCommandsSettings> across DependencyInjection.Jobs.cs (5 sites), InternalCommandCleanupService (4), InternalCommandScheduler (4), the test harness (3), InternalCommandAdministration (2), InternalCommandProcessor (2), DataSourceBranchingFitnessTests (1), and one cleanup-service test.
    486. ADRs: ADR-114.
    487. PollingLoop

      @@ -7026,8 +7766,8 @@

      PollingLoop

      • What it is: the smart-wait polling loop extracted out of the outbox and internal-commands processors so both share one implementation: run cycles until cancelled, re-poll immediately when a cycle reports more work, otherwise wait on a signal bounded by a computed smart wait; plus the retry-backoff and per-source fan-out helpers both processors also share.
      • Depends on: System.TimeProvider, TenantDataSourceTarget.
      • -
      • Concept: this is the smart-wait loop, backoff formula, and per-target fan-out originally described under InternalCommandProcessor/OutboxProcessor, now factored into one shared static class so the two processors cannot drift out of sync on the same shape. [Rubric §12: Performance & Scalability] still applies here directly: ComputeWaitTime is what turns a fixed poll into a smart wait, and ComputeRetryBackoffSeconds's jitter (PollingLoop.cs:358-360) is what keeps a batch that failed together from retrying in lockstep against the same dependency.
      • -
      • Walkthrough: StartupDelay (PollingLoop.cs:24, 5 seconds) and MinimumWait (PollingLoop.cs:27, 1 second) are the shared constants; RunAsync (PollingLoop.cs:46-97) waits StartupDelay, returns via onNoTargets when hasTargets() is false, then loops calling runCycle, re-polling immediately on HasMoreWork and otherwise awaiting waitForSignal for ComputeWaitTime's result, with a cycle exception reported through onCycleError and treated as no pending work; ComputeWaitTime (PollingLoop.cs:117-133) returns the fallback pollingInterval when earliestUpcoming is null, otherwise earliestUpcoming + processingDelay - utcNow, floored at MinimumWait and capped at pollingInterval; DrainAllAsync (PollingLoop.cs:148-184, targets a generic List<TenantDataSourceTarget> plus a drainSource delegate) aggregates HasMoreWork (OR), EarliestUpcoming (min) and a summed PendingDepth across targets, isolating one source's exception via onSourceError from the others; ComputeRetryBackoffSeconds (PollingLoop.cs:196-210) computes base * 2^(attempts-1), clamps the exponent at 16 before Math.Pow (PollingLoop.cs:201-203, so a future settings change cannot overflow it), applies [0.8, 1.2] jitter BEFORE the cap so a capped backoff lands exactly at the ceiling, then caps at capSeconds.
      • +
      • Concept: this is the smart-wait loop, backoff formula, and per-target fan-out originally described under InternalCommandProcessor/OutboxProcessor, now factored into one shared static class so the two processors cannot drift out of sync on the same shape. [Rubric §12: Performance & Scalability] still applies here directly: ComputeWaitTime is what turns a fixed poll into a smart wait, and ComputeRetryBackoffSeconds's jitter (PollingLoop.cs:193) is what keeps a batch that failed together from retrying in lockstep against the same dependency.
      • +
      • Walkthrough: StartupDelay (PollingLoop.cs:18, 5 seconds) and MinimumWait (PollingLoop.cs:21, 1 second) are the shared constants; RunAsync (PollingLoop.cs:40-91) waits StartupDelay, returns via onNoTargets when hasTargets() is false, then loops calling runCycle, re-polling immediately on HasMoreWork and otherwise awaiting waitForSignal for ComputeWaitTime's result, with a cycle exception reported through onCycleError and treated as no pending work; ComputeWaitTime (PollingLoop.cs:104-122) returns the fallback pollingInterval when earliestUpcoming is null, otherwise earliestUpcoming + processingDelay - utcNow, floored at MinimumWait and capped at pollingInterval; DrainAllAsync (PollingLoop.cs:135-171, takes an IReadOnlyList<TenantDataSourceTarget> (PollingLoop.cs:136) plus a drainSource delegate) aggregates HasMoreWork (OR), EarliestUpcoming (min) and a summed PendingDepth across targets, isolating one source's exception via onSourceError from the others; ComputeRetryBackoffSeconds (PollingLoop.cs:183-197) computes base * 2^(attempts-1), clamps the exponent at 16 before Math.Pow (PollingLoop.cs:188, so a future settings change cannot overflow it), applies [0.8, 1.2] jitter BEFORE the cap so a capped backoff lands exactly at the ceiling, then caps at capSeconds.
      • Why it's built this way: the two callers (internal-commands, outbox) had nearly identical loop, wait-time, drain, and backoff code; extracting it here is a straight de-duplication, not a new abstraction over a different concept, so each caller keeps its own settings, signal, and per-source work delegate and passes them in as parameters.
      • Where it's used: OutboxProcessor.cs (7 sites) and InternalCommandProcessor (InternalCommandProcessor.cs, 5 sites).
      @@ -7038,113 +7778,113 @@

      InternalCommandDispatcher

      • What it is: a static dispatcher that resolves and invokes the decorated ICommandHandler<TCommand, Result> for a deserialized IInternalCommand, without the caller needing compile-time knowledge of the command's concrete type.
      • Depends on: ICommandHandler<TCommand, Result> and IInternalCommand (Application layer, not in this group), IServiceProvider, System.Reflection.MethodInfo, ConcurrentDictionary<TKey,TValue>.
      • -
      • Concept introduced, a compiled-reflection invoker cache. [Rubric §12: Performance & Scalability]: building one delegate per command type costs a single MakeGenericMethod + CreateDelegate (InternalCommandDispatcher.cs:526-530); every subsequent row of that type calls the cached delegate directly instead of paying MakeGenericMethod or MethodInfo.Invoke again.
      • -
      • Walkthrough: Invokers (InternalCommandDispatcher.cs:531) caches one Func<IServiceProvider, IInternalCommand, CancellationToken, Task<Result?>> per command Type; InvokeDefinition (InternalCommandDispatcher.cs:534-536) reflectively resolves the private static generic InvokeAsync by nameof, guarded by an inline #pragma warning disable S3011 explaining the target is this same class's own private method, so a rename cannot silently break it (InternalCommandDispatcher.cs:533); ExecuteAsync (InternalCommandDispatcher.cs:552-559) is the public entry point: Invokers.GetOrAdd(command.GetType(), BuildInvoker) then invokes; BuildInvoker (InternalCommandDispatcher.cs:561-565) does the one-time MakeGenericMethod(commandType).CreateDelegate<...>(); InvokeAsync<TCommand> (InternalCommandDispatcher.cs:572-585) resolves the handler via GetService, NOT GetRequiredService (line 578), so a command with no registered handler on this host resolves to null rather than throwing.
      • -
      • Why it's built this way: the doc comment on ExecuteAsync (InternalCommandDispatcher.cs:546-551) states the rationale for the null-not-throw choice directly: a missing handler is a deployment fact (the owning module is disabled here, or the row was written by a different service), and the caller records that fact on the row instead of treating it as an exception to classify.
      • +
      • Concept introduced, a compiled-reflection invoker cache. [Rubric §12: Performance & Scalability]: building one delegate per command type costs a single MakeGenericMethod + CreateDelegate (InternalCommandDispatcher.cs:23-27); every subsequent row of that type calls the cached delegate directly instead of paying MakeGenericMethod or MethodInfo.Invoke again.
      • +
      • Walkthrough: Invokers (InternalCommandDispatcher.cs:28) caches one Func<IServiceProvider, IInternalCommand, CancellationToken, Task<Result?>> per command Type; InvokeDefinition (InternalCommandDispatcher.cs:31-33) reflectively resolves the private static generic InvokeAsync by nameof, guarded by an inline #pragma warning disable S3011 explaining the target is this same class's own private method, so a rename cannot silently break it (InternalCommandDispatcher.cs:30); ExecuteAsync (InternalCommandDispatcher.cs:49-56) is the public entry point: Invokers.GetOrAdd(command.GetType(), BuildInvoker) then invokes; BuildInvoker (InternalCommandDispatcher.cs:58-62) does the one-time MakeGenericMethod(commandType).CreateDelegate<...>(); InvokeAsync<TCommand> (InternalCommandDispatcher.cs:69-82) resolves the handler via GetService, NOT GetRequiredService (line 75), so a command with no registered handler on this host resolves to null rather than throwing.
      • +
      • Why it's built this way: the doc comment on ExecuteAsync (InternalCommandDispatcher.cs:43-48) states the rationale for the null-not-throw choice directly: a missing handler is a deployment fact (the owning module is disabled here, or the row was written by a different service), and the caller records that fact on the row instead of treating it as an exception to classify.
      • Where it's used: InternalCommandProcessor (InternalCommandProcessor.cs, 1 site) calls ExecuteAsync when it executes a claimed row.
      • ADRs: ADR-114.

      InternalCommandAdministration

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandAdministration.cs:37 · Level 13 · class

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandAdministration.cs:34 · Level 16 · class

      • What it is: the operator-facing administration surface for the internal-commands queue: count pending work, list and paginate dead letters, requeue them, and purge completed rows, fanned out across every relational data source (and tenant) this host owns.
      • -
      • Depends on: IServiceScopeFactory, IOptions<InternalCommandsSettings> (InternalCommandsSettings), IEntityDataSourceRegistry, IDataSourceResolver, IInternalCommandSignal, TimeProvider, IOptions<TenancySettings>, ApplicationDbContext, InternalCommandMessage, TenantDataSourceTarget / TenantDataSourceTargets, Error/Result (Domain layer).
      • +
      • Depends on: IServiceScopeFactory, IOptions<InternalCommandsSettings> (InternalCommandsSettings), FrameworkTableTargets, IInternalCommandSignal, TimeProvider, IDbContextFactory, ApplicationDbContext, InternalCommandMessage, TenantDataSourceTarget, Error/Result (Domain layer).
      • Concept: the same operator-lever shape as IOutboxAdministration: count / list-dead-letters / requeue / purge, fanned out per owned data source. [Rubric §8: Data Architecture] (assesses how a system handles database-per-service and multi-tenant fan-out): every operation here resolves its own target set rather than assuming a single database. [Rubric §13: Observability & Operability]: requeue and purge are explicit, logged operator actions, not silent background behavior.
      • -
      • Walkthrough: MaxPageSize (InternalCommandAdministration.cs:471, 500) bounds one page so an admin call cannot pull the whole table; SkipError/TakeError/OlderThanError (InternalCommandAdministration.cs:473-480) are pre-built Error.Validation instances; CountPendingAsync (InternalCommandAdministration.cs:486-507) sums a LongCountAsync per target using the SAME pending predicate as the processor's own poll; ListDeadLettersAsync (InternalCommandAdministration.cs:510-566) paginates across the MERGED result of every target: each target returns at most skip + take rows (ordered by ScheduledOn then Id), because "skip 50" must mean the same thing whether this host owns one database or four, then the merged list is re-ordered and re-paged in memory (InternalCommandAdministration.cs:560-563); RequeueAsync (InternalCommandAdministration.cs:569-624) resets Attempts to 0 and clears DeadLetteredOn/ClaimedUntil/ClaimedBy via ExecuteUpdateAsync (InternalCommandAdministration.cs:599-605) while deliberately preserving LastError ("the only evidence of WHY this row needed requeuing"), then calls signal.Signal() (InternalCommandAdministration.cs:620) to wake the processor immediately instead of waiting for the next poll; PurgeProcessedAsync (InternalCommandAdministration.cs:627-662) ExecuteDeleteAsyncs processed rows older than a caller-supplied cutoff; SelectTargets (InternalCommandAdministration.cs:674-686) resolves its target list through the shared TenantDataSourceTargets.ExpandRelational helper (also used by InternalCommandCleanupService and InternalCommandProcessor), optionally narrowed to one name; VisitAsync<T> (InternalCommandAdministration.cs:693-704) opens a fresh DI scope via the shared scopeFactory.CreateTenantScope(target) extension, which sets the tenant BEFORE the IDbContextFactory context is resolved, because the tenant is both what routes the scoped factory to the right database and what the soft-delete/tenant query filter reads; the constructor's timeProvider parameter is now a plain required parameter rather than an optional one defaulting to TimeProvider.System (InternalCommandAdministration.cs:467,483).
      • -
      • Why it's built this way: the tenant-before-context ordering and the deliberate LastError preservation are both stated inline as design decisions, not incidental; LogRequeued/LogPurged (InternalCommandAdministration.cs:706-710) give operators a durable audit trail of manual interventions on the queue.
      • -
      • Where it's used: registered in DependencyInjection.cs (1 site); referenced from ApplicationDbContext.cs (1); exercised by InternalCommandAdministrationTests.cs (4); SoftDeleteEnforcementTests in both MMCA.Common.Architecture.Tests and MMCA.ADC.Architecture.Tests (1 each) check its hard ExecuteDeleteAsync/ExecuteUpdateAsync calls against the restrict-delete-by-default convention.
      • +
      • Walkthrough: the primary constructor (InternalCommandAdministration.cs:34-40) takes six required parameters, with target selection delegated to an injected FrameworkTableTargets rather than a registry, a resolver and optional tenancy options; MaxPageSize (InternalCommandAdministration.cs:43, 500) bounds one page so an admin call cannot pull the whole table; SkipError/TakeError/OlderThanError (InternalCommandAdministration.cs:45-52) are pre-built Error.Validation instances; every operation returns Error.NotFoundError from UnknownSourceError (InternalCommandAdministration.cs:236-239) when the requested source name matches no owned target; CountPendingAsync (InternalCommandAdministration.cs:58-79) sums a LongCountAsync per target using the processor's pending predicate (ProcessedOn and DeadLetteredOn null, Attempts < MaxAttempts); ListDeadLettersAsync (InternalCommandAdministration.cs:82-138) paginates across the MERGED result of every target: each target returns at most skip + take rows (ordered by ScheduledOn then Id), because "skip 50" must mean the same thing whether this host owns one database or four, then the merged list is re-ordered and re-paged in memory (InternalCommandAdministration.cs:132-135); RequeueAsync (InternalCommandAdministration.cs:141-196) resets Attempts to 0 and clears DeadLetteredOn/ClaimedUntil/ClaimedBy via ExecuteUpdateAsync (InternalCommandAdministration.cs:171-177) while deliberately preserving LastError ("the only evidence of WHY this row needed requeuing"), then calls signal.Signal() (InternalCommandAdministration.cs:192) to wake the processor immediately instead of waiting for the next poll; PurgeProcessedAsync (InternalCommandAdministration.cs:199-234) ExecuteDeleteAsyncs processed rows at or before a caller-supplied cutoff; SelectTargets (InternalCommandAdministration.cs:246-247) is a one-line call to tableTargets.Named(DataSource, DatabaseName, dataSource), recomputed per call because module assemblies can register entities after startup, and optionally narrowed to one name; VisitAsync<T> (InternalCommandAdministration.cs:254-265) opens a fresh DI scope via the shared scopeFactory.CreateTenantScope(target) extension (InternalCommandAdministration.cs:261), which sets the tenant BEFORE the IDbContextFactory context is resolved, because the tenant is both what routes the scoped factory to the right database and what the query filter reads.
      • +
      • Why it's built this way: the tenant-before-context ordering and the deliberate LastError preservation are both stated inline as design decisions, not incidental; LogRequeued/LogPurged (InternalCommandAdministration.cs:267-271) give operators a durable audit trail of manual interventions on the queue.
      • +
      • Where it's used: registered in DependencyInjection.Jobs.cs (1 site); referenced from ApplicationDbContext.cs (1); exercised by InternalCommandAdministrationTests.cs (4); SoftDeleteEnforcementTests in both MMCA.Common.Architecture.Tests and MMCA.ADC.Architecture.Tests (1 each) check its hard ExecuteDeleteAsync/ExecuteUpdateAsync calls against the restrict-delete-by-default convention.
      • ADRs: ADR-119.

      InternalCommandCleanupService

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandCleanupService.cs:44 · Level 13 · class

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.InternalCommands.Administration · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandCleanupService.cs:40 · Level 16 · class

      • What it is: a PeriodicBackgroundService that periodically purges completed and dead-lettered InternalCommands rows past their retention windows, across every relational data source (and tenant) this host owns.
      • -
      • Depends on: PeriodicBackgroundService, IServiceScopeFactory, IOptions<InternalCommandsSettings> (InternalCommandsSettings), IEntityDataSourceRegistry, IDataSourceResolver, TimeProvider, IOptions<TenancySettings>, ApplicationDbContext, InternalCommandMessage, TenantDataSourceTarget / TenantDataSourceTargets.
      • +
      • Depends on: PeriodicBackgroundService, IServiceScopeFactory, IOptions<InternalCommandsSettings> (InternalCommandsSettings), FrameworkTableTargets, TimeProvider, IDbContextFactory, ApplicationDbContext, InternalCommandMessage, TenantDataSourceTarget.
      • Concept: the same "sweep every owned target, isolate one source's failure from the rest" shape as InternalCommandAdministration's own purge path; cross-reference rather than re-teach the tenant-before-context ordering. [Rubric §29: Resilience & Business Continuity]: an unreachable database on one source does not stop the sweep of the others. The hour-scale loop mechanics themselves (interval wait, cancellation, error logging) now come from the shared PeriodicBackgroundService base rather than being hand-rolled here, so this section covers only what this type overrides.
      • -
      • Walkthrough: the type now extends PeriodicBackgroundService(timeProvider, logger) (InternalCommandCleanupService.cs:44-52) instead of BackgroundService directly; it overrides Interval (InternalCommandCleanupService.cs:55, TimeSpan.FromHours(CleanupIntervalHours)), StartupDelay (InternalCommandCleanupService.cs:57-61, equal to Interval, so the first sweep still waits one full interval before competing with startup or migration work), IsEnabled (InternalCommandCleanupService.cs:64-76, false and logs LogCleanupDisabled when RetentionDays <= 0), ExecuteCycleAsync (InternalCommandCleanupService.cs:79, forwards straight to PurgeAsync), and LogCycleFailure (InternalCommandCleanupService.cs:82, forwards to LogCleanupError); PurgeAsync (InternalCommandCleanupService.cs:89-133, internal so a test can drive one sweep without advancing the hour-scale timer) computes cutoff = now - RetentionDays and a separate deadLetterCutoff using DeadLetterRetentionDays when set, else RetentionDays (InternalCommandCleanupService.cs:91-97); for each target from GetTargets (InternalCommandCleanupService.cs:160-166, now built through the shared TenantDataSourceTargets.ExpandRelational helper rather than its own inline source-resolution logic) it opens a scope via scopeFactory.CreateTenantScope(target) (InternalCommandCleanupService.cs:105, sets the tenant before the context is resolved), resolves the ApplicationDbContext, ExecuteDeleteAsyncs processed rows past cutoff (InternalCommandCleanupService.cs:110-113, LogPurged), then calls SweepDeadLettersAsync (InternalCommandCleanupService.cs:139-154) which deletes rows keyed on DeadLetteredOn < deadLetterCutoff (LogDeadLetterPurged); a per-source exception is caught and logged via LogSourcePurgeError rather than aborting the whole sweep.
      • +
      • Walkthrough: the type extends PeriodicBackgroundService(timeProvider, logger) (InternalCommandCleanupService.cs:40-46) and takes five required constructor parameters, with target selection delegated to an injected FrameworkTableTargets; it overrides Interval (InternalCommandCleanupService.cs:55, TimeSpan.FromHours(CleanupIntervalHours)), StartupDelay (InternalCommandCleanupService.cs:61, equal to Interval, so the first sweep waits one full interval before competing with startup or migration work), IsEnabled (InternalCommandCleanupService.cs:64-76, false and logs LogCleanupDisabled when RetentionDays <= 0), ExecuteCycleAsync (InternalCommandCleanupService.cs:79, forwards straight to PurgeAsync), and LogCycleFailure (InternalCommandCleanupService.cs:82, forwards to LogCleanupError); PurgeAsync (InternalCommandCleanupService.cs:89-133, internal so a test can drive one sweep without advancing the hour-scale timer) computes cutoff = now - RetentionDays and a separate deadLetterCutoff using DeadLetterRetentionDays when set, else RetentionDays (InternalCommandCleanupService.cs:91-97); for each target from GetTargets (InternalCommandCleanupService.cs:160-161, a one-line call to tableTargets.Relational(DataSource, DatabaseName), the same set the processor drains) it opens a scope via scopeFactory.CreateTenantScope(target) (InternalCommandCleanupService.cs:105, sets the tenant before the context is resolved), resolves the ApplicationDbContext through IDbContextFactory.GetDbContext(target.Source), ExecuteDeleteAsyncs processed rows past cutoff (InternalCommandCleanupService.cs:110-113, LogPurged), then calls SweepDeadLettersAsync (InternalCommandCleanupService.cs:139-154) which deletes rows keyed on DeadLetteredOn < deadLetterCutoff (LogDeadLetterPurged); a per-source exception is caught and logged via LogSourcePurgeError rather than aborting the whole sweep.
      • Why it's built this way: keying the dead-letter sweep on DeadLetteredOn rather than a completion stamp reflects that a dead-lettered row never got one; the independent DeadLetterRetentionDays window lets operators keep abandoned commands around longer for diagnosis than ordinary completed ones; moving onto PeriodicBackgroundService retires a hand-rolled interval loop that was nearly identical to the outbox and internal-commands processors' own loops.
      • -
      • Where it's used: registered as a hosted service alongside InternalCommandProcessor in DependencyInjection.cs (1 site); its retention posture is what InternalCommandsDisabledNoticeService's log message calls out as the second job a Enabled=false host does not run; exercised directly by InternalCommandCleanupServiceTests.cs (4).
      • +
      • Where it's used: registered as a hosted service alongside InternalCommandProcessor in DependencyInjection.Jobs.cs (1 site); its retention posture is what InternalCommandsDisabledNoticeService's log message calls out as the second job a Enabled=false host does not run; exercised directly by InternalCommandCleanupServiceTests.cs (4).
      • ADRs: ADR-114.

      InternalCommandProcessor

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/InternalCommandProcessor.cs:46 · Level 13 · class

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.InternalCommands.Processing · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/InternalCommandProcessor.cs:43 · Level 16 · class

      • What it is: the BackgroundService at the center of the internal-commands job queue: it drains due rows across every relational data source (and tenant) this host owns, executes each via InternalCommandDispatcher, and records the outcome, using a smart wait instead of a fixed polling interval.
      • -
      • Depends on: BackgroundService, PollingLoop, IServiceScopeFactory, IOptions<InternalCommandsSettings> (InternalCommandsSettings), IInternalCommandSignal, IEntityDataSourceRegistry, IDataSourceResolver, TimeProvider, IOptions<TenancySettings>, InternalCommandCycleResult, InternalCommandMetrics, InternalCommandDispatcher, InternalCommandMessage, TenantDataSourceTarget / TenantDataSourceTargets, ApplicationDbContext, System.Diagnostics.ActivitySource.
      • +
      • Depends on: BackgroundService, PollingLoop, IServiceScopeFactory, IOptions<InternalCommandsSettings> (InternalCommandsSettings), IInternalCommandSignal, FrameworkTableTargets, TimeProvider, InternalCommandCycleResult, InternalCommandMetrics, InternalCommandDispatcher, InternalCommandMessage, TenantDataSourceTarget, IDbContextFactory, ApplicationDbContext, System.Diagnostics.ActivitySource.
      • Concept: the smart-wait polling loop itself now lives in the shared PollingLoop (cross-reference rather than re-teach): this type supplies its own settings, signal, and per-source work, and PollingLoop runs the loop, computes the wait, and aggregates targets. [Rubric §12: Performance & Scalability]: instead of a fixed poll interval, the loop computes how long until the earliest known upcoming row is due, floors that to avoid a hot loop, caps it at the configured fallback interval, and lets IInternalCommandSignal wake it early. [Rubric §13: Observability & Operability]: poll spans are emitted under their own suppressible activity so an idle fleet's constant polling does not dominate telemetry ingestion, and InternalCommandMetrics is fed every cycle.
      • -
      • Walkthrough: constants: PollActivityName (InternalCommandProcessor.cs:1259, must stay in sync with OutboxPollFilterProcessor in MMCA.Common.Aspire, which has no project reference here and so duplicates the string), MaxErrorLength (InternalCommandProcessor.cs:1262, 4000, the column width LastError is truncated to), PrincipalAuthenticationType (InternalCommandProcessor.cs:1268, the auth type stamped on the identity rebuilt from a claimed row); the loop's own MinimumWait/StartupDelay constants and the hand-rolled ExecuteAsync loop body are gone: ExecuteAsync (InternalCommandProcessor.cs:1277-1291) now just calls PollingLoop.RunAsync, passing GetTargets().Count > 0 as hasTargets, LogNoRelationalSources as onNoTargets, a lambda that runs ProcessDueCommandsAsync and maps its result to the (HasMoreWork, EarliestUpcoming) tuple PollingLoop expects, LogCycleError as onCycleError, the configured delay/interval seconds, and signal.WaitAsync as the wait delegate; ComputeWaitTime (InternalCommandProcessor.cs:1303-1308) is now a one-line forward to PollingLoop.ComputeWaitTime, kept as a wrapper so its own unit tests still compile against this type; GetTargets (InternalCommandProcessor.cs:1316-1322, the standalone GetSources helper is gone) now resolves directly through TenantDataSourceTargets.ExpandRelational, recomputed every cycle so a module assembly registered after startup is picked up; ProcessDueCommandsAsync (InternalCommandProcessor.cs:1332-1348, internal for direct test invocation) now forwards the per-target fan-out to PollingLoop.DrainAllAsync, supplying ProcessSourceAsync as the per-target delegate and LogSourceError as the per-source error handler, then publishes the aggregated depth through InternalCommandMetrics.SetPendingDepth; ProcessSourceAsync per target still opens a DI scope, sets the tenant before resolving the ApplicationDbContext (same ordering rule as the admin and cleanup types), fetches up to BatchSize runnable rows via FetchCandidatesAsync, derives the pending-depth gauge input from that same fetch via CountPendingAsync (no extra query unless the batch is saturated), publishes the oldest-due-age gauge from the fetch's own first row (no extra query), splits the ordered rows into a due prefix and an upcoming remainder, returns early with no claim when nothing is due, otherwise claims the due prefix under a fresh claimToken and executes each claimed row, reporting HasMoreDueWork when the due count exactly filled BatchSize and at least one row progressed (so a saturated batch triggers an immediate re-poll); FetchCandidatesAsync runs inside its own suppressible Activity, queries AsNoTracking rows with ProcessedOn/DeadLetteredOn null, Attempts < MaxAttempts, and no unexpired ClaimedUntil, ordered by ScheduledOn then Id; CountPendingAsync short-circuits to the fetched row count when it is below BatchSize (the fetch already saw the whole backlog) and pays for a COUNT query only when the batch is saturated.
      • +
      • Walkthrough: the primary constructor (InternalCommandProcessor.cs:43-49) takes six required parameters, with target selection delegated to an injected FrameworkTableTargets; constants: PollActivityName (InternalCommandProcessor.cs:57, "InternalCommandPoll", must stay in sync with OutboxPollFilterProcessor in MMCA.Common.Aspire, which has no project reference here and so duplicates the string), MaxErrorLength (InternalCommandProcessor.cs:60, 4000, the column width LastError is truncated to), PrincipalAuthenticationType (InternalCommandProcessor.cs:66, "InternalCommand", the auth type stamped on the identity rebuilt from a claimed row); ExecuteAsync (InternalCommandProcessor.cs:75-89) just calls PollingLoop.RunAsync, passing GetTargets().Count > 0 as hasTargets, LogNoRelationalSources as onNoTargets, a lambda that runs ProcessDueCommandsAsync and maps its result to the (HasMoreWork, EarliestUpcoming) tuple PollingLoop expects, LogCycleError as onCycleError, the configured delay/interval seconds, and signal.WaitAsync as the wait delegate; ComputeWaitTime (InternalCommandProcessor.cs:101-106) is a one-line forward to PollingLoop.ComputeWaitTime, kept as a wrapper so its own unit tests still compile against this type; GetTargets (InternalCommandProcessor.cs:114-115) is a one-line call to tableTargets.Relational(DataSource, DatabaseName), recomputed every cycle so a module assembly registered after startup is picked up; ProcessDueCommandsAsync (InternalCommandProcessor.cs:125-141, internal for direct test invocation) forwards the per-target fan-out to PollingLoop.DrainAllAsync, supplying ProcessSourceAsync as the per-target delegate and LogSourceError as the per-source error handler, then publishes the aggregated depth through InternalCommandMetrics.SetPendingDepth; ProcessSourceAsync (InternalCommandProcessor.cs:147-219) per target opens a DI scope via CreateTenantScope (InternalCommandProcessor.cs:153), so the tenant is set before the ApplicationDbContext is resolved through IDbContextFactory (same ordering rule as the admin and cleanup types), fetches up to BatchSize runnable rows via FetchCandidatesAsync, derives the pending-depth gauge input from that same fetch via CountPendingAsync (no extra query unless the batch is saturated), publishes the oldest-due-age gauge from the fetch's own first row (no extra query), splits the ordered rows into a due prefix and an upcoming remainder, returns early with no claim when nothing is due or when another replica claimed the whole prefix, otherwise claims the due prefix under a fresh claimToken and, for each claimed row, first calls RenewClaimAsync (InternalCommandProcessor.cs:204) and skips the row with LogLeaseLostBeforeExecution when the claim is no longer this replica's, else runs ExecuteClaimedAsync; it reports HasMoreDueWork when the due count exactly filled BatchSize and at least one row progressed (InternalCommandProcessor.cs:216, so a saturated batch triggers an immediate re-poll); FetchCandidatesAsync (InternalCommandProcessor.cs:229-248) runs inside its own suppressible Activity, queries AsNoTracking rows with ProcessedOn/DeadLetteredOn null, Attempts < MaxAttempts, and no unexpired ClaimedUntil, ordered by ScheduledOn then Id; CountPendingAsync (InternalCommandProcessor.cs:256-278) short-circuits to the fetched row count when it is below BatchSize (the fetch already saw the whole backlog) and pays for a COUNT query only when the batch is saturated; ClaimDueAsync (InternalCommandProcessor.cs:286-327) stamps ClaimedUntil = now + LeaseSeconds and ClaimedBy = claimToken on the due prefix in one guarded ExecuteUpdateAsync, and on a partial claim re-reads which ids carry this replica's token so only owned rows run; RenewClaimAsync (InternalCommandProcessor.cs:334-348) pushes ClaimedUntil forward by LeaseSeconds in a statement guarded on ClaimedBy == claimToken and returns whether a row was updated, which is what makes LeaseSeconds bound one handler rather than the whole batch; ExecuteClaimedAsync (InternalCommandProcessor.cs:355-426) dead-letters a row on the first attempt when its payload throws JsonException or NotSupportedException on deserialization (InternalCommandProcessor.cs:369-378, reason payload_invalid, logged by LogPayloadInvalid), when its type cannot be resolved (type_unresolvable), or when no handler is registered on this host (handler_missing), records lag and duration histograms around the invocation, completes the row on a successful Result, and otherwise hands off to RecordFailedAttemptAsync (InternalCommandProcessor.cs:435-462), which treats a Result.Failure and a thrown exception alike: dead-letter with attempts_exhausted once MaxAttempts is reached, else schedule a backoff retry.
      • Why it's built this way: the loop, wait-time computation, and per-target aggregation moved into PollingLoop because the outbox processor had nearly identical code; keeping ComputeWaitTime as a thin forwarding wrapper on this type avoids a breaking change to its own existing unit tests. An un-suppressed poll span would swamp telemetry for an idle fleet, and a COUNT on every cycle would be wasted work in the (common) unsaturated case. Cite ADR-114 for the queue itself; the executed handler goes through the same decorated ICommandHandler<TCommand, Result> pipeline any other command uses, per ADR-014.
      • -
      • Where it's used: registered as a hosted service in DependencyInjection.cs (1 site); ADC's ScoreEventSessionsInternalCommand (Conference.Application, 1 site) is a concrete IInternalCommand this processor drains; InternalCommandProcessorTests.cs and the shared test harness exercise ProcessDueCommandsAsync/ComputeWaitTime directly; OutboxPollFilterProcessor.cs (MMCA.Common.Aspire, 2 sites) reads PollActivityName to filter the matching spans from export.
      • -
      • Caveats / not-in-source: the claim-and-execute mechanics (ClaimDueAsync, ExecuteClaimedAsync) live further down the source file and were not re-read line-by-line for this section; sed -n over MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/InternalCommandProcessor.cs covers them for anyone who needs the claim/backoff details.
      • +
      • Where it's used: registered as a hosted service in DependencyInjection.Jobs.cs (1 site); ADC's ScoreEventSessionsInternalCommand (Conference.Application, 1 site) is a concrete IInternalCommand this processor drains; InternalCommandProcessorTests.cs and the shared test harness exercise ProcessDueCommandsAsync/ComputeWaitTime directly; OutboxPollFilterProcessor.cs (MMCA.Common.Aspire, 2 sites) reads PollActivityName to filter the matching spans from export.
      • +
      • Caveats / not-in-source: InvokeAsync, ScheduleRetryAsync, CompleteAsync and DeadLetterAsync (from InternalCommandProcessor.cs:464 onward, through the end of the type at InternalCommandProcessor.cs:712) were not re-read line-by-line for this section; read that range for the principal-restore and retry-write details.
      • ADRs: ADR-014, ADR-114.

      GroupedCount<TKey>

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Repositories · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:185 · Level 0 · record (private sealed, nested)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Repositories · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:186 · Level 0 · record (private sealed, nested)

        -
      • What it is: a two-property carrier for one GROUP BY row: the grouping key and the number of rows carrying it. It is declared private sealed record class inside EFReadRepository<TEntity, TIdentifierType>, so it exists only for the lifetime of one query translation and is invisible to every caller (EFReadRepository.cs:182-185).
      • -
      • Depends on: nothing first-party. TKey is the caller's grouping key type, constrained to notnull at the CountByAsync<TKey> declaration (EFReadRepository.cs:136).
      • -
      • Concept introduced, a named projection target so the provider can translate the grouping. [Rubric §12, Performance and Scalability] assesses whether aggregation happens in the database rather than after materialization. CountByAsync composes query.GroupBy(keySelector).Select(group => new GroupedCount<TKey>(group.Key, group.Count())) (EFReadRepository.cs:145-149), so the COUNT and the GROUP BY are both pushed to SQL and only one row per key comes back. The reason a named type appears here at all rather than an anonymous type or a tuple is translatability: EF Core needs a constructor-shaped projection it can bind by parameter name, and a positional record gives it exactly that with a single line of code. The result is flattened into an IReadOnlyDictionary<TKey, int> immediately afterwards (:146), so the record never escapes the method.
      • +
      • What it is: a two-property carrier for one GROUP BY row: the grouping key and the number of rows carrying it. It is declared private sealed record class inside EFReadRepository<TEntity, TIdentifierType>, so it exists only for the lifetime of one query translation and is invisible to every caller (EFReadRepository.cs:183-186).
      • +
      • Depends on: nothing first-party. TKey is the caller's grouping key type, constrained to notnull at the CountByAsync<TKey> declaration (EFReadRepository.cs:137).
      • +
      • Concept introduced, a named projection target so the provider can translate the grouping. [Rubric §12, Performance and Scalability] assesses whether aggregation happens in the database rather than after materialization. CountByAsync composes query.GroupBy(keySelector).Select(group => new GroupedCount<TKey>(group.Key, group.Count())) (EFReadRepository.cs:146-150), so the COUNT and the GROUP BY are both pushed to SQL and only one row per key comes back. The reason a named type appears here at all rather than an anonymous type or a tuple is translatability: EF Core needs a constructor-shaped projection it can bind by parameter name, and a positional record gives it exactly that with a single line of code. The result is flattened into an IReadOnlyDictionary<TKey, int> immediately afterwards (:146), so the record never escapes the method.
      • Walkthrough
          -
        • Key (EFReadRepository.cs:185): the grouping key, projected from group.Key.
        • -
        • Value (EFReadRepository.cs:185): the int row count, projected from group.Count().
        • +
        • Key (EFReadRepository.cs:186): the grouping key, projected from group.Key.
        • +
        • Value (EFReadRepository.cs:186): the int row count, projected from group.Count().
        • The record has no methods of its own. Being a positional record class, it gets value equality and a Deconstruct free, neither of which the one call site uses; the shape is the whole point.
      • Why it's built this way: nesting it privately inside the repository keeps a purely mechanical translation helper out of the assembly's type surface, while still giving EF a real named type to bind to. It is the counterpart of GroupedSum<TKey>, and the two are deliberately separate rather than one generic Grouped<TKey, TValue>, because the int and decimal aggregate shapes translate through different provider paths.
      • -
      • Where it's used: only in EFReadRepository.CountByAsync<TKey> (EFReadRepository.cs:147, flattened at :146). The public capability it backs is consumed in MMCA.ADC by BookmarkCountService (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/UserSessionBookmarks/Services/BookmarkCountService.cs:38) and GetAttendanceStatsHandler (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/CheckIns/UseCases/GetAttendanceStats/GetAttendanceStatsHandler.cs:32), and covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EFReadRepositoryReadSurfaceTests.cs.
      • +
      • Where it's used: only in EFReadRepository.CountByAsync<TKey> (EFReadRepository.cs:148, flattened at :146). The public capability it backs is consumed in MMCA.ADC by BookmarkCountService (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/UserSessionBookmarks/Services/BookmarkCountService.cs:38) and GetAttendanceStatsHandler (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/CheckIns/UseCases/GetAttendanceStats/GetAttendanceStatsHandler.cs:32), and covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EFReadRepositoryReadSurfaceTests.cs.

      GroupedSum<TKey>

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Repositories · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:190 · Level 0 · record (private sealed, nested)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Repositories · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:191 · Level 0 · record (private sealed, nested)

        -
      • What it is: the summing twin of GroupedCount<TKey>: one GROUP BY row carrying the key and the decimal total for that key (EFReadRepository.cs:187-190). Same nesting, same privacy, same single-method lifetime.
      • +
      • What it is: the summing twin of GroupedCount<TKey>: one GROUP BY row carrying the key and the decimal total for that key (EFReadRepository.cs:188-191). Same nesting, same privacy, same single-method lifetime.
      • Depends on: nothing first-party; see GroupedCount<TKey> for the concept.
      • -
      • Concept: introduced by GroupedCount<TKey>. What is worth reading here is the element selector on the grouping. SumByAsync calls query.GroupBy(keySelector, sumSelector) with two expressions and then group.Sum() with none (EFReadRepository.cs:173-177). The comment at :165-167 explains the choice: choosing the summed column inside the grouping keeps the caller's expression tree intact all the way to the provider, whereas summing over the grouping with a separately supplied expression would need that tree spliced in by hand. [Rubric §12, Performance and Scalability] again: the whole aggregation is one statement.
      • +
      • Concept: introduced by GroupedCount<TKey>. What is worth reading here is the element selector on the grouping. SumByAsync calls query.GroupBy(keySelector, sumSelector) with two expressions and then group.Sum() with none (EFReadRepository.cs:174-178). The comment at :165-167 explains the choice: choosing the summed column inside the grouping keeps the caller's expression tree intact all the way to the provider, whereas summing over the grouping with a separately supplied expression would need that tree spliced in by hand. [Rubric §12, Performance and Scalability] again: the whole aggregation is one statement.
      • Walkthrough
          -
        • Key (EFReadRepository.cs:190): the grouping key.
        • -
        • Value (EFReadRepository.cs:190): the decimal total, projected from group.Sum() over the element-selected column.
        • -
        • Flattened to IReadOnlyDictionary<TKey, decimal> at EFReadRepository.cs:179.
        • +
        • Key (EFReadRepository.cs:191): the grouping key.
        • +
        • Value (EFReadRepository.cs:191): the decimal total, projected from group.Sum() over the element-selected column.
        • +
        • Flattened to IReadOnlyDictionary<TKey, decimal> at EFReadRepository.cs:180.
      • Why it's built this way: identical rationale to its twin. decimal rather than a generic numeric parameter matches the framework's money and points columns, which are the values callers actually total.
      • -
      • Where it's used: only in EFReadRepository.SumByAsync<TKey> (EFReadRepository.cs:175). The capability is consumed in MMCA.ADC by GetLeaderboardHandler (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/Points/UseCases/GetLeaderboard/GetLeaderboardHandler.cs:55) and covered by EFReadRepositoryReadSurfaceTests.cs.
      • +
      • Where it's used: only in EFReadRepository.SumByAsync<TKey> (EFReadRepository.cs:176). The capability is consumed in MMCA.ADC by GetLeaderboardHandler (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/Points/UseCases/GetLeaderboard/GetLeaderboardHandler.cs:55) and covered by EFReadRepositoryReadSurfaceTests.cs.

      LookupRow<TId, TName>

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Repositories · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:720 · Level 0 · class (internal sealed)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Repositories · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:735 · Level 0 · class (internal sealed)

        -
      • What it is: the intermediate row a non-string lookup projection materializes: the entity's identifier plus the name property carried in its own CLR type, rather than pre-formatted to a string (EFReadRepository.cs:712-727). Declared at file scope in EFReadRepository.cs, alongside the class it serves, rather than nested private the way GroupedCount<TKey> and GroupedSum<TKey> are: EFReadRepository<TEntity, TIdentifierType>'s ExecuteRawLookupAsync<TName> is public static and needs LookupRow<TId, TName> in its own signature, which a private nested type could not appear in.
      • -
      • Depends on: nothing first-party. TId and TName are the caller's identifier and raw name types, closed over by EFReadRepository<TEntity, TIdentifierType>'s BuildLookupSelector (EFReadRepository.cs:353) and ExecuteRawLookupAsync<TName> (:282).
      • +
      • What it is: the intermediate row a non-string lookup projection materializes: the entity's identifier plus the name property carried in its own CLR type, rather than pre-formatted to a string (EFReadRepository.cs:727-742). Declared at file scope in EFReadRepository.cs, alongside the class it serves, rather than nested private the way GroupedCount<TKey> and GroupedSum<TKey> are: EFReadRepository<TEntity, TIdentifierType>'s ExecuteRawLookupAsync<TName> is public static and needs LookupRow<TId, TName> in its own signature, which a private nested type could not appear in.
      • +
      • Depends on: nothing first-party. TId and TName are the caller's identifier and raw name types, closed over by EFReadRepository<TEntity, TIdentifierType>'s BuildLookupSelector (EFReadRepository.cs:354) and ExecuteRawLookupAsync<TName> (:282).
      • Concept, why the projection stops short of a string. [Rubric §12, Performance and Scalability] assesses whether a translation failure is designed out rather than caught after the fact. GetAllForLookupAsync's selector used to call ToString() inside the Select for a non-string name property, which has no server-side translation for a value-object property (an Email, say): appending it to the projection threw InvalidOperationException for the whole query and surfaced as an HTTP 500 on a property QueryFieldService had already approved as sortable. LookupRow<TId, TName> is the fix: the projection stops at the raw value, in whatever CLR type EF materializes it through the property's own value converter, and the formatting to a display string happens in memory afterwards, in ExecuteRawLookupAsync<TName>, where ToString() is plain C# rather than an expression tree a provider has to translate.
      • Walkthrough
          -
        • Id (EFReadRepository.cs:723): the entity identifier, TId, defaulted to default! like the other lightweight projection carriers in this file.
        • -
        • Name (EFReadRepository.cs:726): the raw name value, TName?, exactly as the provider materialized it, with no coalescing or formatting applied at this stage.
        • +
        • Id (EFReadRepository.cs:738): the entity identifier, TId, defaulted to default! like the other lightweight projection carriers in this file.
        • +
        • Name (EFReadRepository.cs:741): the raw name value, TName?, exactly as the provider materialized it, with no coalescing or formatting applied at this stage.
      • -
      • Why it's built this way: a generic carrier rather than one record per name type is what lets BuildLookupSelector build the projection once per (TEntity, TName) pair by reflection (Expression.MemberInit over Expression.New(rowType), EFReadRepository.cs:353-357) without a compile-time-known type for every possible name property; sealed and two auto-properties keep it a pure carrier with nothing else to reason about.
      • -
      • Where it's used: only inside EFReadRepository.cs. BuildLookupSelector closes LookupRow<,> over (TIdentifierType, nameAccess.Type) for a non-string name property (EFReadRepository.cs:353); ExecuteRawLookupAsync<TName> takes the resulting Expression<Func<TEntity, LookupRow<TIdentifierType, TName>>> as its selector parameter (:282), projects, orders and caps with it, then formats each row's Name to the returned BaseLookup<TIdentifierType> (:292-296).
      • +
      • Why it's built this way: a generic carrier rather than one record per name type is what lets BuildLookupSelector build the projection once per (TEntity, TName) pair by reflection (Expression.MemberInit over Expression.New(rowType), EFReadRepository.cs:354-358) without a compile-time-known type for every possible name property; sealed and two auto-properties keep it a pure carrier with nothing else to reason about.
      • +
      • Where it's used: only inside EFReadRepository.cs. BuildLookupSelector closes LookupRow<,> over (TIdentifierType, nameAccess.Type) for a non-string name property (EFReadRepository.cs:354); ExecuteRawLookupAsync<TName> takes the resulting Expression<Func<TEntity, LookupRow<TIdentifierType, TName>>> as its selector parameter (:282), projects, orders and caps with it, then formats each row's Name to the returned BaseLookup<TIdentifierType> (:292-296).
      • Caveats / not-in-source: no test file targets LookupRow<TId, TName> by name; it is exercised indirectly through EFReadRepositoryLookupProjectionTests.cs (listed among EFReadRepository<TEntity, TIdentifierType>'s usage sites) whenever a non-string name property is looked up.

      ValueHolder<T>

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Repositories · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/KeysetQueryBuilder.cs:252 · Level 0 · class (private sealed, nested)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Repositories · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/KeysetQueryBuilder.cs:266 · Level 0 · class (private sealed, nested)

        -
      • What it is: a single-field carrier declared private sealed class ValueHolder<T>(T value) inside KeysetQueryBuilder, whose only member is a get-only Value property assigned from the primary constructor (KeysetQueryBuilder.cs:252-256).
      • +
      • What it is: a single-field carrier declared private sealed class ValueHolder<T>(T value) inside KeysetQueryBuilder, whose only member is a get-only Value property assigned from the primary constructor (KeysetQueryBuilder.cs:266-270).
      • Depends on: nothing. T is the caller's boundary-value type, supplied by KeysetQueryBuilder.Capture.
      • Concept introduced, making a literal look like a captured local so EF parameterizes it. [Rubric §12, Performance and Scalability] assesses whether a hot query path reuses compiled plans instead of minting a fresh one per call. A bare Expression.Constant is translated by every EF provider as an inlined literal, which is wrong for a keyset cursor boundary: the value changes on every page of every cursor, so each page would get its own statement text, missing EF's compiled-query cache and filling the server's plan cache with thousands of single-use plans. Reading the value off a member of a constant holder instance is the same expression shape the C# compiler emits for a captured local, so EF's parameter extraction evaluates the subtree once and binds the result as a query parameter instead, leaving the statement text identical across pages.
      • Walkthrough
          -
        • Value (KeysetQueryBuilder.cs:255): the one carried value, assigned once from the constructor parameter, never mutated.
        • +
        • Value (KeysetQueryBuilder.cs:269): the one carried value, assigned once from the constructor parameter, never mutated.
      • Why it's built this way: a whole class for one field is deliberate rather than minimal: KeysetQueryBuilder.Capture needs a MemberExpression reading a property off a ConstantExpression, and a generic single-property class is the smallest shape that produces one, closed over the boundary's declared type via MakeGenericType.
      • @@ -7165,7 +7905,7 @@

        UpdatePropertySetterBuilder<TEnti
      • Set<TProperty>(property, valueFactory) (UpdatePropertySetterBuilder.cs:31-40) is the computed-value overload: the second argument is itself an expression over the entity, which is how SetProperty(x => x.Count, x => x.Count + 1) style updates are expressed. Same guards, same recording, same fluent return.
      • IsEmpty (UpdatePropertySetterBuilder.cs:43) reports whether anything was described at all. EFRepository<TEntity, TIdentifierType> turns a true here into an ArgumentException rather than issuing a no-op UPDATE (EFRepository.cs:137-138).
      • SetsProperty(propertyName) (UpdatePropertySetterBuilder.cs:49) is the audit-stamping guard: it answers "did the caller already assign this column?" so the automatic LastModifiedOn and LastModifiedBy stamp never overwrites an explicit value.
      • -
      • Apply(builder) (UpdatePropertySetterBuilder.cs:52-58) is the replay: iterate the recorded actions, invoke each against EF's real setters builder. It is passed as a method group straight into EF (EFRepository.cs:153).
      • +
      • Apply(builder) (UpdatePropertySetterBuilder.cs:52-58) is the replay: iterate the recorded actions, invoke each against EF's real setters builder. It is passed as a method group straight into EF (EFRepository.cs:156).
      • TrackPropertyName (UpdatePropertySetterBuilder.cs:60-66) only records a name when the selector body is a MemberExpression, which is the shape of a simple e => e.Property lambda. A more complex selector contributes no name, so SetsProperty answers false for it and the audit stamp is applied. That is the safe direction of the two.
    488. @@ -7179,7 +7919,7 @@

      QueryTags

      • What it is: the shared helper that composes and applies EF Core query tags (TagWith), so a generated SQL statement can be traced back to the use case and the query-builder detail that produced it (QueryTags.cs:18-68).
      • -
      • Depends on: QueryTagScope for the ambient use-case half of the tag (Tag<TEntity>, QueryTags.cs:139), and EF Core's IQueryable<T>.TagWith extension. No other framework types.
      • +
      • Depends on: QueryTagScope for the ambient use-case half of the tag (Tag<TEntity>, QueryTags.cs:42), and EF Core's IQueryable<T>.TagWith extension. No other framework types.
      • Concept introduced, a two-half tag composed from an ambient scope and a local detail. [Rubric §13, Observability and Operability] assesses whether a slow query in a provider's query store can be traced back to the code that issued it. A tag has two independent halves: the ambient use case (set by QueryTagScope around a handler) and a detail the query-building code itself knows, such as which specification or which keyset sort ran. Compose (QueryTags.cs:55-67) handles all four presence combinations, joining both halves with a space when both are known, using whichever one is known alone, and returning null when neither is (:60-66). The null case is deliberate rather than an omission: Tag<TEntity> (:39-47) checks for it explicitly because EF throws on an empty tag, so an untagged query is the correct outcome, not a bug to guard against (:44-46).
      • Walkthrough
        • HandlerPrefix = "handler:" (QueryTags.cs:21), SpecificationPrefix = "spec:" (:24), and KeysetPrefix = "keyset:" (:27) are the three internal const prefixes that make a tag's origin recognizable at a glance in a query store.
        • @@ -7198,7 +7938,7 @@

          SpecificationEvaluator

          • What it is: the one place a specification becomes an IQueryable<T>. It always applies the specification's criteria, and when the specification is a QuerySpecification<TEntity, TIdentifierType> it also applies that specification's includes, ordering, and paging (SpecificationEvaluator.cs:11-21).
          • Depends on: ISpecification<TEntity, TIdentifierType> and QuerySpecification<TEntity, TIdentifierType> for the input shape, OrderExpression for each ordering key, IBaseEntity<TIdentifierType> as the entity constraint (SpecificationEvaluator.cs:44), QueryTags for tagging the query with the specification's type name, EF Core's Include and AsSplitQuery extensions, plus System.Reflection and System.Collections.Concurrent for the two caches (:1-6).
          • -
          • Concept introduced, the evaluator half of the specification pattern. [Rubric §2, Design Patterns] assesses whether patterns are implemented completely rather than named; the specification pattern has two halves, a declarative object describing what to select and an evaluator that turns it into a provider query, and this class is the second half. [Rubric §3, Clean Architecture] also applies: the specification types live in the Domain layer and know nothing about EF, so all EF knowledge is concentrated here. The most important thing to internalize is stated in the class doc at SpecificationEvaluator.cs:15-19, and it is a boundary, not an omission. Tracking and soft-delete scope are deliberately not applied here. Those two choices select the base queryable (Table versus TableNoTracking, with or without the named soft-delete filter dropped), which only a repository holding the DbContext can do. The evaluator composes on top of whatever base it is handed, which is why BaseQueryFor lives in EFReadRepository<TEntity, TIdentifierType> (EFReadRepository.cs:560-569) and not here.
          • +
          • Concept introduced, the evaluator half of the specification pattern. [Rubric §2, Design Patterns] assesses whether patterns are implemented completely rather than named; the specification pattern has two halves, a declarative object describing what to select and an evaluator that turns it into a provider query, and this class is the second half. [Rubric §3, Clean Architecture] also applies: the specification types live in the Domain layer and know nothing about EF, so all EF knowledge is concentrated here. The most important thing to internalize is stated in the class doc at SpecificationEvaluator.cs:15-19, and it is a boundary, not an omission. Tracking and soft-delete scope are deliberately not applied here. Those two choices select the base queryable (Table versus TableNoTracking, with or without the named soft-delete filter dropped), which only a repository holding the DbContext can do. The evaluator composes on top of whatever base it is handed, which is why BaseQueryFor lives in EFReadRepository<TEntity, TIdentifierType> (EFReadRepository.cs:575-584) and not here.
          • Walkthrough
            • Apply<TEntity, TIdentifierType>(source, specification, applyShape = true) (SpecificationEvaluator.cs:40-69) is the entry point. It applies specification.Criteria, then tags the resulting query through QueryTags with SpecificationPrefix + specification.GetType().Name (:52-54), tagging first so the label survives whatever shape composes on top of it, because EF carries tags on the query rather than on the operator they were attached to (:50-51). It then returns early when either applyShape is false or the specification is not a QuerySpecification (:56-57). Otherwise it layers includes, then ordering, then Skip when it is a positive integer, then Take (:59-66). Skip before Take, ordering before both: that is the only order in which paging is meaningful.
            • The applyShape parameter earns its own doc paragraph (SpecificationEvaluator.cs:30-35) and it is a real correctness argument, not a micro-optimization. Aggregate reads such as count and exists pass false, because joining includes in to count rows costs a join per navigation, and counting "page 3 of the matches" is never what a caller means.
            • @@ -7210,7 +7950,7 @@

              SpecificationEvaluator

          • Why it's built this way: internal static with no state beyond two caches makes it trivially safe to share across every repository instance in the process. The <remarks> at SpecificationEvaluator.cs:77-80 records the deliberate consolidation: EFReadRepository.ApplyIncludes delegates here rather than duplicating the logic, so the string-include path and the specification path cannot drift apart. The overall contract is ADR-055; the expectation that a specification's criteria translate on more than one engine is ADR-018.
          • -
          • Where it's used: exclusively inside this group. EFReadRepository<TEntity, TIdentifierType> calls Apply from FirstOrDefaultAsync(specification) (EFReadRepository.cs:125-126), from CountAsync(specification) with applyShape: false (:348-349), from both ListAsync overloads (:457-458, :474-475), and from GetPageByCursorAsync again with applyShape: false (:516); its ApplyIncludes is a thin forwarder to this class (:426-429). KeysetQueryBuilder calls ApplyOrderingStep three times (KeysetQueryBuilder.cs:77, :73, :74). Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/SpecificationEvaluatorTests.cs and .../EFReadRepositorySpecificationTests.cs.
          • +
          • Where it's used: exclusively inside this group. EFReadRepository<TEntity, TIdentifierType> calls Apply from FirstOrDefaultAsync(specification) (EFReadRepository.cs:126-127), from CountAsync(specification) with applyShape: false (:348-349), from both ListAsync overloads (:457-458, :474-475), and from GetPageByCursorAsync again with applyShape: false (:516); its ApplyIncludes is a thin forwarder to this class (:426-429). KeysetQueryBuilder calls ApplyOrderingStep three times (KeysetQueryBuilder.cs:77, :73, :74). Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/SpecificationEvaluatorTests.cs and .../EFReadRepositorySpecificationTests.cs.

          KeysetQueryBuilder

          @@ -7223,60 +7963,23 @@

          KeysetQueryBuilder

        • Walkthrough
          • TryResolveSortProperty<TEntity>(sortColumn, out property) (KeysetQueryBuilder.cs:35-47) returns true with a null property when no column was requested (paging by Id alone, :39-40), and otherwise does a case-insensitive public-instance property lookup (:42-44). A false return means the client named something that does not exist, which the caller turns into a validation failure rather than an exception.
          • ApplyOrdering<TEntity, TIdentifierType>(query, sortProperty, descending) (KeysetQueryBuilder.cs:62-82) first tags the query through QueryTags with KeysetPrefix + (sortProperty?.Name ?? "Id") (:71), so every keyset page, specification-driven or not, names itself in the generated SQL. It then builds an e => e.Id selector from the shared parameter (:73-74), then either orders by Id alone in the requested direction (:76-77) or orders by the sort key in the requested direction and then by Id always ascending (:79-81). The identifier tie-break always ascends, which is exactly what the seek predicate assumes.
          • -
          • BuildSeekPredicate<TEntity, TIdentifierType>(sortProperty, sortValue, lastId, descending) (KeysetQueryBuilder.cs:109-166) is the heart of the type, and it has four branches:
              -
            • No sort key (:114-119): the predicate is Id > lastId, flipped to < when the page descends, because there the identifier is the sort key.
            • -
            • Null boundary on a non-nullable key (:128-132): impossible by construction, so it degrades to the plain id seek rather than emitting a comparison against null.
            • -
            • Null boundary on a nullable key (:134-142): stated explicitly rather than left to SQL. Ascending, nulls sort first, so what remains is "any non-null value, or a null with a larger id"; descending, nulls sort last, so what remains is "a null with a larger id". The remarks at :87-93 give exactly this reasoning, and the reason it is needed: SQL comparisons against NULL are unknown and would silently drop rows.
            • -
            • Normal boundary (:145-158): the classic composite sort > v OR (sort == v AND Id > lastId) (:146-148), with the sort half flipped to < for a descending page. When the key is nullable and the page descends, an extra sort == null OR (...) is prepended (:150-156), because null < v is unknown and the nulls that sort last would otherwise vanish.
            • +
            • BuildSeekPredicate<TEntity, TIdentifierType>(sortProperty, sortValue, lastId, descending, nullsSortFirstAscending = true) (KeysetQueryBuilder.cs:118-180) is the heart of the type. The last parameter (:123) says where the engine puts nulls: SQL Server and SQLite sort them first ascending and last descending, PostgreSQL the reverse (ASC NULLS LAST, DESC NULLS FIRST), as the remarks at :102-106 state. Before any null handling the method folds the engine and the page direction into one flag, nullsFirst = nullsSortFirstAscending ? !descending : descending (:143-144), meaning "null keys come before the non-null ones in this page's traversal order". It then has four branches:
                +
              • No sort key (:131-136): the predicate is Id > lastId, flipped to < when the page descends, because there the identifier is the sort key.
              • +
              • Null boundary on a non-nullable key (:148-152): impossible by construction, so it degrades to the plain id seek rather than emitting a comparison against null.
              • +
              • Null boundary on a nullable key (:154-162): stated explicitly rather than left to SQL. Where nulls come first in the traversal, what remains is "any non-null value, or a null with a larger id"; where nulls come last, what remains is "a null with a larger id" (:156-160). The remarks at :94-101 give exactly this reasoning, and the reason it is needed: SQL comparisons against NULL are unknown and would silently drop rows.
              • +
              • Normal boundary (:165-179): the classic composite sort > v OR (sort == v AND Id > lastId) (:166-168), with the sort half flipped to < for a descending page. When the key is nullable and nulls come last in the traversal (!nullsFirst), an extra sort == null OR (...) is prepended (:170-177), because a comparison against null is unknown and the nulls still ahead would otherwise vanish. On SQL Server and SQLite that is the descending page; on PostgreSQL it is the ascending one.
            • -
            • Both boundary constants, the identifier (:119) and the sort key (:152), are built through the private Capture(value, type) (:241-247) rather than a bare Expression.Constant. Capture wraps the value in a ValueHolder<T> instance and returns a MemberExpression reading its Value property, which is the same shape the compiler emits for a captured local, so EF's parameter extraction binds it as a query parameter instead of inlining it as a literal. The null-boundary comparisons deliberately keep real Expression.Constant(null, ...) nodes instead, because IS NULL is a shape, not a value that changes per page.
            • -
            • ToInvariantString(value) (KeysetQueryBuilder.cs:175-185) renders a value for the cursor. The four date and time types use the round-trip "O" format specifically so a cursor never loses sub-second precision and re-seeks onto the wrong row (:161-165, :171-174); strings pass through (:175); anything IFormattable gets invariant formatting (:176); everything else falls back to ToString() (:177).
            • -
            • TryFromInvariantString(targetType, text, out value) (KeysetQueryBuilder.cs:194-218) is the inverse: unwrap Nullable<T> (:191), short-circuit for string (:192-196), otherwise use TypeDescriptor.GetConverter and ConvertFromInvariantString (:198-205), catching only FormatException, NotSupportedException, and ArgumentException and turning them into false (:207-210). A malformed cursor is a validation result, never an exception escaping the repository.
            • -
            • Compare(left, right, greaterThan) (KeysetQueryBuilder.cs:269-303) is the provider-translatability layer, and the remarks at :216-223 explain why it cannot just call Expression.GreaterThan: that factory only exists for types that have the operator, which excludes string. So strings compare through string.Compare(string, string), which EF translates into a native > or < (:228-234); types with a relational operator use it directly (:236-241); and anything else with an IComparable<T> implementation (a Guid key, for instance) compares through CompareTo (:243-257), whose translation is provider-specific. A type with none of the three gets a NotSupportedException naming the type (:246-248), which is a loud failure at query-build time rather than a wrong result.
            • -
            • SupportsRelationalOperator (KeysetQueryBuilder.cs:305-314) enumerates the primitive, enum, decimal, and date and time cases, and falls back to reflecting for a public static op_GreaterThan. StringCompareMethod and ZeroConstant (:271-274) are resolved once as statics.
            • +
            • Both boundary constants, the identifier (:129) and the sort key (:165), are built through the private Capture(value, type) (:255-261, reasoning in the remarks at :234-251) rather than a bare Expression.Constant. Capture wraps the value in a ValueHolder<T> instance and returns a MemberExpression reading its Value property, which is the same shape the compiler emits for a captured local, so EF's parameter extraction binds it as a query parameter instead of inlining it as a literal. The null-boundary comparisons deliberately keep real Expression.Constant(null, ...) nodes instead, because IS NULL is a shape, not a value that changes per page.
            • +
            • ToInvariantString(value) (KeysetQueryBuilder.cs:189-199) renders a value for the cursor. The four date and time types use the round-trip "O" format specifically so a cursor never loses sub-second precision and re-seeks onto the wrong row (:192-195); strings pass through (:196); anything IFormattable gets invariant formatting (:197); everything else falls back to ToString() (:198).
            • +
            • TryFromInvariantString(targetType, text, out value) (KeysetQueryBuilder.cs:208-232) is the inverse: unwrap Nullable<T> (:212), short-circuit for string (:213-217), otherwise use TypeDescriptor.GetConverter and ConvertFromInvariantString (:219-225, returning false up front when the converter cannot read a string, :220-221), catching only FormatException, NotSupportedException, and ArgumentException and turning them into false (:228-231). A malformed cursor is a validation result, never an exception escaping the repository.
            • +
            • Compare(left, right, greaterThan) (KeysetQueryBuilder.cs:283-317) is the provider-translatability layer, and the remarks at :272-282 explain why it cannot just call Expression.GreaterThan: that factory only exists for types that have the operator, which excludes string. So strings compare through string.Compare(string, string), which EF translates into a native > or < (:287-293); types with a relational operator use it directly (:295-300); and anything else with an IComparable<T> implementation (a Guid key, for instance) compares through CompareTo (:302-316), whose translation is provider-specific. A type with none of the three gets a NotSupportedException naming the type (:303-307), which is a loud failure at query-build time rather than a wrong result.
            • +
            • SupportsRelationalOperator (KeysetQueryBuilder.cs:319-328) enumerates the primitive, enum, decimal, date and time, and TimeSpan cases, and falls back to reflecting for a public static op_GreaterThan. StringCompareMethod and ZeroConstant (:330-333) are resolved once as statics.
          • Why it's built this way: keyset paging is declared as a repository-level capability, deliberately not part of the HTTP query contract of ADR-034, and ADR-055 records that split along with the cursor format. Building the predicate as an expression tree rather than as SQL text is what keeps the same code working on more than one provider (ADR-018); reusing SpecificationEvaluator's ordering step rather than reimplementing the ordering call means the keyset ORDER BY and the specification ORDER BY are produced by the same code.
          • -
          • Where it's used: only by EFReadRepository<TEntity, TIdentifierType>'s GetPageByCursorAsync, which calls TryResolveSortProperty (EFReadRepository.cs:624), ApplyOrdering (:533), ToInvariantString twice when encoding the next cursor (:548-549), and, through the private TryBuildSeekPredicate, TryFromInvariantString (:570, :579) and BuildSeekPredicate (:584-585). Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EFReadRepositoryKeysetPagingTests.cs.
          • -
          • Caveats / not-in-source: the NotSupportedException for an unorderable key type and the provider-specific translation of CompareTo are both real limits of the design, and neither is discoverable until a query is built for that key type. No first-party call site in MMCA.ADC or MMCA.Store calls GetPageByCursorAsync today (the only references outside MMCA.Common are the test-support fakes in MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Support/TestSupport.cs and its Identity sibling), so which key and sort types have actually been exercised against a real engine is established by the test suite rather than by production usage.
          • -
          -

          EFReadRepository<TEntity, TIdentifierType>

          -
          -

          MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Repositories · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:20 · Level 6 · class (internal)

          -
          -
            -
          • What it is: the EF Core implementation of IReadRepository<TEntity, TIdentifierType>, the read half of the repository contract: get by id, get many, projected reads, first-or-default, grouped counts and sums, soft-delete-aware finds, lookups, counts, existence checks, specification-driven reads, and keyset pages, with no mutation surface at all (EFReadRepository.cs:15-20).
          • -
          • Depends on: EF Core's DbContext and DbSet<TEntity>, taken as its one primary-constructor parameter (EFReadRepository.cs:20-32); IReadRepository<TEntity, TIdentifierType> as the contract (declared at MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:330); AuditableBaseEntity<TIdentifierType> as the entity constraint (:22); SpecificationEvaluator and KeysetQueryBuilder as its two composition helpers; ApplicationDbContext for the soft-delete filter name (:33); EntityQueryPipeline.MaxUnboundedResultLimit for the lookup row ceiling; LookupRow<TId, TName> as the intermediate projection shape for a non-string lookup name; and Result, Error, KeysetPageRequest, KeysetCollectionResult<T>, KeysetCursor, and BaseLookup<TIdentifierType> for the shapes it returns.
          • -
          • Concept introduced, naming the query filter you drop. [Rubric §11, Security] assesses whether isolation boundaries hold under every code path, and [Rubric §8, Data Architecture] assesses whether the query-filter design is deliberate. EF 10 gives global query filters names, and the framework registers two on the model: SoftDelete and Tenant (ADR-073). EF's parameterless IgnoreQueryFilters() drops all of them. That means a caller who only wanted to see soft-deleted rows would, with the parameterless call, also read across every tenant. So the repository declares a one-element array naming exactly the filter it is willing to drop (EFReadRepository.cs:34-40, resolving ApplicationDbContext.SoftDeleteFilterName from MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:469) and every ignoreQueryFilters: true path passes it (:52, :81, :102, :198, :288, :365, :379, :446). This is the kind of detail worth copying wherever you add a filter-bypassing read.
          • -
          • Concept, tracked versus no-tracking as an explicit repository decision. [Rubric §12, Performance and Scalability]. The four queryable properties at EFReadRepository.cs:511-520 are the vocabulary: Table (tracked), TableNoTracking, TableNoTrackingSingleQuery, and TableNoTrackingSplitQuery. Read paths default to no-tracking to avoid change-tracker overhead, but two exceptions are load-bearing and both are commented in place, see the walkthrough below.
          • -
          • Walkthrough
              -
            • MaxLookupSelectorCacheEntries = 512 (EFReadRepository.cs:30) bounds the lookup-selector cache; past it a selector is built per request rather than cached, which the source calls correct and only slightly slower.
            • -
            • The protected _context field is guarded at construction (EFReadRepository.cs:32), and Entities is a virtual property resolving _context.Set<TEntity>() (:35). Every member goes through one of those two, which is what makes the class subclassable by EFRepository<TEntity, TIdentifierType>.
            • -
            • A private BaseQuery(asTracking) helper now backs every read path that used to write out asTracking ? Table : TableNoTracking inline (GetAllAsync, GetProjectedAsync, FirstOrDefaultAsync(where, ...), FindIncludingDeletedAsync, GetAllForLookupAsync, GetByIdsAsync, GetByIdAsync(id, includes, asTracking)); CountByAsync and SumByAsync call it with asTracking: false explicitly. Consolidating the ternary in one place is what keeps the tracked-versus-untracked decision consistent as new read members are added.
            • -
            • GetAllAsync (EFReadRepository.cs:45-71) is the widest read: pick tracked or untracked (:47-49), optionally drop the named soft-delete filter (:51-52), apply includes (:54), then optional where (:56-57), then optional orderBy (:59-60), then materialize with or without a select (:62-65). Every parameter is optional, which is what makes it the general-purpose read the older query paths call.
            • -
            • GetProjectedAsync<TResult> (EFReadRepository.cs:74-92) is the projection-first read: no includes at all, because a projection selects its own columns, and the Select is applied in the database rather than after materialization (:86).
            • -
            • FirstOrDefaultAsync(where, includes, ...) (EFReadRepository.cs:95-114) is the predicate overload, and the comment at :107 records the point: the predicate goes into EF's FirstOrDefaultAsync so the database issues a TOP 1, rather than materializing a set and narrowing it in memory.
            • -
            • FirstOrDefaultAsync(specification) (EFReadRepository.cs:117-129) is the specification overload, and unlike the aggregate reads it applies the full shape, ordering included. The comment at :118-119 is the reason: "first" is only meaningful against a defined order, and the specification is where that order is declared.
            • -
            • CountByAsync<TKey> (EFReadRepository.cs:132-152) and SumByAsync<TKey> (:150-175) are the two grouped aggregates. Both run untracked, apply the optional predicate, group in the database, and project into the private records GroupedCount<TKey> (:142) and GroupedSum<TKey> (:170) before flattening to a dictionary (:146, :174). The comment at :165-167 explains why SumByAsync passes the sum selector as GroupBy's element selector: it keeps the caller's expression tree intact all the way to the provider.
            • -
            • FindIncludingDeletedAsync (EFReadRepository.cs:193-219) returns a named tuple of active and soft-deleted matches. The comment at :196-197 is the design point: it drops the soft-delete filter and reads once, then partitions in memory (:205-211), because two separate queries would let a concurrent delete land between them and report the same row in neither half.
            • -
            • GetAllForLookupAsync (EFReadRepository.cs:222-261) returns id and name pairs ordered by name, where "name" is a runtime property name. GetOrBuildLookupSelector (:320-341) now returns an untyped LambdaExpression rather than a fixed Expression<Func<TEntity, BaseLookup<TIdentifierType>>>, because BuildLookupSelector (:343-374) builds one of two projection shapes depending on the resolved property's CLR type: a string property still projects straight to BaseLookup<TIdentifierType> with its name coalesced to empty (:365-373), while anything else projects to LookupRow<TId, TName> carrying the raw value (:349-363), because appending ToString() to the projection has no server-side translation for a value-object property and used to throw InvalidOperationException for the whole query. GetAllForLookupAsync pattern-matches the returned LambdaExpression (:240): the string leg runs the original Select/OrderBy/Take inline (:242-247); the non-string leg resolves the raw name's CLR type by reflection (:255) and dispatches, through the cached ExecuteRawLookupMethod (:267-268), to the public static ExecuteRawLookupAsync<TName> (:280-297), which runs the same ordered, capped projection against LookupRow<TIdentifierType, TName> and formats each row's name in memory afterward (:292-296). Both legs keep .Take(EntityQueryPipeline.MaxUnboundedResultLimit) (:245, :288) BEFORE materialization, and the comment above the branch names why (:235-239, tagged SEC-Common-24): this path never enters EntityQueryPipeline, so the framework's row ceiling has to be applied here or a lookup stays the one unpaginated, uncapped read in the framework. The cache underneath is unchanged in behavior: GetOrBuildLookupSelector first resolves the property name to its CANONICAL spelling (:322-326, tagged SEC-Store-14) before keying LookupSelectorCache (:313), checks the cache (:330-331), builds uncached rather than admitting a new entry once MaxLookupSelectorCacheEntries is reached (:335-336), and otherwise memoizes through GetOrAdd (:338-340). Canonicalizing the name first is a fix, not a cosmetic change: reflection resolves a property name case-insensitively, so every case permutation of a real column used to mint its own never-evicted cache entry, 2^N spellings of an N-letter name, from an anonymous endpoint; resolving the PropertyInfo first collapses them onto one entry.
            • -
            • GetByIdsAsync (EFReadRepository.cs:377-399) materializes the id sequence once, short-circuits on an empty set with an empty result (:281-283), and translates to a single Contains (in other words a SQL IN) rather than N round trips (:293).
            • -
            • GetByIdAsync(id) (EFReadRepository.cs:402-414) carries the single most important comment in the file (:303-307) and it encodes two separate bug fixes. First, it is a filtered query, not FindAsync: FindAsync serves a tracked instance straight out of the identity map without evaluating the global soft-delete filter, so an entity soft-deleted earlier in the same scope came back as if it were live. Second, it queries Table (tracked) on purpose: EFRepository<TEntity, TIdentifierType> inherits this member and the generic delete and update handlers load through it, mutate the instance, and save, so a no-tracking query would turn those writes into silent no-ops.
            • -
            • GetByIdAsync(id, includes, asTracking) (EFReadRepository.cs:417-430) is the include-carrying overload and defaults to no-tracking, because a caller asking for includes is normally reading for display.
            • -
            • The three CountAsync overloads (EFReadRepository.cs:433-457) cover no predicate, an expression predicate, and a specification. The specification overload composes through SpecificationEvaluator with applyShape: false (:348-349), so includes, ordering, and paging never reach a COUNT.
            • -
            • The two ExistsAsync overloads (EFReadRepository.cs:462-487) both funnel into the private AnyAsync helper (:394-400), which is provider-aware. The remarks at :387-393 are worth reading before you touch it: Cosmos gets CountAsync because its provider generates invalid SQL (an unresolved root identifier) when translating a predicated AnyAsync into a subquery; every other provider gets AnyAsync, which short-circuits at the first match. The cost of the workaround is stated honestly in the same remark: CountAsync reads every matching row, so on a wide predicate the Cosmos path is proportional to the number of matches. IsCosmosProvider (:402-403) is a provider-name test.
            • -
            • ApplyIncludes (EFReadRepository.cs:547-550) is a protected static forwarder to SpecificationEvaluator, including the collection-navigation split-query auto-switch. The doc at :417-425 says why: the logic lives once so the string-include path and the specification path cannot drift apart.
            • -
            • BaseQueryFor (EFReadRepository.cs:560-569) is the boundary SpecificationEvaluator refuses to cross. It reads AsTracking and IgnoreQueryFilters off a QuerySpecification<TEntity, TIdentifierType> when the specification is one, and gives a plain ISpecification the untracked, filtered default.
            • -
            • The two ListAsync overloads (EFReadRepository.cs:572-600) apply the specification to that base. In the projecting overload the comment at :472-473 records the ordering rule: Select comes last, so ordering and paging run over entity rows and only the resulting page is projected.
            • -
            • AnyAsync(specification) (EFReadRepository.cs:603-614) uses criteria only, through the same Cosmos-aware existence check the predicate overloads use (:489-492).
            • -
            • GetPageByCursorAsync (EFReadRepository.cs:617-674) is the keyset page, and it is the one read that returns a Result rather than a bare value, because two of its failures are caller errors rather than exceptions. An unknown sort column returns Error.InvalidEntityField with the column and type named (:503-512); a malformed cursor returns a validation error with code Error.InvalidCursor (:520-528). Between those it applies the optional specification with applyShape: false (:514-516), the seek predicate (:530), and the (sortKey, Id) ordering (:533). The Take(request.PageSize + 1) at :537 is the next-page probe, and the comment at :535-536 explains the choice: the extra row is never returned, it only says whether a next page exists, which is cheaper and more honest than a COUNT over the whole set. The probe row is trimmed (:539-541), and a next cursor is encoded from the last surviving row only when there is more (:543-550).
            • -
            • TryBuildSeekPredicate (EFReadRepository.cs:681-709) is the private decode-and-build: reject a cursor that fails KeysetCursor.TryDecode (:567), reject an id segment that does not parse to TIdentifierType (:570-574), reject a sort segment that does not parse to the sort property's type (:576-582), and otherwise hand the parsed values to KeysetQueryBuilder (:584-585).
            • -
            -
          • -
          • Why it's built this way: internal rather than public, and every member virtual, is what lets EFRepository<TEntity, TIdentifierType> extend it with writes while consumers hold only the interface. Concentrating the tracking and filter-scope decisions here and pushing pure composition into SpecificationEvaluator and KeysetQueryBuilder is what keeps this class about policy and those about mechanism. The contract and its evolution are recorded in ADR-055; the interface segregation into IEntityReader<TEntity, TIdentifierType> and IEntityQuerier<TEntity, TIdentifierType> beneath IReadRepository is what makes a read-only consumer unable to write at all (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:21, :80, :330).
          • -
          • Where it's used: constructed by RepositoryFactory's CreateReadOnly through a cached ActivatorUtilities factory taking the DbContext (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/Factory/RepositoryFactory.cs:55-56), optionally wrapped in EFReadRepositoryDecorator<TEntity, TIdentifierType> (:58-63). Application code reaches it through IUnitOfWork's GetReadRepository, implemented at MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:53-66. Covered by EFReadRepositoryReadSurfaceTests.cs, EFReadRepositoryKeysetPagingTests.cs, EFReadRepositorySpecificationTests.cs, EFReadRepositoryGetByIdFilterTests.cs, and EFReadRepositoryProjectedFilterTests.cs under MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/.
          • -
          • Caveats / not-in-source: GetAllForLookupAsync resolves nameProperty by reflection at runtime (EFReadRepository.cs:324-326), so an unknown name fails from the expression build rather than at compile time; the source contains no validation of that argument beyond what Expression.Property itself enforces. It is capped by EntityQueryPipeline.MaxUnboundedResultLimit on both the string and the raw-value leg (:245, :288), so unlike FindIncludingDeletedAsync, which still partitions in memory and materializes every matching row including the deleted ones with no upper bound on that set, a lookup can no longer read an unbounded table.
          • +
          • Where it's used: only by EFReadRepository<TEntity, TIdentifierType>'s GetPageByCursorAsync, which calls TryResolveSortProperty (EFReadRepository.cs:639), ApplyOrdering (:669), ToInvariantString twice when encoding the next cursor (:684-685), and, through the private TryBuildSeekPredicate, TryFromInvariantString (:706, :715) and BuildSeekPredicate (:720-721), passing nullsSortFirstAscending from the engine behind the context. Covered by EFReadRepositoryKeysetPagingTests.cs under MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Repositories/Read/, and by KeysetQueryBuilderSqlTests.cs and KeysetQueryBuilderNullOrderingTests.cs under MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Repositories/.
          • +
          • Caveats / not-in-source: the NotSupportedException for an unorderable key type and the provider-specific translation of CompareTo are both real limits of the design, and neither is discoverable until a query is built for that key type. nullsSortFirstAscending defaults to true (KeysetQueryBuilder.cs:123), so a caller that omits it gets SQL Server and SQLite null placement, which is the wrong side of the boundary for PostgreSQL; the one production caller always passes the engine's value (EFReadRepository.cs:720-721). No first-party call site in MMCA.ADC or MMCA.Store calls GetPageByCursorAsync today (the only references outside MMCA.Common are the test-support fakes in MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Support/TestSupport.cs and its Identity sibling), so which key and sort types have actually been exercised against a real engine is established by the test suite rather than by production usage.

          EFReadRepositoryDecorator<TEntity, TIdentifierType>

          @@ -7304,7 +8007,7 @@

          EFRepositoryDecorator<T
          • What it is: the read-write half of the profiling decorator. It extends EFReadRepositoryDecorator<TEntity, TIdentifierType> and adds forwarders for the mutation members of IRepository<TEntity, TIdentifierType>: add, update, row-version, execute-delete, and execute-update (EFRepositoryDecorator.cs:7-14).
          • Depends on: IRepository<TEntity, TIdentifierType>, its base class EFReadRepositoryDecorator<TEntity, TIdentifierType>, ProfilingHelper, IRowVersioned for the child-entity concurrency overload (EFRepositoryDecorator.cs:45), and IUpdatePropertySetter<TEntity> in the ExecuteUpdateAsync signature (:56).
          • -
          • Concept, decorating a derived contract by passing the same instance twice. [Rubric §1, SOLID]. The declaration is the interesting line: EFRepositoryDecorator(IRepository<...> inner) deriving from EFReadRepositoryDecorator<...>(inner) while implementing IRepository<...> (EFRepositoryDecorator.cs:14-18). The single inner argument is handed to the base constructor as an IReadRepository and stored again in this class's own _inner as an IRepository (:21). One object, two typed references: the inherited read members forward through the base's field, the write members through this one. That works precisely because IRepository<TEntity, TIdentifierType> extends IReadRepository<TEntity, TIdentifierType> and IWriteRepository<TEntity, TIdentifierType> (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:493), so the read decorator can be reused verbatim rather than re-forwarded.
          • +
          • Concept, decorating a derived contract by passing the same instance twice. [Rubric §1, SOLID]. The declaration is the interesting line: EFRepositoryDecorator(IRepository<...> inner) deriving from EFReadRepositoryDecorator<...>(inner) while implementing IRepository<...> (EFRepositoryDecorator.cs:14-18). The single inner argument is handed to the base constructor as an IReadRepository and stored again in this class's own _inner as an IRepository (:21). One object, two typed references: the inherited read members forward through the base's field, the write members through this one. That works precisely because IRepository<TEntity, TIdentifierType> extends IReadRepository<TEntity, TIdentifierType> and IWriteRepository<TEntity, TIdentifierType> (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:495), so the read decorator can be reused verbatim rather than re-forwarded.
          • Walkthrough
            • ClassName is "EFRepository" here (EFRepositoryDecorator.cs:20), so writes appear under the read-write repository's label while inherited reads keep the read repository's label.
            • Asynchronous writes follow the base's shape: AddAsync (:23-25), AddRangeAsync (:27-29), UpdateAsync (:31-33), ExecuteDeleteAsync (:48-52), and ExecuteUpdateAsync (:54-59).
            • @@ -7316,31 +8019,68 @@

              EFRepositoryDecorator<T
            • Why it's built this way: inheriting the read decorator rather than composing a second one avoids duplicating twenty-one forwarders, and keeps the two class-name labels distinct so a profile distinguishes a read issued through the write repository from one issued through a read-only repository.
            • Where it's used: applied by RepositoryFactory's Create when UseMiniProfiler is true (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/Factory/RepositoryFactory.cs:34-39, documented at :21-25). Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EFRepositoryDecoratorTests.cs and .../EFRepositoryDecoratorAdditionalTests.cs.
            • -
            • Caveats / not-in-source: there is no Save or SaveChangesAsync forwarder here, and none is missing: flushing is IUnitOfWork's job, not the repository's, so IWriteRepository<TEntity, TIdentifierType> declares no save member at all (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:367-480).
            • +
            • Caveats / not-in-source: there is no Save or SaveChangesAsync forwarder here, and none is missing: flushing is IUnitOfWork's job, not the repository's, so IWriteRepository<TEntity, TIdentifierType> declares no save member at all (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:369-482).
            • +

            +

            EFReadRepository<TEntity, TIdentifierType>

            +
            +

            MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Repositories · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:21 · Level 12 · class (internal)

            +
            +
              +
            • What it is: the EF Core implementation of IReadRepository<TEntity, TIdentifierType>, the read half of the repository contract: get by id, get many, projected reads, first-or-default, grouped counts and sums, soft-delete-aware finds, lookups, counts, existence checks, specification-driven reads, and keyset pages, with no mutation surface at all (EFReadRepository.cs:16-21).
            • +
            • Depends on: EF Core's DbContext and DbSet<TEntity>, taken as its one primary-constructor parameter (EFReadRepository.cs:21-33); IReadRepository<TEntity, TIdentifierType> as the contract (declared at MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:332); AuditableBaseEntity<TIdentifierType> as the entity constraint (:24); SpecificationEvaluator, KeysetQueryBuilder, and QueryTags as its composition helpers; ApplicationDbContext for the soft-delete filter name (:41) and for the engine behind the context, whose DataSourceEngineCapabilities drive the existence check and keyset null ordering (:513-514); EntityQueryPipeline.MaxUnboundedResultLimit for the lookup row ceiling; LookupRow<TId, TName> as the intermediate projection shape for a non-string lookup name; and Result, Error, KeysetPageRequest, KeysetCollectionResult<T>, KeysetCursor, and BaseLookup<TIdentifierType> for the shapes it returns.
            • +
            • Concept introduced, naming the query filter you drop. [Rubric §11, Security] assesses whether isolation boundaries hold under every code path, and [Rubric §8, Data Architecture] assesses whether the query-filter design is deliberate. EF 10 gives global query filters names, and the framework registers two on the model: SoftDelete and Tenant (ADR-073). EF's parameterless IgnoreQueryFilters() drops all of them. That means a caller who only wanted to see soft-deleted rows would, with the parameterless call, also read across every tenant. So the repository declares a one-element array naming exactly the filter it is willing to drop (EFReadRepository.cs:35-41, resolving ApplicationDbContext.SoftDeleteFilterName from MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:474) and every ignoreQueryFilters: true path passes it (:58, :87, :108, :204, :394, :471, :485, :582). This is the kind of detail worth copying wherever you add a filter-bypassing read.
            • +
            • Concept, tracked versus no-tracking as an explicit repository decision. [Rubric §12, Performance and Scalability]. The four queryable properties at EFReadRepository.cs:526-535 are the vocabulary: Table (tracked), TableNoTracking, TableNoTrackingSingleQuery, and TableNoTrackingSplitQuery. Read paths default to no-tracking to avoid change-tracker overhead, but two exceptions are load-bearing and both are commented in place, see the walkthrough below.
            • +
            • Walkthrough
                +
              • MaxLookupSelectorCacheEntries = 512 (EFReadRepository.cs:31) bounds the lookup-selector cache; past it a selector is built per request rather than cached, which the source calls correct and only slightly slower.
              • +
              • The protected _context field is guarded at construction (EFReadRepository.cs:33), and Entities is a virtual property resolving _context.Set<TEntity>() (:43). Every member goes through one of those two, which is what makes the class subclassable by EFRepository<TEntity, TIdentifierType>.
              • +
              • The private BaseQuery(asTracking) helper (EFReadRepository.cs:550-551) picks Table or TableNoTracking and tags the result through QueryTags with the ambient use-case tag. It backs every specification-less read (GetAllAsync, GetProjectedAsync, FirstOrDefaultAsync(where, ...), FindIncludingDeletedAsync, GetAllForLookupAsync, GetByIdsAsync, GetByIdAsync(id, includes, asTracking), and a specification-less keyset page); CountByAsync and SumByAsync call it with asTracking: false explicitly. Its doc (:537-547) records why Table and TableNoTracking stay untagged: they are the public, overridable extension point a consumer composes its own queries on, and the specification path is tagged inside SpecificationEvaluator instead, so no query is tagged twice.
              • +
              • GetAllAsync (EFReadRepository.cs:46-72) is the widest read: pick tracked or untracked (:55), optionally drop the named soft-delete filter (:57-58), apply includes (:60), then optional where (:62-63), then optional orderBy (:65-66), then materialize with or without a select (:68-71). Every parameter is optional, which is what makes it the general-purpose read the older query paths call.
              • +
              • GetProjectedAsync<TResult> (EFReadRepository.cs:75-93) is the projection-first read: no includes at all, because a projection selects its own columns, and the Select is applied in the database rather than after materialization (:92).
              • +
              • FirstOrDefaultAsync(where, includes, ...) (EFReadRepository.cs:96-115) is the predicate overload, and the comment at :113 records the point: the predicate goes into EF's FirstOrDefaultAsync so the database issues a TOP 1, rather than materializing a set and narrowing it in memory.
              • +
              • FirstOrDefaultAsync(specification) (EFReadRepository.cs:118-130) is the specification overload, and unlike the aggregate reads it applies the full shape, ordering included. The comment at :124-125 is the reason: "first" is only meaningful against a defined order, and the specification is where that order is declared.
              • +
              • CountByAsync<TKey> (EFReadRepository.cs:133-153) and SumByAsync<TKey> (:156-181) are the two grouped aggregates. Both run untracked, apply the optional predicate, group in the database, and project into the private records GroupedCount<TKey> (:148) and GroupedSum<TKey> (:176) before flattening to a dictionary (:152, :180). The comment at :171-173 explains why SumByAsync passes the sum selector as GroupBy's element selector: it keeps the caller's expression tree intact all the way to the provider.
              • +
              • FindIncludingDeletedAsync (EFReadRepository.cs:194-220) returns a named tuple of active and soft-deleted matches. The comment at :202-203 is the design point: it drops the soft-delete filter and reads once, then partitions in memory (:211-217), because two separate queries would let a concurrent delete land between them and report the same row in neither half.
              • +
              • GetAllForLookupAsync (EFReadRepository.cs:223-262) returns id and name pairs ordered by name, where "name" is a runtime property name. GetOrBuildLookupSelector (:321-342) returns an untyped LambdaExpression rather than a fixed Expression<Func<TEntity, BaseLookup<TIdentifierType>>>, because BuildLookupSelector (:344-375) builds one of two projection shapes depending on the resolved property's CLR type: a string property projects straight to BaseLookup<TIdentifierType> with its name coalesced to empty (:366-374), while anything else projects to LookupRow<TId, TName> carrying the raw value (:350-364), because appending ToString() to the projection has no server-side translation for a value-object property and throws InvalidOperationException for the whole query (the comment at :251-255). GetAllForLookupAsync pattern-matches the returned LambdaExpression (:241): the string leg runs Select/OrderBy/Take inline (:243-248); the non-string leg resolves the raw name's CLR type by reflection (:256) and dispatches, through the cached ExecuteRawLookupMethod (:268-269), to the public static ExecuteRawLookupAsync<TName> (:281-298), which runs the same ordered, capped projection against LookupRow<TIdentifierType, TName> and formats each row's name in memory afterward (:293-297). Both legs keep .Take(EntityQueryPipeline.MaxUnboundedResultLimit) (:246, :289) BEFORE materialization, and the comment above the branch names why (:236-240, tagged SEC-Common-24): this path never enters EntityQueryPipeline, so the framework's row ceiling has to be applied here or a lookup stays the one unpaginated, uncapped read in the framework. Underneath, GetOrBuildLookupSelector first resolves the property name to its CANONICAL spelling (:325-327, the SEC-Store-14 remark at :306-313) before keying LookupSelectorCache (:314), checks the cache (:331-332), builds uncached rather than admitting a new entry once MaxLookupSelectorCacheEntries is reached (:336-337), and otherwise memoizes through GetOrAdd (:339-341). Canonicalizing the name first is a fix, not a cosmetic change: reflection resolves a property name case-insensitively, so every case permutation of a real column used to mint its own never-evicted cache entry, 2^N spellings of an N-letter name, from an anonymous endpoint; resolving the PropertyInfo first collapses them onto one entry.
              • +
              • GetByIdsAsync (EFReadRepository.cs:378-400) materializes the id sequence once, short-circuits on an empty set with an empty result (:387-389), and translates to a single Contains (in other words a SQL IN) rather than N round trips (:399).
              • +
              • GetByIdAsync(id) (EFReadRepository.cs:403-415) carries the single most important comment in the file (:409-413) and it encodes two separate bug fixes. First, it is a filtered query, not FindAsync: FindAsync serves a tracked instance straight out of the identity map without evaluating the global soft-delete filter, so an entity soft-deleted earlier in the same scope came back as if it were live. Second, it queries Table (tracked) on purpose: EFRepository<TEntity, TIdentifierType> inherits this member and the generic delete and update handlers load through it, mutate the instance, and save, so a no-tracking query would turn those writes into silent no-ops.
              • +
              • GetByIdAsync(id, includes, asTracking) (EFReadRepository.cs:418-431) is the include-carrying overload and defaults to no-tracking, because a caller asking for includes is normally reading for display.
              • +
              • The three CountAsync overloads (EFReadRepository.cs:434-458) cover no predicate, an expression predicate, and a specification. The specification overload composes through SpecificationEvaluator with applyShape: false (:455), so includes, ordering, and paging never reach a COUNT.
              • +
              • The two ExistsAsync overloads (EFReadRepository.cs:463-488) both funnel into the private AnyAsync helper (:500-506), which is engine-aware. The remarks at :490-499 are worth reading before you touch it: the non-relational engine (Cosmos DB) gets CountAsync because its provider generates invalid SQL (an unresolved root identifier) when translating a predicated AnyAsync into a subquery; every relational engine gets AnyAsync, which short-circuits at the first match. The cost of the workaround is stated honestly in the same remark: CountAsync reads every matching row, so on a wide predicate the Cosmos path is proportional to the number of matches. The switch is a capability, not a provider-name test: EngineCapabilities (:513-514) reads DataSourceEngineCapabilities off the ApplicationDbContext's engine, and TranslatesAny (:517) is its IsRelational flag. A plain DbContext that is not a framework context (a directly constructed test double) yields null, which the doc at :508-512 reads as a relational engine that sorts nulls first, so both TranslatesAny and NullsSortFirstAscending (:523) fall back to true.
              • +
              • ApplyIncludes (EFReadRepository.cs:562-565) is a protected static forwarder to SpecificationEvaluator, including the collection-navigation split-query auto-switch. The doc at :553-561 says why: the logic lives once so the string-include path and the specification path cannot drift apart.
              • +
              • BaseQueryFor (EFReadRepository.cs:575-584) is the boundary SpecificationEvaluator refuses to cross. It reads AsTracking and IgnoreQueryFilters off a QuerySpecification<TEntity, TIdentifierType> when the specification is one, and gives a plain ISpecification the untracked, filtered default.
              • +
              • The two ListAsync overloads (EFReadRepository.cs:587-615) apply the specification to that base. In the projecting overload the comment at :608-609 records the ordering rule: Select comes last, so ordering and paging run over entity rows and only the resulting page is projected.
              • +
              • AnyAsync(specification) (EFReadRepository.cs:618-629) uses criteria only, through the same engine-aware existence check the predicate overloads use (:625-628).
              • +
              • GetPageByCursorAsync (EFReadRepository.cs:632-689) is the keyset page, and it is the one read that returns a Result rather than a bare value, because two of its failures are caller errors rather than exceptions. An unknown sort column returns Error.InvalidEntityField with the column and type named (:639-648); a malformed cursor returns a validation error with code Error.InvalidCursor (:654-664). Between those it starts from BaseQuery or applies the optional specification with applyShape: false (:650-652), then the seek predicate (:666), and the (sortKey, Id) ordering (:669). The Take(request.PageSize + 1) at :673 is the next-page probe, and the comment at :671-672 explains the choice: the extra row is never returned, it only says whether a next page exists, which is cheaper and more honest than a COUNT over the whole set. The probe row is trimmed (:675-677), and a next cursor is encoded from the last surviving row only when there is more (:679-686).
              • +
              • TryBuildSeekPredicate (EFReadRepository.cs:696-724) is the private decode-and-build, an instance method because it reads the engine's null ordering: reject a cursor that fails KeysetCursor.TryDecode (:703-704), reject an id segment that does not parse to TIdentifierType (:706-710), reject a sort segment that does not parse to the sort property's type (:712-718), and otherwise hand the parsed values to KeysetQueryBuilder together with nullsSortFirstAscending: NullsSortFirstAscending (:720-721). That flag is what makes a nullable sort key page correctly on PostgreSQL, which sorts nulls last ascending where SQL Server and SQLite sort them first.
              • +
              +
            • +
            • Why it's built this way: internal rather than public, and every member virtual, is what lets EFRepository<TEntity, TIdentifierType> extend it with writes while consumers hold only the interface. Concentrating the tracking and filter-scope decisions here and pushing pure composition into SpecificationEvaluator and KeysetQueryBuilder is what keeps this class about policy and those about mechanism. The contract and its evolution are recorded in ADR-055; the interface segregation into IEntityReader<TEntity, TIdentifierType> and IEntityQuerier<TEntity, TIdentifierType> beneath IReadRepository is what makes a read-only consumer unable to write at all (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:21, :80, :332).
            • +
            • Where it's used: constructed by RepositoryFactory's CreateReadOnly through a cached ActivatorUtilities factory taking the DbContext (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/Factory/RepositoryFactory.cs:55-56), optionally wrapped in EFReadRepositoryDecorator<TEntity, TIdentifierType> (:58-63). Application code reaches it through IUnitOfWork's GetReadRepository, implemented at MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:53-66. Covered by EFReadRepositoryReadSurfaceTests.cs, EFReadRepositoryKeysetPagingTests.cs, EFReadRepositorySpecificationTests.cs, EFReadRepositoryGetByIdFilterTests.cs, EFReadRepositoryProjectedFilterTests.cs, EFReadRepositoryLookupProjectionTests.cs, and EFReadRepositoryLookupSecurityTests.cs under MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Repositories/Read/.
            • +
            • Caveats / not-in-source: GetAllForLookupAsync resolves nameProperty by reflection at runtime (EFReadRepository.cs:325-327), so an unknown name fails from the expression build rather than at compile time; the source contains no validation of that argument beyond what Expression.Property itself enforces. It is capped by EntityQueryPipeline.MaxUnboundedResultLimit on both the string and the raw-value leg (:246, :289), so unlike FindIncludingDeletedAsync, which still partitions in memory and materializes every matching row including the deleted ones with no upper bound on that set, a lookup can no longer read an unbounded table.

            EFRepository<TEntity, TIdentifierType>

            -

            MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Repositories · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:23 · Level 9 · class (internal sealed)

            +

            MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Repositories · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:23 · Level 13 · class (internal sealed)

              -
            • What it is: the read-write EF Core repository. It extends EFReadRepository<TEntity, TIdentifierType> with add, update, concurrency-token, and set-based delete and update operations, implementing IRepository<TEntity, TIdentifierType> (EFRepository.cs:10-29). It stages changes and never saves: the class doc says so outright at :12.
            • -
            • Depends on: the base read repository, constructed with the same DbContext (EFRepository.cs:24, :27); a required TimeProvider (:25) and one optional constructor dependency, ICurrentUserService (:26); UpdatePropertySetterBuilder<TEntity> for set-based updates (:113); AuditableAggregateRootEntity<TIdentifierType> as the entity constraint (:28); and AuditableBaseEntity<TIdentifierType>, IRowVersioned, and IAuditableEntity for the concurrency and audit member names.
            • -
            • Concept introduced, the two write paths and why one of them must stamp its own audit fields. [Rubric §8, Data Architecture] assesses whether audit metadata is reliably captured, and [Rubric §12, Performance & Scalability] assesses whether a cross-cutting policy holds on every path rather than the common one. The framework's normal write path is change-tracked: load, mutate, save, and the save interceptors stamp LastModifiedOn and LastModifiedBy and dispatch domain events. The second path is set-based: ExecuteUpdate and ExecuteDelete issue one SQL statement over matching rows without loading or tracking them, which is far cheaper on a wide update but bypasses the save pipeline entirely, interceptors included. Rather than pretend the two paths are the same, this class closes the gap explicitly for audit fields on ExecuteUpdateAsync (EFRepository.cs:140-151). The class-level <remarks> (:17-22) states what the required TimeProvider and the optional ICurrentUserService are for: both serve ExecuteUpdateAsync's own audit stamping since that path bypasses the save pipeline, and without a user service (direct construction in tests) only the user stamp is skipped, since the clock has no fallback anymore.
            • +
            • What it is: the read-write EF Core repository. It extends EFReadRepository<TEntity, TIdentifierType> with add, update, concurrency-token, and set-based delete and update operations, implementing IRepository<TEntity, TIdentifierType> (EFRepository.cs:10-29). It stages changes and never saves: the class doc says so outright at :13.
            • +
            • Depends on: the base read repository, constructed with the same DbContext (EFRepository.cs:24, :27); a required TimeProvider (:25) and one optional constructor dependency, ICurrentUserService (:26); UpdatePropertySetterBuilder<TEntity> for set-based updates (:135); AuditableAggregateRootEntity<TIdentifierType> as the entity constraint (:28); and AuditableBaseEntity<TIdentifierType>, IRowVersioned, and IAuditableEntity for the concurrency and audit member names.
            • +
            • Concept introduced, the two write paths and why one of them must stamp its own audit fields. [Rubric §8, Data Architecture] assesses whether audit metadata is reliably captured, and [Rubric §12, Performance & Scalability] assesses whether a cross-cutting policy holds on every path rather than the common one. The framework's normal write path is change-tracked: load, mutate, save, and the save interceptors stamp LastModifiedOn and LastModifiedBy and dispatch domain events. The second path is set-based: ExecuteUpdate and ExecuteDelete issue one SQL statement over matching rows without loading or tracking them, which is far cheaper on a wide update but bypasses the save pipeline entirely, interceptors included. Rather than pretend the two paths are the same, this class closes the gap explicitly for audit fields on ExecuteUpdateAsync (EFRepository.cs:140-154). The class-level <remarks> (:17-22) states what the required TimeProvider and the optional ICurrentUserService are for: both serve ExecuteUpdateAsync's own audit stamping since that path bypasses the save pipeline. The code goes one step further than those remarks: with no current user (no user service, or a background or system scope with no user id), LastModifiedBy is stamped with the identifier type's default sentinel rather than left alone (:148-154), exactly as the save pipeline attributes a system save, because leaving the column untouched would keep crediting the change to the previous human editor.
            • Concept, the optional dependency as a testability affordance. [Rubric §14, Testability]. Only ICurrentUserService defaults to null (EFRepository.cs:26); TimeProvider is a required constructor argument (:25), so a test must pass one explicitly (typically TimeProvider.System or a fake), while production construction through ActivatorUtilities fills both from the container. Taking TimeProvider at all, rather than calling DateTime.UtcNow, is what makes the timestamp deterministic under test, which is exactly what EFRepositoryAuditStampTests exercises.
            • Walkthrough
              • AddAsync and AddRangeAsync (EFRepository.cs:32-43) are thin guarded forwarders to Entities.AddAsync and AddRangeAsync. Nothing is saved here; persistence happens at the unit of work's save.
              • -
              • UpdateAsync (EFRepository.cs:52-65) has the one non-obvious body in the mutation set. It first asks the change tracker whether this key is already tracked, and if so copies values onto the tracked entry with CurrentValues.SetValues instead of attaching a second instance, which would throw an "already tracked" exception (:44-50, :57-59). The comment at :55-56 explains the lookup choice: Entities.Local.FindEntry(entity.Id) is an O(1) key lookup against the identity map that never falls back to the database, replacing a linear scan of the LocalView. Untracked entities go through Entities.Update, which attaches and marks modified (:61). The method returns Task.CompletedTask (:63): it is asynchronous only for signature compatibility.
              • +
              • UpdateAsync (EFRepository.cs:52-65) has the one non-obvious body in the mutation set. It first asks the change tracker whether this key is already tracked, and if so copies values onto the tracked entry with CurrentValues.SetValues instead of attaching a second instance, which would throw an "already tracked" exception (:46-51, :58-60). The comment at :56-57 explains the lookup choice: Entities.Local.FindEntry(entity.Id) is an O(1) key lookup against the identity map that never falls back to the database, replacing a linear scan of the LocalView. Untracked entities go through Entities.Update, which attaches and marks modified (:62). The method returns Task.CompletedTask (:64): it is asynchronous only for signature compatibility.
              • UpdateRange (EFRepository.cs:68-72) is the guarded batch forwarder.
              • -
              • The two SetOriginalRowVersion overloads (EFRepository.cs:75-94) implement optimistic concurrency by writing the client's known row version into the tracked entry's original value, so EF's generated UPDATE carries it in the WHERE clause and a concurrent modification raises a concurrency exception instead of silently winning. Both null-guard their arguments and then assign unconditionally (:76-81, :87-92). The second overload takes an IRowVersioned child entity and casts to object for Entry (:90), which is how a child of the aggregate gets the same protection without being an aggregate root itself.
              • +
              • The two SetOriginalRowVersion overloads (EFRepository.cs:75-94) implement optimistic concurrency by writing the client's known row version into the tracked entry's original value, so EF's generated UPDATE carries it in the WHERE clause and a concurrent modification raises a concurrency exception instead of silently winning. Both null-guard their arguments and then assign unconditionally (:77-82, :88-93). The second overload takes an IRowVersioned child entity and casts to object for Entry (:91), which is how a child of the aggregate gets the same protection without being an aggregate root itself.
              • TouchConcurrencyToken(entity) (EFRepository.cs:97-115) forces a concurrency check on a root that a caller wants to protect even though nothing on it changed. It is a no-op unless the tracked entry's state is exactly Unchanged: a root the applier already modified emits its own UPDATE carrying the concurrency predicate already, and marking a property modified on a Deleted or Detached entry would be wrong rather than merely redundant. When the entry is unchanged, it marks the LastModifiedOn audit property IsModified = true, which is what puts the row in the generated UPDATE at all; the audit interceptor then fills in the real value, so the write is a genuine edit record rather than a no-op touch, and EF appends WHERE RowVersion = @original from the concurrency token.
              • -
              • ExecuteDeleteAsync (EFRepository.cs:118-124) is the set-based delete: Entities.Where(where).ExecuteDeleteAsync(...), returning the affected row count (:101).
              • -
              • ExecuteUpdateAsync (EFRepository.cs:127-154) is the flagship. It guards both arguments (:110-111), constructs an UpdatePropertySetterBuilder<TEntity> (:113), lets the caller describe the assignments against the persistence-agnostic interface (:114), and throws ArgumentException when nothing was described (:115-116). Then it stamps: LastModifiedOn from timeProvider.GetUtcNow().UtcDateTime unless the caller already set it (:120-124), and LastModifiedBy from the current user unless the caller already set it and only when a user id is actually available (:126-129). Both guards go through builder.SetsProperty, so an explicit caller assignment always wins. Finally the recorded assignments are replayed into EF as a method group (:131).
              • +
              • ExecuteDeleteAsync (EFRepository.cs:118-124) is the set-based delete: Entities.Where(where).ExecuteDeleteAsync(...), returning the affected row count (:123).
              • +
              • ExecuteUpdateAsync (EFRepository.cs:127-157) is the flagship. It guards both arguments (:132-133), constructs an UpdatePropertySetterBuilder<TEntity> (:135), lets the caller describe the assignments against the persistence-agnostic interface (:136), and throws ArgumentException when nothing was described (:137-138). Then it stamps: LastModifiedOn from timeProvider.GetUtcNow().UtcDateTime unless the caller already set it (:142-146), and LastModifiedBy unless the caller already set it (:151-154), with currentUserService?.UserId ?? default, so a scope with no current user records the default sentinel as the editor (the comment at :148-150). Both guards go through builder.SetsProperty, so an explicit caller assignment always wins. Finally the recorded assignments are replayed into EF as a method group (:156).
              • sealed, and the mutation members are non-virtual: this is the end of the inheritance chain.
            • Why it's built this way: keeping the write members on a subclass of the read repository rather than on a parallel type means the read behavior a write handler relies on (the tracked GetByIdAsync, most of all) is literally the same code a query handler runs. The contract is ADR-055, and the split between staging here and flushing in IUnitOfWork is what makes one transaction span several repositories.
            • -
            • Where it's used: constructed by RepositoryFactory's Create through the same cached ActivatorUtilities factory (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/Factory/RepositoryFactory.cs:31-32), optionally wrapped in EFRepositoryDecorator<TEntity, TIdentifierType> (:34-39). Application code reaches it through IUnitOfWork's GetRepository (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:33). Covered by EFRepositoryAdditionalTests.cs, EFRepositoryAuditStampTests.cs, and EFRepositoryIntegrationTests.cs under MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/.
            • -
            • Caveats / not-in-source: ExecuteUpdateAsync and ExecuteDeleteAsync bypass the save pipeline, so beyond the two audit columns stamped here they raise no domain events and run no other interceptor, the tenant guard included. The source stamps the audit fields and nothing else; whether a given set-based call site needed an event is a judgement the framework does not make for you.
            • +
            • Where it's used: constructed by RepositoryFactory's Create through the same cached ActivatorUtilities factory (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/Factory/RepositoryFactory.cs:31-32), optionally wrapped in EFRepositoryDecorator<TEntity, TIdentifierType> (:34-39). Application code reaches it through IUnitOfWork's GetRepository (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:33). Covered by EFRepositoryAdditionalTests.cs, EFRepositoryAuditStampTests.cs, EFRepositoryConcurrencyTouchTests.cs, and EFRepositoryIntegrationTests.cs under MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Repositories/.
            • +
            • Caveats / not-in-source: ExecuteUpdateAsync and ExecuteDeleteAsync bypass the save pipeline, so beyond the two audit columns stamped here they raise no domain events and run no other interceptor, the tenant guard included. The source stamps the audit fields and nothing else; whether a given set-based call site needed an event is a judgement the framework does not make for you. The class-level remarks still say that without a user service "the user stamp is skipped" (EFRepository.cs:20-21), which no longer matches the body: LastModifiedBy is always stamped unless the caller set it, with default when no user id is available (:151-154). Trust the method body over the remark.

            CosmosIntIdValueGenerator

            @@ -7357,7 +8097,7 @@

            CosmosIntIdValueGenerator

        • Why it's built this way: the counter is deliberately process-local. A durable sequence would need a round trip to the database per insert, which is exactly the cost a Cosmos-shaped workload is trying to avoid, and the class remarks accept the trade-off explicitly (CosmosIntIdValueGenerator.cs:11-15).
        • -
        • Where it's used: installed by the Cosmos branch of EntityTypeConfiguration.ApplyEngineConventions (EntityTypeConfiguration.cs:99) for every entity whose id is value-generated; pinned by CosmosIntIdValueGeneratorTests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/ValueGenerators/CosmosIntIdValueGeneratorTests.cs:6).
        • +
        • Where it's used: installed by CosmosDataSourceEngine.ApplyKeyAndTableMapping (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/CosmosDataSourceEngine.cs:98-99), which EntityTypeConfiguration.ApplyEngineConventions reaches through DataSourceEngines.For(engine) (EntityTypeConfiguration.cs:77), for every entity whose id is value-generated; pinned by CosmosIntIdValueGeneratorTests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/ValueGenerators/CosmosIntIdValueGeneratorTests.cs:6).
        • Caveats / not-in-source: the class remarks state the limit plainly (CosmosIntIdValueGenerator.cs:14): two processes seeded within the same second, or two processes whose counters drift into each other, can mint the same id, and the suggested remedy is a Guid alias for entities that need true uniqueness. Whether any deployed host currently stores entities in Cosmos is Not determinable from source: SQL Server is the engine every host in this workspace configures.

        TenantDataSourceOverrideSettings

        @@ -7371,12 +8111,12 @@

        TenantDataSourceOverrideSettings

      • Walkthrough
        • Five nullable { get; init; } strings, one per engine plus the Cosmos database name: SQLServerConnectionString (TenancySettings.cs:141), PostgreSQLConnectionString (:144), SqliteConnectionString (:147), CosmosConnectionString (:150).
        • CosmosDatabaseName (:153) is optional: when omitted the shared source's database name is kept, which is how one Cosmos account can serve per-tenant databases (:149-152).
        • -
        • The read path is a record with clone, and it is the interesting part. DbContextFactory.ResolveTenantOverride bails out unless there is a resolved tenant, bound tenancy settings, an entry for that tenant, and an entry for that source name (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:164-170), picks the connection string for the source's engine through TenancySettingsValidator.ConnectionStringFor (:154, resolver at TenancySettingsValidator.cs:122-130), and returns null when the tenant overrides this source on a different engine only (DbContextFactory.cs:173-177). Otherwise it clones the shared PhysicalDataSource with the new connection string and, if supplied, the new Cosmos database name (:161-168).
        • -
        • The clone keeps the ORIGINAL DataSourceKey, and the comment above it explains why (DbContextFactory.cs:156-161): EF's model cache is keyed on that key, so replacing only the connection string is what lets one compiled model serve every tenant's database.
        • +
        • The read path is a record with clone, and it is the interesting part. DbContextFactory.ResolveTenantOverride bails out unless there is a resolved tenant, bound tenancy settings, an entry for that tenant, and an entry for that source name (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:170-176), picks the connection string for the source's engine through TenancySettingsValidator.ConnectionStringFor (:154, resolver at TenancySettingsValidator.cs:123-124), and returns null when the tenant overrides this source on a different engine only (DbContextFactory.cs:179-183). Otherwise it clones the shared PhysicalDataSource with the new connection string and, if supplied, the new Cosmos database name (:161-168).
        • +
        • The clone keeps the ORIGINAL DataSourceKey, and the comment above it explains why (DbContextFactory.cs:162-167): EF's model cache is keyed on that key, so replacing only the connection string is what lets one compiled model serve every tenant's database.
      • Why it's built this way: [Rubric §12, Performance & Scalability] is the reason for the key-preserving clone. A per-tenant DataSourceKey would build and cache a separate EF model per tenant, which multiplies startup cost and memory by the tenant count for no schema difference.
      • -
      • Where it's used: DbContextFactory at context-creation time (DbContextFactory.cs:102-110), and TenancySettingsValidator at startup, which rejects an entry that declares no connection string at all (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:76-86) and an entry whose key is not a real physical source name for the engine it declares (:93-104, with the round-trip test at :117-119). The second check exists because the alternative is a silent fall back to the shared database, which is precisely the failure database-per-tenant is bought to prevent.
      • +
      • Where it's used: DbContextFactory at context-creation time (DbContextFactory.cs:104-112), and TenancySettingsValidator at startup, which rejects an entry that declares no connection string at all (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:77-87) and an entry whose key is not a real physical source name for the engine it declares (:93-104, with the round-trip test at :117-119). The second check exists because the alternative is a silent fall back to the shared database, which is precisely the failure database-per-tenant is bought to prevent.
      • Caveats / not-in-source: an override is keyed by physical source name (the name IDataSourceResolver produces), not the logical name a module uses (TenancySettings.cs:123-128). That distinction is invisible in a single-database host, where everything collapses onto Default.

      TenantResolutionStrategy

      @@ -7394,9 +8134,9 @@

      TenantResolutionStrategy

    489. The order is read through EffectiveResolutionOrder, not the bound list: an empty ResolutionOrder falls back to the framework default pair (TenancySettings.cs:76-77). That indirection exists because the configuration binder ADDS to a pre-populated collection rather than replacing it, so a non-empty default would leave a host that configured its own order also running the framework's entries (:42-48).
    490. -
    491. Concept, a declared-but-unimplemented member that cannot silently no-op. [Rubric §9, API & Contract Design]: the member exists so the configuration contract is stable when host-based resolution ships (TenancySettings.cs:22-26). [Rubric §15, Best Practices & Code Quality]: selecting it is not accepted quietly. TenancySettingsValidator walks the effective resolution order and fails options validation with a message naming the value as defined but not implemented (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:54-65), so the host refuses to boot instead of resolving nothing on every request. That is the fail-fast configuration contract (ADR-070) applied to a rule data annotations cannot express.
    492. +
    493. Concept, a declared-but-unimplemented member that cannot silently no-op. [Rubric §9, API & Contract Design]: the member exists so the configuration contract is stable when host-based resolution ships (TenancySettings.cs:22-26). [Rubric §15, Best Practices & Code Quality]: selecting it is not accepted quietly. TenancySettingsValidator walks the effective resolution order and fails options validation with a message naming the value as defined but not implemented (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:55-66), so the host refuses to boot instead of resolving nothing on every request. That is the fail-fast configuration contract (ADR-070) applied to a rule data annotations cannot express.
    494. Why it's built this way: shipping the enum member ahead of the implementation keeps the JSON contract additive, and pairing it with a boot-time rejection removes the only real risk of doing that.
    495. -
    496. Where it's used: TenantResolutionMiddleware (TenantResolutionMiddleware.cs:111-113), TenancySettings.EffectiveResolutionOrder (TenancySettings.cs:76-77), and TenancySettingsValidator (TenancySettingsValidator.cs:56-64).
    497. +
    498. Where it's used: TenantResolutionMiddleware (TenantResolutionMiddleware.cs:111-113), TenancySettings.EffectiveResolutionOrder (TenancySettings.cs:76-77), and TenancySettingsValidator (TenancySettingsValidator.cs:57-65).
    499. DesignTimeDbContextOptions

      @@ -7432,23 +8172,6 @@

      ExplicitAssemblyProvider

    500. Why it's built this way: private sealed and nested means it exists only for the helper's own call path and cannot become a public extension point by accident. It is constructed from the caller's ConfigurationAssemblies list with a spread so later mutation of the options object cannot change the model (DesignTimeDbContextHelper.cs:129).
    501. Where it's used: built once per design-time context and used three ways, registered as the DI-visible IEntityConfigurationAssemblyProvider (DesignTimeDbContextHelper.cs:193), passed directly to the context constructor (:57, :78), and handed to the EntityDataSourceRegistry that decides which entities belong to which physical source (:110).
    502. -

      RestrictDeleteByDefaultConvention

      -
      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Conventions · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/RestrictDeleteByDefaultConvention.cs:41 · Level 1 · class (public sealed)

      -
      -
        -
      • What it is: a model-finalizing convention that stamps every foreign key in the model with the source of its delete behavior, and restricts the ones nobody configured. It walks every declared foreign key once and defaults an unconfigured or convention-sourced cascade to DeleteBehavior.Restrict, unless the foreign key belongs to an owned-type relationship, whose cascade EF itself owns (RestrictDeleteByDefaultConvention.cs:41-107).
      • -
      • Depends on: EF Core's IModelFinalizingConvention, IConventionModelBuilder, IConventionForeignKey, ConfigurationSource and DeleteBehavior, plus DataSource (its one primary-constructor parameter, RestrictDeleteByDefaultConvention.cs:41).
      • -
      • Concept introduced, defaulting to the safe cascade instead of the convenient one. [Rubric §8, Data Architecture] assesses whether the storage design prevents the failure modes its own conventions could otherwise create, and [Rubric §15, Best Practices and Code Quality] assesses whether a default is a deliberate choice rather than whatever the underlying framework happens to ship. EF Core's own convention cascades a required foreign key by default, which means a relationship nobody explicitly configured silently deletes dependents when the principal is deleted. This convention overrides that default: an unconfigured or convention-sourced delete behavior becomes Restrict, so a delete that would fan out across rows the domain never asked to remove fails loudly at the database instead of succeeding silently.
      • -
      • Walkthrough
          -
        • DeleteBehaviorSourceAnnotation, ExplicitSource, ConventionSource and OwnershipSource (RestrictDeleteByDefaultConvention.cs:188-197) are the four public constants: the annotation name the convention stamps on every foreign key, and the three values it can carry, recording whether the behavior came from an explicit configuration call, from this convention, or from EF's ownership handling.
        • -
        • ProcessModelFinalizing (RestrictDeleteByDefaultConvention.cs:199-221) null-guards the model builder, returns immediately for DataSource.CosmosDB (:206-209, the same engine carve-out SoftDeleteUniqueIndexConvention and CrossDataSourceDegradeConvention make), then materializes every declared foreign key across the model into a list before applying anything (:213-215). The comment names why: the cross-source convention runs first and may already have removed relationships, so nothing here should observe a collection mid-mutation.
        • -
        • Apply (RestrictDeleteByDefaultConvention.cs:228-247) is the per-foreign-key decision. An ownership foreign key is stamped OwnershipSource and left untouched (:230-234): EF owns that cascade, and overriding it would fight the owned-type feature. Otherwise the foreign key's existing ConfigurationSource is read; null (nobody ever set one) or ConfigurationSource.Convention (EF's own required-FK rule produced the cascading default) are both treated as "inherited, and ours to replace": the behavior is set to DeleteBehavior.Restrict and stamped ConventionSource (:239-244). Anything else, meaning a configuration explicitly set a delete behavior, is stamped ExplicitSource and left exactly as configured (:246).
        • -
        -
      • -
      • Why it's built this way: the annotation is not cosmetic. Stamping the source of every delete behavior, rather than just flipping the ones that need it, gives a fitness test a byte-for-byte answer to "did this foreign key's cascade come from an explicit choice, from this convention, or from ownership", which is what ADR-119 records as the decision: default every unconfigured relationship to Restrict rather than to EF's own cascading default, and make the source of that decision inspectable rather than implicit. Running at model finalizing, after every module's IEntityTypeConfiguration has declared its own relationships, is what lets the convention see the whole model without needing to know which modules exist, the same timing SoftDeleteUniqueIndexConvention uses for the same reason.
      • -
      • Where it's used: registered by ApplicationDbContext in ConfigureConventions with the context's own engine, alongside the other model-finalizing conventions. Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Conventions/RestrictDeleteByDefaultConventionTests.cs and by MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Domain/DeleteBehaviorConventionTests.cs, which subclasses the shared DeleteBehaviorConventionTestsBase and the ArchitectureRules.DeleteBehavior rule group so ADC's own model is checked against the same contract.
      • -

      TenantEntrySettings

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Tenancy · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettings.cs:121 · Level 1 · class (sealed)

      @@ -7456,32 +8179,13 @@

      TenantEntrySettings

      • What it is: one declared tenant, bound from Tenancy:Tenants:{tenantId}. It holds exactly one member: that tenant's per-data-source connection overrides (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettings.cs:118-130).
      • Depends on: TenantDataSourceOverrideSettings, the value type of its dictionary (TenancySettings.cs:129, declared at :138). Held by TenancySettings.Tenants (:115).
      • -
      • Concept introduced, declaring a tenant is only required for the database-per-tenant case. This is the single most important thing to read off this class, and it is stated in its own doc (TenancySettings.cs:109-114). A shared-schema tenant needs NO entry here at all, because its isolation comes from the global query filter and the TenantSaveChangesInterceptor, which AddInfrastructure registers unconditionally with a comment saying why (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:66-69). Configuration is therefore only how a tenant is PROMOTED to its own database. [Rubric §8, Data Architecture] assesses how deliberately data is partitioned. The dictionary is keyed by PHYSICAL data source name (the name IDataSourceResolver produces, for example Default or Conference), and the key choice is load-bearing enough that TenancySettingsValidator fails the boot on a key that does not round-trip. A tenant can override some sources and share others: a source with an entry is routed to the tenant's own database, a source without one stays shared (TenancySettings.cs:123-128).
      • +
      • Concept introduced, declaring a tenant is only required for the database-per-tenant case. This is the single most important thing to read off this class, and it is stated in its own doc (TenancySettings.cs:109-114). A shared-schema tenant needs NO entry here at all, because its isolation comes from the global query filter and the TenantSaveChangesInterceptor, which AddInfrastructure registers unconditionally with a comment saying why (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:67-70). Configuration is therefore only how a tenant is PROMOTED to its own database. [Rubric §8, Data Architecture] assesses how deliberately data is partitioned. The dictionary is keyed by PHYSICAL data source name (the name IDataSourceResolver produces, for example Default or Conference), and the key choice is load-bearing enough that TenancySettingsValidator fails the boot on a key that does not round-trip. A tenant can override some sources and share others: a source with an entry is routed to the tenant's own database, a source without one stays shared (TenancySettings.cs:123-128).
      • Walkthrough
        • DataSources (TenancySettings.cs:129): a get-only Dictionary<string, TenantDataSourceOverrideSettings> bound from Tenancy:Tenants:{tenantId}:DataSources:{sourceName}. Get-only is the correct shape for a bound dictionary, since the configuration binder populates an existing instance rather than assigning a new one.
      • Why it's built this way: keeping the per-tenant overrides one level below the tenant, rather than flattening connection strings onto the tenant entry, is what lets a single tenant be physically separated on one source while remaining shared on the others, which is the mixed model ADR-073 records.
      • -
      • Where it's used: read by DbContextFactory.ResolveTenantOverride, which looks up the current tenant then the requested source and clones the shared PhysicalDataSource with the tenant's connection string while keeping the ORIGINAL DataSourceKey, so one compiled EF model serves every tenant's database (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:162-187). Also expanded by TenantDataSourceTargets.Expand, which turns the shared sources plus every (tenant, overridden source) pair into the list the outbox and cleanup background services sweep (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:68-78), and validated per entry by TenancySettingsValidator (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:71-106).
      • -
      -

      IndexBuilderExtensions

      -
      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Configuration · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/IndexBuilderExtensions.cs:10 · Level 2 · class (public static, extension container)

      -
      -
        -
      • What it is: a single C# extension(IndexBuilder) block exposing one member, HasSoftDeleteFilter(...), which attaches the IsDeleted = 0 predicate to a hand-authored index in an entity type configuration (IndexBuilderExtensions.cs:10-12, member at :50-64).
      • -
      • Depends on: SoftDeleteFilterSql (the shared predicate builder, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:27), the DataSource engine enum, and EF Core's Microsoft.EntityFrameworkCore.Metadata.Builders.IndexBuilder.
      • -
      • Concept introduced, the filtered (partial) index under soft delete. [Rubric §8, Data Architecture] assesses whether the storage design accounts for the consequences of its own conventions, and [Rubric §12, Performance and Scalability] assesses whether indexes match the queries they serve. Soft delete (ADR-005) means a "deleted" row is still physically present with IsDeleted = 1, and the global query filter on ApplicationDbContext hides it from every read. An index that does not carry the same predicate therefore indexes rows no query will ever return: the deleted rows still occupy index pages, and the optimizer's row estimates include them. The doc comment states this in one sentence (IndexBuilderExtensions.cs:14-18). Adding WHERE [IsDeleted] = 0 to the index makes it a filtered index (SQL Server's term; SQLite calls the same thing a partial index) that matches the shape of every query the application actually issues.
      • -
      • Walkthrough
          -
        • The extension receiver is the IndexBuilder returned by builder.HasIndex(...), so the call chains directly off the index declaration (IndexBuilderExtensions.cs:12, usage sample at :23-26).
        • -
        • engine defaults to DataSource.SQLServer (IndexBuilderExtensions.cs:53), which matches the majority case of a configuration deriving from EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>; a SQLite or PostgreSQL configuration passes DataSource.Sqlite or DataSource.PostgreSQL explicitly. PostgreSQL needs it for a real reason, not just symmetry: its soft-delete flag is a genuine boolean column, so the predicate the engine produces is "IsDeleted" = false rather than SQL Server's [IsDeleted] = 0 (IndexBuilderExtensions.cs:37-43).
        • -
        • additionalFilter is the optional second predicate for an index that is already conditional on something else, for example "[ExternalSessionId] IS NOT NULL" (IndexBuilderExtensions.cs:45-50). The two are joined as {additionalFilter} AND {filterSql} in that exact order (:62-65), which is deliberate: it reproduces the SQL of the hand-authored literal the helper replaced, so switching to the helper does not force a migration. It is also the order SoftDeleteUniqueIndexConvention uses when it appends its own clause, so the two paths yield byte-identical SQL for the same pair of predicates.
        • -
        • The body null-guards the receiver, asks SoftDeleteFilterSql to build the predicate from the model's own metadata, and returns the builder unchanged when the builder returns null (IndexBuilderExtensions.cs:56-60). That null is the Cosmos no-op and the "this entity is not soft-deletable" case, so the call is always safe to write.
        • -
        -
      • -
      • Why it's built this way: the doc comment gives the rationale directly (IndexBuilderExtensions.cs:27-29). A literal HasFilter("[IsDeleted] = 0") hard-codes both the column name and SQL Server's bracket quoting; reading the column name from the model means a HasColumnName rename follows automatically, and delegating the quoting to the engine keeps the same configuration body valid on SQLite. This is the portability posture of ADR-018. The division of labour with SoftDeleteUniqueIndexConvention is explicit: the convention covers every UNIQUE index automatically, and this extension point exists for the case the convention deliberately leaves alone, a hand-authored NON-unique index (IndexBuilderExtensions.cs:20-22). The pair of them is ADR-095.
      • -
      • Where it's used: entity configurations across both applications. In MMCA.Store: MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Infrastructure/Persistence/EntityConfiguration/OrderConfiguration.cs:44,51,58 (the last one with additionalFilter: "[StripeSessionId] IS NOT NULL"), MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Infrastructure/Persistence/EntityConfiguration/UserConfiguration.cs:69,83, .../EntityConfiguration/CustomerConfiguration.cs:82, MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Infrastructure/Persistence/EntityConfiguration/ProductConfiguration.cs:43, .../ProductImageConfiguration.cs:54, .../ProductVariantConfiguration.cs:46, .../CategoryConfiguration.cs:33. In MMCA.ADC's Engagement module: MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/CheckIns/CheckInConfiguration.cs:51,56,62,66,69, .../PointsEntryConfiguration.cs:48,52, .../LivePollVoteConfiguration.cs:37, .../LeaderboardOptInConfiguration.cs:35, .../SessionQuestionUpvoteConfiguration.cs:34, .../UserSessionBookmarkConfiguration.cs:34,39. Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/IndexBuilderExtensionsTests.cs.
      • -
      • Caveats / not-in-source: the ordering constraint is called out in the doc comment (IndexBuilderExtensions.cs:31-35). Unlike the convention, which runs at model finalizing, this member reads the column name at the moment it is called, so a HasColumnName on the soft-delete property must be declared before the index. That only bites a model that renames the column.
      • +
      • Where it's used: read by DbContextFactory.ResolveTenantOverride, which looks up the current tenant then the requested source and clones the shared PhysicalDataSource with the tenant's connection string while keeping the ORIGINAL DataSourceKey, so one compiled EF model serves every tenant's database (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:168-193). Also expanded by TenantDataSourceTargets.Expand, which turns the shared sources plus every (tenant, overridden source) pair into the list the outbox and cleanup background services sweep (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:68-78), and validated per entry by TenancySettingsValidator (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:72-107).

      NullDomainEventDispatcher

      @@ -7495,25 +8199,6 @@

      NullDomainEventDispatcher

    503. Why it's built this way: it is private sealed and nested so it can never be resolved by a production host by mistake. Registering it as the singleton IDomainEventDispatcher (DesignTimeDbContextHelper.cs:146) is what lets the design-time container build the same interceptor set the runtime builds, which is the property that keeps a scaffolded migration honest: the design-time pipeline differs from the runtime pipeline in nothing that touches the model.
    504. Where it's used: registered once in BuildDesignTimeServices (DesignTimeDbContextHelper.cs:146); nowhere else in the codebase.
    505. -

      SoftDeleteUniqueIndexConvention

      -
      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Conventions · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:33 · Level 2 · class (public sealed)

      -
      -
        -
      • What it is: an EF Core model-finalizing convention that puts the IsDeleted = 0 predicate on every unique index of every soft-deletable entity type in the model, so a soft-deleted row stops occupying its unique slot (SoftDeleteUniqueIndexConvention.cs:10-33).
      • -
      • Depends on: EF Core's IModelFinalizingConvention, IConventionModelBuilder and IConventionEntityType; SoftDeleteFilterSql for the predicate text; the DataSource engine enum, taken as its one primary-constructor parameter (SoftDeleteUniqueIndexConvention.cs:33); and IAuditableEntity as the marker for "this entity is soft-deletable".
      • -
      • Concept introduced, the model-finalizing convention as a cross-cutting model rule. [Rubric §6, CQRS & Event-Driven Design] assesses whether policies that apply to every entity are expressed once rather than repeated per configuration, and [Rubric §8, Data Architecture] assesses whether the data model's invariants actually hold. EF exposes convention hooks that run while the model is being built; IModelFinalizingConvention is the last of them, which means it observes the model after every module's IEntityTypeConfiguration has declared its indexes. That timing is what makes a blanket rule possible: the convention does not need to know which modules exist or what they declared, it just walks the finished model. The bug it closes is a genuine soft-delete trap, stated in the doc comment (SoftDeleteUniqueIndexConvention.cs:12-16): soft-delete a speaker, and the unique index on email still blocks creating a new speaker with that email, because the row is invisible to the application but entirely visible to the database's uniqueness check. Adding the filter aligns what the database enforces with what the application shows.
      • -
      • Walkthrough
          -
        • ProcessModelFinalizing (SoftDeleteUniqueIndexConvention.cs:36-50) null-guards the model builder, then returns immediately when the engine is DataSource.CosmosDB (:42-43): Cosmos has no filtered-index concept, so the convention is a documented no-op there (:27-30).
        • -
        • The entity selection is two predicates (SoftDeleteUniqueIndexConvention.cs:45-46): the CLR type must be assignable to IAuditableEntity, and the entity type must not be owned. Owned types share their owner's table and have no independent index story, so they are excluded.
        • -
        • ApplyFilterToUniqueIndexes (SoftDeleteUniqueIndexConvention.cs:52-81) asks SoftDeleteFilterSql for the predicate and bails when it is null (:56-58), then walks the entity's indexes, skipping every non-unique one (:60-63).
        • -
        • For a unique index the convention branches three ways on the existing filter. No filter at all: set the soft-delete predicate (SoftDeleteUniqueIndexConvention.cs:65-70). A filter that already constrains the soft-delete column, whether a hand-authored literal or the output of HasSoftDeleteFilter: leave it exactly as it is, detected by SoftDeleteFilterSql.ContainsPredicate (:74-75, the helper at MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:52). Anything else: append the clause as {existingFilter} AND {filterSql} (:79).
        • -
        • That append is the load-bearing part, and the doc comment explains why it replaced the earlier "skip an index that already has a filter" behavior (SoftDeleteUniqueIndexConvention.cs:17-26). Skipping meant the exact partial-unique indexes a model bothered to hand-author (for example one narrowed on [DedupKey] IS NOT NULL) were the only ones a soft-deleted row could keep blocking. The ContainsPredicate check is what keeps appending idempotent: without it, a second model build would produce ... AND [IsDeleted] = 0 AND [IsDeleted] = 0.
        • -
        -
      • -
      • Why it's built this way: the comment at SoftDeleteUniqueIndexConvention.cs:54-55 names the reason the predicate comes from SoftDeleteFilterSql rather than from a local string: it is the same builder the opt-in extension reaches, so the automatic path and the hand-authored path can never disagree about column name or identifier quoting, and the AND ordering at :77-78 is chosen to match HasSoftDeleteFilter(additionalFilter:) byte for byte. Restricting the automatic behavior to unique indexes is a deliberate blast-radius choice: a unique index without the filter is a correctness bug under soft delete, while a non-unique index without it is only a performance question, and performance questions belong to whoever wrote the index. The whole decision, including the 2026-08-26 revision from skipping to appending, is ADR-095; soft delete as a policy is ADR-005; covering SQL Server and SQLite while skipping Cosmos is the engine-portability posture of ADR-018.
      • -
      • Where it's used: registered by ApplicationDbContext in ConfigureConventions with the context's own engine (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:387), immediately after CrossDataSourceDegradeConvention. The comment there records the ordering intent and the precedence rule: it runs at finalization, after module configurations have declared their indexes, and hand-authored index filters are respected (ApplicationDbContext.cs:384-386). Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Conventions/SoftDeleteUniqueIndexConventionTests.cs.
      • -

      TenancySettings

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Tenancy · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettings.cs:50 · Level 2 · class (sealed)

      @@ -7523,7 +8208,7 @@

      TenancySettings

    506. Depends on: TenantResolutionStrategy (the strategy enum at the top of the same file, :6-28) and TenantEntrySettings (the value type of Tenants, :115). No externals, and deliberately no relational data annotations: the checks that matter here are relational, so they live in TenancySettingsValidator.
    507. Concept introduced, "empty means the default" for bound collections. This is a genuine configuration-binder trap and the class documents it explicitly (TenancySettings.cs:41-48). The .NET configuration binder ADDS to a pre-populated collection rather than replacing it. If ResolutionOrder shipped pre-filled with [Claim, Header], a host that configured its own order would end up running the framework's entries as well. The resolution is a pair of properties: the bound list starts empty (:73, :103), and the framework reads a computed Effective* projection that substitutes a private static default when the bound list is empty (:76-77, :106-107). [Rubric §11, Security] assesses where trust boundaries are drawn. The two implemented strategies are not equivalent and the enum says so: Claim is the trustworthy source because the claim was signed by the token issuer, so a caller cannot pick its own tenant (:8-13), while Header is intended for service-to-service calls behind a trusted gateway, and a public edge that honors it lets any caller name any tenant (:15-20). The default order tries Claim first (:56-57). RequireTenant defaults to true and is documented as failing closed, because with tenancy switched on an unscoped request would read across every tenant (:91-96). - [Rubric §15, Best Practices & Code Quality] assesses whether a new capability is additive for existing hosts. Enabled gates RESOLUTION, not isolation: the global query filter and the TenantSaveChangesInterceptor are always registered and are inert whenever no tenant is resolved (:35-40, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:66-69, and the same point restated on AddMultiTenancy itself at :505-509). A host that never enables tenancy keeps exactly the behavior it had before tenancy shipped, and a host that does enable it can never accidentally leave the write-side guard off.
    508. + [Rubric §15, Best Practices & Code Quality] assesses whether a new capability is additive for existing hosts. Enabled gates RESOLUTION, not isolation: the global query filter and the TenantSaveChangesInterceptor are always registered and are inert whenever no tenant is resolved (:35-40, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:67-70, and the same point restated on AddMultiTenancy itself at :505-509). A host that never enables tenancy keeps exactly the behavior it had before tenancy shipped, and a host that does enable it can never accidentally leave the write-side guard off.
    509. Walkthrough
      • SectionName = "Tenancy" (TenancySettings.cs:53), the one public static field.
      • DefaultResolutionOrder (:56-57): private static [Claim, Header]. DefaultExcludedPathPrefixes (:60-61): private static ["/health", "/alive", "/.well-known"], the probe and discovery endpoints that must answer before any tenant exists.
      • @@ -7536,94 +8221,32 @@

        TenancySettings

    510. Why it's built this way: shared-schema isolation plus optional database-per-tenant promotion is the model ADR-073 records, and this class is its whole operator-facing surface. Defaulting Enabled to false while keeping the filter and interceptor always-on is what makes the feature safe to ship into an existing host, and keeping every relational check in a separate IValidateOptions<T> (rather than in attributes) is what lets the validator reach the resolved data sources.
    511. -
    512. Where it's used: bound with ValidateOnStart in AddMultiTenancy, alongside the TryAddEnumerable registration of TenancySettingsValidator (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:266-278). Read at request time by TenantResolutionMiddleware, which short-circuits when disabled or on an excluded path (MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/TenantResolutionMiddleware.cs:62), walks EffectiveResolutionOrder reading the claim or the header (:107-118), lets the request through unscoped when RequireTenant is off (:75-81), and otherwise answers 400 Bad Request (:83). Read at persistence time by DbContextFactory for per-tenant connection routing (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:45, :144-168), and by the background services through TenantDataSourceTargets.Expand so they sweep every tenant database too (OutboxProcessor.cs:64, OutboxCleanupService.cs:198, OutboxAdministration.cs:42, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:51-81). Startup database initialization uses the same expansion to create each tenant's database (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:131, :138), and the design-time helper supplies a default instance so dotnet ef needs no tenancy configuration (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextHelper.cs:156). All of the runtime consumers take the options as NULLABLE, so a host that never called AddMultiTenancy resolves null and behaves exactly as before.
    513. - -

      CrossDataSourceDegradeConvention

      -
      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Conventions · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:33 · Level 3 · class (public sealed)

      -
      -
        -
      • What it is: the model-finalizing convention that makes database-per-service work without forcing every module to write two versions of its entity configuration. It finds relationships whose two ends resolve to different physical databases and degrades them: the foreign key goes away, the navigation members are ignored, and entity types belonging to another database are removed from this model entirely (CrossDataSourceDegradeConvention.cs:9-33).
      • -
      • Depends on: DataSourceKey (the context's own physical source) and IEntityDataSourceRegistry (the entity-to-database map), both primary-constructor parameters (CrossDataSourceDegradeConvention.cs:33-35); EF Core's IModelFinalizingConvention plus the mutable metadata surface (IMutableModel, IMutableEntityType, IMutableForeignKey, IMutableProperty).
      • -
      • Concept introduced, degrading a relationship instead of forbidding it. [Rubric §7, Microservices Readiness] assesses whether a module can be lifted into its own service without rewriting application code, and [Rubric §8, Data Architecture] assesses whether ownership boundaries in the data model are real. Under database-per-service (ADR-006) an order row and a user row can live in different databases, and no database engine can enforce a foreign key across that line. The naive answers are both bad: forbid the navigation in the domain model (which distorts the domain to fit deployment), or keep the FK and hope the two entities always end up co-located (which breaks the moment they do not). This convention takes a third route. The configuration body is written once, as if everything were in one database, and the model builder subtracts what the current physical source cannot support. Three things are given up and each has a compensating mechanism: the FK constraint is replaced by an index over the surviving scalar columns, in-database navigation is replaced by the INavigationPopulator batch-loading path (ADR-002), and referential consistency is maintained by integration events rather than by the engine (CrossDataSourceDegradeConvention.cs:12-21). The payoff is stated at :25-29: when every entity resolves to the same source, nothing is foreign and the convention is a structural no-op, so the monolith model is identical to the single-database model.
      • -
      • Walkthrough
          -
        • ProcessModelFinalizing opens by casting the convention model builder's metadata to IMutableModel (CrossDataSourceDegradeConvention.cs:44-46). The comment above the cast is load-bearing: cross-cutting helpers (soft-delete filters, concurrency tokens) have already promoted every entity type to the Explicit configuration source, and convention-sourced builder calls cannot override Explicit, so the mutable API is the only surface that can apply these changes (:22-24).
        • -
        • IsForeign (CrossDataSourceDegradeConvention.cs:91-94) is the whole routing decision: look the CLR type's full name up in the registry, and call it foreign when the registry knows it and its key differs from this context's key. An unregistered type is never foreign.
        • -
        • The foreign set is computed over non-owned entity types, and when it is empty the method returns at once (CrossDataSourceDegradeConvention.cs:48-55). That early return is the monolith fast path.
        • -
        • Step 1, degrade the FKs (CrossDataSourceDegradeConvention.cs:62-74): for each local entity, every declared foreign key whose principal is foreign is passed to DegradeForeignKey. FKs declared on foreign dependents are not touched here because step 3 removes those entity types wholesale. Note that addCompensatingIndex is computed once as "engine is not Cosmos" (:65).
        • -
        • DegradeForeignKey (CrossDataSourceDegradeConvention.cs:107-138) captures the non-shadow FK properties, removes the FK (which takes both navigations with it), and then rebuilds the index story. The subtle part is :123-130: EF's own ForeignKeyIndexConvention created an index for the FK, and its removal is processed by a deferred event that would fire after the coverage check below, silently leaving the column unindexed. So the convention drops that convention-sourced index eagerly itself, then checks coverage and adds a plain index only if nothing already covers the columns.
        • -
        • HasCoveringIndex (CrossDataSourceDegradeConvention.cs:140-144) treats an index as covering when the FK columns are a prefix of its column list, in order, which is exactly the condition under which a composite index can serve a lookup on those columns.
        • -
        • Step 2, ignore the foreign members (CrossDataSourceDegradeConvention.cs:79-82, implementation at :151-165): skip navigations pointing at foreign entities are removed, then every CLR property whose type (or collection element type) is foreign is added to the ignore list. The comment at :76-78 explains why the second half is needed: removing a navigation leaves an unmapped CLR property of entity type behind, and EF's model validation rejects that.
        • -
        • UnwrapCollectionElementType (CrossDataSourceDegradeConvention.cs:171-174) is the one-argument-generic unwrap that makes ICollection<ForeignEntity> detectable as a collection of foreign entities.
        • -
        • Step 3, remove the foreign entity types (CrossDataSourceDegradeConvention.cs:85-88). EF's relationship discovery pulls foreign types into the model as a side effect; this is where they leave it.
        • -
        -
      • -
      • Why it's built this way: the Cosmos carve-out on the compensating index is spelled out at CrossDataSourceDegradeConvention.cs:100-105. Cosmos auto-indexes every property and rejects explicit index definitions, so adding a compensating index would fail model validation, and skipping it is what keeps a configuration body carrying a cross-source relationship portable to Cosmos without edits (ADR-018). Doing all of this at model finalizing rather than at configuration time is what lets the same entity configuration serve the monolith and the extracted-service topology (ADR-008) with no per-topology branching in module code.
      • -
      • Where it's used: registered by ApplicationDbContext in ConfigureConventions with the context's DataSourceKey and the resolved IEntityDataSourceRegistry (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:381-382), ahead of SoftDeleteUniqueIndexConvention. Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/CrossDataSourceDegradeConventionTests.cs.
      • +
      • Where it's used: bound with ValidateOnStart in AddMultiTenancy, alongside the TryAddEnumerable registration of TenancySettingsValidator (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:276-288). Read at request time by TenantResolutionMiddleware, which short-circuits when disabled or on an excluded path (MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/TenantResolutionMiddleware.cs:62), walks EffectiveResolutionOrder reading the claim or the header (:107-118), lets the request through unscoped when RequireTenant is off (:75-81), and otherwise answers 400 Bad Request (:83). Read at persistence time by DbContextFactory for per-tenant connection routing (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:47, :144-168), and by the background services through TenantDataSourceTargets.Expand so they sweep every tenant database too (OutboxProcessor.cs:64, OutboxCleanupService.cs:198, OutboxAdministration.cs:42, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:51-81). Startup database initialization uses the same expansion to create each tenant's database (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:182, :138), and the design-time helper supplies a default instance so dotnet ef needs no tenancy configuration (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextHelper.cs:156). All of the runtime consumers take the options as NULLABLE, so a host that never called AddMultiTenancy resolves null and behaves exactly as before.

      TenancySettingsValidator

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Tenancy · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:23 · Level 4 · class (sealed, internal)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Tenancy · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:24 · Level 13 · class (sealed, internal)

        -
      • What it is: the startup validator for TenancySettings. It rejects a resolution order naming an unimplemented strategy, and rejects a per-tenant data-source override that declares no connection string or names a source that does not exist (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:8-23).
      • -
      • Depends on: IDataSourceResolver as an OPTIONAL primary-constructor parameter (TenancySettingsValidator.cs:23), plus DataSource and DataSourceKey (:4, :27-28), and the two settings shapes TenancySettings and TenantDataSourceOverrideSettings. Externals: Microsoft.Extensions.Options for IValidateOptions<T> and ValidateOptionsResult (:2), and System.Globalization for the CultureInfo.InvariantCulture message formatting (:1, :79).
      • -
      • Concept introduced, IValidateOptions<T> when validation needs other services. Data annotations and IValidatableObject are the right tools when a rule only involves the settings object itself. This class is the other half of the options-validation story: IValidateOptions<T> is a DI-resolved service, so it can inject collaborators. Here that collaborator is the data-source resolver, which is the only thing that knows whether Conference is a real physical source in this host. Registration is TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions<TenancySettings>, TenancySettingsValidator>()) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:274-275), with TryAddEnumerable because two modules calling AddMultiTenancy must not run the same validation twice (DependencyInjection.cs:273). The same shape is used by ConnectionStringSettingsValidator; these two are the framework's only custom IValidateOptions<T> implementations. - [Rubric §11, Security] assesses whether misconfiguration can degrade silently. The class doc states the rationale outright: every failure here would otherwise surface as silent cross-tenant behavior at run time, which is exactly the class of bug tenancy exists to prevent, so it is worth a failed boot (TenancySettingsValidator.cs:8-13). The concrete danger is the override key: an unknown logical name resolves to Default, so a mistyped source name would quietly leave that tenant on the shared database instead of its own (:112-116). - [Rubric §15, Best Practices & Code Quality] assesses the quality of failure messages. Each message names the exact configuration path that is wrong and tells the operator what to do: the missing-connection-string message lists the four acceptable properties and notes that removing the entry keeps the source shared (:78-85); the unknown-source message explains that override keys are physical names (:95-104); the Host strategy message names the two supported values (:60-63).
      • +
      • What it is: the startup validator for TenancySettings. It rejects a resolution order naming an unimplemented strategy, and rejects a per-tenant data-source override that declares no connection string or names a source that does not exist (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:9-24).
      • +
      • Depends on: IDataSourceResolver as an OPTIONAL primary-constructor parameter (TenancySettingsValidator.cs:24), plus DataSource and DataSourceKey (:3-4, :28-29, :104), the engine registry DataSourceEngines and its IDataSourceEngine entries (:5, :29, :124), and the two settings shapes TenancySettings and TenantDataSourceOverrideSettings. Externals: Microsoft.Extensions.Options for IValidateOptions<T> and ValidateOptionsResult (:2), and System.Globalization for the CultureInfo.InvariantCulture message formatting (:1, :80).
      • +
      • Concept introduced, IValidateOptions<T> when validation needs other services. Data annotations and IValidatableObject are the right tools when a rule only involves the settings object itself. This class is the other half of the options-validation story: IValidateOptions<T> is a DI-resolved service, so it can inject collaborators. Here that collaborator is the data-source resolver, which is the only thing that knows whether Conference is a real physical source in this host. Registration is TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions<TenancySettings>, TenancySettingsValidator>()) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:284-285), with TryAddEnumerable because two modules calling AddMultiTenancy must not run the same validation twice (DependencyInjection.cs:283). The same shape is used by ConnectionStringSettingsValidator; these two are the framework's only custom IValidateOptions<T> implementations. + [Rubric §11, Security] assesses whether misconfiguration can degrade silently. The class doc states the rationale outright: every failure here would otherwise surface as silent cross-tenant behavior at run time, which is exactly the class of bug tenancy exists to prevent, so it is worth a failed boot (TenancySettingsValidator.cs:9-14). The concrete danger is the override key: an unknown logical name resolves to Default, so a mistyped source name would quietly leave that tenant on the shared database instead of its own (:113-117). + [Rubric §15, Best Practices & Code Quality] assesses the quality of failure messages. Each message names the exact configuration path that is wrong and tells the operator what to do: the missing-connection-string message lists the four acceptable properties and notes that removing the entry keeps the source shared (:79-85); the unknown-source message explains that override keys are physical names (:96-104); the Host strategy message names the two supported values (:61-63).
      • Walkthrough
          -
        • Engines (:27-28): private static [SQLServer, PostgreSQL, Sqlite, CosmosDB], the engines an override can carry a connection string for.
        • -
        • Validate(string? name, TenancySettings options) (:31-47): null-guards the options, collects failures into a list, runs the resolution-order check once and the tenant check per declared tenant, then returns ValidateOptionsResult.Success or Fail(failures). Note that it accumulates ALL failures rather than returning on the first, so one boot attempt reports the whole set.
        • -
        • ValidateResolutionOrder (:54-65): walks EffectiveResolutionOrder (so the framework default is validated too, not just an explicitly configured order) and fails on TenantResolutionStrategy.Host. That enum member exists so the configuration contract is stable for when host-based resolution ships, but selecting it today must not read as "resolve nothing" (:49-53, and the enum's own doc at TenancySettings.cs:22-26).
        • -
        • ValidateTenant (:71-106): for each (sourceName, override) pair, computes which engines the override actually declares a connection string for. Zero engines is a failure and the loop moves on (:76-86). If resolver is null the source-existence check is skipped entirely (:88-91), which is what makes the validator usable in a container that never registered persistence (:14-18). Otherwise every declared engine whose source name is unknown produces a failure (:93-104).
        • -
        • DeclaredEngines (:109-110): a collection expression over Engines filtered by ConnectionStringFor being non-blank.
        • -
        • IsKnownPhysicalSource (:117-119): the round-trip test. Default always passes; otherwise the name must survive resolver.ResolveLogical(engine, sourceName).Name unchanged, because an unknown logical name collapses onto Default and therefore comes back different.
        • -
        • ConnectionStringFor(DataSource, TenantDataSourceOverrideSettings) (:122-130): internal static, a switch expression mapping engine to the matching connection-string property, now with a PostgreSQL => over.PostgreSQLConnectionString arm alongside SQL Server, Sqlite, and Cosmos. It is internal rather than private because it is reused outside validation, which is the detail worth noting next.
        • +
        • Engines (:28-29): private static, built from DataSourceEngines.All ordered by each engine's SubstitutionPriority and projected to its Engine value. With the shipped priorities (SQL Server 0, PostgreSQL 1, Sqlite 2, Cosmos 3: MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SQLServerDataSourceEngine.cs:29, PostgreSQLDataSourceEngine.cs:27, SqliteDataSourceEngine.cs:26, CosmosDataSourceEngine.cs:28) that yields [SQLServer, PostgreSQL, Sqlite, CosmosDB], the engines an override can carry a connection string for, in substitution-priority order (:27). The list is no longer hand-written, so a newly registered engine is validated without touching this class.
        • +
        • Validate(string? name, TenancySettings options) (:32-48): null-guards the options, collects failures into a list, runs the resolution-order check once and the tenant check per declared tenant, then returns ValidateOptionsResult.Success or Fail(failures). Note that it accumulates ALL failures rather than returning on the first, so one boot attempt reports the whole set.
        • +
        • ValidateResolutionOrder (:55-66): walks EffectiveResolutionOrder (so the framework default is validated too, not just an explicitly configured order) and fails on TenantResolutionStrategy.Host. That enum member exists so the configuration contract is stable for when host-based resolution ships, but selecting it today must not read as "resolve nothing" (:50-54, and the enum's own doc at TenancySettings.cs:22-26).
        • +
        • ValidateTenant (:72-107): for each (sourceName, override) pair, computes which engines the override actually declares a connection string for. Zero engines is a failure and the loop moves on (:77-87). If resolver is null the source-existence check is skipped entirely (:89-92), which is what makes the validator usable in a container that never registered persistence (:15-19). Otherwise every declared engine whose source name is unknown produces a failure (:94-105).
        • +
        • DeclaredEngines (:110-111): a collection expression over Engines filtered by ConnectionStringFor being non-blank.
        • +
        • IsKnownPhysicalSource (:118-120): the round-trip test. Default always passes; otherwise the name must survive resolver.ResolveLogical(engine, sourceName).Name unchanged, because an unknown logical name collapses onto Default and therefore comes back different.
        • +
        • ConnectionStringFor(DataSource, TenantDataSourceOverrideSettings) (:123-124): internal static, a one-line delegation to DataSourceEngines.For(engine).GetConnectionString(over), so each engine answers for its own override property (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/IDataSourceEngine.cs:59). For throws InvalidOperationException for an unregistered engine rather than returning null (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/DataSourceEngines.cs:40-43). It is internal rather than private because it is reused outside validation, which is the detail worth noting next.
      • -
      • Why it's built this way: the engine-to-property mapping is needed in three places (validation, target expansion, and connection routing), so it lives once here and the runtime paths call back into the validator's ConnectionStringFor rather than re-implementing the switch (ADR-073). Making the resolver optional rather than required is what keeps the validator constructible in a host that binds tenancy without registering AddInfrastructure, at the cost of skipping the source-existence check there; the strategy and connection-string checks still run.
      • -
      • Where it's used: registered by AddMultiTenancy alongside ValidateOnStart on the options (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:266-278). Its ConnectionStringFor helper is called at run time by TenantDataSourceTargets.Expand when deciding whether a (tenant, source) pair is really overridden (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:73) and by DbContextFactory.ResolveTenantOverride when cloning the physical source for a tenant (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:172). Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/AddMultiTenancyTests.cs, which asserts the single-registration shape (:97-98) and drives the validator both without a resolver (:168) and with one (:206).
      • +
      • Why it's built this way: the engine-to-property mapping is needed in three places (validation, target expansion, and connection routing). The mapping itself now lives on each engine (GetConnectionString(TenantDataSourceOverrideSettings), IDataSourceEngine.cs:59), and this validator's ConnectionStringFor is the single entry point the runtime paths call back into rather than resolving the engine themselves (ADR-073). Deriving Engines from the registry follows the same rule: DataSourceEngines documents that a fifth engine is one new class plus one registry line (DataSourceEngines.cs:14-17), and this validator picks it up with no edit. Making the resolver optional rather than required is what keeps the validator constructible in a host that binds tenancy without registering AddInfrastructure, at the cost of skipping the source-existence check there; the strategy and connection-string checks still run.
      • +
      • Where it's used: registered by AddMultiTenancy alongside ValidateOnStart on the options (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:276-288). Its ConnectionStringFor helper is called at run time by TenantDataSourceTargets.Expand when deciding whether a (tenant, source) pair is really overridden (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:73) and by DbContextFactory.ResolveTenantOverride when cloning the physical source for a tenant (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:178). Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/AddMultiTenancyTests.cs, which asserts the single-registration shape (:97-98) and drives the validator both without a resolver (:168) and with one (:206).
      • Caveats / not-in-source: the type is internal, so it is not part of the framework's public API and a consumer cannot subclass or replace it; a host needing extra tenancy rules registers its own additional IValidateOptions<TenancySettings>.
      -

      EntityTypeBuilderExtensions

      -
      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Configuration · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeBuilderExtensions.cs:13 · Level 5 · class (public static, extension container)

      -
      -
        -
      • What it is: a generic extension<TOwner>(EntityTypeBuilder<TOwner>) block with two members. OwnsMoney(...) maps a Money property as an owned type flattened into two columns on the owner's table: a decimal amount and an ISO 4217 currency code (EntityTypeBuilderExtensions.cs:13, :21-23, member at :51-81). OwnsAddress(...) does the same for an Address property, flattened into six columns (:125-171).
      • -
      • Depends on: Money and Currency from MMCA.Common.Shared.ValueObjects, Address and AddressInvariants from the same namespace, EF Core's EntityTypeBuilder<TEntity> and OwnsOne, and System.Linq.Expressions.
      • -
      • Concept introduced, the round-trip contract of a value converter. [Rubric §4, Domain-Driven Design] assesses whether value objects survive the trip to storage intact, and [Rubric §15, Best Practices and Code Quality] assesses whether edge cases are handled where they arise. A value converter has two legs, write and read, and correctness means every value the write leg can produce is a value the read leg can materialize. The doc comment at EntityTypeBuilderExtensions.cs:37-46 documents a real failure of that contract and its fix. An aggregate can seed a zero total with Money.Zero(), whose currency Code is the empty string, and leave it there; the write leg persists that empty code faithfully. On the way back, Currency.FromCode("") fails, and a bare .Value! turned those rows into a null Currency inside a Money, which is a materialization-time NullReferenceException waiting for the first read. The same applies to any code that has since dropped out of Currency.All. So the read leg coalesces to a sentinel instead.
      • -
      • Walkthrough
          -
        • NoCurrency (EntityTypeBuilderExtensions.cs:26) is that sentinel, obtained as Money.Zero().Currency. The comment at :14-18 explains the indirection: the "no currency" instance is internal to MMCA.Common.Shared, so a zero Money is the only public handle on it.
        • -
        • The extension is generic over the owner with a class constraint (EntityTypeBuilderExtensions.cs:28-29), so it applies to any entity that owns a Money.
        • -
        • The signature takes the navigation expression, the two column names, and a required flag defaulting to true (EntityTypeBuilderExtensions.cs:58-62). The parameter doc at :44-49 is honest about the design rule behind that: required is the only facet that differs across the existing call sites, so it is the only one parameterized beyond the two column names.
        • -
        • All four arguments are guarded, the strings with ArgumentException.ThrowIfNullOrWhiteSpace (EntityTypeBuilderExtensions.cs:64-67).
        • -
        • OwnsOne maps the two members (EntityTypeBuilderExtensions.cs:69-83): Amount gets its column name and IsRequired; Currency gets the two-leg conversion (write currency => currency.Code, read code => Currency.FromCode(code).Value ?? NoCurrency), plus HasMaxLength(3), IsUnicode(false), its column name, and IsRequired. That is the ISO 4217 code shape exactly: three non-Unicode characters.
        • -
        • builder.Navigation(navigationExpression).IsRequired(required) (EntityTypeBuilderExtensions.cs:85) is a separate call from the OwnsOne body because it configures the navigation rather than the owned entity's properties. The builder is returned for chaining (:80).
        • -
        • OwnsAddress(...) (EntityTypeBuilderExtensions.cs:125-171) maps Address's six fields (AddressLine1, AddressLine2, City, State, ZipCode, Country) to columns built by the private helper AddressColumn(columnPrefix, propertyName) (:185-188). Each column is columnPrefix + propertyName, except the redundant leading word Address is dropped from the two line properties first (AddressPropertyPrefix, :19), so the default "Address" prefix yields AddressLine1/AddressLine2 rather than AddressAddressLine1/AddressAddressLine2, and AddressCity, AddressState, AddressZipCode, AddressCountry for the rest; a second address on the same owner passes a different columnPrefix (for example "ShippingAddress") and gets ShippingAddressLine1, ShippingAddressCity, and so on. That is exactly the mapping the framework's own hand-written configurations already declared, so adopting the helper is a zero-diff model change: no migration, no snapshot churn.
        • -
        • Max lengths on the six columns come from AddressInvariants (EntityTypeBuilderExtensions.cs:138,144,149,154,159,164), the same constants the Address value object validates against, so a column can never be shorter than what the domain accepts; every column is non-Unicode varchar, and only AddressLine1 is required, matching the value object, whose other five fields are optional for international address formats. required on the navigation itself defaults to false (:128), matching the existing call sites where an owner may have no address at all.
        • -
        -
      • -
      • Why it's built this way: value objects are validated primitives (ADR-068), which means construction is guarded but persistence must still round-trip every value that construction allowed, including the Money zero sentinel. Flattening Money into two columns and Address into six on the owner's table rather than a separate table keeps a price, a total, or a location a single-row read. Putting each mapping behind one extension member means the read-leg fallback for Money, and the column-naming and invariant-length rules for Address, are each written once and cannot be forgotten by the next configuration that maps one.
      • -
      • Where it's used: OwnsMoney has three call sites, all in MMCA.Store's Sales and Catalog modules: MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Infrastructure/Persistence/EntityConfiguration/OrderConfiguration.cs:26 (Total, with required: false), .../OrderLineConfiguration.cs:28 (UnitPrice), and MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Infrastructure/Persistence/EntityConfiguration/ProductVariantConfiguration.cs:31 (Price); covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/OwnsMoneyTests.cs. OwnsAddress is covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/OwnsAddressTests.cs.
      • -
      -

      StronglyTypedIdModelConfiguration

      -
      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Conventions · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/StronglyTypedIdModelConfiguration.cs:21 · Level 6 · class (public static)

      -
      -
        -
      • What it is: the single call that wires strongly typed identifiers (ADR-115) into EF Core's model, once per host. Apply walks the host's StronglyTypedIdRegistry and declares one pre-convention type mapping per identifier type it describes, so every property of that type, keys, cross-module scalar references, owned-type members and the properties of a framework table alike, maps to its wrapped primitive (StronglyTypedIdModelConfiguration.cs:7-12, :29-51).
      • -
      • Depends on: EF Core's ModelConfigurationBuilder, StronglyTypedIdValueConverter<TSelf, TValue> and its comparer counterpart from MMCA.Common.Infrastructure.Persistence.Conversions, and StronglyTypedIdRegistry from MMCA.Common.Shared.Identifiers.
      • -
      • Concept introduced, pre-convention configuration versus a model-finalizing convention. [Rubric §8, Data Architecture] assesses whether a cross-cutting mapping rule is applied at the point in the pipeline where it actually works, and [Rubric §1, SOLID] assesses whether the opt-in stays orthogonal to code that never adopts it. EF Core exposes two very different places to declare a type-wide rule: ConfigureConventions (pre-convention, runs before the provider's own conventions see the model) and a model-finalizing IModelFinalizingConvention like SoftDeleteUniqueIndexConvention (runs last). The class doc comment states why this one has to be the former (StronglyTypedIdModelConfiguration.cs:14-18): a converter attached at finalization arrives after the provider's value-generation conventions have already inspected the property, so a wrapped int key would silently lose its IDENTITY strategy. Declaring the mapping here means the provider CLR type is known before any of that runs, and a wrapped key generates exactly as its primitive did.
      • -
      • Walkthrough
          -
        • Apply(configurationBuilder, registry) (StronglyTypedIdModelConfiguration.cs:29) null-guards both arguments, then iterates registry.Describe(), which yields the (identifierType, valueType) pairs the host declared (:36).
        • -
        • For each pair it closes the two open generics reflectively: StronglyTypedIdValueConverter<,>.MakeGenericType(identifierType, valueType) and StronglyTypedIdValueComparer<>.MakeGenericType(identifierType) (StronglyTypedIdModelConfiguration.cs:38-41), then calls configurationBuilder.Properties(identifierType).HaveConversion(converterType, comparerType) (:43-45), the pre-convention API that applies the conversion to every property of that CLR type across the whole model, not just one entity's.
        • -
        • configured counts the identifiers actually wired and is returned (StronglyTypedIdModelConfiguration.cs:34,47,50), giving the caller a number to log or assert against rather than a bare void.
        • -
        -
      • -
      • Why it's built this way: strongly typed identifiers sit on the same opt-in footing as smart enumerations (ADR-115): a wrapper is two lines, and adopting the whole feature is one DI call, services.AddStronglyTypedIds(typeof(OrderId).Assembly). The EF half of that contract has to be equally uniform, one call from ApplicationDbContext.ConfigureConventions, which every engine context inherits, so SQL Server, PostgreSQL, SQLite and Cosmos all get the same mapping without an engine branch (StronglyTypedIdModelConfiguration.cs:10-12). Driving the mapping from the registry rather than from a hard-coded type list means a host that never wraps an identifier calls Apply over an empty registry and nothing changes.
      • -
      • Where it's used: called once from ApplicationDbContext's ConfigureConventions (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs).
      • -

      DesignTimeDbContextHelper

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.DbContexts.Design · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextHelper.cs:43 · Level 13 · class (public static)

      @@ -7665,6 +8288,28 @@

      IEntityTypeConfigur
    514. Why it's built this way: the alternative is an attribute-only scheme, where every configuration is annotated and discovery is a reflection sweep over attributes. Putting the engine in the type system instead means the scan is a plain GetInterfaces() match (ModelBuilderExtensions.cs:48-50), the compiler tells a consumer immediately when a configuration implements two engines' contracts, and the entity type is available as a generic argument rather than as something else to look up.
    515. Where it's used: implemented by EntityTypeConfigurationBase<TEntity, TIdentifierType> (EntityTypeConfigurationBase.cs:19-20) and extended by the three engine markers, IEntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, IEntityTypeConfigurationSqlite<TEntity, TIdentifierType>, and IEntityTypeConfigurationCosmos<TEntity, TIdentifierType>.
    516. +

      EntityTypeBuilderExtensions

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Configuration · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeBuilderExtensions.cs:13 · Level 5 · class (public static, extension container)

      +
      +
        +
      • What it is: a generic extension<TOwner>(EntityTypeBuilder<TOwner>) block with two members. OwnsMoney(...) maps a Money property as an owned type flattened into two columns on the owner's table: a decimal amount and an ISO 4217 currency code (EntityTypeBuilderExtensions.cs:13, :21-23, member at :51-81). OwnsAddress(...) does the same for an Address property, flattened into six columns (:125-171).
      • +
      • Depends on: Money and Currency from MMCA.Common.Shared.ValueObjects, Address and AddressInvariants from the same namespace, EF Core's EntityTypeBuilder<TEntity> and OwnsOne, and System.Linq.Expressions.
      • +
      • Concept introduced, the round-trip contract of a value converter. [Rubric §4, Domain-Driven Design] assesses whether value objects survive the trip to storage intact, and [Rubric §15, Best Practices and Code Quality] assesses whether edge cases are handled where they arise. A value converter has two legs, write and read, and correctness means every value the write leg can produce is a value the read leg can materialize. The doc comment at EntityTypeBuilderExtensions.cs:37-46 documents a real failure of that contract and its fix. An aggregate can seed a zero total with Money.Zero(), whose currency Code is the empty string, and leave it there; the write leg persists that empty code faithfully. On the way back, Currency.FromCode("") fails, and a bare .Value! turned those rows into a null Currency inside a Money, which is a materialization-time NullReferenceException waiting for the first read. The same applies to any code that has since dropped out of Currency.All. So the read leg coalesces to a sentinel instead.
      • +
      • Walkthrough
          +
        • NoCurrency (EntityTypeBuilderExtensions.cs:26) is that sentinel, obtained as Money.Zero().Currency. The comment at :14-18 explains the indirection: the "no currency" instance is internal to MMCA.Common.Shared, so a zero Money is the only public handle on it.
        • +
        • The extension is generic over the owner with a class constraint (EntityTypeBuilderExtensions.cs:28-29), so it applies to any entity that owns a Money.
        • +
        • The signature takes the navigation expression, the two column names, and a required flag defaulting to true (EntityTypeBuilderExtensions.cs:58-62). The parameter doc at :44-49 is honest about the design rule behind that: required is the only facet that differs across the existing call sites, so it is the only one parameterized beyond the two column names.
        • +
        • All four arguments are guarded, the strings with ArgumentException.ThrowIfNullOrWhiteSpace (EntityTypeBuilderExtensions.cs:64-67).
        • +
        • OwnsOne maps the two members (EntityTypeBuilderExtensions.cs:69-83): Amount gets its column name and IsRequired; Currency gets the two-leg conversion (write currency => currency.Code, read code => Currency.FromCode(code).Value ?? NoCurrency), plus HasMaxLength(3), IsUnicode(false), its column name, and IsRequired. That is the ISO 4217 code shape exactly: three non-Unicode characters.
        • +
        • builder.Navigation(navigationExpression).IsRequired(required) (EntityTypeBuilderExtensions.cs:85) is a separate call from the OwnsOne body because it configures the navigation rather than the owned entity's properties. The builder is returned for chaining (:80).
        • +
        • OwnsAddress(...) (EntityTypeBuilderExtensions.cs:125-171) maps Address's six fields (AddressLine1, AddressLine2, City, State, ZipCode, Country) to columns built by the private helper AddressColumn(columnPrefix, propertyName) (:185-188). Each column is columnPrefix + propertyName, except the redundant leading word Address is dropped from the two line properties first (AddressPropertyPrefix, :19), so the default "Address" prefix yields AddressLine1/AddressLine2 rather than AddressAddressLine1/AddressAddressLine2, and AddressCity, AddressState, AddressZipCode, AddressCountry for the rest; a second address on the same owner passes a different columnPrefix (for example "ShippingAddress") and gets ShippingAddressLine1, ShippingAddressCity, and so on. That is exactly the mapping the framework's own hand-written configurations already declared, so adopting the helper is a zero-diff model change: no migration, no snapshot churn.
        • +
        • Max lengths on the six columns come from AddressInvariants (EntityTypeBuilderExtensions.cs:138,144,149,154,159,164), the same constants the Address value object validates against, so a column can never be shorter than what the domain accepts; every column is non-Unicode varchar, and only AddressLine1 is required, matching the value object, whose other five fields are optional for international address formats. required on the navigation itself defaults to false (:128), matching the existing call sites where an owner may have no address at all.
        • +
        +
      • +
      • Why it's built this way: value objects are validated primitives (ADR-068), which means construction is guarded but persistence must still round-trip every value that construction allowed, including the Money zero sentinel. Flattening Money into two columns and Address into six on the owner's table rather than a separate table keeps a price, a total, or a location a single-row read. Putting each mapping behind one extension member means the read-leg fallback for Money, and the column-naming and invariant-length rules for Address, are each written once and cannot be forgotten by the next configuration that maps one.
      • +
      • Where it's used: OwnsMoney has three call sites, all in MMCA.Store's Sales and Catalog modules: MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Infrastructure/Persistence/EntityConfiguration/OrderConfiguration.cs:26 (Total, with required: false), .../OrderLineConfiguration.cs:28 (UnitPrice), and MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Infrastructure/Persistence/EntityConfiguration/ProductVariantConfiguration.cs:31 (Price); covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/OwnsMoneyTests.cs. OwnsAddress is covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/OwnsAddressTests.cs.
      • +

      EntityTypeConfigurationBase<TEntity, TIdentifierType>

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfigurationBase.cs:19 · Level 5 · class (public abstract)

      @@ -7675,7 +8320,7 @@

      EntityTypeConfigurat
    517. Concept introduced, keeping a domain-only collection out of the model. An aggregate root collects domain events in memory so the dispatcher can drain them after a save. That collection is not state: it has no column, no table, and no meaning after the transaction closes. EF Core's convention-based model builder does not know that and would happily try to map it, which fails outright or (worse) invents a shadow table. One builder.Ignore call at the root of the hierarchy makes the exclusion automatic for every entity in every consumer, rather than a rule each configuration author has to remember. [Rubric §4, Domain-Driven Design] assesses whether the aggregate's event mechanism stays a domain concern; ignoring the collection is what keeps events a domain construct rather than a persisted one. [Rubric §9, API & Contract Design] assesses whether concerns that apply everywhere are handled once in a shared place; this is the smallest possible example of that, applied at the base class every configuration inherits.
    518. Walkthrough
      • Declaration (EntityTypeConfigurationBase.cs:19-22): abstract, generic over the entity and its identifier type, implementing the base interface. Being abstract means it is never discovered as a configuration itself: the scan skips abstract types (ModelBuilderExtensions.cs:44).
      • -
      • Configure is virtual (EntityTypeConfigurationBase.cs:25): the derived engine-aware class overrides it and calls base.Configure(builder) first, so this rule always runs before engine conventions (see EntityTypeConfiguration<TEntity, TIdentifierType>, EntityTypeConfiguration.cs:43).
      • +
      • Configure is virtual (EntityTypeConfigurationBase.cs:25): the derived engine-aware class overrides it and calls base.Configure(builder) first, so this rule always runs before engine conventions (see EntityTypeConfiguration<TEntity, TIdentifierType>, EntityTypeConfiguration.cs:41).
      • The aggregate-root test (EntityTypeConfigurationBase.cs:29): typeof(IAggregateRoot).IsAssignableFrom(typeof(TEntity)). The rule is applied only to aggregate roots, because only they carry the collection; a plain child entity configured through the same hierarchy is untouched.
      • The exclusion (EntityTypeConfigurationBase.cs:31): builder.Ignore(nameof(AuditableAggregateRootEntity<>.DomainEvents)). The unbound-generic nameof form is worth noticing: it names the member without having to close the generic, so the string stays refactor-safe.
      • What this class deliberately leaves to someone else (EntityTypeConfigurationBase.cs:10-15): the doc records that entity-to-data-source mapping is not registered here as a model-building side effect. EntityDataSourceRegistry derives that mapping eagerly from the configuration class's attributes (UseDataSourceAttribute for the engine, UseDatabaseAttribute or the module namespace for the database), which is what lets routing be known before any model is built.
      • @@ -7697,8 +8342,8 @@

        IEntityTypeConfig
      • new void Configure(...) (IEntityTypeConfigurationCosmos.cs:17): the redeclaration the base interface exists to enable.
    519. -
    520. Caveats / not-in-source: implementing this interface directly, without the attributed base class, is a supported but degraded path. EntityDataSourceRegistry skips configurations that carry no UseDataSourceAttribute (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/EntityDataSourceRegistry.cs:174-178), so such an entity lands in the engine's Default model but is not routable through IUnitOfWork; the code comments call this legacy behavior (ApplicationDbContext.cs:961-965).
    521. -
    522. Where it's used: matched by ApplicationDbContext.ApplyConfigurationsForEntitiesInContext for the Cosmos engine (ApplicationDbContext.cs:952-959) and implemented by EntityTypeConfiguration<TEntity, TIdentifierType> (EntityTypeConfiguration.cs:34).
    523. +
    524. Caveats / not-in-source: implementing this interface directly, without the attributed base class, is a supported but degraded path. EntityDataSourceRegistry skips configurations that carry no UseDataSourceAttribute (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/EntityDataSourceRegistry.cs:174-178), so such an entity lands in the engine's Default model but is not routable through IUnitOfWork; the code comments call this legacy behavior (ApplicationDbContext.cs:957-961).
    525. +
    526. Where it's used: matched by ApplicationDbContext.ApplyConfigurationsForEntitiesInContext for the Cosmos engine (ApplicationDbContext.cs:952-959) and implemented by EntityTypeConfiguration<TEntity, TIdentifierType> (EntityTypeConfiguration.cs:32).
    527. IEntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType>

      @@ -7708,7 +8353,7 @@

      IEntityTypeCo
    528. What it is: the engine marker for PostgreSQL configurations (IEntityTypeConfigurationPostgreSQL.cs:13), the fourth member of the marker-interface family alongside Cosmos, Sqlite, and SQL Server; structurally identical to its siblings.
    529. Depends on: IEntityTypeConfigurationBase<TEntity, TIdentifierType> (extends it, IEntityTypeConfigurationPostgreSQL.cs:13); AuditableBaseEntity<TIdentifierType> (constraint, :14); EF Core's EntityTypeBuilder<TEntity> (NuGet, :1).
    530. Concept reinforced: engine selection by interface identity, taught at IEntityTypeConfigurationBase<TEntity, TIdentifierType>. DataSource.PostgreSQL maps to typeof(IEntityTypeConfigurationPostgreSQL<,>) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:955), the case added alongside the other three when PostgreSQL became a first-class engine (ADR-113).
    531. -
    532. Where it's used: matched by PostgreSQLDbContext through the shared discovery method, and implemented by EntityTypeConfiguration<TEntity, TIdentifierType> (EntityTypeConfiguration.cs:32).
    533. +
    534. Where it's used: matched by PostgreSQLDbContext through the shared discovery method, and implemented by EntityTypeConfiguration<TEntity, TIdentifierType> (EntityTypeConfiguration.cs:30).
    535. IEntityTypeConfigurationSqlite<TEntity, TIdentifierType>

      @@ -7718,7 +8363,7 @@

      IEntityTypeConfig
    536. What it is: the engine marker for SQLite configurations (IEntityTypeConfigurationSqlite.cs:13), structurally identical to its Cosmos and SQL Server siblings.
    537. Depends on: IEntityTypeConfigurationBase<TEntity, TIdentifierType> (IEntityTypeConfigurationSqlite.cs:13); AuditableBaseEntity<TIdentifierType> (:14); EF Core's EntityTypeBuilder<TEntity> (:1).
    538. Concept reinforced: engine selection by interface identity, taught at IEntityTypeConfigurationBase<TEntity, TIdentifierType>. DataSource.Sqlite maps to typeof(IEntityTypeConfigurationSqlite<,>) (ApplicationDbContext.cs:956).
    539. -
    540. Where it's used: matched by SqliteDbContext through the shared discovery method, implemented by EntityTypeConfiguration<TEntity, TIdentifierType> (EntityTypeConfiguration.cs:33), and used directly by the framework's own tests as the scan target: the ApplyAllConfigurations tests pass typeof(IEntityTypeConfigurationSqlite<,>) as the interface to match (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/ModelBuilderExtensionsTests.cs:93, :148), and two test types implement it directly to exercise the unattributed path (ModelBuilderExtensionsTests.cs:108, MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/EntityDataSourceRegistryTests.cs:233).
    541. +
    542. Where it's used: matched by SqliteDbContext through the shared discovery method, implemented by EntityTypeConfiguration<TEntity, TIdentifierType> (EntityTypeConfiguration.cs:31), and used directly by the framework's own tests as the scan target: the ApplyAllConfigurations tests pass typeof(IEntityTypeConfigurationSqlite<,>) as the interface to match (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/ModelBuilderExtensionsTests.cs:93, :148), and two test types implement it directly to exercise the unattributed path (ModelBuilderExtensionsTests.cs:108, MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/EntityDataSourceRegistryTests.cs:233).
    543. IEntityTypeConfigurationSQLServer<TEntity, TIdentifierType>

      @@ -7728,31 +8373,7 @@

      IEntityTypeCon
    544. What it is: the engine marker for SQL Server configurations (IEntityTypeConfigurationSQLServer.cs:13). It is the one of the three that matters in production today, because every deployed entity in ADC and Store routes to SQL Server.
    545. Depends on: IEntityTypeConfigurationBase<TEntity, TIdentifierType> (IEntityTypeConfigurationSQLServer.cs:13); AuditableBaseEntity<TIdentifierType> (:14); EF Core's EntityTypeBuilder<TEntity> (:1).
    546. Concept reinforced: engine selection by interface identity. DataSource.SQLServer maps to typeof(IEntityTypeConfigurationSQLServer<,>) (ApplicationDbContext.cs:957); anything the switch does not recognize throws InvalidOperationException rather than silently building an empty model (ApplicationDbContext.cs:958).
    547. -
    548. Where it's used: matched by SQLServerDbContext through ApplyConfigurationsForEntitiesInContext, and implemented by EntityTypeConfiguration<TEntity, TIdentifierType> (EntityTypeConfiguration.cs:31), which is how all 28 ADC configurations and all 12 Store configurations reach it through the EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> shim.
    549. - -

      EntityTypeConfiguration<TEntity, TIdentifierType>

      -
      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:29 · Level 6 · class (public abstract)

      -
      -
        -
      • What it is: the engine-aware configuration base and the busiest type in this family. It reads the target engine off a UseDataSourceAttribute on the concrete configuration class (or on an inherited shim base) and applies that engine's table/container mapping plus key generation, so a consumer's Configure body only ever describes columns, indexes, and relationships (EntityTypeConfiguration.cs:29-107).
      • -
      • Depends on: EntityTypeConfigurationBase<TEntity, TIdentifierType> (base, EntityTypeConfiguration.cs:30); all four engine markers, IEntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, IEntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType>, IEntityTypeConfigurationSqlite<TEntity, TIdentifierType>, IEntityTypeConfigurationCosmos<TEntity, TIdentifierType> (:31-34); UseDataSourceAttribute and the DataSource enum (:4, :45, :59); NamespaceConventions (:74, :95); CosmosIntIdValueGenerator (:7, :99); the IsIdValueGenerated extension property from EntityTypeExtensions (:63), which is a lookup for IdValueGeneratedAttribute (MMCA.Common/Source/Core/MMCA.Common.Domain/Extensions/EntityTypeExtensions.cs:19); System.Reflection and EF Core's EntityTypeBuilder<TEntity> (:1-3).
      • -
      • Concept introduced, one configuration body that is portable across storage engines. The naive way to support four engines is four configuration classes per entity, or one class littered with if (engine == ...). This class removes both. The engine is declared once, as an attribute, and everything that actually differs between engines is centralized in a single switch here: SQL Server and PostgreSQL share one branch and get a table in a module schema, SQLite gets a bare table (SQLite has no schemas), Cosmos gets a per-module container with the entity id as partition key (EntityTypeConfiguration.cs:65-106). Moving an entity from SQL Server to Cosmos, or from SQL Server to PostgreSQL, is therefore a one-line attribute change. Two other pieces of the framework complete the portability claim rather than this class alone: CosmosDbContext strips every relational index from the built model, because the Cosmos provider rejects them and indexes every property itself (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/CosmosDbContext.cs:83-91), and CrossDataSourceDegradeConvention removes FK constraints and navigations that would span physical sources while keeping the declared scalar FK column and a compensating index (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:9-21). A dedicated test builds a Cosmos model offline from a configuration that keeps a filtered index and a cross-source relationship, and asserts the indexes are gone, the FK is gone, the scalar FK column survives, and the foreign principal is not in the model (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/CosmosConfigurationPortabilityTests.cs:70-76). [Rubric §8, Data Architecture] assesses how deliberately the storage shape is chosen and how tightly the model is bound to one engine. [Rubric §7, Microservices Readiness] assesses whether a module can be lifted out without a rewrite; being able to re-point an entity's engine and database with attributes is a precondition for that (ADR-018, ADR-006, ADR-113). [Rubric §15, Best Practices & Code Quality] assesses whether one decision lives in one place; the per-engine differences live in exactly one switch.
      • -
      • Concept introduced, discovery and routing that agree by construction. This class implements all four engine marker interfaces (EntityTypeConfiguration.cs:31-34), so a configuration derived from it is discovered during every engine's model pass. That sounds wrong until you see the filter: ApplyConfigurationsForEntitiesInContext applies a discovered configuration only when EntityDataSourceRegistry says the entity's DataSourceKey equals this context instance's key (ApplicationDbContext.cs:970-976). Both sides read the same attributes: the registry derives the engine from UseDataSourceAttribute and the logical database from UseDatabaseAttribute, falling back to the entity's module namespace and then to Default (EntityDataSourceRegistry.cs:174-182), while this class reads the same attribute for its conventions. Discovery is broad, routing is exact, and there is no second source of truth to drift. [Rubric §1, SOLID] assesses whether behavior is driven from one declaration; here the attribute is that declaration.
      • -
      • Walkthrough
          -
        • Declaration (EntityTypeConfiguration.cs:29-36): abstract, extends the base class, implements the four markers, constrained to an audited entity with a non-null identifier type.
        • -
        • Configure override (EntityTypeConfiguration.cs:39-51): null-guards the builder (:41), calls base.Configure(builder) so the DomainEvents exclusion runs first (:43), then reads the engine.
        • -
        • The attribute read and its failure mode (EntityTypeConfiguration.cs:45-48): GetType().GetCustomAttribute<UseDataSourceAttribute>()?.DataSource on the runtime type, which is the concrete configuration. UseDataSourceAttribute is declared with Inherited = true (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/UseDataSourceAttribute.cs:12), which is exactly why a class deriving from one of the shim bases inherits its engine without repeating the annotation. When the attribute is missing entirely the code throws InvalidOperationException naming the offending configuration class, rather than defaulting to an engine and mapping the entity somewhere surprising.
        • -
        • ApplyEngineConventions(builder, engine), protected static (EntityTypeConfiguration.cs:59-107): extracted as a static helper so the provider shims share the identical logic. It reads typeof(TEntity).IsIdValueGenerated once (:63), which is the presence test for IdValueGeneratedAttribute on the entity (EntityTypeExtensions.cs:19).
        • -
        • SQL Server / PostgreSQL branch (EntityTypeConfiguration.cs:72-80): one case block deliberately shared by DataSource.SQLServer and DataSource.PostgreSQL (:72-73), documented inline as taking the SQL Server mapping unchanged so an entity moves between the two engines by changing its configuration base class and nothing else, with PostgreSQL house style (snake_case, the public schema) left to a consumer's own naming-convention plugin rather than a framework decision (ADR-113, :67-71). The shared branch: ToTable(entityName, moduleName ?? "dbo"), so the SQL schema is the module name derived from the entity namespace and unmoduled entities land in dbo; HasKey(p => p.Id); then ValueGeneratedOnAdd() when the entity opts into generated ids, ValueGeneratedNever() otherwise. That last branch is what lets a domain factory assign an id itself without EF overwriting it.
        • -
        • SQLite branch (EntityTypeConfiguration.cs:82-89): ToTable(entityName) with no schema, and the generated-id case adds .UseIdentityColumn(1, 1) on top of ValueGeneratedOnAdd().
        • -
        • Cosmos branch (EntityTypeConfiguration.cs:91-102): ToContainer(moduleName ?? entityName).HasPartitionKey(p => p.Id), plus HasValueGenerator<CosmosIntIdValueGenerator>() for generated ids. The inline comment records the reasoning for one container per module (:92-93): all of a module's entities share a container so their relationships and the navigation populators still work, and the entity id doubles as the partition key.
        • -
        • The default arm (EntityTypeConfiguration.cs:104-105): an unimplemented engine throws instead of silently producing an unmapped entity.
        • -
        • NamespaceConventions.GetModuleName (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/NamespaceConventions.cs:20-21): an internal one-line delegation to ModuleNameConventions.GetModuleName, documented as taking the namespace segment immediately preceding Domain (MMCA.Store.Sales.Domain.Orders yields Sales) and returning null when there is no Domain segment (NamespaceConventions.cs:13-19). The same helper feeds the logical database name in the registry (EntityDataSourceRegistry.cs:181), which is the point of it being shared: schema and database name can never drift apart.
        • -
        -
      • -
      • Why it's built this way: the framework's stated multi-database model is one context class per engine and one instance per database (ADR-006), with the engine as an orthogonal axis (ADR-018). That only works if the mapping conventions for an engine live in one place that every context can call, which is this class. Keeping ApplyEngineConventions static and protected rather than inlining it in Configure is what allows the shims to exist as empty declarations, and it is why adding PostgreSQL as a fourth engine (ADR-113) cost one case label rather than a new mapping method.
      • -
      • Where it's used: extended by the four shim bases below (EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, EntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType>, EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, EntityTypeConfigurationCosmos<TEntity, TIdentifierType>), and directly by any configuration that prefers to carry its own [UseDataSource(...)] annotation, as one portability test does (CosmosConfigurationPortabilityTests.cs:101-103). Its conventions are pinned against a real SQLite model in SqliteConfig_Configure_SetsTableNameAndKey and SqliteConfig_Configure_SetsValueGeneratedNever_WhenNoAttribute (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/EntityTypeConfigurationTests.cs:22, :37).
      • +
      • Where it's used: matched by SQLServerDbContext through ApplyConfigurationsForEntitiesInContext, and implemented by EntityTypeConfiguration<TEntity, TIdentifierType> (EntityTypeConfiguration.cs:29), which is how all 28 ADC configurations and all 12 Store configurations reach it through the EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> shim.

      ReadRepositoryExtensions

      @@ -7765,7 +8386,7 @@

      ReadRepositoryExtensions

    550. Walkthrough
      • extension<TEntity, TIdentifierType>(IReadRepository<TEntity, TIdentifierType> repository) (ReadRepositoryExtensions.cs:12-14): a generic extension block whose receiver is the repository; the constraints mirror the interface exactly (TEntity : AuditableBaseEntity<TIdentifierType>, TIdentifierType : notnull).
      • GetByIdOrFailAsync(id, source, includes, asTracking, cancellationToken) (ReadRepositoryExtensions.cs:27-32): note the parameters. source is a string the caller passes (typically its own type name) so the resulting error can name who produced it; includes and asTracking are passed straight through, and asTracking defaults to true, matching the command-handler case where the loaded entity is about to be modified.
      • -
      • The lookup (ReadRepositoryExtensions.cs:34-38): it calls GetAllAsync with where: e => e.Id.Equals(id) rather than a keyed fetch. That is deliberate: GetAllAsync is the overload that takes the includes collection plus tracking (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:85-92, declared on IEntityQuerier<TEntity, TIdentifierType>, which IReadRepository composes at IRepository.cs:330-331), so the helper participates in the full eager-loading pipeline. includes ?? [] keeps the parameter optional.
      • +
      • The lookup (ReadRepositoryExtensions.cs:34-38): it calls GetAllAsync with where: e => e.Id.Equals(id) rather than a keyed fetch. That is deliberate: GetAllAsync is the overload that takes the includes collection plus tracking (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:85-92, declared on IEntityQuerier<TEntity, TIdentifierType>, which IReadRepository composes at IRepository.cs:332-333), so the helper participates in the full eager-loading pipeline. includes ?? [] keeps the parameter optional.
      • The failure branch (ReadRepositoryExtensions.cs:40-45): entities.FirstOrDefault(), and when it is null, Error.NotFound.WithSource(source).WithTarget(typeof(TEntity).Name). Error.NotFound is the shared static instance (MMCA.Common/Source/Core/MMCA.Common.Shared/Abstractions/Error.cs:23) and the two With* calls return copies (Error.cs:120, :126), so the shared instance is never mutated and the caller gets an error that names both the caller and the entity type.
      • The success branch (ReadRepositoryExtensions.cs:47): Result.Success(entity).
      @@ -7774,6 +8395,48 @@

      ReadRepositoryExtensions

    551. Where it's used: the only current callers in the workspace are its own tests (MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Extensions/ReadRepositoryExtensionsTests.cs:15, :40); no ADC or Store handler calls it today, and handlers there still do the explicit null check. It is available to any handler that resolves a read repository through IUnitOfWork.GetReadRepository.
    552. Caveats / not-in-source: the method loads through GetAllAsync, so it materializes a collection and takes the first element rather than issuing a keyed FindAsync; whether that costs anything at the database depends on the provider's translation of the Id.Equals(id) predicate and is not determinable from source.
    553. +

      IndexBuilderExtensions

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Configuration · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/IndexBuilderExtensions.cs:10 · Level 2 · class (public static, extension container)

      +
      +
        +
      • What it is: a single C# extension(IndexBuilder) block exposing one member, HasSoftDeleteFilter(...), which attaches the IsDeleted = 0 predicate to a hand-authored index in an entity type configuration (IndexBuilderExtensions.cs:10-12, member at :50-64).
      • +
      • Depends on: SoftDeleteFilterSql (the shared predicate builder, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:27), the DataSource engine enum, and EF Core's Microsoft.EntityFrameworkCore.Metadata.Builders.IndexBuilder.
      • +
      • Concept introduced, the filtered (partial) index under soft delete. [Rubric §8, Data Architecture] assesses whether the storage design accounts for the consequences of its own conventions, and [Rubric §12, Performance and Scalability] assesses whether indexes match the queries they serve. Soft delete (ADR-005) means a "deleted" row is still physically present with IsDeleted = 1, and the global query filter on ApplicationDbContext hides it from every read. An index that does not carry the same predicate therefore indexes rows no query will ever return: the deleted rows still occupy index pages, and the optimizer's row estimates include them. The doc comment states this in one sentence (IndexBuilderExtensions.cs:14-18). Adding WHERE [IsDeleted] = 0 to the index makes it a filtered index (SQL Server's term; SQLite calls the same thing a partial index) that matches the shape of every query the application actually issues.
      • +
      • Walkthrough
          +
        • The extension receiver is the IndexBuilder returned by builder.HasIndex(...), so the call chains directly off the index declaration (IndexBuilderExtensions.cs:12, usage sample at :23-26).
        • +
        • engine defaults to DataSource.SQLServer (IndexBuilderExtensions.cs:53), which matches the majority case of a configuration deriving from EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>; a SQLite or PostgreSQL configuration passes DataSource.Sqlite or DataSource.PostgreSQL explicitly. PostgreSQL needs it for a real reason, not just symmetry: its soft-delete flag is a genuine boolean column, so the predicate the engine produces is "IsDeleted" = false rather than SQL Server's [IsDeleted] = 0 (IndexBuilderExtensions.cs:37-43).
        • +
        • additionalFilter is the optional second predicate for an index that is already conditional on something else, for example "[ExternalSessionId] IS NOT NULL" (IndexBuilderExtensions.cs:45-50). The two are joined as {additionalFilter} AND {filterSql} in that exact order (:62-65), which is deliberate: it reproduces the SQL of the hand-authored literal the helper replaced, so switching to the helper does not force a migration. It is also the order SoftDeleteUniqueIndexConvention uses when it appends its own clause, so the two paths yield byte-identical SQL for the same pair of predicates.
        • +
        • The body null-guards the receiver, asks SoftDeleteFilterSql to build the predicate from the model's own metadata, and returns the builder unchanged when the builder returns null (IndexBuilderExtensions.cs:56-60). That null is the Cosmos no-op and the "this entity is not soft-deletable" case, so the call is always safe to write.
        • +
        +
      • +
      • Why it's built this way: the doc comment gives the rationale directly (IndexBuilderExtensions.cs:27-29). A literal HasFilter("[IsDeleted] = 0") hard-codes both the column name and SQL Server's bracket quoting; reading the column name from the model means a HasColumnName rename follows automatically, and delegating the quoting to the engine keeps the same configuration body valid on SQLite. This is the portability posture of ADR-018. The division of labour with SoftDeleteUniqueIndexConvention is explicit: the convention covers every UNIQUE index automatically, and this extension point exists for the case the convention deliberately leaves alone, a hand-authored NON-unique index (IndexBuilderExtensions.cs:20-22). The pair of them is ADR-095.
      • +
      • Where it's used: entity configurations across both applications. In MMCA.Store: MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Infrastructure/Persistence/EntityConfiguration/OrderConfiguration.cs:44,51,58 (the last one with additionalFilter: "[StripeSessionId] IS NOT NULL"), MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Infrastructure/Persistence/EntityConfiguration/UserConfiguration.cs:69,83, .../EntityConfiguration/CustomerConfiguration.cs:82, MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Infrastructure/Persistence/EntityConfiguration/ProductConfiguration.cs:43, .../ProductImageConfiguration.cs:54, .../ProductVariantConfiguration.cs:46, .../CategoryConfiguration.cs:33. In MMCA.ADC's Engagement module: MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/CheckIns/CheckInConfiguration.cs:51,56,62,66,69, .../PointsEntryConfiguration.cs:48,52, .../LivePollVoteConfiguration.cs:37, .../LeaderboardOptInConfiguration.cs:35, .../SessionQuestionUpvoteConfiguration.cs:34, .../UserSessionBookmarkConfiguration.cs:34,39. Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/IndexBuilderExtensionsTests.cs.
      • +
      • Caveats / not-in-source: the ordering constraint is called out in the doc comment (IndexBuilderExtensions.cs:31-35). Unlike the convention, which runs at model finalizing, this member reads the column name at the moment it is called, so a HasColumnName on the soft-delete property must be declared before the index. That only bites a model that renames the column.
      • +
      +

      EntityTypeConfiguration<TEntity, TIdentifierType>

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:27 · Level 12 · class (public abstract)

      +
      +
        +
      • What it is: the engine-aware configuration base and the busiest type in this family. It reads the target engine off a UseDataSourceAttribute on the concrete configuration class (or on an inherited shim base) and hands that engine's table/container mapping plus key generation to the matching engine strategy, so a consumer's Configure body only ever describes columns, indexes, and relationships (EntityTypeConfiguration.cs:27-79). It also exposes EffectiveEngine, the engine of the model actually being built, for the few configurations that write engine-specific SQL text themselves (:54-61).
      • +
      • Depends on: EntityTypeConfigurationBase<TEntity, TIdentifierType> (base, EntityTypeConfiguration.cs:28); all four engine markers, IEntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, IEntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType>, IEntityTypeConfigurationSqlite<TEntity, TIdentifierType>, IEntityTypeConfigurationCosmos<TEntity, TIdentifierType> (:29-32); AuditableBaseEntity<TIdentifierType> as the entity constraint (:4, :33); UseDataSourceAttribute and the DataSource enum (:3, :43, :61, :69); DataSourceEngines (:5, :77), which returns the registered IDataSourceEngine for an engine value (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/DataSourceEngines.cs:40-43); the internal EngineAnnotation constant of ApplicationDbContext (:48, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:67); System.Reflection and EF Core's EntityTypeBuilder<TEntity> (:1-2).
      • +
      • Concept introduced, one configuration body that is portable across storage engines. The naive way to support four engines is four configuration classes per entity, or one class littered with if (engine == ...). This class removes both. The engine is declared once, as an attribute, and everything that actually differs between engines is owned by one strategy per engine: this class looks the strategy up with DataSourceEngines.For(engine) and calls its ApplyKeyAndTableMapping (EntityTypeConfiguration.cs:77, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/IDataSourceEngine.cs:95). SQLServerDataSourceEngine and PostgreSQLDataSourceEngine apply the identical mapping, a table in a module schema (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SQLServerDataSourceEngine.cs:100-106, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/PostgreSQLDataSourceEngine.cs:98-101); SqliteDataSourceEngine gives a bare table, since SQLite has no schemas (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SqliteDataSourceEngine.cs:97-102); CosmosDataSourceEngine gives a per-module container with the entity id as partition key (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/CosmosDataSourceEngine.cs:92-101). Moving an entity from SQL Server to Cosmos, or from SQL Server to PostgreSQL, is therefore a one-line attribute change. Two other pieces of the framework complete the portability claim rather than this class alone: CosmosDbContext strips every relational index from the built model, because the Cosmos provider rejects them and indexes every property itself (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/CosmosDbContext.cs:129-137), and CrossDataSourceDegradeConvention removes FK constraints and navigations that would span physical sources while keeping the declared scalar FK column and a compensating index (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:10-22). A dedicated test builds a Cosmos model offline from a configuration that keeps a filtered index and a cross-source relationship, and asserts the indexes are gone, the FK is gone, the scalar FK column survives, and the foreign principal is not in the model (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/CosmosConfigurationPortabilityTests.cs:70-76). [Rubric §8, Data Architecture] assesses how deliberately the storage shape is chosen and how tightly the model is bound to one engine. [Rubric §7, Microservices Readiness] assesses whether a module can be lifted out without a rewrite; being able to re-point an entity's engine and database with attributes is a precondition for that (ADR-018, ADR-006, ADR-113). [Rubric §15, Best Practices & Code Quality] assesses whether one decision lives in one place; each engine's mapping lives in exactly one strategy, and this file names no concrete engine value at all. That last property is enforced: DataSourceBranchingFitnessTests scans Source/**/*.cs and fails on any concrete engine named outside an exact allow-list, which lists the four shim bases but not this class (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/DataSourceBranchingFitnessTests.cs:24-68, :71-80; ADR-130).
      • +
      • Concept introduced, discovery and routing that agree by construction. This class implements all four engine marker interfaces (EntityTypeConfiguration.cs:29-32), so a configuration derived from it is discovered during every engine's model pass. That sounds wrong until you see the filter: ApplyConfigurationsForEntitiesInContext applies a discovered configuration only when EntityDataSourceRegistry says the entity's DataSourceKey equals this context instance's key (ApplicationDbContext.cs:978-980). Both sides read the same attributes: the registry derives the engine from UseDataSourceAttribute and the logical database from UseDatabaseAttribute, falling back to the entity's module namespace and then to Default (EntityDataSourceRegistry.cs:180-182), while this class reads the same attribute for its conventions. Discovery is broad, routing is exact, and there is no second source of truth to drift. [Rubric §1, SOLID] assesses whether behavior is driven from one declaration; here the attribute is that declaration.
      • +
      • Concept introduced, declared engine versus effective engine. The declared engine and the engine of the model being built usually agree, but not always: when a host substitutes an unconfigured engine (the doc comment's example is a SQL Server configuration applied to a PostgreSQL-only host), the configuration's own attribute is the wrong input for hand-written SQL such as an index filter (EntityTypeConfiguration.cs:54-60). ApplyConfigurationsForEntitiesInContext therefore stamps the context's engine onto the model as an annotation for the duration of the configuration pass and removes it in a finally, so the finished model and every migration snapshot are unchanged (ApplicationDbContext.cs:964-969, :983-986). Configure copies that annotation into EffectiveEngine, falling back to the declared engine when no annotation is present (EntityTypeConfiguration.cs:48-49). The key and table mapping still uses the declared engine (:51); EffectiveEngine is for SQL text only. The framework's own consumers show the use: PushNotificationConfiguration builds a filtered unique index whose predicate is quoted for EffectiveEngine through SoftDeleteFilterSql (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/Notifications/PushNotificationConfiguration.cs:72-73), and UserNotificationConfiguration passes it to HasSoftDeleteFilter (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/Notifications/UserNotificationConfiguration.cs:45).
      • +
      • Walkthrough
          +
        • Declaration (EntityTypeConfiguration.cs:27-34): abstract, extends the base class, implements the four markers, constrained to an audited entity with a non-null identifier type.
        • +
        • Configure override (EntityTypeConfiguration.cs:37-52): null-guards the builder (:39), calls base.Configure(builder) so the DomainEvents exclusion runs first (:41), then reads the engine.
        • +
        • The attribute read and its failure mode (EntityTypeConfiguration.cs:43-46): GetType().GetCustomAttribute<UseDataSourceAttribute>()?.DataSource on the runtime type, which is the concrete configuration. UseDataSourceAttribute is declared with Inherited = true (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/UseDataSourceAttribute.cs:12), which is exactly why a class deriving from one of the shim bases inherits its engine without repeating the annotation. When the attribute is missing entirely the code throws InvalidOperationException naming the offending configuration class, rather than defaulting to an engine and mapping the entity somewhere surprising.
        • +
        • EffectiveEngine (EntityTypeConfiguration.cs:48-49, :61): protected with a private set, assigned from the model's engine annotation or the declared engine, then ApplyEngineConventions(builder, engine) runs with the declared engine (:51). The doc comment states it is valid inside Configure after the base call (:58-59).
        • +
        • ApplyEngineConventions(builder, engine), protected static (EntityTypeConfiguration.cs:69-78): extracted as a static helper so the provider shims share the identical logic. After its own null guard (:71) it is a single delegation, DataSourceEngines.For(engine).ApplyKeyAndTableMapping<TEntity, TIdentifierType>(builder) (:77); the inline comment records that each engine owns its mapping, that PostgreSQL takes the SQL Server mapping unchanged (ADR-113), and that Cosmos puts a module's entities in one container partitioned by id (:73-76). An engine value with no registered strategy throws InvalidOperationException ("DataSource ... not implemented") from DataSourceEngines.For instead of silently producing an unmapped entity (DataSourceEngines.cs:40-43).
        • +
        • What the strategies apply: every engine calls HasKey(p => p.Id) and reads typeof(TEntity).IsIdValueGenerated, the presence test for IdValueGeneratedAttribute via EntityTypeExtensions (MMCA.Common/Source/Core/MMCA.Common.Domain/Extensions/EntityTypeExtensions.cs:19). SQL Server and PostgreSQL map ToTable(entityName, moduleName ?? "dbo"), so the SQL schema is the module name derived from the entity namespace and unmoduled entities land in dbo, then ValueGeneratedOnAdd() when the entity opts into generated ids and ValueGeneratedNever() otherwise (SQLServerDataSourceEngine.cs:101-106). That last branch is what lets a domain factory assign an id itself without EF overwriting it. SQLite maps ToTable(entityName) with no schema and adds .UseIdentityColumn(1, 1) in the generated-id case (SqliteDataSourceEngine.cs:97-102). Cosmos maps ToContainer(moduleName ?? entityName).HasPartitionKey(p => p.Id) and uses CosmosIntIdValueGenerator for generated ids (CosmosDataSourceEngine.cs:94-101).
        • +
        • NamespaceConventions.GetModuleName (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/NamespaceConventions.cs:20-21): an internal one-line delegation to ModuleNameConventions.GetModuleName, documented as taking the namespace segment immediately preceding Domain (MMCA.Store.Sales.Domain.Orders yields Sales) and returning null when there is no Domain segment (NamespaceConventions.cs:13-19). The engine strategies use it for the schema and container name (SQLServerDataSourceEngine.cs:101, CosmosDataSourceEngine.cs:95), and the same helper feeds the logical database name in the registry (EntityDataSourceRegistry.cs:181), which is the point of it being shared: schema and database name can never drift apart.
        • +
        +
      • +
      • Why it's built this way: the framework's stated multi-database model is one context class per engine and one instance per database (ADR-006), with the engine as an orthogonal axis (ADR-018). That only works if the mapping conventions for an engine live in one place that every context can call. This class is the single entry point and the engine strategy is that place (ADR-130), so adding an engine means one more strategy in the DataSourceEngines registry (DataSourceEngines.cs:22-28) rather than an edit here. Keeping ApplyEngineConventions static and protected rather than inlining it in Configure is what allows the shims to exist as empty declarations.
      • +
      • Where it's used: extended by the four shim bases below (EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, EntityTypeConfigurationPostgreSQL<TEntity, TIdentifierType>, EntityTypeConfigurationSqlite<TEntity, TIdentifierType>, EntityTypeConfigurationCosmos<TEntity, TIdentifierType>), and directly by any configuration that prefers to carry its own [UseDataSource(...)] annotation, as one portability test does (CosmosConfigurationPortabilityTests.cs:101-103). EffectiveEngine is read by the framework's notification configurations (PushNotificationConfiguration.cs:72-73, UserNotificationConfiguration.cs:45). Its conventions are pinned against a real SQLite model in SqliteConfig_Configure_SetsTableNameAndKey and SqliteConfig_Configure_SetsValueGeneratedNever_WhenNoAttribute (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/EntityTypeConfigurationTests.cs:22, :37).
      • +

      EntityTypeConfigurationCosmos<TEntity, TIdentifierType>

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfigurationCosmos.cs:18 · Level 7 · class (public abstract)

      @@ -7791,7 +8454,7 @@

      EntityTypeConf
      • What it is: the PostgreSQL shim, [UseDataSource(DataSource.PostgreSQL)] over the engine-aware base (EntityTypeConfigurationPostgreSQL.cs:21-25). Its own doc records that the mapping it produces is the SQL Server one, not PostgreSQL house style: a table per entity inside the module schema with an identity key, so an entity moves between the two engines with a single base-class change and no configuration-body edits (:6-17).
      • Depends on: EntityTypeConfiguration<TEntity, TIdentifierType> (base, EntityTypeConfigurationPostgreSQL.cs:23); UseDataSourceAttribute and DataSource (:21); AuditableBaseEntity<TIdentifierType> (:24).
      • -
      • Concept reinforced: the attribute-as-base-class shim taught at EntityTypeConfigurationCosmos<TEntity, TIdentifierType>. Unlike the Cosmos and SQLite shims it delegates to no engine-specific mapping code of its own: the engine-aware base's SQL Server branch handles DataSource.SQLServer and DataSource.PostgreSQL together in one case block, deliberately, so PostgreSQL takes the SQL Server table/key mapping unchanged (EntityTypeConfiguration.cs:72-80); PostgreSQL house style (snake_case identifiers, the public schema) is left to a consumer's own naming-convention plugin rather than built into the framework (ADR-113, EntityTypeConfiguration.cs:67-71).
      • +
      • Concept reinforced: the attribute-as-base-class shim taught at EntityTypeConfigurationCosmos<TEntity, TIdentifierType>. The engine-aware base hands the table/key mapping to the target engine (DataSourceEngines.For(engine).ApplyKeyAndTableMapping, EntityTypeConfiguration.cs:73-77), and the PostgreSQL engine's implementation is deliberately identical to SQL Server's: a table named for the entity in the module schema (falling back to dbo), the Id key, and store generation only when the identifier type is value-generated (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/PostgreSQLDataSourceEngine.cs:92-106). PostgreSQL house style (snake_case identifiers, the public schema) is left to a consumer's own naming-convention plugin rather than built into the framework (ADR-113, EntityTypeConfiguration.cs:73-76).
      • Where it's used: exercised by the framework's own PostgreSQL test suite, PostgreSQLPersistenceTests.cs, DesignTimeDbContextHelperTests.cs, and PostgreSQLDbContextModelTests.cs (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.PostgreSQL.Tests/ and MMCA.Common.Infrastructure.Tests/). No production configuration in ADC, Store, or Helpdesk derives from it today, matching the state ADR-113 records: the engine is plumbed and tested, with no PostgreSQL entity shipped in either app yet.

      EntityTypeConfigurationSqlite<TEntity, TIdentifierType>

      @@ -7801,8 +8464,8 @@

      EntityTypeConfigur
      • What it is: the SQLite shim, identical in shape to its Cosmos sibling: a body-less class carrying [UseDataSource(DataSource.Sqlite)] (EntityTypeConfigurationSqlite.cs:16-20).
      • Depends on: EntityTypeConfiguration<TEntity, TIdentifierType> (base, EntityTypeConfigurationSqlite.cs:18); UseDataSourceAttribute and DataSource (:1, :16); AuditableBaseEntity<TIdentifierType> (:19).
      • -
      • Concept reinforced: the attribute-as-base-class shim taught at EntityTypeConfigurationCosmos<TEntity, TIdentifierType>. Its doc records the SQLite-specific mapping it delegates: table name plus an auto-increment key (EntityTypeConfigurationSqlite.cs:7-12), implemented in the engine-aware base at EntityTypeConfiguration.cs:82-89.
      • -
      • Where it's used: no production configuration in the three repos derives from it, but it is the framework's own in-memory-and-file test engine and is used throughout the Common test suite: the database-initialization tests (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:271, :279), the convention tests (EntityTypeConfigurationTests.cs:58), the multi-source integration tests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/MultiSourceSqliteIntegrationTests.cs:240, :241), and the registry tests (EntityDataSourceRegistryTests.cs:217, :220, including a deliberate duplicate-configuration pair at :228 and :231).
      • +
      • Concept reinforced: the attribute-as-base-class shim taught at EntityTypeConfigurationCosmos<TEntity, TIdentifierType>. Its doc records the SQLite-specific mapping it delegates: table name plus an auto-increment key (EntityTypeConfigurationSqlite.cs:7-12), which the engine-aware base hands to the SQLite engine (EntityTypeConfiguration.cs:77), implemented at MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SqliteDataSourceEngine.cs:91-103.
      • +
      • Where it's used: no production configuration in the three repos derives from it, but it is the framework's own in-memory-and-file test engine and is used throughout the Common test suite: the database-initialization tests (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:321, :279), the convention tests (EntityTypeConfigurationTests.cs:58), the multi-source integration tests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/MultiSourceSqliteIntegrationTests.cs:240, :241), and the registry tests (EntityDataSourceRegistryTests.cs:217, :220, including a deliberate duplicate-configuration pair at :228 and :231).

      EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>

      @@ -7814,6 +8477,23 @@

      EntityTypeConfi
    554. Concept reinforced, and what a consumer inherits by writing one base name. Deriving from this class buys four behaviors without a line of code: the DomainEvents exclusion from EntityTypeConfigurationBase<TEntity, TIdentifierType>, a table named after the entity in a schema named after the module (EntityTypeConfiguration.cs:74), a primary key with the right generation policy for the entity's IdValueGeneratedAttribute (:67-71), and a DataSourceKey in EntityDataSourceRegistry that makes the entity routable through IUnitOfWork and DbContextFactory. Adding a class-level UseDatabaseAttribute on top overrides only the logical database, leaving the engine alone (EntityDataSourceRegistry.cs:180-182). [Rubric §15, Best Practices & Code Quality] assesses how much a consumer must know to add an entity correctly; here it is one base class name.
    555. Where it's used: 28 configurations across ADC's Conference, Engagement, and Identity modules (for example MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Infrastructure/Persistence/EntityConfiguration/UserConfiguration.cs:13 and MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/LivePolls/LivePollConfiguration.cs:16), 12 across Store's Catalog, Sales, and Identity modules, MMCA.Helpdesk's two Tickets configurations (MMCA.Helpdesk/Source/Modules/Tickets/MMCA.Helpdesk.Tickets.Infrastructure/Persistence/EntityConfiguration/TicketConfiguration.cs, MMCA.Helpdesk/Source/Modules/Tickets/MMCA.Helpdesk.Tickets.Infrastructure/Persistence/EntityConfiguration/TicketCommentConfiguration.cs), and the framework's own notification configurations, PushNotificationConfiguration and UserNotificationConfiguration. It is also the SQL Server half of the cross-engine portability test (CosmosConfigurationPortabilityTests.cs:116).
    556. +

      StronglyTypedIdModelConfiguration

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Conventions · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/StronglyTypedIdModelConfiguration.cs:21 · Level 6 · class (public static)

      +
      +
        +
      • What it is: the single call that wires strongly typed identifiers (ADR-115) into EF Core's model, once per host. Apply walks the host's StronglyTypedIdRegistry and declares one pre-convention type mapping per identifier type it describes, so every property of that type, keys, cross-module scalar references, owned-type members and the properties of a framework table alike, maps to its wrapped primitive (StronglyTypedIdModelConfiguration.cs:7-12, :29-51).
      • +
      • Depends on: EF Core's ModelConfigurationBuilder, StronglyTypedIdValueConverter<TSelf, TValue> and its comparer counterpart from MMCA.Common.Infrastructure.Persistence.Conversions, and StronglyTypedIdRegistry from MMCA.Common.Shared.Identifiers.
      • +
      • Concept introduced, pre-convention configuration versus a model-finalizing convention. [Rubric §8, Data Architecture] assesses whether a cross-cutting mapping rule is applied at the point in the pipeline where it actually works, and [Rubric §1, SOLID] assesses whether the opt-in stays orthogonal to code that never adopts it. EF Core exposes two very different places to declare a type-wide rule: ConfigureConventions (pre-convention, runs before the provider's own conventions see the model) and a model-finalizing IModelFinalizingConvention like SoftDeleteUniqueIndexConvention (runs last). The class doc comment states why this one has to be the former (StronglyTypedIdModelConfiguration.cs:14-18): a converter attached at finalization arrives after the provider's value-generation conventions have already inspected the property, so a wrapped int key would silently lose its IDENTITY strategy. Declaring the mapping here means the provider CLR type is known before any of that runs, and a wrapped key generates exactly as its primitive did.
      • +
      • Walkthrough
          +
        • Apply(configurationBuilder, registry) (StronglyTypedIdModelConfiguration.cs:29) null-guards both arguments, then iterates registry.Describe(), which yields the (identifierType, valueType) pairs the host declared (:36).
        • +
        • For each pair it closes the two open generics reflectively: StronglyTypedIdValueConverter<,>.MakeGenericType(identifierType, valueType) and StronglyTypedIdValueComparer<>.MakeGenericType(identifierType) (StronglyTypedIdModelConfiguration.cs:38-41), then calls configurationBuilder.Properties(identifierType).HaveConversion(converterType, comparerType) (:43-45), the pre-convention API that applies the conversion to every property of that CLR type across the whole model, not just one entity's.
        • +
        • configured counts the identifiers actually wired and is returned (StronglyTypedIdModelConfiguration.cs:34,47,50), giving the caller a number to log or assert against rather than a bare void.
        • +
        +
      • +
      • Why it's built this way: strongly typed identifiers sit on the same opt-in footing as smart enumerations (ADR-115): a wrapper is two lines, and adopting the whole feature is one DI call, services.AddStronglyTypedIds(typeof(OrderId).Assembly). The EF half of that contract has to be equally uniform, one call from ApplicationDbContext.ConfigureConventions, which every engine context inherits, so SQL Server, PostgreSQL, SQLite and Cosmos all get the same mapping without an engine branch (StronglyTypedIdModelConfiguration.cs:10-12). Driving the mapping from the registry rather than from a hard-coded type list means a host that never wraps an identifier calls Apply over an empty registry and nothing changes.
      • +
      • Where it's used: called once from ApplicationDbContext's ConfigureConventions (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs).
      • +

      IRepositoryFactory

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Repositories.Factory · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/Factory/IRepositoryFactory.cs:11 · Level 7 · interface (public)

      @@ -7821,7 +8501,7 @@

      IRepositoryFactory

      • What it is: a two-method contract that builds a repository over a caller-supplied DbContext (IRepositoryFactory.cs:11-34). Create returns a read-write IRepository<TEntity, TIdentifierType>, CreateReadOnly returns an IReadRepository<TEntity, TIdentifierType>, and the class doc records the one behavior an implementation is expected to fold in: conditional MiniProfiler wrapping (IRepositoryFactory.cs:7-10).
      • Depends on: EF Core's DbContext as the single parameter of both methods (NuGet, IRepositoryFactory.cs:1, :20, :31); IRepository<TEntity, TIdentifierType> and IReadRepository<TEntity, TIdentifierType> as return types (:2, :19, :30); AuditableAggregateRootEntity<TIdentifierType> and AuditableBaseEntity<TIdentifierType> as the two entity constraints (:3, :21, :32).
      • -
      • Concept introduced, a factory for the argument DI cannot supply. Plain constructor injection can hand a repository a DbContext, and the container does exactly that for the open-generic registration TryAddScoped(typeof(IRepository<,>), typeof(EFRepository<,>)) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:122). That registration can only ever bind one context, because DI resolves by type. This framework does not have one context: it has one context instance per DataSourceKey, created and cached per scope by DbContextFactory, and which instance an entity belongs to is a runtime lookup through IDataSourceService (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:40-41). So the context stops being a dependency and becomes an argument, and an argument-taking creation step is a factory. This is the type-level reason the codebase forbids constructor-injecting IRepository<,> directly: that path silently binds whatever the container's single registration resolves, while everything routed through IUnitOfWork reaches this factory and gets the context its entity actually lives in. [Rubric §2, Design Patterns] assesses whether a pattern earns its place rather than decorating the code; here the factory exists because DI provably cannot express the requirement. [Rubric §8, Data Architecture] assesses how deliberately storage boundaries are drawn; per-source repository construction is what keeps database-per-service (ADR-006) true at the level a handler touches.
      • +
      • Concept introduced, a factory for the argument DI cannot supply. Plain constructor injection can hand a repository a DbContext, and the container does exactly that for the open-generic registration TryAddScoped(typeof(IRepository<,>), typeof(EFRepository<,>)) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:128). That registration can only ever bind one context, because DI resolves by type. This framework does not have one context: it has one context instance per DataSourceKey, created and cached per scope by DbContextFactory, and which instance an entity belongs to is a runtime lookup through IDataSourceService (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:40-41). So the context stops being a dependency and becomes an argument, and an argument-taking creation step is a factory. This is the type-level reason the codebase forbids constructor-injecting IRepository<,> directly: that path silently binds whatever the container's single registration resolves, while everything routed through IUnitOfWork reaches this factory and gets the context its entity actually lives in. [Rubric §2, Design Patterns] assesses whether a pattern earns its place rather than decorating the code; here the factory exists because DI provably cannot express the requirement. [Rubric §8, Data Architecture] assesses how deliberately storage boundaries are drawn; per-source repository construction is what keeps database-per-service (ADR-006) true at the level a handler touches.
      • Walkthrough
        • Create<TEntity, TIdentifierType>(DbContext dbContext) (IRepositoryFactory.cs:19-22): constrained to TEntity : AuditableAggregateRootEntity<TIdentifierType> and TIdentifierType : notnull. Writes go through aggregate roots only, which is the DDD rule expressed in the signature rather than in a comment.
        • CreateReadOnly<TEntity, TIdentifierType>(DbContext dbContext) (IRepositoryFactory.cs:30-33): the same shape with a looser entity constraint, AuditableBaseEntity<TIdentifierType>. Reads may target a child entity that is not itself an aggregate root, which is exactly the asymmetry IUnitOfWork exposes on its own GetRepository / GetReadRepository pair (UnitOfWork.cs:33-35, :53-55).
        • @@ -7829,51 +8509,90 @@

          IRepositoryFactory

      • Why it's built this way: extracting the two lines from UnitOfWork into a contract means the unit of work never asks "is profiling on"; it asks for a repository and gets whichever composition the host configured. It also gives the test suite a substitution point that does not require a real context factory.
      • -
      • Where it's used: registered scoped as IRepositoryFactory -> RepositoryFactory (DependencyInjection.cs:123) and pinned by AddInfrastructure_RegistersIRepositoryFactory (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/DependencyInjectionInfrastructureTests.cs:239-252). Its only production consumer is UnitOfWork, which injects it (UnitOfWork.cs:13, :16) and calls Create (:42) and CreateReadOnly (:62) once per entity type, caching the result for the rest of the scope (:38-44, :58-64).
      • +
      • Where it's used: registered scoped as IRepositoryFactory -> RepositoryFactory (DependencyInjection.cs:129) and pinned by AddInfrastructure_RegistersIRepositoryFactory (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/DependencyInjectionInfrastructureTests.cs:239-252). Its only production consumer is UnitOfWork, which injects it (UnitOfWork.cs:13, :16) and calls Create (:42) and CreateReadOnly (:62) once per entity type, caching the result for the rest of the scope (:38-44, :58-64).
      -

      PushNotificationConfiguration

      +

      CrossDataSourceDegradeConvention

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration.Notifications · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/Notifications/PushNotificationConfiguration.cs:16 · Level 8 · class (internal sealed)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Conventions · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:34 · Level 11 · class (public sealed)

        -
      • What it is: the EF Core mapping for PushNotification, the framework's own broadcast-notification aggregate (PushNotificationConfiguration.cs:16-72). It is one of only two entity configurations that ship inside the framework rather than in a consumer application, and it is where the dedup guarantee behind a retried send is actually enforced.
      • -
      • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> as its base (PushNotificationConfiguration.cs:17); PushNotification and its DedupKeyMaxLength / ScopeKeyMaxLength constants (:3, :47, :53, declared at MMCA.Common/Source/Core/MMCA.Common.Domain/Notifications/PushNotifications/PushNotification.cs:19, :22); PushNotificationInvariants for the title and body lengths (:4, :29, :33); PushNotificationStatus indirectly through the string conversion (:43); UseDatabaseAttribute (:15); the HasSoftDeleteFilter member from IndexBuilderExtensions (:70); EF Core's EntityTypeBuilder<TEntity> (NuGet, :1-2).
      • -
      • Concept introduced, a framework-owned entity that has to name its own home. Every other configuration in this workspace lets convention pick the schema and the logical database: EntityTypeConfiguration<TEntity, TIdentifierType> maps a SQL Server entity to a table in a schema named by NamespaceConventions, which is the namespace segment immediately preceding Domain (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:74). That rule is right for a consumer entity in MMCA.ADC.Conference.Domain.Sessions (schema Conference) and wrong here, because this entity lives in MMCA.Common.Domain.Notifications.PushNotifications, whose preceding segment is Common. The configuration therefore states both facts explicitly: [UseDatabase("Notification")] (PushNotificationConfiguration.cs:15) fixes the logical database that EntityDataSourceRegistry records, and ToTable(nameof(PushNotification), "Notification") (:25) overrides the auto-derived schema after the base call. The class doc states this reasoning in place (:8-14), including the consequence that matters for a small host: a host with no DataSources:Notification entry keeps these tables in its default database. [Rubric §8, Data Architecture] assesses whether the physical layout is a decision rather than an accident; this is a convention override made visible in two attributes on one class. [Rubric §29, Resilience, Reliability & Business Continuity] assesses whether shared capabilities carry their own infrastructure; the notification feature ships its schema with the framework instead of asking each consumer to re-declare it.
      • -
      • Concept introduced, letting the database arbitrate a duplicate send. A "have I already sent this?" check in a handler is a check-then-act race: two retries of the same request can both read "no row" before either writes. The filtered unique index on DedupKey (PushNotificationConfiguration.cs:68-70) removes the race by moving arbitration into the engine, and the filter is what makes it usable: IS NOT NULL keeps the many sends that carry no key from colliding with each other (SQL Server treats NULLs as equal in a unique index), and IsDeleted = 0 keeps a soft-deleted notification from squatting on its key forever. The comment block records both halves and the defect that produced the second one (:55-67). [Rubric §12, Performance & Scalability] assesses whether index choices are reasoned; the file argues for one index and against another in the same class. [Rubric §29, Resilience & Business Continuity] assesses behavior under retry; at-least-once delivery upstream is only safe because this index makes a repeated send idempotent at the storage layer.
      • +
      • What it is: the model-finalizing convention that makes database-per-service work without forcing every module to write two versions of its entity configuration. It finds relationships whose two ends resolve to different physical databases and degrades them: the foreign key goes away, the navigation members are ignored, and entity types belonging to another database are removed from this model entirely (CrossDataSourceDegradeConvention.cs:10-34).
      • +
      • Depends on: DataSourceKey (the context's own physical source) and IEntityDataSourceRegistry (the entity-to-database map), both primary-constructor parameters (CrossDataSourceDegradeConvention.cs:34-36); DataSourceEngines and the IsRelational flag of DataSourceEngineCapabilities, read to decide whether a compensating index is allowed (CrossDataSourceDegradeConvention.cs:66, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/DataSourceEngines.cs:40); EF Core's IModelFinalizingConvention plus the mutable metadata surface (IMutableModel, IMutableEntityType, IMutableForeignKey, IMutableProperty).
      • +
      • Concept introduced, degrading a relationship instead of forbidding it. [Rubric §7, Microservices Readiness] assesses whether a module can be lifted into its own service without rewriting application code, and [Rubric §8, Data Architecture] assesses whether ownership boundaries in the data model are real. Under database-per-service (ADR-006) an order row and a user row can live in different databases, and no database engine can enforce a foreign key across that line. The naive answers are both bad: forbid the navigation in the domain model (which distorts the domain to fit deployment), or keep the FK and hope the two entities always end up co-located (which breaks the moment they do not). This convention takes a third route. The configuration body is written once, as if everything were in one database, and the model builder subtracts what the current physical source cannot support. Three things are given up and each has a compensating mechanism: the FK constraint is replaced by an index over the surviving scalar columns, in-database navigation is replaced by the INavigationPopulator batch-loading path (ADR-002), and referential consistency is maintained by integration events rather than by the engine (CrossDataSourceDegradeConvention.cs:13-22). The payoff is stated at :26-30: when every entity resolves to the same source, nothing is foreign and the convention is a structural no-op, so the monolith model is identical to the single-database model.
      • Walkthrough
          -
        • base.Configure(builder) (PushNotificationConfiguration.cs:22): runs the inherited chain first. EntityTypeConfigurationBase<TEntity, TIdentifierType> ignores the aggregate's in-memory DomainEvents collection (EntityTypeConfigurationBase.cs:29-32), then EntityTypeConfiguration<TEntity, TIdentifierType> reads the engine off the [UseDataSource(DataSource.SQLServer)] carried by the shim base (EntityTypeConfigurationSQLServer.cs:16) and applies the SQL Server conventions: table plus schema, HasKey(p => p.Id), and ValueGeneratedOnAdd() because the entity carries IdValueGeneratedAttribute (EntityTypeConfiguration.cs:45-50, :65-72, PushNotification.cs:15).
        • -
        • The schema override (PushNotificationConfiguration.cs:25): ToTable(nameof(PushNotification), "Notification"), applied after the base call so it replaces the derived Common schema rather than being replaced by it. Ordering is load-bearing here.
        • -
        • Required scalars (:27-39): Title required with HasMaxLength(PushNotificationInvariants.TitleMaxLength) (200, MMCA.Common/Source/Core/MMCA.Common.Domain/Notifications/PushNotifications/Invariants/PushNotificationInvariants.cs:13), Body required at BodyMaxLength (2000, PushNotificationInvariants.cs:16), SentByUserId and RecipientCount required. The column widths are the same constants the domain validates against (PushNotificationInvariants.cs:18-26), so the database cannot be narrower than the invariant.
        • -
        • Status stored as text (:41-44): HasConversion<string>() with HasMaxLength(20). The CLR type is the PushNotificationStatus enum with three members, Pending, Sent, Failed (MMCA.Common/Source/Core/MMCA.Common.Domain/Notifications/PushNotifications/PushNotificationStatus.cs:6-16); persisting the name rather than the ordinal means reordering or inserting an enum member does not silently re-interpret existing rows.
        • -
        • DedupKey (:46-47): nullable, bounded by PushNotification.DedupKeyMaxLength (128, PushNotification.cs:19). The domain records that this is typically the Idempotency-Key header value and that the filtered unique index is what arbitrates the race (PushNotification.cs:39-45).
        • -
        • ScopeKey (:52-53): nullable, bounded by ScopeKeyMaxLength (128, PushNotification.cs:22), and deliberately not indexed. The comment gives the economics (:49-51): the scope filter runs after the primary-key join from UserNotification, over a table holding one row per send, so an index would cost writes without buying a read.
        • -
        • The filtered unique index (:68-70): HasIndex(p => p.DedupKey).IsUnique().HasSoftDeleteFilter(additionalFilter: "[DedupKey] IS NOT NULL"). The helper composes the final predicate as {additionalFilter} AND {softDeletePredicate} (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/IndexBuilderExtensions.cs:62-65), and the soft-delete half comes from SoftDeleteFilterSql, which reads the actual column name out of the model and quotes it per engine (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:32-36). The engine parameter is not passed, so the default DataSource.SQLServer applies (IndexBuilderExtensions.cs:53), which matches this configuration's engine base class. The result is [DedupKey] IS NOT NULL AND [IsDeleted] = 0, asserted verbatim by DedupKeyIndex_FiltersOutSoftDeletedRows (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/PushNotificationConfigurationTests.cs:26-30).
        • -
        • Why the opt-in call is belt and braces, not a requirement (:62-67): SoftDeleteUniqueIndexConvention stamps the soft-delete predicate onto every unique index of a soft-deletable entity, and it now extends a hand-authored filter instead of skipping it: an index that already declares a predicate gets {existingFilter} AND {filterSql} in that exact order (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:65-79), and an index whose filter already constrains the soft-delete column is left untouched so a second model build cannot append the clause twice (:72-75). Because HasSoftDeleteFilter produces the same SQL in the same order from the same column name, the convention recognizes this index and stops, which is what the configuration's own comment says (PushNotificationConfiguration.cs:64-67).
        • +
        • ProcessModelFinalizing opens by casting the convention model builder's metadata to IMutableModel (CrossDataSourceDegradeConvention.cs:45-47). The class doc behind the cast is load-bearing: cross-cutting helpers (soft-delete filters, concurrency tokens) have already promoted every entity type to the Explicit configuration source, and convention-sourced builder calls cannot override Explicit, so the mutable API is the only surface that can apply these changes (:23-25).
        • +
        • IsForeign (CrossDataSourceDegradeConvention.cs:92-95) is the whole routing decision: look the CLR type's full name up in the registry, and call it foreign when the registry knows it and its key differs from this context's key. An unregistered type is never foreign.
        • +
        • The foreign set is computed over non-owned entity types, and when it is empty the method returns at once (CrossDataSourceDegradeConvention.cs:49-56). That early return is the monolith fast path.
        • +
        • Step 1, degrade the FKs (CrossDataSourceDegradeConvention.cs:63-75): for each local entity, every declared foreign key whose principal is foreign is passed to DegradeForeignKey. FKs declared on foreign dependents are not touched here because step 3 removes those entity types wholesale. Note that addCompensatingIndex is computed once, as the context engine's IsRelational capability (DataSourceEngines.For(contextKey.Engine).Capabilities.IsRelational, :66), not as a comparison against one named engine.
        • +
        • DegradeForeignKey (CrossDataSourceDegradeConvention.cs:108-139) captures the non-shadow FK properties, removes the FK (which takes both navigations with it), and returns early when no compensating index is wanted or no scalar column survives (:119-122). The subtle part is :124-131: EF's own ForeignKeyIndexConvention created an index for the FK, and its removal is processed by a deferred event that would fire after the coverage check below, silently leaving the column unindexed. So the convention drops that convention-sourced index eagerly itself, then checks coverage and adds a plain index only if nothing already covers the columns (:133-138).
        • +
        • HasCoveringIndex (CrossDataSourceDegradeConvention.cs:141-145) treats an index as covering when the FK columns are a prefix of its column list, in order, which is exactly the condition under which a composite index can serve a lookup on those columns.
        • +
        • Step 2, ignore the foreign members (CrossDataSourceDegradeConvention.cs:80-83, implementation at :152-166): skip navigations pointing at foreign entities are removed, then every CLR property whose type (or collection element type) is foreign is added to the ignore list. The comment at :77-79 explains why the second half is needed: removing a navigation leaves an unmapped CLR property of entity type behind, and EF's model validation rejects that.
        • +
        • UnwrapCollectionElementType (CrossDataSourceDegradeConvention.cs:172-175) is the one-argument-generic unwrap that makes ICollection<ForeignEntity> detectable as a collection of foreign entities.
        • +
        • Step 3, remove the foreign entity types (CrossDataSourceDegradeConvention.cs:85-89). EF's relationship discovery pulls foreign types into the model as a side effect; this is where they leave it.
      • -
      • Why it's built this way: the two overrides exist because a framework-owned domain type cannot be named by the convention that names consumer domain types, and stating the target explicitly is cheaper than special-casing the convention. The soft-delete clause on the dedup index is a fix, not an original design: it closes a defect where a soft-deleted notification held its dedup key permanently, and ADC's migration for it is an explicit expand-contract drop and recreate, since a filtered index predicate cannot be altered in place (MMCA.ADC/Source/Hosting/MMCA.ADC.Migrations.SqlServer.Notification/Migrations/20260804185520_CommonV1141PushNotificationDedupIndexSoftDeleteFilter.cs:13-31). The migration comment argues the safety case explicitly: the new predicate is strictly more permissive, so a previous revision running against the new index still succeeds.
      • -
      • Where it's used: discovered by assembly scan rather than by a direct reference. AddNotificationInfrastructure() registers this class's assembly as an entity-configuration source (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:26-31, naming the type only to get its Assembly at :29), and the only production caller is ADC's Notification module (MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.API/DependencyInjection.cs:33), whose service points the logical Notification source at the ADC_Notification database (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/appsettings.Development.json:37-41). Store hosts no notification entities today: the only other callers of AddNotificationInfrastructure are Common's own DI tests. Behavior is pinned against a real model built from this exact configuration (PushNotificationConfigurationTests.cs:76-83): index uniqueness (:22-24), the composed filter (:26-30), the scope-key length and nullability (:35-41), and the deliberate absence of a scope-key index (:43-49).
      • -
      • Caveats / not-in-source: the test double builds the model on SQLite (PushNotificationConfigurationTests.cs:69-71) while the configuration targets SQL Server, so the asserted filter string is the one this class hand-authors, not one a SQL Server model build rewrote.
      • +
      • Why it's built this way: the carve-out on the compensating index is spelled out at CrossDataSourceDegradeConvention.cs:101-106. Cosmos, the one non-relational engine (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/DataSourceEngineCapabilities.cs:14-19), auto-indexes every property and rejects explicit index definitions, so adding a compensating index would fail model validation, and skipping it is what keeps a configuration body carrying a cross-source relationship portable to Cosmos without edits (ADR-018). Reading the IsRelational capability rather than testing for DataSource.CosmosDB puts this decision on the same single flag that SoftDeleteUniqueIndexConvention and RestrictDeleteByDefaultConvention consult for their own engine guard, so the three conventions cannot disagree about which engines support indexes and constraints. Doing all of this at model finalizing rather than at configuration time is what lets the same entity configuration serve the monolith and the extracted-service topology (ADR-008) with no per-topology branching in module code.
      • +
      • Where it's used: registered by ApplicationDbContext in ConfigureConventions with the context's DataSourceKey and the resolved IEntityDataSourceRegistry (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:383-387), first of the three finalizing conventions, ahead of SoftDeleteUniqueIndexConvention (:392) and RestrictDeleteByDefaultConvention (:399). Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DataSources/CrossDataSourceDegradeConventionTests.cs.
      -

      UserNotificationConfiguration

      +

      RestrictDeleteByDefaultConvention

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration.Notifications · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/Notifications/UserNotificationConfiguration.cs:15 · Level 8 · class (internal sealed)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Conventions · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/RestrictDeleteByDefaultConvention.cs:42 · Level 11 · class (public sealed)

        -
      • What it is: the EF Core mapping for UserNotification, the per-user inbox row that pairs a recipient with a broadcast (UserNotificationConfiguration.cs:15-47). It is the sibling of PushNotificationConfiguration and shares its shape exactly: internal sealed, [UseDatabase("Notification")], EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> base, and a ToTable that overrides the derived Common schema.
      • -
      • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> (UserNotificationConfiguration.cs:16); UserNotification (:3, :24); UseDatabaseAttribute (:14); EF Core's EntityTypeBuilder<TEntity> (NuGet, :1-2).
      • -
      • Concept reinforced: the schema-and-database override taught at PushNotificationConfiguration, with the identical doc comment stating why (UserNotificationConfiguration.cs:7-13). What is worth studying here instead is the index pair, which shows the unique and the non-unique filtered case side by side. [Rubric §12, Performance & Scalability] assesses whether hot read paths are backed by an index that matches the query's predicate; the unread lookup below is a textbook covering-filter case.
      • +
      • What it is: a model-finalizing convention that stamps every foreign key in the model with the source of its delete behavior, and restricts the ones nobody configured. It walks every declared foreign key once and defaults an unconfigured or convention-sourced cascade to DeleteBehavior.Restrict, unless the foreign key belongs to an owned-type relationship, whose cascade EF itself owns (RestrictDeleteByDefaultConvention.cs:42-108).
      • +
      • Depends on: EF Core's IModelFinalizingConvention, IConventionModelBuilder, IConventionForeignKey, ConfigurationSource and DeleteBehavior; DataSource (its one primary-constructor parameter, RestrictDeleteByDefaultConvention.cs:42); and DataSourceEngines with the IsRelational flag of DataSourceEngineCapabilities for its engine guard (:66).
      • +
      • Concept introduced, defaulting to the safe cascade instead of the convenient one. [Rubric §8, Data Architecture] assesses whether the storage design prevents the failure modes its own conventions could otherwise create, and [Rubric §15, Best Practices and Code Quality] assesses whether a default is a deliberate choice rather than whatever the underlying framework happens to ship. EF Core's own convention cascades a required foreign key by default, which means a relationship nobody explicitly configured silently deletes dependents when the principal is deleted, below the aggregate's invariants, the soft-delete filter and the domain events (RestrictDeleteByDefaultConvention.cs:14-21). This convention overrides that default: an unconfigured or convention-sourced delete behavior becomes Restrict, so a delete that would fan out across rows the domain never asked to remove fails loudly at the database instead of succeeding silently, and a genuine cascade becomes an .OnDelete(DeleteBehavior.Cascade) written in the entity configuration.
      • Walkthrough
          -
        • Base call and schema override (UserNotificationConfiguration.cs:21, :24): same order and same reason as its sibling.
        • -
        • Scalars (:26-36): UserId and PushNotificationId required, IsRead required with HasDefaultValue(false) so an inserted row is unread at the database level too, and ReadOn mapped with no facets, staying nullable because the domain declares it DateTime? (MMCA.Common/Source/Core/MMCA.Common.Domain/Notifications/UserNotifications/UserNotification.cs:24).
        • -
        • Uniqueness per recipient (:38-41): HasIndex(p => new { p.UserId, p.PushNotificationId }).IsUnique().HasFilter("[IsDeleted] = 0"), one inbox row per user per notification among live rows. Note the literal predicate: this index would have received the same filter automatically from SoftDeleteUniqueIndexConvention (SoftDeleteUniqueIndexConvention.cs:60-68), because it is unique and the entity is soft-deletable, so the hand-written string is belt and braces. The convention detects that the declared filter already constrains the soft-delete column and leaves it exactly as written (:72-75).
        • -
        • The unread lookup (:43-45): HasIndex(p => new { p.UserId, p.IsRead }).HasFilter("[IsDeleted] = 0"), non-unique, serving the per-user unread query behind the notification badge. This is the case the convention deliberately skips, since it continues past any index that is not unique (SoftDeleteUniqueIndexConvention.cs:62-63), so the filter here is required to keep soft-deleted rows out of the index. It is written as a literal rather than through HasSoftDeleteFilter() from IndexBuilderExtensions, which is the helper that reads the column name from the model instead (IndexBuilderExtensions.cs:52-66); the produced SQL is identical for this model.
        • -
        • No relationship to PushNotification (:29-30): PushNotificationId is configured as a plain required scalar, with no HasOne or WithMany anywhere in the file, and the domain entity exposes no navigation property either, only the identifier (UserNotification.cs:18). The inbox row references the broadcast by value.
        • +
        • DeleteBehaviorSourceAnnotation, ExplicitSource, ConventionSource and OwnershipSource (RestrictDeleteByDefaultConvention.cs:44-57) are the four public constants: the annotation name the convention stamps on every foreign key (MMCA:DeleteBehaviorSource, :48), and the three values it can carry, recording whether the behavior came from an explicit configuration call, from this convention, or from EF's ownership handling.
        • +
        • ProcessModelFinalizing (RestrictDeleteByDefaultConvention.cs:60-81) null-guards the model builder, returns immediately when the engine is not relational (!DataSourceEngines.For(engine).Capabilities.IsRelational, :66-69, the same capability check SoftDeleteUniqueIndexConvention and CrossDataSourceDegradeConvention make), then materializes every declared foreign key across the model into a list before applying anything (:71-75). The class doc gives the non-relational reason: Cosmos has no foreign-key constraints to restrict, and stamping a delete-behavior decision on a model that cannot enforce one would only make the audit lie (:35-39). The comment above the materialization names why it is a list: the cross-source convention runs first and may already have removed relationships, so nothing here should observe a collection mid-mutation.
        • +
        • Apply (RestrictDeleteByDefaultConvention.cs:88-107) is the per-foreign-key decision. An ownership foreign key is stamped OwnershipSource and left untouched (:90-94): EF owns that cascade, and overriding it would fight the owned-type feature. Otherwise the foreign key's existing delete-behavior ConfigurationSource is read; null (nobody ever set one) or ConfigurationSource.Convention (EF's own required-FK rule produced the cascading default) are both treated as "inherited, and ours to replace": the behavior is set to DeleteBehavior.Restrict and stamped ConventionSource (:96-104). Anything else, meaning a configuration explicitly set a delete behavior, is stamped ExplicitSource and left exactly as configured (:106).
      • -
      • Why it's built this way: modeling the reference as a bare scalar keeps the entity honest about what the database enforces, and it is the shape the framework can move without rework. Both notification tables carry [UseDatabase("Notification")] today, so they land in one database and a declared relationship would be legal; the moment a host routed them apart, CrossDataSourceDegradeConvention would strip the EF relationship and the FK constraint anyway and leave exactly this scalar behind (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:9-21). That is the same rule ADC applies to every cross-module reference under database-per-service (ADR-006).
      • -
      • Where it's used: registered by the same assembly scan as its sibling (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:26-31) and reached in production only through ADC's Notification module (MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.API/DependencyInjection.cs:33), where the UserNotification table lands in the Notification schema of ADC_Notification (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/appsettings.Development.json:37-41).
      • -
      • Caveats / not-in-source: unlike its sibling, this configuration has no dedicated unit test in the Common suite; its mapping is exercised indirectly through the ADC Notification migrations and integration tier.
      • +
      • Why it's built this way: the annotation is not cosmetic. Stamping the source of every delete behavior, rather than just flipping the ones that need it, gives a fitness test a byte-for-byte answer to "did this foreign key's cascade come from an explicit choice, from this convention, or from ownership", read straight off a finalized model without re-running the convention (RestrictDeleteByDefaultConvention.cs:29-34). That is what ADR-119 records as the decision: default every unconfigured relationship to Restrict rather than to EF's own cascading default, and make the source of that decision inspectable rather than implicit. Running at model finalizing, after every module's IEntityTypeConfiguration has declared its own relationships, is what lets the convention see the whole model without needing to know which modules exist, the same timing SoftDeleteUniqueIndexConvention uses for the same reason.
      • +
      • Where it's used: registered by ApplicationDbContext in ConfigureConventions with the context's own engine (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:399), last of the three finalizing conventions so it never stamps a relationship the cross-source convention has already removed (ApplicationDbContext.cs:394-398). Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Conventions/RestrictDeleteByDefaultConventionTests.cs and by MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Domain/DeleteBehaviorConventionTests.cs, which subclasses the shared DeleteBehaviorConventionTestsBase and the ArchitectureRules.DeleteBehavior rule group so ADC's own model is checked against the same contract.
      • +
      +

      SoftDeleteUniqueIndexConvention

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Conventions · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:34 · Level 11 · class (public sealed)

      +
      +
        +
      • What it is: an EF Core model-finalizing convention that puts the IsDeleted = 0 predicate on every unique index of every soft-deletable entity type in the model, so a soft-deleted row stops occupying its unique slot (SoftDeleteUniqueIndexConvention.cs:11-34).
      • +
      • Depends on: EF Core's IModelFinalizingConvention, IConventionModelBuilder and IConventionEntityType; SoftDeleteFilterSql for the predicate text; the DataSource engine enum, taken as its one primary-constructor parameter (SoftDeleteUniqueIndexConvention.cs:34); DataSourceEngines with the IsRelational flag of DataSourceEngineCapabilities for its engine guard (:43); and IAuditableEntity as the marker for "this entity is soft-deletable".
      • +
      • Concept introduced, the model-finalizing convention as a cross-cutting model rule. [Rubric §6, CQRS & Event-Driven Design] assesses whether policies that apply to every entity are expressed once rather than repeated per configuration, and [Rubric §8, Data Architecture] assesses whether the data model's invariants actually hold. EF exposes convention hooks that run while the model is being built; IModelFinalizingConvention is the last of them, which means it observes the model after every module's IEntityTypeConfiguration has declared its indexes. That timing is what makes a blanket rule possible: the convention does not need to know which modules exist or what they declared, it just walks the finished model. The bug it closes is a genuine soft-delete trap, stated in the doc comment (SoftDeleteUniqueIndexConvention.cs:12-17): soft-delete a speaker, and the unique index on email still blocks creating a new speaker with that email, because the row is invisible to the application but entirely visible to the database's uniqueness check. Adding the filter aligns what the database enforces with what the application shows.
      • +
      • Walkthrough
          +
        • ProcessModelFinalizing (SoftDeleteUniqueIndexConvention.cs:37-51) null-guards the model builder, then returns immediately when the engine is not relational (!DataSourceEngines.For(engine).Capabilities.IsRelational, :43-44). The class doc names the coverage: SQL Server, PostgreSQL and SQLite all support partial or filtered indexes, and the convention is a no-op for Cosmos (:28-31).
        • +
        • The entity selection is two predicates (SoftDeleteUniqueIndexConvention.cs:46-47): the CLR type must be assignable to IAuditableEntity, and the entity type must not be owned. Owned types share their owner's table and have no independent index story, so they are excluded.
        • +
        • ApplyFilterToUniqueIndexes (SoftDeleteUniqueIndexConvention.cs:53-82) asks SoftDeleteFilterSql to build the predicate for this engine (:57, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:34) and bails when it is null (:58-59), then walks the entity's indexes, skipping every non-unique one (:63-64). The predicate text is engine-specific: [IsDeleted] = 0 on SQL Server, "IsDeleted" = 0 on SQLite, "IsDeleted" = false on PostgreSQL, whose flag is a real boolean (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SQLServerDataSourceEngine.cs:117, SqliteDataSourceEngine.cs:115, PostgreSQLDataSourceEngine.cs:116).
        • +
        • For a unique index the convention branches three ways on the existing filter. No filter at all: set the soft-delete predicate (SoftDeleteUniqueIndexConvention.cs:67-71). A filter that already constrains the soft-delete column, whether a hand-authored literal or the output of HasSoftDeleteFilter: leave it exactly as it is, detected by SoftDeleteFilterSql.ContainsPredicate (:73-76, the helper at SoftDeleteFilterSql.cs:52). Anything else: append the clause as {existingFilter} AND {filterSql} (:80).
        • +
        • That append is the load-bearing part, and the doc comment explains why it replaced the earlier "skip an index that already has a filter" behavior (SoftDeleteUniqueIndexConvention.cs:18-27). Skipping meant the exact partial-unique indexes a model bothered to hand-author (for example one narrowed on [DedupKey] IS NOT NULL) were the only ones a soft-deleted row could keep blocking. The ContainsPredicate check is what keeps appending idempotent: without it, a second model build would produce ... AND [IsDeleted] = 0 AND [IsDeleted] = 0.
        • +
        +
      • +
      • Why it's built this way: the comment at SoftDeleteUniqueIndexConvention.cs:55-56 names the reason the predicate comes from SoftDeleteFilterSql rather than from a local string: it is the same builder the opt-in extension reaches, so the automatic path and the hand-authored path can never disagree about column name or identifier quoting, and the AND ordering at :78-79 is chosen to match HasSoftDeleteFilter(additionalFilter:) byte for byte. Restricting the automatic behavior to unique indexes is a deliberate blast-radius choice: a unique index without the filter is a correctness bug under soft delete, while a non-unique index without it is only a performance question, and performance questions belong to whoever wrote the index. Guarding on the IsRelational capability rather than on DataSource.CosmosDB keeps this convention aligned with CrossDataSourceDegradeConvention and RestrictDeleteByDefaultConvention on which engines have indexes at all. The whole decision, including the 2026-08-26 revision from skipping to appending, is ADR-095; soft delete as a policy is ADR-005; covering SQL Server, PostgreSQL and SQLite while skipping Cosmos is the engine-portability posture of ADR-018.
      • +
      • Where it's used: registered by ApplicationDbContext in ConfigureConventions with the context's own engine (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:392), immediately after CrossDataSourceDegradeConvention and before RestrictDeleteByDefaultConvention. The comment there records the ordering intent and the precedence rule: it runs at finalization, after module configurations have declared their indexes, and hand-authored index filters are respected (ApplicationDbContext.cs:389-391). UserNotificationConfiguration relies on it outright for its unique index, declaring no filter of its own. Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Conventions/SoftDeleteUniqueIndexConventionTests.cs.
      • +
      +

      PushNotificationConfiguration

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration.Notifications · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/Notifications/PushNotificationConfiguration.cs:16 · Level 14 · class (internal sealed)

      +
      +
        +
      • What it is: the EF Core mapping for PushNotification, the framework's own broadcast-notification aggregate (PushNotificationConfiguration.cs:16-75). It is one of only two entity configurations that ship inside the framework rather than in a consumer application, and it is where the dedup guarantee behind a retried send is actually enforced.
      • +
      • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> as its base (PushNotificationConfiguration.cs:17), including the inherited EffectiveEngine property (:72-73, declared at MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:61); PushNotification and its DedupKeyMaxLength / ScopeKeyMaxLength constants (:3, :47, :53, declared at MMCA.Common/Source/Core/MMCA.Common.Domain/Notifications/PushNotifications/PushNotification.cs:19, :22); PushNotificationInvariants for the title and body lengths (:4, :29, :33); PushNotificationStatus indirectly through the string conversion (:43); UseDatabaseAttribute (:15); the HasSoftDeleteFilter member from IndexBuilderExtensions (:71); QuoteColumn from SoftDeleteFilterSql (:73, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/SoftDeleteFilterSql.cs:69-70); EF Core's EntityTypeBuilder<TEntity> (NuGet, :1-2).
      • +
      • Concept introduced, a framework-owned entity that has to name its own home. Every other configuration in this workspace lets convention pick the schema and the logical database: a SQL Server entity is mapped by its engine to a table in a schema named by NamespaceConventions, which is the namespace segment immediately preceding Domain (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SQLServerDataSourceEngine.cs:101). That rule is right for a consumer entity in MMCA.ADC.Conference.Domain.Sessions (schema Conference) and wrong here, because this entity lives in MMCA.Common.Domain.Notifications.PushNotifications, whose preceding segment is Common. The configuration therefore states both facts explicitly: [UseDatabase("Notification")] (PushNotificationConfiguration.cs:15) fixes the logical database that EntityDataSourceRegistry records, and ToTable(nameof(PushNotification), "Notification") (:25) overrides the auto-derived schema after the base call. The class doc states this reasoning in place (:8-14), including the consequence that matters for a small host: a host with no DataSources:Notification entry keeps these tables in its default database. [Rubric §8, Data Architecture] assesses whether the physical layout is a decision rather than an accident; this is a convention override made visible in two attributes on one class. [Rubric §29, Resilience, Reliability & Business Continuity] assesses whether shared capabilities carry their own infrastructure; the notification feature ships its schema with the framework instead of asking each consumer to re-declare it.
      • +
      • Concept introduced, letting the database arbitrate a duplicate send. A "have I already sent this?" check in a handler is a check-then-act race: two retries of the same request can both read "no row" before either writes. The filtered unique index on DedupKey (PushNotificationConfiguration.cs:69-73) removes the race by moving arbitration into the engine, and the filter is what makes it usable: IS NOT NULL keeps the many sends that carry no key from colliding with each other (SQL Server treats NULLs as equal in a unique index), and IsDeleted = 0 keeps a soft-deleted notification from squatting on its key forever. The comment block records both halves, the defect that produced the second one, and why the filter is written for the engine of the model being built (:55-68). [Rubric §12, Performance & Scalability] assesses whether index choices are reasoned; the file argues for one index and against another in the same class. [Rubric §29, Resilience & Business Continuity] assesses behavior under retry; at-least-once delivery upstream is only safe because this index makes a repeated send idempotent at the storage layer.
      • +
      • Walkthrough
          +
        • base.Configure(builder) (PushNotificationConfiguration.cs:22): runs the inherited chain first. EntityTypeConfigurationBase<TEntity, TIdentifierType> ignores the aggregate's in-memory DomainEvents collection (EntityTypeConfigurationBase.cs:29-32), then EntityTypeConfiguration<TEntity, TIdentifierType> reads the declared engine off the [UseDataSource(DataSource.SQLServer)] carried by the shim base (EntityTypeConfiguration.cs:43-46, EntityTypeConfigurationSQLServer.cs:16), records EffectiveEngine as the engine annotated on the model being built, falling back to the declared one (EntityTypeConfiguration.cs:48-49), and hands the declared engine to its engine's ApplyKeyAndTableMapping (:51, :68-77). For SQL Server that is table plus schema, HasKey(p => p.Id), and ValueGeneratedOnAdd() because the entity carries IdValueGeneratedAttribute (SQLServerDataSourceEngine.cs:94-104, PushNotification.cs:15).
        • +
        • The schema override (PushNotificationConfiguration.cs:25): ToTable(nameof(PushNotification), "Notification"), applied after the base call so it replaces the derived Common schema rather than being replaced by it. Ordering is load-bearing here.
        • +
        • Required scalars (:27-39): Title required with HasMaxLength(PushNotificationInvariants.TitleMaxLength) (200, MMCA.Common/Source/Core/MMCA.Common.Domain/Notifications/PushNotifications/Invariants/PushNotificationInvariants.cs:13), Body required at BodyMaxLength (2000, PushNotificationInvariants.cs:16), SentByUserId and RecipientCount required. The column widths are the same constants the domain validates against (PushNotificationInvariants.cs:18-26), so the database cannot be narrower than the invariant.
        • +
        • Status stored as text (:41-44): HasConversion<string>() with HasMaxLength(20). The CLR type is the PushNotificationStatus enum with three members, Pending, Sent, Failed (MMCA.Common/Source/Core/MMCA.Common.Domain/Notifications/PushNotifications/PushNotificationStatus.cs:6-16); persisting the name rather than the ordinal means reordering or inserting an enum member does not silently re-interpret existing rows.
        • +
        • DedupKey (:46-47): nullable, bounded by PushNotification.DedupKeyMaxLength (128, PushNotification.cs:19). The domain records that this is typically the Idempotency-Key header value and that the filtered unique index is what arbitrates the race (PushNotification.cs:39-45).
        • +
        • ScopeKey (:52-53): nullable, bounded by ScopeKeyMaxLength (128, PushNotification.cs:22), and deliberately not indexed. The comment gives the economics (:49-51): the scope filter runs after the primary-key join from UserNotification, over a table holding one row per send, so an index would cost writes without buying a read.
        • +
        • The filtered unique index (:69-73): HasIndex(p => p.DedupKey).IsUnique().HasSoftDeleteFilter(EffectiveEngine, additionalFilter: ...), where the additional filter is the DedupKey column quoted for EffectiveEngine by SoftDeleteFilterSql.QuoteColumn followed by IS NOT NULL (:73). The helper composes the final predicate as {additionalFilter} AND {softDeletePredicate} (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/IndexBuilderExtensions.cs:62-65), and the soft-delete half comes from SoftDeleteFilterSql, which reads the actual column name out of the model and builds the predicate for the given engine (SoftDeleteFilterSql.cs:34). The comment at :58-61 gives the reason the engine is passed explicitly rather than left to the helper's DataSource.SQLServer default (IndexBuilderExtensions.cs:53): a host that configures only PostgreSQL or SQLite substitutes its engine and applies this SQL Server configuration to that model, and the Cosmos context strips relational indexes. On a SQL Server model the result is [DedupKey] IS NOT NULL AND [IsDeleted] = 0, asserted verbatim by DedupKeyIndex_FiltersOutSoftDeletedRows (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/PushNotificationConfigurationTests.cs:26-30) and by OnASqlServerHost_NotificationIndexFilters_KeepTheBracketedForm (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/NotificationConfigurationEngineTests.cs:68); on a PostgreSQL-only host it is "DedupKey" IS NOT NULL AND "IsDeleted" = false (NotificationConfigurationEngineTests.cs:46).
        • +
        • Why the opt-in call is belt and braces, not a requirement (:63-68): SoftDeleteUniqueIndexConvention stamps the soft-delete predicate onto every unique index of a soft-deletable entity, and it extends a hand-authored filter instead of skipping it: an index that already declares a predicate gets {existingFilter} AND {filterSql} in that exact order (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:67-80), and an index whose filter already constrains the soft-delete column is left untouched so a second model build cannot append the clause twice (:73-76). Because HasSoftDeleteFilter produces the same SQL in the same order from the same column name, the convention recognizes this index and stops, which is what the configuration's own comment says (PushNotificationConfiguration.cs:66-68).
        • +
        +
      • +
      • Why it's built this way: the two overrides exist because a framework-owned domain type cannot be named by the convention that names consumer domain types, and stating the target explicitly is cheaper than special-casing the convention. Writing the filter for EffectiveEngine rather than as a bracketed literal is what lets one SQL Server-declared configuration serve a PostgreSQL or SQLite host: the test that pins it states the failure it prevents, a bracketed SQL Server literal being a syntax error on Npgsql (NotificationConfigurationEngineTests.cs:14-17). The soft-delete clause on the dedup index is a fix, not an original design: it closes a defect where a soft-deleted notification held its dedup key permanently, and ADC's migration for it is an explicit expand-contract drop and recreate, since a filtered index predicate cannot be altered in place (MMCA.ADC/Source/Hosting/MMCA.ADC.Migrations.SqlServer.Notification/Migrations/20260804185520_CommonV1141PushNotificationDedupIndexSoftDeleteFilter.cs:13-31). The migration comment argues the safety case explicitly: the new predicate is strictly more permissive, so a previous revision running against the new index still succeeds.
      • +
      • Where it's used: discovered by assembly scan rather than by a direct reference. AddNotificationInfrastructure() registers this class's assembly as an entity-configuration source (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:26-31, naming the type only to get its Assembly at :29), and the only production caller is ADC's Notification module (MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.API/DependencyInjection.cs:33), whose service points the logical Notification source at the ADC_Notification database (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/appsettings.Development.json:37-41). Store hosts no notification entities today: the only other callers of AddNotificationInfrastructure are Common's own DI tests. Behavior is pinned against a real model built from this exact configuration (PushNotificationConfigurationTests.cs:76-82): index uniqueness (:22-24), the composed filter (:26-30), the scope-key length and nullability (:35-41), and the deliberate absence of a scope-key index (:43-49). The engine substitution is pinned separately by NotificationConfigurationEngineTests, which builds the full context over a PostgreSQL-only and a SQL Server Notification source (NotificationConfigurationEngineTests.cs:27-47, :49-69).
      • +
      • Caveats / not-in-source: the PushNotificationConfigurationTests double builds the model on SQLite (PushNotificationConfigurationTests.cs:69-71) through a plain DbContext that carries no engine annotation, so EffectiveEngine falls back to the declared SQL Server engine (EntityTypeConfiguration.cs:48-49) and the asserted filter is the bracketed SQL Server form; the engine-substituted output is only asserted by NotificationConfigurationEngineTests.

      RepositoryFactory

      @@ -7885,7 +8604,7 @@

      RepositoryFactory

    557. Concept introduced, reflective activation traded for a cached compiled factory. ActivatorUtilities.CreateInstance is the usual way to build a type whose constructor mixes DI-resolved services with caller-supplied arguments, and it is what this class used to call. Its cost is per call: it matches the constructor by reflection every time and caches nothing, so a request touching four aggregates paid four reflective activations. ActivatorUtilities.CreateFactory does the matching once and returns an ObjectFactory, a compiled delegate that can be invoked repeatedly. The insight that makes caching safe is stated in the code (RepositoryFactory.cs:74-79): each closed repository type here always takes the same argument shape, so the delegate can be keyed by type alone with no risk of a shape mismatch. [Rubric §12, Performance & Scalability] assesses whether hot paths avoid repeated per-call work; repository creation is on every command and query, so a per-type one-time cost replaces a per-call one. [Rubric §15, Best Practices & Code Quality] assesses whether an optimization is justified in place rather than left as folklore; the doc comment names the exact scenario it fixes.
    558. Concept reinforced, decoration decided by configuration. The decorator pattern itself is taught in the CQRS pipeline chapter. Here it appears in its simplest form and with a switch: ApplicationSettings.UseMiniProfiler (MMCA.Common/Source/Core/MMCA.Common.Application/Settings/ApplicationSettings.cs:14) decides whether the plain repository is returned or is passed as the inner instance of a timing decorator. When the flag is off there is no wrapper object and no indirection at all, so profiling costs nothing in a production host that leaves it off. [Rubric §13, Observability & Operability] assesses whether the system can be inspected without being rebuilt; per-repository timing is a configuration flip.
    559. Walkthrough
        -
      • Primary constructor (RepositoryFactory.cs:15-18): captures the provider into _serviceProvider and, importantly, resolves IOptions<ApplicationSettings>.Value once into _applicationSettings rather than on every call. Nothing else is resolved at construction, so the class stays cheap to create per scope (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:123 registers it scoped).
      • +
      • Primary constructor (RepositoryFactory.cs:15-18): captures the provider into _serviceProvider and, importantly, resolves IOptions<ApplicationSettings>.Value once into _applicationSettings rather than on every call. Nothing else is resolved at construction, so the class stays cheap to create per scope (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:129 registers it scoped).
      • Create<TEntity, TIdentifierType>(DbContext dbContext) (:26-42): builds EFRepository<TEntity, TIdentifierType> through Factory(..., DbContextArg)(_serviceProvider, [dbContext]) (:31-32). Only the context is passed positionally; the repository's two remaining constructor parameters are optional, TimeProvider? and ICurrentUserService? (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:23-27), and come from the provider, with the class doc recording the fallback when they are absent: the system clock and no user stamp (EFRepository.cs:17-22). When UseMiniProfiler is true (:34) the instance is re-wrapped by activating EFRepositoryDecorator<TEntity, TIdentifierType> with an argument shape of [typeof(IRepository<TEntity, TIdentifierType>)] (:36-38), matching the decorator's single inner parameter (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepositoryDecorator.cs:14).
      • CreateReadOnly<TEntity, TIdentifierType>(DbContext dbContext) (:50-66): the identical sequence against EFReadRepository and EFReadRepositoryDecorator, with the looser AuditableBaseEntity<TIdentifierType> constraint.
      • DbContextArg (:68): a single static Type[] holding typeof(DbContext), shared by both creation paths so the common case allocates no argument-type array per call.
      • @@ -7897,6 +8616,26 @@

        RepositoryFactory

      • Where it's used: injected into UnitOfWork (UnitOfWork.cs:13, :16) and called from GetRepository (:42) and GetReadRepository (:62). Because the unit of work caches one repository per entity type per scope (:38-44), the factory typically runs once per entity type per request, and the compiled delegate is reused for every later request in the process. All four composition outcomes are pinned directly against a real SQLite context: plain repository with the flag off, decorated with it on, and the same pair for the read side (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Repositories/RepositoryFactoryTests.cs:45-95), plus two tests asserting the built instances are functional repositories (:95-115). Those tests construct the factory directly with Options.Create(new ApplicationSettings { UseMiniProfiler = false }) (:98, :100), which is the shape the primary constructor takes.
      • Caveats / not-in-source: the cache is keyed by implementation type only, which is correct exactly as long as every call site for a given closed type passes the same argument shape. Both current call sites do, and the doc comment states that assumption (RepositoryFactory.cs:74-79), but nothing in the code enforces it: a future overload passing a different argumentTypes array for an already-cached type would silently reuse the first delegate.
      +

      UserNotificationConfiguration

      +
      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Persistence.Configuration.EntityTypeConfiguration.Notifications · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/Notifications/UserNotificationConfiguration.cs:15 · Level 14 · class (internal sealed)

      +
      +
        +
      • What it is: the EF Core mapping for UserNotification, the per-user inbox row that pairs a recipient with a broadcast (UserNotificationConfiguration.cs:15-47). It is the sibling of PushNotificationConfiguration and shares its shape exactly: internal sealed, [UseDatabase("Notification")], EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> base, and a ToTable that overrides the derived Common schema.
      • +
      • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> (UserNotificationConfiguration.cs:16), including the inherited EffectiveEngine property (:45, declared at MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:61); UserNotification (:3, :24); UseDatabaseAttribute (:14); the HasSoftDeleteFilter member from IndexBuilderExtensions (:45); SoftDeleteUniqueIndexConvention, which supplies the filter on its unique index (:38-41); EF Core's EntityTypeBuilder<TEntity> (NuGet, :1-2).
      • +
      • Concept reinforced: the schema-and-database override taught at PushNotificationConfiguration, with the identical doc comment stating why (UserNotificationConfiguration.cs:7-13). What is worth studying here instead is the index pair, which shows the two routes a soft-delete filter takes onto an index side by side: the convention for the unique one, the opt-in helper for the non-unique one, neither written as an engine-specific literal. [Rubric §12, Performance & Scalability] assesses whether hot read paths are backed by an index that matches the query's predicate; the unread lookup below is a textbook covering-filter case.
      • +
      • Walkthrough
          +
        • Base call and schema override (UserNotificationConfiguration.cs:21, :24): same order and same reason as its sibling.
        • +
        • Scalars (:26-36): UserId and PushNotificationId required, IsRead required with HasDefaultValue(false) so an inserted row is unread at the database level too, and ReadOn mapped with no facets, staying nullable because the domain declares it DateTime? (MMCA.Common/Source/Core/MMCA.Common.Domain/Notifications/UserNotifications/UserNotification.cs:24).
        • +
        • Uniqueness per recipient (:38-41): HasIndex(p => new { p.UserId, p.PushNotificationId }).IsUnique(), one inbox row per user per notification among live rows, with no filter declared here. The comment states where the predicate comes from (:38-39): SoftDeleteUniqueIndexConvention sets the soft-delete filter on a unique index of a soft-deletable entity that has none (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:67-71), built for the context's own engine (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:392), so the SQL comes out in the syntax of whichever engine the model is built for.
        • +
        • The unread lookup (:43-45): HasIndex(p => new { p.UserId, p.IsRead }).HasSoftDeleteFilter(EffectiveEngine), non-unique, serving the per-user unread query behind the notification badge. This is the case the convention deliberately skips, since it continues past any index that is not unique (SoftDeleteUniqueIndexConvention.cs:63-64), so the explicit call is required to keep soft-deleted rows out of the index. Passing EffectiveEngine rather than relying on the helper's DataSource.SQLServer default (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/IndexBuilderExtensions.cs:53) is what makes the filter [IsDeleted] = 0 on a SQL Server model and "IsDeleted" = false on a PostgreSQL-only host (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Configuration/NotificationConfigurationEngineTests.cs:67, :45).
        • +
        • No relationship to PushNotification (:29-30): PushNotificationId is configured as a plain required scalar, with no HasOne or WithMany anywhere in the file, and the domain entity exposes no navigation property either, only the identifier (UserNotification.cs:18). The inbox row references the broadcast by value.
        • +
        +
      • +
      • Why it's built this way: modeling the reference as a bare scalar keeps the entity honest about what the database enforces, and it is the shape the framework can move without rework. Both notification tables carry [UseDatabase("Notification")] today, so they land in one database and a declared relationship would be legal; the moment a host routed them apart, CrossDataSourceDegradeConvention would strip the EF relationship and the FK constraint anyway and leave exactly this scalar behind (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:10-22). That is the same rule ADC applies to every cross-module reference under database-per-service (ADR-006). Leaving both index filters to engine-aware code rather than a bracketed literal is what lets this SQL Server-declared configuration be applied to a PostgreSQL or SQLite model, where the bracketed form is a syntax error on Npgsql (NotificationConfigurationEngineTests.cs:14-17).
      • +
      • Where it's used: registered by the same assembly scan as its sibling (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:26-31) and reached in production only through ADC's Notification module (MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.API/DependencyInjection.cs:33), where the UserNotification table lands in the Notification schema of ADC_Notification (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/appsettings.Development.json:37-41). Its index filters are pinned by NotificationConfigurationEngineTests, which loads this configuration's assembly into a full context over a PostgreSQL-only and a SQL Server Notification source (NotificationConfigurationEngineTests.cs:42, :64) and asserts both indexes carry the engine's soft-delete predicate (:45, :67, exactly two indexes at :73-74).
      • +
      • Caveats / not-in-source: that test pins only the two index filters; the scalar mapping (required columns, the IsRead default) has no dedicated assertion in the Common suite and is exercised indirectly through the ADC Notification migrations and integration tier.
      • +

      ⬅ Validation • Index • Authentication & Authorization ➡

      @@ -7915,6 +8654,7 @@

      RepositoryFactory

    560. Recording what changed, the audit trail
    561. Repositories, specifications, and the unit of work
    562. Routing an entity to its database
    563. +
    564. One registry answers every engine question
    565. Three model-finalizing conventions and the identifier mapping
    566. Entity configuration and engine portability
    567. Two tables one database owns, refresh sessions and permission grants
    568. diff --git a/docs/onboarding/group-08-auth.html b/docs/onboarding/group-08-auth.html index 8e4a7301..5ac7cbe1 100644 --- a/docs/onboarding/group-08-auth.html +++ b/docs/onboarding/group-08-auth.html @@ -160,13 +160,16 @@

      8. Authentication & AuthorizationAuthenticationValidators); multi-device refresh sessions (RefreshSession, IRefreshSessionStore, RefreshSessionSettings, - RefreshSessionSummaryResponse, and the two workflow-private - helpers IssuedSession and + RefreshSessionSummaryResponse, and the issuer that owns every + session decision, AuthSessionIssuer / + IAuthSessionIssuer, with its two private helpers + IssuedSession and SessionStampingTokenService); the contracts an app's User aggregate exposes to those shared workflows (IAuthUser, IPasswordChangeableUser, IUserPreferences, IErasableUser); password material - (PasswordHasher / IPasswordHasher); brute-force and + (PasswordHasher / IPasswordHasher, and the one + complexity rule both server and client read, PasswordComplexity); brute-force and rate-limit protection (LoginProtectionService / ILoginProtectionService, LoginProtectionSettings); the forgot-password token lifecycle @@ -239,37 +242,37 @@

      Tokens: one signing swi JWT, 15 minutes by default, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwtSettings.cs:61) and an opaque, random refresh token (64 bytes of RandomNumberGenerator output, Base64-encoded, valid 7 days by - default, TokenService.cs:144-147, JwtSettings.cs:64), both produced by + default, TokenService.cs:155-158, JwtSettings.cs:64), both produced by TokenService (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:26). The access token carries a fixed claim spine: sub, jti, iat, plus name, email, and role - (TokenService.cs:102-111), and the app adds its own claims (for example speaker_id or - customer_id) through the additionalClaims parameter (TokenService.cs:113-116). sub is the + (TokenService.cs:113-122), and the app adds its own claims (for example speaker_id or + customer_id) through the additionalClaims parameter (TokenService.cs:124-127). sub is the single carrier of the user identifier: the duplicate custom claim that used to ride alongside it is gone, because two values that can disagree is two claim names every reader has to know - (TokenService.cs:99-101). The port ITokenService + (TokenService.cs:110-112). The port ITokenService (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Auth/ITokenService.cs:8) publishes both lifetimes as default interface members pinned to the same 15-minute / 7-day baseline (ITokenService.cs:33, ITokenService.cs:40), so a hand-written test double reports the same expiry the production settings would, while the concrete service derives them from the bound settings - (TokenService.cs:151, TokenService.cs:154).

      + (TokenService.cs:162, TokenService.cs:165).

      The load-bearing design choice is a single configuration switch, JwtSettings.SigningAlgorithm - (TokenService.cs:76). It defaults to + (TokenService.cs:87). It defaults to JwtSigningAlgorithm.RS256 (JwtSettings.cs:30): the Identity service signs with an RSA private key and every other service validates against the matching public key, which it fetches over JWKS. A single-host monolith opts into HS256 explicitly, where one symmetric Base64 secret both signs and validates because issuer - and validator are the same process (TokenService.cs:76-87, TokenService.cs:206-218). Asymmetric + and validator are the same process (TokenService.cs:87-98, TokenService.cs:226-238). Asymmetric is the default precisely because it is the shape that survives extraction: a compromised non-Identity service can verify tokens but cannot forge them. An issuer with no explicit public key configured derives one from its own private-key parameters so it can still self-validate during refresh - (TokenService.cs:242-256), and the key id from + (TokenService.cs:262-276), and the key id from JwksSettings travels into every RS256 token's kid header so a validator reading the published document selects the right key by name rather than - trying each in turn (TokenService.cs:235-239). Key material is materialized once in the constructor - and the owned RSA handles are disposed with the service (TokenService.cs:37-38, - TokenService.cs:200-204), so token operations never re-parse a PEM. The settings class enforces the + trying each in turn (TokenService.cs:255-259). Key material is materialized once in the constructor + and the owned RSA handles are disposed with the service (TokenService.cs:39-40, + TokenService.cs:220-224), so token operations never re-parse a PEM. The settings class enforces the pairing rather than trusting the host: it implements IValidatableObject and rejects an HS256 secret shorter than 32 characters or an RS256 configuration with no private key (JwtSettings.cs:70-85).

      The public half is served by RsaJwksProvider @@ -296,56 +299,80 @@

      Tokens: one signing swi (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Warmup/OpenIdConnectMetadataWarmupTask.cs:21) so the first authenticated request on a cold replica does not pay the discovery round trip. Validation pins the expected algorithm so an attacker cannot force an algorithm swap: GetPrincipalFromExpiredToken - sets ValidAlgorithms to the single configured value (TokenService.cs:176) and then re-checks the - token header after ValidateToken returns (TokenService.cs:185-189). Only the lifetime check is - skipped there (TokenService.cs:171), because the method exists to read claims out of an + sets ValidAlgorithms to the single configured value (TokenService.cs:187) and then re-checks the + token header after ValidateToken returns (TokenService.cs:196-200). Only the lifetime check is + skipped there (TokenService.cs:182), because the method exists to read claims out of an already-expired token during refresh.

      The shared authentication workflow

      Login, registration, refresh, revocation, and device listing are not re-implemented per app. They live once in AuthenticationServiceBase<TUser> - (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:74), an abstract + (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:63), an abstract base each app's Identity module seals over its concrete User aggregate. The base owns the sequence; the sealed subclass supplies the genuinely app-specific pieces through abstract and virtual hooks: FindUntrackedByEmailAsync and EmailExistsAsync (written against the concrete User so EF - translation is unchanged, AuthenticationServiceBase.cs:585, AuthenticationServiceBase.cs:591), - CreateUser (AuthenticationServiceBase.cs:594), CreateAccessToken - (AuthenticationServiceBase.cs:597), the two optional candidate gates - (AuthenticationServiceBase.cs:691-696), the post-commit OnUserRegisteredAsync - (AuthenticationServiceBase.cs:702), and the overridable "refresh user vanished" error - (AuthenticationServiceBase.cs:710, 401 by default because a token for a deleted user is - indistinguishable from an invalid one). Both token lifetimes are read from - ITokenService with a defensive fallback to the 15-minute / 7-day baseline for a - misconfigured host or a test double (AuthenticationServiceBase.cs:137-146).

      -

      LoginAsync (AuthenticationServiceBase.cs:156) shows the shape. It validates the request first, + translation is unchanged, AuthenticationServiceBase.cs:438, AuthenticationServiceBase.cs:444), + CreateUser (AuthenticationServiceBase.cs:447), CreateAccessToken + (AuthenticationServiceBase.cs:450), the two optional candidate gates + (AuthenticationServiceBase.cs:555-560), the post-commit OnUserRegisteredAsync + (AuthenticationServiceBase.cs:566), and the overridable "refresh user vanished" error + (AuthenticationServiceBase.cs:574, 401 by default because a token for a deleted user is + indistinguishable from an invalid one). What the base does not own is the session: it decides who is + signed in and hands everything about what they are given to + IAuthSessionIssuer, a constructor dependency + (AuthenticationServiceBase.cs:40-43, AuthenticationServiceBase.cs:68), so the workflow never + touches a session row. Even the token service a subclass mints through is the issuer's + (AuthenticationServiceBase.cs:98), which is how the sid and mfa claims reach an app's token + without the app's claim code knowing they exist (see the refresh-session section below).

      +

      LoginAsync (AuthenticationServiceBase.cs:105) shows the shape. It validates the request first, then runs the ADR-029 - lockout check (AuthenticationServiceBase.cs:168), then does the dual-fetch: an untracked, - no-change-tracking query to verify the password cheaply (AuthenticationServiceBase.cs:179, - AuthenticationServiceBase.cs:210), and only on success a second tracked re-fetch of the instance + lockout check (AuthenticationServiceBase.cs:117-122), then does the dual-fetch: an untracked, + no-change-tracking query to verify the password cheaply (AuthenticationServiceBase.cs:130, + AuthenticationServiceBase.cs:143), and only on success a second tracked re-fetch of the instance the app's CreateAccessToken hook mints from, which is also what turns a race that deleted the - account between the two steps into a clean 404 (AuthenticationServiceBase.cs:228-237). The email is + account between the two steps into a clean 404 (AuthenticationServiceBase.cs:177-186). The email is normalized through the Email value object before the query so the EF predicate compares same-typed converted values - (AuthenticationServiceBase.cs:176). Soft-deleted accounts fall out through EF global query filters - and return the same generic 401 as a wrong password (AuthenticationServiceBase.cs:180-192), so the - API never reveals whether an email exists, and a successful login clears the attempt counters - (AuthenticationServiceBase.cs:240) before handing off to the shared token-issue path - (AuthenticationServiceBase.cs:245).

      -

      RegisterAsync (AuthenticationServiceBase.cs:254) rate-limits by source IP, rejects a duplicate + (AuthenticationServiceBase.cs:126). Soft-deleted accounts fall out through EF global query filters + and return the same generic 401 as a wrong password (AuthenticationServiceBase.cs:128-148), and so + does an account with no stored credential at all (the external-OAuth shape), which also pays one + throwaway key derivation so its 401 does not come back measurably faster than a real check + (AuthenticationServiceBase.cs:132-141, AuthenticationServiceBase.cs:589-599): the API never + reveals whether an email exists. The app's candidate gate, the email-confirmation gate and the + second-factor challenge all run after the password check (AuthenticationServiceBase.cs:150-175), + and a successful login clears the attempt counters (AuthenticationServiceBase.cs:189) before + handing off to the shared token-issue path (AuthenticationServiceBase.cs:194).

      +

      RegisterAsync (AuthenticationServiceBase.cs:203) rate-limits by source IP, rejects a duplicate email as a conflict, hashes the password, saves, and only then runs the app's post-commit hook, counts - the registration, and opens the session (AuthenticationServiceBase.cs:266-330). The up-front email + the registration, and opens the session (AuthenticationServiceBase.cs:215-279). The up-front email check is a check-then-act, so two concurrent registrations for the same address both pass it and the loser only fails on the insert. The save is therefore wrapped in a deliberately broad catch that re-checks the address and, if it now exists, returns the same conflict the serialized path would - have produced, rethrowing anything else (AuthenticationServiceBase.cs:291-319); the shared failure - factory keeps the two paths indistinguishable to the caller (AuthenticationServiceBase.cs:915). The + have produced, rethrowing anything else (AuthenticationServiceBase.cs:240-268); the shared failure + factory keeps the two paths indistinguishable to the caller (AuthenticationServiceBase.cs:606). The catch is broad because the Application layer has no EF Core dependency by layer rule and cannot name DbUpdateException; the re-check is what narrows it, and it deliberately runs on CancellationToken.None so a cancelled save can still be classified - (AuthenticationServiceBase.cs:313). RefreshTokenAsync (AuthenticationServiceBase.cs:334) - extracts claims from the expired access token (signature still verified, only lifetime skipped, - AuthenticationServiceBase.cs:347), reads the identifier off sub through - ClaimsPrincipalExtensions (AuthenticationServiceBase.cs:358), and - then resolves the presented refresh token to its session row. Every failure path returns a + (AuthenticationServiceBase.cs:262). The password rule a registration is validated against is + defined exactly once, in PasswordComplexity + (MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/PasswordComplexity.cs:18): 8 to 128 UTF-16 code + units (PasswordComplexity.cs:21, PasswordComplexity.cs:24) with an uppercase letter, a lowercase + letter, a digit and a special character, each a Unicode-aware generated regex with a one-second match + timeout, so an accented or CJK letter counts as a letter and never as the special character + (PasswordComplexity.cs:12-16, PasswordComplexity.cs:27-43), combined in Evaluate + (PasswordComplexity.cs:52-57). It lives in Shared because both sides read it: the server's + StrongPasswordRules matches the same four patterns and the same ceiling + (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:197-201), and + the client form attribute calls Evaluate + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/PasswordComplexityAttribute.cs:24), so the + two cannot give different verdicts for one input, which is the validation-parity point of rubric §24 + the type's own remarks cite (PasswordComplexity.cs:5-10).

      +

      RefreshTokenAsync (AuthenticationServiceBase.cs:283) extracts claims from the expired access + token (signature still verified, only lifetime skipped, AuthenticationServiceBase.cs:295-297), reads + the identifier off sub through ClaimsPrincipalExtensions + (AuthenticationServiceBase.cs:307), carries the mfa claim the user already earned across the + rotation so a signed-in session is not quietly demoted every fifteen minutes + (AuthenticationServiceBase.cs:327-332), and then hands the presented refresh token to the issuer's + RotateAsync (AuthenticationServiceBase.cs:337-343). Every failure path returns a Result rather than throwing, matching the framework-wide Result pattern (see primer §2).

      The request and response DTOs for these flows (LoginRequest, @@ -379,31 +406,32 @@

      Refresh sessions: one row per devic RefreshSession (MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:31), so signing in on a phone leaves a laptop signed in, and the store holds only the token's digest: Create hashes on the way in - so the plaintext never reaches a property (RefreshSession.cs:112-145), and HashToken is an + so the plaintext never reaches a property (RefreshSession.cs:125-158), and HashToken is an unsalted, deterministic SHA-256 rendered as 64 upper-case hex characters precisely because lookups are - by hash (RefreshSession.cs:160-164, width constant at RefreshSession.cs:34). The encoding is + by hash (RefreshSession.cs:173-177, width constant at RefreshSession.cs:42). The encoding is part of the contract, not an implementation detail: the type's own remarks give the byte-for-byte SQL - Server equivalent a consumer's data migration has to reproduce (RefreshSession.cs:151-157). The row + Server equivalent a consumer's data migration has to reproduce (RefreshSession.cs:164-170). The row is deliberately not an aggregate: no audit stamps, no soft-delete flag, no concurrency token, like OutboxMessage and AuditTrailEntry, because rows are only ever inserted or revoked and no global - query filter may hide a revoked row from the reuse check (RefreshSession.cs:22-29). Revoke is + query filter may hide a revoked row from the reuse check (RefreshSession.cs:24-31). Revoke is idempotent by refusal, so the first reason and instant recorded are the ones kept - (RefreshSession.cs:174-189), and the four reason constants (Rotated, SignedOut, ReuseDetected, - SessionCapExceeded, RefreshSession.cs:46-55) are what an operator reads afterwards.

      + (RefreshSession.cs:187-202), and the four reason constants (Rotated, SignedOut, ReuseDetected, + SessionCapExceeded, RefreshSession.cs:54-63) are what an operator reads afterwards.

      Rotation leaves a chain, and the chain is the security mechanism. Using a session revokes it and - records the successor in ReplacedByTokenHash (RefreshSession.cs:79), so presenting an + records the successor in ReplacedByTokenHash (RefreshSession.cs:87), so presenting an already-rotated token lands on a revoked row rather than on nothing: that is the ADR-050 reuse signal, and - the workflow answers it by revoking every live session the user holds - (AuthenticationServiceBase.cs:742-749, AuthenticationServiceBase.cs:844-854). The three + AuthSessionIssuer answers it by revoking every live session the user holds + (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:293-300, + AuthSessionIssuer.cs:378-388). The three rejections behind the single generic error are deliberately different in what they do - (AuthenticationServiceBase.cs:714-754): an unknown hash, or one belonging to another account, is + (AuthSessionIssuer.cs:263-305): an unknown hash, or one belonging to another account, is failed alone, since revoking the family on it would let anyone holding one of this user's expired access tokens sign them out everywhere by posting a random string; a revoked row revokes the family; an expired row is an ordinary end of life, so that device re-authenticates while the others keep working. Two requests presenting the same still-live token are covered by the same rule: rotation is claimed atomically through IRefreshSessionStore.TryRotateAsync - (AuthenticationServiceBase.cs:824-838), and the request that loses the claim is answered exactly + (AuthSessionIssuer.cs:340-388), and the request that loses the claim is answered exactly like a replay because a caller cannot tell the two apart.

      IRefreshSessionStore (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/IRefreshSessionStore.cs:21) is the narrow @@ -421,34 +449,39 @@

      Refresh sessions: one row per devic (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/EFRefreshSessionStore.cs:108-133). That is the [Rubric §8, Data Architecture] half of the story, and the store is registered scoped alongside the unit of work it shares a DbContext with, so a login and its session insert commit - together (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:160-166).

      -

      The workflow around the port is small and worth reading end to end. - IssueTokensAsync (AuthenticationServiceBase.cs:554) opens the session before it mints the access - token, because the token carries the session's id and a session only has an id once it has been - created (AuthenticationServiceBase.cs:564-578); OpenSessionAsync - (AuthenticationServiceBase.cs:762) mints the refresh token, builds the row, and first enforces the - per-user cap by revoking the oldest live sessions - (AuthenticationServiceBase.cs:784, AuthenticationServiceBase.cs:864-877), so one account cannot - grow the table without bound while a legitimate sign-in never fails. Both helpers return - IssuedSession (AuthenticationServiceBase.cs:924), the private pair of "the - plaintext token, which exists nowhere else" and "the row id". The id reaches the client as the - standard sid claim, stamped by SessionStampingTokenService - (AuthenticationServiceBase.cs:936), a pass-through ITokenService armed for the duration of the - app's CreateAccessToken call (AuthenticationServiceBase.cs:618-651, - AuthenticationServiceBase.cs:792-802). Doing it with a wrapper rather than by changing the hook's + together (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:166-172).

      +

      The workflow around the port lives in AuthSessionIssuer + (AuthSessionIssuer.cs:39), not in the authentication base, and is small and worth reading end to + end. IssueTokensAsync (AuthenticationServiceBase.cs:417) hands the user's id and a minting callback + to the issuer's IssueAsync (AuthSessionIssuer.cs:62), which opens the session before it mints + the access token, because the token carries the session's id and a session only has an id once it has + been created (AuthSessionIssuer.cs:73-86). OpenSessionAsync (AuthSessionIssuer.cs:312) mints the + refresh token, builds the row, and, before staging the insert, enforces the per-user cap by revoking + the oldest live sessions (AuthSessionIssuer.cs:334, AuthSessionIssuer.cs:407-429), so one account + cannot grow the table without bound while a legitimate sign-in never fails. OpenSessionAsync and its + rotation twin RotateSessionAsync (AuthSessionIssuer.cs:351) both return + IssuedSession (AuthSessionIssuer.cs:436), the private pair of "the plaintext + token, which exists nowhere else" and "the row id". The id reaches the client as the standard sid + claim, stamped by SessionStampingTokenService + (AuthSessionIssuer.cs:448), a private pass-through ITokenService that the issuer exposes as its + TokenService (AuthSessionIssuer.cs:50-53) and arms through MintForSession for the duration of the + app's CreateAccessToken call (AuthSessionIssuer.cs:130-145, reached from + AuthenticationServiceBase.cs:469-470). Doing it with a wrapper rather than by changing the hook's signature is what makes the claim additive: every existing subclass keeps compiling and starts - emitting sid with no edit. GetSessionsAsync (AuthenticationServiceBase.cs:481) projects the + emitting sid with no edit. GetSessionsAsync (AuthenticationServiceBase.cs:386) delegates to the + issuer's ListActiveAsync (AuthSessionIssuer.cs:191), which projects the user's live sessions into RefreshSessionSummaryResponse (MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/Responses/RefreshSessionSummaryResponse.cs:23), newest first, - flagging the caller's own device by comparing against the token's sid - (AuthenticationServiceBase.cs:489-502); the response deliberately omits the token hash and the + flagging the caller's own device by comparing against the session id the caller passes from the token's + sid (AuthSessionIssuer.cs:199-212); the response deliberately omits the token hash and the rotation link, since nothing a client does with a session needs anything but its id - (RefreshSessionSummaryResponse.cs:6-11). RevokeSessionByIdAsync - (AuthenticationServiceBase.cs:519) signs one device out and treats an already-revoked row as a - success that writes nothing, because a device list clicked twice is the most ordinary duplicate in the - feature (AuthenticationServiceBase.cs:532-537), while RevokeTokenAsync - (AuthenticationServiceBase.cs:416) degrades to signing every device out when the presented token does - not identify a live session of this user's (AuthenticationServiceBase.cs:429-447). Those methods + (RefreshSessionSummaryResponse.cs:6-11). RevokeSessionByIdAsync + (AuthenticationServiceBase.cs:402, delegating to AuthSessionIssuer.cs:227) signs one device out + and treats an already-revoked row as a success that writes nothing, because a device list clicked + twice is the most ordinary duplicate in the feature (AuthSessionIssuer.cs:242-245), while + RevokeTokenAsync (AuthenticationServiceBase.cs:352, delegating to AuthSessionIssuer.cs:148) + degrades to signing every device out when the presented token does not identify a live session of + this user's (AuthSessionIssuer.cs:158-172). Those methods surface on IAuthenticationService (IAuthenticationService.cs:79, IAuthenticationService.cs:96, IAuthenticationService.cs:115) and are exposed by AuthControllerBase as revoke, my-sessions, @@ -471,7 +504,7 @@

      Refresh sessions: one row per devic (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/RefreshSessionCleanupService.cs:48) is registered only when the flag is set, so a service with no RefreshSessions table never starts a sweep over a table it does not have - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:168-175), and the mapping + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:178-185), and the mapping itself is opt-in through RefreshSessionModelBuilderExtensions.ApplyRefreshSessionConfiguration (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/RefreshSessionModelBuilderExtensions.cs:34). @@ -581,15 +614,15 @@

      The second fact a Base32 shared secret (TotpTwoFactorService.cs:40-41), render the otpauth://totp/... provisioning URI an authenticator app is keyed from (TotpTwoFactorService.cs:44, TotpTwoFactorService.cs:54), verify a code inside the configured skew window (TotpTwoFactorService.cs:58), and generate and match - single-use recovery codes (TotpTwoFactorService.cs:87, TotpTwoFactorService.cs:116). It is + single-use recovery codes (TotpTwoFactorService.cs:91, TotpTwoFactorService.cs:120). It is deliberately stateless and persistence-free, so a data migration enrolling existing accounts can call it directly and every method is testable against a fixed clock (ITwoFactorService.cs:10-15). Recovery - codes travel as a RecoveryCodeSet (ITwoFactorService.cs:84), plaintext and + codes travel as a RecoveryCodeSet (ITwoFactorService.cs:97), plaintext and hashes paired positionally so a caller cannot store one set and display another, and the plaintext exists only in the returned value. Matching compares in fixed time through - CryptographicOperations.FixedTimeEquals with no early exit (TotpTwoFactorService.cs:138), and the + CryptographicOperations.FixedTimeEquals with no early exit (TotpTwoFactorService.cs:142), and the codes are Base32 rather than Base64 because that alphabet has no case ambiguity for a user reading one - off paper (TotpTwoFactorService.cs:94-96).

      + off paper (TotpTwoFactorService.cs:98-100).

      TwoFactorSettings (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/TwoFactorSettings.cs:15) binds from Authentication:TwoFactor (TwoFactorSettings.cs:18) and defaults to the RFC 6238 values every @@ -631,15 +664,15 @@

      The second fact credential, so answering "this account needs a second factor" tells the owner something rather than telling an address sweeper which accounts are protected (ITwoFactorAuthenticator.cs:19-21). The shipped TwoFactorAuthenticator - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs:25) reads + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs:36) reads the state and returns TwoFactorOutcome.NotEnrolled when there is no active - factor, so an unenrolled account's sign-in is unchanged (TwoFactorAuthenticator.cs:40, - ITwoFactorAuthenticator.cs:47); otherwise it verifies a TOTP code (TwoFactorAuthenticator.cs:53), - falls back to matching and spending a recovery code (TwoFactorAuthenticator.cs:56, - TwoFactorAuthenticator.cs:71), and fails with one of the two TwoFactorErrors + factor, so an unenrolled account's sign-in is unchanged (TwoFactorAuthenticator.cs:114, + ITwoFactorAuthenticator.cs:47); otherwise it verifies a TOTP code (TwoFactorAuthenticator.cs:114), + falls back to matching and spending a recovery code (TwoFactorAuthenticator.cs:70, + TwoFactorAuthenticator.cs:85), and fails with one of the two TwoFactorErrors codes: Authentication.TwoFactorRequired when a code was needed and none arrived, Authentication.TwoFactorInvalid when one arrived and did not verify - (TwoFactorAuthenticator.cs:48, TwoFactorAuthenticator.cs:60, + (TwoFactorAuthenticator.cs:61, TwoFactorAuthenticator.cs:74, MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/TwoFactorErrors.cs:18, TwoFactorErrors.cs:21). Which method satisfied the challenge is what the issued token records: AuthClaimTypes names the mfa claim and its two values, otp and recovery @@ -662,40 +695,40 @@

      Forgot password: a cach wide: IssueAsync mints a token for an address (IPasswordResetTokenService.cs:23) and ValidateAndConsumeAsync redeems it exactly once (IPasswordResetTokenService.cs:36). The implementation, PasswordResetTokenService - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:26), rides on + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:33), rides on ICacheService and buys four properties in a few lines each:

      • One active token per email. Issuing writes the same per-address key - (PasswordResetTokenService.cs:40, PasswordResetTokenService.cs:77), so requesting a new link + (PasswordResetTokenService.cs:53, PasswordResetTokenService.cs:90), so requesting a new link retires the previous one.
      • Hashed at rest. Only the Base64 of the token's SHA-256 is stored - (PasswordResetTokenService.cs:44-45, PasswordResetTokenService.cs:71-77), so a cache dump hands + (PasswordResetTokenService.cs:57-58, PasswordResetTokenService.cs:84-90), so a cache dump hands out no working reset links, and the comparison on redemption is constant time through - CryptographicOperations.FixedTimeEquals (PasswordResetTokenService.cs:107).
      • + CryptographicOperations.FixedTimeEquals (PasswordResetTokenService.cs:146).
      • An attempt cap. A wrong token increments a counter on the record, and the record is discarded at - MaxValidationAttempts (PasswordResetTokenService.cs:121-142, default 5, + MaxValidationAttempts (PasswordResetTokenService.cs:160-181, default 5, MMCA.Common/Source/Core/MMCA.Common.Application/Auth/PasswordResetSettings.cs:36). The rewrite after a wrong guess uses the record's remaining lifetime rather than a fresh one - (PasswordResetTokenService.cs:135-141), so guessing cannot extend the redeemable window.
      • + (PasswordResetTokenService.cs:174-180), so guessing cannot extend the redeemable window.
      • A per-email request throttle. A counter carrying the window's TTL caps how often one address can - trigger an email (PasswordResetTokenService.cs:55-66, default 3 per 60 minutes, + trigger an email (PasswordResetTokenService.cs:68-79, default 3 per 60 minutes, PasswordResetSettings.cs:40, PasswordResetSettings.cs:44), and a successful redemption deletes - the token and that counter (PasswordResetTokenService.cs:115-116) so a legitimate reset does not + the token and that counter (PasswordResetTokenService.cs:154-155) so a legitimate reset does not leave the user throttled out of a later one.

      Keys are built from an Email-normalized identity, through the same EmailIdentity helper and for the same reason LoginProtectionService does it - (PasswordResetTokenService.cs:34-42). The cached record, PasswordResetEntry - (PasswordResetTokenService.cs:160), is deliberately all JSON primitives: cache values round-trip + (PasswordResetTokenService.cs:47-55). The cached record, PasswordResetEntry + (PasswordResetTokenService.cs:199), is deliberately all JSON primitives: cache values round-trip through System.Text.Json, so a value object or a byte[] member would not survive a distributed - backing store (PasswordResetTokenService.cs:151-155). Token material is 32 random bytes, Base64Url - encoded (PasswordResetTokenService.cs:30, PasswordResetTokenService.cs:68), redeemable for + backing store (PasswordResetTokenService.cs:190-194). Token material is 32 random bytes, Base64Url + encoded (PasswordResetTokenService.cs:39, PasswordResetTokenService.cs:81), redeemable for TokenLifetimeMinutes (default 30, PasswordResetSettings.cs:29), and every rejection (unknown, expired, mismatched, attempt-capped) collapses into one generic failure - (PasswordResetTokenService.cs:144-148). The settings bind from the PasswordReset configuration + (PasswordResetTokenService.cs:183-187). The settings bind from the PasswordReset configuration section and the service is registered scoped in Infrastructure DI (PasswordResetSettings.cs:13, - MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:154-158).

      + MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:160-164).

      The workflow around the port lives in the group-14 handler bases, and it is where the anti-enumeration rule is enforced. ForgotPasswordHandlerBase<TUser, TCommand> @@ -740,16 +773,16 @@

      Email confir (IEmailConfirmationTokenService.cs:28) and ValidateAndConsumeAsync redeeming it exactly once and returning the confirmed account's identifier (IEmailConfirmationTokenService.cs:41). The implementation EmailConfirmationTokenService - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:35) rides on + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:36) rides on ICacheService under its own key prefix and buys the same four - properties: 32 random bytes Base64Url encoded (EmailConfirmationTokenService.cs:39, - EmailConfirmationTokenService.cs:64), only the token's SHA-256 stored - (EmailConfirmationTokenService.cs:130), a constant-time comparison on redemption - (EmailConfirmationTokenService.cs:103), and every rejection collapsed into one generic failure - (EmailConfirmationTokenService.cs:155-157). Its token and request-counter keys go through the same + properties: 32 random bytes Base64Url encoded (EmailConfirmationTokenService.cs:41, + EmailConfirmationTokenService.cs:66), only the token's SHA-256 stored + (EmailConfirmationTokenService.cs:135), a constant-time comparison on redemption + (EmailConfirmationTokenService.cs:108), and every rejection collapsed into one generic failure + (EmailConfirmationTokenService.cs:160-162). Its token and request-counter keys go through the same EmailIdentity normalization as the reset keys - (EmailConfirmationTokenService.cs:125, EmailConfirmationTokenService.cs:127). The cached record is - EmailConfirmationEntry (EmailConfirmationTokenService.cs:169), JSON + (EmailConfirmationTokenService.cs:130, EmailConfirmationTokenService.cs:132). The cached record is + EmailConfirmationEntry (EmailConfirmationTokenService.cs:174), JSON primitives only for the same round-trip reason PasswordResetEntry is.

      EmailConfirmationSettings (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/EmailConfirmation/EmailConfirmationSettings.cs:10) @@ -916,19 +949,19 @@

      Authorization: permissions and (OwnerOrAdminFilterOptions.cs:16-31, ADR-033), with OwnershipHelper - (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:10) supplying both - the bypass-role check (OwnershipHelper.cs:17) and the query-scoping specification factory that + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:11) supplying both + the bypass-role check (OwnershipHelper.cs:18) and the query-scoping specification factory that controllers use to narrow collection endpoints to the caller's own rows - (OwnershipHelper.cs:34-67).

      + (OwnershipHelper.cs:35-68).

      Compiled grants are the baseline, not the ceiling. A host that needs an operator to move a capability between roles without a redeploy calls AddStoredPermissionGrants(configuration) - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:104), which binds + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:108), which binds PermissionGrantSettings (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Permissions/PermissionGrantSettings.cs:12, - bound at DependencyInjection.Auth.cs:106-109), registers the EF-backed - IPermissionGrantStore (DependencyInjection.Auth.cs:114), and decorates the + bound at DependencyInjection.Auth.cs:110-113), registers the EF-backed + IPermissionGrantStore (DependencyInjection.Auth.cs:118), and decorates the compiled registry with LayeredPermissionRegistry - (DependencyInjection.Auth.cs:135-146). The layering rule is a union with no deny row: a role holds a + (DependencyInjection.Auth.cs:139-150). The layering rule is a union with no deny row: a role holds a permission when either the compiled registry or the stored grants say so (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Permissions/LayeredPermissionRegistry.cs:30, LayeredPermissionRegistry.cs:55-65), so a bad edit can add a capability but can never take away one @@ -939,15 +972,15 @@

      Authorization: permissions and IPermissionGrantCache.cs:11-19), and IPermissionGrantCacheInvalidator (IPermissionGrantCache.cs:51, IPermissionGrantCache.cs:59) is what an edit calls. The consequence a host accepts is that a grant - edit reaches other replicas within CacheSeconds (default 300, PermissionGrantSettings.cs:23), with - DataSourceName naming the one database that carries the rows (PermissionGrantSettings.cs:31), + edit reaches other replicas within CacheSeconds (default 300, PermissionGrantSettings.cs:25), with + DataSourceName naming the one database that carries the rows (PermissionGrantSettings.cs:33), exactly as RefreshSessionSettings does. The row itself, PermissionGrant (MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/PermissionGrant.cs:24), is a two-column identity (PermissionGrant.cs:39, PermissionGrant.cs:45) trimmed and length-validated in a Result-returning factory (PermissionGrant.cs:64, widths at PermissionGrant.cs:27 and PermissionGrant.cs:30), and its table is mapped only for the host that opted in, through a model gate - whose mere registration is the opt-in the context reads (DependencyInjection.Auth.cs:112).

      + whose mere registration is the opt-in the context reads (DependencyInjection.Auth.cs:116).

      The administration API sits on top of the same grants. IRoleAdministrationService (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Administration/IRoleAdministrationService.cs:29) @@ -990,7 +1023,7 @@

      Authorization: permissions and the requirement is path-aware. FallbackAuthorizationOptions (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/Fallback/FallbackAuthorizationOptions.cs:14) carries the Enabled switch (on by default, FallbackAuthorizationOptions.cs:52) and the editable - exempt-prefix list seeded from the framework defaults (FallbackAuthorizationOptions.cs:61); the + exempt-prefix list seeded from the framework defaults (FallbackAuthorizationOptions.cs:64); the handler is registered with TryAddEnumerable and the policy attached only while the switch is set (AuthorizationExtensions.cs:80-81, AuthorizationExtensions.cs:86-89).

      Session cookies: keeping SSR authenticated

      @@ -998,10 +1031,10 @@

      Session cookies: keeping SSR browser memory, but a cold server-side render (a new tab, an F5, an external deep link) has no memory to read, so an [Authorize] page would bounce to /login before the interactive phase starts. The fix is a pair of HttpOnly cookies (mmca_auth_access, mmca_auth_refresh, - MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:17-18) + MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:18-19) seeded and cleared from JS through SessionCookieEndpoints - (SessionCookieEndpoints.cs:15, SessionCookieEndpoints.cs:34-44, request body - SessionCookieRequest at SessionCookieEndpoints.cs:77), written with one + (SessionCookieEndpoints.cs:16, SessionCookieEndpoints.cs:34-44, request body + SessionCookieRequest at SessionCookieEndpoints.cs:92), written with one shared set of attributes by SessionCookieJar (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieJar.cs:11: HttpOnly, Secure outside Development, SameSite=Lax, Path=/, and a 7-day max age aligned to the @@ -1025,33 +1058,33 @@

      Session cookies: keeping SSR at CookieSessionRefreshMiddleware.cs:35) runs before UseAuthentication on qualifying navigations (GET plus an Accept header containing text/html, CookieSessionRefreshMiddleware.cs:28-31) and delegates to CookieSessionRefresher - (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:52) through - the ICookieSessionRefresher port (CookieSessionRefresher.cs:30). The + (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:74) through + the ICookieSessionRefresher port (CookieSessionRefresher.cs:32). The refresher first tries to read a still-valid expiry out of the access cookie with a 30-second skew - allowance (CookieSessionRefresher.cs:60, CookieSessionRefresher.cs:155-184); failing that it + allowance (CookieSessionRefresher.cs:84, CookieSessionRefresher.cs:155-184); failing that it exchanges the refresh cookie at the API's auth/refresh endpoint server-to-server - (CookieSessionRefresher.cs:128-131), so the refresh token never reaches browser JS. It then writes + (CookieSessionRefresher.cs:179-182), so the refresh token never reaches browser JS. It then writes the rotated pair back as cookies and stashes the fresh access token on HttpContext.Items (CookieSessionRefresher.cs:88-92) so the current request's authentication reads the new token: CookieTokenReader checks that item before falling back to the request cookie (CookieTokenReader.cs:17, CookieTokenReader.cs:27-33). Concurrent refreshes are collapsed into a single flight by a KeyedSemaphoreStripe keyed on the refresh token plus a 10-second rotation-grace IMemoryCache entry keyed by the old refresh token - (CookieSessionRefresher.cs:61, CookieSessionRefresher.cs:63, CookieSessionRefresher.cs:96-112, - CookieSessionRefresher.cs:145), so a queued herd of requests cannot double-rotate. Striping rather + (CookieSessionRefresher.cs:85, CookieSessionRefresher.cs:87, CookieSessionRefresher.cs:96-112, + CookieSessionRefresher.cs:198), so a queued herd of requests cannot double-rotate. Striping rather than one process-wide lock is deliberate and stated in source: the lock is held across an outbound HTTP call, so a single semaphore serialized every unrelated user's cold navigation behind whichever refresh - was in flight (CookieSessionRefresher.cs:45-50); two unrelated tokens sharing a stripe is harmless + was in flight (CookieSessionRefresher.cs:67-72); two unrelated tokens sharing a stripe is harmless because the grace cache is re-checked per token after acquiring - (CookieSessionRefresher.cs:105-109). A transport failure is not cached and renders the request + (CookieSessionRefresher.cs:155-159). A transport failure is not cached and renders the request anonymously rather than throwing a 500 out of SSR (CookieSessionRefresher.cs:118-123, CookieSessionRefresher.cs:148-152). The same refresher backs the same-origin POST /auth/session/token endpoint the browser polls to hydrate its in-memory token - (SessionCookieEndpoints.cs:50-65), guarded by SameSite=Lax plus a Sec-Fetch-Site cross-site - rejection (SessionCookieEndpoints.cs:53-56, SessionCookieEndpoints.cs:73-75) and returning - SessionTokenResponse (CookieSessionRefresher.cs:21), the browser-safe + (SessionCookieEndpoints.cs:58-80), guarded by SameSite=Lax plus a Sec-Fetch-Site cross-site + rejection (SessionCookieEndpoints.cs:61-64, SessionCookieEndpoints.cs:88-90) and returning + SessionTokenResponse (CookieSessionRefresher.cs:23), the browser-safe projection of the internal SessionTokenResult - (CookieSessionRefresher.cs:15) that deliberately omits the refresh token. This whole cluster is + (CookieSessionRefresher.cs:17) that deliberately omits the refresh token. This whole cluster is ADR-022's server half; the client half across Blazor Server, WASM, and MAUI is ADR-051.

      @@ -1092,16 +1125,16 @@

      Shared primitives and adjacent m caller waits on a semaphore no longer in the table while another creates a fresh one, and never removing it lets caller-supplied keys grow the table without bound (KeyedSemaphoreStripe.cs:7-16). Its consumers today are - CookieSessionRefresher (above, CookieSessionRefresher.cs:63), + CookieSessionRefresher (above, CookieSessionRefresher.cs:87), the IdempotencyFilter - (MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:90), + (MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:91), CachingQueryDecorator<TQuery, TResult> (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/CachingQueryDecorator.cs:250), MemoryCacheService - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:38), and the + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:45), and the default GetOrCreateAsync lock table on ICacheService - (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:145). + (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:192). InProcessDistributedLock is the deliberate exception: it keys on the exact key in a ConcurrentDictionary instead, because its contract has a bounded wait, and stripe false-sharing would turn that into a spurious @@ -1189,7 +1222,7 @@

      Shared primitives and adjacent m and the StronglyTypedIdTypeConverters registrar (MMCA.Common/Source/Core/MMCA.Common.Shared/Identifiers/StronglyTypedIdTypeConverter.cs:21, StronglyTypedIdTypeConverter.cs:82, StronglyTypedIdTypeConverter.cs:89, - StronglyTypedIdTypeConverter.cs:108), object mapping through + StronglyTypedIdTypeConverter.cs:119), object mapping through StronglyTypedIdMappings<TSelf, TValue>, whose four static methods are the contract because Mapperly discovers them by signature on a closed generic type (MMCA.Common/Source/Core/MMCA.Common.Shared/Identifiers/StronglyTypedIdMappings.cs:31, @@ -1301,7 +1334,7 @@

      FallbackAuthorizationOptions

      (AddAuthorizationPolicies(options => options.Enabled = false)) rather than by omission (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/Fallback/FallbackAuthorizationOptions.cs:47-49). ExemptPathPrefixes - (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/Fallback/FallbackAuthorizationOptions.cs:61) + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/Fallback/FallbackAuthorizationOptions.cs:64)
      is a mutable IList<string> seeded with [.. DefaultExemptPathPrefixes], so a host appends its own static roots without losing the framework defaults; the doc comment is explicit that application endpoints belong on [AllowAnonymous], not in this list, because that is what the anonymous-endpoint @@ -1310,6 +1343,14 @@

      FallbackAuthorizationOptions

    569. Why it's built this way: matching is segment-based and case-insensitive against these prefixes (per the ExemptPathPrefixes doc comment), so a coarse prefix like /_framework covers everything beneath it without enumerating individual asset paths.
    570. +
    571. Caveats / not-in-source: the exemption is purely path-based. An undecorated controller routed + under one of these prefixes would pass ungated, and the doc comment names + AnonymousEndpointTestsBase.Endpoints_DeclareAnAuthorizationDecision + as the gate that keeps such a controller from landing + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/Fallback/FallbackAuthorizationOptions.cs:59-62). + That test is off by default, so a repo only gets this protection once it opts into the stricter + gate + (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Api/AnonymousEndpointTestsBase.cs:55-56,118).
    572. Where it's used: bound via services.AddOptions<FallbackAuthorizationOptions>() and read by FallbackAuthorizationHandler inside AuthorizationExtensions.AddAuthorizationPolicies.
    573. @@ -1752,15 +1793,17 @@

      AuthorizationExtensions

      OwnershipHelper

      -

      MMCA.Common.API · MMCA.Common.API.Authorization · MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:10 · Level 9 · class (static)

      +

      MMCA.Common.API · MMCA.Common.API.Authorization · MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:11 · Level 9 · class (static)

        -
      • What it is: static helpers a controller calls to scope a query to the current user's own data, - returning a specification that filters by owner id, or null when the caller holds the privileged - bypass role and should see everything.
      • +
      • What it is: static helpers a controller calls to enforce ownership: to scope a query to the + current user's own data (a specification that filters by owner id, or null when the caller holds + the privileged bypass role), to refuse a scoped read whose owner claim cannot be resolved, and to + check that the caller owns one specific record.
      • Depends on: ICurrentUserService (Application layer, imported at - MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:1). The - specification it hands back is typically a + MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:1), plus + Result and Error + for the two gate methods. The specification it hands back is typically a Specification<TEntity, TIdentifierType>, though the helper itself never says so (see caveats).
      • Concept introduced, ownership scoping at the query level, as distinct from the filter's gate. @@ -1774,60 +1817,91 @@

        OwnershipHelper

        for collection routes.
      • Walkthrough
        • IsAdmin(ICurrentUserService, string bypassRole) - (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:17): a + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:18): a case-insensitive compare of the current user's Role against the bypass role - (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:20). + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:21). bypassRole has no default argument: every caller supplies its own, normally sourced from OwnerOrAdminFilterOptions.BypassRole, so the framework itself declares no role names - (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:13-16). + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:14-17). OwnerOrAdminFilter reuses this same method so the gate and the scoping agree on who bypasses.
        • GetOwnershipSpecification<TSpec, TId>(...) - (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:34): the general + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:35): the general form. It returns null for a bypass-role caller - (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:45-48, no scoping + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:46-49, no scoping needed); otherwise it reads the owner id from the named claim via currentUserService.GetClaimValue<TId>(claimType) and, when present, calls the supplied specFactory(id.Value) to build the scoping specification - (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:50-51). Like + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:51-52). Like IsAdmin, bypassRole is a required parameter with no default - (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:38). The + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:39). The where TId : struct, IParsable<TId> constraint - (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:40) is what lets + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:41) is what lets the claim string be parsed into a strongly-typed id.
        • GetOwnershipSpecification<TSpec>(...) - (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:64): the + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:65): the convenience overload that fixes TId to int and the claim to "customer_id", but still takes bypassRole as a required parameter and forwards it - (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:67).
        • + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:68). +
        • RequireResolvableOwner<TId>(currentUserService, claimType, bypassRole, source, target) + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:91): the + fail-closed gate to call before a read scoped through GetOwnershipSpecification. It returns + Result.Success() when the caller passes IsAdmin or carries a parsable owner claim, and + otherwise a Forbidden failure + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:101-103). The + doc comment frames a missing claim as an authorization answer (403), never an unscoped read and + never a 500 + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:73-78).
        • +
        • ValidateOwnershipAsync<TId>(..., isOwnedBy, source, target, cancellationToken) + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:133): the + single-record gate. A bypass-role caller passes without a lookup + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:146-149); an + unresolvable owner claim is refused with the same Forbidden failure and the lookup never runs + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:151-154); + otherwise the private CheckOwnershipAsync + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:159) awaits the + caller-supplied isOwnedBy(ownerId, cancellationToken) predicate (typically an ExistsAsync + query) and answers Error.NotFound with the given source and target when it is false.
        • +
        • AccessDenied(source, target) + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:174): the one + private factory both gates share, Error.Forbidden("Error.Forbidden", "Access denied.", source, target).
      • Why it's built this way: returning null for bypass-role callers (rather than a "match everything" specification) lets the caller skip filtering entirely on the privileged path; producing a specification rather than running the query keeps the helper in the API layer while the actual - filtering runs in the query pipeline. Removing the "Admin" default arguments (in favor of a required - parameter everywhere) matches the same move on - OwnerOrAdminFilterOptions.BypassRole: the framework no longer names a - default role anywhere in the ownership axis.
      • -
      • Where it's used: called from MMCA.Store controller query actions that must isolate a caller's - data. ShoppingCartsController exposes both an IsAdmin property and a private - GetOwnershipSpecification() over it - (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/ShoppingCartsController.cs:64,66-68), + filtering runs in the query pipeline. Every method takes bypassRole as a required parameter and + checks it through the same IsAdmin, so the scope and both gates agree on who is privileged, and, + like OwnerOrAdminFilterOptions.BypassRole, the framework names no + default role anywhere in the ownership axis. ValidateOwnershipAsync answers a non-owner with 404 + rather than 403 so the existence of another owner's record cannot be probed for + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:113-114).
      • +
      • Where it's used: MMCA.Store's ShoppingCartsController wraps GetOwnershipSpecification + and RequireResolvableOwner in private helpers + (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/ShoppingCartsController.cs:65-67,79-84), and OrdersController does the same - (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/OrdersController.cs:62,65). Its - IsAdmin method is also the bypass check inside OwnerOrAdminFilter + (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/OrdersController.cs:66-68,80-85), + also routing its mutating endpoints through ValidateOwnershipAsync + (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/OrdersController.cs:386-390). + In MMCA.ADC, EventQuestionAnswersController and SessionQuestionAnswersController scope by user + id with GetOwnershipSpecification and gate with RequireResolvableOwner + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventQuestionAnswersController.cs:108,136, + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionQuestionAnswersController.cs:108,136). + IsAdmin also decides the export override AllowUnscopedExport on all four controllers (for + example + MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/OrdersController.cs:249) and is + the bypass check inside OwnerOrAdminFilter (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnerOrAdminFilter.cs:43).
      • Caveats / not-in-source: two gaps are worth knowing. TSpec is an open generic with only a class constraint - (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:39), so the helper - does not itself require the returned type to be a specification: that contract is the caller's. And a - non-admin caller whose claim is missing or unparseable also gets null - (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:51), which means - no scoping, so callers must not read null as "admin". Store closes that in the controller with a - RequireResolvableOwner() gate that forbids the second case explicitly - (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/ShoppingCartsController.cs:80-90); - the helper itself does not distinguish them.
      • + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:40), so the helper + does not itself require the returned type to be a specification: that contract is the caller's. And + GetOwnershipSpecification also returns null for a non-admin caller whose claim is missing or + unparseable + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:52), which means + no scoping. It does not distinguish that case from the bypass role; RequireResolvableOwner does, + but only if the caller invokes it before the read, which nothing in the helper enforces.

      OwnerOrAdminFilter

      @@ -1937,9 +2011,85 @@

      OwnerOrAdminFilter

      as that case, handled with a specification or an explicit per-id check instead).

      +

      ISessionCookieStore

      +
      +

      MMCA.Common.API · MMCA.Common.API.SessionCookies · MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/ISessionCookieStore.cs:14 · Level 0 · interface

      +
      +
        +
      • What it is: the public port for writing and clearing the two HttpOnly session cookies from server + code that obtained the tokens itself, so it can store them without a browser round trip.
      • +
      • Depends on: HttpContext (ASP.NET Core) only. Its one implementation is + SessionCookieStore, a thin adapter over the internal + SessionCookieJar that applies SessionCookieSettings.
      • +
      • Concept introduced, a public door onto an internal cookie writer. [Rubric §11, Security] + assesses whether cookie hardening has one definition. The jar is internal, so before this + interface only MMCA.Common.API itself could set the session cookies. The doc comment + (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/ISessionCookieStore.cs:7-13) names + the new caller: the same-origin API proxy in MMCA.Common.UI.Web, which receives tokens from the + identity endpoint and must persist them with exactly the options (HttpOnly, Secure outside + Development, the configured SameSite, 7-day lifetime) that the session-cookie endpoints and the + server-side refresher already use. Exposing two methods instead of making the jar public keeps the + option-building in one place.
      • +
      • Walkthrough: void Write(HttpContext context, string accessToken, string refreshToken) (:19) + appends both cookies; void Clear(HttpContext context) (:23) expires both. Neither returns + anything: the cookies are a side effect on context.Response.
      • +
      • Why it's built this way: the proxy keeps every credential server-side + (ADR-131), so it is the one + writer of the cookies on a proxied host and needs a supported way to write them that cannot drift + from the endpoints' own writes.
      • +
      • Where it's used: registered as a singleton by AddServerAuthSessionCookie + (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:191); injected into + SameOriginApiProxyEndpoint + (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpoint.cs:33), + SameOriginProxyTransformer + (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginProxyTransformer.cs:39) + and the handoff route of + SessionHandoffEndpoints + (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SessionHandoffEndpoints.cs:42).
      • +
      +

      SessionClaimsToken

      +
      +

      MMCA.Common.API · MMCA.Common.API.SessionCookies · MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionClaimsToken.cs:14 · Level 0 · class

      +
      +
        +
      • What it is: a static helper that turns a signed access token into a claims-only copy: the same + payload under an unsecured header with an empty signature, readable by the browser but useless as a + credential.
      • +
      • Depends on: BCL and NuGet only: Base64Url (System.Buffers.Text), Encoding, and + JwtSecurityTokenHandler (System.IdentityModel.Tokens.Jwt). Switched on by + SessionCookieSettings.ClaimsOnlyBrowserTokens.
      • +
      • Concept introduced, the claims-only browser token. [Rubric §11, Security] and [Rubric §26, + Front-End Security] both assess what an XSS payload could steal. The doc comment + (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionClaimsToken.cs:7-13) states + the trade: the client can still read the user's claims and the expiry to render its authentication + state, but the token authorizes nothing, because every API validates signatures and rejects an + unsigned token. Script on the page therefore holds nothing worth exfiltrating, while the UI keeps + working off the same claims it always read.
      • +
      • Walkthrough: UnsecuredHeader (:17) is computed once as the base64url (no padding) encoding of + {"alg":"none","typ":"JWT"}, as the comment at :16 records. Create(string? accessToken) (:25) + returns null for a blank value or anything JwtSecurityTokenHandler.CanReadToken refuses + (:27-30), then splits on . and, for a three-segment compact JWT, returns + {UnsecuredHeader}.{payload}. (:32-33); any other shape is also null. The trailing dot keeps the + three-segment compact form with an empty signature.
      • +
      • Why it's built this way: copying the payload segment verbatim (rather than re-serializing claims) + guarantees the browser sees exactly the claims and exp the API issued + (ADR-131).
      • +
      • Where it's used: the /auth/session/token route of + SessionCookieEndpoints under claims-only mode + (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:73-75), + the proxy's response rewrite + (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginProxyTransformer.cs:142) + and its refresh answer + (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpoint.cs:369). + Pinned by MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/SessionClaimsTokenTests.cs: + claims and expiry kept but signature dropped (:20), the result rejected by a signature-validating + API (:37), and null for non-JWT input (:56).
      • +
      • Caveats / not-in-source: "authorizes nothing" is a property of each API's JWT bearer validation + (signature required), not of this type; the test at :37 checks it against one validating handler.
      • +

      SessionCookieRequest

      -

      MMCA.Common.API · MMCA.Common.API.SessionCookies · MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:77 · Level 0 · record

      +

      MMCA.Common.API · MMCA.Common.API.SessionCookies · MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:92 · Level 0 · record

      • What it is: the inbound body for POST /auth/session-cookie: the access and refresh token @@ -1954,14 +2104,73 @@

        SessionCookieRequest

        ADR-022's browser session-cookie scheme.
      • Walkthrough: the whole type is one line, public sealed record SessionCookieRequest(string AccessToken, string RefreshToken) - (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:77). It + (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:92). It is nested in the endpoint class it serves, so the contract sits next to its only route.
      • Where it's used: bound by the POST handler at SessionCookieEndpoints.cs:34, which passes both strings straight to SessionCookieJar (:31).
      +

      SessionCookieSettings

      +
      +

      MMCA.Common.API · MMCA.Common.API.SessionCookies · MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieSettings.cs:11 · Level 0 · class

      +
      +
        +
      • What it is: the options object for both session cookies: which SameSite mode they carry and + whether the browser is ever handed a real access token.
      • +
      • Depends on: SameSiteMode (ASP.NET Core) only. Read through IOptions<SessionCookieSettings> by + SessionCookieEndpoints, SessionCookieStore and + CookieSessionRefresher; its claims-only switch selects + SessionClaimsToken.
      • +
      • Concept introduced, one options object for every cookie write. [Rubric §11, Security] assesses + cookie hardening and credential exposure. A cookie delete only matches when it repeats the write's + attributes, so the seed, refresh, store and clear paths all read SameSite from this one object + instead of each choosing a value.
      • +
      • Walkthrough: SameSite (:19) defaults to SameSiteMode.Lax; the doc comment (:13-18) + explains the same-origin API proxy raises it to Strict because the cookie then authenticates data + calls, not only server-side rendering. ClaimsOnlyBrowserTokens (:29) defaults to false; when + true (doc :21-28), POST /auth/session/token answers with a claims-only token instead of the real + access token, and POST /auth/session-cookie ignores posted tokens and answers 204 without + writing, because the server is then the only writer of the cookies.
      • +
      • Why it's built this way: the defaults are the long-standing cookie behavior (the comment at + DependencyInjection.cs:188-189 says so), so a host that never opts in keeps Lax cookies and real + browser tokens; the proxy tightens both settings in one place + (ADR-131).
      • +
      • Where it's used: registered with defaults by AddServerAuthSessionCookie + (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:190, comment at :188-189). + The proxy's registration in + SameOriginApiProxyServiceExtensions + configures it (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyServiceExtensions.cs:69-74), + copying SameSite from + SameOriginApiProxySettings.SessionCookieSameSite + (default Strict, + MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxySettings.cs:74) + and setting ClaimsOnlyBrowserTokens = true. Claims-only behavior is pinned by + MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/ClaimsOnlySessionCookieEndpointsTests.cs + (:27, :47, :62).
      • +
      +

      SessionRefreshStatus

      +
      +

      MMCA.Common.API · MMCA.Common.API.SessionCookies · MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionRefreshOutcome.cs:4 · Level 0 · enum

      +
      +
        +
      • What it is: the three ways a validate-or-refresh attempt can end: Refreshed = 0 (:10), + Rejected = 1 (:16) and Unavailable = 2 (:23).
      • +
      • Depends on: nothing. Carried by SessionRefreshOutcome.
      • +
      • Concept introduced, a dead session is not the same as an unreachable identity endpoint. + [Rubric §29, Resilience, Reliability & Business Continuity] assesses whether a transient fault is + treated as a permanent one. The doc comments draw the line: Refreshed means a usable access token + is available (still-valid cookie, or a rotated pair written back, :6-9); Rejected means no refresh + cookie or the identity endpoint refused it with 400, 401 or 403, so clearing the cookies is correct + (:12-15); Unavailable covers a 5xx, 429 or other non-refusal status, a timeout, a network failure + or an unreadable body, where the cookies may still hold a live session and must be kept for a retry + (:18-22). A caller that clears cookies on any failure would sign a user out over a gateway blip.
      • +
      • Where it's used: produced by CookieSessionRefresher (its + ClassifyFailure, CookieSessionRefresher.cs:118-121) and branched on by the proxy's + FailRefreshAsync + (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpoint.cs:276).
      • +

      SessionTokenResponse

      -

      MMCA.Common.API · MMCA.Common.API.SessionCookies · MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:21 · Level 0 · record

      +

      MMCA.Common.API · MMCA.Common.API.SessionCookies · MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:23 · Level 0 · record

      • What it is: the JSON body returned by POST /auth/session/token: the access token and its UTC @@ -1973,7 +2182,7 @@

        SessionTokenResponse

        the access token, which the SPA holds in memory for its Bearer calls, and deliberately omits the refresh token, which stays exclusively in the HttpOnly cookie. The doc comment states the rule outright - (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:17-20).
      • + (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:19-22).
      • Walkthrough: public sealed record SessionTokenResponse(string AccessToken, DateTime AccessTokenExpiry) (:20). It is the serialized projection of the internal SessionTokenResult, which is why the two types carry the same two members and different visibility of intent.
      • @@ -1982,7 +2191,7 @@

        SessionTokenResponse

      SessionTokenResult

      -

      MMCA.Common.API · MMCA.Common.API.SessionCookies · MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:15 · Level 0 · record struct

      +

      MMCA.Common.API · MMCA.Common.API.SessionCookies · MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:17 · Level 0 · record struct

      • What it is: the internal carrier for a validated access token plus its UTC expiry, returned by @@ -1995,7 +2204,7 @@

        SessionTokenResult

        thrown exception. [Rubric §12, Performance & Scalability] assesses avoidable allocation; a readonly record struct is the light choice for a result produced on every qualifying navigation.
      • Walkthrough: public readonly record struct SessionTokenResult(string AccessToken, DateTime AccessTokenExpiry) - (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:15), with + (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:17), with the one-line summary at :13 naming its provenance ("acquired from the session cookies").
      • Where it's used: the return type of ICookieSessionRefresher.GetOrRefreshAsync (:36); constructed by @@ -2003,36 +2212,84 @@

        SessionTokenResult

        rotation); unwrapped by SessionCookieEndpoints at SessionCookieEndpoints.cs:61.
      +

      SessionRefreshOutcome

      +
      +

      MMCA.Common.API · MMCA.Common.API.SessionCookies · MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionRefreshOutcome.cs:32 · Level 9 · class

      +
      +
        +
      • What it is: the result of + ICookieSessionRefresher.ValidateOrRefreshAsync and .RefreshAsync: + a SessionRefreshStatus, the token when the status is Refreshed, and the + upstream's Retry-After hint when it is Unavailable (doc comment, + MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionRefreshOutcome.cs:26-31).
      • +
      • Depends on: SessionRefreshStatus and + SessionTokenResult; TimeSpan from the BCL.
      • +
      • Concept: a closed result type built only through named factories, so a caller cannot assemble an + inconsistent combination (a Rejected outcome carrying a token, say). It is the richer sibling of + the nullable SessionTokenResult? that GetOrRefreshAsync still returns: that shape cannot tell a + dead session from a transient failure, this one can. [Rubric §29, Resilience, Reliability & Business + Continuity] is the lens, as introduced at SessionRefreshStatus.
      • +
      • Walkthrough: the constructor is private (:36-41). Status (:44), Session (:47, set only + for Refreshed) and RetryAfter (:53, set only for Unavailable when the identity endpoint sent + one) are get-only. The factories are Refreshed(SessionTokenResult session) (:58-59), Rejected() + (:63), which returns one cached RejectedOutcome instance (:34) since it carries no data, and + Unavailable(TimeSpan? retryAfter = null) (:68-69).
      • +
      • Where it's used: built throughout CookieSessionRefresher and + consumed by the proxy's FailRefreshAsync + (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpoint.cs:274-286): + Rejected ends the session, while Unavailable keeps the cookies and answers 503 with a + Retry-After taken from outcome.RetryAfter or a short default + (ADR-131).
      • +

      ICookieSessionRefresher

      -

      MMCA.Common.API · MMCA.Common.API.SessionCookies · MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:30 · Level 1 · interface

      +

      MMCA.Common.API · MMCA.Common.API.SessionCookies · MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:32 · Level 10 · interface

        -
      • What it is: the "validate-or-refresh over the HttpOnly session cookies" port. One method returns - a currently-valid access token for the request, rotating from the refresh cookie when the access - cookie has expired, or null when there is no valid session.
      • -
      • Depends on: HttpContext (ASP.NET Core) and SessionTokenResult. Its - only implementation is CookieSessionRefresher.
      • +
      • What it is: the "validate-or-refresh over the HttpOnly session cookies" port. It returns a + currently-valid access token for the request, rotating from the refresh cookie when the access + cookie has expired, in two shapes (a nullable token, or a + SessionRefreshOutcome that separates a dead session from a transient + failure), plus a forced rotation for when an API rejected a still-valid-looking access token.
      • +
      • Depends on: HttpContext (ASP.NET Core), SessionTokenResult, + SessionRefreshOutcome and SessionRefreshStatus. + Its only implementation is CookieSessionRefresher.
      • Concept introduced, server-side refresh that browser script never sees. [Rubric §11, Security] assesses where the long-lived credential lives. The type comment - (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:23-29) is + (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:25-31) is the contract in prose: if the access cookie's JWT is still valid it is returned as-is; otherwise the refresh cookie is exchanged at the API's auth/refresh endpoint server-to-server, so the refresh token never reaches browser JS; the rotated pair is written back as HttpOnly cookies; and the fresh access token is stashed on HttpContext.Items so the current request's SSR authentication can read it before the Set-Cookie takes effect on the next request.
      • -
      • Walkthrough: Task<SessionTokenResult?> GetOrRefreshAsync(HttpContext context, CancellationToken cancellationToken = default) (:36). The nullable return is the whole vocabulary: a value means - "here is a good access token", null means "no session, treat this caller as anonymous". The doc - comment at :31-35 flags that setting fresh cookies is a side effect of the call.
      • -
      • Why it's built this way: one interface lets the SSR middleware and the /auth/session/token - endpoint share a single refresh path, so exactly one type decides validity and exactly one type - rotates (ADR-022). It is - also what makes both callers trivially testable against a mock.
      • -
      • Where it's used: injected into +
      • Walkthrough: three members. + Task<SessionTokenResult?> GetOrRefreshAsync(HttpContext context, CancellationToken cancellationToken = default) (:39) keeps the simple vocabulary: a value means "here is a good access token", null + means "no session, treat this caller as anonymous"; its doc comment (:34-38) flags that setting + fresh cookies is a side effect. Task<SessionRefreshOutcome> ValidateOrRefreshAsync(...) (:48) is + the same operation with the failure kept apart: Rejected when there is no refresh cookie or the + identity endpoint refused it, Unavailable for 5xx, 429, timeout or network trouble, "so a caller + that clears cookies on failure does so only for a session that is really dead" (:41-47). + Task<SessionRefreshOutcome> RefreshAsync(...) (:59) exchanges the refresh cookie even when the + access cookie still looks valid (the API rejected it: revoked, or signed with a rotated key), and is + single-flighted exactly like GetOrRefreshAsync, so concurrent callers holding the same refresh + cookie share one rotation (:50-58).
      • +
      • Why it's built this way: one interface lets the SSR middleware, the /auth/session/token + endpoint and the same-origin proxy share a single refresh path, so exactly one type decides validity + and exactly one type rotates + (ADR-022, + ADR-131). Callers that render + anonymously on any failure keep the nullable method; callers that clear cookies use the outcome + methods. It is also what makes every caller testable against a mock.
      • +
      • Where it's used: GetOrRefreshAsync by CookieSessionRefreshMiddleware (which runs before - authentication on navigations) and resolved by the /auth/session/token handler - (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:51). - Registered as a singleton at + authentication on navigations), by the /auth/session/token handler + (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:66) and by + SessionHandoffEndpoints + (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SessionHandoffEndpoints.cs:31); + ValidateOrRefreshAsync and RefreshAsync by + SameOriginApiProxyEndpoint + (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpoint.cs:88, + :321, :358). Registered as a singleton at MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:186.

      CookieSessionRefreshMiddleware

      @@ -2066,7 +2323,7 @@

      CookieSessionRefreshMiddleware

      itself cannot double-rotate a token (ADR-022).
    574. Where it's used: registered on both Blazor Server hosts immediately before UseAuthentication(), - MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:234 (with UseAuthentication() on the very next + MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:246 (with UseAuthentication() on the very next statement at :140) and MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:178 (:180). Its gating rules are pinned one test per branch in MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/CookieSessionRefreshMiddlewareTests.cs: @@ -2079,91 +2336,115 @@

      CookieSessionRefreshMiddleware

      SessionCookieEndpoints

      -

      MMCA.Common.API · MMCA.Common.API.SessionCookies · MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:15 · Level 2 · class

      +

      MMCA.Common.API · MMCA.Common.API.SessionCookies · MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:16 · Level 11 · class

      • What it is: the minimal-API mapper for the three session-cookie routes: POST and DELETE /auth/session-cookie (seed and clear the HttpOnly cookies at login and logout) and POST /auth/session/token (the same-origin validate-or-refresh the browser calls to hydrate its in-memory access token). It also owns the two cookie-name constants and the cross-site guard.
      • Depends on: SessionCookieJar, ICookieSessionRefresher, - SessionCookieRequest, SessionTokenResponse, and - ASP.NET Core routing plus Results. The cookies it writes are read back by - CookieTokenReader.
      • + SessionCookieSettings (via IOptions<T>), + SessionClaimsToken, SessionCookieRequest, + SessionTokenResponse, and ASP.NET Core routing plus Results. The + cookies it writes are read back by CookieTokenReader.
      • Concept introduced, the cookie names are the shared contract. AccessTokenCookieName = "mmca_auth_access" and RefreshTokenCookieName = "mmca_auth_refresh" - (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:17-18) + (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:18-19) are public const, and every other type in this feature (the jar, the reader, the refresher) references them instead of a string literal, so the names have exactly one definition. [Rubric §9, API & Contract Design] is the other lens: three tightly-scoped routes, all excluded from OpenAPI - (ExcludeFromDescription at :31 and :63) because they are browser plumbing, not public API + (ExcludeFromDescription at :32 and :78) because they are browser plumbing, not public API surface.
      • Walkthrough: the mapping method lives inside an extension(IEndpointRouteBuilder endpoints) - block (:20), the C# extension-member syntax this codebase uses for fluent registration + block (:21), the C# extension-member syntax this codebase uses for fluent registration (primer §2), so hosts call - app.MapSessionCookieEndpoints(). Inside MapSessionCookieEndpoints (:22-68): a route group for + app.MapSessionCookieEndpoints(). Inside MapSessionCookieEndpoints (:23-83): a route group for /auth/session-cookie is created, excluded from description, and explicitly marked - AllowAnonymous() (:30-32); the comment above it (:26-29) spells out why the anonymity has to be + AllowAnonymous() (:31-33); the comment above it (:27-30) spells out why the anonymity has to be declared rather than left implicit: the POST seeds the cookie jar at login and the DELETE clears it at logout, so both run before or after a session exists, and the default fallback authorization policy (SEC-Common-16) requires an authenticated caller on any endpoint that does not declare - otherwise, which would make sign-in impossible on a Blazor host. The POST (:34-38) binds a - SessionCookieRequest, calls SessionCookieJar.Append and returns 204; - the DELETE (:40-44) calls SessionCookieJar.Delete and returns 204; both DisableAntiforgery() - because there is no antiforgery token cookie to validate on these calls. The /auth/session/token - POST (:50-65) first rejects an obvious cross-site request with 403 (:53-56), then awaits - refresher.GetOrRefreshAsync (:58); a null result becomes a 401 JSON body - { error = "no_session" } (:60), otherwise a SessionTokenResponse is - serialized (:61). That route is AllowAnonymous() (:64) because it authenticates via the cookies - themselves. The private IsCrossSite (:73-75) inspects the Sec-Fetch-Site request header and - treats a missing header as allowed, which the comment at :72 attributes to older browsers.
      • -
      • Why it's built this way: CSRF is defended in depth rather than by antiforgery tokens. The comment - at :71-72 spells it out: POST-only, SameSite=Lax on the cookies (which already blocks - cross-site cookie attachment), and the Sec-Fetch-Site check together stop a cross-site page from - driving these endpoints, which is what makes disabling antiforgery safe here + otherwise, which would make sign-in impossible on a Blazor host. The POST (:35-46) binds a + SessionCookieRequest plus IOptions<SessionCookieSettings> and, unless + ClaimsOnlyBrowserTokens is set (:40), calls SessionCookieJar.Append with the configured + SameSite (:42); either way it returns 204 (:45). The comment at :37-39 explains the + claims-only branch: on a proxied host the server is the only writer of the cookies and the browser + holds nothing worth posting, and answering 204 rather than an error keeps a client written for the + default mode signing in. The DELETE (:48-52) calls SessionCookieJar.Delete with the same + configured SameSite (:50) and returns 204; both DisableAntiforgery() because there is no + antiforgery token cookie to validate on these calls. The /auth/session/token POST (:58-80) + first rejects an obvious cross-site request with 403 (:61-64), then awaits + refresher.GetOrRefreshAsync (:66); a null result becomes a 401 JSON body + { error = "no_session" } (:69). Otherwise the browser token is the real access token, or, on a + claims-only host, SessionClaimsToken.Create(...) ?? string.Empty (:73-75, comment at :72), and a + SessionTokenResponse is serialized (:76). That route is + AllowAnonymous() (:79) because it authenticates via the cookies themselves. The private + IsCrossSite (:88-90) inspects the Sec-Fetch-Site request header and treats a missing header as + allowed, which the comment at :87 attributes to older browsers.
      • +
      • Why it's built this way: CSRF is defended in depth rather than by antiforgery tokens. The comments + at :57 and :86-87 spell it out: POST-only, a SameSite cookie (which already blocks cross-site + cookie attachment; the comments say Lax, the default, and the same-origin proxy raises it to + Strict through SessionCookieSettings), and the Sec-Fetch-Site check + together stop a cross-site page from driving these endpoints, which is what makes disabling + antiforgery safe here (ADR-022). [Rubric §11, Security]. The group-level AllowAnonymous() is the same defense-in-depth posture applied to authentication instead of CSRF: it is a deliberate, commented opt-out of the fallback policy - (SEC-Common-16) rather than an oversight.
      • + (SEC-Common-16) rather than an oversight. The claims-only branches are what let the same three routes + serve a proxied host without ever handing the browser a credential + (ADR-131).
      • Where it's used: mapped by both Blazor Server hosts, - MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:255 and - MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:194. The routes are exercised end to end by + MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:267 and + MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:261. The routes are exercised end to end by MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/SessionCookieEndpointsTests.cs:125, whose CreateHostAsync builds a real pipeline around the mapper; the cases that matter most are the cross-site 403 (:60), the no-session 401 (:91), and the assertion that a valid session returns - the access token but never the refresh token (:104).
      • + the access token but never the refresh token (:104). The claims-only branches are covered by + MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/ClaimsOnlySessionCookieEndpointsTests.cs: + an unsigned claims copy instead of the credential (:27), script-supplied tokens ignored (:47), and + the delete using the configured SameSite (:62).

      SessionCookieJar

      -

      MMCA.Common.API · MMCA.Common.API.SessionCookies · MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieJar.cs:11 · Level 2 · class

      +

      MMCA.Common.API · MMCA.Common.API.SessionCookies · MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieJar.cs:11 · Level 11 · class

      • What it is: the one internal static helper that writes and clears the two HttpOnly auth cookies, - so the endpoints, the server-side refresher, and the SSR middleware all emit identical cookie + so the endpoints, the server-side refresher, and the public cookie store all emit identical cookie options.
      • -
      • Depends on: CookieOptions, HttpContext and IWebHostEnvironment (ASP.NET Core) plus the - cookie-name constants on SessionCookieEndpoints.
      • +
      • Depends on: CookieOptions, HttpContext, IWebHostEnvironment and SameSiteMode (ASP.NET + Core) plus the cookie-name constants on SessionCookieEndpoints. The + SameSite value its callers pass comes from SessionCookieSettings.
      • Concept introduced, one place to build cookie options. [Rubric §11, Security] assesses cookie hardening. Centralizing BuildOptions - (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieJar.cs:31-38) means - every write is HttpOnly = true (:33), Secure outside Development (:34, + (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieJar.cs:37-44) means + every write is HttpOnly = true (:39), Secure outside Development (:40, !environment.IsDevelopment(), so http://localhost dev still works while every deployed - environment forces HTTPS), SameSite = SameSiteMode.Lax (:35), and Path = "/" (:36). Drift - between the seed, refresh, and clear paths is structurally impossible because all three call this - method. The conditional Secure is the one thing an analyzer objects to, and the suppression carries - its justification inline (:30 and :39 bracket a scoped #pragma warning disable S2092), which is - the house style for an accepted deviation.
      • + environment forces HTTPS), SameSite set to the caller's mode (:41), and Path = "/" (:42). + Drift between the seed, refresh, store and clear paths is structurally impossible because all of + them call this method. The conditional Secure is the one thing an analyzer objects to, and the + suppression carries its justification inline (:36 and :45 bracket a scoped + #pragma warning disable S2092), which is the house style for an accepted deviation.
      • Walkthrough: Lifetime = TimeSpan.FromDays(7) (:14) is aligned to the refresh-token lifetime - by the comment at :13, so a cookie never outlives the credential it carries. Append (:16-21) - builds options once and writes both cookies with that 7-day MaxAge. Delete (:23-28) rebuilds - the options with TimeSpan.Zero, which :37 turns into a null MaxAge, and calls - Cookies.Delete for both names.
      • + by the comment at :13, so a cookie never outlives the credential it carries. Append has two + overloads: the four-argument form (:16-17) forwards with SameSiteMode.Lax, and the five-argument + form (:19-24) builds options once with the given SameSite and writes both cookies with that 7-day + MaxAge. Delete mirrors it: the three-argument form (:26-27) forwards with Lax, and the + four-argument form (:29-34) rebuilds the options with TimeSpan.Zero, which :43 turns into a + null MaxAge, and calls Cookies.Delete for both names.
      • Why it's built this way: a delete must send back the same Path, SameSite and Secure attributes as the original write or the browser will not match the cookie and will not clear it. - Sharing BuildOptions between Append and Delete guarantees that - (ADR-022).
      • + Sharing BuildOptions between Append and Delete, and having every caller pass the same + configured SameSite, guarantees that + (ADR-022). The + Lax-defaulting overloads keep the long-standing call shape for callers with no settings in hand.
      • Where it's used: SessionCookieEndpoints (seed at - SessionCookieEndpoints.cs:36, clear at :37) and + SessionCookieEndpoints.cs:42, clear at :50), CookieSessionRefresher (rewrite after rotation, - CookieSessionRefresher.cs:88). The attributes it emits are asserted directly by + CookieSessionRefresher.cs:137) and SessionCookieStore + (ISessionCookieStore.cs:33 and :36), all passing SessionCookieSettings.SameSite. Its + BuildOptions is also named as the precedent for the culture cookie's conditional Secure + suppression (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationExtensions.cs:124). + The attributes it emits are asserted directly by MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/SessionCookieJarTests.cs.

      CookieSessionRefreshMiddlewareExtensions

      @@ -2185,7 +2466,7 @@

      CookieSessionRefreshMiddleware load-bearing rule in bold: register it immediately before UseAuthentication() on the Blazor Server (UI.Web) host.

    575. Where it's used: the two Blazor Server hosts - (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:234, + (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:246, MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:178). Both the null guard and the pipeline wiring are covered directly at MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/CookieSessionRefreshMiddlewareTests.cs:115 @@ -2229,86 +2510,144 @@

      CookieTokenReader

      (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:180), and covered by MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/CookieTokenReaderTests.cs.
    576. +

      SessionCookieStore

      +
      +

      MMCA.Common.API · MMCA.Common.API.SessionCookies · MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/ISessionCookieStore.cs:28 · Level 12 · class

      +
      +
        +
      • What it is: the internal implementation of ISessionCookieStore: a + two-line adapter that forwards to SessionCookieJar with the configured + SameSite.
      • +
      • Depends on: IWebHostEnvironment and IOptions<SessionCookieSettings> (primary constructor, + MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/ISessionCookieStore.cs:28-30), + SessionCookieSettings and SessionCookieJar.
      • +
      • Concept: the adapter half of the public-port-over-internal-helper split introduced at + ISessionCookieStore. It adds no behavior of its own, which is the point: + every cookie option still comes from the jar's single BuildOptions.
      • +
      • Walkthrough: Write (:32-33) calls SessionCookieJar.Append(context, accessToken, refreshToken, environment, settings.Value.SameSite); Clear (:35-36) calls + SessionCookieJar.Delete(context, environment, settings.Value.SameSite). Reading settings.Value on + each call means writes and deletes always agree on SameSite.
      • +
      • Why it's built this way: internal sealed, so consumers outside MMCA.Common.API depend on the + interface and the jar stays hidden; the one-line summary at :27 names it "the + ISessionCookieStore over SessionCookieJar".
      • +
      • Where it's used: registered as the singleton ISessionCookieStore by + AddServerAuthSessionCookie + (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:191); see + ISessionCookieStore for the proxy types that consume it.
      • +

      CookieSessionRefresher

      -

      MMCA.Common.API · MMCA.Common.API.SessionCookies · MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:52 · Level 4 · class

      +

      MMCA.Common.API · MMCA.Common.API.SessionCookies · MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:74 · Level 13 · class

      • What it is: the singleton implementation of ICookieSessionRefresher. It validates the access cookie's JWT locally - and, when that fails, exchanges the refresh cookie at the API's auth/refresh endpoint - server-to-server, writes the rotated pair back as cookies, and single-flights concurrent refreshes so - a burst of requests rotates the token only once.
      • -
      • Depends on: IHttpClientFactory, IMemoryCache, IWebHostEnvironment and - ILogger<CookieSessionRefresher> (primary constructor, - MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:52-56); - KeyedSemaphoreStripe (:62); SessionCookieJar; + and, when that fails (or when a caller forces it), exchanges the refresh cookie at the API's + auth/refresh endpoint server-to-server, writes the rotated pair back as cookies, single-flights + concurrent refreshes so a burst of requests rotates the token only once, and classifies every + failure as a dead session or a transient one.
      • +
      • Depends on: IHttpClientFactory, IMemoryCache, IWebHostEnvironment, + ILogger<CookieSessionRefresher>, TimeProvider and IOptions<SessionCookieSettings> (primary + constructor, + MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:74-80); + KeyedSemaphoreStripe (:87); SessionCookieJar; + SessionCookieSettings for the cookie SameSite; + SessionRefreshOutcome and SessionRefreshStatus; CookieTokenReader for the Items key; SessionCookieEndpoints for the cookie names; and the AuthenticationResponse / RefreshTokenRequest contracts from MMCA.Common.Shared.Auth. It reads token expiry with - System.IdentityModel.Tokens.Jwt.
      • + System.IdentityModel.Tokens.Jwt and names Polly.ExecutionRejectedException in its catch filter.
      • Concept introduced, single-flight refresh under a thundering herd. [Rubric §12, Performance & Scalability] assesses behavior under concurrent load. When an access token expires, many queued navigations can arrive at once; rotating for each would burn the refresh token repeatedly and log the - user out. The type comment (:39-50) states the design: the lock is a striped + user out. The type comment (:61-73) states the design: the lock is a striped KeyedSemaphoreStripe keyed by refresh token rather than one process-wide semaphore, because the lock is held across an outbound HTTP call and a single semaphore would serialize every unrelated user's cold navigation behind whichever refresh happened to be in flight. Two unrelated tokens can still land on the same one of the stripe's 256 lanes (MMCA.Common/Source/Core/MMCA.Common.Shared/Concurrency/KeyedSemaphoreStripe.cs:25), which the comment calls out as harmless precisely because the rotation-grace cache is re-checked per token - after acquiring. Alongside the lock, a 10-second RotationGrace (:60) caches the rotated pair - keyed by the OLD refresh token (:144), so a slightly-late sibling carrying the same expired pair + after acquiring. Alongside the lock, a 10-second RotationGrace (:85) caches the rotated pair + keyed by the OLD refresh token (:198), so a slightly-late sibling carrying the same expired pair gets the same result instead of rotating again.
      • -
      • Walkthrough: GetOrRefreshAsync (:64-93) reads the access cookie (:68) and, if - TryReadValidExpiry passes, returns it untouched (:69-72). Otherwise it reads the refresh cookie - and returns null when there is none (:74-78). It calls RefreshAsync (:80), treats a missing or - blank access token as failure (:81-84), writes the rotated pair with SessionCookieJar.Append - (:87), stashes the fresh access token on context.Items[CookieTokenReader.FreshAccessTokenItemKey] - (:91, with the reason spelled out at :89-90), and returns the new - SessionTokenResult (:92). RefreshAsync (:95-111) is textbook - double-checked locking: a cache hit returns immediately (:97-100), otherwise it acquires the stripe - for this token (:102) and re-checks the cache before doing any work (:105-108). CallRefreshAsync - (:113-152) creates the named client (:115), POSTs a - RefreshTokenRequest to the relative auth/refresh URI with - CancellationToken.None (:127-130) so that once the lock is held the rotation completes and writes - its cookies even if the triggering request was aborted (the reason is at :125-126), bails on a - non-success status (:132-135) or an empty access token (:138-141), and caches the - AuthenticationResponse under the old refresh token for RotationGrace - (:144). TryReadValidExpiry (:154-183) rejects a blank token, refuses anything - JwtSecurityTokenHandler cannot read (:162-166), treats the token as expired when - jwt.ValidTo <= DateTime.UtcNow + ClockSkew (:171, with ClockSkew a 30-second margin at :59), - and swallows only ArgumentException/FormatException (:179-182). CacheKey (:189) builds the +
      • Walkthrough: GetOrRefreshAsync (:89-90) is a projection: it awaits ValidateOrRefreshAsync + and returns its .Session, so every failure becomes null. ValidateOrRefreshAsync (:92-103) + reads the access cookie (:96) and, if TryReadValidExpiry passes, returns a Refreshed outcome + around it untouched (:97-100); otherwise it delegates to RefreshFromCookiesAsync (:102). The + public RefreshAsync (:105-111) skips the validity check and goes straight to + RefreshFromCookiesAsync (:110), which is the forced rotation the interface promises. + RefreshFromCookiesAsync (:123-143) returns Rejected when there is no refresh cookie + (:126-129), calls the private RefreshAsync (:131) and passes its failure through + (:132-135), writes the rotated pair with SessionCookieJar.Append and the configured SameSite + (:137), stashes the fresh access token on + context.Items[CookieTokenReader.FreshAccessTokenItemKey] (:141, with the reason at :139-140), + and returns a Refreshed outcome around the new SessionTokenResult + (:142). The private RefreshAsync (:145-162) is textbook double-checked locking: a cache hit + returns immediately (:148-151), otherwise it acquires the stripe for this token (:153) and + re-checks the cache before doing any work (:156-159). CallRefreshAsync (:164-210) creates the + named client (:166), POSTs a RefreshTokenRequest to the relative + auth/refresh URI with CancellationToken.None (:179-182) so that once the lock is held the + rotation completes and writes its cookies even if the triggering request was aborted (the reason is + at :177-178). A non-success status goes through ClassifyFailure (:184-189): Rejected, or + Unavailable carrying the parsed Retry-After. An empty access token is Unavailable (:192-195). + Success caches the AuthenticationResponse under the old refresh token + for RotationGrace (:198). ClassifyFailure (:118-121) is internal static: 400, 401 and 403 + are the identity endpoint refusing the token, so Rejected; anything else (5xx, 429, 408, a 404 + from a misrouted gateway) says nothing about the token, so Unavailable (doc :113-117). + ReadRetryAfter (:212-231) accepts either form of the header, a delta (:219-222) or a date + measured against the injected TimeProvider (:224-228), and clamps a negative result to zero. + TryReadValidExpiry (:233-262) rejects a blank token, refuses anything JwtSecurityTokenHandler + cannot read (:242-245), treats the token as expired when + jwt.ValidTo <= timeProvider.GetUtcNow().UtcDateTime + ClockSkew (:250, with ClockSkew a + 30-second margin at :84), and swallows only ArgumentException/FormatException (:258-261); it + is an instance method because it reads the injected clock. CacheKey (:268) builds the mmca:session-refresh:{refreshToken} string that is both the cache key and the striping key; the - comment at :185-188 explains it is internal rather than private so a concurrency test can pick + comment at :264-267 explains it is internal rather than private so a concurrency test can pick two refresh tokens that do not collide on a stripe, which is a nice example of a testability - affordance that costs nothing at runtime. [Rubric §14, Testability].
      • -
      • Concept, an SSR-safe failure mode. [Rubric §29, Resilience & Business Continuity] and [Rubric - §13, Observability & Operability] apply to the outbound call. CallRefreshAsync wraps the POST in a - try whose filter narrows to HttpRequestException, OperationCanceledException, JsonException - and NotSupportedException (:147), logs one warning through the source-generated - LogRefreshCallFailed (:149, declared with [LoggerMessage] at :191-192, which is why the class - is partial at :51), and returns null. The comment at :117-122 gives the reasoning: this code - runs during SSR, so an escaping exception would turn a signed-in user's navigation into a 500 - instead of an anonymous render. The failure is deliberately not cached (only a successful rotation - reaches cache.Set at :144), so the next navigation retries, and a missing BaseAddress raises - InvalidOperationException and is left to propagate because that is a host misconfiguration rather - than a runtime condition.
      • + affordance that costs nothing at runtime. [Rubric §14, Testability]: the injected TimeProvider is + the same kind of affordance, letting tests move the clock instead of minting tokens around the wall + clock. +
      • Concept, an SSR-safe failure mode that does not sign users out. [Rubric §29, Resilience, + Reliability & Business Continuity] and [Rubric §13, Observability & Operability] apply to the + outbound call. CallRefreshAsync wraps the POST in a try whose filter narrows to + HttpRequestException, OperationCanceledException, JsonException, NotSupportedException and + Polly.ExecutionRejectedException (:204-205); the comment at :201-203 explains the last one: the + resilience pipeline's own refusals (a timed-out attempt, an open circuit, a rate-limited call) throw + it, for example TimeoutRejectedException with the gateway down, which is neither transport + exception. The catch logs one warning through the source-generated LogRefreshCallFailed (:207, + declared with [LoggerMessage] at :270-271, which is why the class is partial at :74) and + returns Unavailable (:208). The comment at :168-174 gives the reasoning: this code runs during + SSR, so an escaping exception would turn a signed-in user's navigation into a 500 instead of an + anonymous render; it is Unavailable, not Rejected, because nothing said the refresh token is + dead, so a caller that clears cookies must keep them. The failure is deliberately not cached (only a + successful rotation reaches cache.Set at :198), so the next navigation retries, and a missing + BaseAddress raises InvalidOperationException and is left to propagate because that is a host + misconfiguration rather than a runtime condition.
      • Why it's built this way: keying the grace cache by the OLD token is what lets a slightly-late sibling find the already-rotated pair, and striping the lock keeps one user's slow refresh from blocking everyone else's cold navigation. The server-to-server call is what keeps the refresh token off browser JS (ADR-022). - [Rubric §11, Security].
      • + [Rubric §11, Security]. Separating Rejected from Unavailable is what lets the same-origin proxy + clear cookies only for a session that is really over and answer a transient identity-endpoint fault + with 503 plus Retry-After + (ADR-131).
      • Where it's used: resolved as ICookieSessionRefresher by - CookieSessionRefreshMiddleware and by the - /auth/session/token endpoint. Its named HttpClient, RefreshClientName = "SessionCookieRefreshClient" (:57), is configured with the API base address in - AddServerAuthSessionCookie + CookieSessionRefreshMiddleware, by the /auth/session/token + endpoint, and by the same-origin proxy + (SameOriginApiProxyEndpoint, + SessionHandoffEndpoints). Its named + HttpClient, RefreshClientName = "SessionCookieRefreshClient" (:82), is configured with the API + base address in AddServerAuthSessionCookie (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:182-183), which also - registers the refresher as a singleton (:171-172) with an inline note that a shared instance across - requests is what makes single-flight work at all. The validate, rotate, grace-cache and failure paths - are covered by - MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs.
      • + registers the refresher as a singleton (:186) with an inline note (:185) that a shared instance + across requests is what makes single-flight work at all, and registers TimeProvider.System with + TryAdd (:196) because a Blazor Web host calling only this method has no other clock registration + (comment :193-195). The validate, rotate, grace-cache and failure paths are covered by + MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs, + including refusal as Rejected (:327), undecidable statuses as Unavailable with no cookies + written (:354), resilience-pipeline rejections (:396), both Retry-After forms (:420, :436) + and expiry judged against the injected clock (:61).

      SessionCookieAuthenticationHandler

      @@ -2354,7 +2693,7 @@

      SessionCookieAuthenticationHandlerTimeProvider rather than DateTime.UtcNow keeps the expiry check on the same injectable clock as the rest of the auth stack and its tests. [Rubric §14, Testability].
    577. Where it's used: registered as the SessionCookie scheme on both Blazor Server hosts, - MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:102-103 and + MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:103-104 and MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:111-112. Covered directly by MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/SessionCookieAuthenticationHandlerTests.cs, including the fresh-token-from-Items path (:95, which stashes the token under @@ -2382,22 +2721,10 @@

      SessionCookieAuthenticationExtens explicit null arguments are named, so the call site says what it is skipping. The doc comment (:94-97) directs callers to use it after AddAuthentication(SessionCookieAuthenticationHandler.SchemeName).

    578. -
    579. Where it's used: MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:103 and +
    580. Where it's used: MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:104 and MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:112, chained onto the host's AddAuthentication(SessionCookieAuthenticationHandler.SchemeName) call on the preceding line.
    581. -

      IssuedSession

      -
      -

      MMCA.Common.Application · MMCA.Common.Application.Auth · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:924 · Level 0 · record (private sealed, nested)

      -
      -
        -
      • What it is: the two-field result of opening or rotating a refresh session: the plaintext refresh token the client is handed, and the id of the session row it belongs to. It is a private sealed record nested inside AuthenticationServiceBase<TUser> (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:924), not part of the framework's public surface.
      • -
      • Depends on: nothing beyond the BCL (string, Guid). It is produced and consumed entirely inside its declaring class.
      • -
      • Concept introduced: the plaintext token exists in exactly one place, and it is a return value. [Rubric §11, Security] assesses how a bearer credential is stored. RefreshSession rows keep only a digest (RefreshSession.HashToken, used at :349 and :593 to look up by hash), so once a session is persisted the raw token cannot be recovered from the store at all. The type comment says exactly this (:753-757): the plaintext "exists nowhere else". Modelling the hand-off as a small record rather than an out-parameter or a tuple is what keeps that fact readable: every method that can produce a token returns Result<IssuedSession>, so the compiler shows you the complete list of places raw token material is in flight. [Rubric §15, Best Practices & Code Quality] also applies: a positional record gives value equality and immutability for free, and Guid SessionId names what would otherwise be an anonymous second tuple element.
      • -
      • Walkthrough: one positional declaration, IssuedSession(string RefreshToken, Guid SessionId) (:758). RefreshToken is what goes back to the caller in the AuthenticationResponse; SessionId is what the access token's sid claim carries, which is why the session must be created before the token is minted (:481-483).
      • -
      • Why it's built this way: the pairing is load-bearing rather than incidental. A caller that received only the token could not stamp sid, and a caller that received only the id could not answer the client. Returning both together removes the ordering mistake where a token is minted for a session that does not exist yet.
      • -
      • Where it's used: returned by OpenSessionAsync (:620, constructed at :645) and RotateAsync (:659, constructed at :698); unwrapped by IssueTokensAsync (:492-493) and by RefreshTokenAsync (:327-328), each of which reads SessionId to mint the access token and RefreshToken to fill the response.
      • -

      PasswordResetSettings

      MMCA.Common.Application · MMCA.Common.Application.Auth · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/PasswordResetSettings.cs:10 · Level 0 · class (sealed)

      @@ -2405,10 +2732,10 @@

      PasswordResetSettings

      • What it is: the bound options object for the forgot-password workflow: where the reset page lives, how long a token stays redeemable, how many wrong guesses a token tolerates, and how often one address may ask for a reset (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/PasswordResetSettings.cs:6-9).
      • Depends on: System.ComponentModel.DataAnnotations for the range attributes and System.Diagnostics.CodeAnalysis for one scoped suppression (BCL, :1-2). Nothing first-party. Read by the implementation behind IPasswordResetTokenService and by the shared ForgotPasswordHandlerBase<TUser, TCommand>.
      • -
      • Concept: validated options whose defaults keep an unconfigured host bootable. [Rubric §17, DevOps & Deployment] assesses whether policy knobs are configuration rather than constants buried in a handler, and [Rubric §11, Security] assesses whether the security-relevant knobs (token lifetime, attempt cap, request throttle) are bounded rather than free-form. Every numeric member carries a [Range] attribute, and the host binds the section with ValidateDataAnnotations().ValidateOnStart() (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:154-157), so a typo such as TokenLifetimeMinutes: 0 fails the host at startup instead of silently issuing tokens that are already expired.
      • -
      • Walkthrough: const string SectionName = "PasswordReset" (:13) names the configuration section the host binds. ResetUrl (:25) defaults to string.Empty and is deliberately not [Required]: the doc comment (:15-20) records that a host which has not configured a UI base must still boot, and an empty value degrades to a token-only email the user pastes into the reset page by hand. That degradation is visible in the caller, which emits the bare token when the URL is blank and otherwise appends ?email=...&token=... (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ForgotPassword/ForgotPasswordHandlerBase.cs:146-148). The property carries a scoped CA1056 suppression (:21-24) explaining why it is a string and not a System.Uri: it is bound from PasswordReset__ResetUrl, concatenated with a query string, and the empty default is not a valid Uri. The four numeric knobs follow: TokenLifetimeMinutes (:29, [Range(1, 1440)], default 30), MaxValidationAttempts (:36, [Range(1, 100)], default 5), MaxRequestsPerEmail (:40, [Range(1, 100)], default 3), and RequestWindowMinutes (:44, [Range(1, 1440)], default 60). All five members are init-only, so the bound instance is immutable afterwards.
      • +
      • Concept: validated options whose defaults keep an unconfigured host bootable. [Rubric §17, DevOps & Deployment] assesses whether policy knobs are configuration rather than constants buried in a handler, and [Rubric §11, Security] assesses whether the security-relevant knobs (token lifetime, attempt cap, request throttle) are bounded rather than free-form. Every numeric member carries a [Range] attribute, and the host binds the section with ValidateDataAnnotations().ValidateOnStart() (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:160-163), so a typo such as TokenLifetimeMinutes: 0 fails the host at startup instead of silently issuing tokens that are already expired.
      • +
      • Walkthrough: const string SectionName = "PasswordReset" (:13) names the configuration section the host binds. ResetUrl (:25) defaults to string.Empty and is deliberately not [Required]: the doc comment (:15-20) records that a host which has not configured a UI base must still boot, and an empty value degrades to a token-only email the user pastes into the reset page by hand. That degradation is visible in the caller, which emits the bare token when the URL is blank and otherwise appends the escaped address and token as a URL fragment, #email=...&token=... (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ForgotPassword/ForgotPasswordHandlerBase.cs:153-155). A fragment is never sent to the server in the HTTP request, which is the reason the doc comment gives (:16-17): the live token never reaches a server or proxy access log the way a query string would. The property carries a scoped CA1056 suppression (:21-24) explaining why it is a string and not a System.Uri: it is bound from PasswordReset__ResetUrl, concatenated with a URL fragment, and the empty default is not a valid Uri. The four numeric knobs follow: TokenLifetimeMinutes (:29, [Range(1, 1440)], default 30), MaxValidationAttempts (:36, [Range(1, 100)], default 5), MaxRequestsPerEmail (:40, [Range(1, 100)], default 3), and RequestWindowMinutes (:44, [Range(1, 1440)], default 60). All five members are init-only, so the bound instance is immutable afterwards.
      • Why it's built this way: the defaults are a working policy on their own, so adopting the feature costs a registration call and no configuration at all, while the [Range] bounds plus ValidateOnStart make the one genuinely dangerous class of misconfiguration (a zero or negative lifetime, an unbounded attempt cap) unreachable. The decision to keep the whole reset credential in configuration and cache rather than in schema is ADR-091.
      • -
      • Where it's used: bound in the framework's Infrastructure registration (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:154-157, immediately before the token service that reads it is registered at :141); consumed by PasswordResetTokenService as a snapshot field (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:32) for the request window, the throttle ceiling, the token lifetime, and the attempt cap; and exposed to the shared forgot-password handler as a protected Settings property (ForgotPasswordHandlerBase.cs:49) that states the expiry in the email body (:125) and renders the link (:145-147).
      • +
      • Where it's used: bound in the framework's Infrastructure registration (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:160-163, immediately before the token service that reads it is registered at :164); consumed by PasswordResetTokenService as a snapshot field (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:45) for the request window, the throttle ceiling, the token lifetime, and the attempt cap; and exposed to the shared forgot-password handler as a protected Settings property (ForgotPasswordHandlerBase.cs:49) that states the expiry in the email body (:126) and renders the fragment link (:153-155).

      RefreshSessionSettings

      @@ -2420,15 +2747,15 @@

      RefreshSessionSettings

    582. Concept introduced: a flag that places a table rather than switching a feature. [Rubric §8, Data Architecture] assesses whether each table has exactly one owning database, and [Rubric §7, Microservices Readiness] assesses whether that ownership survives splitting a modular host into services. The doc comment on Enabled states the distinction precisely (:20-23): the flag "gates the model, not the workflow". The workflow always issues, rotates and revokes sessions; Enabled decides which host maps the table, runs its migrations, and sweeps it. In a modular host the service that owns identity sets it to true and every other service leaves it false, which is what keeps one table in one database instead of one per service. The Scheduler:Enabled precedent is named in the same comment as the pattern being followed.
    583. Walkthrough: six members, all init-only.
      • const string SectionName = "RefreshSessions" (:12) names the configuration section.
      • -
      • Enabled (:25) defaults to false. Two places read it: the model gate in ApplicationDbContext, which enables the table only when the flag is set and the context instance's physical source name equals DataSourceName (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:331-334), and the hosted-service registration, which starts the retention sweep only when the flag is set (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:171-175, whose comment explains that registering it unconditionally would start a sweep in every service of a modular host, all but one of which has no table to sweep).
      • +
      • Enabled (:25) defaults to false. Two places read it: the model gate in ApplicationDbContext, which enables the table only when the flag is set and the context instance's physical source name equals DataSourceName (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:336-339), and the hosted-service registration, which starts the retention sweep only when the flag is set (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:181-185, whose comment explains that registering it unconditionally would start a sweep in every service of a modular host, all but one of which has no table to sweep).
      • MaxActiveSessionsPerUser (:35, [Range(1, 1000)], default 10) caps live sessions per user. The comment (:28-33) records the deliberate behavior at the ceiling: signing in on device number cap + 1 revokes the oldest live session rather than refusing the login, so the table is bounded without a legitimate sign-in ever failing.
      • DataSourceName (:52, [MinLength(1)], default "Default") names the logical data source whose database holds the table. The comment (:37-49) is worth reading in full: the value answers two questions that must agree, which context maps the table and which context the shipped IRefreshSessionStore reads and writes through, and naming a source that does not exist fails loudly on the first session query rather than reading the wrong database. It is ignored for routing when the consumer ships its own entity configuration for the session entity.
      • RetentionDays (:72, [Range(0, 3650)], default 30) is measured from the instant a session died (its revocation, or its expiry when never revoked), so a live session is never a sweep candidate. The comment (:59-66) states the constraint that makes the number security-relevant: retention bounds reuse detection, because BR-206 catches a replayed refresh token by landing on its revoked row, and a swept row turns that replay into an unknown token that fails alone instead of revoking the family. Thirty days sits well past the seven-day refresh-token lifetime for exactly that reason. 0 keeps every row forever (:67-69).
      • CleanupIntervalHours (:80, [Range(1, 168)], default 6) is how often the sweep runs, ignored when RetentionDays is 0, and matches the outbox sweep cadence because the deadline is measured in days (:74-77).
    584. -
    585. Why it's built this way: the same AddOptions(...).Bind(...).ValidateDataAnnotations().ValidateOnStart() treatment as every other settings class in the framework (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:162-165) means an out-of-range cap or a negative retention window fails the host at startup, not at the first login. Defaulting Enabled to false is the safe direction for a multi-service host: a service that never opts in never grows a table it does not own.
    586. -
    587. Where it's used: bound at MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:162-165; injected as IOptions<RefreshSessionSettings> into AuthenticationServiceBase<TUser> (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:61, read for the cap at :115), into EFRefreshSessionStore (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/EFRefreshSessionStore.cs:34), and into RefreshSessionCleanupService (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/RefreshSessionCleanupService.cs:51, snapshotted at :54). The design-time context helper supplies an instance so dotnet ef can build a model that includes the table (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextHelper.cs:173-174). Each app's Identity service takes it as a required constructor dependency and passes it through, for example MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:57.
    588. +
    589. Why it's built this way: the same AddOptions(...).Bind(...).ValidateDataAnnotations().ValidateOnStart() treatment as every other settings class in the framework (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:168-171) means an out-of-range cap or a negative retention window fails the host at startup, not at the first login. Defaulting Enabled to false is the safe direction for a multi-service host: a service that never opts in never grows a table it does not own.
    590. +
    591. Where it's used: bound at MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:168-171; injected as IOptions<RefreshSessionSettings> into AuthenticationServiceBase<TUser> (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:61, read for the cap at :115), into EFRefreshSessionStore (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/EFRefreshSessionStore.cs:34), and into RefreshSessionCleanupService (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/RefreshSessionCleanupService.cs:51, snapshotted at :54). The design-time context helper supplies an instance so dotnet ef can build a model that includes the table (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Design/DesignTimeDbContextHelper.cs:173-174). Each app's Identity service takes it as a required constructor dependency and passes it through, for example MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:57.
    592. UserAdministrationQuery

      @@ -2442,25 +2769,6 @@

      UserAdministrationQuery

    593. Why it's built this way: a record struct rather than a class avoids a heap allocation for a value that is built once, passed to one call, and discarded, and the two optional filters default to null so a caller that wants an unfiltered page supplies only the paging pair.
    594. Where it's used: built by UsersAdminControllerBase (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Administration/UsersAdminControllerBase.cs) from the incoming query string and passed to IUserAdministrationService<TUserDto>.ListAsync; consumed by each app's UserAdministrationService, for example MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/Administration/UserAdministrationService.cs.
    595. -

      SessionStampingTokenService

      -
      -

      MMCA.Common.Application · MMCA.Common.Application.Auth · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:936 · Level 1 · class (private sealed, nested)

      -
      -
        -
      • What it is: a pass-through ITokenService that appends the current refresh session's sid claim and, when a second factor verified this request, an mfa claim to every access token minted while it is armed, and behaves as the plain inner service the rest of the time. It is a private sealed class nested inside AuthenticationServiceBase<TUser> (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:936).
      • -
      • Depends on: ITokenService (the contract it implements and the inner instance it wraps, :106), AuthClaimTypes for the sid and mfa claim names (:142, :147), and the BCL (System.Security.Claims, System.Globalization.CultureInfo).
      • -
      • Concept introduced: a decorator used to make a new claim additive. [Rubric §2, Design Patterns] assesses idiomatic pattern use, and this is the Decorator pattern applied to a very specific compatibility problem. Access tokens now need to name the session they belong to (and, when relevant, the second factor that satisfied a step-up), but the claim set is produced by the app's own CreateAccessToken hook. The obvious fix, adding parameters to that hook, is a compile break in every consumer for claims the app has no decision to make about. Wrapping the token service instead means the base arms the wrapper around the hook call and the claims appear in tokens minted by subclasses that were never edited. [Rubric §15, Best Practices & Code Quality] is the payoff: an additive protocol change with a zero-line consumer diff, twice over.
      • -
      • Walkthrough: a primary constructor takes the inner service (:106).
          -
        • Guid? CurrentSessionId { get; set; } (:109) is the session arming switch: a session id stamps, null mints unchanged.
        • -
        • string? CurrentMultiFactorMethod { get; set; } (:115) is the second-factor arming switch, set only when a second factor really verified for this request. The remarks on the caller explain why plain mutable properties are safe here: the authentication service is resolved per request (scoped, like the unit of work it saves through) and one request issues one token pair at a time.
        • -
        • AccessTokenLifetime (:118) and RefreshTokenLifetime (:121) forward straight to inner, so the lifetime the base reports is still the JWT settings' value.
        • -
        • GenerateAccessToken(...) (:124-151) is the only member with behavior. When neither switch is armed it delegates verbatim (:131-134). Otherwise it copies the app's additionalClaims into a new List<Claim> (:136, so the caller's sequence is never mutated), then independently appends AuthClaimTypes.SessionId formatted as sessionId.ToString("D", CultureInfo.InvariantCulture) when CurrentSessionId is set (:138-143, the "D" format being the canonical hyphenated Guid form ClaimsPrincipalExtensions.FindSessionId parses back, see ClaimsPrincipalExtensions) and AuthClaimTypes.MultiFactor set to the raw method string when CurrentMultiFactorMethod is set (:145-148), before delegating with the extended list (:150). The two claims are independent: a token can carry either, both, or neither.
        • -
        • GenerateRefreshToken() (:154) and GetPrincipalFromExpiredToken(string token) (:157-158) are plain forwards.
        • -
        -
      • -
      • Why it's built this way: putting the stamping behind an ITokenService rather than inside the base's own method keeps the app hook's signature and semantics untouched while still guaranteeing both claims on the tokens the framework's own flows mint. The escape hatch still applies: an app that mints from its own injected ITokenService reference produces a valid token with neither claim, and can restore them by overriding CreateAccessTokenForSession.
      • -
      • Where it's used: constructed once per authentication service instance (:93), surfaced to subclasses as the protected TokenService property (:120, whose remarks state that minting through the property is what puts sid on the token), and armed and disarmed around the hook call in CreateAccessTokenForSession (:618-631, with the finally guaranteeing both switches disarm even when the hook throws). CurrentMultiFactorMethod is set from the base's _multiFactorMethod field, armed in LoginAsync around IssueTokensAsync and in RefreshTokenAsync around the successor mint (see AuthenticationServiceBase<TUser>).
      • -

      UnconfiguredPermissionRegistry

      MMCA.Common.Application · MMCA.Common.Application.Auth · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/UnconfiguredPermissionRegistry.cs:20 · Level 1 · class (internal sealed partial)

      @@ -2502,7 +2810,7 @@

      ILoginProtectionService

    596. Why it's built this way: keeping the protection policy behind an interface lets the shared authentication workflow compose it in while the concrete cache mechanics stay in the implementation; the null-IP skip keeps the limiter from becoming an availability hazard (ADR-029).

    597. -
    598. Where it's used: injected into AuthenticationServiceBase<TUser> (constructor parameter at MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:78), which calls all five across its login and registration flows: CheckLockoutAsync (:131), IncrementFailedAttemptsAsync on both the unknown-email and wrong-password branches (:146, :161), ResetFailedAttemptsAsync on success (:178), CheckRegistrationRateLimitAsync (:197) and IncrementRegistrationCountAsync (:256). The concrete, cache-backed LoginProtectionService (tuned by LoginProtectionSettings, bound at MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:148-151) implements it, and the framework registers that pairing at :135.

      +
    599. Where it's used: injected into AuthenticationServiceBase<TUser> (constructor parameter at MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:66), which calls all five across its login and registration flows: CheckLockoutAsync (:131), IncrementFailedAttemptsAsync on both the unknown-email and wrong-password branches (:146, :161), ResetFailedAttemptsAsync on success (:178), CheckRegistrationRateLimitAsync (:197) and IncrementRegistrationCountAsync (:256). The concrete, cache-backed LoginProtectionService (tuned by LoginProtectionSettings, bound at MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:154-157) implements it, and the framework registers that pairing at :135.

    600. IPasswordResetTokenService

      @@ -2523,9 +2831,9 @@

      IPasswordResetTokenService

    601. Task<Result<UserIdentifierType>> ValidateAndConsumeAsync(string email, string token, CancellationToken cancellationToken = default) (:36) validates the presented token against the outstanding record and consumes it on success, so a token never redeems twice (:25-28), returning the account the token belongs to.
    602. -
    603. Why it's built this way: taking email on both methods, rather than treating the token as self-describing, is what lets the implementation key its records by address and enforce the per-address throttle and the one-active-token rule at the same key. Returning Result<UserIdentifierType> rather than a boolean means the redeem handler gets the account identity from the token store itself. See PasswordResetTokenService for the mechanics the port hides: a 32-byte random token (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:30), only its digest stored, an attempt counter, and an address normalized through Email before it becomes a cache key (:34-45) so User@x.com and user@x.com cannot hold independent tokens for one account.

      +
    604. Why it's built this way: taking email on both methods, rather than treating the token as self-describing, is what lets the implementation key its records by address and enforce the per-address throttle and the one-active-token rule at the same key. Returning Result<UserIdentifierType> rather than a boolean means the redeem handler gets the account identity from the token store itself. See PasswordResetTokenService for the mechanics the port hides: a 32-byte random token (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:39), only its digest stored, an attempt counter, and an address normalized through Email before it becomes a cache key (:34-45) so User@x.com and user@x.com cannot hold independent tokens for one account.

    605. -
    606. Where it's used: injected into the shared ForgotPasswordHandlerBase<TUser, TCommand> (constructor parameter at MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ForgotPassword/ForgotPasswordHandlerBase.cs:38, called at :72, where a throttled issue is logged and still answered as success) and into ResetPasswordHandlerBase<TUser, TCommand> (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:44, redeemed at :61-62). Both apps' sealed subclasses take the same dependency, for example ADC's ForgotPasswordHandler and ResetPasswordHandler. The framework registers the concrete as scoped at MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:158.

      +
    607. Where it's used: injected into the shared ForgotPasswordHandlerBase<TUser, TCommand> (constructor parameter at MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ForgotPassword/ForgotPasswordHandlerBase.cs:38, called at :72, where a throttled issue is logged and still answered as success) and into ResetPasswordHandlerBase<TUser, TCommand> (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:44, redeemed at :61-62). Both apps' sealed subclasses take the same dependency, for example ADC's ForgotPasswordHandler and ResetPasswordHandler. The framework registers the concrete as scoped at MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:164.

    608. IRoleAdministrationService

      @@ -2564,6 +2872,32 @@

      IUserAdministrationService<TUserD
    609. Why it's built this way: generic over TUserDto so each app's admin list can carry app-specific columns without the framework knowing about them, while the lock/unlock contract's remarks pin the one security-relevant behavior (session revocation on lock) as a documented obligation rather than leaving it to whichever app implements the interface first.
    610. Where it's used: consumed by UsersAdminControllerBase (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Administration/UsersAdminControllerBase.cs); implemented by each app's UserAdministrationService, for example MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/Administration/UserAdministrationService.cs, and registered through MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs.
    611. +

      SoftDeletedUserCache

      +
      +

      MMCA.Common.Application · MMCA.Common.Application.Auth · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/SoftDeletedUserCache.cs:17 · Level 4 · class (static)

      +
      +
        +
      • What it is: the shared cache contract for the soft-deleted user marker (BR-133): the key shape, the marker lifetime, and a one-call helper that writes it. The API middleware reads the marker on every authenticated request; the module that soft-deletes a user writes it (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/SoftDeletedUserCache.cs:6-9).

        +
      • +
      • Depends on: ICacheService from MMCA.Common.Application.Interfaces (:2), the UserIdentifierType alias, and System.Globalization.CultureInfo (BCL, :1).

        +
      • +
      • Concept introduced: revoking a stateless credential without a per-request lookup. [Rubric §11, Security] assesses whether a revoked principal actually loses access, and [Rubric §12, Performance & Scalability] assesses whether such a concern is factored so both ends share one definition. A JWT is a bearer credential: signature validation never asks "is this account still active?", so soft-deleting a user leaves an already-issued access token passing validation until it expires (ADR-047). The textbook fixes (a deny-list, or an account-status query on every request) reintroduce exactly the per-request state that stateless JWT was chosen to avoid. This type is the middle path: a short-lived cache marker written at deletion time and read cheaply on the hot path.

        +

        The remarks (:11-16) explain why the constants live in the Application layer rather than next to the middleware that reads them: a downstream application deleting an account has to write the exact same key the middleware reads, and a private constant in the presentation layer is unreachable from an application-layer command handler. Same reasoning as IdempotencyHeaders, applied one layer up.

        +
      • +
      • Walkthrough: three static members, no state.

        +
          +
        • MarkerDuration => TimeSpan.FromSeconds(30) (:29). The remarks (:22-28) justify the number rather than leaving it magic: the marker only has to cover the window between the delete committing and the next token validation, because once it expires the validator query is the source of truth again and gives the same answer. Short-lived access tokens (15 minutes, the BR-205 default on ITokenService) bound the rest of the exposure, so a longer marker would buy nothing and would keep stale entries alive for users who were never deleted.
        • +
        • KeyFor(UserIdentifierType userId) (:42-43) builds user:deleted:{userId} through string.Create(CultureInfo.InvariantCulture, ...). The remarks (:36-41) name the bug this prevents: an identifier renders differently under some cultures (digit shapes, group separators), so a culture-sensitive key would be written under one request's culture and missed under another, silently letting a deleted user keep making requests. This is a case where the analyzer rule about culture-invariant formatting is guarding a security property, not just a formatting nicety.
        • +
        • MarkDeletedAsync(ICacheService cache, UserIdentifierType userId, CancellationToken cancellationToken = default) (:53-61) null-guards the cache (:58) and writes true under KeyFor(userId) for MarkerDuration (:60). It returns the task without awaiting, so there is no extra async state machine for a one-call passthrough.
        • +
        +
      • +
      • Why it's built this way: publishing the key shape and the TTL as framework API is what keeps the writer and the reader honest, and it is a precondition for the module boundary in ADR-047: Identity owns the delete, every service hosts the middleware, and the only thing they share is a cache entry rather than a database. [Rubric §7, Microservices Readiness] applies directly: an extracted service can enforce the revocation without a reference to the Identity database.

        +
      • +
      • Where it's used: read by SoftDeletedUserMiddleware, which builds the key (MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/SoftDeletedUserMiddleware.cs:85), short-circuits with 401 when the marker is true (:102-105), and on a miss falls back to the validator query (:113-115) and caches that answer, deleted or not, for the same MarkerDuration (:132). Written by the shared delete workflow itself, ahead of either app's post-commit tail: DeleteUserHandlerBase.HandleAsync calls SoftDeletedUserCache.MarkDeletedAsync right after the erasure commits (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:146-148) and swallows a cache fault (:146-149) so a failed marker cannot turn a successful erasure into an error the caller would retry.

        +
      • +
      • Caveats / not-in-source: the marker is best effort on both ends by design. The middleware fails open on a cache outage, falling through to the validator query (:95-100) and proceeding if that is also unavailable (:118-125), and the writer logs and continues on a cache fault. The exposure that leaves is bounded by the access-token lifetime, which is the trade-off ADR-047 accepts explicitly. ADC's handler is the only writer in the source tree today; MMCA.Store soft-deletes users without writing the marker, so there the middleware's own validator-query fallback is what enforces BR-133.

        +
      • +

      IRefreshSessionStore

      MMCA.Common.Application · MMCA.Common.Application.Auth · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/IRefreshSessionStore.cs:21 · Level 4 · interface

      @@ -2588,33 +2922,7 @@

      IRefreshSessionStore

    612. Why it's built this way: hashed-at-rest, per-device session rows are what turn refresh-token rotation into something a user can inspect and revoke per device, and what let reuse detection revoke a whole family (ADR-050, BR-205/206). Keeping the contract in Application means the workflow that uses it is independent of EF, so an extracted Identity service can bring its own store. Making rotation a claim rather than a mutation is the difference between a race that mints two live tokens and a race one side of which is answered as a replay.

    613. -
    614. Where it's used: registered as scoped against the EF implementation at MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:166, with the comment (:143-144) noting the lifetime is deliberate: scoped, like the unit of work it shares a DbContext with, so a login and its session insert commit together. Consumed throughout AuthenticationServiceBase<TUser> (injected at MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:81, exposed to subclasses at :88) for single-device sign-out (:348-350), session listing (:404), targeted revocation (:441), reuse resolution (:592-594), rotation (:682-684), family revocation (:708) and cap eviction (:725).

      -
    615. - -

      SoftDeletedUserCache

      -
      -

      MMCA.Common.Application · MMCA.Common.Application.Auth · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/SoftDeletedUserCache.cs:17 · Level 4 · class (static)

      -
      -
        -
      • What it is: the shared cache contract for the soft-deleted user marker (BR-133): the key shape, the marker lifetime, and a one-call helper that writes it. The API middleware reads the marker on every authenticated request; the module that soft-deletes a user writes it (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/SoftDeletedUserCache.cs:6-9).

        -
      • -
      • Depends on: ICacheService from MMCA.Common.Application.Interfaces (:2), the UserIdentifierType alias, and System.Globalization.CultureInfo (BCL, :1).

        -
      • -
      • Concept introduced: revoking a stateless credential without a per-request lookup. [Rubric §11, Security] assesses whether a revoked principal actually loses access, and [Rubric §12, Performance & Scalability] assesses whether such a concern is factored so both ends share one definition. A JWT is a bearer credential: signature validation never asks "is this account still active?", so soft-deleting a user leaves an already-issued access token passing validation until it expires (ADR-047). The textbook fixes (a deny-list, or an account-status query on every request) reintroduce exactly the per-request state that stateless JWT was chosen to avoid. This type is the middle path: a short-lived cache marker written at deletion time and read cheaply on the hot path.

        -

        The remarks (:11-16) explain why the constants live in the Application layer rather than next to the middleware that reads them: a downstream application deleting an account has to write the exact same key the middleware reads, and a private constant in the presentation layer is unreachable from an application-layer command handler. Same reasoning as IdempotencyHeaders, applied one layer up.

        -
      • -
      • Walkthrough: three static members, no state.

        -
          -
        • MarkerDuration => TimeSpan.FromSeconds(30) (:29). The remarks (:22-28) justify the number rather than leaving it magic: the marker only has to cover the window between the delete committing and the next token validation, because once it expires the validator query is the source of truth again and gives the same answer. Short-lived access tokens (15 minutes, the BR-205 default on ITokenService) bound the rest of the exposure, so a longer marker would buy nothing and would keep stale entries alive for users who were never deleted.
        • -
        • KeyFor(UserIdentifierType userId) (:42-43) builds user:deleted:{userId} through string.Create(CultureInfo.InvariantCulture, ...). The remarks (:36-41) name the bug this prevents: an identifier renders differently under some cultures (digit shapes, group separators), so a culture-sensitive key would be written under one request's culture and missed under another, silently letting a deleted user keep making requests. This is a case where the analyzer rule about culture-invariant formatting is guarding a security property, not just a formatting nicety.
        • -
        • MarkDeletedAsync(ICacheService cache, UserIdentifierType userId, CancellationToken cancellationToken = default) (:53-61) null-guards the cache (:58) and writes true under KeyFor(userId) for MarkerDuration (:60). It returns the task without awaiting, so there is no extra async state machine for a one-call passthrough.
        • -
        -
      • -
      • Why it's built this way: publishing the key shape and the TTL as framework API is what keeps the writer and the reader honest, and it is a precondition for the module boundary in ADR-047: Identity owns the delete, every service hosts the middleware, and the only thing they share is a cache entry rather than a database. [Rubric §7, Microservices Readiness] applies directly: an extracted service can enforce the revocation without a reference to the Identity database.

        -
      • -
      • Where it's used: read by SoftDeletedUserMiddleware, which builds the key (MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/SoftDeletedUserMiddleware.cs:85), short-circuits with 401 when the marker is true (:102-105), and on a miss falls back to the validator query (:113-115) and caches that answer, deleted or not, for the same MarkerDuration (:132). Written by the shared delete workflow itself, ahead of either app's post-commit tail: DeleteUserHandlerBase.HandleAsync calls SoftDeletedUserCache.MarkDeletedAsync right after the erasure commits (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:142-144) and swallows a cache fault (:146-149) so a failed marker cannot turn a successful erasure into an error the caller would retry.

        -
      • -
      • Caveats / not-in-source: the marker is best effort on both ends by design. The middleware fails open on a cache outage, falling through to the validator query (:95-100) and proceeding if that is also unavailable (:118-125), and the writer logs and continues on a cache fault. The exposure that leaves is bounded by the access-token lifetime, which is the trade-off ADR-047 accepts explicitly. ADC's handler is the only writer in the source tree today; MMCA.Store soft-deletes users without writing the marker, so there the middleware's own validator-query fallback is what enforces BR-133.

        +
      • Where it's used: registered as scoped against the EF implementation at MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:172, with the comment (:143-144) noting the lifetime is deliberate: scoped, like the unit of work it shares a DbContext with, so a login and its session insert commit together. Consumed throughout AuthenticationServiceBase<TUser> (injected at MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:81, exposed to subclasses at :88) for single-device sign-out (:348-350), session listing (:404), targeted revocation (:441), reuse resolution (:592-594), rotation (:682-684), family revocation (:708) and cap eviction (:725).

      AuthenticationValidators

      @@ -2624,10 +2932,10 @@

      AuthenticationValidators

      • What it is: a tiny parameter object that bundles the three FluentValidation validators the authentication workflow needs (login, registration, refresh) into one injectable dependency.
      • Depends on: FluentValidation's IValidator<T> (NuGet, :1) over the request DTOs LoginRequest, RegisterRequest, and RefreshTokenRequest (all in MMCA.Common.Shared.Auth, :2).
      • -
      • Concept introduced: the parameter object as a constructor-arity guardrail. [Rubric §1, SOLID] assesses whether a class stays a single, cohesive responsibility rather than sprawling into a god class, and [Rubric §15, Best Practices & Code Quality] assesses whether cross-cutting dependencies are grouped so a class can grow without exploding its constructor. The doc comment (:6-11) states the exact motive: collapsing three closely-related dependencies into one keeps the app's AuthenticationService below the application-service constructor-arity ceiling (a god-class analyzer guardrail) without giving up per-request validation. Because the request DTOs already live in MMCA.Common.Shared.Auth, the bundle is app-agnostic, which is why it could be hoisted out of the apps into the framework. The pressure is real rather than theoretical: even with the bundle, ADC's subclass constructor takes ten parameters (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:49-58).
      • +
      • Concept introduced: the parameter object as a constructor-arity guardrail. [Rubric §1, SOLID] assesses whether a class stays a single, cohesive responsibility rather than sprawling into a god class, and [Rubric §15, Best Practices & Code Quality] assesses whether cross-cutting dependencies are grouped so a class can grow without exploding its constructor. The doc comment (:6-11) states the exact motive: collapsing three closely-related dependencies into one keeps the app's AuthenticationService below the application-service constructor-arity ceiling (a god-class analyzer guardrail) without giving up per-request validation. Because the request DTOs already live in MMCA.Common.Shared.Auth, the bundle is app-agnostic, which is why it could be hoisted out of the apps into the framework. The pressure is real rather than theoretical: even with the bundle, ADC's subclass constructor takes ten parameters (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:51-57).
      • Walkthrough: a primary constructor takes the three IValidator<T> instances (:16-19), and three get-only properties surface them by name: Login (:22), Register (:25), and Refresh (:28), each assigned from its matching constructor parameter. There is no logic here; the type exists purely to shrink the dependency footprint of its consumer.
      • Why it's built this way: a sealed grouping type with get-only properties is the cheapest way to fold three cohesive dependencies into one constructor slot, so the workflow base can validate each request shape without pushing its constructor over the arity limit; DI resolves the three underlying validators and composes them into this one object. Two of the three (LoginRequestValidator, RefreshTokenRequestValidator) come from the framework assembly, while IValidator<RegisterRequest> is satisfied by the app's own RegisterRequestValidator, so the bundle is the point where framework and app validation meet.
      • -
      • Where it's used: injected into AuthenticationServiceBase<TUser> (constructor parameter at MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:80), whose LoginAsync, RegisterAsync, and RefreshTokenAsync call validators.Login (:124), validators.Register (:190), and validators.Refresh (:270) respectively before doing any work. It is registered by each app's Identity module rather than by the framework, since one of its three dependencies is app-owned: MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:36 and MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Application/DependencyInjection.cs:42, both TryAddScoped<AuthenticationValidators>().
      • +
      • Where it's used: injected into AuthenticationServiceBase<TUser> (constructor parameter at MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:67), whose LoginAsync, RegisterAsync, and RefreshTokenAsync call validators.Login (:124), validators.Register (:190), and validators.Refresh (:270) respectively before doing any work. It is registered by each app's Identity module rather than by the framework, since one of its three dependencies is app-owned: MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:36 and MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Application/DependencyInjection.cs:42, both TryAddScoped<AuthenticationValidators>().

      IAuthenticationService

      @@ -2673,52 +2981,62 @@

      RefreshSessionRevocation

      AuthenticationServiceBase<TUser>

      -

      MMCA.Common.Application · MMCA.Common.Application.Auth · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:74 · Level 8 · class (abstract)

      +

      MMCA.Common.Application · MMCA.Common.Application.Auth · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:63 · Level 8 · class (abstract)

      • What it is: the shared authentication workflow (login, registration, refresh-token rotation, per-device and global revocation, session listing) hoisted once into the framework, generic over the app's User aggregate. It realises IAuthenticationService and leaves the genuinely app-specific decisions to a small set of abstract and virtual hooks a sealed subclass overrides.

      • -
      • Depends on: IUnitOfWork and IRepository<TEntity, TIdentifierType> (persistence, G07), ITokenService, IPasswordHasher, ILoginProtectionService, AuthenticationValidators, IRefreshSessionStore, IOptions<RefreshSessionSettings> (the eight required constructor parameters, :75-82), and two optional ones added since: an ITwoFactorAuthenticator? and IOptions<EmailConfirmationSettings>? (:83-84, both default null so every existing subclass keeps compiling untouched), the IAuthUser credential contract plus AuditableAggregateRootEntity<TIdentifierType> as the TUser constraint (:85) and, where a host adopts confirmation, IEmailConfirmableUser (matched in CheckEmailConfirmed), RefreshSession (the session aggregate it creates and revokes), Email (normalizing the login and register address), ClaimsPrincipalExtensions (principal.GetUserId() and, new, principal.FindMultiFactorMethod()), Result and Error, the request and response DTOs, and the BCL TimeProvider (injected at :79, never DateTime.UtcNow, so the clock is testable).

        +
      • Depends on: IUnitOfWork and IRepository<TEntity, TIdentifierType> (persistence, G07), IPasswordHasher, ILoginProtectionService, AuthenticationValidators and IAuthSessionIssuer (the five required constructor parameters, :64-68), and two optional ones: an ITwoFactorAuthenticator? and IOptions<EmailConfirmationSettings>? (:69-70, both default null so a subclass that adopts neither passes nothing), the IAuthUser credential contract plus AuditableAggregateRootEntity<TIdentifierType> as the TUser constraint (:71) and, where a host adopts confirmation, IEmailConfirmableUser (matched in CheckEmailConfirmed), ITokenService (reached only through the issuer's TokenService property, MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/IAuthSessionIssuer.cs:34), TwoFactorOutcome, TwoFactorErrors and AuthClaimTypes (the mfa claim values), Email (normalizing the login and register address), ClaimsPrincipalExtensions (principal.GetUserId() and principal.FindMultiFactorMethod()), Result and Error, and the request and response DTOs. The base itself takes no session store, no session settings and no clock (:63-70): those belong to the issuer.

      • -
      • Concept introduced: the Template Method that de-duplicates a whole vertical slice. [Rubric §2, Design Patterns] assesses idiomatic pattern use: this is a textbook Template Method, the invariant sequence of an operation living in the base while the variable steps are deferred to subclass hooks. [Rubric §15, Best Practices & Code Quality] (DRY across services) and [Rubric §1, SOLID] also apply: the doc comment (:14-19) records that the app Identity modules previously duplicated this workflow at roughly 70 to 95 percent line-identity, so a fix to the lockout order or the rotation logic is written once. [Rubric §11, Security]: the base encodes the security posture directly, validate first, an ILoginProtectionService lockout and rate-limit gate (ADR-029), an untracked-then-tracked dual fetch (ADR-004), and refresh-token rotation with reuse detection (ADR-050, BR-205/206). [Rubric §7, Microservices Readiness]: the workflow depends only on ports, so it runs unchanged whether the Identity module is in-monolith or its own service.

        -

        The session model is the other concept to absorb before reading the code (:35-47). Refresh tokens are not a column on the user: every issue opens its own RefreshSession row, the store holds only RefreshSession.HashToken digests, and rotation revokes the presented session and links it to its successor. Presenting an already-rotated token therefore lands on a revoked row, which is the reuse signal that revokes the user's whole live family (BR-206). Two requests presenting the same live token at the same instant get the same treatment, because the rotation is claimed atomically through IRefreshSessionStore.TryRotateAsync and the loser is answered as a replay rather than handed a second successor. An expired session is not a reuse signal and fails alone. A per-user cap evicts the oldest live session on a new sign-in so one account cannot grow the table without bound.

        +
      • Concept introduced: the Template Method that de-duplicates a whole vertical slice. [Rubric §2, Design Patterns] assesses idiomatic pattern use: this is a textbook Template Method, the invariant sequence of an operation living in the base while the variable steps are deferred to subclass hooks. [Rubric §15, Best Practices & Code Quality] (DRY across services) and [Rubric §1, SOLID] also apply: the doc comment (:19-23) records that the app Identity modules previously duplicated this workflow at roughly 70 to 95 percent line-identity, so a fix to the lockout order or the rotation logic is written once. [Rubric §11, Security]: the base encodes the security posture directly, validate first, an ILoginProtectionService lockout and rate-limit gate (ADR-029), an untracked-then-tracked dual fetch (ADR-004), and, through IAuthSessionIssuer, refresh-token rotation with reuse detection (ADR-050, BR-205/206). [Rubric §7, Microservices Readiness]: the workflow depends only on ports, so it runs unchanged whether the Identity module is in-monolith or its own service.

        +

        The division of labour is the other concept to absorb before reading the code (:39-44): this class decides who is signed in, and IAuthSessionIssuer decides what they are handed. Once a caller is proved, the multi-device refresh sessions (hashed at rest), BR-205 rotation with BR-206 reuse detection, the per-user session cap, and the sid and mfa claims on the minted access token are the issuer's job, so this workflow never touches a RefreshSession row. Every session-shaped member below is a single delegation to the issuer; the reuse, replay, expiry and cap rules are taught on AuthSessionIssuer.

      • Walkthrough (members in teaching order):

          -
        • Constructor and protected accessors (:74-86): a primary constructor takes the eight required collaborators plus the two optional ones; a private field wraps the injected token service in a SessionStampingTokenService (:93); a private _multiFactorMethod field (:105) is the base's own arming state for the second-factor claim, separate from and forwarded into the wrapper's CurrentMultiFactorMethod; protected read-only properties re-expose UnitOfWork (:108), the wrapped TokenService (:120, whose remarks explain that minting through this property is what puts sid on the token), TimeProvider (:123), RefreshSessions (:126), and a Repository resolved lazily as unitOfWork.GetRepository<TUser, UserIdentifierType>() (:129).

          +
        • Constructor and protected accessors (:63-71): a primary constructor takes the five required collaborators plus the two optional ones. A private _multiFactorMethod field (:83) is the base's own arming state for the second-factor claim, a plain field because the service is scoped and one request issues one token pair (remarks, :79-82); it is handed to the issuer at mint time. Protected read-only properties re-expose UnitOfWork (:86), TokenService (:98), which returns sessionIssuer.TokenService, the issuer's stamping instance, so minting through it is what puts sid on the token (remarks, :88-97), and a Repository resolved lazily as unitOfWork.GetRepository<TUser, UserIdentifierType>() (:101-102). Token lifetimes and the session cap are not members of this class: they are configuration read by the issuer.

        • -
        • Lifetimes and cap: virtual AccessTokenLifetime and RefreshTokenLifetime read through to ITokenService (which derives them from Jwt:AccessTokenExpirationMinutes and Jwt:RefreshTokenExpirationDays), falling back to the BR-205 defaults of 15 minutes and 7 days on a non-positive value, meaning a hand-written test double or a misconfigured host. virtual MaxActiveSessionsPerUser reads RefreshSessions:MaxActiveSessionsPerUser.

          +
        • LoginAsync (:105-200): validate the request, check lockout (ADR-029 and BR-212), normalize the raw email into an Email value object so the EF predicate compares same-typed converted values (an invalid address yields a null value object that simply matches no user, which is the invalid-credentials answer anyway). Step 1 is an untracked fetch via the FindUntrackedByEmailAsync hook to verify credentials without change-tracker overhead. The null check now also calls HasStoredCredential(untracked) (:135, private helper at :581-582): an account with no stored password material, the shape an external-OAuth account carries (ADR-036), can never be reached by password login, and both branches answer the identical generic 401 with the identical BurnPasswordVerificationCost(request.Password) call (:137, private helper at :589-599) so the two cases cost the same time and are indistinguishable to a timing observer, before IncrementFailedAttemptsAsync. passwordHasher.VerifyPassword runs next and fails the same way on a wrong password. Only after the password check does the app gate (ValidateLoginCandidateAsync) run, deliberately reordered: reaching it now proves the caller owns the account, so its distinct status message (for example a deactivated-account rejection) is told to the account's owner rather than to anyone sweeping addresses, and running it before the password check (the old order) made account state readable with no credential at all. Two more gates follow the same rule, for the same reason: CheckEmailConfirmed(untracked) (:164, hook at :480-490), off unless the host both supplied EmailConfirmationSettings.RequireConfirmedEmail and the app's User implements IEmailConfirmableUser, and ChallengeSecondFactorCountingFailuresAsync(untracked.Id, request, cancellationToken) (:170-171, private helper at :522-535). That helper runs the overridable ChallengeSecondFactorAsync hook (:504-510), which with no ITwoFactorAuthenticator registered answers TwoFactorOutcome.NotEnrolled outright at no extra cost, and counts a TwoFactorErrors.TwoFactorInvalidCode failure against the account through IncrementFailedAttemptsAsync exactly like a wrong password, so the lockout at the top of the method throttles code guessing per account. A missing code is the ordinary first leg of the challenge and is not counted (:512-517). Step 2 is a tracked re-fetch by id, purely about the instance the app's CreateAccessToken hook mints from, and the second lookup is what turns a race that deleted the account between the two steps into a clean 404. Then ResetFailedAttemptsAsync, and _multiFactorMethod = MultiFactorMethodFor(secondFactor.Value) (:191, private helper at :543-552, mapping a verified TOTP or recovery code to the mfa claim value and any unrecognized outcome to null) arms the field in a try/finally around IssueTokensAsync so the claim lands only on this response and disarms whether or not the call throws.

        • -
        • LoginAsync (:156-251): validate the request, check lockout (ADR-029 and BR-212), normalize the raw email into an Email value object so the EF predicate compares same-typed converted values (an invalid address yields a null value object that simply matches no user, which is the invalid-credentials answer anyway). Step 1 is an untracked fetch via the FindUntrackedByEmailAsync hook to verify credentials without change-tracker overhead. The null check now also calls HasStoredCredential(untracked) (:186, private helper at :890): an account with no stored password material, the shape an external-OAuth account carries (ADR-036), can never be reached by password login, and both branches answer the identical generic 401 with the identical BurnPasswordVerificationCost(request.Password) call (:188, private helper at :898) so the two cases cost the same time and are indistinguishable to a timing observer, before IncrementFailedAttemptsAsync. passwordHasher.VerifyPassword runs next and fails the same way on a wrong password. Only after the password check does the app gate (ValidateLoginCandidateAsync) run, deliberately reordered: reaching it now proves the caller owns the account, so its distinct status message (for example a deactivated-account rejection) is told to the account's owner rather than to anyone sweeping addresses, and running it before the password check (the old order) made account state readable with no credential at all. Two more gates follow the same rule, for the same reason: CheckEmailConfirmed(untracked) (:215, hook at :641-651), off unless the host both supplied EmailConfirmationSettings.RequireConfirmedEmail and the app's User implements IEmailConfirmableUser, and ChallengeSecondFactorAsync(untracked.Id, request.TwoFactorCode, cancellationToken) (:221-222, hook at :665-671), which with no ITwoFactorAuthenticator registered answers TwoFactorOutcome.NotEnrolled outright at no extra cost. Step 2 is a tracked re-fetch by id, purely about the instance the app's CreateAccessToken hook mints from, and the second lookup is what turns a race that deleted the account between the two steps into a clean 404. Then ResetFailedAttemptsAsync, and _multiFactorMethod = MultiFactorMethodFor(secondFactor.Value) (:242, private helper at :679-688, mapping a verified TOTP or recovery code to the mfa claim value and any unrecognized outcome to null) arms the field in a try/finally around IssueTokensAsync so the claim lands only on this response and disarms whether or not the call throws.

          -
        • -
        • RegisterAsync (:254-331): validate, IP rate-limit (ADR-029 and BR-213), reject a duplicate email through the EmailExistsAsync hook, hash the password, build the user through the CreateUser hook, AddAsync and SaveChangesAsync, run the OnUserRegisteredAsync post-commit hook to pick up the instance the first access token is minted from, increment the IP registration count, and open the session last: the session row carries the user id, which a store-generated key only has once the insert has run.

          +
        • RegisterAsync (:203-280): validate, IP rate-limit (ADR-029 and BR-213), reject a duplicate email through the EmailExistsAsync hook, hash the password, build the user through the CreateUser hook, AddAsync and SaveChangesAsync, run the OnUserRegisteredAsync post-commit hook to pick up the instance the first access token is minted from, increment the IP registration count, and open the session last: the session row carries the user id, which a store-generated key only has once the insert has run.

          The save is wrapped in a deliberately broad catch (Exception) whose comment is the teaching material. The email lookup above is a check-then-act: two concurrent registrations for the same address both pass it, and the loser only fails on the insert, against the unique index every consumer puts on Email (ADC unfiltered, Store filtered on IsDeleted). Without the catch, that race surfaces as a generic 500 instead of the 409 a serialized pair would have produced. The catch cannot name DbUpdateException, because Application has no EF Core dependency by layer rule, so the re-check is what narrows it: if the address exists now, the concurrent registration is the cause and the caller gets the same conflict the serial path returns through the shared EmailAlreadyExistsFailure() helper; anything else rethrows untouched and still reaches the exception middleware. The re-check passes CancellationToken.None on purpose: it has to run even when the caller's token is what aborted the save, or a cancelled save could never be classified.

        • -
        • RefreshTokenAsync (:334-413): validate, pull claims from the expired JWT via tokenService.GetPrincipalFromExpiredToken (signature still checked, only lifetime skipped), read the identifier with principal.GetUserId() (rides the standard sub claim, also accepts the NameIdentifier form the bearer handler maps it to, and parses through IParsable so the identifier alias can change shape without editing this file), load the tracked user, run the refresh app gate, resolve the session behind the presented token, rotate it, and answer with a token pair whose access token carries the successor's sid, a client's current-device marker following the rotation instead of pointing at the session the rotation just revoked. Since the second-factor addition, the step-up the user already performed is carried across the rotation too: _multiFactorMethod = principal.FindMultiFactorMethod() (:398) reads the mfa claim off the presented access token, whose signature was already validated, and arms it in a try/finally around the successor mint, the same disarm pattern as LoginAsync. Dropping it would quietly demote a signed-in session every access-token lifetime and make an IRequiresMfa use case unreachable without a fresh sign-in.

          +
        • RefreshTokenAsync (:283-349): validate, pull claims from the expired JWT via TokenService.GetPrincipalFromExpiredToken (:297, the issuer's instance; signature still checked, only lifetime skipped), read the identifier with principal.GetUserId() (rides the standard sub claim, also accepts the NameIdentifier form the bearer handler maps it to, and parses through IParsable so the identifier alias can change shape without editing this file), load the tracked user, run the refresh app gate, and hand the presented refresh token to sessionIssuer.RotateAsync (:337-343) together with a mint callback over CreateAccessTokenForSession. The issuer resolves the session behind the token (reuse detection included), rotates it, and answers with a token pair whose access token carries the successor's sid, a client's current-device marker following the rotation instead of pointing at the session the rotation just revoked. The step-up the user already performed is carried across the rotation too: _multiFactorMethod = principal.FindMultiFactorMethod() (:332) reads the mfa claim off the presented access token, whose signature was already validated, and arms it in a try/finally around the successor mint, the same disarm pattern as LoginAsync. Dropping it would quietly demote a signed-in session every access-token lifetime and make an IRequiresMfa use case unreachable without a fresh sign-in.

        • -
        • RevokeTokenAsync (:416-454): load the user, and when a refresh token was supplied, look up its session by hash. Only a live session belonging to this user identifies the device to sign out; anything else (unknown token, another account's token, an already-revoked row) leaves the caller unidentifiable, so the request degrades to revoking every live session rather than reporting success for a revocation that reached nothing.

          +
        • RevokeTokenAsync (:352-366): load the user (a NotFound when absent), then delegate to sessionIssuer.SignOutAsync(userId, refreshToken, ...) (:363). Only a live session belonging to this user identifies the device to sign out; when the token is absent or identifies no live session of this user, the issuer revokes every live session instead (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/IAuthSessionIssuer.cs:85-94), rather than reporting success for a revocation that reached nothing.

        • -
        • RevokeAllSessionsAsync (:457-472): the unconditional form of the same thing.

          +
        • RevokeAllSessionsAsync (:369-382): the unconditional form of the same thing, through sessionIssuer.SignOutEverywhereAsync (:379).

        • -
        • GetSessionsAsync (:481-505): reads the same "un-revoked sessions for this user" query the cap and family revocation use, then drops expired rows in memory with IsActiveAt(now) and orders newest first with Id as the tie-break. The remarks explain why the filtering is in memory: the store returns expired-but-unrevoked rows on purpose, and a device list must not offer a user a device that can no longer authenticate.

          +
        • GetSessionsAsync (:386-394): no user lookup, so a list is one call to sessionIssuer.ListActiveAsync(userId, currentSessionId, ...) (:391) wrapped in a success. The issuer lists live sessions newest first and leaves expired-but-unrevoked rows out, since a device list must not offer a device that can no longer authenticate (IAuthSessionIssuer.cs:102-113).

        • -
        • RevokeSessionByIdAsync (:519-543): the ownership check is the store query, scoped to the user, so another account's id and a nonexistent id produce the same NotFound; an already-revoked session returns success without writing.

          +
        • RevokeSessionByIdAsync (:402-406): an expression-bodied delegation to sessionIssuer.RevokeSessionAsync. Ownership is checked by the issuer's store query, so another account's id and a nonexistent id produce the same NotFound; an already-revoked session is a success that writes nothing (IAuthSessionIssuer.cs:115-124).

        • -
        • IssueTokensAsync (:554-578): the shared open-and-respond used by login and registration, and reusable by an app-level external-login flow. It opens the session before minting the access token, because the token carries the session's id, saves, and returns the response.

          +
        • IssueTokensAsync (:417-431): the shared open-and-respond used by login and registration, and reusable by an app-level external-login flow. It null-guards the user and delegates to sessionIssuer.IssueAsync(user.Id, sessionId => CreateAccessTokenForSession(user, sessionId), ...) (:425-430): the access token is a callback because it carries the session's id, which exists only once the issuer has opened the session.

        • -
        • The private mechanics: ResolveRotatableSessionAsync (:723) is where the three rejections differ behind one identical error. An unknown hash, or one belonging to another account, is failed alone, because revoking the family on it would let anyone holding one of this user's expired access tokens sign them out everywhere by posting a random token. A revoked row means this exact token was already rotated away or signed out and has come back, which is the BR-206 reuse signal that revokes every live session. An expired row is an ordinary end of life and fails alone. OpenSessionAsync (:762) mints a token, creates the session, enforces the cap, and stages the insert, returning an IssuedSession. RotateAsync (:801) mints the successor and claims the rotation through TryRotateAsync; losing the claim is answered exactly like a replay. RevokeLiveSessionsAsync (:844) revokes without saving. EnforceSessionCapAsync (:864) revokes the oldest live sessions while the user is at or over the cap; expired-but-unrevoked rows do not count against the cap because they authenticate nobody, and age out through the retention sweep instead. InvalidRefreshTokenError() (:883) and EmailAlreadyExistsFailure() (:915) are the two shared failures that keep distinct internal paths indistinguishable to a caller.

          +
        • The private mechanics are login-side only. ChallengeSecondFactorCountingFailuresAsync (:522-535) is the counting wrapper described under LoginAsync. MultiFactorMethodFor (:543-552) maps an unrecognized outcome to null, so an enum that grew cannot silently mint an mfa claim. HasStoredCredential (:581-582) requires both a non-empty hash and a non-empty salt. BurnPasswordVerificationCost (:589-599) runs one throwaway VerifyPassword against a zeroed 64-byte hash and 32-byte salt, skipping a blank password, so a branch that never reaches the real check still pays the key-derivation cost. EmailAlreadyExistsFailure() (:606-608) is the 409 shared by the up-front check and the race recovery so the two paths are indistinguishable to a caller. Session resolution, rotation, family revocation and the cap are private to AuthSessionIssuer.

        • -
        • The hooks: four are abstract, so a subclass must supply them. FindUntrackedByEmailAsync (:585) and EmailExistsAsync (:591) are deliberately written against the app's concrete User so EF translates the predicate byte-for-byte as before, and the second explicitly leaves the app to decide whether soft-deleted accounts count (ignoreQueryFilters: true blocks re-registration of an erased address); CreateUser (:594) runs the app's domain factory; CreateAccessToken (:597) mints the app's claim set (for example speaker_id versus customer_id). Seven virtual members can be overridden, up from five: CreateAccessTokenForSession (:618-631) arms the stamping wrapper (both the session and, now, the multi-factor property) around the hook call; CheckEmailConfirmed (:641-651), new, is the email-confirmation sign-in gate, off unless the host supplied EmailConfirmationSettings.RequireConfirmedEmail and the app's User implements IEmailConfirmableUser; ChallengeSecondFactorAsync (:665-671), new, runs the second-factor challenge and, with no ITwoFactorAuthenticator injected, answers TwoFactorOutcome.NotEnrolled with no extra query, which is what keeps the feature free for an app that has not adopted it; ValidateLoginCandidateAsync (:691) and ValidateRefreshCandidateAsync (:695) add extra gates such as a deactivated-account check; OnUserRegisteredAsync (:702) runs the post-commit side-effect; and CreateRefreshUserMissingError (:710) defaults the vanished-user case to 401 (a token for a missing user is indistinguishable from an invalid one) while letting an app return 404 where its public contract already promises it.

          +
        • The hooks: four are abstract, so a subclass must supply them. FindUntrackedByEmailAsync (:438) and EmailExistsAsync (:444) are deliberately written against the app's concrete User so EF translates the predicate byte-for-byte as before, and the second explicitly leaves the app to decide whether soft-deleted accounts count (ignoreQueryFilters: true blocks re-registration of an erased address); CreateUser (:447) runs the app's domain factory; CreateAccessToken (:450) mints the app's claim set (for example speaker_id versus customer_id). Seven virtual members can be overridden: CreateAccessTokenForSession (:469-470) calls sessionIssuer.MintForSession(sessionId, _multiFactorMethod, () => CreateAccessToken(user)), so the issuer arms its stamping token service for the duration of the app hook and sid (plus mfa when this request verified a second factor) is appended to whatever claim set the app passes, which is what keeps the claims additive with no change to CreateAccessToken's signature (remarks, :456-465); CheckEmailConfirmed (:480-490) is the email-confirmation sign-in gate, off unless the host supplied EmailConfirmationSettings.RequireConfirmedEmail and the app's User implements IEmailConfirmableUser; ChallengeSecondFactorAsync (:504-510) runs the second-factor challenge and, with no ITwoFactorAuthenticator injected, answers TwoFactorOutcome.NotEnrolled with no extra query, which is what keeps the feature free for an app that has not adopted it; ValidateLoginCandidateAsync (:555) and ValidateRefreshCandidateAsync (:559) add extra gates such as a deactivated-account check; OnUserRegisteredAsync (:566) runs the post-commit side-effect; and CreateRefreshUserMissingError (:574) defaults the vanished-user case to 401 (a token for a missing user is indistinguishable from an invalid one) while letting an app return 404 where its public contract already promises it.

      • -
      • Why it's built this way: the untracked-then-tracked dual fetch keeps the common credential-verification path off the change tracker (cheaper, and soft-deleted accounts fall out via EF query filters returning the generic 401) while still giving a tracked instance to mint from (ADR-004). Per-device session rows with hashed tokens, rotation, family revocation on reuse, and a per-user cap are the BR-205/206 model (ADR-050). Password material flows through IAuthUser's PasswordHash and PasswordSalt (ADR-032), and the whole workflow depends only on abstractions, so it is identical whether the module runs in-process or as an extracted service.

        +
      • Why it's built this way: the untracked-then-tracked dual fetch keeps the common credential-verification path off the change tracker (cheaper, and soft-deleted accounts fall out via EF query filters returning the generic 401) while still giving a tracked instance to mint from (ADR-004). Per-device session rows with hashed tokens, rotation, family revocation on reuse, and a per-user cap are the BR-205/206 model (ADR-050), and keeping them behind IAuthSessionIssuer gives the sign-in decision and the session mechanics one owner each (:39-44). Password material flows through IAuthUser's PasswordHash and PasswordSalt (ADR-032), and the whole workflow depends only on abstractions, so it is identical whether the module runs in-process or as an extracted service.

      • -
      • Where it's used: subclassed by each app's sealed AuthenticationService, for example MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:48, which binds TUser = User, adds the Attendee default role (BR-45) and the speaker_id claim (BR-209, built at :275), and re-lists IAuthenticationService (:63) so it can re-implement RegisterAsync and ExternalLoginAsync outright: ADC raises its registration side-effects inside one transactional unit rather than through the OnUserRegisteredAsync hook, because the identity column means the id does not exist until the first save (AuthenticationService.cs:30-41). MMCA.Store supplies its own subclass with a customer_id claim. Consumed by the Identity API controllers via the IAuthenticationService port.

        +
      • Where it's used: subclassed by each app's sealed AuthenticationService, for example MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:50, which binds TUser = User, adds the Attendee default role (BR-45) and the speaker_id claim (BR-209, built at :381-384), passes the injected IAuthSessionIssuer through to the base (:56, :63), and re-lists IAuthenticationService (:66) so it can re-implement RegisterAsync and ExternalLoginAsync outright: ADC raises its registration side-effects inside one transactional unit rather than through the OnUserRegisteredAsync hook, because the identity column means the id does not exist until the first save (AuthenticationService.cs:30-42). MMCA.Store supplies its own subclass with a customer_id claim. Consumed by the Identity API controllers via the IAuthenticationService port.

      • -
      • Caveats / not-in-source: ExternalLoginAsync is intentionally not overridden here: the base inherits the interface's default not-supported failure, and OAuth account linking stays in the app subclass because it is coupled to the app's User factory surface (doc comment, :33-34).

        +
      • Caveats / not-in-source: ExternalLoginAsync is intentionally not overridden here: the base inherits the interface's default not-supported failure, and OAuth account linking stays in the app subclass because it is coupled to the app's User factory surface (doc comment, :37-38).

      +

      IssuedSession

      +
      +

      MMCA.Common.Application · MMCA.Common.Application.Auth.Sessions · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:436 · Level 0 · record (private sealed, nested)

      +
      +
        +
      • What it is: the two-field result of opening or rotating a refresh session: the plaintext refresh token the client is handed, and the id of the session row it belongs to. It is a private sealed record nested inside AuthSessionIssuer (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:436), not part of the framework's public surface.
      • +
      • Depends on: nothing beyond the BCL (string, Guid). It is produced and consumed entirely inside its declaring class.
      • +
      • Concept introduced: the plaintext token exists in exactly one place, and it is a return value. [Rubric §11, Security] assesses how a bearer credential is stored. RefreshSession rows keep only a digest (RefreshSession.HashToken, used at AuthSessionIssuer.cs:155 and :285 to look up by hash), so once a session is persisted the raw token cannot be recovered from the store at all. The type comment says exactly this (AuthSessionIssuer.cs:431-435): the plaintext "exists nowhere else, since the store keeps only its hash". Modelling the hand-off as a small record rather than an out-parameter or a tuple is what keeps that fact readable: both methods that can produce a token return Result<IssuedSession>, so the compiler shows you the complete list of places raw token material is in flight. [Rubric §15, Best Practices & Code Quality] also applies: a positional record gives value equality and immutability for free, and Guid SessionId names what would otherwise be an anonymous second tuple element.
      • +
      • Walkthrough: one positional declaration, IssuedSession(string RefreshToken, Guid SessionId) (AuthSessionIssuer.cs:436). RefreshToken is what goes back to the caller in the AuthenticationResponse; SessionId is what the access token's sid claim carries, which is why the session must be opened before the token is minted (the comment at AuthSessionIssuer.cs:73-74).
      • +
      • Why it's built this way: the pairing is load-bearing rather than incidental. A caller that received only the token could not stamp sid, and a caller that received only the id could not answer the client. Returning both together removes the ordering mistake where a token is minted for a session that does not exist yet.
      • +
      • Where it's used: returned by OpenSessionAsync (AuthSessionIssuer.cs:312, constructed at :337) and RotateSessionAsync (:350, constructed at :390); unwrapped by IssueAsync (:84-85) and by RotateAsync (:120-121), each of which passes SessionId to the caller's mintAccessToken callback and puts RefreshToken into the response.
      • +

      EmailConfirmationSettings

      MMCA.Common.Application · MMCA.Common.Application.Auth.EmailConfirmation · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/EmailConfirmation/EmailConfirmationSettings.cs:10 · Level 0 · class

      @@ -2728,7 +3046,7 @@

      EmailConfirmationSettings

    616. Depends on: nothing first-party; System.ComponentModel.DataAnnotations ([Range]) and System.Diagnostics.CodeAnalysis ([SuppressMessage]) validate the bound values.
    617. Concept introduced: the config-bound settings-class shape (a SectionName constant plus init-only, range-validated properties with sane defaults) is the same one the sibling PasswordResetSettings uses elsewhere in this group; see it there for the general pattern rather than re-teaching it here. [Rubric §11, Security] (assesses whether authentication-adjacent behavior is safely configurable): every numeric knob here is range-constrained (EmailConfirmationSettings.cs:43,50,54,58), so a bad value in configuration fails validation instead of silently disabling throttling.
    618. Walkthrough: ConfirmationUrl (EmailConfirmationSettings.cs:40) defaults to empty rather than being [Required], deliberately, per its own doc comment (EmailConfirmationSettings.cs:30-34): an unconfigured host still boots and degrades to a token-only, paste-in-by-hand flow. TokenLifetimeMinutes (line 44) defaults to 1440 (one day). MaxValidationAttempts (line 51) caps wrong-token guesses at 5 before the record is discarded. MaxRequestsPerEmail (line 55) and RequestWindowMinutes (line 59) throttle re-issuance per address. RequireConfirmedEmail (line 70) defaults to false.
    619. -
    620. Why it's built this way: the RequireConfirmedEmail default is called out as load-bearing in its own remarks (EmailConfirmationSettings.cs:64-69): turning it on locks out every account whose address was never confirmed, so a host backfills existing rows as confirmed before flipping it, and with it off the whole feature is additive. This is the opt-in shape recorded in ADR-116.
    621. +
    622. Why it's built this way: the RequireConfirmedEmail default is called out as load-bearing in its own remarks (EmailConfirmationSettings.cs:49-54): turning it on locks out every account whose address was never confirmed, so a host backfills existing rows as confirmed before flipping it, and with it off the whole feature is additive. This is the opt-in shape recorded in ADR-116.
    623. Where it's used: read by AuthenticationServiceBase and EmailConfirmationTokenService (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs) and registered in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs; ADC binds it via MMCA.ADC.Identity.Application/Users/AuthenticationServiceSettings.cs and consumes it in SendEmailConfirmationHandler.
    624. IPermissionGrantCache

      @@ -2738,7 +3056,7 @@

      IPermissionGrantCache

      • What it is: the read side of the in-memory snapshot of stored (database-granted, as opposed to compiled-into-code) permissions, keyed by role.
      • Depends on: nothing first-party in its own signature; consumed alongside IPermissionRegistry.
      • -
      • Concept introduced: the cache-in-front-of-a-store pattern used across this group for data that is read on every authorization check but changes rarely. [Rubric §12, Performance & Scalability] (assesses whether hot paths avoid a database round trip): GetPermissions (IPermissionGrantCache.cs:99) is synchronous and reads the already-loaded snapshot, so it stays off the authorization hot path's I/O.
      • +
      • Concept introduced: the cache-in-front-of-a-store pattern used across this group for data that is read on every authorization check but changes rarely. [Rubric §12, Performance & Scalability] (assesses whether hot paths avoid a database round trip): GetPermissions (IPermissionGrantCache.cs:29) is synchronous and reads the already-loaded snapshot, so it stays off the authorization hot path's I/O.
      • Walkthrough: GetPermissions(string role) (line 99) returns the role's stored permissions, matched case-insensitively, empty when the role has none or the cache has not loaded yet. RefreshAsync (line 107) reloads the whole snapshot from the store; called at startup, on a configured interval, and explicitly by IPermissionGrantCacheInvalidator after a grant changes.
      • Why it's built this way: separating the read interface from the invalidation interface (IPermissionGrantCacheInvalidator) lets LayeredPermissionRegistry, which only needs to read, depend on a narrower contract than the administration path that needs to force a reload.
      • Where it's used: implemented by PermissionGrantCache (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/PermissionGrantCache.cs), read by LayeredPermissionRegistry (LayeredPermissionRegistry.cs) and StoredPermissionRoleAdministrationService, and registered in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs.
      • @@ -2751,7 +3069,7 @@

        IPermissionGrantCacheInvalidator

      • What it is: the write-triggering half of the cache: forces a reload of one role's grants (or every role) after an administrative change.
      • Depends on: nothing first-party in its own signature.
      • Concept: same cache-invalidation split introduced at IPermissionGrantCache; no new concept here.
      • -
      • Walkthrough: InvalidateAsync(string? role = null, ...) (IPermissionGrantCache.cs:136) invalidates the cached grants and reloads them; null means every role.
      • +
      • Walkthrough: InvalidateAsync(string? role = null, ...) (IPermissionGrantCache.cs:59) invalidates the cached grants and reloads them; null means every role.
      • Why it's built this way: kept on a separate, narrower interface from the read side so a caller that only grants or revokes (the administration surface) depends on exactly the capability it needs.
      • Where it's used: implemented alongside IPermissionGrantCache by PermissionGrantCache.cs, called by StoredPermissionRoleAdministrationService.cs after a grant or revoke, and registered in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs.
      @@ -2763,57 +3081,28 @@

      PermissionGrantSettings

    625. What it is: the options class bound to Authentication:PermissionGrants, controlling how long a cached grant snapshot is served, which logical data source owns the grant table, and which role names the administration surface lists.
    626. Depends on: nothing first-party; System.ComponentModel.DataAnnotations ([Range], [Required]).
    627. Concept: the same config-bound settings shape as EmailConfirmationSettings; no new concept.
    628. -
    629. Walkthrough: CacheSeconds (PermissionGrantSettings.cs:171) defaults to 300 (five minutes) and its remarks (lines 165-169) spell out the trade-off: this is the bound on how stale another replica may be after an edit, since invalidation is per process. DataSourceName (line 179) defaults to "Default", naming the logical data source a modular host points at its Identity database. KnownRoles (line 191) defaults to an empty list, and its remarks (lines 185-190) explain why it must be configured: neither IPermissionRegistry (which knows about a role but does not enumerate roles) nor the grant table (which only knows roles that already have a grant) can list roles on its own.
    630. -
    631. Why it's built this way: a five-minute cache treats a permission grant as an administrative change rather than a per-request one, trading a shorter staleness window for more database reads if lowered.
    632. -
    633. Where it's used: read by PermissionGrantCache.cs, PermissionGrantRefreshService.cs, StoredPermissionRoleAdministrationService.cs, and DesignTimeDbContextHelper.cs; registered in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs.
    634. +
    635. Walkthrough: SectionName (PermissionGrantSettings.cs:12) is "Authentication:PermissionGrants". CacheSeconds (line 25) defaults to 300 (five minutes), is range-validated to 5-3600 (line 24), and its remarks (lines 17-23) spell out two things. First the trade-off: this is the bound on how stale another replica may be after an edit, since invalidation is per process. Second the failure behavior: each cached entry lives three intervals, so the snapshot outlives the gap before the next reload and survives up to two failed reloads, after which it stops answering and stored grants fail closed. DataSourceName (line 33, [Required] at line 32) defaults to "Default", naming the logical data source a modular host points at its Identity database. KnownRoles (line 45) defaults to an empty list, and its remarks (lines 39-44) explain why it must be configured: neither IPermissionRegistry (which answers questions about a role but does not list roles) nor the grant table (which only knows roles that already have a grant) can enumerate roles on its own; left empty, the surface still works but lists nothing until the first grant exists.
    636. +
    637. Why it's built this way: a five-minute cache treats a permission grant as an administrative change rather than a per-request one, trading a shorter staleness window for more database reads if lowered. The three-interval entry lifetime means a transient store outage costs nothing for two reload cycles, while a longer one degrades to denying stored grants (compiled permissions are unaffected, see LayeredPermissionRegistry) rather than serving an arbitrarily old snapshot.
    638. +
    639. Where it's used: read by PermissionGrantCache.cs, PermissionGrantRefreshService.cs, EFPermissionGrantStore.cs, StoredPermissionRoleAdministrationService.cs, ApplicationDbContext.cs, and DesignTimeDbContextHelper.cs (all under MMCA.Common/Source/Core/MMCA.Common.Infrastructure); registered in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs; pinned by PermissionGrantModelGateTests.cs and PermissionGrantCacheTests.cs in MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests.
    640. -

      RecoveryCodeSet

      -
      -

      MMCA.Common.Application · MMCA.Common.Application.Auth.TwoFactor · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/ITwoFactorService.cs:84 · Level 0 · record

      -
      -
        -
      • What it is: a two-field record pairing a freshly generated set of plaintext recovery codes with their stored hashes.
      • -
      • Depends on: nothing first-party.
      • -
      • Concept: a plaintext/hash pair returned once, at generation time only, so the plaintext never has to be persisted or re-derived.
      • -
      • Walkthrough: Codes (ITwoFactorService.cs:85) is the plaintext shown to the user once; Hashes (line 86) is what the store keeps.
      • -
      • Why it's built this way: keeping the plaintext out of any store means a database compromise cannot recover usable recovery codes, only their hashes.
      • -
      • Where it's used: returned by ITwoFactorService.GenerateRecoveryCodes(), consumed by TotpTwoFactorService.cs and exercised in MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs.
      • -
      -

      TwoFactorOutcome

      -
      -

      MMCA.Common.Application · MMCA.Common.Application.Auth.TwoFactor · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/ITwoFactorAuthenticator.cs:47 · Level 0 · enum

      -
      -
        -
      • What it is: the result of a second-factor challenge: NotEnrolled = 0, VerifiedTotp = 1, VerifiedRecoveryCode = 2 (ITwoFactorAuthenticator.cs:237,240,243).
      • -
      • Depends on: nothing first-party.
      • -
      • Concept: same explicitly-numbered, wire-stable outcome-enum shape used throughout the domain event taxonomy (see DomainEntityState in group-02); NotEnrolled = 0 is the default/no-op value, matching that convention.
      • -
      • Walkthrough: three values; NotEnrolled means the account has no active second factor so nothing was challenged, VerifiedTotp means a time-based code from the authenticator app verified, VerifiedRecoveryCode means a single-use recovery code verified and was spent.
      • -
      • Why it's built this way: collapsing the challenge result to one enum lets AuthenticationServiceBase decide, in one switch, whether to continue sign-in unchanged (NotEnrolled) or stamp an mfa claim on the issued token (the two verified cases).
      • -
      • Where it's used: returned by ITwoFactorAuthenticator.ChallengeAsync, read by AuthenticationServiceBase.cs (7 sites) and TwoFactorAuthenticator.cs (7 sites), and by the disable/regenerate handler bases.
      • -
      -

      TwoFactorSettings

      +

      SessionStampingTokenService

      -

      MMCA.Common.Application · MMCA.Common.Application.Auth.TwoFactor · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/TwoFactorSettings.cs:15 · Level 0 · class

      +

      MMCA.Common.Application · MMCA.Common.Application.Auth.Sessions · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:448 · Level 1 · class (private sealed, nested)

        -
      • What it is: the options class bound to Authentication:TwoFactor, controlling the TOTP parameters (issuer, digits, period, clock-skew window) and the recovery-code and secret sizing.
      • -
      • Depends on: nothing first-party; System.ComponentModel.DataAnnotations ([Range], [Required], [StringLength]).
      • -
      • Concept: the same config-bound settings shape as EmailConfirmationSettings; no new concept.
      • -
      • Walkthrough: Issuer (TwoFactorSettings.cs:275) defaults to "MMCA" and labels the account in the authenticator app. Digits (line 279) defaults to 6. PeriodSeconds (line 283) defaults to 30. VerificationWindowSteps (line 295) defaults to 1, and its remarks (lines 285-293) explain the trade-off: one step widens acceptance to roughly ninety seconds to tolerate clock skew, while a larger window multiplies how many codes are live at once for an attacker guessing. RecoveryCodeCount (line 299) defaults to 10. RecoveryCodeByteLength (line 306) defaults to 10 bytes (eighty bits). SecretByteLength (line 310) defaults to 20 bytes, the RFC 4226 recommendation.
      • -
      • Why it's built this way: every size and window is a documented security/usability trade-off rather than an arbitrary constant, per ADR-116.
      • -
      • Where it's used: read by TotpTwoFactorService.cs (2 sites) and registered in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs.
      • +
      • What it is: a pass-through ITokenService that appends the current refresh session's sid claim and, when a second factor verified this request, an mfa claim to every access token minted while it is armed, and behaves as the plain inner service the rest of the time. It is a private sealed class nested inside AuthSessionIssuer (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:448).
      • +
      • Depends on: ITokenService (the contract it implements and the inner instance it wraps, AuthSessionIssuer.cs:448), AuthClaimTypes for the sid and mfa claim names (:484, :489), and the BCL (System.Security.Claims, System.Globalization.CultureInfo).
      • +
      • Concept introduced: a decorator used to make a new claim additive. [Rubric §2, Design Patterns] assesses idiomatic pattern use, and this is the Decorator pattern applied to a very specific compatibility problem. Access tokens need to name the session they belong to (and, when relevant, the second factor that satisfied a step-up), but the claim set is produced by the app's own CreateAccessToken hook. The obvious fix, adding parameters to that hook, is a compile break in every consumer for claims the app has no decision to make about; the type's own remarks say exactly this (AuthSessionIssuer.cs:443-446). Wrapping the token service instead means the issuer arms the wrapper around the hook call and the claims appear in tokens minted by subclasses that were never edited. [Rubric §15, Best Practices & Code Quality] is the payoff: an additive protocol change with a zero-line consumer diff, twice over.
      • +
      • Walkthrough: a primary constructor takes the inner service (AuthSessionIssuer.cs:448).
          +
        • Guid? CurrentSessionId { get; set; } (:451) is the session arming switch: a session id stamps, null mints unchanged.
        • +
        • string? CurrentMultiFactorMethod { get; set; } (:457) is the second-factor arming switch, set only when a second factor really verified for this request. The remarks on the arming method explain why plain mutable properties are safe here (:126-129): the issuer is resolved per request (scoped, like the store it saves through) and one request mints one token at a time.
        • +
        • AccessTokenLifetime (:460) and RefreshTokenLifetime (:463) forward straight to inner, so the lifetime callers read is still the JWT settings' value.
        • +
        • GenerateAccessToken(...) (:466-493) is the only member with behavior. When neither switch is armed it delegates verbatim (:473-476). Otherwise it copies the app's additionalClaims into a new List<Claim> (:478, so the caller's sequence is never mutated), then independently appends AuthClaimTypes.SessionId formatted as sessionId.ToString("D", CultureInfo.InvariantCulture) when CurrentSessionId is set (:480-485, the "D" format being the canonical hyphenated Guid form ClaimsPrincipalExtensions.FindSessionId parses back, see ClaimsPrincipalExtensions) and AuthClaimTypes.MultiFactor set to the raw method string when CurrentMultiFactorMethod is set (:487-490), before delegating with the extended list (:492). The two claims are independent: a token can carry either, both, or neither.
        • +
        • GenerateRefreshToken() (:496) and GetPrincipalFromExpiredToken(string token) (:499-500) are plain forwards.
        -

        ITwoFactorService

        -
        -

        MMCA.Common.Application · MMCA.Common.Application.Auth.TwoFactor · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/ITwoFactorService.cs:16 · Level 1 · interface

        -
        -
          -
        • What it is: the TOTP/authenticator-app abstraction: mints secrets, builds the provisioning URI, verifies codes, and generates/hashes/matches recovery codes.
        • -
        • Depends on: RecoveryCodeSet (return type of GenerateRecoveryCodes).
        • -
        • Concept introduced: a service interface that isolates the cryptographic mechanics of TOTP (RFC 6238-style time-based codes) from the account-state orchestration that lives in ITwoFactorAuthenticator. [Rubric §1, SOLID] (assesses single-responsibility separation): this interface only knows about codes and secrets, never about a user account or a store.
        • -
        • Walkthrough: GenerateSecret() (ITwoFactorService.cs:337) mints a fresh Base32 secret. BuildProvisioningUri(secret, accountName) (line 352) builds the otpauth://totp/... URI an authenticator app scans as a QR code; it is deliberately returned as string rather than System.Uri (the [SuppressMessage] at lines 348-351 explains that round-tripping through Uri would re-normalize the percent-encoding the app parses). VerifyCode(secret, code) (line 361) checks a code within the configured window. GenerateRecoveryCodes() (line 368) returns a fresh RecoveryCodeSet. HashRecoveryCode(code) (line 376) hashes one plaintext code the way the store keeps it. TryMatchRecoveryCode(code, storedHashes, out matchedHash) (line 385) compares in fixed time to avoid a timing side-channel on which stored hash matched.
        • -
        • Why it's built this way: keeping code verification, secret generation, and recovery-code hashing behind one narrow interface means ITwoFactorAuthenticator and the handler bases never touch a raw cryptographic primitive directly.
        • -
        • Where it's used: implemented by TotpTwoFactorService (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TotpTwoFactorService.cs), consumed by the enrollment/regeneration handler bases in MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/TwoFactor/ and by TwoFactorAuthenticator.cs, and registered in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs.
        • + +
        • Why it's built this way: putting the stamping behind an ITokenService rather than inside the caller's own method keeps the app hook's signature and semantics untouched while still guaranteeing both claims on the tokens the framework's own flows mint. The escape hatch still applies: an app that mints from its own injected ITokenService reference produces a valid token with neither claim, and can restore them by overriding CreateAccessTokenForSession (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:469-470).
        • +
        • Where it's used: constructed once per issuer instance (AuthSessionIssuer.cs:50) and exposed as IAuthSessionIssuer.TokenService (:53), which AuthenticationServiceBase<TUser> surfaces to subclasses as its protected TokenService property (AuthenticationServiceBase.cs:98, whose remarks at :88-97 state that minting through the property is what puts sid on the token). Both switches are armed and disarmed by AuthSessionIssuer.MintForSession (AuthSessionIssuer.cs:130-145, with the finally at :140-144 guaranteeing both disarm even when the hook throws), which the base calls from CreateAccessTokenForSession with its _multiFactorMethod field (AuthenticationServiceBase.cs:469-470).

        LayeredPermissionRegistry

        @@ -2823,7 +3112,7 @@

        LayeredPermissionRegistry

      • What it is: an IPermissionRegistry decorator that unions the compiled-into-code permission set with the stored (database-granted) set, so a role's effective permissions are "compiled OR granted", never a replacement of one by the other.
      • Depends on: IPermissionRegistry (the inner, compiled registry), IPermissionGrantCache (the stored-grant cache).
      • Concept introduced: the decorator pattern applied to a registry lookup. [Rubric §2, Design Patterns] (assesses whether a pattern is used idiomatically and solves a real problem): LayeredPermissionRegistry wraps inner without changing its contract, adding a second data source transparently to every caller of IPermissionRegistry.
      • -
      • Walkthrough: the type is a primary-constructor class taking inner and grants (LayeredPermissionRegistry.cs:406-408). GetPermissions(role) (lines 411-428) reads both layers; when the stored set is empty it returns the compiled set as-is (no allocation), and only when both layers contribute does it materialize a HashSet<string> union, per the comment at lines 421-423: this call is a reporting path (administration surface, diagnostics), not the hot authorization path. HasPermission(roles, permission) (lines 431-442) is that hot path: it short-circuits on the compiled registry first (inner.HasPermission), and only falls through to a per-role stored-grant lookup (grants.GetPermissions(role).Contains(permission)) when the compiled check fails; the caller's roles sequence is materialized once (line 438) because both passes would otherwise enumerate it twice.
      • +
      • Walkthrough: the type is a primary-constructor class taking inner and grants (LayeredPermissionRegistry.cs:30-32). GetPermissions(role) (lines 35-52) reads both layers; when the stored set is empty it returns the compiled set as-is (no allocation), and only when both layers contribute does it materialize a HashSet<string> union, per the comment at lines 45-47: this call is a reporting path (administration surface, diagnostics), not the hot authorization path. HasPermission(roles, permission) (lines 55-66) is that hot path: it short-circuits on the compiled registry first (inner.HasPermission), and only falls through to a per-role stored-grant lookup (grants.GetPermissions(role).Contains(permission)) when the compiled check fails; the caller's roles sequence is materialized once (line 62) because both passes would otherwise enumerate it twice.
      • Why it's built this way: separating the always-cheap compiled check from the stored-grant fallback keeps the common case (a permission granted in code) allocation-free and single-pass, per ADR-116, which records the opt-in database-grant layer as additive on top of the existing compiled registry.
      • Where it's used: registered as the IPermissionRegistry implementation in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs (3 sites) when database grants are enabled; exercised directly in MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Auth/LayeredPermissionRegistryTests.cs.
      @@ -2835,21 +3124,31 @@

      EmailConfirmationErrors

    641. What it is: the static Error-factory class for the email-confirmation flow: one factory for a sign-in refused because the address is unconfirmed, one for every failing token redemption.
    642. Depends on: Error (the Result-pattern error type taught in the primer).
    643. Concept: the same named-code-plus-factory-method convention used by every *Errors class in this codebase (see AuthErrorCodes for the general shape); no new concept here.
    644. -
    645. Walkthrough: EmailNotConfirmedCode (EmailConfirmationErrors.cs:466) and InvalidTokenCode (line 469) are the two error codes. EmailNotConfirmed(source) (lines 479-482) returns Error.Unauthorized, distinct from Auth.InvalidCredentials so a UI can offer to resend the link. InvalidToken(source) (lines 487-490) is the single rejection every failing redemption (unknown, expired, mismatched, or attempt-capped token) collapses to.
    646. +
    647. Walkthrough: EmailNotConfirmedCode (EmailConfirmationErrors.cs:12) and InvalidTokenCode (line 15) are the two error codes. EmailNotConfirmed(source) (lines 25-28) returns Error.Unauthorized, distinct from Auth.InvalidCredentials so a UI can offer to resend the link. InvalidToken(source) (lines 33-36) is the single rejection every failing redemption (unknown, expired, mismatched, or attempt-capped token) collapses to.
    648. Why it's built this way: the doc comment on EmailNotConfirmed (lines 474-478) explains it is reachable only after the password has already been proved, so the message can safely tell the account owner what is wrong rather than help an address sweeper enumerate valid accounts; InvalidToken collapsing every failure mode to one message denies an attacker any signal about which reason a redemption failed for.
    649. Where it's used: raised by ConfirmEmailHandlerBase.cs (2 sites) and AuthenticationServiceBase.cs, and by EmailConfirmationTokenService.cs; asserted against in EmailConfirmationHandlerBaseTests.cs, EmailConfirmationTokenServiceTests.cs, and ADC's ConfirmEmailHandlerTests.cs.
    650. -

      TwoFactorErrors

      +

      IAuthSessionIssuer

      -

      MMCA.Common.Application · MMCA.Common.Application.Auth.TwoFactor · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/TwoFactorErrors.cs:15 · Level 2 · class

      +

      MMCA.Common.Application · MMCA.Common.Application.Auth.Sessions · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/IAuthSessionIssuer.cs:26 · Level 3 · interface

        -
      • What it is: the static Error-factory class for the two-factor flow: challenge-required, challenge-invalid, not-enrolled, and enrollment-missing.
      • -
      • Depends on: Error.
      • -
      • Concept: same convention as EmailConfirmationErrors; no new concept.
      • -
      • Walkthrough: four codes (TwoFactorErrors.cs:514,517,520,523). TwoFactorRequired(source) (lines 528-531) is Error.Unauthorized, asking the caller to supply a code or a recovery code. TwoFactorInvalid(source) (lines 541-544) is also Error.Unauthorized; its remarks (lines 536-540) note it is deliberately the same message for a wrong TOTP code and a wrong recovery code, since telling them apart would leak whether a presented value was recovery-code shaped. TwoFactorNotEnrolled(source) (lines 549-552) and TwoFactorEnrollmentMissing(source) (lines 557-560) are both Error.Conflict, for an action attempted on an account with no active factor and on an enrollment that was never started, respectively.
      • -
      • Why it's built this way: Conflict versus Unauthorized is used to distinguish "your account state does not support this action" from "your credentials did not verify", matching the semantics the rest of the auth surface uses for those two error kinds.
      • -
      • Where it's used: raised by ConfirmTwoFactorEnrollmentHandlerBase.cs (2 sites), TwoFactorAuthenticator.cs (2 sites), DisableTwoFactorHandlerBase.cs, and RegenerateRecoveryCodesHandlerBase.cs; asserted in TwoFactorHandlerBaseTests.cs, AuthenticationServiceIdentityCompletionsTests.cs, and TwoFactorAuthenticatorTests.cs.
      • +
      • What it is: the contract that issues, rotates and revokes the access/refresh token pair behind a signed-in device. Its summary (IAuthSessionIssuer.cs:7-12) lists what it owns: the multi-device RefreshSession rows (hashed at rest), BR-205 rotation with BR-206 reuse detection, the per-user live-session cap, and the sid/mfa claims stamped on the access token minted for a session.
      • +
      • Depends on: ITokenService, AuthenticationResponse, RefreshSessionSummaryResponse, Result/Result<T> (the Result pattern taught in the primer), and the UserIdentifierType alias.
      • +
      • Concept introduced: splitting "who is signed in" from "what they are handed". [Rubric §1, SOLID] assesses whether each type has one reason to change. The remarks (IAuthSessionIssuer.cs:15-19) draw the line explicitly: credentials, lockout, the second factor and the app's own gates stay with AuthenticationServiceBase<TUser>, which calls the issuer only once a caller has been proved, and the base's own summary states the same split from the other side (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:40-43: "This class decides who is signed in; IAuthSessionIssuer decides what they are handed"). The access token's claim set still belongs to the app, so the issuer takes it as a Func<Guid, string> mintAccessToken callback: the session id the token must carry exists only once the session has been opened or rotated. [Rubric §11, Security] assesses credential lifecycle handling; the rotation and reuse rules are stated on the contract itself, not left to an implementation.
      • +
      • Walkthrough:
          +
        • TokenService (IAuthSessionIssuer.cs:34): the ITokenService an app mints through. While the issuer is minting for a session it appends sid (and mfa when a second factor verified); at any other time it is the plain registered service (doc at :29-32).
        • +
        • IssueAsync(userId, mintAccessToken, ipAddress, userAgent, ct) (:46): opens a new session, evicting the oldest live one when the per-user cap is full, persists it and returns the pair; the user's other sessions are untouched (:37-38).
        • +
        • RotateAsync(userId, refreshToken, mintAccessToken, ipAddress, userAgent, ct) (:67): BR-205 rotation. An unknown or expired token fails alone; an already-revoked token, or one a concurrent request rotated first, is the BR-206 reuse signal and revokes every live session the user holds. Every rejection carries the same Auth.InvalidRefreshToken error (:54-58).
        • +
        • MintForSession(sessionId, multiFactorMethod, mintAccessToken) (:83): runs the callback with TokenService armed to stamp sid and, when not null, mfa.
        • +
        • SignOutAsync(userId, refreshToken, ct) (:94): revokes the one live session behind the presented token when it belongs to the user; when the token is absent or identifies no live session of this user, every live session is revoked instead (:86-88).
        • +
        • SignOutEverywhereAsync(userId, ct) (:100): revokes every live session and saves.
        • +
        • ListActiveAsync(userId, currentSessionId, ct) (:110): live sessions newest first, with expired-but-unrevoked rows left out because a device list must not offer a device that can no longer authenticate (:103-104).
        • +
        • RevokeSessionAsync(userId, sessionId, ct) (:124): another account's session id and a never-existing id both answer Auth.SessionNotFound; an already-revoked session is a success that writes nothing (:116-118).
        • +
        +
      • +
      • Why it's built this way: the remarks (IAuthSessionIssuer.cs:22-23) give the lifetime rule: scoped, like the session store it writes through, so a sign-in and its session insert share the request's unit of work. Keeping the session rules behind one interface means the authentication workflow "never touches a session row" (AuthenticationServiceBase.cs:43). The multi-device session model is recorded in ADR-097.
      • +
      • Where it's used: implemented by AuthSessionIssuer; registered TryAddScoped in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:176; injected into AuthenticationServiceBase<TUser> (AuthenticationServiceBase.cs:68), which delegates to it from TokenService (:98), RefreshTokenAsync (:337), sign-out (:363, :379), session listing (:391) and revocation (:406), IssueTokensAsync (:425) and CreateAccessTokenForSession (:470). ADC's AuthenticationService takes it as a constructor argument and passes it to the base (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:56,63).

      IEmailConfirmationTokenService

      @@ -2859,45 +3158,83 @@

      IEmailConfirmationTokenService

    651. What it is: the token-lifecycle abstraction for email confirmation: issue a token for an address, validate and consume one presented back.
    652. Depends on: UserIdentifierType (the module's identifier-type alias), Result<T>.
    653. Concept: an issue/validate-and-consume token contract, mirroring the shape IPasswordResetTokenService uses elsewhere in this group; no new concept.
    654. -
    655. Walkthrough: IssueAsync(email, userId, cancellationToken) (IEmailConfirmationTokenService.cs:594) replaces any token already outstanding for that address, so there is one active token per address, and fails only when the per-address request throttle (EmailConfirmationSettings.MaxRequestsPerEmail/RequestWindowMinutes) has been exceeded. ValidateAndConsumeAsync(email, token, cancellationToken) (line 607) validates against the outstanding token and consumes it on success so it never redeems twice, collapsing unknown/expired/mismatched/attempt-capped failures to the single EmailConfirmationErrors.InvalidToken error.
    656. +
    657. Walkthrough: IssueAsync(email, userId, cancellationToken) (IEmailConfirmationTokenService.cs:28) replaces any token already outstanding for that address, so there is one active token per address, and fails only when the per-address request throttle (EmailConfirmationSettings.MaxRequestsPerEmail/RequestWindowMinutes) has been exceeded. ValidateAndConsumeAsync(email, token, cancellationToken) (line 41) validates against the outstanding token and consumes it on success so it never redeems twice, collapsing unknown/expired/mismatched/attempt-capped failures to the single EmailConfirmationErrors.InvalidToken error.
    658. Why it's built this way: consuming on success is what makes redemption single-use; collapsing every failure reason to one error, per its own doc comment (lines 603-606), denies an attacker signal about why a guess failed.
    659. Where it's used: implemented by EmailConfirmationTokenService (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs), consumed by SendEmailConfirmationHandlerBase.cs and ConfirmEmailHandlerBase.cs, and by ADC's SendEmailConfirmationHandler.cs/ConfirmEmailHandler.cs; registered in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs.
    660. -

      ITwoFactorAuthenticator

      +

      IPermissionGrantStore

      -

      MMCA.Common.Application · MMCA.Common.Application.Auth.TwoFactor · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/ITwoFactorAuthenticator.cs:24 · Level 3 · interface

      +

      MMCA.Common.Application · MMCA.Common.Application.Auth.Permissions · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Permissions/IPermissionGrantStore.cs:16 · Level 4 · interface

        -
      • What it is: the account-level orchestration for the second-factor challenge at sign-in, one level above ITwoFactorService's pure cryptographic operations.
      • -
      • Depends on: UserIdentifierType, TwoFactorOutcome, TwoFactorErrors, Result<T>.
      • -
      • Concept: the service-orchestrates-over-a-crypto-primitive layering also seen with IEmailConfirmationTokenService versus its store; no new concept.
      • -
      • Walkthrough: ChallengeAsync(userId, code, cancellationToken) (ITwoFactorAuthenticator.cs:644-647) returns TwoFactorOutcome.NotEnrolled when the account has no active second factor (sign-in continues unchanged, no mfa claim), the verified outcome when a code satisfies the challenge, or a failure: TwoFactorErrors.TwoFactorRequiredCode when a code was needed and none arrived, TwoFactorErrors.TwoFactorInvalidCode when one arrived and did not verify (doc comment lines 636-643).
      • -
      • Why it's built this way: returning an outcome enum rather than a bare boolean lets AuthenticationServiceBase distinguish "no second factor configured" from "verified", which matters for whether it stamps an mfa claim on the issued token.
      • -
      • Where it's used: implemented by TwoFactorAuthenticator (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs), called from AuthenticationServiceBase.cs (2 sites) and registered in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs.
      • +
      • What it is: the persistence abstraction behind the stored-grant layer: read every grant, read one role's grants, grant a permission, revoke a permission.
      • +
      • Depends on: PermissionGrant (the persisted grant row), Result.
      • +
      • Concept: an idempotent grant/revoke pair, so a click repeated by an administration UI is not an error; no new concept beyond that idempotency guarantee.
      • +
      • Walkthrough: GetAllAsync(cancellationToken) (IPermissionGrantStore.cs:23) reads every stored row, used to build the in-memory snapshot the authorization path reads. GetPermissionsAsync(role, cancellationToken) (line 31) reads one role's stored permissions for the administration surface. GrantAsync(role, permission, grantedBy, cancellationToken) (lines 42-46) grants a permission, succeeding and writing nothing when the role already has it stored (doc comment lines 33-36). RevokeAsync(role, permission, cancellationToken) (line 60) removes a stored grant, also idempotent; its remarks (lines 51-55) are explicit that this removes only the STORED grant, never a compiled-into-code one, which is exactly what keeps a data edit from disabling an endpoint the code guarantees.
      • +
      • Why it's built this way: the "stored grant is additive, never subtractive over compiled permissions" rule enforced here is the same invariant LayeredPermissionRegistry.HasPermission implements: the compiled check always wins first, and revoking a stored grant can never take away a compiled-in permission.
      • +
      • Where it's used: implemented by EFPermissionGrantStore (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/EFPermissionGrantStore.cs), read by PermissionGrantCache.cs (2 sites) to build the IPermissionGrantCache snapshot, called by IRoleAdministrationService's implementation StoredPermissionRoleAdministrationService.cs (2 sites), and registered in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs.
      -

      ITwoFactorStore

      +

      AuthSessionIssuer

      -

      MMCA.Common.Application · MMCA.Common.Application.Auth.TwoFactor · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/ITwoFactorStore.cs:24 · Level 3 · interface

      +

      MMCA.Common.Application · MMCA.Common.Application.Auth.Sessions · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:39 · Level 6 · class (sealed)

        -
      • What it is: the persistence abstraction for one account's two-factor state: enrollment lifecycle, recovery-code replacement, and single recovery-code consumption.
      • -
      • Depends on: UserIdentifierType, ITwoFactorUserState (the read shape, taught elsewhere in this group), Result.
      • -
      • Concept: a two-phase enrollment (start, then complete) so an abandoned setup cannot lock an account out; the same two-phase shape a reader has already seen in other enrollment-style flows in this group.
      • -
      • Walkthrough: GetAsync(userId, cancellationToken) (ITwoFactorStore.cs:678) returns null only when the account itself does not exist; an account that never enrolled still returns a state with IsTwoFactorEnabled = false. StartEnrollmentAsync(userId, secret, cancellationToken) (line 688) stores a freshly minted secret WITHOUT activating the factor, per its remarks (lines 681-683), so an abandoned enrollment cannot lock the user out. CompleteEnrollmentAsync(userId, recoveryCodeHashes, cancellationToken) (lines 698-701) activates the factor and stores the first recovery-code hashes, called only after a code from the stored secret has verified. DisableAsync(userId, cancellationToken) (line 710) turns the factor off and clears the secret and recovery codes, so a later re-enrollment starts from a fresh secret. ReplaceRecoveryCodesAsync(userId, recoveryCodeHashes, cancellationToken) (lines 720-723) replaces the whole set, never appends, since regeneration exists to invalidate a list the user believes compromised. ConsumeRecoveryCodeAsync(userId, recoveryCodeHash, cancellationToken) (lines 737-740) removes one hash to spend it, and its remarks (lines 728-731) flag this as security-critical: it must persist before the sign-in it authorized is answered, or the same code could sign in twice.
      • -
      • Why it's built this way: splitting StartEnrollmentAsync/CompleteEnrollmentAsync prevents a half-finished enrollment from ever becoming an active, un-verified second factor.
      • -
      • Where it's used: implemented and called by TwoFactorAuthenticator.cs (2 sites) through the enrollment/disable/regenerate handler bases in MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/TwoFactor/, and registered in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs.
      • +
      • What it is: the framework's IAuthSessionIssuer: multi-device refresh sessions, hashed at rest (AuthSessionIssuer.cs:12-14). Every issue opens its own RefreshSession, so signing in on a second device leaves the first one signed in.
      • +
      • Depends on: ITokenService (mints both tokens), IRefreshSessionStore (the session rows), IOptions<RefreshSessionSettings> (the per-user cap), TimeProvider (every session instant), all as primary-constructor parameters (AuthSessionIssuer.cs:39-43); RefreshSession, AuthenticationResponse, RefreshSessionSummaryResponse and Error/Result from the primer. Its two private helpers are IssuedSession and SessionStampingTokenService.
      • +
      • Concept introduced: refresh-token rotation with family revocation on reuse. [Rubric §11, Security] assesses how bearer credentials are issued, stored and revoked. The class remarks (AuthSessionIssuer.cs:17-26) state the model: the store holds only RefreshSession.HashToken digests; rotation revokes the presented session and links it to its successor; presenting an already-rotated token lands on that revoked row, which is the reuse signal that revokes the user's whole live family (BR-206); two requests presenting the same live token at once are covered by the same rule because the rotation is claimed atomically through IRefreshSessionStore.TryRotateAsync; and an expired session is not a reuse signal. The practical effect: a stolen-and-replayed token costs the attacker the session and costs the user only a re-sign-in.
      • +
      • Walkthrough:
          +
        • Lifetimes (:55-59): AccessTokenLifetime and RefreshTokenLifetime read the token service and fall back to 15 minutes and 7 days when the value is non-positive (a test double or a misconfigured host), so the expiry reported to clients matches the JWT's actual exp (:29-32).
        • +
        • IssueAsync (:62-87): opens the session first (OpenSessionAsync, :75), saves (:81), and only then calls mintAccessToken(opened.Value!.SessionId) (:84); the comment at :73-74 says why: the token carries the session's id in sid, and a session has an id only once created.
        • +
        • OpenSessionAsync (:312-338): generates a refresh token (:319), creates the row through RefreshSession.Create with now + RefreshTokenLifetime (:320-326), runs EnforceSessionCapAsync (:334), stages the insert without saving (:335) and returns an IssuedSession (:337).
        • +
        • EnforceSessionCapAsync (:416-429): reads the user's un-revoked rows, keeps the ones active now, orders oldest first (CreatedAt, then Id), and revokes from the front with ReasonSessionCap until there is room for one more (:425-428). Its summary (:407-415) records the default cap of 10, the 1-1000 startup range, and that expired-but-unrevoked rows do not count and age out through RefreshSessionCleanupService.
        • +
        • RotateAsync (:90-123): ResolveRotatableSessionAsync (:101), then RotateSessionAsync (:109), then the mint with the successor's id (:120), so the client's current-device marker follows the rotation (comment :116-118).
        • +
        • ResolveRotatableSessionAsync (:273-305): a blank token, an unknown hash, or a row of another user fails alone (:279-291); a revoked row revokes every live session with ReasonReuseDetected and saves before failing (:293-300); an expired row fails alone (:302-304). Its summary (:263-272) explains why the unknown-hash case must not revoke the family: otherwise anyone holding one of this user's expired access tokens could sign them out everywhere by posting a random token.
        • +
        • RotateSessionAsync (:350-391): builds the successor, then asks the store to claim the rotation via TryRotateAsync (:374-376). A lost claim means a concurrent request spent this token first, which is indistinguishable from a replay and gets the replay answer: family revoked, saved, Auth.InvalidRefreshToken (:378-388). Rotation replaces one session with one, so the cap is not re-evaluated (:341).
        • +
        • InvalidRefreshTokenError (:257-261): the single Error.Unauthorized("Auth.InvalidRefreshToken", ...) every failing branch returns, so a caller cannot tell unknown from expired from replayed (:253-256).
        • +
        • MintForSession (:130-145): arms the nested SessionStampingTokenService with the session id and second-factor method, runs the callback, and disarms in a finally.
        • +
        • SignOutAsync (:148-174): revokes only a live session of this user found by token hash (:162-169); anything else degrades to revoking every live session rather than reporting success for a revocation that reached nothing (comment :158-161, fallback :172-173). SignOutEverywhereAsync (:177-182) always revokes everything.
        • +
        • ListActiveAsync (:191-213): one query for un-revoked rows, then an in-memory filter to the active ones, newest first, flagging the caller's own session (:202-211).
        • +
        • RevokeSessionAsync (:227-251): the ownership check is the user-scoped FindByIdAsync query (:232), so another account's id and a missing id produce the same Auth.SessionNotFound (:235-239); an already-revoked row returns success without writing (:242-245), for the reasons in its remarks (:216-226).
        -

        IPermissionGrantStore

        +
      • +
      • Why it's built this way: every timestamp comes from the injected TimeProvider, so rotation and expiry are testable without a clock, and every user-visible failure on the refresh path collapses to one error so the endpoint leaks nothing about which rule fired. Treating a lost concurrent rotation as a replay keeps one rule for one observable situation. The multi-device model, rotation and reuse detection are recorded in ADR-097.
      • +
      • Where it's used: registered as the scoped IAuthSessionIssuer in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:176 (the comment at :174-175 says the base delegates every session decision to it); consumed through that interface by AuthenticationServiceBase<TUser>. Constructed directly in AuthenticationServiceBaseTests.cs, AuthenticationServiceIdentityCompletionsTests.cs and RefreshSessionManagementTests.cs (MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Auth/) and in ADC's AuthenticationServiceTests.cs (MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/).
      • +
      +

      RecoveryCodeSet

      -

      MMCA.Common.Application · MMCA.Common.Application.Auth.Permissions · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Permissions/IPermissionGrantStore.cs:16 · Level 4 · interface

      +

      MMCA.Common.Application · MMCA.Common.Application.Auth.TwoFactor · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/ITwoFactorService.cs:97 · Level 0 · record

        -
      • What it is: the persistence abstraction behind the stored-grant layer: read every grant, read one role's grants, grant a permission, revoke a permission.
      • -
      • Depends on: PermissionGrant (the persisted grant row), Result.
      • -
      • Concept: an idempotent grant/revoke pair, so a click repeated by an administration UI is not an error; no new concept beyond that idempotency guarantee.
      • -
      • Walkthrough: GetAllAsync(cancellationToken) (IPermissionGrantStore.cs:768) reads every stored row, used to build the in-memory snapshot the authorization path reads. GetPermissionsAsync(role, cancellationToken) (line 776) reads one role's stored permissions for the administration surface. GrantAsync(role, permission, grantedBy, cancellationToken) (lines 787-791) grants a permission, succeeding and writing nothing when the role already has it stored (doc comment lines 778-780). RevokeAsync(role, permission, cancellationToken) (line 805) removes a stored grant, also idempotent; its remarks (lines 796-800) are explicit that this removes only the STORED grant, never a compiled-into-code one, which is exactly what keeps a data edit from disabling an endpoint the code guarantees.
      • -
      • Why it's built this way: the "stored grant is additive, never subtractive over compiled permissions" rule enforced here is the same invariant LayeredPermissionRegistry.HasPermission implements: the compiled check always wins first, and revoking a stored grant can never take away a compiled-in permission.
      • -
      • Where it's used: implemented by EFPermissionGrantStore (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/EFPermissionGrantStore.cs), read by PermissionGrantCache.cs (2 sites) to build the IPermissionGrantCache snapshot, called by IRoleAdministrationService's implementation StoredPermissionRoleAdministrationService.cs (2 sites), and registered in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs.
      • +
      • What it is: a two-field record pairing a freshly generated set of plaintext recovery codes with their stored hashes.
      • +
      • Depends on: nothing first-party.
      • +
      • Concept: a plaintext/hash pair returned once, at generation time only, so the plaintext never has to be persisted or re-derived.
      • +
      • Walkthrough: Codes (ITwoFactorService.cs:98) is the plaintext shown to the user once; Hashes (line 86) is what the store keeps.
      • +
      • Why it's built this way: keeping the plaintext out of any store means a database compromise cannot recover usable recovery codes, only their hashes.
      • +
      • Where it's used: returned by ITwoFactorService.GenerateRecoveryCodes(), consumed by TotpTwoFactorService.cs and exercised in MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs.
      • +
      +

      TwoFactorOutcome

      +
      +

      MMCA.Common.Application · MMCA.Common.Application.Auth.TwoFactor · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/ITwoFactorAuthenticator.cs:47 · Level 0 · enum

      +
      +
        +
      • What it is: the result of a second-factor challenge: NotEnrolled = 0, VerifiedTotp = 1, VerifiedRecoveryCode = 2 (ITwoFactorAuthenticator.cs:50,53,56).
      • +
      • Depends on: nothing first-party.
      • +
      • Concept: same explicitly-numbered, wire-stable outcome-enum shape used throughout the domain event taxonomy (see DomainEntityState in group-02); NotEnrolled = 0 is the default/no-op value, matching that convention.
      • +
      • Walkthrough: three values; NotEnrolled means the account has no active second factor so nothing was challenged, VerifiedTotp means a time-based code from the authenticator app verified, VerifiedRecoveryCode means a single-use recovery code verified and was spent.
      • +
      • Why it's built this way: collapsing the challenge result to one enum lets AuthenticationServiceBase decide, in one switch, whether to continue sign-in unchanged (NotEnrolled) or stamp an mfa claim on the issued token (the two verified cases).
      • +
      • Where it's used: returned by ITwoFactorAuthenticator.ChallengeAsync, read by AuthenticationServiceBase.cs (7 sites) and TwoFactorAuthenticator.cs (7 sites), and by the disable/regenerate handler bases.
      • +
      +

      TwoFactorSettings

      +
      +

      MMCA.Common.Application · MMCA.Common.Application.Auth.TwoFactor · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/TwoFactorSettings.cs:15 · Level 0 · class

      +
      +
        +
      • What it is: the options class bound to Authentication:TwoFactor, controlling the TOTP parameters (issuer, digits, period, clock-skew window) and the recovery-code and secret sizing.
      • +
      • Depends on: nothing first-party; System.ComponentModel.DataAnnotations ([Range], [Required], [StringLength]).
      • +
      • Concept: the same config-bound settings shape as EmailConfirmationSettings; no new concept.
      • +
      • Walkthrough: Issuer (TwoFactorSettings.cs:26) defaults to "MMCA" and labels the account in the authenticator app. Digits (line 30) defaults to 6. PeriodSeconds (line 34) defaults to 30. VerificationWindowSteps (line 46) defaults to 1, and its remarks (lines 40-44) explain the trade-off: one step widens acceptance to roughly ninety seconds to tolerate clock skew, while a larger window multiplies how many codes are live at once for an attacker guessing. RecoveryCodeCount (line 50) defaults to 10. RecoveryCodeByteLength (line 57) defaults to 10 bytes (eighty bits). SecretByteLength (line 61) defaults to 20 bytes, the RFC 4226 recommendation.
      • +
      • Why it's built this way: every size and window is a documented security/usability trade-off rather than an arbitrary constant, per ADR-116.
      • +
      • Where it's used: read by TotpTwoFactorService.cs (2 sites) and registered in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs.

      IPasswordHasher

      @@ -2909,7 +3246,7 @@

      IPasswordHasher

    661. Concept introduced: hash and salt kept apart. [Rubric §11, Security] assesses credential handling. Returning the hash and the salt as two distinct byte[] members (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Auth/IPasswordHasher.cs:11) rather than one concatenated blob keeps the storage contract explicit: the caller persists two columns, and VerifyPassword (:18) is unambiguous about what it re-derives and compares. Because the algorithm and its parameters live entirely behind this interface, they can be strengthened without touching a single Application handler (ADR-032 sets the current hashing policy, applied inside PasswordHasher).
    662. Walkthrough: (byte[] Hash, byte[] Salt) HashPassword(string password) (:11) returns a named value tuple the caller stores as two fields. bool VerifyPassword(string password, byte[] hash, byte[] salt) (:18) re-derives from the supplied salt and compares. The interface declares no iteration count, algorithm identifier, or format version: every one of those is the concrete's business.
    663. Why it's built this way: a two-method port is the [Rubric §1, SOLID] dependency-inversion story in miniature. Swapping the key-derivation function or raising the iteration count is an Infrastructure change, invisible to the register, login, and change-password use cases that only ever see this contract.
    664. -
    665. Where it's used: constructor-injected into AuthenticationServiceBase<TUser> (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:77), which calls VerifyPassword on the login path (:159) and HashPassword on registration (:210); into the shared ChangePasswordHandlerBase<TUser, TCommand>, which verifies the current password (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:79) before hashing the new one (:61); into ResetPasswordHandlerBase<TUser, TCommand>, which hashes the replacement after the token redeems (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:94); and into the per-app Identity services, handlers and seeders that derive from those, for example ADC's AuthenticationService (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:51), its ChangePasswordHandler (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ChangePassword/ChangePasswordHandler.cs:26) and its module seeder (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/IdentityModuleSeeder.cs:34, which needs the hasher because seed data carries plaintext credentials).
    666. +
    667. Where it's used: constructor-injected into AuthenticationServiceBase<TUser> (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:65), which calls VerifyPassword on the login path (:159) and HashPassword on registration (:210); into the shared ChangePasswordHandlerBase<TUser, TCommand>, which verifies the current password (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:95) before hashing the new one (:61); into ResetPasswordHandlerBase<TUser, TCommand>, which hashes the replacement after the token redeems (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:94); and into the per-app Identity services, handlers and seeders that derive from those, for example ADC's AuthenticationService (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:52), its ChangePasswordHandler (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ChangePassword/ChangePasswordHandler.cs:26) and its module seeder (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/IdentityModuleSeeder.cs:34, which needs the hasher because seed data carries plaintext credentials).
    668. ISoftDeletedUserValidator

      @@ -2930,9 +3267,70 @@

      ITokenService

    669. What it is: the token-minting port called by the login and refresh use cases. It builds a signed JWT access token from explicit identity facts, generates an opaque refresh token, publishes the two token lifetimes, and recovers the ClaimsPrincipal from an expired-but-validly-signed access token.
    670. Depends on: System.Security.Claims (BCL, :1) and the UserIdentifierType alias. Its Infrastructure adapter is TokenService, which signs with the RSA key surfaced by IJwksProvider; SessionStampingTokenService is a second, internal implementation that decorates the first.
    671. Concept introduced: token creation as an Infrastructure detail. [Rubric §3, Clean Architecture] assesses whether library-specific types stay out of the inner layers: the handlers call this contract and never see System.IdentityModel.Tokens.Jwt. GetPrincipalFromExpiredToken (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Auth/ITokenService.cs:48) is the linchpin of the refresh flow: it validates the signature while deliberately ignoring lifetime, so an expired access token can still identify the user whose tokens are being rotated, returning null when the token is invalid (:47).
    672. -
    673. Walkthrough: GenerateAccessToken(UserIdentifierType userId, string email, string role, string fullName, IEnumerable<Claim>? additionalClaims = null) (:17-22) takes the minimum claim set as typed parameters rather than a ready-made principal, with an escape hatch for module-specific claims. GenerateRefreshToken() (:26) returns a cryptographically random base64 string. Two default interface members publish the lifetimes: AccessTokenLifetime (:33, defaulting to 15 minutes) and RefreshTokenLifetime (:40, defaulting to 7 days), both documented as the BR-205 baseline. The comments at :28-32 and :35-39 explain the split: the real implementation derives both from the bound JWT settings, so the expiry reported to a client matches the token's actual exp, while the defaults keep hand-written test doubles on the baseline instead of forcing every double to implement two more members. That derivation is visible in the concrete: TimeSpan.FromMinutes(_jwtSettings.AccessTokenExpirationMinutes) and TimeSpan.FromDays(_jwtSettings.RefreshTokenExpirationDays) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:151 and :129). GetPrincipalFromExpiredToken(string token) (:48) closes the set.
    674. +
    675. Walkthrough: GenerateAccessToken(UserIdentifierType userId, string email, string role, string fullName, IEnumerable<Claim>? additionalClaims = null) (:17-22) takes the minimum claim set as typed parameters rather than a ready-made principal, with an escape hatch for module-specific claims. GenerateRefreshToken() (:26) returns a cryptographically random base64 string. Two default interface members publish the lifetimes: AccessTokenLifetime (:33, defaulting to 15 minutes) and RefreshTokenLifetime (:40, defaulting to 7 days), both documented as the BR-205 baseline. The comments at :28-32 and :35-39 explain the split: the real implementation derives both from the bound JWT settings, so the expiry reported to a client matches the token's actual exp, while the defaults keep hand-written test doubles on the baseline instead of forcing every double to implement two more members. That derivation is visible in the concrete: TimeSpan.FromMinutes(_jwtSettings.AccessTokenExpirationMinutes) and TimeSpan.FromDays(_jwtSettings.RefreshTokenExpirationDays) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:162 and :129). GetPrincipalFromExpiredToken(string token) (:48) closes the set.
    676. Why it's built this way: the explicit-parameter overload is a [Rubric §11, Security] guardrail. The token's contents are a deliberate list, not whatever claims happened to ride in on an inbound principal. Surfacing the lifetimes through the same port removes the duplication where a caller would hard-code an expiry that could drift from the signed exp. Note the consumer still guards: AuthenticationServiceBase<TUser> falls back to the same 15-minute and 7-day baselines when an implementation reports a non-positive lifetime (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:137-146).
    677. -
    678. Where it's used: injected into AuthenticationServiceBase<TUser> (AuthenticationServiceBase.cs:76), which reads the expired principal on refresh (:278), mints refresh tokens when opening and rotating sessions (:627, :667), and re-exposes a wrapped instance to subclasses through its TokenService property (:82). Each app's Identity service mints from that property, for example MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:153-154 (access token plus the speaker_id claim). The rotated pair produced here is what CookieSessionRefresher later exchanges on the browser's behalf.
    679. +
    680. Where it's used: injected into AuthenticationServiceBase<TUser> (AuthenticationServiceBase.cs:76), which reads the expired principal on refresh (:278), mints refresh tokens when opening and rotating sessions (:627, :667), and re-exposes a wrapped instance to subclasses through its TokenService property (:82). Each app's Identity service mints from that property, for example MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:149-150 (access token plus the speaker_id claim). The rotated pair produced here is what CookieSessionRefresher later exchanges on the browser's behalf.
    681. + +

      ITwoFactorService

      +
      +

      MMCA.Common.Application · MMCA.Common.Application.Auth.TwoFactor · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/ITwoFactorService.cs:16 · Level 1 · interface

      +
      +
        +
      • What it is: the TOTP/authenticator-app abstraction: mints secrets, builds the provisioning URI, verifies codes, and generates/hashes/matches recovery codes.
      • +
      • Depends on: RecoveryCodeSet (return type of GenerateRecoveryCodes).
      • +
      • Concept introduced: a service interface that isolates the cryptographic mechanics of TOTP (RFC 6238-style time-based codes) from the account-state orchestration that lives in ITwoFactorAuthenticator. [Rubric §1, SOLID] (assesses single-responsibility separation): this interface only knows about codes and secrets, never about a user account or a store.
      • +
      • Walkthrough: GenerateSecret() (ITwoFactorService.cs:22) mints a fresh Base32 secret. BuildProvisioningUri(secret, accountName) (line 37) builds the otpauth://totp/... URI an authenticator app scans as a QR code; it is deliberately returned as string rather than System.Uri (the [SuppressMessage] at lines 33-36 explains that round-tripping through Uri would re-normalize the percent-encoding the app parses). VerifyCode(secret, code) (line 46) checks a code within the configured number of time steps on each side of the current one. The overload VerifyCode(secret, code, out matchedStep) (line 59) verifies exactly the same way and also reports which time step matched (Unix seconds divided by the period, or 0 when nothing matched), so a caller that keeps per-account state can refuse a replay of a code it already accepted inside the same window. GenerateRecoveryCodes() (line 66) returns a fresh RecoveryCodeSet. HashRecoveryCode(code) (line 74) hashes one plaintext code the way the store keeps it. TryMatchRecoveryCode(code, storedHashes, out matchedHash) (line 83) compares in fixed time to avoid a timing side-channel on which stored hash matched.
      • +
      • Why it's built this way: keeping code verification, secret generation, and recovery-code hashing behind one narrow interface means ITwoFactorAuthenticator and the handler bases never touch a raw cryptographic primitive directly. Replay protection stays out of this interface too: the service only reports the matched step, and the stateful "accept each step once" rule lives in the caller.
      • +
      • Where it's used: implemented by TotpTwoFactorService (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TotpTwoFactorService.cs:61 for the step-reporting overload), consumed by the enrollment/regeneration handler bases in MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/TwoFactor/ and by TwoFactorAuthenticator, which calls the step-reporting overload (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs:65) and then rejects any step at or below the last one it cached for that user (TwoFactorAuthenticator.cs:106). Registered as a singleton in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:47.
      • +
      +

      TwoFactorErrors

      +
      +

      MMCA.Common.Application · MMCA.Common.Application.Auth.TwoFactor · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/TwoFactorErrors.cs:15 · Level 2 · class

      +
      +
        +
      • What it is: the static Error-factory class for the two-factor flow: challenge-required, challenge-invalid, not-enrolled, and enrollment-missing.
      • +
      • Depends on: Error.
      • +
      • Concept: same convention as EmailConfirmationErrors; no new concept.
      • +
      • Walkthrough: four codes (TwoFactorErrors.cs:18,21,24,27). TwoFactorRequired(source) (lines 32-35) is Error.Unauthorized, asking the caller to supply a code or a recovery code. TwoFactorInvalid(source) (lines 45-48) is also Error.Unauthorized; its remarks (lines 40-44) note it is deliberately the same message for a wrong TOTP code and a wrong recovery code, since telling them apart would leak whether a presented value was recovery-code shaped. TwoFactorNotEnrolled(source) (lines 53-56) and TwoFactorEnrollmentMissing(source) (lines 61-64) are both Error.Conflict, for an action attempted on an account with no active factor and on an enrollment that was never started, respectively.
      • +
      • Why it's built this way: Conflict versus Unauthorized is used to distinguish "your account state does not support this action" from "your credentials did not verify", matching the semantics the rest of the auth surface uses for those two error kinds.
      • +
      • Where it's used: raised by ConfirmTwoFactorEnrollmentHandlerBase.cs (2 sites), TwoFactorAuthenticator.cs (2 sites), DisableTwoFactorHandlerBase.cs, and RegenerateRecoveryCodesHandlerBase.cs; asserted in TwoFactorHandlerBaseTests.cs, AuthenticationServiceIdentityCompletionsTests.cs, and TwoFactorAuthenticatorTests.cs.
      • +
      +

      ITwoFactorAuthenticator

      +
      +

      MMCA.Common.Application · MMCA.Common.Application.Auth.TwoFactor · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/ITwoFactorAuthenticator.cs:24 · Level 3 · interface

      +
      +
        +
      • What it is: the account-level orchestration for the second-factor challenge at sign-in, one level above ITwoFactorService's pure cryptographic operations.
      • +
      • Depends on: UserIdentifierType, TwoFactorOutcome, TwoFactorErrors, Result<T>.
      • +
      • Concept: the service-orchestrates-over-a-crypto-primitive layering also seen with IEmailConfirmationTokenService versus its store; no new concept.
      • +
      • Walkthrough: ChallengeAsync(userId, code, cancellationToken) (ITwoFactorAuthenticator.cs:40-43) returns TwoFactorOutcome.NotEnrolled when the account has no active second factor (sign-in continues unchanged, no mfa claim), the verified outcome when a code satisfies the challenge, or a failure: TwoFactorErrors.TwoFactorRequiredCode when a code was needed and none arrived, TwoFactorErrors.TwoFactorInvalidCode when one arrived and did not verify (doc comment lines 32-39).
      • +
      • Why it's built this way: returning an outcome enum rather than a bare boolean lets AuthenticationServiceBase distinguish "no second factor configured" from "verified", which matters for whether it stamps an mfa claim on the issued token.
      • +
      • Where it's used: implemented by TwoFactorAuthenticator (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs), called from AuthenticationServiceBase.cs (2 sites) and registered in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs.
      • +
      +

      ITwoFactorStore

      +
      +

      MMCA.Common.Application · MMCA.Common.Application.Auth.TwoFactor · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/TwoFactor/ITwoFactorStore.cs:24 · Level 3 · interface

      +
      +
        +
      • What it is: the persistence abstraction for one account's two-factor state: enrollment lifecycle, recovery-code replacement, and single recovery-code consumption.
      • +
      • Depends on: UserIdentifierType, ITwoFactorUserState (the read shape, taught elsewhere in this group), Result.
      • +
      • Concept: a two-phase enrollment (start, then complete) so an abandoned setup cannot lock an account out; the same two-phase shape a reader has already seen in other enrollment-style flows in this group.
      • +
      • Walkthrough: GetAsync(userId, cancellationToken) (ITwoFactorStore.cs:34) returns null only when the account itself does not exist; an account that never enrolled still returns a state with IsTwoFactorEnabled = false. StartEnrollmentAsync(userId, secret, cancellationToken) (line 44) stores a freshly minted secret WITHOUT activating the factor, per its summary (lines 36-39), so an abandoned enrollment cannot lock the user out. CompleteEnrollmentAsync(userId, recoveryCodeHashes, cancellationToken) (lines 54-57) activates the factor and stores the first recovery-code hashes, called only after a code from the stored secret has verified. DisableAsync(userId, cancellationToken) (line 66) turns the factor off and clears the secret and recovery codes, so a later re-enrollment starts from a fresh secret. ReplaceRecoveryCodesAsync(userId, recoveryCodeHashes, cancellationToken) (lines 76-79) replaces the whole set, never appends, since regeneration exists to invalidate a list the user believes compromised. ConsumeRecoveryCodeAsync(userId, recoveryCodeHash, cancellationToken) (lines 93-96) removes one hash to spend it, and its remarks (lines 84-88) flag this as security-critical: it must persist before the sign-in it authorized is answered, or the same code could sign in twice.
      • +
      • Why it's built this way: splitting StartEnrollmentAsync/CompleteEnrollmentAsync prevents a half-finished enrollment from ever becoming an active, un-verified second factor.
      • +
      • Where it's used: implemented and called by TwoFactorAuthenticator.cs (2 sites) through the enrollment/disable/regenerate handler bases in MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/TwoFactor/, and registered in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs.
      • +
      +

      ICurrentUserService

      +
      +

      MMCA.Common.Application · MMCA.Common.Application.Interfaces.Infrastructure.Auth · MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Auth/ICurrentUserService.cs:9 · Level 8 · interface

      +
      +
        +
      • What it is: the Application layer's read-only window onto the authenticated caller: the raw ClaimsPrincipal, a strongly-typed UserId, the caller's first role, the full role set, a generic typed-claim reader, and a role-membership helper. It answers "who is calling?" without any handler ever touching HttpContext.
      • +
      • Depends on: System.Security.Claims and IParsable<T> (BCL, :1) plus the solution-wide UserIdentifierType alias (:15); see primer §2. Its adapter is CurrentUserService in Infrastructure.
      • +
      • Concept introduced: the caller-identity port with behavior on the interface. [Rubric §3, Clean Architecture] assesses whether inner layers stay free of transport types, and [Rubric §1, SOLID] (interface segregation) whether a contract exposes only what its clients need. A handler must know the caller to run ownership checks and to stamp audit fields, but it must not depend on IHttpContextAccessor, which would drag ASP.NET Core into the Application project. This interface is that inversion, and the adapter is the only place the accessor appears (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Context/CurrentUserService.cs:17, :25). What makes it worth studying is the use of default interface members: Roles (:45-64) and IsInRole (:88-89) ship real implementations on the contract, so every implementer and every hand-written test double inherits correct multi-role behavior instead of re-deriving it.
      • +
      • Walkthrough: ClaimsPrincipal User (:12) exposes the full principal for advanced inspection. UserIdentifierType? UserId (:15) is the typed identifier, nullable because an unauthenticated request has no user. string? Role (:22) is documented as the first role claim only, with the remarks at :18-21 steering callers to Roles or IsInRole for membership checks. Roles (:45-64) is the interesting member: it reads every role claim, accepting each claim type the JWT middleware may produce (ClaimTypes.Role when inbound claim mapping is on, or the raw role / roles claim when it is off, :50-53), falls back to a single-element list built from Role when the principal yields nothing (:62), and null-guards User even though the property is declared non-nullable (:49). The long remarks at :27-44 justify both accommodations from the nature of a default interface member: it runs against every implementation, including a hand-written double or a mock that stubs only Role, where reading claims alone would have reported no roles and silently turned an authorization check into a denial, and dereferencing a null principal would have turned it into a NullReferenceException. Claims win when present, so a genuine multi-role principal is still read in full. T? GetClaimValue<T>(string claimType) where T : struct, IParsable<T> (:73-74) parses a named claim into any parsable value type and returns null when the claim is missing or unparseable, which is how a module reads its own claim (the doc names speaker_id, :68) without Common ever knowing that claim exists. IsInRole(string roleName) (:88-89) is Roles.Any(role => string.Equals(role, roleName, StringComparison.OrdinalIgnoreCase)).
      • +
      • Why it's built this way: the remarks at :82-87 record the reasoning behind IsInRole checking every claim rather than comparing against Role. Comparing against the first role alone matched only whichever role happened to be listed first, which is latent today because tokens carry a single role, and would have surfaced silently as an authorization denial the moment a second role was added. Typing UserId as the per-app alias instead of a generic parameter keeps the interface concrete and easy to mock while staying correct for each app. [Rubric §11, Security] and [Rubric §15, Best Practices & Code Quality] both apply.
      • +
      • Where it's used: registered as scoped against CurrentUserService at MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:306. It supplies the Roles set the CQRS authorization decorators check permissions against (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/AuthorizationCommandDecorator.cs:32, and its query twin; see group 05); it is how audit fields get their actor, since DbContextFactory passes _currentUserService.UserId into every save (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:68, used at :248, :291, :330, :352 and :414); and it backs the ownership check in OwnerOrAdminFilter and the framework's account controllers.
      • +
      • Caveats / not-in-source: Role deliberately reports only the first role claim; treat it as a display value and use Roles or IsInRole for any decision.

      ITwoFactorUserState

      @@ -2963,7 +3361,7 @@

      IAuthUser

    682. Why it's built this way: keeping the contract in Domain and keeping it small is what makes the shared auth workflow reusable across Store and ADC (both User aggregates satisfy it) while each aggregate stays free to model everything else its own way. See ADR-032 for the password-material policy, ADR-004 for the dual-fetch auth model this contract feeds, and ADR-097 for the refresh-token move.
    683. -
    684. Where it's used: it is half the generic constraint on the shared login and registration workflow, where TUser : AuditableAggregateRootEntity<UserIdentifierType>, IAuthUser (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:85), which reads both properties on the login path (:159) and writes the pair on registration (:210). It is also the base of IPasswordChangeableUser, and the shape hand-written test doubles copy (MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:946).
    685. +
    686. Where it's used: it is half the generic constraint on the shared login and registration workflow, where TUser : AuditableAggregateRootEntity<UserIdentifierType>, IAuthUser (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:71), which reads both properties on the login path (:159) and writes the pair on registration (:210). It is also the base of IPasswordChangeableUser, and the shape hand-written test doubles copy (MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Auth/AuthenticationServiceBaseTests.cs:947).
    687. Caveats / not-in-source: the doc comment on PasswordSalt still says the salt's length selects the verify algorithm (MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/IAuthUser.cs:22). That was true while PasswordHasher also verified a legacy HMAC-SHA512 format; the current implementation has one algorithm and one salt size (SaltSize = 32, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordHasher.cs:15, with no legacy branch) per ADR-102. The comment is stale; the code is the contract.
    688. IPasswordChangeableUser

      @@ -2976,7 +3374,7 @@

      IPasswordChangeableUser

    689. Concept: capability interfaces layered by workflow. [Rubric §1, SOLID] assesses interface segregation, and this is the pattern applied twice over: a User that only ever authenticates satisfies IAuthUser; a User whose app offers self-service password change implements this one and gets PasswordHash and PasswordSalt along with it, because the workflow must verify the current credential before writing the new one (the XML comment states exactly this reason, :7-9). Inheritance here encodes a real dependency between capabilities rather than a taxonomy. [Rubric §4, DDD] also applies: the method returns Result, so the aggregate can refuse the change (an invariant failure) instead of the handler assuming success.
    690. Walkthrough: one member, Result ChangePassword(byte[] newPasswordHash, byte[] newPasswordSalt) (:19). The aggregate receives already-hashed material, never a plaintext password: hashing is the handler's job via IPasswordHasher, so no plaintext ever reaches the Domain layer or an EF change tracker.
    691. Why it's built this way: keeping the hash-and-salt pair as the parameter shape mirrors IAuthUser's two properties and IPasswordHasher's tuple return, so the whole chain from handler to aggregate speaks one vocabulary. See ADR-032.
    692. -
    693. Where it's used: as the generic constraint where TUser : AuditableAggregateRootEntity<UserIdentifierType>, IPasswordChangeableUser on the shared change-password workflow (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:40), which verifies the current password (:55), hashes the new one (:61), and calls ChangePassword with the result (:62). The forgot-password sibling, ResetPasswordHandlerBase<TUser, TCommand>, calls the same member on the redeem path after IPasswordResetTokenService has identified the account (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:94-95). Both apps' User aggregates declare it (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:34-35, MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Domain/Users/User.cs:29-30), which is also how they pick up IAuthUser.
    694. +
    695. Where it's used: as the generic constraint where TUser : AuditableAggregateRootEntity<UserIdentifierType>, IPasswordChangeableUser on the shared change-password workflow (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:49), which verifies the current password (:55), hashes the new one (:61), and calls ChangePassword with the result (:62). The forgot-password sibling, ResetPasswordHandlerBase<TUser, TCommand>, calls the same member on the redeem path after IPasswordResetTokenService has identified the account (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:94-95). Both apps' User aggregates declare it (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:34-35, MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Domain/Users/User.cs:29-30), which is also how they pick up IAuthUser.
    696. IUserPreferences

      @@ -2990,32 +3388,6 @@

      IUserPreferences

    697. Why it's built this way: one replace method keeps the aggregate's invariant check in a single place, and pushing the merge into the workflow keeps the null-means-unchanged policy out of every app's User. Returning Result lets the aggregate reject an unsupported culture or theme value.
    698. Where it's used: the read workflow constrains where TUser : AuditableBaseEntity<UserIdentifierType>, IUserPreferences and projects both properties into a response (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/GetPreferences/GetUserPreferencesHandlerBase.cs:23, :44); the write workflow constrains where TUser : AuditableAggregateRootEntity<UserIdentifierType>, IUserPreferences (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePreferences/ChangePreferencesHandlerBase.cs:26). Both are cross-linked as GetUserPreferencesHandlerBase<TUser> and ChangePreferencesHandlerBase<TUser, TCommand>, and both apps' User aggregates implement the interface.
    699. -

      RefreshSession

      -
      -

      MMCA.Common.Domain · MMCA.Common.Domain.Auth · MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:31 · Level 3 · class (sealed)

      -
      -
        -
      • What it is: one refresh-token session, meaning a single device's right to mint access tokens for one user, held as a hash of the issued refresh token. A user has as many rows as they have signed-in devices, so signing in on a phone no longer signs the same account out of a laptop (BR-205/206, MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:7-10).
      • -
      • Depends on: Result and Error (:3), the UserIdentifierType alias, and from the BCL System.Security.Cryptography.SHA256, System.Text.Encoding, and Convert.ToHexString (:1-2). Persisted by EFRefreshSessionStore behind IRefreshSessionStore, mapped by RefreshSessionModelBuilderExtensions, swept by RefreshSessionCleanupService, tuned by RefreshSessionSettings.
      • -
      • Concept introduced: a refresh token is a credential, so store its digest and make reuse detectable. [Rubric §11, Security] assesses how a long-lived credential is stored, rotated, and revoked, and this one class carries three separate properties that are each worth understanding on their own.
          -
        • Hash at rest. The plaintext refresh token exists only in the response that hands it to the client; the row keeps TokenHash (:63-64), so a database read cannot mint tokens (:11-15). That forces one design consequence the comment calls out explicitly: because lookups are by hash, the digest must be unsalted and deterministic. A per-row salt would make the token unfindable. This is the opposite trade-off from PasswordHasher, and legitimately so: a refresh token is 64 random bytes from a CSPRNG rather than a human-chosen password, so there is no dictionary to run against it and no value in slowing the digest down.
        • -
        • Rotation leaves a chain. Using a session revokes it and records its successor in ReplacedByTokenHash (:16-21, :75-79). The point is not bookkeeping: presenting an already-rotated token lands on a revoked row rather than on nothing, and that difference is the signal that a token was replayed. A stolen-and-replayed token therefore triggers revocation of the whole family instead of failing quietly (BR-206 reuse detection, ADR-050).
        • -
        • Framework bookkeeping, not an aggregate. The class comment (:22-29) is explicit that this is a flat record like OutboxMessage and AuditTrailEntry: no audit stamps, no soft-delete flag, no concurrency token. The reason matters for [Rubric §8, Data Architecture]: rows are never deleted or edited except to be revoked, and no global query filter may hide a revoked row, because the reuse check depends on finding it. It is also mapped only where a consumer opts in (ApplyRefreshSessionConfiguration), since sessions belong to the Identity module's database rather than to every data source (ADR-006 database-per-service).
        • -
        -
      • -
      • Walkthrough
          -
        • Width and reason constants (MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:33-55): TokenHashLength = 64 (the width of a hex-encoded SHA-256 digest, :34), IpAddressMaxLength = 45 (sized to fit an IPv4-mapped IPv6 literal, :37), UserAgentMaxLength = 512 (:40), ReasonRevokedMaxLength = 64 (:43), and the four revocation reasons ReasonRotated (:46), ReasonSignedOut (:49), ReasonReuseDetected (:52), and ReasonSessionCap (:55). Publishing the widths as public const on the Domain type is what lets the EF configuration derive every column width from the same numbers (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/RefreshSessionModelBuilderExtensions.cs:47, :52, :56-58) rather than repeating magic numbers in a mapping file.
        • -
        • State (:57-92): Id defaults to a fresh Guid (:58); UserId, TokenHash, CreatedAt and ExpiresAt are required and init-only (:61-70), so a session cannot be constructed without them and cannot be rewritten afterwards. The three mutable members carry private set and change only through Revoke: RevokedAt (:73), ReplacedByTokenHash (:79), ReasonRevoked (:82). IpAddress (:89) and UserAgent (:92) are optional init-only capture. The comment on IpAddress (:84-88) is a good example of documenting what a field is not for: it identifies a session in a "your devices" list and gives an audit trail for a revocation, and it is never part of a validation decision, so a mobile client changing networks is not signed out.
        • -
        • Derived state: IsRevoked => RevokedAt is not null (:95) and IsActiveAt(DateTime utcNow) => !IsRevoked && ExpiresAt > utcNow (:99). Passing the instant in rather than reading a clock keeps the type free of ambient time, which is what makes it directly unit-testable (see RefreshSessionTests).
        • -
        • Create(...) (:112-145), the factory returning Result<RefreshSession> in the framework's standard shape (see the primer on factory methods and the Result pattern). Two guards: a blank token fails with RefreshSession.TokenRequired (:120-126), and an expiry at or before creation fails with RefreshSession.ExpiryInPast (:128-134), both Error.Validation. On success it hashes the token on the way in (:139), so the plaintext never reaches a property, and truncates the two optional capture fields to their column widths (:142-143). Truncating in the factory rather than trusting the caller is what keeps an oversized User-Agent header from turning a login into a database error.
        • -
        • HashToken(string refreshToken) (:160-164): Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(refreshToken))), guarded by ArgumentException.ThrowIfNullOrWhiteSpace (:162). The <remarks> (:151-157) is the one piece of this file to read twice: the encoding is part of the contract, not an implementation detail, because a consumer's data migration has to reproduce it exactly to carry existing tokens over. It even gives the T-SQL equivalent, CONVERT(char(64), HASHBYTES('SHA2_256', CONVERT(varchar(max), Token)), 2), and explains both halves of why it matches: style 2 emits upper-case hex with no 0x prefix, and the varchar conversion is what makes the hashed bytes UTF-8 rather than SQL Server's default UTF-16. [Rubric §8, Data Architecture] and [Rubric §34, Architecture Governance & Documentation] both apply here: a hash format that a migration must reproduce is a published contract, and it is documented as one.
        • -
        • Revoke(DateTime revokedAt, string reason, string? replacedByTokenHash = null) (:174-189): the only mutator. It is idempotent by refusal (:166-169), returning Error.Invariant("RefreshSession.AlreadyRevoked", ...) when the session is already revoked (:176-182) rather than silently overwriting, so the first reason and instant recorded are the ones kept. That matters for forensics: a session revoked by reuse detection must not have that reason overwritten by a later sign-out. On success it stamps the instant, the truncated reason, and the successor hash (:184-186).
        • -
        • Truncate (:191-192) is the shared private helper, returning the value unchanged when it is null, empty, or already short enough.
        • -
        -
      • -
      • Why it's built this way: ADR-097 records the move from one plaintext refresh-token column on the user row to a session table, and ADR-050 the rotation-and-reuse-detection model the chain implements. Keeping the class free of audit stamps and soft-delete is deliberate rather than an omission, and keeping Create/Revoke as the only ways in and out means every row in the table was validated and every revoked row carries a reason.
      • -
      • Where it's used: AuthenticationServiceBase<TUser> is the main consumer. It hashes a presented token to look the session up (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:432, :593), creates one per login (:628) and per rotation (:668), revokes on sign-out (:360, :367, :385, :456), revokes the live family on reuse detection (:603, :691), and evicts the oldest session with ReasonSessionCap when a user exceeds RefreshSessions:MaxActiveSessionsPerUser (:733, documented at :111). Rotation itself is a claim rather than a plain mutation: IRefreshSessionStore.TryRotateAsync revokes with ReasonRotated and links the successor (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/IRefreshSessionStore.cs:104; the EF implementation does it as a conditional ExecuteUpdate, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/EFRefreshSessionStore.cs:129, :142). The table is mapped through ApplyRefreshSessionConfiguration (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:896), and the account-deletion path deliberately does not revoke sessions (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:103-107): the refresh flow re-fetches the user through the soft-delete query filter, so an erased account's sessions stop working the moment the delete commits.
      • -

      IEmailConfirmableUser

      MMCA.Common.Domain · MMCA.Common.Domain.Auth · MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/IEmailConfirmableUser.cs:22 · Level 3 · interface

      @@ -3040,6 +3412,34 @@

      PermissionGrant

    700. Why it's built this way: validating length and blankness in the factory rather than trusting the caller keeps an oversized or empty value from reaching the database as a constraint violation instead of a [Rubric §4, DDD]-shaped Result failure. Grouping the two comparison conventions on the type that stores the data, rather than leaving them implicit in the store or the registry, is what keeps every reader consistent.
    701. Where it's used: written and read by EFPermissionGrantStore and StoredPermissionRoleAdministrationService, mapped by PermissionGrantModelBuilderExtensions, and its RoleMaxLength/PermissionMaxLength constants are cited by SetUserRolesRequestValidator and SetRolePermissionsRequestValidator (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Validation/AdministrationRequestValidators.cs:22-23, :41-42) when validating admin requests that set roles or permissions.
    702. +

      RefreshSession

      +
      +

      MMCA.Common.Domain · MMCA.Common.Domain.Auth · MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:39 · Level 4 · class (sealed)

      +
      +
        +
      • What it is: one refresh-token session, meaning a single device's right to mint access tokens for one user, held as a hash of the issued refresh token. A user has as many rows as they have signed-in devices, so signing in on a phone no longer signs the same account out of a laptop (BR-205/206, MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:9-12). It implements IAnonymizable (:39) so the device metadata it captures has an erasure path.
      • +
      • Depends on: Result and Error (:5), PiiAttribute (:3), IAnonymizable (:4), the UserIdentifierType alias, and from the BCL System.Security.Cryptography.SHA256, System.Text.Encoding, and Convert.ToHexString (:1-2). Persisted by EFRefreshSessionStore behind IRefreshSessionStore, mapped by RefreshSessionModelBuilderExtensions, swept by RefreshSessionCleanupService, tuned by RefreshSessionSettings.
      • +
      • Concept introduced: a refresh token is a credential, so store its digest and make reuse detectable. [Rubric §11, Security] assesses how a long-lived credential is stored, rotated, and revoked, and this one class carries four separate properties that are each worth understanding on their own.
          +
        • Hash at rest. The plaintext refresh token exists only in the response that hands it to the client; the row keeps TokenHash (:72), so a database read cannot mint tokens (:14-16). That forces one design consequence the comment calls out explicitly: because lookups are by hash, the digest must be unsalted and deterministic. A per-row salt would make the token unfindable. This is the opposite trade-off from PasswordHasher, and legitimately so: a refresh token is 64 random bytes from a CSPRNG rather than a human-chosen password, so there is no dictionary to run against it and no value in slowing the digest down.
        • +
        • Rotation leaves a chain. Using a session revokes it and records its successor in ReplacedByTokenHash (:19-22, :83-87). The point is not bookkeeping: presenting an already-rotated token lands on a revoked row rather than on nothing, and that difference is the signal that a token was replayed. A stolen-and-replayed token therefore triggers revocation of the whole family instead of failing quietly (BR-206 reuse detection, ADR-050).
        • +
        • Framework bookkeeping, not an aggregate. The class comment (:24-31) is explicit that this is a flat record like OutboxMessage and AuditTrailEntry: no audit stamps, no soft-delete flag, no concurrency token. The reason matters for [Rubric §8, Data Architecture]: rows are never deleted or edited except to be revoked, and no global query filter may hide a revoked row, because the reuse check depends on finding it. It is also mapped only where a consumer opts in (ApplyRefreshSessionConfiguration), since sessions belong to the Identity module's database rather than to every data source (ADR-006 database-per-service).
        • +
        • Personal data is separable from the credential. The class comment (:32-37) splits the row in two: IpAddress and UserAgent identify the data subject's device and network, so both carry [Pii] and Anonymize clears them for an erasure request (ADR-005), while the token hashes, timestamps and revocation chain carry no personal data and are kept. That split is what lets reuse detection keep working on an anonymized row. It also satisfies the framework's PII convention, under which any domain entity with a [Pii]-marked property must implement IAnonymizable (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Governance/ArchitectureRules.Governance.cs:10).
        • +
        +
      • +
      • Walkthrough
          +
        • Width and reason constants (MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:41-63): TokenHashLength = 64 (the width of a hex-encoded SHA-256 digest, :42), IpAddressMaxLength = 45 (sized to fit an IPv4-mapped IPv6 literal, :45), UserAgentMaxLength = 512 (:48), ReasonRevokedMaxLength = 64 (:51), and the four revocation reasons ReasonRotated (:54), ReasonSignedOut (:57), ReasonReuseDetected (:60), and ReasonSessionCap (:63, value "SessionCapExceeded"). Publishing the widths as public const on the Domain type is what lets the EF configuration derive every column width from the same numbers (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/RefreshSessionModelBuilderExtensions.cs:47, :52, :56-58) rather than repeating magic numbers in a mapping file.
        • +
        • State (:65-105): Id defaults to a fresh Guid (:66); UserId, TokenHash, CreatedAt and ExpiresAt are required and init-only (:69-78), so a session cannot be constructed without them and cannot be rewritten afterwards. Three revocation members carry private set and change only through Revoke: RevokedAt (:81), ReplacedByTokenHash (:87), ReasonRevoked (:90). IpAddress (:98) and UserAgent (:105) are optional capture, each marked [Pii] (:97, :104) and also private set: they are written once by Create and afterwards only cleared by Anonymize, never set from outside. The UserAgent comment (:100-103) gives the reason for its marking: alongside the IP it fingerprints the data subject's device. The comment on IpAddress (:92-96) is a good example of documenting what a field is not for: it identifies a session in a "your devices" list and gives an audit trail for a revocation, and it is never part of a validation decision, so a mobile client changing networks is not signed out.
        • +
        • Derived state: IsRevoked => RevokedAt is not null (:108) and IsActiveAt(DateTime utcNow) => !IsRevoked && ExpiresAt > utcNow (:112). Passing the instant in rather than reading a clock keeps the type free of ambient time, which is what makes it directly unit-testable (see RefreshSessionTests).
        • +
        • Create(...) (:125-158), the factory returning Result<RefreshSession> in the framework's standard shape (see the primer on factory methods and the Result pattern). Two guards: a blank token fails with RefreshSession.TokenRequired (:133-139), and an expiry at or before creation fails with RefreshSession.ExpiryInPast (:141-147), both Error.Validation. On success it hashes the token on the way in (:152), so the plaintext never reaches a property, and truncates the two optional capture fields to their column widths (:155-156). Truncating in the factory rather than trusting the caller is what keeps an oversized User-Agent header from turning a login into a database error.
        • +
        • HashToken(string refreshToken) (:173-177): Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(refreshToken))), guarded by ArgumentException.ThrowIfNullOrWhiteSpace (:175). The <remarks> (:164-170) is the one piece of this file to read twice: the encoding is part of the contract, not an implementation detail, because a consumer's data migration has to reproduce it exactly to carry existing tokens over. It even gives the T-SQL equivalent, CONVERT(char(64), HASHBYTES('SHA2_256', CONVERT(varchar(max), Token)), 2), and explains both halves of why it matches: style 2 emits upper-case hex with no 0x prefix, and the varchar conversion is what makes the hashed bytes UTF-8 rather than SQL Server's default UTF-16. [Rubric §8, Data Architecture] and [Rubric §34, Architecture Governance & Documentation] both apply here: a hash format that a migration must reproduce is a published contract, and it is documented as one.
        • +
        • Revoke(DateTime revokedAt, string reason, string? replacedByTokenHash = null) (:187-202): the only mutator of session state. It is idempotent by refusal (:180-181), returning Error.Invariant("RefreshSession.AlreadyRevoked", ...) when the session is already revoked (:189-195) rather than silently overwriting, so the first reason and instant recorded are the ones kept. That matters for forensics: a session revoked by reuse detection must not have that reason overwritten by a later sign-out. On success it stamps the instant, the truncated reason, and the successor hash (:197-199).
        • +
        • Anonymize() (:210-215): the IAnonymizable member. It nulls IpAddress and UserAgent and returns Result.Success(). Unlike Revoke it is idempotent by acceptance (:204-209): an already-anonymized session stays as it is and the call still succeeds, because erasing absent data is not an error. It deliberately leaves the session's validity untouched; revoking is the sign-out path's job, so anonymizing a row and ending it remain two separate decisions.
        • +
        • Truncate (:217-218) is the shared private helper, returning the value unchanged when it is null, empty, or already short enough.
        • +
        +
      • +
      • Why it's built this way: ADR-097 records the move from one plaintext refresh-token column on the user row to a session table, and ADR-050 the rotation-and-reuse-detection model the chain implements. Keeping the class free of audit stamps and soft-delete is deliberate rather than an omission, and keeping Create/Revoke/Anonymize as the only ways in and out means every row in the table was validated, every revoked row carries a reason, and personal data can be erased without breaking the chain.
      • +
      • Where it's used: AuthSessionIssuer is the main consumer. It hashes a presented token to look the session up (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:155, :285), creates one per login (:320) and per rotation (:360), revokes on sign-out (:166, :172, :180) and on a by-id revoke from the device list (:247), revokes the live family on reuse detection (:295, :383), and evicts the oldest session with ReasonSessionCap when a user is at RefreshSessions:MaxActiveSessionsPerUser (:427, documented at :409; default 10 at MMCA.Common/Source/Core/MMCA.Common.Application/Auth/RefreshSessionSettings.cs:35). Its ListActiveAsync filters with IsActiveAt and projects IpAddress and UserAgent into RefreshSessionSummaryResponse for the "your devices" list (AuthSessionIssuer.cs:202-211). RefreshSessionRevocation also revokes with ReasonSignedOut (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/RefreshSessionRevocation.cs:47). No code under MMCA.Common/Source calls RefreshSession.Anonymize today: the method is the erasure path the PII convention requires, available to an application's erasure flow. Rotation itself is a claim rather than a plain mutation: IRefreshSessionStore.TryRotateAsync revokes with ReasonRotated and links the successor (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/IRefreshSessionStore.cs:104; the EF implementation does it as a conditional ExecuteUpdate, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Auth/EFRefreshSessionStore.cs:129, :142). The table is mapped through ApplyRefreshSessionConfiguration (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:899), and the account-deletion path deliberately does not revoke sessions (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:107-111): the refresh flow re-fetches the user through the soft-delete query filter, so an erased account's sessions stop working the moment the delete commits.
      • +

      IErasableUser

      MMCA.Common.Domain · MMCA.Common.Domain.Auth · MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/IErasableUser.cs:30 · Level 4 · interface

      @@ -3048,41 +3448,28 @@

      IErasableUser

    703. What it is: the erasure surface an Identity module's User aggregate exposes to the shared DeleteUserHandlerBase<TUser, TCommand> workflow: soft-delete the row, then irreversibly anonymize the personal data it still holds (MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/IErasableUser.cs:6-9).
    704. Depends on: IAnonymizable (its base, contributing Result Anonymize(), :1 and :30) and Result (:2).
    705. Concept introduced: why a Delete() that already exists on the base entity is redeclared here. This is the most instructive comment in the file and it is worth reading in full (:11-29). AuditableBaseEntity<TIdentifierType> already has a Delete(). But an app's User may hide it (public new Result Delete()) to couple account-specific behavior to deletion. A hidden method is not an override, and C# member lookup on a generic type parameter prefers the members of its class constraint, so a shared workflow writing user.Delete() would bind to the base implementation and silently skip the app's version. Because the app User lists this interface in its own base list, the interface map resolves to the most derived Delete() declared on the app type, so invoking it through the interface forces interface dispatch and reaches exactly the member the app intended. [Rubric §1, SOLID] (Liskov: the hidden method is exactly the substitutability hazard this closes) and [Rubric §15, Best Practices & Code Quality] both apply, and this is a case where a language rule, not a style preference, dictates the design. The second paragraph (:25-28) adds the compile-time guarantee: the base entity deliberately does not implement this interface, so a consumer that forgets to declare it fails the generic constraint at compile time rather than losing behavior at run time.
    706. -
    707. Walkthrough: one declared member, Result Delete() (:37), documented as soft-delete plus whatever the app couples to deletion (:32-34), returning a failure when the account is already deleted (:36). Inherited from IAnonymizable is Result Anonymize(), which must be idempotent. The two-step order is visible in the caller: cast once to the interface (IErasableUser erasable = user;, MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:114, with the reason spelled out at :88-92), erasable.Delete() first (:94), the app's own tail hook next (OnAfterSoftDeleteAsync, :101), then erasable.Anonymize() (:108), each short-circuiting on failure.
    708. -
    709. Why it's built this way: soft-delete alone hides a row but retains its personal data, so it does not satisfy an erasure request; anonymize-in-place overwrites the personal fields while keeping the row so foreign keys and the audit trail survive (ADR-005). Splitting the two into separate members lets the workflow run app-specific work between them, which the handler documents as the only point where an app can both read the personal data and know the delete succeeded (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:41-42). [Rubric §30, Compliance, Privacy & Data Governance] assesses exactly this: an erasure path that does not destroy referential integrity.
    710. -
    711. Where it's used: the generic constraint where TUser : AuditableAggregateRootEntity<UserIdentifierType>, IErasableUser on the shared delete-user workflow (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:62), implemented by each app's User aggregate (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:34-35, MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Domain/Users/User.cs:29-30).
    712. +
    713. Walkthrough: one declared member, Result Delete() (:37), documented as soft-delete plus whatever the app couples to deletion (:32-34), returning a failure when the account is already deleted (:36). Inherited from IAnonymizable is Result Anonymize(), which must be idempotent. The two-step order is visible in the caller: cast once to the interface (IErasableUser erasable = user;, MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:118, with the reason spelled out at :88-92), erasable.Delete() first (:94), the app's own tail hook next (OnAfterSoftDeleteAsync, :101), then erasable.Anonymize() (:108), each short-circuiting on failure.
    714. +
    715. Why it's built this way: soft-delete alone hides a row but retains its personal data, so it does not satisfy an erasure request; anonymize-in-place overwrites the personal fields while keeping the row so foreign keys and the audit trail survive (ADR-005). Splitting the two into separate members lets the workflow run app-specific work between them, which the handler documents as the only point where an app can both read the personal data and know the delete succeeded (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:45-46). [Rubric §30, Compliance, Privacy & Data Governance] assesses exactly this: an erasure path that does not destroy referential integrity.
    716. +
    717. Where it's used: the generic constraint where TUser : AuditableAggregateRootEntity<UserIdentifierType>, IErasableUser on the shared delete-user workflow (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:66), implemented by each app's User aggregate (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:34-35, MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Domain/Users/User.cs:29-30).
    718. Caveats / not-in-source: the interface's own comment says an app typically hides Delete() to revoke the refresh token (MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/IErasableUser.cs:16, :34). That phrasing predates the move to RefreshSession rows and no longer describes either app. ADC is the only consumer that hides the method, and its version calls base.Delete() and raises a UserDeleted domain event (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:443-452), with the XML comment there stating outright that sessions are not touched and do not need to be. MMCA.Store does not hide Delete() at all. The load-bearing lesson (interface dispatch over a possibly-hidden base member) is unchanged; the example in the comment is stale.
    719. -

      ICurrentUserService

      -
      -

      MMCA.Common.Application · MMCA.Common.Application.Interfaces.Infrastructure.Auth · MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Auth/ICurrentUserService.cs:9 · Level 8 · interface

      -
      -
        -
      • What it is: the Application layer's read-only window onto the authenticated caller: the raw ClaimsPrincipal, a strongly-typed UserId, the caller's first role, the full role set, a generic typed-claim reader, and a role-membership helper. It answers "who is calling?" without any handler ever touching HttpContext.
      • -
      • Depends on: System.Security.Claims and IParsable<T> (BCL, :1) plus the solution-wide UserIdentifierType alias (:15); see primer §2. Its adapter is CurrentUserService in Infrastructure.
      • -
      • Concept introduced: the caller-identity port with behavior on the interface. [Rubric §3, Clean Architecture] assesses whether inner layers stay free of transport types, and [Rubric §1, SOLID] (interface segregation) whether a contract exposes only what its clients need. A handler must know the caller to run ownership checks and to stamp audit fields, but it must not depend on IHttpContextAccessor, which would drag ASP.NET Core into the Application project. This interface is that inversion, and the adapter is the only place the accessor appears (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Context/CurrentUserService.cs:17, :25). What makes it worth studying is the use of default interface members: Roles (:45-64) and IsInRole (:88-89) ship real implementations on the contract, so every implementer and every hand-written test double inherits correct multi-role behavior instead of re-deriving it.
      • -
      • Walkthrough: ClaimsPrincipal User (:12) exposes the full principal for advanced inspection. UserIdentifierType? UserId (:15) is the typed identifier, nullable because an unauthenticated request has no user. string? Role (:22) is documented as the first role claim only, with the remarks at :18-21 steering callers to Roles or IsInRole for membership checks. Roles (:45-64) is the interesting member: it reads every role claim, accepting each claim type the JWT middleware may produce (ClaimTypes.Role when inbound claim mapping is on, or the raw role / roles claim when it is off, :50-53), falls back to a single-element list built from Role when the principal yields nothing (:62), and null-guards User even though the property is declared non-nullable (:49). The long remarks at :27-44 justify both accommodations from the nature of a default interface member: it runs against every implementation, including a hand-written double or a mock that stubs only Role, where reading claims alone would have reported no roles and silently turned an authorization check into a denial, and dereferencing a null principal would have turned it into a NullReferenceException. Claims win when present, so a genuine multi-role principal is still read in full. T? GetClaimValue<T>(string claimType) where T : struct, IParsable<T> (:73-74) parses a named claim into any parsable value type and returns null when the claim is missing or unparseable, which is how a module reads its own claim (the doc names speaker_id, :68) without Common ever knowing that claim exists. IsInRole(string roleName) (:88-89) is Roles.Any(role => string.Equals(role, roleName, StringComparison.OrdinalIgnoreCase)).
      • -
      • Why it's built this way: the remarks at :82-87 record the reasoning behind IsInRole checking every claim rather than comparing against Role. Comparing against the first role alone matched only whichever role happened to be listed first, which is latent today because tokens carry a single role, and would have surfaced silently as an authorization denial the moment a second role was added. Typing UserId as the per-app alias instead of a generic parameter keeps the interface concrete and easy to mock while staying correct for each app. [Rubric §11, Security] and [Rubric §15, Best Practices & Code Quality] both apply.
      • -
      • Where it's used: registered as scoped against CurrentUserService at MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:296. It supplies the Roles set the CQRS authorization decorators check permissions against (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/AuthorizationCommandDecorator.cs:32, and its query twin; see group 05); it is how audit fields get their actor, since DbContextFactory passes _currentUserService.UserId into every save (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:66, used at :248, :291, :330, :352 and :414); and it backs the ownership check in OwnerOrAdminFilter and the framework's account controllers.
      • -
      • Caveats / not-in-source: Role deliberately reports only the first role claim; treat it as a display value and use Roles or IsInRole for any decision.
      • -

      EmailConfirmationEntry

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Auth · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:169 · Level 0 · record (internal sealed)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Auth · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:174 · Level 0 · record (internal sealed)

        -
      • What it is: the cached confirmation record behind the email-confirmation flow: what EmailConfirmationTokenService writes into the cache when a confirmation token is issued, and reads back when one is redeemed. It is internal sealed, declared as a second type at the bottom of its service's file (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:169-173).
      • +
      • What it is: the cached confirmation record behind the email-confirmation flow: what EmailConfirmationTokenService writes into the cache when a confirmation token is issued, and reads back when one is redeemed. It is internal sealed, declared as a second type at the bottom of its service's file (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:174-178).
      • Depends on: nothing first-party except the UserIdentifierType alias (the per-module global using identifier alias, ADR-048). Four positional parameters, all BCL primitives.
      • Concept: the same cache-DTO rule PasswordResetEntry demonstrates: a value round-tripped through System.Text.Json in a distributed cache must be a JSON primitive, so the token digest travels as Base64 text (TokenHashBase64, :170) rather than as byte[], and the expiry as Unix seconds (ExpiresAtUnixSeconds, :173) rather than as DateTimeOffset. [Rubric §11, Security] applies through the same member name convention: TokenHashBase64, never Token, so the record cannot hold the redeemable secret it guards.
      • Walkthrough: four members, the same shape as PasswordResetEntry:
          -
        • string TokenHashBase64 (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:170): the SHA-256 digest of the issued token, Base64-encoded. Validation re-hashes the presented token and compares digests.
        • +
        • string TokenHashBase64 (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:175): the SHA-256 digest of the issued token, Base64-encoded. Validation re-hashes the presented token and compares digests.
        • UserIdentifierType UserId (:171): the account the token confirms.
        • int FailedAttempts (:172): wrong tokens presented against this record so far.
        • long ExpiresAtUnixSeconds (:173): when the record expires; a failed-attempt rewrite recaches with the remaining lifetime computed from this field so a wrong guess cannot extend how long the token stays redeemable (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:138-151).
      • -
      • Why it's built this way: being a record gives the with expression the attempt-counter rewrite relies on (entry with { FailedAttempts = attempts }, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:150). Being internal keeps the cache layout out of the package's public API.
      • -
      • Where it's used: written by IssueAsync (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:67-73), read by ValidateAndConsumeAsync (:85), and rewritten by RecordFailedAttemptAsync (:148-151). It appears nowhere else.
      • +
      • Why it's built this way: being a record gives the with expression the attempt-counter rewrite relies on (entry with { FailedAttempts = attempts }, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:155). Being internal keeps the cache layout out of the package's public API.
      • +
      • Where it's used: written by IssueAsync (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:69-75), read by ValidateAndConsumeAsync (:85), and rewritten by RecordFailedAttemptAsync (:148-151). It appears nowhere else.

      IJwksProvider

      @@ -3094,7 +3481,7 @@

      IJwksProvider

    720. Concept introduced: publishing a public key instead of sharing a secret. [Rubric §11, Security] assesses key management and blast radius, and [Rubric §7, Microservices Readiness] assesses whether a module can be lifted out without a rewrite. In an extracted-service topology, symmetric HS256 would require every service to hold the same secret, so any one compromised service can mint tokens for all of them. The asymmetric alternative (ADR-004) keeps the RSA private key inside the Identity service and publishes only the public key at a well-known URL; peers fetch it and validate signatures without ever being able to sign. IJwksProvider is how the Identity API obtains that public key set to serve.
    721. Walkthrough: a single synchronous member, JsonWebKeySet GetJsonWebKeySet() (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/IJwksProvider.cs:19). Synchronous is the deliberate shape because key material is resolved once and cached in-process by the implementation. The doc comment sets a contract that the implementation must honor: return an empty key set rather than throwing when no signing key is configured (:13-17), so /.well-known/jwks.json stays a valid, pollable URL even in a host where JWKS publishing is off.
    722. Why it's built this way: an interface here lets tests inject a pre-built key set with no file IO, and the empty-set contract makes the endpoint safe to map unconditionally instead of behind a feature check.
    723. -
    724. Where it's used: registered as services.TryAddSingleton<IJwksProvider, RsaJwksProvider>() (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:186) immediately after the JwksSettings options binding (:165-168); the JWKS minimal-API endpoint calls it, and consuming services fetch the resulting document through AddForwardedJwtBearer at startup (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/IJwksProvider.cs:9).
    725. +
    726. Where it's used: registered as services.TryAddSingleton<IJwksProvider, RsaJwksProvider>() (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:196) immediately after the JwksSettings options binding (:165-168); the JWKS minimal-API endpoint calls it, and consuming services fetch the resulting document through AddForwardedJwtBearer at startup (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/IJwksProvider.cs:9).
    727. JwksSettings

      @@ -3103,7 +3490,7 @@

      JwksSettings

      • What it is: the Jwks section that controls whether an Identity service publishes a JSON Web Key Set at /.well-known/jwks.json, and where its RSA public key comes from (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwksSettings.cs:5-9).
      • Depends on: System.ComponentModel.DataAnnotations for [StringLength] (BCL, :1) only. Consumed by RsaJwksProvider and TokenService through IOptions<JwksSettings>.
      • -
      • Concept introduced: key distribution as configuration. [Rubric §11, Security] assesses how trust is established between services. In a single-process monolith the issuer and the validator can share one symmetric secret. Once a module is extracted, the validator must obtain the issuer's public key without sharing anything secret, which is what a JWKS document is for (ADR-004, ADR-008). [Rubric §7, Microservices Readiness]: the framework ships the endpoint always and the key set empty, so nothing about a deployment changes until a host flips Enabled. The kid contract is the subtle part: KeyId is published as the JWK kid and must match the kid header on tokens the issuer signs (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwksSettings.cs:13-14, restated on the property itself at :28-32), otherwise a validator holding a correct key set still cannot pick the right key. TokenService closes that loop by taking these same options and stamping KeyId onto every RS256 token it signs (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:57-62, :57, :67).
      • +
      • Concept introduced: key distribution as configuration. [Rubric §11, Security] assesses how trust is established between services. In a single-process monolith the issuer and the validator can share one symmetric secret. Once a module is extracted, the validator must obtain the issuer's public key without sharing anything secret, which is what a JWKS document is for (ADR-004, ADR-008). [Rubric §7, Microservices Readiness]: the framework ships the endpoint always and the key set empty, so nothing about a deployment changes until a host flips Enabled. The kid contract is the subtle part: KeyId is published as the JWK kid and must match the kid header on tokens the issuer signs (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwksSettings.cs:13-14, restated on the property itself at :28-32), otherwise a validator holding a correct key set still cannot pick the right key. TokenService closes that loop by taking these same options and stamping KeyId onto every RS256 token it signs (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:65-70, :57, :67).
      • Walkthrough:
        • SectionName = "Jwks" (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwksSettings.cs:20).
        • Enabled (:26), defaulting to false with the rationale spelled out inline (:22-25): existing HMAC-only deployments must not start advertising an RSA key set by accident.
        • @@ -3113,7 +3500,7 @@

          JwksSettings

      • Why it's built this way: default-off plus an empty key set means the endpoint is safe to map unconditionally, and two key sources cover both "inline it in configuration" and "mount it as a secret" without a second code path in the provider.
      • -
      • Where it's used: bound with .ValidateDataAnnotations().ValidateOnStart() in AddInfrastructure (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:182-185), immediately followed by the IJwksProvider registration (:183). TokenService takes it as an optional constructor dependency and falls back to new JwksSettings().KeyId when it is absent (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:79).
      • +
      • Where it's used: bound with .ValidateDataAnnotations().ValidateOnStart() in AddInfrastructure (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:192-195), immediately followed by the IJwksProvider registration (:183). TokenService takes it as an optional constructor dependency and falls back to new JwksSettings().KeyId when it is absent (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:90).

      JwtSigningAlgorithm

      @@ -3125,7 +3512,7 @@

      JwtSigningAlgorithm

    728. Concept introduced: the deployment shape encoded as one configuration value. [Rubric §11, Security] assesses key management: HS256 requires every validator to hold the signing key, which is acceptable only while issuer and validators share a process. RS256 splits the pair, the issuer holds the private key and peers validate against the JWKS endpoint, so no peer ever holds the signing key (ADR-004). [Rubric §7, Microservices Readiness]: making this a configuration value rather than a compile-time choice is what lets the same binaries run both topologies, and the type's own doc says RS256 is also the right choice for a monolith that intends to extract later, because the token format does not change when it does (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwtSigningAlgorithm.cs:8-12). The operational consequence is stated just as plainly: switching a running deployment between the two invalidates every existing token, a hard cutover (:17-18).
    729. Walkthrough: HS256 = 0 (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwtSigningAlgorithm.cs:24) and RS256 = 1 (:27), both with explicit ordinals.
      • The default is RS256, and where that default lives is worth being precise about. The enum's zero value is HS256, so a configuration binder that saw an invalid value would land there; but a host that simply omits Jwt:SigningAlgorithm never has the property set at all, and JwtSettings's own initializer holds (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwtSettings.cs:30). The default is a property initializer, not the enum ordinal.
      • -
      • TokenService branches on the value once, in its constructor, and caches the resulting credentials (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:76-86), with the RSA and HMAC builders at :194 and :180. Each builder throws a named InvalidOperationException when its key material is missing (:184, :200).
      • +
      • TokenService branches on the value once, in its constructor, and caches the resulting credentials (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:87-97), with the RSA and HMAC builders at :194 and :180. Each builder throws a named InvalidOperationException when its key material is missing (:184, :200).
      • The API layer branches on the same value when configuring in-process JWT bearer validation: BuildValidationParameters takes the RSA path for RS256 (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.Authentication.cs:209-232) and, when the public key is absent, throws a message that points the reader at AddForwardedJwtBearer for services that fetch the key through JWKS at runtime instead (:211-215).
    730. @@ -3145,26 +3532,26 @@

      LoginProtectionSettings

    731. Registration rate limiting: MaxRegistrationsPerIpPerHour (default 10, [Range(1, 10000)], MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/LoginProtectionSettings.cs:36-37) and RegistrationRateLimitWindowMinutes (default 60, [Range(1, 1440)], :42-43).
    732. -
    733. Why it's built this way: sealed with init-only properties gives an immutable options object. Every property carries a [Range], and the registration wires .ValidateDataAnnotations().ValidateOnStart() (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:148-151), so an obviously unsafe value such as MaxFailedAttempts = 0 fails the host at startup instead of quietly disabling lockout until someone notices in production. The MaxLockoutSeconds upper bound of 3600 is also what lets LoginProtectionService reason about its shift-clamp safely.
    734. -
    735. Where it's used: bound and validated in AddInfrastructure (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:148-151) immediately before LoginProtectionService is registered (:135). Its sibling PasswordResetSettings is bound in exactly the same shape two lines later (:137-140), as is RefreshSessionSettings (:145-148).
    736. +
    737. Why it's built this way: sealed with init-only properties gives an immutable options object. Every property carries a [Range], and the registration wires .ValidateDataAnnotations().ValidateOnStart() (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:154-157), so an obviously unsafe value such as MaxFailedAttempts = 0 fails the host at startup instead of quietly disabling lockout until someone notices in production. The MaxLockoutSeconds upper bound of 3600 is also what lets LoginProtectionService reason about its shift-clamp safely.
    738. +
    739. Where it's used: bound and validated in AddInfrastructure (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:154-157) immediately before LoginProtectionService is registered (:135). Its sibling PasswordResetSettings is bound in exactly the same shape two lines later (:137-140), as is RefreshSessionSettings (:145-148).
    740. PasswordResetEntry

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Auth · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:160 · Level 0 · record (internal sealed)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Auth · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:199 · Level 0 · record (internal sealed)

        -
      • What it is: the cached reset record behind the forgot-password flow: what PasswordResetTokenService writes into the cache when a reset token is issued, and reads back when one is redeemed. It is internal sealed, declared as a second type at the bottom of its service's file (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:160-164).
      • +
      • What it is: the cached reset record behind the forgot-password flow: what PasswordResetTokenService writes into the cache when a reset token is issued, and reads back when one is redeemed. It is internal sealed, declared as a second type at the bottom of its service's file (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:199-203).
      • Depends on: nothing first-party except the UserIdentifierType alias (the per-module global using identifier alias taught in the primer, ADR-048). Four positional parameters, all BCL primitives.
      • -
      • Concept introduced: a cache DTO is constrained by its serializer, not by your domain. [Rubric §8, Data Architecture] assesses whether each store is given a shape it can actually round-trip, and this four-line record is a compact lesson in that. The XML comment states the rule directly (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:151-155): the cache round-trips values through System.Text.Json, so every member is a JSON primitive. A value object such as Email or a raw byte[] here would not survive a distributed backing store, which is why the token digest is carried as Base64 text (:172) and the expiry as Unix seconds (:175) rather than as byte[] and DateTimeOffset. [Rubric §11, Security] also applies through one member name: TokenHashBase64, not Token. The record is structurally incapable of holding the secret it guards.
      • +
      • Concept introduced: a cache DTO is constrained by its serializer, not by your domain. [Rubric §8, Data Architecture] assesses whether each store is given a shape it can actually round-trip, and this four-line record is a compact lesson in that. The XML comment states the rule directly (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:190-194): the cache round-trips values through System.Text.Json, so every member is a JSON primitive. A value object such as Email or a raw byte[] here would not survive a distributed backing store, which is why the token digest is carried as Base64 text (:172) and the expiry as Unix seconds (:175) rather than as byte[] and DateTimeOffset. [Rubric §11, Security] also applies through one member name: TokenHashBase64, not Token. The record is structurally incapable of holding the secret it guards.
      • Walkthrough: four members, in the order they matter.
          -
        • string TokenHashBase64 (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:161): Base64 of the SHA-256 of the issued token, never the token itself (:167). Validation re-hashes the presented token and compares digests, so the cache never holds redeemable material.
        • +
        • string TokenHashBase64 (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:200): Base64 of the SHA-256 of the issued token, never the token itself (:167). Validation re-hashes the presented token and compares digests, so the cache never holds redeemable material.
        • UserIdentifierType UserId (:173): the account the token redeems to (:168). Storing the id in the record is what lets redemption resolve the user without a second lookup by email.
        • int FailedAttempts (:174): wrong tokens presented against this record so far (:169), the counter the attempt cap is enforced against.
        • long ExpiresAtUnixSeconds (:175): when the record expires (:170). This one exists for a specific reason explained at the rewrite site: when a failed attempt bumps the counter, the record is re-cached with the remaining lifetime computed from this field, so a wrong guess cannot extend how long the token stays redeemable (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:127, :146-152).
      • -
      • Why it's built this way: being a record gives the non-destructive with expression that the attempt-counter update relies on (entry with { FailedAttempts = attempts }, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:139), so the rewrite is a copy rather than a mutation. Being internal keeps a cache-layout detail out of the package's public API: nothing outside the Infrastructure assembly should be able to construct or read one. See ADR-091 for why the reset lifecycle lives in the cache at all.
      • -
      • Where it's used: written by IssueAsync (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:71-77), read by ValidateAndConsumeAsync (:100), and rewritten by RecordFailedAttemptAsync (:148-152). It appears nowhere else.
      • +
      • Why it's built this way: being a record gives the non-destructive with expression that the attempt-counter update relies on (entry with { FailedAttempts = attempts }, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:178), so the rewrite is a copy rather than a mutation. Being internal keeps a cache-layout detail out of the package's public API: nothing outside the Infrastructure assembly should be able to construct or read one. See ADR-091 for why the reset lifecycle lives in the cache at all.
      • +
      • Where it's used: written by IssueAsync (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:84-90), read by ValidateAndConsumeAsync (:100), and rewritten by RecordFailedAttemptAsync (:148-152). It appears nowhere else.

      AuthenticationRequest

      @@ -3199,7 +3586,7 @@

      JwtSettings

    741. Why it's built this way: keeping the conditional rule in code next to the properties it constrains, rather than in the registration call, means every host that binds this section gets the same guarantee without repeating it. The algorithm switch is a hard cutover that invalidates every existing token (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/JwtSigningAlgorithm.cs:17-18), so failing the boot on a half-configured section is much cheaper than discovering it at the first sign or the first validation.
    742. -
    743. Where it's used: bound in AddCommonAuthentication (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.Authentication.cs:146-149), which then re-reads the section eagerly to build the token validation parameters at wiring time (:154-157); consumed by TokenService through IOptions<JwtSettings>, which branches on the algorithm once in the constructor and caches the credentials (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:63-87).
    744. +
    745. Where it's used: bound in AddCommonAuthentication (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.Authentication.cs:146-149), which then re-reads the section eagerly to build the token validation parameters at wiring time (:154-157); consumed by TokenService through IOptions<JwtSettings>, which branches on the algorithm once in the constructor and caches the credentials (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:71-98).
    746. RsaJwksProvider

      @@ -3216,7 +3603,7 @@

      RsaJwksProvider

    747. ResolvePem(JwksSettings settings) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/RsaJwksProvider.cs:57) prefers the inline RsaPublicKeyPem (:60-63) and otherwise reads RsaPublicKeyPath from disk with a synchronous File.ReadAllText (:70), justified in the comment because the read happens on the first request and its success is cached, while a failure is deliberately not cached (:67-69). With neither configured it returns null (:73), which is what lands BuildKeySet on the empty-set path.
    748. -
    749. Why it's built this way: exporting only the public parameters guarantees the private key can never reach the JWKS document even by accident. The inline-PEM-or-path pair supports both secrets-manager injection (env var or config) and a volume-mounted key file, which are the two deployment shapes the framework's samples use. sealed, and registered singleton (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:186) so the cache is process-wide.
    750. +
    751. Why it's built this way: exporting only the public parameters guarantees the private key can never reach the JWKS document even by accident. The inline-PEM-or-path pair supports both secrets-manager injection (env var or config) and a volume-mounted key file, which are the two deployment shapes the framework's samples use. sealed, and registered singleton (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:196) so the cache is process-wide.
    752. Where it's used: the JWKS minimal-API endpoint calls GetJsonWebKeySet() per request; see JwksEndpointExtensions.
    753. PasswordHasher

      @@ -3294,15 +3681,15 @@

      PasswordHasher

      and the executing primitive the same fact. The remaining shape (no per-app hasher, no algorithm parameter on the port) is what lets [Rubric §11, Security] be assessed once for both applications.
    754. Where it's used: registered as services.TryAddSingleton<IPasswordHasher, PasswordHasher>() - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:304); the type is + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:314); the type is stateless, so a singleton is safe. Consumers reach it through the port: AuthenticationServiceBase<TUser> takes it as a constructor - parameter (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:77), + parameter (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:65), verifies on login (:159, and a failure there increments the brute-force counter at :160) and hashes on registration (:210); ChangePasswordHandlerBase<TUser, TCommand> verifies the current password then re-hashes the new one - (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:36, + (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:44, :55, :61); and ResetPasswordHandlerBase<TUser, TCommand> only hashes @@ -3316,33 +3703,38 @@

      PasswordHasher

      TokenService

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Auth · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:26 · Level 2 · class

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Auth · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:27 · Level 2 · class (sealed partial)

      • What it is: the JWT issuer. It mints signed access tokens carrying the user id, email, role and display name (plus any extra claims a caller supplies), generates opaque random refresh tokens, projects both configured lifetimes as TimeSpans, and re-reads an already-expired access token during the refresh flow. It signs with RSA-SHA256 or HMAC-SHA256, decided once at construction from - configuration (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:13-25).
      • -
      • Depends on: ITokenService (the port it implements) and IDisposable; + configuration (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:87-97). + The class is partial only so the source-generated [LoggerMessage] method at its foot can be + emitted (:297-298).
      • +
      • Depends on: ITokenService (the port it implements) and IDisposable + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:27); JwtSettings, JwtSigningAlgorithm and JwksSettings, all bound through - IOptions<T> (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:63-67); - IPermissionRegistry, a required constructor dependency (:29, :65) - that supplies the role-to-permission map baked into every access token. + IOptions<T> (:71-76); + IPermissionRegistry, a required constructor dependency (:30, :73) + that supplies the role-to-permission map baked into every access token; and an + ILogger<TokenService> (:32, :75) used for exactly one log line. Externals: System.IdentityModel.Tokens.Jwt (JwtSecurityToken, JwtSecurityTokenHandler), Microsoft.IdentityModel.Tokens (SigningCredentials, SecurityKey, TokenValidationParameters), - System.Security.Cryptography (RSA, RandomNumberGenerator), and TimeProvider for the clock.
      • + System.Security.Cryptography (RSA, RandomNumberGenerator), Microsoft.Extensions.Logging, + and TimeProvider for the clock.
      • Concept introduced: asymmetric issuance, and one deliberate hole in validation. [Rubric §11, Security] assesses token issuance and algorithm-confusion defense, and two choices here are worth reading slowly. First, GetPrincipalFromExpiredToken sets ValidateLifetime = false - on purpose (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:171) under - a narrowly scoped #pragma warning disable CA5404 (:170, restored at :172) whose comment states + on purpose (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:182) under + a narrowly scoped #pragma warning disable CA5404 (:181, restored at :183) whose comment states why: the refresh flow's whole job is reading claims out of a token that has already expired. Every - other check stays on (:167-169). Second, the algorithm is pinned twice: ValidAlgorithms limits - validation to the one algorithm this instance was built for (:176), and the token header's alg is - re-compared with an ordinal string comparison after validation succeeds (:185-186). That pair is + other check stays on (:178-180). Second, the algorithm is pinned twice: ValidAlgorithms limits + validation to the one algorithm this instance was built for (:187), and the token header's alg is + re-compared with an ordinal string comparison after validation succeeds (:196-200). That pair is the defense against algorithm substitution, where an attacker takes the RSA public key (which is published on purpose) and presents it as an HMAC shared secret. [Rubric §7, Microservices Readiness] applies to the algorithm switch itself: RS256, the default, @@ -3350,116 +3742,129 @@

        TokenService

        fetches the public key from the JWKS document that RsaJwksProvider publishes (ADR-004). HS256 remains available for a single-process monolith, where issuer and validator are the same host and a shared - secret costs nothing (:16-23). [Rubric §14, Testability] shows up in the constructor: the clock - is an injected, required TimeProvider with no TimeProvider.System fallback (:66, :74), - resolved from whatever DI registration adds it in the container's AddServices call, so a test - passes its own and can assert iat/nbf/exp without waiting for wall-clock time to pass. A third, - [Rubric §20, Policy and Enforcement Points] concept sits in GenerateAccessToken itself: every - access token now carries one AuthClaimTypes.Permission claim per permission - IPermissionRegistry grants the token's role (:118-130), so a client can + secret costs nothing (:93-97). [Rubric §14, Testability] shows up in the constructor: the clock + is an injected, required TimeProvider with no TimeProvider.System fallback (:74, :84), + resolved from whatever DI registration adds it in the container's AddServices call (the parameter + doc says so at :55-58), so a test passes its own and can assert iat/nbf/exp without waiting + for wall-clock time to pass. A third, [Rubric §20, Policy and Enforcement Points] concept sits in + GenerateAccessToken itself: every access token carries one + AuthClaimTypes.Permission claim per permission + IPermissionRegistry grants the token's role (:129-141), so a client can gate its own surface on a capability rather than a role name it would otherwise have to know. A host that declared no grants resolves UnconfiguredPermissionRegistry, - which grants nothing, and its tokens simply carry no permission claims (:46-52, the constructor's - own doc comment on the parameter).
      • + which grants nothing, and its tokens simply carry no permission claims (:48-54, the constructor's + own doc comment on the parameter). A fourth, [Rubric §13, Observability], is the split catch in + GetPrincipalFromExpiredToken (:204-216): ordinary rejections stay silent, but anything else is + logged before it is refused, so a fault in the validation path cannot hide behind an ordinary 401.
      • Walkthrough
          -
        • Fields (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:28-38): the - settings snapshot, the injected IPermissionRegistry (:29), the TimeProvider, the - SigningCredentials used to sign, the SecurityKey and algorithm string used to validate, and two - nullable owned RSA handles. The comment at :35-36 explains why IDisposable is on the class at - all: RsaSecurityKey does not own the RSA it wraps, so something has to.
        • +
        • Fields (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:29-40): the + settings snapshot, the injected IPermissionRegistry (:30), the TimeProvider (:31), the + ILogger<TokenService> (:32), the SigningCredentials used to sign, the SecurityKey and + algorithm string used to validate, and two nullable owned RSA handles (:39-40). The comment at + :37-38 explains why IDisposable is on the class at all: RsaSecurityKey does not own the RSA + it wraps, so something has to.
        • The constructor - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:63-87) takes - IPermissionRegistry permissionRegistry as a required parameter, null-guarded alongside - jwtOptions (:69-70) and assigned to _permissionRegistry (:73). TimeProvider timeProvider - is likewise a required parameter with no default (:66), assigned straight to _timeProvider - with no fallback (:74). It materializes all key + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:71-98) takes + IPermissionRegistry permissionRegistry and ILogger<TokenService> logger as required + parameters, null-guarded alongside jwtOptions (:78-80) and assigned to _permissionRegistry + and _logger (:83, :85). TimeProvider timeProvider is likewise a required parameter with no + default (:74), assigned straight to _timeProvider with no fallback (:84); it is not + null-guarded. The logger's parameter doc states its whole job (:59-64): record the one + validation failure that is not an ordinary rejection. The constructor materializes all key material exactly once, so no token operation re-parses a PEM. For JwtSigningAlgorithm.RS256 it - builds RSA credentials and pins SecurityAlgorithms.RsaSha256 (:78-80); the key id it passes is - jwksSettings?.Value.KeyId falling back to a default JwksSettings instance (:79), which is how + builds RSA credentials and pins SecurityAlgorithms.RsaSha256 (:87-92); the key id it passes is + jwksSettings?.Value.KeyId falling back to a default JwksSettings instance (:90), which is how the same kid ends up on both the token and the published JWK (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/RsaJwksProvider.cs:45). Otherwise it - builds HMAC credentials and pins HmacSha256 (:84-85).
        • + builds HMAC credentials and pins HmacSha256 (:95-96).
        • GenerateAccessToken(userId, email, role, fullName, additionalClaims) - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:90-141) reads the - clock once (:97), then builds six claims (:103-111): sub (the user id, formatted with + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:101-152) reads the + clock once (:108), then builds six claims (:114-122): sub (the user id, formatted with CultureInfo.InvariantCulture), jti (a fresh GUID, so a token is individually identifiable), - iat as Unix seconds, and the standard name, email and role claims. The comment at :99-102 is a + iat as Unix seconds, and the standard name, email and role claims. The comment at :110-113 is a design note worth internalizing: sub is the only carrier of the user id. A duplicate custom claim used to ride alongside it, which meant two values that could disagree and two claim names every reader had to know; readers now go through ClaimsPrincipalExtensions instead. Caller-supplied claims are - appended (:113-116). Then, before the token is assembled, permission claims are added - (:118-130): alreadyClaimed collects any Permission claim value already present in claims - (:122-125), and every permission _permissionRegistry.GetPermissions(role) returns that is not - already in that set is appended, ordered ordinally for a deterministic token per role (:127-130). + appended (:124-127). Then, before the token is assembled, permission claims are added + (:129-141): alreadyClaimed collects any Permission claim value already present in claims + (:133-136), and every permission _permissionRegistry.GetPermissions(role) returns that is not + already in that set is appended, ordered ordinally for a deterministic token per role (:138-141). A JwtSecurityToken is then assembled with issuer, audience, notBefore from the injected clock and - expires at now + AccessTokenExpirationMinutes, and serialized.
        • + expires at now + AccessTokenExpirationMinutes (:143-149), and serialized (:151).
        • GenerateRefreshToken() - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:144-148) returns 64 + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:155-159) returns 64 CSPRNG bytes Base64-encoded. It is not a JWT and carries no claims: it is an opaque bearer string whose only property is being unguessable, and the store it is compared against is what gives it meaning.
        • AccessTokenLifetime and RefreshTokenLifetime - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:151, :154) project + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:162, :165) project the configured minutes and days, so callers computing an expiry timestamp never re-read settings and never disagree with the token just minted.
        • GetPrincipalFromExpiredToken(string token) - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:163-197) builds the - validation parameters described above (:165-177), validates (:183), applies the + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:174-217) builds the + validation parameters described above (:176-188), validates (:194), applies the post-validation alg re-check and returns null when the token is not a JwtSecurityToken or the - header disagrees (:185-189), and swallows every exception into null (:193-196). A malformed, + header disagrees (:196-200). The failure path is two catches. A SecurityTokenException or + ArgumentException (bad signature, wrong issuer or audience, malformed input) returns null + without a log line, because a client presenting a bad token is not an event (:204-209). Any + other exception is treated as a fault in the validation path: it is logged at warning level + through the source-generated LogUnexpectedValidationFailure (:214, declared at :297-298) and + still answered with null (:210-216), so the method fails closed either way. A malformed, forged, or wrong-issuer token therefore produces a plain "no principal" answer rather than a parser exception leaking to the caller.
        • -
        • Dispose() (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:200-204) +
        • Dispose() (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:220-224) releases both owned RSA handles.
        • BuildHmacCredentials - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:206-218) throws - InvalidOperationException when SecretForKey is missing (:208-212) and Base64-decodes it into - a SymmetricSecurityKey used for both signing and validation (:215-217).
        • + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:226-238) throws + InvalidOperationException when SecretForKey is missing (:228-232) and Base64-decodes it into + a SymmetricSecurityKey used for both signing and validation (:235-237).
        • BuildRsaCredentials - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:220-275) throws when - RsaPrivateKeyPem is missing (:224-228), imports the private key (:230-233), and stamps the - key id on the signing key (:239) so every RS256 token carries a kid header. The comment at - :235-238 gives the reason: a validator reading the published JWKS selects the key by name, and + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:240-295) throws when + RsaPrivateKeyPem is missing (:244-248), imports the private key (:250-253), and stamps the + key id on the signing key (:259) so every RS256 token carries a kid header. The comment at + :255-258 gives the reason: a validator reading the published JWKS selects the key by name, and without a kid it has to try every published key, which stops working the moment a rotation publishes two. The validation key prefers the configured RsaPublicKeyPem and otherwise derives - the public parameters from the private key (:248-256), so an issuer configured with only a - private key still validates its own tokens during refresh; it carries the same key id (:261). - Both nested try/catch blocks dispose the partially built RSA before rethrowing (:264-268, - :270-274), so a bad PEM does not leak a native handle. Because all of this runs in the + the public parameters from the private key (:265-276), so an issuer configured with only a + private key still validates its own tokens during refresh; it carries the same key id (:281). + Both nested try/catch blocks dispose the partially built RSA before rethrowing (:284-288, + :290-294), so a bad PEM does not leak a native handle. Because all of this runs in the constructor, missing or malformed key material fails at host startup, not on the first login.
      • Why it's built this way: ADR-004 records the asymmetric-issuance rationale. The DI lifetime deserves its own read at the registration site - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:297-303): the comment + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:307-313): the comment there records that a scoped lifetime disposed the underlying RSA at end-of-request while the static CryptoProviderCache in Microsoft.IdentityModel.Tokens still held the cached AsymmetricSignatureProvider wrapping it, throwing ObjectDisposedException on the next RS256 sign. - Singleton is correct because the constructor depends only on singleton options and the service is - stateless afterwards. Baking permission claims into the token itself, rather than having a client - call back to look them up, is the same shape + Singleton is correct because the service is stateless after construction; the comment names only the + IOptions<JwtSettings> dependency, and the logger, clock and permission registry it also takes are + resolved once at that same construction. Baking permission claims into the token itself, rather than + having a client call back to look them up, is the same shape ADR-020 establishes for permission-based authorization: the permission set travels with the credential.
      • Where it's used: registered as services.TryAddSingleton<ITokenService, TokenService>() - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:303), which resolves - IPermissionRegistry from the container alongside the JWT options, and - consumed through the port by AuthenticationServiceBase<TUser>: the - lifetime feeds the response's expiry - (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:137-138, with a - 15-minute floor when the setting is non-positive), the refresh flow reads the expired token (:278), - and refresh tokens are minted at :627 and :667. That base also wraps this service in a private - SessionStampingTokenService pass-through - (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:936) which - appends the AuthClaimTypes.SessionId (sid) claim through the - additionalClaims parameter when a session is armed (:782-800, the claim added at :797), so - per-device session identity is layered on without this type knowing about sessions at all. The sub + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:313), which resolves + IPermissionRegistry and the logger from the container alongside the JWT + options. Consumed through the port in two places. AuthSessionIssuer mints + the tokens: its AccessTokenLifetime reads this service's lifetime with a 15-minute floor when the + setting is non-positive + (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:55-56), and + refresh tokens are minted at :319 and :359. The same file wraps this service in the private + SessionStampingTokenService pass-through (:448), which appends the + AuthClaimTypes.SessionId (sid) claim through the additionalClaims + parameter (:484), so per-device session identity is layered on without this type knowing about + sessions at all. AuthenticationServiceBase<TUser> reads the + expired token in the refresh flow + (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:297). The sub claim it writes is what CurrentUserService and ClaimBasedUserIdProvider read back.
      • Caveats / not-in-source: nothing here rotates or reloads key material. The keys are read once in the constructor - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:63-87) and the + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:71-98) and the instance is a singleton, so a key change takes a host restart. Refresh-token storage, rotation and revocation are not in this file either: it only produces the random string.
      @@ -3477,24 +3882,24 @@

      EmailIdentity

      EmailConfirmationTokenService

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Auth · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:35 · Level 6 · class (sealed)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Auth · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:36 · Level 6 · class (sealed)

        -
      • What it is: the IEmailConfirmationTokenService implementation: the email-confirmation token lifecycle, issue a single-use token for an address, throttle how often one address can ask, hash the token at rest, cap wrong guesses, and consume the token on a successful redeem (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:35-37).
      • -
      • Depends on: IEmailConfirmationTokenService (the Application port it implements); EmailConfirmationSettings via IOptions<> (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:37, snapshotted at :41); ICacheService (:36); EmailConfirmationEntry, its cached record; Result and Error; EmailIdentity, the shared normalizer, at the two key builders (:125, :127); and from the BCL SHA256, RandomNumberGenerator, CryptographicOperations, and System.Buffers.Text.Base64Url.
      • -
      • Concept introduced: the same cache-backed token pattern, applied a third time. [Rubric §11, Security] assesses credential issuance and redemption; [Rubric §15, Best Practices & Code Quality] assesses whether a proven idiom is reused or reinvented per flow. This type is structurally the same shape as PasswordResetTokenService: hash the token at rest, throttle issuance per address, cap validation attempts, and consume both the token and the request counter together on success. The TokenKey doc comment says so directly, citing PasswordResetTokenService as the precedent for normalizing the cache key the same way Email normalizes the lookup value (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:117-124), and both key builders now call EmailIdentity.Normalize instead of a private copy of the same helper (:125, :127). The same read-modify-write gap LoginProtectionService documents on its own throttle is inherited here too: the comment at the top of IssueAsync names it explicitly (:49-50).
      • +
      • What it is: the IEmailConfirmationTokenService implementation: the email-confirmation token lifecycle, issue a single-use token for an address, throttle how often one address can ask, hash the token at rest, cap wrong guesses, and consume the token on a successful redeem (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:12-25). The class remarks add why its keys carry their own emailconfirm: prefix: a key shared with the reset service would make issuing a confirmation link silently invalidate an outstanding password-reset link for the same address (:27-32).
      • +
      • Depends on: IEmailConfirmationTokenService (the Application port it implements, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:39); EmailConfirmationSettings via IOptions<> (:38, snapshotted at :43); ICacheService (:37); TimeProvider (:39), the clock for the expiry stamp and the remaining-lifetime check (:35); EmailConfirmationEntry, its cached record; EmailConfirmationErrors for the failure; Result and Error; EmailIdentity, the shared normalizer, at the two key builders (:130, :132); and from the BCL SHA256, RandomNumberGenerator, CryptographicOperations, and System.Buffers.Text.Base64Url.
      • +
      • Concept introduced: the same cache-backed token pattern, applied a third time. [Rubric §11, Security] assesses credential issuance and redemption; [Rubric §15, Best Practices & Code Quality] assesses whether a proven idiom is reused or reinvented per flow. This type is structurally the same shape as PasswordResetTokenService: hash the token at rest, throttle issuance per address, cap validation attempts, and consume both the token and the request counter together on success. The TokenKey doc comment says so directly, citing PasswordResetTokenService as the precedent for normalizing the cache key the same way Email normalizes the lookup value (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:122-129), and both key builders call EmailIdentity.Normalize instead of a private copy of the same helper (:130, :132). The same read-modify-write gap LoginProtectionService documents on its own throttle is inherited here too: the comment at the top of IssueAsync names it explicitly (:51-52). One place it departs from its sibling: redemption reads through GetFromSharedStoreAsync but takes no distributed lock, so the single-redemption guarantee PasswordResetTokenService adds under IDistributedLock is not present here (see Caveats).
      • Walkthrough
          -
        • TokenByteLength = 32 (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:39) is the only constant; _settings is the snapshotted EmailConfirmationSettings (:41).
        • -
        • IssueAsync(string email, UserIdentifierType userId, CancellationToken) (:44-76): increments the per-email request counter with the configured RequestWindowMinutes TTL (:51-54) and fails with Authentication.ConfirmationThrottled once the count exceeds MaxRequestsPerEmail (:56-62). It then mints 32 CSPRNG bytes rendered with Base64Url.EncodeToString (:64), builds an EmailConfirmationEntry holding the Base64 digest of the token, the user id, a zero attempt count, and the absolute expiry as Unix seconds (:67-71), caches it under the token key with the configured lifetime (:73), and returns the raw token to the caller (:75); the raw token is never written anywhere else.
        • -
        • ValidateAndConsumeAsync(string email, string token, CancellationToken) (:79-115): loads the entry and returns InvalidToken() when there is none (:84-89); a FormatException decoding the stored Base64 removes the unreadable record rather than leaving it to expire (:91-101); the comparison is CryptographicOperations.FixedTimeEquals over the two digests (:103), with token ?? string.Empty so a null token hashes rather than throwing; a mismatch records a failed attempt and returns the same generic failure (:104-107). On a match it removes both the token key and the address's request counter (:109-112), so a confirmed address is not left throttled out of a later legitimate request, and returns the entry's UserId (:114).
        • -
        • TokenKey, RequestKey, HashToken (:117-130): TokenKey and RequestKey each call EmailIdentity.Normalize (:125, :127), producing emailconfirm:token:{normalized} and emailconfirm:req:{normalized} keys; HashToken is the shared SHA-256 helper (:129-130).
        • -
        • RecordFailedAttemptAsync (:132-153): computes attempts = entry.FailedAttempts + 1 and the remaining lifetime from ExpiresAtUnixSeconds (:137-138). At MaxValidationAttempts, or once the remaining lifetime is non-positive, it deletes the record (:140-144); otherwise it rewrites the entry with entry with { FailedAttempts = attempts } at the remaining TTL, not a fresh one (:146-152).
        • -
        • InvalidToken() (:155-157) is the single failure factory built from EmailConfirmationErrors.InvalidToken, so unknown, expired, mismatched and attempt-capped tokens collapse to one error rather than giving an oracle for which addresses have an outstanding confirmation.
        • +
        • TokenByteLength = 32 (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:41) is the only constant; _settings is the snapshotted EmailConfirmationSettings (:43). The primary constructor takes ICacheService, IOptions<EmailConfirmationSettings> and TimeProvider (:36-39).
        • +
        • IssueAsync(string email, UserIdentifierType userId, CancellationToken) (:46-78): increments the per-email request counter with the configured RequestWindowMinutes TTL (:53-56) and fails with Authentication.ConfirmationThrottled once the count exceeds MaxRequestsPerEmail (:58-64). It then mints 32 CSPRNG bytes rendered with Base64Url.EncodeToString (:66), builds an EmailConfirmationEntry holding the Base64 digest of the token, the user id, a zero attempt count, and the absolute expiry as Unix seconds computed from the injected clock (:69-73), caches it under the token key with the configured lifetime (:75), and returns the raw token to the caller (:77); the raw token is never written anywhere else.
        • +
        • ValidateAndConsumeAsync(string email, string token, CancellationToken) (:81-120): loads the entry with GetFromSharedStoreAsync (:90), whose comment gives the reason (:88-89): a token consumed on another replica must be a miss here, never a stale local copy that would let it be redeemed twice. It returns InvalidToken() when there is none (:91-94); a FormatException decoding the stored Base64 removes the unreadable record rather than leaving it to expire (:96-106); the comparison is CryptographicOperations.FixedTimeEquals over the two digests (:108), with token ?? string.Empty so a null token hashes rather than throwing; a mismatch records a failed attempt and returns the same generic failure (:108-112). On a match it removes both the token key and the address's request counter (:114-117), so a confirmed address is not left throttled out of a later legitimate request, and returns the entry's UserId (:119).
        • +
        • TokenKey, RequestKey, HashToken (:122-135): TokenKey and RequestKey each call EmailIdentity.Normalize (:130, :132), producing emailconfirm:token:{normalized} and emailconfirm:req:{normalized} keys; HashToken is the shared SHA-256 helper (:134-135).
        • +
        • RecordFailedAttemptAsync (:137-158): computes attempts = entry.FailedAttempts + 1 and the remaining lifetime from ExpiresAtUnixSeconds against the injected clock (:142-143). At MaxValidationAttempts, or once the remaining lifetime is non-positive, it deletes the record (:145-149); otherwise it rewrites the entry with entry with { FailedAttempts = attempts } at the remaining TTL, not a fresh one (:151-157).
        • +
        • InvalidToken() (:160-162) is the single failure factory built from EmailConfirmationErrors.InvalidToken, so unknown, expired, mismatched and attempt-capped tokens collapse to one error rather than giving an oracle for which addresses have an outstanding confirmation.
      • -
      • Why it's built this way: reusing the pattern PasswordResetTokenService established, rather than a bespoke implementation, is what keeps a third token lifecycle (login lockout, password reset, email confirmation) auditable as one idiom instead of three. ADR-116 frames identity-completion flows like this one as opt-in per host.
      • +
      • Why it's built this way: reusing the pattern PasswordResetTokenService established, rather than a bespoke implementation, is what keeps a third token lifecycle (login lockout, password reset, email confirmation) auditable as one idiom instead of three. ADR-116 frames identity-completion flows like this one as opt-in per host, and ADR-077 is the cache substrate whose shared-store read the redemption relies on. Taking TimeProvider rather than reading DateTimeOffset.UtcNow lets a test move the clock past the expiry without waiting.
      • Where it's used: registered services.TryAddScoped<IEmailConfirmationTokenService, EmailConfirmationTokenService>() (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:75), directly after the EmailConfirmationSettings binding (:69-70).
      • -
      • Caveats / not-in-source: the per-email request throttle is a read-modify-write on the distributed cache, the same gap LoginProtectionService documents on its own counters: concurrent requests can undercount, which loosens the throttle but never tightens it. The failed-attempt rewrite has the same property against the attempt cap.
      • +
      • Caveats / not-in-source: the per-email request throttle is a read-modify-write on the distributed cache, the same gap LoginProtectionService documents on its own counters: concurrent requests can undercount, which loosens the throttle but never tightens it. The failed-attempt rewrite has the same property against the attempt cap. Redemption is not serialized: the shared-store read rules out a stale replica-local copy, but nothing in this file stops two concurrent redemptions that both read before either removes from both succeeding, which is the window PasswordResetTokenService closes with a lock.

      LoginProtectionService

      @@ -3517,36 +3922,38 @@

      LoginProtectionService

    755. IncrementRegistrationCountAsync (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/LoginProtectionService.cs:109-123): no-ops on a missing IP (:111-114) and otherwise increments the per-IP counter with the RegistrationRateLimitWindowMinutes TTL (:119-122). The comment (:116-118) notes the TTL is refreshed on every write, so the window slides rather than staying anchored to the first registration, which only ever tightens the limit.
    756. -
    757. Why it's built this way: reusing ICacheService (Redis in production, in-memory fallback) instead of a bespoke store keeps the service thin and lets counters expire naturally by TTL rather than needing a sweep job; IOptions<> keeps every threshold configurable per environment (ADR-029). Registered scoped (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:152).
    758. -
    759. Where it's used: injected into AuthenticationServiceBase<TUser> (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:78), which calls all five members across its login and registration flows: the lockout check (:131), an increment on both the unknown-user and wrong-password branches (:146, :161), the reset on success (:178), and the registration rate-limit check and increment (:197, :256). Incrementing on the unknown-user branch as well as the wrong-password branch is what keeps the endpoint from becoming a user-enumeration oracle by timing or by lockout behavior.
    760. +
    761. Why it's built this way: reusing ICacheService (Redis in production, in-memory fallback) instead of a bespoke store keeps the service thin and lets counters expire naturally by TTL rather than needing a sweep job; IOptions<> keeps every threshold configurable per environment (ADR-029). Registered scoped (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:158).
    762. +
    763. Where it's used: injected into AuthenticationServiceBase<TUser> (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:66), which calls all five members across its login and registration flows: the lockout check (:131), an increment on both the unknown-user and wrong-password branches (:146, :161), the reset on success (:178), and the registration rate-limit check and increment (:197, :256). Incrementing on the unknown-user branch as well as the wrong-password branch is what keeps the endpoint from becoming a user-enumeration oracle by timing or by lockout behavior.
    764. Caveats / not-in-source: the increment is documented in source as not atomic on the distributed cache today (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/LoginProtectionService.cs:55-63). DistributedCacheService.IncrementAsync is a read-modify-write, because the Redis INCR it used to issue wrote a plain string key while IDistributedCache reads entries back as hashes, and the mismatch made the counter unreadable (WRONGTYPE). The accepted cost: genuinely parallel attempts can overwrite each other's increments, so a concurrent burst can stay under MaxFailedAttempts. Sequential guessing, which is what a credential-stuffing run against one account looks like, still trips the lockout. The comment names the two ways to close the gap (a Lua script that increments within the hash layout, or moving counters off IDistributedCache); neither is implemented today.
    765. PasswordResetTokenService

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Auth · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:26 · Level 6 · class (sealed)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Auth · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:33 · Level 6 · class (sealed)

        -
      • What it is: the IPasswordResetTokenService implementation, and the whole forgot-password token lifecycle in one file: issue a single-use token for an address, throttle how often one address can ask, hash the token at rest, cap wrong guesses, and consume the token on a successful redeem (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:12-24).
      • -
      • Depends on: IPasswordResetTokenService (the Application port, :5); PasswordResetSettings via IOptions<> (:28, snapshotted at :32); ICacheService (:6, :27); PasswordResetEntry (its cached record); Result and Error (:7); EmailIdentity, the shared normalizer, at the two key builders (:40, :42); and from the BCL SHA256, RandomNumberGenerator, CryptographicOperations, and System.Buffers.Text.Base64Url (:1-3).
      • -
      • Concept introduced: a reset token is a bearer credential, so treat it like a password. [Rubric §11, Security] assesses credential issuance and redemption; [Rubric §8, Data Architecture] assesses picking the right store for the right lifetime. Four properties are designed in, and the class doc lists all four up front (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:15-24).
          -
        • Hashed at rest. Only SHA256.HashData(...) of the token is stored (:44-45, :72), so a cache dump does not hand out working reset links. Like RefreshSession and unlike a password, a reset token is high-entropy (32 random bytes, :30, :68) and short-lived, which is why a plain digest is sufficient here where PasswordHasher needs 600,000 PBKDF2 iterations: there is no dictionary to run against a 256-bit random value.
        • -
        • One active token per email. The key is derived purely from the address (:40), so SetAsync overwrites (:77) and an older link stops working the moment a newer one is requested.
        • -
        • Attempt cap. Wrong tokens are counted on the record and the record is discarded at MaxValidationAttempts (:129-133), which turns the token into a credential you cannot grind at.
        • +
        • What it is: the IPasswordResetTokenService implementation, and the whole forgot-password token lifecycle in one file: issue a single-use token for an address, throttle how often one address can ask, hash the token at rest, cap wrong guesses, and consume the token exactly once on a successful redeem (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:12-28).
        • +
        • Depends on: IPasswordResetTokenService (the Application port, :5, :37); PasswordResetSettings via IOptions<> (:35, snapshotted at :45); ICacheService (:6, :34); IDistributedLock (:36), which serializes redemptions of one token (:31); TimeProvider (:37), the clock for the expiry stamp and the remaining-lifetime check (:32); PasswordResetEntry (its cached record); Result and Error (:7); EmailIdentity, the shared normalizer, at the two key builders (:53, :55); and from the BCL SHA256, RandomNumberGenerator, CryptographicOperations, and System.Buffers.Text.Base64Url (:1-3).
        • +
        • Concept introduced: a reset token is a bearer credential, so treat it like a password. [Rubric §11, Security] assesses credential issuance and redemption; [Rubric §8, Data Architecture] assesses picking the right store for the right lifetime. Five properties are designed in, and the class doc lists all five up front (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:15-27).
            +
          • Hashed at rest. Only SHA256.HashData(...) of the token is stored (:57-58, :85), so a cache dump does not hand out working reset links. Like RefreshSession and unlike a password, a reset token is high-entropy (32 random bytes, :39, :81) and short-lived, which is why a plain digest is sufficient here where PasswordHasher needs 600,000 PBKDF2 iterations: there is no dictionary to run against a 256-bit random value.
          • +
          • One active token per email. The key is derived purely from the address (:53), so SetAsync overwrites (:90) and an older link stops working the moment a newer one is requested.
          • +
          • Attempt cap. Wrong tokens are counted on the record and the record is discarded at MaxValidationAttempts (:168-172), which turns the token into a credential you cannot grind at.
          • +
          • Single redemption. The read, compare and remove run as one critical section under an IDistributedLock on the token key (:24-26, :103-117), so two concurrent redemptions of one token cannot both succeed, and a redemption that cannot take the lock is answered as invalid rather than as a second success. The lookup itself reads the shared store, so a token consumed on another replica is a miss rather than a stale local copy (:126-128).
          • No schema change, no sweeper. The whole lifecycle rides ICacheService, so expiry is the cache TTL rather than a background job over a table (ADR-091). Compare LoginProtectionService, which reaches the same conclusion for lockout counters.
        • Walkthrough
            -
          • Primary constructor takes ICacheService cacheService and IOptions<PasswordResetSettings> settings (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:26-28), snapshotting settings.Value into _settings (:32). TokenByteLength = 32 (:30) is the only other constant.
          • -
          • TokenKey and RequestKey (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:40-42) each call EmailIdentity.Normalize(email) rather than carrying a private normalizer, and the doc comment cites LoginProtectionService as the origin of the rule (:34-39): keys built from raw request input would give User@x.com and user@x.com independent tokens and independent request counters while resolving to one account. TokenKey produces pwdreset:token:{normalized} (:40) and RequestKey produces pwdreset:req:{normalized} (:42). HashToken is the shared SHA-256 helper (:44-45).
          • -
          • IssueAsync(string email, UserIdentifierType userId, CancellationToken) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:48-80): throttle first. It increments the per-email request counter with the RequestWindowMinutes TTL (:55-58) and fails with Error.Unauthorized("Auth.ResetThrottled", ...) once the count exceeds MaxRequestsPerEmail (:60-66). Only then does it mint the token: 32 CSPRNG bytes rendered with Base64Url.EncodeToString (:68, URL-safe because the token travels in a query string), builds a PasswordResetEntry holding the Base64 digest, the user id, a zero attempt count and the absolute expiry as Unix seconds (:71-75), caches it under the token key with the configured lifetime (:77), and returns the raw token to the caller to email (:79). The raw token exists only in that return value: it is never written anywhere.
          • -
          • ValidateAndConsumeAsync(string email, string token, CancellationToken) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:83-119): loads the entry (:88-89) and returns InvalidToken() when there is none (:90-93). A FormatException decoding the stored Base64 removes the unreadable record rather than leaving it to expire (:96-105). The comparison is CryptographicOperations.FixedTimeEquals over the two digests (:107), the same timing-side-channel defense PasswordHasher uses, with token ?? string.Empty so a null token hashes rather than throwing. A mismatch records a failed attempt and returns the same generic failure (:108-111). On a match it removes both the token key and the address's request counter (:115-116), so a successful reset does not leave the user throttled out of a later legitimate request (:113-114), and returns the entry's UserId (:118).
          • -
          • RecordFailedAttemptAsync (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:121-142): computes attempts = entry.FailedAttempts + 1 and the remaining lifetime from ExpiresAtUnixSeconds (:126-127). At MaxValidationAttempts, or once the remaining lifetime is non-positive, it deletes the record (:129-133). Otherwise it rewrites the entry with entry with { FailedAttempts = attempts } and a TTL of the remaining seconds, not a fresh lifetime (:135-141), because a wrong guess must not be able to extend how long the token stays redeemable.
          • -
          • InvalidToken() (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:144-148) is the single failure factory: unknown, expired, mismatched and attempt-capped all collapse to one Auth.InvalidResetToken error with one message. That uniformity is deliberate: distinct errors would make the endpoint an oracle for which addresses have an outstanding reset.
          • +
          • Primary constructor takes ICacheService cacheService, IOptions<PasswordResetSettings> settings, IDistributedLock distributedLock and TimeProvider timeProvider (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:33-37), snapshotting settings.Value into _settings (:45). TokenByteLength = 32 (:39) is the one constant; two static timeouts govern the redeem lock, RedeemLockTimeToLive of 10 seconds (:41) and RedeemLockWait of 5 seconds (:43).
          • +
          • TokenKey and RequestKey (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:53-55) each call EmailIdentity.Normalize(email) rather than carrying a private normalizer, and the doc comment cites LoginProtectionService as the origin of the rule (:47-52): keys built from raw request input would give User@x.com and user@x.com independent tokens and independent request counters while resolving to one account. TokenKey produces pwdreset:token:{normalized} (:53) and RequestKey produces pwdreset:req:{normalized} (:55). HashToken is the shared SHA-256 helper (:57-58).
          • +
          • IssueAsync(string email, UserIdentifierType userId, CancellationToken) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:61-93): throttle first. It increments the per-email request counter with the RequestWindowMinutes TTL (:68-71) and fails with Error.Unauthorized("Auth.ResetThrottled", ...) once the count exceeds MaxRequestsPerEmail (:73-79). Only then does it mint the token: 32 CSPRNG bytes rendered with Base64Url.EncodeToString (:81, URL-safe because the token travels in a query string), builds a PasswordResetEntry holding the Base64 digest, the user id, a zero attempt count and the absolute expiry as Unix seconds computed from the injected clock (:84-88), caches it under the token key with the configured lifetime (:90), and returns the raw token to the caller to email (:92). The raw token exists only in that return value: it is never written anywhere.
          • +
          • ValidateAndConsumeAsync(string email, string token, CancellationToken) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:96-118): builds the token key (:101), then tries to acquire the distributed lock lock:{key} with the 10-second TTL and a 5-second wait (:106-108). A null handle (contended or unavailable) returns InvalidToken() (:109-112); otherwise it runs RedeemAsync inside an await using over the handle, so the lock is released however the redemption ends (:114-117). The comment at :103-105 states the reason: without the critical section, two callers that both read before either removes would both succeed.
          • +
          • RedeemAsync (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:120-158): loads the entry with GetFromSharedStoreAsync (:128) and returns InvalidToken() when there is none (:129-132). A FormatException decoding the stored Base64 removes the unreadable record rather than leaving it to expire (:134-144). The comparison is CryptographicOperations.FixedTimeEquals over the two digests (:146), the same timing-side-channel defense PasswordHasher uses, with token ?? string.Empty so a null token hashes rather than throwing. A mismatch records a failed attempt and returns the same generic failure (:146-150). On a match it removes both the token key and the address's request counter (:154-155), so a successful reset does not leave the user throttled out of a later legitimate request (:152-153), and returns the entry's UserId (:157).
          • +
          • RecordFailedAttemptAsync (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:160-181): computes attempts = entry.FailedAttempts + 1 and the remaining lifetime from ExpiresAtUnixSeconds against the injected clock (:165-166). At MaxValidationAttempts, or once the remaining lifetime is non-positive, it deletes the record (:168-172). Otherwise it rewrites the entry with entry with { FailedAttempts = attempts } and a TTL of the remaining seconds, not a fresh lifetime (:174-180), because a wrong guess must not be able to extend how long the token stays redeemable.
          • +
          • InvalidToken() (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:183-187) is the single failure factory: unknown, expired, mismatched, attempt-capped and lock-contended all collapse to one Auth.InvalidResetToken error with one message. That uniformity is deliberate: distinct errors would make the endpoint an oracle for which addresses have an outstanding reset.
        • -
        • Why it's built this way: ADR-091 records the decision. It extends ADR-029 (the cache-backed protection idiom reused here) and sits beside ADR-032, which decided how a password is stored but not how a user who has lost one gets a new one. Keeping the token out of the database is what makes the feature additive: no migration, no new table, and nothing to reap.
        • -
        • Where it's used: registered services.TryAddScoped<IPasswordResetTokenService, PasswordResetTokenService>() (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:158), directly after the PasswordResetSettings binding (:137-140). ForgotPasswordHandlerBase<TUser, TCommand> calls IssueAsync and emails the resulting link (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ForgotPassword/ForgotPasswordHandlerBase.cs:73); ResetPasswordHandlerBase<TUser, TCommand> calls ValidateAndConsumeAsync (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:76-78) before the save, because leaving the token live until the write succeeds would open a replay window, and a token burned by a later invariant failure only costs the user one more reset request (:58-60). It is unit-tested by PasswordResetTokenServiceTests.
        • -
        • Caveats / not-in-source: the per-email request throttle inherits LoginProtectionService's non-atomic increment, and the source says so where it matters (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:53-54): concurrent requests can undercount, which loosens the throttle but never tightens it. The failed-attempt rewrite is a read-modify-write too, so a burst of simultaneous wrong guesses can lose increments against the attempt cap; sequential guessing still trips it.
        • +
        • Why it's built this way: ADR-091 records the decision. It extends ADR-029 (the cache-backed protection idiom reused here) and sits beside ADR-032, which decided how a password is stored but not how a user who has lost one gets a new one. Keeping the token out of the database is what makes the feature additive: no migration, no new table, and nothing to reap. The redeem lock is the ADR-108 primitive, and the shared-store read leans on the ADR-077 cache substrate. Taking TimeProvider rather than reading DateTimeOffset.UtcNow lets a test move the clock past the expiry without waiting.
        • +
        • Where it's used: registered services.TryAddScoped<IPasswordResetTokenService, PasswordResetTokenService>() (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:164), directly after the PasswordResetSettings binding (:160-161). ForgotPasswordHandlerBase<TUser, TCommand> calls IssueAsync and emails the resulting link (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ForgotPassword/ForgotPasswordHandlerBase.cs:73); ResetPasswordHandlerBase<TUser, TCommand> calls ValidateAndConsumeAsync (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:76-77) before the save, because leaving the token live until the write succeeds would open a replay window, and a token burned by a later invariant failure only costs the user one more reset request (:74-75). It is unit-tested by PasswordResetTokenServiceTests.
        • +
        • Caveats / not-in-source: the per-email request throttle inherits LoginProtectionService's non-atomic increment, and the source says so where it matters (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:66-67): concurrent requests can undercount, which loosens the throttle but never tightens it. The redeem lock serializes redemptions, so the failed-attempt rewrite (also a read-modify-write) no longer races another redemption of the same token on a host whose lock is shared; the lock's own cross-replica strength depends on which IDistributedLock the host registers, which is not determinable from this file.

        AdministrationPermissions

        @@ -3656,14 +4063,14 @@

        AuthClaimTypes

      • Permission is written by TokenService itself, one claim per permission the injected IPermissionRegistry grants the token's role, sorted ordinally and deduplicated against any permission claim the caller already supplied - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:118-130), and is read + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:129-141), and is read by PermissionAuthorizationHandler, which checks context.User.HasClaim(AuthClaimTypes.Permission, requirement.Permission) before falling back to the registry (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/PermissionAuthorizationHandler.cs:30-31), and by ClaimsPrincipalExtensions.HasPermissionClaim.
      • Subject is written by TokenService as JwtRegisteredClaimNames.Sub - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:105, with the + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:116, with the one-carrier rationale at :88-91) and read through ClaimsPrincipalExtensions by CurrentUserService, ClaimBasedUserIdProvider, @@ -3671,8 +4078,8 @@

        AuthClaimTypes

        partitioner.
      • SessionId is stamped by the private SessionStampingTokenService decorator inside - AuthenticationServiceBase<TUser> - (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:972) and read + AuthSessionIssuer + (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Sessions/AuthSessionIssuer.cs:448) and read back by FindSessionId for the "my sessions" endpoint (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/AuthControllerBase.cs:187).
      • MultiFactor and its two method values are stamped by the sign-in flow after a second factor @@ -3682,7 +4089,7 @@

        AuthClaimTypes

    766. Caveats / not-in-source: the Permission claim is now written by every token the framework - mints (TokenService.cs:118-130), which supersedes the older behavior where no shipped token + mints (TokenService.cs:129-141), which supersedes the older behavior where no shipped token issuer wrote it; a reader depending on Permission being absent from real tokens would be wrong today. The registry lookup remains the authoritative check either way, since the claim path is additive.
    767. @@ -3804,6 +4211,59 @@

      IUserAdminDTO

      UserListDTO (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/UserListDTO.cs). +

      PasswordComplexity

      +
      +

      MMCA.Common.Shared · MMCA.Common.Shared.Auth · MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/PasswordComplexity.cs:18 · Level 0 · class (static partial)

      +
      +
        +
      • What it is: the one definition of the strong-password rule: the two length bounds, four + source-generated character-class regexes, and an Evaluate predicate that combines them + (MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/PasswordComplexity.cs:5-18).
      • +
      • Depends on: nothing first-party; BCL System.Text.RegularExpressions and the + [GeneratedRegex] source generator (PasswordComplexity.cs:1,34).
      • +
      • Concept introduced, one rule evaluated on both sides of the wire. [Rubric §24, Forms, Validation & UX Safety] assesses validation parity: the client form must give the verdict the + API would. The doc comment states the purpose directly (PasswordComplexity.cs:5-10): the server + validator (StrongPasswordRules<T> + in MMCA.Common.Application) and the client attribute + (PasswordComplexityAttribute in + MMCA.Common.UI) both read this type, so they cannot disagree on the same input. It lives in Shared + because Shared is the one project both of those layers may reference (UI depends on Shared only). + The character classes are Unicode-aware (PasswordComplexity.cs:11-16): an accented or CJK letter + counts as a letter, never as the "special" character.
      • +
      • Walkthrough: MinimumLength is 8 (PasswordComplexity.cs:28) and MaximumLength is 128 + (PasswordComplexity.cs:31), both const int measured in UTF-16 code units, so they are usable in + attribute arguments. Four public static partial Regex properties are source-generated by + [GeneratedRegex], each with a 1000 ms match timeout: Uppercase (\p{Lu}, + PasswordComplexity.cs:34-35), Lowercase (\p{Ll}, :38-39), Digit (\p{Nd}, :42-43), and + SpecialCharacter ([^\p{L}\p{Nd}], anything that is neither a letter nor a decimal digit, + :49-50). Evaluate(string? password) (PasswordComplexity.cs:52-57) returns true only when the + input is non-null, at least MinimumLength long, and matches all four regexes. It deliberately does + not check MaximumLength: the upper bound is a separate rule with its own message on both + sides (PasswordComplexity.cs:52-56).
      • +
      • Why it's built this way: a client rule and a server rule written separately are two copies of + one policy that drift independently. Exposing the regexes (not only Evaluate) lets the + server's FluentValidation chain keep one message per failed class, while the client attribute asks + the single yes/no question. Source-generated regexes avoid runtime regex construction, and the + match timeout bounds evaluation of untrusted input.
      • +
      • Where it's used: StrongPasswordRules<T> feeds both length constants and all four regexes into + its rule chain + (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:196-201); + PasswordComplexityAttribute calls + Evaluate, leaving empty input to [Required] + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/PasswordComplexityAttribute.cs:24); + RegisterModel and + ResetPasswordModel pair that attribute with + [StringLength(PasswordComplexity.MaximumLength)] to supply the separate upper-bound rule + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/RegisterModel.cs:27-28, + MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/ResetPasswordModel.cs:25-26); pinned by + MMCA.Common/Tests/Core/MMCA.Common.Shared.Tests/Auth/PasswordComplexityTests.cs.
      • +
      • Caveats / not-in-source: the server's error messages still hard-code the numbers ("at least 8 + characters", "longer than 128 characters") rather than interpolating the constants + (CommonValidationRules.cs:196-197), and so does the attribute's default message + (PasswordComplexityAttribute.cs:18); changing a bound means editing those strings too. The + plain PasswordRules<T> sibling in the same file still uses literal 8/128 + (CommonValidationRules.cs:180-181) and does not read this type.
      • +

      ClaimsPrincipalExtensions

      MMCA.Common.Shared · MMCA.Common.Shared.Auth · MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/ClaimsPrincipalExtensions.cs:18 · Level 1 · class (static)

      @@ -3883,10 +4343,10 @@

      ClaimsPrincipalExtensions

    768. Why it's built this way: TokenService records the other half of the story in a - comment (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:99-102): a + comment (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:110-113): a duplicate custom user-id claim used to ride alongside sub, which meant two values that could disagree and two claim names every reader had to know. Collapsing the writer to one claim - (TokenService.cs:105) is only safe because one reader absorbs the mapping difference, which is this + (TokenService.cs:116) is only safe because one reader absorbs the mapping difference, which is this type. The sid half comes from ADR-097.

    769. @@ -3898,19 +4358,19 @@

      ClaimsPrincipalExtensions

      (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Context/ClaimBasedUserIdProvider.cs:15); the IdempotencyFilter uses it to scope an idempotency key to a caller - (MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:487); + (MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:541); CurrentUserTargetingContextAccessor uses it for feature-flag targeting (MMCA.Common/Source/Presentation/MMCA.Common.API/FeatureManagement/CurrentUserTargetingContextAccessor.cs:17,86); the opt-in "UserPolicy" rate-limit partition keys on httpContext.User?.Identity?.Name directly rather than on GetUserId() - (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:171-175); + (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:172-177); AuthControllerBase uses FindSessionId() to tell the session list which row is the caller's own (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/AuthControllerBase.cs:187); AuthenticationServiceBase<TUser> uses GetUserId() on the principal recovered from an expired access token during rotation - (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:355-358); and + (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:304-307); and TestPrincipal writes sub precisely so test principals resolve the same way real ones do (MMCA.Common/Source/Hosting/MMCA.Common.Testing.UI/Infrastructure/TestPrincipal.cs:19,27); @@ -4298,7 +4758,7 @@

      ForgotPasswordRequest

      posted by AuthUIService's RequestPasswordResetAsync, deliberately over a bearer-free client so a signed-in caller does not bind the reset to the current session - (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/AuthUIService.cs:204,212). + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/AuthUIService.cs:198,206).
    770. Caveats / not-in-source: both applications now wire this vertical. ADC has a ForgotPasswordCommand (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ForgotPassword/ForgotPasswordCommand.cs:12-13) @@ -4337,7 +4797,7 @@

      LoginRequest

      of the credential was wrong (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Validation/LoginRequestValidator.cs:6-10,15-20); then handled by AuthenticationServiceBase<TUser>.LoginAsync - (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:156-157), which + (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:105-106), which is reached through AuthControllerBase's POST login (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/AuthControllerBase.cs:69,76-77).
    771. @@ -4367,10 +4827,10 @@

      OAuthCodeExchangeRequest

      putting it in a URL acceptable. OAuthControllerBase.ExchangeAsync rejects a blank code (OAuthControllerBase.cs:185-188), looks the code up in - ICacheService (OAuthControllerBase.cs:190-198), and then - removes it so a replayed code cannot mint a second token pair (OAuthControllerBase.cs:200-201); an + ICacheService (OAuthControllerBase.cs:190-200), and then + removes it so a replayed code cannot mint a second token pair (OAuthControllerBase.cs:202-203); an unknown, burned, or expired code all return the same HTTP 400 with a deliberately non-specific - message (OAuthControllerBase.cs:206-209). The action is also marked [NonIdempotent] with the + message (OAuthControllerBase.cs:208-211). The action is also marked [NonIdempotent] with the reason inline: replaying a stored response would defeat the burn and let a leaked code mint the same tokens again (OAuthControllerBase.cs:178).
    772. Where it's used: the body of the OAuth exchange endpoint @@ -4399,9 +4859,9 @@

      RefreshTokenRequest

    773. Where it's used: shape-validated by RefreshTokenRequestValidator, handled by AuthenticationServiceBase<TUser>.RefreshTokenAsync - (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:334-335), which + (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:283-284), which rejects an unreadable token or a principal with no usable user id with an Auth.InvalidToken - failure before it ever looks at the refresh token (AuthenticationServiceBase.cs:351-352,358-362); + failure before it ever looks at the refresh token (AuthenticationServiceBase.cs:300-301,307-311); exposed by AuthControllerBase's POST refresh (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/AuthControllerBase.cs:117,122-123).
    774. @@ -4450,7 +4910,7 @@

      ResetPasswordRequest

      the account's failed-attempt count so a user who reset because of a lockout is not still locked out (ResetPasswordHandlerBase.cs:94-95,101,109-110); posted by AuthUIService's ResetPasswordAsync - (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/AuthUIService.cs:221,231). ADC + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/AuthUIService.cs:215,225). ADC carries it in a ResetPasswordCommand marked ICacheInvalidating (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ResetPassword/ResetPasswordCommand.cs:15-16); @@ -4599,14 +5059,14 @@

      RegisterRequest

      while AuthenticationServiceBase<TUser> hands the whole request to an abstract CreateUser(RegisterRequest request, byte[] passwordHash, byte[] passwordSalt) that each app implements against its own User aggregate - (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:594). Note + (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:447). Note that Password is a plain string on the contract and never travels past the hashing call: RegisterAsync turns it into a hash and salt pair, and that pair, not the password, is what - reaches CreateUser and the aggregate (AuthenticationServiceBase.cs:280-281). + reaches CreateUser and the aggregate (AuthenticationServiceBase.cs:229-230). [Rubric §11, Security].
    775. Where it's used: AuthenticationServiceBase<TUser>.RegisterAsync - (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:254-255), the + (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:203-204), the register endpoint on AuthControllerBase, which binds it [FromBody] on an anonymous, rate-limited, idempotent POST (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/AuthControllerBase.cs:93-102), and @@ -4615,7 +5075,7 @@

      RegisterRequest

      (MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Application/Users/AuthenticationService.cs:52-60), while ADC ignores it entirely and creates the user from email, names, hash, salt, and the default UserRole.Attendee - (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:143-150).
    776. + (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:139-146).

      AuthenticationResponse

      @@ -4641,12 +5101,12 @@

      AuthenticationResponse

      OAuthControllerBase therefore detects a missing exchange entry by testing string.IsNullOrEmpty(response.AccessToken), with the reason written down at the call site - (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/OAuthControllerBase.cs:192-195). + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/OAuthControllerBase.cs:192-197).
    777. Where it's used: produced by AuthenticationServiceBase<TUser> from the shared IssueTokensAsync helper that login and registration both funnel through (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationServiceBase.cs:183,263,474,494) - and directly at the end of a rotation (AuthenticationServiceBase.cs:329); declared as the 200/201 + and directly at the end of a rotation (AuthenticationServiceBase.cs:278); declared as the 200/201 response type on the three AuthControllerBase token endpoints (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/AuthControllerBase.cs:73,97,120); @@ -4794,7 +5254,7 @@

      ModuleNameConventions

      EntityTypeConfiguration<TEntity, TIdentifierType> uses for the SQL Server schema name (falling back to dbo, .../Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs:66) and the Cosmos container - name (EntityTypeConfiguration.cs:87), and what + name (.../DataSources/Engines/CosmosDataSourceEngine.cs:95), and what EntityDataSourceRegistry uses to derive a logical database name when no [UseDatabase] attribute overrides it (.../DataSources/EntityDataSourceRegistry.cs:181).
    778. @@ -4846,14 +5306,14 @@

      RefreshSessionSummaryResponse

    779. Concept introduced, the sanitized read model over a credential-bearing entity. [Rubric §11, Security] assesses what a response is allowed to expose, and [Rubric §9, API & Contract Design] assesses whether a contract carries exactly the fields its consumers need. The entity behind this row holds the material the refresh-token reuse check runs on: a TokenHash - (MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:64) and the rotation link - ReplacedByTokenHash (RefreshSession.cs:79). Both are deliberately absent from this response, + (MMCA.Common/Source/Core/MMCA.Common.Domain/Auth/RefreshSession.cs:72) and the rotation link + ReplacedByTokenHash (RefreshSession.cs:87). Both are deliberately absent from this response, and the doc comment states the reasoning (RefreshSessionSummaryResponse.cs:6-11): shipping either would hand every caller a queryable index of another session's credentials-at-rest for no gain, since nothing a client does with a session needs anything but its id. The rule is enforced by a reflection assertion rather than by review habit: a test asserts the type's property names contain neither TokenHash nor ReplacedByTokenHash - (MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Auth/RefreshSessionManagementTests.cs:200-203). + (MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Auth/RefreshSessionManagementTests.cs:201-204). This is the read-model half of the "never project a secret" discipline; the same instinct is what keeps password hashes out of every user DTO.
    780. Walkthrough: six positional parameters on a sealed record @@ -4883,15 +5343,15 @@

      RefreshSessionSummaryResponse

      AuthenticationServiceBase<TUser>.GetSessionsAsync, which reads unrevoked sessions from IRefreshSessionStore, filters to those active at the current instant, orders newest-first, and projects each into this record - (AuthenticationServiceBase.cs:481-504); returned by the GET my-sessions endpoint on + (AuthenticationServiceBase.cs:386-393)
      ; returned by the GET my-sessions endpoint on AuthControllerBase, which supplies the caller's own session via User.FindSessionId() (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/AuthControllerBase.cs:175-187); fetched client-side by AuthUIService.GetSessionsAsync - (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/AuthUIService.cs:239,246) and + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/AuthUIService.cs:233,240) and rendered by the Sessions page, which uses IsCurrent to disable the revoke action on the caller's own row - (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Sessions.razor.cs:38,108-110,179).
    781. + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Sessions.razor.cs:39,110-112,216).
    782. Caveats / not-in-source: IpAddress and UserAgent are whatever the client sent at issue time and are stored verbatim; nothing in this type or the projection validates, geolocates, or canonicalizes them, so a spoofed user-agent shows up as-is in the device list.
    783. @@ -4930,13 +5390,13 @@

      Releaser

      because releasing a lock held in a remote store is I/O.
    784. Where it's used: every caller of AcquireAsync, always inside a using: MemoryCacheService at - MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:101,112,132, + MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:111,122,142, CookieSessionRefresher at - MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:103, + MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:153, IdempotencyFilter at - MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:206, and the + MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:207, and the ICacheService GetOrCreateAsync default implementation at - MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:112. + MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:159. CachingQueryDecorator<TQuery, TResult> is the one caller that names the type explicitly: its TryAcquirePopulateLockAsync returns KeyedSemaphoreStripe.Releaser? @@ -5230,7 +5690,7 @@

      KeyedSemaphoreStripe

    785. Width is a get-only property (:50), exposed so tests can reason about collisions; one test computes the exact stripe index a key lands on, precisely so it "cannot flake on the one-in-DefaultWidth collision" - (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs:270,291).
    786. + (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/SessionCookies/CookieSessionRefresherTests.cs:496,517).
    787. AcquireAsync (:60) resolves the stripe, awaits WaitAsync(cancellationToken) with ConfigureAwait(false) per ADR-049 (:63), and @@ -5253,19 +5713,19 @@

      KeyedSemaphoreStripe

      process-local lock only serializes duplicates that land on the same replica (017-request-idempotency.md:91-93), which is why IDistributedLock is preferred when present - (MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:34-37).

      + (MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:35-38).

    788. Where it's used: five holders, all static or instance fields that live for the lifetime of their owner, matching the remark that instances are "intended to be held in a static field for the process lifetime" and that stripes are never disposed (KeyedSemaphoreStripe.cs:18-21): - IdempotencyFilter (IdempotencyFilter.cs:90), + IdempotencyFilter (IdempotencyFilter.cs:91), CookieSessionRefresher - (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:63), + (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/CookieSessionRefresher.cs:87), MemoryCacheService - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:38), the + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:45), the CacheKeyLocks holder behind ICacheService's GetOrCreateAsync default implementation - (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:142-145), and the + (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:189-192), and the QueryCacheKeyLocks holder behind CachingQueryDecorator<TQuery, TResult> (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/CachingQueryDecorator.cs:247-250). @@ -5387,9 +5847,9 @@

      ConcurrencyETag

      the error decision to the caller keeps the parser free of HTTP status opinions.
    789. Where it's used: server side, EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>.SetConcurrencyETag - (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:405) writes + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:417) writes Response.Headers[ConcurrencyETag.ETagHeaderName] = ConcurrencyETag.Format(rowVersion) - (EntityControllerBase.cs:413) after a successful by-id read (EntityControllerBase.cs:370), and + (EntityControllerBase.cs:425) after a successful by-id read (EntityControllerBase.cs:382), and the CRUD base does the same after a create (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/CrudEntityControllerBase.cs:112); SupportsIfMatchAttribute reads the @@ -5399,7 +5859,7 @@

      ConcurrencyETag

      EntityServiceBase<TEntityDTO, TIdentifierType> formats the tag (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:199) and attaches it - as If-Match (EntityServiceBase.cs:394), as do ADC's + as If-Match (EntityServiceBase.cs:398), as do ADC's EventService (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Events/EventService.cs:29,41), SessionQuestionUIService @@ -5447,16 +5907,16 @@

      IdempotencyHeaders

    790. Where it's used: server side, IdempotencyFilter re-exports the request header name as a public property - (MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:73), reads it in - the one helper both filter stages share (IdempotencyFilter.cs:165), and appends the replay header - when it serves a cached response (IdempotencyFilter.cs:383); + (MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:74), reads it in + the one helper both filter stages share (IdempotencyFilter.cs:166), and appends the replay header + when it serves a cached response (IdempotencyFilter.cs:384); NotificationsController reads the same request header directly (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Notifications/NotificationsController.cs:62). Client side, EntityServiceBase<TEntityDTO, TIdentifierType> attaches a generated key on retried writes - (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:386), as do ADC's + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:390), as do ADC's SessionQuestionUIService (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Services/SessionLive/SessionQuestionUIService.cs:73) and LivePollUIService @@ -5470,7 +5930,7 @@

      MessageHeaders

    791. What it is: four const string message-broker header names carried on every outbound message: the tenant the publishing scope was resolved to, the publishing user's identifier, that user's roles as a comma-separated list, and the correlation id of the interaction that produced the - message (MessageHeaders.cs:25-38).
    792. + message (MessageHeaders.cs:25-34).
    793. Depends on: nothing; BCL-free constants only.
    794. Concept introduced, the cross-layer wire literal for a broker envelope. Same placement rule ConcurrencyETag and IdempotencyHeaders follow for an @@ -5536,7 +5996,8 @@

      StronglyTypedIdValueParser<TValue&g ADR-115 is the governing record for the opt-in strongly typed identifier feature this class supports.

    795. Where it's used: exclusively by StronglyTypedId.TryParse - (StronglyTypedId.cs:258-259); nothing outside the defining file references it directly.
    796. + (StronglyTypedId.cs:67) and CanParseValues (StronglyTypedId.cs:87); nothing outside the + defining file references it directly.

      IStronglyTypedId<TSelf, TValue>

      @@ -5642,7 +6103,7 @@

      ProblemDetailsResultReader

      body (or a non-JSON body, or no body at all) and turns it back into the Error list and failed Result the server started from - (MMCA.Common/Source/Core/MMCA.Common.Shared/Http/ProblemDetailsResultReader.cs:58-476). + (MMCA.Common/Source/Core/MMCA.Common.Shared/Http/ProblemDetailsResultReader.cs:58-478).
    797. Depends on: Error, Result and ErrorType from MMCA.Common.Shared.Abstractions @@ -5690,36 +6151,41 @@

      ProblemDetailsResultReader

      ReadValidationDictionary (:190-192). Parsed errors win only when the list is non-empty (:195-198); otherwise it falls through to a synthesized error carrying detail or title (:201).
    798. -
    799. ResolveStatus (:419) is a small but deliberate affordance: a caller that passes a non-positive - status gets the status read out of the body's own status member instead (:426-430), which is what +
    800. ResolveStatus (:421) is a small but deliberate affordance: a caller that passes a non-positive + status gets the status read out of the body's own status member instead (:428-432), which is what makes the parser usable against a captured payload with no response object around it.
    801. -
    802. ReadErrorArray (:319) maps each object element through ReadErrorObject and, notably, still - salvages a degraded array of plain strings (:331-336). ReadErrorObject (:342) reads code, +
    803. ReadErrorArray (:321) maps each object element through ReadErrorObject and, notably, still + salvages a degraded array of plain strings (:333-338). ReadErrorObject (:344) reads code, message, type, source and target, with a three-step fallback for the message (message, then code, then the generic default) so an Error is never constructed with an empty - one (:348-353).
    804. -
    805. ReadValidationDictionary (:356) handles the standard ASP.NET Core shape. The key becomes + one (:350-355).
    806. +
    807. ReadValidationDictionary (:358) handles the standard ASP.NET Core shape. The key becomes Validation.{propertyName}, or bare Validation for an object-level rule with an empty key - (:363-365), and the property name is carried separately as Target (:366). A value may be an - array of messages or a single one, and both paths funnel through AddValidationError (:368-378), - which silently ignores non-string and blank entries (:391-400).
    808. -
    809. ParseErrorType (:403) is the one place an inbound string becomes an enum, and it is + (:365-367), and the property name is carried separately as Target (:368). A value may be an + array of messages or a single one, and both paths funnel through AddValidationError (:370-380), + which silently ignores non-string and blank entries (:393-402).
    810. +
    811. ParseErrorType (:405) is the one place an inbound string becomes an enum, and it is defensive in the right way: Enum.TryParse with ignoreCase: true plus Enum.IsDefined - (:405-406). Without the second check TryParse would happily accept an arbitrary numeric string and + (:407-408). Without the second check TryParse would happily accept an arbitrary numeric string and hand back an undefined enum value.
    812. -
    813. ToFailureResult (:212) lifts the parsed errors into a failed Result. ReadAsync (:223) - short-circuits on a 2xx to Result.Success() (:229-232) and otherwise parses the body. - ReadAsync<T> (:257) is the value-returning overload: a non-success status parses errors (:269), - a blank 2xx body is a failure coded EmptyResponseCode (:272-279), a body that deserializes to - null is the same failure with a different message (:284-289), and a JsonException becomes - MalformedResponseCode (:292-295). The "204 is a failure here" rule is stated in the doc with its +
    814. ToFailureResult (:212) lifts the parsed errors into a failed Result. ReadAsync (:224) + short-circuits on a 2xx to Result.Success() (:230-233) and otherwise parses the body. + ReadAsync<T> (:259) is the value-returning overload: a non-success status parses errors (:271), + a blank 2xx body is a failure coded EmptyResponseCode (:274-281), a body that deserializes to + null is the same failure with a different message (:286-292), and a JsonException becomes + MalformedResponseCode (:294-297). The "204 is a failure here" rule is stated in the doc with its escape hatch: use the non-generic overload for endpoints that legitimately answer without a body - (:241-246).
    815. -
    816. ReadBodyAsync (:298) buffers the content as a string and swallows an HttpRequestException back - to null (:311-316), with the comment explaining the judgement: a truncated body should still + (:242-247).
    817. +
    818. Both ReadAsync overloads carry a SuppressMessage for analyzer RS0026 ("Do not add multiple + public overloads with optional parameters", :223,258). The justification records them as + grandfathered: released after the v1.152 public-API baseline while RS0026/RS0027 were off, so + reshaping either signature now would be a breaking change. The two-overload shape is therefore + frozen by the public API contract, not by preference.
    819. +
    820. ReadBodyAsync (:300) buffers the content as a string and swallows an HttpRequestException back + to null (:313-318), with the comment explaining the judgement: a truncated body should still report the status-level failure rather than surface a transport exception "from a reader".
    821. -
    822. TryGetProperty (:444) does case-insensitive member lookup, trying the exact name first and only - then enumerating (:453-459). The doc gives the reason (:438-443): the wire form is camelCase, but +
    823. TryGetProperty (:446) does case-insensitive member lookup, trying the exact name first and only + then enumerating (:455-461). The doc gives the reason (:440-445): the wire form is camelCase, but a hand-assembled or differently-configured payload can be PascalCase, and a reader that understood only one "would silently drop every error field".
    824. @@ -5734,7 +6200,7 @@

      ProblemDetailsResultReader

    825. Where it's used: it is the single funnel for every framework-shaped HTTP read on the client. EntityServiceBase<TEntityDTO, TIdentifierType> uses both overloads - (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:339,367, documented at + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:343,371, documented at :304,321,347), ChildEntityServiceBase uses all three call shapes @@ -5868,21 +6334,29 @@

      StronglyTypedIdTypeConverters

    826. What it is: the registration surface for StronglyTypedIdTypeConverter<TSelf, TValue>: register one identifier type explicitly, or scan an assembly and register every identifier type it declares - (StronglyTypedIdTypeConverter.cs:82-155).
    827. + (StronglyTypedIdTypeConverter.cs:82-166).
    828. Depends on: StronglyTypedId.GetValueType/IsStronglyTypedId for detection; StronglyTypedIdTypeConverter<TSelf, TValue>, attached via TypeDescriptor.AddAttributes.
    829. Concept introduced: none new; the assembly-scan companion to the per-type converter.
    830. -
    831. Walkthrough: Register(Type) (:600) resolves the wrapped value type or throws - ArgumentException naming the offending type (:604-607), builds the closed +
    832. Walkthrough: Register(Type) (:89) resolves the wrapped value type or throws + ArgumentException naming the offending type (:93-96), builds the closed StronglyTypedIdTypeConverter<,> and attaches it with - TypeDescriptor.AddAttributes(identifierType, new TypeConverterAttribute(converterType)) (:611). - RegisterAll(params Assembly[]) (:619) calls Register for every candidate GetIdentifierTypes - yields and returns the count. GetIdentifierTypes(Assembly) (:643) tolerates a partially loadable + TypeDescriptor.AddAttributes(identifierType, new TypeConverterAttribute(converterType)) (:100). + For a value-type identifier it then calls TypeDescriptor.Refresh on the closed Nullable<TId> + (:108-111). The comment gives the failure this prevents (:102-107): MVC binds an optional + TId? query-string parameter through Nullable<TId>'s converter, and TypeDescriptor caches that + NullableConverter with whatever underlying converter it saw first. If anything resolved it before + registration (another host in the process, a library scanning types), the cached converter still + wraps the default struct converter, which cannot read a string, so MVC treats TId? as a complex + type and the request answers 500. Refreshing drops the cache so the next lookup sees the converter + just registered. + RegisterAll(params Assembly[]) (:119) calls Register for every candidate GetIdentifierTypes + yields and returns the count. GetIdentifierTypes(Assembly) (:143) tolerates a partially loadable assembly: it catches ReflectionTypeLoadException and falls back to the partial Types array - (:649-656), the doc comment noting this matches the tolerance the architecture map's + (:149-156), the doc comment noting this matches the tolerance the architecture map's loadable-types helper applies elsewhere, then filters to non-generic value types satisfying - IsStronglyTypedId (:658-664).
    833. + IsStronglyTypedId (:158-164).
    834. Why it's built this way: ADR-115.
    835. Where it's used: called from MMCA.Common.Infrastructure's DependencyInjection @@ -6152,8 +6626,8 @@

      PrivacyFeatures

    836. Where it's used: the framework side is DataExportControllerBase<TQuery>, which carries [FeatureGate(PrivacyFeatures.DataExport)] on the class - (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:58) - with the rationale in the same file's remarks (DataExportControllerBase.cs:52-53). Both apps + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:60) + with the rationale in the same file's remarks (DataExportControllerBase.cs:54-55). Both apps subclass that base with a thin, route-only controller: UsersDataExportController (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersDataExportController.cs:26-35, @@ -6200,60 +6674,6 @@

      RefreshTokenRequestValidator

      AuthenticationValidators (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/AuthenticationValidators.cs:19,28).
    837. -

      ResetPasswordRequestValidator

      -
      -

      MMCA.Common.Application · MMCA.Common.Application.Auth.Validation · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Validation/ResetPasswordRequestValidator.cs:12 · Level 1 · class

      -
      -
        -
      • What it is: the validator for ResetPasswordRequest: address shape on - Email, presence on Token, and the shared strong-password policy on NewPassword - (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Validation/ResetPasswordRequestValidator.cs:16-23).
      • -
      • Depends on: ResetPasswordRequest; - StrongPasswordRules<T>; FluentValidation's - AbstractValidator<T> and its Include composition.
      • -
      • Concept introduced, composing a rule set with Include. [Rubric §11, Security] assesses whether - a policy holds on every path that can change the guarded value, and [Rubric §1, SOLID] the - single-responsibility split that makes that possible. A password-complexity policy is only a policy if - every write path enforces it; if registration demands an uppercase letter and reset does not, reset - is a documented downgrade route. FluentValidation's Include merges another validator's rules for the - same model type into this one, so the policy can live in exactly one class and be pulled into each - writer. The doc comment states the intent: the new password goes through "the same - StrongPasswordRules<T> the registration and change-password requests use, so a reset cannot be a way - around the complexity policy" (ResetPasswordRequestValidator.cs:8-10). StrongPasswordRules<T> is - generic over the containing model and takes a selector expression, which is what lets one rule set - attach to a differently-shaped request each time - (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:188-199): it - enforces non-empty, 8 to 128 characters, and one each of uppercase, lowercase, digit, and - non-alphanumeric.
      • -
      • Walkthrough: three statements in a block-bodied constructor - (ResetPasswordRequestValidator.cs:14-24). Email gets NotEmpty().EmailAddress() (:16-18), - matching the forgot-password half so the two steps agree on what an address is. Token gets - NotEmpty() with a reset-specific message (:20-21); no format check, because the token's validity is - a lookup, not a shape. Then - Include(new StrongPasswordRules<ResetPasswordRequest>(x => x.NewPassword)) (:23) grafts the seven - policy rules onto the NewPassword field. Note the contrast with the weaker sibling - PasswordRules<T> (CommonValidationRules.cs:174-181), which - enforces length only; reset deliberately takes the strong one.
      • -
      • Why it's built this way: the reset flow is - ADR-091, and the hashing - the accepted password ends up under is - ADR-102 (which supersedes - ADR-032). Neither is this validator's concern, which is the point: it only decides whether the - candidate is policy-compliant.
      • -
      • Where it's used: registered by the assembly scan - (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:48) and reached through - CommandRequestValidator<TCommand, TRequest> - for any command implementing ICommandWithRequest<ResetPasswordRequest>, the constraint - ResetPasswordHandlerBase<TUser, TCommand> - declares - (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:50). - The request arrives at - PasswordResetAuthControllerBase<TForgotPasswordCommand, TResetPasswordCommand> - (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/PasswordResetAuthControllerBase.cs:108), - which is why a policy failure surfaces as the documented 400 - (PasswordResetAuthControllerBase.cs:104) while a bad token collapses to 401 - (PasswordResetAuthControllerBase.cs:97,105).
      • -

      SendEmailConfirmationRequestValidator

      MMCA.Common.Application · MMCA.Common.Application.Auth.Validation · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Validation/EmailConfirmationRequestValidators.cs:11 · Level 1 · class

      @@ -6329,7 +6749,7 @@

      UserDataExportDTO

      the degradation path logs the exception but hands the subject a generic reason (ExportUserDataHandlerBase.cs:188-196); and the controller serializes to bytes and returns a file rather than an ObjectResult - (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:101-109). + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:103-113). [Rubric §9, API & Contract Design]: FormatVersion versions "the export document shape itself (not the app's data)" (UserDataExportDTO.cs:18-19), so a consumer parsing an old file can detect an envelope change rather than guess at it. @@ -6362,9 +6782,9 @@

      UserDataExportDTO

      GeneratedOn from an injected TimeProvider rather than a static clock (ExportUserDataHandlerBase.cs:113). DataExportControllerBase<TQuery> - declares it as the 200 response type (DataExportControllerBase.cs:78) and derives the download file + declares it as the 200 response type (DataExportControllerBase.cs:80) and derives the download file name from the package's own GeneratedOn so the file name and the document can never disagree - (DataExportControllerBase.cs:109,124-134). Both apps subclass the handler + (DataExportControllerBase.cs:113,128-138). Both apps subclass the handler (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ExportUserData/ExportUserDataHandler.cs:35, MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Application/Users/UseCases/ExportUserData/ExportUserDataHandler.cs:39) and expose it through their own @@ -6372,6 +6792,60 @@

      UserDataExportDTO

      (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersDataExportController.cs:27, MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.API/Controllers/UsersDataExportController.cs:29). +

      ResetPasswordRequestValidator

      +
      +

      MMCA.Common.Application · MMCA.Common.Application.Auth.Validation · MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Validation/ResetPasswordRequestValidator.cs:12 · Level 1 · class

      +
      +
        +
      • What it is: the validator for ResetPasswordRequest: address shape on + Email, presence on Token, and the shared strong-password policy on NewPassword + (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Validation/ResetPasswordRequestValidator.cs:16-23).
      • +
      • Depends on: ResetPasswordRequest; + StrongPasswordRules<T>; FluentValidation's + AbstractValidator<T> and its Include composition.
      • +
      • Concept introduced, composing a rule set with Include. [Rubric §11, Security] assesses whether + a policy holds on every path that can change the guarded value, and [Rubric §1, SOLID] the + single-responsibility split that makes that possible. A password-complexity policy is only a policy if + every write path enforces it; if registration demands an uppercase letter and reset does not, reset + is a documented downgrade route. FluentValidation's Include merges another validator's rules for the + same model type into this one, so the policy can live in exactly one class and be pulled into each + writer. The doc comment states the intent: the new password goes through "the same + StrongPasswordRules<T> the registration and change-password requests use, so a reset cannot be a way + around the complexity policy" (ResetPasswordRequestValidator.cs:8-10). StrongPasswordRules<T> is + generic over the containing model and takes a selector expression, which is what lets one rule set + attach to a differently-shaped request each time + (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:191-202): it + enforces non-empty, 8 to 128 characters, and one each of uppercase, lowercase, digit, and + non-alphanumeric.
      • +
      • Walkthrough: three statements in a block-bodied constructor + (ResetPasswordRequestValidator.cs:14-24). Email gets NotEmpty().EmailAddress() (:16-18), + matching the forgot-password half so the two steps agree on what an address is. Token gets + NotEmpty() with a reset-specific message (:20-21); no format check, because the token's validity is + a lookup, not a shape. Then + Include(new StrongPasswordRules<ResetPasswordRequest>(x => x.NewPassword)) (:23) grafts the seven + policy rules onto the NewPassword field. Note the contrast with the weaker sibling + PasswordRules<T> (CommonValidationRules.cs:175-182), which + enforces length only; reset deliberately takes the strong one.
      • +
      • Why it's built this way: the reset flow is + ADR-091, and the hashing + the accepted password ends up under is + ADR-102 (which supersedes + ADR-032). Neither is this validator's concern, which is the point: it only decides whether the + candidate is policy-compliant.
      • +
      • Where it's used: registered by the assembly scan + (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:48) and reached through + CommandRequestValidator<TCommand, TRequest> + for any command implementing ICommandWithRequest<ResetPasswordRequest>, the constraint + ResetPasswordHandlerBase<TUser, TCommand> + declares + (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:50). + The request arrives at + PasswordResetAuthControllerBase<TForgotPasswordCommand, TResetPasswordCommand> + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/PasswordResetAuthControllerBase.cs:108), + which is why a policy failure surfaces as the documented 400 + (PasswordResetAuthControllerBase.cs:104) while a bad token collapses to 401 + (PasswordResetAuthControllerBase.cs:97,105).
      • +

      TotpTwoFactorService

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Auth.TwoFactor · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TotpTwoFactorService.cs:35 · Level 2 · class

      @@ -6379,7 +6853,7 @@

      TotpTwoFactorService

      • What it is: the RFC 6238 TOTP implementation of ITwoFactorService: secret generation, provisioning-URI construction, code verification, and recovery-code generation/hashing/matching - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TotpTwoFactorService.cs:35-155).
      • + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TotpTwoFactorService.cs:35-159).
      • Depends on: ITwoFactorService (the contract it implements); TwoFactorSettings (MMCA.Common.Shared) injected as IOptions<TwoFactorSettings>; RecoveryCodeSet (MMCA.Common.Shared) as its return shape; OtpDotNet (Base32Encoding, KeyGeneration, Totp, @@ -6389,38 +6863,45 @@

        TotpTwoFactorService

        of. [Rubric §11, Security] assesses whether the codebase avoids timing side channels around secret comparison. TryMatchRecoveryCode never exits its scan early on a match: the loop "runs to the end so the answer takes the same time whichever code in the list was presented" - (TotpTwoFactorService.cs:136-137), and each comparison itself goes through - CryptographicOperations.FixedTimeEquals rather than == or SequenceEqual (:138). A byte-for-byte + (TotpTwoFactorService.cs:140-141), and each comparison itself goes through + CryptographicOperations.FixedTimeEquals rather than == or SequenceEqual (:142). A byte-for-byte comparison that stops at the first differing byte leaks how many leading bytes an attacker guessed correctly across repeated attempts; a fixed-time comparison over a full linear scan removes that channel entirely.
      • -
      • Walkthrough: internal sealed class constructed with IOptions<TwoFactorSettings> settings - (:35), caching .Value once (:37).
          +
        • Walkthrough: internal sealed class with a primary constructor taking + IOptions<TwoFactorSettings> settings (:35), caching .Value once (:37).
          • GenerateSecret() returns a Base32-encoded random key sized by _settings.SecretByteLength - (:40-41). Base32 rather than Base64: "the alphabet has no case ambiguity and no characters a user - has to guess at while copying a code off paper" (:251-252), the same rationale repeated at the - recovery-code site.
          • -
          • BuildProvisioningUri URL-escapes the issuer and account name and formats the standard otpauth:// - URI a QR code encodes, with algorithm, digit count, and period read from settings (:44-56).
          • -
          • VerifyCode decodes the stored Base32 secret, catching ArgumentException and returning false - rather than throwing when the stored value is not valid Base32: "a stored secret that is not Base32 - can never mint a code, so it verifies nothing. That is a data fault, not a caller fault" - (:229-231). It then builds a Totp with the configured step, hash mode, and digit count, and - checks the normalized code against a verification window of _settings.VerificationWindowSteps steps - on each side of now (:215-240), tolerating clock drift between the server and the user's - authenticator app.
          • + (:40-41). +
          • BuildProvisioningUri rejects a blank secret or account name, URL-escapes the issuer and account + name, and formats the standard otpauth:// URI a QR code encodes: the algorithm is fixed at SHA1, + while digit count and period are read from settings (:44-55).
          • +
          • VerifyCode comes in two overloads. The two-argument form is a one-line forward to the + three-argument form, discarding the step (:58). The three-argument form reports the matched TOTP + time step through out long matchedStep, initialized to 0 so every early false leaves it + defined (:61-63). It returns false for a blank secret or code (:64-67), then decodes the + stored Base32 secret, catching ArgumentException and returning false rather than throwing when + the stored value is not valid Base32: "a stored secret that is not Base32 can never mint a code, so + it verifies nothing. That is a data fault, not a caller fault" (:74-80). It then builds a Totp + with the configured period, SHA-1 hash mode, and digit count (:82), and checks the normalized code + against a verification window of _settings.VerificationWindowSteps steps on each side of now, + writing the step the code matched into matchedStep (:83-87). The window tolerates clock drift + between the server and the user's authenticator app; the reported step is what lets + TwoFactorAuthenticator refuse a replay of the same code inside that + window.
          • GenerateRecoveryCodes produces _settings.RecoveryCodeCount codes, each a Base32-encoded random - byte string with the = padding trimmed, alongside their SHA-256 hashes computed by - HashRecoveryCode (:244-262). Only the hashes and the caller-facing codes are returned; nothing - persists the plaintext codes here.
          • -
          • HashRecoveryCode normalizes the code (strips non-alphanumerics, upper-cases) then SHA-256-hashes it - (:265-270).
          • -
          • TryMatchRecoveryCode normalizes and hashes the presented code once, then fixed-time-compares it - against every stored hash, returning the matched hash (not the code) through an out parameter - (:273-302).
          • + byte string of _settings.RecoveryCodeByteLength bytes with the = padding trimmed, alongside + their SHA-256 hashes computed by HashRecoveryCode (:91-109). Base32 rather than Base64: "the + alphabet has no case ambiguity and no characters a user has to guess at while copying a code off + paper" (:98-99). Only the hashes and the caller-facing codes are returned as a RecoveryCodeSet + (:108); nothing persists the plaintext codes here. +
          • HashRecoveryCode normalizes the code (keeps letters and digits, upper-cases) then SHA-256-hashes + it to an upper-case hex string (:112-117).
          • +
          • TryMatchRecoveryCode normalizes and hashes the presented code once, skips null stored entries, + then fixed-time-compares it against every stored hash, returning the matched hash (not the code) + through an out parameter (:120-149).
          • NormalizeCode is the shared helper both verification paths route through: it strips whitespace and separators a user types or a password manager inserts and upper-cases what remains, "so a code is - matched the way it was generated" (:304-311).
          • + matched the way it was generated" (:151-158).
        • Why it's built this way: two-factor authentication is opt-in @@ -6429,8 +6910,9 @@

          TotpTwoFactorService

          Infrastructure only, which is why this type, rather than anything in Application, is what actually calls into the library.
        • Where it's used: registered in DI - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs) as the ITwoFactorService - implementation behind AddTwoFactorAuthentication(config), and consumed by + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:47, a + TryAddSingleton) as the ITwoFactorService implementation behind AddTwoFactorAuthentication(config) + (DependencyInjection.Auth.cs:39), and consumed by TwoFactorAuthenticator for both TOTP and recovery-code verification.
        • Caveats / not-in-source: internal sealed, so it is reached only through the ITwoFactorService abstraction; a consumer cannot construct or type-check against this class directly.
        • @@ -6493,15 +6975,17 @@

          SetUserRolesRequestValidator

        TwoFactorAuthenticator

        -

        MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Auth.TwoFactor · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs:25 · Level 4 · class

        +

        MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Auth.TwoFactor · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs:36 · Level 4 · class

        • What it is: the ITwoFactorAuthenticator implementation that turns a raw code plus a user's stored two-factor state into one of four challenge outcomes - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs:25-73).
        • + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs:36-116).
        • Depends on: ITwoFactorService (code verification and recovery-code matching); ITwoFactorStore (loading state and - consuming a recovery code); TwoFactorErrors; + consuming a recovery code); ICacheService (remembering the + last accepted TOTP time step per account); TwoFactorSettings + via IOptions<T> (period and window size); TwoFactorErrors; TwoFactorOutcome; the framework's Result<T>; the UserIdentifierType alias.
        • Concept introduced, a challenge is answered with an outcome enum, not a bare boolean. [Rubric §1, SOLID] and [Rubric §11, Security] both bear on the shape of ChallengeAsync's return: Result<TwoFactorOutcome> @@ -6510,32 +6994,47 @@

          TwoFactorAuthenticator

          VerifiedRecoveryCode. Collapsing those five states into one boolean would force the caller to re-derive which one happened from side effects, and a login path that cannot tell "not enrolled" from "denied" cannot decide whether to prompt for enrollment or reject the sign-in.
        • -
        • Walkthrough: internal sealed class taking ITwoFactorService and ITwoFactorStore as primary - constructor parameters (:25-27), implementing ChallengeAsync(userId, code, cancellationToken).
            -
          • Loads the user's state from the store first (:34). If the state is missing or +
          • Walkthrough: internal sealed class taking ITwoFactorService, ITwoFactorStore, + ICacheService, and IOptions<TwoFactorSettings> as primary constructor parameters (:36-40), + implementing ChallengeAsync(userId, code, cancellationToken) (:43-86).
              +
            • Loads the user's state from the store first (:48). If the state is missing or IsTwoFactorEnabled is false, the challenge succeeds trivially with NotEnrolled: "an account that never enrolled, and an account whose row vanished between the password check and here, are both - 'nothing to challenge'" (:36-38).
            • -
            • An enrolled account with no presented code fails with TwoFactorErrors.TwoFactorRequired (:44-47).
            • -
            • The code is checked first against the stored TOTP secret via ITwoFactorService.VerifyCode; a match - returns VerifiedTotp (:49-52).
            • + 'nothing to challenge'" (:50-56). +
            • An enrolled account with no presented code fails with TwoFactorErrors.TwoFactorRequired (:58-62).
            • +
            • The code is checked first against the stored TOTP secret via the out long matchedStep overload of + ITwoFactorService.VerifyCode; a match is not accepted outright but handed to + AcceptTimeStepOnceAsync with the step it matched (:64-68).
            • Failing that, it is checked against the stored recovery-code hashes via ITwoFactorService.TryMatchRecoveryCode; no match fails with TwoFactorErrors.TwoFactorInvalid - (:54-59).
            • + (:70-75).
            • A recovery-code match is then consumed, not just verified: store.ConsumeRecoveryCodeAsync persists the redemption, and if that persistence fails, the whole challenge is answered as a failure rather than a success: "letting the sign-in through would hand out a code that is still live" - (:65-69). Only a successfully persisted consumption returns VerifiedRecoveryCode.
            • + (:77-85). Only a successfully persisted consumption returns VerifiedRecoveryCode. +
            • The private AcceptTimeStepOnceAsync makes a TOTP code single use (:96-114). It keys the account + as twofactor:laststep:{userId} (:101) and reads the last accepted step with + GetFromSharedStoreAsync, deliberately bypassing a local copy: "a step accepted on another replica + must be seen here, or a replayed code would pass against a stale local copy inside its window" + (:103-104). A matched step not newer than the stored one fails with + TwoFactorErrors.TwoFactorInvalid (:106-109). Otherwise the step is written back with a lifetime + of PeriodSeconds * (2 * VerificationWindowSteps + 2) seconds, long enough to outlast every step the + verification window could still accept, and the challenge returns VerifiedTotp (:111-114).
          • Why it's built this way: this is the runtime half of the opt-in two-factor feature - (ADR-116); the recovery - code's one-time-use property is only real if consumption and the outcome it produces are atomic, which - is why the failure path is checked before the outcome is returned rather than after.
          • + (ADR-116). Both kinds of + code are made one-time inside the challenge rather than left to the caller. A recovery code's + one-time-use property is only real if consumption and the outcome it produces are atomic, which is why + the failure path is checked before the outcome is returned rather than after. A TOTP code would + otherwise stay valid for its whole verification window, so a code seen over someone's shoulder could be + replayed; remembering the last accepted step closes that window + (TwoFactorAuthenticator.cs type remarks, :25-29).
          • Where it's used: registered in DI - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs) as the - ITwoFactorAuthenticator behind AddTwoFactorAuthentication(config); the resolved instance is passed - to AuthenticationServiceBase, which answers Authentication.TwoFactorRequired or mints the mfa + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:50, a TryAddScoped) + as the ITwoFactorAuthenticator behind AddTwoFactorAuthentication(config); the resolved instance is + passed to AuthenticationServiceBase<TUser>, which + answers Authentication.TwoFactorRequired or mints the mfa claim depending on the outcome (MMCA.Common/CLAUDE.md, "Identity completions" section).

          StoredPermissionRoleAdministrationService

          @@ -6546,7 +7045,7 @@

          StoredPermissionRoleAdministr
        • What it is: the IRoleAdministrationService implementation that lets an operator list roles, read or replace a role's stored permissions, and view the compiled permission catalog, layered over the code-compiled permission registry - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/Administration/StoredPermissionRoleAdministrationService.cs:45-229).
        • + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/Administration/StoredPermissionRoleAdministrationService.cs:45-237).

        • Depends on: IPermissionRegistry (the compiled grants); IPermissionCatalog (the closed list of declared permissions); IPermissionGrantStore (persisted grants); @@ -6566,28 +7065,31 @@

          StoredPermissionRoleAdministr through a host's own permission registry.

        • Walkthrough: constructed with the registry, catalog, store, cache, invalidator, and settings as primary constructor parameters (:45-51).
            -
          • ListRolesAsync groups all stored grants by role, case-insensitively (:60-64), unions the result +
          • ListRolesAsync groups all stored grants by role, case-insensitively (:59-64), unions the result with the compiled and configured role universes via the private RoleUniverse helper (:66), and returns one RolePermissionsResponse per role pairing its compiled permissions (CompiledPermissions) - with its stored ones (:68-77).
          • + with its stored ones (:68-76).
          • GetCatalogAsync returns every known role alongside the full compiled catalog.Permissions deliberately, not whatever happens to be stored: "widening it with whatever happens to be stored - would let one typo legitimize itself" (:88-90).
          • + would let one typo legitimize itself" (:87-89).
          • GetRoleAsync treats a role as not found only when it has no stored grants, no compiled permissions, and is named in neither KnownRoles nor the catalog's role list, so "a role the host has never named ... does not exist as far as this surface is concerned" rather than reporting it as an empty, - plausible-looking role (:107-110).
          • -
          • SetStoredPermissionsAsync trims and de-duplicates the desired set (:135-137), rejects + plausible-looking role (:105-114).
          • +
          • SetStoredPermissionsAsync trims and de-duplicates the desired set (:133-135), rejects ManageRoles as above, rejects any permission absent from catalog.Permissions with the unknown - names listed in the error (:151-163), then diffs the desired set against the currently stored one: + names listed in the error (:148-160), then diffs the desired set against the currently stored one: grants what is newly desired, revokes what was dropped, short-circuiting on the first store failure - either way (:168-187), and finally invalidates the role's permission cache (:189) before - returning the fresh RolePermissionsResponse.
          • + either way (:162-190). The two loops sit in a try whose finally invalidates the role's + permission cache on every exit, failure and exception included (:168-194): each grant and revoke + "commits on its own, so a failure or a throw part-way through leaves earlier rows already written", + and invalidating only on success would leave the process "serving the pre-edit snapshot until the + next refresh" (:165-167). On success it returns the fresh RolePermissionsResponse (:196-198).
          • The private RoleUniverse helper unions the compiled catalog's roles, the configured KnownRoles, - and whatever roles the store's own rows name, case-insensitively and sorted (:216-224).
          • + and whatever roles the store's own rows name, case-insensitively and sorted (:208-215).
          • The private CompiledPermissions helper reads "through the SAME registry the authorization path uses" and subtracts what the cache already reports, so the two lists a role response carries stay - disjoint (:238-244,246-251).
          • + disjoint (:223-236).
        • Why it's built this way: @@ -6598,8 +7100,9 @@

          StoredPermissionRoleAdministr LayeredPermissionRegistry "without changing the decorators' contract", the principle this service's ManageRoles refusal exists to protect.

        • Where it's used: registered in DI - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs) behind - AddStoredPermissionGrants(config) as the IRoleAdministrationService implementation; consumed + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Auth.cs:128, a + TryAddScoped) behind AddStoredPermissionGrants(config) (DependencyInjection.Auth.cs:108) as the + IRoleAdministrationService implementation; consumed through RolesAdminControllerBase, which is itself gated on AdministrationPermissions.ManageRoles (MMCA.Common/CLAUDE.md, "Identity completions" section).
        • Caveats / not-in-source: internal sealed, so it is reached only through the @@ -6621,7 +7124,7 @@

          ClaimBasedUserIdProvider

          is centralized once. SignalR keys its user-targeted sends on whatever string an IUserIdProvider returns; the built-in provider reads ClaimTypes.NameIdentifier. This framework mints the user id only into sub (see TokenService, - MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:105), and whether that + MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:116), and whether that survives as sub or arrives mapped to NameIdentifier depends on which handler authenticated the connection. Routing through the shared extension is what makes both shapes resolve identically, so a consumer that changes its inbound claim mapping does not silently start delivering zero @@ -6639,7 +7142,7 @@

          ClaimBasedUserIdProvider

          claim value is used verbatim on this side, and the sender formats with CultureInfo.InvariantCulture (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Notifications/Push/SignalRPushNotificationSender.cs:19), matching the invariant formatting the token writer used - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:105). Keeping the whole + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TokenService.cs:116). Keeping the whole provider to one delegating line is what makes that three-way agreement checkable at a glance.
        • Where it's used: registered as services.TryAddSingleton<IUserIdProvider, ClaimBasedUserIdProvider>() @@ -6672,7 +7175,7 @@

          CurrentUserService

          [Rubric §12, Performance & Scalability] explains the Lazy<T> fields (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Context/CurrentUserService.cs:19, :21): because the service is registered scoped - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:296), the claim walk + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:306)
          , the claim walk happens at most once per request no matter how many handlers, filters and save operations ask. [Rubric §27, i18n] covers the trap most codebases miss: claim values are machine-written under CultureInfo.InvariantCulture, so they must be read invariantly too, or a request running under a @@ -6717,11 +7220,11 @@

          CurrentUserService

          (MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/ClaimsPrincipalExtensions.cs:9-16).
        • Where it's used: registered as services.TryAddScoped<ICurrentUserService, CurrentUserService>() - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:296). The + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:306). The highest-traffic consumer is DbContextFactory, which takes it as a constructor dependency - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:50, + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:52, :57) and passes UserId into every save so audit fields are stamped with the acting user (:248, :291, :330, :352, :414); EFRepository<TEntity, TIdentifierType> diff --git a/docs/onboarding/group-09-caching.html b/docs/onboarding/group-09-caching.html index a6d0097f..149c9270 100644 --- a/docs/onboarding/group-09-caching.html +++ b/docs/onboarding/group-09-caching.html @@ -166,26 +166,36 @@

          9. Caching

          (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:10) is a textbook Clean Architecture port/adapter split (see primer §1): the interface lives in MMCA.Common.Application, all three implementations live in MMCA.Common.Infrastructure, and - application code compiles against the interface alone. It declares six members. Four are abstract: + application code compiles against the interface alone. It declares eight members. Four are abstract: GetAsync<T> returning T? with null for a miss (ICacheService.cs:17), SetAsync<T> with an - optional TimeSpan? TTL (ICacheService.cs:26), RemoveAsync for one key (ICacheService.cs:36), - and RemoveByPrefixAsync for bulk eviction by key prefix (ICacheService.cs:42). Two are default - interface members with working bodies, so each one shipped without breaking an implementer: - IncrementAsync (ICacheService.cs:59) is a read-modify-write counter (ICacheService.cs:61-64) that - gives the ADR-029 + optional TimeSpan? TTL (ICacheService.cs:72), RemoveAsync for one key (ICacheService.cs:82), + and RemoveByPrefixAsync for bulk eviction by key prefix (ICacheService.cs:88). Four are default + interface members with working bodies, so each one shipped without breaking an implementer. + TryGetAsync<T> (ICacheService.cs:34) reports presence separately from the value, because for a + value-type T a GetAsync miss answers default(T) and cannot be told apart from a cached 0 or + false (ICacheService.cs:19-23); the default infers presence from a non-null value + (ICacheService.cs:36-37), and the shipped stores that can do better override it. + GetFromSharedStoreAsync<T> (ICacheService.cs:62) reads past any process-local copy, for + single-use records (an OAuth exchange code, a password-reset or email-confirmation token, a + second-factor time step) whose consumption on one replica must be visible on every other one at once + (ICacheService.cs:40-61); its default simply calls GetAsync, which is exact for a store with no + local tier. IncrementAsync (ICacheService.cs:105) is a read-modify-write counter + (ICacheService.cs:107-110) that gives the + ADR-029 brute-force and rate-limit counters one entry point instead of scattered get-then-set pairs, and - GetOrCreateAsync<T> (ICacheService.cs:99) folds read, per-key stripe, double-check, factory and - write into one call (ICacheService.cs:104-124) using the process-wide CacheKeyLocks table - (ICacheService.cs:142-146), cross-referenced in + GetOrCreateAsync<T> (ICacheService.cs:145) folds read, per-key stripe, double-check, factory and + write into one call (ICacheService.cs:150-171), taking presence from TryGetAsync on both checks so + a cached default(T) counts as a hit (ICacheService.cs:153-165), using the process-wide + CacheKeyLocks table (ICacheService.cs:189-193), cross-referenced in Group 5. Its XML doc is explicit about two limits that matter: it caches whatever the factory returned, failed Result included, which is exactly why the caching decorators do not route through it, and its stampede - protection is per process (ICacheService.cs:80-97). RemoveByPrefixAsync is the load-bearing member: + protection is per process (ICacheService.cs:126-143). RemoveByPrefixAsync is the load-bearing member: it is what lets a single write evict every cached read it could have staled, and it is why the backends had to be built rather than used off the shelf (IMemoryCache has no key enumeration, IDistributedCache has no prefix delete). [Rubric §3, Clean Architecture] assesses whether dependencies point inward and infrastructure stays replaceable; this is that rule in one file, since the only thing Application knows - about caching is six method signatures.

          + about caching is eight method signatures.

          Backend selection happens once, at the composition root. AddCaching(IConfiguration?) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:26, called from AddInfrastructure at DependencyInjection.cs:134) always calls AddMemoryCache() @@ -199,18 +209,19 @@

          9. Caching

          that layer because it cannot reference Infrastructure). Without configuration both settings objects are still registered at their defaults (DependencyInjection.Caching.cs:53-57), so IOptions<T> always resolves. ICacheService itself is registered through TryAddSingleton with a factory that - probes the container (DependencyInjection.Caching.cs:59-78). If an IDistributedCache is registered and + probes the container (DependencyInjection.Caching.cs:59-80). If an IDistributedCache is registered and it is not the default MemoryDistributedCache (DependencyInjection.Caching.cs:62), meaning a real out-of-process store such as the Redis cache Aspire wires, the factory builds a DistributedCacheService with whatever IConnectionMultiplexer and ILogger it can resolve plus the bound key namespace and cache settings (DependencyInjection.Caching.cs:64-73); otherwise it falls back to a - MemoryCacheService over the registered IMemoryCache - (DependencyInjection.Caching.cs:77). The same method registers the - IDistributedLock that the API idempotency filter pairs - with the cache, Redis-backed when a + MemoryCacheService over the registered IMemoryCache, handed the same bound + cache settings (DependencyInjection.Caching.cs:77-79). The same method registers the + IDistributedLock that both in-framework callers pair + with the cache, the API idempotency filter and password-reset token redemption + (DependencyInjection.Caching.cs:82-85), Redis-backed when a multiplexer is present and process-local - otherwise (DependencyInjection.Caching.cs:84-98). A single-process monolith therefore caches in-process for + otherwise (DependencyInjection.Caching.cs:86-100). A single-process monolith therefore caches in-process for free, and the identical application code uses Redis the moment a distributed cache is present: no flag, no per-environment branch in a handler. This is the same "abstraction in Application, transport chosen at the edge" pattern the message bus and gRPC clients use, which is what [Rubric §7, Microservices @@ -218,62 +229,74 @@

          9. Caching

          [Rubric §12, Performance and Scalability] rewards (the scaled-out deployment gets a shared cache without touching business code).

          A third substrate, opt in and explicit. AddCommonHybridCache(Action<HybridCacheOptions>?) - (DependencyInjection.Caching.cs:137) calls AddHybridCache() (DependencyInjection.Caching.cs:139) and then + (DependencyInjection.Caching.cs:139) calls AddHybridCache() (DependencyInjection.Caching.cs:141) and then configures HybridCacheOptions through the options pipeline rather than through the - AddHybridCache callback (DependencyInjection.Caching.cs:145-159), because the TTL policy now comes from + AddHybridCache callback (DependencyInjection.Caching.cs:147-161), because the TTL policy now comes from the bound CacheSettings and the callback has no service provider to read it from; the framework sets Expiration from DefaultDuration and LocalCacheExpiration from LocalCacheDuration (falling back to - HybridCacheService.LocalCacheDefault) at DependencyInjection.Caching.cs:152-156, and the host's own hook - runs last so it can override anything (DependencyInjection.Caching.cs:158). The swap of the cache + HybridCacheService.LocalCacheDefault) at DependencyInjection.Caching.cs:154-158, and the host's own hook + runs last so it can override anything (DependencyInjection.Caching.cs:160). The swap of the cache implementation is deliberately RemoveAll<ICacheService>() followed by AddSingleton - (DependencyInjection.Caching.cs:163-177) rather than TryAdd, so the call wins whether it runs before or + (DependencyInjection.Caching.cs:165-179) rather than TryAdd, so the call wins whether it runs before or after AddInfrastructure; the source is equally explicit that this also removes a host's own bespoke ICacheService, so calling it is a statement that the two-level cache is the cache - (DependencyInjection.Caching.cs:126-129). All seven deployed service hosts call it, inside the same "is Redis - configured" branch that registers the distributed cache: ADC Conference at + (DependencyInjection.Caching.cs:128-131). AddCommonHybridCache has no guard of its own, so the + framework also ships the guarded form, AddCommonHybridCacheWhenRedisConfigured(IConfiguration, string) + (DependencyInjection.Caching.cs:200), which calls it only when the redis connection string (the + default connectionName) is non-empty (DependencyInjection.Caching.cs:206-209) and otherwise leaves + the registration untouched. All seven deployed service hosts call that guarded form unconditionally, + right after AddRedisCaching(): ADC Conference at MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:203 and Store Catalog at - MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:94, with ADC Engagement, Identity - and Notification and Store Sales and Identity alongside them. Without Redis the branch does not run and + MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:108, with ADC Engagement, Identity + and Notification and Store Sales and Identity alongside them. Without Redis the guard declines and the host keeps the auto-selected substrate, which is the point: an L1 in front of an in-process L2 buys - nothing (ADR-077).

          + nothing (DependencyInjection.Caching.cs:189-191, + ADR-077).

          The in-process adapter. MemoryCacheService - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:18) wraps - IMemoryCache and carries one side structure: a ConcurrentDictionary<string, object> of live keys - (MemoryCacheService.cs:31), because IMemoryCache cannot enumerate its own keys and without that - shadow index RemoveByPrefixAsync (MemoryCacheService.cs:128-138) would be impossible. Keeping the + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:19) wraps + IMemoryCache, takes the bound CacheSettings (framework defaults when none is + passed, MemoryCacheService.cs:25) so an entry written without an expiration gets the same + DefaultDuration every other store applies (MemoryCacheService.cs:84), and carries one side structure: a ConcurrentDictionary<string, object> of live keys + (MemoryCacheService.cs:38), because IMemoryCache cannot enumerate its own keys and without that + shadow index RemoveByPrefixAsync (MemoryCacheService.cs:138-148) would be impossible. Keeping the cache and the index in agreement is the whole design. Every mutation takes that key's stripe from a per-instance KeyedSemaphoreStripe - (MemoryCacheService.cs:38) and touches the cache before the table - (MemoryCacheService.cs:101-105), because ordering alone cannot close the window: track-then-write + (MemoryCacheService.cs:45) and touches the cache before the table + (MemoryCacheService.cs:111-115), because ordering alone cannot close the window: track-then-write lets a concurrent removal drop the record between the steps, and write-then-track lets a removal run entirely between them, both leaving a live entry nothing can find. The post-eviction callback is deliberately lock-free, since IMemoryCache queues it to the thread pool, and it removes the record only while the record is still its own, comparing the entry token by reference and skipping - EvictionReason.Replaced outright (MemoryCacheService.cs:93-99). GetAsync also matches on the - stored object rather than using the generic TryGetValue<T> overload (MemoryCacheService.cs:46), so - a key reused under a different T surfaces as a clean miss instead of an InvalidCastException.

          + EvictionReason.Replaced outright (MemoryCacheService.cs:103-109). GetAsync also matches on the + stored object rather than using the generic TryGetValue<T> overload (MemoryCacheService.cs:53), so + a key reused under a different T surfaces as a clean miss instead of an InvalidCastException, and + it overrides TryGetAsync with the same type-matched lookup so presence is real rather than inferred + from a non-null value (MemoryCacheService.cs:62-64).

          The out-of-process adapter. DistributedCacheService (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/DistributedCacheService.cs:19) - serializes values to UTF-8 JSON via System.Text.Json (DistributedCacheService.cs:153-157) and + serializes values to UTF-8 JSON via System.Text.Json (DistributedCacheService.cs:161-165) and stores them through IDistributedCache, applying the bound TTL default when the caller supplies none - (DistributedCacheService.cs:37, DistributedCacheService.cs:64). Prefix eviction is where it earns + (DistributedCacheService.cs:37, DistributedCacheService.cs:72). It overrides TryGetAsync with a + real presence check on the stored bytes (DistributedCacheService.cs:48-53) and keeps the default + GetFromSharedStoreAsync, which is already exact here because the store has no process-local tier. + Prefix eviction is where it earns its keep: when an IConnectionMultiplexer is available it hands the namespace-qualified pattern and a raw KeyDeleteAsync to RedisPrefixScanner - (DistributedCacheService.cs:116-122), resolving the IDatabase lazily on the first delete - (DistributedCacheService.cs:114, DistributedCacheService.cs:119). When no multiplexer is + (DistributedCacheService.cs:124-130), resolving the IDatabase lazily on the first delete + (DistributedCacheService.cs:122, DistributedCacheService.cs:127). When no multiplexer is registered, prefix eviction cannot run at all, and rather than failing silently the class logs a - warning once, guarded by an Interlocked.Exchange flag (DistributedCacheService.cs:73, - DistributedCacheService.cs:107-108) and naming the fix (AddRedisClient), because a permanently dead + warning once, guarded by an Interlocked.Exchange flag (DistributedCacheService.cs:81, + DistributedCacheService.cs:115-116) and naming the fix (AddRedisClient), because a permanently dead invalidation is a steady state that must not flood the log on every command; the anomalous "multiplexer with no servers" case and a per-server failure each get their own message - (DistributedCacheService.cs:120-121). All three are compile-time LoggerMessage sources - (DistributedCacheService.cs:159-166). That warn-once-versus-warn-always split is a small but real + (DistributedCacheService.cs:128-129). All three are compile-time LoggerMessage sources + (DistributedCacheService.cs:167-174). That warn-once-versus-warn-always split is a small but real [Rubric §13, Observability and Operability] decision: §13 assesses whether an operator can tell what the system is doing, and a cache whose invalidation quietly does nothing is exactly the failure mode that hides from dashboards. The class also overrides IncrementAsync - (DistributedCacheService.cs:145-151) while keeping the same non-atomic read-modify-write shape, and - the comment above it (DistributedCacheService.cs:126-144) is worth reading: Redis INCR would be + (DistributedCacheService.cs:153-159) while keeping the same non-atomic read-modify-write shape, and + the comment above it (DistributedCacheService.cs:134-152) is worth reading: Redis INCR would be atomic but writes a Redis string, while StackExchangeRedisCache stores every entry as a Redis hash, so an INCR-written counter makes the next read fail with WRONGTYPE. Readability of the counter wins over atomicity, and ADR-026 @@ -295,30 +318,47 @@

          9. Caching

          (RedisPrefixScanner.cs:10-23), which is precisely what lets the two services share the scan while removing keys differently.

          The two-level cache. HybridCacheService - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/HybridCacheService.cs:36) puts an + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/HybridCacheService.cs:44) puts an in-process L1 in front of the registered distributed L2 and lets the platform's HybridCache supply serialization, L1 promotion and stampede protection. Its structural decision is the disjoint - keyspace: every key is written as {prefix}hc:{key} (HybridCacheService.cs:47, - HybridCacheService.cs:260), so the payload layout HybridCache writes can never meet the UTF-8 JSON + keyspace: every key is written as {prefix}hc:{key} (HybridCacheService.cs:55, + HybridCacheService.cs:307), so the payload layout HybridCache writes can never meet the UTF-8 JSON DistributedCacheService writes at one key. That is the WRONGTYPE lesson from IncrementAsync generalized: an entry in the other format is simply a clean miss, including while a rolling deploy runs both builds (HybridCacheService.cs:18-28). RemoveByPrefixAsync consequently runs the scanner over that one keyspace, {namespace}hc:{prefix}*, and deletes each match through HybridCache.RemoveAsync rather than by raw key, so this process's L1 copy goes with the L2 - entry (HybridCacheService.cs:173-179); a missing multiplexer warns once exactly as it does on the - single-level adapter (HybridCacheService.cs:163-171). Reads are fail-soft: a fault is logged, + entry (HybridCacheService.cs:220-226); a missing multiplexer warns once exactly as it does on the + single-level adapter (HybridCacheService.cs:210-218). Reads are fail-soft: a fault is logged, answered as a miss, and the offending entry is dropped best-effort so the next write repopulates it - (HybridCacheService.cs:103-122, HybridCacheService.cs:290-300). IncrementAsync is the one member - that bypasses L1 on both legs (HybridCacheService.cs:71-76, HybridCacheService.cs:206-227), and - the reasoning is a [Rubric §11, Security] point rather than a performance one: a counter cached - per replica would let one process read its own stale count and write it back, so a brute-force limiter - could be held near its starting value indefinitely by a steady stream of attempts against a single - replica. Its faults are deliberately not swallowed either, since a counter that silently reads zero - resets the limit it exists to enforce (HybridCacheService.cs:201-204). GetOrCreateAsync overrides - the interface default with HybridCache's own implementation (HybridCacheService.cs:238-255). + (HybridCacheService.cs:114-133, HybridCacheService.cs:337-347). Two members bypass L1 entirely, + and both share one options instance that disables the L1 read and the L1 write + (SharedStoreReadOptions, HybridCacheService.cs:82-87), because both are values whose correctness + depends on every replica seeing the same thing; the reasoning is a [Rubric §11, Security] point rather + than a performance one. GetFromSharedStoreAsync overrides the interface default so a single-use + record (an OAuth exchange code, a password-reset or email-confirmation token, the last accepted + second-factor time step) consumed on one replica is a miss on every other replica at once rather than + usable a second time for up to the local expiration (HybridCacheService.cs:35-42, + HybridCacheService.cs:150-169); it stays fail-soft like GetAsync, and for such a record a miss is a + refusal, the safe direction. Its callers are + OAuthControllerBase + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/OAuthControllerBase.cs:196), + PasswordResetTokenService + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:128), + EmailConfirmationTokenService + (.../Auth/EmailConfirmationTokenService.cs:90) and + TwoFactorAuthenticator + (.../Auth/TwoFactor/TwoFactorAuthenticator.cs:105). IncrementAsync bypasses L1 on both legs + (HybridCacheService.cs:253-274): a counter cached per replica would let one process read its own + stale count and write it back, so a brute-force limiter could be held near its starting value + indefinitely by a steady stream of attempts against a single replica. Its faults are deliberately not + swallowed, unlike the reads, since a counter that silently reads zero resets the limit it exists to + enforce (HybridCacheService.cs:248-251). The class does not override TryGetAsync, so presence there + is the interface default inferred from a non-null value. GetOrCreateAsync overrides + the interface default with HybridCache's own implementation (HybridCacheService.cs:285-302). Replica L1 staleness after an invalidation is bounded by the local expiration, the shorter of the entry's TTL and Cache:LocalCacheDuration (30 seconds by default, - HybridCacheService.cs:54, HybridCacheService.cs:269-280), not by the eviction, and the source names + HybridCacheService.cs:62, HybridCacheService.cs:316-327), not by the eviction, and the source names that as the accepted cost of the L1 hit rate.

          The key namespace and the TTL policy. CacheKeyPrefixOptions (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/CacheKeyPrefix.cs:31) binds the Cache @@ -341,7 +381,7 @@

          9. Caching

          keys at ApplicationNamespace.cs:31 and ApplicationNamespace.cs:34), so the resolved prefix is never empty and isolation is automatic rather than opt-in (ApplicationNamespace.cs:45-48). Qualify (CacheKeyPrefix.cs:91-92) prepends it. The same namespace instance is handed to the Redis distributed - lock registered beside the cache (DependencyInjection.Caching.cs:91-92), so one application's cache entries + lock registered beside the cache (DependencyInjection.Caching.cs:93-94), so one application's cache entries and its lock keys carry one prefix. Both Redis-capable adapters honor it and apply it inside the adapter rather than through RedisCacheOptions.InstanceName; the rationale in the source (CacheKeyPrefix.cs:16-25) is precise, since InstanceName is prepended below this abstraction where @@ -372,9 +412,10 @@

          9. Caching

          tests the query for IQueryCacheable and passes straight through when it is absent (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/CachingQueryDecorator.cs:64-65). - When present it scopes the key to the resolved tenant through - TenantCacheKey (CachingQueryDecorator.cs:57-58, so two - tenants can never share an entry), takes a lock-free fast path on a hit + When present it scopes the key to the target user for a caller-scoped query through + UserCacheKey and then to the resolved tenant through + TenantCacheKey (CachingQueryDecorator.cs:58-59, so two + tenants or two users can never share an entry), takes a lock-free fast path on a hit (CachingQueryDecorator.cs:74-79), and on a miss acquires a per-key stripe from the process-wide QueryCacheKeyLocks (CachingQueryDecorator.cs:185), re-checks (CachingQueryDecorator.cs:100-105), records the miss on @@ -424,8 +465,8 @@

          9. Caching

          OutputCacheEvictionRequested integration event and its OutputCacheEvictionHandler. Both adopters back that edge with Redis when Redis is configured - (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:193; - MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:104), so the two tiers ride the same + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:195; + MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:100)
          , so the two tiers ride the same Redis instance from opposite ends: Tier 1 through IDistributedCache or HybridCache, Tier 2 through the output-cache store. ADR-026 also records an optional third tier on the client, IUiReadCache, a per-circuit read-through cache over @@ -437,10 +478,10 @@

          9. Caching

          Redis is live in the deployed services: every service host registers the Aspire Redis integration through RedisCachingExtensions, whose AddRedisCaching() brings the IConnectionMultiplexer the SCAN needs along with the distributed cache - (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:183; - MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:86), and the hybrid substrate is - registered inside the same connection-string conditional - (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:201-204). Write-side adoption is + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:185; + MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:89), and the hybrid substrate is + registered through the framework's connection-string guard + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:203). Write-side adoption is broad: about forty types across ADC's Conference, Engagement and Identity modules implement ICacheInvalidating, for example MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Categories/UseCases/UpdateCategoryItem/UpdateCategoryItemCommand.cs:18 @@ -455,14 +496,17 @@

          9. Caching

          (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/LoginProtectionService.cs:19) injects ICacheService for its brute-force and rate-limit counters (LoginProtectionService.cs:64, LoginProtectionService.cs:119), covered in - Group 8, Authentication and Authorization, and + Group 8, Authentication and Authorization, the password-reset and + email-confirmation token services, which count requests through IncrementAsync + (PasswordResetTokenService.cs:68, EmailConfirmationTokenService.cs:53) and redeem their single-use + records through GetFromSharedStoreAsync as described above, and IdempotencyFilter resolves it per request to store and replay responses - (MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:140). The key + (MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:141). The key namespace is live in both deployed applications, and the two pin it differently: ADC's four service hosts all set Cache:KeyPrefix to adc: alongside Application:Namespace adc, because they share one Redis and one Service Bus namespace and must agree on the keyspace they already share - (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/appsettings.json:44, rationale at :30-42), + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/appsettings.json:44-45, rationale at :30-42), while each Store service pins its own (MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/appsettings.json:45, rationale at :29-33). Each of those values restates what the per-application default would already have produced, so the @@ -498,19 +542,19 @@

          CacheKeyPrefixOptions

          HybridCacheService and the Redis distributed lock. It exists so several services sharing one Redis instance cannot read each other's entries by accidentally choosing the same key. Leaving it unset no longer means "no prefix": the empty value is a signal to fall back to - the framework's own per-application default (CacheKeyPrefix.cs:106-109).
        • + the framework's own per-application default (CacheKeyPrefix.cs:37-40).
        • Depends on: nothing first-party for the options object itself; it is a plain POCO bound by Microsoft.Extensions.Options / IConfiguration (BCL). Its value is turned into behavior by CacheKeyNamespace, which is what the cache adapters actually hold, and which now falls back to ApplicationNamespace when this option - is unset (CacheKeyPrefix.cs:106-109).
        • + is unset (CacheKeyPrefix.cs:37-40).
        • Concept introduced, keyspace isolation for a shared cache, on by default. [Rubric §7, Microservices Readiness] assesses whether a module keeps working, and keeps its data to itself, once it is lifted into its own process next to its siblings. A cache instance is exactly the kind of shared infrastructure that survives extraction unchanged, so the isolation a private process gave you for free has to be re-created explicitly. This option is one of two ways to get it: set it explicitly to pin a shared keyspace across hosts of the same application, or leave it unset and let ApplicationNamespace derive a distinct keyspace per - application automatically (CacheKeyPrefix.cs:105-109). [Rubric §11, Security] assesses whether the + application automatically (CacheKeyPrefix.cs:37-40). [Rubric §11, Security] assesses whether the system prevents data reaching a caller who should not see it; SEC-Common-53 (CacheKeyPrefix.cs:90-92) records that this option used to default to no prefix at all, so two applications sharing one Redis silently shared one keyspace for both cache entries and distributed @@ -522,7 +566,7 @@

          CacheKeyPrefixOptions

          populate-lock knob (QueryCachePipelineSettings), all three bound side by side in AddCaching (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:39-57).
        • -
        • KeyPrefix (CacheKeyPrefix.cs:46, doc at CacheKeyPrefix.cs:105-109), string with +
        • KeyPrefix (CacheKeyPrefix.cs:46, doc at CacheKeyPrefix.cs:36-45), string with { get; init; } and a default of string.Empty. The property default is unchanged, but its meaning changed: an empty value now tells CacheKeyNamespace.From(IServiceProvider) to resolve the framework's @@ -545,13 +589,13 @@

          CacheKeyPrefixOptions

          services.Configure<CacheKeyPrefixOptions>(configuration.GetSection(CacheKeyPrefixOptions.SectionName)), and only when a non-null IConfiguration was passed (DependencyInjection.Caching.cs:39). AddInfrastructure always passes one - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:134), so a host composing through the + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:140), so a host composing through the normal entry point gets the binding; a test calling the parameterless AddCaching() overload does not. The bound options are no longer read directly at the call sites; instead CacheKeyNamespace.From(IServiceProvider) resolves them (and the per-application fallback) once per registration, at three sites: the distributed cache factory - (DependencyInjection.Caching.cs:67), the Redis lock factory (DependencyInjection.Caching.cs:91) and - the opt-in hybrid factory (DependencyInjection.Caching.cs:169).
        • + (DependencyInjection.Caching.cs:67), the Redis lock factory (DependencyInjection.Caching.cs:93) and + the opt-in hybrid factory (DependencyInjection.Caching.cs:171).
        • Caveats / not-in-source: MemoryCacheService never sees the prefix, because a per-process keyspace is private by construction and a prefix would add nothing (CacheKeyPrefix.cs:26-29). The prior caveat here, that no checked-in appsettings*.json sets @@ -611,7 +655,7 @@

          CacheOptions

          (026-caching-strategy.md:62).
        • Where it's used: DistributedCacheService.SetAsync calls CacheOptions.Create(expiration ?? _settings.DefaultDuration) for every write - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/DistributedCacheService.cs:64), which + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/DistributedCacheService.cs:72), which is the only first-party call site of Create. DefaultDuration seeds CacheSettings.DefaultDuration (CacheSettings.cs:32), which is what both distributed adapters actually read at runtime. @@ -687,9 +731,9 @@

          RedisPrefixScanner

          of cascading, and here a failing node costs you the freshness of the keys it holds, nothing more.
        • Where it's used: exactly two call sites, one per Redis-capable adapter: DistributedCacheService.RemoveByPrefixAsync - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/DistributedCacheService.cs:116-122) and + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/DistributedCacheService.cs:124-130) and HybridCacheService.RemoveByPrefixAsync - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/HybridCacheService.cs:173-179). + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/HybridCacheService.cs:220-226). Exercised against a real Redis by DistributedCacheServiceRedisTests and HybridCacheServiceRedisTests, @@ -716,39 +760,39 @@

          CacheKeyNamespace

          [Rubric §15, Best Practices & Code Quality] assesses whether the code avoids incidental complexity and defensive noise. Rather than making every call site ask "is a prefix configured?", the unconfigured case is represented by a real instance, None, whose Qualify returns the key - unchanged. There is one branch (CacheKeyPrefix.cs:266-267) instead of a null check at every use. + unchanged. There is one branch (CacheKeyPrefix.cs:91-92) instead of a null check at every use. [Rubric §11, Security] assesses whether the system prevents data reaching a caller who should not see it: the composition-root factory, From(IServiceProvider), is what turns an unset Cache:KeyPrefix into a non-empty, per-application prefix (SEC-Common-53, - CacheKeyPrefix.cs:240-245) rather than into None, so cross-application key collisions are closed + CacheKeyPrefix.cs:65-69) rather than into None, so cross-application key collisions are closed by construction rather than by a host remembering to configure a prefix. [Rubric §14, Testability] assesses whether behavior can be exercised without standing up the world: because the type is a plain object that both adapters take as an optional constructor parameter, a unit test passes new CacheKeyNamespace("svc:") directly and asserts on the qualified key with no configuration system involved - (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Caching/DistributedCacheServiceTests.cs:425, - and the same shape throughout HybridCacheServiceTests.cs:78-95).
        • -
        • Walkthrough: primary constructor CacheKeyNamespace(string prefix) (CacheKeyPrefix.cs:225).
            -
          • None (CacheKeyPrefix.cs:228), a static property initialised to new(string.Empty). One + (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Caching/DistributedCacheServiceTests.cs:450, + and the same shape throughout HybridCacheServiceTests.cs:79-96).
          • +
          • Walkthrough: primary constructor CacheKeyNamespace(string prefix) (CacheKeyPrefix.cs:50).
              +
            • None (CacheKeyPrefix.cs:53), a static property initialised to new(string.Empty). One shared, immutable instance meaning "leave keys alone"; still used directly wherever a caller wants no prefix at all (for example MemoryCacheService's registration), but no longer what an unconfigured Cache:KeyPrefix resolves to through From(IServiceProvider).
            • -
            • Prefix (CacheKeyPrefix.cs:231), get-only, initialised with prefix ?? string.Empty, so a null +
            • Prefix (CacheKeyPrefix.cs:56), get-only, initialised with prefix ?? string.Empty, so a null argument degrades to the no-op prefix rather than throwing later inside string.Concat.
            • -
            • From(IOptions<CacheKeyPrefixOptions>? options) (CacheKeyPrefix.cs:234-238), the original +
            • From(IOptions<CacheKeyPrefixOptions>? options) (CacheKeyPrefix.cs:59-63), the original factory: tolerates a null options object, reads options?.Value.KeyPrefix, and returns None when that is null or empty. It is still the plain, options-only overload used directly by tests; production DI code no longer calls it.
            • -
            • From(IServiceProvider serviceProvider) (CacheKeyPrefix.cs:248-263), the overload the DI +
            • From(IServiceProvider serviceProvider) (CacheKeyPrefix.cs:73-88), the overload the DI factories now call. It null-checks serviceProvider, reads the bound CacheKeyPrefixOptions off it, and returns new CacheKeyNamespace(configured) when Cache:KeyPrefix is set - (CacheKeyPrefix.cs:252-256); otherwise it resolves + (CacheKeyPrefix.cs:77-81); otherwise it resolves ApplicationNamespace.Resolve against the container's IConfiguration and IHostEnvironment and returns - new CacheKeyNamespace($"{applicationNamespace}:") (CacheKeyPrefix.cs:258-262). Because + new CacheKeyNamespace($"{applicationNamespace}:") (CacheKeyPrefix.cs:83-87). Because ApplicationNamespace.Resolve never returns empty (ApplicationNamespace.cs:47-48), this overload never returns None.
            • -
            • Qualify(string key) (CacheKeyPrefix.cs:266-267), expression-bodied: returns key unchanged when +
            • Qualify(string key) (CacheKeyPrefix.cs:91-92), expression-bodied: returns key unchanged when Prefix.Length == 0, otherwise string.Concat(Prefix, key). No separator is inserted for an explicit prefix, so a caller-configured Cache:KeyPrefix must carry its own delimiter ("conference:", not "conference"); the per-application fallback path adds the : itself.
            • @@ -768,10 +812,10 @@

              CacheKeyNamespace

              constructor argument: to DistributedCacheService (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:67), to RedisDistributedLock - (DependencyInjection.Caching.cs:91), and to HybridCacheService in the opt-in - hybrid path (DependencyInjection.Caching.cs:169). Inside each adapter it lands in a _keys field + (DependencyInjection.Caching.cs:93), and to HybridCacheService in the opt-in + hybrid path (DependencyInjection.Caching.cs:171). Inside each adapter it lands in a _keys field with a ?? CacheKeyNamespace.None fallback (DistributedCacheService.cs:30, - HybridCacheService.cs:82). The in-process branch of AddCaching() constructs + HybridCacheService.cs:93). The in-process branch of AddCaching() constructs MemoryCacheService with no namespace at all (DependencyInjection.Caching.cs:76-77), and the comment above it says why: the keyspace is private to the process, so neither the explicit prefix nor the per-application fallback is needed there. @@ -828,12 +872,12 @@

              CacheSettings

              • SectionName = "Cache" (CacheSettings.cs:25).
              • DefaultDuration (:32), the absolute TTL applied when a caller supplies no expiration. - HybridCacheService uses it as the fallback TTL (HybridCacheService.cs:271) + HybridCacheService uses it as the fallback TTL (HybridCacheService.cs:318) and DistributedCacheService does the same - (DistributedCacheService.cs:64).
              • + (DistributedCacheService.cs:72).
              • LocalCacheDuration (:42), nullable, the ceiling on the L1 copy of a two-level entry so a replica that never sees an invalidation still re-reads L2 within the window. Null keeps the - built-in 30-second ceiling (HybridCacheService.cs:54), and the effective L1 lifetime is the + built-in 30-second ceiling (HybridCacheService.cs:62), and the effective L1 lifetime is the shorter of the ceiling and the entry's own TTL (:271-272). The single-level cache services have no L1 and ignore it.
              • PopulateLockTimeout (:57), defaulting to Timeout.InfiniteTimeSpan: waiters block until the @@ -841,7 +885,7 @@

                CacheSettings

                waiter proceed uncached, and the remarks note that zero or a negative value means no bound, exactly like the default (:50-56).
              • Both cache services take the options as an OPTIONAL constructor parameter and fall back to a fresh - instance (HybridCacheService.cs:41, :89; DistributedCacheService.cs:24, :37), so a service + instance (HybridCacheService.cs:49, :89; DistributedCacheService.cs:24, :37), so a service constructed outside the container still gets the framework defaults.
              @@ -855,7 +899,7 @@

              CacheSettings

              The same values are then projected into HybridCache's own option type through the options pipeline rather than the AddHybridCache callback, because the callback has no service provider to read the bound section from, and the host's own hook still runs last so it can override anything the framework set - (DependencyInjection.Caching.cs:145-159, + (DependencyInjection.Caching.cs:147-161, ADR-077).

            • Where it's used: DistributedCacheService and @@ -870,111 +914,146 @@

              ICacheService

              MMCA.Common.Application · MMCA.Common.Application.Interfaces · MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICacheService.cs:10 · Level 3 · interface

        -
      • What it is: the Application layer's cache port. Get by key, set with an optional TTL, remove by - exact key, remove every key matching a prefix, increment a counter, and get-or-create with stampede - protection. It hides whether the backing store is Redis, a SQL distributed cache, an in-process - IMemoryCache, or a two-level HybridCache.
      • -
      • Depends on: BCL types at the signature level (Task, CancellationToken, TimeSpan?) plus - KeyedSemaphoreStripe from MMCA.Common.Shared.Concurrency, - which the default GetOrCreateAsync body uses through the +
      • What it is: the Application layer's cache port. Get by key, look a key up and report presence + separately from the value, read straight from the shared backing store, set with an optional TTL, + remove by exact key, remove every key matching a prefix, increment a counter, and get-or-create with + stampede protection. It hides whether the backing store is Redis, a SQL distributed cache, an + in-process IMemoryCache, or a two-level HybridCache.
      • +
      • Depends on: BCL types at the signature level (Task, CancellationToken, TimeSpan?, value + tuples) plus KeyedSemaphoreStripe from + MMCA.Common.Shared.Concurrency, which the default GetOrCreateAsync body uses through the CacheKeyLocks holder (ICacheService.cs:1, - ICacheService.cs:142-146). Implemented by MemoryCacheService, + ICacheService.cs:188-192). Implemented by MemoryCacheService, DistributedCacheService and HybridCacheService.
      • Concept introduced, dependency inversion for infrastructure. [Rubric §3, Clean Architecture] assesses whether business code depends on abstractions while concrete technology sits at the edges. The Application layer defines this contract; the Infrastructure layer implements it. Handlers, decorators and the auth services never see StackExchange.Redis or Microsoft.Extensions.Caching; they program against this interface and the container decides which adapter they get. Second - concept, the default interface member as a non-breaking extension point. [Rubric §15, Best Practices & Code Quality] assesses whether the codebase can absorb change without a ripple. Two members here - ship with bodies, IncrementAsync (ICacheService.cs:59) and GetOrCreateAsync - (ICacheService.cs:99). Every existing implementer keeps compiling and inherits working behavior, + concept, the default interface member as a non-breaking extension point. [Rubric §15, Best Practices & Code Quality] assesses whether the codebase can absorb change without a ripple. Four + members here ship with bodies: TryGetAsync (ICacheService.cs:34), GetFromSharedStoreAsync + (ICacheService.cs:62), IncrementAsync (ICacheService.cs:105) and GetOrCreateAsync + (ICacheService.cs:145). Every existing implementer keeps compiling and inherits working behavior, while a store with a better primitive overrides. That is how the ADR-029 - counters, and later the whole + counters, the whole ADR-077 two-level substrate, - were added to a published package contract without a breaking change. [Rubric §12, Performance & Scalability]: RemoveByPrefixAsync (ICacheService.cs:42) is the member that makes scoped - invalidation possible, so one mutation evicts a whole family of cached query results without - enumerating individual keys.
      • + and later the shared-store read for single-use records were added to a published package contract + without a breaking change. [Rubric §12, Performance & Scalability]: RemoveByPrefixAsync + (ICacheService.cs:88) is the member that makes scoped invalidation possible, so one mutation + evicts a whole family of cached query results without enumerating individual keys.
      • Walkthrough: members in declaration order.
        • Task<T?> GetAsync<T>(string key, CancellationToken) (ICacheService.cs:17), returns default / null on a miss.
        • +
        • async Task<(bool Found, T? Value)> TryGetAsync<T>(string key, CancellationToken) + (ICacheService.cs:34-38), a default implementation. It exists for a value-type T, where + GetAsync answers a miss with default(T) (0, false, Guid.Empty) that cannot be told apart + from a cached default(T) (ICacheService.cs:19-24). The default body infers presence from a + non-null GetAsync result (ICacheService.cs:36-37), which is exact for reference and nullable + types; MemoryCacheService and + DistributedCacheService override it with a real presence check.
        • +
        • Task<T?> GetFromSharedStoreAsync<T>(string key, CancellationToken) (ICacheService.cs:62-63), + a default implementation that simply calls GetAsync. It reads from the shared backing store, + bypassing any process-local copy, and is meant for single-use records (an OAuth exchange code, a + password-reset or email-confirmation token, a second-factor time step) whose consumption on one + replica must be visible to every other replica at once (ICacheService.cs:40-43). The remarks give + the usage rule: read the record through this member, then remove it; everything read many times + stays on GetAsync (ICacheService.cs:50-54). The default is exact for a store with no + process-local tier; HybridCacheService overrides it + (ICacheService.cs:56-59).
        • Task SetAsync<T>(string key, T value, TimeSpan? expiration = null, CancellationToken) - (ICacheService.cs:26-30). A null expiration means "use the implementation's default TTL", - resolved from CacheSettings on both - distributed paths (whose own default is CacheOptions.DefaultDuration).
        • -
        • Task RemoveAsync(string key, CancellationToken) (ICacheService.cs:36), single-key eviction.
        • -
        • Task RemoveByPrefixAsync(string prefix, CancellationToken) (ICacheService.cs:42), bulk eviction + (ICacheService.cs:72-76). A null expiration means "use the implementation's default TTL", + resolved from CacheSettings on all three shipped stores + (whose own default is CacheOptions.DefaultDuration).
        • +
        • Task RemoveAsync(string key, CancellationToken) (ICacheService.cs:82), single-key eviction.
        • +
        • Task RemoveByPrefixAsync(string prefix, CancellationToken) (ICacheService.cs:88), bulk eviction of every key starting with prefix. This is what CachingCommandDecorator<TCommand, TResult> invokes after a successful mutation.
        • async Task<long> IncrementAsync(string key, TimeSpan expiration, CancellationToken) - (ICacheService.cs:59-65), a default implementation: read the current value as long? (0 on a + (ICacheService.cs:105-111), a default implementation: read the current value as long? (0 on a miss), add one, write it back with expiration, return the new value. The doc - (ICacheService.cs:44-58) is explicit about why it exists: rate-limit and brute-force counters + (ICacheService.cs:90-104) is explicit about why it exists: rate-limit and brute-force counters (ADR-029) built from a raw GetAsync + SetAsync pair let concurrent requests overwrite each other's increments and undercount, so the read-modify-write is at least centralised in one auditable place, and a store with a native counter primitive can override.
        • -
        • async Task<T> GetOrCreateAsync<T>(string key, Func<CancellationToken, Task<T>> factory, TimeSpan? expiration = null, CancellationToken) (ICacheService.cs:99-124), the second default - implementation and the more interesting one. It null-guards the factory (ICacheService.cs:105), - takes a lock-free fast path on a hit (ICacheService.cs:108-110), and only on a miss acquires +
        • async Task<T> GetOrCreateAsync<T>(string key, Func<CancellationToken, Task<T>> factory, TimeSpan? expiration = null, CancellationToken) (ICacheService.cs:145-171), the last default + implementation and the most interesting one. It null-guards the factory (ICacheService.cs:151), + takes a lock-free fast path on a hit (ICacheService.cs:155-157), and only on a miss acquires the key's stripe from CacheKeyLocks - (ICacheService.cs:112), re-reads under the lock (ICacheService.cs:116-118, the double-check that + (ICacheService.cs:159), re-reads under the lock (ICacheService.cs:163-165, the double-check that lets waiters see the value the winner just wrote), then runs the factory and stores its result - (ICacheService.cs:120-121). That is the same read-through-with-stampede-protection shape + (ICacheService.cs:167-168). Both reads go through TryGetAsync, not a null test, so a cached + default(T) of a value type counts as a hit and a miss still runs the factory + (ICacheService.cs:153-154). That is the same read-through-with-stampede-protection shape CachingQueryDecorator<TQuery, TResult> applies to cacheable queries, made available to any caller.
        • -
        • CacheKeyLocks (ICacheService.cs:142-146), the +
        • CacheKeyLocks (ICacheService.cs:188-192), the non-generic holder for the fixed-width KeyedSemaphoreStripe that the default GetOrCreateAsync uses. It is deliberately a separate table from the query decorator's QueryCacheKeyLocks - (ICacheService.cs:137-140): different call sites over different keys, and sharing stripes would + (ICacheService.cs:184-186): different call sites over different keys, and sharing stripes would only widen the unrelated-key collisions striping already tolerates.
      • Why it's built this way: keeping the port in MMCA.Common.Application rather than Infrastructure is what lets the CQRS decorators, which also live in Application, depend on caching without dragging a Redis reference into the business layers. The optional TimeSpan? expiration lets callers override - the global TTL without a second overload. The two defaulted members follow the precedent + the global TTL without a second overload. The four defaulted members follow the precedent ADR-077 names explicitly: a default interface member is how this package grows a capability that no consumer has to react to. - Note the honest boundary the GetOrCreateAsync remarks draw (ICacheService.cs:80-98): caching is + GetFromSharedStoreAsync makes the single-use-record rule a named member rather than a convention, so + a caller states "this read must not be answered from a replica's own memory" at the call site. + Note the honest boundary the GetOrCreateAsync remarks draw (ICacheService.cs:126-144): caching is unconditional there, so a failed Result would be cached, which is exactly why the caching decorators do NOT route through this member and keep their own read/execute/write sequence.
      • Where it's used: both caching decorators take ICacheService by constructor injection (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/CachingQueryDecorator.cs:45, - .../CachingCommandDecorator.cs:33). Outside the pipeline, + .../CachingCommandDecorator.cs:35). Outside the pipeline, LoginProtectionService calls IncrementAsync for failed logins and per-IP registrations - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/LoginProtectionService.cs:64 and :130); - PasswordResetTokenService uses it for both the - per-email request counter and the token entries themselves - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:55, :88, - :100); SoftDeletedUserCache writes the soft-deleted marker - with SetAsync (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/SoftDeletedUserCache.cs:60), + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/LoginProtectionService.cs:64 and :119); + PasswordResetTokenService uses it for the per-email + request counter, the token write and the token read, the last through GetFromSharedStoreAsync + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:68, :90, + :128); EmailConfirmationTokenService follows the + same shape + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/EmailConfirmationTokenService.cs:53, :75, + :90); TwoFactorAuthenticator reads the last accepted + time step through GetFromSharedStoreAsync and writes the new one with SetAsync + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/TwoFactor/TwoFactorAuthenticator.cs:105 + and :113); SoftDeletedUserCache writes the soft-deleted + marker with SetAsync (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/SoftDeletedUserCache.cs:60), read back by SoftDeletedUserMiddleware (MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/SoftDeletedUserMiddleware.cs:91); IdempotencyFilter resolves it per request - (MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:140) to store and - replay the cached response record (IdempotencyFilter.cs:360 and :435, default expiration 24 hours - at :80); and OAuthControllerBase takes it as + (MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:141) to store and + replay the cached response record (IdempotencyFilter.cs:361 and :450, default expiration 24 hours + at :81); and OAuthControllerBase takes it as a constructor dependency - (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/OAuthControllerBase.cs:37). Exactly - one implementation is live per host: AddCaching() registers one via TryAddSingleton + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/OAuthControllerBase.cs:37), writing the + exchange-code entry with SetAsync (OAuthControllerBase.cs:142) and redeeming it through + GetFromSharedStoreAsync (OAuthControllerBase.cs:196). Exactly one implementation is live per + host: AddCaching() registers one via TryAddSingleton (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:59), and - AddCommonHybridCache() replaces it (DependencyInjection.Caching.cs:163-164). The default + AddCommonHybridCache() replaces it (DependencyInjection.Caching.cs:165-166). The default GetOrCreateAsync body is covered by CacheServiceGetOrCreateTests.
      • Caveats / not-in-source: IncrementAsync is not atomic on any shipped implementation. The default body is a read-modify-write, and both distributed adapters override it with the same shape rather than Redis INCR, for the storage-format reason spelled out in the DistributedCacheService section. Stampede protection in - GetOrCreateAsync is likewise per process (ICacheService.cs:88-91): with several replicas over + GetOrCreateAsync is likewise per process (ICacheService.cs:134-136): with several replicas over one shared cache the factory can still run once per replica, and a cluster-wide guarantee would need - a distributed lock, which is deliberately not attempted here. And GetOrCreateAsync has no - first-party caller outside tests today: it is a published extension point plus the member - HybridCacheService overrides.
      • + a distributed lock, which is deliberately not attempted here. GetOrCreateAsync has no first-party + caller outside tests today: it is a published extension point plus the member + HybridCacheService overrides. And TryGetAsync is overridden only by + MemoryCacheService and DistributedCacheService: + HybridCacheService inherits the null-inference default, which is harmless for + its own GetOrCreateAsync (overridden, so it never calls TryGetAsync) but means a direct + TryGetAsync of a value type on that store cannot distinguish a miss from a cached default(T).

      DistributedCacheService

      @@ -1014,29 +1093,34 @@

      DistributedCacheService

    838. GetAsync<T> (DistributedCacheService.cs:40-45), fetches the raw byte[] via cache.GetAsync(_keys.Qualify(key), ...); returns default on null (a miss), else Deserialize<T>.
    839. -
    840. SetAsync<T> (DistributedCacheService.cs:53-66), serializes to bytes and writes with - cache.SetAsync(_keys.Qualify(key), bytes, CacheOptions.Create(expiration ?? _settings.DefaultDuration), ...) (DistributedCacheService.cs:64). That single line is where the +
    841. TryGetAsync<T> (DistributedCacheService.cs:48-53), an override of the + ICacheService default that reads the same raw bytes and decides presence from + them: null bytes are (false, default), anything else is (true, Deserialize<T>(bytes)) + (DistributedCacheService.cs:52). Presence is therefore a property of the stored entry, not of the + deserialized value, so a cached 0 or false is a hit.
    842. +
    843. SetAsync<T> (DistributedCacheService.cs:61-74), serializes to bytes and writes with + cache.SetAsync(_keys.Qualify(key), bytes, CacheOptions.Create(expiration ?? _settings.DefaultDuration), ...) (DistributedCacheService.cs:72). That single line is where the caller's optional TimeSpan?, the configured default and the hard-coded framework default all collapse into one DistributedCacheEntryOptions.
    844. -
    845. RemoveAsync (DistributedCacheService.cs:69-70), expression-bodied passthrough on the qualified +
    846. RemoveAsync (DistributedCacheService.cs:77-78), expression-bodied passthrough on the qualified key.
    847. -
    848. _noMultiplexerWarned (DistributedCacheService.cs:73), an int flag flipped once via +
    849. _noMultiplexerWarned (DistributedCacheService.cs:81), an int flag flipped once via Interlocked.Exchange so the missing-multiplexer warning fires exactly once per process rather than on every mutating command.
    850. -
    851. RemoveByPrefixAsync (DistributedCacheService.cs:100-123). If connectionMultiplexer is null - (DistributedCacheService.cs:102) it logs the no-op once, guarded by Interlocked.Exchange(ref _noMultiplexerWarned, 1) == 0 (DistributedCacheService.cs:107-108), and returns; entries then +
    852. RemoveByPrefixAsync (DistributedCacheService.cs:108-131). If connectionMultiplexer is null + (DistributedCacheService.cs:110) it logs the no-op once, guarded by Interlocked.Exchange(ref _noMultiplexerWarned, 1) == 0 (DistributedCacheService.cs:115-116), and returns; entries then expire on TTL alone. Otherwise it delegates the whole scan to RedisPrefixScanner.RemoveMatchingAsync - (DistributedCacheService.cs:116-122), passing the namespaced pattern - $"{_keys.Qualify(prefix)}*" (DistributedCacheService.cs:118, note the prefix is namespaced too, + (DistributedCacheService.cs:124-130), passing the namespaced pattern + $"{_keys.Qualify(prefix)}*" (DistributedCacheService.cs:126, note the prefix is namespaced too, which is the entire reason the namespace lives here rather than in RedisCacheOptions.InstanceName), a raw KeyDeleteAsync as the per-key delete over a lazily - resolved IDatabase (DistributedCacheService.cs:114 and :120, so a host whose multiplexer + resolved IDatabase (DistributedCacheService.cs:127 and :122, so a host whose multiplexer reports no scannable server never asks for a database), and its own two log hooks - (DistributedCacheService.cs:120-121).
    853. -
    854. IncrementAsync (DistributedCacheService.cs:145-151), an override of the + (DistributedCacheService.cs:128-129).
    855. +
    856. IncrementAsync (DistributedCacheService.cs:153-159), an override of the ICacheService default that keeps the same read-modify-write shape. The remarks - (DistributedCacheService.cs:126-144) are the important read. Redis INCR would be atomic, which + (DistributedCacheService.cs:133-152) are the important read. Redis INCR would be atomic, which is what the member was added for, but INCR writes a Redis string while StackExchangeRedisCache stores every entry as a Redis hash (absexp / sldexp / data, read back with HMGET). Mixing the two at one key makes the next read fail with WRONGTYPE, which @@ -1044,11 +1128,11 @@

      DistributedCacheService

      ADR-029 case). A counter has to live in the same storage format as the reads that consult it, so readability was chosen over atomicity.
    857. -
    858. Deserialize<T> / Serialize<T> (DistributedCacheService.cs:153-157), private static JSON +
    859. Deserialize<T> / Serialize<T> (DistributedCacheService.cs:161-165), private static JSON helpers: SerializeToUtf8Bytes(value) and Deserialize<T>(bytes)! (null-forgiving, since the BCL signature is nominally nullable).
    860. LogPrefixEvictionNoMultiplexer / LogPrefixEvictionNoServer / LogPrefixEvictionServerFailed - (DistributedCacheService.cs:159-166), [LoggerMessage] Warning-level partial methods. The + (DistributedCacheService.cs:167-174), [LoggerMessage] Warning-level partial methods. The first names the fix explicitly ("Register a Redis client (AddRedisClient) to enable prefix eviction"), and the third states the blast radius ("the remaining servers are still processed, so entries on this one are bounded only by their TTL"), which is the difference between a log line and @@ -1064,11 +1148,13 @@

      DistributedCacheService

      than throwing, and the 30-second TTL becomes the staleness backstop. Warn-once keeps that degradation from being invisible without flooding the log. Lifting the scan into RedisPrefixScanner came with the two-level cache: two adapters that evict - differently should still share one eviction algorithm. internal sealed partial: partial for the - generated log methods, internal sealed because it is only ever resolved through the - ICacheService registration.
    861. + differently should still share one eviction algorithm. The class has no process-local tier, so it + keeps the ICacheService default for GetFromSharedStoreAsync: every read already + comes from the shared store. internal sealed partial: partial for the generated log methods, + internal sealed because it is only ever resolved through the ICacheService + registration.
    862. Where it's used: selected by AddCaching() - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:59-78). The + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:59-80). The TryAddSingleton<ICacheService> factory builds this implementation only when an IDistributedCache is present and is not the default MemoryDistributedCache (DependencyInjection.Caching.cs:62), resolving the optional IConnectionMultiplexer @@ -1082,18 +1168,21 @@

      DistributedCacheService

      DistributedCacheServiceTests and, against a real Redis, DistributedCacheServiceRedisTests.
    863. -
    864. Caveats / not-in-source: all seven deployed service hosts call AddCommonHybridCache() inside a - Redis-conditional block (for example - MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:201-204, - MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:94), so this adapter is not the - one the container hands out in those hosts: it is the default for any host that registers a real - distributed cache and does not opt in. IncrementAsync here is not atomic (see the walkthrough); +
    865. Caveats / not-in-source: all seven deployed service hosts call + AddCommonHybridCacheWhenRedisConfigured, which registers the two-level cache only when the redis + connection string is present + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:199-211; for + example MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:203, + MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:108), so this adapter is not the + one the container hands out in those hosts when Redis is configured: it is the default for any host + that registers a real distributed cache and does not opt in. IncrementAsync here is not atomic + (see the walkthrough); ADR-026 records the possible undercount as accepted rather than outstanding.
    866. HybridCacheService

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Caching · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/HybridCacheService.cs:36 · Level 4 · class (internal sealed partial)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Caching · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/HybridCacheService.cs:44 · Level 4 · class (internal sealed partial)

      • What it is: the two-level implementation of ICacheService, backed by @@ -1109,13 +1198,13 @@

        HybridCacheService

        StackExchange.Redis.IConnectionMultiplexer (optional).
      • Concept introduced, two serialization formats must never share one keyspace. [Rubric §8, Data Architecture] assesses whether stored data has one owner and one shape; [Rubric §15, Best Practices & Code Quality] assesses whether a change can be rolled out without a coordinated flag day. Every key this service writes carries a hc: segment inside the configured prefix, {prefix}hc:{key} - (HybridCacheService.cs:20, :48, :261), which is the structural form of the WRONGTYPE lesson + (HybridCacheService.cs:20, :55, :307), which is the structural form of the WRONGTYPE lesson recorded on DistributedCacheService.IncrementAsync. HybridCache writes its own payload layout, not the UTF-8 JSON the older adapter writes, so letting the two meet at one key would reproduce that production failure at every key rather than at one counter. With the keyspaces disjoint, an entry written by the other service is simply invisible to this one (a clean miss) and vice versa, so two hosts can share one Redis without either being able to read a - payload it cannot parse (HybridCacheService.cs:19-28). + payload it cannot parse (HybridCacheService.cs:19-27). ADR-077 adds the case the code comment does not spell out, a rolling deploy where both builds serve traffic against one Redis (077-hybridcache-substrate.md:54-56), records the rejected alternative, a payload discriminator in @@ -1123,65 +1212,79 @@

        HybridCacheService

        (077-hybridcache-substrate.md:58-61); it also states the consequence this code implements, that prefix eviction "scans the hc: keyspace and nothing else" (077-hybridcache-substrate.md:66-67). [Rubric §12, Performance & Scalability]: the whole point of - L1 is removing a network hop and a JSON deserialize from every read of a small hot value.
      • -
      • Walkthrough: primary constructor (HybridCacheService.cs:36-41), the same shape as the + L1 is removing a network hop and a JSON deserialize from every read of a small hot value. Second + concept, knowing which reads must skip the fast tier. [Rubric §11, Security]: an L1 copy is only + safe for a value that tolerates being stale on one replica for the local expiration. A counter and a + single-use record (an OAuth exchange code, a password-reset or email-confirmation token, the last + accepted second-factor time step) do not, so both read from L2 alone (HybridCacheService.cs:35-42).
      • +
      • Walkthrough: primary constructor (HybridCacheService.cs:44-49), the same shape as the distributed adapter but over HybridCache hybrid.
          -
        • KeyspaceSegment (HybridCacheService.cs:47), internal const string = "hc:", applied inside +
        • KeyspaceSegment (HybridCacheService.cs:55), internal const string = "hc:", applied inside the configured namespace.
        • -
        • LocalCacheDefault (HybridCacheService.cs:54), internal static readonly TimeSpan = 30 seconds. +
        • LocalCacheDefault (HybridCacheService.cs:62), internal static readonly TimeSpan = 30 seconds. It is both the default L1 lifetime and the ceiling applied to every entry, and AddCommonHybridCache seeds HybridCacheOptions from the same field when the host configured no Cache:LocalCacheDuration - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:155).
        • -
        • ReadOnlyOptions (HybridCacheService.cs:62-65) and CounterReadOptions - (HybridCacheService.cs:71-76), two static option objects. The first sets + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:157).
        • +
        • ReadOnlyOptions (HybridCacheService.cs:70-73) and SharedStoreReadOptions + (HybridCacheService.cs:82-87), two static option objects. The first sets HybridCacheEntryFlags.DisableUnderlyingData, which tells HybridCache not to invoke the factory and not to write anything, so a miss stays a miss while an L2 hit is still promoted into L1. The - second adds DisableLocalCacheRead | DisableLocalCacheWrite for the counter path.
        • -
        • _keys (HybridCacheService.cs:82), _settings (HybridCacheService.cs:89) and - _noMultiplexerWarned (HybridCacheService.cs:92), identical in role to their counterparts on + second adds DisableLocalCacheRead | DisableLocalCacheWrite, a read answered by L2 alone with no + L1 promotion, shared by the two reads whose correctness depends on every replica seeing the same + value: the counter read in IncrementAsync and GetFromSharedStoreAsync + (HybridCacheService.cs:75-81).
        • +
        • _keys (HybridCacheService.cs:93), _settings (HybridCacheService.cs:100) and + _noMultiplexerWarned (HybridCacheService.cs:103), identical in role to their counterparts on DistributedCacheService.
        • -
        • GetAsync<T> (HybridCacheService.cs:103-122), calls hybrid.GetOrCreateAsync with a static - no-op factory and ReadOnlyOptions (HybridCacheService.cs:109-114), which is how you perform a +
        • GetAsync<T> (HybridCacheService.cs:114-133), calls hybrid.GetOrCreateAsync with a static + no-op factory and ReadOnlyOptions (HybridCacheService.cs:120-125), which is how you perform a plain read through an API whose primary shape is get-or-create. It is fail-soft - (HybridCacheService.cs:95-102): any exception that is not OperationCanceledException is logged - at warning and answered as a miss (HybridCacheService.cs:116-121), and the offending entry is + (HybridCacheService.cs:106-113): any exception that is not OperationCanceledException is logged + at warning and answered as a miss (HybridCacheService.cs:127-132), and the offending entry is dropped best-effort through SelfHealAsync so the next write repopulates it instead of the process failing the same read forever.
        • -
        • SetAsync<T> (HybridCacheService.cs:132-137), one call to hybrid.SetAsync with the options +
        • GetFromSharedStoreAsync<T> (HybridCacheService.cs:150-169), an override of the + ICacheService default. It is the same no-op-factory read as GetAsync, but with + SharedStoreReadOptions (HybridCacheService.cs:159), so the answer always comes from L2 and + nothing is promoted into this replica's memory: a removal on another replica is seen immediately, + which is what keeps a single-use record single-use (HybridCacheService.cs:136-141). It is + fail-soft exactly like GetAsync (HybridCacheService.cs:163-168); for a single-use record a miss + is a refusal, which is the safe direction (HybridCacheService.cs:143-147).
        • +
        • SetAsync<T> (HybridCacheService.cs:179-184), one call to hybrid.SetAsync with the options WriteOptions(expiration) builds.
        • -
        • RemoveAsync (HybridCacheService.cs:141-142), hybrid.RemoveAsync, which clears this process's +
        • RemoveAsync (HybridCacheService.cs:188-189), hybrid.RemoveAsync, which clears this process's L1 copy along with the L2 entry.
        • -
        • RemoveByPrefixAsync (HybridCacheService.cs:161-180). After the same warn-once no-multiplexer - guard (HybridCacheService.cs:163-171) it runs RedisPrefixScanner once, - over this service's own pattern $"{HybridKey(prefix)}*" (HybridCacheService.cs:175), which is +
        • RemoveByPrefixAsync (HybridCacheService.cs:208-227). After the same warn-once no-multiplexer + guard (HybridCacheService.cs:210-218) it runs RedisPrefixScanner once, + over this service's own pattern $"{HybridKey(prefix)}*" (HybridCacheService.cs:222), which is the one keyspace this service writes and therefore the one it evicts. The per-key delete routes back through hybrid.RemoveAsync rather than a raw KeyDeleteAsync - (HybridCacheService.cs:176): a raw delete would clear L2 and leave this process's own L1 copy - serving the value it just invalidated (HybridCacheService.cs:151-155).
        • -
        • IncrementAsync (HybridCacheService.cs:206-227), a read-modify-write like the distributed + (HybridCacheService.cs:223): a raw delete would clear L2 and leave this process's own L1 copy + serving the value it just invalidated (HybridCacheService.cs:199-201).
        • +
        • IncrementAsync (HybridCacheService.cs:253-274), a read-modify-write like the distributed adapter's, and deliberately not routed through this class's own GetAsync / SetAsync because - both legs must bypass L1 (HybridCacheService.cs:210-215 reads with CounterReadOptions, - :220-225 writes with the two disable flags). The reason - (HybridCacheService.cs:192-200) is the sharpest argument in this group: a counter is the one + both legs must bypass L1 (HybridCacheService.cs:257-262 reads with SharedStoreReadOptions, + :266-271 writes with the two disable flags). The reason + (HybridCacheService.cs:240-247) is the sharpest argument in this group: a counter is the one value whose correctness depends on every replica seeing the same number, so an L1 copy would let a process read its own stale count and write it back, and a brute-force counter could then be held near its starting value indefinitely by a steady stream of attempts against one replica. That is a security control silently weakened by a cache optimization, so [Rubric §11, Security] is the category that decided this member, not §12. Faults are also not swallowed here, unlike - GetAsync (HybridCacheService.cs:201-204): a counter that silently reads as zero would reset the + GetAsync (HybridCacheService.cs:249-250): a counter that silently reads as zero would reset the limit it exists to enforce.
        • -
        • GetOrCreateAsync<T> (HybridCacheService.cs:238-255), an override of the interface default that +
        • GetOrCreateAsync<T> (HybridCacheService.cs:285-302), an override of the interface default that hands the work to HybridCache's own primitive, which folds the double-check and the stampede protection into one call and additionally deduplicates concurrent callers before they reach L2. The - factory is passed as state rather than captured (HybridCacheService.cs:248-251), so the + factory is passed as state rather than captured (HybridCacheService.cs:293-301), so the delegate stays static and no closure is allocated per call.
        • -
        • HybridKey (HybridCacheService.cs:260), WriteOptions (HybridCacheService.cs:269-280) and - SelfHealAsync (HybridCacheService.cs:290-300), the private helpers. WriteOptions is where +
        • HybridKey (HybridCacheService.cs:307), WriteOptions (HybridCacheService.cs:316-327) and + SelfHealAsync (HybridCacheService.cs:337-347), the private helpers. WriteOptions is where both dials meet: ttl = expiration ?? _settings.DefaultDuration - (HybridCacheService.cs:271), localCeiling = _settings.LocalCacheDuration ?? LocalCacheDefault - (HybridCacheService.cs:272), and LocalCacheExpiration = ttl < localCeiling ? ttl : localCeiling - (HybridCacheService.cs:277), so a long-lived entry does not sit in another replica's memory for + (HybridCacheService.cs:318), localCeiling = _settings.LocalCacheDuration ?? LocalCacheDefault + (HybridCacheService.cs:319), and LocalCacheExpiration = ttl < localCeiling ? ttl : localCeiling + (HybridCacheService.cs:324), so a long-lived entry does not sit in another replica's memory for its whole TTL after an invalidation that process never saw.
      • @@ -1190,21 +1293,22 @@

        HybridCacheService

        rather than default keeps the release non-breaking: a host that never calls AddCommonHybridCache gets a byte-identical registration to before, and a memory-only host would gain nothing from an L1 in front of an L1 anyway - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:110-116). The + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:112-118). The registration deliberately uses RemoveAll + Add rather than TryAdd so it wins in either call - order (DependencyInjection.Caching.cs:161-164), with the honest warning that RemoveAll does not + order (DependencyInjection.Caching.cs:163-166), with the honest warning that RemoveAll does not distinguish the framework's registration from a host's own custom - ICacheService (DependencyInjection.Caching.cs:125-130). [Rubric §29, Resilience] + ICacheService (DependencyInjection.Caching.cs:127-132). [Rubric §29, Resilience] shows up in the fail-soft read: the cache is an optimization, never the system of record, so an unreadable entry costs a database round trip rather than a failed request.
      • Where it's used: registered only by AddCommonHybridCache - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:137-180), which all - seven deployed service hosts call inside a GetConnectionString("redis") conditional - (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:203, - MMCA.ADC.Engagement.Service/Program.cs:113, MMCA.ADC.Identity.Service/Program.cs:135, - MMCA.ADC.Notification.Service/Program.cs:119, - MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:94, - MMCA.Store.Sales.Service/Program.cs:111, MMCA.Store.Identity.Service/Program.cs:100). Everything + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:139-182), which all + seven deployed service hosts reach through AddCommonHybridCacheWhenRedisConfigured, the guard that + calls it only when the redis connection string is configured + (DependencyInjection.Caching.cs:199-211): MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:203, + MMCA.ADC.Engagement.Service/Program.cs:113, MMCA.ADC.Identity.Service/Program.cs:134, + MMCA.ADC.Notification.Service/Program.cs:116, + MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:108, + MMCA.Store.Sales.Service/Program.cs:113, MMCA.Store.Identity.Service/Program.cs:102. Everything downstream still talks to ICacheService and is unaware. Covered by HybridCacheServiceTests, the registration semantics by @@ -1218,14 +1322,18 @@

        HybridCacheService

        CachingCommandDecorator<TCommand, TResult> already performs (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/CachingCommandDecorator.cs:45 - and :79). Because the keyspaces are disjoint, a host that switches substrate starts cold: entries + and :81). It does not apply to counters or to reads made through GetFromSharedStoreAsync, which + never touch L1; a caller that reads a single-use record through plain GetAsync instead still gets + the L1 window, so the protection depends on the call site choosing the right member. TryGetAsync + is not overridden here, so it uses the ICacheService null-inference default. + Because the keyspaces are disjoint, a host that switches substrate starts cold: entries another ICacheService implementation wrote are invisible here and age out on their own TTL, which ADR-077 records as a cost rather than a correctness problem (077-hybridcache-substrate.md:61-65).

      MemoryCacheService

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Caching · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:18 · Level 4 · class (internal sealed)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Caching · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/MemoryCacheService.cs:19 · Level 4 · class (internal sealed)

      • What it is: the in-process implementation of ICacheService, backed by @@ -1233,14 +1341,16 @@

        MemoryCacheService

        its own ConcurrentDictionary<string, object> tracking table so it can honor RemoveByPrefixAsync, the one capability the BCL memory cache lacks. The cache and that table are two structures that have to agree, so every mutation of a key runs under that key's lock stripe - (MemoryCacheService.cs:8-17).
      • + (MemoryCacheService.cs:9-18).
      • Depends on: IMemoryCache / MemoryCacheEntryOptions and ConcurrentDictionary<TKey, TValue> - (both BCL), plus KeyedSemaphoreStripe from - MMCA.Common.Shared.Concurrency (MemoryCacheService.cs:4, :38) for the per-key mutual exclusion. - Implements ICacheService and overrides neither default member, so it inherits - the non-atomic IncrementAsync and the stripe-plus-double-check GetOrCreateAsync. It takes no - CacheKeyNamespace and no - CacheSettings at all.
      • + (both BCL), KeyedSemaphoreStripe from + MMCA.Common.Shared.Concurrency (MemoryCacheService.cs:5, :45) for the per-key mutual exclusion, + and the optional CacheSettings (MemoryCacheService.cs:19, + :25) for the default TTL. Implements ICacheService and overrides TryGetAsync + but none of the other default members, so it inherits the non-atomic IncrementAsync, the + stripe-plus-double-check GetOrCreateAsync and the GetAsync-backed GetFromSharedStoreAsync + (exact here, since there is no shared store beyond this process). It takes no + CacheKeyNamespace.
      • Concept introduced, a shadow index to back-fill a missing API. [Rubric §12, Performance & Scalability] assesses cheap reads and sound invalidation; an in-process cache is the lowest-latency option available but is not shared across instances, so it is correct for a single-process monolith or for genuinely per-instance data and wrong for anything else. The teachable mechanic is the shadow @@ -1250,49 +1360,58 @@

        MemoryCacheService

        distributed adapters, so swapping backends changes nothing for callers. Second concept, an invariant that write ordering cannot buy you. [Rubric §15, Best Practices & Code Quality] assesses everyday craftsmanship, including whether comments explain why rather than what. The SetAsync - remarks (MemoryCacheService.cs:55-63) are the worked example: with two structures to update, + remarks (MemoryCacheService.cs:67-75) are the worked example: with two structures to update, track-then-write lets a concurrent removal drop the tracking record between the two steps, and write-then-track lets a removal run entirely between them; both leave a live entry nothing can find. Neither order closes the window, so the class buys the invariant with mutual exclusion instead, and says so in the code rather than leaving the next reader to rediscover it.
      • -
      • Walkthrough: primary constructor injection (MemoryCacheService.cs:18), IMemoryCache cache.
          -
        • _keys (MemoryCacheService.cs:31), new ConcurrentDictionary<string, object>(StringComparer.Ordinal). The value is load-bearing: it is the tracking token of the +
        • Walkthrough: primary constructor injection (MemoryCacheService.cs:19), IMemoryCache cache + required and IOptions<CacheSettings>? settings = null optional.
            +
          • _settings (MemoryCacheService.cs:25), the bound Cache section or new CacheSettings() when a + host built the service without one. It supplies the expiration of an entry written without one, as + every other store does (MemoryCacheService.cs:21-24).
          • +
          • _keys (MemoryCacheService.cs:38), new ConcurrentDictionary<string, object>(StringComparer.Ordinal). The value is load-bearing: it is the tracking token of the cache entry the record belongs to, a plain object compared by reference - (MemoryCacheService.cs:20-30). It exists so a post-eviction callback, which necessarily runs + (MemoryCacheService.cs:27-37). It exists so a post-eviction callback, which necessarily runs after its own entry may already have been superseded, can remove only its OWN record and never the record of a newer live entry. Ordinal comparison matches the ordinal prefix test below.
          • -
          • _keyLocks (MemoryCacheService.cs:38), a +
          • _keyLocks (MemoryCacheService.cs:45), a KeyedSemaphoreStripe serializing the paired mutation of the cache and _keys for one key. It is per instance rather than static - (MemoryCacheService.cs:33-37): the tracking table belongs to this service instance, so two + (MemoryCacheService.cs:40-44): the tracking table belongs to this service instance, so two instances have nothing to serialize against each other.
          • -
          • GetAsync<T> (MemoryCacheService.cs:41-52), calls cache.TryGetValue(key, out var stored) and - then type-checks the stored object with stored is T typed (MemoryCacheService.cs:46) before +
          • GetAsync<T> (MemoryCacheService.cs:48-59), calls cache.TryGetValue(key, out var stored) and + then type-checks the stored object with stored is T typed (MemoryCacheService.cs:53) before returning it, wrapped in Task.FromResult (there is no real async work; IMemoryCache is synchronous). It takes no lock: it touches only the cache. The pattern match is deliberate - (MemoryCacheService.cs:43-45): the generic TryGetValue<T> overload performs an unchecked + (MemoryCacheService.cs:50-52): the generic TryGetValue<T> overload performs an unchecked (T)stored cast and throws InvalidCastException when a key is reused under a different T, so matching on the stored object turns a type mismatch (or a stored null) into a clean miss.
          • -
          • SetAsync<T> (MemoryCacheService.cs:64-106), the method that establishes the invariant the class - rests on. It builds MemoryCacheEntryOptions (MemoryCacheService.cs:70) and sets - AbsoluteExpirationRelativeToNow only when expiration.HasValue - (MemoryCacheService.cs:72-75), so there is no 30-second floor on this path: an unset TTL means no - time-based expiry, unlike the distributed paths. It mints this entry's identity, var token = new object() (MemoryCacheService.cs:78), and registers the post-eviction callback - (MemoryCacheService.cs:93-99) with that token as the callback state. The callback body - skips EvictionReason.Replaced (MemoryCacheService.cs:97) and removes through the - KeyValuePair overload, _keys.TryRemove(new KeyValuePair<string, object>(evictedKey.ToString()!, state!)), which deletes the record only while the tracked value is still this entry's own token. - The comment (MemoryCacheService.cs:80-92) explains the shape: the callback stays deliberately - lock-free because IMemoryCache queues it to the thread pool, and waiting on a stripe from a - pool thread would stall the pool behind whichever caller holds it; running lock-free means it can - land when the key already carries a newer live entry, so the token check is what stops it - untracking an entry that is still cached (live but invisible to RemoveByPrefixAsync, clearable - only by its TTL). Only then does the write happen, under the key's stripe: using (await _keyLocks.AcquireAsync(key, cancellationToken)...) (MemoryCacheService.cs:101), cache.Set(key, value, options) (MemoryCacheService.cs:103), _keys[key] = token - (MemoryCacheService.cs:104).
          • -
          • RemoveAsync (MemoryCacheService.cs:110-117), the same stripe and the same order - (MemoryCacheService.cs:109): acquire (:112), cache.Remove (:114), then _keys.TryRemove(key, out _) (:115).
          • -
          • RemoveByPrefixAsync (MemoryCacheService.cs:128-138), iterates _keys.Keys.Where(k => k.StartsWith(prefix, StringComparison.Ordinal)) (MemoryCacheService.cs:130) and removes each key - from both stores under its own stripe (MemoryCacheService.cs:132-136). Two details from the - remarks (MemoryCacheService.cs:120-127): the candidate list is a snapshot, because +
          • TryGetAsync<T> (MemoryCacheService.cs:62-64), an override of the + ICacheService default with the same type-checked lookup, returning (true, typed) on a match and (false, default) otherwise. Presence comes from TryGetValue itself, so a + cached 0 or false is a hit, which is what lets the inherited GetOrCreateAsync cache a + value-type default(T) instead of re-running its factory.
          • +
          • SetAsync<T> (MemoryCacheService.cs:76-116), the method that establishes the invariant the class + rests on. It builds MemoryCacheEntryOptions with AbsoluteExpirationRelativeToNow = expiration ?? _settings.DefaultDuration (MemoryCacheService.cs:82-85), so an entry written without a TTL + expires on the configured default exactly as on the distributed paths. It mints this entry's + identity, var token = new object() (MemoryCacheService.cs:88), and registers the post-eviction + callback (MemoryCacheService.cs:103-109) with that token as the callback state. The callback + body skips EvictionReason.Replaced (MemoryCacheService.cs:106) and removes through the + KeyValuePair overload, _keys.TryRemove(new KeyValuePair<string, object>(evictedKey.ToString()!, state!)) (MemoryCacheService.cs:107), which deletes the record only while the tracked value is + still this entry's own token. The comment (MemoryCacheService.cs:90-102) explains the shape: the + callback stays deliberately lock-free because IMemoryCache queues it to the thread pool, and + waiting on a stripe from a pool thread would stall the pool behind whichever caller holds it; + running lock-free means it can land when the key already carries a newer live entry, so the token + check is what stops it untracking an entry that is still cached (live but invisible to + RemoveByPrefixAsync, clearable only by its TTL). Only then does the write happen, under the key's + stripe: using (await _keyLocks.AcquireAsync(key, cancellationToken)...) + (MemoryCacheService.cs:111), cache.Set(key, value, options) (MemoryCacheService.cs:113), + _keys[key] = token (MemoryCacheService.cs:114).
          • +
          • RemoveAsync (MemoryCacheService.cs:120-127), the same stripe and the same order + (MemoryCacheService.cs:119): acquire (:122), cache.Remove (:124), then _keys.TryRemove(key, out _) (:125).
          • +
          • RemoveByPrefixAsync (MemoryCacheService.cs:138-148), iterates _keys.Keys.Where(k => k.StartsWith(prefix, StringComparison.Ordinal)) (MemoryCacheService.cs:140) and removes each key + from both stores under its own stripe (MemoryCacheService.cs:142-146). Two details from the + remarks (MemoryCacheService.cs:130-137): the candidate list is a snapshot, because ConcurrentDictionary.Keys already copies, so it is enumerated outside every lock; and each stripe is released before the next one is taken, never accumulated across the loop, because distinct keys can map to the same stripe and to different stripes in a different relative order, so holding @@ -1307,22 +1426,30 @@

            MemoryCacheService

            entries that are still live. The stripe then covers what neither guard can, the window between the two writes that any single-threaded reading of the code hides. Striping rather than a semaphore per key is a bounded-memory choice made once in - KeyedSemaphoreStripe and reused here. The class is internal sealed because it is only ever resolved through the ICacheService registration.
          • + KeyedSemaphoreStripe and reused here. Taking + CacheSettings gives all three stores one TTL policy, so a + caller's omitted expiration means the same thing whichever adapter the host resolved. The class is + internal sealed because it is only ever resolved through the ICacheService + registration.
          • Where it's used: the fallback branch of AddCaching() - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:77). AddCaching() - always calls AddMemoryCache() first (DependencyInjection.Caching.cs:28), so when no real distributed - cache is registered this is the ICacheService the container hands out, and a host - with no Redis behaves as a single-instance cached monolith with the full interface intact. Consumed - through the interface by both CQRS caching decorators, + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:77-79), which + passes the optional IOptions<CacheSettings> (DependencyInjection.Caching.cs:79). AddCaching() + always calls AddMemoryCache() first (DependencyInjection.Caching.cs:28), so when no real + distributed cache is registered this is the ICacheService the container hands out, + and a host with no Redis behaves as a single-instance cached monolith with the full interface intact. + Consumed through the interface by both CQRS caching decorators, LoginProtectionService, PasswordResetTokenService, SoftDeletedUserCache and IdempotencyFilter. Unit-tested by MemoryCacheServiceTests, which pins the - concurrency behavior deterministically rather than racing for it: the test takes the key's own stripe - first, then asserts that a SetAsync and a RemoveByPrefixAsync both park on it - (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Caching/MemoryCacheServiceTests.cs:190) - and that RemoveAsync waits on the same stripe as SetAsync (MemoryCacheServiceTests.cs:221).
          • + default-TTL behavior + (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Caching/MemoryCacheServiceTests.cs:26), the + value-type GetOrCreateAsync miss that depends on TryGetAsync (MemoryCacheServiceTests.cs:40), + and the concurrency behavior deterministically rather than racing for it: the test takes the key's + own stripe first, then asserts that a SetAsync and a RemoveByPrefixAsync both park on it + (MemoryCacheServiceTests.cs:218) and that RemoveAsync waits on the same stripe as SetAsync + (MemoryCacheServiceTests.cs:251).
          • Caveats / not-in-source: the cache is per-process, so two replicas hold independent and potentially divergent copies until each entry's TTL or an explicit eviction reconciles them. That is why the distributed adapters exist for scaled-out deployments, and why diff --git a/docs/onboarding/group-10-notifications.html b/docs/onboarding/group-10-notifications.html index d483b6de..cf554ce2 100644 --- a/docs/onboarding/group-10-notifications.html +++ b/docs/onboarding/group-10-notifications.html @@ -162,9 +162,9 @@

            10. Notifications (Push + In forwarder) is chosen at the composition root, and a default is always registered so nothing has to be configured for DI to resolve. NullPushNotificationSender and NullLiveChannelPublisher are no-ops - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:315-316), + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:325-326), IEmailSender defaults to the real SmtpEmailSender - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:314), and + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:324), and INotificationRecipientProvider gets its default in the Application layer (MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/DependencyInjection.cs:75).

            There are really four delivery channels here, and it is worth separating them up front because @@ -173,7 +173,7 @@

            10. Notifications (Push + In
          • The durable in-app inbox. Every send writes one UserNotification row per recipient, so a user who was offline at send time still sees the message when they next open their inbox - (MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:115-123). + (MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:124-132). This is the persistent half of the two-channel model (ADR-024).
          • The transient SignalR push. IPushNotificationSender fans the same message out to any connections the recipient has open right now via the @@ -296,13 +296,13 @@

            The broadcast send flow, end to end< delivered, and every retry of that key would then report success forever (SendPushNotificationCommand.cs:11-19). Inside that transaction the handler runs a deliberate ordering - (MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:36-167):

            + (MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:41-176):

            • Dedup lookup first (lines 39-50). When a key is present the handler requeries for an existing PushNotification with that key and, on a hit, returns it mapped to a DTO without sending anything again. The lookup goes through unitOfWork.GetReadRepository<...>() rather than an injected repository, so it reads the same - data source the write below targets (SendPushNotificationHandler.cs:174-183).
            • + data source the write below targets (SendPushNotificationHandler.cs:194-203).
            • Resolve recipients through INotificationRecipientProvider, failing early with a PushNotification.NoRecipients validation error when the set is empty (lines 52-62).
            • @@ -397,7 +397,7 @@

              The inbox side

              content newest-first, applies the scope predicate to the PushNotification side only when one was supplied, and clamps paging through PagingMath with a 500-row page ceiling - (MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/UserNotifications/UseCases/GetInbox/GetMyNotificationsHandler.cs:21,32,39-59). + (MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/UserNotifications/UseCases/GetInbox/GetMyNotificationsHandler.cs:21,32,39-61).
            • The unread count (GetUnreadNotificationCountQuery / GetUnreadNotificationCountHandler), served [ResponseCache(NoStore = true)] so the bell badge is never stale @@ -416,7 +416,7 @@

              The inbox side

              tracked Table rather than TableNoTracking: an AsNoTracking source anywhere in a composed EF query switches the whole query to no-tracking, the UserNotification rows would come back untracked, and the MarkAsRead mutations would silently never be saved - (MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/UserNotifications/UseCases/MarkAllRead/MarkAllNotificationsReadHandler.cs:30-45) + (MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/UserNotifications/UseCases/MarkAllRead/MarkAllNotificationsReadHandler.cs:37-46)
              ([Rubric §8, Data Architecture]).

            UserNotification.MarkAsRead is idempotent and takes the read timestamp @@ -428,7 +428,7 @@

            The inbox side

            GetNotificationHistoryHandler pages the PushNotification audit rows newest-first under the same 500-row clamp and maps them through PushNotificationDTOMapper - (MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/GetHistory/GetNotificationHistoryHandler.cs:21,30-42). + (MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/GetHistory/GetNotificationHistoryHandler.cs:21,30-43). List reads that go through the generic query service take a different path again: PushNotificationDTOProjector wraps the Mapperly-generated PushNotificationDTOProjection, and merely registering it switches @@ -457,7 +457,7 @@

            The SignalR transp caller may read what is published to it, and the keys are consecutive integers, so enumerating them costs nothing. A host that publishes anything channel-scoped which is not public to every signed-in user has to register one. ADC does, per connection in the Notification host - (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:234), and the implementation is + (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:231), and the implementation is LiveChannelJoinAuthorizer: it refuses an unauthenticated principal, admits the privileged read audience without a lookup (ConferenceReadAudience.PrivilegedRoles, the same Organizer / ContentEditor pair that reads the unfiltered catalog through REST), and otherwise @@ -488,7 +488,7 @@

            The SignalR transp ([Rubric §7, Microservices Readiness]). In a monolith the default NullLiveChannelPublisher is registered, and a host that maps the hub swaps in the real SignalRLiveChannelPublisher - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:316, + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:326, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:66). In extracted ADC, Engagement is a different process from the one that owns the WebSocket, so Engagement's live layer depends on ILiveChannelPublisher as usual but the composition root Replaces the @@ -498,7 +498,7 @@

            The SignalR transp LiveChannelGrpcService, which then delegates to the local SignalRLiveChannelPublisher, the only host whose IHubContext can reach connected clients - (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:22-38). + (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:31-63). Both the adapter and the whole live path are best-effort by contract (ADR-039): every transport, resolution, or broken-circuit failure is logged and swallowed, never thrown, so a publishing command can never fail because Notification is slow or down @@ -510,19 +510,19 @@

            The SignalR transp named grpc (8081 in the container, 5996 locally) is declared in the Kestrel:Endpoints config section and resolved by peers as _grpc.notification through the services__notification__grpc__0 entry - (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:59-74). The host maps the hub - itself at /hubs/notifications via MapNotificationHub() (Program.cs:285-289, the path coming + (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:58-73). The host maps the hub + itself at /hubs/notifications via MapNotificationHub() (Program.cs:272-276, the path coming from PushNotificationSettings.HubPath, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Notifications/Push/PushNotificationSettings.cs:17) and both - gRPC services on that dedicated endpoint (Program.cs:298,306). The two gRPC surfaces are not + gRPC services on that dedicated endpoint (Program.cs:285,293). The two gRPC surfaces are not protected alike, and the difference is deliberate: the live-channel ingress is mapped - .AllowAnonymous() (Program.cs:298) because it is reachable only on the internal service network + .AllowAnonymous() (Program.cs:285) because it is reachable only on the internal service network and its caller (Engagement's LiveChannelPublishProcessor background drain) has no HttpContext and forwards no bearer - (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:13-20), + (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:16-23), while the export rpc is mapped with .RequireAuthorization() because its response carries personal - data keyed by a raw user id (Program.cs:302-306).

            + data keyed by a raw user id (Program.cs:289-293).

            The module host, native-device registration, and the privacy export

            On the ADC side the whole capability is packaged by NotificationModule (MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.API/NotificationModule.cs:15), an @@ -714,7 +714,7 @@

            IEmailSender

          • Why it's built this way: keeping the interface in Application (and the SMTP dependency in Infrastructure) is what lets a test host register a no-op sender and production register SmtpEmailSender. Registration is TryAddTransient<IEmailSender, SmtpEmailSender>() - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:314), so the TryAdd lets + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:324), so the TryAdd lets a host pre-register its own sender and win.
          • Where it's used: the framework's own consumer is the password-reset flow: ForgotPasswordHandlerBase<TUser, TCommand> @@ -757,7 +757,7 @@

            ILiveChannelPublisher

            [Rubric §7, Microservices Readiness] assesses whether cross-boundary calls go through abstractions that can be re-homed onto a network transport: in MMCA.ADC this exact interface is served over gRPC by the Notification host's LiveChannelGrpcService - (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:22) and + (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:31) and consumed through a scoped adapter that Replaces the local registration in publishing services (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Contracts/DependencyInjection.cs:48), so the boundary already survives extraction (ADR-007, @@ -770,7 +770,7 @@

            ILiveChannelPublisher

            of the business of knowing each live event's schema; the presentation and UI layers agree on the contract. Non-delivery to absent clients is the intended semantics, not a gap. The default registration is the inert NullLiveChannelPublisher - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:316), replaced by + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:326), replaced by SignalRLiveChannelPublisher when a host opts into the SignalR wiring (same file, line 632).
          • Where it's used: ADC's conference-day live layer does not inject it into command handlers. @@ -824,7 +824,7 @@

            INotificationRecipientProvider

            so the TryAdd finds the slot already taken.
          • Where it's used: SendPushNotificationHandler takes it as a primary constructor dependency - (MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:29), + (MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:34), resolves the audience, then hands it to IPushNotificationSender. Until an app registers its own provider, NullNotificationRecipientProvider returns an empty audience.
          • @@ -857,14 +857,14 @@

            IPushNotificationSender

          • Why it's built this way: three targeting methods rather than one "audience" parameter keeps each call site's intent explicit and lets the SignalR implementation map user-targeting to hub groups directly. The default registration is the no-op - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:315) so a host with no + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:325) so a host with no real-time transport still resolves the port; the SignalR wiring replaces it with AddTransient (same file, line 631), a deliberate override rather than a TryAdd.
          • Where it's used: SendPushNotificationHandler fans a message out - through this port (SendPushNotificationHandler.cs:30) after persisting the + through this port (SendPushNotificationHandler.cs:35) after persisting the PushNotification record and its per-user UserNotification rows. That handler also carries a second, separate delivery leg, - INativePushSender (SendPushNotificationHandler.cs:31), which is the OS-level channel of + INativePushSender (SendPushNotificationHandler.cs:36), which is the OS-level channel of ADR-044 rather than part of this port.
          @@ -885,7 +885,7 @@

          MarkAllNotificationsReadCommand

          That is a decision, not an omission: the Transactional decorator only opens a transaction for a command that implements ITransactional and otherwise passes it straight through - (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:27-29), + (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:29-31), so marking notifications read runs on the handler's own single SaveChangesAsync rather than an ambient transaction. [Rubric §6, CQRS & Event-Driven] assesses whether the write path is modeled as explicit messages with explicit cross-cutting opt-ins; the decorator pipeline is described in @@ -1080,7 +1080,7 @@

          GetMyNotificationsHandler

          and the handler pays a join at read time to reassemble the inbox view. [Rubric §3, Clean Architecture]: the handler expresses the join as a LINQ IQueryable but never calls EF's ToListAsync or CountAsync directly, delegating those to IQueryableExecutor so Application stays EF-free. - [Rubric §11, Security]: the where un.UserId == query.UserId clause (line 48) is the entire tenancy + [Rubric §11, Security]: the where un.UserId == query.UserId clause (line 50) is the entire tenancy boundary for an inbox, and the value comes from the resolved caller rather than from client input (NotificationInboxController.cs:49,55). The scope filter is explicitly not part of that boundary: the comment above it (lines 36-38) calls scope "a view filter, not a security boundary".
        • @@ -1096,21 +1096,24 @@

          GetMyNotificationsHandler

          pn.ScopeKey == null || pn.ScopeKey == scopeKey predicate when a scope is supplied (lines 39-44, the local string scopeKey on line 42 giving the expression tree a non-nullable capture); build the LINQ query-syntax join of UserNotification to that source on un.PushNotificationId equals pn.Id, - filtered to query.UserId, ordered by pn.CreatedOn descending, projected into UserNotificationDTO - (lines 46-59, mapping id, push id, title, body, IsRead, ReadOn, and SentOn = pn.CreatedOn); count - the joined set (line 61); page it with Skip(skip).Take(take) and materialize (lines 63-65); wrap - total, page size and floored page number into - PaginationMetadata (line 69) and return a + filtered to query.UserId, ordered by pn.CreatedOn descending then un.Id descending (line 51), + projected into UserNotificationDTO (lines 48-61, mapping id, push id, title, body, IsRead, ReadOn, + and SentOn = pn.CreatedOn); count the joined set (line 63); page it with Skip(skip).Take(take) and + materialize (lines 65-67); wrap total, page size and floored page number into + PaginationMetadata (line 71) and return a successful PagedCollectionResult<T> - (line 70). + (line 72).
        • Why it's built this way: both repositories are read through TableNoTracking, which is correct for a read (no change-tracking overhead since nothing is saved). Server-side projection into the DTO means only the needed columns cross the wire, and the count runs against the same joined expression so the total is consistent with the page. Note the count and the page are two round trips against one composed IQueryable, the standard cost of a paged read. The scope predicate is applied to the push-notification side before the join rather than after it, so an unscoped read composes exactly the query it always - did. The metadata reports the clamped take and the floored page number (line 69) so the response - describes the page actually served: the comment on lines 67-68 makes that explicit, and it matters + did. The un.Id tie-break after CreatedOn makes the sort a total order: without it, two notifications + created in the same instant have no defined relative order, so they could swap places between two + OFFSET pages and one would repeat while the other vanished (the comment on lines 46-47 states exactly + that). The metadata reports the clamped take and the floored page number (line 71) so the response + describes the page actually served: the comment on lines 69-70 makes that explicit, and it matters because the [Range] attributes at the API boundary (NotificationInboxController.cs:44-45) do not protect a direct in-process caller.
        • Where it's used: injected as a closed IQueryHandler into InboxController @@ -1159,46 +1162,57 @@

          GetUnreadNotificationCountHandler

          MarkAllNotificationsReadHandler

          -

          MMCA.Common.Application · MMCA.Common.Application.Notifications.UserNotifications.UseCases.MarkAllRead · MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/UserNotifications/UseCases/MarkAllRead/MarkAllNotificationsReadHandler.cs:12 · Level 8 · class

          +

          MMCA.Common.Application · MMCA.Common.Application.Notifications.UserNotifications.UseCases.MarkAllRead · MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/UserNotifications/UseCases/MarkAllRead/MarkAllNotificationsReadHandler.cs:18 · Level 8 · class

          • What it is: the command handler that clears a user's unread notifications (all of them, or just the - ones in a scope): it loads the tracked unread rows, calls the domain method on each, and saves once.
          • -
          • Depends on: IUnitOfWork, - IQueryableExecutor and the BCL TimeProvider - (primary constructor, lines 12-15). Implements + ones in a scope) with one set-based UPDATE: it builds a predicate over the user's unread rows and + hands it to the repository's ExecuteUpdateAsync, never loading a row.
          • +
          • Depends on: IUnitOfWork, resolved as + IRepository<TEntity, TIdentifierType> + via GetRepository, and the BCL TimeProvider (primary constructor, lines 18-20). Implements ICommandHandler<in TCommand, TResult><MarkAllNotificationsReadCommand, Result> - and drives UserNotification's MarkAsRead.
          • -
          • Concept introduced: write handlers read through Table, not TableNoTracking, and one - no-tracking source poisons the whole composed query. The two inbox reads in this part use - TableNoTracking; this one uses repository.Table (line 24) precisely because the entities must stay - attached to the change tracker for the subsequent SaveChangesAsync to see their mutations. The - scoped join goes further and uses the tracked Table on the PushNotification - side too (line 42), and the comment on lines 35-40 explains why that is load-bearing: in EF Core an - AsNoTracking source anywhere in a composed query switches the whole query to no-tracking, so the - UserNotification rows would come back untracked and the MarkAsRead mutations would never be - persisted, making a scoped read-all a silent no-op. Projecting select un (line 44) means only - UserNotification instances are materialized, so no PushNotification is tracked by the join. - [Rubric §4, DDD] assesses whether state changes go through the aggregate rather than around it: the - handler never assigns IsRead itself, it calls notification.MarkAsRead(readOnUtc) (line 54) and the - entity owns the transition, including the already-read early return - (MMCA.Common/Source/Core/MMCA.Common.Domain/Notifications/UserNotifications/UserNotification.cs:58-67). - [Rubric §14, Testability]: the read timestamp comes from an injected TimeProvider - (line 15, used at line 51) rather than DateTime.UtcNow, which is what makes the clock substitutable - in a unit test; the domain XML doc states that intent directly (UserNotification.cs:53-57).
          • -
          • Walkthrough: get the UserNotification repository (line 22); compose the tracked - unread query for this user (lines 24-25); apply the same conditional scope join the count handler uses, - over tracked tables (lines 30-45); materialize through the executor (lines 47-49); take one UTC instant - for the whole batch (line 51) so every row in one call reports the same read time; loop and call - MarkAsRead (lines 52-55); persist only if something changed, guarded by if (unread.Count > 0) - (lines 57-60); return Result.Success() (line 62).
          • -
          • Why it's built this way: the Count > 0 guard makes a repeated "mark all read" a genuine no-op at - the database, which matters because the UI can fire it on every inbox open. This is a load-then-save - loop rather than a set-based ExecuteUpdate, which keeps the transition inside the entity (and lets - MarkAsRead stay the single place the invariant lives) at the cost of materializing the unread rows. - Since MarkAllNotificationsReadCommand does not implement - ITransactional, the single SaveChangesAsync is the atomic - unit; see Group 05 for the decorator order.
          • + and writes the same two columns UserNotification's MarkAsRead owns. +
          • Concept introduced: a set-based write that deliberately bypasses the change tracker. The type's + <remarks> (lines 13-17) carry the argument: an inbox can hold an unbounded number of unread rows, and + MarkAsRead only sets IsRead and ReadOn and raises no domain event + (MMCA.Common/Source/Core/MMCA.Common.Domain/Notifications/UserNotifications/UserNotification.cs:58-67), + so issuing UPDATE ... SET IsRead = 1, ReadOn = @now WHERE ... loses nothing that loading and tracking + each row would have kept. The repository contract spells out the cost of the bypass: no domain events, + global query filters still apply to the predicate, and the update runs on the ambient transaction when + one is active + (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:460-481). + Audit fields are not lost either: because ExecuteUpdate skips the save pipeline's audit interceptor, + EFRepository stamps LastModifiedOn and LastModifiedBy itself unless the caller set them + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:140-154). + [Rubric §12, Performance & Scalability]: one statement regardless of inbox size, with no + materialization. [Rubric §4, DDD] assesses whether state changes go through the aggregate rather than + around it: here they go around it on purpose, and the invariant MarkAsRead protects (an already-read + row keeps its original ReadOn, UserNotification.cs:60-63) is preserved by the !un.IsRead term in + the predicate, so a read row never matches and its timestamp is never overwritten. + [Rubric §14, Testability]: the read timestamp comes from an injected TimeProvider (line 20, used at + line 48) rather than DateTime.UtcNow, which is what makes the clock substitutable in a unit test.
          • +
          • Walkthrough: get the UserNotification write repository via GetRepository + (line 27); form the base predicate un.UserId == command.UserId && !un.IsRead as an + Expression<Func<UserNotification, bool>> (lines 29-30); when and only when a non-blank ScopeKey + arrived (line 37), copy it into a local (line 39), take the PushNotification + Table (line 40) and replace the predicate with one that adds a correlated + pushNotifications.Any(pn => pn.Id == un.PushNotificationId && (pn.ScopeKey == null || pn.ScopeKey == scopeKey)) + test (lines 42-45), which EF translates to an EXISTS subquery; take one UTC instant for the whole + batch (line 48) so every row in one call reports the same read time; call + repository.ExecuteUpdateAsync with the predicate and setters for IsRead = true and + ReadOn = readOnUtc (lines 50-55); return Result.Success() (line 57).
          • +
          • Why it's built this way: the scope condition mirrors the unread count for the two reasons the + comment on lines 32-36 gives: a scoped client must not mark rows it cannot see as read, and a no-scope + command keeps the legacy predicate exactly as it was rather than inheriting PushNotification's + soft-delete global query filter. Expressing the scope as an EXISTS subquery rather than a join keeps + the update targeted at UserNotification alone while the push-notification filter applies to the + subquery exactly as it did to the former join (same comment). A repeated "mark all read" is a cheap + no-op at the database: the predicate matches zero rows and nothing changes, which matters because the + UI can fire it on every inbox open. The returned row count is discarded, since the command reports + success either way. Since MarkAllNotificationsReadCommand does not + implement ITransactional, the single UPDATE statement is + the atomic unit; see Group 05 for the decorator order.
          • Where it's used: injected into InboxController as ICommandHandler<MarkAllNotificationsReadCommand, Result> (NotificationInboxController.cs:33) and invoked by the PUT read-all action (NotificationInboxController.cs:123), which returns 204 on @@ -1233,13 +1247,15 @@

            GetNotificationHistoryHandler

            repository itself for the total via repository.CountAsync (line 33, note this one goes through the repository, not the queryable executor, because there is no composed predicate to count); page TableNoTracking ordered by - CreatedOn descending with Skip/Take and materialize the entities through the executor - (lines 35-40); run them through dtoMapper.MapToDTOs (line 42); build + CreatedOn descending then Id descending with Skip/Take and materialize the entities through the + executor (lines 35-41); run them through dtoMapper.MapToDTOs (line 43); build PaginationMetadata from the total, the clamped - take and the floored page number (line 46); return a successful - PagedCollectionResult<T> (line 48).
          • -
          • Why it's built this way: history has no per-user state, so there is nothing to join; a dedicated - mapper (versus an inline projection) is used because + take and the floored page number (line 47); return a successful + PagedCollectionResult<T> (line 49).
          • +
          • Why it's built this way: the ThenByDescending(n => n.Id) tie-break (line 38) makes the order + total, so two notifications sent in the same instant cannot repeat or skip across OFFSET pages, the + same guarantee the inbox handler gets from its un.Id tie-break. History has no per-user state, so + there is nothing to join; a dedicated mapper (versus an inline projection) is used because PushNotificationDTO is a richer contract reused across the push endpoints, and centralizing that mapping keeps the shape consistent (see ADR-001 on manual/Mapperly mapping).
          • @@ -1704,7 +1720,7 @@

            UserNotificationExportService

          DependencyInjection

          -

          MMCA.ADC.Notification.Application · MMCA.ADC.Notification.Application · MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.Application/DependencyInjection.cs:12 · Level 9 · class (static)

          +

          MMCA.ADC.Notification.Application · MMCA.ADC.Notification.Application · MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.Application/DependencyInjection.cs:13 · Level 9 · class (static)

          • What it is: the Notification module's application-layer DI composition. Its single extension @@ -1717,22 +1733,26 @@

            DependencyInjection

            UserNotificationExportService, and the framework's AddNotificationApplicationServices() registration.
          • Concept, the extension(IServiceCollection) registration block. DI wiring here uses the C# - preview extension-member syntax (DependencyInjection.cs:14), which lets a library add methods + preview extension-member syntax (DependencyInjection.cs:15), which lets a library add methods directly to IServiceCollection (taught in the primer). [Rubric §3, Clean Architecture] is visible in the split of responsibility: this method registers the module's app-specific choices (attendees as recipients, the export service) and then calls the - framework's AddNotificationApplicationServices() (line 31) for the reusable handlers, mapper, + framework's AddNotificationApplicationServices() (line 32) for the reusable handlers, mapper, validator, and entity query service, so shared and app-specific wiring stay separate and only the app-specific half needs review when policy changes.
          • -
          • Walkthrough: AddModuleNotificationApplication (lines 19-34) discards the unused - applicationSettings with _ = applicationSettings; (line 21, commented "Reserved for future use", +
          • Walkthrough: AddModuleNotificationApplication (lines 20-35) discards the unused + applicationSettings with _ = applicationSettings; (line 22, commented "Reserved for future use", which also keeps the analyzers quiet about an unused parameter on a fixed signature), registers AttendeeNotificationRecipientProvider as the scoped - INotificationRecipientProvider (line 24), registers + INotificationRecipientProvider (line 25), registers UserNotificationExportService as the scoped - IUserNotificationExportService for the privacy export (line 28), calls the framework's - AddNotificationApplicationServices() (line 31), and returns the collection for chaining (line 33).
          • + IUserNotificationExportService for the privacy export (line 29), calls the framework's + AddNotificationApplicationServices() (line 32), and returns the collection for chaining (line 34). + Both module registrations use TryAddScoped (lines 25 and 29), so they apply only when no + implementation of that interface is registered yet: a registration made before this method runs + (a host override or a test double) wins, and calling the method twice does not stack a second + descriptor.
          • Where it's used: called first thing by the API-layer DependencyInjection.AddNotificationModule (MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.API/DependencyInjection.cs:32), which is @@ -1740,7 +1760,7 @@

            DependencyInjection

          LiveChannelGrpcService

          -

          MMCA.ADC.Notification.Service · MMCA.ADC.Notification.Service.Grpc · MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:22 · Level 1 · class (sealed)

          +

          MMCA.ADC.Notification.Service · MMCA.ADC.Notification.Service.Grpc · MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:31 · Level 1 · class (sealed)

          • What it is: the gRPC server endpoint that other services call to fan an ephemeral "live" @@ -1748,29 +1768,51 @@

            LiveChannelGrpcService

            LiveChannelPushService.LiveChannelPushServiceBase and delegates each call to the framework's ILiveChannelPublisher.
          • Depends on: ILiveChannelPublisher (injected via the primary - constructor, MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:22; + constructor, MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/LiveChannelGrpcService.cs:31; in this host it resolves to SignalRLiveChannelPublisher, registered by AddPushNotifications because Notification is the host that maps the SignalR NotificationHub), the generated LiveChannelPushService base (compiled from the - .Contracts .proto, LiveChannelGrpcService.cs:23), and Grpc.Core.ServerCallContext.
          • + .Contracts .proto, LiveChannelGrpcService.cs:32), and Grpc.Core.ServerCallContext.
          • Concept introduced, the live-channel ingress (ADR-039). [Rubric §6, CQRS & Event-Driven] assesses whether state changes travel as events; [Rubric §7, Microservices Readiness] assesses whether cross-process collaboration rides typed transports. The conference-day live layer (LivePolls, SessionQuestions) lives in the Engagement service, but only the Notification host owns the SignalR IHubContext that can reach browsers. So Engagement calls THIS gRPC endpoint post-commit to hand off an ephemeral event, which the service passes to the local publisher that fans it out over - NotificationHub (doc comment LiveChannelGrpcService.cs:7-12). This is the server + NotificationHub (doc comment LiveChannelGrpcService.cs:10-15). This is the server half; its client half is LiveChannelPublisherGrpcAdapter.
          • -
          • Walkthrough: the single PushToChannel override (LiveChannelGrpcService.cs:26) null-guards - request and context (LiveChannelGrpcService.cs:30-31), then awaits +
          • Walkthrough: the single PushToChannel override (LiveChannelGrpcService.cs:47) null-guards + request and context (LiveChannelGrpcService.cs:51-52), validates the three fields + (LiveChannelGrpcService.cs:54-56), then awaits publisher.PublishAsync(request.ChannelKey, request.EventName, request.PayloadJson, context.CancellationToken) - (LiveChannelGrpcService.cs:33-35) and returns an empty PushToChannelResponse - (LiveChannelGrpcService.cs:37). The channel key, event name, and payload are opaque strings: the - transport relays the event rather than modeling it.
          • + (LiveChannelGrpcService.cs:58-60) and returns an empty PushToChannelResponse + (LiveChannelGrpcService.cs:62). The transport still relays the event rather than modeling it, but it + no longer accepts arbitrary strings: the class doc (LiveChannelGrpcService.cs:24-29) calls this the + "bounded payloads" compensating control for an unauthenticated ingress.
              +
            • Three caps (LiveChannelGrpcService.cs:34-36): MaxChannelKeyLength = 200, + MaxEventNameLength = 100, MaxPayloadBytes = 64 KiB (64 * 1024).
            • +
            • AllowedChars (LiveChannelGrpcService.cs:43-44), a SearchValues<char> over + abcdefghijklmnopqrstuvwxyz0123456789:.-, is the one character set shared by channel keys and event + names; its doc comment (LiveChannelGrpcService.cs:38-42) notes it covers every producer format, + NotificationScopeKey keys such as event:1 and the live-layer event names + such as poll.results-changed.
            • +
            • ValidateName (LiveChannelGrpcService.cs:65-81) rejects a blank value, one over its cap, and one + containing any character outside AllowedChars (ContainsAnyExcept, LiveChannelGrpcService.cs:77). + ValidatePayload (LiveChannelGrpcService.cs:83-89) rejects a payload whose UTF-8 byte count + exceeds the cap; a null payload passes.
            • +
            • Every refusal is an RpcException with StatusCode.InvalidArgument built by InvalidArgument + (LiveChannelGrpcService.cs:91-92), thrown before the publisher is reached, with messages formatted + under CultureInfo.InvariantCulture. [Rubric §11, Security]: input bounding is what limits the + blast radius of a surface that cannot authenticate its caller.
            • +
            +
          • Why it's built this way (security posture): there is deliberately no [Authorize], and the - endpoint is mapped without RequireAuthorization - (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:275). [Rubric §11, Security]: - the doc comment (LiveChannelGrpcService.cs:13-20) gives two reasons. First, this surface is reachable + endpoint is mapped with an explicit .AllowAnonymous() + (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:285). The comment above it + (Program.cs:281-284) says why the opt-out is stated rather than implied: the framework's fallback + authorization policy gates any endpoint that declares nothing (SEC-Common-16), which would have silenced + the whole live layer. [Rubric §11, Security]: + the doc comment (LiveChannelGrpcService.cs:16-23) gives two reasons. First, this surface is reachable only on the internal service network (a dedicated internal port in Azure Container Apps, never routed by the Gateway), the same posture as the other internal gRPC services (it names BookmarkCountsGrpcService). Second, authorization is not addable here: the publishing caller is @@ -1778,10 +1820,10 @@

            LiveChannelGrpcService

            background drain, which runs with no HttpContext and therefore forwards no bearer token. Transport-wise it rides the ADR-012 mixed-endpoint profile: Notification keeps its default endpoint Http1AndHttp2 for SignalR WebSockets - (Program.cs:75) and serves this h2c gRPC ingress on a dedicated Http2-only endpoint named grpc + (Program.cs:73) and serves this h2c gRPC ingress on a dedicated Http2-only endpoint named grpc (port 8081 in the container, 5996 locally), declared in the Kestrel:Endpoints config section rather - than in code; the full reasoning is spelled out at Program.cs:59-74.
          • -
          • Where it's used: mapped by the Notification service's Program.cs (Program.cs:275); invoked by + than in code; the full reasoning is spelled out at Program.cs:58-72.
          • +
          • Where it's used: mapped by the Notification service's Program.cs (Program.cs:285); invoked by LiveChannelPublisherGrpcAdapter running inside Engagement.

          @@ -1840,7 +1882,7 @@

          LiveChannelPublisherGrpcAdapter

          NullLiveChannelPublisher default (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Contracts/DependencyInjection.cs:48). The one caller today is the Engagement service's composition root - (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:283).
        • + (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:285).

        NullLiveChannelPublisher

        @@ -1873,7 +1915,7 @@

        NullLiveChannelPublisher

        best-effort by design).
      • Where it's used: registered as the framework default with services.TryAddTransient<ILiveChannelPublisher, NullLiveChannelPublisher>() - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:316) so the port is always + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:326) so the port is always resolvable; AddPushNotifications adds the SignalR implementation over it (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:66), and in ADC Engagement's composition root services.Replace(...) overwrites it with the LiveChannelPublisherGrpcAdapter @@ -1905,7 +1947,7 @@

        NullPushNotificationSender

        AddPushNotifications(). The send handler always calls the port; whether anything reaches a browser is a composition-root decision.
      • Where it's used: registered as the default IPushNotificationSender by AddInfrastructure - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:315); superseded by the + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:325); superseded by the SignalR sender in any host that calls AddPushNotifications (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:65).
      @@ -1936,11 +1978,14 @@

      SendPushNotificationCommand

      plus one body member.
      • Request (SendPushNotificationCommand.cs:22) is the validated DTO; SentByUserId (SendPushNotificationCommand.cs:23) is the audit/authorization context.
      • -
      • DedupKey (SendPushNotificationCommand.cs:32), an init-only string?, is the optional +
      • DedupKey (SendPushNotificationCommand.cs:35), an init-only string?, is the optional deduplication key, typically the caller's Idempotency-Key header. Its doc comment - (SendPushNotificationCommand.cs:25-31) states the contract: when present, a send whose key has + (SendPushNotificationCommand.cs:25-34) states the contract: when present, a send whose key has already been seen returns the existing notification instead of creating a second one and sending - again; when null (the default every existing caller gets) the send behaves exactly as before. + again; when null (the default every existing caller gets) the send behaves exactly as before. The + key is scoped to the sender (SendPushNotificationCommand.cs:31-33): the handler stores and + looks up a SHA-256 of {SentByUserId}:{key}, never the raw client key, so two senders reusing one + client key never collide, and the stored value always fits the column. [Rubric §29, Resilience & Business Continuity] assesses whether a retry is safe: this property is what makes a retried broadcast at-most-once at the delivery level (see the matching logic in SendPushNotificationHandler). It complements, and is distinct from, @@ -1988,7 +2033,7 @@

        SmtpEmailSender

        [Rubric §17, DevOps & Deployment]: host, port, credentials, and the default from/to addresses come from configuration bound at startup by AddInfrastructure (services.AddOptions<SmtpSettings>().Bind(configuration.GetSection(SmtpSettings.SectionName)), - MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:92-93), never hard-coded, + MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:98-99), never hard-coded, so the same code targets a real relay in production and a local SMTP container in development.
        • [Rubric §11, Security] (SEC-Common-54): TLS is no longer read verbatim from SmtpSettings.EnableSsl. The container-preferred constructor resolves it through @@ -2033,7 +2078,7 @@

          SmtpEmailSender

          would be a breaking public-API change. Unlike push, email has no null-object default: SmtpEmailSender itself is what AddInfrastructure registers as IEmailSender (services.TryAddTransient<IEmailSender, SmtpEmailSender>(), - MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:314), so a host that never + MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:324), so a host that never configures SMTP settings still resolves a real sender that will fail at send time rather than silently no-op.
        • Where it's used: injected as IEmailSender by callers (the port, not this class); the transient @@ -2103,40 +2148,40 @@

          NotificationHub

          drifting on a magic string.
        • ChannelKeyMatchTimeout (1 second) and the ChannelKeyRegexCache (StringComparer.Ordinal) back the validation helper.
        • -
        • ConnectionsPerUser (NotificationHub.cs:264): a static ConcurrentDictionary<string, int>, +
        • ConnectionsPerUser (NotificationHub.cs:51): a static ConcurrentDictionary<string, int>, StringComparer.Ordinal, tracking live connection count per user identifier on this replica.
        • -
        • OnConnectedAsync (NotificationHub.cs:267-285): when MaxConnectionsPerUser is positive and +
        • OnConnectedAsync (NotificationHub.cs:54-72): when MaxConnectionsPerUser is positive and Context.UserIdentifier is set, atomically increments the count with AddOrUpdate - (:274); if the new count exceeds the cap, it gives the slot straight back via Decrement - (:279, so a refused connection cannot keep counting against the user and lock it out once the + (:61); if the new count exceeds the cap, it gives the slot straight back via Decrement + (:66, so a refused connection cannot keep counting against the user and lock it out once the aborted ones drain) and throws HubException("Too many concurrent connections for this account.") - (:280); otherwise it calls base.OnConnectedAsync() (:284).
        • -
        • OnDisconnectedAsync (NotificationHub.cs:288-297): decrements the caller's count, then calls + (:67); otherwise it calls base.OnConnectedAsync() (:71).
        • +
        • OnDisconnectedAsync (NotificationHub.cs:75-84): decrements the caller's count, then calls base.OnDisconnectedAsync(exception).
        • -
        • Decrement(string userId) (NotificationHub.cs:303-322): a lock-free loop over +
        • Decrement(string userId) (NotificationHub.cs:90-109): a lock-free loop over TryGetValue/TryRemove/TryUpdate that removes the entry entirely at zero, so the dictionary tracks only users who are actually connected rather than everyone who ever was.
        • -
        • JoinChannelAsync(string channelKey) (NotificationHub.cs:332): attributed +
        • JoinChannelAsync(string channelKey) (NotificationHub.cs:119): attributed [HubMethodName(JoinChannelMethod)], it validates the key via EnsureValidChannelKey - (:334), then awaits EnsureAuthorizedForChannelAsync(channelKey) (:335) before calling - Groups.AddToGroupAsync(Context.ConnectionId, channelKey, Context.ConnectionAborted) (:340-341).
        • -
        • LeaveChannelAsync(string channelKey) (NotificationHub.cs:349): + (:121), then awaits EnsureAuthorizedForChannelAsync(channelKey) (:122) before calling + Groups.AddToGroupAsync(Context.ConnectionId, channelKey, Context.ConnectionAborted) (:127-128).
        • +
        • LeaveChannelAsync(string channelKey) (NotificationHub.cs:136): [HubMethodName(LeaveChannelMethod)], validates then Groups.RemoveFromGroupAsync(...) with the same - connection token (:354-355). Leaving is not re-checked against the authorizer.
        • -
        • EnsureAuthorizedForChannelAsync (NotificationHub.cs:362-377): a no-op when joinAuthorizer is + connection token (:141-142). Leaving is not re-checked against the authorizer.
        • +
        • EnsureAuthorizedForChannelAsync (NotificationHub.cs:149-164): a no-op when joinAuthorizer is null; otherwise awaits joinAuthorizer.CanJoinAsync(Context.User, channelKey, Context.ConnectionAborted) and throws HubException("Not authorized for this channel.") on a false result, so a caller not entitled to the channel is refused before the group membership is created and never receives a single published payload.
        • Both JoinChannelAsync/LeaveChannelAsync take no CancellationToken parameter, and the comment - at NotificationHub.cs:337-339 explains why: a hub method signature is the client-visible RPC + at NotificationHub.cs:124-126 explains why: a hub method signature is the client-visible RPC contract bound by SignalR's dispatcher, so the cancellation token comes from the connection (Context.ConnectionAborted) instead, and the repo's CancellationTokenConventionTests carry an explicit exemption for it. [Rubric §15, Best Practices & Code Quality]: the convention is enforced by a test, and the deviation is documented at the deviation site.
        • -
        • EnsureValidChannelKey (NotificationHub.cs:379): GetOrAdds the cached Regex for +
        • EnsureValidChannelKey (NotificationHub.cs:166): GetOrAdds the cached Regex for settings.Value.ChannelKeyPattern; an empty or non-matching key throws - HubException("Invalid channel key.") (:387), which SignalR surfaces to the caller rather than + HubException("Invalid channel key.") (:174), which SignalR surfaces to the caller rather than tearing down the connection.
      • @@ -2156,7 +2201,7 @@

        NotificationHub

        path is /hubs/notifications (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Notifications/Push/PushNotificationSettings.cs:17), and in ADC the Notification service calls it at - MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:289. It is driven by + MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:276. It is driven by SignalRPushNotificationSender (per-user notification delivery) and SignalRLiveChannelPublisher (ephemeral channel events), both via IHubContext<NotificationHub>. AddPushNotifications registers the IUserIdProvider @@ -2335,7 +2380,7 @@

        SendPushNotificationRequestValidat

        SendPushNotificationHandler

        -

        MMCA.Common.Application · MMCA.Common.Application.Notifications.PushNotifications.UseCases.Send · MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:27 · Level 9 · class (sealed partial)

        +

        MMCA.Common.Application · MMCA.Common.Application.Notifications.PushNotifications.UseCases.Send · MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:32 · Level 9 · class (sealed partial)

        • What it is: the command handler for the push-notification broadcast. It short-circuits duplicate @@ -2349,7 +2394,7 @@

          SendPushNotificationHandler

          by ADR-044), PushNotificationDTOMapper (the success-payload mapper), and ILogger<> (all injected via the primary constructor, - MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:27-33); + MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:32-38); the persisted aggregates are PushNotification and UserNotification. Implements ICommandHandler<in TCommand, TResult> @@ -2359,74 +2404,80 @@

          SendPushNotificationHandler

          separately performs two best-effort real-time pushes (SignalR, then native OS push). These are different reliability tiers on purpose: the aggregate and inbox rows are the durable record a recipient can retrieve later, while the SignalR and native pushes are the immediate deliveries that - an offline user may miss. The class doc (SendPushNotificationHandler.cs:17-25) states the atomicity + an offline user may miss. The class doc (SendPushNotificationHandler.cs:20-30) states the atomicity contract that ties the durable half together: all three saves are one unit because SendPushNotificationCommand is ITransactional.
        • Walkthrough (teaching order)
            -
          1. deduplication gate (SendPushNotificationHandler.cs:40-51): the command's +
          2. deduplication gate (SendPushNotificationHandler.cs:45-60): the command's DedupKey is normalized so whitespace counts as absent - (SendPushNotificationHandler.cs:42, "a blank header cannot claim the single empty key"); when a - key is present, FindByDedupKeyAsync looks for an already-persisted notification and, on a hit, + (SendPushNotificationHandler.cs:49-51, "a blank header cannot claim the single empty key"), and a + present key is replaced by its sender-scoped hash (see SenderScopedDedupKey below), so another + caller reusing the same client key can neither suppress this send nor be handed this sender's + notification (comment SendPushNotificationHandler.cs:46-48); when a key is present, FindByDedupKeyAsync looks for an already-persisted notification and, on a hit, logs and returns the existing one mapped to a DTO without sending again - (SendPushNotificationHandler.cs:45-50).
          3. -
          4. resolve recipients (SendPushNotificationHandler.cs:54-55) via the app-specific + (SendPushNotificationHandler.cs:54-59).
          5. +
          6. resolve recipients (SendPushNotificationHandler.cs:63-64) via the app-specific INotificationRecipientProvider (in ADC, AttendeeNotificationRecipientProvider). An empty set short-circuits to a Result.Failure with an Error - Validation code PushNotification.NoRecipients (SendPushNotificationHandler.cs:57-63), before + Validation code PushNotification.NoRecipients (SendPushNotificationHandler.cs:66-72), before any rows are written.
          7. -
          8. create the audit aggregate (SendPushNotificationHandler.cs:66-72) via +
          9. create the audit aggregate (SendPushNotificationHandler.cs:75-81) via PushNotification.Create(title, body, sentByUserId, recipientIds.Count, dedupKey, scopeKey); - propagate errors on failure (SendPushNotificationHandler.cs:73-76), then add to the repository - (SendPushNotificationHandler.cs:79-80). This is where the aggregate's + propagate errors on failure (SendPushNotificationHandler.cs:82-85), then add to the repository + (SendPushNotificationHandler.cs:88-89). This is where the aggregate's PushNotificationCreated domain event is captured to the outbox (ADR-003).
          10. -
          11. first save, with a race requery (SendPushNotificationHandler.cs:82-113): the save is wrapped +
          12. first save, with a race requery (SendPushNotificationHandler.cs:91-122): the save is wrapped in a try/catch (Exception) under a justified #pragma warning disable CA1031 - (SendPushNotificationHandler.cs:86-88). The long comment - (SendPushNotificationHandler.cs:90-101) is the teaching point: the dedup lookup in step 1 is a + (SendPushNotificationHandler.cs:95-97). The long comment + (SendPushNotificationHandler.cs:99-110) is the teaching point: the dedup lookup in step 1 is a check-then-act, so two concurrent retries of the same send both pass it and the loser only fails here, on the insert, against the filtered unique index on DedupKey. The catch requeries by key - with CancellationToken.None (SendPushNotificationHandler.cs:104, so a cancelled save can still + with CancellationToken.None (SendPushNotificationHandler.cs:113, so a cancelled save can still be classified) and, if a winner now exists, returns it - (SendPushNotificationHandler.cs:105-109); anything else rethrows untouched - (SendPushNotificationHandler.cs:112) so a genuine persistence fault still reaches the exception + (SendPushNotificationHandler.cs:114-118); anything else rethrows untouched + (SendPushNotificationHandler.cs:121) so a genuine persistence fault still reaches the exception middleware. The broad catch is deliberate: Application has no EF Core dependency under the layer rule, so DbUpdateException is not a type this file can name, and the requery is what narrows it. Same shape as EfInboxStore's MessageId unique-index handling.
          13. -
          14. durable inbox (SendPushNotificationHandler.cs:115-123): one +
          15. durable inbox (SendPushNotificationHandler.cs:124-132): one UserNotification.Create(recipientId, notification.Id) row per recipient, added and saved. This is what lets a user retrieve a notification they missed while offline.
          16. -
          17. best-effort SignalR delivery (SendPushNotificationHandler.cs:125-143): +
          18. best-effort SignalR delivery (SendPushNotificationHandler.cs:134-152): pushNotificationSender.SendToUsersAsync(...) inside a try/catch with its own justified CA1031 - suppression (SendPushNotificationHandler.cs:137-139). A delivery failure is non-fatal: success - calls notification.MarkAsSent() plus an info log (SendPushNotificationHandler.cs:134-135), + suppression (SendPushNotificationHandler.cs:146-148). A delivery failure is non-fatal: success + calls notification.MarkAsSent() plus an info log (SendPushNotificationHandler.cs:143-144), failure calls notification.MarkAsFailed() plus an error log - (SendPushNotificationHandler.cs:141-142); the failure becomes recorded status, not a thrown + (SendPushNotificationHandler.cs:150-151); the failure becomes recorded status, not a thrown exception.
          19. -
          20. best-effort native push (SendPushNotificationHandler.cs:145-162, +
          21. best-effort native push (SendPushNotificationHandler.cs:154-171, ADR-044): nativePushSender.SendToUsersAsync(...) in a third try/catch with its own suppression - (SendPushNotificationHandler.cs:157-159). This is the OS-level channel that can reach devices - whose app is backgrounded or killed (comment SendPushNotificationHandler.cs:145-148). It is + (SendPushNotificationHandler.cs:166-168). This is the OS-level channel that can reach devices + whose app is backgrounded or killed (comment SendPushNotificationHandler.cs:154-157). It is purely additive: the SignalR leg above already decided the audit status, so a native-push - failure only logs a warning (LogNativePushFailed, SendPushNotificationHandler.cs:161) and never + failure only logs a warning (LogNativePushFailed, SendPushNotificationHandler.cs:170) and never touches Status. The default NullNativePushSender keeps this a no-op until a notification hub is configured.
          22. -
          23. persist final status (SendPushNotificationHandler.cs:164) and return the mapped DTO - (SendPushNotificationHandler.cs:166).
          24. +
          25. persist final status (SendPushNotificationHandler.cs:173) and return the mapped DTO + (SendPushNotificationHandler.cs:175).
            -
          • FindByDedupKeyAsync (SendPushNotificationHandler.cs:174-183) resolves the read repository from - the unit of work (unitOfWork.GetReadRepository<...>(), SendPushNotificationHandler.cs:176), never +
          • SenderScopedDedupKey (SendPushNotificationHandler.cs:185-187) derives the stored key as + Convert.ToHexString(SHA256.HashData(...)) over the invariant-culture string + {sentByUserId}:{clientKey}, so the persisted value is always 64 hex characters (within the + column) whatever the client sent (doc comment SendPushNotificationHandler.cs:178-184). [Rubric §11, Security]: the client key alone no longer identifies a notification across senders.
          • +
          • FindByDedupKeyAsync (SendPushNotificationHandler.cs:194-203) resolves the read repository from + the unit of work (unitOfWork.GetReadRepository<...>(), SendPushNotificationHandler.cs:196), never an injected IRepository<TEntity, TIdentifierType>, so the lookup runs against the same data source as the write (its doc comment says exactly that, - SendPushNotificationHandler.cs:169-173).
          • + SendPushNotificationHandler.cs:189-193).
          • Five source-generated [LoggerMessage] methods close the file - (SendPushNotificationHandler.cs:185-198): sent (Information), delivery-failed (Error), + (SendPushNotificationHandler.cs:205-218): sent (Information), delivery-failed (Error), native-failed (Warning), dedup hit (Information), and dedup race requery (Information). [Rubric §13, Observability].
          @@ -2445,11 +2496,13 @@

          SendPushNotificationHandler

          ADC by the Notification DI facade); the real-time legs land on connected clients through NotificationHub and, for native push, through the configured OS notification hub.
        • Caveats / not-in-source: the three SaveChangesAsync calls - (SendPushNotificationHandler.cs:84, :122, :163) are separate saves, not separate transactions: + (SendPushNotificationHandler.cs:93, :132, :173) are separate saves, not separate transactions: atomicity comes from the ambient transaction the Transactional decorator opens because the command implements ITransactional, so the decorator, not this file, is where the commit happens. A - consequence the class doc accepts explicitly (SendPushNotificationHandler.cs:17-25) is that both - sender calls run inside that transaction. There is still no automatic redelivery of a failed push: the + consequence the class doc accepts explicitly (SendPushNotificationHandler.cs:27-29) is that both + sender calls run inside that unit, so a transient fault on the final status save re-runs them under + the execution strategy: delivery is at-least-once for the live channels (SignalR and native) and + exactly-once for the inbox rows. There is still no automatic redelivery of a failed push: the outcome is recorded, not re-attempted.

        @@ -2467,7 +2520,9 @@

        UserNotificationExportGrpcService

        MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/UserNotificationExportGrpcService.cs:27), the generated UserNotificationExportService.UserNotificationExportServiceBase (compiled from the .Contracts .proto, UserNotificationExportGrpcService.cs:28), and Grpc.Core.ServerCallContext. - Uses System.Globalization.CultureInfo for invariant-culture date formatting, and projects into + Formats timestamps through the framework's + GrpcWireFormat (MMCA.Common.Grpc, imported at + UserNotificationExportGrpcService.cs:4), and projects into UserNotificationExportItemDTO shapes on the far side.
      • Concept introduced, cross-service data-subject export over internal gRPC. [Rubric §30, Compliance, Privacy & Data Governance] assesses whether the system can satisfy a subject-access request across service boundaries: each service owns its own database @@ -2475,8 +2530,10 @@

        UserNotificationExportGrpcService

        ADC_Notification, not in Identity; this RPC lets the Identity export aggregator gather that slice without a cross-database query. [Rubric §7, Microservices Readiness] and [Rubric §9, API & Contract Design]: the export contract is a versioned .proto shared through the .Contracts package, the same extraction pattern as the live-channel ingress. [Rubric §27, i18n]: timestamps are - serialized with the round-trip "O" format under CultureInfo.InvariantCulture - (UserNotificationExportGrpcService.cs:47 and :51) so the export is locale-stable.
      • + serialized with the round-trip "O" format under CultureInfo.InvariantCulture by + GrpcWireFormat.FormatUtc + (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/GrpcWireFormat.cs:42-43, called at + UserNotificationExportGrpcService.cs:47 and :49) so the export is locale-stable.
      • Walkthrough: the single GetUserNotificationExport override (UserNotificationExportGrpcService.cs:31) null-guards request and context (UserNotificationExportGrpcService.cs:35-36), then awaits @@ -2484,18 +2541,22 @@

        UserNotificationExportGrpcService

        UserNotificationExportGrpcService.cs:38-40) to get the in-process items. It builds a GetUserNotificationExportResponse (UserNotificationExportGrpcService.cs:42) and AddRanges a projection of each item into a UserNotificationExportItem - (UserNotificationExportGrpcService.cs:43-56): NotificationId, Title, SentOn, IsRead, ReadOn - (string.Empty when the notification is unread, UserNotificationExportGrpcService.cs:49-51), and - ScopeKey (also string.Empty when null, UserNotificationExportGrpcService.cs:55, because proto3 - has no null string, per the comment at :53-54). Both timestamps pass through - DateTime.SpecifyKind(..., DateTimeKind.Utc) before ToString("O", ...): the doc comment - (UserNotificationExportGrpcService.cs:20-25) explains why, SQL Server hands back - DateTimeKind.Unspecified values and the "O" format omits the Z marker for that kind, so the - stamp only restores the marker the wire contract promises (the stored values are already UTC).
      • + (UserNotificationExportGrpcService.cs:43-54): NotificationId, Title, SentOn via + GrpcWireFormat.FormatUtc (UserNotificationExportGrpcService.cs:47), IsRead, ReadOn via + GrpcWireFormat.FormatUtcOrEmpty (string.Empty when the notification is unread, + UserNotificationExportGrpcService.cs:49; GrpcWireFormat.cs:51-52), and ScopeKey (also + string.Empty when null, UserNotificationExportGrpcService.cs:53, because proto3 has no null + string, per the comment at :51-52). FormatUtc stamps DateTimeKind.Utc with + DateTime.SpecifyKind before ToString("O", ...): the class doc + (UserNotificationExportGrpcService.cs:20-25, restated once for every contract in + GrpcWireFormat.cs:11-17) explains why, SQL Server hands back DateTimeKind.Unspecified values and + the "O" format omits the Z marker for that kind, so the stamp only restores the marker the wire + contract promises (the stored values are already UTC). Writing the format once in the framework means + this server and its client adapter cannot drift apart.
      • Why it's built this way (security posture): unlike LiveChannelGrpcService, this endpoint requires authorization: it is mapped with .RequireAuthorization() - (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:306) and the class doc says why + (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:293) and the class doc says why (UserNotificationExportGrpcService.cs:14-19). [Rubric §11, Security], [Rubric §30, Compliance, Privacy & Data Governance]: internal-only ingress is not sufficient here because the response carries personal data keyed by a raw UserId, so the calling service forwards the end user's JWT via JwtForwardingClientInterceptor and @@ -2503,7 +2564,7 @@

        UserNotificationExportGrpcService

        ADR-012 mixed-endpoint profile, UserNotificationExportGrpcService.cs:11-13).
      • -
      • Where it's used: mapped by the Notification service's Program.cs (Program.cs:306); the wire is +
      • Where it's used: mapped by the Notification service's Program.cs (Program.cs:293); the wire is dialed by its client half UserNotificationExportServiceGrpcAdapter, which runs inside the Identity service's export aggregator and stitches this Notification slice into the full @@ -2529,7 +2590,8 @@

        UserNotificationExportServiceG constructor, MMCA.ADC/Source/Services/MMCA.ADC.Notification.Contracts/UserNotificationExportServiceGrpcAdapter.cs:17-18); UserNotificationExportItemDTO (the returned shape); the - UserIdentifierType alias; System.Globalization for invariant parsing. Its server counterpart is + UserIdentifierType alias; GrpcWireFormat for parsing + the wire timestamps. Its server counterpart is UserNotificationExportGrpcService.

      • Concept: the mirror image of LiveChannelPublisherGrpcAdapter's error policy, and the contrast is @@ -2550,19 +2612,25 @@

        UserNotificationExportServiceG with a GetUserNotificationExportRequest { UserId = userId }, the deadline, and the caller's token (:30-36). No try/catch: failures surface.

      • The projection (:38-49) maps each wire item back into a - UserNotificationExportItemDTO with a collection expression. Two - fields undo the proto3 encoding: ReadOn becomes null when the string is empty (:44), and - ScopeKey likewise (:48), with the comment at :46-47 recording that a peer replica predating the - field and a genuinely unscoped notification both arrive as the empty string and mean the same thing.
      • -
      • ParseRoundtripUtc(value) (:59-63) is the counterpart of the server's "O" formatting: - DateTime.Parse under CultureInfo.InvariantCulture with - DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal. The comment block (:52-58) is - worth reading in full: those two flags defend against a peer replica that still emits the - marker-less form during a rolling deploy (without them a suffix-less value parses as - Kind=Unspecified, and AssumeUniversal alone yields Kind=Local), while a Z-suffixed value - takes the same path and keeps its instant. DateTimeStyles.RoundtripKind is deliberately absent - because DateTime.Parse rejects it alongside either flag with an ArgumentException, and its job - (preserving a non-UTC kind) is the opposite of what this contract wants. [Rubric §15, Best Practices & Code Quality]: a subtle BCL interaction is documented at the point of use.
      • + UserNotificationExportItemDTO with a collection expression. + SentOn is read by GrpcWireFormat.ParseUtc (:42). Two fields undo the proto3 encoding: ReadOn + becomes null when the string is empty through GrpcWireFormat.ParseUtcOrNull (:44), and + ScopeKey likewise by an inline length check (:48), with the comment at :46-47 recording that a + peer replica predating the field and a genuinely unscoped notification both arrive as the empty + string and mean the same thing. +
      • The parsing itself lives in the framework, the counterpart of the server's "O" formatting: + ParseUtc is DateTime.Parse under CultureInfo.InvariantCulture with + DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal + (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/GrpcWireFormat.cs:37, :61-62). The comment + there (GrpcWireFormat.cs:32-36) is worth reading in full: those two flags defend against a peer + replica that still emits the marker-less form during a rolling deploy (without them a suffix-less + value parses as Kind=Unspecified, and AssumeUniversal alone yields Kind=Local), while a + Z-suffixed value takes the same path and keeps its instant. DateTimeStyles.RoundtripKind is + deliberately absent because DateTime.Parse rejects it alongside either flag with an + ArgumentException, and its job (preserving a non-UTC kind) is the opposite of what this contract + wants. A malformed value throws FormatException (GrpcWireFormat.cs:24-25), which this adapter + lets propagate like any other failure. [Rubric §15, Best Practices & Code Quality]: a subtle BCL + interaction is documented once, beside the shared helper both ends call.
    867. Why it's built this way: keeping the class internal (:17) means nothing outside the .Contracts @@ -2575,7 +2643,7 @@

      UserNotificationExportServiceG services.Replace(...) (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Contracts/DependencyInjection.cs:84); the one caller today is the Identity service's composition root - (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:317), whose + (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:316), whose ExportUserDataHandler consumes the interface.


    868. @@ -2643,9 +2711,9 @@

      DependencyInjection

      best-effort per section, so if this peer stays unreachable after the resilience pipeline the Identity handler marks the Notifications section unavailable instead of failing the whole export.
    869. Where it's used: AddNotificationLiveChannelClient is called by the Engagement service's - application pipeline (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:283); + application pipeline (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:285); AddNotificationUserExportClient by the Identity service's - (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:317). The matching AppHost references + (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:316). The matching AppHost references that inject the services__notification__grpc__0 entry are engagementService.WithReference(notificationService) (MMCA.ADC/Source/Hosting/MMCA.ADC.AppHost/Program.cs:282) and @@ -3209,7 +3277,7 @@

      NotificationsController

      response, but only while the cached entry survives. So the action ALSO reads the raw header itself and carries it into the domain as the command's DedupKey (lines 60-69), where a key that has already been seen returns the existing notification instead of sending a second time - (MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationCommand.cs:25-32). + (MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationCommand.cs:25-35). The filter protects the response; the DedupKey protects delivery when the cache is cold, evicted, or degraded. See ADR-024. [Rubric §29, Resilience & Business Continuity] assesses whether a retried or replayed request can diff --git a/docs/onboarding/group-11-navigation-populators.html b/docs/onboarding/group-11-navigation-populators.html index a0189c05..b941d7b8 100644 --- a/docs/onboarding/group-11-navigation-populators.html +++ b/docs/onboarding/group-11-navigation-populators.html @@ -327,13 +327,13 @@

      11. Navig subsystem is why the database-per-service split is feasible without rewriting query code ([Rubric §7, Microservices Readiness]): when a relationship's ends move to different sources, the EF model's CrossDataSourceDegradeConvention - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:33) + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:34) drops the relationship and ignores the CLR navigation members, routing runtime navigation "through the existing INavigationPopulator batch-loading machinery instead" - (CrossDataSourceDegradeConvention.cs:15-17), the metadata provider starts reporting that navigation + (CrossDataSourceDegradeConvention.cs:16-18), the metadata provider starts reporting that navigation as unsupported, and the populator path picks it up automatically. The application code that issued the query never changes, and when every entity resolves to the same physical source the convention is a - structural no-op (CrossDataSourceDegradeConvention.cs:25-29). Second, the design is a clean + structural no-op (CrossDataSourceDegradeConvention.cs:26-30). Second, the design is a clean illustration of keeping policy out of the domain: the what (a [Navigation] marker) lives in Domain, the whether (supported versus unsupported) is computed in Application from an Infrastructure capability check, and the how (batch SQL) is a static helper. Three responsibilities, three layers, diff --git a/docs/onboarding/group-12-api-hosting-mapping.html b/docs/onboarding/group-12-api-hosting-mapping.html index 199f4eee..22688fa6 100644 --- a/docs/onboarding/group-12-api-hosting-mapping.html +++ b/docs/onboarding/group-12-api-hosting-mapping.html @@ -204,7 +204,7 @@

      12. API Host (:154-158), AddServerAuthSessionCookie (DependencyInjection.cs:174) registers the Blazor Server host's SSR cookie reader (CookieTokenReader, :180) plus the singleton CookieSessionRefresher (:186, singleton so its - in-flight map is shared across requests), and AddModuleHealthChecks (DependencyInjection.cs:202) + in-flight map is shared across requests), and AddModuleHealthChecks (DependencyInjection.cs:212) turns ModuleLoader discovery results into module-{Name} health checks, tagged module so /health?tag=module filters them (Healthy for enabled modules at :206-212, Degraded for the disabled ones from :214). @@ -216,7 +216,7 @@

      12. API Host api-version header (AddCommonApiVersioning, line 37, reader at line 46, v1.0 assumed when the header is absent at line 44, ADR-046), rate limiting - (AddCommonRateLimiting, three overloads at WebApplicationBuilderExtensions.RateLimiting.cs:326, + (AddCommonRateLimiting, three overloads at WebApplicationBuilderExtensions.RateLimiting.cs:328, :344, and :362), Brotli and Gzip compression at CompressionLevel.Fastest (AddCommonResponseCompression, line 62, both providers pinned to Fastest at lines 71 and 76 because these are dynamic per-request payloads on fractional @@ -257,7 +257,7 @@

      12. API Host (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationExtensions.cs:48) and its Action<MiddlewarePipelineBuilder> overload (WebApplicationExtensions.cs:60) both route through one private ApplyPipeline that seeds the defaults, lets the host adjust them, validates the result, and - only then applies each step (WebApplicationExtensions.cs:153-160). The steps themselves are + only then applies each step (WebApplicationExtensions.cs:168-175). The steps themselves are MiddlewarePipelineStep records (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/Pipeline/MiddlewarePipelineStep.cs:21), each a stable Name plus an Action<WebApplication> Configure delegate, both null-validated on construction @@ -334,7 +334,7 @@

      12. API Host RateLimitingSettings.GlobalPermitLimit (default 300 requests per minute, MMCA.Common/Source/Presentation/MMCA.Common.API/RateLimiting/RateLimitingSettings.cs:40) per authenticated user: GlobalRateLimitPartition - (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:136) + (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:137) routes anonymous traffic to AnonymousPartition (line 58), and health, liveness, /.well-known/*, and gRPC traffic bypass the limiter outright (IsRateLimitBypassed, lines 42-46). The gRPC arm of that exemption is keyed on the routed endpoint rather than on the request's Content-Type @@ -371,7 +371,7 @@

      12. API Host automatic and Blazor Server circuits issue it server-side from one shared host IP. A consumer that inherits the base without calling AddCommonRateLimiting fails at startup on an unregistered policy, which is the loud failure rather than the silent one. Two knobs sit on top of that baseline, both - reachable only through the IConfiguration overload (WebApplicationBuilderExtensions.RateLimiting.cs:344) that + reachable only through the IConfiguration overload (WebApplicationBuilderExtensions.RateLimiting.cs:346) that binds the RateLimiting section. Algorithm (RateLimitingSettings.cs:53) selects the RateLimitAlgorithm enum (MMCA.Common/Source/Presentation/MMCA.Common.API/RateLimiting/RateLimitAlgorithm.cs:8): FixedWindow @@ -382,7 +382,7 @@

      12. API Host in-memory counter for RedisFixedWindowRateLimiter (MMCA.Common/Source/Presentation/MMCA.Common.API/RateLimiting/RedisFixedWindowRateLimiter.cs:37), so a limit means the same thing behind a load balancer as it does on one node. All three choices funnel - through one private factory, CreateLimitedPartition (WebApplicationBuilderExtensions.RateLimiting.cs:205), which + through one private factory, CreateLimitedPartition (WebApplicationBuilderExtensions.RateLimiting.cs:207), which takes the Redis path only when an IConnectionMultiplexer is actually registered and otherwise falls through to the in-memory limiters rather than failing startup (lines 214-236). The Redis limiter is worth reading for its three deliberate compromises: it stores one INCR counter per partition per @@ -396,7 +396,7 @@

      12. API Host RedisRateLimitLease (RedisFixedWindowRateLimiter.cs:169) is the two-instance lease type it hands out, Acquired and Rejected as shared statics (lines 172 and 175) so a permitted request allocates nothing. The auth-ip policy stays per-instance whatever - Distributed says (allowDistributed: false, WebApplicationBuilderExtensions.RateLimiting.cs:291): per-account + Distributed says (allowDistributed: false, WebApplicationBuilderExtensions.RateLimiting.cs:293): per-account lockout already backs it, and a login throttle that fails open on a Redis outage is a worse trade than one that stays local (ADR-019 for the layering, and @@ -405,14 +405,14 @@

      12. API Host once at the edge.

      Correlation, tenancy, and the soft-deleted-user gate: three ambient facts established once. CorrelationIdMiddleware - (MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/CorrelationIdMiddleware.cs:15) reads the - X-Correlation-ID request header (the constant lives at CorrelationIdMiddleware.cs:18), falling back + (MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/CorrelationIdMiddleware.cs:18) reads the + X-Correlation-ID request header (the constant lives at CorrelationIdMiddleware.cs:21), falling back to the current W3C trace id then ASP.NET's TraceIdentifier (lines 32-34), writes it onto the scoped ICorrelationContext (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ICorrelationContext.cs:8, implemented by CorrelationContext, which self-seeds a GUID when no middleware ever sets one, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Context/CorrelationContext.cs:12), and echoes it - back through Response.OnStarting (CorrelationIdMiddleware.cs:37-41). That single id is what the + back through Response.OnStarting (CorrelationIdMiddleware.cs:44-48). That single id is what the CQRS logging decorators read (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/LoggingCommandDecorator.cs:17 and :24) and stamp onto every log scope through the @@ -457,7 +457,7 @@

      12. API Host ways. Thrown exceptions are caught by the handler chain registered in AddCommonExceptionHandlers (DependencyInjection.cs:154-158), evaluated most-specific-first: OperationCanceledExceptionHandler (499 Client Closed Request, - OperationCanceledExceptionHandler.cs:32), DomainExceptionHandler (400, + OperationCanceledExceptionHandler.cs:37), DomainExceptionHandler (400, DomainExceptionHandler.cs:32), DbUpdateExceptionHandler (409 at line 33 with a deliberately generic detail so database schema names never leak, DbUpdateExceptionHandler.cs:35-37), ValidationExceptionHandler (400 @@ -574,7 +574,7 @@

      12. API Host ADR-036 and ADR-043); DataExportControllerBase<TQuery> - (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:59), + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:61), the data-subject access and portability endpoint (ADR-076), which serves GET {userId}/export (line 77) as a real file download rather than an inline body (returned through @@ -613,20 +613,20 @@

      12. API Host subclass, and inherits a fully paged, filterable, exportable, error-mapped REST resource, with [Rubric §30, Compliance & Data Governance] covered by the DSAR base.

      Export is a route, not a content negotiation. EntityControllerBase.ExportAsync - (EntityControllerBase.cs:245-249) streams the same filtered collection the paged endpoint serves as + (EntityControllerBase.cs:252-257) streams the same filtered collection the paged endpoint serves as an RFC 4180 CSV download, page-looping the query service server-side (ADR-078). It is a distinct path rather than an Accept: text/csv variant for two reasons stated in the source: the public output-cache policy varies by query string but not by Accept, so a cached JSON body could be replayed to a CSV request, and AddAPI sets ReturnHttpNotAcceptable = false, so a negotiation miss would fall - back to JSON silently instead of returning 406 (EntityControllerBase.cs:196-201). The work is split + back to JSON silently instead of returning 406 (EntityControllerBase.cs:199-204). The work is split in two. The controller keeps the HTTP concerns (the action, the row scope, the file name, and the headers) and hands the format to EntityCsvExporter<TEntityDTO> (MMCA.Common/Source/Presentation/MMCA.Common.API/Export/EntityCsvExporter.cs:17, an internal static class whose own summary draws that line at :9-14), which owns which DTO properties can be exported, how a page of rows becomes CSV records, and the paging loop that streams every page under a row - ceiling (WriteAsync, line 105, invoked from EntityControllerBase.cs:267). Its record-writing + ceiling (WriteAsync, line 105, invoked from EntityControllerBase.cs:279). Its record-writing primitive is CsvWriter (MMCA.Common/Source/Presentation/MMCA.Common.API/Export/CsvWriter.cs:34), a deliberately hand-written internal helper: it quotes only when RFC 4180 requires it (WriteField, line 145, with @@ -641,24 +641,24 @@

      12. API Host invariant ToString is exactly the cell a reader expects (:26-28). And a caller who names one of those dropped properties in fields= gets an Error.InvalidEntityField validation failure rather than a quietly missing column (ValidateFields, EntityCsvExporter.cs:60, called first thing in the action - at EntityControllerBase.cs:257-259). Then the exporter opens a StreamWriter over Response.Body + at EntityControllerBase.cs:265-267). Then the exporter opens a StreamWriter over Response.Body (EntityCsvExporter.cs:121) but commits nothing until the first page has succeeded: headers, BOM, and the header row go out only then (:144-151), and a first-page failure comes back as a plain failed Result (:130-131), which is what keeps the "a failure on page one still returns Problem Details" - path honest (EntityControllerBase.cs:288-289). A failure on a later page can no longer change the + path honest (EntityControllerBase.cs:300-301). A failure on a later page can no longer change the status, so the exporter logs it through the controller (LogExportPageFailure, - EntityControllerBase.cs:593) and ends the file with an explicit # export incomplete after N rows + EntityControllerBase.cs:646) and ends the file with an explicit # export incomplete after N rows line (EntityCsvExporter.cs:133-137, text at :321-322) rather than letting it pass for a complete export of fewer rows. The row ceiling is announced up front through the X-Export-Row-Limit header - (constant at EntityControllerBase.cs:469, default 100,000 at line 460, overridable per host through + (constant at EntityControllerBase.cs:481, default 100,000 at line 460, overridable per host through MaxExportRows at lines 76-83, written alongside the Content-Disposition attachment name in BeginExportResponse at lines 571-572, the name being a UTC {controller}-{yyyyMMddTHHmmssZ}.csv built at lines 582-585) with a # export truncated at N rows notice written as a final body line - (EntityCsvExporter.cs:180-183, text at :315-316), because headers are frozen the moment the first + (EntityCsvExporter.cs:179-182, text at :315-316), because headers are frozen the moment the first byte flushes; when the ceiling lands exactly on a page boundary the exporter consults the page's TotalItemCount instead of issuing one more query just to find out (:169-175). Row scoping is a shared hook rather than an export-only one: GetReadSpecificationAsync - (EntityControllerBase.cs:531-533) is what all five read actions apply (both GetAllAsync + (EntityControllerBase.cs:571-573) is what all five read actions apply (both GetAllAsync overloads, the lookup, GetByIdAsync, and the export, :497-499), resolved once per request so one instance filters every page of an export loop (:527-528). It is asynchronous because row scoping is rarely a pure function of the principal (:507-512), it returns GetExportSpecification(), itself @@ -672,7 +672,7 @@

      12. API Host IdempotentAttribute (MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotentAttribute.cs:16), a one-line ServiceFilterAttribute that resolves the scoped IdempotencyFilter - (MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:67) from DI + (MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:68) from DI (ADR-017). The filter runs at two MVC stages. As an IAsyncResourceFilter it runs before model binding, the last point at which the body can be made replayable, and calls EnableBuffering only when an Idempotency-Key header is @@ -750,7 +750,7 @@

      12. API Host the honest strength, because a strong tag promises byte-for-byte equality of the representation, which this cannot promise when the same row version renders differently under a fields= projection (ConcurrencyETag.cs:13). The read side emits it automatically: EntityControllerBase.GetByIdAsync - calls SetConcurrencyETag (EntityControllerBase.cs:370), which resolves the DTO's RowVersion + calls SetConcurrencyETag (EntityControllerBase.cs:382), which resolves the DTO's RowVersion property once per closed controller type (line 379, so a DTO with no token costs no per-request reflection), reads the token off either the typed DTO or the camelCase dictionary a fields= projection produces (ReadRowVersion, lines 422-431), and writes the header at line 413; the method @@ -912,7 +912,7 @@

      12. API Host flows through a service-to-service hop without any handler threading the token by hand: the HTTP twin of JwtForwardingClientInterceptor. DatabaseInitializationExtensions - (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:20) + (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:28) initializes every physical data source the host owns: InitializeDatabaseAsync (line 34) validates the strategy before touching anything (EnsureKnownStrategy, called at line 46 and declared at line 180, which accepts only Migrate or None and otherwise throws naming the valid values through @@ -931,7 +931,7 @@

      12. API Host ADR-073), and finishes by running the enabled modules' seeders on the default scope only (line 110). Five smaller startup helpers round out the host: OpenApiEndpointExtensions - (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/Endpoints/OpenApiEndpointExtensions.cs:22) maps the + (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/Endpoints/OpenApiEndpointExtensions.cs:25) maps the per-version OpenAPI document (MapCommonOpenApi, lines 34-38) and the optional Scalar reference UI (MapCommonScalarUi, lines 52-56), both outside Production only, ApiParameterDescriptorBackfillProvider @@ -945,7 +945,7 @@

      12. API Host (MMCA.Common/Source/Presentation/MMCA.Common.API/OpenApi/StronglyTypedIdSchemaTransformer.cs:24) and StronglyTypedIdParameterTransformer (same file, line 74), both registered by AddCommonOpenApi through a ConfigureAll that reaches every versioned document - (WebApplicationBuilderExtensions.cs:100-104) and both inert in a host that declares no wrappers: the + (WebApplicationBuilderExtensions.cs:102-106) and both inert in a host that declares no wrappers: the schema half clears the object schema the default generator produces for a one-property struct and rewrites it as the wrapped primitive's type and format (:38-41, mapped at :46 and :51) so the document matches the bare-primitive wire shape, and the operation half does the same for route and @@ -1057,7 +1057,7 @@

      PublicEndpointOutputCachePolicy

    870. Concept introduced (auth-header-tolerant output caching). The framework UI attaches a Bearer token to every outgoing API request, including reads of [AllowAnonymous] endpoints whose payload is identical for every caller. Under the default policy those reads bypass the output cache for any signed-in user and land on the database each time (PublicEndpointOutputCachePolicy.cs:15-20). [Rubric §12, Performance & Scalability] assesses whether hot read paths avoid redundant work; this policy is a direct performance lever, letting public reads share one cached entry across authenticated and anonymous callers. [Rubric §11, Security] assesses trust boundaries; the class docs are explicit that a cached response is served verbatim to every subsequent caller, so it must be applied only to identity-independent payloads, and the bypassRoles mechanism exists precisely so a privileged role that receives an elevated payload (for example administrators seeing unpublished rows) is never served or stored from the shared cache (:32-38). A second SECURITY remark covers multi-tenancy (:43-48): the tenant-vary rule below is a backstop, not a license, an endpoint whose payload depends on the caller (rather than only on the tenant) still must not use this policy. This is the edge tier of the two-tier caching model (ADR-026); the authenticated-read decision itself is ADR-040.
    871. Walkthrough: a TenantVaryByKey = "t" constant (:50) mirrors the t:{tenantId} prefix the caching CQRS decorators use, and three instance fields hold the rest of the config, _expiration, _bypassRoles, _tags (:52-54). Two constructors: the params string[] tags overload delegates to the full one with an empty bypass-roles array, and the primary constructor guards its inputs (ThrowIfLessThanOrEqual(expiration, TimeSpan.Zero), null checks on both arrays). All three interface methods are explicitly implemented, so they are reachable only through IOutputCachePolicy. CacheRequestAsync (:81) computes attemptOutputCaching as "is a GET/HEAD request" AND "is not a bypassed caller" (:86-87), enables output caching, sets AllowCacheLookup/AllowCacheStorage to that flag, allows locking, sets the expiration (:88-92), and (matching the built-in default) varies the cache key by every query-string parameter via CacheVaryByRules.QueryKeys = "*" (:96). It then resolves the tenant from context.HttpContext.RequestServices and, when one is present, stamps it into CacheVaryByRules.VaryByValues[TenantVaryByKey] (:98-106), an unresolved tenant, single-tenant host or background call, adds nothing; then copies the eviction tags in. ServeFromCacheAsync is a no-op returning ValueTask.CompletedTask. ServeResponseAsync refuses to store any response that set a cookie or returned a non-200 status, the same guard the built-in default applies. Two private helpers close it out: IsCacheableRequest (:134-135, GET or HEAD) and IsBypassedCaller (:141-142, Array.Exists(_bypassRoles, user.HasRole)). HasRole is deliberate, not ClaimsPrincipal.IsInRole: IsInRole only matches the identity's own role claim type, so against a provider that emits bare role/roles claims the permission handler would grant the elevated payload while this bypass check missed it, and that response would be stored under the shared public key (:137-140).
    872. Why it's built this way: it mirrors the built-in default policy minus exactly one behavior, the authenticated-request bail-out, so its caching, query-key variance, and cookie/status guards stay identical to what developers already expect. Bypass roles get the default behavior back (no lookup, no storage), which keeps elevated payloads out of the shared cache without disabling caching for everyone. Stamping the resolved tenant into the vary key means a multi-tenant host's shared cache entry per path+query never crosses tenants, since the same path and query mean different rows per tenant (:98-101). A raw IOutputCachePolicy implementation inherits none of the default policy's behavior, so every guard is re-implemented here.
    873. -
    874. Where it's used: registered as a named policy by OutputCacheOptionsExtensions and referenced from controller actions via [OutputCache(PolicyName = ...)]. Store's Catalog service registers four such policies with no bypass roles (MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:149-154); ADC's Conference service registers ten, most of them with an admin bypass-roles array (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:268-297), including two short 60-second policies for the fast-moving NowNextCache (:231) and BookmarkCountsCache (:243). The tags passed here are the same strings OutputCacheEvictionHandler evicts by.
    875. +
    876. Where it's used: registered as a named policy by OutputCacheOptionsExtensions and referenced from controller actions via [OutputCache(PolicyName = ...)]. Store's Catalog service registers four such policies with no bypass roles (MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:149-154); ADC's Conference service registers ten, most of them with an admin bypass-roles array (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:267-296), including two short 60-second policies for the fast-moving NowNextCache (:231) and BookmarkCountsCache (:243). The tags passed here are the same strings OutputCacheEvictionHandler evicts by.
    877. OutputCacheOptionsExtensions

      @@ -1069,7 +1069,7 @@

      OutputCacheOptionsExtensions

    878. Concept: this is a thin fluent facade over OutputCacheOptions.AddPolicy, using a C# extension(OutputCacheOptions options) block (OutputCacheOptionsExtensions.cs:8) so the policy registration reads as a first-class option on the options object. See the DI registration extension(T) convention. [Rubric §9, API & Contract Design] is relevant: the helper gives callers a self-documenting, named entry point instead of hand-constructing the policy at each call site.
    879. Walkthrough: two overloads of AddPublicEndpointPolicy. The first (OutputCacheOptionsExtensions.cs:20-21) takes name, expiration, and params string[] tags and registers new PublicEndpointOutputCachePolicy(expiration, tags). The second (:34-35) adds a string[] bypassRoles parameter before the params string[] tags and forwards to the three-argument policy constructor, for endpoints whose payload is identical for every caller except one privileged role. Both are expression-bodied and return void, mutating the options in place.
    880. Why it's built this way: keeping the policy construction behind a named helper means the "apply only to [AllowAnonymous], identity-independent endpoints" guidance travels with the API surface (see the doc comments at :10-16 and :23-29) instead of being re-derived at each registration.
    881. -
    882. Where it's used: called during host composition where the app configures AddOutputCache(...); the registered name is then referenced by [OutputCache(PolicyName = ...)] on controller actions. Store's Catalog service uses the two-argument form (MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:149-154); ADC's Conference service mostly uses the bypass-roles form (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:268-297).
    883. +
    884. Where it's used: called during host composition where the app configures AddOutputCache(...); the registered name is then referenced by [OutputCache(PolicyName = ...)] on controller actions. Store's Catalog service uses the two-argument form (MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:149-154); ADC's Conference service mostly uses the bypass-roles form (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:267-296).
    885. ModuleControllerFeatureProvider

      @@ -1097,7 +1097,7 @@

      OutputCacheEvictionHandler

    886. Walkthrough: the primary constructor takes IOutputCacheStore outputCacheStore and ILogger<OutputCacheEvictionHandler> logger (:32-34). HandleAsync(OutputCacheEvictionRequested integrationEvent, CancellationToken) (:38) null-guards the event (:42), then loops the tags (:44), skipping blank entries (:46-49). Each tag goes through outputCacheStore.EvictByTagAsync(tag, cancellationToken) inside its own try (:51-53) followed by a Debug log (:54). The catch (:56-62) is deliberately broad, with the comment noting that CA1031/S2221 are suggestions here because an eviction store that throws must not turn a coherence hint into a dead-lettered message; it records the failure on OutputCacheMetrics (:60) and logs a Warning naming the tag and stating that responses carrying it stay cached until their own TTL expires (:61, template at :71-74).
    887. Why it's built this way: partial plus two [LoggerMessage] declarations (:66-74) gives source-generated, allocation-free logging at Debug for the success path and Warning for the failure path. Keeping the handler free of any broker type is what lets the same class serve both the in-process dispatcher and the MassTransit consumer, which is the extraction property the module system is built around (ADR-007, ADR-008).
    888. -
    889. Where it's used: registered by OutputCacheEvictionExtensions.AddOutputCacheEvictionHandler(). The broker half is RegisterOutputCacheEvictionConsumer() inside the host's AddBrokerMessaging configuration, which wires the generic IntegrationEventConsumer<OutputCacheEvictionRequested> onto this handler; ADC's Conference service calls both (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:303 and :352). The tags it evicts are the ones passed to OutputCacheOptionsExtensions.AddPublicEndpointPolicy. Framework coverage is OutputCacheEvictionHandlerTests (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Caching/OutputCacheEvictionHandlerTests.cs).
    890. +
    891. Where it's used: registered by OutputCacheEvictionExtensions.AddOutputCacheEvictionHandler(). The broker half is RegisterOutputCacheEvictionConsumer() inside the host's AddBrokerMessaging configuration, which wires the generic IntegrationEventConsumer<OutputCacheEvictionRequested> onto this handler; ADC's Conference service calls both (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:302 and :352). The tags it evicts are the ones passed to OutputCacheOptionsExtensions.AddPublicEndpointPolicy. Framework coverage is OutputCacheEvictionHandlerTests (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Caching/OutputCacheEvictionHandlerTests.cs).
    892. OutputCacheEvictionExtensions

      @@ -1121,25 +1121,25 @@

      OutputCacheEvictionExtensions

    893. Why it's built this way: the class doc states the pairing rule plainly (:12-16): this is the DI half, RegisterOutputCacheEvictionConsumer() on the MassTransit bus configurator is the broker half, and a host that wants the behavior calls both. Splitting them is what keeps the handler broker-agnostic, since the DI registration has no messaging dependency at all. Housing the eviction verbs in the same class keeps the write-side helper and the read-side coherence path in one file.
    894. -
    895. Where it's used: ADC's Conference service calls services.AddOutputCacheEvictionHandler() (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:303) alongside the broker registration (:352). ADC's Conference controllers use the throwing form after a write, for example SpeakersController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:305) and SpeakerCategoryItemsController, which evicts three tags in one call (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerCategoryItemsController.cs:178); Store's Catalog controllers use the best-effort form, for example ProductsController (MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.API/Controllers/ProductsController.cs:242) and CategoriesController (MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.API/Controllers/CategoriesController.cs:168). Framework coverage: OutputCacheEvictTagsTests pins both verbs, including the CancellationToken.None rule and the swallow-versus-throw split (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Caching/OutputCacheEvictTagsTests.cs:59-113), and OutputCacheEvictionHandlerTests pins the double-registration behavior (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Caching/OutputCacheEvictionHandlerTests.cs:107-114).
    896. +
    897. Where it's used: ADC's Conference service calls services.AddOutputCacheEvictionHandler() (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:302) alongside the broker registration (:352). ADC's Conference controllers use the throwing form after a write, for example SpeakersController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:314) and SpeakerCategoryItemsController, which evicts three tags in one call (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerCategoryItemsController.cs:186); Store's Catalog controllers use the best-effort form, for example ProductsController (MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.API/Controllers/ProductsController.cs:242) and CategoriesController (MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.API/Controllers/CategoriesController.cs:168). Framework coverage: OutputCacheEvictTagsTests pins both verbs, including the CancellationToken.None rule and the swallow-versus-throw split (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Caching/OutputCacheEvictTagsTests.cs:59-113), and OutputCacheEvictionHandlerTests pins the double-registration behavior (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Caching/OutputCacheEvictionHandlerTests.cs:107-114).
    898. DependencyInjection

      -

      MMCA.Common.API · MMCA.Common.API · MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:26 · Level 11 · class (static)

      +

      MMCA.Common.API · MMCA.Common.API · MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:26 · Level 14 · class (static)

      • What it is: the primary DI entry point for the MMCA.Common.API layer. Using a C# extension(IServiceCollection services) block (DependencyInjection.cs:28) it adds six methods to IServiceCollection: AddAPI, AddErrorLocalization, AddErrorResources<TResource>, AddCommonExceptionHandlers, AddServerAuthSessionCookie, and AddModuleHealthChecks.
      • -
      • Depends on: a broad slice of the API layer plus feature management and localization (DependencyInjection.cs:1-19). Notable first-party types wired here: CurrencyJsonConverter, the Shared-layer EnumerationJsonConverterFactory (aliased because the Shared and API namespaces both surface converters), and StronglyTypedIdJsonConverterFactory (ADR-115 strongly typed identifiers, serializing wrapper types as their bare primitive), UnhandledResultFailureFilter, IdempotencyFilter and IdempotencySettings, OwnerOrAdminFilter and its OwnerOrAdminFilterOptions, ModuleControllerFeatureProvider, CurrentUserTargetingContextAccessor and DisabledFeatureHandler, IErrorLocalizer/ErrorLocalizer, ErrorResources/ErrorResourceSource, the exception handlers (OperationCanceledExceptionHandler, DomainExceptionHandler, DbUpdateExceptionHandler, ValidationExceptionHandler, GlobalExceptionHandler), CookieTokenReader and ICookieSessionRefresher/CookieSessionRefresher, and ModuleLoader/ModulesSettings. Externals: Microsoft.FeatureManagement (including its IDisabledFeaturesHandler contract), Microsoft.Extensions.Localization, ASP.NET Core MVC/ProblemDetails/HealthChecks.
      • -
      • Concept introduced (layered DI wiring at the API edge). [Rubric §3, Clean Architecture] assesses whether each layer registers only its own concerns; this class wires controllers, JSON/XML formatters, filters, feature management, exception handlers, and health checks, all API-layer edges, and reaches down to Application only for ModulesSettings/ModuleLoader (:16-17). [Rubric §13, Observability & Operability] and [Rubric §17, DevOps] both apply through AddModuleHealthChecks (:188), which projects module state into /health checks tagged module so /health?tag=module reports each module's status (:180-181). [Rubric §9, API & Contract Design] is relevant: every parameter is optional and defaulted so a host wires only what it needs.
      • +
      • Depends on: a broad slice of the API layer plus feature management and localization (DependencyInjection.cs:1-19). Notable first-party types wired here: CurrencyJsonConverter, the Shared-layer EnumerationJsonConverterFactory (aliased because the Shared and API namespaces both surface converters), and StronglyTypedIdJsonConverterFactory (ADR-115 strongly typed identifiers, serializing wrapper types as their bare primitive), UnhandledResultFailureFilter, IdempotencyFilter and IdempotencySettings, OwnerOrAdminFilter and its OwnerOrAdminFilterOptions, ModuleControllerFeatureProvider, CurrentUserTargetingContextAccessor and DisabledFeatureHandler, IErrorLocalizer/ErrorLocalizer, ErrorResources/ErrorResourceSource, the exception handlers (OperationCanceledExceptionHandler, DomainExceptionHandler, DbUpdateExceptionHandler, ValidationExceptionHandler, GlobalExceptionHandler), CookieTokenReader and ICookieSessionRefresher/CookieSessionRefresher, the session-cookie options and store (SessionCookieSettings, ISessionCookieStore/SessionCookieStore, from MMCA.Common.API.SessionCookies, :15), and ModuleLoader/ModulesSettings. Externals: Microsoft.FeatureManagement (including its IDisabledFeaturesHandler contract), Microsoft.Extensions.Localization, ASP.NET Core MVC/ProblemDetails/HealthChecks.
      • +
      • Concept introduced (layered DI wiring at the API edge). [Rubric §3, Clean Architecture] assesses whether each layer registers only its own concerns; this class wires controllers, JSON/XML formatters, filters, feature management, exception handlers, and health checks, all API-layer edges, and reaches down to Application only for ModulesSettings/ModuleLoader (:16-17). [Rubric §13, Observability & Operability] and [Rubric §17, DevOps] both apply through AddModuleHealthChecks (:212), which projects module state into /health checks tagged module so /health?tag=module reports each module's status (:204-205). [Rubric §9, API & Contract Design] is relevant: every parameter is optional and defaulted so a host wires only what it needs.
      • Walkthrough
          -
        • AddAPI(ModulesSettings? modulesSettings = null, IConfiguration? configuration = null) (DependencyInjection.cs:45) registers controllers with ReturnHttpNotAcceptable = false and the UnhandledResultFailureFilter global filter (:47-50), adds three JSON converters (:52-65) and the XML DataContract formatters (:67). The second converter is the load-bearing one for enumerations: concrete enumerations do not inherit the base class's [JsonConverter] attribute because System.Text.Json resolves it with inherit: false, so the factory is registered once here and every Enumeration<T> serializes by Name across the whole API surface (:56-59). The third, StronglyTypedIdJsonConverterFactory (:61-65), does the same job for ADR-115 strongly typed identifiers, so wrapper types serialize as the bare primitive they wrap without the attribute being repeated on every wrapper a consumer declares; in a host that declares no wrappers the factory converts nothing. It then conditionally registers ModuleControllerFeatureProvider when modulesSettings is non-null (:69-73), conditionally binds IdempotencySettings from the config section with data-annotation validation on start (:75-81), and registers the scoped IdempotencyFilter and OwnerOrAdminFilter (scoped because they depend on scoped services such as ICacheService and ICurrentUserService, :83-85). Right after that it binds OwnerOrAdminFilterOptions with data-annotation validation but deliberately not ValidateOnStart (:87-92): BypassRole is required with no default, the framework knows no role names, so validating at startup would fail every host that never applies the filter; validating on first resolve puts the message in front of the host that actually uses it. Feature management comes next and is worth reading closely: AddHttpContextAccessor() is called first because it is TryAdd-based and therefore safe to repeat, then AddFeatureManagement().WithTargeting<CurrentUserTargetingContextAccessor>() registers the feature manager plus the built-in Percentage/TimeWindow/Targeting filters and supplies the targeting audience from the current request's principal, which is what makes a percentage rollout sticky per user instead of random per request; the accessor is a singleton, which is exactly why it reads IHttpContextAccessor rather than the scoped ICurrentUserService. Finally the singleton DisabledFeatureHandler is bound to IDisabledFeaturesHandler and AddErrorLocalization() is called.
        • -
        • AddErrorLocalization() (:107) registers ASP.NET localization (:109), the singleton IErrorLocalizer via TryAddSingleton so a host can substitute its own (:110), and the framework's own ErrorResources source (:111); AddErrorResources<TResource>() (:122) adds a module's resource anchor as another ErrorResourceSource built from an IStringLocalizerFactory (:124-125). This is the ADR-027 edge error-localization extension point, keyed by Error.Code, and modules add their translations additively (:115-121).
        • -
        • AddCommonExceptionHandlers() (:135) registers ProblemDetails (adding a requestId extension from TraceIdentifier, :137-139) then five IExceptionHandlers in specificity order (:140-144): OperationCanceled, DomainException, DbUpdate, Validation, and GlobalExceptionHandler as the catch-all. ASP.NET Core invokes them in registration order and stops at the first that handles the exception, hence most-specific first and the 500 fallback last (:131-132).
        • -
        • AddServerAuthSessionCookie(string apiBaseAddress) (:160) wires the SSR-prerender auth path: it guards the address (:162), then adds HttpContextAccessor, memory cache, the scoped CookieTokenReader (:164-166), a named HttpClient pointed at the internal API base address (:168-169), and the CookieSessionRefresher as a singleton (:172). The singleton is load-bearing: its in-flight map must be shared across requests for single-flight refresh to work (:171). The doc comment is explicit that the address is the internal endpoint, not the browser-facing one (:155-158).
        • -
        • AddModuleHealthChecks(ModuleLoader moduleLoader) (:188) adds one health check per module, Healthy for each enabled module (:192-198) and Degraded for each disabled one (:200-207), named module-{Name} and tagged module. It must run after ModuleLoader's DiscoverAndRegister (:183-186).
        • +
        • AddAPI(ModulesSettings? modulesSettings = null, IConfiguration? configuration = null) (DependencyInjection.cs:45) registers controllers with ReturnHttpNotAcceptable = false and the UnhandledResultFailureFilter global filter (:47-50), adds three JSON converters (:52-65) and the XML DataContract formatters (:67). The second converter is the load-bearing one for enumerations: concrete enumerations do not inherit the base class's [JsonConverter] attribute because System.Text.Json resolves it with inherit: false, so the factory is registered once here and every Enumeration<T> serializes by Name across the whole API surface (:56-59). The third, StronglyTypedIdJsonConverterFactory (:61-65), does the same job for ADR-115 strongly typed identifiers, so wrapper types serialize as the bare primitive they wrap without the attribute being repeated on every wrapper a consumer declares; in a host that declares no wrappers the factory converts nothing. It then conditionally registers ModuleControllerFeatureProvider when modulesSettings is non-null (:69-73), conditionally binds IdempotencySettings from the config section with data-annotation validation on start (:75-81), and registers the scoped IdempotencyFilter and OwnerOrAdminFilter (scoped because they depend on scoped services such as ICacheService and ICurrentUserService, :83-85). Right after that it binds OwnerOrAdminFilterOptions with data-annotation validation but deliberately not ValidateOnStart (:87-92): BypassRole is required with no default, the framework knows no role names, so validating at startup would fail every host that never applies the filter; validating on first resolve puts the message in front of the host that actually uses it. Feature management comes next (:94-107) and is worth reading closely: AddHttpContextAccessor() is called first because it is TryAdd-based and therefore safe to repeat, then AddFeatureManagement().WithTargeting<CurrentUserTargetingContextAccessor>() registers the feature manager plus the built-in Percentage/TimeWindow/Targeting filters and supplies the targeting audience from the current request's principal, which is what makes a percentage rollout sticky per user instead of random per request; the accessor is a singleton, which is exactly why it reads IHttpContextAccessor rather than the scoped ICurrentUserService. Finally the singleton DisabledFeatureHandler is bound to IDisabledFeaturesHandler and AddErrorLocalization() is called (:110).
        • +
        • AddErrorLocalization() (:121) registers ASP.NET localization (:123), the singleton IErrorLocalizer via TryAddSingleton so a host can substitute its own (:124), and the framework's own ErrorResources source (:125); AddErrorResources<TResource>() (:136) adds a module's resource anchor as another ErrorResourceSource built from an IStringLocalizerFactory (:138-139). This is the ADR-027 edge error-localization extension point, keyed by Error.Code, and modules add their translations additively (:116-118).
        • +
        • AddCommonExceptionHandlers() (:149) registers ProblemDetails (adding a requestId extension from TraceIdentifier, :151-153) then five IExceptionHandlers in specificity order (:154-158): OperationCanceled, DomainException, DbUpdate, Validation, and GlobalExceptionHandler as the catch-all. ASP.NET Core invokes them in registration order and stops at the first that handles the exception, hence most-specific first and the 500 fallback last (:145-146).
        • +
        • AddServerAuthSessionCookie(string apiBaseAddress) (:174) wires the SSR-prerender auth path: it guards the address (:176), then adds HttpContextAccessor, memory cache, the scoped CookieTokenReader (:178-180), a named HttpClient pointed at the internal API base address (:182-183), and the CookieSessionRefresher as a singleton (:186). The singleton is load-bearing: its in-flight map must be shared across requests for single-flight refresh to work (:185). It then registers the SessionCookieSettings options with no configuration binding, so SameSite and the claims-only browser token keep their default behavior unless the same-origin API proxy in MMCA.Common.UI.Web tightens them on opt-in (ADR-131), and the singleton ISessionCookieStore/SessionCookieStore via TryAddSingleton (:188-191). Last it registers TimeProvider.System via TryAddSingleton (:193-196): the refresher judges access-token expiry against the injected clock, and a Blazor Web host that calls only this method has no AddServices registration to supply one, while TryAdd never displaces a clock the host registered itself. The doc comment is explicit that the address is the internal endpoint, not the browser-facing one (:169-171).
        • +
        • AddModuleHealthChecks(ModuleLoader moduleLoader) (:212) adds one health check per module, Healthy for each enabled module (:216-222) and Degraded for each disabled one (:224-231), named module-{Name} and tagged module. It must run after ModuleLoader's DiscoverAndRegister (:208-209).
      • -
      • Why it's built this way: bundling the API-edge concerns behind small, defaulted extension methods lets each host opt into exactly the surface it needs (a JWT-only test host skips AddServerAuthSessionCookie; a monolith with no disabled modules passes a null modulesSettings; a host with no idempotency configuration passes a null configuration and keeps the defaults). The exception-handler ordering, the enumeration and strongly-typed-id converter factories, the OwnerOrAdminFilterOptions validate-on-resolve (not on-start) choice, the targeting accessor's singleton lifetime, and the refresher's singleton lifetime are the non-obvious, correctness-critical choices, and each carries an inline comment. Error localization is registered automatically by AddAPI so modules only add their own resources additively (ADR-027).
      • +
      • Why it's built this way: bundling the API-edge concerns behind small, defaulted extension methods lets each host opt into exactly the surface it needs (a JWT-only test host skips AddServerAuthSessionCookie; a monolith with no disabled modules passes a null modulesSettings; a host with no idempotency configuration passes a null configuration and keeps the defaults). The exception-handler ordering, the enumeration and strongly-typed-id converter factories, the OwnerOrAdminFilterOptions validate-on-resolve (not on-start) choice, the targeting accessor's singleton lifetime, the refresher's singleton lifetime, and the TryAdd fallback clock for hosts that call only AddServerAuthSessionCookie are the non-obvious, correctness-critical choices, and each carries an inline comment. Error localization is registered automatically by AddAPI so modules only add their own resources additively (ADR-027).
      • Where it's used: called from every service host's composition (Program.cs of the ADC/Store/Helpdesk API hosts and the integration-test hosts) to wire the shared API layer; AddApplicationDecorators() still runs last in the overall sequence (see MMCA.Common/CLAUDE.md DI ordering note).
      • Caveats / not-in-source: the relative ordering of AddAPI against AddInfrastructure/AddApplication in a given host is not fixed by this file; only AddApplicationDecorators() last is load-bearing.
      @@ -1162,7 +1162,7 @@

      CsvWriter

    899. Why it's built this way: the writer takes a TextWriter rather than returning a string, because the whole point of the export endpoint is that no full result set exists in memory at any moment. Keeping the type internal static means it is a framework implementation detail, not a public surface a consumer can bind to and then be broken by.
    900. Caveats / not-in-source: the writer deliberately does not neutralize spreadsheet formula injection. A cell whose value opens with =, +, -, or @ is written verbatim, and the type doc records the reasoning (CsvWriter.cs:27-32): CSV is treated as a data-faithful format here and prefixing would corrupt legitimate negative numbers, so a host that opens untrusted exports in a spreadsheet is expected to import them as text. ADR-078 records the same decision in the same direction, and names the price (a known spreadsheet risk carried by every consumer).
    901. -
    902. Where it's used: only by EntityCsvExporter<TEntityDTO>.WriteAsync, which takes the encoding at EntityCsvExporter.cs:121, writes the BOM and header at EntityCsvExporter.cs:148-149, and each data row via WritePage (EntityCsvExporter.cs:217), reached in turn by EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>.ExportAsync (EntityControllerBase.cs:267).
    903. +
    904. Where it's used: only by EntityCsvExporter<TEntityDTO>.WriteAsync, which takes the encoding at EntityCsvExporter.cs:121, writes the BOM and header at EntityCsvExporter.cs:148-149, and each data row via WritePage (EntityCsvExporter.cs:225), reached in turn by EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>.ExportAsync (EntityControllerBase.cs:279).
    905. ServiceInfoResponse

      @@ -1257,68 +1257,74 @@

      EntityCsvExporter<TEntityDTO>

      • What it is: the page-looping CSV export engine EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>.ExportAsync delegates to: it validates a fields= request, walks the paged query service page by page, and streams RFC 4180 CSV to a caller-supplied stream. Closed over the DTO type alone (EntityCsvExporter.cs:17), so its exclusion lists and column resolution are computed once per DTO shape without needing the entity or identifier type at all.
      • -
      • Depends on: CsvWriter (byte order mark, header, rows), Error, PagedCollectionResult<T>, QueryFieldService (ShapeData, the shaped-row fallback), System.Reflection, System.Text.Json (JsonNamingPolicy.CamelCase), and System.Globalization.
      • +
      • Depends on: CsvWriter (byte order mark, header, rows), Error, PagedCollectionResult<T>, PaginationMetadata (TotalItemCount, the last-page and cap-boundary arbiter), QueryFieldService (ShapeData, the shaped-row fallback), System.Reflection, System.Text.Json (JsonNamingPolicy.CamelCase), and System.Globalization.
      • Concept introduced: extracting a generic algorithm out of the controller that only invokes it. [Rubric §1, SOLID] covers Single Responsibility: the page loop, the column resolution, and the exclusion lists are export mechanics independent of HTTP, so hoisting them out of EntityControllerBase<TEntity, TEntityDTO, TIdentifierType> lets ExportAsync shrink to validate, delegate, and translate the result (class doc, EntityCsvExporter.cs:10-14). [Rubric §15, Best Practices & Code Quality]: WriteAsync takes the fetch as a delegate (Func<int, CancellationToken, Task<Result<PagedCollectionResult<object>>>>) rather than the query service itself, so the exporter carries no dependency on IEntityQueryService<TEntity, TEntityDTO, TIdentifierType> and could stream any paged source a caller adapts to that shape.
      • Walkthrough
          -
        • The three exclusion sets, UnexportablePropertyNames (EntityCsvExporter.cs:30-36), UnexportableColumns (:42-43), and UnexportableFields (:49-50), are static readonly fields computed once per closed EntityCsvExporter<TEntityDTO>, since a DTO's shape cannot change at runtime: the property names that cannot render a faithful scalar CSV cell (binary concurrency tokens and every collection type except string, IsExportableType at :197-200), their camelCase column-name form, and a case-insensitive lookup set for fields= matching.
        • +
        • The three exclusion sets, UnexportablePropertyNames (EntityCsvExporter.cs:30-36), UnexportableColumns (:42-43), and UnexportableFields (:49-50), are static readonly fields computed once per closed EntityCsvExporter<TEntityDTO>, since a DTO's shape cannot change at runtime: the property names that cannot render a faithful scalar CSV cell (binary concurrency tokens and every collection type except string, IsExportableType at :205-208), their camelCase column-name form, and a case-insensitive lookup set for fields= matching.
        • ValidateFields(fields) (:60) rejects a fields= request naming one of those properties as an Error.InvalidEntityField failure (:69-73), before a single byte of the body is written; an empty exclusion set short-circuits to null (:62-63).
        • -
        • WriteAsync(body, fetchPageAsync, pageSize, maxExportRows, fields, beginResponse, onFailureAfterStart, cancellationToken) (:105) is the page loop: constructing the StreamWriter (:121) sends nothing, so a first-page failure still returns Result.Failure with nothing written (:128-130) for the caller to turn into Problem Details. On the first successful page it resolves columns, calls beginResponse(), and writes the BOM and header (:144-151); each page is written by WritePage (:217) and flushed. Three termination conditions mirror the pre-extraction controller loop: a short page ends the export (:166-167), a page that stopped mid-page marks it truncated (:159-163), and a cap landing exactly on a page boundary consults PaginationMetadata.TotalItemCount rather than issuing a wasted page (:171-175). A mid-stream failure after the header started calls onFailureAfterStart and writes the incomplete marker instead of returning failure (:128-138).
        • -
        • ResolveColumns (:252) takes the shaped keys of the first row when there is one, or falls back to the DTO's own properties in declaration order, filtered by fields, for an empty result, so an export of nothing still owes a header row.
        • -
        • ShapeRow (:282) and BuildCells (:293) normalize a row (typed DTO or already-shaped dynamic object) to a camelCase-keyed dictionary and then project it onto the resolved column order, substituting null for a column a row does not carry.
        • -
        • TruncationMarker (:315) and IncompleteMarker (:321) format the two trailing marker lines with CultureInfo.InvariantCulture. ParseFields (:307) splits a fields= value into a case-insensitive set.
        • +
        • WriteAsync(body, fetchPageAsync, pageSize, maxExportRows, fields, beginResponse, onFailureAfterStart, cancellationToken) (:105) is the page loop: constructing the StreamWriter (:121) sends nothing, so a first-page failure still returns Result.Failure with nothing written (:128-131) for the caller to turn into Problem Details. On the first successful page it resolves columns, calls beginResponse(), and writes the BOM and header (:144-151); each page is written by WritePage (:225) and flushed. Three termination conditions follow: a page that stopped mid-page marks the export truncated (:159-163), IsLastPage ends it on the last page (:165-166), and a cap landing exactly on a page boundary consults PaginationMetadata.TotalItemCount rather than issuing a wasted page (:170-174). A mid-stream failure after the header started calls onFailureAfterStart and writes the incomplete marker instead of returning failure (:136-138).
        • +
        • IsLastPage(itemCount, pageSize, rowsWritten, metadata) (:196-197) does not trust a short page on its own: an empty page ends the export, but a short non-empty page ends it only when rowsWritten has reached TotalItemCount. The query pipeline clamps every page to its own ceiling (EntityQueryPipeline.MaxUnboundedResultLimit = 1000, MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/EntityQueryPipeline.cs:23), so a full clamped page can come back shorter than a larger requested page size, and ending there would cut the export short with no marker line (doc, :190-195).
        • +
        • ResolveColumns (:260) takes the shaped keys of the first row when there is one, or falls back to the DTO's own properties in declaration order, filtered by fields, for an empty result, so an export of nothing still owes a header row.
        • +
        • ShapeRow (:290) and BuildCells (:301) normalize a row (typed DTO or already-shaped dynamic object) to a camelCase-keyed dictionary and then project it onto the resolved column order, substituting null for a column a row does not carry.
        • +
        • TruncationMarker (:323) and IncompleteMarker (:329) format the two trailing marker lines with CultureInfo.InvariantCulture. ParseFields (:315) splits a fields= value into a case-insensitive set.
      • -
      • Why it's built this way: generic over TEntityDTO alone, because export needs only the DTO shape to compute its exclusion lists and resolve columns; the fetch delegate carries everything else the caller's specific entity/query pipeline needs. internal static keeps it a framework implementation detail behind ExportAsync, not a public surface a consumer can bind to.
      • -
      • Caveats / not-in-source: this type has no HTTP awareness of its own; the authorization symmetry and no-output-caching caveats belong to EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>, which is the only caller.
      • -
      • Where it's used: only by EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>.ExportAsync, which calls ValidateFields (EntityControllerBase.cs:257) and WriteAsync (EntityControllerBase.cs:267-286).
      • +
      • Why it's built this way: generic over TEntityDTO alone, because export needs only the DTO shape to compute its exclusion lists and resolve columns; the fetch delegate carries everything else the caller's specific entity/query pipeline needs. internal static keeps it a framework implementation detail behind ExportAsync, not a public surface a consumer can bind to. The last-page test reads the total rather than the page length because the page length is a property of the pipeline's clamp, not of the data.
      • +
      • Caveats / not-in-source: this type has no HTTP awareness of its own; the authorization symmetry, the fail-closed row scoping, and the no-output-caching caveats belong to EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>, which is the only caller.
      • +
      • Where it's used: only by EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>.ExportAsync, which calls ValidateFields (EntityControllerBase.cs:265) and WriteAsync (EntityControllerBase.cs:279-298). Unit coverage is EntityCsvExporterTests (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Export/EntityCsvExporterTests.cs).

      EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>

      MMCA.Common.API · MMCA.Common.API.Controllers · MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:34 · Level 6 · class (abstract)

        -
      • What it is: the generic read-only controller that gives any entity five working REST endpoints (GET /, GET /paged, GET /export, GET /lookup, GET /{id}) with filtering, sorting, pagination, field projection, one shared row-scoping hook, and an ETag on the by-id read, by delegating to the IEntityQueryService<TEntity, TEntityDTO, TIdentifierType> pipeline.
      • -
      • Depends on: ApiControllerBase (base), IEntityControllerBase<TEntityDTO, TIdentifierType> (implements), IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, AuditableBaseEntity<TIdentifierType> (constraint), ApplicationSettings (through IOptions<T>), CollectionResult<T>, PagedCollectionResult<T>, BaseLookup<TIdentifierType>, QueryFilterModelBinder, Error, EntityCsvExporter<TEntityDTO>, ConcurrencyETag, QueryFieldService, and Specification<TEntity, TIdentifierType>; ASP.NET Core MVC, Asp.Versioning, System.Text.Json, TimeProvider, and ILogger.
      • +
      • What it is: the generic read-only controller that gives any entity five working REST endpoints (GET /, GET /paged, GET /export, GET /lookup, GET /{id}) with filtering, sorting, pagination, field projection, one shared row-scoping hook, a fail-closed export, and an ETag on the by-id read, by delegating to the IEntityQueryService<TEntity, TEntityDTO, TIdentifierType> pipeline.
      • +
      • Depends on: ApiControllerBase (base), IEntityControllerBase<TEntityDTO, TIdentifierType> (implements), IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, AuditableBaseEntity<TIdentifierType> (constraint), ApplicationSettings (through IOptions<T>), CollectionResult<T>, PagedCollectionResult<T>, BaseLookup<TIdentifierType>, QueryFilterModelBinder, Error, EntityCsvExporter<TEntityDTO>, ConcurrencyETag, QueryFieldService, and Specification<TEntity, TIdentifierType>; EntityQueryPipeline.MaxUnboundedResultLimit for the page-size clamp; ASP.NET Core MVC, Asp.Versioning, System.Text.Json, TimeProvider, and ILogger.
      • Concept introduced: generic controller bases that eliminate CRUD boilerplate. [Rubric §9, API & Contract Design] covers uniform endpoint conventions; [Rubric §1, SOLID] covers the Open/Closed side, since a new entity controller extends this base rather than re-writing the endpoints. The class-level [ApiController], [Route("[controller]")], [ApiVersion("1.0")] (EntityControllerBase.cs:31-33) plus the three generic constraints (EntityControllerBase.cs:41-43) turn the type parameters into the contract: name the entity, the DTO, and the identifier alias, and the routes, the versioning, and the query behavior follow (ADR-034).
      • Walkthrough
          -
        • Primary constructor (EntityControllerBase.cs:34-39) takes the query service and an ILogger, both null-guarded into the QueryService (:47) and Logger (:52) protected properties.
        • -
        • MaxPageSize (:58-65) resolves IOptions<ApplicationSettings> per-request from HttpContext.RequestServices, falling back to 500 (:63). Per-request resolution means a settings change takes effect without a restart. MaxExportRows (:78-86) does the same for the export ceiling, with the extra rule that a configured value of zero or less is treated as unconfigured (:83-84), because a cap of zero would silently serve every caller a header-only file (:73-77). EntityName (:91) is typeof(TEntity).Name, used in log messages.
        • -
        • GetAllAsync unpaged (:109, [HttpGet] at :106): resolves the read specification (:115), then delegates to the query service with pageNumber: 1 and pageSize: MaxPageSize (:122-123), so even the "all" endpoint is capped, then either HandleFailure or Ok.
        • -
        • GetAllAsync paged (:157, [HttpGet("paged")] at :153): clamps with Math.Min(pageSize, MaxPageSize) (:168), and on success serializes PaginationMetadata into the X-Pagination response header (:187) rather than mixing it into the body, [Rubric §9] again, and [Rubric §12, Performance & Scalability] for the clamp.
        • -
        • ExportAsync (:249, [HttpGet("export")] at :245) is covered as its own concept below.
        • -
        • GetAllForLookupAsync (:374, [HttpGet("lookup")] at :371): returns CollectionResult<BaseLookup<TIdentifierType>>, a lightweight id/label pair, with a [Required] nameProperty (:375) choosing the label.
        • -
        • GetByIdAsync (:415, [HttpGet("{id}")] at :410): includeFKs defaults to true for the single-entity case (:417), and on success it calls SetConcurrencyETag(result.Value) before returning (:436-437).
        • -
        • HandleFailure override (:505-519): logs the first error at Warning, guarded by Logger.IsEnabled (:508), before delegating to ApiControllerBase.HandleFailure, so the read path gets observability ([Rubric §13, Observability & Operability]) without changing the response mapping. Note it logs the first error while the base ranks the status by the most severe one: the log line is a breadcrumb, not the status decision.
        • -
        • Every read action passes asTracking: false to the query service (for example :124), because a read endpoint never mutates what it loaded.
        • -
        -
      • -
      • Concept introduced: one row-scoping hook for every read the controller serves. [Rubric §11, Security] assesses whether an authorization decision can be reproduced consistently across every path that returns the same rows; [Rubric §15, Best Practices & Code Quality] assesses whether that decision lives in one place. GetReadSpecificationAsync(cancellationToken) (:597-599) returns the Specification<TEntity, TIdentifierType> applied by all five read actions: both GetAllAsync overloads, GetAllForLookupAsync, GetByIdAsync, and ExportAsync (each resolves it at :115, :170, :378, :422, and :264). Four properties are worth internalizing, all stated on the hook's own doc (:562-596).
          -
        • It is asynchronous because row scoping usually is (:572-579). Scope is rarely a pure function of the current principal: it comes from a query handler, a claim lookup that hits a store, a tenancy read. A synchronous hook forced such a controller to override all five actions by hand purely to get an await in before the query. An override with nothing to await returns ValueTask.FromResult(...) and allocates nothing.
        • -
        • It narrows, it never replaces (:580-586). The query service ANDs the specification with the caller's filters dictionary and fields projection, so a caller can only narrow what the specification already allows; a filter naming excluded rows yields an empty page rather than leaking them (:137-142).
        • -
        • A rejected row is a 404, not a 403 (:587-591, restated on GetByIdAsync at :404-409). The specification narrows the query, so the row is simply absent. Answering "forbidden" would confirm the id exists and turn a scoped read into an existence oracle a caller could walk.
        • -
        • The lookup endpoint carries the scope as a predicate (:364-370). GetAllForLookupAsync has no specification parameter, so the scope travels as specification?.Criteria (:382); a null specification passes a null predicate, the unscoped query this endpoint always issued. A dropdown that lists what the list endpoint hides would be an oracle of its own. - GetExportSpecification() (:626) is the synchronous half: it returns null by default and is what GetReadSpecificationAsync returns by default (:599). A controller that can build its scope without awaiting overrides this one and gets all five actions scoped; a controller that needs an await overrides the async hook instead, and then this one is no longer consulted (:606-613). The remarks are blunt about the consequence of overriding neither on a row-scoped controller (:614-619): it hands every caller the whole table in one request. See ADR-033 for the ownership model this hook reproduces in the query.
        • -
        -
      • -
      • Concept introduced: handing the client a precondition token on the way out. [Rubric §8, Data Architecture] and [Rubric §9, API & Contract Design]. SetConcurrencyETag(object? dto) (:471) emits the read's concurrency token as a weak ETag so a client can hand it straight back as an If-Match precondition on the next write (see SupportsIfMatchAttribute) instead of round-tripping it through the request body. Three details make it cheap and honest.
          -
        • RowVersionProperty (:445-449) is resolved once per closed controller type because a DTO's shape cannot change at runtime: the first public instance byte[] property named exactly RowVersion. For a DTO with no concurrency token it is null, which makes the whole method a no-op with no per-request reflection at all (:473-474).
        • -
        • ReadRowVersion (:488) handles both shapes a served row can take: a typed DTO, read through the cached PropertyInfo (:490-491), or a shaped dictionary keyed by JSON names when the caller asked for a field projection, probed under both rowVersion and RowVersion (:493-496).
        • -
        • A DTO without a token gets no header at all (:476-477). The remark says why (:458-463): absent is the correct answer for a resource that has no version to condition on, and a fabricated tag would invite preconditions the write side cannot honour. The method is protected rather than private (:464-469) so a derived controller serving a row from a custom read action emits the identical header instead of re-implementing the reflection and the base64 format.
        • -
        -
      • -
      • Concept introduced: streaming a bulk extract from a paged read. [Rubric §12, Performance & Scalability] assesses whether a large response is bounded and whether memory grows with the result set; [Rubric §11, Security] covers who may pull a whole table in one request. ExportAsync answers the "export what you filtered" request without any new query path (ADR-078). The action itself is now a thin adapter over EntityCsvExporter<TEntityDTO>, which owns the page loop, the column resolution, and the CSV format; this controller keeps only the HTTP concerns.
          -
        • A dedicated route, not content negotiation ([HttpGet("export")], :245). The remarks name the two behaviors that make an Accept: text/csv formatter wrong here (:196-203): the public output-cache policy varies by query string but not by Accept, so a cached JSON body could be replayed to a CSV request, and AddAPI sets ReturnHttpNotAcceptable = false (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:49), so a negotiation miss falls back to JSON silently instead of returning 406. A distinct path has neither failure mode.
        • -
        • Field validation before a byte moves (:257-259): EntityCsvExporter<TEntityDTO>.ValidateFields (EntityCsvExporter<TEntityDTO>.ValidateFields) rejects a fields= request naming a property the CSV cannot render, as an Error.InvalidEntityField failure, rather than quietly dropping the column.
        • -
        • Row scoping through the shared hook: the specification is resolved once, before the first page (:262), so the same closed-over instance filters every page the fetch delegate passed to WriteAsync reads, and it is the same hook the list endpoints read, so an export can no longer drift wider than the list it mirrors (:228-235, doc-comment range).
        • -
        • The page loop lives in the exporter now: ExportAsync builds a fetch delegate that calls QueryService.GetAllAsync (:267-280) at pageSize = Math.Max(1, MaxPageSize) (:265) and hands it, beginResponse, and onFailureAfterStart to EntityCsvExporter<TEntityDTO>.WriteAsync (:267); the loop, its three termination conditions, and the truncation bookkeeping are documented on that type.
        • -
        • Headers first, then body (BeginExportResponse, :566): content type text/csv; charset=utf-8 (CsvContentType, :463, set at :570), a Content-Disposition attachment whose file name is {controller}-{yyyyMMdd'T'HHmmss'Z'}.csv built invariantly from an injected TimeProvider (:568, :571, BuildExportFileName at :582-585, prefix at :481-486), and X-Export-Row-Limit (ExportRowLimitHeaderName, :469, appended at :572) so a client can tell "exactly at the limit" from "coincidentally that many rows".
        • -
        • Truncation rides in the body. Headers freeze the moment the first body byte flushes, so a truncated export ends with a # export truncated at N rows record (marker built by EntityCsvExporter<TEntityDTO>.TruncationMarker) and a mid-stream query failure ends with an incomplete marker plus a Warning log (LogExportPageFailure, :593-604, wired as onFailureAfterStart at :285). A failure on the FIRST page, before anything was written, still returns Problem Details through HandleFailure (:289), which is exactly what "constructing the writer sends nothing" protects on the exporter side.
        • +
        • Primary constructor (EntityControllerBase.cs:34-39) takes the query service and an ILogger, both null-guarded into the QueryService (:45) and Logger (:50) protected properties.
        • +
        • MaxPageSize (:59-66) resolves IOptions<ApplicationSettings> per-request from HttpContext.RequestServices, falling back to 500, and clamps the result to the range 1 to EntityQueryPipeline.MaxUnboundedResultLimit (:64; the ceiling is 1000 at MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/EntityQueryPipeline.cs:23). The pipeline caps every page at that ceiling anyway, so the clamp keeps the pagination metadata and the export loop from assuming a page size the pipeline does not serve (:53-58). Per-request resolution means a settings change takes effect without a restart. MaxExportRows (:79-87) does the same for the export ceiling, with the extra rule that a configured value of zero or less is treated as unconfigured (:84-85), because a cap of zero would silently serve every caller a header-only file (:75-77). EntityName (:92) is typeof(TEntity).Name, used in log messages and in the export refusal.
        • +
        • GetAllAsync unpaged (:110, [HttpGet] at :107): resolves the read specification (:116), then delegates to the query service with pageNumber: 1 and pageSize: MaxPageSize (:123-124), so even the "all" endpoint is capped, then either HandleFailure or Ok.
        • +
        • GetAllAsync paged (:158, [HttpGet("paged")] at :154): clamps with Math.Min(pageSize, MaxPageSize) (:169), and on success serializes PaginationMetadata into the X-Pagination response header (:188) rather than mixing it into the body, [Rubric §9] again, and [Rubric §12, Performance & Scalability] for the clamp.
        • +
        • ExportAsync (:257, [HttpGet("export")] at :252) is covered as its own concept below.
        • +
        • GetAllForLookupAsync (:320, [HttpGet("lookup")] at :317): returns CollectionResult<BaseLookup<TIdentifierType>>, a lightweight id/label pair, with a [Required] nameProperty (:321) choosing the label.
        • +
        • GetByIdAsync (:361, [HttpGet("{id}")] at :356): includeFKs defaults to true for the single-entity case (:363), and on success it calls SetConcurrencyETag(result.Value) before returning (:382-383).
        • +
        • HandleFailure override (:451-465): logs the first error at Warning, guarded by Logger.IsEnabled (:454), before delegating to ApiControllerBase.HandleFailure, so the read path gets observability ([Rubric §13, Observability & Operability]) without changing the response mapping. Note it logs the first error while the base ranks the status by the most severe one: the log line is a breadcrumb, not the status decision.
        • +
        • Every read action passes asTracking: false to the query service (for example :125), because a read endpoint never mutates what it loaded.
        • +
        +
      • +
      • Concept introduced: one row-scoping hook for every read the controller serves. [Rubric §11, Security] assesses whether an authorization decision can be reproduced consistently across every path that returns the same rows; [Rubric §15, Best Practices & Code Quality] assesses whether that decision lives in one place. GetReadSpecificationAsync(cancellationToken) (:571-573) returns the Specification<TEntity, TIdentifierType> applied by all five read actions: both GetAllAsync overloads, GetAllForLookupAsync, GetByIdAsync, and ExportAsync (each resolves it at :116, :171, :324, :368, and :270). Four properties are worth internalizing, all stated on the hook's own doc (:535-570).
          +
        • It is asynchronous because row scoping usually is (:547-552). Scope is rarely a pure function of the current principal: it comes from a query handler, a claim lookup that hits a store, a tenancy read. A synchronous hook forced such a controller to override all five actions by hand purely to get an await in before the query. An override with nothing to await returns ValueTask.FromResult(...) and allocates nothing.
        • +
        • It narrows, it never replaces (:555-559). The query service ANDs the specification with the caller's filters dictionary and fields projection, so a caller can only narrow what the specification already allows; a filter naming excluded rows yields an empty page rather than leaking them (:139-142).
        • +
        • A rejected row is a 404, not a 403 (:562-564, restated on GetByIdAsync at :350-355). The specification narrows the query, so the row is simply absent. Answering "forbidden" would confirm the id exists and turn a scoped read into an existence oracle a caller could walk.
        • +
        • The lookup endpoint carries the scope as a predicate (:311-316). GetAllForLookupAsync has no specification parameter, so the scope travels as specification?.Criteria (:328); a null specification passes a null predicate, the unscoped query this endpoint always issued. A dropdown that lists what the list endpoint hides would be an oracle of its own. + GetExportSpecification() (:601) is the synchronous half: it returns null by default and is what GetReadSpecificationAsync returns by default (:573). A controller that can build its scope without awaiting overrides this one and gets all five actions scoped; a controller that needs an await overrides the async hook instead, and then this one is no longer consulted (:583-587). For the four JSON reads a null specification still means unscoped; for the export it means refused (next concept), so a row-scoped controller that overrides neither hook gets a 403 on /export rather than handing every caller the whole table (:590-593). See ADR-033 for the ownership model this hook reproduces in the query.
        • +
        +
      • +
      • Concept introduced: a bulk read that fails closed when no scope was declared. [Rubric §11, Security] assesses whether the default on a sensitive path is the safe one. ExportAsync is the one read that can hand a caller a whole table in one request, so it does not treat a null specification as "unscoped": when the hook resolves to null and AllowUnscopedExport is false, it returns before any query runs (:273-274) with the Error.Forbidden built by UnscopedExportRefused (:608-613), which HandleFailure turns into a 403 Problem Details carrying the code ExportRowScopeRequiredErrorCode = "Export.RowScopeRequired" (:487; the 403 is declared on the action at :255). The error message names the three ways out: override GetReadSpecificationAsync, override GetExportSpecification, or opt in.
          +
        • AllowUnscopedExport (protected virtual bool, default false, :508) is the deliberate opt-in. Its doc (:489-507) limits it to controllers whose rows every caller allowed to reach the endpoint may see (reference data, a catalog, an admin-only table already behind a role gate), and notes it is read per request, so it may depend on the principal: return null from the read hook for an administrator and a specification for everyone else, and opt in only for the administrator.
        • +
        • A non-null specification never consults the property (:504-505): a scoped export needs no opt-in. The effect is that a controller which forgot to scope its export serves no rows rather than every row (:231-238).
        • +
        +
      • +
      • Concept introduced: handing the client a precondition token on the way out. [Rubric §8, Data Architecture] and [Rubric §9, API & Contract Design]. SetConcurrencyETag(object? dto) (:417) emits the read's concurrency token as a weak ETag so a client can hand it straight back as an If-Match precondition on the next write (see SupportsIfMatchAttribute) instead of round-tripping it through the request body. Three details make it cheap and honest.
          +
        • RowVersionProperty (:391-395) is resolved once per closed controller type because a DTO's shape cannot change at runtime: the first public instance byte[] property named exactly RowVersion. For a DTO with no concurrency token it is null, which makes the whole method a no-op with no per-request reflection at all (:419-420).
        • +
        • ReadRowVersion (:434) handles both shapes a served row can take: a typed DTO, read through the cached PropertyInfo (:436-437), or a shaped dictionary keyed by JSON names when the caller asked for a field projection, probed under both rowVersion and RowVersion (:439-442).
        • +
        • A DTO without a token gets no header at all (:422-423). The remark says why (:406-408): absent is the correct answer for a resource that has no version to condition on, and a fabricated tag would invite preconditions the write side cannot honour. The method is protected rather than private (:411-414) so a derived controller serving a row from a custom read action emits the identical header instead of re-implementing the reflection and the base64 format.
        • +
        +
      • +
      • Concept introduced: streaming a bulk extract from a paged read. [Rubric §12, Performance & Scalability] assesses whether a large response is bounded and whether memory grows with the result set; [Rubric §11, Security] covers who may pull a whole table in one request. ExportAsync answers the "export what you filtered" request without any new query path (ADR-078). The action itself is a thin adapter over EntityCsvExporter<TEntityDTO>, which owns the page loop, the column resolution, and the CSV format; this controller keeps only the HTTP concerns.
          +
        • A dedicated route, not content negotiation ([HttpGet("export")], :252). The remarks name the two behaviors that make an Accept: text/csv formatter wrong here (:199-204): the public output-cache policy varies by query string but not by Accept, so a cached JSON body could be replayed to a CSV request, and AddAPI sets ReturnHttpNotAcceptable = false (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:49), so a negotiation miss falls back to JSON silently instead of returning 406. A distinct path has neither failure mode.
        • +
        • Field validation before a byte moves (:265-267): EntityCsvExporter<TEntityDTO>.ValidateFields rejects a fields= request naming a property the CSV cannot render, as an Error.InvalidEntityField failure, rather than quietly dropping the column.
        • +
        • Row scoping through the shared hook, fail-closed: the specification is resolved once, before the first page (:270), and refused when null without the opt-in (:273-274, previous concept), so the same closed-over instance filters every page the fetch delegate reads, and it is the same hook the list endpoints read, so an export cannot drift wider than the list it mirrors.
        • +
        • The page loop lives in the exporter: ExportAsync builds a fetch delegate that calls QueryService.GetAllAsync (:281-292) at pageSize = Math.Max(1, MaxPageSize) (:277) and hands it, beginResponse, and onFailureAfterStart to EntityCsvExporter<TEntityDTO>.WriteAsync (:279); the loop, its termination conditions, and the truncation bookkeeping are documented on that type.
        • +
        • Headers first, then body (BeginExportResponse, :619): content type text/csv; charset=utf-8 (CsvContentType, :475, set at :623), a Content-Disposition attachment whose file name is {controller}-{yyyyMMdd'T'HHmmss'Z'}.csv built invariantly from an injected TimeProvider (:621, :624, BuildExportFileName at :635-638, prefix ExportFileNamePrefix at :520-533), and X-Export-Row-Limit (ExportRowLimitHeaderName, :481, appended at :625) so a client can tell "exactly at the limit" from "coincidentally that many rows".
        • +
        • Truncation rides in the body. Headers freeze the moment the first body byte flushes, so a truncated export ends with a # export truncated at N rows record (marker built by EntityCsvExporter<TEntityDTO>.TruncationMarker) and a mid-stream query failure ends with an incomplete marker plus a Warning log (LogExportPageFailure, :646-658, wired as onFailureAfterStart at :297). A failure on the FIRST page, before anything was written, still returns Problem Details through HandleFailure (:301), which is exactly what "constructing the writer sends nothing" protects on the exporter side.
        • Column resolution is derived, not invented: EntityCsvExporter<TEntityDTO>.ResolveColumns takes the shaped keys of the first row, so the CSV columns for a given fields= request are the same camelCase JSON names the JSON endpoints emit; an empty result still gets a header row built from the DTO's own properties in declaration order. UnexportablePropertyNames on that type drops the properties that cannot render a faithful scalar cell, byte[]/ReadOnlyMemory<byte> concurrency tokens and every collection type except string, computed once per closed DTO type. Value objects and other class-typed properties are deliberately NOT dropped, because a record or value object has a meaningful invariant ToString. See EntityCsvExporter<TEntityDTO> for the full walkthrough.
      • -
      • Why it's built this way: the controller stays thin. All filtering/sorting/paging lives in IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, and manual DTO mapping (ADR-001) keeps entities off the wire. The controller only translates HTTP concerns: query strings, headers, status codes. The export reuses the paged read wholesale precisely so it cannot disagree with the grid it was launched from, and the row ceiling (DefaultMaxExportRows = 100_000, :526, matching ApplicationSettings.MaxExportRows's own default) is a number an operator can reason about rather than an unbounded connection hold.
      • -
      • Caveats / not-in-source: the export carries no attributes of its own. It inherits whatever [Authorize] or [FeatureGate] the derived controller declares, so a controller that exposes paged anonymously exposes export anonymously; the symmetry is deliberate and documented (:205-211). The export is also not output-cached.
      • -
      • Where it's used: the base for every read-only module controller, for example ADC's child-collection controllers SessionSpeakersController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionSpeakersController.cs:56) and CategoryItemsController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:70). Extended by AggregateRootEntityControllerBase<TEntity, TEntityDTO, TIdentifierType, TCreateRequest> for entities that also create and delete. The scoping hooks are widely overridden today: the async GetReadSpecificationAsync by ADC's SpeakersController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:95), SessionsController (SessionsController.cs:75), RoomsController (RoomsController.cs:120) and their child controllers, and by Store's owner-scoped ShoppingCartsController (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/ShoppingCartsController.cs:206); the synchronous GetExportSpecification by ADC's EventsController (EventsController.cs:69) and SessionQuestionAnswersController (SessionQuestionAnswersController.cs:107) and by Store's OrdersController (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/OrdersController.cs:244). Store's CustomersController deliberately overrides neither and records why (its list endpoints are Admin-only rather than row-scoped, so there is no ownership specification to reproduce, MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.API/Controllers/CustomersController.cs:86-100). Framework coverage lives in EntityControllerBaseTests, EntityControllerBaseExportTests, EntityControllerBaseETagTests, and EntityControllerBaseReadSpecificationTests (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Controllers/), the last of which drives both hooks through purpose-built doubles (EntityControllerBaseReadSpecificationTests.cs:356 and :377).
      • +
      • Why it's built this way: the controller stays thin. All filtering/sorting/paging lives in IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, and manual DTO mapping (ADR-001) keeps entities off the wire. The controller only translates HTTP concerns: query strings, headers, status codes. The export reuses the paged read wholesale precisely so it cannot disagree with the grid it was launched from, and the row ceiling (DefaultMaxExportRows = 100_000, :472, matching ApplicationSettings.MaxExportRows's own default) is a number an operator can reason about rather than an unbounded connection hold. The fail-closed default puts the cost of a forgotten scope on the controller author (a 403 in their own tests) instead of on the data.
      • +
      • Caveats / not-in-source: the export carries no authorization attributes of its own. It inherits whatever [Authorize] or [FeatureGate] the derived controller declares, so a controller that exposes paged anonymously exposes export anonymously once it scopes the rows or opts in; the symmetry is deliberate and documented (:207-211). The fail-closed rule applies to ExportAsync only: the four JSON reads still run unscoped on a null specification. The export is also not output-cached.
      • +
      • Where it's used: the base for every read-only module controller, for example ADC's child-collection controllers SessionSpeakersController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionSpeakersController.cs:56) and CategoryItemsController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:70). Extended by AggregateRootEntityControllerBase<TEntity, TEntityDTO, TIdentifierType, TCreateRequest> for entities that also create and delete. The scoping hooks are widely overridden: the async GetReadSpecificationAsync by ADC's SpeakersController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:96), SessionsController (SessionsController.cs:75), RoomsController (RoomsController.cs:120) and their child controllers, and by Store's owner-scoped ShoppingCartsController (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/ShoppingCartsController.cs:200); the synchronous GetExportSpecification by ADC's EventsController (EventsController.cs:69) and SessionQuestionAnswersController (SessionQuestionAnswersController.cs:107) and by Store's OrdersController (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/OrdersController.cs:241). AllowUnscopedExport is overridden two ways. Unconditional true on reference and catalog data: ADC's ConferenceCategoriesController (ConferenceCategoriesController.cs:47), QuestionsController (QuestionsController.cs:46) and CategoryItemsController (CategoryItemsController.cs:73), and Store's ProductsController (MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.API/Controllers/ProductsController.cs:51), CategoriesController (CategoriesController.cs:48), InventoryItemsController (InventoryItemsController.cs:43) and CustomersController (MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.API/Controllers/CustomersController.cs:48), whose export sits behind CustomersManage and whose list endpoints are Admin-only rather than row-scoped (:90-104). Per principal, where the read hook returns null to a privileged caller: ADC's SpeakersController (SpeakersController.cs:106), SessionsController (SessionsController.cs:93), RoomsController (RoomsController.cs:138), EventsController (EventsController.cs:77), SessionQuestionAnswersController (SessionQuestionAnswersController.cs:119) and their siblings, and Store's OrdersController (OrdersController.cs:249) and ShoppingCartsController (ShoppingCartsController.cs:209). Framework coverage lives in EntityControllerBaseTests, EntityControllerBaseExportTests (opt-in doubles at EntityControllerBaseExportTests.cs:534 and :652), EntityControllerBaseETagTests, and EntityControllerBaseReadSpecificationTests (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Controllers/), the last of which drives both hooks through purpose-built doubles (EntityControllerBaseReadSpecificationTests.cs:358 and :377).

      OAuthControllerBase

      @@ -1326,22 +1332,22 @@

      OAuthControllerBase

      • What it is: the base controller for external OAuth2 sign-in (Google, GitHub, Apple). It runs the challenge/callback/complete/exchange dance so a browser or native head can log in through a provider and receive a local JWT pair without ever exposing tokens in a redirect URL.
      • -
      • Depends on: IAuthenticationService (ExternalLoginAsync), ICacheService, IConfiguration, ExternalAuthExtensions (the scheme constant, OAuthControllerBase.cs:42), NonIdempotentAttribute, AuthenticationResponse, OAuthCodeExchangeRequest, and Error; the Google, GitHub, and Apple OAuth packages and System.Security.Cryptography.
      • -
      • Concept introduced: the code-exchange OAuth completion pattern. [Rubric §11, Security] assesses how credentials move through the system; the design's whole point is that the redirect after a successful provider login carries only a single-use opaque code, never the access/refresh tokens, so tokens never land in the address bar, browser history, the Referer header, or upstream access logs (OAuthControllerBase.cs:286-288). [Rubric §7, Microservices Readiness]: the base is hoisted from the app hosts so every service reuses the identical flow, with the sealed subclass supplying only [Route("auth/oauth")] and versioning. See ADR-036.
      • +
      • Depends on: IAuthenticationService (ExternalLoginAsync), ICacheService (SetAsync, GetFromSharedStoreAsync, RemoveAsync), IConfiguration, ExternalAuthExtensions (the scheme constant, OAuthControllerBase.cs:40), NonIdempotentAttribute, AuthenticationResponse, OAuthCodeExchangeRequest, and Error; the Google, GitHub, and Apple OAuth packages and System.Security.Cryptography.
      • +
      • Concept introduced: the code-exchange OAuth completion pattern. [Rubric §11, Security] assesses how credentials move through the system; the design's whole point is that the redirect after a successful provider login carries only a single-use opaque code, never the access/refresh tokens, so tokens never land in the address bar, browser history, the Referer header, or upstream access logs (OAuthControllerBase.cs:138-140). [Rubric §7, Microservices Readiness]: the base is hoisted from the app hosts so every service reuses the identical flow, with the sealed subclass supplying only [Route("auth/oauth")] and versioning, and the burn check reads the shared cache store so the single-use guarantee holds across replicas. See ADR-036.
      • Walkthrough
          -
        • OAuthExchangeCodePrefix and a 2-minute OAuthExchangeCodeLifetime (OAuthControllerBase.cs:193-198) namespace and time-box the server-side token stash; the short TTL matches the single redirect-then-POST round trip. A second constant, ClientStateItemKey = "clientState" (:197), names a separate AuthenticationProperties.Items slot for the client's own opaque per-attempt value, deliberately not called "state" because that name belongs to the OAuth handler's own protocol value (:195-196).
        • -
        • GoogleLogin (:207), GitHubLogin (:217), and AppleLogin (:230) each now take an optional state query parameter alongside returnUrl and carry [AllowAnonymous] directly (:206, :216, :229); all three call ChallengeProvider (:449), which stashes both returnUrl and the client's state under ClientStateItemKey in AuthenticationProperties.Items and sets RedirectUri = "/auth/oauth/complete" (:451-463). Apple is the newest of the three and needs no special action here: its callback arrives as a cross-site form POST (response_mode=form_post is forced by the name/email scopes) that the middleware handles at /auth/callback/apple like any other provider (:221-224).
        • -
        • CompleteAsync (:248) also carries [AllowAnonymous] now (:246). After the middleware handles the provider callback, it reads the external cookie (:251), redirects to /login?error=oauth_failed when the ticket did not survive (:253-258), reads both the stashed returnUrl and clientState via ReadChallengeState (:263, :299-300), extracts provider claims (ExtractClaims, :266), calls ExternalLoginAsync to find or create the local user and mint tokens (:273-274), signs out the temporary external cookie (:284), then mints a 32-byte hex exchangeCode (:289), stashes the token pair in the cache under it (:290-291), and redirects with the code and, when the client sent one, the echoed state (:293, URL built by BuildSuccessRedirectUrl at :302-316).
        • -
        • ReadChallengeState (:299-300) is a safe-lookup helper (GetString is TryGetValue under the hood): the challenge normally stashes both returnUrl and clientState, but a ticket minted without them (custom challenge, provider round-trip edge) falls back to "/" and null instead of throwing on the Items indexer.
        • -
        • BuildSuccessRedirectUrl (:302-316) appends &state= (URL-escaped) only when clientState is non-empty (:309-311), for both the web redirect and the native AppendQuery branch (:313-315), so callers that never sent a state see no change to the redirect shape.
        • -
        • Name handling is defensive: ExtractName (:372) prefers GivenName/Surname claims and otherwise splits the Name claim, falling back to ("User", "") when there is no usable space-separated name (:386-400), so a provider that returns only a display name still yields a creatable local account. That fallback matters most for Apple, which returns name claims on the first authorization only.
        • -
        • Native heads (ADR-043): GetAllowedMobileReturnUrl (:424) returns the stashed returnUrl as the redirect target only when it is an absolute URI whose custom scheme is listed in OAuth:AllowedReturnUrlSchemes; http/https never match (:426-428), so the allowlist cannot become an open redirect, and a missing or empty section (or a test double returning null from GetSection) means "no allowlist", the exact pre-ADR-043 behavior (:433-437). Failures route to the same surface through RedirectError (:412-415), so a native window closes on an error instead of stranding on a web login page.
        • -
        • ExchangeAsync (:329): [HttpPost("exchange")] with [NonIdempotent(...)] and [AllowAnonymous] (:326-327); the UI swaps the code for the real AuthenticationResponse out-of-band. Because that response is a struct, a cache miss yields a default value rather than null, so the miss is detected via an empty AccessToken (:340-346). The code is then removed (:349), making it single-use so a leaked or replayed code cannot mint a second token pair. Both failure paths return the same opaque 400 "Invalid sign-in code" (InvalidCode, :354-360).
        • +
        • OAuthExchangeCodePrefix and a 2-minute OAuthExchangeCodeLifetime (OAuthControllerBase.cs:45-50) namespace and time-box the server-side token stash; the short TTL matches the single redirect-then-POST round trip. A second constant, ClientStateItemKey = "clientState" (:49), names a separate AuthenticationProperties.Items slot for the client's own opaque per-attempt value, deliberately not called "state" because that name belongs to the OAuth handler's own protocol value (:47-48).
        • +
        • GoogleLogin (:59), GitHubLogin (:69), and AppleLogin (:82) each take an optional state query parameter alongside returnUrl and carry [AllowAnonymous] directly (:58, :68, :81); all three call ChallengeProvider (:303), which stashes both returnUrl and the client's state under ClientStateItemKey in AuthenticationProperties.Items and sets RedirectUri = "/auth/oauth/complete" (:305-317). Apple needs no special action here: its callback arrives as a cross-site form POST (response_mode=form_post is forced by the name/email scopes) that the middleware handles at /auth/callback/apple like any other provider (:73-76).
        • +
        • CompleteAsync (:100) carries [AllowAnonymous] (:98). After the middleware handles the provider callback, it reads the external cookie (:103), redirects to /login?error=oauth_failed when the ticket did not survive (:105-110), reads both the stashed returnUrl and clientState via ReadChallengeState (:115, :151-152), extracts provider claims (ExtractClaims, :118), calls ExternalLoginAsync to find or create the local user and mint tokens (:125-126), signs out the temporary external cookie (:136), then mints a 32-byte hex exchangeCode (:141), stashes the token pair in the cache under it (:142-143), and redirects with the code and, when the client sent one, the echoed state (:145, URL built by BuildSuccessRedirectUrl at :154-168).
        • +
        • ReadChallengeState (:151-152) is a safe-lookup helper (GetString is TryGetValue under the hood): the challenge normally stashes both returnUrl and clientState, but a ticket minted without them (custom challenge, provider round-trip edge) falls back to "/" and null instead of throwing on the Items indexer.
        • +
        • BuildSuccessRedirectUrl (:154-168) appends &state= (URL-escaped) only when clientState is non-empty (:161-163), for both the web redirect and the native AppendQuery branch (:165-167), so callers that never sent a state see no change to the redirect shape.
        • +
        • Name handling is defensive: ExtractName (:226) prefers GivenName/Surname claims and otherwise splits the Name claim through SplitFullName, falling back to ("User", "") when there is no usable space-separated name (:240-251), so a provider that returns only a display name still yields a creatable local account. That fallback matters most for Apple, which returns name claims on the first authorization only.
        • +
        • Native heads (ADR-043): GetAllowedMobileReturnUrl (:278) returns the stashed returnUrl as the redirect target only when it is an absolute URI whose custom scheme is listed in OAuth:AllowedReturnUrlSchemes; http/https never match (:280-282), so the allowlist cannot become an open redirect, and a missing or empty section (or a test double returning null from GetSection) means "no allowlist", the exact pre-ADR-043 behavior (:287-290). Failures route to the same surface through RedirectError (:266-269), so a native window closes on an error instead of stranding on a web login page.
        • +
        • ExchangeAsync (:181): [HttpPost("exchange")] with [NonIdempotent(...)] and [AllowAnonymous] (:177-179); the UI swaps the code for the real AuthenticationResponse out-of-band. The lookup goes through ICacheService.GetFromSharedStoreAsync (:196) rather than the ordinary read: a code already burned on another replica must be a miss here, not a stale local copy that would mint a second token pair (:194-195). Because the response is a struct, a cache miss yields a default value rather than null, so the miss is detected via an empty AccessToken (:197-200). The code is then removed (:203), making it single-use so a leaked or replayed code cannot mint a second token pair. Both failure paths return the same opaque 400 "Invalid sign-in code" (InvalidCode, :208-214).
      • -
      • Why it's built this way: carrying tokens in a redirect is the classic OAuth token-leak vector; the single-use code plus a short-lived server-side stash closes it while keeping the client flow a plain redirect and one POST. The [NonIdempotent] justification on ExchangeAsync (:326) records why this one endpoint must stay outside the replay contract: replaying the stored response would defeat the burn, letting a leaked code mint the same tokens again for the whole retention window. The AppendQuery helper (:440) deliberately uses OriginalString rather than ToString(), because Uri normalization appends a trailing slash to authority-only URIs (e.g. myapp://oauth-complete) and native authenticator callback matching can be exact (:442-444). The client-supplied state is carried opaquely: the server never interprets or trusts it, only stores and echoes it back so the client can bind the completion to the flow it started (:458-461).
      • -
      • Caveats / not-in-source: the provider scheme registration and the concrete ExternalLoginAsync implementation live outside this base (ExternalAuthExtensions and the app's IAuthenticationService); this file assumes both are wired. The complete and exchange actions are hidden from OpenAPI with [ApiExplorerSettings(IgnoreApi = true)] (:247, :328) because they are browser redirects, not a documented client contract.
      • -
      • Where it's used: subclassed by each app's sealed OAuth controller, today ADC's OAuthController (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/OAuthController.cs:20), which adds only the class-level routing and versioning attributes. ExchangeAsync is one of the endpoints the framework's anonymous-endpoint architecture gate lists by name (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Api/AnonymousEndpointTests.cs:31), so its [AllowAnonymous] is an approved exception rather than an oversight. Framework coverage is OAuthControllerBaseTests (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Controllers/OAuthControllerBaseTests.cs).
      • +
      • Why it's built this way: carrying tokens in a redirect is the classic OAuth token-leak vector; the single-use code plus a short-lived server-side stash closes it while keeping the client flow a plain redirect and one POST. Reading the stash from the shared store keeps "single-use" true when the API runs on more than one replica, where a per-node copy could otherwise outlive the burn. The [NonIdempotent] justification on ExchangeAsync (:178) records why this one endpoint must stay outside the replay contract: replaying the stored response would defeat the burn, letting a leaked code mint the same tokens again for the whole retention window. The AppendQuery helper (:294) deliberately uses OriginalString rather than ToString(), because Uri normalization appends a trailing slash to authority-only URIs (e.g. myapp://oauth-complete) and native authenticator callback matching can be exact (:296-298). The client-supplied state is carried opaquely: the server never interprets or trusts it, only stores and echoes it back so the client can bind the completion to the flow it started (:312-314).
      • +
      • Caveats / not-in-source: the provider scheme registration and the concrete ExternalLoginAsync implementation live outside this base (ExternalAuthExtensions and the app's IAuthenticationService); this file assumes both are wired. How GetFromSharedStoreAsync bypasses a local tier is the cache implementation's concern (ICacheService), not this file's. The complete and exchange actions are hidden from OpenAPI with [ApiExplorerSettings(IgnoreApi = true)] (:99, :180) because they are browser redirects, not a documented client contract.
      • +
      • Where it's used: subclassed by each app's sealed OAuth controller, today ADC's OAuthController (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/OAuthController.cs:20), which adds only the class-level routing and versioning attributes. ExchangeAsync is one of the endpoints the framework's anonymous-endpoint architecture gate lists by name (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Api/AnonymousEndpointTests.cs:31), so its [AllowAnonymous] is an approved exception rather than an oversight. Framework coverage is OAuthControllerBaseTests (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Controllers/Auth/OAuthControllerBaseTests.cs), and ADC exercises the exchange end to end in OAuthExchangeTests (MMCA.ADC/Tests/Integration/MMCA.ADC.Identity.IntegrationTests/Auth/OAuthExchangeTests.cs).

      AggregateRootEntityControllerBase<TEntity, TEntityDTO, TIdentifierType, TCreateRequest>

      @@ -1350,7 +1356,7 @@

      EntityControllerBase<TEntity, TEntityDTO, TIdentifierType> (the read endpoints) by adding a CreateAsync (POST) and a DeleteAsync (DELETE) for aggregate-root entities.
    906. Depends on: EntityControllerBase<TEntity, TEntityDTO, TIdentifierType> (base), IAggregateRootEntityControllerBase<TEntityDTO, TIdentifierType, TCreateRequest> (implements), ICommandHandler<in TCommand, TResult> (create and delete handlers), DeleteEntityCommand<TEntity, TIdentifierType>, AuditableAggregateRootEntity<TIdentifierType> (constraint), ICreateRequest (constraint), IdempotentAttribute; ASP.NET Core MVC and Asp.Versioning.
    907. -
    908. Concept introduced: idempotent creation guarded at the endpoint. [Rubric §9, API & Contract Design] assesses safe mutation; CreateAsync carries [Idempotent] (AggregateRootEntityControllerBase.cs:60), which wires IdempotencyFilter so a retried POST carrying the same Idempotency-Key replays the original 201 (flagged X-Idempotent-Replay: true, IdempotencyFilter.cs:40) instead of creating a duplicate aggregate, exactly what mobile and flaky-network clients need. A duplicate that arrives while the first request is still running and cannot take the lock within the 5-second LockWait (IdempotencyFilter.cs:104, awaited at IdempotencyFilter.cs:250) is answered with 409 Conflict rather than a replay (IdempotencyFilter.cs:304-309). Because the fitness rule reads attributes with inherit: true, every concrete controller that inherits this action satisfies its POST idempotency-intent obligation through this base (see NonIdempotentAttribute). [Rubric §1, SOLID]: the four constraints (AggregateRootEntityControllerBase.cs:41-44, notably TEntity : AuditableAggregateRootEntity<TIdentifierType>) enforce at compile time that only aggregate roots reach this create/delete surface.
    909. +
    910. Concept introduced: idempotent creation guarded at the endpoint. [Rubric §9, API & Contract Design] assesses safe mutation; CreateAsync carries [Idempotent] (AggregateRootEntityControllerBase.cs:60), which wires IdempotencyFilter so a retried POST carrying the same Idempotency-Key replays the original 201 (flagged X-Idempotent-Replay: true, IdempotencyFilter.cs:41) instead of creating a duplicate aggregate, exactly what mobile and flaky-network clients need. A duplicate that arrives while the first request is still running and cannot take the lock within the 5-second LockWait (IdempotencyFilter.cs:105, awaited at IdempotencyFilter.cs:251) is answered with 409 Conflict rather than a replay (IdempotencyFilter.cs:305-310). Because the fitness rule reads attributes with inherit: true, every concrete controller that inherits this action satisfies its POST idempotency-intent obligation through this base (see NonIdempotentAttribute). [Rubric §1, SOLID]: the four constraints (AggregateRootEntityControllerBase.cs:41-44, notably TEntity : AuditableAggregateRootEntity<TIdentifierType>) enforce at compile time that only aggregate roots reach this create/delete surface.
    911. Walkthrough
      • Primary constructor (AggregateRootEntityControllerBase.cs:28-39): four parameters, where queryService and logger are forwarded to the EntityControllerBase<TEntity, TEntityDTO, TIdentifierType> base (:38), plus createHandler and deleteHandler. The logger is typed ILogger<EntityControllerBase<...>>, not of this class, because ILogger<T> is not covariant and the base ctor requires that exact type; the #pragma warning disable S6672 (:35-37) is a justified, narrowly-scoped suppression documenting exactly that ([Rubric §15, Best Practices]).
      • CreateHandler property (:48): protected, so a derived controller that overrides CreateAsync to build a more specific command can still reach the handler. deleteHandler stays a captured constructor parameter, used directly at :93, because nothing overrides delete today.
      • @@ -1386,23 +1392,22 @@

        AuthControllerBase

      • What it is: the abstract base for password-based authentication and session management: login, register, refresh, revoke-everywhere, list-my-devices, and revoke-one-device. A downstream module (Identity) inherits it and adds the route prefix, version attribute, and any module-specific endpoints.
      • -
      • Depends on: ApiControllerBase (base), IAuthenticationService and ICurrentUserService (injected), LoginRequest, RegisterRequest, RefreshTokenRequest, AuthenticationResponse, RefreshSessionSummaryResponse, ClaimsPrincipalExtensions (FindSessionId), IdempotentAttribute and NonIdempotentAttribute, and the RateLimitPolicyAuthIp constant on WebApplicationBuilderExtensions; Microsoft.AspNetCore.RateLimiting for [EnableRateLimiting].
      • -
      • Concept introduced: a secure-by-default base, not just a shared one. [Rubric §9, API & Contract Design] assesses uniform endpoint conventions and [Rubric §1, SOLID] the Open/Closed angle (the base provides virtual endpoints, a derived controller overrides only what differs), but the load-bearing idea here is [Rubric §11, Security]: anti-spray throttling is on by default. LoginAsync and RegisterAsync carry [EnableRateLimiting(WebApplicationBuilderExtensions.RateLimitPolicyAuthIp)] (AuthControllerBase.cs:72 and :94), so any consumer inheriting this base gets per-IP protection without opting in. The class doc (AuthControllerBase.cs:20-29) records why: the earlier arrangement shipped the policy in the framework and left each app to attach it, and an app that simply inherited these actions silently had no spray protection at all, because the global limiter deliberately no-ops for anonymous traffic and account lockout is per-email (ADR-019, ADR-029). Every action otherwise shares the same Result-to-ActionResult shape: call the service, check result.IsFailure, return HandleFailure(result.Errors) or the success result. None carries business logic; they are thin HTTP adapters over IAuthenticationService.
      • -
      • Concept introduced: every POST states its idempotency intent. This base is the framework's worked example of the NonIdempotentAttribute rule, and reading its actions together is the fastest way to internalize the distinction. RegisterAsync is [Idempotent] (:92): a retried registration should replay the original 201 rather than fail on a duplicate email. The others carry [NonIdempotent("...")] with a written harm: login issues a token pair, so a replayed response would hand a retrying client the tokens minted for an earlier call and extend the lifetime of credentials the caller may already have discarded (:68); refresh rotates the refresh token, so a replay would return a token the rotation has already invalidated and hand the client dead credentials (:116); and revocation must reach the store on every call, since a replayed 204 would report success for a revoke that never ran and leave a refresh token live (:143) or a device signed in (:206) after the user asked otherwise.
      • +
      • Depends on: ApiControllerBase (base), IAuthenticationService and ICurrentUserService (injected), LoginRequest, RegisterRequest, RefreshTokenRequest, AuthenticationResponse, RefreshSessionSummaryResponse, ClaimsPrincipalExtensions (FindSessionId), NonIdempotentAttribute, and the RateLimitPolicyAuthIp constant on WebApplicationBuilderExtensions; Microsoft.AspNetCore.RateLimiting for [EnableRateLimiting].
      • +
      • Concept introduced: a secure-by-default base, not just a shared one. [Rubric §9, API & Contract Design] assesses uniform endpoint conventions and [Rubric §1, SOLID] the Open/Closed angle (the base provides virtual endpoints, a derived controller overrides only what differs), but the load-bearing idea here is [Rubric §11, Security]: anti-spray throttling is on by default. LoginAsync and RegisterAsync carry [EnableRateLimiting(WebApplicationBuilderExtensions.RateLimitPolicyAuthIp)] (AuthControllerBase.cs:72 and :96), so any consumer inheriting this base gets per-IP protection without opting in. The class doc (AuthControllerBase.cs:21-28) records why: the earlier arrangement shipped the policy in the framework and left each app to attach it, and an app that simply inherited these actions silently had no spray protection at all, because the global limiter deliberately no-ops for anonymous traffic and account lockout is per-email (ADR-019, ADR-029). Every action otherwise shares the same Result-to-ActionResult shape: call the service, check result.IsFailure, return HandleFailure(result.Errors) or the success result. None carries business logic; they are thin HTTP adapters over IAuthenticationService.
      • +
      • Concept introduced: every POST states its idempotency intent, and here every one opts out. This base is the framework's worked example of the NonIdempotentAttribute rule: all five POSTs carry [NonIdempotent("...")] with a written harm, because each one either mints credentials or must reach the store. Login issues a token pair, so a replayed response would hand a retrying client the tokens minted for an earlier call and extend the lifetime of credentials the caller may already have discarded (:70). Register issues a token pair too, so a replay would hand back tokens minted for an earlier call and keep serving them after the session was revoked; a retried registration instead hits the email-exists 409, which the justification names as the correct answer (:94, the 409 declared at :100). Refresh rotates the refresh token, so a replay would return a token the rotation has already invalidated and hand the client dead credentials (:118). Revocation must reach the store on every call, since a replayed 204 would report success for a revoke that never ran and leave a refresh token live (:145) or a device signed in (:208) after the user asked otherwise.
      • Walkthrough
          -
        • Primary constructor (:41-43) exposes AuthenticationService and CurrentUserService as protected properties (:46 and :49) so derived controllers can reach them for extra endpoints.
        • -
        • ClientIpAddress (:56) and ClientUserAgent (:62) are the two request facts every write action forwards to the service. The IP is recorded on the refresh session and drives the registration rate limit, and behind a proxy it is the forwarded value only when the host configured UseForwardedHeaders (:51-55). The user agent is purely informational, so a device list can name the session a user is looking at; nothing validates against it (:58-61).
        • -
        • LoginAsync (:74): [HttpPost("login")], [NonIdempotent], [AllowAnonymous], throttled per IP (:67-70); calls LoginAsync(request, ClientIpAddress, ClientUserAgent, ...) (:78-80) and returns Ok or HandleFailure. The [ProducesResponseType] attributes (:71-73) feed the OpenAPI contract and include the 429 the limiter can produce.
        • -
        • RegisterAsync (:99): [HttpPost("register")], [Idempotent], anonymous and throttled (:91-94); returns StatusCode(StatusCodes.Status201Created, ...) (:109), correctly 201 Created for a new account rather than 200. It is virtual so a module can override it to inject extra context (:88-89).
        • -
        • RefreshAsync (:120): [AllowAnonymous] (:117), since exchanging an expired token pair is pre-authentication, and deliberately not throttled (:28-34): refresh is automatic and periodic rather than user-initiated, Blazor Server circuits issue it server-side so every Server-circuit user shares the UI host's IP, and refresh tokens are high-entropy, so brute force is not the threat password spraying is.
        • -
        • RevokeAsync (:147): [Authorize] (:144); reads CurrentUserService.UserId, returns Unauthorized() if null (:149-151) as a defensive guard even though [Authorize] should already prevent a null id, then calls RevokeAllSessionsAsync and returns NoContent() (:153-159). The endpoint carries no body, so it cannot name the device it is called from: it signs the user out everywhere, which is what a caller with no way to identify its own session should get (:135-140).
        • -
        • GetMySessionsAsync (:177): [HttpGet("my-sessions")] plus [Authorize] (:173-174), one row per live refresh session, newest first. The "this is the device you are on" flag comes from the access token's own sid claim, read with User.FindSessionId() (:185), so no client state is involved and nothing has to send a refresh token to a read endpoint; a token minted before sid shipped simply flags no row (:166-170).
        • -
        • RevokeSessionAsync (:211): [HttpPost("revoke/{sessionId:guid}")] (:205), the per-device counterpart. The session is named in the route rather than by its refresh token, so a client can sign out a device it does not hold the token for, which is the whole point of a device list. Ownership is enforced in the store query, so another account's session id answers 404 exactly as a nonexistent one does, and revoking an already-revoked session answers 204 because a device list is where duplicate clicks come from (:196-202).
        • +
        • Primary constructor (:43-45) exposes AuthenticationService and CurrentUserService as protected properties (:48 and :51) so derived controllers can reach them for extra endpoints.
        • +
        • ClientIpAddress (:58) and ClientUserAgent (:64) are the two request facts every write action forwards to the service. The IP is recorded on the refresh session and drives the registration rate limit, and behind a proxy it is the forwarded value only when the host configured UseForwardedHeaders (:53-57). The user agent is purely informational, so a device list can name the session a user is looking at; nothing validates against it (:60-63).
        • +
        • LoginAsync (:76): [HttpPost("login")], [NonIdempotent], [AllowAnonymous], throttled per IP (:69-72); calls LoginAsync(request, ClientIpAddress, ClientUserAgent, ...) (:80-82) and returns Ok or HandleFailure. The [ProducesResponseType] attributes (:73-75) feed the OpenAPI contract and include the 429 the limiter can produce.
        • +
        • RegisterAsync (:101): [HttpPost("register")], [NonIdempotent], anonymous and throttled (:93-96); returns StatusCode(StatusCodes.Status201Created, ...) (:111), correctly 201 Created for a new account rather than 200. It is virtual so a module can override it to inject extra context (:90-91).
        • +
        • RefreshAsync (:122): [AllowAnonymous] (:119), since exchanging an expired token pair is pre-authentication, and deliberately not throttled (:31-35): refresh is automatic and periodic rather than user-initiated, Blazor Server circuits issue it server-side so every Server-circuit user shares the UI host's IP, and refresh tokens are high-entropy, so brute force is not the threat password spraying is.
        • +
        • RevokeAsync (:149): [Authorize] (:146); reads CurrentUserService.UserId, returns Unauthorized() if null (:151-153) as a defensive guard even though [Authorize] should already prevent a null id, then calls RevokeAllSessionsAsync and returns NoContent() (:155-161). The endpoint carries no body, so it cannot name the device it is called from: it signs the user out everywhere, which is what a caller with no way to identify its own session should get (:137-142).
        • +
        • GetMySessionsAsync (:179): [HttpGet("my-sessions")] plus [Authorize] (:175-176), one row per live refresh session, newest first. The "this is the device you are on" flag comes from the access token's own sid claim, read with User.FindSessionId() (:187), so no client state is involved and nothing has to send a refresh token to a read endpoint; a token minted before sid shipped simply flags no row (:168-172).
        • +
        • RevokeSessionAsync (:213): [HttpPost("revoke/{sessionId:guid}")] (:207), the per-device counterpart. The session is named in the route rather than by its refresh token, so a client can sign out a device it does not hold the token for, which is the whole point of a device list. Ownership is enforced in the store query, so another account's session id answers 404 exactly as a nonexistent one does, and revoking an already-revoked session answers 204 because a device list is where duplicate clicks come from (:198-204).
      • -
      • Why it's built this way: [Rubric §15, Best Practices & Code Quality]: adding a new token flow means changing one base, not N module controllers; keeping the methods virtual (rather than the class open-ended) keeps the override surface intentional. The rate-limit default is deliberately a loud dependency: a consumer that inherits this base without calling AddCommonRateLimiting() (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:326, which registers the "auth-ip" policy at :395-397, constant defined at :23, with an authIpPermitLimit default of 30 requests per minute per IP at :326) fails at startup on an unregistered policy rather than silently serving unthrottled logins (AuthControllerBase.cs:37-41).
      • -
      • Caveats / not-in-source: the per-IP policy partitions on Connection.RemoteIpAddress (WebApplicationBuilderExtensions.RateLimiting.cs:280) and deliberately does not limit when that address is null (in-process TestServer, integration tests): AuthIpRateLimitPartition returns RateLimitPartition.GetNoLimiter("__unknown-ip") in that case (WebApplicationBuilderExtensions.RateLimiting.cs:283), a fail-open posture matching the global limiter and documented at WebApplicationBuilderExtensions.RateLimiting.cs:387-394.
      • -
      • Where it's used: the base of every app's Identity AuthController, reached today through UserAccountAuthControllerBase<TChangePasswordCommand, TChangePreferencesCommand>, which both ADC (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/AuthController.cs:30) and Store (MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.API/Controllers/AuthController.cs:27) extend. Password recovery is deliberately NOT on this chain: it ships as the sibling PasswordResetAuthControllerBase<TForgotPasswordCommand, TResetPasswordCommand>. The framework's own coverage drives the base through a minimal test double, TestAuthController (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Controllers/Auth/AuthControllerBaseTests.cs:339), with the throttling asserted separately in AuthControllerBaseRateLimitTests.
      • +
      • Why it's built this way: [Rubric §15, Best Practices & Code Quality]: adding a new token flow means changing one base, not N module controllers; keeping the methods virtual (rather than the class open-ended) keeps the override surface intentional. The rate-limit default is deliberately a loud dependency: a consumer that inherits this base without calling AddCommonRateLimiting() (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:328, which registers the "auth-ip" policy at :395-397, constant defined at :23, with an authIpPermitLimit default of 30 requests per minute per IP at :326) fails at startup on an unregistered policy rather than silently serving unthrottled logins (AuthControllerBase.cs:38-40).
      • +
      • Caveats / not-in-source: the per-IP policy partitions on Connection.RemoteIpAddress (WebApplicationBuilderExtensions.RateLimiting.cs:282) and deliberately does not limit when that address is null (in-process TestServer, integration tests): AuthIpRateLimitPartition returns RateLimitPartition.GetNoLimiter("__unknown-ip") in that case (WebApplicationBuilderExtensions.RateLimiting.cs:285), a fail-open posture matching the global limiter and documented at WebApplicationBuilderExtensions.RateLimiting.cs:393-400.- Where it's used: the base of every app's Identity AuthController, reached today through UserAccountAuthControllerBase<TChangePasswordCommand, TChangePreferencesCommand>, which both ADC (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/AuthController.cs:41) and Store (MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.API/Controllers/AuthController.cs:34) extend. Password recovery is deliberately NOT on this chain: it ships as the sibling PasswordResetAuthControllerBase<TForgotPasswordCommand, TResetPasswordCommand>. RegisterAsync is one of the anonymous endpoints the framework's architecture gate lists by name (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Api/AnonymousEndpointTests.cs:27). The framework's own coverage drives the base through a minimal test double, TestAuthController (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Controllers/Auth/AuthControllerBaseTests.cs:350), with the throttling asserted separately in AuthControllerBaseRateLimitTests.

      PasswordResetAuthControllerBase<TForgotPasswordCommand, TResetPasswordCommand>

      @@ -1477,19 +1482,19 @@

      IdempotencyMetrics

    912. Why it's built this way: internal static keeps the instruments off the package's public API while still letting the filter in the same assembly record them. A host exports these by registering the meter, which the Aspire service defaults do with the name as a literal, because MMCA.Common.Aspire has no reference to MMCA.Common.API; the duplication is called out in the type doc (IdempotencyMetrics.cs:8-10). OutputCacheMetrics uses the identical pattern for the eviction consumer.
    913. -
    914. Where it's used: every recording site is in IdempotencyFilter: RecordDegraded on a faulted lock acquisition (IdempotencyFilter.cs:255), a failed cache read (IdempotencyFilter.cs:364) and a failed cache write (IdempotencyFilter.cs:439); RecordConflict for an in-flight duplicate (IdempotencyFilter.cs:267) and a body mismatch (IdempotencyFilter.cs:374); RecordReplayed on every served replay (IdempotencyFilter.cs:380).
    915. +
    916. Where it's used: every recording site is in IdempotencyFilter: RecordDegraded on a faulted lock acquisition (IdempotencyFilter.cs:256), a failed cache read (IdempotencyFilter.cs:365) and a failed cache write (IdempotencyFilter.cs:454); RecordConflict for an in-flight duplicate (IdempotencyFilter.cs:268) and a body mismatch (IdempotencyFilter.cs:375); RecordReplayed on every served replay (IdempotencyFilter.cs:381).
    917. IdempotencyRecord

      -

      MMCA.Common.API · MMCA.Common.API.Idempotency · MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyRecord.cs:14 · Level 0 · record (sealed, positional)

      +

      MMCA.Common.API · MMCA.Common.API.Idempotency · MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyRecord.cs:21 · Level 0 · record (sealed, positional)

        -
      • What it is: the cached snapshot of an idempotent action's response: the HTTP status code, the JSON-serialized body, and the hash of the request body that produced it. It is what IdempotencyFilter writes after the first successful execution and replays for every duplicate carrying the same key and the same payload.
      • -
      • Depends on: nothing beyond the BCL; a three-parameter positional record, all three parameters required.
      • -
      • Concept: introduced fully by IdempotencyFilter; this is the value it persists. Two properties of the shape are load-bearing. First, only status and body are captured, no headers, which is why the replay path re-adds X-Idempotent-Replay itself (IdempotencyFilter.cs:383) and why a replayed 201 does not carry the original Location header. Second, RequestBodyHash is non-nullable, so there is no "no hash stored" state to fall through: every record carries one and a key replayed with a different payload is always rejected rather than served someone else's response (parameter doc, IdempotencyRecord.cs:9-13). A body-less request hashes the empty payload rather than storing nothing. [Rubric §9, API & Contract Design] assesses whether a persisted contract states its invariants in its shape rather than in a comment; making the hash required is what removes the unchecked path.
      • -
      • Walkthrough: IdempotencyRecord(int StatusCode, string ResponseBody, string RequestBodyHash) (IdempotencyRecord.cs:14). StatusCode is the original response's code, defaulting to 200 when an ObjectResult carried none (IdempotencyFilter.cs:453). ResponseBody is non-nullable: an ObjectResult stores objectResult.Value serialized with JsonSerializerOptions.Web (IdempotencyFilter.cs:456-459), while a body-less 2xx such as the 204 from NoContent() stores string.Empty (IdempotencyFilter.cs:468), which is the signal the replay path uses to answer with a bare status code instead of a JSON content result. RequestBodyHash is the lowercase hex SHA-256 of the request body (IdempotencyFilter.cs:188-192).
      • -
      • Why it's built this way: keeping the record to three primitives makes it provider-agnostic, so the same artifact round-trips through the in-memory cache or Redis with no serializer coupling. Distinguishing "empty body" from "no body" via the empty string (rather than a nullable) keeps the JSON shape stable across both cases, and the comment at IdempotencyFilter.cs:463-465 names that convention at the one place a body-less result is stored.
      • -
      • Where it's used: read by IdempotencyFilter through ICacheService (IdempotencyFilter.cs:360), built by its BuildRecord (IdempotencyFilter.cs:448), and written back with the configured expiration (IdempotencyFilter.cs:435).
      • +
      • What it is: the cached snapshot of an idempotent action's response: the HTTP status code, the JSON-serialized body, the hash of the request body that produced it, and the two response headers a client may act on (Location and ETag). It is what IdempotencyFilter writes after the first successful execution and replays for every duplicate carrying the same key and the same payload.
      • +
      • Depends on: nothing beyond the BCL; a five-parameter positional record whose first three parameters are required and whose last two are optional and nullable.
      • +
      • Concept: introduced fully by IdempotencyFilter; this is the value it persists. Two properties of the shape are load-bearing. First, the record captures the status, the body and exactly two headers, not the whole header collection: Location so a replayed 201 Created still says where the resource is, and ETag so a replayed response still carries its concurrency token (parameter docs, IdempotencyRecord.cs:14-20; replay at IdempotencyFilter.cs:386-392). The replay path adds X-Idempotent-Replay itself (IdempotencyFilter.cs:384). Second, RequestBodyHash is non-nullable, so there is no "no hash stored" state to fall through: every record carries one and a key replayed with a different payload is always rejected rather than served someone else's response (parameter doc, IdempotencyRecord.cs:9-13). A body-less request hashes the empty payload rather than storing nothing. [Rubric §9, API & Contract Design] assesses whether a persisted contract states its invariants in its shape rather than in a comment; making the hash required is what removes the unchecked path.
      • +
      • Walkthrough: IdempotencyRecord(int StatusCode, string ResponseBody, string RequestBodyHash, string? Location = null, string? ETag = null) (IdempotencyRecord.cs:21-26). StatusCode is the original response's code, defaulting to 200 when an ObjectResult carried none (IdempotencyFilter.cs:476). ResponseBody is non-nullable: an ObjectResult stores objectResult.Value serialized with the host's MVC JSON options (falling back to JsonSerializerOptions.Web), and stores string.Empty when the value is null (IdempotencyFilter.cs:435-436 and :481), while a body-less 2xx such as the 204 from NoContent() also stores string.Empty (IdempotencyFilter.cs:495); the empty string is the signal the replay path uses to answer with a bare status code instead of a JSON content result. RequestBodyHash is the lowercase hex SHA-256 of the request body (IdempotencyFilter.cs:190-193). Location is resolved only for an ObjectResult (IdempotencyFilter.cs:487) and is null when the response carried none; ETag is whatever the action wrote to the response headers, null when empty (IdempotencyFilter.cs:468-469). The parameter doc notes that Location is also null for records written before the field existed (IdempotencyRecord.cs:16), which is why both new parameters are optional.
      • +
      • Why it's built this way: keeping the record to primitives makes it provider-agnostic, so the same artifact round-trips through the in-memory cache or Redis with no serializer coupling. Adding the two headers as trailing optional parameters keeps a record cached before they existed deserializable, with the replay simply skipping a header it does not have (IdempotencyFilter.cs:388-392). Distinguishing "empty body" from "no body" via the empty string (rather than a nullable) keeps the JSON shape stable across both cases, and the comment at IdempotencyFilter.cs:490-492 names that convention at the place a body-less status result is stored.
      • +
      • Where it's used: read by IdempotencyFilter through ICacheService (IdempotencyFilter.cs:361), built by its BuildRecord (IdempotencyFilter.cs:463), and written back with the configured expiration (IdempotencyFilter.cs:450).

      IdempotencySettings

      @@ -1501,7 +1506,7 @@

      IdempotencySettings

    918. Concept: the standard options pattern (see the primer). [Rubric §17, DevOps & Deployment] assesses whether a cross-cutting concern is configurable without becoming mandatory; the whole section is optional because the single property has a default, so a host that never mentions idempotency still behaves correctly (type doc, IdempotencySettings.cs:5-8).
    919. Walkthrough: SectionName is a public static readonly string equal to "Idempotency" (IdempotencySettings.cs:12), so the binding key is a symbol rather than a magic string at the call site. CacheExpirationHours defaults to 24 (IdempotencySettings.cs:16) and is constrained by [Range(1, 168)] (IdempotencySettings.cs:15), one hour to one week. The property is init-only, so the value is fixed once bound.
    920. Why it's built this way: AddAPI binds the section only when the caller passes an IConfiguration, and when it does it chains .ValidateDataAnnotations().ValidateOnStart() (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:77-80), so an out-of-range CacheExpirationHours fails the host at startup rather than at the first idempotent POST. [Rubric §15, Best Practices & Code Quality]: fail fast, loudly, at composition time. RateLimitingSettings follows the same shape one directory over.
    921. -
    922. Where it's used: resolved as IOptions<IdempotencySettings> inside IdempotencyFilter at store time (IdempotencyFilter.cs:427-431). Note the deliberate GetService, not GetRequiredService: when the options are absent (the AddAPI overload called with no configuration) the filter falls back to its hard-coded 24-hour DefaultExpiration (IdempotencyFilter.cs:80) instead of throwing.
    923. +
    924. Where it's used: resolved as IOptions<IdempotencySettings> inside IdempotencyFilter at store time (IdempotencyFilter.cs:442-446). Note the deliberate GetService, not GetRequiredService: when the options are absent (the AddAPI overload called with no configuration) the filter falls back to its hard-coded 24-hour DefaultExpiration (IdempotencyFilter.cs:81) instead of throwing.
    925. IErrorLocalizer

      @@ -1537,7 +1542,7 @@

      DisabledFeatureHandler

    926. Concept introduced: feature gating at the HTTP edge. [Rubric §9, API & Contract Design] assesses whether every response, success or refusal, follows one uniform contract; this handler makes the disabled-feature path match the ApiControllerBase.HandleFailure shape rather than leaking a framework default. [Rubric §6, CQRS & Event-Driven Design] covers concerns applied uniformly across endpoints, and feature flags are exactly that. Note the split: this class gates controller actions decorated with [FeatureGate], while FeatureGateCommandDecorator<TCommand, TResult> gates CQRS handlers one layer deeper. The two surfaces cover the two entry points into a gated capability, which is precisely the dual-surface enforcement ADR-031 records.
    927. Walkthrough: HandleDisabledFeatures(features, context) (DisabledFeatureHandler.cs:16) sets context.Result to an ObjectResult wrapping a ProblemDetails with Status = 404 and a fixed title/detail ("Feature not available", DisabledFeatureHandler.cs:18-23), and also sets the outer StatusCode = 404 (DisabledFeatureHandler.cs:25) so the response code and the body agree. It returns Task.CompletedTask (DisabledFeatureHandler.cs:28): the work is synchronous, there is nothing to await.
    928. Why it's built this way: the payload deliberately does not name the disabled feature. An anonymous caller learns only that the endpoint is unavailable, not which flag is off, so the flag set is not enumerable from outside. The features parameter is available but unused for that reason.
    929. -
    930. Where it's used: registered as the app's IDisabledFeaturesHandler singleton inside AddAPI (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:107, immediately after services.AddFeatureManagement().WithTargeting<CurrentUserTargetingContextAccessor>() on DependencyInjection.cs:105-106); invoked by Microsoft.FeatureManagement.Mvc whenever a [FeatureGate] action is hit with its flag disabled. In the framework itself that covers DataExportControllerBase<TQuery> while PrivacyFeatures.DataExport is off (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:58) and the three notification controllers gated on NotificationFeatures.PushNotifications (Controllers/Notifications/DevicesController.cs:23, NotificationInboxController.cs:27, NotificationsController.cs:28).
    931. +
    932. Where it's used: registered as the app's IDisabledFeaturesHandler singleton inside AddAPI (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:107, immediately after services.AddFeatureManagement().WithTargeting<CurrentUserTargetingContextAccessor>() on DependencyInjection.cs:105-106); invoked by Microsoft.FeatureManagement.Mvc whenever a [FeatureGate] action is hit with its flag disabled. In the framework itself that covers DataExportControllerBase<TQuery> while PrivacyFeatures.DataExport is off (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:60) and the three notification controllers gated on NotificationFeatures.PushNotifications (Controllers/Notifications/DevicesController.cs:23, NotificationInboxController.cs:27, NotificationsController.cs:28).
    933. ErrorLocalizer

      @@ -1553,42 +1558,43 @@

      ErrorLocalizer

      IdempotencyFilter

      -

      MMCA.Common.API · MMCA.Common.API.Idempotency · MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:67 · Level 4 · class (sealed, partial)

      +

      MMCA.Common.API · MMCA.Common.API.Idempotency · MMCA.Common/Source/Presentation/MMCA.Common.API/Idempotency/IdempotencyFilter.cs:68 · Level 4 · class (sealed, partial)

        -
      • What it is: the ASP.NET Core filter that gives write operations client-driven idempotency. A client attaches an Idempotency-Key header; the first successful response for that key is cached and every subsequent request carrying the same key gets the stored response back, without re-running the action. It implements both IAsyncActionFilter and IAsyncResourceFilter (IdempotencyFilter.cs:68), which is the detail that makes body-aware deduplication possible.
      • -
      • Depends on: ICacheService and IDistributedLock, both resolved per request from RequestServices (IdempotencyFilter.cs:140 and IdempotencyFilter.cs:148); IdempotencyRecord; IdempotencySettings via IOptions<>; IdempotencyMetrics; IdempotencyHeaders for the two header names; KeyedSemaphoreStripe as the no-distributed-lock fallback; ClaimsPrincipalExtensions for the caller's identity. Externals: SHA256, Encoding, System.Text.Json, and ILogger with source-generated [LoggerMessage] partials.
      • +
      • What it is: the ASP.NET Core filter that gives write operations client-driven idempotency. A client attaches an Idempotency-Key header; the first successful response for that key is cached and every subsequent request carrying the same key gets the stored response back, without re-running the action. It implements both IAsyncActionFilter and IAsyncResourceFilter (IdempotencyFilter.cs:69), which is the detail that makes body-aware deduplication possible.
      • +
      • Depends on: ICacheService and IDistributedLock, both resolved per request from RequestServices (IdempotencyFilter.cs:141 and IdempotencyFilter.cs:149); IdempotencyRecord; IdempotencySettings via IOptions<>; IdempotencyMetrics; IdempotencyHeaders for the two header names; KeyedSemaphoreStripe as the no-distributed-lock fallback; ClaimsPrincipalExtensions for the caller's identity. Externals: SHA256, Encoding, System.Text.Json (with the host's MVC IOptions<JsonOptions> resolved per request when present), IUrlHelperFactory for reconstructing a created result's Location, and ILogger with source-generated [LoggerMessage] partials.
      • Concept introduced: idempotent mutation as a lock plus a caller-scoped key plus a body binding. [Rubric §29, Resilience & Business Continuity] assesses surviving client retries without duplicating side effects; [Rubric §9, API & Contract Design] covers safe retry semantics on non-safe verbs; [Rubric §11, Security] applies because the key derivation is a trust boundary, not just a cache detail; [Rubric §13, Observability & Operability] applies because every degraded path is counted rather than silent. Three ideas compose here.
          -
        1. Double-check locking (doc comment, IdempotencyFilter.cs:22-32): read the cache with no lock (the common fast path); on a miss take the lock for this key; re-read, because a concurrent duplicate may have finished while this one waited; only then execute and store. Without the lock, two near-simultaneous retries of a slow create both miss and both execute.
        2. -
        3. The lock must be visible to every replica. A per-process stripe only serializes duplicates that land on the same instance, and both deployed apps run more than one (IdempotencyFilter.cs:33-38, restated at IdempotencyFilter.cs:219-223), so the primary path uses an IDistributedLock and the stripe is only the fallback for a host that registers none.
        4. -
        5. The key alone is not enough. A stored response is bound to the request body that produced it, so a key reused with a different payload is refused (422) rather than replayed, which would otherwise silently swallow a genuinely new write (IdempotencyFilter.cs:43-51).
        6. +
        7. Double-check locking (doc comment, IdempotencyFilter.cs:23-33): read the cache with no lock (the common fast path); on a miss take the lock for this key; re-read, because a concurrent duplicate may have finished while this one waited; only then execute and store. Without the lock, two near-simultaneous retries of a slow create both miss and both execute.
        8. +
        9. The lock must be visible to every replica. A per-process stripe only serializes duplicates that land on the same instance, and both deployed apps run more than one (IdempotencyFilter.cs:34-39, restated at IdempotencyFilter.cs:221-224), so the primary path uses an IDistributedLock and the stripe is only the fallback for a host that registers none.
        10. +
        11. The key alone is not enough. A stored response is bound to the request body that produced it, so a key reused with a different payload is refused (422) rather than replayed, which would otherwise silently swallow a genuinely new write (IdempotencyFilter.cs:44-52).
      • Walkthrough
          -
        • Constants and shared state: IdempotencyKeyHeader delegates to IdempotencyHeaders.IdempotencyKey (IdempotencyFilter.cs:73, defined at MMCA.Common/Source/Core/MMCA.Common.Shared/Http/IdempotencyHeaders.cs:19 as "Idempotency-Key"); CacheKeyPrefix returns "idempotency:" (IdempotencyFilter.cs:75); DefaultExpiration of 24 hours (IdempotencyFilter.cs:80); LockTimeToLive of 30 seconds and LockWait of 5 seconds (IdempotencyFilter.cs:97 and IdempotencyFilter.cs:104), sized so a slow action finishes under its own lock while a dead replica does not block a retry for long; and EmptyBodyHash, the SHA-256 of zero bytes (IdempotencyFilter.cs:110-111).
        • -
        • KeyLocks (IdempotencyFilter.cs:90) is a static KeyedSemaphoreStripe. The comment above it (IdempotencyFilter.cs:82-89) gives the reason for striping over one-semaphore-per-key: the key embeds a caller-supplied value, so a per-key table either grows without bound or needs an eager removal that races (a removal between another request's lookup and its wait lets a third request create a fresh semaphore, and both then execute concurrently).
        • -
        • Resource stage. OnResourceExecutionAsync (IdempotencyFilter.cs:119) runs before model binding, the last point at which the body can still be made replayable. It calls Request.EnableBuffering() only when the header is present (IdempotencyFilter.cs:121-122), so ordinary traffic on an [Idempotent] action pays nothing, then awaits next() (IdempotencyFilter.cs:124).
        • -
        • Action stage. OnActionExecutionAsync (IdempotencyFilter.cs:128): no header means straight through to next() with nothing else resolved (IdempotencyFilter.cs:131-136), so idempotency is opt-in per request as well as per action. Otherwise it derives the cache key (IdempotencyFilter.cs:138), hashes the body (:139), resolves ICacheService (:140), and tries the lock-free replay (:143). On a miss it picks a lock strategy: GetService<IDistributedLock>() returning null routes to the process-stripe path (:148-153), otherwise the distributed path (:155). The comment at :146-147 states the invariant that shapes both paths: the lock has to span execute-and-store, so a duplicate cannot slip in between the action finishing and its response reaching the cache.
        • -
        • ReadIdempotencyKey (IdempotencyFilter.cs:163-170) treats a missing or blank header as absent, and both stages call it so they agree on what "has a key" means.
        • -
        • ComputeRequestBodyHashAsync (IdempotencyFilter.cs:182-193): a stream that cannot seek was never buffered, so it takes EmptyBodyHash rather than throwing (:185-186), which leaves such a request deduplicated on its key alone. Otherwise it rewinds to 0, hashes with SHA256.HashDataAsync, and rewinds again (:188-190) because model binding still has to read the whole body.
        • -
        • ExecuteUnderProcessLockAsync (IdempotencyFilter.cs:199): acquires the stripe honoring RequestAborted inside a using so release survives a throw (:206), re-checks the cache (:209), then executes and stores (:212).
        • -
        • ExecuteUnderDistributedLockAsync (IdempotencyFilter.cs:238) is where the interesting policy lives, and it separates three outcomes that a naive implementation conflates (rationale in the remarks, :219-236).
            -
          • The lock backend faults: counted on idempotency.degraded, logged, and the action runs unguarded (:253-259). There is no holder to wait for, so refusing would turn a cache blip into a write outage.
          • -
          • The wait expires with the lock still held elsewhere: the holder is executing this key right now, so it logs the timeout and re-checks the cache first, and only if still nothing is stored does it record an in_flight conflict and answer with InFlightDuplicateResult() (:261-273). That helper (:301-310) is a 409 ProblemDetails titled "Request in progress" telling the client to retry with the same key: retryable and honest, where executing would be the duplicate write.
          • -
          • The lock is acquired: await using on the handle (:275), double-check (:278), then execute and store (:281).
          • -
          -
        • -
        • ExecuteAndStoreAsync (IdempotencyFilter.cs:286-295) is the two-line shared tail of all three paths: await next(), then TryStoreAsync the executed result.
        • -
        • TryReplayAsync (IdempotencyFilter.cs:351) serves both the fast path and every double-check, returning whether it short-circuited. A cache read that throws is reported as "nothing stored" after counting a degradation (:358-367). A stored record whose RequestBodyHash differs from this request's hash (an ordinal comparison, :372) yields a body_mismatch conflict and BodyMismatchResult() (:372-378), a 422 whose remarks explain the status choice: 409 is already spent on "still in flight", which is retry-with-the-same-key, while key reuse with a different body is not retryable until the client picks a new key (:317-331). On a genuine hit it counts the replay (:380), appends X-Idempotent-Replay: true (:383, name from IdempotencyHeaders.cs:25), and short-circuits with a bare StatusCodeResult when the stored body is empty or a ContentResult with application/json otherwise (:384-391). That split is what makes a replayed 204 look like the original 204 instead of a 204 carrying a content type.
        • -
        • TryStoreAsync (IdempotencyFilter.cs:416) builds the record and returns early when it is not cacheable (:423-425), reads the expiration from IdempotencySettings when registered and DefaultExpiration otherwise (:427-431), and swallows a failing SetAsync after counting it (:433-441): the action already ran, so failing here would push the client into the very retry the filter exists to deduplicate (:410-414).
        • -
        • BuildRecord (IdempotencyFilter.cs:448) decides what is cacheable. An ObjectResult with a 2xx status stores its value serialized with JsonSerializerOptions.Web (:452-461; the VSTHRD103 suppression at :454 documents that serializing to a string is correctly synchronous). A StatusCodeResult with a 2xx status, which is what NoContent(), OkResult and StatusCode(int) produce, stores the empty string (:466-469). Everything else, including every non-2xx and every redirect or file result, returns null and is not stored (:471-472); IsSuccess is the >= 200 and < 300 test (:476).
        • -
        • BuildCacheKey (IdempotencyFilter.cs:485-499) is the security-relevant part: the subject is the caller's user-id claim via FindUserIdValue() (:487, MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/ClaimsPrincipalExtensions.cs:26, which reads sub and falls back to the mapped NameIdentifier) or, unauthenticated, anon:{remote address} (:488); the route is the attribute route template falling back to the request path (:490-492); those plus the HTTP method and the client key are joined with a newline (:495, a character valid in none of the components, so the tuple cannot be forged), SHA-256 hashed (:496), and emitted as idempotency:{lowercase hex} (:498).
        • -
        • Logging is entirely source-generated [LoggerMessage] partials with instance forwarders (IdempotencyFilter.cs:501-552): one Information message for a served replay (:501-507) and five Warnings covering body mismatch (:509-514), in-flight duplicate (:516-521), lock-wait timeout (:523-528), cache-read failure (:530-536) and cache-store failure (:538-544), plus lock unavailability (:546-552). [Rubric §13, Observability & Operability]: each degraded path has both a counter and a log line naming the cache key.
        • -
        -
      • -
      • Why it's built this way: keying on the bare client value would make the key space global, so two callers who happened to pick the same value would share an entry and one user's serialized body could be replayed to another; with services sharing one cache instance that collision reaches across endpoints and services (SECURITY note, IdempotencyFilter.cs:59-65; ADR-017). Hashing also bounds the stored key length regardless of what a client sends (:478-484). Non-2xx results are deliberately not stored (IdempotencyFilter.cs:400-403) because replaying a failure for the whole retention window would mean a client retrying after a transient 500 keeps receiving that 500 for 24 hours instead of the retry actually executing. The 204 case is explicitly covered rather than skipped (:405-409): skipping it left every command answering NoContent() with nothing stored, so the body-less writes, the ones most likely to be retried, were the ones idempotency did not actually cover. And the whole cache-and-lock layer is treated as best-effort infrastructure (IdempotencyFilter.cs:52-58): deduplication is an optimization over an at-least-once client retry, so a Redis outage must degrade dedup, not every write endpoint carrying the attribute.
      • -
      • Caveats / not-in-source: the replay restores only the status code and the JSON body. Response headers the original action set (a 201's Location, for example) are not captured by IdempotencyRecord and are therefore not reproduced; only X-Idempotent-Replay is added.
      • -
      • Where it's used: registered scoped in AddAPI because it depends on scoped services (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:84, comment at :76), and attached to actions through IdempotentAttribute. In the framework itself that is the create endpoint on AggregateRootEntityControllerBase<TEntity, TEntityDTO, TIdentifierType, TCreateRequest> (AggregateRootEntityControllerBase.cs:60), the update endpoint on CrudEntityControllerBase<TEntity, TEntityDTO, TIdentifierType, TCreateRequest, TUpdateRequest> (CrudEntityControllerBase.cs:89, alongside [SupportsIfMatch]), the register endpoint on AuthControllerBase (AuthControllerBase.cs:94), both actions of PasswordResetAuthControllerBase<TForgotPasswordCommand, TResetPasswordCommand> (PasswordResetAuthControllerBase.cs:76 and :100), and the send endpoint of NotificationsController (NotificationsController.cs:44). Downstream, ADC marks its Engagement writes (check-ins, bookmarks, live polls, session questions) and its Conference collection writes, and Store marks its cart, order and catalog writes.
      • +
      • Constants and shared state: IdempotencyKeyHeader delegates to IdempotencyHeaders.IdempotencyKey (IdempotencyFilter.cs:74, defined at MMCA.Common/Source/Core/MMCA.Common.Shared/Http/IdempotencyHeaders.cs:19 as "Idempotency-Key"); CacheKeyPrefix returns "idempotency:" (IdempotencyFilter.cs:76); DefaultExpiration of 24 hours (IdempotencyFilter.cs:81); LockTimeToLive of 30 seconds and LockWait of 5 seconds (IdempotencyFilter.cs:98 and IdempotencyFilter.cs:105), sized so a slow action finishes under its own lock while a dead replica does not block a retry for long; and EmptyBodyHash, the SHA-256 of zero bytes (IdempotencyFilter.cs:111-112).
      • +
      • KeyLocks (IdempotencyFilter.cs:91) is a static KeyedSemaphoreStripe. The comment above it (IdempotencyFilter.cs:83-90) gives the reason for striping over one-semaphore-per-key: the key embeds a caller-supplied value, so a per-key table either grows without bound or needs an eager removal that races (a removal between another request's lookup and its wait lets a third request create a fresh semaphore, and both then execute concurrently).
      • +
      • Resource stage. OnResourceExecutionAsync (IdempotencyFilter.cs:120) runs before model binding, the last point at which the body can still be made replayable. It calls Request.EnableBuffering() only when the header is present (IdempotencyFilter.cs:122-123), so ordinary traffic on an [Idempotent] action pays nothing, then awaits next() (IdempotencyFilter.cs:125).
      • +
      • Action stage. OnActionExecutionAsync (IdempotencyFilter.cs:129): no header means straight through to next() with nothing else resolved (IdempotencyFilter.cs:132-137), so idempotency is opt-in per request as well as per action. Otherwise it derives the cache key (IdempotencyFilter.cs:139), hashes the body (:140), resolves ICacheService (:141), and tries the lock-free replay (:144-145). On a miss it picks a lock strategy: GetService<IDistributedLock>() returning null routes to the process-stripe path (:149-154), otherwise the distributed path (:156-157). The comment at :147-148 states the invariant that shapes both paths: the lock has to span execute-and-store, so a duplicate cannot slip in between the action finishing and its response reaching the cache.
      • +
      • ReadIdempotencyKey (IdempotencyFilter.cs:164-171) treats a missing or blank header as absent, and both stages call it so they agree on what "has a key" means.
      • +
      • ComputeRequestBodyHashAsync (IdempotencyFilter.cs:183-194): a stream that cannot seek was never buffered, so it takes EmptyBodyHash rather than throwing (:186-187), which leaves such a request deduplicated on its key alone. Otherwise it rewinds to 0, hashes with SHA256.HashDataAsync, and rewinds again (:189-191) because model binding still has to read the whole body.
      • +
      • ExecuteUnderProcessLockAsync (IdempotencyFilter.cs:200): acquires the stripe honoring RequestAborted inside a using so release survives a throw (:207), re-checks the cache (:210), then executes and stores (:213).
      • +
      • ExecuteUnderDistributedLockAsync (IdempotencyFilter.cs:239) is where the interesting policy lives, and it separates three outcomes that a naive implementation conflates (rationale in the remarks, :220-238).
          +
        • The lock backend faults: counted on idempotency.degraded, logged, and the action runs unguarded (:254-260). There is no holder to wait for, so refusing would turn a cache blip into a write outage.
        • +
        • The wait expires with the lock still held elsewhere: the holder is executing this key right now, so it logs the timeout and re-checks the cache first, and only if still nothing is stored does it record an in_flight conflict and answer with InFlightDuplicateResult() (:262-274). That helper (:302-311) is a 409 ProblemDetails titled "Request in progress" telling the client to retry with the same key: retryable and honest, where executing would be the duplicate write.
        • +
        • The lock is acquired: await using on the handle (:276), double-check (:279), then execute and store (:282).
        • +
        +
      • +
      • ExecuteAndStoreAsync (IdempotencyFilter.cs:287-296) is the two-line shared tail of all three paths: await next(), then TryStoreAsync the executed result.
      • +
      • TryReplayAsync (IdempotencyFilter.cs:352) serves both the fast path and every double-check, returning whether it short-circuited. A cache read that throws is reported as "nothing stored" after counting a degradation (:359-367). A stored record whose RequestBodyHash differs from this request's hash (an ordinal comparison, :373) yields a body_mismatch conflict and BodyMismatchResult() (:373-379), a 422 whose remarks explain the status choice: 409 is already spent on "still in flight", which is retry-with-the-same-key, while key reuse with a different body is not retryable until the client picks a new key (:318-322). On a genuine hit it counts the replay (:381), appends X-Idempotent-Replay: true (:384, name from IdempotencyHeaders.cs:25), restores the stored Location and ETag headers when the record carries them (:386-392, so a replayed 201 still says where the resource is and a replayed response still carries its concurrency token), and short-circuits with a bare StatusCodeResult when the stored body is empty or a ContentResult with application/json otherwise (:394-401). That split is what makes a replayed 204 look like the original 204 instead of a 204 carrying a content type.
      • +
      • TryStoreAsync (IdempotencyFilter.cs:426) resolves the host's MVC JsonOptions and falls back to JsonSerializerOptions.Web only when none is registered (:433-436): the replayed body has to be shaped exactly like the original, which MVC wrote with the host's converters. It then builds the record and returns early when it is not cacheable (:438-440), reads the expiration from IdempotencySettings when registered and DefaultExpiration otherwise (:442-446), and swallows a failing SetAsync after counting it (:448-456): the action already ran, so failing here would push the client into the very retry the filter exists to deduplicate (:421-424).
      • +
      • BuildRecord (IdempotencyFilter.cs:463) decides what is cacheable. It first reads any ETag the action wrote to the response, storing null when it is empty (:468-469). An ObjectResult with a non-2xx status returns null (:476-478); a 2xx one stores its value serialized with the resolved options, or the empty string when the value is null so an Accepted() with no payload replays as a body-less status rather than a JSON null (:473-474 and :481; the VSTHRD103 suppression at :480 documents that serializing to a string is correctly synchronous), together with the resolved Location and the ETag (:483-488). A StatusCodeResult with a 2xx status, which is what NoContent(), OkResult and StatusCode(int) produce, stores the empty string plus the ETag (:493-496). Everything else, including every redirect or file result, returns null and is not stored (:498-499); IsSuccess is the >= 200 and < 300 test (:530).
      • +
      • ResolveLocation (IdempotencyFilter.cs:507-528) exists because a created result writes its Location when the RESULT executes, after this filter has already run, so the filter computes it the same way the result will (:503-506): a CreatedResult gives its Location directly (:511-512), a CreatedAtRouteResult goes through IUrlHelperFactory and Link (:514-517), a CreatedAtActionResult through IUrlHelperFactory and Action with the request's scheme and host (:519-523), and any other ObjectResult yields null (:525-526).
      • +
      • BuildCacheKey (IdempotencyFilter.cs:539-553) is the security-relevant part: the subject is the caller's user-id claim via FindUserIdValue() (:541, MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/ClaimsPrincipalExtensions.cs:26, which reads sub and falls back to the mapped NameIdentifier) or, unauthenticated, anon:{remote address} (:542); the route is the attribute route template falling back to the request path (:544-546); those plus the HTTP method and the client key are joined with a newline (:548-549, a character valid in none of the components, so the tuple cannot be forged), SHA-256 hashed (:550), and emitted as idempotency:{lowercase hex} (:552).
      • +
      • Logging is entirely source-generated [LoggerMessage] partials with instance forwarders (IdempotencyFilter.cs:555-606): one Information message for a served replay (:555-561) and six Warnings covering body mismatch (:563-568), in-flight duplicate (:570-575), lock-wait timeout (:577-582), cache-read failure (:584-590), cache-store failure (:592-598) and lock unavailability (:600-606). [Rubric §13, Observability & Operability]: each degraded path has both a counter and a log line naming the cache key.
      • +
      + +
    934. Why it's built this way: keying on the bare client value would make the key space global, so two callers who happened to pick the same value would share an entry and one user's serialized body could be replayed to another; with services sharing one cache instance that collision reaches across endpoints and services (SECURITY note, IdempotencyFilter.cs:60-66; ADR-017). Hashing also bounds the stored key length regardless of what a client sends (:532-538). Non-2xx results are deliberately not stored (IdempotencyFilter.cs:410-413) because replaying a failure for the whole retention window would mean a client retrying after a transient 500 keeps receiving that 500 for 24 hours instead of the retry actually executing. The 204 case is explicitly covered rather than skipped (:416-419): skipping it left every command answering NoContent() with nothing stored, so the body-less writes, the ones most likely to be retried, were the ones idempotency did not actually cover. And the whole cache-and-lock layer is treated as best-effort infrastructure (IdempotencyFilter.cs:53-59): deduplication is an optimization over an at-least-once client retry, so a Redis outage must degrade dedup, not every write endpoint carrying the attribute.
    935. +
    936. Caveats / not-in-source: the replay restores the status code, the JSON body, and the Location and ETag headers stored on IdempotencyRecord (IdempotencyFilter.cs:386-392); any other header the original action set is not captured and is therefore not reproduced, and X-Idempotent-Replay is added. Location is reconstructed only for CreatedResult, CreatedAtRouteResult and CreatedAtActionResult, and for the two URL-helper cases only when an IUrlHelperFactory is registered (IdempotencyFilter.cs:515 and :521); a Location the action set on the response by hand is not captured. A record cached before these fields existed replays without them.
    937. +
    938. Where it's used: registered scoped in AddAPI because it depends on scoped services (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:84, comment at :76), and attached to actions through IdempotentAttribute. In the framework itself that is the create endpoint on AggregateRootEntityControllerBase<TEntity, TEntityDTO, TIdentifierType, TCreateRequest> (AggregateRootEntityControllerBase.cs:60), the update endpoint on CrudEntityControllerBase<TEntity, TEntityDTO, TIdentifierType, TCreateRequest, TUpdateRequest> (CrudEntityControllerBase.cs:89, alongside [SupportsIfMatch]), both actions of PasswordResetAuthControllerBase<TForgotPasswordCommand, TResetPasswordCommand> (PasswordResetAuthControllerBase.cs:76 and :100), and the send endpoint of NotificationsController (NotificationsController.cs:44). The token-issuing and revoking actions of AuthControllerBase, register included, opt out explicitly with NonIdempotentAttribute (AuthControllerBase.cs:70, :94, :118, :145, :208). Downstream, ADC marks its Engagement writes (check-ins, bookmarks, live polls, session questions) and its Conference collection writes, and Store marks its cart, order and catalog writes.
    939. IdempotentAttribute

      @@ -1599,7 +1605,7 @@

      IdempotentAttribute

    940. Depends on: ServiceFilterAttribute from ASP.NET Core MVC; resolves IdempotencyFilter from DI.
    941. Concept introduced: service filters (DI-resolved action filters). [Rubric §2, Design Patterns] covers the filter idiom and how the instance is obtained. A plain [TypeFilter] constructs the filter itself, which would confine it to constructor arguments MVC can supply; ServiceFilterAttribute (IdempotentAttribute.cs:16) resolves it from the container instead, which is what lets the filter be registered AddScoped and reach scoped services such as ICacheService (remarks, IdempotentAttribute.cs:10-14). [Rubric §15, Best Practices & Code Quality]: [AttributeUsage(AttributeTargets.Method)] (IdempotentAttribute.cs:15) makes misapplication at class level a compile error rather than a silent no-op.
    942. Walkthrough: the entire type is one line, public sealed class IdempotentAttribute() : ServiceFilterAttribute(typeof(IdempotencyFilter)) (IdempotentAttribute.cs:16); the primary constructor forwards the filter type to the base. The doc notes the filter must be registered in DI, which AddAPI does (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:84); without that registration, resolution fails at request time rather than at startup. It also states the no-op contract: an action carrying the attribute but reached by a request with no Idempotency-Key header executes normally (IdempotentAttribute.cs:5-9).
    943. -
    944. Why it's built this way: opt-in per action is the ADR-017 decision. Nothing is deduplicated unless the action declares it, so adding the attribute is additive and safe, and the client still decides per request whether to send a key at all (a keyless request short-circuits at IdempotencyFilter.cs:131-136). The flip side, that a POST which should be idempotent but is missing the attribute gets no protection, is closed by a fitness function rather than by inventory discipline: see NonIdempotentAttribute, whose PostActionsDeclareIdempotencyIntent rule requires every POST action to carry one of the two attributes (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Cqrs/ArchitectureRules.Idempotency.cs:44). Because that rule reads attributes with inherit: true, a concrete controller deriving from one of the framework bases already satisfies it through the base's method (ArchitectureRules.Idempotency.cs:30-36).
    945. +
    946. Why it's built this way: opt-in per action is the ADR-017 decision. Nothing is deduplicated unless the action declares it, so adding the attribute is additive and safe, and the client still decides per request whether to send a key at all (a keyless request short-circuits at IdempotencyFilter.cs:132-137). The flip side, that a POST which should be idempotent but is missing the attribute gets no protection, is closed by a fitness function rather than by inventory discipline: see NonIdempotentAttribute, whose PostActionsDeclareIdempotencyIntent rule requires every POST action to carry one of the two attributes (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Cqrs/ArchitectureRules.Idempotency.cs:44). Because that rule reads attributes with inherit: true, a concrete controller deriving from one of the framework bases already satisfies it through the base's method (ArchitectureRules.Idempotency.cs:30-36).
    947. Where it's used: applied in the framework to the create endpoint of AggregateRootEntityControllerBase<TEntity, TEntityDTO, TIdentifierType, TCreateRequest> (AggregateRootEntityControllerBase.cs:60), the update endpoint of CrudEntityControllerBase<TEntity, TEntityDTO, TIdentifierType, TCreateRequest, TUpdateRequest> (CrudEntityControllerBase.cs:89), AuthControllerBase.RegisterAsync (AuthControllerBase.cs:94), the forgot-password and reset-password actions of PasswordResetAuthControllerBase<TForgotPasswordCommand, TResetPasswordCommand> (PasswordResetAuthControllerBase.cs:76, :100) and NotificationsController.SendAsync (NotificationsController.cs:44); and in the apps to the ADC Conference/Engagement/Identity and Store Sales/Catalog write endpoints listed under IdempotencyFilter.
    948. CurrentUserTargetingContextAccessor

      @@ -1610,7 +1616,7 @@

      CurrentUserTargetingContextAccessor
    949. What it is: the ITargetingContextAccessor that supplies the audience (a user id plus that user's groups) which the feature-management Targeting filter evaluates, read from the current HTTP request's principal.
    950. Depends on: Microsoft.FeatureManagement.FeatureFilters.ITargetingContextAccessor and TargetingContext, Microsoft.AspNetCore.Http.IHttpContextAccessor, System.Security.Claims, and ClaimsPrincipalExtensions for the user-id read (CurrentUserTargetingContextAccessor.cs:1-4). Registered by AddAPI; a sibling of DisabledFeatureHandler in the same folder.
    951. Concept introduced: a percentage rollout that is sticky per user rather than random per request. A Percentage feature filter with no targeting rolls a die on every evaluation, so the same user sees the feature on one request and off the next: unusable for a UI. The Targeting filter fixes that by hashing the audience's user id, which makes the answer deterministic for a given user across requests and across instances, and this accessor is what supplies that id (:10-14). [Rubric §29, Resilience, Reliability & Business Continuity] assesses whether a cross-cutting toggle is applied uniformly; [Rubric §11, Security] and [Rubric §17, DevOps] both bear on the rollout being an operational lever rather than a deploy. See ADR-031. - Two source decisions are worth carrying. First, the user id is the standard sub claim TokenService emits, read through ClaimsPrincipalExtensions so the ClaimTypes.NameIdentifier form the bearer handler maps it to resolves identically, the same read CurrentUserService and IdempotencyFilter perform (:15-19, and IdempotencyFilter.cs:487); the principal's name is the fallback for a token carrying neither. Second, the accessor is a singleton (that is the lifetime WithTargeting gives it), so it cannot take the scoped ICurrentUserService; it reads IHttpContextAccessor instead and re-derives the roles itself, which the doc calls out explicitly (:22-24).
    952. + Two source decisions are worth carrying. First, the user id is the standard sub claim TokenService emits, read through ClaimsPrincipalExtensions so the ClaimTypes.NameIdentifier form the bearer handler maps it to resolves identically, the same read CurrentUserService and IdempotencyFilter perform (:15-19, and IdempotencyFilter.cs:541); the principal's name is the fallback for a token carrying neither. Second, the accessor is a singleton (that is the lifetime WithTargeting gives it), so it cannot take the scoped ICurrentUserService; it reads IHttpContextAccessor instead and re-derives the roles itself, which the doc calls out explicitly (:22-24).
    953. Walkthrough: GetContextAsync() (:63) reads httpContextAccessor.HttpContext?.User (:65). An unauthenticated or absent principal yields an empty context, UserId = null and Groups = [] (:67-74), so a targeted feature is simply off for anonymous callers unless the audience opts everyone in (:26-29); the method never returns null, because a feature filter must not be able to fail a request (:57-61). For an authenticated caller it collects the role claims, accepting each claim type the JWT middleware may produce: the standard ClaimTypes.Role URI when inbound claim mapping is on, or the raw role / roles claim when it is off (:76-82). Finally it builds the TargetingContext with user.FindUserIdValue() ?? user.Identity.Name (:86) and those groups (:87).
    954. Why it's built this way: accepting three role claim types is not defensive padding, it is the concrete consequence of ASP.NET Core's inbound claim mapping being configurable; matching only ClaimTypes.Role would silently drop every group when a host turns mapping off, and a group-targeted rollout would then behave as an ungrouped one. The class doc carries a worked FeatureManagement configuration example (:30-51) showing a rollout that always includes the Organizer role, includes 25 percent of everyone else, and pins two named users, which is the fastest way to see what the context is actually feeding.
    955. Where it's used: registered inside AddAPI as services.AddFeatureManagement().WithTargeting<CurrentUserTargetingContextAccessor>() (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:105-106), immediately after AddHttpContextAccessor() (:90, which is TryAdd-based and therefore safe to call here as well as elsewhere, the reasoning recorded at :84-89). From there it is consumed only by the Targeting feature filter, whose verdicts reach the HTTP edge through [FeatureGate] and DisabledFeatureHandler, and the CQRS layer through FeatureGateCommandDecorator<TCommand, TResult>.
    956. @@ -1630,7 +1636,7 @@

      ApiParameterDescriptorBackfillPr
    957. Why it's built this way: the class remarks (ApiParameterDescriptorBackfillProvider.cs:12-42) document the exact failure it guards. MVC leaves ParameterDescriptor null for a route-template token with no matching action parameter (normal for [Route("api/v{version:apiVersion}/orders")] or [Route("api/{tenant}/orders")]), while Asp.Versioning.OpenApi 10.2.1 reads arg.ParameterDescriptor.Name without a null check (:21-27). The fix is deliberately general (any unbound route token, not just the version token) so a {tenant} or {region} segment is covered by the same guard (:28-33).
    958. -
    959. Where it's used: registered once through the private AddApiParameterDescriptorBackfill() helper (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.cs:116-118), which uses TryAddEnumerable so calling both AddCommonApiVersioning() (WebApplicationBuilderExtensions.cs:37, which calls the helper at :261) and AddCommonOpenApi() (:403, calling at :406) still yields exactly one instance. The de-duplication rationale is on the helper's own doc (:411-417).
    960. +
    961. Where it's used: registered once through the private AddApiParameterDescriptorBackfill() helper (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.cs:118-120), which uses TryAddEnumerable so calling both AddCommonApiVersioning() (WebApplicationBuilderExtensions.cs:37, which calls the helper at :261) and AddCommonOpenApi() (:403, calling at :406) still yields exactly one instance. The de-duplication rationale is on the helper's own doc (:411-417).
    962. DbUpdateExceptionHandler

      @@ -1653,16 +1659,16 @@

      DbUpdateExceptionHandler

      OperationCanceledExceptionHandler

      -

      MMCA.Common.API · MMCA.Common.API.Middleware · MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/OperationCanceledExceptionHandler.cs:16 · Level 0 · class (sealed)

      +

      MMCA.Common.API · MMCA.Common.API.Middleware · MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/OperationCanceledExceptionHandler.cs:18 · Level 0 · class (sealed)

        -
      • What it is: the handler that maps OperationCanceledException (a client that hung up mid-request) to HTTP 499 Client Closed Request instead of letting it inflate the 500 rate.
      • +
      • What it is: the handler that maps an OperationCanceledException caused by the client hanging up mid-request (HttpContext.RequestAborted is cancelled) to HTTP 499 Client Closed Request instead of letting it inflate the 500 rate. Any other cancellation is deliberately left alone.
      • Depends on: IProblemDetailsService, ILogger<OperationCanceledExceptionHandler>; chain concept at DbUpdateExceptionHandler.
      • -
      • Concept: [Rubric §13, Observability & Operability] assesses whether the signals operators alert on distinguish real faults from normal client behavior. 499 is a non-standard nginx-origin code (class comment, OperationCanceledExceptionHandler.cs:8-13) that monitoring stacks read as "the caller gave up", so cancellations stop looking like server errors on a dashboard.
      • -
      • Walkthrough: TryHandleAsync (OperationCanceledExceptionHandler.cs:22) type-tests and returns false for anything else (:27-28), logs at LogWarning with a client-disconnected message (:30), sets StatusCodes.Status499ClientClosedRequest (:32), and still builds a problem document titled "Operation Canceled Exception" (:33-43) which it writes through TryWriteAsync (:45). Note that Status499ClientClosedRequest is a real constant on ASP.NET Core's StatusCodes, so the non-standard code is spelled symbolically, not as a magic number.
      • -
      • Why it's built this way: it is registered first in the chain (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:154). That position matters: a cancellation propagating out of a CancellationToken would otherwise be claimed by nothing until GlobalExceptionHandler turned it into a 500. It is also why every catch in the cache-touching middleware and limiters filters OperationCanceledException out explicitly rather than swallowing it (for example SoftDeletedUserMiddleware.cs:93 and RedisFixedWindowRateLimiter.cs:147).
      • -
      • Where it's used: AddCommonExceptionHandlers() (DependencyInjection.cs:154); it fires for request-abort propagation from any handler that honors its CancellationToken.
      • -
      • Caveats / not-in-source: whether the 499 body actually reaches a disconnected client is not determinable from this file; the write is attempted unconditionally at :45.
      • +
      • Concept: [Rubric §13, Observability & Operability] assesses whether the signals operators alert on distinguish real faults from normal client behavior. 499 is a non-standard nginx-origin code (class comment, OperationCanceledExceptionHandler.cs:8-12) that monitoring stacks read as "the caller gave up", so client cancellations stop looking like server errors on a dashboard. The same comment draws the other half of the line (:13-14): a cancellation the client did not cause (a downstream HttpClient timeout, an internal token) IS a server error and must still count as one.
      • +
      • Walkthrough: TryHandleAsync (OperationCanceledExceptionHandler.cs:24) applies a two-part guard (:29-33): it returns false unless the exception is an OperationCanceledException and httpContext.RequestAborted.IsCancellationRequested is true. Past the guard it logs at LogWarning with a client-disconnected message (:35), sets StatusCodes.Status499ClientClosedRequest (:37), and still builds a problem document titled "Operation Canceled Exception" (:38-48) which it writes through TryWriteAsync (:50). Note that Status499ClientClosedRequest is a real constant on ASP.NET Core's StatusCodes, so the non-standard code is spelled symbolically, not as a magic number.
      • +
      • Why it's built this way: it is registered first in the chain (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:154). That position matters: a client abort propagating out of a CancellationToken would otherwise be claimed by nothing until GlobalExceptionHandler turned it into a 500. The RequestAborted check is the reverse protection: an OperationCanceledException thrown while the client is still connected (a timeout inside the request) falls through to GlobalExceptionHandler and is answered and logged as the 500 it is, rather than being hidden as a 499 Warning. It is also why every catch in the cache-touching middleware and limiters filters OperationCanceledException out explicitly rather than swallowing it (for example SoftDeletedUserMiddleware.cs:93 and RedisFixedWindowRateLimiter.cs:147).
      • +
      • Where it's used: AddCommonExceptionHandlers() (DependencyInjection.cs:154); it fires for request-abort propagation from any handler that honors its CancellationToken, and only while RequestAborted is signalled.
      • +
      • Caveats / not-in-source: whether the 499 body actually reaches a disconnected client is not determinable from this file; once the guard passes, the write is attempted unconditionally at :50.

      QueryFilterModelBinder

      @@ -1682,7 +1688,7 @@

      QueryFilterModelBinder

    963. Why it's built this way: silent discard plus case-insensitive matching makes the grammar forgiving for hand-built UI query strings, while the hard cap keeps a malicious caller from turning the query string into a reflection amplifier.
    964. -
    965. Where it's used: applied per parameter with [ModelBinder(typeof(QueryFilterModelBinder))] on the list actions of IEntityControllerBase<TEntityDTO, TIdentifierType> (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/IEntityControllerBase.cs:51) and EntityControllerBase<TEntity, TEntityDTO, TIdentifierType> (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:163 for the paged read and :256 for the CSV export).
    966. +
    967. Where it's used: applied per parameter with [ModelBinder(typeof(QueryFilterModelBinder))] on the list actions of IEntityControllerBase<TEntityDTO, TIdentifierType> (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/IEntityControllerBase.cs:51) and EntityControllerBase<TEntity, TEntityDTO, TIdentifierType> (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:166 for the paged read and :256 for the CSV export).
    968. ValidationExceptionHandler

      @@ -1732,7 +1738,7 @@

      ErrorHttpMapping

      • What it is: the single table that maps domain ErrorType values to HTTP status codes, the ranking rule that picks one status for a whole failure, and the builder for the errors extension array that problem documents carry.
      • Depends on: Error, ErrorType, ErrorTypeSeverity, IErrorLocalizer; System.Collections.Frozen and ASP.NET StatusCodes (ErrorHttpMapping.cs:1-4).
      • -
      • Concept: the Result-to-HTTP translation table. This is where the Result pattern meets the HTTP contract. [Rubric §9, API & Contract Design] assesses whether error responses are consistent across the whole surface: because both ApiControllerBase and UnhandledResultFailureFilter call into this one class, they cannot drift (the class doc says exactly that, ErrorHttpMapping.cs:8-13). [Rubric §7, Microservices Readiness] applies to the ranking: the severity order itself lives in MMCA.Common.Shared (MMCA.Common/Source/Core/MMCA.Common.Shared/Abstractions/ErrorTypeSeverity.cs:30) so the gRPC edge classifies the same aggregate identically (ErrorHttpMapping.cs:45-46, and see MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:117). [Rubric §27, i18n] applies through the localizer parameter.
      • +
      • Concept: the Result-to-HTTP translation table. This is where the Result pattern meets the HTTP contract. [Rubric §9, API & Contract Design] assesses whether error responses are consistent across the whole surface: because both ApiControllerBase and UnhandledResultFailureFilter call into this one class, they cannot drift (the class doc says exactly that, ErrorHttpMapping.cs:8-13). [Rubric §7, Microservices Readiness] applies to the ranking: the severity order itself lives in MMCA.Common.Shared (MMCA.Common/Source/Core/MMCA.Common.Shared/Abstractions/ErrorTypeSeverity.cs:30) so the gRPC edge classifies the same aggregate identically (ErrorHttpMapping.cs:45-46, and see MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:122). [Rubric §27, i18n] applies through the localizer parameter.
      • Walkthrough
        • ErrorTypeToStatusCode (ErrorHttpMapping.cs:20-31): a FrozenDictionary<ErrorType, int> with Validation to 400, Invariant to 400, NotFound to 404, Conflict to 409, Unauthorized to 401, Forbidden to 403, UnprocessableEntity to 422, Failure to 400, and Unexpected to 500. FrozenDictionary is the right structure for a table built once at startup and read on every failed request: it trades slower construction for the fastest lookups (:16-19).
        • GetStatusCode(ErrorType) (:37-38) uses GetValueOrDefault(..., Status400BadRequest), so a future error type falls back to 400 rather than throwing inside an error path.
        • @@ -1759,7 +1765,7 @@

          StronglyTypedIdSchemaTransformer

      • Why it's built this way: both mapping methods are internal static rather than private, so StronglyTypedIdParameterTransformer reuses the identical type/format decision for route and query parameters instead of duplicating the switch; the two transformers can never disagree about how one wrapper renders.
      • -
      • Where it's used: registered unconditionally inside AddCommonOpenApi() via options.AddSchemaTransformer(new StronglyTypedIdSchemaTransformer()) on every OpenApiOptions instance (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.cs:102, inside the ConfigureAll<OpenApiOptions> block at :502-506, whose comment states it is inert in a host that declares no wrappers, :498-501). ConfigureAll rather than a single named document is deliberate: AddOpenApi() creates one OpenApiOptions per discovered API version, and the wire shape is identical in every one.
      • +
      • Where it's used: registered unconditionally inside AddCommonOpenApi() via options.AddSchemaTransformer(new StronglyTypedIdSchemaTransformer()) on every OpenApiOptions instance (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.cs:104, inside the ConfigureAll<OpenApiOptions> block at :502-506, whose comment states it is inert in a host that declares no wrappers, :498-501). ConfigureAll rather than a single named document is deliberate: AddOpenApi() creates one OpenApiOptions per discovered API version, and the wire shape is identical in every one.

      TenantResolutionMiddleware

      @@ -1790,15 +1796,15 @@

      StronglyTypedIdParameterTransformer
    969. What it is: an IOpenApiOperationTransformer that rewrites the per-operation parameter schema for a strongly typed identifier bound from a route segment or a query string, the operation-level counterpart to StronglyTypedIdSchemaTransformer.
    970. Depends on: Microsoft.AspNetCore.OpenApi.IOpenApiOperationTransformer, Microsoft.OpenApi.Models.OpenApiParameter/OpenApiSchema; StronglyTypedIdSchemaTransformer.MapType/.MapFormat for the type/format decision, and StronglyTypedId.TryDescribe to recognize a wrapper.
    971. Concept: a component-level schema and a parameter-level schema are generated by two different ASP.NET Core OpenAPI extension points, so fixing the component schema alone leaves an operation's own parameter list still describing the wrapper's object shape; the framework installs one transformer of each kind so both descriptions agree. [Rubric §9, API & Contract Design] applies for the same reason as the sibling type: a client generator reading only the operation's parameters (the common case for a route or query parameter) needs the primitive shape too.
    972. -
    973. Walkthrough: TransformAsync(OpenApiOperation, OpenApiOperationTransformerContext, CancellationToken) (StronglyTypedIdSchemaTransformer.cs:85-117):
        -
      • Null-guards operation and context (:90-91), then exits early when operation.Parameters is null (:93-94).
      • -
      • Iterates context.Description.ParameterDescriptions (:96), one entry per bound parameter on the action. For each, it resolves the effective type as described.ModelMetadata?.ModelType ?? described.Type (:98) and skips it when that type is null or is not a strongly typed identifier per StronglyTypedId.TryDescribe (:99-100).
      • -
      • Matches the description back to the operation's OpenApiParameter by name: operation.Parameters.OfType<OpenApiParameter>().FirstOrDefault(p => string.Equals(p.Name, described.Name, StringComparison.Ordinal)) (:102-104), and skips a miss (:106-107).
      • -
      • Replaces parameter.Schema outright with a new OpenApiSchema { Type = StronglyTypedIdSchemaTransformer.MapType(valueType), Format = StronglyTypedIdSchemaTransformer.MapFormat(valueType) } (:109-113), rather than mutating the existing schema in place, since the wrapper's original parameter schema carries nothing worth preserving.
      • +
      • Walkthrough: TransformAsync(OpenApiOperation, OpenApiOperationTransformerContext, CancellationToken) (StronglyTypedIdSchemaTransformer.cs:77-109):
          +
        • Null-guards operation and context (:82-83), then exits early when operation.Parameters is null (:85-86).
        • +
        • Iterates context.Description.ParameterDescriptions (:88), one entry per bound parameter on the action. For each, it resolves the effective type as described.ModelMetadata?.ModelType ?? described.Type (:90) and skips it when that type is null or is not a strongly typed identifier per StronglyTypedId.TryDescribe (:91-92).
        • +
        • Matches the description back to the operation's OpenApiParameter by name: operation.Parameters.OfType<OpenApiParameter>().FirstOrDefault(p => string.Equals(p.Name, described.Name, StringComparison.Ordinal)) (:94-96), and skips a miss (:98-99).
        • +
        • Replaces parameter.Schema outright with a new OpenApiSchema { Type = StronglyTypedIdSchemaTransformer.MapType(valueType), Format = StronglyTypedIdSchemaTransformer.MapFormat(valueType) } (:101-105), rather than mutating the existing schema in place, since the wrapper's original parameter schema carries nothing worth preserving.
      • Why it's built this way: reusing the sibling transformer's MapType/MapFormat instead of re-deriving the mapping keeps the component schema and every parameter schema for the same wrapper type identical by construction; there is no separate table to fall out of sync.
      • -
      • Where it's used: registered unconditionally alongside its sibling inside AddCommonOpenApi() via options.AddOperationTransformer(new StronglyTypedIdParameterTransformer()) (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.cs:103), inside the same ConfigureAll<OpenApiOptions> block (:502-506) that installs StronglyTypedIdSchemaTransformer.
      • +
      • Where it's used: registered unconditionally alongside its sibling inside AddCommonOpenApi() via options.AddOperationTransformer(new StronglyTypedIdParameterTransformer()) (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.cs:105), inside the same ConfigureAll<OpenApiOptions> block (:502-506) that installs StronglyTypedIdSchemaTransformer.

      UnhandledResultFailureFilter

      @@ -1823,22 +1829,24 @@

      UnhandledResultFailureFilter

      CorrelationIdMiddleware

      -

      MMCA.Common.API · MMCA.Common.API.Middleware · MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/CorrelationIdMiddleware.cs:15 · Level 9 · class (sealed)

      +

      MMCA.Common.API · MMCA.Common.API.Middleware · MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/CorrelationIdMiddleware.cs:18 · Level 9 · class (sealed)

      • What it is: convention-based middleware that establishes one correlation ID per request, publishes it on the scoped ICorrelationContext, and echoes it back to the caller in the X-Correlation-ID response header.
      • Depends on: ICorrelationContext (injected per invoke, so it is resolved from the request scope) and System.Diagnostics.Activity (CorrelationIdMiddleware.cs:1-3).
      • Concept: distributed trace correlation is introduced at ICorrelationContext; this is the type that populates it. [Rubric §13, Observability & Operability] assesses whether one logical operation can be reconstructed across log entries and services: with this middleware second in the pipeline, every later log line, outbox row and downstream call can carry the same ID.
      • Walkthrough
          -
        • HeaderName = "X-Correlation-ID" (CorrelationIdMiddleware.cs:18) is a public const, so clients, tests and downstream code all name the header from one place.
        • -
        • InvokeAsync(HttpContext, ICorrelationContext) (:27): the second parameter is per-invoke injected, which is how convention-based middleware consumes a scoped service from a singleton middleware instance. Both arguments are null-guarded (:29-30).
        • -
        • The resolution waterfall (:32-34): the inbound X-Correlation-ID header wins, else Activity.Current?.TraceId (the W3C trace ID that OpenTelemetry propagates), else ASP.NET's HttpContext.TraceIdentifier. A caller-supplied ID is therefore honored, and the ID always exists.
        • -
        • correlationContext.SetCorrelationId(correlationId) (:36) publishes it, then context.Response.OnStarting(...) (:37-41) registers a callback that writes the response header. Writing it in OnStarting rather than after await next(...) is the only safe way: once the response has begun, header writes throw.
        • -
        • await next(context) (:43) continues the pipeline.
        • +
        • HeaderName = "X-Correlation-ID" (CorrelationIdMiddleware.cs:21) is a public const, so clients, tests and downstream code all name the header from one place.
        • +
        • MaxLength = 64 (:24) is an internal const: the width of the persisted correlation columns (its doc comment at :23).
        • +
        • InvokeAsync(HttpContext, ICorrelationContext) (:33): the second parameter is per-invoke injected, which is how convention-based middleware consumes a scoped service from a singleton middleware instance. Both arguments are null-guarded (:35-36).
        • +
        • The resolution waterfall (:38-41): an inbound X-Correlation-ID header wins when it is not null, empty or whitespace (string.IsNullOrWhiteSpace, :39), and is passed through Truncate (:41); otherwise Activity.Current?.TraceId (the W3C trace ID that OpenTelemetry propagates), else ASP.NET's HttpContext.TraceIdentifier (:40). A caller-supplied ID is therefore honored, a blank one is treated as absent, and the ID always exists.
        • +
        • Truncate (:56) cuts a caller value longer than MaxLength to its first 64 characters with a range slice and returns shorter values unchanged.
        • +
        • correlationContext.SetCorrelationId(correlationId) (:43) publishes it, then context.Response.OnStarting(...) (:44-48) registers a callback that writes the response header. Writing it in OnStarting rather than after await next(...) is the only safe way: once the response has begun, header writes throw.
        • +
        • await next(context) (:50) continues the pipeline.
      • -
      • Why it's built this way: accepting the client's ID makes cross-system correlation possible when the caller already has a trace, and falling back to Activity.Current means the correlation ID and the OpenTelemetry trace ID are the same value in a normally instrumented host.
      • -
      • Where it's used: it is the second step of the default pipeline, right after the exception handler (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/Pipeline/MiddlewarePipelineBuilder.cs:38-40, following ExceptionHandler at :34-36), so everything downstream (including the exception handlers above) logs under a known ID. The step is named MiddlewarePipelineStepNames.CorrelationId (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/Pipeline/MiddlewarePipelineStepNames.cs:19), which is the handle a host uses to insert its own middleware around it. The CQRS logging decorators read the same context.
      • +
      • Why it's built this way: accepting the client's ID makes cross-system correlation possible when the caller already has a trace, and falling back to Activity.Current means the correlation ID and the OpenTelemetry trace ID are the same value in a normally instrumented host. Truncating at the boundary rather than at the database means the id echoed in the response header is exactly the one stored (class comment, :13-15), and treating a blank header as absent keeps an empty value from displacing the trace-ID fallback.
      • +
      • Where it's used: it is the second step of the default pipeline, right after the exception handler (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/Pipeline/MiddlewarePipelineBuilder.cs:38-40, following ExceptionHandler at :34-36), so everything downstream (including the exception handlers above) logs under a known ID. The step is named MiddlewarePipelineStepNames.CorrelationId (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/Pipeline/MiddlewarePipelineStepNames.cs:20), which is the handle a host uses to insert its own middleware around it. The CQRS logging decorators read the same context.

      SoftDeletedUserMiddleware

      @@ -1927,7 +1935,7 @@

      RateLimitAlgorithm

    974. Why it's built this way: an enum rather than a boolean keeps the configuration self-describing in appsettings.json ("Algorithm": "SlidingWindow") and leaves room for a third algorithm without a breaking rename. The distributed Redis path is deliberately not an algorithm value: it is the separate Distributed flag on RateLimitingSettings, because it changes where the counter lives rather than how it replenishes, and RedisFixedWindowRateLimiter implements only the fixed window.
    975. -
    976. Where it's used: RateLimitingSettings.Algorithm defaults it to FixedWindow (MMCA.Common/Source/Presentation/MMCA.Common.API/RateLimiting/RateLimitingSettings.cs:53), and CreateLimitedPartition branches on it (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:237) to build either a SlidingWindowRateLimiterOptions partition (:239-246) or a FixedWindowRateLimiterOptions one (:249-255).
    977. +
    978. Where it's used: RateLimitingSettings.Algorithm defaults it to FixedWindow (MMCA.Common/Source/Presentation/MMCA.Common.API/RateLimiting/RateLimitingSettings.cs:53), and CreateLimitedPartition branches on it (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:239) to build either a SlidingWindowRateLimiterOptions partition (:239-246) or a FixedWindowRateLimiterOptions one (:249-255).
    979. RedisRateLimitLease

      @@ -1951,7 +1959,7 @@

      CommonForwardedHeadersExtensions

    980. Concept (a UI-specific entry point, ordered first). The doc comment (CommonForwardedHeadersExtensions.cs:14-17) states the ordering contract directly: call it FIRST in a server-rendered UI host's pipeline, before anything reads the scheme, the host, or the client address (security headers, rate limiting, HTTPS redirection, antiforgery). [Rubric §13, Observability & Operability] and [Rubric §29, Resilience & Business Continuity] both apply: a UI host that reads the wrong scheme before forwarded headers are adopted can mis-evaluate an HTTPS-only cookie or antiforgery check.
    981. Walkthrough: UseCommonUiForwardedHeaders(ForwardedHeaders? headers = null) (:24-25) is a one-line extension member that calls app.UseForwardedHeaders(CommonForwardedHeaders.Create(headers)).
    982. Why it's built this way: a named extension on IApplicationBuilder rather than a bare call to CommonForwardedHeaders.Create() plus UseForwardedHeaders gives UI hosts one call to place first, matching the naming convention of the service-host pipeline members on WebApplicationExtensions. CommonForwardedHeaders's own doc comment cross-references this member (CommonForwardedHeaders.cs:9) so a reader lands on the UI-host entry point from either type.
    983. -
    984. Where it's used: the ADC Blazor UI host calls it first in its pipeline, before UseCommonMiddlewarePipeline-equivalent steps (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:192).
    985. +
    986. Where it's used: the ADC Blazor UI host calls it first in its pipeline, before UseCommonMiddlewarePipeline-equivalent steps (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:204).
    987. Caveats / not-in-source: nothing enforces the "call it first" contract; a host that places it later gets whatever scheme and host the raw connection reports until this call runs.
    988. RateLimitingSettings

      @@ -1970,7 +1978,7 @@

      RateLimitingSettings

    989. HubPathPrefixes (:91, default ["/hubs"]) and AnonymousHubPermitLimit (:99, [Range(1, 1_000_000)], default 60) are a pair added for SEC-ADC-25 (RateLimitingSettings.cs:75-91, :93-99). The global limiter exempts anonymous callers everywhere else on purpose (public reads are output-cached, server-rendered Blazor traffic shares one host IP); a real-time hub is the exception, because a gateway that bypasses /hubs at the edge (ADR-024 requires it, since the hub authenticates from a query-string token the edge cannot read) left anonymous /hubs/*/negotiate traffic metered nowhere. HubPathPrefixes names which paths get that anonymous-IP metering, and AnonymousHubPermitLimit is deliberately generous, since one browser tab reconnecting behind a flaky network legitimately negotiates several times a minute.
    990. -
    991. Why it's built this way: bundling the knobs into one bound object is what let AddCommonRateLimiting grow a configuration overload without changing the five-parameter one, and the "same defaults" rule means the two overloads are observationally identical for a host that configures nothing (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:326-334 builds an instance from the old parameters). The fall-back-rather-than-fail choice on Distributed is deliberate: a rate limiter that refuses to start is a worse outage than one that limits per instance. The AuthIpPermitLimit default of 30 is itself argued in the overload's parameter doc (WebApplicationBuilderExtensions.RateLimiting.cs:318-324): Blazor Server circuits issue the login call server-side, so every Server-circuit user shares the UI host's IP and a tighter cap would throttle real logins.
    992. +
    993. Why it's built this way: bundling the knobs into one bound object is what let AddCommonRateLimiting grow a configuration overload without changing the five-parameter one, and the "same defaults" rule means the two overloads are observationally identical for a host that configures nothing (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:328-336 builds an instance from the old parameters). The fall-back-rather-than-fail choice on Distributed is deliberate: a rate limiter that refuses to start is a worse outage than one that limits per instance. The AuthIpPermitLimit default of 30 is itself argued in the overload's parameter doc (WebApplicationBuilderExtensions.RateLimiting.cs:320-326): Blazor Server circuits issue the login call server-side, so every Server-circuit user shares the UI host's IP and a tighter cap would throttle real logins.
    994. Where it's used: bound in AddCommonRateLimiting(IConfiguration) with configuration.GetSection(RateLimitingSettings.SectionName).Get<RateLimitingSettings>() ?? new RateLimitingSettings() (WebApplicationBuilderExtensions.RateLimiting.cs:348-349), then threaded through every partition selector (GlobalRateLimitPartition at :136, UserPolicyRateLimitPartition at :171, AuthIpRateLimitPartition at :278) and into CreateLimitedPartition (:205-256), which reads Distributed (:214), Algorithm (:237), and SegmentsPerWindow (:242). HubPathPrefixes and AnonymousHubPermitLimit are read by AnonymousPartition (WebApplicationBuilderExtensions.RateLimiting.cs:58-68) and IsAnonymousHubRequest (:78-88, internal so InternalsVisibleTo lets it be unit-tested directly): an unauthenticated request that matches a hub prefix is metered per client IP through CreateLimitedPartition with redisScope: "hub" and allowDistributed: true (:60-67); every other anonymous request keeps taking RateLimitPartition.GetNoLimiter("__anonymous") (:68).
    995. RedisFixedWindowRateLimiter

      @@ -1996,8 +2004,8 @@

      RedisFixedWindowRateLimiter

    996. AcquireAsyncCore (:118) is the real path: stamp the timestamp (:122), reject outright when permitCount > _permitLimit (:124-127), compute the window as GetUtcNow().ToUnixTimeSeconds() / 60 (:129) and build the invariant-culture key (:130), StringIncrementAsync (:135), and, when count <= permitCount (meaning this increment created the key), set a 65-second TTL (:142). That skew past the window length covers clock drift between instances, since an early-expiring key would hand the partition a fresh allowance inside the same window (:139-141). The decision is the final compare (:145). The catch (:147-155) excludes OperationCanceledException, logs at most once per window via Interlocked.Exchange on the static field (:149-152), and returns Acquired.
    997. -
    998. Why it's built this way: partial plus [LoggerMessage] (:158-161) gives a source-generated, allocation-free warning that names the partition and states plainly that requests are permitted uncounted until Redis recovers. The partition key arrives already scoped by the caller (for example global:alice, :58-61) so two policies limiting the same user never share one counter; that scoping is applied in CreateLimitedPartition as $"{redisScope}:{key}" (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:229).
    999. -
    1000. Where it's used: constructed only inside CreateLimitedPartition (WebApplicationBuilderExtensions.RateLimiting.cs:227-229), and only when allowDistributed && settings.Distributed is true (:214) and an IConnectionMultiplexer resolves (:220-222); the logger falls back to NullLogger when the request has no service provider (:224-225, which is also why RequestServices is read through a nullable local, :216-219). When no multiplexer is registered the code falls through to the in-memory limiters rather than failing startup (:232-234). The auth-ip policy passes allowDistributed: false (WebApplicationBuilderExtensions.RateLimiting.cs:291), so login throttling never depends on Redis.
    1001. +
    1002. Why it's built this way: partial plus [LoggerMessage] (:158-161) gives a source-generated, allocation-free warning that names the partition and states plainly that requests are permitted uncounted until Redis recovers. The partition key arrives already scoped by the caller (for example global:alice, :58-61) so two policies limiting the same user never share one counter; that scoping is applied in CreateLimitedPartition as $"{redisScope}:{key}" (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.RateLimiting.cs:231).
    1003. +
    1004. Where it's used: constructed only inside CreateLimitedPartition (WebApplicationBuilderExtensions.RateLimiting.cs:229-231), and only when allowDistributed && settings.Distributed is true (:214) and an IConnectionMultiplexer resolves (:220-222); the logger falls back to NullLogger when the request has no service provider (:224-225, which is also why RequestServices is read through a nullable local, :216-219). When no multiplexer is registered the code falls through to the in-memory limiters rather than failing startup (:232-234). The auth-ip policy passes allowDistributed: false (WebApplicationBuilderExtensions.RateLimiting.cs:293), so login throttling never depends on Redis.
    1005. MiniProfilerExtensions

      @@ -2041,8 +2049,8 @@

      SignalRExtensions

    1006. Depends on: NotificationHub (Infrastructure), PushNotificationSettings, and IOptions<T>.
    1007. Concept (conditional real-time endpoint mapping). [Rubric §6, CQRS & Event-Driven]: the SignalR hub is the real-time delivery arm of the notification pipeline, so mapping it behind a settings gate means a host that does not push notifications simply never opens the endpoint, and the same Program.cs line is safe in every host.
    1008. Walkthrough: MapNotificationHub() (SignalRExtensions.cs:22) resolves IOptions<PushNotificationSettings> through GetService<T>(), which returns null when nothing registered it, and takes ?.Value (:24). Only when settings is { Enabled: true } does it call MapHub<NotificationHub>(settings.HubPath) (:25-28). The doc comment (:16-21) notes it must run after UseCommonMiddlewarePipeline() so authentication and routing are already in place.
    1009. -
    1010. Why it's built this way: GetService rather than GetRequiredService, plus the property-pattern guard, is what makes the call unconditionally safe; it matches the same "always call, no-op if not applicable" convention as the JWKS and OIDC mappers. The hub path is also why the JWT bearer options carry an access_token query-string fallback for /hubs on both authentication paths (WebApplicationBuilderExtensions.cs:605-618 and :556-569): a WebSocket cannot send an Authorization header.
    1011. -
    1012. Where it's used: the ADC Notification service maps it after the shared pipeline (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:289, with the pipeline itself at :244); that host reads the hub path from configuration rather than hard-coding it, which the comment there records (:257-260).
    1013. +
    1014. Why it's built this way: GetService rather than GetRequiredService, plus the property-pattern guard, is what makes the call unconditionally safe; it matches the same "always call, no-op if not applicable" convention as the JWKS and OIDC mappers. The hub path is also why the JWT bearer options carry an access_token query-string fallback for /hubs on both authentication paths (WebApplicationBuilderExtensions.Authentication.cs:111-124 and :162-175): a WebSocket cannot send an Authorization header.
    1015. +
    1016. Where it's used: the ADC Notification service maps it after the shared pipeline (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:276, with the pipeline itself at :244); that host reads the hub path from configuration rather than hard-coding it, which the comment there records (:257-260).
    1017. ModuleHostExtensions

      @@ -2093,19 +2101,19 @@

      WebApplicationBuilderExtensions

    1018. CorsPolicyAllowSpecificOrigins / CorsPolicyAllowAll (WebApplicationBuilderExtensions.cs:25, :28): the two policy names the pipeline chooses between by environment.
    1019. AddCommonApiVersioning() (WebApplicationBuilderExtensions.cs:37): header-based versioning through the api-version reader with AssumeDefaultVersionWhenUnspecified and ReportApiVersions (:44-46), the API explorer group format 'v'VVV and SubstituteApiVersionInUrl (:48-51), then the backfill guard (:54). The comment (:39-41) records that DefaultApiVersion is deliberately not set because 1.0 is already the framework default and restating it trips AV0011/AV0024. See ADR-046.
    1020. AddCommonResponseCompression() (WebApplicationBuilderExtensions.cs:62): Brotli plus Gzip, enabled for HTTPS, both at CompressionLevel.Fastest (:64-76); the comment (:72-74) justifies Fastest for gzip too on fractional-vCPU hosts serving dynamic payloads.
    1021. -
    1022. AddCommonOpenApi() (WebApplicationBuilderExtensions.cs:91): services.AddApiVersioning().AddOpenApi() (:93) plus the backfill guard (:94). It also registers StronglyTypedIdSchemaTransformer and StronglyTypedIdParameterTransformer on every discovered API version through services.ConfigureAll<OpenApiOptions> (ADR-115, :100-104), rather than a named document, because AddOpenApi above creates one OpenApiOptions per discovered version and the wire shape is identical in all of them; the comment (:96-99) notes this is inert in a host that declares no strongly typed identifier wrappers. The comment on the base call (:86-87) notes the parameterless AddApiVersioning() only returns the builder, so options configured by AddCommonApiVersioning accumulate independently of call order. Pair it with MapCommonOpenApi() on OpenApiEndpointExtensions.
    1023. -
    1024. AddApiParameterDescriptorBackfill() (WebApplicationBuilderExtensions.cs:116, private) registers ApiParameterDescriptorBackfillProvider via TryAddEnumerable (:117-118), which de-duplicates on implementation type, so calling both AddCommonApiVersioning and AddCommonOpenApi installs the guard exactly once.
    1025. -
    1026. AddCommonCors(IConfiguration) (WebApplicationBuilderExtensions.cs:124): the restrictive production policy takes its origins from Cors:AllowedOrigins and allowlists four headers and five methods with AllowCredentials (:128-134); the development any-origin policy sits under a justified #pragma warning disable S5122 explaining it is only ever selected when the environment is Development (:136-141). See ADR-082.
    1027. +
    1028. AddCommonOpenApi() (WebApplicationBuilderExtensions.cs:95) registers no OpenAPI document of its own: it installs the backfill guard (:97) and registers StronglyTypedIdSchemaTransformer and StronglyTypedIdParameterTransformer through services.ConfigureAll<OpenApiOptions> (ADR-115, :102-106), rather than on a named document, so whatever document names the host registers are all covered; the comment (:99-101) notes this is inert in a host that declares no strongly typed identifier wrappers. The host must make the services.AddOpenApi() call in its own project (either order relative to this one), because the OpenAPI XML-comment source generator attaches the host's controller summaries by intercepting the AddOpenApi call sites of the project being compiled, and a registration made inside this assembly would carry none of them (doc comment :82-87). Pair it with MapCommonOpenApi() on OpenApiEndpointExtensions, which the same doc comment says fails at startup outside Production when no v1 document was registered (:87-89).
    1029. +
    1030. AddApiParameterDescriptorBackfill() (WebApplicationBuilderExtensions.cs:118, private) registers ApiParameterDescriptorBackfillProvider via TryAddEnumerable (:119-120), which de-duplicates on implementation type, so calling both AddCommonApiVersioning and AddCommonOpenApi installs the guard exactly once (:111-117).
    1031. +
    1032. AddCommonCors(IConfiguration) (WebApplicationBuilderExtensions.cs:126): the restrictive production policy takes its origins from Cors:AllowedOrigins and allowlists four headers and five methods with AllowCredentials (:130-136); the development any-origin policy sits under a justified #pragma warning disable S5122 explaining it is only ever selected when the environment is Development (:138-143). See ADR-082.
    1033. RateLimitPolicyAuthIp (WebApplicationBuilderExtensions.RateLimiting.cs:23): the named "auth-ip" policy for anonymous authentication attempts. Its doc comment (:16-22) states why it exists: the global limiter deliberately no-ops for anonymous traffic and per-account lockout is per-email, which would leave a password spray (one password, many emails) from a single source unthrottled.
    1034. IsRateLimitBypassed(HttpContext) (WebApplicationBuilderExtensions.RateLimiting.cs:42) exempts /health, /alive and /.well-known/* (:43-45), then a mapped gRPC endpoint through IsGrpcEndpoint (:46), all legitimately high-frequency. The gRPC arm used to key on the request's Content-Type header; the remark (:27-36, SEC-Common-44) records why it no longer does: a caller-supplied Content-Type: application/grpc header is unverifiable and used to hand any authenticated account the no-limiter partition, switching off its 300/min cap entirely, while routed endpoint metadata comes from the server's own MapGrpcService registrations and cannot be forged. It is internal rather than private specifically so the exemption logic is unit-testable through InternalsVisibleTo instead of only under a request flood (:37-40).
    1035. AnonymousPartition(HttpContext, RateLimitingSettings) (:58, private) is the partition an unauthenticated request now counts against: RateLimitPartition.GetNoLimiter("__anonymous") everywhere except a real-time hub path, where IsAnonymousHubRequest routes it through CreateLimitedPartition keyed on the client IP with redisScope: "hub" and settings.AnonymousHubPermitLimit (:59-68). The remark (SEC-ADC-25, :52-57) is the reason: a gateway that bypasses /hubs at the edge (ADR-024 requires it, because the hub authenticates from a query-string token the edge cannot read) left an anonymous negotiate flood metered nowhere at all.
    1036. IsAnonymousHubRequest(HttpContext, RateLimitingSettings) (:78) null-guards both arguments (:80-81) and matches the request path against settings.HubPathPrefixes (:85-87); internal for the same InternalsVisibleTo reason.
    1037. GrpcServerMetadataNamespace (:95, the literal "Grpc.AspNetCore.Server") and IsGrpcEndpoint(HttpContext) (:103) together are the routed-metadata check: matched by namespace name so MMCA.Common.API needs no package reference on the gRPC server stack, which only the extracted service hosts take (:90-93), returning true only when the resolved endpoint carries metadata from that namespace (:105-121).
    1038. -
    1039. GlobalRateLimitPartition has two overloads. The permit-count one (:128-129) simply wraps its argument in a RateLimitingSettings and delegates; the settings one (:136) holds the logic: a NoLimiter for bypassed infrastructure (:138-141) and, for unauthenticated callers, a delegate to AnonymousPartition rather than an unconditional NoLimiter (:143-146), otherwise a partition keyed by Identity.Name, then the user-id claim, then the remote IP, then the literal "authenticated" (:148-151), built through CreateLimitedPartition with redisScope: "global", queueLimit: 0 and allowDistributed: true (:153-160).
    1040. -
    1041. UserPolicyRateLimitPartition (:171) is the same shape for the opt-in "UserPolicy" limiter: one bucket per authenticated user, falling back to the client IP and then a shared anonymous bucket (:173-175), with redisScope: "user" and the configured queue limit (:177-184). It was extracted from the inline lambda it used to be so the key selection is unit-testable (:167-169).
    1042. -
    1043. CreateLimitedPartition (:205) is the single place a partition is built, and reading it is the fastest way to understand the whole limiter. When allowDistributed && settings.Distributed it resolves IConnectionMultiplexer through a nullable local, because HttpContext.RequestServices is declared non-nullable but is genuinely null outside a request pipeline such as a bare DefaultHttpContext in a unit test (:214-220). With a connection it returns a partition whose factory builds a RedisFixedWindowRateLimiter over $"{redisScope}:{key}" (:227-229), falling back to a null logger when none is registered (:224-225). Without a connection it deliberately falls through to the in-memory limiters rather than failing startup, so a host that turns the flag on before wiring Redis degrades to per-instance limits instead of losing rate limiting altogether (:232-234). The in-memory branch honours RateLimitAlgorithm: a sliding window with SegmentsPerWindow segments (:237-247) or the default fixed window (:249-255), both over TimeSpan.FromMinutes(1) with QueueProcessingOrder.OldestFirst.
    1044. -
    1045. AuthIpRateLimitPartition (:269 permit-count overload, :278 settings overload) partitions the "auth-ip" policy on the client IP and returns no limiter at all when the IP is unattributable (:282-283). The remark (:262-267) explains the choice: failing open on a null IP beats collapsing every such request into one shared bucket, which would throttle the in-process TestServer and the integration tier to a standstill. It passes allowDistributed: false (:291), so login throttling stays per-instance whatever Distributed says, because per-account login protection already backs it and a login throttle that fails open on a Redis outage is a worse trade than one that stays local (:200-203).
    1046. -
    1047. AddCommonRateLimiting has three overloads (all WebApplicationBuilderExtensions.RateLimiting.cs). The permit-count one (:326) keeps the defaults permitLimit: 100, queueLimit: 2, perUserPermitLimit: 30, globalPermitLimit: 300, authIpPermitLimit: 30 and simply builds a RateLimitingSettings from them (:326-334). The IConfiguration one (:344) binds the RateLimiting section, falling back to a default instance when the section is absent (:348-349). The settings one (:362) does the work: rejection status 429 (:368), the always-on GlobalLimiter (:370-371), the opt-in "FixedPolicy" (:376-383, allowDistributed: false), "UserPolicy" (:385), and auth-ip (:395-397). Two comments carry the reasoning: "FixedPolicy" keeps its name whichever algorithm is configured, because it is referenced by name from [EnableRateLimiting] attributes in three repos and renaming it on a settings change would silently unlimit every endpoint using it (:373-375); and the auth-ip policy takes the client IP from Connection.RemoteIpAddress, which the shared pipeline has already resolved from X-Forwarded-For because UseForwardedHeaders runs before UseRateLimiter (:387-394). The doc comment on the permit-count overload (:296-325) explains why anonymous traffic is deliberately unlimited and why authIpPermitLimit is 30 rather than a tighter 10: Blazor Server circuits issue the login call server-side, so every Server-circuit user shares the UI host's IP. See ADR-019.
    1048. +
    1049. GlobalRateLimitPartition has two overloads. The permit-count one (:129-130) simply wraps its argument in a RateLimitingSettings and delegates; the settings one (:137) holds the logic: a NoLimiter for bypassed infrastructure (:139-142) and, for unauthenticated callers, a delegate to AnonymousPartition rather than an unconditional NoLimiter (:144-147), otherwise a partition keyed by the user-id (subject) claim, then Identity.Name, then the remote IP, then the literal "authenticated" (:149-152), built through CreateLimitedPartition with redisScope: "global", queueLimit: 0 and allowDistributed: true (:154-161). The subject claim leads because it is unique per account, while the name claim carries the full name, which two users can share and would then pool into one bucket (:124-126).
    1050. +
    1051. UserPolicyRateLimitPartition (:172) is the same shape for the opt-in "UserPolicy" limiter: one bucket per authenticated user (subject claim, then name), falling back to the client IP and then a shared anonymous bucket (:174-177), with redisScope: "user" and the configured queue limit (:179-186). It was extracted from the inline lambda it used to be so the key selection is unit-testable (:168-171).
    1052. +
    1053. CreateLimitedPartition (:207) is the single place a partition is built, and reading it is the fastest way to understand the whole limiter. When allowDistributed && settings.Distributed it resolves IConnectionMultiplexer through a nullable local, because HttpContext.RequestServices is declared non-nullable but is genuinely null outside a request pipeline such as a bare DefaultHttpContext in a unit test (:216-222). With a connection it returns a partition whose factory builds a RedisFixedWindowRateLimiter over $"{redisScope}:{key}" (:229-231), falling back to a null logger when none is registered (:226-227). Without a connection it deliberately falls through to the in-memory limiters rather than failing startup, so a host that turns the flag on before wiring Redis degrades to per-instance limits instead of losing rate limiting altogether (:234-236). The in-memory branch honours RateLimitAlgorithm: a sliding window with SegmentsPerWindow segments (:239-249) or the default fixed window (:251-257), both over TimeSpan.FromMinutes(1) with QueueProcessingOrder.OldestFirst.
    1054. +
    1055. AuthIpRateLimitPartition (:271 permit-count overload, :280 settings overload) partitions the "auth-ip" policy on the client IP and returns no limiter at all when the IP is unattributable (:284-285). The remark (:264-269) explains the choice: failing open on a null IP beats collapsing every such request into one shared bucket, which would throttle the in-process TestServer and the integration tier to a standstill. It passes allowDistributed: false (:293), so login throttling stays per-instance whatever Distributed says, because per-account login protection already backs it and a login throttle that fails open on a Redis outage is a worse trade than one that stays local (:202-205).
    1056. +
    1057. AddCommonRateLimiting has three overloads (all WebApplicationBuilderExtensions.RateLimiting.cs). The permit-count one (:328) keeps the defaults permitLimit: 100, queueLimit: 2, perUserPermitLimit: 30, globalPermitLimit: 300, authIpPermitLimit: 30 and simply builds a RateLimitingSettings from them (:328-336). The IConfiguration one (:346) null-guards the configuration (:348), registers RateLimitingSettings as options bound to the RateLimiting section with data-annotation validation on start, so a [Range] violation fails at startup rather than building a limiter that throws on every authenticated request (:350-353), and then reads the same section into the settings it passes on, falling back to a default instance when the section is absent (:355-357). The settings one (:368) does the work: rejection status 429 (:374), the always-on GlobalLimiter (:376-377), the opt-in "FixedPolicy" (:382-389, allowDistributed: false), "UserPolicy" (:391), and auth-ip (:401-403). Two comments carry the reasoning: "FixedPolicy" keeps its name whichever algorithm is configured, because it is referenced by name from [EnableRateLimiting] attributes in three repos and renaming it on a settings change would silently unlimit every endpoint using it (:379-381); and the auth-ip policy takes the client IP from Connection.RemoteIpAddress, which the shared pipeline has already resolved from X-Forwarded-For because UseForwardedHeaders runs before UseRateLimiter (:393-400). The doc comment on the permit-count overload (:298-327) explains why anonymous traffic is deliberately unlimited and why authIpPermitLimit is 30 rather than a tighter 10: Blazor Server circuits issue the login call server-side, so every Server-circuit user shares the UI host's IP. See ADR-019.
    1058. AddForwardedJwtBearer(authority, audience, configuration, environment, requireHttpsMetadata = null) (WebApplicationBuilderExtensions.Authentication.cs:51) is the extracted-service mode. It validates all four required arguments (:58-61), then resolves the metadata posture in three steps: the explicit argument when it is not null, then RequireHttpsMetadataConfigKey, then true everywhere except Development (:63-65). When the resolved value is false outside Development it registers InsecureJwtMetadataWarningStartupFilter so the deviation is logged once at startup (:67-71), and finally delegates to the private AddForwardedJwtBearerCore (:73). Its authority argument is normally the result of JwtAuthorityExtensions.GetRequiredJwtAuthority().
    1059. RequireHttpsMetadataConfigKey (:24, value "Authentication:JwtBearer:RequireHttpsMetadata"): the one configuration key that can override the secure-by-default metadata posture. Its comment (:18-23) narrows the legitimate use to an authority that is genuinely plain HTTP (an internal-ingress h2c service URL) and asks for the justification to be recorded beside the setting.
    1060. AddForwardedJwtBearerCore (:76, private) does the JWT wiring: Authority, Audience and RequireHttpsMetadata (:84-86), validation parameters that deliberately leave ValidIssuer unset so the middleware takes the issuer from the discovery document (:94-99), and ValidAlgorithms = [RsaSha256] as defense against an algorithm-confusion swap (:101-107). It also installs the SignalR access_token query-string fallback for /hubs (:110-124) and then calls AddAuthorizationPolicies() (:127).
    1061. @@ -2114,9 +2122,9 @@

      WebApplicationBuilderExtensions

    1062. BuildValidationParameters(JwtSettings) (:207, also internal static) branches on JwtSigningAlgorithm: RS256 requires RsaPublicKeyPem and throws a message that points at AddForwardedJwtBearer when it is missing (:211-215), imports the PEM into an RSA held for the app lifetime (:217-220, with a justified CA2000 suppression) and pins RS256 (:230); the default HS256 path builds a SymmetricSecurityKey from the validated secret and pins HmacSha256 (:234-247).
    1063. -
    1064. Why it's built this way: two authentication entry points are the framework's monolith-to-microservice hinge (ADR-004): the monolith or the issuing Identity service validates in process against a local key, while an extracted service validates against the issuer's published JWKS with no shared secret. The explicit ValidAlgorithms pin on both paths is deliberate defense in depth rather than trust in the token header. Taking IConfiguration and IHostEnvironment as required arguments on AddForwardedJwtBearer is what makes "HTTPS metadata unless you say otherwise" the default a host cannot forget: the insecure value stays reachable for the deployments that need it, but only through a named key and never silently. On the limiter side, factoring every partition through CreateLimitedPartition is what let the Redis counter and the sliding window arrive without touching a single partition-key rule: the policy names, the bypass list and every key are identical whatever the settings say, and only the permit counts, the algorithm and the counter's location change (WebApplicationBuilderExtensions.RateLimiting.cs:354-358). Keying the gRPC exemption and the hub exemption on server-controlled state (routed endpoint metadata, configured hub path prefixes) rather than caller-supplied headers is the same defense-in-depth posture applied to the exemption list itself, not just to the authenticated-caller partitions.
    1065. -
    1066. Where it's used: every ADC and Store service host calls the builder-side registrations (AddCommonCors, AddCommonApiVersioning, AddCommonRateLimiting, AddCommonResponseCompression) at startup. Identity hosts take the in-process mode while the other services take the forwarded mode, passing builder.Configuration and builder.Environment so the framework resolves the metadata posture (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:327-331, MMCA.Store/Source/Services/MMCA.Store.Sales.Service/Program.cs:179-183). The "auth-ip" policy is applied by attribute on the login and register actions of AuthControllerBase (AuthControllerBase.cs:72 and :94), so every consumer inheriting that base gets it without opting in. The internal partition helpers are exercised directly by WebApplicationBuilderExtensionsTests, RateLimitPartitionTests and RateLimitAlgorithmSelectionTests; the metadata resolution by ForwardedJwtBearerSecurityTests.
    1067. -
    1068. Caveats / not-in-source: the five permit-limit defaults are framework defaults only. What a given deployment actually enforces is whatever the host passes or configures under RateLimiting, and that configuration value is not determinable from this file. Likewise Distributed only takes effect when the host also registers an IConnectionMultiplexer; whether it does is host composition, not this file.
    1069. +
    1070. Why it's built this way: two authentication entry points are the framework's monolith-to-microservice hinge (ADR-004): the monolith or the issuing Identity service validates in process against a local key, while an extracted service validates against the issuer's published JWKS with no shared secret. The explicit ValidAlgorithms pin on both paths is deliberate defense in depth rather than trust in the token header. Taking IConfiguration and IHostEnvironment as required arguments on AddForwardedJwtBearer is what makes "HTTPS metadata unless you say otherwise" the default a host cannot forget: the insecure value stays reachable for the deployments that need it, but only through a named key and never silently. On the limiter side, factoring every partition through CreateLimitedPartition is what let the Redis counter and the sliding window arrive without touching a single partition-key rule: the policy names, the bypass list and every key are identical whatever the settings say, and only the permit counts, the algorithm and the counter's location change (WebApplicationBuilderExtensions.RateLimiting.cs:360-365). Keying the gRPC exemption and the hub exemption on server-controlled state (routed endpoint metadata, configured hub path prefixes) rather than caller-supplied headers is the same defense-in-depth posture applied to the exemption list itself, not just to the authenticated-caller partitions.
    1071. +
    1072. Where it's used: every ADC and Store service host calls the builder-side registrations (AddCommonCors, AddCommonApiVersioning, AddCommonRateLimiting, AddCommonResponseCompression) at startup. Identity hosts take the in-process mode while the other services take the forwarded mode, passing builder.Configuration and builder.Environment so the framework resolves the metadata posture (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:327-331, MMCA.Store/Source/Services/MMCA.Store.Sales.Service/Program.cs:179-183). Each ADC service host pairs its own services.AddOpenApi() with AddCommonOpenApi() (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:317-318). The "auth-ip" policy is applied by attribute on the login and register actions of AuthControllerBase (AuthControllerBase.cs:72 and :96), so every consumer inheriting that base gets it without opting in. The internal partition helpers are exercised directly by WebApplicationBuilderExtensionsTests, RateLimitPartitionTests and RateLimitAlgorithmSelectionTests; the metadata resolution by ForwardedJwtBearerSecurityTests.
    1073. +
    1074. Caveats / not-in-source: the five permit-limit defaults are framework defaults only. What a given deployment actually enforces is whatever the host passes or configures under RateLimiting, and that configuration value is not determinable from this file. Likewise Distributed only takes effect when the host also registers an IConnectionMultiplexer; whether it does is host composition, not this file. The doc comment on the permit-count AddCommonRateLimiting overload still describes the gRPC bypass as application/grpc traffic (WebApplicationBuilderExtensions.RateLimiting.cs:305-307); the code keys it on routed endpoint metadata (IsGrpcEndpoint, :103), so trust the code over that sentence.
    1075. WebApplicationExtensions

      @@ -2127,40 +2135,43 @@

      WebApplicationExtensions

    1076. Depends on: MiddlewarePipelineBuilder (which owns the step list) and SupportedCultures; ASP.NET localization and cookie primitives.
    1077. Concept (one canonical, ordered pipeline, applied through one private helper). [Rubric §13, Observability & Operability] and [Rubric §13, Observability & Operability]: middleware order is behavior, not taste. Correlation must be established before anything downstream logs, and authentication must run before the rate limiter so the per-user partition sees a principal at all. Centralizing the order means a host cannot get it wrong (ADR-079). [Rubric §27, i18n] applies through the localization wiring (ADR-027).
    1078. Walkthrough
        -
      • Two internal constants, PreForwardedSchemeKey (WebApplicationExtensions.cs:24) and PreForwardedHostKey (:33), name the HttpContext.Items slots that the pipeline's PreForwardedCapture step writes before UseForwardedHeaders rewrites scheme and host. The comment on the host key (:24-32) records why: Aspire/DCP injects an X-Forwarded-Host pointing at the canonical launchSettings URL, which internal callers cannot reach.
      • -
      • UseCommonMiddlewarePipeline() (:46) is a one-liner: ApplyPipeline(app, configure: null). Its doc comment (:37-45) states the contract in one sentence worth keeping: the order is data, not prose, named by MiddlewarePipelineStepNames and frozen by the MiddlewarePipelineOrderTestsBase fitness function.
      • -
      • UseCommonMiddlewarePipeline(Action<MiddlewarePipelineBuilder> configure) (:58) is the scoped escape hatch: it null-guards the delegate (:60) and routes through the same helper (:61). The XML doc declares both failure modes, ArgumentNullException and the InvalidOperationException an invariant violation raises (:56-57).
      • -
      • UseCommonRequestLocalization() (:71) builds the supported list from SupportedCultures.All (:73), appends the pseudo-locale in Development only (:78-81), and sets the default plus both supported and supported-UI culture lists (:84-87). It is itself the RequestLocalization step of the default pipeline, and Blazor UI hosts call it explicitly before MapRazorComponents so SSR prerender runs under the right culture (:64-70).
      • -
      • MapCultureEndpoint() (:102) is now a one-line default that calls the new MapCultureEndpoint(bool httpOnly) overload with httpOnly: false, the value it always used. MapCultureEndpoint(bool httpOnly) (:115) does the real work: it maps the anonymous GET /culture/set?culture=&redirectUri= that the culture switcher calls, honors only allowlisted cultures and the pseudo-locale only in Development (:119, :122), writes the standard ASP.NET culture cookie with the caller-chosen HttpOnly (:128-136, with Secure conditional on the environment and both S3330/S2092 deviations justified inline at :124), then local-redirects (:140-141) to force a full reload. The httpOnly parameter (:108-114) exists so a Server-only host that never runs WASM can pass true and keep the cookie out of script reach; a host that serves a WebAssembly client must keep the parameterless overload's false, because the WASM runtime reads the cookie directly on startup (MmcaCultureBootstrap.SetBrowserCultureAsync).
      • -
      • ApplyPipeline (:138, private) is the whole application step: seed the defaults (:140), let the host's delegate adjust them if there is one (:141), then foreach over builder.Build() invoking each step's Configure in order (:143-146). Because both public overloads route through here, the zero-argument path is exactly the validated default pipeline (:133-137).
      • +
      • Two internal constants, PreForwardedSchemeKey (WebApplicationExtensions.cs:24) and PreForwardedHostKey (:35), name the HttpContext.Items slots that the pipeline's PreForwardedCapture step writes before UseForwardedHeaders rewrites scheme and host. The comment on the host key (:26-34) records why: Aspire/DCP injects an X-Forwarded-Host pointing at the canonical launchSettings URL, which internal callers cannot reach.
      • +
      • UseCommonMiddlewarePipeline() (:48) is a one-liner: ApplyPipeline(app, configure: null). Its doc comment (:39-47) states the contract in one sentence worth keeping: the order is data, not prose, named by MiddlewarePipelineStepNames and frozen by the MiddlewarePipelineOrderTestsBase fitness function.
      • +
      • UseCommonMiddlewarePipeline(Action<MiddlewarePipelineBuilder> configure) (:60) is the scoped escape hatch: it null-guards the delegate (:62) and routes through the same helper (:63). The XML doc declares both failure modes, ArgumentNullException and the InvalidOperationException an invariant violation raises (:58-59).
      • +
      • UseCommonRequestLocalization() (:73) builds the supported list from SupportedCultures.All (:75), appends the pseudo-locale in Development only (:80-83), and sets the default plus both supported and supported-UI culture lists (:86-89). It is itself the RequestLocalization step of the default pipeline, and Blazor UI hosts call it explicitly before MapRazorComponents so SSR prerender runs under the right culture (:66-72).
      • +
      • MapCultureEndpoint() (:102) is a one-line default that calls the MapCultureEndpoint(bool httpOnly) overload with httpOnly: false. MapCultureEndpoint(bool httpOnly) (:115) does the real work: it maps the anonymous GET /culture/set?culture=&redirectUri= that the culture switcher calls, honors only allowlisted cultures and the pseudo-locale only in Development (:119, :122), writes the standard ASP.NET culture cookie with the caller-chosen HttpOnly (:125-136, with Secure conditional on the environment and both S3330/S2092 deviations justified inline at :124), then local-redirects (:143-144) to force a full reload. The redirect target passes through IsLocalRedirectTarget first and falls back to / when the requested target is missing or not local (:140-143): Results.LocalRedirect throws at execution for a non-local URL, which on this anonymous endpoint would surface as a 500 rather than a redirect. The httpOnly parameter (:108-114) exists so a Server-only host that never runs WASM can pass true and keep the cookie out of script reach; a host that serves a WebAssembly client must keep the parameterless overload's false, because the WASM runtime reads the cookie directly on startup (MmcaCultureBootstrap.SetBrowserCultureAsync).
      • +
      • IsLocalRedirectTarget(string?) (:158, private) applies the rule LocalRedirect itself enforces, ahead of it (:151-157): a non-blank value with a single leading / that is not followed by a second / or a \, since browsers treat either of those as a protocol-relative URL to another host (:159-161).
      • +
      • ApplyPipeline (:168, private) is the whole application step: seed the defaults (:170), let the host's delegate adjust them if there is one (:171), then foreach over builder.Build() invoking each step's Configure in order (:173-176). Because both public overloads route through here, the zero-argument path is exactly the validated default pipeline (:163-167).
    1079. Why it's built this way: pushing the step list out into MiddlewarePipelineBuilder and keeping only ApplyPipeline here is what lets the order be inspected and asserted while the entry point stays a single line in a host's Program.cs. The configure-then-Build sequence is deliberate: the host mutates first and the invariants are checked last, so a customization is judged on its result rather than on the order the host happened to make its edits.
    1080. -
    1081. Where it's used: called once per service host after app.Build(), for example MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:444, MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:268, MMCA.Store/Source/Services/MMCA.Store.Sales.Service/Program.cs:278, and MMCA.Helpdesk/Source/Hosts/MMCA.Helpdesk.Web/Program.cs:130. The Blazor UI hosts instead call the localization and culture-endpoint members directly (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:220 and :169).
    1082. +
    1083. Where it's used: called once per service host after app.Build(), for example MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:444, MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:262, MMCA.Store/Source/Services/MMCA.Store.Sales.Service/Program.cs:299, and MMCA.Helpdesk/Source/Hosts/MMCA.Helpdesk.Web/Program.cs:142. The Blazor UI hosts instead call the localization and culture-endpoint members directly (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:232 and :271).
    1084. Caveats / not-in-source: a host that maps additional endpoints (SignalR hubs, minimal-API endpoints, app-association documents) does so after this call; the framework cannot enforce that ordering, it only documents it on the members that require it (for example SignalRExtensions.MapNotificationHub, MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/SignalRExtensions.cs:16-21). Note also that MMCA.Common.UI ships a different WebApplicationExtensions (see WebApplicationExtensions in the UI framework chapter); the two share a name and nothing else.
    1085. DatabaseInitializationExtensions

      -

      MMCA.Common.API · MMCA.Common.API.Startup · MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:20 · Level 13 · class (static, extension block)

      +

      MMCA.Common.API · MMCA.Common.API.Startup · MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:28 · Level 15 · class (static, two extension blocks)

        -
      • What it is: the shared startup routine every downstream host calls once after app.Build(). It walks each physical data source the host actually uses, creates or migrates that schema according to the configured strategy, repeats the pass for every tenant that keeps its own copy of a source, and finally runs each enabled module's seeder.
      • -
      • Depends on: IEntityDataSourceRegistry, IDataSourceResolver, IDbContextFactory, DataSourceKey and its DataSource engine enum, PhysicalDataSource, TenantDataSourceTargets / TenantDataSourceTarget, ITenantContext, TenancySettings, ApplicationSettings, and ModuleLoader. Externally it uses only IServiceProvider scoping, IOptions<T>, and EF Core's DatabaseFacade (MigrateAsync, EnsureCreatedAsync, GetPendingMigrationsAsync).
      • +
      • What it is: the shared startup routine every downstream host calls once after app.Build(), either directly or through a WebApplication wrapper that skips it during build-time OpenAPI generation. It walks each physical data source the host actually uses, creates or migrates that schema according to the configured strategy, repeats the pass for every tenant that keeps its own copy of a source, and finally runs each enabled module's seeder.
      • +
      • Depends on: IEntityDataSourceRegistry, IDataSourceResolver, IDbContextFactory, DataSourceKey and its DataSource engine enum, PhysicalDataSource, TenantDataSourceTargets / TenantDataSourceTarget, ITenantContext, TenancySettings, ApplicationSettings, ModuleLoader, and ModuleHostContext for the WebApplication wrapper. Externally it uses only IServiceProvider scoping, IOptions<T>, the host's environment and configuration, and EF Core's DatabaseFacade (MigrateAsync, EnsureCreatedAsync, GetPendingMigrationsAsync).
      • Concept (strategy-driven, per-source database initialization). [Rubric §8, Data Architecture] assesses deliberate persistence decisions including migrations: because of database-per-service (ADR-006) a host may own several physical databases at once, so "initialize the database" is really a loop over sources, and polyglot persistence (ADR-018) means those sources do not all have a migrations pipeline. [Rubric §17, DevOps] assesses how schema reaches an environment: the single DatabaseInitStrategy string is the switch between a development host that self-migrates on boot and a production host whose migrations are applied by the deployment pipeline and which must therefore refuse to start if it finds itself behind. [Rubric §13, Observability & Operability]: every failure path here throws a message that names the offending value or the exact pending migrations plus the command to run, so a bad boot is diagnosable from the startup log alone.
      • -
      • Walkthrough: the entire public surface is one extension member on IServiceProvider (DatabaseInitializationExtensions.cs:22), declared with C# extension(T) syntax like the rest of the framework's DI and startup helpers.
          -
        • InitializeDatabaseAsync(applicationSettings, moduleLoader, cancellationToken) (:35-38) starts by null-guarding all three inputs (:40-42).
        • -
        • Strategy is validated first (:47). EnsureKnownStrategy (:182) accepts only the ordinal strings "Migrate" and "None" (:184-185) and otherwise throws UnknownStrategy (:187), whose message names the valid values (:194-195). The comment (:44-46) gives the reason for doing it up front: a misspelled strategy is a configuration mistake, and the host must stop before the code below has already created the migrationless sources.
        • -
        • It then opens a scope (:49) and warms the entity data-source registry: resolving IEntityDataSourceRegistry and calling GetPhysicalSourcesInUse() (:54-56) scans the configuration assemblies once so entity-to-database routing is deterministic before the first repository call, replacing a lazy model-building side effect (:51-53).
        • -
        • The migrationless pass (:70-87) covers Cosmos, PostgreSQL and SQLite sources in use (:71). A source with an empty connection string is skipped (:75-78), since integration tests may omit one; a source where PhysicalDataSource.UsesMigrations is true is also skipped (:80-83); everything left is created outright with EnsureCreatedAsync (:85-86). Two comments carry the load here. The first (:60-65) says this is the only path that creates such a source: without it the first repository call fails. The second (:67-69) explains the UsesMigrations exclusion: running EnsureCreated against a PostgreSQL or SQLite source that does have a migrations assembly writes the tables with no __EFMigrationsHistory row, after which every migration is both pending and un-appliable because its CREATE TABLE hits an existing table. UsesMigrations itself is the per-engine rule on the source record (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/PhysicalDataSource.cs:41-46): SQL Server always, PostgreSQL and SQLite only with a configured migrations assembly (PostgreSQLMigrationsAssembly / SqliteMigrationsAssembly), Cosmos never.
        • -
        • The strategy switch (:92-102) has exactly two live branches. "Migrate" delegates to IDbContextFactory.MigrateAsync (:95), which applies pending migrations for every migration-owned source in use (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/IDbContextFactory.cs:74-81). "None" calls ThrowIfPendingMigrationsAsync (:98). The default arm rethrows UnknownStrategy (:101), unreachable in practice because of the guard at :47 but kept so the switch is total.
        • -
        • The tenant pass runs next (:104-105) and module seeding last (:111). Seeding deliberately runs on the default scope only; the comment (:107-110) states the rationale: a seeder writes reference data the application needs to boot, no module declares which of its seeders are tenant-scoped, and running one twice against a shared database is worse than not running it per tenant at all.
        • -
        • InitializeTenantDatabasesAsync (:125) resolves IOptions<TenancySettings> with GetService rather than GetRequiredService (:132) and returns immediately when tenancy was never registered or no tenants are configured (:133-136). Otherwise it expands the sources into targets via TenantDataSourceTargets.Expand and keeps only the ones with a tenant id (:138-139), because Expand also emits the shared, tenant-less target for each source, which the pass above already handled. For each target it opens a fresh scope through IServiceScopeFactory.CreateTenantScope(target) (:140), the shared helper (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:134) every other per-tenant background pass (the outbox processor, the internal-command processor, the audit-trail and outbox cleanup jobs) also calls; it creates the scope and, when the target carries a tenant id, calls ITenantContext.SetTenant on it before returning (TenantDataSourceTargets.cs:138-142), so the tenant is always set before anything asks that scope for a context. The remark (:120-124) is still the reason a fresh scope is required at all: the scoped context factory binds one physical database per source for the life of a scope, so reusing the outer scope would keep handing back the shared database.
        • -
        • Per tenant it reads UsesMigrations off the shared resolved source (:150), on the grounds stated at :147-149: a tenant's copy is the same schema on a different connection, the migrations assembly is declared once on the source, and a per-tenant override only replaces the connection string. It then applies the same strategy (:152-171): under "Migrate" a migrated source gets MigrateAsync and anything else gets EnsureCreatedAsync (:155-162), under "None" it defers to ThrowIfTenantPendingMigrationsAsync (:166).
        • -
        • The two production rails. ThrowIfTenantPendingMigrationsAsync (:201) no-ops for a non-migrated source (:207-210), otherwise queries GetPendingMigrationsAsync (:212) and, when anything is pending, throws naming the tenant target, the migrations, and the dotnet ef database update remedy (:218-221). ThrowIfPendingMigrationsAsync (:241) short-circuits on IDbContextFactory.HasPendingMigrationsAsync (:247-250), then builds a per-source breakdown by re-querying each key that passes IsMigrationTarget (:252-261) before throwing (:263-265). IsMigrationTarget (:231-233) mirrors the factory's own rule (a source a migrations pipeline owns, minus an optional non-SQL-Server source left without a connection string) so the breakdown names exactly the sources the factory checked, rather than reporting an empty list (:224-228).
        • -
        -
      • -
      • Why it's built this way: one shared init path keeps every service host consistent, so adding a database engine or a tenant changes this file rather than five Program.cs files. PostgreSQL joining the migrationless pass alongside Cosmos and SQLite (ADR-113) is exactly that: a host that names no PostgreSQLMigrationsAssembly gets the same EnsureCreatedAsync treatment a migrations-less SQLite source already got, with no change to the strategy switch below it. The "None" strategy is the deploy-time guarantee that an app never serves traffic against an un-migrated database when migrations are the pipeline's job, and validating the strategy string before any side effect keeps a typo from silently degrading into "schema untouched, first query fails". The tenant pass exists because nothing else ever opens a per-tenant database (ADR-073), so without it such a database is never created and never migrated (:116-118).
      • -
      • Where it's used: called once per host after app.Build(), before the middleware pipeline is wired: MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:440, MMCA.Store/Source/Services/MMCA.Store.Sales.Service/Program.cs:275, and MMCA.Helpdesk/Source/Hosts/MMCA.Helpdesk.Web/Program.cs:127 are representative; the ADC and Store service hosts pass moduleHost.ApplicationSettings and moduleHost.ModuleLoader off ModuleHostContext (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/ModuleHostContext.cs:52). The migrationless-engine loop and the strategy contract are covered by DatabaseInitializationExtensionsTests (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:42, :95, :143, :184), which exercise SQLite create, SQLite migrate, the "None" failure message, and the unknown-strategy guard.
      • +
      • Walkthrough: the public surface is two extension members in two extension(T) blocks, one on IServiceProvider (DatabaseInitializationExtensions.cs:33) and one on WebApplication (:128), declared with C# extension(T) syntax like the rest of the framework's DI and startup helpers. The class carries a justified CA1708 suppression because multiple extension blocks in one static class trip that rule on compiler-generated grouping members (:24-27).
          +
        • InitializeDatabaseAsync(applicationSettings, moduleLoader, cancellationToken) (:45-48) starts by null-guarding all three inputs (:50-52).
        • +
        • Strategy is validated first (:57). EnsureKnownStrategy (:231) accepts only the ordinal strings "Migrate" and "None" (:233-234) and otherwise throws UnknownStrategy (:236), whose message names the valid values (:243-244). The comment (:54-56) gives the reason for doing it up front: a misspelled strategy is a configuration mistake, and the host must stop before the code below has already created the migrationless sources.
        • +
        • It then opens a scope (:59) and warms the entity data-source registry: resolving IEntityDataSourceRegistry and calling GetPhysicalSourcesInUse() (:64-66) scans the configuration assemblies once so entity-to-database routing is deterministic before the first repository call, replacing a lazy model-building side effect (:61-63).
        • +
        • The migrationless pass (:84-100) walks every source in use with no engine filter (:84). A source with an empty connection string is skipped (:88-91), since integration tests may omit one; a source where PhysicalDataSource.UsesMigrations is true is also skipped (:93-96); everything left is created outright with EnsureCreatedAsync (:98-99). The comments carry the load here. The first (:70-76) says this is the only path that creates such a source (without it the first repository call fails) and that the per-tenant pass repeats it for a tenant's own copy under either strategy. The second (:78-80) explains the UsesMigrations exclusion: running EnsureCreated against a PostgreSQL or SQLite source that does have a migrations assembly writes the tables with no __EFMigrationsHistory row, after which every migration is both pending and un-appliable because its CREATE TABLE hits an existing table. The third (:82-83) is why no engine filter is needed: a SQL Server source always answers UsesMigrations and is skipped, so only a migration-less source reaches EnsureCreated. UsesMigrations itself is the per-engine rule on the source record, read from the engine's MigrationPolicy capability (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/PhysicalDataSource.cs:42-48): Always (SQL Server), WhenAssemblyConfigured (PostgreSQL and SQLite, true only with a configured migrations assembly), Never (Cosmos), per the doc comment above it (PhysicalDataSource.cs:36-39).
        • +
        • The strategy switch (:105-115) has exactly two live branches. "Migrate" delegates to IDbContextFactory.MigrateAsync (:108), which applies pending migrations for every migration-owned source in use (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/IDbContextFactory.cs:74-81). "None" calls ThrowIfPendingMigrationsAsync (:111). The default arm rethrows UnknownStrategy (:114), unreachable in practice because of the guard at :57 but kept so the switch is total.
        • +
        • The tenant pass runs next (:117-118) and module seeding last (:124). Seeding deliberately runs on the default scope only; the comment (:120-123) states the rationale: a seeder writes reference data the application needs to boot, no module declares which of its seeders are tenant-scoped, and running one twice against a shared database is worse than not running it per tenant at all.
        • +
        • InitializeDatabaseUnlessDesignTimeAsync(moduleHost, cancellationToken) (:146) is the WebApplication wrapper the service hosts call. It null-guards both the app and the ModuleHostContext (:150-151), returns a completed task without touching any database when the environment is Development and the MmcaOpenApiDesignTime configuration switch (OpenApiDesignTimeKey, :31) is true (:153-156), and otherwise forwards to InitializeDatabaseAsync with moduleHost.ApplicationSettings and moduleHost.ModuleLoader (:158-161). The remarks (:134-141) give the reason: build-time OpenAPI generation (MmcaGenerateOpenApiDocument) starts the host with no database reachable, and the schema step is the one startup action that throws rather than logs when it cannot connect. Honouring the switch only in Development means a host in any other environment can never be talked out of initializing its schema (ADR-122).
        • +
        • InitializeTenantDatabasesAsync (:175) resolves IOptions<TenancySettings> with GetService rather than GetRequiredService (:182) and returns immediately when tenancy was never registered or no tenants are configured (:183-186). Otherwise it expands the sources into targets via TenantDataSourceTargets.Expand and keeps only the ones with a tenant id (:188-189), because Expand also emits the shared, tenant-less target for each source, which the pass above already handled. For each target it opens a fresh scope through IServiceScopeFactory.CreateTenantScope(target) (:191), the shared helper (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/TenantDataSourceTargets.cs:92) every other per-tenant background pass (the outbox processor, the internal-command processor, the audit-trail and outbox cleanup jobs) also calls; it creates the scope and, when the target carries a tenant id, calls ITenantContext.SetTenant on it before returning (TenantDataSourceTargets.cs:99), so the tenant is always set before anything asks that scope for a context. The remark (:170-174) is still the reason a fresh scope is required at all: the scoped context factory binds one physical database per source for the life of a scope, so reusing the outer scope would keep handing back the shared database.
        • +
        • Per tenant it reads UsesMigrations off the shared resolved source (:199), on the grounds stated at :196-198: a tenant's copy is the same schema on a different connection, the migrations assembly is declared once on the source, and a per-tenant override only replaces the connection string. It then applies the same strategy (:201-220): under "Migrate" a migrated source gets MigrateAsync and anything else gets EnsureCreatedAsync (:204-211), under "None" it defers to ApplyNoneStrategyToTenantAsync (:215).
        • +
        • ApplyNoneStrategyToTenantAsync (:251, private) is the tenant-side "None" branch. A migration-less tenant copy (Cosmos, or PostgreSQL/SQLite with no migrations assembly) is created with EnsureCreatedAsync (:257-261), mirroring the shared migrationless pass, because nothing else ever creates it (:246-250); every other copy goes to ThrowIfTenantPendingMigrationsAsync (:263).
        • +
        • The two production rails. ThrowIfTenantPendingMigrationsAsync (:271) no-ops for a non-migrated source (:277-280), otherwise queries GetPendingMigrationsAsync (:282) and, when anything is pending, throws naming the tenant target, the migrations, and the dotnet ef database update remedy (:288-291). ThrowIfPendingMigrationsAsync (:300) short-circuits on IDbContextFactory.HasPendingMigrationsAsync (:306-309), then builds a per-source breakdown by re-querying each key whose resolved source answers PhysicalDataSource.IsMigrationTarget (:312-320) before throwing (:322-324). IsMigrationTarget now lives on the source record (PhysicalDataSource.cs:57-60): a source that UsesMigrations and either belongs to an engine whose sources require a connection string (SQL Server, so a missing one fails loudly at startup) or has one configured. Its doc comment (PhysicalDataSource.cs:50-56) states that the context factory and this initializer share that one rule, so the breakdown names exactly the sources the factory checked rather than an empty list (:294-299).
        • +
        +
      • +
      • Why it's built this way: one shared init path keeps every service host consistent, so adding a database engine or a tenant changes this file rather than five Program.cs files. PostgreSQL joining the migrationless pass alongside Cosmos and SQLite (ADR-113) is exactly that: a host that names no PostgreSQLMigrationsAssembly gets the same EnsureCreatedAsync treatment a migrations-less SQLite source already got, with no change to the strategy switch below it. The "None" strategy is the deploy-time guarantee that an app never serves traffic against an un-migrated database when migrations are the pipeline's job, and validating the strategy string before any side effect keeps a typo from silently degrading into "schema untouched, first query fails". The tenant pass exists because nothing else ever opens a per-tenant database (ADR-073), so without it such a database is never created and never migrated (:165-169). The "None" tenant branch creating a migration-less copy keeps that true under either strategy, matching what the shared pass already did. Skipping initialization for build-time OpenAPI generation through a Development-only switch, rather than a flag any environment can set, keeps the one exemption from becoming a way to boot production against an untouched schema.
      • +
      • Where it's used: called once per host after app.Build(), before the middleware pipeline is wired. The ADC and Store service hosts call the wrapper, await app.InitializeDatabaseUnlessDesignTimeAsync(moduleHost) (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:441, MMCA.Store/Source/Services/MMCA.Store.Sales.Service/Program.cs:296), which reads ApplicationSettings and ModuleLoader off ModuleHostContext (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/ModuleHostContext.cs:44, :54); Helpdesk calls InitializeDatabaseAsync directly with its own settings and loader (MMCA.Helpdesk/Source/Hosts/MMCA.Helpdesk.Web/Program.cs:139). The migrationless-engine loop and the strategy contract are covered by DatabaseInitializationExtensionsTests (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:43, :97, :145, :183, :233), which exercise SQLite create, SQLite migrate, the "None" failure message, the "None" creation of a migration-less tenant copy, and the unknown-strategy guard.
      • Caveats / not-in-source: which strategy a given deployment runs is configuration, not code. The framework default is "Migrate" (MMCA.Common/Source/Core/MMCA.Common.Application/Settings/ApplicationSettings.cs:43); what any environment overrides it to lives in that app's settings and cannot be read here. The tenant pass is also only as complete as TenancySettings: Expand emits a tenant target only when the tenant declares an override connection string for that source, so a tenant that shares the default database is initialized by the shared pass and never appears in the per-tenant loop.

      SupportedCultures

      @@ -2204,7 +2215,7 @@

      IConcurrencyAware

    1086. Concept introduced (optimistic concurrency, header-only). [Rubric §8, Data Architecture] assesses deliberate persistence: transactions, migrations, soft-delete, audit, and concurrency control. SQL Server's rowversion is a token the database changes on every update of a row. A read DTO carries the current RowVersion so the client can state a precondition on the next write, and the persistence layer can then refuse a conflicting concurrent edit instead of silently overwriting it. The doc comment (IConcurrencyAware.cs:3-7) spells out the full round trip: DTO to ETag to If-Match to SetOriginalRowVersion. [Rubric §9, API & Contract Design] applies too, because the precondition is expressed in HTTP's own vocabulary rather than in a body field.
    1087. Walkthrough: one property, byte[] RowVersion { get; init; } (IConcurrencyAware.cs:19), and it is not nullable. The remark (IConcurrencyAware.cs:9-13) is the reason: the token is the If-Match header's whole content, so it is never optional. A DTO read from a persisted aggregate always has one (AuditableBaseEntity.RowVersion is non-null), and a write that states no precondition is refused with 428 Precondition Required rather than falling back to last-write-wins. The same remark states the other half of the current design: update requests carry no token, because the precondition travels in the header alone. Note the [SuppressMessage("Performance", "CA1819")] on IConcurrencyAware.cs:18: exposing a byte[] property normally trips the "properties should not return arrays" analyzer rule, but it is required to round-trip the EF token, and the suppression is justified inline ([Rubric §15, Best Practices]: suppressions are tracked and explained, not blanket-disabled).
    1088. Why it's built this way: ADR-035 puts the precondition on the resource read rather than on each request model, so a module adds conditional writes by making its read DTO concurrency-aware plus tagging the action, and no request record has to loosen its immutability to receive a token. Keeping the contract in Shared means the same interface is visible to the Blazor client that must echo the tag and to the Infrastructure repository that consumes the bytes.
    1089. -
    1090. Where it's used: implemented by the read DTOs across the apps: ADC's SessionDTO (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Sessions/SessionDTO.cs:15) and LivePollDTO (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Shared/LivePolls/LivePollDTO.cs:8), Store's OrderDTO (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Shared/Orders/OrderDTO.cs:9) and InventoryItemDTO (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Shared/Inventory/InventoryItemDTO.cs:9), and Helpdesk's TicketDTO (MMCA.Helpdesk/Source/Modules/Tickets/MMCA.Helpdesk.Tickets.Shared/Tickets/TicketDTO.cs:10). On the server, EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>.SetConcurrencyETag tests a returned DTO for this interface and writes the ETag response header (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:405-413, called from :436). On the client, EntityServiceBase<TEntityDTO, TIdentifierType>.ConcurrencyTagOf pattern-matches the same interface to build the outgoing If-Match value, treating an empty array as no token (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:197-201).
    1091. +
    1092. Where it's used: implemented by the read DTOs across the apps: ADC's SessionDTO (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Sessions/SessionDTO.cs:15) and LivePollDTO (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Shared/LivePolls/LivePollDTO.cs:8), Store's OrderDTO (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Shared/Orders/OrderDTO.cs:9) and InventoryItemDTO (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.Shared/Inventory/InventoryItemDTO.cs:9), and Helpdesk's TicketDTO (MMCA.Helpdesk/Source/Modules/Tickets/MMCA.Helpdesk.Tickets.Shared/Tickets/TicketDTO.cs:10). On the server, EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>.SetConcurrencyETag tests a returned DTO for this interface and writes the ETag response header (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:417-425, called from :436). On the client, EntityServiceBase<TEntityDTO, TIdentifierType>.ConcurrencyTagOf pattern-matches the same interface to build the outgoing If-Match value, treating an empty array as no token (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:197-201).
    1093. Caveats / not-in-source: the "update requests carry no token" half is a convention this interface cannot enforce by itself; it is pinned by the shipped fitness rule UpdateRequests_ShouldNotImplement_IConcurrencyAware (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Domain/ConcurrencyConventionTestsBase.cs:14), which each consumer app subclasses.
    1094. JwtForwardingDelegatingHandler

      @@ -2221,8 +2232,8 @@

      JwtForwardingDelegatingHandler

    1095. Why it's built this way: the no-op-without-HttpContext behavior (:13-14) is what makes the handler safe to attach unconditionally: background work uses its own credentials and is not accidentally given the last request's token. Slicing and re-forming the scheme instead of copying the string verbatim means a malformed or scheme-less inbound value still leaves the client with a valid header.
    1096. -
    1097. Where it's used: wired by the framework's typed-service-client helper, AddTypedServiceClient<TInterface, TImplementation> (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:141), which calls AddHttpContextAccessor() and TryAddTransient<JwtForwardingDelegatingHandler>() (:147-148) and then chains .AddHttpMessageHandler<JwtForwardingDelegatingHandler>() onto the typed HttpClient (:151-153) alongside Aspire service discovery and the standard resilience handler (:156).
    1098. -
    1099. Caveats / not-in-source: no shipped app calls AddTypedServiceClient today. The helper's own doc scopes it to HTTP contracts that do not warrant a gRPC binding (webhook receivers, public REST endpoints, third-party wrappers) and points at MMCA.Common.Grpc.AddTypedGrpcClient as the preferred service-to-service path (DependencyInjection.Messaging.cs:131-134), which is the path the cross-service calls in ADC and Store actually take. This handler is therefore live framework code on a currently unused registration path.
    1100. +
    1101. Where it's used: wired by the framework's typed-service-client helper, AddTypedServiceClient<TInterface, TImplementation> (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:152), which calls AddHttpContextAccessor() and TryAddTransient<JwtForwardingDelegatingHandler>() (:147-148) and then chains .AddHttpMessageHandler<JwtForwardingDelegatingHandler>() onto the typed HttpClient (:151-153) alongside Aspire service discovery and the standard resilience handler (:156).
    1102. +
    1103. Caveats / not-in-source: no shipped app calls AddTypedServiceClient today. The helper's own doc scopes it to HTTP contracts that do not warrant a gRPC binding (webhook receivers, public REST endpoints, third-party wrappers) and points at MMCA.Common.Grpc.AddTypedGrpcClient as the preferred service-to-service path (DependencyInjection.Messaging.cs:136-139), which is the path the cross-service calls in ADC and Store actually take. This handler is therefore live framework code on a currently unused registration path.
    1104. AppAssociationOptions

      @@ -2240,7 +2251,7 @@

      AppAssociationOptions

    1105. Why it's built this way: required init gives compile-checked construction plus immutability once bound (see the primer on required/init immutability), which matches the lifetime: a host builds one instance at startup and the endpoint reads it for the process lifetime. Defaulting the two collections to [] means a host that ships only one platform still constructs a valid document for the other. See ADR-043 for the deep-link decision this serves.
    1106. -
    1107. Where it's used: constructed inline by the ADC Blazor web host and passed straight to the mapper, with the Android and Apple identifiers read from the AppAssociation configuration section (with in-code fallbacks) and the applinks patterns hard-coded to the app's routes (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:266-277). The comment there (:180-183) records a trap worth reading: the Release Android head overrides ApplicationId to ivanball.AtlDevCon, so that is the package Digital Asset Links must name, not the Debug-only id.
    1108. +
    1109. Where it's used: constructed inline by the ADC Blazor web host and passed straight to the mapper, with the Android and Apple identifiers read from the AppAssociation configuration section (with in-code fallbacks) and the applinks patterns hard-coded to the app's routes (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:279-290). The comment there (:180-183) records a trap worth reading: the Release Android head overrides ApplicationId to ivanball.AtlDevCon, so that is the package Digital Asset Links must name, not the Debug-only id.
    1110. Caveats / not-in-source: the type performs no validation. Whether a fingerprint or bundle id is the correct one for the shipped app is only observable at install time on the device.
    1111. MiddlewarePipelineStep

      @@ -2286,19 +2297,21 @@

      MiddlewarePipelineStepNames

      OpenApiEndpointExtensions

      -

      MMCA.Common.API · MMCA.Common.API.Startup.Endpoints · MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/Endpoints/OpenApiEndpointExtensions.cs:22 · Level 0 · class (static, extension block)

      +

      MMCA.Common.API · MMCA.Common.API.Startup.Endpoints · MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/Endpoints/OpenApiEndpointExtensions.cs:25 · Level 0 · class (static, extension block)

      • What it is: two extension(WebApplication app) mapping helpers that expose the generated OpenAPI document and an optional interactive reference UI, both outside Production only.
      • -
      • Depends on: Scalar.AspNetCore (NuGet) for the reference UI and Asp.Versioning's WithDocumentPerVersion() convention. It pairs with AddCommonOpenApi() on WebApplicationBuilderExtensions, which registers the generator.
      • -
      • Concept introduced (the OpenAPI document as a dev/CI artifact, not a public surface). [Rubric §9, API & Contract Design] assesses whether an API has a machine-readable contract and whether that contract is guarded against silent drift. The doc comment (OpenApiEndpointExtensions.cs:7-21) is explicit that the guarding happens at two levels: the framework-owned part of the generated document (the versioned naming convention, the unbound-route-token backfill, the generated ProblemDetails error schema) is diffed against a committed baseline in-repo by OpenApiBaselineTests (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/OpenApi/OpenApiBaselineTests.cs), which fails on any change until the baseline is regenerated deliberately in the same pull request, while each consumer's concrete API surface stays the concern of the contract-snapshot tests in that host's integration tier. Mapping outside Production is the security posture [Rubric §11, Security]: these are internal services reached through the Gateway, which does not route the endpoint.
      • +
      • Depends on: Microsoft.AspNetCore.OpenApi for MapOpenApi() and the keyed IOpenApiDocumentProvider it probes, and Scalar.AspNetCore (NuGet) for the reference UI. It pairs with the host's own services.AddOpenApi(), which registers the v1 document, plus AddCommonOpenApi() on WebApplicationBuilderExtensions, which only configures the registered documents and registers none of its own (doc comment OpenApiEndpointExtensions.cs:13-14, :37-38).
      • +
      • Concept introduced (the OpenAPI document as a dev/CI artifact, not a public surface). [Rubric §9, API & Contract Design] assesses whether an API has a machine-readable contract and whether that contract is guarded against silent drift. The doc comment (OpenApiEndpointExtensions.cs:10-24) is explicit that the guarding happens at two levels: the framework-owned part of the generated document (the unbound-route-token backfill, the generated ProblemDetails error schema) is diffed against a committed baseline in-repo by OpenApiBaselineTests (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/OpenApi/OpenApiBaselineTests.cs), which fails on any change until the baseline is regenerated deliberately in the same pull request, while each consumer's concrete API surface stays the concern of that host's committed document and its contract-snapshot tests. Mapping outside Production is the security posture [Rubric §11, Security]: these are internal services reached through the Gateway, which does not route the endpoint.
      • Walkthrough
          -
        • MapCommonOpenApi() (OpenApiEndpointExtensions.cs:34) calls the built-in MapOpenApi() only when !app.Environment.IsProduction() (:36-39) and chains .WithDocumentPerVersion(), which applies the API-versioning convention so the route resolves one document per discovered API version (/openapi/v1.json for v1.0, doc comment :26-33). It is a no-op in Production and returns app for chaining (:41).
        • -
        • MapCommonScalarUi() (OpenApiEndpointExtensions.cs:52) is the opt-in developer convenience: it calls MapScalarApiReference() outside Production (:54-57), rendering /scalar/{documentName}. Assets ship inside the Scalar.AspNetCore package rather than a CDN (:49-50), so it works offline and in CI.
        • +
        • DefaultDocumentName (OpenApiEndpointExtensions.cs:28) is the private constant "v1": the document name plain AddOpenApi() registers and this endpoint serves.
        • +
        • MapCommonOpenApi() (OpenApiEndpointExtensions.cs:60) does nothing in Production and returns app for chaining (:77). Outside Production (:62) it first resolves IServiceProviderIsKeyedService and checks that a keyed IOpenApiDocumentProvider exists for "v1" (:64-65); if not, it throws InvalidOperationException telling the host to call services.AddOpenApi() in the host project itself, because that is the call the OpenAPI XML-comment generator intercepts to attach the host's summaries (:67-71). A host that calls only the framework method therefore fails at startup (and at build-time document generation) instead of silently serving no document (doc comment :36-41). It then maps the document with app.MapOpenApi().AllowAnonymous() (:74). The anonymous declaration is load-bearing: the framework's fallback authorization policy gates every endpoint that states nothing and /openapi is not one of its exempt prefixes, so an undeclared mapping would answer 401 to the contract-snapshot tests and API tooling (doc comment :43-50).
        • +
        • The method carries a [SuppressMessage("Usage", "AV0030:Missing WithDocumentPerVersion")] (:56-59). The justification records why: the analyzer fires because AddCommonApiVersioning's AddApiExplorer call lives in this assembly, but no versioned OpenAPI registration exists (Asp.Versioning.OpenApi is not referenced, so WithDocumentPerVersion() is not available). The host serves the single plain v1 document.
        • +
        • MapCommonScalarUi() (OpenApiEndpointExtensions.cs:89) is the opt-in developer convenience: it calls MapScalarApiReference() outside Production (:91-94), rendering /scalar/{documentName}, and requires services.AddOpenApi() + AddCommonOpenApi() + MapCommonOpenApi() (doc comment :83-84). Assets ship inside the Scalar.AspNetCore package rather than a CDN (:86-87), so it works offline and in CI.
      • -
      • Why it's built this way: one shared pair of helpers keeps every service's OpenAPI story identical and enforces the "internal spec, not public surface" convention in one place instead of per host. The version-aware document mapping is what keeps the route stable as versions accumulate (ADR-046).
      • -
      • Where it's used: inside this workspace the only caller is the framework's own probe host, OpenApiProbeHost (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/OpenApi/OpenApiProbeHost.cs:40 registers, :59 maps), which is what OpenApiBaselineTests and the ApiParameterDescriptorBackfillProvider tests boot. The ADC and Store service hosts today call the stock ASP.NET pair directly instead. This is the framework offering a convention ahead of the consumers adopting it; OpenApiContractTestsBase (MMCA.Common/Source/Hosting/MMCA.Common.Testing/Conformance/OpenApiContractTestsBase.cs:22) is the base a consumer subclasses once it does.
      • +
      • Why it's built this way: one shared pair of helpers keeps every service's OpenAPI story identical and enforces the "internal spec, not public surface" convention in one place instead of per host. The registration guard turns a missing host AddOpenApi() call (which would otherwise lose the XML-comment summaries or serve nothing) into a startup failure, and the explicit AllowAnonymous() keeps the contract reachable before a client holds a token without widening the fallback policy's exempt list. API versioning itself is ADR-046.
      • +
      • Where it's used: every ADC and Store service host maps the document through it (for example MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:451 and MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:322), and the ADC integration tiers subclass OpenApiContractTestsBase (MMCA.Common/Source/Hosting/MMCA.Common.Testing/Conformance/OpenApiContractTestsBase.cs:22), for example MMCA.ADC/Tests/Integration/MMCA.ADC.Conference.IntegrationTests/Contract/OpenApiContractTests.cs:15. In the framework, OpenApiProbeHost (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/OpenApi/OpenApiProbeHost.cs:40-41 registers, :61 maps) is what OpenApiBaselineTests boots, and MapCommonOpenApiAuthorizationTests (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/OpenApi/MapCommonOpenApiAuthorizationTests.cs:23) pins the anonymous declaration and the Production no-op.

      ICorrelationContext

      @@ -2310,7 +2323,7 @@

      ICorrelationContext

    1112. Concept introduced (request correlation as an injected ambient value). [Rubric §13, Observability & Operability] assesses whether one logical request can be reassembled from disjoint logs; a correlation ID is the value that makes that possible. [Rubric §3, Clean Architecture] is the reason this interface exists at all: handlers, decorators and interceptors need the ID, but they must not reach for HttpContext, so the contract sits in Application and the holder sits in Infrastructure, keeping the dependency arrow pointing inward. [Rubric §13, Observability & Operability]: nothing in the business path sets or threads the ID, it is populated once at the edge and read wherever it is needed.
    1113. Walkthrough: two members. string CorrelationId { get; } (ICorrelationContext.cs:11) is read-only to consumers. void SetCorrelationId(string correlationId) (ICorrelationContext.cs:15) is the single write path, on the same interface, so the edge component that populates the value can do it through the DI-resolved instance without a second, internal interface. The doc comment (ICorrelationContext.cs:3-6) states the intended lifecycle: set by middleware from the X-Correlation-ID header or generated, then carried through the handler pipeline in structured-logging scopes.
    1114. Why it's built this way: a two-member interface is small enough that a test or a background host can supply its own holder, and putting the setter here (rather than on the concrete class) keeps middleware depending on the abstraction. ITenantContext is deliberately modelled on it, and says so: one scoped instance per request, populated once at the edge (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/ITenantContext.cs:11).
    1115. -
    1116. Where it's used: registered as TryAddScoped<ICorrelationContext, CorrelationContext>() (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:288). Populated by CorrelationIdMiddleware, which takes it as a method-injected parameter of InvokeAsync (MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/CorrelationIdMiddleware.cs:27). Consumed by LoggingCommandDecorator<TCommand, TResult> (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/LoggingCommandDecorator.cs:19) and LoggingQueryDecorator<TQuery, TResult> (.../LoggingQueryDecorator.cs:16), which wrap the ID into their log scope for the duration of the pipeline.
    1117. +
    1118. Where it's used: registered as TryAddScoped<ICorrelationContext, CorrelationContext>() (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:298). Populated by CorrelationIdMiddleware, which takes it as a method-injected parameter of InvokeAsync (MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/CorrelationIdMiddleware.cs:33). Consumed by LoggingCommandDecorator<TCommand, TResult> (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/LoggingCommandDecorator.cs:19) and LoggingQueryDecorator<TQuery, TResult> (.../LoggingQueryDecorator.cs:16), which wrap the ID into their log scope for the duration of the pipeline.
    1119. Caveats / not-in-source: not every component can use it. The audit-trail interceptor is a singleton and records the ambient Activity trace id instead of this scoped value, and says so in its own comment (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailEntry.cs:89).
    1120. CorrelationContext

      @@ -2322,7 +2335,7 @@

      CorrelationContext

    1121. Depends on: ICorrelationContext (Level 0, MMCA.Common.Application.Interfaces), the abstraction it implements (CorrelationContext.cs:1, :9). Uses BCL Guid only.
    1122. Concept (where the correlation value actually lives). The pattern itself is introduced at ICorrelationContext; this is its one shipped implementation. [Rubric §13, Observability & Operability]: the eager default is what makes correlation always-on, so a log line emitted from a path with no HTTP request still carries an ID. [Rubric §3, Clean Architecture]: the concrete holder sits in Infrastructure while every consumer depends on the Application interface.
    1123. Walkthrough: CorrelationId (CorrelationContext.cs:12) is { get; private set; }, initialized eagerly to Guid.NewGuid().ToString("N") so a value always exists even if no middleware runs (a background processor, a test path, a gRPC call). SetCorrelationId(string) (CorrelationContext.cs:15-19) overwrites it, guarding the input with ArgumentException.ThrowIfNullOrWhiteSpace (:17) so a blank header can never wipe the ID. The private setter means the only write path is that one guarded method.
    1124. -
    1125. Why it's built this way: a scoped holder with an eager default keeps correlation cheap and unconditional: every code path has an ID without a null check, and inbound requests still adopt the caller's ID for cross-service tracing. The "N" GUID format (32 hex digits, no hyphens) keeps the value compact in log lines and headers. Registration is TryAddScoped (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:288): scoped, so one instance lives per request, and TryAdd, so a host that wants its own implementation can register it first and win.
    1126. +
    1127. Why it's built this way: a scoped holder with an eager default keeps correlation cheap and unconditional: every code path has an ID without a null check, and inbound requests still adopt the caller's ID for cross-service tracing. The "N" GUID format (32 hex digits, no hyphens) keeps the value compact in log lines and headers. Registration is TryAddScoped (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:298): scoped, so one instance lives per request, and TryAdd, so a host that wants its own implementation can register it first and win.
    1128. Where it's used: CorrelationIdMiddleware resolves it per request and calls SetCorrelationId with the inbound X-Correlation-ID header, falling back to the current Activity trace ID and then to HttpContext.TraceIdentifier (MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/CorrelationIdMiddleware.cs:32-36), then echoes the value back on the response through OnStarting (:37-41). Downstream, LoggingCommandDecorator<TCommand, TResult> and LoggingQueryDecorator<TQuery, TResult> take it as a constructor dependency and wrap the ID into their log scope for the full pipeline duration.
    1129. BaseLookup<TIdentifierType>

      @@ -2333,9 +2346,9 @@

      BaseLookup<TIdentifierType>

    1130. What it is: a minimal DTO for dropdown and autocomplete lookups: just Id and Name.
    1131. Depends on: IBaseDTO<TIdentifierType> (Level 0), which it implements (BaseLookup.cs:8).
    1132. Concept (right-sized response shapes). [Rubric §9, API & Contract Design] assesses whether responses are shaped to their consumer rather than dumping full entities. Instead of returning a full entity DTO to populate a <select> element, the system returns BaseLookup<T>, carrying only the id and the display name; this cuts wire size and avoids coupling the UI to full entity shapes it does not need. Both Id (BaseLookup.cs:12) and Name (BaseLookup.cs:15) are required, so hand-written construction is compile-checked, and record equality gives value semantics for free.
    1133. -
    1134. Walkthrough: the type itself is four lines, but its interesting half lives in the repository that projects into it. EFReadRepository<TEntity, TIdentifierType>.GetAllForLookupAsync (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:222) takes the name of the display property and lets the database do the shaping. The selector is built once per (entity type, property name) pair into a cache (:247-248), binding Id and the named property into a BaseLookup<TIdentifierType> via Expression.MemberInit (:262-268). Note the consequence for required: an expression-tree MemberInit constructs the record without the compiler's required-member check, which is legal because required is a compile-time contract, not a runtime one. Above the repository the shape travels as a CollectionResult<T> through EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>.GetAllForLookupAsync (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:308-323).
    1135. +
    1136. Walkthrough: the type itself is four lines, but its interesting half lives in the repository that projects into it. EFReadRepository<TEntity, TIdentifierType>.GetAllForLookupAsync (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:223) takes the name of the display property and lets the database do the shaping. The selector is built once per (entity type, property name) pair into a cache (:247-248), binding Id and the named property into a BaseLookup<TIdentifierType> via Expression.MemberInit (:262-268). Note the consequence for required: an expression-tree MemberInit constructs the record without the compiler's required-member check, which is legal because required is a compile-time contract, not a runtime one. Above the repository the shape travels as a CollectionResult<T> through EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>.GetAllForLookupAsync (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:320-335).
    1137. Why it's built this way: one shared lookup shape means the UI's generic select components bind to a single type regardless of which entity fills them, and projecting inside the SQL query (rather than materializing entities and mapping) keeps a lookup list cheap [Rubric §12, Performance & Scalability]; caching the compiled expression per property name keeps the reflection cost to the first call.
    1138. -
    1139. Where it's used: returned by the lookup path at every layer: the read repository (EFReadRepository.cs:222), IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, the controller base (EntityControllerBase.cs:308), and the UI's IEntityService<TEntityDTO, TIdentifierType>.
    1140. +
    1141. Where it's used: returned by the lookup path at every layer: the read repository (EFReadRepository.cs:223), IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, the controller base (EntityControllerBase.cs:320), and the UI's IEntityService<TEntityDTO, TIdentifierType>.
    1142. AppAssociationEndpointExtensions

      @@ -2353,7 +2366,7 @@

      AppAssociationEndpointExtensions

    1143. Why it's built this way: building the payload once at map time avoids a per-request allocation for a document that never changes [Rubric §12, Performance & Scalability], and holding the RFC 8615 well-known paths as public constants keeps them from drifting between hosts or between the endpoint and any gateway forwarding rule.
    1144. -
    1145. Where it's used: the ADC Blazor web host maps them once at startup (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:267), which is the host that ships a companion MAUI Hybrid app. Both documents' exact shapes are asserted by AppAssociationEndpointTests (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Startup/AppAssociationEndpointTests.cs).
    1146. +
    1147. Where it's used: the ADC Blazor web host maps them once at startup (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:280), which is the host that ships a companion MAUI Hybrid app. Both documents' exact shapes are asserted by AppAssociationEndpointTests (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Startup/AppAssociationEndpointTests.cs).
    1148. JwksEndpointExtensions

      @@ -2408,7 +2421,7 @@

      SupportsIfMatchAttribute

    1149. Why it's built this way: making the attribute its own filter keeps adoption to a single line on an action with no host wiring, which matters for a framework whose consumers upgrade in lockstep (ADR-035). Routing the token through HttpContext.Items instead of through the bound model is what removed reflection from the request path entirely: there is no per-type property lookup, and request records stay init-only as the immutability fitness rules require.
    1150. -
    1151. Where it's used: applied across all three consumer apps on writes whose stale-view risk is real: ADC's EventsController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:215, :264, :297), SessionsController (.../SessionsController.cs:319) and SessionQuestionsController (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.API/Controllers/SessionQuestionsController.cs:134, :160, :186); Store's OrdersController (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/OrdersController.cs:293, :325, :385, each reading the token with SupportsIfMatchAttribute.RequiredToken(HttpContext) at :306, :338, :401) and InventoryItemsController (.../InventoryItemsController.cs:91, :124); and Helpdesk's TicketsController (MMCA.Helpdesk/Source/Modules/Tickets/MMCA.Helpdesk.Tickets.API/Controllers/TicketsController.cs:113, :141, :209). ADC pins the convention with a ConditionalWriteConventionTests fitness test that asserts the attribute is present on the endpoints that need it (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.API.Tests/Conventions/ConditionalWriteConventionTests.cs:31). The read half of the round trip is EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>.SetConcurrencyETag (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:405-413), which gives the client the tag to send back.
    1152. +
    1153. Where it's used: applied across all three consumer apps on writes whose stale-view risk is real: ADC's EventsController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:222, :264, :297), SessionsController (.../SessionsController.cs:319) and SessionQuestionsController (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.API/Controllers/SessionQuestionsController.cs:134, :160, :186); Store's OrdersController (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.API/Controllers/OrdersController.cs:293, :325, :385, each reading the token with SupportsIfMatchAttribute.RequiredToken(HttpContext) at :306, :338, :401) and InventoryItemsController (.../InventoryItemsController.cs:91, :124); and Helpdesk's TicketsController (MMCA.Helpdesk/Source/Modules/Tickets/MMCA.Helpdesk.Tickets.API/Controllers/TicketsController.cs:113, :141, :209). ADC pins the convention with a ConditionalWriteConventionTests fitness test that asserts the attribute is present on the endpoints that need it (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.API.Tests/Conventions/ConditionalWriteConventionTests.cs:31). The read half of the round trip is EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>.SetConcurrencyETag (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:417-425), which gives the client the tag to send back.
    1154. MiddlewarePipelineBuilder

      @@ -2478,7 +2491,7 @@

      CurrencyJsonConverter

    1155. Why it's built this way: routing serialization and deserialization through Currency.FromCode keeps the single validation gate for currency codes in the value object itself, so the API layer neither duplicates the allowlist nor accepts a Currency the domain would reject.
    1156. -
    1157. Where it's used: registered globally for MVC in DependencyInjection.AddAPI, which chains .AddJsonOptions(...) onto AddControllers and adds an instance to the options converters (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:52-54), so every controller request and response serializes Currency as a string uniformly. That registration is also cited as the precedent for the enumeration converter factory added beside it (MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:54).
    1158. +
    1159. Where it's used: registered globally for MVC in DependencyInjection.AddAPI, which chains .AddJsonOptions(...) onto AddControllers and adds an instance to the options converters (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:52-54), so every controller request and response serializes Currency as a string uniformly. That registration is also cited as the precedent for the enumeration converter factory added beside it (MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:55).
    1160. Caveats / not-in-source: there is a second, same-named converter in the Shared layer, CurrencyJsonConverter (MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Financial/Currency.cs:73), attached to the value object by a [JsonConverter] attribute (Currency.cs:13) so that non-MVC paths (cache, outbox, integration events, typed HttpClient calls) also get string form. The two apply the same input rules and differ only in the exception text (Currency.cs:85 quotes the code) and in returning a nullable Currency? (Currency.cs:76). Which of the two wins for a given payload is a System.Text.Json converter-precedence question (an options-registered converter versus a type-level attribute) and is not determinable from this source alone; since their input rules match, the answer does not change what is accepted.
    1161. UsersAdminControllerBase<TUserDto>

      @@ -2502,23 +2515,23 @@

      UsersAdminControllerBase<TUserDto&g

      DataExportControllerBase<TQuery>

      -

      MMCA.Common.API · MMCA.Common.API.Controllers.Privacy · MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:59 · Level 10 · class (abstract)

      +

      MMCA.Common.API · MMCA.Common.API.Controllers.Privacy · MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:61 · Level 10 · class (abstract)

      • What it is: the shipped base controller for the data-subject export endpoint (GET {route}/{userId}/export, the GDPR/CCPA access and portability request). A subclass supplies its app's query type and a route; the base owns the action, the authorization posture, the feature gate, and the file-download delivery.
      • -
      • Depends on: ApiControllerBase (its base, for HandleFailure at MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/ApiControllerBase.cs:35), IQueryHandler<in TQuery, TResult> closed over UserDataExportDTO, ICurrentUserService, Result and Error, PrivacyFeatures, and the constraint IUserOwnedRequest on TQuery (DataExportControllerBase.cs:59-62). Externals: ASP.NET Core MVC, System.Text.Json, and Microsoft.FeatureManagement.Mvc's [FeatureGate].
      • -
      • Concept (a privacy endpoint shipped as a base class, not a registered controller). [Rubric §30, Compliance/Privacy/Data Governance] assesses whether legal obligations such as subject access, portability, and erasure are first-class code rather than manual operations: the access/portability half of a DSAR is framework code here, so an app gets it by subclassing rather than by re-implementing the dispatch, the ownership posture, and the delivery format. [Rubric §11, Security] shows up as defence in depth: the class-level [Authorize] (:57) demands an authenticated caller, while the handler independently enforces owner-or-privileged-role, so the endpoint cannot leak another subject's data even if a subclass is mounted without its own [Authorize] (:49-53). [Rubric §6, CQRS & Event-Driven Design] covers the [FeatureGate(PrivacyFeatures.DataExport)] (:58, the flag string is "Privacy.DataExport" at MMCA.Common/Source/Core/MMCA.Common.Shared/Privacy/PrivacyFeatures.cs:12): the whole surface stays off, answered by DisabledFeatureHandler's 404, until a host deliberately turns the flag on.
      • +
      • Depends on: ApiControllerBase (its base, for HandleFailure at MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/ApiControllerBase.cs:35), IQueryHandler<in TQuery, TResult> closed over UserDataExportDTO, ICurrentUserService, Result and Error, PrivacyFeatures, and the constraint IUserOwnedRequest on TQuery (DataExportControllerBase.cs:61-64). Externals: ASP.NET Core MVC (including its IOptions<JsonOptions>), System.Text.Json, Microsoft.Extensions.DependencyInjection and Microsoft.Extensions.Options (:6-7), and Microsoft.FeatureManagement.Mvc's [FeatureGate].
      • +
      • Concept (a privacy endpoint shipped as a base class, not a registered controller). [Rubric §30, Compliance/Privacy/Data Governance] assesses whether legal obligations such as subject access, portability, and erasure are first-class code rather than manual operations: the access/portability half of a DSAR is framework code here, so an app gets it by subclassing rather than by re-implementing the dispatch, the ownership posture, and the delivery format. [Rubric §11, Security] shows up as defence in depth: the class-level [Authorize] (:59) demands an authenticated caller, while the handler independently enforces owner-or-privileged-role, so the endpoint cannot leak another subject's data even if a subclass is mounted without its own [Authorize] (:52-55). [Rubric §6, CQRS & Event-Driven Design] covers the [FeatureGate(PrivacyFeatures.DataExport)] (:60, the flag string is "Privacy.DataExport" at MMCA.Common/Source/Core/MMCA.Common.Shared/Privacy/PrivacyFeatures.cs:12): the whole surface stays off, answered by DisabledFeatureHandler's 404, until a host deliberately turns the flag on.
      • Walkthrough
          -
        • The primary constructor takes the app's query handler and ICurrentUserService (:59-61); the latter is re-exposed as the protected CurrentUserService property (:68) so a subclass can reuse it. ExportContentType = "application/json" (:65) is the one media type the package is served as.
        • -
        • ExportAsync(UserIdentifierType userId, CancellationToken) (:82-84) is routed by the action-level [HttpGet("{userId}/export")] (:77) only: the route prefix lives on the subclass, so a controller routed at Users serves /Users/{userId}/export (:38-42). The declared responses are 200 with a UserDataExportDTO, plus 401/403/404 as ProblemDetails (:78-81).
        • -
        • It reads CurrentUserService.UserId first and, when there is none, short-circuits through HandleFailure with Error.Unauthorized("Privacy.Unauthorized", ...) (:86-90), so even the unauthenticated case comes back as RFC 9457 Problem Details rather than a bare status.
        • -
        • It then builds the app's query through the abstract CreateQuery(userId, currentUserId, CurrentUserService.Role) factory (:92, declared at :119-122), awaits the handler (:93-94), and maps a failed Result through HandleFailure(result.Errors) (:96-99), which is what turns the handler's own ownership refusal into a 403.
        • -
        • On success it serializes the package itself with JsonSerializer.SerializeToUtf8Bytes(export, JsonSerializerOptions.Web) (:107) and returns File(payload, ExportContentType, BuildFileName(...)) (:109). The inline comment (:103-106) is explicit about why: Ok(export) would content-negotiate and render inline, and this document exists to be saved, while JsonSerializerOptions.Web keeps the payload byte-shape identical to every other response the API produces.
        • -
        • BuildFileName (:133-134) composes user-data-{userId}-{yyyyMMdd}.json with CultureInfo.InvariantCulture, taking the date from the package's own UserDataExportDTO.GeneratedOn so the file name and the document always agree, and so a saved file sorts and parses the same in every locale (:124-129).
        • +
        • The primary constructor takes the app's query handler and ICurrentUserService (:61-63); the latter is re-exposed as the protected CurrentUserService property (:70) so a subclass can reuse it. ExportContentType = "application/json" (:67) is the one media type the package is served as.
        • +
        • ExportAsync(UserIdentifierType userId, CancellationToken) (:84-86) is routed by the action-level [HttpGet("{userId}/export")] (:79) only: the route prefix lives on the subclass, so a controller routed at Users serves /Users/{userId}/export (:41-43). The declared responses are 200 with a UserDataExportDTO, plus 401/403/404 as ProblemDetails (:80-83).
        • +
        • It reads CurrentUserService.UserId first and, when there is none, short-circuits through HandleFailure with Error.Unauthorized("Privacy.Unauthorized", ...) (:88-92), so even the unauthenticated case comes back as RFC 9457 Problem Details rather than a bare status.
        • +
        • It then builds the app's query through the abstract CreateQuery(userId, currentUserId, CurrentUserService.Role) factory (:94, declared at :123-126), awaits the handler (:95-96), and maps a failed Result through HandleFailure(result.Errors) (:98-101), which is what turns the handler's own ownership refusal into a 403.
        • +
        • On success it serializes the package itself with JsonSerializer.SerializeToUtf8Bytes(export, json) (:111) and returns File(payload, ExportContentType, BuildFileName(...)) (:113). The options are the host's MVC JSON options, resolved per request as HttpContext.RequestServices?.GetService<IOptions<JsonOptions>>()?.Value.JsonSerializerOptions, with JsonSerializerOptions.Web only as the fallback when none resolve (:109-110). The inline comment (:105-108) is explicit about why: Ok(export) would content-negotiate and render inline, and this document exists to be saved, while reusing the MVC options (the host's converters included) keeps the payload byte-shape identical to every other response the API produces.
        • +
        • BuildFileName (:137-138) composes user-data-{userId}-{yyyyMMdd}.json with CultureInfo.InvariantCulture, taking the date from the package's own UserDataExportDTO.GeneratedOn so the file name and the document always agree, and so a saved file sorts and parses the same in every locale (:128-133).
      • -
      • Why it's built this way: ADR-076 hoists the export idiom that ADC and Store each wrote by hand. It ships as an abstract base with a CreateQuery factory rather than as a concrete controller added through an application part because the query type is app-owned (each app's ExportUserDataQuery lives in its own Application assembly), and a concrete controller could not construct a type it cannot see (DataExportControllerBase.cs:43-47). The route staying on the subclass follows the AuthControllerBase precedent: the app owns its URL space (:38-42).
      • -
      • Where it's used: both full apps now derive from it, and each subclass is nothing but a route plus a query factory: ADC's UsersDataExportController (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersDataExportController.cs:26-35) and Store's (MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.API/Controllers/UsersDataExportController.cs:19-29). Both are routed at the literal Users rather than [controller], and both say why: [Route("[controller]")] would resolve to UsersDataExport and move the published path (ADC :19-22, Store :9-13). The framework's own coverage is DataExportControllerBaseTests, whose TestDataExportController is the third subclass in source (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Controllers/Privacy/DataExportControllerBaseTests.cs:270).
      • +
      • Why it's built this way: ADR-076 hoists the export idiom that ADC and Store each wrote by hand. It ships as an abstract base with a CreateQuery factory rather than as a concrete controller added through an application part because the query type is app-owned (each app's ExportUserDataQuery lives in its own Application assembly), and a concrete controller could not construct a type it cannot see (DataExportControllerBase.cs:46-49). The route staying on the subclass follows the AuthControllerBase precedent: the app owns its URL space (:41-43).
      • +
      • Where it's used: both full apps now derive from it, and each subclass is nothing but a route plus a query factory: ADC's UsersDataExportController (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersDataExportController.cs:26-35) and Store's (MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.API/Controllers/UsersDataExportController.cs:19-29). Both are routed at the literal Users rather than [controller], and both say why: [Route("[controller]")] would resolve to UsersDataExport and move the published path (ADC :19-22, Store :9-13). The framework's own coverage is DataExportControllerBaseTests, whose TestDataExportController is the third subclass in source (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Controllers/Privacy/DataExportControllerBaseTests.cs:267).

      RolesAdminControllerBase

      diff --git a/docs/onboarding/group-13-grpc-contracts.html b/docs/onboarding/group-13-grpc-contracts.html index 03a08936..c99d7853 100644 --- a/docs/onboarding/group-13-grpc-contracts.html +++ b/docs/onboarding/group-13-grpc-contracts.html @@ -169,7 +169,7 @@

      13. gRPC & Inter-Service Contracts

      The cast of types in this group is small, and all but one lives in MMCA.Common.Grpc: a transport-only package that, per Clean Architecture, depends on Shared only (it must never couple to Domain, Application, or Infrastructure; see primer §1). - There are six: DependencyInjection (the registration surface, + There are seven: DependencyInjection (the registration surface, AddGrpcServiceDefaults() server-side and AddTypedGrpcClient<TClient>(serviceName) client-side, at MMCA.Common/Source/Presentation/MMCA.Common.Grpc/DependencyInjection.cs:25), GrpcResultExceptionInterceptor (server-side: turns a failed @@ -178,7 +178,9 @@

      13. gRPC & Inter-Service Contracts caller's bearer token downstream), ResultGrpcExtensions (the whole Result to RpcException mapping, in both directions), ResultFailureException (the in-band carrier between a service method and - that interceptor), and the lone MMCA.Common.Shared marker + that interceptor), GrpcWireFormat (the shared string encodings for timestamps + and money, at MMCA.Common/Source/Presentation/MMCA.Common.Grpc/GrpcWireFormat.cs:30), and the lone + MMCA.Common.Shared marker ServiceContractAttribute (MMCA.Common/Source/Core/MMCA.Common.Shared/Abstractions/ServiceContractAttribute.cs:21), which tags the wire surface of an extracted service. The concrete .proto definitions and the typed clients they @@ -221,14 +223,14 @@

      13. gRPC & Inter-Service Contracts resolved interface is always the gRPC adapter pointing at the extracted peer. Ordering is not left to chance: each host registers these helpers as steps inside services.AddMmcaApplicationPipeline(pipeline => pipeline.Register(moduleHost.RegisterModules).Register(s => s.AddConferenceSessionValidationClient())...) - (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:279-283), so module discovery runs + (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:281-285), so module discovery runs first and the client replacements run after it, in declaration order.

      Result over the wire, the outbound half. The codebase's pervasive Result pattern (errors as values, not exceptions; see primer §2) survives the hop intact. On the server, a gRPC service implementation calls the inner C# service, gets back a Result, and calls result.ThrowIfFailure() (from ResultGrpcExtensions, - MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:69); see + MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:70); see SessionBookmarksGrpcService (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Grpc/SessionBookmarksGrpcService.cs:39). That guard throws a ResultFailureException @@ -239,27 +241,32 @@

      13. gRPC & Inter-Service Contracts AddGrpcServiceDefaults() catches it for all four server call shapes (unary, server-, client-, and duplex-streaming, at GrpcResultExceptionInterceptor.cs:22, :42, :63, :83), logs it through a source-generated LoggerMessage (GrpcResultExceptionInterceptor.cs:140), and rethrows - errors.ToRpcException(). That encoder (ResultGrpcExtensions.cs:112) picks the status from the + errors.ToRpcException(). That encoder (ResultGrpcExtensions.cs:117) picks the status from the most severe error rather than the first, via ErrorTypeSeverity.MostSevere - (ResultGrpcExtensions.cs:117), so an aggregate built by Result.Combine cannot be downgraded by + (ResultGrpcExtensions.cs:122), so an aggregate built by Result.Combine cannot be downgraded by error ordering; the ErrorType to StatusCode table itself is a FrozenDictionary - (ResultGrpcExtensions.cs:35-47) that mirrors the HTTP mapping in + (ResultGrpcExtensions.cs:36-48) that mirrors the HTTP mapping in ErrorHttpMapping used by ApiControllerBase. Every error is then serialized into the trailers as error-{i}-code, -message, -type, and (when non-empty) -source - and -target entries (ResultGrpcExtensions.cs:125-140).

      + and -target entries (ResultGrpcExtensions.cs:130-145). Because gRPC text metadata is printable + ASCII only, the message, source, and target values are percent-encoded as UTF-8 for every character + outside that range and for % itself (ResultGrpcExtensions.cs:284-311), so an accented name or a + newline in a validation message cannot break the trailer at the transport; a value that is already + plain printable ASCII goes on the wire unchanged (ResultGrpcExtensions.cs:286-289).

      Result over the wire, the inbound half. The same class owns the decoder, so the round trip is closed by framework code rather than by hand-rolled parsing in each adapter. Metadata.ToErrors() - (ResultGrpcExtensions.cs:165) walks error-{i}-code from index zero and stops at the first gap, - matching the contiguous layout the encoder writes, and an unrecognized error-{i}-type falls back to - ErrorType.Failure instead of throwing (ResultGrpcExtensions.cs:269-272), so a newer peer that adds + (ResultGrpcExtensions.cs:171) walks error-{i}-code from index zero and stops at the first gap, + matching the contiguous layout the encoder writes, percent-decodes the message, source, and target + (ResultGrpcExtensions.cs:189-192, :314-315), and an unrecognized error-{i}-type falls back to + ErrorType.Failure instead of throwing (ResultGrpcExtensions.cs:275-278), so a newer peer that adds an error type cannot break an older client. On top of that, RpcException.ToResult() and - ToResult<T>() (ResultGrpcExtensions.cs:210 and :234) hand the caller a failed Result directly: + ToResult<T>() (ResultGrpcExtensions.cs:216 and :240) hand the caller a failed Result directly: structured trailers win when present, and a pure transport fault that carries none (a reset connection, an exceeded deadline) degrades to a single ErrorType.Failure error coded Grpc.{StatusCode} and stamped with the calling member's name via [CallerMemberName] - (ResultGrpcExtensions.cs:285-289). A client adapter's catch block is therefore one line: + (ResultGrpcExtensions.cs:328-332). A client adapter's catch block is therefore one line: return ex.ToResult(); (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Contracts/SessionBookmarkValidationServiceGrpcAdapter.cs:58). That symmetry, one error model over two transports, is the [Rubric §9, API & Contract Design] and @@ -270,22 +277,46 @@

      13. gRPC & Inter-Service Contracts placeholder detail "Unspecified failure"; the interceptor keeps StatusCode.Internal for that case and substitutes the real message (plus any inner exception's message) rather than synthesizing an Error.Failure, which would map to InvalidArgument and wrongly blame the caller.

      +

      Values protobuf has no lossless scalar for. Errors are not the only thing both ends must agree on + byte for byte. GrpcWireFormat is a static class that writes the two encodings + the hand-written services and adapters share, so the format lives in one place rather than in each + .proto mapping. Timestamps travel as ISO 8601 round-trip ("O") strings: FormatUtc stamps + DateTimeKind.Utc before formatting (GrpcWireFormat.cs:42-43), because SQL Server hands columns + back as Unspecified and "O" would otherwise drop the Z, and ParseUtc reads either form, with + or without the marker, as UTC on the same instant through AssumeUniversal | AdjustToUniversal + (GrpcWireFormat.cs:37, :61-62), which keeps a rolling deploy with an older replica on the other end + working. An optional timestamp maps "absent" to the empty string, since proto3 has no null string + (FormatUtcOrEmpty / ParseUtcOrNull, GrpcWireFormat.cs:51-52, :71-72). Money travels as an + invariant-culture decimal string (GrpcWireFormat.cs:77, :83-84), so a price is never rounded + through a double and never misread under a comma-decimal locale. The Parse* methods throw + FormatException on a corrupt peer payload, treating it as a fault; an adapter that wants to map a + malformed amount to its own answer uses TryParseDecimal instead (GrpcWireFormat.cs:93-94). In ADC + the data-subject export edges are the users: Engagement's server writes FormatUtc + (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Grpc/UserEngagementExportGrpcService.cs:50) + and its adapter reads ParseUtc + (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Contracts/UserEngagementExportServiceGrpcAdapter.cs:45), + and Notification pairs FormatUtcOrEmpty with ParseUtcOrNull for a notification's read time + (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/UserNotificationExportGrpcService.cs:49, + MMCA.ADC/Source/Services/MMCA.ADC.Notification.Contracts/UserNotificationExportServiceGrpcAdapter.cs:44).

      Auth and the network shape. Every typed client wired by AddTypedGrpcClient<TClient> (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/DependencyInjection.cs:87) gets a JwtForwardingClientInterceptor (DependencyInjection.cs:93-98) that copies the inbound Authorization header off the current HttpContext onto the outgoing call's metadata, so the caller's JWT rides along to the downstream service and distributed authorization - works without each handler threading a token by hand. It is a no-op outside an HTTP request, for - example in a background processor - (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/Interceptors/JwtForwardingClientInterceptor.cs:82-85), - and it refuses to duplicate a header a prior interceptor already set - (JwtForwardingClientInterceptor.cs:90-94). It is the gRPC counterpart of the HTTP + works without each handler threading a token by hand. When the inbound request authenticated without + an Authorization header (a SignalR hub connection passes its token as ?access_token=), it + forwards the token saved on the authentication ticket instead, read as access_token and rebuilt as + a Bearer value, while a header that is present always wins + (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/Interceptors/JwtForwardingClientInterceptor.cs:124-132). + It is a no-op outside an HTTP request, for example in a background processor + (JwtForwardingClientInterceptor.cs:91-95, :119-122), and it refuses to duplicate a header a prior + interceptor already set (JwtForwardingClientInterceptor.cs:100-104). It is the gRPC counterpart of the HTTP JwtForwardingDelegatingHandler. The downstream service validates that forwarded token against the issuer's JWKS, not a shared secret (ADR-004; see RsaJwksProvider / IJwksProvider). The server half also sets EnableDetailedErrors = false and adds server reflection - (DependencyInjection.cs:43, :36), so tools like grpcurl can introspect the schema without exception + (DependencyInjection.cs:43, :46), so tools like grpcurl can introspect the schema without exception detail leaking to callers. [Rubric §11, Security] is touched three times over here: federated JWT validation rather than a shared secret, token forwarding that never widens the caller's authority, and detailed errors kept off.

      @@ -300,8 +331,12 @@

      13. gRPC & Inter-Service Contracts can defeat HTTP/2 negotiation. On top of that, AddStandardResilienceHandler gives every gRPC client an explicit Polly pipeline sourced entirely from GrpcResilienceDefaults - (DependencyInjection.cs:127-136): the attempt timeout, total timeout, and retry budget are the same - values the HTTP defaults use, and the circuit-breaker values are spelled out (FailureRatio 0.5, + (DependencyInjection.cs:127-142): the attempt timeout, total timeout, and retry budget are the same + values the HTTP defaults use, but the retry is narrowed: every gRPC call is a POST, so only a failure + to reach the peer at all (an HttpRequestException, such as DNS or connect during a replica + rollover) is retried, never a status or an attempt timeout that may already have reached the handler + and could run the call twice (DependencyInjection.cs:133-137). The circuit-breaker values are + spelled out (FailureRatio 0.5, MinimumThroughput 10, BreakDuration 10 seconds, at MMCA.Common/Source/Core/MMCA.Common.Shared/Resilience/GrpcResilienceDefaults.cs:27-33) precisely because an east-west gRPC call bypasses the Gateway's active health checks @@ -325,18 +360,18 @@

      13. gRPC & Inter-Service Contracts MMCA.ADC/Source/Services/*/Protos/, each with a generated client, a hand-written adapter, and a server-side service class. Reading them as consumer to producer: Engagement to Conference for ISessionBookmarkValidationService and IEventLiveValidationService - (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:281-282), Engagement to Notification - for the best-effort live-channel push (Program.cs:283, replacing the framework's + (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:283-284), Engagement to Notification + for the best-effort live-channel push (Program.cs:285, replacing the framework's NullLiveChannelPublisher behind ILiveChannelPublisher), Conference to Engagement for IBookmarkCountService on the speaker dashboard - (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:409), Notification to Identity for - attendee user ids (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:225), and + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:411), Notification to Identity for + attendee user ids (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:222), and Identity to Engagement plus Identity to Notification for the cross-service data-subject export - aggregation (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:316-317). Server sides are + aggregation (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:315-316). Server sides are mapped in each host with AddGrpcServiceDefaults() plus app.MapGrpcService<...>(), mostly behind .RequireAuthorization() - (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:421, :396-397).

      + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:424, :396-397).

      The startup-ordering edge worth knowing. Conference and Engagement call each other, so the AppHost gives Engagement a WithReference(conference).WaitFor(conference) but the reverse Conference to Engagement edge only a WithReference with no WaitFor @@ -372,20 +407,85 @@

      13. gRPC & Inter-Service Contracts executable governance keeping this transport genuinely at the edge [Rubric §34, Architecture Governance & Documentation] and [Rubric §15, Best Practices & Code Quality]. Generated gRPC client classes need no attribute: they are part of the contract surface by virtue of their .proto.

      +

      GrpcWireFormat

      +
      +

      MMCA.Common.Grpc · MMCA.Common.Grpc · MMCA.Common/Source/Presentation/MMCA.Common.Grpc/GrpcWireFormat.cs:30 · Level 0 · class (static)

      +
      +
        +
      • What it is: the shared string encodings a hand-written gRPC service and its client adapter use + for the two values protobuf has no lossless scalar for: UTC timestamps, written as ISO 8601 + round-trip ("O") strings, and money, written as invariant-culture decimal strings. Both ends of a + contract call these helpers, so the wire format is defined once (doc summary, + GrpcWireFormat.cs:4-8).
      • +
      • Depends on: System.Globalization (CultureInfo, DateTimeStyles, NumberStyles, BCL, + GrpcWireFormat.cs:1) only. Nothing first-party and nothing from Grpc.Core: it is pure string + formatting that happens to live in the transport package.
      • +
      • Concept introduced, a single wire encoding for non-scalar values. [Rubric §9, API & Contract Design] (assesses whether a contract has one unambiguous wire shape): the format of a timestamp or an + amount is part of the contract, so a producer that writes "O" and a consumer that parses with the + thread culture would disagree silently; routing both ends through one class removes that choice from + every adapter. [Rubric §29, Resilience & Business Continuity] (assesses tolerance of partial + failure and mixed-version operation): ParseUtc accepts a timestamp with or without the Z marker + because a rolling deploy can leave a replica that still emits the marker-less form on the other end + (lines 15-16).
      • +
      • Walkthrough: one private constant, then format/parse pairs.
          +
        • UtcStyles (GrpcWireFormat.cs:37) is AssumeUniversal | AdjustToUniversal. The comment above it + (lines 32-36) records why: a suffix-less value would otherwise parse as Unspecified, and + AssumeUniversal alone yields Local; RoundtripKind is deliberately absent because + DateTime.Parse rejects it alongside either Assume* or AdjustToUniversal.
        • +
        • FormatUtc(DateTime) (line 42) calls DateTime.SpecifyKind(value, DateTimeKind.Utc) and formats + with "O" under CultureInfo.InvariantCulture, so the output always ends in Z. + FormatUtcOrEmpty(DateTime?) (line 51) writes the empty string for null, because proto3 has no + null string.
        • +
        • ParseUtc(string) (line 61) is DateTime.Parse(value, InvariantCulture, UtcStyles), yielding + DateTimeKind.Utc on the same instant whether or not the marker was present. + ParseUtcOrNull(string?) (line 71) maps the empty string (also what a peer predating the field + sends) back to null.
        • +
        • FormatDecimal(decimal) (line 77) writes value.ToString(InvariantCulture), keeping the scale (for + example 1234.50). ParseDecimal(string) (line 83) parses with NumberStyles.Number under the + invariant culture, and TryParseDecimal(string?, out decimal) (line 93) is the non-throwing + variant.
        • +
        +
      • +
      • Why it's built this way: the doc remarks (lines 9-28) give three reasons. SQL Server hands + DateTime columns back as Unspecified, and "O" omits the Z for that kind, so FormatUtc + stamps Utc before formatting: the stored values are already UTC, so a value is relabelled, never + converted. A double would round a price, and the invariant culture on both ends keeps "1234.50" + from being read back as a different number under a comma-decimal locale. Finally, ParseUtc and + ParseDecimal throw FormatException on a malformed value, treating a corrupt peer payload as a + fault; an adapter that wants its own answer for bad input uses TryParseDecimal and decides itself.
      • +
      • Where it's used: the ADC export contracts on both sides of the wire. Server side, + UserEngagementExportGrpcService + (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Grpc/UserEngagementExportGrpcService.cs:50,56,65) + and UserNotificationExportGrpcService + (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Grpc/UserNotificationExportGrpcService.cs:47,49) + call FormatUtc / FormatUtcOrEmpty; client side, the matching adapters + UserEngagementExportServiceGrpcAdapter + (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Contracts/UserEngagementExportServiceGrpcAdapter.cs:45,51,60) + and UserNotificationExportServiceGrpcAdapter + (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Contracts/UserNotificationExportServiceGrpcAdapter.cs:42,44) + call ParseUtc / ParseUtcOrNull. Covered by GrpcWireFormatTests + (MMCA.Common/Tests/Presentation/MMCA.Common.Grpc.Tests/GrpcWireFormatTests.cs:18).
      • +
      • Caveats / not-in-source: none of the usage sites read for this section calls the decimal + helpers; their consumers (if any outside ADC) are not determinable from the usage list in hand.
      • +

      JwtForwardingClientInterceptor

      -

      MMCA.Common.Grpc · MMCA.Common.Grpc.Interceptors · MMCA.Common/Source/Presentation/MMCA.Common.Grpc/Interceptors/JwtForwardingClientInterceptor.cs:19 · Level 0 · class (sealed)

      +

      MMCA.Common.Grpc · MMCA.Common.Grpc.Interceptors · MMCA.Common/Source/Presentation/MMCA.Common.Grpc/Interceptors/JwtForwardingClientInterceptor.cs:27 · Level 0 · class (sealed)

      • What it is: a gRPC client-side interceptor that copies the inbound Authorization header from the current HttpContext onto every outgoing gRPC call's metadata, so the caller's JWT bearer - token rides along to downstream services. It is the gRPC counterpart of the HTTP + token rides along to downstream services. When the request authenticated without that header (a + SignalR hub connection passes its token as ?access_token=), it forwards the token saved on the + authentication ticket instead (class remarks, JwtForwardingClientInterceptor.cs:19-25). It is the gRPC counterpart of the HTTP JwtForwardingDelegatingHandler in the API/Infrastructure layer.
      • Depends on: Grpc.Core.Interceptors.Interceptor (the base class) and Grpc.Core call types (NuGet, see primer §3, "Transport"); Microsoft.AspNetCore.Http.IHttpContextAccessor (ASP.NET Core, injected as a primary-constructor - parameter, JwtForwardingClientInterceptor.cs:19). Nothing first-party: it lives in + parameter, JwtForwardingClientInterceptor.cs:27); IAuthenticateResultFeature and the + AuthenticationProperties.GetTokenValue helper (using Microsoft.AspNetCore.Authentication, + line 3) for the saved-token fallback. Nothing first-party: it lives in MMCA.Common.Grpc, which by the layer rules depends on Shared only and is pure transport (see primer §1).
      • Concept introduced, gRPC interceptors and token forwarding across a service mesh. [Rubric §7, Microservices Readiness] (assesses whether application code talks to abstractions while transport @@ -401,24 +501,33 @@

        JwtForwardingClientInterceptor

        RsaJwksProvider).
      • Walkthrough: members in execution order.
        • private const string AuthorizationHeader = "Authorization" - (JwtForwardingClientInterceptor.cs:21), the single header name.
        • -
        • The five overrides (AsyncUnaryCall line 24, BlockingUnaryCall line 35, - AsyncServerStreamingCall line 46, AsyncClientStreamingCall line 57, AsyncDuplexStreamingCall - line 67) each follow the same three-step shape: + (JwtForwardingClientInterceptor.cs:29), the single header name, and + private const string AccessTokenName = "access_token" (line 31), the name under which + JwtBearerOptions.SaveToken stores the token on the authentication ticket.
        • +
        • The five overrides (AsyncUnaryCall line 34, BlockingUnaryCall line 45, + AsyncServerStreamingCall line 56, AsyncClientStreamingCall line 67, AsyncDuplexStreamingCall + line 77) each follow the same three-step shape: ArgumentNullException.ThrowIfNull(continuation), build a new context via WithForwardedAuthorization(context), then invoke continuation(...). The two streaming variants - whose continuation takes no request argument call continuation(newContext) (lines 63 and 73); + whose continuation takes no request argument call continuation(newContext) (lines 73 and 83); the other three pass (request, newContext).
        • -
        • WithForwardedAuthorization<TRequest, TResponse> (JwtForwardingClientInterceptor.cs:76-99) is the - shared helper. It reads httpContextAccessor.HttpContext?.Request?.Headers.Authorization.ToString() - (line 81); if that is null or empty it returns the context unchanged (lines 82-85), the +
        • WithForwardedAuthorization<TRequest, TResponse> (JwtForwardingClientInterceptor.cs:86-109) is the + shared helper. It asks ResolveAuthorization(httpContextAccessor.HttpContext) for the value to + forward (line 91); if that is null or empty it returns the context unchanged (lines 92-95), the deliberate no-op when there is no HTTP request, for example a background processor or hosted service invoking a gRPC client outside a request. Otherwise it takes the call's existing - Options.Headers (or a fresh Metadata via the collection expression [], line 87), then checks - whether Authorization is already present (lines 90-94) and bails out if a prior interceptor or + Options.Headers (or a fresh Metadata via the collection expression [], line 97), then checks + whether Authorization is already present (lines 100-104) and bails out if a prior interceptor or the caller already set it, so the header is never duplicated. Only then does it headers.Add(...) - (line 96), rebuild the call options with WithHeaders (line 97), and return a new - ClientInterceptorContext carrying context.Method, context.Host, and the new options (line 98).
        • + (line 106), rebuild the call options with WithHeaders (line 107), and return a new + ClientInterceptorContext carrying context.Method, context.Host, and the new options (line 108). +
        • ResolveAuthorization(HttpContext?) (JwtForwardingClientInterceptor.cs:117-133) picks the value. + No HttpContext answers null (lines 119-122). A non-empty inbound Authorization header is + returned as-is and always wins (lines 124-128). Otherwise it reads + httpContext.Features.Get<IAuthenticateResultFeature>()?.AuthenticateResult?.Properties?.GetTokenValue("access_token") + and, when that is non-empty, answers $"Bearer {savedToken}" (lines 130-132). This is what lets a + call made from inside a SignalR hub, where the token arrived on the query string rather than in a + header, still carry the caller's identity downstream.
      • Why it's built this way: sealing the class and overriding all five call shapes makes token @@ -426,7 +535,9 @@

        JwtForwardingClientInterceptor

        interceptor rather than at each call site keeps consumer code transport-agnostic, which is exactly the extraction boundary ADR-007 and ADR-008 want. The - duplicate-header guard means it composes safely with other interceptors.
      • + duplicate-header guard means it composes safely with other interceptors. The saved-token fallback + forwards the token the request actually authenticated with, and only when no header is present, so + it never overrides an explicit Authorization value.
      • Where it's used: registered automatically by AddTypedGrpcClient<TClient> in this group's DependencyInjection (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/DependencyInjection.cs:93,98), so every typed gRPC @@ -543,7 +654,7 @@

        ResultFailureException

        Errors-carrying constructor is the one used.
      • Where it's used: thrown by ThrowIfFailure() and UnwrapOrThrow<T>() in ResultGrpcExtensions - (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:74,91); caught by + (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:75,92); caught by GrpcResultExceptionInterceptor in all four server-handler shapes.
      • Caveats / not-in-source: the three CA1032 constructors produce an instance with no errors, and that case is not free downstream. The interceptor treats it specially (see @@ -611,7 +722,7 @@

        GrpcResultExceptionInterceptor

      ResultGrpcExtensions

      -

      MMCA.Common.Grpc · MMCA.Common.Grpc · MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:29 · Level 3 · class (static)

      +

      MMCA.Common.Grpc · MMCA.Common.Grpc · MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:30 · Level 3 · class (static)

      • What it is: the extension members that bridge Result @@ -623,13 +734,14 @@

        ResultGrpcExtensions

        ErrorType, ErrorTypeSeverity, Result and Result<T> (all via - using MMCA.Common.Shared.Abstractions, ResultGrpcExtensions.cs:7); + using MMCA.Common.Shared.Abstractions, ResultGrpcExtensions.cs:8); ResultFailureException (Level 2); Grpc.Core (NuGet); - System.Collections.Frozen, System.Globalization, and System.Runtime.CompilerServices (BCL).
      • + System.Collections.Frozen, System.Globalization, System.Runtime.CompilerServices, and + System.Text (StringBuilder, Rune, for trailer escaping) (BCL, ResultGrpcExtensions.cs:1-5).
      • Concept introduced, a symmetric wire codec for the Result pattern. [Rubric §9, API & Contract Design] (assesses consistent error shapes across protocols) and [Rubric §7, Microservices Readiness] (the Result pattern behaves identically over HTTP and gRPC). Where ErrorHttpMapping maps ErrorType to HTTP status - codes, ErrorTypeToStatusCode here (lines 35-47) maps it to gRPC StatusCode: + codes, ErrorTypeToStatusCode here (lines 36-48) maps it to gRPC StatusCode: Validation, Invariant, and Failure to InvalidArgument; NotFound to NotFound; Conflict to Aborted; Unauthorized to Unauthenticated; Forbidden to PermissionDenied; UnprocessableEntity to FailedPrecondition; Unexpected to Internal. A FrozenDictionary is the @@ -640,48 +752,54 @@

        ResultGrpcExtensions

        wire shape has exactly one definition.
      • Walkthrough: the class is a set of C# extension(T) blocks (see primer §4), which is why it carries a file-level - [SuppressMessage] for CA1708 (lines 25-28): with multiple extension blocks in one static class the + [SuppressMessage] for CA1708 (lines 26-29): with multiple extension blocks in one static class the analyzer flags the compiler-generated grouping members as case-colliding, a false positive.
          -
        • extension(ErrorType errorType) (line 49) contributes ToGrpcStatusCode() (line 56): +
        • extension(ErrorType errorType) (line 50) contributes ToGrpcStatusCode() (line 57): GetValueOrDefault(errorType, StatusCode.InvalidArgument), so an unmapped error type still produces a valid status.
        • -
        • extension(Result result) (line 60) contributes ThrowIfFailure() (line 69), the guard a gRPC +
        • extension(Result result) (line 61) contributes ThrowIfFailure() (line 70), the guard a gRPC service method calls first: null-check, then - if (result.IsFailure) throw new ResultFailureException(result.Errors) (lines 72-75).
        • -
        • extension<T>(Result<T> result) (line 79) contributes UnwrapOrThrow() (line 86), the typed - variant: throws on failure, otherwise returns result.Value! (line 94).
        • -
        • extension(IReadOnlyList<Error> errors) (line 98) contributes ToRpcException() (line 112), the + if (result.IsFailure) throw new ResultFailureException(result.Errors) (lines 73-76).
        • +
        • extension<T>(Result<T> result) (line 80) contributes UnwrapOrThrow() (line 87), the typed + variant: throws on failure, otherwise returns result.Value! (line 95).
        • +
        • extension(IReadOnlyList<Error> errors) (line 99) contributes ToRpcException() (line 117), the encoder. The status code comes from ErrorTypeSeverity.MostSevere(errors).Type.ToGrpcStatusCode() - (line 117), falling back to StatusCode.Internal for an empty list; the Status.Detail is the - joined "Code: Message" summary or the literal "Unspecified failure" (lines 120-122). It then + (line 122), falling back to StatusCode.Internal for an empty list; the Status.Detail is the + joined "Code: Message" summary or the literal "Unspecified failure" (lines 125-127). It then walks every error and writes structured trailing metadata: error-{i}-code, error-{i}-message, - and error-{i}-type always (lines 128-130), plus error-{i}-source and error-{i}-target only when - non-empty (lines 131-139). Every key is built with CultureInfo.InvariantCulture so the wire form - cannot vary by locale. It returns new RpcException(new Status(statusCode, detail), trailers) - (line 142).
        • -
        • extension(Metadata? trailers) (line 146) contributes ToErrors() (line 165), the exact inverse. - Null or empty trailers decode to [] (lines 167-170). Otherwise it loops from index zero, reading - error-{i}-code and stopping at the first missing code (lines 177-181), which matches the - contiguous layout the encoder writes; a missing message decodes to the empty string and a missing - source or target to null (lines 183-186), mirroring the encoder's omission rule.
        • -
        • extension(RpcException exception) (line 196) contributes ToResult() (line 210) and - ToResult<T>() (line 234), which close the round trip. Both decode exception.Trailers.ToErrors() + and error-{i}-type always (lines 133-135), plus error-{i}-source and error-{i}-target only when + non-empty (lines 136-144). Every key is built with CultureInfo.InvariantCulture so the wire form + cannot vary by locale. The message, source, and target values pass through + EscapeTrailerValue (lines 134, 138, 143); the code and the type name are written raw. It returns + new RpcException(new Status(statusCode, detail), trailers) (line 147).
        • +
        • extension(Metadata? trailers) (line 151) contributes ToErrors() (line 171), the exact inverse. + Null or empty trailers decode to [] (lines 173-176). Otherwise it loops from index zero, reading + error-{i}-code and stopping at the first missing code (lines 183-187), which matches the + contiguous layout the encoder writes; message, source, and target are percent-decoded through + Unescape, a missing message decodes to the empty string, and a missing source or target to null + (lines 189-192), mirroring the encoder's omission rule.
        • +
        • extension(RpcException exception) (line 202) contributes ToResult() (line 216) and + ToResult<T>() (line 240), which close the round trip. Both decode exception.Trailers.ToErrors() and return Result.Failure(errors) when the trailers carried a structured failure, or - Result.Failure(TransportError(exception, source)) when they did not (lines 216-218 and 240-242). + Result.Failure(TransportError(exception, source)) when they did not (lines 222-224 and 246-248). Both take a [CallerMemberName] string source = "" parameter, so the synthesized transport error is stamped with the calling adapter method's name for free.
        • -
        • The private helpers close the file. ErrorFactories (lines 251-263) is a second FrozenDictionary - mapping each ErrorType to its Error factory method, documented (lines 246-250) as a lookup table +
        • The private helpers close the file. ErrorFactories (lines 257-269) is a second FrozenDictionary + mapping each ErrorType to its Error factory method, documented (lines 252-256) as a lookup table rather than a switch so adding an error type stays a one-line entry instead of pushing the decoder past the cyclomatic-complexity ceiling [Rubric §15, Best Practices & Code Quality]. - ParseErrorType (line 269) does a case-sensitive Enum.TryParse and falls back to - ErrorType.Failure, BuildError (line 275) dispatches through the factory table, and - TransportError (line 285) builds the stand-in error coded $"Grpc.{exception.StatusCode}" carrying - exception.Status.Detail.
        • + ParseErrorType (line 275) does a case-sensitive Enum.TryParse and falls back to + ErrorType.Failure. EscapeTrailerValue (line 284) returns the value unchanged when every + character is verbatim-safe, otherwise walks it rune by rune and writes each byte of a non-verbatim + rune's UTF-8 encoding as %XX; IsVerbatimTrailerChar (line 311) defines verbatim-safe as printable + ASCII (' ' to '~') except % itself, so the decoder can tell an escape from a literal. + Unescape (line 314) is Uri.UnescapeDataString, with null staying null. BuildError + (line 318) dispatches through the factory table, and TransportError (line 328) builds the stand-in + error coded $"Grpc.{exception.StatusCode}" carrying exception.Status.Detail.
      • -
      • Why it's built this way: three decisions are worth naming.
          -
        1. The most severe error picks the status, not the first one (line 117). The encoder ranks the list +
        2. Why it's built this way: four decisions are worth naming.
            +
          1. The most severe error picks the status, not the first one (line 122). The encoder ranks the list through ErrorTypeSeverity (MMCA.Common/Source/Core/MMCA.Common.Shared/Abstractions/ErrorTypeSeverity.cs:69, ties keep the earliest error), the same ranking the HTTP edge uses, so an aggregate built by Result.Combine @@ -689,14 +807,19 @@

            ResultGrpcExtensions

            answers Unauthenticated. Ranking picks the status only; all errors still travel in the trailers.
          2. The decoder degrades rather than throws. An unrecognized error-{i}-type falls back to - ErrorType.Failure (line 272) instead of raising, so a newer peer that adds an error type cannot + ErrorType.Failure (line 278) instead of raising, so a newer peer that adds an error type cannot break an older client, and an RpcException with no structured trailers at all (a reset connection, an exceeded deadline) still reaches the caller as a Result failure rather than an exception - (lines 280-289). That is the [Rubric §29, Resilience & Business Continuity] angle: the client-side + (lines 323-333). That is the [Rubric §29, Resilience & Business Continuity] angle: the client-side programming model never changes shape because the network misbehaved.
          3. Trailers carry the full error list, not a flattened string, so the client reconstructs real Error objects with their original Code, Message, Type, Source, and Target. This is what makes the Result pattern survive the hop intact (ADR-007).
          4. +
          5. Trailer values are percent-encoded, not passed raw (doc comment, lines 110-114). gRPC text + metadata is printable ASCII only, so an accented character or a newline in an error message would + break the trailer at the transport. Escaping only what needs it means a plain-ASCII value without + % goes on the wire unchanged, and the decoder's Unescape restores the original string exactly, + so the round trip holds for any message text.
        3. Where it's used: ThrowIfFailure and UnwrapOrThrow are called by the gRPC service @@ -758,18 +881,19 @@

          DependencyInjection

          HttpResilienceDefaults: PooledConnectionLifetime (10 minutes), PooledConnectionIdleTimeout (5 minutes), KeepAlivePingDelay (60 seconds), KeepAlivePingTimeout (30 seconds), and KeepAlivePingPolicy = WithActiveRequests - (MMCA.Common/Source/Core/MMCA.Common.Shared/Resilience/HttpResilienceDefaults.cs:34,37,40,43). - Fourth it layers AddStandardResilienceHandler back on (lines 127-136), setting every knob from + (MMCA.Common/Source/Core/MMCA.Common.Shared/Resilience/HttpResilienceDefaults.cs:36,39,42,45). + Fourth it layers AddStandardResilienceHandler back on (lines 127-142), setting every knob from GrpcResilienceDefaults: a 30-second attempt timeout and 90-second total request timeout re-exposed from the outbound-HTTP path, one retry beyond the initial attempt, and an explicit circuit breaker (60-second sampling window, FailureRatio 0.5, MinimumThroughput 10, BreakDuration 10 seconds) - (MMCA.Common/Source/Core/MMCA.Common.Shared/Resilience/GrpcResilienceDefaults.cs:15-33). It returns - the original IHttpClientBuilder (line 137), not the resilience-pipeline builder, so callers can - keep chaining.
        4. + (MMCA.Common/Source/Core/MMCA.Common.Shared/Resilience/GrpcResilienceDefaults.cs:15-33). The retry + is narrowed by options.Retry.ShouldHandle = args => ValueTask.FromResult(args.Outcome.Exception is HttpRequestException) + (line 137): only a failure to reach the service is retried. It returns the original + IHttpClientBuilder (line 143), not the resilience-pipeline builder, so callers can keep chaining.
      -
    1162. Why it's built this way: three deliberate decisions live here, each documented inline and each +

    1163. Why it's built this way: four deliberate decisions live here, each documented inline and each worth reading before changing anything.

      1. h2c (HTTP/2 cleartext) over http://{serviceName}, not HTTPS (lines 54-62). Aspire's @@ -790,6 +914,12 @@

        DependencyInjection

        directly and bypasses the Gateway's active health checks, so the breaker is the only thing that notices a peer going bad. Timeouts and the retry budget, by contrast, are re-exposed from HttpResilienceDefaults so the two paths cannot drift.
      2. +
      3. The retry predicate is replaced, not left at the default (comment at lines 133-136). Every gRPC + call is an HTTP POST, so the standard handler's unsafe-method opt-out would switch the retry off + entirely. Instead only an HttpRequestException (a DNS or connect failure, for example during an + ACA replica rollover) is retried; a returned status or an attempt timeout may already have reached + the handler, and replaying it could run the call twice. That keeps the single retry safe for + non-idempotent calls [Rubric §29, Resilience & Business Continuity].

      The doc comment is explicit that application code should not consume the generated client directly (lines 66-76): register a hand-written adapter implementing the consuming module's own C# diff --git a/docs/onboarding/group-14-module-system-composition.html b/docs/onboarding/group-14-module-system-composition.html index a11dd125..fa04b2d9 100644 --- a/docs/onboarding/group-14-module-system-composition.html +++ b/docs/onboarding/group-14-module-system-composition.html @@ -241,48 +241,51 @@

      The module contract and stub, remote client) rather than scattered if (moduleEnabled) checks.

      Discovery and Kahn-ordered registration

      ModuleLoader - (MMCA.Common/Source/Core/MMCA.Common.Application/Modules/ModuleLoader.cs:15) is the engine. Its one - DiscoverAndRegister overload (ModuleLoader.cs:58-64) takes the assemblies to scan as a required + (MMCA.Common/Source/Core/MMCA.Common.Application/Modules/ModuleLoader.cs:16) is the engine. Its one + DiscoverAndRegister overload (ModuleLoader.cs:59-65) takes the assemblies to scan as a required parameter: there is no AppDomain-scan convenience, and the parameter doc says why, because an AppDomain scan only sees assemblies already loaded, so a module assembly that is referenced but not - yet touched by any code path would be silently absent from discovery (ModuleLoader.cs:48-53). The - scan guards each GetTypes() call against a throwing assembly so one bad reference logs a warning - instead of aborting the whole pass (ModuleLoader.cs:71-84), then instantiates every concrete - IModule via Activator.CreateInstance (ModuleLoader.cs:86-89) and every concrete + yet touched by any code path would be silently absent from discovery (ModuleLoader.cs:49-54). The + scan guards each GetTypes() call so one bad reference never aborts the whole pass: a + ReflectionTypeLoadException keeps every type that did load, so one unloadable type does not make the + assembly's modules vanish, and any other failure drops just that assembly; both shapes log at Error, + because a module that silently fails to register is an outage that looks like a configuration choice + (ModuleLoader.cs:69-95). It then instantiates every concrete + IModule via Activator.CreateInstance (ModuleLoader.cs:97-100) and every concrete IModuleSeeder into a case-insensitive dictionary keyed by ModuleName - (ModuleLoader.cs:91-94).

      -

      Ordering is Kahn's topological sort (ModuleLoader.cs:271-321) over the modules' declared + (ModuleLoader.cs:102-105).

      +

      Ordering is Kahn's topological sort (ModuleLoader.cs:282-332) over the modules' declared Dependencies. Kahn's algorithm is BFS over a dependency graph: compute each module's in-degree - (count of unprocessed dependencies, ModuleLoader.cs:276), build the reverse adjacency list of - dependents (ModuleLoader.cs:279-292), seed a queue with the zero-in-degree modules - (ModuleLoader.cs:295-296), and as each is emitted decrement its dependents' in-degrees, enqueuing - any that reach zero (ModuleLoader.cs:305-309). A dependency name that was never discovered is - skipped rather than treated as an edge (ModuleLoader.cs:286-287); validation catches it later. If + (count of unprocessed dependencies, ModuleLoader.cs:287), build the reverse adjacency list of + dependents (ModuleLoader.cs:290-303), seed a queue with the zero-in-degree modules + (ModuleLoader.cs:306-307), and as each is emitted decrement its dependents' in-degrees, enqueuing + any that reach zero (ModuleLoader.cs:316-320). A dependency name that was never discovered is + skipped rather than treated as an edge (ModuleLoader.cs:297-298); validation catches it later. If fewer modules come out than went in, the remainder form a cycle and the loader throws with the - offending names (ModuleLoader.cs:313-318). The payoff is an ordering where a module's DI + offending names (ModuleLoader.cs:324-329). The payoff is an ordering where a module's DI registrations always exist before any dependent registers, which matters because the CQRS decorator pipeline (below) can only wrap handlers that are already in the container.

      For each sorted module the loader checks ModulesSettings.IsModuleEnabled - (ModuleLoader.cs:101). A disabled module gets RegisterDisabledStubs called, has the exact service - descriptors that call added recorded (ModuleLoader.cs:107-109), and is listed in - DisabledModuleNames (ModuleLoader.cs:111); an enabled one runs ValidateModuleDependencies then - RegisterEnabledModule (ModuleLoader.cs:115-116) and contributes its seeder if one was found - (ModuleLoader.cs:118-121). Registration is also where per-module configuration is loaded by + (ModuleLoader.cs:112). A disabled module gets RegisterDisabledStubs called, has the exact service + descriptors that call added recorded (ModuleLoader.cs:118-120), and is listed in + DisabledModuleNames (ModuleLoader.cs:122); an enabled one runs ValidateModuleDependencies then + RegisterEnabledModule (ModuleLoader.cs:126-127) and contributes its seeder if one was found + (ModuleLoader.cs:129-132). Registration is also where per-module configuration is loaded by convention: before calling module.Register(...) the loader adds modules.{name}.json and, when an environment name is passed, modules.{name}.{environment}.json to the configuration builder - (ModuleLoader.cs:174-178), so a module can ship its own config file. Dependency validation - (ModuleLoader.cs:125-158) is microservice-aware: a dependency that is disabled in-process but listed + (ModuleLoader.cs:185-189), so a module can ship its own config file. Dependency validation + (ModuleLoader.cs:136-169) is microservice-aware: a dependency that is disabled in-process but listed in that consumer's ModuleSettings RemoteDependencies is treated as satisfied remotely, and only a RequiresDependencies = true module with a genuinely unsatisfied dependency - throws, with an error message that spells out the three ways to fix it (ModuleLoader.cs:139-147). - Every step emits a [LoggerMessage]-generated structured log (ModuleLoader.cs:323-342), so the + throws, with an error message that spells out the three ways to fix it (ModuleLoader.cs:150-158). + Every step emits a [LoggerMessage]-generated structured log (ModuleLoader.cs:334-356), so the startup log tells you exactly which modules loaded, in what order, and how long each took - (ModuleLoader.cs:180-183).

      + (ModuleLoader.cs:191-194).

      Trusting configuration is not quite enough, so the loader offers a second, post-build gate: - ValidateRemoteDependencies(serviceProvider) (ModuleLoader.cs:201-223) walks every remote-declared + ValidateRemoteDependencies(serviceProvider) (ModuleLoader.cs:212-234) walks every remote-declared dependency, resolves each service type the disabled module's stub had registered, throws when one does not resolve at all, and logs a warning when it still resolves to the stub implementation - (ModuleLoader.cs:225-246). That converts a forgotten gRPC-client registration from a first-request + (ModuleLoader.cs:236-257). That converts a forgotten gRPC-client registration from a first-request mystery into a startup failure. It is a capability, not a habit: today only MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs calls it, and no ADC or Store host does. [Rubric §13, Observability & Operability] is the category at play in both @@ -299,7 +302,7 @@

      Discovery and Kahn-ordered regi deliberately does not run inside AddModuleHost: it has to land inside the application pipeline described next, and its position relative to a host's other steps is a per-host decision (ModuleHostContext.cs:12-19). After discovery the loader also drives startup data through - SeedAllAsync (ModuleLoader.cs:255-261), which invokes each collected + SeedAllAsync (ModuleLoader.cs:266-272), which invokes each collected IModuleSeeder.SeedAsync in registration order. IModuleSeeder (MMCA.Common/Source/Core/MMCA.Common.Application/Modules/IModuleSeeder.cs:8) is a two-member @@ -346,7 +349,7 @@

      The two comp (DependencyInjection.cs:189-192). Rather than leaving that as a comment, the framework enforces it. AddApplicationDecorators() finishes by calling SealPipeline, which TryAdds a singleton instance of the private marker DecoratorPipelineSeal - (DependencyInjection.cs:150, :707, :720-721), and every registration entry point that + (DependencyInjection.cs:150, :294, :307-308), and every registration entry point that contributes handlers (ScanModuleApplicationServices, AddEntityCrud, AddEntityUpdateVerb, AddEntityUpdate, AddMmcaApplicationPipeline itself) opens with ThrowIfPipelineSealed, which scans the collection for that marker and throws a message naming the offending call @@ -386,44 +389,44 @@

      The two comp [Rubric §6, CQRS & Event-Driven] and [Rubric §1, SOLID] (open/closed) live here: cross-cutting behavior is added by wrapping, not by editing handlers.

      The Infrastructure root - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:46) exposes - AddInfrastructure(configuration) (DependencyInjection.cs:56), which binds most of the settings + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:47) exposes + AddInfrastructure(configuration) (DependencyInjection.cs:57), which binds most of the settings types in this chapter, registers the three save interceptors as singletons - (DependencyInjection.cs:60-69), the persistence stack (data-source service and resolver, entity + (DependencyInjection.cs:61-70), the persistence stack (data-source service and resolver, entity registry, the scoped and singleton context factories, repositories, unit of work, - DependencyInjection.cs:58-124), Scrutor-scans the framework's own EF entity configurations - (DependencyInjection.cs:127-132), adds caching (DependencyInjection.cs:134), registers the refresh + DependencyInjection.cs:59-130), Scrutor-scans the framework's own EF entity configurations + (DependencyInjection.cs:133-138), adds caching (DependencyInjection.cs:140), registers the refresh session store and its retention sweep behind the same flag that maps the table - (DependencyInjection.cs:162-175), and enrolls a startup validator that fails the host on a bad - upcaster graph (DependencyInjection.cs:193-194). The outbox hosted services are conditional: the + (DependencyInjection.cs:168-185), and enrolls a startup validator that fails the host on a bad + upcaster graph (DependencyInjection.cs:203-204). The outbox hosted services are conditional: the method reads MessageBusSettings, calls EnsureOutboxAvailableForProvider, and adds OutboxProcessor plus OutboxCleanupService only when IsOutboxEnabled, otherwise registering OutboxDisabledNoticeService so the choice is - visible in the log (DependencyInjection.cs:204-215, + visible in the log (DependencyInjection.cs:214-225, ADR-100). The OutboxMessages table stays mapped either way, so flipping the flag is never a migration - (DependencyInjection.cs:198-203). It closes by composing the internal-command queue - (AddInternalCommands, DependencyInjection.cs:222), running AddServices() (:240) and then + (DependencyInjection.cs:208-213). It closes by composing the internal-command queue + (AddInternalCommands, DependencyInjection.cs:232), running AddServices() (:234) and then layering the impersonation decorator over whatever ICurrentUserService that left registered - (:242-249), both described two sections below. Optional add-ons sit alongside the root: + (:236-243), both described two sections below. Optional add-ons sit alongside the root: the class is partial, split by concern across sibling files, so - AddCommonHybridCache (DependencyInjection.Caching.cs:137), AddScheduledJobs + AddCommonHybridCache (DependencyInjection.Caching.cs:139), AddScheduledJobs (DependencyInjection.Jobs.cs:37), AddTwoFactorAuthentication (DependencyInjection.Auth.cs:39), - AddEmailConfirmation (:67), AddStoredPermissionGrants (:104), AddAuditTrail - (DependencyInjection.Jobs.cs:108), AddMultiTenancy (DependencyInjection.cs:266), AddServices - (:284), AddEntityConfigurationAssembly (:338), AddStronglyTypedIds (:366), + AddEmailConfirmation (:67), AddStoredPermissionGrants (:108), AddAuditTrail + (DependencyInjection.Jobs.cs:108), AddMultiTenancy (DependencyInjection.cs:276), AddServices + (:294), AddEntityConfigurationAssembly (:348), AddStronglyTypedIds (:376), AddNotificationInfrastructure (DependencyInjection.Notifications.cs:26), AddPushNotifications (:41), AddNativePushNotifications (:82), AddAzureBlobFileStorage (:114), AddBrokerMessaging - (DependencyInjection.Messaging.cs:41) and the typed-client helper - AddTypedServiceClient<TInterface, TImplementation>(serviceName) (:141) that swaps an in-process + (DependencyInjection.Messaging.cs:42) and the typed-client helper + AddTypedServiceClient<TInterface, TImplementation>(serviceName) (:152) that swaps an in-process abstraction for an HTTP transport.

      AddCaching (MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:26) also registers this chapter's one cross-replica primitive: an IDistributedLock that resolves to RedisDistributedLock when the host has an IConnectionMultiplexer registered, and to the warn-once InProcessDistributedLock otherwise - (MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:84-98). The Redis implementation is the + (MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:86-100). The Redis implementation is the standard SET key token NX PX ttl acquire (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Concurrency/RedisDistributedLock.cs:67) with a compare-and-delete release script (RedisDistributedLock.cs:36-37, evaluated at :104), handing back @@ -450,20 +453,20 @@

      Opt-in platform feat and the AuditTrailReader that projects AuditTrailEntryDTO rows, and contributes its own retention job (DependencyInjection.Jobs.cs:110-124), which only actually runs when the host also enabled the scheduler. - AddMultiTenancy(configuration) (DependencyInjection.cs:266) binds + AddMultiTenancy(configuration) (DependencyInjection.cs:276) binds TenancySettings and registers TenancySettingsValidator as an IValidateOptions<TenancySettings> - through TryAddEnumerable (DependencyInjection.cs:268-276); note what it does not do, because + through TryAddEnumerable (DependencyInjection.cs:278-286); note what it does not do, because that is the design: TenantSaveChangesInterceptor and ITenantContext, whose scoped implementation TenantContext reports IsResolved = false until a request calls SetTenant (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Context/TenantContext.cs:17, :20, registered - at DependencyInjection.cs:294), are registered unconditionally by + at DependencyInjection.cs:304), are registered unconditionally by AddInfrastructure and AddServices and stay inert until a tenant is resolved, so the framework can never sit in the half-wired state where entities carry ITenantEntity but the write-side guard is off - (DependencyInjection.cs:66-69, :294, + (DependencyInjection.cs:67-70, :294, ADR-073). Finally AddUserDataExportSection<TSection>() (MMCA.Common.Application/DependencyInjection.Extensibility.cs:38) accumulates IUserDataExportSection contributors into the one @@ -475,13 +478,13 @@

      Opt-in platform feat (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Scheduling/ScheduledJobRunner.cs:39) is the one of these with real runtime machinery, and it reuses the outbox's idioms wholesale. It is a BackgroundService that returns immediately (after one log line) when Scheduler:Enabled is false - (ScheduledJobRunner.cs:77-82), waits out a 15-second startup delay so migrations finish first - (ScheduledJobRunner.cs:69, :87), then loops: run one cycle that reconciles the ScheduledJobs rows + (ScheduledJobRunner.cs:85-90), waits out a 15-second startup delay so migrations finish first + (ScheduledJobRunner.cs:69, :95), then loops: run one cycle that reconciles the ScheduledJobs rows against the registered jobs, claims due rows with a lease, executes and stamps the outcome - (ScheduledJobRunner.cs:94-99, :205), and smart-waits until the earliest upcoming occurrence capped - at Scheduler:PollingIntervalSeconds (ScheduledJobRunner.cs:112-120). A claim attempt is a single + (ScheduledJobRunner.cs:102-107, :213), and smart-waits until the earliest upcoming occurrence capped + at Scheduler:PollingIntervalSeconds (ScheduledJobRunner.cs:120-128). A claim attempt is a single filtered ExecuteUpdateAsync against the still-unleased predicate, so two racing replicas both issue - it and exactly one matches (ScheduledJobRunner.cs:433-435); it returns a JobClaim + it and exactly one matches (ScheduledJobRunner.cs:435-445); it returns a JobClaim carrying either this replica's lock token or null when another replica won the row (ScheduledJobRunner.cs:447), which is what makes an occurrence run exactly once across a scaled host. The persisted row is ScheduledJobEntry @@ -490,7 +493,7 @@

      Opt-in platform feat concurrency token, and it is host-scoped, living in the Default data source only (ScheduledJobEntry.cs:8-19), on whichever relational engine Scheduler:DataSource names (SQL Server unless set, and never Cosmos because the table is relational, SchedulerSettings.cs:45-52), which - each cycle resolves before touching the table (ScheduledJobRunner.cs:215-216). SchedulerMetrics + each cycle resolves before touching the table (ScheduledJobRunner.cs:223-224). SchedulerMetrics (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Scheduling/SchedulerMetrics.cs:16) publishes the MMCA.Common.Scheduler meter with a run counter tagged by job and outcome (SchedulerMetrics.cs:28-31), a duration histogram (SchedulerMetrics.cs:39-42) and a schedule-lag @@ -529,7 +532,7 @@

      Opt-in platform feat is checked at boot by EventUpcasterStartupValidator (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Messaging/Consumers/EventUpcasterStartupValidator.cs:20), an IHostedService that AddInfrastructure enrolls through TryAddEnumerable - (MMCA.Common.Infrastructure/DependencyInjection.cs:193): resolving the registry is the check, + (MMCA.Common.Infrastructure/DependencyInjection.cs:203): resolving the registry is the check, because its constructor rejects a duplicate source, a self-mapping and a cycle, so a misconfigured host fails to start instead of dead-lettering events hours later (EventUpcasterStartupValidator.cs:7-17, :23-29).

      @@ -537,7 +540,7 @@

      Deferred work, and t

      The last two things AddInfrastructure composes are a queue for work a request wants done later, and the machinery that lets whatever drains a queue act as the identity that filled it. AddInternalCommands (MMCA.Common.Infrastructure/DependencyInjection.Jobs.cs:146, called at - DependencyInjection.cs:222) binds InternalCommandsSettings, registers the scheduler and the operator + DependencyInjection.cs:232) binds InternalCommandsSettings, registers the scheduler and the operator surface scoped (DependencyInjection.Jobs.cs:160-161, :165-166), and then takes the outbox's posture deliberately: the table is mapped either way, and InternalCommands:Enabled decides only whether the processor and its retention sweep run (DependencyInjection.Jobs.cs:173-177) or a disabled-notice @@ -554,9 +557,12 @@

      Deferred work, and t idempotent (IInternalCommand.cs:19-29). IInternalCommandScheduler (IInternalCommandScheduler.cs:16) writes the row on the caller's own unit of work: scheduling from inside an ITransactional command commits with the aggregate change or rolls back with it - (IInternalCommandScheduler.cs:9-14, :34-40), which is the outbox's atomicity guarantee applied to - work the host wants to do rather than to a message it wants to publish. Both overloads return - Result<Guid> (IInternalCommandScheduler.cs:41-44, :54-57), the handle the administration surface + (IInternalCommandScheduler.cs:9-14), which is the outbox's atomicity guarantee applied to + work the host wants to do rather than to a message it wants to publish. With a transaction active the + row is only enrolled, and when no later save follows the transactional pipeline saves it just before + the commit; with none active the row is saved at once, flushing whatever else is pending on that + context (IInternalCommandScheduler.cs:34-43). Both overloads return + Result<Guid> (IInternalCommandScheduler.cs:45-48, :59-62), the handle the administration surface takes. A row stores the command's assembly-qualified CLR name unless InternalCommandNameAttribute (InternalCommandNameAttribute.cs:25) declares a stable identity, the internal-command twin of @@ -632,7 +638,7 @@

      Deferred work, and t applies it with Scrutor's TryDecorate after AddServices() has run its own TryAddScoped, guarded on the presence of the ScopedUserOverride registration so a host whose modules each call AddInfrastructure does not stack one wrapper per call - (DependencyInjection.cs:226-233). The broker side of the same restore is + (DependencyInjection.cs:236-243). The broker side of the same restore is ConsumerOriginRestore (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Messaging/Consumers/ConsumerOriginRestore.cs:24), the one step of the restore that knows about a transport: it reads the headers @@ -656,7 +662,7 @@

      Assembly anchors

      (MMCA.Common/Source/Core/MMCA.Common.Domain/AssemblyReference.cs:8-12)
      beside a non-static class ClassReference (AssemblyReference.cs:18) for the places a generic constraint forbids a static type. AddApplication uses the Application pair for the common validators (MMCA.Common.Application/DependencyInjection.cs:48) and AddInfrastructure uses the Infrastructure - pair to scan entity configurations (MMCA.Common.Infrastructure/DependencyInjection.cs:127-132). They + pair to scan entity configurations (MMCA.Common.Infrastructure/DependencyInjection.cs:133-138). They are deliberately behavior-free; their whole job is to name an assembly for the scanning and governance tooling. A related, test-only assembly anchor lives in CreateMigrationProofTable @@ -671,7 +677,7 @@

      Configuration binding, the Se lives next to the shape it binds. The pattern in AddInfrastructure is uniform: services.AddOptions<T>().Bind(configuration.GetSection(T.SectionName)).ValidateDataAnnotations().ValidateOnStart() (for example ConnectionStringSettings at - MMCA.Common.Infrastructure/DependencyInjection.cs:71-74), so misconfiguration fails fast at + MMCA.Common.Infrastructure/DependencyInjection.cs:72-75), so misconfiguration fails fast at startup rather than lazily on first use (ADR-070). There are no ISettings facade interfaces over these types: consumers take IOptions<T> (or the concrete @@ -717,7 +723,7 @@

      Configuration binding, the Se ConnectionStringSettingsValidator (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/ConnectionStringSettingsValidator.cs:30) is registered as an IValidateOptions<ConnectionStringSettings> via TryAddEnumerable - (MMCA.Common.Infrastructure/DependencyInjection.cs:78-79) and passes only when the top-level section + (MMCA.Common.Infrastructure/DependencyInjection.cs:82-83) and passes only when the top-level section names a database on any engine or one DataSources entry does (ConnectionStringSettingsValidator.cs:47-53, :56-72), failing with a message that lists both shapes because which one is missing depends on whether the host is a single-database monolith or a @@ -727,17 +733,17 @@

      Configuration binding, the Se (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourceEntrySettings.cs:19) is the logical-to-physical source map for database-per-service, built directly from Get<Dictionary<...>> rather than through the options pipeline - (MMCA.Common.Infrastructure/DependencyInjection.cs:83-85) because a root-level dictionary section + (MMCA.Common.Infrastructure/DependencyInjection.cs:85-89) because a root-level dictionary section does not bind that way, with a constructor that rejects an empty or reserved "Default" key (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/DataSourcesSettings.cs:27-40).

      The rest of the platform sections follow the same discipline: MessageBusSettings and its MessageBusProvider enum (InProcess / RabbitMq / AzureServiceBus, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Messaging/MessageBusSettings.cs:236-252) that AddBrokerMessaging switches on, short-circuiting entirely for InProcess - (MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:50-53) and otherwise Replace-ing both + (MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:51-54) and otherwise Replace-ing both IMessageBus and IEventBus with their broker-backed counterparts - (DependencyInjection.Messaging.cs:93, :99), with the whole local-emulator path for Azure Service Bus + (DependencyInjection.Messaging.cs:94, :100), with the whole local-emulator path for Azure Service Bus quarantined in ServiceBusEmulatorSupport (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Messaging/ServiceBusEmulatorSupport.cs:32) and entered only when the resolved connection string carries UseDevelopmentEmulator=true, a token a real @@ -764,9 +770,9 @@

      Configuration binding, the Se :119-130), so a host registers them unconditionally and a deployment switches the channel on by configuration alone. What makes that safe is that AddServices has already registered a working null-object default behind each of those contracts: NullNativePushSender and - NullPushDeviceRegistrar (DependencyInjection.cs:320-321), and + NullPushDeviceRegistrar (DependencyInjection.cs:330-331), and NullFileStorageService beside the real - ImageSharpImageProcessor (DependencyInjection.cs:325-326). Application + ImageSharpImageProcessor (DependencyInjection.cs:335-336). Application code therefore always resolves something, and a configured section merely upgrades the registration to AzureNotificationHubNativePushSender plus AzureNotificationHubDeviceRegistrar @@ -803,7 +809,7 @@

      Configuration binding, the Se TenantEntrySettings (TenancySettings.cs:121) keys TenantDataSourceOverrideSettings (TenancySettings.cs:138) by physical data source name, and TenancySettingsValidator - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:23) fails + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettingsValidator.cs:24) fails the boot when an override names a source that does not exist, when it declares no connection string at all, or when the resolution order names TenantResolutionStrategy Host, which is defined but not implemented (TenancySettings.cs:21-27). Every one of those failures is a @@ -821,7 +827,7 @@

      Configuration binding, the Se sanitizes the result to characters that are safe in a Redis key, a channel name and a queue name (:42-43). Composition reads it where the shared resource is wired: AddPushNotifications for the backplane channel prefix (MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:59), - AddBrokerMessaging for the endpoint prefix (DependencyInjection.Messaging.cs:80), and the cache-key builder + AddBrokerMessaging for the endpoint prefix (DependencyInjection.Messaging.cs:81), and the cache-key builder (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/CacheKeyPrefix.cs:83). The default is per-application rather than empty because two hosts pointed at one Redis or one broker otherwise share every keyspace, and NotificationHub ships in the framework, so its backplane channel name is @@ -864,14 +870,14 @@

      Shared us line-identical copies (or would have), so the workflow was hoisted into abstract bases that each app subclasses: ChangePasswordHandlerBase<TUser, TCommand> - (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:34, + (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:42, ADR-032), ChangePreferencesHandlerBase<TUser, TCommand> (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePreferences/ChangePreferencesHandlerBase.cs:23), GetUserPreferencesHandlerBase<TUser> (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/GetPreferences/GetUserPreferencesHandlerBase.cs:21), DeleteUserHandlerBase<TUser, TCommand> - (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:58), + (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:62), the erasure workflow behind ADR-005, ExportUserDataHandlerBase<TUser, TQuery> @@ -886,7 +892,7 @@

      Shared us rejection to one Auth.InvalidResetToken error so the endpoint reveals nothing about which addresses hold accounts (ResetPasswordHandlerBase.cs:20-24). Both password-writing bases also revoke every live refresh session on the account once the new credential is saved, so a stolen refresh chain - cannot outlive the remediation the user just performed (ChangePasswordHandlerBase.cs:29-31, :94; + cannot outlive the remediation the user just performed (ChangePasswordHandlerBase.cs:30-32, :113; ResetPasswordHandlerBase.cs:36-38, :106; ADR-097).

      Six more bases cover the account-security workflows a host opts into with @@ -932,7 +938,7 @@

      Shared us IUserScopedCommand.cs:13), and IUserOwnedRequest (adds CurrentUserId and CurrentUserRole, IUserOwnedRequest.cs:8). The commands stay app-side precisely because the two apps disagree on their pipeline attributes: ADC marks the password-change command ICacheInvalidating and - Store does not (ChangePasswordHandlerBase.cs:18-23). Note that + Store does not (ChangePasswordHandlerBase.cs:19-24). Note that IUserScopedCommand<out TRequest> is deliberately not ICommandWithRequest<TRequest>: implementing the latter also opts a command into automatic CommandRequestValidator registration, which is a per-app decision, so implementing this one alone @@ -959,10 +965,8 @@

      Shared us owner-or-privileged-role decision returning a Forbidden Error or null (UserOwnershipRule.cs:38) with the privileged-role test passed in already evaluated because each app owns its own role vocabulary - (UserOwnershipRule.cs:15-19); UserUseCaseLog - (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UserUseCaseLog.cs:11), a non-generic - [LoggerMessage] holder so every subclass emits identical text while the log category still comes from - the subclass's own ILogger<T> (UserUseCaseLog.cs:13-67); + (UserOwnershipRule.cs:15-19), while each base is itself a partial class that declares its own + [LoggerMessage] audit lines (for example ChangePasswordHandlerBase.cs:131); SoftDeletedUserValidator<TUser> (MMCA.Common/Source/Core/MMCA.Common.Application/Users/SoftDeletedUserValidator.cs:20), which answers ISoftDeletedUserValidator with one @@ -979,7 +983,7 @@

      End-to-end: one host's boot

      single assembly that declares ConferenceModule and a Serilog-backed ModuleLoader logger (Program.cs:366-369), and passes the resulting ModulesSettings to AddAPI (Program.cs:371). The whole handler-contributing - sequence then goes inside one AddMmcaApplicationPipeline call (Program.cs:407-412): step one is + sequence then goes inside one AddMmcaApplicationPipeline call (Program.cs:409-415): step one is moduleHost.RegisterModules (module discovery), step two replaces the disabled Engagement stub with a real gRPC client (AddEngagementBookmarkCountClient()), and step three is AddBrokerMessaging with its integration-event consumers, so MessageBusSettings Provider decides @@ -988,7 +992,7 @@

      End-to-end: one host's boot

      Enabled in its configuration; every other discovered module takes the RegisterDisabledStubs path. The pipeline call closes with AddApplicationDecorators() and seals the collection, so the decorators wrap the now-registered Conference handlers and any later handler registration throws rather than - running bare. Afterwards the host adds module health checks from the loader (Program.cs:424) and + running bare. Afterwards the host adds module health checks from the loader (Program.cs:427) and finally app.Services.InitializeDatabaseAsync(moduleHost.ApplicationSettings, moduleHost.ModuleLoader) (Program.cs:440) applies migrations and runs the module seeders the loader collected. The exact same module assemblies, dropped into a monolith host with every module Enabled, would Kahn-sort into one @@ -1051,7 +1055,7 @@

      ClassReference

    1164. Why it's built this way: keeping a separate non-static anchor sidesteps the static-class generic-argument restriction while leaving AssemblyReference static (and therefore impossible to instantiate accidentally). Every module's Application assembly defines its own ClassReference, so each module scans itself by passing its local copy.

    1165. -
    1166. Where it's used: as the TAssemblyMarker argument in ScanModuleApplicationServices<TAssemblyMarker>(). All three ADC module composition roots call services.ScanModuleApplicationServices<ClassReference>() with their own local copy (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:53, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143, MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/DependencyInjection.cs:85), as do Store's three (MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Application/DependencyInjection.cs:53, .../Identity/MMCA.Store.Identity.Application/DependencyInjection.cs:51, .../Sales/MMCA.Store.Sales.Application/DependencyInjection.cs:65) and Helpdesk's single module (MMCA.Helpdesk/Source/Modules/Tickets/MMCA.Helpdesk.Tickets.Application/DependencyInjection.cs:34). The framework root AddApplication() passes the Application-layer copy to AddValidatorsFromAssemblyContaining<ClassReference>() (DependencyInjection.cs:49).

      +
    1167. Where it's used: as the TAssemblyMarker argument in ScanModuleApplicationServices<TAssemblyMarker>(). All three ADC module composition roots call services.ScanModuleApplicationServices<ClassReference>() with their own local copy (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:53, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139, MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/DependencyInjection.cs:85), as do Store's three (MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.Application/DependencyInjection.cs:53, .../Identity/MMCA.Store.Identity.Application/DependencyInjection.cs:51, .../Sales/MMCA.Store.Sales.Application/DependencyInjection.cs:65) and Helpdesk's single module (MMCA.Helpdesk/Source/Modules/Tickets/MMCA.Helpdesk.Tickets.Application/DependencyInjection.cs:34). The framework root AddApplication() passes the Application-layer copy to AddValidatorsFromAssemblyContaining<ClassReference>() (DependencyInjection.cs:49).


    1168. @@ -1097,7 +1101,7 @@

      IModuleSeeder

    1169. Walkthrough: string ModuleName { get; } (MMCA.Common/Source/Core/MMCA.Common.Application/Modules/IModuleSeeder.cs:13) must match the corresponding IModule.Name so the loader can correlate a seeder to its module and keep it in topological position; Task SeedAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken) (:18) is the single work method, and the XML doc states it is called only for enabled modules (:16).

    1170. -
    1171. Where it's used: discovered by ModuleLoader into a case-insensitive dictionary keyed by ModuleName (ModuleLoader.cs:91-94) and kept only when the matching module is enabled (ModuleLoader.cs:118-121). The actual invocation happens at host startup: DatabaseInitializationExtensions calls moduleLoader.SeedAllAsync(scope.ServiceProvider, cancellationToken) after schema initialization and tenant-database initialization (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:110). Seeding deliberately runs on the default scope only, not once per tenant, and the comment above the call gives the reason: no module declares which seeders apply per tenant, and running one twice against a shared database is worse than not running it per tenant at all (:107-110). The implementations are one per data-owning module: ConferenceModuleSeeder (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/ConferenceModuleSeeder.cs:13) and IdentityModuleSeeder (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/IdentityModuleSeeder.cs:15) in ADC; CatalogModuleSeeder (MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.API/CatalogModuleSeeder.cs:11), SalesModuleSeeder (.../Sales/MMCA.Store.Sales.API/SalesModuleSeeder.cs:14) and IdentityModuleSeeder (.../Identity/MMCA.Store.Identity.API/IdentityModuleSeeder.cs:12) in Store.

      +
    1172. Where it's used: discovered by ModuleLoader into a case-insensitive dictionary keyed by ModuleName (ModuleLoader.cs:102-105) and kept only when the matching module is enabled (ModuleLoader.cs:129-132). The actual invocation happens at host startup: DatabaseInitializationExtensions calls moduleLoader.SeedAllAsync(scope.ServiceProvider, cancellationToken) after schema initialization and tenant-database initialization (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:124). Seeding deliberately runs on the default scope only, not once per tenant, and the comment above the call gives the reason: no module declares which seeders apply per tenant, and running one twice against a shared database is worse than not running it per tenant at all (:107-110). The implementations are one per data-owning module: ConferenceModuleSeeder (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/ConferenceModuleSeeder.cs:13) and IdentityModuleSeeder (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/IdentityModuleSeeder.cs:15) in ADC; CatalogModuleSeeder (MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.API/CatalogModuleSeeder.cs:11), SalesModuleSeeder (.../Sales/MMCA.Store.Sales.API/SalesModuleSeeder.cs:14) and IdentityModuleSeeder (.../Identity/MMCA.Store.Identity.API/IdentityModuleSeeder.cs:12) in Store.


    1173. @@ -1116,7 +1120,7 @@

      InternalCommandDeadLetter

    1174. Why it's built this way: a positional record gives structural equality for free, which the type's only production consumer, ListDeadLettersAsync's materialization, does not need but a test comparing expected rows does.

    1175. -
    1176. Where it's used: the element type of Task<Result<IReadOnlyList<InternalCommandDeadLetter>>> returned by IInternalCommandAdministration.ListDeadLettersAsync (IInternalCommandAdministration.cs:137), implemented by InternalCommandAdministration (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandAdministration.cs).

      +
    1177. Where it's used: the element type of Task<Result<IReadOnlyList<InternalCommandDeadLetter>>> returned by IInternalCommandAdministration.ListDeadLettersAsync (IInternalCommandAdministration.cs:42), implemented by InternalCommandAdministration (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Administration/InternalCommandAdministration.cs).

    1178. Caveats / not-in-source: no shipped UI or API endpoint renders this type in this workspace; it is consumed only by the administration implementation and its tests.

    1179. @@ -1164,7 +1168,7 @@

      MmcaApplicationPipelineBuilder

    1180. Why it's built this way: the callback shape is what makes the ordering rule structural instead of advisory. AddMmcaApplicationPipeline runs AddApplication(), invokes the callback with a freshly constructed builder, and then returns AddApplicationDecorators() (MMCA.Common.Application/DependencyInjection.cs:207-216), so the decorators are last by construction. Everything that is not a handler registration (infrastructure, API, telemetry, options, health checks) deliberately stays outside the call, because its order relative to the decorators does not matter (MMCA.Common.Application/DependencyInjection.cs:195-196).

    1181. -
    1182. Where it's used: constructed in exactly one place, AddMmcaApplicationPipeline (MMCA.Common.Application/DependencyInjection.cs:207). Every ADC and Store service host composes through it: ADC Conference (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:407-412), Identity (.../MMCA.ADC.Identity.Service/Program.cs:288), Engagement (.../MMCA.ADC.Engagement.Service/Program.cs:278), Notification (.../MMCA.ADC.Notification.Service/Program.cs:215), Store Catalog (MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:233), Sales (.../MMCA.Store.Sales.Service/Program.cs:234) and Identity (.../MMCA.Store.Identity.Service/Program.cs:211). The architecture fitness tests replay the same shape, MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/DecoratorPipelineOrderTests.cs:50-51 and MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/ApplicationPipelineCompositionTests.cs:34.

      +
    1183. Where it's used: constructed in exactly one place, AddMmcaApplicationPipeline (MMCA.Common.Application/DependencyInjection.cs:207). Every ADC and Store service host composes through it: ADC Conference (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:409-415), Identity (.../MMCA.ADC.Identity.Service/Program.cs:288), Engagement (.../MMCA.ADC.Engagement.Service/Program.cs:278), Notification (.../MMCA.ADC.Notification.Service/Program.cs:215), Store Catalog (MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:233), Sales (.../MMCA.Store.Sales.Service/Program.cs:234) and Identity (.../MMCA.Store.Identity.Service/Program.cs:211). The architecture fitness tests replay the same shape, MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/DecoratorPipelineOrderTests.cs:50-51 and MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/ApplicationPipelineCompositionTests.cs:34.

    1184. Caveats / not-in-source: the MMCA.Helpdesk host does not use the builder. It writes the three-call sequence by hand (MMCA.Helpdesk/Source/Hosts/MMCA.Helpdesk.Web/Program.cs:66, :104, :120) with the ordering rule stated as a comment above it (:65), which is still a supported composition, just the unguarded one.

    1185. @@ -1191,33 +1195,33 @@

      IModule


      ModuleLoader

      -

      MMCA.Common.Application · MMCA.Common.Application.Modules · MMCA.Common/Source/Core/MMCA.Common.Application/Modules/ModuleLoader.cs:15 · Level 2 · class (sealed, partial)

      +

      MMCA.Common.Application · MMCA.Common.Application.Modules · MMCA.Common/Source/Core/MMCA.Common.Application/Modules/ModuleLoader.cs:16 · Level 2 · class (sealed, partial)

      • What it is: the engine of the module system. It reflects over the assemblies the host names to find every IModule and IModuleSeeder implementation, sorts the modules into dependency order with Kahn's topological sort, registers each enabled module into the DI container while recording stub registrations for the disabled ones, and afterwards can verify against the built container that every remote-declared dependency was actually re-wired.

      • Depends on: IModule (Level 1), IModuleSeeder (Level 0), ApplicationSettings (Level 0), ModulesSettings (Level 1). Externals: IServiceCollection / ServiceDescriptor / IConfigurationBuilder, Microsoft.Extensions.Logging (source-generated [LoggerMessage] methods, NullLogger<T>), System.Diagnostics.Stopwatch, System.Reflection.

      • -
      • Concept introduced, Kahn's topological sort for DI registration ordering. [Rubric §2, Design Patterns] assesses use of the right algorithm for the problem: ordering items so each appears after everything it depends on is textbook topological sort, and TopologicalSort (ModuleLoader.cs:271) implements the BFS-based Kahn variant. [Rubric §7, Microservices Readiness]: the loader is what makes partial enablement (one module per service host) work at all. [Rubric §15, Best Practices & Code Quality]: modules name their dependencies as strings and the loader resolves and sorts them at startup, so adding a module is purely additive, with no central registration list to edit. [Rubric §13, Observability and Operability]: seven [LoggerMessage] partial methods (:323-342) give allocation-free structured diagnostics of which modules loaded, in what order, with which satisfied or unsatisfied dependencies, and how long each Register took.

        +
      • Concept introduced, Kahn's topological sort for DI registration ordering. [Rubric §2, Design Patterns] assesses use of the right algorithm for the problem: ordering items so each appears after everything it depends on is textbook topological sort, and TopologicalSort (ModuleLoader.cs:282) implements the BFS-based Kahn variant. [Rubric §7, Microservices Readiness]: the loader is what makes partial enablement (one module per service host) work at all. [Rubric §15, Best Practices & Code Quality]: modules name their dependencies as strings and the loader resolves and sorts them at startup, so adding a module is purely additive, with no central registration list to edit. [Rubric §13, Observability and Operability]: eight [LoggerMessage] partial methods (:334-356) give allocation-free structured diagnostics of which modules loaded, in what order, with which satisfied or unsatisfied dependencies, how long each Register took, and which assemblies failed to scan or loaded only partially (those two at Error, :349-353).

      • Walkthrough

          -
        • State (ModuleLoader.cs:17-21): three private lists, _enabledModules, _seeders, _disabledModuleNames, the first and third surfaced as the read-only EnabledModules (:24) and DisabledModuleNames (:27) properties. A fourth field, _stubRegistrations (:20), is a case-insensitive Dictionary<string, List<ServiceDescriptor>> recording exactly which descriptors each disabled module's stub registration added; _modulesSettings (:21) caches the settings for the post-build validation pass. Logger (:33) is an init-only ILogger<ModuleLoader> defaulting to NullLogger<ModuleLoader>.Instance, so the loader runs silently unless a host supplies one.
        • -
        • DiscoverAndRegister (:58-64) is the single entry point, and it takes the assemblies to scan as a required parameter. There is no ambient-scan overload, and the XML doc gives the reason: an AppDomain scan only sees assemblies already loaded, so a module assembly that is referenced but not yet touched by any code path would be silently absent from discovery (:48-53).
        • -
        • Discovery (:71-94): flattens moduleAssemblies through GetTypes() inside a try/catch (:74-82) that logs and skips assemblies which throw (for example ReflectionTypeLoadException from a missing transitive reference) rather than aborting the whole scan. It then instantiates every concrete, non-abstract, non-interface IModule via Activator.CreateInstance (:86-89) and every IModuleSeeder into an OrdinalIgnoreCase dictionary keyed by ModuleName (:91-94).
        • -
        • Per-module loop (:99-122): for a module disabled per ModulesSettings.IsModuleEnabled, it logs, snapshots services.Count, calls module.RegisterDisabledStubs(services), stores the newly appended descriptors under the module's name (:107-109), records the name, and continues. An enabled module runs ValidateModuleDependencies then RegisterEnabledModule, and if a seeder with a matching name exists it is appended to _seeders (:118-121).
        • -
        • ValidateModuleDependencies (:125): computes the module's disabled dependencies (:131-133), subtracts those declared remote via ModulesSettings.IsDependencyRemote (:135-137), and throws InvalidOperationException only if a genuinely unsatisfied dependency remains and RequiresDependencies is true; the message spells out the three remediations, enable the module, disable this one, or add the name to Modules:{Name}:RemoteDependencies (:139-147). Otherwise it logs a warning per unsatisfied-but-tolerated dependency (:149-152) and an information line per remote-satisfied one (:154-157).
        • -
        • RegisterEnabledModule (:160): before calling module.Register, it adds the conventional per-module JSON configuration files modules.{name}.json and, when an environment name was supplied, modules.{name}.{environment}.json, both optional and reloadOnChange: true (:174-178); the name is lower-cased with ToLowerInvariant under a documented CA1308 suppression for the file-naming convention (:171-173). It times the Register call with a Stopwatch and logs the elapsed milliseconds (:180-184).
        • -
        • ValidateRemoteDependencies (:201): the post-build half of the extraction story. Given the built root provider, it creates a scope (:208) and, for every enabled module and every dependency that module declared remote, looks up the descriptors the disabled peer's stubs added and calls ValidateRemoteDependencyStubs (:210-222). That helper (:225) skips open generics, resolves each stub's ServiceType, and throws with a remediation message if it does not resolve at all (:233-238); if it resolves but is still the stub implementation type, it only logs a warning (:240-244), because a best-effort dependency may intentionally keep its stub. Configuration trust alone is not enough: a typo in a RemoteDependencies entry or a forgotten AddTypedGrpcClient would otherwise surface as a first-request failure or a silent no-op instead of at startup (:187-200).
        • -
        • SeedAllAsync (:255): awaits each collected seeder's SeedAsync in registration (that is, topological) order, with ConfigureAwait(false) (:257-260).
        • -
        • TopologicalSort (:271): builds modulesByName, inDegree, and a reverse-adjacency dependents map, all OrdinalIgnoreCase (:273-279); while building the graph it ignores dependencies on modules that were not discovered (:286-287), deferring those to registration-time validation. It seeds a Queue<string> with the zero-in-degree modules (:295-296) and drains it, decrementing each dependent's in-degree and enqueuing at zero (:299-310). If fewer modules were emitted than exist, the remainder form a cycle, and it throws InvalidOperationException naming them (:313-318).
        • +
        • State (ModuleLoader.cs:18-22): three private lists, _enabledModules, _seeders, _disabledModuleNames, the first and third surfaced as the read-only EnabledModules (:25) and DisabledModuleNames (:28) properties. A fourth field, _stubRegistrations (:21), is a case-insensitive Dictionary<string, List<ServiceDescriptor>> recording exactly which descriptors each disabled module's stub registration added; _modulesSettings (:22) caches the settings for the post-build validation pass. Logger (:34) is an init-only ILogger<ModuleLoader> defaulting to NullLogger<ModuleLoader>.Instance, so the loader runs silently unless a host supplies one.
        • +
        • DiscoverAndRegister (:59-65) is the single entry point, and it takes the assemblies to scan as a required parameter. There is no ambient-scan overload, and the XML doc gives the reason: an AppDomain scan only sees assemblies already loaded, so a module assembly that is referenced but not yet touched by any code path would be silently absent from discovery (:49-53).
        • +
        • Discovery (:74-105): flattens moduleAssemblies through GetTypes() inside a try with two catches, and neither aborts the whole scan. A ReflectionTypeLoadException (the shape a missing transitive reference produces) is caught first (:81-88): it joins the non-null LoaderExceptions messages, logs LogAssemblyPartiallyLoaded at Error, and keeps every type that did load (ex.Types.OfType<Type>(), :87), so one unloadable type does not make the assembly's other modules vanish. Any other exception (:89-93) logs LogAssemblyScanFailed, also at Error and now with the exception attached, and skips that assembly. The code comment gives the reason for Error rather than Warning: a module that silently fails to register is an outage that looks like a configuration choice (:69-73). It then instantiates every concrete, non-abstract, non-interface IModule via Activator.CreateInstance (:97-100) and every IModuleSeeder into an OrdinalIgnoreCase dictionary keyed by ModuleName (:102-105).
        • +
        • Per-module loop (:110-133): for a module disabled per ModulesSettings.IsModuleEnabled, it logs, snapshots services.Count, calls module.RegisterDisabledStubs(services), stores the newly appended descriptors under the module's name (:118-120), records the name, and continues. An enabled module runs ValidateModuleDependencies then RegisterEnabledModule, and if a seeder with a matching name exists it is appended to _seeders (:129-132).
        • +
        • ValidateModuleDependencies (:136): computes the module's disabled dependencies (:142-144), subtracts those declared remote via ModulesSettings.IsDependencyRemote (:146-148), and throws InvalidOperationException only if a genuinely unsatisfied dependency remains and RequiresDependencies is true; the message spells out the three remediations, enable the module, disable this one, or add the name to Modules:{Name}:RemoteDependencies (:150-158). Otherwise it logs a warning per unsatisfied-but-tolerated dependency (:160-163) and an information line per remote-satisfied one (:165-168).
        • +
        • RegisterEnabledModule (:171): before calling module.Register, it adds the conventional per-module JSON configuration files modules.{name}.json and, when an environment name was supplied, modules.{name}.{environment}.json, both optional and reloadOnChange: true (:185-189); the name is lower-cased with ToLowerInvariant under a documented CA1308 suppression for the file-naming convention (:182-184). It times the Register call with a Stopwatch and logs the elapsed milliseconds (:191-194).
        • +
        • ValidateRemoteDependencies (:212): the post-build half of the extraction story. Given the built root provider, it creates a scope (:219) and, for every enabled module and every dependency that module declared remote, looks up the descriptors the disabled peer's stubs added and calls ValidateRemoteDependencyStubs (:221-233). That helper (:236) skips open generics, resolves each stub's ServiceType, and throws with a remediation message if it does not resolve at all (:244-249); if it resolves but is still the stub implementation type, it only logs a warning (:251-255), because a best-effort dependency may intentionally keep its stub. Configuration trust alone is not enough: a typo in a RemoteDependencies entry or a forgotten AddTypedGrpcClient would otherwise surface as a first-request failure or a silent no-op instead of at startup (:198-211).
        • +
        • SeedAllAsync (:266): awaits each collected seeder's SeedAsync in registration (that is, topological) order, with ConfigureAwait(false) (:268-271).
        • +
        • TopologicalSort (:282): builds modulesByName, inDegree, and a reverse-adjacency dependents map, all OrdinalIgnoreCase (:284-290); while building the graph it ignores dependencies on modules that were not discovered (:297-298), deferring those to registration-time validation. It seeds a Queue<string> with the zero-in-degree modules (:306-307) and drains it, decrementing each dependent's in-degree and enqueuing at zero (:310-321). If fewer modules were emitted than exist, the remainder form a cycle, and it throws InvalidOperationException naming them (:324-329).
      • Why it's built this way: convention over configuration. Discovery plus sort means no manual ordering and no module-registration list to keep in sync, which is the decision recorded in ADR-059 (a disabled module is represented by stub registrations rather than by absence, so a dependent always resolves something). Making the assembly list an explicit parameter rather than an ambient scan trades one line at the host for deterministic discovery.

      • -
      • Where it's used: constructed by ModuleHostExtensions.AddModuleHost, which attaches a logger when the host supplies one and registers the loader as a singleton (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/ModuleHostExtensions.cs:78-82), then hands it back on a ModuleHostContext whose RegisterModules step is the actual DiscoverAndRegister call (ModuleHostContext.cs:66-77). Every ADC and Store service registers that step inside its application pipeline, for example ADC Conference (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:367, :347-348) and Store Catalog (MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:122, :233). Each of those hosts enables exactly one module in configuration; the MMCA.Helpdesk monolith instead constructs the loader itself with a console logger and calls DiscoverAndRegister directly, naming typeof(TicketsModule).Assembly as the one assembly to scan (MMCA.Helpdesk/Source/Hosts/MMCA.Helpdesk.Web/Program.cs:97-113). Seeding runs later, from DatabaseInitializationExtensions (DatabaseInitializationExtensions.cs:110).

        +
      • Where it's used: constructed by ModuleHostExtensions.AddModuleHost, which attaches a logger when the host supplies one and registers the loader as a singleton (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/ModuleHostExtensions.cs:78-82), then hands it back on a ModuleHostContext whose RegisterModules step is the actual DiscoverAndRegister call (ModuleHostContext.cs:66-77). Every ADC and Store service registers that step inside its application pipeline, for example ADC Conference (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:367, :347-348) and Store Catalog (MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:122, :233). Each of those hosts enables exactly one module in configuration; the MMCA.Helpdesk monolith instead constructs the loader itself with a console logger and calls DiscoverAndRegister directly, naming typeof(TicketsModule).Assembly as the one assembly to scan (MMCA.Helpdesk/Source/Hosts/MMCA.Helpdesk.Web/Program.cs:97-113). Seeding runs later, from DatabaseInitializationExtensions (DatabaseInitializationExtensions.cs:124).

      • -
      • Caveats / not-in-source: ValidateRemoteDependencies has no production caller today. It is exercised only by ModuleLoaderTests (MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:138, :151, :166); no Program.cs in this workspace calls it after builder.Build(), so a mis-declared RemoteDependencies entry still surfaces at first request rather than at startup unless a host opts in.

        +
      • Caveats / not-in-source: ValidateRemoteDependencies has no production caller today. It is exercised only by ModuleLoaderTests (MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Modules/ModuleLoaderTests.cs:139, :151, :166); no Program.cs in this workspace calls it after builder.Build(), so a mis-declared RemoteDependencies entry still surfaces at first request rather than at startup unless a host opts in.


      @@ -1299,7 +1303,7 @@

      DeleteBlobInternalCommandH
    1186. Depends on: IDeleteBlobInternalCommand (Level 4, the TCommand constraint), ICommandHandler<TCommand, Result> (the contract it implements), IFileStorageService (constructor dependency, DeleteAsync) and ErrorType (NotFound, tested against the failure result). Externally, Microsoft.Extensions.Logging for its three source-generated [LoggerMessage] methods.

    1187. -
    1188. Concept introduced, a base class that turns "not found" into a non-failure. [Rubric §29, Reliability and Resilience] assesses whether a retryable job queue treats an already-completed side effect as success rather than as a reason to retry forever: a blob deleted by an earlier, since-crashed attempt (or removed out of band) must not keep the internal command dead-lettering. HandleAsync (DeleteBlobInternalCommandHandlerBase.cs:62-86) checks result.Errors.Any(e => e.Type == ErrorType.NotFound) (:74) and returns Result.Success() in that case (:77) instead of propagating the storage failure. [Rubric §15, Best Practices & Code Quality]: the class is abstract partial, generic over TCommand, and provides one override point, protected virtual string BlobKind => "Blob"; (:59), so a concrete handler needs only a class declaration and, optionally, a friendlier log label; DeleteSessionAssetBlobInternalCommandHandler and DeleteAvatarBlobInternalCommandHandler both derive from it with no additional logic of their own.

      +
    1189. Concept introduced, a base class that turns "not found" into a non-failure. [Rubric §29, Reliability and Resilience] assesses whether a retryable job queue treats an already-completed side effect as success rather than as a reason to retry forever: a blob deleted by an earlier, since-crashed attempt (or removed out of band) must not keep the internal command dead-lettering. HandleAsync (DeleteBlobInternalCommandHandlerBase.cs:41-65) checks result.Errors.Any(e => e.Type == ErrorType.NotFound) (:53) and returns Result.Success() in that case (:56) instead of propagating the storage failure. [Rubric §15, Best Practices & Code Quality]: the class is abstract partial, generic over TCommand, and provides one override point, protected virtual string BlobKind => "Blob"; (:38), so a concrete handler needs only a class declaration and, optionally, a friendlier log label; DeleteSessionAssetBlobInternalCommandHandler and DeleteAvatarBlobInternalCommandHandler both derive from it with no additional logic of their own.

    1190. Walkthrough: a primary-constructor class taking IFileStorageService fileStorage and ILogger logger (:49-51), constrained where TCommand : IDeleteBlobInternalCommand (:53). HandleAsync(TCommand command, CancellationToken cancellationToken = default) (:62) null-guards the command (:64), calls fileStorage.DeleteAsync(command.BlobName, cancellationToken) (:66), and branches three ways: success logs LogBlobDeleted and returns the successful result (:68-72); a NotFound error logs LogBlobAlreadyGone and returns Result.Success() (:74-78); any other error logs LogBlobDeleteFailed with the first error's message (or "Unknown error" if the error list is empty, :84) and returns the original failed result (:80-85). The three [LoggerMessage] partial methods (:88-95) are Information, Information, and Warning respectively, each interpolating BlobKind and BlobName so the log line reads naturally for whichever concrete command is running.

    1191. @@ -1320,18 +1324,19 @@

      IInternalCommandScheduler

    1192. Depends on: IInternalCommand (Level 3, the type every scheduled payload must implement) and, through its return doc, IInternalCommandAdministration (the scheduled id is the handle the administration surface operates on). BCL (DateTimeOffset?, TimeSpan).

    1193. -
    1194. Concept introduced, transaction-aware durable scheduling. [Rubric §29, Reliability and Resilience]: scheduling a command is not fire-and-forget the way an in-memory Task.Delay would be, it is a database write that must be atomic with whatever aggregate change triggered it. The XML remarks on ScheduleAsync (:217-223) state the rule explicitly: with a transaction active on the target data source the row is only enrolled and the caller's own commit persists it; with no transaction active the row is saved immediately, which flushes anything else pending on that context exactly as the unit of work's own save would. [Rubric §6, CQRS and Event-Driven]: scheduling inside an ITransactional command, or right after the caller's own save, is how a scheduled command and the state change that caused it commit or roll back together.

      +
    1195. Concept introduced, transaction-aware durable scheduling. [Rubric §29, Reliability and Resilience]: scheduling a command is not fire-and-forget the way an in-memory Task.Delay would be, it is a database write that must be atomic with whatever aggregate change triggered it. The XML remarks on ScheduleAsync (:34-43) state the rule explicitly: with a transaction active on the target data source the row is only enrolled, and the caller's next save writes it; when no save follows (a command scheduled after the handler's last save), the transactional pipeline saves it just before the commit, so it still commits with the aggregate change. That pipeline refuses to commit any other change left unsaved, so a handler cannot lean on it to save its own work (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Decorators/TransactionalCommandDecorator.cs:16). With no transaction active the row is saved immediately, which flushes anything else pending on that context exactly as the unit of work's own save would. [Rubric §6, CQRS and Event-Driven]: scheduling inside an ITransactional command, or right after the caller's own save, is how a scheduled command and the state change that caused it commit or roll back together.

    1196. Walkthrough: two overloads, both returning Task<Result<Guid>> where the Guid is the scheduled row's id, the same id IInternalCommandAdministration later operates on.

        -
      • ScheduleAsync(IInternalCommand command, DateTimeOffset? runAt = null, CancellationToken cancellationToken = default) (:224-227): runAt is the earliest instant the command may run, converted to UTC; null or already-past means "as soon as the processor next polls". The processor never runs a row before this instant but makes no promise about how quickly after it a busy queue gets there.
      • -
      • ScheduleAsync(IInternalCommand command, TimeSpan delay, CancellationToken cancellationToken = default) (:237-240): schedules after delay has elapsed; a delay of zero or less also means "as soon as possible".
      • +
      • ScheduleAsync(IInternalCommand command, DateTimeOffset? runAt = null, CancellationToken cancellationToken = default) (:45-48): runAt is the earliest instant the command may run, converted to UTC; null or already-past means "as soon as the processor next polls". The processor never runs a row before this instant but makes no promise about how quickly after it a busy queue gets there.
      • +
      • ScheduleAsync(IInternalCommand command, TimeSpan delay, CancellationToken cancellationToken = default) (:59-62): schedules after delay has elapsed; a delay of zero or less also means "as soon as possible".
      • Both fail when the command's payload cannot round-trip through JSON or the row cannot be written.
      • +
      • Both carry a SuppressMessage for analyzer rule RS0026 (multiple public overloads with optional parameters) (:44, :58), justified as a grandfathered public overload released after the v1.152 public-API baseline: changing either signature now would be a breaking change.
    1197. Why it's built this way: two overloads rather than one nullable-DateTimeOffset parameter give the common "run in N minutes" case a TimeSpan call site without the caller doing DateTimeOffset.UtcNow.Add(...) arithmetic itself, while the absolute-instant overload covers "run at this specific time".

    1198. -
    1199. Where it's used: called wherever a host wants to defer a command, for example ADC's AuthController (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/AuthController.cs), DeleteUserHandler, RemoveUserAvatarHandler, SetUserAvatarHandler (all under MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/), and Conference's DeleteEventHandler, DeleteSessionAssetHandler, UploadSessionAssetHandler, DeleteSessionHandler (under MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/). Implemented by InternalCommandScheduler and registered in MMCA.Common.Infrastructure's DependencyInjection.cs. Referenced by ADR-024, ADR-054, ADR-086, ADR-114 (the decision record for the internal-command queue itself) and ADR-121.

      +
    1200. Where it's used: called wherever a host wants to defer a command, for example ADC's AuthController (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/AuthController.cs), DeleteUserHandler, RemoveUserAvatarHandler, SetUserAvatarHandler (all under MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/), and Conference's DeleteEventHandler, DeleteSessionAssetHandler, UploadSessionAssetHandler, DeleteSessionHandler (under MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/). ADC's SessionSelectionController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionSelectionController.cs) also takes it. Implemented by InternalCommandScheduler (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/InternalCommandScheduler.cs) and registered with TryAddScoped in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Jobs.cs:161. Referenced by ADR-024, ADR-054, ADR-086, ADR-114 (the decision record for the internal-command queue itself) and ADR-121.

    1201. Caveats / not-in-source: the doc's transaction-aware persistence rule is stated in the interface's own XML remarks, not enforced by a compile-time type; a caller that intends transactional enrollment but is not actually inside an ITransactional command silently gets the immediate-save behavior instead.

    1202. @@ -1379,7 +1384,7 @@

      DependencyInjection

    1203. Why it's built this way: [Rubric §3, Clean Architecture]: registration lives in a static, partial DependencyInjection class at the composition root, so domain and Application types never reference the container. The pervasive TryAdd* and TryDecorate pattern lets a consuming app override any framework default simply by registering its own implementation first. Each of the four files carries its own extension(IServiceCollection services) block, the C# extension-member syntax the framework uses for its public DI surface (ADR-106, and primer §4 for the syntax itself).

    1204. -
    1205. Where it's used: by every consuming host. ADC and Store services call AddMmcaApplicationPipeline with the module-discovery step, the cross-service gRPC clients and the broker wiring inside the callback (for example MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:407-412, MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:233); MMCA.Helpdesk writes the sequence by hand, AddApplication() (MMCA.Helpdesk/Source/Hosts/MMCA.Helpdesk.Web/Program.cs:66), module discovery (:104), AddApplicationDecorators() (:120). ScanModuleApplicationServices<ClassReference>() is called from each module's own composition root (see ClassReference for the seven call sites). AddUserDataExportSection<TSection>() is called by ADC's Identity module for its two cross-module sections (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:48-49) and by Store's IdentityModule for its one (MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.API/IdentityModule.cs:40). VerifyDecoratorPipeline() is called by the architecture fitness tests (MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/DecoratorPipelineOrderTests.cs:66, MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/ApplicationPipelineCompositionTests.cs:160).

      +
    1206. Where it's used: by every consuming host. ADC and Store services call AddMmcaApplicationPipeline with the module-discovery step, the cross-service gRPC clients and the broker wiring inside the callback (for example MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:409-415, MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:233); MMCA.Helpdesk writes the sequence by hand, AddApplication() (MMCA.Helpdesk/Source/Hosts/MMCA.Helpdesk.Web/Program.cs:66), module discovery (:104), AddApplicationDecorators() (:120). ScanModuleApplicationServices<ClassReference>() is called from each module's own composition root (see ClassReference for the seven call sites). AddUserDataExportSection<TSection>() is called by ADC's Identity module for its two cross-module sections (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:48-49) and by Store's IdentityModule for its one (MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.API/IdentityModule.cs:40). VerifyDecoratorPipeline() is called by the architecture fitness tests (MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/DecoratorPipelineOrderTests.cs:66, MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/ApplicationPipelineCompositionTests.cs:160).

    1207. Caveats / not-in-source: several other classes named DependencyInjection exist across the framework and the apps (Infrastructure, API, Grpc, UI, Notifications, and one per module) with the same name but different namespaces and methods. This section covers only the MMCA.Common.Application root at MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:27; the others are documented in their own groups. AddApplicationProfiling() has no caller in any host in this workspace: its only callers are unit tests (MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/DependencyInjectionTests.cs:113, :123).

    1208. @@ -1399,7 +1404,7 @@

      AssemblyReference

    1209. Why it's built this way: static readonly rather than a property means the reflection call happens once per process, and the self-referencing typeof makes the anchor refactor-proof: moving the file inside the project changes nothing, and moving it out of the project is exactly the case you would want to notice.

    1210. -
    1211. Where it's used: nothing inside MMCA.Common references the Domain copy today. The Application copy backs AddValidatorsFromAssemblyContaining<ClassReference>() (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:48) and the Infrastructure copy backs the entity-configuration scan FromAssemblyOf<ClassReference>() (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:129); the Domain layer registers nothing by convention, so its marker stays available rather than exercised.

      +
    1212. Where it's used: nothing inside MMCA.Common references the Domain copy today. The Application copy backs AddValidatorsFromAssemblyContaining<ClassReference>() (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:48) and the Infrastructure copy backs the entity-configuration scan FromAssemblyOf<ClassReference>() (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:135); the Domain layer registers nothing by convention, so its marker stays available rather than exercised.

    1213. Caveats / not-in-source: the XML doc names architecture tests as a consumer, but the per-repo architecture maps anchor the Domain layer with typeof(MMCA.Common.Domain.Entities.BaseEntity<>).Assembly instead (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/AdcArchitectureMap.cs:22, and identically at MMCA.Helpdesk/Tests/Architecture/MMCA.Helpdesk.Architecture.Tests/HelpdeskArchitectureMap.cs:16). Whether a downstream consumer outside this workspace scans through this anchor is Not determinable from source here.

    1214. @@ -1418,7 +1423,7 @@

      ClassReference

    1215. Walkthrough: a single body-less type declaration, public class ClassReference; (AssemblyReference.cs:18). No members, no constructor, no interface. Deliberately not sealed and not static, because both would defeat its purpose as a generic argument for helpers that may construct it.

    1216. -
    1217. Where it's used: as with its static twin, the Domain copy has no call site inside MMCA.Common; the Application and Infrastructure copies are the ones the composition roots scan through (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:48, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:129).

      +
    1218. Where it's used: as with its static twin, the Domain copy has no call site inside MMCA.Common; the Application and Infrastructure copies are the ones the composition roots scan through (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:48, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:135).


    1219. @@ -1448,7 +1453,7 @@

      UserDataExportSectionDefaults

    1220. Depends on: nothing. It is a single const string, which is why it is Level 0 even though the export pipeline around it is not.

    1221. -
    1222. Concept introduced, the caller-safe failure message. [Rubric §30, Compliance, Privacy and Data Governance] assesses whether privacy obligations are met in the product rather than in a policy document: a data-subject access request has a legal deadline, so a partially unavailable package that says so plainly is a better answer than a failed request (ADR-076). [Rubric §11, Security] assesses what leaves the trust boundary: this text reaches an end user, so it is deliberately generic and carries no exception message, stack trace, peer address, or connection string; the detail goes to the log instead, through UserUseCaseLog.ExportSectionUnavailable. [Rubric §13, Observability and Operability]: splitting the audience in two (generic sentence out, full exception in) is the pattern, not an oversight.

      +
    1223. Concept introduced, the caller-safe failure message. [Rubric §30, Compliance, Privacy and Data Governance] assesses whether privacy obligations are met in the product rather than in a policy document: a data-subject access request has a legal deadline, so a partially unavailable package that says so plainly is a better answer than a failed request (ADR-076). [Rubric §11, Security] assesses what leaves the trust boundary: this text reaches an end user, so it is deliberately generic and carries no exception message, stack trace, peer address, or connection string; the detail goes to the log instead, through the handler-local ExportSectionUnavailable source-generated log message on ExportUserDataHandlerBase<TUser, TQuery> (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ExportUserData/ExportUserDataHandlerBase.cs:202). [Rubric §13, Observability and Operability]: splitting the audience in two (generic sentence out, full exception in) is the pattern, not an oversight.

    1224. Walkthrough: one member, public const string UnavailableReason (IUserDataExportSection.cs:112-113), whose value is "This section could not be retrieved. The data is unchanged; the export can be requested again later." The XML doc records the intent (IUserDataExportSection.cs:107-111): the subject learns the section is incomplete and retryable, and learns nothing about the internal failure. const rather than static readonly because the value is compile-time and callers use it in default-parameter position and in object initializers.

    1225. @@ -1458,137 +1463,6 @@

      UserDataExportSectionDefaults


      -

      UserUseCaseLog

      -
      -

      MMCA.Common.Application · MMCA.Common.Application.Users · MMCA.Common/Source/Core/MMCA.Common.Application/Users/UserUseCaseLog.cs:11 · Level 0 · class (internal static partial)

      -
      -
        -
      • What it is: a non-generic holder for the fifteen compile-time-generated log messages emitted by the shared Users use-case bases: password changed, preferences changed, account erased, a failed soft-deleted revocation marker, a degraded data-export section, the four that trace the forgot-password / reset-password pair, the four that trace the email-confirmation flow, and the four two-factor lifecycle events (enrollment started, enabled, disabled, recovery codes regenerated).

        -
      • -
      • Depends on: Microsoft.Extensions.Logging (ILogger, [LoggerMessage], imported at UserUseCaseLog.cs:1) and the UserIdentifierType alias. No first-party types.

        -
      • -
      • Concept introduced, source-generated logging in a non-generic holder. [Rubric §13, Observability and Operability] assesses whether diagnostics are structured, cheap, and consistent. [LoggerMessage] is a Roslyn source generator: it emits a strongly typed, allocation-free log call with a pre-compiled message template, so nothing is boxed or formatted when the level is disabled. The subtle part is where the methods live. Putting them on a generic base class would produce one generated logger per closed generic type; declaring them once in a plain static holder means every app subclass writes the identical message text, while the log category still comes from the ILogger<THandler> the subclass injects, so filtering by handler behaves exactly as it did before the shared bases existed (UserUseCaseLog.cs:5-10). [Rubric §15, Best Practices & Code Quality]: one place to change the wording of a security-relevant or privacy-relevant event.

        -
      • -
      • Walkthrough: fifteen internal static partial void declarations, each attributed with a level and a template. Every method takes the ILogger as its first parameter, which is what lets a generic base pass its own injected logger into a non-generic holder. Four of them take an Exception, and the generator finds it by type wherever it sits in the signature, so the failure detail is attached to the entry rather than interpolated into the message: ExportSectionUnavailable, PasswordResetEmailFailed, and EmailConfirmationEmailFailed take it in the conventional second position (after ILogger, before the template arguments), while SoftDeletedMarkerFailed takes it last, after the userId its template formats (UserUseCaseLog.cs:22-23). The class is internal, so none of this is public package surface.

        -
        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        MethodFile:LineLevelNotes
        PasswordChangedUserUseCaseLog.cs:13-14Information"User {UserId} password changed"
        PreferencesChangedUserUseCaseLog.cs:16-17Information"User {UserId} preferences changed"
        UserErasedUserUseCaseLog.cs:19-20Information"User {UserId} account deleted and personal data anonymized"
        SoftDeletedMarkerFailedUserUseCaseLog.cs:22-23WarningTakes an Exception in last position; "the deleted user's existing access token stays usable until it expires"
        ExportSectionUnavailableUserUseCaseLog.cs:25-26WarningTakes an Exception; "export continues with Available=false"
        PasswordResetRequestedUserUseCaseLog.cs:28-29InformationReset email sent
        PasswordResetEmailFailedUserUseCaseLog.cs:31-32WarningTakes an Exception; "the issued token stays valid"
        PasswordResetCompletedUserUseCaseLog.cs:34-35Information"Password reset completed for user {UserId}"
        PasswordResetRejectedUserUseCaseLog.cs:39-40InformationTakes only a string reason, deliberately no user id and no address
        EmailConfirmationRequestedUserUseCaseLog.cs:116-117Information"Email confirmation requested for user {UserId}; confirmation email sent"
        EmailConfirmationEmailFailedUserUseCaseLog.cs:119-120WarningTakes an Exception; "the issued token stays valid"
        EmailConfirmedUserUseCaseLog.cs:122-123Information"Email address confirmed for user {UserId}"
        EmailConfirmationRejectedUserUseCaseLog.cs:127-128InformationTakes only a string reason, deliberately no user id and no address
        TwoFactorEnrollmentStartedUserUseCaseLog.cs:130-131Information"Two-factor enrollment started for user {UserId}"
        TwoFactorEnabledUserUseCaseLog.cs:133-134Information"Two-factor authentication enabled for user {UserId}"
        TwoFactorDisabledUserUseCaseLog.cs:136-137Information"Two-factor authentication disabled for user {UserId}"
        TwoFactorRecoveryCodesRegeneratedUserUseCaseLog.cs:139-140Information"Two-factor recovery codes regenerated for user {UserId}"
        -
      • -
      • Why it's built this way: the wording of UserErased records the erasure model rather than a hard delete, the framework default in ADR-005 (the row survives soft-deleted while personal fields are anonymized). SoftDeletedMarkerFailed sits next to it and covers the other half of a deletion, the token revocation of ADR-047: the erasure is already committed when the revocation marker is written, so a cache failure there is caught and logged rather than propagated (DeleteUserHandlerBase.cs:146-149), and the template spells out the exact residual exposure the operator is being told about, that the deleted user's already-issued access token keeps working until it expires. Naming the consequence in the message is the point: a bare "cache write failed" would not tell an on-call engineer that a security control silently degraded. ExportSectionUnavailable is at Warning and not Error on purpose: a degraded section is an expected, handled outcome of a best-effort fan-out, and the request itself still succeeds. PasswordResetRejected is the most instructive signature in the file: the comment above it states the rule (UserUseCaseLog.cs:37-38), that the reset endpoints answer identically whether or not the address exists, so the log must not become the account-enumeration oracle the HTTP responses refuse to be. That is [Rubric §11, Security] applied to telemetry rather than to a response body, and it is why the method carries a free-text reason and nothing identifying. PasswordResetEmailFailed at Warning records the same split: the token was issued and stays valid, so the send failure is operationally interesting but is not a failed request. EmailConfirmationRejected repeats the same enumeration-safe pattern for the confirmation flow: the comment above it states the confirmation endpoints answer identically whether or not the address exists, so it too carries neither an address nor an account id (UserUseCaseLog.cs:125-126). EmailConfirmationEmailFailed mirrors PasswordResetEmailFailed at Warning, for the same reason: the token was already issued and stays valid, so a send failure is operationally interesting but not a failed request. The four two-factor methods stay at Information: enrollment, enabling, disabling, and recovery-code regeneration are all successful, caller-initiated state transitions rather than degraded or rejected outcomes, so none of them needs a Warning or an Exception parameter.

        -
      • -
      • Where it's used: ChangePasswordHandlerBase<TUser, TCommand> calls PasswordChanged after a successful save (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:97); ChangePreferencesHandlerBase<TUser, TCommand> calls PreferencesChanged (.../ChangePreferences/ChangePreferencesHandlerBase.cs:59); DeleteUserHandlerBase<TUser, TCommand> calls SoftDeletedMarkerFailed from the catch around the revocation marker (.../DeleteUser/DeleteUserHandlerBase.cs:148) and UserErased once the post-commit tail has run (:156); ExportUserDataHandlerBase<TUser, TQuery> calls ExportSectionUnavailable from its per-section catch (.../ExportUserData/ExportUserDataHandlerBase.cs:190); ForgotPasswordHandlerBase<TUser, TCommand> calls PasswordResetRejected three times, for a malformed address, an unknown address, and a throttled request (.../ForgotPassword/ForgotPasswordHandlerBase.cs:61, :69, :76), then PasswordResetEmailFailed (:95) or PasswordResetRequested (:99); ResetPasswordHandlerBase<TUser, TCommand> calls PasswordResetRejected for a rejected token and an unresolvable account (.../ResetPassword/ResetPasswordHandlerBase.cs:67, :76) and PasswordResetCompleted on success (:92); SendEmailConfirmationHandlerBase<TUser, TCommand> and ConfirmEmailHandlerBase<TUser, TCommand> (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/EmailConfirmation/SendEmailConfirmationHandlerBase.cs, .../ConfirmEmailHandlerBase.cs) call the email-confirmation quartet; BeginTwoFactorEnrollmentHandlerBase<TCommand>, ConfirmTwoFactorEnrollmentHandlerBase<TCommand>, DisableTwoFactorHandlerBase<TCommand>, and RegenerateRecoveryCodesHandlerBase<TCommand> (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/TwoFactor/) each call the matching two-factor method.

        -
      • -
      -

      ApplicationSettings

      MMCA.Common.Application · MMCA.Common.Application.Settings · MMCA.Common/Source/Core/MMCA.Common.Application/Settings/ApplicationSettings.cs:8 · Level 0 · class (sealed)

      @@ -1604,7 +1478,7 @@

      ApplicationSettings

    1226. Why it's built this way: static SectionName keeps registration DRY, and init immutability makes one bound instance safe to share across the process as a singleton IOptions<T> value and safe to hand to every module by value. Validating a bound value with an attribute rather than a hand-written guard keeps the ceiling declarative, while the endpoint-side fallback means a host that never opts into validation still cannot produce an unbounded export.

    1227. -
    1228. Where it's used: bound and validated by ModuleHostExtensions.AddModuleHost, which calls AddOptions<ApplicationSettings>().Bind(...).ValidateDataAnnotations().ValidateOnStart() and then reads the section a second time to get a plain instance, throwing when the section is absent (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/ModuleHostExtensions.cs:61-67). That instance is carried on ModuleHostContext.ApplicationSettings (ModuleHostContext.cs:44) and passed by value into every IModule.Register(services, configuration, applicationSettings) call, so a module reads global settings without resolving anything. Each knob then has a distinct consumer: MaxPageSize is resolved per request by EntityControllerBase<TEntity, TEntityDTO, TIdentifierType> through IOptions<ApplicationSettings> with a 500 fallback when nothing is registered (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:56-62); MaxExportRows the same way, with DefaultMaxExportRows = 100_000 (EntityControllerBase.cs:460) used both when no settings exist and when a host configures a non-positive value (:78-85, :266, ADR-078); UseMiniProfiler gates MiniProfilerExtensions (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/MiniProfilerExtensions.cs:18) and the profiling repository wrappers in RepositoryFactory (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/Factory/RepositoryFactory.cs:34, :58); DatabaseInitStrategy drives the startup switch in DatabaseInitializationExtensions (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:91-101), where any third value throws an exception naming the two valid ones (:195).

      +
    1229. Where it's used: bound and validated by ModuleHostExtensions.AddModuleHost, which calls AddOptions<ApplicationSettings>().Bind(...).ValidateDataAnnotations().ValidateOnStart() and then reads the section a second time to get a plain instance, throwing when the section is absent (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/ModuleHostExtensions.cs:61-67). That instance is carried on ModuleHostContext.ApplicationSettings (ModuleHostContext.cs:44) and passed by value into every IModule.Register(services, configuration, applicationSettings) call, so a module reads global settings without resolving anything. Each knob then has a distinct consumer: MaxPageSize is resolved per request by EntityControllerBase<TEntity, TEntityDTO, TIdentifierType> through IOptions<ApplicationSettings> with a 500 fallback when nothing is registered (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:59-65); MaxExportRows the same way, with DefaultMaxExportRows = 100_000 (EntityControllerBase.cs:472) used both when no settings exist and when a host configures a non-positive value (:78-85, :266, ADR-078); UseMiniProfiler gates MiniProfilerExtensions (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/MiniProfilerExtensions.cs:18) and the profiling repository wrappers in RepositoryFactory (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/Factory/RepositoryFactory.cs:34, :58); DatabaseInitStrategy drives the startup switch in DatabaseInitializationExtensions (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:105-115), where any third value throws an exception naming the two valid ones (:195).


    1230. @@ -1661,7 +1535,7 @@

      IUserOwnedRequest

    1231. Why it's built this way: the XML doc names the goal (IUserOwnedRequest.cs:3-7), a single uniformly applied owner-or-privileged-role check across the account-deletion and data-export use cases in both apps. The role itself is deliberately not interpreted here: each app owns its own role vocabulary, so the interface carries the raw claim string and the rule takes an already-evaluated boolean.

    1232. -
    1233. Where it's used: the type constraint on all three consumers of the ownership rule: DeleteUserHandlerBase<TUser, TCommand> (.../DeleteUser/DeleteUserHandlerBase.cs:42), ExportUserDataHandlerBase<TUser, TQuery> (.../ExportUserData/ExportUserDataHandlerBase.cs:55), and the API-layer DataExportControllerBase<TQuery> (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:62). It is also the parameter type of UserOwnershipRule.CheckOwnership. Implemented app-side by each export query (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ExportUserData/ExportUserDataQuery.cs:12, MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Application/Users/UseCases/ExportUserData/ExportUserDataQuery.cs:13) and by each delete command, both of which pair it with ICacheInvalidating (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/DeleteUser/DeleteUserCommand.cs:14, MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Application/Users/UseCases/DeleteUser/DeleteUserCommand.cs:17).

      +
    1234. Where it's used: the type constraint on all three consumers of the ownership rule: DeleteUserHandlerBase<TUser, TCommand> (.../DeleteUser/DeleteUserHandlerBase.cs:42), ExportUserDataHandlerBase<TUser, TQuery> (.../ExportUserData/ExportUserDataHandlerBase.cs:55), and the API-layer DataExportControllerBase<TQuery> (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:64). It is also the parameter type of UserOwnershipRule.CheckOwnership. Implemented app-side by each export query (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ExportUserData/ExportUserDataQuery.cs:12, MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Application/Users/UseCases/ExportUserData/ExportUserDataQuery.cs:13) and by each delete command, both of which pair it with ICacheInvalidating (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/DeleteUser/DeleteUserCommand.cs:14, MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Application/Users/UseCases/DeleteUser/DeleteUserCommand.cs:17).


    1235. @@ -1725,7 +1599,7 @@

      ModulesSettings

    1236. Why it's built this way: subclassing Dictionary<,> rather than wrapping one keeps the binder's job trivial (the section is literally a map) while still giving the two questions the loader asks a named, testable home instead of leaving TryGetValue chains scattered through composition code.

    1237. -
    1238. Where it's used: bound and validated alongside ApplicationSettings by ModuleHostExtensions.AddModuleHost, which falls back to an empty map when the section is absent (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/ModuleHostExtensions.cs:69-76) and carries it on ModuleHostContext (ModuleHostContext.cs:47). Consumed by ModuleLoader for both the enable check and the remote-dependency bypass (ModuleLoader.cs:101, :132, :136, :213), and by ModuleControllerFeatureProvider to keep MVC from mapping a disabled module's controllers (MMCA.Common/Source/Presentation/MMCA.Common.API/ModuleControllerFeatureProvider.cs:28-29, :36-41); it is also the optional first parameter of AddAPI (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:45).

      +
    1239. Where it's used: bound and validated alongside ApplicationSettings by ModuleHostExtensions.AddModuleHost, which falls back to an empty map when the section is absent (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/ModuleHostExtensions.cs:69-76) and carries it on ModuleHostContext (ModuleHostContext.cs:47). Consumed by ModuleLoader for both the enable check and the remote-dependency bypass (ModuleLoader.cs:112, :132, :136, :213), and by ModuleControllerFeatureProvider to keep MVC from mapping a disabled module's controllers (MMCA.Common/Source/Presentation/MMCA.Common.API/ModuleControllerFeatureProvider.cs:28-29, :36-41); it is also the optional first parameter of AddAPI (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:45).


    1240. @@ -1776,18 +1650,18 @@

      UserOwnershipRule


      ExportUserDataHandlerBase<TUser, TQuery>

      -

      MMCA.Common.Application · MMCA.Common.Application.Users.UseCases.ExportUserData · MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ExportUserData/ExportUserDataHandlerBase.cs:49 · Level 8 · class (abstract, generic)

      +

      MMCA.Common.Application · MMCA.Common.Application.Users.UseCases.ExportUserData · MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ExportUserData/ExportUserDataHandlerBase.cs:49 · Level 8 · class (abstract partial, generic)

      • What it is: the shared data-subject export workflow (GDPR/CCPA access and portability). It authorizes the caller, loads the account read-only, asks the subclass for the app's own snapshot of that account, fans out best-effort over every registered IUserDataExportSection, and assembles one JSON-ready package the data subject can be handed.

      • -
      • Depends on: IUnitOfWork and, through it, IReadRepository<TEntity, TIdentifierType>; IUserDataExportSection (injected as an IEnumerable); IUserOwnedRequest (the TQuery constraint) and UserOwnershipRule; AuditableAggregateRootEntity<TIdentifierType> (the TUser constraint); IQueryHandler<in TQuery, TResult> (the contract it implements); Result / Error; the Shared-layer DTOs UserDataExportDTO and UserDataExportSectionDTO (MMCA.Common/Source/Core/MMCA.Common.Shared/Privacy/UserDataExportDTO.cs:15 and :61); and UserUseCaseLog. Externals: TimeProvider and ILogger (both injected through the primary constructor, ExportUserDataHandlerBase.cs:50-53).

        +
      • Depends on: IUnitOfWork and, through it, IReadRepository<TEntity, TIdentifierType>; IUserDataExportSection (injected as an IEnumerable); IUserOwnedRequest (the TQuery constraint) and UserOwnershipRule; AuditableAggregateRootEntity<TIdentifierType> (the TUser constraint); IQueryHandler<in TQuery, TResult> (the contract it implements); Result / Error; the Shared-layer DTOs UserDataExportDTO and UserDataExportSectionDTO (MMCA.Common/Source/Core/MMCA.Common.Shared/Privacy/UserDataExportDTO.cs:15 and :61); and UserDataExportSectionDefaults. Externals: TimeProvider and ILogger (both injected through the primary constructor, ExportUserDataHandlerBase.cs:50-53), plus the [LoggerMessage] source generator from Microsoft.Extensions.Logging for its one private log method (:201-202).

      • Concept introduced, the template-method handler that owns the workflow and delegates only what is genuinely app-specific. [Rubric §2, Design Patterns] assesses deliberate pattern use: this is Template Method applied to a use case, with exactly three extension points (one abstract role check, one abstract projection, one virtual tail) and everything else fixed. [Rubric §15, Best Practices & Code Quality]: the two apps previously carried near-identical export handlers; hoisting the workflow left each subclass with the two decisions that actually differ. [Rubric §30, Compliance, Privacy and Data Governance]: the assembled document is PII by design, so its class doc states it must never be logged or cached, and notes that the caching query decorator is not applicable because the query implements no IQueryCacheable (ExportUserDataHandlerBase.cs:42-45). [Rubric §29, Resilience]: the fan-out degrades per section rather than failing, since a data-subject request carries a legal deadline (ADR-076). [Rubric §14, Testability]: because authorization arrives inside the query and time arrives as TimeProvider, the whole workflow is exercised with mocks and no host (MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Users/ExportUserDataHandlerBaseTests.cs:17).

      • Walkthrough

          -
        • Shape and constraints (ExportUserDataHandlerBase.cs:49-55): a primary-constructor abstract class taking IUnitOfWork, IEnumerable<IUserDataExportSection>, TimeProvider, and ILogger, implementing IQueryHandler<TQuery, Result<UserDataExportDTO>>, with TUser : AuditableAggregateRootEntity<UserIdentifierType> and TQuery : IUserOwnedRequest.
        • +
        • Shape and constraints (ExportUserDataHandlerBase.cs:49-55): a primary-constructor abstract partial class (partial so the logging source generator can supply the body of its private log method) taking IUnitOfWork, IEnumerable<IUserDataExportSection>, TimeProvider, and ILogger, implementing IQueryHandler<TQuery, Result<UserDataExportDTO>>, with TUser : AuditableAggregateRootEntity<UserIdentifierType> and TQuery : IUserOwnedRequest.
        • CurrentFormatVersion = "1.0" (:61) is stamped into the envelope. Its doc is precise about scope (:57-60): it versions the envelope only, so an app changing its own subject or section payloads does not move it.
        • UnitOfWork is exposed protected (:64) purely so a subject-snapshot override can read further aggregates. HandlerName is virtual and defaults to GetType().Name (:71), so a subclass that kept the pre-hoist class name reports the identical error payload it did before.
        • HandleAsync (:74-122). It null-guards the query (:78), then runs the ownership gate through UserOwnershipRule.CheckOwnership with HasExportPrivilege(query.CurrentUserRole) and the code "User.ExportForbidden" (:81-90), failing fast on rejection.
        • @@ -1796,15 +1670,15 @@

          ExportUserDataHandlerBase<TUse
        • The fan-out (:102-108): a plain foreach over the injected sections, awaited one at a time. The comment states both reasons (:102-103): sections share the scoped unit of work and its DbContext, which is not thread-safe, and registration order is the published order of the document. This is the one place where the obvious "parallelize the I/O" instinct is wrong.
        • The envelope (:110-117): FormatVersion, GeneratedOn = timeProvider.GetUtcNow(), UserId, the subject, and the section envelopes.
        • The tail (:119): OnExportCompletedAsync, then Result.Success(export) (:121).
        • -
        • RunSectionAsync (:166-199) is the degradation boundary. It calls the section, copies the four result fields into a UserDataExportSectionDTO (:177-183), and catches with the filter when (ex is not OperationCanceledException) (:185), so cancellation is not degradation and propagates as cancellation. On any other exception it logs through UserUseCaseLog.ExportSectionUnavailable with the full exception (:190) and returns an envelope with Available = false and the generic UserDataExportSectionDefaults.UnavailableReason (:192-197): detail to the log, nothing internal to the subject.
        • +
        • RunSectionAsync (:166-199) is the degradation boundary. It calls the section, copies the four result fields into a UserDataExportSectionDTO (:177-183), and catches with the filter when (ex is not OperationCanceledException) (:185), so cancellation is not degradation and propagates as cancellation. On any other exception it logs the full exception through its own private source-generated ExportSectionUnavailable (:190, declared at :201-202 at LogLevel.Warning with the template "Data-subject export section {Section} unavailable for user {UserId}; export continues with Available=false") and returns an envelope with Available = false and the generic UserDataExportSectionDefaults.UnavailableReason (:192-197): detail to the log, nothing internal to the subject.
        • The three extension points: HasExportPrivilege(string?) (abstract, :130), the role that bypasses ownership; BuildSubjectSnapshotAsync(TUser, TQuery, CancellationToken) (abstract, :144-147), which fields of the account are portable personal data, asynchronous and given the query because an app may need to read a second owned aggregate (:22-25); and OnExportCompletedAsync(...) (virtual, defaulting to Task.CompletedTask, :159-164), the app's tail for an access-log row or a metric, documented as not mutating the export and as owning its own failures because the export has already succeeded (:149-153).
      • -
      • Why it's built this way: ADR-076 records the decision, and the shape deliberately mirrors DeleteUserHandlerBase<TUser, TCommand>: the same ownership gate through the same helper, the same privileged-role hook. The credential fields are excluded from the snapshot by contract (:132-136): a password hash and salt, a refresh token, and an external-provider key are secrets, not portable personal data.

        +
      • Why it's built this way: ADR-076 records the decision, and the shape deliberately mirrors DeleteUserHandlerBase<TUser, TCommand>: the same ownership gate through the same helper, the same privileged-role hook. The credential fields are excluded from the snapshot by contract (:132-136): a password hash and salt, a refresh token, and an external-provider key are secrets, not portable personal data. The degraded-section log is Warning, not Error, on purpose: a degraded section is an expected, handled outcome of a best-effort fan-out and the request itself still succeeds (:201). Declaring that log method on the class itself keeps it next to the only call site, while the log category is whatever ILogger the app subclass passes to the primary constructor.

      • Where it's used: subclassed in each app's Identity Application assembly, where the convention scanner picks the concrete subclass up as an ordinary IQueryHandler with no extra registration (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:111-115, and the point is spelled out in the export-section registration doc at MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Extensibility.cs:31-36). ADC's ExportUserDataHandler closes it over User and ExportUserDataQuery and overrides only the two abstract members (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ExportUserData/ExportUserDataHandler.cs:30, privilege = UserRole.IsOrganizer at :38, snapshot at :41); Store's does the same with its own User (MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Application/Users/UseCases/ExportUserData/ExportUserDataHandler.cs:34). Behaviour is pinned by ExportUserDataHandlerBaseTests, including a section that throws (.../ExportUserDataHandlerBaseTests.cs:138) and a section that cancels (:217).

      • -
      • Caveats / not-in-source: the framework also ships an abstract [FeatureGate]-d endpoint, DataExportControllerBase<TQuery> (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:58-59), but neither app subclasses it: both kept their own export endpoints on top of this handler base, which the ADR records as an unadopted part of the decision.

        +
      • Caveats / not-in-source: the framework also ships an abstract [FeatureGate]-d endpoint, DataExportControllerBase<TQuery> (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:60-61), but neither app subclasses it: both kept their own export endpoints on top of this handler base, which the ADR records as an unadopted part of the decision.


      @@ -1858,7 +1732,7 @@

      ClassReference

    1241. Walkthrough: a single-line body-less type declaration at AssemblyReference.cs:11 (public class ClassReference;). No members.

    1242. -
    1243. Where it's used: DependencyInjection.AddInfrastructure calls services.Scan(scan => scan.FromAssemblyOf<ClassReference>()...) (DependencyInjection.cs:128-132, the anchor itself at :112) to discover every IEntityTypeConfigurationBase<TEntity, TIdentifierType> in the Infrastructure assembly and register it as its implemented interfaces with a scoped lifetime.

      +
    1244. Where it's used: DependencyInjection.AddInfrastructure calls services.Scan(scan => scan.FromAssemblyOf<ClassReference>()...) (DependencyInjection.cs:134-138, the anchor itself at :112) to discover every IEntityTypeConfigurationBase<TEntity, TIdentifierType> in the Infrastructure assembly and register it as its implemented interfaces with a scoped lifetime.


    1245. @@ -1969,7 +1843,7 @@

      ApplicationNamespace

      CacheKeyNamespace.From(IServiceProvider) for the per-application default Redis key namespace (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Caching/CacheKeyPrefix.cs:83-87), by MessageBusSettings.EndpointPrefix's resolution inside - AddBrokerMessaging when the setting is unset (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:79-81), and referenced + AddBrokerMessaging when the setting is unset (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:80-82), and referenced from DependencyInjection.Messaging.cs for the broker endpoint formatter. Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Configuration/ApplicationNamespaceTests.cs.

      @@ -1997,7 +1871,7 @@

      SmtpSettings

    1246. Why it's built this way: annotations plus ValidateOnStart cost one line at registration and move an entire class of misconfiguration from run time to boot, the contract ADR-070 describes. Resolving TLS through SmtpTransportSecurity instead of a hard-coded default keeps the failure mode fail-closed outside Development (ADR-122).

    1247. -
    1248. Where it's used: bound with .ValidateDataAnnotations().ValidateOnStart() in AddInfrastructure (DependencyInjection.cs:78-81); read by SmtpEmailSender.

      +
    1249. Where it's used: bound with .ValidateDataAnnotations().ValidateOnStart() in AddInfrastructure (DependencyInjection.cs:79-82); read by SmtpEmailSender.


    1250. @@ -2018,9 +1892,9 @@

      PeriodicBackgroundService

    1251. StartupDelay is virtual with a 15 second default (PeriodicBackgroundService.cs:86). The doc says why it exists: let the application finish initializing before background work competes with it. Tests override it to shorten the wait (PeriodicBackgroundServiceTests.cs:117).
    1252. IsEnabled is virtual and defaults to true (PeriodicBackgroundService.cs:93). It is read once, at the top of ExecuteAsync, and a false logs and returns (:110-114), so the service occupies no thread and no timer for the rest of the process. Flipping the toggle at runtime does not restart it.
    1253. LogCycleFailure(exception) is virtual (PeriodicBackgroundService.cs:100-101): the default writes one generic Error naming the service through the generated LogCycleError. The doc comment on the source spells out why it is overridable: a subclass can keep its own message and its own event id, which dashboards and tests key on.
    1254. -
    1255. ExecuteCycleAsync(stoppingToken) is the abstract single sweep (PeriodicBackgroundService.cs:105), and its contract is stated in the doc comment: exceptions are logged and do not stop the loop.
    1256. -
    1257. ExecuteAsync (PeriodicBackgroundService.cs:108-150) is the algorithm. The startup delay is wrapped in its own try whose catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) returns cleanly (:120-123), so a host stopped during the delay does not log a shutdown as an error. The loop then runs until cancellation (:125), awaiting the cycle, breaking on a shutdown cancellation (:131-135), routing any other exception through LogCycleFailure (:136-139), and waiting the interval under the same cancellation-aware guard (:141-148).
    1258. -
    1259. The two [LoggerMessage] methods (PeriodicBackgroundService.cs:152-156) are source-generated: LogDisabled at Information, LogCycleError at Error with the exception attached and a message that states the recovery ("it will retry on the next interval"). Both stamp GetType().Name, so the log line names the concrete sweep, not the base class; LogCycleError is now called through LogCycleFailure rather than directly from the loop.
    1260. +
    1261. ExecuteCycleAsync(stoppingToken) is the abstract single sweep (PeriodicBackgroundService.cs:50), and its contract is stated in the doc comment: exceptions are logged and do not stop the loop.
    1262. +
    1263. ExecuteAsync (PeriodicBackgroundService.cs:53-95) is the algorithm. The startup delay is wrapped in its own try whose catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) returns cleanly (:65-68), so a host stopped during the delay does not log a shutdown as an error. The loop then runs until cancellation (:70), awaiting the cycle, breaking on a shutdown cancellation (:76-80), routing any other exception through LogCycleFailure (:81-84), and waiting the interval under the same cancellation-aware guard (:86-93).
    1264. +
    1265. The two [LoggerMessage] methods (PeriodicBackgroundService.cs:97-101) are source-generated: LogDisabled at Information, LogCycleError at Error with the exception attached and a message that states the recovery ("it will retry on the next interval"). Both stamp GetType().Name, so the log line names the concrete sweep, not the base class; LogCycleError is now called through LogCycleFailure rather than directly from the loop.
    1266. Why it's built this way: the class doc draws the boundary explicitly (PeriodicBackgroundService.cs:12-16): this is for periodic reconciliation and cleanup work, and it is deliberately not what the outbox processor uses, because the outbox's signal-driven smart wait does not fit a fixed interval. The background-work posture is ADR-052, and a fixed-interval sweep is a different thing again from the cron-style recurring scheduler of ADR-074, which is why this base stays as small as it is.

      @@ -2053,7 +1927,7 @@

      InProcessDistributedLock

    1267. Why it's built this way: ADR-017 makes the lock a registration rather than an optional dependency, so a host always resolves something; this type is the honest floor of that guarantee. Logging the degradation once, instead of silently behaving like a lock, is what keeps "we have a distributed lock" from becoming a false belief in a multi-replica deployment.

    1268. -
    1269. Where it's used: registered by AddCaching in the Infrastructure composition root when no IConnectionMultiplexer is resolvable (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:95-97, inside the IDistributedLock factory at :84-98; see DependencyInjection), which covers MMCA.Helpdesk, local single-process runs, and tests. Behavior is pinned by InProcessDistributedLockTests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Concurrency/InProcessDistributedLockTests.cs:12).

      +
    1270. Where it's used: registered by AddCaching in the Infrastructure composition root when no IConnectionMultiplexer is resolvable (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:97-99, inside the IDistributedLock factory at :84-98; see DependencyInjection), which covers MMCA.Helpdesk, local single-process runs, and tests. Behavior is pinned by InProcessDistributedLockTests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Concurrency/InProcessDistributedLockTests.cs:12).


    1271. @@ -2151,7 +2025,7 @@

      RedisDistributedLock

    1272. Why it's built this way: ADR-017 replaced the process-local guard around the idempotency filter's execute-then-store window with this, because a striped semaphore stops serializing anything once a service runs more than one replica. Choosing the one-instance SET NX PX lock over Redlock is a stated trade: simpler, dependent on a single Redis, and paired with a contract that tells callers never to lean on it for an invariant persistence can enforce.

    1273. -
    1274. Where it's used: selected by AddCaching whenever an IConnectionMultiplexer is resolvable (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:84-98, see DependencyInjection), passing the same CacheKeyNamespace the distributed cache gets (DependencyInjection.Caching.cs:67, :91). The one in-framework caller is the API IdempotencyFilter; RedisDistributedLockTests covers the acquire and release commands against a mocked IDatabase.

      +
    1275. Where it's used: selected by AddCaching whenever an IConnectionMultiplexer is resolvable (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Caching.cs:86-100, see DependencyInjection), passing the same CacheKeyNamespace the distributed cache gets (DependencyInjection.Caching.cs:67, :91). The one in-framework caller is the API IdempotencyFilter; RedisDistributedLockTests covers the acquire and release commands against a mocked IDatabase.

    1276. Caveats / not-in-source: whether a given deployed environment actually supplies the redis connection string is an infrastructure/config fact, not a source fact, so "which implementation is live in environment X" is Not determinable from source here; the source only settles that the presence of a registered IConnectionMultiplexer decides it.

    1277. @@ -2159,14 +2033,14 @@

      RedisDistributedLock


      DependencyInjection

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:46 · Level 14 · class (static, extension, partial)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:47 · Level 14 · class (static, extension, partial)

        -
      • What it is: the single composition root for the entire Infrastructure layer. A static partial class split across six files by concern (DependencyInjection.cs, .Auth.cs, .Caching.cs, .Jobs.cs, .Messaging.cs, .Notifications.cs), each carrying its own C# preview extension(IServiceCollection services) block, so the registration surface reads as one type while the methods live next to the settings and services they wire. DependencyInjection.cs itself carries AddInfrastructure(IConfiguration) (:56); .Caching.cs carries AddCaching(IConfiguration?) (DependencyInjection.Caching.cs:26) and AddCommonHybridCache(Action<HybridCacheOptions>?) (:137); .Jobs.cs carries AddScheduledJobs(IConfiguration) (DependencyInjection.Jobs.cs:37), AddScheduledJob<TJob>() (:72) and AddAuditTrail(IConfiguration) (:108); .Notifications.cs carries AddNotificationInfrastructure() (DependencyInjection.Notifications.cs:26), AddPushNotifications(IConfiguration) (:41), AddNativePushNotifications(IConfiguration) (:82) and AddAzureBlobFileStorage(IConfiguration) (:114); .Messaging.cs carries AddBrokerMessaging(IConfiguration, Action?) (DependencyInjection.Messaging.cs:41) and AddTypedServiceClient<TInterface, TImplementation>(string) (:141), plus five private static helpers below its extension block (:179, :202, :241, :337, :358); .Auth.cs carries the three opt-in identity completions (DependencyInjection.Auth.cs:16-151); and DependencyInjection.cs itself also carries AddMultiTenancy(IConfiguration) (:266), AddServices() (:284), AddEntityConfigurationAssembly(Assembly) (:338) and AddStronglyTypedIds(params Assembly[]) (:366).

        +
      • What it is: the single composition root for the entire Infrastructure layer. A static partial class split across six files by concern (DependencyInjection.cs, .Auth.cs, .Caching.cs, .Jobs.cs, .Messaging.cs, .Notifications.cs), each carrying its own C# preview extension(IServiceCollection services) block, so the registration surface reads as one type while the methods live next to the settings and services they wire. DependencyInjection.cs itself carries AddInfrastructure(IConfiguration) (:56); .Caching.cs carries AddCaching(IConfiguration?) (DependencyInjection.Caching.cs:26) and AddCommonHybridCache(Action<HybridCacheOptions>?) (:137); .Jobs.cs carries AddScheduledJobs(IConfiguration) (DependencyInjection.Jobs.cs:37), AddScheduledJob<TJob>() (:72) and AddAuditTrail(IConfiguration) (:108); .Notifications.cs carries AddNotificationInfrastructure() (DependencyInjection.Notifications.cs:26), AddPushNotifications(IConfiguration) (:41), AddNativePushNotifications(IConfiguration) (:82) and AddAzureBlobFileStorage(IConfiguration) (:114); .Messaging.cs carries AddBrokerMessaging(IConfiguration, Action?) (DependencyInjection.Messaging.cs:42) and AddTypedServiceClient<TInterface, TImplementation>(string) (:141), plus five private static helpers below its extension block (:179, :202, :241, :337, :358); .Auth.cs carries the three opt-in identity completions (DependencyInjection.Auth.cs:16-155); and DependencyInjection.cs itself also carries AddMultiTenancy(IConfiguration) (:266), AddServices() (:284), AddEntityConfigurationAssembly(Assembly) (:338) and AddStronglyTypedIds(params Assembly[]) (:366).

      • Depends on: nearly every Infrastructure type below it, wired by interface. Persistence: DbContextFactory, PhysicalDbContextFactory, DataSourceService, DataSourceResolver, EntityDataSourceRegistry, DefaultEntityConfigurationAssemblyProvider, EFQueryableExecutor, SqlServerUniqueConstraintViolationDetector, EFRepository<TEntity, TIdentifierType>, RepositoryFactory, UnitOfWork, AuditSaveChangesInterceptor, DomainEventSaveChangesInterceptor, TenantSaveChangesInterceptor. Messaging/outbox: IMessageBus/InProcessMessageBus/BrokerMessageBus, IEventBus/InProcessEventBus/BrokerEventBus, OutboxSignal, OutboxProcessor, OutboxCleanupService, OutboxDisabledNoticeService, OutboxAdministration, EfInboxStore/NoOpInboxStore/InboxDisabledWarningService, ServiceBusEmulatorSupport. Cross-cutting: ICacheService with DistributedCacheService/MemoryCacheService/HybridCacheService, IDistributedLock with RedisDistributedLock/InProcessDistributedLock, IJwksProvider/RsaJwksProvider, TokenService, LoginProtectionService, PasswordResetTokenService, EFRefreshSessionStore with RefreshSessionCleanupService, EventUpcasterStartupValidator, ScheduledJobRunner, IAuditTrailReader/AuditTrailReader, TenantContext, CorrelationContext, JwtForwardingDelegatingHandler. Settings: ConnectionStringSettings with ConnectionStringSettingsValidator, DataSourcesSettings/DataSourceEntrySettings, MessageBusSettings, OutboxSettings, PersistenceSettings, SmtpSettings, JwksSettings, CacheSettings, QueryCachePipelineSettings, LoginProtectionSettings, PasswordResetSettings, RefreshSessionSettings, SchedulerSettings, AuditTrailSettings, TenancySettings with TenancySettingsValidator, PushNotificationSettings, NativePushSettings, FileStorageSettings. Externals: MassTransit v8 (pinned by policy), StackExchange.Redis, Microsoft.Extensions.Caching.Hybrid, Microsoft.AspNetCore.SignalR, Microsoft.Azure.NotificationHubs, Azure.Storage.Blobs / Azure.Identity, Microsoft.Extensions.Http.Resilience, Scrutor.

      • -
      • Concept introduced, the mega-composition-root plus the swap-at-the-edge extraction pattern. [Rubric §3, Clean Architecture] assesses whether wiring lives at the edge rather than in the core: every concrete Infrastructure choice is registered here, not in Application or Domain. [Rubric §12, Performance & Scalability] and [Rubric §7, Microservices Readiness]: the method bodies are the framework's default posture, and each optional capability (broker, scheduler, audit trail, tenancy, hybrid cache, push, native push, blob storage) is a separate opt-in method a host layers on, so the same package runs as a monolith or as an extracted service without recompiling the core. The default everywhere is TryAdd*, meaning a host can pre-register its own implementation and the framework will not clobber it; the places that deliberately break that rule are the broker swap (Replace, DependencyInjection.Messaging.cs:93 and :99), AddCommonHybridCache (RemoveAll, DependencyInjection.Caching.cs:163), and the two push registrations that overwrite their own null defaults (DependencyInjection.Notifications.cs:65-66), and each says so in a comment. [Rubric §17, DevOps]: registering a capability is consistently NOT the same as enabling it, the scheduler and the audit trail both stay inert until their Enabled flag is true (DependencyInjection.Jobs.cs:31-35, :89-95), so a host ships the registration and an environment turns it on. [Rubric §15, Best Practices & Code Quality]: the two places where a misconfiguration would be silent instead throw or warn at startup, which is the running theme of this file.

        +
      • Concept introduced, the mega-composition-root plus the swap-at-the-edge extraction pattern. [Rubric §3, Clean Architecture] assesses whether wiring lives at the edge rather than in the core: every concrete Infrastructure choice is registered here, not in Application or Domain. [Rubric §12, Performance & Scalability] and [Rubric §7, Microservices Readiness]: the method bodies are the framework's default posture, and each optional capability (broker, scheduler, audit trail, tenancy, hybrid cache, push, native push, blob storage) is a separate opt-in method a host layers on, so the same package runs as a monolith or as an extracted service without recompiling the core. The default everywhere is TryAdd*, meaning a host can pre-register its own implementation and the framework will not clobber it; the places that deliberately break that rule are the broker swap (Replace, DependencyInjection.Messaging.cs:94 and :99), AddCommonHybridCache (RemoveAll, DependencyInjection.Caching.cs:165), and the two push registrations that overwrite their own null defaults (DependencyInjection.Notifications.cs:65-66), and each says so in a comment. [Rubric §17, DevOps]: registering a capability is consistently NOT the same as enabling it, the scheduler and the audit trail both stay inert until their Enabled flag is true (DependencyInjection.Jobs.cs:31-35, :89-95), so a host ships the registration and an environment turns it on. [Rubric §15, Best Practices & Code Quality]: the two places where a misconfiguration would be silent instead throw or warn at startup, which is the running theme of this file.

      • Walkthrough (in registration order):

          @@ -2178,23 +2052,23 @@

          DependencyInjection

        • The auth block (:148-175) binds LoginProtectionSettings, PasswordResetSettings and RefreshSessionSettings with their scoped services, and gates one hosted service: RefreshSessionCleanupService is registered only when RefreshSessions:Enabled is true (:171-175), on the same flag that maps the table. The comment names the failure it avoids (:168-170): an unconditional registration would start an hourly sweep in every service of a modular host, all but one of which has no table to sweep.
        • Startup validation of the upcaster graph (:188-193): EventUpcasterStartupValidator is an IHostedService added through TryAddEnumerable, so a duplicate source, a self-map or a cycle fails the host at start rather than dead-lettering the first retired-contract message (ADR-090).
        • The outbox is a transport decision (:195-220). OutboxSignal is always registered (:195). Then the message-bus section is read eagerly (:203-204), passed to the private EnsureOutboxAvailableForProvider guard (:205), and MessageBusSettings.IsOutboxEnabled decides between the two hosted outbox services (OutboxProcessor plus OutboxCleanupService, :207-211) and a single OutboxDisabledNoticeService (:214). The comment states the trade in full (:197-202): a broker deployment cannot deliver without the outbox, while a single-process host would pay two hosted services, a table and a poll loop for a hop it never takes, and the OutboxMessages table stays mapped either way so flipping the flag is never a migration (ADR-100). The operator surface OutboxAdministration is scoped, because it creates one child scope per data source it visits (:217-220). The method then calls the private AddInternalCommands helper (:222) and AddServices() (:224).
        • -
        • AddCaching (DependencyInjection.Caching.cs:26-101) does two probes in one method, and its IConfiguration parameter is optional. With configuration it binds the key-prefix options plus CacheSettings and the Application layer's QueryCachePipelineSettings (:41-51); without it, both are registered bare so IOptions<T> still resolves to framework defaults instead of failing a host that called the parameterless overload (:54-57, with the reasoning at :30-38). The cache: if an IDistributedCache is registered and is not the no-op MemoryDistributedCache (:62), it builds DistributedCacheService over it plus any IConnectionMultiplexer, the optional CacheKeyNamespace and the TTL settings (:64-73); otherwise MemoryCacheService (:77), where the keyspace is private to the process so no prefix is needed. Both factories now resolve the namespace with CacheKeyNamespace.From(sp) (:67, :91) rather than pulling IOptions<CacheKeyPrefixOptions> out by hand at the call site, so the resolution logic lives once on the type instead of being repeated at every registration lambda. The lock: RedisDistributedLock when a multiplexer resolves (:87-93), InProcessDistributedLock otherwise (:95-97). The comment explains why the lock is registered next to the cache at all (:80-83): its one in-framework caller, the API idempotency filter, pairs the two, since the lock guards the execute-then-store window the cache entry closes.
        • -
        • AddCommonHybridCache (DependencyInjection.Caching.cs:137-180) is the opt-in two-level cache (ADR-077). It calls AddHybridCache (:139), then configures HybridCacheOptions through the options pipeline rather than the AddHybridCache callback (:145-159), because the TTL policy now comes from the bound Cache section and the callback has no service provider to read it from (:141-144); the host's own hook runs last so it can override anything the framework set (:158). It then deliberately does RemoveAll<ICacheService>() before AddSingleton (:161-177) so the call wins whether it runs before or after AddInfrastructure. The remarks are honest about the cost of that choice (:125-130): RemoveAll does not distinguish the framework's registration from a host's own custom ICacheService, so calling this is a statement that the two-level cache IS the cache.
        • +
        • AddCaching (DependencyInjection.Caching.cs:26-103) does two probes in one method, and its IConfiguration parameter is optional. With configuration it binds the key-prefix options plus CacheSettings and the Application layer's QueryCachePipelineSettings (:41-51); without it, both are registered bare so IOptions<T> still resolves to framework defaults instead of failing a host that called the parameterless overload (:54-57, with the reasoning at :30-38). The cache: if an IDistributedCache is registered and is not the no-op MemoryDistributedCache (:62), it builds DistributedCacheService over it plus any IConnectionMultiplexer, the optional CacheKeyNamespace and the TTL settings (:64-73); otherwise MemoryCacheService (:77), where the keyspace is private to the process so no prefix is needed. Both factories now resolve the namespace with CacheKeyNamespace.From(sp) (:67, :91) rather than pulling IOptions<CacheKeyPrefixOptions> out by hand at the call site, so the resolution logic lives once on the type instead of being repeated at every registration lambda. The lock: RedisDistributedLock when a multiplexer resolves (:87-93), InProcessDistributedLock otherwise (:95-97). The comment explains why the lock is registered next to the cache at all (:80-83): its one in-framework caller, the API idempotency filter, pairs the two, since the lock guards the execute-then-store window the cache entry closes.
        • +
        • AddCommonHybridCache (DependencyInjection.Caching.cs:139-182) is the opt-in two-level cache (ADR-077). It calls AddHybridCache (:139), then configures HybridCacheOptions through the options pipeline rather than the AddHybridCache callback (:145-159), because the TTL policy now comes from the bound Cache section and the callback has no service provider to read it from (:141-144); the host's own hook runs last so it can override anything the framework set (:158). It then deliberately does RemoveAll<ICacheService>() before AddSingleton (:161-177) so the call wins whether it runs before or after AddInfrastructure. The remarks are honest about the cost of that choice (:125-130): RemoveAll does not distinguish the framework's registration from a host's own custom ICacheService, so calling this is a statement that the two-level cache IS the cache.
        • AddScheduledJobs (DependencyInjection.Jobs.cs:37-51) and AddScheduledJob<TJob> (:72-78) wire the recurring-job feature (ADR-074). The first binds SchedulerSettings with validation (:39-42) and registers ScheduledJobRunner via TryAddEnumerable rather than AddHostedService (:44-48), so a host or two modules calling it twice cannot run two runners racing for the same rows. The second registers one job scoped, both as the concrete type and into the accumulating IEnumerable<IScheduledJob> (:75-76); the doc spells out that the job is scoped because the runner resolves it in a fresh scope per execution and it must hold no state between runs (:65-70).
        • AddAuditTrail (DependencyInjection.Jobs.cs:108-127) binds AuditTrailSettings (:110-113), registers the trail interceptor as a singleton for the same statelessness reason as the other three (:115-117), the scoped reader (:119), and, notably, AddScheduledJob<AuditTrailCleanupJob>() (:124). Registering the retention job here rather than in AddScheduledJobs keeps the two features independent, and the remarks state the operational consequence plainly (:96-102): without the scheduler the trail still records every change and nothing is ever purged, so AuditTrail:RetentionDays is inert.
        • -
        • AddMultiTenancy (DependencyInjection.cs:266-278) binds TenancySettings and adds TenancySettingsValidator through TryAddEnumerable (:273-275). What it switches on is resolution, not isolation: the filter, the interceptor and ITenantContext are always present and inert until a tenant resolves (:244-251), and a Tenancy:Tenants:{id}:DataSources:{sourceName} override naming a source that does not exist fails startup rather than silently falling back to the shared database (:257-264).
        • -
        • The three opt-in identity completions (ADR-116), in DependencyInjection.Auth.cs. AddTwoFactorAuthentication(configuration) (DependencyInjection.Auth.cs:39) binds TwoFactorSettings and registers ITwoFactorService/TotpTwoFactorService singleton (pure over its arguments) and ITwoFactorAuthenticator/TwoFactorAuthenticator scoped (shares the request's unit of work); it registers no ITwoFactorStore deliberately, because the account's secret and recovery hashes belong to the consumer's own User aggregate. AddEmailConfirmation(configuration) binds EmailConfirmationSettings and registers IEmailConfirmationTokenService/EmailConfirmationTokenService scoped; calling it changes nothing about sign-in until the host's User also implements IEmailConfirmableUser and sets RequireConfirmedEmail. AddStoredPermissionGrants(configuration) binds PermissionGrantSettings, registers the PermissionGrantModelGate singleton (its presence is what tells ApplicationDbContext to map the PermissionGrant table for the one data source named by Authentication:PermissionGrants:DataSourceName), the EF grant store, a PermissionGrantCache singleton that answers as the read cache, the invalidator and the refresh IHostedService all from one instance (so an edit and the reads that follow it cannot see two different snapshots), IRoleAdministrationService/StoredPermissionRoleAdministrationService, and decorates whatever IPermissionRegistry is already registered with LayeredPermissionRegistry via TryDecorate; it must be called AFTER AddAuthorizationPolicies() for that decoration to have anything to wrap, and falls back to registering an empty compiled registry first when TryDecorate finds nothing, so the stored layer still works standalone.
        • -
        • AddServices (DependencyInjection.cs:284-329) registers the small services and encodes a subtle lifetime lesson: TokenService is a singleton (:303) with a six-line comment explaining why (:297-302): a scoped lifetime disposed the RSA handle at end-of-request while IdentityModel's static CryptoProviderCache still held the cached signature provider wrapping it, throwing ObjectDisposedException on the next RS256 sign. [Rubric §11, Security] (correct signing-key lifecycle). It also sets the default IEventBus to InProcessEventBus (:305) and the default IMessageBus to InProcessMessageBus (:311), registers ITenantContext unconditionally with the reasoning inline (:290-294), and wires the inert no-op defaults for push (:315-316), native push (ADR-044, :318-321) and file storage (ADR-045, :323-326) so hosts can call the opt-in methods unconditionally. The image processor beside the storage default is the exception: it is dependency-free, so it is always the real one (:326).
        • +
        • AddMultiTenancy (DependencyInjection.cs:276-288) binds TenancySettings and adds TenancySettingsValidator through TryAddEnumerable (:273-275). What it switches on is resolution, not isolation: the filter, the interceptor and ITenantContext are always present and inert until a tenant resolves (:244-251), and a Tenancy:Tenants:{id}:DataSources:{sourceName} override naming a source that does not exist fails startup rather than silently falling back to the shared database (:257-264).
        • +
        • The three opt-in identity completions (ADR-116), in DependencyInjection.Auth.cs. AddTwoFactorAuthentication(configuration) (DependencyInjection.Auth.cs:39) binds TwoFactorSettings and registers ITwoFactorService/TotpTwoFactorService singleton (pure over its arguments) and ITwoFactorAuthenticator/TwoFactorAuthenticator scoped (shares the request's unit of work); it registers no ITwoFactorStore deliberately, because the account's secret and recovery hashes belong to the consumer's own User aggregate. AddEmailConfirmation(configuration) (DependencyInjection.Auth.cs:67) binds EmailConfirmationSettings and registers IEmailConfirmationTokenService/EmailConfirmationTokenService scoped, plus TimeProvider.System through TryAddSingleton (:79), because the token service stamps expiries from the injected clock: AddServices registers the same clock (DependencyInjection.cs:323), and the TryAdd keeps this call self-sufficient without displacing a host's own TimeProvider; calling it changes nothing about sign-in until the host's User also implements IEmailConfirmableUser and sets RequireConfirmedEmail. AddStoredPermissionGrants(configuration) binds PermissionGrantSettings, registers the PermissionGrantModelGate singleton (its presence is what tells ApplicationDbContext to map the PermissionGrant table for the one data source named by Authentication:PermissionGrants:DataSourceName), the EF grant store, a PermissionGrantCache singleton that answers as the read cache, the invalidator and the refresh IHostedService all from one instance (so an edit and the reads that follow it cannot see two different snapshots), IRoleAdministrationService/StoredPermissionRoleAdministrationService, and decorates whatever IPermissionRegistry is already registered with LayeredPermissionRegistry via TryDecorate; it must be called AFTER AddAuthorizationPolicies() for that decoration to have anything to wrap, and falls back to registering an empty compiled registry first when TryDecorate finds nothing, so the stored layer still works standalone.
        • +
        • AddServices (DependencyInjection.cs:294-339) registers the small services and encodes a subtle lifetime lesson: TokenService is a singleton (:303) with a six-line comment explaining why (:297-302): a scoped lifetime disposed the RSA handle at end-of-request while IdentityModel's static CryptoProviderCache still held the cached signature provider wrapping it, throwing ObjectDisposedException on the next RS256 sign. [Rubric §11, Security] (correct signing-key lifecycle). It also sets the default IEventBus to InProcessEventBus (:305) and the default IMessageBus to InProcessMessageBus (:311), registers ITenantContext unconditionally with the reasoning inline (:290-294), and wires the inert no-op defaults for push (:315-316), native push (ADR-044, :318-321) and file storage (ADR-045, :323-326) so hosts can call the opt-in methods unconditionally. The image processor beside the storage default is the exception: it is dependency-free, so it is always the real one (:326).
        • The opt-in channels. AddPushNotifications (DependencyInjection.Notifications.cs:41-70) binds the settings (:43-46), adds SignalR (:48), adds the Redis backplane only when a redis connection string exists (:51-62), and replaces the null senders (:65-66). AddNativePushNotifications (:82-102) and AddAzureBlobFileStorage (:114-142) both re-read their section eagerly with .Get<T>() (:87, :119) because the decision whether to register at all has to be made at composition time, and both return early on an incomplete section (:88-93, :120-130), which is what makes an unconfigured environment a no-op instead of a startup crash. The absolute-URI check at :125-126 is worth copying: an empty-string ServiceUri binds to a relative Uri, so only { IsAbsoluteUri: true } counts.
        • -
        • AddBrokerMessaging (DependencyInjection.Messaging.cs:41-121) is the extraction pivot. It reads MessageBusSettings, falling back to new MessageBusSettings() when the section is absent (:47-48); on InProcess it returns immediately (:50-53), leaving the in-process bus in place; otherwise it re-runs the outbox guard (:58, with the comment at :55-57: a service host that wires the broker without the full infrastructure registration must still fail loudly), resolves the connection string (:60), calls AddMassTransit (:62-89) and then Replaces the scoped IMessageBus with BrokerMessageBus (:93) and IEventBus with BrokerEventBus (:99), the deliberate exception to the TryAdd rule, because the in-process bus must not run alongside the broker. Inside the MassTransit callback, unless MessageBusSettings.PreserveDefaultEndpointNames is set, it installs a KebabCaseEndpointNameFormatter with includeNamespace: false (:77-85) carrying either the configured EndpointPrefix or, when that is unset, the resolved ApplicationNamespace (SEC-Common-53), because every service on a shared broker would otherwise derive the same queue name from the same consumer type and collide; PreserveDefaultEndpointNames exists solely so a deployment predating 1.188.0 can keep its bare pre-existing queue names across the upgrade.
        • -
        • The inbox branch (DependencyInjection.Messaging.cs:106-118) chooses the consumer-side dedup store from settings.IsInboxEnabled, the resolved posture rather than the raw flag (unset means ON for a broker, Messaging/MessageBusSettings.cs:141): EfInboxStore scoped when on (DependencyInjection.Messaging.cs:108), and when off the singleton NoOpInboxStore plus an InboxDisabledWarningService hosted service (:112-117). That second registration is the whole point of the branch: a disabled dedup store looks exactly like an enabled one until a duplicate side effect reaches a customer, so the posture costs one startup Warning to make visible (:114-116).
        • -
        • The five private helpers (DependencyInjection.Messaging.cs:179-361) sit outside the extension block. EnsureOutboxAvailableForProvider (:179-186) throws when a broker transport is paired with an explicitly disabled outbox, and the message says why in operational terms: the outbox is the only publish path a broker deployment has, so the alternative is every cross-service event vanishing while the service looks healthy. ResolveBrokerConnectionString (:202-211) applies an explicit precedence: MessageBus:ConnectionString, then ConnectionStrings:rabbitmq (what Aspire injects), then ConnectionStrings:messaging; without that fallback MassTransit would default to localhost:5672 and never reach the Aspire-allocated container port. ConfigureBrokerTransport (:241-324) does the per-transport wiring, calling ApplyBackpressure (:274, :314) right before cfg.ConfigureEndpoints(context) on both the RabbitMQ and the Azure Service Bus branches. ApplyBackpressure (:337-348) is the new fifth helper: it applies PrefetchCount and ConcurrentMessageLimit to the bus factory configurator, range-guarding each in code (a positive check) rather than by [Range], because AddBrokerMessaging binds the section with Get<MessageBusSettings>(), which never runs DataAnnotations. BuildRedeliveryIntervals (:358-361) maps the configured seconds to TimeSpans, dropping non-positive entries because a zero interval would schedule an immediate redelivery and turn the second retry level into a hot loop. The first three carry a justified IDE0051 suppression documenting a Roslyn false positive: the analyzer in SDK 10.0.201+ does not see references crossing the extension-block boundary.
        • -
        • Two levels of retry, and one asymmetry between transports. [Rubric §29, Resilience & Business Continuity]: every receive endpoint gets an exponential-backoff UseMessageRetry policy driven by MessageBusSettings (DependencyInjection.Messaging.cs:269-273 for RabbitMQ, :309-313 for Azure Service Bus). Above it sits second-level UseDelayedRedelivery, which reschedules a message through the broker over RedeliveryIntervalsSeconds (one minute, ten minutes, one hour by default) so an outage measured in hours does not dead-letter the event; it is registered before UseMessageRetry so the retry filter stays innermost and every immediate attempt is exhausted first (:256-267). The asymmetry is deliberate: Azure Service Bus schedules messages natively, so redelivery is applied unconditionally there (:299-307), while RabbitMQ needs the rabbitmq_delayed_message_exchange plugin that the Aspire development container does not ship, so it is gated behind EnableDelayedRedelivery, default false (:260). Right after each UseMessageRetry call, ApplyBackpressure applies PrefetchCount and ConcurrentMessageLimit to the same configurator before cfg.ConfigureEndpoints(context) runs (:274, :314).
        • -
        • The emulator detour (DependencyInjection.Messaging.cs:284-297). On the Azure Service Bus branch, the host call is not unconditional: ServiceBusEmulatorSupport.IsEmulatorConnectionString decides between ConfigureEmulatorHost(cfg, connectionString, settings.EmulatorAdminEndpoint) (:290-291) and the production cfg.Host(connectionString) (:295). The comment states the property that makes this safe (:284-287): the emulator token no real namespace emits is the only way in, so the production path is reached byte for byte as before (ADR-066).
        • -
        • AddTypedServiceClient (DependencyInjection.Messaging.cs:141-158) wires a typed HttpClient to Aspire service discovery (http://{serviceName}, :151-152), attaches JwtForwardingDelegatingHandler (:148, :153) so the inbound bearer token flows downstream, and adds the standard Polly resilience handler (:156); the S5332 suppression at :150 documents the deliberate cleartext in-cluster address, and the doc notes gRPC is preferred for service-to-service contracts (:131-135).
        • +
        • AddBrokerMessaging (DependencyInjection.Messaging.cs:42-126) is the extraction pivot. It reads MessageBusSettings, falling back to new MessageBusSettings() when the section is absent (:47-48); on InProcess it returns immediately (:50-53), leaving the in-process bus in place; otherwise it re-runs the outbox guard (:58, with the comment at :55-57: a service host that wires the broker without the full infrastructure registration must still fail loudly), resolves the connection string (:60), calls AddMassTransit (:62-89) and then Replaces the scoped IMessageBus with BrokerMessageBus (:93) and IEventBus with BrokerEventBus (:99), the deliberate exception to the TryAdd rule, because the in-process bus must not run alongside the broker. Inside the MassTransit callback, unless MessageBusSettings.PreserveDefaultEndpointNames is set, it installs a KebabCaseEndpointNameFormatter with includeNamespace: false (:77-85) carrying either the configured EndpointPrefix or, when that is unset, the resolved ApplicationNamespace (SEC-Common-53), because every service on a shared broker would otherwise derive the same queue name from the same consumer type and collide; PreserveDefaultEndpointNames exists solely so a deployment predating 1.188.0 can keep its bare pre-existing queue names across the upgrade.
        • +
        • The inbox branch (DependencyInjection.Messaging.cs:107-123) chooses the consumer-side dedup store from settings.IsInboxEnabled, the resolved posture rather than the raw flag (unset means ON for a broker, Messaging/MessageBusSettings.cs:141): EfInboxStore scoped when on (DependencyInjection.Messaging.cs:109), and when off the singleton NoOpInboxStore plus an InboxDisabledWarningService hosted service (:112-117). That second registration is the whole point of the branch: a disabled dedup store looks exactly like an enabled one until a duplicate side effect reaches a customer, so the posture costs one startup Warning to make visible (:114-116).
        • +
        • The five private helpers (DependencyInjection.Messaging.cs:196-378) sit outside the extension block. EnsureOutboxAvailableForProvider (:179-186) throws when a broker transport is paired with an explicitly disabled outbox, and the message says why in operational terms: the outbox is the only publish path a broker deployment has, so the alternative is every cross-service event vanishing while the service looks healthy. ResolveBrokerConnectionString (:202-211) applies an explicit precedence: MessageBus:ConnectionString, then ConnectionStrings:rabbitmq (what Aspire injects), then ConnectionStrings:messaging; without that fallback MassTransit would default to localhost:5672 and never reach the Aspire-allocated container port. ConfigureBrokerTransport (:241-324) does the per-transport wiring, calling ApplyBackpressure (:274, :314) right before cfg.ConfigureEndpoints(context) on both the RabbitMQ and the Azure Service Bus branches. ApplyBackpressure (:337-348) is the new fifth helper: it applies PrefetchCount and ConcurrentMessageLimit to the bus factory configurator, range-guarding each in code (a positive check) rather than by [Range], because AddBrokerMessaging binds the section with Get<MessageBusSettings>(), which never runs DataAnnotations. BuildRedeliveryIntervals (:358-361) maps the configured seconds to TimeSpans, dropping non-positive entries because a zero interval would schedule an immediate redelivery and turn the second retry level into a hot loop. The first three carry a justified IDE0051 suppression documenting a Roslyn false positive: the analyzer in SDK 10.0.201+ does not see references crossing the extension-block boundary.
        • +
        • Two levels of retry, and one asymmetry between transports. [Rubric §29, Resilience & Business Continuity]: every receive endpoint gets an exponential-backoff UseMessageRetry policy driven by MessageBusSettings (DependencyInjection.Messaging.cs:286-290 for RabbitMQ, :309-313 for Azure Service Bus). Above it sits second-level UseDelayedRedelivery, which reschedules a message through the broker over RedeliveryIntervalsSeconds (one minute, ten minutes, one hour by default) so an outage measured in hours does not dead-letter the event; it is registered before UseMessageRetry so the retry filter stays innermost and every immediate attempt is exhausted first (:256-267). The asymmetry is deliberate: Azure Service Bus schedules messages natively, so redelivery is applied unconditionally there (:299-307), while RabbitMQ needs the rabbitmq_delayed_message_exchange plugin that the Aspire development container does not ship, so it is gated behind EnableDelayedRedelivery, default false (:260). Right after each UseMessageRetry call, ApplyBackpressure applies PrefetchCount and ConcurrentMessageLimit to the same configurator before cfg.ConfigureEndpoints(context) runs (:274, :314).
        • +
        • The emulator detour (DependencyInjection.Messaging.cs:301-314). On the Azure Service Bus branch, the host call is not unconditional: ServiceBusEmulatorSupport.IsEmulatorConnectionString decides between ConfigureEmulatorHost(cfg, connectionString, settings.EmulatorAdminEndpoint) (:290-291) and the production cfg.Host(connectionString) (:295). The comment states the property that makes this safe (:284-287): the emulator token no real namespace emits is the only way in, so the production path is reached byte for byte as before (ADR-066).
        • +
        • AddTypedServiceClient (DependencyInjection.Messaging.cs:152-175) wires a typed HttpClient to Aspire service discovery (http://{serviceName}, :151-152), attaches JwtForwardingDelegatingHandler (:148, :153) so the inbound bearer token flows downstream, and adds the standard Polly resilience handler (:156); the S5332 suppression at :150 documents the deliberate cleartext in-cluster address, and the doc notes gRPC is preferred for service-to-service contracts (:131-135).
      • -
      • Why it's built this way: the extension(IServiceCollection) syntax keeps every Infrastructure registration in one file without a proliferation of static helper classes (ADR-106), and pushing all concrete choices into one composition root at the layer edge is what keeps Application and Domain free of framework references (ADR-006 for the database-per-service wiring, ADR-007/ADR-008 for the broker/extraction path). See the DI-sequence note in MMCA.Common/CLAUDE.md: hosts call AddApplicationDecorators() last so Scrutor can decorate handlers already registered, but the relative position of AddInfrastructure is not otherwise ordering-sensitive, and AddCommonHybridCache is explicitly documented as order-independent in both directions (DependencyInjection.Caching.cs:117-124).

        +
      • Why it's built this way: the extension(IServiceCollection) syntax keeps every Infrastructure registration in one file without a proliferation of static helper classes (ADR-106), and pushing all concrete choices into one composition root at the layer edge is what keeps Application and Domain free of framework references (ADR-006 for the database-per-service wiring, ADR-007/ADR-008 for the broker/extraction path). See the DI-sequence note in MMCA.Common/CLAUDE.md: hosts call AddApplicationDecorators() last so Scrutor can decorate handlers already registered, but the relative position of AddInfrastructure is not otherwise ordering-sensitive, and AddCommonHybridCache is explicitly documented as order-independent in both directions (DependencyInjection.Caching.cs:119-126).

      • Where it's used: called from each service host's Program.cs (the reference apps and the extracted MMCA.ADC.* service hosts) after AddApplication(); the optional methods (AddScheduledJobs, AddAuditTrail, AddMultiTenancy, AddCommonHybridCache, AddBrokerMessaging, AddPushNotifications, AddNativePushNotifications, AddAzureBlobFileStorage) are added by the specific hosts that need those capabilities. AddScheduledJobsTests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Scheduling/AddScheduledJobsTests.cs:15) pins the double-registration behavior of the scheduler pair.

      • @@ -2272,7 +2146,7 @@

        BrokerMetrics

      • Why it's built this way: internal static readonly instruments created once at type initialization means the recording sites resolve nothing from DI. The meter name is duplicated as a literal in MMCA.Common.Aspire (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.Telemetry.cs:311) because that package has no reference to Infrastructure, and the doc records that duplication rather than letting the next reader discover it (BrokerMetrics.cs:8-11). The comment above the Aspire list states the operational consequence: these instruments are inert in a host that stays on the in-process bus (Extensions.Telemetry.cs:296-306).

      • -
      • Where it's used: FaultIntegrationEventConsumer<TEvent> adds to FaultCounter right after logging the fault (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Messaging/Consumers/FaultIntegrationEventConsumer.cs:50-52), and OutboxProcessor adds to CircuitOpenCounter on the BrokenCircuitException branch of its publish path (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:603-609), where the surrounding comment explains why the counter is per row while the log line is per cycle (OutboxProcessor.cs:597-602, :661-665). Both recording sites are pinned by tests that listen on the meter name: FaultIntegrationEventConsumerTests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Services/FaultIntegrationEventConsumerTests.cs) and OutboxProcessorTests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/OutboxProcessorTests.cs).

        +
      • Where it's used: FaultIntegrationEventConsumer<TEvent> adds to FaultCounter right after logging the fault (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Messaging/Consumers/FaultIntegrationEventConsumer.cs:50-52), and OutboxProcessor adds to CircuitOpenCounter on the BrokenCircuitException branch of its publish path (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:598-604), where the surrounding comment explains why the counter is per row while the log line is per cycle (OutboxProcessor.cs:592-597, :661-665). Both recording sites are pinned by tests that listen on the meter name: FaultIntegrationEventConsumerTests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Services/FaultIntegrationEventConsumerTests.cs) and OutboxProcessorTests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/OutboxProcessorTests.cs).


      @@ -2299,7 +2173,7 @@

      ServiceBusEmulatorSupport

    1278. Why it's built this way: ADR-066 records the local Service Bus emulator path as part of transport selection, and ADR-016 records the MassTransit v8 pin that forces the custom-clients overload and the quota lowering. Keeping all of it in one internal static type is what lets the production branch of ConfigureBrokerTransport stay a one-liner, so a reader of the transport wiring sees the production path first and the development affordance as an explicit detour.

    1279. -
    1280. Where it's used: ConfigureBrokerTransport calls IsEmulatorConnectionString and, on a match, ConfigureEmulatorHost(cfg, connectionString, settings.EmulatorAdminEndpoint) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:290-291, the production cfg.Host(connectionString) in the else at :295). The admin endpoint is bound from MessageBus:EmulatorAdminEndpoint (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Messaging/MessageBusSettings.cs:49), which an Aspire AppHost sets from the emulator resource (MMCA.Common/Source/Hosting/MMCA.Common.Aspire.Hosting/Extensions.cs:289-291, alongside MessageBus__Provider=AzureServiceBus and the AMQP connection string). The test tier applies the same quota lowering from its own fixture, ServiceBusEmulatorFixtureBase (MMCA.Common/Source/Hosting/MMCA.Common.Testing/Fixtures/ServiceBusEmulatorFixtureBase.cs:74), which pins the emulator image (:61). ServiceBusEmulatorSupportTests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Messaging/ServiceBusEmulatorSupportTests.cs:14) covers marker detection including casing, the endpoint swap, the loud failure on a missing or non-HTTP admin endpoint, and the one-hour quota constant.

      +
    1281. Where it's used: ConfigureBrokerTransport calls IsEmulatorConnectionString and, on a match, ConfigureEmulatorHost(cfg, connectionString, settings.EmulatorAdminEndpoint) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:307-308, the production cfg.Host(connectionString) in the else at :295). The admin endpoint is bound from MessageBus:EmulatorAdminEndpoint (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Messaging/MessageBusSettings.cs:49), which an Aspire AppHost sets from the emulator resource (MMCA.Common/Source/Hosting/MMCA.Common.Aspire.Hosting/Extensions.cs:289-291, alongside MessageBus__Provider=AzureServiceBus and the AMQP connection string). The test tier applies the same quota lowering from its own fixture, ServiceBusEmulatorFixtureBase (MMCA.Common/Source/Hosting/MMCA.Common.Testing/Fixtures/ServiceBusEmulatorFixtureBase.cs:74), which pins the emulator image (:61). ServiceBusEmulatorSupportTests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Messaging/ServiceBusEmulatorSupportTests.cs:14) covers marker detection including casing, the endpoint swap, the loud failure on a missing or non-HTTP admin endpoint, and the one-hour quota constant.

    1282. Caveats / not-in-source: whether a given AppHost run uses the emulator or RabbitMQ is a host and environment decision, so "which transport a developer is on right now" is Not determinable from source here; the source only settles that the emulator branch is reachable exactly when the resolved connection string carries the marker.

    1283. @@ -2319,8 +2193,8 @@

      MessageBusProvider

    1284. Walkthrough: InProcess = 0 (MessageBusSettings.cs:241), the modular-monolith default served by InProcessMessageBus; RabbitMq = 1 (:209), MassTransit on RabbitMQ for development microservice deployments and tests; AzureServiceBus = 2 (:214), MassTransit on Azure Service Bus for production.

        -
      • AddBrokerMessaging re-reads the section eagerly, substituting a default instance when it is absent (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:47-48), and returns without touching the container when the value is InProcess (:50-53).
      • -
      • The transport configuration then switches on the same value to pick UsingRabbitMq (DependencyInjection.Messaging.cs:248-277) or UsingAzureServiceBus (:279-317), with InProcess as an explicit arm rather than a fall-through (:319-322).
      • +
      • AddBrokerMessaging re-reads the section eagerly, substituting a default instance when it is absent (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:48-49), and returns without touching the container when the value is InProcess (:50-53).
      • +
      • The transport configuration then switches on the same value to pick UsingRabbitMq (DependencyInjection.Messaging.cs:265-294) or UsingAzureServiceBus (:279-317), with InProcess as an explicit arm rather than a fall-through (:319-322).
      • The enum also gates delivery policy, not just the client type: MessageBusSettings.RedeliveryIntervalsSeconds (MessageBusSettings.cs:211, defaulting to [60, 600, 3600]) is applied unconditionally on Azure Service Bus, which has native scheduled delivery, and only when EnableDelayedRedelivery is set on RabbitMQ, which needs the delayed-message-exchange plugin (:188-193).
    1285. @@ -2352,7 +2226,7 @@

      AzureNotificationHubNativePushSend
    1286. Why it's built this way: sending the two payloads unconditionally rather than looking up each user's platform keeps the sender free of any device registry of its own. The hub already knows each installation's platform from the registration written by AzureNotificationHubDeviceRegistrar, so a payload that does not apply to a given installation is simply not delivered to it. That is the division of labour ADR-044 records: the hub is the device registry, and this codebase stores no push tokens.

    1287. -
    1288. Where it's used: registered only by AddNativePushNotifications(configuration) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:82-102), and only when the NativePush section is present with Enabled: true, a connection string, and a hub name (:88-93); the hub client itself is built from the connection string in the same call (:95-97) and this sender replaces the inert default at :98. MMCA.ADC's Notification module makes that call unconditionally (MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.API/DependencyInjection.cs:38), so the channel is switched on by configuration alone. The consuming application code is SendPushNotificationHandler (MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:31, :151), where the best-effort catch lives.

      +
    1289. Where it's used: registered only by AddNativePushNotifications(configuration) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:82-102), and only when the NativePush section is present with Enabled: true, a connection string, and a hub name (:88-93); the hub client itself is built from the connection string in the same call (:95-97) and this sender replaces the inert default at :98. MMCA.ADC's Notification module makes that call unconditionally (MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.API/DependencyInjection.cs:38), so the channel is switched on by configuration alone. The consuming application code is SendPushNotificationHandler (MMCA.Common/Source/Core/MMCA.Common.Application/Notifications/PushNotifications/UseCases/Send/SendPushNotificationHandler.cs:36, :151), where the best-effort catch lives.

    1290. Caveats / not-in-source: there is no dedicated unit test file for this class under MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Notifications/Push/; the logic it owns beyond the hub calls (payload shapes, tag chunking) is tested through NativePushPayloads. Whether a given deployment has working platform credentials is a provisioning question, not determinable from source.

    1291. @@ -2371,9 +2245,9 @@

      NullNativePushSender

    1292. Walkthrough: SendToUsersAsync (NullNativePushSender.cs:13-14) and BroadcastAsync (:17-18) are expression-bodied returns of Task.CompletedTask. There is no logging, deliberately: a per-notification "push was skipped" line in a host that never intends to enable the channel is noise, not signal.

    1293. -
    1294. Why it's built this way: ADR-044 records that the framework pipeline ships inert by default and each consumer switches it on by provisioning a hub. This class is that decision expressed in code, and the DI comment says so in one line (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:318).

      +
    1295. Why it's built this way: ADR-044 records that the framework pipeline ships inert by default and each consumer switches it on by provisioning a hub. This class is that decision expressed in code, and the DI comment says so in one line (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:328).

    1296. -
    1297. Where it's used: registered by AddInfrastructure through TryAddTransient (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:320), immediately alongside NullPushDeviceRegistrar (:580). AddNativePushNotifications uses plain AddTransient for the Azure pair (:678-679), so the later registration wins and replaces this one.

      +
    1298. Where it's used: registered by AddInfrastructure through TryAddTransient (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:330), immediately alongside NullPushDeviceRegistrar (:580). AddNativePushNotifications uses plain AddTransient for the Azure pair (:678-679), so the later registration wins and replaces this one.

    1299. Caveats / not-in-source: because the swap is "last registration wins" rather than a removal, both descriptors remain in the collection when native push is enabled. Anything resolving IEnumerable<INativePushSender> would see the no-op as well as the real sender; no first-party code does.

    1300. @@ -2386,7 +2260,7 @@

      PushNotificationSettings

      • What it is: the PushNotifications section: the on switch, the SignalR hub path, the regular expression a channel key must match before a client may join or leave a channel, and the - per-user cap on simultaneous hub connections (PushNotificationSettings.cs:8-141).

        + per-user cap on simultaneous hub connections (PushNotificationSettings.cs:8-43).

      • Depends on: NotificationScopeKey from MMCA.Common.Shared.Notifications (:1), for the Pattern constant that supplies the default. See @@ -2407,7 +2281,7 @@

        PushNotificationSettings

        takes that alignment on itself, which the doc says outright (:22-27).

        [Rubric §11, Security] also covers MaxConnectionsPerUser: an [Authorize] hub without a connection cap lets one authenticated user open unbounded WebSockets and exhaust the replica, - denying live notifications to everyone else (PushNotificationSettings.cs:133-138). 0 opts a + denying live notifications to everyone else (PushNotificationSettings.cs:31-40). 0 opts a host back out of the cap entirely.

      • Walkthrough: one static field and four init properties.

        @@ -2425,10 +2299,10 @@

        PushNotificationSettings

        cannot hang the connection, and the cache means join and leave do not recompile per call (NotificationHub.cs:43-44).
      • MaxConnectionsPerUser (:43): [Range(0, 10_000)], default 20; 0 disables the cap - (PushNotificationSettings.cs:139-140). Tagged SEC-ADC-25 in the doc comment: the hub is + (PushNotificationSettings.cs:41-42). Tagged SEC-ADC-25 in the doc comment: the hub is [Authorize] but had no OnConnectedAsync override, so a single valid user token could open unbounded WebSockets and exhaust the replica, stopping live notifications for everyone - (:133-138).
      • + (:35-40).
    1301. Why it's built this way: a configurable pattern rather than a hard-coded one lets each @@ -2497,7 +2371,7 @@

      MessageBusSettings

      before they become lost messages. This class configures two independent retry tiers. RetryLimit / RetryMinIntervalSeconds / RetryMaxIntervalSeconds feed the in-process exponential UseMessageRetry filter applied to every broker receive endpoint - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:269-273 for RabbitMQ, :309-313 for Azure Service Bus). + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:286-290 for RabbitMQ, :309-313 for Azure Service Bus). EnableDelayedRedelivery and RedeliveryIntervalsSeconds feed a second, broker-scheduled tier that sits above it, so a message that exhausts its immediate attempts is scheduled back onto the queue instead of dead-lettering (:161-195).

      @@ -2514,20 +2388,20 @@

      MessageBusSettings

      zero-configuration case.
    1302. ConnectionString (:26): nullable, and only the first of three sources. ResolveBrokerConnectionString prefers it, then falls back to ConnectionStrings:rabbitmq and - ConnectionStrings:messaging (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:202-211), which is what Aspire injects + ConnectionStrings:messaging (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:219-228), which is what Aspire injects via WithReference(broker). Without that fallback MassTransit would default to localhost:5672 - and miss the Aspire-allocated container port (DependencyInjection.Messaging.cs:195-196).
    1303. + and miss the Aspire-allocated container port (DependencyInjection.Messaging.cs:212-213).
    1304. EmulatorAdminEndpoint (:49) with [StringLength(2048)] (:48): the base address of the Azure Service Bus emulator's HTTP management plane. It exists only because MassTransit is pinned to v8, which has no vendor emulator mode; the one v8 path onto the emulator is the custom-clients Host overload that needs a data-plane client AND a management-plane client, and the emulator serves those on two different ports, so the management client cannot be derived from ConnectionString alone (:33-40). It is read only when the connection string carries - UseDevelopmentEmulator=true (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:288-291), a token no real namespace + UseDevelopmentEmulator=true (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:305-308), a token no real namespace emits, so the production path is untouched (:294-296).
    1305. EndpointPrefix (:67) with [StringLength(64)] (:66): when set, AddMassTransit installs new KebabCaseEndpointNameFormatter(settings.EndpointPrefix, includeNamespace: false) - (DependencyInjection.Messaging.cs:77-85). includeNamespace: false is deliberate: the prefix is the + (DependencyInjection.Messaging.cs:78-86). includeNamespace: false is deliberate: the prefix is the only namespacing applied, so a queue name stays readable and survives a consumer type moving between folders (:59-63). Unset does NOT mean "no prefix" (SEC-Common-53): it means the resolved ApplicationNamespace, because two applications sharing one @@ -2547,38 +2421,38 @@

      MessageBusSettings

      NoOpInboxStore plus a startup InboxDisabledWarningService when it is false, so an opt-out is recorded in the log rather than passing silently - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:106-118). The InboxMessages table is part of the shared relational + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:107-123). The InboxMessages table is part of the shared relational model created by the standard migrations, so turning it on for a migrated host needs no schema work (:91-97).
    1306. EnableOutbox (:151) and IsOutboxEnabled (:159). AddInfrastructure reads the resolved value to decide whether to run OutboxProcessor and OutboxCleanupService or the single OutboxDisabledNoticeService - (DependencyInjection.cs:189-201). Turning it off under a broker is not honored: + (DependencyInjection.cs:199-211). Turning it off under a broker is not honored: EnsureOutboxAvailableForProvider throws at registration, because a broker with no outbox has - no delivery channel at all (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:179-186, argued at :162-168). The check + no delivery channel at all (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:196-203, argued at :162-168). The check runs in both AddInfrastructure (:188) and AddBrokerMessaging (:749) so a service host that wires only the broker still fails loudly. The OutboxMessages table stays mapped either way, so flipping the flag is never a migration.
    1307. EnableDelayedRedelivery (:179), default false. It gates second-level redelivery on RabbitMQ only, because that transport needs the rabbitmq_delayed_message_exchange plugin the Aspire development container does not ship, and enabling it against a plugin-less broker fails at bus - start (:167-172, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:260-267). Azure Service Bus schedules natively, so + start (:167-172, MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:277-284). Azure Service Bus schedules natively, so the flag is deliberately not consulted there and the intervals apply unconditionally - (:173-177, DependencyInjection.Messaging.cs:299-307).
    1308. + (:173-177, DependencyInjection.Messaging.cs:316-324).
    1309. RedeliveryIntervalsSeconds (:195): defaults to [60, 600, 3600], one minute, ten minutes and one hour, a spread wide enough to ride out a dependency restart, a failover and a short incident without an operator replaying the error queue by hand (:181-187). BuildRedeliveryIntervals drops non-positive entries, because a zero or negative interval schedules an immediate redelivery, which is what UseMessageRetry already does and would turn the second level into a hot loop; when nothing survives, the caller skips the filter entirely - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:358-361).
    1310. + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:375-378).
    1311. PrefetchCount (:217) and ConcurrentMessageLimit (:227): both nullable int, both unset by default, and both range-guarded in code rather than by [Range], because AddBrokerMessaging binds this section with Get<MessageBusSettings>(), which never runs DataAnnotations, so an attribute would document the bound without enforcing it (:209-215, :219-225). ApplyBackpressure applies each one, unset or non-positive, to the transport's - own default rather than pushing a stalling value (DependencyInjection.Messaging.cs:337-348). + own default rather than pushing a stalling value (DependencyInjection.Messaging.cs:354-365). PrefetchCount caps how many messages the transport holds locally ahead of the consumer; a window larger than the work a consumer can finish inside its lock or lease is what turns a slow consumer into redeliveries, while a window of one serializes the endpoint (:210-214). @@ -2598,7 +2472,7 @@

      MessageBusSettings

      the misconfiguration becomes a startup exception instead of silently dropped events.

    1312. Where it's used: bound with .ValidateDataAnnotations().ValidateOnStart() in - AddInfrastructure (DependencyInjection.cs:163-166), then re-read eagerly in the same method to + AddInfrastructure (DependencyInjection.cs:169-172), then re-read eagerly in the same method to gate the outbox hosted services (:186-198). AddBrokerMessaging reads it again, falling back to new MessageBusSettings() when the section is absent (:738-739), short-circuits on InProcess (:741-744), and otherwise replaces IMessageBus with @@ -2610,10 +2484,10 @@

      MessageBusSettings

      parameter and consults the resolved outbox flag to choose between writing rows and dispatching directly (InProcessEventBus.cs:39, :80-84), and OutboxCleanupService reads IsInboxEnabled to - decide whether to purge inbox rows (OutboxCleanupService.cs:57). Emulator wiring is delegated to + decide whether to purge inbox rows (OutboxCleanupService.cs:52). Emulator wiring is delegated to ServiceBusEmulatorSupport. ApplyBackpressure reads PrefetchCount and ConcurrentMessageLimit off the bound instance inside the RabbitMQ and - Azure Service Bus branches of ConfigureBrokerTransport (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:274, + Azure Service Bus branches of ConfigureBrokerTransport (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:291, :314). Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Settings/SettingsTests.cs, which pins both resolution rules unset and explicit (SettingsTests.cs:267-268, :271-273, :286-287, @@ -2668,9 +2542,9 @@

      NullPushDeviceRegistrar

    1313. Walkthrough: UpsertAsync (NullPushDeviceRegistrar.cs:15-16) and DeleteAsync (:19-20) are both expression-bodied Task.FromResult(Result.Success()). There is no validation of the platform string here, unlike AzureNotificationHubDeviceRegistrar, so a request that the real registrar would reject as an unsupported platform is accepted by this one.

    1314. -
    1315. Why it's built this way: ADR-044 ships the pipeline inert, and both halves of the channel (the sender and the registrar) have to be inert together, which is why they are registered as a pair (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:320-321) and replaced as a pair (:678-679).

      +
    1316. Why it's built this way: ADR-044 ships the pipeline inert, and both halves of the channel (the sender and the registrar) have to be inert together, which is why they are registered as a pair (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:330-331) and replaced as a pair (:678-679).

    1317. -
    1318. Where it's used: registered by AddInfrastructure (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:321) and resolved by DevicesController (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Notifications/DevicesController.cs:27) in any host that has not called AddNativePushNotifications with an enabled section.

      +
    1319. Where it's used: registered by AddInfrastructure (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:331) and resolved by DevicesController (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Notifications/DevicesController.cs:27) in any host that has not called AddNativePushNotifications with an enabled section.

    1320. Caveats / not-in-source: the divergence in validation strictness between this and the real registrar means a test running against the default registration cannot catch an invalid Platform value. That behavior is only exercised through AzureNotificationHubDeviceRegistrar.

    1321. @@ -2705,20 +2579,20 @@

      FileStorageSettings


      JobClaim

      -

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Scheduling · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Scheduling/ScheduledJobRunner.cs:447 · Level 0 · record (private nested, sealed)

      +

      MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Scheduling · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Scheduling/ScheduledJobRunner.cs:455 · Level 0 · record (private nested, sealed)

      • What it is: the result of one attempt to claim a due job row: which row was due, the occurrence it was due at, the cron expression to compute the next occurrence from, and the claim token, which is null when a different replica won the row.

      • Depends on: nothing first-party. BCL only (string, DateTime, Guid?). It exists solely as the return shape of ScheduledJobRunner.TryClaimNextDueAsync.

      • -
      • Concept introduced, three answers in one return value. A claim attempt has three outcomes, not two: nothing was due, something was due and this replica took it, or something was due and another replica took it first. [Rubric §15, Best Practices & Code Quality] assesses whether the code makes illegal states hard to express: the method returns JobClaim?, so null is "nothing due" and a non-null instance with a null LockToken is "lost the race" (ScheduledJobRunner.cs:398-401). Collapsing the latter two into one null would have cost the caller the ability to mark that name as attempted for this cycle and move on, which is exactly what stops the loop spinning on a row this replica will never own (ScheduledJobRunner.cs:382-391). A positional record gets that shape in one line with value equality and no mutable state.

        +
      • Concept introduced, three answers in one return value. A claim attempt has three outcomes, not two: nothing was due, something was due and this replica took it, or something was due and another replica took it first. [Rubric §15, Best Practices & Code Quality] assesses whether the code makes illegal states hard to express: the method returns JobClaim?, so null is "nothing due" and a non-null instance with a null LockToken is "lost the race" (ScheduledJobRunner.cs:406-409). Collapsing the latter two into one null would have cost the caller the ability to mark that name as attempted for this cycle and move on, which is exactly what stops the loop spinning on a row this replica will never own (ScheduledJobRunner.cs:390-399). A positional record gets that shape in one line with value equality and no mutable state.

      • -
      • Walkthrough: four positional members, each documented (ScheduledJobRunner.cs:442-447): JobName, DueOn (the occurrence the row was due at, used for the lag metric), CronExpression (what the next occurrence is computed from, read from the row rather than recomputed from settings so an in-flight schedule change cannot retarget a run in progress), and Guid? LockToken. It is constructed once, at ScheduledJobRunner.cs:439, where the token is passed as claimed == 0 ? null : lockToken: the count of rows the claiming ExecuteUpdateAsync actually matched IS the race result.

        +
      • Walkthrough: four positional members, each documented (ScheduledJobRunner.cs:450-455): JobName, DueOn (the occurrence the row was due at, used for the lag metric), CronExpression (what the next occurrence is computed from, read from the row rather than recomputed from settings so an in-flight schedule change cannot retarget a run in progress), and Guid? LockToken. It is constructed once, at ScheduledJobRunner.cs:447, where the token is passed as claimed == 0 ? null : lockToken: the count of rows the claiming ExecuteUpdateAsync actually matched IS the race result.

      • Why it's built this way: private sealed record keeps a purely internal carrier invisible to the package's public surface, so it costs nothing in API compatibility terms while still giving the claim path a named, immutable shape (ADR-074).

      • -
      • Where it's used: returned by TryClaimNextDueAsync (ScheduledJobRunner.cs:402-440) and consumed by RunDueJobsAsync (ScheduledJobRunner.cs:365-393).

        +
      • Where it's used: returned by TryClaimNextDueAsync (ScheduledJobRunner.cs:410-448) and consumed by RunDueJobsAsync (ScheduledJobRunner.cs:373-401).


      @@ -2731,7 +2605,7 @@

      ScheduledJobEntry

    1322. Depends on: nothing first-party and nothing beyond string, DateTime and Guid from the BCL, which is why it is Level 0.

    1323. -
    1324. Concept introduced, the framework bookkeeping entity. [Rubric §4, DDD] assesses whether the domain model stays a model of the business: this class is deliberately not an IAuditableEntity and not an aggregate root, exactly like OutboxMessage, because it is framework bookkeeping rather than domain state (ScheduledJobEntry.cs:8-14). The consequences are concrete: no soft-delete flag, so no global query filter applies to it; no audit stamps, so the save interceptors have nothing to write (which is why the runner can call a plain SaveChangesAsync with no user id, ScheduledJobRunner.cs:314-319); and no concurrency token, because its concurrency control is the explicit claim lease instead. [Rubric §8, Data Architecture]: the table is host-scoped and lives in the Default data source only, unlike the outbox, which exists once per physical source (ScheduledJobEntry.cs:15-18). Jobs belong to a host, not to a database. [Rubric §11, Security] by omission: it also carries no user data, which is what keeps it in the audit trail's framework-entity exclusion set alongside the outbox and inbox rows (Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:113-119, this type at :118).

      +
    1325. Concept introduced, the framework bookkeeping entity. [Rubric §4, DDD] assesses whether the domain model stays a model of the business: this class is deliberately not an IAuditableEntity and not an aggregate root, exactly like OutboxMessage, because it is framework bookkeeping rather than domain state (ScheduledJobEntry.cs:8-14). The consequences are concrete: no soft-delete flag, so no global query filter applies to it; no audit stamps, so the save interceptors have nothing to write (which is why the runner can call a plain SaveChangesAsync with no user id, ScheduledJobRunner.cs:322-327); and no concurrency token, because its concurrency control is the explicit claim lease instead. [Rubric §8, Data Architecture]: the table is host-scoped and lives in the Default data source only, unlike the outbox, which exists once per physical source (ScheduledJobEntry.cs:15-18). Jobs belong to a host, not to a database. [Rubric §11, Security] by omission: it also carries no user data, which is what keeps it in the audit trail's framework-entity exclusion set alongside the outbox and inbox rows (Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:113-119, this type at :118).

    1326. Walkthrough, in the order the runner touches them:

        @@ -2759,13 +2633,13 @@

        ScheduledJobOverrideSettings

      • Concept introduced, code default plus configuration override. An IScheduledJob ships with a CronExpression compiled into it, which is the right default because the job's author knows what cadence the work needs. An operator who has to change that cadence for one environment should not need a release. [Rubric §17, DevOps] assesses whether operational behavior can be changed without a code change; this pair is the minimal answer: an absent entry leaves the compiled-in schedule in force, and a present, non-blank Cron replaces it (SchedulerSettings.cs:54-59, :68-73). [Rubric §15, Best Practices & Code Quality]: the override is keyed by IScheduledJob.Name, the job's own stable identity, so configuration and code agree on one name rather than on a class name that refactoring would break.

      • -
      • Walkthrough: a single string? Cron { get; init; } (SchedulerSettings.cs:74). The whole mechanism lives in the reader, not in this type: ScheduledJobRunner.ResolveCronExpression looks the job up by name and takes the override only when it is present AND non-blank, otherwise the job's own expression (ScheduledJobRunner.cs:162-166). Blank-means-absent matters, because a JSON key set to "" is a common way to try to clear a value and would otherwise produce an unparseable schedule.

        +
      • Walkthrough: a single string? Cron { get; init; } (SchedulerSettings.cs:74). The whole mechanism lives in the reader, not in this type: ScheduledJobRunner.ResolveCronExpression looks the job up by name and takes the override only when it is present AND non-blank, otherwise the job's own expression (ScheduledJobRunner.cs:170-174). Blank-means-absent matters, because a JSON key set to "" is a common way to try to clear a value and would otherwise produce an unparseable schedule.

      • -
      • Why it's built this way: the runner treats a changed expression as a schedule rewrite. On each cycle it reads the stored expressions (ScheduledJobRunner.cs:267-273), leaves a row untouched when the resolved expression matches (recomputing every cycle would push the next occurrence forward forever and nothing would ever fire, :279-285), and otherwise recomputes the next occurrence from the current instant and either updates the row with a schedule-changed log (:293-298) or inserts a new one (:299-310). So an operator edit is picked up on the next cycle, with no restart, which is what makes the override useful in the first place (ADR-074).

        +
      • Why it's built this way: the runner treats a changed expression as a schedule rewrite. On each cycle it reads the stored expressions (ScheduledJobRunner.cs:275-281), leaves a row untouched when the resolved expression matches (recomputing every cycle would push the next occurrence forward forever and nothing would ever fire, :279-285), and otherwise recomputes the next occurrence from the current instant and either updates the row with a schedule-changed log (:293-298) or inserts a new one (:299-310). So an operator edit is picked up on the next cycle, with no restart, which is what makes the override useful in the first place (ADR-074).

      • Where it's used: ScheduledJobRunner only, through SchedulerSettings.Jobs.

      • -
      • Caveats: an override that does not parse is not rejected at startup, unlike the range-checked scalar settings in this namespace. The runner logs the parse failure (ScheduledJobRunner.cs:287-291, message at :569-570) and records the job with the skipped outcome and a DateTime.MaxValue next run (:301-308), so a bad cron string is an observable non-run rather than a failed boot.

        +
      • Caveats: an override that does not parse is not rejected at startup, unlike the range-checked scalar settings in this namespace. The runner logs the parse failure (ScheduledJobRunner.cs:295-299, message at :569-570) and records the job with the skipped outcome and a DateTime.MaxValue next run (:301-308), so a bad cron string is an observable non-run rather than a failed boot.


      @@ -2778,7 +2652,7 @@

      SchedulerMetrics

    1327. Depends on: System.Diagnostics.Metrics (BCL) only (SchedulerMetrics.cs:1). Nothing first-party.

    1328. -
    1329. Concept introduced, the one-meter-per-feature instrument holder. [Rubric §13, Observability & Operability] assesses whether a running system can be understood from outside: a background loop is invisible by construction (no request, no response code), so the only evidence that a schedule is healthy is telemetry. The three instruments here are chosen to answer the three operator questions: is it running, how long does it take, and is it on time. [Rubric §31, Cost/FinOps] shows up in the same design: the per-occurrence start and finish lines are logged at Debug rather than Information precisely because a busy schedule would otherwise double the runner's steady-state log volume (ScheduledJobRunner.cs:574-581), and the numbers an operator alerts on come from these metrics instead. A host exports them by registering the MeterName meter; the Aspire service defaults (ConfigureOpenTelemetry) already do (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.Telemetry.cs:310), and the doc records that the name is duplicated as a literal there because that package has no reference to Infrastructure (SchedulerMetrics.cs:5-14).

      +
    1330. Concept introduced, the one-meter-per-feature instrument holder. [Rubric §13, Observability & Operability] assesses whether a running system can be understood from outside: a background loop is invisible by construction (no request, no response code), so the only evidence that a schedule is healthy is telemetry. The three instruments here are chosen to answer the three operator questions: is it running, how long does it take, and is it on time. [Rubric §31, Cost/FinOps] shows up in the same design: the per-occurrence start and finish lines are logged at Debug rather than Information precisely because a busy schedule would otherwise double the runner's steady-state log volume (ScheduledJobRunner.cs:583-590), and the numbers an operator alerts on come from these metrics instead. A host exports them by registering the MeterName meter; the Aspire service defaults (ConfigureOpenTelemetry) already do (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.Telemetry.cs:310), and the doc records that the name is duplicated as a literal there because that package has no reference to Infrastructure (SchedulerMetrics.cs:5-14).

    1331. Walkthrough:

        @@ -2790,7 +2664,7 @@

        SchedulerMetrics

      • Why it's built this way: internal static readonly instruments created once at type initialization means the runner records without resolving anything from DI, and keeping the meter name a constant on the same type is what lets a test assert against it. SchedulerMetricsTests (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Scheduling/SchedulerMetricsTests.cs:13) pins the instrument surface.

      • -
      • Where it's used: ScheduledJobRunner.ExecuteClaimedJobAsync records the lag before invoking the job (ScheduledJobRunner.cs:464-467) and the duration plus the outcome-tagged count after it returns (ScheduledJobRunner.cs:471-474).

        +
      • Where it's used: ScheduledJobRunner.ExecuteClaimedJobAsync records the lag before invoking the job (ScheduledJobRunner.cs:472-475) and the duration plus the outcome-tagged count after it returns (ScheduledJobRunner.cs:479-482).


      @@ -2879,8 +2753,8 @@

      TenantContext

    1332. The idempotent middle branch is the load-bearing one, and the comment at :30-31 names the scenario: the resolution middleware and a background worker that re-asserts the tenant on the same scope must not fight each other. Same value is a no-op, different value is a hard failure.
    1333. -
    1334. Why it's built this way: the registration comment in AddInfrastructure explains why the class is always registered rather than opted into (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:290-294): everything that reads it treats an unresolved tenant as "no tenancy", so an always-on registration costs one object per scope and removes a whole class of "works until someone forgets the opt-in" bug. Only AddMultiTenancy(configuration) turns the mechanism itself on.
    1335. -
    1336. Where it's used: written by TenantResolutionMiddleware on the request path (MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/TenantResolutionMiddleware.cs:70), and by the per-tenant background paths that create their own scope: database initialization (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:140), OutboxProcessor, OutboxCleanupService, OutboxAdministration, and AuditTrailCleanupJob. It is read by DbContextFactory for routing, by TenantSaveChangesInterceptor, and by the caching decorators through TenantCacheKey. Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Context/TenantContextTests.cs, and exercised for routing by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/DbContextFactoryTenantTests.cs.
    1337. +
    1338. Why it's built this way: the registration comment in AddInfrastructure explains why the class is always registered rather than opted into (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:300-304): everything that reads it treats an unresolved tenant as "no tenancy", so an always-on registration costs one object per scope and removes a whole class of "works until someone forgets the opt-in" bug. Only AddMultiTenancy(configuration) turns the mechanism itself on.
    1339. +
    1340. Where it's used: written by TenantResolutionMiddleware on the request path (MMCA.Common/Source/Presentation/MMCA.Common.API/Middleware/TenantResolutionMiddleware.cs:70), and by the per-tenant background paths that create their own scope: database initialization (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:191), OutboxProcessor, OutboxCleanupService, OutboxAdministration, and AuditTrailCleanupJob. It is read by DbContextFactory for routing, by TenantSaveChangesInterceptor, and by the caching decorators through TenantCacheKey. Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Context/TenantContextTests.cs, and exercised for routing by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/Tenancy/DbContextFactoryTenantTests.cs.
    1341. Caveats / not-in-source: the class is not thread-safe, and does not need to be as a scoped service on a request path, but two threads sharing one scope and racing on SetTenant with different values is not guarded against.

    1342. @@ -2911,18 +2785,18 @@

      SchedulerSettings

      see the table (SchedulerSettings.cs:10-15).

      [Rubric §15, Best Practices & Code Quality] assesses whether adopting a framework feature is additive. Because registration and activation are separate (AddScheduledJobs binds and registers the runner, but - the runner returns immediately unless Enabled is true, ScheduledJobRunner.cs:77), a host can + the runner returns immediately unless Enabled is true, ScheduledJobRunner.cs:85), a host can ship the registration and turn the scheduler on per environment - (DependencyInjection.cs:385-389).

      + (DependencyInjection.cs:419-423).

      [Rubric §29, Resilience and Business Continuity] assesses safe behavior under replication. LeaseSeconds is what makes multiple replicas safe: a replica claims a job row for that many seconds and other replicas skip claimed rows, so no occurrence runs twice; if the claiming replica dies mid-execution, the row becomes claimable again once the lease expires - (SchedulerSettings.cs:36-43, applied at ScheduledJobRunner.cs:424).

      + (SchedulerSettings.cs:36-43, applied at ScheduledJobRunner.cs:432).

      [Rubric §31, Cost and FinOps] assesses idle-cost defaults. PollingIntervalSeconds is a BOUND on the smart wait, not a hot loop: the runner normally sleeps until the earliest due job and uses this value only to cap that sleep and to notice a configuration-driven schedule change - (SchedulerSettings.cs:28-34, ScheduledJobRunner.cs:113-116).

      + (SchedulerSettings.cs:28-34, ScheduledJobRunner.cs:121-124).

    1343. Walkthrough: one static field, four init properties and one get-only dictionary.

        @@ -2935,16 +2809,16 @@

        SchedulerSettings

      • DataSource (:52): default DataSource.SQLServer. Jobs are host-scoped, so there is exactly one ScheduledJobs table per host rather than one per source, and this only names the relational engine that table lives on; Cosmos DB is not a valid value (:45-51). The runner resolves it - against DataSourceKey.DefaultName (ScheduledJobRunner.cs:216).
      • + against DataSourceKey.DefaultName (ScheduledJobRunner.cs:224).
      • Jobs (:60): a get-only Dictionary<string, ScheduledJobOverrideSettings> keyed by IScheduledJob.Name and bound from Scheduler:Jobs:{Name}. ResolveCronExpression takes the override only when the entry exists AND its Cron is non-blank, otherwise the job's compiled-in expression stands - (ScheduledJobRunner.cs:162-166). A changed expression is picked up on the next cycle: the + (ScheduledJobRunner.cs:170-174). A changed expression is picked up on the next cycle: the runner compares the resolved expression against the stored one, leaves an unchanged row alone (recomputing it every cycle would push every schedule forward and nothing would ever fire), and otherwise rewrites the stored value and recomputes the next occurrence from the current instant - (ScheduledJobRunner.cs:276-298).
      • + (ScheduledJobRunner.cs:284-306).
    1344. Why it's built this way: a persistent, database-backed cron scheduler with per-row leases is the @@ -2977,13 +2851,13 @@

      AzureBlobFileStorageService

      • What it is: the Azure Blob Storage implementation of IFileStorageService. It uploads and deletes blobs inside the one container the host configured, and returns a Result rather than letting a storage exception escape (AzureBlobFileStorageService.cs:10-15).
      • Depends on: Azure.Storage.Blobs.BlobContainerClient and ILogger<T> as its two primary-constructor parameters (AzureBlobFileStorageService.cs:15-17), IFileStorageService as the contract, and Result / Error for its return shapes.
      • -
      • Concept introduced, taking the container as a dependency rather than creating it. [Rubric §11, Security] assesses whether an application holds only the permissions it needs, and [Rubric §17, DevOps] assesses whether resource provisioning lives with infrastructure rather than in application startup. The class doc is explicit (AzureBlobFileStorageService.cs:12-13): the container, and crucially its public-access level, is provisioned by infrastructure and not created here. A CreateIfNotExists in application code would need container-management rights at runtime and would make the access level a property of whichever code path ran first. [Rubric §13, Observability and Operability] also applies: RequestFailedException is caught, logged with the exception, and translated into a domain-shaped failure whose message says nothing about Azure (:35-42, :54-61), so the operator sees the cause and the caller sees a stable error code.
      • +
      • Concept introduced, taking the container as a dependency rather than creating it. [Rubric §11, Security] assesses whether an application holds only the permissions it needs, and [Rubric §17, DevOps] assesses whether resource provisioning lives with infrastructure rather than in application startup. The class doc is explicit (AzureBlobFileStorageService.cs:12-13): the container, and crucially its public-access level, is provisioned by infrastructure and not created here. A CreateIfNotExists in application code would need container-management rights at runtime and would make the access level a property of whichever code path ran first. [Rubric §13, Observability and Operability] also applies: RequestFailedException is caught, logged with the exception, and translated into a domain-shaped failure whose message says nothing about Azure (:50-57, :69-76), so the operator sees the cause and the caller sees a stable error code.
      • Walkthrough
          -
        • IsConfigured is a constant true (AzureBlobFileStorageService.cs:79). Its whole purpose is to differ from the false on NullFileStorageService.
        • -
        • UploadAsync(blobName, content, contentType, ct) (AzureBlobFileStorageService.cs:82-83) is now a thin overload that forwards to UploadAsync(blobName, content, contentType, FileUploadOptions.None, ct), so a caller with no extra headers to set does not have to know the richer overload exists.
        • -
        • The real work is UploadAsync(blobName, content, contentType, options, ct) (AzureBlobFileStorageService.cs:86-116): it null-guards options (:88), resolves a blob client from the container, and uploads with BlobUploadOptions carrying the content type plus options.ContentDisposition and options.CacheControl as HTTP headers (:93-104), returning the blob's absolute URI on success (:106). Setting ContentType at upload time is what makes the blob serve correctly when a browser fetches the URL directly; the two added headers let a caller control how the browser offers or caches the file (for example, forcing a download versus inline display).
        • -
        • Its catch is narrow: only RequestFailedException, the Azure SDK's own failure type (AzureBlobFileStorageService.cs:108). It logs and returns Error.Failure with code FileStorage.UploadFailed and a caller-safe message (:110-114). Anything that is not a storage failure still propagates.
        • -
        • DeleteAsync(blobName, ct) (AzureBlobFileStorageService.cs:119-135) uses DeleteBlobIfExistsAsync, so an unknown blob name is success, which is what the interface promises ("unknown names succeed (idempotent)", MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Storage/IFileStorageService.cs:38). The same narrow catch yields code FileStorage.DeleteFailed.
        • +
        • IsConfigured is a constant true (AzureBlobFileStorageService.cs:20). Its whole purpose is to differ from the false on NullFileStorageService.
        • +
        • UploadAsync(blobName, content, contentType, ct) (AzureBlobFileStorageService.cs:23-24) is a thin overload that forwards to UploadAsync(blobName, content, contentType, FileUploadOptions.None, ct), so a caller with no extra headers to set does not have to know the richer overload exists.
        • +
        • The real work is UploadAsync(blobName, content, contentType, options, ct) (AzureBlobFileStorageService.cs:28-58). It carries a targeted SuppressMessage for RS0026 (:27): two public overloads each ending in an optional CancellationToken is what that public-API analyzer flags, and the justification records that this overload shipped after the v1.152 baseline while RS0026/RS0027 were off, so changing its signature now would be a breaking change. The body null-guards options (:30), resolves a blob client from the container (:34), and uploads with BlobUploadOptions carrying the content type plus options.ContentDisposition and options.CacheControl as HTTP headers (:35-46), returning the blob's absolute URI on success (:48). Setting ContentType at upload time is what makes the blob serve correctly when a browser fetches the URL directly; the two added headers let a caller control how the browser offers or caches the file (for example, forcing a download versus inline display).
        • +
        • Its catch is narrow: only RequestFailedException, the Azure SDK's own failure type (AzureBlobFileStorageService.cs:50). It logs and returns Error.Failure with code FileStorage.UploadFailed and a caller-safe message (:52-56). Anything that is not a storage failure still propagates.
        • +
        • DeleteAsync(blobName, ct) (AzureBlobFileStorageService.cs:61-77) uses DeleteBlobIfExistsAsync, so an unknown blob name is success, which is what the interface promises ("unknown names succeed (idempotent)", MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Storage/IFileStorageService.cs:39). The same narrow catch yields code FileStorage.DeleteFailed (:69-75).
      • Why it's built this way: ADR-045 records blob storage as the home for binary content the databases should not hold. The registration is where the credential story lives (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:114-142): ServiceUri with DefaultAzureCredential is the managed-identity production path, ConnectionString is the local Azurite path, and ContainerName is required for either. There is one carefully commented trap at :125, an empty-string ServiceUri binds to a relative Uri, so only IsAbsoluteUri counts as configured. An incomplete section returns before registering anything, which leaves the null default in place, so a host can call AddAzureBlobFileStorage unconditionally.
      • @@ -2993,27 +2867,28 @@

        AzureBlobFileStorageService


        ImageSharpImageProcessor

        -

        MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Storage · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Storage/ImageSharpImageProcessor.cs:15 · Level 4 · class (public sealed)

        +

        MMCA.Common.Infrastructure · MMCA.Common.Infrastructure.Storage · MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Storage/ImageSharpImageProcessor.cs:16 · Level 4 · class (public sealed)

          -
        • What it is: the ImageSharp implementation of IImageProcessor. It decodes an uploaded image, orients and crops it to a square, strips all metadata, and re-encodes it as JPEG, returning the bytes as a Result (ImageSharpImageProcessor.cs:10-15).
        • +
        • What it is: the ImageSharp implementation of IImageProcessor. It decodes an uploaded image, orients and crops it to a square, strips all metadata, and re-encodes it as JPEG, returning the bytes as a Result (ImageSharpImageProcessor.cs:11-16).
        • Depends on: SixLabors.ImageSharp (plus its Formats.Jpeg and Processing namespaces), IImageProcessor as the contract, and Result / Error. It takes no constructor dependencies, which is why it can be registered as a singleton.
        • -
        • Concept introduced, re-encoding as a sanitization boundary. [Rubric §11, Security] assesses whether untrusted input is neutralized rather than merely inspected, and [Rubric §30, Compliance, Privacy and Data Governance] assesses whether personal data is removed where it enters. The class doc states both payoffs in one sentence (ImageSharpImageProcessor.cs:11-13): decoding plus a full re-encode means only pixels survive, so EXIF metadata (including GPS coordinates, which are PII) and any polyglot payload smuggled into the original file are discarded. This is a stronger property than validation: a file that is simultaneously a valid JPEG and a valid archive or script cannot survive a decode-and-re-encode as anything but an image. The upload-side companion is ImageContentSniffer, which narrows the accepted formats by magic bytes rather than by the client-declared content type.
        • +
        • Concept introduced, re-encoding as a sanitization boundary. [Rubric §11, Security] assesses whether untrusted input is neutralized rather than merely inspected, and [Rubric §30, Compliance, Privacy and Data Governance] assesses whether personal data is removed where it enters. The class doc states both payoffs in one sentence (ImageSharpImageProcessor.cs:12-14): decoding plus a full re-encode means only pixels survive, so EXIF metadata (including GPS coordinates, which are PII) and any polyglot payload smuggled into the original file are discarded. This is a stronger property than validation: a file that is simultaneously a valid JPEG and a valid archive or script cannot survive a decode-and-re-encode as anything but an image. The upload-side companion is ImageContentSniffer, which narrows the accepted formats by magic bytes rather than by the client-declared content type.
        • Walkthrough
            -
          • Two public constants bound the decode: MaxDecodedPixels is 50,000,000 (ImageSharpImageProcessor.cs:267), and MaxDecodedDimension is 20,000 on either edge (:274). The remarks on MaxDecodedPixels name the trap directly (SEC-Common-28, :262-266): ADR-045's 2 MB cap bounds the compressed file, which a decompression bomb satisfies, a 2 MB PNG can declare 40000x40000 and cost roughly 6 GB of frame buffer the moment it is decoded, and a few concurrent uploads then take the replica out on an allocation the caller chose. MaxDecodedDimension closes the companion gap: a 1x200000 strip is inside the pixel-count cap yet still pathological for the resampler.
          • -
          • NormalizeToSquareJpegAsync(content, size, ct) (ImageSharpImageProcessor.cs:277-343) null-guards content (:279), then reads the format header with Image.IdentifyAsync before allocating any frame (:287-288). The comment explains the double payoff (:283-286): the header check refuses a declared-oversize image before the allocation it was built to provoke, and doing that check first keeps the failure a 400 (Error.Validation) instead of the OutOfMemoryException that would otherwise escape the catch clause below as a 500.
          • -
          • A declared size past either constant returns Error.Validation with code Image.TooLarge and the offending dimensions in the message (ImageSharpImageProcessor.cs:290-296); the stream position is restored when seekable (:298-301) before Image.LoadAsync actually decodes it (:303).
          • -
          • A second gate re-checks the decoded frame's actual dimensions (ImageSharpImageProcessor.cs:305-313), because a format whose header understates its real size, or a multi-frame file, would otherwise slip past the header check.
          • -
          • The mutation chain is AutoOrient() then Resize with ResizeMode.Crop to a size by size square (ImageSharpImageProcessor.cs:315-323). The ordering comment is the kind of detail that is only learned by shipping the bug: the EXIF orientation flag has to be baked into the pixels before metadata is stripped, or portrait phone photos come out rotated.
          • -
          • The three metadata profiles are then nulled explicitly: EXIF, XMP, and IPTC (ImageSharpImageProcessor.cs:325-327). Nulling all three matters, because camera and location data can live in more than one of them.
          • -
          • Encoding is JpegEncoder { Quality = 85 } into a MemoryStream, returned as a byte array (ImageSharpImageProcessor.cs:329-334). The stream is disposed through await using with ConfigureAwait(false).
          • -
          • The catch is exception-filtered to exactly two ImageSharp types, UnknownImageFormatException and InvalidImageContentException (ImageSharpImageProcessor.cs:336-342), and turns them into Error.Validation with code Image.Undecodable. Undecodable input is the caller's problem, so it is a validation error and not a server failure; anything else still propagates.
          • -
          • TooLargeToDecode(width, height) (ImageSharpImageProcessor.cs:352-355) is the one private helper both gates call: either edge past MaxDecodedDimension, or the area (cast to long before multiplying, to avoid an int overflow on a large declared size) past MaxDecodedPixels.
          • +
          • Two public constants bound the decode: MaxDecodedPixels is 50,000,000 (ImageSharpImageProcessor.cs:27), and MaxDecodedDimension is 20,000 on either edge (:34). The remarks on MaxDecodedPixels name the trap directly (SEC-Common-28, :22-26): ADR-045's 2 MB cap bounds the compressed file, which a decompression bomb satisfies, a 2 MB PNG can declare 40000x40000 and cost roughly 6 GB of frame buffer the moment it is decoded, and a few concurrent uploads then take the replica out on an allocation the caller chose. MaxDecodedDimension closes the companion gap: a 1x200000 strip is inside the pixel-count cap yet still pathological for the resampler.
          • +
          • NormalizeToSquareJpegAsync(content, size, ct) (ImageSharpImageProcessor.cs:37-106) null-guards content (:39), then reads the format header with Image.IdentifyAsync before allocating any frame (:47-48). The comment explains the double payoff (:43-46): the header check refuses a declared-oversize image before the allocation it was built to provoke, and doing that check first keeps the failure a 400 (Error.Validation) instead of the OutOfMemoryException that would otherwise escape the catch clause below as a 500.
          • +
          • A declared size past either constant returns Error.Validation with code Image.TooLarge and the offending dimensions in the message (ImageSharpImageProcessor.cs:50-56); the stream position is restored when seekable (:58-61) before Image.LoadAsync actually decodes it (:66).
          • +
          • That decode passes new DecoderOptions { MaxFrames = 1 } (ImageSharpImageProcessor.cs:66). The comment above it gives the reason (:63-65): the output is a single JPEG built from the first frame, and decoding every frame of an animated file is how a small upload becomes hundreds of megabytes of pixel buffers. MaxFrames is what bounds the multi-frame case; the dimension gates alone bound only one frame at a time.
          • +
          • A second gate re-checks the decoded frame's actual dimensions (ImageSharpImageProcessor.cs:68-76), because a format whose header understates its real size would otherwise slip past the header check.
          • +
          • The mutation chain is AutoOrient() then Resize with ResizeMode.Crop to a size by size square (ImageSharpImageProcessor.cs:78-86). The ordering comment is the kind of detail that is only learned by shipping the bug: the EXIF orientation flag has to be baked into the pixels before metadata is stripped, or portrait phone photos come out rotated.
          • +
          • The three metadata profiles are then nulled explicitly: EXIF, XMP, and IPTC (ImageSharpImageProcessor.cs:88-90). Nulling all three matters, because camera and location data can live in more than one of them.
          • +
          • Encoding is JpegEncoder { Quality = 85 } into a MemoryStream, returned as a byte array (ImageSharpImageProcessor.cs:92-97). The stream is disposed through await using with ConfigureAwait(false).
          • +
          • The catch is exception-filtered to exactly two ImageSharp types, UnknownImageFormatException and InvalidImageContentException (ImageSharpImageProcessor.cs:99-105), and turns them into Error.Validation with code Image.Undecodable. Undecodable input is the caller's problem, so it is a validation error and not a server failure; anything else still propagates.
          • +
          • TooLargeToDecode(width, height) (ImageSharpImageProcessor.cs:115-118) is the one private helper both gates call: either edge past MaxDecodedDimension, or the area (cast to long before multiplying, to avoid an int overflow on a large declared size) past MaxDecodedPixels.
        • Why it's built this way: ADR-045 records normalization as a framework leg rather than an application concern, so every consumer that accepts an image gets the same stripping and the same output shape. Keeping the size a parameter rather than a constant lets the calling handler own the product decision: MMCA.ADC's avatar path passes its own square size from SetUserAvatarHandler. The two decode-size gates close SEC-Common-28, a decompression-bomb denial-of-service that the 2 MB compressed-file cap alone did not prevent, by refusing before the allocation happens rather than catching after.
        • -
        • Where it's used: registered as a singleton by AddInfrastructure (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:326), and the comment there records why it is unconditional (:583): the image processor is dependency-free and always real, unlike the file storage default beside it (:584). Called by SetUserAvatarHandler before the upload. Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Storage/ImageSharpImageProcessorTests.cs.
        • -
        • Caveats / not-in-source: the two decode-size gates bound the frame's dimensions and pixel count, but nothing here further bounds working-set memory beyond that; ImageContentSniffer restricts the accepted formats to JPEG, PNG, and WebP by magic bytes, and the compressed-file size guard remains the handler's to write.
        • +
        • Where it's used: registered as a singleton by AddInfrastructure (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:336), and the comment there records why it is unconditional (:334): the image processor is dependency-free and always real, unlike the file storage default beside it (:335). Called by SetUserAvatarHandler before the upload. Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Storage/ImageSharpImageProcessorTests.cs, with the single-frame bound pinned separately by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Storage/ImageSharpImageProcessorFrameBoundTests.cs.
        • +
        • Caveats / not-in-source: the two decode-size gates bound the frame's dimensions and pixel count and MaxFrames = 1 bounds the frame count, but nothing here further bounds working-set memory beyond that; ImageContentSniffer restricts the accepted formats to JPEG, PNG, and WebP by magic bytes, and the compressed-file size guard remains the handler's to write.

        NullFileStorageService

        @@ -3026,14 +2901,14 @@

        NullFileStorageService

      • Concept: the null-object default introduced at NullNativePushSender, with one instructive difference. [Rubric §29, Resilience and Business Continuity] assesses whether a missing capability degrades predictably. This null object is not uniformly silent: the two operations are treated asymmetrically because their meanings differ. A caller that wanted to store something and got nothing must be told (an upload that silently succeeded while storing nothing would leave a dangling URL in the database), whereas a caller that wanted something gone already has what it asked for.
      • Walkthrough
        • IsConfigured returns false (NullFileStorageService.cs:14), the flag the interface offers so a handler can gate a feature (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Storage/IFileStorageService.cs:13-14).
        • -
        • UploadAsync(blobName, content, contentType, ct) and the FileUploadOptions overload UploadAsync(blobName, content, contentType, options, ct) (NullFileStorageService.cs:17-21, :23-24) both simply return NotConfigured(): the extra options parameter changes nothing about a store that never stores anything.
        • -
        • DeleteAsync (NullFileStorageService.cs:27-28) returns success.
        • -
        • NotConfigured() (NullFileStorageService.cs:30-33) is the private helper both upload overloads share: a failed Result carrying Error.Failure with code FileStorage.NotConfigured, a message naming the actual condition ("No file storage is configured for this host"), and source set to the type name. Factoring it out is what let the second overload's implementation be a one-line forward when FileUploadOptions was added to the interface.
        • +
        • UploadAsync(blobName, content, contentType, ct) and the FileUploadOptions overload UploadAsync(blobName, content, contentType, options, ct) (NullFileStorageService.cs:17-18, :22-23) both simply return NotConfigured(): the extra options parameter changes nothing about a store that never stores anything. The options overload carries the same grandfathered RS0026 suppression as its Azure sibling (:21), since changing a shipped public signature would be a breaking change.
        • +
        • DeleteAsync (NullFileStorageService.cs:26-27) returns success.
        • +
        • NotConfigured() (NullFileStorageService.cs:29-33) is the private helper both upload overloads share: a failed Result carrying Error.Failure with code FileStorage.NotConfigured, a message naming the actual condition ("No file storage is configured for this host"), and source set to the type name. Factoring it out is what let the second overload's implementation be a one-line forward when FileUploadOptions was added to the interface.
      • Why it's built this way: the class doc says it directly (NullFileStorageService.cs:7-9), feature endpoints degrade cleanly. Because the failure is a Result and not an exception (ADR-013), an avatar upload in a host with no storage configured returns a clean error response instead of a 500, and the same code path works whether or not the deployment has provisioned a storage account (ADR-045).
      • -
      • Where it's used: registered by AddInfrastructure through TryAddTransient (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:325), replaced by AzureBlobFileStorageService when AddAzureBlobFileStorage finds a complete FileStorage section (:719). It is also the storage the ADC profile E2E suite runs against (MMCA.ADC/Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/Identity/ProfileManagementTests.cs).
      • -
      • Caveats / not-in-source: no first-party code currently reads IsConfigured; the only two declarations are the ones in this unit (NullFileStorageService.cs:14 and AzureBlobFileStorageService.cs:79). Consumers today discover the unconfigured state from the failed upload result instead, so the gating flag is an available extension point rather than an exercised one.
      • +
      • Where it's used: registered by AddInfrastructure through TryAddTransient (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:335), replaced by AzureBlobFileStorageService when AddAzureBlobFileStorage finds a complete FileStorage section (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Notifications.cs:139). It is also the storage the ADC profile E2E suite runs against (MMCA.ADC/Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/Identity/ProfileManagementTests.cs), and both upload overloads are exercised by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Storage/FileUploadOptionsOverloadTests.cs.
      • +
      • Caveats / not-in-source: nothing inside MMCA.Common reads IsConfigured; the two declarations are the ones in this unit (NullFileStorageService.cs:14 and AzureBlobFileStorageService.cs:20). The flag is exercised downstream: MMCA.ADC's UploadSessionAssetHandler checks it before touching storage and returns its own SessionAsset.StorageNotConfigured failure, whose message steers the user to add a link instead (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/SessionAssets/UseCases/UploadFile/UploadSessionAssetHandler.cs:56-62). The avatar path still discovers the unconfigured state from the failed upload result.

      ScopedUserOverride

      @@ -3063,11 +2938,11 @@

      AmbientOrigin

    1345. What it is: the shared helper that flattens a capturing principal's roles for storage, rebuilds a ClaimsPrincipal from what was stored, and restores a captured user, tenant, and correlation id onto a fresh scope (AmbientOrigin.cs:19-163). It is the one place this logic exists; everything that later replays a captured origin, an internal command row, an outbox row, a broker message, calls into it.
    1346. Depends on: ScopedUserOverride and ITenantContext (from TenantContext) as the scope services it restores onto; ICorrelationContext for the correlation id; the UserIdentifierType alias; System.Security.Claims for Claim/ClaimsIdentity/ClaimsPrincipal; AuthClaimTypes for the subject claim type.
    1347. Concept introduced, one flatten/rebuild pair reused across every async boundary. [Rubric §11, Security] assesses whether identity propagation is centralized rather than reimplemented per call site, and [Rubric §8, Data Architecture] assesses whether the tenancy invariant holds even off the request path. MaxRolesLength (AmbientOrigin.cs:24-27, value 512) is the column width shared by InternalCommands.UserRoles and OutboxMessages.UserRoles; FlattenRoles truncates to fit rather than throw, and is explicitly null-tolerant even though ICurrentUserService.Roles is declared non-nullable, because Roles is a default interface member that runs against hand-written doubles and mocks that stub only the properties a caller touches (:35-40). A null there must read as "no roles", never as a capture that throws inside a save.
    1348. -
    1349. Concept, the tenant is the one value Restore will not overwrite. Restore (AmbientOrigin.cs:611-647) documents an explicit asymmetry (:587-604): the principal and correlation id are set or cleared on every call, because a scope reused across several messages cannot let one message's user answer for the next, but ITenantContext is single-valued for the life of a scope by contract, since a scope whose tenant changed mid-flight has already read rows under the previous one. Restore therefore sets the tenant only when the scope has not already resolved a different one (:621-627); a host running database-per-tenant is unaffected, because its work is already one scope per tenant.
    1350. +
    1351. Concept, the tenant is the one value Restore will not overwrite. Restore (AmbientOrigin.cs:127-162) documents an explicit asymmetry (:103-114): the principal and correlation id are set or cleared on every call, because a scope reused across several messages cannot let one message's user answer for the next, but ITenantContext is single-valued for the life of a scope by contract, since a scope whose tenant changed mid-flight has already read rows under the previous one. Restore therefore sets the tenant only when the scope has not already resolved a different one (:137-143); a host running database-per-tenant is unaffected, because its work is already one scope per tenant.
    1352. Walkthrough
        -
      • FlattenRoles(roles) (AmbientOrigin.cs:525-541) joins the roles with a comma and truncates to MaxRolesLength, returning null for a null or empty source.
      • -
      • BuildPrincipal(userId, userRoles, authenticationType) (AmbientOrigin.cs:557-580) returns null when userId is null, meaning no user was captured, and the execution stays anonymous rather than inventing an identity. Otherwise it builds a sub claim from the id and one role claim per comma-separated entry, then wraps them in a ClaimsIdentity stamped with authenticationType, which is what makes IsAuthenticated true.
      • -
      • Restore(services, userId, userRoles, tenantId, correlationId, authenticationType) (AmbientOrigin.cs:611-647) resolves each of the three scope services with GetService rather than a required resolve, so a bare provider (a directly constructed test fixture) simply restores nothing rather than failing the hop (:600-603). It sets the tenant first (:619-627), because it routes the scoped context factory to the right database and every repository resolved afterward reads its query filter from it; then it sets or clears ScopedUserOverride (:629-640); then it sets the correlation id when one was carried (:642-645).
      • +
      • FlattenRoles(roles) (AmbientOrigin.cs:41-57) joins the roles with a comma and truncates to MaxRolesLength, returning null for a null or empty source.
      • +
      • BuildPrincipal(userId, userRoles, authenticationType) (AmbientOrigin.cs:73-96) returns null when userId is null, meaning no user was captured, and the execution stays anonymous rather than inventing an identity. Otherwise it builds a sub claim from the id and one role claim per comma-separated entry, then wraps them in a ClaimsIdentity stamped with authenticationType, which is what makes IsAuthenticated true.
      • +
      • Restore(services, userId, userRoles, tenantId, correlationId, authenticationType) (AmbientOrigin.cs:127-162) resolves each of the three scope services with GetService rather than a required resolve, so a bare provider (a directly constructed test fixture) simply restores nothing rather than failing the hop (:116-118). It sets the tenant first (:135-143), because it routes the scoped context factory to the right database and every repository resolved afterward reads its query filter from it; then it sets or clears ScopedUserOverride (:145-156); then it sets the correlation id when one was carried (:158-161).
    1353. Why it's built this way: centralizing flatten, rebuild, and restore in one internal static class means every caller, the internal command scheduler, the outbox processor, the broker consumers, restores context identically; a divergence in any one of them would have been a silent identity or tenant leak rather than a compile error.
    1354. @@ -3181,24 +3056,24 @@

      ScheduledJobRunner

      • What it is: the framework's recurring job scheduler. A BackgroundService that runs the host's registered IScheduledJob implementations on their cron schedules, using a persistent job store (ScheduledJobEntry) and the outbox processor's claim-lease idiom so an occurrence executes exactly once across every replica.

      • -
      • Depends on: IServiceScopeFactory, ILogger<ScheduledJobRunner>, IOptions<SchedulerSettings> (SchedulerSettings), IDataSourceResolver, and an optional TimeProvider (ScheduledJobRunner.cs:39-44). At run time it resolves IDbContextFactory per cycle (:214) and works against ApplicationDbContext; it emits through SchedulerMetrics and returns its claim results as JobClaim. Externals: EF Core (ExecuteUpdateAsync), Microsoft.Extensions.Hosting, and Cronos, aliased as CronSchedule (ScheduledJobRunner.cs:12).

        +
      • Depends on: IServiceScopeFactory, ILogger<ScheduledJobRunner>, IOptions<SchedulerSettings> (SchedulerSettings), IDataSourceResolver, and an optional TimeProvider (ScheduledJobRunner.cs:39-44). At run time it resolves IDbContextFactory per cycle (:222) and works against ApplicationDbContext; it emits through SchedulerMetrics and returns its claim results as JobClaim. Externals: EF Core (ExecuteUpdateAsync), Microsoft.Extensions.Hosting, and Cronos, aliased as CronSchedule (ScheduledJobRunner.cs:12).

      • -
      • Concept introduced, the claim-lease scheduler. [Rubric §29, Resilience & Business Continuity] assesses whether work survives failure: a schedule that lives in a timer dies with the process, so the schedule lives in a table and the loop only reads it. [Rubric §12, Performance & Scalability] assesses scale-out: an interval-driven hosted service runs on every replica, so scaling a service to three instances silently triples a purge. The fix is the outbox's claim: a single ExecuteUpdateAsync that stamps LockedUntil and LockToken over a Where admitting only unleased-or-expired rows, where the count of matched rows IS the race result (ScheduledJobRunner.cs:427-439). Two replicas both issue that update and exactly one matches. [Rubric §13, Observability & Operability]: every branch that a human would need to explain later has a [LoggerMessage] line, including the ones that do nothing (disabled scheduler, duplicate job name, lease lost). [Rubric §14, Testability]: the injected TimeProvider plus an internal RunCycleAsync mean a test drives one whole cycle deterministically without waiting on wall-clock timers (ScheduledJobRunner.cs:204-205).

        +
      • Concept introduced, the claim-lease scheduler. [Rubric §29, Resilience & Business Continuity] assesses whether work survives failure: a schedule that lives in a timer dies with the process, so the schedule lives in a table and the loop only reads it. [Rubric §12, Performance & Scalability] assesses scale-out: an interval-driven hosted service runs on every replica, so scaling a service to three instances silently triples a purge. The fix is the outbox's claim: a single ExecuteUpdateAsync that stamps LockedUntil and LockToken over a Where admitting only unleased-or-expired rows, where the count of matched rows IS the race result (ScheduledJobRunner.cs:435-447). Two replicas both issue that update and exactly one matches. [Rubric §13, Observability & Operability]: every branch that a human would need to explain later has a [LoggerMessage] line, including the ones that do nothing (disabled scheduler, duplicate job name, lease lost). [Rubric §14, Testability]: the injected TimeProvider plus an internal RunCycleAsync mean a test drives one whole cycle deterministically without waiting on wall-clock timers (ScheduledJobRunner.cs:212-213).

      • Walkthrough:

          -
        • State and constants. _settings snapshots IOptions<SchedulerSettings>.Value once (ScheduledJobRunner.cs:46), and _timeProvider falls back to TimeProvider.System (:47). The three outcome strings Succeeded, Failed and Skipped are named constants (:50, :53, :59), MaxErrorLength is 2048 and matches the LastError column width (:62), StartupDelay is 15 seconds so the host finishes module registration and migration before the first cycle touches the table (:69), and MinimumWait is one second, the floor that stops an overdue row hot-looping the runner (:72).
        • -
        • ExecuteAsync (:75-127), the loop. It returns immediately when Scheduler:Enabled is false, after one log line (:77-83), so a disabled scheduler is visible in the logs of a host that expected it without costing a line per cycle. Then the startup delay (:85-92), then forever: run a cycle, and swallow exceptions in two distinct ways. OperationCanceledException during shutdown breaks the loop (:101-105); any other exception is logged and the loop waits out the interval (:106-111), because one bad cycle (an unreachable database, a model mismatch) must not take the scheduler down for the life of the process.
        • -
        • The smart wait. ComputeWaitTime (:138-152) is a pure static function: the polling interval when nothing is registered, otherwise the time until the earliest upcoming occurrence, floored at MinimumWait and capped at the configured interval. This is the same "sleep until there is something to do" shape OutboxProcessor uses, and it is what keeps an idle scheduler from polling on a fixed tick.
        • -
        • RunCycleAsync (:205-228). One DI scope per cycle. It resolves the registered jobs (:208), returns early when there are none (:209-212), takes the context for the Default logical source on the configured engine through IDataSourceResolver (:214-216), reconciles registrations, runs due jobs, and finally reads back the earliest NextRunOn across the registered names (:221-227) which becomes the next wait.
        • -
        • ResolveRegisteredJobs (:235-250) groups GetServices<IScheduledJob>() by Name (ordinal) and keeps the first of each group, logging a collision rather than throwing (:243-246). Two jobs sharing a name would share one schedule row, and the design choice is that one mis-registered module must not stop every other job.
        • -
        • SyncRegistrationsAsync (:259-320) reconciles the table against the registered jobs. It reads the stored expressions once (:267-271), and for each job compares the resolved expression against the stored one: unchanged means leave the row alone (:279-285), with the comment naming the bug that would otherwise appear, recomputing NextRunOn every cycle would push every schedule forward forever and nothing would ever fire. A changed expression goes through the set-based UpdateScheduleAsync (:326-358) so a row another replica is currently executing is not disturbed by the change tracker; a new job is inserted (:293-311). An unparsable expression parks the row at DateTime.MaxValue and records Skipped instead of throwing (:287-308, and on the update path at :348-357).
        • -
        • ResolveCronExpression (:162-166) is the configuration override point: Scheduler:Jobs:{Name}:Cron when present and non-blank, otherwise the job's compiled-in default. TryGetNextOccurrence (:176-197) wraps Cronos and catches exactly CronFormatException and ArgumentException (:189), converting a malformed expression into a parked row plus an error message rather than a crashed runner.
        • -
        • RunDueJobsAsync (:365-393) claims and runs one row at a time, tracking an attempted set so each name is tried at most once per cycle (:370-382). A JobClaim with a null LockToken means another replica won that row, so it is skipped rather than retried (:384-391).
        • -
        • TryClaimNextDueAsync (:402-440) reads the earliest due, unleased row (:409-416), mints a Guid token and a lease of Scheduler:LeaseSeconds from now (:423-424), then issues the conditional claim update described above (:429-437).
        • -
        • ExecuteClaimedJobAsync (:454-512) records the lag (floored at zero so a clock adjustment cannot publish a negative duration, :464-467), invokes the job, records duration and the outcome-tagged counter (:469-474), then computes the next occurrence from the instant execution finished, not from the occurrence that just ran (:476-480). That is the missed-run policy: a host down for a day runs each job once on startup and returns to cadence instead of replaying a backlog. The final stamp is guarded by the claim token (:489-503): a replica whose lease expired mid-execution matches nothing, logs LogLeaseLost and drops its stale outcome (:505-509) rather than overwriting the current holder's record.
        • -
        • InvokeJobAsync (:519-551) resolves the job in a fresh DI scope (:523-525), exactly like a request, so a job body gets scoped services (a unit of work, repositories, handlers) and the long-lived runner never captures a scoped dependency. A missing job returns Skipped with a message (:527-531); a cancellation during host shutdown is rethrown so the row stays leased and the occurrence is retried when the lease expires rather than being recorded as a failure it never was (:539-545); any other exception is logged and recorded as Failed (:546-550), so the schedule still advances and a permanently failing job cannot hot-loop.
        • -
        • Log levels are a cost decision. The per-occurrence start and completion lines are Debug (:577-581) with the reason stated inline (a busy schedule would otherwise double this runner's steady-state log volume, [Rubric §31, Cost/FinOps], :574-576), while every failure line stays Error or Warning (:556-587).
        • +
        • State and constants. _settings snapshots IOptions<SchedulerSettings>.Value once (ScheduledJobRunner.cs:46), and _timeProvider falls back to TimeProvider.System (:47). The three outcome strings Succeeded, Failed and Skipped are named constants (:50, :53, :59), MaxErrorLength is 2048 and matches the LastError column width (:62), StartupDelay is 15 seconds so the host finishes module registration and migration before the first cycle touches the table (:69), and MinimumWait is one second, the floor that stops an overdue row hot-looping the runner (:72). StampTimeout is five seconds (:80), the budget for recording an outcome after a job has returned (see ExecuteClaimedJobAsync below).
        • +
        • ExecuteAsync (:83-135), the loop. It returns immediately when Scheduler:Enabled is false, after one log line (:85-91), so a disabled scheduler is visible in the logs of a host that expected it without costing a line per cycle. Then the startup delay (:93-100), then forever: run a cycle, and swallow exceptions in two distinct ways. OperationCanceledException during shutdown breaks the loop (:109-113); any other exception is logged and the loop waits out the interval (:114-119), because one bad cycle (an unreachable database, a model mismatch) must not take the scheduler down for the life of the process.
        • +
        • The smart wait. ComputeWaitTime (:146-160) is a pure static function: the polling interval when nothing is registered, otherwise the time until the earliest upcoming occurrence, floored at MinimumWait and capped at the configured interval. This is the same "sleep until there is something to do" shape OutboxProcessor uses, and it is what keeps an idle scheduler from polling on a fixed tick.
        • +
        • RunCycleAsync (:213-236). One DI scope per cycle. It resolves the registered jobs (:216), returns early when there are none (:217-220), takes the context for the Default logical source on the configured engine through IDataSourceResolver (:222-224), reconciles registrations, runs due jobs, and finally reads back the earliest NextRunOn across the registered names (:229-235) which becomes the next wait.
        • +
        • ResolveRegisteredJobs (:243-258) groups GetServices<IScheduledJob>() by Name (ordinal) and keeps the first of each group, logging a collision rather than throwing (:251-254). Two jobs sharing a name would share one schedule row, and the design choice is that one mis-registered module must not stop every other job.
        • +
        • SyncRegistrationsAsync (:267-328) reconciles the table against the registered jobs. It reads the stored expressions once (:275-279), and for each job compares the resolved expression against the stored one: unchanged means leave the row alone (:287-293), with the comment naming the bug that would otherwise appear, recomputing NextRunOn every cycle would push every schedule forward forever and nothing would ever fire. A changed expression goes through the set-based UpdateScheduleAsync (:334-366) so a row another replica is currently executing is not disturbed by the change tracker; a new job is inserted (:307-319). An unparsable expression parks the row at DateTime.MaxValue and records Skipped instead of throwing (:295-316, and on the update path at :356-365).
        • +
        • ResolveCronExpression (:170-174) is the configuration override point: Scheduler:Jobs:{Name}:Cron when present and non-blank, otherwise the job's compiled-in default. TryGetNextOccurrence (:184-205) wraps Cronos and catches exactly CronFormatException and ArgumentException (:197), converting a malformed expression into a parked row plus an error message rather than a crashed runner.
        • +
        • RunDueJobsAsync (:373-401) claims and runs one row at a time, tracking an attempted set so each name is tried at most once per cycle (:378-390). A JobClaim with a null LockToken means another replica won that row, so it is skipped rather than retried (:392-399).
        • +
        • TryClaimNextDueAsync (:410-448) reads the earliest due, unleased row (:417-424), mints a Guid token and a lease of Scheduler:LeaseSeconds from now (:431-432), then issues the conditional claim update described above (:437-445).
        • +
        • ExecuteClaimedJobAsync (:462-521) records the lag (floored at zero so a clock adjustment cannot publish a negative duration, :472-475), invokes the job, records duration and the outcome-tagged counter (:477-482), then computes the next occurrence from the instant execution finished, not from the occurrence that just ran (:484-488). That is the missed-run policy: a host down for a day runs each job once on startup and returns to cadence instead of replaying a backlog. The final stamp is guarded by the claim token (:497-512): a replica whose lease expired mid-execution matches nothing, logs LogLeaseLost and drops its stale outcome (:514-518) rather than overwriting the current holder's record. That stamp runs under its own CancellationTokenSource(StampTimeout, _timeProvider) (:500, passed at :511) rather than the host stopping token (the source calls this "the outbox M8 shape"): the doc on StampTimeout (:74-79) explains that a completed occurrence must always advance its schedule and release its lease, or a graceful stop landing between the job and the stamp would leave the row leased and re-run work that already finished once the lease expires. Building the source from the injected TimeProvider keeps that budget testable on a fake clock.
        • +
        • InvokeJobAsync (:528-560) resolves the job in a fresh DI scope (:532-534), exactly like a request, so a job body gets scoped services (a unit of work, repositories, handlers) and the long-lived runner never captures a scoped dependency. A missing job returns Skipped with a message (:536-540); a cancellation during host shutdown is rethrown so the row stays leased and the occurrence is retried when the lease expires rather than being recorded as a failure it never was (:548-554); any other exception is logged and recorded as Failed (:555-559), so the schedule still advances and a permanently failing job cannot hot-loop. The two shutdown paths are deliberately different: a job interrupted mid-run is retried, while a job that ran to completion gets its stamp under StampTimeout regardless of the stopping token.
        • +
        • Log levels are a cost decision. The per-occurrence start and completion lines are Debug (:586-590) with the reason stated inline (a busy schedule would otherwise double this runner's steady-state log volume, [Rubric §31, Cost/FinOps], :583-585), while every failure line stays Error or Warning (:562-596).
      • Why it's built this way: ADR-074 records the decision to extend the durable polling loop already in production instead of adopting Hangfire or Quartz.NET, on the grounds that the missing piece was a cron expression, not a product, and that every extracted service host (ADR-008) would otherwise have to reason about a new dependency. Cron parsing is the one thing bought rather than built (Cronos, MIT, zero-dependency).

        @@ -3245,8 +3120,8 @@

        EventUpcasterStartupValidator

      • StopAsync (EventUpcasterStartupValidator.cs:33) is a completed task. There is nothing to unwind.
      -
    1355. Why it's built this way: the registration is the other half of the design, and its comment is explicit (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:189-193): TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, EventUpcasterStartupValidator>()), not AddHostedService, because two modules calling AddInfrastructure must not run the same validation twice. TryAddEnumerable de-duplicates on the implementation type, which AddHostedService does not. The class doc adds the cost note (EventUpcasterStartupValidator.cs:13-17): a host with no upcasters resolves an empty registry, and the whole mechanism costs one no-op call at start (ADR-090).
    1356. -
    1357. Where it's used: registered once inside AddInfrastructure (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:193), so every host that calls it gets the check. Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Messaging/Consumers/EventUpcasterStartupValidatorTests.cs.
    1358. +
    1359. Why it's built this way: the registration is the other half of the design, and its comment is explicit (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:199-203): TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, EventUpcasterStartupValidator>()), not AddHostedService, because two modules calling AddInfrastructure must not run the same validation twice. TryAddEnumerable de-duplicates on the implementation type, which AddHostedService does not. The class doc adds the cost note (EventUpcasterStartupValidator.cs:13-17): a host with no upcasters resolves an empty registry, and the whole mechanism costs one no-op call at start (ADR-090).
    1360. +
    1361. Where it's used: registered once inside AddInfrastructure (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:203), so every host that calls it gets the check. Covered by MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Messaging/Consumers/EventUpcasterStartupValidatorTests.cs.

    1362. [Rubric §10, Messaging & Integration Architecture] applies: this type sits on the path a message takes once it leaves the process (outbox, bus, consumer, or broker plumbing), which is what section 10 scores.

      @@ -3264,28 +3139,30 @@

      BeginTwoFactorEnrollmentHan ICommandHandler<in TCommand, TResult> closed over Result<TwoFactorSetupResponse> (:38). TCommand is constrained to IUserScopedRequest (:39), the narrowest of this family's command constraints: this workflow reads only the caller's id - and carries no request payload. Also uses UserUseCaseLog. Externals: - Microsoft.Extensions.Logging.

      + and carries no request payload. Its one audit line is a private source-generated [LoggerMessage] + method, TwoFactorEnrollmentStarted (:106-107), which is why the class is declared partial + (:35). Externals: Microsoft.Extensions.Logging.

    1363. Concept introduced: a fresh secret on every call, never a reuse. An interrupted enrollment must not leave a secret live that somebody who saw the first QR code could still key an authenticator with, so HandleAsync calls twoFactorService.GenerateSecret() unconditionally rather than resuming a prior - attempt (:79, comment at :66-68).

      + attempt (:79, comment at :76-78).

      [Rubric §11: Security] assesses secret-material handling. The account is checked for an - already-enabled factor before a new secret is even generated (:51-57), and the response carries the + already-enabled factor before a new secret is even generated (:61-67), and the response carries the secret plus a provisioning URI built by the service, never anything the handler constructs itself - (:79-81).

      + (:89-91).

    1364. -
    1365. Walkthrough: one virtual member and one method.

      +
    1366. Walkthrough: one virtual member, one method and one abstract hook.

      • HandlerName (:45): protected virtual, defaulting to GetType().Name, the same source-stamping pattern used by every other handler base in this family.
      • HandleAsync(TCommand, CancellationToken) (:48-92): null-guards the command (:52); loads the - two-factor state (:54), returning Error.NotFound when absent (:57-58); returns a Conflict - when already enabled (:61-66); resolves the account label through the one abstract hook (:69), - failing NotFound when it cannot (:70-73); generates the secret (:79); starts the enrollment in - the store (:81), propagating its failure untouched (:82-84); logs TwoFactorEnrollmentStarted - (:87); and returns the secret and provisioning URI (:89-91).
      • + two-factor state (:54), returning Error.NotFound when absent (:55-59); returns a Conflict + (Authentication.TwoFactorAlreadyEnabled) when already enabled (:61-67); resolves the account + label through the one abstract hook (:69), failing NotFound when it comes back blank + (:70-74); generates the secret (:79); starts the enrollment in the store (:81), propagating its + failure untouched (:82-85); logs TwoFactorEnrollmentStarted (:87); and returns the secret and + provisioning URI (:89-91).
      • ResolveAccountNameAsync(UserIdentifierType, CancellationToken) (:102-104): protected abstract. The one app-specific step, because each app's User exposes its authenticator label (normally the email) differently.
      • @@ -3293,7 +3170,8 @@

        BeginTwoFactorEnrollmentHan
      • Why it's built this way: enrollment, confirmation, disablement and recovery-code regeneration are four small, independently-testable steps rather than one wide handler, so each app subclass only ever - forwards the app-specific lookups its use case actually needs.

        + forwards the app-specific lookups its use case actually needs. Each step owns its log message as a + private [LoggerMessage] method, so the message text lives beside the only code that emits it.

      • Where it's used: covered by MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Users/TwoFactorHandlerBaseTests.cs through a @@ -3317,11 +3195,12 @@

        ConfirmTwoFactorEnrollmen ICommandHandler<in TCommand, TResult> over Result<TwoFactorRecoveryCodesResponse> (:32). TCommand is an IUserScopedCommand<out TRequest> carrying a TwoFactorCodeRequest - (:33). Also uses TwoFactorErrors and UserUseCaseLog.

        + (:33). Also uses TwoFactorErrors, and logs through a private source-generated [LoggerMessage] + method, TwoFactorEnabled (:75-76), hence the partial declaration (:29).

      • Concept introduced: the pending secret has to still be there to confirm against. The handler reads the state the Begin step wrote and fails with TwoFactorErrors.TwoFactorEnrollmentMissing when there - is no secret, treating an empty or zero-length secret the same as a missing one (:49-52, + is no secret, treating an empty or zero-length secret the same as a missing one (:49-53, TwoFactorSecret is not { Length: > 0 } secret), so a confirm call that races an unstarted or already-completed enrollment cannot verify against a blank value.

        [Rubric §1: SOLID] assesses open/closed. Verification (VerifyCode) and persistence @@ -3332,9 +3211,10 @@

        ConfirmTwoFactorEnrollmen
        • HandlerName (:39).
        • HandleAsync(TCommand, CancellationToken) (:42-73): null-guard (:46); load state and fail on a - missing/empty secret (:48-53); verify the submitted code (:55-58); generate recovery codes - (:60); complete the enrollment with their hashes (:62-68); log TwoFactorEnabled (:70); return - the plaintext codes to the caller once, in the response (:72).
        • + missing/empty secret (:48-53); verify the submitted code, failing with + TwoFactorErrors.TwoFactorInvalid (:55-58); generate recovery codes (:60); complete the + enrollment with their hashes (:62-68); log TwoFactorEnabled (:70); return the plaintext codes + to the caller once, in the response (:72).

      • Why it's built this way: recovery codes are only ever returned at this one moment, on the response @@ -3364,13 +3244,14 @@

        DisableTwoFactorHandlerBase<TCom ICommandHandler<in TCommand, TResult> over Result (:32). TCommand is an IUserScopedCommand<out TRequest> carrying a TwoFactorCodeRequest - (:33). Also uses TwoFactorErrors and UserUseCaseLog.

        + (:33). Also uses TwoFactorErrors, and logs through a private source-generated [LoggerMessage] + method, TwoFactorDisabled (:73-74), hence the partial declaration (:29).

      • Concept introduced: a distinguishable not-enrolled outcome. ChallengeAsync returns a TwoFactorOutcome, and the handler treats NotEnrolled as a distinct failure - (Auth.TwoFactorNotEnrolled, comment at :57-60) rather than an idempotent success: the caller just - presented a code, and the code was never actually checked against anything, so the response says so - rather than silently agreeing.

        + (TwoFactorErrors.TwoFactorNotEnrolled, :57-60, comment at :54-56) rather than an idempotent + success: the caller just presented a code, and the code was never actually checked against anything, + so the response says so rather than silently agreeing.

        [Rubric §11: Security] assesses whether the disable path itself is gated. Disabling two-factor cannot happen without a live code challenge, so an attacker who only has the session cookie (and not the authenticator) cannot turn protection off.

        @@ -3401,26 +3282,27 @@

        RegenerateRecoveryCodesHandl

      • What it is: the shared regenerate-recovery-codes workflow: challenge the submitted code, then - replace the account's whole recovery-code set (RegenerateRecoveryCodesHandlerBase.cs:30, :43-77).

        + replace the account's whole recovery-code set (RegenerateRecoveryCodesHandlerBase.cs:30, :44-77).

      • Depends on: ITwoFactorAuthenticator, ITwoFactorService, ITwoFactorStore and an ILogger (:30-34), the widest primary constructor of the four TwoFactor bases; implements ICommandHandler<in TCommand, TResult> over Result<TwoFactorRecoveryCodesResponse> (:34). TCommand is an IUserScopedCommand<out TRequest> carrying a TwoFactorCodeRequest - (:35).

        + (:35). Logs through a private source-generated [LoggerMessage] method, + TwoFactorRecoveryCodesRegenerated (:79-80), hence the partial declaration (:30).

      • Concept introduced: regeneration replaces, it does not append. ReplaceRecoveryCodesAsync takes the freshly generated hash set and is expected to overwrite whatever codes existed before (:66-68), so a code from a batch the caller regenerated away is no longer valid, closing off an old code that may have leaked.

        [Rubric §11: Security] assesses whether the regenerate path is itself gated the same way disable is: - a live code challenge, with the same NotEnrolled distinct failure, has to succeed first (:50-58).

        + a live code challenge, with the same NotEnrolled distinct failure, has to succeed first (:50-62).

      • Walkthrough: one virtual member and one method.

        • HandlerName (:41).
        • -
        • HandleAsync(TCommand, CancellationToken) (:43-77): null-guard (:48); challenge (:50-56); the +
        • HandleAsync(TCommand, CancellationToken) (:44-77): null-guard (:48); challenge (:50-56); the NotEnrolled outcome fails distinctly (:58-62); generate a new code batch (:64); replace them in the store (:66-68), propagating a failure untouched (:69-72); log TwoFactorRecoveryCodesRegenerated (:74); return the new plaintext codes (:76).
        • @@ -3450,8 +3332,8 @@

          ConsumerOriginRestore

        • Depends on: AmbientOrigin for the actual restore, MassTransit's Headers and IServiceProvider, MessageHeaders for the four header names, and the UserIdentifierType alias with its TryParse.
        • Concept introduced, parsing rather than trusting a typed header. [Rubric §10, Messaging & Integration Architecture] assesses whether the pipeline handles transport differences correctly rather than assuming one broker's behavior. The user id is transported as text and parsed back rather than read as a typed header, because a broker is free to widen an integer header; Azure Service Bus hands one back as a long even when it was published as an int. Parsing the canonical string form is the one reading that behaves the same on every transport. A malformed value is treated as a publisher bug, not a reason to fail the consume, so it simply yields no identity rather than throwing.
        • Walkthrough
            -
          • Apply(headers, services, authenticationType) (ConsumerOriginRestore.cs:24-57) parses MessageHeaders.UserId with UserIdentifierType.TryParse under the invariant culture, defaulting to null on failure (:684-690).
          • -
          • It then calls AmbientOrigin.Restore with that id and the remaining three headers, UserRoles, TenantId, and CorrelationId, read straight through as strings (ConsumerOriginRestore.cs:692-698).
          • +
          • Apply(headers, services, authenticationType) (ConsumerOriginRestore.cs:24-57) parses MessageHeaders.UserId with UserIdentifierType.TryParse under the invariant culture, defaulting to null on failure (:41-47).
          • +
          • It then calls AmbientOrigin.Restore with that id and the remaining three headers, UserRoles, TenantId, and CorrelationId, read straight through as strings (ConsumerOriginRestore.cs:49-55).
        • Why it's built this way: keeping the header-reading and the id-parsing in this one adapter, separate from AmbientOrigin itself, means the restore logic stays transport-agnostic while this class owns the one transport-specific decision, how a MassTransit header is read back as a string.
        • @@ -3461,33 +3343,37 @@

          ConsumerOriginRestore


          ChangePasswordHandlerBase<TUser, TCommand>

          -

          MMCA.Common.Application · MMCA.Common.Application.Users.UseCases.ChangePassword · MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:34 · Level 8 · class (abstract)

          +

          MMCA.Common.Application · MMCA.Common.Application.Users.UseCases.ChangePassword · MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:42 · Level 8 · class (abstract)

          • What it is: the shared password-rotation workflow for an authenticated user. Load the account, - reject a credential-less account, verify the current password against the stored hash, hash the new - one, let the aggregate apply its own invariants, and persist only if the aggregate accepted the change - (ChangePasswordHandlerBase.cs:34, :56-101).

            + reject a credential-less account, check the per-account change-password lockout, verify the current + password against the stored hash (counting a miss against the lockout), hash the new one, let the + aggregate apply its own invariants, and persist only if the aggregate accepted the change + (ChangePasswordHandlerBase.cs:42, :65-120).

          • Depends on: IUnitOfWork, IPasswordHasher and an ILogger as primary-constructor parameters, plus a required - IRefreshSessionStore and an optional TimeProvider - (:37-41); it implements + IRefreshSessionStore, a required + ILoginProtectionService and an optional TimeProvider + (:42-48); it implements ICommandHandler<in TCommand, TResult> - closed over Result (:41). Its two constraints are + closed over Result (:48). Its two constraints are the interesting part: TUser must be an AuditableAggregateRootEntity<TIdentifierType> keyed by UserIdentifierType and implement - IPasswordChangeableUser (:28), and TCommand must + IPasswordChangeableUser (:49), and TCommand must be an IUserScopedCommand<out TRequest> carrying a - ChangePasswordRequest (:29). It also uses - Error and the shared - UserUseCaseLog. Externals: Microsoft.Extensions.Logging (:1).

            + ChangePasswordRequest (:50). It also uses + Error, and its audit line is a private + source-generated [LoggerMessage] method, PasswordChanged (:131-132), which is why the class is + declared partial (:42). Externals: System.Globalization and Microsoft.Extensions.Logging + (:1-2).

          • Concept introduced: the generic template-method handler, and the two axes it is generic over. The two app Identity modules carried line-identical copies of this handler, differing only in log - text (ChangePasswordHandlerBase.cs:13-17). Hoisting them needed two variation points, and each is + text (ChangePasswordHandlerBase.cs:14-18). Hoisting them needed two variation points, and each is a separate generic parameter for a separate reason. TUser varies because each app owns its own User aggregate and the framework must never reference either; the capability it needs is named by an interface constraint instead, so the base can call ChangePassword without knowing the type @@ -3503,59 +3389,81 @@

            ChangePasswordHandlerBase<TU ICommandWithRequest<out TRequest>: that marker also opts the command into automatic CommandRequestValidator<TCommand, TRequest> - registration, which is a per-app decision (IUserScopedCommand.cs:6-11).

            + registration, which is a per-app decision (IUserScopedCommand.cs:6-11), and the class remarks + record the same split (ChangePasswordHandlerBase.cs:19-24).

            [Rubric §1: SOLID] assesses open/closed and dependency inversion. The workflow is closed for modification and open for extension along exactly two declared axes plus the HandlerName hook, and - every collaborator is an abstraction: unit of work, hasher, logger, and the aggregate's own - capability interface.

            + every collaborator is an abstraction: unit of work, hasher, lockout service, refresh-session store, + logger, and the aggregate's own capability interface.

            Concept introduced: a credential-less account has no current password to prove, so it has no change-password path either. An external-OAuth account carries an empty PasswordHash and PasswordSalt (ADR-036), and the - handler now rejects that case explicitly, before the verify, rather than letting a zero-length hash - compare against anything (:69-77).

            -

            [Rubric §11: Security] assesses credential handling. Three properties are visible in the code. - The current password is verified before anything is written (:79), the failure is an + handler rejects that case explicitly, before the lockout check and the verify, rather than letting a + zero-length hash compare against anything (:78-86).

            +

            Concept introduced: a wrong current password counts against a lockout, just as a failed sign-in + does. Without it, anyone holding a live session could use this endpoint as an unthrottled + password oracle. The handler therefore runs the current-password check through the same + failed-attempt counter and exponential lockout the sign-in path uses + (ADR-029): + CheckLockoutAsync before the verify, returning its failure as-is when the key is locked + (:88-93); IncrementFailedAttemptsAsync on a mismatch (:97); and ResetFailedAttemptsAsync once + the password verifies (:102). The counter key is password-change:{userId}, built by the private + ProtectionKey helper with the id rendered under CultureInfo.InvariantCulture (:128-129). The + constructor documentation gives the two reasons it is not keyed by email: IAuthUser exposes no + address, and a separate key keeps a change-password lockout from locking the owner out of sign-in + (:34-40, :122-127).

            +

            [Rubric §11: Security] assesses credential handling. Four properties are visible in the code. + Repeated guesses are throttled by the lockout above before the hasher is even called (:88-93). The + current password is verified before anything is written (:95), and the failure is an Unauthorized error with a stable code rather than a message that distinguishes "no such user" from - "wrong password" at this layer (:73-77, :81-83), and nothing in the handler ever logs the plaintext, - the hash or the salt: the success log carries only the user id (UserUseCaseLog.cs:13-14). Hashing + "wrong password" at this layer (:82-86, :95-100). Nothing in the handler ever logs the + plaintext, the hash or the salt: the success log carries only the user id (:131-132). Hashing itself is delegated to IPasswordHasher, whose contract returns a hash and a fresh salt as a tuple (IPasswordHasher.cs:11), the shape ADR-032 fixes. A successful change also revokes every live refresh session on the account through the required IRefreshSessionStore, via the shared - RefreshSessionRevocation helper (:91-95), so a stolen - refresh chain cannot outlive the rotation that was meant to remediate it + RefreshSessionRevocation helper (:112-114, comment + at :110-111), so a stolen refresh chain cannot outlive the rotation that was meant to remediate it (ADR-097). Only the - timeProvider parameter still defaults to null; the base caches it in a private - _timeProvider field, falling back to TimeProvider.System (:43), and a caller must supply the - refresh-session store explicitly.

            + timeProvider parameter defaults to null; the base caches it in a private _timeProvider field, + falling back to TimeProvider.System (:52), and a caller must supply the refresh-session store and + the lockout service explicitly.

            [Rubric §4: DDD] assesses whether business rules live in the domain. The handler never mutates - the user's fields: it calls user.ChangePassword(newHash, newSalt) (:86) and returns whatever + the user's fields: it calls user.ChangePassword(newHash, newSalt) (:105) and returns whatever Result the aggregate produced, so an aggregate invariant (for example refusing rotation on a deleted account) short-circuits the save without the handler knowing the rule exists.

          • -
          • Walkthrough: two protected members and one method.

            +
          • Walkthrough: two protected members, the handler method, and two private helpers.

              -
            • Primary constructor (:36-41): unitOfWork, passwordHasher, logger, the required - refreshSessions, and the optional timeProvider. The logger is typed as the non-generic ILogger - so a subclass can pass its own ILogger<TAppHandler> and keep the log category app-specific while - the message text stays shared (UserUseCaseLog.cs:5-10).
            • -
            • UnitOfWork (:46): a protected pass-through over the captured parameter, exposed so an app +
            • Primary constructor (:42-48): unitOfWork, passwordHasher, logger, the required + refreshSessions and loginProtection, and the optional timeProvider. The logger is typed as + the non-generic ILogger so a subclass can pass its own ILogger<TAppHandler> and keep the log + category app-specific while the message text stays in the base's own [LoggerMessage] + declaration (ADC passes ILogger<ChangePasswordHandler> at + MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ChangePassword/ChangePasswordHandler.cs:27).
            • +
            • UnitOfWork (:55): a protected pass-through over the captured parameter, exposed so an app subclass can enlist further aggregates in the same unit of work.
            • -
            • HandlerName (:53): protected virtual, defaulting to GetType().Name. This is the detail +
            • HandlerName (:62): protected virtual, defaulting to GetType().Name. This is the detail that made the hoist behavior-preserving: because each app keeps a subclass literally named ChangePasswordHandler, the source field on every returned error is byte-identical to what it - was before the workflow moved (:48-52).
            • -
            • HandleAsync(TCommand, CancellationToken) (:56-101): null-guards the command (:60); takes the - write repository via GetRepository (:62) because this path saves; loads by - command.UserId (:63) and returns Error.NotFound stamped with the handler name and the - aggregate type name when the account is missing (:66); rejects a credential-less account before - the verify (:69-77); verifies the current password and returns - Error.Unauthorized("Auth.InvalidCurrentPassword", ...) on mismatch (:79-83); hashes the new - password into a (newHash, newSalt) tuple (:85); calls the aggregate (:86); and only on - success saves, revokes refresh sessions and logs (:87-97). The aggregate's result is returned - either way (:100), so a domain failure propagates unchanged.
            • + was before the workflow moved (:57-61). +
            • HandleAsync(TCommand, CancellationToken) (:65-120): null-guards the command (:69); takes the + write repository via GetRepository (:71) because this path saves; loads by + command.UserId (:72) and returns Error.NotFound stamped with the handler name and the + aggregate type name when the account is missing (:75); rejects a credential-less account + (:82-86); builds the counter key and checks the lockout (:88-93); verifies the current password, + incrementing the counter and returning + Error.Unauthorized("Auth.InvalidCurrentPassword", ...) on mismatch (:95-100); resets the + counter (:102); hashes the new password into a (newHash, newSalt) tuple (:104); calls the + aggregate (:105); and only on success saves (:108), revokes refresh sessions (:112-114) and + logs PasswordChanged (:116). The aggregate's result is returned either way (:119), so a domain + failure propagates unchanged.
            • +
            • ProtectionKey(UserIdentifierType) (:128-129): private static, the password-change: counter + key described above.
            • +
            • PasswordChanged(ILogger, UserIdentifierType) (:131-132): the source-generated Information-level + audit line, "User {UserId} password changed".
          • Why it's built this way: the two apps had drifted into identical code, and identical code in two @@ -3563,24 +3471,28 @@

            ChangePasswordHandlerBase<TU generic parameters and one virtual hook, is the standing preference for reusable infrastructure in this workspace. Keeping the command record app-side is not a compromise but the correct boundary: the record is where CQRS pipeline policy is declared, and that policy is genuinely per app - (ChangePasswordHandlerBase.cs:18-23).

            + (ChangePasswordHandlerBase.cs:19-24).

          • Where it's used: subclassed once per app, each subclass empty apart from the constructor - forwarding + forwarding, and both forward the lockout service (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ChangePassword/ChangePasswordHandler.cs:24, - :21; - MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Application/Users/UseCases/ChangePassword/ChangePasswordHandler.cs:16, - :20). Both subclasses are picked up as scoped command handlers by + :30; + MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Application/Users/UseCases/ChangePassword/ChangePasswordHandler.cs:28, + :34). Both subclasses are picked up as scoped command handlers by ScanModuleApplicationServices<TAssemblyMarker>() (see DependencyInjection) and are then wrapped by the decorator pipeline. The workflow is pinned directly by MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Users/ChangePasswordHandlerBaseTests.cs:18, - which drives it through a test double subclass (:122-123).

            + which drives it through a test double subclass that also takes an ILoginProtectionService + (:254-260).

          • Caveats: new-password strength is not checked here. Both apps' commands additionally implement ICommandWithRequest<ChangePasswordRequest> (ChangePasswordCommand.cs:15 in ADC, :13 in Store), which routes the payload through the Validating decorator before the handler runs, so - the base can assume a syntactically valid request. Neither app's command implements ITransactional, - so the single SaveChangesAsync at :89 is the whole atomic unit.

            + the base can assume a syntactically valid request. The lockout counter is reset as soon as the + current password verifies (:102), before the aggregate is asked to accept the new one (:105), so + an aggregate rejection after a correct password still clears the count. Neither app's command + implements ITransactional, so the single SaveChangesAsync at :108 is the whole atomic unit for + the aggregate write; the lockout counter is written through ILoginProtectionService, outside it.


          @@ -3601,7 +3513,8 @@

          ChangePreferencesHandlerBase implementing IUserPreferences (:26), and TCommand is an IUserScopedCommand<out TRequest> carrying a ChangePreferencesRequest (:27). Also uses - Error and UserUseCaseLog. + Error, and logs through a private source-generated + [LoggerMessage] method, PreferencesChanged (:65-66), hence the partial declaration (:23). Externals: Microsoft.Extensions.Logging (:1).

        • Concept introduced: null means "leave alone", and where that rule is enforced. The request record @@ -3622,19 +3535,18 @@

          ChangePreferencesHandlerBase otherwise produce (ADR-027 culture, ADR-028 theme).

        • -
        • Walkthrough: same shape as the password base, one collaborator shorter.

          +
        • Walkthrough: same shape as the password base, with fewer collaborators.

            -
          • Primary constructor (:23-25): unitOfWork and logger; no hasher, since nothing here is - credential material.
          • +
          • Primary constructor (:23-25): unitOfWork and logger; no hasher, lockout or session store, + since nothing here is credential material.
          • UnitOfWork (:30) and HandlerName (:37): the same two protected members, with the same rationale (an app subclass named ChangePreferencesHandler keeps the pre-hoist error source, :32-36).
          • HandleAsync(TCommand, CancellationToken) (:40-63): null-guard (:44); write repository via GetRepository (:46); load by command.UserId (:47); Error.NotFound stamped with handler and aggregate names when missing (:50); the merged call to user.UpdatePreferences(...) - (:53-55); and, only when the aggregate succeeded, SaveChangesAsync plus - UserUseCaseLog.PreferencesChanged (:56-60). The aggregate's result is returned unchanged - (:62).
          • + (:53-55); and, only when the aggregate succeeded, SaveChangesAsync plus the + PreferencesChanged log (:56-60). The aggregate's result is returned unchanged (:62).
        • Why it's built this way: identical to the rationale for @@ -3679,20 +3591,23 @@

          ConfirmEmailHandlerBase<TUser, AuditableAggregateRootEntity<TIdentifierType> implementing IEmailConfirmableUser (:37), and TCommand is an ICommandWithRequest<out TRequest> - carrying a ConfirmEmailRequest (:38). Also uses EmailConfirmationErrors and - UserUseCaseLog.

          + carrying a ConfirmEmailRequest (:38). Also uses EmailConfirmationErrors, and logs through two + private source-generated [LoggerMessage] methods, EmailConfirmed (:95-96) and + EmailConfirmationRejected (:100-101), hence the partial declaration (:33).

        • Concept introduced: already-confirmed is a success that writes nothing. A confirmation link opened twice, whether by a mail-client link prefetch or a user's double tap, is the most ordinary duplicate this feature sees, so the handler checks user.IsEmailConfirmed after resolving the account and - returns success with no save when it is already true (:61-68, comment at :61-63).

          + returns success with no save when it is already true (:77-81, comment at :74-76).

          [Rubric §11: Security] assesses whether a rejected token leaks account state. Both rejection paths, an invalid or expired token and an account that no longer resolves, return the identical EmailConfirmationErrors.InvalidToken(HandlerName), differing only in a log reason string - (:59-60, :69-71), so the response cannot be used to distinguish "bad token" from "account gone" the - way ResetPasswordHandlerBase<TUser, TCommand> closes the same gap for password resets.

          + (:59-63, :68-72), so the response cannot be used to distinguish "bad token" from "account gone" the + way ResetPasswordHandlerBase<TUser, TCommand> closes the + same gap for password resets. The rejection log itself carries the reason and nothing else, no + address and no account id, as the comment on its declaration records (:98-101).

        • -
        • Walkthrough: one protected member and one method.

          +
        • Walkthrough: two protected members and one method.

          • UnitOfWork (:41): exposed for app-level extensions, same shape as the other Users bases.
          • HandlerName (:47): protected virtual, defaulting to GetType().Name.
          • @@ -3722,28 +3637,30 @@

            ConfirmEmailHandlerBase<TUser,

            DeleteUserHandlerBase<TUser, TCommand>

            -

            MMCA.Common.Application · MMCA.Common.Application.Users.UseCases.DeleteUser · MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:58 · Level 8 · class (abstract)

            +

            MMCA.Common.Application · MMCA.Common.Application.Users.UseCases.DeleteUser · MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:62 · Level 8 · class (abstract)

            • What it is: the shared account-erasure workflow: authorize owner-or-privileged-role, soft-delete the account, run the app's tail hook, irreversibly anonymize the personal data in place, save, write the shared soft-deleted marker that revokes the account's already-issued access tokens, then drain a - post-commit queue (DeleteUserHandlerBase.cs:58, :76-159). It is the most extensible of - the seven Users bases: one abstract member and one virtual hook.

              + post-save queue (DeleteUserHandlerBase.cs:62, :80-163). It exposes one abstract member and one + virtual hook.

            • Depends on: IUnitOfWork, an ICacheService and an ILogger - (:58-61); implements + (:62-65); implements ICommandHandler<in TCommand, TResult> - over Result (:61). Constraints: TUser is an + over Result (:65). Constraints: TUser is an AuditableAggregateRootEntity<TIdentifierType> - implementing IErasableUser (:62), and TCommand is an - IUserOwnedRequest (:63), not an + implementing IErasableUser (:66), and TCommand is an + IUserOwnedRequest (:67), not an IUserScopedCommand<out TRequest>: this workflow needs the caller as well as the target, and carries no request payload. It also uses - UserOwnershipRule, Error, - SoftDeletedUserCache and - UserUseCaseLog. Externals: Microsoft.Extensions.Logging (:1).

              + UserOwnershipRule, Error and + SoftDeletedUserCache, and logs through two private + source-generated [LoggerMessage] methods, UserErased at Information (:196-197) and + SoftDeletedMarkerFailed at Warning (:199-200), hence the partial declaration (:62). Externals: + Microsoft.Extensions.Logging (:1).

            • Concept introduced: anonymize-in-place erasure, and why the row survives. Soft-delete (IsDeleted = true) hides a row but keeps its personal data, so it does not by itself satisfy a @@ -3755,113 +3672,124 @@

              DeleteUserHandlerBase<TUser, TCo (MMCA.Common/Source/Core/MMCA.Common.Domain/Interfaces/IAnonymizable.cs:24-30). This handler is where that policy becomes a sequence (ADR-005).

              -

              Concept introduced: interface dispatch as a correctness device. The comment at :109-113 is one +

              Concept introduced: interface dispatch as a correctness device. The comment at :113-117 is one of the most instructive in the framework. An app's User may hide the base entity's Delete() - with public new Result Delete() to add account-specific behavior such as revoking the refresh - token. A hidden method is not an override, and member lookup on a generic type parameter prefers the - members of its class constraint, so a bare user.Delete() inside this base would bind to + with public new Result Delete() to add account-specific behavior. A hidden method is not an + override, and member lookup on a generic type parameter prefers the members of its class + constraint, so a bare user.Delete() inside this base would bind to AuditableBaseEntity<TIdentifierType>.Delete() and silently skip the app's version. The workflow therefore assigns the user to an - IErasableUser local first and calls through the interface (:114-115), because the interface map + IErasableUser local first and calls through the interface (:118-119), because the interface map resolves to the most derived Delete() the app type declares. The interface's own remarks record the same reasoning from the contract side (IErasableUser.cs:13-23), including the deliberate choice not to implement it on the base entity so a forgetful consumer fails the generic constraint at compile time rather than losing behavior at run time (IErasableUser.cs:26-27).

              -

              Concept introduced: the post-commit queue. OnAfterSoftDeleteAsync receives an - ICollection<Func<CancellationToken, Task>> afterCommit (:187). Work that must not happen unless - the erasure actually commits (deleting a blob, notifying another system) is enqueued rather than - run inline, and the base drains the queue in order after SaveChangesAsync (:151-154). The subtle +

              Concept introduced: the post-save queue. OnAfterSoftDeleteAsync receives an + ICollection<Func<CancellationToken, Task>> afterCommit (:192). Work that must not happen unless + the erasure is actually saved (deleting a blob, notifying another system) is enqueued rather than + run inline, and the base drains the queue in order after SaveChangesAsync (:155-158). The subtle benefit named in the docs is that the override can hand values it captured before anonymization to a - post-commit closure without parking them in mutable handler state (:41-47), which matters because - handlers are scoped and a field would be a shared mutable across the whole request.

              + post-save closure without parking them in mutable handler state (:49-51), which matters because + handlers are scoped and a field would be a shared mutable across the whole request. The class summary + is precise about what "after" means: under an ITransactional command the save is not the commit, + so the marker and every afterCommit action run before the transaction commits, and only + idempotent, retry-safe work belongs there; anything that must survive with the erasure should be + scheduled as an internal command inside the unit instead (:18-21, repeated on the parameter at + :179-186).

              Concept introduced: token revocation as part of the shared workflow, best effort and first. - Between the save and the app's tail the base writes the shared soft-deleted marker through - SoftDeletedUserCache.MarkDeletedAsync (:140-149), which + Between the save and the queued actions the base writes the shared soft-deleted marker through + SoftDeletedUserCache.MarkDeletedAsync (:144-153), which is what stops an erased account's already-issued access token from working before the next database lookup would notice (ADR-047). - Two decisions are documented on the class and are worth reading as a pair (:19-35). It is - best effort: the erasure is already committed, so a cache fault must not turn a successful, + Two decisions are documented on the class and are worth reading as a pair (:24-39). It is + best effort: the erasure is already saved, so a cache fault must not turn a successful, irreversible deletion into a failure the caller would retry, and the write is wrapped in a - try/catch that logs a warning through UserUseCaseLog.SoftDeletedMarkerFailed and continues - (:146-149). And it runs before the app's post-commit tail, because that tail is unbounded app + try/catch (excluding OperationCanceledException) that logs SoftDeletedMarkerFailed and + continues (:150-153). And it runs before the queued actions, because that tail is unbounded app work (deleting a blob, calling storage) and every second it takes is a second the deleted account's token still works. Owning the marker here rather than in each app's tail is what gives both apps the - identical revocation window; the hook's own docs tell an override not to re-stamp it (:175-181).

              + identical revocation window; the hook's own docs tell an override not to re-stamp it (:185-186).

              [Rubric §30: Compliance, Privacy & Data Governance] assesses whether a data-subject erasure request is actually satisfiable. The sequence here is the mechanism behind both apps' published - erasure promise: soft-delete, then irreversible anonymization, in one transaction (:12-18).

              + erasure promise: soft-delete, then irreversible anonymization, persisted in one save (:12-21).

              [Rubric §11: Security] assesses authorization placement. The very first thing the method does, - before it touches the repository, is the ownership check (:83-92), so an unauthorized caller + before it touches the repository, is the ownership check (:86-96), so an unauthorized caller cannot even confirm that an account id exists. The privileged-role test is passed in already evaluated because each app owns its own role vocabulary (UserOwnershipRule.cs:15-19).

              [Rubric §1: SOLID] assesses the template-method shape. The invariant order (authorize, load, - delete, tail, anonymize, save, revoke, post-commit) is fixed by the base; only the two hooks vary.

              + delete, tail, anonymize, save, revoke, queued actions) is fixed by the base; only the two hooks vary.

            • -
            • Walkthrough: two protected members, the handler method, and two hooks.

              +
            • Walkthrough: two protected members, the handler method, two hooks and two log methods.

                -
              • Primary constructor (:58-61): unitOfWork, cacheService, logger. UnitOfWork (:66) is +
              • Primary constructor (:62-65): unitOfWork, cacheService, logger. UnitOfWork (:70) is protected specifically so a tail hook can reach further aggregates; the cache is not exposed, because the only write the base makes through it is the marker.
              • -
              • HandlerName (:73): the same GetType().Name default that preserves the pre-hoist error - source.
              • -
              • HandleAsync(TCommand, CancellationToken) (:76-159):
                  -
                • Authorization first (:83-88) through +
                • HandlerName (:77): the same GetType().Name default that preserves the pre-hoist error + source (:72-76).
                • +
                • HandleAsync(TCommand, CancellationToken) (:80-163):
                    +
                  • Authorization first (:86-92) through UserOwnershipRule.CheckOwnership (UserOwnershipRule.cs:38), with the code "User.DeleteForbidden" and a message the caller sees; a non-null return is the failure - (:89-92).
                  • -
                  • Load through the write repository (:94-95); Error.NotFound when absent (:98).
                  • -
                  • IErasableUser erasable = user; erasable.Delete() (:114-115) with the dispatch rationale - above; a failure returns immediately (:116-119). The comment there also records why + (:93-96).
                  • +
                  • Load through the write repository (:98-99); Error.NotFound when absent (:102).
                  • +
                  • IErasableUser erasable = user; erasable.Delete() (:118-119) with the dispatch rationale + above; a failure returns immediately (:120-123). The comment there also records why outstanding refresh sessions are not revoked at this point: the refresh flow re-fetches the user through the same soft-delete query filter, so an erased account's sessions stop working the - moment this commits (:104-108).
                  • -
                  • Allocate the afterCommit list and call OnAfterSoftDeleteAsync (:121-122); a failed tail - aborts before anything is persisted (:123-126).
                  • -
                  • erasable.Anonymize() (:129), also short-circuiting on failure (:130-133).
                  • -
                  • SaveChangesAsync (:135), then the best-effort marker write (:140-149), then the - post-commit drain in order (:151-154), then the UserUseCaseLog.UserErased log (:156) and - Result.Success() (:158).
                  • + moment this commits, and an app that also wants the rows tidied revokes them from its tail via + IRefreshSessionStore in the same unit of work (:107-111). +
                  • Allocate the afterCommit list and call OnAfterSoftDeleteAsync (:125-126); a failed tail + aborts before anything is persisted (:127-130).
                  • +
                  • erasable.Anonymize() (:133), also short-circuiting on failure (:134-137).
                  • +
                  • SaveChangesAsync (:139), then the best-effort marker write (:144-153), then the queued + actions drained in order (:155-158), then the UserErased log (:160) and + Result.Success() (:162).
                • -
                • HasDeletePrivilege(string? currentUserRole) (:167): protected abstract. Deliberately +
                • HasDeletePrivilege(string? currentUserRole) (:171): protected abstract. Deliberately abstract rather than virtual-defaulting-to-false, so adopting the base forces an explicit answer - about which role bypasses ownership.
                • -
                • OnAfterSoftDeleteAsync(user, command, afterCommit, cancellationToken) (:184-189): protected virtual, defaulting to Task.FromResult(Result.Success()). Its position is load-bearing and - documented: it runs after Delete() and before Anonymize() (:50-54), which is the only - point where an override can both read personal data that anonymization is about to erase and - enlist further aggregates in the same unit of work. Running it before Delete() would let a - cascaded aggregate's error mask the account's own AlreadyDeleted error.
                • + about which role bypasses ownership; its XML doc names UserRole.IsAdmin(...) only as an example + of "whatever the app's privileged role is" (:165-170). +
                • OnAfterSoftDeleteAsync(user, command, afterCommit, cancellationToken) (:189-194): protected virtual, defaulting to Task.FromResult(Result.Success()); returning a failure aborts the erasure + before anything is persisted (:173-175). Its position is load-bearing and documented: it runs + after Delete() and before Anonymize() (:45-48), which is the only point where an + override can both read personal data that anonymization is about to erase and enlist further + aggregates in the same unit of work. Running it after Delete() rather than before means an + already-deleted account fails with its own AlreadyDeleted error rather than one raised by a + cascaded aggregate (:54-58).
              • Why it's built this way: the hook contract was derived from what the two apps actually needed, and both uses are visible in their overrides. ADC's override (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/DeleteUser/DeleteUserHandler.cs:43-74) captures the avatar blob name before anonymization clears the URL (DeleteUserHandler.cs:51), - raises the cross-service UserDeleted domain event on the aggregate so its outbox row is written by - the very save that commits the erasure (:58), and queues only the blob deletion as a post-commit - action, with a comment recording that the base already wrote the marker ahead of this tail - (:60-67). + raises the cross-service UserDeleted event on the aggregate so its outbox row is written by the + very save that commits the erasure (:59), and schedules the avatar blob deletion as a durable + internal command through + IInternalCommandScheduler inside the erasure transaction rather than + queueing it on afterCommit, so an erasure that persists can never lose the instruction to delete the + photo (:61-71, :76-86); a scheduling failure fails the erasure (:82-85). Store instead cascades in the same unit of work, erasing the linked Customer that holds its name/email/address PII and returning the Customer's own failure untouched so nothing is persisted (MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Application/Users/UseCases/DeleteUser/DeleteUserHandler.cs:41-65). - One hook covers both because it can do work inline and schedule work for after the commit.

                + One hook covers both because it can do work inline and schedule work for after the save.

              • Where it's used: subclassed once per app, each supplying its own privileged role to - HasDeletePrivilege, whose XML doc now names UserRole.IsAdmin(...) generically rather than citing - ADC's and Store's roles by name (:158-159) - (MMCA.ADC/.../DeleteUser/DeleteUserHandler.cs:28, :34, with HasDeletePrivilege returning - UserRole.IsOrganizer(...) at :38-39; MMCA.Store/.../DeleteUser/DeleteUserHandler.cs:25, :29, - with UserRole.IsAdmin(...) at :32-33). Both subclasses now take the + HasDeletePrivilege + (MMCA.ADC/.../DeleteUser/DeleteUserHandler.cs:29, :35, with HasDeletePrivilege returning + UserRole.IsOrganizer(...) at :39-40; MMCA.Store/.../DeleteUser/DeleteUserHandler.cs:25, :29, + with UserRole.IsAdmin(...) at :32-33). Both subclasses take the ICacheService purely to hand it to this base. Covered directly by MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Users/DeleteUserHandlerBaseTests.cs:17, whose fixture user type is TestHidingDeleteUser (MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Users/UserUseCaseTestDoubles.cs:103) precisely so the hidden-Delete() dispatch rule above is a regression test rather than a comment.

              • -
              • Caveats: post-commit actions run after the erasure has already succeeded, so each one owns its - own failure handling; the base does not wrap them (:151-154, and see the documented expectation at - :175-181). The one exception is the marker write the base performs itself, which it does wrap - (:140-149). Both apps' DeleteUserCommand records are +

              • Caveats: queued afterCommit actions run after the erasure has been saved, so each one owns its + own failure handling; the base does not wrap them (:155-158, and see the documented expectation at + :179-186). Under an ITransactional command they, and the marker, run before the commit + (:18-21). The one exception to "not wrapped" is the marker write the base performs itself, which it + does wrap (:144-153). Both apps' DeleteUserCommand records are ICacheInvalidating, so the cache prefix they carry is invalidated by the decorator after the handler returns success, outside this class.

              • @@ -3874,94 +3802,97 @@

                ForgotPasswordHandlerBase<TU
                • What it is: the shared start-a-password-reset workflow: parse the submitted address, resolve the account behind it, mint a single-use token, and email it. Every outcome returns - Result.Success() (ForgotPasswordHandlerBase.cs:36, :51-100).

                  + Result.Success() (ForgotPasswordHandlerBase.cs:36, :52-101).

                • Depends on: IUnitOfWork, IPasswordResetTokenService, IEmailSender, IOptions<PasswordResetSettings> and an ILogger - (:35-40); implements + (:36-41); implements ICommandHandler<in TCommand, TResult> - over Result (:40). Constraints: TUser is only an + over Result (:41). Constraints: TUser is only an AuditableAggregateRootEntity<TIdentifierType> - keyed by UserIdentifierType (:41) with no capability interface at all, because the workflow - reads nothing off the aggregate except Id (:72), and TCommand is an + keyed by UserIdentifierType (:42) with no capability interface at all, because the workflow + reads nothing off the aggregate except Id (:73), and TCommand is an ICommandWithRequest<out TRequest> - carrying a ForgotPasswordRequest (:42). It also uses - the Email value object (:57) and - UserUseCaseLog. Externals: Microsoft.Extensions.Options, - Microsoft.Extensions.Logging, System.Globalization and System.Net.WebUtility (:1-4).

                  + carrying a ForgotPasswordRequest (:43). It also uses + the Email value object (:58), and logs through three + private source-generated [LoggerMessage] methods (:157-166), hence the partial declaration + (:36). Externals: System.Globalization, System.Net.WebUtility, Microsoft.Extensions.Logging + and Microsoft.Extensions.Options (:1-4).

                • Concept introduced: the success-always handler, and anti-enumeration as a return-type decision. Every other command base in this family reports its failures. This one cannot. A response that differs between "we sent you a reset link" and "no such account" is an account-enumeration oracle: anyone can walk an address list and learn which addresses are registered. So the four ways this workflow can fail to send anything all return Result.Success() and differ only in a log line: a - malformed address (:58-62), an address with no account (:66-70), a request the token service - throttled (:73-77), and an email send that threw (:90-96). The class remarks state the rule + malformed address (:59-63), an address with no account (:67-71), a request the token service + throttled (:74-78), and an email send that threw (:91-97). The class remarks state the rule outright and name the one exception: only the request validator can produce a 400, and it inspects - the shape of the address alone (:20-25, + the shape of the address alone (:21-26, MMCA.Common/Source/Core/MMCA.Common.Application/Auth/Validation/ForgotPasswordRequestValidator.cs:11-16).

                  [Rubric §11: Security] assesses whether a public endpoint leaks facts about who holds an account. The leak surface is wider than the HTTP response, and the code closes it in three places. The result - is uniform (:62, :70, :77, :95). The controller turns every one of them into the same + is uniform (:62, :70, :77, :96). The controller turns every one of them into the same 202 Accepted (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/PasswordResetAuthControllerBase.cs:82-93). - And the rejection log deliberately carries a reason string but no address and no account id, so the - log does not become the oracle the response is not - (UserUseCaseLog.cs:37-40). Only the paths that already proved an account exists log a user id - (UserUseCaseLog.cs:28-35).

                  + And the rejection log, PasswordResetRejected, deliberately carries a reason string but no address + and no account id, so the log does not become the oracle the response is not (:163-166, the + comment at :163-164 says so). Only the paths that already proved an account exists log a user id: + PasswordResetRequested (:157-158) and the Warning-level PasswordResetEmailFailed (:160-161).

                  [Rubric §29: Resilience & Business Continuity] assesses what happens when a dependency fails - mid-workflow. A send failure is caught, logged with the exception, and swallowed (:90-96); the + mid-workflow. A send failure is caught, logged with the exception, and swallowed (:91-97); the token has already been issued and is still valid, so the user can retry or use the link from a later - request. The catch filter excludes OperationCanceledException (:90) so a cancelled request is + request. The catch filter excludes OperationCanceledException (:91) so a cancelled request is not misreported as a delivered reset.

                  [Rubric §3: Clean Architecture] assesses dependency direction. The workflow lives in the Application layer and reaches the SMTP relay, the token cache and the database only through interfaces; the one thing it genuinely cannot express in the framework, an address-to-account lookup - over an app-owned User aggregate, is the single abstract member (:109).

                  + over an app-owned User aggregate, is the single abstract member (:110).

                • -
                • Walkthrough: two protected properties, the handler method, and four hooks.

                  +
                • Walkthrough: two protected properties, the handler method, four hooks and three log methods.

                    -
                  • Primary constructor (:35-40): unitOfWork, tokenService, emailSender, settings, logger.
                  • -
                  • UnitOfWork (:45): exposed so the lookup override can reach a read repository. Both apps use it +
                  • Primary constructor (:36-41): unitOfWork, tokenService, emailSender, settings, logger.
                  • +
                  • UnitOfWork (:46): exposed so the lookup override can reach a read repository. Both apps use it for exactly that.
                  • -
                  • Settings (:48): settings.Value, unwrapped once so the body reads +
                  • Settings (:49): settings.Value, unwrapped once so the body reads Settings.TokenLifetimeMinutes rather than settings.Value....
                  • -
                  • HandleAsync(TCommand, CancellationToken) (:51-100): null-guard (:55); Email.Create on the - raw string, so a malformed address never reaches the lookup (:57-62); FindUntrackedByEmailAsync - (:65); tokenService.IssueAsync(email.Value, user.Id, ...) (:72), whose failure means the +
                  • HandleAsync(TCommand, CancellationToken) (:52-101): null-guard (:56); Email.Create on the + raw string, so a malformed address never reaches the lookup (:58-63); FindUntrackedByEmailAsync + (:66); tokenService.IssueAsync(email.Value, user.Id, ...) (:73), whose failure means the per-email throttle fired; then the send, composed from the three Compose* hooks and sent as HTML - (:83-88); and finally the PasswordResetRequested log and success (:98-99).
                  • -
                  • FindUntrackedByEmailAsync(Email, CancellationToken) (:109): protected abstract. The only + (:84-89); and finally the PasswordResetRequested log and success (:99-100).
                  • +
                  • FindUntrackedByEmailAsync(Email, CancellationToken) (:110): protected abstract. The only app-specific step, because each app's User stores the address differently.
                  • -
                  • ComposeSubject() (:113): protected virtual, "Reset your password". Override to localize or +
                  • ComposeSubject() (:114): protected virtual, "Reset your password". Override to localize or rebrand.
                  • -
                  • ComposeBody(string? resetLink, string token) (:123-134): protected virtual. It carries the +
                  • ComposeBody(string? resetLink, string token) (:124-135): protected virtual. It carries the link and the raw token, because clients without deep linking (the MAUI head) need the token - typed into the reset page by hand (:115-119). Both the link and the token go through - WebUtility.HtmlEncode before interpolation into the HTML (:128, :132), and the expiry is - rendered with CultureInfo.InvariantCulture (:125).
                  • -
                  • ComposeResetLink(string email, string token) (:144-147): protected virtual. Returns null + typed into the reset page by hand (:116-120). Both the link and the token go through + WebUtility.HtmlEncode before interpolation into the HTML (:129, :133), and the expiry is + rendered with CultureInfo.InvariantCulture (:126).
                  • +
                  • ComposeResetLink(string email, string token) (:152-155): protected virtual. Returns null when PasswordResetSettings.ResetUrl is blank, so an unconfigured host degrades to a token-only email rather than emailing a broken link; otherwise it appends #email=...&token=... with both values Uri.EscapeDataString-encoded, as a URL fragment rather than a query string.
                  • +
                  • PasswordResetRequested, PasswordResetEmailFailed and PasswordResetRejected (:157-166): + private static partial, the three source-generated audit lines described under Security.

                  Concept introduced: the live token rides in the URL fragment, not the query string. A fragment is never sent to a server, so putting the address and the single-use token there keeps them out of ingress access logs, out of request telemetry (url.query is exported unredacted), and out of any Referer header the reset page emits; the page itself reads the values from location.hash and - scrubs them from the address bar (:144-155, remarks on ComposeResetLink).

                  + scrubs them from the address bar (:145-155, remarks on ComposeResetLink).

                • Why it's built this way: the reset token is deliberately not a database row. It lives in the distributed cache, hashed at rest, with the per-email request throttle and the per-token attempt cap enforced by the token service rather than by this handler (ADR-091; - MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:45, :71, + MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Auth/PasswordResetTokenService.cs:58, :71, :140). That split is why the handler's only reaction to a throttled request is a log line: it never learns which limit fired. The command record stays app-side for the same reason it does in the ChangePassword hoist, and the base reads it only through ICommandWithRequest<ForgotPasswordRequest> - (:27-31).

                  + (:27-32).

                • Where it's used: subclassed once per app, each override implementing the address lookup as an untracked GetAllAsync filtered on the Email value object @@ -3993,25 +3924,26 @@

                  ResetPasswordHandlerBase<TUse
                • What it is: the shared complete-a-password-reset workflow: redeem the single-use token, hash the new password, let the aggregate apply its invariants, persist, then clear the account's lockout so the user can sign in immediately with the new credential (ResetPasswordHandlerBase.cs:41, - :50-93).

                  + :65-114).

                • Depends on: IUnitOfWork, IPasswordHasher, IPasswordResetTokenService, ILoginProtectionService and an ILogger, plus a required IRefreshSessionStore and an optional - TimeProvider (:43-49); + TimeProvider (:41-48); implements ICommandHandler<in TCommand, TResult> - over Result (:35). Constraints: TUser is an + over Result (:48). Constraints: TUser is an AuditableAggregateRootEntity<TIdentifierType> - implementing IPasswordChangeableUser (:36), the same + implementing IPasswordChangeableUser (:49), the same capability ChangePasswordHandlerBase<TUser, TCommand> requires, and TCommand is an ICommandWithRequest<out TRequest> - carrying a ResetPasswordRequest (:37). Also uses - Error and UserUseCaseLog. - Externals: Microsoft.Extensions.Logging (:1).

                  + carrying a ResetPasswordRequest (:50). Also uses + Error, and logs through two private source-generated + [LoggerMessage] methods, PasswordResetCompleted (:122-123) and PasswordResetRejected + (:127-128), hence the partial declaration (:41). Externals: Microsoft.Extensions.Logging.

                • Concept introduced: burn the token before the write, not after. The token is consumed at the top of the method, before anything is saved (:76-78), and the comment explains the trade: leaving it @@ -4028,8 +3960,8 @@

                  ResetPasswordHandlerBase<TUse edit to make two branches distinguishable by accident.

                  [Rubric §11: Security] assesses whether an anonymous endpoint leaks account state. Two mechanisms do the work here: the uniform error above, and a rejection log that names only a reason string, - never an address or an account id (:81, :90, and UserUseCaseLog.cs:37-40). The - matching controller action turns every failure into the same 401 + never an address or an account id (:81, :90, and the declaration with its comment at + :125-128). The matching controller action turns every failure into the same 401 (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/PasswordResetAuthControllerBase.cs:99-118).

                  [Rubric §29: Resilience & Business Continuity] assesses whether a user can recover unaided. The final ResetFailedAttemptsAsync call (:110) is the part that makes a reset actually usable: a user @@ -4041,22 +3973,24 @@

                  ResetPasswordHandlerBase<TUse has to die with the old password. After saving, the handler revokes every live refresh session on the account through the required IRefreshSessionStore, using the shared - RefreshSessionRevocation helper (:103-107), the same - mechanism ChangePasswordHandlerBase<TUser, TCommand> + RefreshSessionRevocation helper (:105-107, comment + at :103-104), the same mechanism + ChangePasswordHandlerBase<TUser, TCommand> uses (ADR-097). Only the - timeProvider parameter still defaults to null; the base caches it in a private _timeProvider + timeProvider parameter defaults to null; the base caches it in a private _timeProvider field, falling back to TimeProvider.System (:52), and a caller must supply the refresh-session store explicitly.

                  [Rubric §4: DDD] assesses whether the rules live in the domain. As with the change-password base, the handler hashes and then calls user.ChangePassword(newHash, newSalt) (:95), returning the aggregate's own result on failure without saving or clearing the lockout (:96-99).

                • -
                • Walkthrough: two protected members, the handler method, and one private helper.

                  +
                • Walkthrough: two protected members, the handler method, one private helper and two log methods.

                    -
                  • Primary constructor (:43-50): unitOfWork, passwordHasher, tokenService, loginProtection, - logger, the required refreshSessions, and the optional timeProvider. Seven collaborators, the - widest of the Users bases, because a reset touches the token store, the hasher, the database, the - refresh session store and the lockout store in one pass.
                  • +
                  • Primary constructor (:41-48): unitOfWork, passwordHasher, tokenService, loginProtection, + logger, the required refreshSessions, and the optional timeProvider. Seven collaborators, one + more than ChangePasswordHandlerBase<TUser, TCommand>, + because a reset touches the token store, the hasher, the database, the refresh session store and + the lockout store in one pass.
                  • UnitOfWork (:55) and HandlerName (:62): the same two protected members as the other bases, with the same rationale (an app subclass named ResetPasswordHandler reports that name as the error source, :57-61).
                  • @@ -4065,10 +3999,12 @@

                    ResetPasswordHandlerBase<TUse rejection (:79-83); take the account id the token resolved to (:85) and load it through the write repository (:86-87), failing with the same generic error if it is gone (:88-92); hash the new password (:94) and call the aggregate (:95); SaveChangesAsync (:101); revoke - refresh sessions (:103-107); clear the lockout (:110); log completion and return the aggregate's - success result (:112-113). + refresh sessions (:105-107); clear the lockout (:110); log PasswordResetCompleted and return + the aggregate's success result (:112-113).
                  • InvalidToken() (:116-120): the single Error.Unauthorized("Auth.InvalidResetToken", ...) construction, stamped with HandlerName.
                  • +
                  • PasswordResetCompleted and PasswordResetRejected (:122-128): private static partial, the + source-generated audit lines.
                • Why it's built this way: the reset half of the recovery vertical had to share the @@ -4102,8 +4038,11 @@

                  ResetPasswordHandlerBase<TUse (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ResetPassword/ResetPasswordCommand.cs:16, :18) and Store's is not, which is the reason the command record stays app-side. The lockout clear at :110 runs after the save and refresh-session revocation, and is not part of the transaction: if - it throws, the password has already changed. Not determinable from source: whether any deployment - configures a + it throws, the password has already changed. It clears the counter keyed by the request's email + address (request.Email, :110), which is the sign-in counter; the separate + password-change:{userId} counter that + ChangePasswordHandlerBase<TUser, TCommand> keeps is not + touched here. Not determinable from source: whether any deployment configures a ResetFailedAttemptsAsync implementation that can fail in a way the caller would notice, since the contract returns a bare Task with no result (ILoginProtectionService.cs:33).

                • @@ -4130,42 +4069,45 @@

                  SendEmailConfirmationHan and TCommand is an ICommandWithRequest<out TRequest> carrying a SendEmailConfirmationRequest (:47). Also uses the - Email value object and - UserUseCaseLog.

                  + Email value object, and logs through three private + source-generated [LoggerMessage] methods (:176-185), hence the partial declaration (:40).

                • Concept introduced: an optional capability check with a safe default. Unlike ConfirmEmailHandlerBase<TUser, TCommand>, which constrains TUser to IEmailConfirmableUser, this base does not: its IsAlreadyConfirmed hook pattern-matches the aggregate against the interface at runtime and answers false when it is not implemented - (user is Domain.Auth.IEmailConfirmableUser { IsEmailConfirmed: true }), so an app that has not - adopted the confirmation contract still gets a working send workflow rather than a generic constraint - it cannot satisfy.

                  + (user is Domain.Auth.IEmailConfirmableUser { IsEmailConfirmed: true }, :129-130), so an app that + has not adopted the confirmation contract still gets a working send workflow rather than a generic + constraint it cannot satisfy.

                  [Rubric §11: Security] assesses account-enumeration resistance, the same concern ForgotPasswordHandlerBase<TUser, TCommand> closes for password resets. A malformed address (:60-65), an address with no account (:67-73), an already-confirmed address (:75-81), a throttled token request (:83-88) and a send that threw - (:92-107) all return the identical Result.Success() and differ only in a log reason string, never - an address or account id in the rejection log.

                  + (:92-107) all return the identical Result.Success() and differ only in a log reason string. + The rejection log, EmailConfirmationRejected, carries that reason and never an address or account + id (:182-185); only EmailConfirmationRequested (:176-177) and the Warning-level + EmailConfirmationEmailFailed (:179-180), both reached after an account resolved, log a user id.

                • -
                • Walkthrough: two protected properties, the handler method, and four hooks (mirroring +

                • Walkthrough: two protected properties, the handler method, and five hooks (mirroring ForgotPasswordHandlerBase<TUser, TCommand> member for - member).

                  + member, plus the already-confirmed check).

                  • UnitOfWork (:50) and Settings (:53).
                  • HandleAsync(TCommand, CancellationToken) (:56-111): null-guard (:58); Email.Create on the raw string (:60-65); FindUntrackedByEmailAsync (:68); the already-confirmed short-circuit (:75-81); tokenService.IssueAsync (:83-88); the send, composed from the three Compose* hooks - (:92-100); and the EmailConfirmationRequested log and success (:108-110).
                  • -
                  • FindUntrackedByEmailAsync(Email, CancellationToken): protected abstract, the one app-specific - lookup.
                  • -
                  • IsAlreadyConfirmed(TUser): protected virtual, the pattern-match default described above.
                  • -
                  • ComposeSubject(): protected virtual, "Confirm your email address".
                  • -
                  • ComposeBody(string?, string) and ComposeConfirmationLink(string, string): protected virtual, - carrying both the link and the raw token for the same no-deep-linking (MAUI) reason as the - password-reset base, and, like + (:92-100); and the EmailConfirmationRequested log and success (:109-110).
                  • +
                  • FindUntrackedByEmailAsync(Email, CancellationToken) (:120): protected abstract, the one + app-specific lookup.
                  • +
                  • IsAlreadyConfirmed(TUser) (:129-130): protected virtual, the pattern-match default described + above.
                  • +
                  • ComposeSubject() (:134): protected virtual, "Confirm your email address".
                  • +
                  • ComposeBody(string?, string) (:144-155) and ComposeConfirmationLink(string, string) + (:171-174): protected virtual, carrying both the link and the raw token for the same + no-deep-linking (MAUI) reason as the password-reset base, and, like ForgotPasswordHandlerBase<TUser, TCommand>.ComposeResetLink, riding the address and token in the URL fragment rather than the query string for the same - ingress-log and Referer reasons.
                  • + ingress-log and Referer reasons (:165-170).
                • Why it's built this way: it is the send half of the same email-verification vertical whose @@ -4195,17 +4137,17 @@

                  UpcastingIntegrationEventConsum
                • Depends on: IEventUpcasterRegistry, IServiceProvider, IInboxStore, and ILogger<T> as its four primary-constructor parameters (UpcastingIntegrationEventConsumer.cs:32-36); MassTransit's IConsumer<TEvent>; EventNameResolver for the inbox key; ConsumerOriginRestore to restore the publisher's identity, tenant, and correlation id before the inbox is touched; System.Linq.Expressions and System.Collections.Concurrent for the dispatch cache.
                • Concept introduced, contract retirement without a coordinated deploy. [Rubric §6, CQRS and Event-Driven] assesses whether the event pipeline can evolve its contracts, and [Rubric §7, Microservices Readiness] assesses whether two services can be deployed independently. Renaming or reshaping an integration event across a distributed system normally forces a lockstep deploy, because in-flight messages of the old shape have no handler on the other side. ADR-090 resolves that with a registered IEventUpcaster chain plus this consumer: the old queue stays bound and drains, each message walks the upcaster chain to the terminal type, and the handler code only ever knows the newest contract. The pairing rule is a real trap and is stated in the doc (UpcastingIntegrationEventConsumer.cs:20-22): do not register both this and the plain IntegrationEventConsumer<TEvent> for the same type, because they would compete for one queue and run the handlers twice.
                • Concept, dedup keyed on the original message id. [Rubric §12, Performance & Scalability] assesses whether idempotency is applied uniformly rather than per handler. Idempotent consumption (ADR-021) keys on the message id carried in the envelope. The doc (UpcastingIntegrationEventConsumer.cs:24-28) records that the registry preserves the envelope across every upcast hop, so the id this consumer records is the same id a plain consumer would have recorded, and a redelivery is recognised whatever contract the handlers ultimately see.
                • -
                • Concept, restoring the publisher's origin before the inbox is touched. [Rubric §11, Security] and [Rubric §8, Data Architecture] both apply here: under database-per-tenant, the inbox store's own routing depends on the tenant this restore sets. PrincipalAuthenticationType (UpcastingIntegrationEventConsumer.cs:842-847) is declared as the same constant IntegrationEventConsumer<TEvent> uses, because an upcast changes the contract, never the hop the identity came back from. Consume calls ConsumerOriginRestore.Apply (:856-860) before the dedup check for exactly the reason the sibling consumer does it in the same order: a retired contract is delivered with the same headers as a current one, so a consumer that skipped this would run its handlers anonymous while its sibling did not.
                • +
                • Concept, restoring the publisher's origin before the inbox is touched. [Rubric §11, Security] and [Rubric §8, Data Architecture] both apply here: under database-per-tenant, the inbox store's own routing depends on the tenant this restore sets. PrincipalAuthenticationType (UpcastingIntegrationEventConsumer.cs:49-54) is declared as the same constant IntegrationEventConsumer<TEvent> uses, because an upcast changes the contract, never the hop the identity came back from. Consume calls ConsumerOriginRestore.Apply (:63-67) before the dedup check for exactly the reason the sibling consumer does it in the same order: a retired contract is delivered with the same headers as a current one, so a consumer that skipped this would run its handlers anonymous while its sibling did not.
                • Walkthrough
                    -
                  • Consume(context) (UpcastingIntegrationEventConsumer.cs:850-938) restores the publisher's origin first via ConsumerOriginRestore.Apply (:860), then reads the message and takes integrationEvent.MessageId before any upcasting (:864), which is the point of the earlier concept paragraph on dedup.
                  • -
                  • The inbox key is EventNameResolver.GetInboxName(typeof(TEvent)) (UpcastingIntegrationEventConsumer.cs:868), the retired contract's [EventName] identity when it declares one and its short type name otherwise, which is what every row written so far holds.
                  • -
                  • inbox.TryBeginAsync(...) returning false means already processed: log at Debug and return (UpcastingIntegrationEventConsumer.cs:872-876). TryBegin also stages the inbox row in the scope's unit of work, so a handler's own SaveChangesAsync commits the dedup row atomically with the handler's mutations.
                  • -
                  • With no upcaster registered for the type, the consumer logs at Information and continues (UpcastingIntegrationEventConsumer.cs:878-883). This is the documented degrade path: the registry returns the instance untouched, and handlers for the original type run as usual.
                  • -
                  • UpcastToTerminal produces the terminal instance, and a type change is logged at Debug with both contract names (UpcastingIntegrationEventConsumer.cs:885-891).
                  • -
                  • Handler dispatch is non-generic because the terminal type is only known at runtime. DispatchCache (UpcastingIntegrationEventConsumer.cs:838-840) memoizes, per terminal type, the closed IIntegrationEventHandler<> interface and a compiled invoker. BuildInvoker (:948-968) builds an expression tree that casts the two object parameters to their concrete types and calls the strongly-typed HandleAsync directly, so reflection is paid once per contract instead of once per message. This mirrors what DomainEventDispatcher does for the in-process path.
                  • -
                  • The dispatch loop (UpcastingIntegrationEventConsumer.cs:901-926) counts handlers and awaits each invoker. Its catch (:914-924) is the failure contract: inbox.Abandon(messageId) discards the staged row so the redelivery is reprocessed rather than skipped as a duplicate (:916-918), the handler failure is logged with the handler's full type name, and the exception is rethrown so MassTransit applies the configured UseMessageRetry policy before dead-lettering (:920-923). OperationCanceledException is excluded from the catch filter, so a shutdown is not treated as a handler failure.
                  • -
                  • Zero handlers is an Information log, not an error (UpcastingIntegrationEventConsumer.cs:928-933): returning normally lets MassTransit ack the message, and the comment states plainly that the broker will not retry.
                  • -
                  • inbox.CompleteAsync(...) (UpcastingIntegrationEventConsumer.cs:937) persists the staged row unless a handler's own save already committed it. The message is recorded only on a successful consume, because the failure path above rethrows.
                  • +
                  • Consume(context) (UpcastingIntegrationEventConsumer.cs:57-145) restores the publisher's origin first via ConsumerOriginRestore.Apply (:67), then reads the message and takes integrationEvent.MessageId before any upcasting (:71), which is the point of the earlier concept paragraph on dedup.
                  • +
                  • The inbox key is EventNameResolver.GetInboxName(typeof(TEvent)) (UpcastingIntegrationEventConsumer.cs:75), the retired contract's [EventName] identity when it declares one and its short type name otherwise, which is what every row written so far holds.
                  • +
                  • inbox.TryBeginAsync(...) returning false means already processed: log at Debug and return (UpcastingIntegrationEventConsumer.cs:79-83). TryBegin also stages the inbox row in the scope's unit of work, so a handler's own SaveChangesAsync commits the dedup row atomically with the handler's mutations.
                  • +
                  • With no upcaster registered for the type, the consumer logs at Information and continues (UpcastingIntegrationEventConsumer.cs:85-90). This is the documented degrade path: the registry returns the instance untouched, and handlers for the original type run as usual.
                  • +
                  • UpcastToTerminal produces the terminal instance, and a type change is logged at Debug with both contract names (UpcastingIntegrationEventConsumer.cs:92-98).
                  • +
                  • Handler dispatch is non-generic because the terminal type is only known at runtime. DispatchCache (UpcastingIntegrationEventConsumer.cs:45-47) memoizes, per terminal type, the closed IIntegrationEventHandler<> interface and a compiled invoker. BuildInvoker (:155-175) builds an expression tree that casts the two object parameters to their concrete types and calls the strongly-typed HandleAsync directly, so reflection is paid once per contract instead of once per message. This mirrors what DomainEventDispatcher does for the in-process path.
                  • +
                  • The dispatch loop (UpcastingIntegrationEventConsumer.cs:106-133) counts handlers and awaits each invoker. Its catch (:121-132) is the failure contract: inbox.Abandon(messageId) discards the staged row so the redelivery is reprocessed rather than skipped as a duplicate (:123-125), the handler failure is logged with the handler's full type name, and the exception is rethrown so MassTransit applies the configured UseMessageRetry policy before dead-lettering (:127-131). OperationCanceledException is excluded from the catch filter, so a shutdown is not treated as a handler failure.
                  • +
                  • Zero handlers is an Information log, not an error (UpcastingIntegrationEventConsumer.cs:135-140): returning normally lets MassTransit ack the message, and the comment states plainly that the broker will not retry.
                  • +
                  • inbox.CompleteAsync(...) (UpcastingIntegrationEventConsumer.cs:144) persists the staged row unless a handler's own save already committed it. The message is recorded only on a successful consume, because the failure path above rethrows.
                • Why it's built this way: the alternative to a per-retired-type consumer is version-tolerant handlers, each branching on a schema version, which spreads the migration across every handler and never gets cleaned up. Concentrating it in one registered consumer per retired contract means the retirement is visible in one place in a host's Program.cs and can be deleted when the old queue is drained (ADR-010 sets the versioning policy, ADR-090 ships the mechanism, ADR-016 pins the MassTransit version this all runs on).
                • @@ -4262,7 +4204,7 @@

                  CreateMigrationProofTable

                  the schema is behind the code and has to name the pending migration in the failure so an operator knows what to apply. That guard can only be exercised against a genuinely pending, genuinely named migration, which is what this type provides - (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:144, + (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Startup/DatabaseInitializationExtensionsTests.cs:145, :168).

                • Walkthrough: two attributes, two constants, and the migration pair.

                  @@ -4306,7 +4248,7 @@

                  CreateMigrationProofTable

                  uses it for the production guard in DatabaseInitializationExtensions: the "None" strategy must throw naming the pending migration and must apply nothing on the way out - (DatabaseInitializationExtensionsTests.cs:153-154, :167-174). + (DatabaseInitializationExtensionsTests.cs:154-155, :167-174). Its deliberate non-consumer is DbContextFactoryMigrationTargetTests, which needs a migrations assembly that declares nothing.

                  diff --git a/docs/onboarding/group-15-common-ui-framework.html b/docs/onboarding/group-15-common-ui-framework.html index 5248608e..25b99475 100644 --- a/docs/onboarding/group-15-common-ui-framework.html +++ b/docs/onboarding/group-15-common-ui-framework.html @@ -162,7 +162,7 @@

                  15. Common A second, thinner package MMCA.Common.UI.Web sits above it and holds the pieces that need an ASP.NET pipeline (server-side token storage, the Blazor Content-Security-Policy provider, the trusted-caller header an SSR host stamps on its gateway calls, and the host's own edge rate limiter - and ceiling on open circuits). The per-app and per-module Razor pages in the consumer apps + and ceiling on open circuits, plus an opt-in same-origin API proxy that keeps every token out of script). The per-app and per-module Razor pages in the consumer apps (chapter 21) derive from and consume these primitives, and the same components render across Blazor Server, WebAssembly and MAUI with no per-platform reimplementation. [Rubric §18, UI Architecture & Component Design] assesses component reuse, separation of @@ -181,21 +181,31 @@

                  15. Common (MMCA.Common.UI/Services/Api/AuthenticatedServiceBase.cs:15). That base owns the cross-cutting concerns of an outbound call. First, a Polly retry policy: 3 retries with exponential backoff (2s, 4s, 8s) plus up to one second of random jitter so a fleet of clients does not re-converge on the same instant - (AuthenticatedServiceBase.cs:25, :114-116), over a deliberate retryable set rather than "any + (AuthenticatedServiceBase.cs:25, :119-122), over a deliberate retryable set rather than "any 5xx", since 501 and 505 are permanent verdicts and are excluded while 408 and 429 are explicit - invitations to come back (AuthenticatedServiceBase.cs:100-109); the policy's onRetry disposes each + invitations to come back (AuthenticatedServiceBase.cs:100-114); the policy's onRetry disposes each superseded response, because Polly hands the caller only the final outcome and an undisposed 5xx leaks its content buffer and holds its connection out of the pool under exactly the sustained failure - the retries exist to survive (AuthenticatedServiceBase.cs:131-134). Second, a helper that creates a + the retries exist to survive (AuthenticatedServiceBase.cs:130-140). Second, a helper that creates a "APIClient" HttpClient from IHttpClientFactory and stamps the JWT Bearer token onto it from ITokenStorageService, swallowing the InvalidOperationException that JS - interop throws during SSR prerender (AuthenticatedServiceBase.cs:51-70); a sibling + interop throws during SSR prerender (AuthenticatedServiceBase.cs:51-76); a sibling CreateClientWithToken builds a client around an explicitly supplied token so a request the API answered 401 can be replayed with one acquired straight from ITokenRefresher - rather than resending the token the server just rejected (AuthenticatedServiceBase.cs:80-87).

                  + rather than resending the token the server just rejected (AuthenticatedServiceBase.cs:78-93). + The same policy object is reachable without inheritance: IdempotentReadRetry + (MMCA.Common.UI/Services/Api/IdempotentReadRetry.cs:18) hands anonymous lookup services the exact + instance the base exposes to its subclasses, so the two paths cannot drift, and its only entry point + is a GET because re-sending is safe only for an idempotent read (IdempotentReadRetry.cs:9-16, :24, + :35-44, AuthenticatedServiceBase.cs:36). Its read-side sibling PagedReadAll + (MMCA.Common.UI/Services/Api/PagedReadAll.cs:19) exists because a single read truncates silently at + the API's page-size maximum: it pages a /paged endpoint in stable id order with a PageSize of 500 + until the server's reported total is reached, hands back the first failure unchanged, and stops after + 40 pages (20,000 rows) whatever the server claims (PagedReadAll.cs:12-18, :22, :28, :43-46, + :57-90).

                  Retry and idempotency are coupled on purpose: NewIdempotencyKey() - (AuthenticatedServiceBase.cs:43) is generated once per logical write and set as a default header - on the single client that serves every attempt (EntityServiceBase.cs:159-163, :376-397), so a + (AuthenticatedServiceBase.cs:49) is generated once per logical write and set as a default header + on the single client that serves every attempt (EntityServiceBase.cs:159-163, :376-402), so a retried create dedupes on the server instead of producing a duplicate row (the server half is IdempotencyHeaders and IdempotentAttribute, @@ -206,13 +216,13 @@

                  15. Common IConcurrencyAware, and that tag travels as the If-Match header on the same per-operation client, so every retry states the same precondition instead of a later attempt succeeding against a version the user never saw - (EntityServiceBase.cs:197-200, :389-395, + (EntityServiceBase.cs:197-200, :393-399, ADR-035). Responses come back in the same PagedCollectionResult<T> / CollectionResult<T> envelopes the API returns, and both SendRequestAsync overloads read the response through ProblemDetailsResultReader - (EntityServiceBase.cs:324-342, :354-370), so a 404 arrives as an + (EntityServiceBase.cs:328-343, :358-371), so a 404 arrives as an ErrorType.NotFound failure, a rejection as Validation, a 500 as Unexpected, each carrying the server's own text. What the reader cannot convert is the absence of a response, and that is @@ -242,8 +252,9 @@

                  15. Common

                  A third caching tier, on the client. IUiReadCache (MMCA.Common.UI/Services/Caching/IUiReadCache.cs:32) is a read-through cache sitting in front of the API client, so a list re-read twice within a few seconds (a grid re-mounted by navigation, a lookup - rendered in two components) costs one round trip. Its four members are TryGetFresh, Set, - InvalidatePrefix and Clear (IUiReadCache.cs:42, :51, :59, :66), and the key is + rendered in two components) costs one round trip. Its operations are TryGetFresh, Set, + InvalidatePrefix and Clear (IUiReadCache.cs:50, :59, :78, :85), plus a Generation counter that moves on every + invalidation (:35-40), and the key is deliberately the relative URL, path plus the full query string, which is the same key shape the server's authenticated output cache uses, so a filter, page or sort change misses on both tiers rather than being served stale by one of them (IUiReadCache.cs:9-15, @@ -251,24 +262,27 @@

                  15. Common UiReadCache (MMCA.Common.UI/Services/Caching/UiReadCache.cs:18) implements it as a lock-guarded ordinal dictionary (UiReadCache.cs:20, :25) with lazy expiry, dropping a stale entry when it is next read instead of running a sweep timer over the few dozen entries a circuit ever - holds (UiReadCache.cs:11-14, :50-52), and TTL resolution picks the longest matching route + holds (UiReadCache.cs:12-14, :68, :76), and TTL resolution picks the longest matching route prefix so a child route can state a different budget than the endpoint it sits under - (UiReadCache.cs:120-135). Staleness is configuration, not accident: + (UiReadCache.cs:157-178). Staleness is configuration, not accident: UiReadCacheOptions (MMCA.Common.UI/Common/Settings/UiReadCacheOptions.cs:13) binds the UiReadCache section with an Enabled escape hatch, a 60-second DefaultTtl and the per-prefix override map (UiReadCacheOptions.cs:16, :24, :32, :41). EntityServiceBase takes the cache as an optional constructor argument, so a service registered without one behaves exactly like a plain - GET (EntityServiceBase.cs:47, :58, :241-268); only successes are stored, because caching a + GET (EntityServiceBase.cs:47, :58, :241-272), and a bypassCache flag forces a round trip + for a read the user explicitly asked to be current (:231-235); only successes are stored, because caching a failure would pin a transient outage in front of the user for the whole TTL and let a 404 survive the - create that fixed it (EntityServiceBase.cs:260-265); and every successful write invalidates its own - endpoint prefix (EntityServiceBase.cs:281-287). The one cross-cutting hazard is scope: the cache is + create that fixed it (EntityServiceBase.cs:264-269), while the generation captured before the GET + drops a late store that a concurrent write has already made stale (:258-261, + IUiReadCache.cs:63-70); and every successful write invalidates its own + endpoint prefix (EntityServiceBase.cs:279-290). The one cross-cutting hazard is scope: the cache is scoped, which is per-circuit on Blazor Server but per app lifetime on WebAssembly and MAUI, so AuthUIService.LogoutAsync clears it explicitly rather than trusting the scope to - end with the session (MMCA.Common.UI/Services/Auth/AuthUIService.cs:138, and again on an - unrefreshable session at :168); the device-local document cache that backs offline list snapshots is + end with the session (MMCA.Common.UI/Services/Auth/AuthUIService.cs:352, and again on an + unrefreshable session at :162); the device-local document cache that backs offline list snapshots is wiped in the same pass, since those snapshots are keyed by surface rather than by user - (AuthUIService.cs:38-42, :366). [Rubric §12, Performance & Scalability] and [Rubric §19, State Management] both land here, and the tier is recorded in + (AuthUIService.cs:38-42, :397-406). [Rubric §12, Performance & Scalability] and [Rubric §19, State Management] both land here, and the tier is recorded in ADR-026.

                  The list page: DataGridListPageBase<TDto>. This is the most concept-dense type in the group and the centerpiece of the compose-do-not-repeat thesis. Every list screen in every consumer app derives @@ -321,7 +335,7 @@

                  15. Common releases its own disposables in a Dispose(bool) override that calls the base last (:22-23, :86-101). [Rubric §22, Responsive & Cross-Browser] is named by BreakpointConstants and exercised by MobileInfiniteScrollList<TItem> - (MMCA.Common.UI/Components/Lists/MobileInfiniteScrollList.razor.cs:21), the mobile card list whose + (MMCA.Common.UI/Components/Lists/MobileInfiniteScrollList.razor.cs:20), the mobile card list whose IntersectionObserver sentinel, 500-item rendered cap (:54, :214) and generation-guarded supersession of in-flight fetches keep a long list bounded; a page that wants infinite scroll without giving up its own card markup renders InfiniteScrollSentinel alone @@ -347,7 +361,7 @@

                  15. Common NavigationManager to the browser history API so a detail page can perform a real history.back() when a previous entry exists and fall back to a fixed path otherwise. [Rubric §19, State Management & Data Flow] assesses a deliberate, scoped state model rather than ambient globals: these are registered Scoped, so each circuit gets its own instance - (MMCA.Common.UI/DependencyInjection.cs:133-135). [Rubric §25, Navigation & Information Architecture] covers the route catalogue (RoutePaths + (MMCA.Common.UI/DependencyInjection.cs:149-151). [Rubric §25, Navigation & Information Architecture] covers the route catalogue (RoutePaths (MMCA.Common.UI/Common/RoutePaths.cs:7), NavItem with its role, claim, permission, section and group facets plus resource-key titles resolved per circuit (MMCA.Common.UI/Common/NavItem.cs:20), and the NavSection enum whose declaration @@ -365,7 +379,7 @@

                  15. Common through JwtAuthenticationStateProvider so AuthorizeView reacts immediately, and coordinates push-registration through the device-capability contract IPushRegistrationService - (AuthUIService.cs:32). Two named codes make its local-only failures legible rather than null: + (AuthUIService.cs:49). Two named codes make its local-only failures legible rather than null: Auth.TokenStorageUnavailable when the sign-in succeeded but JS interop could not persist the tokens, and Auth.MissingAccessToken when a 2xx carried no usable token, which means the response shape drifted (AuthUIService.cs:57, :63). The provider round trip is bound to the client that started @@ -380,10 +394,10 @@

                  15. Common victim's app in as the attacker (OAuthFlowStateStore.cs:8-16). A host with no durable storage at all reports IsEnforced false and keeps the previous behavior, because nothing can be written across the redirect (:39, :48-51, :70-73). Alongside login, register, OAuth exchange, logout, refresh and - change-password, it carries the self-service reset pair (IAuthUIService.cs:55, :62, + change-password, it carries the self-service reset pair (IAuthUIService.cs:57, :64, ADR-091) and the multi-device session pair: GetSessionsAsync lists the caller's live refresh sessions newest first - and RevokeSessionAsync ends one of them (IAuthUIService.cs:70, :79, + and RevokeSessionAsync ends one of them (IAuthUIService.cs:72, :81, ADR-097). Those two are rendered by the framework-owned Sessions page (MMCA.Common.UI/Pages/Auth/Sessions.razor.cs:26), reachable at /profile/sessions @@ -391,7 +405,10 @@

                  15. Common other device's session, and a page-level sign-out-everywhere that also ends the caller's own and is therefore followed by the local logout and a redirect. The current device's row carries no button at all, since revoking it from here would leave the app signed in on a dead session until the access - token expired (Sessions.razor.cs:16-24, :33, :55). Each row is labelled from + token expired (Sessions.razor.cs:16-24, :110-112). The page-level path is + RevokeAllSessionsAsync, which signs out locally only once the server confirmed the revoke + (IAuthUIService.cs:91), and one busy flag disables every button while any revoke is in flight, so a + second click cannot race the list rebuild (Sessions.razor.cs:52-55). Each row is labelled from UserAgentSummary (MMCA.Common.UI/Services/Auth/UserAgentSummary.cs:18), which extracts a browser and a platform from the raw header with the most specific token winning (every Chromium browser also says "Chrome", and Chrome and Edge both say "Safari", @@ -402,7 +419,7 @@

                  15. Common way, as a framework page over a framework client. IEmailConfirmationUIService (MMCA.Common.UI/Services/Auth/IEmailConfirmationUIService.cs:21) is a separate interface rather than two new IAuthUIService members, so a consumer's own IAuthUIService implementation keeps compiling - (MMCA.Common.UI/DependencyInjection.cs:126-128). Its two anonymous, Result-returning calls redeem a + (MMCA.Common.UI/DependencyInjection.cs:142-144). Its two anonymous, Result-returning calls redeem a link's single-use token and ask for a fresh link, the latter answering success on any well-formed request because the endpoint returns 202 whether or not the address holds an account (IEmailConfirmationUIService.cs:28, :30-38). The in-box @@ -453,17 +470,110 @@

                  15. Common wires it that way (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Program.cs:89). The ISessionCookieSync / JsFetchSessionCookieSync pair mirrors the in-memory access token into the cookie by firing the fetch from the browser, so - the Set-Cookie lands in the user's own jar under both render modes. All three storage services agree + the Set-Cookie lands in the user's own jar under both render modes, and both calls report + whether the jar was actually updated, false on a non-2xx answer or when JS interop is unavailable + (MMCA.Common.UI/Services/Auth/ISessionCookieSync.cs:8-19, JsFetchSessionCookieSync.cs:26-34). All three storage services agree on one 30-second expiry skew read through JwtTokenInfo.IsFresh (MMCA.Common.UI/Services/Auth/Tokens/JwtTokenInfo.cs:16-37, used at WasmTokenStorageService.cs:15,24 and - ServerTokenStorageService.cs:23,40), which parses the token client-side without validating its + ServerTokenStorageService.cs:41), which parses the token client-side without validating its signature, because the API validates every request. Every outbound call also passes AuthDelegatingHandler, which attaches the stored bearer token to requests that do not go through CreateAuthenticatedClientAsync - (MMCA.Common.UI/Services/Auth/AuthDelegatingHandler.cs:10). The lifecycle across render modes is + (MMCA.Common.UI/Services/Auth/AuthDelegatingHandler.cs:12), except one that sets its SkipBearer request + option: the token-refresh POST does, because that endpoint is anonymous and reading storage from + inside a refresh would re-enter the very acquisition waiting on it (AuthDelegatingHandler.cs:9-19). The lifecycle across render modes is ADR-051; the cross-service JWKS validation these tokens flow into is ADR-004.

                  +

                  An opt-in same-origin API proxy: no token in the page at all. By default a browser client still + holds an access token in memory, the one /auth/session/token hands to script. A Blazor Web host can + remove even that by calling AddCommonSameOriginApiProxy(configuration) and + MapCommonSameOriginApiProxy() from MMCA.Common.UI.Web, a backend-for-frontend in which the browser + calls /api/** on the UI host's own origin and the host forwards to the gateway with the bearer taken + from the HttpOnly session cookie + (SameOriginApiProxyServiceExtensions, + MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyServiceExtensions.cs:12-18, :51; + SameOriginApiProxyEndpointExtensions, + MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpointExtensions.cs:15, :33). + SameOriginApiProxySettings + (MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxySettings.cs:21) binds SameOriginApiProxy + with a default for every value, so a host opts in with no section at all: a /api PathPrefix, a + GatewayAddress that falls back to Api:ApiEndpoint so a service-discovery name resolves exactly as + it does for the host's own clients, auth/login, auth/register and auth/oauth/exchange as the + token-issuing paths, auth/refresh and auth/revoke, and SameSite=Strict session cookies (:31, + :38, :46, :52, :59, :66, :74; the gateway fallback at + SameOriginApiProxyServiceExtensions.cs:57-64). + SameOriginApiProxySettingsValidator + (MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxySettingsValidator.cs:14) runs on start and + refuses a prefix or path carrying route syntax, a query, a fragment or whitespace, a gateway that is + not an absolute address, and any SameSite other than Strict or Lax, because a cookie that + authenticates data calls must never ride on a cross-site request (:7-13, :16, :26-49). Opting in + also makes the session cookies claims-only toward the browser and has /client-config advertise the + proxy, which is what moves the WebAssembly "APIClient" and notification hub onto it + (SameOriginApiProxyServiceExtensions.cs:21-28, :69-74).

                  +

                  Each request runs through SameOriginApiProxyEndpoint + (MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpoint.cs:29) in a fixed order (:15-28). + A foreign Origin or Sec-Fetch-Site is refused with 403 before anything else, and a WebSocket + upgrade must carry this host's own Origin, because an upgrade is not CORS-protected and the origin + is the only proof of who opened it (:115-131, :232-237); OPTIONS is answered locally with 204 + and no CORS grant, so the gateway's CORS policy is never consulted on the proxy's behalf + (:241-245); and every unsafe method must carry the X-CSRF: 1 header named by + SameOriginProxyHeaders + (MMCA.Common.UI/Services/Auth/SameOriginProxyHeaders.cs:11, :14, :17), the one exemption being + an HTTP/2 WebSocket, to which a browser cannot add a header (SameOriginApiProxyEndpoint.cs:250-254). + The header is defense in depth behind the origin check and carries no secret + (SameOriginProxyHeaders.cs:3-9). The session step then validates or refreshes the cookie's access + token through the single-flighted ICookieSessionRefresher: a refused refresh token clears the + cookies and answers 401, while a refresh that could not be decided keeps them and answers 503 with + Retry-After, so a blip at the identity endpoint does not sign the user out (:22-25, :261-284). + A browser POST to the refresh path is answered by the proxy itself from the refresh cookie + (:351-356), and a safe method the gateway answers 401 gets one forced refresh and one replay, never + an unsafe one whose body the upstream could apply twice (:313-328). The per-request YARP transform + is SameOriginProxyTransformer + (MMCA.Common.UI.Web/SameOriginProxy/SameOriginProxyTransformer.cs:34): it maps {prefix}/rest to + {gateway}/rest, replaces whatever Authorization the browser sent with the session's bearer, and + keeps the session cookies and the CSRF header off the upstream request (:26-33, :55-62). Its + ProxyResponseMode (:14) picks the response treatment: Forward copies, + TokenIssuing moves a sign-in's token pair into the cookies and sends the browser the same JSON with + a claims-only accessToken and an empty refreshToken under no-store, and Revoke clears the + cookies whatever the upstream answered, so a failed revoke cannot strand the user in a session + (:16-23, :84-95, :100-145). Forwarding goes through + SameOriginProxyInvoker + (MMCA.Common.UI.Web/SameOriginProxy/SameOriginProxyInvoker.cs:19), one HttpMessageInvoker built + the way YARP requires rather than taken from IHttpClientFactory: no cookie container, which would + replay one user's upstream cookies on another user's request, no redirects or decompression, and none + of the host's default handlers, since a retry would replay a streamed body and the trusted-caller + header would exempt every browser request from the gateway's rate limiter (:9-18, :50-58). On the + client, SameOriginProxyRequestHandler + (MMCA.Common.UI/Services/Auth/SameOriginProxyRequestHandler.cs:11) is the innermost "APIClient" + handler: it strips any Authorization header, since the client only ever holds a claims-only token, + and stamps the CSRF header (:3-10, :18-20).

                  +

                  The Blazor Server circuit keeps calling the gateway server-to-server, but its tokens still have to + meet the browser's cookie jar, which it can reach only through a script fetch. The proxy therefore + replaces the circuit's ITokenRefresher and ISessionCookieSync with + HandoffTokenRefresher and + HandoffSessionCookieSync + (SameOriginApiProxyServiceExtensions.cs:83-84, + MMCA.Common.UI.Web/SameOriginProxy/HandoffSessionServices.cs:14, :39), which trade only + ciphertext with the page. SessionHandoffProtector + (MMCA.Common.UI.Web/SameOriginProxy/SessionHandoffProtector.cs:16) encrypts and signs with the + host's data-protection key ring under one purpose each way, so an access-token handoff can never be + replayed as a cookie write, and expires a handoff after one minute (:7-15, :19, :23-27); a + tampered, expired or wrong-purpose value unprotects to null (:57-72), and the cookie direction + serializes the pair as the private TokenPair record (:33-34, :75). + SessionHandoffEndpoints + (MMCA.Common.UI.Web/SameOriginProxy/SessionHandoffEndpoints.cs:15) maps the two halves, + POST /auth/session/handoff (validate-or-refresh from the cookies, answered with a protected access + token) and POST /auth/session-cookie/handoff (seed the cookies from a protected pair), both + requiring the CSRF header and both exchanging the HandoffBody { "handoff": "..." } + record (:17-18, :23-60, :64). Because a registration that lands later would silently put the + access token back in the page, MapCommonSameOriginApiProxy resolves both services and fails the boot + naming the culprit when either is not the handoff implementation, and it also fails when + AddCommonSameOriginApiProxy was never called, which it detects through the internal + SameOriginApiProxyMarker + (SameOriginApiProxyEndpointExtensions.cs:38-44, :59-92, + SameOriginApiProxyServiceExtensions.cs:91-92). [Rubric §26, Front-End Security] is the home + category: with the proxy on, no token is readable by script on the page.

                  Front-end security beyond tokens. [Rubric §26, Front-End Security] assesses token handling, XSS exposure and secret storage, and this group answers it in five places: keeping the refresh token out of JS-reachable storage (above); BlazorCspPolicyProvider, which pins @@ -480,7 +590,7 @@

                  15. Common previous user's page out of the bfcache while anonymous pages stay bfcache-eligible (MMCA.Common.UI/Extensions/WebApplicationExtensions.cs:24-44); the returnUrl sanitizer already covered; and, on a native head, the app-lock overlay BiometricGate - (MMCA.Common.UI/Components/Capabilities/BiometricGate.razor.cs:17), which re-arms once the app has + (MMCA.Common.UI/Components/Capabilities/BiometricGate.razor.cs:18), which re-arms once the app has sat in the background longer than its 30-second ReLockAfter parameter, short by design because a device handed to someone else is usually away for longer than a glance at a notification (BiometricGate.razor.cs:19-25). It subscribes to the resume event regardless of the current @@ -520,7 +630,7 @@

                  15. Common ships a limiter of its own. UiRateLimitingSettings (UiRateLimitingSettings.cs:33) binds the UiRateLimiting section with an Enabled switch that defaults on, a per-IP PermitLimit of 300 over a 60-second WindowSeconds, and a replica-wide - GlobalConcurrencyLimit of 200 (:36, :43, :58, :62, :74). The 300 sits well above any single + GlobalConcurrencyLimit of 200 (:36, :43, :58, :62, :76). The 300 sits well above any single visitor on purpose, because an office or carrier NAT presents many visitors as one address, and a host whose audience shares one egress (a venue's wifi) is told to raise it (:49-55). UiRateLimitingExtensions @@ -546,7 +656,7 @@

                  15. Common container's memory and stated as an abuse ceiling rather than a capacity plan, plus a tighter disconnected retention of 25 circuits for 60 seconds against the framework's 100 and three minutes (:20, :27-38, :46, :57). BoundedCircuitHandler - (MMCA.Common.UI.Web/Hardening/BoundedCircuitHandler.cs:39) keeps the count as a CircuitHandler, + (MMCA.Common.UI.Web/Hardening/BoundedCircuitHandler.cs:43) keeps the count as a CircuitHandler, the documented extension point that sees a circuit open and close (:14-20). It increments first and rolls back on refusal so two simultaneous opens cannot both take the last slot, floors the close-side decrement at zero so an unpaired close never hands out permits forever, and runs last among the @@ -560,7 +670,7 @@

                  15. Common validates the options on start and registers the handler as a singleton, since circuit handlers resolve from each circuit's own scope and a scoped one would count to one and cap nothing (:13-17, :28-40, :51-60). Both read the same section, so the two numbers cannot drift apart. The ADC Blazor - Web host wires all four calls (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:68, :62, + Web host wires all four calls (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:69, :62, :127, :197). [Rubric §12, Performance & Scalability] and [Rubric §29, Resilience]: a public origin sheds load at its own edge instead of letting an unauthenticated loop exhaust its container.

                  Forms declare their rules once. The shared auth forms (LoginModel, @@ -569,7 +679,7 @@

                  15. Common PasswordComplexityAttribute mirroring the server's rule (at least 8 characters with upper, lower, digit and a non-alphanumeric character) so the form gives the verdict the API would, and deferring empty input to [Required] so a blank field shows one message rather - than two (MMCA.Common.UI/Pages/Auth/PasswordComplexityAttribute.cs:12, :21-30). + than two (MMCA.Common.UI/Pages/Auth/PasswordComplexityAttribute.cs:15, :21-30). AbsoluteUrlAttribute (MMCA.Common.UI/Validation/AbsoluteUrlAttribute.cs:26) is the same parity argument with sharper stakes: it requires an absolute http/https URL (:39-52) because these values are rendered @@ -584,7 +694,12 @@

                  15. Common (MMCA.Common.UI/Validation/DataAnnotationsModelValidator.cs:21) runs the attributes on the model and resolves every produced message as a resource key with pass-through, so a model can declare ErrorMessage = "Validation.AbsoluteUrl" and a plain-English message still renders verbatim - (DataAnnotationsModelValidator.cs:13-19, :36-41); a consumer that keeps its rules in + (DataAnnotationsModelValidator.cs:13-19, :36-41); an EditForm gets the same localizing path by swapping + <DataAnnotationsValidator /> for + LocalizedDataAnnotationsValidator + (MMCA.Common.UI/Validation/LocalizedDataAnnotationsValidator.cs:21), which writes the results into + the form's EditContext, re-validating a field when it changes and the whole model on submit + (:9-20); a consumer that keeps its rules in FluentValidation supplies its own implementation and MMCA.Common.UI never references a validation library. That client-side parity is the point of [Rubric §24, Forms, Validation & UX Safety]: the client predicts, the server decides.

                  @@ -609,7 +724,7 @@

                  15. Common rendered toast already is the live-region content and a second channel would read the same sentence twice (MudToastService.cs:12-17). The dialog facade collapses a dismissal (backdrop click, escape) onto false, so a caller only ever branches on true (MudAppDialogService.cs:14-26). Both are - registered by their own AddCommonUiFacades() (MMCA.Common.UI/DependencyInjection.cs:181-186), + registered by their own AddCommonUiFacades() (MMCA.Common.UI/DependencyInjection.cs:197-202), separate from AddUIShared so a bUnit harness can resolve exactly these two without the rest of the shared-UI surface. [Rubric §1, SOLID] (dependency inversion) and [Rubric §14, Testability]: a component test records toasts instead of driving a rendered snackbar host.

                  @@ -638,7 +753,7 @@

                  15. Common into the chrome, down to the skip-to-content link the shared layout renders first (MMCA.Common.UI/Layout/MainLayout.razor:17).

                  Dark mode is a service, not a flag. ThemeService - (MMCA.Common.UI/Theme/ThemeService.cs:17, registered Scoped at DependencyInjection.cs:138) + (MMCA.Common.UI/Theme/ThemeService.cs:17, registered Scoped at DependencyInjection.cs:154) owns the preference: InitializeAsync reads the stored value through a theme.js module and falls back to the OS prefers-color-scheme only when nothing is stored (ThemeService.cs:18, :34), SetDarkModeAsync persists through the same module and raises OnChange (ThemeService.cs:28, @@ -667,7 +782,7 @@

                  15. Common culture as an Accept-Language header through CultureDelegatingHandler (MMCA.Common.UI/Services/Culture/CultureDelegatingHandler.cs:13, :20-25), wired into the "APIClient" - pipeline at DependencyInjection.cs:93,117, because the cross-origin Gateway does not carry the + pipeline at DependencyInjection.cs:93,125, because the cross-origin Gateway does not carry the cookie through to the services and that header is what makes a backend failure come back localized. View strings are externalized to co-located .resx resolved by IStringLocalizer<T> (AddLocalization() at DependencyInjection.cs:66), anchored by two marker types: @@ -757,7 +872,7 @@

                  15. Common component types to render in the app bar and at the root layout (IUIModule.cs:12-22). The registration prologue is shared too: AddUIModule<TModule>() runs one Scrutor scan that picks up every IEntityService<,> implementation in the module's assembly as scoped, then registers the - descriptor as a singleton (MMCA.Common.UI/DependencyInjection.cs:273-283), so a module's own + descriptor as a singleton (MMCA.Common.UI/DependencyInjection.cs:289-299), so a module's own Add{Module}UI() no longer carries its own copy of that scan and can still register services that must win afterwards. UIModuleConfiguration lets a host switch a module off through Modules:{name}:Enabled, defaulting to enabled when the section is absent @@ -781,19 +896,21 @@

                  15. Common INotificationInboxUIService and IPushNotificationUIService) call the API; and NotificationHubService - (MMCA.Common.UI/Services/Notifications/NotificationHubService.cs:26) holds the SignalR + (MMCA.Common.UI/Services/Notifications/NotificationHubService.cs:34) holds the SignalR connection to the API's NotificationHub, retrying an initial connect up to 3 times with doubling backoff and discarding a connection that never started so - a later join is not blocked forever (NotificationHubService.cs:28, :146-176). The same connection + a later join is not blocked forever (NotificationHubService.cs:36). The same connection carries ephemeral live channel events (ADR-039): components join through - JoinChannelAsync (NotificationHubService.cs:192), membership is reference-counted per key by + JoinChannelAsync (NotificationHubService.cs:231), membership is reference-counted per key by ChannelReferenceCounter (MMCA.Common.UI/Services/Notifications/ChannelReferenceCounter.cs:16) so one subscriber leaving does not cut the channel off for the others, handlers are multicast through disposable - ChannelSubscription handles (NotificationHubService.cs:412), and every held + ChannelSubscription handles (NotificationHubService.cs:490), and every held channel is re-joined on Reconnected because SignalR group membership does not survive a new - connection (NotificationHubService.cs:143). Which notifications a user sees can be narrowed by + connection (NotificationHubService.cs:178). On a WebAssembly client whose host runs the same-origin + proxy, the hub connects through it and sends the proxy's CSRF header instead of an access token + (NotificationHubService.cs:74-83, :361-363). Which notifications a user sees can be narrowed by INotificationScopeProvider, an app-supplied scope key such as "event:2" that both HTTP services consume so a send and the reads that follow agree, defaulting to the unscoped NullNotificationScopeProvider and contractually @@ -816,7 +933,12 @@

                  15. Common token refresh tears both instances down and rebuilds them (NotificationBell.razor.cs:22-29). The whole feature is wired by its own AddNotificationUI() (MMCA.Common.UI/Notifications/DependencyInjection.cs:12, :20-42), kept separate so an app that does - not want real-time notifications never pays for the SignalR plumbing.

                  + not want real-time notifications never pays for the SignalR plumbing. A host that skips it can also set + HideNotificationPagesWhenUnregistered (MMCA.Common.UI/Common/Settings/LayoutSettings.cs:49), and + NotificationPageGate + (MMCA.Common.UI/Notifications/NotificationPageGate.cs:12) then has the router answer the three + notification pages with the not-found page whenever no NotificationUIModule is registered + (:14-25).

                  Administration screens ship as components, not pages. Identity administration is the second finished feature in the package (ADR-116), and it is @@ -877,7 +999,7 @@

                  15. Common

                  How it wires up at startup. A host's Program.cs calls AddUIShared(configuration) once, a C# extension(IServiceCollection) member (see primer §4) on - DependencyInjection (MMCA.Common.UI/DependencyInjection.cs:28, :36-162). + DependencyInjection (MMCA.Common.UI/DependencyInjection.cs:28, :36-178). In order it binds and validates on start ApiSettings, so a missing endpoint fails the host rather than the first request (:39-42; the read-only face of those options is IApiSettings, whose WasmApiEndpoint lets the server call an internal URL while @@ -890,10 +1012,12 @@

                  15. Common culture delegating handlers and the named "APIClient" whose base address comes from ApiSettings and whose timeout is pinned to HttpResilienceDefaults.TotalRequestTimeout - rather than the BCL's arbitrary 100s, so the transport never pre-empts the resilience budget - (:92-117); calls AddCommonUiFacades() for the toast and dialog pair (:121); then TryAdds - AuthUIService, the email-confirmation client (:128), the - OAuthFlowStateStore (:132), + rather than the BCL's arbitrary 100s, so the transport never pre-empts the resilience budget + (:92-125; when Api:SameOriginApiEndpoint is configured the base address is that proxy endpoint + and SameOriginProxyRequestHandler joins as the innermost handler, + :96-133); calls AddCommonUiFacades() for the toast and dialog pair (:137); then TryAdds + AuthUIService, the email-confirmation client (:144), the + OAuthFlowStateStore (:148), the two list-page state services, NavigationHistoryService, ThemeService, EndpointCultureApplier, @@ -906,11 +1030,11 @@

                  15. Common that downstream apps override with ConfigurationOAuthUISettings, which reads provider availability from the OAuth section for a server host and from pre-computed Enabled flags for a WASM client - (:124-158, MMCA.Common.UI/Services/Auth/OAuth/ConfigurationOAuthUISettings.cs:13, :24-30); and finally - calls AddDeviceCapabilityDefaults() so every capability contract resolves on every head (:162, + (:139-174, MMCA.Common.UI/Services/Auth/OAuth/ConfigurationOAuthUISettings.cs:13, :24-30); and finally + calls AddDeviceCapabilityDefaults() so every capability contract resolves on every head (:178, ADR-042, chapter 26). The TryAdd* discipline is what lets a consumer pre-register its own implementation and win. Browser - hosts add AddClientAuthSessionCookieSync() (:193-196) and AddWasmFormFactor() (:205-206); a + hosts add AddClientAuthSessionCookieSync() (:209-211) and AddWasmFormFactor() (:221); a Blazor Server head adds AddCommonServerTokenStorage(), AddCommonBlazorCsp() (before AddCommonSecurityHeaders, so it beats the TryAdded static provider) and AddCommonWebFormFactor() from MMCA.Common.UI.Web @@ -918,10 +1042,10 @@

                  15. Common UseAuthenticatedNoStore() middleware. The administration screens are opt-in on the same pattern: AddUserAdministrationUI<TUserDto>() registers the generic service and forwards the non-generic actions contract to that same instance, so the roster and the actions it performs go through one - object and one substitute in a test (MMCA.Common.UI/DependencyInjection.cs:222-229), and - AddRoleAdministrationUI() registers the role client (:248-250); nothing in the framework calls + object and one substitute in a test (MMCA.Common.UI/DependencyInjection.cs:238-245), and + AddRoleAdministrationUI() registers the role client (:264-266); nothing in the framework calls either, so an app serving no administration endpoints registers nothing and the components are - simply never rendered (:216-217, :243-244). An + simply never rendered. An SSR host behind the gateway also calls AddTrustedCallerHeader(configuration), which composes TrustedCallerHandler (MMCA.Common.UI.Web/Security/TrustedCallerHandler.cs:35) onto every HttpClient the host creates, @@ -934,30 +1058,32 @@

                  15. Common scheme, host and port match the gateway origin and replacing rather than appending the header, because the gateway compares one single-valued header in constant time (TrustedCallerHandler.cs:9-27, :67-79). UISharedAssemblyReference - (MMCA.Common.UI/DependencyInjection.cs:288) is the marker other assemblies scan against.

                  + (MMCA.Common.UI/DependencyInjection.cs:304) is the marker other assemblies scan against.

                  How a WebAssembly client learns its API address. The WASM bundle is static, so the API base address is fetched at runtime rather than baked in. On the server side, ClientConfigEndpointExtensions - (MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs:15) adds + (MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs:17) adds MapClientConfigEndpoint, an anonymous GET /client-config excluded from the OpenAPI description - whose framework-owned Api section carries Api:WasmApiEndpoint (:18, :21, :50-79). It + whose framework-owned Api section carries Api:WasmApiEndpoint (:17, :52-81). It fails closed: with that key missing it throws rather than fall back to Api:ApiEndpoint, which is the server head's service-discovery name a browser cannot resolve, so the client fails to start - instead of pointing at the wrong API (:32-38, :53-59). The endpoint declares AllowAnonymous + instead of pointing at the wrong API (:32-38, :53-59). When the host runs the same-origin proxy, + the same api section also carries sameOriginApiEndpoint, the proxy prefix with a trailing slash, + which is what moves the client's "APIClient" onto it (:84-99). The endpoint declares AllowAnonymous itself, because the client fetches the document before it can hold a session and a host's fallback - policy would otherwise gate it (:40-44, :78). A host adds its own sections through a per-request + policy would otherwise gate it (:40-44, :80). A host adds its own sections through a per-request ClientConfigBuilder (MMCA.Common.UI.Web/ClientConfig/ClientConfigBuilder.cs:17), whose Add refuses a blank name, the reserved Api section and a duplicate, and whose remarks warn that everything added is served to every browser before sign-in, so only public values belong there (:12-16, :45-62). The client half is MmcaClientConfigBootstrap - (MMCA.Common.UI/Common/Settings/MmcaClientConfigBootstrap.cs:24), whose LoadAsync fetches the + (MMCA.Common.UI/Common/Settings/MmcaClientConfigBootstrap.cs:28), whose LoadAsync fetches the document from the app's own origin with a 15-second per-attempt timeout and a single retry after one second (for the cold-start case where the Server host is still warming), then rethrows a second failure on purpose, and returns a buffered stream because browser fetch streams do not support the synchronous reads AddJsonStream performs (:12-22, :30, :33, :44-85). Only the timeout it did not ask for is retried, never the caller's own cancellation (:87-90). ADC wires both halves - (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:247, + (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:259, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Program.cs:31).

                  The small Level-0 supporting cast fills in the rest: NotificationRoutePaths (MMCA.Common.UI/Common/NotificationRoutePaths.cs:8), whose deep-link builder formats invariantly @@ -1014,7 +1140,7 @@

                  IAppDialogService

                • Depends on: nothing first-party (the file carries no using directives at all). Implemented by MudAppDialogService over MudBlazor's IDialogService.
                • Concept introduced, the vendor-neutral UI facade. [Rubric §32, Dependency & Supply-Chain] assesses whether a third-party dependency is contained behind your own contract or spread across call sites; [Rubric §14, Testability] assesses whether a unit of behavior can be exercised without its infrastructure; [Rubric §1, SOLID] covers the dependency-inversion half of the same idea. The framework applies all three the same way twice: this interface and its sibling IToastService are the only shapes pages depend on, and their two implementations are the only types in the framework that name MudBlazor's IDialogService / ISnackbar (MudAppDialogService at MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/MudAppDialogService.cs:11, doc comment at :6-10). The payoff is concrete: a bUnit test answers a confirmation prompt with a stub instead of rendering, driving and dismissing a real dialog. The doc comment (IAppDialogService.cs:3-13) also states the deliberate scope limit: only the yes/no shape is abstracted, and richer entity-specific dialogs (DeleteConfirmation) stay component-side rather than growing this contract.
                • Walkthrough: one member. ConfirmAsync(string title, string message, string confirmText, string cancelText) returns Task<bool> (IAppDialogService.cs:26). Two contract details are stated in the XML doc and honored by the implementation. First, every string parameter is documented as "already-localized" (:21-24): the facade never touches IStringLocalizer, the caller resolves its own copy, which is what keeps the resource key next to the page that owns it (ADR-027). Second, dismissing the dialog without choosing counts as declining (:17-19), so a caller only ever has to branch on true. MudAppDialogService implements exactly that by collapsing MudBlazor's tri-state answer with return confirmed is true; (MudAppDialogService.cs:25), because ShowMessageBoxAsync answers null for a backdrop click or an escape key press (MudAppDialogService.cs:16-18).
                • -
                • Why it's built this way: a four-string method with a bool answer is the smallest contract that covers every destructive-action prompt in the framework, and keeping it that small is what makes the vendor genuinely swappable: the whole surface an alternative renderer must satisfy is one method. It is registered by AddCommonUiFacades() alongside the toast facade (DependencyInjection, MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:184), scoped to match the MudBlazor services it wraps. See ADR-067, whose 2026-08-29 revision records the vendor choice and these two facades, and whose 2026-08-31 revision records their move into their own registration call.
                • +
                • Why it's built this way: a four-string method with a bool answer is the smallest contract that covers every destructive-action prompt in the framework, and keeping it that small is what makes the vendor genuinely swappable: the whole surface an alternative renderer must satisfy is one method. It is registered by AddCommonUiFacades() alongside the toast facade (DependencyInjection, MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:200), scoped to match the MudBlazor services it wraps. See ADR-067, whose 2026-08-29 revision records the vendor choice and these two facades, and whose 2026-08-31 revision records their move into their own registration call.
                • Where it's used: injected by the shared framework surfaces that ask before doing something lossy: DataGridListPageBase<TDto>, the notification list, send and inbox pages, ListPageActions, the signed-in-devices page (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Sessions.razor.cs), and the UnsavedChangesGuard component. Registered for component tests by the shipped bUnit base's Services.AddCommonUiFacades() call (MMCA.Common/Source/Hosting/MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:53), which uses TryAdd semantics so a test that wants a recording double registers one afterwards (BunitComponentTestBase.cs:50-52).

                @@ -1028,7 +1154,7 @@

                IHomePageContent

              • Concept introduced, late-bound content injection into a packaged shell. [Rubric §18, UI Architecture & Component Design] assesses whether shared UI infrastructure adapts to per-app content without duplication. The shared package owns the route: Home.razor declares @page "/" once (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Home.razor:1), injects IEnumerable<IHomePageContent> (Home.razor:3), and renders <DynamicComponent Type="_contentType" /> when a provider resolved (Home.razor:8-11). Because the component arrives as a runtime Type rather than a compile-time reference, the framework package renders an app's landing page without referencing the app. When no implementation is registered the page falls back to a localized welcome panel (Home.razor:12-21), so a brand-new host still renders something coherent.
              • Walkthrough: two read-only members. ComponentType (IHomePageContent.cs:11) is the System.Type of the Razor component to render as the home-page body. PageTitle (IHomePageContent.cs:14) is the browser-tab title, bound by Home.razor:6.
              • Why it's built this way: an inverted dependency (the app registers into the framework, never the reverse) is what lets the whole shell ship as a NuGet package. Compare the sibling mechanism in IUIModule: both hand the framework a Type or an Assembly and let reflection do the binding, and both exist for the same reason (ADR-067).
              • -
              • Where it's used: implemented once per app and registered once per head. ADC registers ADCHomePageContent as a singleton in all three heads (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:92, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Program.cs:45, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/MauiProgram.cs:124), with two separate implementations, one for the web heads (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Pages/ADCHomePageContent.cs:11) and one for MAUI (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/Pages/ADCHomePageContent.cs:8). Store does the same with StoreHomePageContent (MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:101, MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web.Client/Program.cs:41, MMCA.Store/Source/Hosts/UI/MMCA.Store.UI/MauiProgram.cs:76).
              • +
              • Where it's used: implemented once per app and registered once per head. ADC registers ADCHomePageContent as a singleton in all three heads (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:93, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Program.cs:45, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/MauiProgram.cs:124), with two separate implementations, one for the web heads (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Pages/ADCHomePageContent.cs:11) and one for MAUI (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/Pages/ADCHomePageContent.cs:8). Store does the same with StoreHomePageContent (MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:101, MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web.Client/Program.cs:41, MMCA.Store/Source/Hosts/UI/MMCA.Store.UI/MauiProgram.cs:76).
              • Caveats / not-in-source: the injection point is IEnumerable<IHomePageContent>, so several registrations do not fail; which one wins is decided by Home.razor's selection code (Home.razor:23 onward), not by this interface. Every current host registers exactly one.

              @@ -1080,7 +1206,7 @@

              RoutePaths

          • Why it's built this way: static readonly rather than const is sufficient because these strings are consumed in navigation and Href expressions, not in attribute arguments. That has one consequence worth knowing: a @page directive still needs its own literal, so Home.razor:1 writes @page "/" directly and this constant covers only the linking and navigating side. Sessions shows the cost of the convention: the route literal appears in the page's own @page directive and again here, and only the tests hold the two together.
          • -
          • Where it's used: RoutePaths.Home backs the navbar brand link (MMCA.Common/Source/Presentation/MMCA.Common.UI/Layout/NavMenu.razor:18), the Home nav link (NavMenu.razor:58), and the first breadcrumb of the sessions page (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Sessions.razor.cs:65). RoutePaths.Sessions backs the authenticated-section nav link (NavMenu.razor:142) and is asserted by name in both repos' component tests: MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Layout/NavMenuTests.cs:76 pins that the link renders inside .nav-auth-section, :75 pins its position, :88 pins that it is absent for an anonymous user, and MMCA.Store/Tests/Modules/Identity/MMCA.Store.Identity.UI.Tests/Pages/Profile/ProfileTests.cs:67 pins that the profile page links to it.
          • +
          • Where it's used: RoutePaths.Home backs the navbar brand link (MMCA.Common/Source/Presentation/MMCA.Common.UI/Layout/NavMenu.razor:18), the Home nav link (NavMenu.razor:58), and the first breadcrumb of the sessions page (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Sessions.razor.cs:66). RoutePaths.Sessions backs the authenticated-section nav link (NavMenu.razor:142) and is asserted by name in both repos' component tests: MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Layout/NavMenuTests.cs:76 pins that the link renders inside .nav-auth-section, :75 pins its position, :88 pins that it is absent for an anonymous user, and MMCA.Store/Tests/Modules/Identity/MMCA.Store.Identity.UI.Tests/Pages/Profile/ProfileTests.cs:67 pins that the profile page links to it.
          • Caveats / not-in-source: Sessions is still listed in PublicAPI.Unshipped.txt (MMCA.Common/Source/Presentation/MMCA.Common.UI/PublicAPI.Unshipped.txt:339) while Home is baselined in PublicAPI.Shipped.txt:916, so the two members sit at different points in the public-API baseline cycle.

          @@ -1094,21 +1220,21 @@

          ToastSeverity

        • Concept reinforced, keeping the vendor enum out of call sites. The facade idea is taught at IAppDialogService; this enum is the part of it that is easy to skip. A contract that abstracts the snackbar but takes MudBlazor.Severity as a parameter has not abstracted anything, because every caller still references the vendor assembly. The doc comment says exactly that (IToastService.cs:3-7): the five levels mirror what every component library exposes, so a host maps them one-to-one without losing anything, and "naming them here is what keeps the vendor's own severity enum out of page code". [Rubric §32, Dependency & Supply-Chain] and [Rubric §9, API & Contract Design] both apply: the mapping to MudBlazor.Severity lives in one private method inside MudToastService, so swapping renderers is a change to one switch.
        • Walkthrough: five explicitly numbered members, each documented by what it means for the user rather than by color. Normal = 0 (IToastService.cs:11), neutral with no color emphasis. Info = 1 (:14), something happened that the user did not ask for. Success = 2 (:17), the action the user asked for completed. Warning = 3 (:20), completed partially or with something worth knowing. Error = 4 (:23), the action failed. The explicit values keep the enum stable if members are ever reordered.
        • Why it's built this way: five levels rather than four, because Normal (an uncolored toast) is a distinct affordance from Info, and not more, because there is no shape beyond these that the framework raises. Info is the default parameter value on both ShowPersistent and ShowAction (:72, :100), which is the neutral choice for an unprompted message.
        • -
        • Where it's used: the severity parameter of IToastService.Show, ShowPersistent and ShowAction; the severity parameter of ResultUiExtensions.NotifyOnFailure, where it defaults to ToastSeverity.Error (MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/ResultUiExtensions.cs:269); and MudToastService, the one type that maps it to MudBlazor.Severity (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/MudToastService.cs:27).
        • +
        • Where it's used: the severity parameter of IToastService.Show, ShowPersistent and ShowAction; the severity parameter of ResultUiExtensions.NotifyOnFailure, where it defaults to ToastSeverity.Error (MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/ResultUiExtensions.cs:272); and MudToastService, the one type that maps it to MudBlazor.Severity (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/MudToastService.cs:27).

        UISharedAssemblyReference

        -

        MMCA.Common.UI · MMCA.Common.UI · MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:288 · Level 0 · class

        +

        MMCA.Common.UI · MMCA.Common.UI · MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:304 · Level 0 · class

        • What it is: an empty marker class whose only job is to give code a compile-checked typeof(...).Assembly handle on the shared UI assembly.
        • Depends on: nothing.
        • Concept introduced, the assembly-marker type. Reflection over an assembly needs an Assembly instance, and there are two ways to get one: a string (Assembly.Load("MMCA.Common.UI"), which fails at run time when someone renames the project) or a type reference (typeof(UISharedAssemblyReference).Assembly, which fails at compile time and is carried along by a rename refactoring). Every layer of the framework ships an equivalent marker; this is the UI layer's. [Rubric §15, Best Practices & Code Quality] assesses exactly this kind of refactor-safety over stringly-typed lookups.
        • -
        • Walkthrough: a single declaration using the semicolon type body, public class UISharedAssemblyReference; (DependencyInjection.cs:288), with its doc comment on line 217. It shares a file with DependencyInjection but is declared at namespace scope beneath it, outside that static class, because a type nested inside a static class could not serve as a public marker the same way. It carries no members, so nothing can accidentally depend on state it does not have.
        • +
        • Walkthrough: a single declaration using the semicolon type body, public class UISharedAssemblyReference; (DependencyInjection.cs:304), with its doc comment on line 217. It shares a file with DependencyInjection but is declared at namespace scope beneath it, outside that static class, because a type nested inside a static class could not serve as a public marker the same way. It carries no members, so nothing can accidentally depend on state it does not have.
        • Why it's built this way: type-only, public and empty is the whole point. It is the assembly's identity expressed as a symbol the compiler tracks.
        • Where it's used: the architecture fitness suite is the real consumer. CommonArchitectureMap registers the assembly as the framework's UI layer with Framework(Layer.Ui, typeof(Common.UI.UISharedAssemblyReference).Assembly) (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/CommonArchitectureMap.cs:27), which is what lets the shared layer-dependency rules know which assembly is the UI layer; AnonymousEndpointTests includes it in the assemblies it scans (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Api/AnonymousEndpointTests.cs:19); and NavigationContractTests enumerates its types with typeof(UI.UISharedAssemblyReference).Assembly.GetTypes() (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Ui/NavigationContractTests.cs:105). [Rubric §34, Architecture Governance & Documentation] applies: this one-line type is what makes a layer boundary machine-checkable.
        • -
        • Caveats / not-in-source: the doc comment (DependencyInjection.cs:287) offers "e.g., for Scrutor scanning" as the motivating case, but no Scrutor registration in this repo takes its scan root from this marker. AddUIModule<TModule>() scans FromAssemblyOf<TModule>() (DependencyInjection.cs:277), taking the root from the module descriptor's own assembly instead. Trust the call sites: the current consumers are the architecture tests.
        • +
        • Caveats / not-in-source: the doc comment (DependencyInjection.cs:303) offers "e.g., for Scrutor scanning" as the motivating case, but no Scrutor registration in this repo takes its scan root from this marker. AddUIModule<TModule>() scans FromAssemblyOf<TModule>() (DependencyInjection.cs:293), taking the root from the module descriptor's own assembly instead. Trust the call sites: the current consumers are the architecture tests.

        IToastService

        @@ -1126,7 +1252,7 @@

        IToastService

      • ShowAction(string message, string actionText, Func<Task> onAction, ToastSeverity severity = ToastSeverity.Info, bool requireInteraction = false) (:96-101) is the undo / view-it / retry shape a bare message cannot express. Two contract details are documented rather than enforced. First, the callback runs outside any render callback, so nothing catches what it throws: a caller whose work can fail must guard it and raise its own failure toast (:78-81, restated at :86-88). Second, requireInteraction: true pins the toast open until the user dismisses it or takes the action, and the MudBlazor implementation additionally renders it filled, following the same emphasis convention ShowPersistent uses, "because a toast that waits for the user has to look like it is waiting" (:90-95).
      -
    1367. Why it's built this way: a small, void-returning, already-localized contract is what allows the vendor to appear in exactly one class. It is registered scoped by AddCommonUiFacades() (DependencyInjection, MMCA.Common.UI/DependencyInjection.cs:183) to match the lifetime of the MudBlazor ISnackbar it wraps. See ADR-067.
    1368. +
    1369. Why it's built this way: a small, void-returning, already-localized contract is what allows the vendor to appear in exactly one class. It is registered scoped by AddCommonUiFacades() (DependencyInjection, MMCA.Common.UI/DependencyInjection.cs:199) to match the lifetime of the MudBlazor ISnackbar it wraps. See ADR-067.
    1370. Where it's used: essentially everywhere a page reports an outcome. Inside the framework package: DataGridListPageBase<TDto>, MobileInfiniteScrollList<TItem>, ListPageActions, the three notification pages, the sessions page, and the UnsavedChangesGuard, SharePageButton, ApiFileDownloadButton and NotificationListener components. Outside it, every consumer page reaches it indirectly through ResultUiExtensions.NotifyOnFailure. Component tests resolve it from the shipped bUnit base (BunitComponentTestBase.cs:53, whose comment at :50-51 records that without it a consumer's component test fails to resolve IToastService and each repo ends up re-registering the same pair).

    1371. @@ -1168,7 +1294,7 @@

      IUIModule

    1372. Why it's built this way: the two default interface members are what keep the simple case simple. A module that only contributes navigation implements two properties, not four, and can gain app-bar or layout contributions later without a breaking change to anything already written. Passing an Assembly rather than a list of page types keeps route discovery reflective, so adding a page is never a framework edit.
    1373. -
    1374. Where it's used: implemented by module descriptors across the workspace: the framework's own NotificationUIModule (MMCA.Common/Source/Presentation/MMCA.Common.UI/Notifications/NotificationUIModule.cs:15), ADC's ConferenceUIModule (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/ConferenceUIModule.cs:14), EngagementUIModule (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/EngagementUIModule.cs:17), IdentityUIModule (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/IdentityUIModule.cs:15) and the MAUI-head-only DeviceUIModule (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/DeviceUIModule.cs:18), plus Store's CatalogUIModule (MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.UI/CatalogUIModule.cs:13), SalesUIModule (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.UI/SalesUIModule.cs:16), IdentityUIModule (MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.UI/IdentityUIModule.cs:13) and MauiUIModule (MMCA.Store/Source/Hosts/UI/MMCA.Store.UI/MauiUIModule.cs:14). The optional members earn their keep in practice: NotificationUIModule contributes NotificationBell to the app bar and NotificationListener to the layout (NotificationUIModule.cs:23-25), Store's SalesUIModule contributes CartButton plus CartDrawer and OrphanOrderRecovery (SalesUIModule.cs:30-32), ADC's EngagementUIModule contributes LiveEventListener (EngagementUIModule.cs:31), and ADC's DeviceUIModule contributes five headless native listeners at once (DeviceUIModule.cs:33). Registration goes through AddUIModule<TModule>() (see DependencyInjection), which each module's own one-line Add{Module}UI() delegates to, for example MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.UI/DependencyInjection.cs:19. A stub implementation drives the menu tests (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Layout/NavMenuTests.cs:290) and another drives the backend-less component gallery (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Gallery/Stubs/GalleryUIModule.cs:14).
    1375. +
    1376. Where it's used: implemented by module descriptors across the workspace: the framework's own NotificationUIModule (MMCA.Common/Source/Presentation/MMCA.Common.UI/Notifications/NotificationUIModule.cs:15), ADC's ConferenceUIModule (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/ConferenceUIModule.cs:14), EngagementUIModule (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/EngagementUIModule.cs:17), IdentityUIModule (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/IdentityUIModule.cs:15) and the MAUI-head-only DeviceUIModule (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/DeviceUIModule.cs:18), plus Store's CatalogUIModule (MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.UI/CatalogUIModule.cs:13), SalesUIModule (MMCA.Store/Source/Modules/Sales/MMCA.Store.Sales.UI/SalesUIModule.cs:16), IdentityUIModule (MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.UI/IdentityUIModule.cs:13) and MauiUIModule (MMCA.Store/Source/Hosts/UI/MMCA.Store.UI/MauiUIModule.cs:14). The optional members earn their keep in practice: NotificationUIModule contributes NotificationBell to the app bar and NotificationListener to the layout (NotificationUIModule.cs:23-25), Store's SalesUIModule contributes CartButton plus CartDrawer and OrphanOrderRecovery (SalesUIModule.cs:30-32), ADC's EngagementUIModule contributes LiveEventListener (EngagementUIModule.cs:31), and ADC's DeviceUIModule contributes five headless native listeners at once (DeviceUIModule.cs:33). Registration goes through AddUIModule<TModule>() (see DependencyInjection), which each module's own one-line Add{Module}UI() delegates to, for example MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.UI/DependencyInjection.cs:19. A stub implementation drives the menu tests (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Layout/NavMenuTests.cs:308) and another drives the backend-less component gallery (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Gallery/Stubs/GalleryUIModule.cs:14).

    1377. IEntityService<TEntityDTO, TIdentifierType>

      @@ -1189,7 +1315,7 @@

      IEntityService<TEntityDTO,
    1378. Why it's built this way: an interface keeps Blazor components testable (mock the contract, no HTTP) and hides the API URL structure behind a typed surface. The generic-over-DTO shape is the client mirror of the server's generic controller layer (ADR-034), which is why one base class implements it for every entity in the system. The uniform Result return is deliberate rather than convenient: mixing nullable returns for reads with bool for writes forces each call site to invent its own error story, whereas returning Result everywhere means one small set of helpers covers all seven members.
    1379. -
    1380. Where it's used: implemented for every entity by EntityServiceBase<TEntityDTO, TIdentifierType> (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:43-47, which also composes an optional IUiReadCache) and consumed by every module CRUD page, most of them through DataGridListPageBase<TDto>. Registration is automatic: AddUIModule<TModule>() runs a Scrutor scan over the module's assembly that picks up every class assignable to IEntityService<,> and registers it scoped as its implemented interfaces (DependencyInjection, MMCA.Common.UI/DependencyInjection.cs:276-280). The (Items, TotalItems) shape of GetPagedAsync is also the shape MobileInfiniteScrollList<TItem>'s page-fetch delegate expects.
    1381. +
    1382. Where it's used: implemented for every entity by EntityServiceBase<TEntityDTO, TIdentifierType> (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:43-47, which also composes an optional IUiReadCache) and consumed by every module CRUD page, most of them through DataGridListPageBase<TDto>. Registration is automatic: AddUIModule<TModule>() runs a Scrutor scan over the module's assembly that picks up every class assignable to IEntityService<,> and registers it scoped as its implemented interfaces (DependencyInjection, MMCA.Common.UI/DependencyInjection.cs:292-296). The (Items, TotalItems) shape of GetPagedAsync is also the shape MobileInfiniteScrollList<TItem>'s page-fetch delegate expects.

    1383. ResultUiExtensions

      @@ -1199,7 +1325,7 @@

      ResultUiExtensions

      • What it is: the page-side half of the Result transport. It is the four things a Blazor page ever does with a failed Result, written once so no page hand-rolls them again: unwrap the value, push the message into an inline alert, raise it as a toast, or branch on why it failed.

      • -
      • Depends on: Result, Error, ErrorType and ErrorTypeSeverity from MMCA.Common.Shared.Abstractions (ResultUiExtensions.cs:3); IToastService and ToastSeverity (:4); Microsoft.Extensions.Localization.IStringLocalizer (:2); System.Diagnostics.CodeAnalysis.NotNullWhenAttribute (:1).

        +
      • Depends on: Result, Error, ErrorType and ErrorTypeSeverity from MMCA.Common.Shared.Abstractions (ResultUiExtensions.cs:3); IToastService and ToastSeverity (:4); Microsoft.Extensions.Localization.IStringLocalizer (:2); System.Diagnostics.CodeAnalysis.NotNullWhenAttribute and SuppressMessageAttribute (:1).

      • Concept introduced, localize-with-pass-through, and severity-ordered deduplication. [Rubric §24, Forms, Validation & UX Safety] assesses whether a failure reaches the user as one clear, actionable sentence; [Rubric §27, Internationalization] assesses whether that sentence follows the active culture; [Rubric §15, Best Practices & Code Quality] assesses whether the same five lines get re-typed per page. Two mechanisms carry all three.

          @@ -1212,9 +1338,9 @@

          ResultUiExtensions

          • Unwrapping. TryGetValue<T>(this Result<T>, [NotNullWhen(true)] out T? value) (:82-97) reads like Dictionary.TryGetValue so the success and failure branches sit side by side. The implementation carries a subtle correctness note in its comment (:86-88): the failure branch is decided by result.IsFailure, not by whether the value is null, because for a value type (a (Items, TotalItems) tuple, an int count) default is never null and a null test alone would report every failure as a success. The three-argument overload (:116-133) hands the errors back on the failing branch and documents one edge honestly (:102-109): a success carrying a null value also takes the failing branch, and a success has no errors, so errors comes back empty. The framework's own services never produce that shape (a 2xx with no value fails with Http.EmptyResponse), but a caller switching on the error list should not assume it is non-empty.
          • Composing the message. LocalizedErrorMessages(this Result, IStringLocalizer?) (:145-159) returns an empty list for a success, so a caller can bind it without a null or success check, and otherwise orders by ErrorTypeSeverity.Rank descending, localizes, drops blanks, and takes ordinal-distinct values (:154-158). LocalizedErrorMessage (:169-173) joins that list with a space and returns null for a success. LocalizeDistinct(IEnumerable<string>?, IStringLocalizer?) (:185-197) gives the same treatment to a plain message list, specifically the MudForm.Errors shape whose entries are resource keys produced by the model's DataAnnotations; it preserves original order rather than re-ranking, since those entries carry no ErrorType.
          • -
          • Rendering. OnFailureSetError(this Result, Action<string?> setError, IStringLocalizer?) (:226-233) hands the composed message to the page's own error field, the one an inline MudAlert or the PageErrorState component renders (its doc comment names it by its current home, MMCA.Common.UI.Components.PageState.PageErrorState, at :200-202, matching the file at MMCA.Common/Source/Presentation/MMCA.Common.UI/Components/PageState/PageErrorState.razor:1), and clears it on success by passing null (:231), which is why a retry that succeeds does not leave a stale alert on screen. NotifyOnFailure(this Result, IToastService, IStringLocalizer?, ToastSeverity = Error) (:265-281) raises the composed message as one toast, never one per error, calling toast.Show(message, severity) only when the composed message is non-null (:274-278). Both return the same result instance so the call can sit inline, and both have a Result<T> overload that delegates to the non-generic one and returns the typed result (:237-241, :285-293), which is what keeps (await Service.AddAsync(dto, token)).NotifyOnFailure(Toast, L) chainable.
          • -
          • Branching on why. HasErrorType(this Result, ErrorType) (:303-307) is the general predicate; the doc comment (:295-299) notes the category survives the HTTP round trip through ProblemDetailsResultReader, which is what makes this meaningful client-side at all. IsNotFound() (:315) and IsUnauthorized() (:323) are the two named cases, and their doc comments state the intended UI reaction: a 404 becomes a "not found" state rather than an error alert, a 401 becomes a redirect to the login route.
          • -
          • The private helper. Localize(string message, IStringLocalizer?) (:325-334) is where pass-through actually happens: a null localizer or a blank message returns the input unchanged, otherwise it indexes the localizer and returns localized.ResourceNotFound ? message : localized.Value (:333).
          • +
          • Rendering. OnFailureSetError(this Result, Action<string?> setError, IStringLocalizer?) (:227-234) hands the composed message to the page's own error field, the one an inline MudAlert or the PageErrorState component renders (its doc comment names it by its current home, MMCA.Common.UI.Components.PageState.PageErrorState, at :200-202, matching the file at MMCA.Common/Source/Presentation/MMCA.Common.UI/Components/PageState/PageErrorState.razor:1), and clears it on success by passing null (:232), which is why a retry that succeeds does not leave a stale alert on screen. NotifyOnFailure(this Result, IToastService, IStringLocalizer?, ToastSeverity = Error) (:268-284) raises the composed message as one toast, never one per error, calling toast.Show(message, severity) only when the composed message is non-null (:278-281). Both return the same result instance so the call can sit inline, and both have a Result<T> overload that delegates to the non-generic one and returns the typed result (:239-243, :289-297), which is what keeps (await Service.AddAsync(dto, token)).NotifyOnFailure(Toast, L) chainable. All four rendering overloads carry the same [SuppressMessage("ApiDesign", "RS0026...")] (:226, :238, :267, :288): two public overloads with optional parameters is the shape RS0026 forbids, and the justification records them as grandfathered, released after the v1.152 public-API baseline while RS0026/RS0027 were off, so changing the signatures now would be a breaking change.
          • +
          • Branching on why. HasErrorType(this Result, ErrorType) (:307-311) is the general predicate; the doc comment (:299-302) notes the category survives the HTTP round trip through ProblemDetailsResultReader, which is what makes this meaningful client-side at all. IsNotFound() (:319) and IsUnauthorized() (:327) are the two named cases, and their doc comments state the intended UI reaction: a 404 becomes a "not found" state rather than an error alert, a 401 becomes a redirect to the login route.
          • +
          • The private helper. Localize(string message, IStringLocalizer?) (:329-338) is where pass-through actually happens: a null localizer or a blank message returns the input unchanged, otherwise it indexes the localizer and returns localized.ResourceNotFound ? message : localized.Value (:337).
        • Why it's built this way: extension methods on Result rather than an injectable service, because there is no state and nothing to resolve, so a page uses them without a constructor parameter and a unit test calls them directly. Every rendering helper returns the result it was given, which is what allows the fluent one-liner style the class doc advertises. The nullable IStringLocalizer? parameter everywhere means the helpers work from a context that has no localizer (they then render verbatim) rather than forcing one in.

          @@ -1241,7 +1367,7 @@

          NotificationRoutePaths

      • Why it's built this way: kept separate from RoutePaths so an app that never enables the notification module carries no irrelevant constants, and so notification routes evolve on their own. string.Create with an explicit culture, rather than plain string interpolation, is the analyzer-friendly way to state "this formatting is deliberate" in a repo where every analyzer is an error.
      • -
      • Where it's used: the notification surfaces navigate by these constants rather than by literals: NotificationSend.razor.cs:62 (its breadcrumb back to the list), :117 (post-send navigation) and :136 (the cancel path), NotificationList.razor.cs:77 (navigate to send), and NotificationBell.razor.cs:238 (NavigateToInbox). NotificationUIModule builds its two NavItem entries from NotificationInbox and Notifications (NotificationUIModule.cs:19-20). ADC's AppActionRouteMapTests asserts that a push action resolves to NotificationRoutePaths.NotificationInbox (MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.UI.Tests/Services/AppActionRouteMapTests.cs:39).
      • +
      • Where it's used: the notification surfaces navigate by these constants rather than by literals: NotificationSend.razor.cs:62 (its breadcrumb back to the list), :117 (post-send navigation) and :136 (the cancel path), NotificationList.razor.cs:77 (navigate to send), and NotificationBell.razor.cs:249 (NavigateToInbox). NotificationUIModule builds its two NavItem entries from NotificationInbox and Notifications (NotificationUIModule.cs:19-20). ADC's AppActionRouteMapTests asserts that a push action resolves to NotificationRoutePaths.NotificationInbox (MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.UI.Tests/Services/AppActionRouteMapTests.cs:39).
      • Caveats / not-in-source: NotificationInboxItem has no call site in any of the four repos as of this source; it is listed in PublicAPI.Unshipped.txt (MMCA.Common/Source/Presentation/MMCA.Common.UI/PublicAPI.Unshipped.txt:315, recorded there with its resolved signature NotificationInboxItem(int id)), while the three string constants are baselined in PublicAPI.Shipped.txt:913-915. The {Id:int} route it targets is live; the typed builder for it is shipped but not yet adopted.

      @@ -1251,29 +1377,29 @@

      DependencyInjection

      • What it is: the composition root of the UI layer. One AddUIShared(configuration) call wires the shared UI infrastructure every head needs (Blazor Server, WebAssembly, MAUI), and six smaller methods cover the per-head, per-module and per-opt-in registrations.
      • -
      • Depends on: nearly the whole group. Settings: ApiSettings, LayoutSettings, UiReadCacheOptions, NotificationBellOptions. Caching: IUiReadCache / UiReadCache. Localization: PseudoStringLocalizerFactory, ResxMudLocalizer, InvariantMudLocalizationInterceptor. HTTP: AuthDelegatingHandler, CultureDelegatingHandler, HttpResilienceDefaults. Facades: IToastService / MudToastService, IAppDialogService / MudAppDialogService. Services: IAuthUIService, IEmailConfirmationUIService / EmailConfirmationUIService, OAuthFlowStateStore, ListPageStateService, ListPageQueryStateService, NavigationHistoryService, ThemeService, ICultureApplier / EndpointCultureApplier, IPublicLinkBuilder / NavigationPublicLinkBuilder, IUserPreferenceWriter, IUserPreferenceReader, IOAuthUISettings / DefaultOAuthUISettings, ISessionCookieSync / JsFetchSessionCookieSync. Administration opt-ins: IUserAdminUIService<TUserDto> / UserAdminService<TUserDto>, IUserAdminActionsUIService, IRoleAdminUIService / RoleAdminService. Capabilities: IFormFactor / WasmFormFactor. Composition: IUIModule, IEntityService<TEntityDTO, TIdentifierType>. Externals: Scrutor (Decorate, Scan), MudBlazor, and Microsoft.Extensions.{Configuration, DependencyInjection, Localization, Options} (DependencyInjection.cs:1-17).
      • +
      • Depends on: nearly the whole group. Settings: ApiSettings, LayoutSettings, UiReadCacheOptions, NotificationBellOptions. Caching: IUiReadCache / UiReadCache. Localization: PseudoStringLocalizerFactory, ResxMudLocalizer, InvariantMudLocalizationInterceptor. HTTP: AuthDelegatingHandler, CultureDelegatingHandler, SameOriginProxyRequestHandler, HttpResilienceDefaults. Facades: IToastService / MudToastService, IAppDialogService / MudAppDialogService. Services: IAuthUIService, IEmailConfirmationUIService / EmailConfirmationUIService, OAuthFlowStateStore, ListPageStateService, ListPageQueryStateService, NavigationHistoryService, ThemeService, ICultureApplier / EndpointCultureApplier, IPublicLinkBuilder / NavigationPublicLinkBuilder, IUserPreferenceWriter, IUserPreferenceReader, IOAuthUISettings / DefaultOAuthUISettings, ISessionCookieSync / JsFetchSessionCookieSync. Administration opt-ins: IUserAdminUIService<TUserDto> / UserAdminService<TUserDto>, IUserAdminActionsUIService, IRoleAdminUIService / RoleAdminService. Capabilities: IFormFactor / WasmFormFactor. Composition: IUIModule, IEntityService<TEntityDTO, TIdentifierType>. Externals: Scrutor (Decorate, Scan), MudBlazor, and Microsoft.Extensions.{Configuration, DependencyInjection, Localization, Options} (DependencyInjection.cs:1-20).
      • Concept introduced, the composition root written as an extension(T) block. The whole registration surface lives inside extension(IServiceCollection services) (DependencyInjection.cs:30) rather than as classic this-parameter extension methods; see primer, C# extension(T) types for the language mechanics, taught once. [Rubric §15, Best Practices & Code Quality] assesses one consistent idiom across layers, and this file matches the other DependencyInjection classes in every layer of the workspace. [Rubric §33, Developer Experience] assesses fail-fast startup and a small number of calls per host. [Rubric §12, Performance & Scalability] assesses whether concerns are wired once, centrally: localization, culture forwarding, authentication, resilience and caching are all configured here rather than per page.
      • Walkthrough: seven methods in the extension block.
          -
        • AddUIShared(IConfiguration configuration) (:36-165), in order:
            +
          • AddUIShared(IConfiguration configuration) (:36-181), in order:
            • Options. ApiSettings binds with .ValidateDataAnnotations().ValidateOnStart() (:39-42), so a missing ApiEndpoint fails the host at startup rather than at the first HTTP call. LayoutSettings binds without validation because empty defaults are acceptable (:45-46). UiReadCacheOptions (:50-51) and NotificationBellOptions (:53-54) bind the client-side staleness policy; the comment (:48-49) records that both sections are optional and an absent section leaves the compiled-in defaults, which is what a host gets without configuring anything.
            • Clock and read cache. TryAddSingleton(TimeProvider.System) (:58), with a comment explaining both directions of the TryAdd (:56-57): a host that already registered one, as AddInfrastructure does, keeps it, and a test substitutes a FakeTimeProvider. TryAddScoped<IUiReadCache, UiReadCache>() (:63) is scoped so it is per-circuit on Blazor Server; the comment (:60-62) records the consequence on the other heads, where the scope is the app lifetime, which is why the sign-out path clears it explicitly, otherwise one account's reads would outlive its session [Rubric §26, Front-End Security].
            • Localization (ADR-027). AddLocalization() (:66) for IStringLocalizer<T>, then Decorate<IStringLocalizerFactory, PseudoStringLocalizerFactory>() (:73), registered unconditionally because the pseudo-locale transform is inert under every other culture and the pseudo locale is only ever activatable in Development (:68-72). TryAddTransient<MudBlazor.MudLocalizer, ResxMudLocalizer>() (:79) localizes MudBlazor's own component text; the comment (:75-78) records why TryAdd is authoritative regardless of host registration order, namely that AddMudServices registers no MudLocalizer of its own and a DI resolution test guards that assumption. AddLocalizationInterceptor<InvariantMudLocalizationInterceptor>() (:88) follows immediately: MudBlazor's own default interceptor reads its built-in English strings by assigning CultureInfo.CurrentUICulture (invariant, then the previous value back), which writes an AsyncLocal culture into the calling context; on a MAUI hybrid head, where that is the main thread, nothing resets it and the app pins to its launch language even after a culture switch reloads the thread defaults. InvariantMudLocalizationInterceptor reads the same resource under an explicit invariant culture instead, and registers with replace semantics so it wins whether AddMudServices ran before or after this call (ADR-027 Decision 10) (:81-87).
            • -
            • The one HTTP client. Both delegating handlers register transient (:92-93), then the named "APIClient" (:96-117). Its factory resolves IOptions<ApiSettings> and sets client.BaseAddress = new Uri(apiSettings.ApiEndpoint!, UriKind.Absolute) (:103-106). There is deliberately no hand-written endpoint guard, and the comment says why (:98-102): resolving .Value runs the ValidateDataAnnotations rules registered above, so a missing [Required] endpoint already fails as an OptionsValidationException, and a second check would only give the same failure a different, less informative exception. client.Timeout is pinned to HttpResilienceDefaults.TotalRequestTimeout (:112) because the BCL's own 100-second default was chosen with no knowledge of the resilience budget and would cut a call off mid-policy at an arbitrary point (:108-111) [Rubric §29, Resilience & Business Continuity]. Default headers are cleared and Accept: application/json added (:113-114), and the two handlers chain in order (:116-117) so every outgoing call carries both the bearer token and the active UI culture as Accept-Language.
            • -
            • Facades. services.AddCommonUiFacades() (:121), factored out so a bUnit harness can register exactly these two without pulling in the whole shared-UI surface (:119-120).
            • -
            • Scoped services, all via TryAdd so several composing hosts cannot double-register: IAuthUIService (:124), IEmailConfirmationUIService to EmailConfirmationUIService (:128, the /confirm-email page's client per ADR-116; a separate interface rather than new IAuthUIService members, so a consumer's own IAuthUIService implementation keeps compiling, per the comment at :126-127), OAuthFlowStateStore (:132, binding an OAuth completion to the flow this client started so a deep-linked completion code from someone else's provider round trip is dropped instead of exchanged, per its own comment at :130-131), ListPageStateService (:133), ListPageQueryStateService (:134), NavigationHistoryService (:135), ThemeService (:138, ADR-028), ICultureApplier defaulting to EndpointCultureApplier (:144), IPublicLinkBuilder defaulting to NavigationPublicLinkBuilder (:150), and the per-user preference writer and reader (:153-154), documented as best-effort and a no-op for an anonymous user (:152). TryAddSingleton<IOAuthUISettings, DefaultOAuthUISettings>() (:158) supplies a no-op default that a downstream app replaces.
            • -
            • Capabilities. AddDeviceCapabilityDefaults() (:162, ADR-042) so every capability contract resolves on every head, registered here specifically so MAUI and browser hosts can override afterwards under last-registration-wins (:160-161).
            • +
            • The one HTTP client. Both delegating handlers register transient (:92-93). Before the client itself, usesSameOriginProxy is computed from the raw configuration (:98-99): it is true only when Api:SameOriginApiEndpoint carries a value, which per the comment (:95-97) happens only on a WebAssembly client whose Server host opted into the same-origin API proxy (ADR-131) and handed that value down through /client-config; every other host (Server, MAUI, a WASM client of a host that did not opt in) keeps the pipeline unchanged. Then the named "APIClient" (:102-125), whose builder is kept in apiClient (:102) so a handler can be appended afterwards. Its factory resolves IOptions<ApiSettings> and sets client.BaseAddress = new Uri(apiSettings.SameOriginApiEndpoint ?? apiSettings.ApiEndpoint!, UriKind.Absolute) (:109-114): the same-origin proxy base, when present, replaces the gateway URL for data calls, and the comment (:112-113) notes the bootstrap has already resolved it to an absolute address. There is deliberately no hand-written endpoint guard, and the comment says why (:104-108): resolving .Value runs the ValidateDataAnnotations rules registered above, so a missing [Required] endpoint already fails as an OptionsValidationException, and a second check would only give the same failure a different, less informative exception. client.Timeout is pinned to HttpResilienceDefaults.TotalRequestTimeout (:120) because the BCL's own 100-second default was chosen with no knowledge of the resilience budget and would cut a call off mid-policy at an arbitrary point (:116-119) [Rubric §29, Resilience & Business Continuity]. Default headers are cleared and Accept: application/json added (:121-122), and the two handlers chain in order (:124-125) so every outgoing call carries both the bearer token and the active UI culture as Accept-Language. When usesSameOriginProxy is true, SameOriginProxyRequestHandler is registered transient and appended as the innermost handler (:127-133); the comment (:129-130) gives the reason for that position: it must see, and strip, every Authorization header the outer handlers or a service's DefaultRequestHeaders attached, and it stamps the proxy's CSRF header [Rubric §26, Front-End Security].
            • +
            • Facades. services.AddCommonUiFacades() (:137), factored out so a bUnit harness can register exactly these two without pulling in the whole shared-UI surface (:135-136).
            • +
            • Scoped services, all via TryAdd so several composing hosts cannot double-register: IAuthUIService (:140), IEmailConfirmationUIService to EmailConfirmationUIService (:144, the /confirm-email page's client per ADR-116; a separate interface rather than new IAuthUIService members, so a consumer's own IAuthUIService implementation keeps compiling, per the comment at :142-143), OAuthFlowStateStore (:148, binding an OAuth completion to the flow this client started so a deep-linked completion code from someone else's provider round trip is dropped instead of exchanged, per its own comment at :146-147), ListPageStateService (:149), ListPageQueryStateService (:150), NavigationHistoryService (:151), ThemeService (:154, ADR-028), ICultureApplier defaulting to EndpointCultureApplier (:160), IPublicLinkBuilder defaulting to NavigationPublicLinkBuilder (:166), and the per-user preference writer and reader (:169-170), documented as best-effort and a no-op for an anonymous user (:168). TryAddSingleton<IOAuthUISettings, DefaultOAuthUISettings>() (:174) supplies a no-op default that a downstream app replaces.
            • +
            • Capabilities. AddDeviceCapabilityDefaults() (:178, ADR-042) so every capability contract resolves on every head, registered here specifically so MAUI and browser hosts can override afterwards under last-registration-wins (:176-177).
          • -
          • AddCommonUiFacades() (:181-186): two TryAddScoped calls, IToastService to MudToastService (:183) and IAppDialogService to MudAppDialogService (:184). Its doc comment (:167-180) is the clearest statement of the facade rule anywhere in the codebase: these two implementations are the ONLY types in the framework that name MudBlazor's ISnackbar / IDialogService, they are scoped to match the MudBlazor services they wrap, and the method is called both by AddUIShared and by the shipped bUnit base so a component test resolves the facades without the rest of the shared-UI surface.
          • -
          • AddClientAuthSessionCookieSync() (:193-197): one TryAddScoped<ISessionCookieSync, JsFetchSessionCookieSync>() (:195), the bridge that mirrors the client's in-memory tokens into the HttpOnly cookie read during server-side SSR prerender. Called from both the Blazor Server host and the WebAssembly client (:188-192).
          • -
          • AddWasmFormFactor() (:205-206): registers IFormFactor to WasmFormFactor as a singleton. The doc comment names the two alternatives (:199-204): AddCommonWebFormFactor() from MMCA.Common.UI.Web on the Blazor Server head, AddMauiFormFactor() from MMCA.Common.UI.Maui on the MAUI head.
          • -
          • AddUserAdministrationUI<TUserDto>() (:222-232), the ADR-116 UI opt-in for user administration: registers IUserAdminUIService<TUserDto> to UserAdminService<TUserDto> (:224), then forwards IUserAdminActionsUIService to the same instance rather than registering it a second time (:228-229), so the actions the component performs and the page it lists go through one instance (and one substitute, in a test), per the comment at :226-227. The doc comment (:208-221) states the call convention (once per app, after AddUIShared, with the app's own administration DTO) and that an app which serves no administration endpoints registers nothing and the component the service backs is simply never rendered.
          • -
          • AddRoleAdministrationUI() (:248-253): the non-generic ADR-116 sibling, registering IRoleAdminUIService to RoleAdminService (:250). Its doc comment (:234-247) explains why it takes no type parameter unlike AddUserAdministrationUI<TUserDto>(): roles and permissions are strings the framework already owns, so there is no app DTO to name.
          • -
          • AddUIModule<TModule>() (:273-283), constrained to TModule : class, IUIModule (:274): a Scrutor scan FromAssemblyOf<TModule>() registering every IEntityService<TEntityDTO, TIdentifierType> implementation scoped as its implemented interfaces (:276-280), then AddSingleton<IUIModule, TModule>() (:282). The doc comment (:262-267) records the deliberate boundary: this is the two-step prologue every module's Add{Module}UI() opens with, and module-specific services stay with the caller afterwards, so a module whose service must beat a shared default still controls its own registration order. The type-parameter doc (:269-272) states the constraint that follows from using the descriptor's assembly as the scan root: it must live alongside the module's entity services and Razor pages.
          • +
          • AddCommonUiFacades() (:197-202): two TryAddScoped calls, IToastService to MudToastService (:199) and IAppDialogService to MudAppDialogService (:200). Its doc comment (:183-196) is the clearest statement of the facade rule anywhere in the codebase: these two implementations are the ONLY types in the framework that name MudBlazor's ISnackbar / IDialogService, they are scoped to match the MudBlazor services they wrap, and the method is called both by AddUIShared and by the shipped bUnit base so a component test resolves the facades without the rest of the shared-UI surface.
          • +
          • AddClientAuthSessionCookieSync() (:209-213): one TryAddScoped<ISessionCookieSync, JsFetchSessionCookieSync>() (:211), the bridge that mirrors the client's in-memory tokens into the HttpOnly cookie read during server-side SSR prerender. Called from both the Blazor Server host and the WebAssembly client (:204-208).
          • +
          • AddWasmFormFactor() (:221-222): registers IFormFactor to WasmFormFactor as a singleton. The doc comment names the two alternatives (:215-220): AddCommonWebFormFactor() from MMCA.Common.UI.Web on the Blazor Server head, AddMauiFormFactor() from MMCA.Common.UI.Maui on the MAUI head.
          • +
          • AddUserAdministrationUI<TUserDto>() (:238-248), the ADR-116 UI opt-in for user administration: registers IUserAdminUIService<TUserDto> to UserAdminService<TUserDto> (:240), then forwards IUserAdminActionsUIService to the same instance rather than registering it a second time (:244-245), so the actions the component performs and the page it lists go through one instance (and one substitute, in a test), per the comment at :242-243. The doc comment (:224-237) states the call convention (once per app, after AddUIShared, with the app's own administration DTO) and that an app which serves no administration endpoints registers nothing and the component the service backs is simply never rendered.
          • +
          • AddRoleAdministrationUI() (:264-269): the non-generic ADR-116 sibling, registering IRoleAdminUIService to RoleAdminService (:266). Its doc comment (:250-263) explains why it takes no type parameter unlike AddUserAdministrationUI<TUserDto>(): roles and permissions are strings the framework already owns, so there is no app DTO to name.
          • +
          • AddUIModule<TModule>() (:289-299), constrained to TModule : class, IUIModule (:290): a Scrutor scan FromAssemblyOf<TModule>() registering every IEntityService<TEntityDTO, TIdentifierType> implementation scoped as its implemented interfaces (:292-296), then AddSingleton<IUIModule, TModule>() (:298). The doc comment (:278-283) records the deliberate boundary: this is the two-step prologue every module's Add{Module}UI() opens with, and module-specific services stay with the caller afterwards, so a module whose service must beat a shared default still controls its own registration order. The type-parameter doc (:285-288) states the constraint that follows from using the descriptor's assembly as the scan root: it must live alongside the module's entity services and Razor pages.
        • -
        • Why it's built this way: TryAdd throughout is both a safety property (several composing hosts calling AddUIShared cannot double-register) and the override mechanism (a host that registers its own implementation before the call wins). Two ordering choices push in the opposite direction and are called out in comments because they are load-bearing: ICultureApplier's default round-trips a server /culture/set endpoint that a MAUI hybrid head does not have, so hybrids override it after AddUIShared (:140-143), and IPublicLinkBuilder's default resolves against the browser origin, which is wrong for a MAUI WebView whose origin is a virtual host nobody else can open, so that is overridden after as well (:146-149). InvariantMudLocalizationInterceptor follows the same head-dependent pattern one layer down: MudBlazor's own interceptor is correct everywhere except the MAUI hybrid main thread, so the replacement is registered unconditionally here rather than left to the affected head to override, because replace semantics make the registration order irrelevant (:81-88). Read together, the file encodes a rule worth carrying into any new registration: a contract whose correct implementation depends on the head is defaulted here and replaced later (or, where replace semantics make order irrelevant, registered outright), while a contract that is the same everywhere is TryAdded and left alone. The two administration opt-ins follow the same principle one level up: nothing else in the framework calls either one, so an app that serves no administration or role-administration endpoints registers nothing and the corresponding component is simply never rendered.
        • -
        • Where it's used: called once at startup by all six consuming UI hosts (ADC's MMCA.ADC.UI.Web, MMCA.ADC.UI.Web.Client and MAUI MMCA.ADC.UI, plus the three Store equivalents), each followed by the per-module Add{Module}UI() calls, which are usually one-liners over AddUIModule<TModule>(), for example MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.UI/DependencyInjection.cs:19 and MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/DependencyInjection.cs:24. AddCommonUiFacades() has a second caller outside any host, the shipped bUnit base (MMCA.Common/Source/Hosting/MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:53). AddUserAdministrationUI<TUserDto>() and AddRoleAdministrationUI() are each meant to be called once per app, after AddUIShared, by an app that opts into the corresponding ADR-116 administration surface (:212, :239); nothing inside the framework itself calls either one. The "APIClient" configured here is the client every EntityServiceBase<TEntityDTO, TIdentifierType>-derived service resolves. The assembly this class lives in is also the one UISharedAssemblyReference names for the architecture fitness suite.
        • +
        • Why it's built this way: TryAdd throughout is both a safety property (several composing hosts calling AddUIShared cannot double-register) and the override mechanism (a host that registers its own implementation before the call wins). Two ordering choices push in the opposite direction and are called out in comments because they are load-bearing: ICultureApplier's default round-trips a server /culture/set endpoint that a MAUI hybrid head does not have, so hybrids override it after AddUIShared (:156-159), and IPublicLinkBuilder's default resolves against the browser origin, which is wrong for a MAUI WebView whose origin is a virtual host nobody else can open, so that is overridden after as well (:162-165). InvariantMudLocalizationInterceptor follows the same head-dependent pattern one layer down: MudBlazor's own interceptor is correct everywhere except the MAUI hybrid main thread, so the replacement is registered unconditionally here rather than left to the affected head to override, because replace semantics make the registration order irrelevant (:81-88). SameOriginProxyRequestHandler is the one registration decided by configuration rather than by order: it is added only when Api:SameOriginApiEndpoint is present (:98-99, :127-133), so a host that never opts in carries no trace of it in its pipeline. Read together, the file encodes a rule worth carrying into any new registration: a contract whose correct implementation depends on the head is defaulted here and replaced later (or, where replace semantics make order irrelevant, registered outright), while a contract that is the same everywhere is TryAdded and left alone. The two administration opt-ins follow the same principle one level up: nothing else in the framework calls either one, so an app that serves no administration or role-administration endpoints registers nothing and the corresponding component is simply never rendered.
        • +
        • Where it's used: called once at startup by all six consuming UI hosts (ADC's MMCA.ADC.UI.Web, MMCA.ADC.UI.Web.Client and MAUI MMCA.ADC.UI, plus the three Store equivalents), each followed by the per-module Add{Module}UI() calls, which are usually one-liners over AddUIModule<TModule>(), for example MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.UI/DependencyInjection.cs:19 and MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/DependencyInjection.cs:24. AddCommonUiFacades() has a second caller outside any host, the shipped bUnit base (MMCA.Common/Source/Hosting/MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:53). AddUserAdministrationUI<TUserDto>() and AddRoleAdministrationUI() are each meant to be called once per app, after AddUIShared, by an app that opts into the corresponding ADR-116 administration surface (:228, :255); nothing inside the framework itself calls either one. The "APIClient" configured here is the client every EntityServiceBase<TEntityDTO, TIdentifierType>-derived service resolves. The assembly this class lives in is also the one UISharedAssemblyReference names for the architecture fitness suite.

        ApiFileDownloadButton

        @@ -1286,10 +1412,10 @@

        ApiFileDownloadButton

      • Walkthrough:
        • Parameters (lines 17-80). Three are [EditorRequired]: RelativeApiPath (line 19), FileName (line 30) and ShareTitle (line 35). ContentType defaults to application/octet-stream (line 43) and is what the share sheet uses to pick target apps. Icon defaults to the generic download glyph (line 47), Size to Size.Small (line 51). AriaLabel, UnavailableMessage and FailureMessage (lines 59, 66, 73) are nullable overrides over localized defaults. HttpClientName defaults to "APIClient" (line 80), the framework's bearer-token plus culture-header client.
        • AccessibleLabel (line 84) resolves AriaLabel ?? L["Button.Download.Aria"].Value, and the markup binds it to BOTH aria-label and title on either branch (ApiFileDownloadButton.razor:22-23, :31-32), so an icon-only control always carries a name. The default key lives in the component's own resource file (Components/ApiFileDownloadButton.resx:15).
        • -
        • BrowserDownloadUrl (lines 89-98) is the browser branch's Href. It prefers WasmApiEndpoint over ApiEndpoint (line 93) because the browser needs the externally reachable gateway URL: on the Server head ApiEndpoint may be a container-internal name, and on the WASM head ApiEndpoint is already the browser-reachable value fetched from /client-config (lines 86-88). With no base URL configured it falls back to the relative path (lines 94-95); otherwise it composes an absolute URI (line 96).
        • -
        • ShareDownloadedFileAsync (lines 100-149) is the native branch. It re-entrancy-guards on _isExporting and an empty path (lines 102-105), sanitizes the file name before the fetch so an unusable name costs no download (lines 110-116), creates the named client and pulls the bytes (lines 118-119), stages the file (line 121), and calls Share.ShareFileAsync (line 123), toasting a warning when no share surface accepted it (line 125). Three catch blocks all surface a toast rather than throwing: HttpRequestException (line 128), OperationCanceledException (line 132, which here is the HttpClient timeout, not a disposal, since no token is passed), and a bare Exception (line 138) covering the staging write and the share sheet, because this runs in an OnClick callback where an unhandled exception is fatal to a native host (lines 140-143). The finally clears the guard (line 147).
        • -
        • ResolveStagedFileName (lines 162-171) reduces the caller's name to a bare file name with Path.GetFileName and rejects . and .. (lines 164-170), returning null when nothing usable remains. The remarks (lines 155-161) state the exact hazard: Path.Combine discards its first argument outright when the second is rooted, and .. segments walk out of the temp root, so an unsanitized name would decide where the delete and the write land.
        • -
        • StageFileAsync (lines 180-192) writes into Path.GetTempPath() under the already-sanitized name (line 182), deleting any leftover first (lines 184-187) so a truncated previous copy is never shared. It deliberately does not delete after sharing: on Android the share intent returns as soon as it launches, so deleting would race the receiving app (lines 174-178).
        • +
        • BrowserDownloadUrl (lines 93-103) is the browser branch's Href. The anchor is a plain top-level navigation, so it carries cookies but never an Authorization header (lines 86-92), and the base is chosen in that light as SameOriginApiEndpoint ?? WasmApiEndpoint ?? ApiEndpoint (line 98). On a WebAssembly client of a host running the same-origin API proxy the link targets the proxy, the same base the "APIClient" uses: the browser sends the HttpOnly session cookie and the proxy attaches the real bearer server-side, so an authenticated download works with no token in script (ADR-131). Everywhere else it prefers WasmApiEndpoint over ApiEndpoint because the browser needs the externally reachable gateway URL: on the Server head ApiEndpoint may be a container-internal name, and on the WASM head ApiEndpoint is already the browser-reachable value fetched from /client-config. With no base URL configured it falls back to the relative path (lines 99-100); otherwise it composes an absolute URI (line 101).
        • +
        • ShareDownloadedFileAsync (lines 105-154) is the native branch. It re-entrancy-guards on _isExporting and an empty path (lines 107-110), sanitizes the file name before the fetch so an unusable name costs no download (lines 115-121), creates the named client and pulls the bytes (lines 123-124), stages the file (line 126), and calls Share.ShareFileAsync (line 128), toasting a warning when no share surface accepted it (line 130). Three catch blocks all surface a toast rather than throwing: HttpRequestException (line 133), OperationCanceledException (line 137, which here is the HttpClient timeout, not a disposal, since no token is passed), and a bare Exception (line 143) covering the staging write and the share sheet, because this runs in an OnClick callback where an unhandled exception is fatal to a native host (lines 145-147). The finally clears the guard (line 152).
        • +
        • ResolveStagedFileName (lines 167-176) reduces the caller's name to a bare file name with Path.GetFileName and rejects . and .. (lines 169-175), returning null when nothing usable remains. The remarks (lines 160-166) state the exact hazard: Path.Combine discards its first argument outright when the second is rooted, and .. segments walk out of the temp root, so an unsanitized name would decide where the delete and the write land.
        • +
        • StageFileAsync (lines 185-197) writes into Path.GetTempPath() under the already-sanitized name (line 187), deleting any leftover first (lines 189-192) so a truncated previous copy is never shared. It deliberately does not delete after sharing: on Android the share intent returns as soon as it launches, so deleting would race the receiving app (lines 179-184).
      • Why it's built this way: the download mechanics are the part every consumer would otherwise re-implement per file type, and they are exactly the part that differs per head, so they belong in the framework behind a capability query (ADR-042). Keeping the wording out of the component (labels are parameters with localized fallbacks) is what lets one button serve a calendar file, an export, or a receipt without the framework knowing any of those words.
      • @@ -1307,7 +1433,7 @@

        IApiSettings

      • Walkthrough: string? ApiEndpoint { get; } (line 9) and string? WasmApiEndpoint { get; } (line 17). Both are nullable, because the interface itself imposes no requirement; the [Required] rule lives on the implementation (ApiSettings).
      • Why it's built this way: a read-only interface over an options class is the shape that lets a consumer state "I only read configuration" instead of taking a mutable settings object. It also documents the contract in one place while ApiSettings carries the binding and validation attributes.
      • Where it's used: implemented by ApiSettings (Common/Settings/ApiSettings.cs:9). The two endpoint values are read through IOptions<ApiSettings> at the /client-config endpoints and in the API client factory, not through this interface.
      • -
      • Caveats / not-in-source: no injection site resolves IApiSettings today: a repo-wide search finds the interface only at its declaration and on the ApiSettings class. The doc comment (line 15) says WasmApiEndpoint "falls back to ApiEndpoint when null", but neither host's /client-config endpoint actually does that today: both ADC and Store serve it through the shared MapClientConfigEndpoint (MMCA.Common.UI.Web), which throws an InvalidOperationException naming the missing key when WasmApiEndpoint is unset instead of substituting ApiEndpoint (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs:56-58, called from MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:247 and MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:247). The fail-closed behavior is now framework policy shared by both hosts, not a per-host divergence, and the doc comment's fallback claim is stale.
      • +
      • Caveats / not-in-source: no injection site resolves IApiSettings today: a repo-wide search finds the interface only at its declaration and on the ApiSettings class. The doc comment (line 15) says WasmApiEndpoint "falls back to ApiEndpoint when null", but neither host's /client-config endpoint actually does that today: both ADC and Store serve it through the shared MapClientConfigEndpoint (MMCA.Common.UI.Web), which throws an InvalidOperationException naming the missing key when WasmApiEndpoint is unset instead of substituting ApiEndpoint (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs:58-60, called from MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:259 and MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:247). The fail-closed behavior is now framework policy shared by both hosts, not a per-host divergence, and the doc comment's fallback claim is stale.

      InfiniteScrollSentinel

      @@ -1319,13 +1445,13 @@

      InfiniteScrollSentinel

    1384. Concept introduced, the observer as a child component so disposal is correct. [Rubric §23, Front-End Performance] (assesses render and network cost: paging on demand instead of loading everything, and detaching observers so they stop costing anything), [Rubric §21, Accessibility] (assesses whether dynamic content changes are announced without hijacking focus) and [Rubric §18, UI Architecture]. The design point recorded in the doc comment (lines 14-19) is a lifecycle one: a page deriving from DataGridListPageBase<TDto> cannot hook async disposal because its DisposeAsync is not virtual, whereas a child component is disposed by the renderer the moment the host stops rendering it. Rendering the sentinel only while more pages exist therefore makes "detach the observer" a rendering decision rather than a bookkeeping one, and a filter reset that refills the list gets a fresh instance with a fresh observer.
    1385. Walkthrough:
      • Parameters: OnVisible (line 26), IsLoading (line 29) which renders the inline progress row, and LoadingLabel (line 32), the localized accessible name for that row.
      • -
      • State (lines 34-39): a per-instance _observerId (a Guid "N" string, line 34), the _sentinelRef element reference, the imported _module, the _dotNetRef self-reference handed to JS, plus _observing and _disposed flags.
      • -
      • OnSentinelVisible() (lines 45-47) is the [JSInvokable] callback. Its name is fixed by the shared JS module, which calls it by string (lines 41-44). It returns immediately when disposed, otherwise marshals onto the renderer with InvokeAsync before invoking OnVisible.
      • -
      • OnAfterRenderAsync (lines 50-58) attaches the observer once, on the first render only.
      • -
      • AttachObserverAsync (lines 98-113) imports the module lazily (lines 102-103), creates the DotNetObjectReference (line 104), calls observe with the reference, the element and the id (line 105), and sets _observing. A JSDisconnectedException is swallowed (lines 108-112): during prerendering or circuit teardown there is no JS to talk to, and the list simply stops at the pages already loaded rather than failing the render.
      • -
      • DisposeAsync (lines 61-96) suppresses finalization, guards re-entry with _disposed, calls unobserve when it was observing (lines 76-79), disposes the module (line 81), tolerates both JSDisconnectedException and JSException (lines 84-91), and disposes the DotNetObjectReference in a finally (line 94) so the .NET side is released even if the JS side already went away.
      • +
      • State (lines 34-44): a per-instance _observerId (a Guid "N" string, line 34), the _sentinelRef element reference, the imported _module, the _dotNetRef self-reference handed to JS, the _observing flag, _wasLoading (line 43, the previous render's IsLoading), and _disposed.
      • +
      • OnSentinelVisible() (lines 50-52) is the [JSInvokable] callback. Its name is fixed by the shared JS module, which calls it by string (lines 46-49). It returns immediately when disposed, otherwise marshals onto the renderer with InvokeAsync before invoking OnVisible.
      • +
      • OnAfterRenderAsync (lines 55-66) attaches the observer on the first render, and re-attaches it on the render where a load has just finished: loadJustFinished is _wasLoading && !IsLoading && _observing (line 57), _wasLoading is then updated (line 58), and either condition triggers AttachObserverAsync (line 60). The reason is recorded on the field (lines 40-42): an IntersectionObserver reports threshold crossings only, so a sentinel that is still inside the viewport when the appended page arrives would never fire again and the list would stall until the user scrolled away and back. Re-observing creates a fresh observer, which delivers a fresh initial entry.
      • +
      • AttachObserverAsync (lines 106-121) imports the module lazily (lines 110-111), creates the DotNetObjectReference once (line 112), calls observe with the reference, the element and the id (line 113), and sets _observing (line 114). A JSDisconnectedException is swallowed (lines 116-120): during prerendering or circuit teardown there is no JS to talk to, and the list simply stops at the pages already loaded rather than failing the render.
      • +
      • DisposeAsync (lines 69-104) suppresses finalization, guards re-entry with _disposed, calls unobserve when it was observing (lines 84-87), disposes the module (line 89), tolerates both JSDisconnectedException and JSException (lines 92-99), and disposes the DotNetObjectReference in a finally (line 102) so the .NET side is released even if the JS side already went away.
      • The markup (Components/InfiniteScrollSentinel.razor:4-13) is a single div carrying the element reference, with the progress row rendered only while IsLoading. That row is role="status" aria-live="polite" aria-busy="true" (line 9), matching PageLoadingState's politeness so a screen reader hears that more items are loading without the announcement interrupting reading.
      • -
      • The JS side is deliberately tiny: observe disconnects any prior observer for the id, creates an IntersectionObserver with rootMargin: '200px' and invokes OnSentinelVisible on intersection (wwwroot/infinite-scroll.js:3-14); unobserve disconnects and forgets the id (lines 16-22). The 200px margin is what makes the next page start loading slightly before the sentinel is on screen.
      • +
      • The JS side is deliberately tiny: observe disconnects any prior observer for the id, creates an IntersectionObserver with rootMargin: '200px' and invokes OnSentinelVisible on intersection (wwwroot/infinite-scroll.js:3-14); unobserve disconnects and forgets the id (lines 16-22). The 200px margin is what makes the next page start loading slightly before the sentinel is on screen, and the disconnect-before-create step is what makes the post-load re-attach above safe to call repeatedly with the same id.
    1386. Why it's built this way: extracting just the observer is what lets a page keep its own cards, empty state and error state and still get infinite scroll (lines 10-13). The alternative, folding the behavior into the list component, would force any page that wants infinite scroll to also adopt that component's layout.
    1387. @@ -1336,7 +1462,7 @@

      LayoutSettings

      MMCA.Common.UI · MMCA.Common.UI.Common.Settings · MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/Settings/LayoutSettings.cs:9 · Level 0 · class (sealed)

        -
      • What it is: the four settings that make the shared shell look and behave like a specific application: the navbar brand text, an optional brand logo URL, the footer text, and an optional role gate on the framework-owned "signed-in devices" nav entry.
      • +
      • What it is: the five settings that make the shared shell look and behave like a specific application: the navbar brand text, an optional brand logo URL, the footer text, an optional role gate on the framework-owned "signed-in devices" nav entry, and an opt-in switch that hides the framework's notification pages in a host that never registered the notification UI.
      • Depends on: nothing first-party. System.Diagnostics.CodeAnalysis.SuppressMessage (BCL) for one analyzer waiver, and the Microsoft.Extensions.Options binder at registration time.
      • Concept introduced, a settings section as a bound options class. [Rubric §17, DevOps & Deployment] (assesses whether configuration is centralized and typed rather than read ad hoc) and [Rubric §20, Design System and Theming] (assesses whether the look of the app is expressed once rather than repeated per page). The shape repeats across every settings class in this namespace: a public static readonly string SectionName naming the configuration section (line 12), init-only properties with compiled-in defaults, and one services.AddOptions<T>().Bind(configuration.GetSection(T.SectionName)) call in AddUIShared (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:45-46). Because every property has a default, an absent section is not an error: the host simply gets the compiled-in values. Components then take IOptions<LayoutSettings> and read .Value, so nothing in the shell parses configuration itself.
      • Walkthrough:
          @@ -1345,29 +1471,32 @@

          LayoutSettings

        • FooterText (line 18) defaults to string.Empty, and MainLayout renders the footer block only when it is non-blank (Layout/MainLayout.razor:72-76). An empty default therefore means "no footer", not "an empty footer".
        • BrandLogoUrl (line 30) defaults to empty, which renders the text-only brand. When set, NavMenu emits an img beside the brand text with alt="" and aria-hidden="true" (Layout/NavMenu.razor:22-24): the image is decorative because the brand link already carries its own accessible name, so alt text here would only repeat it to a screen reader (lines 20-24). The property carries a [SuppressMessage] for CA1056 (lines 26-29) whose justification records why it is a string and not a Uri: the value is usually a host-relative path such as /img/logo.svg, which System.Uri cannot represent without RelativeOrAbsolute round-tripping.
        • SessionsNavRequiredRole (line 39) is nullable and unset by default, which shows the "signed-in devices" nav entry to every signed-in user. NavMenu reads it in OnInitializedAsync and computes _showSessionsLink as unset-or-blank OR the current user is in that role (Layout/NavMenu.razor:214-215). The gate covers only the menu entry: /profile/sessions itself stays reachable for any signed-in account whether or not the link is shown (Layout/NavMenu.razor:78-82).
        • +
        • HideNotificationPagesWhenUnregistered (line 49) defaults to false, which leaves the routes exactly as they are. When true, the framework's notification pages (/notifications, /notifications/inbox, /notifications/inbox/{Id} and /notifications/send) answer with the not-found page in a host that never called AddNotificationUI(), instead of being reachable by URL with none of the services they need (lines 41-48). The decision is NotificationPageGate.Hides, which requires the flag, a page type in its notification set, and no registered NotificationUIModule (MMCA.Common/Source/Presentation/MMCA.Common.UI/Notifications/NotificationPageGate.cs:22-25), so a host that registers the notification UI is unaffected either way.
      • -
      • Why it's built this way: the shell ships in the framework package, so the only way a consuming app can brand it, or narrow a framework-owned menu entry, without forking is configuration. Keeping the branding in appsettings.json also means a deployment can rebrand without a rebuild.
      • -
      • Where it's used: injected as IOptions<LayoutSettings> by Layout/NavMenu.razor:12 and Layout/MainLayout.razor:11. Configured by every UI host, for example MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/appsettings.json:15-18. Covered by NavMenuTests.
      • +
      • Why it's built this way: the shell ships in the framework package, so the only way a consuming app can brand it, narrow a framework-owned menu entry, or close framework-owned routes it does not use, without forking is configuration. Keeping the branding in appsettings.json also means a deployment can rebrand without a rebuild.
      • +
      • Where it's used: injected as IOptions<LayoutSettings> by Layout/NavMenu.razor:12 and Layout/MainLayout.razor:11, and read by NotificationPageGate (Notifications/NotificationPageGate.cs:22). Configured by every UI host, for example MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/appsettings.json:15-18. Covered by NavMenuTests and NotificationPageGateTests.

      MmcaClientConfigBootstrap

      -

      MMCA.Common.UI · MMCA.Common.UI.Common.Settings · MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/Settings/MmcaClientConfigBootstrap.cs:24 · Level 0 · class (static)

      +

      MMCA.Common.UI · MMCA.Common.UI.Common.Settings · MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/Settings/MmcaClientConfigBootstrap.cs:28 · Level 2 · class (static)

        -
      • What it is: the WebAssembly client's bootstrap-time fetch of the client-config document (served by ClientConfigEndpointExtensions.MapClientConfigEndpoint) into a stream AddJsonStream can consume, with one automatic retry on failure or timeout.
      • -
      • Depends on: System.Net.Http.HttpClient (BCL) only; no first-party type. That is deliberate, since it runs before builder.Services exists.
      • +
      • What it is: the WebAssembly client's bootstrap-time fetch of the client-config document (served by ClientConfigEndpointExtensions.MapClientConfigEndpoint) into a stream AddJsonStream can consume, with one automatic retry on failure or timeout, and with a same-origin API proxy path resolved to an absolute address on the way through.
      • +
      • Depends on: ApiSettings, used only for its SectionName and the SameOriginApiEndpoint property name when rewriting the document; otherwise System.Net.Http.HttpClient, System.Text.Json.Nodes and System.Text.Encoding (BCL). Nothing it touches needs a service provider, which is deliberate, since it runs before builder.Services exists.
      • Concept introduced, a bounded retry at the one point in startup where configuration itself is still unknown. [Rubric §29, Resilience, Reliability & Business Continuity] (assesses whether a single transient failure at startup is survivable) and [Rubric §17, DevOps & Deployment]. Every other resilience mechanism in the framework (Polly pipelines, the outbox, internal commands) runs once DI and configuration exist; this runs before both, in Program.cs, fetching the very document that will configure the rest of the client. A Polly-based retry is unavailable at this point (no service provider yet), so the retry is hand-rolled: one attempt, then one retry after a fixed delay, with the caller's own HttpClient and its own Timeout.
      • Walkthrough:
          -
        • ClientConfigPath = "client-config" (line 28), the relative path both overloads fetch, matching the endpoint's own route.
        • -
        • DefaultTimeout (line 31, 15 seconds) and DefaultRetryDelay (line 34, 1 second) are the defaults the single-argument overload uses.
        • -
        • LoadAsync(Uri baseAddress, CancellationToken) (lines 45-56) is the convenience overload: it builds a short-lived HttpClient scoped to baseAddress with DefaultTimeout, then delegates to the caller-owned overload.
        • -
        • LoadAsync(HttpClient client, TimeSpan retryDelay, CancellationToken) (lines 68-86) is the real logic: fetch the bytes (line 77), and on HttpRequestException or a caller-uncancelled timeout (IsTimeout, lines 79, 90-91) wait retryDelay then fetch once more (lines 81-82) with no further retry. IsTimeout (lines 90-91) is the detail that makes the retry precise: HttpClient reports its own per-request timeout as TaskCanceledException, the same exception type the caller's own cancellation produces, so the two are told apart by checking whether the caller's token was the one that fired; only a timeout the caller did not ask for is retried.
        • -
        • The result is wrapped in a non-writable MemoryStream (line 85), the shape AddJsonStream expects.
        • +
        • ClientConfigPath = "client-config" (line 31), the relative path both overloads fetch, matching the endpoint's own route.
        • +
        • DefaultTimeout (line 34, 15 seconds) and DefaultRetryDelay (line 37, 1 second) are the defaults the single-argument overload uses.
        • +
        • LoadAsync(Uri baseAddress, CancellationToken) (lines 49-60) is the convenience overload: it builds a short-lived HttpClient scoped to baseAddress with DefaultTimeout, then delegates to the caller-owned overload. It carries an RS0027 [SuppressMessage] (line 48) whose justification records it as a grandfathered public overload whose signature cannot change without a breaking change.
        • +
        • LoadAsync(HttpClient client, TimeSpan retryDelay, CancellationToken) (lines 72-90) is the real logic: fetch the bytes (line 81), and on HttpRequestException or a caller-uncancelled timeout (IsTimeout, lines 83, 142-143) wait retryDelay then fetch once more (lines 85-86) with no further retry. IsTimeout (lines 142-143) is the detail that makes the retry precise: HttpClient reports its own per-request timeout as TaskCanceledException, the same exception type the caller's own cancellation produces, so the two are told apart by checking whether the caller's token was the one that fired; only a timeout the caller did not ask for is retried.
        • +
        • The result passes through ResolveSameOriginApiEndpoint against the client's BaseAddress and is wrapped in a non-writable MemoryStream (line 89), the shape AddJsonStream expects.
        • +
        • ResolveSameOriginApiEndpoint(byte[], Uri?) (lines 98-128, internal) exists because a host running the same-origin API proxy serves Api:SameOriginApiEndpoint as an origin-relative path (the server cannot know the public origin a browser used behind ingress) while the "APIClient" needs an absolute base address (lines 92-97). It returns the document byte for byte unless every precondition holds: an absolute base address (line 100), a document that parses (a JsonException is left for configuration binding to report, lines 110-114), an Api object holding a non-blank string SameOriginApiEndpoint that is not already an http(s) URL (lines 116-124). Only then does it replace the value with new Uri(baseAddress, path).AbsoluteUri and re-serialize (lines 126-127). Parsing is case-insensitive on property names (line 108).
        • +
        • IsHttpAbsolute(string) (lines 136-138, internal) accepts only an absolute http or https URI. A bare absolute-Uri.TryCreate check is not enough: on Unix-like runtimes, browser WebAssembly included, "/api/" parses as the absolute file:///api/, so the relative path would be left unresolved and every client call would go to file:///api/... (lines 130-135).
      • -
      • Why it's built this way: a single flaky fetch (a cold container, a brief network blip during a rollout) would otherwise fail the entire WASM client boot before it can render anything, including an error page with any styling. One retry buys resilience against exactly that class of transient failure without turning a genuine outage into a long hang, since both attempts still respect DefaultTimeout.
      • -
      • Where it's used: MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Program.cs calls LoadAsync to populate the WASM client's configuration before any service is registered. The endpoint it fetches from is ClientConfigEndpointExtensions (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs). Covered by MmcaClientConfigBootstrapTests (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Common/Settings/MmcaClientConfigBootstrapTests.cs).
      • +
      • Why it's built this way: a single flaky fetch (a cold container, a brief network blip during a rollout) would otherwise fail the entire WASM client boot before it can render anything, including an error page with any styling. One retry buys resilience against exactly that class of transient failure without turning a genuine outage into a long hang, since both attempts still respect DefaultTimeout. Resolving the proxy path here rather than on the server keeps the server ignorant of the browser-facing origin, and doing it on the raw document means every later consumer of ApiSettings on the client sees an absolute address (ADR-131).
      • +
      • Where it's used: MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Program.cs calls LoadAsync to populate the WASM client's configuration before any service is registered. The endpoint it fetches from is ClientConfigEndpointExtensions (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs). Covered by MmcaClientConfigBootstrapTests (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Common/Settings/MmcaClientConfigBootstrapTests.cs) and SameOriginProxyClientTests (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/SameOriginProxyClientTests.cs).
      • Caveats / not-in-source: only one retry is ever attempted regardless of how the second attempt fails; a second transient failure fails the boot outright.

      NotificationBellOptions

      @@ -1377,11 +1506,11 @@

      NotificationBellOptions

      • What it is: the two numbers that define how stale the unread-notification badge is allowed to be: how often it re-reads the API, and how old its count may be before a navigation re-reads it.
      • Depends on: nothing first-party. TimeSpan (BCL). Bound with the same options shape LayoutSettings introduces.
      • -
      • Concept introduced, staleness as a stated policy. [Rubric §19, State Management] (assesses whether client-side state has an explicit freshness contract) and [Rubric §31, Cost and FinOps] (assesses whether the design lets an operator trade money against latency). Both numbers are host decisions rather than compiled constants: a deployment paying per API call widens the poll, one where an unread count must feel instant narrows it (lines 6-10). The framing in the doc comment is important for reading the code: the periodic read is the backstop behind the real-time push, not the primary path, so PollInterval is the budget for "how long a missed push may go unnoticed" (lines 17-21).
      • +
      • Concept introduced, staleness as a stated policy. [Rubric §19, State Management] (assesses whether client-side state has an explicit freshness contract) and [Rubric §31, Cost and FinOps] (assesses whether the design lets an operator trade money against latency). Both numbers are host decisions rather than compiled constants: a deployment paying per API call widens the poll, one where an unread count must feel instant narrows it (lines 6-10). The framing in the doc comment is important for reading the code: the periodic read is the backstop behind the real-time push, not the primary path, so PollInterval is the budget for "how long a missed push may go unnoticed" (lines 17-22).
      • Walkthrough:
        • SectionName = "NotificationBell" (line 15).
        • -
        • PollInterval (line 22), default 30 seconds. NotificationBell builds its PeriodicTimer from it (Components/Notifications/NotificationBell.razor.cs:92), against the injected clock rather than the ambient one.
        • -
        • NavigationRefreshMaxAge (line 29), default 30 seconds. On a page change the bell accepts the count it already holds unless it is older than this window (NotificationBell.razor.cs:155, via State.IsStale(...)). That is what keeps a user clicking through five pages in ten seconds from issuing five reads of a number that has not moved (lines 24-28).
        • +
        • PollInterval (line 23), default 30 seconds. NotificationBell builds its PeriodicTimer from it (Components/Notifications/NotificationBell.razor.cs:103), against the injected clock rather than the ambient one. Zero or a negative value disables periodic polling while the push refresh and the navigation refresh keep working (lines 20-21): the bell returns before creating the timer when PollInterval <= TimeSpan.Zero (NotificationBell.razor.cs:96-99), because PeriodicTimer rejects such a period and throwing there faulted the circuit (lines 94-95).
        • +
        • NavigationRefreshMaxAge (line 30), default 30 seconds. On a page change the bell accepts the count it already holds unless it is older than this window (NotificationBell.razor.cs:166, via State.IsStale(...)). That is what keeps a user clicking through five pages in ten seconds from issuing five reads of a number that has not moved (lines 25-29).
      • Why it's built this way: both values are pure policy with no correct universal answer, so they belong in configuration; and because both have defaults, a host that says nothing keeps the framework's chosen 30-second budgets.
      • @@ -1429,7 +1558,7 @@

        UIModuleConfiguration

      • Concept introduced, composing a UI host from configuration. [Rubric §7, Microservices Readiness] (assesses whether the same codebase can be deployed as different subsets) and [Rubric §17, DevOps & Deployment]. The server-side module system registers IModule implementations in topological order; the UI side has its own analogue, IUIModule, registered by each module's AddXUI() extension (ADR-067). This helper is the gate in front of those calls: a host that switches a module off never calls AddXUI(), so no IUIModule descriptor is registered, and the shell composes without that module's routes, nav entries or services. The default-on behavior (lines 7-8) is a compatibility choice: a host with no Modules section behaves exactly as it did before the section existed.
      • Walkthrough: ModulesSectionName = "Modules" (line 12) and IsModuleEnabled(IConfiguration configuration, string moduleName) (lines 18-22). It walks two section levels, Modules then the module name (line 20), and returns !section.Exists() || section.GetValue("Enabled", true) (line 21). Read carefully, that is two independent defaults: an absent module entry is enabled, and a present entry missing the Enabled key is also enabled. Only an explicit false turns a module off.
      • Why it's built this way: a static helper over IConfiguration (rather than a bound options class) is what makes it usable at the exact point it is needed, inside Program.cs/MauiProgram.cs before the service provider exists. Keeping the check in the framework rather than hand-rolling builder.Configuration["Modules:X:Enabled"] per host is what keeps the default-on semantics identical across all six heads.
      • -
      • Where it's used: all six UI hosts gate their module registrations with it. ADC: MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:150-160, MMCA.ADC.UI.Web.Client/Program.cs:55-64, MMCA.ADC.UI/MauiProgram.cs:127-136 (Identity, Conference, Engagement, Notification). Store: MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:120-126, MMCA.Store.UI.Web.Client/Program.cs:51-57, MMCA.Store.UI/MauiProgram.cs:83-89 (Catalog, Sales, Identity). The corresponding configuration block is MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/appsettings.json:9-14.
      • +
      • Where it's used: all six UI hosts gate their module registrations with it. ADC: MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:162-172, MMCA.ADC.UI.Web.Client/Program.cs:55-64, MMCA.ADC.UI/MauiProgram.cs:127-136 (Identity, Conference, Engagement, Notification). Store: MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:120-126, MMCA.Store.UI.Web.Client/Program.cs:51-57, MMCA.Store.UI/MauiProgram.cs:83-89 (Catalog, Sales, Identity). The corresponding configuration block is MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/appsettings.json:9-14.

      UiReadCacheOptions

      @@ -1465,7 +1594,7 @@

      WebApplicationExtensions

    1388. Why it's built this way: an IApplicationBuilder extension is the idiomatic ASP.NET Core registration shape, and the OnStarting hook is what allows a single narrow registration to make an after-the-fact decision (was this response authenticated? was it HTML?) instead of duplicating the check at every page. The remarks (lines 19-23) state the one ordering constraint: register it before MapRazorComponents so it wraps every page response.
    1389. -
    1390. Where it's used: both Blazor Web hosts call it once: MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:227 and MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:171.
    1391. +
    1392. Where it's used: both Blazor Web hosts call it once: MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:239 and MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:171.
    1393. Caveats / not-in-source: whether a given browser honors no-store as bfcache-ineligibility is browser behavior, not code, and cannot be verified from this source. This type is distinct from the same-named WebApplicationExtensions in the API layer; they share a name across assemblies, not an implementation.
    1394. ApiSettings

      @@ -1475,36 +1604,38 @@

      ApiSettings

      • What it is: the bound implementation of IApiSettings: the "Api" configuration section, validated at startup so a host with no API endpoint fails immediately instead of at the first request.
      • Depends on: IApiSettings (the read-only contract it implements) and System.ComponentModel.DataAnnotations.RequiredAttribute (BCL).
      • -
      • Concept introduced, fail-fast configuration. [Rubric §29, Resilience, Reliability & Business Continuity] and [Rubric §15, Best Practices and Code Quality]. The class is three lines of data, but the behavior lives in how it is registered: AddOptions<ApiSettings>().Bind(...).ValidateDataAnnotations().ValidateOnStart() (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:39-42). ValidateDataAnnotations turns the [Required] attribute into an options validator, and ValidateOnStart runs that validator during host startup rather than lazily on first resolution, so a missing Api:ApiEndpoint surfaces as an OptionsValidationException naming the key before the host accepts traffic (ADR-070). That is what licenses the null-forgiving apiSettings.ApiEndpoint! in the client factory (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:106): the validator, not a local check, is the guarantee.
      • +
      • Concept introduced, fail-fast configuration. [Rubric §29, Resilience, Reliability & Business Continuity] and [Rubric §15, Best Practices and Code Quality]. The class is three properties of data, but the behavior lives in how it is registered: AddOptions<ApiSettings>().Bind(...).ValidateDataAnnotations().ValidateOnStart() (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:39-42). ValidateDataAnnotations turns the [Required] attribute into an options validator, and ValidateOnStart runs that validator during host startup rather than lazily on first resolution, so a missing Api:ApiEndpoint surfaces as an OptionsValidationException naming the key before the host accepts traffic (ADR-070). That is what licenses the null-forgiving apiSettings.ApiEndpoint! in the client factory (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:111-114): the validator, not a local check, is the guarantee.
      • Walkthrough:
        • SectionName = "Api" (line 12), the same convention every settings class here uses.
        • [Required] public string? ApiEndpoint { get; init; } (lines 15-16). Nullable so the binder can leave it unset, [Required] so leaving it unset fails validation. init-only, so a bound instance is immutable after construction.
        • WasmApiEndpoint { get; init; } (line 19) carries <inheritdoc /> and no [Required]: it is optional at the contract level, and each host decides whether an absent value is acceptable.
        • +
        • SameOriginApiEndpoint { get; init; } (line 33) is declared on the class only, not on IApiSettings, and is null everywhere except on a WebAssembly client whose Server host opted in with AddCommonSameOriginApiProxy (lines 21-32). The host serves it as an origin-relative path in /client-config and MmcaClientConfigBootstrap.LoadAsync resolves it to an absolute address. When it is set, the "APIClient" and the notification hub target it instead of ApiEndpoint, send no Authorization header (the proxy attaches the bearer from the HttpOnly session cookie server-side) and add the proxy's CSRF header; ApiEndpoint keeps the gateway URL for full-page navigations that must reach the gateway itself, such as the external sign-in challenge (ADR-131). In the client factory the switch is client.BaseAddress = new Uri(apiSettings.SameOriginApiEndpoint ?? apiSettings.ApiEndpoint!, ...) (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:114), and the same configuration key decides at registration time whether the proxy pipeline is wired at all (DependencyInjection.cs:95-99).
      • -
      • Why it's built this way: sealed plus init gives an immutable snapshot of configuration that cannot drift while the app runs. Putting the validation attribute on the options class rather than writing a guard in the HttpClient factory keeps one failure mode with one message: the comment at MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:98-102 records that a second hand-written check would only give the same failure a different, less informative exception.
      • -
      • Where it's used: bound in AddUIShared (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:39-42); read by the named "APIClient" HttpClient factory to set BaseAddress (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:103-106, alongside the 90-second total-request timeout at :97); read by ApiFileDownloadButton for its browser download URL (Components/ApiFileDownloadButton.razor.cs:93); and served to the WebAssembly client by each Server head's /client-config endpoint, both routed through the shared MapClientConfigEndpoint (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs:50-59; called at MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:247-253 and MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:247).
      • +
      • Why it's built this way: sealed plus init gives an immutable snapshot of configuration that cannot drift while the app runs. Putting the validation attribute on the options class rather than writing a guard in the HttpClient factory keeps one failure mode with one message: the comment at MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:104-108 records that a second hand-written check would only give the same failure a different, less informative exception.
      • +
      • Where it's used: bound in AddUIShared (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:39-42); read by the named "APIClient" HttpClient factory to set BaseAddress (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:102-114, alongside the 90-second total-request timeout at :120); read by ApiFileDownloadButton for its browser download URL (Components/Forms/ApiFileDownloadButton.razor.cs:98); read by NotificationHubService and BlazorCspPolicyProvider; and served to the WebAssembly client by each Server head's /client-config endpoint, both routed through the shared MapClientConfigEndpoint (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs:52-61; called at MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:259-265 and MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:247).

      MobileInfiniteScrollList<TItem>

      -

      MMCA.Common.UI · MMCA.Common.UI.Components.Lists · MMCA.Common/Source/Presentation/MMCA.Common.UI/Components/Lists/MobileInfiniteScrollList.razor.cs:21 · Level 3 · class (generic partial component)

      +

      MMCA.Common.UI · MMCA.Common.UI.Components.Lists · MMCA.Common/Source/Presentation/MMCA.Common.UI/Components/Lists/MobileInfiniteScrollList.razor.cs:20 · Level 3 · class (generic partial component)

      • What it is: the mobile card list every list page falls back to on a narrow viewport. It owns the whole loop: an IntersectionObserver sentinel that asks for the next page, an accumulated item list rendered through a caller-supplied card template, a rendered-item cap that bounds DOM growth, generation-guarded supersession of in-flight fetches, and localized load-failure handling with a Retry button.
      • -
      • Depends on: Result and its generic form (the fetch delegate's return type), IToastService, SharedResource through IStringLocalizer<SharedResource>, ResultUiExtensions.LocalizedErrorMessage, the EmptyState component, the ClickableCard component each row's card is wrapped in, and the shared _content/MMCA.Common.UI/infinite-scroll.js module it shares with InfiniteScrollSentinel. Externals: IJSRuntime, DotNetObjectReference, CancellationTokenSource, MudBlazor primitives.
      • -
      • Concept introduced, generation-guarded supersession. [Rubric §19, State Management] (assesses whether concurrent updates to client state have a defined winner) and [Rubric §23, Front-End Performance]. The hard problem in an infinite list is not fetching, it is what happens when the user changes the filter while a fetch is in flight. Cancellation alone is not enough: the fetch delegate is consumer-supplied and may ignore its CancellationToken entirely, so a superseded call can still complete successfully and try to append rows to a list that was cleared. The answer here is a monotonically increasing _generation counter (line 63). A load snapshots it before awaiting and discards its results if the value moved while it waited (lines 174-176, 192). The token cancellation is still issued (it stops work that does honor it), but the generation, not the token, is authoritative (lines 189-191). The second half of the pattern is that the page counter is computed, not committed: targetPage = _currentPage + 1 (line 183) and _currentPage only advances on a successful, non-superseded completion (line 207), so a cancelled, failed or superseded fetch leaves nothing to compensate back and no page is ever re-requested (lines 180-182).
      • +
      • Depends on: Result and its generic form (the fetch delegate's return type), SharedResource through IStringLocalizer<SharedResource>, ResultUiExtensions.LocalizedErrorMessage, the EmptyState component, the ClickableCard component each row's card is wrapped in, and the shared _content/MMCA.Common.UI/infinite-scroll.js module it shares with InfiniteScrollSentinel. Externals: IJSRuntime, DotNetObjectReference, CancellationTokenSource, MudBlazor primitives.
      • +
      • Concept introduced, generation-guarded supersession. [Rubric §19, State Management] (assesses whether concurrent updates to client state have a defined winner) and [Rubric §23, Front-End Performance]. The hard problem in an infinite list is not fetching, it is what happens when the user changes the filter while a fetch is in flight. Cancellation alone is not enough: the fetch delegate is consumer-supplied and may ignore its CancellationToken entirely, so a superseded call can still complete successfully and try to append rows to a list that was cleared. The answer here is a monotonically increasing _generation counter (line 62). A load snapshots it before awaiting and discards its results if the value moved while it waited (lines 181-183, 199). The token cancellation is still issued (it stops work that does honor it), but the generation, not the token, is authoritative (lines 196-198). The second half of the pattern is that the page counter is computed, not committed: targetPage = _currentPage + 1 (line 190) and _currentPage only advances on a successful, non-superseded completion (line 214), so a cancelled, failed or superseded fetch leaves nothing to compensate back and no page is ever re-requested (lines 187-189).
      • Walkthrough:
          -
        • Injected services (lines 22-24) and parameters (lines 26-53). CardTemplate is an [EditorRequired] RenderFragment<TItem> (line 28). FetchPageResult (line 38) is the fetch delegate in the shape every Result-returning UI service already has, (page, pageSize, cancellationToken) returning Result<(IReadOnlyList<TItem> Items, int TotalItems)>, which is IEntityService<TEntityDTO, TIdentifierType>.GetPagedAsync minus the filter and sort arguments. PageSize defaults to 10 (line 40), MaxRenderedItems to 500 (line 53).
        • -
        • State (lines 55-83): _items, _totalCount, _currentPage, _generation, the four render flags (_isInitialLoad, _isLoadingMore, _hasMore, _loadError), _loadErrorMessage, and the interop handles (_sentinelRef, _jsModule, _dotNetRef, _observerId, _cts, _observerAttached, _disposed).
        • -
        • OnInitializedAsync (lines 85-91) validates the delegate then loads page 1. ValidateFetchParameter (lines 98-105) throws an InvalidOperationException when FetchPageResult is null, deliberately before the load, so a misconfigured call site fails loudly instead of rendering as a load failure with a Retry button that can never succeed (lines 93-97).
        • -
        • OnAfterRenderAsync (lines 107-113) attaches the observer only once there are items to scroll past and the initial load is done (line 109). AttachObserverAsync (lines 115-129) and DetachObserverAsync (lines 131-146) import and call the same observe/unobserve module functions the sentinel component uses, tolerating JSDisconnectedException on both paths.
        • -
        • OnSentinelVisible() (lines 148-161), the [JSInvokable] entry point, early-returns when already loading, exhausted or disposed (lines 151-154), then loads on the renderer's context and re-renders.
        • -
        • LoadNextPageAsync(bool isInitial) (lines 163-245) is the core. It guards re-entry (lines 165-168), clears the error state (lines 171-172), snapshots the generation and publishes a fresh CancellationTokenSource (lines 176-178), computes targetPage (line 183), and awaits the delegate (line 187). After the await it checks disposal and generation (line 192), unwraps the Result with TryGetValue and routes a failure to SetLoadFailed (lines 197-203), and only then commits: advance the page, append the items, record the total (lines 207-209), and recompute _hasMore as _items.Count < _totalCount && _items.Count < MaxRenderedItems (line 213), which is where the DOM cap stops the loop. OperationCanceledException is swallowed as a normal supersession (lines 215-218); any other exception raises the generic failure, again only for the current generation (lines 219-227). The finally (lines 228-244) is careful about ownership: only the current generation may clear _isLoadingMore (a superseding reset already cleared it and may have set it again), and only the still-current CancellationTokenSource is disposed here (ReferenceEquals, line 237), because a resetter that took one over already cancelled and disposed it.
        • -
        • SetLoadFailed (lines 258-267) sets the inline error state and, on the initial load only, also raises a toast, because an initial failure renders as an empty state and the toast is otherwise the only signal the user gets (lines 247-251). The message comes from failure?.LocalizedErrorMessage(L) (line 261); a raw exception passes null, because exception text is neither translatable nor safe to surface (lines 253-257), and the generic resource string is used instead.
        • -
        • CardCallback(TItem item) (lines 402-405), declared between SetLoadFailed and RetryAsync, binds one row's click behavior for the markup below. When OnCardClick has a delegate it returns an EventCallback.Factory.Create wrapping the invoke; when it does not, it returns default, deliberately not EventCallback.Empty, because EventCallback.Empty wraps a no-op Action so its HasDelegate is still true and every card would present as interactive. default is the only value ClickableCard reads as "no handler wired", which is what lets it render a plain, non-focusable card instead of a keyboard control that does nothing (lines 395-401).
        • -
        • ResetAsync() (lines 279-315) is the public API a page calls when filters change. Order matters and is commented: bump the generation first so any in-flight fetch is already superseded (line 283), then cancel and dispose the stale token source (lines 285-292), then clear _isLoadingMore explicitly (lines 294-297, because the superseded load will not clear it), then reset the list and every flag (lines 299-305), detach the observer (lines 307-308), and reload from page 1 (lines 312-314).
        • -
        • DisposeAsync (lines 317-349) guards re-entry, cancels and disposes the token source, detaches the observer, disposes the JS module tolerating JSDisconnectedException, and disposes the DotNetObjectReference.
        • -
        • The markup (Components/MobileInfiniteScrollList.razor:1-43) renders one of three shapes: an indeterminate progress bar on the initial load (lines 5-13), EmptyState when the list came back empty (lines 15-17), or a MudStack of ClickableCard wrappers around the caller's template, each keyed by item and wired to CardCallback(item) (lines 21-28). Below the cards it renders the sentinel div only while _hasMore (lines 30-43) and the inline error plus Retry button when a later page failed.
        • +
        • Injected services (lines 22-23: IJSRuntime and the localizer, no toast service) and parameters (lines 25-52). CardTemplate is an [EditorRequired] RenderFragment<TItem> (line 27). FetchPageResult (line 37), also [EditorRequired], is the fetch delegate in the shape every Result-returning UI service already has, (page, pageSize, cancellationToken) returning Result<(IReadOnlyList<TItem> Items, int TotalItems)>, which is IEntityService<TEntityDTO, TIdentifierType>.GetPagedAsync minus the filter and sort arguments. PageSize defaults to 10 (line 39), MaxRenderedItems to 500 (line 52).
        • +
        • State (lines 54-87): _items, _totalCount, _currentPage, _generation, the four render flags (_isInitialLoad, _isLoadingMore, _hasMore, _loadError), _loadErrorMessage, the interop handles (_sentinelRef, _jsModule, _dotNetRef, _observerId, _cts, _observerAttached), _reobservePending (line 86) and _disposed.
        • +
        • OnInitializedAsync (lines 89-95) validates the delegate then loads page 1. ValidateFetchParameter (lines 102-109) throws an InvalidOperationException when FetchPageResult is null, deliberately before the load, so a misconfigured call site fails loudly instead of rendering as a load failure with a Retry button that can never succeed (lines 97-101).
        • +
        • OnAfterRenderAsync (lines 111-120) consumes and clears _reobservePending (lines 113-114), then attaches the observer only while more pages exist, there are items to scroll past and the initial load is done, either when none is attached yet or when a re-observe was queued (line 116). AttachObserverAsync (lines 122-136) and DetachObserverAsync (lines 138-153) import and call the same observe/unobserve module functions the sentinel component uses, tolerating JSDisconnectedException on both paths.
        • +
        • OnSentinelVisible() (lines 155-168), the [JSInvokable] entry point, early-returns when already loading, exhausted or disposed (lines 158-161), then loads on the renderer's context and re-renders.
        • +
        • LoadNextPageAsync(bool isInitial) (lines 170-254) is the core. It guards re-entry (lines 172-175), clears the error state (lines 178-179), snapshots the generation and publishes a fresh CancellationTokenSource (lines 183-185), computes targetPage (line 190), and awaits the delegate (line 194). After the await it checks disposal and generation (line 199), unwraps the Result with TryGetValue and routes a failure to SetLoadFailed (lines 204-210), and only then commits: advance the page, append the items, record the total (lines 214-216), recompute _hasMore as _items.Count < _totalCount && _items.Count < MaxRenderedItems (line 220), which is where the DOM cap stops the loop, and call MarkReobserveAfterAppend(isInitial) (line 222). OperationCanceledException is swallowed as a normal supersession (lines 224-227); any other exception raises the generic failure, again only for the current generation (lines 228-236). The finally (lines 237-253) is careful about ownership: only the current generation may clear _isLoadingMore (a superseding reset already cleared it and may have set it again), and only the still-current CancellationTokenSource is disposed here (ReferenceEquals, line 246), because a resetter that took one over already cancelled and disposed it.
        • +
        • MarkReobserveAfterAppend(bool isInitial) (lines 260-266) sets _reobservePending after a non-initial append that left more pages. The IntersectionObserver reports threshold crossings only, so a sentinel still inside the viewport after the append would never fire again; re-observing creates a fresh observer, which delivers an initial entry with the current state (lines 83-85). This is the same stall InfiniteScrollSentinel guards against with its post-load re-attach.
        • +
        • SetLoadFailed(Result? failure) (lines 278-282) sets the inline error state and nothing else: no toast is raised, because the inline alert is announced on its own, rendered in place of the list for a failed first page or below it for a later page (lines 268-271). The message comes from failure?.LocalizedErrorMessage(L) (line 281); a raw exception passes null, because exception text is neither translatable nor safe to surface (lines 272-276), and the generic resource string is used instead.
        • +
        • CardCallback(TItem item) (lines 291-294), declared between SetLoadFailed and RetryAsync, binds one row's click behavior for the markup below. When OnCardClick has a delegate it returns an EventCallback.Factory.Create wrapping the invoke; when it does not, it returns default, deliberately not EventCallback.Empty, because EventCallback.Empty wraps a no-op Action so its HasDelegate is still true and every card would present as interactive. default is the only value ClickableCard reads as "no handler wired", which is what lets it render a plain, non-focusable card instead of a keyboard control that does nothing (lines 284-290).
        • +
        • ResetAsync() (lines 306-342) is the public API a page calls when filters change. Order matters and is commented: bump the generation first so any in-flight fetch is already superseded (line 310), then cancel and dispose the stale token source (lines 312-319), then clear _isLoadingMore explicitly (lines 321-324, because the superseded load will not clear it), then reset the list and every flag (lines 326-332), detach the observer (lines 334-335), and reload from page 1 (lines 337-341).
        • +
        • DisposeAsync (lines 344-376) guards re-entry, cancels and disposes the token source, detaches the observer, disposes the JS module tolerating JSDisconnectedException, and disposes the DotNetObjectReference.
        • +
        • The markup (Components/Lists/MobileInfiniteScrollList.razor:1-64) renders one of four shapes: an indeterminate progress bar inside a role="status" live region on the initial load (lines 5-14), the load-failure alert when the first page itself failed (lines 15-20, because a failed first page is not an empty list), EmptyState when the list came back empty (lines 21-24), or a MudStack of ClickableCard wrappers around the caller's template, each keyed by item and wired to CardCallback(item) (lines 27-34). Below the cards it renders the sentinel div only while _hasMore, with a polite live-region spinner while a page loads (lines 36-49), and the same alert when a later page failed (lines 51-54). That alert is one RenderFragment, LoadFailedAlert, carrying role="alert" with the message and a Retry button (lines 58-63).
      • Why it's built this way: the component encapsulates the part of infinite scroll that is genuinely hard to get right (supersession, cancellation ownership, disposal, the DOM cap) and leaves the part that is app-specific (what a card looks like, where the data comes from) to parameters. The Result-returning delegate rather than a raw Task<List<T>> is what makes a localized failure message reachable without the component knowing any error catalogue.
      • @@ -1521,10 +1652,11 @@

        MoneyExtensions

      • Concept introduced, formatting lives in the UI layer, not the value object. [Rubric section 3, Clean Architecture] (assesses whether presentation concerns stay out of the inner layers: Money knows amounts and currencies, it does not know what a price looks like) and [Rubric section 20, Design System and Theming] (assesses consistent presentation of a recurring data shape; one formatter means every price on every page reads the same). It also shows the C# extension(T) preview syntax used for something other than DI: two blocks, one on Money and one on IReadOnlyCollection<Money>, sit in a single static class so both spellings (price.ToDisplayString() and prices.ToDisplayRange()) are available from one using.
      • Walkthrough:
        • The class carries a file-level [SuppressMessage("Naming", "CA1708")] (lines 10-13): with two or more extension(T) blocks in one static class, CA1708 flags the compiler-generated grouping members as case-colliding. The justification records that no user-visible identifier differs only by case, a known analyzer trap of the preview syntax.
        • -
        • extension(Money price) (lines 16-21) exposes ToDisplayString(CultureInfo? culture = null) (lines 19-20), which delegates to FormatGroup(price.Amount, price.Amount, price.Currency.Code, culture): passing the same value as both bounds is what makes the shared helper render a single price rather than a degenerate range. Left unset, culture resolves inside FormatGroup to CultureInfo.CurrentCulture, the request's culture as already set by its culture provider; a caller passes one explicitly only when rendering off the reader's thread (a background job, an export, a test).
        • -
        • extension(IReadOnlyCollection<Money> prices) (lines 23-47) exposes ToDisplayRange(CultureInfo? culture = null) (lines 32-46): returns string.Empty for an empty collection (lines 34-37), resolves culture ?? CultureInfo.CurrentCulture once into resolved (line 78 of the diff, ahead of the grouping), then groups by Currency.Code with StringComparer.Ordinal and formats each group from its own min and max under resolved, joining the groups with ", ". Grouping is the load-bearing detail: a mixed-currency collection renders one range per currency, each with its own symbol, instead of collapsing unrelated amounts under whichever currency appeared first. The inline comment notes GroupBy preserves first-appearance order, so the single-currency case (every collection in practice today) is unchanged. Resolving the culture once outside the Select and passing it into every FormatGroup call is what keeps a one-element range and the single price it holds reading identically: the two paths must not drift.
        • -
        • Symbol(string code) (lines 54-59), a private switch mapping "USD" to $ and "EUR" to the escaped euro sign (line 57, escaped to keep the source file ASCII-only). Every other code, including the empty code of the Currency.None sentinel behind Money.Zero() (MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Financial/Currency.cs:23, Money.cs:142), renders with no symbol rather than falsely claiming dollars.
        • -
        • FormatGroup(decimal min, decimal max, string code, CultureInfo? culture) (lines 65-73), the single formatting path: resolves culture ?? CultureInfo.CurrentCulture, then formats with "N2" under that resolved culture so an English reader gets $1,234.56 USD and a Spanish reader gets $1.234,56 USD, a single price when min == max and a hyphen-separated range otherwise, and the trailing code appended only when it is non-empty. Only the digit grouping and decimal separator follow the culture; the currency symbol and the trailing ISO code always come from the money itself, so a USD price stays USD in every locale.
        • +
        • extension(Money price) (lines 16-31) exposes ToDisplayString(CultureInfo? culture = null) (lines 29-30), which delegates to FormatGroup(price.Amount, price.Amount, price.Currency.Code, culture): passing the same value as both bounds is what makes the shared helper render a single price rather than a degenerate range. Left unset, culture resolves inside FormatGroup to CultureInfo.CurrentCulture, the request's culture as already set by its culture provider; a caller passes one explicitly only when rendering off the reader's thread (a background job, an export, a test).
        • +
        • extension(IReadOnlyCollection<Money> prices) (lines 33-64) exposes ToDisplayRange(CultureInfo? culture = null) (lines 47-63): returns string.Empty for an empty collection (lines 49-52), resolves culture ?? CultureInfo.CurrentCulture once into resolved (line 54, ahead of the grouping), then groups by Currency.Code with StringComparer.Ordinal and formats each group from its own min and max under resolved (lines 58-60), joining the groups with ", " (line 62). Grouping is the load-bearing detail: a mixed-currency collection renders one range per currency, each with its own symbol, instead of collapsing unrelated amounts under whichever currency appeared first. The inline comment notes GroupBy preserves first-appearance order, so the single-currency case (every collection in practice today) is unchanged. Resolving the culture once outside the Select and passing it into every FormatGroup call is what keeps a one-element range and the single price it holds reading identically: the two paths must not drift.
        • +
        • Symbol(string code) (lines 71-76), a private switch mapping "USD" to $ and "EUR" to the escaped euro sign (line 74, escaped to keep the source file ASCII-only). Every other code, including the empty code of the Currency.None sentinel behind Money.Zero() (MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Financial/Currency.cs:23, Money.cs:142), renders with no symbol rather than falsely claiming dollars.
        • +
        • FormatGroup(decimal min, decimal max, string code, CultureInfo? culture) (lines 88-97), the single formatting path: resolves culture ?? CultureInfo.CurrentCulture (line 90), then renders a single price when min == max and a hyphen-separated range otherwise, each bound through FormatAmount (lines 92-94), and appends the trailing code only when it is non-empty (line 96). Only the digit grouping and decimal separator follow the culture; the currency symbol and the trailing ISO code always come from the money itself, so a USD price stays USD in every locale.
        • +
        • FormatAmount(decimal amount, string symbol, CultureInfo culture) (lines 103-106) formats one amount with "N2" under the resolved culture, so an English reader gets $1,234.56 and a Spanish reader gets $1.234,56. A negative amount puts the sign before the symbol (-$5.00, not $-5.00) by formatting Math.Abs(amount) behind a leading -, which keeps the culture's digit grouping intact (lines 99-102).
      • Why it's built this way: presentational formatting belongs above the domain, so Money stays display-agnostic and the same value can be rendered differently by a different head. The move from a hardcoded CultureInfo.InvariantCulture to a caller-optional, request-defaulted CultureInfo.CurrentCulture lets the number format follow the reader's culture (a Spanish reader's thousands and decimal separators) while the currency symbol and ISO code stay tied to the money's own currency, so the price never implies a currency the data does not carry. The optional parameter keeps every existing call site source-compatible while opening a path for a background job, an export, or a test to format under an explicit culture instead of the ambient one.
      • @@ -1810,87 +1942,87 @@

        ConfirmationState

      ForgotPasswordModel

      -

      MMCA.Common.UI · MMCA.Common.UI.Pages.Auth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/ForgotPasswordModel.cs:9 · Level 0 · class (sealed)

      +

      MMCA.Common.UI · MMCA.Common.UI.Pages.Auth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/ForgotPasswordModel.cs:13 · Level 0 · class (sealed)

      • What it is: the EditForm backing model for the Forgot Password page: one Email string carrying DataAnnotations for shape validation. Nothing else is collected, because nothing else is needed to start a reset.
      • Depends on: System.ComponentModel.DataAnnotations (BCL): [Required], [EmailAddress]. Nothing first-party.
      • -
      • Concept introduced, validation deliberately capped at "shape" because of an anti-enumeration contract. [Rubric §24, Forms, Validation & UX Safety] assesses whether a form gives a clear per-field verdict before submit; [Rubric §26, Front-End Security] assesses whether the front end avoids leaking information the back end withholds. Every other form in this group validates as much as it can client-side. This one stops at "is this a syntactically valid address", because the interesting question, does an account exist for it, is one the server refuses to answer: ADR-091 Decision 3 has the reset request succeed on every path (malformed address, no account, throttled, failed send), so a distinguishable client-side outcome would reintroduce exactly the account-enumeration oracle the endpoint exists to avoid. The doc comment (ForgotPasswordModel.cs:5-8) states that trade-off directly.
      • -
      • Walkthrough: one get; set; property. Email (line 13) carries [Required(ErrorMessage = "Email is required")] and [EmailAddress(ErrorMessage = "Enter a valid email address")] (lines 11-12) and defaults to string.Empty.
      • +
      • Concept introduced, validation deliberately capped at "shape" because of an anti-enumeration contract. [Rubric §24, Forms, Validation & UX Safety] assesses whether a form gives a clear per-field verdict before submit; [Rubric §26, Front-End Security] assesses whether the front end avoids leaking information the back end withholds. Every other form in this group validates as much as it can client-side. This one stops at "is this a syntactically valid address", because the interesting question, does an account exist for it, is one the server refuses to answer: ADR-091 Decision 3 has the reset request succeed on every path (malformed address, no account, throttled, failed send), so a distinguishable client-side outcome would reintroduce exactly the account-enumeration oracle the endpoint exists to avoid. The doc comment (ForgotPasswordModel.cs:5-12) states that trade-off directly.
      • +
      • Walkthrough: one get; set; property. Email (line 17) carries [Required(ErrorMessage = "Auth.Field.Email.Required")] and [EmailAddress(ErrorMessage = "Auth.Field.Email.Invalid")] (lines 15-16) and defaults to string.Empty. Each ErrorMessage is a resource key, not display text: the doc comment (lines 8-11) names the page's LocalizedDataAnnotationsValidator as the component that resolves it (ADR-027).
      • Why it's built this way: sealed and mutable (set, not init) because EditForm two-way-binds the input to the model; keeping the model to one field is what makes the page's anti-enumeration behavior easy to reason about, since there is no second field whose validation could betray a lookup.
      • -
      • Where it's used: instantiated as _model by ForgotPassword.razor (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/ForgotPassword.razor:66) and bound by its <EditForm Model="_model" OnValidSubmit="HandleRequestAsync"> plus <DataAnnotationsValidator /> (lines 34-35), with the field wired For="@(() => _model.Email)" (line 37) so the message attaches to that input. On valid submit HandleRequestAsync (lines 75-92) calls IAuthUIService.RequestPasswordResetAsync(_model.Email) (line 81, contract at MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/IAuthUIService.cs:57) inside a try whose catch is empty on purpose (lines 83-86) and whose finally sets _isSubmitted = true unconditionally (line 90), so the confirmation renders for every submitted address whether the call succeeded, failed, or threw.
      • -
      • Caveats / not-in-source: RequestPasswordResetAsync returns a Result and the call site never inspects it (line 81); the comment block above the method (lines 70-74) records that this is the anti-enumeration rule rather than a dropped result. The gallery E2E suite pins the behavior by asserting the confirmation appears with no backend at all (MMCA.Common/Tests/Presentation/MMCA.Common.UI.E2E.Tests/Auth/ForgotPasswordPageE2ETests.cs:28), with WCAG 2.1 AA scans on both the form and the confirmation state (:41, :50).
      • +
      • Where it's used: instantiated as _model by ForgotPassword.razor (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/ForgotPassword.razor:71) and bound by its <EditForm Model="_model" OnValidSubmit="HandleRequestAsync"> plus <LocalizedDataAnnotationsValidator /> (lines 38-39), with the field wired For="@(() => _model.Email)" (line 41) so the message attaches to that input. On valid submit HandleRequestAsync (lines 80-97) calls IAuthUIService.RequestPasswordResetAsync(_model.Email) (line 86, contract at MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/IAuthUIService.cs:57) inside a try whose catch is empty on purpose (lines 88-91) and whose finally sets _isSubmitted = true unconditionally (line 95), so the confirmation renders for every submitted address whether the call succeeded, failed, or threw.
      • +
      • Caveats / not-in-source: RequestPasswordResetAsync returns a Result and the call site never inspects it (line 86); the comment block above the method (lines 75-79) records that this is the anti-enumeration rule rather than a dropped result. The gallery E2E suite pins the behavior by asserting the confirmation appears with no backend at all (MMCA.Common/Tests/Presentation/MMCA.Common.UI.E2E.Tests/Auth/ForgotPasswordPageE2ETests.cs:28), with WCAG 2.1 AA scans on both the form and the confirmation state (:41, :50).

      LoginModel

      -

      MMCA.Common.UI · MMCA.Common.UI.Pages.Auth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/LoginModel.cs:9 · Level 0 · class (sealed)

      +

      MMCA.Common.UI · MMCA.Common.UI.Pages.Auth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/LoginModel.cs:13 · Level 0 · class (sealed)

      • What it is: the EditForm backing model for the Login page: two string properties (Email, Password) carrying DataAnnotations for field-level validation.
      • -
      • Depends on: System.ComponentModel.DataAnnotations (BCL): [Required], [EmailAddress]. Nothing first-party.
      • -
      • Concept introduced, the form-backing model plus DataAnnotationsValidator. [Rubric §24, Forms, Validation & UX Safety] assesses whether forms validate at the field level with clear inline messages before submit; [Rubric §26, Front-End Security] assesses that client-side checks are a UX convenience, not the trust boundary. A Blazor EditForm binds to a plain model, a <DataAnnotationsValidator /> reads the attributes and surfaces a per-field message as the user types, and the submit handler only fires on a valid form. The doc comment (LoginModel.cs:5-8) is explicit that the server remains the authority on whether the credentials are actually valid: the form only prevents an obviously malformed request.
      • +
      • Depends on: System.ComponentModel.DataAnnotations (BCL): [Required], [EmailAddress]. Nothing first-party in the type itself; its message keys are resolved by LocalizedDataAnnotationsValidator on the page.
      • +
      • Concept introduced, the form-backing model plus a localizing DataAnnotations validator. [Rubric §24, Forms, Validation & UX Safety] assesses whether forms validate at the field level with clear inline messages before submit; [Rubric §26, Front-End Security] assesses that client-side checks are a UX convenience, not the trust boundary; [Rubric §27, Internationalization] assesses whether validation messages translate. A Blazor EditForm binds to a plain model, a <LocalizedDataAnnotationsValidator /> reads the attributes and surfaces a per-field message as the user types, and the submit handler only fires on a valid form. Each ErrorMessage is a resource key that the validator resolves (doc comment, LoginModel.cs:8-11, ADR-027), so the model carries no English text. The doc comment (LoginModel.cs:5-12) is also explicit that the server remains the authority on whether the credentials are actually valid: the form only prevents an obviously malformed request.
      • Walkthrough: two get; set; properties.
          -
        • Email (line 13), [Required(ErrorMessage = "Email is required")] plus [EmailAddress(ErrorMessage = "Enter a valid email address")] (lines 11-12), defaulting to string.Empty.
        • -
        • Password (line 16), [Required(ErrorMessage = "Password is required")] (line 15). There is deliberately no complexity rule here: login validates an existing credential, not a new one, and rejecting a legacy password client-side would lock a user out of their own account.
        • +
        • Email (line 17), [Required(ErrorMessage = "Auth.Field.Email.Required")] plus [EmailAddress(ErrorMessage = "Auth.Field.Email.Invalid")] (lines 15-16), defaulting to string.Empty.
        • +
        • Password (line 20), [Required(ErrorMessage = "Auth.Field.Password.Required")] (line 19). There is deliberately no complexity rule here: login validates an existing credential, not a new one, and rejecting a legacy password client-side would lock a user out of their own account.
      • -
      • Why it's built this way: sealed and mutable because EditForm two-way-binds each input; the messages are authored inline so each field shows exactly one verdict.
      • -
      • Where it's used: instantiated as _model and bound by Login.razor (<EditForm Model="_model" OnValidSubmit="HandleLoginAsync"> plus <DataAnnotationsValidator />, MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Login.razor:33-34, field at line 159, inputs bound For="@(() => _model.Email)" and For="@(() => _model.Password)" at lines 40 and 46 so each MudTextField shows its own message). On valid submit the page hands the credentials to IAuthUIService as a LoginRequest (Login.razor:204). Sibling of RegisterModel; its shape rules are unit-tested alongside the other auth models in MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Pages/Auth/AuthModelValidationTests.cs:11.
      • +
      • Why it's built this way: sealed and mutable because EditForm two-way-binds each input; each attribute names one resource key so each field shows exactly one verdict, in the reader's language.
      • +
      • Where it's used: instantiated as _model and bound by Login.razor (<EditForm Model="_model" OnValidSubmit="HandleLoginAsync"> plus <LocalizedDataAnnotationsValidator />, MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Login.razor:39-40, field at line 172, inputs bound For="@(() => _model.Email)" and For="@(() => _model.Password)" at lines 48 and 54 so each MudTextField shows its own message). On valid submit the page hands the credentials to IAuthUIService as a LoginRequest (Login.razor:241). Sibling of RegisterModel; its shape rules are unit-tested alongside the other auth models in MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Pages/Auth/AuthModelValidationTests.cs:11.

      PasswordComplexityAttribute

      -

      MMCA.Common.UI · MMCA.Common.UI.Pages.Auth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/PasswordComplexityAttribute.cs:12 · Level 0 · class (sealed attribute)

      +

      MMCA.Common.UI · MMCA.Common.UI.Pages.Auth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/PasswordComplexityAttribute.cs:15 · Level 1 · class (sealed attribute)

      • What it is: a custom ValidationAttribute that enforces the framework's password-strength rule on any form that sets a new password: at least 8 characters including an uppercase, a lowercase, a digit, and a special (non-alphanumeric) character.
      • -
      • Depends on: System.ComponentModel.DataAnnotations (ValidationAttribute, ValidationResult, ValidationContext) and char.IsUpper/IsLower/IsDigit/IsLetterOrDigit (BCL). Nothing first-party.
      • -
      • Concept introduced, extending DataAnnotations with a domain rule. [Rubric §24, Forms, Validation & UX Safety] assesses client-side validation parity with the server. Beyond the built-in [Required] and [EmailAddress], a bespoke rule subclasses ValidationAttribute and overrides IsValid, which plugs it straight into the same DataAnnotationsValidator that drives the rest of the form. The doc comment (lines 5-9) states the intent: mirror the server's rule so the EditForm gives the same verdict the API would. What happens to an accepted password server-side (PBKDF2-HMAC-SHA512 hashing with legacy-hash compatibility) is ADR-032; this attribute is only the client-side gate, never the security boundary.
      • +
      • Depends on: System.ComponentModel.DataAnnotations (ValidationAttribute, ValidationResult, ValidationContext, BCL) and the first-party PasswordComplexity rule in MMCA.Common.Shared.Auth (MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/PasswordComplexity.cs:18).
      • +
      • Concept introduced, extending DataAnnotations with a domain rule defined once for both sides. [Rubric §24, Forms, Validation & UX Safety] assesses client-side validation parity with the server. Beyond the built-in [Required] and [EmailAddress], a bespoke rule subclasses ValidationAttribute and overrides IsValid, which plugs it straight into the same validator that drives the rest of the form. Parity is structural rather than copied: the attribute does not encode the character rules itself, it calls PasswordComplexity.Evaluate (PasswordComplexity.cs:52), the single Unicode-aware definition the server's StrongPasswordRules also uses (doc comment, lines 6-13; PasswordComplexity.cs:5-17). That definition counts any \p{Lu} as uppercase, any \p{Ll} as lowercase, any \p{Nd} as a digit, and anything that is neither a letter nor a decimal digit as special (PasswordComplexity.cs:27-43), so an accented or CJK letter is a letter, never the special character. What happens to an accepted password server-side (PBKDF2-HMAC-SHA512 hashing with legacy-hash compatibility) is ADR-032; this attribute is only the client-side gate, never the security boundary.
      • Walkthrough:
          -
        • [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] (line 11), so it is applied as [PasswordComplexity] on a single property.
        • -
        • The constructor (lines 14-17) seeds the base ErrorMessage with the full human-readable rule, so a form that does not override the message still shows something actionable.
        • -
        • IsValid(object?, ValidationContext) (lines 19-39): returns ValidationResult.Success for a non-string or a null/empty input (lines 21-24), deliberately deferring the "missing" message to RequiredAttribute so the field shows one message rather than two. Otherwise it evaluates five predicates in one boolean (Length >= 8, Any(char.IsUpper), Any(char.IsLower), Any(char.IsDigit), Any(c => !char.IsLetterOrDigit(c)), lines 26-30) and, on failure, returns a ValidationResult scoped to the member name (lines 37-38) so the message attaches to the right input.
        • +
        • [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] (line 14), so it is applied as [PasswordComplexity] on a single property.
        • +
        • The constructor (lines 17-20) seeds the base ErrorMessage with the full English rule, so a usage that does not override the message still shows something actionable. The framework's own forms override it with the resource key Auth.Field.Password.Complexity (see RegisterModel).
        • +
        • IsValid(object?, ValidationContext) (lines 22-33): one guard (line 24) returns ValidationResult.Success for a non-string, a null/empty input, or a password for which PasswordComplexity.Evaluate is true. Deferring the empty case to RequiredAttribute keeps the field to one message rather than two. On failure it returns a ValidationResult scoped to the member name (lines 31-32) so the message attaches to the right input; the member lookup is null-conditional (validationContext?.MemberName, line 31) because the base class routes the context-free IsValid(object) overload here with no context (comment, lines 29-30), and there is then no member to attach the error to.
      • -
      • Why it's built this way: because the rule is an attribute rather than page code, a second form that sets a password gets identical behavior by adding one line, which is exactly how the reset vertical picked it up. Delegating emptiness to [Required] is what keeps one field from stacking two errors.
      • -
      • Where it's used: applied to RegisterModel.Password (RegisterModel, RegisterModel.cs:23) and to ResetPasswordModel.NewPassword (ResetPasswordModel, ResetPasswordModel.cs:21); evaluated by the <DataAnnotationsValidator /> in Register.razor (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Register.razor:28) and ResetPassword.razor (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/ResetPassword.razor:36).
      • -
      • Caveats / not-in-source: the doc comment (line 6) still describes the attribute as the rule "for the Register form" although the reset form carries it too; the code is the wider truth. The comment also claims parity with the server's rule, but this file encodes only the client check, so whether the server rule is byte-identical is not verifiable from this source.
      • +
      • Why it's built this way: because the rule is an attribute rather than page code, a second form that sets a password gets identical behavior by adding one line, which is exactly how the reset vertical picked it up. Delegating the predicate to a type in the Shared project, the one project both client and server may reference, is what makes the two verdicts identical by construction rather than by review. Delegating emptiness to [Required] keeps one field from stacking two errors. The length cap is deliberately not part of this attribute: PasswordComplexity.MaximumLength is a separate rule with its own message on both sides (PasswordComplexity.cs:24, :45-49).
      • +
      • Where it's used: applied to RegisterModel.Password (RegisterModel, RegisterModel.cs:28) and to ResetPasswordModel.NewPassword (ResetPasswordModel, ResetPasswordModel.cs:26); evaluated by the LocalizedDataAnnotationsValidator in Register.razor (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Register.razor:33) and ResetPassword.razor (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/ResetPassword.razor:43). Unit-tested in MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Pages/Auth/PasswordComplexityAttributeTests.cs:12; its agreement with the server rule is pinned by the architecture test MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Ui/PasswordRuleParityTests.cs:21.
      • +
      • Caveats / not-in-source: the doc comment (line 7) still describes the attribute as the rule "for the Register form" although the reset form carries it too; the code is the wider truth.

      RegisterModel

      -

      MMCA.Common.UI · MMCA.Common.UI.Pages.Auth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/RegisterModel.cs:9 · Level 0 · class (sealed)

      +

      MMCA.Common.UI · MMCA.Common.UI.Pages.Auth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/RegisterModel.cs:14 · Level 1 · class (sealed)

      • What it is: the EditForm backing model for the Register page: name, email, and password fields with DataAnnotations, plus six optional address fields.
      • -
      • Depends on: System.ComponentModel.DataAnnotations ([Required], [StringLength], [EmailAddress], [Compare]) and the sibling first-party PasswordComplexityAttribute.
      • -
      • Concept reinforced, multi-field form validation with a cross-field compare. [Rubric §24, Forms, Validation & UX Safety]. This builds on the LoginModel shape with three richer rules: [PasswordComplexity] on the password (paired with a [StringLength(128)] cap), [Compare(nameof(Password))] on the confirmation (a cross-field equality check the validator resolves by property name), and an address block left attribute-free because it is optional. The doc comment (lines 5-8) notes the annotations mirror the server's rules so client and server agree.
      • +
      • Depends on: System.ComponentModel.DataAnnotations ([Required], [StringLength], [EmailAddress], [Compare]), the sibling first-party PasswordComplexityAttribute, and PasswordComplexity for the shared length cap.
      • +
      • Concept reinforced, multi-field form validation with a cross-field compare. [Rubric §24, Forms, Validation & UX Safety]. This builds on the LoginModel shape with three richer rules: [PasswordComplexity] on the password (paired with a [StringLength(PasswordComplexity.MaximumLength)] cap, 128 at MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/PasswordComplexity.cs:24, the constant the server rule shares), [Compare(nameof(Password))] on the confirmation (a cross-field equality check the validator resolves by property name), and an address block left attribute-free because it is optional. The doc comment (lines 6-13) notes the annotations mirror the server's rules so client and server agree, and that every ErrorMessage is a resource key resolved by the page's LocalizedDataAnnotationsValidator (ADR-027).
      • Walkthrough:
          -
        • FirstName / LastName (lines 12, 15), each [Required] with its own message (lines 11, 14).
        • -
        • Email (line 19), [Required] plus [EmailAddress] (lines 17-18).
        • -
        • Password (line 24), [Required] plus [StringLength(128, ErrorMessage = "Password cannot be longer than 128 characters")] plus [PasswordComplexity] (lines 21-23).
        • -
        • ConfirmPassword (line 28), [Required] plus [Compare(nameof(Password), ErrorMessage = "Passwords do not match")] (lines 26-27).
        • -
        • AddressLine1 plus nullable AddressLine2/City/State/ZipCode/Country (lines 31-36), with no validation attributes; the inline comment (line 30) states that an empty Line 1 means "no address supplied".
        • +
        • FirstName / LastName (lines 17, 20), each [Required] with its own key, Auth.Field.FirstName.Required / Auth.Field.LastName.Required (lines 16, 19).
        • +
        • Email (line 24), [Required] plus [EmailAddress] (lines 22-23), keyed Auth.Field.Email.Required / Auth.Field.Email.Invalid.
        • +
        • Password (line 29), [Required(ErrorMessage = "Auth.Field.Password.Required")] plus [StringLength(PasswordComplexity.MaximumLength, ErrorMessage = "Auth.Field.Password.MaxLength")] plus [PasswordComplexity(ErrorMessage = "Auth.Field.Password.Complexity")] (lines 26-28).
        • +
        • ConfirmPassword (line 33), [Required(ErrorMessage = "Auth.Field.ConfirmPassword.Required")] plus [Compare(nameof(Password), ErrorMessage = "Auth.Field.ConfirmPassword.Mismatch")] (lines 31-32).
        • +
        • AddressLine1 plus nullable AddressLine2/City/State/ZipCode/Country (lines 36-41), with no validation attributes; the inline comment (line 35) states that an empty Line 1 means "no address supplied".
      • Why it's built this way: the address fields stay attribute-free so a user can register without supplying one; the model is a flat view-model that the page projects onto the wire DTO at submit time rather than reusing a domain type directly, which is what lets the optional-address rule live in page code instead of leaking into the contract.
      • -
      • Where it's used: instantiated as _model and bound by Register.razor (<EditForm Model="_model" OnValidSubmit="HandleRegisterAsync"> plus <DataAnnotationsValidator />, MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Register.razor:27-28, field at line 123). On valid submit the page projects it into a RegisterRequest (Register.razor:162), folding the address fields into an Address through BuildAddressResult(), which returns null when all six are blank (lines 132-138) and otherwise calls Address.Create(...) (line 140). Its password block is mirrored by ResetPasswordModel; the accepted password is hashed server-side per ADR-032. Rendering and validation behavior are covered by MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Pages/Auth/RegisterFormTests.cs and the gallery E2E suite (MMCA.Common/Tests/Presentation/MMCA.Common.UI.E2E.Tests/RegisterPageE2ETests.cs).
      • +
      • Where it's used: instantiated as _model and bound by Register.razor (<EditForm Model="_model" OnValidSubmit="HandleRegisterAsync"> plus <LocalizedDataAnnotationsValidator />, MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Register.razor:32-33, field at line 148). On valid submit the page projects it into a RegisterRequest (Register.razor:189), folding the address fields into an Address through BuildAddressResult() (line 156), which returns null when all six are blank (lines 158-164) and otherwise calls Address.Create(...) (line 166). Its password block is mirrored by ResetPasswordModel; the accepted password is hashed server-side per ADR-032. Shape rules are unit-tested in MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Pages/Auth/AuthModelValidationTests.cs:11; rendering and validation behavior are covered by MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Pages/Auth/RegisterFormTests.cs:22 and the gallery E2E suite (MMCA.Common/Tests/Presentation/MMCA.Common.UI.E2E.Tests/Auth/RegisterPageE2ETests.cs:9).

      ResetPasswordModel

      -

      MMCA.Common.UI · MMCA.Common.UI.Pages.Auth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/ResetPasswordModel.cs:10 · Level 0 · class (sealed)

      +

      MMCA.Common.UI · MMCA.Common.UI.Pages.Auth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/ResetPasswordModel.cs:15 · Level 1 · class (sealed)

      • What it is: the EditForm backing model for the Reset Password page: the address and the emailed reset token that identify the request, plus the new password and its confirmation.
      • -
      • Depends on: System.ComponentModel.DataAnnotations ([Required], [StringLength], [EmailAddress], [Compare]) and the sibling first-party PasswordComplexityAttribute.
      • -
      • Concept reinforced, the same password block as registration, on a credential-carrying form. [Rubric §24, Forms, Validation & UX Safety] and [Rubric §26, Front-End Security]. The password half is exactly the shape RegisterModel introduced, which is the payoff of expressing the complexity rule as an attribute rather than page code. What is new is the top half: Email and Token are not values the user chooses, they are the credential the server minted and mailed. The client validates only that both are present and that the address is well formed; every substantive rejection (unknown, expired, mismatched, or attempt-capped token) collapses into one server-side error by design (ADR-091 Decision 3), so the form must not try to pre-judge a token it cannot verify.
      • +
      • Depends on: System.ComponentModel.DataAnnotations ([Required], [StringLength], [EmailAddress], [Compare]), the sibling first-party PasswordComplexityAttribute, and PasswordComplexity for the shared length cap.
      • +
      • Concept reinforced, the same password block as registration, on a credential-carrying form. [Rubric §24, Forms, Validation & UX Safety] and [Rubric §26, Front-End Security]. The password half is exactly the shape RegisterModel introduced, which is the payoff of expressing the complexity rule as an attribute rather than page code. What is new is the top half: Email and Token are not values the user chooses, they are the credential the server minted and mailed. The client validates only that both are present and that the address is well formed; every substantive rejection (unknown, expired, mismatched, or attempt-capped token) collapses into one server-side error by design (ADR-091 Decision 3), so the form must not try to pre-judge a token it cannot verify. As on the sibling models, each ErrorMessage is a resource key resolved by the page's LocalizedDataAnnotationsValidator (doc comment, lines 10-13, ADR-027).
      • Walkthrough: four get; set; properties, each defaulting to string.Empty.
          -
        • Email (line 14), [Required(ErrorMessage = "Email is required")] plus [EmailAddress(ErrorMessage = "Enter a valid email address")] (lines 12-13).
        • -
        • Token (line 17), [Required(ErrorMessage = "Reset token is required")] (line 16) and nothing more: length, encoding, and freshness are all properties of the server-side cache record.
        • -
        • NewPassword (line 22), [Required] plus [StringLength(128, ErrorMessage = "Password cannot be longer than 128 characters")] plus [PasswordComplexity] (lines 19-21).
        • -
        • ConfirmPassword (line 26), [Required] plus [Compare(nameof(NewPassword), ErrorMessage = "Passwords do not match")] (lines 24-25), the cross-field check retargeted at NewPassword.
        • +
        • Email (line 19), [Required(ErrorMessage = "Auth.Field.Email.Required")] plus [EmailAddress(ErrorMessage = "Auth.Field.Email.Invalid")] (lines 17-18).
        • +
        • Token (line 22), [Required(ErrorMessage = "Auth.Field.Token.Required")] (line 21) and nothing more: length, encoding, and freshness are all properties of the server-side cache record.
        • +
        • NewPassword (line 27), [Required(ErrorMessage = "Auth.Field.Password.Required")] plus [StringLength(PasswordComplexity.MaximumLength, ErrorMessage = "Auth.Field.Password.MaxLength")] plus [PasswordComplexity(ErrorMessage = "Auth.Field.Password.Complexity")] (lines 24-26).
        • +
        • ConfirmPassword (line 31), [Required(ErrorMessage = "Auth.Field.ConfirmPassword.Required")] plus [Compare(nameof(NewPassword), ErrorMessage = "Auth.Field.ConfirmPassword.Mismatch")] (lines 29-30), the cross-field check retargeted at NewPassword.
      • -
      • Why it's built this way: the doc comment (lines 5-9) records the load-bearing choice, that Email and Token arrive prefilled from the reset link but stay editable, so a user who only has the raw token text from the email (the situation on a native head with no working deep link) can paste it by hand. Making those two ordinary bound fields rather than read-only parameters buys that fallback for free.
      • -
      • Where it's used: instantiated as _model by ResetPassword.razor (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/ResetPassword.razor:95) and bound by its <EditForm Model="_model" OnValidSubmit="HandleResetAsync"> plus <DataAnnotationsValidator /> (lines 35-36). The page declares [SupplyParameterFromQuery] Email and Token properties (lines 89-93) and copies them into the model in OnParametersSet (lines 102-113), filling a field only when it is still blank (lines 104, 109) so a value the user corrected by hand is not overwritten when parameters are set again. HandleResetAsync (lines 115-140) calls IAuthUIService.ResetPasswordAsync(_model.Email, _model.Token, _model.NewPassword) (line 122, contract at MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/IAuthUIService.cs:64), flips _isCompleted on success, and on failure renders result.LocalizedErrorMessage(L) or the generic Auth.Reset.GenericError string (line 129). The prefill path is pinned by a gallery E2E test (MMCA.Common/Tests/Presentation/MMCA.Common.UI.E2E.Tests/Auth/ResetPasswordPageE2ETests.cs:31), with a WCAG 2.1 AA scan alongside it (:43).
      • +
      • Why it's built this way: the doc comment (lines 7-9) records the load-bearing choice, that Email and Token arrive prefilled from the reset link but stay editable, so a user who only has the raw token text from the email (the situation on a native head with no working deep link) can paste it by hand. Making those two ordinary bound fields rather than read-only parameters buys that fallback for free.
      • +
      • Where it's used: instantiated as _model by ResetPassword.razor (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/ResetPassword.razor:105) and bound by its <EditForm Model="_model" OnValidSubmit="HandleResetAsync"> plus <LocalizedDataAnnotationsValidator /> (lines 42-43). The model is prefilled from two sources, both of which fill a field only when it is still blank so a value the user corrected by hand is not overwritten. First, the page declares [SupplyParameterFromQuery] Email and Token properties (lines 99-103) and copies them in OnParametersSet (lines 112-123, blank checks at 114 and 119). Second, OnAfterRenderAsync (lines 130-177) reads the URL fragment once on first render through the locationHashTake JS call (line 143), which also scrubs it from the address bar, and fills email / token from it (lines 161-170); the comment at lines 125-129 explains why: a fragment is never sent to a server, so the live token stays out of ingress logs and request telemetry, while the query-string path keeps older links working. HandleResetAsync (lines 179-204) calls IAuthUIService.ResetPasswordAsync(_model.Email, _model.Token, _model.NewPassword) (line 186, contract at MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/IAuthUIService.cs:64), flips _isCompleted on success, and on failure renders result.LocalizedErrorMessage(L) or the generic Auth.Reset.GenericError string (line 193), which a thrown call also falls back to (lines 196-199). The prefill path is pinned by a gallery E2E test (MMCA.Common/Tests/Presentation/MMCA.Common.UI.E2E.Tests/Auth/ResetPasswordPageE2ETests.cs:31), with a WCAG 2.1 AA scan alongside it (:43).
      • Caveats / not-in-source: the model has no rule tying Token to the address; that pairing is enforced by the server's cache record (ADR-091 Decision 1), not by anything visible here.

      DetailPageBase

      @@ -2019,24 +2151,24 @@

      Sessions

      MMCA.Common.UI · MMCA.Common.UI.Pages.Auth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Sessions.razor.cs:26 · Level 6 · class (partial, page code-behind)

        -
      • What it is: the code-behind for the signed-in devices page at /profile/sessions: one row per live refresh session, with a per-device sign-out and a sign-out-everywhere.
      • -
      • Depends on: IAuthUIService (line 28), IToastService (line 30), SharedResource as IStringLocalizer<SharedResource> (line 31), RefreshSessionSummaryResponse (the row DTO, line 38), Result (line 41), ResultUiExtensions (IsNotFound, NotifyOnFailure), UserAgentSummary (line 181), and RoutePaths (line 65). Externals: Blazor's NavigationManager and MudBlazor's BreadcrumbItem / MudTable.
      • -
      • Concept introduced, two revoke paths with deliberately different endings. [Rubric §11, Security] assesses whether a user can see and end their own live credentials; [Rubric §25, Navigation & IA] assesses whether a destructive action leaves the app in a coherent place. The class comment (lines 16-24) states the model: a row's button calls the per-session revoke, which ends one other device's session and leaves this one alone, while the page-level button is the account-wide revoke, which also ends the session the caller is using and therefore must be followed by the normal local sign-out and a redirect. The consequence is visible in the markup: the row for the current device offers no button at all (Sessions.razor:62-69), because revoking it from a row would leave the app signed in on a dead session until the access token expired, which reads to a user as a broken sign-out.
      • -
      • Concept introduced (2), a single in-flight gate over two different operations. [Rubric §18, UI Architecture & Component Design]. IsBusy (line 55) is _revokingSessionId is not null || IsRevokingAll, and every button in the markup reads it (Sessions.razor:74, :90). One flag over both operations is what stops a second click from starting a concurrent revoke while the list is about to be rebuilt underneath it, and _revokingSessionId doubles as the per-row spinner selector (Sessions.razor:77).
      • +
      • What it is: the code-behind for the signed-in devices page at /profile/sessions: one row per live refresh session, with a per-device sign-out and a sign-out-everywhere, each behind a confirmation dialog.
      • +
      • Depends on: IAuthUIService (line 28), IToastService (line 30), IAppDialogService (line 31), SharedResource as IStringLocalizer<SharedResource> (line 32), RefreshSessionSummaryResponse (the row DTO, line 39), Result (line 42), ResultUiExtensions (IsNotFound, NotifyOnFailure), UserAgentSummary (line 218), and RoutePaths (line 66). Externals: Blazor's NavigationManager and MudBlazor's BreadcrumbItem / MudTable.
      • +
      • Concept introduced, two revoke paths with deliberately different endings. [Rubric §11, Security] assesses whether a user can see and end their own live credentials; [Rubric §25, Navigation & IA] assesses whether a destructive action leaves the app in a coherent place. The class comment (lines 16-24) states the model: a row's button calls the per-session revoke, which ends one other device's session and leaves this one alone, while the page-level button is the account-wide revoke, which also ends the session the caller is using and therefore must be followed by the local sign-out and a redirect, but only once the server confirmed the revoke (line 21). The consequence is visible in the markup: the row for the current device offers no button at all (Sessions.razor:68-75), because revoking it from a row would leave the app signed in on a dead session until the access token expired, which reads to a user as a broken sign-out.
      • +
      • Concept introduced (2), a single in-flight gate over two different operations. [Rubric §18, UI Architecture & Component Design]. IsBusy (line 56) is _revokingSessionId is not null || IsRevokingAll, and every button in the markup reads it (Sessions.razor:80, :97). One flag over both operations is what stops a second click from starting a concurrent revoke while the list is about to be rebuilt underneath it, and _revokingSessionId doubles as the per-row spinner selector (Sessions.razor:84). Both revoke handlers check IsBusy twice, before and after the awaited confirmation dialog (lines 112 and 123, 171 and 182), because another operation can start while the dialog is open.
      • Walkthrough:
          -
        • State (lines 33-58): a component-scoped CancellationTokenSource (line 35) passed into every service call and cancelled on dispose, _breadcrumbs, _sessions, the nullable _loadResult that carries the last load outcome for inline rendering (line 41), _revokingSessionId, IsLoading (line 49), IsBusy (line 55), and IsRevokingAll (line 58). LoginRoute is a const (line 33).
        • -
        • OnInitializedAsync (lines 60-70): builds the breadcrumbs here rather than in a field initializer so the injected localizer is available (comment at line 62, ADR-027), then loads.
        • -
        • LoadSessionsAsync (lines 72-101): stores the whole Result in _loadResult (line 80) so the markup can render the failure inline with a retry button rather than as a toast (Sessions.razor:16, :22-29; the comment at Sessions.razor:14-15 explains why: an empty table and a failed load look identical once a toast expires). On failure it empties _sessions deliberately (lines 87-91), so a stale device list can never be left on screen for a user to act on. OperationCanceledException is swallowed as expected-on-disposal (lines 93-96) and IsLoading clears in the finally.
        • -
        • RevokeSessionAsync(session) (lines 108-147): refuses to run while busy or for the current device (lines 110-113, a second guard behind the markup's), marks the row in flight, and then treats three outcomes distinctly. Success toasts (line 123). A not-found result, which means the session is already gone (a duplicate click, or the device signed itself out), toasts at info severity instead (lines 125-130), because the user's intent is satisfied and there is nothing to correct. Any other failure goes through result.NotifyOnFailure(Toast, L) and returns without reloading (lines 131-135). On either satisfied outcome it reloads the list from the server rather than removing the row locally (line 137), because the server is the authority on what is still live and a reload also catches a session that expired while the page sat open (doc comment, lines 103-107).
        • -
        • RevokeAllAsync (lines 154-172): guards on IsBusy, calls IAuthUIService.LogoutAsync() (line 165), which is exactly the account-wide revoke plus the local token clear and auth-state notification, and then navigates to /login with forceLoad: true (line 166) so the circuit and any cached client state are rebuilt rather than kept alive against a revoked identity.
        • -
        • DescribeDevice(session) (lines 179-190): parses browser and platform out of the user agent via UserAgentSummary.Parse (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/UserAgentSummary.cs:66) and composes them through a resource format string in the both-known case (line 185), so the word order translates rather than being concatenated in English order; a single known part is returned as is, and neither known falls back to an explicit "unknown device" string (line 188). [Rubric §27, Internationalization].
        • -
        • FormatInstant(DateTime) (lines 196-197): stamps the incoming value as UTC, converts to local time, and formats with CultureInfo.CurrentCulture general short pattern, because the endpoint reports UTC and "signed in at 03:14" only means something on the clock the reader uses.
        • -
        • Dispose(bool) / Dispose() (lines 199-219): the standard idempotent pattern, cancelling and disposing the CTS.
        • +
        • State (lines 34-59): a component-scoped CancellationTokenSource (line 36) passed into every service call and cancelled on dispose, _breadcrumbs, _sessions, the nullable _loadResult that carries the last load outcome for inline rendering (line 42), _revokingSessionId, IsLoading (line 50), IsBusy (line 56), and IsRevokingAll (line 59). LoginRoute is a const (line 34).
        • +
        • OnInitializedAsync (lines 61-71): builds the breadcrumbs here rather than in a field initializer so the injected localizer is available (comment at line 63, ADR-027), then loads.
        • +
        • LoadSessionsAsync (lines 73-102): stores the whole Result in _loadResult (line 81) so the markup can render the failure inline with a retry button rather than as a toast (Sessions.razor:17, :23-30; the comment at Sessions.razor:15-16 explains why: an empty table and a failed load look identical once a toast expires). On failure it empties _sessions deliberately (lines 87-92), so a stale device list can never be left on screen for a user to act on. OperationCanceledException is swallowed as expected-on-disposal (lines 94-97) and IsLoading clears in the finally.
        • +
        • RevokeSessionAsync(session) (lines 110-160): refuses to run while busy or for the current device (lines 112-115, a second guard behind the markup's), then asks for confirmation through IAppDialogService.ConfirmAsync with a message naming the device via DescribeDevice (lines 117-121), because the device loses its session at once (doc comment, lines 104-109). After the post-dialog re-check (lines 123-126) it marks the row in flight (line 128) and treats three outcomes distinctly. Success toasts (line 136). A not-found result, which means the session is already gone (a duplicate click, or the device signed itself out), toasts at info severity instead (lines 138-143), because the user's intent is satisfied and there is nothing to correct. Any other failure goes through result.NotifyOnFailure(Toast, L) and returns without reloading (lines 144-148). On either satisfied outcome it reloads the list from the server rather than removing the row locally (line 150), because the server is the authority on what is still live and a reload also catches a session that expired while the page sat open.
        • +
        • RevokeAllAsync (lines 169-209): guards on IsBusy, confirms through ConfirmAsync (lines 176-180) because this also ends the session the user is on, re-checks, then calls IAuthUIService.RevokeAllSessionsAsync(_cts.Token) (line 191, contract at MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/IAuthUIService.cs:91), which is the account-wide revoke followed, on success, by the local sign-out. Only on success does it navigate to /login with forceLoad: true (line 194), so the circuit and any cached client state are rebuilt rather than kept alive against a revoked identity. A failed revoke is shown through result.NotifyOnFailure(Toast, L) (line 198) and the page stays put: the doc comment (lines 162-168) explains that a best-effort logout would claim every device was signed out even when the server refused. OperationCanceledException is swallowed as expected-on-disposal (lines 201-204) and IsRevokingAll clears in the finally (lines 205-208).
        • +
        • DescribeDevice(session) (lines 216-227): parses browser and platform out of the user agent via UserAgentSummary.Parse (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/UserAgentSummary.cs:66) and composes them through a resource format string in the both-known case (line 222), so the word order translates rather than being concatenated in English order; a single known part is returned as is, and neither known falls back to an explicit "unknown device" string (line 225). [Rubric §27, Internationalization]. The same label feeds the per-row confirmation message and aria-label.
        • +
        • FormatInstant(DateTime) (lines 233-234): stamps the incoming value as UTC, converts to local time, and formats with CultureInfo.CurrentCulture general short pattern, because the endpoint reports UTC and "signed in at 03:14" only means something on the clock the reader uses.
        • +
        • Dispose(bool) / Dispose() (lines 236-256): the standard idempotent pattern, cancelling and disposing the CTS.
      • -
      • Why it's built this way: rendering the load failure inline instead of as a toast is a deliberate [Rubric §24, Forms, Validation & UX Safety] choice for a page whose empty state is indistinguishable from its failure state, and it is the same reasoning DataGridListPageBase<TDto> encodes in its LoadFailed flag. Treating "already revoked" as an informational outcome rather than an error avoids punishing a user for a double click on an idempotent action.
      • +
      • Why it's built this way: rendering the load failure inline instead of as a toast is a deliberate [Rubric §24, Forms, Validation & UX Safety] choice for a page whose empty state is indistinguishable from its failure state, and it is the same reasoning DataGridListPageBase<TDto> encodes in its LoadFailed flag. Treating "already revoked" as an informational outcome rather than an error avoids punishing a user for a double click on an idempotent action. Confirming both revokes before acting is the same section's destructive-action rule: each one signs a device out immediately.
      • Where it's used: routed at /profile/sessions behind [Authorize] with [StreamRendering(false)] (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Sessions.razor:1-6), reachable in any host that maps the framework's UI pages. Its component behavior is covered by bUnit tests (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Pages/Auth/SessionsTests.cs:27) and its rendered accessibility by the gallery E2E WCAG 2.1 AA scan (MMCA.Common/Tests/Presentation/MMCA.Common.UI.E2E.Tests/Auth/SessionsPageE2ETests.cs:21).
      • -
      • Caveats / not-in-source: RevokeAllAsync has no catch, so a throw from LogoutAsync propagates out of the handler with IsRevokingAll reset by the finally but no toast; whether the local sign-out completed in that case is a property of IAuthUIService, not of this file. The accessibility markers the page relies on (the text-variant "this device" chip at Sessions.razor:52-53, chosen so the marker does not depend on color alone, and the per-button aria-labels at :76 and :92) live in the markup half, not in this code-behind.
      • +
      • Caveats / not-in-source: whether RevokeAllSessionsAsync completes the local sign-out when the server call succeeds but token clearing fails is a property of IAuthUIService, not of this file; any exception other than OperationCanceledException still propagates out of both handlers with no toast. The accessibility markers the page relies on (the text-variant "this device" chip at Sessions.razor:58-59, chosen so the marker does not depend on color alone, and the per-button aria-labels at :83 and :100) live in the markup half, not in this code-behind.

      LazyJsModule

      @@ -2062,20 +2194,27 @@

      LazyJsModule

      three fields: a Lock (LazyJsModule.cs:22), the in-flight import task (LazyJsModule.cs:24) and the resolved module (LazyJsModule.cs:25). IsImported (LazyJsModule.cs:28) exists so disposal can skip work. -
    1395. GetOrImportAsync (LazyJsModule.cs:34) starts with a lock-free fast path returning the cached - module (LazyJsModule.cs:36-39), then takes the lock only to publish or read the in-flight task - (LazyJsModule.cs:44-48); the inline comment (LazyJsModule.cs:41-42) notes why holding a lock +
    1396. GetOrImportAsync (LazyJsModule.cs:37) starts with a lock-free fast path returning the cached + module (LazyJsModule.cs:39-42), then takes the lock only to publish or read the in-flight task + (LazyJsModule.cs:47-51); the inline comment (LazyJsModule.cs:44-45) notes why holding a lock there is safe, since ImportAsync reaches its first await immediately and nothing slow runs under - it. The awaited task is then shared by every caller (LazyJsModule.cs:52).
    1397. -
    1398. The finally block is the subtle part (LazyJsModule.cs:54-68): it clears the field only when the - task did not complete successfully (LazyJsModule.cs:58), and only when the field still holds - this task (LazyJsModule.cs:62), because clearing unconditionally could drop a newer import - started after this one completed and split the next set of callers.
    1399. -
    1400. ImportAsync (LazyJsModule.cs:71-79) performs the actual - js.InvokeAsync<IJSObjectReference>("import", ...) and assigns _module.
    1401. -
    1402. DisposeAsync (LazyJsModule.cs:82-99) returns immediately when nothing was imported - (LazyJsModule.cs:84-87), nulls the field before awaiting (LazyJsModule.cs:89), and swallows - JSDisconnectedException (LazyJsModule.cs:95-98), since a torn-down circuit is the normal end of + it. The import is started with no caller token at all (LazyJsModule.cs:49), and each caller then + awaits the shared task through WaitAsync(cancellationToken) (LazyJsModule.cs:55), so a caller's + token cancels only that caller's wait, never the import. The method summary + (LazyJsModule.cs:30-36) gives the reason: binding the import to the first caller's token would let + that one caller's cancellation fault the import for every concurrent awaiter.
    1403. +
    1404. The finally block is the subtle part (LazyJsModule.cs:57-73): it clears the field only when the + task has completed and did not complete successfully (LazyJsModule.cs:63), and only when the + field still holds this task (LazyJsModule.cs:67), because clearing unconditionally could drop a + newer import started after this one completed and split the next set of callers. The inline + comment (LazyJsModule.cs:59-62) covers the cancellation case: a caller that stopped waiting on its + own token leaves the import still running rather than failed, so it stays cached for the others.
    1405. +
    1406. ImportAsync (LazyJsModule.cs:76-84) performs the actual + js.InvokeAsync<IJSObjectReference>("import", CancellationToken.None, modulePath) + (LazyJsModule.cs:78-80) and assigns _module (LazyJsModule.cs:82).
    1407. +
    1408. DisposeAsync (LazyJsModule.cs:87-104) returns immediately when nothing was imported + (LazyJsModule.cs:89-92), nulls the field before awaiting (LazyJsModule.cs:94), and swallows + JSDisconnectedException (LazyJsModule.cs:100-103), since a torn-down circuit is the normal end of life for a scoped UI service.
    1409. @@ -2167,7 +2306,7 @@

      SharedResource

    1410. Concept introduced, the resource-anchor type. [Rubric §27, Internationalization] assesses whether user-facing copy is externalized to per-culture resources keyed stably rather than hard-coded. ASP.NET Core's IStringLocalizer<T> convention resolves keys against the resource file whose base name matches the type T, so a dedicated empty class becomes the name that ties many components to one shared string table: injecting IStringLocalizer<SharedResource> anywhere reads the same dotted, stable keys (Common.Error.Load, Grid.Snackbar.LoadCancelled, Auth.Sessions.Title). The doc comment (lines 3-8) enumerates the chrome it covers: buttons, layout labels, snackbar and error templates, and the culture- and theme-switcher text. Its counterpart for library chrome is MudTranslations.
    1411. Walkthrough: there are no members. The whole contract is "be a public sealed type named SharedResource in this namespace, with sibling .resx files". The work lives in the key/value pairs and in the localization middleware that resolves them by culture.
    1412. Why it's built this way: a marker type is the idiomatic ASP.NET Core way to scope a shared resource table without inventing a real class, and one anchor keeps the chrome strings in a single table every component shares (ADR-027).
    1413. -
    1414. Where it's used: injected as IStringLocalizer<SharedResource> by DataGridListPageBase<TDto> for its cancellation toast and its Result error rendering (DataGridListPageBase.cs:25), by Sessions for every label on the devices page (Sessions.razor.cs:31), by the auth pages for their field labels and messages, and handed to ErrorMessages.Configure from the root layout (MMCA.Common/Source/Presentation/MMCA.Common.UI/Layout/MainLayout.razor:103) so the static helper resolves the same table.
    1415. +
    1416. Where it's used: injected as IStringLocalizer<SharedResource> by DataGridListPageBase<TDto> for its cancellation toast and its Result error rendering (DataGridListPageBase.cs:25), by Sessions for every label on the devices page (Sessions.razor.cs:32), by the auth pages for their field labels and messages, and handed to ErrorMessages.Configure from the root layout (MMCA.Common/Source/Presentation/MMCA.Common.UI/Layout/MainLayout.razor:103) so the static helper resolves the same table.
    1417. Caveats / not-in-source: the .resx files are resources, not .cs; their per-key contents are not enumerated here.
    1418. ListPageQueryStateService

      @@ -2192,39 +2331,47 @@

      ListPageQueryStateService

      transports the same record travels over.
    1419. Walkthrough
      • The key names are private constants (ListPageQueryStateService.cs:30-40), including the - "search" filter name that maps to q by convention and the desc/1 markers.
      • -
      • ReadCurrent() (ListPageQueryStateService.cs:45-49) is the instance entry point: it resolves the + "search" filter name that maps to q by convention and the desc/1 markers. A separate + MaxPageSize constant of 1000 (ListPageQueryStateService.cs:45) caps the page size a URL may + ask for; its summary (ListPageQueryStateService.cs:42-44) ties it to the same ceiling + KeysetPageRequest.MaxPageSize states.
      • +
      • ReadCurrent() (ListPageQueryStateService.cs:50-54) is the instance entry point: it resolves the absolute URI from the injected NavigationManager and hands the query to the parser.
      • -
      • ParseQueryString (ListPageQueryStateService.cs:56) is deliberately static and public, +
      • ParseQueryString (ListPageQueryStateService.cs:61) is deliberately static and public, documented as a pure helper exposed for unit testing without a NavigationManager - (ListPageQueryStateService.cs:51-55). It reads the three integers through TryGetInt - (ListPageQueryStateService.cs:212-222, which parses with CultureInfo.InvariantCulture and falls - back to the supplied default rather than throwing), treats a blank sort value as no sort - (ListPageQueryStateService.cs:65-72), matches desc case-insensitively - (ListPageQueryStateService.cs:77) but the dense marker 1 ordinally - (ListPageQueryStateService.cs:83), then walks every remaining key, folding q into the search - filter and stripping the f: prefix off the rest (ListPageQueryStateService.cs:87-103).
      • -
      • BuildPath (ListPageQueryStateService.cs:122) is the inverse, and the omission rules are visible - one by one: page only when > 0 (ListPageQueryStateService.cs:129), page size only when > 0 - (ListPageQueryStateService.cs:134), mobile page only when > 1 - (ListPageQueryStateService.cs:139), sd only when a sort column exists and is descending - (ListPageQueryStateService.cs:144-151), d only when dense - (ListPageQueryStateService.cs:153-156); with no parameters at all it returns the bare base path - (ListPageQueryStateService.cs:175-177).
      • -
      • ReplaceState (ListPageQueryStateService.cs:196) writes the URL back using - NavigationOptions { ReplaceHistoryEntry = true } (ListPageQueryStateService.cs:209) so filter + (ListPageQueryStateService.cs:56-60). It reads the three integers through TryGetInt + (ListPageQueryStateService.cs:222-232, which parses with CultureInfo.InvariantCulture and falls + back to the supplied default rather than throwing). Out-of-range values get the same fallback as + unparsable ones (ListPageQueryStateService.cs:65-69): the page is floored at 0, the mobile page + at 1, and a page size outside 1..1000 collapses to 0, the page default, through + ClampPageSize (ListPageQueryStateService.cs:220). The inline comment states the reason: a + hand-edited or stale link with p=-3 or ps=5000 must not leave the list stuck on a failing + fetch. It then treats a blank sort value as no sort + (ListPageQueryStateService.cs:72-79), matches desc case-insensitively + (ListPageQueryStateService.cs:84) but the dense marker 1 ordinally + (ListPageQueryStateService.cs:90), then walks every remaining key, folding q into the search + filter and stripping the f: prefix off the rest (ListPageQueryStateService.cs:94-110).
      • +
      • BuildPath (ListPageQueryStateService.cs:129) is the inverse, and the omission rules are visible + one by one: page only when > 0 (ListPageQueryStateService.cs:136), page size only when > 0 + (ListPageQueryStateService.cs:141), mobile page only when > 1 + (ListPageQueryStateService.cs:146), sd only when a sort column exists and is descending + (ListPageQueryStateService.cs:151-158), d only when dense + (ListPageQueryStateService.cs:160-163); with no parameters at all it returns the bare base path + (ListPageQueryStateService.cs:182-184).
      • +
      • ReplaceState (ListPageQueryStateService.cs:203) writes the URL back using + NavigationOptions { ReplaceHistoryEntry = true } (ListPageQueryStateService.cs:216) so filter changes do not pollute the back stack.
    1420. Why it's built this way: the most instructive part is the guard in ReplaceState - (ListPageQueryStateService.cs:201-206), which drops the write when the current path no longer - matches the owning basePath. The remarks (ListPageQueryStateService.cs:186-195) record the + (ListPageQueryStateService.cs:208-213), which drops the write when the current path no longer + matches the owning basePath. The remarks (ListPageQueryStateService.cs:193-202) record the diagnosed defect: a grid-state write is inherently deferred (a debounced search, a late ServerData completion), so it can land after the user has already navigated away. Building from the then-current URI used to stamp grid parameters onto the next page's URL and issue a spurious navigation that disposed it mid-load, and detail pages reached by clicking a list row had their first data fetch canceled about 66ms in, leaving them stuck on their loading state.
    1421. -
    1422. Where it's used: registered TryAddScoped (DependencyInjection.cs:134) and injected into +
    1423. Where it's used: registered TryAddScoped (DependencyInjection.cs:150) and injected into DataGridListPageBase<TDto> (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Common/DataGridListPageBase.cs:28), which reads the URL on initialization and on parameter changes (DataGridListPageBase.cs:203 and @@ -2281,7 +2428,7 @@

      ListPageStateService

      calling page", which is why this class swallows what LazyJsModule deliberately does not. Scoped registration means one instance per circuit, so the in-memory dictionary is naturally per-user without any keying by identity.
    1424. -
    1425. Where it's used: registered TryAddScoped (DependencyInjection.cs:133) and injected into +
    1426. Where it's used: registered TryAddScoped (DependencyInjection.cs:149) and injected into DataGridListPageBase<TDto> (DataGridListPageBase.cs:27), which reads it during state restore (DataGridListPageBase.cs:205), hydrates from session on first render (DataGridListPageBase.cs:333-338), records scroll offsets (DataGridListPageBase.cs:397), and @@ -2324,8 +2471,8 @@

      MudAppDialogService

      component-side (IAppDialogService.cs:3-7). Keeping the implementation internal and registered by AddUIShared means an app cannot accidentally depend on the MudBlazor type through this path.
    1427. Where it's used: registered with - TryAddScoped<IAppDialogService, MudAppDialogService>() (DependencyInjection.cs:184, under the - facade-registration doc at DependencyInjection.cs:167-180). Consumers resolve the interface: the + TryAddScoped<IAppDialogService, MudAppDialogService>() (DependencyInjection.cs:200, under the + facade-registration doc at DependencyInjection.cs:183-196). Consumers resolve the interface: the shared UnsavedChangesGuard component (MMCA.Common/Source/Presentation/MMCA.Common.UI/Components/Forms/UnsavedChangesGuard.razor:14 and UnsavedChangesGuard.razor:57) and the Helpdesk seed's ticket pages @@ -2353,11 +2500,11 @@

      MudToastService

      library's API. Every page, component and Result helper in both applications depends on IToastService; only this class and MudAppDialogService know that MudBlazor exists, and the DI comment says so outright - (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:169-172). + (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:185-188)
      . [Rubric §14, Testability & Test Strategy] is the practical payoff: a test records toasts against the interface without rendering a snackbar host, which is how ResultUiExtensions.NotifyOnFailure can be tested at all - (MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/ResultUiExtensions.cs:265,277). + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/ResultUiExtensions.cs:268,280). [Rubric §1, SOLID Principles] covers the shape: an internal implementation behind a public interface means no consumer can name the concrete type even by accident. The class doc comment also states why toast text is not duplicated into a second screen-reader channel: MmcaThemeProviders @@ -2396,21 +2543,21 @@

      MudToastService

    1428. Why it's built this way: keeping the vendor type behind a facade is what makes the component library swappable in principle and mockable in practice, and it is the reason the framework ships AddCommonUiFacades() as its own registration - (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:181-186), factored out so a + (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:197-202), factored out so a bUnit harness can register exactly these two services without pulling in the whole shared-UI surface (comment at lines 144-157). Every method returns void: a toast is fire-and-forget by design, and MudBlazor's ISnackbar.Add is synchronous.
    1429. Where it's used: registered by AddCommonUiFacades with TryAddScoped - (DependencyInjection.cs:183), which AddUIShared calls for every host - (DependencyInjection.cs:121). Consumers resolve IToastService, never this type: the framework's + (DependencyInjection.cs:199), which AddUIShared calls for every host + (DependencyInjection.cs:137). Consumers resolve IToastService, never this type: the framework's NotificationListener raises an incoming push as a persistent toast (MMCA.Common/Source/Presentation/MMCA.Common.UI/Components/Notifications/NotificationListener.razor:49), ResultUiExtensions.NotifyOnFailure turns a failed Result into one - (MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/ResultUiExtensions.cs:277), and ADC's + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/ResultUiExtensions.cs:280), and ADC's LiveEventListener uses the action shape for its reconnect prompt - (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Components/LiveEventListener.razor.cs:80,165). + (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Components/LiveEventListener.razor.cs:81,166). Its own behavior is pinned by MudToastServiceTests, which captures the options lambda and applies it to a fresh SnackbarOptions carrying MudBlazor's defaults, so the @@ -2454,19 +2601,19 @@

      IOAuthUISettings

      ADR-036, and the mobile callback variant in ADR-043.
    1430. Where it's used: AddUIShared() registers the no-op default with TryAddSingleton - (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:158, with the override + (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:174, with the override instructions in the comment at lines 133-134). The shared login page injects it (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Login.razor:11), guards each provider button on it (lines 86, 107, 128) and folds the three flags into one _hasExternalProviders value that decides whether the whole external-login block renders (line 167). MMCA.ADC registers ConfigurationOAuthUISettings on all three heads - (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:84, + (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:85, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Program.cs:38, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/MauiProgram.cs:99).
    1431. PendingAttempt

      -

      MMCA.Common.UI · MMCA.Common.UI.Services.Auth.OAuth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/OAuth/OAuthFlowStateStore.cs:89 · Level 0 · record (private, sealed)

      +

      MMCA.Common.UI · MMCA.Common.UI.Services.Auth.OAuth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/OAuth/OAuthFlowStateStore.cs:92 · Level 0 · record (private, sealed)

      • What it is: the private record OAuthFlowStateStore persists while an OAuth @@ -2474,7 +2621,7 @@

        PendingAttempt

        timestamp used to expire an abandoned attempt).
      • Depends on: nothing beyond the BCL (string, DateTimeOffset).
      • Where it's used: written and read only inside - OAuthFlowStateStore (OAuthFlowStateStore.cs:80, 100); it never crosses the + OAuthFlowStateStore (OAuthFlowStateStore.cs:82, 100); it never crosses the store's own boundary.

      ISessionCookieSync

      @@ -2483,9 +2630,13 @@

      ISessionCookieSync

      • What it is: a two-method contract for mirroring the client's in-memory tokens into the browser's - HttpOnly auth cookies, and for clearing them again on logout.
      • + HttpOnly auth cookies, and for clearing them again on logout. Both methods report whether the + cookie jar was actually updated.
      • Depends on: nothing first-party. Implemented by - JsFetchSessionCookieSync; consumed by + JsFetchSessionCookieSync and, on a Blazor Server host that enabled + the same-origin proxy, by HandoffSessionCookieSync + (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/HandoffSessionServices.cs:39); + consumed by WasmTokenStorageService and by ServerTokenStorageService (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Services/ServerTokenStorageService.cs:21).
      • @@ -2501,22 +2652,69 @@

        ISessionCookieSync

        This contract is the reason a bookmarked authorized route renders instead of redirecting.
      -
    1432. Walkthrough: two members, both returning a bare Task because neither has anything to report. - SyncAsync(accessToken, refreshToken) writes the pair (ISessionCookieSync.cs:10) and - ClearAsync() removes it (line 12).
    1433. +
    1434. Walkthrough: two members, both returning Task<bool>: true when the cookie jar was + updated, false when the write failed or could not be attempted (a non-2xx answer, a dropped + connection, JS interop unavailable). + SyncAsync(accessToken, refreshToken) writes the pair (ISessionCookieSync.cs:15) and + ClearAsync() removes it (line 20). The two callers treat the answers differently on purpose. + A failed sync is surfaced: WasmTokenStorageService throws + InvalidOperationException when SyncAsync returns false + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/Tokens/WasmTokenStorageService.cs:68-71), + so AuthUIService reports Auth.TokenStorageUnavailable instead of a sign-in + that silently ends at the first access-token expiry, when no cookie exists to refresh from + (comment at lines 64-67; ServerTokenStorageService does the same at + ServerTokenStorageService.cs:89). A failed clear is discarded (_ = await sessionCookieSync.ClearAsync(), WasmTokenStorageService.cs:80), because sign-out proceeds + locally whatever the cookie endpoint answered.
    1435. Why it's built this way: the shape is the client half of ADR-022, which decided the BFF-style mmca_auth_access / mmca_auth_refresh HttpOnly cookie pair and the /auth/session/token hydration endpoint. Keeping it an interface (rather than calling JS interop inline from token storage) is what lets a bUnit or unit test drive the storage services with a mock and no browser, which WasmTokenStorageServiceTests does - (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/Auth/WasmTokenStorageServiceTests.cs:27).
    1436. + (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/Auth/WasmTokenStorageServiceTests.cs:29).
    1437. Where it's used: registered by the dedicated extension AddClientAuthSessionCookieSync(), which TryAddScopeds JsFetchSessionCookieSync - (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:193-197); the doc there + (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:209-213); the doc there records that both the Blazor Server host and the WebAssembly client call it (lines 165-169).
    1438. +

      SameOriginProxyHeaders

      +
      +

      MMCA.Common.UI · MMCA.Common.UI.Services.Auth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/SameOriginProxyHeaders.cs:11 · Level 0 · class (static)

      +
      +
        +
      • What it is: two constants naming the fixed CSRF request header the same-origin API proxy + requires on every state-changing request: CsrfHeaderName = "X-CSRF" + (SameOriginProxyHeaders.cs:14) and CsrfHeaderValue = "1" (line 17).
      • +
      • Depends on: nothing. It lives in MMCA.Common.UI rather than MMCA.Common.UI.Web so the + client that stamps the header and the server that checks it share one definition.
      • +
      • Concept introduced, a custom header as a CSRF signal. A cross-site page can make a browser send + a simple POST carrying the user's cookies, but it cannot add a custom header without a CORS + preflight. The proxy answers OPTIONS itself with no CORS grant and refuses any request whose + Origin or Sec-Fetch-Site names another origin, so the header is defense in depth behind that + origin check; the value carries no secret (SameOriginProxyHeaders.cs:3-10).
          +
        • [Rubric §26, Front-End Security] assesses cookie-borne credentials against cross-site request + forgery. Once the bearer moves into an HttpOnly cookie, a header the browser will not attach on a + cross-site request is what distinguishes the app's own calls.
        • +
        +
      • +
      • Walkthrough: two const string members and no behavior. Being const, both values are baked + into each caller at compile time.
      • +
      • Why it's built this way: one shared type instead of a literal per call site keeps the stamp + and the check from drifting apart. The proxy design is + ADR-131.
      • +
      • Where it's used: stamped by SameOriginProxyRequestHandler + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/SameOriginProxyRequestHandler.cs:19-20) + and by NotificationHubService on the SignalR connection options + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Notifications/NotificationHubService.cs:363); + checked by SameOriginApiProxyEndpoint in HasCsrfHeader, which + demands exactly one value equal to CsrfHeaderValue + (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpoint.cs:110-113); + stripped before forwarding by SameOriginProxyTransformer + (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginProxyTransformer.cs:61). + Covered by SameOriginProxyClientTests + (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/SameOriginProxyClientTests.cs:26).
      • +

      UserAgentSummary

      MMCA.Common.UI · MMCA.Common.UI.Services.Auth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/UserAgentSummary.cs:18 · Level 0 · class (internal, static)

      @@ -2560,7 +2758,7 @@

      UserAgentSummary

      internal because nothing outside the package should treat it as a UA parser.
    1439. Where it's used: exactly one call site, Sessions.DescribeDevice - (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Sessions.razor.cs:181), whose switch + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Sessions.razor.cs:218), whose switch covers all four null combinations and falls back to a localized "unknown device" string (lines 183-189). Pinned by UserAgentSummaryTests (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/Auth/UserAgentSummaryTests.cs:13). @@ -2604,12 +2802,12 @@

      ConfigurationOAuthUISettings

      ADR-036.
    1440. Where it's used: MMCA.ADC registers it with AddSingleton (which replaces the framework's TryAddSingleton default regardless of ordering) on the Blazor Server head - (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:84), the WASM client + (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:85), the WASM client (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Program.cs:38) and MAUI (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/MauiProgram.cs:99, whose comment at line 78 explains that the MAUI registration goes before AddUIShared because that call TryAdds the default). The server head also projects the resolved flags to the WASM client through its /client-config - endpoint (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:247).
    1441. + endpoint (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:259).

      DefaultOAuthUISettings

      @@ -2632,7 +2830,7 @@

      DefaultOAuthUISettings

      to enable specific providers (lines 3-6).
    1442. Why it's built this way: internal because nothing outside the package should name it, and a semicolon body because the default interface members already say everything. It is registered with - TryAddSingleton (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:158), so + TryAddSingleton (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:174), so a host that registers first keeps its own, and a host that registers afterwards with AddSingleton wins the resolution.
    1443. Where it's used: resolved as IOAuthUISettings in every host that has not @@ -2648,49 +2846,52 @@

      OAuthFlowStateStore

      callback returns matches. It is the anti-CSRF half of the flow described by ADR-036.
    1444. Depends on: ILocalCacheStore (device-local storage, injected, OAuthFlowStateStore.cs:20) for persistence, TimeProvider (injected, defaulted to TimeProvider.System, OAuthFlowStateStore.cs:20, - 56) for the expiry check, System.Security.Cryptography.RandomNumberGenerator to mint the state + 31) for the expiry check, System.Security.Cryptography.RandomNumberGenerator to mint the state value, and its own private record PendingAttempt as the stored shape.
    1445. Concept introduced, device-local state binding for a redirect-based flow. A browser or MAUI OAuth challenge leaves the app entirely and comes back on a different navigation, so nothing in memory survives the round trip; the only thing that can prove the callback belongs to the request this client made is a value written to durable storage before the redirect and checked after it. [Rubric §11, Security] assesses this exact class of defense: BeginAsync mints the value with - RandomNumberGenerator.GetHexString(32, lowercase: true) (OAuthFlowStateStore.cs:78), a + RandomNumberGenerator.GetHexString(32, lowercase: true) (OAuthFlowStateStore.cs:53), a cryptographically strong source, not Guid.NewGuid() or a counter.
    1446. Walkthrough:
        -
      • StorageKey = "auth.oauth-flow" (OAuthFlowStateStore.cs:48) and AttemptLifetime = 10 minutes - (OAuthFlowStateStore.cs:54, "comfortably longer than a provider round trip and far shorter than a +
      • StorageKey = "auth.oauth-flow" (OAuthFlowStateStore.cs:23) and AttemptLifetime = 10 minutes + (OAuthFlowStateStore.cs:29, "comfortably longer than a provider round trip and far shorter than a session, so an abandoned attempt cannot be revived days later").
      • -
      • IsEnforced (OAuthFlowStateStore.cs:64) reports store.IsAvailable: false only on a host that +
      • IsEnforced (OAuthFlowStateStore.cs:39) reports store.IsAvailable: false only on a host that registered neither the browser nor the native local-storage capability, in which case the caller keeps its prior behavior because nothing durable can be written across the redirect.
      • -
      • BeginAsync (OAuthFlowStateStore.cs:71) returns null immediately when storage is unavailable - (73-76), otherwise mints the state, stores a PendingAttempt under StorageKey, and returns the - state for the caller to append to the challenge URL (78-83).
      • -
      • TryCompleteAsync (OAuthFlowStateStore.cs:93) returns true unconditionally when storage is - unavailable (95-98, matching BeginAsync's no-op), otherwise reads and unconditionally removes the - pending attempt (100-101, "removed either way, so a value is good for exactly one completion"), - fails if there was none or it expired (103-106), and otherwise accepts either an exact match or an - empty returnedState (108-111, some redirects cannot carry the parameter back).
      • +
      • BeginAsync (OAuthFlowStateStore.cs:46) returns null immediately when storage is unavailable + (48-51), otherwise mints the state, stores a PendingAttempt under StorageKey, and returns the + state for the caller to append to the challenge URL (53-58).
      • +
      • TryCompleteAsync (OAuthFlowStateStore.cs:70) returns true unconditionally when storage is + unavailable (72-75, matching BeginAsync's no-op), otherwise reads and unconditionally removes the + pending attempt (77-78, "removed either way, so a value is good for exactly one completion"), + and fails if there was none or it expired (80-83). When a pending attempt exists, it accepts only + a non-empty returnedState that matches exactly, ordinal comparison (88-89). An absent or empty + value is refused: every legitimate flow round-trips the state, and accepting an omitted parameter + would let a pasted link bypass the binding (comment at 85-87, doc at 61-66).
    1447. Why it's built this way: ADR-036 (Website/docs-src/adr/036-external-oauth-login.md) is the OAuth - design this binds into; the single-use, time-boxed local record is what keeps a replayed or stale - callback from being accepted after the window in which it could plausibly be legitimate.
    1448. + design this binds into; the single-use, time-boxed local record, together with refusing a completion + that omits the state, is what keeps a replayed, stale, or forged callback from being accepted.
    1449. Where it's used: constructed inside MMCA.Common/Source/Presentation/MMCA.Common.UI.Maui/Capabilities/Auth/MauiExternalAuthBroker.cs for the native OAuth callback path, and registered in - MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs. Pinned by + MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:148 (TryAddScoped). Pinned by OAuthFlowStateStoreTests (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/Auth/OAuthFlowStateStoreTests.cs).
    1450. AuthDelegatingHandler

      -

      MMCA.Common.UI · MMCA.Common.UI.Services.Auth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/AuthDelegatingHandler.cs:10 · Level 1 · class (sealed)

      +

      MMCA.Common.UI · MMCA.Common.UI.Services.Auth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/AuthDelegatingHandler.cs:12 · Level 1 · class (sealed)

      • What it is: the DelegatingHandler that attaches the stored JWT as a Bearer header on every - outgoing API request, so no UI service ever sets an Authorization header by hand.
      • + outgoing API request, so no UI service ever sets an Authorization header by hand. One request can + opt out through the SkipBearer request option.
      • Depends on: ITokenStorageService (its only constructor parameter); externals System.Net.Http.Headers.AuthenticationHeaderValue and DelegatingHandler.
      • Concept introduced, the HTTP message-handler pipeline. HttpClient composes handlers into a @@ -2704,23 +2905,38 @@

        AuthDelegatingHandler

        refresh, or expiry; it asks storage for whatever token exists and moves on.
      -
    1451. Walkthrough: SendAsync(request, cancellationToken) (AuthDelegatingHandler.cs:14) awaits - GetAccessTokenAsync() (line 17), and only when the result is non-blank sets - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token) (lines 18-21) - before delegating to base.SendAsync (line 23). An anonymous call therefore goes out with no header - at all rather than an empty one, which matters for the endpoints that are deliberately anonymous. +
    1452. Walkthrough:
        +
      • SkipBearer (AuthDelegatingHandler.cs:20) is a public static + HttpRequestOptionsKey<bool> named "MMCA.Common.UI.Auth.SkipBearer". The token refresh POST + sets it: that endpoint is anonymous, and reading token storage from inside a refresh would + re-enter the very acquisition that is waiting for the request to complete (doc at lines 15-19).
      • +
      • SendAsync(request, cancellationToken) (AuthDelegatingHandler.cs:23) first checks the option: + when it is set to true the request passes straight to base.SendAsync without touching token + storage at all (lines 27-30). Otherwise it awaits GetAccessTokenAsync() (line 32), and only + when the result is non-blank sets + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token) (lines 33-36) + before delegating to base.SendAsync (line 38). An anonymous call therefore goes out with no + header at all rather than an empty one, which matters for the endpoints that are deliberately + anonymous. Note the freshness work happens inside the token service: by the time this handler sees a token, a stale one has already been re-acquired.
      • +
      +
    1453. Why it's built this way: a handler rather than a base-class helper, because the pipeline applies to everything the named client sends, including calls made by code that never inherits from a framework base. It is registered AddTransient (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:92), the lifetime AddHttpMessageHandler expects.
    1454. Where it's used: added to the "APIClient" pipeline alongside the culture handler - (DependencyInjection.cs:116-117, with the intent stated at lines 75-76). One documented bypass + (DependencyInjection.cs:124-125, with the intent stated at lines 75-76). One documented bypass exists: AuthenticatedServiceBase builds a client with the token set directly for the cases where the pipeline is not in play - (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/AuthenticatedServiceBase.cs:47). Covered + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/AuthenticatedServiceBase.cs:53). The + SkipBearer option is set by DirectApiTokenRefresher on its refresh + request (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/Tokens/DirectApiTokenRefresher.cs:46). + On a WebAssembly client of a host running the same-origin proxy, + SameOriginProxyRequestHandler sits inside this handler and + strips whatever header it attached (DependencyInjection.cs:126-133). Covered by AuthDelegatingHandlerTests (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/Auth/AuthDelegatingHandlerTests.cs:16) and by a DI resolution test that exists because the pipeline must be able to construct it @@ -2728,7 +2944,7 @@

      AuthDelegatingHandler

      JsFetchSessionCookieSync

      -

      MMCA.Common.UI · MMCA.Common.UI.Services.Auth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/JsFetchSessionCookieSync.cs:11 · Level 1 · class (sealed)

      +

      MMCA.Common.UI · MMCA.Common.UI.Services.Auth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/JsFetchSessionCookieSync.cs:13 · Level 1 · class (sealed)

      • What it is: the ISessionCookieSync implementation. It calls two small JS @@ -2741,33 +2957,37 @@

        JsFetchSessionCookieSync

        the server, so a server-issued HTTP call would put the cookie in the server's own handler, not the user's browser. Routing the call through JS interop makes the browser the one issuing the request, which is the only way the Set-Cookie reaches the right cookie jar - (JsFetchSessionCookieSync.cs:5-9).
          + (JsFetchSessionCookieSync.cs:6-8).
          • [Rubric §26, Front-End Security] again: the tokens transit JS only for this one same-origin POST and are never persisted anywhere JS can read afterwards.
          • [Rubric §29, Resilience & Business Continuity] assesses degradation. Every interop failure is - absorbed, so a prerender pass or a disconnected circuit cannot throw out of a token write.
          • + caught and turned into a false answer, so a prerender pass or a disconnected circuit cannot throw + out of a cookie write, yet the caller still learns the write did not happen.
        • Walkthrough:
            -
          • IsInteropUnavailable(ex) (JsFetchSessionCookieSync.cs:13) is the shared exception filter, +
          • IsInteropUnavailable(ex) (JsFetchSessionCookieSync.cs:15-16) is the shared exception filter, naming the four types that mean "there is no live JS runtime right now": InvalidOperationException, JSDisconnectedException, JSException and - OperationCanceledException (line 14).
          • -
          • SyncAsync(accessToken, refreshToken) (line 16) invokes mmcaAuthCookie.set with both tokens - (line 20) and swallows an interop failure, the comment noting the cookie will be synced on the - next write (lines 22-25).
          • -
          • ClearAsync() (line 28) invokes mmcaAuthCookie.clear (line 32) under the same filter, the - comment noting the cookie will still be cleared when the user next logs in or the token expires - (lines 34-37).
          • + OperationCanceledException. +
          • SyncAsync(accessToken, refreshToken) (line 19) returns + InvokeAsync<bool>("mmcaAuthCookie.set", accessToken, refreshToken) (line 23): the script itself + reports whether the endpoint answered 2xx. An interop failure returns false, the comment noting + nothing was written (lines 25-29).
          • +
          • ClearAsync() (line 33) returns InvokeAsync<bool>("mmcaAuthCookie.clear") (line 37) under the + same filter, and an interop failure again returns false because the cookie was not cleared by + this call (lines 39-43). The class doc records that the script tries the clear twice + (JsFetchSessionCookieSync.cs:9-11).
        • Why it's built this way: a shared static filter rather than two duplicated when clauses keeps - the definition of "interop unavailable" in one place, and swallowing rather than rethrowing matches - the fact that this is a mirror of state that already exists in memory. The cookie contract itself + the definition of "interop unavailable" in one place. Reporting rather than swallowing silently is + what lets the token storage services refuse a sign-in whose cookie never landed (see + ISessionCookieSync). The cookie contract itself belongs to ADR-022.
        • Where it's used: TryAddScoped behind ISessionCookieSync by AddClientAuthSessionCookieSync() - (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:195); consumed by + (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:211); consumed by WasmTokenStorageService and ServerTokenStorageService.
        • Caveats / not-in-source: the mmcaAuthCookie.set / .clear JS implementations live in @@ -2830,6 +3050,42 @@

          JwtAuthenticationStateProvider

          above would not match a mock (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/Auth/AuthUIServiceTests.cs:67-69).
        +

        SameOriginProxyRequestHandler

        +
        +

        MMCA.Common.UI · MMCA.Common.UI.Services.Auth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/SameOriginProxyRequestHandler.cs:11 · Level 1 · class (internal, sealed)

        +
        +
          +
        • What it is: the innermost handler of the "APIClient" pipeline on a WebAssembly client whose + host runs the same-origin API proxy. It removes any Authorization header and stamps the proxy's + CSRF header on every request (SameOriginProxyRequestHandler.cs:3-10).
        • +
        • Depends on: SameOriginProxyHeaders for the header name and value; + external DelegatingHandler.
        • +
        • Concept introduced, the browser holds no usable bearer. Behind the proxy the client only ever + holds a claims-only token; the proxy attaches the real bearer server-side from the HttpOnly session + cookie. Any Authorization header the outer handlers (such as + AuthDelegatingHandler) or a service's DefaultRequestHeaders attached is + therefore noise at best, so this handler clears it rather than trusting every caller to know.
            +
          • [Rubric §26, Front-End Security] assesses whether a usable credential is reachable from browser + code. Here it is not, and the transport enforces that for every request.
          • +
          +
        • +
        • Walkthrough: one override. SendAsync(request, cancellationToken) + (SameOriginProxyRequestHandler.cs:14) guards request with ArgumentNullException.ThrowIfNull + (line 16), sets request.Headers.Authorization = null (line 18), removes any existing CSRF header + and adds exactly one with TryAddWithoutValidation (lines 19-20), then delegates to + base.SendAsync (line 21). Removing first guarantees a single value, which is what the proxy's + check demands.
        • +
        • Why it's built this way: registering it innermost means it sees the request after every other + handler and service has had its say, so one place decides what reaches the proxy. It is + registered only when Api:SameOriginApiEndpoint is configured, so every other client (Blazor + Server, MAUI, a WebAssembly client of a host that did not opt in) keeps its pipeline unchanged + (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:94-99). The proxy design is + ADR-131.
        • +
        • Where it's used: AddTransient and AddHttpMessageHandler inside AddUIShared() behind the + usesSameOriginProxy check (DependencyInjection.cs:128-133), after the auth and culture handlers + (lines 124-125). The proxy side that reads the CSRF header is + SameOriginApiProxyEndpoint.
        • +

        IEmailConfirmationUIService

        MMCA.Common.UI · MMCA.Common.UI.Services.Auth · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/IEmailConfirmationUIService.cs:21 · Level 3 · interface

        @@ -2917,8 +3173,8 @@

        IAuthUIService

        • What it is: the single client-side authentication contract: login, register, OAuth code - exchange, logout, refresh, the three password flows, and the signed-in-devices list with revoke. - Every auth page in the framework talks to this and nothing else.
        • + exchange, logout, refresh, the three password flows, the signed-in-devices list with revoke, and + sign-out of every device. Every auth page in the framework talks to this and nothing else.
        • Depends on: Result and its generic form from MMCA.Common.Shared.Abstractions; the MMCA.Common.Shared.Auth contracts LoginRequest, @@ -2946,33 +3202,39 @@

          IAuthUIService

        • LoginAsync(LoginRequest, ct) and RegisterAsync(RegisterRequest, ct) both return Result<AuthenticationResponse> and both store tokens on success (IAuthUIService.cs:21,24).
        • -
        • ExchangeOAuthCodeAsync(code, ct) (line 29) trades a single-use completion code carried in the +
        • ExchangeOAuthCodeAsync(code, ct) (line 31) trades a single-use completion code carried in the redirect URL for the token pair through auth/oauth/exchange, stores the tokens and notifies auth state; the doc's closing sentence names the reason for the indirection, keeping tokens out of the - address bar (lines 24-28).
        • -
        • LogoutAsync() (line 37) is the deliberate no-Result member: it revokes the server-side refresh + address bar (lines 26-30).
        • +
        • LogoutAsync() (line 39) is the deliberate no-Result member: it revokes the server-side refresh sessions and clears local storage, and returns nothing because the local sign-out happens whatever the server answered. A user who asked to leave must never be kept signed in by a failed network - call (lines 31-36).
        • -
        • TryRefreshTokenAsync(ct) (line 45) is the deliberate bool member: it makes no API call of its + call (lines 33-38).
        • +
        • TryRefreshTokenAsync(ct) (line 47) is the deliberate bool member: it makes no API call of its own, since the host's ITokenRefresher owns the exchange, and its two states - ("session still live", "session gone") are not errors to render (lines 39-44).
        • -
        • ChangePasswordAsync(currentPassword, newPassword, ct) (line 48) hits auth/password.
        • -
        • RequestPasswordResetAsync(email, ct) (line 55) hits the anonymous auth/forgot-password. The + ("session still live", "session gone") are not errors to render (lines 41-46).
        • +
        • ChangePasswordAsync(currentPassword, newPassword, ct) (line 50) hits auth/password.
        • +
        • RequestPasswordResetAsync(email, ct) (line 57) hits the anonymous auth/forgot-password. The doc pins the semantics: the endpoint answers 202 for every well-formed address as an anti-enumeration measure, so a success means "accepted", never "an account exists" - (lines 50-54).
        • -
        • ResetPasswordAsync(email, token, newPassword, ct) (line 62) completes the reset via the + (lines 52-56).
        • +
        • ResetPasswordAsync(email, token, newPassword, ct) (line 64) completes the reset via the anonymous auth/reset-password; an invalid, expired or already-consumed token comes back as a - failure carrying the server's generic message (lines 57-61).
        • -
        • GetSessionsAsync(ct) (line 70) returns + failure carrying the server's generic message (lines 59-63).
        • +
        • GetSessionsAsync(ct) (line 72) returns Result<IReadOnlyList<RefreshSessionSummaryResponse>> from auth/my-sessions, newest first, and documents that exactly one row can carry IsCurrent, resolved server-side from the access token's - sid claim (lines 64-69).
        • -
        • RevokeSessionAsync(sessionId, ct) (line 79) signs one device out via auth/revoke/{sessionId}; + sid claim (lines 66-71).
        • +
        • RevokeSessionAsync(sessionId, ct) (line 81) signs one device out via auth/revoke/{sessionId}; another account's session id (or a nonexistent one) answers 404 and arrives as an ErrorType.NotFound failure, while revoking an - already-revoked session succeeds (lines 72-76).
        • + already-revoked session succeeds (lines 74-80). +
        • RevokeAllSessionsAsync(ct) (line 91) signs out of every device via auth/revoke and, unlike + LogoutAsync, returns the server's answer as a Result. A failed revoke is reported rather than + hidden, and the local session is left in place so the caller can say so and let the user retry; + on success it also performs the same local sign-out LogoutAsync does (lines 83-90). The pair + is the clearest statement of the return-shape rule: the same endpoint, two members, because "leave + this device" must never fail while "end every session" must never claim success it did not get.
      • Why it's built this way: the interface is where the three return shapes are justified, and each @@ -2985,11 +3247,12 @@

        IAuthUIService

        ADR-036 and ADR-043.
      • Where it's used: registered TryAddScoped against AuthUIService by - AddUIShared() (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:124, the + AddUIShared() (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:140, the comment at line 108 noting TryAdd prevents duplicate registration when several hosts call in); injected by the shipped auth pages, including Login (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Login.razor) and - Sessions.
      • + Sessions, whose sign-out-everywhere action calls RevokeAllSessionsAsync + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Sessions.razor.cs:191).

      AuthUIService

      @@ -3004,16 +3267,16 @@

      AuthUIService

      Result carrying the server's own error text, so no page has to interpret an HttpResponseMessage.
    1455. Depends on: first-party - IAuthUIService (the contract it implements, AuthUIService.cs:43), + IAuthUIService (the contract it implements, AuthUIService.cs:51), ITokenStorageService (token persistence, injected at AuthUIService.cs:46), ITokenRefresher (the host-specific renewal path, AuthUIService.cs:47), JwtAuthenticationStateProvider (injected as the framework AuthenticationStateProvider base type at AuthUIService.cs:48 and pattern-matched back down), IPushRegistrationService (native push cleanup, AuthUIService.cs:49), - IUiReadCache (optional, defaulted to null at AuthUIService.cs:43), + IUiReadCache (optional, defaulted to null at AuthUIService.cs:50), ILocalCacheStore (optional device-local document cache, defaulted to null, - AuthUIService.cs:44-51, wiped on sign-out alongside the read cache), + AuthUIService.cs:51, wiped on sign-out alongside the read cache), HttpResultExecutor (transport-fault translation), ProblemDetailsResultReader (response translation), and the shared auth contracts @@ -3036,13 +3299,16 @@

      AuthUIService

      surface in the browser: here the only code that reads or writes tokens is this service plus AuthDelegatingHandler, both of them going through ITokenStorageService rather than doing JS interop of their own. - [Rubric §11, Security] assesses the end-to-end auth design: sign-out is deliberately local-first, - the remote revoke is best effort, and both the server call and the local clear are wrapped so a dropped - connection can never strand a user inside a session they asked to leave (AuthUIService.cs:111-145). + [Rubric §11, Security] assesses the end-to-end auth design. There are two sign-outs with opposite + failure policies. LogoutAsync is deliberately local-first: the remote revoke is best effort, and + both the server call and the local clear are wrapped so a dropped connection can never strand a user + inside a session they asked to leave (AuthUIService.cs:88-114, 320-359). RevokeAllSessionsAsync + is server-first: it reports a failed revoke and only signs out locally once the server confirmed, + so "signed out everywhere" is never claimed when it did not happen (AuthUIService.cs:118-139). [Rubric §19, State Management] assesses who owns mutable client state and when it is invalidated: this service is the single writer of auth state, and it is also the thing that empties the read cache, because on WebAssembly and MAUI the DI scope is the app lifetime, so cached rows would otherwise - outlive the account that fetched them (AuthUIService.cs:33-37, 124-127). + outlive the account that fetched them (AuthUIService.cs:33-37, 349-352). [Rubric §18, UI Architecture] sees the same shape the entity services use, a typed service over the named "APIClient" returning Result, so pages render failures with ResultUiExtensions instead of catching exceptions. @@ -3057,7 +3323,7 @@

      AuthUIService

      carried no access token, which means the response shape drifted. Both are const string, so tests and pages branch on them without duplicating literals. The private ApiClientName = "APIClient" (AuthUIService.cs:65) names the shared client registered in - MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:95.
    1456. + MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:102.
    1457. LoginAsync and RegisterAsync are one-liners over the private AuthenticateAsync, differing only in the relative URL, auth/login and auth/register (AuthUIService.cs:68-73).
    1458. ExchangeOAuthCodeAsync (AuthUIService.cs:76) guards the code client-side first: a blank code @@ -3067,70 +3333,79 @@

      AuthUIService

      (AuthUIService.cs:84). The single-use code arrives in the redirect URL, which is what keeps the tokens themselves out of the address bar (ADR-036, Website/docs-src/adr/036-external-oauth-login.md).
    1459. -
    1460. AuthenticateAsync (AuthUIService.cs:272) is the shared body of all three. It posts the credential +
    1461. AuthenticateAsync (AuthUIService.cs:266) is the shared body of all three. It posts the credential through HttpResultExecutor and reads the response with - ProblemDetailsResultReader.ReadAsync<AuthenticationResponse> (AuthUIService.cs:277-284), returns - early on failure (273-276), then checks the access token is actually present and fails with - MissingAccessTokenCode if it is not (279-283). Only then does it call + ProblemDetailsResultReader.ReadAsync<AuthenticationResponse> (AuthUIService.cs:271-278), returns + early on failure (280-283), then checks the access token is actually present and fails with + MissingAccessTokenCode if it is not (285-290). Only then does it call tokenStorageService.SetTokensAsync inside a try that converts an InvalidOperationException into - a TokenStorageUnavailableCode failure carrying the exception message as detail (285-298). The - point of that branch is that valid credentials nothing can hold are a failure, not a silent no-op. + a TokenStorageUnavailableCode failure carrying the exception message as detail (292-305). The + point of that branch is that valid credentials nothing can hold are a failure, not a silent no-op; + on browser hosts it also fires when the HttpOnly cookie write reports failure, because the token + storage services throw on a false from ISessionCookieSync. Finally it pattern-matches the injected AuthenticationStateProvider down to JwtAuthenticationStateProvider and calls - NotifyUserAuthentication(accessToken) (300-303, and + NotifyUserAuthentication(accessToken) (307-310, and MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/JwtAuthenticationStateProvider.cs:60). The is test rather than a cast is what lets a host register a different provider without breaking sign-in.
    1462. -
    1463. LogoutAsync (AuthUIService.cs:88) runs four steps, each isolated so a failure cannot stop the - next. First pushRegistration.UnregisterAsync() inside a bare catch (82-91): the Devices DELETE - is authenticated, so it has to happen while the access token is still valid, and it is a no-op on web - heads (ADR-044, Website/docs-src/adr/044-native-push-delivery.md). Second, if a token can be read, - a Bearer header is attached and auth/revoke is posted, again inside a catch (93-113). Third, - tokenStorageService.ClearTokensAsync() under catch (InvalidOperationException) for the - interop-unavailable case (115-122). Fourth, readCache?.Clear() followed by the private - ClearLocalCacheAsync() (which no-ops when localCache is null and swallows - InvalidOperationException the same way the token clear does, AuthUIService.cs:355-368), then - NotifyUserLogout() - (129-132, and .../Services/Auth/JwtAuthenticationStateProvider.cs:71). The two - #pragma warning disable CA1031 blocks (86-88, 107-109) are deliberate and annotated in place: - catching everything is the correct policy for a best-effort cleanup step.
    1464. -
    1465. TryRefreshTokenAsync (AuthUIService.cs:148) makes no HTTP call of its own. It asks - tokenRefresher.AcquireAccessTokenAsync for a token (141); browser hosts renew through the +
    1466. LogoutAsync (AuthUIService.cs:88) runs three steps, each isolated so a failure cannot stop the + next. First the private UnregisterPushAsync() (call at line 90). Second, if a token can be read, a + Bearer header is attached and auth/revoke is posted inside a bare catch (94-111). Third, the + private SignOutLocallyAsync() (line 114).
    1467. +
    1468. RevokeAllSessionsAsync (AuthUIService.cs:118) posts the same auth/revoke through + HttpResultExecutor on an authenticated client, reading it with the + non-generic reader (120-128). Only when the result is a success does it run UnregisterPushAsync() + then SignOutLocallyAsync(), in the same order as LogoutAsync so the push unregister still holds a + valid access token (130-136); either way it returns the server's Result (line 138). A failure + leaves the local session intact for the caller to report and retry.
    1469. +
    1470. TryRefreshTokenAsync (AuthUIService.cs:142) makes no HTTP call of its own. It asks + tokenRefresher.AcquireAccessTokenAsync for a token (147); browser hosts renew through the same-origin cookie proxy so the refresh token never reaches JS (SameOriginProxyTokenRefresher) and MAUI renews straight from secure storage (DirectApiTokenRefresher). A null or blank answer means the session is gone, which this method treats exactly like a sign-out: clear tokens, clear the read cache and the local cache (also through ClearLocalCacheAsync()), notify logout, return false - (143-164). A token that comes back is published with - NotifyUserAuthentication and answered with true (166-171). The bool return is the honest type - here, because neither outcome is an error a page would render (IAuthUIService.cs:41-47).
    1471. + (149-171). It spells these steps out inline rather than calling SignOutLocallyAsync(). A token + that comes back is published with NotifyUserAuthentication and answered with true (173-178). + The bool return is the honest type here, because neither outcome is an error a page would render + (IAuthUIService.cs:41-47).
    1472. The password trio all wrap HttpResultExecutor. - ChangePasswordAsync (175) is the only one that authenticates: it builds a + ChangePasswordAsync (182) is the only one that authenticates: it builds a ChangePasswordRequest and PUTs it to auth/password - (179-188). RequestPasswordResetAsync (191) and ResetPasswordAsync (208) deliberately use - the plain factory client with no Bearer header (197, 216), because a reset must not be bound to - whatever session happens to be open. The comment at 201-202 records the contract that matters: - auth/forgot-password answers 202 for every well-formed address, so a success never means "this - account exists".
    1473. -
    1474. The two session methods back the devices page. GetSessionsAsync (226) GETs auth/my-sessions + (189-193). RequestPasswordResetAsync (198) and ResetPasswordAsync (215) deliberately use + the plain factory client with no Bearer header (204, 223), because a reset must not be bound to + whatever session happens to be open (comment at 202-203). The comment at 208-209 records the + contract that matters: auth/forgot-password answers 202 for every well-formed address, so a + success never means "this account exists".
    1475. +
    1476. The two session methods back the devices page. GetSessionsAsync (233) GETs auth/my-sessions and reads it with the generic reader into IReadOnlyList<RefreshSessionSummaryResponse> - (234-235). RevokeSessionAsync (240) posts auth/revoke/{sessionId} and reads it with the - non-generic reader (249-250), because that endpoint answers 204 and + (241-242). RevokeSessionAsync (247) posts auth/revoke/{sessionId} and reads it with the + non-generic reader (254-257), because that endpoint answers 204 and ProblemDetailsResultReader's generic overload turns an empty 2xx body into an EmptyResponseCode failure - (MMCA.Common/Source/Core/MMCA.Common.Shared/Http/ProblemDetailsResultReader.cs:274-279). Picking + (MMCA.Common/Source/Core/MMCA.Common.Shared/Http/ProblemDetailsResultReader.cs:276-281). Picking the wrong overload there would turn every successful revoke into an error.
    1477. -
    1478. Three private helpers close the class. CreateAuthenticatedClientAsync (314) creates the APIClient +
    1479. Five private helpers close the class. UnregisterPushAsync (320) calls + pushRegistration.UnregisterAsync() inside a bare catch (322-331): the Devices DELETE is + authenticated, so it has to happen while the access token is still valid, and it is a no-op on web + heads (ADR-044, Website/docs-src/adr/044-native-push-delivery.md). SignOutLocallyAsync (338) + is the local half of every sign-out: tokenStorageService.ClearTokensAsync() under + catch (InvalidOperationException) for the interop-unavailable case (340-347), then + readCache?.Clear() and ClearLocalCacheAsync() (352-353), then NotifyUserLogout() (355-358, + and .../Services/Auth/JwtAuthenticationStateProvider.cs:71). The two + #pragma warning disable CA1031 blocks (106-108 in LogoutAsync, 326-328 in + UnregisterPushAsync) are deliberate and annotated in place: catching everything is the correct + policy for a best-effort cleanup step. CreateAuthenticatedClientAsync (367) creates the APIClient and sets DefaultRequestHeaders.Authorization from the stored token; its doc comment states plainly that it mirrors AuthenticatedServiceBase and cannot inherit it, because - this is not an entity service and takes a different dependency set (308-313). - ReadAccessTokenAsync (327) swallows InvalidOperationException from the store and returns null, + this is not an entity service and takes a different dependency set (361-366). + ReadAccessTokenAsync (380) swallows InvalidOperationException from the store and returns null, so an SSR prerender proceeds tokenless and lets the API answer 401 like any other failure - (333-338). ClearLocalCacheAsync (AuthUIService.cs:353) is the newest of the three: it no-ops - when localCache is null, otherwise calls localCache.ClearAsync() inside a - catch (InvalidOperationException) for the same JS-interop-gone case the token clear guards against - (355-368), and both LogoutAsync and TryRefreshTokenAsync call it right after readCache?.Clear() - so a device-local offline snapshot never survives to be read by the next account on the same device.
    1480. + (386-391). ClearLocalCacheAsync (AuthUIService.cs:397) no-ops when localCache is null, + otherwise calls localCache.ClearAsync() inside a catch (InvalidOperationException) for the same + JS-interop-gone case the token clear guards against (399-411), so a device-local offline snapshot + never survives to be read by the next account on the same device.
    1481. Why it's built this way: ADR-051 (Website/docs-src/adr/051-client-auth-token-lifecycle.md) is the @@ -3148,12 +3423,12 @@

      AuthUIService

      wording and ErrorType, so the page shows that rather than a client-invented message.
    1482. Where it's used: registered TryAddScoped<IAuthUIService, AuthUIService>() in - MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:124, so every host that adds + MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:140, so every host that adds the shared UI gets it. Consumers inside the shared UI are the auth pages (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Login.razor:204, Pages/Auth/Register.razor:161, Pages/Auth/OAuthComplete.razor:65, Pages/Auth/ForgotPassword.razor:81, Pages/Auth/ResetPassword.razor:122), the devices page - Sessions (Pages/Auth/Sessions.razor.cs:79, 119, 165), and both shells + Sessions (Pages/Auth/Sessions.razor.cs:79, 119, 165, 191), and both shells (Layout/MainLayout.razor:108, Layout/NavMenu.razor:185, which call LogoutAsync). Downstream, the ADC and Store profile pages call ChangePasswordAsync (Profile, @@ -3171,15 +3446,16 @@

      AuthUIService

      AuthUIServiceTests (AuthUIServiceTests.cs:488, 454) and the gallery stub. Renewal in a running app happens further down, inside the storage service and the refreshers, so this method is a public entry point that nothing currently enters. The - auth/revoke call in LogoutAsync is described in the comment as "fire-and-forget" but is in fact - awaited (AuthUIService.cs:116); the accurate reading is best-effort-and-ignored, and on a bad network - the awaited call can add its full timeout to a sign-out. That same call passes no CancellationToken, - because LogoutAsync takes none by design (IAuthUIService.cs:39). Finally, + auth/revoke call in LogoutAsync is described in the comment as "fire-and-forget" (lines 99-101) but + is in fact awaited (AuthUIService.cs:104); the accurate reading is best-effort-and-ignored, and on a + bad network the awaited call can add its full timeout to a sign-out. That same call passes no + CancellationToken, because LogoutAsync takes none by design (IAuthUIService.cs:39). Finally, CreateAuthenticatedClientAsync sets a DefaultRequestHeaders Bearer while AuthDelegatingHandler is already attaching one to every APIClient request - from the same store (DependencyInjection.cs:116, Services/Auth/AuthDelegatingHandler.cs:17-21), so + from the same store (DependencyInjection.cs:124, Services/Auth/AuthDelegatingHandler.cs:32-36), so the header is computed twice per authenticated call; both values come from the same source, so this is - redundancy rather than a defect.
    1483. + redundancy rather than a defect. Behind the same-origin proxy both are then stripped by + SameOriginProxyRequestHandler.

      CultureDelegatingHandler

      @@ -3220,7 +3496,7 @@

      CultureDelegatingHandler

      culture travels on calls made by code that has never heard of localization. It is registered transient in DependencyInjection (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:93) and appended to the - "APIClient" pipeline after the auth handler (DependencyInjection.cs:116-117). + "APIClient" pipeline after the auth handler (DependencyInjection.cs:124-125).
    1484. Where it's used: every request through the "APIClient" named client, which is every call made by EntityServiceBase<TEntityDTO, TIdentifierType>, ChildEntityServiceBase, @@ -3316,9 +3592,9 @@

      ISecureTokenStore

      services.AddScoped<ISecureTokenStore, MauiSecureTokenStore>() (MMCA.Common/Source/Presentation/MMCA.Common.UI.Maui/DependencyInjection.cs:99, with the rationale at lines 66-77). Injected into DirectApiTokenRefresher - (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/Tokens/DirectApiTokenRefresher.cs:21) and + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/Tokens/DirectApiTokenRefresher.cs:27) and mocked directly in DirectApiTokenRefresherTests - (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/Auth/DirectApiTokenRefresherTests.cs:31).
    1485. + (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/Auth/DirectApiTokenRefresherTests.cs:32).
    1486. Caveats / not-in-source: no browser host implements it. Resolving it on a Blazor Server or WASM head is a DI failure by design, not an oversight.
    1487. @@ -3398,7 +3674,7 @@

      ITokenStorageService

      AuthUIService, AuthenticatedServiceBase (which uses it for the direct-token path that bypasses the delegating handler, - MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/AuthenticatedServiceBase.cs:47) and + MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/AuthenticatedServiceBase.cs:53) and NotificationHubService for the SignalR access-token provider. Test hosts substitute StubTokenStorageService and NullTokenStorageService (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Gallery/Stubs/NullTokenStorageService.cs:8). @@ -3470,7 +3746,7 @@

      IUiReadCache

      land on Clear(). The cache is registered scoped, which is one instance per Blazor Server circuit but one per app lifetime on WebAssembly and MAUI, where the scope outlives a sign-out. The contract therefore states that the sign-out path calls Clear so one account's reads can never be - served to the next (IUiReadCache.cs:22-26,61-65). + served to the next (IUiReadCache.cs:22-26,80-84).
    1488. [Rubric §29, Resilience & Business Continuity] shows in the storage rule: only successful reads are stored, so a transient outage cannot pin an error in front of the user (IUiReadCache.cs:19-20).
    1489. Why the parameter is a string and not a Uri. The CA1054 suppression (lines 28-31) is @@ -3478,34 +3754,47 @@

      IUiReadCache

      as a relative URL, it is compared by ordinal prefix and stored verbatim, and System.Uri would re-encode and re-normalize it, which is exactly what must not happen to a key when the point is matching the server's key byte for byte.
    1490. +
    1491. The in-flight read race, and the generation counter that closes it. A read issued before a + write and completing after it would otherwise re-store exactly the value the write just + invalidated. Generation (line 40) is a counter that moves on every InvalidatePrefix and Clear; + a reader captures it before the GET and hands it back to the three-argument Set, which drops the + value when an invalidation happened in between (lines 34-39, 61-70). [Rubric §19, State Management & Data Flow] assesses exactly this kind of stale-write ordering.
    1492. -
    1493. Walkthrough: four members.
        -
      • bool TryGetFresh<T>(string url, out T? value) (line 42) reports a fresh hit; a miss, an expired +
      • Walkthrough: six members, two of them default interface members.
          +
        • long Generation => 0 (line 40) is the invalidation generation. The default body returns a + constant, so an implementation that does not track generations never drops a value.
        • +
        • bool TryGetFresh<T>(string url, out T? value) (line 50) reports a fresh hit; a miss, an expired entry, or a disabled cache all read as false, and the doc notes that a hit stored under a - different type also reads as a miss (line 37).
        • -
        • void Set<T>(string url, T value) (line 51) stores a successfully read value stamped with the + different type also reads as a miss (line 45).
        • +
        • void Set<T>(string url, T value) (line 59) stores a successfully read value stamped with the current time, and is a no-op when caching is disabled. The doc is explicit that only success values - are ever passed here (line 50).
        • -
        • void InvalidatePrefix(string routePrefix) (line 59) drops every entry whose key starts with the + are ever passed here (line 58).
        • +
        • void Set<T>(string url, T value, long generation) (line 70) stores only when no invalidation + happened since generation was read; its default body forwards to the two-argument Set and + ignores the generation (lines 61-65, 70).
        • +
        • void InvalidatePrefix(string routePrefix) (line 78) drops every entry whose key starts with the prefix, ordinally. That is how one endpoint's create, update or delete clears that endpoint's list, - paged, lookup and by-id entries in a single call (lines 53-58).
        • -
        • void Clear() (line 66) drops everything, for the sign-out case above.
        • + paged, lookup and by-id entries in a single call (lines 73-75). +
        • void Clear() (line 85) drops everything, for the sign-out case above.
      • Why it's built this way: an interface (rather than a concrete helper) is what lets the whole feature be optional. Both consumers take IUiReadCache? with a null default, so a host that registers nothing gets exactly the plain GET the read methods issued before a cache existed (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:47, - MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/AuthUIService.cs:43). Prefix + MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/AuthUIService.cs:50). Prefix invalidation rather than per-key invalidation matches how the framework's endpoints are shaped: one - resource owns one route prefix, so a write knows what it invalidated without enumerating the reads.
      • + resource owns one route prefix, so a write knows what it invalidated without enumerating the reads. + The two generation members are default interface members, so a host-supplied implementation that + does not care about the in-flight race only has to implement the original four.
      • Where it's used: registered TryAddScoped against UiReadCache by AddUIShared (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:63). Read through by EntityServiceBase<TEntityDTO, TIdentifierType>'s - GetCachedAsync (EntityServiceBase.cs:248-268) and invalidated by its InvalidateOnSuccess - (EntityServiceBase.cs:281-287); cleared on sign-out and on an unrefreshable session by - AuthUIService (AuthUIService.cs:138,168).
      • + GetCachedAsync (EntityServiceBase.cs:241-272), which captures Generation before the GET + (EntityServiceBase.cs:261) and stores through the three-argument Set (EntityServiceBase.cs:268), + and invalidated by its InvalidateOnSuccess (EntityServiceBase.cs:285-291); cleared on sign-out and + on an unrefreshable session by AuthUIService (AuthUIService.cs:162,352).
      • Caveats / not-in-source: nothing here is shared between users or between tabs. It is an in-memory per-scope cache, so a second browser tab on WebAssembly has its own instance and its own entries.
      @@ -3550,9 +3839,9 @@

      EndpointCultureApplier

      not-found page, which is exactly why MAUI heads register their own applier after AddUIShared.
    1494. Where it's used: registered as the default with TryAddScoped<ICultureApplier, EndpointCultureApplier>() - (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:144), with the + (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:160), with the comment above it - (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:140-143) recording that a hybrid head overrides it + (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:156-159) recording that a hybrid head overrides it afterwards. The MAUI replacement is MauiCultureApplier.
    1495. @@ -3608,60 +3897,6 @@

      MmcaCultureBootstrap

      MauiCultureInitializer.
      -

      DirectApiTokenRefresher

      -
      -

      MMCA.Common.UI · MMCA.Common.UI.Services.Auth.Tokens · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/Tokens/DirectApiTokenRefresher.cs:19 · Level 1 · class (sealed)

      -
      -
        -
      • What it is: the MAUI ITokenRefresher. It reads the token pair out of OS - SecureStorage, exchanges it against the API's cross-origin auth/refresh endpoint, and writes the - rotated pair back.
      • -
      • Depends on: ISecureTokenStore (deliberately, not - ITokenStorageService), - RefreshTokenRequest and - AuthenticationResponse from MMCA.Common.Shared.Auth; - externals IHttpClientFactory and System.Net.Http.Json.
      • -
      • Concept introduced, direct token handling where there is no DOM. The class doc justifies the - choice: MAUI has no browser and thus no XSS surface, so holding a refresh token client-side and - posting it is acceptable there in a way it is not in a browser - (DirectApiTokenRefresher.cs:7-10). This is the counterpart to - SameOriginProxyTokenRefresher, and reading the two together is - the clearest statement of the framework's per-platform threat model.
          -
        • [Rubric §11, Security] assesses per-platform credential handling, exactly as above.
        • -
        • [Rubric §1, SOLID], dependency direction: the second doc paragraph is unusually explicit - (lines 10-16). Every operation it performs is a raw read or write, so it takes the raw store; - taking ITokenStorageService instead would close the loop and let a - refresh re-enter the acquisition that started it.
        • -
        -
      • -
      • Walkthrough: AcquireAccessTokenAsync(cancellationToken) (DirectApiTokenRefresher.cs:25) is a - straight line of early returns, each producing null rather than an exception:
          -
        • read both tokens from the store (lines 26-27), and bail when either is blank (lines 29-32), so a - never-logged-in device costs one storage read and no network call;
        • -
        • create the named "APIClient" (ApiClientName, line 22) and POST a - RefreshTokenRequest carrying both tokens to the relative - auth/refresh (lines 34-36);
        • -
        • bail on any non-success status (lines 38-41), which is how a revoked or reused refresh token - arrives;
        • -
        • deserialize an AuthenticationResponse and bail when - the access token came back blank (lines 43-47);
        • -
        • persist the rotated pair through ISecureTokenStore.SetTokensAsync and - return the new access token (lines 49-50).
        • -
        -
      • -
      • Why it's built this way: the rotation-on-refresh shape it participates in is - ADR-097, which made - refresh sessions hashed, rotating and per device: writing the returned pair back is not an - optimization, it is required, because the old refresh token is dead after the exchange. The - using var httpClient (line 34) and the relative Uri both lean on the named client configured - once in AddUIShared (MMCA.Common.UI/DependencyInjection.cs:96-117).
      • -
      • Where it's used: registered on the MAUI head, - AddScoped<ITokenRefresher, DirectApiTokenRefresher>() - (MMCA.Store/Source/Hosts/UI/MMCA.Store.UI/MauiProgram.cs:97). Covered by - DirectApiTokenRefresherTests - (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/Auth/DirectApiTokenRefresherTests.cs:17), - which mocks the store and the HTTP handler (lines 22-34).
      • -

      SameOriginProxyTokenRefresher

      MMCA.Common.UI · MMCA.Common.UI.Services.Auth.Tokens · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/Tokens/SameOriginProxyTokenRefresher.cs:11 · Level 1 · class (sealed)

      @@ -3679,7 +3914,7 @@

      SameOriginProxyTokenRefresher

      server-side, and only the access token comes back over the wire (SameOriginProxyTokenRefresher.cs:5-9). The server half is SessionCookieEndpoints, which maps POST /auth/session/token - (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:50).
        + (MMCA.Common/Source/Presentation/MMCA.Common.API/SessionCookies/SessionCookieEndpoints.cs:58).
        • [Rubric §26, Front-End Security] assesses whether a long-lived credential is reachable from scripts. It is not: an XSS on this page can steal an access token that expires in minutes, not the refresh token behind it.
        • @@ -3791,13 +4026,19 @@

          WasmTokenStorageService

          (lines 46-47).
        • GetRefreshTokenAsync() (line 59) returns null unconditionally, an honest answer rather than a stub: in the browser there is nothing to return.
        • -
        • SetTokensAsync(accessToken, refreshToken) (line 61) stores the access token in memory and seeds - the HttpOnly cookies through ISessionCookieSync.SyncAsync (line 66); the - comment records that the refresh token transits JS only for that same-origin POST (lines 64-65).
        • -
        • ClearTokensAsync() (line 69) nulls the field and clears the cookies (lines 71-72).
        • -
        • HydrateAsync() (line 75) is one line of work: call +
        • SetTokensAsync(accessToken, refreshToken) (line 61) stores the access token in memory (line 63) + and seeds the HttpOnly cookies through ISessionCookieSync.SyncAsync + (line 68); the comment records that the refresh token transits JS only for that same-origin POST + (lines 64-65). A false from the cookie write throws InvalidOperationException (lines 68-71), + after the in-memory token is set, so AuthUIService reports + Auth.TokenStorageUnavailable instead of a login that silently signs out at the first + access-token expiry, when no cookie exists to refresh from (lines 65-67).
        • +
        • ClearTokensAsync() (line 74) nulls the field and clears the cookies (lines 76, 80). Clearing is + best-effort by contract, so a false from ClearAsync is discarded; the comment points a caller + that needs proof the session ended at IAuthUIService.RevokeAllSessionsAsync (lines 77-80).
        • +
        • HydrateAsync() (line 83) is one line of work: call ITokenRefresher.AcquireAccessTokenAsync, store the result, return it - (lines 77-78).
        • + (lines 85-86).
      • Why it's built this way: the class doc records that it was hoisted out of the app WASM clients @@ -3806,11 +4047,13 @@

        WasmTokenStorageService

        MMCA.Common.UI.Web (lines 3-10). The two share the skew constant, the Lock, and the same single-flight shape; the server one adds an HttpContext branch for the prerender pass. The cookie-only storage model is ADR-022.
      • -
      • Where it's used: registered on the WASM client, +
      • Where it's used: registered on the WASM clients, AddScoped<ITokenStorageService, WasmTokenStorageService>() - (MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web.Client/Program.cs:44). Covered by - WasmTokenStorageServiceTests, which drives the concurrency path directly - (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/Auth/WasmTokenStorageServiceTests.cs:16,104).
      • + (MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web.Client/Program.cs:45, + MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Program.cs:48). Covered by + WasmTokenStorageServiceTests, which drives the concurrency path directly and pins the failed + cookie write as a throw + (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/Auth/WasmTokenStorageServiceTests.cs:16,168).

      UiReadCache

      @@ -3836,7 +4079,12 @@

      UiReadCache

    1496. Ordinal comparison as a correctness requirement. The comment at lines 22-24 makes the point that two URLs differing only in case are two different requests to the server, so they must be two different entries here. That is why the default Dictionary<string, ...> comparer is left alone - and every prefix check passes StringComparison.Ordinal explicitly (lines 95, 127).
    1497. + and every prefix check passes StringComparison.Ordinal explicitly (lines 137, 170). +
    1498. One generation counter for the whole dictionary. _generation moves under _sync on every + InvalidatePrefix and Clear, and the comment at lines 29-30 states why it is one counter rather + than one per prefix: the cache holds tens of entries, and a global counter also covers Clear. The + cost is that a write to one endpoint also drops an in-flight read of an unrelated one, which is a + wasted store, never a stale one.
    1499. [Rubric §14, Testability] shows in the injected TimeProvider (line 16): TTL behavior is exercised with a fake clock rather than by sleeping, in UiReadCacheTests.
    1500. @@ -3844,23 +4092,31 @@

      UiReadCache

    1501. Walkthrough:
      • Fields: the Lock _sync (line 20), the entry dictionary mapping URL to a (object Value, DateTimeOffset StoredAt) tuple (line 25), the null-guarded _timeProvider - (line 26), and the eagerly unwrapped _options (line 27). Storing the value as object is what - lets one dictionary hold every read shape the app makes.
      • -
      • TryGetFresh<T> (lines 30-67) guards the URL, sets value = default, and short-circuits to - false when _options.Enabled is off (lines 36-39). It then takes the lock and applies three - exits: no entry (lines 45-48); an entry older than ResolveTtl(url), which is removed on the - way out (lines 50-54); and an entry whose stored value is not a T, which is also removed, because + (line 26), the eagerly unwrapped _options (line 27), and the long _generation counter + (line 31). Storing the value as object is what lets one dictionary hold every read shape the app + makes.
      • +
      • Generation (lines 34-43) reads _generation under the same lock that guards its increments.
      • +
      • TryGetFresh<T> (lines 46-83) guards the URL, sets value = default, and short-circuits to + false when _options.Enabled is off (lines 52-55). It then takes the lock and applies three + exits: no entry (lines 61-64); an entry older than ResolveTtl(url), which is removed on the + way out (lines 66-70); and an entry whose stored value is not a T, which is also removed, because the same URL read back as a different type means the caller changed shape and the stored value can - no longer answer the question (lines 56-62). Only then is it a hit (lines 64-65).
      • -
      • Set<T> (lines 70-85) is a no-op when caching is disabled or the value is null (line 74), - stamps GetUtcNow() outside the lock, and assigns inside it (lines 79-84).
      • -
      • InvalidatePrefix (lines 88-103) materializes the matching keys into a list under the lock before - removing them (lines 94-101), because removing while enumerating the same dictionary would throw.
      • -
      • Clear (lines 106-112) empties the dictionary under the lock.
      • -
      • ResolveTtl (lines 120-135) is the freshness lookup: it starts from + no longer answer the question (lines 72-78). Only then is it a hit (lines 80-81).
      • +
      • Set<T>(url, value) (lines 86-101) is a no-op when caching is disabled or the value is null + (line 90), stamps GetUtcNow() outside the lock, and assigns inside it (lines 95-100).
      • +
      • Set<T>(url, value, generation) (lines 104-126) has the same guards, then, inside the lock, + returns without storing when generation no longer equals _generation (lines 119-122): a write + invalidated the endpoint, or a sign-out cleared everything, while the read was in flight, so the + value may be exactly what the write made stale (lines 117-118). Otherwise it assigns (line 124).
      • +
      • InvalidatePrefix (lines 129-145) increments _generation (line 135) and materializes the + matching keys into a list under the lock before removing them (lines 136-143), because removing + while enumerating the same dictionary would throw.
      • +
      • Clear (lines 148-155) increments _generation (line 152) and empties the dictionary under the + lock.
      • +
      • ResolveTtl (lines 163-178) is the freshness lookup: it starts from UiReadCacheOptions.DefaultTtl and scans every configured route prefix, keeping the TTL of the - longest prefix the URL starts with (lines 125-132). The doc says why longest-match rather than - first-match (lines 114-118): a nested route can state a stricter budget than the endpoint above it, + longest prefix the URL starts with (lines 168-175). The doc says why longest-match rather than + first-match (lines 157-162): a nested route can state a stricter budget than the endpoint above it, whatever order the configuration happens to enumerate in.
    1502. @@ -3877,8 +4133,77 @@

      UiReadCache

      stale list corrects itself within one user's attention span.
    1503. Where it's used: resolved as IUiReadCache by EntityServiceBase<TEntityDTO, TIdentifierType> and - AuthUIService; covered by UiReadCacheTests - (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/Caching/UiReadCacheTests.cs:15).
    1504. + AuthUIService; covered by UiReadCacheTests + (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/Caching/UiReadCacheTests.cs:15), + whose generation cases drop a late Set after an invalidation and after a Clear + (UiReadCacheTests.cs:156,175). + +

      DirectApiTokenRefresher

      +
      +

      MMCA.Common.UI · MMCA.Common.UI.Services.Auth.Tokens · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/Tokens/DirectApiTokenRefresher.cs:25 · Level 2 · class (sealed)

      +
      +
        +
      • What it is: the MAUI ITokenRefresher. It reads the token pair out of OS + SecureStorage, exchanges it against the API's cross-origin auth/refresh endpoint, and writes the + rotated pair back.
      • +
      • Depends on: ISecureTokenStore (deliberately, not + ITokenStorageService), + AuthDelegatingHandler (for its SkipBearer request option), + RefreshTokenRequest and + AuthenticationResponse from MMCA.Common.Shared.Auth; + externals IHttpClientFactory and System.Net.Http.Json.
      • +
      • Concept introduced, direct token handling where there is no DOM. The class doc justifies the + choice: MAUI has no browser and thus no XSS surface, so holding a refresh token client-side and + posting it is acceptable there in a way it is not in a browser + (DirectApiTokenRefresher.cs:7-10). This is the counterpart to + SameOriginProxyTokenRefresher, and reading the two together is + the clearest statement of the framework's per-platform threat model.
          +
        • [Rubric §11, Security] assesses per-platform credential handling, exactly as above.
        • +
        • [Rubric §1, SOLID], dependency direction: the second doc paragraph is unusually explicit + (lines 10-16). Every operation it performs is a raw read or write, so it takes the raw store; + taking ITokenStorageService instead would close the loop and let a + refresh re-enter the acquisition that started it.
        • +
        • The same cycle through the HTTP pipeline. The third doc paragraph (lines 18-23) names the + indirect route back into that loop: the APIClient carries + AuthDelegatingHandler, which reads the storage service for a bearer. + The refresh POST therefore sets AuthDelegatingHandler.SkipBearer (line 46), so the anonymous + refresh call never reaches the storage instance that is awaiting it. The handler honors the option + by passing the request through untouched + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/AuthDelegatingHandler.cs:20,27).
        • +
        +
      • +
      • Walkthrough: AcquireAccessTokenAsync(cancellationToken) (DirectApiTokenRefresher.cs:31) is a + straight line of early returns, each producing null rather than an exception:
          +
        • read both tokens from the store (lines 33-34), and bail when either is blank (lines 36-39), so a + never-logged-in device costs one storage read and no network call;
        • +
        • create the named "APIClient" (ApiClientName, line 29), build an HttpRequestMessage POSTing a + RefreshTokenRequest carrying both tokens as + JsonContent to the relative auth/refresh (lines 41-45), mark it SkipBearer (line 46), and + send it (line 47);
        • +
        • bail on any non-success status (lines 49-52), which is how a revoked or reused refresh token + arrives;
        • +
        • deserialize an AuthenticationResponse and bail when + the access token came back blank (lines 54-58);
        • +
        • persist the rotated pair through ISecureTokenStore.SetTokensAsync and + return the new access token (lines 60-61).
        • +
        +
      • +
      • Why it's built this way: the rotation-on-refresh shape it participates in is + ADR-097, which made + refresh sessions hashed, rotating and per device: writing the returned pair back is not an + optimization, it is required, because the old refresh token is dead after the exchange. An explicit + HttpRequestMessage rather than PostAsJsonAsync is what gives the call an Options bag to carry + SkipBearer. The using var httpClient (line 41) and the relative Uri both lean on the named + client configured once in AddUIShared, which also attaches the delegating handler + (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:102,124).
      • +
      • Where it's used: registered on the MAUI heads, + AddScoped<ITokenRefresher, DirectApiTokenRefresher>() + (MMCA.Store/Source/Hosts/UI/MMCA.Store.UI/MauiProgram.cs:98, + MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/MauiProgram.cs:164). Covered by + DirectApiTokenRefresherTests + (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/Auth/DirectApiTokenRefresherTests.cs:18), + which stubs the store and the HTTP handler (lines 25-36) and asserts the SkipBearer mark on the + refresh POST (line 62).

      ChannelReferenceCounter

      @@ -3890,7 +4215,7 @@

      ChannelReferenceCounter

      SignalR server to join a group on the first join and to leave it only on the last matching leave.
    1505. Depends on: nothing first-party. It is a System.Threading.Lock plus a Dictionary<string, int> (BCL). It is owned as a private field by NotificationHubService - (NotificationHubService.cs:42), and sits beside (not inside) the separate handler bookkeeping that + (NotificationHubService.cs:51), and sits beside (not inside) the separate handler bookkeeping that ChannelSubscription unwinds.
    1506. Concept introduced, reference-counted group membership. [Rubric §19, State Management & Data Flow] assesses how a shared per-circuit resource is owned when more than one component holds it at once. A live channel is exactly that resource: an invisible layout listener and a page can both be @@ -3933,20 +4258,20 @@

      ChannelReferenceCounter

      connection. The ADR says the hub service tracks membership; this class is how that tracking is done so two concurrent holders cannot evict each other.
    1507. Where it's used: three call sites, all inside NotificationHubService: - AddRef in JoinChannelAsync (NotificationHubService.cs:197, deliberately counted before the + AddRef in JoinChannelAsync (NotificationHubService.cs:236, deliberately counted before the connection is started so the replay inside StartAsync sees it, line 196), Release in LeaveChannelAsync (line 229), and Snapshot in RejoinChannelsAsync (line 351). Covered directly by ChannelReferenceCounterTests - (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/Notifications/NotificationHubServiceTests.cs:320, + (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/Notifications/NotificationHubServiceTests.cs:381, whose summary names the H13 regression it locks down, lines 314-319). The two concurrent holders it exists for are real: LiveEventListener and the HappeningNow page both join the same event channel key.
    1508. Caveats / not-in-source: it counts joins only; it knows nothing about handlers. Subscriptions live in a separate _channelSubscriptions dictionary under a different lock - (NotificationHubService.cs:36,43), so disposing a ChannelSubscription + (NotificationHubService.cs:45,52), so disposing a ChannelSubscription does not decrement the count, and leaving a channel does not remove handlers - (NotificationHubService.cs:221-222 states that pairing requirement).
    1509. + (NotificationHubService.cs:260-261 states that pairing requirement).

      INotificationScopeProvider

      @@ -4085,7 +4410,7 @@

      NotificationState

    1510. Where it's used: injected into NotificationBell, which claims the slot with TryRegisterPoller(this), listens on OnPollerSlotFreed to take over, and gates its navigation refresh on IsStale - (MMCA.Common/Source/Presentation/MMCA.Common.UI/Components/Notifications/NotificationBell.razor.cs:53,56,107,119,155,173,256), + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Components/Notifications/NotificationBell.razor.cs:52,55,118,130,166,184,267), and into NotificationInbox; driven by real-time pushes that arrive over NotificationHubService. Covered by NotificationStateTests (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/Notifications/NotificationStateTests.cs:13).
    1511. @@ -4154,7 +4479,7 @@

      BackNavigationResult

      MauiBackNavigationBridge.
    1512. Concept introduced, the interop return contract. This record is also the wire shape of a single JS interop call: nav-interop.js's tryGoBack() returns an object that deserializes straight into - it (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Navigation/MauiBackNavigationBridge.cs:45), + it (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Navigation/MauiBackNavigationBridge.cs:48), so the C# type and the JS return value are one contract.
    1513. Walkthrough: public sealed record BackNavigationResult(bool Handled, bool AtRoot) (MauiBackNavigationBridge.cs:19). Handled is true when the WebView's history stack contained a @@ -4291,7 +4616,7 @@
    1514. Why it's built this way: AddUIShared registers this implementation with TryAddScoped - (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:150), so every head gets a + (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:166), so every head gets a working builder without opting in, and the one head that must differ replaces it afterwards: AddCommonMauiPublicLinkBuilder() registers MauiPublicLinkBuilder over the @@ -4328,21 +4653,27 @@

      MauiBackNavigationBridge

      renderer thread with access to the WebView's IJSRuntime (MauiBackNavigationBridge.cs:21-27).
    1515. Walkthrough: HandleBackPressedAsync(IJSRuntime js) (line 38) null-checks the runtime (ArgumentNullException.ThrowIfNull, line 40), dynamically imports - ./_content/MMCA.Common.UI/nav-interop.js (ModulePath, line 30) and invokes its tryGoBack() - helper, deserializing the answer into a BackNavigationResult - (lines 44-46). Three interop failure modes are caught explicitly and collapse to the same safe value + ./_content/MMCA.Common.UI/nav-interop.js (ModulePath, line 30) into an await using module + reference (line 47) and invokes its tryGoBack() helper, deserializing the answer into a + BackNavigationResult (lines 48-49). The module reference is disposed on + every call: the class is static, so nothing else owns it, and re-importing on each back press without + disposing would leak one JS object reference per press (comment at lines 44-46). Three interop + failure modes are caught explicitly and collapse to the same safe value new BackNavigationResult(Handled: false, AtRoot: true): InvalidOperationException when Blazor is - not yet hydrated (lines 48-52), JSDisconnectedException (lines 53-56), and JSException - (lines 57-60). A not-yet-ready WebView therefore reports "at root, not handled" and the host falls - back to its default back behavior.
    1516. + not yet hydrated (lines 51-55), JSDisconnectedException (lines 56-59, which also covers a dispose + on a torn-down circuit), and JSException (lines 60-63). A not-yet-ready WebView therefore reports + "at root, not handled" and the host falls back to its default back behavior.
    1517. Why it's built this way: a static helper with no state fits a one-shot interop call, and - returning a data record instead of throwing keeps the native handler branch-free. Collapsing the - three JS exception types into one safe default means an unhydrated or disconnected circuit never - crashes the native back button.
    1518. -
    1519. Where it's used: MAUI host projects call it from their page back-button handler; the returned - BackNavigationResult tells the host whether to exit the app.
    1520. -
    1521. Caveats / not-in-source: the nav-interop.js tryGoBack() implementation and the MAUI host - wiring live outside this unit; only the C# side of the bridge is visible here.
    1522. + returning a data record instead of throwing keeps the native handler branch-free. Because it holds no + state, it also cannot cache the imported module, which is why each call owns and disposes its own + reference. Collapsing the three JS exception types into one safe default means an unhydrated or + disconnected circuit never crashes the native back button. +
    1523. Where it's used: the MAUI host base page in MMCA.Common.UI.Maui + (MMCA.Common/Source/Presentation/MMCA.Common.UI.Maui/MainPageBase.cs:69) calls it from its + OnBackButtonPressed override (MainPageBase.cs:30); the returned BackNavigationResult tells the host + whether to exit the app.
    1524. +
    1525. Caveats / not-in-source: the nav-interop.js tryGoBack() implementation and the + MainPageBase dispatch logic live outside this unit; only the C# side of the bridge is visible here.
    1526. @@ -4394,7 +4725,7 @@

      ChannelSubscription

      -

      MMCA.Common.UI · MMCA.Common.UI.Services.Notifications · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Notifications/NotificationHubService.cs:412 · Level 2 · class (private, sealed, nested)

      +

      MMCA.Common.UI · MMCA.Common.UI.Services.Notifications · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Notifications/NotificationHubService.cs:490 · Level 2 · class (private, sealed, nested)

      • What it is: the disposable handle returned when a caller subscribes to a live channel on @@ -4409,7 +4740,7 @@

        ChannelSubscription

        [Rubric §1, SOLID] shows in the encapsulation: only the hub service can construct one, and only it knows how to remove one, so the bookkeeping has a single owner.
      • Walkthrough: a primary-constructor nested class capturing owner, channelKey and handler - (NotificationHubService.cs:412), exposing ChannelKey (line 414) and Handler (line 416) as + (NotificationHubService.cs:490), exposing ChannelKey (line 414) and Handler (line 416) as get-only properties. Dispose() (line 418) simply calls owner.RemoveSubscription(this), which takes the shared _channelSync lock, removes the entry, and prunes the channel's list once it empties (lines 373-386). The Handler property is what DispatchChannelEventAsync invokes on each @@ -4423,83 +4754,111 @@

        ChannelSubscription

      • Caveats / not-in-source: disposing a subscription unregisters the handler only. It does not release a channel join: those are counted separately by ChannelReferenceCounter, and the source states the pairing requirement - explicitly (NotificationHubService.cs:221-222).
      • + explicitly (NotificationHubService.cs:260-261).

      NotificationHubService

      -

      MMCA.Common.UI · MMCA.Common.UI.Services.Notifications · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Notifications/NotificationHubService.cs:26 · Level 2 · class (sealed, partial)

      +

      MMCA.Common.UI · MMCA.Common.UI.Services.Notifications · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Notifications/NotificationHubService.cs:34 · Level 2 · class (sealed, partial)

      • What it is: the client-side SignalR connection manager. It opens a connection to /hubs/notifications after login, invokes a callback for received notifications, and carries the ephemeral live-channel events that components join and subscribe to.
      • -
      • Depends on: ApiSettings (for the hub URL, via IOptions<ApiSettings>) and - ITokenStorageService (for the bearer token); +
      • Depends on: ApiSettings (for the hub URL, via IOptions<ApiSettings>, reading + SameOriginApiEndpoint first and ApiEndpoint second), + ITokenStorageService (for the bearer token on a direct connection) and + SameOriginProxyHeaders (the CSRF header on a proxied connection); ChannelReferenceCounter (membership counting) and ChannelSubscription (its subscription handle). Externals: - Microsoft.AspNetCore.SignalR.Client (HubConnection, HubConnectionBuilder), ILogger<T> with + Microsoft.AspNetCore.SignalR.Client (HubConnection, HubConnectionBuilder), + Microsoft.AspNetCore.Http.Connections.Client (HttpConnectionOptions), ILogger<T> with [LoggerMessage] source generation, System.Threading.Lock and SemaphoreSlim; implements IAsyncDisposable.
      • Concept introduced, resilient client-side real-time with re-joinable channels. [Rubric §6, CQRS & Event-Driven] extends to the browser here: the server pushes notifications and channel events over SignalR instead of the client polling for everything. - [Rubric §29, Resilience & Business Continuity] shows in four distinct mechanisms, each with its + [Rubric §29, Resilience & Business Continuity] shows in five distinct mechanisms, each with its rationale in source:
        • the initial connect retries with exponential backoff up to MaxRetries = 3 starting at - InitialRetryDelay of 2 seconds and doubling (lines 28, 71, 145-180), so a token-not-yet-ready or + InitialRetryDelay of 2 seconds and doubling (lines 36, 92, 184-219), so a token-not-yet-ready or API-still-starting race recovers;
        • -
        • WithAutomaticReconnect() (line 128) keeps long sessions alive, and because SignalR group +
        • WithAutomaticReconnect() (line 163) keeps long sessions alive, and because SignalR group membership does not survive a new connection, Reconnected re-joins every held channel - (lines 141-143, RejoinChannelsAsync lines 348-371);
        • + (lines 176-178, RejoinChannelsAsync lines 423-446); +
        • when that automatic reconnect schedule is exhausted the connection raises Closed with an error, + and the service runs a fresh serialized StartAsync through RestartAfterCloseAsync + (lines 180-182, 381-394), so live notifications do not stay dead for the rest of the session. A + Closed with a null error is a deliberate stop or dispose and is never restarted, and a + connection that never started raises no Closed, so the restart loop is bounded (lines 390-392; + class doc lines 27-32);
        • a terminal start failure discards the connection object (DiscardUnstartedConnectionAsync, - lines 310-319) instead of leaving it in the field, because the null guard at line 121 would - otherwise make every later StartAsync a permanent no-op (the comment at lines 169-171 records - exactly this);
        • -
        • StartAsync is serialized by a SemaphoreSlim (line 40), since two components calling + lines 370-379) instead of leaving it in the field, because a connection that never started must + not satisfy the guard in StartCoreAsync, or every later StartAsync (including one reached + through JoinChannelAsync) would no-op forever (comment at lines 208-210). The guard itself only + leaves a connected, connecting or reconnecting connection alone (line 151); a Disconnected one + is discarded and rebuilt (lines 148-159);
        • +
        • StartAsync is serialized by a SemaphoreSlim (line 49), since two components calling JoinChannelAsync at once on Blazor Server (which has no single synchronization context) could both see a null connection, both build one, and leak the loser socket with duplicate server - registrations (lines 76-83). + registrations (lines 103-110). [Rubric §13, Observability & Operability] applies too: every outcome is a source-generated - structured log (lines 388-410), and failures on the channel paths are logged, never thrown.
        • + structured log (lines 463-488, including LogReconnectExhausted at line 470), and failures on the + channel paths are logged, never thrown.
      • Walkthrough:
          -
        • Constants and fields: the four hub method names (lines 29-32), _channelSync guarding the - subscription dictionary (line 36), _startSync guarding start (line 40, a SemaphoreSlim rather - than a Lock because the guarded body awaits, lines 38-39), the - ChannelReferenceCounter (line 42), and _channelSubscriptions mapping - a channel key to its handler list (line 43).
        • -
        • NotificationCallback (line 51) is a settable Func<string, string, Task>? the host assigns to - surface a snackbar; IsConnected (line 65) reports the connection state; InitialRetryDelay - (line 71) is internal and settable so a test can exercise the terminal-failure path without - waiting out the real multi-second backoff (lines 67-70).
        • -
        • The constructor (lines 53-62) builds _hubUrl from ApiSettings.ApiEndpoint trimmed of its - trailing slash plus /hubs/notifications (line 61), throwing if the options are absent (line 60).
        • -
        • StartAsync (lines 85-117) bails when disposed (lines 87-90), takes _startSync - (tolerating ObjectDisposedException, lines 92-100), runs StartCoreAsync, and releases in a - finally that tolerates the same disposal race (lines 106-116).
        • -
        • StartCoreAsync (lines 119-181) returns immediately when a connection already exists (line 121), - builds the HubConnection with an AccessTokenProvider bound to - ITokenStorageService.GetAccessTokenAsync (line 127), registers ReceiveNotification fanning out - to NotificationCallback (lines 131-137) and ReceiveChannelEvent to DispatchChannelEventAsync - (line 139), wires the reconnect re-join (line 143), then runs the retry loop, which on success also - replays any channel joins requested before the connection came up (line 160).
        • -
        • JoinChannelAsync(channelKey) (lines 192-214) counts the join before starting the connection - so the replay inside StartAsync sees it (lines 196-197), then invokes the server JoinChannel - only on the first join and only when connected (lines 202-213).
        • -
        • LeaveChannelAsync(channelKey) (lines 225-244) is the mirror: it invokes LeaveChannel only when - Release reports the last outstanding leave (lines 229-243).
        • -
        • OnChannelEvent(channelKey, handler) (lines 255-273) creates a +
        • Constants and fields: the four hub method names (lines 37-40), _sameOriginProxyEndpoint + (line 43), _channelSync guarding the subscription dictionary (line 45), _startSync guarding + start (line 49, a SemaphoreSlim rather than a Lock because the guarded body awaits, + lines 47-48), the ChannelReferenceCounter (line 51), and + _channelSubscriptions mapping a channel key to its handler list (line 52).
        • +
        • NotificationCallback (line 60) is a settable Func<string, string, Task>? the host assigns to + surface a snackbar; IsConnected (line 86) reports the connection state; InitialRetryDelay + (line 92) is internal and settable so a test can exercise the terminal-failure path without + waiting out the real multi-second backoff (lines 88-91); ConnectionFactory (line 98) is an + internal test-only hook that replaces the HubConnectionBuilder call so a test can supply a + connection over an in-memory transport, and is null in production (lines 94-97).
        • +
        • The constructor (lines 62-77) null-checks the options (line 69), records + ApiSettings.SameOriginApiEndpoint (line 74), and builds _hubUrl from that endpoint, falling + back to ApiSettings.ApiEndpoint and throwing when neither is set (line 75), trimmed of its + trailing slash plus /hubs/notifications (line 76). UsesSameOriginProxy (line 83) reports which + path was taken. The same-origin path serves a WebAssembly client of an opted-in host: the hub is + reached through the UI host's /api proxy, which attaches the bearer from the HttpOnly session + cookie, so the connection carries no client-held token (comment at lines 71-73).
        • +
        • StartAsync (lines 112-144) bails when disposed (lines 114-117), takes _startSync + (tolerating ObjectDisposedException, lines 119-127), runs StartCoreAsync, and releases in a + finally that tolerates the same disposal race (lines 133-143).
        • +
        • StartCoreAsync (lines 146-220) returns immediately unless the existing connection is null or + Disconnected (line 151), discards a dead one (lines 156-159), then builds the HubConnection + from ConnectionFactory when set or from a HubConnectionBuilder configured by + ConfigureConnection (lines 161-164). It registers ReceiveNotification fanning out to + NotificationCallback (lines 166-172) and ReceiveChannelEvent to DispatchChannelEventAsync + (line 174), wires the reconnect re-join (line 178) and the exhausted-reconnect restart (line 182), + then runs the retry loop, which on success also replays any channel joins requested before the + connection came up (line 199).
        • +
        • ConfigureConnection(options) (lines 357-368) applies the transport options. On a direct + gateway connection it binds AccessTokenProvider to ITokenStorageService.GetAccessTokenAsync + (line 367). Through the same-origin proxy it sends no token at all and adds the + SameOriginProxyHeaders CSRF header (line 363): the negotiate call is a + POST that the proxy refuses without it, while the WebSocket upgrade (to which the browser cannot + add headers) is accepted by the proxy only with the page's own Origin (doc at lines 349-356).
        • +
        • JoinChannelAsync(channelKey) (lines 231-253) counts the join before starting the connection + so the replay inside StartAsync sees it (lines 235-236), then invokes the server JoinChannel + only on the first join and only when connected (lines 241-252).
        • +
        • LeaveChannelAsync(channelKey) (lines 264-283) is the mirror: it invokes LeaveChannel only when + Release reports the last outstanding leave (lines 268-282).
        • +
        • OnChannelEvent(channelKey, handler) (lines 294-312) creates a ChannelSubscription and appends it to the channel's handler list under the lock, returning the subscription as the unsubscribe token. Subscribing deliberately does not - join the channel; the doc says to call JoinChannelAsync as well (lines 249-250).
        • -
        • DispatchChannelEventAsync (lines 321-346) snapshots the subscriber list under the lock - (line 331), then invokes each handler in isolation, logging (never rethrowing) a failure so one bad - subscriber cannot starve the rest (lines 334-345).
        • -
        • StopAsync (lines 278-285) disposes and clears the connection; DisposeAsync (lines 288-303) sets - _disposed, stops, and disposes the semaphore. The comment at lines 298-301 records the deliberate + join the channel; the doc says to call JoinChannelAsync as well (lines 288-289).
        • +
        • DispatchChannelEventAsync (lines 396-421) snapshots the subscriber list under the lock + (line 406), then invokes each handler in isolation, logging (never rethrowing) a failure so one bad + subscriber cannot starve the rest (lines 409-420).
        • +
        • StopAsync (lines 317-324) disposes and clears the connection; DisposeAsync (lines 327-342) sets + _disposed, stops, and disposes the semaphore. The comment at lines 337-340 records the deliberate choice not to wait for an in-flight start: it can be sitting in a multi-second backoff, and - blocking a Blazor circuit teardown on it would be worse.
        • + blocking a Blazor circuit teardown on it would be worse. RestartAfterCloseAsync also checks + _disposed first (lines 383-386), so a close during teardown starts nothing.
      • Why it's built this way: sealed and scoped per circuit, because a connection and its channel @@ -4509,7 +4868,11 @@

        NotificationHubService

        shape is the client half of ADR-039, with push notifications themselves covered by - ADR-024.
      • + ADR-024. Choosing the transport + once in the constructor, and keeping the token-or-CSRF decision in ConfigureConnection, is what + lets the same service run either directly against the gateway or behind the same-origin API proxy of + ADR-131 without a client-held + token.
      • Where it's used: registered as a scoped service by AddNotificationUI() (MMCA.Common/Source/Presentation/MMCA.Common.UI/Notifications/DependencyInjection.cs:36); started after login and stopped on logout; its notification callback drives @@ -4717,12 +5080,20 @@

        PushNotificationService

      • The primary constructor passes the resource name "notifications" plus the factory and token service to EntityServiceBase and keeps scopeProvider (PushNotificationService.cs:16-22).
      • -
      • SendAsync(request, ct) (lines 23-47) null-guards the request (line 27), then applies scoping +
      • SendAsync(request, ct) (lines 23-52) null-guards the request (line 28), then applies scoping conditionally: a request that already carries a ScopeKey is sent unchanged, and only an unscoped - one picks up the ambient key via a record with expression (lines 31-39, rationale at lines - 29-30). It then POSTs through SendRequestAsync<PushNotificationDTO> (lines 41-46).
      • -
      • GetHistoryAsync(pageNumber = 1, pageSize = 10, ct) (lines 50-59) builds an invariant-culture - pageNumber/pageSize query (line 55) and sends a GET through the same helper (lines 56-58). + one picks up the ambient key via a record with expression (lines 32-40, rationale at lines + 30-31). It then POSTs through SendRequestAsync<PushNotificationDTO> with a fresh + idempotencyKey: NewIdempotencyKey() (lines 45-51). The send is retried on a transient failure, + so without a key an attempt that reached the server before failing would broadcast twice; the key + rides on every attempt so the [Idempotent] endpoint collapses the duplicate (comment at + lines 42-44). NewIdempotencyKey() is a GUID in N format + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/AuthenticatedServiceBase.cs:49), and + the base sets it as a default header on the one client that serves every retry attempt + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:384-391).
      • +
      • GetHistoryAsync(pageNumber = 1, pageSize = 10, ct) (lines 54-64) builds an invariant-culture + pageNumber/pageSize query (line 60) and sends a GET through the same helper (lines 61-63), + with no idempotency key since a read is safe to repeat. Note it does not send a scope: history is the admin's full send log.
      @@ -4775,15 +5146,21 @@

      BrandColors

      MMCA.Common.UI · MMCA.Common.UI.Theme · MMCA.Common/Source/Presentation/MMCA.Common.UI/Theme/BrandColors.cs:10 · Level 0 · class (static)

        -
      • What it is: the single C# source of truth for the brand palette: six hex constants (a primary triad and a secondary triad) that MMCATheme reads for both its light and dark MudBlazor variants.
      • -
      • Depends on: nothing first-party. It is mirrored by the CSS custom properties in wwwroot/app.css (--mmca-primary, --mmca-primary-dark, --mmca-primary-light, --mmca-secondary, --mmca-secondary-dark, named in the doc comments at BrandColors.cs:5-8,12,15,18,22,28).
      • -
      • Concept introduced, a fitness-tested duplication. [Rubric §20, Design System & Theming] assesses whether visual tokens are centralized rather than scattered as literals; here the palette lives in exactly one C# class. [Rubric §34, Architecture Governance & Documentation] assesses whether necessary duplication is monitored: C# cannot read CSS at build time, so the same colors must exist in both BrandColors and app.css, and BrandColorTokenTests in MMCA.Common.UI.Tests asserts the two stay in sync so the copy cannot silently drift (BrandColors.cs:6-8).
          -
        • [Rubric §21, Accessibility] lands here rather than only in the theme: the Secondary constant carries its own contrast math in source, Teal 700 #00796B holding about 5.3:1 on light surfaces, replacing the Teal 600 #00897B that measured about 4.0:1 and sat under the WCAG 2.1 AA 4.5:1 floor for normal text (BrandColors.cs:21-26).
        • +
        • What it is: the single C# source of truth for every hex value in the theme. It holds the two brand triads (primary and secondary), the app-chrome colors shared by both modes, and the full light and dark palettes, so every hex value MMCATheme assigns in either MudBlazor variant is one of these constants (doc comment, MMCA.Common/Source/Presentation/MMCA.Common.UI/Theme/BrandColors.cs:4-5).
        • +
        • Depends on: nothing first-party. Part of it is mirrored by CSS custom properties in wwwroot/app.css; the doc comments name --mmca-primary, --mmca-primary-dark, --mmca-primary-light, --mmca-secondary, --mmca-secondary-dark (BrandColors.cs:12,15,18,22,28), plus --mmca-sidebar-bg on ChromeBackground (line 36) and --mmca-divider on LightDivider (line 80).
        • +
        • Concept introduced, a fitness-tested duplication. [Rubric §20, Design System & Theming] assesses whether visual tokens are centralized rather than scattered as literals; here the whole palette lives in exactly one C# class, and the theme carries no hex literal of its own. [Rubric §34, Architecture Governance & Documentation] assesses whether necessary duplication is monitored: C# cannot read CSS at build time, so the brand colors must exist in both BrandColors and app.css, and BrandColorTokenTests in MMCA.Common.UI.Tests asserts the two stay in sync so the copy cannot silently drift (BrandColors.cs:6-8). The test pins the five brand tokens, one InlineData row each (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Theme/BrandColorTokenTests.cs:35-39); the --mmca-sidebar-bg and --mmca-divider mirrors are named in the doc comments but have no row there.
            +
          • [Rubric §21, Accessibility] lands here rather than only in the theme: the Secondary constant carries its own contrast math in source, Teal 700 #00796B holding about 5.3:1 on light surfaces, replacing the Teal 600 #00897B that measured about 4.0:1 and sat under the WCAG 2.1 AA 4.5:1 floor for normal text (BrandColors.cs:21-26). The contrast reasoning for LightWarning and DarkError is deliberately not here: their summaries point at MMCATheme, where the math sits beside the assignment (lines 56, 109).
          • +
          +
        • +
        • Walkthrough: public const string fields in four commented groups.
            +
          • The primary triad: Primary = "#1565C0" (line 13), PrimaryDark = "#0D47A1" (line 16), PrimaryLight = "#42A5F5" used for accents and dark-mode contrast (line 19). The secondary triad: Secondary = "#00796B" (line 26, with the contrast rationale immediately above it at lines 21-25), SecondaryDark = "#00695C" (line 29), and SecondaryLight = "#4DB6AC" (line 32).
          • +
          • Shared chrome, identical in both palettes (line 34): ChromeBackground = "#1A2035" for the app bar and sidebar (line 37), ChromeText = "#FFFFFF" (line 40), and ChromeTextMuted = "#FFFFFFB3", white at 70% alpha for sidebar text and icons (line 43).
          • +
          • The light palette (lines 45-84): LightTertiary Purple 700, LightInfo Blue 700, LightSuccess Green 800, LightWarning = "#A85D00" with LightWarningContrastText = "#FFFFFF", LightError Red 800, then LightBackground = "#FAFBFC", LightSurface, the two text tones, LightActionDefault, and the two dividers.
          • +
          • The dark palette (lines 86-131): DarkPrimaryLighten Blue 200, DarkSecondaryDarken = "#00897B" (Teal 600, the same value the light Secondary was moved off for contrast, line 92), DarkSecondaryLighten, DarkTertiary, DarkInfo, DarkSuccess, DarkWarning Orange 400, DarkError = "#FF8A80" (line 110), then DarkBackground = "#1A2027", DarkSurface = "#27303A", text, action and divider tones. Note DarkInfo and PrimaryLight share #42A5F5, and DarkTextSecondary and DarkActionDefault share #B0BEC5: each keeps its own name so a role can change without dragging the other.
        • -
        • Walkthrough: six public const string fields. The primary triad: Primary = "#1565C0" (line 13), PrimaryDark = "#0D47A1" (line 16), PrimaryLight = "#42A5F5" used for accents and dark-mode contrast (line 19). The secondary triad: Secondary = "#00796B" (line 26, with the contrast rationale immediately above it at lines 21-25), SecondaryDark = "#00695C" (line 29), and SecondaryLight = "#4DB6AC" (line 32).
        • -
        • Why it's built this way: const rather than static readonly means the values can appear in contexts that require compile-time constants; the governance is the fitness test, not the language keyword. Keeping the palette in one class means a rebrand touches one file plus the mirrored CSS, and the accessibility reasoning travels with the value it justifies instead of living in a review comment.
        • -
        • Where it's used: the MMCATheme light and dark palettes (MMCA.Common/Source/Presentation/MMCA.Common.UI/Theme/MMCATheme.cs:18-24,60-69); BrandColorTokenTests; any component that references a brand color programmatically.
        • +
        • Why it's built this way: const rather than static readonly means the values can appear in contexts that require compile-time constants; the governance is the fitness test, not the language keyword. Keeping every palette value in one class means a rebrand or a contrast fix touches one file plus the mirrored CSS, and the theme reads as a mapping of roles to names rather than a wall of hex.
        • +
        • Where it's used: every palette entry of MMCATheme that is not an rgba(...) literal (MMCA.Common/Source/Presentation/MMCA.Common.UI/Theme/MMCATheme.cs:18-54,60-110); BrandColorTokenTests; the doc comment of BrandColorTokenTestsBase, the per-host stylesheet check that consumer repos subclass (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Ui/BrandColorTokenTestsBase.cs:6-13); any component that references a brand color programmatically.

        IModelValidator

        @@ -4951,7 +5328,7 @@

        ThemeService

        prerender test uses the get invocation as proof that the first render ran at all (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Theme/MmcaThemeProvidersPrerenderTests.cs:29-33).
      • Where it's used: registered by AddUIShared as scoped - (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:138) and consumed by + (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:154) and consumed by MmcaThemeProviders, which subscribes in OnInitialized, initializes on first render and unsubscribes on dispose (MMCA.Common/Source/Presentation/MMCA.Common.UI/Theme/MmcaThemeProviders.razor:28,35,119), by @@ -5053,7 +5430,7 @@

        ApiUserPreferenceWriter

        boolean latch is the deliberate detail (ApiUserPreferenceWriter.cs:31-36): a fresh sign-in produces a different token, so writing resumes with no reset step and no staleness of its own.
      • Where it's used: registered with TryAddScoped in DependencyInjection - (MMCA.Common.UI/DependencyInjection.cs:153); resolved optionally by the theme toggle + (MMCA.Common.UI/DependencyInjection.cs:169); resolved optionally by the theme toggle (ThemeToggle.razor:23-27) and the culture switcher (CultureSwitcher.razor:38-42).

      @@ -5090,6 +5467,30 @@

      IUserPreferenceReader

      stored value already matches the current one (Login.razor:237-238).
      +

      LocalizedDataAnnotationsValidator

      +
      +

      MMCA.Common.UI · MMCA.Common.UI.Validation · MMCA.Common/Source/Presentation/MMCA.Common.UI/Validation/LocalizedDataAnnotationsValidator.cs:21 · Level 2 · class (sealed, ComponentBase, IDisposable)

      +
      +
        +
      • What it is: a drop-in replacement for the stock <DataAnnotationsValidator /> inside an EditForm. It runs the same DataAnnotations rules, but through DataAnnotationsModelValidator, so every ErrorMessage is resolved as a resource key and fields bound with For show the translated message; a message that is not a known key passes through unchanged, exactly as with the stock validator (doc comment, LocalizedDataAnnotationsValidator.cs:9-15).
      • +
      • Depends on: DataAnnotationsModelValidator (the localizing rule engine it constructs, line 54) and SharedResource (the resource marker behind the injected IStringLocalizer<SharedResource>, lines 30-31). Externals: Microsoft.AspNetCore.Components (ComponentBase, [CascadingParameter], [Inject]), Microsoft.AspNetCore.Components.Forms (EditContext, ValidationMessageStore, FieldIdentifier, FieldChangedEventArgs, ValidationRequestedEventArgs), Microsoft.Extensions.Localization, System.Reflection.
      • +
      • Concept introduced, one localizing path for both form styles. [Rubric §27, Internationalization & Localization] assesses whether user-facing text, validation messages included, resolves per culture from one catalog. The MudForm pages already localize through ModelValidation and DataAnnotationsModelValidator; the EditForm pages used the stock validator, which renders ErrorMessage verbatim, so a model declaring a resource key would show the key. This component closes that gap by feeding the same engine into the EditContext instead of a MudBlazor Validation delegate (doc comment, lines 11-14). See ADR-027 for the localization model.
          +
        • [Rubric §24, Forms, Validation & UX Safety] assesses single-declaration rules and consistent feedback timing. The rules stay on the model; this class only decides when they run: per field on change, and for every public property on submit (doc comment, lines 16-19).
        • +
        +
      • +
      • Walkthrough
          +
        • State is three nullable fields, the attached EditContext, its ValidationMessageStore, and the validator (lines 23-25). CurrentEditContext is a private [CascadingParameter] (lines 27-28) and the localizer a private [Inject] (lines 30-31), so the markup takes no parameters at all: <LocalizedDataAnnotationsValidator />.
        • +
        • OnParametersSet (lines 37-57) throws InvalidOperationException when no cascading EditContext exists, naming the fix ("Place it inside an EditForm", lines 39-43). It returns early when the context is the one already attached (lines 45-48); otherwise it detaches from any previous context (line 50), then creates the message store and a new DataAnnotationsModelValidator(Localizer) and subscribes to OnFieldChanged and OnValidationRequested (lines 52-56). Re-attaching on a swapped context is what keeps a form that replaces its model working.
        • +
        • HandleFieldChanged (lines 59-70) clears the changed field's messages, adds _validator.Validate(field.Model, field.FieldName), and calls NotifyValidationStateChanged (lines 66-69).
        • +
        • HandleValidationRequested (lines 72-94) clears every message (line 79), reflects the model's public instance properties, drops indexers, and de-duplicates names ordinally (lines 82-86), then validates each one into its own FieldIdentifier (lines 88-91) before notifying once (line 93).
        • +
        • Detach (lines 96-109) unsubscribes both handlers, clears the store and nulls all three fields; Dispose is exactly Detach() (line 34), so the component never leaves a handler on a context that outlives it.
        • +
        +
      • +
      • Why it's built this way: reusing DataAnnotationsModelValidator rather than re-implementing resolution means the EditForm and MudForm paths cannot disagree about which keys resolve. Injecting IStringLocalizer<SharedResource> rather than taking a page localizer as a parameter keeps the drop-in property: swapping the tag is the whole migration, and the auth forms' Auth.Field.* keys live in that shared catalog (MMCA.Common/CHANGELOG.md:36).
      • +
      • Where it's used: the four auth EditForm pages, Login (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Login.razor:40), Register (Register.razor:33), ForgotPassword (ForgotPassword.razor:39) and ResetPassword (ResetPassword.razor:43); their models, LoginModel, RegisterModel, ForgotPasswordModel and ResetPasswordModel, name it in their doc comments as the reason their ErrorMessage values are keys (LoginModel.cs:10, RegisterModel.cs:11, ForgotPasswordModel.cs:10, ResetPasswordModel.cs:12). It is public API of the package (PublicAPI.Shipped.txt:1193-1195).
      • +
      • Caveats: submit validation walks top-level properties only and calls the per-property path, so it does not run class-level attributes or IValidatableObject.Validate, which the stock validator's whole-object pass does; a model relying on either loses that check when it switches. Messages always resolve against SharedResource, so a key that exists only in a page's own resource file passes through untranslated. No file under MMCA.Common/Tests names this type, so its attach, re-attach and submit behavior is unpinned except through whatever the auth-page E2E runs exercise.
      • +
      +

      MMCATheme

      MMCA.Common.UI · MMCA.Common.UI.Theme · MMCA.Common/Source/Presentation/MMCA.Common.UI/Theme/MMCATheme.cs:9 · Level 2 · class (static)

      @@ -5101,25 +5502,25 @@

      MMCATheme

    1527. Concept introduced, one theme, accessibility-justified, with contrast math for both text and non-text UI. [Rubric §20, Design System & Theming] assesses whether an app has a single coherent theme rather than per-page overrides; MMCATheme.Instance is that one object (line 11). [Rubric §21, Accessibility] is unusually visible here, because most non-default color choices carry inline WCAG 2.1 AA/1.4.11 contrast math in a comment immediately above the value:

        -
      • light Warning = "#A85D00" / WarningContrastText = "#FFFFFF" (lines 35-36): the palette color is used as text and as a border as often as it is a fill (Color.Warning on MudText/MudLink/MudIcon and on outlined chips/buttons), and MudBlazor's default #F57F17 is only about 2.65:1 on Surface, failing the 4.5:1 floor everywhere it is text; #A85D00 is 4.96:1 on Surface and 4.79:1 on Background, and is dark enough that white becomes the correct on-color label (comment, lines 29-34);
      • +
      • light Warning = BrandColors.LightWarning (#A85D00) / WarningContrastText = BrandColors.LightWarningContrastText (#FFFFFF) (lines 35-36): the palette color is used as text and as a border as often as it is a fill (Color.Warning on MudText/MudLink/MudIcon and on outlined chips/buttons), and MudBlazor's default #F57F17 is only about 2.65:1 on Surface, failing the 4.5:1 floor everywhere it is text; #A85D00 is 4.96:1 on Surface and 4.79:1 on Background, and is dark enough that white becomes the correct on-color label (comment, lines 29-34);
      • light LinesInputs = "rgba(0,0,0,0.45)" (line 42): outlined-field borders answer to the 3:1 non-text floor (WCAG 1.4.11); MudBlazor's default is only 3.03:1/3.01:1, passing with no margin, so any host that darkens Background drops it below (comment, lines 38-41);
      • -
      • dark PrimaryContrastText = "rgba(0,0,0,0.87)" (line 66), because white on the lightened dark-mode primary #42A5F5 is about 2.65:1 fill-on-fill while dark text is about 6.6:1 (lines 62-65);
      • +
      • dark PrimaryContrastText = "rgba(0,0,0,0.87)" (line 66), because white on the lightened dark-mode primary #42A5F5 is about 2.65:1 fill-on-fill while dark text is about 6.6:1 (comment, lines 63-65);
      • dark SecondaryContrastText/TertiaryContrastText/InfoContrastText/SuccessContrastText (lines 76, 78, 80, 82): every lightened dark-mode accent takes the same Material treatment, white is 2.36-2.65:1 as a fill across the four colors while rgba(0,0,0,0.87) lands at 6.96-7.70:1, and each accent stays legible as text on Surface too (comment, lines 71-75);
      • dark WarningContrastText (line 85), white on #FFA726 being about 2.0:1 against about 10.8:1 (line 84);
      • -
      • dark Error = "#FF8A80" / ErrorContrastText = "rgba(0,0,0,0.87)" (lines 90, 93): the previous #EF5350 was only 3.84:1 as text on Surface, below the 4.5:1 floor wherever Color.Error is a label rather than a fill (inline validation copy, outlined error chips); #FF8A80 reads 5.86:1 on Surface, with the white-vs-dark label question resolved the same way as Primary (comments, lines 86-89, 91-92);
      • +
      • dark Error = BrandColors.DarkError (#FF8A80) / ErrorContrastText = "rgba(0,0,0,0.87)" (lines 90, 93): the previous #EF5350 was only 3.84:1 as text on Surface, below the 4.5:1 floor wherever Color.Error is a label rather than a fill (inline validation copy, outlined error chips); #FF8A80 reads 5.86:1 on Surface, with the white-vs-dark label question resolved the same way as Primary (comments, lines 86-89, 91-92);
      • dark LinesInputs = "rgba(255,255,255,0.5)" (line 98): the dark default rgba(255,255,255,0.3) is only 2.6:1 on Surface, effectively invisible to a low-vision user, while 0.5 is 4.59:1/5.10:1 (comment, lines 94-97).
      -

      The Secondary (light) contrast rationale is deliberately not repeated here: line 21 points at BrandColors, where the value and its justification live together.

      +

      The Secondary (light) contrast rationale is deliberately not repeated here: line 21 points at BrandColors, where the value and its justification live together. The reverse holds for Warning and Error: their constants live in BrandColors, whose summaries point back here for the math.

    1528. -
    1529. Walkthrough: a single static MudTheme Instance { get; } (line 11) initialized with four blocks.

      +
    1530. Walkthrough: a single static MudTheme Instance { get; } (line 11) initialized with four blocks. Every hex value in both palettes is a BrandColors constant (comment, lines 15-17); the only literals left are the rgba(...) on-color labels and input lines, each justified by the contrast comment above it.

        -
      • PaletteLight (lines 13-55) reads its primary and secondary triads straight from BrandColors (lines 18-24), sets the semantic colors (Tertiary, Info, Success, Warning, Error, lines 25-37, including the WCAG-driven Warning and LinesInputs values above), and then fixes app chrome: appbar #1A2035, background #FAFBFC, surface white, the drawer tones, text and divider values (lines 43-54).
      • -
      • PaletteDark (lines 56-111) lightens the primary for contrast on dark surfaces (Primary = BrandColors.PrimaryLight, line 60), keeps the same appbar and drawer chrome so the shell reads identically in both modes, and darkens the surface stack (Background = "#1A2027", Surface = "#27303A") with light text and dark dividers.
      • +
      • PaletteLight (lines 13-55) reads its primary and secondary triads from BrandColors (lines 18-24), sets the semantic colors from the Light* constants (Tertiary, Info, Success, Warning, Error, lines 25-37, including the WCAG-driven Warning and LinesInputs values above), and then fixes app chrome: AppbarBackground and DrawerBackground from BrandColors.ChromeBackground, DrawerText/DrawerIcon from ChromeTextMuted, and background, surface, text, action and divider values from the matching Light* constants (lines 43-54).
      • +
      • PaletteDark (lines 56-111) lightens the primary for contrast on dark surfaces (Primary = BrandColors.PrimaryLight, line 60), takes the same Chrome* constants for app bar and drawer so the shell reads identically in both modes (lines 99-105), and darkens the surface stack (Background = BrandColors.DarkBackground, Surface = BrandColors.DarkSurface, lines 101-102) with the Dark* text and divider tones.
      • Typography (lines 112-190). Default sets the font stack Inter, Segoe UI, Helvetica Neue, Arial, sans-serif; the comment above it records that Inter is self-hosted by this RCL (wwwroot/fonts plus an @font-face block in wwwroot/app.css) and that before those faces existed the stack silently fell through to Segoe UI, so the two must stay in step. H1 through H4 use display weights 800/800/700/700 with slight negative letter spacing, which the comment explains is how Inter is meant to be set at large sizes; H5 and H6 stay at weight 600 with no negative tracking. Subtitle1/Subtitle2 sit at weight 500, Body1/Body2 set line heights 1.6 and 1.5, and Button sets TextTransform = "none", because MudBlazor's default uppercasing wrecks localized strings (German compounds, accented capitals) and reads dated; weight 600 keeps the label as prominent as the shouting did.
      • LayoutProperties sets DefaultBorderRadius = "6px" (lines 191-193).
    1531. -
    1532. Why it's built this way: a static get-only property means the theme is constructed once and shared by every MudThemeProvider. Sourcing the brand hues from BrandColors rather than re-typing hex is what lets BrandColorTokenTests police C# versus CSS drift, and the per-color contrast comments turn accessibility decisions into reviewable source rather than tribal knowledge, now covering both text-on-fill (4.5:1) and non-text UI like input borders (3:1, WCAG 1.4.11) rather than text alone. The button-casing override is a small but instructive case of [Rubric §27, Internationalization & Localization] reaching into theming: a purely visual default became a localization problem.

      +
    1533. Why it's built this way: a static get-only property means the theme is constructed once and shared by every MudThemeProvider. Sourcing every hex value from BrandColors rather than re-typing it is what lets BrandColorTokenTests police C# versus CSS drift and keeps this class a mapping of roles to named colors, and the per-color contrast comments turn accessibility decisions into reviewable source rather than tribal knowledge, now covering both text-on-fill (4.5:1) and non-text UI like input borders (3:1, WCAG 1.4.11) rather than text alone. The button-casing override is a small but instructive case of [Rubric §27, Internationalization & Localization] reaching into theming: a purely visual default became a localization problem.

    1534. Where it's used: applied in the root layout of the Blazor Web and MAUI hosts via MudThemeProvider Theme="MMCATheme.Instance".

    1535. @@ -5182,7 +5583,7 @@

      ApiUserPreferenceReader

      the anonymous (null) case. Both return Empty (line 33).
    1536. The request itself is three lines: resolve the named "APIClient" (line 38), which already carries the bearer and Accept-Language handlers - (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:116-117), then + (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:124-125), then GetFromJsonAsync<UserPreferences> against the relative URI auth/preferences (lines 39-41).
    1537. Null-coalescing on the deserialized value (line 42) means a body of literal null is the same as no preference.
    1538. @@ -5197,9 +5598,9 @@

      ApiUserPreferenceReader

      returning a shared empty record is what makes the reconciliation safe to await unconditionally in the login flow. It is the read half of a pair: ApiUserPreferenceWriter is the write half, and the two are registered together - (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:153-154). + (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:169-170).
    1539. Where it's used: registered by AddUIShared with TryAddScoped - (DependencyInjection.cs:154) and injected by exactly one page, the framework's login page + (DependencyInjection.cs:170) and injected by exactly one page, the framework's login page (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/Login.razor:14). Its ApplyStoredPreferencesAndNavigateAsync calls GetAsync, applies a stored theme through ThemeService, and, when the stored culture differs from the current one, hands the @@ -5213,60 +5614,6 @@

      ApiUserPreferenceReader

      HttpResultExecutor enforces elsewhere in this package; the single caller passes no token (Login.razor:230), so the difference is invisible in current use.
    1540. -

      TrustedCallerHandler

      -
      -

      MMCA.Common.UI.Web · MMCA.Common.UI.Web.Security · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Security/TrustedCallerHandler.cs:35 · Level 0 · class (sealed, DelegatingHandler)

      -
      -
        -
      • What it is: an outgoing HttpClient handler that stamps a shared-secret header onto a request - only when the request targets the configured gateway origin, so the gateway can trust that the call - came from this app rather than from an arbitrary caller.
      • -
      • Depends on: nothing first-party. Externals: System.Net.Http (DelegatingHandler, - HttpRequestMessage, HttpResponseMessage), BCL Uri.
      • -
      • Concept introduced, an edge-trust header scoped to origin match, not applied blindly. - [Rubric §11, Security] assesses whether a trust signal is bounded to the audience it is meant for. - The constructor takes a header name, a shared secret, and the gateway origin - (TrustedCallerHandler.cs:39), and SendAsync only stamps the header when the outgoing request's - scheme, host, and port match that origin (TrustedCallerHandler.cs:57-63); a request to any other - destination goes out unmodified, so the secret cannot leak to a third-party origin the same - HttpClient happens to call. ADR-088 - is the policy this enforces on the client side.
      • -
      • Walkthrough
          -
        • The constructor (TrustedCallerHandler.cs:39-48) null/blank-guards headerName and secret and - null-guards gatewayOrigin, storing all three as readonly fields.
        • -
        • SendAsync (TrustedCallerHandler.cs:51-72) null-guards the request, then compares - request.RequestUri against _gatewayOrigin with Uri.Compare over - UriComponents.Scheme | UriComponents.HostAndPort, case-insensitive - (TrustedCallerHandler.cs:57-63). On a match it removes any existing value for the header and adds - exactly one via TryAddWithoutValidation (TrustedCallerHandler.cs:67-68); the comment - (TrustedCallerHandler.cs:65-66) explains the replace-not-append choice, the gateway compares one - header value in constant time, so a second value would silently fail the comparison. It then calls - base.SendAsync unconditionally.
        • -
        -
      • -
      • Why it's built this way: a DelegatingHandler composes into the existing HttpClient pipeline - without every call site having to remember to add the header itself, and gating on origin means the - same registered handler is safe to reuse on a client that also talks to other hosts.
      • -
      • Where it's used: registered in MMCA.Common.UI.Web's - DependencyInjection (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/DependencyInjection.cs); - its behavior is pinned by TrustedCallerHandlerTests - (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Web.Tests/Security/TrustedCallerHandlerTests.cs).
      • -
      -

      BlazorCspSettings

      -
      -

      MMCA.Common.UI.Web · MMCA.Common.UI.Web.Security · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Security/BlazorCspSettings.cs:18 · Level 0 · class (sealed)

      -
      -
        -
      • What it is: the bindable options type for an opt-in frame-src allowance on the Blazor host's Content-Security-Policy, one string list of origins a page may embed in an <iframe> (BlazorCspSettings.cs:30-42).
      • -
      • Depends on: nothing first-party. It is read by BlazorCspPolicyProvider through BuildFrameSrc and validated at startup by BlazorCspSettingsValidator. Externals: Microsoft.Extensions.Options binding conventions (BCL IList<string>).
      • -
      • Concept introduced, an empty default that changes nothing. [Rubric §26, Front-End Security] assesses whether a security-relevant default stays strict until a deployment explicitly opts out. SectionName = "BlazorCsp" (line 28) names the configuration section; FrameSources (line 42) initializes to an empty list, so an unconfigured host emits no frame-src directive at all and the policy stays byte-identical to the pre-existing baseline (comment, lines 30-33). Only a deployment that needs to embed a specific third-party origin (the doc comment's example is a map provider's embed endpoint, lines 30-31) adds entries.
          -
        • The doc comment (lines 33-40) states the origin shape a configured entry must satisfy: an absolute https origin with no path, query, fragment, user info, wildcard, quote, semicolon, comma or whitespace, so the field carries the contract BlazorCspSettingsValidator enforces rather than leaving it implicit.
        • -
        -
      • -
      • Walkthrough: two members: SectionName (line 28, a public const string) and FrameSources (line 42, public IList<string> initialized to []).
      • -
      • Why it's built this way: a settings class bound through IOptions<BlazorCspSettings> fits the same registration and validation pipeline every other options type in the host uses, so adding frame-src support did not need a new configuration mechanism, only a new section.
      • -
      • Where it's used: bound and validated in MMCA.Common.UI.Web's DependencyInjection (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/DependencyInjection.cs), consumed by BlazorCspPolicyProvider's constructor as IOptions<BlazorCspSettings>.
      • -

      BlazorCircuitLimitSettings

      MMCA.Common.UI.Web · MMCA.Common.UI.Web.Hardening · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Hardening/BlazorCircuitLimitSettings.cs:17 · Level 0 · class (sealed)

      @@ -5291,132 +5638,37 @@

      UiRateLimitingSettings

      • What it is: the bound options type for a Blazor Web host's own edge rate limiting, under the UiRateLimiting configuration section: an on/off switch, a per-client-IP request budget and window, and a replica-wide concurrency ceiling.
      • -
      • Depends on: nothing first-party; RangeAttribute on PermitLimit, WindowSeconds, and GlobalConcurrencyLimit (UiRateLimitingSettings.cs:57, :61, :73).
      • +
      • Depends on: nothing first-party; RangeAttribute on PermitLimit, WindowSeconds, and GlobalConcurrencyLimit (UiRateLimitingSettings.cs:57, :61, :75).
      • Concept introduced, a Blazor host's own edge limiter because it is a separate origin from the Gateway. The class doc (:9-32) states why a UI host needs one at all: the Blazor UI is a separate externally reachable origin from the Gateway (its own FQDN), so the ADR-088 Gateway edge limiter never sees a request to it, and every page load on this origin opens an interactive Server circuit against a typically 0.25 vCPU / 0.5 GiB container. It is shaped like the Gateway's limiter (same per-IP-plus-global-concurrency pairing, anonymous traffic included) but shipped as a distinct kit rather than referenced from MMCA.Common.Gateway, because a Blazor host is not a reverse proxy and its exemptions differ (it serves /_framework and /_content itself). Both counts are per replica, in memory, the same deliberate trade the Gateway kit makes: an edge limiter answers in microseconds on every request, and a shared counter would put a network round trip in front of the whole site. [Rubric §29, Resilience & Business Continuity] and [Rubric §11, Validation] apply as with BlazorCircuitLimitSettings: a documented, traffic-shape-derived number behind [Range] validation.
      • Walkthrough
        • SectionName => "UiRateLimiting" (:36).
        • Enabled (:43), default true; the doc comment (:39-42) calls out its escape hatch use, a load or capacity proof driven from one runner IP that the per-IP window cannot tell from a flood.
        • PermitLimit (:57-58), [Range(1, 1_000_000)], default 300, which no real visitor approaches (a page load costs a handful of requests once static assets are exempt, and Interactive Auto hands the session to WebAssembly after the first render). The doc comment (:48-56) sets it well above a single visitor on purpose because an office or mobile-carrier NAT presents many visitors as one IP, and tells a host whose audience sits behind one address (a venue's wifi, a single corporate egress) to raise it, since the whole crowd arrives as a single partition key.
        • WindowSeconds (:61-62), [Range(1, 3600)], default 60.
        • -
        • GlobalConcurrencyLimit (:73-74), [Range(1, 1_000_000)], default 200: a ceiling rather than a rate, guarding against a slow downstream backing up threads on this replica; excess requests are rejected with 429 immediately rather than queued (:64-72), and unlike the per-IP window it needs no widening for a shared-address audience because in-flight concurrency is bounded by what the container can render, not by how many people share an address.
        • +
        • GlobalConcurrencyLimit (:75-76), [Range(1, 1_000_000)], default 200: a ceiling rather than a rate, guarding against a slow downstream backing up threads on this replica; excess requests are rejected with 429 immediately rather than queued (:64-74), and unlike the per-IP window it needs no widening for a shared-address audience because in-flight concurrency is bounded by what the container can render, not by how many people share an address. The doc comment (:71-73) also records that the Blazor circuit transport (/_blazor) is excluded from this ceiling: a circuit WebSocket would hold one permit for its whole lifetime, so open circuits are bounded by BlazorCircuitLimits:MaxActiveCircuits (BlazorCircuitLimitSettings) instead.
      • -
      • Why it's built this way: same rationale as BlazorCircuitLimitSettings, a bound, validated, environment-tunable options type rather than inline literals in UiRateLimitingExtensions. See ADR-088.
      • +
      • Why it's built this way: same rationale as BlazorCircuitLimitSettings, a bound, validated, environment-tunable options type rather than inline literals in UiRateLimitingExtensions. See ADR-088 and ADR-124 for the split between the request concurrency ceiling and the circuit ceiling.
      • Where it's used: bound and read by UiRateLimitingExtensions's AddUiRateLimiting(), which closes over a resolved instance rather than IOptions<T> per request (see that section).
      -

      NotificationSendModel

      -
      -

      MMCA.Common.UI · MMCA.Common.UI.Pages.Notifications · MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Notifications/NotificationSendModel.cs:19 · Level 1 · class (sealed)

      -
      -
        -
      • What it is: the two-property form model for the push-notification compose page. Its DataAnnotations are the single declaration of that form's field rules.
      • -
      • Depends on: first-party: SendPushNotificationRequest (the endpoint contract, whose length constants it reuses, lines 23 and 28). Externals: System.ComponentModel.DataAnnotations (RequiredAttribute, MaxLengthAttribute). It is consumed by NotificationSend through ModelValidation and DataAnnotationsModelValidator.
      • -
      • Concept introduced, one number shared by the cap, the message, and the server invariant. The lengths are not declared here. MaxLength(SendPushNotificationRequest.TitleMaxLength) (line 23) and MaxLength(SendPushNotificationRequest.BodyMaxLength) (line 28) point at the shared request contract, which fixes them at 200 and 2000 (MMCA.Common/Source/Core/MMCA.Common.Shared/Notifications/PushNotifications/SendPushNotificationRequest.cs:15,21). The same constants drive the input cap and the character counter in the markup (NotificationSend.razor:49-50,61-62), and the server-side validator enforces the same numbers.
          -
        • [Rubric §24, Forms, Validation & UX Safety] assesses whether client and server agree. Here they cannot disagree, because there is one literal and everything else is a reference to it.
        • -
        • [Rubric §9, API & Contract Design] assesses whether contract facts live with the contract; putting the length constants on the request record (the type the endpoint binds) rather than on the form model is what makes the sharing possible in the first place.
        • -
        • [Rubric §27, Internationalization & Localization]: each ErrorMessage is a resource key ("Notif.Send.Field.Title.Required", line 22, and its three siblings), resolved by the page's localizing DataAnnotationsModelValidator per ADR-027. This is the concrete case the pass-through localization in that validator was designed for.
        • -
        -
      • -
      • Walkthrough: two mutable string properties, both initialized to string.Empty so a fresh model binds cleanly. Title (line 24) carries [Required] with key Notif.Send.Field.Title.Required (line 22) and [MaxLength(200)] with key Notif.Send.Field.Title.MaxLength (line 23). Body (line 29) carries the matching pair, Notif.Send.Field.Message.Required (line 27) and Notif.Send.Field.Message.MaxLength with the 2000-character cap (line 28).
      • -
      • Why it's built this way: a settable class rather than a record with init accessors, because MudBlazor two-way binding (@bind-Value="_model.Title") writes back into the instance. It is a separate type from SendPushNotificationRequest because the form model is mutable and carries presentation rules, while the request record is the immutable wire contract; the page maps one to the other in a single line (NotificationSend.razor.cs:111).
      • -
      • Where it's used: held as private readonly NotificationSendModel _model = new() by NotificationSend (NotificationSend.razor.cs:36) and bound by both fields in its markup.
      • -
      -

      BiometricGate

      -
      -

      MMCA.Common.UI · MMCA.Common.UI.Components.Capabilities · MMCA.Common/Source/Presentation/MMCA.Common.UI/Components/Capabilities/BiometricGate.razor.cs:17 · Level 1 · class (partial, component code-behind)

      -
      -
        -
      • What it is: a hybrid-head app-lock gate. It locks the UI behind a biometric prompt whenever the - app returns from the background having sat there longer than ReLockAfter, so a device handed to - someone else, or simply left unattended, does not reopen straight into a signed-in session.
      • -
      • Depends on: injected services resolved by the component (AppLifecycle's Resumed event, - Preferences, TokenStorage, Biometrics, Navigation, Logger, the page's localizer L); - MudBlazor's MudButton for the focus target (_unlockButton, line 105).
      • -
      • Concept introduced, an async-void lifecycle handler that cannot afford to throw. - [Rubric §11, Security] assesses whether a re-auth control actually re-arms on every qualifying - event, not just the first one. The class subscribes to AppLifecycle.Resumed on first render - regardless of the current preference, because app lock can be switched on later in the same session - (comment, lines 128-129), and every resume re-reads the preference. [Rubric §18, UI Architecture & Component Design] covers the handler shape itself: OnResumed (line 169) is a plain - EventHandler<AppResumedEventArgs>, so it cannot be async; it fires ReLockAsync with an explicit - discard, and the security comment (lines 163-168) states why that discard is safe here, ReLockAsync - observes its own failures with a catch-all, and an unobserved exception in a true async-void handler - would crash a native head's process (VSTHRD100).
      • -
      • Walkthrough
          -
        • ReLockAfter (lines 100-101) is a [Parameter] TimeSpan defaulting to 30 seconds; the comment - (lines 96-98) explains the default is deliberately short, a device handed off is usually away from - its owner longer than a glance at a notification.
        • -
        • OnAfterRenderAsync(firstRender) (lines 115-141): first focuses the Unlock button if - _focusUnlockPending and the button reference now exists (lines 117-121), because that flag can - only be acted on once the locked branch has actually rendered. On firstRender it subscribes to - AppLifecycle.Resumed (lines 130-131), then calls ShouldLockAsync and, if it returns true, sets - _locked and awaits UnlockAsync (lines 133-140).
        • -
        • ReLockAsync (lines 171-206) returns early if already locked or if the background duration was - under ReLockAfter (line 175), otherwise dispatches the lock-and-unlock sequence through - InvokeAsync (lines 180-190). It catches ObjectDisposedException and InvalidOperationException - as expected teardown races (lines 192-199), and a general Exception last, logged rather than - rethrown, with the comment (lines 200-204) recording that this is the exhaustive catch an async-void - handler needs.
        • -
        • ShouldLockAsync (lines 208-218) returns false if the AppLockEnabled device preference is off, - otherwise returns whether a refresh token is still stored, nothing to protect without a session.
        • -
        • UnlockAsync (lines 220-234) calls Biometrics.AuthenticateAsync with a localized prompt reason; - success clears _locked, failure queues _focusUnlockPending for the next render rather than - focusing immediately, because the button does not exist yet on the very first lock.
        • -
        • FocusUnlockAsync (lines 240-254) tolerates JSDisconnectedException (circuit gone) and - InvalidOperationException (prerendering, or the element already removed).
        • -
        • SignOutAsync (lines 256-261) clears tokens, clears _locked, and navigates to /login.
        • -
        • Dispose(bool) (lines 145-154) unsubscribes from AppLifecycle.Resumed if subscribed, guarded so - it is safe to call more than once; the public Dispose() (lines 157-161) forwards to it and - suppresses finalization.
        • -
        -
      • -
      • Why it's built this way: the re-lock decision is driven entirely by wall-clock background - duration rather than any UI state, so it behaves the same whether the app was backgrounded for a - phone call or for the rest of the day. Catching broadly inside ReLockAsync but narrowly inside - FocusUnlockAsync reflects the difference between an operation whose caller cannot observe a failure - at all (the lifecycle event) and one whose failure just means "skip the focus this time".
      • -
      • Where it's used: MMCA.ADC's DeviceUIModule - (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/DeviceUIModule.cs) and App.xaml.cs - (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/App.xaml.cs); pinned by BiometricGateTests - (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Components/Capabilities/BiometricGateTests.cs).
      • -
      -

      BlazorCspSettingsValidator

      -
      -

      MMCA.Common.UI.Web · MMCA.Common.UI.Web.Security · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Security/BlazorCspSettingsValidator.cs:13 · Level 1 · class (internal, sealed)

      -
      -
        -
      • What it is: the startup IValidateOptions<BlazorCspSettings> that rejects a frame-src origin the moment it is misshapen, so a bad entry fails fast rather than silently reaching the CSP header.
      • -
      • Depends on: first-party: BlazorCspSettings (the options type it validates). Externals: Microsoft.Extensions.Options (IValidateOptions<T>, ValidateOptionsResult), BCL SearchValues<char>, Uri.
      • -
      • Concept introduced, an explicit character denylist plus a shape check, not a regex. The fail-fast configuration contract of ADR-070 (Website/docs-src/adr/070-fail-fast-configuration-contract.md) assesses whether a misconfigured setting is caught at startup instead of at request time. Validate (lines 21-31) runs every configured FrameSources entry through IsValidOrigin and fails the whole registration with one message per bad entry (lines 26-30) rather than starting the host on a policy it cannot honor.
          -
        • ForbiddenCharacters (line 18, a SearchValues<char> over *'";,@?#) is the set that could either break out of a CSP source expression or widen it, plus the URL delimiters (user info, query, fragment) a plain origin never carries. IsValidOrigin (lines 21-25 of the walkthrough below) rejects on that set before it ever calls Uri.TryCreate, so a value crafted to look like a URL but carrying a CSP metacharacter never reaches the parser.
        • -
        • [Rubric §26, Front-End Security] assesses browser-side hardening; this validator is what keeps BlazorCspSettings's contract (an absolute https origin, no path, wildcard, or delimiter) a checked invariant instead of a comment.
        • -
        -
      • -
      • Walkthrough
          -
        • Validate(string? name, BlazorCspSettings options) (lines 21-31) null-guards options, projects every entry that fails IsValidOrigin into a message naming the section, the entry, and the shape it must satisfy (lines 26-30), and returns ValidateOptionsResult.Success only when that list is empty.
        • -
        • IsValidOrigin(string? source) (lines 34-49), internal static: rejects null, whitespace-only, any entry containing whitespace, anything matching ForbiddenCharacters, or anything Uri.TryCreate cannot parse as absolute (lines 36-41). What remains must be https (ordinal case-insensitive), have a non-empty host, and have PathAndQuery equal to exactly "/" (lines 46-48); the comment (lines 44-45) notes the forbidden set already rules out user info, a query, and a fragment, so a root-only path is the last thing separating an origin from a full URL.
        • -
        • ToOrigin(string source) (lines 55-56), internal static: new Uri(source, UriKind.Absolute).GetLeftPart(UriPartial.Authority), the canonical scheme://host[:port] form used to splice a validated entry into the CSP header, lower-cased host, default port dropped, no trailing slash.
        • -
        -
      • -
      • Why it's built this way: an explicit character denylist checked before parsing is stricter than trusting Uri alone, because Uri will happily parse strings a CSP source expression cannot safely contain; failing every bad entry at once (rather than the first) gives a deployment the whole list of what to fix in one pass.
      • -
      • Where it's used: registered alongside BlazorCspSettings in MMCA.Common.UI.Web's DependencyInjection (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/DependencyInjection.cs); ToOrigin is reused by BlazorCspPolicyProvider's BuildFrameSrc to canonicalize an already-validated entry.
      • -

      BoundedCircuitHandler

      -

      MMCA.Common.UI.Web · MMCA.Common.UI.Web.Hardening · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Hardening/BoundedCircuitHandler.cs:39 · Level 1 · class (sealed, partial, CircuitHandler)

      +

      MMCA.Common.UI.Web · MMCA.Common.UI.Web.Hardening · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Hardening/BoundedCircuitHandler.cs:43 · Level 1 · class (sealed, partial, CircuitHandler)

      • What it is: a Blazor CircuitHandler that refuses a new circuit once this replica already holds BlazorCircuitLimitSettings.MaxActiveCircuits active circuits, and counts circuits back down as they close.
      • -
      • Depends on: CircuitHandler (base class), IOptions<BlazorCircuitLimitSettings> and ILogger<BoundedCircuitHandler> (primary-constructor parameters, BoundedCircuitHandler.cs:39-41).
      • -
      • Concept introduced, a singleton counter guarding a per-circuit-scoped hook. The class doc (:8-36) states why a handler rather than CircuitOptions: DisconnectedCircuitMaxRetained bounds only circuits that already dropped their connection, and since every page load on a public Blazor origin opens one (a host rendering Interactive Auto makes the first render a Server circuit), the handler is the documented extension point that sees a circuit being opened and closed. It also states why the class throws rather than returning a refusal value: OnCircuitOpenedAsync returns Task with no "refuse" return, so an exception is the only way to stop a circuit starting, one of the few places in the framework where the Result pattern does not apply because the contract belongs to ASP.NET Core. [Rubric §29, Resilience & Business Continuity] assesses this directly: an explicit admission-control decision at the point circuits are opened, rather than letting an unbounded flood of circuits exhaust a replica's memory. [Rubric §14, Testability] applies through ActiveCircuits (:49), an internal-readable counter exposed specifically so BoundedCircuitHandlerTests can assert the increment/decrement/floor behavior without inspecting private state.
      • +
      • Depends on: CircuitHandler (base class), IOptions<BlazorCircuitLimitSettings> and ILogger<BoundedCircuitHandler> (primary-constructor parameters, BoundedCircuitHandler.cs:43-45).
      • +
      • Concept introduced, a singleton counter guarding a per-circuit-scoped hook. The class doc (:9-40) states why a handler rather than CircuitOptions: DisconnectedCircuitMaxRetained bounds only circuits that already dropped their connection, and since every page load on a public Blazor origin opens one (a host rendering Interactive Auto makes the first render a Server circuit), the handler is the documented extension point that sees a circuit being opened and closed. It also states why the class throws rather than returning a refusal value: OnCircuitOpenedAsync returns Task with no "refuse" return, so an exception is the only way to stop a circuit starting, one of the few places in the framework where the Result pattern does not apply because the contract belongs to ASP.NET Core. Finally (:31-39) it explains the one piece of per-circuit state on a singleton: the set of admitted circuits, keyed by reference, which exists because a refused circuit is still torn down through OnCircuitClosedAsync, and without the set that close would release a permit some other circuit holds. [Rubric §29, Resilience & Business Continuity] assesses this directly: an explicit admission-control decision at the point circuits are opened, rather than letting an unbounded flood of circuits exhaust a replica's memory. [Rubric §14, Testability] applies through ActiveCircuits (:55), a public read-only counter the doc comment (:51-54) says exists so a consuming host can assert the ceiling behaves and a diagnostic endpoint can report saturation; BoundedCircuitHandlerTests reads it to assert the increment, decrement and floor behavior without inspecting private state.
      • Walkthrough
          -
        • _activeCircuits (:43) and ActiveCircuits => Volatile.Read(ref _activeCircuits) (:49): the live count, read with Volatile.Read because it is written from Interlocked calls on possibly-concurrent circuit-open/close callbacks.
        • -
        • Order => int.MaxValue (:55): runs LAST among registered handlers on the way in, so a refusal happens after cheaper handlers have already done their work rather than in the middle of it (:51-54).
        • -
        • OnCircuitOpenedAsync (:58-76): increments first, then rolls back on refusal (:64-67) rather than checking-then-incrementing, because two simultaneous opens could otherwise both observe the last free slot and both take it; over the ceiling it decrements, logs via LogCircuitRefused, and returns a faulted Task carrying an InvalidOperationException telling the caller to retry (:67-72).
        • -
        • OnCircuitClosedAsync (:79-91): decrements, then floors at zero (:84-88) rather than trusting the open/close pairing, because a circuit torn down before this handler ran would otherwise drive the count negative and hand out permits forever.
        • -
        • LogCircuitRefused (:93-96): a [LoggerMessage]-generated warning logger, partial method paired with the partial class declaration.
        • +
        • _admitted (:47): a ConcurrentDictionary<Circuit, byte> constructed with ReferenceEqualityComparer.Instance, used as a set of the circuits this handler admitted.
        • +
        • _activeCircuits (:49) and ActiveCircuits => Volatile.Read(ref _activeCircuits) (:55): the live count, read with Volatile.Read because it is written from Interlocked calls on possibly-concurrent circuit-open/close callbacks.
        • +
        • Order => int.MaxValue (:61): runs LAST among registered handlers on the way in, so a refusal happens after cheaper handlers have already done their work rather than in the middle of it (:57-60).
        • +
        • OnCircuitOpenedAsync (:64-85): guards circuit against null (:66), then increments first and rolls back on refusal (:70-75) rather than checking-then-incrementing, because two simultaneous opens could otherwise both observe the last free slot and both take it; over the ceiling it decrements, logs via LogCircuitRefused, and returns a faulted Task carrying an InvalidOperationException telling the caller to retry (:75-80). Only an admitted circuit is added to _admitted (:83).
        • +
        • OnCircuitClosedAsync (:88-109): guards circuit against null (:90), then returns without touching the count unless _admitted.TryRemove finds the circuit (:92-98), because the framework's teardown calls every handler's close unconditionally, including for a circuit this handler refused, and that close must not release a permit another circuit holds. An admitted circuit decrements the count, which then floors at zero (:100-106) as a last line of defence against a count driven negative handing out permits forever.
        • +
        • LogCircuitRefused (:111-114): a [LoggerMessage]-generated warning logger, partial method paired with the partial class declaration.
      • -
      • Why it's built this way: see BlazorCircuitLimitSettings for why 200 is the number; this handler is the enforcement point for that ceiling. See ADR-088.
      • +
      • Why it's built this way: see BlazorCircuitLimitSettings for why 200 is the number; this handler is the enforcement point for that ceiling. See ADR-088 and ADR-124.
      • Where it's used: registered as a singleton CircuitHandler by BlazorCircuitLimitExtensions's AddBoundedBlazorCircuits(), called from MMCA.ADC.UI.Web's Blazor Server host composition (Program.cs).

      UiRateLimitingExtensions

      @@ -5426,44 +5678,19 @@

      UiRateLimitingExtensions

      • What it is: the registration and pipeline wiring for a Blazor Web host's edge rate limiter, an IServiceCollection extension that builds a chained per-IP-and-global limiter from UiRateLimitingSettings, and an IApplicationBuilder extension that adds it to the pipeline.
      • Depends on: UiRateLimitingSettings; System.Threading.RateLimiting (RateLimitPartition, FixedWindowRateLimiterOptions, ConcurrencyLimiterOptions, PartitionedRateLimiter); ASP.NET Core's IServiceCollection/AddRateLimiter and IApplicationBuilder/UseRateLimiter. The class carries a [SuppressMessage("Naming", "CA1708", ...)] attribute (:18-21) whose justification records why: with multiple extension(T) blocks in one static class, CA1708 flags the compiler-generated grouping members as case-colliding even though no user-visible identifier differs only by case.
      • -
      • Concept introduced, exempting probes and static assets from a per-IP window. IsExempt (:62-71) matches two things: a fixed prefix list (/health, /alive, /_framework, /_content, /hubs, :42) matched on whole segments so /healthz is not caught by /health, and any path whose last segment carries a file extension. The doc comment (:33-41) explains the /hubs entry mirrors the Gateway's own GatewayRateLimiting:BypassPathPrefixes bypass for long-lived SignalR traffic, stated here by declaration even though nothing under /hubs is served by this host today. [Rubric §29, Resilience & Business Continuity] applies to the chained limiter design; [Rubric §1, SOLID] applies to IsExempt being the single decision both partitions delegate to, so the exemption rule cannot drift between the two limiters.
      • +
      • Concept introduced, exempting probes and static assets from a per-IP window, and long-lived connections from the concurrency ceiling. IsExempt (:75-84) matches two things: a fixed prefix list (/health, /alive, /_framework, /_content, /hubs, /api/hubs, :52) matched on whole segments so /healthz is not caught by /health, and any path whose last segment carries a file extension. The doc comment (:41-51) explains the /hubs entry mirrors the Gateway's own GatewayRateLimiting:BypassPathPrefixes bypass for long-lived SignalR traffic, so a host that ever fronts a hub on this origin is covered by declaration rather than by accident, and that /api/hubs is the same hub traffic arriving through the same-origin API proxy at its default prefix (SameOriginApiProxyEndpoint): without it every open hub WebSocket would hold a concurrency lease for its whole lifetime and exhaust the ceiling. /_blazor is treated differently on each limiter (:65-69): it has no extension and no exemption from the per-IP window, because the negotiate endpoint is exactly what opens a circuit, but BlazorTransportPrefix (:33-39) keeps it out of the concurrency ceiling, because the same prefix carries the circuit WebSocket, whose lease would otherwise be held for the whole circuit lifetime; open circuits are bounded by BlazorCircuitLimitSettings and BoundedCircuitHandler instead. [Rubric §29, Resilience & Business Continuity] applies to the chained limiter design; [Rubric §1, SOLID] applies to IsExempt being the single decision both partitions delegate to, so the shared exemption rule cannot drift between the two limiters, with the concurrency partition adding only the transport prefix on top.
      • Walkthrough
        • ExemptPartitionKey, UnknownIpPartitionKey, ConcurrencyPartitionKey (:25, :28, :31): the three partition keys the limiter buckets requests into.
        • -
        • ExemptPrefixes (:42) and IsExempt(PathString) (:62-71): the shared exemption rule, detailed above.
        • -
        • ClientIpPartition(HttpContext, UiRateLimitingSettings) (:81-109): no limiter for exempt paths, no limiter for an unresolvable client IP (fail open, :94-98, so an in-process TestServer is never collapsed into one shared bucket), otherwise a FixedWindowRateLimiter keyed on the client IP with QueueLimit = 0 (rejected immediately, not queued).
        • -
        • ConcurrencyPartition(HttpContext, UiRateLimitingSettings) (:119-134): the replica-wide ConcurrencyLimiter, one bucket for the whole process, exempt for the same paths.
        • -
        • AddUiRateLimiting(IConfiguration) (:145-178), an IServiceCollection extension: binds and validates UiRateLimitingSettings (:150-153), then resolves a plain instance from configuration to close over in the partition callbacks (:158-159) rather than resolving IOptions<T> per request, because the partition callback runs on the hot path of every request and an out-of-range value has already failed ValidateOnStart(). RejectionStatusCode is set to 429 (:163); when Enabled is false the method returns early with no GlobalLimiter configured (:165-168); otherwise it chains the client-IP and concurrency partitions with PartitionedRateLimiter.CreateChained (:172-176), so a request must satisfy both.
        • -
        • UseUiRateLimiting() (:187-191), an IApplicationBuilder extension: calls the framework's UseRateLimiter().
        • -
        -
      • -
      • Why it's built this way: see UiRateLimitingSettings for the tuning rationale. See ADR-088 for the edge-hardening split between this host and the Gateway.
      • -
      • Where it's used: AddUiRateLimiting and UseUiRateLimiting are called from MMCA.ADC.UI.Web's Blazor Server host composition (Program.cs); the extension methods live on the IServiceCollection/IApplicationBuilder extension blocks (:136, :181).
      • -
      -

      BlazorCspPolicyProvider

      -
      -

      MMCA.Common.UI.Web · MMCA.Common.UI.Web.Security · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Security/BlazorCspPolicyProvider.cs:28 · Level 2 · class (internal, sealed)

      -
      -
        -
      • What it is: the Content-Security-Policy provider for a Blazor Web host. It computes one CSP string at construction, pinning connect-src to 'self' plus the configured API or Gateway origin (https and its matching WebSocket origin), optionally adding an opt-in frame-src directive, and hands the result to the shared security-headers middleware on every request.
      • -
      • Depends on: first-party: ICspPolicyProvider (the contract it implements, line 24), CspPolicy (the value it returns, a policy string plus an Enforce flag), SecurityHeadersMiddleware (its only consumer, named in the class doc at line 12), ApiSettings (the endpoint source, injected as IOptions<ApiSettings>, line 35), and BlazorCspSettings (the frame-src source, injected as IOptions<BlazorCspSettings>, line 36) whose entries it canonicalizes with BlazorCspSettingsValidator.ToOrigin (line 94). Externals: Microsoft.Extensions.Options (IOptions<T>), Microsoft.AspNetCore.Hosting (IWebHostEnvironment), Microsoft.AspNetCore.Http (HttpContext), BCL Uri.
      • -
      • Concept introduced, a computed CSP that fails closed. [Rubric §26, Front-End Security] assesses whether the browser is told which origins may load scripts and open connections. A static CSP cannot express "this deployment's API origin", because that origin is configuration, so the policy is built rather than hard-coded. The two directives that matter for exfiltration are locked: script-src 'self' 'wasm-unsafe-eval' (line 99, where the WASM allowance is what lets the Blazor WebAssembly runtime instantiate) and the computed connect-src (line 79). The load-bearing decision is what happens when the origin cannot be determined. The provider narrows connect-src to 'self', keeps the rest of the policy unchanged, and stays enforced (Enforce: true, line 62). A misconfigured endpoint therefore surfaces immediately as blocked API calls in the browser console rather than as a permissive header that protects nothing and that nobody notices, and the class doc states the reasoning outright: a security response header that quietly stops being enforced is the worse failure mode (lines 16-20).
          -
        • [Rubric §11, Security] assesses the wider defense posture; this class is one control in a chain that also includes the session-cookie auth design and the security-headers middleware, and it is deliberately internal (line 24) so the only supported way to get it is the registration call, not a hand-wired new.
        • -
        • frame-src is opt-in and null-safe. BlazorCspSettings's default is an empty FrameSources list, and BuildFrameSrc (line 85) returns null when it is empty (lines 87-90), which is what keeps the policy byte-identical to the pre-existing baseline for every deployment that has not opted in (comment, lines 83-84).
        • -
        -
      • -
      • Walkthrough
          -
        • _policy (line 32) is a single CspPolicy field computed once. The constructor (lines 33-43) null-guards apiOptions, cspOptions, and environment and calls BuildCsp(apiOptions.Value, BuildFrameSrc(cspOptions.Value), environment.IsDevelopment()) (line 42). Because the type is registered as a singleton, this runs exactly once per process.
        • -
        • GetPolicy(HttpContext context) (line 45) ignores the context and returns the cached policy, so the per-request cost is a field read.
        • -
        • BuildCsp (lines 49-80) now takes the pre-computed frameSrc as a parameter and resolves the endpoint as api.WasmApiEndpoint ?? api.ApiEndpoint (line 51). The guard on lines 55-59 rejects a blank value, a non-absolute URI, and any scheme that is not http or https; the comment on lines 53-54 records why the scheme check is not redundant: on Linux a rooted path such as /relative/path parses as an absolute file:// URI and would otherwise sail through Uri.TryCreate. A rejected endpoint returns the enforced connect-src 'self' policy, still carrying frameSrc (line 62).
        • -
        • With a valid endpoint it derives origin via apiUri.GetLeftPart(UriPartial.Authority) (line 66, scheme://host:port), picks wss or ws to match (line 67), and composes connect-src 'self' {origin} {wsScheme}://{authority} (line 68). The WebSocket origin is there for the SignalR notification hub, so the live push channel is allowed without opening connect-src to the world.
        • -
        • Development only (lines 74-77) appends http://localhost:* and ws://localhost:* for Visual Studio Browser Link and Hot Reload, whose ports change per run (comment, lines 70-73); the production policy is untouched.
        • -
        • BuildFrameSrc(BlazorCspSettings settings) (lines 85-98) is new: it returns null on an empty FrameSources (lines 87-90), so the directive is omitted entirely rather than emitted empty; otherwise it maps every entry through BlazorCspSettingsValidator.ToOrigin, de-duplicates with StringComparer.OrdinalIgnoreCase (lines 92-95), and joins the result into "frame-src 'self' {origins}" (line 97). The comment (lines 82-84) notes every entry already passed the startup validator, so canonicalization here cannot fail.
        • -
        • BuildPolicy (lines 103-112) now takes frameSrc as a parameter and assembles the directive list: default-src 'self', the script-src above plus 'unsafe-inline' in Development only (line 99, for the injected Hot Reload bootstrap), style-src 'self' 'unsafe-inline', img-src 'self' data: https: (line 101, deliberately open because profile pictures and content images come from arbitrary external hosts, per the comment on lines 93-96 of the class), font-src 'self', the computed connect-src, the frame-src directive when non-null (line 110, spliced in with its own trailing "; " so a null value leaves the rest of the policy unchanged), base-uri 'self', form-action 'self', and frame-ancestors 'none' (line 112, clickjacking protection).
        • +
        • BlazorTransportPrefix (:39): "/_blazor", the Blazor circuit transport (negotiate, the circuit WebSocket and its long-polling fallback), excluded from the concurrency ceiling only.
        • +
        • ExemptPrefixes (:52) and IsExempt(PathString) (:75-84): the shared exemption rule, detailed above; internal so it is unit-testable via InternalsVisibleTo (:70-73).
        • +
        • ClientIpPartition(HttpContext, UiRateLimitingSettings) (:94-122): no limiter for exempt paths, no limiter for an unresolvable client IP (fail open, :107-112, so an in-process TestServer is never collapsed into one shared bucket), otherwise a FixedWindowRateLimiter keyed on the client IP with QueueLimit = 0 (rejected immediately, not queued, :114-121).
        • +
        • ConcurrencyPartition(HttpContext, UiRateLimitingSettings) (:134-150): the replica-wide ConcurrencyLimiter, one bucket for the whole process with QueueLimit = 0, exempt for every path IsExempt matches plus any path under BlazorTransportPrefix (:141-142).
        • +
        • AddUiRateLimiting(IConfiguration) (:161-194), an IServiceCollection extension: binds and validates UiRateLimitingSettings (:166-169), then resolves a plain instance from configuration to close over in the partition callbacks (:174-175) rather than resolving IOptions<T> per request, because the partition callback runs on the hot path of every request and an out-of-range value has already failed ValidateOnStart(). RejectionStatusCode is set to 429 (:179); when Enabled is false the method returns early with no GlobalLimiter configured (:181-184); otherwise it chains the client-IP and concurrency partitions with PartitionedRateLimiter.CreateChained (:188-192), so a request must satisfy both.
        • +
        • UseUiRateLimiting() (:203-207), an IApplicationBuilder extension: calls the framework's UseRateLimiter().
      • -
      • Why it's built this way: computing once and caching keeps the hot path free, and returning a CspPolicy record rather than writing a header directly keeps the provider testable and lets one middleware own header emission. Registering it with AddSingleton (not TryAdd) is what makes it replace the default static provider, which is why the ordering rule in the class doc (lines 21-22) matters: call AddCommonBlazorCsp() before AddCommonSecurityHeaders. frame-src is threaded through as a plain string? parameter rather than read a second time inside BuildPolicy, so the policy string is still built from one pass over its inputs.
      • -
      • Where it's used: registered by AddCommonBlazorCsp() in the MMCA.Common.UI.Web DependencyInjection (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/DependencyInjection.cs); the policy it returns is emitted by SecurityHeadersMiddleware. The class doc notes it was hoisted out of the app Blazor Web hosts where it had been byte-identical (line 21).
      • -
      • Caveats / not-in-source: the registration method's own XML doc still describes the fallback as a "permissive Report-Only fallback on misconfiguration" (MMCA.Common.UI.Web/DependencyInjection.cs:39). The code is the truth: the fallback is enforced and narrowed to 'self' (line 62). Treat that doc line as stale.
      • +
      • Why it's built this way: see UiRateLimitingSettings for the tuning rationale. See ADR-088 for the edge-hardening split between this host and the Gateway, ADR-124 for why circuits are bounded by their own ceiling rather than by this limiter, and ADR-131 for the same-origin proxy behind the /api/hubs exemption.
      • +
      • Where it's used: AddUiRateLimiting and UseUiRateLimiting are called from MMCA.ADC.UI.Web's Blazor Server host composition (Program.cs); the extension methods live on the IServiceCollection/IApplicationBuilder extension blocks (:152, :197).

      BlazorCircuitLimitExtensions

      @@ -5481,95 +5708,404 @@

      BlazorCircuitLimitExtensions

    1541. Why it's built this way: keeps circuit-limit registration next to the retention-callback factory, both consumers of the same BlazorCircuitLimitSettings, while the enforcement logic itself stays in BoundedCircuitHandler. See ADR-088.
    1542. Where it's used: AddBoundedBlazorCircuits() and RetentionFrom are both called from MMCA.ADC.UI.Web's Blazor Server host composition (Program.cs).
    1543. -

      NotificationInbox

      +

      HandoffBody

      -

      MMCA.Common.UI · MMCA.Common.UI.Pages.Notifications · MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Notifications/NotificationInbox.razor.cs:26 · Level 4 · class (partial, page)

      +

      MMCA.Common.UI.Web · MMCA.Common.UI.Web.SameOriginProxy · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SessionHandoffEndpoints.cs:64 · Level 0 · record

        -
      • What it is: the code-behind for the per-user notification inbox, routed at both @page "/notifications/inbox" and @page "/notifications/inbox/{Id:int}" (NotificationInbox.razor:1-2). It fetches the signed-in user's notifications a page at a time, renders each as a read or unread card, lets the user mark items read individually or all at once, highlights and scrolls to a deep-linked notification, and reloads the current page when a real-time push asks for a refresh.
      • -
      • Depends on: first-party: INotificationInboxUIService (the typed read-side HTTP service), NotificationState (the per-circuit unread-count store and refresh signal), UserNotificationDTO (the row shape), IToastService (the toast abstraction), ResultUiExtensions (the NotifyOnFailure helper), and SharedResource (the resx anchor for the localizer). Externals: MudBlazor (IScrollManager, ScrollBehavior, BreadcrumbItem, Icons), Microsoft.AspNetCore.Components ([Inject], [Parameter], OnInitializedAsync, OnParametersSet, OnAfterRenderAsync, InvokeAsync, StateHasChanged), Microsoft.Extensions.Localization (IStringLocalizer<T>), BCL CancellationTokenSource, IDisposable, Math.Ceiling, CultureInfo.InvariantCulture.
      • -
      • Concept introduced, the Blazor code-behind page pattern (.razor plus .razor.cs partial class). The three notification pages in this unit are authored as partial classes split across two files: the .razor holds declarative MudBlazor markup and the routes, the .razor.cs holds the C# (public partial class NotificationInbox, line 26), the injected services, the view state, and the handlers. The framework constructs the component, calls OnInitializedAsync (line 73) once, and re-renders when a handler mutates a field. Four habits recur across all three pages and are worth learning once here:
          -
        • Disposal-safe async with a per-component CancellationTokenSource. A readonly CancellationTokenSource _cts (line 45) is created with the component and its token is passed to every service call. Dispose(bool) (lines 338-350) cancels and disposes it behind the classic _disposed guard (line 336). Every async handler swallows OperationCanceledException silently (for example lines 241-244) because that is the expected outcome when the user navigates away mid-fetch.
        • -
        • Result-typed failures, not exceptions. The service calls return a Result, so the page branches on TryGetValue (line 220) and routes the failure through result.NotifyOnFailure(Toast, L) (line 238). The comment there (lines 236-237) records the rule that makes this safe: one toast, and the list is left as it was rather than blanked, so a transient failure does not erase what is on screen.
        • -
        • Busy flags gate the UI. IsLoading and IsSaving (lines 51-52) are protected with private setters; the markup shows progress while loading and sets Disabled="IsSaving" on the action controls (NotificationInbox.razor:18) so a double click cannot double-post.
        • -
        • Push-driven refresh via an event subscription. OnInitializedAsync subscribes to NotificationState.OnRefreshRequested (line 83) and Dispose(bool) unsubscribes (line 344).
        • -
        • [Rubric §19, State Management & Data Flow] assesses where state lives; transient view state stays in private fields (_notifications, _currentPage, _totalPages, lines 54-56) while the shared unread count is written back into the scoped NotificationState (lines 289, 323) and its refresh signal is read. Local stays local, shared stays shared.
        • -
        • [Rubric §25, Navigation, Routing & Information Architecture] assesses route structure. The second @page directive is a typed deep link: a push payload or an email can point straight at one notification. The class doc (lines 18-24) states the two design rules that follow from it: the :int route constraint is the validation boundary, so a malformed id never reaches the component (the router renders NotFound instead), and an id that is simply not on the loaded page degrades silently to the plain inbox rather than raising an error the user can do nothing about.
        • -
        • [Rubric §21, Accessibility]: the icon-only mark-read control carries an explicit localized aria-label (NotificationInbox.razor:59).
        • -
        • [Rubric §27, Internationalization & Localization]: this page holds no literal English. The injected IStringLocalizer<SharedResource> L (line 33) resolves the title (line 47), the breadcrumbs, and every toast (L["Notif.AllMarkedRead"], line 324). The breadcrumb trail is built inside OnInitializedAsync (lines 77-81), not in a field initializer, so the injected localizer is available and labels re-resolve per circuit under the active culture (comment, lines 75-76, citing ADR-027).
        • +
        • What it is: the JSON body both session-handoff endpoints exchange, { "handoff": "..." }: a single nullable Handoff string (SessionHandoffEndpoints.cs:63-64). It is a nested internal sealed record of SessionHandoffEndpoints.
        • +
        • Depends on: nothing first-party.
        • +
        • Concept: a wire DTO for an opaque, Data-Protection-sealed token; the sealing is taught on SessionHandoffProtector.
        • +
        • Walkthrough: the token endpoint returns it with a protected access token (SessionHandoffEndpoints.cs:34); the cookie endpoint binds it from the request body and unprotects body.Handoff (SessionHandoffEndpoints.cs:42,49).
        • +
        • Why it's built this way: the property is nullable so a missing or empty body binds instead of failing model binding; the protector then treats null or whitespace as "no handoff" (SessionHandoffProtector.cs:59-62) and the endpoint answers 400 invalid_handoff (SessionHandoffEndpoints.cs:50-53).
        • +
        • Where it's used: only inside SessionHandoffEndpoints.cs.
        • +
        +

        ProxyResponseMode

        +
        +

        MMCA.Common.UI.Web · MMCA.Common.UI.Web.SameOriginProxy · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginProxyTransformer.cs:14 · Level 0 · enum

        +
        +
          +
        • What it is: how the same-origin proxy treats the upstream response of one forwarded request: Forward (copy as is), TokenIssuing (a sign-in: move the token pair into the session cookies and strip it from the body), Revoke (a sign-out: copy, then clear the session cookies) (SameOriginProxyTransformer.cs:17,20,23).
        • +
        • Depends on: nothing first-party.
        • +
        • Concept: a strategy selector passed into the per-request SameOriginProxyTransformer; see that section for each mode's mechanism.
        • +
        • Walkthrough: SameOriginApiProxyEndpoint picks the mode in ResolveMode (SameOriginApiProxyEndpoint.cs:291-311): every non-POST is Forward; a POST to the refresh path yields null (the proxy answers it itself); a POST to a token-issuing path is TokenIssuing; a POST to the revoke path is Revoke; any other POST is Forward.
        • +
        • Why it's built this way: the endpoint decides once per request and the transformer branches on it (SameOriginProxyTransformer.cs:64,84,91), so the request path matching stays in one class and the body rewriting in another.
        • +
        • Where it's used: SameOriginApiProxyEndpoint and SameOriginProxyTransformer only; it is internal.
        • +
        +

        SameOriginApiProxyMarker

        +
        +

        MMCA.Common.UI.Web · MMCA.Common.UI.Web.SameOriginProxy · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyServiceExtensions.cs:92 · Level 0 · class

        +
        +
          +
        • What it is: an empty internal sealed class registered as a singleton only by AddCommonSameOriginApiProxy (SameOriginApiProxyServiceExtensions.cs:81,92), so other code can ask the container "did this host opt in to the proxy?".
        • +
        • Depends on: nothing.
        • +
        • Concept: a DI marker service: presence in the container is the signal, the instance carries no state.
        • +
        • Walkthrough: no members.
        • +
        • Why it's built this way: checking a private marker type is cheaper and more precise than probing for a public service a host could register for other reasons (for example the options type, which binding alone would create).
        • +
        • Where it's used: SameOriginApiProxyEndpointExtensions throws when it is absent, naming the missing call (SameOriginApiProxyEndpointExtensions.cs:38-42); ClientConfigEndpointExtensions reads it to decide whether to publish the proxy's path prefix to the client (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs:93,98).
        • +
        +

        SameOriginApiProxySettings

        +
        +

        MMCA.Common.UI.Web · MMCA.Common.UI.Web.SameOriginProxy · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxySettings.cs:21 · Level 0 · class

        +
        +
          +
        • What it is: the options class for the same-origin API proxy, bound from configuration section SameOriginApiProxy (SameOriginApiProxySettings.cs:24) and validated at startup.
        • +
        • Depends on: SameSiteMode (ASP.NET Core); first-party only by reference: the gateway address falls back to ApiSettings's ApiEndpoint.
        • +
        • Concept: options pattern with fail-fast validation, same shape as the other settings classes in this group (for example BlazorCspSettings with BlazorCspSettingsValidator). [Rubric §33, Developer Experience] assesses whether misconfiguration surfaces early and clearly; every knob here has a default, so a host opts in with no configuration at all.
        • +
        • Walkthrough:
            +
          • DefaultTokenIssuingPaths = auth/login, auth/register, auth/oauth/exchange (SameOriginApiProxySettings.cs:31): the endpoints whose successful response carries a token pair.
          • +
          • PathPrefix = /api (:38): a request to {PathPrefix}/orders/5 is forwarded to {gateway}/orders/5.
          • +
          • GatewayAddress (:46): null by default; AddCommonSameOriginApiProxy fills it from Api:ApiEndpoint when unset (SameOriginApiProxyServiceExtensions.cs:57-64), so an Aspire discovery name such as https+http://gateway resolves as it does for the host's API clients.
          • +
          • AdditionalTokenIssuingPaths (:52): empty; a host adds paths such as auth/2fa/verify.
          • +
          • RefreshPath = auth/refresh (:59): answered by the proxy itself from the refresh cookie.
          • +
          • RevokePath = auth/revoke (:66): forwarded, then the cookies are cleared whatever upstream answered.
          • +
          • SessionCookieSameSite = SameSiteMode.Strict (:74); Lax is the only other accepted value.
          • +
          +
        • +
        • Why it's built this way: recorded in ADR-131: a WebAssembly client calling the gateway cross-origin cannot carry the HttpOnly session cookie, so tokens end up script-readable; a same-origin proxy on the UI host lets the cookie authenticate data calls instead.
        • +
        • Where it's used: SameOriginApiProxyServiceExtensions (binding, and copying SessionCookieSameSite onto SessionCookieSettings, SameOriginApiProxyServiceExtensions.cs:69-74), SameOriginApiProxySettingsValidator, SameOriginApiProxyEndpoint, SameOriginApiProxyEndpointExtensions and ClientConfigEndpointExtensions.cs:98.
        • +
        +

        SameOriginProxyInvoker

        +
        +

        MMCA.Common.UI.Web · MMCA.Common.UI.Web.SameOriginProxy · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginProxyInvoker.cs:19 · Level 0 · class

        +
        +
          +
        • What it is: an IDisposable holder for the one HttpMessageInvoker YARP's forwarder sends every proxied request through (SameOriginProxyInvoker.cs:19,37).
        • +
        • Depends on: IServiceDiscoveryHttpMessageHandlerFactory (Microsoft.Extensions.ServiceDiscovery), SocketsHttpHandler, YARP's ReverseProxyPropagator.
        • +
        • Concept: YARP's IHttpForwarder expects a long-lived invoker with a transport tuned for proxying, not a pooled HttpClient. [Rubric §12, Performance & Scalability] assesses connection reuse and resource lifetime: one singleton transport is shared by all requests.
        • +
        • Walkthrough:
            +
          • The public ctor resolves the optional discovery factory and builds the handler (SameOriginProxyInvoker.cs:24-31); the invoker is created with disposeHandler: false and the class keeps _ownedHandler to dispose itself (:22,39-43).
          • +
          • An internal ctor takes a test-supplied handler, for example a TestServer's (:34-35); that handler is not owned or disposed.
          • +
          • CreateHandler (:45-69) configures SocketsHttpHandler: UseProxy = false, AllowAutoRedirect = false, no automatic decompression, UseCookies = false, multiple HTTP/2 connections, ConnectTimeout 15 s, and a ReverseProxyPropagator over DistributedContextPropagator.Current (:50-59). When discovery is registered it wraps the transport (:61) so https+http://gateway resolves. The null-out-then-finally pattern disposes the transport only if wrapping throws.
          • +
          +
        • +
        • Why it's built this way: redirects, cookies and decompression must pass through to the browser untouched, which is why they are all off; the propagator keeps the trace context flowing to the gateway.
        • +
        • Where it's used: injected into SameOriginApiProxyEndpoint (SameOriginApiProxyEndpoint.cs:31,342), registered by SameOriginApiProxyServiceExtensions.cs:78, and built from a handler in the test harness MMCA.Common/Tests/Presentation/MMCA.Common.UI.Web.Tests/SameOriginProxy/ProxyTestHarness.cs.
        • +
        +

        TokenPair

        +
        +

        MMCA.Common.UI.Web · MMCA.Common.UI.Web.SameOriginProxy · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SessionHandoffProtector.cs:75 · Level 0 · record

        +
        +
          +
        • What it is: a private nested record (AccessToken, RefreshToken) of SessionHandoffProtector (SessionHandoffProtector.cs:75), the plaintext shape serialized to JSON before it is sealed.
        • +
        • Depends on: nothing first-party; System.Text.Json.
        • +
        • Walkthrough: serialized in ProtectTokenPair (SessionHandoffProtector.cs:33-34) and deserialized in UnprotectTokenPair, which rejects a null pair or either token blank (:46-49).
        • +
        • Where it's used: only inside SessionHandoffProtector.cs; callers see a value tuple instead.
        • +
        +

        SameOriginApiProxySettingsValidator

        +
        +

        MMCA.Common.UI.Web · MMCA.Common.UI.Web.SameOriginProxy · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxySettingsValidator.cs:14 · Level 1 · class

        +
        +
          +
        • What it is: the IValidateOptions<SameOriginApiProxySettings> that fails the boot on an unusable proxy configuration, collecting every failure into one result (SameOriginApiProxySettingsValidator.cs:14,19-51).
        • +
        • Depends on: SameOriginApiProxySettings; SearchValues<char>, ValidateOptionsResult.
        • +
        • Concept: startup validation via ValidateOnStart, cross-referenced to BlazorCspSettingsValidator. [Rubric §11, Security] applies to the SameSite check: only Strict or Lax pass, because the cookie now authenticates data calls (:40-43).
        • +
        • Walkthrough:
            +
          • ForbiddenPathCharacters = {}*?#\ (:16): route syntax, query, fragment and backslash.
          • +
          • PathPrefix must pass IsValidPrefix (:26-31,54-60): non-blank, longer than one char, starts with /, does not end with /, no whitespace, no forbidden char. It becomes a route pattern, hence the literal-path rule.
          • +
          • GatewayAddress must be an absolute URI with a host (:33-38); the message names the Api:ApiEndpoint fallback.
          • +
          • RefreshPath, RevokePath and every AdditionalTokenIssuingPaths entry must pass IsValidRelativePath (:45-49,62-67): not blank after trimming /, no whitespace, no forbidden char, no ://.
          • +
          +
        • +
        • Why it's built this way: an invalid prefix would otherwise surface as a route-pattern exception or a silently unmatched route at request time; naming the setting at boot is cheaper to diagnose.
        • +
        • Where it's used: registered with TryAddEnumerable by SameOriginApiProxyServiceExtensions.cs:66-67.
        • +
        +

        SessionHandoffProtector

        +
        +

        MMCA.Common.UI.Web · MMCA.Common.UI.Web.SameOriginProxy · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SessionHandoffProtector.cs:16 · Level 1 · class

        +
        +
          +
        • What it is: seals tokens with ASP.NET Core Data Protection so the Blazor Server circuit can pass them through browser script without script being able to read or replay them later (SessionHandoffProtector.cs:16).
        • +
        • Depends on: IDataProtectionProvider / ITimeLimitedDataProtector (Microsoft.AspNetCore.DataProtection); nested TokenPair.
        • +
        • Concept introduced, the protected handoff. With the proxy enabled the HttpOnly cookies are the only session store, but a Server circuit runs over a WebSocket where it cannot read cookies on demand, and it may need to seed the cookies after a sign-in. The handoff hops through JS interop carrying an opaque blob that only this server can open, valid for one minute. [Rubric §26, Front-End Security] assesses keeping credentials out of script-reachable storage: the browser holds ciphertext with a 1-minute lifetime, never a usable bearer.
        • +
        • Walkthrough:
            +
          • Lifetime = 1 minute, "one fetch plus one interop hop" (:19).
          • +
          • Two purpose-isolated protectors under the root MMCA.Common.UI.Web.SameOriginProxy.SessionHandoff, one for AccessToken, one for TokenPair (:21-27), so a blob minted for one cannot be opened as the other.
          • +
          • ProtectAccessToken / UnprotectAccessToken (:29,31); ProtectTokenPair serializes a TokenPair to JSON (:33-34); UnprotectTokenPair returns a tuple or null on a missing, blank or malformed pair (:36-55).
          • +
          • TryUnprotect (:57-73) maps null or whitespace input, and CryptographicException or FormatException (tampered, expired, wrong purpose, retired key), to null.
          • +
          +
        • +
        • Why it's built this way: see ADR-131. Returning null instead of throwing lets each endpoint answer a clean 401 or 400.
        • +
        • Where it's used: SessionHandoffEndpoints, HandoffTokenRefresher, HandoffSessionCookieSync; registered as a singleton at SameOriginApiProxyServiceExtensions.cs:80; tested in MMCA.Common/Tests/Presentation/MMCA.Common.UI.Web.Tests/SameOriginProxy/SameOriginApiProxyAuthFlowTests.cs and SessionHandoffServicesTests.cs.
        • +
        • Caveats / not-in-source: whether the host's Data Protection key ring is persisted and shared across replicas is host configuration; AddDataProtection() here (SameOriginApiProxyServiceExtensions.cs:77) adds no persistence. Not determinable from this source.
        • +
        +

        HandoffSessionCookieSync

        +
        +

        MMCA.Common.UI.Web · MMCA.Common.UI.Web.SameOriginProxy · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/HandoffSessionServices.cs:39 · Level 2 · class

        +
        +
          +
        • What it is: the proxy-mode ISessionCookieSync for the Blazor Server circuit: after a sign-in it hands the browser a protected token pair to post back, instead of the raw tokens (HandoffSessionServices.cs:39).
        • +
        • Depends on: IJSRuntime, SessionHandoffProtector.
        • +
        • Concept: cross-reference SessionHandoffProtector; it replaces the default sync (compare JsFetchSessionCookieSync).
        • +
        • Walkthrough: SyncAsync calls mmcaAuthHandoff.setCookie with ProtectTokenPair(...) (:41-51); the script posts it to /auth/session-cookie/handoff (see SessionHandoffEndpoints). ClearAsync calls mmcaAuthCookie.clear (:53-63). Both return false when interop is unavailable (InvalidOperationException, JSDisconnectedException, JSException, OperationCanceledException, :65-66).
        • +
        • Where it's used: registered with Replace as scoped (SameOriginApiProxyServiceExtensions.cs:84) and verified at map time by SameOriginApiProxyEndpointExtensions.
        • +
        • Caveats / not-in-source: the mmcaAuthHandoff / mmcaAuthCookie JavaScript is not in this C# source and was not read.
        • +
        +

        HandoffTokenRefresher

        +
        +

        MMCA.Common.UI.Web · MMCA.Common.UI.Web.SameOriginProxy · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/HandoffSessionServices.cs:14 · Level 2 · class

        +
        +
          +
        • What it is: the proxy-mode ITokenRefresher for the Blazor Server circuit: it gets an access token from the cookie session through a protected handoff (HandoffSessionServices.cs:14).
        • +
        • Depends on: IJSRuntime, SessionHandoffProtector.
        • +
        • Concept: cross-reference SessionHandoffProtector; it replaces the default refresher (compare DirectApiTokenRefresher).
        • +
        • Walkthrough: AcquireAccessTokenAsync calls mmcaAuthHandoff.getToken (:20), whose script calls /auth/session/handoff; the returned blob is unprotected server-side (:21). Interop failure during SSR prerender or a disconnected circuit returns null (:23-28).
        • +
        • Where it's used: registered with Replace as scoped (SameOriginApiProxyServiceExtensions.cs:83); tested in SessionHandoffServicesTests.cs.
        • +
        • Caveats / not-in-source: the JavaScript side is not in this C# source.
        • +
        +

        SameOriginProxyTransformer

        +
        +

        MMCA.Common.UI.Web · MMCA.Common.UI.Web.SameOriginProxy · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginProxyTransformer.cs:34 · Level 12 · class

        +
        +
          +
        • What it is: the per-request YARP HttpTransformer that rewrites the outbound request (path, bearer, cookies) and treats the response according to its ProxyResponseMode (SameOriginProxyTransformer.cs:34-39).
        • +
        • Depends on: ISessionCookieStore, SessionCookieEndpoints (cookie names), SessionClaimsToken, SameOriginProxyHeaders; YARP HttpTransformer and RequestUtilities.
        • +
        • Concept: [Rubric §26, Front-End Security] (credentials kept out of script): the token pair from a sign-in never reaches the browser body, and the cookie jar's token copies never reach the upstream.
        • +
        • Walkthrough:
            +
          • TransformRequestAsync (:47-69): runs YARP's default, strips the prefix to build the destination URI (:55-58), sets Authorization: Bearer from the session or removes it (:60), drops the CSRF header (:61), removes the session cookies (:62), and in TokenIssuing mode clears Accept-Encoding so the body arrives as plain JSON (:64-68).
          • +
          • TransformResponseAsync (:71-98): when captureUnauthorized is on and upstream answered 401, sets UnauthorizedCaptured and copies nothing so the endpoint can replay (:76-80); in Revoke clears the cookies whatever upstream said (:84-89); in TokenIssuing on success rewrites the body (:91-95).
          • +
          • RewriteTokenResponseAsync / TryMoveTokensToCookies (:106-145): parses the JSON case-insensitively, writes both tokens to the cookie store (:141), replaces accessToken with its claims-only form and empties refreshToken (:142-143), and sets no-store (:115-117). A body without a token pair passes through unchanged.
          • +
          • RemoveSessionCookies (:156-174): filters only the access and refresh cookie pairs from the Cookie header; other cookies pass.
          • +
          +
        • +
        • Why it's built this way: the browser-facing JSON keeps its shape, so a client written for the non-proxy mode still parses a well-formed response (:100-105).
        • +
        • Where it's used: created per request by SameOriginApiProxyEndpoint (SameOriginApiProxyEndpoint.cs:336-337).
        • +
        +

        SameOriginApiProxyEndpoint

        +
        +

        MMCA.Common.UI.Web · MMCA.Common.UI.Web.SameOriginProxy · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpoint.cs:29 · Level 13 · class

        +
        +
          +
        • What it is: the request handler behind {PathPrefix}/{**path}: gates the request, resolves the session from the cookies, forwards to the gateway with a bearer, and refreshes and replays once on an unexpected 401 (SameOriginApiProxyEndpoint.cs:29-35,60-105).
        • +
        • Depends on: YARP IHttpForwarder, SameOriginProxyInvoker, ICookieSessionRefresher with SessionRefreshOutcome / SessionRefreshStatus, ISessionCookieStore, SameOriginApiProxySettings, SameOriginProxyTransformer, SessionClaimsToken.
        • +
        • Concept introduced, a Backend-for-Frontend proxy. The UI host forwards same-origin calls to the gateway, so the browser's cookie authenticates them and the bearer is attached server-side. [Rubric §26, Front-End Security] assesses CSRF and origin defenses: a same-origin gate and a CSRF header gate run before anything is forwarded. [Rubric §29, Resilience & Business Continuity] assesses graceful degradation: an undecided refresh answers 503 with Retry-After and keeps the session.
        • +
        • Walkthrough:
            +
          • Config: RequestConfig uses a 100 s activity timeout and HTTP/1.1 with RequestVersionOrLower (:43-48), which lets YARP turn an HTTP/2 WebSocket into an HTTP/1.1 upgrade; DefaultRetryAfter 5 s (:51); _tokenIssuingPaths merges the defaults and additions case-insensitively (:54-58).
          • +
          • InvokeAsync (:60-105): disables status code pages so a proxied empty 401/404 is not replaced by an HTML page (:66-69); runs TryAnswerBeforeForwardingAsync; resolves the mode, with null meaning RefreshLocallyAsync (:76-81); for non-sign-in requests that carry a session cookie calls ValidateOrRefreshAsync and fails via FailRefreshAsync when no session results (:85-96); forwards with 401 capture on only for a bearer plus a replayable request (:98-99); replays on capture (:101-104).
          • +
          • Gates, in order (TryAnswerBeforeForwardingAsync, :232-259): CrossOriginRejection (403 cross_origin_rejected); OPTIONS answered 204 locally, never forwarded, no CORS grant; unsafe methods other than an HTTP/2 WebSocket CONNECT need the CSRF header (403 csrf_header_required). HasCsrfHeader requires exactly one header value equal to SameOriginProxyHeaders's value (:110-113).
          • +
          • CrossOriginRejection (:131-137): OriginRejection refuses a foreign Origin and an upgrade with no Origin (:180-188); IsOwnOrigin compares scheme and server against the request as seen after forwarded headers, refusing paths, user info and the null origin (:146-160); FetchSiteRejection allows same-origin, and none only on a non-upgrade GET or HEAD (:190-207). IsWebSocketExtendedConnect detects RFC 8441 CONNECT (:169-172).
          • +
          • IsReplayable (:215-217): GET, HEAD or OPTIONS and not an upgrade, so nothing with a body is re-sent.
          • +
          • FailRefreshAsync (:274-285): Rejected ends the session (clear cookies, 401 session_expired, :262-266); anything else answers 503 session_refresh_unavailable with Retry-After from upstream or 5 s.
          • +
          • RefreshAndReplayAsync (:318-329): resets status to 200, forces RefreshAsync, forwards again without capture.
          • +
          • RefreshLocallyAsync (:356-374): rotates from the refresh cookie and answers the sign-in shape with a claims-only access token, empty refresh token and the expiry, no-store.
          • +
          • Four [LoggerMessage] warnings (:376-386); ForwardAsync logs a forward error only if the client did not abort (:339-349).
          • +
          +
        • +
        • Why it's built this way: ADR-131 records the decision; the gateway stays the API edge and authorizes the forwarded bearer.
        • +
        • Where it's used: mapped by SameOriginApiProxyEndpointExtensions; its HasCsrfHeader is reused by SessionHandoffEndpoints (SessionHandoffEndpoints.cs:26,44); registered as a singleton at SameOriginApiProxyServiceExtensions.cs:79.
        • +
        +

        SameOriginApiProxyServiceExtensions

        +
        +

        MMCA.Common.UI.Web · MMCA.Common.UI.Web.SameOriginProxy · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyServiceExtensions.cs:37 · Level 14 · class

        +
        +
          +
        • What it is: the opt-in registration, services.AddCommonSameOriginApiProxy(configuration) (SameOriginApiProxyServiceExtensions.cs:51), written as a C# extension(IServiceCollection) block (see C# extension(T) types).
        • +
        • Depends on: every proxy type above; ApiSettings, SessionCookieSettings, ITokenRefresher, ISessionCookieSync; YARP AddHttpForwarder, Data Protection.
        • +
        • Walkthrough:
            +
          • Binds SameOriginApiProxySettings, post-configures the gateway from Api:ApiEndpoint when unset, ValidateOnStart (:55-65), and adds the validator (:66-67).
          • +
          • Configures SessionCookieSettings: SameSite from the proxy settings and ClaimsOnlyBrowserTokens = true (:69-74).
          • +
          • AddHttpForwarder, AddDataProtection (:76-77); singletons for the invoker, endpoint, protector and SameOriginApiProxyMarker (:78-81).
          • +
          • Replaces the scoped ITokenRefresher and ISessionCookieSync with HandoffTokenRefresher and HandoffSessionCookieSync (:83-84).
          • +
          +
        • +
        • Why it's built this way: opt-in per host; a host that never calls it is unchanged (ADR-131). Replace means call order matters, which MapCommonSameOriginApiProxy checks.
        • +
        • Where it's used: paired with SameOriginApiProxyEndpointExtensions; exercised in MMCA.Common/Tests/Presentation/MMCA.Common.UI.Web.Tests/SameOriginProxy/SameOriginApiProxyOptInTests.cs.
        • +
        • Caveats / not-in-source: which consumer hosts call it is not determinable from this source (no call site under MMCA.Common/Source other than its own pairing).
        • +
        +

        SessionHandoffEndpoints

        +
        +

        MMCA.Common.UI.Web · MMCA.Common.UI.Web.SameOriginProxy · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SessionHandoffEndpoints.cs:15 · Level 14 · class

        +
        +
          +
        • What it is: maps the two Blazor Server circuit handoff endpoints, POST /auth/session/handoff and POST /auth/session-cookie/handoff (SessionHandoffEndpoints.cs:17-18,20).
        • +
        • Depends on: ICookieSessionRefresher, ISessionCookieStore, SessionHandoffProtector, SameOriginApiProxyEndpoint (HasCsrfHeader), HandoffBody.
        • +
        • Walkthrough:
            +
          • Token handoff (:23-38): requires the CSRF header (else 403), calls GetOrRefreshAsync on the cookie session, answers 401 no_session or a HandoffBody with a protected access token.
          • +
          • Cookie handoff (:41-60): requires the CSRF header, unprotects the token pair (400 invalid_handoff on failure), writes the cookies, answers 204.
          • +
          • Both are ExcludeFromDescription, AllowAnonymous, DisableAntiforgery; the CSRF header is the gate.
        • +
        • Where it's used: called from MapCommonSameOriginApiProxy (SameOriginApiProxyEndpointExtensions.cs:54); reached from the browser by HandoffTokenRefresher and HandoffSessionCookieSync through their JS interop.
        • +
        +

        SameOriginApiProxyEndpointExtensions

        +
        +

        MMCA.Common.UI.Web · MMCA.Common.UI.Web.SameOriginProxy · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpointExtensions.cs:15 · Level 15 · class

        +
        +
          +
        • What it is: endpoints.MapCommonSameOriginApiProxy() (SameOriginApiProxyEndpointExtensions.cs:33), which maps {PathPrefix}/{**path} for every method (WebSocket upgrades included) plus the handoff endpoints, after checking the registrations.
        • +
        • Depends on: SameOriginApiProxyMarker, SameOriginApiProxySettings, SameOriginApiProxyEndpoint, SessionHandoffEndpoints, HandoffTokenRefresher, HandoffSessionCookieSync.
        • +
        • Concept: fail-fast wiring checks. [Rubric §33, Developer Experience]: a wrong registration order fails the boot with a message naming the fix rather than silently leaking tokens.
        • +
        • Walkthrough:
            +
          • Throws InvalidOperationException if the marker is absent (:38-42).
          • +
          • VerifyCircuitRegistrations (:64-69) resolves ITokenRefresher and ISessionCookieSync in a scope; a resolved type other than the handoff implementation throws, telling the host to call AddCommonSameOriginApiProxy after every such registration (:71-92). A missing dependency such as IJSRuntime (no interactive Server circuit) skips the check (:79-83).
          • +
          • Maps the proxy route to proxy.InvokeAsync as ExcludeFromDescription, AllowAnonymous, DisableAntiforgery (:49-52), then SessionHandoffEndpoints.Map (:54).
          • +
          +
        • +
        • Why it's built this way: a later ITokenRefresher registration would put the access token back in the page on every Server-circuit hydration (:59-63); the doc comment asks hosts to map it after UseAuthorization (:19-26).
        • +
        • Where it's used: by consumer hosts that opted in. Not determinable from this source which hosts do.
        • +
        +

        TrustedCallerHandler

        +
        +

        MMCA.Common.UI.Web · MMCA.Common.UI.Web.Security · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Security/TrustedCallerHandler.cs:35 · Level 0 · class (sealed, DelegatingHandler)

        +
        +
          +
        • What it is: an outgoing HttpClient handler that stamps a shared-secret header onto a request + only when the request targets the configured gateway origin, so the gateway can trust that the call + came from this app rather than from an arbitrary caller.
        • +
        • Depends on: nothing first-party. Externals: System.Net.Http (DelegatingHandler, + HttpRequestMessage, HttpResponseMessage), BCL Uri.
        • +
        • Concept introduced, an edge-trust header scoped to origin match, not applied blindly. + [Rubric §11, Security] assesses whether a trust signal is bounded to the audience it is meant for. + The constructor takes a header name, a shared secret, and the gateway origin + (TrustedCallerHandler.cs:39), and SendAsync only stamps the header when the outgoing request's + scheme, host, and port match that origin (TrustedCallerHandler.cs:57-63); a request to any other + destination goes out unmodified, so the secret cannot leak to a third-party origin the same + HttpClient happens to call. ADR-088 + is the policy this enforces on the client side.
        • Walkthrough
            -
          • PageSize (line 28) is const int 20: fixed-size server-side pagination, not infinite scroll. Injected InboxService, NotificationState, Toast, L, and MudBlazor's ScrollManager (lines 30-34).
          • -
          • [Parameter] public int? Id (line 43) is the deep-link route parameter. The doc (lines 36-42) explains the type choice: UserNotificationIdentifierType is an int alias, and a route parameter's type must be written out for the :int constraint to bind, so the parameter is declared int? and converted where it is used.
          • -
          • Deep-link state is four separate fields, and each exists for a distinct reason (lines 62-71): _highlightedId (found on the loaded page), _pendingScrollId (a scroll the next render owes), _scrolledId (already scrolled, so a re-render or push-driven reload never scrolls twice), and _appliedId (the Id the state was last computed for, to detect a re-navigation).
          • -
          • OnParametersSet (lines 94-103) clears the _scrolledId latch when _appliedId != Id and then recomputes the target. The doc (lines 88-93) records the bug this prevents: navigating from /notifications/inbox/5 to /notifications/inbox/9 reuses the component instance, so without clearing the latch the second deep link would highlight but never move the viewport.
          • -
          • OnAfterRenderAsync (lines 106-118) is the only place a scroll happens. It returns unless a scroll is pending and the component is alive, clears _pendingScrollId and sets _scrolledId before the await (comment, line 113, so a re-entrant render cannot queue the same scroll twice), and calls ScrollManager.ScrollIntoViewAsync(CardSelector(id), ScrollBehavior.Smooth) (line 117).
          • -
          • ApplyDeepLinkTarget (lines 126-140) matches the route id against what the loaded page actually holds: Id is not { } id || id <= 0 || !_notifications.Exists(n => n.Id == id) clears both the highlight and the pending scroll (lines 128-133). The doc (lines 120-125) is the "no toast" decision, a deep link the user cannot act on is not an error they caused.
          • -
          • The card-chrome helpers: IsDeepLinkTarget (line 142); CardElementId (lines 144-147, formatting notification-{id} with CultureInfo.InvariantCulture because it becomes a DOM id) and CardSelector (line 149); CardElevation (lines 152-160, 4 when deep-linked, else 1 for unread and 0 for read); CardClass (lines 162-168); and CardStyle (lines 175-183), which builds the unread left border and the deep-link ring from MudBlazor palette variables (var(--mud-palette-primary), var(--mud-palette-secondary)) rather than literal hex, so both themes stay legible (comment, lines 170-174). [Rubric §20, Design System & Theming] shows up here: even inline styles source their colors from the theme's CSS custom properties.
          • -
          • Push coalescing. HandleRefreshRequested (lines 185-193) is EventHandler-shaped so it cannot be async Task; it discards into InvokeAsync(RefreshFromPushAsync) (line 192). RefreshFromPushAsync (lines 195-212) sets _refreshPending = true and returns when a load is already in flight (lines 202-208); the comment (lines 204-205) states the invariant, never drop the push, so overlapping pushes coalesce into exactly one trailing reload instead of vanishing.
          • -
          • LoadNotificationsAsync (lines 214-258): sets IsLoading, calls GetInboxAsync(_currentPage, PageSize, _cts.Token) (line 219), and on success materializes page.Items (line 222), computes _totalPages from page.PaginationMetadata.TotalItemCount with Math.Ceiling (line 223) clamped to a floor of 1 (lines 224-227) so an empty inbox never renders a zero-page pager, then recomputes the deep-link highlight (line 232, only a successful load can decide whether the id is present). The tail (lines 253-257) drains _refreshPending with one more RefreshFromPushAsync; the comment (lines 250-252) explains why the recursion is bounded, the flag is cleared first, so a push arriving during this reload queues one more and no further.
          • -
          • OnPageChangedAsync(int page) (lines 260-264): records the page and reloads.
          • -
          • MarkReadAsync(UserNotificationDTO) (lines 266-300): calls MarkReadAsync(notification.Id, _cts.Token) (line 271), returns early on failure after a toast (lines 272-276), then optimistically patches local state, locating the row with FindIndex (line 279) and replacing it via a record with-expression, notification with { IsRead = true, ReadOn = DateTime.UtcNow } (line 282). It then refetches the authoritative unread count (line 286) and pushes it into NotificationState.SetUnreadCount only when the count call succeeded (lines 287-290); a failed count means "unknown", so the badge keeps its value (comment, line 285).
          • -
          • MarkAllReadAsync (lines 302-334): one service call (line 307), a loop flipping every unread row in place (lines 315-321), then SetUnreadCount(0) (line 323) and a localized success toast (line 324).
          • -
          • Disposal: _disposed (line 336), Dispose(bool) (lines 338-350) unsubscribing the refresh event and cancelling the _cts, Dispose() (lines 352-356) with GC.SuppressFinalize.
          • +
          • The constructor (TrustedCallerHandler.cs:39-48) null/blank-guards headerName and secret and + null-guards gatewayOrigin, storing all three as readonly fields.
          • +
          • SendAsync (TrustedCallerHandler.cs:51-72) null-guards the request, then compares + request.RequestUri against _gatewayOrigin with Uri.Compare over + UriComponents.Scheme | UriComponents.HostAndPort, case-insensitive + (TrustedCallerHandler.cs:57-63). On a match it removes any existing value for the header and adds + exactly one via TryAddWithoutValidation (TrustedCallerHandler.cs:67-68); the comment + (TrustedCallerHandler.cs:65-66) explains the replace-not-append choice, the gateway compares one + header value in constant time, so a second value would silently fail the comparison. It then calls + base.SendAsync unconditionally.
        • -
        • Why it's built this way: the page is a thin view over INotificationInboxUIService, so all HTTP and JSON live in the service and the component stays testable against a stub. Patching local state after a mark-read (rather than refetching the page) keeps the interaction snappy while still reconciling the shared badge from the server, and the coalescing refresh keeps the list current when pushes arrive in bursts. The deep-link machinery is worth reading as a case study in idempotent side effects: a scroll is a one-shot action in a component model that re-renders freely, so it needs the "owed" and "already done" flags to be correct under re-render, re-navigation, and disposal.
        • -
        • Where it's used: rendered at /notifications/inbox (and /notifications/inbox/{Id:int}) for authenticated users; the route constant and nav entry come from NotificationRoutePaths and NotificationUIModule. NotificationBell reads the same NotificationState this page writes, and the layout-mounted NotificationListener raises the OnRefreshRequested signal it consumes. Its admin siblings are NotificationList and NotificationSend.
        • +
        • Why it's built this way: a DelegatingHandler composes into the existing HttpClient pipeline + without every call site having to remember to add the header itself, and gating on origin means the + same registered handler is safe to reuse on a client that also talks to other hosts.
        • +
        • Where it's used: registered in MMCA.Common.UI.Web's + DependencyInjection (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/DependencyInjection.cs); + its behavior is pinned by TrustedCallerHandlerTests + (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Web.Tests/Security/TrustedCallerHandlerTests.cs).
        -

        NotificationList

        +

        BlazorCspSettings

        -

        MMCA.Common.UI · MMCA.Common.UI.Pages.Notifications · MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Notifications/NotificationList.razor.cs:16 · Level 6 · class (partial, page)

        +

        MMCA.Common.UI.Web · MMCA.Common.UI.Web.Security · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Security/BlazorCspSettings.cs:18 · Level 0 · class (sealed)

          -
        • What it is: the code-behind for the admin/organizer push-notification history page, routed at @page "/notifications" (NotificationList.razor:1). It loads previously sent broadcasts and renders them in a status table, with a button onward to the compose page.
        • -
        • Depends on: first-party: IPushNotificationUIService (the send and history HTTP service), PushNotificationDTO (the row shape, carrying status and recipient count), NotificationRoutePaths (the route constants), IToastService, ResultUiExtensions (NotifyOnFailure), and SharedResource. Externals: MudBlazor (BreadcrumbItem, Icons), Microsoft.AspNetCore.Components (NavigationManager, [Inject]), Microsoft.Extensions.Localization.
        • -
        • Concept reinforced, the same code-behind shape as NotificationInbox. Same [Inject] service set (lines 18-21), same readonly CancellationTokenSource _cts plus dispose pattern (lines 23, 79-98), same IsLoading gate (line 29), same cancellation-swallowing load (lines 67-70), same result.NotifyOnFailure(Toast, L) failure surface (line 64). It differs only in what it loads and how much of it.
            -
          • [Rubric §25, Navigation, Routing & Information Architecture] assesses route structure and inter-page flow; navigation goes through NotificationRoutePaths constants (NavigateToSend targets NotificationRoutePaths.NotificationSend, line 77) rather than a literal URL, so a route change happens in exactly one file.
          • -
          • [Rubric §27, Internationalization & Localization] picks up an extra trick here: DisplayStatus(string status) (lines 34-38) looks up L[$"Notif.Status.{status}"] and falls back to the raw wire value when localized.ResourceNotFound (line 37). The comparison value stays the untranslated wire string while only the displayed chip text localizes, which keeps transport values and presentation separate and means a newly added server status renders (untranslated) instead of blanking (the comment on line 33 cites ADR-027). This is the same pass-through-on-unknown-key discipline that DataAnnotationsModelValidator applies to error messages.
          • +
          • What it is: the bindable options type for an opt-in frame-src allowance on the Blazor host's Content-Security-Policy, one string list of origins a page may embed in an <iframe> (BlazorCspSettings.cs:23-35).
          • +
          • Depends on: nothing first-party. It is read by BlazorCspPolicyProvider through BuildFrameSrc and validated at startup by BlazorCspSettingsValidator. Externals: Microsoft.Extensions.Options binding conventions (BCL IList<string>).
          • +
          • Concept introduced, an empty default that changes nothing. [Rubric §26, Front-End Security] assesses whether a security-relevant default stays strict until a deployment explicitly opts out. SectionName = "BlazorCsp" (line 28) names the configuration section; FrameSources (line 42) initializes to an empty list, so an unconfigured host emits no frame-src directive at all and the policy stays byte-identical to the pre-existing baseline (comment, lines 30-33). Only a deployment that needs to embed a specific third-party origin (the doc comment's example is a map provider's embed endpoint, lines 30-31) adds entries.
              +
            • The doc comment (lines 33-40) states the origin shape a configured entry must satisfy: an absolute https origin with no path, query, fragment, user info, wildcard, quote, semicolon, comma or whitespace, so the field carries the contract BlazorCspSettingsValidator enforces rather than leaving it implicit.
          • +
          • Walkthrough: two members: SectionName (line 28, a public const string) and FrameSources (line 42, public IList<string> initialized to []).
          • +
          • Why it's built this way: a settings class bound through IOptions<BlazorCspSettings> fits the same registration and validation pipeline every other options type in the host uses, so adding frame-src support did not need a new configuration mechanism, only a new section.
          • +
          • Where it's used: bound and validated in MMCA.Common.UI.Web's DependencyInjection (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/DependencyInjection.cs), consumed by BlazorCspPolicyProvider's constructor as IOptions<BlazorCspSettings>.
          • +
          +

          NotificationSendModel

          +
          +

          MMCA.Common.UI · MMCA.Common.UI.Pages.Notifications · MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Notifications/NotificationSendModel.cs:19 · Level 1 · class (sealed)

          +
          +
            +
          • What it is: the two-property form model for the push-notification compose page. Its DataAnnotations are the single declaration of that form's field rules.
          • +
          • Depends on: first-party: SendPushNotificationRequest (the endpoint contract, whose length constants it reuses, lines 23 and 28). Externals: System.ComponentModel.DataAnnotations (RequiredAttribute, MaxLengthAttribute). It is consumed by NotificationSend through ModelValidation and DataAnnotationsModelValidator.
          • +
          • Concept introduced, one number shared by the cap, the message, and the server invariant. The lengths are not declared here. MaxLength(SendPushNotificationRequest.TitleMaxLength) (line 23) and MaxLength(SendPushNotificationRequest.BodyMaxLength) (line 28) point at the shared request contract, which fixes them at 200 and 2000 (MMCA.Common/Source/Core/MMCA.Common.Shared/Notifications/PushNotifications/SendPushNotificationRequest.cs:15,21). The same constants drive the input cap and the character counter in the markup (NotificationSend.razor:49-50,61-62), and the server-side validator enforces the same numbers.
              +
            • [Rubric §24, Forms, Validation & UX Safety] assesses whether client and server agree. Here they cannot disagree, because there is one literal and everything else is a reference to it.
            • +
            • [Rubric §9, API & Contract Design] assesses whether contract facts live with the contract; putting the length constants on the request record (the type the endpoint binds) rather than on the form model is what makes the sharing possible in the first place.
            • +
            • [Rubric §27, Internationalization & Localization]: each ErrorMessage is a resource key ("Notif.Send.Field.Title.Required", line 22, and its three siblings), resolved by the page's localizing DataAnnotationsModelValidator per ADR-027. This is the concrete case the pass-through localization in that validator was designed for.
            • +
            +
          • +
          • Walkthrough: two mutable string properties, both initialized to string.Empty so a fresh model binds cleanly. Title (line 24) carries [Required] with key Notif.Send.Field.Title.Required (line 22) and [MaxLength(200)] with key Notif.Send.Field.Title.MaxLength (line 23). Body (line 29) carries the matching pair, Notif.Send.Field.Message.Required (line 27) and Notif.Send.Field.Message.MaxLength with the 2000-character cap (line 28).
          • +
          • Why it's built this way: a settable class rather than a record with init accessors, because MudBlazor two-way binding (@bind-Value="_model.Title") writes back into the instance. It is a separate type from SendPushNotificationRequest because the form model is mutable and carries presentation rules, while the request record is the immutable wire contract; the page maps one to the other in a single line (NotificationSend.razor.cs:111).
          • +
          • Where it's used: held as private readonly NotificationSendModel _model = new() by NotificationSend (NotificationSend.razor.cs:36) and bound by both fields in its markup.
          • +
          +

          BiometricGate

          +
          +

          MMCA.Common.UI · MMCA.Common.UI.Components.Capabilities · MMCA.Common/Source/Presentation/MMCA.Common.UI/Components/Capabilities/BiometricGate.razor.cs:18 · Level 1 · class (partial, component code-behind)

          +
          +
            +
          • What it is: a hybrid-head app-lock gate. It locks the UI behind a biometric prompt whenever the + app returns from the background having sat there longer than ReLockAfter, so a device handed to + someone else, or simply left unattended, does not reopen straight into a signed-in session.
          • +
          • Depends on: injected services resolved by the component (AppLifecycle's Resumed event, + Preferences, TokenStorage, Biometrics, Navigation, Logger, the page's localizer L); + MudBlazor's MudButton for the focus target (_unlockButton, line 30).
          • +
          • Concept introduced, an async-void lifecycle handler that cannot afford to throw. + [Rubric §11, Security] assesses whether a re-auth control actually re-arms on every qualifying + event, not just the first one. The class subscribes to AppLifecycle.Resumed on first render + regardless of the current preference, because app lock can be switched on later in the same session + (comment, lines 53-54), and every resume re-reads the preference. [Rubric §18, UI Architecture & Component Design] covers the handler shape itself: OnResumed (line 94) is a plain + EventHandler<AppResumedEventArgs>, so it cannot be async; it fires ReLockAsync with an explicit + discard, and the security comment (lines 88-93) states why that discard is safe here, ReLockAsync + observes its own failures with a catch-all, and an unobserved exception in a true async-void handler + would crash a native head's process (VSTHRD100).
          • +
          • Concept introduced, a precondition read that fails open before opt-in and closed after it. + [Rubric §11, Security] also assesses which way a control fails when its own inputs are unreadable. + The class summary states the rule (lines 15-16) and ShouldLockAsync documents both halves + (lines 133-138): if the AppLockEnabled preference itself cannot be read, nothing says the owner + opted in, so the gate stays open rather than forcing a biometric prompt on a device that may have + none enrolled; if the preference reads as on but the stored session cannot be read, the session + state is unknown and the gate locks. Either way the method never throws, which matters because it + runs from both the first render and the resume path.
          • Walkthrough
              -
            • Injected NotificationService, NavigationManager, Toast, L (lines 18-21); Title reads L["Notif.List.Title"].Value (line 25); _breadcrumbs (line 27) is built Home to Push Notifications in OnInitializedAsync (lines 43-47), with the leaf crumb disabled: true to mark the current page (line 46).
            • -
            • _notifications is an IReadOnlyCollection<PushNotificationDTO> initialized empty (line 31).
            • -
            • OnInitializedAsync (lines 40-50) builds the breadcrumbs then awaits LoadNotificationsAsync.
            • -
            • LoadNotificationsAsync (lines 52-75): calls GetHistoryAsync(pageNumber: 1, pageSize: 50, _cts.Token) (line 57) and copies history.Items into _notifications on success (line 60), otherwise raises the localized failure toast (line 64). This page fetches one fixed 50-row page and lets MudBlazor page that buffer client-side; unlike the inbox there is no server round-trip per page.
            • -
            • NavigateToSend (line 77) sends the "send new" button to the compose page.
            • -
            • Disposal mirrors the family: _disposed (line 79), Dispose(bool) cancelling the _cts (lines 81-92), Dispose() (lines 94-98).
            • +
            • ReLockAfter (lines 25-26) is a [Parameter] TimeSpan defaulting to 30 seconds; the comment + (lines 20-24) explains the default is deliberately short, a device handed off is usually away from + its owner longer than a glance at a notification.
            • +
            • OnAfterRenderAsync(firstRender) (lines 40-66): first focuses the Unlock button if + _focusUnlockPending and the button reference now exists (lines 42-46), because that flag can + only be acted on once the locked branch has actually rendered (field comment, lines 32-36). On + firstRender it subscribes to AppLifecycle.Resumed (lines 55-56), then calls ShouldLockAsync + and, if it returns true, sets _locked and awaits UnlockAsync (lines 58-65).
            • +
            • ReLockAsync (lines 96-131) returns early if already locked or if the background duration was + under ReLockAfter (line 100), otherwise dispatches the lock-and-unlock sequence through + InvokeAsync (lines 105-115). It catches ObjectDisposedException and InvalidOperationException + as expected teardown races (lines 117-124), and a general Exception last, logged rather than + rethrown, with the comment (lines 127-128) recording that this is the exhaustive catch an async-void + handler needs.
            • +
            • ShouldLockAsync (lines 139-167) wraps the preference read in its own try (lines 142-150): a + failure is logged as a warning and returns false. A preference that is off also returns false + (lines 152-155). Otherwise a second try (lines 157-166) returns whether a refresh token is still + stored, nothing to protect without a session, and a failure there is logged and returns true + (line 165).
            • +
            • UnlockAsync (lines 169-183) calls Biometrics.AuthenticateAsync with a localized prompt reason; + success clears _locked, failure queues _focusUnlockPending for the next render rather than + focusing immediately, because the button does not exist yet on the very first lock.
            • +
            • FocusUnlockAsync (lines 189-203) tolerates JSDisconnectedException (circuit gone) and + InvalidOperationException (prerendering, or the element already removed).
            • +
            • SignOutAsync (lines 205-210) clears tokens, clears _locked, and navigates to /login.
            • +
            • Dispose(bool) (lines 70-79) unsubscribes from AppLifecycle.Resumed if subscribed, guarded so + it is safe to call more than once; the public Dispose() (lines 82-86) forwards to it and + suppresses finalization.
          • -
          • Why it's built this way: broadcast history is low-volume admin data, so one 50-row fetch with client-side paging is simpler and adequate, and it avoids server-side paging plumbing that would earn nothing. Keeping HTTP behind IPushNotificationUIService mirrors the inbox and keeps the component a thin view. The shared [Rubric §18, UI Architecture & Component Design] story is told under NotificationInbox.
          • -
          • Where it's used: rendered at /notifications for organizer and admin roles (the nav entry from NotificationUIModule is gated on RoleNames.Organizer); it links onward to NotificationSend.
          • -
          • Caveats / not-in-source: the 50-row ceiling is a client-side choice in this file; what the server does when more than 50 broadcasts exist (whether the page silently truncates the history) is not visible here.
          • +
          • Why it's built this way: the re-lock decision is driven entirely by wall-clock background + duration rather than any UI state, so it behaves the same whether the app was backgrounded for a + phone call or for the rest of the day. Catching broadly inside ReLockAsync but narrowly inside + FocusUnlockAsync reflects the difference between an operation whose caller cannot observe a failure + at all (the lifecycle event) and one whose failure just means "skip the focus this time". The two + catches inside ShouldLockAsync resolve in opposite directions on purpose: before opt-in the safe + default is "no lock", after opt-in it is "lock".
          • +
          • Where it's used: MMCA.ADC's DeviceUIModule + (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/DeviceUIModule.cs) and App.xaml.cs + (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/App.xaml.cs); pinned by BiometricGateTests + (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Components/Capabilities/BiometricGateTests.cs).
          -

          NotificationSend

          +

          BlazorCspSettingsValidator

          -

          MMCA.Common.UI · MMCA.Common.UI.Pages.Notifications · MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Notifications/NotificationSend.razor.cs:20 · Level 6 · class (partial, page)

          +

          MMCA.Common.UI.Web · MMCA.Common.UI.Web.Security · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Security/BlazorCspSettingsValidator.cs:13 · Level 1 · class (internal, sealed)

            -
          • What it is: the code-behind for the compose-and-broadcast form, routed at @page "/notifications/send" (NotificationSend.razor:1). It collects a title and a message into NotificationSendModel, validates them against that model's own annotations, sends one broadcast through the Notification API, reports the recipient count, and returns to the history page.
          • -
          • Depends on: first-party: IPushNotificationUIService (the SendAsync call), NotificationSendModel (the form model), ModelValidation + DataAnnotationsModelValidator (the validation bridge), INotificationScopeProvider (the targeting caption), SendPushNotificationRequest (the wire contract), PushNotificationDTO (the result carrying RecipientCount), Result, NotificationRoutePaths, ErrorMessages, IToastService, ResultUiExtensions, and SharedResource. The markup also composes an UnsavedChangesGuard bound to IsDirty (NotificationSend.razor:12). Externals: MudBlazor (MudForm, BreadcrumbItem, Icons), Microsoft.AspNetCore.Components (NavigationManager, OnInitialized, OnInitializedAsync), Microsoft.Extensions.Localization.
          • -
          • Concept introduced, MudForm validation driven entirely by the model. This is the family's form page, and it is the worked example of the validation stack in this unit. The markup declares <MudForm @ref="_form" Model="_model"> with two fields that set For, Validation="@_validate", and read their affordances off the same model (NotificationSend.razor:43-66). No rule is declared in markup: the comment above the form (lines 38-42 of the markup) spells out the division, Required drives the asterisk and aria-required while its message still comes from the model, MaxLength caps the input, and Counter shows the budget from the shared request contract so the numbers cannot drift from the server. The C# holds the form by reference (MudForm? _form, line 36) and explicitly drives validation before sending: await _form.ValidateAsync() then a guard on _form.IsValid (lines 98-105). MudForm has no OnValidSubmit, so that explicit pass is the gate (comment, lines 96-97).
              -
            • [Rubric §24, Forms, Validation & UX Safety] assesses input validation, double-submit protection, and feedback. All four are present: model-declared rules run through the shared delegate, an IsSaving flag (line 33) bound to Disabled on both buttons (NotificationSend.razor:73,80) so the send cannot be fired twice, a warning toast ErrorMessages.ValidationError on a failed gate (line 103), and a success toast naming the recipient count (line 116).
            • -
            • Two failure surfaces, deliberately not one. _sendResult (line 42) holds the last attempt's outcome and is rendered inline by the shared ErrorSummary component (NotificationSend.razor:36), while the toast stays the transient cue. The markup comment (lines 31-35) records the design constraint: the summary is deliberately not fed MudForm.Errors, because every field already renders its own message inline and MudBlazor contributes a generic "Required" of its own that would stack on top of the model's wording. _sendResult is nulled at the start of every attempt (line 94), so the summary never shows a stale failure.
            • -
            • [Rubric §21, Accessibility] and [Rubric §18, UI Architecture & Component Design] meet in the ErrorSummary: a failure that only appeared as a toast would time out on a long form and be unrecoverable for a screen-reader user who missed it (comment, lines 121-122).
            • -
            • [Rubric §27, Internationalization & Localization]: every string resolves through IStringLocalizer<SharedResource> L (line 24), including the success message L.Plural("Notif.Send.SentTo", sent.RecipientCount, sent.RecipientCount) (line 117), which passes the count through the shared plural extension so the resource file (not C#) picks the plural form as well as the word order. As with its siblings the breadcrumb trail is built in an initialization hook, here the synchronous OnInitialized (lines 55-67, comment citing ADR-027).
            • -
            • [Rubric §24, Forms, Validation & UX Safety] again, for the navigate-away guard: Sent (a private bool, set true immediately before the post-send NavigateTo) and IsDirty (true whenever Sent is false and either field is non-blank) feed the markup's UnsavedChangesGuard, so leaving with typed-but-unsent text prompts, while the send's own redirect never does. The comment on the Sent setter records the ordering: it is set before the navigation call, because the guard reads the live accessor.
            • +
            • What it is: the startup IValidateOptions<BlazorCspSettings> that rejects a frame-src origin the moment it is misshapen, so a bad entry fails fast rather than silently reaching the CSP header.
            • +
            • Depends on: first-party: BlazorCspSettings (the options type it validates). Externals: Microsoft.Extensions.Options (IValidateOptions<T>, ValidateOptionsResult), BCL SearchValues<char>, Uri.
            • +
            • Concept introduced, an explicit character denylist plus a shape check, not a regex. The fail-fast configuration contract of ADR-070 (Website/docs-src/adr/070-fail-fast-configuration-contract.md) assesses whether a misconfigured setting is caught at startup instead of at request time. Validate (lines 21-31) runs every configured FrameSources entry through IsValidOrigin and fails the whole registration with one message per bad entry (lines 26-30) rather than starting the host on a policy it cannot honor.
                +
              • ForbiddenCharacters (line 18, a SearchValues<char> over *'";,@?#) is the set that could either break out of a CSP source expression or widen it, plus the URL delimiters (user info, query, fragment) a plain origin never carries. IsValidOrigin (lines 21-25 of the walkthrough below) rejects on that set before it ever calls Uri.TryCreate, so a value crafted to look like a URL but carrying a CSP metacharacter never reaches the parser.
              • +
              • [Rubric §26, Front-End Security] assesses browser-side hardening; this validator is what keeps BlazorCspSettings's contract (an absolute https origin, no path, wildcard, or delimiter) a checked invariant instead of a comment.
            • Walkthrough
                -
              • Injected NotificationService, NavigationManager, Toast, L, and ScopeProvider (lines 21-25); _cts (line 27); Title (line 29); _breadcrumbs (line 31) built Home to Push Notifications to Send (lines 58-63), where the middle crumb is a real link via NotificationRoutePaths.Notifications (line 61) and the leaf is disabled: true (line 62).
              • -
              • _model (line 35) is a readonly NotificationSendModel created with the component; _validate (line 46) is the single Func<object, string, IEnumerable<string>> MudBlazor calls with (model, member path), wired in OnInitialized as ModelValidation.For(_model, new DataAnnotationsModelValidator(L)) (line 66). One delegate serves both fields, and no rule is written twice (comment, lines 44-45).
              • -
              • OnInitializedAsync (lines 69-87) resolves the optional scope caption: ScopeProvider.GetCurrentScopeDisplayNameAsync(_cts.Token) (line 77), and only when the name is non-blank does it build _scopeCaption (lines 78-81). The field doc (lines 48-53) and the async doc (lines 71-74) give the reasoning: a scoped application applies its scope to the send automatically, so without a caption the operator would be composing a broadcast with no visible statement of who receives it, and when there is no scope the page renders no caption at all rather than an empty line. [Rubric §24, Forms, Validation & UX Safety] again: making an implicit targeting decision visible is part of a safe destructive-ish action.
              • -
              • SendNotificationAsync (lines 89-134): null-guards _form (lines 91-92), clears _sendResult (line 94), validates and warns on failure (lines 98-105); then under IsSaving builds new SendPushNotificationRequest(_model.Title, _model.Body) (line 110) and awaits SendAsync(request, _cts.Token) (line 111), storing the result for the summary (line 112). On a non-null PushNotificationDTO it raises the success toast with sent.RecipientCount (line 117), sets Sent = true (line 121), and navigates back to the list; otherwise result.NotifyOnFailure(Toast, L) (line 123). The cancellation catch (lines 126-129) additionally names the InteractiveAuto render-mode transition, the case where the WebAssembly runtime takes over mid-call; IsSaving is cleared in finally (lines 130-133).
              • -
              • NavigateToList (line 136) is the Cancel button's handler, back to NotificationRoutePaths.Notifications.
              • -
              • Sent (a private bool property) and IsDirty (!Sent && (Title or Body non-whitespace)) are declared just after NavigateToList, immediately above the disposal members; Sent is documented as being set the moment a send has succeeded and the page is about to navigate away on its own, so the unsaved-changes guard does not prompt over work that has just left the building.
              • -
              • Disposal mirrors the family: _disposed (line 138), Dispose(bool) (lines 140-151), Dispose() (lines 153-157).
              • +
              • Validate(string? name, BlazorCspSettings options) (lines 21-31) null-guards options, projects every entry that fails IsValidOrigin into a message naming the section, the entry, and the shape it must satisfy (lines 26-30), and returns ValidateOptionsResult.Success only when that list is empty.
              • +
              • IsValidOrigin(string? source) (lines 34-49), internal static: rejects null, whitespace-only, any entry containing whitespace, anything matching ForbiddenCharacters, or anything Uri.TryCreate cannot parse as absolute (lines 36-41). What remains must be https (ordinal case-insensitive), have a non-empty host, and have PathAndQuery equal to exactly "/" (lines 46-48); the comment (lines 44-45) notes the forbidden set already rules out user info, a query, and a fragment, so a root-only path is the last thing separating an origin from a full URL.
              • +
              • ToOrigin(string source) (lines 55-56), internal static: new Uri(source, UriKind.Absolute).GetLeftPart(UriPartial.Authority), the canonical scheme://host[:port] form used to splice a validated entry into the CSP header, lower-cased host, default port dropped, no trailing slash.
            • -
            • Why it's built this way: a deliberately small form that still demonstrates the full pattern. The rules live on NotificationSendModel so the client cap, the client message, and the server invariant all read the same constants; HTTP stays behind IPushNotificationUIService so the component is unit-testable; and the Sent/IsDirty pair keeps the unsaved-changes guard from firing on the page's own post-send redirect, which is the one navigation on this page that is not the user abandoning work. The send is fire-and-confirm: the server fans out to recipients through the push pipeline (see Group 10) and returns only the aggregate count.
            • -
            • Where it's used: rendered at /notifications/send for organizer and admin roles, reached from the button on NotificationList. The server-side validator for SendPushNotificationRequest enforces the same rules a second time, so client validation is a UX affordance rather than the security boundary.
            • +
            • Why it's built this way: an explicit character denylist checked before parsing is stricter than trusting Uri alone, because Uri will happily parse strings a CSP source expression cannot safely contain; failing every bad entry at once (rather than the first) gives a deployment the whole list of what to fix in one pass.
            • +
            • Where it's used: registered alongside BlazorCspSettings in MMCA.Common.UI.Web's DependencyInjection (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/DependencyInjection.cs); ToOrigin is reused by BlazorCspPolicyProvider's BuildFrameSrc to canonicalize an already-validated entry.

            AuthenticatedServiceBase

            @@ -5587,7 +6123,7 @@

            AuthenticatedServiceBase

          • Concept introduced, why a base class and not just the handler pipeline. [Rubric §29, Resilience & Business Continuity] assesses whether transient failures are absorbed rather than surfaced; the retry policy is that. The sharper teaching is in the - CreateAuthenticatedClientAsync doc comment (AuthenticatedServiceBase.cs:45-50): + CreateAuthenticatedClientAsync doc comment (AuthenticatedServiceBase.cs:51-56): IHttpClientFactory creates its handlers in a separate DI scope from the Blazor circuit, so a DelegatingHandler cannot reach the circuit's IJSRuntime to read the in-memory access token. The base class works around that by reading the token from the circuit-scoped storage service itself and @@ -5600,39 +6136,44 @@

            AuthenticatedServiceBase

            rather than one per service instance per circuit. ApiClientName pins the named client to "APIClient" (AuthenticatedServiceBase.cs:27), and both constructor arguments are null-checked into private fields (AuthenticatedServiceBase.cs:29-30).
          • -
          • NewIdempotencyKey() (AuthenticatedServiceBase.cs:43) returns a compact - Guid.NewGuid().ToString("N"), and its remarks (AuthenticatedServiceBase.cs:35-41) carry the +
          • SharedRetryPolicy (AuthenticatedServiceBase.cs:36) is an internal static getter that returns + that same RetryPolicy instance. Its doc comment (AuthenticatedServiceBase.cs:32-35) names the + only reader, IdempotentReadRetry, which reuses the exact policy object + without inheriting from this class, so the anonymous read path and the authenticated one cannot + drift apart.
          • +
          • NewIdempotencyKey() (AuthenticatedServiceBase.cs:49) returns a compact + Guid.NewGuid().ToString("N"), and its remarks (AuthenticatedServiceBase.cs:41-47) carry the load-bearing rule: the value is generated once per logical operation and reused across every retry attempt, because the server-side idempotency filter keys its cached response off it. Generating a new key per attempt would defeat the dedup entirely and let a retry create a duplicate record.
          • -
          • CreateAuthenticatedClientAsync() (AuthenticatedServiceBase.cs:51) resolves the "APIClient" - (AuthenticatedServiceBase.cs:53), reads the token (AuthenticatedServiceBase.cs:57), sets - Authorization: Bearer when non-blank (AuthenticatedServiceBase.cs:58-62), and catches +
          • CreateAuthenticatedClientAsync() (AuthenticatedServiceBase.cs:57) resolves the "APIClient" + (AuthenticatedServiceBase.cs:59), reads the token (AuthenticatedServiceBase.cs:63), sets + Authorization: Bearer when non-blank (AuthenticatedServiceBase.cs:64-68), and catches InvalidOperationException to proceed without a token during SSR prerender, when JS interop is - unavailable (AuthenticatedServiceBase.cs:64-67).
          • -
          • CreateClientWithToken(string accessToken) (AuthenticatedServiceBase.cs:80) is the replay path. - Its doc comment (AuthenticatedServiceBase.cs:72-78) explains why it exists: after the API answers + unavailable (AuthenticatedServiceBase.cs:70-73).
          • +
          • CreateClientWithToken(string accessToken) (AuthenticatedServiceBase.cs:86) is the replay path. + Its doc comment (AuthenticatedServiceBase.cs:78-84) explains why it exists: after the API answers 401, the stored token still looks fresh by the client clock, so re-reading storage would just resend the token the server has already rejected. The caller passes the token it acquired straight from ITokenRefresher; the method rejects a blank one - (AuthenticatedServiceBase.cs:82) and stamps the header unconditionally - (AuthenticatedServiceBase.cs:85).
          • -
          • IsRetryableResponse (AuthenticatedServiceBase.cs:100) is the retry predicate. It first excludes - 501 Not Implemented and 505 HTTP Version Not Supported (AuthenticatedServiceBase.cs:102-105) - because, as the remarks say (AuthenticatedServiceBase.cs:94-99), those are permanent verdicts and + (AuthenticatedServiceBase.cs:88) and stamps the header unconditionally + (AuthenticatedServiceBase.cs:91).
          • +
          • IsRetryableResponse (AuthenticatedServiceBase.cs:106) is the retry predicate. It first excludes + 501 Not Implemented and 505 HTTP Version Not Supported (AuthenticatedServiceBase.cs:108-111) + because, as the remarks say (AuthenticatedServiceBase.cs:100-105), those are permanent verdicts and retrying only burns the budget and delays the error the caller needs to see. It then accepts anything >= 500 plus 408 Request Timeout and 429 Too Many Requests - (AuthenticatedServiceBase.cs:107-108), the two codes where the server is explicitly inviting a + (AuthenticatedServiceBase.cs:113-114), the two codes where the server is explicitly inviting a later attempt.
          • -
          • DefaultBackoff (AuthenticatedServiceBase.cs:114-116) is the shipped schedule: 2^attempt +
          • DefaultBackoff (AuthenticatedServiceBase.cs:120-122) is the shipped schedule: 2^attempt seconds (2s, 4s, 8s) plus up to one second of random jitter, so a fleet of clients does not re-converge on the same instant. The S2245/CA5394 suppression around it - (AuthenticatedServiceBase.cs:111) documents that the randomness only spaces retries and feeds no + (AuthenticatedServiceBase.cs:117) documents that the randomness only spaces retries and feeds no security decision.
          • -
          • BuildRetryPolicy(Func<int, TimeSpan> backoff) (AuthenticatedServiceBase.cs:131-134) is +
          • BuildRetryPolicy(Func<int, TimeSpan> backoff) (AuthenticatedServiceBase.cs:137-140) is internal and backoff-injectable so a test can exercise the disposal contract without waiting out the real delays. Its onRetry disposes the retried attempt's response - (AuthenticatedServiceBase.cs:134); the remarks (AuthenticatedServiceBase.cs:123-130) explain + (AuthenticatedServiceBase.cs:140); the remarks (AuthenticatedServiceBase.cs:129-136) explain that Polly hands the caller only the final outcome, so without this every intermediate 5xx, 408 or 429 response leaks its content buffer and keeps its connection out of the handler pool until finalization, exactly under the sustained backend failure the retries exist to survive. A retried @@ -5653,110 +6194,87 @@

            AuthenticatedServiceBase

            (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Notifications/NotificationInboxService.cs:34), and through them by every module-level UI service in the consumer apps. The members show up as NewIdempotencyKey() on writes (EntityServiceBase.cs:162), RetryPolicy.ExecuteAsync around each - call (EntityServiceBase.cs:338 and EntityServiceBase.cs:366) and CreateClientWithToken on the - 401 replay (NotificationInboxService.cs:148).
          • + call (EntityServiceBase.cs:342 and EntityServiceBase.cs:370) and CreateClientWithToken on the + 401 replay (NotificationInboxService.cs:148). The policy itself is also reached without + inheritance by IdempotentReadRetry + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/IdempotentReadRetry.cs:24), which + gives anonymous lookup services the same retries on GETs.

          -

          ClientConfigBuilder

          +

          BlazorCspPolicyProvider

          -

          MMCA.Common.UI.Web · MMCA.Common.UI.Web.ClientConfig · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/ClientConfig/ClientConfigBuilder.cs:17 · Level 2 · class (sealed)

          +

          MMCA.Common.UI.Web · MMCA.Common.UI.Web.Security · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Security/BlazorCspPolicyProvider.cs:28 · Level 2 · class (internal, sealed)

            -
          • What it is: the accumulator a host's own extras delegate writes into when it wants extra data - served on the anonymous /client-config document, one named section per Add call.
          • -
          • Depends on: ClientConfigEndpointExtensions (its ApiSectionName - constant, checked as the one reserved name, ClientConfigBuilder.cs:57); Microsoft.AspNetCore.Http - (HttpContext, BCL) and Microsoft.Extensions.DependencyInjection / - Microsoft.Extensions.Configuration for the two convenience accessors it republishes.
          • -
          • Concept, a builder with an internal constructor. [Rubric §26, Front-End Security] covers what - this type prevents: the constructor is internal (line 29), so the only place a ClientConfigBuilder - can come into existence is inside MapClientConfigEndpoint's own request delegate, which is also the - one place a host's extras callback runs. A host cannot construct one ahead of time and stash - arbitrary state in it outside the request it belongs to.
          • -
          • Walkthrough
              -
            • _sections (line 27) is a Dictionary<string, object?> keyed with StringComparer.OrdinalIgnoreCase, - so "OAuth" and "oauth" collide as the same section.
            • -
            • HttpContext (line 32) and the two accessors built on it, Services (line 35, reads - HttpContext.RequestServices) and Configuration (line 38, resolves IConfiguration from those - services), let an extras delegate reach DI and configuration without the endpoint having to pass - them separately.
            • -
            • Add(string name, object? value) (line 53) blank-checks name - (ArgumentException.ThrowIfNullOrWhiteSpace, line 55), rejects the reserved - ClientConfigEndpointExtensions.ApiSectionName case-insensitively (lines 57-62), then adds through - _sections.TryAdd (line 64) so a second call with the same name throws rather than silently - overwriting the first (lines 65-67). It returns this for chaining (line 69).
            • -
            • Sections (line 40) is internal, an IReadOnlyDictionary<string, object?> view the endpoint reads - back after the delegate returns; nothing outside this package can enumerate a builder's contents.
            • +
            • What it is: the Content-Security-Policy provider for a Blazor Web host. It computes one CSP string at construction, pinning connect-src to 'self' plus the configured API or Gateway origin (https and its matching WebSocket origin), optionally adding an opt-in frame-src directive, and hands the result to the shared security-headers middleware on every request.
            • +
            • Depends on: first-party: ICspPolicyProvider (the contract it implements, line 24), CspPolicy (the value it returns, a policy string plus an Enforce flag), SecurityHeadersMiddleware (its only consumer, named in the class doc at line 12), ApiSettings (the endpoint source, injected as IOptions<ApiSettings>, line 35), and BlazorCspSettings (the frame-src source, injected as IOptions<BlazorCspSettings>, line 36) whose entries it canonicalizes with BlazorCspSettingsValidator.ToOrigin (line 94). Externals: Microsoft.Extensions.Options (IOptions<T>), Microsoft.AspNetCore.Hosting (IWebHostEnvironment), Microsoft.AspNetCore.Http (HttpContext), BCL Uri.
            • +
            • Concept introduced, a computed CSP that fails closed. [Rubric §26, Front-End Security] assesses whether the browser is told which origins may load scripts and open connections. A static CSP cannot express "this deployment's API origin", because that origin is configuration, so the policy is built rather than hard-coded. The two directives that matter for exfiltration are locked: script-src 'self' 'wasm-unsafe-eval' (line 99, where the WASM allowance is what lets the Blazor WebAssembly runtime instantiate) and the computed connect-src (line 79). The load-bearing decision is what happens when the origin cannot be determined. The provider narrows connect-src to 'self', keeps the rest of the policy unchanged, and stays enforced (Enforce: true, line 62). A misconfigured endpoint therefore surfaces immediately as blocked API calls in the browser console rather than as a permissive header that protects nothing and that nobody notices, and the class doc states the reasoning outright: a security response header that quietly stops being enforced is the worse failure mode (lines 16-20).
                +
              • [Rubric §11, Security] assesses the wider defense posture; this class is one control in a chain that also includes the session-cookie auth design and the security-headers middleware, and it is deliberately internal (line 24) so the only supported way to get it is the registration call, not a hand-wired new.
              • +
              • frame-src is opt-in and null-safe. BlazorCspSettings's default is an empty FrameSources list, and BuildFrameSrc (line 85) returns null when it is empty (lines 87-90), which is what keeps the policy byte-identical to the pre-existing baseline for every deployment that has not opted in (comment, lines 83-84).
            • -
            • Why it's built this way: the reserved-name and duplicate-name guards live on the builder rather - than being left to the endpoint's own loop, so every caller of Add, present or future, gets the same - two checks for free. The internal constructor and internal Sections getter keep the type's read side - as narrow as its write side: a host can only add, never inspect what a different extras call already - added.
            • -
            • Where it's used: constructed once per request inside - ClientConfigEndpointExtensions.MapClientConfigEndpoint's handler - and handed to the caller's extras delegate - (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs:61-62); - its Sections are then read back to assemble the response document - (ClientConfigEndpointExtensions.cs:71-74). Exercised directly by - ClientConfigEndpointTests - (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Web.Tests/ClientConfig/ClientConfigEndpointTests.cs).
            • -
            -

            ClientConfigEndpointExtensions

            -
            -

            MMCA.Common.UI.Web · MMCA.Common.UI.Web.ClientConfig · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs:15 · Level 2 · class (static)

            -
            -
              -
            • What it is: the extension that maps GET /client-config, the anonymous document a WASM client's - bootstrap fetches to learn the browser-reachable API base address before it can hold a session, plus - whatever extra sections the host contributes.
            • -
            • Depends on: ClientConfigBuilder (constructed per request, line 138) and - ApiSettings (IOptions<ApiSettings>, read for WasmApiEndpoint, line 130); externals - Microsoft.AspNetCore.Routing (IEndpointRouteBuilder, RouteHandlerBuilder), System.Text.Json - (JsonNamingPolicy.CamelCase), and the ASP.NET Core minimal-API Results type.
            • -
            • Concept introduced, fail-closed instead of falling back. [Rubric §22, Configuration and Secrets Management] assesses whether a bad or missing configuration value is caught rather than silently - degrading a live host. The endpoint's remarks (lines 108-115) state the rule directly: it serves - Api:WasmApiEndpoint, the browser-reachable gateway URL, and never falls back to Api:ApiEndpoint, - the server head's own service-discovery name, because a browser cannot resolve that name and serving - it would hand the client a silently broken base address. A host missing the key answers every request - with a server error naming the missing key (lines 131-136), which surfaces as a failed client start - instead of a client quietly pointed at the wrong API.
            • -
            • Concept introduced, anonymous by declared necessity. [Rubric §11, Security] covers why this - endpoint is one of the few that must allow anonymous access on purpose: the WASM client fetches this - document before it can hold a session (remarks, lines 116-122), and a host's fallback authorization - policy would otherwise gate it like any other endpoint that states nothing. The endpoint therefore - declares AllowAnonymous() itself (line 155) rather than relying on an ambient default, and the - remarks warn that only values the public site renders anyway belong in extras (lines 123-125).
            • Walkthrough
                -
              • Two public constants: ClientConfigPath = "/client-config" (line 95) and - ApiSectionName = "Api" (line 98), the one section name ClientConfigBuilder - treats as reserved.
              • -
              • The extension(IEndpointRouteBuilder endpoints) block (line 100, see - primer) holds the single member - MapClientConfigEndpoint(Action<ClientConfigBuilder>? extras = null) (line 127).
              • -
              • The handler (lines 128-154) reads apiSettings.Value.WasmApiEndpoint (line 130), throws - InvalidOperationException naming the missing key when it is blank (lines 131-136), builds a - ClientConfigBuilder for the request and invokes extras on it (lines - 138-139), seeds the response with the camelCased Api section carrying ApiEndpoint (lines 143-146), - then overlays every builder section, each name run through the same JsonNamingPolicy.CamelCase - policy the web JSON defaults apply to members (lines 148-151), so the document's shape matches what - an anonymous-object payload would have produced anyway.
              • -
              • The route is finished with .AllowAnonymous().ExcludeFromDescription() (lines 155-156): anonymous - for the reason above, and excluded from the OpenAPI description because it is not a data endpoint a - client integrates against, it is bootstrap plumbing.
              • +
              • _policy (line 32) is a single CspPolicy field computed once. The constructor (lines 33-43) null-guards apiOptions, cspOptions, and environment and calls BuildCsp(apiOptions.Value, BuildFrameSrc(cspOptions.Value), environment.IsDevelopment()) (line 42). Because the type is registered as a singleton, this runs exactly once per process.
              • +
              • GetPolicy(HttpContext context) (line 45) ignores the context and returns the cached policy, so the per-request cost is a field read.
              • +
              • BuildCsp (lines 49-80) now takes the pre-computed frameSrc as a parameter and resolves the endpoint as api.WasmApiEndpoint ?? api.ApiEndpoint (line 51). The guard on lines 55-59 rejects a blank value, a non-absolute URI, and any scheme that is not http or https; the comment on lines 53-54 records why the scheme check is not redundant: on Linux a rooted path such as /relative/path parses as an absolute file:// URI and would otherwise sail through Uri.TryCreate. A rejected endpoint returns the enforced connect-src 'self' policy, still carrying frameSrc (line 62).
              • +
              • With a valid endpoint it derives origin via apiUri.GetLeftPart(UriPartial.Authority) (line 66, scheme://host:port), picks wss or ws to match (line 67), and composes connect-src 'self' {origin} {wsScheme}://{authority} (line 68). The WebSocket origin is there for the SignalR notification hub, so the live push channel is allowed without opening connect-src to the world.
              • +
              • Development only (lines 74-77) appends http://localhost:* and ws://localhost:* for Visual Studio Browser Link and Hot Reload, whose ports change per run (comment, lines 70-73); the production policy is untouched.
              • +
              • BuildFrameSrc(BlazorCspSettings settings) (lines 85-98) is new: it returns null on an empty FrameSources (lines 87-90), so the directive is omitted entirely rather than emitted empty; otherwise it maps every entry through BlazorCspSettingsValidator.ToOrigin, de-duplicates with StringComparer.OrdinalIgnoreCase (lines 92-95), and joins the result into "frame-src 'self' {origins}" (line 97). The comment (lines 82-84) notes every entry already passed the startup validator, so canonicalization here cannot fail.
              • +
              • BuildPolicy (lines 103-112) now takes frameSrc as a parameter and assembles the directive list: default-src 'self', the script-src above plus 'unsafe-inline' in Development only (line 99, for the injected Hot Reload bootstrap), style-src 'self' 'unsafe-inline', img-src 'self' data: https: (line 101, deliberately open because profile pictures and content images come from arbitrary external hosts, per the comment on lines 93-96 of the class), font-src 'self', the computed connect-src, the frame-src directive when non-null (line 110, spliced in with its own trailing "; " so a null value leaves the rest of the policy unchanged), base-uri 'self', form-action 'self', and frame-ancestors 'none' (line 112, clickjacking protection).
            • -
            • Why it's built this way: putting the base-address selection, the reserved-name policy and the - camelCase conversion all in one mapped endpoint means every host that calls - MapClientConfigEndpoint gets the identical fail-closed contract; a host only supplies the pieces - specific to it through extras.
            • -
            • Where it's used: called from a host's endpoint mapping alongside the other framework endpoints; - its handler constructs ClientConfigBuilder - (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/ClientConfig/ClientConfigBuilder.cs, 3 call - sites) and is pinned by ClientConfigEndpointTests - (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Web.Tests/ClientConfig/ClientConfigEndpointTests.cs, 2 - call sites).
            • +
            • Why it's built this way: computing once and caching keeps the hot path free, and returning a CspPolicy record rather than writing a header directly keeps the provider testable and lets one middleware own header emission. Registering it with AddSingleton (not TryAdd) is what makes it replace the default static provider, which is why the ordering rule in the class doc (lines 21-22) matters: call AddCommonBlazorCsp() before AddCommonSecurityHeaders. frame-src is threaded through as a plain string? parameter rather than read a second time inside BuildPolicy, so the policy string is still built from one pass over its inputs.
            • +
            • Where it's used: registered by AddCommonBlazorCsp() in the MMCA.Common.UI.Web DependencyInjection (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/DependencyInjection.cs); the policy it returns is emitted by SecurityHeadersMiddleware. The class doc notes it was hoisted out of the app Blazor Web hosts where it had been byte-identical (line 21).
            • +
            • Caveats / not-in-source: the registration method's own XML doc still describes the fallback as a "permissive Report-Only fallback on misconfiguration" (MMCA.Common.UI.Web/DependencyInjection.cs:39). The code is the truth: the fallback is enforced and narrowed to 'self' (line 62). Treat that doc line as stale.
            • +
            +

            IdempotentReadRetry

            +
            +

            MMCA.Common.UI · MMCA.Common.UI.Services.Api · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/IdempotentReadRetry.cs:18 · Level 2 · class (static)

            +
            +
              +
            • What it is: the retry that AuthenticatedServiceBase applies, made + reachable without inheriting from it, so a service that makes anonymous reads (a lookup service that + creates a plain APIClient) gets three retries instead of a single attempt (class summary, + lines 5-8).
            • +
            • Depends on: AuthenticatedServiceBase for the policy instance, read + through its internal SharedRetryPolicy (line 24, + MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/AuthenticatedServiceBase.cs:36). + Externals: Polly.Retry (AsyncRetryPolicy<HttpResponseMessage>, line 1) and BCL HttpClient.
            • +
            • Concept introduced, retry is only safe for an idempotent verb. [Rubric §29, Resilience, Reliability & Business Continuity] assesses whether transient failures are absorbed rather than + surfaced on every call path, not just the inherited one. [Rubric §9, API & Contract Design] + covers the limit: the class remarks (lines 9-16) state that the policy re-sends on an + HttpRequestException, a 5xx other than 501/505, 408 or 429, up to three times with 2s, 4s, 8s + backoff plus jitter, and that re-sending is only safe when doing the operation twice is the same as + doing it once. That is why the only entry point is a GET; a write that needs retries goes through + EntityServiceBase<TEntityDTO, TIdentifierType>, + whose creates carry an Idempotency-Key the server deduplicates on.
            • +
            • Walkthrough
                +
              • Policy (line 24) is an internal static getter returning + AuthenticatedServiceBase.SharedRetryPolicy. Its doc comment (lines 20-23) stresses that it is the + same object, not a copy built from the same settings, so the two paths cannot drift.
              • +
              • GetAsync(HttpClient httpClient, Uri requestUri, CancellationToken cancellationToken) (line 35) + null-guards both arguments (lines 40-41) and returns + Policy.ExecuteAsync(token => httpClient.GetAsync(requestUri, token), cancellationToken) + (line 43), so the token is honored between attempts and by each send. The doc comment (lines 26-34) + sets the ownership rule: every retried response is disposed by the policy, the caller owns and + disposes the final one, and HttpRequestException escapes only when every attempt failed without a + response.
              • +
              +
            • +
            • Why it's built this way: sharing the instance rather than re-declaring the policy keeps one + definition of "retryable" and one backoff schedule for the whole UI layer, including the + response-disposal behavior of AuthenticatedServiceBase.BuildRetryPolicy. Exposing a GET-only + surface instead of a general ExecuteAsync is the guardrail: the type cannot be used to retry a + non-idempotent write by accident.
            • +
            • Where it's used: ADC lookup services that read anonymously, for example SpeakerLookupService + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Speakers/SpeakerLookupService.cs:25), + EventLookupService (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Events/EventLookupService.cs:83), + CategoryItemLookupService (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Categories/CategoryItemLookupService.cs:54), + SessionLookupService (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Services/Lookups/SessionLookupService.cs:26,57) + and NowNextService (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Services/HappeningNow/NowNextService.cs:26). + Several of them pass it as the per-page fetch inside PagedReadAll. Pinned by + IdempotentReadRetryTests + (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/Api/IdempotentReadRetryTests.cs).

            HttpResultExecutor

            @@ -5830,11 +6348,11 @@

            HttpResultExecutor

            hierarchy, including a consumer app's hand-written client, adopt the same contract with one call.
          • Where it's used: it wraps every dispatch in this package, EntityServiceBase<TEntityDTO, TIdentifierType> - (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:332,362), + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:336,366), ChildEntityServiceBase (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/ChildEntityServiceBase.cs:37,53,71), AuthUIService - (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/AuthUIService.cs:192,205,226,241,254,277) + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Auth/AuthUIService.cs:186,199,220,235,248,271) and NotificationInboxService (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Notifications/NotificationInboxService.cs:43,61,81,98). Outside the framework it is called directly by services that sit outside the base hierarchy: Store's @@ -5856,73 +6374,102 @@

            HttpResultExecutor

            user-facing messages are hard-coded English by design, so a fully localized app still shows them untranslated unless the page branches on the code itself.
          -

          IRoleAdminUIService

          -
          -

          MMCA.Common.UI · MMCA.Common.UI.Services.Administration · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Administration/IRoleAdminUIService.cs:22 · Level 3 · interface

          -
          -
            -
          • What it is: the typed client contract for the role-admin editor: read every role's compiled and - stored permissions, read one role, read the catalog an editor renders from, and replace one role's - stored permission set.
          • -
          • Depends on: first-party: RolePermissionsResponse (the - per-role read and write shape), PermissionCatalogResponse - (the closed sets a role editor may choose from), and Result - / Result<T> (IRoleAdminUIService.cs:30,36,44,54) for every member's return type. Its sole - implementation is RoleAdminService.
          • -
          • Concept: each member's XML doc names the exact endpoint it calls (GET Admin/Roles, - GET Admin/Roles/{role}, GET Admin/Roles/catalog, PUT Admin/Roles/{role}/permissions, lines - 27,32,40,47-48), so the contract doubles as the wire map for the role-admin surface without a - separate reference. [Rubric §1, SOLID Principles] reads this as a small, role-scoped interface: - it names only what a role editor needs and nothing a user editor would, the mirror decision to the - split between IUserAdminActionsUIService and - IUserAdminUIService<TUserDto> one member table below.
          • -
          • Walkthrough: GetAllAsync (line 30) and GetAsync(string role, ...) (line 36) are the two reads - that feed the role list and role-detail pages. GetCatalogAsync (line 44) reads the roles-plus- - permissions catalog a role editor renders its checkboxes from. SetStoredPermissionsAsync(role, permissions, ...) (lines 54-57) replaces the complete stored permission set for one role in a single - PUT, not an incremental add/remove, so the caller always sends the full desired set.
          • -
          • Why it's built this way: this is an ADR-116 opt-in surface - (ADR-116): the admin app - can wire role management by registering RoleAdminService against this contract - without the framework hard-wiring an admin UI into every consumer.
          • -
          • Where it's used: implemented by RoleAdminService and registered against it in - DependencyInjection (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs); - consumed by RoleAdminEdit.razor.cs and RoleAdminList.razor.cs; its ADC identity pages carry the - behavior-pinning tests - (MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.UI.Tests/Pages/Roles/RoleEditTests.cs, - RoleListTests.cs).
          • +

            PagedReadAll

            +
            +

            MMCA.Common.UI · MMCA.Common.UI.Services.Api · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/PagedReadAll.cs:19 · Level 3 · class (static)

            +
            +
              +
            • What it is: a helper that reads every row of an entity controller's /paged endpoint by + requesting page after page until the server's reported total is reached, for hand-written services + that need the whole set in one list (class summary, lines 8-11).
            • +
            • Depends on: Result and Result<T> for the return + shape, PagedCollectionResult<T> and its + PaginationMetadata for each page (imported + through MMCA.Common.Shared.Abstractions and MMCA.Common.UI.Common, lines 3-4). Externals: BCL + CultureInfo, string.Create, System.Diagnostics.CodeAnalysis.
            • +
            • Concept introduced, a single read truncates silently. [Rubric §19, State Management & Data Flow] assesses whether the data a page holds is the data the server has. The remarks (lines 12-18) + record the trap: the API clamps any requested page size to its own maximum + (ApplicationSettings.MaxPageSize, 500 by default), and the GET-all endpoint ignores pageSize and + always returns that maximum, so rows past it vanish with nothing to say so. Paging until + PaginationMetadata.TotalItemCount is reached is the only unbounded route. [Rubric §29, Resilience, Reliability & Business Continuity] applies to the loop bound: a server that misreports its total + cannot make the client spin forever.
            • +
            • Walkthrough
                +
              • PageSize (line 22) is a public const int of 500, the API's default maximum, so no request is + clamped (comment, line 21). MaxPages (line 28) is a private const of 40, the runaway guard of + 20,000 rows (comment, lines 24-27).
              • +
              • LookupPageUrl(string entity, int pageNumber) (line 43) builds + {entity}/paged?pageNumber=...&pageSize=500&sortColumn=Id&sortDirection=asc&includeFKs=false&includeChildren=false + with string.Create(CultureInfo.InvariantCulture, ...) (lines 44-46). Sorting by Id ascending + keeps consecutive pages from skipping or repeating a row, and the doc comment (lines 30-38) makes + the exact text part of a contract: a host that warms its output cache by replaying these URLs must + replay them byte for byte. The CA1055 suppression (lines 39-42) keeps the return a string for + that reason, since callers both compose it as text and wrap it as a relative Uri.
              • +
              • ReadAllAsync<T>(Func<int, Task<Result<PagedCollectionResult<T>>>> fetchPage, CancellationToken) + (line 57) null-guards the delegate (line 61) and loops pageNumber from 1 to MaxPages + (line 65). Each iteration checks cancellation first (line 67), fetches the page (line 69), and + returns the first failure unchanged as Result.Failure<List<T>>(result.Errors) (lines 70-73). An + empty page ends the loop (lines 75-79); otherwise the items are accumulated (line 81) and the loop + ends once the count reaches TotalItemCount (lines 83-86). The method returns + Result.Success(accumulated) (line 89), so hitting the 40-page guard yields the rows read so far + rather than a failure.
              • +
              +
            • +
            • Why it's built this way: the fetch is a delegate rather than an HttpClient, so each caller + keeps its own transport choice (an authenticated SendRequestAsync, or an anonymous + IdempotentReadRetry GET) and its own filters, while the paging rule lives + once. Returning a Result rather than throwing keeps it inside the framework's client contract + that a page branches on failures instead of catching them.
            • +
            • Where it's used: ADC UI services SpeakerLookupService + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Speakers/SpeakerLookupService.cs:20), + EventLookupService (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Events/EventLookupService.cs:78), + CategoryItemLookupService (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Categories/CategoryItemLookupService.cs:37) + and OrganizerFeedbackService + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Feedback/OrganizerFeedbackService.cs:27,82). + The ADC Conference service's SelfHttpOutputCacheWarmupTask replays URLs in the + LookupPageUrl shape + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/SelfHttpOutputCacheWarmupTask.cs:38). Pinned + by PagedReadAllTests + (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/Api/PagedReadAllTests.cs). + OrganizerFeedbackService composes its own filtered /paged URLs with PagedReadAll.PageSize + rather than LookupPageUrl (OrganizerFeedbackService.cs:32,87), since it needs an EventId or + SessionId filter.
            -

            IUserAdminActionsUIService

            +

            NotificationInbox

            -

            MMCA.Common.UI · MMCA.Common.UI.Services.Administration · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Administration/IUserAdminActionsUIService.cs:16 · Level 3 · interface

            +

            MMCA.Common.UI · MMCA.Common.UI.Pages.Notifications · MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Notifications/NotificationInbox.razor.cs:26 · Level 4 · class (partial, page)

              -
            • What it is: the account-mutation contract for user admin: lock, unlock, and two shapes of - role-replacement, a full-set SetRolesAsync and a single-role convenience SetRoleAsync.
            • -
            • Depends on: first-party: SetUserRolesRequest (implied wire - shape behind SetRolesAsync), Result - (IUserAdminActionsUIService.cs:22,26,33,41,56,58); UserIdentifierType (the module's identifier - alias) names every account parameter. Extended by - IUserAdminUIService<TUserDto>.
            • -
            • Concept, a member that exists only because a mock cannot proxy a default interface method. The - remarks on SetRoleAsync (lines 108-111) give the reasoning directly: the obvious shape would have - been a default interface implementation over SetRolesAsync that wraps the single role in a - one-element list, but a default implementation on an interface is not virtual to a mock proxy, so a - test could neither stub nor verify it if it were written that way. Declaring SetRoleAsync as its - own interface member keeps it mockable at the cost of one more line on the contract. - [Rubric §14, Testability] is the rubric this decision serves.
            • -
            • Walkthrough: LockAsync(userId, ...) (line 84) and UnlockAsync(userId, ...) (line 90) are the - two session-revoking actions. SetRolesAsync(userId, roles, ...) (lines 97-100) replaces the complete - role set an account holds. SetRoleAsync(userId, role, ...) (line 116) is documented as sending a - one-element set to the same replacement endpoint (lines 102-106), the shape both shipped consumers - need because each holds exactly one role per account.
            • -
            • Why it's built this way: separated from the read/page surface in - IUserAdminUIService<TUserDto> so the mutating actions have one - narrow, generic-free contract a page or a test can depend on without also depending on the DTO type - parameter.
            • -
            • Where it's used: extended by IUserAdminUIService<TUserDto>; - implemented by UserAdminService<TUserDto>; called directly from - UserAdminList.razor.cs for the lock/unlock/role actions on the user-admin grid, and pinned by - UserListTests.cs (ADC) and UserAdminListTests.cs (Common).
            • +
            • What it is: the code-behind for the per-user notification inbox, routed at both @page "/notifications/inbox" and @page "/notifications/inbox/{Id:int}" (NotificationInbox.razor:1-2). It fetches the signed-in user's notifications a page at a time, renders each as a read or unread card, lets the user mark items read individually or all at once, highlights and scrolls to a deep-linked notification, and reloads the current page when a real-time push asks for a refresh.
            • +
            • Depends on: first-party: INotificationInboxUIService (the typed read-side HTTP service), NotificationState (the per-circuit unread-count store and refresh signal), UserNotificationDTO (the row shape), IToastService (the toast abstraction), ResultUiExtensions (the NotifyOnFailure helper), and SharedResource (the resx anchor for the localizer). Externals: MudBlazor (IScrollManager, ScrollBehavior, BreadcrumbItem, Icons), Microsoft.AspNetCore.Components ([Inject], [Parameter], OnInitializedAsync, OnParametersSet, OnAfterRenderAsync, InvokeAsync, StateHasChanged), Microsoft.Extensions.Localization (IStringLocalizer<T>), BCL CancellationTokenSource, IDisposable, Math.Ceiling, CultureInfo.InvariantCulture.
            • +
            • Concept introduced, the Blazor code-behind page pattern (.razor plus .razor.cs partial class). The three notification pages in this unit are authored as partial classes split across two files: the .razor holds declarative MudBlazor markup and the routes, the .razor.cs holds the C# (public partial class NotificationInbox, line 26), the injected services, the view state, and the handlers. The framework constructs the component, calls OnInitializedAsync (line 73) once, and re-renders when a handler mutates a field. Four habits recur across all three pages and are worth learning once here:
                +
              • Disposal-safe async with a per-component CancellationTokenSource. A readonly CancellationTokenSource _cts (line 45) is created with the component and its token is passed to every service call. Dispose(bool) (lines 338-350) cancels and disposes it behind the classic _disposed guard (line 336). Every async handler swallows OperationCanceledException silently (for example lines 241-244) because that is the expected outcome when the user navigates away mid-fetch.
              • +
              • Result-typed failures, not exceptions. The service calls return a Result, so the page branches on TryGetValue (line 220) and routes the failure through result.NotifyOnFailure(Toast, L) (line 238). The comment there (lines 236-237) records the rule that makes this safe: one toast, and the list is left as it was rather than blanked, so a transient failure does not erase what is on screen.
              • +
              • Busy flags gate the UI. IsLoading and IsSaving (lines 51-52) are protected with private setters; the markup shows progress while loading and sets Disabled="IsSaving" on the action controls (NotificationInbox.razor:18) so a double click cannot double-post.
              • +
              • Push-driven refresh via an event subscription. OnInitializedAsync subscribes to NotificationState.OnRefreshRequested (line 83) and Dispose(bool) unsubscribes (line 344).
              • +
              • [Rubric §19, State Management & Data Flow] assesses where state lives; transient view state stays in private fields (_notifications, _currentPage, _totalPages, lines 54-56) while the shared unread count is written back into the scoped NotificationState (lines 289, 323) and its refresh signal is read. Local stays local, shared stays shared.
              • +
              • [Rubric §25, Navigation, Routing & Information Architecture] assesses route structure. The second @page directive is a typed deep link: a push payload or an email can point straight at one notification. The class doc (lines 18-24) states the two design rules that follow from it: the :int route constraint is the validation boundary, so a malformed id never reaches the component (the router renders NotFound instead), and an id that is simply not on the loaded page degrades silently to the plain inbox rather than raising an error the user can do nothing about.
              • +
              • [Rubric §21, Accessibility]: the icon-only mark-read control carries an explicit localized aria-label (NotificationInbox.razor:59).
              • +
              • [Rubric §27, Internationalization & Localization]: this page holds no literal English. The injected IStringLocalizer<SharedResource> L (line 33) resolves the title (line 47), the breadcrumbs, and every toast (L["Notif.AllMarkedRead"], line 324). The breadcrumb trail is built inside OnInitializedAsync (lines 77-81), not in a field initializer, so the injected localizer is available and labels re-resolve per circuit under the active culture (comment, lines 75-76, citing ADR-027).
              • +
              +
            • +
            • Walkthrough
                +
              • PageSize (line 28) is const int 20: fixed-size server-side pagination, not infinite scroll. Injected InboxService, NotificationState, Toast, L, and MudBlazor's ScrollManager (lines 30-34).
              • +
              • [Parameter] public int? Id (line 43) is the deep-link route parameter. The doc (lines 36-42) explains the type choice: UserNotificationIdentifierType is an int alias, and a route parameter's type must be written out for the :int constraint to bind, so the parameter is declared int? and converted where it is used.
              • +
              • Deep-link state is four separate fields, and each exists for a distinct reason (lines 62-71): _highlightedId (found on the loaded page), _pendingScrollId (a scroll the next render owes), _scrolledId (already scrolled, so a re-render or push-driven reload never scrolls twice), and _appliedId (the Id the state was last computed for, to detect a re-navigation).
              • +
              • OnParametersSet (lines 94-103) clears the _scrolledId latch when _appliedId != Id and then recomputes the target. The doc (lines 88-93) records the bug this prevents: navigating from /notifications/inbox/5 to /notifications/inbox/9 reuses the component instance, so without clearing the latch the second deep link would highlight but never move the viewport.
              • +
              • OnAfterRenderAsync (lines 106-118) is the only place a scroll happens. It returns unless a scroll is pending and the component is alive, clears _pendingScrollId and sets _scrolledId before the await (comment, line 113, so a re-entrant render cannot queue the same scroll twice), and calls ScrollManager.ScrollIntoViewAsync(CardSelector(id), ScrollBehavior.Smooth) (line 117).
              • +
              • ApplyDeepLinkTarget (lines 126-140) matches the route id against what the loaded page actually holds: Id is not { } id || id <= 0 || !_notifications.Exists(n => n.Id == id) clears both the highlight and the pending scroll (lines 128-133). The doc (lines 120-125) is the "no toast" decision, a deep link the user cannot act on is not an error they caused.
              • +
              • The card-chrome helpers: IsDeepLinkTarget (line 142); CardElementId (lines 144-147, formatting notification-{id} with CultureInfo.InvariantCulture because it becomes a DOM id) and CardSelector (line 149); CardElevation (lines 152-160, 4 when deep-linked, else 1 for unread and 0 for read); CardClass (lines 162-168); and CardStyle (lines 175-183), which builds the unread left border and the deep-link ring from MudBlazor palette variables (var(--mud-palette-primary), var(--mud-palette-secondary)) rather than literal hex, so both themes stay legible (comment, lines 170-174). [Rubric §20, Design System & Theming] shows up here: even inline styles source their colors from the theme's CSS custom properties.
              • +
              • Push coalescing. HandleRefreshRequested (lines 185-193) is EventHandler-shaped so it cannot be async Task; it discards into InvokeAsync(RefreshFromPushAsync) (line 192). RefreshFromPushAsync (lines 195-212) sets _refreshPending = true and returns when a load is already in flight (lines 202-208); the comment (lines 204-205) states the invariant, never drop the push, so overlapping pushes coalesce into exactly one trailing reload instead of vanishing.
              • +
              • LoadNotificationsAsync (lines 214-258): sets IsLoading, calls GetInboxAsync(_currentPage, PageSize, _cts.Token) (line 219), and on success materializes page.Items (line 222), computes _totalPages from page.PaginationMetadata.TotalItemCount with Math.Ceiling (line 223) clamped to a floor of 1 (lines 224-227) so an empty inbox never renders a zero-page pager, then recomputes the deep-link highlight (line 232, only a successful load can decide whether the id is present). The tail (lines 253-257) drains _refreshPending with one more RefreshFromPushAsync; the comment (lines 250-252) explains why the recursion is bounded, the flag is cleared first, so a push arriving during this reload queues one more and no further.
              • +
              • OnPageChangedAsync(int page) (lines 260-264): records the page and reloads.
              • +
              • MarkReadAsync(UserNotificationDTO) (lines 266-300): calls MarkReadAsync(notification.Id, _cts.Token) (line 271), returns early on failure after a toast (lines 272-276), then optimistically patches local state, locating the row with FindIndex (line 279) and replacing it via a record with-expression, notification with { IsRead = true, ReadOn = DateTime.UtcNow } (line 282). It then refetches the authoritative unread count (line 286) and pushes it into NotificationState.SetUnreadCount only when the count call succeeded (lines 287-290); a failed count means "unknown", so the badge keeps its value (comment, line 285).
              • +
              • MarkAllReadAsync (lines 302-334): one service call (line 307), a loop flipping every unread row in place (lines 315-321), then SetUnreadCount(0) (line 323) and a localized success toast (line 324).
              • +
              • Disposal: _disposed (line 336), Dispose(bool) (lines 338-350) unsubscribing the refresh event and cancelling the _cts, Dispose() (lines 352-356) with GC.SuppressFinalize.
              • +
              +
            • +
            • Why it's built this way: the page is a thin view over INotificationInboxUIService, so all HTTP and JSON live in the service and the component stays testable against a stub. Patching local state after a mark-read (rather than refetching the page) keeps the interaction snappy while still reconciling the shared badge from the server, and the coalescing refresh keeps the list current when pushes arrive in bursts. The deep-link machinery is worth reading as a case study in idempotent side effects: a scroll is a one-shot action in a component model that re-renders freely, so it needs the "owed" and "already done" flags to be correct under re-render, re-navigation, and disposal.
            • +
            • Where it's used: rendered at /notifications/inbox (and /notifications/inbox/{Id:int}) for authenticated users; the route constant and nav entry come from NotificationRoutePaths and NotificationUIModule. NotificationBell reads the same NotificationState this page writes, and the layout-mounted NotificationListener raises the OnRefreshRequested signal it consumes. Its admin siblings are NotificationList and NotificationSend.

            ChildEntityServiceBase

            @@ -6026,30 +6573,30 @@

            EntityServiceBase<TEntit PaginationMetadata (lines 73, 116, 125), IUiReadCache (optional constructor parameter, line 47), IConcurrencyAware and - ConcurrencyETag (lines 197-199, 394), - IdempotencyHeaders (line 386), - ProblemDetailsResultReader (lines 339, 367), - HttpResultExecutor (lines 332, 362) and + ConcurrencyETag (lines 197-199, 398), + IdempotencyHeaders (line 390), + ProblemDetailsResultReader (lines 343, 371), + HttpResultExecutor (lines 336, 366) and ITokenStorageService (line 46); Polly through the inherited RetryPolicy, and System.Net.Http.Json (BCL).
          • Concept introduced, one dispatch point for every cross-cutting HTTP concern. [Rubric §29, Resilience, Reliability & Business Continuity] assesses whether retry, auth and error handling are applied in one place instead of repeated per call: the six public methods contain only URL construction, and the - two SendRequestAsync overloads (lines 324 and 354) contain all of the policy. + two SendRequestAsync overloads (lines 328 and 358) contain all of the policy. [Rubric §19, State Management & Data Flow] applies because components never touch HttpClient: they inject the typed interface and receive DTOs wrapped in a Result. [Rubric §29, Resilience, Reliability & Business Continuity] applies through the inherited three-retry exponential-backoff-with-jitter policy (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/AuthenticatedServiceBase.cs:25), whose - predicate retries 5xx plus 408 and 429 but not 501 or 505 (AuthenticatedServiceBase.cs:100).
          • + predicate retries 5xx plus 408 and 429 but not 501 or 505 (AuthenticatedServiceBase.cs:106).
          • Concept introduced, retry safety for a non-idempotent verb. [Rubric §9, API & Contract Design] assesses whether client and server share an explicit protocol for duplicate writes. A retry policy that re-issues a POST is a correctness hazard: if the first attempt reached the server and only the response was lost, the retry creates a second record. AddAsync is the one method that passes a key (line 162), minted by AuthenticatedServiceBase.NewIdempotencyKey() as a compact GUID - (AuthenticatedServiceBase.cs:43). The key is set as a default request header on the client - (line 386) rather than per request, and that one client instance serves every retry attempt, so all - attempts carry the identical value (the comment at lines 382-385 says exactly that). The server side + (AuthenticatedServiceBase.cs:49). The key is set as a default request header on the client + (line 390) rather than per request, and that one client instance serves every retry attempt, so all + attempts carry the identical value (the comment at lines 386-389 says exactly that). The server side is the opt-in IdempotencyFilter, and both ends read the header name from the shared IdempotencyHeaders constant. Reads, full-PUT updates and deletes send no key because they are naturally idempotent @@ -6109,26 +6656,30 @@

            EntityServiceBase<TEntit subclass overrides when the route key is not the DTO's own id.

          • GetCachedAsync<T>(url, ct, bypassCache) (line 241) guards the url (line 246), goes straight to the network when no cache is registered or the caller asked to bypass (lines 248-251), answers from a - fresh entry when there is one (lines 253-256), and otherwise fetches and stores. Only a success - with a non-null value is stored (lines 262-265), because caching a failure would pin a transient + fresh entry when there is one (lines 253-256), and otherwise fetches and stores. Before the GET it + captures ReadCache.Generation (line 261), and the store passes that value back as + ReadCache.Set(url, result.Value, generation) (line 268); the comment (lines 258-260) explains + that a write invalidating this endpoint while the read is in flight moves the generation, so the + late store is dropped instead of re-caching a value the write just made stale. Only a success + with a non-null value is stored (lines 264-269), because caching a failure would pin a transient outage in front of the user for the whole TTL and a cached 404 would survive the create that fixed it. bypassCache exists for a read the user explicitly asked to be current, a refresh button or a re-poll after a push (documented at lines 231-235).
          • -
          • InvalidateOnSuccess (line 281) drops this endpoint's cached reads by prefix, and only on success - (lines 283-286): a rejected write changed nothing, so invalidating there would throw away entries - that are still accurate. AsReadOnlyList (line 293) presents a deserialized Items collection - without assuming the JSON reader produced a list (lines 296-298).
          • -
          • SendRequestAsync<T> (line 324) and SendRequestAsync (line 354) are the center of the class and - are structurally identical: null-guard the lambda (lines 330, 360), wrap everything in - HttpResultExecutor (lines 332, 362), build a client for this one logical - operation (lines 337, 365), execute the caller's lambda through RetryPolicy with the cancellation - token threaded in so a cancelled operation does not sleep out its backoff (lines 338, 366), and +
          • InvalidateOnSuccess (line 285) drops this endpoint's cached reads by prefix, and only on success + (lines 287-290): a rejected write changed nothing, so invalidating there would throw away entries + that are still accurate. AsReadOnlyList (line 297) presents a deserialized Items collection + without assuming the JSON reader produced a list (lines 298-303).
          • +
          • SendRequestAsync<T> (line 328) and SendRequestAsync (line 358) are the center of the class and + are structurally identical: null-guard the lambda (lines 334, 364), wrap everything in + HttpResultExecutor (lines 336, 366), build a client for this one logical + operation (lines 341, 369), execute the caller's lambda through RetryPolicy with the cancellation + token threaded in so a cancelled operation does not sleep out its backoff (lines 342, 370), and hand the response to ProblemDetailsResultReader - (lines 339, 367). The generic overload fails a 2xx with no body via the reader's + (lines 343, 371). The generic overload fails a 2xx with no body via the reader's EmptyResponseCode, which is why the body-less overload exists at all (documented at lines - 319-323). The client is kept in scope across the read on purpose (comment at lines 335-336).
          • -
          • CreateRequestClientAsync(idempotencyKey, ifMatch) (line 376) is where both conditional headers are - attached (lines 380-395), each with a comment stating the retry property it preserves: the same key + 323-327). The client is kept in scope across the read on purpose (comment at lines 339-340).
          • +
          • CreateRequestClientAsync(idempotencyKey, ifMatch) (line 380) is where both conditional headers are + attached (lines 384-399), each with a comment stating the retry property it preserves: the same key on every attempt, and the same precondition on every attempt so a write that lost the race fails consistently instead of succeeding on a later attempt against a version the caller never saw.
          @@ -6156,13 +6707,258 @@

          EntityServiceBase<TEntit EntityServiceBaseCachingTests and EntityServiceBaseIdempotencyRetryTests, which asserts the key is emitted on creates only and stays identical across attempts.

        • -
        • Caveats: GetAllAsync has no page-size bound in source; it asks the "all" endpoint for - everything and materializes the result, which is why grids use GetPagedAsync instead. The read +
        • Caveats: GetAllAsync sends no page size, but it is not unbounded: the "all" endpoint ignores + pageSize and returns at most the API's maximum page size (500 by default), so rows past it are + silently dropped (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/PagedReadAll.cs:12-18). + A service that needs every row pages with PagedReadAll, and grids use + GetPagedAsync. The generation guard only works against an IUiReadCache that + implements it: the interface defaults Generation to 0 and the three-argument Set to the plain + Set (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Caching/IUiReadCache.cs:40,70), + which the shipped UiReadCache overrides + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Caching/UiReadCache.cs:34,104). The read cache is keyed by URL alone and holds nothing about who fetched it, so it is only safe while IUiReadCache stays a per-circuit registration. GetPagedAsync accepts a filters dictionary that the signature does not declare nullable (line 80), yet the body null-checks it (line 97): defensive against a caller the signature says cannot exist.
        +

        NotificationList

        +
        +

        MMCA.Common.UI · MMCA.Common.UI.Pages.Notifications · MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Notifications/NotificationList.razor.cs:16 · Level 6 · class (partial, page)

        +
        +
          +
        • What it is: the code-behind for the admin/organizer push-notification history page, routed at @page "/notifications" (NotificationList.razor:1). It loads previously sent broadcasts and renders them in a status table, with a button onward to the compose page.
        • +
        • Depends on: first-party: IPushNotificationUIService (the send and history HTTP service), PushNotificationDTO (the row shape, carrying status and recipient count), NotificationRoutePaths (the route constants), IToastService, ResultUiExtensions (NotifyOnFailure), and SharedResource. Externals: MudBlazor (BreadcrumbItem, Icons), Microsoft.AspNetCore.Components (NavigationManager, [Inject]), Microsoft.Extensions.Localization.
        • +
        • Concept reinforced, the same code-behind shape as NotificationInbox. Same [Inject] service set (lines 18-21), same readonly CancellationTokenSource _cts plus dispose pattern (lines 23, 79-98), same IsLoading gate (line 29), same cancellation-swallowing load (lines 67-70), same result.NotifyOnFailure(Toast, L) failure surface (line 64). It differs only in what it loads and how much of it.
            +
          • [Rubric §25, Navigation, Routing & Information Architecture] assesses route structure and inter-page flow; navigation goes through NotificationRoutePaths constants (NavigateToSend targets NotificationRoutePaths.NotificationSend, line 77) rather than a literal URL, so a route change happens in exactly one file.
          • +
          • [Rubric §27, Internationalization & Localization] picks up an extra trick here: DisplayStatus(string status) (lines 34-38) looks up L[$"Notif.Status.{status}"] and falls back to the raw wire value when localized.ResourceNotFound (line 37). The comparison value stays the untranslated wire string while only the displayed chip text localizes, which keeps transport values and presentation separate and means a newly added server status renders (untranslated) instead of blanking (the comment on line 33 cites ADR-027). This is the same pass-through-on-unknown-key discipline that DataAnnotationsModelValidator applies to error messages.
          • +
          +
        • +
        • Walkthrough
            +
          • Injected NotificationService, NavigationManager, Toast, L (lines 18-21); Title reads L["Notif.List.Title"].Value (line 25); _breadcrumbs (line 27) is built Home to Push Notifications in OnInitializedAsync (lines 43-47), with the leaf crumb disabled: true to mark the current page (line 46).
          • +
          • _notifications is an IReadOnlyCollection<PushNotificationDTO> initialized empty (line 31).
          • +
          • OnInitializedAsync (lines 40-50) builds the breadcrumbs then awaits LoadNotificationsAsync.
          • +
          • LoadNotificationsAsync (lines 52-75): calls GetHistoryAsync(pageNumber: 1, pageSize: 50, _cts.Token) (line 57) and copies history.Items into _notifications on success (line 60), otherwise raises the localized failure toast (line 64). This page fetches one fixed 50-row page and lets MudBlazor page that buffer client-side; unlike the inbox there is no server round-trip per page.
          • +
          • NavigateToSend (line 77) sends the "send new" button to the compose page.
          • +
          • Disposal mirrors the family: _disposed (line 79), Dispose(bool) cancelling the _cts (lines 81-92), Dispose() (lines 94-98).
          • +
          +
        • +
        • Why it's built this way: broadcast history is low-volume admin data, so one 50-row fetch with client-side paging is simpler and adequate, and it avoids server-side paging plumbing that would earn nothing. Keeping HTTP behind IPushNotificationUIService mirrors the inbox and keeps the component a thin view. The shared [Rubric §18, UI Architecture & Component Design] story is told under NotificationInbox.
        • +
        • Where it's used: rendered at /notifications for organizer and admin roles (the nav entry from NotificationUIModule is gated on RoleNames.Organizer); it links onward to NotificationSend.
        • +
        • Caveats / not-in-source: the 50-row ceiling is a client-side choice in this file; what the server does when more than 50 broadcasts exist (whether the page silently truncates the history) is not visible here.
        • +
        +

        NotificationSend

        +
        +

        MMCA.Common.UI · MMCA.Common.UI.Pages.Notifications · MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Notifications/NotificationSend.razor.cs:20 · Level 6 · class (partial, page)

        +
        +
          +
        • What it is: the code-behind for the compose-and-broadcast form, routed at @page "/notifications/send" (NotificationSend.razor:1). It collects a title and a message into NotificationSendModel, validates them against that model's own annotations, sends one broadcast through the Notification API, reports the recipient count, and returns to the history page.
        • +
        • Depends on: first-party: IPushNotificationUIService (the SendAsync call), NotificationSendModel (the form model), ModelValidation + DataAnnotationsModelValidator (the validation bridge), INotificationScopeProvider (the targeting caption), SendPushNotificationRequest (the wire contract), PushNotificationDTO (the result carrying RecipientCount), Result, NotificationRoutePaths, ErrorMessages, IToastService, ResultUiExtensions, and SharedResource. The markup also composes an UnsavedChangesGuard bound to IsDirty (NotificationSend.razor:12). Externals: MudBlazor (MudForm, BreadcrumbItem, Icons), Microsoft.AspNetCore.Components (NavigationManager, OnInitialized, OnInitializedAsync), Microsoft.Extensions.Localization.
        • +
        • Concept introduced, MudForm validation driven entirely by the model. This is the family's form page, and it is the worked example of the validation stack in this unit. The markup declares <MudForm @ref="_form" Model="_model"> with two fields that set For, Validation="@_validate", and read their affordances off the same model (NotificationSend.razor:43-66). No rule is declared in markup: the comment above the form (lines 38-42 of the markup) spells out the division, Required drives the asterisk and aria-required while its message still comes from the model, MaxLength caps the input, and Counter shows the budget from the shared request contract so the numbers cannot drift from the server. The C# holds the form by reference (MudForm? _form, line 36) and explicitly drives validation before sending: await _form.ValidateAsync() then a guard on _form.IsValid (lines 98-105). MudForm has no OnValidSubmit, so that explicit pass is the gate (comment, lines 96-97).
            +
          • [Rubric §24, Forms, Validation & UX Safety] assesses input validation, double-submit protection, and feedback. All four are present: model-declared rules run through the shared delegate, an IsSaving flag (line 33) bound to Disabled on both buttons (NotificationSend.razor:73,80) so the send cannot be fired twice, a warning toast ErrorMessages.ValidationError on a failed gate (line 103), and a success toast naming the recipient count (line 116).
          • +
          • Two failure surfaces, deliberately not one. _sendResult (line 42) holds the last attempt's outcome and is rendered inline by the shared ErrorSummary component (NotificationSend.razor:36), while the toast stays the transient cue. The markup comment (lines 31-35) records the design constraint: the summary is deliberately not fed MudForm.Errors, because every field already renders its own message inline and MudBlazor contributes a generic "Required" of its own that would stack on top of the model's wording. _sendResult is nulled at the start of every attempt (line 94), so the summary never shows a stale failure.
          • +
          • [Rubric §21, Accessibility] and [Rubric §18, UI Architecture & Component Design] meet in the ErrorSummary: a failure that only appeared as a toast would time out on a long form and be unrecoverable for a screen-reader user who missed it (comment, lines 121-122).
          • +
          • [Rubric §27, Internationalization & Localization]: every string resolves through IStringLocalizer<SharedResource> L (line 24), including the success message L.Plural("Notif.Send.SentTo", sent.RecipientCount, sent.RecipientCount) (line 117), which passes the count through the shared plural extension so the resource file (not C#) picks the plural form as well as the word order. As with its siblings the breadcrumb trail is built in an initialization hook, here the synchronous OnInitialized (lines 55-67, comment citing ADR-027).
          • +
          • [Rubric §24, Forms, Validation & UX Safety] again, for the navigate-away guard: Sent (a private bool, set true immediately before the post-send NavigateTo) and IsDirty (true whenever Sent is false and either field is non-blank) feed the markup's UnsavedChangesGuard, so leaving with typed-but-unsent text prompts, while the send's own redirect never does. The comment on the Sent setter records the ordering: it is set before the navigation call, because the guard reads the live accessor.
          • +
          +
        • +
        • Walkthrough
            +
          • Injected NotificationService, NavigationManager, Toast, L, and ScopeProvider (lines 21-25); _cts (line 27); Title (line 29); _breadcrumbs (line 31) built Home to Push Notifications to Send (lines 58-63), where the middle crumb is a real link via NotificationRoutePaths.Notifications (line 61) and the leaf is disabled: true (line 62).
          • +
          • _model (line 35) is a readonly NotificationSendModel created with the component; _validate (line 46) is the single Func<object, string, IEnumerable<string>> MudBlazor calls with (model, member path), wired in OnInitialized as ModelValidation.For(_model, new DataAnnotationsModelValidator(L)) (line 66). One delegate serves both fields, and no rule is written twice (comment, lines 44-45).
          • +
          • OnInitializedAsync (lines 69-87) resolves the optional scope caption: ScopeProvider.GetCurrentScopeDisplayNameAsync(_cts.Token) (line 77), and only when the name is non-blank does it build _scopeCaption (lines 78-81). The field doc (lines 48-53) and the async doc (lines 71-74) give the reasoning: a scoped application applies its scope to the send automatically, so without a caption the operator would be composing a broadcast with no visible statement of who receives it, and when there is no scope the page renders no caption at all rather than an empty line. [Rubric §24, Forms, Validation & UX Safety] again: making an implicit targeting decision visible is part of a safe destructive-ish action.
          • +
          • SendNotificationAsync (lines 89-134): null-guards _form (lines 91-92), clears _sendResult (line 94), validates and warns on failure (lines 98-105); then under IsSaving builds new SendPushNotificationRequest(_model.Title, _model.Body) (line 110) and awaits SendAsync(request, _cts.Token) (line 111), storing the result for the summary (line 112). On a non-null PushNotificationDTO it raises the success toast with sent.RecipientCount (line 117), sets Sent = true (line 121), and navigates back to the list; otherwise result.NotifyOnFailure(Toast, L) (line 123). The cancellation catch (lines 126-129) additionally names the InteractiveAuto render-mode transition, the case where the WebAssembly runtime takes over mid-call; IsSaving is cleared in finally (lines 130-133).
          • +
          • NavigateToList (line 136) is the Cancel button's handler, back to NotificationRoutePaths.Notifications.
          • +
          • Sent (a private bool property) and IsDirty (!Sent && (Title or Body non-whitespace)) are declared just after NavigateToList, immediately above the disposal members; Sent is documented as being set the moment a send has succeeded and the page is about to navigate away on its own, so the unsaved-changes guard does not prompt over work that has just left the building.
          • +
          • Disposal mirrors the family: _disposed (line 138), Dispose(bool) (lines 140-151), Dispose() (lines 153-157).
          • +
          +
        • +
        • Why it's built this way: a deliberately small form that still demonstrates the full pattern. The rules live on NotificationSendModel so the client cap, the client message, and the server invariant all read the same constants; HTTP stays behind IPushNotificationUIService so the component is unit-testable; and the Sent/IsDirty pair keeps the unsaved-changes guard from firing on the page's own post-send redirect, which is the one navigation on this page that is not the user abandoning work. The send is fire-and-confirm: the server fans out to recipients through the push pipeline (see Group 10) and returns only the aggregate count.
        • +
        • Where it's used: rendered at /notifications/send for organizer and admin roles, reached from the button on NotificationList. The server-side validator for SendPushNotificationRequest enforces the same rules a second time, so client validation is a UX affordance rather than the security boundary.
        • +
        +

        ClientConfigBuilder

        +
        +

        MMCA.Common.UI.Web · MMCA.Common.UI.Web.ClientConfig · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/ClientConfig/ClientConfigBuilder.cs:17 · Level 2 · class (sealed)

        +
        +
          +
        • What it is: the accumulator a host's own extras delegate writes into when it wants extra data + served on the anonymous /client-config document, one named section per Add call.
        • +
        • Depends on: ClientConfigEndpointExtensions (its ApiSectionName + constant, checked as the one reserved name, ClientConfigBuilder.cs:57); Microsoft.AspNetCore.Http + (HttpContext, BCL) and Microsoft.Extensions.DependencyInjection / + Microsoft.Extensions.Configuration for the two convenience accessors it republishes.
        • +
        • Concept, a builder with an internal constructor. [Rubric §26, Front-End Security] covers what + this type prevents: the constructor is internal (line 29), so the only place a ClientConfigBuilder + can come into existence is inside MapClientConfigEndpoint's own request delegate, which is also the + one place a host's extras callback runs. A host cannot construct one ahead of time and stash + arbitrary state in it outside the request it belongs to.
        • +
        • Walkthrough
            +
          • _sections (line 27) is a Dictionary<string, object?> keyed with StringComparer.OrdinalIgnoreCase, + so "OAuth" and "oauth" collide as the same section.
          • +
          • HttpContext (line 32) and the two accessors built on it, Services (line 35, reads + HttpContext.RequestServices) and Configuration (line 38, resolves IConfiguration from those + services), let an extras delegate reach DI and configuration without the endpoint having to pass + them separately.
          • +
          • Add(string name, object? value) (line 53) blank-checks name + (ArgumentException.ThrowIfNullOrWhiteSpace, line 55), rejects the reserved + ClientConfigEndpointExtensions.ApiSectionName case-insensitively (lines 57-62), then adds through + _sections.TryAdd (line 64) so a second call with the same name throws rather than silently + overwriting the first (lines 65-67). It returns this for chaining (line 69).
          • +
          • Sections (line 40) is internal, an IReadOnlyDictionary<string, object?> view the endpoint reads + back after the delegate returns; nothing outside this package can enumerate a builder's contents.
          • +
          +
        • +
        • Why it's built this way: the reserved-name and duplicate-name guards live on the builder rather + than being left to the endpoint's own loop, so every caller of Add, present or future, gets the same + two checks for free. The internal constructor and internal Sections getter keep the type's read side + as narrow as its write side: a host can only add, never inspect what a different extras call already + added.
        • +
        • Where it's used: constructed once per request inside + ClientConfigEndpointExtensions.MapClientConfigEndpoint's handler + and handed to the caller's extras delegate + (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs:63-64); + its Sections are then read back to assemble the response document + (ClientConfigEndpointExtensions.cs:73-76). Exercised directly by + ClientConfigEndpointTests + (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Web.Tests/ClientConfig/ClientConfigEndpointTests.cs).
        • +
        +

        ClientConfigEndpointExtensions

        +
        +

        MMCA.Common.UI.Web · MMCA.Common.UI.Web.ClientConfig · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/ClientConfig/ClientConfigEndpointExtensions.cs:17 · Level 2 · class (static)

        +
        +
          +
        • What it is: the extension that maps GET /client-config, the anonymous document a WASM client's + bootstrap fetches to learn the browser-reachable API base address before it can hold a session, plus + whatever extra sections the host contributes.
        • +
        • Depends on: ClientConfigBuilder (constructed per request, line 63), + ApiSettings (IOptions<ApiSettings>, read for WasmApiEndpoint, line 55), and, only + in a host running the same-origin API proxy, SameOriginApiProxyMarker + (probed from request services, line 93) and SameOriginApiProxySettings + (read for PathPrefix, line 98); externals Microsoft.AspNetCore.Routing (IEndpointRouteBuilder, + RouteHandlerBuilder), Microsoft.AspNetCore.Http (HttpContext), System.Text.Json + (JsonNamingPolicy.CamelCase), and the ASP.NET Core minimal-API Results type.
        • +
        • Concept introduced, fail-closed instead of falling back. [Rubric §22, Configuration and Secrets Management] assesses whether a bad or missing configuration value is caught rather than silently + degrading a live host. The endpoint's remarks (lines 33-40) state the rule directly: it serves + Api:WasmApiEndpoint, the browser-reachable gateway URL, and never falls back to Api:ApiEndpoint, + the server head's own service-discovery name, because a browser cannot resolve that name and serving + it would hand the client a silently broken base address. A host missing the key answers every request + with a server error naming the missing key (lines 56-61), which surfaces as a failed client start + instead of a client quietly pointed at the wrong API.
        • +
        • Concept introduced, anonymous by declared necessity. [Rubric §11, Security] covers why this + endpoint is one of the few that must allow anonymous access on purpose: the WASM client fetches this + document before it can hold a session (remarks, lines 41-46), and a host's fallback authorization + policy would otherwise gate it like any other endpoint that states nothing. The endpoint therefore + declares AllowAnonymous() itself (line 80) rather than relying on an ambient default, and the + remarks warn that only values the public site renders anyway belong in extras (lines 44-45).
        • +
        • Concept introduced, an additive key for the same-origin proxy. A host that runs the same-origin + API proxy (ADR-131) gets one + extra member in the Api section, sameOriginApiEndpoint, the proxy's origin-relative base (for + example /api/) that the WASM bootstrap resolves against the page's own origin, while apiEndpoint + stays the gateway for full-page navigations (doc, lines 84-90). The proxy's presence is detected + from DI (the marker service), not from configuration, so every host without the proxy serves the + section exactly as before and an existing client sees no change.
        • +
        • Walkthrough
            +
          • Two public constants: ClientConfigPath = "/client-config" (line 20) and + ApiSectionName = "Api" (line 23), the one section name ClientConfigBuilder + treats as reserved.
          • +
          • The extension(IEndpointRouteBuilder endpoints) block (line 25, see + primer) holds the single member + MapClientConfigEndpoint(Action<ClientConfigBuilder>? extras = null) (line 52).
          • +
          • The handler (lines 53-79) takes the request's HttpContext plus IOptions<ApiSettings>, reads + apiSettings.Value.WasmApiEndpoint (line 55), throws InvalidOperationException naming the missing + key when it is blank (lines 56-61), builds a ClientConfigBuilder for the + request and invokes extras on it (lines 63-64), seeds the response with the camelCased Api + section produced by BuildApiSection(context, wasmApiEndpoint) (lines 68-71), then overlays every + builder section, each name run through the same JsonNamingPolicy.CamelCase policy the web JSON + defaults apply to members (lines 73-76), so the document's shape matches what an anonymous-object + payload would have produced anyway.
          • +
          • The route is finished with .AllowAnonymous().ExcludeFromDescription() (lines 80-81): anonymous + for the reason above, and excluded from the OpenAPI description because it is not a data endpoint a + client integrates against, it is bootstrap plumbing.
          • +
          • The private BuildApiSection(HttpContext, string) (lines 91-100) returns new { ApiEndpoint } when + GetService<SameOriginApiProxyMarker>() is null (lines 93-96); otherwise it reads + IOptions<SameOriginApiProxySettings>.Value.PathPrefix (line 98) and returns + new { ApiEndpoint, SameOriginApiEndpoint = prefix + "/" } (line 99).
          • +
          +
        • +
        • Why it's built this way: putting the base-address selection, the reserved-name policy and the + camelCase conversion all in one mapped endpoint means every host that calls + MapClientConfigEndpoint gets the identical fail-closed contract; a host only supplies the pieces + specific to it through extras. The same-origin key is decided here too, from the proxy's own DI + marker, so a host that adds the proxy does not have to remember a second client-config change.
        • +
        • Where it's used: called from a host's endpoint mapping alongside the other framework endpoints; + its handler constructs ClientConfigBuilder + (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/ClientConfig/ClientConfigBuilder.cs, 3 call + sites) and is pinned by ClientConfigEndpointTests + (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Web.Tests/ClientConfig/ClientConfigEndpointTests.cs, 2 + call sites).
        • +
        +

        IRoleAdminUIService

        +
        +

        MMCA.Common.UI · MMCA.Common.UI.Services.Administration · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Administration/IRoleAdminUIService.cs:22 · Level 3 · interface

        +
        +
          +
        • What it is: the typed client contract for the role-admin editor: read every role's compiled and + stored permissions, read one role, read the catalog an editor renders from, and replace one role's + stored permission set.
        • +
        • Depends on: first-party: RolePermissionsResponse (the + per-role read and write shape), PermissionCatalogResponse + (the closed sets a role editor may choose from), and Result + / Result<T> (IRoleAdminUIService.cs:30,36,44,54) for every member's return type. Its sole + implementation is RoleAdminService.
        • +
        • Concept: each member's XML doc names the exact endpoint it calls (GET Admin/Roles, + GET Admin/Roles/{role}, GET Admin/Roles/catalog, PUT Admin/Roles/{role}/permissions, lines + 27,32,40,47-48), so the contract doubles as the wire map for the role-admin surface without a + separate reference. [Rubric §1, SOLID Principles] reads this as a small, role-scoped interface: + it names only what a role editor needs and nothing a user editor would, the mirror decision to the + split between IUserAdminActionsUIService and + IUserAdminUIService<TUserDto> one member table below.
        • +
        • Walkthrough: GetAllAsync (line 30) and GetAsync(string role, ...) (line 36) are the two reads + that feed the role list and role-detail pages. GetCatalogAsync (line 44) reads the roles-plus- + permissions catalog a role editor renders its checkboxes from. SetStoredPermissionsAsync(role, permissions, ...) (lines 54-57) replaces the complete stored permission set for one role in a single + PUT, not an incremental add/remove, so the caller always sends the full desired set.
        • +
        • Why it's built this way: this is an ADR-116 opt-in surface + (ADR-116): the admin app + can wire role management by registering RoleAdminService against this contract + without the framework hard-wiring an admin UI into every consumer.
        • +
        • Where it's used: implemented by RoleAdminService and registered against it in + DependencyInjection (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs); + consumed by RoleAdminEdit.razor.cs and RoleAdminList.razor.cs; its ADC identity pages carry the + behavior-pinning tests + (MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.UI.Tests/Pages/Roles/RoleEditTests.cs, + RoleListTests.cs).
        • +
        +

        IUserAdminActionsUIService

        +
        +

        MMCA.Common.UI · MMCA.Common.UI.Services.Administration · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Administration/IUserAdminActionsUIService.cs:16 · Level 3 · interface

        +
        +
          +
        • What it is: the account-mutation contract for user admin: lock, unlock, and two shapes of + role-replacement, a full-set SetRolesAsync and a single-role convenience SetRoleAsync.
        • +
        • Depends on: first-party: SetUserRolesRequest (implied wire + shape behind SetRolesAsync), Result + (IUserAdminActionsUIService.cs:22,26,33,41,56,58); UserIdentifierType (the module's identifier + alias) names every account parameter. Extended by + IUserAdminUIService<TUserDto>.
        • +
        • Concept, a member that exists only because a mock cannot proxy a default interface method. The + remarks on SetRoleAsync (lines 108-111) give the reasoning directly: the obvious shape would have + been a default interface implementation over SetRolesAsync that wraps the single role in a + one-element list, but a default implementation on an interface is not virtual to a mock proxy, so a + test could neither stub nor verify it if it were written that way. Declaring SetRoleAsync as its + own interface member keeps it mockable at the cost of one more line on the contract. + [Rubric §14, Testability] is the rubric this decision serves.
        • +
        • Walkthrough: LockAsync(userId, ...) (line 84) and UnlockAsync(userId, ...) (line 90) are the + two session-revoking actions. SetRolesAsync(userId, roles, ...) (lines 97-100) replaces the complete + role set an account holds. SetRoleAsync(userId, role, ...) (line 116) is documented as sending a + one-element set to the same replacement endpoint (lines 102-106), the shape both shipped consumers + need because each holds exactly one role per account.
        • +
        • Why it's built this way: separated from the read/page surface in + IUserAdminUIService<TUserDto> so the mutating actions have one + narrow, generic-free contract a page or a test can depend on without also depending on the DTO type + parameter.
        • +
        • Where it's used: extended by IUserAdminUIService<TUserDto>; + implemented by UserAdminService<TUserDto>; called directly from + UserAdminList.razor.cs for the lock/unlock/role actions on the user-admin grid, and pinned by + UserListTests.cs (ADC) and UserAdminListTests.cs (Common).
        • +

        IUserAdminUIService<TUserDto>

        MMCA.Common.UI · MMCA.Common.UI.Services.Administration · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Administration/IUserAdminUIService.cs:18 · Level 4 · interface

        @@ -6236,35 +7032,6 @@

        RoleAdminService

        (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs). See ADR-116.
      -

      ServerTokenStorageService

      -
      -

      MMCA.Common.UI.Web · MMCA.Common.UI.Web.Services · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Services/ServerTokenStorageService.cs:18 · Level 4 · class (sealed)

      -
      -
        -
      • What it is: the Blazor Server implementation of ITokenStorageService: a cookie-only token store with no localStorage. During SSR prerender it reads the access token from the HttpOnly session cookie; on the live interactive circuit it holds the access token in memory only and re-acquires it from those cookies through a same-origin refresh endpoint. The refresh token is never readable from JavaScript.
      • -
      • Depends on: first-party: ITokenStorageService (the contract, line 21), CookieTokenReader (reads the access and refresh cookies off the request), ISessionCookieSync (seeds and clears the HttpOnly cookies), ITokenRefresher (acquires a fresh access token from /auth/session/token), and JwtTokenInfo (client-side freshness check). Its WASM sibling is WasmTokenStorageService (named in the class doc, line 14). Externals: Microsoft.AspNetCore.Http (IHttpContextAccessor, HttpContext), BCL Lock, Task, TimeSpan.
      • -
      • Concept introduced, the two-world token store (SSR request versus interactive circuit). A Blazor Web page runs twice: first as a server-side prerender inside a live HTTP request, where an HttpContext exists and JS interop does not, and then as a stateful circuit with no HttpContext. One store has to serve both worlds, and this class branches on httpContextAccessor.HttpContext is not null (line 34) to decide which source of truth applies.
          -
        • SSR prerender (lines 34-37): the request's HttpOnly cookie wins, read via cookieTokenReader.ReadAccessToken() (line 36), because the middleware may have just refreshed it in place on this navigation (comment, lines 32-33).
        • -
        • Interactive circuit (lines 39-71): the token lives in the _accessToken field (line 27). If JwtTokenInfo.IsFresh(_accessToken, ExpirySkew) (line 40) says it survives the 30-second skew (line 23), it is returned as is; otherwise it is re-acquired.
        • -
        • [Rubric §26, Front-End Security] assesses how credentials are held in the browser. This is a deliberate XSS-hardening design: the long-lived refresh token stays in an HttpOnly cookie unreachable from script, the access token exists only in circuit memory and is never persisted, and the refresh token transits JS exactly once, for the same-origin POST that seeds the cookies at login (SetTokensAsync, lines 81-87).
        • -
        • [Rubric §11, Security] assesses the wider auth model; this store is one edge of the browser session-cookie design (ADR-022), the piece that decides where a bearer token is read on each side of the prerender boundary.
        • -
        • [Rubric §12, Performance & Scalability] shows up in the single-flight hydrate: a Blazor circuit is genuinely multi-threaded, and several consumers (the delegating handler, the auth-state provider, the SignalR connection) can all miss the cache at once.
        • -
        -
      • -
      • Walkthrough
          -
        • The primary constructor (lines 17-21) takes IHttpContextAccessor, CookieTokenReader, ISessionCookieSync, and ITokenRefresher. The type is sealed and carries no app-specific state, which is why it could be hoisted out of both app hosts (class doc, lines 13-15).
        • -
        • Fields: ExpirySkew (line 23, a static readonly TimeSpan of 30 seconds), the Lock _hydrateSync (line 25, the .NET 9+ dedicated lock object), the in-memory _accessToken (line 27), and _hydrateInFlight (line 28), the shared acquisition task.
        • -
        • GetAccessTokenAsync (lines 30-72): the SSR/circuit branch, then the single-flight guard. _hydrateInFlight ??= HydrateAsync() executes inside lock (_hydrateSync) (lines 50-54) and the resulting task is copied to a local before the lock is released. The comment on lines 45-48 records exactly why the naive unguarded ??= was not enough: two callers could each start a hydrate and the later completion would overwrite the other's token; HydrateAsync reaches its first await immediately, so nothing slow runs under the lock. The finally (lines 60-71) clears _hydrateInFlight only when it is still reference-equal to the task this caller awaited (line 66), so a newer hydrate started after this one completed is not dropped, which would split the next set of callers again.
        • -
        • GetRefreshTokenAsync (lines 74-79): returns cookieTokenReader.ReadRefreshToken() during SSR and null on the circuit, because the HttpOnly refresh cookie is unreadable there; it wraps the value in Task.FromResult rather than being async (no await needed).
        • -
        • SetTokensAsync (lines 81-87): caches the access token in memory (line 83) and calls sessionCookieSync.SyncAsync(accessToken, refreshToken) (line 86) to seed the HttpOnly cookies at login.
        • -
        • ClearTokensAsync (lines 89-93): nulls the in-memory token and calls sessionCookieSync.ClearAsync() on logout.
        • -
        • HydrateAsync (lines 95-99): the private acquisition, _accessToken = await tokenRefresher.AcquireAccessTokenAsync() (line 97), caching and returning the new token.
        • -
        -
      • -
      • Why it's built this way: Blazor Server's split lifecycle breaks the naive "read a token from storage" store, which would either fail during prerender (no JS) or leak the refresh token to script if it used localStorage. Branching on HttpContext presence and keeping the refresh token cookie-only resolves both. The locked single-flight is the correction of a real concurrency defect in the simpler ??= version, and it is worth reading as a small case study in why "good enough" atomicity on a circuit is not good enough. See ADR-022.
      • -
      • Where it's used: registered as the scoped ITokenStorageService by AddCommonServerTokenStorage() in the MMCA.Common.UI.Web DependencyInjection (MMCA.Common.UI.Web/DependencyInjection.cs:32-36); consumer hosts call that instead of shipping their own copy.
      • -
      • Caveats / not-in-source: the cookie names, lifetimes, and the /auth/session/token endpoint itself are not in this file; they live in the MMCA.Common.API session-cookie plumbing referenced by the doc comment (lines 12-15).
      • -

      UserAdminService<TUserDto>

      MMCA.Common.UI · MMCA.Common.UI.Services.Administration · MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Administration/UserAdminService.cs:26 · Level 5 · class (sealed)

      @@ -6319,28 +7086,28 @@

      NotificationBell

      • What it is: the code-behind for the app-bar notification bell. It renders an unread badge from the scoped NotificationState, and exactly one instance at a time holds the single active-poller slot, so a bell placed in two layout slots never doubles the API traffic.
      • Depends on: first-party: NotificationState (badge value, change and refresh events, staleness clock, and the poller slot), INotificationInboxUIService (the unread-count call), NotificationBellOptions (the two intervals), NotificationRoutePaths (the inbox route), and SharedResource (the resx anchor for the injected localizer). Externals: Microsoft.AspNetCore.Components ([Inject], NavigationManager, LocationChangedEventArgs, OnAfterRenderAsync, InvokeAsync, StateHasChanged), Microsoft.Extensions.Options (IOptions<T>), Microsoft.Extensions.Localization (IStringLocalizer<T>), BCL TimeProvider, PeriodicTimer, CancellationTokenSource, IDisposable.
      • -
      • Concept introduced, a poller slot with symmetric registration and handover. [Rubric §19, State Management & Data Flow] assesses how shared UI state is coordinated. The bell is a component, so a responsive layout can legitimately render it twice at once (desktop app bar and mobile drawer). Without coordination each copy would start its own timer and its own navigation refresh, doubling the unread-count endpoint's load for no user benefit. The design is a slot held by an owner object, not a bare counter: State.TryRegisterPoller(this) (line 56) takes the slot only when it is free or already this instance's (NotificationState.cs:111-124), and State.UnregisterPoller(this) releases it only when this instance actually holds it and then raises OnPollerSlotFreed (NotificationState.cs:133-149).
          -
        • Why owner identity matters here. The class remarks (lines 22-28) name the real scenario: hosts render the bell inside <AuthorizeView>, which tears the children down and rebuilds them on every authentication-state change, including a routine access-token refresh. Registration is therefore strictly symmetric (every instance unregisters on dispose, whether or not it was polling, line 256), and the surviving instance claims the freed slot through OnPollerSlotFreed (line 53), so the circuit never ends up with a badge that nobody refreshes.
        • -
        • [Rubric §23, Front-End Performance] assesses avoidable network work. Two mechanisms cut it. The slot removes an entire duplicate polling stream in dual-placement layouts. And the navigation trigger is throttled by staleness rather than firing per click: OnLocationChanged reads only when State.IsStale(Options.Value.NavigationRefreshMaxAge) (line 155), because navigation is an ambient trigger, not evidence that the count moved (doc, lines 143-148). The defaults are 30 seconds for both the poll interval and the navigation max age (MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/Settings/NotificationBellOptions.cs:22,29), and both are configuration, not constants.
        • -
        • The staleness policy has exactly three tiers, and they are deliberate. The first read and every periodic tick are unconditional; a navigation reads only past the configured window; a real-time push calls State.MarkStale() first and then reads regardless (lines 171-175), because the server has just said the data changed, so the age of the number carries no information any more (doc, lines 165-169). That is the one path that must never be throttled.
        • -
        • [Rubric §14, Testability] assesses whether time-dependent behavior can be driven deterministically. Both the timer and the age comparison run off the injected TimeProvider Clock (line 37), and the PeriodicTimer is constructed with the TimeProvider overload (line 92) precisely so a test drives the loop instead of waiting out a real interval (comment, lines 90-91).
        • +
        • Concept introduced, a poller slot with symmetric registration and handover. [Rubric §19, State Management & Data Flow] assesses how shared UI state is coordinated. The bell is a component, so a responsive layout can legitimately render it twice at once (desktop app bar and mobile drawer). Without coordination each copy would start its own timer and its own navigation refresh, doubling the unread-count endpoint's load for no user benefit. The design is a slot held by an owner object, not a bare counter: State.TryRegisterPoller(this) (line 55) takes the slot only when it is free or already this instance's (NotificationState.cs:111-124), and State.UnregisterPoller(this) releases it only when this instance actually holds it and then raises OnPollerSlotFreed (NotificationState.cs:133-149).
            +
          • Why owner identity matters here. The class remarks (lines 22-29) name the real scenario: hosts render the bell inside <AuthorizeView>, which tears the children down and rebuilds them on every authentication-state change, including a routine access-token refresh. Registration is therefore strictly symmetric (every instance unregisters on dispose, whether or not it was polling, line 267), and the surviving instance claims the freed slot through OnPollerSlotFreed (subscribed at line 52), so the circuit never ends up with a badge that nobody refreshes.
          • +
          • [Rubric §23, Front-End Performance] assesses avoidable network work. Three mechanisms cut it. The slot removes an entire duplicate polling stream in dual-placement layouts, and it owns every refresh trigger, not just the timer: the push-refresh subscription State.OnRefreshRequested += HandleRefreshRequested is made inside BecomeActivePollerAsync (line 80), next to the navigation hook, because subscribing every bell made each push read the API once per placement (comment, lines 77-79); a takeover runs that method again, so the surviving bell picks the subscription up with the slot. And the navigation trigger is throttled by staleness rather than firing per click: OnLocationChanged reads only when State.IsStale(Options.Value.NavigationRefreshMaxAge) (line 166), because navigation is an ambient trigger, not evidence that the count moved (doc, lines 154-159). The defaults are 30 seconds for both the poll interval and the navigation max age (MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/Settings/NotificationBellOptions.cs:23,30), and both are configuration, not constants. A zero or negative PollInterval switches the periodic backstop off entirely (lines 96-99) while push and navigation refresh keep working; the comment (lines 94-95) records that PeriodicTimer rejects such a period and throwing there faulted the circuit.
          • +
          • The staleness policy has exactly three tiers, and they are deliberate. The first read and every periodic tick are unconditional; a navigation reads only past the configured window; a real-time push calls State.MarkStale() first and then reads regardless (lines 182-186), because the server has just said the data changed, so the age of the number carries no information any more (doc, lines 177-179). That is the one path that must never be throttled.
          • +
          • [Rubric §14, Testability] assesses whether time-dependent behavior can be driven deterministically. Both the timer and the age comparison run off the injected TimeProvider Clock (line 37), and the PeriodicTimer is constructed with the TimeProvider overload (line 103) precisely so a test drives the loop instead of waiting out a real interval (comment, lines 101-102).
          • [Rubric §21, Accessibility]: the bell button carries an explicit localized aria-label="@L["Notif.Bell.Aria"]" in the markup (NotificationBell.razor:10), which is what the injected IStringLocalizer<SharedResource> L (line 35) is for.
          • -
          • Fire-and-forget from a synchronous event handler. HandlePollerSlotFreed (lines 98-99), OnLocationChanged (lines 153-159), and HandleRefreshRequested (lines 171-175) are all EventHandler-shaped, so they cannot be async Task. Rather than async void (which turns an unobserved exception into a process crash, VSTHRD100, per the comments at lines 96-97 and 149-152), they discard the task with _ = and rely on the callee observing its own failures.
          • +
          • Fire-and-forget from a synchronous event handler. HandlePollerSlotFreed (lines 109-110), OnLocationChanged (lines 164-170), and HandleRefreshRequested (lines 182-186) are all EventHandler-shaped, so they cannot be async Task. Rather than async void (which turns an unobserved exception into a process crash, VSTHRD100, per the comments at lines 107-108 and 160-163), they discard the task with _ = and rely on the callee observing its own failures.
        • Walkthrough
          • Injected members (lines 32-37): State, InboxService, NavigationManager, L, IOptions<NotificationBellOptions> Options, and TimeProvider Clock. Fields (lines 39-42): a per-component CancellationTokenSource _cts, the PeriodicTimer? _pollTimer, and the two flags _isActivePoller and _disposed.
          • -
          • OnAfterRenderAsync(bool firstRender) (lines 44-60) returns immediately on subsequent renders (lines 46-49), subscribes to all three state events (lines 51-53), then tries for the slot and, on success, calls BecomeActivePollerAsync() (lines 56-59).
          • -
          • BecomeActivePollerAsync (lines 68-94) is the double-start-guarded start: it bails on _disposed || _isActivePoller (lines 70-73), latches _isActivePoller, hooks LocationChanged (line 76), does the unconditional first read (line 79), then re-checks _disposed after that await (lines 85-88). The comment (lines 81-84) explains what that guard prevents: Dispose may have run during the read, having already disposed the then-null timer and the token source, so starting the loop now would leak a PeriodicTimer nothing disposes and fault the discarded task on a disposed _cts. Only then does it create the timer from Options.Value.PollInterval and the injected clock (line 92) and launch PollLoopAsync with an explicit discard (line 93). The method doc (lines 62-67) notes it always runs on the renderer's synchronization context, which is what makes the simple _isActivePoller guard sufficient rather than needing an interlocked operation.
          • -
          • TryTakeOverPollingAsync (lines 105-121) is the handover path: it re-checks the guards and claims the slot (line 107), then marshals the actual start onto this component's renderer with InvokeAsync(BecomeActivePollerAsync) (line 114), because the event was raised synchronously from the disposing bell's thread (doc, lines 101-104). If that dispatch hits ObjectDisposedException it hands the slot straight back (lines 116-120) so another bell can claim it.
          • -
          • PollLoopAsync (lines 123-141) awaits _pollTimer!.WaitForNextTickAsync(_cts.Token) in a loop (line 127) and refreshes each tick. It catches OperationCanceledException (the expected disposal exit) and ObjectDisposedException (disposed between timer creation and the first wait, where reading _cts.Token throws rather than cancelling, comment lines 137-139).
          • -
          • RefreshUnreadCountAsync (lines 177-216) is the one place that touches the network: it bails when _disposed (lines 179-182), calls InboxService.GetUnreadCountAsync(_cts.Token) (line 186), and returns without touching the badge when the result is a failure (lines 187-193). That early return is load-bearing: the comment (lines 189-192) records that zeroing the badge on an unknown count is what used to erase a push increment, and that a failed read is silent by design because the bell has no surface to report it on. On success it re-checks _disposed and marshals State.SetUnreadCount(unread) plus StateHasChanged() back onto the renderer with InvokeAsync (lines 195-202). Three catch tiers follow (lines 204-215): cancellation, disposal during the async gap, and a bare catch for network or deserialization failures where the badge keeps its last value. That catch-all is what makes the discards above safe.
          • -
          • HandleStateChanged (lines 218-219) discards into RerenderSafeAsync (lines 221-236), which re-renders through InvokeAsync(StateHasChanged) and tolerates a dispose landing between the event firing and the render dispatch.
          • -
          • NavigateToInbox (line 238) sends the click to NotificationRoutePaths.NotificationInbox.
          • -
          • Dispose(bool disposing) (lines 240-261) sets _disposed first (line 247), unsubscribes all three state events and LocationChanged (lines 248-251), then calls State.UnregisterPoller(this) unconditionally (line 256). The comment (lines 253-255) states both halves of why that is safe: a bell that claimed the slot but was torn down before it started polling still frees it, and a bell that never held it cannot evict the live poller, because the state object checks owner identity. It then disposes the timer and cancels and disposes the _cts (lines 258-260). Dispose() (lines 263-267) is the public half with GC.SuppressFinalize.
          • +
          • OnAfterRenderAsync(bool firstRender) (lines 44-59) returns immediately on subsequent renders (lines 46-49), subscribes every instance to two state events, OnChange and OnPollerSlotFreed (lines 51-52), then tries for the slot and, on success, calls BecomeActivePollerAsync() (lines 55-58).
          • +
          • BecomeActivePollerAsync (lines 67-105) is the double-start-guarded start: it bails on _disposed || _isActivePoller (lines 69-72), latches _isActivePoller, hooks LocationChanged (line 75) and OnRefreshRequested (line 80), does the unconditional first read (line 83), then re-checks _disposed after that await (lines 89-92). The comment (lines 85-88) explains what that guard prevents: Dispose may have run during the read, having already disposed the then-null timer and the token source, so starting the loop now would leak a PeriodicTimer nothing disposes and fault the discarded task on a disposed _cts. It then returns without a timer when PollInterval <= TimeSpan.Zero (lines 96-99). Only then does it create the timer from Options.Value.PollInterval and the injected clock (line 103) and launch PollLoopAsync with an explicit discard (line 104). The method doc (lines 61-66) notes it always runs on the renderer's synchronization context, which is what makes the simple _isActivePoller guard sufficient rather than needing an interlocked operation.
          • +
          • TryTakeOverPollingAsync (lines 116-132) is the handover path: it re-checks the guards and claims the slot (line 118), then marshals the actual start onto this component's renderer with InvokeAsync(BecomeActivePollerAsync) (line 125), because the event was raised synchronously from the disposing bell's thread (doc, lines 112-115). If that dispatch hits ObjectDisposedException it hands the slot straight back (lines 127-131) so another bell can claim it.
          • +
          • PollLoopAsync (lines 134-152) awaits _pollTimer!.WaitForNextTickAsync(_cts.Token) in a loop (line 138) and refreshes each tick. It catches OperationCanceledException (the expected disposal exit) and ObjectDisposedException (disposed between timer creation and the first wait, where reading _cts.Token throws rather than cancelling, comment lines 149-150).
          • +
          • RefreshUnreadCountAsync (lines 188-227) is the one place that touches the network: it bails when _disposed (lines 190-193), calls InboxService.GetUnreadCountAsync(_cts.Token) (line 197), and returns without touching the badge when the result is a failure (lines 198-204). That early return is load-bearing: the comment (lines 200-202) records that zeroing the badge on an unknown count is what used to erase a push increment, and that a failed read is silent by design because the bell has no surface to report it on. On success it re-checks _disposed and marshals State.SetUnreadCount(unread) plus StateHasChanged() back onto the renderer with InvokeAsync (lines 206-213). Three catch tiers follow (lines 215-226): cancellation, disposal during the async gap, and a bare catch for network or deserialization failures where the badge keeps its last value. That catch-all is what makes the discards above safe.
          • +
          • HandleStateChanged (lines 229-230) discards into RerenderSafeAsync (lines 232-247), which re-renders through InvokeAsync(StateHasChanged) and tolerates a dispose landing between the event firing and the render dispatch.
          • +
          • NavigateToInbox (line 249) sends the click to NotificationRoutePaths.NotificationInbox.
          • +
          • Dispose(bool disposing) (lines 251-272) sets _disposed first (line 258), unsubscribes all three state events and LocationChanged (lines 259-262; removing a handler that a non-poller never added is a no-op), then calls State.UnregisterPoller(this) unconditionally (line 267). The comment (lines 264-266) states both halves of why that is safe: a bell that claimed the slot but was torn down before it started polling still frees it, and a bell that never held it cannot evict the live poller, because the state object checks owner identity. It then disposes the timer and cancels and disposes the _cts (lines 269-271). Dispose() (lines 274-278) is the public half with GC.SuppressFinalize.
        • -
        • Why it's built this way: a live unread badge is a genuinely useful affordance, but a naive implementation is a request amplifier (one timer per rendered copy, per circuit) and a fragile one under <AuthorizeView> churn. Owner-identity registration plus a freed-slot event keeps the affordance, removes the amplification, and survives the teardown-rebuild cycle that a plain counter would leave stuck. Making both intervals options and both clocks injected turns the polling policy into something a host can tune and a test can drive.
        • +
        • Why it's built this way: a live unread badge is a genuinely useful affordance, but a naive implementation is a request amplifier (one timer and one push read per rendered copy, per circuit) and a fragile one under <AuthorizeView> churn. Owner-identity registration plus a freed-slot event keeps the affordance, removes the amplification, and survives the teardown-rebuild cycle that a plain counter would leave stuck. Making both intervals options and both clocks injected turns the polling policy into something a host can tune and a test can drive.
        • Where it's used: contributed to the shell as an app-bar component by NotificationUIModule (NotificationUIModule.cs:23); it reads the same NotificationState that NotificationInbox writes after a mark-read, so the badge stays consistent with the inbox without either component knowing about the other.

        NotificationUIModule

        @@ -6382,7 +7149,57 @@

        DependencyInjection

      • Walkthrough: inside the extension block (line 14), AddNotificationUI() (line 20) registers, in order, INotificationScopeProvider to NullNotificationScopeProvider via TryAddScoped (line 24, the default no-op scope consumed by both HTTP services and read for the caption on NotificationSend), IPushNotificationUIService to PushNotificationService (scoped, line 27), INotificationInboxUIService to NotificationInboxService (scoped, line 30), NotificationState as a concrete scoped type (line 33, one unread-count owner per Blazor circuit), NotificationHubService (scoped SignalR client, line 36), and finally NotificationUIModule as a singleton IUIModule (line 39), because the descriptor is immutable shell metadata rather than per-circuit state. It returns services for chaining (line 41).
      • Why it's built this way: the scoped-versus-singleton split is the load-bearing part. HTTP services, state, and the hub connection are per-circuit (a Blazor circuit is a DI scope, and the unread count belongs to one user's session), while the nav and shell descriptor is process-wide and immutable. Bundling all six behind one extension keeps host startup honest and makes the feature's dependency surface reviewable in one screen.
      • Where it's used: called from the Program.cs of each consuming host (Blazor Web and MAUI) that opts into the notification UI; it complements the main MMCA.Common.UI registration rather than replacing it.
      • -
      • Caveats / not-in-source: this method does not register NotificationBellOptions. The bell injects IOptions<NotificationBellOptions> (NotificationBell.razor.cs:36) and the options type supplies its own defaults (NotificationBellOptions.cs:22,29), so the unconfigured case works, but where a host binds the NotificationBell configuration section is not visible from this file.
      • +
      • Caveats / not-in-source: this method does not register NotificationBellOptions. The bell injects IOptions<NotificationBellOptions> (NotificationBell.razor.cs:36) and the options type supplies its own defaults (NotificationBellOptions.cs:23,30), so the unconfigured case works, but where a host binds the NotificationBell configuration section is not visible from this file.
      • +
      +

      NotificationPageGate

      +
      +

      MMCA.Common.UI · MMCA.Common.UI.Notifications · MMCA.Common/Source/Presentation/MMCA.Common.UI/Notifications/NotificationPageGate.cs:12 · Level 8 · class (internal, static)

      +
      +
        +
      • What it is: the one-method predicate the framework router consults to decide whether a matched notification page should be answered with the not-found page instead, because the host opted in and never registered the notification UI (class doc, lines 7-11).
      • +
      • Depends on: first-party: LayoutSettings (the HideNotificationPagesWhenUnregistered opt-in, MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/Settings/LayoutSettings.cs:49), IUIModule (the registered modules it scans), NotificationUIModule (the module whose presence means "registered"), and the three routable pages it guards, NotificationList, NotificationInbox and NotificationSend. Externals: BCL System.Type and LINQ (Contains, Any).
      • +
      • Concept introduced, a feature's routes follow its registration. [Rubric §25, Navigation, Routing & Information Architecture] assesses whether what is reachable by URL matches what the host actually composed. The notification pages ship in the MMCA.Common.UI assembly itself, so they are routable in every host whether or not it called AddNotificationUI(); a host that never registered the feature would serve pages whose services it never registered, which fail at render. The UI module pattern (ADR-067) already makes the NotificationUIModule singleton the evidence of registration, so the gate reads that rather than probing for individual services.
          +
        • Opt-in, not a behavior change. HideNotificationPagesWhenUnregistered defaults to false, which leaves the routes exactly as they are, and a host that registers the notification UI is unaffected either way (LayoutSettings.cs:38-49). Only a host that sets the flag and lacks the module gets the not-found answer.
        • +
        +
      • +
      • Walkthrough
          +
        • NotificationPages (line 15) is a private static Type[] of typeof(NotificationList), typeof(NotificationInbox) and typeof(NotificationSend), the routable pages that need the services AddNotificationUI() registers.
        • +
        • Hides(Type pageType, LayoutSettings settings, IEnumerable<IUIModule> modules) (lines 22-25) is a single short-circuiting conjunction: the flag is on (line 23), the matched page is one of the three (line 24), and no registered module is NotificationUIModule (line 25). The cheap flag check comes first, so a host that never opts in pays nothing beyond a boolean read per route match.
        • +
        +
      • +
      • Why it's built this way: keeping the decision in a small internal static predicate, with the page type, settings and module list passed in, keeps Routes.razor to a one-line branch and lets a unit test cover every combination without rendering a router. Answering with the framework's own not-found page inside the main layout gives the user the same experience as any unknown URL instead of a render failure.
      • +
      • Where it's used: called once per route match from the <Found> branch of the framework router, NotificationPageGate.Hides(routeData.PageType, LayoutOptions.Value, UIModules) (MMCA.Common/Source/Presentation/MMCA.Common.UI/Routes.razor:16), which renders Pages.NotFound inside MainLayout when it returns true; pinned by NotificationPageGateTests (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Notifications/NotificationPageGateTests.cs, 4 references).
      • +
      • Caveats / not-in-source: the LayoutSettings doc lists four routes (/notifications, /notifications/inbox, /notifications/inbox/{Id}, /notifications/send) while the gate matches three page types, so the inbox detail route is presumably served by one of those three pages; which page carries it is not visible from these files.
      • +
      +

      ServerTokenStorageService

      +
      +

      MMCA.Common.UI.Web · MMCA.Common.UI.Web.Services · MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Services/ServerTokenStorageService.cs:18 · Level 13 · class (sealed)

      +
      +
        +
      • What it is: the Blazor Server implementation of ITokenStorageService: a cookie-only token store with no localStorage. During SSR prerender it reads the access token from the HttpOnly session cookie; on the live interactive circuit it holds the access token in memory only and re-acquires it from those cookies through a same-origin refresh endpoint. The refresh token is never readable from JavaScript.
      • +
      • Depends on: first-party: ITokenStorageService (the contract, line 22), CookieTokenReader (reads the access and refresh cookies off the request), ISessionCookieSync (seeds and clears the HttpOnly cookies, and reports whether the write succeeded), ITokenRefresher (acquires a fresh access token from /auth/session/token), and JwtTokenInfo (client-side freshness check). Its WASM sibling is WasmTokenStorageService (named in the class doc, line 15). Externals: Microsoft.AspNetCore.Http (IHttpContextAccessor, HttpContext), BCL Lock, Task, TimeSpan, InvalidOperationException.
      • +
      • Concept introduced, the two-world token store (SSR request versus interactive circuit). A Blazor Web page runs twice: first as a server-side prerender inside a live HTTP request, where an HttpContext exists and JS interop does not, and then as a stateful circuit with no HttpContext. One store has to serve both worlds, and this class branches on httpContextAccessor.HttpContext is not null (line 35) to decide which source of truth applies.
          +
        • SSR prerender (lines 35-38): the request's HttpOnly cookie wins, read via cookieTokenReader.ReadAccessToken() (line 37), because the middleware may have just refreshed it in place on this navigation (comment, lines 33-34).
        • +
        • Interactive circuit (lines 40-72): the token lives in the _accessToken field (line 28). If JwtTokenInfo.IsFresh(_accessToken, ExpirySkew) (line 41) says it survives the 30-second skew (line 24), it is returned as is; otherwise it is re-acquired.
        • +
        • [Rubric §26, Front-End Security] assesses how credentials are held in the browser. This is a deliberate XSS-hardening design: the long-lived refresh token stays in an HttpOnly cookie unreachable from script, the access token exists only in circuit memory and is never persisted, and the refresh token transits JS exactly once, for the same-origin POST that seeds the cookies at login (SetTokensAsync, lines 82-93).
        • +
        • [Rubric §11, Security] assesses the wider auth model; this store is one edge of the browser session-cookie design (ADR-022) and of the client token lifecycle (ADR-051), the piece that decides where a bearer token is read on each side of the prerender boundary.
        • +
        • Login fails loudly, logout stays best-effort. The two cookie writes are deliberately asymmetric. SetTokensAsync throws InvalidOperationException("The session cookie could not be written.") when SyncAsync returns false (lines 89-92), after the in-memory token is already set, so AuthUIService reports Auth.TokenStorageUnavailable instead of a login that looks successful and then silently signs out at the first access-token expiry, when no cookie exists to refresh from (comment, lines 85-88). ClearTokensAsync discards the boolean (_ = await sessionCookieSync.ClearAsync(), line 101), because logout is best-effort by contract (IAuthUIService.LogoutAsync never fails); a caller that needs proof the session ended uses IAuthUIService.RevokeAllSessionsAsync (comment, lines 98-100).
        • +
        • [Rubric §12, Performance & Scalability] shows up in the single-flight hydrate: a Blazor circuit is genuinely multi-threaded, and several consumers (the delegating handler, the auth-state provider, the SignalR connection) can all miss the cache at once.
        • +
        +
      • +
      • Walkthrough
          +
        • The primary constructor (lines 18-22) takes IHttpContextAccessor, CookieTokenReader, ISessionCookieSync, and ITokenRefresher. The type is sealed and carries no app-specific state, which is why it could be hoisted out of both app hosts (class doc, lines 14-16).
        • +
        • Fields: ExpirySkew (line 24, a static readonly TimeSpan of 30 seconds), the Lock _hydrateSync (line 26, the .NET 9+ dedicated lock object), the in-memory _accessToken (line 28), and _hydrateInFlight (line 29), the shared acquisition task.
        • +
        • GetAccessTokenAsync (lines 31-73): the SSR/circuit branch, then the single-flight guard. _hydrateInFlight ??= HydrateAsync() executes inside lock (_hydrateSync) (lines 51-55) and the resulting task is copied to a local before the lock is released. The comment on lines 46-49 records exactly why the naive unguarded ??= was not enough: two callers could each start a hydrate and the later completion would overwrite the other's token; HydrateAsync reaches its first await immediately, so nothing slow runs under the lock. The finally (lines 61-72) clears _hydrateInFlight only when it is still reference-equal to the task this caller awaited (line 67), so a newer hydrate started after this one completed is not dropped, which would split the next set of callers again.
        • +
        • GetRefreshTokenAsync (lines 75-80): returns cookieTokenReader.ReadRefreshToken() during SSR and null on the circuit, because the HttpOnly refresh cookie is unreadable there; it wraps the value in Task.FromResult rather than being async (no await needed).
        • +
        • SetTokensAsync (lines 82-93): caches the access token in memory (line 84), then awaits sessionCookieSync.SyncAsync(accessToken, refreshToken) and throws when it reports failure (lines 89-92).
        • +
        • ClearTokensAsync (lines 95-102): nulls the in-memory token (line 97) and awaits sessionCookieSync.ClearAsync() with its result explicitly discarded (line 101).
        • +
        • HydrateAsync (lines 104-108): the private acquisition, _accessToken = await tokenRefresher.AcquireAccessTokenAsync().ConfigureAwait(false) (line 106), caching and returning the new token. Both awaits on the hydrate path use ConfigureAwait(false) (lines 59, 106), the library posture the cookie-write awaits do not take.
        • +
        +
      • +
      • Why it's built this way: Blazor Server's split lifecycle breaks the naive "read a token from storage" store, which would either fail during prerender (no JS) or leak the refresh token to script if it used localStorage. Branching on HttpContext presence and keeping the refresh token cookie-only resolves both. The locked single-flight is the correction of a real concurrency defect in the simpler ??= version, and it is worth reading as a small case study in why "good enough" atomicity on a circuit is not good enough. Surfacing a failed cookie write at login, while tolerating one at logout, puts the failure where the user can still act on it. See ADR-022.
      • +
      • Where it's used: registered as the scoped ITokenStorageService by AddCommonServerTokenStorage() in the MMCA.Common.UI.Web DependencyInjection (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/DependencyInjection.cs:32-35); consumer hosts call that instead of shipping their own copy. Pinned by ServerTokenStorageServiceTests (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Web.Tests/Services/ServerTokenStorageServiceTests.cs).
      • +
      • Caveats / not-in-source: the cookie names, lifetimes, and the /auth/session/token endpoint itself are not in this file; they live in the MMCA.Common.API session-cookie plumbing referenced by the doc comment (lines 9-17).

      DependencyInjection

      diff --git a/docs/onboarding/group-16-aspire-orchestration.html b/docs/onboarding/group-16-aspire-orchestration.html index 62360886..d6d0c76f 100644 --- a/docs/onboarding/group-16-aspire-orchestration.html +++ b/docs/onboarding/group-16-aspire-orchestration.html @@ -147,7 +147,7 @@

      Onboarding guide

      16. Aspire Orchestration & Service Defaults

      -

      This chapter covers the hosting boundary: how the distributed MMCA system is composed and run, locally as a single dotnet run, and in Azure Container Apps (ACA) as a set of independent revisions. Four distinct concerns live side by side here, and it pays to separate them up front. AppHost-side code (MMCA.Common.Aspire.Hosting, MMCA.ADC.AppHost) is the orchestrator: it declares the resource graph, containers, databases, broker, the four services, the gateway, the UI, and wires their dependencies. Service-side code (MMCA.Common.Aspire) is the baseline every running process opts into: OpenTelemetry, health checks, service discovery, HTTP resilience, Kestrel listener profiles, startup warm-up, Redis wiring, Serilog, vault-backed configuration, a shared DataProtection key ring, and hardened security headers. Edge-side code (MMCA.Common.Aspire.Gateway) is the small kit a reverse-proxy host adds on top: correlation, an edge rate limiter, and per-downstream readiness. YARP building blocks (MMCA.Common.Gateway) are the fourth: cluster request profiles, destination health-check defaults, route/cluster trace headers, named per-route limiter policies, and the forwarded-headers step, all applied on top of whatever route table the host loaded. The AppHost and service assemblies are deliberately kept apart so a running service never drags in the full Aspire.Hosting tooling graph (MMCA.Common/Source/Hosting/MMCA.Common.Aspire.Hosting/Extensions.cs:13-17), and the service-side package carries exactly one first-party project reference, MMCA.Common.Shared, taken solely so the resilience constants have one home (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/MMCA.Common.Aspire.csproj:75). The gateway package goes further: it has no project reference at all and one runtime package, YARP (MMCA.Common/Source/Hosting/MMCA.Common.Gateway/MMCA.Common.Gateway.csproj:15), because the host it serves has no application container to share. ADC has no app-local ServiceDefaults project: all four services, the Gateway, and the Blazor UI host consume MMCA.Common.Aspire's AddServiceDefaults() directly (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:105, MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:99, MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:84, MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:90, MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/Program.cs:58, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:51). Everything in this group is plumbing, but it is the plumbing that makes ADR-006 (database-per-service), ADR-008 (service topology + YARP), ADR-009 (resilience / RTO-RPO), and ADR-041 (observability and telemetry) real at runtime rather than aspirational.

      +

      This chapter covers the hosting boundary: how the distributed MMCA system is composed and run, locally as a single dotnet run, and in Azure Container Apps (ACA) as a set of independent revisions. Four distinct concerns live side by side here, and it pays to separate them up front. AppHost-side code (MMCA.Common.Aspire.Hosting, MMCA.ADC.AppHost) is the orchestrator: it declares the resource graph, containers, databases, broker, the four services, the gateway, the UI, and wires their dependencies. Service-side code (MMCA.Common.Aspire) is the baseline every running process opts into: OpenTelemetry, health checks, service discovery, HTTP resilience, Kestrel listener profiles, startup warm-up, Redis wiring, Serilog, vault-backed configuration, a shared DataProtection key ring, and hardened security headers. Edge-side code (MMCA.Common.Aspire.Gateway) is the small kit a reverse-proxy host adds on top: correlation, an edge rate limiter, and per-downstream readiness. YARP building blocks (MMCA.Common.Gateway) are the fourth: cluster request profiles, destination health-check defaults, route/cluster trace headers, named per-route limiter policies, and the forwarded-headers step, all applied on top of whatever route table the host loaded. The AppHost and service assemblies are deliberately kept apart so a running service never drags in the full Aspire.Hosting tooling graph (MMCA.Common/Source/Hosting/MMCA.Common.Aspire.Hosting/Extensions.cs:13-17), and the service-side package carries exactly one first-party project reference, MMCA.Common.Shared, taken solely so the resilience constants have one home (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/MMCA.Common.Aspire.csproj:75). The gateway package goes further: it has no project reference at all and one runtime package, YARP (MMCA.Common/Source/Hosting/MMCA.Common.Gateway/MMCA.Common.Gateway.csproj:15), because the host it serves has no application container to share. ADC has no app-local ServiceDefaults project: all four services, the Gateway, and the Blazor UI host consume MMCA.Common.Aspire's AddServiceDefaults() directly (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:107, MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:100, MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:86, MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:89, MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/Program.cs:58, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:52). Everything in this group is plumbing, but it is the plumbing that makes ADR-006 (database-per-service), ADR-008 (service topology + YARP), ADR-009 (resilience / RTO-RPO), and ADR-041 (observability and telemetry) real at runtime rather than aspirational.

      The orchestrator: declaring the resource graph

      When you run dotnet run --project Source/Hosting/MMCA.ADC.AppHost, Aspire executes Program.cs top to bottom, building a resource model rather than starting anything immediately. It declares a persistent SQL Server container (MMCA.ADC/Source/Hosting/MMCA.ADC.AppHost/Program.cs:15-16), four databases on it (ADC_Identity / ADC_Conference / ADC_Engagement / ADC_Notification, Program.cs:37-40), Redis (:44-45), a broker chosen at :89-101, and a MailDev SMTP container (:109), then the four service projects (:127, :156, :199, :227), the YARP Gateway with its HTTPS endpoint pinned to port 6001 (:344-355), and the Blazor UI pinned to 6002 (:381-397). All of the cross-cutting wiring vocabulary lives in one reusable, cross-app place: the Extensions static class of MMCA.Common.Aspire.Hosting (Level 1, MMCA.Common/Source/Hosting/MMCA.Common.Aspire.Hosting/Extensions.cs:23). It supplies the fluent helpers, grouped into C# extension(T) blocks by the resource type they attach to (primer §4): the three provisioning helpers AddMailDev (:141), AddMessageBroker (:160) and AddServiceBusEmulatorBroker (:201); the two WithBroker overloads (:252 for RabbitMQ, :280 for the emulator); WithJwksDiscovery (:309); the three CI-only helpers WithE2eRsaKeys (:353), WithE2eRegistrationThrottleLift (:392) and WithE2eGatewayRateLimitLift (:440); and the per-engine data-source helpers WithSQLServerDataSource (:483), WithCosmosDataSource (:512) and WithSqliteDataSource (:537), the last two added for polyglot persistence (ADR-018).

      .WithSQLServerDataSource(db, "Conference") (MMCA.Common/Source/Hosting/MMCA.Common.Aspire.Hosting/Extensions.cs:483) is the AppHost manifestation of ADR-006: in one fluent chain it adds .WithReference(database), .WaitFor(database), and injects a single connection-string env var, DataSources__{logicalName}__SQLServerConnectionString (Extensions.cs:490-493). That one entry is the whole configuration: with no top-level connection string the single database a host declares this way also becomes its Default source, so the framework's own tables, the readiness health check and the migrations-assembly lookup all resolve from it, and the logical name collapses onto Default, leaving one context, one change tracker and one migration set per service (the rationale is spelled out in the method's own doc comment, :468-478). The routing types that consume those env vars, DataSourceResolver and EntityDataSourceRegistry, live in the persistence group. The ADC AppHost calls it once per service (MMCA.ADC/Source/Hosting/MMCA.ADC.AppHost/Program.cs:131,160,203,231). This paragraph is where [Rubric §8, Data Architecture] (each service owns its store, with no shared write path) meets [Rubric §7, Microservices Readiness] (the topology is a declaration in one file, not a rewrite).

      @@ -161,28 +161,28 @@

      (MMCA.ADC/Source/Hosting/MMCA.ADC.AppHost/Program.cs:317-352): Identity, Conference and Engagement take WithH2cHealthCheck() (:242, :213, :166) because they run Kestrel Http2-only on cleartext, while Notification takes the stock WithHttpHealthCheck("/alive") (:140) because it runs the mixed Http1AndHttp2 profile. Every service gate is /alive, never /health/ready, and the reason is a startup cycle: service readiness includes the warm-up gate, whose OIDC task fetches discovery through the gateway, but the gateway waits for the services. The gateway resource itself stays on /alive (:354) in the sharpest form of the same rule, because its readiness aggregate includes the per-downstream probes and one unreachable downstream would stop ui.WaitFor(gateway) from ever releasing. Only the UI, which nothing waits for, gates on /health/ready (:396). One further subtlety worth internalizing: Conference and Engagement form a bidirectional gRPC pair, and a reciprocal WaitFor would deadlock, so the AppHost deliberately omits the reverse edge (:273, reasoning at :258-272) and lets transient "peer not ready" failures self-heal via the Polly pipeline baked into AddTypedGrpcClient. The same judgement is applied twice more: Engagement to Notification carries no WaitFor because the live-channel push is fire-and-forget (:281), and Identity's two export edges carry none because those peers already wait on Identity for JWKS (:291-292). The typed-client and interceptor machinery, JwtForwardingClientInterceptor and GrpcResultExceptionInterceptor, lives in the gRPC group (ADR-007).

      Because a composition mistake in that one file (a renamed resource, a reference that no longer resolves, a WaitFor cycle, a data source that stopped being injected) is invisible to dotnet build and to every other test tier, ADC keeps one tier that boots the real thing. It is three small types over a framework base. AdcAppHostFixture (Level 3, MMCA.ADC/Tests/Integration/MMCA.ADC.AppHost.SmokeTests/AdcAppHostFixture.cs:28) derives from the shared AppHostFixtureBase<Projects.MMCA_ADC_AppHost> in MMCA.Common.Testing.Aspire and supplies only what is ADC-specific: the preconditions it needs, OptIn | Docker | DeveloperCertificate (:31-34), a readiness budget of 12 minutes to start and 8 minutes to become ready (:42-43), and the resources to await, notification, engagement, conference, identity, gateway, listed in dependency order so a failure names the first thing that did not come up rather than the gateway waiting on it (:52-53). The UI and the MAUI head are deliberately absent (:45-50). The developer-certificate precondition is not incidental: the gateway is an https endpoint, so every probe against it would otherwise fail with UntrustedRoot, and the base also mints the ephemeral RS256 keypair the identity service signs with and its peers validate against (:16-26). AdcAppHostCollection (Level 4, same file :60) is the one-line ICollectionFixture<AdcAppHostFixture> binding with the collection name as a constant (:63), which is what makes the expensive stack start once for the whole tier rather than once per assertion. AdcAppHostSmokeTests (Level 5, MMCA.ADC/Tests/Integration/MMCA.ADC.AppHost.SmokeTests/AdcAppHostSmokeTests.cs:32) joins that collection and inherits the assertion helpers from AppHostTestBase<AdcAppHostFixture>: the gateway answers /health (:76-81), the gateway publishes the identity key set (asked for through the gateway, because identity's cleartext endpoint is Http2-only and a default backchannel HttpClient cannot reach it, :83-94), the three h2c services answer on the negotiated HTTP/2 version rather than merely answering (:96-109), and Notification answers h2c on its dedicated grpc endpoint (:111-120), the ADR-012 mixed-profile half. Its ServiceDataSources theory data (:39-45) pairs each of the four services with the logical DataSources:{Module} name it declares, because a service wired to the wrong database, or to none, still starts. Every test opens with SkipWhenUnavailable() (:67-73), written as a branch rather than Assert.SkipWhen(!Fixture.IsAvailable, Fixture.SkipReason) for a reason worth reading (:58-66): SkipReason is null precisely when the fixture did start, and SkipWhen validates its reason argument before it looks at the condition, so the compact form throws ArgumentNullException and fails every test on exactly the runner that has the opt-in, Docker and the certificate. The tier is probational and non-gating (:18-23): it runs continue-on-error in the nightly cross-service-tests.yml and the cross-service-freshness deploy gate keys off the cross-service job, never this one. That bounded exception is recorded in ADR-098, and the shared fixture base it now sits on is ADR-117.

      The service baseline: AddServiceDefaults()

      -

      Every running host calls one method early in Program.cs: AddServiceDefaults() from the single framework-grade Extensions in MMCA.Common.Aspire (Level 11, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:18, a partial class whose health-check and telemetry halves live in Extensions.Health.cs and Extensions.Telemetry.cs beside it). There is no ADC-local copy. AddServiceDefaults<TBuilder>() (Extensions.cs:29) is four lines of composition, ConfigureOpenTelemetry(), AddDefaultHealthChecks(), AddWarmupReadiness(), AddServiceDiscovery() (:31-34), followed by one ConfigureHttpClientDefaults block (:38) that applies to every HttpClient the host later creates: typed clients, named clients, the YARP forwarder, and the gateway's own downstream probe clients alike. That block installs the standard Polly pipeline (:48-54), re-adds service discovery to the handler chain (:55), and then replaces the primary handler with a tuned SocketsHttpHandler (:68-76, rationale comment at :57-67). The handler is the clearest [Rubric §31, Cost & FinOps] decision in the codebase: recycling pooled connections picks up ACA replica DNS rollover without a restart, holding idle connections in the pool avoids paying TCP and TLS handshakes on every low-traffic inter-service call, and socket-level keep-alive pings keep the TCP connection warm without generating HTTP traffic, so an idle replica stays on idle-vCPU billing (documented in-code as roughly 8x cheaper than active). EnableMultipleHttp2Connections = true (:75) keeps a single multiplexed HTTP/2 connection from becoming the bottleneck for the gRPC edges above.

      +

      Every running host calls one method early in Program.cs: AddServiceDefaults() from the single framework-grade Extensions in MMCA.Common.Aspire (Level 11, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:19, a partial class whose health-check and telemetry halves live in Extensions.Health.cs and Extensions.Telemetry.cs beside it). There is no ADC-local copy. AddServiceDefaults<TBuilder>() (Extensions.cs:30) is four lines of composition, ConfigureOpenTelemetry(), AddDefaultHealthChecks(), AddWarmupReadiness(), AddServiceDiscovery() (:31-34), followed by one ConfigureHttpClientDefaults block (:38) that applies to every HttpClient the host later creates: typed clients, named clients, the YARP forwarder, and the gateway's own downstream probe clients alike. That block installs the standard Polly pipeline (:48-54), re-adds service discovery to the handler chain (:55), and then replaces the primary handler with a tuned SocketsHttpHandler (:68-76, rationale comment at :57-67). The handler is the clearest [Rubric §31, Cost & FinOps] decision in the codebase: recycling pooled connections picks up ACA replica DNS rollover without a restart, holding idle connections in the pool avoids paying TCP and TLS handshakes on every low-traffic inter-service call, and socket-level keep-alive pings keep the TCP connection warm without generating HTTP traffic, so an idle replica stays on idle-vCPU billing (documented in-code as roughly 8x cheaper than active). EnableMultipleHttp2Connections = true (:75) keeps a single multiplexed HTTP/2 connection from becoming the bottleneck for the gRPC edges above.

      One source of truth for resilience numbers

      -

      The numbers that pipeline uses are not literals in the Aspire package. They live in HttpResilienceDefaults (Level 0, MMCA.Common/Source/Core/MMCA.Common.Shared/Resilience/HttpResilienceDefaults.cs:10), a static class of read-only properties in MMCA.Common.Shared: a 30 second per-attempt timeout (:13), a 60 second circuit-breaker sampling window (:16), a 90 second total request timeout including retries (:19), one retry beyond the initial attempt (:28), a 10 minute pooled-connection lifetime (:34), a 5 minute pooled idle timeout (:37), and 60 second / 30 second keep-alive ping delay and timeout (:40,43). It sits in Shared for a layer reason: MMCA.Common.Aspire and MMCA.Common.Grpc may only depend on Shared, and before this type existed each package hand-mirrored the values and drifted (10s/30s library defaults on the gRPC side against the tuned 30s/90s on the HTTP side, stated in the doc comment at :3-9). The retry budget is the interesting constant: it is pinned at one deliberately (HttpResilienceDefaults.cs:21-27) because the UI service base classes already own user-facing retries, and a full retry budget re-applied at every hop turns a backend brownout into an up-to-16x request storm at exactly the wrong moment.

      +

      The numbers that pipeline uses are not literals in the Aspire package. They live in HttpResilienceDefaults (Level 0, MMCA.Common/Source/Core/MMCA.Common.Shared/Resilience/HttpResilienceDefaults.cs:10), a static class of read-only properties in MMCA.Common.Shared: a 30 second per-attempt timeout (:13), a 60 second circuit-breaker sampling window (:16), a 90 second total request timeout including retries (:19), one retry beyond the initial attempt (:28), a 10 minute pooled-connection lifetime (:34), a 5 minute pooled idle timeout (:37), and 60 second / 30 second keep-alive ping delay and timeout (:40,43). It sits in Shared for a layer reason: MMCA.Common.Aspire and MMCA.Common.Grpc may only depend on Shared, and before this type existed each package hand-mirrored the values and drifted (10s/30s library defaults on the gRPC side against the tuned 30s/90s on the HTTP side, stated in the doc comment at :3-9). The retry budget is the interesting constant: it is pinned at one deliberately (HttpResilienceDefaults.cs:21-29) because the UI service base classes already own user-facing retries, and a full retry budget re-applied at every hop turns a backend brownout into an up-to-16x request storm at exactly the wrong moment.

      Two siblings sit in the same folder and complete the picture. GrpcResilienceDefaults (Level 1, MMCA.Common/Source/Core/MMCA.Common.Shared/Resilience/GrpcResilienceDefaults.cs:12) is what the typed gRPC clients in MMCA.Common.Grpc read (group 13): its timeouts and retry budget are re-exposed from the HTTP defaults so the east-west path can never drift from the outbound-HTTP path (:15-24), while the breaker shape is stated explicitly here (0.5 failure ratio at :27, minimum throughput 10 at :30, a 10 second break at :33) because east-west calls address a peer directly and bypass the gateway's active health checks, so the breaker is the only thing that notices a peer going bad (:3-11). BrokerResilienceDefaults (Level 0, MMCA.Common/Source/Core/MMCA.Common.Shared/Resilience/BrokerResilienceDefaults.cs:24) does the same job for the other outbound transport: the circuit breaker guarding the outbox's broker-publish path. Four properties, a 0.5 failure ratio (:32), a minimum throughput of 10 attempts (:40), a 30 second sampling window (:47), and a 15 second break duration (:55), are read by OutboxProcessor in MMCA.Common.Infrastructure. Its doc comment carries the two decisions worth reading (BrokerResilienceDefaults.cs:8-22): a breaker is needed at all because a broker outage makes each of a 50-row batch's publishes wait out its own transport timeout, so one cycle can spend minutes discovering nothing; and it is deliberately not paired with a Polly retry, because the outbox already owns retry (RetryCount, exponential backoff with jitter, then dead-letter) and a second policy would make a row's effective attempt count an accident of two independent budgets. Three constants files, three transports, no drift: [Rubric §29, Resilience & Business Continuity] (ADR-009) and [Rubric §29, Resilience, Reliability & Business Continuity].

      Listeners and probes: one Kestrel profile per host shape

      -

      Before a service can answer a health probe it has to be listening on a protocol the prober speaks, and that is not free when the same port serves inbound gRPC. KestrelEndpointExtensions (Level 1, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Kestrel/KestrelEndpointExtensions.cs:24) turns both deployed profiles into a single call, ConfigureEndpointsWithHealthProbe(defaultProtocols, redeclareCleartextEndpoint, cleartextPort) (:77). It applies the requested protocols to every Kestrel endpoint default and then declares the explicit listeners computed by BuildListenerPlan (:84-98, plan at :113). The plan is empty when HealthProbe:Port (:31) is unconfigured (:119-122), which is exactly the local and integration-test case: no explicit Listen call, so Aspire's dynamic ports keep working and two co-hosted services cannot collide. When the deployment injects the port, the plan is one or two KestrelListenerSpec records (:132), each a (Port, Protocols) pair: an Http1-only listener on the probe port, preceded by a re-declaration of the main cleartext listener (default 8080, :37) when redeclareCleartextEndpoint is left at its default (:124-126), because an explicit Listen call otherwise overrides the container's ASPNETCORE_HTTP_PORTS binding entirely. The reason for the second listener is operational and documented at the top of the file (:8-22): ACA httpGet probes speak HTTP/1.1, which an Http2-only endpoint rejects with GOAWAY HTTP_1_1_REQUIRED, which is why those probes used to be TCP-only and never consulted the real dependency-aware health checks. A dedicated Http1 listener on a port that is never published through ingress gives the platform a real target, since MapDefaultEndpoints() maps the health routes on every listener. ADC's three REST services pass HttpProtocols.Http2 (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:89, MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:83, MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:68) and the SignalR-hosting Notification service passes Http1AndHttp2 with redeclareCleartextEndpoint: false because its endpoints come from configuration (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:74). This is ADR-012 made concrete, and it tags [Rubric §17, DevOps & Deployment].

      +

      Before a service can answer a health probe it has to be listening on a protocol the prober speaks, and that is not free when the same port serves inbound gRPC. KestrelEndpointExtensions (Level 1, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Kestrel/KestrelEndpointExtensions.cs:24) turns both deployed profiles into a single call, ConfigureEndpointsWithHealthProbe(defaultProtocols, redeclareCleartextEndpoint, cleartextPort) (:77). It applies the requested protocols to every Kestrel endpoint default and then declares the explicit listeners computed by BuildListenerPlan (:84-98, plan at :113). The plan is empty when HealthProbe:Port (:31) is unconfigured (:119-122), which is exactly the local and integration-test case: no explicit Listen call, so Aspire's dynamic ports keep working and two co-hosted services cannot collide. When the deployment injects the port, the plan is one or two KestrelListenerSpec records (:132), each a (Port, Protocols) pair: an Http1-only listener on the probe port, preceded by a re-declaration of the main cleartext listener (default 8080, :37) when redeclareCleartextEndpoint is left at its default (:124-126), because an explicit Listen call otherwise overrides the container's ASPNETCORE_HTTP_PORTS binding entirely. The reason for the second listener is operational and documented at the top of the file (:8-22): ACA httpGet probes speak HTTP/1.1, which an Http2-only endpoint rejects with GOAWAY HTTP_1_1_REQUIRED, which is why those probes used to be TCP-only and never consulted the real dependency-aware health checks. A dedicated Http1 listener on a port that is never published through ingress gives the platform a real target, since MapDefaultEndpoints() maps the health routes on every listener. ADC's three REST services pass HttpProtocols.Http2 (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:91, MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:84, MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:70) and the SignalR-hosting Notification service passes Http1AndHttp2 with redeclareCleartextEndpoint: false because its endpoints come from configuration (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:73). This is ADR-012 made concrete, and it tags [Rubric §17, DevOps & Deployment].

      Health checks: liveness, readiness, and the "optional" tag

      -

      MapDefaultEndpoints() (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.Health.cs:123) exposes the three-probe surface the platform reads: /health (every check, for humans and dashboards, :134), /alive (only checks tagged live, :138-141), and /health/ready (:154-156). None of those three paths is a literal at the mapping site: they come from HealthEndpointPaths (Level 0, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/HealthEndpointPaths.cs:8), a constants holder carrying Health (:11), Alive (:14) and Ready (:17) plus one predicate, IsProbePath (:29), which answers case-insensitively for /alive, /health, and anything below /health/, so a sub-route a host adds later is covered without a second edit (:30-33). The type exists to keep the mapping, the probe telemetry filters and the gateway's downstream probe from drifting apart (:3-7). Every ADC host maps it (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:443, MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:348, MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:320, MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:267, MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/Program.cs:170, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:215). The tag vocabulary is a named type rather than string literals, HealthCheckTags (Level 0, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/HealthCheckTags.cs:6), with three constants: Live (:12), Ready (:18), and Optional (:32). Read the readiness predicate carefully, because it encodes a hard-won operational rule: it excludes both live and optional (Extensions.Health.cs:156). Excluding live keeps a downstream SQL outage from restarting the container. Excluding optional is the subtler half, argued at length in both the tag's own doc comment (HealthCheckTags.cs:20-31) and the endpoint comment (Extensions.Health.cs:148-153): a dependency the app degrades gracefully without (a distributed cache behind an in-memory fallback, a broker behind a retrying outbox) must not gate readiness, because making it readiness-fatal converts a partial degradation into a total outage when every replica goes unready at once. Those checks still surface on /health, so the degradation stays visible without being self-inflicted.

      +

      MapDefaultEndpoints() (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.Health.cs:123) exposes the three-probe surface the platform reads: /health (every check, for humans and dashboards, :134), /alive (only checks tagged live, :138-141), and /health/ready (:154-156). None of those three paths is a literal at the mapping site: they come from HealthEndpointPaths (Level 0, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/HealthEndpointPaths.cs:8), a constants holder carrying Health (:11), Alive (:14) and Ready (:17) plus one predicate, IsProbePath (:29), which answers case-insensitively for /alive, /health, and anything below /health/, so a sub-route a host adds later is covered without a second edit (:30-33). The type exists to keep the mapping, the probe telemetry filters and the gateway's downstream probe from drifting apart (:3-7). Every ADC host maps it (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:443, MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:344, MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:319, MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:261, MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/Program.cs:170, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:227). The tag vocabulary is a named type rather than string literals, HealthCheckTags (Level 0, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/HealthCheckTags.cs:6), with three constants: Live (:12), Ready (:18), and Optional (:32). Read the readiness predicate carefully, because it encodes a hard-won operational rule: it excludes both live and optional (Extensions.Health.cs:156). Excluding live keeps a downstream SQL outage from restarting the container. Excluding optional is the subtler half, argued at length in both the tag's own doc comment (HealthCheckTags.cs:20-31) and the endpoint comment (Extensions.Health.cs:148-153): a dependency the app degrades gracefully without (a distributed cache behind an in-memory fallback, a broker behind a retrying outbox) must not gate readiness, because making it readiness-fatal converts a partial degradation into a total outage when every replica goes unready at once. Those checks still surface on /health, so the degradation stays visible without being self-inflicted.

      The two aggregating probes are not computed per request. /health and /health/ready are both mapped through MapCachedHealthChecks(path, predicate) (Extensions.Health.cs:170-192), which answers exactly as MapHealthChecks does (the status name as text/plain, 503 only when unhealthy, :182-189) but serves the report from CachedHealthReportProvider (Level 1, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Health/CachedHealthReportProvider.cs:25), keyed by path so the full report and the readiness subset never share a result (Extensions.Health.cs:167, :179). The window comes from HealthReportCacheOptions (Level 0, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Health/HealthReportCacheOptions.cs:28): one property, CacheSeconds, defaulting to 5 and range-validated to 0-300, with 0 restoring probe-per-request behavior (:37-38), bound from the HealthChecks section (:31). Read the reason in its remarks (:10-22), because it is an availability finding rather than a performance tweak: both endpoints are anonymous and sit on the always-bypassed rate-limit prefix, and executing every registered check per request turned N anonymous requests into N x (services + database + cache) backend operations, a SELECT 1 on a pooled SQL connection, a Redis PING, a broker check, and on a gateway one outbound /alive per fronted service, which exhausts the SQL pool without an account. Caching the report bounds that amplification at one probe round per window regardless of inbound rate, which a bypass list alone cannot do. /alive is deliberately left uncached (Extensions.Health.cs:132-133, HealthReportCacheOptions.cs:19-22): it runs only the self check, costs nothing, and is what the platform restarts a container on, so it must answer for the current instant. The provider keeps one private Entry (CachedHealthReportProvider.cs:33) per endpoint key, a cached report, its expiry, and a SemaphoreSlim refresh slot (:35-41), and the single-flight rule is the subtle part: a caller that cannot take the slot serves the stale report rather than queueing (:75-81), so a slow dependency cannot convert a probe flood into a request backlog; only the very first call after startup, with nothing to serve, waits for the round in progress (:83-85), and the winner re-checks for a freshly published report before probing again (:89-93). The clock is an injected TimeProvider (:28, registered with TryAddSingleton(TimeProvider.System) at Extensions.Health.cs:37) so the window is testable without waiting.

      -

      AddDefaultHealthChecks() (Extensions.Health.cs:25) registers the baseline self check tagged Live (:28) and, alongside it, the cache: the options bound with ValidateDataAnnotations().ValidateOnStart() (:33-36) and the provider as a singleton (:38). AddInfrastructureHealthChecks(requireDatabase) (:69) adds the dependency probes. The relational half is engine-agnostic and multi-source: AddDatabaseHealthChecks (:208) asks RelationalSources (:258) for every distinct database the host declares on each of the three relational engines, SQL Server, PostgreSQL and SQLite (:213-215), reading both the top-level ConnectionStrings section and every named DataSources entry (:269, :275) and deduplicating by connection string (:284-292), then registers one untagged check per database (:227, :232, :237). Untagged means they do gate readiness: readiness for a host that owns several databases means every database it serves from is reachable. The requireDatabase asymmetry is deliberate and documented (Extensions.Health.cs:50-63): a host that cannot resolve its own database is misconfigured, so no relational engine at all throws at startup (:217-223), the same fail-fast posture as ADR-070; Redis and RabbitMQ are optional per host and are added only when their connection string is present, tagged Optional (Extensions.Health.cs:84-91, :99-103), so the same binary runs unchanged in an integration-test environment where those containers are absent. All four ADC services pass requireDatabase: true (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:216, MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:148, MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:144, MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:132).

      +

      AddDefaultHealthChecks() (Extensions.Health.cs:25) registers the baseline self check tagged Live (:28) and, alongside it, the cache: the options bound with ValidateDataAnnotations().ValidateOnStart() (:33-36) and the provider as a singleton (:38). AddInfrastructureHealthChecks(requireDatabase) (:69) adds the dependency probes. The relational half is engine-agnostic and multi-source: AddDatabaseHealthChecks (:208) asks RelationalSources (:258) for every distinct database the host declares on each of the three relational engines, SQL Server, PostgreSQL and SQLite (:213-215), reading both the top-level ConnectionStrings section and every named DataSources entry (:269, :275) and deduplicating by connection string (:284-292), then registers one untagged check per database (:227, :232, :237). Untagged means they do gate readiness: readiness for a host that owns several databases means every database it serves from is reachable. The requireDatabase asymmetry is deliberate and documented (Extensions.Health.cs:50-63): a host that cannot resolve its own database is misconfigured, so no relational engine at all throws at startup (:217-223), the same fail-fast posture as ADR-070; Redis and RabbitMQ are optional per host and are added only when their connection string is present, tagged Optional (Extensions.Health.cs:84-91, :99-103), so the same binary runs unchanged in an integration-test environment where those containers are absent. All four ADC services pass requireDatabase: true (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:215, MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:146, MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:143, MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:128).

      The Redis branch is worth reading as a case study in why the tag rules exist, because it was learned in production. RedisCachingExtensions (Level 0, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Caching/RedisCachingExtensions.cs:29) is the framework-owned way to wire a host to Redis: AddRedisCaching(connectionName) (:57) registers the distributed cache and the IConnectionMultiplexer client with DisableHealthChecks = true on both (:64-65), and AddRedisOutputCaching(connectionName) (:91) backs the ASP.NET Core output cache with the same instance so tag eviction crosses replicas (:99). Both are no-ops when the connection string is absent (:59-62, :93-97). The reason for the wrapper is stated in its own doc comment (:12-27): Aspire's Redis integrations register the AspNetCore.HealthChecks.Redis check with no tags, an untagged check silently gates readiness, and that check issues CLUSTER INFO against anything StackExchange.Redis 3.x detects as clustered, which is how it sees Azure Managed Redis (Enterprise tier). The server refuses that command outside admin mode, so every probe threw against a healthy cache, every replica reported not ready, and the platform stopped routing traffic the applications could serve. The fix is structural rather than a tag patch on someone else's registration: the Aspire checks are switched off at the source and Common contributes RedisPingHealthCheck (Level 0, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Health/RedisPingHealthCheck.cs:26), which issues nothing but PING (:52), resolves the multiplexer from DI and only lazily builds its own behind a semaphore when the host registered no client (:47-48,90-101), reports rather than throws on any failure (:64-72), and is registered once as a singleton tagged Optional (Extensions.Health.cs:84-91). ADC's four services call AddRedisCaching() (.../Conference.Service/Program.cs:130, .../Identity.Service/Program.cs:123, .../Engagement.Service/Program.cs:102, .../Notification.Service/Program.cs:105), and Conference additionally calls AddRedisOutputCaching() (:140). This is [Rubric §17, DevOps & Deployment] (the probe contract the deployment platform consumes) and [Rubric §13, Observability & Operability] made concrete; the readiness revision is recorded in ADR-025.

      Telemetry: what gets exported, and what it costs

      ConfigureOpenTelemetry() (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.Telemetry.cs:71) wires logging with formatted messages and scopes (:73-77), metrics, and tracing (:79-81). Metrics are configured by a private ConfigureMetrics(metrics, configuration) (:243), which adds nine framework meters by literal name, MMCA.Common.Outbox, MMCA.Common.Cqrs, MMCA.Common.Idempotency, MMCA.Common.Scheduler, MMCA.Common.Broker, MMCA.Common.OutputCache, MMCA.Common.BestEffort, MMCA.Common.InternalCommands, and the optional AI package's MMCA.Common.AI (the AiTelemetryName constant at :44) (:307-315), because the Aspire package has no reference to the assemblies that define them (its only project reference is Shared), plus Polly's own Polly meter (:322). Tracing adds the host's own application-name source plus the MMCA.Common.Outbox, MMCA.Common.InternalCommands and MMCA.Common.AI activity sources, again as literals (:83-86). Several of those meters are inert until a host opts into the feature behind them, the scheduler in a host that never enables Scheduler:Enabled, the broker meter in a host that stays on the in-process bus, and the AI meter until a host adds that package and enables Ai:Enabled, which the in-code comment states explicitly (:296-306). Four cost levers hang off this method, and all four fail safe; a fifth, Telemetry:EnablePollyDurationMetrics (the key is a named constant at :25, read by IsInstrumentationEnabled at :221-222), inverts the default the other way and drops the two Polly duration histograms unless a host asks for them (:324-340). First, OutboxPollFilterProcessor (Level 9, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Telemetry/OutboxPollFilterProcessor.cs:17) is registered via .AddProcessor(...) (Extensions.Telemetry.cs:122): a BaseProcessor<Activity> whose OnEnd (OutboxPollFilterProcessor.cs:32) walks each ending span's in-process parent chain (:37), matches on both operation name and source name so an unrelated span called OutboxPoll is not swept up (:39-40, the two literals pinned as private constants at :23-24), and clears ActivityTraceFlags.Recorded (:45) so the batch exporters skip it. It must be registered before the exporters so its OnEnd runs first, and it returns rather than throws on a null activity (:29-33), because a telemetry callback must never take the process down. Real per-message OutboxProcess work restores an explicit parent context and is never a poll descendant, so genuine outbox telemetry survives (the poll machinery, OutboxProcessor, IOutboxSignal, and OutboxMessage, lives in the events and outbox group, ADR-003). Second, TryGetTraceSampleRatio (Extensions.Telemetry.cs:185) reads an optional Telemetry:TracesSampleRatio and, only when it parses inside the open interval (0,1) (:189-191), installs a ParentBasedSampler(TraceIdRatioBasedSampler(...)) (:137-138) for head-based sampling; a typo, a blank, or an out-of-range value falls back to sampling everything, so a mistake can never silently blackhole all telemetry, and ParentBased keeps a sampled-in trace intact across service boundaries. Third, IsInstrumentationDisabled (Extensions.Telemetry.cs:208-209) backs two opt-in kill switches, Telemetry:DisableHttpClientMetrics (:254) and Telemetry:DisableRuntimeMetrics (:281), which drop the two highest-volume metric families on a low-traffic multi-service deployment. Note how they drop them: skipping the Add*Instrumentation call is not enough, because a deployed host also calls UseAzureMonitor() and the Azure Monitor distro adds the System.Net.Http and System.Runtime meters itself, so each branch installs a MetricStreamConfiguration.Drop View over the whole meter instead (:266-270, :286-289, reasoning at :256-265 and :283-285). A View applies to the MeterProvider regardless of which component added the meter, which is what makes the toggle authoritative rather than advisory. Unset keeps everything, so a host that does not opt in sees no behavior change. Fourth, and unlike the other three on by default, Telemetry:FilterProbeTelemetry (the key is a named constant, Extensions.Telemetry.cs:19, read by IsProbeTelemetryFilterEnabled at :233-234) keeps health-probe traffic out of trace export: absent, blank or unparseable all mean filter, and only an explicit boolean false turns it off for a host debugging its own probes (:224-234). It takes two passes, because a probe request and its dependency children are sampled independently. ProbeTelemetryFilter (Level 9, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Telemetry/ProbeTelemetryFilter.cs:27) supplies the two instrumentation predicates, wired onto the default-named ASP.NET Core and HttpClient options (Extensions.Telemetry.cs:106-109) so they also govern the instrumentation the Azure Monitor distro adds, which is what makes them authoritative without a View (:103-105). ShouldCollectRequest (ProbeTelemetryFilter.cs:40) refuses an inbound probe request and on its way out stamps an mmca.probe tag (:33, :51) on the current server activity; ShouldCollectOutgoing (:62) refuses the outbound probe calls that have no inbound parent to inherit from, the gateway's DownstreamServiceHealthCheck calls to each backend's /alive and YARP's active health checks, both driven by background timers (:20-25). The marker exists because refusing the request makes the instrumentation return before it writes url.path, leaving descendants with no way to recognize their probe ancestor (:12-19). ProbeTelemetryFilterProcessor (Level 10, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Telemetry/ProbeTelemetryFilterProcessor.cs:20) is the second pass, registered only when the knob is on and, like the outbox one, ahead of the exporters (Extensions.Telemetry.cs:124-130): OnStart (:29) and OnEnd (:40) run the same walk up the in-process parent chain (:52) and, on a probe ancestor, clear both ActivityTraceFlags.Recorded and IsAllDataRequested (:59-60), so the database check's SELECT 1, the Redis PING and the gateway's /alive call never reach an exporter. A parent counts as a probe when it carries the marker tag (:68) or is a server span whose url.path, http.route, or route-suffixed display name is a probe path (:76-79); server kind only, so a normal request never loses its whole subtree because one dependency happened to be a probe (:73-75). Both passes return rather than throw on a null activity (:44-48), and running at end as well as at start is deliberate, since a client span is started before its instrumentation has written any attribute (:32-39). Metrics are untouched by design, so probe traffic stays on dashboards while leaving the trace bill (:14-17); the volume is concrete, probe requests accounted for every AppRequests row in both production workspaces and their children for most of the AppDependencies volume (ProbeTelemetryFilter.cs:8-11, ProbeTelemetryFilterProcessor.cs:10-12). Exporters are equally conditional: AddOpenTelemetryExporters (Extensions.Telemetry.cs:159) enables OTLP when OTEL_EXPORTER_OTLP_ENDPOINT is present (the local Aspire dashboard, :161-167) and Azure Monitor when APPLICATIONINSIGHTS_CONNECTION_STRING is present (:169-175), and both can be active at once. The whole shape is ADR-041: instrument where auto-instrumentation is blind, then expose cost knobs whose defaults never go dark. [Rubric §13, Observability & Operability] and [Rubric §31, Cost & FinOps].

      One logging detail belongs to this boundary rather than to the telemetry pipeline itself. SerilogHostExtensions (Level 0, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Logging/SerilogHostExtensions.cs:27) carries the bootstrap every service host used to repeat by hand: AddCommonSerilog(logFilePath, configure) (:48) builds the framework configuration (CreateLoggerConfiguration at :98: minimum level Debug in Development and Information elsewhere via ResolveMinimumLevel at :76, EF Core and ASP.NET Core overridden to Warning at :108-109, console always at :110, a daily rolling file sink everywhere except Production via ShouldWriteFileSink at :87), publishes it as the global Log.Logger, and registers it with builder.Logging.AddSerilog(...) (:54-55). That last line is the load-bearing part, stated in the type's doc comment (:16-21): UseSerilog() replaces the whole ILoggerFactory and silently bypasses every other provider, including the OpenTelemetry to Azure Monitor one AddServiceDefaults() wires, so a host that calls it publishes no application log line to App Insights at all. AddSerilog adds Serilog alongside the others instead. CreateBootstrapLoggerFactory() (:67) covers the startup diagnostics a host needs before the DI container exists, module discovery above all. All four ADC services call AddCommonSerilog immediately before AddServiceDefaults() (.../Conference.Service/Program.cs:99-100, .../Identity.Service/Program.cs:96-97, .../Engagement.Service/Program.cs:82-83, .../Notification.Service/Program.cs:85-86).

      Warm-up: defeating ACA cold-start

      -

      The warm-up subsystem exists for one concrete failure mode: the "first request fails, second succeeds" pattern on a CPU-throttled idle ACA replica, where lazy initialization (OIDC discovery fetch, connection-pool establishment, JIT) stretches past a client timeout. AddWarmupReadiness() (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:91, folded into AddServiceDefaults at :33) registers four cooperating pieces. IWarmupTask (Level 0, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Warmup/IWarmupTask.cs:9) is the unit of startup work: a Name for logs (:12) plus ExecuteAsync (:15). WarmupHostedService (Level 1, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Warmup/WarmupHostedService.cs:28) is a BackgroundService that runs all registered tasks in parallel exactly once (:53) and then opens the gate in a finally block (:56-60), so even when a task throws, the replica is never wedged permanently out of rotation. Each task additionally runs under a 120 second ceiling applied with WaitAsync over an injectable TimeProvider (:42,44-45,69): a task that neither completes nor throws used to leave Task.WhenAll pending forever and the gate closed with it, and the timeout turns that case into the same log-and-continue path as a failure (:77-82), which closes the one gap ADR-025 originally recorded as open. A per-task catch logs a genuine failure at Warning and lets the others proceed (:84-88), while a real host-shutdown cancellation is rethrown (:73-76). WarmupReadinessGate (Level 0, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Warmup/WarmupReadinessGate.cs:10) is a thread-safe one-shot flag (Volatile.Read at :15, Interlocked.Exchange at :18, over an int), consumed by WarmupReadinessHealthCheck (Level 1, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Warmup/WarmupReadinessHealthCheck.cs:9), which is registered tagged HealthCheckTags.Ready (Extensions.cs:98-99) and reports Unhealthy until the gate opens (WarmupReadinessHealthCheck.cs:14-16). That is exactly what makes /health/ready hold ingress traffic off a still-warming replica.

      -

      Two task shapes ship with the framework. OpenIdConnectMetadataWarmupTask (Level 1, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Warmup/OpenIdConnectMetadataWarmupTask.cs:21) is registered unconditionally by AddWarmupReadiness (Extensions.cs:96): it reads Authentication:JwtBearer:Authority (:30, the same key the AppHost's WithJwksDiscovery injects), returns quietly when it is unset (:31-34), warns and returns when it is not a valid absolute URI (:36-43), and otherwise GETs {authority}/.well-known/openid-configuration through the shared IHttpClientFactory (:45-46) to warm DNS, TCP, TLS, and the connection pool. Its remarks state the honest caveat (:14-20): the JwtBearer middleware caches discovery state separately and still performs its own first fetch, but now over a warm connection. SelfHttpWarmupTaskBase (Level 1, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Warmup/SelfHttpWarmupTaskBase.cs:28) is the abstract base for the deeper variant: after awaiting ApplicationStarted (:102, implemented at :191, because the warm-up runner starts before Kestrel is listening) it replays a subclass-supplied list of WarmupPaths (:59) against this host's own bound cleartext port, resolved from the server's addresses feature with ASPNETCORE_URLS and port 8080 as fallbacks (ResolveWarmupPort, :157, default at :39). Three virtual members are the extension points that make it usable on every host shape: RequestVersion and RequestVersionPolicy default to HTTP/2 pinned exactly (:70,77), because an Http2-only cleartext endpoint rejects a silently downgraded HTTP/1.1 request, and RequireSuccessStatusCode (:90) can be turned off so an intentional 401 on an [Authorize] route still counts (the refusal traverses Kestrel, routing, and authentication, which is the JIT cost being paid down). The whole task is skipped under the Testing environment (:46,95-98), where WebApplicationFactory's in-memory server never opens a socket. Services register their own tasks via AddWarmupTask<TTask>() (Extensions.cs:114): ADC wires SelfHttpOutputCacheWarmupTask in Conference (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:310) and a SelfHttpWarmupTask in Engagement and Identity (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:158, MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:171). This whole subsystem is the decision recorded in ADR-025: warm-up is wired into AddServiceDefaults so every host gets it, the gate opens even when a task fails or times out (availability over strict warmth), and the missed work is re-paid as a lazy retry on the first real request through the resilience pipeline. Tag-wise it leans on [Rubric §29, Resilience & Business Continuity] and [Rubric §17, DevOps & Deployment].

      +

      The warm-up subsystem exists for one concrete failure mode: the "first request fails, second succeeds" pattern on a CPU-throttled idle ACA replica, where lazy initialization (OIDC discovery fetch, connection-pool establishment, JIT) stretches past a client timeout. AddWarmupReadiness() (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:99, folded into AddServiceDefaults at :33) registers four cooperating pieces. IWarmupTask (Level 0, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Warmup/IWarmupTask.cs:9) is the unit of startup work: a Name for logs (:12) plus ExecuteAsync (:15). WarmupHostedService (Level 1, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Warmup/WarmupHostedService.cs:28) is a BackgroundService that runs all registered tasks in parallel exactly once (:53) and then opens the gate in a finally block (:56-60), so even when a task throws, the replica is never wedged permanently out of rotation. Each task additionally runs under a 120 second ceiling applied with WaitAsync over an injectable TimeProvider (:42,44-45,69): a task that neither completes nor throws used to leave Task.WhenAll pending forever and the gate closed with it, and the timeout turns that case into the same log-and-continue path as a failure (:77-82), which closes the one gap ADR-025 originally recorded as open. A per-task catch logs a genuine failure at Warning and lets the others proceed (:84-88), while a real host-shutdown cancellation is rethrown (:73-76). WarmupReadinessGate (Level 0, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Warmup/WarmupReadinessGate.cs:10) is a thread-safe one-shot flag (Volatile.Read at :15, Interlocked.Exchange at :18, over an int), consumed by WarmupReadinessHealthCheck (Level 1, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Warmup/WarmupReadinessHealthCheck.cs:9), which is registered tagged HealthCheckTags.Ready (Extensions.cs:106-107) and reports Unhealthy until the gate opens (WarmupReadinessHealthCheck.cs:14-16). That is exactly what makes /health/ready hold ingress traffic off a still-warming replica.

      +

      Two task shapes ship with the framework. OpenIdConnectMetadataWarmupTask (Level 1, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Warmup/OpenIdConnectMetadataWarmupTask.cs:21) is registered unconditionally by AddWarmupReadiness (Extensions.cs:104): it reads Authentication:JwtBearer:Authority (:30, the same key the AppHost's WithJwksDiscovery injects), returns quietly when it is unset (:31-34), warns and returns when it is not a valid absolute URI (:36-43), and otherwise GETs {authority}/.well-known/openid-configuration through the shared IHttpClientFactory (:45-46) to warm DNS, TCP, TLS, and the connection pool. Its remarks state the honest caveat (:14-20): the JwtBearer middleware caches discovery state separately and still performs its own first fetch, but now over a warm connection. SelfHttpWarmupTaskBase (Level 1, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Warmup/SelfHttpWarmupTaskBase.cs:28) is the abstract base for the deeper variant: after awaiting ApplicationStarted (:102, implemented at :191, because the warm-up runner starts before Kestrel is listening) it replays a subclass-supplied list of WarmupPaths (:59) against this host's own bound cleartext port, resolved from the server's addresses feature with ASPNETCORE_URLS and port 8080 as fallbacks (ResolveWarmupPort, :157, default at :39). Three virtual members are the extension points that make it usable on every host shape: RequestVersion and RequestVersionPolicy default to HTTP/2 pinned exactly (:70,77), because an Http2-only cleartext endpoint rejects a silently downgraded HTTP/1.1 request, and RequireSuccessStatusCode (:90) can be turned off so an intentional 401 on an [Authorize] route still counts (the refusal traverses Kestrel, routing, and authentication, which is the JIT cost being paid down). The whole task is skipped under the Testing environment (:46,95-98), where WebApplicationFactory's in-memory server never opens a socket. Services register their own tasks via AddWarmupTask<TTask>() (Extensions.cs:122): ADC wires SelfHttpOutputCacheWarmupTask in Conference (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:309) and a SelfHttpWarmupTask in Engagement and Identity (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:157, MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:169). This whole subsystem is the decision recorded in ADR-025: warm-up is wired into AddServiceDefaults so every host gets it, the gate opens even when a task fails or times out (availability over strict warmth), and the missed work is re-paid as a lazy retry on the first real request through the resilience pipeline. Tag-wise it leans on [Rubric §29, Resilience & Business Continuity] and [Rubric §17, DevOps & Deployment].

      Configuration secrets: the vault as one more configuration source

      -

      Connection strings, RSA signing keys and OAuth client secrets have to reach the process somehow, and KeyVaultConfigurationExtensions (Level 0, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Configuration/KeyVaultConfigurationExtensions.cs:21) is the framework's answer: AddCommonKeyVaultConfiguration() (:78) layers an Azure Key Vault over the host configuration so every secret is readable through IConfiguration exactly like any other setting, and overrides the sources added before it (:109). Two keys drive it. KeyVault:Uri is the gate (:80): absent or whitespace and the method does nothing at all (:85-88), so a developer machine, a test host, and the Helpdesk seed keep the sources they already have and take no Azure dependency at startup. KeyVault:ReloadIntervalMinutes is optional (:96), and a non-positive or unparseable value throws rather than falling back to "never reload" (:97-107), because a silently ignored interval leaves the host serving startup values forever and the operator only finds out when a rotated credential fails to take effect. Authentication is DefaultAzureCredential (:109), so a deployed host uses its managed identity and a developer falls back to the Azure CLI or Visual Studio sign-in; the secret naming convention is the double dash, which the provider maps onto the configuration separator (ConnectionStrings--Default arrives as ConnectionStrings:Default). Two design notes are worth carrying forward. First, this is deliberately not called from AddServiceDefaults() (:56-62), for the same reason AddCommonDataProtection is not: service defaults run in every host, and an unconditional Azure dependency at startup would be a liability on a laptop. Second, builder.Configuration is a ConfigurationManager, which builds and loads each source as it is added (:63-69), so the vault is read synchronously at this point in startup and the call belongs early, before the settings binding that reads those values. All four ADC services and the Blazor UI host opt in immediately after AddServiceDefaults() (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:114, MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:107, MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:92, MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:98, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:59). [Rubric §11, Security] (secrets out of the repository and out of the process environment, rotatable without a redeploy) and [Rubric §17, DevOps & Deployment].

      +

      Connection strings, RSA signing keys and OAuth client secrets have to reach the process somehow, and KeyVaultConfigurationExtensions (Level 0, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Configuration/KeyVaultConfigurationExtensions.cs:21) is the framework's answer: AddCommonKeyVaultConfiguration() (:78) layers an Azure Key Vault over the host configuration so every secret is readable through IConfiguration exactly like any other setting, and overrides the sources added before it (:109). Two keys drive it. KeyVault:Uri is the gate (:80): absent or whitespace and the method does nothing at all (:85-88), so a developer machine, a test host, and the Helpdesk seed keep the sources they already have and take no Azure dependency at startup. KeyVault:ReloadIntervalMinutes is optional (:96), and a non-positive or unparseable value throws rather than falling back to "never reload" (:97-107), because a silently ignored interval leaves the host serving startup values forever and the operator only finds out when a rotated credential fails to take effect. Authentication is DefaultAzureCredential (:109), so a deployed host uses its managed identity and a developer falls back to the Azure CLI or Visual Studio sign-in; the secret naming convention is the double dash, which the provider maps onto the configuration separator (ConnectionStrings--Default arrives as ConnectionStrings:Default). Two design notes are worth carrying forward. First, this is deliberately not called from AddServiceDefaults() (:56-62), for the same reason AddCommonDataProtection is not: service defaults run in every host, and an unconditional Azure dependency at startup would be a liability on a laptop. Second, builder.Configuration is a ConfigurationManager, which builds and loads each source as it is added (:63-69), so the vault is read synchronously at this point in startup and the call belongs early, before the settings binding that reads those values. All four ADC services and the Blazor UI host opt in immediately after AddServiceDefaults() (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:116, MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:108, MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:94, MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:97, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:60). [Rubric §11, Security] (secrets out of the repository and out of the process environment, rotatable without a redeploy) and [Rubric §17, DevOps & Deployment].

      Security headers, CORS, and the shared key ring at the host edge

      -

      The next boundary in this group hardens the request and response edge. The shared response-header middleware is defined entirely in MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Security/SecurityHeaders.cs. SecurityHeadersSettings (Level 0, :19) holds the strongly-typed values bound from the "SecurityHeaders" section (:22): FrameOptions defaulting to DENY (:25), ReferrerPolicy (:28), PermissionsPolicy (:31), HSTS opt-out and value (:34,37), an enforce-versus-report-only switch (:58), and a complete hardened CSP baseline (:53-55) that ships script-src 'self' 'wasm-unsafe-eval' and style-src 'self' 'unsafe-inline' at exactly the strength Blazor and MudBlazor require, so an HTML host that never registers a provider still gets a functional policy rather than one silently missing both directives, while the JSON, WebSocket and static responses of API and gateway hosts are unaffected (:39-52). ICspPolicyProvider (Level 1, :72) is the per-host CSP extension point, with StaticCspPolicyProvider (Level 2, :79) as the default that resolves the configured policy once in its constructor (:83-90) and returns it for every request (:92); a Blazor host registers its own dynamic implementation before calling AddCommonSecurityHeaders, which is exactly what ADC's UI host does with the shared BlazorCspPolicyProvider behind AddCommonBlazorCsp() (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:118-119, also turning HSTS off there because the client-facing Gateway emits it). CspPolicy (Level 0, :64) is the resolved (Value, Enforce) record that decides between Content-Security-Policy and Content-Security-Policy-Report-Only, keeping a policy and its enforcement mode inseparable. SecurityHeadersMiddleware (Level 2, :133) stamps X-Content-Type-Options, X-Frame-Options, Referrer-Policy, and Permissions-Policy on every response (:167-170), adds HSTS only outside Development (decided once in the constructor at :158, applied at :172-175), and emits whichever CSP header the provider's Enforce flag selects (:191-198). Between those two steps sits the nonce path: a policy carrying the literal {nonce} token gets a fresh 16-byte value per request (:136,139,184-189), stashed raw in HttpContext.Items before the rest of the pipeline runs so the page render can read it, and substituted into the header as the quoted 'nonce-<value>' source-list form. CspNonce (Level 0, :110) is the tiny public surface for that: the ItemKey constant (:113) and a Get(context) helper (:120-124) a layout calls to stamp the value onto its own script tags. It is the supported path off 'unsafe-inline', and a policy with no placeholder generates nothing (:98-108). SecurityHeadersExtensions (Level 3, :210) supplies the two registration halves: AddCommonSecurityHeaders (:220), which binds the config section (:226-230) and registers the default provider through TryAddSingleton so a pre-registered custom provider wins (:237), and UseCommonSecurityHeaders (:245). The stated point is to centralize what each client-facing host previously hand-rolled, eliminating drift; this is ADR-023.

      -

      Two siblings complete this boundary. GatewayCorsExtensions (Level 0, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/GatewayCorsExtensions.cs:16) exposes AddCommonGatewayCors (:24), which registers the reverse proxy's default CORS policy: allow-any origin in Development (:34-41) but in every other environment restricting origins to Cors:AllowedOrigins while allowing any header and method plus credentials (:44-52), because the CORS spec forbids combining allow-any-origin with credentials. It is the gateway half of the two-tier posture in ADR-082, deliberately looser than MMCA.Common.API's per-service allow-listed AddCommonCors, because a proxy must pass arbitrary client headers through; ADC's Gateway wires it alongside the header middleware (MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/Program.cs:118,123,171). DataProtectionExtensions (Level 0, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/DataProtection/DataProtectionExtensions.cs:19) fixes the multi-replica half of the same story: the framework default keeps the ASP.NET Core key ring in memory, so on a scaled-out host an auth cookie or antiforgery token minted by replica A cannot be decrypted by replica B, and the user sees random sign-outs that follow the load balancer rather than any pattern. AddCommonDataProtection() (:52) persists the key ring to a blob whose URI comes from DataProtection:BlobStorageUri, authenticated with DefaultAzureCredential (:54,68,71-72), sets an application discriminator from DataProtection:ApplicationName or the host name (:64), and optionally encrypts the ring at rest with a Key Vault key (:81-85). The two gates are deliberately independent (:74-80): blob persistence is what makes cookies portable and has to work without the Key Vault Crypto User role, which is granted out of band and can lag a deployment, so coupling them would turn an optional hardening gap into a total authentication outage. Absent config, the method returns immediately (:59-61), so a developer machine, a test host, and the Helpdesk seed take no Azure dependency at startup. ADC calls it on the two hosts that mint cookies and antiforgery tokens, Identity and the Blazor UI (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:114, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:61); this is ADR-069. Relevant tags across this boundary: [Rubric §11, Security] and [Rubric §26, Front-End Security] (defense-in-depth headers, CSP with a nonce path, and a key ring that survives scale-out), and [Rubric §13, Observability & Operability] (one shared middleware, one shared CORS policy, and one shared key-ring registration instead of N hand-rolled copies).

      +

      The next boundary in this group hardens the request and response edge. The shared response-header middleware is defined entirely in MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Security/SecurityHeaders.cs. SecurityHeadersSettings (Level 0, :19) holds the strongly-typed values bound from the "SecurityHeaders" section (:22): FrameOptions defaulting to DENY (:25), ReferrerPolicy (:28), PermissionsPolicy (:31), HSTS opt-out and value (:34,37), an enforce-versus-report-only switch (:58), and a complete hardened CSP baseline (:53-55) that ships script-src 'self' 'wasm-unsafe-eval' and style-src 'self' 'unsafe-inline' at exactly the strength Blazor and MudBlazor require, so an HTML host that never registers a provider still gets a functional policy rather than one silently missing both directives, while the JSON, WebSocket and static responses of API and gateway hosts are unaffected (:39-52). ICspPolicyProvider (Level 1, :72) is the per-host CSP extension point, with StaticCspPolicyProvider (Level 2, :79) as the default that resolves the configured policy once in its constructor (:83-90) and returns it for every request (:92); a Blazor host registers its own dynamic implementation before calling AddCommonSecurityHeaders, which is exactly what ADC's UI host does with the shared BlazorCspPolicyProvider behind AddCommonBlazorCsp() (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:119-120, also turning HSTS off there because the client-facing Gateway emits it). CspPolicy (Level 0, :64) is the resolved (Value, Enforce) record that decides between Content-Security-Policy and Content-Security-Policy-Report-Only, keeping a policy and its enforcement mode inseparable. SecurityHeadersMiddleware (Level 2, :133) stamps X-Content-Type-Options, X-Frame-Options, Referrer-Policy, and Permissions-Policy on every response (:167-170), adds HSTS only outside Development (decided once in the constructor at :158, applied at :172-175), and emits whichever CSP header the provider's Enforce flag selects (:191-198). Between those two steps sits the nonce path: a policy carrying the literal {nonce} token gets a fresh 16-byte value per request (:136,139,184-189), stashed raw in HttpContext.Items before the rest of the pipeline runs so the page render can read it, and substituted into the header as the quoted 'nonce-<value>' source-list form. CspNonce (Level 0, :110) is the tiny public surface for that: the ItemKey constant (:113) and a Get(context) helper (:120-124) a layout calls to stamp the value onto its own script tags. It is the supported path off 'unsafe-inline', and a policy with no placeholder generates nothing (:98-108). SecurityHeadersExtensions (Level 3, :210) supplies the two registration halves: AddCommonSecurityHeaders (:220), which binds the config section (:226-230) and registers the default provider through TryAddSingleton so a pre-registered custom provider wins (:237), and UseCommonSecurityHeaders (:245). The stated point is to centralize what each client-facing host previously hand-rolled, eliminating drift; this is ADR-023.

      +

      Two siblings complete this boundary. GatewayCorsExtensions (Level 0, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/GatewayCorsExtensions.cs:16) exposes AddCommonGatewayCors (:24), which registers the reverse proxy's default CORS policy: allow-any origin in Development (:34-41) but in every other environment restricting origins to Cors:AllowedOrigins while allowing any header and method plus credentials (:44-52), because the CORS spec forbids combining allow-any-origin with credentials. It is the gateway half of the two-tier posture in ADR-082, deliberately looser than MMCA.Common.API's per-service allow-listed AddCommonCors, because a proxy must pass arbitrary client headers through; ADC's Gateway wires it alongside the header middleware (MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/Program.cs:118,123,171). DataProtectionExtensions (Level 0, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/DataProtection/DataProtectionExtensions.cs:19) fixes the multi-replica half of the same story: the framework default keeps the ASP.NET Core key ring in memory, so on a scaled-out host an auth cookie or antiforgery token minted by replica A cannot be decrypted by replica B, and the user sees random sign-outs that follow the load balancer rather than any pattern. AddCommonDataProtection() (:52) persists the key ring to a blob whose URI comes from DataProtection:BlobStorageUri, authenticated with DefaultAzureCredential (:54,68,71-72), sets an application discriminator from DataProtection:ApplicationName or the host name (:64), and optionally encrypts the ring at rest with a Key Vault key (:81-85). The two gates are deliberately independent (:74-80): blob persistence is what makes cookies portable and has to work without the Key Vault Crypto User role, which is granted out of band and can lag a deployment, so coupling them would turn an optional hardening gap into a total authentication outage. Absent config, the method returns immediately (:59-61), so a developer machine, a test host, and the Helpdesk seed take no Azure dependency at startup. ADC calls it on the two hosts that mint cookies and antiforgery tokens, Identity and the Blazor UI (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:115, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:62); this is ADR-069. Relevant tags across this boundary: [Rubric §11, Security] and [Rubric §26, Front-End Security] (defense-in-depth headers, CSP with a nonce path, and a key ring that survives scale-out), and [Rubric §13, Observability & Operability] (one shared middleware, one shared CORS policy, and one shared key-ring registration instead of N hand-rolled copies).

      The gateway edge kit: correlation, rate limiting, downstream readiness

      A YARP gateway is the one process every client request passes through, and it is also the one host that has no application container: no DbContext, no module loader, no ICorrelationContext. That is why the three edge behaviors live in their own namespace, MMCA.Common.Aspire.Gateway, with no dependency beyond ASP.NET Core itself. The decision, including the three responsibilities the edge explicitly declines, is ADR-088.

      GatewayCorrelationMiddleware (Level 9, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Gateway/GatewayCorrelationMiddleware.cs:27) is the twin of MMCA.Common.API's CorrelationIdMiddleware and exists precisely because that one cannot run here: it writes onto a scoped ICorrelationContext that only a host with the Common application services registered owns. This one takes RequestDelegate and nothing else (:27), which is what makes it safe to drop into a bare proxy. InvokeAsync (:42) reads X-Correlation-ID (the header name is a public constant deliberately duplicating the API package's literal, :34, since the two packages share no reference) and, when the caller sent none, mints one from Activity.Current?.TraceId with TraceIdentifier as the fallback, then writes it onto the request headers (:53-54). Writing the request side is the whole point: the proxied request carries the ID downstream, so the service-side middleware adopts it instead of minting a second one. The response echo is registered through HttpResponse.OnStarting (:58) so it survives a response whose headers the forwarder writes. GatewayCorrelationExtensions (Level 10, same file :73) is the one-line UseGatewayCorrelation() pipeline call (:82).

      @@ -190,7 +190,7 @@

      The

      Readiness is the third piece, and it is the one that learned the most. GatewayHealthCheckExtensions (Level 2, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Gateway/GatewayHealthCheckExtensions.cs:70) exposes AddGatewayDownstreamHealthChecks in two overloads (:130, :148), which for each Aspire service name registers a named HttpClient whose base address is the service-discovery form http://{name} (:189-201, resolved by the AddServiceDiscovery that AddServiceDefaults already wired, so nothing hard-codes a host or port) and a downstream-{name} health check over it (:203-212). Two choices carry the design (:113-128): the checks are tagged Ready, never Live, because restarting the gateway fixes nothing about a downstream outage; and their failure status is Unhealthy rather than Degraded, because /health/ready treats Degraded as passing, so a Degraded check would report a problem while still taking traffic the gateway cannot serve. The probe budget is 2 seconds (:84), short because it runs per downstream on every readiness poll. DownstreamServiceHealthCheck (Level 1, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Gateway/DownstreamServiceHealthCheck.cs:39) is the hand-rolled check itself, a GET and a status comparison (:143-175), with a narrow catch list plus a cancellation guard so host shutdown is never reported as a dependency fault (:75-87). It probes /alive, not /health/ready, taking the path from the shared HealthEndpointPaths.Alive constant rather than a literal of its own (:46), and the reason is in its remarks (:17-21): the gateway needs to know whether the service exists and is reachable, and probing readiness would make every rolling downstream deployment register as a gateway failure. The protocol question is settled per downstream rather than configured: DownstreamProbeVersion (Level 0, MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Gateway/GatewayHealthCheckExtensions.cs:10) offers Auto, Http2 and Http11 (:18,25,32), and GatewayDownstreamHealthCheckOptions (Level 1, same file :40) defaults to Auto (:59) because neither fixed answer is right for every head: an h2c-only endpoint refuses HTTP/1.1, and a mixed Http1AndHttp2 cleartext endpoint without ALPN answers HTTP_1_1_REQUIRED to HTTP/2 forever (:42-57). Under Auto the check asks for HTTP/2 first and, on a protocol refusal only (IsProtocolRefusal at :184 matching VersionNegotiationError or HttpProtocolError), retries once as HTTP/1.1 inside the same poll (:98-131), then latches the version that answered with Interlocked.CompareExchange (:192) so the fallback is a one-time cost per downstream rather than a per-poll one. Nothing latches on a connectivity fault, because a transient outage says nothing about which protocol the endpoint speaks. GatewayDownstreamRegistry (Level 0, same file as the extensions, :225) is the small ledger that makes the registration idempotent, the same shape as the H2c one above: GetOrAdd (:257) attaches one instance to the collection and TryClaim (:240) skips a name an earlier call already registered.

      The YARP building blocks: cluster profiles, ejection, trace headers, route policies

      The route table itself is configuration, not code (ADR-089), and MMCA.Common.Gateway deliberately does not load it: LoadFromConfig (or any other IProxyConfigProvider) stays the host's call, and this package only adds behavior on top of whatever source the host chose (MMCA.Common/Source/Hosting/MMCA.Common.Gateway/GatewayReverseProxyExtensions.cs:15-20). GatewaySettings (Level 2, MMCA.Common/Source/Hosting/MMCA.Common.Gateway/GatewaySettings.cs:12) is its whole configuration surface, bound from the MmcaGateway section (:15), and it duplicates nothing from the YARP ReverseProxy section: only the cross-cutting defaults a gateway would otherwise copy into every cluster by hand. GatewayReverseProxyExtensions (Level 4, :26) is the single entry point, AddMmcaGateway, in a configuration overload that binds with ValidateDataAnnotations().ValidateOnStart() (:47-59) and a settings overload for a host that composes in code (:68-79, supplying an explicit IOptions<> instance because the settings are init-only and the filters close over them for the process lifetime). Both funnel into one private Wire (:86) that registers the named policies and attaches two config filters plus one transform provider (:88-93).

      -

      Four building blocks hang off that. First, GatewayClusterProfileConfigFilter (Level 3, MMCA.Common/Source/Hosting/MMCA.Common.Gateway/Configuration/GatewayClusterProfileConfigFilter.cs:25) resolves each cluster's forwarder request profile from three sources, per property rather than per block (Resolve at :59, the four most-specific-wins helpers at :82-122): the cluster's own HttpRequest, then GatewayClusterRequestProfile (Level 0, GatewaySettings.cs:52) under ClusterRequestOverrides[clusterId], then ClusterRequestDefaults. That ordering is the point: a gateway fronting services that all speak h2c declares the shared activity timeout once instead of repeating an identical block per cluster, while the one cluster that genuinely differs still says so locally. The profile expresses the version pair as text (:58,64) so a mistyped value fails at startup with a message naming the cluster (ParseVersion at :76, ParseVersionPolicy at :96) instead of binding to a silent default, and IsSameAs (:131) returns an unchanged cluster by reference so YARP's config-change detection sees no churn on reload. Second, GatewayHealthCheckDefaultsConfigFilter (Level 3, MMCA.Common/Source/Hosting/MMCA.Common.Gateway/Configuration/GatewayHealthCheckDefaultsConfigFilter.cs:17) fills in destination health checks for clusters that declare none (:25-47), additively: an operator's explicit Passive or Active block is kept verbatim (:29-33). GatewayHealthCheckDefaults (Level 1, GatewaySettings.cs:119) splits into GatewayPassiveHealthCheckDefaults (Level 0, :132), on by default with the built-in TransportFailureRate policy and a 60 second reactivation period (:135-142) because passive checks watch the responses the gateway is already forwarding and cost no extra traffic, and GatewayActiveHealthCheckDefaults (Level 0, :150), off by default with ConsecutiveFailures, a 10 second interval, a 5 second timeout and a /alive path (:153-171) chosen for the same reason the gateway's own probes use it: readiness on a downstream flips during its own rolling deployment, and ejecting a destination for that is the gateway reacting to a healthy deployment as if it were an outage. Third, GatewayTraceHeaderTransformProvider (Level 3, MMCA.Common/Source/Hosting/MMCA.Common.Gateway/Transforms/GatewayTraceHeaderTransformProvider.cs:18) stamps the matched route id and target cluster id onto every proxied request from GatewayTraceHeaderSettings (Level 0, GatewaySettings.cs:178, defaults X-MMCA-Route and X-MMCA-Cluster at :181,184), capturing the ids once at build time (:51-54) and removing any inbound value before writing its own (:60-61), because a header a service trusts must be one only the gateway can set. Fourth, GatewayRoutePolicyExtensions (Level 3, MMCA.Common/Source/Hosting/MMCA.Common.Gateway/RateLimiting/GatewayRoutePolicyExtensions.cs:27) registers one named fixed-window policy per entry in RateLimiterPolicies (AddGatewayRoutePolicies at :39), the policies a YARP route references through its own RateLimiterPolicy property. Each GatewayRoutePolicySettings (Level 1, GatewaySettings.cs:212) states a GatewayRoutePolicyPartition (Level 0, :199, ClientIp or Global), a permit limit defaulting to 30, a 60 second window and a queue limit of zero (:215-230), and its PartitionKey (:243) returns null for an unresolvable IP so Partition (GatewayRoutePolicyExtensions.cs:81) hands back a no-limiter partition (:88-89) rather than one shared bucket. Values are validated at registration, not at the first throttled request (:49-52), again ADR-070. These policies are additive to any global limiter the host installs (:16-21): ASP.NET Core evaluates the global limiter and the route's named policy independently, so a request must satisfy both, and the two packages stay independent of each other.

      +

      Four building blocks hang off that. First, GatewayClusterProfileConfigFilter (Level 3, MMCA.Common/Source/Hosting/MMCA.Common.Gateway/Configuration/GatewayClusterProfileConfigFilter.cs:25) resolves each cluster's forwarder request profile from three sources, per property rather than per block (Resolve at :59, the four most-specific-wins helpers at :82-122): the cluster's own HttpRequest, then GatewayClusterRequestProfile (Level 0, GatewaySettings.cs:52) under ClusterRequestOverrides[clusterId], then ClusterRequestDefaults. That ordering is the point: a gateway fronting services that all speak h2c declares the shared activity timeout once instead of repeating an identical block per cluster, while the one cluster that genuinely differs still says so locally. The profile expresses the version pair as text (:58,64) so a mistyped value fails at startup with a message naming the cluster (ParseVersion at :76, ParseVersionPolicy at :96) instead of binding to a silent default, and IsSameAs (:131) returns an unchanged cluster by reference so YARP's config-change detection sees no churn on reload. Second, GatewayHealthCheckDefaultsConfigFilter (Level 3, MMCA.Common/Source/Hosting/MMCA.Common.Gateway/Configuration/GatewayHealthCheckDefaultsConfigFilter.cs:17) fills in destination health checks for clusters that declare none (:25-47), additively: an operator's explicit Passive or Active block is kept verbatim (:29-33). GatewayHealthCheckDefaults (Level 1, GatewaySettings.cs:119) splits into GatewayPassiveHealthCheckDefaults (Level 0, :132), on by default with the built-in TransportFailureRate policy and a 60 second reactivation period (:135-142) because passive checks watch the responses the gateway is already forwarding and cost no extra traffic, and GatewayActiveHealthCheckDefaults (Level 0, :150), off by default with ConsecutiveFailures, a 10 second interval, a 5 second timeout and a /alive path (:153-171) chosen for the same reason the gateway's own probes use it: readiness on a downstream flips during its own rolling deployment, and ejecting a destination for that is the gateway reacting to a healthy deployment as if it were an outage. Third, GatewayTraceHeaderTransformProvider (Level 3, MMCA.Common/Source/Hosting/MMCA.Common.Gateway/Transforms/GatewayTraceHeaderTransformProvider.cs:18) stamps the matched route id and target cluster id onto every proxied request from GatewayTraceHeaderSettings (Level 0, GatewaySettings.cs:178, defaults X-MMCA-Route and X-MMCA-Cluster at :181,184), capturing the ids once at build time (:51-54) and removing any inbound value before writing its own (:60-61), because a header a service trusts must be one only the gateway can set. Fourth, GatewayRoutePolicyExtensions (Level 3, MMCA.Common/Source/Hosting/MMCA.Common.Gateway/RateLimiting/GatewayRoutePolicyExtensions.cs:27) registers one named fixed-window policy per entry in RateLimiterPolicies (AddGatewayRoutePolicies at :39), the policies a YARP route references through its own RateLimiterPolicy property. Each GatewayRoutePolicySettings (Level 1, GatewaySettings.cs:212) states a GatewayRoutePolicyPartition (Level 0, :199, ClientIp or Global), a permit limit defaulting to 30, a 60 second window and a queue limit of zero (:215-230), and its PartitionKey (:243) returns null for an unresolvable IP so Partition (GatewayRoutePolicyExtensions.cs:94) hands back a no-limiter partition (:88-89) rather than one shared bucket. Values are validated at registration, not at the first throttled request (:49-52), again ADR-070. These policies are additive to any global limiter the host installs (:16-21): ASP.NET Core evaluates the global limiter and the route's named policy independently, so a request must satisfy both, and the two packages stay independent of each other.

      The fifth type in this package is not about YARP configuration at all. ForwardedHeadersExtensions (Level 0, MMCA.Common/Source/Hosting/MMCA.Common.Gateway/ForwardedHeadersExtensions.cs:23) supplies UseCommonForwardedHeaders() (:36) and the shared CreateForwardedHeadersOptions() (:55) that applies X-Forwarded-For, -Proto and -Host with the known-proxy and known-network allow-lists cleared (:59-63). Clearing them is load-bearing: cloud reverse proxies front the app from internal IPs that are in neither default allow-list, so leaving the defaults in place makes the middleware ignore every forwarded header and report the ingress IP as the client. It lives here because a service host gets this step from UseCommonMiddlewarePipeline in MMCA.Common.API, and a gateway takes none of that package (:9-17); without it a gateway hand-rolls the same five lines, and getting them wrong is invisible until production, where an unforwarded gateway collapses the per-client-IP rate-limit partition into one shared window for every real user.

      ADC's Gateway wires all of this in an order the comments call out as contractual (MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/Program.cs:58-149 for registration, :124-158 for the pipeline): AddServiceDefaults() (:57), the edge limiter (:64), one call registering all four downstream checks on the Auto default (:78), shared security headers (:84) and CORS (:89), then AddReverseProxy().LoadFromConfig(...).AddMmcaGateway(...).AddServiceDiscoveryDestinationResolver() (:112-115). The pipeline is forwarded headers first so the limiter sees the real client IP (:124), then correlation (:129) so even a 429 from the limiter carries an ID, then security headers (:134), MapDefaultEndpoints (:136), CORS (:137), the limiter after CORS so a rejected preflight is still a CORS answer (:144), static files and the /privacy alias (:149-155), and finally MapReverseProxy() (:158) so a throttled request never reaches a backend. Its GatewayRateLimiting section adds /hubs to the bypass list (MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/appsettings.json:21-26) because a SignalR connection is long-lived and its negotiate and reconnect traffic must not be throttled, and its MmcaGateway section declares the shared 100 second forwarder activity timeout, the one-hour override for the notification-hub cluster, active destination probing at a 30 second interval, and the auth-tight per-route policy (appsettings.json:27-56). [Rubric §7, Microservices Readiness], [Rubric §13, Observability & Operability], and [Rubric §29, Resilience & Business Continuity].

      How it all fits at runtime

      @@ -227,7 +227,7 @@

      DataProtectionExtensions

    1544. Why it's built this way: Configuration-gated rather than always-on, so the framework never forces an Azure dependency on a host that does not need one; two independent gates rather than one, so hardening cannot take availability down with it; and a static class with an extension(T) block, which is the codebase's standard registration idiom (see primer, C# extension(T) types). Both decisions are recorded in ADR-069, which also notes that a host that simply never calls the method keeps the broken per-replica default.
    1545. -
    1546. Where it's used: The three hosts across both apps that mint cookies or antiforgery tokens: MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:61, MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:114 (Identity performs OAuth cookie cryptography and runs multi-replica with no session affinity, so a login started on one replica can fail on the other), and MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:82. Covered by DataProtectionExtensionsTests, which asserts both gates on a built service provider without any Azure credential (the blob client is constructed lazily by the repository, never at registration time).
    1547. +
    1548. Where it's used: The three hosts across both apps that mint cookies or antiforgery tokens: MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:62, MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:115 (Identity performs OAuth cookie cryptography and runs multi-replica with no session affinity, so a login started on one replica can fail on the other), and MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:82. Covered by DataProtectionExtensionsTests, which asserts both gates on a built service provider without any Azure credential (the blob client is constructed lazily by the repository, never at registration time).
    1549. Caveats / not-in-source: Nothing in this file validates that the configured URI points at a reachable blob: a wrong URI surfaces at first key-ring access, not at startup. Gate 2 needs a Key Vault Crypto User grant that is made out of band, which is exactly why it is a second, independent gate.

    1550. @@ -260,7 +260,7 @@

      HealthCheckTags

    1551. Why it's built this way: Constants rather than an enum, because the health-checks API takes string tags; a const also lets the values appear in collection expressions such as tags: [HealthCheckTags.Optional] with no conversion. The three-way split (live / ready / optional / untagged) encodes a deliberate blast-radius policy: liveness restarts, readiness withholds traffic, optional only reports. That policy is recorded in ADR-025, whose revision added the optional exclusion to /health/ready for exactly the total-outage reason above, and it is what ADR-088 applies to a dependency rather than to a startup task.
    1552. -
    1553. Where it's used: The service-defaults Extensions: the warm-up check is registered with [HealthCheckTags.Ready] (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:99), the "self" check with [HealthCheckTags.Live] (:238), and the conditional Redis and RabbitMQ checks with [HealthCheckTags.Optional] (:289, :301). The endpoint predicates read them back: /alive requires Live (:376-379) and /health/ready excludes both Live and Optional (:392-395). The relational database checks are deliberately left untagged (:462, :467), which is what makes them readiness-fatal. GatewayHealthCheckExtensions tags every downstream probe Ready. The tagging is asserted by InfrastructureHealthChecksTests and, for the Redis path specifically, by RedisReadinessSafetyTests.
    1554. +
    1555. Where it's used: The service-defaults Extensions: the warm-up check is registered with [HealthCheckTags.Ready] (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:107), the "self" check with [HealthCheckTags.Live] (:238), and the conditional Redis and RabbitMQ checks with [HealthCheckTags.Optional] (:289, :301). The endpoint predicates read them back: /alive requires Live (:376-379) and /health/ready excludes both Live and Optional (:392-395). The relational database checks are deliberately left untagged (:462, :467), which is what makes them readiness-fatal. GatewayHealthCheckExtensions tags every downstream probe Ready. The tagging is asserted by InfrastructureHealthChecksTests and, for the Redis path specifically, by RedisReadinessSafetyTests.

    1556. HealthEndpointPaths

      @@ -299,7 +299,7 @@

      KeyVaultConfigurationExtensions

    1557. Why it's built this way: Deliberately not called from AddServiceDefaults(), and the doc comment says so in as many words (:56-62): service defaults run in every host, developer machine and test host and Helpdesk seed included, so an unconditional Azure dependency at startup would be a liability there rather than a feature. A host that wants vault-backed configuration opts in with this one call, exactly like DataProtectionExtensions. The loud failure on a bad reload interval is the same fail-fast contract as ADR-070: a misconfiguration whose only symptom would appear later, during an incident, is worth a startup crash. ADR-061 records the platform half of the same posture (production secrets as keyVaultUrl references resolved by a user-assigned managed identity); this extension is the in-process complement, adding the whole vault as a configuration source rather than binding secrets one env var at a time.
    1558. -
    1559. Where it's used: Five ADC hosts (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:59, MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:107, MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:114, MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:92, MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:98) and five Store deployables (MMCA.Store/Source/Hosts/MMCA.Store.Gateway/Program.cs:63, MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:41, MMCA.Store/Source/Services/MMCA.Store.Identity.Service/Program.cs:47, MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:41, MMCA.Store/Source/Services/MMCA.Store.Sales.Service/Program.cs:59). Each call sits immediately after AddServiceDefaults() in the ADC hosts and immediately before it in the Store ones, and always ahead of any settings binding. Covered by KeyVaultConfigurationExtensionsTests, which asserts the gate, the appended source with and without a reload interval, and both throwing paths.
    1560. +
    1561. Where it's used: Five ADC hosts (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:60, MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:108, MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:116, MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:94, MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:97) and five Store deployables (MMCA.Store/Source/Hosts/MMCA.Store.Gateway/Program.cs:63, MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:41, MMCA.Store/Source/Services/MMCA.Store.Identity.Service/Program.cs:47, MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:41, MMCA.Store/Source/Services/MMCA.Store.Sales.Service/Program.cs:59). Each call sits immediately after AddServiceDefaults() in the ADC hosts and immediately before it in the Store ones, and always ahead of any settings binding. Covered by KeyVaultConfigurationExtensionsTests, which asserts the gate, the appended source with and without a reload interval, and both throwing paths.
    1562. Caveats / not-in-source: A malformed KeyVault:Uri throws UriFormatException from new Uri(...) (documented at :72-74); source contains no explicit URI validation of its own. Nothing here verifies the identity actually holds the Secrets User role: because the source loads synchronously as it is added, an authentication failure is a startup crash rather than a degraded feature.

    1563. @@ -318,14 +318,14 @@

      RedisCachingExtensions

    1564. AddRedisOutputCaching(connectionName = DefaultConnectionName) (:91) reads the same connection string (:93), returns untouched when it is missing (:94-97), and otherwise registers AddStackExchangeRedisOutputCache(options => options.Configuration = connectionString) (:99). The doc comment (:70-84) covers three things: cross-replica tag eviction is the reason it exists (the built-in store is per-replica memory, so an EvictByTagAsync reaches only the replica that served the mutation); it should be called before AddOutputCache(...), which registers its store with TryAdd so the explicit Redis store wins either way, but the order documents the intent; and this integration registers no health check of its own, so there is nothing to disable in this method.
    1565. -
    1566. Why it's built this way: The wrapper is a structural fix, not a patch. A tag added to another package's registration would have to be re-applied every time that package changed its registration shape, and the incident showed that an untagged check is invisible until a specific server tier trips it. Disabling the vendor checks at the source and contributing one PING-only check the framework owns makes the readiness contract explicit and testable. ADR-025 is the record consumers cite at the call site for the "readiness checks are PING-class" rule (for example MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:118).
    1567. -
    1568. Where it's used: All four ADC services call AddRedisCaching() unconditionally, since the wrapper self-gates: Identity (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:125), Conference (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:183), Engagement (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:103), and Notification (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:109); ADC Conference also calls AddRedisOutputCaching() (:140). The three Store services call it inside an explicit if on the connection string (MMCA.Store/Source/Services/MMCA.Store.Identity.Service/Program.cs:92, MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:86, MMCA.Store/Source/Services/MMCA.Store.Sales.Service/Program.cs:103), because that guard also scopes the hybrid-cache registration beside it, which has no self-gate of its own (MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:84-85); Store Catalog adds AddRedisOutputCaching() in the same branch (:104). Covered by RedisReadinessSafetyTests, which asserts that the wrapper registers no untagged Aspire check, that every Redis check is tagged Optional, that the readiness predicate admits none of them, and that both methods register nothing without a connection string.
    1569. +
    1570. Why it's built this way: The wrapper is a structural fix, not a patch. A tag added to another package's registration would have to be re-applied every time that package changed its registration shape, and the incident showed that an untagged check is invisible until a specific server tier trips it. Disabling the vendor checks at the source and contributing one PING-only check the framework owns makes the readiness contract explicit and testable. ADR-025 is the record consumers cite at the call site for the "readiness checks are PING-class" rule (for example MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:119).
    1571. +
    1572. Where it's used: All four ADC services call AddRedisCaching() unconditionally, since the wrapper self-gates: Identity (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:126), Conference (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:185), Engagement (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:105), and Notification (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:108); ADC Conference also calls AddRedisOutputCaching() (:140). The three Store services call it inside an explicit if on the connection string (MMCA.Store/Source/Services/MMCA.Store.Identity.Service/Program.cs:92, MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:86, MMCA.Store/Source/Services/MMCA.Store.Sales.Service/Program.cs:103), because that guard also scopes the hybrid-cache registration beside it, which has no self-gate of its own (MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:84-85); Store Catalog adds AddRedisOutputCaching() in the same branch (:104). Covered by RedisReadinessSafetyTests, which asserts that the wrapper registers no untagged Aspire check, that every Redis check is tagged Optional, that the readiness predicate admits none of them, and that both methods register nothing without a connection string.
    1573. Caveats / not-in-source: The methods gate on the connection string only; nothing here verifies the endpoint is reachable, which is what the separate PING check is for. AddRedisOutputCaching reads the connection string directly rather than going through Aspire's client integration, so it does not participate in that integration's options binding.

    1574. Extensions

      -

      MMCA.Common.Aspire · MMCA.Common.Aspire · MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:18 · Level 11 · class (static)

      +

      MMCA.Common.Aspire · MMCA.Common.Aspire · MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:19 · Level 11 · class (static)

      Disambiguation: this is the framework (Common.Aspire) Extensions, the canonical service-defaults bootstrap consumed by every running service, including all four ADC services and all three Store services (neither app ships a local ServiceDefaults project). The other class named Extensions in this chapter is the Common.Aspire.Hosting Extensions, the AppHost-side broker, JWKS, and data-source wiring.

      @@ -333,10 +333,10 @@

      Extensions

      • What it is: The shared Aspire service-defaults bootstrap: AddServiceDefaults(), AddWarmupReadiness(), ConfigureOpenTelemetry(), AddDefaultHealthChecks(), AddInfrastructureHealthChecks(), AddWarmupTask<T>(), MapDefaultEndpoints(), and MapCachedHealthChecks(). It configures OpenTelemetry (logs, metrics, tracing), cached health-check endpoints (/health, /alive, /health/ready), service discovery, the warm-up readiness pipeline, and a Polly resilience pipeline plus a FinOps-tuned SocketsHttpHandler for every outbound HttpClient.
      • Depends on: IWarmupTask, OpenIdConnectMetadataWarmupTask, WarmupHostedService, WarmupReadinessGate, WarmupReadinessHealthCheck, HealthCheckTags, HealthEndpointPaths, RedisPingHealthCheck, CachedHealthReportProvider, HealthReportCacheOptions, OutboxPollFilterProcessor, ProbeTelemetryFilter, ProbeTelemetryFilterProcessor, and HttpResilienceDefaults; plus Azure Monitor, OpenTelemetry, Polly / Microsoft.Extensions.Http.Resilience, the AspNetCore health-check packages, and the RabbitMQ client (NuGet).
      • -
      • Concept introduced, Aspire service defaults as a shared cross-cutting bootstrap. [Rubric §13, Observability & Operability] (centralised OpenTelemetry plus health endpoints; it subscribes eight MMCA meters, Polly's own resilience meter, and the MMCA.Common.Outbox/MMCA.Common.InternalCommands trace sources by literal name, plus, new, the optional AI package's meter and trace source under the single literal AiTelemetryName = "MMCA.Common.AI" (Extensions.Telemetry.cs:44), inert until a host adopts MMCA.Common.AI and sets Ai:Enabled, and installs OutboxPollFilterProcessor to drop noisy idle-poll spans before export, plus, by default, the two probe-telemetry filters). [Rubric §29, Resilience & Business Continuity] (Polly on every outbound client, ADR-009, and the Polly meter now exported so a retry storm or an open circuit shows up as a signal rather than plain latency). [Rubric §31, Cost / FinOps] (four trace/metric-side cost knobs: Telemetry:FilterProbeTelemetry on by default, Telemetry:EnablePollyDurationMetrics off by default, plus Telemetry:TracesSampleRatio and the two HttpClient/runtime metric-family switches opt-in; and the SocketsHttpHandler tuning at Extensions.cs:68-76, whose rationale comment at :102-112 is the codebase's clearest FinOps decision: PooledConnectionLifetime picks up ACA replica DNS rollover, PooledConnectionIdleTimeout avoids repeated TLS handshakes, and socket keep-alive pings keep TCP alive without counting as ACA user traffic, so the replica stays on idle-vCPU billing, roughly 8x cheaper than active). [Rubric §27, Security/AppSec] (the health-report cache: SEC-Common-71/SEC-ADC-17 bound an anonymous-flood amplification on /health and /health/ready, see the MapCachedHealthChecks bullet below). [Rubric §13, Observability & Operability] (one call, one baseline, every host). The telemetry half of this is ADR-041; the warm-up half is ADR-025.
      • +
      • Concept introduced, Aspire service defaults as a shared cross-cutting bootstrap. [Rubric §13, Observability & Operability] (centralised OpenTelemetry plus health endpoints; it subscribes eight MMCA meters, Polly's own resilience meter, and the MMCA.Common.Outbox/MMCA.Common.InternalCommands trace sources by literal name, plus, new, the optional AI package's meter and trace source under the single literal AiTelemetryName = "MMCA.Common.AI" (Extensions.Telemetry.cs:44), inert until a host adopts MMCA.Common.AI and sets Ai:Enabled, and installs OutboxPollFilterProcessor to drop noisy idle-poll spans before export, plus, by default, the two probe-telemetry filters). [Rubric §29, Resilience & Business Continuity] (Polly on every outbound client, ADR-009, and the Polly meter now exported so a retry storm or an open circuit shows up as a signal rather than plain latency). [Rubric §31, Cost / FinOps] (four trace/metric-side cost knobs: Telemetry:FilterProbeTelemetry on by default, Telemetry:EnablePollyDurationMetrics off by default, plus Telemetry:TracesSampleRatio and the two HttpClient/runtime metric-family switches opt-in; and the SocketsHttpHandler tuning at Extensions.cs:76-84, whose rationale comment at :65-75 is the codebase's clearest FinOps decision: PooledConnectionLifetime picks up ACA replica DNS rollover, PooledConnectionIdleTimeout avoids repeated TLS handshakes, and socket keep-alive pings keep TCP alive without counting as ACA user traffic, so the replica stays on idle-vCPU billing, roughly 8x cheaper than active). [Rubric §27, Security/AppSec] (the health-report cache: SEC-Common-71/SEC-ADC-17 bound an anonymous-flood amplification on /health and /health/ready, see the MapCachedHealthChecks bullet below). [Rubric §13, Observability & Operability] (one call, one baseline, every host). The telemetry half of this is ADR-041; the warm-up half is ADR-025.
      • Walkthrough:
          -
        • AddServiceDefaults<TBuilder>() (Extensions.cs:29, in a generic extension<TBuilder>(TBuilder builder) where TBuilder : IHostApplicationBuilder block at :65-66, so it works for a web host or a worker): calls ConfigureOpenTelemetry, AddDefaultHealthChecks, AddWarmupReadiness, then AddServiceDiscovery (:76-79), then ConfigureHttpClientDefaults (:83), which affects every HttpClient registered downstream (typed clients, named clients, the YARP forwarder, and the gateway's own downstream probe clients). Inside it, AddStandardResilienceHandler pulls all four values from HttpResilienceDefaults (:93-99): a 30-second per-attempt timeout, a 60-second circuit-breaker sampling window, a 90-second total request timeout, and MaxRetryAttempts = 1. http.AddServiceDiscovery() (:100) lets clients resolve logical service names, and ConfigurePrimaryHttpMessageHandler installs the tuned SocketsHttpHandler (:113-121) including KeepAlivePingPolicy.WithActiveRequests and EnableMultipleHttp2Connections = true (:119-120, so one multiplexed HTTP/2 connection cannot become a bottleneck). The comment at :85-92 is worth reading for the retry-budget reasoning: exactly one retry per hop, because the UI service base classes own user-facing retries, and stacking full budgets at every hop previously multiplied a backend brownout into up to sixteen gateway hits per user action.
        • -
        • AddWarmupReadiness() (Extensions.cs:91): registers the singleton WarmupReadinessGate (:138), the WarmupHostedService runner (:139), the built-in OpenIdConnectMetadataWarmupTask as an IWarmupTask (:141), and the WarmupReadinessHealthCheck as the "warmup" check tagged HealthCheckTags.Ready (:143-144). AddWarmupTask<TTask>() (:363, in an extension(IServiceCollection services) block at :354) lets a consumer add service-specific warm-ups by registering AddSingleton<IWarmupTask, TTask>() (:366).
        • +
        • AddServiceDefaults<TBuilder>() (Extensions.cs:30, in a generic extension<TBuilder>(TBuilder builder) where TBuilder : IHostApplicationBuilder block at :21-22, so it works for a web host or a worker): calls ConfigureOpenTelemetry, AddDefaultHealthChecks, AddWarmupReadiness, then AddServiceDiscovery (:32-35), then ConfigureHttpClientDefaults (:39), which affects every HttpClient registered downstream (typed clients, named clients, the YARP forwarder, and the gateway's own downstream probe clients). Inside it, AddStandardResilienceHandler (:49) pulls all four values from HttpResilienceDefaults (:51-54): a 30-second per-attempt timeout, a 60-second circuit-breaker sampling window, a 90-second total request timeout, and MaxRetryAttempts = 1. It then calls options.Retry.DisableFor(HttpMethod.Post, HttpMethod.Patch) (:61), so a POST or PATCH is never replayed: the comment at :56-60 explains that an attempt which timed out may still be running server-side and a server-to-server hop carries no idempotency key, while GET, PUT, DELETE and the other idempotent verbs keep their one retry, which is why it names the two verbs instead of using DisableForUnsafeHttpMethods (that helper would also switch the retry off for PUT and DELETE). http.AddServiceDiscovery() (:63) lets clients resolve logical service names, and ConfigurePrimaryHttpMessageHandler installs the tuned SocketsHttpHandler (:76-84) including KeepAlivePingPolicy.WithActiveRequests and EnableMultipleHttp2Connections = true (:82-83, so one multiplexed HTTP/2 connection cannot become a bottleneck). The comment at :41-48 is worth reading for the retry-budget reasoning: exactly one retry per hop, because the UI service base classes own user-facing retries, and stacking full budgets at every hop previously multiplied a backend brownout into up to sixteen gateway hits per user action.
        • +
        • AddWarmupReadiness() (Extensions.cs:99): registers the singleton WarmupReadinessGate (:101), the WarmupHostedService runner (:102), the built-in OpenIdConnectMetadataWarmupTask as an IWarmupTask (:104), and the WarmupReadinessHealthCheck as the "warmup" check tagged HealthCheckTags.Ready (:106-107). AddWarmupTask<TTask>() (:122, in an extension(IServiceCollection services) block at :113) lets a consumer add service-specific warm-ups by registering AddSingleton<IWarmupTask, TTask>() (:125).
        • ConfigureOpenTelemetry() (Extensions.Telemetry.cs:71): logging with IncludeFormattedMessage and IncludeScopes (:161-162); the metrics half is now a dedicated private ConfigureMetrics(metrics, configuration) method (:166, defined at :520, "a method rather than an inline lambda so each knob stays readable"). Tracing adds the application's own source plus MMCA.Common.Outbox, MMCA.Common.InternalCommands, and, new, .AddSource(AiTelemetryName) (Extensions.Telemetry.cs:86), the optional AI package's combined trace/metric name: inert until a host both references MMCA.Common.AI and enables it, because subscribing a source nothing publishes to is a no-op. Then branches on the probe-telemetry cost knob, IsProbeTelemetryFilterEnabled (:199). When filtering is on (the default), ASP.NET Core and HttpClient instrumentation are added with filters, options.Filter = Telemetry.ProbeTelemetryFilter.ShouldCollectRequest and options.FilterHttpRequestMessage = Telemetry.ProbeTelemetryFilter.ShouldCollectOutgoing (:191-194); when it is off they are added plain (:198-199). The comment above the branch (:173-184) carries the measurement behind it: Container Apps liveness and readiness probes, the gateway's downstream aggregate probes, YARP active health checks and the availability web test made up 100% of the AppRequests rows in both production workspaces, and their children (the health check's SELECT 1, the Redis PING, the gateway's calls to each backend's /alive) most of the AppDependencies rows; sampling cannot help there, because probe spans are exactly what the sampler keeps proportionally. It also records what is deliberately left alone: metrics, so probe traffic still shows on dashboards. Because both filters configure the default-named instrumentation options, they also apply to the instrumentation the Azure Monitor distro adds, which is why this knob needs no View, unlike the metrics knobs in ConfigureMetrics. .AddProcessor(new Telemetry.OutboxPollFilterProcessor()) (:207) always registers, and a ProbeTelemetryFilterProcessor only under the same knob, both before the exporters so their OnEnd clears the Recorded flag first. When TryGetTraceSampleRatio succeeds it installs new ParentBasedSampler(new TraceIdRatioBasedSampler(ratio)), parent-based so a sampled-in request keeps its whole trace intact across service boundaries. Finally it calls the private AddOpenTelemetryExporters (:334).
        • ConfigureMetrics(metrics, configuration) (Extensions.Telemetry.cs:243, private static): always adds ASP.NET Core instrumentation (:522), then the two metric-family disable knobs, Telemetry:DisableHttpClientMetrics (:531) and Telemetry:DisableRuntimeMetrics (:558), each a two-part switch where the disabled branch does not merely skip the instrumentation call: it installs a metrics.AddView(...) that returns MetricStreamConfiguration.Drop for the System.Net.Http/System.Net.NameResolution meters (:543-547) or the System.Runtime meter (:563-566). The comment above the HttpClient branch (:524-530) records why the View is needed: a deployed host also calls UseAzureMonitor(), and the Azure Monitor distro adds those meters itself, so the toggle is authoritative only because a View applies to the whole MeterProvider regardless of which component added the meter. It then subscribes eight MMCA meters by literal name (:582-589): MMCA.Common.Outbox, .Cqrs, .Idempotency, .Scheduler, .Broker, .OutputCache, .BestEffort, and .InternalCommands, plus, new, a ninth .AddMeter(AiTelemetryName) (Extensions.Telemetry.cs:315) for the optional AI package's token spend and call duration, inert until a host adds that package and enables Ai:Enabled. The comment above them (:573-581) names what each carries and which are inert until a host opts in. It then adds Polly's own meter, PollyMeterName = "Polly" (:49, :596), the meter Polly.Extensions' telemetry listener emits through, named as a literal because Aspire holds no reference to Polly's own assemblies (:47-48); the comment at :591-595 explains why this matters: the standard resilience handler is on every HttpClient and every gRPC typed client, so this meter is the only production signal that a client is retrying, timing out, or being refused by an open circuit, and until it was subscribed a brownout looked, from the outside, exactly like latency. The always-exported counter is resilience.polly.strategy.events (PollyStrategyEventsInstrument, :57), tagged with pipeline.name, strategy.name, event.name (OnRetry/OnCircuitOpened/OnCircuitClosed/OnTimeout/...), event.severity and exception.type (:52-56). Polly's two duration histograms, resilience.polly.strategy.attempt.duration and resilience.polly.pipeline.duration (PollyAttemptDurationInstrument/PollyPipelineDurationInstrument, :60,63), are the new opt-in cost knob: dropped by a metrics.AddView (:608-613) unless a host sets Telemetry:EnablePollyDurationMetrics=true (EnablePollyDurationMetricsConfigKey, :43), because they re-measure what http.client.request.duration and the dependency traces already report and run on a pipeline invoked on every outbound call (:598-605).
        • TryGetTraceSampleRatio(configuration, out ratio) (Extensions.Telemetry.cs:185, internal static): reads the optional Telemetry:TracesSampleRatio knob and returns true only for a value that parses invariantly and falls in the open interval (0,1); absent, unparseable, or out-of-range input returns false and leaves ratio at 1.0. Sampling therefore fails toward keeping everything, so a typo can never silently drop all telemetry.
        • @@ -353,8 +353,8 @@

          Extensions

      • Why it's built this way: One bootstrap means a baseline change (a new meter, a tighter timeout, the poll filter, the Redis check swap, the health-report cache) propagates to every consumer in lockstep. There is a single framework copy and no per-app variants: neither ADC nor Store ships an app-local ServiceDefaults project, so the warm-up gate, the MMCA meters, the poll filter, and the Azure Monitor exporter branch are uniform across every service. Every knob that could hide end-user signal defaults to the expensive-but-safe setting, which is the deliberate bias recorded in ADR-041: a cost surprise is recoverable, a silent telemetry blackout during an incident is not. The two knobs that default the other way, Telemetry:FilterProbeTelemetry and Telemetry:EnablePollyDurationMetrics, are the exceptions that prove the rule: what the first drops is the platform probing itself, and what the second withholds is a histogram that duplicates signal already captured elsewhere; neither one hides an incident. The health-report cache added under AddDefaultHealthChecks/MapCachedHealthChecks extends the same bias to a different axis, DoS resistance: an anonymous flood on /health or /health/ready can no longer force one dependency probe round per request. The class also carries a scoped CA1708 suppression documenting a known analyzer false positive: with two or more extension(T) blocks in one static class, the compiler-generated grouping members trip the "identifiers should differ by more than case" rule.
      • -
      • Where it's used: builder.AddServiceDefaults() early in each framework-consuming host's Program.cs: ADC Gateway (MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/Program.cs:58), ADC UI (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:51), the four ADC services (Identity :97, Conference :100, Engagement :83, Notification :86), the Store gateway, UI and three services (MMCA.Store/Source/Hosts/MMCA.Store.Gateway/Program.cs:65, MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:76, MMCA.Store/Source/Services/MMCA.Store.Identity.Service/Program.cs:74, MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:68, MMCA.Store/Source/Services/MMCA.Store.Sales.Service/Program.cs:85), and the Helpdesk seed (MMCA.Helpdesk/Source/Hosts/MMCA.Helpdesk.Web/Program.cs:14). app.MapDefaultEndpoints() follows Build() in each (ADC Gateway :136, ADC UI :119, ADC Conference :375, Helpdesk :129). AddInfrastructureHealthChecks(requireDatabase: true) is the standard service-host call: ADC Identity :146, Conference :163, Engagement :143, Notification :128, Store Identity :129, Catalog :133, Sales :142, and Helpdesk :31. The internal cost-knob helpers are covered by TracesSampleRatioTests and MetricsInstrumentationToggleTests; the Polly meter by PollyResilienceMetricsTests (MMCA.Common/Tests/Hosting/MMCA.Common.Aspire.Tests/Telemetry/PollyResilienceMetricsTests.cs); the conditional infrastructure checks by InfrastructureHealthChecksTests and RedisReadinessSafetyTests.
      • -
      • Caveats / not-in-source: requireDatabase defaults to false, so a service host that forgets to pass true registers no database check and can report ready without one; every current service host passes it. The RabbitMQ branch silently registers nothing when the connection string is present but not a valid absolute URI, so a malformed broker URI produces no check and no error at startup. Probe-trace filtering is on by default, so a host debugging its own probes sees no request traces for them until it sets Telemetry:FilterProbeTelemetry=false; nothing logs that the filter is active. Telemetry:EnablePollyDurationMetrics defaults to off, so the per-attempt and per-pipeline duration histograms are silent unless a host explicitly opts in. The AiTelemetryName source and meter are always subscribed regardless of whether a host references MMCA.Common.AI; nothing logs that they are inert, and AiDependencyIsolationTestsBase is what keeps the reference itself out of hosts that never adopt the package. /health and /health/ready now serve a cached report rather than always running the underlying checks live; the cache window/behavior is governed by HealthReportCacheOptions and CachedHealthReportProvider, not by this class.
      • +
      • Where it's used: builder.AddServiceDefaults() early in each framework-consuming host's Program.cs: ADC Gateway (MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/Program.cs:58), ADC UI (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:52), the four ADC services (Identity :97, Conference :100, Engagement :83, Notification :86), the Store gateway, UI and three services (MMCA.Store/Source/Hosts/MMCA.Store.Gateway/Program.cs:65, MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:76, MMCA.Store/Source/Services/MMCA.Store.Identity.Service/Program.cs:74, MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:68, MMCA.Store/Source/Services/MMCA.Store.Sales.Service/Program.cs:85), and the Helpdesk seed (MMCA.Helpdesk/Source/Hosts/MMCA.Helpdesk.Web/Program.cs:14). app.MapDefaultEndpoints() follows Build() in each (ADC Gateway :136, ADC UI :119, ADC Conference :375, Helpdesk :129). AddInfrastructureHealthChecks(requireDatabase: true) is the standard service-host call: ADC Identity :146, Conference :163, Engagement :143, Notification :128, Store Identity :129, Catalog :133, Sales :142, and Helpdesk :31. The internal cost-knob helpers are covered by TracesSampleRatioTests and MetricsInstrumentationToggleTests; the Polly meter by PollyResilienceMetricsTests (MMCA.Common/Tests/Hosting/MMCA.Common.Aspire.Tests/Telemetry/PollyResilienceMetricsTests.cs); the conditional infrastructure checks by InfrastructureHealthChecksTests and RedisReadinessSafetyTests.
      • +
      • Caveats / not-in-source: Because the POST/PATCH retry exclusion lives in ConfigureHttpClientDefaults, it applies to every HttpClient in the host; a transient failure on a non-idempotent call surfaces after one attempt, and any replay for those verbs is the caller's decision. requireDatabase defaults to false, so a service host that forgets to pass true registers no database check and can report ready without one; every current service host passes it. The RabbitMQ branch silently registers nothing when the connection string is present but not a valid absolute URI, so a malformed broker URI produces no check and no error at startup. Probe-trace filtering is on by default, so a host debugging its own probes sees no request traces for them until it sets Telemetry:FilterProbeTelemetry=false; nothing logs that the filter is active. Telemetry:EnablePollyDurationMetrics defaults to off, so the per-attempt and per-pipeline duration histograms are silent unless a host explicitly opts in. The AiTelemetryName source and meter are always subscribed regardless of whether a host references MMCA.Common.AI; nothing logs that they are inert, and AiDependencyIsolationTestsBase is what keeps the reference itself out of hosts that never adopt the package. /health and /health/ready now serve a cached report rather than always running the underlying checks live; the cache window/behavior is governed by HealthReportCacheOptions and CachedHealthReportProvider, not by this class.

      DownstreamProbeVersion

      @@ -430,7 +430,7 @@

      GatewayRateLimitingSettings

    1575. Why it's built this way: sealed with init-only properties, so the settings object the limiter closes over cannot be mutated after validation. Data-annotation attributes rather than hand-written guards, because both entry points into the limiter validate them: the configuration overload through ValidateDataAnnotations().ValidateOnStart() and the object overload through an explicit Validator.ValidateObject call, which is ADR-070's fail-fast contract honoured on both paths. The per-replica choice, the anonymous inclusion, the two-tier bypass and the synthetic-traffic and trusted-caller amendments are all recorded in ADR-088, which extends ADR-019 with this fourth, edge tier. The trusted-caller secret is a second, independent property rather than a reuse of SyntheticTrafficSecret, so the two exemptions can be granted, rotated and revoked one without the other.
    1576. -
    1577. Where it's used: Bound from the "GatewayRateLimiting" section by AddGatewayRateLimiting(IConfiguration) (GatewayRateLimitingExtensions.cs:254-261) and read by GatewayRateLimitingExtensions' bypass checks and the two partition functions. Both consumers configure it in the gateway appsettings.json beside the route table: ADC keeps the defaults and bypasses /hubs (MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/appsettings.json:21-26), because a SignalR negotiate-plus-reconnect storm from one shared office address is the pattern a per-IP window misreads as abuse; Store keeps the defaults and bypasses /Payments (MMCA.Store/Source/Hosts/MMCA.Store.Gateway/appsettings.json:16-21), because a 429 to a Stripe webhook is a retry and eventually a disabled endpoint. The synthetic-traffic secret is deployment-only in both apps: a Key Vault secret named synthetic-traffic-secret mapped onto GatewayRateLimiting__SyntheticTrafficSecret and present only when the parameter is supplied (MMCA.ADC/infra/main.bicep:979-982, :1690; MMCA.Store/infra/main.bicep:924-927), and ADC's scheduled load test presents it as X-Synthetic-Traffic-Key (MMCA.ADC/.github/workflows/load-test.yml:8-11). TrustedCallerSecret is read on the calling-host side by MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Security/TrustedCallerHandler.cs and DependencyInjection.cs.
    1578. +
    1579. Where it's used: Bound from the "GatewayRateLimiting" section by AddGatewayRateLimiting(IConfiguration) (GatewayRateLimitingExtensions.cs:272-279) and read by GatewayRateLimitingExtensions' bypass checks and the two partition functions. Both consumers configure it in the gateway appsettings.json beside the route table: ADC keeps the defaults and bypasses /hubs (MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/appsettings.json:21-26), because a SignalR negotiate-plus-reconnect storm from one shared office address is the pattern a per-IP window misreads as abuse; Store keeps the defaults and bypasses /Payments (MMCA.Store/Source/Hosts/MMCA.Store.Gateway/appsettings.json:16-21), because a 429 to a Stripe webhook is a retry and eventually a disabled endpoint. The synthetic-traffic secret is deployment-only in both apps: a Key Vault secret named synthetic-traffic-secret mapped onto GatewayRateLimiting__SyntheticTrafficSecret and present only when the parameter is supplied (MMCA.ADC/infra/main.bicep:979-982, :1690; MMCA.Store/infra/main.bicep:924-927), and ADC's scheduled load test presents it as X-Synthetic-Traffic-Key (MMCA.ADC/.github/workflows/load-test.yml:8-11). TrustedCallerSecret is read on the calling-host side by MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Security/TrustedCallerHandler.cs and DependencyInjection.cs.
    1580. Caveats / not-in-source: The range bounds constrain each value independently; nothing checks that GlobalConcurrencyLimit is sensible relative to PermitLimit, or that a bypass prefix matches a real route. The multiplication by replica count is documented on the type but not enforced or measured anywhere in source. Neither secret has a rotation mechanism in source.

    1581. @@ -539,20 +539,21 @@

      GatewayRateLimitingExtensions

    1582. Depends on: GatewayRateLimitingSettings (its configuration), plus System.Threading.RateLimiting (PartitionedRateLimiter, RateLimitPartition, FixedWindowRateLimiterOptions, ConcurrencyLimiterOptions), CryptographicOperations (BCL cryptography), IServiceCollection / IApplicationBuilder, and Validator (BCL data annotations).
    1583. Concept introduced, two limiters that answer two different questions. [Rubric §11, Security], [Rubric §12, Performance & Scalability], [Rubric §29, Resilience]. The class doc (:13-33) states both halves. First, the edge posture is the deliberate opposite of the service tier's: AddCommonRateLimiting in MMCA.Common.API partitions by authenticated user and exempts anonymous traffic, but at the edge there is no authenticated identity yet (the gateway forwards the token, it does not validate it into a principal) and anonymous traffic is precisely what has to be bounded, so the partition key is the client IP and anonymity is not an exemption. Second, the two limiters are chained rather than merged because a fixed window bounds how fast one caller may arrive, while a concurrency limiter bounds how much work the replica holds at once regardless of who sent it; a request must satisfy both, and the second failure is one a per-IP window structurally cannot see. ADR-088 extends ADR-019 with this fourth tier.
    1584. Concept introduced, a constant-time secret comparison at the edge. [Rubric §11, Security], [Rubric §14, Testability]. PresentsSecret, the shared check behind both bypasses, is small but is the only place in this chapter that handles a secret, and its remarks name three separate defences that are easy to get wrong individually: the comparison runs over UTF-8 bytes through CryptographicOperations.FixedTimeEquals, which is constant time for equal-length inputs and returns false for unequal lengths without leaking anything past the length an attacker already chose; exactly one header value is required, so a caller cannot spray candidates in a multi-valued header; and the whole path is short-circuited when no secret is configured, so a bypass cannot be claimed by presenting the header alone.
    1585. -
    1586. Concept introduced, two independent secrets exempting the same partition. [Rubric §11, Security], [Rubric §9, API & Contract Design]. IsSyntheticTraffic and IsTrustedInternalCaller are two distinct checks over two distinct header/secret pairs, but they both fall through the same PresentsSecret helper and both feed the same IsExemptFromLimiters union. The comment on IsTrustedInternalCaller states the case it generalizes the synthetic-traffic bypass for: a server-rendered UI host makes every back-end call, token refreshes above all, from ONE address on behalf of every signed-in visitor, so the per-IP window collapses the whole site into one partition under ordinary load. Keeping the two secrets separate properties (never merged into one "bypass secret") is deliberate: a load-test runner's credential and a UI host's credential are granted, rotated and revoked independently.
    1587. +
    1588. Concept introduced, two independent secrets exempting the same partition. [Rubric §11, Security], [Rubric §9, API & Contract Design]. IsSyntheticTraffic and IsTrustedInternalCaller are two distinct checks over two distinct header/secret pairs, but they both fall through the same PresentsSecret helper and both feed the same IsExemptFromLimiters union. The comment on IsTrustedInternalCaller states the case it generalizes the synthetic-traffic bypass for: a server-rendered UI host makes every back-end call, token refreshes above all, from ONE address on behalf of every signed-in visitor, so the per-IP window collapses the whole site into one partition under ordinary load. Keeping the two secrets separate properties (never merged into one "bypass secret") is deliberate: a load-test runner's credential and a UI host's credential are granted, rotated and revoked independently. The trusted-caller proof is also the only one that outlives this limiter: the per-IP partition stamps it into HttpContext.Items so the named per-route policies of GatewayRoutePolicyExtensions, which run after the global limiter and cannot see these settings, honor the same exemption without re-checking the secret.
    1589. Walkthrough:
      • Three partition-key constants (:46, :49, :52): "__bypass" for anything exempt, "__unknown-ip" for an unattributable caller, and "__gateway", the single key the concurrency limiter counts everything against.
      • -
      • AlwaysBypassedPrefixes = ["/health", "/alive", "/.well-known"] (:59). The comment above it (:54-58) is the reason these are hard-coded rather than configured: probes and JWKS discovery run at high frequency by design, and throttling them converts a traffic spike into a failed probe and a container restart.
      • -
      • IsBypassed(path, settings) (:71) concatenates the hard-coded prefixes with the host's configured BypassPathPrefixes and matches with PathString.StartsWithSegments(..., OrdinalIgnoreCase) (:76-79), skipping blank entries. Segment matching is the detail: /healthz is not matched by /health. It is internal rather than private so the matcher is unit-testable.
      • -
      • IsSyntheticTraffic(httpContext, settings) (:98) delegates straight to PresentsSecret with SyntheticTrafficHeaderName/SyntheticTrafficSecret (:103-106).
      • -
      • IsTrustedInternalCaller(httpContext, settings) (:131) delegates the same way with TrustedCallerHeaderName/TrustedCallerSecret (:136-139), the exemption added for a trusted server-to-server caller.
      • -
      • PresentsSecret(httpContext, headerName, expectedSecret) (:146) is the shared implementation both delegate to: returns false when either the secret or the header name is blank (:148-151), requires exactly one header value (:153-156), and finishes with CryptographicOperations.FixedTimeEquals over the UTF-8 bytes of both sides (:159-162).
      • -
      • IsExemptFromLimiters(httpContext, settings) (:172) is the union both partition functions now call: bypassed path, or proven synthetic traffic, or a proven trusted internal caller (:173-175).
      • -
      • ClientIpPartition(httpContext, settings) (:185) returns RateLimitPartition.GetNoLimiter(BypassPartitionKey) when IsExemptFromLimiters applies (:192-195); returns GetNoLimiter(UnknownIpPartitionKey) when Connection.RemoteIpAddress is null (:197-202), a fail-open choice whose comment explains the alternative it rejects, that collapsing every unattributable request into one shared bucket would throttle an in-process TestServer and the integration tier to a standstill. Otherwise it returns a fixed-window limiter keyed on the IP string, with PermitLimit and Window from settings and QueueLimit = 0 (:205-212), so overage is rejected immediately rather than queued.
      • -
      • ConcurrencyPartition(httpContext, settings) (:223) applies the same IsExemptFromLimiters test and otherwise returns one concurrency limiter on the single "__gateway" key with GlobalConcurrencyLimit permits and, again, QueueLimit = 0 (:230-236).
      • -
      • AddGatewayRateLimiting(IConfiguration configuration) (:249, in an extension(IServiceCollection services) block) binds the "GatewayRateLimiting" section through AddOptions().Bind(section).ValidateDataAnnotations().ValidateOnStart() (:254-258), then delegates to the object overload with an eagerly-bound copy, falling back to defaults when the section is absent (:260-261).
      • -
      • AddGatewayRateLimiting(GatewayRateLimitingSettings settings) (:271) is the overload every path funnels into. It runs Validator.ValidateObject(settings, ..., validateAllProperties: true) at registration (:279), with the reasoning inline in the comment above it: the limiter closes over this settings instance rather than resolving IOptions per request, so an out-of-range value (or a secret under 32 characters, synthetic or trusted-caller) has to throw where it is configured, not at the first throttle. It then calls AddRateLimiter (:281), sets RejectionStatusCode to 429 (:283), and assigns options.GlobalLimiter = PartitionedRateLimiter.CreateChained(...) over the two partition functions (:288-292). Assignment rather than AddPolicy is deliberate: calling the method twice replaces the limiter instead of throwing on a duplicate policy name.
      • -
      • UseGatewayRateLimiting() (:305, in an extension(IApplicationBuilder app) block) null-guards (:307) and returns app.UseRateLimiter() (:308). Its doc comment carries the ordering contract: after UseForwardedHeaders() so the client IP is the caller's rather than the ingress's, and before the proxy is mapped.
      • +
      • TrustedInternalCallerItemKey = "MMCA.Common.Gateway.TrustedInternalCaller" (:62) is the HttpContext.Items key the per-IP partition stamps once a request has proven the trusted-caller secret. Its doc comment (:54-61) explains why the literal is duplicated rather than shared: the named per-route policies of MMCA.Common.Gateway read it (the same literal is GatewayRoutePolicyExtensions.TrustedInternalCallerItemKey, MMCA.Common/Source/Hosting/MMCA.Common.Gateway/RateLimiting/GatewayRoutePolicyExtensions.cs:38, read at :99), the global limiter runs before the endpoint policy in the same middleware, and the Gateway package deliberately takes no reference to this one, so the proof travels on the request rather than being re-derived.
      • +
      • AlwaysBypassedPrefixes = ["/health", "/alive", "/.well-known"] (:69). The comment above it (:64-68) is the reason these are hard-coded rather than configured: probes and JWKS discovery run at high frequency by design, and throttling them converts a traffic spike into a failed probe and a container restart.
      • +
      • IsBypassed(path, settings) (:81) concatenates the hard-coded prefixes with the host's configured BypassPathPrefixes and matches with PathString.StartsWithSegments(..., OrdinalIgnoreCase) (:86-89), skipping blank entries. Segment matching is the detail: /healthz is not matched by /health. It is internal rather than private so the matcher is unit-testable.
      • +
      • IsSyntheticTraffic(httpContext, settings) (:108) delegates straight to PresentsSecret with SyntheticTrafficHeaderName/SyntheticTrafficSecret (:113-116).
      • +
      • IsTrustedInternalCaller(httpContext, settings) (:141) delegates the same way with TrustedCallerHeaderName/TrustedCallerSecret (:146-149), the exemption added for a trusted server-to-server caller.
      • +
      • PresentsSecret(httpContext, headerName, expectedSecret) (:156) is the shared implementation both delegate to: returns false when either the secret or the header name is blank (:158-161), requires exactly one header value (:163-167), and finishes with CryptographicOperations.FixedTimeEquals over the UTF-8 bytes of both sides (:169-172).
      • +
      • IsExemptFromLimiters(httpContext, settings) (:182) is the union both partition functions call: bypassed path, or proven synthetic traffic, or a proven trusted internal caller (:183-185).
      • +
      • ClientIpPartition(httpContext, settings) (:195) first tests IsTrustedInternalCaller on its own (:202-208): on a match it sets httpContext.Items[TrustedInternalCallerItemKey] = true and returns RateLimitPartition.GetNoLimiter(BypassPartitionKey), the inline comment naming the reason (the named per-route policies run after this limiter and cannot see these settings). It then returns the same no-limiter partition when IsExemptFromLimiters applies (:210-213); returns GetNoLimiter(UnknownIpPartitionKey) when Connection.RemoteIpAddress is null (:215-221), a fail-open choice whose comment explains the alternative it rejects, that collapsing every unattributable request into one shared bucket would throttle an in-process TestServer and the integration tier to a standstill. Otherwise it returns a fixed-window limiter keyed on the IP string, with PermitLimit and Window from settings and QueueLimit = 0 (:223-230), so overage is rejected immediately rather than queued. Only this partition stamps the item: ConcurrencyPartition does not.
      • +
      • ConcurrencyPartition(httpContext, settings) (:241) applies the same IsExemptFromLimiters test and otherwise returns one concurrency limiter on the single "__gateway" key with GlobalConcurrencyLimit permits and, again, QueueLimit = 0 (:248-255).
      • +
      • AddGatewayRateLimiting(IConfiguration configuration) (:267, in an extension(IServiceCollection services) block) binds the "GatewayRateLimiting" section through AddOptions().Bind(section).ValidateDataAnnotations().ValidateOnStart() (:272-276), then delegates to the object overload with an eagerly-bound copy, falling back to defaults when the section is absent (:278-279).
      • +
      • AddGatewayRateLimiting(GatewayRateLimitingSettings settings) (:289) is the overload every path funnels into. It runs Validator.ValidateObject(settings, ..., validateAllProperties: true) at registration (:297), with the reasoning inline in the comment above it: the limiter closes over this settings instance rather than resolving IOptions per request, so an out-of-range value (or a secret under 32 characters, synthetic or trusted-caller) has to throw where it is configured, not at the first throttle. It then calls AddRateLimiter (:299), sets RejectionStatusCode to 429 (:301), and assigns options.GlobalLimiter = PartitionedRateLimiter.CreateChained(...) over the two partition functions (:306-310). Assignment rather than AddPolicy is deliberate: calling the method twice replaces the limiter instead of throwing on a duplicate policy name.
      • +
      • UseGatewayRateLimiting() (:323, in an extension(IApplicationBuilder app) block) null-guards (:325) and returns app.UseRateLimiter() (:326). Its doc comment carries the ordering contract: after UseForwardedHeaders() so the client IP is the caller's rather than the ingress's, and before the proxy is mapped.
    1590. Why it's built this way: Pure static partition functions with internal visibility make the decisions that actually contain the policy (which paths are exempt, which requests prove which secret, which partition a request lands in) assertable without a running server, which is [Rubric §14, Testability] applied at the smallest useful granularity. Both secret checks funneling through one PresentsSecret private helper means the constant-time comparison and the multi-value guard are written and reviewed once, not twice. Validating in the object overload rather than only through the options pipeline closes the second way in. And the in-memory, per-replica design is accepted rather than solved (see GatewayRateLimitingSettings): an approximate ceiling that needs no network call and cannot fail is the right shape for the one process the whole fleet sits behind. The class carries the same scoped CA1708 suppression as its siblings.
    1591. @@ -679,7 +680,7 @@

      SecurityHeadersSettings

    1592. Why it's built this way: Sealed and mutable (get; set;, not init-only) so the configure delegate and IOptions binding can adjust the defaults before the middleware starts. CredentialPathPrefixes is exposed as a get-only IList<string> rather than a settable property, so a host augments the default list in place (.Add(...)) instead of replacing it outright. The complete-baseline default is codified in ADR-023, whose Status block records the 2026-09-01 revision that replaced the earlier deliberately-incomplete baseline (which omitted script-src and style-src to avoid breaking a Blazor host) with one that ships both directives at Blazor and MudBlazor strength. The trade-off moved from "safe but partial" to "functional and still hardened", and the property's own doc comment carries the reasoning rather than leaving it only in the ADR.
    1593. -
    1594. Where it's used: Bound and registered by SecurityHeadersExtensions.AddCommonSecurityHeaders (SecurityHeaders.cs:252-256); read by StaticCspPolicyProvider (:86-89) and SecurityHeadersMiddleware, whose IsCredentialPath helper reads CredentialPathPrefixes on every request to decide the Referrer-Policy and cache-control headers. Both apps' UI hosts override EnableHsts to false through the configure delegate (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:119, MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:140), while both Gateways take the defaults (MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/Program.cs:118, MMCA.Store/Source/Hosts/MMCA.Store.Gateway/Program.cs:71).
    1595. +
    1596. Where it's used: Bound and registered by SecurityHeadersExtensions.AddCommonSecurityHeaders (SecurityHeaders.cs:252-256); read by StaticCspPolicyProvider (:86-89) and SecurityHeadersMiddleware, whose IsCredentialPath helper reads CredentialPathPrefixes on every request to decide the Referrer-Policy and cache-control headers. Both apps' UI hosts override EnableHsts to false through the configure delegate (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:120, MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:140), while both Gateways take the defaults (MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/Program.cs:118, MMCA.Store/Source/Hosts/MMCA.Store.Gateway/Program.cs:71).
    1597. Caveats / not-in-source: X-Content-Type-Options has no setting; the middleware hard-codes nosniff, so it is the one header a host cannot weaken through configuration. Matching against CredentialPathPrefixes is segment-based (StartsWithSegments), so /reset-password covers /reset-password/anything but never /reset-passwords; source shows no validation that a configured prefix actually corresponds to a route either app declares.

    1598. @@ -701,7 +702,7 @@

      SerilogHostExtensions

    1599. Why it's built this way: A static class with one extension(WebApplicationBuilder) block, the codebase's standard registration idiom (see primer, C# extension(T) types), which is also why it carries the scoped CA1708 suppression with the false-positive justification spelled out (:23-26). The invariant that matters (exactly one Serilog provider, never a factory replacement) is guarded by a framework test that asserts the built container contains exactly one SerilogLoggerProvider (MMCA.Common/Tests/Hosting/MMCA.Common.Aspire.Tests/Logging/SerilogHostExtensionsTests.cs:139), which ADR-041 flags as guarded in the framework rather than at the consumer: nothing in a host's own build stops a new service from reaching for UseSerilog().
    1600. -
    1601. Where it's used: All seven ADC and Store service hosts, each pairing AddCommonSerilog with CreateBootstrapLoggerFactory() in the same file: ADC Conference (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:104 and :307), Engagement (:82, :208), Identity (:96, :247), Notification (:85, :186); Store Catalog (MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:67 and :121), Identity (:73, :117), Sales (:84, :130).
    1602. +
    1603. Where it's used: All seven ADC and Store service hosts, each pairing AddCommonSerilog with CreateBootstrapLoggerFactory() in the same file: ADC Conference (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:106 and :307), Engagement (:82, :208), Identity (:96, :247), Notification (:85, :186); Store Catalog (MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:67 and :121), Identity (:73, :117), Sales (:84, :130).
    1604. Caveats / not-in-source: Adoption is not uniform, and the gap is visible in source. Neither Gateway references Serilog in its Program.cs, and neither does MMCA.ADC.UI.Web: those three take the plain OpenTelemetry logging that AddServiceDefaults gives them. MMCA.Store.UI.Web is a third shape, hand-rolling the same configuration inline (MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:66) and registering it as one provider (:74) without calling the helper, so the defaults exist in two places and a framework change reaches seven hosts and misses the eighth. ADR-041 records both facts as accepted costs. The helper also attaches only to WebApplicationBuilder, not to the generic IHostApplicationBuilder that AddServiceDefaults uses, so a non-web worker host cannot call it.

    1605. @@ -751,7 +752,7 @@

      ICspPolicyProvider

    1606. Concept introduced, the CSP extension point. [Rubric §11, Security] (CSP as defence in depth) and [Rubric §26, Front-End Security] (CSP protecting Blazor pages from injected script). The doc comment (SecurityHeaders.cs:78-83) states the extensibility rule: a Blazor host needing a dynamic connect-src, for example one pinned at runtime to its API origin, registers a custom ICspPolicyProvider before calling AddCommonSecurityHeaders, because that method registers the default only via TryAddSingleton (:237), so a pre-registered custom provider wins.
    1607. Walkthrough: A single method, CspPolicy? GetPolicy(HttpContext context) (SecurityHeaders.cs:87): returns the policy to emit for the current response, or null to emit none. Taking the HttpContext makes it per-request, so a provider can vary the policy by path or request properties. [Rubric §1, SOLID] is the shape: one method, one reason to change, and the middleware depends on this abstraction rather than on any concrete policy source.
    1608. Why it's built this way: A one-method interface is the minimal extension point for a per-consumer CSP allow-list; returning the CspPolicy record rather than a bare string carries the enforce-or-report decision atomically. ADR-023 explains why the indirection exists at all: a JSON/Gateway host and a Blazor/MudBlazor host need different policies (the latter must pin connect-src to an origin it only learns at runtime from configuration), and a wrong CSP hard-breaks the app, so the policy must be resolved through a provider rather than baked in as a constant.
    1609. -
    1610. Where it's used: Implemented by the default StaticCspPolicyProvider (SecurityHeaders.cs:91) and by the shared BlazorCspPolicyProvider in MMCA.Common.UI.Web, which both apps' Blazor hosts register through AddCommonBlazorCsp (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/DependencyInjection.cs:52) ahead of AddCommonSecurityHeaders (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:118-119, MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:139-140). Resolved per request inside SecurityHeadersMiddleware (SecurityHeaders.cs:203).
    1611. +
    1612. Where it's used: Implemented by the default StaticCspPolicyProvider (SecurityHeaders.cs:91) and by the shared BlazorCspPolicyProvider in MMCA.Common.UI.Web, which both apps' Blazor hosts register through AddCommonBlazorCsp (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/DependencyInjection.cs:52) ahead of AddCommonSecurityHeaders (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:119-120, MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:139-140). Resolved per request inside SecurityHeadersMiddleware (SecurityHeaders.cs:203).

    1613. KestrelEndpointExtensions

      @@ -771,7 +772,7 @@

      KestrelEndpointExtensions

    1614. Why it's built this way: The decision is separated from the binding so it can be asserted as data (see KestrelListenerSpec). The probe port is configuration-gated rather than always-on so local and test runs keep Aspire's dynamic ports. Failing fast rather than failing quiet follows the same contract as ADR-070: a misconfiguration whose only symptom is a silently unprobeable revision is worth a startup crash. And the helper lives in the shared framework package rather than in each service, which is what let seven service hosts across two apps collapse onto one implementation.
    1615. -
    1616. Where it's used: All four ADC services, three on the h2c profile (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:83, MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:89, MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:68) and Notification on the mixed profile (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:74, passing HttpProtocols.Http1AndHttp2, redeclareCleartextEndpoint: false); plus all three Store services, two on h2c (MMCA.Store/Source/Services/MMCA.Store.Identity.Service/Program.cs:64, MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:58) and Sales on the mixed profile (MMCA.Store/Source/Services/MMCA.Store.Sales.Service/Program.cs:75). Covered by KestrelEndpointExtensionsTests, whose cases pin the empty-plan, blank-value, fail-fast, both-profile, always-Http1, and custom-cleartext-port behaviors.
    1617. +
    1618. Where it's used: All four ADC services, three on the h2c profile (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:84, MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:91, MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:70) and Notification on the mixed profile (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:73, passing HttpProtocols.Http1AndHttp2, redeclareCleartextEndpoint: false); plus all three Store services, two on h2c (MMCA.Store/Source/Services/MMCA.Store.Identity.Service/Program.cs:64, MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/Program.cs:58) and Sales on the mixed profile (MMCA.Store/Source/Services/MMCA.Store.Sales.Service/Program.cs:75). Covered by KestrelEndpointExtensionsTests, whose cases pin the empty-plan, blank-value, fail-fast, both-profile, always-Http1, and custom-cleartext-port behaviors.
    1619. Caveats / not-in-source: The probe port is supplied per app by the Container Apps Bicep rather than by anything in application code, so a host whose deployment omits HealthProbe__Port simply gets the empty plan and answers probes on its configured endpoints. Source does not state why the two mixed-profile hosts (ADC Notification and Store Sales) are the ones configured from appsettings.json.

    1620. @@ -842,7 +843,7 @@

      SecurityHeadersMiddleware

    1621. Why it's built this way: A sealed conventional middleware (constructor plus InvokeAsync), not an IMiddleware, so it is a singleton per pipeline with zero per-request resolution. Resolving CSP through the injected ICspPolicyProvider rather than reading settings directly is what makes per-host dynamic policies possible while every other header stays uniform. Computing _enableHsts once in the constructor avoids re-checking the environment on every request, and the nonce generation is gated on the placeholder being present, so a policy that does not ask for one pays no cryptographic-RNG call at all. IsCredentialPath is evaluated per request against configuration rather than hard-coded, so a host extends the credential-path list (both apps' /reset-password and /auth/oauth-complete routes today) without a code change. This single middleware is ADR-023's "one hardened default, defined once" decision: centralising the header set removes the per-host drift between Gateway and UI (and between apps) that came from each edge host hand-rolling its own headers, and makes a new edge host secure by default.
    1622. -
    1623. Where it's used: Added to the pipeline by SecurityHeadersExtensions.UseCommonSecurityHeaders (SecurityHeaders.cs:274). Per ADR-023 it is wired at both edges of both apps: MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/Program.cs:168, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:198, MMCA.Store/Source/Hosts/MMCA.Store.Gateway/Program.cs:159, and MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:145. Covered by SecurityHeadersMiddlewareTests in MMCA.Common.Aspire.Tests, whose nonce cases assert that every placeholder occurrence is replaced (MMCA.Common/Tests/Hosting/MMCA.Common.Aspire.Tests/Security/SecurityHeadersMiddlewareTests.cs:171-173), and by the shared per-host SecurityHeadersTestsBase that both Gateway test projects subclass.
    1624. +
    1625. Where it's used: Added to the pipeline by SecurityHeadersExtensions.UseCommonSecurityHeaders (SecurityHeaders.cs:274). Per ADR-023 it is wired at both edges of both apps: MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/Program.cs:168, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:210, MMCA.Store/Source/Hosts/MMCA.Store.Gateway/Program.cs:159, and MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:145. Covered by SecurityHeadersMiddlewareTests in MMCA.Common.Aspire.Tests, whose nonce cases assert that every placeholder occurrence is replaced (MMCA.Common/Tests/Hosting/MMCA.Common.Aspire.Tests/Security/SecurityHeadersMiddlewareTests.cs:171-173), and by the shared per-host SecurityHeadersTestsBase that both Gateway test projects subclass.
    1626. Caveats / not-in-source: The headers are set on the way in, so a downstream component that removes or overwrites Response.Headers after this point wins; nothing here re-asserts them on the way out. IsCredentialPath recomputes the segment match twice per credential-path request (once for Referrer-Policy, once for the cache-control branch); source shows no caching of that boolean within one InvokeAsync call.

    1627. @@ -874,7 +875,7 @@

      SecurityHeadersExtensions

    1628. Why it's built this way: A static class with extension(T) blocks is the codebase's standard DI and registration idiom (see primer, C# extension(T) types and ADR-106). The TryAddSingleton ordering contract is what makes the per-consumer CSP override work without a configuration flag or a builder API. ADR-023 documents the matching foot-gun: because the default provider is TryAdd-registered, a host must register its custom ICspPolicyProvider before calling AddCommonSecurityHeaders, or the static default silently wins.
    1629. -
    1630. Where it's used: Called by each client-facing host: MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/Program.cs:118, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:119, MMCA.Store/Source/Hosts/MMCA.Store.Gateway/Program.cs:71, MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:140. The two UI hosts call AddCommonBlazorCsp() first (ADC Program.cs:99, Store Program.cs:139, each with the ordering comment right above it at ADC :97-98 and Store :137-138) so BlazorCspPolicyProvider wins over the StaticCspPolicyProvider default.
    1631. +
    1632. Where it's used: Called by each client-facing host: MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/Program.cs:118, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:120, MMCA.Store/Source/Hosts/MMCA.Store.Gateway/Program.cs:71, MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:140. The two UI hosts call AddCommonBlazorCsp() first (ADC Program.cs:99, Store Program.cs:139, each with the ordering comment right above it at ADC :97-98 and Store :137-138) so BlazorCspPolicyProvider wins over the StaticCspPolicyProvider default.
    1633. Caveats / not-in-source: Nothing enforces the registration order at build time; the ordering contract is documented in the doc comments, in both hosts' inline comments, and in ADR-023, but a host that got it backwards would silently ship the static baseline instead of its own policy.
    1634. Extensions

      @@ -1031,7 +1032,7 @@

      IWarmupTask

    1635. Concept introduced, startup warm-up. [Rubric §29, Resilience & Business Continuity] assesses proactive elimination of cold-start failure modes; [Rubric §12, Performance & Scalability] assesses whether latency is predictable rather than paid by whoever arrives first. After a deployment, or after an idle Azure Container Apps replica is scaled back up, the first requests hit cold paths: EF model build, connection-pool establishment, JIT, OIDC discovery. Warm-up tasks run those eagerly. Per the interface doc comment (IWarmupTask.cs:3-8), tasks run in parallel after host start, and failures are logged but do not prevent the readiness gate from opening, so a transient dependency outage cannot wedge a replica permanently out of rotation.
    1636. Walkthrough: string Name { get; } (IWarmupTask.cs:12), a stable identifier used in the runner's structured logs and, per the doc comment, in metrics. Task ExecuteAsync(CancellationToken cancellationToken) (:15) performs the work; the token is the host's stoppingToken, passed through by the runner.
    1637. Why it's built this way: An interface rather than an abstract class, for maximum implementation freedom. The one place the framework does supply a base class is the self-HTTP shape (SelfHttpWarmupTaskBase), where the mechanics rather than the contract are the shared risk. The non-fatal-failure contract is enforced by the runner (WarmupHostedService), not by the interface, so implementations stay simple and never reason about their own failure semantics. ADR-025 records the decision.
    1638. -
    1639. Where it's used: Discovered via DI. AddWarmupReadiness registers the built-in OpenIdConnectMetadataWarmupTask (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:96), and a host adds its own with AddWarmupTask<TTask>() (Extensions.cs:114, whose body is the single services.AddSingleton<IWarmupTask, TTask>() at :351). The whole IEnumerable<IWarmupTask> is injected into WarmupHostedService (Warmup/WarmupHostedService.cs:29).
    1640. +
    1641. Where it's used: Discovered via DI. AddWarmupReadiness registers the built-in OpenIdConnectMetadataWarmupTask (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:104), and a host adds its own with AddWarmupTask<TTask>() (Extensions.cs:122, whose body is the single services.AddSingleton<IWarmupTask, TTask>() at :351). The whole IEnumerable<IWarmupTask> is injected into WarmupHostedService (Warmup/WarmupHostedService.cs:29).
    1642. Caveats / not-in-source: The interface carries no timeout, no ordering, and no retry. All three are the runner's business: tasks run concurrently in whatever order Task.WhenAll schedules them (WarmupHostedService.cs:53-54), each under a 120-second ceiling (:42, applied at :69), and a failed task is never retried by the subsystem, only re-paid lazily on the first real request.

    1643. @@ -1045,7 +1046,7 @@

      WarmupReadinessGate

    1644. Concept introduced, the readiness probe. [Rubric §29, Resilience & Business Continuity], [Rubric §17, DevOps], [Rubric §13, Observability & Operability]. Azure Container Apps (and Kubernetes) withhold traffic from a replica until its readiness probe returns healthy. The health check reports unhealthy until IsReady is true, so a fresh replica does not receive production traffic until warm-up has had its chance, avoiding slow or failed first requests (ADR-025). Note the deliberate split from liveness: readiness is what holds traffic back, while /alive stays narrow so a dependency outage never restarts the container (see HealthCheckTags).
    1645. Walkthrough: private int _isReady (WarmupReadinessGate.cs:12), 0 or 1. Using an int with Volatile.Read and Interlocked.Exchange rather than a bool plus a lock gives lock-free, correctly published reads and writes. IsReady => Volatile.Read(ref _isReady) == 1 (:15): the volatile read prevents a CPU from returning a stale cached value, which matters because the writer is a background thread and the readers are probe requests on the thread pool. internal void MarkReady() => Interlocked.Exchange(ref _isReady, 1) (:18): idempotent, safe to call twice, and the doc comment says so.
    1646. Why it's built this way: Sealed, so no subclass can reinterpret readiness. MarkReady() is internal (:18), so no external caller can prematurely open the gate; only WarmupHostedService, in the same assembly, may. The type carries no "not ready again" transition on purpose: readiness here means "warm-up has run", a startup fact rather than a live health signal, and the live signals are the separate dependency checks that also sit on /health/ready.
    1647. -
    1648. Where it's used: Registered as a singleton by AddWarmupReadiness (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:93); opened in the finally of WarmupHostedService.ExecuteAsync (Warmup/WarmupHostedService.cs:58); read by WarmupReadinessHealthCheck.CheckHealthAsync (Warmup/WarmupReadinessHealthCheck.cs:14), which is registered as the "warmup" check tagged HealthCheckTags.Ready (Extensions.cs:98-99) and surfaced at /health/ready (Extensions.Health.cs:123-159). Covered by WarmupReadinessGateTests.
    1649. +
    1650. Where it's used: Registered as a singleton by AddWarmupReadiness (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:101); opened in the finally of WarmupHostedService.ExecuteAsync (Warmup/WarmupHostedService.cs:58); read by WarmupReadinessHealthCheck.CheckHealthAsync (Warmup/WarmupReadinessHealthCheck.cs:14), which is registered as the "warmup" check tagged HealthCheckTags.Ready (Extensions.cs:106-107) and surfaced at /health/ready (Extensions.Health.cs:123-159). Covered by WarmupReadinessGateTests.

    1651. GatewayHealthCheckDefaults

      @@ -1074,7 +1075,7 @@

      GatewayRoutePolicySettings

    1652. PermitLimit = 30 (:219), [Range(1, int.MaxValue)] (:218): requests allowed per window.
    1653. WindowSeconds = 60 (:223), [Range(1, 3600)] (:222): window length, capped at an hour.
    1654. QueueLimit (:230), [Range(0, 10_000)] (:229): defaults to zero, and the doc gives the reason (:225-228): a queue at the edge converts a throttle into latency the caller cannot see the cause of. Rejecting immediately with a 429 is an answer the caller can act on.
    1655. -
    1656. GlobalPartitionKey = "__global" (:233), internal const: the single key used by Partition = Global, and reused as the (unused) key of the no-limiter partition in GatewayRoutePolicyExtensions.Partition (MMCA.Common/Source/Hosting/MMCA.Common.Gateway/RateLimiting/GatewayRoutePolicyExtensions.cs:89).
    1657. +
    1658. GlobalPartitionKey = "__global" (:233), internal const: the single key used by Partition = Global, and reused as the (unused) key of the no-limiter partition in GatewayRoutePolicyExtensions.Partition (MMCA.Common/Source/Hosting/MMCA.Common.Gateway/RateLimiting/GatewayRoutePolicyExtensions.cs:107).
    1659. PartitionKey(IPAddress? remoteIpAddress) (:243-246), internal: returns GlobalPartitionKey for a global policy, otherwise remoteIpAddress?.ToString(), which is null when the address is unresolvable. Internal rather than private so the selection is unit-testable without going through a live limiter, which the doc states (:242).
    1660. @@ -1093,8 +1094,8 @@

      OpenIdConnectMetadataWarmupTask

    1661. Concept introduced: [Rubric §29, Resilience & Business Continuity]. The doc comment (OpenIdConnectMetadataWarmupTask.cs:6-13) names the failure mode precisely: without warm-up, the JWT bearer middleware fetches {authority}/.well-known/openid-configuration lazily on the first authenticated request, and on a CPU-throttled idle ACA Consumption replica that fetch can stretch past the client timeout, the textbook "first request fails, second succeeds" pattern. Pre-fetching warms DNS, TCP, TLS, and the HttpClient pool. The honest caveat is stated in the type's own remarks (:14-20): the middleware's ConfigurationManager caches discovery state separately, so it still performs its own fetch on the first request; the intent is that the fetch now runs over a warm connection and completes in single-digit milliseconds.
    1662. Walkthrough: A primary constructor injects IHttpClientFactory, IConfiguration, and ILogger<OpenIdConnectMetadataWarmupTask> (:21-24). Name => "OpenIdConnectMetadata" (:26). ExecuteAsync (:28) reads Authentication:JwtBearer:Authority (:30) and returns immediately when it is unset (:31-34), so a non-authenticating host pays nothing. It then builds the discovery URI by trimming a trailing slash and appending /.well-known/openid-configuration (:36-39), logging a warning and returning if the result is not a valid absolute URI (:41-42). Finally it creates a client from the factory under the task type's own name (:45) and issues a GET (:46), logging the status code (:48). Both log lines are source-generated [LoggerMessage] partial methods (:51-57): EventId 1 at Warning for the invalid authority, EventId 2 at Information for the fetch.
    1663. Why it's built this way: internal sealed partial. Internal because it is wired by the framework's AddWarmupReadiness rather than by consumers, partial for the [LoggerMessage] source generator, sealed by default policy. Reusing IHttpClientFactory is the load-bearing detail: the warm-up exercises exactly the connection pool (and the tuned SocketsHttpHandler from HttpResilienceDefaults) that the real auth path will use, so the warmth transfers. It reads the same configuration key the AppHost's JWKS discovery wiring injects, which is what makes the task self-configuring rather than needing its own settings class. ADR-025 records both the decision and the middleware-cache caveat.
    1664. -
    1665. Where it's used: Registered as an IWarmupTask singleton by AddWarmupReadiness (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:96), so every host that calls AddServiceDefaults gets it; executed in parallel with any host-registered tasks by WarmupHostedService.
    1666. -
    1667. Caveats / not-in-source: The HTTP call passes no explicit timeout of its own. It is bounded twice from outside: by the shared Polly total-request timeout that ConfigureHttpClientDefaults applies to every factory client (Extensions.cs:52, 90 seconds from HttpResilienceDefaults, MMCA.Common/Source/Core/MMCA.Common.Shared/Resilience/HttpResilienceDefaults.cs:19), and by the runner's 120-second per-task ceiling (Warmup/WarmupHostedService.cs:42). A non-2xx discovery response is logged at Information like any other status (:48) rather than treated as a failure, so a 404 authority warms the connection and reports success.
    1668. +
    1669. Where it's used: Registered as an IWarmupTask singleton by AddWarmupReadiness (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:104), so every host that calls AddServiceDefaults gets it; executed in parallel with any host-registered tasks by WarmupHostedService.
    1670. +
    1671. Caveats / not-in-source: The HTTP call passes no explicit timeout of its own. It is bounded twice from outside: by the shared Polly total-request timeout that ConfigureHttpClientDefaults applies to every factory client (Extensions.cs:53, 90 seconds from HttpResilienceDefaults, MMCA.Common/Source/Core/MMCA.Common.Shared/Resilience/HttpResilienceDefaults.cs:19), and by the runner's 120-second per-task ceiling (Warmup/WarmupHostedService.cs:42). A non-2xx discovery response is logged at Information like any other status (:48) rather than treated as a failure, so a 404 authority warms the connection and reports success.

    1672. SelfHttpWarmupTaskBase

      @@ -1125,7 +1126,7 @@

      SelfHttpWarmupTaskBase

    1673. The subsystem as a whole is ADR-025, and the HTTP/2-only cleartext endpoints this class must speak to are ADR-012.
    1674. -
    1675. Where it's used: Never registered by the framework. A host derives a task and registers it with AddWarmupTask<TTask>() (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:114). ADC has three derived tasks: SelfHttpOutputCacheWarmupTask in Conference, which replays a fixed list of anonymous read URLs (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/SelfHttpOutputCacheWarmupTask.cs:59, :62, registered at .../MMCA.ADC.Conference.Service/Program.cs:256), plus a SelfHttpWarmupTask in Engagement (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/SelfHttpWarmupTask.cs:39, :42, registered at Program.cs:157) and a SelfHttpWarmupTask in Identity (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/SelfHttpWarmupTask.cs:39, :42, registered at Program.cs:169), both of which override RequireSuccessStatusCode to false because their one path is protected (:49 in each). Store has three more of the same shape (Identity Program.cs:144, Catalog Program.cs:162, Sales Program.cs:157); its Catalog task takes the defaults outright (MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/SelfHttpOutputCacheWarmupTask.cs:46, :49), while its Sales task is the one that overrides the version pin as well, staying on HTTP/1.1 with RequestVersionOrLower because that host serves no inbound gRPC (MMCA.Store/Source/Services/MMCA.Store.Sales.Service/SelfHttpOutputCacheWarmupTask.cs:56, :59). Behavior is covered by SelfHttpWarmupTaskBaseTests, which drives a configurable subclass against real Kestrel listeners started as Http2 or Http1.
    1676. +
    1677. Where it's used: Never registered by the framework. A host derives a task and registers it with AddWarmupTask<TTask>() (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:122). ADC has three derived tasks: SelfHttpOutputCacheWarmupTask in Conference, which replays a fixed list of anonymous read URLs (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/SelfHttpOutputCacheWarmupTask.cs:65, :62, registered at .../MMCA.ADC.Conference.Service/Program.cs:256), plus a SelfHttpWarmupTask in Engagement (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/SelfHttpWarmupTask.cs:39, :42, registered at Program.cs:157) and a SelfHttpWarmupTask in Identity (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/SelfHttpWarmupTask.cs:39, :42, registered at Program.cs:169), both of which override RequireSuccessStatusCode to false because their one path is protected (:49 in each). Store has three more of the same shape (Identity Program.cs:144, Catalog Program.cs:162, Sales Program.cs:157); its Catalog task takes the defaults outright (MMCA.Store/Source/Services/MMCA.Store.Catalog.Service/SelfHttpOutputCacheWarmupTask.cs:46, :49), while its Sales task is the one that overrides the version pin as well, staying on HTTP/1.1 with RequestVersionOrLower because that host serves no inbound gRPC (MMCA.Store/Source/Services/MMCA.Store.Sales.Service/SelfHttpOutputCacheWarmupTask.cs:56, :59). Behavior is covered by SelfHttpWarmupTaskBaseTests, which drives a configurable subclass against real Kestrel listeners started as Http2 or Http1.
    1678. Caveats / not-in-source:
      • A required-success failure abandons the remaining paths. EnsureSuccessStatusCode throws out of the loop into the outer catch (:127, :142), so with the default profile the first bad path ends the run and the later ones are never warmed.
      • The value-exactness rule is a convention, not a check. Nothing verifies that a derived task's WarmupPaths match the URLs real callers build, so a drifted query string warms a cache entry no caller reads, silently and with a success log. The derived tasks record the mapping in comments.
      • @@ -1149,8 +1150,8 @@

        WarmupHostedService

      • Four source-generated [LoggerMessage] methods (:91-105): LogTaskCompleted (:93, EventId 1, Information), LogTaskFailed (:97, EventId 2, Warning, carrying the exception), LogWarmupComplete (:101, EventId 3, Information), and LogTaskTimedOut (:105, EventId 4, Warning, carrying the exception plus the limit in seconds so the log line names the ceiling that fired).
    1679. -
    1680. Why it's built this way: internal sealed partial. Running the tasks with Task.WhenAll rather than sequentially means total warm-up time is the slowest task, not the sum. The finally-open-the-gate pattern is the resilience invariant from IWarmupTask's contract, expressed in the one place that can enforce it, and the per-task WaitAsync is what makes that invariant hold against a task that hangs instead of throwing. Distinguishing host cancellation (rethrow) from timeout and from ordinary failure (both log and continue) keeps shutdown clean while making warm-up best-effort. Two details are deliberate: the WaitAsync overload takes a TimeProvider, which is why the constructor accepts one, so a test can drive the timeout path without a real wall-clock wait; and 120 seconds is chosen to sit above the 90-second shared Polly total-request timeout that already bounds the built-in OIDC task's HTTP call (HttpResilienceDefaults, MMCA.Common/Source/Core/MMCA.Common.Shared/Resilience/HttpResilienceDefaults.cs:19, applied in Extensions.cs:52), so per the constant's own comment (:36-41) a task that reaches this limit is one that bypassed those defaults or is waiting on something that will never arrive.
    1681. -
    1682. Where it's used: Registered as a hosted service by AddWarmupReadiness (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:94), which supplies no values for the two optional parameters, so production runs on TimeProvider.System and the 120-second default. Covered by WarmupHostedServiceTests, which constructs the service directly and passes a short taskTimeout to exercise the hanging-task case.
    1683. +
    1684. Why it's built this way: internal sealed partial. Running the tasks with Task.WhenAll rather than sequentially means total warm-up time is the slowest task, not the sum. The finally-open-the-gate pattern is the resilience invariant from IWarmupTask's contract, expressed in the one place that can enforce it, and the per-task WaitAsync is what makes that invariant hold against a task that hangs instead of throwing. Distinguishing host cancellation (rethrow) from timeout and from ordinary failure (both log and continue) keeps shutdown clean while making warm-up best-effort. Two details are deliberate: the WaitAsync overload takes a TimeProvider, which is why the constructor accepts one, so a test can drive the timeout path without a real wall-clock wait; and 120 seconds is chosen to sit above the 90-second shared Polly total-request timeout that already bounds the built-in OIDC task's HTTP call (HttpResilienceDefaults, MMCA.Common/Source/Core/MMCA.Common.Shared/Resilience/HttpResilienceDefaults.cs:19, applied in Extensions.cs:53), so per the constant's own comment (:36-41) a task that reaches this limit is one that bypassed those defaults or is waiting on something that will never arrive.
    1685. +
    1686. Where it's used: Registered as a hosted service by AddWarmupReadiness (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:102), which supplies no values for the two optional parameters, so production runs on TimeProvider.System and the 120-second default. Covered by WarmupHostedServiceTests, which constructs the service directly and passes a short taskTimeout to exercise the hanging-task case.
    1687. Caveats / not-in-source: WaitAsync abandons rather than cancels, so a timed-out task keeps running detached for the life of the host (the comment at :79-80 says exactly this); the only token that can stop it is the host's stoppingToken at shutdown. The ceiling also bounds the damage rather than removing it: a replica whose warm-up hangs stays out of rotation for the full 120 seconds before it is admitted. And the ceiling is not operator-tunable, since TaskTimeoutSeconds is a private const (:42) and the taskTimeout parameter is reachable only from a direct constructor call, with no configuration binding anywhere in the file.

    1688. @@ -1164,7 +1165,7 @@

      WarmupReadinessHealthCheck

    1689. Concept introduced: [Rubric §13, Observability & Operability], [Rubric §29, Resilience & Business Continuity]. This is the bridge between the in-process gate and the hosting platform: the readiness probe's HTTP result is driven by one boolean. The readiness-probe concept itself is taught at WarmupReadinessGate, and the tag vocabulary at HealthCheckTags.
    1690. Walkthrough: A single method, CheckHealthAsync(HealthCheckContext, CancellationToken) (WarmupReadinessHealthCheck.cs:11-16), returning Task.FromResult(gate.IsReady ? HealthCheckResult.Healthy("Warm-up complete.") : HealthCheckResult.Unhealthy("Warm-up in progress.")). It is synchronous under an async signature because reading a volatile int involves no I/O; Task.FromResult avoids a state machine entirely.
    1691. Why it's built this way: internal sealed, since the framework wires it. Keeping it trivially cheap (no I/O, no allocation beyond the result) means the platform can poll the readiness endpoint frequently without cost, which matters because probe intervals are measured in seconds. The two description strings are what an operator sees in the /health/ready payload, so they are written for a human reading a probe failure.
    1692. -
    1693. Where it's used: Registered as the "warmup" check tagged HealthCheckTags.Ready by AddWarmupReadiness (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:98-99); surfaced at /health/ready by MapDefaultEndpoints (Extensions.Health.cs:123-159), whose predicate admits every check tagged neither live nor optional (:154-156). Covered by WarmupReadinessHealthCheckTests.
    1694. +
    1695. Where it's used: Registered as the "warmup" check tagged HealthCheckTags.Ready by AddWarmupReadiness (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:106-107); surfaced at /health/ready by MapDefaultEndpoints (Extensions.Health.cs:123-159), whose predicate admits every check tagged neither live nor optional (:154-156). Covered by WarmupReadinessHealthCheckTests.

    1696. GatewaySettings

      @@ -1186,7 +1187,7 @@

      GatewaySettings

    1697. Why it's built this way: init-only properties throughout, which is what lets GatewayReverseProxyExtensions hand the whole object to DI as an IOptions<GatewaySettings> instance rather than mutating one into place (its comment says exactly that, MMCA.Common/Source/Hosting/MMCA.Common.Gateway/GatewayReverseProxyExtensions.cs:73-75). Every collection and child block has a non-null default, so a gateway that adds the package and configures nothing still gets passive health checks and trace headers, which is the "useful with zero configuration" posture the whole kit is built around.
    1698. Where it's used: bound by AddMmcaGateway(IConfiguration) with .ValidateDataAnnotations().ValidateOnStart() (GatewayReverseProxyExtensions.cs:54-57). Live sections: MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/appsettings.json:27-56 and MMCA.Store/Source/Hosts/MMCA.Store.Gateway/appsettings.json:22-40. Covered by AddMmcaGatewayTests, which binds a full section from an in-memory configuration (MMCA.Common/Tests/Hosting/MMCA.Common.Gateway.Tests/AddMmcaGatewayTests.cs:58-86), pins the section name (:100) and asserts the no-section defaults (:88-97).
    1699. -
    1700. Caveats / not-in-source: ValidateDataAnnotations validates the top-level object; the [Range] annotations on the rate-limiter policies are additionally validated explicitly inside AddGatewayRoutePolicies (MMCA.Common/Source/Hosting/MMCA.Common.Gateway/RateLimiting/GatewayRoutePolicyExtensions.cs:49-52), which is why that loop exists rather than relying on options validation alone.
    1701. +
    1702. Caveats / not-in-source: ValidateDataAnnotations validates the top-level object; the [Range] annotations on the rate-limiter policies are additionally validated explicitly inside AddGatewayRoutePolicies (MMCA.Common/Source/Hosting/MMCA.Common.Gateway/RateLimiting/GatewayRoutePolicyExtensions.cs:60-63), which is why that loop exists rather than relying on options validation alone.

    1703. GatewayReverseProxyExtensions

      @@ -1227,7 +1228,7 @@

      BrokerResilienceDefaults

    1704. Why it's built this way: expression-bodied static properties rather than fields or constants, for the same reason as its sibling: TimeSpan cannot be a const, and a property is evaluated at call time instead of being inlined into the consuming assembly. Living in MMCA.Common.Shared rather than next to the processor is what makes the four numbers reviewable as a policy: a reader sees the whole breaker in one screen, with each value's justification attached to the member it justifies, without opening a 900-line background service. ADR-009 is the governing decision: resilience is a framework invariant, not a per-call choice.
    1705. Where it's used: OutboxProcessor builds its publish pipeline from all four (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:690-701, values at :759-762), holds it in an instance field (:99) rather than a static one so parallel test processors cannot open each other's circuit (field doc comment :87-98), and logs an opening through LogBrokerCircuitOpen (:664). The pipeline also excludes OperationCanceledException from the handled set (:763-764), so a host shutdown cancelling a batch mid-flight is not counted as broker unhealthiness. Circuit openings surface as telemetry through the MMCA.Common.Broker meter that Common.Aspire Extensions subscribes by literal name (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.Telemetry.cs:311).
    1706. -
    1707. Caveats / not-in-source: the four values are not configurable. There is no settings class and no configuration key, so tuning them is a framework release. The breaker guards the broker-publish call only, never the database calls, which the processor's field doc states outright (OutboxProcessor.cs:92-94): a breaker on those would open exactly when the processor most needs to persist retry state.
    1708. +
    1709. Caveats / not-in-source: the four values are not configurable. There is no settings class and no configuration key, so tuning them is a framework release. The breaker guards the broker-publish call only, never the database calls, which the processor's field doc states outright (OutboxProcessor.cs:90-92): a breaker on those would open exactly when the processor most needs to persist retry state.

    1710. HttpResilienceDefaults

      @@ -1235,22 +1236,23 @@

      HttpResilienceDefaults

      MMCA.Common.Shared · MMCA.Common.Shared.Resilience · MMCA.Common/Source/Core/MMCA.Common.Shared/Resilience/HttpResilienceDefaults.cs:10 · Level 0 · class (static)

        -
      • What it is: eight static properties that are the single source of truth for the outbound-HTTP resilience window (attempt timeout, breaker window, total timeout, retry count) and for the SocketsHttpHandler connection hygiene (pool lifetime, idle timeout, keep-alive ping delay and timeout). Two independently packaged transports read them, so the HTTP path and the gRPC path cannot drift apart (MMCA.Common/Source/Core/MMCA.Common.Shared/Resilience/HttpResilienceDefaults.cs:10-44).
      • -
      • Depends on: nothing first-party and nothing outside the BCL (TimeSpan, int). All the arrows point at it: the Common.Aspire Extensions service-defaults bootstrap (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:50-53,70-73), the gRPC typed-client DependencyInjection (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/DependencyInjection.cs:113-116), and GrpcResilienceDefaults, which re-exposes four of them.
      • -
      • Concept introduced, the shared-constant class as a drift gate. Most of this chapter is about behavior; this type is about agreement. Two packages that ship separately have to configure the same Polly and socket numbers, and nothing in the type system makes them match, so the codebase turns the numbers into a third artifact both are obliged to read. [Rubric §29, Resilience & Business Continuity] assesses graceful degradation under partial failure, and these four Polly values are the shape of that degradation: how long one attempt gets, how wide a window the breaker judges failures over, how long the caller waits in total, and how many times a single hop retries. ADR-009 is the governing decision, and its first clause is exactly this posture: resilience is a framework invariant, not a per-call choice. [Rubric §12, Performance & Scalability] shows up in the retry arithmetic below, because a retry policy is a load multiplier and multipliers compound per hop rather than adding. [Rubric §31, Cost/FinOps] is why the socket-handler half is here at all instead of being left at the BCL defaults: those four values are tuned for an idle Azure Container Apps replica (:30-43). And [Rubric §15, Best Practices & Code Quality] is the file's entire reason to exist: one edit moves both transports, and the alternative was already tried and failed, which the class doc records (:3-9).
      • +
      • What it is: eight static properties that are the single source of truth for the outbound-HTTP resilience window (attempt timeout, breaker window, total timeout, retry count) and for the SocketsHttpHandler connection hygiene (pool lifetime, idle timeout, keep-alive ping delay and timeout). Two independently packaged transports read them, so the HTTP path and the gRPC path cannot drift apart (MMCA.Common/Source/Core/MMCA.Common.Shared/Resilience/HttpResilienceDefaults.cs:10-46).
      • +
      • Depends on: nothing first-party and nothing outside the BCL (TimeSpan, int). All the arrows point at it: the Common.Aspire Extensions service-defaults bootstrap (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:51-54,78-81), the gRPC typed-client DependencyInjection (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/DependencyInjection.cs:113-116), the Infrastructure typed service client (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:169-172), and GrpcResilienceDefaults, which re-exposes four of them.
      • +
      • Concept introduced, the shared-constant class as a drift gate. Most of this chapter is about behavior; this type is about agreement. Two packages that ship separately have to configure the same Polly and socket numbers, and nothing in the type system makes them match, so the codebase turns the numbers into a third artifact both are obliged to read. [Rubric §29, Resilience & Business Continuity] assesses graceful degradation under partial failure, and these four Polly values are the shape of that degradation: how long one attempt gets, how wide a window the breaker judges failures over, how long the caller waits in total, and how many times a single hop retries. ADR-009 is the governing decision, and its first clause is exactly this posture: resilience is a framework invariant, not a per-call choice. [Rubric §12, Performance & Scalability] shows up in the retry arithmetic below, because a retry policy is a load multiplier and multipliers compound per hop rather than adding. [Rubric §31, Cost/FinOps] is why the socket-handler half is here at all instead of being left at the BCL defaults: those four values are tuned for an idle Azure Container Apps replica (:32-45). And [Rubric §15, Best Practices & Code Quality] is the file's entire reason to exist: one edit moves both transports, and the alternative was already tried and failed, which the class doc records (:3-9).
      • Walkthrough: eight expression-bodied static properties in two conceptual blocks.
        • The Polly window, the values handed to AddStandardResilienceHandler:
          • AttemptTimeout = 30 s (:13): the budget for one individual HTTP attempt.
          • CircuitBreakerSamplingDuration = 60 s (:16): the rolling window the breaker computes its failure ratio over, so a burst is judged against a minute of traffic rather than against the last handful of calls.
          • TotalRequestTimeout = 90 s (:19): the ceiling on the whole logical call including retries. The three numbers nest: a 30 s attempt inside a 90 s total leaves room for the initial attempt, the one retry, and the backoff between them.
          • -
          • MaxRetryAttempts = 1 (:28): retries beyond the initial attempt, deliberately one, and carrying the longest comment in the file (:21-27).
          • +
          • MaxRetryAttempts = 1 (:30): retries beyond the initial attempt, deliberately one, and carrying the longest comment in the file (:21-29).
        • -
        • Why one retry, the part worth internalizing. The Blazor UI already owns user-facing retries: AuthenticatedServiceBase holds a static Polly policy that retries 3 times with exponential backoff (2 s, 4 s, 8 s) plus up to a second of jitter on HttpRequestException or a retryable response status (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/AuthenticatedServiceBase.cs:19-25), which is up to 4 attempts per user action. If every hop underneath also spent a full retry budget, the attempts would multiply instead of adding: the call-site comment records the previous worst case as 4 outer x 4 inner = 16 gateway hits for one click (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:45-47). That is a retry storm, the failure mode where the load a struggling backend receives goes up precisely because it is failing. One transient-fault retry per hop still absorbs a dropped connection while bounding the multiplier.
        • +
        • Why one retry, the part worth internalizing. The Blazor UI already owns user-facing retries: AuthenticatedServiceBase holds a static Polly policy that retries 3 times with exponential backoff (2 s, 4 s, 8 s) plus up to a second of jitter on HttpRequestException or a retryable response status (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/AuthenticatedServiceBase.cs:19-25), which is up to 4 attempts per user action. If every hop underneath also spent a full retry budget, the attempts would multiply instead of adding: the call-site comment records the previous worst case as 4 outer x 4 inner = 16 gateway hits for one click (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:46-48). That is a retry storm, the failure mode where the load a struggling backend receives goes up precisely because it is failing. One transient-fault retry per hop still absorbs a dropped connection while bounding the multiplier.
        • +
        • The one retry never replays a mutation (:27-28). The value itself is a count; what keeps it safe is how each reader scopes it. The HTTP service defaults call options.Retry.DisableFor(HttpMethod.Post, HttpMethod.Patch) (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:61), because a timed-out attempt may still be running server-side and a server-to-server hop carries no idempotency key, while GET, PUT, DELETE and the other idempotent verbs keep their retry (the call-site comment at Extensions.cs:56-60 explains why it names two verbs instead of using DisableForUnsafeHttpMethods, which would also switch off PUT and DELETE). The gRPC path cannot use a verb filter at all, since every gRPC call is a POST, so it sets Retry.ShouldHandle to retry only an HttpRequestException, a failure to reach the service such as DNS or connect during an ACA replica rollover (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/DependencyInjection.cs:133-137).
        • The socket-handler block, the values handed to SocketsHttpHandler:
            -
          • PooledConnectionLifetime = 10 min (:34): a pooled connection is recycled on this cadence, which forces DNS to be re-resolved, so an ACA replica rollover is picked up without an app restart.
          • -
          • PooledConnectionIdleTimeout = 5 min (:37): idle connections stay pooled that long, so low-traffic inter-service calls skip the TCP plus TLS handshake.
          • -
          • KeepAlivePingDelay = 60 s (:40) and KeepAlivePingTimeout = 30 s (:43): the HTTP/2 socket-level keep-alive ping interval and the timeout waiting for its acknowledgement.
          • +
          • PooledConnectionLifetime = 10 min (:36): a pooled connection is recycled on this cadence, which forces DNS to be re-resolved, so an ACA replica rollover is picked up without an app restart.
          • +
          • PooledConnectionIdleTimeout = 5 min (:39): idle connections stay pooled that long, so low-traffic inter-service calls skip the TCP plus TLS handshake.
          • +
          • KeepAlivePingDelay = 60 s (:42) and KeepAlivePingTimeout = 30 s (:45): the HTTP/2 socket-level keep-alive ping interval and the timeout waiting for its acknowledgement.
        @@ -1261,16 +1263,16 @@

        HttpResilienceDefaults

      • Properties, not fields or constants. TimeSpan cannot be a const at all, and an expression-bodied static property is evaluated at call time rather than inlined into the consuming assembly the way a const int is at compile time. The flat one-line-per-value shape also keeps the file readable as a ledger. BrokerResilienceDefaults is the same pattern applied to the outbox's broker-publish breaker, and GrpcResilienceDefaults is the same pattern applied to the east-west gRPC path.
      -
    1711. Where it's used: three call sites, two of which read it.
        -
      • AddServiceDefaults routes it through ConfigureHttpClientDefaults, so every HttpClient in the process inherits it (typed clients, named clients, the YARP forwarder, per the comment at Extensions.cs:36-37): the four Polly values go into AddStandardResilienceHandler (Extensions.cs:48-54) and the four socket values into ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler { ... }) (Extensions.cs:67-75).
      • -
      • AddTypedGrpcClient<TClient>(serviceName) re-applies the socket block (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/DependencyInjection.cs:110-118) and takes the resilience block from GrpcResilienceDefaults (:106-115). It has to restate the socket values: forcing HTTP/2 means overriding the primary handler, which bypasses the wrapper the global ConfigureHttpClientDefaults installed, so the connection-hygiene values must come from the same source of truth (the reasoning is spelled out at DependencyInjection.cs:101-109).
      • -
      • Not a reader: AddTypedServiceClient<TInterface, TImplementation> in Infrastructure (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:141) calls builder.AddStandardResilienceHandler() with no options delegate (:156), so that registration does not itself consume these constants, even though its own doc comment describes its resilience as matching the standard handler from AddServiceDefaults (:126-127). Not determinable from source: how that second standard handler and the global ConfigureHttpClientDefaults pipeline compose into one effective policy on that client at runtime.
      • +
      • Where it's used: three call sites, all of which read it.
          +
        • AddServiceDefaults routes it through ConfigureHttpClientDefaults, so every HttpClient in the process inherits it (typed clients, named clients, the YARP forwarder, per the comment at Extensions.cs:37-38): the four Polly values go into AddStandardResilienceHandler (Extensions.cs:49-62), with the POST/PATCH retry opt-out at :61, and the four socket values into ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler { ... }) (Extensions.cs:76-84).
        • +
        • AddTypedGrpcClient<TClient>(serviceName) re-applies the socket block (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/DependencyInjection.cs:110-118) and takes the resilience block from GrpcResilienceDefaults (:127-142). It has to restate the socket values: forcing HTTP/2 means overriding the primary handler, which bypasses the wrapper the global ConfigureHttpClientDefaults installed, so the connection-hygiene values must come from the same source of truth (the reasoning is spelled out at DependencyInjection.cs:101-109).
        • +
        • AddTypedServiceClient<TInterface, TImplementation> in Infrastructure (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.Messaging.cs:152) gives its own standard resilience handler AttemptTimeout, TotalRequestTimeout, MaxRetryAttempts and CircuitBreakerSamplingDuration from this class (:167-173) rather than the library defaults. Its doc comment records that the host defaults still stack one retry on top, putting the worst case at 4 attempts, not 8 (:142-145). That handler sets no POST/PATCH opt-out of its own; the only DisableFor is the one in the global pipeline (Extensions.cs:61). Not determinable from source: how the client's own standard handler and the global ConfigureHttpClientDefaults pipeline compose into one effective policy at runtime, and therefore whether a POST through this client can be retried by the inner handler.
      • Caveats / not-in-source:
        • No test pins the numbers. The ADR-009 fitness function ResilienceHandlerTests asserts that AddTypedGrpcClient registers a standard resilience handler at all, and runtime breaker behavior is covered separately by ResilienceCircuitBreakerFaultInjectionTests, which drives its own short values rather than these. A call site that quietly stopped reading a property would still compile.
        • -
        • Two handler settings are still duplicated literals rather than centralised here: KeepAlivePingPolicy = HttpKeepAlivePingPolicy.WithActiveRequests and EnableMultipleHttp2Connections = true (Extensions.cs:73-74, DependencyInjection.cs:98,103). The first one matters when reading the ping values: under WithActiveRequests the pings apply to connections that have outstanding requests, so what holds a genuinely idle pooled connection open is PooledConnectionIdleTimeout, not KeepAlivePingDelay.
        • -
        • The billing claim is a platform statement. "Does not count as user traffic to the ACA platform" (HttpResilienceDefaults.cs:39), expanded at the call site to idle-vCPU billing roughly 8x cheaper than active (Extensions.cs:61-64), describes how Azure Container Apps meters a replica. Not determinable from source: the code can set a ping interval, it cannot demonstrate how the platform bills it.
        • +
        • Two handler settings are still duplicated literals rather than centralised here: KeepAlivePingPolicy = HttpKeepAlivePingPolicy.WithActiveRequests and EnableMultipleHttp2Connections = true (Extensions.cs:82-83, DependencyInjection.cs:112,117). The first one matters when reading the ping values: under WithActiveRequests the pings apply to connections that have outstanding requests, so what holds a genuinely idle pooled connection open is PooledConnectionIdleTimeout, not KeepAlivePingDelay.
        • +
        • The billing claim is a platform statement. "Does not count as user traffic to the ACA platform" (HttpResilienceDefaults.cs:41), expanded at the call site to idle-vCPU billing roughly 8x cheaper than active (Extensions.cs:70-73), describes how Azure Container Apps meters a replica. Not determinable from source: the code can set a ping interval, it cannot demonstrate how the platform bills it.
      @@ -1290,7 +1292,7 @@

      GrpcResilienceDefaults

    1712. Why it's built this way: the same layer reasoning as its sibling. MMCA.Common.Grpc may depend only on MMCA.Common.Shared, so a values class in Shared is the one address both the Aspire hosting bootstrap and the gRPC presentation package can reach. Splitting the file from HttpResilienceDefaults rather than adding three members to it keeps each file readable as one transport's policy, while the delegation keeps them numerically identical where they must be. The three breaker values match BrokerResilienceDefaults's ratio and throughput exactly and differ only in break duration (10 s versus 15 s), which is a consistent framework posture rather than three independently tuned policies.
    1713. -
    1714. Where it's used: AddTypedGrpcClient<TClient>(serviceName) copies all seven into the standard resilience handler's options (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/DependencyInjection.cs:127-136), with the rationale restated at the call site (:99-105). Covered by ResilienceHandlerTests and ResilienceCircuitBreakerFaultInjectionTests.
    1715. +
    1716. Where it's used: AddTypedGrpcClient<TClient>(serviceName) copies all seven into the standard resilience handler's options (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/DependencyInjection.cs:127-142), with the rationale restated at the call site (:99-105). Covered by ResilienceHandlerTests and ResilienceCircuitBreakerFaultInjectionTests.
    1717. Caveats / not-in-source: as with its siblings, none of the seven values is configurable, and no test asserts the specific numbers. Changing one is a framework release.

    1718. @@ -1303,7 +1305,7 @@

      GatewayTraceHeaderTransformProvider
    1719. Depends on: GatewaySettings, injected as IOptions<GatewaySettings> and reduced immediately to its GatewayTraceHeaderSettings sub-object (GatewayTraceHeaderTransformProvider.cs:18-21). Externals: YARP's ITransformProvider, TransformBuilderContext, TransformRouteValidationContext / TransformClusterValidationContext and the AddRequestTransform extension (Yarp.ReverseProxy.Transforms / .Transforms.Builder, :1-3), plus Microsoft.Extensions.Options. Registered by GatewayReverseProxyExtensions alongside GatewayClusterProfileConfigFilter and GatewayHealthCheckDefaultsConfigFilter.
    1720. Concept introduced, a header a downstream can trust. [Rubric §11, Security], [Rubric §13, Observability & Operability], [Rubric §7, Microservices Readiness]. There are two ideas here and the second is the one that matters. The first is a YARP extension point: an ITransformProvider is asked to contribute transforms into a route's pipeline when that pipeline is built, which is how a gateway adds behaviour to every route without listing it in each route's JSON. The second is the trust question. Correlating a downstream log line back to a gateway route by matching URL path patterns stops working the moment two routes share a prefix, so the proxy stamps the answer it alone knows. But the instant a downstream trusts a header, that header becomes an input an attacker can supply: a client that sets X-MMCA-Route: identity-auth itself would have its request attributed to a route it never matched. §11 is the category that cares, and the fix is structural rather than validating: the transform removes both header names before writing its own (:60-61), so the value a service reads can only have come from the gateway. The class doc states the rule directly, "A header a service trusts must be one only the gateway can set" (:11-15). §7 is why the fact is worth having at all: once modules run as separate deployables (ADR-008), route selection is a decision made in one process and consumed in the logs of another.
    1721. Walkthrough:
        -
      • A primary constructor takes IOptions<GatewaySettings> and the single field _settings is initialized to options.Value.TraceHeaders with an inline ArgumentNullException guard (:18-21). The provider therefore holds the trace-header sub-settings only, resolved once at construction: the DI registration supplies IOptions<GatewaySettings> as a fixed Options.Create(settings) singleton (GatewayReverseProxyExtensions.cs:191), so there is no reload path to honour.
      • +
      • A primary constructor takes IOptions<GatewaySettings> and the single field _settings is initialized to options.Value.TraceHeaders with an inline ArgumentNullException guard (:18-21). The provider therefore holds the trace-header sub-settings only, resolved once at construction: the DI registration supplies IOptions<GatewaySettings> as a fixed Options.Create(settings) singleton (GatewayReverseProxyExtensions.cs:76), so there is no reload path to honour.
      • ValidateRoute (:25) and ValidateCluster (:33) are deliberately empty, each with a comment saying why (:27-28, :35): the provider is applied to every route unconditionally and reads nothing from the route's own transform list, so there is no operator-supplied parameter that could be mistyped and therefore nothing to validate. YARP requires the two methods; implementing them as no-ops with a stated reason is the honest form.
      • Apply(TransformBuilderContext context) (:40) null-guards the context (:42), then applies the kill switch: when _settings.Enabled is false it returns having added nothing (:44-47). Disabled means no transform in the pipeline at all, not a transform that runs and does nothing.
      • Four values are hoisted into locals before the transform delegate is created: the two configured header names and the route's RouteId and ClusterId (:51-54). The comment above them (:49-50) is the rationale: these ids are fixed for the lifetime of this route's transform pipeline, so resolving them per request would be work that can never produce a different answer. The captured locals are what keep the per-request delegate to a few header operations.
      • @@ -1363,22 +1365,23 @@

        GatewayRoutePolicyExtensions

        MMCA.Common.Gateway · MMCA.Common.Gateway.RateLimiting · MMCA.Common/Source/Hosting/MMCA.Common.Gateway/RateLimiting/GatewayRoutePolicyExtensions.cs:27 · Level 3 · class (static)

      • -
      • What it is: the registration of the named rate-limiter policies a YARP route references by name. One extension(IServiceCollection) member, AddGatewayRoutePolicies(GatewaySettings), plus the internal static Partition helper that decides what one request counts against.
      • -
      • Depends on: GatewaySettings and GatewayRoutePolicySettings. Externals: System.Threading.RateLimiting (RateLimitPartition, FixedWindowRateLimiterOptions, QueueProcessingOrder), Microsoft.AspNetCore.RateLimiting via AddRateLimiter, HttpContext, and Validator from data annotations. Called by GatewayReverseProxyExtensions.
      • +
      • What it is: the registration of the named rate-limiter policies a YARP route references by name. One extension(IServiceCollection) member, AddGatewayRoutePolicies(GatewaySettings), plus the internal static Partition helper that decides what one request counts against and the internal const TrustedInternalCallerItemKey it reads to exempt a request the edge limiter has already proven trusted.
      • +
      • Depends on: GatewaySettings and GatewayRoutePolicySettings. Externals: System.Threading.RateLimiting (RateLimitPartition, FixedWindowRateLimiterOptions, QueueProcessingOrder), Microsoft.AspNetCore.RateLimiting via AddRateLimiter, HttpContext, and Validator from data annotations. Called by GatewayReverseProxyExtensions. Coupled by a string literal, not a reference, to GatewayRateLimitingExtensions, which writes the HttpContext.Items flag this type reads.
      • Concept introduced, named policies as an additive second limiter. [Rubric §11, Security], [Rubric §17, DevOps & Deployment]. The doc's second paragraph is the operationally important part (MMCA.Common/Source/Hosting/MMCA.Common.Gateway/RateLimiting/GatewayRoutePolicyExtensions.cs:15-21): these policies are additive to any global limiter the host installs (for example MMCA.Common.Aspire.Gateway.AddGatewayRateLimiting, which assigns RateLimiterOptions.GlobalLimiter). ASP.NET Core evaluates the global limiter and the route's named policy independently, so a request must satisfy both. Nothing here overwrites the global limiter, and the two packages stay independent of each other. The division of labour is also worth naming: YARP already knows how to attach a named policy to a route, what it cannot do is create one (:12-14), so this method exists purely to fill that gap without every gateway hand-writing an AddPolicy block.
      • Walkthrough:
          -
        • AddGatewayRoutePolicies(GatewaySettings settings) (:39-69) inside the extension(IServiceCollection services) block (:29).
            -
          • Null-guards both the collection and the settings (:41-42), then short-circuits when no policies are declared (:44-47), so a gateway with no named policies never calls AddRateLimiter at all.
          • -
          • Validates every policy up front with Validator.ValidateObject(policy, new ValidationContext(policy), validateAllProperties: true) (:49-52). This runs before any registration, so an out-of-range PermitLimit throws at registration rather than at the first throttled request. The doc names the governing decision (:34-35), ADR-070 fail-fast.
          • -
          • Calls AddRateLimiter (:54), sets RejectionStatusCode = StatusCodes.Status429TooManyRequests (:56), and registers one options.AddPolicy<string>(name, ...) per entry (:66), skipping blank names (:60-63). var captured = policy (:65) is the loop-variable capture guard that keeps each registered delegate bound to its own policy.
          • +
          • TrustedInternalCallerItemKey = "MMCA.Common.Gateway.TrustedInternalCaller" (:38), internal const. Its doc (:29-37) is the contract: the edge limiter in MMCA.Common.Aspire (AddGatewayRateLimiting) stamps this HttpContext.Items key with true once a request has proven the trusted-internal-caller secret, and because that limiter is the global limiter it runs before the route policy in the same middleware, so the stamp is present by the time Partition runs. The two packages do not reference each other, so the literal is repeated on the Aspire side (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Gateway/GatewayRateLimitingExtensions.cs:62), where ClientIpPartition writes it (:202-207). A host without the edge limiter never stamps it, and every request is then limited as if the key did not exist.
          • +
          • AddGatewayRoutePolicies(GatewaySettings settings) (:50-80) inside the extension(IServiceCollection services) block (:40).
              +
            • Null-guards both the collection and the settings (:52-53), then short-circuits when no policies are declared (:55-58), so a gateway with no named policies never calls AddRateLimiter at all.
            • +
            • Validates every policy up front with Validator.ValidateObject(policy, new ValidationContext(policy), validateAllProperties: true) (:60-63). This runs before any registration, so an out-of-range PermitLimit throws at registration rather than at the first throttled request. The doc names the governing decision (:45-46), ADR-070 fail-fast.
            • +
            • Calls AddRateLimiter (:65), sets RejectionStatusCode = StatusCodes.Status429TooManyRequests (:67), and registers one options.AddPolicy<string>(name, ...) per entry (:77), skipping blank names (:71-74). var captured = policy (:76) is the loop-variable capture guard that keeps each registered delegate bound to its own policy.
          • -
          • Partition(HttpContext, GatewayRoutePolicySettings) (:81-98), internal so the partition-key selection is unit-testable (:80): asks the policy for the key from httpContext.Connection.RemoteIpAddress (:86), then branches (:88-97). A null key yields RateLimitPartition.GetNoLimiter(...), the fail-open path GatewayRoutePolicySettings explains; otherwise a GetFixedWindowLimiter built from the policy's PermitLimit, WindowSeconds and QueueLimit, with QueueProcessingOrder.OldestFirst and AutoReplenishment = true (:90-97).
          • +
          • Partition(HttpContext, GatewayRoutePolicySettings) (:94-116), internal so the partition-key selection is unit-testable (:93). After its null guards it first checks the trusted-caller stamp: when httpContext.Items holds TrustedInternalCallerItemKey with the value true, it returns RateLimitPartition.GetNoLimiter(GatewayRoutePolicySettings.GlobalPartitionKey) (:99-102). The doc gives the reason (:86-88): this is the same exemption the edge limiter grants, and a server-rendered UI's sign-ins all leave from one address, so without it they would share one per-IP bucket. Otherwise it asks the policy for the key from httpContext.Connection.RemoteIpAddress (:104), then branches (:106-115). A null key yields RateLimitPartition.GetNoLimiter(...), the fail-open path GatewayRoutePolicySettings explains; otherwise a GetFixedWindowLimiter built from the policy's PermitLimit, WindowSeconds and QueueLimit, with QueueProcessingOrder.OldestFirst and AutoReplenishment = true (:108-115).
        • Why it's built this way: a fixed window is the only limiter shape exposed, which keeps the configuration surface two numbers wide, and AutoReplenishment = true means the window refills on its own timer rather than requiring the host to pump it. RejectionStatusCode is set here rather than left to the host so a throttled request is a 429 regardless of which package installed the limiter. The class carries the same scoped CA1708 suppression as the other extension(T) classes in the kit (:23-26).
        • Where it's used: called from GatewayReverseProxyExtensions.Wire (MMCA.Common/Source/Hosting/MMCA.Common.Gateway/GatewayReverseProxyExtensions.cs:88), so both AddMmcaGateway overloads register it. In the consumer gateways the resulting auth-tight policy is enforced by the single app.UseGatewayRateLimiting() call that also serves the global edge limiter (MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/Program.cs:177 with the explanation at :140-142, MMCA.Store/Source/Hosts/MMCA.Store.Gateway/Program.cs:170). Covered by GatewayRoutePolicyTests.
        • -
        • Caveats / not-in-source: AddRateLimiter is additive, so calling it from both this method and a host's own global-limiter registration is safe, but nothing here detects a host that declared policies and then forgot UseRateLimiter(). In that case the policies are registered and simply never applied, with no error.
        • +
        • Caveats / not-in-source: AddRateLimiter is additive, so calling it from both this method and a host's own global-limiter registration is safe, but nothing here detects a host that declared policies and then forgot UseRateLimiter(). In that case the policies are registered and simply never applied, with no error. The trusted-caller exemption rests on two copies of one string literal (GatewayRoutePolicyExtensions.cs:38 and GatewayRateLimitingExtensions.cs:62); source shows nothing that ties them together at compile time, so a rename on one side would silently end the exemption on the named policies while the edge limiter kept granting it.

        AdcAppHostFixture

        @@ -1447,16 +1450,16 @@

        OutboxPollFilterProcessor

      • Depends on: OpenTelemetry's BaseProcessor<Activity> and System.Diagnostics.Activity / ActivityTraceFlags (OutboxPollFilterProcessor.cs:1-2). No first-party types, on purpose (see the walkthrough). Conceptually it is paired with OutboxProcessor and InternalCommandProcessor (ADR-114), whose spans it filters, and it is registered by the service-defaults Extensions.
      • Concept introduced, suppressing a span without breaking the trace. [Rubric §13, Observability & Operability], [Rubric §31, Cost & FinOps]. OpenTelemetry gives every Activity a Recorded flag, and the SDK's batch export processors skip anything unrecorded. Clearing that flag in an earlier processor's OnEnd is therefore how you drop a span from export without interfering with its creation, its timing, or its parent-child links, which is what you want when the span is real work that simply is not worth paying to store. §31 is the category that makes this necessary rather than nice: the OutboxProcessor polls every relational outbox on a recurring cycle, and the internal-commands processor polls every internal-command queue table the same way, and in a deployed environment the Azure Monitor distro's automatic instrumentation adds a SqlClient dependency span for each of those queries. Idle polls would dominate Application Insights and Log Analytics ingestion and spam the local Aspire dashboard, for zero diagnostic value. §13 is the constraint on the fix: filtering must not blind you to real work, and it does not, because per-message OutboxProcess spans and per-command InternalCommandExecute spans are started with an explicit parent context restored from the trace and span ids stored on the message (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:717-723) and so are never descendants of a poll span. ADR-041 records the policy this implements: instrument where auto-instrumentation is blind, then add cost knobs whose defaults never go dark; ADR-114 is what added the second poll family this processor now shares that policy with.
      • Walkthrough:
          -
        • Four private const string fields: OutboxActivitySourceName = "MMCA.Common.Outbox", PollActivityName = "OutboxPoll", InternalCommandsActivitySourceName = "MMCA.Common.InternalCommands", InternalCommandsPollActivityName = "InternalCommandPoll" (:23-26). They are duplicated literals, not references to the Infrastructure constants, and the comment above them (:17-22) is the design rationale: MMCA.Common.Aspire does not reference MMCA.Common.Infrastructure, so AddServiceDefaults() stays usable from a host that never takes the persistence stack (EF Core, MassTransit, SignalR/Redis). Its one project reference is MMCA.Common.Shared, for HttpResilienceDefaults. The same literals also appear in the AddMeter / AddSource calls in Extensions.cs. The counterparts they must stay in sync with are OutboxProcessor.PollActivityName and its activity source new("MMCA.Common.Outbox"), plus InternalCommandMetrics.MeterName and InternalCommandProcessor.PollActivityName on the queue side.
        • -
        • OnEnd(Activity data) (:27) is the single override. It opens with a null check that returns instead of throwing (:29-33), under the comment "Never throw from a telemetry callback": an exception escaping an SDK callback is an observability feature taking the process down.
        • -
        • The match is a loop up the in-process parent chain, for (var current = data; current is not null; current = current.Parent) (:37). Walking rather than checking only the immediate parent is what catches grandchildren: the SqlClient span the Azure Monitor distro creates can sit more than one level below the poll.
        • -
        • The predicate is factored out into IsSuppressedPoll(Activity) (:55-60), which matches either poll family: OperationName == PollActivityName && Source.Name == OutboxActivitySourceName, or OperationName == InternalCommandsPollActivityName && Source.Name == InternalCommandsActivitySourceName. Matching on both operation name and source name (not name alone) is what guards against a consumer span that happens to share a poll's name from a different source (:35-36).
        • -
        • On a match it clears the flag with data.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded and returns (:45-47). The comment (:42-44) states the ordering contract that makes it work: the batch export processors skip an unrecorded activity, and this processor is registered before the exporters so its OnEnd runs first.
        • +
        • Four private const string fields: OutboxActivitySourceName = "MMCA.Common.Outbox", PollActivityName = "OutboxPoll", InternalCommandsActivitySourceName = "MMCA.Common.InternalCommands", InternalCommandsPollActivityName = "InternalCommandPoll" (:26-29). They are duplicated literals, not references to the Infrastructure constants, and the comment above them (:19-25) is the design rationale: MMCA.Common.Aspire does not reference MMCA.Common.Infrastructure, so AddServiceDefaults() stays usable from a host that never takes the persistence stack (EF Core, MassTransit, SignalR/Redis). Its one project reference is MMCA.Common.Shared, for HttpResilienceDefaults. The same literals also appear in the AddMeter / AddSource calls in Extensions.Telemetry.cs (for example .AddSource("MMCA.Common.Outbox") and .AddSource("MMCA.Common.InternalCommands") at MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.Telemetry.cs:84-85). The counterparts they must stay in sync with are OutboxProcessor.PollActivityName and its activity source new("MMCA.Common.Outbox"), plus InternalCommandMetrics.MeterName and InternalCommandProcessor.PollActivityName on the queue side.
        • +
        • OnEnd(Activity data) (:32) is the single override. It opens with a null check that returns instead of throwing (:34-38), under the comment "Never throw from a telemetry callback": an exception escaping an SDK callback is an observability feature taking the process down.
        • +
        • The match is a loop up the in-process parent chain, for (var current = data; current is not null; current = current.Parent) (:42). Walking rather than checking only the immediate parent is what catches grandchildren: the SqlClient span the Azure Monitor distro creates can sit more than one level below the poll.
        • +
        • The predicate is factored out into IsSuppressedPoll(Activity) (:55-64), which matches either poll family: OperationName == PollActivityName && Source.Name == OutboxActivitySourceName, or OperationName == InternalCommandsPollActivityName && Source.Name == InternalCommandsActivitySourceName. Matching on both operation name and source name (not name alone) is what guards against a consumer span that happens to share a poll's name from a different source (:40-41).
        • +
        • On a match it clears the flag with data.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded and returns (:49-50). The comment (:46-48) states the ordering contract that makes it work: the batch export processors skip an unrecorded activity, and this processor is registered before the exporters so its OnEnd runs first.
      • Why it's built this way: Sealed, stateless, and registered as a single instance. The literal duplication is a deliberate, commented trade of a small sync obligation against an assembly dependency that would force every service-defaults consumer to take the persistence stack. Clearing Recorded rather than declining to start the activity keeps the poll span available to a local debugger and to any processor registered before this one, and keeps the parent chain intact for the spans that are exported. Extracting IsSuppressedPoll rather than inlining a second if inside the loop is what let the internal-commands family join without touching the walk or the null-guard: OnEnd still reads as one loop over one predicate. Returning on a null activity rather than asserting follows the rule the rest of the telemetry code obeys: a cost knob must never be able to fail the request path. ADR-041 documents this as one of three cost levers whose defaults fail safe, alongside head-based sampling and the two metric kill switches.
      • -
      • Where it's used: Registered once, inside ConfigureOpenTelemetry's WithTracing block (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.Telemetry.cs:81), as .AddProcessor(new Telemetry.OutboxPollFilterProcessor()) (Extensions.Telemetry.cs:122), immediately after .AddSource("MMCA.Common.Outbox") (:84) and before builder.AddOpenTelemetryExporters() (:141). Every host that calls AddServiceDefaults() gets it, with no opt-in and no configuration key. The outbox spans it targets are opened in two places: the candidate fetch (OutboxProcessor.cs:341) and the saturated-batch backlog COUNT (OutboxProcessor.cs:288), both using var pollActivity = OutboxActivitySource.StartActivity(PollActivityName). The internal-commands spans come from the queue side's own poll, in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/InternalCommandProcessor.cs. Covered by OutboxPollFilterProcessorTests, which drives a real ActivityListener and asserts each behaviour: the poll span itself is unrecorded (OutboxPollFilterProcessorTests.cs:43), a child is (:54), a grandchild is (:68), an unrelated root and a same-name span from a different source stay recorded (:81, :92), a real OutboxProcess span stays recorded (:104), and a null activity does not throw (:115).
      • -
      • Caveats / not-in-source: The walk is over Activity.Parent, which is the in-process chain only. A span whose parent exists only as a propagated remote context is not matched, so the suppression covers exactly what the poll starts inside the same process, which is what the SqlClient child is. Suppression is unconditional: there is no configuration key to keep poll spans for a debugging session, so diagnosing either poll path means reading the outbox or internal-commands meters, or temporarily removing the processor. Source shows nothing that verifies the four duplicated literals against OutboxProcessor and InternalCommandProcessor's constants at build or test time, so the sync obligation in the comment (:17-19) is honoured by review, not by the compiler.
      • +
      • Where it's used: Registered once, inside ConfigureOpenTelemetry's WithTracing block (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.Telemetry.cs:81), as .AddProcessor(new Telemetry.OutboxPollFilterProcessor()) (Extensions.Telemetry.cs:122), immediately after .AddSource("MMCA.Common.Outbox") (:84) and before builder.AddOpenTelemetryExporters() (:141). Every host that calls AddServiceDefaults() gets it, with no opt-in and no configuration key. The outbox spans it targets are opened in two places: the candidate fetch (OutboxProcessor.cs:330) and the saturated-batch backlog COUNT (OutboxProcessor.cs:277), both using var pollActivity = OutboxActivitySource.StartActivity(PollActivityName). The internal-commands spans come from the queue side's own poll, in MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/InternalCommandProcessor.cs. Covered by OutboxPollFilterProcessorTests, which drives a real ActivityListener and asserts each behaviour: the poll span itself is unrecorded (OutboxPollFilterProcessorTests.cs:43), a child is (:54), a grandchild is (:68), an unrelated root and a same-name span from a different source stay recorded (:81, :92), a real OutboxProcess span stays recorded (:104), and a null activity does not throw (:115).
      • +
      • Caveats / not-in-source: The walk is over Activity.Parent, which is the in-process chain only. A span whose parent exists only as a propagated remote context is not matched, so the suppression covers exactly what the poll starts inside the same process, which is what the SqlClient child is. Suppression is unconditional: there is no configuration key to keep poll spans for a debugging session, so diagnosing either poll path means reading the outbox or internal-commands meters, or temporarily removing the processor. Source shows nothing that verifies the four duplicated literals against OutboxProcessor and InternalCommandProcessor's constants at build or test time, so the sync obligation in the comment (:19-21) is honoured by review, not by the compiler.

      ProbeTelemetryFilter

      diff --git a/docs/onboarding/group-17-conference-domain.html b/docs/onboarding/group-17-conference-domain.html index 303a686a..45491462 100644 --- a/docs/onboarding/group-17-conference-domain.html +++ b/docs/onboarding/group-17-conference-domain.html @@ -260,7 +260,7 @@

      Ten aggregates and their holds an optional Email value object (Speaker.cs:31), and carries the cross-module LinkedUserId FK to an Identity User (Speaker.cs:58). Speaker Ids are Sessionize-assigned GUIDs, with a fallback to Guid.NewGuid() for organizer-created and seeded - speakers (see the in-code note at Speaker.cs:155-161). + speakers (see the in-code note at Speaker.cs:156-162).
    1722. Sponsor (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sponsors/Sponsor.cs:18) is a flat root belonging to exactly one event by scalar EventId (Sponsor.cs:45, with a private-setter @@ -389,8 +389,8 @@

      The aggregate shape, taught once

      event (Event.cs:222). The isIdValueGenerated ? default : id!.Value dance (Event.cs:202,218) reconciles database-generated IDs with explicitly supplied ones. Each root spells that reconciliation slightly differently: Speaker generates a GUID when no id is supplied - (Speaker.cs:161), Category throws for a missing id when identity is not - database-generated (Category.cs:69), Activity uses the plain Event form + (Speaker.cs:162), Category throws for a missing id when identity is not + database-generated (Category.cs:70), Activity uses the plain Event form (Activity.cs:99), and SessionAsset mints Guid.NewGuid() when no id is supplied (SessionAsset.cs:301). SessionAsset also splits the factory three ways: a general Create that takes the kind as a value for a deserializer or an import, plus the CreateLink and CreateFile @@ -398,7 +398,7 @@

      The aggregate shape, taught once

      all three funnelling into one private helper whose Result.Combine runs the six invariant checks before anything is constructed, SessionAsset.cs:289-295).
    1723. Mutator methods (Update at Event.cs:247, Publish/Unpublish at Event.cs:299,319, - LinkUser/UnlinkUser on Speaker at Speaker.cs:272,290) that re-validate, mutate, and raise an + LinkUser/UnlinkUser on Speaker at Speaker.cs:274,292) that re-validate, mutate, and raise an Updated event. Lifecycle guards return failures rather than throwing: publishing an already published event yields the "Event.AlreadyPublished" invariant error (Event.cs:301-308).
    1724. An overridden Delete() (Event.cs:355) that soft-deletes children first and the root last, @@ -409,16 +409,16 @@

      The aggregate shape, taught once

      failing child leaves the cascade reported as a failure instead of a half-applied delete whose earlier children and root were already flagged. Session does the same for its three child collections (Session.cs:311-314,317), Category for its items - (Category.cs:107-108,111), Sponsor, Activity, and + (Category.cs:109-110,113), Sponsor, Activity, and SessionAsset have nothing to cascade to and simply raise their Deleted events (Sponsor.cs:190-197, Activity.cs:180-188, SessionAsset.cs:265,270), and Speaker uses its override for a different job: clearing the cross-context link while deliberately leaving its junction children alive so the Sessionize import can reactivate them - in place (Speaker.cs:241-267). Soft-delete is the default everywhere ([Rubric §8, Data Architecture]: the IsDeleted flag plus EF Core global query filters, never a hard DELETE; + in place (Speaker.cs:243-269). Soft-delete is the default everywhere ([Rubric §8, Data Architecture]: the IsDeleted flag plus EF Core global query filters, never a hard DELETE; ADR-005).
    1725. Restore methods for the Sessionize round-trip (RestoreRoom at Event.cs:465, RestoreEventSpeaker at Event.cs:573, RestoreSessionSpeaker and RestoreSessionCategoryItem at - Session.cs:375,458, and RestoreSpeakerCategoryItem at Speaker.cs:352): a re-imported child that + Session.cs:375,458, and RestoreSpeakerCategoryItem at Speaker.cs:354): a re-imported child that was previously soft-deleted is reactivated in place rather than re-inserted. A restore has to clear the same uniqueness bar as an add, which is why RestoreRoom re-runs the duplicate-name check before reactivating (Event.cs:486), and it first refuses any room owned by a different event @@ -465,10 +465,10 @@

      Invariants, business rules MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Questions/QuestionInvariants.cs:40,43). QuestionInvariants validates the free-text enum-like fields against allow-lists (QuestionInvariants.cs:31,34,37, checked at :71,86,101) and, for answers, dispatches on the - question type (QuestionInvariants.cs:118): Rating must parse as an invariant-culture integer 1 - through 5 (QuestionInvariants.cs:133), Text is capped at 2000 characters - (QuestionInvariants.cs:28,147), and Email must parse as a System.Net.Mail.MailAddress - (QuestionInvariants.cs:163). ActivityInvariants is the compact newcomer: name, venue name, venue + question type (QuestionInvariants.cs:122): Rating must parse as an invariant-culture integer 1 + through 5 (QuestionInvariants.cs:137), Text is capped at 2000 characters + (QuestionInvariants.cs:28,151), and Email must parse as a System.Net.Mail.MailAddress + (QuestionInvariants.cs:167). ActivityInvariants is the compact newcomer: name, venue name, venue address, and venue URL length checks plus a start-before-end time-range rule (ActivityInvariants.cs:36,48,58,68,79). PartnerInvariants is smaller still: a name presence-and-length rule plus optional-length rules for the logo and website URLs @@ -488,10 +488,10 @@

      Invariants, business rules list renders ascending and a negative value would silently sort ahead of everything an organizer arranged (SessionAssetInvariants.cs:119-126). CategoryInvariants enforces case-insensitive uniqueness of an item name within its category (BR-138, - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/CategoryInvariants.cs:44), + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/CategoryInvariants.cs:51), and its in-code note explains why the exclusion parameter is nullable rather than defaulted: a database-generated CategoryItem id is 0 until the save, so a default exclusion would silently - exempt every unsaved sibling (CategoryInvariants.cs:49-51). Centralizing each rule as a named, + exempt every unsaved sibling (CategoryInvariants.cs:56-58). Centralizing each rule as a named, side-effect-free method is what makes the domain exhaustively unit-testable ([Rubric §14, Testability]), and the error codes ("Event.AlreadyPublished" at Event.cs:304, "Session.StatusIneligible" at SessionInvariants.cs:112) are the business vocabulary. The recurring // BR-NN comments are traceability links back to the business-requirements catalogue.

      @@ -511,7 +511,7 @@

      Invariants, business rules the UI apply one BR-49 rule and one status vocabulary (SessionStatuses.cs:4-7): the domain's status-eligibility invariant calls it (SessionInvariants.cs:110), and so does the Engagement module's feedback page through its direct Conference.Shared reference - (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Pages/Feedback/SessionFeedback.razor.cs:150, + (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Pages/Feedback/SessionFeedback.razor.cs:157, MMCA.ADC.Engagement.UI.csproj:12). [Rubric §8, Data Architecture] (deliberate handling of externally sourced data).

      Domain events and the outbox spine

      @@ -554,7 +554,7 @@

      Domain events and the outbox spinedeclare what happened, which is the Clean Architecture division of labor ([Rubric §6, CQRS & Event-Driven]). One domain detail matters for the cross-context link: Speaker.Delete() captures the previous LinkedUserId before clearing it and passes it into the Deleted - SpeakerChanged event (Speaker.cs:254,261,263), whose optional + SpeakerChanged event (Speaker.cs:256,263,265), whose optional PreviousLinkedUserId payload field (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/DomainEvents/SpeakerChanged.cs:20) exists precisely so the cross-context cleanup handler has what it needs even though the field is @@ -930,8 +930,8 @@

      AssemblyReference, ClassReference

    1726. Depends on: nothing first-party; only System.Reflection (AssemblyReference.cs:1).
    1727. Concept introduced, the assembly-marker pattern. [Rubric §2, Design Patterns] (assesses whether the patterns in use are idiomatic and solve a real problem): instead of hard-coding an assembly-name string, a scanner takes a typeof(...) from a type it knows lives in the target assembly, so renaming the assembly cannot silently break discovery. Every layer of every ADC module ships this same pair (see the sibling pairs in group-18 Conference.Application, group-19 Conference.Infrastructure, and group-20 Conference.API), so registration and discovery code reads the same way in every project. MMCA.Common ships the same pair in its own layers (for example MMCA.Common/Source/Core/MMCA.Common.Domain/AssemblyReference.cs:8,18), and its doc comment there records the split explicitly: ClassReference is the anchor for the case where a static type cannot be used.
    1728. Walkthrough: AssemblyReference.Assembly (AssemblyReference.cs:7) is a public static readonly Assembly; AssemblyName (AssemblyReference.cs:8) is its short name, falling back to string.Empty when reflection returns null. ClassReference (AssemblyReference.cs:11) has no members at all.
    1729. -
    1730. Why it's built this way: a typeof() handle is refactor-safe where a magic string is not, and a non-static ClassReference can be passed where a static class cannot. A C# static type is not a legal generic type argument, so a generic scanning API such as services.ScanModuleApplicationServices<ClassReference>() (used by the Application-layer sibling at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143) needs the non-static form.
    1731. -
    1732. Where it's used: the Domain pair has no call site in MMCA.ADC/Source today. The layer pairs that are actually consumed are the Application one (Conference.Application/DependencyInjection.cs:130) and the framework's own (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:129, MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:48). The Domain pair exists so the layer-parallel convention holds across all five layers of the module.
    1733. +
    1734. Why it's built this way: a typeof() handle is refactor-safe where a magic string is not, and a non-static ClassReference can be passed where a static class cannot. A C# static type is not a legal generic type argument, so a generic scanning API such as services.ScanModuleApplicationServices<ClassReference>() (used by the Application-layer sibling at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139) needs the non-static form.
    1735. +
    1736. Where it's used: the Domain pair has no call site in MMCA.ADC/Source today. The layer pairs that are actually consumed are the Application one (Conference.Application/DependencyInjection.cs:130) and the framework's own (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:135, MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:48). The Domain pair exists so the layer-parallel convention holds across all five layers of the module.
    1737. Caveats / not-in-source: whether the convention is enforced (an architecture fitness rule requiring one pair per project) is not visible from these files; no test in MMCA.ADC/Tests references either type.

    1738. @@ -946,7 +946,7 @@

      ConferencePermissions

    1739. Walkthrough
      • Ten public const string capabilities named EventsManage = conference:events:manage (ConferencePermissions.cs:12), SessionsManage (:15), SpeakersManage (:18), RoomsManage (:21), CategoriesManage (:24), QuestionsManage (:27), SessionSelectionManage = conference:session-selection:manage (:30), SponsorsManage = conference:sponsors:manage (:33), PartnersManage = conference:partners:manage (:36), and ActivitiesManage = conference:activities:manage (:39), plus an eleventh added under ADR-123, SessionAssetsManage = conference:session-assets:manage (:47). The {module}:{resource}:{verb} shape keeps the namespace collision-free across modules. SessionAssetsManage's doc comment (:41-46) states the asymmetry that makes it necessary: a speaker needs no capability to manage the materials of a session they present, because the handlers accept them on the strength of the session's own speaker list, and this capability is what lets an organizer or content editor do the same for a session they do not present.
      • All (ConferencePermissions.cs:53-67): an IReadOnlyList<string> collection expression naming all eleven, for granting the entire capability set to a role in one line.
      • -
      • ContentManagement (ConferencePermissions.cs:74-84): the catalog-curation subset, SessionsManage + SpeakersManage + CategoriesManage + SponsorsManage + PartnersManage + ActivitiesManage + SessionAssetsManage. Its doc comment (ConferencePermissions.cs:69-73) is the load-bearing part: a content-editor role holds these but not event structure, rooms, questions, or session selection, a distinction that capability checks express centrally and role checks cannot.
      • +
      • ContentManagement (ConferencePermissions.cs:70-79): the catalog-curation subset, SessionsManage + SpeakersManage + CategoriesManage + SponsorsManage + PartnersManage + ActivitiesManage + SessionAssetsManage. Its doc comment (ConferencePermissions.cs:65-69) is the load-bearing part: a content-editor role holds these but not event structure, rooms, questions, or session selection, a distinction that capability checks express centrally and role checks cannot.
    1740. Why it's built this way: a per-module catalog keeps each module's capability vocabulary self-contained (the Conference module can add a capability without touching Identity), and pairing the constants with named subsets makes the grants read declaratively at the registration site instead of as a hand-maintained string list. Adding a capability is then a two-line change: the constant, and its entry in whichever subsets should carry it, exactly the shape SessionAssetsManage followed when session assets shipped.
    1741. @@ -962,8 +962,8 @@

      RefreshFromSessionizeResultDTO

    1742. Depends on: nothing first-party (BCL only).
    1743. Concept introduced, the informative mutation response. [Rubric §9, API & Contract Design] (assesses stable, useful response contracts): rather than returning 204 No Content for a bulk sync, the endpoint returns counts so the caller can verify that the expected number of sessions, speakers, and categories landed. This is the read-back shape of a bulk write. [Rubric §13, Observability & Operability] also applies in the small: the Warnings list turns silent partial-import oddities into something an operator can read off the response.
    1744. Walkthrough: eight required init properties (RefreshFromSessionizeResultDTO.cs:10-31). Six are int counts, CategoriesSynced (line 10), CategoryItemsSynced (line 13), RoomsSynced (line 16), QuestionsSynced (line 19), SpeakersSynced (line 22), and SessionsSynced (line 25). SkippedSoftDeleted (line 28) counts entities that a sync re-encountered but did not restore because the app had soft-deleted them (BR-136). Warnings (line 31) is an IReadOnlyList<string> of non-fatal issues such as a duration violation or a date-range mismatch. Every property is required, so a partial or forgotten field cannot be constructed.
    1745. -
    1746. Why it's built this way: SkippedSoftDeleted is surfaced explicitly because an organizer who soft-deleted a session and then re-ran a sync would otherwise be puzzled why the count does not match Sessionize. The handler even folds that count into the warning list when it is non-zero (RefreshFromSessionizeHandler.cs:158-161).
    1747. -
    1748. Where it's used: built by the RefreshFromSessionizeCommand handler, which reads the per-strategy SessionizeSyncResult values and the shared sync context into it (RefreshFromSessionizeHandler.cs:173-183), with an all-zero instance returned when Sessionize sends an empty response (RefreshFromSessionizeHandler.cs:130-140). Returned by EventLifecycleController.RefreshAsync (EventLifecycleController.cs:117, whose [Idempotent] attribute replays the first response for a retried Idempotency-Key rather than starting a second import, EventLifecycleController.cs:116) and surfaced to the organizer UI through EventService.RefreshFromSessionizeAsync (EventService.cs:43-51), which the EventDetail page holds as _refreshResult (EventDetail.razor.cs:63, assigned at :254).
    1749. +
    1750. Why it's built this way: SkippedSoftDeleted is surfaced explicitly because an organizer who soft-deleted a session and then re-ran a sync would otherwise be puzzled why the count does not match Sessionize. The handler even folds that count into the warning list when it is non-zero (RefreshFromSessionizeHandler.cs:182-185).
    1751. +
    1752. Where it's used: built by the RefreshFromSessionizeCommand handler, which reads the per-strategy SessionizeSyncResult values and the shared sync context into it (RefreshFromSessionizeHandler.cs:195-205), with an all-zero instance returned when Sessionize sends an empty response (RefreshFromSessionizeHandler.cs:134-144). Returned by EventLifecycleController.RefreshAsync (EventLifecycleController.cs:117, whose [Idempotent] attribute replays the first response for a retried Idempotency-Key rather than starting a second import, EventLifecycleController.cs:116) and surfaced to the organizer UI through EventService.RefreshFromSessionizeAsync (EventService.cs:43-51), which the EventDetail page holds as _refreshResult (EventDetail.razor.cs:63, assigned at :254).

    1753. SessionizeCodeFormat

      @@ -1066,7 +1066,7 @@

      ConferenceReadAudience

    1754. Where it's used: three layers, one definition.

        -
      • The Conference service host spreads it into adminBypassRoles (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:267) and hands that array to ten named output-cache policies (Program.cs:268-297: ConferencePublicCache, EventsCache, SessionsCache, SpeakersCache, RoomsCache, CategoriesCache, QuestionsCache, SponsorsCache, ActivitiesCache, BookmarkCountsCache). The comment directly above states the single-source-of-truth rule and its consequence: if the two lists ever named different roles, a privileged payload would be cached and served to the public (Program.cs:264-266). One policy deliberately takes no bypass list, NowNextCache (Program.cs:285), because its payload is identical for every role.
      • +
      • The Conference service host spreads it into adminBypassRoles (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:266) and hands that array to ten named output-cache policies (Program.cs:267-296: ConferencePublicCache, EventsCache, SessionsCache, SpeakersCache, RoomsCache, CategoriesCache, QuestionsCache, SponsorsCache, ActivitiesCache, BookmarkCountsCache). The comment directly above states the single-source-of-truth rule and its consequence: if the two lists ever named different roles, a privileged payload would be cached and served to the public (Program.cs:263-265). One policy deliberately takes no bypass list, NowNextCache (Program.cs:284), because its payload is identical for every role.
      • The API layer wraps it as the ICurrentUserService extension IsPrivilegedConferenceReader() (CurrentUserServiceExtensions.cs:24-25), which the controllers use to decide whether to apply a public filter specification at all: EventsController picks null or a PublishedEventSpecification from it (.../Controllers/EventsController.cs:75, with a second guard at :141), and SessionsController (:60), SpeakersController (:65), SponsorsController (:52), ActivitiesController (:52), RoomsController (:104), SessionSpeakersController (:59), SessionCategoryItemsController (:59), SpeakerCategoryItemsController (:59), and EventSpeakersController (:58) each expose it as a private IsPrivileged property.
      • The Blazor UI reads it directly when sizing its filters and detail views: .../MMCA.ADC.Conference.UI/Pages/Public/PublicSessionList.razor.cs:121, .../PublicSpeakerList.razor.cs:101, .../PublicEventList.razor.cs:77, .../PublicEventDetail.razor.cs:64, and .../PublicSpeakerDetail.razor.cs:205.
      @@ -1090,8 +1090,8 @@

      CurrentEventSelector

    1755. ToUtc(localWallClock, timeZone) (CurrentEventSelector.cs:89-100): the DST guard. Both window boundaries land on local midnight, which is inside the spring-forward gap in zones that transition at 00:00 (the doc comment names America/Santiago and Asia/Beirut, CurrentEventSelector.cs:79-84); that wall time never existed, so a raw TimeZoneInfo.ConvertTimeToUtc would throw. The method null-guards the zone (CurrentEventSelector.cs:91), re-kinds the input as Unspecified (CurrentEventSelector.cs:93), and shifts an invalid time forward by one hour into the hour that did exist (CurrentEventSelector.cs:94-97) before converting (CurrentEventSelector.cs:99). Ambiguous (fall-back) times resolve to the zone's standard offset, which is ConvertTimeToUtc's own behavior.
    1756. -
    1757. Why it's built this way: the accessor-delegate design lets one vetted implementation of the "current event" rule serve every surface, so the home page, the live layer, and the list-page default filter can never disagree about which event is featured. Colocating GetLiveWindowUtc here keeps the window definition identical to the one EventLiveInfo advertises (its doc comment points at that record, CurrentEventSelector.cs:7), and making ToUtc public means the same spring-forward-gap fix is reused rather than re-derived: the home page's countdown calls it directly for that reason (ADCHome.razor.cs:325-331).
    1758. -
    1759. Where it's used: the Conference ADCHome page (ADCHome.razor.cs:199 for the selection, ADCHome.razor.cs:330-331 for ToUtc); the shared EventFilteredListPageBase, which resolves the default event filter once for every list page that derives from it (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Common/EventFilteredListPageBase.cs:180-188); the pages that call it directly, ActivityCreate (ActivityCreate.razor.cs:63), PublicActivityList (PublicActivityList.razor.cs:55), PublicEventList (PublicEventList.razor.cs:102), PublicSponsorList (PublicSponsorList.razor.cs:54), SponsorCreate (SponsorCreate.razor.cs:62), SpeakerDashboard (SpeakerDashboard.razor.cs:170), and SessionSelectionDashboard (SessionSelectionDashboard.razor.cs:69); the Engagement LiveEventService, which uses both SelectCurrentOrNext and GetLiveWindowUtc (LiveEventService.cs:27, LiveEventService.cs:38); and server-side by GetNowNextHandler, which resolves the current event and its window for the Now/Next query (GetNowNextHandler.cs:101, GetNowNextHandler.cs:68) and by EventLiveValidationService itself, which delegates rather than repeating the math (EventLiveValidationService.cs:229). Callers holding an EventDTO usually go through the CurrentEventDefaults wrapper instead.
    1760. +
    1761. Why it's built this way: the accessor-delegate design lets one vetted implementation of the "current event" rule serve every surface, so the home page, the live layer, and the list-page default filter can never disagree about which event is featured. Colocating GetLiveWindowUtc here keeps the window definition identical to the one EventLiveInfo advertises (its doc comment points at that record, CurrentEventSelector.cs:7), and making ToUtc public means the same spring-forward-gap fix is reused rather than re-derived: the home page's countdown calls it directly for that reason (ADCHome.razor.cs:328-334).
    1762. +
    1763. Where it's used: the Conference ADCHome page (ADCHome.razor.cs:202 for the selection, ADCHome.razor.cs:333-334 for ToUtc); the shared EventFilteredListPageBase, which resolves the default event filter once for every list page that derives from it (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Common/EventFilteredListPageBase.cs:181-189); the pages that call it directly, ActivityCreate (ActivityCreate.razor.cs:64), PublicActivityList (PublicActivityList.razor.cs:71), PublicEventList (PublicEventList.razor.cs:102), PublicSponsorList (PublicSponsorList.razor.cs:70), SponsorCreate (SponsorCreate.razor.cs:63), SpeakerDashboard (SpeakerDashboard.razor.cs:171), and SessionSelectionDashboard (SessionSelectionDashboard.razor.cs:71); the Engagement LiveEventService, which uses both SelectCurrentOrNext and GetLiveWindowUtc (LiveEventService.cs:27, LiveEventService.cs:38); and server-side by GetNowNextHandler, which resolves the current event and its window for the Now/Next query (GetNowNextHandler.cs:101, GetNowNextHandler.cs:68) and by EventLiveValidationService itself, which delegates rather than repeating the math (EventLiveValidationService.cs:234). Callers holding an EventDTO usually go through the CurrentEventDefaults wrapper instead.

    1764. CurrentEventDefaults

      @@ -1129,7 +1129,7 @@

      PartnerType

    1765. Concept introduced, the numeric value doubling as display order. [Rubric §9, API & Contract Design] (a small, purpose-built enum rather than a bare bool or free-text string): the doc comment states the numbers are not arbitrary, they are the display order of the public partners band, community first, then special (PartnerType.cs:3-5). Community is deliberately the zero value: it is the common case and, unlike a sponsorship tier, carries no commercial weight, so an omitted type landing on it is harmless (remarks, PartnerType.cs:7-10).
    1766. Walkthrough: two explicitly numbered members, Community = 0 (PartnerType.cs:15) and Special = 1 (PartnerType.cs:18). Explicit numbering keeps the wire meaning and the display order stable if the members are ever reordered in source.
    1767. Why it's built this way: making the enum's numeric order the sort key means a consumer that groups by PartnerType and orders ascending gets the correct display order for free, with no separate lookup table to keep in sync.
    1768. -
    1769. Where it's used: carried on Partner.PartnerType (Partner.cs:25, set in both create and reconstitute constructors, Partner.cs:51,59,82,118,129) and on PartnerDTO.PartnerType. Seeded by ConferenceModuleDbSeeder's sample partners, two Community and one Special (ConferenceModuleDbSeeder.cs:424-429). Read by the ADC home page's partner band: ADCHome groups the fetched partners by PartnerType, orders the groups ascending so Community renders first, and orders each group by Sort then Name (ADCHome.razor.cs:294-301). Exercised across the full CRUD test surface for Partner (domain, application, API, and UI test projects for creation, update, and validation).
    1770. +
    1771. Where it's used: carried on Partner.PartnerType (Partner.cs:25, set in both create and reconstitute constructors, Partner.cs:51,59,82,118,129) and on PartnerDTO.PartnerType. Seeded by ConferenceModuleDbSeeder's sample partners, two Community and one Special (ConferenceModuleDbSeeder.cs:424-429). Read by the ADC home page's partner band: ADCHome groups the fetched partners by PartnerType, orders the groups ascending so Community renders first, and orders each group by Sort then Name (ADCHome.razor.cs:297-304). Exercised across the full CRUD test surface for Partner (domain, application, API, and UI test projects for creation, update, and validation).

    1772. QuestionModerationDefault

      @@ -1155,7 +1155,7 @@

      SponsorLiveInfo

    1773. Concept: the same server-resolved cross-module snapshot RoomSessionInfo introduces, applied to a Sponsor. [Rubric §7, Microservices Readiness]: Engagement never references the Sponsor entity; it asks Conference three questions and gets three values. [Rubric §11, Security]: because the QR is printed, the server must confirm the sponsor still exists and its event is published before recording anything, and the soft-delete query filter means a pulled sponsor answers exactly like one that never existed, so an old printed QR simply stops working (EventLiveValidationService.cs:110-123).
    1774. Walkthrough: a positional sealed record with three parameters (SponsorLiveInfo.cs:12-15), EventId (line 13), IsPublished (line 14), and SponsorName (line 15, carried so a consumer can render a confirmation without a second cross-module call, per the doc comment SponsorLiveInfo.cs:3-8).
    1775. Why it's built this way: the booth-visit write needs the owning event for scoping, the published flag for the gate, and the name for the confirmation screen. Returning all three in one record keeps the scan path to a single round-trip.
    1776. -
    1777. Where it's used: produced by EventLiveValidationService.GetSponsorLiveInfoAsync (EventLiveValidationService.cs:106-141, which looks the sponsor up, then its owning event, and returns NotFound for either miss), served over gRPC via the GetSponsorLiveInfo rpc (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Contracts/Protos/event_live_validation.proto:40) and its adapter (EventLiveValidationServiceGrpcAdapter.cs:99). Consumed by the Engagement RecordSponsorVisitHandler, which propagates a lookup failure unchanged (RecordSponsorVisitHandler.cs:61-62), rejects an unpublished event (RecordSponsorVisitHandler.cs:65-66), scopes the check-in row to the returned EventId (RecordSponsorVisitHandler.cs:77), and echoes SponsorName on the response whether the visit is new or a replay (RecordSponsorVisitHandler.cs:92). The fail-open stub returns new SponsorLiveInfo(default, true, string.Empty) (DisabledEventLiveValidationService.cs:51).
    1778. +
    1779. Where it's used: produced by EventLiveValidationService.GetSponsorLiveInfoAsync (EventLiveValidationService.cs:106-141, which looks the sponsor up, then its owning event, and returns NotFound for either miss), served over gRPC via the GetSponsorLiveInfo rpc (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Contracts/Protos/event_live_validation.proto:40) and its adapter (EventLiveValidationServiceGrpcAdapter.cs:122). Consumed by the Engagement RecordSponsorVisitHandler, which propagates a lookup failure unchanged (RecordSponsorVisitHandler.cs:61-62), rejects an unpublished event (RecordSponsorVisitHandler.cs:65-66), scopes the check-in row to the returned EventId (RecordSponsorVisitHandler.cs:77), and echoes SponsorName on the response whether the visit is new or a replay (RecordSponsorVisitHandler.cs:92). The fail-open stub returns new SponsorLiveInfo(default, true, string.Empty) (DisabledEventLiveValidationService.cs:51).

    1780. SessionAssetKind

      @@ -1198,21 +1198,21 @@

      SessionAssetLimits

      validator, the access service and the API controller, rather than each layer hard-coding its own copy. The doc comment on AllowedFileExtensions notes the extension check is only half the gate: the framework's DocumentContentSniffer also reads the real bytes, so a renamed executable is refused even - though its extension is on the allow-list (SessionAssetLimits.cs:74-77). -
    1781. Walkthrough: five members (SessionAssetLimits.cs:10-95).
        + though its extension is on the allow-list (SessionAssetLimits.cs:29-33). +
      • Walkthrough: five members (SessionAssetLimits.cs:10-49).
        • MaxFileBytes = 50 * 1024 * 1024 (SessionAssetLimits.cs:10): the accepted upload size.
        • -
        • MaxRequestBytes = MaxFileBytes + 64 * 1024 (SessionAssetLimits.cs:65): the transport-level cap on +
        • MaxRequestBytes = MaxFileBytes + 64 * 1024 (SessionAssetLimits.cs:20): the transport-level cap on the whole multipart body. It adds 64 KB of headroom over MaxFileBytes for the multipart boundary lines and the part's Content-Disposition/Content-Type headers; without it, a file at exactly MaxFileBytes is rejected by Kestrel with a bare 413 before the friendlier - SessionAsset.InvalidUpload check ever runs (SessionAssetLimits.cs:58-64). The file itself still + SessionAsset.InvalidUpload check ever runs (SessionAssetLimits.cs:13-19). The file itself still tops out at MaxFileBytes via that domain check and the command validator.
        • -
        • MaxAssetsPerSession = 10 (SessionAssetLimits.cs:72): the cap on live assets per session, sized so +
        • MaxAssetsPerSession = 10 (SessionAssetLimits.cs:27): the cap on live assets per session, sized so a speaker publishing a deck, a handout, a repository link and a recording link sits well inside it while the public page stays bounded.
        • -
        • AllowedFileExtensions (SessionAssetLimits.cs:79-88): a lower-case, dot-prefixed list (.pdf, +
        • AllowedFileExtensions (SessionAssetLimits.cs:34-43): a lower-case, dot-prefixed list (.pdf, .pptx, .docx, .xlsx, .zip, .md, .txt).
        • -
        • AcceptAttribute = string.Join(',', AllowedFileExtensions) (SessionAssetLimits.cs:94): the same +
        • AcceptAttribute = string.Join(',', AllowedFileExtensions) (SessionAssetLimits.cs:49): the same list rendered as a browser accept attribute value, so the file picker only offers formats the server will accept.
        @@ -1236,7 +1236,7 @@

        RoomSessionInfo

      • Concept introduced, the server-resolved target (never trust a client-supplied id). [Rubric §11, Security] (assesses whether authorization-relevant inputs are decided server-side): the attendee's device sends a room id, not a session id, and Conference resolves which session that room is hosting at the call instant. A tampered QR therefore cannot record a check-in against an arbitrary session. [Rubric §9, API & Contract Design] also applies in a subtle way: SessionTitle rides along so the caller can render a confirmation without a second cross-module call, and the grace window travels in the request rather than living on this record, because "how early does a scan count" is check-in policy owned by Engagement, not schedule data owned by Conference (doc comment, RoomSessionInfo.cs:10-14, and the same split restated on the interface, IEventLiveValidationService.cs:52-55).
      • Walkthrough: a positional sealed record with four parameters (RoomSessionInfo.cs:20-24), SessionId (the session the room is hosting at the query instant, line 19), SessionTitle (line 20), EventId (the owning event, line 21), and IsPublished (line 22). No behavior.
      • Why it's built this way: bundling the title and the published flag with the id keeps the door-scan path to a single cross-module round-trip, and keeping the grace window out of the record preserves the boundary: Conference answers the schedule question, Engagement decides the policy.
      • -
      • Where it's used: produced by EventLiveValidationService.GetCurrentRoomSessionInfoAsync (EventLiveValidationService.cs:145), which excludes unscheduled sessions (EventLiveValidationService.cs:157-167), resolves the event's zone without a fallback (EventLiveValidationService.cs:182-186), converts session wall-clock times through CalendarExportMapper.ToUtc (EventLiveValidationService.cs:191-199), and prefers an in-progress session over an upcoming one inside the grace window (EventLiveValidationService.cs:203-210) before building the record (EventLiveValidationService.cs:218-222). Carried over the wire by EventLiveValidationServiceGrpcAdapter (EventLiveValidationServiceGrpcAdapter.cs:128) against the GetCurrentRoomSessionInfo rpc (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Contracts/Protos/event_live_validation.proto:47). Consumed by the Engagement RecordRoomCheckInHandler, which passes the configured grace window (RecordRoomCheckInHandler.cs:52-54, from CheckInSettings.RoomCheckInGraceMinutes, default 15, MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Shared/CheckIns/CheckInSettings.cs:21), collapses a NotFound into a generic "no session is starting here" message so room ids do not leak while still propagating transport failures unchanged (RecordRoomCheckInHandler.cs:55-65, :98-102), rejects an unpublished event (RecordRoomCheckInHandler.cs:68-69), and uses the resolved SessionId/SessionTitle for the check-in row and its response (RecordRoomCheckInHandler.cs:73, :91-92).
      • +
      • Where it's used: produced by EventLiveValidationService.GetCurrentRoomSessionInfoAsync (EventLiveValidationService.cs:145), which excludes unscheduled sessions (EventLiveValidationService.cs:157-172), resolves the event's zone without a fallback (EventLiveValidationService.cs:187-191), converts session wall-clock times through CalendarExportMapper.ToUtc (EventLiveValidationService.cs:196-204), and prefers an in-progress session over an upcoming one inside the grace window (EventLiveValidationService.cs:208-215) before building the record (EventLiveValidationService.cs:223-227). Carried over the wire by EventLiveValidationServiceGrpcAdapter (EventLiveValidationServiceGrpcAdapter.cs:151) against the GetCurrentRoomSessionInfo rpc (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Contracts/Protos/event_live_validation.proto:47). Consumed by the Engagement RecordRoomCheckInHandler, which passes the configured grace window (RecordRoomCheckInHandler.cs:52-54, from CheckInSettings.RoomCheckInGraceMinutes, default 15, MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Shared/CheckIns/CheckInSettings.cs:21), collapses a NotFound into a generic "no session is starting here" message so room ids do not leak while still propagating transport failures unchanged (RecordRoomCheckInHandler.cs:55-65, :98-102), rejects an unpublished event (RecordRoomCheckInHandler.cs:68-69), and uses the resolved SessionId/SessionTitle for the check-in row and its response (RecordRoomCheckInHandler.cs:73, :91-92).

      PartnerDTO

      @@ -1249,7 +1249,7 @@

      PartnerDTO

    1782. Concept: the same aggregate-DTO shape EventDTO uses at a smaller scale, length constants and a concurrency token living on the DTO because, per its own doc comment, it is the lowest layer the domain invariants, EF configuration, and UI can all reach (PartnerDTO.cs:9-12). [Rubric §9, API & Contract Design] (a stable, purpose-built contract) and [Rubric §8, Data Architecture] (IConcurrencyAware round-trips the EF RowVersion token so a concurrent edit is detected instead of silently overwritten).
    1783. Walkthrough: four length constants, NameMaxLength = 200 (PartnerDTO.cs:18), LogoUrlMaxLength = 2000 (PartnerDTO.cs:21), DescriptionMaxLength = 2000 (PartnerDTO.cs:24), and WebsiteUrlMaxLength = 2000 (PartnerDTO.cs:27); identity and concurrency, Id (required, PartnerDTO.cs:30) and RowVersion (byte[] defaulting to [], PartnerDTO.cs:33); Name (required string, PartnerDTO.cs:36) and PartnerType (PartnerDTO.cs:39); optional scalars LogoUrl, Description, and WebsiteUrl (all string?, PartnerDTO.cs:42,45,48); Sort (int, the display order within the partner's type group, PartnerDTO.cs:51); and EventId (the owning event's foreign key, PartnerDTO.cs:54).
    1784. Why it's built this way: keeping the four length constants on the DTO rather than duplicating them in the domain lets PartnerInvariants re-export them by reference (NameMaxLength = PartnerDTO.NameMaxLength and the same pattern for the other three, PartnerInvariants.cs:16,19,22,25) instead of restating the numbers, so the UI, the domain guard, and the EF configuration all read one source of truth.
    1785. -
    1786. Where it's used: mapped from Partner by the Mapperly-generated PartnerDTOMapper.MapToDTO (PartnerDTOMapper.cs:17, per ADR-001); it is the DTO type parameter of CreatePartnerHandler (CreatePartnerHandler.cs:15-20, built over CreateEntityHandlerBase<PartnerCreateRequest, Partner, PartnerIdentifierType, PartnerDTO>) and of PartnersController, so its inherited GetAll/GetById/Create/paged/lookup/export actions all speak it (PartnersController.cs:38-41,78-153). Read by the ADC home page's partner band, grouped and sorted client-side (ADCHome.razor.cs:294-301), and bound by the UI's PartnerFormModel/PartnerEditModel.
    1787. +
    1788. Where it's used: mapped from Partner by the Mapperly-generated PartnerDTOMapper.MapToDTO (PartnerDTOMapper.cs:17, per ADR-001); it is the DTO type parameter of CreatePartnerHandler (CreatePartnerHandler.cs:15-20, built over CreateEntityHandlerBase<PartnerCreateRequest, Partner, PartnerIdentifierType, PartnerDTO>) and of PartnersController, so its inherited GetAll/GetById/Create/paged/lookup/export actions all speak it (PartnersController.cs:38-41,86-161). Read by the ADC home page's partner band, grouped and sorted client-side (ADCHome.razor.cs:297-304), and bound by the UI's PartnerFormModel/PartnerEditModel.

    1789. SessionLiveInfo

      @@ -1403,7 +1403,7 @@

      NowNextSessionDTO

    1790. Where it's used: nested as the Now and Next lists on NowNextDTO; each row is built by the handler's private ToRow projection over a Session aggregate (GetNowNextHandler.cs:73), then served on the anonymous now-next endpoints of - EventsController (EventsController.cs:165-174,181-188).
    1791. + EventsController (EventsController.cs:172-181,188-195).

      SessionStatuses

      @@ -1461,14 +1461,14 @@

      NowNextDTO

    1792. Why it's built this way: batching Next as a list rather than a single session is the modelling decision that makes the widget correct on a multi-track schedule. Local-plus-UTC times live on the row type, keeping this envelope thin. The endpoint is [AllowAnonymous] and output-cached under the - NowNextCache policy (EventsController.cs:165-167) because the payload is public and changes with + NowNextCache policy (EventsController.cs:172-174) because the payload is public and changes with the clock, which is the [Rubric §12, Performance & Scalability] lever for a widget that polls.
    1793. Where it's used: returned as Result<NowNextDTO> by GetNowNextHandler (GetNowNextHandler.cs:25) for GetNowNextQuery; exposed by EventsController both per event - (GET {id}/now-next, EventsController.cs:168) and in the id-less "current event" form the - home-screen widget calls (GET now-next, EventsController.cs:183). Both + (GET {id}/now-next, EventsController.cs:175) and in the id-less "current event" form the + home-screen widget calls (GET now-next, EventsController.cs:190). Both NowNextWidgetProvider and Engagement's INowNextService deliberately mirror this wire shape locally instead of referencing the type, so neither takes a project reference on Conference.
    1794. @@ -1628,7 +1628,7 @@

      SessionDTO

      constants (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/SessionInvariants.cs:16,19,22,31), which is what the domain guards, the EF configuration and the validators then read, while the UI form binds them straight off this type - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Sessions/SessionFormModel.cs:38,42,46). + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Sessions/SessionFormModel.cs:44,48,52). One edit here moves the database column, the domain guard and the client-side MaxLength together.
    1795. Concept, the concurrency-aware read contract. [Rubric §9, API & Contract Design] and [Rubric §8, Data Architecture]. Beyond IBaseDTO's Id, this DTO implements IConcurrencyAware, so it round-trips the EF @@ -1772,7 +1772,7 @@

      DisabledSessionBookmarkValidat (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/ConferenceModule.cs:21-25), which IModule declares as an opt-in default no-op (MMCA.Common/Source/Core/MMCA.Common.Application/Modules/IModule.cs:34) and ModuleLoader invokes - for a disabled module (MMCA.Common/Source/Core/MMCA.Common.Application/Modules/ModuleLoader.cs:108).

    1796. + for a disabled module (MMCA.Common/Source/Core/MMCA.Common.Application/Modules/ModuleLoader.cs:119).
    1797. Walkthrough: two one-line members, both returning already-completed tasks.
      • ValidateSessionForBookmarkAsync(...) (DisabledSessionBookmarkValidationService.cs:33-34) returns Task.FromResult(Result.Success()), approving unconditionally.
      • @@ -1792,7 +1792,7 @@

        DisabledSessionBookmarkValidat (ConferenceModule.cs:23) alongside the IEventLiveValidationService stub. In the deployed split-service topology it is a fallback that gets overwritten: the extracted Engagement service calls AddConferenceSessionValidationClient - (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:281), and that registration uses + (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:283), and that registration uses ServiceCollectionDescriptorExtensions.Replace so the resolved ISessionBookmarkValidationService is the gRPC adapter, whether the prior binding was the real in-process service or this stub @@ -1841,7 +1841,7 @@

        SessionAiScoreDTO

      • Concept introduced, the self-contained scored row. [Rubric §12, Performance & Scalability] (assesses shaping the payload so the client does no secondary lookups) and [Rubric §9, API & Contract Design]. The interesting move is that the score does not travel alone: alongside the numbers it carries SpeakerLocalities, SessionCategories, and SessionLevel (lines 56-62), so the organizer dashboard can print a complete, sortable row for each session without an N+1 fetch back to the Session, Speaker, or Category aggregates. [Rubric §13, Observability & Operability]: the record also records how the number was produced, ModelUsed (line 39), PromptVersion (line 47), and ScoredOn (line 50), so a score is auditable, comparable against another score, and its staleness visible.
      • Walkthrough: SessionId + SessionTitle (lines 9-12, required) identify the row. Then the scores, all required decimal on a 1.0 to 10.0 scale: an OverallScore (line 15) plus six dimension sub-scores, TopicRelevanceScore, DescriptionQualityScore, NoveltyScore, ActionableTakeawaysScore, DepthOrInsightQualityScore, CredibilityExperienceScore (lines 18-33). Reasoning (line 36, required) holds the model's free-text justification. ModelUsed (line 39), PromptVersion (line 47), and ScoredOn (line 50) capture provenance, all three required. PromptVersion is the reviewer-brief contract version behind the numbers, shaped yyyy-MM-dd.N or the sentinel legacy for rows written before the column existed (documented at SessionAiScoreDTO.cs:41-46); the value comes from the scoring service's own constant, today "2026-09-04.1" (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Sessions/Scoring/SessionScoringService.cs:53). The tail members are optional display context: Status (line 53, nullable), SpeakerLocalities and SessionCategories (lines 56-59, defaulted to [] so never null), and SessionLevel (line 62, nullable). SpeakerLocalities is the speaker-locality convention surfacing here: those tier names come from a CategoryItem under the "Where are you traveling from" category (Sessionize id 121854), not from any geographic field on Speaker (resolved by SpeakerLocalityHelper in Conference.Application, SpeakerLocalityHelper.cs:19-33).
      • Why it's built this way: embedding display context in the score DTO avoids per-row lookups on a dashboard that shows every session in an event at once, and recording the model id, the prompt contract version, and the timestamp keeps an AI-produced number honest and re-scorable. Two scores are only comparable when both the model and the prompt behind them match, so surfacing PromptVersion next to ModelUsed is what lets a reader tell a genuine ranking change from a change of reviewer brief.
      • -
      • Where it's used: nested as the AiScores collection on SessionSelectionDashboardDTO; projected from the SessionAiScore entity by GetSessionSelectionDashboardHandler (GetSessionSelectionDashboardHandler.cs:374 carries PromptVersion across), written by ScoreEventSessionsHandler (which stamps aiScoringService.ModelId and aiScoringService.PromptVersion, ScoreEventSessionsHandler.cs:83, and returns a ScoreEventSessionsResultDTO summary), and read back through SessionSelectionController.
      • +
      • Where it's used: nested as the AiScores collection on SessionSelectionDashboardDTO; projected from the SessionAiScore entity by GetSessionSelectionDashboardHandler (GetSessionSelectionDashboardHandler.cs:374 carries PromptVersion across), written by SessionScoringRunner, the pass that ScoreEventSessionsInternalCommandHandler runs (ScoreEventSessionsInternalCommandHandler.cs:42), which stamps aiScoringService.ModelId and aiScoringService.PromptVersion (SessionScoringRunner.cs:91) and returns a ScoreEventSessionsResultDTO summary), and read back through SessionSelectionController.
      • Caveats / not-in-source: the 1.0 to 10.0 range and the status vocabulary are documented in the property comments and enforced by the scoring handler and the external model prompt, not by this record. The legacy sentinel is likewise a convention, not a constant on this type: rows predating the column were backfilled with it by the AddSessionAiScorePromptVersion migration (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Sessions/SessionAiScoreConfiguration.cs:59-61).

      [Rubric §16, AI-Native Application Architecture] applies: this type is part of the AI session-scoring feature (a model call behind a port, versioned prompt and model, an evaluation gate, metered spend; ADR-111).

      @@ -2049,7 +2049,7 @@

      SponsorTier

      (SponsorTier.cs:15-24). A plain ascending sort therefore renders the largest package first with no lookup table, and PublicSponsorList relies on exactly that: it groups by Tier and calls .OrderBy(g => g.Key) - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Sponsors/PublicSponsorList.razor.cs:88-92), + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Sponsors/PublicSponsorList.razor.cs:103-107), breaking ties inside a tier by Sort then Name. Renumbering a member would silently reorder the public page.
    1798. It is zero-based because CA1008 requires a zero member (SponsorTier.cs:9-11), which has the @@ -2075,8 +2075,8 @@

      SponsorTier

      SponsorDTO.Tier and on SponsorCreateRequest / SponsorUpdateRequest; the UI keys its - grouped view model on it (PublicSponsorList.razor.cs:41) and renders the label through a localized - resource key, L[$"Tier.{tier}"] (PublicSponsorList.razor.cs:44), which is [Rubric §27, i18n] in + grouped view model on it (PublicSponsorList.razor.cs:45) and renders the label through a localized + resource key, L[$"Tier.{tier}"] (PublicSponsorList.razor.cs:48), which is [Rubric §27, i18n] in miniature: the enum member name is the resource key, so the label translates without a switch statement.

    1799. @@ -2141,7 +2141,7 @@

      ActivityDTO

      (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Activities/Activity.cs:28-36). The DTO faithfully carries that decision instead of quietly converting: a consumer that needs an absolute instant has to combine the value with the event's zone, and the public page simply formats it as-is - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Activities/PublicActivityList.razor.cs:44). + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Activities/PublicActivityList.razor.cs:48).
    1800. Walkthrough: five constants (ActivityDTO.cs:17-30) then eleven properties (ActivityDTO.cs:33-63). Id and RowVersion are the two framework contracts (lines 33 and 36; the token defaults to an empty array rather than being nullable, see QuestionDTO). Name @@ -2153,7 +2153,7 @@

      ActivityDTO

      (Activity.cs:38-42, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Activities/PublicActivityList.razor:47), and VenueAddress is what the "directions" affordance hands to a maps URL, labelled with the venue - name or the activity name when there is none (PublicActivityList.razor.cs:106-114). SortOrder + name or the activity name when there is none (PublicActivityList.razor.cs:121-129). SortOrder (line 60) breaks ties between activities that start at the same minute. EventId (line 63) scopes the activity to exactly one event. Note what is absent: the entity's [Navigation] Event? reference @@ -2176,9 +2176,9 @@

      ActivityDTO

      (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Activities/ActivitiesController.cs:41)
      , whose anonymous reads are narrowed for non-privileged callers by one overridden read hook, GetReadSpecificationAsync, so an excluded activity is a 404 rather than a redacted record - (ActivitiesController.cs:61-79,81-82); rendered by + (ActivitiesController.cs:61-79,89-90); rendered by PublicActivityList, which orders by StartTime then - SortOrder (PublicActivityList.razor.cs:88-89), and by the organizer-facing + SortOrder (PublicActivityList.razor.cs:103-104), and by the organizer-facing ActivityList; written through ActivityCreateRequest and ActivityUpdateRequest.
    1801. @@ -2462,30 +2462,30 @@

      ConferenceFeatures

    1802. What it is: the feature-flag name catalog for the Conference module. It holds two constants: SessionizeIntegration = "Conference.SessionizeIntegration" (ConferenceFeatures.cs:23), which gates the Sessionize external-data sync capability, and SessionScoring = "Conference.SessionScoring" - (ConferenceFeatures.cs:96), which gates the AI session-scoring pass.
    1803. + (ConferenceFeatures.cs:38), which gates the AI session-scoring pass.
    1804. Depends on: nothing first-party, beyond the [FeatureFlag] attribute (MMCA.Common/Source/Core/MMCA.Common.Shared/FeatureFlags/FeatureFlagAttribute.cs:32) and FeatureFlagLifetime it is decorated with.
    1805. Concept introduced, feature flags as named constants. Rubric section 6, CQRS and Event-Driven Design (assesses whether cross-cutting behavior such as flags and configuration is centralized rather than scattered). Each constant's value matches a key under the "FeatureManagement" configuration section, and per the class doc comment (ConferenceFeatures.cs:5-9) it is consumed with [FeatureGate] attributes and the IFeatureGated marker interface. Centralizing the string here means the flag name is written once: a typo cannot silently split one flag into two, one of which is never configured and therefore always off. The "{Module}.{Feature}" naming convention keeps flags from different modules unambiguous inside one configuration file. The mechanism itself is ADR-031.
    1806. Walkthrough: two public const string members, each decorated [FeatureFlag(FeatureFlagLifetime.Permanent, Owner = "ADC Conference")] - (ConferenceFeatures.cs:22,95). SessionizeIntegration's doc comment (ConferenceFeatures.cs:12-15) + (ConferenceFeatures.cs:22,37). SessionizeIntegration's doc comment (ConferenceFeatures.cs:12-15) records the runtime contract: when the flag is disabled, RefreshFromSessionizeCommand short-circuits with a failure result and organizers manage event data manually instead of syncing. Its second <para> (ConferenceFeatures.cs:16-20) records why the lifetime is Permanent rather than Temporary: it is a shipped kill switch over a live external dependency, kept so an organizer can shed the Sessionize call during a provider incident, so it is not a rollout toggle and carries no removal - date (ADR-031). SessionScoring's doc comment (ConferenceFeatures.cs:84-87) records the parallel + date (ADR-031). SessionScoring's doc comment (ConferenceFeatures.cs:25-29) records the parallel contract: when disabled, the organizer trigger endpoint answers 404 and ScoreEventSessionsInternalCommand short-circuits with a failure result, so organizers select sessions from the counts and overlap views alone, and scores already stored stay readable, only the pass that - produces new ones is switched off. Its <para> (ConferenceFeatures.cs:88-93) gives the reason it is + produces new ones is switched off. Its <para> (ConferenceFeatures.cs:30-35) gives the reason it is Permanent: a scoring pass is one paid Anthropic call per session, and the organizer needs a way to stop it during a provider incident, a pricing surprise, or a runaway loop. FeatureFlagRegistry is what enforces that a Permanent flag never carries a RemoveBy date and a Temporary one always does.
    1807. Why it's built this way: putting the Sessionize sync and the AI scoring pass each behind a flag lets organizers turn the capability off (for example during a Sessionize API maintenance window, or a scoring provider incident or cost spike) through configuration, with no redeploy; declaring both Permanent with an owner records, in the attribute itself, that these are durable operational switches rather than debt to clean up later.
    1808. -
    1809. Where it's used: RefreshFromSessionizeCommand implements IFeatureGated (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeCommand.cs:13) and returns SessionizeIntegration from its FeatureName property (RefreshFromSessionizeCommand.cs:19); ScoreEventSessionsInternalCommand and SessionSelectionController read SessionScoring (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/ScoreEventSessionsInternalCommand.cs, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionSelectionController.cs), so the pipeline decorator (G05), not the handler body, does the gating.
    1810. +
    1811. Where it's used: RefreshFromSessionizeCommand implements IFeatureGated (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeCommand.cs:13) and returns SessionizeIntegration from its FeatureName property (RefreshFromSessionizeCommand.cs:21); ScoreEventSessionsInternalCommand and SessionSelectionController read SessionScoring (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/ScoreEventSessionsInternalCommand.cs, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionSelectionController.cs), so the pipeline decorator (G05), not the handler body, does the gating.

    1812. CategoryItemChanged

      @@ -2531,14 +2531,14 @@

      CategoryItemChanged

      through the dispatch pipeline unchanged.
    1813. Where it's used: raised by Category at all three child mutation points, AddCategoryItem, UpdateCategoryItem, and RemoveCategoryItem - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/Category.cs:144,176,194); + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/Category.cs:146,178,196); collected on the aggregate and dispatched in-process by DomainEventDispatcher after SaveChangesAsync.
    1814. Caveats / not-in-source: no handler subscribes to it in the ADC source today; it is available for future observers and audit. Note also the one asymmetry: deleting the parent category cascade soft-deletes its items (BR-71) through a single DeleteChildren call but raises one CategoryChanged(Deleted) rather than one CategoryItemChanged per item - (Category.cs:102-114), so a subscriber must treat parent deletion as implying its children.
    1815. + (Category.cs:104-116), so a subscriber must treat parent deletion as implying its children.

      ConferenceCategoryDTO

      @@ -2698,8 +2698,8 @@

      CategoryChanged

    1816. Why it's built this way: one lifecycle event per aggregate keeps the event surface small, and State lets a handler branch on the transition rather than subscribing to three separate types.
    1817. Where it's used: raised from Category's Create (Added), Update (Updated), - and Delete (Deleted) at Category.cs:72,95,111, where the delete only raises once the combined - cascade result succeeds (Category.cs:106-111); dispatched in-process by + and Delete (Deleted) at Category.cs:73,97,113, where the delete only raises once the combined + cascade result succeeds (Category.cs:108-113); dispatched in-process by DomainEventDispatcher after SaveChangesAsync.
    1818. Caveats / not-in-source: like CategoryItemChanged, no handler subscribes to it in the ADC source today. It is an in-process domain event, not an integration event, so it never @@ -2920,7 +2920,7 @@

      SpeakerCategoryItemChanged

    1819. Walkthrough: sealed record class with State, SpeakerId, SpeakerCategoryItemId, CategoryItemId (SpeakerCategoryItemChanged.cs:13-17).
    1820. Where it's used: raised by Speaker's AddSpeakerCategoryItem (declared at - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:314, raises at :335), + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:316, raises at :335), RestoreSpeakerCategoryItem (:352, raises Added at :364), and RemoveSpeakerCategoryItem (:374, raises at :382); captured by the outbox.
    1821. Caveats / not-in-source: no handler subscribes today.
    1822. @@ -2943,7 +2943,7 @@

      SpeakerQuestionAnswerChanged

    1823. Walkthrough: sealed record class with State, SpeakerId, SpeakerQuestionAnswerId, QuestionId (SpeakerQuestionAnswerChanged.cs:13-17).
    1824. Where it's used: raised by Speaker's AddSpeakerQuestionAnswer (declared at - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:401, raises at :414), + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:403, raises at :414), UpdateSpeakerQuestionAnswer (:425, raises at :438), and RemoveSpeakerQuestionAnswer (:448, raises at :456); captured by the outbox.
    1825. Caveats / not-in-source: no handler subscribes today.
    1826. @@ -3158,10 +3158,10 @@

      SpeakerChanged

    1827. Walkthrough: sealed record class SpeakerChanged(DomainEntityState State, SpeakerIdentifierType SpeakerId, string FullName, UserIdentifierType? PreviousLinkedUserId = null) chaining (State, SpeakerId) to the base (SpeakerChanged.cs:16-21). The default null on the fourth parameter is what keeps the non-delete raise sites a three-argument call: Create (raises at - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:168), Update (:235), + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:169), Update (:235), LinkUser (:284), and UnlinkUser (:302), while the Delete override (declared at :251) is the only four-argument call: it captures LinkedUserId into a local before calling base.Delete(), nulls the - field, and passes the captured value (Speaker.cs:253-265). Note that LinkUser and UnlinkUser both emit + field, and passes the captured value (Speaker.cs:255-267). Note that LinkUser and UnlinkUser both emit Updated, not a bespoke link event, so a subscriber cannot tell a link change from a name edit by event type alone.
    1828. Why it's built this way: an event is an immutable record of what already happened, so snapshotting the @@ -3269,7 +3269,7 @@

      SpeakerUnlinkedFromUser

    1829. Why it's built this way: it closes the loop on the eventually-consistent link, and it is the downstream half of a SpeakerChanged delete. That is exactly why Speaker.Delete snapshots the previous link id into a local before the soft-delete runs - (Speaker.cs:251-254) and carries it on the domain event (Speaker.cs:263): the handler that publishes + (Speaker.cs:253-256) and carries it on the domain event (Speaker.cs:265): the handler that publishes this integration event would otherwise have nothing left to read.
    1830. Where it's used: added to the aggregate by UnlinkUserFromSpeakerHandler @@ -3317,7 +3317,7 @@

      SessionFeedbackSubmitted

      IPointsAwarder (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/Points/IntegrationEventHandlers/SessionFeedbackSubmittedPointsHandler.cs:42-49), and is registered as a broker consumer in the Engagement service host - (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:287).
    1831. + (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:289).

      EventFeedbackSubmitted

      @@ -3347,7 +3347,7 @@

      EventFeedbackSubmitted

      and the OutboxProcessor publishes it through IMessageBus instead, wrapped in a broker-publish resilience pipeline - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:537-550). + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Outbox/Processing/OutboxProcessor.cs:529-544). The registered transport then decides delivery: in-process for the monolith, MassTransit broker for the extracted services.
    1832. It carries an explicit wire name. [EventName("Conference.EventFeedbackSubmitted.v1")] @@ -3387,7 +3387,7 @@

      EventFeedbackSubmitted

      PointsSubjectKeys and calls IPointsAwarder (EventFeedbackSubmittedPointsHandler.cs:40-47); registered as a broker consumer in the Engagement service - host (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:288).

      + host (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:290).


    1833. @@ -3411,8 +3411,8 @@

      SessionAiScore

    1834. Why it's built this way: range validation belongs to the domain because it is a statement about what a score is, not about who asked for one. Keeping the check in a private helper shared by the two public entry points means a future range change cannot be applied to one path and forgotten on the other.
    1835. -
    1836. Where it's used: created by the scoring pass (SessionScoringRunner, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/SessionScoringRunner.cs:81, over the repository resolved at :28, passing aiScoringService.ModelId and aiScoringService.PromptVersion as the provenance pair at :83), invoked from the internal-command handler ScoreEventSessionsInternalCommandHandler (ADR-114), read back by the organizer dashboard (GetSessionSelectionDashboardHandler, .../GetSessionSelectionDashboard/GetSessionSelectionDashboardHandler.cs:98,338,363), configured by SessionAiScoreConfiguration in Infrastructure, projected as SessionAiScoreDTO, and rendered by the organizer page SessionSelectionAiScores.
    1837. -
    1838. Caveats / not-in-source: Update is not on the re-scoring path today. The handler replaces a session's score with a delete-then-add pair inside the same step that writes the new one (SessionScoringRunner.cs:107-109), a choice its comment justifies by partial-failure behavior: N sequential paid model calls follow, so a run that dies partway through has replaced only what it actually re-scored, and the unique filtered index on SessionId keeps at most one live row either way (SessionScoringRunner.cs:96-105). Update therefore remains a valid domain operation with no current caller in Source.
    1839. +
    1840. Where it's used: created by the scoring pass (SessionScoringRunner, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/SessionScoringRunner.cs:87, over the repository resolved at :28, passing aiScoringService.ModelId and aiScoringService.PromptVersion as the provenance pair at :83), invoked from the internal-command handler ScoreEventSessionsInternalCommandHandler (ADR-114), read back by the organizer dashboard (GetSessionSelectionDashboardHandler, .../GetSessionSelectionDashboard/GetSessionSelectionDashboardHandler.cs:98,338,363), configured by SessionAiScoreConfiguration in Infrastructure, projected as SessionAiScoreDTO, and rendered by the organizer page SessionSelectionAiScores.
    1841. +
    1842. Caveats / not-in-source: Update is not on the re-scoring path today. The handler replaces a session's score with a delete-then-add pair inside the same step that writes the new one (SessionScoringRunner.cs:107-109), a choice its comment justifies by partial-failure behavior: N sequential paid model calls follow, so a run that dies partway through has replaced only what it actually re-scored, and the unique filtered index on SessionId keeps at most one live row either way (SessionScoringRunner.cs:102-111). Update therefore remains a valid domain operation with no current caller in Source.

    1843. [Rubric §16, AI-Native Application Architecture] applies: this type is part of the AI session-scoring feature (a model call behind a port, versioned prompt and model, an evaluation gate, metered spend; ADR-111).

      @@ -3508,15 +3508,15 @@

      Category

      callers never hold a bare CategoryItem. Two consequences are visible directly in this file:

      1. All child mutation routes through the parent. AddCategoryItem, UpdateCategoryItem, and - RemoveCategoryItem (Category.cs:125, :156, :186) live on Category, never on + RemoveCategoryItem (Category.cs:127, :158, :188) live on Category, never on CategoryItem, and each raises a CategoryItemChanged from - the root (for example Category.cs:144) so observers learn the aggregate changed.
      2. + the root (for example Category.cs:146) so observers learn the aggregate changed.
      3. The private list enforces encapsulation. _categoryItems is a private readonly List<CategoryItem> (Category.cs:27); the public surface is the read-only projection CategoryItems => _categoryItems.AsReadOnly() (Category.cs:31). EF still materializes the backing field, which is why the private parameterless constructor exists (Category.cs:34).

      [Rubric §8, Data Architecture] also applies: cascade soft-delete is orchestrated by the aggregate, not - by a handler. Delete() (Category.cs:102-114) cascade-soft-deletes every still-active child per BR-71 + by a handler. Delete() (Category.cs:104-116) cascade-soft-deletes every still-active child per BR-71 before raising CategoryChanged(Deleted).

    1844. Walkthrough

      @@ -3529,36 +3529,38 @@

      Category

    1845. Constructors (Category.cs:34-44): the private EF constructor sets Title = string.Empty to satisfy the non-nullable field before EF assigns columns; the private field constructor is what the factory calls.
    1846. -
    1847. Create (Category.cs:54-75): validate through CategoryInvariants.EnsureTitleIsValid, then +
    1848. Create (Category.cs:54-76): validate through one Result.Combine of + CategoryInvariants.EnsureTitleIsValid and CategoryInvariants.EnsureTypeIsValid + (Category.cs:60-62), so a missing title and an over-long Type are reported together, then resolve whether the id is database-generated via typeof(Category).IsIdValueGenerated - (Category.cs:65), then construct. The id expression is worth reading closely: + (Category.cs:66), then construct. The id expression is worth reading closely: Id = id ?? (isIdValueGenerated ? default : throw new ArgumentNullException(nameof(id))) - (Category.cs:69), so a supplied id always wins, an omitted id is legal only because this type is + (Category.cs:70), so a supplied id always wins, an omitted id is legal only because this type is id-value-generated, and any other combination fails loudly rather than silently writing a zero. - Finally AddDomainEvent(new CategoryChanged(DomainEntityState.Added, ...)) (Category.cs:72). This is + Finally AddDomainEvent(new CategoryChanged(DomainEntityState.Added, ...)) (Category.cs:73). This is the canonical validate, then construct, then emit shape used by every aggregate in this chapter.
    1849. -
    1850. Update (Category.cs:84-98): re-validates the title, writes the three scalars, raises - CategoryChanged(Updated).
    1851. -
    1852. Delete (Category.cs:102-114): one Result.Combine of +
    1853. Update (Category.cs:85-100): re-validates the title and the optional type with the same + Result.Combine (Category.cs:87-89), writes the three scalars, raises CategoryChanged(Updated).
    1854. +
    1855. Delete (Category.cs:104-116): one Result.Combine of DeleteChildren<CategoryItem, CategoryItemIdentifierType>(_categoryItems) and base.Delete() - (:106-108). DeleteChildren is the framework helper on + (:108-110). DeleteChildren is the framework helper on AuditableAggregateRootEntity<TIdentifierType> that replaced the loop each aggregate used to hand-roll; it skips already-deleted children so - re-deleting a parent is idempotent. The comment at :104-105 records the ordering rationale: children + re-deleting a parent is idempotent. The comment at :106-107 records the ordering rationale: children first, root last, so a failing child aggregates into the combined result instead of leaving a half-applied delete.
    1856. -
    1857. AddCategoryItem (Category.cs:125-147): uniqueness check first (BR-138) via +
    1858. AddCategoryItem (Category.cs:127-149): uniqueness check first (BR-138) via CategoryInvariants.EnsureCategoryItemNameIsUnique, then delegate construction to CategoryItem.Create, then add to the private list, then emit. Callers never new CategoryItem(...).
    1859. -
    1860. UpdateCategoryItem (Category.cs:156-179): resolves the child through the private - GetCategoryItemOrNotFound helper (Category.cs:205, which wraps the base +
    1861. UpdateCategoryItem (Category.cs:158-181): resolves the child through the private + GetCategoryItemOrNotFound helper (Category.cs:207, which wraps the base GetChildOrNotFound<T, TId> so a missing child becomes an Error rather than a null), re-checks uniqueness while - excluding the item being renamed (:167-168), then delegates to the child's own Update.
    1862. -
    1863. RemoveCategoryItem (Category.cs:186-197): a single call to the base - RemoveChildOrNotFound<CategoryItem, CategoryItemIdentifierType> (:188), which resolves and + excluding the item being renamed (:169-170), then delegates to the child's own Update.
    1864. +
    1865. RemoveCategoryItem (Category.cs:188-199): a single call to the base + RemoveChildOrNotFound<CategoryItem, CategoryItemIdentifierType> (:190), which resolves and soft-deletes in one step, then emits CategoryItemChanged(Deleted).
    1866. -
    1867. SetCategoryItems (Category.cs:201): an internal hook used only by the navigation populator +
    1868. SetCategoryItems (Category.cs:203): an internal hook used only by the navigation populator after a cross-source load. It calls the base SetItems(_categoryItems, ...) and raises no domain events, because it is hydration, not a domain mutation.
    1869. @@ -3586,8 +3588,8 @@

      CategoryInvariants

      • What it is: the invariant rules for Category and its - CategoryItem children: title validation, item-name validation, and case-insensitive - uniqueness checking (BR-138).
      • + CategoryItem children: title validation, the optional type's length cap, + item-name validation, and case-insensitive uniqueness checking (BR-138).
      • Depends on: CommonInvariants (the reusable lower layer it delegates to, CategoryInvariants.cs:3), Result and @@ -3605,17 +3607,21 @@

        CategoryInvariants

        constants at ConferenceCategoryDTO.cs:17, :20, and CategoryItemDTO.cs:16. Note these are public static readonly int here rather than the public const int used by the other invariant classes in this chapter.
      • -
      • EnsureTitleIsValid (:26-29) and EnsureCategoryItemNameIsValid (:31-34): each a Result.Combine +
      • EnsureTitleIsValid (:26-29) and EnsureCategoryItemNameIsValid (:38-41): each a Result.Combine of CommonInvariants.EnsureStringIsNotEmpty plus CommonInvariants.EnsureStringMaxLength, with the message built through string.Create(CultureInfo.InvariantCulture, ...) so the text does not vary by ambient culture. That call is needed here and not in the sibling classes precisely because the length is a static readonly int rather than a compile-time constant, so the interpolation is evaluated at run time.
      • -
      • EnsureCategoryItemNameIsUnique (:44-65): takes the existing item collection plus an optional +
      • EnsureTypeIsValid (:35-36, documented as the M139 column cap at :31): a single call to + CommonInvariants.EnsureOptionalStringMaxLength with code Category.Type.TooLong. Because Type is + optional, the helper treats a null or empty value as success and only fails when the value exceeds + TypeMaxLength (MMCA.Common/Source/Core/MMCA.Common.Domain/Invariants/CommonInvariants.cs:242-245).
      • +
      • EnsureCategoryItemNameIsUnique (:51-72): takes the existing item collection plus an optional excludeItemId (so renaming an item to its own name during an update does not self-conflict). It - skips IsDeleted items and compares with StringComparison.OrdinalIgnoreCase (:53-56), returning - Error.Conflict("CategoryItem.Name.Duplicate") on a duplicate (:58-64). The inline comment at - :50-52 records why the exclusion is modeled as a nullable rather than defaulted: defaulting to + skips IsDeleted items and compares with StringComparison.OrdinalIgnoreCase (:60-63), returning + Error.Conflict("CategoryItem.Name.Duplicate") on a duplicate (:65-71). The inline comment at + :57-59 records why the exclusion is modeled as a nullable rather than defaulted: defaulting to default(id) would silently exclude every unsaved sibling, since a database-generated CategoryItem id is 0 until the save.
      @@ -3625,15 +3631,18 @@

      CategoryInvariants

      as a parameter so it stays a pure function with no repository and no EF dependency, which is what lets the aggregate call it in memory.
    1870. Where it's used: called from Category's Create, Update, AddCategoryItem, and - UpdateCategoryItem, and from CategoryItem's Create and Update; the length - constants are read by the Categories EF configurations + UpdateCategoryItem (Create and Update combine EnsureTitleIsValid with EnsureTypeIsValid at + Category.cs:60-62 and :87-89), and from CategoryItem's Create and Update; the + length constants are read by the Categories EF configurations (ConferenceCategoryConfiguration at :27 and :34, - CategoryItemConfiguration at :19).
    1871. -
    1872. Caveats / not-in-source: TypeMaxLength has no rule method. Nothing in this class validates - Category.Type, and neither Category.Create nor Category.Update checks it (Category.cs:60-61, - :86-87); the constant's only consumer is the EF column at ConferenceCategoryConfiguration.cs:34, so - an over-long Type is caught at the database, not by the domain.
    1873. + CategoryItemConfiguration at :19), + and TitleMaxLength plus CategoryItemNameMaxLength by the FluentValidation rules in + ConferenceCategoryValidationRules.cs:20 and :34. +
    1874. Caveats / not-in-source: the domain now caps Category.Type through EnsureTypeIsValid, matching + the EF column at ConferenceCategoryConfiguration.cs:34, but ConferenceCategoryValidationRules has no + Type rule (only :20 and :34 read the constants), so an over-long Type passes request validation + and is rejected by the aggregate as a domain Result failure.
    1875. CategoryItem

      @@ -3802,7 +3811,7 @@

      Session

      The private constructors (Session.cs:119 and :121) plus the static Result<Session> Create factory (Session.cs:187) are what make that boundary real: there is no way to obtain a Session that skipped validation or the creation event.

      A second concept lands here too: the change trail. The class is marked IAuditedEntity (Session.cs:22), and the class doc comment (Session.cs:16-20) gives the reason in business terms: sessions are written by organizers and overwritten by the Sessionize sync, so "what changed this title, room, or time slot, and was it a person or the importer" is a question that actually gets asked, and only a change history answers it. [Rubric §13, Observability & Operability]: audit stamps say who touched the row last, the trail says what the sequence was.

      -

      A third: the framework owns the child-collection mechanics. [Rubric §1, SOLID] and [Rubric §15, Best Practices & Code Quality]. Delete, restore, and remove-by-id are not hand-rolled loops here; they call four protected static helpers on the base class, DeleteChildren, RestoreChild, RemoveChildOrNotFound, and GetChildOrNotFound (MMCA.Common/Source/Core/MMCA.Common.Domain/Entities/AuditableAggregateRootEntity.cs:273, :212, :156, :103). The split is deliberate and documented at the helper: the helper owns the mechanics, and the aggregate method owns the meaning, so the caller still decides which domain event to raise and what it carries (AuditableAggregateRootEntity.cs:129-145).

      +

      A third: the framework owns the child-collection mechanics. [Rubric §1, SOLID] and [Rubric §15, Best Practices & Code Quality]. Delete, restore, and remove-by-id are not hand-rolled loops here; they call four protected static helpers on the base class, DeleteChildren, RestoreChild, RemoveChildOrNotFound, and GetChildOrNotFound (MMCA.Common/Source/Core/MMCA.Common.Domain/Entities/AuditableAggregateRootEntity.cs:275, :212, :156, :103). The split is deliberate and documented at the helper: the helper owns the mechanics, and the aggregate method owns the meaning, so the caller still decides which domain event to raise and what it carries (AuditableAggregateRootEntity.cs:131-147).

    1876. Walkthrough

        @@ -3812,9 +3821,9 @@

        Session

      • Child collections (Session.cs:90-116): three private readonly List<T> fields exposed as IReadOnlyCollection<T> through .AsReadOnly(). SessionSpeakers (:88) and SessionCategoryItems (:110) carry [Navigation(IsCollection = true)]. SessionQuestionAnswers (:104) deliberately does not, and its remarks (:95-103) are worth reading in full: the collection grows with attendance rather than with the schedule, it was riding along on the hottest public reads (the session grid, session detail, the speaker dashboard) which never render it, and it is the one child collection here that is not public data, since its dedicated controller is authenticated and scopes rows per caller while GET /sessions?includeChildren=true is anonymous. Handlers that genuinely need the answers pass an explicit includes: list. [Rubric §12, Performance & Scalability] and [Rubric §11, Security] in one attribute that is absent.
      • Create (Session.cs:187-237): combines EnsureTitleIsValid, EnsureEndsAtIsAfterStartsAt, and EnsureOptionalTextLengthsAreValid (:205-208) so all validation failures surface together, then reads typeof(Session).IsIdValueGenerated (:212). Session carries no [IdValueGenerated] attribute, so that is false and the factory assigns id!.Value (:229); the identical line in a database-generated entity leaves default. It ends by raising SessionChanged(Added) (:234).
      • Update (Session.cs:257-299): the same three-invariant combine (:273-276), then assigns every mutable field including the two workflow booleans and RoomId (:280-294, now also recomputing Duration at :284), and raises SessionChanged(Updated) (:296).
      • -
      • Delete (Session.cs:306-320): one Result.Combine over three DeleteChildren<TChild, TChildId> calls and base.Delete() (:310-314), then SessionChanged(Deleted) only when the whole combine succeeded (:316-317). The comment above it (:308-309) records why combine rather than short-circuit: aggregating every child failure with the root's own means a failing child cannot leave earlier children and the root already flagged. The helper itself skips children that are already deleted (AuditableAggregateRootEntity.cs:283-286), which makes re-deleting a parent idempotent with respect to its children.
      • +
      • Delete (Session.cs:306-320): one Result.Combine over three DeleteChildren<TChild, TChildId> calls and base.Delete() (:310-314), then SessionChanged(Deleted) only when the whole combine succeeded (:316-317). The comment above it (:308-309) records why combine rather than short-circuit: aggregating every child failure with the root's own means a failing child cannot leave earlier children and the root already flagged. The helper itself skips children that are already deleted (AuditableAggregateRootEntity.cs:285-288), which makes re-deleting a parent idempotent with respect to its children.
      • Child mutation methods: speakers at Session.cs:338 (AddSessionSpeaker), :352 (RestoreSessionSpeaker), :371 (RemoveSessionSpeaker); category items at :397, :435, :457; question answers at :484 (AddSessionQuestionAnswer), :508 (UpdateSessionQuestionAnswer), :531 (RemoveSessionQuestionAnswer). Each delegates to the child's own Create / UpdateAnswer or to a base helper, mutates the private list, and raises the child-specific *Changed event.
      • -
      • The restore path (Session.cs:375-387 and :435-450) is the interesting one. It takes the join instance rather than an id, because a soft-deleted row is excluded by the global query filter and so must be resolved by the caller (remarks at :345-349). It hands the instance to RestoreChild<...> along with the aggregate's own error code, "Session.Speaker.NotDeleted" (:356-357) or "Session.CategoryItem.NotDeleted" (:439-443); the helper refuses a not-deleted candidate (AuditableAggregateRootEntity.cs:223-231), calls the child's Reactivate(), and re-adds it to the list only if absent (:243-246). The aggregate then raises SessionSpeakerChanged(Added) because the association re-enters the visible set (Session.cs:384). BR-135: an association that reappears in the Sessionize feed is reactivated rather than duplicated by a second row.
      • +
      • The restore path (Session.cs:375-387 and :435-450) is the interesting one. It takes the join instance rather than an id, because a soft-deleted row is excluded by the global query filter and so must be resolved by the caller (remarks at :345-349). It hands the instance to RestoreChild<...> along with the aggregate's own error code, "Session.Speaker.NotDeleted" (:356-357) or "Session.CategoryItem.NotDeleted" (:439-443); the helper refuses a not-deleted candidate (AuditableAggregateRootEntity.cs:225-233), calls the child's Reactivate(), and re-adds it to the list only if absent (:243-246). The aggregate then raises SessionSpeakerChanged(Added) because the association re-enters the visible set (Session.cs:384). BR-135: an association that reappears in the Sessionize feed is reactivated rather than duplicated by a second row.
      • Removal (Session.cs:394-405, :457-468, :531-542): each calls RemoveChildOrNotFound<TChild, TChildId> and, on success, raises the matching *Changed(Deleted) event with the removed child's id. A missing or already-deleted id yields a NotFound failure from the helper rather than a null reference.
      • Lookup helper (Session.cs:573-576): a single private GetSessionQuestionAnswerOrNotFound routing through the base GetChildOrNotFound<TChild, TChildId>, used only by UpdateSessionQuestionAnswer (:512). The other two children have no update path, so they need no lookup wrapper.
      • SetSession* methods (Session.cs:409, :472, :546): internal, used only by the navigation populator. They call the base SetItems helper (AuditableAggregateRootEntity.cs:60) to replace in-memory collections during query-side population, bypassing domain logic. They are never on the command path.
      • @@ -3899,7 +3908,7 @@

        PartnerInvariants

        keeps each single-rule guard to one expression instead of a hand-written null check. [Rubric §15, Best Practices & Code Quality].
      • Where it's used: Partner's private Validate helper, called from both Create - (Partner.cs:159) and Update (Partner.cs:194); the constants additionally feed + (Partner.cs:89) and Update (Partner.cs:124); the constants additionally feed PartnerConfiguration's HasMaxLength calls (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Partners/PartnerConfiguration.cs, 4 call sites) and PartnerValidationRules @@ -3917,7 +3926,7 @@

        SpeakerInvariants

      • What it is: the domain rule set for the Speaker aggregate and its - SpeakerQuestionAnswer child. Ten field-length constants plus three + SpeakerQuestionAnswer child. Ten field-length constants plus four EnsureXxx guards that the two entities call before they mutate anything.
      • Depends on: CommonInvariants (Level 5), Result (Level 2), and @@ -3938,7 +3947,7 @@

        SpeakerInvariants

        :25, :32, :36, :44, :48, :52, :56, :60), and so does every FluentValidation rule in SpeakerValidationRules (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Speakers/Validation/SpeakerValidationRules.cs:16, - :27, :46, :65). [Rubric §15, Best Practices & Code Quality]: one edit in the Shared DTO moves the Razor + :27, :46, :65, :84, :103). [Rubric §15, Best Practices & Code Quality]: one edit in the Shared DTO moves the Razor maxlength, the validator message, the domain guard, and the column width together, because there is exactly one definition point.
      • Walkthrough
          @@ -3959,7 +3968,18 @@

          SpeakerInvariants

          message to a field.
        • EnsureLastNameIsValid (:50-53): identical shape against LastNameMaxLength, error codes Speaker.LastName.Empty and Speaker.LastName.TooLong.
        • -
        • EnsureAnswerValueIsValid(string answerValue, string source) (:55-58): the same shape against +
        • EnsureOptionalFieldsAreValid(tagLine, profilePicture, twitterHandle, linkedInUrl, gitHubUrl, websiteUrl, source) (:68-85): one Result.Combine of nine checks. Six are + CommonInvariants.EnsureOptionalStringMaxLength + (MMCA.Common/Source/Core/MMCA.Common.Domain/Invariants/CommonInvariants.cs:242), which passes a + null or empty value and otherwise caps it at the matching constant (:77-82, codes + Speaker.TagLine.TooLong through Speaker.WebsiteUrl.TooLong). The other three are + CommonInvariants.EnsureUrlIsWellFormed (CommonInvariants.cs:293) on the LinkedIn, GitHub and + website links (:83-85), which also passes an empty value and otherwise demands an absolute + http or https URL (Speaker.LinkedInUrl.Invalid and siblings). The doc comment (:55-58) names + both reasons: the caps (M139) stop SQL truncation and the URL shape (M161) stops a link a browser + would run as script, and putting both in the domain covers every write path (API, Sessionize sync, + seeder) rather than only the HTTP one.
        • +
        • EnsureAnswerValueIsValid(string answerValue, string source) (:87-90): the same shape against AnswerValueMaxLength, but the error codes are namespaced to the child entity, SpeakerQuestionAnswer.AnswerValue.Empty and SpeakerQuestionAnswer.AnswerValue.TooLong, because that is the type a consumer sees the failure from.
        • @@ -3972,20 +3992,24 @@

          SpeakerInvariants

          cannot reference Domain, use the identical figure. Static methods returning Result keep the aggregate free of exceptions on the validation path. -
        • Where it's used: Speaker.Create (Speaker.cs:140-141) and Speaker.Update - (Speaker.cs:218-219); SpeakerQuestionAnswer.Create +
        • Where it's used: Speaker.Create (Speaker.cs:140-142) and Speaker.Update + (Speaker.cs:219-221); SpeakerQuestionAnswer.Create (SpeakerQuestionAnswer.cs:52) and UpdateAnswer (SpeakerQuestionAnswer.cs:73). The constants additionally feed SpeakerConfiguration, SpeakerQuestionAnswerConfiguration - (SpeakerQuestionAnswerConfiguration.cs:25), and SpeakerValidationRules. Covered directly by + (SpeakerQuestionAnswerConfiguration.cs:25), SpeakerValidationRules, and + SpeakerSyncStrategy, which pre-screens + Sessionize links against the same caps and drops an oversized or malformed one with a warning + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/SpeakerSyncStrategy.cs:220-235) + so the domain guard does not reject the whole speaker. Covered directly by SpeakerInvariantsTests.
        • -
        • Caveats / not-in-source: only three of the ten constants have a matching EnsureXxx method. - Email, tag line, profile picture, the three URL fields and the Twitter handle are enforced by the - application validator and the EF column width, not by a domain guard, so a caller that constructs a - Speaker through the domain factory alone (a test, or the Sessionize importer) can exceed those - lengths and only fail at SaveChangesAsync. Bio is a further step out: it has no constant here at - all (the SpeakerDTO.BioMaxLength of 4000 at SpeakerDTO.cs:37 is not mirrored), and - SpeakerConfiguration.cs:28-29 maps it with no HasMaxLength, so the column is unbounded.
        • +
        • Caveats / not-in-source: EmailMaxLength is the one profile constant with no domain guard here; + the email goes through the Email value object in + Speaker.Create instead, while the 255 cap itself is enforced by the application validator + (SpeakerValidationRules.cs:46) and the EF column width (SpeakerConfiguration.cs:44). Bio is a + further step out: it has no constant here at all (the SpeakerDTO.BioMaxLength of 4000 at + SpeakerDTO.cs:37 is not mirrored), and SpeakerConfiguration.cs:28-29 maps it with no + HasMaxLength, so the column is unbounded.

        QuestionInvariants

        @@ -4003,8 +4027,9 @@

        QuestionInvariants

        richer. [Rubric §4, Domain-Driven Design]: the closed value sets and the answer rules are expressed as domain logic, not as API or UI validation. The permitted values are held as data rather than as long switch statements: ValidQuestionEntities, ValidQuestionTypes, and ValidQuestionSources are - private static readonly string[] (QuestionInvariants.cs:31, :34, :37) checked with - StringComparer.OrdinalIgnoreCase.
      • + private static readonly string[] (QuestionInvariants.cs:31, :34, :37). Entity and type are + checked with StringComparer.Ordinal (:74, :91), source with StringComparer.OrdinalIgnoreCase + (:106).
      • Walkthrough
        • Length constants (QuestionInvariants.cs:16-28): QuestionTextMaxLength (1000) and the three 20-char discriminator limits (QuestionEntityMaxLength, QuestionTypeMaxLength, QuestionSourceMaxLength), @@ -4016,21 +4041,25 @@

          QuestionInvariants

          SessionInvariants and EventInvariants use.
        • EnsureQuestionTextIsValid (:51-63): an explicit IsNullOrWhiteSpace guard first, then max length via CommonInvariants.EnsureStringMaxLength.
        • -
        • EnsureQuestionEntityIsValid (:71-78), EnsureQuestionTypeIsValid (:86-93), and - EnsureQuestionSourceIsValid (:101-108): membership tests against the closed arrays, each returning - a specific Error.Invariant code.
        • -
        • EnsureAnswerValueMatchesQuestionType (:118-129): a switch expression on questionType +
        • EnsureQuestionEntityIsValid (:73-80), EnsureQuestionTypeIsValid (:90-97), and + EnsureQuestionSourceIsValid (:105-112): membership tests against the closed arrays, each returning + a specific Error.Invariant code. The entity and type checks are ordinal, so only the canonical + casing ("Session", "Rating") passes; the doc comments (:67-68, :84-85) give the reason (M143): + consumers match and switch on the stored value case-sensitively, so a differently cased value + accepted here would be silently unmatched downstream. The source check stays case-insensitive.
        • +
        • EnsureAnswerValueMatchesQuestionType (:122-133): a switch expression on questionType dispatching to three private validators, because what counts as a valid answer depends on the question's type:
            -
          • ValidateRatingAnswer (:131-143): int.TryParse with NumberStyles.Integer and +
          • ValidateRatingAnswer (:135-147): int.TryParse with NumberStyles.Integer and CultureInfo.InvariantCulture, requiring 1 to 5, otherwise Error.Validation. The invariant culture is deliberate: a rating must parse identically wherever the request originates.
          • -
          • ValidateTextAnswer (:145-157): length must not exceed TextAnswerMaxLength (2000).
          • -
          • ValidateEmailAnswer (:159-174): constructs a System.Net.Mail.MailAddress and treats a +
          • ValidateTextAnswer (:149-161): length must not exceed TextAnswerMaxLength (2000).
          • +
          • ValidateEmailAnswer (:163-178): constructs a System.Net.Mail.MailAddress and treats a FormatException as invalid, letting the BCL be the format authority.
          • An unrecognized type falls through to Error.Invariant("Question.QuestionType.Unknown") - (:124-128). Note the dispatch is an ordinal switch on the literal strings, so it is - case-sensitive here even though EnsureQuestionTypeIsValid accepts any casing.
          • + (:128-132). The dispatch is an ordinal switch on the literal strings, which is exactly the + case-sensitive consumer the ordinal EnsureQuestionTypeIsValid check protects: a stored type can + only ever be one of the three canonical spellings.
        @@ -4047,8 +4076,10 @@

        QuestionInvariants

        QuestionConfiguration (QuestionConfiguration.cs:19, :23, :27, :37).
      • Caveats / not-in-source: the XML doc on EnsureQuestionEntityIsValid (:66) still says the valid - values are "Session" or "Event", while the array (:31) and the failure message (:75) both include - "Speaker". The array is the operative rule; that one doc line is stale.
      • + values are "Session" or "Event", while the array (:31) and the failure message (:77) both include + "Speaker". The array is the operative rule; that one doc line is stale. Question.Update takes no + source parameter (Question.cs:108-113) and re-checks only text, entity and type (:116-118), so + EnsureQuestionSourceIsValid runs once, at Create (Question.cs:83).

      SessionAssetInvariants

      @@ -4099,8 +4130,8 @@

      SessionAssetInvariants

      CreateCore and Update free of inline conditionals, and combining the guards into Result.Combine calls (see SessionAsset) surfaces every problem in one round trip rather than one error at a time. -
    1877. Where it's used: called from SessionAsset's CreateCore (SessionAsset.cs:447-453) - and Update (SessionAsset.cs:405-408). Unit-tested directly by SessionAssetInvariantsTests +
    1878. Where it's used: called from SessionAsset's CreateCore (SessionAsset.cs:289-295) + and Update (SessionAsset.cs:247-250). Unit-tested directly by SessionAssetInvariantsTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Domain.Tests/SessionAssets/SessionAssetInvariantsTests.cs). The constants and the kind check additionally feed SessionAssetValidationRules and SessionAssetConfiguration in the Application and Infrastructure @@ -4252,51 +4283,55 @@

      Speaker

      string.Empty so they are definitely assigned before EF writes the columns; the private seven-parameter one (:82-98) can only be reached through the factory, which guarantees validation ran first.
    1879. -
    1880. Create(...) (:116-171): note the ordering. The optional email is parsed into a value object +
    1881. Create(...) (:116-172): note the ordering. The optional email is parsed into a value object first (:130-137), returning Result.Failure<Speaker>(emailResult.Errors) immediately if it - is malformed, and only then are the name guards combined (:139-141). Identity resolution is the - interesting part (:145, :161): SpeakerIdentifierType is a client-assigned Guid, so a + is malformed, and only then are the field guards combined (:139-142): the two name guards plus + SpeakerInvariants.EnsureOptionalFieldsAreValid over the tag line, picture, + Twitter handle and three links (:142), so an over-long or non-http(s) link fails the factory + rather than the database write. Identity resolution is the + interesting part (:146, :162): SpeakerIdentifierType is a client-assigned Guid, so a Sessionize-imported speaker carries the id the feed gave it, while Id = id ?? (isIdValueGenerated ? default : Guid.NewGuid()) generates one when the caller passes null. The comment above that line - (:156-160) records why the null-coalescing exists at all: the earlier id!.Value threw "Nullable + (:157-161) records why the null-coalescing exists at all: the earlier id!.Value threw "Nullable object must have a value" for organizer-created speakers and for the seeder, which both pass null. - The four social links are set through an object initializer (:162-165) rather than the - constructor. Finally AddDomainEvent(new SpeakerChanged(DomainEntityState.Added, speaker.Id, speaker.FullName)) (:168).
    1882. -
    1883. Update(...) (:195-238): the same email-then-names validation order (:208-219), then eleven - scalar writes (:223-233), then SpeakerChanged(Updated, ...) (:235). LinkedUserId is - deliberately absent from the parameter list, and the remarks (:176-182) explain the risk that + The four social links are set through an object initializer (:163-166) rather than the + constructor. Finally AddDomainEvent(new SpeakerChanged(DomainEntityState.Added, speaker.Id, speaker.FullName)) (:169).
    1884. +
    1885. Update(...) (:196-240): the same email-then-fields validation order (:209-221, the optional + field guard at :221), then eleven + scalar writes (:225-235), then SpeakerChanged(Updated, ...) (:237). LinkedUserId is + deliberately absent from the parameter list, and the remarks (:177-183) explain the risk that drove that: LinkUser and UnlinkUser carry the uniqueness check and raise the events that keep Identity's User.LinkedSpeakerId in sync, whereas a generic update raises only SpeakerChanged, so writing the link here would silently desynchronize the two sides.
    1886. -
    1887. Delete() (:251-267): captures LinkedUserId into a local before anything else (:254), - calls base.Delete() (:256), and on success clears the link inside the Conference context - (:261) and raises SpeakerChanged(Deleted, Id, FullName, previousLinkedUserId) (:263). The +
    1888. Delete() (:253-269): captures LinkedUserId into a local before anything else (:256), + calls base.Delete() (:258), and on success clears the link inside the Conference context + (:263) and raises SpeakerChanged(Deleted, Id, FullName, previousLinkedUserId) (:265). The captured value is the whole point: by the time the handler runs, the field on the entity is null, - so the event has to carry it. The doc comment (:240-249) also records a deliberate non + so the event has to carry it. The doc comment (:242-251) also records a deliberate non cascade: the child associations survive the soft-delete, because the Sessionize import reactivates them in place when the speaker returns and no cascade-restore counterpart exists, and junction reads follow the parent's visibility so the surviving children are not observable meanwhile.
    1889. -
    1890. LinkUser(UserIdentifierType userId) (:272-286) and UnlinkUser() (:290-304): mirror guards. - Linking an already-linked speaker fails with Speaker.AlreadyLinked (:276-281), unlinking an - unlinked one fails with Speaker.NotLinked (:294-299). Both raise SpeakerChanged(Updated, ...) - on success.
    1891. -
    1892. Category-item children (:314-390): AddSpeakerCategoryItem (:314) guards against a - duplicate live association with _speakerCategoryItems.Exists(sci => !sci.IsDeleted && sci.CategoryItemId == categoryItemId) (:318), failing with Speaker.CategoryItem.Duplicate; the +
    1893. LinkUser(UserIdentifierType userId) (:274-288) and UnlinkUser() (:292-306): mirror guards. + Linking an already-linked speaker fails with Speaker.AlreadyLinked (:276-283), unlinking an + unlinked one fails with Speaker.NotLinked (:294-301). Both raise SpeakerChanged(Updated, ...) + on success (:286, :304).
    1894. +
    1895. Category-item children (:316-392): AddSpeakerCategoryItem (:316) guards against a + duplicate live association with _speakerCategoryItems.Exists(sci => !sci.IsDeleted && sci.CategoryItemId == categoryItemId) (:320), failing with Speaker.CategoryItem.Duplicate; the !sci.IsDeleted half is what leaves room for the reactivation path. RestoreSpeakerCategoryItem - (:352) takes the join instance rather than an id, because a soft-deleted row is excluded by - the global query filter and so must be resolved by the caller (remarks, :345-349); it delegates + (:354) takes the join instance rather than an id, because a soft-deleted row is excluded by + the global query filter and so must be resolved by the caller (remarks, :347-351); it delegates to the framework's RestoreChild<TChild, TChildId> - (MMCA.Common/Source/Core/MMCA.Common.Domain/Entities/AuditableAggregateRootEntity.cs:212) and - then raises SpeakerCategoryItemChanged(Added, ...) (:364), because the association re-enters the - visible set. RemoveSpeakerCategoryItem (:374) goes through RemoveChildOrNotFound - (AuditableAggregateRootEntity.cs:156) and raises the Deleted variant (:382).
    1896. -
    1897. Question-answer children (:401-464): AddSpeakerQuestionAnswer (:401) has no duplicate + (MMCA.Common/Source/Core/MMCA.Common.Domain/Entities/AuditableAggregateRootEntity.cs:214) and + then raises SpeakerCategoryItemChanged(Added, ...) (:366), because the association re-enters the + visible set. RemoveSpeakerCategoryItem (:376) goes through RemoveChildOrNotFound + (AuditableAggregateRootEntity.cs:158) and raises the Deleted variant (:384).
    1898. +
    1899. Question-answer children (:403-466): AddSpeakerQuestionAnswer (:403) has no duplicate guard, unlike the category-item side; validation lives in the child factory. - UpdateSpeakerQuestionAnswer (:425) resolves through the private - GetSpeakerQuestionAnswerOrNotFound helper (:467, over GetChildOrNotFound at - AuditableAggregateRootEntity.cs:103), calls answer.UpdateAnswer and re-raises - SpeakerQuestionAnswerChanged(Updated, ...) (:438). RemoveSpeakerQuestionAnswer (:448) + UpdateSpeakerQuestionAnswer (:427) resolves through the private + GetSpeakerQuestionAnswerOrNotFound helper (:469, over GetChildOrNotFound at + AuditableAggregateRootEntity.cs:105), calls answer.UpdateAnswer and re-raises + SpeakerQuestionAnswerChanged(Updated, ...) (:440). RemoveSpeakerQuestionAnswer (:450) mirrors the category-item remove.
    1900. -
    1901. Populator hooks (:389, :463): SetSpeakerCategoryItems and SetSpeakerQuestionAnswers are +
    1902. Populator hooks (:391, :465): SetSpeakerCategoryItems and SetSpeakerQuestionAnswers are internal, not public, and delegate to the framework's SetItems (AuditableAggregateRootEntity.cs:60). Internal visibility is the compromise that lets the same-assembly populator rehydrate a cross-source load without opening bulk replacement to the @@ -4319,7 +4354,7 @@

      Speaker

      (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Users/IntegrationEventHandlers/UserRegisteredHandler.cs:105); imported and reconciled by SpeakerSyncStrategy - (.../Events/UseCases/RefreshFromSessionize/SpeakerSyncStrategy.cs:157,161,174,178); hydrated by + (.../Events/UseCases/RefreshFromSessionize/SpeakerSyncStrategy.cs:121,134,145,170,174); hydrated by SpeakerNavigationPopulator (.../Speakers/SpeakerNavigationPopulator.cs:20,27); its delete observed by SpeakerDeletedHandler, which publishes @@ -4388,7 +4423,7 @@

      SpeakerCategoryItem

      the join is a child of the speaker root, not a root of its own, because it has no lifecycle independent of the speaker.
    1903. Where it's used: created, restored and removed only through Speaker - (Speaker.cs:314,352,374); reconciled against the feed by + (Speaker.cs:316,354,376); reconciled against the feed by SpeakerSyncStrategy.SyncCategoryItems (.../RefreshFromSessionize/SpeakerSyncStrategy.cs:145-164), which is the only caller of RestoreSpeakerCategoryItem; projected by @@ -4443,7 +4478,7 @@

      SpeakerQuestionAnswer

      including the Sessionize import, which never touches a FluentValidation validator, is held to the same 4000-character rule.
    1904. Where it's used: added, updated and removed only through Speaker - (Speaker.cs:401,425,448); populated from the feed by + (Speaker.cs:403,427,450); populated from the feed by SpeakerSyncStrategy.SyncQuestionAnswers (.../RefreshFromSessionize/SpeakerSyncStrategy.cs:166-181), which updates a live answer in place when one exists for the question and adds otherwise; projected by @@ -4781,7 +4816,7 @@

      EventQuestionAnswer

    1905. Caveats / not-in-source: nothing in this file checks that AnswerValue matches the referenced question's type. EventInvariants.EnsureAnswerValueIsValid (EventInvariants.cs:140) only enforces not-empty plus 4000 characters. The BR-124 shape rule - (QuestionInvariants.EnsureAnswerValueMatchesQuestionType, QuestionInvariants.cs:118) is applied one + (QuestionInvariants.EnsureAnswerValueMatchesQuestionType, QuestionInvariants.cs:122) is applied one layer up by AddEventQuestionAnswerHandler's ValidateQuestionAsync (AddEventQuestionAnswerHandler.cs:67-70), where the handler has the question @@ -4893,7 +4928,7 @@

      Partner

      database rather than an externally supplied value. What is new for this part is the generic write-side CRUD adoption: ADC's Conference module registers Partner as a fourth AddEntityCrud aggregate alongside Category, Activity, and Sponsor - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:156), + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:152), which is the mechanism ADR-099 (Generic Write-Side Entity Commands) adds: the create, update, and delete handlers come from the framework rather than being hand-written per aggregate, and a PartnerUpdateApplier supplies the piece that is still aggregate-specific, translating the generic diff --git a/docs/onboarding/group-18-conference-application.html b/docs/onboarding/group-18-conference-application.html index 1269e3ac..1ab8a93e 100644 --- a/docs/onboarding/group-18-conference-application.html +++ b/docs/onboarding/group-18-conference-application.html @@ -189,12 +189,9 @@

      The vertical-slice anatomy of Transactional, then the handler) without knowing it exists. Commands that change cached read data implement ICacheInvalidating and publish the aggregate prefix the pipeline evicts (MMCA.ADC.Conference.Application/Sessions/UseCases/Update/UpdateSessionCommand.cs:16-20, - MMCA.ADC.Conference.Application/Events/UseCases/Update/UpdateEventCommand.cs:16-20). Five commands + MMCA.ADC.Conference.Application/Events/UseCases/Update/UpdateEventCommand.cs:16-20). Four commands must be atomic across several writes and additionally implement - ITransactional: the Sessionize refresh - (MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeCommand.cs:13, - which also carries IFeatureGated with the - SessionizeIntegration flag at :19), the two speaker link commands + ITransactional: the two speaker link commands (MMCA.ADC.Conference.Application/Speakers/UseCases/LinkUser/LinkUserToSpeakerCommand.cs:13, MMCA.ADC.Conference.Application/Speakers/UseCases/UnlinkUser/UnlinkUserFromSpeakerCommand.cs:12), and the two feedback-form batches, @@ -203,6 +200,13 @@

      The vertical-slice anatomy of whole form so a partially applied form can never be observed (MMCA.ADC.Conference.Application/Sessions/UseCases/BatchAddSessionQuestionAnswers/BatchAddSessionQuestionAnswersCommand.cs:22-28, MMCA.ADC.Conference.Application/Events/UseCases/BatchAddEventQuestionAnswers/BatchAddEventQuestionAnswersCommand.cs:22-28). + The Sessionize refresh is the deliberate exception: it carries + ICacheInvalidating and + IFeatureGated with the SessionizeIntegration flag + (MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeCommand.cs:15, + :21) but is not ITransactional, because its handler commits a throttle stamp on its own before + the external call and opens an explicit transaction only around the import (:8-12, described in the + Sessionize section below). The event batch carries its answers as BatchEventQuestionAnswerItem records (BatchAddEventQuestionAnswersCommand.cs:9-11); BatchAddEventQuestionAnswersCommandValidator adds the @@ -303,7 +307,7 @@

      The vertical-slice anatomy of

      Both the create and update session paths run the shared room checks through SessionRoomScheduling.ValidateRoomAssignmentAsync (MMCA.ADC.Conference.Application/Sessions/Validation/SessionRoomScheduling.cs:44, called at - MMCA.ADC.Conference.Application/Sessions/UseCases/Create/CreateSessionHandler.cs:111-119 and + MMCA.ADC.Conference.Application/Sessions/UseCases/Create/CreateSessionHandler.cs:112-120 and UpdateSessionHandler.cs:66-74). It enforces two rules: the requested room must belong to the session's own event (BR-130, SessionRoomScheduling.cs:59-67), and the requested slot must not double-book the room, probed with BuildOverlapPredicate (SessionRoomScheduling.cs:93), a @@ -324,7 +328,9 @@

      The vertical-slice anatomy of handler overrides HandleAsync to retry a bounded three times (CreateSessionHandler.cs:33, :60), each retry in a fresh DI scope because the ambient DbContext still tracks the failed insert (CreateSessionHandler.cs:54-58). An explicitly supplied id (a Sessionize import) is respected - as-is and gets a single attempt (CreateSessionHandler.cs:42-43). The layering rule that forbids an + as-is and gets a single attempt (CreateSessionHandler.cs:42-43). The same PrepareAsync hook also + refuses a create whose parent event is missing or soft-deleted, with or without a room, and includes + the event's rooms only when the BR-130 check needs them (CreateSessionHandler.cs:97-107). The layering rule that forbids an EF Core or SQL-client reference here does not force string matching on the exception message: the handler injects the framework port IUniqueConstraintViolationDetector @@ -526,18 +532,18 @@

      Query ser C# extension(IServiceCollection) member at DependencyInjection.cs:57-59). It explicitly binds the closed generics and the ports Scrutor cannot infer (the AI scoring pass at :67, the session-asset write authorization at :72, then each aggregate's navigation populator, query service, and, where - the module owns one, delete handler at :75-133, plus the two cross-module validation services at - :136 and :139; the cascade-deletion domain service needs no registration because it is a static + the module owns one, delete handler at :75-129, plus the two cross-module validation services at + :132 and :135; the cascade-deletion domain service needs no registration because it is a static class), - and then calls ScanModuleApplicationServices<ClassReference>() (DependencyInjection.cs:143) to + and then calls ScanModuleApplicationServices<ClassReference>() (DependencyInjection.cs:139) to discover the "many small things" (every handler, mapper, validator, applier, and event handler) by convention. AssemblyReference and ClassReference are the marker types that anchor that scan (MMCA.ADC.Conference.Application/AssemblyReference.cs:5 and :11).

      The ordering of the last block is load-bearing and the file says so. Four plain-CRUD aggregates get their create, update and delete verbs from one AddEntityCrud call each (Category, Activity, - Sponsor and Partner, DependencyInjection.cs:153-156), and Speaker gets the derived-command + Sponsor and Partner, DependencyInjection.cs:149-152), and Speaker gets the derived-command update path because BR-214's CallerIsOrganizer is state the request body must never hold - (DependencyInjection.cs:164). Those calls sit after the scan on purpose: they use TryAdd, so the + (DependencyInjection.cs:160). Those calls sit after the scan on purpose: they use TryAdd, so the module's own CreateXHandler (already registered by the scan, and the only one of the three verbs that still carries module vocabulary in a log line) keeps the create verb, while the update and delete verbs, which nothing in this assembly registers, come from the framework. The mutation itself still lives on @@ -614,6 +620,22 @@

      Event-driven rea (ADR-003), so the application code never references MassTransit. [Rubric §6, CQRS & Event-Driven] and [Rubric §7, Microservices Readiness]: the module collaborates through events and interfaces, never direct cross-module type references.

      +

      The second integration handler, UserDeletedFeedbackHandler, is the + Conference half of account erasure. When Identity publishes UserDeleted, it removes every session and + event feedback answer that account wrote, because an answer is keyed by its CreatedBy user id, may hold + free text, and lives in the Conference database, which the Identity-side erasure cannot reach (PRIVACY.md + section 5, + MMCA.ADC.Conference.Application/Users/IntegrationEventHandlers/UserDeletedFeedbackHandler.cs:11-20). + It rides the same + ScopedIntegrationEventHandlerBase<TIntegrationEvent> + (UserDeletedFeedbackHandler.cs:23-26), loads only the sessions and events that still hold an active + answer by that user, tracked, with the answer collection included (:54-58, :80-84), and removes each + answer through the aggregate's own RemoveSessionQuestionAnswer / RemoveEventQuestionAnswer method + rather than a set-based delete (:69, :95). Both aggregate types are saved together in one commit + (:45). It is idempotent the way at-least-once delivery requires: only active answers match, so a + redelivery finds nothing and returns before writing (:40-43), and an exception propagates so a failed + erasure is retried rather than acked away (:16-18). [Rubric §30, Compliance, Privacy & Data + Governance]: erasure follows the data into the database that owns it.

      Two in-process services close the loop with the Engagement module. SessionBookmarkValidationService (MMCA.ADC.Conference.Application/Sessions/SessionBookmarkValidationService.cs:12) and @@ -629,15 +651,15 @@

      Event-driven rea SessionBookmarkValidationService.cs:32-38), so the eligibility rule is the domain's, not a copy, and also answers the "which sessions belong to this event" lookup Engagement needs for event-scoped reads (SessionBookmarkValidationService.cs:42-54). The latter answers four live-layer questions (the - event's window at EventLiveValidationService.cs:25, a session's at :48, a sponsor booth's at - :104, and which session a room is hosting right now at :143, so a check-in never has to trust a + event's window at EventLiveValidationService.cs:27, a session's at :50, a sponsor booth's at + :106, and which session a room is hosting right now at :145, so a check-in never has to trust a client-supplied session id) and deliberately does not compute the window itself: it delegates to the domain's CurrentEventSelector.GetLiveWindowUtc - (EventLiveValidationService.cs:223-230) so the midnight-to-midnight rule and the spring-forward-gap + (EventLiveValidationService.cs:230-237) so the midnight-to-midnight rule and the spring-forward-gap guard stay identical to the ones the home surfaces and the now/next snapshot use. Its session variant adds the assigned speaker ids (BR-236), the plenum flag, and the event's question-moderation default - (BR-233, EventLiveValidationService.cs:90-100) after re-applying the BR-91 and BR-49 eligibility - checks (:65-73). The traffic also runs the other way: + (BR-233, EventLiveValidationService.cs:90-102) after re-applying the BR-91 and BR-49 eligibility + checks (:67-75). The traffic also runs the other way: GetSessionBookmarkCountsHandler re-verifies server-side that every requested session really belongs to the speaker before delegating the counting to Engagement's batched IBookmarkCountService, silently dropping ids that @@ -711,13 +733,13 @@

      (:16-19). UploadSessionAssetHandler is the one with a real ordering problem. It refuses early when file storage is not configured for the environment, telling the caller to add a link - instead (.../UseCases/UploadFile/UploadSessionAssetHandler.cs:54-60), then derives the content type + instead (.../UseCases/UploadFile/UploadSessionAssetHandler.cs:56-62), then derives the content type from the bytes rather than from the client's declaration, through the framework's - DocumentContentSniffer (:62-71), authorizes, checks the cap, and stores the file under + DocumentContentSniffer (:64-73), authorizes, checks the cap, and stores the file under {eventId}/{sessionId}/{assetId}/{sanitized-file-name} with a server-minted GUID, which keeps the path - unguessable even though the container is public-read (:28-33, :88-92). PDFs are stored with an + unguessable even though the container is public-read (:31-34, :90-94). PDFs are stored with an inline disposition so a phone opens the deck in its own viewer, and every other format is handed over - as a download rather than rendered by whatever the browser decides to do with it (:138-153). The + as a download rather than rendered by whatever the browser decides to do with it (:140-155). The bytes travel as ReadOnlyMemory<byte> rather than a Stream because the format gate and the storage upload both have to read them and a single-pass stream cannot serve both (.../UseCases/UploadFile/UploadSessionAssetCommand.cs:9-19); the command carries its own three-minute @@ -730,7 +752,10 @@

      DeleteSessionAssetBlobInternalCommand, a durable row on the framework's internal-command queue (ADR-114) scheduled instead of an inline delete, both when the aggregate factory refuses and from the catch around the save, which then rethrows exactly as it would - have (UploadSessionAssetHandler.cs:110-131, :164-172). + have (UploadSessionAssetHandler.cs:112-116, :126-133). The scheduling itself runs from a fresh DI + scope with no cancellation token, because the request scope's context is the one whose save just failed + and a cancelled request would otherwise cancel the cleanup with it; a failure to schedule is logged and + never replaces the exception being rethrown (:171-185). DeleteSessionAssetHandler schedules the same command after the row is committed, so the container never loses a file the database still points at (.../UseCases/Delete/DeleteSessionAssetHandler.cs:63-73), and @@ -781,10 +806,10 @@

      The Sessionize imp the client runs behind the standard resilience pipeline, an unreachable API arrives as a Polly TimeoutRejectedException or BrokenCircuitException as often as an HttpRequestException, and an HTML error page served with a success status arrives as a JsonException or NotSupportedException, so all - five share one friendly failure (:106-116), with an explicit + five share one friendly failure (:106-120, the exception set at :230-235), with an explicit ThrowIfCancellationRequested() first so caller cancellation is never reported as an upstream outage - (:109). The client's own refusal (a code it cannot safely compose into a request URI, the SSRF guard) - is surfaced rather than swallowed as an empty import (:118-123).

      + (:113). The client's own refusal (a code it cannot safely compose into a request URI, the SSRF guard) + is surfaced rather than swallowed as an empty import (:122-127).

      The throttle has a second half, and it is the interesting one. The five-minute check is a check-then-act, so two requests a moment apart both read an expired stamp and both paid for the metered Sessionize call. The refresh stamp is therefore written and committed before the call, which turns @@ -792,30 +817,37 @@

      The Sessionize imp matches no row, its concurrency conflict is caught through the framework's IConcurrencyConflictDetector port, and it returns the same BR-63 throttled error the time check would have produced a moment later, so the API surface and its status code do not change - (RefreshFromSessionizeHandler.cs:75-92, the shared error at :192-194). No distributed lock is + (RefreshFromSessionizeHandler.cs:75-96, the shared error declared once at :208-218 so the two paths + cannot drift apart). No distributed lock is needed: the concurrency token every auditable entity already carries does the work - (ADR-035). The consequence is + (ADR-035). That save is the + first of two commits, which is why the command is not ITransactional: the stamp has to be visible to a + concurrent writer at once, while the import below is the second commit, wrapped in its own explicit + transaction through ExecuteInTransactionAsync so it stays all-or-nothing (:82-84, :147-151). The + consequence is written down beside it: a failure after that point leaves the window advanced and blocks a retry for up to five minutes, which is deliberate, because rolling the stamp back would hand any caller an unbounded - retry loop against a metered third-party API at exactly the moment that API is failing (:94-100).

      + retry loop against a metered third-party API at exactly the moment that API is failing (:98-104).

      The handler then runs five ISessionizeSyncStrategy implementations in dependency order (CategorySyncStrategy, RoomSyncStrategy, QuestionSyncStrategy, SpeakerSyncStrategy, - SessionSyncStrategy, declared at :30 and invoked at :153-156), each - carrying its work via a shared SessionizeSyncContext (:144-150) and returning + SessionSyncStrategy, declared at :30 and invoked at :176-180), each + carrying its work via a shared SessionizeSyncContext (:168-174) and returning a SessionizeSyncResult with a primary and an optional secondary count (ISessionizeSyncStrategy.cs:21-28). An empty response is treated as success, not an error - (:127-141), and nothing is stamped at the end of the run because the claim above already committed - the stamp (:163-164). Each strategy bulk-loads its entity family in one call (no N+1, + (:131-145), and nothing is stamped at the end of the run because the claim above already committed + the stamp (:187-188). Each strategy bulk-loads its entity family in one call (no N+1, MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/SessionSyncStrategy.cs:21-29), - upserts via the domain's Create/Update methods (SessionSyncStrategy.cs:73-117), skips soft-deleted - rows (BR-136, SessionSyncStrategy.cs:79-85, warned once at RefreshFromSessionizeHandler.cs:158-161), + upserts via the domain's Create/Update methods (SessionSyncStrategy.cs:73-124), skips soft-deleted + rows (BR-136, SessionSyncStrategy.cs:81-87, warned once at RefreshFromSessionizeHandler.cs:182-185), and accumulates human-readable warnings through SessionizeSyncWarnings instead of aborting on one bad record, for a refused update as well as a refused create, so neither - reads as a successful sync (SessionSyncStrategy.cs:94-99, :111-117, plus the BR-86/BR-122 time - warnings at :53-71). A final - RequestIdentityInsert() (RefreshFromSessionizeHandler.cs:168) lets - SQL Server accept Sessionize's own integer IDs before one batched SaveChangesAsync (:169). + reads as a successful sync (SessionSyncStrategy.cs:96-101, :113-118, plus the BR-86/BR-122 time + warnings at :53-71). A session whose feed room the room strategy did not import is stored without a + room rather than pointed at a room the event does not own, which would fail the final save on the + foreign key and roll back the whole import (SessionSyncStrategy.cs:126-131). A final + RequestExplicitKeyInsert() (RefreshFromSessionizeHandler.cs:190-192) lets + SQL Server accept Sessionize's own integer IDs before one batched SaveChangesAsync (:193). [Rubric §2, Design Patterns] (Strategy solving a real Open/Closed problem: a new entity family is a new strategy, not an edit to the orchestrator), [Rubric §12, Performance & Scalability] (bulk loads plus a single save round-trip), and [Rubric §17, DevOps & Deployment] (the throttle, the feature gate, and @@ -905,10 +937,10 @@

      Decision support: AI (DependencyInjection.cs:67) rather than a second handler, because a handler may not inject another handler and the architecture suite enforces it (ISessionScoringRunner.cs:9-14). It loads every non-service session for the event - (MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/SessionScoringRunner.cs:30-34), - batch-loads the speakers into a lookup (:39-51), and, when the command carried a request instant, + (MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/SessionScoringRunner.cs:36-40), + batch-loads the speakers into a lookup (:45-57), and, when the command carried a request instant, drops every session whose score was written at or after it, because that score already answers the - request (:53, :141-174; a null instant scores every session, ISessionScoringRunner.cs:19-22). + request (:59, :161-195; a null instant scores every session, ISessionScoringRunner.cs:19-22). It then scores the rest session by session through IAiScoringService, a port implemented in infrastructure by SessionScoringService, which sends a @@ -917,16 +949,18 @@

      Decision support: AI (MMCA.ADC.Conference.Infrastructure/Sessions/Scoring/SessionScoringService.cs:12-19, :46-49); the port also exposes the ModelId and PromptVersion that are persisted on every score (IAiScoringService.cs:15-30). The replace strategy is the detail worth studying, because the comment records a - reversal (:96-105): an earlier version deleted every score on the event up front in one set-based + reversal (SessionScoringRunner.cs:102-111): an earlier version deleted every score on the event up front in one set-based ExecuteDeleteAsync so the dashboard would reset to zero and count up, but that paid for the progress animation with real data, since N sequential paid calls follow and the first one to fail on an expired key or a rate limit left every session it never reached with no score at all. Today the delete is - per-session and happens in the same step that writes the replacement (:106-110), so a run that dies + per-session and happens in the same step that writes the replacement (:112), so a run that dies partway through has replaced only what it re-scored and a session whose call failed keeps the score it - already had (:101-103); the unique filtered index on SessionId makes the delete-then-add pair safe, - and the number of scores replaced is logged once at the end of the pass (:122-125). Each successful score is saved immediately (:109-110) so the UI can still - show real-time progress, a per-session save failure only increments a counter and continues (:115-119), - and the pass fails as a whole only when every session failed (:129-135). The contract is + already had (:107-109); the unique filtered index on SessionId makes the delete-then-add pair safe, + and the number of scores replaced is logged once at the end of the pass (:124-127). Each replacement + runs its delete, add and save on a fresh DI scope's unit of work, so a failed save dies with its scope + instead of poisoning the next session's save (:142-159). Each successful score is saved immediately (:155-157) so the UI can still + show real-time progress, a per-session save failure only increments a counter and continues (:117-121), + and the pass fails as a whole only when every session failed (:131-137). The contract is deliberately never-throw: ScoreSessionAsync returns a SessionScoringResult with a Success flag and seven 1.0-10.0 sub-scores (overall, topic relevance, description quality, novelty, actionable takeaways, depth/insight quality, @@ -936,7 +970,7 @@

      Decision support: AI SessionScoringInput and SpeakerInfo (IAiScoringService.cs:33-51); the result is mapped onto the SessionAiScore aggregate for persistence together with - the port's model id and prompt version (SessionScoringRunner.cs:81-85). Keeping the port here and the HTTP/LLM adapter in + the port's model id and prompt version (SessionScoringRunner.cs:87-91). Keeping the port here and the HTTP/LLM adapter in infrastructure is textbook [Rubric §3, Clean Architecture] (the application layer depends on an interface, never the SDK) and [Rubric §7, Microservices Readiness] (the AI provider is swappable behind one interface).

      @@ -986,7 +1020,7 @@

      ClassReference

    1906. Concept, the scan-anchor marker. [Rubric §2, Design Patterns] assesses idiomatic registration wiring. The framework's scanning API is generic over an anchor type, so ScanModuleApplicationServices<ClassReference>() reads as "scan the assembly that contains ClassReference", that is, this Application layer. The class body is empty (AssemblyReference.cs:11); its only job is to be a compile-time-checked stand-in for the assembly.
    1907. Walkthrough: public class ClassReference { } on one line. No fields, no methods, and deliberately neither sealed nor static, because a static class cannot be used as a generic type argument.
    1908. Why it's built this way: a dedicated marker keeps the scan call site refactor-safe and avoids anchoring the scan on a real domain or handler type that might later move to another assembly.
    1909. -
    1910. Where it's used: passed as the type argument to services.ScanModuleApplicationServices<ClassReference>() in DependencyInjection (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143). The generic parameter it satisfies is TAssemblyMarker on ScanModuleApplicationServices, declared in MMCA.Common (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:28-30) with a where TAssemblyMarker : class constraint, and its body simply forwards typeof(TAssemblyMarker).Assembly to the Assembly-typed overload (:46).
    1911. +
    1912. Where it's used: passed as the type argument to services.ScanModuleApplicationServices<ClassReference>() in DependencyInjection (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139). The generic parameter it satisfies is TAssemblyMarker on ScanModuleApplicationServices, declared in MMCA.Common (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:28-30) with a where TAssemblyMarker : class constraint, and its body simply forwards typeof(TAssemblyMarker).Assembly to the Assembly-typed overload (:46).
    1913. GetPublicActivityFilterQuery

      @@ -1018,10 +1052,10 @@

      ActivityEventIdRules<T>

      • What it is: a reusable rule fragment that enforces "an activity must name the event it belongs to", applied to whichever EventIdentifierType property a caller points it at.
      • -
      • Depends on: RequiredIdRules<T, TId> (its base class, MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:142-148) and System.Linq.Expressions (BCL). The EventIdentifierType in the selector signature (ActivityValidationRules.cs:83) is the module's identifier alias, declared once as a global using in the Shared project and linked solution-wide, which is why no import for it appears in this file.
      • +
      • Depends on: RequiredIdRules<T, TId> (its base class, MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:143-149) and System.Linq.Expressions (BCL). The EventIdentifierType in the selector signature (ActivityValidationRules.cs:83) is the module's identifier alias, declared once as a global using in the Shared project and linked solution-wide, which is why no import for it appears in this file.
      • Concept: the module-local rule fragment taught on ActivityTimeRangeRules<T>, here in its inheriting form: the fragment writes no RuleFor chain of its own and instead binds a framework rule to a module-specific label and error code. [Rubric §1, SOLID]: one field contract per fragment, so changing it is a one-line edit in one place.
      • -
      • Walkthrough: one expression-bodied constructor, ActivityEventIdRules(Expression<Func<T, EventIdentifierType>> selector) (ActivityValidationRules.cs:83), whose whole body is the base call : base(selector, "an Event for the Activity", "Activity.EventId.Required") (:84). The base contributes RuleFor(selector).NotEmpty() with the message "You must specify {fieldName}" and the supplied error code (CommonValidationRules.cs:146-147), so the label reads as a full phrase with its own article: "You must specify an Event for the Activity". Because EventIdentifierType aliases int, NotEmpty() here rejects 0, the default of an unset id, rather than a null. The base's remarks (CommonValidationRules.cs:133-139) note that the same check covers a Guid key by rejecting Guid.Empty.
      • -
      • Why it's built this way: the XML doc above the class (ActivityValidationRules.cs:75-79) states the rule's reason in domain terms: activities are scheduled per event, so an unscoped activity has nowhere to appear. Encoding that as a request-level rule means the caller gets a field-addressed validation failure before any handler or aggregate is touched. Passing the error code explicitly is what makes the failure machine-addressable: the framework's WithOptionalErrorCode helper (CommonValidationRules.cs:30-32) attaches a code only when one is supplied, so a fragment that omits it inherits the base message and no code.
      • +
      • Walkthrough: one expression-bodied constructor, ActivityEventIdRules(Expression<Func<T, EventIdentifierType>> selector) (ActivityValidationRules.cs:83), whose whole body is the base call : base(selector, "an Event for the Activity", "Activity.EventId.Required") (:84). The base contributes RuleFor(selector).NotEmpty() with the message "You must specify {fieldName}" and the supplied error code (CommonValidationRules.cs:147-148), so the label reads as a full phrase with its own article: "You must specify an Event for the Activity". Because EventIdentifierType aliases int, NotEmpty() here rejects 0, the default of an unset id, rather than a null. The base's remarks (CommonValidationRules.cs:134-140) note that the same check covers a Guid key by rejecting Guid.Empty.
      • +
      • Why it's built this way: the XML doc above the class (ActivityValidationRules.cs:75-79) states the rule's reason in domain terms: activities are scheduled per event, so an unscoped activity has nowhere to appear. Encoding that as a request-level rule means the caller gets a field-addressed validation failure before any handler or aggregate is touched. Passing the error code explicitly is what makes the failure machine-addressable: the framework's WithOptionalErrorCode helper (CommonValidationRules.cs:31-33) attaches a code only when one is supplied, so a fragment that omits it inherits the base message and no code.
      • Where it's used: included by ActivityCreateRequestValidator (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Activities/UseCases/Create/ActivityCreateRequestValidator.cs:15) and by that validator only, as the comment beside the call says (:13-14). ActivityUpdateRequestValidator does not include it, because ActivityUpdateRequest carries no EventId at all: its doc comment (.../Activities/UseCases/Update/ActivityUpdateRequest.cs:6-9) records the choice, moving an activity between events is a create plus a delete, so a mistyped id cannot silently relocate a published social event.

      ActivitySortOrderRules<T>

      @@ -1030,10 +1064,10 @@

      ActivitySortOrderRules<T>

      • What it is: a reusable rule fragment enforcing that an activity's display sort order is non-negative.
      • -
      • Depends on: NonNegativeIntRules<T> (its base class, MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:122-127), System.Linq.Expressions (BCL). No domain constant.
      • +
      • Depends on: NonNegativeIntRules<T> (its base class, MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:123-128), System.Linq.Expressions (BCL). No domain constant.
      • Concept: the inheriting form of the module-local rule fragment, as on ActivityEventIdRules<T>. [Rubric §15, Best Practices & Code Quality] assesses whether a constraint lives in one place; a single fragment shared by the create and update paths, itself delegating to one framework rule, is that.
      • -
      • Walkthrough: one expression-bodied constructor, ActivitySortOrderRules(Expression<Func<T, int>> selector) (ActivityValidationRules.cs:119), body : base(selector, "Sort Order", "Activity.SortOrder.Negative") (:120). The base contributes GreaterThanOrEqualTo(0) with the message "Sort Order must be greater than or equal to 0" (CommonValidationRules.cs:125-126) and attaches the supplied code.
      • -
      • Why it's built this way: the base chosen is the non-negative one, not PositiveIntRules<T> (CommonValidationRules.cs:100), because zero is a legitimate "first in the list" position. The choice of base class is the business rule here, which is why the class doc (ActivityValidationRules.cs:112-115) says only "non-negative value" and nothing else.
      • +
      • Walkthrough: one expression-bodied constructor, ActivitySortOrderRules(Expression<Func<T, int>> selector) (ActivityValidationRules.cs:119), body : base(selector, "Sort Order", "Activity.SortOrder.Negative") (:120). The base contributes GreaterThanOrEqualTo(0) with the message "Sort Order must be greater than or equal to 0" (CommonValidationRules.cs:126-127) and attaches the supplied code.
      • +
      • Why it's built this way: the base chosen is the non-negative one, not PositiveIntRules<T> (CommonValidationRules.cs:101), because zero is a legitimate "first in the list" position. The choice of base class is the business rule here, which is why the class doc (ActivityValidationRules.cs:112-115) says only "non-negative value" and nothing else.
      • Where it's used: pulled in once, by ActivityFieldRules<T> (ActivityValidationRules.cs:137), so both the create and the update path get it without either validator naming it.

      ActivityDescriptionRules<T>

      @@ -1042,11 +1076,11 @@

      ActivityDescriptionRules<T>

      • What it is: a length-only rule fragment for the optional activity description.
      • -
      • Depends on: OptionalStringRules<T> (its base class, MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:53-58), ActivityInvariants (its DescriptionMaxLength constant), System.Linq.Expressions (BCL).
      • +
      • Depends on: OptionalStringRules<T> (its base class, MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:54-59), ActivityInvariants (its DescriptionMaxLength constant), System.Linq.Expressions (BCL).
      • Concept: the module-local rule fragment (ActivityTimeRangeRules<T>) in its inheriting form. The four optional string rules in this file and ActivityNameRules<T> write no RuleFor chain at all: they subclass a framework fragment and pass it a field label plus the domain's max-length constant. That is the module's whole contribution here, binding a generic rule to a domain invariant. [Rubric §15, Best Practices & Code Quality].
      • -
      • Walkthrough: one constructor, ActivityDescriptionRules(Expression<Func<T, string?>> selector) (ActivityValidationRules.cs:28), whose body is only a base call: : base(selector, "Activity Description", ActivityInvariants.DescriptionMaxLength) (:29). The base contributes MaximumLength(maxLength) and nothing else (CommonValidationRules.cs:56-57), so null and empty are both accepted. DescriptionMaxLength is 2000, declared on ActivityDTO (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Activities/ActivityDTO.cs:21) and re-exported as a const by the domain (.../MMCA.ADC.Conference.Domain/Activities/ActivityInvariants.cs:19).
      • +
      • Walkthrough: one constructor, ActivityDescriptionRules(Expression<Func<T, string?>> selector) (ActivityValidationRules.cs:28), whose body is only a base call: : base(selector, "Activity Description", ActivityInvariants.DescriptionMaxLength) (:29). The base contributes MaximumLength(maxLength) and nothing else (CommonValidationRules.cs:57-58), so null and empty are both accepted. DescriptionMaxLength is 2000, declared on ActivityDTO (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Activities/ActivityDTO.cs:21) and re-exported as a const by the domain (.../MMCA.ADC.Conference.Domain/Activities/ActivityInvariants.cs:19).
      • Why it's built this way: the length is declared once on the DTO, which the Blazor pages bind their input caps to, and read from there by the domain invariants, the EF configuration and this validator (ActivityInvariants.cs:7-12 states exactly that chain), so a schema change cannot leave a stale validator behind.
      • -
      • Caveats: the inherited fragments pass no error code, so their failures carry a message but no stable code (WithOptionalErrorCode is a no-op when the code is null, CommonValidationRules.cs:30-32). And unlike the name and venue fields, which have domain-side guards emitting coded errors (ActivityInvariants.cs:36, :48, :58, :68), the description has only the constant at ActivityInvariants.cs:19 and no Ensure... guard beside it, so this fragment is the enforcement point on the request path.
      • +
      • Caveats: the inherited fragments pass no error code, so their failures carry a message but no stable code (WithOptionalErrorCode is a no-op when the code is null, CommonValidationRules.cs:31-33). And unlike the name and venue fields, which have domain-side guards emitting coded errors (ActivityInvariants.cs:36, :48, :58, :68), the description has only the constant at ActivityInvariants.cs:19 and no Ensure... guard beside it, so this fragment is the enforcement point on the request path.
      • Where it's used: pulled in by ActivityFieldRules<T> (ActivityValidationRules.cs:138), bound to IActivityFieldsRequest.Description.

      ActivityNameRules<T>

      @@ -1055,9 +1089,9 @@

      ActivityNameRules<T>

      • What it is: the required-string rule fragment for an activity's display name: non-empty and within the domain's maximum length.
      • -
      • Depends on: RequiredStringRules<T> (its base class, MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:41-47), ActivityInvariants (NameMaxLength), System.Linq.Expressions (BCL).
      • +
      • Depends on: RequiredStringRules<T> (its base class, MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:42-48), ActivityInvariants (NameMaxLength), System.Linq.Expressions (BCL).
      • Concept: the inheriting form of the module-local rule fragment, as on ActivityDescriptionRules<T>. This one subclasses the required base rather than the optional one, which is the only structural difference between the two. [Rubric §1, SOLID], [Rubric §24, Forms, Validation & UX Safety].
      • -
      • Walkthrough: one constructor, ActivityNameRules(Expression<Func<T, string>> selector) (ActivityValidationRules.cs:16), body : base(selector, "Activity Name", ActivityInvariants.NameMaxLength) (:17). Note the non-nullable string selector, versus the string? of the optional siblings: the compiler enforces at the call site that only a required property can be passed here. The base contributes NotEmpty() plus MaximumLength(...) (CommonValidationRules.cs:44-46), with both messages built from the "Activity Name" label and the max-length message formatted through string.Create(CultureInfo.InvariantCulture, ...), which is what keeps the analyzers-as-errors build satisfied about culture-sensitive formatting. The ceiling is 200 (.../MMCA.ADC.Conference.Shared/Activities/ActivityDTO.cs:18, re-exported at ActivityInvariants.cs:16).
      • +
      • Walkthrough: one constructor, ActivityNameRules(Expression<Func<T, string>> selector) (ActivityValidationRules.cs:16), body : base(selector, "Activity Name", ActivityInvariants.NameMaxLength) (:17). Note the non-nullable string selector, versus the string? of the optional siblings: the compiler enforces at the call site that only a required property can be passed here. The base contributes NotEmpty() plus MaximumLength(...) (CommonValidationRules.cs:45-47), with both messages built from the "Activity Name" label and the max-length message formatted through string.Create(CultureInfo.InvariantCulture, ...), which is what keeps the analyzers-as-errors build satisfied about culture-sensitive formatting. The ceiling is 200 (.../MMCA.ADC.Conference.Shared/Activities/ActivityDTO.cs:18, re-exported at ActivityInvariants.cs:16).
      • Why it's built this way: the same value guards the request path here and the aggregate path in ActivityInvariants.EnsureNameIsValid (ActivityInvariants.cs:36-39), which emits the coded errors Activity.Name.Empty and Activity.Name.TooLong. Two layers, one constant, two distinguishable failures.
      • Where it's used: pulled in by ActivityFieldRules<T> (ActivityValidationRules.cs:135), bound to IActivityFieldsRequest.Name.
      @@ -1089,14 +1123,14 @@

      ActivityVenueUrlRules<T>

      • What it is: the rule fragment for the optional external venue website URL: a length bound plus an absolute http/https scheme check, applied only when a value is present.
      • -
      • Depends on: AbsoluteUrlRules<T> (composed, not inherited, MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:85-94), FluentValidation's AbstractValidator<T>, ActivityInvariants (VenueUrlMaxLength), System.Linq.Expressions (BCL).
      • +
      • Depends on: AbsoluteUrlRules<T> (composed, not inherited, MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:86-95), FluentValidation's AbstractValidator<T>, ActivityInvariants (VenueUrlMaxLength), System.Linq.Expressions (BCL).
      • Concept, conditional composition inside a fragment. This is the one activity fragment that is neither a plain subclass nor a hand-written chain: it derives from AbstractValidator<T> and folds a framework fragment in under a When(...) guard. [Rubric §26, Front-End Security] and [Rubric §11, Security] both assess whether untrusted input that will later be rendered as a link is constrained at the boundary rather than at the point of rendering. The class doc (ActivityValidationRules.cs:56-62) names the exposure directly: the value cannot become an executable javascript: or data: link when the public activity page renders it. Doing the scheme check here, at the request boundary, means no consumer has to remember it.
      • Walkthrough: one constructor, ActivityVenueUrlRules(Expression<Func<T, string?>> selector) (ActivityValidationRules.cs:66), with a two-statement body.
        • var accessor = selector.Compile(); (:68) turns the selector into an executable delegate once, at construction, the same technique ActivityTimeRangeRules<T> uses for its cross-field comparison.
        • -
        • When(x => !string.IsNullOrWhiteSpace(accessor(x)), () => Include(new AbsoluteUrlRules<T>(selector, "Venue URL", ActivityInvariants.VenueUrlMaxLength))); (:70-71). The When predicate is what makes an empty value pass outright, and the included framework fragment supplies both checks: MaximumLength(2000) and Must(BeAnAbsoluteHttpUrl) with the message "Venue URL must be an absolute http or https URL" (CommonValidationRules.cs:88-90). The scheme predicate delegates to CommonInvariants.EnsureUrlIsWellFormed (CommonValidationRules.cs:92-93), which passes a null or empty value and otherwise requires Uri.TryCreate(url, UriKind.Absolute, ...) to succeed with a scheme of exactly http or https (MMCA.Common/Source/Core/MMCA.Common.Domain/Invariants/CommonInvariants.cs:293-297, :436-439). The ceiling is 2000 (.../MMCA.ADC.Conference.Shared/Activities/ActivityDTO.cs:30, re-exported at ActivityInvariants.cs:28).
        • +
        • When(x => !string.IsNullOrWhiteSpace(accessor(x)), () => Include(new AbsoluteUrlRules<T>(selector, "Venue URL", ActivityInvariants.VenueUrlMaxLength))); (:70-71). The When predicate is what makes an empty value pass outright, and the included framework fragment supplies both checks: MaximumLength(2000) and Must(BeAnAbsoluteHttpUrl) with the message "Venue URL must be an absolute http or https URL" (CommonValidationRules.cs:89-91). The scheme predicate delegates to CommonInvariants.EnsureUrlIsWellFormed (CommonValidationRules.cs:93-94), which passes a null or empty value and otherwise requires Uri.TryCreate(url, UriKind.Absolute, ...) to succeed with a scheme of exactly http or https (MMCA.Common/Source/Core/MMCA.Common.Domain/Invariants/CommonInvariants.cs:293-297, :436-439). The ceiling is 2000 (.../MMCA.ADC.Conference.Shared/Activities/ActivityDTO.cs:30, re-exported at ActivityInvariants.cs:28).
      • -
      • Why it's built this way: routing the scheme check through the same invariant the domain would call means the validator and the domain answer identically instead of drifting into two definitions of "a usable URL" (CommonValidationRules.cs:77-83 states that intent). A relative path, a bare host, and a javascript: payload all fail the same rule. Keeping the value a plain string rather than a Uri is also deliberate: the framework suppresses CA1054 on the invariant with the note that the point is to validate an untrusted string before anything turns it into a Uri (CommonInvariants.cs:289-292).
      • +
      • Why it's built this way: routing the scheme check through the same invariant the domain would call means the validator and the domain answer identically instead of drifting into two definitions of "a usable URL" (CommonValidationRules.cs:78-84 states that intent). A relative path, a bare host, and a javascript: payload all fail the same rule. Keeping the value a plain string rather than a Uri is also deliberate: the framework suppresses CA1054 on the invariant with the note that the point is to validate an untrusted string before anything turns it into a Uri (CommonInvariants.cs:289-292).
      • Caveats: the module's own domain guard for this field, ActivityInvariants.EnsureVenueUrlIsValid (.../MMCA.ADC.Conference.Domain/Activities/ActivityInvariants.cs:68-69), is still length-only: it calls EnsureOptionalStringMaxLength and emits Activity.VenueUrl.TooLong, with no scheme check. So the request path is stricter than the aggregate path for this one field, and a value written by a code path that bypasses the request validator would not be scheme-checked. Neither the framework fragment nor this one passes an error code, so a scheme failure carries a message but no stable dotted code.
      • Where it's used: pulled in by ActivityFieldRules<T> (ActivityValidationRules.cs:141), bound to IActivityFieldsRequest.VenueUrl.
      @@ -1110,7 +1144,7 @@

      ActivityFieldRules<T>

    1914. Concept, the composite fragment. The individual fragments answer "how is one field validated". This type answers "what is the field set", and the constraint where T : IActivityFieldsRequest (:131) is what makes the property selectors compile without knowing the concrete request type: p => p.Name binds against the interface, and the closed generic resolves to whichever record the validator supplies. [Rubric §1, SOLID] (open for extension: a new shared field is one interface property plus one Include here, and both request validators pick it up with no edit) and [Rubric §24, Forms, Validation & UX Safety] (the create and update paths cannot drift apart, because there is exactly one list) both land squarely on this type. [Rubric §14, Testability]: because the composite is generic and stateless, a test can instantiate it over either request type, or over a purpose-built stub implementing the interface.
    1915. Walkthrough: a parameterless constructor (ActivityValidationRules.cs:133) with seven Include calls, in the order name, time range, sort order, description, venue name, venue address, venue URL (:135-141). Each one constructs the sibling fragment closed over T and hands it the matching interface property. FluentValidation's Include merges the target validator's rules into this one rather than nesting them, so the failures surface with their own property paths and a caller sees one flat result.
    1916. Why it's built this way: the class doc (ActivityValidationRules.cs:123-128) states the contract: these are the rules the create and the update request share, declared once, with each concrete validator including this and adding only the rules its own operation needs. Today there is exactly one such delta, the create-only ActivityEventIdRules<T>.
    1917. -
    1918. Where it's used: included by ActivityCreateRequestValidator (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Activities/UseCases/Create/ActivityCreateRequestValidator.cs:11, followed by the create-only event-id fragment at :15) and by ActivityUpdateRequestValidator, whose entire body is one expression-bodied Include of this fragment (.../Activities/UseCases/Update/ActivityUpdateRequestValidator.cs:10). Both validators are registered by the convention scan in DependencyInjection (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143) and reached through the framework's command-to-request validator bridge, which the AddEntityCrud call for Activity completes (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:154).
    1919. +
    1920. Where it's used: included by ActivityCreateRequestValidator (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Activities/UseCases/Create/ActivityCreateRequestValidator.cs:11, followed by the create-only event-id fragment at :15) and by ActivityUpdateRequestValidator, whose entire body is one expression-bodied Include of this fragment (.../Activities/UseCases/Update/ActivityUpdateRequestValidator.cs:10). Both validators are registered by the convention scan in DependencyInjection (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139) and reached through the framework's command-to-request validator bridge, which the AddEntityCrud call for Activity completes (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:150).
    1921. DependencyInjection

      @@ -1118,26 +1152,26 @@

      DependencyInjection

      • What it is: the Conference module's application-layer composition root: a static class exposing AddModuleConferenceApplication(ApplicationSettings), which registers every application service this module needs into the DI container.
      • -
      • Depends on: ApplicationSettings; the Conference domain aggregates and children (Event, Session, Speaker, Category, CategoryItem, Question, Activity, Sponsor, Partner, Room, EventSpeaker, EventQuestionAnswer, SessionSpeaker, SessionCategoryItem, SessionQuestionAnswer, SpeakerCategoryItem, SpeakerQuestionAnswer); the framework generics EntityQueryService<TEntity, TEntityDTO, TIdentifierType>, IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, INavigationPopulator<in TEntity>, NullNavigationPopulator<TEntity>, DeleteEntityCommand<TEntity, TIdentifierType> and DeleteEntityHandler<TEntity, TIdentifierType>; the module's own ISessionScoringRunner and ISessionAssetAccessService; the cross-module ports ISessionBookmarkValidationService and IEventLiveValidationService; the create/update request records the CRUD registrations close over (ConferenceCategoryCreateRequest, ConferenceCategoryUpdateRequest, ActivityCreateRequest, ActivityUpdateRequest, SponsorCreateRequest, SponsorUpdateRequest, PartnerCreateRequest, PartnerUpdateRequest, SpeakerUpdateRequest) plus UpdateSpeakerCommand; ClassReference; and Microsoft.Extensions.DependencyInjection with its Extensions namespace (the TryAdd* helpers).
      • +
      • Depends on: ApplicationSettings; the Conference domain aggregates and children (Event, Session, Speaker, Category, CategoryItem, Question, Activity, Sponsor, Partner, Room, EventSpeaker, EventQuestionAnswer, SessionSpeaker, SessionCategoryItem, SessionQuestionAnswer, SpeakerCategoryItem); the framework generics EntityQueryService<TEntity, TEntityDTO, TIdentifierType>, IEntityQueryService<TEntity, TEntityDTO, TIdentifierType>, INavigationPopulator<in TEntity>, NullNavigationPopulator<TEntity>, DeleteEntityCommand<TEntity, TIdentifierType> and DeleteEntityHandler<TEntity, TIdentifierType>; the module's own ISessionScoringRunner and ISessionAssetAccessService; the cross-module ports ISessionBookmarkValidationService and IEventLiveValidationService; the create/update request records the CRUD registrations close over (ConferenceCategoryCreateRequest, ConferenceCategoryUpdateRequest, ActivityCreateRequest, ActivityUpdateRequest, SponsorCreateRequest, SponsorUpdateRequest, PartnerCreateRequest, PartnerUpdateRequest, SpeakerUpdateRequest) plus UpdateSpeakerCommand; ClassReference; and Microsoft.Extensions.DependencyInjection with its Extensions namespace (the TryAdd* helpers).
      • Concept, the module composition root written as an extension(IServiceCollection) block. [Rubric §5, Vertical Slice] assesses whether each module wires its own slice rather than a central registry knowing about every type; [Rubric §2, Design Patterns] assesses idiomatic registration. The registration method lives inside a C# extension(IServiceCollection services) block (DependencyInjection.cs:57), so callers write services.AddModuleConferenceApplication(settings): the same extension(T) member style used for DI across the codebase, explained once in the primer. The class comment (DependencyInjection.cs:50-54) names the deliberate split this file embodies: explicit registrations for the generic per-entity services (which cannot be discovered by convention, because the closed generic has to be spelled out) and Scrutor assembly scanning for everything hand-written (handlers, mappers, validators), so adding a use case needs no edit here.
      • Walkthrough (in body order):
        • _ = applicationSettings (DependencyInjection.cs:61): the settings object is part of the module registration contract but this module does not branch on it today; the discard plus the inline comment "Reserved for future use (e.g., profiler decorators)" is what keeps the unused-parameter analyzer quiet without dropping the parameter from the signature.
        • -
        • Session scoring runner (:66-70): ISessionScoringRunner to SessionScoringRunner, scoped. The comment above it (:66-69) states the current shape: long-running AI scoring now runs off the request path on the framework's internal-command processor (ADR-114), the controller schedules ScoreEventSessionsInternalCommand, and the row survives a deploy or a crash; the convention scan below picks that command's handler (ScoreEventSessionsInternalCommandHandler) up like any other, and the pass it delegates to is this plain scoped service. This replaces an earlier in-memory singleton queue pair, one concrete registration plus a factory-resolved interface registration, that this file no longer contains.
        • -
        • Session-asset write authorization (:72-75): ISessionAssetAccessService to SessionAssetAccessService, scoped. The comment above it (:72-74) gives the reason it needs its own service rather than an attribute: a speaker's right to publish material against their own session is DATA (the session's speaker list), not a capability, so no declarative attribute can express it and the check has to live where the handlers can reach it.
        • -
        • Aggregate roots with custom navigation populators (:78-92): Event (:78-80), Session (:82-84), Speaker (:86-88) and Category (:90-92) each get three scoped registrations, an INavigationPopulator<T> (their bespoke populators EventNavigationPopulator, SessionNavigationPopulator, SpeakerNavigationPopulator, ConferenceCategoryNavigationPopulator), an IEntityQueryService<T, TDTO, TId>, and a delete-command handler. Six of the twelve lines deviate from the generic default: Event binds its delete to the bespoke DeleteEventHandler (:80) because deleting an event has to cascade, Session binds its query service to SessionEntityQueryService (:83) and its delete to DeleteSessionHandler (:84), Speaker binds its query service to SpeakerEntityQueryService (:87), and Category binds its query service to ConferenceCategoryEntityQueryService (:91) and its delete to DeleteConferenceCategoryHandler (:92). Everything else uses the framework generics unchanged.
        • -
        • Aggregate roots with no navigation properties at all (:94-97): Question is the only member of this bucket, and it is the only entity in the whole file registered with NullNavigationPopulator<Question> (:95), the do-nothing populator that satisfies the contract when there is nothing to eager-load, plus the generic EntityQueryService and DeleteEntityHandler.
        • -
        • Aggregate roots whose only navigation is the parent Event FK reference (:99-107): Activity (:100-101), Sponsor (:103-104) and Partner (:106-107) each get a bespoke populator (ActivityNavigationPopulator, SponsorNavigationPopulator, PartnerNavigationPopulator) that resolves just that back-reference, plus the generic query service. None of the three gets a delete handler here: Activity, Sponsor and Partner all take theirs from the AddEntityCrud calls further down. Partner is the newest member of this bucket, following the same two-registration shape as its siblings.
        • -
        • Child entities (:109-136): Room, CategoryItem, EventSpeaker, EventQuestionAnswer, SessionSpeaker, SessionCategoryItem, SessionQuestionAnswer and SpeakerCategoryItem each get their own FK populator (RoomNavigationPopulator, CategoryItemNavigationPopulator, EventSpeakerNavigationPopulator, EventQuestionAnswerNavigationPopulator, SessionSpeakerNavigationPopulator, SessionCategoryItemNavigationPopulator, SessionQuestionAnswerNavigationPopulator, SpeakerCategoryItemNavigationPopulator) plus the base EntityQueryService, except Room, which binds its query service to RoomEntityQueryService (:111); and deliberately no delete handler: children are removed through their aggregate root, never addressed directly by a delete command. SpeakerQuestionAnswer is the one asymmetry, it gets SpeakerQuestionAnswerNavigationPopulator (:136) but no query service, and the comment above it (:134-135) says so outright: it has no query service today, and registering the populator future-proofs the one that would be added alongside it.
        • -
        • Cross-module ports (:138-142): ISessionBookmarkValidationService to SessionBookmarkValidationService (:139) and IEventLiveValidationService to EventLiveValidationService (:142), the in-process interfaces the Engagement module consumes (the file's comments name Engagement and its live layer as the consumers).
        • -
        • Convention scan (:146): services.ScanModuleApplicationServices<ClassReference>() sweeps this assembly for, per the comment on :144-145, domain event handlers, DTO/request mappers, command/query handlers, and validators. The framework method behind it (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:28-30, forwarding to the Assembly overload at :46) first calls ThrowIfPipelineSealed (:49), which fails loudly if AddApplicationDecorators() already ran on this collection, because handlers registered after that point would never be wrapped by the decorator pipeline. It then runs a series of Scrutor services.Scan(...) passes: singleton lifetimes for domain and integration event handlers (:52-63), scoped for mappers and the rest.
        • -
        • Generic write side for the plain-CRUD aggregates (:148-159): one AddEntityCrud<...>() call each for Category (:156), Activity (:157), Sponsor (:158) and Partner (:159), closed over the aggregate, its DTO, its identifier type, and its create and update request records. The framework method (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Crud.cs:74-99) registers a generic create handler, a generic update handler and a generic delete handler, all with TryAdd, and then bridges the update command to its request validator with AddCommandRequestValidator (:94-96), which is the call that lets ActivityFieldRules<T> run for an update command the scan never sees. The long comment above the calls (:148-155) explains the placement: these run after the scan on purpose, so the module's own CreateXHandler (already registered, and the only one of the three that still carries module vocabulary in a log line) keeps the create verb, while the update and delete verbs, which nothing in this assembly registers, come from the framework. Partner (:159) is the newest of the four, closed over PartnerDTO, PartnerIdentifierType, PartnerCreateRequest and PartnerUpdateRequest; it slots into the same generic call the other three plain-CRUD aggregates use, adding no new code path.
        • -
        • Derived-command update for Speaker (:161-167): AddEntityUpdate<UpdateSpeakerCommand, Speaker, SpeakerDTO, SpeakerIdentifierType, SpeakerUpdateRequest>() (:167). The comment (:161-166) gives the reason Speaker cannot use the plain CRUD call: its update carries one piece of state the request body must never hold (BR-214's CallerIsOrganizer), so it rides the derived-command path, UpdateSpeakerCommand plus the command-aware SpeakerUpdateApplier the scan picked up. The framework method (.../MMCA.Common.Application/DependencyInjection.cs:442-457) TryAdds the generic command handler and registers the validator bridge, so the module's own UpdateSpeakerHandler keeps the handler slot and the bridge still completes.
        • -
        • The method then returns services (:169) for fluent chaining.
        • +
        • Session scoring runner (:63-67): ISessionScoringRunner to SessionScoringRunner, scoped. The comment above it (:63-66) states the current shape: long-running AI scoring now runs off the request path on the framework's internal-command processor (ADR-114), the controller schedules ScoreEventSessionsInternalCommand, and the row survives a deploy or a crash; the convention scan below picks that command's handler (ScoreEventSessionsInternalCommandHandler) up like any other, and the pass it delegates to is this plain scoped service.
        • +
        • Session-asset write authorization (:69-72): ISessionAssetAccessService to SessionAssetAccessService, scoped. The comment above it (:69-71) gives the reason it needs its own service rather than an attribute: a speaker's right to publish material against their own session is DATA (the session's speaker list), not a capability, so no declarative attribute can express it and the check has to live where the handlers can reach it.
        • +
        • Aggregate roots with custom navigation populators (:74-89): Event (:75-77), Session (:79-81), Speaker (:83-85) and Category (:87-89) each get three scoped registrations, an INavigationPopulator<T> (their bespoke populators EventNavigationPopulator, SessionNavigationPopulator, SpeakerNavigationPopulator, ConferenceCategoryNavigationPopulator), an IEntityQueryService<T, TDTO, TId>, and a delete-command handler. Six of the twelve lines deviate from the generic default: Event binds its delete to the bespoke DeleteEventHandler (:77) because deleting an event has to cascade, Session binds its query service to SessionEntityQueryService (:80) and its delete to DeleteSessionHandler (:81), Speaker binds its query service to SpeakerEntityQueryService (:84), and Category binds its query service to ConferenceCategoryEntityQueryService (:88) and its delete to DeleteConferenceCategoryHandler (:89). Everything else uses the framework generics unchanged.
        • +
        • Aggregate roots with no navigation properties at all (:91-94): Question is the only member of this bucket, and it is the only entity in the whole file registered with NullNavigationPopulator<Question> (:92), the do-nothing populator that satisfies the contract when there is nothing to eager-load, plus the generic EntityQueryService and DeleteEntityHandler.
        • +
        • Aggregate roots whose only navigation is the parent Event FK reference (:96-104): Activity (:97-98), Sponsor (:100-101) and Partner (:103-104) each get a bespoke populator (ActivityNavigationPopulator, SponsorNavigationPopulator, PartnerNavigationPopulator) that resolves just that back-reference, plus the generic query service. None of the three gets a delete handler here: Activity, Sponsor and Partner all take theirs from the AddEntityCrud calls further down. Partner follows the same two-registration shape as its siblings.
        • +
        • Child entities (:106-129): Room, CategoryItem, EventSpeaker, EventQuestionAnswer, SessionSpeaker, SessionCategoryItem, SessionQuestionAnswer and SpeakerCategoryItem each get their own FK populator (RoomNavigationPopulator, CategoryItemNavigationPopulator, EventSpeakerNavigationPopulator, EventQuestionAnswerNavigationPopulator, SessionSpeakerNavigationPopulator, SessionCategoryItemNavigationPopulator, SessionQuestionAnswerNavigationPopulator, SpeakerCategoryItemNavigationPopulator) plus the base EntityQueryService, except Room, which binds its query service to RoomEntityQueryService (:108); and deliberately no delete handler: children are removed through their aggregate root, never addressed directly by a delete command. The last pair is SpeakerCategoryItem (:128-129). SpeakerQuestionAnswer, a child of Speaker, gets no registration in this file at all: neither a navigation populator nor a query service.
        • +
        • Cross-module ports (:131-135): ISessionBookmarkValidationService to SessionBookmarkValidationService (:132) and IEventLiveValidationService to EventLiveValidationService (:135), the in-process interfaces the Engagement module consumes (the file's comments name Engagement and its live layer as the consumers).
        • +
        • Convention scan (:139): services.ScanModuleApplicationServices<ClassReference>() sweeps this assembly for, per the comment on :137-138, domain event handlers, DTO/request mappers, command/query handlers, and validators. The framework method behind it (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:28, forwarding to the Assembly overload at :46) first calls ThrowIfPipelineSealed (:49), which fails loudly if AddApplicationDecorators() already ran on this collection, because handlers registered after that point would never be wrapped by the decorator pipeline. It then runs a series of Scrutor services.Scan(...) passes: singleton lifetimes for domain and integration event handlers (:52-63), scoped for mappers and the rest (from :65).
        • +
        • Generic write side for the plain-CRUD aggregates (:141-152): one AddEntityCrud<...>() call each for Category (:149), Activity (:150), Sponsor (:151) and Partner (:152), closed over the aggregate, its DTO, its identifier type, and its create and update request records. The framework method (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Crud.cs:74) registers a generic create handler, a generic update handler and a generic delete handler, all with TryAdd, and then bridges the update command to its request validator with AddCommandRequestValidator (:94), which is the call that lets ActivityFieldRules<T> run for an update command the scan never sees. The long comment above the calls (:141-148) explains the placement: these run after the scan on purpose, so the module's own CreateXHandler (already registered, and the only one of the three that still carries module vocabulary in a log line) keeps the create verb, while the update and delete verbs, which nothing in this assembly registers, come from the framework. Partner (:152) is closed over PartnerDTO, PartnerIdentifierType, PartnerCreateRequest and PartnerUpdateRequest; it slots into the same generic call the other three plain-CRUD aggregates use, adding no new code path.
        • +
        • Derived-command update for Speaker (:154-160): AddEntityUpdate<UpdateSpeakerCommand, Speaker, SpeakerDTO, SpeakerIdentifierType, SpeakerUpdateRequest>() (:160). The comment (:154-159) gives the reason Speaker cannot use the plain CRUD call: its update carries one piece of state the request body must never hold (BR-214's CallerIsOrganizer), so it rides the derived-command path, UpdateSpeakerCommand plus the command-aware SpeakerUpdateApplier the scan picked up. The framework method (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Crud.cs:187) calls ThrowIfPipelineSealed (:193), TryAdds the generic command handler and registers the validator bridge (:199), so the module's own UpdateSpeakerHandler keeps the handler slot and the bridge still completes.
        • +
        • The method then returns services (:162) for fluent chaining.
      • -
      • Why it's built this way: every registration uses TryAdd* rather than Add*, so a host (or a test) can register its own implementation first and this method will not clobber it or produce a duplicate registration; the same TryAdd semantics are what let the CRUD and update helpers sit after the scan and fill only the gaps it left. Moving AI scoring onto the internal-command processor (ADR-114) collapsed what used to be a two-registrations-one-instance in-memory queue pair into one plain scoped ISessionScoringRunner, because durability now comes from the command row surviving in the outbox/queue rather than from an in-process singleton staying alive. Splitting explicit generics from convention scanning keeps a file that wires roughly twenty entities inside a compact body while still registering the module's dozens of hand-written handlers. Registering the cross-module validation services here as ordinary in-process interfaces is exactly what lets the same module code run co-located or split behind gRPC without a rewrite (ADR-007, ADR-008); the per-entity INavigationPopulator registrations are the populator pattern of ADR-002 being bound one entity at a time.
      • +
      • Why it's built this way: every registration uses TryAdd* rather than Add*, so a host (or a test) can register its own implementation first and this method will not clobber it or produce a duplicate registration; the same TryAdd semantics are what let the CRUD and update helpers sit after the scan and fill only the gaps it left. AI scoring needs only one plain scoped ISessionScoringRunner because it runs on the internal-command processor (ADR-114): durability comes from the command row surviving a deploy or a crash, not from an in-process singleton staying alive. Splitting explicit generics from convention scanning keeps a file that wires roughly twenty entities inside a compact body while still registering the module's dozens of hand-written handlers. Registering the cross-module validation services here as ordinary in-process interfaces is exactly what lets the same module code run co-located or split behind gRPC without a rewrite (ADR-007, ADR-008); the per-entity INavigationPopulator registrations are the populator pattern of ADR-002 being bound one entity at a time.
      • Where it's used: called by the Conference module's API-layer registration (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/DependencyInjection.cs:24), which is itself invoked through the module's IModule implementation during host startup; modules are discovered and registered in topological order by the ModuleLoader.
      • -
      • Caveats / not in source: applicationSettings is accepted and immediately discarded; the "profiler decorators" the comment reserves it for do not exist in this layer today. ISessionizeService is absent from this file on purpose: the Application layer owns that port, but its typed-client registration lives in Conference Infrastructure. The ordering constraint the two ThrowIfPipelineSealed guards enforce (this method must run before AddApplicationDecorators()) is checked at registration time, not expressed in the type system, so a host that calls them in the wrong order learns it from an exception rather than from the compiler. The domain-service registration for IEventCascadeDeletionDomainService this section previously described no longer appears in the current source: the diff at DependencyInjection.cs:60-65 shows the "Domain services" comment and the TryAddSingleton<IEventCascadeDeletionDomainService, EventCascadeDeletionDomainService>() line removed, and no replacement registration for that interface exists in the file's current 55-169 span.
      • +
      • Caveats / not in source: applicationSettings is accepted and immediately discarded; the "profiler decorators" the comment reserves it for do not exist in this layer today. ISessionizeService is absent from this file on purpose: the Application layer owns that port, but its typed-client registration lives in Conference Infrastructure. The ordering constraint the ThrowIfPipelineSealed guards in the framework helpers enforce (this method must run before AddApplicationDecorators()) is checked at registration time, not expressed in the type system, so a host that calls them in the wrong order learns it from an exception rather than from the compiler. No domain service is registered in this file: the DependencyInjection.cs:55-165 span holds no IEventCascadeDeletionDomainService registration.

      GetPublicActivityFilterHandler

      @@ -1154,7 +1188,7 @@

      GetPublicActivityFilterHandler

    1922. Why it's built this way: the class doc (:10-15) states the alignment: the id-list shape mirrors the sponsor, speaker and session public filters, so no navigation join is required and the criteria stays translatable on any engine (ADR-018). Centralizing the id resolution in PublicConferenceVisibility rather than repeating the IsPublished projection in every public-filter handler means the definition of "published" changes in one place.
    1923. -
    1924. Where it's used: injected into ActivitiesController as IQueryHandler<GetPublicActivityFilterQuery, Result<Specification<Activity, ActivityIdentifierType>>> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Activities/ActivitiesController.cs:45) and called from the controller's GetReadSpecificationAsync override (ActivitiesController.cs:69-79), the framework read hook, which short-circuits to null for privileged readers (:71-72, guarded by IsPrivileged at :52) so Organizers and ContentEditors keep seeing activities of events still being assembled. Because the hook is the base controller's, every read action (list, paged, lookup, by-id) is scoped from that one place, and an activity the filter excludes is a 404 rather than a redacted record (:60-65). The handler is registered by convention, not explicitly: the scan in DependencyInjection (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143) picks up every IQueryHandler<,> in the assembly.
    1925. +
    1926. Where it's used: injected into ActivitiesController as IQueryHandler<GetPublicActivityFilterQuery, Result<Specification<Activity, ActivityIdentifierType>>> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Activities/ActivitiesController.cs:45) and called from the controller's GetReadSpecificationAsync override (ActivitiesController.cs:69-79), the framework read hook, which short-circuits to null for privileged readers (:71-72, guarded by IsPrivileged at :52) so Organizers and ContentEditors keep seeing activities of events still being assembled. Because the hook is the base controller's, every read action (list, paged, lookup, by-id) is scoped from that one place, and an activity the filter excludes is a 404 rather than a redacted record (:60-65). The handler is registered by convention, not explicitly: the scan in DependencyInjection (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139) picks up every IQueryHandler<,> in the assembly.
    1927. Caveats / not in source: the controller maps a failed Result to null (ActivitiesController.cs:78), that is, to no filter at all, the fail-open shape the sibling public-filter handlers share; no path in this handler produces a failure today. The two-query shape (published event ids, then activities) is also two round trips by construction, and nothing in the handler or in PublicConferenceVisibility memoizes the id list, so each call re-reads it.
    1928. ConferenceCategoryUpdateRequest

      @@ -1164,10 +1198,10 @@

      ConferenceCategoryUpdateRequest

      • What it is: the request body of PUT /conferencecategories/{id}, three properties describing the new state of a Category: a Title, a Sort order, and an optional Type.
      • Depends on: nothing first-party and nothing external. It is a plain payload record with no domain types, no base class, and no marker interface (ConferenceCategoryUpdateRequest.cs:1-14 is the whole file).
      • -
      • Concept introduced: the update request as payload only. [Rubric §9, API & Contract Design] assesses whether the contract crossing the wire says exactly what the caller controls and nothing more. An update needs three things: which row, what the caller last saw, and what to write. Only the third is in this record. The row id arrives on the route, and the concurrency token arrives in the If-Match header (ADR-035), pulled out by SupportsIfMatchAttribute.RequiredToken(HttpContext) in the action (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Categories/ConferenceCategoriesController.cs:124). The framework's UpdateEntityCommand<TEntity, TUpdateRequest, TIdentifierType> marries the three into one dispatchable object (ConferenceCategoriesController.cs:127). [Rubric §11, Security]: a token that lives in the header rather than the body cannot be dropped by a caller who simply omits a property, because a conditional write with no precondition never reaches a handler at all (428 Precondition Required, declared at ConferenceCategoriesController.cs:118).
      • +
      • Concept introduced: the update request as payload only. [Rubric §9, API & Contract Design] assesses whether the contract crossing the wire says exactly what the caller controls and nothing more. An update needs three things: which row, what the caller last saw, and what to write. Only the third is in this record. The row id arrives on the route, and the concurrency token arrives in the If-Match header (ADR-035), pulled out by SupportsIfMatchAttribute.RequiredToken(HttpContext) in the action (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Categories/ConferenceCategoriesController.cs:127). The framework's UpdateEntityCommand<TEntity, TUpdateRequest, TIdentifierType> marries the three into one dispatchable object (ConferenceCategoriesController.cs:130). [Rubric §11, Security]: a token that lives in the header rather than the body cannot be dropped by a caller who simply omits a property, because a conditional write with no precondition never reaches a handler at all (428 Precondition Required, declared at ConferenceCategoriesController.cs:121).
      • Walkthrough: public record class ConferenceCategoryUpdateRequest (:4, deliberately not sealed, though nothing derives from it today) with three init members. Title is required string (:7), so the record cannot be constructed without one and a JSON body that omits it fails model binding before any validator runs. Sort is a plain int (:10) that defaults to 0. Type is string? (:13), documented as an optional discriminator such as "session" or "speaker".
      • -
      • Why it's built this way: the three members line up one for one with the aggregate's guarded mutation, Category.Update(string title, int sort, string? type) (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/Category.cs:84), which is what lets ConferenceCategoryUpdateApplier be four lines of delegation with no field-by-field copying. Keeping the record free of framework markers is what lets one generic command and one generic handler serve it, with the mapping explicit rather than reflective (ADR-001).
      • -
      • Where it's used: bound from the body by ConferenceCategoriesController's UpdateAsync (ConferenceCategoriesController.cs:119-135); named as the TUpdateRequest argument of AddEntityCrud (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:153), which is what closes the framework's update handler and validator bridge over it; validated by ConferenceCategoryUpdateRequestValidator; applied to the aggregate by ConferenceCategoryUpdateApplier. Its create-side sibling is ConferenceCategoryCreateRequest, which needs no separate command because a create carries its identity in the body.
      • +
      • Why it's built this way: the three members line up one for one with the aggregate's guarded mutation, Category.Update(string title, int sort, string? type) (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/Category.cs:85), which is what lets ConferenceCategoryUpdateApplier be four lines of delegation with no field-by-field copying. Keeping the record free of framework markers is what lets one generic command and one generic handler serve it, with the mapping explicit rather than reflective (ADR-001).
      • +
      • Where it's used: bound from the body by ConferenceCategoriesController's UpdateAsync (ConferenceCategoriesController.cs:122-138); named as the TUpdateRequest argument of AddEntityCrud (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:149), which is what closes the framework's update handler and validator bridge over it; validated by ConferenceCategoryUpdateRequestValidator; applied to the aggregate by ConferenceCategoryUpdateApplier. Its create-side sibling is ConferenceCategoryCreateRequest, which needs no separate command because a create carries its identity in the body.

      SessionizeCategoryItem

      @@ -1274,7 +1308,7 @@

      SessionizeSpeaker

    1929. Concept: an external-API contract DTO (SessionizeCategoryItem). [Rubric §9, API & Contract Design].
    1930. Walkthrough: twelve init properties (SessionizeModels.cs:88-122). Id is a Guid (line 89), unlike the int ids of every other Sessionize entity in this file. The optional profile fields Bio (line 98), TagLine (line 101), ProfilePicture (line 104), and FullName (line 116) are nullable strings, while FirstName (line 92) and LastName (line 95) are empty-defaulted non-nullable ones. IsTopSpeaker is a bool (line 107). Four collections, Links (line 110), Sessions (IReadOnlyList<int>, line 113), CategoryItems (IReadOnlyList<int>, line 119), and QuestionAnswers (line 122), are all IReadOnlyList<T> defaulted to [].
    1931. Why it's built this way: the Guid speaker id lines up with the Conference module's SpeakerIdentifierType = Guid alias, so the import can carry a Sessionize speaker id straight into a domain Speaker key without a conversion or a lookup table. That both FullName and the FirstName/LastName pair exist is Sessionize's redundancy, not the module's: the wire model keeps both and lets SpeakerSyncStrategy choose.
    1932. -
    1933. Where it's used: nested inside SessionizeResponse's Speakers (SessionizeModels.cs:15); imported by SpeakerSyncStrategy, which takes the record directly as a parameter (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/SpeakerSyncStrategy.cs:101-102). One consequence of that import is load-bearing elsewhere in this chapter: every synced speaker without an active link gets an EventSpeaker row (SpeakerSyncStrategy.cs:59), which is why GetPublicEventSpeakerFilterHandler cannot treat that junction as an acceptance signal.
    1934. +
    1935. Where it's used: nested inside SessionizeResponse's Speakers (SessionizeModels.cs:15); imported by SpeakerSyncStrategy, which takes the record directly as a parameter (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/SpeakerSyncStrategy.cs:103-104). One consequence of that import is load-bearing elsewhere in this chapter: every synced speaker without an active link gets an EventSpeaker row (SpeakerSyncStrategy.cs:61), which is why GetPublicEventSpeakerFilterHandler cannot treat that junction as an acceptance signal.

    1936. SessionizeResponse

      @@ -1300,7 +1334,7 @@

      ISessionizeService

    1937. Concept introduced, the outbound-port interface (dependency inversion at an external boundary). [Rubric §3, Clean Architecture] assesses whether the Application layer depends only on abstractions it owns, with concrete adapters living further out; [Rubric §7, Microservices Readiness] assesses isolating third-party calls behind a swappable boundary. Here the Application layer declares what it needs from Sessionize (this interface), while the HTTP client that actually calls the API lives in Conference Infrastructure and implements it: SessionizeService, registered as a typed client with the base address https://sessionize.com/api/v2/ via services.AddHttpClient<ISessionizeService, SessionizeService>(...) (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/DependencyInjection.cs:32-34). That inversion is what keeps the import use case exercisable without a network: the integration tier substitutes FakeSessionizeService (MMCA.ADC/Tests/Integration/MMCA.ADC.Conference.IntegrationTests/Infrastructure/FakeSessionizeService.cs:13) and feeds a canned response.
    1938. Walkthrough: a single method, Task<Result<SessionizeResponse?>> GetAllAsync(string sessionizeCode, CancellationToken cancellationToken = default) (ISessionizeService.cs:19). sessionizeCode is the per-event Sessionize code (the XML doc gives the example "kqf8l42a", line 9); the code is composed directly into the request URI, so the implementation refuses a malformed one with a failure Result instead of sending it; the nullable payload inside a success signals an empty or absent response instead of throwing, so the caller decides whether an empty import is an error; and the defaulted trailing CancellationToken follows the codebase convention that every async boundary is cancelable.
    1939. Why it's built this way: a narrow, single-purpose port is the smallest surface the import needs, which makes both the real HTTP adapter and its test double trivial to write and keeps retry and timeout policy an Infrastructure concern. Folding the malformed-code check into the Result return (rather than throwing) keeps that validation on the same error channel as every other business refusal in the handler pipeline.
    1940. -
    1941. Where it's used: constructor-injected into RefreshFromSessionizeHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeHandler.cs:22) and called there as responseResult = await sessionizeService.GetAllAsync(@event.SessionizeCode, cancellationToken) (RefreshFromSessionizeHandler.cs:101-104), assigned into a local of type Result<SessionizeResponse?> before the handler inspects it. Note that it is not registered by this layer's DependencyInjection: the Application layer owns the interface, Infrastructure owns and registers the implementation.
    1942. +
    1943. Where it's used: constructor-injected into RefreshFromSessionizeHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeHandler.cs:22) and called there as responseResult = await sessionizeService.GetAllAsync(@event.SessionizeCode, cancellationToken) (RefreshFromSessionizeHandler.cs:105-108), assigned into a local of type Result<SessionizeResponse?> before the handler inspects it. Note that it is not registered by this layer's DependencyInjection: the Application layer owns the interface, Infrastructure owns and registers the implementation.

    1944. ConferenceCategoryUpdateApplier

      @@ -1315,10 +1349,10 @@

      ConferenceCategoryUpdateApplier

    1945. The class declaration binds the three type arguments once: IEntityUpdateApplier<Category, ConferenceCategoryUpdateRequest, ConferenceCategoryIdentifierType> (:13). That closed triple is what the DI scan matches on and what closes the generic handler over this aggregate.
    1946. ApplyAsync (:16) guards both arguments with ArgumentNullException.ThrowIfNull (:18-19). These are programming-error guards, not validation: a null here means the framework handed the applier something impossible, which is an exception rather than a Result failure.
    1947. The body is one delegation, wrapped in Task.FromResult because there is nothing to await: entity.Update(request.Title, request.Sort, request.Type) (:21-24). Nothing in this file writes a property.
    1948. -
    1949. Inside the aggregate, Category.Update (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/Category.cs:84) is where the rules live: it combines CategoryInvariants.EnsureTitleIsValid and returns the failure before touching state (Category.cs:86-89), assigns the three properties (:91-93), and raises CategoryChanged with DomainEntityState.Updated (:95). A refused title therefore never mutates the tracked entity, which is exactly the contract the interface documents.
    1950. +
    1951. Inside the aggregate, Category.Update (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/Category.cs:85) is where the rules live: it combines CategoryInvariants.EnsureTitleIsValid and returns the failure before touching state (Category.cs:87-91), assigns the three properties (:91-93), and raises CategoryChanged with DomainEntityState.Updated (:95). A refused title therefore never mutates the tracked entity, which is exactly the contract the interface documents.
    -
  • Why it's built this way: the module's DependencyInjection spells out the trade explicitly (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:145-153). One AddEntityCrud<Category, ConferenceCategoryDTO, ConferenceCategoryIdentifierType, ConferenceCategoryCreateRequest, ConferenceCategoryUpdateRequest>() call registers the create, update and delete handlers closed over this aggregate's types (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Crud.cs:82-96), and the call is deliberately placed after the module scan because it uses TryAdd: the module's own CreateConferenceCategoryHandler keeps the create verb, while the update and delete verbs, which nothing in this assembly registers, come from the framework. The result is that a plain-CRUD aggregate costs one registration line plus this applier, and the domain keeps every invariant and every event. Compare the Speaker path in the same file (DependencyInjection.cs:149-155), which needs the command-aware applier because BR-214 carries state the request body must never hold.
  • +
  • Why it's built this way: the module's DependencyInjection spells out the trade explicitly (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:141-149). One AddEntityCrud<Category, ConferenceCategoryDTO, ConferenceCategoryIdentifierType, ConferenceCategoryCreateRequest, ConferenceCategoryUpdateRequest>() call registers the create, update and delete handlers closed over this aggregate's types (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Crud.cs:82-96), and the call is deliberately placed after the module scan because it uses TryAdd: the module's own CreateConferenceCategoryHandler keeps the create verb, while the update and delete verbs, which nothing in this assembly registers, come from the framework. The result is that a plain-CRUD aggregate costs one registration line plus this applier, and the domain keeps every invariant and every event. Compare the Speaker path in the same file (DependencyInjection.cs:145-151), which needs the command-aware applier because BR-214 carries state the request body must never hold.
  • Where it's used: picked up by the convention scan services.ScanModuleApplicationServices<ClassReference>() (DependencyInjection.cs:132), which registers every IEntityUpdateApplier<,,> implementation (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:92); resolved as a constructor dependency of the generic UpdateEntityHandler (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/UpdateEntityHandler.cs:50), which serves the PUT /conferencecategories/{id} action on ConferenceCategoriesController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Categories/ConferenceCategoriesController.cs:39 and :125-127).
  • Testing: ConferenceCategoryUpdateApplierTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Categories/UseCases/ConferenceCategoryUpdateApplierTests.cs:19) exercises the applier the way it actually runs, composed inside a real UpdateEntityHandler over a mocked repository (:29-32) rather than in isolation. Four tests: the happy path asserting the DTO carries all three updated fields (:48), a not-found id mapping to ErrorType.NotFound (:69), a whitespace-only title that fails the domain invariant and, crucially, asserts SaveChangesAsync was never called (:87-108), and the mirror assertion that a valid request saves exactly once (:111). [Rubric §14, Testability]: the third test is the one that protects the "a refusal leaves the entity untouched" half of the applier contract, which is the half a hand-written applier is most likely to break.
  • @@ -1333,7 +1367,7 @@

    ConferenceCategoryUpdateReques
  • Concept: none new; this is Include composition (taught in group 06) in its smallest possible form, plus one piece of wiring worth tracing once. The validator is written against the request, but the object the pipeline dispatches is an UpdateEntityCommand<TEntity, TUpdateRequest, TIdentifierType>. The bridge is registered by the same AddEntityCrud call that registers the handler: a CommandRequestValidator<TCommand, TRequest> closed over the command and this request (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Crud.cs:94-96, rationale at DependencyInjection.Crud.cs:53-61). The command is a closed generic built at registration time, so the module scan's reflection bridge cannot see it, which is why the framework registers it explicitly and why TryAdd still lets a hand-written command validator win. [Rubric §24, Forms/Validation/UX Safety] assesses whether input constraints are single-sourced and consistently applied; [Rubric §1, SOLID]: this class's only job is composition, so a title-rule change never has to be found in two files.
  • Walkthrough: sealed class ConferenceCategoryUpdateRequestValidator : AbstractValidator<ConferenceCategoryUpdateRequest> (:7), with an expression-bodied constructor (:9-10) folding in a single parameterized rule set: Include(new ConferenceCategoryTitleRules<ConferenceCategoryUpdateRequest>(p => p.Title)). The rule set itself (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Categories/Validation/ConferenceCategoryValidationRules.cs:14-21) attaches two rules with stable error codes: NotEmpty() under Category.Title.Required (:19) and MaximumLength(CategoryInvariants.TitleMaxLength) under Category.Title.MaxLength (:20). That max length is not a literal: CategoryInvariants reads it from ConferenceCategoryDTO.TitleMaxLength (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/CategoryInvariants.cs:18), which the EF configuration and the Blazor input caps also bind to, so the number is declared once and enforced at every layer.
  • Why it's built this way: Sort and Type carry no rules here, and that is a decision rather than a gap. Sort has no field-level business constraint on a category (its child items do get one, through CategoryItemSortRules<T> over the framework's NonNegativeIntRules<T>), and Type is a free-form optional discriminator. The title check is deliberately duplicated between here and the aggregate: this validator gives the caller a 400 with a stable error code before the transaction opens, while CategoryInvariants.EnsureTitleIsValid inside Category.Update is the invariant that holds even when the aggregate is mutated from a path that never saw a request. [Rubric §3, Clean Architecture] assesses whether each layer holds the checks it is entitled to hold.
  • -
  • Where it's used: registered by the module scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143) as IValidator<ConferenceCategoryUpdateRequest>, then reached through the command bridge above whenever PUT /conferencecategories/{id} dispatches (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Categories/ConferenceCategoriesController.cs:126-128). The same ConferenceCategoryTitleRules<T> object is included by the create-side ConferenceCategoryCreateRequestValidator against a different request type, which is the whole point of parameterizing the rule set by T.
  • +
  • Where it's used: registered by the module scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139) as IValidator<ConferenceCategoryUpdateRequest>, then reached through the command bridge above whenever PUT /conferencecategories/{id} dispatches (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Categories/ConferenceCategoriesController.cs:129-131). The same ConferenceCategoryTitleRules<T> object is included by the create-side ConferenceCategoryCreateRequestValidator against a different request type, which is the whole point of parameterizing the rule set by T.
  • Testing: ConferenceCategoryUpdateRequestValidatorTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Categories/Validation/ConferenceCategoryUpdateRequestValidatorTests.cs:6), two tests using FluentValidation's TestValidateAsync helper: a valid request produces no errors (:20), and a request built with with { Title = string.Empty } produces an error on Title (:31). The max-length branch is not covered here.

  • @@ -1399,7 +1433,7 @@

    BatchAddEventQuestionAnswe
  • Concept: the same boundary-versus-domain split taught on AddEventQuestionAnswerCommandValidator, extended with a collection-level rule the single-answer validator has no need for. Per-answer value shape (rule three, below) mirrors the single case exactly; what is new is rule two, a duplicate-question guard that only makes sense once more than one answer travels in the same request.
  • Walkthrough: an expression-bodied constructor with three rule blocks (BatchAddEventQuestionAnswersCommandValidator.cs:12-27). (1) RuleFor(x => x.Answers).NotEmpty().WithMessage("At least one answer is required.") (:14-16) rejects an empty batch. (2) RuleFor(x => x.Answers).Must(answers => answers is null || answers.Select(a => a.QuestionId).ToHashSet().Count == answers.Count).WithMessage("Each question may be answered only once per request.") (:18-20) rejects a batch that answers the same question twice, comparing the distinct-id count against the list count. (3) RuleForEach(x => x.Answers).ChildRules(answer => answer.RuleFor(a => a.AnswerValue).NotEmpty().WithMessage("Answer value is required.")) (:22-26) applies the single-answer non-empty check to every item in the list.
  • Why it's built this way: none of the three rules needs loaded state, so, like its single-answer sibling, the validator has no dependencies and can be constructed with new in a unit test; rules that do need state (event published, question belongs to the event, answer shape versus question type) stay in the handler, exactly where the single-answer path keeps them.
  • -
  • Where it's used: resolved by the validating decorator around BatchAddEventQuestionAnswersHandler, registered by the module's convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143); covered directly by BatchAddEventQuestionAnswersCommandValidatorTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/Validation/BatchAddEventQuestionAnswersCommandValidatorTests.cs:1).
  • +
  • Where it's used: resolved by the validating decorator around BatchAddEventQuestionAnswersHandler, registered by the module's convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); covered directly by BatchAddEventQuestionAnswersCommandValidatorTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/Validation/BatchAddEventQuestionAnswersCommandValidatorTests.cs:1).

  • BatchAddEventQuestionAnswersHandler

    @@ -1412,7 +1446,7 @@

    BatchAddEventQuestionAnswersHandler
  • Concept introduced, validate the whole form before writing anything. AddEventQuestionAnswerHandler checks event-published and question-type rules for its one answer inline, ahead of the upsert. This handler splits that same pair of checks into two passes over the whole batch. ValidateAsync (:75-109) first runs the event-eligibility check once, EventQuestionAnswerRules.EnsureEventAcceptsFeedback(parentEvent, ...) (:80-81), then loads every referenced question in one batched query, questionRepo.GetAllAsync(where: q => questionIds.Contains(q.Id), asTracking: false, ...) (:87-93), and checks each answer against its own question with EventQuestionAnswerRules.EnsureAnswerIsValid inside a loop that returns on the first failure (:96-105). Only once every answer in the batch passes does Apply (:116-159) touch the aggregate, so a batch never partially fails mid-write: the caller gets either every answer applied or none. [Rubric §12, Performance & Scalability]: the batched question load turns what would be N per-answer lookups into one IN query. [Rubric §6, CQRS and Event-Driven]: the create path still raises EventFeedbackSubmitted once per newly created answer (:150-154), identical to what the single-answer handler emits for the same submission spread across multiple calls, so Engagement's award logic cannot tell the two entry points apart.
  • Walkthrough: HandleAsync (:30-68) runs four steps. (1) Load the event tracked with EventQuestionAnswers eager-loaded, includes: [nameof(Event.EventQuestionAnswers)], asTracking: true (:36-41); a missing event returns Error.NotFound tagged with the handler and entity names (:42-47). (2) ValidateAsync (:49-53), the event-eligibility check and the batched per-answer question check described above. (3) Apply (:55-59, body :116-159): for each answer, look for an existing non-deleted answer by the caller on that question inside the already-loaded collection (:127-128, BR-107 upsert, unchanged from the single-answer handler); update it via parentEvent.UpdateEventQuestionAnswer if found (:131-140), otherwise add via parentEvent.AddEventQuestionAnswer and raise EventFeedbackSubmitted for that newly created row only (:142-155). (4) One SaveChangesAsync for the whole batch (:63), then log through the [LoggerMessage] partial LogAnswersSubmitted (:64, declared :161-162) and map every applied answer to a DTO (:66-67).
  • Why it's built this way: batching the question load and validating every answer before any write are both direct consequences of accepting a whole form as one request: an event with many questions must not cost one round trip per question, and a caller must not see nineteen answers saved and the twentieth rejected. The upsert logic itself, BR-107, is left unchanged from the single-answer path so the two entry points cannot drift on what "already answered" means.
  • -
  • Where it's used: resolved as ICommandHandler<BatchAddEventQuestionAnswersCommand, Result<IReadOnlyList<EventQuestionAnswerDTO>>> by EventQuestionAnswersController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventQuestionAnswersController.cs), exercised by EventQuestionAnswersControllerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.API.Tests/Controllers/Events/EventQuestionAnswersControllerTests.cs:5); registered by the module's convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143); covered by BatchAddEventQuestionAnswersHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/UseCases/BatchAddEventQuestionAnswers/BatchAddEventQuestionAnswersHandlerTests.cs:3 and 2 more).
  • +
  • Where it's used: resolved as ICommandHandler<BatchAddEventQuestionAnswersCommand, Result<IReadOnlyList<EventQuestionAnswerDTO>>> by EventQuestionAnswersController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventQuestionAnswersController.cs), exercised by EventQuestionAnswersControllerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.API.Tests/Controllers/Events/EventQuestionAnswersControllerTests.cs:5); registered by the module's convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); covered by BatchAddEventQuestionAnswersHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/UseCases/BatchAddEventQuestionAnswers/BatchAddEventQuestionAnswersHandlerTests.cs:3 and 2 more).
  • Caveats: currentUserService.UserId!.Value (:120) uses the null-forgiving operator, the same shape as AddEventQuestionAnswerHandler; only the controller's authorization attribute makes an unauthenticated call unreachable, the handler itself states no such guard.

  • @@ -1431,7 +1465,7 @@

    GetPublicRoomFilterHandler

  • Why it's built this way: the summary (:10-15) says the id-list shape mirrors the sponsor, speaker, and session public filters, so no navigation join is required and the criteria stays translatable on any engine (ADR-018). Room is the easy case for that rule because it carries a real EventId column (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/Room.cs:38), so the parent's visibility is expressible directly on the child's own column, with no traversal and no join table. Sourcing the id list from the shared PublicConferenceVisibility rather than restating IsPublished here is what keeps one definition of "published" behind every public read.
  • -
  • Where it's used: RoomsController injects it as an IQueryHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:97) and calls it from its GetReadSpecificationAsync override (RoomsController.cs:120-130), which short-circuits to null when IsPrivileged (:123-124, backed by currentUserService.IsPrivilegedConferenceReader() at :104), so Organizer and ContentEditor readers see every room. GetReadSpecificationAsync is the framework's read hook declared on EntityControllerBase<TEntity, TEntityDTO, TIdentifierType> (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:531) and consumed by every base read action (EntityControllerBase.cs:113, :170, :264, :378, :422), so overriding it once scopes all four of the controller's [AllowAnonymous] reads at a stroke: the unpaged list (RoomsController.cs:136-144), the paged list (:152-165), the lookup (:173-179) and the by-id read (:186-195). Those four actions are attribute-only passthroughs whose bodies delegate straight to base; each also carries [OutputCache(PolicyName = "RoomsCache")] (:138, :154, :175, :188), and the three write actions evict the conference:rooms output-cache tag afterwards (:225, :254, :272).
  • +
  • Where it's used: RoomsController injects it as an IQueryHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:97) and calls it from its GetReadSpecificationAsync override (RoomsController.cs:120-130), which short-circuits to null when IsPrivileged (:123-124, backed by currentUserService.IsPrivilegedConferenceReader() at :104), so Organizer and ContentEditor readers see every room. GetReadSpecificationAsync is the framework's read hook declared on EntityControllerBase<TEntity, TEntityDTO, TIdentifierType> (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:571) and consumed by every base read action (EntityControllerBase.cs:116, :170, :264, :378, :422), so overriding it once scopes all four of the controller's [AllowAnonymous] reads at a stroke: the unpaged list (RoomsController.cs:144-152), the paged list (:152-165), the lookup (:173-179) and the by-id read (:186-195). Those four actions are attribute-only passthroughs whose bodies delegate straight to base; each also carries [OutputCache(PolicyName = "RoomsCache")] (:138, :154, :175, :188), and the three write actions evict the conference:rooms output-cache tag afterwards (:225, :254, :272).
  • Testing: there is no per-handler unit-test class for this filter; it is covered from the controller side by RoomsControllerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.API.Tests/Controllers/Events/RoomsControllerTests.cs:26), which mocks the handler (:32) and asserts the paired behavior on all four reads: the specification is applied for an anonymous or Attendee caller and never resolved for an Organizer or ContentEditor one (:199 and :214 unpaged, :229 and :245 paged, :261 and :277 lookup, :292 and :312 by-id). SetupPublicRoomFilter stands in a filter of its own, r => r.EventId == 1 (:329-333), and VerifyFilterNeverResolved (:335-338) is what proves the privileged path never even calls the handler. [Rubric §14, Testability]: the branch worth protecting is the privileged short-circuit, because a regression there is silent (privileged callers would simply see less), and these are the tests that would catch it.
  • Caveats / not-in-source: the override maps a failed Result to null, that is, to no filter at all (RoomsController.cs:129), the same fail-open shape noted on GetPublicEventSpeakerFilterHandler. No path in this handler produces a failure today. The published-event id list is also materialized into the predicate, so the IN list grows with the number of published events; PublicConferenceVisibility describes that table as bounded at single-digit rows, but nothing in code enforces that bound.
  • @@ -1443,7 +1477,7 @@

    GetPublicEventSpeakerFilterHandler
  • What it is: the handler for GetPublicEventSpeakerFilterQuery. It asks PublicConferenceVisibility twice, once for the published event ids and once for the visible speaker ids, and returns an EventSpeaker.EventId IN (...) AND EventSpeaker.SpeakerId IN (...) specification.
  • Depends on: IUnitOfWork (:23, injected only to hand on to the resolver), PublicConferenceVisibility, InlineSpecification<TEntity, TIdentifierType> and its base Specification<TEntity, TIdentifierType>, EventSpeaker, and Result. It implements IQueryHandler<in TQuery, TResult> to Result<Specification<EventSpeaker, EventSpeakerIdentifierType>> (:24).
  • -
  • Concept introduced: the two-parent junction filter. [Rubric §11, Security]. The other public filters in this family each derive from a single parent: GetPublicRoomFilterHandler follows the event, GetPublicSpeakerFilterHandler follows the speaker's eligible sessions. This one ANDs two independent legs, and the remarks explain why the second is not redundant (:16-21): the Sessionize import writes an EventSpeaker row for every speaker in the response, which you can read in SpeakerSyncStrategy itself, where every synced speaker without an active link gets context.Event.AddEventSpeaker(null, ss.Id) (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/SpeakerSyncStrategy.cs:59). An event-only filter would therefore republish the entire imported roster through the association endpoint, which is exactly what the public speaker list hides.
  • +
  • Concept introduced: the two-parent junction filter. [Rubric §11, Security]. The other public filters in this family each derive from a single parent: GetPublicRoomFilterHandler follows the event, GetPublicSpeakerFilterHandler follows the speaker's eligible sessions. This one ANDs two independent legs, and the remarks explain why the second is not redundant (:16-21): the Sessionize import writes an EventSpeaker row for every speaker in the response, which you can read in SpeakerSyncStrategy itself, where every synced speaker without an active link gets context.Event.AddEventSpeaker(null, ss.Id) (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/SpeakerSyncStrategy.cs:61). An event-only filter would therefore republish the entire imported roster through the association endpoint, which is exactly what the public speaker list hides.
  • Concept: the duplicate scalar read, taken deliberately. [Rubric §12, Performance & Scalability]. The inline comment (:35-37) records a cost decision rather than an oversight. The junction read carries no event context, so the speaker rule spans every published event; both resolver calls read the Event table, described there as bounded at single-digit rows; and the duplicate scalar read was judged cheaper than threading the already-resolved ids through the shared resolver's signature. The trade-off is in the open: one extra projection query per request in exchange for keeping PublicConferenceVisibility's API narrow.
  • Walkthrough:
    1. Resolve the published event ids: PublicConferenceVisibility.GetPublishedEventIdsAsync(unitOfWork, cancellationToken) (:31-33). Inside the resolver that is one scalar projection of Event.Id filtered by IsPublished, read untracked (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Common/PublicConferenceVisibility.cs:42-44), then materialized once so the caller embeds a stable collection EF can translate to IN (PublicConferenceVisibility.cs:46-47).
    2. @@ -1467,7 +1501,7 @@

      SessionizeSyncResult

    3. Concept: none new. It is a deliberately anaemic result record co-located with the contract that returns it, ISessionizeSyncStrategy (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/ISessionizeSyncStrategy.cs:7), rather than living in its own file. [Rubric §9, API and Contract Design] assesses whether a contract can grow without breaking its implementors: returning a record instead of a bare int means a future step can report a third number by adding one init property, and the five existing strategies keep compiling untouched.
    4. Walkthrough: PrimarySynced (ISessionizeSyncStrategy.cs:24) is the headline count, for example speakers synced. SecondarySynced (ISessionizeSyncStrategy.cs:27) is the optional child count, for example the category items synced alongside their categories. Neither is required, so a strategy that has nothing secondary to report constructs new SessionizeSyncResult { PrimarySynced = n } and leaves the other at its default of zero, which is what four of the five strategies do (for example RoomSyncStrategy.cs:79).
    5. Why it's built this way: the counts are what the organizer sees after an import, so they must mean "the domain accepted this row", not "the feed listed this row". Every strategy increments only after the aggregate call succeeded, which is why the record is filled at the end of the loop rather than from the feed's own collection sizes.
    6. -
    7. Where it's used: returned by all five strategies; collected into a List<SessionizeSyncResult> by RefreshFromSessionizeHandler (RefreshFromSessionizeHandler.cs:152-156) and projected into RefreshFromSessionizeResultDTO (RefreshFromSessionizeHandler.cs:173-183).
    8. +
    9. Where it's used: returned by all five strategies; collected into a List<SessionizeSyncResult> by RefreshFromSessionizeHandler (RefreshFromSessionizeHandler.cs:176-180) and projected into RefreshFromSessionizeResultDTO (RefreshFromSessionizeHandler.cs:195-205).

    10. SessionizeSyncWarnings

      @@ -1480,20 +1514,20 @@

      SessionizeSyncWarnings

    11. Concept introduced, the first-error idiom. A failed Result carries a collection of Error values, but a warning line has room for one reason. FirstErrorMessage (SessionizeSyncWarnings.cs:17-18) uses a C# list pattern, result.Errors is [var first, ..], to bind the head of the collection when one exists and fall back to the literal "Unknown error" when the failure carries none. [Rubric §15, Best Practices and Code Quality] assesses whether recurring micro-logic is expressed once: five strategies needed the same sentence, so the idiom lives in one internal static method instead of five near-copies.
    12. Walkthrough: one member. internal static string FirstErrorMessage(Result result) (SessionizeSyncWarnings.cs:17), expression-bodied, reading the head of the collection through a pattern rather than materializing a LINQ query.
    13. Why it's built this way: internal and static because this is an implementation detail of a single use case, not a service. There is nothing to inject and nothing to mock, so it is a static call rather than a dependency.
    14. -
    15. Where it's used: CategorySyncStrategy (CategorySyncStrategy.cs:104), RoomSyncStrategy (RoomSyncStrategy.cs:72), SessionSyncStrategy (SessionSyncStrategy.cs:115) and SpeakerSyncStrategy (SpeakerSyncStrategy.cs:131).
    16. -
    17. Caveats: QuestionSyncStrategy does not use it. Its create-failure warning joins every error message with "; " instead (QuestionSyncStrategy.cs:80), so the question path reports all reasons where the other four report the first one.
    18. +
    19. Where it's used: CategorySyncStrategy (CategorySyncStrategy.cs:112), RoomSyncStrategy (RoomSyncStrategy.cs:72), SessionSyncStrategy (SessionSyncStrategy.cs:117) and SpeakerSyncStrategy (SpeakerSyncStrategy.cs:139).
    20. +
    21. Caveats: QuestionSyncStrategy does not use it. Its create-failure warning joins every error message with "; " instead (QuestionSyncStrategy.cs:85), so the question path reports all reasons where the other four report the first one.

    22. RefreshFromSessionizeCommand

      -

      MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeCommand.cs:13 · Level 8 · record (sealed)

      +

      MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeCommand.cs:15 · Level 8 · record (sealed)

      • What it is: the command that asks for one event's data to be re-pulled from Sessionize (UC-6). It is a single-parameter record carrying the event id.
      • -
      • Depends on: EventIdentifierType (the module's identifier alias, see the primer), Event (used only to build the cache prefix from its full type name), ConferenceFeatures, and three pipeline markers from MMCA.Common: ICacheInvalidating, ITransactional and IFeatureGated.
      • -
      • Concept: the marker-driven decorator pipeline is taught in group 5. What is worth studying here is that one small record opts into three cross-cutting behaviors at once by implementing three interfaces (RefreshFromSessionizeCommand.cs:13). [Rubric §12, Performance & Scalability] assesses whether transactions, caching and feature gating are applied declaratively rather than hand-coded per handler: the handler below contains no transaction call, no cache eviction and no feature-flag check, because all three are decided by the markers on this type. [Rubric §29, Resilience and Business Continuity] assesses whether a risky dependency can be switched off without a deploy: IFeatureGated makes the whole Sessionize integration a runtime toggle.
      • -
      • Walkthrough: the positional parameter EventId (RefreshFromSessionizeCommand.cs:13). CachePrefix returns $"{typeof(Event).FullName}:" (RefreshFromSessionizeCommand.cs:16), so a successful refresh evicts the whole Event cache region rather than a single key: the import touches events, rooms, sessions, speakers, categories and questions, so a narrower eviction would leave stale reads behind. FeatureName returns ConferenceFeatures.SessionizeIntegration (RefreshFromSessionizeCommand.cs:19), whose value is the string "Conference.SessionizeIntegration" (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/ConferenceFeatures.cs:23). ITransactional is what makes the five entity families commit or roll back together.
      • -
      • Why it's built this way: all five sync steps write through one IUnitOfWork and one save, so a half-applied import (rooms in, sessions out) is not reachable. Sessions reference rooms and speakers, so a partial commit would leave dangling references; the transactional marker is a correctness requirement here, not a convenience.
      • +
      • Depends on: EventIdentifierType (the module's identifier alias, see the primer), Event (used only to build the cache prefix from its full type name), ConferenceFeatures, and two pipeline markers from MMCA.Common: ICacheInvalidating and IFeatureGated.
      • +
      • Concept: the marker-driven decorator pipeline is taught in group 5. What is worth studying here is a marker deliberately left off. The record implements ICacheInvalidating and IFeatureGated (RefreshFromSessionizeCommand.cs:15) but not ITransactional, even though the import must be atomic. The XML comment records why: the handler commits the BR-63 throttle stamp on its own before the Sessionize call, then wraps only the entity syncs and their save in an explicit transaction, so the import stays all-or-nothing while the stamp survives a failed call. The transactional decorator would have put both commits inside one ambient transaction and rolled the stamp back with any failure. [Rubric §12, Performance & Scalability] assesses whether cross-cutting behaviors are applied declaratively and chosen per use case rather than by habit: cache eviction and feature gating stay declarative here, and the one behavior that does not fit the declarative shape moves into the handler on purpose. [Rubric §29, Resilience and Business Continuity] assesses whether a risky dependency can be switched off without a deploy: IFeatureGated makes the whole Sessionize integration a runtime toggle.
      • +
      • Walkthrough: the positional parameter EventId (RefreshFromSessionizeCommand.cs:15). CachePrefix returns $"{typeof(Event).FullName}:" (RefreshFromSessionizeCommand.cs:18), so a successful refresh evicts the whole Event cache region rather than a single key: the import touches events, rooms, sessions, speakers, categories and questions, so a narrower eviction would leave stale reads behind. FeatureName returns ConferenceFeatures.SessionizeIntegration (RefreshFromSessionizeCommand.cs:21), whose value is the string "Conference.SessionizeIntegration" (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/ConferenceFeatures.cs:23). Atomicity of the five entity families comes from the handler's explicit ExecuteInTransactionAsync call, see RefreshFromSessionizeHandler.
      • +
      • Why it's built this way: the use case needs two commits with different lifetimes. The throttle stamp must be visible to a concurrent writer at once and must outlive a failed import, while the import itself must commit or roll back as one unit through one IUnitOfWork, because sessions reference rooms and speakers and a partial commit would leave dangling references. A marker expresses one transaction around the whole command, so the command declares none and the handler owns both boundaries (ADR-107, transaction execution contract).
      • Where it's used: constructed by EventLifecycleController.RefreshAsync (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventLifecycleController.cs:117-123) and passed to the injected ICommandHandler<in TCommand, TResult> (EventLifecycleController.cs:35).

      @@ -1506,8 +1540,8 @@

      SessionizeSyncContext

    23. Depends on: SessionizeResponse, Event, IUnitOfWork, and List<string> (BCL).
    24. Concept introduced, the mutable run context behind an immutable-looking record. Four members are required ... { get; init; } (SessionizeSyncContext.cs:13-16), so the identity of the run (which feed, which event, which unit of work, which warnings list) cannot be swapped by a step. The accumulators are still mutable, in two different ways: Warnings is init-only yet holds a List<string> whose contents every step appends to, and SkippedSoftDeleted is a plain { get; set; } counter (SessionizeSyncContext.cs:19) each step increments when it meets a soft-deleted row the feed still lists (BR-136). [Rubric §1, SOLID] assesses interface and parameter-shape discipline: bundling the run state into one type is what lets ISessionizeSyncStrategy keep a two-parameter signature that never changes when one step needs an extra input.
    25. Walkthrough: Response (:13) is the deserialized feed. Event (:14) is the tracked aggregate the handler loaded with its Rooms and EventSpeakers navigations, which is why RoomSyncStrategy and SpeakerSyncStrategy can consult those collections without a query. UnitOfWork (:15) is the shared unit of work: every strategy resolves its repositories from it, which is what keeps all five steps inside one transaction and one change tracker. Warnings (:16) is the running list that ends up on the response DTO. SkippedSoftDeleted (:19) is the running count of soft-deleted rows skipped.
    26. -
    27. Why it's built this way: shared mutable state is normally a smell. It is safe here for one reason visible in the orchestrator: the strategies run strictly sequentially in a foreach (RefreshFromSessionizeHandler.cs:153-156), never concurrently. That sequencing is also a hard dependency requirement (categories before speakers and sessions, rooms before sessions), so the context's design and the execution order reinforce each other.
    28. -
    29. Where it's used: created once per command (RefreshFromSessionizeHandler.cs:144-150) and passed to each strategy's SyncAsync.
    30. +
    31. Why it's built this way: shared mutable state is normally a smell. It is safe here for one reason visible in the orchestrator: the strategies run strictly sequentially in a foreach (RefreshFromSessionizeHandler.cs:177-180), never concurrently. That sequencing is also a hard dependency requirement (categories before speakers and sessions, rooms before sessions), so the context's design and the execution order reinforce each other.
    32. +
    33. Where it's used: created once per command (RefreshFromSessionizeHandler.cs:168-174) and passed to each strategy's SyncAsync.
    34. Caveats: nothing in the type enforces the sequential assumption. List<string> and the int counter are not thread-safe, so a change that ran the independent steps in parallel would need a concurrent collection and an interlocked counter.

    35. @@ -1527,7 +1561,7 @@

      ISessionizeSyncStrategy


      CategorySyncStrategy

      -

      MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/CategorySyncStrategy.cs:12 · Level 10 · class (internal sealed)

      +

      MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/CategorySyncStrategy.cs:13 · Level 10 · class (internal sealed)

      • What it is: the first step of the import. It upserts Category rows and their CategoryItem children from the feed, and it runs first because speakers and sessions reference category items.

        @@ -1536,18 +1570,18 @@

        CategorySyncStrategy

      • Concept introduced, the four-phase upsert shape the other four strategies reuse. Read it once here and the remaining strategies become variations on it.

          -
        1. Bulk pre-load. The strategy resolves the repository once (CategorySyncStrategy.cs:16), projects the feed's ids (:21) and issues a single GetByIdsAsync with includes: [nameof(Category.CategoryItems)], asTracking: true and ignoreQueryFilters: true (:22-27), then indexes the result by id (:28). One query replaces N GetByIdAsync calls. [Rubric §12, Performance and Scalability] assesses whether repeated data access is batched: a full ADC re-import walks hundreds of feed rows, so an id-at-a-time lookup would be an N+1 against the same table.
        2. -
        3. Discriminate. For each feed row (:32): if a stored row exists and is soft-deleted, bump context.SkippedSoftDeleted and skip (:36-40, BR-136). If it exists and is live, call the aggregate's Update (:42). If it does not exist, go to the Create factory (:99).
        4. -
        5. Sync children. SyncCategoryItems (:62-90) compares feed items against the loaded CategoryItems collection by id, skips soft-deleted ones (:71-75), and routes the rest to UpdateCategoryItem or AddCategoryItem on the parent aggregate (:79-83), never to a child repository.
        6. -
        7. Batch add and report. New categories accumulate in a local list and are flushed with one AddRangeAsync (:54-57), then the counts are returned (:59).
        8. +
        9. Bulk pre-load. The strategy resolves the repository once (CategorySyncStrategy.cs:17), projects the feed's ids (:22) and issues a single GetByIdsAsync with includes: [nameof(Category.CategoryItems)], asTracking: true and ignoreQueryFilters: true (:23-28), then indexes the result by id (:29). One query replaces N GetByIdAsync calls. [Rubric §12, Performance and Scalability] assesses whether repeated data access is batched: a full ADC re-import walks hundreds of feed rows, so an id-at-a-time lookup would be an N+1 against the same table.
        10. +
        11. Discriminate. For each feed row (:33): if a stored row exists and is soft-deleted, bump context.SkippedSoftDeleted and skip (:37-41, BR-136). If it exists and is live, call the aggregate's Update (:43); a refused update keeps the stored values and adds a "was not updated" warning (:44-48), and the row still counts as synced (:50). If it does not exist, go to the Create factory (:107).
        12. +
        13. Sync children. SyncCategoryItems (:69-98) compares feed items against the loaded CategoryItems collection by id, skips soft-deleted ones (:78-82), and routes the rest to UpdateCategoryItem or AddCategoryItem on the parent aggregate (:84-86), never to a child repository. Both calls return a Result: a refused item is reported through ItemRefusedWarning and is not counted (:87-92).
        14. +
        15. Batch add and report. New categories accumulate in a local list and are flushed with one AddRangeAsync (:61-64), then the counts are returned (:66).
        -

        [Rubric §4, DDD] assesses whether invariants stay inside aggregates: every mutation in this file is a call on Category, and category items are only ever reached through their parent, so the aggregate boundary holds even under a bulk import.

        +

        [Rubric §4, DDD] assesses whether invariants stay inside aggregates: every mutation in this file is a call on Category, and category items are only ever reached through their parent, so the aggregate boundary holds even under a bulk import, and the aggregate's own refusals are reported rather than bypassed.

      • -
      • Walkthrough of the specifics: CreateNewCategory (:92-118) is where the failure policy shows. Category.Create returns a Result; on failure the strategy appends a warning naming the title and id and quoting SessionizeSyncWarnings.FirstErrorMessage (:104), then returns without counting the row. On success it adds every feed item to the fresh aggregate (:109-113), queues the category (:115) and increments the count through a ref int parameter (:116). Note the asymmetry between the two paths: for an existing category items are reconciled against what is stored, while for a new one they are simply added, because there is nothing to reconcile against.

        +
      • Walkthrough of the specifics: CreateNewCategory (:100-133) is where the failure policy shows. Category.Create returns a Result; on failure the strategy appends a warning naming the title and id and quoting SessionizeSyncWarnings.FirstErrorMessage (:112), then returns without counting the row. On success it adds every feed item to the fresh aggregate (:117-128), reporting and skipping any item the aggregate refuses (for example a BR-138 duplicate name, :120-125), queues the category (:130) and increments the count through a ref int parameter (:131). ItemRefusedWarning (:135-136) formats the item name, id and parent category title once so both item paths word the warning identically. Note the asymmetry between the two paths: for an existing category items are reconciled against what is stored, while for a new one they are simply added, because there is nothing to reconcile against.

      • -
      • Why it's built this way: a single bad row must not abort an import of hundreds, so the strategy degrades: warn, skip, keep going, and let the organizer read the warnings on the response. ignoreQueryFilters: true is required rather than optional here, because a feed id is the row's literal primary key: a soft-deleted category is invisible under the global filter, so without the flag the strategy would treat it as new and the insert would violate the primary key and roll the whole refresh back.

        +
      • Why it's built this way: a single bad row must not abort an import of hundreds, so the strategy degrades: warn, skip, keep going, and let the organizer read the warnings on the response. Every Result the aggregate returns is inspected, so a refused update or item never reads as a silent success. ignoreQueryFilters: true is required rather than optional here, because a feed id is the row's literal primary key: a soft-deleted category is invisible under the global filter, so without the flag the strategy would treat it as new and the insert would violate the primary key and roll the whole refresh back.

      • -
      • Where it's used: instance zero of the handler's strategy array (RefreshFromSessionizeHandler.cs:32); its two counts become CategoriesSynced and CategoryItemsSynced on the response (RefreshFromSessionizeHandler.cs:175-176). Covered by MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/CategorySyncStrategyTests.cs:15.

        +
      • Where it's used: instance zero of the handler's strategy array (RefreshFromSessionizeHandler.cs:32); its two counts become CategoriesSynced and CategoryItemsSynced on the response (RefreshFromSessionizeHandler.cs:197-198). Covered by MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/CategorySyncStrategyTests.cs:15.


      @@ -1557,19 +1591,19 @@

      QuestionSyncStrategy

    • What it is: the step that upserts Question rows. It adds two concerns the category step does not have: deciding which entity a question belongs to, and refusing feed ids that would collide with organizer-created questions.
    • -
    • Depends on: SessionizeSyncContext, SessionizeQuestion, Question, QuestionInvariants, and the repository from the context's unit of work.
    • +
    • Depends on: SessionizeSyncContext, SessionizeQuestion, Question, QuestionInvariants, SessionizeSyncWarnings, and the repository from the context's unit of work.
    • Concept: the four-phase shape is taught under CategorySyncStrategy. What is new here is the reserved identifier band. QuestionInvariants.ManualIdRangeStart and ManualIdRangeEnd are 999_999_000 and 999_999_999 (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Questions/QuestionInvariants.cs:40 and :43). Organizer-created questions take ids from that band; Sessionize allocates from far below it. A feed id landing inside the band would silently overwrite a question a human wrote, so the strategy filters those rows out with a warning before anything reaches the database (QuestionSyncStrategy.cs:30-41). [Rubric §8, Data Architecture] assesses how identity is allocated when rows arrive from two sources into one table: the split-range convention is what lets imported and hand-created questions share a key space without a mapping table.
    • Walkthrough:
      • Answer-derived classification (:20-27): before touching the database, the strategy builds two hash sets, the question ids answered by speakers and the question ids answered by sessions, by flattening QuestionAnswers across the feed's speakers and sessions.
      • Reserved-id filter (:30-41): the guard above, producing validQuestions.
      • Bulk pre-load (:44-50): one GetByIdsAsync over the surviving ids with tracking on and query filters off, indexed by id.
      • -
      • Classification and mapping: DeriveQuestionEntity (:101-118) returns "Session" when a session answered the question, "Speaker" when a speaker did, and otherwise falls back to the stored value before defaulting to "Session" (:117). That ordering matters: a feed that happens to carry no answers this run offers no classification signal, so an existing question keeps its recorded entity instead of being reclassified. MapSessionizeQuestionType (:123-129) collapses the feed's open type vocabulary onto the three domain-valid values, passing "Rating" and "Email" through and mapping everything else (Short_Text, Long_Text, Url, YesNo) to "Text".
      • -
      • Update or create (:61-83): a soft-deleted match is skipped and counted (:63-67, BR-136); a live match is updated with existingQuestion.IsRequired passed back in (:69), so the organizer's required flag survives a re-sync. A new question is created with isRequired: false and questionSource: "Sessionize" (:73), which is how imported questions stay distinguishable from hand-created ones; a failed create warns and skips (:80-81).
      • -
      • Batch add and report (:88-93).
      • +
      • Classification and mapping: DeriveQuestionEntity (:106-123) returns "Session" when a session answered the question, "Speaker" when a speaker did, and otherwise falls back to the stored value before defaulting to "Session" (:122). That ordering matters: a feed that happens to carry no answers this run offers no classification signal, so an existing question keeps its recorded entity instead of being reclassified. MapSessionizeQuestionType (:128-134) collapses the feed's open type vocabulary onto the three domain-valid values, passing "Rating" and "Email" through and mapping everything else (Short_Text, Long_Text, Url, YesNo) to "Text".
      • +
      • Update or create (:61-88): a soft-deleted match is skipped and counted (:63-67, BR-136); a live match is updated with existingQuestion.IsRequired passed back in (:69), so the organizer's required flag survives a re-sync. The update returns a Result: a refusal keeps the stored values and adds a "was not updated" warning quoting SessionizeSyncWarnings.FirstErrorMessage (:70-74), while the row is still counted. A new question is created with isRequired: false and questionSource: "Sessionize" (:78), which is how imported questions stay distinguishable from hand-created ones; a failed create warns and skips without counting (:83-87).
      • +
      • Batch add and report (:93-98).
    • Why it's built this way: classification cannot come from the feed's question record itself, only from which side of the payload answered it, which is why the two hash sets are computed up front rather than per row. Preserving IsRequired and the stored entity value on update is the same principle the room step applies to organizer-entered fields: the import owns the fields Sessionize sends and nothing else.
    • -
    • Where it's used: instance two of the handler's array (RefreshFromSessionizeHandler.cs:34), reported as QuestionsSynced (RefreshFromSessionizeHandler.cs:178). MapSessionizeQuestionType is internal static so it can be exercised directly: the assembly grants InternalsVisibleTo to the application test project (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/MMCA.ADC.Conference.Application.csproj:3), and the tests live at MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/QuestionSyncStrategyTests.cs:16.
    • +
    • Where it's used: instance two of the handler's array (RefreshFromSessionizeHandler.cs:34), reported as QuestionsSynced (RefreshFromSessionizeHandler.cs:200). MapSessionizeQuestionType is internal static so it can be exercised directly: the assembly grants InternalsVisibleTo to the application test project (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/MMCA.ADC.Conference.Application.csproj:3), and the tests live at MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/QuestionSyncStrategyTests.cs:16.

    RoomSyncStrategy

    @@ -1590,7 +1624,7 @@

    RoomSyncStrategy

  • Why it's built this way: the whole refresh is one transaction (ITransactional on RefreshFromSessionizeCommand), which makes any uncaught constraint violation an all-or-nothing loss. Each guard here converts a would-be transaction abort into a single skipped row plus a warning line. [Rubric §29, Resilience and Business Continuity] assesses whether a partial upstream defect degrades gracefully rather than taking the operation down.
  • -
  • Where it's used: instance one of the handler's array (RefreshFromSessionizeHandler.cs:33), reported as RoomsSynced (RefreshFromSessionizeHandler.cs:177). Covered by MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/RoomSyncStrategyTests.cs:11.
  • +
  • Where it's used: instance one of the handler's array (RefreshFromSessionizeHandler.cs:33), reported as RoomsSynced (RefreshFromSessionizeHandler.cs:199). Covered by MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/RoomSyncStrategyTests.cs:11.

  • SessionSyncStrategy

    @@ -1600,37 +1634,38 @@

    SessionSyncStrategy

    • What it is: the last step of the import. It upserts Session rows and their three join collections (speakers, category items, question answers), and it runs last because a session references rooms, speakers and category items the earlier steps created.
    • Depends on: SessionizeSyncContext, SessionizeSession, SessionizeQuestionAnswer, Session, SessionizeSyncWarnings, and the repository from the context's unit of work.
    • -
    • Concept introduced, reactivate rather than re-add (BR-135). The session and its collections are loaded with ignoreQueryFilters: true (SessionSyncStrategy.cs:23-28), so the loaded SessionSpeakers, SessionCategoryItems and SessionQuestionAnswers collections carry the removed associations too. SyncSessionChildren (:117-159) uses that: for a feed association with no live match it first looks for a soft-deleted row with the same key and calls RestoreSessionSpeaker (:132) or RestoreSessionCategoryItem (:149), and only adds a new row when none exists (:136 and :153). Adding instead would leave the removed row in place and double the association. [Rubric §8, Data Architecture] assesses whether soft-delete is handled consistently on the write path as well as the read path: here the filters are turned off precisely so the write path can see and revive what the read path hides.
    • +
    • Concept introduced, reactivate rather than re-add (BR-135). The session and its collections are loaded with ignoreQueryFilters: true (SessionSyncStrategy.cs:23-28), so the loaded SessionSpeakers, SessionCategoryItems and SessionQuestionAnswers collections carry the removed associations too. SyncSessionChildren (:146-188) uses that: for a feed association with no live match it first looks for a soft-deleted row with the same key and calls RestoreSessionSpeaker (:161) or RestoreSessionCategoryItem (:178), and only adds a new row when none exists (:165 and :182). Adding instead would leave the removed row in place and double the association. [Rubric §8, Data Architecture] assesses whether soft-delete is handled consistently on the write path as well as the read path: here the filters are turned off precisely so the write path can see and revive what the read path hides.
    • Walkthrough:
        -
      • Bulk pre-load (:23-29) with all three child collections included.
      • +
      • Bulk pre-load (:22-29) with all three child collections included.
      • Advisory time validation (:53-71): ValidateSessionTimes warns when a session starts before the event's StartDate or ends after its EndDate (BR-86, :56-64) and when EndsAt is at or before StartsAt (BR-122, :67-70). None of these reject the session: the row is imported as-is and the organizer decides. [Rubric §24, Forms, Validation and UX Safety] assesses whether a system distinguishes a hard invariant from an advisory: schedule anomalies in a live conference feed are usually real and in flight, so blocking the import would be worse than reporting it.
      • -
      • Resolve or create (:73-122): a soft-deleted match is skipped and counted (:81-85, BR-136). A live match is updated with the feed's fields, while AccessibilityInfo and ResourceLinks are read back off the stored session (:93) because Sessionize does not send them; a refused update no longer passes silently, it now surfaces a warning exactly like a refused create instead of leaving the stored session looking successfully synced (:94-99). A miss goes to Session.Create (:104-110), which receives null for those same two fields and the event id from the context; a failed create warns and returns null (:111-117).
      • -
      • Children (:124-183): speakers and category items follow the restore-or-add shape above; SyncSessionQuestionAnswers (:168-183) updates the live answer for a question id if one exists and adds one otherwise, keyed on QuestionId with an IsDeleted guard (:172-173).
      • +
      • Resolve the room first (:79): ResolveImportedRoomId (:132-144) passes the feed's room id through only when it is null or when the event's loaded Rooms collection holds a live room with that id (:134-135). A room the room step skipped (owned by another event, refused by the aggregate, or in the reserved range) is not on the event, so assigning it would fail the final save on the foreign key and roll back the whole import; instead the session is stored without a room and a warning names both the session and the room id (:140-143).
      • +
      • Resolve or create (:73-124): a soft-deleted match is skipped and counted (:83-87, BR-136). A live match is updated with the feed's fields and the resolved room id, while AccessibilityInfo and ResourceLinks are read back off the stored session (:95) because Sessionize does not send them; a refused update surfaces a "was not updated" warning exactly like a refused create, instead of leaving the stored session looking successfully synced (:96-101). A miss goes to Session.Create (:106-112), which receives null for those same two fields, the event id from the context and the resolved room id; a failed create warns and returns null (:113-119).
      • +
      • Children (:146-205): speakers and category items follow the restore-or-add shape above; SyncSessionQuestionAnswers (:190-205) updates the live answer for a question id if one exists and adds one otherwise, keyed on QuestionId with an IsDeleted guard (:194-195).
      • Batch add and report (:45-50).
    • -
    • Why it's built this way: an import that runs repeatedly against a moving feed must be idempotent in the practical sense, that re-running it does not multiply rows. Keying every child comparison on the domain id plus an IsDeleted check, and preferring restore over insert, is what delivers that.
    • -
    • Where it's used: instance four of the handler's array (RefreshFromSessionizeHandler.cs:36), reported as SessionsSynced (RefreshFromSessionizeHandler.cs:180). Covered by MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/SessionSyncStrategyTests.cs:15.
    • +
    • Why it's built this way: an import that runs repeatedly against a moving feed must be idempotent in the practical sense, that re-running it does not multiply rows. Keying every child comparison on the domain id plus an IsDeleted check, and preferring restore over insert, is what delivers that. The room check applies the same degrade-not-abort rule across steps: one session pointing at a room an earlier step declined costs that session its room, not the whole refresh.
    • +
    • Where it's used: instance four of the handler's array (RefreshFromSessionizeHandler.cs:36), reported as SessionsSynced (RefreshFromSessionizeHandler.cs:202). Covered by MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/SessionSyncStrategyTests.cs:15.

    SpeakerSyncStrategy

    -

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/SpeakerSyncStrategy.cs:14 · Level 10 · class (internal sealed)

    +

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/SpeakerSyncStrategy.cs:16 · Level 10 · class (internal sealed)

    • What it is: the step that upserts Speaker rows, links each speaker to the event through EventSpeaker, and syncs each speaker's category items and question answers. It also turns the feed's loose link list into typed social fields.
    • -
    • Depends on: SessionizeSyncContext, SessionizeSpeaker, SessionizeLink, SessionizeQuestionAnswer, Speaker, Event, EventSpeaker, SessionizeSyncWarnings, plus both IRepository<TEntity, TIdentifierType> and IReadRepository<TEntity, TIdentifierType>.
    • -
    • Concept introduced, reviving an association the aggregate cannot see. The event was loaded by the handler with the global query filters on, so a removed EventSpeaker link is simply absent from context.Event.EventSpeakers. Re-adding it would create a second row alongside the removed one. LoadDeletedEventSpeakersAsync (:81-99) closes that hole: it opens a read repository for EventSpeaker (:85), queries this event's deleted links with ignoreQueryFilters: true and asTracking: true (:87-92), and groups them by speaker id, taking the first of each group because a speaker added and removed repeatedly can carry more than one removed link (:96-98). The link decision then reads: skip if a live link exists, restore a deleted one if either the aggregate or that dictionary has it, otherwise add (:48-61, BR-135). [Rubric §8, Data Architecture] assesses whether the soft-delete convention is applied coherently across an aggregate boundary, which is exactly the trap this method sidesteps.
    • +
    • Depends on: SessionizeSyncContext, SessionizeSpeaker, SessionizeLink, SessionizeQuestionAnswer, Speaker, Event, EventSpeaker, SpeakerInvariants, CommonInvariants, SessionizeSyncWarnings, plus both IRepository<TEntity, TIdentifierType> and IReadRepository<TEntity, TIdentifierType>.
    • +
    • Concept introduced, reviving an association the aggregate cannot see. The event was loaded by the handler with the global query filters on, so a removed EventSpeaker link is simply absent from context.Event.EventSpeakers. Re-adding it would create a second row alongside the removed one. LoadDeletedEventSpeakersAsync (:83-101) closes that hole: it opens a read repository for EventSpeaker (:87), queries this event's deleted links with ignoreQueryFilters: true and asTracking: true (:89-94), and groups them by speaker id, taking the first of each group because a speaker added and removed repeatedly can carry more than one removed link (:98-100). The link decision then reads: skip if a live link exists, restore a deleted one if either the aggregate or that dictionary has it, otherwise add (:50-63, BR-135). [Rubric §8, Data Architecture] assesses whether the soft-delete convention is applied coherently across an aggregate boundary, which is exactly the trap this method sidesteps.
    • Walkthrough:
        -
      • Bulk pre-load (:23-29) with SpeakerCategoryItems and SpeakerQuestionAnswers included, filters off, tracking on.
      • -
      • Social-link extraction (:183-215): ExtractSocialLinks walks the feed's links and matches LinkType case-insensitively, sending "Twitter" through ExtractTwitterHandle (:197-200), "LinkedIn" to the LinkedIn field (:201-204), "Blog" and "Company_Website" to the website field (:205-209), and falling back to a URL-content check for "github" (:210-213). ExtractTwitterHandle (:217-240) strips six known twitter.com and x.com prefixes, trims a leading @ and surrounding slashes, and returns null for an empty result. The #pragma warning disable S5332 around the replacement chain (:227-237) is deliberate and documented in place: the plain-http literals are input patterns being removed, not addresses this code connects to, and dropping them would leave legacy Sessionize profile links unparsed.
      • -
      • Create or update (:101-143): a soft-deleted speaker is skipped and counted (:113-117). A live one is updated with existingSpeaker.Email?.Value passed back in (:120) so the stored email survives, since the feed does not carry it. A new speaker goes through Speaker.Create with a null email (:126) and is then immediately updated (:137-140) because the factory does not accept the social fields; a failed create warns and returns null (:127-133).
      • -
      • Children (:145-181): SyncCategoryItems (:145-164) uses the restore-or-add shape; SyncQuestionAnswers (:166-181) updates a live answer by its id or adds a new one.
      • -
      • Batch add and report (:67-72).
      • +
      • Bulk pre-load (:24-31) with SpeakerCategoryItems and SpeakerQuestionAnswers included, filters off, tracking on.
      • +
      • Social-link extraction (:201-238): ExtractSocialLinks takes the whole feed speaker plus the context's warning list (:39), walks the speaker's links and matches LinkType case-insensitively, sending "Twitter" through ExtractTwitterHandle (:217-223), "LinkedIn" to the LinkedIn field (:224-227), "Blog" and "Company_Website" to the website field (:228-232), and falling back to a URL-content check for "github" (:233-236). Each value is screened before it is kept: a Twitter handle longer than SpeakerInvariants.TwitterHandleMaxLength is dropped (:220-222), and the three URLs go through AcceptUrl (:240-244), which keeps a link only when it fits its column limit from SpeakerInvariants and passes CommonInvariants.EnsureUrlIsWellFormed. A rejected link goes to DropLink (:246-252), which adds a warning naming the speaker and the field and returns null. ExtractTwitterHandle (:254-277) strips six known twitter.com and x.com prefixes, trims a leading @ and surrounding slashes, and returns null for an empty result. The #pragma warning disable S5332 around the replacement chain (:264-274) is deliberate and documented in place: the plain-http literals are input patterns being removed, not addresses this code connects to, and dropping them would leave legacy Sessionize profile links unparsed.
      • +
      • Create or update (:103-156): a soft-deleted speaker is skipped and counted (:115-119). A live one is updated with existingSpeaker.Email?.Value passed back in (:122) so the stored email survives, since the feed does not carry it; a refused update keeps the stored values and adds a "was not updated" warning (:125-129). A new speaker goes through Speaker.Create with a null email (:134) and is then immediately updated (:145-148) because the factory does not accept the social fields; if that follow-up update is refused the speaker is still created and a warning says it lacks its social links (:149-153). A failed create warns and returns null (:135-141).
      • +
      • Children (:158-194): SyncCategoryItems (:158-177) uses the restore-or-add shape; SyncQuestionAnswers (:179-194) updates a live answer by its id or adds a new one.
      • +
      • Batch add and report (:69-74).
    • -
    • Why it's built this way: the module treats Sessionize as the owner of the fields Sessionize sends and the organizer as the owner of everything else, so every update call in this file threads the locally held values (email here, room detail in the room step, IsRequired in the question step) back through the aggregate rather than blanking them. [Rubric §30, Compliance, Privacy and Data Governance] assesses whether personal data is written only from the source entitled to set it: the speaker email is never overwritten by an import.
    • -
    • Where it's used: instance three of the handler's array (RefreshFromSessionizeHandler.cs:35), reported as SpeakersSynced (RefreshFromSessionizeHandler.cs:179). Covered by MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/SpeakerSyncStrategyTests.cs:12.
    • +
    • Why it's built this way: the module treats Sessionize as the owner of the fields Sessionize sends and the organizer as the owner of everything else, so every update call in this file threads the locally held values (email here, room detail in the room step, IsRequired in the question step) back through the aggregate rather than blanking them. [Rubric §30, Compliance, Privacy and Data Governance] assesses whether personal data is written only from the source entitled to set it: the speaker email is never overwritten by an import. Screening each link individually serves the degrade-not-abort rule at field level: the domain's URL rule would otherwise refuse the whole speaker update for one malformed or oversized link, so the bad link is dropped with a warning and the rest of the profile still syncs.
    • +
    • Where it's used: instance three of the handler's array (RefreshFromSessionizeHandler.cs:35), reported as SpeakersSynced (RefreshFromSessionizeHandler.cs:201). Covered by MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/SpeakerSyncStrategyTests.cs:12.

    RefreshFromSessionizeHandler

    @@ -1638,36 +1673,37 @@

    RefreshFromSessionizeHandler

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Events.UseCases.RefreshFromSessionize · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeHandler.cs:20 · Level 14 · class (sealed partial)

      -
    • What it is: the command handler behind UC-6. It checks the preconditions, claims the refresh window by stamping and committing it on the event before calling out, calls the Sessionize API, runs the five strategies in dependency order against one shared context, saves everything in one further transaction, and returns the per-entity counts and warnings.

      +
    • What it is: the command handler behind UC-6. It checks the preconditions, claims the refresh window by stamping and committing it on the event before calling out (the first commit), calls the Sessionize API, then runs the five strategies in dependency order against one shared context and saves the import inside one explicit transaction (the second commit), and returns the per-entity counts and warnings.

    • -
    • Depends on: IUnitOfWork, ISessionizeService, ICurrentUserService, IConcurrencyConflictDetector (MMCA.Common), TimeProvider (BCL), ILogger<T> (Microsoft.Extensions.Logging), Event, Result and Error, RefreshFromSessionizeCommand, RefreshFromSessionizeResultDTO, SessionizeResponse, SessionizeSyncContext, SessionizeSyncResult, ISessionizeSyncStrategy and its five implementations. Externals: Polly.CircuitBreaker and Polly.Timeout (for the two rejection types it catches) and System.Text.Json.

      +
    • Depends on: IUnitOfWork, ISessionizeService, ICurrentUserService, IConcurrencyConflictDetector (MMCA.Common), TimeProvider (BCL), ILogger<T> (Microsoft.Extensions.Logging), Event, Result and Error, RefreshFromSessionizeCommand, RefreshFromSessionizeResultDTO, SessionizeResponse, SessionizeSyncContext, SessionizeSyncResult, ISessionizeSyncStrategy and its five implementations. Externals: Polly.CircuitBreaker and Polly.Timeout (for the two rejection types it catches) and System.Text.Json.

    • -
    • Concept introduced, turning a check-then-act throttle into a claim. The five-minute BR-63 window used to be read-only: two requests a moment apart could both see an expired stamp and both pay for the metered Sessionize call. The handler now writes and commits RecordSessionizeRefresh (:81, inside :75-92) before it calls out at all, which turns the event row itself into the claim: both writers loaded the same RowVersion, so the loser's UPDATE matches no row and unitOfWork.SaveChangesAsync (:84) throws. The catch clause filters on concurrencyConflictDetector.IsConcurrencyConflict(ex) (:86) and answers with the same Event.Sessionize.Throttled failure the five-minute check would have given a moment later, so the API surface and status code do not change; no distributed lock is needed because the concurrency token every AuditableBaseEntity already carries does the work. IsSessionizeUnavailable (:206-211, unchanged) still classifies HttpRequestException, Polly's TimeoutRejectedException and BrokenCircuitException, and JsonException or NotSupportedException as "no usable Sessionize data right now" so an upstream outage answers 502 instead of 500, and it still re-checks cancellation first (:109) so a broadened catch cannot turn a caller's cancellation into an outage answer. [Rubric §29, Resilience and Business Continuity] assesses this kind of graceful degradation and concurrency-safe claim against a metered third-party dependency. [Rubric §13, Observability and Operability] assesses whether operators can tell a dependency outage from a defect.

      +
    • Concept introduced, turning a check-then-act throttle into a claim. Read on its own, the five-minute BR-63 window is a check-then-act: two requests a moment apart could both see an expired stamp and both pay for the metered Sessionize call. The handler writes and commits RecordSessionizeRefresh (:85, inside :75-96) before it calls out at all, which turns the event row itself into the claim: both writers loaded the same RowVersion, so the loser's UPDATE matches no row and unitOfWork.SaveChangesAsync (:88) throws. Because the command is not ITransactional, that save commits on its own and is visible to a concurrent writer at once (:82-84). The catch clause filters on concurrencyConflictDetector.IsConcurrencyConflict(ex) (:90) and answers with the same Event.Sessionize.Throttled failure the five-minute check would have given a moment later, so the API surface and status code do not change; no distributed lock is needed because the concurrency token every AuditableBaseEntity already carries does the work. IsSessionizeUnavailable (:230-235) classifies HttpRequestException, Polly's TimeoutRejectedException and BrokenCircuitException, and JsonException or NotSupportedException as "no usable Sessionize data right now" so an upstream outage answers 502 instead of 500, and the catch re-checks cancellation first (:113) so a broadened catch cannot turn a caller's cancellation into an outage answer. [Rubric §29, Resilience and Business Continuity] assesses this kind of graceful degradation and concurrency-safe claim against a metered third-party dependency. [Rubric §13, Observability and Operability] assesses whether operators can tell a dependency outage from a defect.

    • Walkthrough:

      • Static strategy array (:30-37): five stateless instances in dependency order (categories, rooms, questions, speakers, sessions), with the ordering rationale in the comment above them (:28-29). static readonly because the strategies hold no state; all state lives in the per-command context.
      • -
      • Primary constructor (:20-26): six dependencies, concurrencyConflictDetector added for the BR-63 claim below. sealed partial is what allows the [LoggerMessage] source-generated log method at the bottom of the file (:215-216).
      • +
      • Primary constructor (:20-26): six dependencies, concurrencyConflictDetector serving the BR-63 claim below. sealed partial is what allows the [LoggerMessage] source-generated log method at the bottom of the file (:237-238).
      • Load the aggregate (:45-50): GetByIdAsync with Rooms and EventSpeakers included and asTracking: true, returning Error.NotFound when the event is missing.
      • Precondition, a configured code (:58-66, BR-6): a blank SessionizeCode fails with Event.Sessionize.NoCode.
      • Precondition, the throttle (:68-73, BR-63): if LastSessionizeRefreshOn is less than five minutes before timeProvider.GetUtcNow(), the handler fails with ThrottledError() and never calls the API. Time comes from an injected TimeProvider, which is what makes the window testable without waiting.
      • -
      • Claim the window (:75-92, BR-63 concurrency half): stamps and commits the refresh before calling out, per the concept above; a concurrency conflict on that save is treated as losing the claim, not as an error.
      • -
      • Call the API (:94-116): sessionizeService.GetAllAsync now returns Result<SessionizeResponse?> rather than a bare response; the stamp is already committed at this point, so a failure from here on leaves the throttle window advanced for up to five minutes rather than rolling it back, a deliberate bound on retry spend against a metered API. IsSessionizeUnavailable classification as above.
      • -
      • Surface the client's own failure (:118-123): if responseResult.IsFailure, its errors are forwarded directly. The client's only failure today is refusing a code it cannot safely compose into a request URI (an SSRF guard).
      • -
      • An empty response is success (:125-141): a null responseResult.Value is not an error, since an event may have no data yet; the handler returns a DTO of zeros with no warnings. It no longer re-stamps the refresh here, since the stamp was already written and committed in the claim step above.
      • -
      • Run the strategies (:143-156): one context is built and the five SyncAsync calls run sequentially, collecting a SessionizeSyncResult each.
      • -
      • Summarize skips (:158-161): a non-zero SkippedSoftDeleted becomes one final warning line naming the count and BR-136.
      • -
      • Save without re-stamping (:163-169): the comment records explicitly that nothing is stamped here, then unitOfWork.RequestIdentityInsert() (:168) is called before the single SaveChangesAsync (:169). This is the load-bearing detail of the whole use case: Sessionize rows keep their external ids as primary keys in tables whose key columns are IDENTITY, so the unit of work has to wrap the save in SET IDENTITY_INSERT ON/OFF per table. The request flag is declared on IUnitOfWork (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IUnitOfWork.cs:45), forwarded to the context factory (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:73), read and reset on the next save (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:246-247), and routed to the per-table round-splitting path for SQL Server contexts (DbContextFactory.cs:266-268, implemented at DbContextFactory.cs:303).
      • -
      • Log and project (:171-183): the source-generated information-level log records the event id, then the five results are read positionally, results[0] through results[4], into the response DTO.
      • -
      • ThrottledError() (:186-196): the one BR-63 failure declared once so the pre-check and the concurrency-loss path cannot drift apart, since EventsController.RefreshAsync matches on its code to answer 429.
      • -
      -

      [Rubric §6, CQRS and Event-Driven] assesses whether writes flow through one explicit handler boundary: this type implements ICommandHandler<in TCommand, TResult> (:26), is wrapped by the decorator pipeline described in group 5, and the controller knows only the interface. [Rubric §3, Clean Architecture] assesses dependency direction: the handler names ISessionizeService, never an HTTP client, so the outbound call is an abstraction the infrastructure layer satisfies with SessionizeService.

      +
    • Claim the window (:75-96, BR-63 concurrency half): stamps and commits the refresh before calling out, per the concept above; a concurrency conflict on that save is treated as losing the claim, not as an error. This is the first of the two commits.
    • +
    • Call the API (:98-120): sessionizeService.GetAllAsync returns Result<SessionizeResponse?> (:105-108); the stamp is already committed at this point, so a failure from here on leaves the throttle window advanced for up to five minutes rather than rolling it back, a deliberate bound on retry spend against a metered API (:98-104). IsSessionizeUnavailable classification as above.
    • +
    • Surface the client's own failure (:122-127): if responseResult.IsFailure, its errors are forwarded directly. The client's only failure is refusing a code it cannot safely compose into a request URI (an SSRF guard).
    • +
    • An empty response is success (:131-145): a null responseResult.Value is not an error, since an event may have no data yet; the handler returns a DTO of zeros with no warnings and opens no import transaction.
    • +
    • Open the import transaction (:147-151): the second commit. unitOfWork.ExecuteInTransactionAsync runs the private ImportAsync inside one explicit transaction (:149), so every strategy and the final save are all-or-nothing while the stamp above stands on its own. The method is declared on IUnitOfWork (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IUnitOfWork.cs:63) and delegated to the context factory (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:76-79). The source-generated information-level log then records the event id (:153) and the import's result is returned as-is (:155).
    • +
    • Run the strategies (ImportAsync, :162-206): one context is built (:168-174) and the five SyncAsync calls run sequentially, collecting a SessionizeSyncResult each (:176-180).
    • +
    • Summarize skips (:182-185): a non-zero SkippedSoftDeleted becomes one final warning line naming the count and BR-136.
    • +
    • Save without re-stamping (:187-193): the comment records explicitly that nothing is stamped here, then unitOfWork.RequestExplicitKeyInsert() (:192) is called before the single SaveChangesAsync (:193). This is the load-bearing detail of the whole use case: Sessionize rows keep their external ids as primary keys in tables whose key columns are IDENTITY, so the unit of work has to wrap the save in SET IDENTITY_INSERT ON/OFF per table. The request flag is declared on IUnitOfWork (IUnitOfWork.cs:45), forwarded to the context factory (UnitOfWork.cs:73), set at MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Factory/DbContextFactory.cs:321, read and reset on the next save (DbContextFactory.cs:272-273), and routed to the per-table round-splitting path only when the context's engine supplies an explicit-key insert dialect (DbContextFactory.cs:292-293, implemented at DbContextFactory.cs:329); SQL Server is the engine that does (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DataSources/Engines/SQLServerDataSourceEngine.cs:49).
    • +
    • Project (:195-205): the five results are read positionally, results[0] through results[4], into the response DTO.
    • +
    • ThrottledError() (:214-218): the one BR-63 failure declared once so the pre-check and the concurrency-loss path cannot drift apart, since EventLifecycleController.RefreshAsync matches on its code to answer 429.
    • +
    +

    [Rubric §6, CQRS and Event-Driven] assesses whether writes flow through one explicit handler boundary: this type implements ICommandHandler<in TCommand, TResult> (:26), is wrapped by the decorator pipeline described in group 5 (the command does not carry ITransactional, so the pipeline opens no transaction around it), and the controller knows only the interface. [Rubric §3, Clean Architecture] assesses dependency direction: the handler names ISessionizeService, never an HTTP client, so the outbound call is an abstraction the infrastructure layer satisfies with SessionizeService.

    -
  • Why it's built this way: one transaction across five entity families is not an optimization, it is what keeps sessions from referencing rooms or speakers that did not commit. That constraint drives the rest of the design: strategies must not throw on a bad row (a throw would abort everything), they must not provoke primary key collisions (hence the unscoped lookups and the reserved-band guards), and the handler must not save between steps. The claim-before-call ordering (ADR-035, optimistic concurrency) is a second, separate constraint: it costs a second SaveChangesAsync round trip on every refresh, but it is what makes the five-minute throttle correct under concurrent requests rather than merely correct-most-of-the-time.

    +
  • Why it's built this way: one transaction across five entity families is not an optimization, it is what keeps sessions from referencing rooms or speakers that did not commit. That constraint drives the rest of the design: strategies must not throw on a bad row (a throw would abort everything), they must not provoke primary key collisions (hence the unscoped lookups and the reserved-band guards), and the handler must not save between steps. The claim-before-call ordering (ADR-035, optimistic concurrency) is a second, separate constraint, and the two together are why the handler owns its transaction boundaries instead of declaring ITransactional (ADR-107, transaction execution contract): the stamp has to commit alone and survive a failed call, the import has to commit as a unit, and one ambient transaction around the whole command could not give both. The cost is a second SaveChangesAsync round trip on every refresh, which is what makes the five-minute throttle correct under concurrent requests rather than merely correct-most-of-the-time.

  • -
  • Where it's used: injected into EventsController as ICommandHandler<RefreshFromSessionizeCommand, Result<RefreshFromSessionizeResultDTO>> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:54) and invoked from RefreshAsync (EventsController.cs:329-334), which is [HttpPost("{id}/refresh")] and [Idempotent] (EventsController.cs:327-328). The controller maps the two well-known error codes onto transport: Event.Sessionize.Throttled becomes a 429 with a Retry-After of 300 seconds (EventsController.cs:340-343) and Event.Sessionize.Unavailable becomes a 502 (EventsController.cs:347-348). Unit tests live at MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeHandlerTests.cs:16, with an integration tier at MMCA.ADC/Tests/Integration/MMCA.ADC.Conference.IntegrationTests/Organizer/Events/SessionizeRefreshTests.cs.

    +
  • Where it's used: injected into EventLifecycleController as ICommandHandler<RefreshFromSessionizeCommand, Result<RefreshFromSessionizeResultDTO>> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventLifecycleController.cs:35) and invoked from RefreshAsync (EventLifecycleController.cs:117-123), which is [HttpPost("{id}/refresh")] and [Idempotent] (EventLifecycleController.cs:115-116). The controller maps the two well-known error codes onto transport: Event.Sessionize.Throttled becomes a 429 with a Retry-After of 300 seconds (EventLifecycleController.cs:128-132) and Event.Sessionize.Unavailable becomes a 502 (EventLifecycleController.cs:135-136). Unit tests live at MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeHandlerTests.cs:17, with an integration tier at MMCA.ADC/Tests/Integration/MMCA.ADC.Conference.IntegrationTests/Organizer/Events/SessionizeRefreshTests.cs.

  • -
  • Caveats: the DTO projection is positional (:175-180), so the response mapping is coupled to the order of the static array; inserting a sixth strategy anywhere but the end would silently shift every count that follows it. RecordSessionizeRefresh dereferences currentUserService.UserId!.Value (:81) with the null-forgiving operator, so the handler assumes an authenticated caller and would throw for an anonymous one; the endpoint's authorization is what upholds that assumption. A failure between the committed claim and the final save leaves the throttle window advanced for up to five minutes, a deliberate, time-bounded, self-clearing cost rather than a rollback.

    +
  • Caveats: the DTO projection is positional (:197-202), so the response mapping is coupled to the order of the static array; inserting a sixth strategy anywhere but the end would silently shift every count that follows it. RecordSessionizeRefresh dereferences currentUserService.UserId!.Value (:85) with the null-forgiving operator, so the handler assumes an authenticated caller and would throw for an anonymous one; the endpoint's authorization is what upholds that assumption. A failure between the committed claim and the final save leaves the throttle window advanced for up to five minutes, a deliberate, time-bounded, self-clearing cost rather than a rollback.


  • @@ -1729,10 +1765,10 @@

    RoomSortRules<T>

    • What it is: a reusable rule fragment enforcing that a room's sort-order value is non-negative. It is two lines long, because it inherits the whole rule from a framework base and supplies only the field label and the error code.
    • -
    • Depends on: NonNegativeIntRules<T>, its base class from the framework (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:122), plus System.Linq.Expressions.Expression<> (BCL). No domain constant.
    • -
    • Concept, subclassing a framework fragment while keeping the module's error code. The framework base takes an optional errorCode parameter (CommonValidationRules.cs:124) and applies it to every rule it declares through the internal WithOptionalErrorCode helper (:30-32), which returns the rule untouched when the code is null. That parameter is why a module fragment can inherit the base and still hand clients a stable machine-readable code, which is the trade the hand-written fragments in this module otherwise have to make by writing the rule chain out. [Rubric §9, API & Contract Design] assesses the stability of the error contract clients consume: Room.Sort.Negative travels with the message rather than the message having to be parsed. [Rubric §15, Best Practices & Code Quality] assesses whether a constraint lives in exactly one place: the comparison itself is written once in the framework, and the module supplies only the naming.
    • -
    • Walkthrough: the class declares : NonNegativeIntRules<T> (RoomValidationRules.cs:27) and its constructor takes an Expression<Func<T, int>> selector, forwarding base(selector, "Sort", "Room.Sort.Negative") (:29-30). The base registers a single GreaterThanOrEqualTo(0) rule with the message "Sort must be greater than or equal to 0" and attaches the supplied code (CommonValidationRules.cs:124-126).
    • -
    • Why it's built this way: GreaterThanOrEqualTo(0) rather than GreaterThan(0) because zero is a legitimate "first in the list" position, which is why the framework's PositiveIntRules<T> (CommonValidationRules.cs:100-105) is the wrong base to reuse here. Sort order drives deterministic room ordering in the UI, so a negative value is rejected at the application boundary.
    • +
    • Depends on: NonNegativeIntRules<T>, its base class from the framework (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:123), plus System.Linq.Expressions.Expression<> (BCL). No domain constant.
    • +
    • Concept, subclassing a framework fragment while keeping the module's error code. The framework base takes an optional errorCode parameter (CommonValidationRules.cs:125) and applies it to every rule it declares through the internal WithOptionalErrorCode helper (:30-32), which returns the rule untouched when the code is null. That parameter is why a module fragment can inherit the base and still hand clients a stable machine-readable code, which is the trade the hand-written fragments in this module otherwise have to make by writing the rule chain out. [Rubric §9, API & Contract Design] assesses the stability of the error contract clients consume: Room.Sort.Negative travels with the message rather than the message having to be parsed. [Rubric §15, Best Practices & Code Quality] assesses whether a constraint lives in exactly one place: the comparison itself is written once in the framework, and the module supplies only the naming.
    • +
    • Walkthrough: the class declares : NonNegativeIntRules<T> (RoomValidationRules.cs:27) and its constructor takes an Expression<Func<T, int>> selector, forwarding base(selector, "Sort", "Room.Sort.Negative") (:29-30). The base registers a single GreaterThanOrEqualTo(0) rule with the message "Sort must be greater than or equal to 0" and attaches the supplied code (CommonValidationRules.cs:125-127).
    • +
    • Why it's built this way: GreaterThanOrEqualTo(0) rather than GreaterThan(0) because zero is a legitimate "first in the list" position, which is why the framework's PositiveIntRules<T> (CommonValidationRules.cs:101-106) is the wrong base to reuse here. Sort order drives deterministic room ordering in the UI, so a negative value is rejected at the application boundary.
    • Where it's used: Included by AddRoomCommandValidator (AddRoomCommandValidator.cs:12) and UpdateRoomCommandValidator (UpdateRoomCommandValidator.cs:12), both on p => p.Sort, alongside RoomCapacityRules<T>. Both validators have a negative-sort test (CommandValidatorTests.cs:92, :137).
    • Caveats / not-in-source: nothing in EventInvariants mirrors this rule, so unlike room name and room capacity the sort order has no domain-side backstop: this fragment is the only guard on the path.
    @@ -1742,12 +1778,12 @@

    EventNameRules<T>

    • What it is: a reusable rule fragment for the event name: non-empty and bounded by EventInvariants.NameMaxLength, which resolves to EventDTO.NameMaxLength, 500 characters (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:17, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Events/EventDTO.cs:19).
    • -
    • Depends on: RequiredStringRules<T>, its base class from the framework (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:41), and EventInvariants for the bound, which in turn re-exports the constant from EventDTO.
    • -
    • Concept: the same fragment idiom taught on ActivityEventIdRules<T>, but taken to its terse extreme: instead of writing a rule chain, this fragment subclasses the framework's shared RequiredStringRules<T> and passes it a field label and a bound, delegating the NotEmpty plus MaximumLength wiring to the base (CommonValidationRules.cs:43-46). [Rubric §4, DDD] assesses ubiquitous language in code: the class is named EventNameRules after the domain field, not a generic "NameValidator". [Rubric §15, Best Practices & Code Quality] assesses reuse across repos: the module writes two lines, because the shared base owns the message shape and the length constant lives once in the domain.
    • -
    • Walkthrough: one constructor taking an Expression<Func<T, string>> selector (:16), whose entire body is the base call base(selector, "Event Name", EventInvariants.NameMaxLength) (:17). The base produces the two messages "You must enter a Event Name" and "Event Name cannot be longer than 500 characters" (CommonValidationRules.cs:44-46).
    • +
    • Depends on: RequiredStringRules<T>, its base class from the framework (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:42), and EventInvariants for the bound, which in turn re-exports the constant from EventDTO.
    • +
    • Concept: the same fragment idiom taught on ActivityEventIdRules<T>, but taken to its terse extreme: instead of writing a rule chain, this fragment subclasses the framework's shared RequiredStringRules<T> and passes it a field label and a bound, delegating the NotEmpty plus MaximumLength wiring to the base (CommonValidationRules.cs:44-47). [Rubric §4, DDD] assesses ubiquitous language in code: the class is named EventNameRules after the domain field, not a generic "NameValidator". [Rubric §15, Best Practices & Code Quality] assesses reuse across repos: the module writes two lines, because the shared base owns the message shape and the length constant lives once in the domain.
    • +
    • Walkthrough: one constructor taking an Expression<Func<T, string>> selector (:16), whose entire body is the base call base(selector, "Event Name", EventInvariants.NameMaxLength) (:17). The base produces the two messages "You must enter a Event Name" and "Event Name cannot be longer than 500 characters" (CommonValidationRules.cs:45-47).
    • Why it's built this way: the length constant is the same value the domain-side invariant enforces (EventInvariants.EnsureNameIsValid, EventInvariants.cs:71-74, error codes Event.Name.Empty and Event.Name.TooLong) and the same one the EF configuration and the form field use, so the validation message, the aggregate guard, and the schema cannot drift apart.
    • Where it's used: Included by EventFieldRules<T> on p => p.Name (EventValidationRules.cs:178), which reaches both EventCreateRequestValidator and EventUpdateRequestValidator. The empty-name case is pinned at EventCreateRequestValidatorTests.cs:33.
    • -
    • Caveats / not-in-source: this fragment passes no errorCode to the base, even though the base accepts one (CommonValidationRules.cs:43), so the event name yields a human message with no machine-readable code, unlike RoomNameRules<T> or RoomSortRules<T>. The base message also reads "You must enter a Event Name", an article-agreement artifact of building the message from the field label.
    • +
    • Caveats / not-in-source: this fragment passes no errorCode to the base, even though the base accepts one (CommonValidationRules.cs:44), so the event name yields a human message with no machine-readable code, unlike RoomNameRules<T> or RoomSortRules<T>. The base message also reads "You must enter a Event Name", an article-agreement artifact of building the message from the field label.

    EventOrganizerContactEmailRules<T>

    @@ -1755,10 +1791,10 @@

    EventOrganizerContactEmailRules<T&g

    • What it is: a rule fragment for the event's optional organizer contact email. When the caller supplies a value it must be a well-formed email address no longer than EventInvariants.OrganizerContactEmailMaxLength, 255 characters (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:38, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Events/EventDTO.cs:40); when the caller leaves it blank, no rule runs at all.
    • -
    • Depends on: EmailRules<T>, the shared framework fragment it wraps (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:64-70), and EventInvariants. It inherits AbstractValidator<T> directly and uses System.Linq.Expressions.Expression<>.
    • -
    • Concept, conditional inclusion of a required-field fragment. The fragments seen so far either always apply (EventNameRules<T>) or are unconditionally length-only (RoomFloorRules<T>). This one is different: the shared EmailRules<T> it wants to reuse starts with NotEmpty (CommonValidationRules.cs:67), which is exactly wrong for an optional field. Rather than fork a near-copy of the shared fragment, the constructor compiles the selector once (var accessor = selector.Compile();, EventValidationRules.cs:64) and wraps the whole Include in FluentValidation's When(...), so the required-email rules are only registered against instances that actually carry a value (:64-65). [Rubric §1, SOLID] assesses open/closed adherence: optionality is composed around the shared rule, never by modifying it. [Rubric §24, Forms, Validation & UX Safety] assesses whether validation matches the field's real optionality: an organizer who never fills the field sees no error, while a typo in a filled field is still rejected as a bad address.
    • -
    • Walkthrough: the constructor takes an Expression<Func<T, string>> selector (:60), compiles it to a delegate (:62), then calls When(x => !string.IsNullOrWhiteSpace(accessor(x)), () => Include(new EmailRules<T>(selector, "Organizer Contact Email", EventInvariants.OrganizerContactEmailMaxLength))) (:64-65). Inside the guard the shared base contributes three chained rules: NotEmpty, EmailAddress, and MaximumLength, all with messages built from the "Organizer Contact Email" field label (CommonValidationRules.cs:66-69).
    • -
    • Why it's built this way: the field's doc comment states the product reason (:51-55): the value is optional, and an empty value means the public page falls back to the configured support address rather than showing nothing. That fallback is real. PublicEventDetail seeds _supportEmail from Configuration["Support:Email"] (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventDetail.razor.cs:43, :56) and, once the event loads, keeps the configured address when Event.OrganizerContactEmail is blank and uses the event's own address otherwise (:142-144). Rejecting a blank value at the boundary would break that intended default.
    • +
    • Depends on: EmailRules<T>, the shared framework fragment it wraps (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:65-71), and EventInvariants. It inherits AbstractValidator<T> directly and uses System.Linq.Expressions.Expression<>.
    • +
    • Concept, conditional inclusion of a required-field fragment. The fragments seen so far either always apply (EventNameRules<T>) or are unconditionally length-only (RoomFloorRules<T>). This one is different: the shared EmailRules<T> it wants to reuse starts with NotEmpty (CommonValidationRules.cs:68), which is exactly wrong for an optional field. Rather than fork a near-copy of the shared fragment, the constructor compiles the selector once (var accessor = selector.Compile();, EventValidationRules.cs:64) and wraps the whole Include in FluentValidation's When(...), so the required-email rules are only registered against instances that actually carry a value (:64-65). [Rubric §1, SOLID] assesses open/closed adherence: optionality is composed around the shared rule, never by modifying it. [Rubric §24, Forms, Validation & UX Safety] assesses whether validation matches the field's real optionality: an organizer who never fills the field sees no error, while a typo in a filled field is still rejected as a bad address.
    • +
    • Walkthrough: the constructor takes an Expression<Func<T, string>> selector (:60), compiles it to a delegate (:62), then calls When(x => !string.IsNullOrWhiteSpace(accessor(x)), () => Include(new EmailRules<T>(selector, "Organizer Contact Email", EventInvariants.OrganizerContactEmailMaxLength))) (:64-65). Inside the guard the shared base contributes three chained rules: NotEmpty, EmailAddress, and MaximumLength, all with messages built from the "Organizer Contact Email" field label (CommonValidationRules.cs:67-70).
    • +
    • Why it's built this way: the field's doc comment states the product reason (:51-55): the value is optional, and an empty value means the public page falls back to the configured support address rather than showing nothing. That fallback is real. PublicEventDetail seeds _supportEmail from Configuration["Support:Email"] (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventDetail.razor.cs:42, :56) and, once the event loads, keeps the configured address when Event.OrganizerContactEmail is blank and uses the event's own address otherwise (:142-144). Rejecting a blank value at the boundary would break that intended default.
    • Where it's used: Included by EventFieldRules<T> as p => p.OrganizerContactEmail! (EventValidationRules.cs:182), so it reaches both EventCreateRequestValidator and EventUpdateRequestValidator. Blank, valid, malformed, and overlong cases each have a test (EventCreateRequestValidatorTests.cs:83, :93, :103, :113).
    • Caveats / not-in-source: the selector type is non-nullable string while the underlying interface member is string? (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/Validation/IEventFieldsRequest.cs:30), which is why the call site passes the null-forgiving p => p.OrganizerContactEmail!. The ! only silences the compiler; the runtime null is handled by the When guard, and that combination is what makes it safe. There is also no domain-side invariant for this field: EventInvariants defines the length constant (:37) and the EF configuration applies it to the column (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Events/EventConfiguration.cs:60-62), but no Ensure... method validates the address, so this fragment is the only format check on the path.
    @@ -1768,12 +1804,12 @@

    EventSponsorshipPacketUrlRules<T>
    • What it is: a rule fragment for the event's optional sponsorship-packet URL. When a value is supplied it must be no longer than EventInvariants.SponsorshipPacketUrlMaxLength, 2000 characters (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:41, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Events/EventDTO.cs:43), and an absolute http or https URL.
    • -
    • Depends on: AbsoluteUrlRules<T>, the shared framework fragment it wraps (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:85-92), and EventInvariants. It inherits AbstractValidator<T> directly. Transitively it depends on CommonInvariants, because the framework fragment delegates its scheme predicate to CommonInvariants.EnsureUrlIsWellFormed (CommonValidationRules.cs:91-92).
    • -
    • Concept, the scheme check as a rendering-safety rule. Structurally this is the same conditional-inclusion shape taught on EventOrganizerContactEmailRules<T>: compile the selector once, then Include a shared fragment inside a When guard. What it adds is which fragment it wraps. A length-only bound accepts javascript:alert(1) and data:application/pdf;base64,..., and those values become executable the moment a link or an image renders them; the shared fragment's remarks say exactly that (CommonValidationRules.cs:76-83). AbsoluteUrlRules<T> therefore adds Must(BeAnAbsoluteHttpUrl) on top of the same MaximumLength, and the predicate resolves through CommonInvariants.EnsureUrlIsWellFormed (MMCA.Common/Source/Core/MMCA.Common.Domain/Invariants/CommonInvariants.cs:293-297), whose private IsAbsoluteHttpUrl requires Uri.TryCreate(url, UriKind.Absolute, ...) to succeed with an ordinal scheme match against http or https (:436-439). [Rubric §11, Security] assesses whether untrusted input is constrained before it reaches a rendering surface: the string is validated as a string, deliberately, before anything turns it into a Uri (the suppression justification at CommonInvariants.cs:289-292 spells that reasoning out). [Rubric §26, Front-End Security] assesses the same question from the browser's side: the value ends up in an Href, so a non-http scheme reaching persistence would be a stored script vector.
    • -
    • Walkthrough: the constructor takes an Expression<Func<T, string?>> selector (:80), note the nullable string? here as against the non-nullable selector of its email sibling, compiles it (:82), and registers When(x => !string.IsNullOrWhiteSpace(accessor(x)), () => Include(new AbsoluteUrlRules<T>(selector, "Sponsorship Packet URL", EventInvariants.SponsorshipPacketUrlMaxLength))) (:84-85). The shared base contributes two rules: MaximumLength with the message "Sponsorship Packet URL cannot be longer than 2000 characters", then Must(BeAnAbsoluteHttpUrl) with the message "Sponsorship Packet URL must be an absolute http or https URL" (CommonValidationRules.cs:87-89).
    • -
    • Why it's built this way: the doc comment gives both reasons (:69-75). The product reason is that the field is optional, and an empty value means the landing page and the public sponsor page hide the sponsorship call to action rather than rendering a dead link: PublicSponsorList renders its download button only when the URL is non-blank (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Sponsors/PublicSponsorList.razor:29, :36, with the value loaded at PublicSponsorList.razor.cs:63) and the landing page guards its own call to action the same way (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Home/ADCHome.razor:315, :327). The safety reason is the scheme check described above. Wrapping the shared fragment rather than re-declaring the rules keeps the validator and the framework's URL invariant answering identically.
    • +
    • Depends on: AbsoluteUrlRules<T>, the shared framework fragment it wraps (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:86-93), and EventInvariants. It inherits AbstractValidator<T> directly. Transitively it depends on CommonInvariants, because the framework fragment delegates its scheme predicate to CommonInvariants.EnsureUrlIsWellFormed (CommonValidationRules.cs:92-93).
    • +
    • Concept, the scheme check as a rendering-safety rule. Structurally this is the same conditional-inclusion shape taught on EventOrganizerContactEmailRules<T>: compile the selector once, then Include a shared fragment inside a When guard. What it adds is which fragment it wraps. A length-only bound accepts javascript:alert(1) and data:application/pdf;base64,..., and those values become executable the moment a link or an image renders them; the shared fragment's remarks say exactly that (CommonValidationRules.cs:77-84). AbsoluteUrlRules<T> therefore adds Must(BeAnAbsoluteHttpUrl) on top of the same MaximumLength, and the predicate resolves through CommonInvariants.EnsureUrlIsWellFormed (MMCA.Common/Source/Core/MMCA.Common.Domain/Invariants/CommonInvariants.cs:293-297), whose private IsAbsoluteHttpUrl requires Uri.TryCreate(url, UriKind.Absolute, ...) to succeed with an ordinal scheme match against http or https (:436-439). [Rubric §11, Security] assesses whether untrusted input is constrained before it reaches a rendering surface: the string is validated as a string, deliberately, before anything turns it into a Uri (the suppression justification at CommonInvariants.cs:289-292 spells that reasoning out). [Rubric §26, Front-End Security] assesses the same question from the browser's side: the value ends up in an Href, so a non-http scheme reaching persistence would be a stored script vector.
    • +
    • Walkthrough: the constructor takes an Expression<Func<T, string?>> selector (:80), note the nullable string? here as against the non-nullable selector of its email sibling, compiles it (:82), and registers When(x => !string.IsNullOrWhiteSpace(accessor(x)), () => Include(new AbsoluteUrlRules<T>(selector, "Sponsorship Packet URL", EventInvariants.SponsorshipPacketUrlMaxLength))) (:84-85). The shared base contributes two rules: MaximumLength with the message "Sponsorship Packet URL cannot be longer than 2000 characters", then Must(BeAnAbsoluteHttpUrl) with the message "Sponsorship Packet URL must be an absolute http or https URL" (CommonValidationRules.cs:88-90).
    • +
    • Why it's built this way: the doc comment gives both reasons (:69-75). The product reason is that the field is optional, and an empty value means the landing page and the public sponsor page hide the sponsorship call to action rather than rendering a dead link: PublicSponsorList renders its download button only when the URL is non-blank (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Sponsors/PublicSponsorList.razor:29, :36, with the value loaded at PublicSponsorList.razor.cs:79) and the landing page guards its own call to action the same way (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Home/ADCHome.razor:315, :327). The safety reason is the scheme check described above. Wrapping the shared fragment rather than re-declaring the rules keeps the validator and the framework's URL invariant answering identically.
    • Where it's used: Included by EventFieldRules<T> on p => p.SponsorshipPacketUrl (EventValidationRules.cs:183), reaching both EventCreateRequestValidator and EventUpdateRequestValidator. The scheme rule is pinned by a theory that feeds it a scheme-less host and a data: payload (EventCreateRequestValidatorTests.cs:218-221), and the optional and overlong cases at :130 and :153.
    • -
    • Caveats / not-in-source: the wrapped base sets no WithErrorCode because this fragment passes none (CommonValidationRules.cs:87), so a client keying off error codes gets nothing for this field. Neither is there a domain-side backstop: no Ensure... method in the Conference module calls EnsureUrlIsWellFormed, so a caller that bypasses the validator can still persist a non-http value within the length bound, subject only to the EF column length (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Events/EventConfiguration.cs:65-66).
    • +
    • Caveats / not-in-source: the wrapped base sets no WithErrorCode because this fragment passes none (CommonValidationRules.cs:88), so a client keying off error codes gets nothing for this field. Neither is there a domain-side backstop: no Ensure... method in the Conference module calls EnsureUrlIsWellFormed, so a caller that bypasses the validator can still persist a non-http value within the length bound, subject only to the EF column length (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Events/EventConfiguration.cs:65-66).

    EventTicketingUrlRules<T>

    @@ -1781,9 +1817,9 @@

    EventTicketingUrlRules<T>

    • What it is: a rule fragment for the event's optional ticketing URL, bounded to EventInvariants.TicketingUrlMaxLength, 2000 characters (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:44, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Events/EventDTO.cs:46) and required to be an absolute http or https URL when supplied. It is the exact twin of its sponsorship sibling, one field over.
    • -
    • Depends on: AbsoluteUrlRules<T> (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:85-92) and EventInvariants. It inherits AbstractValidator<T> directly.
    • +
    • Depends on: AbsoluteUrlRules<T> (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:86-93) and EventInvariants. It inherits AbstractValidator<T> directly.
    • Concept: the conditional-inclusion shape taught on EventOrganizerContactEmailRules<T> and the scheme check taught on EventSponsorshipPacketUrlRules<T>, repeated verbatim. Reading the three optional event fragments in a row (:57, :77, :97) is the clearest illustration of the module's convention: an optional field gets a compiled accessor, a When presence guard, and an Include of a shared framework fragment, and the only things that vary between them are which framework fragment is wrapped and which invariant constant bounds it. [Rubric §2, Design Patterns] assesses whether a recurring shape is expressed as a reusable composition rather than duplicated logic: the wrapping is identical, only the parameters change.
    • -
    • Walkthrough: the constructor takes an Expression<Func<T, string?>> selector (:100), compiles it to a delegate (:102), and registers When(x => !string.IsNullOrWhiteSpace(accessor(x)), () => Include(new AbsoluteUrlRules<T>(selector, "Ticketing URL", EventInvariants.TicketingUrlMaxLength))) (:104-105). The shared base contributes the MaximumLength rule whose message is "Ticketing URL cannot be longer than 2000 characters" and the Must(BeAnAbsoluteHttpUrl) rule whose message is "Ticketing URL must be an absolute http or https URL" (CommonValidationRules.cs:87-89).
    • +
    • Walkthrough: the constructor takes an Expression<Func<T, string?>> selector (:100), compiles it to a delegate (:102), and registers When(x => !string.IsNullOrWhiteSpace(accessor(x)), () => Include(new AbsoluteUrlRules<T>(selector, "Ticketing URL", EventInvariants.TicketingUrlMaxLength))) (:104-105). The shared base contributes the MaximumLength rule whose message is "Ticketing URL cannot be longer than 2000 characters" and the Must(BeAnAbsoluteHttpUrl) rule whose message is "Ticketing URL must be an absolute http or https URL" (CommonValidationRules.cs:88-90).
    • Why it's built this way: the doc comment states the product reason (:89-95): the field is optional, and an empty value means the landing page and the public event page hide the ticketing call to action. Both sites guard on the value being non-blank before rendering a button, the landing page at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Home/ADCHome.razor:71-75 and the public event page at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventDetail.razor:114-118, so a blank value is a supported product state rather than a validation failure. The 2000-character bound is the same constant the EF configuration applies to the column (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Events/EventConfiguration.cs:69-70).
    • Where it's used: Included by EventFieldRules<T> on p => p.TicketingUrl (EventValidationRules.cs:184), reaching both EventCreateRequestValidator and EventUpdateRequestValidator. A theory pins the rejection of a scheme-less host, a site-relative path, and a javascript: payload (EventCreateRequestValidatorTests.cs:205-209).
    • Caveats / not-in-source: as with the sponsorship URL, no WithErrorCode is attached because the wrapped base is given none. Note also that the landing page's pre-conference ticketing button is a separate, hard-coded constant (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Home/ADCHome.razor.cs:34, used at ADCHome.razor:154) and does not flow through this field or this rule.
    • @@ -1807,8 +1843,8 @@

      RoomAccessibilityInfoRules<T>

      • What it is: a rule fragment bounding a room's optional accessibility-info text to EventInvariants.RoomAccessibilityInfoMaxLength, 500 characters (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:56, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Rooms/RoomDTO.cs:25).
      • -
      • Depends on: OptionalStringRules<T>, its base class from the framework (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:53), and EventInvariants, which re-exports the constant from RoomDTO.
      • -
      • Concept: the same subclass-a-framework-fragment idiom taught on RoomSortRules<T>, applied to the optional-string case. Optionality is expressed in the base's own shape: OptionalStringRules<T> declares MaximumLength and nothing else, so a null value passes with no guard needed (CommonValidationRules.cs:55-57). [Rubric §15, Best Practices & Code Quality] assesses whether a constraint lives in exactly one place: the rule is written once in the framework, the bound once in the domain, and the module contributes only a label and a code.
      • +
      • Depends on: OptionalStringRules<T>, its base class from the framework (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:54), and EventInvariants, which re-exports the constant from RoomDTO.
      • +
      • Concept: the same subclass-a-framework-fragment idiom taught on RoomSortRules<T>, applied to the optional-string case. Optionality is expressed in the base's own shape: OptionalStringRules<T> declares MaximumLength and nothing else, so a null value passes with no guard needed (CommonValidationRules.cs:56-58). [Rubric §15, Best Practices & Code Quality] assesses whether a constraint lives in exactly one place: the rule is written once in the framework, the bound once in the domain, and the module contributes only a label and a code.
      • Walkthrough: the class declares : OptionalStringRules<T> (RoomValidationRules.cs:76) and forwards base(selector, "Accessibility Info", EventInvariants.RoomAccessibilityInfoMaxLength, "Room.AccessibilityInfo.MaxLength") from a constructor taking an Expression<Func<T, string?>> (:78-79). The base emits the message "Accessibility Info cannot be longer than 500 characters" carrying the supplied code.
      • Why it's built this way: accessibility notes are free text an organizer may not have yet, so absence is valid; only the length is constrained, using the same constant the domain and the persistence configuration share. Passing the fourth errorCode argument is what keeps the field machine-addressable despite the rule being inherited.
      • Where it's used: Included by AddRoomCommandValidator (AddRoomCommandValidator.cs:16) and UpdateRoomCommandValidator (UpdateRoomCommandValidator.cs:16), both on p => p.AccessibilityInfo; the overlong case is pinned at MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/Validation/CommandValidatorTests.cs:161.
      • @@ -1819,7 +1855,7 @@

        RoomFloorRules<T>

        • What it is: a rule fragment bounding a room's optional floor label to EventInvariants.RoomFloorMaxLength, 100 characters (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:50, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Rooms/RoomDTO.cs:19).
        • -
        • Depends on: OptionalStringRules<T> (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:53) and EventInvariants.
        • +
        • Depends on: OptionalStringRules<T> (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:54) and EventInvariants.
        • Concept: structurally identical to RoomAccessibilityInfoRules<T>: a nullable string? selector, one inherited MaximumLength rule, no NotEmpty.
        • Walkthrough: : OptionalStringRules<T> (RoomValidationRules.cs:52) with the constructor forwarding base(selector, "Floor", EventInvariants.RoomFloorMaxLength, "Room.Floor.MaxLength") (:54-55).
        • Why it's built this way: a floor is a label ("2", "Mezzanine"), not a required attribute of a room, so the fragment constrains only its length.
        • @@ -1831,7 +1867,7 @@

          RoomLocationRules<T>

          • What it is: a rule fragment bounding a room's optional location text to EventInvariants.RoomLocationMaxLength, 255 characters (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:53, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Rooms/RoomDTO.cs:22).
          • -
          • Depends on: OptionalStringRules<T> (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:53) and EventInvariants.
          • +
          • Depends on: OptionalStringRules<T> (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:54) and EventInvariants.
          • Concept: structurally identical to RoomFloorRules<T>. Reading the three optional room fragments together (:51, :63, :75) shows why the family exists at all: each is two lines, and the only things that vary are the invariant constant, the message noun, and the error code. [Rubric §2, Design Patterns] assesses whether a recurring shape is expressed as a reusable composition: the shared base owns the rule, and each field owns only its naming.
          • Walkthrough: : OptionalStringRules<T> (RoomValidationRules.cs:64) with the constructor forwarding base(selector, "Location", EventInvariants.RoomLocationMaxLength, "Room.Location.MaxLength") (:66-67).
          • Where it's used: Included by AddRoomCommandValidator (AddRoomCommandValidator.cs:15) and UpdateRoomCommandValidator (UpdateRoomCommandValidator.cs:15), both on p => p.Location; the overlong case is pinned at CommandValidatorTests.cs:153.
          • @@ -1843,7 +1879,7 @@

            RoomNameRules<T>

            • What it is: a rule fragment for a room's name: non-empty and bounded by EventInvariants.RoomNameMaxLength, 255 characters (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:47, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Rooms/RoomDTO.cs:16). It is the one required field among the room fragments.
            • Depends on: EventInvariants. It inherits AbstractValidator<T> directly rather than a framework base.
            • -
            • Concept: the same shape as EventNameRules<T>, but written out on AbstractValidator<T> rather than derived from RequiredStringRules<T>, even though the base would fit. Writing the chain locally buys two distinct error codes, one per bound, which the base cannot express: its single optional errorCode is applied to every rule it declares (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:11-18, :43-46), so a field whose bounds must answer under different codes still declares its own rules. [Rubric §9, API & Contract Design] assesses the stability of the error contract clients consume: Room.Name.Required and Room.Name.MaxLength are separately addressable, whereas the event name yields a message only.
            • +
            • Concept: the same shape as EventNameRules<T>, but written out on AbstractValidator<T> rather than derived from RequiredStringRules<T>, even though the base would fit. Writing the chain locally buys two distinct error codes, one per bound, which the base cannot express: its single optional errorCode is applied to every rule it declares (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:12-19, :43-46), so a field whose bounds must answer under different codes still declares its own rules. [Rubric §9, API & Contract Design] assesses the stability of the error contract clients consume: Room.Name.Required and Room.Name.MaxLength are separately addressable, whereas the event name yields a message only.
            • Walkthrough: the constructor takes a single Expression<Func<T, string>> selector (:16) and chains NotEmpty with the message "You must enter a Room Name" and the code Room.Name.Required (:17-18), then MaximumLength(EventInvariants.RoomNameMaxLength) with the code Room.Name.MaxLength (:19).
            • Why it's built this way: the required name is what distinguishes this fragment from the three optional room fields; keeping each field as its own fragment lets a command validator compose exactly the mix it needs, which is what the two room validators do line by line.
            • Where it's used: Included by AddRoomCommandValidator (AddRoomCommandValidator.cs:11) and UpdateRoomCommandValidator (UpdateRoomCommandValidator.cs:11), both on p => p.Name; empty and overlong cases are pinned at CommandValidatorTests.cs:75, :84, and :129.
            • @@ -1908,7 +1944,7 @@

              QuestionUpdateRequest

            • Concept introduced: a field that is in the contract but only conditionally editable. QuestionEntity (:10) and QuestionType (:13) are plain required string members here, so the wire contract accepts a new value for either. Whether that value is allowed is not a property of the record: UpdateQuestionHandler probes the three answer tables and rejects the change once any answer exists (BR-137, UpdateQuestionHandler.cs:41-75). Contrast this with a field removed from a request entirely, which is how the module expresses "never editable through this path". The distinction tells you where to look for a rule: a shape constraint lives in the record, a state-dependent constraint cannot, because the record has no access to the database. [Rubric §4, DDD] assesses whether rules live where the knowledge to enforce them lives. [Rubric §9, API & Contract Design] assesses contract uniformity: the payload stays the same between create and update, and the difference surfaces as a validation error with a stable code rather than as a missing property.
            • Walkthrough: QuestionText (:7), QuestionEntity (:10, documented as "Session" or "Event"), and QuestionType (:13, documented as "Rating", "Text", or "Email") are required string with init setters. Sort (:16) and IsRequired (:19) are plain value members that default to 0 and false. Note the near-miss in naming: IsRequired is the survey question's own "an attendee must answer this" flag, not the C# required modifier that governs three of its siblings. There is no RowVersion member: the concurrency token travels beside the payload on UpdateQuestionCommand (UpdateQuestionCommand.cs:14), read from the If-Match header rather than from the body.
            • Why it's built this way: the two discriminator strings are free-form string, not enums, so adding a question type or a new target entity does not require a change to the contract type; the legal values are asserted in the domain instead (QuestionInvariants, called from MMCA.ADC.Conference.Domain/Questions/Question.cs:116-118).
            • -
            • Where it's used: bound by QuestionsController on PUT {id} (MMCA.ADC.Conference.API/Controllers/Questions/QuestionsController.cs:120), validated by QuestionUpdateRequestValidator, wrapped in UpdateQuestionCommand (:125), and consumed by UpdateQuestionHandler.
            • +
            • Where it's used: bound by QuestionsController on PUT {id} (MMCA.ADC.Conference.API/Controllers/Questions/QuestionsController.cs:123), validated by QuestionUpdateRequestValidator, wrapped in UpdateQuestionCommand (:125), and consumed by UpdateQuestionHandler.

            DeleteSessionAssetCommandValidator

            @@ -1956,7 +1992,7 @@

            UpdateQuestionCommand

          • Concept introduced: the concurrency token as a command member, not a body member. The token is a third positional parameter (:14) rather than a field on the request record, and its doc comment states both where it comes from and that it is not optional: the caller's last-observed version, read from the If-Match header, on an endpoint where "a conditional update that states no precondition never reaches this command" (:9-13). [Rubric §9, API & Contract Design] assesses where a precondition belongs: If-Match is an HTTP-level precondition, so it is lifted off the body by the controller and handed to the command as data, keeping the JSON payload free of transport concerns. [Rubric §8, Data Architecture] assesses concurrency control: this is the ADR-035 optimistic-concurrency contract (Website/docs-src/adr/035-optimistic-concurrency.md), enforced by the base handler rather than by this record.
          • Walkthrough: sealed record UpdateQuestionCommand(QuestionIdentifierType Id, QuestionUpdateRequest Request, byte[] RowVersion) with both interfaces on the declaration (:14), and one computed member, CachePrefix => $"{typeof(Question).FullName}:" (:17). The prefix is what the cache-invalidating decorator uses to evict every cached read of the question aggregate after a successful write.
          • Why it's built this way: the type is byte[], not string, because that is the shape EF Core stamps back onto the tracked entity; encoding and decoding the ETag is the API layer's job (SupportsIfMatchAttribute). Declaring the parameter non-nullable states the endpoint's contract in the type system: an unconditional caller is rejected at the boundary with 428, never here.
          • -
          • Where it's used: constructed by QuestionsController on PUT {id} (MMCA.ADC.Conference.API/Controllers/Questions/QuestionsController.cs:126), with the token pulled from the request by SupportsIfMatchAttribute.RequiredToken(HttpContext) (:122), against the injected ICommandHandler<UpdateQuestionCommand, Result<QuestionDTO>> (:37), and handled by UpdateQuestionHandler.
          • +
          • Where it's used: constructed by QuestionsController on PUT {id} (MMCA.ADC.Conference.API/Controllers/Questions/QuestionsController.cs:129), with the token pulled from the request by SupportsIfMatchAttribute.RequiredToken(HttpContext) (:122), against the injected ICommandHandler<UpdateQuestionCommand, Result<QuestionDTO>> (:37), and handled by UpdateQuestionHandler.

          GetSessionAssetsHandler

          @@ -1980,7 +2016,7 @@

          GetPublicPartnerFilterHandler

        • Concept: the query-that-returns-a-specification shape is taught under GetPublicSessionFilterHandler: a handler whose result is a reusable predicate rather than data, so the visibility rule is resolved once in the Application layer and applied by whichever read the controller is serving. [Rubric §6, CQRS and Event-Driven] assesses whether reads are expressed as explicit, single-purpose query objects; this is a query whose payload is the filter itself. [Rubric §11, Security] assesses the anonymous read surface: the rule here is one line of predicate, and it is the only thing standing between an unpublished event's sponsor roster and an anonymous caller.
        • Walkthrough: HandleAsync (:20-30) resolves the published event ids via PublicConferenceVisibility.GetPublishedEventIdsAsync(unitOfWork, cancellationToken) (:24-26), a scalar, untracked projection of Event.Id where IsPublished, materialized once so the list embedded in the predicate is stable and EF-translatable to IN. It then wraps p => publishedEventIds.Contains(p.EventId) in an InlineSpecification<TEntity, TIdentifierType> and returns Result.Success (:28-29). Nothing here can fail, so Result is used for pipeline uniformity rather than to carry an error.
        • Why it's built this way: Partner carries a real EventId column, so its parent's visibility is expressible directly on the child's own column with no traversal and no join table, mirroring GetPublicRoomFilterHandler and the other public-filter handlers in this group. Sourcing the id list from the shared PublicConferenceVisibility rather than restating IsPublished here is what keeps one definition of "published" behind every public read.
        • -
        • Where it's used: PartnersController injects it as an IQueryHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Partners/PartnersController.cs:46) and calls it from its GetReadSpecificationAsync override (PartnersController.cs:66-76), which short-circuits to null when IsPrivileged (:69-70, backed by currentUserService.IsPrivilegedConferenceReader() at :57), so Organizer and ContentEditor readers see every partner, including those of events still being assembled. That override is the framework's read hook declared on EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>, so overriding it once scopes all of the controller's [AllowAnonymous] reads: the unpaged list (PartnersController.cs:81-86), the paged list (:92-105), and the by-id read (:118-127); the lookup (:107-113) inherits no read-specification hook and stays unscoped. The export action denies non-privileged callers outright with Forbid() (:144-147) rather than serving an unfiltered roster (BR-108).
        • +
        • Where it's used: PartnersController injects it as an IQueryHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Partners/PartnersController.cs:46) and calls it from its GetReadSpecificationAsync override (PartnersController.cs:66-76), which short-circuits to null when IsPrivileged (:69-70, backed by currentUserService.IsPrivilegedConferenceReader() at :57), so Organizer and ContentEditor readers see every partner, including those of events still being assembled. That override is the framework's read hook declared on EntityControllerBase<TEntity, TEntityDTO, TIdentifierType>, so overriding it once scopes all of the controller's [AllowAnonymous] reads: the unpaged list (PartnersController.cs:89-94), the paged list (:92-105), and the by-id read (:118-127); the lookup (:107-113) inherits no read-specification hook and stays unscoped. The export action denies non-privileged callers outright with Forbid() (:144-147) rather than serving an unfiltered roster (BR-108).
        • Caveats / not-in-source: the override maps a failed Result to null, that is, to no filter at all (PartnersController.cs:75), the same fail-open shape as the other public-filter overrides in this module. No path in this handler produces a failure today.

        UpdateQuestionHandler

        @@ -1998,7 +2034,7 @@

        UpdateQuestionHandler

      • LogMutated (:86-87) is the post-save logging hook, calling the [LoggerMessage] partial LogQuestionUpdated declared at :89-90. The base calls it only after a successful save (MutateEntityHandlerBase.cs:318).
      -
    • Why it's built this way: the refusal is an Error.Validation with a stable code rather than a thrown exception, so it travels the same Result channel as a FluentValidation failure and the controller's shared HandleFailure turns it into a client-error response with no special case (MMCA.ADC.Conference.API/Controllers/Questions/QuestionsController.cs:128-129). Overriding MutateAsync rather than HandleAsync is what keeps the ADR-035 concurrency stamp and the DTO mapping identical across every update handler in the module.
    • +
    • Why it's built this way: the refusal is an Error.Validation with a stable code rather than a thrown exception, so it travels the same Result channel as a FluentValidation failure and the controller's shared HandleFailure turns it into a client-error response with no special case (MMCA.ADC.Conference.API/Controllers/Questions/QuestionsController.cs:131-132). Overriding MutateAsync rather than HandleAsync is what keeps the ADR-035 concurrency stamp and the DTO mapping identical across every update handler in the module.
    • Where it's used: injected into QuestionsController as ICommandHandler<UpdateQuestionCommand, Result<QuestionDTO>> (MMCA.ADC.Conference.API/Controllers/Questions/QuestionsController.cs:38) and invoked on PUT {id} (:112-132), after which the controller evicts the conference:questions output-cache tag (:131).
    • Caveats / not-in-source: the BR-137 check is a read followed by a write with no lock between them, so an answer submitted in the window between the probe and the save is not caught. The row version protects the question row, not the answer tables. Whether that race has ever occurred in practice is not determinable from source.
    @@ -2163,7 +2199,7 @@

    BatchAddSessionQuestionAnswersCom
  • Concept introduced: ITransactional as an all-or-nothing declaration on the message. The module's decorator pipeline reads markers off the command type rather than off the handler: TransactionalCommandDecorator<TCommand, TResult> is registered innermost of the command decorators (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:134) and opens a transaction only for commands carrying ITransactional. Adding the marker here is therefore the entire mechanism by which a partially applied form becomes unobservable, and the doc comment says exactly that (:17-18). [Rubric §6, CQRS & Event-Driven] assesses whether cross-cutting behavior is declared on the message and applied by the pipeline rather than hand-rolled in each handler: the handler contains no transaction code at all. [Rubric §8, Data Architecture] assesses write atomicity: one transaction plus one SaveChangesAsync means an accepted form is one durable unit, which also keeps the outbox rows this write produces in the same commit (ADR-003). [Rubric §12, Performance & Scalability] assesses request economy: the doc comment records the batch's real payoff, which is that the session, its owning event, and every referenced question are read once instead of once per answer (:15-17).
  • Walkthrough: two positional members (:22-24), SessionId and Answers typed as IReadOnlyList<BatchSessionQuestionAnswerItem>, with both markers on the declaration (:24) and one computed member, CachePrefix => $"{typeof(Session).FullName}:" (:27), which evicts the session aggregate's cached reads after a successful write. The class-level summary is where the semantics live: every answer is upserted the same way the single-answer command does (BR-107), and the form is accepted or refused together (:13-19).
  • Why it's built this way: the alternative, having the client loop the single-answer endpoint, gives the same rows but not the same guarantees: a network failure halfway through leaves a half-saved form the client has to reconcile, and every call repeats the session, event, and question reads. Declaring the whole form as one message moves both problems into the pipeline (ADR-014).
  • -
  • Where it's used: constructed by SessionQuestionAnswersController on POST SessionQuestionAnswers/batch (MMCA.ADC.Conference.API/Controllers/Sessions/SessionQuestionAnswersController.cs:254-272, the command built at :200-202) against the injected ICommandHandler<BatchAddSessionQuestionAnswersCommand, Result<IReadOnlyList<SessionQuestionAnswerDTO>>> (:79), validated by BatchAddSessionQuestionAnswersCommandValidator, and handled by BatchAddSessionQuestionAnswersHandler.
  • +
  • Where it's used: constructed by SessionQuestionAnswersController on POST SessionQuestionAnswers/batch (MMCA.ADC.Conference.API/Controllers/Sessions/SessionQuestionAnswersController.cs:258-276, the command built at :200-202) against the injected ICommandHandler<BatchAddSessionQuestionAnswersCommand, Result<IReadOnlyList<SessionQuestionAnswerDTO>>> (:79), validated by BatchAddSessionQuestionAnswersCommandValidator, and handled by BatchAddSessionQuestionAnswersHandler.
  • Caveats / not-in-source: the endpoint also carries IdempotentAttribute (SessionQuestionAnswersController.cs:193), so a retry with the same Idempotency-Key replays the first response (ADR-017) rather than re-applying the form. That replay contract lives on the action, not on this record: dispatching the same command twice through the handler directly would run the BR-107 upsert twice.
  • GetCategoryDistributionHandler

    @@ -2375,11 +2411,11 @@

    ExportEventCalendarQuery

  • What it is: the read request behind "add the whole conference to my calendar": one event id, answered with an RFC 5545 .ics document covering every exportable session on that event's schedule.
  • Depends on: the EventIdentifierType alias, an int in this module (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/MMCA.ADC.Conference.GlobalUsings.IdentifierType.cs:10). No other first-party types, nothing external.
  • Concept introduced: the query whose result is a document, not a DTO. Every other read in this group resolves to a shaped DTO; this one resolves to Result<string> where the string is a complete calendar file (ExportEventCalendarHandler.cs:18). The query/handler split itself is taught by IQueryHandler<in TQuery, TResult> and is cross-referenced rather than re-taught. - [Rubric §9, API & Contract Design] assesses whether a contract matches the representation its consumer needs: a calendar client wants text/calendar bytes, so the use case produces the serialized document and the controller only wraps it in a File(...) response (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:155-158). + [Rubric §9, API & Contract Design] assesses whether a contract matches the representation its consumer needs: a calendar client wants text/calendar bytes, so the use case produces the serialized document and the controller only wraps it in a File(...) response (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:162-165). [Rubric §6, CQRS & Event-Driven] applies as for the sibling reads: a named message with no side effects, bound to exactly one handler by the generic argument.
  • Walkthrough: one positional parameter, EventId (:5), documented by the summary and <param> above it (:3-4). No body, no defaults, no marker interface.
  • Why it's built this way: the event-wide and single-session exports are genuinely different reads (one loads the whole schedule plus the room map, the other loads one session and then checks its parent), so they get separate messages instead of one query with a nullable session id. See ExportSessionCalendarQuery for the narrow twin.
  • -
  • Where it's used: constructed by EventsController on GET Events/{id}/ics, an [AllowAnonymous] action under the EventsCache output-cache policy (EventsController.cs:148-159), resolved through the injected IQueryHandler<ExportEventCalendarQuery, Result<string>> (:54). The browser-side caller is the add-to-calendar button on PublicEventDetail (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventDetail.razor:27-28).
  • +
  • Where it's used: constructed by EventsController on GET Events/{id}/ics, an [AllowAnonymous] action under the EventsCache output-cache policy (EventsController.cs:155-166), resolved through the injected IQueryHandler<ExportEventCalendarQuery, Result<string>> (:54). The browser-side caller is the add-to-calendar button on PublicEventDetail (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventDetail.razor:27-28).
  • Caveats / not-in-source: the doc comment tags the feature "ADR-042 Wave 5" (:3). ADR-042 is the MAUI device-capability abstraction (Website/docs-src/adr/042-device-capability-abstraction.md:1) and says nothing about iCalendar, so read that tag as a delivery-wave label rather than as a pointer to a specification of this export.
  • ExportSessionCalendarQuery

    @@ -2392,7 +2428,7 @@

    ExportSessionCalendarQuery

  • Concept: none new; it is the same document-producing read message as ExportEventCalendarQuery, which teaches the shape. [Rubric §5, Vertical Slice] assesses feature cohesion: both queries, both handlers, and the mapper they share sit in one ExportCalendar folder, so the whole capability is one directory.
  • Walkthrough: one positional parameter, SessionId (:5), with the summary and <param> above it (:3-4).
  • Why it's built this way: the single-session export is what a public attendee actually uses while browsing the agenda, and it enforces a stricter rule than the event export does (the session itself must be exportable, not merely present in a published event). Keeping it a separate message keeps that rule in one handler rather than as a branch inside a combined one.
  • -
  • Where it's used: constructed by SessionsController on GET Sessions/{id}/ics, [AllowAnonymous] under the SessionsCache policy (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:262-273), through the injected IQueryHandler<ExportSessionCalendarQuery, Result<string>> (:52). The UI caller is the add-to-calendar button on PublicSessionDetail (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Sessions/PublicSessionDetail.razor:38-39).
  • +
  • Where it's used: constructed by SessionsController on GET Sessions/{id}/ics, [AllowAnonymous] under the SessionsCache policy (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:270-281), through the injected IQueryHandler<ExportSessionCalendarQuery, Result<string>> (:52). The UI caller is the add-to-calendar button on PublicSessionDetail (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Sessions/PublicSessionDetail.razor:38-39).
  • ISessionScoresCacheEvictor

    @@ -2415,10 +2451,10 @@

    SessionScoringResult

  • What it is - what the AI scorer hands back for one session: seven numeric sub-scores, the model's free-text Reasoning, and a Success flag that says whether any of it means anything.
  • Depends on - the SessionIdentifierType alias (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/MMCA.ADC.Conference.GlobalUsings.IdentifierType.cs:19) and BCL decimal, string, bool. No first-party types.
  • Concept introduced - the never-throw service result. IAiScoringService contracts that scoring never throws and reports failure in the result instead (IAiScoringService.cs:9), and this record is the vehicle. The adapter's failure path builds it with every score at 0m, Reasoning = "Scoring failed", and Success = false (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Sessions/Scoring/SessionScoringService.cs:344-357), so one bad session never aborts a loop over hundreds. [Rubric §29 - Resilience & Business Continuity] assesses whether a flaky external dependency degrades one item or the whole run: here it degrades one. [Rubric §13 - Observability & Operability] assesses whether an outcome is legible after the fact: Reasoning is persisted onto SessionAiScore alongside the model id, so an organizer can see why a session scored what it scored and which model said so.
  • -
  • Walkthrough - ten required init members (:43-70): SessionId; the seven decimal scores OverallScore, TopicRelevanceScore, DescriptionQualityScore, NoveltyScore, ActionableTakeawaysScore, DepthOrInsightQualityScore, CredibilityExperienceScore, each documented as 1.0 to 10.0; Reasoning; and Success. required on all ten means no partially-populated instance can be constructed, which is what lets the handler read result.SessionId rather than the loop variable when building the entity (SessionScoringRunner.cs:82).
  • +
  • Walkthrough - ten required init members (:43-70): SessionId; the seven decimal scores OverallScore, TopicRelevanceScore, DescriptionQualityScore, NoveltyScore, ActionableTakeawaysScore, DepthOrInsightQualityScore, CredibilityExperienceScore, each documented as 1.0 to 10.0; Reasoning; and Success. required on all ten means no partially-populated instance can be constructed, which is what lets the handler read result.SessionId rather than the loop variable when building the entity (SessionScoringRunner.cs:88).
  • Why it's built this way - the 1.0 to 10.0 range in the doc comments is documentation on this record only. The invariant is enforced one layer in, by SessionAiScore.Create (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/SessionAiScore.cs:77), whose EnsureScoreInRange rejects anything outside >= 1.0m and <= 10.0m with a SessionAiScore.OutOfRange error (:134-141). Keeping the transport record permissive and the entity strict means a model that returns nonsense produces a counted failure instead of a corrupt row.
  • -
  • Where it's used - returned by IAiScoringService.ScoreSessionAsync (IAiScoringService.cs:11-13), produced by SessionScoringService and by FakeAiScoringService in the integration test host (MMCA.ADC/Tests/Integration/MMCA.ADC.Conference.IntegrationTests/Infrastructure/FakeAiScoringService.cs), consumed by SessionScoringRunner (SessionScoringRunner.cs:465-478). It is application-internal: the shape the API returns is SessionAiScoreDTO.
  • -
  • Caveats / not-in-source - a Success = false result carries all-zero scores, which SessionAiScore.Create would reject outright. Nothing in the type enforces that pairing; the handler simply never reaches Create on a failed result because it checks Success first (SessionScoringRunner.cs:74-79).
  • +
  • Where it's used - returned by IAiScoringService.ScoreSessionAsync (IAiScoringService.cs:11-13), produced by SessionScoringService and by FakeAiScoringService in the integration test host (MMCA.ADC/Tests/Integration/MMCA.ADC.Conference.IntegrationTests/Infrastructure/FakeAiScoringService.cs), consumed by SessionScoringRunner (SessionScoringRunner.cs:78-91). It is application-internal: the shape the API returns is SessionAiScoreDTO.
  • +
  • Caveats / not-in-source - a Success = false result carries all-zero scores, which SessionAiScore.Create would reject outright. Nothing in the type enforces that pairing; the handler simply never reaches Create on a failed result because it checks Success first (SessionScoringRunner.cs:80-85).
  • [Rubric §16, AI-Native Application Architecture] applies: this type is part of the AI session-scoring feature (a model call behind a port, versioned prompt and model, an evaluation gate, metered spend; ADR-111).

    SpeakerInfo

    @@ -2429,9 +2465,9 @@

    SpeakerInfo

  • What it is - the slice of a speaker that the AI model is allowed to see: full name, optional tagline, optional biography (:23-26).
  • Depends on - nothing but BCL strings. Notably it does not carry the SpeakerIdentifierType (a Guid in this module, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/MMCA.ADC.Conference.GlobalUsings.IdentifierType.cs:23), nor an email, a photo url, or any other Speaker field.
  • Concept introduced - the minimized projection at an external boundary. Everything in this record leaves the system: SessionScoringService concatenates the name, tagline, and bio into the prompt body it sends through the governed chat client (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Sessions/Scoring/SessionScoringService.cs:304-310). Building a purpose-shaped record instead of passing the entity means the set of fields that can reach a third party is a three-line declaration a reviewer can read at a glance. [Rubric §30 - Compliance/Privacy/Data Governance] assesses data minimization at a processor boundary: the identifier is deliberately absent, so what leaves is speaker-authored public bio text with no key to join it back. [Rubric §11 - Security] assesses whether such a boundary is explicit rather than incidental; here it is a type, not a convention.
  • -
  • Walkthrough - three positional members (:23-26), the last two nullable. The handler builds one per non-deleted SessionSpeaker it can resolve, from speaker.FullName, speaker.TagLine, speaker.Bio (SessionScoringRunner.cs:63-69).
  • +
  • Walkthrough - three positional members (:23-26), the last two nullable. The handler builds one per non-deleted SessionSpeaker it can resolve, from speaker.FullName, speaker.TagLine, speaker.Bio (SessionScoringRunner.cs:69-75).
  • Why it's built this way - a scoring prompt needs the speaker's credibility signals and nothing else. Widening the model would silently widen what is sent to a paid third-party API, which is the kind of change a dedicated record forces into review.
  • -
  • Where it's used - as the Speakers list on SessionScoringInput (IAiScoringService.cs:51); constructed only in SessionScoringRunner (SessionScoringRunner.cs:454-460).
  • +
  • Where it's used - as the Speakers list on SessionScoringInput (IAiScoringService.cs:51); constructed only in SessionScoringRunner (SessionScoringRunner.cs:69-75).
  • Caveats / not-in-source - the doc comments state "max 500 chars" for TagLine and "max 4000 chars" for Bio (:21-22), but nothing in this record, the handler, or the scoring adapter truncates or validates either value: the adapter XML-escapes them (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Sessions/Scoring/SessionScoringService.cs:304-310) and relies on the framework's PiiRedactionGuardrail, registered on the governed chat pipeline, to strip contact details before the call goes out, but neither step truncates. Treat those numbers as descriptive of the source fields, not as an enforced bound on the prompt. A different, unrelated SpeakerInfo exists in the Conference UI assembly (SpeakerInfo); the two only share a name.
  • SessionScoringInput

    @@ -2442,9 +2478,9 @@

    SessionScoringInput

  • What it is - everything the AI scorer is given about one session: its id, title, optional description, and the SpeakerInfo projections for its speakers (:33-37).
  • Depends on - SpeakerInfo (same file, :23) and the SessionIdentifierType alias (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/MMCA.ADC.Conference.GlobalUsings.IdentifierType.cs:19). External: IReadOnlyList<T> only.
  • Concept - none new; it is the request half of the never-throw port taught at IAiScoringService, and the minimization rationale is taught at SpeakerInfo. [Rubric §3 - Clean Architecture] assesses whether an external capability is described in the application's own language: this record names a session and its speakers, not a prompt, a token budget, or a JSON body, all of which stay inside the Infrastructure adapter.
  • -
  • Walkthrough - four positional members (:33-37). Speakers is documented as possibly empty (:32), and the handler does produce an empty list for a session whose speaker links resolve to nothing (SessionScoringRunner.cs:63-69). SessionId is carried through the call and echoed back on SessionScoringResult, which is what lets the handler pair a result with its session without holding a map.
  • +
  • Walkthrough - four positional members (:33-37). Speakers is documented as possibly empty (:32), and the handler does produce an empty list for a session whose speaker links resolve to nothing (SessionScoringRunner.cs:69-75). SessionId is carried through the call and echoed back on SessionScoringResult, which is what lets the handler pair a result with its session without holding a map.
  • Why it's built this way - passing a purpose-built input record rather than the Session entity keeps the domain aggregate out of the adapter and keeps the prompt's ingredients auditable in four lines.
  • -
  • Where it's used - the sole payload parameter of IAiScoringService.ScoreSessionAsync (:11-13); built once per session by SessionScoringRunner (SessionScoringRunner.cs:462), consumed by SessionScoringService.
  • +
  • Where it's used - the sole payload parameter of IAiScoringService.ScoreSessionAsync (:11-13); built once per session by SessionScoringRunner (SessionScoringRunner.cs:77), consumed by SessionScoringService.
  • [Rubric §16, AI-Native Application Architecture] applies: this type is part of the AI session-scoring feature (a model call behind a port, versioned prompt and model, an evaluation gate, metered spend; ADR-111).

    IAiScoringService

    @@ -2457,13 +2493,13 @@

    IAiScoringService

  • Concept introduced - port and adapter for an unreliable, paid, external capability. [Rubric §3 - Clean Architecture] assesses which layer owns the abstraction: the application declares the port, while the prompt text and the JSON contract records live in Infrastructure's SessionScoringService, registered with services.TryAddScoped<IAiScoringService>(...) over a governed IChatClient resolved with GetService, not GetRequiredService (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/DependencyInjection.cs:47-50); the transport, the credential, the model, and the per-call timeout all belong to MMCA.Common.AI's chat client now, so nothing in this module constructs an HttpClient or names a vendor. [Rubric §1 - SOLID] assesses the dependency-inversion direction: SessionScoringRunner depends on this interface, so swapping providers touches one registration. [Rubric §29 - Resilience & Business Continuity] assesses failure containment: the "Never throws (failure is indicated in the result)" clause (:9) is the Result philosophy applied to a network call, and it is what lets the handler's per-session loop keep going. [Rubric §13 - Observability & Operability] assesses whether an AI output can be explained after the fact: the port makes both the model id (:16) and the prompt version (:30) part of the contract, so every stored score carries the two inputs that decide what it means (the governance rule is ADR-111, Website/docs-src/adr/111-ai-session-scoring-governance.md). [Rubric §14 - Testability] assesses whether the use case can run without the dependency: FakeAiScoringService implements this interface, so the scoring path is exercised with no HTTP and no API key.
  • Walkthrough
    • ScoreSessionAsync(SessionScoringInput, CancellationToken = default) (:11-13) returns Task<SessionScoringResult>. There is no Result<T> here and no exception path: the success or failure signal is the Success flag on the returned record.
    • -
    • ModelId { get; } (:16) exposes which model produced a score. The handler stamps it onto the persisted entity as the modelUsed argument (SessionScoringRunner.cs:85), so a score row records both the number and its provenance.
    • -
    • PromptVersion { get; } (:30) exposes the version of the prompt contract the implementation sends, as a dated yyyy-MM-dd.N string, and the handler stamps it on the same call (SessionScoringRunner.cs:85). The doc comment (:18-29) is where the operating rule lives: bump it on any change to the system prompt, the user-prompt assembly (speaker formatting included), the redaction rules applied to submitted text, or the structured-output schema. It also records why the rule holds, that the golden evaluation suite pins the rendered prompt by hash per version, so an unbumped prompt change fails there rather than silently re-basing every score.
    • +
    • ModelId { get; } (:16) exposes which model produced a score. The handler stamps it onto the persisted entity as the modelUsed argument (SessionScoringRunner.cs:91), so a score row records both the number and its provenance.
    • +
    • PromptVersion { get; } (:30) exposes the version of the prompt contract the implementation sends, as a dated yyyy-MM-dd.N string, and the handler stamps it on the same call (SessionScoringRunner.cs:91). The doc comment (:18-29) is where the operating rule lives: bump it on any change to the system prompt, the user-prompt assembly (speaker formatting included), the redaction rules applied to submitted text, or the structured-output schema. It also records why the rule holds, that the golden evaluation suite pins the rendered prompt by hash per version, so an unbumped prompt change fails there rather than silently re-basing every score.
    • Scope: one session per call. Nothing on this interface batches, so the fan-out policy (sequential, one at a time) is the handler's decision rather than the port's.
  • Why it's built this way - defining the port in Application lets the scoring use case be exercised with a fake scorer, and lets the AI vendor change without touching the handler. Exposing ModelId and PromptVersion on the port rather than hard-coding strings in the handler means the recorded provenance cannot drift from the client that actually made the call: the implementation that assembles the prompt is the one thing that can honestly name its version.
  • -
  • Where it's used - constructor-injected into SessionScoringRunner (SessionScoringRunner.cs:408-411); implemented by SessionScoringService in production and FakeAiScoringService in the integration test host.
  • +
  • Where it's used - constructor-injected into SessionScoringRunner (SessionScoringRunner.cs:23-27); implemented by SessionScoringService in production and FakeAiScoringService in the integration test host.
  • [Rubric §16, AI-Native Application Architecture] applies: this type is part of the AI session-scoring feature (a model call behind a port, versioned prompt and model, an evaluation gate, metered spend; ADR-111).

    ISessionScoringRunner

    @@ -2537,18 +2573,19 @@

    CalendarExportMapper

    SessionScoringRunner

    -

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.DecisionSupport.ScoreEventSessions · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/SessionScoringRunner.cs:17 · Level 9 · class (sealed, partial)

    +

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.DecisionSupport.ScoreEventSessions · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/SessionScoringRunner.cs:23 · Level 9 · class (sealed, partial)

    • What it is - the ISessionScoringRunner implementation: loads an event's non-service sessions and their speakers, scores each session through IAiScoringService one at a time, and persists a SessionAiScore per successfully scored session.
    • -
    • Depends on - IUnitOfWork, IAiScoringService, ILogger<T> by primary constructor (:408-411); implements ISessionScoringRunner. Domain types: Session, Speaker, SessionAiScore. Application types: SpeakerInfo, SessionScoringInput.
    • -
    • Concept introduced - per-session replace instead of a bulk delete. The comment above the delete-then-add pair states the tradeoff directly: an up-front bulk delete of every existing score on the event would reset the dashboard to zero and then pay for N sequential Anthropic calls, and the first call to fail on an expired key or a rate limit leaves every session after it with no score at all (:487-496). Deleting and re-adding one session's row inside its own loop iteration means a run that dies partway through has only replaced what it re-scored, and a session whose call fails keeps the score it already had. The unique filtered index on SessionId (SessionAiScoreConfiguration) makes the pair safe: at most one live row per session either way (:494-496). - [Rubric §19, Restrict Delete by Default] applies (ADR-119): the delete here is scoped to sc => sc.SessionId == sessionId (:498), not a table-wide clear, and it exists only to keep the unique-index invariant across a re-score. - [Rubric §29, Resilience & Business Continuity] assesses failure containment inside a loop over paid external calls: a failed ScoreSessionAsync result or a failed SessionAiScore.Create both increment failed and continue (:465-470, :478-483); a save exception is caught (excluding OperationCanceledException) and also counted as a failure rather than aborting the loop (:506-510). - Concept introduced - skipping what a queued duplicate or a retry already answered. RunAsync now takes skipScoredSinceUtc (:415) and passes it, along with the loaded sessions, to ExcludeScoredSinceAsync (:444, defined at :537-565). When the parameter is set, that method queries IUnitOfWork's read repository for SessionAiScore rows on the event's sessions whose LastModifiedOn ?? CreatedOn is at or after the instant (:548-555), and drops those sessions from the pending set before the scoring loop starts (:557-564). The doc comment states why: such a score already answers the request that asked for this pass, so re-scoring it would only pay for the same call twice (:531-535).
    • -
    • Walkthrough - RunAsync (:413-529): loads non-service sessions for the event with SessionSpeakers included, untracked (:421-425), and returns an empty-but-successful result immediately if there are none (:427-428). Batch-loads every distinct, non-deleted speaker referenced by those sessions in one GetByIdsAsync call (:431-442) rather than per-session. Narrows the session list to pending via ExcludeScoredSinceAsync before logging the scoring-started count (:444-446). For each session in pending, builds the non-deleted speakers' SpeakerInfo projections (:454-460), wraps them in a SessionScoringInput (:462), and calls aiScoringService.ScoreSessionAsync (:463). A successful SessionScoringResult goes through SessionAiScore.Create with the model id and prompt version stamped from the service (:472-476); a successful create runs the delete-then-add-then-save sequence and increments scored (:497-504). After the loop, a run where every session failed and none scored returns Result.Failure with code AiScoring.AllFailed (:520-526); otherwise it returns the scored/failed counts as ScoreEventSessionsResultDTO (:528).
    • -
    • Why it's built this way - the class summary and the delete-then-add comment both explain the same design point from two angles: per-session granularity trades a slightly more complex loop body for a run that degrades gracefully under partial external failure, which matters because each unit of work is a billed, rate-limited call to a third-party model. ExcludeScoredSinceAsync extends the same instinct to the request boundary: a session already answered by a prior claim on this event is work the loop should not redo, not just work it should tolerate failing.
    • -
    • Where it's used - registered as ISessionScoringRunner in MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs; the sole implementation injected into ScoreEventSessionsInternalCommandHandler, which now forwards ScoreEventSessionsInternalCommand's RequestedAtUtc as skipScoredSinceUtc.
    • +
    • Depends on - IUnitOfWork, IServiceScopeFactory, IAiScoringService, ILogger<T> by primary constructor (:23-27); implements ISessionScoringRunner. Domain types: Session, Speaker, SessionAiScore. Application types: SpeakerInfo, SessionScoringInput.
    • +
    • Concept introduced - per-session replace instead of a bulk delete. The comment above the delete-then-add pair states the tradeoff directly: an up-front bulk delete of every existing score on the event would reset the dashboard to zero and then pay for N sequential Anthropic calls, and the first call to fail on an expired key or a rate limit leaves every session after it with no score at all (:102-111). Deleting and re-adding one session's row inside its own loop iteration means a run that dies partway through has only replaced what it re-scored, and a session whose call fails keeps the score it already had. The unique filtered index on SessionId (SessionAiScoreConfiguration) makes the pair safe: at most one live row per session either way (:109-111). + Concept introduced - one DI scope per replace. The delete, add and save for a session run in ReplaceScoreAsync (:146-159) on a fresh scope's IUnitOfWork: scopeFactory.CreateAsyncScope() (:151), GetRequiredService<IUnitOfWork>() (:152), then ExecuteDeleteAsync, AddAsync and SaveChangesAsync on that scope's repository (:155-157), returning the deleted-row count. The class summary gives the reason: a failed insert stays tracked after its exception, so on one shared context it would be re-issued by every later session's save and fail the rest of the pass; the reads stay on the injected unit of work (:17-21, and the method summary at :142-145). + [Rubric §19, Restrict Delete by Default] applies (ADR-119): the delete here is scoped to sc => sc.SessionId == sessionId (:155), not a table-wide clear, and it exists only to keep the unique-index invariant across a re-score. + [Rubric §29, Resilience & Business Continuity] assesses failure containment inside a loop over paid external calls: a failed ScoreSessionAsync result or a failed SessionAiScore.Create both increment failed and continue (:80-85, :93-98); an exception from the replace is caught (excluding OperationCanceledException) and also counted as a failure rather than aborting the loop (:117-121), and because that replace ran on its own scope, the failed entity is disposed with it instead of riding into the next session's save. + Concept introduced - skipping what a queued duplicate or a retry already answered. RunAsync takes skipScoredSinceUtc (:31) and passes it, along with the loaded sessions, to ExcludeScoredSinceAsync (:59, defined at :167-195). When the parameter is set, that method queries IUnitOfWork's read repository for SessionAiScore rows on the event's sessions whose LastModifiedOn ?? CreatedOn is at or after the instant (:178-185), and drops those sessions from the pending set before the scoring loop starts (:187-194). The doc comment states why: such a score already answers the request that asked for this pass, so re-scoring it would only pay for the same call twice (:161-166).
    • +
    • Walkthrough - RunAsync (:29-140): loads non-service sessions for the event with SessionSpeakers included, untracked (:36-40), and returns an empty-but-successful result immediately if there are none (:42-43). Batch-loads every distinct, non-deleted speaker referenced by those sessions in one GetByIdsAsync call (:45-57) rather than per-session. Narrows the session list to pending via ExcludeScoredSinceAsync before logging the scoring-started count (:59-61). For each session in pending, builds the non-deleted speakers' SpeakerInfo projections (:69-75), wraps them in a SessionScoringInput (:77), and calls aiScoringService.ScoreSessionAsync (:78). A successful SessionScoringResult goes through SessionAiScore.Create with the model id and prompt version stamped from the service (:87-91); a successful create hands the new score to ReplaceScoreAsync, adds its deleted count to replaced, and increments scored (:112-114). After the loop it logs the replaced count when non-zero (:124-127); a run where every session failed and none scored returns Result.Failure with code AiScoring.AllFailed (:131-137); otherwise it returns the scored/failed counts as ScoreEventSessionsResultDTO (:139).
    • +
    • Why it's built this way - the class summary and the delete-then-add comment both explain the same design point from two angles: per-session granularity trades a slightly more complex loop body for a run that degrades gracefully under partial external failure, which matters because each unit of work is a billed, rate-limited call to a third-party model. ExcludeScoredSinceAsync extends the same instinct to the request boundary: a session already answered by a prior claim on this event is work the loop should not redo, not just work it should tolerate failing. The per-replace scope applies the same containment to the change tracker: one session's failed write cannot fail the writes of the sessions after it.
    • +
    • Where it's used - registered as ISessionScoringRunner via TryAddScoped in MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:67; the sole implementation injected into ScoreEventSessionsInternalCommandHandler, which forwards ScoreEventSessionsInternalCommand's RequestedAtUtc as skipScoredSinceUtc (ScoreEventSessionsInternalCommandHandler.cs:88).
    • Testing: SessionScoringRunnerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/DecisionSupport/SessionScoringRunnerTests.cs).
    • ADRs: ADR-119 (Website/docs-src/adr/119-restrict-delete-by-default.md).
    • Caveats / not-in-source: sessions are scored strictly sequentially, one Anthropic call per iteration, with no batching or parallel fan-out; the class doc comment does not state a rationale for that choice beyond what IAiScoringService's "one session per call" scope implies.
    • @@ -2609,7 +2646,7 @@

      GetPublicSessionCategoryItemF
    • Depends on: IUnitOfWork (:17, injected only to hand on to the resolver); PublicConferenceVisibility; InlineSpecification<TEntity, TIdentifierType>; SessionCategoryItem; Result. Implements IQueryHandler<in TQuery, TResult> to Result<Specification<SessionCategoryItem, SessionCategoryItemIdentifierType>> (:18).
    • Concept: none new; the derived junction filter is taught under GetPublicSessionSpeakerFilterHandler, and this is the category-assignment instance of the same shape. It calls the identical resolver method its speaker-side twin does and restates nothing of the rule. [Rubric §15, Best Practices & Code Quality]: the junction cannot drift away from the entity whose visibility it follows, because it holds no copy of that entity's rule. [Rubric §8, Data Architecture]: the answer arrives as an id list turned into Contains, not a navigation join, so the criteria stays translatable on any provider (ADR-018).
    • Walkthrough
        -
      1. Resolve the visible session ids (:25-27): PublicConferenceVisibility.GetVisibleSessionIdsAsync(unitOfWork, cancellationToken). Inside the resolver that is the same CrossSourceSpecification.BuildAsync call GetPublicSessionFilterHandler makes, over Session and Event with principalPredicate: e => e.IsPublished, dependentForeignKey: s => s.EventId, and localPredicate: PublicSessionStatusSpecification.StatusCriteria (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Common/PublicConferenceVisibility.cs:63-70). The resolver then hands that specification straight to the repository's spec-taking projection overload, ListAsync(specification, s => s.Id, cancellationToken) (PublicConferenceVisibility.cs:77-79; the overload is declared at MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:279-282 and projects server side, after the specification's ordering and paging, :264-273). The comment above the call explains why passing a specification is enough (PublicConferenceVisibility.cs:72-74): a plain specification contributes its Criteria and nothing else, so this is the untracked, soft-delete-filtered read the explicit GetProjectedAsync arguments would otherwise have to spell out. The comment at :61-62 states the property being bought: the same helper and the same criteria the public-session read filter uses, so a session hidden from the session list can never stay reachable through a junction read.
      2. +
      3. Resolve the visible session ids (:25-27): PublicConferenceVisibility.GetVisibleSessionIdsAsync(unitOfWork, cancellationToken). Inside the resolver that is the same CrossSourceSpecification.BuildAsync call GetPublicSessionFilterHandler makes, over Session and Event with principalPredicate: e => e.IsPublished, dependentForeignKey: s => s.EventId, and localPredicate: PublicSessionStatusSpecification.StatusCriteria (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Common/PublicConferenceVisibility.cs:63-70). The resolver then hands that specification straight to the repository's spec-taking projection overload, ListAsync(specification, s => s.Id, cancellationToken) (PublicConferenceVisibility.cs:77-79; the overload is declared at MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:281-284 and projects server side, after the specification's ordering and paging, :264-273). The comment above the call explains why passing a specification is enough (PublicConferenceVisibility.cs:72-74): a plain specification contributes its Criteria and nothing else, so this is the untracked, soft-delete-filtered read the explicit GetProjectedAsync arguments would otherwise have to spell out. The comment at :61-62 states the property being bought: the same helper and the same criteria the public-session read filter uses, so a session hidden from the session list can never stay reachable through a junction read.
      4. Wrap and return (:29-31): sci => sessionIds.Contains(sci.SessionId) inside an InlineSpecification<TEntity, TIdentifierType>, returned as Result.Success. No failure path.
    • @@ -2627,9 +2664,9 @@

      GetPublicSessionFilterQuery

    • Depends on: nothing first-party, nothing external. Its whole payload is its identity as a message type.
    • Concept introduced: the marker query whose answer is a filter, not rows. Most read slices in this chapter return DTOs, and the calendar pair above returns a document. This family returns a Specification<TEntity, TIdentifierType>, a reusable predicate object the caller composes with its own paging, sorting, and filters before a single row is fetched. That indirection is what lets one visibility rule serve four different endpoint shapes (list, paged list, lookup, by-id) without any of them restating it. The query needs no members because the rule takes no parameters: it is the same rule for every caller who is not privileged. [Rubric §11, Security] assesses whether an authorization rule is enforced once, server side, on every path that can reach the data. The doc comment (:3-10) spells out the rule and why it is an allow-list rather than a deny-list: Accepted-or-unset sessions whose parent event is published, so a session in any other state (waitlisted, nominated, queued, declined, or an unrecognized Sessionize value) is invisible by default. A deny-list would silently expose the next status Sessionize invents. - [Rubric §2, Design Patterns] assesses whether a recognized pattern is used where it earns its keep. Specification-as-return-value keeps the predicate composable: SessionsController ANDs it with the speaker filter rather than choosing between them (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:125-127).
    • + [Rubric §2, Design Patterns] assesses whether a recognized pattern is used where it earns its keep. Specification-as-return-value keeps the predicate composable: SessionsController ANDs it with the speaker filter rather than choosing between them (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:133-135).
    • Walkthrough: no members, no methods, no validation. The doc comment (:6-9) also records the physical constraint that shapes the handler: the design treats Session and Event as potentially living in different data sources, so the published-event check cannot be a navigation join and the handler delegates to the framework's cross-source specification helper.
    • -
    • Why it's built this way: an empty record still buys a distinct type, and a distinct type is what the CQRS pipeline dispatches on. new GetPublicSessionFilterQuery() selects GetPublicSessionFilterHandler through the module's Scrutor scan, services.ScanModuleApplicationServices<ClassReference>() (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143, the strategy documented at :43-44), which registers every implementation of IQueryHandler<in TQuery, TResult> in the module assembly with a scoped lifetime (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:252-256). The visibility rule is therefore reached the same way every other read is, with the same decorators around it.
    • +
    • Why it's built this way: an empty record still buys a distinct type, and a distinct type is what the CQRS pipeline dispatches on. new GetPublicSessionFilterQuery() selects GetPublicSessionFilterHandler through the module's Scrutor scan, services.ScanModuleApplicationServices<ClassReference>() (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139, the strategy documented at :43-44), which registers every implementation of IQueryHandler<in TQuery, TResult> in the module assembly with a scoped lifetime (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:252-256). The visibility rule is therefore reached the same way every other read is, with the same decorators around it.
    • Where it's used: handled by GetPublicSessionFilterHandler; injected into SessionsController (SessionsController.cs:50) and constructed in that controller's GetReadSpecificationAsync override (SessionsController.cs:75-85), which short-circuits to null for privileged readers (:79-80). That hook feeds the unpaged list (:148), the paged list through BuildPagedSessionSpecificationAsync (:110, applied at :187), the lookup (:213-216), and the by-id read (:225-231), each of which is [AllowAnonymous] (:136, :162, :211, :223) under the class-level [HasPermission(ConferencePermissions.SessionsManage)] (:43).
    • Caveats / not-in-source: the doc comment states that Session lives in Cosmos DB and Event in SQL Server (:7-8), and the controller repeats it (SessionsController.cs:64-66). In ADC as configured today both are SQL Server entities: SessionConfiguration derives from EntityTypeConfigurationSQLServer<Session, SessionIdentifierType> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Sessions/SessionConfiguration.cs:12-13) and EventConfiguration from the same SQL Server base (.../EntityConfiguration/EventConfiguration.cs:11-12). The cross-source treatment is therefore prophylactic against the polyglot option (ADR-018) rather than a description of the deployed engine split.
    @@ -2654,10 +2691,10 @@

    GetSessionsBySpeakerFilterQuery

  • Depends on: the SpeakerIdentifierType alias (System.Guid in Conference, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/MMCA.ADC.Conference.GlobalUsings.IdentifierType.cs:23). Nothing else.
  • Concept introduced: the virtual filter key. A client filtering the paged session list by speaker sends SpeakerId as an ordinary filter key, but Session has no SpeakerId column: the link lives in the SessionSpeaker join. The doc comment (:4-9) records the resolution: the link is resolved as an ID-list projection so the resulting criteria stays engine-portable and the Session aggregate keeps a by-id boundary to Speaker, following the GetSpeakersByEventFilterQuery precedent (BR-132). [Rubric §4, DDD] assesses whether aggregates reference each other by identifier instead of by object graph; this query exists precisely so a cross-aggregate question can be answered without giving Session a navigation to Speaker. - [Rubric §9, API & Contract Design]: the key is intercepted in the controller and never forwarded to the generic filter pipeline, which rejects unknown properties, and an unparseable value ignores the key rather than failing the request (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:93-97, :112-117).
  • + [Rubric §9, API & Contract Design]: the key is intercepted in the controller and never forwarded to the generic filter pipeline, which rejects unknown properties, and an unparseable value ignores the key rather than failing the request (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:101-105, :112-117).
  • Walkthrough: a single positional member, SpeakerId (:11), documented as the speaker whose sessions should match (:10). No behavior; all of it is in GetSessionsBySpeakerFilterHandler.
  • Why it's built this way: modelling the answer as a specification (rather than as a list of sessions) lets the endpoint keep its generic list-page-sort machinery and simply AND one more criterion into it, which is exactly what the paged action does.
  • -
  • Where it's used: handled by GetSessionsBySpeakerFilterHandler; injected into SessionsController (SessionsController.cs:51) and constructed in BuildPagedSessionSpecificationAsync after the SpeakerId key is removed from the filter dictionary and parsed (SessionsController.cs:111-119). The two specifications are ANDed, never substituted (:126-128); the remark at :100-104 states why: dropping the public filter for a speaker-scoped request would leak non-accepted sessions to non-privileged callers.
  • +
  • Where it's used: handled by GetSessionsBySpeakerFilterHandler; injected into SessionsController (SessionsController.cs:51) and constructed in BuildPagedSessionSpecificationAsync after the SpeakerId key is removed from the filter dictionary and parsed (SessionsController.cs:119-127). The two specifications are ANDed, never substituted (:126-128); the remark at :100-104 states why: dropping the public filter for a speaker-scoped request would leak non-accepted sessions to non-privileged callers.
  • GetSessionsBySpeakerFilterHandler

    @@ -2666,7 +2703,7 @@

    GetSessionsBySpeakerFilterHandler

  • What it is: the handler for GetSessionsBySpeakerFilterQuery. It projects the session ids linked to the speaker through the SessionSpeaker join and returns a Session.Id IN (...) filter (GetSessionsBySpeakerFilterHandler.cs:21-23).
  • Depends on: IUnitOfWork (primary-constructor parameter, :22); IQueryHandler<in TQuery, TResult> closed over Result<Specification<Session, SessionIdentifierType>> (:23); InlineSpecification<TEntity, TIdentifierType> and Specification<TEntity, TIdentifierType>; Session and SessionSpeaker; Result.
  • -
  • Concept introduced: ID-list projection instead of a navigation join. Rather than expressing the rule as one LINQ expression that walks Session -> SessionSpeaker -> Speaker, the handler runs a scalar projection query first and embeds its result in the predicate. GetProjectedAsync<TResult>(select, where, asTracking, ignoreQueryFilters, cancellationToken) returns only the selected column instead of whole entities; it is declared on the IEntityQuerier<TEntity, TIdentifierType> facet (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:105-110, facet declared at :80) that IReadRepository<TEntity, TIdentifierType> composes (IRepository.cs:330-331). +
  • Concept introduced: ID-list projection instead of a navigation join. Rather than expressing the rule as one LINQ expression that walks Session -> SessionSpeaker -> Speaker, the handler runs a scalar projection query first and embeds its result in the predicate. GetProjectedAsync<TResult>(select, where, asTracking, ignoreQueryFilters, cancellationToken) returns only the selected column instead of whole entities; it is declared on the IEntityQuerier<TEntity, TIdentifierType> facet (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:105-110, facet declared at :80) that IReadRepository<TEntity, TIdentifierType> composes (IRepository.cs:332-333). [Rubric §8, Data Architecture] assesses whether query shapes survive the storage topology the architecture allows. A navigation join is only translatable when both ends sit in the same physical source; an IN over materialized ids translates on every provider, which is what ADR-018 needs. The framework enforces the same constraint mechanically for declared specification classes through the SpecificationsDoNotNavigateToOtherEntities fitness rule, which instantiates parameterless specifications and inspects their Criteria (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.Specifications.cs:24, evaluation at :53, failure message at :74). [Rubric §4, DDD]: the class comment (:9-14) states the second property being bought, that the Session aggregate keeps its by-id boundary to the Speaker aggregate even while answering a cross-aggregate question.
  • Walkthrough
      @@ -2676,7 +2713,7 @@

      GetSessionsBySpeakerFilterHandler

    1. Why it's built this way: the <para> in the class comment (:15-19) records the one behavior that is easy to get wrong downstream. A speaker with no sessions yields an empty id list, and an empty IN matches nothing, which is the correct answer; that is exactly why the caller must apply the specification rather than skip it when the list is empty. Skipping it would turn "this speaker presents nothing" into "show every session".
    2. -
    3. Where it's used: SessionsController's BuildPagedSessionSpecificationAsync is the only consumer (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:118-120), which ANDs the result with the public-session filter via AndSpecification<TEntity, TIdentifierType> (:126-128) and falls back to the public filter alone if this handler reports failure (:123-124).
    4. +
    5. Where it's used: SessionsController's BuildPagedSessionSpecificationAsync is the only consumer (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:126-128), which ANDs the result with the public-session filter via AndSpecification<TEntity, TIdentifierType> (:126-128) and falls back to the public filter alone if this handler reports failure (:123-124).
    6. Testing: GetSessionsBySpeakerFilterHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/GetSessionsBySpeakerFilter/GetSessionsBySpeakerFilterHandlerTests.cs:17), five tests on the shared HandlerTestBase<THandler>: the success shape (:60), a presented session matching (:69), a session the speaker does not present being excluded (:79), a speaker with no sessions matching nothing (:89), and repeated join rows collapsing to one id (:101, the Distinct() at :40).
    7. Caveats / not-in-source: the id list is materialized into the predicate, so the IN list grows with the number of sessions one speaker presents. That is naturally bounded for a conference speaker, but nothing in this file enforces a bound.
    8. @@ -2696,7 +2733,7 @@

      GetPublicSessionFilterHandler

  • Why it's built this way: the whole handler is two statements because the reusable mechanics were pushed into the framework. What stays local is the pair of business predicates, which is the part that can change. The rule is enforced at the application layer rather than in the controller so that every caller of the query gets it, including the ones added later.
  • -
  • Where it's used: SessionsController via its GetReadSpecificationAsync override (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:75-85), which reaches the unpaged list, the paged list, the lookup, and the by-id read. The lookup action is worth reading: it is an attribute-only passthrough to the framework base action (SessionsController.cs:213-216) precisely because the read hook already scopes it, and a lookup endpoint would otherwise be a side channel listing the sessions the list and detail endpoints already hide (:204-208).
  • +
  • Where it's used: SessionsController via its GetReadSpecificationAsync override (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:75-85), which reaches the unpaged list, the paged list, the lookup, and the by-id read. The lookup action is worth reading: it is an attribute-only passthrough to the framework base action (SessionsController.cs:221-224) precisely because the read hook already scopes it, and a lookup endpoint would otherwise be a side channel listing the sessions the list and detail endpoints already hide (:204-208).
  • Testing: GetPublicSessionFilterHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/GetPublicSessionFilter/GetPublicSessionFilterHandlerTests.cs:14), seven test methods asserted against the produced criteria rather than against the handler's internals: the success shape (:72), the public statuses matching for a published event (:84, a theory over Accepted and null), the non-public statuses excluded (:101, a theory over the non-eligible Sessionize values), a session of an unpublished event excluded (:111), no published events matching nothing (:121), the principal predicate selecting only published events (:139), and the cancellation token reaching the event query (:162).
  • Caveats / not-in-source: CrossSourceSpecification materializes the matching principal keys into the predicate, and its own note (CrossSourceSpecification.cs:17-20) scopes the technique to small or bounded principal sets, the "published events" shape. Nothing in this handler bounds that set; it is bounded in practice by how many events a conference publishes. Note also that the controller maps a failed Result to null, meaning no filter (SessionsController.cs:84), which would widen the read rather than narrow it; nothing in this handler can produce that failure today, so the exposure is latent rather than live.
  • @@ -2742,7 +2779,7 @@

    GetPublicSpeakerFilterQuery

  • Concept introduced: the optional scope parameter. The same rule has to answer two different questions: "which speakers are public anywhere" and "which speakers are public on this event". Rather than two query types, one nullable parameter distinguishes them, and the default = null (GetPublicSpeakerFilterQuery.cs:20) means the un-scoped call site reads as new GetPublicSpeakerFilterQuery(). [Rubric §9, API & Contract Design] assesses contract expressiveness: the nullable is documented per-parameter (:15-19) as "the paged list has one, everything else passes none", so the two modes are part of the published contract rather than folklore. [Rubric §11, Security] as with the junction query: the id only narrows the rule, it can never widen it, because an unpublished or unknown scoped event resolves to an empty visible set (MMCA.ADC.Conference.Application/Common/PublicConferenceVisibility.cs:113-120).
  • Walkthrough: public sealed record GetPublicSpeakerFilterQuery(EventIdentifierType? EventId = null) (:20). The <remarks> block (:9-14) explains the shape the handler will return: Speaker carries no status or event column of its own, so the rule cannot be expressed as a property comparison and is instead resolved into an id list and returned as a Speaker.Id IN (...) criteria, following the BR-132 precedent. That keeps the criteria free of navigation joins, so it stays translatable on any engine and Speaker keeps its by-id boundary to the Session and Event aggregates.
  • Why it's built this way: making the scope optional rather than required is what lets one handler serve the paged list, the lookup, GetById, and the junction reads. The alternative (a required id plus a sentinel) would have pushed the "no context" case into every caller.
  • -
  • Where it's used: constructed by SpeakersController in BuildPublicSpeakerSpecificationAsync (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:82) and answered by GetPublicSpeakerFilterHandler.
  • +
  • Where it's used: constructed by SpeakersController in BuildPublicSpeakerSpecificationAsync (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:83) and answered by GetPublicSpeakerFilterHandler.
  • ISessionFieldsRequest

    @@ -2764,7 +2801,7 @@

    SessionEventIdRules<T>

  • What it is: the reusable rule fragment asserting that a session request actually names a parent event. It is the only fragment in this file that validates a non-string field, and the only one that pins its own error code.
  • Depends on: RequiredIdRules<T, TId> (its base class, SessionValidationRules.cs:25), System.Linq.Expressions.Expression<Func<T, EventIdentifierType>> for the property selector (:1,27), and the module alias EventIdentifierType.
  • Concept introduced: the parameterized rule fragment, the shape every rule in this file follows. Rather than repeating RuleFor(x => x.EventId).NotEmpty() inside each command validator, the constraint lives once in a small generic AbstractValidator<T> subclass whose constructor takes the property selector; a concrete validator then folds it in with FluentValidation's Include(...). Because the fragment is generic in T, the same type validates the create request, the update request, or any future import request, each pointing the selector at its own property. The framework supplies the bases these fragments specialize: RequiredIdRules<T, TId> here, and RequiredStringRules<T> or OptionalStringRules<T> for the seven string fragments below. [Rubric §1, SOLID]: one fragment, one field contract, composed rather than copied. [Rubric §24, Forms/Validation/UX Safety] assesses whether input constraints are declared once and applied consistently at every entry path.
  • -
  • Walkthrough: the expression-bodied constructor (:27-28) forwards base(selector, "an Event for the Session", "Session.EventId.Required"). The base (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:142-148) chains a single NotEmpty() clause with the message You must specify {fieldName} and applies the optional error code (:145-147), which is why the field phrase carries its own article: the caller supplies "an Event for the Session" and the base interpolates it verbatim. Because EventIdentifierType is int (MMCA.ADC.Conference.Shared/MMCA.ADC.Conference.GlobalUsings.IdentifierType.cs:10), FluentValidation's NotEmpty tests against default(int), so the rule rejects a missing or zero event id; the base's remarks (CommonValidationRules.cs:133-139) note that the same check rejects Guid.Empty for a GUID key, which is what makes one base serve both id shapes.
  • +
  • Walkthrough: the expression-bodied constructor (:27-28) forwards base(selector, "an Event for the Session", "Session.EventId.Required"). The base (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:143-149) chains a single NotEmpty() clause with the message You must specify {fieldName} and applies the optional error code (:145-147), which is why the field phrase carries its own article: the caller supplies "an Event for the Session" and the base interpolates it verbatim. Because EventIdentifierType is int (MMCA.ADC.Conference.Shared/MMCA.ADC.Conference.GlobalUsings.IdentifierType.cs:10), FluentValidation's NotEmpty tests against default(int), so the rule rejects a missing or zero event id; the base's remarks (CommonValidationRules.cs:134-140) note that the same check rejects Guid.Empty for a GUID key, which is what makes one base serve both id shapes.
  • Why it's built this way: the stable error code is what the API error-mapping layer keys on, so it is part of the contract and not just display text. Keeping the event-id check as its own fragment (instead of folding it into the shared SessionFieldRules<T> list) is what lets the update path deliberately omit it: an update request also carries EventId, but re-parenting is rejected by the handler under BR-140 with the Session.EventId.Immutable error (MMCA.ADC.Conference.Application/Sessions/UseCases/Update/UpdateSessionHandler.cs:44-52), not by the validator.
  • Testing: SessionValidationRulesTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/Validation/SessionValidationRulesTests.cs:8) pins both outcomes directly on the fragment: a supplied event id passes (:48) and default(int) fails (:55).
  • Where it's used: Included by SessionCreateRequestValidator as its create-only delta (MMCA.ADC.Conference.Application/Sessions/UseCases/Create/SessionCreateRequestValidator.cs:16), directly beside the comment explaining the omission (:13-15). It is deliberately absent from SessionUpdateRequestValidator, whose whole body is one Include of the shared field rules (SessionUpdateRequestValidator.cs:12).
  • @@ -2776,7 +2813,7 @@

    SessionAccessibilityInfoRules<T>
  • What it is: the rule fragment bounding a session's optional accessibility-info text to 500 characters.
  • Depends on: OptionalStringRules<T> (its base class, SessionValidationRules.cs:87) and SessionInvariants for the bound (:4,90).
  • -
  • Concept reinforced: the parameterized rule fragment introduced by SessionEventIdRules<T>, in its shortest possible form. Deriving from the framework's OptionalStringRules<T> means the subclass declares nothing but a base(...) call: the base applies MaximumLength and no NotEmpty (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:55-57), which is exactly the contract a nullable field needs. [Rubric §1, SOLID] and [Rubric §24, Forms/Validation/UX Safety].
  • +
  • Concept reinforced: the parameterized rule fragment introduced by SessionEventIdRules<T>, in its shortest possible form. Deriving from the framework's OptionalStringRules<T> means the subclass declares nothing but a base(...) call: the base applies MaximumLength and no NotEmpty (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:56-58), which is exactly the contract a nullable field needs. [Rubric §1, SOLID] and [Rubric §24, Forms/Validation/UX Safety].
  • Walkthrough: sealed class SessionAccessibilityInfoRules<T> : OptionalStringRules<T> (:86-87); the constructor (:89) forwards base(selector, "Accessibility Info", SessionInvariants.AccessibilityInfoMaxLength) (:90). The label "Accessibility Info" is what appears in the generated length message; the bound is 500, reached through SessionInvariants (MMCA.ADC.Conference.Domain/Sessions/SessionInvariants.cs:25) which itself forwards to SessionDTO.AccessibilityInfoMaxLength (MMCA.ADC.Conference.Shared/Sessions/SessionDTO.cs:31).
  • Why it's built this way: the number lives on the DTO, the lowest layer the UI can also reach, and the domain invariant re-exports it (SessionInvariants.cs:7-11). One constant therefore feeds the Blazor markup, this validator, the aggregate's own EnsureStringMaxLength guard (SessionInvariants.cs:80), and the EF column width, so a limit change cannot drift between layers.
  • Where it's used: Included by SessionFieldRules<T> (SessionValidationRules.cs:122), which is the single list both the create and the update validator fold in.
  • @@ -2799,7 +2836,7 @@

    SessionLiveUrlRules<T>

    • What it is: the rule fragment bounding a session's optional live-stream URL to 2000 characters. Length only: it deliberately does not check that the value is a well-formed URL.
    • Depends on: OptionalStringRules<T> (:62) and SessionInvariants (:65).
    • -
    • Concept introduced: the deliberately weak format rule. Every other URL-ish field in a codebase invites a Uri.TryCreate check, and this one refuses it on purpose. The class doc (:55-60) records why: the value is stored as an opaque string for Sessionize compatibility, so whatever the import writes has to round-trip unchanged. Validating a format the upstream source does not guarantee would reject real data. The contrast is visible one file over in the framework: AbsoluteUrlRules<T> exists and adds an absolute http/https check on top of the same length bound (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:85-94), so choosing OptionalStringRules<T> here is a decision, not an omission. [Rubric §24, Forms/Validation/UX Safety] assesses fitness of validation, not quantity: a rule that would fail on legitimate imported values is worse than no rule. [Rubric §26, Front-End Security] is the counterweight worth naming, and the framework doc states it plainly (CommonValidationRules.cs:77-83): a bounded string still accepts javascript: and data: values, so anything that renders this field as a link owns its own scheme checking.
    • +
    • Concept introduced: the deliberately weak format rule. Every other URL-ish field in a codebase invites a Uri.TryCreate check, and this one refuses it on purpose. The class doc (:55-60) records why: the value is stored as an opaque string for Sessionize compatibility, so whatever the import writes has to round-trip unchanged. Validating a format the upstream source does not guarantee would reject real data. The contrast is visible one file over in the framework: AbsoluteUrlRules<T> exists and adds an absolute http/https check on top of the same length bound (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:86-95), so choosing OptionalStringRules<T> here is a decision, not an omission. [Rubric §24, Forms/Validation/UX Safety] assesses fitness of validation, not quantity: a rule that would fail on legitimate imported values is worse than no rule. [Rubric §26, Front-End Security] is the counterweight worth naming, and the framework doc states it plainly (CommonValidationRules.cs:78-84): a bounded string still accepts javascript: and data: values, so anything that renders this field as a link owns its own scheme checking.
    • Walkthrough: the constructor (:64) forwards base(selector, "Live URL", SessionInvariants.LiveUrlMaxLength) (:65); LiveUrlMaxLength is 2000 (MMCA.ADC.Conference.Shared/Sessions/SessionDTO.cs:37, re-exported at MMCA.ADC.Conference.Domain/Sessions/SessionInvariants.cs:31). The aggregate's own guard is length-only too, with the same stated reason (SessionInvariants.cs:57,78).
    • Caveats / not-in-source: the fragment guarantees length only. Whether any specific consumer sanitizes the value before rendering is not determinable from this file.
    • Where it's used: Included by SessionFieldRules<T> (SessionValidationRules.cs:120).
    • @@ -2844,9 +2881,9 @@

      SessionTitleRules<T>

      • What it is: the rule fragment for a session title: non-empty and bounded to 500 characters. It is the one string fragment in this file with a required field, and the first type declared in it.
      • Depends on: RequiredStringRules<T> (its base class, SessionValidationRules.cs:14) and SessionInvariants (:17).
      • -
      • Concept reinforced: the parameterized fragment from SessionEventIdRules<T>, specialized against the required string base rather than the optional one. RequiredStringRules<T> chains NotEmpty() then MaximumLength(maxLength) with generated messages built from the field label (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:43-46), so choosing the base is how a fragment declares required-versus-optional. [Rubric §1, SOLID] and [Rubric §24, Forms/Validation/UX Safety].
      • +
      • Concept reinforced: the parameterized fragment from SessionEventIdRules<T>, specialized against the required string base rather than the optional one. RequiredStringRules<T> chains NotEmpty() then MaximumLength(maxLength) with generated messages built from the field label (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:44-47), so choosing the base is how a fragment declares required-versus-optional. [Rubric §1, SOLID] and [Rubric §24, Forms/Validation/UX Safety].
      • Walkthrough: sealed class SessionTitleRules<T> : RequiredStringRules<T> (:13-14); the constructor (:16) forwards base(selector, "Session Title", SessionInvariants.TitleMaxLength) (:17). TitleMaxLength is 500 (MMCA.ADC.Conference.Shared/Sessions/SessionDTO.cs:18, re-exported at MMCA.ADC.Conference.Domain/Sessions/SessionInvariants.cs:16), the same constant the aggregate's own title guard cites (SessionInvariants.cs:49-52).
      • -
      • Caveats / not-in-source: unlike SessionEventIdRules<T>, none of the seven string fragments in this file pass an error code, even though both framework bases accept an optional one and apply it to every rule they declare (CommonValidationRules.cs:43,55, via OptionalErrorCodeExtensions at :30-32). Their failures therefore surface with generated messages and FluentValidation's default codes, not the stable Session.<Field>.<Reason> codes the domain invariants use (SessionInvariants.cs:51-52,76-81).
      • +
      • Caveats / not-in-source: unlike SessionEventIdRules<T>, none of the seven string fragments in this file pass an error code, even though both framework bases accept an optional one and apply it to every rule they declare (CommonValidationRules.cs:44,56, via OptionalErrorCodeExtensions at :30-32). Their failures therefore surface with generated messages and FluentValidation's default codes, not the stable Session.<Field>.<Reason> codes the domain invariants use (SessionInvariants.cs:51-52,76-81).
      • Testing: SessionValidationRulesTests covers the fragment's three outcomes directly: a valid title passes (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/Validation/SessionValidationRulesTests.cs:25), an empty one fails (:32), and one over the bound fails (:40).
      • Where it's used: Included by SessionFieldRules<T> (SessionValidationRules.cs:117), the first entry in the shared list.
      @@ -2880,7 +2917,7 @@

      SessionRoomScheduling

    • Why it's built this way: factoring the rule into a static class (rather than duplicating it in each handler, or pushing it into Session) is a direct consequence of where the data lives. The rule spans two aggregates: it needs the parent Event's rooms and it needs every other session's schedule, so no single aggregate can enforce it, and it belongs in the application layer beside the handlers that load both. It is also why these fields are absent from ISessionFieldsRequest: a rule that has to query cannot live in a FluentValidation fragment. Keeping the predicate a separate public member is what lets the update path pass excludeSessionId so a session can keep or shrink its own slot without colliding with itself.
    • Testing: SessionRoomSchedulingTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/Validation/SessionRoomSchedulingTests.cs:13) exercises the predicate as a pure function, which is what makes the half-open rule cheap to pin down: overlapping (:54) and fully contained (:62) slots match, while back-to-back (:70), same-room-different-day (:79), different-room (:87), self-excluded (:95), and unscheduled (:104) cases do not. A final test asserts DoubleBookedError is conflict-typed (:112).
    • Caveats / not-in-source: the class doc notes that deliberate co-location (lightning talks sharing one slot) would need this check relaxed from a rejection to a warning (:13-15). No such relaxation exists in the current code.
    • -
    • Where it's used: called by CreateSessionHandler with excludeSessionId: null (MMCA.ADC.Conference.Application/Sessions/UseCases/Create/CreateSessionHandler.cs:111-119, only when the command actually carries a room, :100) and by UpdateSessionHandler with excludeSessionId: command.Id (MMCA.ADC.Conference.Application/Sessions/UseCases/Update/UpdateSessionHandler.cs:66-74), each passing its own handler name as the error source. Both load the parent event with includes: [nameof(Event.Rooms)] and asTracking: false first (CreateSessionHandler.cs:103-107, UpdateSessionHandler.cs:57-61), because that collection is what the cross-event check reads.
    • +
    • Where it's used: called by CreateSessionHandler with excludeSessionId: null (MMCA.ADC.Conference.Application/Sessions/UseCases/Create/CreateSessionHandler.cs:112-120, only when the command actually carries a room, :100) and by UpdateSessionHandler with excludeSessionId: command.Id (MMCA.ADC.Conference.Application/Sessions/UseCases/Update/UpdateSessionHandler.cs:66-74), each passing its own handler name as the error source. Both load the parent event with includes: [nameof(Event.Rooms)] and asTracking: false first (CreateSessionHandler.cs:103-107, UpdateSessionHandler.cs:57-61), because that collection is what the cross-event check reads.

    GetPublicSpeakerCategoryItemFilterHandler

    @@ -2894,7 +2931,7 @@

    GetPublicSpeakerCategoryItemF
  • Why it's built this way: the junction row follows the visibility of its parent, so the handler reuses the one visible-speaker computation instead of re-deriving a junction-specific rule. Filtering by an id list rather than a navigation join keeps the criteria engine-portable (ADR-018) and preserves the by-id boundary between SpeakerCategoryItem and the Session and Event aggregates the rule actually reads. The query parameter is accepted and unused because the IQueryHandler<in TQuery, TResult> contract requires it, which is also why the cancellation token is passed by name (:27).
  • Testing: GetPublicSpeakerCategoryItemFilterHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/GetPublicSpeakerCategoryItemFilter/GetPublicSpeakerCategoryItemFilterHandlerTests.cs:19), four tests: the success shape (:66), a row of a visible speaker matching (:75), a row of a hidden speaker excluded (:85), and no visible speakers matching nothing (:95).
  • Caveats / not-in-source: the id list is materialized into the expression, so the generated SQL carries as many parameters as there are visible speakers. What that costs at conference scale is not determinable from this file.
  • -
  • Where it's used: injected into SpeakerCategoryItemsController (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerCategoryItemsController.cs:52) and invoked from its GetReadSpecificationAsync override (:73-83), the framework's read hook: it returns null for privileged readers (Organizer/ContentEditor, :59,76-77) so they see every row, and otherwise returns the handler's specification (:79-82). Because the hook is the base controller's, every read action is scoped from that one place and an excluded row is a 404 rather than a redacted record (:61-72), covering GetAll (:85), the paged list (:95), the lookup (:114), and GetById (:122), each [AllowAnonymous] (:86, :96, :115, :123) under the class-level [HasPermission(ConferencePermissions.SpeakersManage)] requirement (:47). Registration is convention-based: the module's ScanModuleApplicationServices<ClassReference>() call picks up every handler in the assembly (MMCA.ADC.Conference.Application/DependencyInjection.cs:143).
  • +
  • Where it's used: injected into SpeakerCategoryItemsController (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerCategoryItemsController.cs:52) and invoked from its GetReadSpecificationAsync override (:73-83), the framework's read hook: it returns null for privileged readers (Organizer/ContentEditor, :59,76-77) so they see every row, and otherwise returns the handler's specification (:79-82). Because the hook is the base controller's, every read action is scoped from that one place and an excluded row is a 404 rather than a redacted record (:61-72), covering GetAll (:85), the paged list (:95), the lookup (:114), and GetById (:122), each [AllowAnonymous] (:86, :96, :115, :123) under the class-level [HasPermission(ConferencePermissions.SpeakersManage)] requirement (:47). Registration is convention-based: the module's ScanModuleApplicationServices<ClassReference>() call picks up every handler in the assembly (MMCA.ADC.Conference.Application/DependencyInjection.cs:139).
  • GetPublicSpeakerFilterHandler

    @@ -2919,7 +2956,7 @@

    GetSessionBookmarkCountQuery

  • Depends on: the module identifier aliases SpeakerIdentifierType (a System.Guid) and SessionIdentifierType (an int), declared in MMCA.ADC.Conference.Shared/MMCA.ADC.Conference.GlobalUsings.IdentifierType.cs:23 and :15. Nothing else.
  • Concept introduced: carrying the subject alongside the object. The count itself only needs a session id. The speaker id is in the message because the authorization rule is "you may see the count for a session you are assigned to", and that rule is enforced by the handler rather than by a route filter. Putting the speaker in the query keeps the authorization input explicit and testable instead of hidden in ambient request state. [Rubric §11, Security] assesses whether object-level authorization is enforced next to the data access; here the pairing in the query is what makes that possible. [Rubric §6, CQRS & Event-Driven]: a read that spans two bounded contexts still travels as one ordinary query record with no behavior on it.
  • Walkthrough: public sealed record GetSessionBookmarkCountQuery(SpeakerIdentifierType SpeakerId, SessionIdentifierType SessionId) (:6), with per-parameter docs naming SpeakerId as "the speaker requesting the count" (:4) and SessionId as the session to count (:5). There are no members, no markers, and no cache-invalidation interface.
  • -
  • Where it's used: constructed by SpeakersController at MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:449 for the GET /Speakers/{speakerId}/sessions/{sessionId}/bookmarks/count endpoint (:439-454), and answered by GetSessionBookmarkCountHandler. Its batch sibling is GetSessionBookmarkCountsQuery, which the dashboard uses to avoid a per-session fan-out (:459-470).
  • +
  • Where it's used: constructed by SpeakersController at MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:460 for the GET /Speakers/{speakerId}/sessions/{sessionId}/bookmarks/count endpoint (:439-454), and answered by GetSessionBookmarkCountHandler. Its batch sibling is GetSessionBookmarkCountsQuery, which the dashboard uses to avoid a per-session fan-out (:459-470).
  • GetSessionBookmarkCountsQuery

    @@ -2931,7 +2968,7 @@

    GetSessionBookmarkCountsQuery

  • Concept introduced: the batched query, and why the caller's id list is an input rather than an authorization. The singular sibling GetSessionBookmarkCountQuery answers for one session; this record takes a whole collection (:8) so a dashboard listing N sessions makes one round trip instead of N. The important design point is what the record does not mean: SessionIds is a request, not a grant. The speaker id travels alongside (:7) precisely so GetSessionBookmarkCountsHandler can re-derive server-side which of those sessions the speaker is actually entitled to see. [Rubric §11, Security] assesses whether authorization decisions are made from server-held state rather than from client-supplied lists: the shape of this query is what makes that possible, because it forces the pairing of "who is asking" with "what they asked about". [Rubric §12, Performance & Scalability]: collapsing a per-row fan-out into one batched intent is the query-shape half of an N+1 fix.
  • Walkthrough: two positional parameters on a sealed record (:6-8), SpeakerId (:7) and SessionIds (:8). The declared parameter type is IReadOnlyCollection<SessionIdentifierType>, so the handler can cheaply test Count before doing any work without committing the caller to a particular collection implementation. There are no methods and no markers: this is a pure read intent.
  • Why it's built this way: queries in this codebase are plain records with no behavior, so the IQueryHandler<in TQuery, TResult> implementation stays the single place where the read is described (see Group 05).
  • -
  • Where it's used: constructed by SpeakersController on GET {speakerId}/sessions/bookmarks/counts (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:460-471), which binds sessionIds from the query string with [FromQuery] (:464) and null-coalesces a missing array to an empty one (:468).
  • +
  • Where it's used: constructed by SpeakersController on GET {speakerId}/sessions/bookmarks/counts (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:471-482), which binds sessionIds from the query string with [FromQuery] (:464) and null-coalesces a missing array to an empty one (:468).
  • GetSessionFeedbackQuery

    @@ -2942,7 +2979,7 @@

    GetSessionFeedbackQuery

  • Depends on: the identifier aliases SpeakerIdentifierType and SessionIdentifierType (:6). Nothing else.
  • Concept reinforced: the same "who is asking plus what they asked about" pair taught at GetSessionBookmarkCountsQuery, in its single-target form. The speaker id is not decorative: GetSessionFeedbackHandler rejects the read with a Forbidden error when the speaker is not assigned to the session, so the query type carries exactly the two facts the authorization check needs. [Rubric §11, Security]: the read is scoped by a server-verified relationship, not by trusting the route.
  • Walkthrough: a one-line sealed record with two positional parameters, SpeakerId and SessionId (:6). The XML docs (:3-5) name the business rule and each parameter's role.
  • -
  • Where it's used: constructed by SpeakersController on GET {speakerId}/sessions/{sessionId}/feedback (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:417-436). The endpoint is [Authorize] (:417) and applies a self-or-organizer gate before the handler ever runs: the caller must hold the Organizer role or carry the route speaker's speaker_id claim, otherwise it returns Forbid() (:423-426). It carries no [OutputCache] attribute, and the endpoint summary records why (:410-415): free-text comments are the speaker's own read, so every response is authorization-dependent and must not be publicly cached.
  • +
  • Where it's used: constructed by SpeakersController on GET {speakerId}/sessions/{sessionId}/feedback (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:428-447). The endpoint is [Authorize] (:417) and applies a self-or-organizer gate before the handler ever runs: the caller must hold the Organizer role or carry the route speaker's speaker_id claim, otherwise it returns Forbid() (:423-426). It carries no [OutputCache] attribute, and the endpoint summary records why (:410-415): free-text comments are the speaker's own read, so every response is authorization-dependent and must not be publicly cached.
  • GetSpeakersByEventFilterQuery

    @@ -2954,7 +2991,7 @@

    GetSpeakersByEventFilterQuery

  • Concept introduced: the query that returns a specification, not rows. Most queries in this module resolve to a DTO or a count. This one resolves to a predicate object that the caller then composes into a larger read. The reason is spelled out in the type's own doc comment (:3-10): a Speaker has no EventId column, and it can belong to an event by two independent link paths, the EventSpeaker join written by the Sessionize sync and the SessionSpeaker join written by organizer session management. Resolving that union has to happen in a handler with repository access, but the result still has to be a filter so it can be ANDed with the caller's other criteria and passed to the generic paged read. Returning a specification is how a multi-step lookup is turned back into a single composable clause. [Rubric §2, Design Patterns] assesses whether recognized patterns are applied where they earn their keep: this is Specification used as a first-class return value, not just as a parameter. [Rubric §8, Data Architecture]: the doc comment records the deliberate choice to resolve the joins as ID-list projections so the criteria stay engine-portable rather than depending on a navigation join.
  • Walkthrough: the whole type is one line (:12); the nine lines above it (:3-11) are the design rationale, which is unusually long for a record and is the load-bearing part to read. It names the two link paths, states that they are populated by different flows so the handler must union them, and notes that Speaker has no EventId column.
  • Why it's built this way: keeping Speaker free of an EventId column preserves the aggregate boundary (a speaker exists independently of any event, and relates to events by id, not by containment). The cost of that DDD choice is this two-path lookup, and the specification return type is what keeps the cost contained in one handler. See ADR-055 for the repository-plus-specification data-access contract this leans on.
  • -
  • Where it's used: SpeakersController lifts an EventId out of the incoming filter dictionary and removes it unconditionally, because Speaker has no such column and the generic filter pipeline rejects unknown properties (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:140-150). When an id parsed, it calls the handler and folds the returned specification into the public-visibility specification (:161-175) before running the paged read (:177-188).
  • +
  • Where it's used: SpeakersController lifts an EventId out of the incoming filter dictionary and removes it unconditionally, because Speaker has no such column and the generic filter pipeline rejects unknown properties (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:149-159). When an id parsed, it calls the handler and folds the returned specification into the public-visibility specification (:161-175) before running the paged read (:177-188).
  • Caveats / not-in-source: this filter is distinct from the BR-239 public-visibility rule, which is resolved separately by BuildPublicSpeakerSpecificationAsync (SpeakersController.cs:161). The two are ANDed (:171-173), so an event-scoped listing shows the intersection, not the union.
  • ISpeakerFieldsRequest

    @@ -2976,8 +3013,8 @@

    SpeakerEmailRules<T>

    • What it is: the rule fragment for a speaker's optional email address. When a value is supplied it must be a well-formed address of at most 255 characters; when it is absent nothing is checked.
    • Depends on: FluentValidation's AbstractValidator<T> (extended directly, SpeakerValidationRules.cs:39), the framework's EmailRules<T> (:46), SpeakerInvariants for the bound (:3,46), and System.Linq.Expressions.Expression<Func<T, string>> for the property selector (:1,41).
    • -
    • Concept introduced: making a required rule optional with When, and the cost of the compiled accessor. EmailRules<T> is a required email rule: it chains NotEmpty, EmailAddress and MaximumLength in one pass (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:67-70). A speaker's email is optional, and the naive fix would be to re-declare the two clauses that still apply and drop NotEmpty. This fragment does the opposite: it keeps the framework rule intact and gates the entire Include behind a When predicate (:45-46), so the definition of "a valid email" stays in exactly one place for the whole workspace and this file only decides whether to apply it. The mechanism has a price worth understanding: the When predicate needs a value, not an expression tree, so the constructor calls selector.Compile() (:43) and closes over the resulting delegate. Compilation happens once per fragment instance (that is, once per validator construction), and the delegate is then invoked on each validation pass. [Rubric §24, Forms/Validation/UX Safety] assesses whether validation matches the real contract of the field: an optional field validated by a required rule would reject legitimate blank input, and this is how that is avoided without weakening the format check. [Rubric §15, Best Practices & Code Quality]: reuse over re-declaration, so the format rule cannot drift per module.
    • -
    • Walkthrough: sealed class SpeakerEmailRules<T> : AbstractValidator<T> (:38-39); the constructor takes the selector (:41), compiles it into an accessor (:43), and registers one conditional group: When(x => !string.IsNullOrWhiteSpace(accessor(x)), () => Include(new EmailRules<T>(selector, "Email", SpeakerInvariants.EmailMaxLength))) (:45-46). The label "Email" is interpolated into the generated messages ("You must enter a Email", "You must enter a valid Email", CommonValidationRules.cs:68-70); EmailMaxLength is 255 (MMCA.ADC.Conference.Shared/Speakers/SpeakerDTO.cs:27, surfaced through SpeakerInvariants.cs:22). No error code is supplied, so the failures carry FluentValidation's default codes rather than a stable Speaker.Email.* code.
    • +
    • Concept introduced: making a required rule optional with When, and the cost of the compiled accessor. EmailRules<T> is a required email rule: it chains NotEmpty, EmailAddress and MaximumLength in one pass (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:68-71). A speaker's email is optional, and the naive fix would be to re-declare the two clauses that still apply and drop NotEmpty. This fragment does the opposite: it keeps the framework rule intact and gates the entire Include behind a When predicate (:45-46), so the definition of "a valid email" stays in exactly one place for the whole workspace and this file only decides whether to apply it. The mechanism has a price worth understanding: the When predicate needs a value, not an expression tree, so the constructor calls selector.Compile() (:43) and closes over the resulting delegate. Compilation happens once per fragment instance (that is, once per validator construction), and the delegate is then invoked on each validation pass. [Rubric §24, Forms/Validation/UX Safety] assesses whether validation matches the real contract of the field: an optional field validated by a required rule would reject legitimate blank input, and this is how that is avoided without weakening the format check. [Rubric §15, Best Practices & Code Quality]: reuse over re-declaration, so the format rule cannot drift per module.
    • +
    • Walkthrough: sealed class SpeakerEmailRules<T> : AbstractValidator<T> (:38-39); the constructor takes the selector (:41), compiles it into an accessor (:43), and registers one conditional group: When(x => !string.IsNullOrWhiteSpace(accessor(x)), () => Include(new EmailRules<T>(selector, "Email", SpeakerInvariants.EmailMaxLength))) (:45-46). The label "Email" is interpolated into the generated messages ("You must enter a Email", "You must enter a valid Email", CommonValidationRules.cs:69-71); EmailMaxLength is 255 (MMCA.ADC.Conference.Shared/Speakers/SpeakerDTO.cs:27, surfaced through SpeakerInvariants.cs:22). No error code is supplied, so the failures carry FluentValidation's default codes rather than a stable Speaker.Email.* code.
    • Why it's built this way: the class doc (:30-36) states the business reason for optionality directly: a speaker imported from Sessionize can arrive without an email, and the BR-207 auto-link by email match simply finds no user in that case. A supplied value must still be a real address, because it is the one the organizer contacts the speaker on. Rejecting the import to satisfy a validator would be the wrong trade.
    • Where it's used: Included by SpeakerFieldRules<T> (:121), which passes p => p.Email! because ISpeakerFieldsRequest declares the property nullable.
    @@ -2988,12 +3025,12 @@

    SpeakerFirstNameRules<T>

    • What it is: the rule fragment for a speaker's first name: required and bounded to 200 characters. It is the first type declared in the speaker validation file.
    • Depends on: RequiredStringRules<T> (its base class, SpeakerValidationRules.cs:13), SpeakerInvariants for the bound (:3,16), and Expression<Func<T, string>> for the selector (:1,15).
    • -
    • Concept reinforced: the parameterized rule fragment introduced at SessionEventIdRules<T>, here in its simplest form. Deriving from the framework's required base rather than the optional one is how a fragment declares required-versus-optional: RequiredStringRules<T> chains NotEmpty() then MaximumLength(maxLength) with generated messages built from the field label (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:44-46). [Rubric §1, SOLID]: one fragment, one field contract, composed rather than copied.
    • +
    • Concept reinforced: the parameterized rule fragment introduced at SessionEventIdRules<T>, here in its simplest form. Deriving from the framework's required base rather than the optional one is how a fragment declares required-versus-optional: RequiredStringRules<T> chains NotEmpty() then MaximumLength(maxLength) with generated messages built from the field label (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:45-47). [Rubric §1, SOLID]: one fragment, one field contract, composed rather than copied.
    • Walkthrough: sealed class SpeakerFirstNameRules<T> : RequiredStringRules<T> (:12-13); the constructor (:15) forwards base(selector, "First Name", SpeakerInvariants.FirstNameMaxLength) (:16). FirstNameMaxLength is 200 (MMCA.ADC.Conference.Shared/Speakers/SpeakerDTO.cs:21, re-exported by SpeakerInvariants.cs:16), the same constant the aggregate's own guard cites when it builds the Speaker.FirstName.TooLong failure (MMCA.ADC.Conference.Domain/Speakers/SpeakerInvariants.cs:48).
    • Why it's built this way: sourcing the length from SpeakerInvariants rather than a literal keeps the request validator, the domain guard, and the EF column width citing one constant, so a limit change cannot drift between layers.
    • Testing: SpeakerValidationRulesTests covers this fragment through a happy path (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/Validation/SpeakerValidationRulesTests.cs:25), an empty value (:32), and an over-length value (:40).
    • Where it's used: Included by SpeakerFieldRules<T> with the selector p => p.FirstName (:119).
    • -
    • Caveats / not-in-source: neither this fragment nor any other in the file supplies the optional errorCode argument the framework bases accept (CommonValidationRules.cs:43,55,66,87), so request-level failures surface with FluentValidation's default codes, not the stable Speaker.<Field>.<Reason> codes the domain invariants use (SpeakerInvariants.cs:47-53).
    • +
    • Caveats / not-in-source: neither this fragment nor any other in the file supplies the optional errorCode argument the framework bases accept (CommonValidationRules.cs:44,56,67,88), so request-level failures surface with FluentValidation's default codes, not the stable Speaker.<Field>.<Reason> codes the domain invariants use (SpeakerInvariants.cs:47-53).

    SpeakerGitHubUrlRules<T>

    @@ -3002,8 +3039,8 @@

    SpeakerGitHubUrlRules<T>

    • What it is: the rule fragment for a speaker's optional GitHub profile URL. When a value is supplied it must be at most 2000 characters and an absolute http or https URL.
    • Depends on: AbstractValidator<T> directly (:77), the framework's AbsoluteUrlRules<T> (:84), SpeakerInvariants (:84), and Expression<Func<T, string?>> for the selector (:79).
    • -
    • Concept introduced: the scheme check as an injection control at the write boundary. A length-only bound on a URL column accepts javascript: and data: values, which become executable the moment something renders them as a link href or an image src. AbsoluteUrlRules<T> closes that by chaining MaximumLength with Must(BeAnAbsoluteHttpUrl) (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:88-90), and it delegates the predicate to CommonInvariants.EnsureUrlIsWellFormed (:93) so the request validator and the domain invariant answer identically rather than drifting into two definitions of "valid URL". This fragment's own doc states the concrete exposure it is guarding (:69-74): the speaker pages put the stored value straight into a link. Note the deliberate contrast with the session URL fragments in this same module: SessionLiveUrlRules<T> checks length only, on purpose, because that value must round-trip whatever Sessionize wrote. Speaker profile links are organizer-entered, so the stronger rule is affordable here and is not there. [Rubric §11, Security] assesses whether untrusted input is constrained where it enters the system rather than only where it is displayed; [Rubric §26, Front-End Security] is the paired concern the check relieves, since the renderer no longer has to scheme-filter a value the writer already rejected. [Rubric §24, Forms/Validation/UX Safety]: the caller is told at submit time, not silently given a dead or dangerous link.
    • -
    • Walkthrough: sealed class SpeakerGitHubUrlRules<T> : AbstractValidator<T> (:76-77); the constructor compiles the selector into an accessor (:81) and gates the whole rule behind When(x => !string.IsNullOrWhiteSpace(accessor(x)), ...) (:83), exactly the optionality mechanism taught at SpeakerEmailRules<T>. Inside, it includes new AbsoluteUrlRules<T>(selector, "GitHub URL", SpeakerInvariants.GitHubUrlMaxLength) (:84); GitHubUrlMaxLength is 2000 (MMCA.ADC.Conference.Shared/Speakers/SpeakerDTO.cs:49, via SpeakerInvariants.cs:37). The label "GitHub URL" is what appears in both generated messages, including "GitHub URL must be an absolute http or https URL" (CommonValidationRules.cs:90).
    • +
    • Concept introduced: the scheme check as an injection control at the write boundary. A length-only bound on a URL column accepts javascript: and data: values, which become executable the moment something renders them as a link href or an image src. AbsoluteUrlRules<T> closes that by chaining MaximumLength with Must(BeAnAbsoluteHttpUrl) (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:89-91), and it delegates the predicate to CommonInvariants.EnsureUrlIsWellFormed (:93) so the request validator and the domain invariant answer identically rather than drifting into two definitions of "valid URL". This fragment's own doc states the concrete exposure it is guarding (:69-74): the speaker pages put the stored value straight into a link. Note the deliberate contrast with the session URL fragments in this same module: SessionLiveUrlRules<T> checks length only, on purpose, because that value must round-trip whatever Sessionize wrote. Speaker profile links are organizer-entered, so the stronger rule is affordable here and is not there. [Rubric §11, Security] assesses whether untrusted input is constrained where it enters the system rather than only where it is displayed; [Rubric §26, Front-End Security] is the paired concern the check relieves, since the renderer no longer has to scheme-filter a value the writer already rejected. [Rubric §24, Forms/Validation/UX Safety]: the caller is told at submit time, not silently given a dead or dangerous link.
    • +
    • Walkthrough: sealed class SpeakerGitHubUrlRules<T> : AbstractValidator<T> (:76-77); the constructor compiles the selector into an accessor (:81) and gates the whole rule behind When(x => !string.IsNullOrWhiteSpace(accessor(x)), ...) (:83), exactly the optionality mechanism taught at SpeakerEmailRules<T>. Inside, it includes new AbsoluteUrlRules<T>(selector, "GitHub URL", SpeakerInvariants.GitHubUrlMaxLength) (:84); GitHubUrlMaxLength is 2000 (MMCA.ADC.Conference.Shared/Speakers/SpeakerDTO.cs:49, via SpeakerInvariants.cs:37). The label "GitHub URL" is what appears in both generated messages, including "GitHub URL must be an absolute http or https URL" (CommonValidationRules.cs:91).
    • Where it's used: Included by SpeakerFieldRules<T> with the selector p => p.GitHubUrl (:123).
    • Caveats / not-in-source: the rule constrains the scheme and the length, not the host, so an absolute https URL pointing anywhere passes. Nothing in this fragment requires the value to be a github.com address.
    @@ -3061,11 +3098,11 @@

    GetSessionBookmarkCountHandler

    • What it is: the handler for GetSessionBookmarkCountQuery. It verifies the asking speaker is actually assigned to the session, then asks the Engagement module for the count. Conference never reads Engagement's bookmark table itself.
    • Depends on: IQueryHandler<in TQuery, TResult> (GetSessionBookmarkCountHandler.cs:14,16), IUnitOfWork (:3,15), IBookmarkCountService from MMCA.ADC.Engagement.Shared.UserSessionBookmarks (:2,16), Session with its SessionSpeaker children (:1,26), plus Result and Error (:5).
    • -
    • Concept introduced: the cross-context read through an owned interface. Bookmarks belong to Engagement's bounded context and live in Engagement's own database (database-per-service, ADR-006), so there is no join available and no table Conference is allowed to touch. Instead Engagement publishes a two-method contract in its Shared project, marked [ServiceContract] to declare it part of an extracted service's wire surface (MMCA.ADC.Engagement.Shared/UserSessionBookmarks/IBookmarkCountService.cs:10-11), and Conference depends on that abstraction (GetSessionBookmarkCountHandler.cs:16). In the monolith DI binds it to the in-process BookmarkCountService with TryAddScoped (MMCA.ADC.Engagement.Application/DependencyInjection.cs:43); in the extracted topology the Conference service host calls AddEngagementBookmarkCountClient() (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:409), which registers the typed gRPC client and then uses services.Replace so the adapter wins over whatever is already in the container (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Contracts/DependencyInjection.cs:43-51, adapter at MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Contracts/BookmarkCountServiceGrpcAdapter.cs:14); and when the Engagement module is switched off entirely the module's RegisterDisabledStubs hook supplies DisabledBookmarkCountService (MMCA.ADC.Engagement.API/EngagementModule.cs:30-32). The handler is unchanged in all three cases. [Rubric §7, Microservices Readiness] assesses whether cross-module calls go through abstractions that can be re-pointed at a transport; this is that pattern in one file, and the gRPC path is ADR-007. [Rubric §3, Clean Architecture]: the Application layer names an interface and never a transport. [Rubric §11, Security]: the ownership check sits in the handler, immediately beside the data it guards.
    • +
    • Concept introduced: the cross-context read through an owned interface. Bookmarks belong to Engagement's bounded context and live in Engagement's own database (database-per-service, ADR-006), so there is no join available and no table Conference is allowed to touch. Instead Engagement publishes a two-method contract in its Shared project, marked [ServiceContract] to declare it part of an extracted service's wire surface (MMCA.ADC.Engagement.Shared/UserSessionBookmarks/IBookmarkCountService.cs:10-11), and Conference depends on that abstraction (GetSessionBookmarkCountHandler.cs:16). In the monolith DI binds it to the in-process BookmarkCountService with TryAddScoped (MMCA.ADC.Engagement.Application/DependencyInjection.cs:43); in the extracted topology the Conference service host calls AddEngagementBookmarkCountClient() (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:411), which registers the typed gRPC client and then uses services.Replace so the adapter wins over whatever is already in the container (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Contracts/DependencyInjection.cs:43-51, adapter at MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Contracts/BookmarkCountServiceGrpcAdapter.cs:14); and when the Engagement module is switched off entirely the module's RegisterDisabledStubs hook supplies DisabledBookmarkCountService (MMCA.ADC.Engagement.API/EngagementModule.cs:30-32). The handler is unchanged in all three cases. [Rubric §7, Microservices Readiness] assesses whether cross-module calls go through abstractions that can be re-pointed at a transport; this is that pattern in one file, and the gRPC path is ADR-007. [Rubric §3, Clean Architecture]: the Application layer names an interface and never a transport. [Rubric §11, Security]: the ownership check sits in the handler, immediately beside the data it guards.
    • Walkthrough: the primary constructor (:14-16) injects IUnitOfWork and IBookmarkCountService. HandleAsync (:19-21) resolves the session repository off the unit of work (:23, never by constructor-injecting IRepository<,> directly) and loads the session by id with its SessionSpeakers included and asTracking: false (:24-28), since this is a pure read. A missing session returns Error.NotFound stamped with the handler name and target (:29-30). The authorization step (:33) then requires at least one non-soft-deleted SessionSpeaker whose SpeakerId matches the caller, and otherwise returns Error.Forbidden coded Speaker.NotAssigned with a message, source and target (:35-40). Only after both checks does it call bookmarkCountService.GetBookmarkCountForSessionAsync(query.SessionId, cancellationToken) (:43) and wrap the integer in Result.Success (:45).
    • Why it's built this way: ordering matters. The not-found check precedes the assignment check, and the assignment check precedes the cross-context call, so an unauthorized caller never causes a gRPC hop and never learns anything beyond "forbidden". The explicit !ss.IsDeleted test at :33 is written out because the child collection is filtered in memory after eager loading rather than by the EF global query filter alone (ADR-005).
    • Testing: GetSessionBookmarkCountHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/GetSessionBookmarkCount/GetSessionBookmarkCountHandlerTests.cs:12) covers exactly the three outcomes above: a missing session (:25), an unassigned speaker (:45), and the assigned happy path returning the count (:66).
    • -
    • Where it's used: injected into SpeakersController as IQueryHandler<GetSessionBookmarkCountQuery, Result<int>> (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:55) and invoked by the GET /Speakers/{speakerId}/sessions/{sessionId}/bookmarks/count endpoint (:439-454), which is [AllowAnonymous] (:440) and served through the BookmarkCountsCache output-cache policy (:441). The batch equivalent used by the speaker dashboard is GetSessionBookmarkCountsHandler (:459-470).
    • +
    • Where it's used: injected into SpeakersController as IQueryHandler<GetSessionBookmarkCountQuery, Result<int>> (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:56) and invoked by the GET /Speakers/{speakerId}/sessions/{sessionId}/bookmarks/count endpoint (:439-454), which is [AllowAnonymous] (:440) and served through the BookmarkCountsCache output-cache policy (:441). The batch equivalent used by the speaker dashboard is GetSessionBookmarkCountsHandler (:459-470).

    GetSessionBookmarkCountsHandler

    @@ -3078,7 +3115,7 @@

    GetSessionBookmarkCountsHandler

  • Walkthrough: the primary constructor takes the unit of work and the count service (:17-19); the class implements IQueryHandler<GetSessionBookmarkCountsQuery, Result<IReadOnlyDictionary<SessionIdentifierType, int>>> (:20). HandleAsync (:23-25) starts with an empty-input short circuit returning an empty dictionary without touching the database (:27-31). It then takes the read repository (GetReadRepository, :33) and loads the requested sessions with their SessionSpeakers eager-included, asTracking: false (:34-38). The authorization projection (:43-46) keeps sessions where any SessionSpeaker matches the query's SpeakerId and is not soft-deleted, and selects just the ids. A second short circuit returns an empty dictionary when nothing survived (:48-52). Finally it calls bookmarkCountService.GetBookmarkCountsForSessionsAsync(authorizedSessionIds, ...) (:54-56) and wraps the returned dictionary in Result.Success (:58).
  • Why it's built this way: the batched contract exists so the Speaker Dashboard makes one call instead of one per session; the class doc comment (:9-16) names that as the reason, and the interface contract guarantees every requested id is present in the result with zero-bookmark sessions mapping to 0 (MMCA.ADC.Engagement.Shared/UserSessionBookmarks/IBookmarkCountService.cs:22-23). Note the explicit !ss.IsDeleted test at :44: the eager-loaded child collection is filtered in memory here, so the check is written out rather than relying solely on the EF global soft-delete filter (ADR-005). [Rubric §12, Performance & Scalability]: two round trips total (one local read, one cross-module call) regardless of session count, and zero when the id list is empty.
  • Testing: GetSessionBookmarkCountsHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/GetSessionBookmarkCounts/GetSessionBookmarkCountsHandlerTests.cs:12) pins all three paths: the empty-input short circuit asserted to query nothing (:34), the filtering behavior where only the speaker's own sessions are counted (:48), and the "no requested session is assigned" case that returns empty without counting (:73).
  • -
  • Where it's used: injected into SpeakersController (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:56) and invoked from the bookmarks/counts endpoint (:467-469), which is [AllowAnonymous] and served through the BookmarkCountsCache output-cache policy (:460-461).
  • +
  • Where it's used: injected into SpeakersController (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:57) and invoked from the bookmarks/counts endpoint (:467-469), which is [AllowAnonymous] and served through the BookmarkCountsCache output-cache policy (:460-461).
  • Caveats / not-in-source: when the Engagement module is disabled in a host, the interface resolves to DisabledBookmarkCountService instead (MMCA.ADC.Engagement.API/EngagementModule.cs:32), whose batch method returns a zero for each distinct requested id rather than an empty map (MMCA.ADC.Engagement.Shared/UserSessionBookmarks/DisabledBookmarkCountService.cs:18-22), so this handler's cross-module hop degrades to "all zeros" rather than failing.
  • GetSessionFeedbackHandler

    @@ -3092,7 +3129,7 @@

    GetSessionFeedbackHandler

  • Walkthrough: the primary constructor takes only the unit of work (:15-16). HandleAsync (:19-21) takes the Session repository (:23) and loads the session by id with SessionSpeakers and SessionQuestionAnswers eager-included and asTracking: false (:24-28), returning Error.NotFound sourced and targeted for diagnostics when it is missing (:29-30). The ownership gate follows (:33-40). With no answers it returns an empty SessionFeedbackDTO carrying just the session id and title (:44-53), avoiding the question query altogether. Otherwise it collects the distinct answered question ids into a HashSet (:56) and loads only those questions (:57-62), building a dictionary lookup (:63). The aggregation loop groups answers by question id (:68), skips a group whose question was not found (:70-71), and branches on question.QuestionType == "Rating" (:73): the rating branch parses each answer, keeps the parsed values (:75-79), and, only if at least one parsed (:81), emits a RatingQuestionSummary with AverageRating and ResponseCount (:83-89); every other question type emits a TextQuestionResponses with all raw answer strings via the collection expression [.. group.Select(a => a.AnswerValue)] (:94-99). The final DTO is assembled and wrapped in Result.Success (:103-109).
  • Why it's built this way: the comment at :42 records that soft-deleted answers are already excluded by the EF global query filter, so the aggregation does not re-filter them (contrast the explicit !ss.IsDeleted on the eager-loaded speaker links at :33). Loading questions by the answered-id set rather than by session avoids pulling the whole question bank. See ADR-005 for the soft-delete model these filters implement.
  • Testing: GetSessionFeedbackHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/GetSessionFeedback/GetSessionFeedbackHandlerTests.cs:12) covers the missing session (:27), the unassigned speaker (:48), and the no-answers short circuit (:69).
  • -
  • Where it's used: injected into SpeakersController (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:54) and invoked from the session-feedback endpoint after its self-or-organizer gate (:428-430).
  • +
  • Where it's used: injected into SpeakersController (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:55) and invoked from the session-feedback endpoint after its self-or-organizer gate (:428-430).
  • Caveats / not-in-source: the rating branch is selected by the literal string "Rating" (:73); the domain does constrain the column to ["Rating", "Text", "Email"] (MMCA.ADC.Conference.Domain/Questions/QuestionInvariants.cs:34), but this handler shares no constant with it, so the two definitions are coupled only by convention.
  • GetSpeakersByEventFilterHandler

    @@ -3140,10 +3177,10 @@

    ActivityUpdateRequest

    • What it is: the body a client PUTs to update an existing conference Activity, the non-session items on the agenda (receptions, breaks, after-parties). It carries every editable field and nothing else.
    • Depends on: IActivityFieldsRequest from MMCA.ADC.Conference.Application.Activities.Validation (MMCA.ADC.Conference.Application/Activities/UseCases/Update/ActivityUpdateRequest.cs:1,10). Nothing else: DateTime, string, and int are BCL. There is not a single domain type in its member list, which is the point of a request DTO.
    • -
    • Concept introduced: what a full-replacement update body deliberately leaves out. Two members you might expect are absent, each for its own reason. There is no EventId, and the <remarks> says why: moving an activity between events is a create plus a delete, so a mistyped EventId cannot silently relocate a published social event (:6-9). That is the same rule SponsorUpdateRequest applies to bought sponsor placement, so the module has one consistent answer to "can a PUT reparent an aggregate?". There is also no concurrency token on the body: the version the caller edited travels in the HTTP If-Match header instead, read by SupportsIfMatchAttribute and passed to the command by the controller (MMCA.ADC.Conference.API/Controllers/Activities/ActivitiesController.cs:183,192,194-196), so a request without one answers 428 and a stale one answers 412 (ADR-035, stated in the action's own doc comment at :175-178). [Rubric §9, API & Contract Design] assesses whether a contract makes the safe thing the only expressible thing: the dangerous operation is not merely validated against, it is absent from the type, and the conditional-request machinery lives in the transport layer where HTTP already has semantics for it. [Rubric §4, DDD]: the owning event is part of the activity's identity within the conference, not an ordinary attribute, so it is not editable through the attribute-editing endpoint.
    • +
    • Concept introduced: what a full-replacement update body deliberately leaves out. Two members you might expect are absent, each for its own reason. There is no EventId, and the <remarks> says why: moving an activity between events is a create plus a delete, so a mistyped EventId cannot silently relocate a published social event (:6-9). That is the same rule SponsorUpdateRequest applies to bought sponsor placement, so the module has one consistent answer to "can a PUT reparent an aggregate?". There is also no concurrency token on the body: the version the caller edited travels in the HTTP If-Match header instead, read by SupportsIfMatchAttribute and passed to the command by the controller (MMCA.ADC.Conference.API/Controllers/Activities/ActivitiesController.cs:191,200,202-204), so a request without one answers 428 and a stale one answers 412 (ADR-035, stated in the action's own doc comment at :175-178). [Rubric §9, API & Contract Design] assesses whether a contract makes the safe thing the only expressible thing: the dangerous operation is not merely validated against, it is absent from the type, and the conditional-request machinery lives in the transport layer where HTTP already has semantics for it. [Rubric §4, DDD]: the owning event is part of the activity's identity within the conference, not an ordinary attribute, so it is not editable through the attribute-editing endpoint.
    • Walkthrough: public record class ActivityUpdateRequest : IActivityFieldsRequest (:10). Name (:13) is the one required member, so the model binder rejects a body without it before any validator runs. Description (:16) is optional. StartTime and EndTime (:19, :22) are plain DateTime values documented as event-local (:18, :21). VenueName (:25) is optional and documented such that empty means the main conference venue, with VenueAddress (:28) and VenueUrl (:31) alongside it for off-site items. SortOrder (:34) breaks ties between activities that start at the same time (:33). Every member is init-only, so the bound request is immutable for the rest of the pipeline, and every one of the eight satisfies a member of IActivityFieldsRequest.
    • Why it's built this way: making the PUT a full replacement rather than a patch means the applier can pass every field straight through to the aggregate without distinguishing "not supplied" from "cleared" (MMCA.ADC.Conference.Application/Activities/UseCases/Update/ActivityUpdateApplier.cs:25-33). Marking only Name as required pushes the single non-negotiable field to bind time and leaves the graded constraints (lengths, the time ordering) to ActivityUpdateRequestValidator, which produces a readable message per field.
    • -
    • Where it's used: bound from the body by ActivitiesController on PUT {id} (MMCA.ADC.Conference.API/Controllers/Activities/ActivitiesController.cs:187-190), wrapped into UpdateEntityCommand<Activity, ActivityUpdateRequest, ActivityIdentifierType> with the If-Match token (:193-195), validated by ActivityUpdateRequestValidator through the command-to-request validator bridge, and applied field by field by ActivityUpdateApplier. It is also the TUpdateRequest argument of the module's CRUD registration (MMCA.ADC.Conference.Application/DependencyInjection.cs:154).
    • +
    • Where it's used: bound from the body by ActivitiesController on PUT {id} (MMCA.ADC.Conference.API/Controllers/Activities/ActivitiesController.cs:195-198), wrapped into UpdateEntityCommand<Activity, ActivityUpdateRequest, ActivityIdentifierType> with the If-Match token (:193-195), validated by ActivityUpdateRequestValidator through the command-to-request validator bridge, and applied field by field by ActivityUpdateApplier. It is also the TUpdateRequest argument of the module's CRUD registration (MMCA.ADC.Conference.Application/DependencyInjection.cs:150).
    • Caveats / not-in-source: nothing in the record pins a DateTimeKind, so whether the wire value arrives as UTC or unspecified local time is not determinable from this file: the doc comments only say "event-local" (:18, :21).

    SponsorEventIdRules<T>

    @@ -3154,7 +3191,7 @@

    SponsorEventIdRules<T>

  • What it is: a one-rule reusable validator that asserts a sponsor request actually names an event. It is generic over the request type, so the same rule binds to any record that has an event id.
  • Depends on: RequiredIdRules<T, TId> from MMCA.Common.Application.Validation (MMCA.ADC.Conference.Application/Sponsors/Validation/SponsorValidationRules.cs:4,116), Expression<Func<T, TProperty>> from the BCL (:1,118), and the EventIdentifierType alias (:116,118, aliased in the module's Shared project and taught in the primer).
  • Concept introduced: the parameterized rule set. This file is where sponsor validation is packaged rather than written inline, so learn the shape here. A rule set is a sealed class Foo<T> deriving from a MMCA.Common validation base, whose constructor takes an Expression<Func<T, TProperty>> selector and forwards it plus the field's contract to the base. It is generic because the contract belongs to the concept ("a sponsor's event id"), not to any one request record: SponsorCreateRequest is a different type from SponsorUpdateRequest, yet both could reuse the identical object by supplying their own property selector. Consumers fold it in with FluentValidation's Include(...), which merges the included validator's rules into the host validator as if they had been typed there. The payoff is that a constraint has exactly one definition and many bindings, so a change to it cannot land on the create path while missing the update path. [Rubric §24, Forms/Validation/UX Safety] assesses whether input constraints are single-sourced and applied consistently at every entry point: this whole file exists to make that true for sponsors. [Rubric §1, SOLID]: each rule set has one reason to change, the contract of one field.
  • -
  • Walkthrough: sealed class SponsorEventIdRules<T> : RequiredIdRules<T, EventIdentifierType> (:115-116), with an expression-bodied constructor that forwards (selector, "an Event for the Sponsor", "Sponsor.EventId.Required") (:118-119). The base declares RuleFor(selector).NotEmpty().WithMessage($"You must specify {fieldName}") and applies the optional error code (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:145-147). Two details matter. First, NotEmpty on an identifier is the deliberate check, rejecting 0 for an integer key and Guid.Empty for a Guid key, which is what "an id was never supplied" looks like on the wire for both shapes (CommonValidationRules.cs:133-139). Second, the field phrase is interpolated verbatim, so the caller supplies the article: passing "an Event for the Sponsor" yields "You must specify an Event for the Sponsor". WithErrorCode is contract, not decoration: Sponsor.EventId.Required is what an API client or a test keys on, while the message is the human-facing half.
  • +
  • Walkthrough: sealed class SponsorEventIdRules<T> : RequiredIdRules<T, EventIdentifierType> (:115-116), with an expression-bodied constructor that forwards (selector, "an Event for the Sponsor", "Sponsor.EventId.Required") (:118-119). The base declares RuleFor(selector).NotEmpty().WithMessage($"You must specify {fieldName}") and applies the optional error code (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:146-148). Two details matter. First, NotEmpty on an identifier is the deliberate check, rejecting 0 for an integer key and Guid.Empty for a Guid key, which is what "an id was never supplied" looks like on the wire for both shapes (CommonValidationRules.cs:134-140). Second, the field phrase is interpolated verbatim, so the caller supplies the article: passing "an Event for the Sponsor" yields "You must specify an Event for the Sponsor". WithErrorCode is contract, not decoration: Sponsor.EventId.Required is what an API client or a test keys on, while the message is the human-facing half.
  • Why it's built this way: the XML doc states the business reason directly (:110-114), that sponsors are sold per event, so an unscoped sponsor has nowhere to appear. This rule is also the only enforcement of that fact at the application boundary: the Sponsor aggregate's Create composes name, logo URL, and booth number invariants but does not re-check the event id (MMCA.ADC.Conference.Domain/Sponsors/Sponsor.cs:120-122), because a zero-valued foreign key would already fail at the database.
  • Where it's used: included exactly once, by SponsorCreateRequestValidator as its create-only delta, with the reason written in a comment above the line (MMCA.ADC.Conference.Application/Sponsors/UseCases/Create/SponsorCreateRequestValidator.cs:13-15). SponsorUpdateRequestValidator includes only the shared set (MMCA.ADC.Conference.Application/Sponsors/UseCases/Update/SponsorUpdateRequestValidator.cs:10), because SponsorUpdateRequest carries no event id at all.
  • @@ -3165,8 +3202,8 @@

    SponsorSortRules<T>

    • What it is: the reusable rule that a sponsor's display order is not negative.
    • Depends on: NonNegativeIntRules<T> from MMCA.Common.Application.Validation (SponsorValidationRules.cs:4,127) and Expression<Func<T, int>> (:1,129). No Domain constant is involved, which is why it sits at Level 1 alongside SponsorEventIdRules<T> rather than with the length rules.
    • -
    • Concept introduced: none new; it is the parameterized rule set taught at SponsorEventIdRules<T>, applied to an integer. Worth noting is that the rule is a floor, not a range: GreaterThanOrEqualTo(0) (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:126) leaves the upper end open, so an organizer can insert a sponsor anywhere in a tier by choosing a large number rather than renumbering the rest. [Rubric §24, Forms/Validation/UX Safety]: the one constraint that has a real meaning is declared, and nothing more is invented.
    • -
    • Walkthrough: sealed class SponsorSortRules<T> : NonNegativeIntRules<T> (:126-127) with an expression-bodied constructor forwarding (selector, "Sort", "Sponsor.Sort.Negative") (:129-130). The base produces the message "Sort must be greater than or equal to 0" and stamps the error code on the rule (CommonValidationRules.cs:124-126).
    • +
    • Concept introduced: none new; it is the parameterized rule set taught at SponsorEventIdRules<T>, applied to an integer. Worth noting is that the rule is a floor, not a range: GreaterThanOrEqualTo(0) (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:127) leaves the upper end open, so an organizer can insert a sponsor anywhere in a tier by choosing a large number rather than renumbering the rest. [Rubric §24, Forms/Validation/UX Safety]: the one constraint that has a real meaning is declared, and nothing more is invented.
    • +
    • Walkthrough: sealed class SponsorSortRules<T> : NonNegativeIntRules<T> (:126-127) with an expression-bodied constructor forwarding (selector, "Sort", "Sponsor.Sort.Negative") (:129-130). The base produces the message "Sort must be greater than or equal to 0" and stamps the error code on the rule (CommonValidationRules.cs:125-127).
    • Where it's used: not included directly by either request validator; it is folded into the shared bundle by SponsorFieldRules<T> (SponsorValidationRules.cs:146), which both validators include. That indirection is the reuse this file is built for.

    SponsorBoothNumberRules<T>

    @@ -3176,7 +3213,7 @@

    SponsorBoothNumberRules<T>

    • What it is: the reusable length rule for a sponsor's optional expo booth number.
    • Depends on: OptionalStringRules<T> from MMCA.Common.Application.Validation (SponsorValidationRules.cs:4,104) and SponsorInvariants for the constant BoothNumberMaxLength (:3,107).
    • -
    • Concept introduced: the three-argument subclass, and where a length constant really lives. Four of the rule sets in this file (this one, description, name, Twitter handle) are a two-line sealed class whose constructor forwards to a shared MMCA.Common base with three arguments: the property selector, a human-facing field label, and a max length. The base does the work, RuleFor(selector).MaximumLength(maxLength) with a generated message (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:55-57). Two design points are worth internalizing. First, the subclass exists purely to name the pairing of a field with its constant, so callers write new SponsorBoothNumberRules<T>(p => p.BoothNumber) and cannot accidentally bind the booth-number field to the description's length. Second, follow the constant back and it does not stop at the Domain layer: SponsorInvariants.BoothNumberMaxLength is itself = SponsorDTO.BoothNumberMaxLength (MMCA.ADC.Conference.Domain/Sponsors/SponsorInvariants.cs:34), and the literal 50 is declared on the DTO in the Shared project (MMCA.ADC.Conference.Shared/Sponsors/SponsorDTO.cs:36), which the invariants file explains is the lowest layer the UI can also reach, so markup and domain validation cannot drift apart (SponsorInvariants.cs:7-12). The EF configuration reads the same constant for the column width (MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Sponsors/SponsorConfiguration.cs:56). One number therefore governs the input control, the validator's message, the domain guard, and the column, so a 51-character booth number is rejected with a readable error instead of truncating or throwing at the database. That dependency on the Domain constant is why these seven sit at Level 7 while the two rule sets above sit at Level 1. [Rubric §8, Data Architecture] assesses whether storage constraints and application constraints agree; [Rubric §15, Best Practices & Code Quality]: widening a column is a one-constant change that propagates to every layer that cares.
    • +
    • Concept introduced: the three-argument subclass, and where a length constant really lives. Four of the rule sets in this file (this one, description, name, Twitter handle) are a two-line sealed class whose constructor forwards to a shared MMCA.Common base with three arguments: the property selector, a human-facing field label, and a max length. The base does the work, RuleFor(selector).MaximumLength(maxLength) with a generated message (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:56-58). Two design points are worth internalizing. First, the subclass exists purely to name the pairing of a field with its constant, so callers write new SponsorBoothNumberRules<T>(p => p.BoothNumber) and cannot accidentally bind the booth-number field to the description's length. Second, follow the constant back and it does not stop at the Domain layer: SponsorInvariants.BoothNumberMaxLength is itself = SponsorDTO.BoothNumberMaxLength (MMCA.ADC.Conference.Domain/Sponsors/SponsorInvariants.cs:34), and the literal 50 is declared on the DTO in the Shared project (MMCA.ADC.Conference.Shared/Sponsors/SponsorDTO.cs:36), which the invariants file explains is the lowest layer the UI can also reach, so markup and domain validation cannot drift apart (SponsorInvariants.cs:7-12). The EF configuration reads the same constant for the column width (MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Sponsors/SponsorConfiguration.cs:56). One number therefore governs the input control, the validator's message, the domain guard, and the column, so a 51-character booth number is rejected with a readable error instead of truncating or throwing at the database. That dependency on the Domain constant is why these seven sit at Level 7 while the two rule sets above sit at Level 1. [Rubric §8, Data Architecture] assesses whether storage constraints and application constraints agree; [Rubric §15, Best Practices & Code Quality]: widening a column is a one-constant change that propagates to every layer that cares.
    • Walkthrough: sealed class SponsorBoothNumberRules<T> : OptionalStringRules<T> (:103-104); the constructor takes Expression<Func<T, string?>> selector (:106) and calls : base(selector, "Booth Number", SponsorInvariants.BoothNumberMaxLength) (:107). There is no body. The selector type is nullable, which is the whole difference between the optional base and the required one: a null booth number passes.
    • Why it's built this way: the field is optional in the domain too. SponsorInvariants.EnsureBoothNumberIsValid delegates to CommonInvariants.EnsureOptionalStringMaxLength with the same constant and the error code Sponsor.BoothNumber.TooLong (SponsorInvariants.cs:64-65), and its doc comment records the deliberate rule that a booth number is accepted even when the sponsor is not flagged as an exhibitor, because the flag drives display and does not reject stored data (:57-60).
    • Where it's used: included by SponsorFieldRules<T> (SponsorValidationRules.cs:152), so it reaches both sponsor request validators; re-checked in the aggregate through SponsorInvariants.EnsureBoothNumberIsValid on both Create and Update (MMCA.ADC.Conference.Domain/Sponsors/Sponsor.cs:122,168).
    • @@ -3200,7 +3237,7 @@

      SponsorLinkedInUrlRules<T>

      • What it is: the reusable rule for the optional sponsor LinkedIn URL: bounded length plus an absolute http/https check, skipped entirely when the field is blank.
      • Depends on: AbstractValidator<T> from FluentValidation (SponsorValidationRules.cs:2,75), AbsoluteUrlRules<T> from MMCA.Common.Application.Validation (:4,82), and SponsorInvariants.LinkedInUrlMaxLength (:3,82, = SponsorDTO.LinkedInUrlMaxLength at SponsorInvariants.cs:28, literal 2000 at SponsorDTO.cs:30).
      • -
      • Concept introduced: the conditional rule set, and why a URL field is not just a bounded string. This is the first of the three URL rule sets in reading order, so learn the shape here; logo and website are the same construct. Unlike the length-only siblings it does not derive from a rule base, it contains one. The constructor compiles the selector once into an accessor (:79) and wraps the include in When(x => !string.IsNullOrWhiteSpace(accessor(x)), () => Include(new AbsoluteUrlRules<T>(...))) (:81-82), so an empty value passes untouched and a supplied value gets both the length bound and a scheme check. The check itself is the point: the base's Must(BeAnAbsoluteHttpUrl) delegates to CommonInvariants.EnsureUrlIsWellFormed (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:90,92-93), and the base's own remarks state the threat plainly, that bounded-string treatment accepts javascript: and data: values which become executable the moment a link or an image renders them (:77-83). The XML doc here repeats the local reason: the sponsor pages render the value straight into a link target (:68-73). [Rubric §11, Security] assesses whether untrusted input is constrained at the boundary in the shape it will be used: a stored URL is an injection vector, so the scheme is validated where the value enters, not where it renders. [Rubric §15, Best Practices & Code Quality]: the validator and the domain invariant answer through the same helper, so they cannot disagree.
      • +
      • Concept introduced: the conditional rule set, and why a URL field is not just a bounded string. This is the first of the three URL rule sets in reading order, so learn the shape here; logo and website are the same construct. Unlike the length-only siblings it does not derive from a rule base, it contains one. The constructor compiles the selector once into an accessor (:79) and wraps the include in When(x => !string.IsNullOrWhiteSpace(accessor(x)), () => Include(new AbsoluteUrlRules<T>(...))) (:81-82), so an empty value passes untouched and a supplied value gets both the length bound and a scheme check. The check itself is the point: the base's Must(BeAnAbsoluteHttpUrl) delegates to CommonInvariants.EnsureUrlIsWellFormed (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:91,93-94), and the base's own remarks state the threat plainly, that bounded-string treatment accepts javascript: and data: values which become executable the moment a link or an image renders them (:77-83). The XML doc here repeats the local reason: the sponsor pages render the value straight into a link target (:68-73). [Rubric §11, Security] assesses whether untrusted input is constrained at the boundary in the shape it will be used: a stored URL is an injection vector, so the scheme is validated where the value enters, not where it renders. [Rubric §15, Best Practices & Code Quality]: the validator and the domain invariant answer through the same helper, so they cannot disagree.
      • Walkthrough: sealed class SponsorLinkedInUrlRules<T> : AbstractValidator<T> (:74-75); the constructor takes Expression<Func<T, string?>> selector (:77), compiles it to a delegate (:79), and registers the conditional include with the label "LinkedIn URL" (:81-82). Two mechanics are worth noticing: Include inside a When block applies the condition to every rule the included validator declares, and the accessor is compiled once at construction rather than per validation pass, since the rule set instance is built once per validator instance.
      • Why it's built this way: the length bound alone would have let a hostile value through while still fitting the column, and adding an ad-hoc Matches regex here would have created a second definition of "valid URL" that could drift from the domain's. Composing the shared AbsoluteUrlRules<T> keeps one definition and lets the blank case stay explicit and local.
      • Where it's used: included by SponsorFieldRules<T> (SponsorValidationRules.cs:150).
      • @@ -3226,8 +3263,8 @@

        SponsorNameRules<T>

        • What it is: the reusable rule set for the sponsor's display name, the one sponsor string that is mandatory as well as bounded.
        • Depends on: RequiredStringRules<T> from MMCA.Common.Application.Validation (SponsorValidationRules.cs:4,14) and SponsorInvariants.NameMaxLength (:17, = SponsorDTO.NameMaxLength at SponsorInvariants.cs:16, literal 200 at SponsorDTO.cs:18).
        • -
        • Concept introduced: required versus optional, chosen by base class. This is the one string rule set in the file that derives from RequiredStringRules<T> rather than OptionalStringRules<T>, and that single choice is the whole difference in behavior. The required base chains NotEmpty() ahead of MaximumLength(...) and takes a non-nullable Expression<Func<T, string>> selector (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:43-46); the optional base takes a nullable selector and declares only the length rule (:55-57). So "is this field mandatory?" is answered once, by which base you extend, and the compiler helps: binding a string? property to this rule set will not compile, which is also why ISponsorFieldsRequest declares Name as non-nullable and the other seven as nullable or value types. [Rubric §1, SOLID]: two small bases, each with one responsibility, compose into every field contract in the module. [Rubric §24, Forms/Validation/UX Safety]: mandatory-ness is declared in one place per field rather than restated per request record.
        • -
        • Walkthrough: sealed class SponsorNameRules<T> : RequiredStringRules<T> (:13-14); the constructor takes Expression<Func<T, string>> selector (:16) and forwards (selector, "Sponsor Name", SponsorInvariants.NameMaxLength) (:17). The base produces two messages: "You must enter a Sponsor Name" and "Sponsor Name cannot be longer than 200 characters" (CommonValidationRules.cs:45-46). No error code is passed, so WithOptionalErrorCode leaves both rules coded by FluentValidation's default (CommonValidationRules.cs:30-32).
        • +
        • Concept introduced: required versus optional, chosen by base class. This is the one string rule set in the file that derives from RequiredStringRules<T> rather than OptionalStringRules<T>, and that single choice is the whole difference in behavior. The required base chains NotEmpty() ahead of MaximumLength(...) and takes a non-nullable Expression<Func<T, string>> selector (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:44-47); the optional base takes a nullable selector and declares only the length rule (:55-57). So "is this field mandatory?" is answered once, by which base you extend, and the compiler helps: binding a string? property to this rule set will not compile, which is also why ISponsorFieldsRequest declares Name as non-nullable and the other seven as nullable or value types. [Rubric §1, SOLID]: two small bases, each with one responsibility, compose into every field contract in the module. [Rubric §24, Forms/Validation/UX Safety]: mandatory-ness is declared in one place per field rather than restated per request record.
        • +
        • Walkthrough: sealed class SponsorNameRules<T> : RequiredStringRules<T> (:13-14); the constructor takes Expression<Func<T, string>> selector (:16) and forwards (selector, "Sponsor Name", SponsorInvariants.NameMaxLength) (:17). The base produces two messages: "You must enter a Sponsor Name" and "Sponsor Name cannot be longer than 200 characters" (CommonValidationRules.cs:46-47). No error code is passed, so WithOptionalErrorCode leaves both rules coded by FluentValidation's default (CommonValidationRules.cs:31-33).
        • Why it's built this way: validation here is the fast, message-friendly first pass, not the authority. The Sponsor aggregate re-checks the same rule through SponsorInvariants.EnsureNameIsValid in both Create and Update (MMCA.ADC.Conference.Domain/Sponsors/Sponsor.cs:120,166), which returns Result errors carrying the stable codes Sponsor.Name.Empty and Sponsor.Name.TooLong (SponsorInvariants.cs:44-45). A caller that bypasses the request pipeline still cannot create a nameless sponsor.
        • Where it's used: included by SponsorFieldRules<T> as the first entry of the shared bundle (SponsorValidationRules.cs:145).
        @@ -3265,7 +3302,7 @@

        SponsorFieldRules<T>

      • Concept introduced: the shared bundle, and the create/update delta as one visible line. The parameterized rule sets only pay off if request validators are assembled from them, and this class is the assembly step for everything both sponsor operations have in common. Because T is constrained to ISponsorFieldsRequest, every selector is written against the interface member (p => p.Name, p => p.Sort, and so on), so the bundle binds to any implementing record without naming it. The instructive part is what is left out: this class does not include SponsorEventIdRules<T>, and neither does the update validator, so the difference between the two validators is a single extra Include on the create side with the reason written beside it (MMCA.ADC.Conference.Application/Sponsors/UseCases/Create/SponsorCreateRequestValidator.cs:11-15 versus MMCA.ADC.Conference.Application/Sponsors/UseCases/Update/SponsorUpdateRequestValidator.cs:10). Auditing "does the update path enforce everything the create path does?" is a two-file read with one line of difference. [Rubric §24, Forms/Validation/UX Safety] assesses whether every write entry point applies the same field constraints: the shared set is literally one shared object, and the only difference is structural rather than an oversight. [Rubric §15, Best Practices & Code Quality]: adding a sponsor field means adding one property to the interface, one rule set, and one Include here, after which both operations enforce it.
      • Walkthrough: sealed class SponsorFieldRules<T> : AbstractValidator<T> where T : ISponsorFieldsRequest (:139-141) with a parameterless constructor (:143-153) containing eight Include(new XRules<T>(p => p.Field)) statements in the order name, sort, logo URL, description, website URL, LinkedIn URL, Twitter handle, booth number. FluentValidation's Include merges each included validator's rules into this one, and this validator is in turn included by the request validators, so the rules flatten into a single rule list by the time the pipeline runs.
      • Why it's built this way: the alternative, repeating eight Include lines in each request validator, worked but made the two lists independent artifacts that had to be compared by eye. Hoisting them behind an interface constraint makes the shared set a single definition and reduces each request validator to its own delta, which is the same shape ActivityFieldRules<T> uses for activities.
      • -
      • Where it's used: included by SponsorCreateRequestValidator (SponsorCreateRequestValidator.cs:11) and SponsorUpdateRequestValidator (SponsorUpdateRequestValidator.cs:10). Neither is constructed by hand: ScanModuleApplicationServices calls FluentValidation's AddValidatorsFromAssembly(moduleAssembly) (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:258, invoked at MMCA.ADC.Conference.Application/DependencyInjection.cs:143), so the request validators are registered and resolved by the pipeline.
      • +
      • Where it's used: included by SponsorCreateRequestValidator (SponsorCreateRequestValidator.cs:11) and SponsorUpdateRequestValidator (SponsorUpdateRequestValidator.cs:10). Neither is constructed by hand: ScanModuleApplicationServices calls FluentValidation's AddValidatorsFromAssembly(moduleAssembly) (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:258, invoked at MMCA.ADC.Conference.Application/DependencyInjection.cs:139), so the request validators are registered and resolved by the pipeline.

      ActivityUpdateApplier

      @@ -3274,7 +3311,7 @@

      ActivityUpdateApplier

      • What it is: the six-line class that turns an ActivityUpdateRequest into a call on the Activity aggregate's guarded Update method. It is the only activity-specific code on the update path; everything else is the framework's generic handler.
      • Depends on: IEntityUpdateApplier<TEntity, TUpdateRequest, TIdentifierType> from MMCA.Common.Application.Interfaces.Mapping (MMCA.ADC.Conference.Application/Activities/UseCases/Update/ActivityUpdateApplier.cs:2,17), the Activity aggregate (:1,17,20), ActivityUpdateRequest (:17,20), Result from MMCA.Common.Shared.Abstractions (:3,20), and the ActivityIdentifierType alias (:17).
      • -
      • Concept introduced: the update applier, the one extension point of the generic write path. ADC no longer hand-writes an update handler per aggregate. The module registers services.AddEntityCrud<Activity, ActivityDTO, ActivityIdentifierType, ActivityCreateRequest, ActivityUpdateRequest>() (MMCA.ADC.Conference.Application/DependencyInjection.cs:154), which binds ICommandHandler<UpdateEntityCommand<Activity, ActivityUpdateRequest, ActivityIdentifierType>, Result<ActivityDTO>> to the framework's UpdateEntityHandler<TEntity, TEntityDTO, TIdentifierType, TUpdateRequest> with TryAdd semantics (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Crud.cs:86-88). That generic handler loads the entity, stamps the row version, and saves, but it cannot know a field name, so it delegates the mutation to an injected IEntityUpdateApplier<...> (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/UpdateEntityHandler.cs:50,84-92). This class is that delegate for activities, and it is registered by convention: ScanModuleApplicationServices scans the module assembly for IEntityUpdateApplier<,,> implementations and registers them scoped, with the comment stating the intent, that appliers wrap the aggregate's guarded mutation methods so the generic handler never has to know a field name (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:227-235). [Rubric §2, Design Patterns] assesses whether variation is isolated behind a small abstraction: the whole per-entity variation of an update is one method. [Rubric §4, DDD]: the applier calls the aggregate's method rather than assigning properties, so invariants and the domain event stay inside the aggregate. [Rubric §14, Testability]: the class has no dependencies at all, so it can be exercised with a plain in-memory Activity and no test host.
      • +
      • Concept introduced: the update applier, the one extension point of the generic write path. ADC no longer hand-writes an update handler per aggregate. The module registers services.AddEntityCrud<Activity, ActivityDTO, ActivityIdentifierType, ActivityCreateRequest, ActivityUpdateRequest>() (MMCA.ADC.Conference.Application/DependencyInjection.cs:150), which binds ICommandHandler<UpdateEntityCommand<Activity, ActivityUpdateRequest, ActivityIdentifierType>, Result<ActivityDTO>> to the framework's UpdateEntityHandler<TEntity, TEntityDTO, TIdentifierType, TUpdateRequest> with TryAdd semantics (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Crud.cs:86-88). That generic handler loads the entity, stamps the row version, and saves, but it cannot know a field name, so it delegates the mutation to an injected IEntityUpdateApplier<...> (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/UpdateEntityHandler.cs:50,84-92). This class is that delegate for activities, and it is registered by convention: ScanModuleApplicationServices scans the module assembly for IEntityUpdateApplier<,,> implementations and registers them scoped, with the comment stating the intent, that appliers wrap the aggregate's guarded mutation methods so the generic handler never has to know a field name (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:227-235). [Rubric §2, Design Patterns] assesses whether variation is isolated behind a small abstraction: the whole per-entity variation of an update is one method. [Rubric §4, DDD]: the applier calls the aggregate's method rather than assigning properties, so invariants and the domain event stay inside the aggregate. [Rubric §14, Testability]: the class has no dependencies at all, so it can be exercised with a plain in-memory Activity and no test host.
      • Walkthrough: sealed class ActivityUpdateApplier : IEntityUpdateApplier<Activity, ActivityUpdateRequest, ActivityIdentifierType> (:16-17). ApplyAsync (:20) is synchronous work returned as a completed task: it null-guards both arguments (:22-23) and returns Task.FromResult(entity.Update(...)) passing the eight request members positionally (:25-33). Everything downstream of that call belongs to the domain: Activity.Update combines the name, time-range, venue-name, venue-address, and venue-URL invariants and returns early on failure before mutating anything (MMCA.ADC.Conference.Domain/Activities/Activity.cs:155-162), then assigns the fields (:164-171) and raises ActivityChanged with DomainEntityState.Updated (:173). The refusal travels back as a Result, and the handler's base workflow stops the write before SaveChangesAsync. The <remarks> records the deliberate omission: the request carries no EventId, so the aggregate's parent reference is untouched here (:12-15).
      • Why it's built this way: the framework handler documents that no events are raised in the handler because domain events belong to the aggregate's own mutation methods, and that a handler publishing anything of its own would fire for the generic path and stay silent for a hand-written one (UpdateEntityHandler.cs:34-37). Keeping the applier a pure delegation preserves that property. It also keeps the repository out of reach: the generic handler takes its repository from IUnitOfWork and never constructor-injects one, because only the unit of work knows which physical data source the aggregate resolves to (UpdateEntityHandler.cs:39-41).
      • Where it's used: never constructed by hand. It is resolved as IEntityUpdateApplier<Activity, ActivityUpdateRequest, ActivityIdentifierType> into the generic handler that ActivitiesController injects as ICommandHandler<UpdateEntityCommand<Activity, ActivityUpdateRequest, ActivityIdentifierType>, Result<ActivityDTO>> (MMCA.ADC.Conference.API/Controllers/Activities/ActivitiesController.cs:43) and invokes from PUT {id} (:193-195). The sponsors slice has the same shape in SponsorUpdateApplier.
      • @@ -3289,7 +3326,7 @@

        ActivityUpdateRequestValidator

      • Concept introduced: none new; this is the shared-bundle composition taught at SponsorFieldRules<T>, on the activities side. What the pairing shows is the delta discipline in its smallest possible form: this validator is a single Include(new ActivityFieldRules<ActivityUpdateRequest>()) (:10), while ActivityCreateRequestValidator includes the same bundle plus ActivityEventIdRules<ActivityCreateRequest> with the reason in a comment (MMCA.ADC.Conference.Application/Activities/UseCases/Create/ActivityCreateRequestValidator.cs:11-15). Everything the create path enforces on a shared field, the update path enforces by construction, including the cross-field rules the bundle carries: ActivityFieldRules<T> includes a time-range rule set that takes two selectors, start and end, alongside the name, sort-order, description, and three venue rule sets (MMCA.ADC.Conference.Application/Activities/Validation/ActivityValidationRules.cs:135-141). [Rubric §5, Vertical Slice]: the validator lives in the UseCases/Update folder beside the request and applier it serves, not in a module-wide validators bucket.
      • Walkthrough: sealed class ActivityUpdateRequestValidator : AbstractValidator<ActivityUpdateRequest> (:7) with an expression-bodied constructor (:9-10). Three lines of body, no branching. Note the generic argument: the bundle is constrained where T : IActivityFieldsRequest (ActivityValidationRules.cs:131), which ActivityUpdateRequest satisfies (ActivityUpdateRequest.cs:10), so the compiler rejects a request record that is missing a shared field.
      • Why it's built this way: cross-field ordering (end after start) cannot be expressed by a per-property rule set, so it is packaged inside the bundle rather than written inline here. That keeps this class purely declarative: it cannot drift from the create path except by the one line that differs, which is visible in review.
      • -
      • Where it's used: never constructed by hand. ScanModuleApplicationServices calls AddValidatorsFromAssembly(moduleAssembly) (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:258, invoked at MMCA.ADC.Conference.Application/DependencyInjection.cs:143), so this validator is registered as IValidator<ActivityUpdateRequest>. AddEntityCrud then registers a CommandRequestValidator<TCommand, TRequest> bridging UpdateEntityCommand<Activity, ActivityUpdateRequest, ActivityIdentifierType> to it (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Crud.cs:94-96), and ValidatingCommandDecorator<TCommand, TResult> runs it before the handler.
      • +
      • Where it's used: never constructed by hand. ScanModuleApplicationServices calls AddValidatorsFromAssembly(moduleAssembly) (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:258, invoked at MMCA.ADC.Conference.Application/DependencyInjection.cs:139), so this validator is registered as IValidator<ActivityUpdateRequest>. AddEntityCrud then registers a CommandRequestValidator<TCommand, TRequest> bridging UpdateEntityCommand<Activity, ActivityUpdateRequest, ActivityIdentifierType> to it (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Crud.cs:94-96), and ValidatingCommandDecorator<TCommand, TResult> runs it before the handler.

      GetPublicSponsorFilterHandler

      @@ -3312,7 +3349,7 @@

      CategoryItemSortRules<T>

    • What it is: a two-line FluentValidation rule set stating that a category item's display sort order may not be negative, bound to whichever int property the caller points it at.
    • Depends on: NonNegativeIntRules<T> from MMCA.Common.Application.Validation, which it derives from (ConferenceCategoryValidationRules.cs:5,42), and Expression<Func<T, int>> from System.Linq.Expressions (:2,44). FluentValidation's AbstractValidator<T> arrives through the base class, not through a direct reference.
    • Concept introduced: the selector-parameterized rule set, specialized by inheritance. A rule set in this codebase is a validator generic over the request type T whose constructor takes an expression naming the property to check. That is what lets one rule serve unrelated request shapes: AddCategoryItemCommand and UpdateCategoryItemCommand share no base type, yet both hand p => p.Sort to this class. Three specialization styles sit in this one file. ConferenceCategoryTitleRules<T> and CategoryItemNameRules<T> write their RuleFor chain out in full; this type instead subclasses a Common rule set and supplies nothing but vocabulary, a field phrase and an error code (:44-45). Inheritance is the right tool here precisely because the predicate is already generic (any non-negative int) and only the naming is local. [Rubric §1, SOLID] assesses whether a type has one reason to change: the bound lives in Common and this subclass changes only when the field is renamed. [Rubric §24, Forms/Validation/UX Safety] assesses whether every inbound field carries a contract at the boundary rather than only in the UI: a negative sort order is refused server-side. [Rubric §15, Best Practices & Code Quality]: the check exists once for the whole workspace.
    • -
    • Walkthrough: sealed class CategoryItemSortRules<T> : NonNegativeIntRules<T> (:41-42). The constructor (:44-45) is an expression-bodied base call passing the selector plus the two literals "Sort order" and "CategoryItem.Sort.Negative", and that is the entire body. The behavior lives in the base (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:122-126): RuleFor(selector).GreaterThanOrEqualTo(0) with the message "{fieldName} must be greater than or equal to 0" and WithOptionalErrorCode(errorCode), so a caller that passes no code gets FluentValidation's default and this caller gets the stable one.
    • +
    • Walkthrough: sealed class CategoryItemSortRules<T> : NonNegativeIntRules<T> (:41-42). The constructor (:44-45) is an expression-bodied base call passing the selector plus the two literals "Sort order" and "CategoryItem.Sort.Negative", and that is the entire body. The behavior lives in the base (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:123-127): RuleFor(selector).GreaterThanOrEqualTo(0) with the message "{fieldName} must be greater than or equal to 0" and WithOptionalErrorCode(errorCode), so a caller that passes no code gets FluentValidation's default and this caller gets the stable one.
    • Why it's built this way: the comparison is GreaterThanOrEqualTo(0) and not GreaterThan(0) because zero is the legitimate first position in a sort sequence, so the rule has to admit it. Splitting the human message from a machine-readable code is the same split used everywhere in this module: the message is what an organizer reads, the code is what a client branches on, and only the code is contractual.
    • Where it's used: AddCategoryItemCommandValidator (MMCA.ADC.Conference.Application/Categories/UseCases/AddCategoryItem/AddCategoryItemCommandValidator.cs:12) and UpdateCategoryItemCommandValidator (MMCA.ADC.Conference.Application/Categories/UseCases/UpdateCategoryItem/UpdateCategoryItemCommandValidator.cs:12). Those are the only two binding sites in the module; both commands declare Sort as a positional int (AddCategoryItemCommand.cs:18, UpdateCategoryItemCommand.cs:18).
    @@ -3323,11 +3360,11 @@

    EventUpdateRequest

    • What it is: the full-replacement payload for editing a conference Event: identity and schedule (name, description, the two dates, the time zone), the Sessionize link code, the venue and logistics fields an attendee sees, the per-event question moderation default, and the outward-facing contact and URLs an edition publishes.
    • Depends on: IEventFieldsRequest from MMCA.ADC.Conference.Application.Events.Validation, which it implements (EventUpdateRequest.cs:1,7), and the QuestionModerationDefault enum from MMCA.ADC.Conference.Shared.Events (:2,37). Every other member is a BCL primitive, including DateOnly for the two dates (:16,19).
    • -
    • Concept introduced: the precondition is not in the body. This record carries no RowVersion member at all. The caller's last-observed optimistic-concurrency token travels in the HTTP If-Match header, is decoded by SupportsIfMatchAttribute into the request's Items bag, and is read back by the action through SupportsIfMatchAttribute.RequiredToken(HttpContext) (MMCA.Common/Source/Presentation/MMCA.Common.API/Concurrency/SupportsIfMatchAttribute.cs:68-76; the call site is MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:224). The consequence for this type is that it stays a pure domain payload: it cannot express "I do not know what I read", because a request that states no precondition is answered 428 Precondition Required by the filter and never reaches the action at all (EventsController.cs:220-223). [Rubric §9, API & Contract Design] assesses whether the protocol's own semantics are used where they exist: conditional requests are an HTTP feature, and modeling them as a nullable body field would have made the guarantee opt-out. [Rubric §8, Data Architecture] assesses how concurrent writes to one row are reconciled: optimistic concurrency with a caller-supplied token, per ADR-035, rather than pessimistic locking or last-write-wins.
    • +
    • Concept introduced: the precondition is not in the body. This record carries no RowVersion member at all. The caller's last-observed optimistic-concurrency token travels in the HTTP If-Match header, is decoded by SupportsIfMatchAttribute into the request's Items bag, and is read back by the action through SupportsIfMatchAttribute.RequiredToken(HttpContext) (MMCA.Common/Source/Presentation/MMCA.Common.API/Concurrency/SupportsIfMatchAttribute.cs:68-76; the call site is MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:231). The consequence for this type is that it stays a pure domain payload: it cannot express "I do not know what I read", because a request that states no precondition is answered 428 Precondition Required by the filter and never reaches the action at all (EventsController.cs:220-223). [Rubric §9, API & Contract Design] assesses whether the protocol's own semantics are used where they exist: conditional requests are an HTTP feature, and modeling them as a nullable body field would have made the guarantee opt-out. [Rubric §8, Data Architecture] assesses how concurrent writes to one row are reconciled: optimistic concurrency with a caller-supplied token, per ADR-035, rather than pessimistic locking or last-write-wins.
    • Concept introduced: date-only scheduling plus a named time zone, rather than an offset. The event's span is two DateOnly values (:16,19) and its TimeZone is a string documented as an IANA identifier (:21-22). Nothing here is a DateTimeOffset, so the record cannot bake in a UTC offset that is wrong half the year: the calendar day is the fact, and the zone id is how a consumer resolves a wall-clock session time to an instant. That is also what makes the time zone load-bearing enough to earn its own advisory rule on the update path (BR-131, see UpdateEventHandler). [Rubric §27, i18n]: an IANA id is the portable, culture-neutral way to express when this conference happens, and it is checked against the host's time zone database by EventTimeZoneRules<T> (MMCA.ADC.Conference.Application/Events/Validation/EventValidationRules.cs:27,43,46).
    • Walkthrough: four members are required and therefore cannot be omitted by a caller: Name (:10), StartDate (:16), EndDate (:19) and TimeZone (:22). The optional strings are Description (:13), SessionizeCode (:25, the code tying this edition to a Sessionize event feed), VenueAddress (:28), VenueMapUrl (:31), WiFiInfo (:34), OrganizerContactEmail (:40), SponsorshipPacketUrl (:43) and TicketingUrl (:46). QuestionModerationDefault (:37) is the one enum member, documented as the BR-233 moderation default for live-layer session questions. Every member is init-only, and seven of them satisfy the get-only contract of IEventFieldsRequest (MMCA.ADC.Conference.Application/Events/Validation/IEventFieldsRequest.cs:12-37), which is what lets one shared rule set validate both the create and the update body.
    • Why it's built this way: the update request carries one field the create request does not. EventCreateRequest implements the same interface (MMCA.ADC.Conference.Application/Events/UseCases/Create/EventCreateRequest.cs:11) but has no QuestionModerationDefault member, and EventCreateRequestMapper skips that argument entirely when it calls the factory (MMCA.ADC.Conference.Application/Events/UseCases/Create/EventCreateRequestMapper.cs:19-32), so a new event takes the domain default Pending (MMCA.ADC.Conference.Domain/Events/Event.cs:181; enum members at MMCA.ADC.Conference.Shared/Events/Live/QuestionModerationDefault.cs:10,13). Moderation posture is therefore something an organizer opts into after the event exists rather than a decision forced at creation time, and the cautious value is the one you get by default. [Rubric §11, Security] assesses whether defaults fail safe: unmoderated display of attendee-submitted text is the riskier state, and it is never the implicit one.
    • -
    • Where it's used: bound from the body by EventsController on PUT {id} (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:214-222), validated by EventUpdateRequestValidator, wrapped together with the If-Match token in UpdateEventCommand (:232), and passed field by field into Event.Update by UpdateEventHandler (UpdateEventHandler.cs:59-72).
    • +
    • Where it's used: bound from the body by EventsController on PUT {id} (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:221-229), validated by EventUpdateRequestValidator, wrapped together with the If-Match token in UpdateEventCommand (:232), and passed field by field into Event.Update by UpdateEventHandler (UpdateEventHandler.cs:59-72).

    IPartnerFieldsRequest

    @@ -3348,7 +3385,7 @@

    PartnerEventIdRules<T>

    • What it is: the rule set requiring that a partner's owning event is specified: partners are listed per event, so an unscoped partner has nowhere to appear.
    • Depends on: RequiredIdRules<T, TId> from MMCA.Common.Application.Validation, which it derives from (:56), and the EventIdentifierType module alias (:57).
    • -
    • Concept introduced: none new; the same selector-parameterized specialization taught by CategoryItemSortRules<T>: the constructor supplies nothing but the field phrase and the error code, and the base does the work (RequiredIdRules<T, TId> checks the field is not the identifier's default, MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:142-148).
    • +
    • Concept introduced: none new; the same selector-parameterized specialization taught by CategoryItemSortRules<T>: the constructor supplies nothing but the field phrase and the error code, and the base does the work (RequiredIdRules<T, TId> checks the field is not the identifier's default, MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:143-149).
    • Walkthrough: sealed class PartnerEventIdRules<T> : RequiredIdRules<T, EventIdentifierType> (:55-56). The constructor (:58) passes the field phrase "an Event for the Partner" and the code Partner.EventId.Required.
    • Why it's built this way: this rule is included only by PartnerCreateRequestValidator, never by the update validator, because IPartnerFieldsRequest does not carry EventId at all (see that section).
    • Where it's used: PartnerCreateRequestValidator.cs:15, included alongside PartnerFieldRules<T>.
    • @@ -3372,7 +3409,7 @@

      UpdateEventResult

      • What it is: the two-member envelope UpdateEventHandler returns: the updated EventDTO plus a boolean saying whether this particular update changed the time zone while sessions already existed.
      • Depends on: EventDTO from MMCA.ADC.Conference.Shared.Events (UpdateEventCommand.cs:2,25). Nothing else; the second member is a bool.
      • -
      • Concept introduced: the advisory result, distinct from success and from failure. The Result pattern gives a handler two outcomes, success with a value or failure with errors. BR-131 is neither: changing an event's time zone after sessions are scheduled does not violate an invariant (the write is legitimate and must be persisted), but it does change what every already-stored session time means. Rejecting it would be wrong, and silently accepting it would be worse. The answer here is a third channel carried inside the success value, a flag the caller can act on. This is a small type with a large lesson, namely that "succeeded, with something you should know" deserves a first-class shape rather than a log line the operator never reads. [Rubric §9, API & Contract Design] assesses how non-fatal conditions are conveyed: EventsController translates the flag into an X-Warning response header and still returns 200 with the DTO (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:234-242), so the body stays exactly the EventDTO the API contract promises and the advisory rides beside it. [Rubric §13, Observability & Operability]: the condition is surfaced to the human who caused it, at the moment they caused it.
      • +
      • Concept introduced: the advisory result, distinct from success and from failure. The Result pattern gives a handler two outcomes, success with a value or failure with errors. BR-131 is neither: changing an event's time zone after sessions are scheduled does not violate an invariant (the write is legitimate and must be persisted), but it does change what every already-stored session time means. Rejecting it would be wrong, and silently accepting it would be worse. The answer here is a third channel carried inside the success value, a flag the caller can act on. This is a small type with a large lesson, namely that "succeeded, with something you should know" deserves a first-class shape rather than a log line the operator never reads. [Rubric §9, API & Contract Design] assesses how non-fatal conditions are conveyed: EventsController translates the flag into an X-Warning response header and still returns 200 with the DTO (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:241-249), so the body stays exactly the EventDTO the API contract promises and the advisory rides beside it. [Rubric §13, Observability & Operability]: the condition is surfaced to the human who caused it, at the moment they caused it.
      • Walkthrough: one line. sealed record UpdateEventResult(EventDTO Event, bool HasTimeZoneWarning) (:24), with both members documented on the declaration (:21-23). It has no methods and no behavior; it exists to name a pair.
      • Why it's built this way: it lives in the same file as UpdateEventCommand (:15) because the two are one use case's input and output and are never referenced apart. Keeping the envelope in the Application layer rather than widening EventDTO with a HasTimeZoneWarning property matters: the flag is a fact about this write, not a property of the event, so it must not be persisted, cached, or returned by any read.
      • Where it's used: constructed by UpdateEventHandler in its BuildResult override (UpdateEventHandler.cs:80-86), named as the fourth type argument of the handler's base class (:22) and in the controller's injected dependency (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:47), and unwrapped by the controller, which reads the flag (:239) and then returns only result.Value.Event (:247). No other layer sees the envelope.
      • @@ -3386,7 +3423,7 @@

        CategoryItemNameRules<T>

      • Depends on: FluentValidation's AbstractValidator<T>, which it derives from directly (ConferenceCategoryValidationRules.cs:3,29), CategoryInvariants from the Domain layer for the length bound (:4,34), Expression<Func<T, string>> (:2,31) and CultureInfo (:1,34).
      • Concept introduced: the length constant is read, never retyped. MaximumLength(CategoryInvariants.CategoryItemNameMaxLength) (:34) resolves through CategoryInvariants.CategoryItemNameMaxLength (MMCA.ADC.Conference.Domain/Categories/CategoryInvariants.cs:24) to CategoryItemDTO.NameMaxLength, which is the const int 500 that also feeds the Blazor input caps and the EF column width (MMCA.ADC.Conference.Shared/Categories/CategoryItemDTO.cs:16; the arrangement is documented on the invariants class itself, CategoryInvariants.cs:10-13). The same constant is then interpolated into the user-facing message with string.Create(CultureInfo.InvariantCulture, ...) (:34), so the number in the error text can never drift from the number being enforced. [Rubric §15, Best Practices & Code Quality] assesses whether one decision lives in one place: widening the field is a one-constant change that propagates to the validator message, the aggregate guard and the schema. [Rubric §27, i18n]: the explicit invariant culture is required by the analyzer baseline and makes the interpolation deterministic; these messages are not localized, which is a stated limit rather than an oversight.
      • Walkthrough: sealed class CategoryItemNameRules<T> : AbstractValidator<T> (:28-29). The constructor (:31-34) is a single expression-bodied RuleFor(selector) chain with two links: NotEmpty() carrying the message "You must enter a Category Item Name" and the code CategoryItem.Name.Required (:33), then MaximumLength(...) with the interpolated message and the code CategoryItem.Name.MaxLength (:34). Every link states its own WithErrorCode, so a client can distinguish "missing" from "too long" without parsing English.
      • -
      • Why it's built this way: what is not here matters as much as what is. Uniqueness of a category item name inside its parent (BR-138) cannot be a field rule, because it needs the sibling collection, so it lives on the aggregate as CategoryInvariants.EnsureCategoryItemNameIsUnique (MMCA.ADC.Conference.Domain/Categories/CategoryInvariants.cs:44-65). The split is the standard one in this codebase: shape checks at the boundary, relationship checks in the domain. [Rubric §4, DDD] assesses whether invariants that require aggregate state stay inside the aggregate.
      • +
      • Why it's built this way: what is not here matters as much as what is. Uniqueness of a category item name inside its parent (BR-138) cannot be a field rule, because it needs the sibling collection, so it lives on the aggregate as CategoryInvariants.EnsureCategoryItemNameIsUnique (MMCA.ADC.Conference.Domain/Categories/CategoryInvariants.cs:51-72). The split is the standard one in this codebase: shape checks at the boundary, relationship checks in the domain. [Rubric §4, DDD] assesses whether invariants that require aggregate state stay inside the aggregate.
      • Where it's used: AddCategoryItemCommandValidator (MMCA.ADC.Conference.Application/Categories/UseCases/AddCategoryItem/AddCategoryItemCommandValidator.cs:11) and UpdateCategoryItemCommandValidator (MMCA.ADC.Conference.Application/Categories/UseCases/UpdateCategoryItem/UpdateCategoryItemCommandValidator.cs:11), each pairing it with CategoryItemSortRules<T> over the same command.

      ConferenceCategoryTitleRules<T>

      @@ -3409,7 +3446,7 @@

      PartnerDescriptionRules<T>

    • What it is: the rule set for a partner's optional description: no constraint but a maximum length.
    • Depends on: OptionalStringRules<T> from MMCA.Common.Application.Validation, which it derives from (:44), and PartnerInvariants from the Domain layer for the bound (:47).
    • Concept introduced: none new; the length constant is read from PartnerInvariants.DescriptionMaxLength, the same read-never-retyped pattern taught by CategoryItemNameRules<T>. The constant resolves to PartnerDTO.DescriptionMaxLength (MMCA.ADC.Conference.Domain/Partners/PartnerInvariants.cs:21-22), the same number the domain invariant PartnerInvariants.EnsureDescriptionIsValid re-checks with the error code Partner.Description.TooLong (PartnerInvariants.cs:56).
    • -
    • Walkthrough: sealed class PartnerDescriptionRules<T> : OptionalStringRules<T> (:43-44). The constructor (:46-47) passes the field phrase "Partner Description" and PartnerInvariants.DescriptionMaxLength. OptionalStringRules<T> itself is a single MaximumLength check (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:55-57); unlike PartnerNameRules<T>, there is no NotEmpty, since the field is optional.
    • +
    • Walkthrough: sealed class PartnerDescriptionRules<T> : OptionalStringRules<T> (:43-44). The constructor (:46-47) passes the field phrase "Partner Description" and PartnerInvariants.DescriptionMaxLength. OptionalStringRules<T> itself is a single MaximumLength check (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:56-58); unlike PartnerNameRules<T>, there is no NotEmpty, since the field is optional.
    • Why it's built this way: same boundary-plus-domain split as CategoryItemNameRules<T>: the validator refuses an over-long request before a handler runs, and PartnerInvariants.EnsureDescriptionIsValid re-checks the aggregate no matter who calls it.
    • Where it's used: included by PartnerFieldRules<T> (:89), the only binding site.
    @@ -3421,7 +3458,7 @@

    PartnerNameRules<T>

  • What it is: the rule set for a partner's name: non-empty, and no longer than the domain's declared maximum.
  • Depends on: RequiredStringRules<T> from MMCA.Common.Application.Validation, which it derives from (:14), and PartnerInvariants for the bound (:17).
  • Concept introduced: none new; the same shape as CategoryItemNameRules<T> and ConferenceCategoryTitleRules<T> one field over on a different aggregate. PartnerInvariants.NameMaxLength resolves to PartnerDTO.NameMaxLength (PartnerInvariants.cs:15-16), the same number PartnerInvariants.EnsureNameIsValid re-checks in the aggregate with the code Partner.Name.TooLong (PartnerInvariants.cs:36).
  • -
  • Walkthrough: sealed class PartnerNameRules<T> : RequiredStringRules<T> (:13-14). The constructor (:16-17) passes the field phrase "Partner Name" and PartnerInvariants.NameMaxLength. RequiredStringRules<T> chains NotEmpty then MaximumLength, each with its own message (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:41-47).
  • +
  • Walkthrough: sealed class PartnerNameRules<T> : RequiredStringRules<T> (:13-14). The constructor (:16-17) passes the field phrase "Partner Name" and PartnerInvariants.NameMaxLength. RequiredStringRules<T> chains NotEmpty then MaximumLength, each with its own message (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:42-48).
  • Why it's built this way: same as its siblings in this file: the boundary check and the domain guard read one shared constant, so they cannot disagree.
  • Where it's used: included by PartnerFieldRules<T> (:85), the only binding site.
  • @@ -3459,7 +3496,7 @@

    UpdateEventCommand

  • Concept introduced: the concurrency token as a command parameter. byte[] RowVersion is the third positional member and it is non-nullable (:15). The doc comment states the contract in full: the token is the caller's last-observed version, read from the If-Match header, and it is required because a conditional update that states no precondition never reaches this command (:10-14). Making it a parameter of the command rather than a property of the request has two effects worth internalizing. First, the Application layer stays HTTP-agnostic while still receiving the precondition: the header is decoded at the boundary and handed in as data. Second, the type system carries the guarantee. There is no legal way to construct this command without a token, so no handler has to consider the "caller declined to check" case. [Rubric §6, CQRS & Event-Driven] assesses whether writes are explicit intents carrying everything the handler needs; [Rubric §9, API & Contract Design]: 428 for a missing precondition and 412 for a stale one are decided at the boundary and never leak into the command.
  • Walkthrough: sealed record UpdateEventCommand(EventIdentifierType Id, EventUpdateRequest Request, byte[] RowVersion) implementing both marker interfaces on the declaration line (:15). The single member is CachePrefix => $"{typeof(Event).FullName}:" (:18), the key namespace the caching decorator evicts on success. UpdateEventResult sits directly below in the same file (:24).
  • Why it's built this way: the command does not implement ITransactional. Its handler writes one aggregate and saves once, so the ambient SaveChangesAsync boundary suffices, and the transactional decorator is reserved for commands that coordinate multiple aggregates (ADR-014; see TransactionalCommandDecorator<TCommand, TResult>). Deriving the cache prefix from typeof(Event).FullName rather than a literal keeps the writer and the readers agreed on a key namespace that a rename cannot desynchronize.
  • -
  • Where it's used: constructed by EventsController from the route id, the body and the decoded token (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:224-228) against the injected ICommandHandler<UpdateEventCommand, Result<UpdateEventResult>> (:49), and handled by UpdateEventHandler.
  • +
  • Where it's used: constructed by EventsController from the route id, the body and the decoded token (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:231-235) against the injected ICommandHandler<UpdateEventCommand, Result<UpdateEventResult>> (:49), and handled by UpdateEventHandler.
  • EventUpdateRequestValidator

    @@ -3518,11 +3555,11 @@

    SessionUpdateRequest

    • What it is: the full-replacement payload a client PUTs to edit an existing Session. It carries the parent event id, the title and description, the scheduled window, four booleans describing what kind of slot this is, four optional strings (live URL, recording URL, accessibility info, resource links), and the optional room assignment.
    • -
    • Depends on: ISessionFieldsRequest from MMCA.ADC.Conference.Application.Sessions.Validation, which it implements (SessionUpdateRequest.cs:1,6). Every other member is a BCL primitive or one of the module's identifier aliases (EventIdentifierType at :9, RoomIdentifierType at :51), so the record pulls in no domain type at all. Like EventUpdateRequest it has no RowVersion member: the precondition arrives in the If-Match header (MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:302,311).
    • +
    • Depends on: ISessionFieldsRequest from MMCA.ADC.Conference.Application.Sessions.Validation, which it implements (SessionUpdateRequest.cs:1,6). Every other member is a BCL primitive or one of the module's identifier aliases (EventIdentifierType at :9, RoomIdentifierType at :51), so the record pulls in no domain type at all. Like EventUpdateRequest it has no RowVersion member: the precondition arrives in the If-Match header (MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:310,319).
    • Concept introduced: carrying a field you are not allowed to change, so the server can detect that you tried. EventId is required here (:9) even though a session can never move between events, and the doc comment on the property says exactly that ("Must match the session's current EventId (BR-140: immutable after creation)", :8). The rule is enforced downstream by UpdateSessionHandler, which compares the request value against the loaded entity and fails with an unprocessable-entity error coded Session.EventId.Immutable (MMCA.ADC.Conference.Application/Sessions/UseCases/Update/UpdateSessionHandler.cs:45-52). Carry-and-verify is the right choice when the field is genuinely part of the resource's identity in the client's mental model: a session editor already knows which event it is working under, so sending it costs nothing and turns a client bug (posting session 42's body to session 43's route) into a loud rejection rather than a silent cross-event write. [Rubric §9, API & Contract Design] assesses whether a contract makes illegal states detectable rather than merely undocumented: the field's presence is what makes the mismatch checkable at all. [Rubric §11, Security]: a request body is caller-controlled, so an immutability rule that exists only in a UI is not a rule; the check that counts is the server-side comparison.
    • Walkthrough: two members are required, EventId (:9) and Title (:12). Description (:15) is optional. The schedule is two nullable DateTime values, StartsAt (:18) and EndsAt (:21), so an unscheduled session is a legal state. Status (:24) is a free-form optional string. Four booleans describe the slot: IsInformed (:27) and IsConfirmed (:30) track the speaker-communication workflow, IsServiceSession (:33) marks lunch and break blocks, and IsPlenumSession (:36) marks whole-room slots. Four optional strings follow: LiveUrl (:39), RecordingUrl (:42), AccessibilityInfo (:45) and ResourceLinks (:48). RoomId (:51) is the nullable room assignment, and it is the one member that triggers cross-aggregate work in the handler. Every member is init-only; seven of them satisfy ISessionFieldsRequest (MMCA.ADC.Conference.Application/Sessions/Validation/ISessionFieldsRequest.cs:13-35), whose remarks record why EventId deliberately stays off that interface (:8-12).
    • Why it's built this way: nullable StartsAt/EndsAt are load-bearing rather than lazy. Sessions exist before the schedule is drawn, so "no time yet" must round-trip through the edit form without inventing a placeholder date; SessionRoomScheduling reads the same nullability and simply skips the double-booking probe when either bound is missing (MMCA.ADC.Conference.Application/Sessions/Validation/SessionRoomScheduling.cs:69-70). Because the PUT is a full replacement, the handler passes all fourteen editable fields straight into Session.Update (MMCA.ADC.Conference.Domain/Sessions/Session.cs:257-271) without ever distinguishing "omitted" from "cleared".
    • -
    • Where it's used: bound from the body by SessionsController on PUT {id} (MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:301-310), validated by SessionUpdateRequestValidator, wrapped in UpdateSessionCommand (:331), and consumed field by field by UpdateSessionHandler.
    • +
    • Where it's used: bound from the body by SessionsController on PUT {id} (MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:309-318), validated by SessionUpdateRequestValidator, wrapped in UpdateSessionCommand (:331), and consumed field by field by UpdateSessionHandler.

    UploadSessionAssetCommand

    @@ -3567,11 +3604,11 @@

    UpdateSessionResult

    • What it is: the two-member envelope UpdateSessionHandler returns: the updated SessionDTO plus a boolean saying whether the new session times fall outside the parent event's date range.
    • Depends on: SessionDTO from MMCA.ADC.Conference.Shared.Sessions (UpdateSessionCommand.cs:2,25). The second member is a bool.
    • -
    • Concept introduced: none new. This is the advisory-result shape taught at UpdateEventResult, applied to a second rule: BR-86 rather than BR-131. What is worth carrying forward is that the shape recurs, which is what makes it a pattern rather than a one-off. Both cases share the same class of problem: the write is legal and must be persisted, but it leaves the data in a state a human should look at. Scheduling a session outside the conference dates is not an invariant violation (the organizer may be mid-edit, or the event dates may be about to move), so failing the request would be wrong, and swallowing it would leave a session nobody can attend. [Rubric §9, API & Contract Design]: SessionsController reads the flag, appends an X-Warning response header and still returns 200 with only the DTO in the body (MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:321-327), so the envelope itself never reaches the wire. [Rubric §13, Observability & Operability]: the warning reaches the person who caused it, at the moment they caused it.
    • +
    • Concept introduced: none new. This is the advisory-result shape taught at UpdateEventResult, applied to a second rule: BR-86 rather than BR-131. What is worth carrying forward is that the shape recurs, which is what makes it a pattern rather than a one-off. Both cases share the same class of problem: the write is legal and must be persisted, but it leaves the data in a state a human should look at. Scheduling a session outside the conference dates is not an invariant violation (the organizer may be mid-edit, or the event dates may be about to move), so failing the request would be wrong, and swallowing it would leave a session nobody can attend. [Rubric §9, API & Contract Design]: SessionsController reads the flag, appends an X-Warning response header and still returns 200 with only the DTO in the body (MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:329-335), so the envelope itself never reaches the wire. [Rubric §13, Observability & Operability]: the warning reaches the person who caused it, at the moment they caused it.
    • Walkthrough: one line, sealed record UpdateSessionResult(SessionDTO Session, bool HasDateRangeWarning) (:24), documented on the declaration (:21-23). No methods, no behavior; it exists to name a pair.
    • Why it's built this way: it shares a file with UpdateSessionCommand (:15) because the two are one use case's input and output and are never referenced apart. Keeping the flag out of SessionDTO is the load-bearing part: HasDateRangeWarning is a fact about this particular write, not a property of the session, so it must never be persisted, cached, or returned by a read endpoint.
    • Where it's used: constructed by UpdateSessionHandler in its BuildResult override (UpdateSessionHandler.cs:110-116), named as the fourth type argument of the handler's base class (:23) and in the controller's injected handler type (MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:47), and unwrapped by the controller, which reads result.Value!.HasDateRangeWarning (:338) and then returns result.Value.Session (:344).
    • -
    • Caveats / not-in-source: the create path reaches the same header by a different route. It has no result envelope: the controller itself re-reads the parent event through the event query service and compares the requested times inline (MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:274-287). Only the update path routes the flag through a handler result.
    • +
    • Caveats / not-in-source: the create path reaches the same header by a different route. It has no result envelope: the controller itself re-reads the parent event through the event query service and compares the requested times inline (MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:282-295). Only the update path routes the flag through a handler result.

    AddSessionAssetLinkHandler

    @@ -3632,8 +3669,8 @@

    UpdateSessionCommand

  • Concept introduced: none new; this is the id-plus-request-plus-token shape taught at UpdateEventCommand, byte for byte including the doc comment on RowVersion (:10-14). What is worth noting here is what the two marker interfaces buy and what they do not. ICacheInvalidating opts the command into the CachingCommandDecorator<TCommand, TResult> so a successful update evicts the session read cache under this prefix, and ICommandWithRequest<out TRequest> is what routes SessionUpdateRequestValidator into the pipeline. Neither is ITransactional, so this command runs without an explicit ambient transaction; the base handler's single SaveChangesAsync is its own unit of work, which suffices because the write touches one aggregate. [Rubric §6, CQRS & Event-Driven] assesses whether cross-cutting behavior attaches declaratively rather than being wired inside handlers (ADR-014).
  • Walkthrough: three positional parameters with both interfaces on the declaration line (:15). CachePrefix (:18) returns $"{typeof(Session).FullName}:". The result type UpdateSessionResult shares the file (:24).
  • Why it's built this way: deriving the cache prefix from typeof(Session).FullName rather than a literal keeps the writer (this command) and the reader (the session query cache) agreed on one key namespace that a rename cannot desynchronize.
  • -
  • Caveats / not-in-source: two different caches are cleared on this path, and they are not duplicates. The decorator evicts the application-level query cache keyed by this CachePrefix; separately, SessionsController evicts the ASP.NET output cache by tag after a successful update (MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:326, tags conference:sessions and conference). One is the handler's data cache, the other is the HTTP response cache.
  • -
  • Where it's used: constructed by SessionsController from the route id, the body and the decoded token (MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:311-315) and handled by UpdateSessionHandler.
  • +
  • Caveats / not-in-source: two different caches are cleared on this path, and they are not duplicates. The decorator evicts the application-level query cache keyed by this CachePrefix; separately, SessionsController evicts the ASP.NET output cache by tag after a successful update (MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:334, tags conference:sessions and conference). One is the handler's data cache, the other is the HTTP response cache.
  • +
  • Where it's used: constructed by SessionsController from the route id, the body and the decoded token (MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:319-323) and handled by UpdateSessionHandler.
  • UploadSessionAssetCommandValidator

    @@ -3649,16 +3686,16 @@

    UploadSessionAssetCommandValidator

    UploadSessionAssetHandler

    -

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.SessionAssets.UseCases.UploadFile · MMCA.ADC.Conference.Application/SessionAssets/UseCases/UploadFile/UploadSessionAssetHandler.cs:35 · Level 9 · class

    +

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.SessionAssets.UseCases.UploadFile · MMCA.ADC.Conference.Application/SessionAssets/UseCases/UploadFile/UploadSessionAssetHandler.cs:37 · Level 9 · class

    • What it is: the handler for UploadSessionAssetCommand. It checks storage is configured, re-derives the content type from the bytes, authorizes the write against the session, uploads the bytes to blob storage, creates the SessionAsset aggregate, and schedules a cleanup of the blob if anything downstream of the upload fails.
    • -
    • Depends on: ICommandHandler<UploadSessionAssetCommand, Result<SessionAssetDTO>> (:116), IUnitOfWork (:110), ISessionAssetAccessService (:111), IFileStorageService (:112), IInternalCommandScheduler (:113), SessionAssetDTOMapper (:114), DocumentContentSniffer/DocumentFormats, BlobNames, FileUploadOptions, the SessionAsset aggregate, and DeleteSessionAssetBlobInternalCommand, and ILogger<T> with [LoggerMessage] (:115).
    • -
    • Concept introduced: scheduling orphan-blob cleanup on every failure path after the bytes are already written. The blob PUT happens before the aggregate is created and before the row is saved (:168-171), so two later failures, a domain validation failure from SessionAsset.CreateFile (:184-188) and a database exception from the repository add/save (:192-205), both leave bytes in storage with nothing pointing at them unless something schedules a delete. Both paths call the same private helper, ScheduleOrphanCleanupAsync, which enqueues a DeleteSessionAssetBlobInternalCommand through IInternalCommandScheduler and only logs a warning (does not fail the request) if even the scheduling itself fails (:238-246). The database-exception path re-throws after scheduling the cleanup, so the exception still reaches the pipeline's own exception handling exactly as it would have without the cleanup call (:198-205). [Rubric §13, Observability & Operability] assesses whether a side effect that cannot be undone (a blob already written) has a recorded, retryable path to reconciliation rather than a silent leak.
    • -
    • Walkthrough: HandleAsync (:122-210) first checks fileStorage.IsConfigured and fails with SessionAsset.StorageNotConfigured if not (:128-134), then re-derives the content type with DocumentContentSniffer.Detect, failing SessionAsset.UnsupportedFormat if it comes back null (:138-145). It authorizes the write through accessService.AuthorizeSessionWriteAsync (:147-154) and confirms the session has a room through accessService.EnsureSessionHasRoomAsync (:156-160), builds a sanitized blob name from the authorized event id, the session id, a new asset id and the sanitized filename (:162-166), uploads via the private UploadAsync helper (:168-171), creates the aggregate with SessionAsset.CreateFile (:173-188), persists it through the repository and SaveChangesAsync (:192-197), logs via the source-generated LogFileUploaded (:207), and returns the mapped DTO (:209). The private UploadAsync helper (:218-236) picks FileUploadOptions.Inline for application/pdf and FileUploadOptions.Attachment for every other content type (:225-227), documented as giving a PDF the browser's own viewer while every other format downloads (:212-217). AssetCacheControl (:119) sets public, max-age=31536000, immutable, documented as safe because a fresh blob name always carries a fresh asset id, so the same blob name never changes content.
    • -
    • Why it's built this way: the class is partial for the two [LoggerMessage]-generated methods, LogFileUploaded (:248-249) and LogOrphanScheduleFailed (:251-252). Scheduling the cleanup rather than deleting inline keeps the handler from taking on a second blocking storage call inside an already-failing request, and keeps the deletion itself retryable through the internal command's own pipeline.
    • -
    • Caveats / not-in-source: ScheduleOrphanCleanupAsync can itself fail to schedule (for example if the internal command store is unavailable); that path only logs LogOrphanScheduleFailed and leaves the blob orphaned in storage for manual removal (:244-246).
    • -
    • Where it's used: constructed by SessionAssetsController on the upload endpoint and handled here; referenced in tests under MMCA.ADC.Conference.Application.Tests/SessionAssets/UseCases/UploadSessionAssetHandlerTests.cs. ADR-123 (123-speaker-session-assets.md) covers the session-asset feature this handler is part of.
    • +
    • Depends on: ICommandHandler<UploadSessionAssetCommand, Result<SessionAssetDTO>> (:44), IUnitOfWork (:38), ISessionAssetAccessService (:39), IFileStorageService (:40), IServiceScopeFactory (:41, used to resolve IInternalCommandScheduler from a fresh scope at :182-183), SessionAssetDTOMapper (:42), ILogger<T> with [LoggerMessage] (:43), DocumentContentSniffer/DocumentFormats, BlobNames, FileUploadOptions, the SessionAsset aggregate, and DeleteSessionAssetBlobInternalCommand.
    • +
    • Concept introduced: scheduling orphan-blob cleanup on every failure path after the bytes are already written, from a scope the failure cannot poison. The blob PUT happens before the aggregate is created and before the row is saved (:96-99), so two later failures, a domain validation failure from SessionAsset.CreateFile (:112-116) and a database exception from the repository add/save (:120-133), both leave bytes in storage with nothing pointing at them unless something schedules a delete. Both paths call the same private helper, ScheduleOrphanCleanupAsync(string blobName) (:178-195), which opens a new async DI scope, resolves IInternalCommandScheduler from it (:182-183) and enqueues a DeleteSessionAssetBlobInternalCommand with CancellationToken.None (:184-186). Its doc comment gives the reasons (:171-177): the request scope's context is the one whose save just failed and still tracks the added asset, so scheduling on it re-threw the original failure and wrote no row, and a cancelled request token cancelled the cleanup along with the request. A Failure result from the scheduler logs LogOrphanScheduleFailed (:188-189), and any exception thrown while scheduling is caught and logged through LogOrphanScheduleThrew (:191-194), so a scheduling problem never replaces the exception the caller is about to rethrow. The database-exception path re-throws after scheduling the cleanup, so the exception still reaches the pipeline's own exception handling exactly as it would have without the cleanup call (:126-132). [Rubric §13, Observability & Operability] assesses whether a side effect that cannot be undone (a blob already written) has a recorded, retryable path to reconciliation rather than a silent leak.
    • +
    • Walkthrough: HandleAsync (:50-138) first checks fileStorage.IsConfigured and fails with SessionAsset.StorageNotConfigured if not (:56-62), then re-derives the content type with DocumentContentSniffer.Detect, failing SessionAsset.UnsupportedFormat if it comes back null (:66-73). It authorizes the write through accessService.AuthorizeSessionWriteAsync (:75-82) and confirms the session has a room through accessService.EnsureSessionHasRoomAsync (:84-88), builds a sanitized blob name from the authorized event id, the session id, a new asset id and the sanitized filename (:90-94), uploads via the private UploadAsync helper (:96-99), creates the aggregate with SessionAsset.CreateFile (:101-116), persists it through the repository and SaveChangesAsync (:120-125), logs via the source-generated LogFileUploaded (:135), and returns the mapped DTO (:137). The private UploadAsync helper (:146-169) picks FileUploadOptions.Inline for application/pdf and FileUploadOptions.Attachment for every other content type (:153-155), documented as giving a PDF the browser's own viewer while every other format downloads (:140-145). It wraps the caller's buffer in a read-only MemoryStream without copying when MemoryMarshal.TryGetArray exposes the backing array, and only copies via ToArray() for a memory that is not array-backed (:157-162). AssetCacheControl (:47) sets public, max-age=31536000, immutable, documented as safe because a fresh blob name always carries a fresh asset id, so the same blob name never changes content.
    • +
    • Why it's built this way: the class is partial for the three [LoggerMessage]-generated methods, LogFileUploaded (:197-198), LogOrphanScheduleFailed (:200-201) and LogOrphanScheduleThrew (:203-204). Scheduling the cleanup rather than deleting inline keeps the handler from taking on a second blocking storage call inside an already-failing request, and keeps the deletion itself retryable through the internal command's own pipeline. Taking IServiceScopeFactory rather than the scheduler itself is what lets the cleanup write land on a clean context instead of the one whose save just failed.
    • +
    • Caveats / not-in-source: ScheduleOrphanCleanupAsync can itself fail to schedule (a failure result or a thrown exception, for example if the internal command store is unavailable); both paths only log a warning that the blob is left in storage and must be removed by hand (:188-194, :200-204), so the blob stays orphaned until someone removes it.
    • +
    • Where it's used: constructed by SessionAssetsController on the upload endpoint and handled here; referenced in tests under MMCA.ADC.Conference.Application.Tests/SessionAssets/UseCases/UploadSessionAssetHandlerTests.cs and in MMCA.ADC.Architecture.Tests/Cqrs/ConstructorDependencyCountTests.cs. ADR-123 (123-speaker-session-assets.md) covers the session-asset feature this handler is part of, and ADR-054 (054-saga-compensation-and-reconciliation.md) also mentions it.

    UpdateSessionAssetHandler

    @@ -3696,8 +3733,8 @@

    SpeakerUpdateRequest

  • Concept introduced: the update request defined by what it leaves out, twice over. Read this record against the update requests in the rest of the module and two absences stand out, both deliberate and both documented on the declaration itself.

      -
    • No linked-user field. Speaker.LinkedUserId is editable state on the aggregate, but it is not in this contract (:6-7). The aggregate says the same thing from the other side: Speaker.Update assigns ten fields plus IsTopSpeaker and deliberately never touches the link, because only LinkUser and UnlinkUser carry the BR-208 uniqueness check and raise the events that keep Identity's User.LinkedSpeakerId in sync (MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:176-182,223-233). A field removed from the wire contract is the strongest form of "this is not editable here": no validator has to reject it, and no reviewer has to remember the rule.
    • -
    • No concurrency token member. Unlike ConferenceCategoryUpdateRequest, this record does not implement IConcurrencyAware and carries no RowVersion. The token arrives in the If-Match header instead, decoded by the filter and read out with SupportsIfMatchAttribute.RequiredToken(HttpContext) at the action (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:324,339). That is the ADR-035 conditional-write path in its strict form: a request with no precondition never reaches the handler at all, so the token cannot be null by the time UpdateSpeakerCommand is built.
    • +
    • No linked-user field. Speaker.LinkedUserId is editable state on the aggregate, but it is not in this contract (:6-7). The aggregate says the same thing from the other side: Speaker.Update assigns ten fields plus IsTopSpeaker and deliberately never touches the link, because only LinkUser and UnlinkUser carry the BR-208 uniqueness check and raise the events that keep Identity's User.LinkedSpeakerId in sync (MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:177-183,225-235). A field removed from the wire contract is the strongest form of "this is not editable here": no validator has to reject it, and no reviewer has to remember the rule.
    • +
    • No concurrency token member. Unlike ConferenceCategoryUpdateRequest, this record does not implement IConcurrencyAware and carries no RowVersion. The token arrives in the If-Match header instead, decoded by the filter and read out with SupportsIfMatchAttribute.RequiredToken(HttpContext) at the action (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:334,350). That is the ADR-035 conditional-write path in its strict form: a request with no precondition never reaches the handler at all, so the token cannot be null by the time UpdateSpeakerCommand is built.

    [Rubric §9, API & Contract Design] assesses whether the wire contract expresses the operation's real boundaries: here the payload is the editable state and nothing more, with the precondition living in the transport where HTTP already defines its semantics. [Rubric §11, Security]: a field a caller cannot send is a field a crafted body cannot set, which is the same reasoning that keeps CallerIsOrganizer off this record and on the command (SpeakerUpdateApplier).

  • @@ -3705,7 +3742,7 @@

    SpeakerUpdateRequest

  • Why it's built this way: implementing ISpeakerFieldsRequest is what lets the create and the update request share one rule list. SpeakerFieldRules<T> is generic over T : ISpeakerFieldsRequest and includes six rule sets by interface member rather than by concrete property (MMCA.ADC.Conference.Application/Speakers/Validation/SpeakerValidationRules.cs:113-125), so the name, email and URL contracts are declared once for both entry paths. [Rubric §15, Best Practices & Code Quality]: adding a shared speaker field is an interface member plus one Include, not an edit in two validators that can drift apart.

  • -
  • Where it's used: bound from the body by SpeakersController on PUT {id} (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:330), validated by SpeakerUpdateRequestValidator, wrapped into UpdateSpeakerCommand with the role flag and the header token (:351), and consumed field by field by SpeakerUpdateApplier.

    +
  • Where it's used: bound from the body by SpeakersController on PUT {id} (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:340), validated by SpeakerUpdateRequestValidator, wrapped into UpdateSpeakerCommand with the role flag and the header token (:351), and consumed field by field by SpeakerUpdateApplier.

  • SponsorUpdateRequest

    @@ -3717,9 +3754,9 @@

    SponsorUpdateRequest

  • Depends on: ISponsorFieldsRequest from MMCA.ADC.Conference.Application.Sponsors.Validation and the SponsorTier enum from MMCA.ADC.Conference.Shared.Sponsors (SponsorUpdateRequest.cs:1-2,11,17). Everything else is a BCL primitive.
  • Concept introduced: none new; it is the same shape as SpeakerUpdateRequest, including the deliberate omission and the header-only concurrency token. The omission here is the owning event: moving a sponsor between conference editions is a create plus a delete, so a mistyped EventId cannot silently relocate bought placement (:7-10), and the interface repeats the reasoning so the create side keeps the field while the update side never sees it (MMCA.ADC.Conference.Application/Sponsors/Validation/ISponsorFieldsRequest.cs:8-11). [Rubric §9, API & Contract Design] assesses whether an operation's contract expresses only what that operation may change. [Rubric §4, Domain-Driven Design]: an event's sponsor roster is part of that event's identity, so re-parenting is a different intent rather than a field edit.
  • Walkthrough: public record class SponsorUpdateRequest : ISponsorFieldsRequest (:11). Name is the single required member (:14). Tier (:17) is the SponsorTier enum, Sort (:35) the integer display order within the tier, and IsExhibitor (:38) the boolean that pairs with the optional BoothNumber (:41). The remaining optional strings are LogoUrl (:20), Description (:23), WebsiteUrl (:26), LinkedInUrl (:29), and TwitterHandle (:32). All members are init-only.
  • -
  • Why it's built this way: because the record adds nothing beyond the interface's fields, the sponsor update rides the framework's generic command with no module-specific command type at all: the controller constructs UpdateEntityCommand<Sponsor, SponsorUpdateRequest, SponsorIdentifierType> directly (MMCA.ADC.Conference.API/Controllers/Sponsors/SponsorsController.cs:195). Compare UpdateSpeakerCommand, which exists only because the speaker update needs one more piece of server-decided state. [Rubric §5, Vertical Slice]: the slice contains exactly what it needs, a request record, a validator and an applier, and borrows the rest.
  • +
  • Why it's built this way: because the record adds nothing beyond the interface's fields, the sponsor update rides the framework's generic command with no module-specific command type at all: the controller constructs UpdateEntityCommand<Sponsor, SponsorUpdateRequest, SponsorIdentifierType> directly (MMCA.ADC.Conference.API/Controllers/Sponsors/SponsorsController.cs:203). Compare UpdateSpeakerCommand, which exists only because the speaker update needs one more piece of server-decided state. [Rubric §5, Vertical Slice]: the slice contains exactly what it needs, a request record, a validator and an applier, and borrows the rest.
  • Caveats / not-in-source: Tier carries no IsInEnum rule in SponsorUpdateRequestValidator; SponsorFieldRules<T> includes no tier rule (MMCA.ADC.Conference.Application/Sponsors/Validation/SponsorValidationRules.cs:145-152). An out-of-range integer bound into the enum-typed property is therefore not rejected at the validator.
  • -
  • Where it's used: bound by SponsorsController on PUT {id} (MMCA.ADC.Conference.API/Controllers/Sponsors/SponsorsController.cs:189), validated by SponsorUpdateRequestValidator, and applied by SponsorUpdateApplier under the framework's generic update handler, registered with one AddEntityCrud call (MMCA.ADC.Conference.Application/DependencyInjection.cs:155).
  • +
  • Where it's used: bound by SponsorsController on PUT {id} (MMCA.ADC.Conference.API/Controllers/Sponsors/SponsorsController.cs:197), validated by SponsorUpdateRequestValidator, and applied by SponsorUpdateApplier under the framework's generic update handler, registered with one AddEntityCrud call (MMCA.ADC.Conference.Application/DependencyInjection.cs:151).
  • PartnerUpdateRequest

    @@ -3730,8 +3767,8 @@

    PartnerUpdateRequest

  • Depends on: IPartnerFieldsRequest from MMCA.ADC.Conference.Application.Partners.Validation and the PartnerType enum (PartnerUpdateRequest.cs:1). Everything else is a BCL primitive.
  • Concept introduced: none new; it is the same shape as SponsorUpdateRequest: a request-only aggregate whose IPartnerFieldsRequest interface deliberately leaves the owning event off the wire contract, because re-parenting a partner to a different event is a create plus a delete, not a field edit (MMCA.ADC.Conference.Application/Partners/Validation/IPartnerFieldsRequest.cs:10-12). [Rubric §9, API & Contract Design] assesses whether a contract expresses only what that operation may change.
  • Walkthrough: public record class PartnerUpdateRequest : IPartnerFieldsRequest (:11). Name is the single required member (:14). PartnerType (:17) is the enum, Sort (:29) the integer display order within the type. The optional strings are LogoUrl (:20), Description (:23), and WebsiteUrl (:26). Every member is init-only.
  • -
  • Why it's built this way: because the record adds nothing beyond the interface's fields, the partner update rides the framework's generic command with no module-specific command type: services.AddEntityCrud<Partner, PartnerDTO, PartnerIdentifierType, PartnerCreateRequest, PartnerUpdateRequest>() registers the create, update and delete slice in one line (MMCA.ADC.Conference.Application/DependencyInjection.cs:156). [Rubric §5, Vertical Slice]: the slice is a request record, a validator and an applier, and borrows the rest.
  • -
  • Where it's used: bound from the body by PartnersController on PUT {id} (MMCA.ADC.Conference.API/Controllers/Partners/PartnersController.cs:179), validated by PartnerUpdateRequestValidator, and applied by PartnerUpdateApplier under the framework's generic update handler.
  • +
  • Why it's built this way: because the record adds nothing beyond the interface's fields, the partner update rides the framework's generic command with no module-specific command type: services.AddEntityCrud<Partner, PartnerDTO, PartnerIdentifierType, PartnerCreateRequest, PartnerUpdateRequest>() registers the create, update and delete slice in one line (MMCA.ADC.Conference.Application/DependencyInjection.cs:152). [Rubric §5, Vertical Slice]: the slice is a request record, a validator and an applier, and borrows the rest.
  • +
  • Where it's used: bound from the body by PartnersController on PUT {id} (MMCA.ADC.Conference.API/Controllers/Partners/PartnersController.cs:187), validated by PartnerUpdateRequestValidator, and applied by PartnerUpdateApplier under the framework's generic update handler.
  • ISessionAssetAccessService

    @@ -3755,7 +3792,7 @@

    SpeakerDeletedHandler

  • Depends on: IDomainEventHandler<in TDomainEvent> closed over SpeakerChanged (SpeakerDeletedHandler.cs:3,5,22), IEventBus resolved per invocation (:5,41), SpeakerUnlinkedFromUser (:4,43), DomainEntityState (:6,29), and two externals: IServiceScopeFactory (Microsoft.Extensions.DependencyInjection, :1,21) and ILogger<T> plus [LoggerMessage] (Microsoft.Extensions.Logging, :2,22,48).

  • -
  • Concept introduced: the domain-event handler as the boundary between two modules' data. A speaker and a user each hold a pointer to the other: Speaker.LinkedUserId in Conference and User.LinkedSpeakerId in Identity. They live in different modules and, under database-per-service (ADR-006), different databases, so one transaction cannot clear both. The aggregate clears its own side inside Speaker.Delete() and captures the old value first, precisely so the handler can still see it after the field is null (MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:253-263). This handler then converts that in-process fact into a published contract, and Identity's SpeakerUnlinkedFromUserHandler applies it on its own side and in its own transaction. Three mechanics are worth internalizing:

    +
  • Concept introduced: the domain-event handler as the boundary between two modules' data. A speaker and a user each hold a pointer to the other: Speaker.LinkedUserId in Conference and User.LinkedSpeakerId in Identity. They live in different modules and, under database-per-service (ADR-006), different databases, so one transaction cannot clear both. The aggregate clears its own side inside Speaker.Delete() and captures the old value first, precisely so the handler can still see it after the field is null (MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:255-265). This handler then converts that in-process fact into a published contract, and Identity's SpeakerUnlinkedFromUserHandler applies it on its own side and in its own transaction. Three mechanics are worth internalizing:

    • State gating. One event type covers added, updated and deleted, so the handler's first real statement is a guard that returns for anything other than DomainEntityState.Deleted (:29-30). Subscribing to a lifecycle event and filtering is the module's convention; there is no SpeakerDeleted type.
    • Singleton handler, per-invocation scope. Domain event handlers are registered with singleton lifetime by the module scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:192-197). A singleton cannot hold a scoped IEventBus, so the handler injects IServiceScopeFactory and opens its own await using scope for the publish (:40-41). The comment on the call site says exactly this (:36-37).
    • @@ -3769,7 +3806,7 @@

      SpeakerDeletedHandler

    • Caveats / not-in-source: the handler does not await Identity's reaction and has no compensating action if that side fails; the retry and dead-letter behavior belongs to the transport and the outbox (see group 04), not to this file.

    • -
    • Where it's used: never constructed by name. It is discovered by ScanModuleApplicationServices<ClassReference>() (MMCA.ADC.Conference.Application/DependencyInjection.cs:143) and invoked by DomainEventDispatcher as part of the save that soft-deleted the speaker.

      +
    • Where it's used: never constructed by name. It is discovered by ScanModuleApplicationServices<ClassReference>() (MMCA.ADC.Conference.Application/DependencyInjection.cs:139) and invoked by DomainEventDispatcher as part of the save that soft-deleted the speaker.

    UpdateSpeakerCommand

    @@ -3780,8 +3817,8 @@

    UpdateSpeakerCommand

  • What it is: the write intent for updating a Speaker. It is the framework's generic update command plus one extra member: whether the caller holds the Organizer role.
  • Depends on: UpdateEntityCommand<TEntity, TUpdateRequest, TIdentifierType> from MMCA.Common.Application.UseCases as its base record (UpdateSpeakerCommand.cs:2,25), SpeakerUpdateRequest (:22), and the Speaker type, reached only through the base's cache prefix (:1).
  • Concept introduced: deriving from the generic command to carry server-decided state. The framework's base command is deliberately not sealed, and its own documentation names this exact case: an update often carries a flag the server decided rather than the caller (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/UpdateEntityCommand.cs:28-37). CallerIsOrganizer (:23) is bound at the API edge from the role claim, never from the body (:14-16), and BR-214 is the rule that makes it matter: a speaker may edit their own profile, but only an organizer may set the curation flag IsTopSpeaker. Putting that bit on the command rather than the request is the whole design: a request member is caller-controlled, a command member built by the controller is not. Deriving buys three things for free, all stated on the base: the inherited Id, Request and RowVersion; the inherited CachePrefix, defaulting to typeof(Speaker).FullName + ":" (UpdateEntityCommand.cs:65); and the ICommandWithRequest<TRequest> implementation that keeps the IValidator<SpeakerUpdateRequest> bridge running so no UpdateSpeakerCommandValidator has to exist (:9-10). [Rubric §11, Security] assesses whether a privilege decision is made where the privilege is known: the role is read from the authenticated principal at the controller, and the applier consumes a boolean it cannot influence. [Rubric §6, CQRS & Event-Driven]: the command remains the pipeline's single dispatch key, so all four decorators behave identically for the derived type.
  • -
  • Walkthrough: sealed record UpdateSpeakerCommand(SpeakerIdentifierType Id, SpeakerUpdateRequest Request, bool CallerIsOrganizer, byte[] RowVersion) : UpdateEntityCommand<Speaker, SpeakerUpdateRequest, SpeakerIdentifierType>(Id, Request, RowVersion) (:20-25). Three of the four positional members are forwarded straight to the base constructor; only CallerIsOrganizer is new. RowVersion is a non-nullable byte[] because the endpoint is conditional: SupportsIfMatchAttribute answers 428 for a missing If-Match and 412 for a stale one before the action body runs (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:317-320,324), so a null token cannot reach the command. There is no body: every member is either inherited or positional.
  • -
  • Why it's built this way: because the module declares its own command type, the framework registration is AddEntityUpdate rather than the plain AddEntityCrud used for sponsors and activities (MMCA.ADC.Conference.Application/DependencyInjection.cs:164, and the block comment explaining the choice at :144-149). That call closes the framework's derived-command handler over UpdateSpeakerCommand with TryAdd and registers the command-to-request validator bridge (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Crud.cs:195-199). TryAdd is the load-bearing detail: the module's own UpdateSpeakerHandler was already registered by the assembly scan, so it keeps the verb and the framework registration only completes the bridge.
  • +
  • Walkthrough: sealed record UpdateSpeakerCommand(SpeakerIdentifierType Id, SpeakerUpdateRequest Request, bool CallerIsOrganizer, byte[] RowVersion) : UpdateEntityCommand<Speaker, SpeakerUpdateRequest, SpeakerIdentifierType>(Id, Request, RowVersion) (:20-25). Three of the four positional members are forwarded straight to the base constructor; only CallerIsOrganizer is new. RowVersion is a non-nullable byte[] because the endpoint is conditional: SupportsIfMatchAttribute answers 428 for a missing If-Match and 412 for a stale one before the action body runs (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:327-330,334), so a null token cannot reach the command. There is no body: every member is either inherited or positional.
  • +
  • Why it's built this way: because the module declares its own command type, the framework registration is AddEntityUpdate rather than the plain AddEntityCrud used for sponsors and activities (MMCA.ADC.Conference.Application/DependencyInjection.cs:160, and the block comment explaining the choice at :144-149). That call closes the framework's derived-command handler over UpdateSpeakerCommand with TryAdd and registers the command-to-request validator bridge (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Crud.cs:195-199). TryAdd is the load-bearing detail: the module's own UpdateSpeakerHandler was already registered by the assembly scan, so it keeps the verb and the framework registration only completes the bridge.
  • Where it's used: constructed by SpeakersController after its BR-214 authorization check, with named arguments for the two non-obvious members (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:333-342), against the injected ICommandHandler<UpdateSpeakerCommand, Result<SpeakerDTO>> (:49); consumed by SpeakerUpdateApplier and handled by UpdateSpeakerHandler.
  • PartnerUpdateApplier

    @@ -3793,8 +3830,8 @@

    PartnerUpdateApplier

  • Depends on: IEntityUpdateApplier<TEntity, TUpdateRequest, TIdentifierType> (PartnerUpdateApplier.cs:2,15), the Partner aggregate (:1), PartnerUpdateRequest (:15), and Result (:3,17).
  • Concept introduced: none new; it is the request-only applier shape taught at SponsorUpdateApplier, one aggregate over: no member on PartnerUpdateRequest needs server-decided state, so the applier takes the request rather than a command, and the module needs no command type of its own for a partner edit. [Rubric §3, Clean Architecture] assesses whether each layer holds only what it is entitled to hold: the Application layer translates a DTO into an aggregate call, and every business check stays in Domain.
  • Walkthrough: public sealed class PartnerUpdateApplier : IEntityUpdateApplier<Partner, PartnerUpdateRequest, PartnerIdentifierType> (:12-13). ApplyAsync null-guards the entity and the request (:16-17) and returns Task.FromResult(entity.Update(...)) with all six request members in the aggregate's parameter order (:19-25). Partner.Update validates the name, logo URL and website URL, returns early on failure, then assigns the six fields and raises PartnerChanged with DomainEntityState.Updated (MMCA.ADC.Conference.Domain/Partners/Partner.cs:116-137).
  • -
  • Why it's built this way: keeping the applier free of any command type is what lets the whole partner write side register in a single AddEntityCrud<Partner, PartnerDTO, PartnerIdentifierType, PartnerCreateRequest, PartnerUpdateRequest>() call (MMCA.ADC.Conference.Application/DependencyInjection.cs:156), with the framework supplying the update and delete handlers and the module supplying only this applier and the create side. [Rubric §15, Best Practices & Code Quality]: the partner update slice is three small files and one registration line.
  • -
  • Where it's used: resolved from the container as IEntityUpdateApplier<Partner, PartnerUpdateRequest, PartnerIdentifierType> by the framework's generic UpdateEntityHandler<TEntity, TEntityDTO, TIdentifierType, TUpdateRequest>, which the PUT action reaches through UpdateEntityCommand<Partner, PartnerUpdateRequest, PartnerIdentifierType> (MMCA.ADC.Conference.API/Controllers/Partners/PartnersController.cs:177,185).
  • +
  • Why it's built this way: keeping the applier free of any command type is what lets the whole partner write side register in a single AddEntityCrud<Partner, PartnerDTO, PartnerIdentifierType, PartnerCreateRequest, PartnerUpdateRequest>() call (MMCA.ADC.Conference.Application/DependencyInjection.cs:152), with the framework supplying the update and delete handlers and the module supplying only this applier and the create side. [Rubric §15, Best Practices & Code Quality]: the partner update slice is three small files and one registration line.
  • +
  • Where it's used: resolved from the container as IEntityUpdateApplier<Partner, PartnerUpdateRequest, PartnerIdentifierType> by the framework's generic UpdateEntityHandler<TEntity, TEntityDTO, TIdentifierType, TUpdateRequest>, which the PUT action reaches through UpdateEntityCommand<Partner, PartnerUpdateRequest, PartnerIdentifierType> (MMCA.ADC.Conference.API/Controllers/Partners/PartnersController.cs:185,193).
  • PartnerUpdateRequestValidator

    @@ -3816,8 +3853,8 @@

    SpeakerUpdateApplier

  • Depends on: IEntityUpdateCommandApplier<TEntity, TUpdateRequest, TIdentifierType, in TCommand> (SpeakerUpdateApplier.cs:2,14), the Speaker aggregate (:1), UpdateSpeakerCommand and SpeakerUpdateRequest (:14), Result and MutationContext (:3,17,20).
  • Concept introduced: the command-aware applier, and the privilege-gated field. The framework has two applier contracts. IEntityUpdateApplier<TEntity, TUpdateRequest, TIdentifierType> receives only the request, which is right when the body is the whole truth (see SponsorUpdateApplier). This one receives the whole command, which is what an update needs when it depends on state the request does not and must not carry (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/IEntityUpdateCommandApplier.cs:14-21). The rule it implements is a single expression: var isTopSpeaker = command.CallerIsOrganizer ? command.Request.IsTopSpeaker : entity.IsTopSpeaker (:32). A self-editing speaker's submitted value is discarded in favour of the stored one, so a crafted body cannot feature its own speaker on the site. The comment above it also records the second half of the story: LinkedUserId is not in this path at all, because the governed /link and /unlink endpoints carry the BR-208 uniqueness check and raise the events that keep Identity in sync (:26-31). [Rubric §11, Security] assesses whether authorization decisions constrain the write and not merely the route: the role check at the controller decides who may call, and this line decides what a caller may change. [Rubric §1, SOLID]: the applier is the only type that names speaker fields, so the generic handler above it stays field-agnostic. [Rubric §3, Clean Architecture]: the applier translates, the aggregate validates.
  • Walkthrough: sealed class SpeakerUpdateApplier : IEntityUpdateCommandApplier<Speaker, SpeakerUpdateRequest, SpeakerIdentifierType, UpdateSpeakerCommand> (:13-14). ApplyAsync takes the tracked entity, the command, the MutationContext and a token (:17-22), null-guards entity and command (:23-24), computes the gated flag (:32), and returns Task.FromResult(entity.Update(...)) with the ten request fields in the aggregate's parameter order plus the computed flag (:34-45). It is synchronous work wrapped in a completed task, because the mutation is pure in-memory state on an already-loaded aggregate; nothing here touches the database. The MutationContext parameter is accepted and unused: this update neither needs to carry a derived value out to the post-save hooks nor to call SkipSave.
  • -
  • Why it's built this way: returning the aggregate's own Result unchanged is the contract. Speaker.Update first parses the optional email into an Email value object and returns that failure if the address is malformed (MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:208-215), then combines the two name invariants (:217-221), and only then assigns and raises SpeakerChanged (:223-235). A refusal therefore reaches the caller as domain errors and the generic handler never saves, because the instance handed to the applier is the tracked one and a failure must leave it untouched (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/IEntityUpdateCommandApplier.cs:28-32).
  • -
  • Where it's used: injected by concrete type into UpdateSpeakerHandler (MMCA.ADC.Conference.Application/Speakers/UseCases/Update/UpdateSpeakerHandler.cs:19), which passes it to the framework base as the IEntityUpdateCommandApplier<...> the workflow calls. It is picked up by the module scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143), which registers it as itself as well as by its interfaces.
  • +
  • Why it's built this way: returning the aggregate's own Result unchanged is the contract. Speaker.Update first parses the optional email into an Email value object and returns that failure if the address is malformed (MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:209-216), then combines the two name invariants (:217-221), and only then assigns and raises SpeakerChanged (:223-235). A refusal therefore reaches the caller as domain errors and the generic handler never saves, because the instance handed to the applier is the tracked one and a failure must leave it untouched (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/IEntityUpdateCommandApplier.cs:28-32).
  • +
  • Where it's used: injected by concrete type into UpdateSpeakerHandler (MMCA.ADC.Conference.Application/Speakers/UseCases/Update/UpdateSpeakerHandler.cs:19), which passes it to the framework base as the IEntityUpdateCommandApplier<...> the workflow calls. It is picked up by the module scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139), which registers it as itself as well as by its interfaces.
  • SpeakerUpdateRequestValidator

    @@ -3839,8 +3876,8 @@

    SponsorUpdateApplier

  • Depends on: IEntityUpdateApplier<TEntity, TUpdateRequest, TIdentifierType> (SponsorUpdateApplier.cs:2,13), the Sponsor aggregate (:1), SponsorUpdateRequest (:13), and Result (:3,16).
  • Concept introduced: none new; it is the request-only twin of SpeakerUpdateApplier and the more common of the two shapes. It is worth reading precisely because there is nothing to decide: the body is the whole truth for a sponsor edit, so the applier takes the request rather than a command, and the module needs no command type of its own. The contract's own documentation frames the pair: the create mapper owns "request to a new aggregate", the applier owns "request onto an existing aggregate", and putting the mutation behind this interface is what lets the framework ship one generic update handler (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Mapping/IEntityDTOMapper.cs:62-69). [Rubric §3, Clean Architecture] assesses whether each layer holds only what it is entitled to hold: the Application layer translates a DTO into an aggregate call, and every business check stays in Domain.
  • Walkthrough: sealed class SponsorUpdateApplier : IEntityUpdateApplier<Sponsor, SponsorUpdateRequest, SponsorIdentifierType> (:12-13). ApplyAsync null-guards the entity and the request (:18-19) and returns Task.FromResult(entity.Update(...)) with all ten request members in the aggregate's parameter order (:21-31). Sponsor.Update combines three invariant checks (name, logo URL, booth number) and returns early on failure, then assigns the ten fields and raises SponsorChanged with DomainEntityState.Updated (MMCA.ADC.Conference.Domain/Sponsors/Sponsor.cs:165-183). No MutationContext parameter exists on this interface, so this applier has no way to skip the save or to hand a value to a post-save hook, and needs neither.
  • -
  • Why it's built this way: keeping the applier free of any command type is what allows the whole sponsor write side to be registered in a single line, AddEntityCrud<Sponsor, SponsorDTO, SponsorIdentifierType, SponsorCreateRequest, SponsorUpdateRequest>() (MMCA.ADC.Conference.Application/DependencyInjection.cs:155), with the framework supplying the update and delete handlers and the module supplying only this applier and the create side. [Rubric §15, Best Practices & Code Quality]: the sponsor update slice is three small files and one registration line.
  • -
  • Where it's used: resolved from the container as IEntityUpdateApplier<Sponsor, SponsorUpdateRequest, SponsorIdentifierType> by the framework's generic UpdateEntityHandler<TEntity, TEntityDTO, TIdentifierType, TUpdateRequest>, which the PUT action reaches through UpdateEntityCommand<Sponsor, SponsorUpdateRequest, SponsorIdentifierType> (MMCA.ADC.Conference.API/Controllers/Sponsors/SponsorsController.cs:43,195).
  • +
  • Why it's built this way: keeping the applier free of any command type is what allows the whole sponsor write side to be registered in a single line, AddEntityCrud<Sponsor, SponsorDTO, SponsorIdentifierType, SponsorCreateRequest, SponsorUpdateRequest>() (MMCA.ADC.Conference.Application/DependencyInjection.cs:151), with the framework supplying the update and delete handlers and the module supplying only this applier and the create side. [Rubric §15, Best Practices & Code Quality]: the sponsor update slice is three small files and one registration line.
  • +
  • Where it's used: resolved from the container as IEntityUpdateApplier<Sponsor, SponsorUpdateRequest, SponsorIdentifierType> by the framework's generic UpdateEntityHandler<TEntity, TEntityDTO, TIdentifierType, TUpdateRequest>, which the PUT action reaches through UpdateEntityCommand<Sponsor, SponsorUpdateRequest, SponsorIdentifierType> (MMCA.ADC.Conference.API/Controllers/Sponsors/SponsorsController.cs:43,203).
  • SponsorUpdateRequestValidator

    @@ -3875,8 +3912,8 @@

    UpdateSpeakerHandler

  • Depends on: UpdateEntityCommandHandler<TCommand, TEntity, TEntityDTO, TIdentifierType, TUpdateRequest> (UpdateSpeakerHandler.cs:6,22), IUnitOfWork (:5,18), SpeakerUpdateApplier (:19), SpeakerDTOMapper (:2,20), the Speaker aggregate (:3), SpeakerDTO (:4), and ILogger<T> with [LoggerMessage] (:1,21,34).
  • Concept introduced: subclassing a generic handler for vocabulary alone. There is no HandleAsync in this file. The base's shared machinery does all of it, and it is worth tracing once, because every write handler in the module ultimately runs it (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/MutateEntityHandlerBase.cs:280-321): get the typed repository from the unit of work (:279), load the aggregate tracked (:280), return Error.NotFound tagged with HandlerName and the entity type when it is gone (:281-282), stamp the caller's token with repository.SetOriginalRowVersion(entity, rowVersion) when one is present (:290-291), run the mutation (:293), short-circuit on refusal (:294-295), honour a SkipSave (:299-300), save once (:302), then log and run the post-save hook (:304-305). The DTO-returning subclass maps the saved aggregate through the registered mapper on the way out (:352-359). The concurrency stamp is the non-obvious line: without it EF Core would compare the row against the version it loaded a moment ago and always succeed, whereas stamping the client's last-seen token turns the UPDATE predicate into "has anyone written this row since the client read it?" and raises a concurrency exception when they have, surfaced as 412 rather than silent last-write-wins (ADR-035, and the in-code note at :284-289). The derived-command flavour then hands the whole command to the applier rather than only the request (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/UpdateEntityHandler.cs:218-223). [Rubric §15, Best Practices & Code Quality] assesses whether shared workflow lives in one place: a change to the update workflow is one edit in MMCA.Common, not one per aggregate. [Rubric §13, Observability & Operability]: the two overrides are precisely the operator-facing parts, the name in an error and the message in a log.
  • Walkthrough: sealed partial class UpdateSpeakerHandler(IUnitOfWork unitOfWork, SpeakerUpdateApplier updateApplier, SpeakerDTOMapper dtoMapper, ILogger<UpdateSpeakerHandler> logger) : UpdateEntityCommandHandler<UpdateSpeakerCommand, Speaker, SpeakerDTO, SpeakerIdentifierType, SpeakerUpdateRequest>(unitOfWork, updateApplier, dtoMapper) (:17-25). Three of the four constructor parameters are forwarded to the base; only the logger stays local. HandlerName => nameof(UpdateSpeakerHandler) (:28) overrides the base's default, which would otherwise read as the open generic plus the command name (UpdateEntityHandler.cs:199), so a NotFound failure reads as it always has. LogMutated forwards to the source-generated partial with the speaker id (:31-32), declared at [LoggerMessage(Level = LogLevel.Information, Message = "Speaker {SpeakerId} updated")] (:34-35). The class is partial for that generator, and sealed like every other handler here.
  • -
  • Why it's built this way: the module could have skipped this file entirely and let AddEntityUpdate register the framework handler closed over UpdateSpeakerCommand. It keeps the subclass for the two vocabulary items, and the registration order is what makes that work: the module scan registers this handler first (MMCA.ADC.Conference.Application/DependencyInjection.cs:143), then AddEntityUpdate runs with TryAdd and therefore does not displace it, while still completing the validator bridge (:150, explained in the block comment at :144-149; the framework side is MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Crud.cs:195-199). It also raises no events of its own, deliberately: domain events belong to the aggregate's mutation methods, and a handler that published something would fire on the generic path and stay silent on a hand-written one (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/UpdateEntityHandler.cs:33-37).
  • -
  • Where it's used: resolved as ICommandHandler<UpdateSpeakerCommand, Result<SpeakerDTO>> by SpeakersController (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:45,341), behind the standard decorator pipeline; after a success the controller evicts the conference:speakers and conference output-cache tags and returns the mapped DTO (:357-358).
  • +
  • Why it's built this way: the module could have skipped this file entirely and let AddEntityUpdate register the framework handler closed over UpdateSpeakerCommand. It keeps the subclass for the two vocabulary items, and the registration order is what makes that work: the module scan registers this handler first (MMCA.ADC.Conference.Application/DependencyInjection.cs:139), then AddEntityUpdate runs with TryAdd and therefore does not displace it, while still completing the validator bridge (:150, explained in the block comment at :144-149; the framework side is MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Crud.cs:195-199). It also raises no events of its own, deliberately: domain events belong to the aggregate's mutation methods, and a handler that published something would fire on the generic path and stay silent on a hand-written one (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/UpdateEntityHandler.cs:33-37).
  • +
  • Where it's used: resolved as ICommandHandler<UpdateSpeakerCommand, Result<SpeakerDTO>> by SpeakersController (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:45,352), behind the standard decorator pipeline; after a success the controller evicts the conference:speakers and conference output-cache tags and returns the mapped DTO (:357-358).
  • DeleteSessionAssetBlobInternalCommand

    @@ -3918,10 +3955,10 @@

    AddCategoryItemCommand

    • What it is: the write intent for adding one CategoryItem (a "Beginner", a "Track A") to an existing Category. Four positional members and one computed cache prefix.
    • Depends on: ICacheInvalidating from MMCA.Common.Application.UseCases (AddCategoryItemCommand.cs:2,18) and the Category domain type, used only for its FullName in the prefix (:1,21). ConferenceCategoryIdentifierType and CategoryItemIdentifierType are the module's identifier aliases.
    • -
    • Concept introduced: the command that is its own request, and the optional client-supplied identity. Most write slices in this module are a pair, a request record plus a command that wraps it, which is what wires the automatic IValidator<TRequest> bridge (ICommandWithRequest<out TRequest>). This one is flat: the API's own AddCategoryItemRequest is unpacked member by member into the command at the controller (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:135-139), so the command is the validated object and AddCategoryItemCommandValidator targets the command type directly. The second point of interest is CategoryItemId (:16), a nullable identifier on a create: null means "let the database generate the key", non-null means "use this Sessionize-assigned id" (:11). That is what lets an import replay upstream keys and a hand-created item take a local one, through one code path. [Rubric §6, CQRS & Event-Driven] assesses whether writes are explicit intents carrying everything the handler needs; [Rubric §8, Data Architecture]: an externally-assigned primary key is a real modelling decision, and making it an option on the command keeps the import from needing a parallel write path.
    • +
    • Concept introduced: the command that is its own request, and the optional client-supplied identity. Most write slices in this module are a pair, a request record plus a command that wraps it, which is what wires the automatic IValidator<TRequest> bridge (ICommandWithRequest<out TRequest>). This one is flat: the API's own AddCategoryItemRequest is unpacked member by member into the command at the controller (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:138-142), so the command is the validated object and AddCategoryItemCommandValidator targets the command type directly. The second point of interest is CategoryItemId (:16), a nullable identifier on a create: null means "let the database generate the key", non-null means "use this Sessionize-assigned id" (:11). That is what lets an import replay upstream keys and a hand-created item take a local one, through one code path. [Rubric §6, CQRS & Event-Driven] assesses whether writes are explicit intents carrying everything the handler needs; [Rubric §8, Data Architecture]: an externally-assigned primary key is a real modelling decision, and making it an option on the command keeps the import from needing a parallel write path.
    • Walkthrough: sealed record AddCategoryItemCommand(ConferenceCategoryIdentifierType CategoryId, CategoryItemIdentifierType? CategoryItemId, string Name, int Sort) : ICacheInvalidating (:14-18). CategoryId is the parent to load, Name and Sort are the child's two editable fields. CachePrefix is an expression-bodied property returning $"{typeof(Category).FullName}:" (:21), the key namespace the CachingCommandDecorator<TCommand, TResult> wipes after a successful handle. The prefix names the parent aggregate, not the child, because a category's cached read includes its items.
    • Why it's built this way: deriving the prefix from typeof(Category).FullName rather than a literal keeps the writer and the cached reader agreed on one key namespace that a rename cannot desynchronize. The command implements no ITransactional marker: the handler writes one aggregate and saves once, so the ambient save boundary suffices (ADR-014).
    • -
    • Where it's used: constructed by CategoryItemsController on POST against the injected ICommandHandler<AddCategoryItemCommand, Result<CategoryItemDTO>> (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:65,128-140), and handled by AddCategoryItemHandler. That action carries [Idempotent] (:129), so a retried request with the same Idempotency-Key replays the first response instead of adding a second row (IdempotentAttribute).
    • +
    • Where it's used: constructed by CategoryItemsController on POST against the injected ICommandHandler<AddCategoryItemCommand, Result<CategoryItemDTO>> (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:65,131-143), and handled by AddCategoryItemHandler. That action carries [Idempotent] (:129), so a retried request with the same Idempotency-Key replays the first response instead of adding a second row (IdempotentAttribute).

    CategoryItemDTOMapper

    @@ -3932,7 +3969,7 @@

    CategoryItemDTOMapper

  • Depends on: IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType> closed over the three category-item types (CategoryItemDTOMapper.cs:1-3,12-13), and the Riok.Mapperly.Abstractions [Mapper] attribute (:4,11).
  • Concept introduced: the source-generated DTO mapper. The body of MapToDTO is not in this file. [Mapper] on a partial class tells the Mapperly source generator to emit the property-by-property assignment at compile time, and the class declares only the signature: public partial CategoryItemDTO MapToDTO(CategoryItem entity) (:16). Three consequences follow, and they are the whole reason the codebase prefers this over a runtime convention mapper such as AutoMapper (ADR-001). A property rename breaks the build rather than silently producing a null field. The generated code is plain, steppable C# with no reflection or expression compilation at request time. And a stack trace points at a specific line in a specific mapper. The mapping itself is by name: CategoryItemDTO declares Id, Name, Sort and CategoryId (MMCA.ADC.Conference.Shared/Categories/CategoryItemDTO.cs:19,22,25,28), all of which exist on the entity. [Rubric §15, Best Practices & Code Quality] assesses whether repetitive mechanical code is generated rather than hand-maintained; [Rubric §12, Performance & Scalability]: compile-time mapping removes per-request reflection from every read path this mapper serves.
  • Walkthrough: [Mapper] (:11) then public sealed partial class CategoryItemDTOMapper : IEntityDTOMapper<CategoryItem, CategoryItemDTO, CategoryItemIdentifierType> (:12-13). One generated member, MapToDTO (:16). MapToDTOs is written out by hand as a null-guard plus a collection expression over Select(MapToDTO) (:19-23), which is the same body the interface already supplies as a default implementation (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Mapping/IEntityDTOMapper.cs:27-32); re-declaring it makes the batch method visible on the concrete type rather than only through the interface. ADR-001 notes this re-declaration as the prevailing habit across the 31 mappers in Store and ADC.
  • -
  • Where it's used: injected by name into AddCategoryItemHandler (MMCA.ADC.Conference.Application/Categories/UseCases/AddCategoryItem/AddCategoryItemHandler.cs:17) and into UpdateCategoryItemHandler; nested as a child mapper by ConferenceCategoryDTOMapper (ConferenceCategoryDTOMapper.cs:14,18). It is registered by the module scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143) both as itself and by its interface, which is what lets a framework generic such as EntityQueryService<TEntity, TEntityDTO, TIdentifierType> resolve it as IEntityDTOMapper<...> without naming it.
  • +
  • Where it's used: injected by name into AddCategoryItemHandler (MMCA.ADC.Conference.Application/Categories/UseCases/AddCategoryItem/AddCategoryItemHandler.cs:17) and into UpdateCategoryItemHandler; nested as a child mapper by ConferenceCategoryDTOMapper (ConferenceCategoryDTOMapper.cs:14,18). It is registered by the module scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139) both as itself and by its interface, which is what lets a framework generic such as EntityQueryService<TEntity, TEntityDTO, TIdentifierType> resolve it as IEntityDTOMapper<...> without naming it.
  • ConferenceCategoryCreateRequest

    @@ -3942,9 +3979,9 @@

    ConferenceCategoryCreateRequest

  • What it is: the inbound contract for creating a conference Category, the aggregate behind vocabularies such as "Level", "Track", and "Session Format". As with the other create slices in this module it is both the HTTP request body and the command the CQRS pipeline dispatches: there is no separate CreateConferenceCategoryCommand.
  • Depends on: ICreateRequest and ICacheInvalidating from MMCA.Common.Application.Interfaces / .UseCases (ConferenceCategoryCreateRequest.cs:2-3,10), the Category domain type used only to build the cache prefix (ConferenceCategoryCreateRequest.cs:1,13), and the ConferenceCategoryIdentifierType alias (= int, MMCA.ADC.Conference.Shared/MMCA.ADC.Conference.GlobalUsings.IdentifierType.cs:9).
  • Concept: the create-request-as-command shape introduced by EventCreateRequest, applied to the smallest aggregate in the module. Implementing the marker ICreateRequest (ConferenceCategoryCreateRequest.cs:10) is what lets the generic create machinery accept this record straight off the wire, hand it to an IEntityRequestMapper<TEntity, TCreateRequest, TIdentifierType>, and dispatch it as ICommandHandler<ConferenceCategoryCreateRequest, Result<ConferenceCategoryDTO>>. Note that it is declared public record class rather than sealed record (ConferenceCategoryCreateRequest.cs:10), the only shape difference from its command siblings in this unit. [Rubric §9, API & Contract Design] assesses whether an inbound contract is explicit about shape and optionality: exactly one member is required (Title, ConferenceCategoryCreateRequest.cs:19), and the other three are optional by declaration, which is the endpoint's optionality documentation. [Rubric §12, Performance & Scalability]: cache eviction is declared, not coded, because the caching decorator reads CachePrefix off the request.
  • -
  • Walkthrough: CachePrefix => $"{typeof(Category).FullName}:" (ConferenceCategoryCreateRequest.cs:13) is the eviction key the caching decorator purges on success, keyed on the aggregate root so every cached category read is invalidated together. Id (ConferenceCategoryCreateRequest.cs:16) is a non-nullable ConferenceCategoryIdentifierType, which matters downstream: Category.Create declares its id parameter as nullable (MMCA.ADC.Conference.Domain/Categories/Category.cs:54) precisely so a caller can say "no id", but this request can never express that, so an omitted id binds to 0 and the factory's throw branch (Category.cs:69) is unreachable from the HTTP path (unreachable for this aggregate in any case, since Category carries [IdValueGenerated], Category.cs:15). The reason the factory accepts an id at all is the Sessionize import, which carries category ids assigned upstream (MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/CategorySyncStrategy.cs:99). Title is required (ConferenceCategoryCreateRequest.cs:19), Sort is a plain int display order (ConferenceCategoryCreateRequest.cs:22), and Type is the optional discriminator whose documented examples are "session" and "speaker" (ConferenceCategoryCreateRequest.cs:25). Every member is init-only, so the request cannot be mutated after model binding.
  • +
  • Walkthrough: CachePrefix => $"{typeof(Category).FullName}:" (ConferenceCategoryCreateRequest.cs:13) is the eviction key the caching decorator purges on success, keyed on the aggregate root so every cached category read is invalidated together. Id (ConferenceCategoryCreateRequest.cs:16) is a non-nullable ConferenceCategoryIdentifierType, which matters downstream: Category.Create declares its id parameter as nullable (MMCA.ADC.Conference.Domain/Categories/Category.cs:54) precisely so a caller can say "no id", but this request can never express that, so an omitted id binds to 0 and the factory's throw branch (Category.cs:69) is unreachable from the HTTP path (unreachable for this aggregate in any case, since Category carries [IdValueGenerated], Category.cs:15). The reason the factory accepts an id at all is the Sessionize import, which carries category ids assigned upstream (MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/CategorySyncStrategy.cs:107). Title is required (ConferenceCategoryCreateRequest.cs:19), Sort is a plain int display order (ConferenceCategoryCreateRequest.cs:22), and Type is the optional discriminator whose documented examples are "session" and "speaker" (ConferenceCategoryCreateRequest.cs:25). Every member is init-only, so the request cannot be mutated after model binding.
  • Why it's built this way: collapsing request and command removes a mapping step with no behavior of its own, and keeping the id nullable on the domain factory while non-nullable on the wire contract lets one aggregate serve both the API (store-generated keys) and the importer (Sessionize-assigned keys).
  • -
  • Where it's used: bound by the ConferenceCategoriesController create action (MMCA.ADC.Conference.API/Controllers/Categories/ConferenceCategoriesController.cs:97-98), which types its base class on it (ConferenceCategoriesController.cs:42) and injects the handler as ICommandHandler<ConferenceCategoryCreateRequest, Result<ConferenceCategoryDTO>> (ConferenceCategoriesController.cs:37); validated by ConferenceCategoryCreateRequestValidator, converted by ConferenceCategoryCreateRequestMapper, handled by CreateConferenceCategoryHandler. It is also one half of the AddEntityCrud<Category, ConferenceCategoryDTO, ConferenceCategoryIdentifierType, ConferenceCategoryCreateRequest, ConferenceCategoryUpdateRequest>() registration (MMCA.ADC.Conference.Application/DependencyInjection.cs:153); its edit-side counterpart is ConferenceCategoryUpdateRequest.
  • +
  • Where it's used: bound by the ConferenceCategoriesController create action (MMCA.ADC.Conference.API/Controllers/Categories/ConferenceCategoriesController.cs:100-101), which types its base class on it (ConferenceCategoriesController.cs:42) and injects the handler as ICommandHandler<ConferenceCategoryCreateRequest, Result<ConferenceCategoryDTO>> (ConferenceCategoriesController.cs:37); validated by ConferenceCategoryCreateRequestValidator, converted by ConferenceCategoryCreateRequestMapper, handled by CreateConferenceCategoryHandler. It is also one half of the AddEntityCrud<Category, ConferenceCategoryDTO, ConferenceCategoryIdentifierType, ConferenceCategoryCreateRequest, ConferenceCategoryUpdateRequest>() registration (MMCA.ADC.Conference.Application/DependencyInjection.cs:149); its edit-side counterpart is ConferenceCategoryUpdateRequest.
  • RemoveCategoryItemCommand

    @@ -3954,9 +3991,9 @@

    RemoveCategoryItemCommand

  • What it is: the write intent for removing one CategoryItem from its owning Category. It names the category and the item, and nothing else.
  • Depends on: ICacheInvalidating (RemoveCategoryItemCommand.cs:2,14), the Category type for the cache prefix (RemoveCategoryItemCommand.cs:1,17), and the ConferenceCategoryIdentifierType / CategoryItemIdentifierType aliases (both = int, MMCA.ADC.Conference.Shared/MMCA.ADC.Conference.GlobalUsings.IdentifierType.cs:8-9).
  • Concept: the remove-child command shape, the mirror of AddCategoryItemCommand and deliberately narrower than it. An add carries the child's payload; a remove carries only the two identifiers, because the child already exists and the only decision left is which one. Note that both identifiers here are non-nullable (RemoveCategoryItemCommand.cs:13-14), where the add's child id is optional: an add may invent identity, a remove may only reference it. The pair (CategoryId, CategoryItemId) is what makes the operation aggregate-scoped: the handler loads the category and asks it to remove the item, so no caller can delete an item by id alone and bypass the aggregate's rules. The cache prefix is keyed on Category, not on the child, because a cached category read carries its items inline. [Rubric §4, Domain-Driven Design] assesses whether children are mutated through their root: the command's shape makes any other access path impossible to express.
  • -
  • Walkthrough: a sealed record with two positional parameters, CategoryId and CategoryItemId (RemoveCategoryItemCommand.cs:12-14), plus the single computed CachePrefix => $"{typeof(Category).FullName}:" (RemoveCategoryItemCommand.cs:17). There is no validator class in the folder, because two required identifiers have nothing to check beyond model binding, and there is no RowVersion member: unlike the conditional category update, a category-item removal states no optimistic-concurrency precondition, which is exactly the case the framework's RowVersion(command) hook returns null for (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/MutateEntityHandlerBase.cs:91). Note what CategoryId is allowed to be: the DELETE endpoint takes it as a [FromQuery] argument with no [Required] and no default (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:182), so it legitimately arrives as 0, and RemoveCategoryItemHandler is the piece that copes with that.
  • -
  • Why it's built this way: keeping the payload to two identifiers means the command is fully described by the route plus one query argument, and it leaves the aggregate as the only place that knows what removing an item means (a soft delete on the child through RemoveChildOrNotFound plus a CategoryItemChanged domain event, MMCA.ADC.Conference.Domain/Categories/Category.cs:188-194).
  • -
  • Where it's used: constructed by the CategoryItemsController as new RemoveCategoryItemCommand(categoryId, id) (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:186, handler injected at CategoryItemsController.cs:67) and handled by RemoveCategoryItemHandler. Its add and update counterparts are AddCategoryItemCommand and UpdateCategoryItemCommand.
  • +
  • Walkthrough: a sealed record with two positional parameters, CategoryId and CategoryItemId (RemoveCategoryItemCommand.cs:12-14), plus the single computed CachePrefix => $"{typeof(Category).FullName}:" (RemoveCategoryItemCommand.cs:17). There is no validator class in the folder, because two required identifiers have nothing to check beyond model binding, and there is no RowVersion member: unlike the conditional category update, a category-item removal states no optimistic-concurrency precondition, which is exactly the case the framework's RowVersion(command) hook returns null for (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/MutateEntityHandlerBase.cs:91). Note what CategoryId is allowed to be: the DELETE endpoint takes it as a [FromQuery] argument with no [Required] and no default (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:185), so it legitimately arrives as 0, and RemoveCategoryItemHandler is the piece that copes with that.
  • +
  • Why it's built this way: keeping the payload to two identifiers means the command is fully described by the route plus one query argument, and it leaves the aggregate as the only place that knows what removing an item means (a soft delete on the child through RemoveChildOrNotFound plus a CategoryItemChanged domain event, MMCA.ADC.Conference.Domain/Categories/Category.cs:190-196).
  • +
  • Where it's used: constructed by the CategoryItemsController as new RemoveCategoryItemCommand(categoryId, id) (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:189, handler injected at CategoryItemsController.cs:67) and handled by RemoveCategoryItemHandler. Its add and update counterparts are AddCategoryItemCommand and UpdateCategoryItemCommand.
  • AddCategoryItemCommandValidator

    @@ -3968,7 +4005,7 @@

    AddCategoryItemCommandValidator

  • Concept introduced: validating the command when there is no request record to validate. Everywhere else in this module the validator targets the request DTO and the framework bridges it to the command through CommandRequestValidator<TCommand, TRequest>. AddCategoryItemCommand carries no request, so the bridge has nothing to bridge and the validator names the command type directly (:7). Nothing else changes: the ValidatingCommandDecorator<TCommand, TResult> resolves IValidator<AddCategoryItemCommand> exactly as it would resolve any other, because the decorator keys on the command type either way. This is the clean illustration that the bridge is a convenience for the two-record shape, not a requirement of the pipeline. [Rubric §24, Forms/Validation/UX Safety] assesses whether every inbound field has a declared contract; [Rubric §1, SOLID]: this class composes and does nothing else, so a name-length change is found in one rule set rather than in each validator that guards a name.
  • Walkthrough: sealed class AddCategoryItemCommandValidator : AbstractValidator<AddCategoryItemCommand> (:7). The constructor (:9-13) is two Include calls with property selectors: CategoryItemNameRules<AddCategoryItemCommand>(p => p.Name) (:11) contributes NotEmpty plus MaximumLength(CategoryInvariants.CategoryItemNameMaxLength) with the stable codes CategoryItem.Name.Required and CategoryItem.Name.MaxLength (MMCA.ADC.Conference.Application/Categories/Validation/ConferenceCategoryValidationRules.cs:31-34), and CategoryItemSortRules<AddCategoryItemCommand>(p => p.Sort) (:12) is a thin subclass of the shared NonNegativeIntRules<T> carrying the code CategoryItem.Sort.Negative (ConferenceCategoryValidationRules.cs:41-46). CategoryId and CategoryItemId carry no rule: a missing parent is a NotFound the handler reports after a load, not a shape error.
  • Why it's built this way: the name's maximum length is not a literal here. The rule set reads CategoryInvariants.CategoryItemNameMaxLength, the same constant the domain guard and the EF column width use (MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Categories/CategoryItemConfiguration.cs:18-20), so the validator message, the aggregate's own check and the database agree on one number. [Rubric §15, Best Practices & Code Quality]: widening the field is a one-constant change.
  • -
  • Where it's used: discovered by assembly scanning (MMCA.ADC.Conference.Application/DependencyInjection.cs:143) and executed by the ValidatingCommandDecorator<TCommand, TResult> ahead of AddCategoryItemHandler.
  • +
  • Where it's used: discovered by assembly scanning (MMCA.ADC.Conference.Application/DependencyInjection.cs:139) and executed by the ValidatingCommandDecorator<TCommand, TResult> ahead of AddCategoryItemHandler.
  • ConferenceCategoryDTOMapper

    @@ -3980,7 +4017,7 @@

    ConferenceCategoryDTOMapper

  • Concept introduced: nested mapper composition. A generated mapper knows how to copy scalar properties by name, but Category.CategoryItems is a collection of entities and the DTO wants a collection of DTOs. [UseMapper] on a private field holding another mapper (:17-18) tells the generator to route any member it cannot map directly through that mapper, so the emitted MapToDTO projects each CategoryItem through CategoryItemDTOMapper rather than the parent mapper duplicating the child's field list. Composition rather than a second copy of the same mapping is what keeps a change to CategoryItemDTO from needing an edit in two files. [Rubric §15, Best Practices & Code Quality] assesses whether one fact is expressed once; [Rubric §2, Design Patterns]: this is plain constructor composition, and it is why the child mapper is registered as its own concrete type by the module scan and not only by its interface.
  • Walkthrough: [Mapper] (:12), then public sealed partial class ConferenceCategoryDTOMapper(CategoryItemDTOMapper categoryItemDTOMapper) : IEntityDTOMapper<Category, ConferenceCategoryDTO, ConferenceCategoryIdentifierType> (:13-15). The primary-constructor parameter is captured into a [UseMapper] private readonly field (:17-18), which is the form the generator looks for. MapToDTO is the generated partial, decorated with [MapProperty("CategoryItems.Count", nameof(ConferenceCategoryDTO.CategoryItemCount))] (:25-26): the DTO's CategoryItemCount has no like-named source property, so this attribute tells the generator to compute it from the loaded child collection's Count() instead, the same number the grid sorts on server-side (:21-23). MapToDTOs is the hand-written null-guard plus projection (:29-33). Note the naming asymmetry the mapper straddles: the domain type is Category, the DTO is ConferenceCategoryDTO, and the identifier alias is ConferenceCategoryIdentifierType, so this file is the place where the two vocabularies meet. The target DTO also carries a RowVersion (MMCA.ADC.Conference.Shared/Categories/ConferenceCategoryDTO.cs:26), which is what lets a client echo the token back as an If-Match precondition on the next update (ADR-035).
  • Caveats / not-in-source: whether the CategoryItems collection is populated in any given response depends on what the caller loaded, not on the mapper. A category read without the child include maps to an empty CategoryItems list, and now also to a CategoryItemCount of 0, since both are computed from the same navigation.
  • -
  • Where it's used: injected into CreateConferenceCategoryHandler (MMCA.ADC.Conference.Application/Categories/UseCases/Create/CreateConferenceCategoryHandler.cs:18), and resolved as IEntityDTOMapper<Category, ConferenceCategoryDTO, ConferenceCategoryIdentifierType> by the framework's generic write and read paths registered through AddEntityCrud<Category, ...> (MMCA.ADC.Conference.Application/DependencyInjection.cs:153).
  • +
  • Where it's used: injected into CreateConferenceCategoryHandler (MMCA.ADC.Conference.Application/Categories/UseCases/Create/CreateConferenceCategoryHandler.cs:18), and resolved as IEntityDTOMapper<Category, ConferenceCategoryDTO, ConferenceCategoryIdentifierType> by the framework's generic write and read paths registered through AddEntityCrud<Category, ...> (MMCA.ADC.Conference.Application/DependencyInjection.cs:149).
  • ConferenceCategoryCreateRequestMapper

    @@ -3990,9 +4027,9 @@

    ConferenceCategoryCreateRequestMa
  • What it is: the one adapter that turns a ConferenceCategoryCreateRequest into a Category domain entity, by calling the aggregate's Create factory and returning whatever Result<T> it produces.
  • Depends on: IEntityRequestMapper<TEntity, TCreateRequest, TIdentifierType> from MMCA.Common.Application.Interfaces (ConferenceCategoryCreateRequestMapper.cs:2,11-12), the Category aggregate and its ConferenceCategoryIdentifierType alias (ConferenceCategoryCreateRequestMapper.cs:1), and Result<T> from MMCA.Common.Shared.Abstractions (ConferenceCategoryCreateRequestMapper.cs:3,15).
  • Concept: request-to-entity mapping as a separate injectable role. The generic create pipeline never constructs entities itself: CreateEntityHandlerBase<TCreateRequest, TEntity, TIdentifierType, TEntityDTO> resolves an IEntityRequestMapper<TEntity, TCreateRequest, TIdentifierType> and asks it for one (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:44,90), which is what lets one workflow serve every aggregate while each aggregate keeps its own construction rules. Two properties matter. First, it is a plain sealed class with no [Mapper] attribute (ConferenceCategoryCreateRequestMapper.cs:11): unlike the read-side DTO mappers in this unit, request-to-entity conversion is deliberately hand-written, because it must go through a factory that can fail, which a property-copy generator cannot express. Second, it returns Task<Result<Category>> rather than a Category, so an invalid request produces a failure value that flows back as a 400-class response instead of an exception. [Rubric §3, Clean Architecture] assesses whether the domain stays independent of the delivery mechanism: the controller knows a request type, the domain knows a factory, and this class is the only thing that knows both. [Rubric §4, Domain-Driven Design]: the factory stays the single construction path, so no invariant can be bypassed by new. See ADR-001 for the no-reflection mapping policy.
  • -
  • Walkthrough: one method. CreateEntityAsync(ConferenceCategoryCreateRequest request, CancellationToken) (ConferenceCategoryCreateRequestMapper.cs:15) null-guards with ArgumentNullException.ThrowIfNull(request) (ConferenceCategoryCreateRequestMapper.cs:17), then returns Task.FromResult(Category.Create(request.Id, request.Title, request.Sort, request.Type)) (ConferenceCategoryCreateRequestMapper.cs:19-23). The method is synchronous in substance: Task.FromResult satisfies the async contract without a state machine, because the factory does no I/O. Inside the factory (MMCA.ADC.Conference.Domain/Categories/Category.cs:54-75) the title is checked by CategoryInvariants.EnsureTitleIsValid through Result.Combine (Category.cs:61), the id is resolved against the [IdValueGenerated] check (Category.cs:65,69), and a CategoryChanged domain event with DomainEntityState.Added is queued on the new aggregate (Category.cs:72) for the outbox to pick up at save time. Because the request's Id is non-nullable (ConferenceCategoryCreateRequest.cs:16), what actually reaches the factory is 0 when the client omits it.
  • -
  • Why it's built this way: pushing construction into Category.Create means the invariant check and the domain event run for every caller, not just HTTP ones (the Sessionize importer calls the same factory at MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/CategorySyncStrategy.cs:99). The mapper adds no rules of its own, which is exactly what makes it safe to have several entry points into the same aggregate.
  • -
  • Where it's used: injected into CreateConferenceCategoryHandler as IEntityRequestMapper<Category, ConferenceCategoryCreateRequest, ConferenceCategoryIdentifierType> (MMCA.ADC.Conference.Application/Categories/UseCases/Create/CreateConferenceCategoryHandler.cs:17), registered by the module's convention scan rather than an explicit AddScoped line (MMCA.ADC.Conference.Application/DependencyInjection.cs:143).
  • +
  • Walkthrough: one method. CreateEntityAsync(ConferenceCategoryCreateRequest request, CancellationToken) (ConferenceCategoryCreateRequestMapper.cs:15) null-guards with ArgumentNullException.ThrowIfNull(request) (ConferenceCategoryCreateRequestMapper.cs:17), then returns Task.FromResult(Category.Create(request.Id, request.Title, request.Sort, request.Type)) (ConferenceCategoryCreateRequestMapper.cs:19-23). The method is synchronous in substance: Task.FromResult satisfies the async contract without a state machine, because the factory does no I/O. Inside the factory (MMCA.ADC.Conference.Domain/Categories/Category.cs:54-76) the title is checked by CategoryInvariants.EnsureTitleIsValid through Result.Combine (Category.cs:61), the id is resolved against the [IdValueGenerated] check (Category.cs:65,69), and a CategoryChanged domain event with DomainEntityState.Added is queued on the new aggregate (Category.cs:72) for the outbox to pick up at save time. Because the request's Id is non-nullable (ConferenceCategoryCreateRequest.cs:16), what actually reaches the factory is 0 when the client omits it.
  • +
  • Why it's built this way: pushing construction into Category.Create means the invariant check and the domain event run for every caller, not just HTTP ones (the Sessionize importer calls the same factory at MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/CategorySyncStrategy.cs:107). The mapper adds no rules of its own, which is exactly what makes it safe to have several entry points into the same aggregate.
  • +
  • Where it's used: injected into CreateConferenceCategoryHandler as IEntityRequestMapper<Category, ConferenceCategoryCreateRequest, ConferenceCategoryIdentifierType> (MMCA.ADC.Conference.Application/Categories/UseCases/Create/CreateConferenceCategoryHandler.cs:17), registered by the module's convention scan rather than an explicit AddScoped line (MMCA.ADC.Conference.Application/DependencyInjection.cs:139).
  • ConferenceCategoryCreateRequestValidator

    @@ -4004,7 +4041,7 @@

    ConferenceCategoryCreateReques
  • Concept: rule composition by Include with a property selector, the mechanism taught by QuestionTextRules<T>. Include(new ConferenceCategoryTitleRules<ConferenceCategoryCreateRequest>(p => p.Title)) (ConferenceCategoryCreateRequestValidator.cs:10) copies the shared title rules onto this request's Title property, and the update-side validator includes the same rule object against ConferenceCategoryUpdateRequest, so create and update cannot disagree about what a valid title is. Equally instructive is what is not validated: Sort and Type have no rules at all here, even though a sibling rule object for a sort value exists (CategoryItemSortRules<T>, applied only to category items by AddCategoryItemCommandValidator and UpdateCategoryItemCommandValidator). A negative Sort on a category is therefore accepted. [Rubric §24, Forms, Validation & UX Safety] assesses whether invalid input is rejected at the boundary: the title path is covered, the sort path is not. [Rubric §15, Best Practices & Code Quality]: one shared rule object is one edit point instead of one per slice.
  • Walkthrough: the whole type is an expression-bodied constructor (ConferenceCategoryCreateRequestValidator.cs:9-10). The rule bodies it pulls in live at MMCA.ADC.Conference.Application/Categories/Validation/ConferenceCategoryValidationRules.cs:17-20: NotEmpty with code Category.Title.Required (ConferenceCategoryValidationRules.cs:19), then MaximumLength(CategoryInvariants.TitleMaxLength) with code Category.Title.MaxLength (ConferenceCategoryValidationRules.cs:20), where the limit is 255, declared on the wire contract (MMCA.ADC.Conference.Shared/Categories/ConferenceCategoryDTO.cs:17) and forwarded by the domain (MMCA.ADC.Conference.Domain/Categories/CategoryInvariants.cs:18). Note the domain declares that forward as public static readonly int rather than const, so it is read at runtime rather than baked into each caller.
  • Why it's built this way: validating at the pipeline boundary gives the caller a complete, field-addressed error list in one round trip, while Category.Create keeps its own EnsureTitleIsValid check (MMCA.ADC.Conference.Domain/Categories/Category.cs:61) as the backstop for non-HTTP callers such as the Sessionize import. The duplication is intentional and cheap because both sides read the same constant.
  • -
  • Where it's used: resolved by the validation decorator around CreateConferenceCategoryHandler, registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143); covered directly by ConferenceCategoryCreateRequestValidatorTests (MMCA.ADC.Conference.Application.Tests/Categories/Validation/ConferenceCategoryCreateRequestValidatorTests.cs:7).
  • +
  • Where it's used: resolved by the validation decorator around CreateConferenceCategoryHandler, registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); covered directly by ConferenceCategoryCreateRequestValidatorTests (MMCA.ADC.Conference.Application.Tests/Categories/Validation/ConferenceCategoryCreateRequestValidatorTests.cs:7).
  • AddCategoryItemHandler

    @@ -4013,10 +4050,10 @@

    AddCategoryItemHandler

    • What it is: the handler for AddCategoryItemCommand. It supplies five small overrides to the framework's add-a-child workflow: which parent to load, what to eager-load, which aggregate method to call, how to map the new child, and what to log.
    • Depends on: AddChildEntityHandlerBase<TCommand, TParent, TIdentifierType, TChild, TChildDTO> (AddCategoryItemHandler.cs:6,19), IUnitOfWork forwarded to the base (:5,16), CategoryItemDTOMapper (:2,17), Result (:7,29), the Category and CategoryItem domain types (:3), CategoryItemDTO (:4), and ILogger<T> (:1,18).
    • -
    • Concept introduced: the template-method handler. The base class owns the entire workflow and calls back into the subclass for the parts only the module knows: get the repository, load the parent by id with the declared includes and change tracking, return NotFound tagged with the handler name and the parent type when it is gone, call Apply, short-circuit on failure, save once, log, run the post-commit hook, and return the mapped child (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/ChildEntityHandlerBase.cs:102-127). The subclass writes no try, no null check and no save. Two hooks are worth calling out because their defaults encode a policy. AsTracking defaults to true, since a no-tracking load would turn the add into a silent no-op (ChildEntityHandlerBase.cs:50). And Includes is abstract, not virtual (:55), which forces every subclass to make an explicit decision about loading the child collection rather than inheriting a quiet default. [Rubric §2, Design Patterns] assesses whether recurring workflows are factored behind a stable extension point; [Rubric §4, Domain-Driven Design]: the handler assigns nothing itself, it calls the aggregate method that owns the invariant and raises CategoryItemChanged (MMCA.ADC.Conference.Domain/Categories/Category.cs:142-144).
    • +
    • Concept introduced: the template-method handler. The base class owns the entire workflow and calls back into the subclass for the parts only the module knows: get the repository, load the parent by id with the declared includes and change tracking, return NotFound tagged with the handler name and the parent type when it is gone, call Apply, short-circuit on failure, save once, log, run the post-commit hook, and return the mapped child (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/ChildEntityHandlerBase.cs:102-127). The subclass writes no try, no null check and no save. Two hooks are worth calling out because their defaults encode a policy. AsTracking defaults to true, since a no-tracking load would turn the add into a silent no-op (ChildEntityHandlerBase.cs:50). And Includes is abstract, not virtual (:55), which forces every subclass to make an explicit decision about loading the child collection rather than inheriting a quiet default. [Rubric §2, Design Patterns] assesses whether recurring workflows are factored behind a stable extension point; [Rubric §4, Domain-Driven Design]: the handler assigns nothing itself, it calls the aggregate method that owns the invariant and raises CategoryItemChanged (MMCA.ADC.Conference.Domain/Categories/Category.cs:144-146).
    • Walkthrough: sealed partial class AddCategoryItemHandler(IUnitOfWork unitOfWork, CategoryItemDTOMapper dtoMapper, ILogger<AddCategoryItemHandler> logger) : AddChildEntityHandlerBase<AddCategoryItemCommand, Category, ConferenceCategoryIdentifierType, CategoryItem, CategoryItemDTO>(unitOfWork) (:15-19); only the unit of work is forwarded to the base, the mapper and logger stay local. The five overrides, in file order: Includes => [] with a comment stating that nothing is eager-loaded on this path (:21-23); ParentId returns command.CategoryId (:26); Apply is one line into the aggregate, parent.AddCategoryItem(command.CategoryItemId, command.Name, command.Sort) (:29-30); MapChild is one line into the mapper (:33); LogAdded forwards to the source-generated partial with the item name and the category id (:36-40).
    • -
    • Caveats / not-in-source: the empty Includes and the aggregate's uniqueness check are in tension, and it is worth knowing which guard actually holds. Category.AddCategoryItem opens with BR-138, a case-insensitive uniqueness check against the in-memory _categoryItems collection (MMCA.ADC.Conference.Domain/Categories/Category.cs:131-134), and the base class documents that loading the child collection is what makes such a check meaningful (ChildEntityHandlerBase.cs:15-19,106-107). With no include declared and no AutoInclude configured on the navigation, that collection is not eager-loaded, so a cold add sees an empty list and the BR-138 check passes. The guard that does hold in that case is the database: a unique index on (CategoryId, Name) (MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Categories/CategoryItemConfiguration.cs:30-31). The practical difference is the shape of the answer, a clean domain error versus a unique-index violation surfaced by the middleware. Whether any given request has the collection loaded through change-tracker fix-up from an earlier read in the same scope is not determinable from source.
    • -
    • Where it's used: resolved as ICommandHandler<AddCategoryItemCommand, Result<CategoryItemDTO>> by CategoryItemsController (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:65), behind the standard decorator pipeline, and registered by the module scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143).
    • +
    • Caveats / not-in-source: the empty Includes and the aggregate's uniqueness check are in tension, and it is worth knowing which guard actually holds. Category.AddCategoryItem opens with BR-138, a case-insensitive uniqueness check against the in-memory _categoryItems collection (MMCA.ADC.Conference.Domain/Categories/Category.cs:133-136), and the base class documents that loading the child collection is what makes such a check meaningful (ChildEntityHandlerBase.cs:15-19,106-107). With no include declared and no AutoInclude configured on the navigation, that collection is not eager-loaded, so a cold add sees an empty list and the BR-138 check passes. The guard that does hold in that case is the database: a unique index on (CategoryId, Name) (MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Categories/CategoryItemConfiguration.cs:30-31). The practical difference is the shape of the answer, a clean domain error versus a unique-index violation surfaced by the middleware. Whether any given request has the collection loaded through change-tracker fix-up from an earlier read in the same scope is not determinable from source.
    • +
    • Where it's used: resolved as ICommandHandler<AddCategoryItemCommand, Result<CategoryItemDTO>> by CategoryItemsController (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:65), behind the standard decorator pipeline, and registered by the module scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139).

    CreateConferenceCategoryHandler

    @@ -4025,10 +4062,10 @@

    CreateConferenceCategoryHandler

    • What it is: the handler that creates a conference Category. It is fourteen lines long, and none of them orchestrate anything: the create workflow lives in the framework base class, and this type supplies the four collaborators plus the one log message that is specific to categories.
    • Depends on: CreateEntityHandlerBase<TCreateRequest, TEntity, TIdentifierType, TEntityDTO> from MMCA.Common.Application.UseCases (CreateConferenceCategoryHandler.cs:7,20-21), IUnitOfWork (CreateConferenceCategoryHandler.cs:6,16), IEntityRequestMapper<TEntity, TCreateRequest, TIdentifierType> satisfied by ConferenceCategoryCreateRequestMapper (CreateConferenceCategoryHandler.cs:5,17), ConferenceCategoryDTOMapper (CreateConferenceCategoryHandler.cs:2,18), ILogger<T> with a source-generated [LoggerMessage] partial (CreateConferenceCategoryHandler.cs:1,19,26-27), the Category aggregate (CreateConferenceCategoryHandler.cs:3), and ConferenceCategoryDTO from the Shared project (CreateConferenceCategoryHandler.cs:4).
    • -
    • Concept introduced (for this unit), the create workflow as an inherited template method. The base is an abstract class that implements ICommandHandler<TCreateRequest, Result<TEntityDTO>> rather than a decorator or a helper, and the reason is registration: the concrete subclass stays the registered handler, so the module scan keeps discovering it and the decorator pipeline keeps wrapping it, while Scrutor never registers the abstract base (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:17-23,42-46). Read the four constructor arguments as a pipeline (CreateConferenceCategoryHandler.cs:15-19): the request mapper turns the wire contract into a validated entity, the unit of work supplies the typed repository and owns the transaction boundary, and the DTO mapper turns the persisted entity back into a wire contract. Notice the asymmetry in how the two mappers are declared: the request mapper arrives by interface (CreateConferenceCategoryHandler.cs:17) because the base is written against that abstraction, while the DTO mapper arrives by concrete type (CreateConferenceCategoryHandler.cs:18) and widens to the base's IEntityDTOMapper<...> parameter. Notice equally what is absent: no validator call (the validation decorator already ran ConferenceCategoryCreateRequestValidator), no cache eviction (the caching decorator reads CachePrefix off the request), and no try/catch (failures arrive as Result values). That absence is the point of the decorator pipeline taught in Group 05. [Rubric §5, Vertical Slice] assesses whether a use case is self-contained: the four Create types live in one folder and this handler is the slice's entry point. [Rubric §3, Clean Architecture]: the Application layer depends on abstractions and on the Domain, never on EF Core or ASP.NET. [Rubric §6, CQRS & Event-Driven]: one command type, one handler, one write path, and the CategoryChanged event raised inside the factory (MMCA.ADC.Conference.Domain/Categories/Category.cs:72) is captured by the base's single SaveChangesAsync. [Rubric §15, Best Practices & Code Quality]: the workflow exists once, so a fix to the create sequence reaches every module at the same time.
    • +
    • Concept introduced (for this unit), the create workflow as an inherited template method. The base is an abstract class that implements ICommandHandler<TCreateRequest, Result<TEntityDTO>> rather than a decorator or a helper, and the reason is registration: the concrete subclass stays the registered handler, so the module scan keeps discovering it and the decorator pipeline keeps wrapping it, while Scrutor never registers the abstract base (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:17-23,42-46). Read the four constructor arguments as a pipeline (CreateConferenceCategoryHandler.cs:15-19): the request mapper turns the wire contract into a validated entity, the unit of work supplies the typed repository and owns the transaction boundary, and the DTO mapper turns the persisted entity back into a wire contract. Notice the asymmetry in how the two mappers are declared: the request mapper arrives by interface (CreateConferenceCategoryHandler.cs:17) because the base is written against that abstraction, while the DTO mapper arrives by concrete type (CreateConferenceCategoryHandler.cs:18) and widens to the base's IEntityDTOMapper<...> parameter. Notice equally what is absent: no validator call (the validation decorator already ran ConferenceCategoryCreateRequestValidator), no cache eviction (the caching decorator reads CachePrefix off the request), and no try/catch (failures arrive as Result values). That absence is the point of the decorator pipeline taught in Group 05. [Rubric §5, Vertical Slice] assesses whether a use case is self-contained: the four Create types live in one folder and this handler is the slice's entry point. [Rubric §3, Clean Architecture]: the Application layer depends on abstractions and on the Domain, never on EF Core or ASP.NET. [Rubric §6, CQRS & Event-Driven]: one command type, one handler, one write path, and the CategoryChanged event raised inside the factory (MMCA.ADC.Conference.Domain/Categories/Category.cs:73) is captured by the base's single SaveChangesAsync. [Rubric §15, Best Practices & Code Quality]: the workflow exists once, so a fix to the create sequence reaches every module at the same time.
    • Walkthrough: primary-constructor injection of the four collaborators, forwarded to the base's own primary constructor (CreateConferenceCategoryHandler.cs:15-21). Only two members are declared here. LogCreated(Category entity) overrides the base's no-op logging hook and calls the source-generated partial LogConferenceCategoryCreated(logger, entity.Id, entity.Title) (CreateConferenceCategoryHandler.cs:24, message template at CreateConferenceCategoryHandler.cs:26-27); the hook is invoked after the save (CreateEntityHandlerBase.cs:99), so the logged id is the store-generated key rather than the 0 that arrived on the request. The inherited sequence is worth reading once: HandleAsync null-guards the command and delegates to CreateCoreAsync (CreateEntityHandlerBase.cs:56-63), which runs the pass-through PrepareAsync hook (CreateEntityHandlerBase.cs:84,114-118), awaits requestMapper.CreateEntityAsync and short-circuits a factory failure into the right generic shape with Result.Failure<TEntityDTO>(result.Errors) (CreateEntityHandlerBase.cs:90-92), resolves attemptUnitOfWork.GetRepository<TEntity, TIdentifierType>() (CreateEntityHandlerBase.cs:95), calls PersistAsync, whose default is AddAsync followed by exactly one SaveChangesAsync (CreateEntityHandlerBase.cs:129-140), then LogCreated, then the no-op OnCreatedAsync post-commit hook, and finally returns Result.Success(dtoMapper.MapToDTO(entity)) (CreateEntityHandlerBase.cs:97-102). This handler overrides none of PrepareAsync, PersistAsync or OnCreatedAsync: a category needs no app-assigned key, no bespoke persist step, and no post-commit publish.
    • Why it's built this way: the single SaveChangesAsync is the one place audit fields are stamped, domain events are captured, and outbox rows are written (ADR-003), so the workflow deliberately owns exactly one call to it and a subclass that wants a different persist step has to say so by overriding PersistAsync. Note also why CreateCoreAsync takes the unit of work as a parameter rather than closing over the injected one: a create path that computes its own primary key and retries on a unique-constraint collision must run each attempt against a fresh scope's unit of work, because the ambient DbContext still tracks the failed insert (CreateEntityHandlerBase.cs:29-36,69-73). Returning a ConferenceCategoryDTO rather than the entity keeps the domain type from crossing the API boundary, per ADR-001.
    • -
    • Where it's used: resolved by the ConferenceCategoriesController as ICommandHandler<ConferenceCategoryCreateRequest, Result<ConferenceCategoryDTO>> (MMCA.ADC.Conference.API/Controllers/Categories/ConferenceCategoriesController.cs:38) and reached through AggregateRootEntityControllerBase<TEntity, TEntityDTO, TIdentifierType, TCreateRequest> (ConferenceCategoriesController.cs:42), whose create action the ADC controller overrides only to evict the conference:categories output-cache tag after the call (ConferenceCategoriesController.cs:95-103). The category's edit path is not a sibling handler: it is the framework's generic UpdateEntityHandler<TEntity, TEntityDTO, TIdentifierType, TUpdateRequest> plus a ConferenceCategoryUpdateApplier, wired by the same AddEntityCrud<...>() call (MMCA.ADC.Conference.Application/DependencyInjection.cs:153). Covered by CreateConferenceCategoryHandlerTests (MMCA.ADC.Conference.Application.Tests/Categories/UseCases/CreateConferenceCategoryHandlerTests.cs:13).
    • +
    • Where it's used: resolved by the ConferenceCategoriesController as ICommandHandler<ConferenceCategoryCreateRequest, Result<ConferenceCategoryDTO>> (MMCA.ADC.Conference.API/Controllers/Categories/ConferenceCategoriesController.cs:38) and reached through AggregateRootEntityControllerBase<TEntity, TEntityDTO, TIdentifierType, TCreateRequest> (ConferenceCategoriesController.cs:42), whose create action the ADC controller overrides only to evict the conference:categories output-cache tag after the call (ConferenceCategoriesController.cs:95-103). The category's edit path is not a sibling handler: it is the framework's generic UpdateEntityHandler<TEntity, TEntityDTO, TIdentifierType, TUpdateRequest> plus a ConferenceCategoryUpdateApplier, wired by the same AddEntityCrud<...>() call (MMCA.ADC.Conference.Application/DependencyInjection.cs:149). Covered by CreateConferenceCategoryHandlerTests (MMCA.ADC.Conference.Application.Tests/Categories/UseCases/CreateConferenceCategoryHandlerTests.cs:13).

    RemoveCategoryItemHandler

    @@ -4037,8 +4074,8 @@

    RemoveCategoryItemHandler

    • What it is: the handler for RemoveCategoryItemCommand. It is UpdateCategoryItemHandler plus one extra override: it can find its aggregate two different ways, because the DELETE endpoint does not guarantee the owning category id.
    • Depends on: RemoveChildEntityHandlerBase<TCommand, TParent, TIdentifierType> from MMCA.Common.Application.UseCases (RemoveCategoryItemHandler.cs:4,16), IUnitOfWork (RemoveCategoryItemHandler.cs:3,14), IRepository<TEntity, TIdentifierType> as the LoadAsync parameter (RemoveCategoryItemHandler.cs:26), ILogger<T> with a source-generated [LoggerMessage] (RemoveCategoryItemHandler.cs:1,15,62-63), the Category aggregate and its CategoryItem child (RemoveCategoryItemHandler.cs:2), and Result (RemoveCategoryItemHandler.cs:5).
    • -
    • Concept introduced, resolving the aggregate from the child when the caller does not name it. The base it extends is a two-line specialization of the bare-Result mutate base whose only job is to make Includes abstract instead of virtual, because a remove that cannot see the child collection cannot find the child and reports a misleading NotFound (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/ChildEntityHandlerBase.cs:129-152). The interesting part is the LoadAsync override. The usual child-mutation handler loads by aggregate id and stops there; this one cannot assume it has an aggregate id, because the DELETE endpoint takes categoryId as a plain [FromQuery] argument (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:182) and the UI's generic delete sends only the item id, so CategoryId arrives as default (0). The handler branches on that (RemoveCategoryItemHandler.cs:35): when the id is unset it calls repository.FirstOrDefaultAsync with a predicate that finds the owner through its children, c => c.CategoryItems.Any(ci => ci.Id == command.CategoryItemId) (RemoveCategoryItemHandler.cs:37-41); otherwise it loads by id directly (RemoveCategoryItemHandler.cs:44-48). Both branches pass includes: Includes and asTracking: AsTracking, and both are load-bearing: the predicate overload of FirstOrDefaultAsync defaults asTracking to false (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:133-138), so an untracked load would make the change on a detached graph and lose it silently at SaveChangesAsync. [Rubric §4, Domain-Driven Design] assesses whether children are reached through their root: even the id-less path resolves the root first and then asks it to remove the child. [Rubric §12, Performance & Scalability]: the fallback path is a filter-over-children query rather than a keyed lookup, which is the cost of accepting a request that omits the owner; it is one row from the database rather than a materialized set, which is exactly what FirstOrDefaultAsync exists to guarantee (IRepository.cs:112-121). [Rubric §13, Observability & Operability]: the source-generated log message records both identifiers with structured fields and allocates nothing when the level is disabled.
    • -
    • Walkthrough: primary-constructor injection of IUnitOfWork and ILogger<RemoveCategoryItemHandler>, forwarded to the base (RemoveCategoryItemHandler.cs:13-16). Includes => [nameof(Category.CategoryItems)] satisfies the base's abstract member (RemoveCategoryItemHandler.cs:19), EntityId(command) => command.CategoryId supplies the key for the by-id branch (RemoveCategoryItemHandler.cs:22), and the LoadAsync override holds the two-branch lookup described above with an ArgumentNullException.ThrowIfNull(repository) guard first (RemoveCategoryItemHandler.cs:25-49). MutateAsync is again one line, Task.FromResult(entity.RemoveCategoryItem(command.CategoryItemId)) (RemoveCategoryItemHandler.cs:52-56), and LogMutated calls the generated LogCategoryItemRemoved partial (RemoveCategoryItemHandler.cs:59-60, template at :62-63). Everything else is the inherited sequence: not-found becomes Error.NotFound.WithSource(HandlerName).WithTarget(typeof(TEntity).Name) (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/MutateEntityHandlerBase.cs:282-283), which means the fallback branch reports a missing category even when what the caller actually got wrong was the item id; a domain failure is returned unchanged, so a missing-child failure from RemoveChildOrNotFound (MMCA.ADC.Conference.Domain/Categories/Category.cs:188-191) reaches the caller with its own error intact; and only a successful mutation reaches the single SaveChangesAsync (MutateEntityHandlerBase.cs:298-316). Inside the aggregate the removal is a soft delete on the child performed by the framework's RemoveChildOrNotFound helper, followed by a CategoryItemChanged event with DomainEntityState.Deleted (Category.cs:188-194).
    • +
    • Concept introduced, resolving the aggregate from the child when the caller does not name it. The base it extends is a two-line specialization of the bare-Result mutate base whose only job is to make Includes abstract instead of virtual, because a remove that cannot see the child collection cannot find the child and reports a misleading NotFound (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/ChildEntityHandlerBase.cs:129-152). The interesting part is the LoadAsync override. The usual child-mutation handler loads by aggregate id and stops there; this one cannot assume it has an aggregate id, because the DELETE endpoint takes categoryId as a plain [FromQuery] argument (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:185) and the UI's generic delete sends only the item id, so CategoryId arrives as default (0). The handler branches on that (RemoveCategoryItemHandler.cs:35): when the id is unset it calls repository.FirstOrDefaultAsync with a predicate that finds the owner through its children, c => c.CategoryItems.Any(ci => ci.Id == command.CategoryItemId) (RemoveCategoryItemHandler.cs:37-41); otherwise it loads by id directly (RemoveCategoryItemHandler.cs:44-48). Both branches pass includes: Includes and asTracking: AsTracking, and both are load-bearing: the predicate overload of FirstOrDefaultAsync defaults asTracking to false (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:134-139), so an untracked load would make the change on a detached graph and lose it silently at SaveChangesAsync. [Rubric §4, Domain-Driven Design] assesses whether children are reached through their root: even the id-less path resolves the root first and then asks it to remove the child. [Rubric §12, Performance & Scalability]: the fallback path is a filter-over-children query rather than a keyed lookup, which is the cost of accepting a request that omits the owner; it is one row from the database rather than a materialized set, which is exactly what FirstOrDefaultAsync exists to guarantee (IRepository.cs:112-121). [Rubric §13, Observability & Operability]: the source-generated log message records both identifiers with structured fields and allocates nothing when the level is disabled.
    • +
    • Walkthrough: primary-constructor injection of IUnitOfWork and ILogger<RemoveCategoryItemHandler>, forwarded to the base (RemoveCategoryItemHandler.cs:13-16). Includes => [nameof(Category.CategoryItems)] satisfies the base's abstract member (RemoveCategoryItemHandler.cs:19), EntityId(command) => command.CategoryId supplies the key for the by-id branch (RemoveCategoryItemHandler.cs:22), and the LoadAsync override holds the two-branch lookup described above with an ArgumentNullException.ThrowIfNull(repository) guard first (RemoveCategoryItemHandler.cs:25-49). MutateAsync is again one line, Task.FromResult(entity.RemoveCategoryItem(command.CategoryItemId)) (RemoveCategoryItemHandler.cs:52-56), and LogMutated calls the generated LogCategoryItemRemoved partial (RemoveCategoryItemHandler.cs:59-60, template at :62-63). Everything else is the inherited sequence: not-found becomes Error.NotFound.WithSource(HandlerName).WithTarget(typeof(TEntity).Name) (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/MutateEntityHandlerBase.cs:282-283), which means the fallback branch reports a missing category even when what the caller actually got wrong was the item id; a domain failure is returned unchanged, so a missing-child failure from RemoveChildOrNotFound (MMCA.ADC.Conference.Domain/Categories/Category.cs:190-193) reaches the caller with its own error intact; and only a successful mutation reaches the single SaveChangesAsync (MutateEntityHandlerBase.cs:298-316). Inside the aggregate the removal is a soft delete on the child performed by the framework's RemoveChildOrNotFound helper, followed by a CategoryItemChanged event with DomainEntityState.Deleted (Category.cs:188-194).
    • Why it's built this way: the save-only-on-success shape is the framework's canonical command body, so a rejected removal writes nothing at all and the single SaveChangesAsync stays the one boundary that stamps audit fields, captures domain events, and writes the outbox row (ADR-003). The owner-resolution fallback exists because the UI reuses one generic delete action for every child grid, and the alternative would have been a bespoke client call per child type. Making it a LoadAsync override rather than a bespoke handler body is what keeps the rest of the sequence identical to every other mutation, which is the case the base's remarks explicitly call out (ChildEntityHandlerBase.cs:135-139).
    • Where it's used: resolved by the CategoryItemsController as ICommandHandler<RemoveCategoryItemCommand, Result> (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:67) and invoked at CategoryItemsController.cs:185-187, after which the controller evicts the conference:categories and conference output-cache tags and returns 204 No Content (CategoryItemsController.cs:194-195). Covered by RemoveCategoryItemHandlerTests (MMCA.ADC.Conference.Application.Tests/Categories/UseCases/RemoveCategoryItemHandlerTests.cs:11).
    @@ -4061,10 +4098,10 @@

    UpdateCategoryItemCommand

    • What it is: the write intent for editing one CategoryItem inside its owning Category: the two identifiers that locate it plus the two fields that may change.
    • Depends on: ICacheInvalidating (UpdateCategoryItemCommand.cs:2,18), the Category type for the cache prefix (UpdateCategoryItemCommand.cs:1,21), and the ConferenceCategoryIdentifierType / CategoryItemIdentifierType aliases.
    • -
    • Concept: the update-child shape, which sits between the add and remove shapes. It carries the aggregate id and the child id like a remove, plus exactly the fields that are editable and no others: Name is a non-nullable string and Sort a non-nullable int (UpdateCategoryItemCommand.cs:17-18), so this command cannot be used to blank a name by omission. Because both editable fields are here rather than spread across a partial-update document, the record is the complete answer to "what can this endpoint change", and it is also the exact target the validator binds to. [Rubric §9, API & Contract Design] assesses whether an edit contract states precisely what is mutable: the four positional parameters are that statement. [Rubric §6, CQRS & Event-Driven]: one command type, one handler, one write path, with the resulting CategoryItemChanged domain event raised inside the aggregate (MMCA.ADC.Conference.Domain/Categories/Category.cs:176).
    • -
    • Walkthrough: four positional parameters, CategoryId, CategoryItemId, Name, Sort (UpdateCategoryItemCommand.cs:14-18), with CachePrefix => $"{typeof(Category).FullName}:" (UpdateCategoryItemCommand.cs:21). Unlike RemoveCategoryItemCommand this command is always fully populated: the controller reads the item id from the route and the owning category id from a required member of the request body, UpdateCategoryItemRequest (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:42-51,161-166), so the handler never has to hunt for the owner. That single difference in what the wire guarantees is why the update handler keeps the framework's default by-id load and the remove handler does not.
    • +
    • Concept: the update-child shape, which sits between the add and remove shapes. It carries the aggregate id and the child id like a remove, plus exactly the fields that are editable and no others: Name is a non-nullable string and Sort a non-nullable int (UpdateCategoryItemCommand.cs:17-18), so this command cannot be used to blank a name by omission. Because both editable fields are here rather than spread across a partial-update document, the record is the complete answer to "what can this endpoint change", and it is also the exact target the validator binds to. [Rubric §9, API & Contract Design] assesses whether an edit contract states precisely what is mutable: the four positional parameters are that statement. [Rubric §6, CQRS & Event-Driven]: one command type, one handler, one write path, with the resulting CategoryItemChanged domain event raised inside the aggregate (MMCA.ADC.Conference.Domain/Categories/Category.cs:178).
    • +
    • Walkthrough: four positional parameters, CategoryId, CategoryItemId, Name, Sort (UpdateCategoryItemCommand.cs:14-18), with CachePrefix => $"{typeof(Category).FullName}:" (UpdateCategoryItemCommand.cs:21). Unlike RemoveCategoryItemCommand this command is always fully populated: the controller reads the item id from the route and the owning category id from a required member of the request body, UpdateCategoryItemRequest (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:42-51,164-169), so the handler never has to hunt for the owner. That single difference in what the wire guarantees is why the update handler keeps the framework's default by-id load and the remove handler does not.
    • Why it's built this way: a narrow, explicitly-typed update command keeps the write surface auditable and gives the caching decorator a well-defined eviction boundary; a general "patch the item entity" contract would have neither property, and it could not be validated by a single AbstractValidator<T>.
    • -
    • Where it's used: constructed by the CategoryItemsController (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:162, handler injected at CategoryItemsController.cs:66), validated by UpdateCategoryItemCommandValidator, and handled by UpdateCategoryItemHandler.
    • +
    • Where it's used: constructed by the CategoryItemsController (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:165, handler injected at CategoryItemsController.cs:66), validated by UpdateCategoryItemCommandValidator, and handled by UpdateCategoryItemHandler.

    EventQuestionAnswerDTOMapper

    @@ -4075,7 +4112,7 @@

    EventQuestionAnswerDTOMapper

  • Depends on: IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType> from MMCA.Common.Application.Interfaces (MMCA.ADC.Conference.Application/Events/DTOs/EventQuestionAnswerDTOMapper.cs:3,13), Riok.Mapperly.Abstractions (NuGet, EventQuestionAnswerDTOMapper.cs:4,11), the EventQuestionAnswer entity (EventQuestionAnswerDTOMapper.cs:1), the EventQuestionAnswerDTO contract from the Shared project (EventQuestionAnswerDTOMapper.cs:2), and the EventQuestionAnswerIdentifierType alias (= int, MMCA.ADC.Conference.Shared/MMCA.ADC.Conference.GlobalUsings.IdentifierType.cs:11).
  • Concept introduced (for this unit), source-generated DTO mapping. [Mapper] on a partial class (EventQuestionAnswerDTOMapper.cs:11-12) tells the Mapperly generator to fill in the body of every partial method it finds, here MapToDTO (EventQuestionAnswerDTOMapper.cs:16). The generated body is straight-line property assignment: no reflection, no expression trees, no runtime configuration, and a compile error rather than a silent null if a target member has no source. That is the whole point of ADR-001: mapping is either hand-written or generated, never reflective. [Rubric §12, Performance & Scalability] assesses whether hot paths avoid avoidable runtime work: every read endpoint maps its result set, so a generated assignment beats a reflective copy at the exact place volume lands. [Rubric §9, API & Contract Design] assesses what crosses the wire: the DTO, not the entity, so a domain refactor cannot silently reshape the JSON. [Rubric §14, Testability] assesses whether logic can be exercised in isolation: the mapper is a pure function of its input and is tested directly (EventQuestionAnswerDTOMapperTests).
  • Walkthrough: two members. public partial EventQuestionAnswerDTO MapToDTO(EventQuestionAnswer entity) (EventQuestionAnswerDTOMapper.cs:16) is the declaration whose implementation the generator supplies. MapToDTOs(IReadOnlyCollection<EventQuestionAnswer>) (EventQuestionAnswerDTOMapper.cs:19-23) is hand-written and deliberately so: it null-guards with ArgumentNullException.ThrowIfNull (EventQuestionAnswerDTOMapper.cs:21) and then projects with a collection expression over a spread, [.. entityCollection.Select(MapToDTO)] (EventQuestionAnswerDTOMapper.cs:22), which materializes a single array without an intermediate List<T> growth cycle. The collection method delegating to the generated single-item method is the shape every mapper in this family repeats.
  • -
  • Where it's used: injected concretely into AddEventQuestionAnswerHandler (MMCA.ADC.Conference.Application/Events/UseCases/AddEventQuestionAnswer/AddEventQuestionAnswerHandler.cs:22, used at :93 and :116), consumed as a child mapper by EventDTOMapper through [UseMapper] (MMCA.ADC.Conference.Application/Events/DTOs/EventDTOMapper.cs:27-28), and resolved as IEntityDTOMapper<EventQuestionAnswer, EventQuestionAnswerDTO, EventQuestionAnswerIdentifierType> by the generic EntityQueryService<TEntity, TEntityDTO, TIdentifierType> registered for the entity (MMCA.ADC.Conference.Application/DependencyInjection.cs:117, constructor parameter at MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:36). Registration of the mapper itself is by the module's convention scan, services.ScanModuleApplicationServices<ClassReference>() (MMCA.ADC.Conference.Application/DependencyInjection.cs:143).
  • +
  • Where it's used: injected concretely into AddEventQuestionAnswerHandler (MMCA.ADC.Conference.Application/Events/UseCases/AddEventQuestionAnswer/AddEventQuestionAnswerHandler.cs:22, used at :93 and :116), consumed as a child mapper by EventDTOMapper through [UseMapper] (MMCA.ADC.Conference.Application/Events/DTOs/EventDTOMapper.cs:27-28), and resolved as IEntityDTOMapper<EventQuestionAnswer, EventQuestionAnswerDTO, EventQuestionAnswerIdentifierType> by the generic EntityQueryService<TEntity, TEntityDTO, TIdentifierType> registered for the entity (MMCA.ADC.Conference.Application/DependencyInjection.cs:117, constructor parameter at MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:36). Registration of the mapper itself is by the module's convention scan, services.ScanModuleApplicationServices<ClassReference>() (MMCA.ADC.Conference.Application/DependencyInjection.cs:139).
  • EventQuestionAnswerRules

    @@ -4107,10 +4144,10 @@

    PublishedEventSpecification

    • What it is: a one-line query filter object that restricts an event query to published events only. It is how BR-108 ("non-privileged readers see only published events") is expressed as data rather than as an if inside every read endpoint (MMCA.ADC.Conference.Application/Events/Specifications/PublishedEventSpecification.cs:7-9).
    • Depends on: Specification<TEntity, TIdentifierType> from MMCA.Common.Domain.Specifications (PublishedEventSpecification.cs:3,11), the Event aggregate and its EventIdentifierType alias (PublishedEventSpecification.cs:2,11), and System.Linq.Expressions (PublishedEventSpecification.cs:1,14).
    • -
    • Concept introduced, authorization expressed as a specification. The specification pattern itself is taught in Group 03; what this type introduces is using it as a security filter. The base class exposes a Criteria expression that the repository composes into the EF query, so the restriction is applied in SQL rather than after materialization: an unpublished event is never loaded, never counted in a page total, and never reaches the serializer. Because the filter is a first-class object, the decision "does this caller get the filter" becomes a nullable value at one call site instead of branching query code in five actions. [Rubric §11, Security] assesses whether authorization is enforced at the data boundary rather than in the view: a non-privileged reader's query cannot return an unpublished row at all, and a filtered-out row answers 404 rather than 403, so the response does not confirm that the id exists (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:522-524). [Rubric §12, Performance & Scalability] assesses over-fetching: pushing the predicate into the expression tree keeps paging counts correct and avoids materializing rows the caller may not see.
    • -
    • Walkthrough: the whole type is a single expression-bodied override, public override Expression<Func<Event, bool>> Criteria => e => e.IsPublished (PublishedEventSpecification.cs:14). There is no constructor and no state, so an instance is cheap to allocate per request, which matters because the framework builds it once per request and reuses it across every page of an export loop (EntityControllerBase.cs:526-529).
    • +
    • Concept introduced, authorization expressed as a specification. The specification pattern itself is taught in Group 03; what this type introduces is using it as a security filter. The base class exposes a Criteria expression that the repository composes into the EF query, so the restriction is applied in SQL rather than after materialization: an unpublished event is never loaded, never counted in a page total, and never reaches the serializer. Because the filter is a first-class object, the decision "does this caller get the filter" becomes a nullable value at one call site instead of branching query code in five actions. [Rubric §11, Security] assesses whether authorization is enforced at the data boundary rather than in the view: a non-privileged reader's query cannot return an unpublished row at all, and a filtered-out row answers 404 rather than 403, so the response does not confirm that the id exists (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:562-564). [Rubric §12, Performance & Scalability] assesses over-fetching: pushing the predicate into the expression tree keeps paging counts correct and avoids materializing rows the caller may not see.
    • +
    • Walkthrough: the whole type is a single expression-bodied override, public override Expression<Func<Event, bool>> Criteria => e => e.IsPublished (PublishedEventSpecification.cs:14). There is no constructor and no state, so an instance is cheap to allocate per request, which matters because the framework builds it once per request and reuses it across every page of an export loop (EntityControllerBase.cs:566-569).
    • Why it's built this way: keeping BR-108 in one named type means the event read endpoints share one definition of "visible event", and the class name makes the business rule greppable. Inheriting from the framework Specification<TEntity, TIdentifierType> base lets the same object flow through the generic query service and repository without an ADC-specific overload.
    • -
    • Where it's used: EventsController supplies it from exactly one override, protected override Specification<Event, EventIdentifierType>? GetExportSpecification(), which returns null (no filter) when currentUserService.IsPrivilegedConferenceReader() and a new instance otherwise (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:69-70). That is the framework's synchronous scoping hook: GetReadSpecificationAsync returns ValueTask.FromResult(GetExportSpecification()) by default (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:531-533), and every read action (both GetAllAsync overloads, GetAllForLookupAsync, GetByIdAsync and ExportAsync) applies it (EntityControllerBase.cs:496-501). The ADC read actions themselves are attribute-only passthroughs to their base implementations (EventsController.cs:77-124), so the filter is stated once and cannot drift between list and export.
    • +
    • Where it's used: EventsController supplies it from exactly one override, protected override Specification<Event, EventIdentifierType>? GetExportSpecification(), which returns null (no filter) when currentUserService.IsPrivilegedConferenceReader() and a new instance otherwise (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:69-70). That is the framework's synchronous scoping hook: GetReadSpecificationAsync returns ValueTask.FromResult(GetExportSpecification()) by default (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:571-573), and every read action (both GetAllAsync overloads, GetAllForLookupAsync, GetByIdAsync and ExportAsync) applies it (EntityControllerBase.cs:496-501). The ADC read actions themselves are attribute-only passthroughs to their base implementations (EventsController.cs:77-124), so the filter is stated once and cannot drift between list and export.
    • Caveats / not-in-source: the gate is the read audience, not the Organizer role alone: IsPrivilegedConferenceReader() is the shared predicate, and the same call also guards the export action outright with a Forbid() before the base implementation runs (EventsController.cs:141-144), so today a non-privileged caller is denied the CSV rather than served a filtered one. The criteria here do not exclude soft-deleted rows; that is handled separately by the EF global query filter (see Group 07).

    RoomDTOMapper

    @@ -4132,10 +4169,10 @@

    UpdateCategoryItemCommandValidator
  • What it is: the validator the pipeline runs against an UpdateCategoryItemCommand. Like its add-side twin it holds no rule bodies: it is two Include calls.
  • Depends on: FluentValidation.AbstractValidator<T> (NuGet, UpdateCategoryItemCommandValidator.cs:1,7) and the two shared rule objects CategoryItemNameRules<T> and CategoryItemSortRules<T> from MMCA.ADC.Conference.Application.Categories.Validation (UpdateCategoryItemCommandValidator.cs:2,11-12).
  • -
  • Concept: validating a command rather than a request. ConferenceCategoryCreateRequestValidator targets a wire contract; this one targets the record the controller assembles from a route value plus a body (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:161-166). The pipeline treats them identically because both are just T to FluentValidation, which is what makes it possible to validate at the same boundary whether or not a slice has a distinct request type. The two included rule objects also show the generic-rule pattern working over different selector types and across the repo boundary: CategoryItemNameRules<T> is an ADC rule taking an Expression<Func<T, string>> (MMCA.ADC.Conference.Application/Categories/Validation/ConferenceCategoryValidationRules.cs:28,31), while CategoryItemSortRules<T> is a two-line subclass of the framework's NonNegativeIntRules<T> that supplies only the field phrase and the error code (ConferenceCategoryValidationRules.cs:41-45). [Rubric §24, Forms, Validation & UX Safety] assesses boundary rejection with actionable messages: both rules carry stable codes, CategoryItem.Name.Required / CategoryItem.Name.MaxLength (ConferenceCategoryValidationRules.cs:33-34) and CategoryItem.Sort.Negative (ConferenceCategoryValidationRules.cs:45). [Rubric §1, SOLID]: name rules and sort rules are separate objects with separate reasons to change, and the validator composes them instead of inheriting a fat base.
  • -
  • Walkthrough: a block-bodied constructor with two statements (UpdateCategoryItemCommandValidator.cs:9-13): Include(new CategoryItemNameRules<UpdateCategoryItemCommand>(p => p.Name)) (UpdateCategoryItemCommandValidator.cs:11) and Include(new CategoryItemSortRules<UpdateCategoryItemCommand>(p => p.Sort)) (UpdateCategoryItemCommandValidator.cs:12). The name rule enforces NotEmpty plus MaximumLength(CategoryInvariants.CategoryItemNameMaxLength), which is 500 (MMCA.ADC.Conference.Shared/Categories/CategoryItemDTO.cs:16, forwarded at MMCA.ADC.Conference.Domain/Categories/CategoryInvariants.cs:24); the sort rule resolves to the framework's GreaterThanOrEqualTo(0) with the message "Sort order must be greater than or equal to 0" (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:124-126). What this validator does not check is uniqueness of the name within the category: that rule (BR-138) needs the sibling collection and therefore lives in the aggregate, where the update path passes the item being edited as the exclusion (MMCA.ADC.Conference.Domain/Categories/Category.cs:167-168).
  • +
  • Concept: validating a command rather than a request. ConferenceCategoryCreateRequestValidator targets a wire contract; this one targets the record the controller assembles from a route value plus a body (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:164-169). The pipeline treats them identically because both are just T to FluentValidation, which is what makes it possible to validate at the same boundary whether or not a slice has a distinct request type. The two included rule objects also show the generic-rule pattern working over different selector types and across the repo boundary: CategoryItemNameRules<T> is an ADC rule taking an Expression<Func<T, string>> (MMCA.ADC.Conference.Application/Categories/Validation/ConferenceCategoryValidationRules.cs:28,31), while CategoryItemSortRules<T> is a two-line subclass of the framework's NonNegativeIntRules<T> that supplies only the field phrase and the error code (ConferenceCategoryValidationRules.cs:41-45). [Rubric §24, Forms, Validation & UX Safety] assesses boundary rejection with actionable messages: both rules carry stable codes, CategoryItem.Name.Required / CategoryItem.Name.MaxLength (ConferenceCategoryValidationRules.cs:33-34) and CategoryItem.Sort.Negative (ConferenceCategoryValidationRules.cs:45). [Rubric §1, SOLID]: name rules and sort rules are separate objects with separate reasons to change, and the validator composes them instead of inheriting a fat base.
  • +
  • Walkthrough: a block-bodied constructor with two statements (UpdateCategoryItemCommandValidator.cs:9-13): Include(new CategoryItemNameRules<UpdateCategoryItemCommand>(p => p.Name)) (UpdateCategoryItemCommandValidator.cs:11) and Include(new CategoryItemSortRules<UpdateCategoryItemCommand>(p => p.Sort)) (UpdateCategoryItemCommandValidator.cs:12). The name rule enforces NotEmpty plus MaximumLength(CategoryInvariants.CategoryItemNameMaxLength), which is 500 (MMCA.ADC.Conference.Shared/Categories/CategoryItemDTO.cs:16, forwarded at MMCA.ADC.Conference.Domain/Categories/CategoryInvariants.cs:24); the sort rule resolves to the framework's GreaterThanOrEqualTo(0) with the message "Sort order must be greater than or equal to 0" (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:125-127). What this validator does not check is uniqueness of the name within the category: that rule (BR-138) needs the sibling collection and therefore lives in the aggregate, where the update path passes the item being edited as the exclusion (MMCA.ADC.Conference.Domain/Categories/Category.cs:169-170).
  • Why it's built this way: field-shape rules that need only the incoming values run at the boundary where they can be reported as a complete list; rules that need loaded state run in the domain. Splitting them that way is why the validator can stay a pure, dependency-free composition with no repository dependency.
  • -
  • Where it's used: resolved by the validation decorator around UpdateCategoryItemHandler, registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143); covered by UpdateCategoryItemCommandValidatorTests, which shares a file with its add-side twin (MMCA.ADC.Conference.Application.Tests/Categories/Validation/CategoryCommandValidatorTests.cs:54). Its add-side sibling is AddCategoryItemCommandValidator, which is the same two Include calls over a different command (MMCA.ADC.Conference.Application/Categories/UseCases/AddCategoryItem/AddCategoryItemCommandValidator.cs:11-12).
  • +
  • Where it's used: resolved by the validation decorator around UpdateCategoryItemHandler, registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); covered by UpdateCategoryItemCommandValidatorTests, which shares a file with its add-side twin (MMCA.ADC.Conference.Application.Tests/Categories/Validation/CategoryCommandValidatorTests.cs:54). Its add-side sibling is AddCategoryItemCommandValidator, which is the same two Include calls over a different command (MMCA.ADC.Conference.Application/Categories/UseCases/AddCategoryItem/AddCategoryItemCommandValidator.cs:11-12).
  • EventDTOMapper

    @@ -4147,7 +4184,7 @@

    EventDTOMapper

  • Concept introduced, composing generated mappers and escaping to hand-written code. Three Mapperly features carry this class. [UseMapper] on a field (EventDTOMapper.cs:21,24,27) tells the generator "when you need to map a Room, an EventSpeaker, or an EventQuestionAnswer, call this instance instead of generating a second copy", which is how an aggregate DTO gets its child collections filled without duplicating child mapping logic. [MapperIgnoreTarget] (EventDTOMapper.cs:51) is the escape hatch: it tells the generator to leave one target member alone so the class can set it itself. The member in question is LastSessionizeRefreshBy, a UserIdentifierType? on the entity (MMCA.ADC.Conference.Domain/Events/Event.cs:86) but a string? on the DTO (MMCA.ADC.Conference.Shared/Events/EventDTO.cs:100); a nullable-value-type-to-string conversion is not something the generator will invent, and the file's own comment says so (EventDTOMapper.cs:36). The third feature is the private user-defined mapping method: private static string? NullableEmailToString(Email? email) => email?.Value (EventDTOMapper.cs:54-55) is not called by any code in this file, it is picked up by the generator as the conversion to use whenever it needs an Email? on the entity as a string? on the DTO. That is the case for OrganizerContactEmail, an Email? value object on the aggregate (MMCA.ADC.Conference.Domain/Events/Event.cs:59) and a plain string? on the DTO (MMCA.ADC.Conference.Shared/Events/EventDTO.cs:82), per ADR-068. Note the difference from the LastSessionizeRefreshBy case: a one-line unwrap the generator can call for itself stays declarative, while a conversion needing a CultureInfo decision is lifted into the hand-written wrapper. The general lesson is that a source generator is not all-or-nothing: keep generation for the ninety percent of straight property copies and hand-write only the member that needs a decision. [Rubric §12, Performance & Scalability] assesses avoidable runtime work on hot paths: the whole event read path, including children, is generated assignment plus one with expression. [Rubric §9, API & Contract Design] assesses wire shape: the DTO's own types are chosen for the wire (an id rendered as a string), and this class is where the two type systems meet. [Rubric §15, Best Practices & Code Quality] assesses correctness details: the conversion uses CultureInfo.InvariantCulture (EventDTOMapper.cs:39-40) rather than the ambient culture, so the value is stable regardless of server locale.
  • Walkthrough: the primary constructor takes the three child mappers (EventDTOMapper.cs:15-18) and assigns each to a [UseMapper]-annotated readonly field (EventDTOMapper.cs:21-28). The public MapToDTO(Event entity) (EventDTOMapper.cs:31-42) is hand-written: it null-guards (EventDTOMapper.cs:33), calls the private generated MapToDTOGenerated(entity) (EventDTOMapper.cs:34), and then returns a with expression that sets the one ignored member, LastSessionizeRefreshBy = entity.LastSessionizeRefreshBy?.ToString(System.Globalization.CultureInfo.InvariantCulture) (EventDTOMapper.cs:37-41). Because EventDTO is a record, the with copy is a cheap shallow clone that leaves every generated assignment intact. MapToDTOs (EventDTOMapper.cs:45-49) is the same null-guarded spread projection as its siblings. The generated method is declared next, private partial EventDTO MapToDTOGenerated(Event entity) carrying [MapperIgnoreTarget(nameof(EventDTO.LastSessionizeRefreshBy))] (EventDTOMapper.cs:51-52), and the file ends with the one-line NullableEmailToString helper the generator consumes (EventDTOMapper.cs:54-55).
  • Why it's built this way: making the public method the wrapper and the generated method private means no caller can accidentally bypass the fix-up and receive a DTO with a null LastSessionizeRefreshBy. Keeping the child mappers injected rather than generated inline means the same RoomDTO shape is produced whether a room is read directly or as part of an event, per ADR-001.
  • -
  • Where it's used: injected into CreateEventHandler (MMCA.ADC.Conference.Application/Events/UseCases/Create/CreateEventHandler.cs:18) and UpdateEventHandler (MMCA.ADC.Conference.Application/Events/UseCases/Update/UpdateEventHandler.cs:20), and resolved as IEntityDTOMapper<Event, EventDTO, EventIdentifierType> by the event query service (MMCA.ADC.Conference.Application/DependencyInjection.cs:76, constructor parameter at MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:36). Registration is by the module's convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143). Tested by EventDTOMapperTests.
  • +
  • Where it's used: injected into CreateEventHandler (MMCA.ADC.Conference.Application/Events/UseCases/Create/CreateEventHandler.cs:18) and UpdateEventHandler (MMCA.ADC.Conference.Application/Events/UseCases/Update/UpdateEventHandler.cs:20), and resolved as IEntityDTOMapper<Event, EventDTO, EventIdentifierType> by the event query service (MMCA.ADC.Conference.Application/DependencyInjection.cs:76, constructor parameter at MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:36). Registration is by the module's convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139). Tested by EventDTOMapperTests.
  • RoomEntityQueryService

    @@ -4168,22 +4205,22 @@

    EventLiveValidationService

    • What it is: Conference's answer to four questions the Engagement module's conference-day features have to ask before they will record anything: is this event published and when is it live, is this session eligible for the live layer and who speaks at it, does this sponsor exist and which event owns it, and which session is this room hosting right now. It is the implementation behind the cross-module contract IEventLiveValidationService (MMCA.ADC.Conference.Application/Events/EventLiveValidationService.cs:24). Note the accessibility: the class is internal, so nothing outside this assembly can name it, and every consumer sees only the interface.
    • -
    • Depends on: IUnitOfWork and the BCL TimeProvider (:22), the Event, Session, and Sponsor entities, SessionInvariants for the two eligibility rules (:67, :71), CurrentEventSelector for the live-window math (:227), CalendarExportMapper for wall-clock to UTC conversion (:194-195), and the four result records EventLiveInfo, SessionLiveInfo, SponsorLiveInfo, and RoomSessionInfo.
    • -
    • Concept introduced, the cross-module read contract: Engagement needs facts about conference data but must not reference Conference's entities, or the two modules could never be deployed apart. The pattern that solves it has three parts. The interface and its four record types live in MMCA.ADC.Conference.Shared (MMCA.ADC.Conference.Shared/Events/Live/IEventLiveValidationService.cs:13), a project both sides may reference. The implementation lives here, in Conference.Application, where the entities are. And the binding is swappable: in the modular monolith this class is registered (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); in a split topology the Contracts package replaces it with a gRPC adapter that implements the same interface over the wire (EventLiveValidationServiceGrpcAdapter, MMCA.ADC/Source/Services/MMCA.ADC.Conference.Contracts/EventLiveValidationServiceGrpcAdapter.cs:27, swapped in with services.Replace(...) at MMCA.ADC/Source/Services/MMCA.ADC.Conference.Contracts/DependencyInjection.cs:79); and in a host that does not load Conference at all, the module registers DisabledEventLiveValidationService instead (MMCA.ADC.Conference.API/ConferenceModule.cs:24). Engagement's handlers see one interface in all three worlds. [Rubric §7, Microservices Readiness] assesses whether a module can be extracted without a rewrite: this is the extraction contract itself (ADR-007, ADR-008). [Rubric §9, API and Contract Design]: the DTO-like records carry only scalars and id lists, which is what keeps them serializable over gRPC unchanged.
    • +
    • Depends on: IUnitOfWork and the BCL TimeProvider (:24), the Event, Session, and Sponsor entities, SessionInvariants for the two eligibility rules (:69, :73, and again in the room filter at :165), CurrentEventSelector for the live-window math (:234), CalendarExportMapper for wall-clock to UTC conversion (:201-202), and the four result records EventLiveInfo, SessionLiveInfo, SponsorLiveInfo, and RoomSessionInfo.
    • +
    • Concept introduced, the cross-module read contract: Engagement needs facts about conference data but must not reference Conference's entities, or the two modules could never be deployed apart. The pattern that solves it has three parts. The interface and its four record types live in MMCA.ADC.Conference.Shared (MMCA.ADC.Conference.Shared/Events/Live/IEventLiveValidationService.cs:13), a project both sides may reference. The implementation lives here, in Conference.Application, where the entities are. And the binding is swappable: in the modular monolith this class is registered (MMCA.ADC.Conference.Application/DependencyInjection.cs:135); in a split topology the Contracts package replaces it with a gRPC adapter that implements the same interface over the wire (EventLiveValidationServiceGrpcAdapter, MMCA.ADC/Source/Services/MMCA.ADC.Conference.Contracts/EventLiveValidationServiceGrpcAdapter.cs:27, swapped in with services.Replace(...) at MMCA.ADC/Source/Services/MMCA.ADC.Conference.Contracts/DependencyInjection.cs:79); and in a host that does not load Conference at all, the module registers DisabledEventLiveValidationService instead (MMCA.ADC.Conference.API/ConferenceModule.cs:24). Engagement's handlers see one interface in all three worlds. [Rubric §7, Microservices Readiness] assesses whether a module can be extracted without a rewrite: this is the extraction contract itself (ADR-007, ADR-008). [Rubric §9, API and Contract Design]: the DTO-like records carry only scalars and id lists, which is what keeps them serializable over gRPC unchanged.
    • Walkthrough: four public methods and one private helper.
        -
      • GetEventLiveInfoAsync (:25-45) loads the event by id with no includes and asTracking: false (:30-34), returns Error.NotFound tagged with source and target when it is missing (:36-40), computes the window, and returns the published flag plus both boundaries (:42-44).
      • -
      • GetSessionLiveInfoAsync (:48-101) loads the session with its SessionSpeakers (:55), then applies the two bookmark-eligibility rules before anything else: EnsureNotServiceSession (BR-91, :67-69) and EnsureStatusIsEligible (BR-49, :71-73), both borrowed from the domain's own invariant helper so the live layer cannot drift from the bookmark rules. Only then does it fetch the owning event (:75-86), project the non-deleted speaker ids (:90-91), and return them with the plenum flag and the event's question-moderation default (:93-100).
      • -
      • GetSponsorLiveInfoAsync (:104-140) answers the printed-QR booth-visit lookup. The comment at :108-109 is the design note worth keeping: because the read repository applies the soft-delete filter, a pulled sponsor answers exactly like one that never existed, so a printed QR for a dropped sponsor simply stops working.
      • -
      • GetCurrentRoomSessionInfoAsync (:143-221) is the one with real logic. It loads every session in the room (:148-153), drops the ones with no schedule (:157-159), resolves the owning event from the first survivor (:167-172), resolves the event's IANA zone (:182), converts each session's wall-clock start and end into UTC through CalendarExportMapper.ToUtc (:189-197), and then picks: an in-progress session (StartsAtUtc <= now < EndsAtUtc) wins, and only if there is none does it accept the earliest session starting inside the grace window (:201-208). The comment at :199-200 explains why the order matters: back-to-back sessions overlap inside the grace window, and the attendee scanning the room QR is standing in the one that is actually running. The grace value is clamped at zero before use (:184), and the window scan is restricted to sessions of the resolved event (:190).
      • -
      • ComputeLiveWindowUtc (:226-230) delegates to CurrentEventSelector.GetLiveWindowUtc (MMCA.ADC.Conference.Shared/Events/CurrentEventSelector.cs:66-76) rather than repeating the rule. That shared helper defines the window as start date at 00:00 local through end date plus one day at 00:00 local (exclusive), and its ToUtc handles the spring-forward gap where local midnight never existed by shifting into the hour that did exist (CurrentEventSelector.cs:87-95). The comment at :223-225 states the reason plainly: the home surfaces, the now-next snapshot, and this service must agree on when an event is live, or two surfaces disagree in front of an audience.
      • +
      • GetEventLiveInfoAsync (:27-47) loads the event by id with no includes and asTracking: false (:31-36), returns Error.NotFound tagged with source and target when it is missing (:38-42), computes the window, and returns the published flag plus both boundaries (:44-46).
      • +
      • GetSessionLiveInfoAsync (:50-103) loads the session with its SessionSpeakers (:57), then applies the two bookmark-eligibility rules before anything else: EnsureNotServiceSession (BR-91, :69-71) and EnsureStatusIsEligible (BR-49, :73-75), both borrowed from the domain's own invariant helper so the live layer cannot drift from the bookmark rules. Only then does it fetch the owning event (:77-88), project the non-deleted speaker ids (:92-93), and return them with the plenum flag and the event's question-moderation default (:95-102).
      • +
      • GetSponsorLiveInfoAsync (:106-142) answers the printed-QR booth-visit lookup. The comment at :110-111 is the design note worth keeping: because the read repository applies the soft-delete filter, a pulled sponsor answers exactly like one that never existed, so a printed QR for a dropped sponsor simply stops working.
      • +
      • GetCurrentRoomSessionInfoAsync (:145-228) is the one with real logic. It loads every session in the room (:150-155), then drops in one filter every session that cannot be the match (:161-166): those with no schedule, service sessions (BR-91), and declined or cancelled ones (BR-49, through the same SessionInvariants.EnsureStatusIsEligible the session lookup uses). The comment at :157-160 states the reason: the session lookup's two eligibility rules apply here too, so a room check-in can never be recorded against a session the live layer would refuse. It then resolves the owning event from the first survivor (:174-179), resolves the event's IANA zone (:189), converts each session's wall-clock start and end into UTC through CalendarExportMapper.ToUtc (:196-204), and then picks: an in-progress session (StartsAtUtc <= now < EndsAtUtc) wins, and only if there is none does it accept the earliest session starting inside the grace window (:208-215). The comment at :206-207 explains why the order matters: back-to-back sessions overlap inside the grace window, and the attendee scanning the room QR is standing in the one that is actually running. The grace value is clamped at zero before use (:191), and the window scan is restricted to sessions of the resolved event (:197).
      • +
      • ComputeLiveWindowUtc (:233-237) delegates to CurrentEventSelector.GetLiveWindowUtc (MMCA.ADC.Conference.Shared/Events/CurrentEventSelector.cs:66-76) rather than repeating the rule. That shared helper defines the window as start date at 00:00 local through end date plus one day at 00:00 local (exclusive), and its ToUtc handles the spring-forward gap where local midnight never existed by shifting into the hour that did exist (CurrentEventSelector.cs:87-95). The comment at :230-232 states the reason plainly: the home surfaces, the now-next snapshot, and this service must agree on when an event is live, or two surfaces disagree in front of an audience.
    • -
    • Why it's built this way: TimeProvider is injected rather than DateTime.UtcNow being called, which is what makes the room-resolution rules testable at all: the suite drives them with FakeTimeProvider pinned to 2026-09-15 14:30 UTC (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/EventLiveValidationServiceTests.cs:27, :38). [Rubric §14, Testability] assesses whether behavior can be exercised deterministically: the suite exercises all four methods, including back-to-back sessions (:305), both grace-window boundaries (:279, :293), unscheduled sessions (:321), unknown rooms (:335), other rooms' sessions (:346), and unpublished events (:82, :248, :360). The grace window is a parameter, not a Conference setting, because it is check-in policy and Conference only answers the schedule question (MMCA.ADC.Conference.Shared/Events/Live/IEventLiveValidationService.cs:52-55).
    • -
    • Where it's used: registered as services.TryAddScoped<IEventLiveValidationService, EventLiveValidationService>() (MMCA.ADC.Conference.Application/DependencyInjection.cs:139) and consumed exclusively by Engagement: the live-poll lifecycle handlers, the session-question submit and moderation handlers, and the check-in flows including CheckInProcessor (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/CheckIns/Services/CheckInProcessor.cs:111, :162), RecordRoomCheckInHandler (.../CheckIns/UseCases/RecordRoomCheckIn/RecordRoomCheckInHandler.cs:28), RecordSponsorVisitHandler (.../CheckIns/UseCases/RecordSponsorVisit/RecordSponsorVisitHandler.cs:36), SubmitQuestionHandler (.../SessionQuestions/UseCases/Submit/SubmitQuestionHandler.cs:27), and OpenLivePollHandler (.../LivePolls/UseCases/Open/OpenLivePollHandler.cs:22). In the split topology it is exposed over gRPC by EventLiveValidationGrpcService (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Grpc/EventLiveValidationGrpcService.cs:22).
    • +
    • Why it's built this way: TimeProvider is injected rather than DateTime.UtcNow being called, which is what makes the room-resolution rules testable at all: the suite drives them with FakeTimeProvider pinned to 2026-09-15 14:30 UTC (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/EventLiveValidationServiceTests.cs:27, :39). [Rubric §14, Testability] assesses whether behavior can be exercised deterministically: the suite exercises all four methods, including back-to-back sessions (:333), both grace-window boundaries (:280, :321), a service session in progress yielding to the eligible talk inside the grace (:295), a declined session in progress answering NotFound (:309), unscheduled sessions (:349), unknown rooms (:363), other rooms' sessions (:374), and unpublished events (:83, :249, :388). The grace window is a parameter, not a Conference setting, because it is check-in policy and Conference only answers the schedule question (MMCA.ADC.Conference.Shared/Events/Live/IEventLiveValidationService.cs:52-55).
    • +
    • Where it's used: registered as services.TryAddScoped<IEventLiveValidationService, EventLiveValidationService>() (MMCA.ADC.Conference.Application/DependencyInjection.cs:135) and consumed exclusively by Engagement: the live-poll lifecycle handlers, the session-question submit and moderation handlers, and the check-in flows including CheckInProcessor (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/CheckIns/Services/CheckInProcessor.cs:111, :162), RecordRoomCheckInHandler (.../CheckIns/UseCases/RecordRoomCheckIn/RecordRoomCheckInHandler.cs:28), RecordSponsorVisitHandler (.../CheckIns/UseCases/RecordSponsorVisit/RecordSponsorVisitHandler.cs:36), SubmitQuestionHandler (.../SessionQuestions/UseCases/Submit/SubmitQuestionHandler.cs:27), and OpenLivePollHandler (.../LivePolls/UseCases/Open/OpenLivePollHandler.cs:22). In the split topology it is exposed over gRPC by EventLiveValidationGrpcService (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Grpc/EventLiveValidationGrpcService.cs:22).
    • Caveats / not-in-source: three edges.
        -
      • A bad time-zone id throws. TimeZoneInfo.FindSystemTimeZoneById(conferenceEvent.TimeZone) is called with no try/catch and no fallback (:182), and the comment above it says that is deliberate: EventInvariants.EnsureTimeZoneIsValid guards every write path, so an unresolvable stored id is a data defect that must surface rather than degrade silently (:180-181). ComputeLiveWindowUtc inherits the same posture through the shared helper (CurrentEventSelector.cs:72).
      • -
      • The first two methods resolve the write-capable repository through unitOfWork.GetRepository<...>() (:29, :52, :75) while the sponsor and room methods use GetReadRepository<...>() (:110, :123, :148, :167). Every call passes asTracking: false, so the reads are untracked either way; the inconsistency is in which repository is asked for, not in what the query does.
      • -
      • GetCurrentRoomSessionInfoAsync loads all sessions for the room and filters in memory (:149-159, :189-197), because the wall-clock to UTC conversion is compiled code that cannot be translated to SQL. Room-sized session counts make that fine today; nothing in the code bounds it.
      • +
      • A bad time-zone id throws. TimeZoneInfo.FindSystemTimeZoneById(conferenceEvent.TimeZone) is called with no try/catch and no fallback (:189), and the comment above it says that is deliberate: EventInvariants.EnsureTimeZoneIsValid guards every write path, so an unresolvable stored id is a data defect that must surface rather than degrade silently (:187-188). ComputeLiveWindowUtc inherits the same posture through the shared helper (CurrentEventSelector.cs:72).
      • +
      • The first two methods resolve the write-capable repository through unitOfWork.GetRepository<...>() (:31, :54, :77) while the sponsor and room methods use GetReadRepository<...>() (:112, :125, :150, :174). Every call passes asTracking: false, so the reads are untracked either way; the inconsistency is in which repository is asked for, not in what the query does.
      • +
      • GetCurrentRoomSessionInfoAsync loads all sessions for the room and filters in memory (:151-166, :196-204), because the wall-clock to UTC conversion and the status-eligibility check are compiled code that cannot be translated to SQL. Room-sized session counts make that fine today; nothing in the code bounds it.
    @@ -4279,7 +4316,7 @@

    UpdateCategoryItemHandler

    • What it is: the handler for UpdateCategoryItemCommand. It declares four things (which child collection to eager-load, which id addresses the aggregate, which domain method to call, and what to log) and inherits everything else from the framework's load-mutate-save workflow.
    • Depends on: MutateEntityHandlerBase<TCommand, TEntity, TIdentifierType> from MMCA.Common.Application.UseCases (UpdateCategoryItemHandler.cs:4,22), IUnitOfWork (UpdateCategoryItemHandler.cs:3,20), ILogger<T> with a source-generated [LoggerMessage] (UpdateCategoryItemHandler.cs:1,21,41-42), the Category aggregate and its CategoryItem child (UpdateCategoryItemHandler.cs:2), and Result (UpdateCategoryItemHandler.cs:5).
    • -
    • Concept introduced (for this unit), the load-mutate-save template method. The shared machinery lives in MutateEntityHandlerCore<TCommand, TEntity, TIdentifierType> (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/MutateEntityHandlerBase.cs:52), and the piece worth understanding is why the core deliberately does not implement ICommandHandler<,> itself: a real handler returns one of three shapes (a bare Result for verb-style commands, a Result<T> carrying the refreshed DTO, a Result<T> carrying a purpose-built payload), and a single type advertising two handler interfaces would register a bogus second handler entry during the module scan (MutateEntityHandlerBase.cs:17-28). So the core is abstract machinery and three thin subclasses close it over a return shape; this handler picks the bare-Result one (MutateEntityHandlerBase.cs:333-346). The choice is visible in the endpoint: a category-item update answers 204 No Content (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:175), so there is nothing to map back. Compare this handler line by line with RemoveCategoryItemHandler: identical primary constructor, identical Includes, identical EntityId, and the only structural difference is that the remove overrides LoadAsync and this one does not, because an update always arrives with its owner id in the body. [Rubric §4, Domain-Driven Design] assesses whether the aggregate owns its invariants: the handler never touches the child collection itself, and the BR-138 case-insensitive uniqueness check that makes this operation interesting lives inside Category.UpdateCategoryItem (MMCA.ADC.Conference.Domain/Categories/Category.cs:167-168). [Rubric §14, Testability]: with only a unit of work and a logger injected, the handler is exercised in the unit tier against a faked repository (UpdateCategoryItemHandlerTests).
    • +
    • Concept introduced (for this unit), the load-mutate-save template method. The shared machinery lives in MutateEntityHandlerCore<TCommand, TEntity, TIdentifierType> (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/MutateEntityHandlerBase.cs:52), and the piece worth understanding is why the core deliberately does not implement ICommandHandler<,> itself: a real handler returns one of three shapes (a bare Result for verb-style commands, a Result<T> carrying the refreshed DTO, a Result<T> carrying a purpose-built payload), and a single type advertising two handler interfaces would register a bogus second handler entry during the module scan (MutateEntityHandlerBase.cs:17-28). So the core is abstract machinery and three thin subclasses close it over a return shape; this handler picks the bare-Result one (MutateEntityHandlerBase.cs:333-346). The choice is visible in the endpoint: a category-item update answers 204 No Content (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:178), so there is nothing to map back. Compare this handler line by line with RemoveCategoryItemHandler: identical primary constructor, identical Includes, identical EntityId, and the only structural difference is that the remove overrides LoadAsync and this one does not, because an update always arrives with its owner id in the body. [Rubric §4, Domain-Driven Design] assesses whether the aggregate owns its invariants: the handler never touches the child collection itself, and the BR-138 case-insensitive uniqueness check that makes this operation interesting lives inside Category.UpdateCategoryItem (MMCA.ADC.Conference.Domain/Categories/Category.cs:169-170). [Rubric §14, Testability]: with only a unit of work and a logger injected, the handler is exercised in the unit tier against a faked repository (UpdateCategoryItemHandlerTests).
    • Walkthrough: primary-constructor injection of IUnitOfWork and ILogger<UpdateCategoryItemHandler>, with the unit of work forwarded to the base (UpdateCategoryItemHandler.cs:19-22). Four overrides follow. Includes => [nameof(Category.CategoryItems)] (UpdateCategoryItemHandler.cs:25) is load-bearing: without the include the child collection is empty and the domain method finds nothing to update. EntityId(command) => command.CategoryId (UpdateCategoryItemHandler.cs:28) is how the base addresses the aggregate. MutateAsync is one line, Task.FromResult(entity.UpdateCategoryItem(command.CategoryItemId, command.Name, command.Sort)) (UpdateCategoryItemHandler.cs:31-35); the signature is asynchronous so that a mutation needing a cross-service lookup fits the same hook, and a purely synchronous domain call wraps with Task.FromResult (MutateEntityHandlerBase.cs:100-106). LogMutated calls the generated partial with both identifiers as structured fields (UpdateCategoryItemHandler.cs:38-39, template at :41-42). The inherited body then reads: resolve the repository from the unit of work (MutateEntityHandlerBase.cs:280), load through LoadAsync, whose default is GetByIdAsync(EntityId(command), Includes, AsTracking, ...) with AsTracking defaulting to true because a no-tracking load would turn the mutation into a silent no-op (MutateEntityHandlerBase.cs:72-76,152-160), fail with Error.NotFound.WithSource(HandlerName).WithTarget(typeof(TEntity).Name) when the row is absent (MutateEntityHandlerBase.cs:282-283, HandlerName defaulting to the concrete type's own name at :62), skip the row-version stamp because RowVersion(command) is null for an unconditional endpoint (MutateEntityHandlerBase.cs:91,292-294), run the mutation and return its errors unchanged on failure (MutateEntityHandlerBase.cs:298-300), then save once, log, and run the no-op post-commit hook (MutateEntityHandlerBase.cs:316-319). Inside the aggregate the order of checks matters: the child is located first (Category.cs:161), then the uniqueness rule runs excluding the item being edited (Category.cs:167-168), then the child's own Update applies the values after re-checking the name invariant (Category.cs:172; MMCA.ADC.Conference.Domain/Categories/CategoryItem.cs:73), and only then is CategoryItemChanged with DomainEntityState.Updated queued (Category.cs:176).
    • Why it's built this way: keeping the handler declarative means every rule that could reject the edit is discoverable in one place, the aggregate, and the sequence that must never vary (tracked load, not-found, concurrency stamp, mutate, save once) is written once in the framework instead of copy-pasted per slice. The concurrency stamp is the clearest example of why that matters: SetOriginalRowVersion is applied by the base for any command that reports a token, which is how a conditional endpoint gets 412 Precondition Failed rather than last-write-wins (ADR-035, MutateEntityHandlerBase.cs:285-294).
    • Where it's used: resolved by the CategoryItemsController as ICommandHandler<UpdateCategoryItemCommand, Result> (MMCA.ADC.Conference.API/Controllers/Categories/CategoryItemsController.cs:66) and invoked at CategoryItemsController.cs:161-167, followed by an explicit eviction of the conference:categories and conference output-cache tags and a 204 No Content (CategoryItemsController.cs:174-175). Covered by UpdateCategoryItemHandlerTests (MMCA.ADC.Conference.Application.Tests/Categories/UseCases/UpdateCategoryItemHandlerTests.cs:11).
    • @@ -4294,7 +4331,7 @@

      AddEventSpeakerCommand

    • Concept: the add-join command, the narrowest add shape in the module. A join row has no organizer-entered payload of its own, so the command is three identifiers and nothing more (AddEventSpeakerCommand.cs:10-13). The mixed key types are worth noticing: the parent is an int and the speaker is a Guid, because speakers carry Sessionize-assigned GUIDs (BR-61, noted at MMCA.ADC.Conference.Shared/MMCA.ADC.Conference.GlobalUsings.IdentifierType.cs:3) while the join row itself is database-generated ([IdValueGenerated] on MMCA.ADC.Conference.Domain/Events/EventSpeaker.cs:13). That single alias file is why the difference costs the command nothing: the type is stated once and every layer follows it. [Rubric §4, Domain-Driven Design] assesses whether children are created through their root: the command names the parent id first, so the only expressible operation is "add this speaker to that event".
    • Walkthrough: three positional parameters, EventId, the optional EventSpeakerId, and SpeakerId (AddEventSpeakerCommand.cs:10-13), plus CachePrefix => $"{typeof(Event).FullName}:" (:16). Duplicate protection is not on this record and not in its validator: the aggregate refuses a second live association with Error.Invariant("Event.Speaker.Duplicate", ...) (MMCA.ADC.Conference.Domain/Events/Event.cs:540-547), which is a rule that needs the sibling collection and therefore cannot live at the boundary.
    • Why it's built this way: keeping the join command to identifiers means the endpoint contract, the validator target, and the aggregate call signature stay in one-to-one correspondence, and it leaves the "already associated" decision with the only object that can see the existing associations.
    • -
    • Where it's used: constructed by the event-speakers controller as new AddEventSpeakerCommand(request.EventId, null, request.SpeakerId) from an AddEventSpeakerRequest body (MMCA.ADC.Conference.API/Controllers/Events/EventSpeakersController.cs:165,169, handler injected at :49), validated by AddEventSpeakerCommandValidator, and handled by AddEventSpeakerHandler.
    • +
    • Where it's used: constructed by the event-speakers controller as new AddEventSpeakerCommand(request.EventId, null, request.SpeakerId) from an AddEventSpeakerRequest body (MMCA.ADC.Conference.API/Controllers/Events/EventSpeakersController.cs:173,177, handler injected at :49), validated by AddEventSpeakerCommandValidator, and handled by AddEventSpeakerHandler.

    AddRoomCommand

    @@ -4306,7 +4343,7 @@

    AddRoomCommand

  • Concept introduced, the nullable id that means "compute one", not "let the database assign one". Every other optional-identity add in this group relies on the store to generate the key, because the child carries [IdValueGenerated]. Room does not: it is declared without that attribute (MMCA.ADC.Conference.Domain/Events/Room.cs:13) and its doc comment states the reason, room ids are Sessionize-assigned rather than database-generated (Room.cs:8-11). The integer primary key is the upstream identifier, which is what lets the Sessionize import call @event.AddRoom(sr.Id, sr.Name, sr.Sort) with a foreign id (MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RoomSyncStrategy.cs:112). So when an organizer creates a room by hand there is no database sequence to fall back on, and RoomIdentifierType? RoomId (AddRoomCommand.cs:17) becomes an instruction to AddRoomHandler to allocate an id out of a reserved range that cannot collide with Sessionize's. Read this record together with that handler: the nullable parameter here is the whole reason the handler carries a retry loop. [Rubric §8, Data Architecture] assesses whether key ownership is deliberate: an app-assigned key shared with an external system is a real design choice, and its cost (allocation plus collision handling) is paid explicitly rather than hidden. [Rubric §9, API and Contract Design]: two required fields (Name, Sort) and four nullable ones (Capacity, Floor, Location, AccessibilityInfo, :20-23) are the endpoint's optionality contract.
  • Walkthrough: eight positional parameters (AddRoomCommand.cs:15-23), implementing ICacheInvalidating (:23), with CachePrefix => $"{typeof(Event).FullName}:" (:26) keyed on the aggregate root rather than on Room. Name and Sort are non-nullable, so the command cannot express "leave the name alone", which is correct for an add. Name uniqueness within the event is not here and not in the validator: the aggregate enforces it before creating the room (MMCA.ADC.Conference.Domain/Events/Event.cs:394-396), backed by the IX_Room_EventId_Name index the handler names by hand (AddRoomHandler.cs:37).
  • Why it's built this way: the eight parameters are exactly the arguments of Event.AddRoom (MMCA.ADC.Conference.Domain/Events/Event.cs:385-392), so nothing is dropped or invented between the wire and the aggregate, and the optional id keeps one aggregate method serving both the organizer path and the importer path.
  • -
  • Where it's used: constructed by the rooms controller from an AddRoomRequest body (MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:207,211, handler injected at :94), validated by AddRoomCommandValidator, and handled by AddRoomHandler. Its edit and delete counterparts are UpdateRoomCommand and RemoveRoomCommand.
  • +
  • Where it's used: constructed by the rooms controller from an AddRoomRequest body (MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:215,219, handler injected at :94), validated by AddRoomCommandValidator, and handled by AddRoomHandler. Its edit and delete counterparts are UpdateRoomCommand and RemoveRoomCommand.
  • EventCreateRequest

    @@ -4318,7 +4355,7 @@

    EventCreateRequest

  • Concept introduced, the shared-field interface that keeps create and update honest. This record implements three interfaces, and the third is the interesting one. IEventFieldsRequest (MMCA.ADC.Conference.Application/Events/Validation/IEventFieldsRequest.cs:12-37) declares the seven event fields that create and update validate identically. A FluentValidation validator is generic over the type it validates, so a rule written for the create request cannot normally be reused by the update request. Declaring the shared shape as an interface lets EventFieldRules<T> be constrained where T : IEventFieldsRequest (MMCA.ADC.Conference.Application/Events/Validation/EventValidationRules.cs:172-174) and written once, so the two operations cannot drift apart on what a valid event name, time zone, date range, contact email, sponsorship URL, or ticketing URL is. What is deliberately off the interface is as instructive as what is on it: the live-layer moderation default is carried only by the update request, and its rule lives in that operation's validator (IEventFieldsRequest.cs:8-11). [Rubric §15, Best Practices & Code Quality] assesses whether a change has one edit point: adding a shared event field means one interface member and one line in EventFieldRules<T>, and both operations follow. [Rubric §9, API and Contract Design]: exactly four members are required (Name, StartDate, EndDate, TimeZone, :20,26,29,32) and nine are optional by declaration. [Rubric §12, Performance & Scalability]: eviction is declared through CachePrefix, not coded in the handler.
  • Walkthrough: CachePrefix => $"{typeof(Event).FullName}:" (EventCreateRequest.cs:14) is the key the caching decorator purges on success. Id (:17) is a non-nullable EventIdentifierType, so an omitted id binds to 0; that is harmless here because Event carries [IdValueGenerated] (MMCA.ADC.Conference.Domain/Events/Event.cs:23) and the factory resolves the key as Id = isIdValueGenerated ? default : id!.Value (Event.cs:203), which means the factory's id!.Value branch is unreachable for this aggregate. The four required members (:20,26,29,32) are the ones the domain factory guards, EnsureNameIsValid, EnsureTimeZoneIsValid, and EnsureDateRangeIsValid (Event.cs:180-183). The nine optional members are the edition's publishable extras: Description, SessionizeCode, VenueAddress, VenueMapUrl, WiFiInfo, OrganizerContactEmail, SponsorshipPacketUrl, and TicketingUrl (:23,35,38,41,44,47,50,53). Every member is init-only, so model binding is the last chance to set anything. Note the type declaration is public record class rather than sealed record (:11), matching its sibling create requests in this module.
  • Why it's built this way: collapsing request and command removes a mapping step with no behavior of its own, and pushing the shared field list onto an interface keeps the create and update validators from being two copies of the same rules that can silently diverge.
  • -
  • Where it's used: bound by the events controller (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:198), which types its base class on it (EventsController.cs:59) and injects the handler as ICommandHandler<EventCreateRequest, Result<EventDTO>> (:48); validated by EventCreateRequestValidator, converted by EventCreateRequestMapper, and handled by CreateEventHandler. Its edit-side counterpart is EventUpdateRequest.
  • +
  • Where it's used: bound by the events controller (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:205), which types its base class on it (EventsController.cs:59) and injects the handler as ICommandHandler<EventCreateRequest, Result<EventDTO>> (:48); validated by EventCreateRequestValidator, converted by EventCreateRequestMapper, and handled by CreateEventHandler. Its edit-side counterpart is EventUpdateRequest.
  • PublishEventCommand

    @@ -4327,10 +4364,10 @@

    PublishEventCommand

    • What it is: the write intent for the publish transition on an Event, the flip that makes an edition visible to attendees. It carries the event id and the caller's optimistic-concurrency token, and nothing else.
    • Depends on: ICacheInvalidating (PublishEventCommand.cs:2,13), the Event type for the cache prefix (:1,16), the EventIdentifierType alias (= int), and byte[] (BCL).
    • -
    • Concept introduced, the conditional state-transition command. Most commands in this module carry payload. This one carries a precondition. byte[] RowVersion (:13) is non-nullable and its doc comment states the contract in the code itself: it is the caller's last-observed token, read from the request's If-Match header, and it is required because a transition that states no precondition never reaches this command (:8-12). The enforcement is split across three places worth following. The controller declares [SupportsIfMatch] so a request without the header answers 428 Precondition Required and never runs the action (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:215), and pulls the token with SupportsIfMatchAttribute.RequiredToken(HttpContext) (:272). PublishEventHandler reports it back to the framework through its RowVersion override (PublishEventHandler.cs:25). The framework's write workflow stamps it as the entity's original version, repository.SetOriginalRowVersion(entity, rowVersion) (MMCA.Common.Application/UseCases/Crud/MutateEntityHandlerBase.cs:294), so a decision made against a stale view fails the save and answers 412 Precondition Failed instead of last-write-wins. That is ADR-035. [Rubric §9, API and Contract Design] assesses whether a mutating endpoint states its concurrency contract: here the precondition is a required field of the command type, not a convention. [Rubric §29, Resilience and Business Continuity]: pairing the conditional token with [Idempotent] (EventsController.cs:263) means a retried publish replays the stored response rather than racing a second transition.
    • +
    • Concept introduced, the conditional state-transition command. Most commands in this module carry payload. This one carries a precondition. byte[] RowVersion (:13) is non-nullable and its doc comment states the contract in the code itself: it is the caller's last-observed token, read from the request's If-Match header, and it is required because a transition that states no precondition never reaches this command (:8-12). The enforcement is split across three places worth following. The controller declares [SupportsIfMatch] so a request without the header answers 428 Precondition Required and never runs the action (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:222), and pulls the token with SupportsIfMatchAttribute.RequiredToken(HttpContext) (:272). PublishEventHandler reports it back to the framework through its RowVersion override (PublishEventHandler.cs:25). The framework's write workflow stamps it as the entity's original version, repository.SetOriginalRowVersion(entity, rowVersion) (MMCA.Common.Application/UseCases/Crud/MutateEntityHandlerBase.cs:294), so a decision made against a stale view fails the save and answers 412 Precondition Failed instead of last-write-wins. That is ADR-035. [Rubric §9, API and Contract Design] assesses whether a mutating endpoint states its concurrency contract: here the precondition is a required field of the command type, not a convention. [Rubric §29, Resilience and Business Continuity]: pairing the conditional token with [Idempotent] (EventsController.cs:263) means a retried publish replays the stored response rather than racing a second transition.
    • Walkthrough: a sealed record with two positional parameters (PublishEventCommand.cs:13) plus CachePrefix => $"{typeof(Event).FullName}:" (:16). There is no PublishEventCommandValidator, and that absence is asserted rather than accidental: the command is listed under "identifier-only commands" in the architecture fitness rule that requires every data-carrying command to have a validator (MMCA.ADC.Architecture.Tests/Cqrs/CommandValidatorCoverageTests.cs:32, the rule's reasoning at :26-30). Its whole payload is a route-supplied id plus a header-supplied token, so a validator would be ceremony. The "already published" refusal lives in the aggregate, Error.Invariant("Event.AlreadyPublished", ...) (MMCA.ADC.Conference.Domain/Events/Event.cs:301-308).
    • Why it's built this way: a verb command with no payload keeps the transition auditable and gives the pipeline a precise eviction boundary, while carrying the concurrency token as a required constructor parameter makes it impossible to construct the command without a stated precondition.
    • -
    • Where it's used: constructed by the events controller as new PublishEventCommand(id, rowVersion) (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:276, handler injected as ICommandHandler<PublishEventCommand, Result> at :50) and handled by PublishEventHandler. Its mirror is UnpublishEventCommand.
    • +
    • Where it's used: constructed by the events controller as new PublishEventCommand(id, rowVersion) (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:283, handler injected as ICommandHandler<PublishEventCommand, Result> at :50) and handled by PublishEventHandler. Its mirror is UnpublishEventCommand.

    AddEventQuestionAnswerCommand

    @@ -4342,7 +4379,7 @@

    AddEventQuestionAnswerCommand

  • Concept: the optional-identity add command taught by AddCategoryItemCommand, applied to the one add-child command in this family that carries free text. EventQuestionAnswerIdentifierType? EventQuestionAnswerId (AddEventQuestionAnswerCommand.cs:13) is nullable for the same reason: the domain factory declares EventQuestionAnswer.Create(EventQuestionAnswerIdentifierType? id, ...) (MMCA.ADC.Conference.Domain/Events/EventQuestionAnswer.cs:46-49) and resolves it as Id = isIdValueGenerated ? default : id!.Value (EventQuestionAnswer.cs:60). Because the entity carries [IdValueGenerated] (EventQuestionAnswer.cs:12), a null id becomes 0 and EF assigns the key at save, so the null branch is the only one the HTTP path ever takes. What makes this command different from its siblings is string AnswerValue (:14): it is caller-typed content, which is why it is the only add command in this family with a validator that has a rule body of its own (AddEventQuestionAnswerCommandValidator). [Rubric §9, API and Contract Design] assesses whether an inbound contract states its shape and optionality explicitly: three non-nullable fields plus one nullable id is the endpoint's own documentation of what a caller must supply. [Rubric §12, Performance & Scalability]: eviction is declared, not coded, because the caching decorator reads CachePrefix off the command rather than the handler purging by hand.
  • Walkthrough: a sealed record with four positional parameters (AddEventQuestionAnswerCommand.cs:11-15) implementing ICacheInvalidating (:15). The single body member is CachePrefix => $"{typeof(Event).FullName}:" (:18), keyed on the aggregate root and not on the child, because a cached event read carries its answers inline. Note what the command does not carry: no user id. The answering attendee is not a wire field, it is resolved server-side from ICurrentUserService inside AddEventQuestionAnswerHandler (AddEventQuestionAnswerHandler.cs:50), so no caller can post feedback in another attendee's name by shaping a body.
  • Why it's built this way: the command names exactly the values the aggregate method Event.AddEventQuestionAnswer needs (MMCA.ADC.Conference.Domain/Events/Event.cs:619-622) and nothing else, and it deliberately omits identity so that authorship is a server fact rather than a request field.
  • -
  • Where it's used: constructed by the event-question-answers controller as new AddEventQuestionAnswerCommand(request.EventId, null, request.QuestionId, request.AnswerValue) from an AddEventQuestionAnswerRequest body (MMCA.ADC.Conference.API/Controllers/Events/EventQuestionAnswersController.cs:230,234, handler injected at :57), validated by AddEventQuestionAnswerCommandValidator, and handled by AddEventQuestionAnswerHandler. The controller always passes null for the id, so the explicit-id parameter has no HTTP caller today.
  • +
  • Where it's used: constructed by the event-question-answers controller as new AddEventQuestionAnswerCommand(request.EventId, null, request.QuestionId, request.AnswerValue) from an AddEventQuestionAnswerRequest body (MMCA.ADC.Conference.API/Controllers/Events/EventQuestionAnswersController.cs:234,238, handler injected at :57), validated by AddEventQuestionAnswerCommandValidator, and handled by AddEventQuestionAnswerHandler. The controller always passes null for the id, so the explicit-id parameter has no HTTP caller today.
  • AddEventSpeakerCommandValidator

    @@ -4354,7 +4391,7 @@

    AddEventSpeakerCommandValidator

  • Concept: the alias-aware default check. NotEqual(default(SpeakerIdentifierType)) (:12) reads as a type-agnostic rule, but it resolves to Guid.Empty because the alias is System.Guid (MMCA.ADC.Conference.Shared/MMCA.ADC.Conference.GlobalUsings.IdentifierType.cs:23). Written this way, the rule keeps working if the alias is ever retyped, which is the point of routing every key through the alias file rather than naming Guid here. This matters more than for the int-keyed commands: an omitted Guid binds to Guid.Empty rather than to a value that would obviously fail a foreign-key lookup, so without this rule an empty speaker id would reach the aggregate and surface as a persistence error rather than a field-addressed 400.
  • Walkthrough: an expression-bodied constructor (AddEventSpeakerCommandValidator.cs:10-13) with RuleFor(x => x.SpeakerId).NotEqual(default(SpeakerIdentifierType)).WithMessage("Speaker ID is required."). Note what is missing relative to its sibling in this unit: there is no .WithErrorCode(...) call, so this rule reports FluentValidation's default code (NotEqualValidator) where AddEventQuestionAnswerCommandValidator reports the stable EventQuestionAnswer.AnswerValue.Required (AddEventQuestionAnswerCommandValidator.cs:14). A client branching on error codes gets a module-specific contract from one and a library-default one from the other. Duplicate association is not checked here: it needs the sibling collection, so the aggregate owns it (MMCA.ADC.Conference.Domain/Events/Event.cs:538-546).
  • Why it's built this way: the join command carries only identifiers, so the single field a boundary validator can meaningfully reject is an unset speaker id.
  • -
  • Where it's used: resolved by the validating decorator around AddEventSpeakerHandler, registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143); covered by AddEventSpeakerCommandValidatorTests (Group 27).
  • +
  • Where it's used: resolved by the validating decorator around AddEventSpeakerHandler, registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); covered by AddEventSpeakerCommandValidatorTests (Group 27).
  • AddEventSpeakerHandler

    @@ -4366,7 +4403,7 @@

    AddEventSpeakerHandler

  • Concept introduced, the add-child handler as five answers. The framework owns the shape every add-child write repeats: resolve the parent repository, load the parent with the declared includes and tracking, return NotFound if it is absent, call the aggregate method, save, log, run the post-commit hook, and map the child (MMCA.Common.Application/UseCases/Crud/ChildEntityHandlerBase.cs:102-126). A concrete handler supplies only what the framework cannot know: which collection to include, how to read the parent id off the command, which aggregate method to call, how to map the child, and what to log. Everything else, including the failure taxonomy, is inherited. Compare this thirty-line file with AddEventQuestionAnswerHandler's hundred and twenty: the base is used where the workflow fits and bypassed where it does not, rather than being contorted. [Rubric §2, Design Patterns] assesses whether a recurring shape is captured once: the template-method base is that capture. [Rubric §1, SOLID]: the five protected override members are the extension points, and the handler adds no public surface of its own. [Rubric §15, Best Practices & Code Quality]: a change to the write workflow (for example, an added post-commit hook) lands in the framework, not in each module's handlers.
  • Walkthrough: five overrides. Includes => [nameof(Event.EventSpeakers)] (AddEventSpeakerHandler.cs:24) is the load-bearing one, and its comment says why (:21-22): the base declares Includes abstract (ChildEntityHandlerBase.cs:55) precisely so that naming or deliberately not naming the join collection is an explicit decision, because an unloaded collection makes the aggregate's duplicate check run against an empty in-memory list and turns a double submit into a raw unique-index 409. ParentId reads command.EventId (:27). Apply calls parent.AddEventSpeaker(command.EventSpeakerId, command.SpeakerId) (:30-31), the aggregate method that owns the duplicate rule and raises EventSpeakerChanged (MMCA.ADC.Conference.Domain/Events/Event.cs:536,557). MapChild delegates to the Mapperly mapper (:34). LogAdded forwards to the source-generated [LoggerMessage] partial (:37-41), which is why the class is partial. Tracking is left at the base default of true (ChildEntityHandlerBase.cs:49), because a no-tracking load would make the add a silent no-op.
  • Why it's built this way: the base is an abstract class implementing ICommandHandler<in TCommand, TResult> rather than a decorator, so the concrete subclass stays the registered handler and the pipeline decorators wrap it normally.
  • -
  • Where it's used: resolved as ICommandHandler<AddEventSpeakerCommand, Result<EventSpeakerDTO>> by the event-speakers controller (MMCA.ADC.Conference.API/Controllers/Events/EventSpeakersController.cs:49,169); registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143); covered by AddEventSpeakerHandlerTests (Group 27).
  • +
  • Where it's used: resolved as ICommandHandler<AddEventSpeakerCommand, Result<EventSpeakerDTO>> by the event-speakers controller (MMCA.ADC.Conference.API/Controllers/Events/EventSpeakersController.cs:49,177); registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); covered by AddEventSpeakerHandlerTests (Group 27).
  • AddRoomCommandValidator

    @@ -4378,7 +4415,7 @@

    AddRoomCommandValidator

  • Concept: rule composition by Include with a property selector, the mechanism the module's rule objects introduce, at its widest use in this unit. Each rule is generic in T and takes an Expression<Func<T, ...>>, so new RoomNameRules<AddRoomCommand>(p => p.Name) means "apply the room-name rules to this type's Name property", and the add command and the update command include the same six objects rather than restating them. The limits all resolve back to one place, EventInvariants, which forwards them from the DTO constants: name 255 (MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:47, MMCA.ADC.Conference.Shared/Rooms/RoomDTO.cs:16), floor 100 (EventInvariants.cs:49, RoomDTO.cs:19), location 255 (EventInvariants.cs:52, RoomDTO.cs:22), accessibility info 500 (EventInvariants.cs:55, RoomDTO.cs:25). Because the DTO constant is also what the EF configuration reads, the API error message and the column width cannot disagree. [Rubric §15, Best Practices & Code Quality] assesses whether a change has one edit point: raising a room limit is one edit to the DTO constant. [Rubric §24, Forms, Validation and UX Safety]: each rule attaches a stable machine code alongside its human message, for example Room.Name.Required and Room.Name.MaxLength (MMCA.ADC.Conference.Application/Events/Validation/RoomValidationRules.cs:18-19).
  • Walkthrough: a block-bodied constructor with six statements (AddRoomCommandValidator.cs:9-17). Two rules are required-field rules: RoomNameRules<T> derives from the framework's RequiredStringRules<T> and enforces non-empty plus max length (RoomValidationRules.cs:13-20), and RoomSortRules<T> derives from NonNegativeIntRules<T> with the code Room.Sort.Negative (:26-32). Four are conditional: RoomCapacityRules<T> applies GreaterThan(0) with code Room.Capacity.NotPositive only .When(...) the value is not null (:37-45), and the three optional strings derive from OptionalStringRules<T> with their own max-length codes (:51-80). Nothing here checks room-name uniqueness within the event: that needs the sibling collection, so the aggregate owns it (MMCA.ADC.Conference.Domain/Events/Event.cs:396-398).
  • Why it's built this way: field-shape rules that need only the incoming values run at the boundary where they can be reported as one complete, field-addressed list, and expressing them as reusable objects is what keeps the add path and the edit path from drifting on what a valid room looks like.
  • -
  • Where it's used: resolved by the validating decorator around AddRoomHandler, registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143); covered by AddRoomCommandValidatorTests (Group 27).
  • +
  • Where it's used: resolved by the validating decorator around AddRoomHandler, registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); covered by AddRoomCommandValidatorTests (Group 27).
  • EventCreateRequestMapper

    @@ -4390,7 +4427,7 @@

    EventCreateRequestMapper

  • Concept introduced, the write-side mapper as a factory call. The read-side mappers in this module are Mapperly-generated property copies (ADR-001). The write side deliberately is not. Generating a property copy into an entity would have to write private setters and would bypass the guard clauses, so this mapper is hand-written and its entire body is a call to Event.Create (:19-32). That keeps a single door into the aggregate: a new event exists only if EnsureNameIsValid, EnsureTimeZoneIsValid, and EnsureDateRangeIsValid all pass (MMCA.ADC.Conference.Domain/Events/Event.cs:195-198), and the failure comes back as errors rather than a half-built object. [Rubric §4, Domain-Driven Design] assesses whether the aggregate controls its own construction: the Application layer holds no new Event(...). [Rubric §3, Clean Architecture]: the dependency points inward, the mapper knows the domain and the domain knows nothing of the request type. [Rubric §14, Testability]: the mapper is a pure function of its input and can be exercised without a database.
  • Walkthrough: one method, CreateEntityAsync (EventCreateRequestMapper.cs:15-33). It null-guards with ArgumentNullException.ThrowIfNull(request) (:17), then returns Task.FromResult(Event.Create(...)) (:19-32): the work is synchronous, and the Task exists only because the interface is async for the mappers that do need I/O. Thirteen arguments are forwarded positionally except the last three, which are passed by name (organizerContactEmail, sponsorshipPacketUrl, ticketingUrl, :30-32). One factory parameter is deliberately not forwarded: questionModerationDefault, which keeps its declared default of QuestionModerationDefault.Pending (MMCA.ADC.Conference.Domain/Events/Event.cs:181). That is the same asymmetry IEventFieldsRequest records: the moderation default is an update-time concern, so a create never states it and every new event starts on the safe setting.
  • Why it's built this way: routing creation through the factory is what makes the invariants unskippable, and delegating rather than copying means adding a domain guard automatically covers the HTTP path with no change here.
  • -
  • Where it's used: injected into CreateEventHandler as IEntityRequestMapper<Event, EventCreateRequest, EventIdentifierType> (MMCA.ADC.Conference.Application/Events/UseCases/Create/CreateEventHandler.cs:17) and consumed by the framework's create workflow at requestMapper.CreateEntityAsync(request, cancellationToken) (MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:90). Registration is by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143), not an explicit AddScoped line.
  • +
  • Where it's used: injected into CreateEventHandler as IEntityRequestMapper<Event, EventCreateRequest, EventIdentifierType> (MMCA.ADC.Conference.Application/Events/UseCases/Create/CreateEventHandler.cs:17) and consumed by the framework's create workflow at requestMapper.CreateEntityAsync(request, cancellationToken) (MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:90). Registration is by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139), not an explicit AddScoped line.
  • EventCreateRequestValidator

    @@ -4402,7 +4439,7 @@

    EventCreateRequestValidator

  • Concept: rule composition through the shared-field interface introduced by EventCreateRequest. Because EventFieldRules<T> is constrained where T : IEventFieldsRequest and reaches its fields through the interface (MMCA.ADC.Conference.Application/Events/Validation/EventValidationRules.cs:172-185), this validator needs no property selectors at all: the interface already names the properties. That is the difference from the room and category validators, which pass a lambda per field. [Rubric §15, Best Practices & Code Quality] assesses whether a change has one edit point: create and update share one rule list, so a new event field is validated identically on both paths or on neither.
  • Walkthrough: an expression-bodied constructor, Include(new EventFieldRules<EventCreateRequest>()) (EventCreateRequestValidator.cs:9-10). What that pulls in is six rule sets (EventValidationRules.cs:145-150): name required plus max length 500 via RequiredStringRules<T> (:13-18, limit at MMCA.ADC.Conference.Shared/Events/EventDTO.cs:19); time zone required, max length 100, and a Must(BeAValidIanaTimeZone) check that resolves the identifier through TimeZoneInfo.FindSystemTimeZoneById and reports Event.TimeZone.InvalidIana on a TimeZoneNotFoundException (:25-48, BR-87); a date-range rule requiring both dates and asserting endDate >= startDate with code Event.EndDate.BeforeStart (:113-131); and three conditional rules for the optional organizer email, sponsorship packet URL, and ticketing URL, each applying the framework's shared email or absolute-URL rules only When a value is present (:57-67,77-87,100-107). The URL rules check the scheme, which is what keeps a rendered link from carrying an executable javascript: or data: target (:69-75).
  • Why it's built this way: writing the shared rules once against an interface and leaving each concrete validator to add only its own delta is what makes "create and update agree" a compile-time property rather than a review convention.
  • -
  • Where it's used: resolved by the validating decorator around CreateEventHandler, registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143); covered by EventCreateRequestValidatorTests (Group 27, MMCA.ADC.Conference.Application.Tests/Events/Validation/EventCreateRequestValidatorTests.cs). Its edit-side counterpart is EventUpdateRequestValidator.
  • +
  • Where it's used: resolved by the validating decorator around CreateEventHandler, registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); covered by EventCreateRequestValidatorTests (Group 27, MMCA.ADC.Conference.Application.Tests/Events/Validation/EventCreateRequestValidatorTests.cs). Its edit-side counterpart is EventUpdateRequestValidator.
  • AddEventQuestionAnswerCommandValidator

    @@ -4411,10 +4448,10 @@

    AddEventQuestionAnswerCommandVal
    • What it is: the FluentValidation validator the pipeline runs against an AddEventQuestionAnswerCommand before the handler sees it. It is one rule.
    • Depends on: FluentValidation.AbstractValidator<T> (NuGet, AddEventQuestionAnswerCommandValidator.cs:1,8) and the command it validates.
    • -
    • Concept: the boundary-versus-domain split, seen at its sharpest. The validator checks that AnswerValue is non-empty and stops there (:11-14). Three further rules about the same field live elsewhere, on purpose. Max length (4000 characters) is a domain invariant applied by the entity factory (EventInvariants.EnsureAnswerValueIsValid, MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:140, with AnswerValueMaxLength at :58). Whether the answer's shape is legal depends on the question being answered, so EnsureAnswerValueMatchesQuestionType (MMCA.ADC.Conference.Domain/Questions/QuestionInvariants.cs:118-129) switches on the question type and demands, for a "Rating", an integer between 1 and 5 (QuestionInvariants.cs:131-143). And whether the question even applies to events is a cross-aggregate lookup the handler performs (AddEventQuestionAnswerHandler.cs:64-74). None of the last three can be decided from the incoming values alone, which is exactly the criterion for what a boundary validator may hold. [Rubric §24, Forms, Validation and UX Safety] assesses whether invalid input is rejected at the boundary with actionable messages: the rule carries both human text and the stable machine code EventQuestionAnswer.AnswerValue.Required (:13-14), and a client may branch on the code while the wording changes freely.
    • +
    • Concept: the boundary-versus-domain split, seen at its sharpest. The validator checks that AnswerValue is non-empty and stops there (:11-14). Three further rules about the same field live elsewhere, on purpose. Max length (4000 characters) is a domain invariant applied by the entity factory (EventInvariants.EnsureAnswerValueIsValid, MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:140, with AnswerValueMaxLength at :58). Whether the answer's shape is legal depends on the question being answered, so EnsureAnswerValueMatchesQuestionType (MMCA.ADC.Conference.Domain/Questions/QuestionInvariants.cs:122-133) switches on the question type and demands, for a "Rating", an integer between 1 and 5 (QuestionInvariants.cs:131-143). And whether the question even applies to events is a cross-aggregate lookup the handler performs (AddEventQuestionAnswerHandler.cs:64-74). None of the last three can be decided from the incoming values alone, which is exactly the criterion for what a boundary validator may hold. [Rubric §24, Forms, Validation and UX Safety] assesses whether invalid input is rejected at the boundary with actionable messages: the rule carries both human text and the stable machine code EventQuestionAnswer.AnswerValue.Required (:13-14), and a client may branch on the code while the wording changes freely.
    • Walkthrough: an expression-bodied constructor (AddEventQuestionAnswerCommandValidator.cs:10-14), RuleFor(x => x.AnswerValue).NotEmpty().WithMessage("Answer value is required.").WithErrorCode("EventQuestionAnswer.AnswerValue.Required"). Nothing is checked about EventQuestionAnswerId (there is nothing to validate about an absent id) or about EventId and QuestionId (a wrong id answers NotFound from the handler, not a field error).
    • Why it's built this way: keeping only the value-shape rule here means the validator has no dependencies and can be constructed with new in a unit test, while the rules that need loaded state stay where the state is.
    • -
    • Where it's used: resolved by the validating decorator around AddEventQuestionAnswerHandler, registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143); covered directly by AddEventQuestionAnswerCommandValidatorTests (Group 27).
    • +
    • Where it's used: resolved by the validating decorator around AddEventQuestionAnswerHandler, registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); covered directly by AddEventQuestionAnswerCommandValidatorTests (Group 27).

    AddEventQuestionAnswerHandler

    @@ -4426,7 +4463,7 @@

    AddEventQuestionAnswerHandler

  • Concept introduced, the handler that is hand-written because the workflow is not the generic one. Every other add-child handler in this unit subclasses a framework base (AddEventSpeakerHandler is the clean example). This one implements ICommandHandler<in TCommand, TResult> directly (:23) because its workflow branches: the same POST either adds a new answer or rewrites the caller's existing one (BR-107), which no "add child" base can express. The second idea it introduces is the cross-module notification raised on the aggregate before the save. entity.AddDomainEvent(new EventFeedbackSubmitted(userId, entity.Id, timeProvider.GetUtcNow().UtcDateTime)) (:112) does not send anything: it appends to the aggregate's pending list, and the outbox captures the message in the same SaveChangesAsync transaction that writes the answer row (ADR-003). Either both land or neither does, so Engagement can never award points for feedback that was rolled back. The event itself is a versioned contract, [EventName("Conference.EventFeedbackSubmitted.v1")] (MMCA.ADC.Conference.Shared/Events/IntegrationEvents/EventFeedbackSubmitted.cs:19-24). [Rubric §6, CQRS and Event-Driven] assesses whether modules communicate through durable, named events rather than direct calls: this handler names no Engagement type at all. [Rubric §7, Microservices Readiness]: the notification crosses a module boundary through the message contract in the Shared project, which is what makes the module extractable. [Rubric §11, Security]: authorship is taken from the authenticated principal (:50), never from the request body, and the controller is [Authorize] (MMCA.ADC.Conference.API/Controllers/Events/EventQuestionAnswersController.cs:77).
  • Walkthrough: HandleAsync (:26-58) runs five steps. (1) Load the event tracked, with the answers collection eager-loaded, includes: [nameof(Event.EventQuestionAnswers)], asTracking: true (:31-35); a missing event returns Error.NotFound tagged with the handler and entity names (:36-37). (2) BR-108, the event must be published, via EventQuestionAnswerRules.EnsureEventAcceptsFeedback(entity, nameof(AddEventQuestionAnswerHandler)) (:41-44, forwarding to EventInvariants.EnsureEventIsPublished, MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:151-157). (3) ValidateQuestionAsync (:60-79) loads the Question from its own repository (:174-175) and hands both remaining checks to EventQuestionAnswerRules.EnsureAnswerIsValid in one call (:177-178), covering BR-128 (question exists and targets "Event") and BR-124 (answer matches the question's type) from the one copy the batch path also uses. (4) BR-107, the upsert: the caller's identity comes from currentUserService.UserId!.Value (:50) and the existing answer is found in memory with FirstOrDefault(a => !a.IsDeleted && a.QuestionId == command.QuestionId && a.CreatedBy == userId) (:51-52), which is why the include in step 1 is load-bearing. (5) The branch: UpdateExistingAnswerAsync (:81-94) calls entity.UpdateEventQuestionAnswer, saves, logs, and maps the existing child; CreateNewAnswerAsync (:96-117) calls entity.AddEventQuestionAnswer, raises the integration event, saves, logs, and maps the new child. Note the asymmetry, stated in the code comment at :109-111: EventFeedbackSubmitted is raised on the create path only, so editing an answer does not award a second round of points. Logging goes through a source-generated [LoggerMessage] partial (:119-120), which is why the class is partial.
  • Why it's built this way: upsert-on-POST is the right contract for a feedback form (a re-submit is a correction, not a duplicate), and the only way to detect "the caller already answered this question" is against the loaded sibling collection, which places the rule in the handler rather than the boundary. Filtering on !a.IsDeleted (:52) rather than relying on the query filter reflects the soft-delete model (ADR-005): the collection was materialized through the aggregate, so the predicate is applied in memory.
  • -
  • Where it's used: resolved as ICommandHandler<AddEventQuestionAnswerCommand, Result<EventQuestionAnswerDTO>> by the event-question-answers controller (MMCA.ADC.Conference.API/Controllers/Events/EventQuestionAnswersController.cs:80,234); registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143); covered by AddEventQuestionAnswerHandlerTests (Group 27).
  • +
  • Where it's used: resolved as ICommandHandler<AddEventQuestionAnswerCommand, Result<EventQuestionAnswerDTO>> by the event-question-answers controller (MMCA.ADC.Conference.API/Controllers/Events/EventQuestionAnswersController.cs:80,238); registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); covered by AddEventQuestionAnswerHandlerTests (Group 27).
  • Caveats: currentUserService.UserId!.Value (:50) uses the null-forgiving operator, so an unauthenticated invocation would throw rather than return a failure. The controller's [Authorize] (EventQuestionAnswersController.cs:54) is what makes that unreachable over HTTP; the handler itself states no such guard.
  • CreateEventHandler

    @@ -4439,7 +4476,7 @@

    CreateEventHandler

  • Concept: the create counterpart of the template-method base AddEventSpeakerHandler demonstrates. CreateCoreAsync runs the fixed sequence (MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:77-103): the optional PrepareAsync pre-map step, requestMapper.CreateEntityAsync (:89), PersistAsync which adds and saves (:96,128-145), LogCreated (:98), the OnCreatedAsync post-commit hook (:99), and finally dtoMapper.MapToDTO(entity) (:101). Three of those five hooks are virtual with working defaults, so a create slice with no special needs overrides exactly one. Note the generic constraints (CreateEntityHandlerBase.cs:46-49): the request must implement ICreateRequest, the entity must be an AuditableAggregateRootEntity<TIdentifierType>, and the DTO must implement IBaseDTO<TIdentifierType>, so the workflow is only reusable where the shape genuinely matches. [Rubric §2, Design Patterns] assesses whether a recurring shape is captured once. [Rubric §13, Observability and Operability]: LogCreated is a no-op in the base by design (CreateEntityHandlerBase.cs:147-150), because a log message is per-module vocabulary and the base only provides the call site.
  • Walkthrough: the primary constructor forwards unitOfWork, requestMapper, and dtoMapper to the base (CreateEventHandler.cs:15-21). The only member is LogCreated, which forwards to the source-generated [LoggerMessage] partial LogEventCreated with the id and name (:24-27), so the emitted event is structured and allocation-free rather than an interpolated string. PrepareAsync and PersistAsync are left at their defaults: an event needs no app-assigned key (unlike AddRoomHandler) and no non-standard persist step. Audit stamping and soft-delete are not visible here at all; they are applied by ApplicationDbContext.SaveChangesAsync (see the primer).
  • Why it's built this way: a create path with nothing unusual about it should be a declaration of its three collaborators plus a log message, and the fact that this file is 28 lines is the evidence that the framework absorbed the rest.
  • -
  • Where it's used: resolved as ICommandHandler<EventCreateRequest, Result<EventDTO>> by the events controller, which passes it to its base action (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:46,54,198); registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143); covered by CreateEventHandlerTests (Group 27).
  • +
  • Where it's used: resolved as ICommandHandler<EventCreateRequest, Result<EventDTO>> by the events controller, which passes it to its base action (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:46,54,205); registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); covered by CreateEventHandlerTests (Group 27).
  • PublishEventHandler

    @@ -4451,7 +4488,7 @@

    PublishEventHandler

  • Concept introduced, the verb-command handler that answers with a bare Result. The three-parameter MutateEntityHandlerBase is the base for commands where the caller needs only success or the refused invariant, which is exactly what publish, unpublish, open, close, and remove-a-child are (MMCA.Common.Application/UseCases/Crud/MutateEntityHandlerBase.cs:333-344). Its shared workflow, MutateCoreAsync (:270-308), resolves the repository, loads the aggregate, returns Error.NotFound if it is absent (:279-282), stamps the optimistic-concurrency token if the handler reports one (:290-291), runs MutateAsync, short-circuits without saving if the mutation marked the run as a no-op (:299-300), saves, logs, and runs the post-commit hook. The conditional stamp is the mechanism behind ADR-035: a handler serving an unconditional endpoint reports null from the base default (:90) and the stamp is skipped, while a handler on a conditional endpoint always has a token, because a request without an If-Match header never reaches the action. [Rubric §9, API and Contract Design] assesses whether concurrency is part of the contract rather than an afterthought. [Rubric §29, Resilience and Business Continuity]: a stale write is refused with 412 instead of silently overwriting a concurrent edit.
  • Walkthrough: four overrides. EntityId reads command.Id (PublishEventHandler.cs:19). RowVersion returns command.RowVersion (:25), the single line that turns this into a conditional write, with the reason stated in the comment above it (:21-22). MutateAsync returns Task.FromResult(entity.Publish()) (:28-32), delegating the entire decision to the aggregate, which refuses a second publish with Event.AlreadyPublished and otherwise sets IsPublished and raises EventChanged (MMCA.ADC.Conference.Domain/Events/Event.cs:299-315). LogMutated forwards to the [LoggerMessage] partial (:35-39). Includes is left at the base default of empty (MutateEntityHandlerBase.cs:69), because publishing touches no child collection.
  • Why it's built this way: keeping the transition rule in the aggregate and the concurrency policy in the framework leaves the handler with nothing to hold but the four bindings, which is what makes an audit of "which endpoints are conditional" a search for RowVersion overrides.
  • -
  • Where it's used: resolved as ICommandHandler<PublishEventCommand, Result> by the events controller (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:51,276), which evicts the conference:events output-cache tag after a success (:282); registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143); covered by PublishEventHandlerTests (Group 27). Its mirror is UnpublishEventHandler.
  • +
  • Where it's used: resolved as ICommandHandler<PublishEventCommand, Result> by the events controller (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:51,283), which evicts the conference:events output-cache tag after a success (:282); registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); covered by PublishEventHandlerTests (Group 27). Its mirror is UnpublishEventHandler.
  • AddRoomHandler

    @@ -4463,7 +4500,7 @@

    AddRoomHandler

  • Concept introduced, the application-assigned key and the retry that it forces. Follow the chain from AddRoomCommand: Room has no [IdValueGenerated] attribute, because its integer key is the Sessionize room id (MMCA.ADC.Conference.Domain/Events/Room.cs:8-13). There is therefore no database sequence to fall back on for an organizer-created room, and something in the application has to pick a number that Sessionize will never pick. The answer is a reserved band: RoomManualIdRangeStart = 999_999_000 and RoomManualIdRangeEnd = 999_999_999 (MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:66,69), one thousand ids allocated globally across all events. Allocation is read-then-write ("take the max in the band, add one", AddRoomHandler.cs:136-138), which is a race by construction, so the handler pairs it with a bounded retry. The second idea is the attempt-scoped unit of work. A retry cannot reuse the ambient DbContext: it still tracks the failed insert, so a recomputed id would never persist. The framework anticipated this by taking the unit of work as a parameter of MutateCoreAsync (MMCA.Common.Application/UseCases/Crud/MutateEntityHandlerBase.cs:271-276), which is what makes "retry in a fresh scope" a parameter rather than a second copy of the workflow, as the class remarks say (AddRoomHandler.cs:18-22). [Rubric §8, Data Architecture] assesses whether key generation is deliberate and collision-safe: a reserved, exhaustion-checked band with an explicit retry is that answer stated in code. [Rubric §29, Resilience and Business Continuity] assesses whether a foreseeable race is handled rather than surfaced: a lost id race becomes a warning and a retry, not a 500. [Rubric §12, Performance and Scalability]: the allocation query is a filtered, ignoreQueryFilters read over the reserved band only (:130-134), so it never scans the full room table.
  • Walkthrough (fields, then the loop, then the hooks). Four const values carry the policy: MaxManualIdAttempts = 3 (:31), RoomNameIndexName = "IX_Room_EventId_Name" (:37), and two context keys, AttemptUnitOfWorkKey and AddedRoomKey (:44,47), each with a comment stating why it exists. EntityId reads command.EventId (:50) and Includes => [nameof(Event.Rooms)] (:56) is load-bearing for the same reason as in AddEventSpeakerHandler: without it the aggregate's duplicate-name check runs against an empty list. HandleAsync (:59-92) is overridden rather than inherited. An explicit caller id short-circuits to a single attempt with no recomputation, because a collision there is a genuine caller error (:63-66). Otherwise a while (true) loop runs attempt 1 against the injected unit of work (:74-75) and every later attempt inside await using var scope = scopeFactory.CreateAsyncScope() (:82-84), re-running the whole workflow including the event load, since reusing the previous attempt's entity would append a second room and re-raise RoomChanged (:77-81). The catch filter is narrow: when (attempt < MaxManualIdAttempts && IsUniqueKeyViolation(ex)) (:86). AddRoomAttemptAsync (:98-111) creates the MutationContext, stashes the attempt's unit of work in it, calls MutateCoreAsync, and shapes the DTO from what the mutation recorded. MutateAsync (:114-160) reads the attempt's unit of work back out of the context (:120, defaulting to the injected one), allocates the id when command.RoomId is null (:126-144) and fails with Room.ManualIdRangeExhausted if the band is full (:140-141), calls entity.AddRoom(...) (:146-153), and stores the new room under AddedRoomKey (:157). BuildResult maps that stored room to a RoomDTO (:167-168). IsUniqueKeyViolation (:178-190) walks the whole InnerException chain looking for "duplicate key" while excluding any message naming the room-name index, and the reason is spelled out at :170-177: the Application layer cannot reference EF Core types, so detection is message-based, and a name conflict must not be retried because recomputing an id would never clear it. Two [LoggerMessage] partials close the file, an information-level "room added" and a warning-level collision notice (:192-196).
  • Why it's built this way: the retry lives in the handler and not in the framework because the thing being recomputed (the next id in a reserved band) is domain knowledge; the framework contributes only the attempt-scoped workflow that makes re-running it clean. The bound of three attempts is a deliberate ceiling: after that the caller gets the conflict rather than an unbounded loop.
  • -
  • Where it's used: resolved as ICommandHandler<AddRoomCommand, Result<RoomDTO>> by the rooms controller (MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:94,211), whose POST action is marked [Idempotent] so a retried request replays the first response instead of adding a second row (RoomsController.cs:205); registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143); covered by AddRoomHandlerTests (Group 27). The Sessionize import does not go through this handler: RoomSyncStrategy calls the aggregate directly with the upstream id (MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RoomSyncStrategy.cs:112).
  • +
  • Where it's used: resolved as ICommandHandler<AddRoomCommand, Result<RoomDTO>> by the rooms controller (MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:94,219), whose POST action is marked [Idempotent] so a retried request replays the first response instead of adding a second row (RoomsController.cs:205); registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); covered by AddRoomHandlerTests (Group 27). The Sessionize import does not go through this handler: RoomSyncStrategy calls the aggregate directly with the upstream id (MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/RoomSyncStrategy.cs:112).
  • Caveats: the reserved band holds 1,000 manual ids across all events (EventInvariants.cs:65,68), and it is allocated from the maximum in use rather than from a free list, so soft-deleted manual rooms still consume their number. What happens at exhaustion is defined (Room.ManualIdRangeExhausted, AddRoomHandler.cs:141); whether any reclamation exists is not determinable from source.
  • RemoveRoomCommand

    @@ -4475,7 +4512,7 @@

    RemoveRoomCommand

  • Depends on: ICacheInvalidating (RemoveRoomCommand.cs:2, RemoveRoomCommand.cs:11), the Event type for the cache prefix (RemoveRoomCommand.cs:1, RemoveRoomCommand.cs:14), and the EventIdentifierType / RoomIdentifierType aliases.
  • Concept: nothing new; the remove-child shape taught by RemoveEventQuestionAnswerCommand. Rooms are the child family whose identifiers can be externally assigned (Sessionize ids), which makes the soft delete matter more than usual: retiring the row rather than deleting it keeps a later refresh from colliding with a reused key, and the aggregate has an explicit restore path for a room that comes back (MMCA.ADC.Conference.Domain/Events/Event.cs:449-458). [Rubric §15, Best Practices & Code Quality] assesses uniformity: the third identical remove command in the same module is a sign the shape is a convention, so a reader who has understood one has understood all of them.
  • Walkthrough: public sealed record RemoveRoomCommand(EventIdentifierType EventId, RoomIdentifierType RoomId) : ICacheInvalidating (RemoveRoomCommand.cs:9-11) with CachePrefix => $"{typeof(Event).FullName}:" (RemoveRoomCommand.cs:14).
  • -
  • Where it's used: constructed by RoomsController.DeleteAsync as new RemoveRoomCommand(eventId, id) (MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:260-267, handler injected at RoomsController.cs:96), which evicts the conference:rooms output-cache tag and returns 204 No Content (RoomsController.cs:272-273). Handled by RemoveRoomHandler; its add-side counterpart is AddRoomCommand.
  • +
  • Where it's used: constructed by RoomsController.DeleteAsync as new RemoveRoomCommand(eventId, id) (MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:268-275, handler injected at RoomsController.cs:96), which evicts the conference:rooms output-cache tag and returns 204 No Content (RoomsController.cs:272-273). Handled by RemoveRoomHandler; its add-side counterpart is AddRoomCommand.
  • UnpublishEventCommand

    @@ -4486,7 +4523,7 @@

    UnpublishEventCommand

  • Depends on: ICacheInvalidating (UnpublishEventCommand.cs:2, UnpublishEventCommand.cs:13), the Event type for the cache prefix (UnpublishEventCommand.cs:1, UnpublishEventCommand.cs:16), the EventIdentifierType alias, and byte[] from the BCL.
  • Concept introduced, the conditional transition command. RowVersion is declared as a non-nullable byte[], not as an optional parameter with a default (UnpublishEventCommand.cs:13), and the XML comment states the reason: the token is read from the request's If-Match header and a transition that states no precondition never reaches this command (UnpublishEventCommand.cs:8-12). That is enforced at the boundary rather than here: SupportsIfMatchAttribute answers a missing header with 428 Precondition Required before the action body runs (MMCA.Common.API/Concurrency/SupportsIfMatchAttribute.cs:165) and rewrites a downstream conflict to 412 Precondition Failed (SupportsIfMatchAttribute.cs:130-153), which is why the parameter can be required here without a null check anywhere in the slice. [Rubric §6, CQRS and Event-Driven] assesses whether writes are modelled as named intents: "unpublish" is a type, not an IsPublished = false mutation, so the pipeline decorators, the idempotency store and the logs can tell the two directions apart without inspecting a payload field. [Rubric §8, Data Architecture] assesses concurrency control: the token turns the write into a database predicate (ADR-035).
  • Walkthrough: public sealed record UnpublishEventCommand(EventIdentifierType Id, byte[] RowVersion) : ICacheInvalidating (UnpublishEventCommand.cs:13) with CachePrefix => $"{typeof(Event).FullName}:" (UnpublishEventCommand.cs:16). Apart from the name, it is identical to PublishEventCommand (MMCA.ADC.Conference.Application/Events/UseCases/Publish/PublishEventCommand.cs:13-17).
  • -
  • Where it's used: constructed by EventsController.UnpublishAsync as new UnpublishEventCommand(id, rowVersion), where rowVersion comes from SupportsIfMatchAttribute.RequiredToken(HttpContext) (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:224-228, handler injected at EventsController.cs:51). The POST {id}/unpublish endpoint carries [Idempotent] and [SupportsIfMatch] and declares 409, 412 and 428 (EventsController.cs:295-300); on success it evicts the conference:events output-cache tag and returns 204 No Content (EventsController.cs:314-315). Handled by UnpublishEventHandler.
  • +
  • Where it's used: constructed by EventLifecycleController.UnpublishAsync as new UnpublishEventCommand(id, rowVersion), where rowVersion comes from SupportsIfMatchAttribute.RequiredToken(HttpContext) (MMCA.ADC.Conference.API/Controllers/Events/EventLifecycleController.cs:94-97, handler injected at EventLifecycleController.cs:34). The POST {id}/unpublish endpoint carries [Idempotent] and [SupportsIfMatch] and declares 409, 412 and 428 (EventLifecycleController.cs:84-89); on success it evicts the conference:events output-cache tag and returns 204 No Content (EventLifecycleController.cs:103-104). Handled by UnpublishEventHandler.
  • QuestionDTOMapper

    @@ -4515,7 +4552,7 @@

    UpdateEventQuestionAnswerCommand

  • Depends on: ICacheInvalidating (UpdateEventQuestionAnswerCommand.cs:2, UpdateEventQuestionAnswerCommand.cs:13), the Event type for the cache prefix (UpdateEventQuestionAnswerCommand.cs:1, UpdateEventQuestionAnswerCommand.cs:16), and the EventIdentifierType / EventQuestionAnswerIdentifierType aliases.
  • Concept: the remove-child shape plus one payload field. The detail worth noticing is what the record does not carry: no author, no timestamp. Ownership is decided server-side by UpdateEventQuestionAnswerHandler from ICurrentUserService, so a caller cannot claim to be editing on someone else's behalf by shaping the payload. [Rubric §11, Security] assesses whether identity is ambient rather than client-supplied: the absence of an owner field is the enforcement.
  • Walkthrough: public sealed record UpdateEventQuestionAnswerCommand(EventIdentifierType EventId, EventQuestionAnswerIdentifierType EventQuestionAnswerId, string AnswerValue) : ICacheInvalidating (UpdateEventQuestionAnswerCommand.cs:10-13) with CachePrefix => $"{typeof(Event).FullName}:" (UpdateEventQuestionAnswerCommand.cs:16). The payload rules are enforced twice on the way down: UpdateEventQuestionAnswerCommandValidator rejects an empty or over-long value at the boundary, and EventInvariants.EnsureAnswerValueIsValid runs again inside EventQuestionAnswer.UpdateAnswer (MMCA.ADC.Conference.Domain/Events/EventQuestionAnswer.cs:73).
  • -
  • Where it's used: constructed by EventQuestionAnswersController.UpdateAsync as new UpdateEventQuestionAnswerCommand(request.EventId, id, request.AnswerValue), taking the event id from the UpdateEventQuestionAnswerRequest body and the answer id from the route (MMCA.ADC.Conference.API/Controllers/Events/EventQuestionAnswersController.cs:275-286, handler injected at EventQuestionAnswersController.cs:58). Handled by UpdateEventQuestionAnswerHandler.
  • +
  • Where it's used: constructed by EventQuestionAnswersController.UpdateAsync as new UpdateEventQuestionAnswerCommand(request.EventId, id, request.AnswerValue), taking the event id from the UpdateEventQuestionAnswerRequest body and the answer id from the route (MMCA.ADC.Conference.API/Controllers/Events/EventQuestionAnswersController.cs:279-290, handler injected at EventQuestionAnswersController.cs:58). Handled by UpdateEventQuestionAnswerHandler.
  • UpdateRoomCommand

    @@ -4524,9 +4561,9 @@

    UpdateRoomCommand

    • What it is: the full-replacement update for one room inside an event. It is the widest command in the event slice: two ids plus the six room fields (MMCA.ADC.Conference.Application/Events/UseCases/UpdateRoom/UpdateRoomCommand.cs:15-23).
    • Depends on: ICacheInvalidating (UpdateRoomCommand.cs:2, UpdateRoomCommand.cs:23); the Event type, referenced only for the cache prefix (UpdateRoomCommand.cs:1, UpdateRoomCommand.cs:26); the EventIdentifierType and RoomIdentifierType aliases.
    • -
    • Concept reinforced, the whole-object update command: [Rubric §9, API and Contract Design] assesses whether a mutation contract is unambiguous. Every optional field is declared nullable (Capacity, Floor, Location, AccessibilityInfo, UpdateRoomCommand.cs:20-23) and is passed through to the domain as sent (MMCA.ADC.Conference.Domain/Events/Event.cs:440), so omitting one clears it rather than leaving it alone. That is the defining property of a PUT-shaped command, and it is why RoomsController binds it from a complete UpdateRoomRequest body (MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:236). [Rubric §21, Accessibility] is touched at the data level: AccessibilityInfo (UpdateRoomCommand.cs:23) carries a room's accessibility notes to attendees, so the write path preserves that information as a first-class member rather than folding it into free text.
    • +
    • Concept reinforced, the whole-object update command: [Rubric §9, API and Contract Design] assesses whether a mutation contract is unambiguous. Every optional field is declared nullable (Capacity, Floor, Location, AccessibilityInfo, UpdateRoomCommand.cs:20-23) and is passed through to the domain as sent (MMCA.ADC.Conference.Domain/Events/Event.cs:440), so omitting one clears it rather than leaving it alone. That is the defining property of a PUT-shaped command, and it is why RoomsController binds it from a complete UpdateRoomRequest body (MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:244). [Rubric §21, Accessibility] is touched at the data level: AccessibilityInfo (UpdateRoomCommand.cs:23) carries a room's accessibility notes to attendees, so the write path preserves that information as a first-class member rather than folding it into free text.
    • Walkthrough: a sealed record with eight positional parameters (UpdateRoomCommand.cs:15-23), each documented individually (UpdateRoomCommand.cs:7-14), and the single CachePrefix member keyed on the Event type name (UpdateRoomCommand.cs:26). The prefix is the parent's, not the room's, because a room is a child of the event aggregate and every cached read that could contain it is keyed under Event. Note what is absent: no RowVersion, so unlike UnpublishEventCommand a room edit is last-write-wins.
    • -
    • Where it's used: constructed by RoomsController's PUT /Rooms/{id} action (MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:234-249, handler injected at RoomsController.cs:95), which evicts the conference:rooms output-cache tag and returns 204 No Content on success (RoomsController.cs:254-255); validated by UpdateRoomCommandValidator; handled by UpdateRoomHandler.
    • +
    • Where it's used: constructed by RoomsController's PUT /Rooms/{id} action (MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:242-257, handler injected at RoomsController.cs:95), which evicts the conference:rooms output-cache tag and returns 204 No Content on success (RoomsController.cs:254-255); validated by UpdateRoomCommandValidator; handled by UpdateRoomHandler.

    RemoveEventQuestionAnswerCommand

    @@ -4535,10 +4572,10 @@

    RemoveEventQuestionAnswerCommand

    • What it is: the intent to remove one EventQuestionAnswer from an Event. Two positional parameters, the owning EventId and the EventQuestionAnswerId (MMCA.ADC.Conference.Application/Events/UseCases/RemoveEventQuestionAnswer/RemoveEventQuestionAnswerCommand.cs:9-11).
    • Depends on: ICacheInvalidating (RemoveEventQuestionAnswerCommand.cs:2, RemoveEventQuestionAnswerCommand.cs:11), the Event type for the cache prefix (RemoveEventQuestionAnswerCommand.cs:1, RemoveEventQuestionAnswerCommand.cs:14), and the EventIdentifierType / EventQuestionAnswerIdentifierType aliases (ADR-048).
    • -
    • Concept introduced, the remove-child command shape. Every child removal in this module is the same two-identifier record: the aggregate root first, the child second, nothing else. Naming the root is not redundant. The write has to travel through the aggregate so the invariant checks and the EventQuestionAnswerChanged domain event fire, so the handler loads the Event and calls a method on it rather than deleting a row by id. The removal itself is a soft delete: the framework's RemoveChildOrNotFound resolves the active child and calls its Delete() (MMCA.Common.Domain/Entities/AuditableAggregateRootEntity.cs:163, AuditableAggregateRootEntity.cs:171), which sets IsDeleted = true and fails with Error.AlreadyDeleted if the row was already retired (MMCA.Common.Domain/Entities/AuditableBaseEntity.cs:67-80). That is ADR-005 applied to a child entity. [Rubric §4, Domain-Driven Design] assesses whether children are mutated through their root: the command's shape makes that structurally unavoidable. [Rubric §8, Data Architecture] assesses deletion policy: rows are retired, not destroyed, and the EF global query filter hides them from every later read.
    • +
    • Concept introduced, the remove-child command shape. Every child removal in this module is the same two-identifier record: the aggregate root first, the child second, nothing else. Naming the root is not redundant. The write has to travel through the aggregate so the invariant checks and the EventQuestionAnswerChanged domain event fire, so the handler loads the Event and calls a method on it rather than deleting a row by id. The removal itself is a soft delete: the framework's RemoveChildOrNotFound resolves the active child and calls its Delete() (MMCA.Common.Domain/Entities/AuditableAggregateRootEntity.cs:165, AuditableAggregateRootEntity.cs:171), which sets IsDeleted = true and fails with Error.AlreadyDeleted if the row was already retired (MMCA.Common.Domain/Entities/AuditableBaseEntity.cs:67-80). That is ADR-005 applied to a child entity. [Rubric §4, Domain-Driven Design] assesses whether children are mutated through their root: the command's shape makes that structurally unavoidable. [Rubric §8, Data Architecture] assesses deletion policy: rows are retired, not destroyed, and the EF global query filter hides them from every later read.
    • Walkthrough: a sealed record with EventId and EventQuestionAnswerId (RemoveEventQuestionAnswerCommand.cs:9-11), plus CachePrefix => $"{typeof(Event).FullName}:" (RemoveEventQuestionAnswerCommand.cs:14), keyed on the root because a cached event read carries its answers with it. CachingCommandDecorator<TCommand, TResult> acts on that prefix only after the inner handler succeeded, scopes it to the calling tenant, and evicts with CancellationToken.None so the cleanup outlives a caller that walked away (MMCA.Common.Application/UseCases/Decorators/CachingCommandDecorator.cs:61-74).
    • -
    • Why it's built this way: keeping the delete as an aggregate operation rather than a repository-level ExecuteDelete preserves domain events, audit stamping and soft-delete semantics, all three of which a bulk delete bypasses, as the repository contract warns in as many words (MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:445-449).
    • -
    • Where it's used: constructed by EventQuestionAnswersController.DeleteAsync as new RemoveEventQuestionAnswerCommand(eventId, id), with the event id taken from the query string and the answer id from the route (MMCA.ADC.Conference.API/Controllers/Events/EventQuestionAnswersController.cs:291-298, handler injected at EventQuestionAnswersController.cs:59). Handled by RemoveEventQuestionAnswerHandler, which adds the ownership rule the record does not express.
    • +
    • Why it's built this way: keeping the delete as an aggregate operation rather than a repository-level ExecuteDelete preserves domain events, audit stamping and soft-delete semantics, all three of which a bulk delete bypasses, as the repository contract warns in as many words (MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:447-451).
    • +
    • Where it's used: constructed by EventQuestionAnswersController.DeleteAsync as new RemoveEventQuestionAnswerCommand(eventId, id), with the event id taken from the query string and the answer id from the route (MMCA.ADC.Conference.API/Controllers/Events/EventQuestionAnswersController.cs:295-302, handler injected at EventQuestionAnswersController.cs:59). Handled by RemoveEventQuestionAnswerHandler, which adds the ownership rule the record does not express.
    • Caveats / not-in-source: unlike UnpublishEventCommand, this command carries no RowVersion, so a child removal is not a conditional write and a concurrent edit of the same answer is last-write-wins.

    RemoveEventSpeakerCommand

    @@ -4550,7 +4587,7 @@

    RemoveEventSpeakerCommand

  • Depends on: ICacheInvalidating (MMCA.ADC.Conference.Application/Events/UseCases/RemoveEventSpeaker/RemoveEventSpeakerCommand.cs:2, RemoveEventSpeakerCommand.cs:11), the Event type for the cache prefix (RemoveEventSpeakerCommand.cs:1, RemoveEventSpeakerCommand.cs:14), and the EventIdentifierType / EventSpeakerIdentifierType aliases.
  • Concept: nothing new; the remove-child shape taught by RemoveEventQuestionAnswerCommand. What it demonstrates is why link entities are worth having: removing a speaker from an event is a soft delete of one join row, so the speaker keeps existing, keeps its own identity, and can be linked to another edition of the conference. [Rubric §4, Domain-Driven Design] assesses aggregate boundaries: the association belongs to the event, the speaker is its own aggregate, and this command can only reach the former.
  • Walkthrough: public sealed record RemoveEventSpeakerCommand(EventIdentifierType EventId, EventSpeakerIdentifierType EventSpeakerId) : ICacheInvalidating (RemoveEventSpeakerCommand.cs:9-11) with CachePrefix => $"{typeof(Event).FullName}:" (RemoveEventSpeakerCommand.cs:14).
  • -
  • Where it's used: constructed by EventSpeakersController.DeleteAsync as new RemoveEventSpeakerCommand(eventId, id) (MMCA.ADC.Conference.API/Controllers/Events/EventSpeakersController.cs:186-193, handler injected at EventSpeakersController.cs:50); the same action then evicts three output-cache tags, conference:events, conference:speakers and conference (EventSpeakersController.cs:200). Handled by RemoveEventSpeakerHandler; its add-side counterpart is AddEventSpeakerCommand.
  • +
  • Where it's used: constructed by EventSpeakersController.DeleteAsync as new RemoveEventSpeakerCommand(eventId, id) (MMCA.ADC.Conference.API/Controllers/Events/EventSpeakersController.cs:194-201, handler injected at EventSpeakersController.cs:50); the same action then evicts three output-cache tags, conference:events, conference:speakers and conference (EventSpeakersController.cs:200). Handled by RemoveEventSpeakerHandler; its add-side counterpart is AddEventSpeakerCommand.
  • UpdateEventQuestionAnswerCommandValidator

    @@ -4559,10 +4596,10 @@

    UpdateEventQuestionAnswerComm
    • What it is: the FluentValidation validator for UpdateEventQuestionAnswerCommand. One chained rule on AnswerValue: present, and no longer than the ceiling any answer type can reach (MMCA.ADC.Conference.Application/Events/UseCases/UpdateEventQuestionAnswer/UpdateEventQuestionAnswerCommandValidator.cs:19-25).
    • Depends on: FluentValidation's AbstractValidator<T> (NuGet, UpdateEventQuestionAnswerCommandValidator.cs:1, UpdateEventQuestionAnswerCommandValidator.cs:16) and QuestionInvariants for the length constant (UpdateEventQuestionAnswerCommandValidator.cs:2, UpdateEventQuestionAnswerCommandValidator.cs:23).
    • -
    • Concept introduced, splitting a business rule by what the boundary can know. BR-124 says an answer must match its question's type: a Rating is an integer 1 to 5, an Email is a valid address, a Text answer is at most 2000 characters (MMCA.ADC.Conference.Domain/Questions/QuestionInvariants.cs:110-129). A validator sees only the command, and the command does not carry the question's type, so only the type-independent half of the rule can live here. The class comment says exactly that and names where the rest lives (UpdateEventQuestionAnswerCommandValidator.cs:10-15). [Rubric §24, Forms, Validation and UX Safety] assesses whether bad input is rejected before business logic runs with a message a client can act on: both failures carry a human message and a stable dotted code, EventQuestionAnswer.AnswerValue.Required and EventQuestionAnswer.AnswerValue.TooLong (UpdateEventQuestionAnswerCommandValidator.cs:22, UpdateEventQuestionAnswerCommandValidator.cs:25). [Rubric §6, CQRS & Event-Driven Design] assesses whether that happens uniformly: the handler never calls this class. ValidatingCommandDecorator<TCommand, TResult> runs every registered validator for the command type before the transactional decorator opens a transaction (ADR-014), so a malformed answer costs no database work. [Rubric §4, Domain-Driven Design] assesses rule placement: the ceiling here is the same constant the domain uses, borrowed rather than restated.
    • +
    • Concept introduced, splitting a business rule by what the boundary can know. BR-124 says an answer must match its question's type: a Rating is an integer 1 to 5, an Email is a valid address, a Text answer is at most 2000 characters (MMCA.ADC.Conference.Domain/Questions/QuestionInvariants.cs:114-133). A validator sees only the command, and the command does not carry the question's type, so only the type-independent half of the rule can live here. The class comment says exactly that and names where the rest lives (UpdateEventQuestionAnswerCommandValidator.cs:10-15). [Rubric §24, Forms, Validation and UX Safety] assesses whether bad input is rejected before business logic runs with a message a client can act on: both failures carry a human message and a stable dotted code, EventQuestionAnswer.AnswerValue.Required and EventQuestionAnswer.AnswerValue.TooLong (UpdateEventQuestionAnswerCommandValidator.cs:22, UpdateEventQuestionAnswerCommandValidator.cs:25). [Rubric §6, CQRS & Event-Driven Design] assesses whether that happens uniformly: the handler never calls this class. ValidatingCommandDecorator<TCommand, TResult> runs every registered validator for the command type before the transactional decorator opens a transaction (ADR-014), so a malformed answer costs no database work. [Rubric §4, Domain-Driven Design] assesses rule placement: the ceiling here is the same constant the domain uses, borrowed rather than restated.
    • Walkthrough: public sealed class UpdateEventQuestionAnswerCommandValidator : AbstractValidator<UpdateEventQuestionAnswerCommand> (UpdateEventQuestionAnswerCommandValidator.cs:16) with an expression-bodied constructor (UpdateEventQuestionAnswerCommandValidator.cs:18). RuleFor(x => x.AnswerValue) chains .NotEmpty() with the message "Answer value is required." (:19-22) and .MaximumLength(QuestionInvariants.TextAnswerMaxLength) (:23), whose message interpolates the same constant so the text cannot drift from the limit (:24). The constant is 2000 (MMCA.ADC.Conference.Domain/Questions/QuestionInvariants.cs:28), the identical value ValidateTextAnswer enforces when the question type is known (QuestionInvariants.cs:145-157).
    • Why it's built this way: the type-specific half of BR-124 needs the owning question, which is a database read, so it runs in the handler that has the entity in hand rather than in the validator (UpdateEventQuestionAnswerCommandValidator.cs:12-15). Keeping the boundary rule cheap is the point: the validator only reads the message.
    • -
    • Where it's used: discovered by the module's convention scan, services.ScanModuleApplicationServices<ClassReference>() (MMCA.ADC.Conference.Application/DependencyInjection.cs:143), and executed by ValidatingCommandDecorator<TCommand, TResult> ahead of UpdateEventQuestionAnswerHandler.
    • +
    • Where it's used: discovered by the module's convention scan, services.ScanModuleApplicationServices<ClassReference>() (MMCA.ADC.Conference.Application/DependencyInjection.cs:139), and executed by ValidatingCommandDecorator<TCommand, TResult> ahead of UpdateEventQuestionAnswerHandler.
    • Caveats / not-in-source: two asymmetries are visible in source and unexplained by it. First, the add-side counterpart it says it mirrors, AddEventQuestionAnswerCommandValidator, declares only the NotEmpty rule and no length ceiling (MMCA.ADC.Conference.Application/Events/UseCases/AddEventQuestionAnswer/AddEventQuestionAnswerCommandValidator.cs:10-14), so the 2000-character boundary check exists on the update path only. Second, the type-specific BR-124 check is called on the add path (MMCA.ADC.Conference.Application/Events/UseCases/AddEventQuestionAnswer/AddEventQuestionAnswerHandler.cs:78) but not by UpdateEventQuestionAnswerHandler; on the update path the deepest remaining guard is EventInvariants.EnsureAnswerValueIsValid, whose ceiling is the wider AnswerValueMaxLength of 4000 (MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:59, EventInvariants.cs:139-142). No test in the repository references this validator by name.

    UpdateRoomCommandValidator

    @@ -4575,7 +4612,7 @@

    UpdateRoomCommandValidator

  • Concept reinforced, rule composition with Include: [Rubric §15, Best Practices & Code Quality] assesses whether one constraint is written once: rather than restate the room constraints in the add validator and again here, both Include the same generic rule sets, parameterized by a property selector. Include merges the included validator's rules in as though they had been declared inline, so composition costs nothing at validation time. [Rubric §24, Forms, Validation and UX Safety] covers what those rules produce: each carries a human message and a stable error code, for example Room.Name.Required and Room.Name.MaxLength (MMCA.ADC.Conference.Application/Events/Validation/RoomValidationRules.cs:18-19), so a client can branch on the code instead of parsing English. The ceilings come from the domain, not from the validator: EventInvariants.RoomNameMaxLength, RoomFloorMaxLength, RoomLocationMaxLength and RoomAccessibilityInfoMaxLength (MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:47, EventInvariants.cs:49, EventInvariants.cs:52, EventInvariants.cs:55) forward to the shared DTO constants 255, 100, 255 and 500 (MMCA.ADC.Conference.Shared/Rooms/RoomDTO.cs:16, RoomDTO.cs:19, RoomDTO.cs:22, RoomDTO.cs:25), which are the same numbers the domain invariant enforces (EventInvariants.cs:137), so the form limit, the API limit and the domain limit cannot drift apart.
  • Walkthrough: a sealed class whose whole body is a six-line constructor (UpdateRoomCommandValidator.cs:9-17). Four of the six rule sets contribute no rule bodies of their own, they bind a framework base to one field: RoomSortRules<T> subclasses NonNegativeIntRules<T> with the code Room.Sort.Negative (RoomValidationRules.cs:26-31), and RoomFloorRules<T>, RoomLocationRules<T> and RoomAccessibilityInfoRules<T> each subclass OptionalStringRules<T> with a display name, a ceiling and a code, so a null value passes and only an over-long one fails (RoomValidationRules.cs:51-56, RoomValidationRules.cs:63-68, RoomValidationRules.cs:75-80). The two that write their own chain are RoomNameRules<T>, required plus max length (RoomValidationRules.cs:13-19), and RoomCapacityRules<T>, which demands a capacity greater than zero but only when one was supplied, via .When(x => selector.Compile()(x) is not null) (RoomValidationRules.cs:37-44).
  • Why it's built this way: extracting field rules into shared generic classes is the module-wide convention; add a constraint once and every command that includes the rule set picks it up, which is why AddRoomCommandValidator includes the identical six. Running them ahead of the transaction is the pipeline's job: ValidatingCommandDecorator<TCommand, TResult> sits outside TransactionalCommandDecorator<TCommand, TResult> (ADR-014), so a malformed room never opens a database transaction.
  • -
  • Where it's used: discovered by the module's validator scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143) and run by the validating decorator ahead of UpdateRoomHandler. Covered by UpdateRoomCommandValidatorTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/Validation/CommandValidatorTests.cs:116-118).
  • +
  • Where it's used: discovered by the module's validator scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139) and run by the validating decorator ahead of UpdateRoomHandler. Covered by UpdateRoomCommandValidatorTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/Validation/CommandValidatorTests.cs:116-118).
  • Caveats / not-in-source: name uniqueness within an event is not validated here. It cannot be: the rule needs the event's other rooms, so it lives in the aggregate as EnsureRoomNameIsUnique and comes back as the invariant error Event.Room.Duplicate (MMCA.ADC.Conference.Domain/Events/Event.cs:695-712, code at Event.cs:680).
  • UnpublishEventHandler

    @@ -4588,22 +4625,9 @@

    UnpublishEventHandler

  • Concept introduced, stamping a client token as the tracked entity's original value. This is the application-side half of ADR-035, and in this handler it is one line: protected override byte[]? RowVersion(UnpublishEventCommand command) => command.RowVersion (UnpublishEventHandler.cs:25), with the reason stated inline just above (:21-22). The workflow then calls repository.SetOriginalRowVersion(entity, rowVersion) when the reported token is non-empty (MMCA.Common.Application/UseCases/Crud/MutateEntityHandlerBase.cs:292-294), and the repository writes that value into EF's change tracker as the RowVersion property's original value (MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:75-83), so the UPDATE EF emits carries WHERE RowVersion = @clientToken. If someone else touched the row, zero rows match, EF raises DbUpdateConcurrencyException, and because the endpoint is decorated with SupportsIfMatchAttribute the resulting conflict is rewritten to 412 Precondition Failed (MMCA.Common.API/Concurrency/SupportsIfMatchAttribute.cs:130-153). [Rubric §8, Data Architecture] assesses concurrency control: the arbitration is a database predicate, not an application-level compare. [Rubric §12, Performance and Scalability] assesses contention: optimistic concurrency takes no locks, so simultaneous readers are never blocked and only the losing writer pays. [Rubric §6, CQRS and Event-Driven] assesses intent modelling: the reverse transition is its own command and its own handler, so both directions are separately auditable and separately loggable.
  • Walkthrough: primary constructor and base call (UnpublishEventHandler.cs:13-16); EntityId(command) => command.Id (:19); the RowVersion override (:25); MutateAsync returning Task.FromResult(entity.Unpublish()) (:28-32); LogMutated forwarding to the generated LogEventUnpublished (:35-36, declared :38-39). No Includes override, so the base issues a by-id load with no eager-loaded navigations (MMCA.Common.Application/UseCases/Crud/MutateEntityHandlerBase.cs:70, :158): the transition touches only the root's own flag, so loading the children would be wasted I/O. The decision itself is the aggregate's: Event.Unpublish fails with the invariant Event.AlreadyUnpublished when the flag is already clear, otherwise sets IsPublished = false and raises EventChanged with DomainEntityState.Updated (MMCA.ADC.Conference.Domain/Events/Event.cs:319-335).
  • Why it's built this way: unpublishing is the direction where the stale-view guard earns its keep. Hiding an event a colleague has just re-published, based on a page rendered minutes ago, is precisely the mistake ADR-035 converts into a 412 rather than a silent overwrite. Returning the aggregate's Result unchanged preserves the Event.AlreadyUnpublished code all the way to the HTTP response instead of flattening it into a generic 400.
  • -
  • Where it's used: dispatched by EventsController.UnpublishAsync (MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:52, EventsController.cs:301-316), which evicts the conference:events output-cache tag and returns 204 No Content on success (EventsController.cs:314-315). Covered by UnpublishEventHandlerTests.
  • +
  • Where it's used: dispatched by EventLifecycleController.UnpublishAsync (MMCA.ADC.Conference.API/Controllers/Events/EventLifecycleController.cs:34, EventLifecycleController.cs:90-105), which evicts the conference:events output-cache tag and returns 204 No Content on success (EventLifecycleController.cs:103-104). Covered by UnpublishEventHandlerTests.
  • Caveats / not-in-source: unpublishing changes what non-privileged readers can see, but nothing in this handler evicts a cached read directly. Distributed-cache eviction is the caching decorator's job, driven by the CachePrefix on UnpublishEventCommand (MMCA.Common.Application/UseCases/Decorators/CachingCommandDecorator.cs:61-74), and the separate ASP.NET output cache is evicted by the controller.
  • -

    UpdateEventQuestionAnswerHandler

    -
    -

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Events.UseCases.UpdateEventQuestionAnswer · MMCA.ADC.Conference.Application/Events/UseCases/UpdateEventQuestionAnswer/UpdateEventQuestionAnswerHandler.cs:21 · Level 10 · class (sealed partial)

    -
    -
      -
    • What it is: the handler for UpdateEventQuestionAnswerCommand. It applies the same BR-52/BR-53 ownership gate as its remove twin and then asks the aggregate to change the answer text (MMCA.ADC.Conference.Application/Events/UseCases/UpdateEventQuestionAnswer/UpdateEventQuestionAnswerHandler.cs:11-14).
    • -
    • Depends on: MutateEntityHandlerBase<TCommand, TEntity, TIdentifierType> closed over the command, Event and EventIdentifierType (UpdateEventQuestionAnswerHandler.cs:25); IUnitOfWork (:21); ICurrentUserService (:22); ILogger<UpdateEventQuestionAnswerHandler> (:23); RoleNames (:6, :43); the EventQuestionAnswer child type; Result / Error.
    • -
    • Concept: nothing new; the template method taught by RemoveEventQuestionAnswerHandler and the row-level ownership check taught there too, reproduced line for line with "You can only update your own answers." in place of the delete message (:45-49). Reading the two side by side is the fastest way to see that the rule is a policy about the caller and the row, not about the verb. [Rubric §11, Security] assesses object-level authorization: identity comes from the ambient principal and the owner comes from the persisted audit field, never from the command. [Rubric §4, Domain-Driven Design] assesses invariant placement: the text rule stays on the entity, so no future caller can bypass it by writing a new handler.
    • -
    • Walkthrough: primary constructor and base call (UpdateEventQuestionAnswerHandler.cs:21-25); Includes => [nameof(Event.EventQuestionAnswers)] (:27); EntityId(command) => command.EventId (:30); MutateAsync (:33-55) null-guards (:38-39), finds the candidate answer (:42), applies the ownership gate returning Error.Forbidden with the code EventQuestionAnswer.NotOwner (:43-50), then returns entity.UpdateEventQuestionAnswer(command.EventQuestionAnswerId, command.AnswerValue) (:52-54). The aggregate re-resolves the child through GetEventQuestionAnswerOrNotFound (MMCA.ADC.Conference.Domain/Events/Event.cs:647, helper at Event.cs:692-695), delegates to answer.UpdateAnswer(...) which runs EventInvariants.EnsureAnswerValueIsValid before assigning (MMCA.ADC.Conference.Domain/Events/EventQuestionAnswer.cs:71-80), and raises EventQuestionAnswerChanged with DomainEntityState.Updated only after both pass (Event.cs:629). LogMutated (:58-59) fires only after the commit, through the generated LogEventQuestionAnswerUpdated (:61-62).
    • -
    • Why it's built this way: the ownership predicate is duplicated between this handler and the remove handler rather than hoisted into a shared helper. With one condition and one message each, the two slices stay independently readable and independently changeable, which is the trade the vertical-slice layout makes on purpose ([Rubric §5, Vertical Slice]).
    • -
    • Where it's used: registered by the module scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143); dispatched by EventQuestionAnswersController.UpdateAsync on PUT {id} (MMCA.ADC.Conference.API/Controllers/Events/EventQuestionAnswersController.cs:82, EventQuestionAnswersController.cs:163-174), which returns 204 No Content on success. Covered by UpdateEventQuestionAnswerHandlerTests.
    • -
    • Caveats / not-in-source: the type-specific half of BR-124 is not applied on this path. QuestionInvariants.EnsureAnswerValueMatchesQuestionType is called by the add-side handler (MMCA.ADC.Conference.Application/Events/UseCases/AddEventQuestionAnswer/AddEventQuestionAnswerHandler.cs:78) but nowhere in this file, so an update is checked only against the boundary rule in UpdateEventQuestionAnswerCommandValidator and the type-agnostic domain guard. Nothing in source states whether that is deliberate. As in the remove twin, currentUserService.UserId!.Value (:43) assumes an authenticated caller, and the log line records only the answer id (:61-62), so the audit trail for "who changed this text" lives in the row's LastModifiedBy stamp rather than in the log.
    • -

    UpdateRoomHandler

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Events.UseCases.UpdateRoom · MMCA.ADC.Conference.Application/Events/UseCases/UpdateRoom/UpdateRoomHandler.cs:13 · Level 10 · class (sealed partial)

    @@ -4614,7 +4638,7 @@

    UpdateRoomHandler

  • Concept reinforced, the handler as a pass-through to the root: [Rubric §4, Domain-Driven Design] assesses where the rules live, and this handler is the clearest example in the unit of them living elsewhere. It re-checks nothing UpdateRoomCommandValidator already checked and decides nothing the aggregate decides. Event.UpdateRoom (MMCA.ADC.Conference.Domain/Events/Event.cs:422-447) resolves the room or returns not-found (Event.cs:404-406), enforces name uniqueness excluding the room being edited (Event.cs:409-411), forwards to room.Update(...) for the field-level invariants (Event.cs:413-415), and raises RoomChanged with DomainEntityState.Updated only after all three pass (Event.cs:417). [Rubric §3, Clean Architecture] assesses dependency direction: the handler touches abstractions only, with no EF type in sight.
  • Walkthrough: primary constructor and base call (UpdateRoomHandler.cs:13-16); Includes => [nameof(Event.Rooms)] (:19), which is what makes the aggregate's uniqueness scan see the sibling rooms; EntityId(command) => command.EventId (:22); MutateAsync forwarding the six fields positionally to entity.UpdateRoom(...) inside Task.FromResult (:25-36); LogMutated calling the generated LogRoomUpdated with both ids (:39-40, declared :42-43).
  • Why it's built this way: the uniqueness rule is the reason the whole Rooms collection is loaded for what looks like a single-row edit. It is an aggregate-scoped invariant, so it can only be answered with the aggregate in hand; pushing it to a database index alone would surface as an opaque constraint violation instead of the typed Event.Room.Duplicate error (MMCA.ADC.Conference.Domain/Events/Event.cs:707).
  • -
  • Where it's used: registered by the module scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143); invoked through the decorator pipeline by RoomsController's PUT /Rooms/{id} action (MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:95, RoomsController.cs:234-256), which returns 204 No Content and evicts the conference:rooms output cache on success. Covered by UpdateRoomHandlerTests.
  • +
  • Where it's used: registered by the module scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); invoked through the decorator pipeline by RoomsController's PUT /Rooms/{id} action (MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:95, RoomsController.cs:234-256), which returns 204 No Content and evicts the conference:rooms output cache on success. Covered by UpdateRoomHandlerTests.
  • Caveats / not-in-source: no RowVersion override, so the base skips the concurrency stamp (MMCA.Common.Application/UseCases/Crud/MutateEntityHandlerBase.cs:91, :290-291) and two organizers editing the same room concurrently are resolved last-write-wins, unlike the publish and unpublish transitions.
  • RemoveEventQuestionAnswerHandler

    @@ -4635,7 +4659,7 @@

    RemoveEventQuestionAnswerHandler

  • Why it's built this way: putting the gate inside MutateAsync rather than before the load is what makes it correct. The check needs the persisted CreatedBy, and a refusal there short-circuits the workflow before SaveChangesAsync is ever reached (MMCA.Common.Application/UseCases/Crud/MutateEntityHandlerBase.cs:298-300), so a forbidden delete opens no write.
  • -
  • Where it's used: registered by the module scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143) and dispatched through the decorator pipeline by EventQuestionAnswersController.DeleteAsync (MMCA.ADC.Conference.API/Controllers/Events/EventQuestionAnswersController.cs:83, EventQuestionAnswersController.cs:179-190), which returns 204 No Content on success. Covered by RemoveEventQuestionAnswerHandlerTests.
  • +
  • Where it's used: registered by the module scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139) and dispatched through the decorator pipeline by EventQuestionAnswersController.DeleteAsync (MMCA.ADC.Conference.API/Controllers/Events/EventQuestionAnswersController.cs:83, EventQuestionAnswersController.cs:179-190), which returns 204 No Content on success. Covered by RemoveEventQuestionAnswerHandlerTests.
  • Caveats / not-in-source: this remove derives from the general MutateEntityHandlerBase<TCommand, TEntity, TIdentifierType> while its two siblings below use RemoveChildEntityHandlerBase<TCommand, TParent, TIdentifierType>; nothing in source states why. The two bases differ only in that the child base makes Includes a required override (MMCA.Common.Application/UseCases/Crud/ChildEntityHandlerBase.cs:152), and this handler overrides it anyway. Also, currentUserService.UserId!.Value (:43) assumes an authenticated caller, and the ownership gate is skipped entirely when no matching active answer is found (:43), leaving the not-found decision to the aggregate.
  • RemoveRoomHandler

    @@ -4647,7 +4671,7 @@

    RemoveRoomHandler

  • Depends on: RemoveChildEntityHandlerBase<TCommand, TParent, TIdentifierType> closed over the command, Event and EventIdentifierType (MMCA.ADC.Conference.Application/Events/UseCases/RemoveRoom/RemoveRoomHandler.cs:16); IUnitOfWork (:14); ILogger<RemoveRoomHandler> (:15); the Room child type; Result.
  • Concept: nothing new; the remove-child template taught by RemoveEventSpeakerHandler. The thing worth carrying away is what a soft delete means for a room specifically: sessions scheduled into it keep referencing a row that still exists, so history stays readable rather than turning into dangling identifiers (ADR-005). [Rubric §8, Data Architecture] assesses referential integrity under deletion: retiring the row keeps every prior reference resolvable.
  • Walkthrough: primary constructor and base call (RemoveRoomHandler.cs:13-16); the required Includes => [nameof(Event.Rooms)] (:19); EntityId(command) => command.EventId (:22); MutateAsync returning Task.FromResult(entity.RemoveRoom(command.RoomId)) (:25-29); LogMutated calling the generated LogRoomRemoved with both ids (:32-33, declared :35-36). Event.RemoveRoom (MMCA.ADC.Conference.Domain/Events/Event.cs:511-521) delegates to RemoveChildOrNotFound (Event.cs:486) and raises RoomChanged with DomainEntityState.Deleted (Event.cs:491), which the outbox picks up in the same transaction as the soft delete (ADR-003).
  • -
  • Where it's used: registered by the module scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143); dispatched by RoomsController.DeleteAsync, which takes the room id from the route and the event id from the query string (MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:96, RoomsController.cs:260-273) and evicts the conference:rooms output cache before returning 204 No Content. Covered by RemoveRoomHandlerTests.
  • +
  • Where it's used: registered by the module scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); dispatched by RoomsController.DeleteAsync, which takes the room id from the route and the event id from the query string (MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:96, RoomsController.cs:260-273) and evicts the conference:rooms output cache before returning 204 No Content. Covered by RemoveRoomHandlerTests.
  • Caveats / not-in-source: the room name-uniqueness rule excludes soft-deleted rooms from its scan (MMCA.ADC.Conference.Domain/Events/Event.cs:700-703), so a name freed by this handler becomes reusable immediately, and a room that comes back through a Sessionize refresh goes through the aggregate's separate restore path rather than a new insert (Event.cs:422-431).
  • RemoveEventSpeakerHandler

    @@ -4658,9 +4682,22 @@

    RemoveEventSpeakerHandler

  • What it is: the handler for RemoveEventSpeakerCommand. It is the remove-child template with nothing added: load the aggregate with its speakers, delegate, save, log.
  • Depends on: RemoveChildEntityHandlerBase<TCommand, TParent, TIdentifierType> closed over the command, Event and EventIdentifierType (MMCA.ADC.Conference.Application/Events/UseCases/RemoveEventSpeaker/RemoveEventSpeakerHandler.cs:16); IUnitOfWork (:14); ILogger<RemoveEventSpeakerHandler> (:15); Result.
  • Concept introduced, the child-remove base that makes one override mandatory. RemoveChildEntityHandlerBase<TCommand, TParent, TIdentifierType> adds no workflow of its own: it derives from MutateEntityHandlerBase<TCommand, TEntity, TIdentifierType> and re-declares Includes as abstract override (MMCA.Common.Application/UseCases/Crud/ChildEntityHandlerBase.cs:143-153). That single change converts a bug class into a compile error: a remove whose parent was loaded without the child collection scans an empty list and reports a wrong not-found, so the base refuses to let a subclass forget the include (ChildEntityHandlerBase.cs:128-133). [Rubric §1, SOLID] assesses the Liskov relationship between the two bases: the child base narrows the contract by removing a default, and adds no behavior. [Rubric §15, Best Practices and Code Quality] assesses whether correctness is pushed to the compiler rather than to review.
  • -
  • Walkthrough: primary constructor and base call (RemoveEventSpeakerHandler.cs:13-16); the required Includes => [nameof(Event.EventSpeakers)] (:19); EntityId(command) => command.EventId (:22); MutateAsync returning Task.FromResult(entity.RemoveEventSpeaker(command.EventSpeakerId)) (:25-29); LogMutated calling the generated LogSpeakerRemovedFromEvent with the speaker and event ids (:32-33, declared :35-36). The aggregate resolves the child through the framework's RemoveChildOrNotFound, which finds the active child, calls its Delete() and propagates that failure if the row was already retired (MMCA.Common.Domain/Entities/AuditableAggregateRootEntity.cs:156-178), then raises EventSpeakerChanged with DomainEntityState.Deleted (MMCA.ADC.Conference.Domain/Events/Event.cs:592-603).
  • +
  • Walkthrough: primary constructor and base call (RemoveEventSpeakerHandler.cs:13-16); the required Includes => [nameof(Event.EventSpeakers)] (:19); EntityId(command) => command.EventId (:22); MutateAsync returning Task.FromResult(entity.RemoveEventSpeaker(command.EventSpeakerId)) (:25-29); LogMutated calling the generated LogSpeakerRemovedFromEvent with the speaker and event ids (:32-33, declared :35-36). The aggregate resolves the child through the framework's RemoveChildOrNotFound, which finds the active child, calls its Delete() and propagates that failure if the row was already retired (MMCA.Common.Domain/Entities/AuditableAggregateRootEntity.cs:158-180), then raises EventSpeakerChanged with DomainEntityState.Deleted (MMCA.ADC.Conference.Domain/Events/Event.cs:592-603).
  • Why it's built this way: there is no ownership rule here because an event-speaker link has no per-user owner: the endpoint's role gate is the whole authorization story. The handler also adds no error text of its own, so the aggregate stays the single author of "why not", which is what makes the remove handlers in this module readable as one pattern with local variations rather than as independent implementations.
  • -
  • Where it's used: registered by the module scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143); dispatched by EventSpeakersController.DeleteAsync (MMCA.ADC.Conference.API/Controllers/Events/EventSpeakersController.cs:50, EventSpeakersController.cs:186-200). Covered by RemoveEventSpeakerHandlerTests.
  • +
  • Where it's used: registered by the module scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); dispatched by EventSpeakersController.DeleteAsync (MMCA.ADC.Conference.API/Controllers/Events/EventSpeakersController.cs:50, EventSpeakersController.cs:186-200). Covered by RemoveEventSpeakerHandlerTests.
  • + +

    UpdateEventQuestionAnswerHandler

    +
    +

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Events.UseCases.UpdateEventQuestionAnswer · MMCA.ADC.Conference.Application/Events/UseCases/UpdateEventQuestionAnswer/UpdateEventQuestionAnswerHandler.cs:22 · Level 14 · class (sealed partial)

    +
    +
      +
    • What it is: the handler for UpdateEventQuestionAnswerCommand. It applies the same BR-52/BR-53 ownership gate as its remove twin, then re-applies the rules the create path enforces (BR-108 on the event, BR-128 and BR-124 on the question and the value) before asking the aggregate to change the answer text (MMCA.ADC.Conference.Application/Events/UseCases/UpdateEventQuestionAnswer/UpdateEventQuestionAnswerHandler.cs:12-15, :56-57).
    • +
    • Depends on: MutateEntityHandlerBase<TCommand, TEntity, TIdentifierType> closed over the command, Event and EventIdentifierType (UpdateEventQuestionAnswerHandler.cs:26); IUnitOfWork (:23), also reached through the base's UnitOfWork property for a read repository (:78); ICurrentUserService (:24); ILogger<UpdateEventQuestionAnswerHandler> (:25); RoleNames from MMCA.ADC.Identity.Shared.Authorization (:4, :45); EventQuestionAnswerRules (:74, :80); Question (:3, :78); the EventQuestionAnswer child type; Result / Error.
    • +
    • Concept: the template method and the row-level ownership check are the ones taught by RemoveEventQuestionAnswerHandler, reproduced line for line with "You can only update your own answers." in place of the delete message (:47-51). Reading the two side by side is the fastest way to see that the ownership rule is a policy about the caller and the row, not about the verb. What this slice adds is content-rule reuse across verbs: the update calls the same EventQuestionAnswerRules helpers the single and batch add handlers call (MMCA.ADC.Conference.Application/Events/UseCases/AddEventQuestionAnswer/AddEventQuestionAnswerHandler.cs:41, :69; MMCA.ADC.Conference.Application/Events/UseCases/BatchAddEventQuestionAnswers/BatchAddEventQuestionAnswersHandler.cs:80, :98), so a value that could not be submitted cannot be edited in either. [Rubric §11, Security] assesses object-level authorization: identity comes from the ambient principal and the owner comes from the persisted audit field, never from the command. [Rubric §4, Domain-Driven Design] assesses invariant placement: the type-agnostic text rule stays on the entity, and the question-dependent rules live in one application-layer helper because they need a second aggregate (Question) that the Event root does not hold.
    • +
    • Walkthrough: primary constructor and base call (UpdateEventQuestionAnswerHandler.cs:22-26); Includes => [nameof(Event.EventQuestionAnswers)] (:29); EntityId(command) => command.EventId (:32); MutateAsync is async (:35-66): it null-guards (:40-41), finds the candidate answer (:44), applies the ownership gate returning Error.Forbidden with the code EventQuestionAnswer.NotOwner (:45-52), then, when the answer was found, awaits EnsureUpdateIsAcceptedAsync and returns its failure unchanged (:54-61), and finally returns entity.UpdateEventQuestionAnswer(command.EventQuestionAnswerId, command.AnswerValue) (:63-65). The private EnsureUpdateIsAcceptedAsync (:68-82) checks the loaded event through EventQuestionAnswerRules.EnsureEventAcceptsFeedback (:74-76), which delegates to EventInvariants.EnsureEventIsPublished (MMCA.ADC.Conference.Application/Events/EventQuestionAnswerRules.cs:28-29); reads the answer's question by id through UnitOfWork.GetReadRepository<Question, QuestionIdentifierType>() (:78-79); and returns EventQuestionAnswerRules.EnsureAnswerIsValid (:80-81), which fails with Error.Validation code Question.NotFoundOrWrongEntity when the question is missing or its QuestionEntity is not "Event", and otherwise runs QuestionInvariants.EnsureAnswerValueMatchesQuestionType (EventQuestionAnswerRules.cs:39-54). The aggregate then re-resolves the child through GetEventQuestionAnswerOrNotFound (MMCA.ADC.Conference.Domain/Events/Event.cs:647, helper at Event.cs:719-722), delegates to answer.UpdateAnswer(...) which runs EventInvariants.EnsureAnswerValueIsValid before assigning (MMCA.ADC.Conference.Domain/Events/EventQuestionAnswer.cs:71-73), and raises EventQuestionAnswerChanged with DomainEntityState.Updated only after both pass (Event.cs:656). LogMutated (:85-86) fires only after the commit, through the generated LogEventQuestionAnswerUpdated (:88-89).
    • +
    • Why it's built this way: the ownership predicate is duplicated between this handler and the remove handler rather than hoisted into a shared helper. With one condition and one message each, the two slices stay independently readable and independently changeable, which is the trade the vertical-slice layout makes on purpose ([Rubric §5, Vertical Slice]). The content rules make the opposite trade: EventQuestionAnswerRules exists because a second copy of them "would be a second place for the two surfaces to disagree" (EventQuestionAnswerRules.cs:13-15), and it takes the event and the question as arguments rather than looking them up (EventQuestionAnswerRules.cs:22-23, :32-33), so this handler passes the already-loaded Event and pays exactly one extra read. The gate runs before the rules, so a non-owner gets 403 without learning anything about the event's publish state or the question.
    • +
    • Where it's used: registered by the module scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); dispatched by EventQuestionAnswersController.UpdateAsync on PUT {id} (MMCA.ADC.Conference.API/Controllers/Events/EventQuestionAnswersController.cs:82, EventQuestionAnswersController.cs:163-174), which returns 204 No Content on success. Covered by UpdateEventQuestionAnswerHandlerTests.
    • +
    • Caveats / not-in-source: the BR-108 published check applies to every caller, Organizers included: the Organizer bypass covers only the ownership gate (:45), so an answer on an unpublished event cannot be edited by anyone through this path. When no active answer matches, both the gate and the rules are skipped (:45, :54) and the not-found decision is left to the aggregate. As in the remove twin, currentUserService.UserId!.Value (:45) assumes an authenticated caller, and the log line records only the answer id (:88-89), so the audit trail for "who changed this text" lives in the row's LastModifiedBy stamp rather than in the log.

    SpeakerCategoryItemDTOMapper

    @@ -4676,7 +4713,7 @@

    SpeakerCategoryItemDTOMapper

  • Why it's built this way: DTO shaping stays a compile-checked, allocation-lean step owned by the Application layer, so the API contract cannot drift from the entity without a build error (ADR-001).
  • -
  • Where it's used: injected into AddSpeakerCategoryItemHandler (AddSpeakerCategoryItemHandler.cs:17), composed into SpeakerDTOMapper as a [UseMapper] field for the parent's child collection (SpeakerDTOMapper.cs:23-24), and resolved by the generic EntityQueryService<TEntity, TEntityDTO, TIdentifierType> registered for this entity (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:129). The mapper itself is registered by the convention scan (DependencyInjection.cs:143), not by an explicit line. Covered by SpeakerCategoryItemDTOMapperTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/DTOs/SpeakerCategoryItemDTOMapperTests.cs).
  • +
  • Where it's used: injected into AddSpeakerCategoryItemHandler (AddSpeakerCategoryItemHandler.cs:17), composed into SpeakerDTOMapper as a [UseMapper] field for the parent's child collection (SpeakerDTOMapper.cs:23-24), and resolved by the generic EntityQueryService<TEntity, TEntityDTO, TIdentifierType> registered for this entity (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:129). The mapper itself is registered by the convention scan (DependencyInjection.cs:139), not by an explicit line. Covered by SpeakerCategoryItemDTOMapperTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/DTOs/SpeakerCategoryItemDTOMapperTests.cs).
  • Caveats / not-in-source: what the generated MapToDTO actually copies is decided by the property names on the entity and the DTO; the generated file is not in the repository, so the field list is only observable through the two type definitions and a build.
  • SpeakerQuestionAnswerDTOMapper

    @@ -4687,7 +4724,7 @@

    SpeakerQuestionAnswerDTOMapper

  • What it is: the Mapperly mapper for SpeakerQuestionAnswer to SpeakerQuestionAnswerDTO, the speaker's answers to the conference's profile questions. Structurally identical to SpeakerCategoryItemDTOMapper: the [Mapper] attribute (SpeakerQuestionAnswerDTOMapper.cs:11), the partial MapToDTO (SpeakerQuestionAnswerDTOMapper.cs:16), and the hand-written MapToDTOs (SpeakerQuestionAnswerDTOMapper.cs:19-23).
  • Depends on: IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType> closed over the answer entity, its DTO, and SpeakerQuestionAnswerIdentifierType (SpeakerQuestionAnswerDTOMapper.cs:13), plus Mapperly.
  • Concept introduced: none new; see SpeakerCategoryItemDTOMapper for the generated-mapper mechanism and why MapToDTOs is re-declared on the class.
  • -
  • Where it's used: this is the one mapper in the speaker family with no handler and no query service of its own. It reaches the wire only through composition: SpeakerDTOMapper holds it as a [UseMapper] field (SpeakerDTOMapper.cs:26-27) and the generator calls it while filling SpeakerDTO.SpeakerQuestionAnswers (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Speakers/SpeakerDTO.cs:103). Registration is by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143), which is why no explicit line exists for it while SpeakerCategoryItem gets one at DependencyInjection.cs:128-129. The entity's navigation populator is registered on its own at DependencyInjection.cs:133, and the comment above it records why that pair is uneven: SpeakerQuestionAnswer has no query service today, and registering the populator future-proofs the one that would be added alongside it (DependencyInjection.cs:131-132). Covered by SpeakerQuestionAnswerDTOMapperTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/DTOs/SpeakerQuestionAnswerDTOMapperTests.cs).
  • +
  • Where it's used: this is the one mapper in the speaker family with no handler and no query service of its own. It reaches the wire only through composition: SpeakerDTOMapper holds it as a [UseMapper] field (SpeakerDTOMapper.cs:26-27) and the generator calls it while filling SpeakerDTO.SpeakerQuestionAnswers (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Speakers/SpeakerDTO.cs:103). Registration is by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139), which is why no explicit line exists for it while SpeakerCategoryItem gets one at DependencyInjection.cs:128-129. The entity's navigation populator is registered on its own at DependencyInjection.cs:133, and the comment above it records why that pair is uneven: SpeakerQuestionAnswer has no query service today, and registering the populator future-proofs the one that would be added alongside it (DependencyInjection.cs:131-132). Covered by SpeakerQuestionAnswerDTOMapperTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/DTOs/SpeakerQuestionAnswerDTOMapperTests.cs).
  • AddSpeakerCategoryItemCommand

    @@ -4696,10 +4733,10 @@

    AddSpeakerCategoryItemCommand

    • What it is: the command that tags a speaker with a category item. Category items are how a speaker's topics and locality are modeled, so this is the "tag this speaker" message. Three positional parameters: the owning SpeakerId, an optional SpeakerCategoryItemId for the join entity, and the CategoryItemId being associated (AddSpeakerCategoryItemCommand.cs:13-16).
    • Depends on: ICacheInvalidating, the pipeline marker it implements (AddSpeakerCategoryItemCommand.cs:16); the Speaker domain type, referenced only to build the cache prefix; and the SpeakerIdentifierType (a System.Guid), SpeakerCategoryItemIdentifierType (an int), and CategoryItemIdentifierType (an int) module aliases (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/MMCA.ADC.Conference.GlobalUsings.IdentifierType.cs:23, MMCA.ADC.Conference.GlobalUsings.IdentifierType.cs:22, MMCA.ADC.Conference.GlobalUsings.IdentifierType.cs:8, ADR-048).
    • -
    • Concept introduced, the nullable child id on an Add command: the second parameter is SpeakerCategoryItemIdentifierType?, documented as "Explicit ID for the new join entity, or null for database-generated identity" (AddSpeakerCategoryItemCommand.cs:11). The REST path always passes null and lets the database assign the key (SpeakerCategoryItemsController at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerCategoryItemsController.cs:170); the parameter exists so a caller that already knows the id can supply it, which is exactly the shape the aggregate's method takes (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:314-316). [Rubric §9, API and Contract Design] assesses whether a contract states precisely what a caller may decide: making the id nullable rather than defaulted keeps "let the database choose" distinct from "I chose zero".
    • +
    • Concept introduced, the nullable child id on an Add command: the second parameter is SpeakerCategoryItemIdentifierType?, documented as "Explicit ID for the new join entity, or null for database-generated identity" (AddSpeakerCategoryItemCommand.cs:11). The REST path always passes null and lets the database assign the key (SpeakerCategoryItemsController at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerCategoryItemsController.cs:178); the parameter exists so a caller that already knows the id can supply it, which is exactly the shape the aggregate's method takes (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:316-318). [Rubric §9, API and Contract Design] assesses whether a contract states precisely what a caller may decide: making the id nullable rather than defaulted keeps "let the database choose" distinct from "I chose zero".
    • Walkthrough: the record body holds one member, CachePrefix => $"{typeof(Speaker).FullName}:" (AddSpeakerCategoryItemCommand.cs:18-19). That satisfies ICacheInvalidating, so CachingCommandDecorator<TCommand, TResult> evicts every cache entry under the Speaker prefix after the command succeeds. The join row has no cache namespace of its own, which is the point: tagging a speaker flushes the whole speaker read surface in one stroke rather than requiring per-query bookkeeping, so a stale nested category item cannot survive inside an already-cached speaker.
    • Why it's built this way: caching and invalidation are declared by the message and applied uniformly by the pipeline (ADR-026, ADR-014), never hand-wired inside a handler. [Rubric §12, Performance & Scalability]: the command says what it invalidates; it does not know how. Note what is absent: no ITransactional, because the whole write lands in one aggregate and one SaveChangesAsync with no cross-context event to keep atomic.
    • -
    • Where it's used: validated by AddSpeakerCategoryItemCommandValidator, handled by AddSpeakerCategoryItemHandler, and constructed by the POST /SpeakerCategoryItems action from an AddSpeakerCategoryItemRequest body (SpeakerCategoryItemsController.cs:165-171, handler injected at SpeakerCategoryItemsController.cs:50), on a controller gated by the SpeakersManage permission at the class level (SpeakerCategoryItemsController.cs:47, ADR-020). That action also carries [Idempotent] (SpeakerCategoryItemsController.cs:164), so a retried request with the same Idempotency-Key replays the first response instead of adding a second row, and the comment above it records why the attribute is written out here rather than inherited (SpeakerCategoryItemsController.cs:157-162). Its mirror image is RemoveSpeakerCategoryItemCommand.
    • +
    • Where it's used: validated by AddSpeakerCategoryItemCommandValidator, handled by AddSpeakerCategoryItemHandler, and constructed by the POST /SpeakerCategoryItems action from an AddSpeakerCategoryItemRequest body (SpeakerCategoryItemsController.cs:173-179, handler injected at SpeakerCategoryItemsController.cs:50), on a controller gated by the SpeakersManage permission at the class level (SpeakerCategoryItemsController.cs:47, ADR-020). That action also carries [Idempotent] (SpeakerCategoryItemsController.cs:172), so a retried request with the same Idempotency-Key replays the first response instead of adding a second row, and the comment above it records why the attribute is written out here rather than inherited (SpeakerCategoryItemsController.cs:165-170). Its mirror image is RemoveSpeakerCategoryItemCommand.

    SpeakerCreateRequest

    @@ -4708,10 +4745,10 @@

    SpeakerCreateRequest

    • What it is: the create-request DTO for a conference speaker. Like QuestionCreateRequest it doubles as the command: CreateSpeakerHandler is registered as ICommandHandler<SpeakerCreateRequest, Result<SpeakerDTO>> against this type, so there is no separate CreateSpeakerCommand.
    • Depends on: three interfaces, all declared on one line (SpeakerCreateRequest.cs:11). ICreateRequest is the empty marker the generic request-mapper and create-handler contracts constrain on; ICacheInvalidating declares the eviction; and ISpeakerFieldsRequest is the module's own shape interface that lets one rule set validate both the create and the update request. It also references Speaker for the cache prefix and the SpeakerIdentifierType alias (SpeakerCreateRequest.cs:14, SpeakerCreateRequest.cs:17).
    • -
    • Concept introduced, the request that implements a validation shape: [Rubric §15, Best Practices & Code Quality] assesses whether a rule is written once. ISpeakerFieldsRequest names the six members the create and update paths validate identically (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Speakers/Validation/ISpeakerFieldsRequest.cs:12-31), so SpeakerFieldRules<T> can be declared once over that constraint instead of twice over two concrete records. The interface's own remark records the one deliberate omission: FullName stays off it, because only the create path carries the member and no validator has a rule for it (ISpeakerFieldsRequest.cs:9-10). [Rubric §9, API and Contract Design] also applies: the controller binds this record straight from the request body (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:301) and it is the fourth generic argument of the base controller (SpeakersController.cs:52), so the wire contract is an explicit versionable type rather than the domain entity leaking outward. [Rubric §5, Vertical Slice] applies to the folder: request, mapper, validator, and handler for Create all sit in Speakers/UseCases/Create.
    • +
    • Concept introduced, the request that implements a validation shape: [Rubric §15, Best Practices & Code Quality] assesses whether a rule is written once. ISpeakerFieldsRequest names the six members the create and update paths validate identically (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Speakers/Validation/ISpeakerFieldsRequest.cs:12-31), so SpeakerFieldRules<T> can be declared once over that constraint instead of twice over two concrete records. The interface's own remark records the one deliberate omission: FullName stays off it, because only the create path carries the member and no validator has a rule for it (ISpeakerFieldsRequest.cs:9-10). [Rubric §9, API and Contract Design] also applies: the controller binds this record straight from the request body (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:310) and it is the fourth generic argument of the base controller (SpeakersController.cs:53), so the wire contract is an explicit versionable type rather than the domain entity leaking outward. [Rubric §5, Vertical Slice] applies to the folder: request, mapper, validator, and handler for Create all sit in Speakers/UseCases/Create.
    • Walkthrough: a record class (not sealed) with init-only members. CachePrefix (SpeakerCreateRequest.cs:13-14) is the invalidation tag. Id (SpeakerCreateRequest.cs:16-17) is a SpeakerIdentifierType, and its comment records that it is Sessionize-assigned: the caller supplies the key rather than the database generating it, which is what lets an import be idempotent. Three members are required, so the record cannot be constructed without them: FirstName (SpeakerCreateRequest.cs:20), LastName (SpeakerCreateRequest.cs:23), and FullName (SpeakerCreateRequest.cs:26). The rest are optional init members: Email (SpeakerCreateRequest.cs:29), Bio (SpeakerCreateRequest.cs:32), TagLine (SpeakerCreateRequest.cs:35), ProfilePicture (SpeakerCreateRequest.cs:38), the IsTopSpeaker flag (SpeakerCreateRequest.cs:41), and the four profile links TwitterHandle (SpeakerCreateRequest.cs:44), LinkedInUrl (SpeakerCreateRequest.cs:47), GitHubUrl (SpeakerCreateRequest.cs:50), and WebsiteUrl (SpeakerCreateRequest.cs:53).
    • Why it's built this way: required plus init gives compile-time enforcement of the minimum payload while leaving the rest optional, and collapsing request and command into one type keeps a simple create slice to a single message (contrast the child-mutation flows, where the controller builds a distinct command record such as AddSpeakerCategoryItemCommand).
    • -
    • Where it's used: bound by SpeakersController on the SpeakersManage-gated POST /Speakers (SpeakersController.cs:298-307); validated by SpeakerCreateRequestValidator; translated to a domain entity by SpeakerCreateRequestMapper; handled by CreateSpeakerHandler. Its update-side sibling on the same shape interface is SpeakerUpdateRequest.
    • +
    • Where it's used: bound by SpeakersController on the SpeakersManage-gated POST /Speakers (SpeakersController.cs:307-316); validated by SpeakerCreateRequestValidator; translated to a domain entity by SpeakerCreateRequestMapper; handled by CreateSpeakerHandler. Its update-side sibling on the same shape interface is SpeakerUpdateRequest.
    • Caveats / not-in-source: FullName is required on the contract but never reaches the domain. Speaker computes FullName => $"{FirstName} {LastName}" (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:61) and SpeakerCreateRequestMapper does not pass it to the factory, so a caller-supplied value is accepted and discarded. It is the only member of this record the mapper drops; every other one, the four profile links included, is forwarded (SpeakerCreateRequestMapper.cs:19-31). Nothing on this type says so.

    SessionAssetDTOMapper

    @@ -4721,10 +4758,10 @@

    SessionAssetDTOMapper

    • What it is: the outbound mapper that turns a SessionAsset entity into the SessionAssetDTO the API returns. Like QuestionDTOMapper, its single-entity method has no body: Mapperly generates it at compile time from the [Mapper] attribute (SessionAssetDTOMapper.cs:14).
    • Depends on: IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType> closed over SessionAsset, SessionAssetDTO and the SessionAssetIdentifierType alias (SessionAssetDTOMapper.cs:16); the SessionAsset entity and the SessionAssetDTO contract; Riok.Mapperly.Abstractions (NuGet).
    • -
    • Concept reinforced, source-generated DTO mapping: [Rubric §2, Design Patterns] assesses whether repetitive translation code is factored out rather than hand-written property by property: the partial declaration at SessionAssetDTOMapper.cs:29 is the whole contribution to the single-entity mapping, and the generator emits the field assignments into a companion generated file. [Rubric §3, Clean Architecture] assesses direction: the domain entity never leaves the Application layer, only the DTO does. The convention is set out in ADR-001; the same shape appears on QuestionDTOMapper.
    • -
    • Walkthrough: [Mapper] marks the class as the generator's trigger; it is declared sealed partial (SessionAssetDTOMapper.cs:15) so the generated half can be merged in. public partial SessionAssetDTO MapToDTO(SessionAsset entity) (SessionAssetDTOMapper.cs:29) is the generated member. MapToDTOs (SessionAssetDTOMapper.cs:32-36) is hand-written: it null-guards the collection (SessionAssetDTOMapper.cs:34) and projects with a collection expression over the generated single-item mapper, [.. entityCollection.Select(MapToDTO)] (SessionAssetDTOMapper.cs:35).
    • +
    • Concept reinforced, source-generated DTO mapping: [Rubric §2, Design Patterns] assesses whether repetitive translation code is factored out rather than hand-written property by property: the partial declaration at SessionAssetDTOMapper.cs:19 is the whole contribution to the single-entity mapping, and the generator emits the field assignments into a companion generated file. [Rubric §3, Clean Architecture] assesses direction: the domain entity never leaves the Application layer, only the DTO does. The convention is set out in ADR-001; the same shape appears on QuestionDTOMapper.
    • +
    • Walkthrough: [Mapper] marks the class as the generator's trigger; it is declared sealed partial (SessionAssetDTOMapper.cs:15) so the generated half can be merged in. public partial SessionAssetDTO MapToDTO(SessionAsset entity) (SessionAssetDTOMapper.cs:19) is the generated member. MapToDTOs (SessionAssetDTOMapper.cs:22-26) is hand-written: it null-guards the collection (SessionAssetDTOMapper.cs:24) and projects with a collection expression over the generated single-item mapper, [.. entityCollection.Select(MapToDTO)] (SessionAssetDTOMapper.cs:25).
    • Why it's built this way: the collection method re-declares what IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType> already supplies as a default interface implementation, the same reasoning QuestionDTOMapper documents: handlers inject this mapper as a concrete class rather than through the interface, and a default interface member is reachable only through the interface, so the method has to be re-declared on the class to stay callable from those call sites.
    • -
    • Where it's used: constructor-injected as a concrete type into AddSessionAssetLinkHandler (MMCA.ADC.Conference.Application/SessionAssets/UseCases/AddLink/AddSessionAssetLinkHandler.cs:23), GetSessionAssetsHandler (MMCA.ADC.Conference.Application/SessionAssets/UseCases/GetBySession/GetSessionAssetsHandler.cs:30), the update handler (MMCA.ADC.Conference.Application/SessionAssets/UseCases/Update/UpdateSessionAssetHandler.cs:22) and the upload handler (MMCA.ADC.Conference.Application/SessionAssets/UseCases/UploadFile/UploadSessionAssetHandler.cs:40). Picked up by the module's convention scan, the same registration path QuestionDTOMapper uses (MMCA.ADC.Conference.Application/DependencyInjection.cs:135). Covered by SessionAssetDTOMapperTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/SessionAssets/DTOs/SessionAssetDTOMapperTests.cs).
    • +
    • Where it's used: constructor-injected as a concrete type into AddSessionAssetLinkHandler (MMCA.ADC.Conference.Application/SessionAssets/UseCases/AddLink/AddSessionAssetLinkHandler.cs:23), GetSessionAssetsHandler (MMCA.ADC.Conference.Application/SessionAssets/UseCases/GetBySession/GetSessionAssetsHandler.cs:30), the update handler (MMCA.ADC.Conference.Application/SessionAssets/UseCases/Update/UpdateSessionAssetHandler.cs:22) and the upload handler (MMCA.ADC.Conference.Application/SessionAssets/UseCases/UploadFile/UploadSessionAssetHandler.cs:42). Picked up by the module's convention scan, the same registration path QuestionDTOMapper uses (MMCA.ADC.Conference.Application/DependencyInjection.cs:131). Covered by SessionAssetDTOMapperTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/SessionAssets/DTOs/SessionAssetDTOMapperTests.cs).
    • Caveats / not-in-source: as with QuestionDTOMapper, the generated field assignments are not readable in this file, only in build output, so a member added to the DTO without a matching entity member surfaces as a generator diagnostic at build time rather than as anything visible here.

    QuestionCreateRequest

    @@ -4737,7 +4774,7 @@

    QuestionCreateRequest

  • Concept introduced, the request whose id field is accepted and then thrown away: [Rubric §9, API and Contract Design] assesses whether a contract is honest about what the caller controls. Id is a settable init member (QuestionCreateRequest.cs:16), but its own doc comment says "Auto-generated by the handler; caller-provided values are ignored". That is not laziness: the question id space is shared with Sessionize, so CreateQuestionHandler allocates from a reserved manual range and overwrites whatever arrived (CreateQuestionHandler.cs:86). Compare SpeakerCreateRequest, where the caller-supplied id IS honored because a speaker id is a Sessionize-assigned GUID. Two create requests in the same module, opposite id policies, and the only place either is stated is a one-line comment on the property.
  • Walkthrough: a record class (not sealed) with init-only members. CachePrefix => $"{typeof(Question).FullName}:" (QuestionCreateRequest.cs:13) is the invalidation tag. Exactly one member is required, QuestionText (QuestionCreateRequest.cs:19), so the record cannot be constructed without it. The rest are optional: QuestionEntity (QuestionCreateRequest.cs:22), QuestionType (QuestionCreateRequest.cs:25), Sort (QuestionCreateRequest.cs:28), and IsRequired (QuestionCreateRequest.cs:31).
  • Why it's built this way: required plus init gives compile-time enforcement of the minimum payload while leaving the rest optional, and collapsing request and command into one type keeps a simple create slice to a single message. [Rubric §5, Vertical Slice]: the request, its mapper, its validator, and its handler all sit in Questions/UseCases/Create.
  • -
  • Where it's used: bound from the body by QuestionsController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Questions/QuestionsController.cs:95-98), and it is also the fourth generic argument of that controller's base AggregateRootEntityControllerBase<TEntity, TEntityDTO, TIdentifierType, TCreateRequest> (QuestionsController.cs:42-43); validated by QuestionCreateRequestValidator; translated to a domain entity by QuestionCreateRequestMapper; handled by CreateQuestionHandler.
  • +
  • Where it's used: bound from the body by QuestionsController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Questions/QuestionsController.cs:98-101), and it is also the fourth generic argument of that controller's base AggregateRootEntityControllerBase<TEntity, TEntityDTO, TIdentifierType, TCreateRequest> (QuestionsController.cs:42-43); validated by QuestionCreateRequestValidator; translated to a domain entity by QuestionCreateRequestMapper; handled by CreateQuestionHandler.
  • Caveats / not-in-source: QuestionEntity and QuestionType are declared nullable here, but Question.Create takes them as non-nullable and validates both against closed value lists, ["Session", "Event", "Speaker"] and ["Rating", "Text", "Email"] (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Questions/QuestionInvariants.cs:31, QuestionInvariants.cs:34). QuestionCreateRequestMapper bridges the gap with ! (QuestionCreateRequestMapper.cs:22-23), so omitting either field is not a binding error but an invariant failure at create time. Nothing on this record says so.
  • SpeakerDTOMapper

    @@ -4747,7 +4784,7 @@

    SpeakerDTOMapper

    • What it is: the mapper for the Speaker aggregate itself. It composes the two child mappers, and it is the single place where BR-66 redacts speaker email from anyone who is not an organizer.
    • Depends on: IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType> closed over Speaker / SpeakerDTO / SpeakerIdentifierType (SpeakerDTOMapper.cs:21); the two sibling mappers SpeakerCategoryItemDTOMapper and SpeakerQuestionAnswerDTOMapper; ICurrentUserService; the Email value object; RoleNames; and Mapperly (SpeakerDTOMapper.cs:1-21).
    • -
    • Concept introduced, a redacting mapper, and why the redaction lives here: [Rubric §11, Security] assesses whether a PII rule is enforced at one chokepoint rather than at each call site, and [Rubric §30, Compliance, Privacy and Data Governance] assesses whether personal data has a stated handling rule. MapToDTO does not expose the generated mapping directly. It calls the private generated method and then decides: currentUserService.IsInRole(RoleNames.Organizer) ? dto : dto with { Email = null } (SpeakerDTOMapper.cs:33-36). Because every speaker read path in the module goes through this one mapper (SpeakerEntityQueryService.cs:19, CreateSpeakerHandler.cs:18, UpdateSpeakerHandler.cs:20), there is no endpoint that can accidentally return a speaker email to the public. That single-chokepoint property is also what makes the framework's inherited CSV export a hole worth patching separately: it streams past the DTO mapper entirely, which is why SpeakersController overrides the export action, gates it on SpeakersManage, and denies non-privileged callers outright (BR-239, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:271-294). Reading the mapper alone would leave you believing the rule was airtight; reading the pair shows where it needed help.
    • +
    • Concept introduced, a redacting mapper, and why the redaction lives here: [Rubric §11, Security] assesses whether a PII rule is enforced at one chokepoint rather than at each call site, and [Rubric §30, Compliance, Privacy and Data Governance] assesses whether personal data has a stated handling rule. MapToDTO does not expose the generated mapping directly. It calls the private generated method and then decides: currentUserService.IsInRole(RoleNames.Organizer) ? dto : dto with { Email = null } (SpeakerDTOMapper.cs:33-36). Because every speaker read path in the module goes through this one mapper (SpeakerEntityQueryService.cs:19, CreateSpeakerHandler.cs:18, UpdateSpeakerHandler.cs:20), there is no endpoint that can accidentally return a speaker email to the public. That single-chokepoint property is also what makes the framework's inherited CSV export a hole worth patching separately: it streams past the DTO mapper entirely, which is why SpeakersController overrides the export action, gates it on SpeakersManage, and denies non-privileged callers outright (BR-239, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:280-303). Reading the mapper alone would leave you believing the rule was airtight; reading the pair shows where it needed help.
    • Concept introduced, mapper composition with [UseMapper]: each injected child mapper is stored in a private field annotated [UseMapper] (SpeakerDTOMapper.cs:23-27). That attribute tells the Mapperly generator: when you need to map a SpeakerCategoryItem to a SpeakerCategoryItemDTO while filling this type, call that mapper instead of generating a second, private copy of the same mapping. The payoff is that SpeakerDTO's two child collections (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Speakers/SpeakerDTO.cs:100, SpeakerDTO.cs:103) are filled by exactly the same code the child endpoints use, so a speaker read and a GET /SpeakerCategoryItems read can never disagree about a child's shape. [Rubric §2, Design Patterns]: composition over duplication, expressed declaratively. [Rubric §15, Best Practices & Code Quality]: adding a field to a child DTO is one edit, not three.
    • Walkthrough: four methods plus the two fields.
      • The primary constructor takes the two child mappers and ICurrentUserService (SpeakerDTOMapper.cs:17-20), assigned to the [UseMapper] fields (SpeakerDTOMapper.cs:23-27).
      • @@ -4768,9 +4805,9 @@

        AddSpeakerCategoryItemCommandVal
        • What it is: the FluentValidation validator for AddSpeakerCategoryItemCommand. It asserts one thing: the caller actually supplied a category item.
        • Depends on: FluentValidation's AbstractValidator<T> (AddSpeakerCategoryItemCommandValidator.cs:1, AddSpeakerCategoryItemCommandValidator.cs:8) and the CategoryItemIdentifierType alias.
        • -
        • Concept introduced, the Validating decorator stage: [Rubric §24, Forms, Validation and UX Safety] assesses whether bad input is rejected before it reaches business logic, and [Rubric §6, CQRS & Event-Driven Design] assesses whether that happens uniformly. The handler never calls this class. ValidatingCommandDecorator<TCommand, TResult> runs every registered validator for the command type outside the transaction, because it is registered after the transactional decorator and therefore wraps it (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:134-136, ADR-014), so a malformed command costs no database work. Registration is by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143): dropping a validator file next to the command is the entire wiring step, which is the vertical-slice payoff, [Rubric §5, Vertical Slice].
        • +
        • Concept introduced, the Validating decorator stage: [Rubric §24, Forms, Validation and UX Safety] assesses whether bad input is rejected before it reaches business logic, and [Rubric §6, CQRS & Event-Driven Design] assesses whether that happens uniformly. The handler never calls this class. ValidatingCommandDecorator<TCommand, TResult> runs every registered validator for the command type outside the transaction, because it is registered after the transactional decorator and therefore wraps it (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:134-136, ADR-014), so a malformed command costs no database work. Registration is by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139): dropping a validator file next to the command is the entire wiring step, which is the vertical-slice payoff, [Rubric §5, Vertical Slice].
        • Walkthrough: an expression-bodied constructor with a single rule (AddSpeakerCategoryItemCommandValidator.cs:10-13): RuleFor(x => x.CategoryItemId).NotEqual(default(CategoryItemIdentifierType)) with the message "Category item ID is required." Because the identifier alias is a value type (an int), default is the "not supplied" sentinel that model binding produces for a missing JSON field, so this rule is what turns a silently omitted field into a 400 rather than a lookup miss deeper in.
        • -
        • Why it's built this way: the validator covers only what can be judged from the message itself. Whether the association is a duplicate is decided against loaded state in the aggregate (Speaker.AddSpeakerCategoryItem at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:318-325) rather than restated here. [Rubric §4, Domain-Driven Design]: the invariant stays in the aggregate; the validator only guards the shape.
        • +
        • Why it's built this way: the validator covers only what can be judged from the message itself. Whether the association is a duplicate is decided against loaded state in the aggregate (Speaker.AddSpeakerCategoryItem at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:320-327) rather than restated here. [Rubric §4, Domain-Driven Design]: the invariant stays in the aggregate; the validator only guards the shape.
        • Where it's used: resolved by the Validating decorator for AddSpeakerCategoryItemCommand; covered directly by AddSpeakerCategoryItemCommandValidatorTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/Validation/SpeakerCommandValidatorTests.cs:6, instantiating the validator at SpeakerCommandValidatorTests.cs:8).

        AddSpeakerCategoryItemHandler

        @@ -4781,17 +4818,17 @@

        AddSpeakerCategoryItemHandler

      • What it is: the handler for AddSpeakerCategoryItemCommand. It writes no workflow of its own: it subclasses the framework's shared add-a-child workflow and fills in five hooks.
      • Depends on: AddChildEntityHandlerBase<TCommand, TParent, TIdentifierType, TChild, TChildDTO> closed over the command, Speaker, SpeakerIdentifierType, SpeakerCategoryItem, and SpeakerCategoryItemDTO (AddSpeakerCategoryItemHandler.cs:19); IUnitOfWork, passed straight through to the base (AddSpeakerCategoryItemHandler.cs:16); SpeakerCategoryItemDTOMapper as a concrete type (AddSpeakerCategoryItemHandler.cs:17); Result; and Microsoft.Extensions.Logging.
      • Concept introduced, the template-method handler base: [Rubric §2, Design Patterns] assesses whether a repeated workflow is factored into one place. The load-delegate-save-map sequence every add-a-child handler used to write by hand lives once, in AddChildEntityHandlerBase.HandleAsync (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/ChildEntityHandlerBase.cs:102-126): resolve the parent repository from the unit of work, load it with the declared includes and tracking, fail with Error.NotFound stamped with the handler name and the parent type name when it is gone (ChildEntityHandlerBase.cs:111-112), call Apply, short-circuit on a failed invariant (ChildEntityHandlerBase.cs:114-116), save only after that (ChildEntityHandlerBase.cs:118), then log and map. [Rubric §1, SOLID]: the base owns the sequence, the subclass owns the vocabulary, and the base stays abstract so Scrutor keeps registering the concrete handler and the decorator pipeline keeps wrapping it (ChildEntityHandlerBase.cs:17-23).
      • -
      • Concept introduced, loading the children the invariant needs: [Rubric §4, Domain-Driven Design] assesses whether application code mutates child entities directly. It does not here: Apply is one expression, parent.AddSpeakerCategoryItem(command.SpeakerCategoryItemId, command.CategoryItemId) (AddSpeakerCategoryItemHandler.cs:30-31), and the aggregate is where the duplicate rule, the child factory call, and the SpeakerCategoryItemChanged domain event live (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:314-338). Includes is abstract on the base for exactly this reason (ChildEntityHandlerBase.cs:14-19): naming the join collection has to be a deliberate act, and the comment above the override in this file says what breaks without it (AddSpeakerCategoryItemHandler.cs:21-22). Without the include, the duplicate check at Speaker.cs:318 runs against an empty in-memory list and a double submit surfaces as a raw unique-index 409 instead of a worded invariant failure. Tracking is the other half, defaulted to true on the base because a no-tracking load would make the save a silent no-op (ChildEntityHandlerBase.cs:46-50). [Rubric §8, Data Architecture]: the in-memory check is still backed at the database level by a unique index on (SpeakerId, CategoryItemId) filtered to non-deleted rows (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Speakers/SpeakerCategoryItemConfiguration.cs:30-32), so a race that beats the check still cannot write a duplicate row.
      • +
      • Concept introduced, loading the children the invariant needs: [Rubric §4, Domain-Driven Design] assesses whether application code mutates child entities directly. It does not here: Apply is one expression, parent.AddSpeakerCategoryItem(command.SpeakerCategoryItemId, command.CategoryItemId) (AddSpeakerCategoryItemHandler.cs:30-31), and the aggregate is where the duplicate rule, the child factory call, and the SpeakerCategoryItemChanged domain event live (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:316-340). Includes is abstract on the base for exactly this reason (ChildEntityHandlerBase.cs:14-19): naming the join collection has to be a deliberate act, and the comment above the override in this file says what breaks without it (AddSpeakerCategoryItemHandler.cs:21-22). Without the include, the duplicate check at Speaker.cs:320 runs against an empty in-memory list and a double submit surfaces as a raw unique-index 409 instead of a worded invariant failure. Tracking is the other half, defaulted to true on the base because a no-tracking load would make the save a silent no-op (ChildEntityHandlerBase.cs:46-50). [Rubric §8, Data Architecture]: the in-memory check is still backed at the database level by a unique index on (SpeakerId, CategoryItemId) filtered to non-deleted rows (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Speakers/SpeakerCategoryItemConfiguration.cs:30-32), so a race that beats the check still cannot write a duplicate row.
      • Walkthrough: a primary constructor taking unitOfWork, the concrete dtoMapper, and a typed ILogger, forwarding only the unit of work to the base (AddSpeakerCategoryItemHandler.cs:15-19). Then five members, and nothing else.
        • Includes returns [nameof(Speaker.SpeakerCategoryItems)] (AddSpeakerCategoryItemHandler.cs:24).
        • ParentId pulls command.SpeakerId (AddSpeakerCategoryItemHandler.cs:27).
        • -
        • Apply calls the aggregate method and returns its Result<SpeakerCategoryItem> unchanged (AddSpeakerCategoryItemHandler.cs:30-31), so a duplicate association reaches the client worded as the aggregate worded it (Speaker.CategoryItem.Duplicate, Speaker.cs:320-325).
        • +
        • Apply calls the aggregate method and returns its Result<SpeakerCategoryItem> unchanged (AddSpeakerCategoryItemHandler.cs:30-31), so a duplicate association reaches the client worded as the aggregate worded it (Speaker.CategoryItem.Duplicate, Speaker.cs:322-327).
        • MapChild is the one-line call into the module's own child mapper (AddSpeakerCategoryItemHandler.cs:34). The base takes a MapChild hook rather than an injected IEntityDTOMapper because the DTO belongs to the CHILD entity, whose identifier type is not the parent's (ChildEntityHandlerBase.cs:20-24).
        • LogAdded forwards to the source-generated LogCategoryItemAdded (AddSpeakerCategoryItemHandler.cs:37-38, declared at AddSpeakerCategoryItemHandler.cs:40-41), which is why the class is partial. The base calls it only after a successful save (ChildEntityHandlerBase.cs:122).
      • Why it's built this way: the handler is pure vocabulary because the surrounding layers already own everything else: validation and cache eviction by the decorators (ADR-014), the workflow by the base class, the rules by the aggregate. [Rubric §13, Observability and Operability]: logging goes through a [LoggerMessage] partial, allocation-free and compile-checked, and the base gives it exactly one call site so a log can never be emitted for a write that did not commit.
      • -
      • Where it's used: registered by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143, which drives the framework's command-handler scan at MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:105-109) and injected into SpeakerCategoryItemsController as ICommandHandler<AddSpeakerCategoryItemCommand, Result<SpeakerCategoryItemDTO>> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerCategoryItemsController.cs:50). Its counterpart is RemoveSpeakerCategoryItemHandler. Covered by AddSpeakerCategoryItemHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/AddSpeakerCategoryItemHandlerTests.cs).
      • +
      • Where it's used: registered by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139, which drives the framework's command-handler scan at MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:105-109) and injected into SpeakerCategoryItemsController as ICommandHandler<AddSpeakerCategoryItemCommand, Result<SpeakerCategoryItemDTO>> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerCategoryItemsController.cs:50). Its counterpart is RemoveSpeakerCategoryItemHandler. Covered by AddSpeakerCategoryItemHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/AddSpeakerCategoryItemHandlerTests.cs).
      • Caveats / not-in-source: the base exposes two more hooks this handler leaves alone, HandlerName (defaulting to the concrete type name, ChildEntityHandlerBase.cs:44) and the post-commit OnAddedAsync (ChildEntityHandlerBase.cs:98-99). Neither is overridden here, so nothing in this file records that they exist.

      SpeakerCreateRequestMapper

      @@ -4801,11 +4838,11 @@

      SpeakerCreateRequestMapper

      • What it is: the adapter that turns a validated SpeakerCreateRequest into a Speaker entity by calling the aggregate's static factory.
      • Depends on: IEntityRequestMapper<TEntity, TCreateRequest, TIdentifierType> closed over Speaker, SpeakerCreateRequest, and SpeakerIdentifierType (SpeakerCreateRequestMapper.cs:11-12); Speaker; Result.
      • -
      • Concept reinforced, the request mapper as the only door into a factory: see QuestionCreateRequestMapper for the pattern. Speaker.Create (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:116-171) parses the optional email into an Email value object and bails on a malformed address (Speaker.cs:130-137), combines the first- and last-name invariants with Result.Combine so both failures surface at once (Speaker.cs:139-143), assigns the client-supplied id or generates one (Speaker.cs:161), and raises SpeakerChanged (Speaker.cs:168). The id fallback carries its own scar: the comment records that the previous id!.Value threw "Nullable object must have a value" and killed both Conference's startup seeding and every organizer create (Speaker.cs:156-160). [Rubric §4, Domain-Driven Design], [Rubric §15, Best Practices and Code Quality].
      • -
      • Walkthrough: CreateEntityAsync (SpeakerCreateRequestMapper.cs:15) null-guards (SpeakerCreateRequestMapper.cs:17), then returns Task.FromResult(Speaker.Create(...)) (SpeakerCreateRequestMapper.cs:19-31). Twelve of the factory's parameters are filled from the request (Id, FirstName, LastName, Email, Bio, TagLine, ProfilePicture, IsTopSpeaker, TwitterHandle, LinkedInUrl, GitHubUrl, WebsiteUrl); only FullName is dropped, because the entity computes it (Speaker.cs:61). The method is synchronous work behind an async signature: Task.FromResult satisfies the interface without an allocation-heavy state machine.
      • +
      • Concept reinforced, the request mapper as the only door into a factory: see QuestionCreateRequestMapper for the pattern. Speaker.Create (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:116-172) parses the optional email into an Email value object and bails on a malformed address (Speaker.cs:130-137), combines the first- and last-name invariants with Result.Combine so both failures surface at once (Speaker.cs:139-144), assigns the client-supplied id or generates one (Speaker.cs:162), and raises SpeakerChanged (Speaker.cs:169). The id fallback carries its own scar: the comment records that the previous id!.Value threw "Nullable object must have a value" and killed both Conference's startup seeding and every organizer create (Speaker.cs:157-161). [Rubric §4, Domain-Driven Design], [Rubric §15, Best Practices and Code Quality].
      • +
      • Walkthrough: CreateEntityAsync (SpeakerCreateRequestMapper.cs:15) null-guards (SpeakerCreateRequestMapper.cs:17), then returns Task.FromResult(Speaker.Create(...)) (SpeakerCreateRequestMapper.cs:21-33). The id goes through one translation: request.Id == default ? null : request.Id (SpeakerCreateRequestMapper.cs:22), so an empty GUID becomes a null id for the factory. Twelve of the factory's parameters are filled from the request (Id, FirstName, LastName, Email, Bio, TagLine, ProfilePicture, IsTopSpeaker, TwitterHandle, LinkedInUrl, GitHubUrl, WebsiteUrl); only FullName is dropped, because the entity computes it (Speaker.cs:61). The method is synchronous work behind an async signature: Task.FromResult satisfies the interface without an allocation-heavy state machine.
      • Why it's built this way: delegating every field check to the factory keeps validation in the domain instead of duplicated in the Application layer, and the generic create pipeline can drive any aggregate through the same contract (ADR-001).
      • -
      • Where it's used: constructor-injected into CreateSpeakerHandler as IEntityRequestMapper<Speaker, SpeakerCreateRequest, SpeakerIdentifierType> (CreateSpeakerHandler.cs:17) and forwarded to its base (CreateSpeakerHandler.cs:20-21), which invokes it at MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:90; registered by the framework's request-mapper scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:80-84), driven from the module at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143.
      • -
      • Caveats / not-in-source: the factory's id parameter is SpeakerIdentifierType?, but the request's Id is the non-nullable alias, so this path always passes a value and the Guid.NewGuid() fallback at Speaker.cs:161 is unreachable from it. A POST /Speakers body that omits Id therefore creates a speaker whose key is Guid.Empty rather than a fresh GUID. The null-id branch is reached only by callers that pass id: null explicitly, which is what the sample-data seeder does (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/DbContexts/Seeding/ConferenceModuleDbSeeder.cs:215-216); the Sessionize import supplies the Sessionize GUID instead (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/SpeakerSyncStrategy.cs:126). Nothing in this file or the request records that expectation.
      • +
      • Where it's used: constructor-injected into CreateSpeakerHandler as IEntityRequestMapper<Speaker, SpeakerCreateRequest, SpeakerIdentifierType> (CreateSpeakerHandler.cs:17) and forwarded to its base (CreateSpeakerHandler.cs:20-21), which invokes it at MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:90; registered by the framework's request-mapper scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:80-84), driven from the module at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139.
      • +
      • Caveats / not-in-source: the factory's id parameter is SpeakerIdentifierType?, but the request's Id is the non-nullable alias, so a POST /Speakers body that omits Id binds it as Guid.Empty. The mapper's comment names exactly that case (SpeakerCreateRequestMapper.cs:19-20) and maps the default value to null, which routes the call into the factory's Guid.NewGuid() fallback (Speaker.cs:162) instead of persisting an empty key. The flip side is that a caller cannot deliberately create a speaker keyed Guid.Empty through this path. The same null-id branch is what the sample-data seeder hits by passing id: null explicitly (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/DbContexts/Seeding/ConferenceModuleDbSeeder.cs:215-216); the Sessionize import supplies the Sessionize GUID instead and does not go through this mapper (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/SpeakerSyncStrategy.cs:134).

      SpeakerCreateRequestValidator

      @@ -4818,7 +4855,7 @@

      SpeakerCreateRequestValidator

    • Concept introduced, a validation rule that is a security control: [Rubric §26, Front-End Security] assesses whether stored values that reach the browser as executable targets are constrained at the point of write. The three URL rule sets exist for that reason, and their doc comments say so: the scheme check keeps a stored link from carrying a javascript: or data: target, because the speaker pages put the value straight into a link (SpeakerValidationRules.cs:51-55, SpeakerValidationRules.cs:69-74, SpeakerValidationRules.cs:88-93). [Rubric §11, Security] reaches the same code from the server side: a request that never passes validation never persists the payload.
    • Walkthrough: a sealed class whose whole body is an expression-bodied constructor (SpeakerCreateRequestValidator.cs:9-10) calling Include(new SpeakerFieldRules<SpeakerCreateRequest>()). The ceilings all come from the domain rather than from the validator: SpeakerInvariants.FirstNameMaxLength and LastNameMaxLength are 200, EmailMaxLength is 255, and the three URL lengths are 2000 (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/SpeakerInvariants.cs:16, SpeakerInvariants.cs:19, SpeakerInvariants.cs:22, SpeakerInvariants.cs:34, SpeakerInvariants.cs:37, SpeakerInvariants.cs:40), each forwarding to the constant on SpeakerDTO (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Speakers/SpeakerDTO.cs:21-52), which is also what the name invariants enforce (SpeakerInvariants.cs:48, SpeakerInvariants.cs:53).
    • Why it's built this way: extracting field rules into shared generic classes is the module-wide convention, and hoisting the whole list onto an interface-constrained rule set removes the last piece of duplication, the six selector lambdas that the create and the update validator would otherwise each write out.
    • -
    • Where it's used: discovered by the module's validator scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143) and run by ValidatingCommandDecorator<TCommand, TResult> ahead of CreateSpeakerHandler. Covered by SpeakerCreateRequestValidatorTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/Validation/SpeakerCreateRequestValidatorTests.cs), with the rule sets themselves covered by SpeakerValidationRulesTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/Validation/SpeakerValidationRulesTests.cs).
    • +
    • Where it's used: discovered by the module's validator scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139) and run by ValidatingCommandDecorator<TCommand, TResult> ahead of CreateSpeakerHandler. Covered by SpeakerCreateRequestValidatorTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/Validation/SpeakerCreateRequestValidatorTests.cs), with the rule sets themselves covered by SpeakerValidationRulesTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/Validation/SpeakerValidationRulesTests.cs).
    • Caveats / not-in-source: Bio, TagLine, ProfilePicture, and TwitterHandle are not on ISpeakerFieldsRequest and are validated nowhere in this layer, even though SpeakerInvariants declares max lengths for three of them (SpeakerInvariants.cs:25, SpeakerInvariants.cs:28, SpeakerInvariants.cs:31). An over-long tagline is therefore caught by the database column, not by a worded validation error.

    QuestionCreateRequestMapper

    @@ -4831,7 +4868,7 @@

    QuestionCreateRequestMapper

  • Concept introduced, the request mapper as the only door into a factory: [Rubric §4, Domain-Driven Design] assesses whether an entity can be constructed in an invalid state. It cannot: Question.Create (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Questions/Question.cs:70) combines four invariant checks before it allocates anything (Question.cs:79-85) and raises QuestionChanged on success (Question.cs:94), returning a Result<Question> throughout. A bad request therefore becomes an error list, never a half-built object. Unlike a DTO mapper, which copies fields outward, a request mapper translates inward and is allowed to fail. Note the class carries no [Mapper] attribute: this is hand-written mapping, not Mapperly generation (contrast SpeakerCategoryItemDTOMapper). [Rubric §3, Clean Architecture]: keeping it in its own class is what lets CreateQuestionHandler and its base depend on the generic interface instead of the factory signature.
  • Walkthrough: CreateEntityAsync (QuestionCreateRequestMapper.cs:15) null-guards the request (QuestionCreateRequestMapper.cs:17), then returns Task.FromResult(Question.Create(...)) (QuestionCreateRequestMapper.cs:19-26): the work is synchronous and the Task exists only to satisfy the async interface. Two things in that call are worth reading twice. The nullable QuestionEntity and QuestionType are forced with ! (QuestionCreateRequestMapper.cs:22-23), which does not make them non-null; it hands a possible null to invariants that reject it against a closed list (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Questions/QuestionInvariants.cs:71-72, QuestionInvariants.cs:86-87), so an omitted field becomes a worded invariant error rather than a null-reference exception. And questionSource is hard-coded to "User" (QuestionCreateRequestMapper.cs:26), never taken from the request: an API-created question can never claim to have come from Sessionize, the other member of the closed source list (QuestionInvariants.cs:37).
  • Why it's built this way: delegating every field check to the factory keeps validation in the domain instead of duplicated in the Application layer, and the generic create pipeline can drive any aggregate through the same contract (ADR-001). Pinning the source server-side is the same instinct as never taking an identity from a request body: provenance is not a caller's field to set.
  • -
  • Where it's used: resolved as IEntityRequestMapper<Question, QuestionCreateRequest, QuestionIdentifierType> and constructor-injected into CreateQuestionHandler (CreateQuestionHandler.cs:22), which forwards it to its base; the base invokes it at MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:90. Registered by the framework's request-mapper scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:82), driven from the module at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143.
  • +
  • Where it's used: resolved as IEntityRequestMapper<Question, QuestionCreateRequest, QuestionIdentifierType> and constructor-injected into CreateQuestionHandler (CreateQuestionHandler.cs:22), which forwards it to its base; the base invokes it at MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:90. Registered by the framework's request-mapper scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:82), driven from the module at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139.
  • QuestionCreateRequestValidator

    @@ -4843,7 +4880,7 @@

    QuestionCreateRequestValidator

  • Concept reinforced, rule composition with Include: [Rubric §15, Best Practices & Code Quality] assesses whether the same constraint is written once. Rather than repeat a required-plus-max-length rule in the create validator and again in the update validator, both Include the same generic rule set, parameterized by a property selector (QuestionCreateRequestValidator.cs:10). Include merges the included validator's rules into this one as though they were declared inline, so composition costs nothing at validation time. [Rubric §24, Forms, Validation and UX Safety] covers what the rules produce: QuestionTextRules<T> attaches NotEmpty and MaximumLength(QuestionInvariants.QuestionTextMaxLength) with stable error codes Question.QuestionText.Required and Question.QuestionText.MaxLength (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Questions/Validation/QuestionValidationRules.cs:17-18). The ceiling is 1000 characters, and it is one constant reached through two hops: QuestionInvariants.QuestionTextMaxLength forwards to QuestionDTO.QuestionTextMaxLength (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Questions/QuestionInvariants.cs:16, value declared at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Questions/QuestionDTO.cs:17), and the domain invariant enforces the same constant (QuestionInvariants.cs:62), so the form limit, the domain limit, and the column width cannot drift apart.
  • Walkthrough: a sealed class whose whole body is an expression-bodied constructor (QuestionCreateRequestValidator.cs:9-10) calling Include(new QuestionTextRules<QuestionCreateRequest>(p => p.QuestionText)).
  • Why it's built this way: extracting field rules into shared generic classes is the module-wide convention; add a constraint once and every request type that includes the rule set picks it up.
  • -
  • Where it's used: discovered by the module's validator scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143) and run by ValidatingCommandDecorator<TCommand, TResult> ahead of CreateQuestionHandler. Covered by QuestionCreateRequestValidatorTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Questions/Validation/QuestionCreateRequestValidatorTests.cs).
  • +
  • Where it's used: discovered by the module's validator scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139) and run by ValidatingCommandDecorator<TCommand, TResult> ahead of CreateQuestionHandler. Covered by QuestionCreateRequestValidatorTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Questions/Validation/QuestionCreateRequestValidatorTests.cs).
  • Caveats / not-in-source: only QuestionText is validated here. QuestionEntity, QuestionType, and QuestionSource are left entirely to the domain invariants inside Question.Create (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Questions/Question.cs:79-85), so an invalid or missing value arrives as an invariant failure rather than a field-level validation error.
  • CreateSpeakerHandler

    @@ -4862,8 +4899,8 @@

    CreateSpeakerHandler

  • Why it's built this way: validation, cache invalidation, and transaction scope are all handled by the pipeline decorators wrapped around this handler (declared by the markers on SpeakerCreateRequest), and the create sequence itself is the framework's, which is why the module-specific part of a create reduces to a log message (ADR-014). [Rubric §1, SOLID]: the handler's one reason to change is the use case's own vocabulary.
  • -
  • Where it's used: registered by the framework's command-handler scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:105-109, driven from MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143); injected into SpeakersController as ICommandHandler<SpeakerCreateRequest, Result<SpeakerDTO>> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:44) and dispatched by its POST /Speakers override, which delegates to the base controller and then evicts the conference:speakers and conference output-cache tags (SpeakersController.cs:298-307). The action is gated on the SpeakersManage permission (SpeakersController.cs:299). Covered by CreateSpeakerHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/CreateSpeakerHandlerTests.cs).
  • -
  • Caveats / not-in-source: nothing here guards against a caller re-submitting an id that already exists; the insert simply fails on the primary key and surfaces through the shared exception handling. The Sessionize import path relies on that, since it supplies the Sessionize GUID as the id (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/SpeakerSyncStrategy.cs:126), but no comment in this file records the expectation.
  • +
  • Where it's used: registered by the framework's command-handler scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:105-109, driven from MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139); injected into SpeakersController as ICommandHandler<SpeakerCreateRequest, Result<SpeakerDTO>> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:44) and dispatched by its POST /Speakers override, which delegates to the base controller and then evicts the conference:speakers and conference output-cache tags (SpeakersController.cs:307-316). The action is gated on the SpeakersManage permission (SpeakersController.cs:308). Covered by CreateSpeakerHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/CreateSpeakerHandlerTests.cs).
  • +
  • Caveats / not-in-source: nothing here guards against a caller re-submitting an id that already exists; the insert simply fails on the primary key and surfaces through the shared exception handling. The Sessionize import path relies on that, since it supplies the Sessionize GUID as the id (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/RefreshFromSessionize/SpeakerSyncStrategy.cs:134), but no comment in this file records the expectation.
  • CreateQuestionHandler

    @@ -4883,7 +4920,7 @@

    CreateQuestionHandler

  • Why it's built this way: the class comment contrasts this slice with the session create path (CreateQuestionHandler.cs:36-38): there is no explicit-id branch here, because this handler always overrides the caller id, which is precisely what makes every attempt retryable. A handler that sometimes honored a caller id could not blindly recompute on collision.
  • -
  • Where it's used: registered by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143); injected into QuestionsController as ICommandHandler<QuestionCreateRequest, Result<QuestionDTO>> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Questions/QuestionsController.cs:37) and dispatched by its POST /Questions override, which delegates to the base controller and then evicts the conference:questions output-cache tag (QuestionsController.cs:95-103). The whole controller is gated on the QuestionsManage permission except the explicitly anonymous read actions (QuestionsController.cs:34, QuestionsController.cs:46). Covered by CreateQuestionHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Questions/UseCases/CreateQuestionHandlerTests.cs).
  • +
  • Where it's used: registered by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139); injected into QuestionsController as ICommandHandler<QuestionCreateRequest, Result<QuestionDTO>> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Questions/QuestionsController.cs:37) and dispatched by its POST /Questions override, which delegates to the base controller and then evicts the conference:questions output-cache tag (QuestionsController.cs:98-106). The whole controller is gated on the QuestionsManage permission except the explicitly anonymous read actions (QuestionsController.cs:34, QuestionsController.cs:49). Covered by CreateQuestionHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Questions/UseCases/CreateQuestionHandlerTests.cs).
  • Caveats / not-in-source: the range read materializes every manual-range question on every create attempt to compute one maximum. At the conference's question volume that is negligible, but nothing in the file bounds it, and no comment records the trade-off.
  • UserRegisteredHandler

    @@ -4907,7 +4944,7 @@

    UserRegisteredHandler

  • Why it's built this way: the auto-link is deliberately eventually consistent (UserRegisteredHandler.cs:39-44). A brand-new user's first token does not carry the speaker_id claim; it appears on the next token refresh after this handler completes. The class comment also records why the handler does not evict the 5-minute SpeakersCache output cache: doing so would require an ASP.NET Core dependency the Application layer must not take, so the cache simply expires.
  • -
  • Where it's used: registered as a singleton by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143, scanning rule at MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:200-204) and fed by the Conference service host, which wires the generic IntegrationEventConsumer<T> adapter for this event with x.RegisterIntegrationEventConsumer<UserRegistered>() (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:411, explained at Program.cs:363-367). Its producer is Identity's registration path, which raises UserRegistered on the user aggregate. Covered by UserRegisteredHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Users/IntegrationEventHandlers/UserRegisteredHandlerTests.cs).
  • +
  • Where it's used: registered as a singleton by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139, scanning rule at MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:200-204) and fed by the Conference service host, which wires the generic IntegrationEventConsumer<T> adapter for this event with x.RegisterIntegrationEventConsumer<UserRegistered>() (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:413, explained at Program.cs:363-367). Its producer is Identity's registration path, which raises UserRegistered on the user aggregate. Covered by UserRegisteredHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Users/IntegrationEventHandlers/UserRegisteredHandlerTests.cs).
  • Caveats / not-in-source: the email query loads every matching speaker with asTracking: true (UserRegisteredHandler.cs:163-167); nothing in the file bounds that set, and nothing states a policy for what a duplicate speaker email is supposed to mean. The unique-index race described at UserRegisteredHandler.cs:112-115 (two registrations matching one speaker concurrently) is expected to surface as a DbUpdateException; that the database actually enforces a unique index on Speaker.LinkedUserId is asserted by the comment, not visible in this file.
  • LinkUserToSpeakerCommand

    @@ -4929,10 +4966,10 @@

    RemoveSpeakerCategoryItemCommand

    • What it is: the mirror image of AddSpeakerCategoryItemCommand, the message that detaches one category-item tag from a speaker. Two positional parameters: the owning SpeakerId and the SpeakerCategoryItemId of the join entity to remove (RemoveSpeakerCategoryItemCommand.cs:12-14).
    • Depends on: ICacheInvalidating, the only interface it implements (RemoveSpeakerCategoryItemCommand.cs:14); the Speaker domain type, referenced solely to build the cache prefix; and the SpeakerIdentifierType and SpeakerCategoryItemIdentifierType module aliases (ADR-048).
    • -
    • Concept reinforced, the remove command addresses the join row, not the tag: the second parameter is the identity of the association (SpeakerCategoryItem), not of the category item being untagged. That asymmetry with the Add command (which takes the CategoryItemId it wants to attach) is deliberate: once the association exists, the REST resource the client holds is the junction row, so a delete names it in the route and takes the speaker id from the query string (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerCategoryItemsController.cs:186-194). [Rubric §9, API and Contract Design] assesses whether the contract addresses the thing the caller actually has a handle on.
    • +
    • Concept reinforced, the remove command addresses the join row, not the tag: the second parameter is the identity of the association (SpeakerCategoryItem), not of the category item being untagged. That asymmetry with the Add command (which takes the CategoryItemId it wants to attach) is deliberate: once the association exists, the REST resource the client holds is the junction row, so a delete names it in the route and takes the speaker id from the query string (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerCategoryItemsController.cs:194-202). [Rubric §9, API and Contract Design] assesses whether the contract addresses the thing the caller actually has a handle on.
    • Walkthrough: a sealed record whose whole body is one expression-bodied member, CachePrefix => $"{typeof(Speaker).FullName}:" (RemoveSpeakerCategoryItemCommand.cs:16-17). That is the same prefix every other speaker write declares, so an untag flushes the whole cached speaker read surface rather than trying to surgically evict the one nested collection (ADR-026). Note the absence of ITransactional: the write touches a single aggregate in a single SaveChangesAsync, so there is nothing to keep atomic across contexts (contrast UnlinkUserFromSpeakerCommand, below).
    • Why it's built this way: the message declares its cross-cutting effects and the pipeline applies them (ADR-014); the handler stays free of cache code. [Rubric §12, Performance & Scalability].
    • -
    • Where it's used: constructed by the DELETE /SpeakerCategoryItems/{id} action of SpeakerCategoryItemsController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerCategoryItemsController.cs:186-194, handler injected at SpeakerCategoryItemsController.cs:51), a controller gated on the SpeakersManage permission at the class level rather than per action (SpeakerCategoryItemsController.cs:47, ADR-020); handled by RemoveSpeakerCategoryItemHandler.
    • +
    • Where it's used: constructed by the DELETE /SpeakerCategoryItems/{id} action of SpeakerCategoryItemsController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerCategoryItemsController.cs:194-202, handler injected at SpeakerCategoryItemsController.cs:51), a controller gated on the SpeakersManage permission at the class level rather than per action (SpeakerCategoryItemsController.cs:47, ADR-020); handled by RemoveSpeakerCategoryItemHandler.

    UnlinkUserFromSpeakerCommand

    @@ -4970,7 +5007,7 @@

    ActivityCreateRequest

  • Concept reinforced, the shared-field interface: implementing IActivityFieldsRequest (ActivityCreateRequest.cs:11, contract at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Activities/Validation/IActivityFieldsRequest.cs:12-37) is what lets one generic rule set validate both the create and the update body. The interface deliberately omits EventId (IActivityFieldsRequest.cs:8-11): only the create request carries the owning event, because moving an activity between events is a create plus a delete. [Rubric §15, Best Practices & Code Quality].
  • Walkthrough: a record class with init-only members. CachePrefix => $"{typeof(Activity).FullName}:" (ActivityCreateRequest.cs:13-14) is the invalidation tag. Id (ActivityCreateRequest.cs:17) is ignored as described above. Only Name is required (ActivityCreateRequest.cs:20). Description is optional (ActivityCreateRequest.cs:23); StartTime and EndTime (ActivityCreateRequest.cs:26, ActivityCreateRequest.cs:29) are plain DateTime values in the owning event's wall-clock time. The venue trio VenueName, VenueAddress, and VenueUrl (ActivityCreateRequest.cs:32, ActivityCreateRequest.cs:35, ActivityCreateRequest.cs:38) is carried on the activity rather than inherited from the event, because an after-party is usually somewhere else; an empty VenueName means the main conference venue. SortOrder (ActivityCreateRequest.cs:41) breaks ties between activities that start at the same minute, and EventId (ActivityCreateRequest.cs:44) is the owning event.
  • Why it's built this way: keeping the venue on the activity instead of the event is a modeling decision: an activity is deliberately not a session, has no room and no speakers, and frequently happens off site. [Rubric §4, Domain-Driven Design].
  • -
  • Where it's used: bound by ActivitiesController on the ActivitiesManage-gated POST /Activities (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Activities/ActivitiesController.cs:162-171) and used as the fourth generic argument of its base controller (ActivitiesController.cs:49-50); validated by ActivityCreateRequestValidator; mapped by ActivityCreateRequestMapper; handled by CreateActivityHandler. The update side has its own request, ActivityUpdateRequest, applied by ActivityUpdateApplier.
  • +
  • Where it's used: bound by ActivitiesController on the ActivitiesManage-gated POST /Activities (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Activities/ActivitiesController.cs:170-179) and used as the fourth generic argument of its base controller (ActivitiesController.cs:49-50); validated by ActivityCreateRequestValidator; mapped by ActivityCreateRequestMapper; handled by CreateActivityHandler. The update side has its own request, ActivityUpdateRequest, applied by ActivityUpdateApplier.
  • ConferenceCategoryEntityQueryService

    @@ -4985,25 +5022,26 @@

    ConferenceCategoryEntityQueryServi
  • Where it's used: registered as the closed-generic read service for Category alongside the module's other category registrations (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs, category registration block). Covered by ConferenceCategoryEntityQueryServiceTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Categories/ConferenceCategoryEntityQueryServiceTests.cs).
  • Caveats / not-in-source: nothing in this file states whether CategoryItems.Count() includes soft-deleted items; that depends on the global query filter applied where the EF expression is translated, not on anything visible here.
  • -

    DeleteConferenceCategoryHandler

    +

    UserDeletedFeedbackHandler

    -

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Categories.UseCases.Delete · MMCA.ADC.Conference.Application/Categories/UseCases/Delete/DeleteConferenceCategoryHandler.cs:14 · Level 9 · class (sealed)

    +

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Users.IntegrationEventHandlers · MMCA.ADC.Conference.Application/Users/IntegrationEventHandlers/UserDeletedFeedbackHandler.cs:23 · Level 9 · class (sealed partial)

      -
    • What it is: the delete handler for a conference Category. It is a four-line subclass of the framework's generic delete workflow that changes exactly two things: the name a not-found failure reports, and the child collection the aggregate's cascade needs loaded.
    • -
    • Depends on: DeleteEntityHandler<TEntity, TIdentifierType> closed over Category and ConferenceCategoryIdentifierType (DeleteConferenceCategoryHandler.cs:14-15); IUnitOfWork, passed straight through to the base (DeleteConferenceCategoryHandler.cs:14); the CategoryItem child collection, referenced by name only.
    • -
    • Concept introduced, a generic handler with two structural extension points: [Rubric §2, Design Patterns] and [Rubric §1, SOLID]. The base is deliberately left unsealed and its workflow split into overridable steps, because the two things a real delete outgrows are structural rather than behavioral (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/DeleteEntityHandler.cs:13-32): the child collections the aggregate's own Delete() cascade has to see, declared in Includes, and a cross-aggregate invariant that must refuse the delete first, implemented in OnDeletingAsync. A subclass that overrides neither behaves exactly like the base, down to the query it issues. This handler overrides only Includes, so it keeps the base's OnDeletingAsync acceptance (DeleteEntityHandler.cs:126-130). The load-bearing consequence is in the base's LoadAsync (DeleteEntityHandler.cs:101-114): with no includes it issues the bare by-id query, and with includes it switches to the eager-loading overload under AsTracking (DeleteEntityHandler.cs:64). Declare nothing and the cascade sees an empty in-memory collection, which is how child rows survive a soft-deleted parent.
    • -
    • Concept reinforced, the aggregate owns the cascade: Category.Delete() is where BR-71 actually lives (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/Category.cs:102-114): it combines DeleteChildren<CategoryItem, CategoryItemIdentifierType>(_categoryItems) with base.Delete() in one Result.Combine (Category.cs:106-108), so a failing child aggregates into the combined result instead of leaving a half-applied delete, and raises CategoryChanged with DomainEntityState.Deleted only on success (Category.cs:110-111). The base handler raises no events of its own by design (DeleteEntityHandler.cs:23-26), which is what keeps the generic path and a hand-written one indistinguishable from outside. [Rubric §4, Domain-Driven Design].
    • +
    • What it is: the Conference-side subscriber to Identity's UserDeleted integration event. When an account is erased, this handler removes the session and event feedback answers that account wrote, because those answers are keyed by their CreatedBy user id, may hold free text, and live in the Conference database, which the Identity-side erasure cannot reach (UserDeletedFeedbackHandler.cs:11-14, PRIVACY.md section 5).
    • +
    • Depends on: ScopedIntegrationEventHandlerBase<TIntegrationEvent> closed over UserDeleted (UserDeletedFeedbackHandler.cs:23-26); IServiceScopeFactory and ILogger<UserDeletedFeedbackHandler>, both forwarded to the base; IUnitOfWork, resolved from the per-delivery provider (UserDeletedFeedbackHandler.cs:35); the Session and Event aggregates and their SessionQuestionAnswer and EventQuestionAnswer children.
    • +
    • Concept reinforced, erasure that crosses a database boundary is an event, not a join: Identity owns the account and Conference owns the answers, in separate databases with no foreign key between them (ADR-006). Identity cannot delete rows it cannot see, so it publishes the fact and each owning module erases its own copy. This is the same subscriber shape as UserRegisteredHandler: the base opens the async DI scope (MMCA.Common/Source/Core/MMCA.Common.Application/DomainEvents/ScopedIntegrationEventHandlerBase.cs:45-51) and wraps the body in a log-and-rethrow exception filter (ScopedIntegrationEventHandlerBase.cs:57), so the subclass writes only HandleScopedAsync (UserDeletedFeedbackHandler.cs:29-47). The class remarks state the consequence directly: exceptions propagate so a failed erasure is retried rather than acked away (UserDeletedFeedbackHandler.cs:16-18). [Rubric §7, Microservices Readiness], [Rubric §29, Resilience].
    • +
    • Concept reinforced, idempotent by filter, not by inbox: delivery is at-least-once, so redelivery is normal (ADR-021). The handler is idempotent because both queries match only active answers (answer.CreatedBy == userId && !answer.IsDeleted, UserDeletedFeedbackHandler.cs:56 and UserDeletedFeedbackHandler.cs:82): once removed, a second delivery finds nothing, both counters are zero, and the early return skips the save entirely (UserDeletedFeedbackHandler.cs:40-43). UserDeletedFeedbackHandlerTests pins exactly that (HandleAsync_OnRedelivery_WritesOnlyOnce). [Rubric §11, Security] assesses whether personal data is erased everywhere it is stored, not only in the module that owns the account.
    • Walkthrough:
        -
      • The whole class is a primary constructor forwarding IUnitOfWork to the base (DeleteConferenceCategoryHandler.cs:14-15) plus two property overrides.
      • -
      • HandlerName => nameof(DeleteConferenceCategoryHandler) (DeleteConferenceCategoryHandler.cs:18) replaces the base default, which reports the open generic name DeleteEntityHandler (DeleteEntityHandler.cs:50). The value is stamped into the Source of the NotFound failure (DeleteEntityHandler.cs:74), so an API error names the module path rather than a framework type.
      • -
      • Includes => [nameof(Category.CategoryItems)] (DeleteConferenceCategoryHandler.cs:21) is the one behavioral change, naming the read-only child collection exposed at Category.cs:31.
      • -
      • Everything else runs in the base HandleAsync (DeleteEntityHandler.cs:67-89): resolve the repository from the unit of work (DeleteEntityHandler.cs:71), load (DeleteEntityHandler.cs:72), fail NotFound when the row is gone (DeleteEntityHandler.cs:73-74), run the pre-delete refusal hook (DeleteEntityHandler.cs:76-78), call entity.Delete() (DeleteEntityHandler.cs:80), and save plus log only on success (DeleteEntityHandler.cs:81-86).
      • +
      • HandleScopedAsync reads UserId off the event, resolves the unit of work, and runs the two removals in sequence (UserDeletedFeedbackHandler.cs:34-38).
      • +
      • RemoveSessionAnswersAsync (UserDeletedFeedbackHandler.cs:49-73) loads, with tracking and the SessionQuestionAnswers collection included, only the sessions holding at least one active answer by that user (UserDeletedFeedbackHandler.cs:54-58). For each session it snapshots the matching answer ids into a list first (UserDeletedFeedbackHandler.cs:63-68), because removal mutates the collection being enumerated, then calls session.RemoveSessionQuestionAnswer(answerId) and counts only the successes (UserDeletedFeedbackHandler.cs:69).
      • +
      • RemoveEventAnswersAsync (UserDeletedFeedbackHandler.cs:75-99) is the same algorithm over Event and EventQuestionAnswers, ending in conferenceEvent.RemoveEventQuestionAnswer(answerId) (UserDeletedFeedbackHandler.cs:95).
      • +
      • Removal goes through the aggregate root, never a direct repository delete: Session.RemoveSessionQuestionAnswer (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:554-565) and Event.RemoveEventQuestionAnswer (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/Event.cs:666-677) each raise their ...QuestionAnswerChanged domain event with DomainEntityState.Deleted, so downstream projections see the erasure like any other removal. [Rubric §4, Domain-Driven Design].
      • +
      • Both aggregate types are saved in one SaveChangesAsync (UserDeletedFeedbackHandler.cs:45), and the source-generated LogErased partial records both counts at Information level (UserDeletedFeedbackHandler.cs:46, declared at UserDeletedFeedbackHandler.cs:101-102).
    • -
    • Why it's built this way: the same shape recurs for every aggregate whose delete cascades, which is why DeleteEventHandler and DeleteSessionHandler are their own subclasses while aggregates with no owned children (Speaker, Question) are registered against the raw generic instead (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:85, DependencyInjection.cs:94). [Rubric §15, Best Practices & Code Quality]: the delete workflow exists once.
    • -
    • Where it's used: registered explicitly, not by the convention scan, as the implementation of ICommandHandler<DeleteEntityCommand<Category, ConferenceCategoryIdentifierType>, Result> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:89); invoked through the decorator pipeline by the DELETE /ConferenceCategories/{id} override of ConferenceCategoriesController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Categories/ConferenceCategoriesController.cs:137-146, handler injected at ConferenceCategoriesController.cs:40), a controller gated on CategoriesManage at the class level (ConferenceCategoriesController.cs:35) that evicts the conference:categories output-cache tag after the delete (ConferenceCategoriesController.cs:144).
    • -
    • Caveats / not-in-source: the explicit registration is what makes this subclass reachable. Because the command type is the closed generic DeleteEntityCommand<TEntity, TIdentifierType>, the convention scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:246-250) would also see it; nothing in this file records which registration wins, and the module uses TryAddScoped (DependencyInjection.cs:78) so ordering decides. There is no dedicated unit-test file for this handler in MMCA.ADC.Conference.Application.Tests/Categories/UseCases/.
    • +
    • Why it's built this way: the handler deliberately takes no new dependency beyond the unit of work, reuses the aggregates' own removal methods, and relies on the base for scope and retry semantics, so the only new code is the "which rows belong to this user" query.
    • +
    • Where it's used: registered by the Application convention scan like every integration-event handler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139) and fed by the Conference service host through .RegisterIntegrationEventConsumer<UserDeleted>() (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:414, explained at Program.cs:406-408), which wires the generic IntegrationEventConsumer<TEvent> adapter. Covered by UserDeletedFeedbackHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Users/IntegrationEventHandlers/UserDeletedFeedbackHandlerTests.cs:22, UserDeletedFeedbackHandlerTests.cs:44).
    • +
    • Caveats / not-in-source: "delete" here is a soft delete. Both domain methods go through RemoveChildOrNotFound, which calls child.Delete() and documents its return as "the soft-deleted child" (MMCA.Common/Source/Core/MMCA.Common.Domain/Entities/AuditableAggregateRootEntity.cs:155-173, ADR-005). Whether the answer's free text is later scrubbed or the row physically purged is Not determinable from this file. The queries load whole sessions and events with their full answer collections, unbounded, for one user's rows; for the conference's scale that is cheap, but nothing in the file bounds it.

    ActivityCreateRequestMapper

    @@ -5047,7 +5085,7 @@

    CreateActivityHandler

  • Why it's built this way: the pipeline supplies everything this handler does not: ValidatingCommandDecorator<TCommand, TResult> runs ActivityCreateRequestValidator first, and CachingCommandDecorator<TCommand, TResult> acts on the CachePrefix the request declares (ADR-014, chain at MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:134-140). [Rubric §12, Performance & Scalability].
  • -
  • Where it's used: registered by the command-handler scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:246-250, driven from MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143); injected into ActivitiesController as ICommandHandler<ActivityCreateRequest, Result<ActivityDTO>> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Activities/ActivitiesController.cs:42) and handed to its base controller (ActivitiesController.cs:49-50), whose POST /Activities the controller overrides only to evict the conference:activities and conference output-cache tags after the create (ActivitiesController.cs:162-171). The action is gated on the ActivitiesManage permission (ActivitiesController.cs:163) while the read endpoints stay anonymous (ActivitiesController.cs:82, ActivitiesController.cs:98). Covered by CreateActivityHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Activities/UseCases/CreateActivityHandlerTests.cs).
  • +
  • Where it's used: registered by the command-handler scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:246-250, driven from MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139); injected into ActivitiesController as ICommandHandler<ActivityCreateRequest, Result<ActivityDTO>> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Activities/ActivitiesController.cs:42) and handed to its base controller (ActivitiesController.cs:49-50), whose POST /Activities the controller overrides only to evict the conference:activities and conference output-cache tags after the create (ActivitiesController.cs:170-179). The action is gated on the ActivitiesManage permission (ActivitiesController.cs:171) while the read endpoints stay anonymous (ActivitiesController.cs:90, ActivitiesController.cs:106). Covered by CreateActivityHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Activities/UseCases/CreateActivityHandlerTests.cs).
  • CategoryItemNavigationPopulator

    @@ -5072,7 +5110,7 @@

    ConferenceCategoryNavigationPopul
  • Concept introduced in this group, declarative child loading: the counterpart to the FK direction taught on ActivityNavigationPopulator. Three things change. First, ChildNavigationDescriptor.RequiresChildren is hard-coded true (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Navigation/ChildNavigationDescriptor.cs:25), so the load is gated on the caller's includeChildren flag (DeclarativeNavigationPopulator.cs:36), matching the [Navigation(IsCollection = true)] attribute that puts the property in the child bucket during discovery (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/NavigationMetadataProvider.cs:78). Second, the key pair inverts: the parent supplies its own primary key and the child supplies the FK that points back. Third, the assignment cannot be a property set, because the collection is exposed as IReadOnlyCollection<CategoryItem> over a private list (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/Category.cs:31).
  • Walkthrough: one descriptor.
    • PropertyName = nameof(Category.CategoryItems) (:17), ParentKeySelector = e => e.Id (:18), ChildForeignKeySelector = child => child.CategoryId (:19, the FK declared at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/CategoryItem.cs:27).
    • -
    • AssignAction = (e, categoryItems) => e.SetCategoryItems(categoryItems) (:20) calls an internal aggregate mutator (Category.cs:201), reachable from this assembly only because the Domain project grants InternalsVisibleTo to MMCA.ADC.Conference.Application. Nothing outside the module can replace the collection.
    • +
    • AssignAction = (e, categoryItems) => e.SetCategoryItems(categoryItems) (:20) calls an internal aggregate mutator (Category.cs:203), reachable from this assembly only because the Domain project grants InternalsVisibleTo to MMCA.ADC.Conference.Application. Nothing outside the module can replace the collection.
    • The load routes through NavigationLoader.LoadChildrenPropertyAsync (ChildNavigationDescriptor.cs:41, implemented at MMCA.Common/Source/Core/MMCA.Common.Application/Services/NavigationLoader.cs:118), which is one batched WHERE CategoryId IN (...parentIds) query per descriptor, not one per category.
  • @@ -5092,14 +5130,14 @@

    UnlinkUserFromSpeakerHandler

  • Walkthrough:
    • EntityId (UnlinkUserFromSpeakerHandler.cs:31) is the one abstract member the core requires (MutateEntityHandlerBase.cs:81): it returns command.SpeakerId, which is all the workflow needs to load the aggregate.
    • Includes is left at the base default of empty (MutateEntityHandlerBase.cs:70), because the link is a scalar column on the aggregate root and nothing nested is read.
    • -
    • MutateAsync (UnlinkUserFromSpeakerHandler.cs:34-53) null-guards both arguments (UnlinkUserFromSpeakerHandler.cs:39-40), then captures previousUserId BEFORE calling entity.UnlinkUser() (UnlinkUserFromSpeakerHandler.cs:42-43). This is the load-bearing line: the domain method clears LinkedUserId (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:301), so reading it afterwards would yield null and the event could not name the user that was unlinked. The aggregate itself rejects an unlinked speaker with Speaker.NotLinked (Speaker.cs:292-299).
    • +
    • MutateAsync (UnlinkUserFromSpeakerHandler.cs:34-53) null-guards both arguments (UnlinkUserFromSpeakerHandler.cs:39-40), then captures previousUserId BEFORE calling entity.UnlinkUser() (UnlinkUserFromSpeakerHandler.cs:42-43). This is the load-bearing line: the domain method clears LinkedUserId (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:303), so reading it afterwards would yield null and the event could not name the user that was unlinked. The aggregate itself rejects an unlinked speaker with Speaker.NotLinked (Speaker.cs:294-301).
    • On success, and only when a previous user actually existed, it raises the event on the aggregate (UnlinkUserFromSpeakerHandler.cs:44-50) and returns the domain Result unchanged (UnlinkUserFromSpeakerHandler.cs:52), so a domain rejection reaches the API with its own codes intact.
    • LogMutated (UnlinkUserFromSpeakerHandler.cs:56-57) is the post-save log hook, calling the source-generated [LoggerMessage] partial declared at UnlinkUserFromSpeakerHandler.cs:59-60. [Rubric §13, Observability and Operability].
  • Why it's built this way: Conference and Identity own separate databases (ADR-006), so there is no foreign key to cascade and the back-link has to travel as an event; the ITransactional marker on the command plus the in-mutation raise are together what make that event as durable as the write it describes.
  • -
  • Where it's used: registered by the module's application scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143); invoked by DELETE /Speakers/{id}/link on SpeakerLinksController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerLinksController.cs:59-74, handler injected at SpeakerLinksController.cs:35), which evicts the speaker output-cache tags and answers 204 No Content (SpeakerLinksController.cs:72-73). The emitted event is consumed on the Identity side to clear User.LinkedSpeakerId. Covered by UnlinkUserFromSpeakerHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/UnlinkUserFromSpeakerHandlerTests.cs).
  • -
  • Caveats / not-in-source: the previousUserId.HasValue test (UnlinkUserFromSpeakerHandler.cs:44) can never be false on the success path, because UnlinkUser() fails when nothing is linked (Speaker.cs:292-299). It is defensive, not a live branch. The handler also does not override RowVersion (MutateEntityHandlerBase.cs:91), so the unlink is unconditional: it carries no If-Match precondition, unlike the activity update path.
  • +
  • Where it's used: registered by the module's application scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139); invoked by DELETE /Speakers/{id}/link on SpeakerLinksController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerLinksController.cs:59-74, handler injected at SpeakerLinksController.cs:35), which evicts the speaker output-cache tags and answers 204 No Content (SpeakerLinksController.cs:72-73). The emitted event is consumed on the Identity side to clear User.LinkedSpeakerId. Covered by UnlinkUserFromSpeakerHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/UnlinkUserFromSpeakerHandlerTests.cs).
  • +
  • Caveats / not-in-source: the previousUserId.HasValue test (UnlinkUserFromSpeakerHandler.cs:44) can never be false on the success path, because UnlinkUser() fails when nothing is linked (Speaker.cs:294-301). It is defensive, not a live branch. The handler also does not override RowVersion (MutateEntityHandlerBase.cs:91), so the unlink is unconditional: it carries no If-Match precondition, unlike the activity update path.
  • RemoveSpeakerCategoryItemHandler

    @@ -5109,7 +5147,7 @@

    RemoveSpeakerCategoryItemHandler

  • What it is: the handler for RemoveSpeakerCategoryItemCommand. It is the smallest write handler in this chapter: four members, none of them longer than a line, over the framework's remove-a-child workflow.
  • Depends on: RemoveChildEntityHandlerBase<TCommand, TParent, TIdentifierType> closed over the command, Speaker, and SpeakerIdentifierType (RemoveSpeakerCategoryItemHandler.cs:16); IUnitOfWork, forwarded to the base (RemoveSpeakerCategoryItemHandler.cs:14); the Speaker aggregate and its SpeakerCategoryItem child; Result; Microsoft.Extensions.Logging.
  • Concept introduced, a base class that makes one override mandatory: [Rubric §1, SOLID] and [Rubric §15, Best Practices]. RemoveChildEntityHandlerBase<TCommand, TParent, TIdentifierType> adds nothing to MutateEntityHandlerBase<TCommand, TEntity, TIdentifierType> except one line: protected abstract override IEnumerable<string> Includes { get; } (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/ChildEntityHandlerBase.cs:143-153). Re-declaring an inherited virtual as abstract turns an easy-to-forget override into a compile error. The reason is stated directly above it (ChildEntityHandlerBase.cs:130-133): a remove that cannot see the child collection cannot find the child and reports a wrong NotFound. This is a type-level fix for a class of bug that used to be a convention.
  • -
  • Concept reinforced, mutate only through the aggregate root: the handler never touches the join entity. The workflow loads the speaker with its SpeakerCategoryItems collection and MutateAsync hands the id to entity.RemoveSpeakerCategoryItem(...) (RemoveSpeakerCategoryItemHandler.cs:29), which resolves the child through the shared RemoveChildOrNotFound helper, soft-deletes it, and raises SpeakerCategoryItemChanged (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:374-385). [Rubric §4, Domain-Driven Design] assesses whether the aggregate boundary is respected on writes: a handler that deleted the join row through its own repository would bypass the domain event and the aggregate's own not-found error.
  • +
  • Concept reinforced, mutate only through the aggregate root: the handler never touches the join entity. The workflow loads the speaker with its SpeakerCategoryItems collection and MutateAsync hands the id to entity.RemoveSpeakerCategoryItem(...) (RemoveSpeakerCategoryItemHandler.cs:29), which resolves the child through the shared RemoveChildOrNotFound helper, soft-deletes it, and raises SpeakerCategoryItemChanged (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:376-387). [Rubric §4, Domain-Driven Design] assesses whether the aggregate boundary is respected on writes: a handler that deleted the join row through its own repository would bypass the domain event and the aggregate's own not-found error.
  • Walkthrough:
    • Primary constructor (RemoveSpeakerCategoryItemHandler.cs:13-16): IUnitOfWork straight to the base, plus a typed ILogger<RemoveSpeakerCategoryItemHandler> the class keeps for its own log line. No DTO mapper, because a remove returns the non-generic Result.
    • Includes => [nameof(Speaker.SpeakerCategoryItems)] (RemoveSpeakerCategoryItemHandler.cs:19) is the mandatory override, naming the collection exposed at Speaker.cs:67. It reaches the load through the core's default LoadAsync (MutateEntityHandlerBase.cs:152-160), which passes it plus AsTracking to the repository's eager-loading by-id overload.
    • @@ -5119,7 +5157,7 @@

      RemoveSpeakerCategoryItemHandler

  • Why it's built this way: leaving removal semantics in the aggregate, cache eviction on the command (ICacheInvalidating), and load-mutate-save in the base leaves the handler with nothing but the four facts that are genuinely its own: which collection to load, which id to load, which domain method to call, and what to log. [Rubric §15, Best Practices & Code Quality].
  • -
  • Where it's used: registered by the module's application scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143, scanning rule at MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:105-109); invoked through the decorator pipeline by SpeakerCategoryItemsController's delete action (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerCategoryItemsController.cs:186-203), which then evicts both parents' output-cache tags (SpeakerCategoryItemsController.cs:201). Covered by RemoveSpeakerCategoryItemHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/RemoveSpeakerCategoryItemHandlerTests.cs).
  • +
  • Where it's used: registered by the module's application scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139, scanning rule at MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:105-109); invoked through the decorator pipeline by SpeakerCategoryItemsController's delete action (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerCategoryItemsController.cs:194-211), which then evicts both parents' output-cache tags (SpeakerCategoryItemsController.cs:209). Covered by RemoveSpeakerCategoryItemHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/RemoveSpeakerCategoryItemHandlerTests.cs).
  • LinkUserToSpeakerHandler

    @@ -5132,12 +5170,12 @@

    LinkUserToSpeakerHandler

  • Walkthrough: a primary constructor (LinkUserToSpeakerHandler.cs:25-28) and three overrides.
    • EntityId names the aggregate to load, command.SpeakerId (LinkUserToSpeakerHandler.cs:31). The base does the load and returns Error.NotFound stamped with the handler name and the entity type name when the speaker is gone (MutateEntityHandlerBase.cs:281-283).
    • MutateAsync (LinkUserToSpeakerHandler.cs:34-67) is the whole use case. It null-guards both arguments (LinkUserToSpeakerHandler.cs:39-40), then runs the BR-208 uniqueness guard: it reaches a second repository through the base's exposed UnitOfWork (LinkUserToSpeakerHandler.cs:43), queries every speaker whose LinkedUserId equals the target user (LinkUserToSpeakerHandler.cs:44-47), and fails with Error.Invariant(code: "Speaker.UserAlreadyLinked", ...) if any OTHER speaker already holds that link (LinkUserToSpeakerHandler.cs:48-55). The s.Id != command.SpeakerId test is what makes re-linking the same pair a no-op rather than an error.
    • -
    • entity.LinkUser(command.UserId) (LinkUserToSpeakerHandler.cs:57) is the domain decision. The aggregate refuses a speaker that is already linked, returning Speaker.AlreadyLinked (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:272-281), so the handler owns only the cross-row rule and the entity owns its own. Only on success does the integration event go on the aggregate (LinkUserToSpeakerHandler.cs:58-64), and the domain Result is returned unchanged (LinkUserToSpeakerHandler.cs:66), so a rejection reaches the API with its error codes intact.
    • +
    • entity.LinkUser(command.UserId) (LinkUserToSpeakerHandler.cs:57) is the domain decision. The aggregate refuses a speaker that is already linked, returning Speaker.AlreadyLinked (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:274-283), so the handler owns only the cross-row rule and the entity owns its own. Only on success does the integration event go on the aggregate (LinkUserToSpeakerHandler.cs:58-64), and the domain Result is returned unchanged (LinkUserToSpeakerHandler.cs:66), so a rejection reaches the API with its error codes intact.
    • LogMutated (LinkUserToSpeakerHandler.cs:70-71) forwards to the generated LogUserLinkedToSpeaker (LinkUserToSpeakerHandler.cs:73-74); the base calls it only after a successful save (MutateEntityHandlerBase.cs:318).
  • Why it's built this way: splitting the rules (uniqueness across speakers in the handler, "already linked?" inside the aggregate) keeps each check where the data for it lives, and putting the event raise inside MutateAsync rather than after the save is a deliberate durability fix rather than a style choice.
  • -
  • Where it's used: registered by the Conference application scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143); invoked through the decorator pipeline by the PUT /Speakers/{id}/link action (SpeakerLinksController at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerLinksController.cs:40-56), which returns 204 No Content and evicts the speaker output-cache tags on success (SpeakerLinksController.cs:54-55). The emitted event is consumed on the Identity side to set User.LinkedSpeakerId. This organizer-driven path is also the deliberate fallback for speakers the automatic email match in UserRegisteredHandler cannot claim. Covered by LinkUserToSpeakerHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/LinkUserToSpeakerHandlerTests.cs).
  • +
  • Where it's used: registered by the Conference application scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139); invoked through the decorator pipeline by the PUT /Speakers/{id}/link action (SpeakerLinksController at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerLinksController.cs:40-56), which returns 204 No Content and evicts the speaker output-cache tags on success (SpeakerLinksController.cs:54-55). The emitted event is consumed on the Identity side to set User.LinkedSpeakerId. This organizer-driven path is also the deliberate fallback for speakers the automatic email match in UserRegisteredHandler cannot claim. Covered by LinkUserToSpeakerHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/UseCases/LinkUserToSpeakerHandlerTests.cs).
  • Caveats / not-in-source: the BR-208 guard is a read-then-write with no lock, so two concurrent links naming the same user could both pass it; nothing in this file says what settles that race. The base also supports an optimistic-concurrency stamp driven by a RowVersion hook (MutateEntityHandlerBase.cs:285-294, ADR-035), which this handler does not override, so the link endpoint is unconditional.
  • SessionBookmarkValidationService

    @@ -5154,31 +5192,9 @@

    SessionBookmarkValidationService

  • Why it's built this way: bookmarks and sessions live in different databases (ADR-006), so Engagement cannot join to a session table to check eligibility; the only correct move is to ask the owner. Keeping the answer in a narrow interface means the question survives the process split unchanged.
  • -
  • Where it's used: registered explicitly, not by the convention scan, as the in-process implementation at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:136; consumed by Engagement's CreateBookmarkHandler (CreateBookmarkHandler.cs:18) and GetUserBookmarksHandler; exposed over the wire by SessionBookmarksGrpcService, which wraps this instance as its inner (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Grpc/SessionBookmarksGrpcService.cs:23). Covered by SessionBookmarkValidationServiceTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/SessionBookmarkValidationServiceTests.cs:14), which pins the type through the interface rather than the concrete class.
  • +
  • Where it's used: registered explicitly, not by the convention scan, as the in-process implementation at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:132; consumed by Engagement's CreateBookmarkHandler (CreateBookmarkHandler.cs:18) and GetUserBookmarksHandler; exposed over the wire by SessionBookmarksGrpcService, which wraps this instance as its inner (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Grpc/SessionBookmarksGrpcService.cs:23). Covered by SessionBookmarkValidationServiceTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/SessionBookmarkValidationServiceTests.cs:14), which pins the type through the interface rather than the concrete class.
  • Caveats / not-in-source: GetSessionIdsByEventAsync loads whole session entities and projects the ids in memory (SessionBookmarkValidationService.cs:47-53) rather than projecting in the query, so the cost scales with session row size, not id count. It also applies no visibility filter: the ids of every non-deleted session in the event are returned, eligible or not, which is safe only because the caller uses them to narrow a bookmark list the user already owns.
  • -

    SessionCategoryItemDTOMapper

    -
    -

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.DTOs · MMCA.ADC.Conference.Application/Sessions/DTOs/SessionCategoryItemDTOMapper.cs:12 · Level 9 · class (sealed partial)

    -
    -
      -
    • What it is: the entity-to-DTO mapper for the SessionCategoryItem join entity, source-generated by Mapperly.
    • -
    • Depends on: IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType> closed over SessionCategoryItem / SessionCategoryItemDTO / SessionCategoryItemIdentifierType (MMCA.ADC.Conference.Application/Sessions/DTOs/SessionCategoryItemDTOMapper.cs:13), and Riok.Mapperly.Abstractions for the [Mapper] attribute (SessionCategoryItemDTOMapper.cs:4, :11).
    • -
    • Concept reinforced, compile-time mapping: the mechanism is taught on SpeakerCategoryItemDTOMapper; this is the session-side twin, the same shape over different types. [Mapper] on a partial class makes the generator emit the body of MapToDTO as plain property assignments, so an unmatched property is a build diagnostic rather than a silent null, and there is no runtime reflection or expression compilation on the read path (ADR-001). [Rubric §12, Performance and Scalability] and [Rubric §15, Best Practices and Code Quality].
    • -
    • Walkthrough: two members. MapToDTO (SessionCategoryItemDTOMapper.cs:16) is partial with no body, which is the generator's hook. MapToDTOs (:19-23) is hand-written: ArgumentNullException.ThrowIfNull then a collection-expression spread over Select(MapToDTO). Declaring the plural on the class is what makes it reachable through the concrete type, which matters because AddSessionCategoryItemHandler injects the concrete mapper, not the interface.
    • -
    • Where it's used: injected into AddSessionCategoryItemHandler (MMCA.ADC.Conference.Application/Sessions/UseCases/AddSessionCategoryItem/AddSessionCategoryItemHandler.cs:18); composed into SessionDTOMapper as a [UseMapper] field (SessionDTOMapper.cs:26-27); resolved by the generic EntityQueryService<TEntity, TEntityDTO, TIdentifierType> registered for this entity (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:123). Registered by the convention scan, self and interfaces, scoped (DependencyInjection.cs:143). Covered by SessionCategoryItemDTOMapperTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/DTOs/SessionCategoryItemDTOMapperTests.cs:8).
    • -
    -

    SessionQuestionAnswerDTOMapper

    -
    -

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.DTOs · MMCA.ADC.Conference.Application/Sessions/DTOs/SessionQuestionAnswerDTOMapper.cs:12 · Level 9 · class (sealed partial)

    -
    -
      -
    • What it is: the Mapperly mapper for SessionQuestionAnswer, the entity that stores one attendee's answer to one session question.
    • -
    • Depends on: IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType> closed over SessionQuestionAnswer / SessionQuestionAnswerDTO / SessionQuestionAnswerIdentifierType (MMCA.ADC.Conference.Application/Sessions/DTOs/SessionQuestionAnswerDTOMapper.cs:13), and Riok.Mapperly.Abstractions (SessionQuestionAnswerDTOMapper.cs:11).
    • -
    • Concept reinforced: none new. Identical in structure to SessionCategoryItemDTOMapper: a partial MapToDTO the generator fills in (SessionQuestionAnswerDTOMapper.cs:16) and a hand-written null-guarded MapToDTOs (:19-23).
    • -
    • Where it's used: injected into AddSessionQuestionAnswerHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/AddSessionQuestionAnswer/AddSessionQuestionAnswerHandler.cs:24), where both the update branch (:96) and the create branch (:116) map through it, and into BatchAddSessionQuestionAnswersHandler, which maps the whole applied set in one spread (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/BatchAddSessionQuestionAnswers/BatchAddSessionQuestionAnswersHandler.cs:25, :66); composed into SessionDTOMapper (SessionDTOMapper.cs:23-24); resolved by the query service registered for this entity (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:126). Covered by SessionQuestionAnswerDTOMapperTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/DTOs/SessionQuestionAnswerDTOMapperTests.cs:8).
    • -
    • Caveats / not-in-source: the DTO shape is decided entirely by the two type definitions and the generated file, which is not in the repository. Whether an answer's author is exposed to a caller is a property question on SessionQuestionAnswerDTO, not something this file controls.
    • -

    SessionQuestionAnswerRules

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions · MMCA.ADC.Conference.Application/Sessions/SessionQuestionAnswerRules.cs:14 · Level 9 · class (internal static)

    @@ -5189,30 +5205,19 @@

    SessionQuestionAnswerRules

  • Concept introduced, a rule module that takes its data as arguments: the two methods are deliberately pure. Neither one loads the parent event or the question it judges; both take an already-read (and possibly null) entity as a parameter, and the XML docs say why: "The parent event is passed in rather than looked up here, so the batch path can read it once for the whole request" (SessionQuestionAnswerRules.cs:17-19), and the same argument for questions at :50-51. That inversion is the whole reason the class exists. A shared rule that did its own reads would make the batch handler issue one event read and one question read per answer; taking the data as an argument lets BatchAddSessionQuestionAnswersHandler read the event once and load every distinct question in one query before it loops (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/BatchAddSessionQuestionAnswers/BatchAddSessionQuestionAnswersHandler.cs:90-97). [Rubric §12, Performance and Scalability] assesses whether a batch operation avoids per-item round-trips; here the fix is a signature decision, not a cache. [Rubric §1, SOLID]: the rule depends on data, not on the way that data is fetched, so both callers can satisfy it differently. [Rubric §15, Best Practices & Code Quality]: adding a sixth rule is one edit that both surfaces inherit, and the two paths cannot drift apart.
  • Walkthrough: two internal static methods, both returning a bare Result and both taking a source string that is stamped onto any failure so the error names the calling handler.
    • EnsureSessionAcceptsFeedback(Session session, Event? parentEvent, string source) (SessionQuestionAnswerRules.cs:24-47) runs three checks in order, each short-circuiting. SessionInvariants.EnsureNotServiceSession for BR-91 (:27-31, defined at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/SessionInvariants.cs:94): a break or a lunch slot takes no feedback. SessionInvariants.EnsureStatusIsEligible for BR-49 (:34-38, defined at SessionInvariants.cs:109), the same allow-list PublicSessionStatusSpecification expresses for reads, here in its compiled-code form against a loaded row. Then BR-108: a null parentEvent becomes Error.NotFound targeted at Event (:41-44), and only a non-null event reaches EventInvariants.EnsureEventIsPublished (:46, defined at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:151). Folding the missing-event case into the rule rather than the caller is what keeps both handlers from having to remember it.
    • -
    • EnsureAnswerIsValid(Question? question, string answerValue, string source) (:56-71) is BR-128 followed by BR-124. The BR-128 branch collapses two failure modes into one error, question is null || question.QuestionEntity != "Session" (:59), returning Error.Validation coded Question.NotFoundOrWrongEntity and targeted at nameof(SessionQuestionAnswer.QuestionId) (:61-65). Deliberately not distinguishing "no such question" from "a speaker question" is a small information-disclosure decision: a caller probing ids learns nothing about which questions exist. [Rubric §11, Security]. BR-124 then delegates the type match to QuestionInvariants.EnsureAnswerValueMatchesQuestionType(question.QuestionType, answerValue, source) (:69-70, defined at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Questions/QuestionInvariants.cs:118), so the knowledge of what a Rating or a Text answer may look like stays in the Domain layer.
    • +
    • EnsureAnswerIsValid(Question? question, string answerValue, string source) (:56-71) is BR-128 followed by BR-124. The BR-128 branch collapses two failure modes into one error, question is null || question.QuestionEntity != "Session" (:59), returning Error.Validation coded Question.NotFoundOrWrongEntity and targeted at nameof(SessionQuestionAnswer.QuestionId) (:61-65). Deliberately not distinguishing "no such question" from "a speaker question" is a small information-disclosure decision: a caller probing ids learns nothing about which questions exist. [Rubric §11, Security]. BR-124 then delegates the type match to QuestionInvariants.EnsureAnswerValueMatchesQuestionType(question.QuestionType, answerValue, source) (:69-70, defined at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Questions/QuestionInvariants.cs:122), so the knowledge of what a Rating or a Text answer may look like stays in the Domain layer.
  • Why it's built this way: [Rubric §3, Clean Architecture] assesses whether the Application layer orchestrates rules that the Domain owns. Every check here forwards to a domain invariant; the class adds ordering, the null-event case, and the BR-128 shape check, and nothing else. [Rubric §5, Vertical Slice] is the tension worth naming: the two feedback slices deliberately share this file rather than each restating the rules, because a divergence between "submit one answer" and "submit a page of answers" would be a live correctness bug, not a stylistic one.
  • Where it's used: AddSessionQuestionAnswerHandler calls both methods from its private validation steps, passing the event and question it just read (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/AddSessionQuestionAnswer/AddSessionQuestionAnswerHandler.cs:70-71, :80-81); BatchAddSessionQuestionAnswersHandler calls EnsureSessionAcceptsFeedback once for the request (BatchAddSessionQuestionAnswersHandler.cs:83-84) and EnsureAnswerIsValid once per answer against a pre-built questionsById dictionary (:98-103). Because the class is internal static, those are the only two possible callers today, and any third has to live in the same assembly.
  • Caveats / not-in-source: there is no test class dedicated to this type; it is exercised only through the two handlers' tests. The "Session" literal at :59 is a magic string with no shared constant behind it, so a question-entity rename would have to be caught by a test rather than the compiler.
  • -

    SessionSpeakerDTOMapper

    -
    -

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.DTOs · MMCA.ADC.Conference.Application/Sessions/DTOs/SessionSpeakerDTOMapper.cs:12 · Level 9 · class (sealed partial)

    -
    -
      -
    • What it is: the Mapperly mapper for SessionSpeaker, the join entity that assigns a speaker to a session.
    • -
    • Depends on: IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType> closed over SessionSpeaker / SessionSpeakerDTO / SessionSpeakerIdentifierType (MMCA.ADC.Conference.Application/Sessions/DTOs/SessionSpeakerDTOMapper.cs:13), and Riok.Mapperly.Abstractions (SessionSpeakerDTOMapper.cs:11).
    • -
    • Concept reinforced: none new; see SessionCategoryItemDTOMapper. Same two members, same split between the generated MapToDTO (SessionSpeakerDTOMapper.cs:16) and the hand-written MapToDTOs (:19-23).
    • -
    • Where it's used: injected as the concrete type into AddSessionSpeakerHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/AddSessionSpeaker/AddSessionSpeakerHandler.cs:18); composed into SessionDTOMapper (SessionDTOMapper.cs:20-21); resolved by the query service registered for this entity (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:120). Covered by SessionSpeakerDTOMapperTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/DTOs/SessionSpeakerDTOMapperTests.cs:8).
    • -
    • Caveats / not-in-source: this mapper projects the join row only. Whether a parent SessionDTO arrives carrying its SessionSpeakers at all depends on whether the read path ran SessionNavigationPopulator for that collection; nothing in this file influences it.
    • -

    PartnerCreateRequest

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Partners.UseCases.Create · MMCA.ADC.Conference.Application/Partners/UseCases/Create/PartnerCreateRequest.cs:12 · Level 9 · record

      -
    • What it is: the create-request DTO for a conference partner (sponsor logos, exhibitors and the like), one of eight fields covering display, links, ordering and the owning event (PartnerCreateRequest.cs:25-53).
    • +
    • What it is: the create-request DTO for a conference partner (sponsor logos, exhibitors and the like), one of eight fields covering display, links, ordering and the owning event (PartnerCreateRequest.cs:12-40).
    • Depends on: ICreateRequest, the empty marker the generic request-mapper and create-handler contracts constrain on; ICacheInvalidating, which declares the eviction; and IPartnerFieldsRequest, the module's shape interface shared with the update request (PartnerCreateRequest.cs:12, IPartnerFieldsRequest.cs:14-33). It also references Partner for the cache prefix (PartnerCreateRequest.cs:28) and PartnerType and EventIdentifierType as member types.
    • Concept reinforced, the request that implements a validation shape: see SpeakerCreateRequest, which teaches the pattern. IPartnerFieldsRequest names the six members PartnerFieldRules<T> validates identically on create and update (IPartnerFieldsRequest.cs:16-32); the interface's own remark records why EventId is deliberately absent from it: only the create request carries and validates the owning event, because moving a partner between events is a create plus a delete, not an update (IPartnerFieldsRequest.cs:10-12). [Rubric #15, Best Practices & Code Quality] assesses whether a shared rule is written once; [Rubric #5, Vertical Slice] applies to the folder, request/mapper/validator/handler for Create all sitting in Partners/UseCases/Create.
    • Walkthrough: a record class with init-only members. CachePrefix (PartnerCreateRequest.cs:27-28) is the invalidation tag. Id (PartnerCreateRequest.cs:30-31) is a PartnerIdentifierType, and the doc comment states plainly that it is database-generated and any caller-supplied value is ignored, the opposite of the Sessionize-assigned speaker id. Name (PartnerCreateRequest.cs:33-34) is the one required member. The rest are optional init members: PartnerType (:36-37), LogoUrl (:39-40), Description (:42-43), WebsiteUrl (:45-46), Sort (:48-49), and EventId (:51-52), the FK naming which event the partner belongs to.
    • @@ -5228,11 +5233,31 @@

      PartnerDTOMapper

    • What it is: the entity-to-DTO mapper for partners (conference sponsors' close cousin: the organizations that support the event but are tracked separately). No redaction, no conditional projection: the Mapperly-generated copy of Partner into PartnerDTO, because partner data is public by design, shown on the landing page (PartnerDTOMapper.cs:8-10).
    • Depends on: IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType> closed over Partner / PartnerDTO / PartnerIdentifierType (PartnerDTOMapper.cs:14), and the Mapperly source generator via [Mapper] from Riok.Mapperly.Abstractions (PartnerDTOMapper.cs:4, :12).
    • Concept reinforced, source-generated mapping: the pattern is introduced in Group 12 and governed by ADR-001. MapToDTO is declared partial with no body (PartnerDTOMapper.cs:17); Mapperly reads both types at compile time and emits the property-by-property assignment, so a member added to the entity but not to the DTO surfaces as a build diagnostic rather than a silently missing field at runtime. [Rubric §15, Best Practices & Code Quality] assesses whether repetitive code is generated rather than hand-maintained: the assignments exist and none of them are in this file.
    • -
    • Walkthrough: two members. public partial PartnerDTO MapToDTO(Partner entity); (PartnerDTOMapper.cs:29) is the generated one. MapToDTOs (PartnerDTOMapper.cs:32-36) is hand-written and, read side by side, is what the interface's default implementation already provides: a null guard (:34) plus [.. entityCollection.Select(MapToDTO)] (:35). The duplication is not pointless: a C# default interface member is reachable only through the interface, and this mapper is injected by its concrete type into CreatePartnerHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Partners/UseCases/Create/CreatePartnerHandler.cs:18), which the framework's Scrutor scan supports by registering mappers AsSelfWithInterfaces (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:206-210).
    • +
    • Walkthrough: two members. public partial PartnerDTO MapToDTO(Partner entity); (PartnerDTOMapper.cs:17) is the generated one. MapToDTOs (PartnerDTOMapper.cs:20-24) is hand-written and, read side by side, is what the interface's default implementation already provides: a null guard (:22) plus [.. entityCollection.Select(MapToDTO)] (:23). The duplication is not pointless: a C# default interface member is reachable only through the interface, and this mapper is injected by its concrete type into CreatePartnerHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Partners/UseCases/Create/CreatePartnerHandler.cs:18), which the framework's Scrutor scan supports by registering mappers AsSelfWithInterfaces (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:206-210).
    • Why it's built this way: same shape as its sibling SponsorDTOMapper: no injected collaborators, a pure function, because neither partner nor sponsor data carries anything to filter at the DTO boundary. [Rubric §11, Security] assesses whether sensitive data is filtered at the boundary that owns it: here the conclusion is documented (PartnerDTOMapper.cs:8-10) rather than an omission.
    • -
    • Where it's used: registered by the module's convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143, which reaches the IEntityDTOMapper<,,> sweep at MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:206-210); injected concretely into CreatePartnerHandler (CreatePartnerHandler.cs:18) and forwarded from there into the framework create workflow's dtoMapper slot (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:45, used at :101); and resolved through the interface by the closed-generic read service registered for partners, EntityQueryService<Partner, PartnerDTO, PartnerIdentifierType> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:104).
    • +
    • Where it's used: registered by the module's convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139, which reaches the IEntityDTOMapper<,,> sweep at MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:206-210); injected concretely into CreatePartnerHandler (CreatePartnerHandler.cs:18) and forwarded from there into the framework create workflow's dtoMapper slot (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:45, used at :101); and resolved through the interface by the closed-generic read service registered for partners, EntityQueryService<Partner, PartnerDTO, PartnerIdentifierType> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:104).
    • Caveats / not-in-source: the generated MapToDTO has no null guard: MapToDTO(null!) throws NullReferenceException (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Partners/DTOs/PartnerDTOMapperTests.cs:58-62), while the hand-written collection overload throws the more conventional ArgumentNullException (PartnerDTOMapperTests.cs:93-95). Every caller in this codebase passes a materialized entity, so the asymmetry is documented behavior rather than a live failure mode.
    +

    DeleteConferenceCategoryHandler

    +
    +

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Categories.UseCases.Delete · MMCA.ADC.Conference.Application/Categories/UseCases/Delete/DeleteConferenceCategoryHandler.cs:14 · Level 9 · class (sealed)

    +
    +
      +
    • What it is: the delete handler for a conference Category. It is a four-line subclass of the framework's generic delete workflow that changes exactly two things: the name a not-found failure reports, and the child collection the aggregate's cascade needs loaded.
    • +
    • Depends on: DeleteEntityHandler<TEntity, TIdentifierType> closed over Category and ConferenceCategoryIdentifierType (DeleteConferenceCategoryHandler.cs:14-15); IUnitOfWork, passed straight through to the base (DeleteConferenceCategoryHandler.cs:14); the CategoryItem child collection, referenced by name only.
    • +
    • Concept introduced, a generic handler with two structural extension points: [Rubric §2, Design Patterns] and [Rubric §1, SOLID]. The base is deliberately left unsealed and its workflow split into overridable steps, because the two things a real delete outgrows are structural rather than behavioral (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/DeleteEntityHandler.cs:13-32): the child collections the aggregate's own Delete() cascade has to see, declared in Includes, and a cross-aggregate invariant that must refuse the delete first, implemented in OnDeletingAsync. A subclass that overrides neither behaves exactly like the base, down to the query it issues. This handler overrides only Includes, so it keeps the base's OnDeletingAsync acceptance (DeleteEntityHandler.cs:126-130). The load-bearing consequence is in the base's LoadAsync (DeleteEntityHandler.cs:101-114): with no includes it issues the bare by-id query, and with includes it switches to the eager-loading overload under AsTracking (DeleteEntityHandler.cs:64). Declare nothing and the cascade sees an empty in-memory collection, which is how child rows survive a soft-deleted parent.
    • +
    • Concept reinforced, the aggregate owns the cascade: Category.Delete() is where BR-71 actually lives (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Categories/Category.cs:104-116): it combines DeleteChildren<CategoryItem, CategoryItemIdentifierType>(_categoryItems) with base.Delete() in one Result.Combine (Category.cs:108-110), so a failing child aggregates into the combined result instead of leaving a half-applied delete, and raises CategoryChanged with DomainEntityState.Deleted only on success (Category.cs:112-113). The base handler raises no events of its own by design (DeleteEntityHandler.cs:23-26), which is what keeps the generic path and a hand-written one indistinguishable from outside. [Rubric §4, Domain-Driven Design].
    • +
    • Walkthrough:
        +
      • The whole class is a primary constructor forwarding IUnitOfWork to the base (DeleteConferenceCategoryHandler.cs:14-15) plus two property overrides.
      • +
      • HandlerName => nameof(DeleteConferenceCategoryHandler) (DeleteConferenceCategoryHandler.cs:18) replaces the base default, which reports the open generic name DeleteEntityHandler (DeleteEntityHandler.cs:50). The value is stamped into the Source of the NotFound failure (DeleteEntityHandler.cs:74), so an API error names the module path rather than a framework type.
      • +
      • Includes => [nameof(Category.CategoryItems)] (DeleteConferenceCategoryHandler.cs:21) is the one behavioral change, naming the read-only child collection exposed at Category.cs:31.
      • +
      • Everything else runs in the base HandleAsync (DeleteEntityHandler.cs:67-89): resolve the repository from the unit of work (DeleteEntityHandler.cs:71), load (DeleteEntityHandler.cs:72), fail NotFound when the row is gone (DeleteEntityHandler.cs:73-74), run the pre-delete refusal hook (DeleteEntityHandler.cs:76-78), call entity.Delete() (DeleteEntityHandler.cs:80), and save plus log only on success (DeleteEntityHandler.cs:81-86).
      • +
      +
    • +
    • Why it's built this way: the same shape recurs for every aggregate whose delete cascades, which is why DeleteEventHandler and DeleteSessionHandler are their own subclasses while aggregates with no owned children (Speaker, Question) are registered against the raw generic instead (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:85, DependencyInjection.cs:94). [Rubric §15, Best Practices & Code Quality]: the delete workflow exists once.
    • +
    • Where it's used: registered explicitly, not by the convention scan, as the implementation of ICommandHandler<DeleteEntityCommand<Category, ConferenceCategoryIdentifierType>, Result> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:89); invoked through the decorator pipeline by the DELETE /ConferenceCategories/{id} override of ConferenceCategoriesController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Categories/ConferenceCategoriesController.cs:140-149, handler injected at ConferenceCategoriesController.cs:40), a controller gated on CategoriesManage at the class level (ConferenceCategoriesController.cs:35) that evicts the conference:categories output-cache tag after the delete (ConferenceCategoriesController.cs:147).
    • +
    • Caveats / not-in-source: the explicit registration is what makes this subclass reachable. Because the command type is the closed generic DeleteEntityCommand<TEntity, TIdentifierType>, the convention scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:246-250) would also see it; nothing in this file records which registration wins, and the module uses TryAddScoped (DependencyInjection.cs:78) so ordering decides. There is no dedicated unit-test file for this handler in MMCA.ADC.Conference.Application.Tests/Categories/UseCases/.
    • +

    SessionCategoryItemNavigationPopulator

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions · MMCA.ADC.Conference.Application/Sessions/SessionCategoryItemNavigationPopulator.cs:11 · Level 10 · class (sealed)

    @@ -5245,20 +5270,6 @@

    SessionCategoryItemNavigationPop
  • Why it's built this way: this indirection exists because of the cross-source degradation rule. When a relationship can span physical data sources, EF's navigation is stripped and only the scalar foreign key survives, so hydration has to be a second batched query rather than an Include (ADR-002, ADR-006). [Rubric §3, Clean Architecture]: the Application layer describes hydration with repository abstractions and property selectors, with no EF Core namespace anywhere in the file.
  • Where it's used: registered as the INavigationPopulator<SessionCategoryItem> implementation, services.TryAddScoped<INavigationPopulator<SessionCategoryItem>, SessionCategoryItemNavigationPopulator>() (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:122), directly above the base EntityQueryService<TEntity, TEntityDTO, TIdentifierType> registration for the same entity (:110), which is the pairing that puts it on every direct read of a session category item (ADR-034). Covered by SessionCategoryItemNavigationPopulatorTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/SessionCategoryItemNavigationPopulatorTests.cs:9).
  • -

    SessionDTOMapper

    -
    -

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.DTOs · MMCA.ADC.Conference.Application/Sessions/DTOs/SessionDTOMapper.cs:14 · Level 10 · class (sealed partial)

    -
    -
      -
    • What it is: the mapper that turns a Session aggregate into a SessionDTO, including its three child collections. It is the composite of the three child mappers in this unit, and that is why it sits a level above them.
    • -
    • Depends on: IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType> closed over Session / SessionDTO / SessionIdentifierType (MMCA.ADC.Conference.Application/Sessions/DTOs/SessionDTOMapper.cs:18); SessionSpeakerDTOMapper, SessionQuestionAnswerDTOMapper, and SessionCategoryItemDTOMapper, all three taken by primary constructor (SessionDTOMapper.cs:14-17); Riok.Mapperly.Abstractions.
    • -
    • Concept introduced, composing generated mappers with [UseMapper]: the three injected mappers are stored in fields marked [UseMapper] (SessionDTOMapper.cs:20-27). That attribute tells Mapperly: when you need to map a SessionSpeaker while generating the body of MapToDTO(Session), do not invent a nested mapping, call this field. The result is one generated method per type, reused wherever the type appears, instead of a copy of the child mapping inlined into every parent. Change how a SessionSpeaker projects and every parent DTO that embeds one follows automatically. [Rubric §1, SOLID]: each mapper has one reason to change. [Rubric §15, Best Practices & Code Quality]: the composition is declared in three fields rather than maintained as duplicated assignment code.
    • -
    • Concept introduced, an explicit flattening override alongside generated composition: MapToDTO no longer just says partial; it now carries [MapProperty("Room.Name", nameof(SessionDTO.RoomName))] (SessionDTOMapper.cs:71), and the XML <remarks> above it states the tradeoff directly: RoomName is flattened out of the Room navigation, and since that navigation only loads when the read asked for FK includes, a list read leaves the flattened value null (SessionDTOMapper.cs:66-70). The field exists purely so the session grids have something to sort and display by instead of the room GUID; SessionEntityQueryService is the type that turns that same flattened path into a sortable DTO property. [Rubric §9, API and Contract Design]: the DTO trades a foreign key for a display-ready string, and the doc comment says exactly when that string is trustworthy.
    • -
    • Walkthrough: three [UseMapper] readonly fields assigned from the primary-constructor parameters (SessionDTOMapper.cs:20-27), the partial MapToDTO the generator fills in, now annotated with the Room.Name flattening attribute described above (:65-72), and the hand-written MapToDTOs with its null guard and Select spread (:75-79). The class is sealed partial and carries [Mapper] (:13-14), which is what makes the generation happen at all.
    • -
    • Why it's built this way: generated composition keeps the DTO projection compile-checked end to end, so adding a property to SessionDTO that no entity property feeds is a build error rather than a null in a response (ADR-001).
    • -
    • Where it's used: injected as the concrete type into CreateSessionHandler, which forwards it to its CreateEntityHandlerBase (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/Create/CreateSessionHandler.cs:26, :29-30), and into UpdateSessionHandler, which maps the saved entity directly (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/Update/UpdateSessionHandler.cs:21, mapped at :115); resolved as IEntityDTOMapper<Session, SessionDTO, SessionIdentifierType> by the generic EntityQueryService<TEntity, TEntityDTO, TIdentifierType> registered for sessions (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:75), which is what puts it on every session read (ADR-034). Registered self-and-interfaces by the convention scan (DependencyInjection.cs:143). Covered by SessionDTOMapperTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/DTOs/SessionDTOMapperTests.cs:9).
    • -
    • Caveats / not-in-source: the generated file is not in the repository, so which properties actually get copied is observable only from the two type definitions and a build. In particular, whether the child collections or the Event and Room references are populated at map time depends entirely on whether the read path ran SessionNavigationPopulator for them first; the mapper maps what it is given. RoomName inherits the same dependency: it is only non-null when a Room include ran.
    • -

    SessionNavigationPopulator

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions · MMCA.ADC.Conference.Application/Sessions/SessionNavigationPopulator.cs:13 · Level 10 · class (sealed)

    @@ -5305,10 +5316,10 @@

    CreatePartnerHandler

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Partners.UseCases.Create · MMCA.ADC.Conference.Application/Partners/UseCases/Create/CreatePartnerHandler.cs:15 · Level 10 · class (sealed partial)

      -
    • What it is: the handler that creates a partner. Its entire body is a logging hook and the [LoggerMessage] partial that hook calls (CreatePartnerHandler.cs:81-85); the workflow comes from the base class.
    • -
    • Depends on: CreateEntityHandlerBase<TCreateRequest, TEntity, TIdentifierType, TEntityDTO> closed over PartnerCreateRequest, Partner, PartnerIdentifierType, and PartnerDTO (CreatePartnerHandler.cs:78-79); IUnitOfWork (CreatePartnerHandler.cs:74); IEntityRequestMapper<TEntity, TCreateRequest, TIdentifierType>, satisfied by PartnerCreateRequestMapper (CreatePartnerHandler.cs:75); PartnerDTOMapper as a concrete type (CreatePartnerHandler.cs:76); Microsoft.Extensions.Logging. All four constructor parameters except the logger are forwarded to the base.
    • +
    • What it is: the handler that creates a partner. Its entire body is a logging hook and the [LoggerMessage] partial that hook calls (CreatePartnerHandler.cs:24-27); the workflow comes from the base class.
    • +
    • Depends on: CreateEntityHandlerBase<TCreateRequest, TEntity, TIdentifierType, TEntityDTO> closed over PartnerCreateRequest, Partner, PartnerIdentifierType, and PartnerDTO (CreatePartnerHandler.cs:20-21); IUnitOfWork (CreatePartnerHandler.cs:16); IEntityRequestMapper<TEntity, TCreateRequest, TIdentifierType>, satisfied by PartnerCreateRequestMapper (CreatePartnerHandler.cs:17); PartnerDTOMapper as a concrete type (CreatePartnerHandler.cs:18); Microsoft.Extensions.Logging. All four constructor parameters except the logger are forwarded to the base.
    • Concept reinforced, the shared create workflow: see CreateSpeakerHandler, which teaches CreateEntityHandlerBase's CreateCoreAsync sequence in full: run PrepareAsync, map the request through the entity factory, resolve the repository from the unit of work, persist with AddAsync plus SaveChangesAsync, then log and return Result.Success(dtoMapper.MapToDTO(entity)). This handler is a structural twin: nothing here is overridden except LogCreated.
    • -
    • Walkthrough: the primary constructor (CreatePartnerHandler.cs:73-79) and two members. LogCreated (CreatePartnerHandler.cs:82) overrides the base's no-op hook and forwards to the generated partial, recording the id and the partner's name. LogPartnerCreated (CreatePartnerHandler.cs:84-85) is the [LoggerMessage] partial, which is why the class is partial. Nothing else is overridden: PrepareAsync, PersistAsync, HandleAsync, and OnCreatedAsync are all left at their base defaults, because a partner's id is database-generated and the request carries none of the cross-context or transactional concerns some sibling handlers do.
    • +
    • Walkthrough: the primary constructor (CreatePartnerHandler.cs:15-21) and two members. LogCreated (CreatePartnerHandler.cs:24) overrides the base's no-op hook and forwards to the generated partial, recording the id and the partner's name. LogPartnerCreated (CreatePartnerHandler.cs:26-27) is the [LoggerMessage] partial, which is why the class is partial. Nothing else is overridden: PrepareAsync, PersistAsync, HandleAsync, and OnCreatedAsync are all left at their base defaults, because a partner's id is database-generated and the request carries none of the cross-context or transactional concerns some sibling handlers do.
    • Why it's built this way: validation, cache invalidation, and transaction scope are all handled by the pipeline decorators wrapped around this handler, declared by the markers on PartnerCreateRequest; the create sequence itself is the framework's, which is why the module-specific part of a create reduces to a log message (ADR-014).
    • Where it's used: registered by the framework's command-handler scan, driven from the module's convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs); injected into PartnersController as ICommandHandler<PartnerCreateRequest, Result<PartnerDTO>> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Partners/PartnersController.cs). Covered by CreatePartnerHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Partners/UseCases/CreatePartnerHandlerTests.cs).
    • Caveats / not-in-source: nothing here guards against a database-level unique-constraint violation on the partner name or ordering; a duplicate simply fails on the insert and surfaces through the shared exception handling.
    • @@ -5323,7 +5334,7 @@

      PartnerCreateRequestMapper

    • Concept reinforced, the request mapper as the only door into a factory: see SpeakerCreateRequestMapper for the pattern. Partner.Create (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Partners/Partner.cs:79-103) validates the name, logo URL, and website URL (Partner.cs:89-91), then decides the id: when Partner's identity is database-generated it discards any caller-supplied value and defaults it, and only assigns the caller's id when the entity's identity is NOT database-generated (Partner.cs:93-98). It then raises PartnerChanged (Partner.cs:100). [Rubric #4, Domain-Driven Design]: every field check lives in the factory, not in this mapper.
    • Walkthrough: CreateEntityAsync (PartnerCreateRequestMapper.cs:11) null-guards the request (PartnerCreateRequestMapper.cs:13), then returns Task.FromResult(Partner.Create(...)) (PartnerCreateRequestMapper.cs:15-23), forwarding all eight request members: Id, Name, PartnerType, LogoUrl, Description, WebsiteUrl, Sort, and EventId. Unlike SpeakerCreateRequestMapper, which drops one computed field, every member of PartnerCreateRequest reaches the factory unchanged. The method is synchronous work behind an async signature: Task.FromResult satisfies the interface without an allocation-heavy state machine.
    • Why it's built this way: delegating every field check to the factory keeps validation in the domain instead of duplicated in the Application layer, and the generic create pipeline can drive any aggregate through the same contract (ADR-001).
    • -
    • Where it's used: constructor-injected into CreatePartnerHandler as IEntityRequestMapper<Partner, PartnerCreateRequest, PartnerIdentifierType> (CreatePartnerHandler.cs:75) and forwarded to its base; registered by the framework's request-mapper scan, driven from the module's convention scan. Covered by CreatePartnerHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Partners/UseCases/CreatePartnerHandlerTests.cs).
    • +
    • Where it's used: constructor-injected into CreatePartnerHandler as IEntityRequestMapper<Partner, PartnerCreateRequest, PartnerIdentifierType> (CreatePartnerHandler.cs:17) and forwarded to its base; registered by the framework's request-mapper scan, driven from the module's convention scan. Covered by CreatePartnerHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Partners/UseCases/CreatePartnerHandlerTests.cs).
    • Caveats / not-in-source: whether Id actually reaches the created entity depends entirely on Partner's IsIdValueGenerated flag (Partner.cs:93), which this mapper does not inspect; a caller cannot tell from this file alone whether a supplied id will be honored.

    PartnerCreateRequestValidator

    @@ -5346,9 +5357,9 @@

    SessionEntityQueryService

    • What it is: the session-specific override of the generic query service, and the one type in this unit whose sole reason to exist is a single sort/filter mapping: RoomName (MMCA.ADC.Conference.Application/Sessions/SessionEntityQueryService.cs:119-122).
    • -
    • Depends on: EntityQueryService<TEntity, TEntityDTO, TIdentifierType> closed over Session / SessionDTO / SessionIdentifierType, which it extends (SessionEntityQueryService.cs:100-101); IUnitOfWork, INavigationMetadataProvider, IEntityQueryPipeline, SessionDTOMapper, and INavigationPopulator<Session>, all five taken by primary constructor and forwarded to the base unchanged (:100-107).
    • -
    • Concept introduced, a DTO-to-entity property map for a flattened field: the base EntityQueryService<TEntity, TEntityDTO, TIdentifierType> exposes a virtual DTOToEntityPropertyMap that the query pipeline consults when a caller asks to sort or filter by a DTO property name; by default it is empty, because most DTO properties map to an identically-named entity property and need no translation. RoomName breaks that default: it is a flattened field with no entity property behind it (SessionDTOMapper.cs:71, described on SessionDTOMapper), so without an entry a sort on either session grid's room column would be rejected by the pipeline and silently fall back to the default order. This class overrides the property with a one-entry Dictionary<string, string>, [nameof(SessionDTO.RoomName)] = "Room.Name" (SessionEntityQueryService.cs:119-122), which lets the database order by the joined room name rather than the room GUID; the XML doc on the field states the reasoning explicitly, including which alternative (RoomId) it deliberately avoids (:109-118). [Rubric §9, API and Contract Design]: a DTO field that has no direct backing property still gets a well-defined, queryable meaning instead of silently failing sort/filter requests. [Rubric §12, Performance and Scalability]: the translation lets the sort happen in the database rather than after loading every row.
    • -
    • Walkthrough: a primary constructor that takes all five base dependencies and forwards them unchanged to EntityQueryService<Session, SessionDTO, SessionIdentifierType> (SessionEntityQueryService.cs:100-107); a private static readonly IReadOnlyDictionary<string, string> PropertyMap field holding the one RoomName -> Room.Name entry (:119-122); and a one-line protected override returning that field as DTOToEntityPropertyMap (:125-126). No method is overridden; the class exists to change one piece of static configuration on the generic base.
    • +
    • Depends on: EntityQueryService<TEntity, TEntityDTO, TIdentifierType> closed over Session / SessionDTO / SessionIdentifierType, which it extends (SessionEntityQueryService.cs:21-22); IUnitOfWork, INavigationMetadataProvider, IEntityQueryPipeline, SessionDTOMapper, and INavigationPopulator<Session>, all five taken by primary constructor and forwarded to the base unchanged (:15-22).
    • +
    • Concept introduced, a DTO-to-entity property map for a flattened field: the base EntityQueryService<TEntity, TEntityDTO, TIdentifierType> exposes a virtual DTOToEntityPropertyMap that the query pipeline consults when a caller asks to sort or filter by a DTO property name; by default it is empty, because most DTO properties map to an identically-named entity property and need no translation. RoomName breaks that default: it is a flattened field with no entity property behind it (SessionDTOMapper.cs:35, described on SessionDTOMapper), so without an entry a sort on either session grid's room column would be rejected by the pipeline and silently fall back to the default order. This class overrides the property with a one-entry Dictionary<string, string>, [nameof(SessionDTO.RoomName)] = "Room.Name" (SessionEntityQueryService.cs:34-37), which lets the database order by the joined room name rather than the room GUID; the XML doc on the field states the reasoning explicitly, including which alternative (RoomId) it deliberately avoids (:24-33). [Rubric §9, API and Contract Design]: a DTO field that has no direct backing property still gets a well-defined, queryable meaning instead of silently failing sort/filter requests. [Rubric §12, Performance and Scalability]: the translation lets the sort happen in the database rather than after loading every row.
    • +
    • Walkthrough: a primary constructor that takes all five base dependencies and forwards them unchanged to EntityQueryService<Session, SessionDTO, SessionIdentifierType> (SessionEntityQueryService.cs:15-22); a private static readonly IReadOnlyDictionary<string, string> PropertyMap field holding the one RoomName -> Room.Name entry (:34-37); and a one-line protected override returning that field as DTOToEntityPropertyMap (:39-40). No method is overridden; the class exists to change one piece of static configuration on the generic base.
    • Why it's built this way: keeping the whole service generic (EntityQueryService<TEntity, TEntityDTO, TIdentifierType>) and giving individual entities a thin subclass only when they need one avoids writing a bespoke query service for every entity in the module; sessions need this one specifically because RoomName is the only flattened, sortable field in the DTO. [Rubric §15, Best Practices and Code Quality]: the override is scoped to exactly the one entity that needs it.
    • Where it's used: registered as the concrete session query service by the module's DependencyInjection, in place of the generic base, at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:75, so every session read (list, filter, or single-entity) goes through this subclass's property map rather than the base's empty one (ADR-034). Covered by SessionEntityQueryServiceTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/SessionEntityQueryServiceTests.cs), which exercises the property map through the base's public sort/filter surface.
    • Caveats / not-in-source: the file adds no other entity to the map even though other DTOs in this chapter also flatten fields (for example the room and event labels other list grids display); whether those grids simply do not support sorting on those columns, or route through a different mechanism, is not stated in this file.
    • @@ -5360,9 +5371,9 @@

      AddSessionQuestionAnswerCommand

      • What it is: the message an attendee's session feedback travels on. Four positional fields: the owning SessionId, an optional SessionQuestionAnswerId for the answer row, the QuestionId being answered, and the AnswerValue text (MMCA.ADC.Conference.Application/Sessions/UseCases/AddSessionQuestionAnswer/AddSessionQuestionAnswerCommand.cs:11-15).
      • Depends on: ICacheInvalidating (AddSessionQuestionAnswerCommand.cs:15), the Session type referenced only for its FullName when building the cache prefix, and the SessionIdentifierType / SessionQuestionAnswerIdentifierType / QuestionIdentifierType module aliases (ADR-048).
      • -
      • Concept reinforced: none new. The nullable child id works exactly as on AddSessionCategoryItemCommand; the file states the same contract at AddSessionQuestionAnswerCommand.cs:8, "Explicit ID for the answer entity, or null for database-generated identity", and the REST path always passes null (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionQuestionAnswersController.cs:234). The parameter exists because that is the shape the aggregate method takes (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:507).
      • +
      • Concept reinforced: none new. The nullable child id works exactly as on AddSessionCategoryItemCommand; the file states the same contract at AddSessionQuestionAnswerCommand.cs:8, "Explicit ID for the answer entity, or null for database-generated identity", and the REST path always passes null (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionQuestionAnswersController.cs:238). The parameter exists because that is the shape the aggregate method takes (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:507).
      • Walkthrough: one member, CachePrefix => $"{typeof(Session).FullName}:" (AddSessionQuestionAnswerCommand.cs:17-18). The interesting part of this record is what it does not carry: no author id. Ownership is resolved server side from ICurrentUserService inside AddSessionQuestionAnswerHandler (AddSessionQuestionAnswerHandler.cs:52), so a client cannot submit feedback as someone else. [Rubric §11, Security] assesses whether identity ever travels as request data; here it does not.
      • -
      • Where it's used: validated by AddSessionQuestionAnswerCommandValidator, handled by AddSessionQuestionAnswerHandler, and built from an AddSessionQuestionAnswerRequest by the [Idempotent] POST /SessionQuestionAnswers action of SessionQuestionAnswersController (SessionQuestionAnswersController.cs:228-235, ADR-017), on a controller whose whole surface requires an authenticated caller (SessionQuestionAnswersController.cs:77).
      • +
      • Where it's used: validated by AddSessionQuestionAnswerCommandValidator, handled by AddSessionQuestionAnswerHandler, and built from an AddSessionQuestionAnswerRequest by the [Idempotent] POST /SessionQuestionAnswers action of SessionQuestionAnswersController (SessionQuestionAnswersController.cs:232-239, ADR-017), on a controller whose whole surface requires an authenticated caller (SessionQuestionAnswersController.cs:77).

      AddSessionSpeakerCommand

      @@ -5371,39 +5382,9 @@

      AddSessionSpeakerCommand

      • What it is: the command that attaches a speaker to an existing session. Three positional parameters: the owning SessionId, an optional SessionSpeakerId for the join row, and the SpeakerId being associated (MMCA.ADC.Conference.Application/Sessions/UseCases/AddSessionSpeaker/AddSessionSpeakerCommand.cs:10-13).
      • Depends on: ICacheInvalidating (AddSessionSpeakerCommand.cs:13); the Session type, referenced only for its FullName when building the cache prefix; and the SessionIdentifierType / SessionSpeakerIdentifierType / SpeakerIdentifierType module aliases (ADR-048).
      • -
      • Concept reinforced, the nullable child id on an Add command: the second parameter is SessionSpeakerIdentifierType?, documented in the file as "Explicit ID for the join entity, or null for database-generated identity" (AddSessionSpeakerCommand.cs:8). The REST path always passes null and lets the database assign the key (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionSpeakersController.cs:170); the parameter exists because that is the exact shape the aggregate method takes (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:338-340). The same shape is taught on AddSessionCategoryItemCommand.
      • +
      • Concept reinforced, the nullable child id on an Add command: the second parameter is SessionSpeakerIdentifierType?, documented in the file as "Explicit ID for the join entity, or null for database-generated identity" (AddSessionSpeakerCommand.cs:8). The REST path always passes null and lets the database assign the key (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionSpeakersController.cs:178); the parameter exists because that is the exact shape the aggregate method takes (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:338-340). The same shape is taught on AddSessionCategoryItemCommand.
      • Walkthrough: the record body is a single member, CachePrefix => $"{typeof(Session).FullName}:" (AddSessionSpeakerCommand.cs:15-16). That is the session-wide prefix every session write in this module declares, so one eviction after a successful command clears every cached session projection instead of requiring per-query bookkeeping (ADR-026). The command itself never touches a cache: it declares what it invalidates and the caching decorator does the work (ADR-014). [Rubric §12, Performance & Scalability] assesses whether concerns like caching live in one pipeline stage rather than being re-implemented per handler; here the handler has no cache code at all.
      • -
      • Where it's used: constructed by the hand-written POST /SessionSpeakers action of SessionSpeakersController from an AddSessionSpeakerRequest body (SessionSpeakersController.cs:163-171), on a controller gated by the SessionsManage permission (SessionSpeakersController.cs:47, ADR-020) and marked [Idempotent] so a retried request replays the first response rather than adding a second row (SessionSpeakersController.cs:164, ADR-017); validated by AddSessionSpeakerCommandValidator; handled by AddSessionSpeakerHandler.
      • -
      -

      GetNowNextQuery

      -
      -

      MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.NowNext · MMCA.ADC.Conference.Application/Sessions/UseCases/NowNext/GetNowNextQuery.cs:23 · Level 9 · record

      -
      -
        -
      • What it is: the request for the "happening now / up next" snapshot. One parameter, EventIdentifierType? EventId: a value targets that published event, null asks the handler to feature the current-or-next published event (MMCA.ADC.Conference.Application/Sessions/UseCases/NowNext/GetNowNextQuery.cs:23).
      • -
      • Depends on: IQueryCacheable (GetNowNextQuery.cs:23); the Session type for the key prefix; System.Globalization for the invariant id formatting (GetNowNextQuery.cs:1).
      • -
      • Concept introduced, a query that declares its own cache key: commands implement ICacheInvalidating and name a prefix to evict; queries implement IQueryCacheable and name a key plus a TTL. The caching decorator reads both, so a cached read and the writes that invalidate it agree only because they agree on the prefix string (ADR-014, ADR-026). [Rubric §12, Performance and Scalability] assesses whether hot reads are cached at a layer that can be evicted correctly; the doc comment states the reasoning, a hot, public, non-user-specific read behind a home-screen widget (GetNowNextQuery.cs:13-20).
      • -
      • Walkthrough: two computed members and no methods.
          -
        • CacheKey (GetNowNextQuery.cs:26-35) builds {Session full name}:NowNext:{scope} where scope is the event id formatted with CultureInfo.InvariantCulture, or the literal "current" when the id is null (:30-33). Two things matter here. The key sits under the same Session aggregate prefix the session commands declare as their CachePrefix, so any session write evicts this entry through prefix eviction once an IConnectionMultiplexer is registered. And the id-less form gets its own stable key rather than colliding with whichever event happens to be current.
        • -
        • CacheDuration (GetNowNextQuery.cs:38) is TimeSpan.FromSeconds(30). The file explains why the TTL is short and why it is not redundant with prefix eviction (:16-19): the payload changes with the wall clock as sessions roll over time buckets, event-level edits are not session writes, and the TTL is the sole backstop when prefix eviction is unavailable. [Rubric §29, Resilience and Business Continuity]: correctness degrades to "at most 30 seconds stale" rather than "wrong until someone writes a session".
        • -
        -
      • -
      • Why it's built this way: the widget has no event id of its own (GetNowNextQuery.cs:10-12), so an id-less form has to exist; making it a nullable parameter on one query rather than a second query type keeps one handler, one cache policy, and one payload shape (ADR-042 Wave 8, cited in the file at :8).
      • -
      • Where it's used: constructed twice by EventsController, with an id for the per-event now-next action (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:172) and with EventId: null for the event-less one (EventsController.cs:186). Both actions are [AllowAnonymous] and carry [OutputCache(PolicyName = "NowNextCache")] (EventsController.cs:166-167, :186-187), a 60-second public policy tagged conference and conference:sessions (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:285). Handled by GetNowNextHandler. Covered by GetNowNextQueryCacheTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/NowNext/GetNowNextQueryCacheTests.cs:14).
      • -
      • Caveats / not-in-source: there are two independent cache layers on this read, the 30-second query cache declared here and the 60-second HTTP output cache declared on the endpoint. Nothing in source ties the two durations together, so the staleness a widget actually sees is bounded by the output cache, not by CacheDuration.
      • -
      -

      SessionCreateRequest

      -
      -

      MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.Create · MMCA.ADC.Conference.Application/Sessions/UseCases/Create/SessionCreateRequest.cs:11 · Level 9 · record class

      -
      -
        -
      • What it is: the POST body for creating a session and, unusually, the command message itself. There is no separate CreateSessionCommand: CreateSessionHandler closes CreateEntityHandlerBase<TCreateRequest, TEntity, TIdentifierType, TEntityDTO> over this record (MMCA.ADC.Conference.Application/Sessions/UseCases/Create/CreateSessionHandler.cs:29-30), so the request record travels the whole decorator pipeline unchanged.
      • -
      • Depends on: ICreateRequest, ICacheInvalidating, and ISessionFieldsRequest (SessionCreateRequest.cs:11); the Session type for the prefix; the SessionIdentifierType, EventIdentifierType, and RoomIdentifierType aliases.
      • -
      • Concept introduced, the request DTO as the command: ICreateRequest is a pure marker with no members; its only job is to be a generic constraint on IEntityRequestMapper<TEntity, TCreateRequest, TIdentifierType> and on the create handler base (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:47). That constraint is what lets the generic create pipeline in AggregateRootEntityControllerBase bind a request body straight to a handler with no intermediate command type (SessionsController.cs:55-56). [Rubric §9, API and Contract Design] assesses whether the wire contract is explicit: it is, but the price is that the HTTP contract and the internal command contract are one type and cannot evolve independently. [Rubric §34, Architecture Governance & Documentation]: one type instead of two, at the cost of that coupling.
      • -
      • Concept introduced, an interface that exists to make a validator reusable: ISessionFieldsRequest declares the seven session fields the create and update requests validate identically, Title, Description, Status, LiveUrl, RecordingUrl, AccessibilityInfo, and ResourceLinks (MMCA.ADC.Conference.Application/Sessions/Validation/ISessionFieldsRequest.cs:13-35). Implementing it is what lets SessionFieldRules<T> declare the shared rule list once over a constrained T (SessionValidationRules.cs:111-124). Fields validated by only one operation stay off the interface deliberately: EventId is create-only, because BR-140 makes it immutable afterwards (ISessionFieldsRequest.cs:8-12). [Rubric §1, SOLID]: the interface is the smallest surface that makes the rules generic, not a mirror of the record.
      • -
      • Walkthrough: CachePrefix (SessionCreateRequest.cs:14) is the same session-wide prefix the child commands use, so a create evicts cached session reads. Seventeen init-only data properties follow. Only two are required: Title (:20) and EventId (:62). Id (:17) is a plain non-nullable SessionIdentifierType documented as "auto-generated if not provided", which in practice means a caller sends nothing and the property arrives as 0; CreateSessionHandler reads that 0 as its signal to allocate an id from the reserved manual range. LiveUrl, RecordingUrl, AccessibilityInfo, and ResourceLinks (:47, :50, :53, :56) are nullable strings rather than Uri, matching how Sessionize exports them; the validation rule sets say the same thing, length-only because the value is stored as an opaque string for Sessionize compatibility (SessionValidationRules.cs:56-58). Status (:32) is a nullable string, not an enum, which is what makes a session with no status at all representable (the organizer-created case PublicSessionStatusSpecification has to allow for). [Rubric §15, Best Practices and Code Quality]: init-only accessors make the request immutable once bound, and CreateSessionHandler uses with rather than mutation when it fills in the id (CreateSessionHandler.cs:94).
      • -
      • Where it's used: bound from the body of POST /Sessions on SessionsController (SessionsController.cs:263-265), an action that overrides the base create purely to add the BR-86 date-range warning header and to make the [Idempotent] contract visible at the ADC endpoint (SessionsController.cs:256-262); validated by SessionCreateRequestValidator; turned into an entity by SessionCreateRequestMapper; handled by CreateSessionHandler.
      • -
      • Caveats / not-in-source: Duration (:59) never reaches the domain. It is not among the arguments SessionCreateRequestMapper passes to Session.Create (SessionCreateRequestMapper.cs:19-35), and Session.Create has no parameter for it (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:187-203). That is by design, since the entity computes it from StartsAt and EndsAt (Session.cs:80), but a caller can send a value that is silently discarded and nothing in the contract says so.
      • +
      • Where it's used: constructed by the hand-written POST /SessionSpeakers action of SessionSpeakersController from an AddSessionSpeakerRequest body (SessionSpeakersController.cs:171-179), on a controller gated by the SessionsManage permission (SessionSpeakersController.cs:47, ADR-020) and marked [Idempotent] so a retried request replays the first response rather than adding a second row (SessionSpeakersController.cs:172, ADR-017); validated by AddSessionSpeakerCommandValidator; handled by AddSessionSpeakerHandler.

      AddSessionCategoryItemCommand

      @@ -5412,9 +5393,9 @@

      AddSessionCategoryItemCommand

      • What it is: the command that tags a session with a category item (the mechanism behind session topics, levels, and localities). Three positional parameters: the owning SessionId, an optional SessionCategoryItemId for the join entity, and the CategoryItemId being associated (MMCA.ADC.Conference.Application/Sessions/UseCases/AddSessionCategoryItem/AddSessionCategoryItemCommand.cs:10-13).
      • Depends on: ICacheInvalidating, the one interface it implements (AddSessionCategoryItemCommand.cs:13); the Session type, used only for its FullName when building the cache prefix (AddSessionCategoryItemCommand.cs:1, :16); and the SessionIdentifierType, SessionCategoryItemIdentifierType, and CategoryItemIdentifierType module aliases, all three int (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/MMCA.ADC.Conference.GlobalUsings.IdentifierType.cs:19, :14, :6).
      • -
      • Concept introduced, the nullable child id on an Add command: the second parameter is SessionCategoryItemIdentifierType?, documented in the file as "Explicit ID for the join entity, or null for database-generated identity" (AddSessionCategoryItemCommand.cs:8). The REST path always passes null and lets the database assign the key (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionCategoryItemsController.cs:170); the parameter exists because that is the exact signature the aggregate factory takes (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:420-422), and a caller that already knows the id (the Sessionize import is the obvious one) can supply it. [Rubric §9, API and Contract Design] assesses whether a contract states exactly what a caller may decide: a nullable id rather than a defaulted one keeps "let the database choose" distinct from "I chose zero".
      • +
      • Concept introduced, the nullable child id on an Add command: the second parameter is SessionCategoryItemIdentifierType?, documented in the file as "Explicit ID for the join entity, or null for database-generated identity" (AddSessionCategoryItemCommand.cs:8). The REST path always passes null and lets the database assign the key (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionCategoryItemsController.cs:178); the parameter exists because that is the exact signature the aggregate factory takes (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:420-422), and a caller that already knows the id (the Sessionize import is the obvious one) can supply it. [Rubric §9, API and Contract Design] assesses whether a contract states exactly what a caller may decide: a nullable id rather than a defaulted one keeps "let the database choose" distinct from "I chose zero".
      • Walkthrough: the record body is one member, CachePrefix => $"{typeof(Session).FullName}:" (AddSessionCategoryItemCommand.cs:15-16). That is the same session-wide prefix AddSessionQuestionAnswerCommand and RemoveSessionCategoryItemCommand declare, so one eviction after a successful command covers every cached session projection rather than requiring per-query bookkeeping (ADR-026). The command itself never touches a cache: it only declares what it invalidates, and the caching decorator does the work (ADR-014). [Rubric §12, Performance & Scalability].
      • -
      • Where it's used: constructed by the POST /SessionCategoryItems action of SessionCategoryItemsController from an AddSessionCategoryItemRequest body (SessionCategoryItemsController.cs:165-171), on a controller gated by the SessionsManage permission (SessionCategoryItemsController.cs:47, ADR-020); validated by AddSessionCategoryItemCommandValidator; handled by AddSessionCategoryItemHandler.
      • +
      • Where it's used: constructed by the POST /SessionCategoryItems action of SessionCategoryItemsController from an AddSessionCategoryItemRequest body (SessionCategoryItemsController.cs:173-179), on a controller gated by the SessionsManage permission (SessionCategoryItemsController.cs:47, ADR-020); validated by AddSessionCategoryItemCommandValidator; handled by AddSessionCategoryItemHandler.

      PublicSessionStatusSpecification

      @@ -5430,6 +5411,39 @@

      PublicSessionStatusSpecification

    • Where it's used: PublicConferenceVisibility uses both forms, the static expression as the localPredicate of a cross-source build (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Common/PublicConferenceVisibility.cs:68) and an instance in specification algebra (PublicConferenceVisibility.cs:148); GetPublicSessionFilterHandler uses the static expression for the public session list (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/GetPublicSessionFilter/GetPublicSessionFilterHandler.cs:34, with the sharing intent stated at :17).
    • Caveats / not-in-source: the collation argument in the remarks is a statement about the deployed SQL Server, not something the type can enforce. On a case-sensitive collation or a different engine, the expression and SessionStatuses.IsEligible could disagree, and nothing in the code would catch it.
    +

    SessionCategoryItemDTOMapper

    +
    +

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.DTOs · MMCA.ADC.Conference.Application/Sessions/DTOs/SessionCategoryItemDTOMapper.cs:12 · Level 9 · class (sealed partial)

    +
    +
      +
    • What it is: the entity-to-DTO mapper for the SessionCategoryItem join entity, source-generated by Mapperly.
    • +
    • Depends on: IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType> closed over SessionCategoryItem / SessionCategoryItemDTO / SessionCategoryItemIdentifierType (MMCA.ADC.Conference.Application/Sessions/DTOs/SessionCategoryItemDTOMapper.cs:13), and Riok.Mapperly.Abstractions for the [Mapper] attribute (SessionCategoryItemDTOMapper.cs:4, :11).
    • +
    • Concept reinforced, compile-time mapping: the mechanism is taught on SpeakerCategoryItemDTOMapper; this is the session-side twin, the same shape over different types. [Mapper] on a partial class makes the generator emit the body of MapToDTO as plain property assignments, so an unmatched property is a build diagnostic rather than a silent null, and there is no runtime reflection or expression compilation on the read path (ADR-001). [Rubric §12, Performance and Scalability] and [Rubric §15, Best Practices and Code Quality].
    • +
    • Walkthrough: two members. MapToDTO (SessionCategoryItemDTOMapper.cs:16) is partial with no body, which is the generator's hook. MapToDTOs (:19-23) is hand-written: ArgumentNullException.ThrowIfNull then a collection-expression spread over Select(MapToDTO). Declaring the plural on the class is what makes it reachable through the concrete type, which matters because AddSessionCategoryItemHandler injects the concrete mapper, not the interface.
    • +
    • Where it's used: injected into AddSessionCategoryItemHandler (MMCA.ADC.Conference.Application/Sessions/UseCases/AddSessionCategoryItem/AddSessionCategoryItemHandler.cs:18); composed into SessionDTOMapper as a [UseMapper] field (SessionDTOMapper.cs:26-27); resolved by the generic EntityQueryService<TEntity, TEntityDTO, TIdentifierType> registered for this entity (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:123). Registered by the convention scan, self and interfaces, scoped (DependencyInjection.cs:139). Covered by SessionCategoryItemDTOMapperTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/DTOs/SessionCategoryItemDTOMapperTests.cs:8).
    • +
    +

    SessionQuestionAnswerDTOMapper

    +
    +

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.DTOs · MMCA.ADC.Conference.Application/Sessions/DTOs/SessionQuestionAnswerDTOMapper.cs:12 · Level 9 · class (sealed partial)

    +
    +
      +
    • What it is: the Mapperly mapper for SessionQuestionAnswer, the entity that stores one attendee's answer to one session question.
    • +
    • Depends on: IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType> closed over SessionQuestionAnswer / SessionQuestionAnswerDTO / SessionQuestionAnswerIdentifierType (MMCA.ADC.Conference.Application/Sessions/DTOs/SessionQuestionAnswerDTOMapper.cs:13), and Riok.Mapperly.Abstractions (SessionQuestionAnswerDTOMapper.cs:11).
    • +
    • Concept reinforced: none new. Identical in structure to SessionCategoryItemDTOMapper: a partial MapToDTO the generator fills in (SessionQuestionAnswerDTOMapper.cs:16) and a hand-written null-guarded MapToDTOs (:19-23).
    • +
    • Where it's used: injected into AddSessionQuestionAnswerHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/AddSessionQuestionAnswer/AddSessionQuestionAnswerHandler.cs:24), where both the update branch (:96) and the create branch (:116) map through it, and into BatchAddSessionQuestionAnswersHandler, which maps the whole applied set in one spread (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/BatchAddSessionQuestionAnswers/BatchAddSessionQuestionAnswersHandler.cs:25, :66); composed into SessionDTOMapper (SessionDTOMapper.cs:23-24); resolved by the query service registered for this entity (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:126). Covered by SessionQuestionAnswerDTOMapperTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/DTOs/SessionQuestionAnswerDTOMapperTests.cs:8).
    • +
    • Caveats / not-in-source: the DTO shape is decided entirely by the two type definitions and the generated file, which is not in the repository. Whether an answer's author is exposed to a caller is a property question on SessionQuestionAnswerDTO, not something this file controls.
    • +
    +

    SessionSpeakerDTOMapper

    +
    +

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.DTOs · MMCA.ADC.Conference.Application/Sessions/DTOs/SessionSpeakerDTOMapper.cs:12 · Level 9 · class (sealed partial)

    +
    +
      +
    • What it is: the Mapperly mapper for SessionSpeaker, the join entity that assigns a speaker to a session.
    • +
    • Depends on: IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType> closed over SessionSpeaker / SessionSpeakerDTO / SessionSpeakerIdentifierType (MMCA.ADC.Conference.Application/Sessions/DTOs/SessionSpeakerDTOMapper.cs:13), and Riok.Mapperly.Abstractions (SessionSpeakerDTOMapper.cs:11).
    • +
    • Concept reinforced: none new; see SessionCategoryItemDTOMapper. Same two members, same split between the generated MapToDTO (SessionSpeakerDTOMapper.cs:16) and the hand-written MapToDTOs (:19-23).
    • +
    • Where it's used: injected as the concrete type into AddSessionSpeakerHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/AddSessionSpeaker/AddSessionSpeakerHandler.cs:18); composed into SessionDTOMapper (SessionDTOMapper.cs:20-21); resolved by the query service registered for this entity (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:120). Covered by SessionSpeakerDTOMapperTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/DTOs/SessionSpeakerDTOMapperTests.cs:8).
    • +
    • Caveats / not-in-source: this mapper projects the join row only. Whether a parent SessionDTO arrives carrying its SessionSpeakers at all depends on whether the read path ran SessionNavigationPopulator for that collection; nothing in this file influences it.
    • +

    AddSessionQuestionAnswerCommandValidator

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.AddSessionQuestionAnswer · MMCA.ADC.Conference.Application/Sessions/UseCases/AddSessionQuestionAnswer/AddSessionQuestionAnswerCommandValidator.cs:8 · Level 10 · class (sealed)

    @@ -5437,7 +5451,7 @@

    AddSessionQuestionAnswerComman
    • What it is: the FluentValidation validator for AddSessionQuestionAnswerCommand. One rule: RuleFor(x => x.AnswerValue).NotEmpty() with the message "Answer value is required." (MMCA.ADC.Conference.Application/Sessions/UseCases/AddSessionQuestionAnswer/AddSessionQuestionAnswerCommandValidator.cs:10-13).
    • Depends on: FluentValidation's AbstractValidator<T> and nothing else (AddSessionQuestionAnswerCommandValidator.cs:1, :8).
    • -
    • Concept reinforced: the validating decorator stage, taught on AddSessionCategoryItemCommandValidator. The handler never calls this class; registration is by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143).
    • +
    • Concept reinforced: the validating decorator stage, taught on AddSessionCategoryItemCommandValidator. The handler never calls this class; registration is by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139).
    • Why it's built this way: NotEmpty covers null, empty, and whitespace-only text, which is all that can be judged without knowing the question. The semantic check, that the answer matches the question's declared type, needs the Question row and therefore lives behind the handler as BR-124, in SessionQuestionAnswerRules.EnsureAnswerIsValid (MMCA.ADC.Conference.Application/Sessions/SessionQuestionAnswerRules.cs:69-70). Shape rules here, data-dependent rules where the data is. [Rubric §24, Forms, Validation and UX Safety].
    • Where it's used: resolved by the validating decorator for AddSessionQuestionAnswerCommand; covered by AddSessionQuestionAnswerCommandValidatorTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/Validation/SessionCommandValidatorTests.cs:9).
    • Caveats / not-in-source: neither SessionId nor QuestionId is validated here, so a zero id passes validation and fails later, the session id as a NotFound from the handler's load and the question id as the BR-128 validation failure.
    • @@ -5455,13 +5469,13 @@

      AddSessionQuestionAnswerHandler

    • Walkthrough: five members, and the ordering between them is the rule hierarchy.
      • HandleAsync (:28-60) loads the session with its SessionQuestionAnswers and asTracking: true (:32-37), because both the upsert lookup and the subsequent mutation need the children tracked. A missing session is a stamped Error.NotFound (:38-39).
      • ValidateSessionEligibilityAsync (:62-71) reads the parent Event through its own repository (:66-67) and hands both aggregates to SessionQuestionAnswerRules.EnsureSessionAcceptsFeedback (:69-70), which chains SessionInvariants.EnsureNotServiceSession (BR-91, a break or a lunch slot takes no feedback, SessionQuestionAnswerRules.cs:27, defined at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/SessionInvariants.cs:94), SessionInvariants.EnsureStatusIsEligible (BR-49, the same allow-list PublicSessionStatusSpecification expresses for reads, SessionQuestionAnswerRules.cs:34, defined at SessionInvariants.cs:109), a null-event NotFound (SessionQuestionAnswerRules.cs:41-44), and EventInvariants.EnsureEventIsPublished (BR-108, SessionQuestionAnswerRules.cs:46, defined at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:151).
      • -
      • ValidateQuestionAsync (:73-82) loads the Question (:77-78) and delegates to SessionQuestionAnswerRules.EnsureAnswerIsValid (:80-81), which rejects a question that does not exist or whose QuestionEntity is not "Session" with a validation error coded Question.NotFoundOrWrongEntity (BR-128, SessionQuestionAnswerRules.cs:59-66), then hands the answer text to QuestionInvariants.EnsureAnswerValueMatchesQuestionType (BR-124, SessionQuestionAnswerRules.cs:69-70, defined at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Questions/QuestionInvariants.cs:118).
      • +
      • ValidateQuestionAsync (:73-82) loads the Question (:77-78) and delegates to SessionQuestionAnswerRules.EnsureAnswerIsValid (:80-81), which rejects a question that does not exist or whose QuestionEntity is not "Session" with a validation error coded Question.NotFoundOrWrongEntity (BR-128, SessionQuestionAnswerRules.cs:59-66), then hands the answer text to QuestionInvariants.EnsureAnswerValueMatchesQuestionType (BR-124, SessionQuestionAnswerRules.cs:69-70, defined at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Questions/QuestionInvariants.cs:122).
      • UpdateExistingAnswerAsync (:84-97) calls session.UpdateSessionQuestionAnswer(existingAnswer.Id, command.AnswerValue) (:90, aggregate method at Session.cs:531), saves, and maps the same tracked instance back out, so the response carries the new value.
      • CreateNewAnswerAsync (:99-117) calls session.AddSessionQuestionAnswer(...) (:105, aggregate method at Session.cs:507), raises the integration event, saves, and maps the child the aggregate returned. Both branches emit the same LogQuestionAnswerAddedToSession line (:119-120), so the log does not distinguish an insert from an update.
    • Why it's built this way: eligibility rules are shared with the batch path, so the handler composes helpers instead of copying conditions, and every check returns a Result that folds into the same failure channel (ADR-013). The branch on an existing answer is also why this slice keeps a hand-written HandleAsync instead of subclassing AddChildEntityHandlerBase<TCommand, TParent, TIdentifierType, TChild, TChildDTO>: that base's Apply hook returns one child from one aggregate call (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/ChildEntityHandlerBase.cs:70), which an upsert with two different aggregate methods cannot express. [Rubric §3, Clean Architecture]: the two cross-aggregate reads go through IUnitOfWork repositories, never EF types.
    • -
    • Where it's used: registered by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143); injected into SessionQuestionAnswersController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionQuestionAnswersController.cs:80), whose whole surface requires an authenticated caller (:75), from the [Idempotent] POST /SessionQuestionAnswers action (:166-173). Covered by AddSessionQuestionAnswerHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/AddSessionQuestionAnswer/AddSessionQuestionAnswerHandlerTests.cs:15).
    • +
    • Where it's used: registered by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139); injected into SessionQuestionAnswersController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionQuestionAnswersController.cs:80), whose whole surface requires an authenticated caller (:75), from the [Idempotent] POST /SessionQuestionAnswers action (:166-173). Covered by AddSessionQuestionAnswerHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/AddSessionQuestionAnswer/AddSessionQuestionAnswerHandlerTests.cs:15).
    • Caveats / not-in-source: currentUserService.UserId!.Value (:52) is null-forgiving. Nothing inside this handler enforces that a user id is present; the guarantee comes from the controller policy, so a caller reaching this code with no id would fault rather than fail gracefully. The three validation steps each issue their own round-trip (session, event, question), which is three reads before any write on the create path.

    AddSessionSpeakerCommandValidator

    @@ -5471,10 +5485,10 @@

    AddSessionSpeakerCommandValidator

  • What it is: a one-rule FluentValidation validator for AddSessionSpeakerCommand, rejecting a missing speaker id before the handler touches the database.
  • Depends on: FluentValidation's AbstractValidator<T> closed over the command (AddSessionSpeakerCommandValidator.cs:1, :8), and the SpeakerIdentifierType alias for the default comparison.
  • -
  • Concept reinforced, validation at the command boundary: the validation decorator runs every registered AbstractValidator<TCommand> before the handler (ADR-014), which is why AddSessionSpeakerHandler contains no shape checks on its inputs. Discovery is by convention: the module's ScanModuleApplicationServices<ClassReference>() call registers validators alongside handlers and mappers in one line (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143). [Rubric §24, Forms, Validation and UX Safety] assesses whether validation is present and applied before business logic; it is, and the failure surfaces as a structured Result rather than an exception.
  • +
  • Concept reinforced, validation at the command boundary: the validation decorator runs every registered AbstractValidator<TCommand> before the handler (ADR-014), which is why AddSessionSpeakerHandler contains no shape checks on its inputs. Discovery is by convention: the module's ScanModuleApplicationServices<ClassReference>() call registers validators alongside handlers and mappers in one line (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139). [Rubric §24, Forms, Validation and UX Safety] assesses whether validation is present and applied before business logic; it is, and the failure surfaces as a structured Result rather than an exception.
  • Walkthrough: an expression-bodied constructor (AddSessionSpeakerCommandValidator.cs:10-13): RuleFor(x => x.SpeakerId).NotEqual(default(SpeakerIdentifierType)).WithMessage("Speaker ID is required."). Because the identifier alias is int, default is 0, and this is the module's standard idiom for "a value-typed id was not supplied".
  • Why it's built this way: the interesting rule, that a speaker cannot be attached twice, cannot live here. Duplicate detection needs the session's existing speaker list, so it is an aggregate invariant instead (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:338-352, error code Session.Speaker.Duplicate at :322). The split is deliberate: the validator checks shape, the aggregate checks state. [Rubric §4, DDD].
  • -
  • Where it's used: discovered by the convention scan (DependencyInjection.cs:143) and invoked by the validation decorator ahead of AddSessionSpeakerHandler. Covered by AddSessionSpeakerCommandValidatorTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/Validation/SessionCommandValidatorTests.cs:87).
  • +
  • Where it's used: discovered by the convention scan (DependencyInjection.cs:139) and invoked by the validation decorator ahead of AddSessionSpeakerHandler. Covered by AddSessionSpeakerCommandValidatorTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/Validation/SessionCommandValidatorTests.cs:87).
  • Caveats / not-in-source: SessionId is not validated. A command with SessionId == 0 passes validation and fails as Error.NotFound from the inherited load (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/ChildEntityHandlerBase.cs:111-112), a correct outcome reached by a slower path than the speaker-id check gets.
  • AddSessionSpeakerHandler

    @@ -5494,64 +5508,10 @@

    AddSessionSpeakerHandler

  • LogAdded (:36-37) forwards to LogSpeakerAddedToSession, a [LoggerMessage] source-generated partial (:39-40). The base's default is a no-op, because logging is per-module vocabulary (ChildEntityHandlerBase.cs:84-87).
  • -
  • Why it's built this way: returning the created DTO rather than bare success lets the controller answer 201 Created with a location route pointing at the new join row (SessionSpeakersController.cs:182-185), which is what the generic create action does for aggregates and what a hand-written child create has to do for itself.
  • -
  • Where it's used: registered by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143); injected into SessionSpeakersController as addHandler (SessionSpeakersController.cs:50) and called at SessionSpeakersController.cs:169-171, after which the controller evicts the sessions output-cache tags (SessionSpeakersController.cs:181). Covered by AddSessionSpeakerHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/AddSessionSpeaker/AddSessionSpeakerHandlerTests.cs:12).
  • +
  • Why it's built this way: returning the created DTO rather than bare success lets the controller answer 201 Created with a location route pointing at the new join row (SessionSpeakersController.cs:190-193), which is what the generic create action does for aggregates and what a hand-written child create has to do for itself.
  • +
  • Where it's used: registered by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139); injected into SessionSpeakersController as addHandler (SessionSpeakersController.cs:50) and called at SessionSpeakersController.cs:177-179, after which the controller evicts the sessions output-cache tags (SessionSpeakersController.cs:189). Covered by AddSessionSpeakerHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/AddSessionSpeaker/AddSessionSpeakerHandlerTests.cs:12).
  • Caveats / not-in-source: nothing here checks that SpeakerId refers to an existing speaker. A well-formed id for a speaker that does not exist reaches the database and fails there as a foreign-key violation, not as a Result. The handler also leaves OnAddedAsync (ChildEntityHandlerBase.cs:98-99) at its default, so there is no post-commit publish on this path.
  • -

    GetNowNextHandler

    -
    -

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.NowNext · MMCA.ADC.Conference.Application/Sessions/UseCases/NowNext/GetNowNextHandler.cs:20 · Level 10 · class (sealed)

    -
    -
      -
    • What it is: the read handler behind the now-next snapshot. It picks an event, filters that event's sessions down to the publicly exportable ones, and splits them into "running at this instant" and "the next batch to start".
    • -
    • Depends on: IQueryHandler<in TQuery, TResult> closed over GetNowNextQuery and Result<NowNextDTO> (GetNowNextHandler.cs:22); IUnitOfWork (:21); TimeProvider from the BCL (:22); CalendarExportMapper for IsExportable and ToUtc (:1, :48, :81-82); CurrentEventSelector for the live window and the current-or-next rule (:68, :101); the Event and Session aggregates; NowNextSessionDTO.
    • -
    • Concept introduced, injecting the clock: the handler takes TimeProvider and reads timeProvider.GetUtcNow() once at the top (GetNowNextHandler.cs:29), then uses that single instant for every comparison in the method. Two consequences. First, the snapshot is internally consistent: a session cannot be classified as both running and upcoming because the clock moved between two comparisons. Second, the whole "is it 9:30 on conference morning" question becomes a test input, which is exactly how GetNowNextHandlerTests pins its scenarios (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/NowNext/GetNowNextHandlerTests.cs:20). [Rubric §14, Testability] assesses whether ambient state is injected rather than reached for; a DateTime.UtcNow in this method would make the behavior untestable.
    • -
    • Concept introduced, wall clock versus instant: a conference schedule is authored in local wall-clock time, but "is it running now" is a question about instants. The handler resolves the event's TimeZone string to a TimeZoneInfo (:45) and converts each session's stored wall-clock StartsAt and EndsAt to a DateTimeOffset through CalendarExportMapper.ToUtc (:81-82), which also shifts spring-forward gap times ahead one hour so an invalid local time still yields an instant (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/ExportCalendar/CalendarExportMapper.cs:48-57). NowNextSessionDTO then carries both forms, local for printing on a badge or widget and UTC for callers doing their own math (:74-82). [Rubric §27, Internationalization] in its time-zone sense: the displayed value is the event's zone, never the server's.
    • -
    • Concept introduced, refusing to swallow a data defect: the time-zone lookup has no fallback. The comment at :43-44 states the reasoning: EventInvariants.EnsureTimeZoneIsValid guards every write path (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:82), so a stored id always resolves, and an unresolvable one is a data defect that must surface rather than be papered over with UTC. [Rubric §13, Observability and Operability] assesses whether a broken invariant is visible: a TimeZoneNotFoundException here is loud, where a silent UTC fallback would render a schedule that is quietly hours wrong.
    • -
    • Walkthrough: one public method and two private helpers.
        -
      • HandleAsync (GetNowNextHandler.cs:25-72) starts with the clock (:29), then calls SelectEventAsync and refuses anything missing or unpublished with Error.NotFound targeting Event (:31-36). That guard is the access control for this endpoint: both actions are [AllowAnonymous], so "published" is the only thing standing between an anonymous caller and an unannounced event's schedule. [Rubric §11, Security].
      • -
      • It loads every session for the event through unitOfWork.GetReadRepository<Session, SessionIdentifierType>() with no includes and no visibility specification (:38-40), builds a room-id-to-name dictionary from the already-included Rooms (:41), then resolves the time zone (:45). Both repository lookups in this handler now go through GetReadRepository rather than GetRepository (:38, :89): the handler never writes, so the read-only repository is the correct one on both paths.
      • -
      • Eligibility reuses the calendar-export rule rather than restating it: rows = sessions.Where(CalendarExportMapper.IsExportable) (:47-50), which means scheduled at both ends, not a service session, and status-eligible per BR-49 through the single SessionStatuses.IsEligible allow-list (CalendarExportMapper.cs:27-29). One definition, two public surfaces.
      • -
      • now is every row whose UTC window contains the instant, ordered by start then room name case-insensitively (:52-56). next is deliberately not "the single next session": the handler takes the minimum future start (:59-60) and returns every row sharing it (:61-66), with the comment stating the intent, so parallel tracks show together (:58).
      • -
      • isLive compares the instant against the event's live window from CurrentEventSelector.GetLiveWindowUtc (:68-69, defined at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Events/CurrentEventSelector.cs:66), the same window the home surfaces use, and the payload is assembled at :71.
      • -
      • ToRow (:74-82) projects one session, resolving the room name through the dictionary and returning null when the session has no room (:78).
      • -
      • SelectEventAsync (:84-107) branches on the nullable id: an explicit id is a direct GetByIdAsync including Rooms (:92-94); otherwise it loads all published events with their rooms (:97-99) and hands them to CurrentEventSelector.SelectCurrentOrNext with accessor lambdas for start, end, and zone (:101-106, defined at CurrentEventSelector.cs:24). Passing accessors rather than an interface is what lets that selector serve both entities and DTOs across the module.
      • -
      -
    • -
    • Why it's built this way: the file states the contract at :12-19, that eligibility and DST discipline are shared with the calendar export deliberately. A widget and an .ics download that disagreed about which sessions are public would be a visible defect, and the only way to guarantee they agree is to call the same predicate.
    • -
    • Where it's used: registered by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143); injected into EventsController as nowNextHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:50) and called from both now-next actions (EventsController.cs:172, :191). The payload is fetched over HTTP by NowNextService (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Services/HappeningNow/NowNextService.cs:14), rendered by HappeningNow, and read by the Android NowNextWidgetProvider. Covered by GetNowNextHandlerTests.
    • -
    • Caveats / not-in-source: the query at :38-40 loads every session row for the event, then filters, projects, sorts, and buckets in memory (:47-66). Nothing is pushed to the database beyond the EventId predicate, so the cost scales with the event's total session count rather than with the handful of rows the snapshot returns. The two cache layers on the endpoint make that acceptable in practice, not correct in principle. Separately, ToRow dereferences session.StartsAt! and session.EndsAt! (:79-82); that is safe only because IsExportable already rejected null-scheduled sessions, a coupling the compiler cannot check.
    • -
    -

    SessionCreateRequestMapper

    -
    -

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.Create · MMCA.ADC.Conference.Application/Sessions/UseCases/Create/SessionCreateRequestMapper.cs:11 · Level 10 · class (sealed)

    -
    -
      -
    • What it is: the adapter that turns a validated SessionCreateRequest into a Session entity by calling the domain factory. It is one method long and contains no logic of its own.
    • -
    • Depends on: IEntityRequestMapper<TEntity, TCreateRequest, TIdentifierType> closed over Session / SessionCreateRequest / SessionIdentifierType (SessionCreateRequestMapper.cs:11-12); Result; Session.Create.
    • -
    • Concept introduced, request-to-entity mapping is not DTO mapping: entity-to-DTO mapping is source-generated by Mapperly (ADR-001), because it is a mechanical property copy with no rules. Going the other way is the opposite: constructing an entity is where invariants are enforced, so it cannot be generated. This class is therefore hand-written and does exactly one thing, forward the request's fields to the factory, so that Session.Create remains the only path into a valid Session. [Rubric §4, DDD] assesses whether entities can be constructed in an invalid state; here they cannot, because the mapper has no other constructor available to it. [Rubric §2, Design Patterns]: this is an adapter, and its value is precisely that it has no behavior of its own to disagree with the factory.
    • -
    • Walkthrough: CreateEntityAsync (SessionCreateRequestMapper.cs:15-36) guards its argument with ArgumentNullException.ThrowIfNull (:17), then returns Task.FromResult(Session.Create(...)) with fourteen positional arguments plus isInformed: and isConfirmed: passed by name (:19-35). The two named arguments matter: Session.Create declares them as trailing optional parameters after the optional roomId (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:201-203), so naming them is what lets the call skip nothing and stay readable. The method is async in signature only: it returns a completed task because nothing here awaits, which keeps the interface uniform for mappers that do need I/O without paying a state machine for the ones that do not. Validation happens inside the factory, which combines three invariant checks before allocating (Session.cs:205-208: title validity, end-after-start, optional text lengths) and returns Result.Failure<Session> with the combined errors when any fails.
    • -
    • Why it's built this way: the generic create pipeline needs a uniform way to get from some request type to some entity, and the only thing that can vary per entity is which factory to call with which fields. Isolating that in a one-method class means the pipeline never sees a domain constructor.
    • -
    • Where it's used: registered by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143); injected into CreateSessionHandler through the interface, not the concrete type (CreateSessionHandler.cs:25), and invoked by the framework's CreateCoreAsync (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:90). Covered by SessionCreateRequestMapperTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/Create/SessionCreateRequestMapperTests.cs:6).
    • -
    • Caveats / not-in-source: the mapper passes request.Id into a SessionIdentifierType? parameter (SessionCreateRequestMapper.cs:20), so a request whose Id is still 0 would reach the factory as 0 rather than as null. That does not happen on the live path, because CreateSessionHandler replaces a default id with a computed one in its PrepareAsync override before the mapper runs (CreateSessionHandler.cs:79-95), but the mapper itself does not enforce it. See also the Duration property this method drops, noted under SessionCreateRequest.
    • -
    -

    SessionCreateRequestValidator

    -
    -

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.Create · MMCA.ADC.Conference.Application/Sessions/UseCases/Create/SessionCreateRequestValidator.cs:7 · Level 10 · class (sealed)

    -
    -
      -
    • What it is: the input validator for SessionCreateRequest. Its body is two Include calls and nothing else (SessionCreateRequestValidator.cs:9-17).
    • -
    • Depends on: FluentValidation's AbstractValidator<T> (:1, :7); SessionFieldRules<T> and SessionEventIdRules<T> from MMCA.ADC.Conference.Application.Sessions.Validation (:2).
    • -
    • Concept reinforced, composing validators with Include, and where the shared set stops: FluentValidation's Include folds another validator's rules into this one as though they had been written inline. The seven field rules the create and update requests share are already folded once, inside SessionFieldRules<T>, which is generic over T : ISessionFieldsRequest and reads its properties through the interface rather than through per-call selectors (MMCA.ADC.Conference.Application/Sessions/Validation/SessionValidationRules.cs:111-124). So this validator includes that one set (:11) and then adds only its own per-operation delta. [Rubric §15, Best Practices & Code Quality] assesses whether a rule has a single home: change the title constraint once, in SessionTitleRules<T> via SessionFieldRules<T>, and both request paths move together. [Rubric §1, SOLID]: each rule set is one reason to change, and the interface constraint is the smallest thing that makes the shared set generic.
    • -
    • Walkthrough: the constructor (SessionCreateRequestValidator.cs:9-17) has two statements.
        -
      • Include(new SessionFieldRules<SessionCreateRequest>()) (:11) pulls in title, description, status, live URL, recording URL, accessibility info, and resource links in that order (SessionValidationRules.cs:117-123). Title is required plus max-length (SessionValidationRules.cs:13-18); the other six are optional-string max-length rules bounded by SessionInvariants constants.
      • -
      • Include(new SessionEventIdRules<SessionCreateRequest>(p => p.EventId)) (:16) is the create-only delta, and the comment says why (:13-15): a session is scheduled inside an event, and the event it belongs to is chosen exactly once. BR-140 makes it immutable afterwards, so the update path validates it nowhere and UpdateSessionHandler rejects a change instead. The rule itself is a RequiredIdRules specialization carrying the error code Session.EventId.Required (SessionValidationRules.cs:24-29).
      • -
      -
    • -
    • Why it's built this way: the parallel SessionUpdateRequestValidator includes the same SessionFieldRules<T> closed over its own request type and adds a different delta. Parameterizing the shared set by the request type, constrained to ISessionFieldsRequest, is what makes that reuse possible across two records that are otherwise unrelated.
    • -
    • Where it's used: discovered by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143) and run by the validation decorator ahead of CreateSessionHandler (ADR-014). Covered by SessionCreateRequestValidatorTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/Validation/SessionCreateRequestValidatorTests.cs:7).
    • -
    • Caveats / not-in-source: the validator says nothing about StartsAt versus EndsAt. Ordering is a domain invariant, checked by SessionInvariants.EnsureEndsAtIsAfterStartsAt inside the factory (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:207, defined at SessionInvariants.cs:126), so an inverted range is rejected one layer later than a too-long title is. Room assignment is likewise absent here and enforced by the handler (CreateSessionHandler.cs:100-122).
    • -

    AddSessionCategoryItemCommandValidator

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.AddSessionCategoryItem · MMCA.ADC.Conference.Application/Sessions/UseCases/AddSessionCategoryItem/AddSessionCategoryItemCommandValidator.cs:8 · Level 10 · class (sealed)

    @@ -5561,7 +5521,7 @@

    AddSessionCategoryItemCommandVal
  • Depends on: FluentValidation's AbstractValidator<T> and nothing else (AddSessionCategoryItemCommandValidator.cs:1, :8).
  • Concept introduced, shape checks at the pipeline's front door: the validating decorator runs every registered validator for the command type before AddSessionCategoryItemHandler sees the message and before the transaction opens (ADR-014), so a malformed command costs no database work and the handler can assume a well-formed message. The division of labor is worth naming: "is the request well formed?" lives here, "is the operation allowed?" lives in the handler and the aggregate. That is why the duplicate-tag rule is NOT in this file: detecting it needs the loaded session, so it lives in Session.AddSessionCategoryItem as the Session.CategoryItem.Duplicate invariant (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:424-431). [Rubric §24, Forms, Validation and UX Safety] assesses whether bad input is rejected before it reaches business logic; [Rubric §15, Best Practices and Code Quality]: cheap guards stay declarative.
  • Walkthrough: an expression-bodied constructor holding one RuleFor(x => x.CategoryItemId).NotEqual(default(CategoryItemIdentifierType)).WithMessage("Category item ID is required.") (AddSessionCategoryItemCommandValidator.cs:10-13). Because CategoryItemIdentifierType is int (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/MMCA.ADC.Conference.GlobalUsings.IdentifierType.cs:8), this rejects a zero id, the value an unset JSON field binds to. Writing it as default(CategoryItemIdentifierType) rather than 0 means the rule survives an alias change to a GUID without editing.
  • -
  • Why it's built this way: the convention scan auto-registers every AbstractValidator in the assembly, services.ScanModuleApplicationServices<ClassReference>() (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143), so dropping a validator file next to its command is the entire wiring step. [Rubric §5, Vertical Slice]: no registration line to forget in a distant file.
  • +
  • Why it's built this way: the convention scan auto-registers every AbstractValidator in the assembly, services.ScanModuleApplicationServices<ClassReference>() (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139), so dropping a validator file next to its command is the entire wiring step. [Rubric §5, Vertical Slice]: no registration line to forget in a distant file.
  • Where it's used: resolved as IValidator<AddSessionCategoryItemCommand> by the validating decorator on every dispatch of that command. Covered by AddSessionCategoryItemCommandValidatorTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/Validation/SessionCommandValidatorTests.cs:109), one of four validator test classes sharing that file.
  • Caveats / not-in-source: SessionId is not validated here. A zero session id therefore reaches the handler and comes back as the aggregate's not-found error rather than a validation failure. Nothing in the file says whether that is deliberate.
  • @@ -5580,33 +5540,26 @@

    AddSessionCategoryItemHandler

  • Apply is the single line that does business work: parent.AddSessionCategoryItem(command.SessionCategoryItemId, command.CategoryItemId) (:31-32). The aggregate rejects a duplicate with Session.CategoryItem.Duplicate (Session.cs:424-431), creates the child through its own factory (Session.cs:433), and raises SessionCategoryItemChanged (Session.cs:441).
  • MapChild is dtoMapper.MapToDTO(child) (:35). The base calls it after the save (ChildEntityHandlerBase.cs:118, :124), which is what lets the DTO carry the identity the database generated, and is the whole reason the command's join id is nullable.
  • LogAdded forwards to the source-generated LogCategoryItemAddedToSession (:38-39, declared at :41-42), one structured Information line naming the category item and the session. [Rubric §13, Observability and Operability]: emitted once, after the save, never on the failure path.
  • -
  • Note the return shape: this add handler answers with a DTO, unlike the remove handlers in this chapter which return the bare Result. The controller needs the database-assigned join id to answer 201 Created with a location header (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionCategoryItemsController.cs:179-182).
  • +
  • Note the return shape: this add handler answers with a DTO, unlike the remove handlers in this chapter which return the bare Result. The controller needs the database-assigned join id to answer 201 Created with a location header (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionCategoryItemsController.cs:187-190).
  • Why it's built this way: atomicity is the one SaveChangesAsync inside the base covering both the join row and the domain event the aggregate raised, both in the same ADC_Conference database (ADR-006, ADR-003). Pushing the load-mutate-save shape into MMCA.Common means every module's add-a-child handler gets the same tracking, the same NotFound stamping, and the same post-save ordering for free; what stays local is the include list, the aggregate call, the mapper, and the log vocabulary. [Rubric §15, Best Practices & Code Quality].
  • -
  • Where it's used: registered by the module's application scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143); injected into SessionCategoryItemsController as ICommandHandler<AddSessionCategoryItemCommand, Result<SessionCategoryItemDTO>> (SessionCategoryItemsController.cs:50) and dispatched by its POST action, which is marked [Idempotent] so a retried request with the same Idempotency-Key replays the first response instead of adding a second row (SessionCategoryItemsController.cs:163-171), and which evicts the conference:sessions, conference:categories, and conference output-cache tags before returning (:178). Covered by AddSessionCategoryItemHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/AddSessionCategoryItem/AddSessionCategoryItemHandlerTests.cs:12).
  • +
  • Where it's used: registered by the module's application scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139); injected into SessionCategoryItemsController as ICommandHandler<AddSessionCategoryItemCommand, Result<SessionCategoryItemDTO>> (SessionCategoryItemsController.cs:50) and dispatched by its POST action, which is marked [Idempotent] so a retried request with the same Idempotency-Key replays the first response instead of adding a second row (SessionCategoryItemsController.cs:171-179), and which evicts the conference:sessions, conference:categories, and conference output-cache tags before returning (:178). Covered by AddSessionCategoryItemHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/AddSessionCategoryItem/AddSessionCategoryItemHandlerTests.cs:12).
  • Caveats / not-in-source: HandlerName is not overridden, so the NotFound failure is stamped with GetType().Name (ChildEntityHandlerBase.cs:44), which resolves to the concrete handler's name at runtime. The OnAddedAsync post-commit hook is left at its no-op default (ChildEntityHandlerBase.cs:98-99); this slice raises no integration event.
  • -

    CreateSessionHandler

    +

    SessionDTOMapper

    -

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.Create · MMCA.ADC.Conference.Application/Sessions/UseCases/Create/CreateSessionHandler.cs:22 · Level 14 · class (sealed partial)

    +

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.DTOs · MMCA.ADC.Conference.Application/Sessions/DTOs/SessionDTOMapper.cs:14 · Level 10 · class (sealed partial)

      -
    • What it is: the session create handler, and the most involved handler in this chapter. On top of the shared create workflow it allocates application-assigned ids out of a reserved range, guards room assignment, and retries a bounded number of times when a concurrent create takes the id it computed.
    • -
    • Depends on: CreateEntityHandlerBase<TCreateRequest, TEntity, TIdentifierType, TEntityDTO> closed over SessionCreateRequest, Session, SessionIdentifierType, and SessionDTO (CreateSessionHandler.cs:29-30); IUnitOfWork (:23); IServiceScopeFactory from Microsoft.Extensions.DependencyInjection (:24); IEntityRequestMapper<TEntity, TCreateRequest, TIdentifierType> (:25, implemented by SessionCreateRequestMapper); the concrete SessionDTOMapper (:26); IUniqueConstraintViolationDetector (:27); SessionInvariants for the reserved range; SessionRoomScheduling for BR-130; the Event aggregate.
    • -
    • Concept introduced, application-assigned ids in a reserved range: session primary keys are not database-generated, because the int PK is the Sessionize id (CreateSessionHandler.cs:76-78). An organizer-created session therefore needs an id that can never collide with one Sessionize will later import. The domain reserves the top of the range for that: SessionInvariants.ManualIdRangeStart is 999_999_000 and ManualIdRangeEnd is 999_999_999 (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/SessionInvariants.cs:44, :47), so a thousand manual ids sit above anything Sessionize issues. The handler queries the existing rows in that window with ignoreQueryFilters: true (:81-85), takes Max + 1 or the range start when empty (:87-89), and fails with a plain Error.Failure coded Session.ManualIdRangeExhausted when the range is exhausted (:91-92). Ignoring the query filters is load-bearing: a soft-deleted session still occupies its id, so counting only visible rows would hand out an id the database already holds. [Rubric §8, Data Architecture] assesses whether the identity strategy matches the data's provenance; here an externally-owned key space forced the choice, and the reserved range is how the two writers coexist.
    • -
    • Concept introduced, retrying a lost id race in a fresh DI scope: computing Max + 1 and inserting is a read-then-write race, so two concurrent organizer creates can compute the same id. The handler accepts that and recovers instead of locking. MaxManualIdAttempts is 3 (:33). The HandleAsync override (:36-66) loops, and the catch filter engages only when attempts remain and the detector classifies the exception as a unique-constraint violation (:60). The subtle part is the retry, which does not reuse the ambient unit of work: scopeFactory.CreateAsyncScope() produces a fresh scope and a fresh IUnitOfWork (:56-57), because the ambient DbContext still tracks the insert that just failed and would replay it (:54-55). This is precisely the extension point the base documents: a manual-id retry variant overrides HandleAsync to wrap CreateCoreAsync in its retry loop and overrides PrepareAsync to recompute the id per attempt, and CreateCoreAsync takes the unit of work as a parameter so a retry can run against a fresh scope while reusing the whole workflow (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:29-36, :76-79). [Rubric §29, Resilience and Business Continuity] assesses whether transient contention is survived rather than surfaced; a bounded, condition-filtered retry is the shape that does not turn a real error into an infinite loop. [Rubric §12, Performance and Scalability]: the design trades a rare retry for never taking a table lock.
    • -
    • Concept introduced, classifying a provider error without referencing the provider: the Application layer references the domain and no data provider at all, so it cannot see SqlException.Number 2601 or 2627, nor the EF Core DbUpdateException that wraps them. Rather than match on the exception message, which is a provider and locale detail, the question is declared as IUniqueConstraintViolationDetector and answered in Infrastructure where the provider types already live (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IUniqueConstraintViolationDetector.cs:9-29). A miss is safe rather than silent: an unclassified exception simply propagates (IUniqueConstraintViolationDetector.cs:26-29). [Rubric §3, Clean Architecture] assesses whether layer boundaries hold under pressure; they do here, and swapping engines swaps the implementation with every recovering handler unchanged.
    • -
    • Walkthrough: two overrides and two log methods; the create workflow itself is inherited.
        -
      • HandleAsync (:36-66) first short-circuits: a caller-supplied id (a Sessionize import, for example) is respected as-is and gets a single attempt with no recomputation, because a collision there is a genuine caller error (:40-43). Otherwise the retry loop runs, calling the inherited CreateCoreAsync on the ambient unit of work for attempt one (:51-52) and on a scoped one thereafter (:56-58), logging a warning on each collision (:63).
      • -
      • PrepareAsync (:69-125) is the base's optional pre-map hook (CreateEntityHandlerBase.cs:114-118), and it runs once per attempt. It resolves the repository (:74), allocates the manual id when needed (:79-95, ending in command = command with { Id = nextId }, a copy rather than a mutation), then validates room assignment only when a room was requested (:100-122). That branch loads the parent Event with its Rooms untracked (:102-107), returns Error.NotFound targeting Event when it is missing (:108-109), and delegates to SessionRoomScheduling.ValidateRoomAssignmentAsync for BR-130 cross-event validation plus the double-booking guard (:111-119, defined at MMCA.ADC.Conference.Application/Sessions/Validation/SessionRoomScheduling.cs:44, with excludeSessionId: null because nothing exists yet to exclude). The comment at :97-99 explains why the event is not loaded unconditionally: a room-less session has nothing to validate, and unlike update's BR-86 warning, create has no other use for the event.
      • -
      • Everything after PrepareAsync is the base's CreateCoreAsync (CreateEntityHandlerBase.cs:77-103): map through the request mapper, AddAsync then SaveChangesAsync via PersistAsync, call LogCreated, then map the SessionDTO.
      • -
      • LogCreated (:128) is the base's post-save hook, forwarding to the module's template. Two [LoggerMessage] partials close the file: LogSessionCreated at information level (:130-131) and LogManualIdCollision at warning level with the attempt counters (:133-134). The warning is the operational signal that the id race is happening more often than expected. [Rubric §13, Observability and Operability].
      • -
      -
    • -
    • Why it's built this way: every complication in this file traces to one fact, that the session key space is shared with an external system. ADR-006 gives the module its own database, but not its own id authority for sessions. Given that, the reserved range prevents collision by construction, and the retry handles the only race the range cannot prevent. Expressing both as overrides on the shared base rather than as a hand-written handler is the point of ADR-099: the unusual parts stay visible and the ordinary parts stay shared.
    • -
    • Where it's used: registered by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143); injected into SessionsController as createHandler (SessionsController.cs:46), passed into AggregateRootEntityControllerBase (SessionsController.cs:55-56), and called from the overridden POST /Sessions action (SessionsController.cs:267), which is marked [Idempotent] so a retried request replays rather than creating twice (SessionsController.cs:262, ADR-017). Covered by CreateSessionHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/Create/CreateSessionHandlerTests.cs:16).
    • -
    • Caveats / not-in-source: the manual-id query loads every session row in the reserved range as entities and computes Max in memory (:81-89) rather than asking the database for the maximum. The range caps at a thousand rows, so the cost is bounded, but it is not a scalar query. The retry loop is also while (true) with its bound expressed only in the catch filter (:46, :60): correct as written, since a non-matching exception or an exhausted budget propagates, but the termination condition is not local to the loop header.
    • +
    • What it is: the mapper that turns a Session aggregate into a SessionDTO, including its three child collections. It is the composite of the three child mappers in this unit, and that is why it sits a level above them.
    • +
    • Depends on: IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType> closed over Session / SessionDTO / SessionIdentifierType (MMCA.ADC.Conference.Application/Sessions/DTOs/SessionDTOMapper.cs:18); SessionSpeakerDTOMapper, SessionQuestionAnswerDTOMapper, and SessionCategoryItemDTOMapper, all three taken by primary constructor (SessionDTOMapper.cs:14-17); Riok.Mapperly.Abstractions.
    • +
    • Concept introduced, composing generated mappers with [UseMapper]: the three injected mappers are stored in fields marked [UseMapper] (SessionDTOMapper.cs:20-27). That attribute tells Mapperly: when you need to map a SessionSpeaker while generating the body of MapToDTO(Session), do not invent a nested mapping, call this field. The result is one generated method per type, reused wherever the type appears, instead of a copy of the child mapping inlined into every parent. Change how a SessionSpeaker projects and every parent DTO that embeds one follows automatically. [Rubric §1, SOLID]: each mapper has one reason to change. [Rubric §15, Best Practices & Code Quality]: the composition is declared in three fields rather than maintained as duplicated assignment code.
    • +
    • Concept introduced, an explicit flattening override alongside generated composition: MapToDTO no longer just says partial; it now carries [MapProperty("Room.Name", nameof(SessionDTO.RoomName))] (SessionDTOMapper.cs:35), and the XML <remarks> above it states the tradeoff directly: RoomName is flattened out of the Room navigation, and since that navigation only loads when the read asked for FK includes, a list read leaves the flattened value null (SessionDTOMapper.cs:30-34). The field exists purely so the session grids have something to sort and display by instead of the room GUID; SessionEntityQueryService is the type that turns that same flattened path into a sortable DTO property. [Rubric §9, API and Contract Design]: the DTO trades a foreign key for a display-ready string, and the doc comment says exactly when that string is trustworthy.
    • +
    • Walkthrough: three [UseMapper] readonly fields assigned from the primary-constructor parameters (SessionDTOMapper.cs:20-27), the partial MapToDTO the generator fills in, now annotated with the Room.Name flattening attribute described above (:65-72), and the hand-written MapToDTOs with its null guard and Select spread (:75-79). The class is sealed partial and carries [Mapper] (:13-14), which is what makes the generation happen at all.
    • +
    • Why it's built this way: generated composition keeps the DTO projection compile-checked end to end, so adding a property to SessionDTO that no entity property feeds is a build error rather than a null in a response (ADR-001).
    • +
    • Where it's used: injected as the concrete type into CreateSessionHandler, which forwards it to its CreateEntityHandlerBase (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/Create/CreateSessionHandler.cs:26, :29-30), and into UpdateSessionHandler, which maps the saved entity directly (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/Update/UpdateSessionHandler.cs:21, mapped at :115); resolved as IEntityDTOMapper<Session, SessionDTO, SessionIdentifierType> by the generic EntityQueryService<TEntity, TEntityDTO, TIdentifierType> registered for sessions (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:75), which is what puts it on every session read (ADR-034). Registered self-and-interfaces by the convention scan (DependencyInjection.cs:139). Covered by SessionDTOMapperTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/DTOs/SessionDTOMapperTests.cs:9).
    • +
    • Caveats / not-in-source: the generated file is not in the repository, so which properties actually get copied is observable only from the two type definitions and a build. In particular, whether the child collections or the Event and Room references are populated at map time depends entirely on whether the read path ran SessionNavigationPopulator for them first; the mapper maps what it is given. RoomName inherits the same dependency: it is only non-null when a Room include ran.

    RemoveSessionSpeakerCommand

    @@ -5617,27 +5570,7 @@

    RemoveSessionSpeakerCommand

  • Depends on: ICacheInvalidating (RemoveSessionSpeakerCommand.cs:11), the Session type for the prefix, and the SessionIdentifierType / SessionSpeakerIdentifierType aliases.
  • Concept reinforced: structurally identical to RemoveSessionCategoryItemCommand. Note the asymmetry with the matching Add command: AddSessionSpeakerCommand takes a nullable child id (the database may assign it), while every Remove command takes a required one. There is nothing to generate on removal, so a null there would only be a way to express "remove nothing". The behavioral difference is downstream, not here: RemoveSessionSpeakerHandler treats a SessionId of default as "not supplied" and resolves the owning session from the join id instead (RemoveSessionSpeakerHandler.cs:35-42), which is only possible because SessionIdentifierType is a value type with a meaningless zero.
  • Walkthrough: one member, CachePrefix => $"{typeof(Session).FullName}:" (RemoveSessionSpeakerCommand.cs:13-14), identical to the Add side, so attaching and detaching a speaker evict the same set of cached session projections.
  • -
  • Where it's used: constructed by the DELETE /SessionSpeakers/{id} action of SessionSpeakersController, route id plus [FromQuery] sessionId (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionSpeakersController.cs:188-196), on a controller gated at the class level by the SessionsManage permission (SessionSpeakersController.cs:47, ADR-020); handled by RemoveSessionSpeakerHandler, injected at SessionSpeakersController.cs:51.
  • - -

    SponsorCreateRequest

    -
    -

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sponsors.UseCases.Create · MMCA.ADC.Conference.Application/Sponsors/UseCases/Create/SponsorCreateRequest.cs:12 · Level 9 · record class

    -
    -
      -
    • What it is: the body a POST /Sponsors binds to, and, without any translation step, the command the CQRS pipeline dispatches. Twelve init-only members describe a sponsor or exhibitor; one extra member, CachePrefix, tells the pipeline what to evict when the write succeeds (MMCA.ADC.Conference.Application/Sponsors/UseCases/Create/SponsorCreateRequest.cs:15).
    • -
    • Depends on: three interfaces, all declared on one line (SponsorCreateRequest.cs:12). Two are framework markers: ICreateRequest and ICacheInvalidating. The third, ISponsorFieldsRequest, is the module's own (MMCA.ADC.Conference.Application/Sponsors/Validation/ISponsorFieldsRequest.cs:12). It also depends on the Sponsor entity type (referenced only through typeof for the cache prefix) and the SponsorTier enum. SponsorIdentifierType and EventIdentifierType are both module aliases for int.
    • -
    • Concept introduced, the request that is also the command: most codebases carry a request DTO at the edge and translate it into an internal command. Here the two collapse. The controller declares ICommandHandler<SponsorCreateRequest, Result<SponsorDTO>> directly (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sponsors/SponsorsController.cs:42) and passes the same type as the TCreateRequest argument of its generic base (SponsorsController.cs:49), so a single declaration is the OpenAPI schema, the validation target, the mapper input, and the cache-invalidation carrier. The cost is that a wire concern and a use-case concern share one type; the benefit is that there is exactly one place to add a field. [Rubric §5, Vertical Slice] assesses whether a feature is expressible as one thin, self-contained slice: the sponsor create slice is this file plus a validator, a mapper, and a handler, all in the same folder. [Rubric §9, API and Contract Design] assesses whether the published contract is explicit: required string Name (:21) is the only member the binder will not default, and every other member is optional by construction.
    • -
    • Concept introduced, the shared-fields interface: ISponsorFieldsRequest declares the eight members the create and the update request validate identically (ISponsorFieldsRequest.cs:15-36). It exists purely so SponsorFieldRules<T> can be written once against a constraint rather than once per request type (MMCA.ADC.Conference.Application/Sponsors/Validation/SponsorValidationRules.cs:139-141). What is off the interface matters as much as what is on it: EventId, Id, Tier, and IsExhibitor are absent. The file states the reason for EventId (ISponsorFieldsRequest.cs:9-10): only the create request carries it, because moving a sponsor between events is a create plus a delete. [Rubric §15, Best Practices & Code Quality] assesses whether shared shape is expressed structurally rather than duplicated: two request types, one rule list, and the compiler enforces the overlap.
    • -
    • Walkthrough: the members in the order they matter.
        -
      • CachePrefix => $"{typeof(Sponsor).FullName}:" (:15) is the entity's fully-qualified name plus a colon. All three sponsor mutations declare that same prefix, so they evict one shared namespace of keys. The eviction is performed by CachingCommandDecorator<TCommand, TResult> on success only.
      • -
      • Id (:18) is documented as database-generated with caller-supplied values ignored, and the factory is what makes that true: it consults typeof(Sponsor).IsIdValueGenerated and substitutes default whenever the store owns the key (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sponsors/Sponsor.cs:126-131). Nothing rejects a supplied id; it is simply discarded.
      • -
      • Name (:21) is required, so an omitted name fails model binding before any validator runs. Tier (:24) is the enum, Sort (:42) the within-tier display order, EventId (:45) the owning event, IsExhibitor (:48) and BoothNumber (:51) the expo-floor pair, and LogoUrl, Description, WebsiteUrl, LinkedInUrl, TwitterHandle (:27-39) the optional branding strings.
      • -
      • Every member is init, so once the binder has filled the instance the validator, the mapper, and the handler all see the same frozen values.
      • -
      -
    • -
    • Why it's built this way: the shape mirrors SponsorDTO member for member minus the concurrency token, which keeps the round trip (POST a request, receive a DTO) readable without a mapping table (ADR-001). Declaring cache invalidation as a property rather than calling a cache API keeps the Application layer free of cache infrastructure, which is what [Rubric §12, Performance & Scalability] looks for: the concern is declared once, and one decorator implements it for every command that declares it.
    • -
    • Where it's used: SponsorsController takes the handler for it in its constructor (SponsorsController.cs:42) and overrides CreateAsync to add the SponsorsManage permission attribute and the output-cache eviction (SponsorsController.cs:162-171); SponsorCreateRequestValidator validates it, SponsorCreateRequestMapper turns it into an entity, and CreateSponsorHandler persists it. The framework's generic CRUD registration also closes over it (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:155).
    • -
    • Caveats / not-in-source: sponsors sit behind two independent caches, and this property addresses only one. CachePrefix drives the framework's prefix eviction; the ASP.NET output cache in front of the public sponsor reads is a separate store the controller evicts by tag in the same action (SponsorsController.cs:169). Removing either half leaves stale sponsor data visible somewhere.
    • +
    • Where it's used: constructed by the DELETE /SessionSpeakers/{id} action of SessionSpeakersController, route id plus [FromQuery] sessionId (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionSpeakersController.cs:196-204), on a controller gated at the class level by the SessionsManage permission (SessionSpeakersController.cs:47, ADR-020); handled by RemoveSessionSpeakerHandler, injected at SessionSpeakersController.cs:51.

    SponsorDTOMapper

    @@ -5653,7 +5586,7 @@

    SponsorDTOMapper

  • Why it's built this way: contrast it with its sibling. SpeakerDTOMapper injects the current-user service and blanks the speaker's email for anyone who is not an Organizer. Sponsors have no such member, so this mapper needs no collaborators and stays a pure function. [Rubric §11, Security] assesses whether sensitive data is filtered at the boundary that owns it: here the boundary exists and has nothing to filter, which is a documented conclusion (:8-11) rather than an omission. [Rubric §30, Compliance and Data Governance] lands in the same place: nothing on the sponsor record is personal data.
  • -
  • Where it's used: registered by the module's convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143, which reaches the IEntityDTOMapper<,,> sweep at MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:206-210); injected concretely into CreateSponsorHandler (CreateSponsorHandler.cs:18) and forwarded from there into the framework create workflow's dtoMapper slot (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:45, used at :101); and resolved through the interface by the closed-generic read service registered for sponsors, EntityQueryService<Sponsor, SponsorDTO, SponsorIdentifierType> (MMCA.ADC.Conference.Application/DependencyInjection.cs:101).
  • +
  • Where it's used: registered by the module's convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139, which reaches the IEntityDTOMapper<,,> sweep at MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:206-210); injected concretely into CreateSponsorHandler (CreateSponsorHandler.cs:18) and forwarded from there into the framework create workflow's dtoMapper slot (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:45, used at :101); and resolved through the interface by the closed-generic read service registered for sponsors, EntityQueryService<Sponsor, SponsorDTO, SponsorIdentifierType> (MMCA.ADC.Conference.Application/DependencyInjection.cs:101).
  • Caveats / not-in-source: the generated MapToDTO has no null guard, and the suite pins that: MapToDTO(null!) throws NullReferenceException (SponsorDTOMapperTests, MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sponsors/DTOs/SponsorDTOMapperTests.cs:75-81), while the hand-written collection overload throws the more conventional ArgumentNullException (SponsorDTOMapper.cs:22). Every caller in this codebase passes a materialized entity, so the asymmetry is documented behavior rather than a live failure mode.
  • UpdateSessionQuestionAnswerCommand

    @@ -5663,14 +5596,14 @@

    UpdateSessionQuestionAnswerCommand
  • What it is: a three-value command to change the text of one answer on one session's questionnaire. It carries the owning session id, the answer id, and the new text (MMCA.ADC.Conference.Application/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerCommand.cs:10-13).
  • Depends on: ICacheInvalidating (:13) and the Session type, referenced only through typeof to build the cache prefix (:16). SessionIdentifierType and SessionQuestionAnswerIdentifierType are both int.
  • -
  • Concept reinforced, commands address the aggregate root: the pattern is taught in Group 05. Note what the first parameter buys. The REST route already identifies the answer (PUT /SessionQuestionAnswers/{id}), yet the command still requires SessionId, supplied from the request body (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionQuestionAnswersController.cs:281). That is not redundancy: SessionQuestionAnswer is a child inside the Session aggregate, so the only legal way to mutate it is to load the root and go through it, and the root's id is what makes that load possible. It is also what lets UpdateSessionQuestionAnswerHandler keep the framework's default by-id load and skip the join-id fallback its remove siblings need. [Rubric §4, DDD] assesses whether aggregate boundaries are respected in the write model: the command's shape enforces the boundary before the handler even runs.
  • +
  • Concept reinforced, commands address the aggregate root: the pattern is taught in Group 05. Note what the first parameter buys. The REST route already identifies the answer (PUT /SessionQuestionAnswers/{id}), yet the command still requires SessionId, supplied from the request body (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionQuestionAnswersController.cs:285). That is not redundancy: SessionQuestionAnswer is a child inside the Session aggregate, so the only legal way to mutate it is to load the root and go through it, and the root's id is what makes that load possible. It is also what lets UpdateSessionQuestionAnswerHandler keep the framework's default by-id load and skip the join-id fallback its remove siblings need. [Rubric §4, DDD] assesses whether aggregate boundaries are respected in the write model: the command's shape enforces the boundary before the handler even runs.
  • Walkthrough: a positional record with one computed member.
    • The three positional parameters SessionId, SessionQuestionAnswerId, and AnswerValue (:11-13) become init-only properties, so the command is immutable once constructed.
    • CachePrefix => $"{typeof(Session).FullName}:" (:16) names the Session, not the answer. Evicting the parent's namespace is what matters: nothing caches a bare answer, but a session read that includes its answers would otherwise keep serving the old text.
  • -
  • Why it's built this way: the handler's response type is a bare Result, not Result<T>, because a successful update returns nothing beyond a 204 (SessionQuestionAnswersController.cs:284-286). That choice is what selects the three-parameter MutateEntityHandlerBase<TCommand, TEntity, TIdentifierType> rather than its DTO-returning four-parameter sibling. Keeping the command a record also makes it structurally comparable, which is what the API tests lean on when they assert the handler was called with the values the route and body supplied.
  • -
  • Where it's used: constructed by SessionQuestionAnswersController.UpdateAsync (SessionQuestionAnswersController.cs:273-287), validated by UpdateSessionQuestionAnswerCommandValidator, and handled by UpdateSessionQuestionAnswerHandler.
  • +
  • Why it's built this way: the handler's response type is a bare Result, not Result<T>, because a successful update returns nothing beyond a 204 (SessionQuestionAnswersController.cs:288-290). That choice is what selects the three-parameter MutateEntityHandlerBase<TCommand, TEntity, TIdentifierType> rather than its DTO-returning four-parameter sibling. Keeping the command a record also makes it structurally comparable, which is what the API tests lean on when they assert the handler was called with the values the route and body supplied.
  • +
  • Where it's used: constructed by SessionQuestionAnswersController.UpdateAsync (SessionQuestionAnswersController.cs:277-291), validated by UpdateSessionQuestionAnswerCommandValidator, and handled by UpdateSessionQuestionAnswerHandler.
  • Caveats / not-in-source: nothing checks that the SessionId in the body actually owns the {id} in the route. It does not need to: the handler loads the session named in the command and looks the answer up inside that aggregate's own collection, so a mismatched pair simply finds no child and returns NotFound (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:531-534).
  • RemoveSessionCategoryItemCommand

    @@ -5682,7 +5615,7 @@

    RemoveSessionCategoryItemCommand

  • Depends on: ICacheInvalidating (RemoveSessionCategoryItemCommand.cs:11), the Session type for the prefix, and the SessionIdentifierType / SessionCategoryItemIdentifierType aliases.
  • Concept reinforced: none new. Note the asymmetry with the matching Add command: AddSessionCategoryItemCommand takes a nullable child id (the database may assign it), while every Remove command takes a required one. There is nothing to generate on removal, so a null there would only be a way to express "remove nothing".
  • Walkthrough: one member, CachePrefix => $"{typeof(Session).FullName}:" (RemoveSessionCategoryItemCommand.cs:13-14), identical to the Add side, so adding and removing a tag evict the same set of cached session projections.
  • -
  • Where it's used: constructed by the DELETE /SessionCategoryItems/{id} action of SessionCategoryItemsController, which takes the join id from the route and the session id from an optional query string (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionCategoryItemsController.cs:186-194), on a controller gated by the SessionsManage permission (SessionCategoryItemsController.cs:47); handled by RemoveSessionCategoryItemHandler.
  • +
  • Where it's used: constructed by the DELETE /SessionCategoryItems/{id} action of SessionCategoryItemsController, which takes the join id from the route and the session id from an optional query string (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionCategoryItemsController.cs:194-202), on a controller gated by the SessionsManage permission (SessionCategoryItemsController.cs:47); handled by RemoveSessionCategoryItemHandler.
  • Caveats / not-in-source: because SessionId is a value-typed alias with no "absent" representation, a caller that omits the optional query parameter model-binds it to 0 rather than being rejected. That is the exact shape RemoveSessionCategoryItemHandler has to cope with, and nothing on this record says so.
  • RemoveSessionQuestionAnswerCommand

    @@ -5694,141 +5627,109 @@

    RemoveSessionQuestionAnswerCommandDepends on: ICacheInvalidating (RemoveSessionQuestionAnswerCommand.cs:11), the Session type for the prefix, and the SessionIdentifierType / SessionQuestionAnswerIdentifierType aliases (ADR-048).
  • Concept reinforced: the same two-id removal shape as RemoveSessionCategoryItemCommand. What is worth noticing is what this record does not carry: no caller identity. Ownership for BR-52 and BR-53 is resolved server side inside RemoveSessionQuestionAnswerHandler (RemoveSessionQuestionAnswerHandler.cs:70-78), so a client cannot claim to be an answer's author by shaping the request. [Rubric §11, Security] assesses whether identity ever travels as request data; here it does not.
  • Walkthrough: one member, CachePrefix => $"{typeof(Session).FullName}:" (RemoveSessionQuestionAnswerCommand.cs:13-14). The prefix names the Session rather than the answer, so one eviction after a successful removal clears every cached session projection that could still be showing the answer (ADR-026). The record contains no cache code: it declares what it invalidates, and CachingCommandDecorator<TCommand, TResult> does the work on success only (ADR-014).
  • -
  • Where it's used: constructed by the DELETE /SessionQuestionAnswers/{id} action of SessionQuestionAnswersController, route id plus [FromQuery] sessionId (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionQuestionAnswersController.cs:290-298), on a controller whose whole surface requires only an authenticated caller (SessionQuestionAnswersController.cs:77) rather than the SessionsManage permission the other two junction controllers demand; handled by RemoveSessionQuestionAnswerHandler, injected at SessionQuestionAnswersController.cs:83.
  • - -

    ActivityNavigationPopulator

    -
    -

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Activities · MMCA.ADC.Conference.Application/Activities/ActivityNavigationPopulator.cs:12 · Level 10 · class (sealed)

    -
    -
      -
    • What it is: the navigation populator for Activity. It declares one thing: how to hydrate an activity's parent Event reference when EF Core cannot reach it with .Include(). The class body is empty (MMCA.ADC.Conference.Application/Activities/ActivityNavigationPopulator.cs:24-25); the entire implementation is the descriptor list handed to the base constructor (:14-23).
    • -
    • Depends on: DeclarativeNavigationPopulator<TEntity> closed over Activity (:14), FKNavigationDescriptor<TEntity, TChild, TChildId> closed over Activity / Event / EventIdentifierType (:16), IUnitOfWork forwarded untouched to the base (:12-14), and the Activity and Event entities.
    • -
    • Concept introduced in this group, FK back-reference hydration: Group 11 teaches the populator machinery; what this class introduces here is the other direction of it. A child-collection descriptor answers "give me the rows that point at me"; an FK descriptor answers "give me the one row I point at". The framework separates the two with a single boolean: FKNavigationDescriptor.RequiresChildren is hard-coded false (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Navigation/FKNavigationDescriptor.cs:23), and the base uses it to pick which caller flag gates the load, includeFKs rather than includeChildren (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Navigation/DeclarativeNavigationPopulator.cs:36). [Rubric §2, Design Patterns] assesses whether behavior is factored into reusable shapes: this is Template Method configured by data, so a new navigation is a descriptor rather than a new query method. [Rubric §7, Microservices Readiness] assesses whether code survives a physical split: the whole reason this file exists is that a join is unavailable when parent and child live in different data sources (ADR-006, ADR-018).
    • -
    • Walkthrough: one descriptor, four settings, and a base algorithm worth following once.
        -
      • PropertyName = nameof(Activity.Event) (:18) is the match key. The base builds an ordinal HashSet of the property names the metadata provider flagged as unsupported and loads only descriptors whose name is in it (DeclarativeNavigationPopulator.cs:30-37). The name comes from the navigation property itself, public Event? Event { get; private set; } (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Activities/Activity.cs:58), which carries a bare [Navigation] attribute; with IsCollection left at its default, metadata discovery files it in the foreign-key bucket (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/NavigationMetadataProvider.cs:78), which is exactly what makes RequiresChildren => false the right gate.
      • -
      • ParentKeySelector = e => e.EventId (:19). The descriptor types this as Func<TEntity, TChildId?> (FKNavigationDescriptor.cs:26), and Activity.EventId is a non-nullable int (Activity.cs:54), so the compiler widens it to int?. The nullable signature exists for entities whose FK is genuinely optional; here it can never be null.
      • -
      • ChildForeignKeySelector = child => child.Id (:20). Read that carefully: for an FK reference the "child foreign key" is the target's own primary key, because the predicate being built matches Event.Id against the set of Activity.EventId values.
      • -
      • AssignAction = (e, events) => e.SetEvent(events.FirstOrDefault()) (:21) calls a public aggregate mutator (Activity.cs:192) rather than assigning the property directly. The navigation itself has a private set (Activity.cs:58), so hydration goes through a named method the entity owns, which is the same discipline the child-collection populators follow. [Rubric §4, DDD]: even an infrastructure-driven write enters the entity through its own API.
      • -
      • The load is NavigationLoader.LoadFKPropertyAsync (FKNavigationDescriptor.cs:39-45), and it is deliberately batched: collect the distinct non-null keys (MMCA.Common/Source/Core/MMCA.Common.Application/Services/NavigationLoader.cs:58), return early assigning empty lists when there are none (:63), build child => parentIds.Contains(child.Id) as an expression tree (:71), run one GetAllAsync with that predicate against the read repository (:80), group the results into a dictionary (:89), and assign per parent (:92). One query for a page of activities, not one per activity.
      • -
      • Two guards mean this usually costs nothing: PopulateAsync returns immediately when the entity list is empty or when the metadata reported no unsupported includes at all (DeclarativeNavigationPopulator.cs:27-28). On a topology where activities and events share a source, that second guard is always true and the populator never touches the database.
      • -
      -
    • -
    • Why it's built this way: ADR-002 makes hydration a declaration in the Application layer rather than an EF concern, and NavigationMetadataProvider decides per navigation whether .Include() is available. Because that decision is configuration, this file is inert in the monolith and becomes the hydration path after a split, with no change to the controller, the query service, or the DTO. [Rubric §3, Clean Architecture]: there is no EF Core namespace anywhere in the file. [Rubric §12, Performance and Scalability]: the batched IN shape is what keeps a paged list from degrading into N+1.
    • -
    • Where it's used: registered as services.TryAddScoped<INavigationPopulator<Activity>, ActivityNavigationPopulator>() (MMCA.ADC.Conference.Application/DependencyInjection.cs:97) and consumed by the closed-generic EntityQueryService<TEntity, TEntityDTO, TIdentifierType> registered on the next line (:88), which invokes it as part of the read pipeline. Its tests assert the DI shape and both empty-input guards without touching the unit of work (ActivityNavigationPopulatorTests, MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Activities/ActivityNavigationPopulatorTests.cs:15-43).
    • -
    • Caveats / not-in-source: AssignAction runs for every parent, including those whose lookup found nothing, in which case FirstOrDefault() assigns null (NavigationLoader.cs:92-99). Combined with the soft-delete query filter the read repository applies (ADR-005), an activity whose owning event has been soft-deleted comes back with Event == null rather than as an error, and a caller that renders the event name has to handle that null.
    • +
    • Where it's used: constructed by the DELETE /SessionQuestionAnswers/{id} action of SessionQuestionAnswersController, route id plus [FromQuery] sessionId (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionQuestionAnswersController.cs:294-302), on a controller whose whole surface requires only an authenticated caller (SessionQuestionAnswersController.cs:77) rather than the SessionsManage permission the other two junction controllers demand; handled by RemoveSessionQuestionAnswerHandler, injected at SessionQuestionAnswersController.cs:83.
    -

    CreateSponsorHandler

    +

    GetNowNextQuery

    -

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sponsors.UseCases.Create · MMCA.ADC.Conference.Application/Sponsors/UseCases/Create/CreateSponsorHandler.cs:15 · Level 10 · class (sealed, partial)

    +

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.NowNext · MMCA.ADC.Conference.Application/Sessions/UseCases/NowNext/GetNowNextQuery.cs:23 · Level 9 · record

      -
    • What it is: the command handler for SponsorCreateRequest, and a good demonstration of how little a create handler has to contain. The whole class is a constructor, one two-line override, and a source-generated log method (MMCA.ADC.Conference.Application/Sponsors/UseCases/Create/CreateSponsorHandler.cs:15-28). Every step of the create itself lives in the framework base it derives from.
    • -
    • Depends on: CreateEntityHandlerBase<TCreateRequest, TEntity, TIdentifierType, TEntityDTO> closed over SponsorCreateRequest / Sponsor / SponsorIdentifierType / SponsorDTO (:20-21); IUnitOfWork (:16), IEntityRequestMapper<TEntity, TCreateRequest, TIdentifierType> (:17, satisfied at runtime by SponsorCreateRequestMapper), and the concrete SponsorDTOMapper (:18), all three forwarded straight into the base constructor; plus ILogger<CreateSponsorHandler> (:19), which is the one dependency the base does not take.
    • -
    • Concept introduced, the workflow base class: the base is an abstract class that itself implements ICommandHandler<in TCommand, TResult> (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:46). That detail is load-bearing and the file says why (CreateEntityHandlerBase.cs:17-23): because the concrete subclass is what carries the closed interface, the module's Scrutor scan keeps discovering it, the decorator pipeline keeps wrapping it, and Scrutor never registers the abstract base. Inheritance here buys code reuse without changing the registration story. [Rubric §1, SOLID] assesses whether shared behavior is factored out without leaking: the subclass overrides one hook and inherits eight steps. [Rubric §2, Design Patterns]: Template Method again, this time over a write workflow instead of over navigation loading.
    • -
    • Walkthrough: two members here, and the inherited pipeline behind them.
        -
      • LogCreated(Sponsor entity) (:24) is the single override, forwarding to the module's own [LoggerMessage] partial. The base declares LogCreated as a no-op virtual precisely so logging stays per-module vocabulary (CreateEntityHandlerBase.cs:148-151).
      • -
      • LogSponsorCreated (:26-27) is the source-generated partial with [LoggerMessage(Level = LogLevel.Information, Message = "Sponsor {SponsorId} created with name '{Name}'")]. That is why the class is partial: the generator supplies the body, and the template's {SponsorId} and {Name} become structured fields rather than a formatted string. [Rubric §13, Observability and Operability] assesses whether logs are queryable: the created id is a field, not text inside a message (ADR-041).
      • -
      • The inherited HandleAsync (CreateEntityHandlerBase.cs:56-63) null-guards the command and calls CreateCoreAsync (:76-102), which runs: PrepareAsync (:83, a pass-through by default, :113-117), requestMapper.CreateEntityAsync with an early exit on a domain failure (:89-91), attemptUnitOfWork.GetRepository<Sponsor, SponsorIdentifierType>() (:94), PersistAsync (:96, which is AddAsync then SaveChangesAsync, :137-138), then LogCreated (:98), OnCreatedAsync (:99, a no-op here), and finally Result.Success(dtoMapper.MapToDTO(entity)) (:101) so the caller receives the store-assigned identity in the response body.
      • -
      • Notice what is still absent from the whole path: no validation call, no transaction scope, no cache eviction, and no try/catch. Validation is applied by ValidatingCommandDecorator<TCommand, TResult> resolving SponsorCreateRequestValidator; invalidation is applied by CachingCommandDecorator<TCommand, TResult> reading CachePrefix off the request; the decorator ordering is registered at MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:134-140. [Rubric §12, Performance & Scalability]: every concern that would otherwise be copy-pasted into thirty handlers lives in a decorator.
      • +
      • What it is: the request for the "happening now / up next" snapshot. One parameter, EventIdentifierType? EventId: a value targets that published event, null asks the handler to feature the current-or-next published event (MMCA.ADC.Conference.Application/Sessions/UseCases/NowNext/GetNowNextQuery.cs:23).
      • +
      • Depends on: IQueryCacheable (GetNowNextQuery.cs:23); the Session type for the key prefix; System.Globalization for the invariant id formatting (GetNowNextQuery.cs:1).
      • +
      • Concept introduced, a query that declares its own cache key: commands implement ICacheInvalidating and name a prefix to evict; queries implement IQueryCacheable and name a key plus a TTL. The caching decorator reads both, so a cached read and the writes that invalidate it agree only because they agree on the prefix string (ADR-014, ADR-026). [Rubric §12, Performance and Scalability] assesses whether hot reads are cached at a layer that can be evicted correctly; the doc comment states the reasoning, a hot, public, non-user-specific read behind a home-screen widget (GetNowNextQuery.cs:13-20).
      • +
      • Walkthrough: two computed members and no methods.
          +
        • CacheKey (GetNowNextQuery.cs:26-35) builds {Session full name}:NowNext:{scope} where scope is the event id formatted with CultureInfo.InvariantCulture, or the literal "current" when the id is null (:30-33). Two things matter here. The key sits under the same Session aggregate prefix the session commands declare as their CachePrefix, so any session write evicts this entry through prefix eviction once an IConnectionMultiplexer is registered. And the id-less form gets its own stable key rather than colliding with whichever event happens to be current.
        • +
        • CacheDuration (GetNowNextQuery.cs:38) is TimeSpan.FromSeconds(30). The file explains why the TTL is short and why it is not redundant with prefix eviction (:16-19): the payload changes with the wall clock as sessions roll over time buckets, event-level edits are not session writes, and the TTL is the sole backstop when prefix eviction is unavailable. [Rubric §29, Resilience and Business Continuity]: correctness degrades to "at most 30 seconds stale" rather than "wrong until someone writes a session".
      • -
      • Why it's built this way: the repository is resolved from the unit of work inside the workflow rather than constructor-injected, and the base states the rule outright (CreateEntityHandlerBase.cs:25-28): only the unit of work knows which physical data source the entity resolves to. Note also the injection asymmetry, the request mapper as an interface and the DTO mapper as a concrete class (CreateSponsorHandler.cs:17-18). That is a DI fact rather than a style choice: the framework scan registers both mapper families AsSelfWithInterfaces (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:206-210, :213-217), so either shape resolves.
      • -
      • Where it's used: registered by the ICommandHandler<,> assembly scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:246-250, invoked from MMCA.ADC.Conference.Application/DependencyInjection.cs:143) and injected into SponsorsController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sponsors/SponsorsController.cs:42), reached through the base CreateAsync that the controller's permission-gated override wraps (SponsorsController.cs:162-171). The generic CRUD registration for sponsors runs after the scan and uses TryAdd, so this class keeps the create verb while the framework supplies update and delete (MMCA.ADC.Conference.Application/DependencyInjection.cs:145-155). Tests: CreateSponsorHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sponsors/UseCases/CreateSponsorHandlerTests.cs:14).
      • -
      • Caveats / not-in-source: nothing in the Application layer proves that EventId names a real event. SponsorCreateRequestValidator only requires it to be non-default through SponsorEventIdRules<T> (MMCA.ADC.Conference.Application/Sponsors/Validation/SponsorValidationRules.cs:115-120), the mapper performs no lookup, and Sponsor.Create validates only name, logo URL, and booth number (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sponsors/Sponsor.cs:119-122). The guarantee comes one layer down: the EF configuration declares a required FK to Event (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Sponsors/SponsorConfiguration.cs:59-65), so a bogus id fails at SaveChangesAsync as a database error rather than as a validation Result.
      • +
      • Why it's built this way: the widget has no event id of its own (GetNowNextQuery.cs:10-12), so an id-less form has to exist; making it a nullable parameter on one query rather than a second query type keeps one handler, one cache policy, and one payload shape (ADR-042 Wave 8, cited in the file at :8).
      • +
      • Where it's used: constructed twice by EventsController, with an id for the per-event now-next action (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:179) and with EventId: null for the event-less one (EventsController.cs:193). Both actions are [AllowAnonymous] and carry [OutputCache(PolicyName = "NowNextCache")] (EventsController.cs:173-174, :186-187), a 60-second public policy tagged conference and conference:sessions (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:284). Handled by GetNowNextHandler. Covered by GetNowNextQueryCacheTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/NowNext/GetNowNextQueryCacheTests.cs:14).
      • +
      • Caveats / not-in-source: there are two independent cache layers on this read, the 30-second query cache declared here and the 60-second HTTP output cache declared on the endpoint. Nothing in source ties the two durations together, so the staleness a widget actually sees is bounded by the output cache, not by CacheDuration.
      -

      PublicConferenceVisibility

      +

      SessionCreateRequest

      -

      MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Common · MMCA.ADC.Conference.Application/Common/PublicConferenceVisibility.cs:28 · Level 10 · class (static)

      +

      MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.Create · MMCA.ADC.Conference.Application/Sessions/UseCases/Create/SessionCreateRequest.cs:11 · Level 9 · record class

        -
      • What it is: the single definition of what an anonymous or non-privileged caller may see in the conference catalog, expressed as three id-list resolvers that the public read filters turn into IN (...) specifications (MMCA.ADC.Conference.Application/Common/PublicConferenceVisibility.cs:10-15). Three rules: an event is visible when published (BR-108), a session is visible when its event is visible and its status is on the BR-49 allow-list, and a speaker is visible when they have at least one eligible session inside the scoped published-event set (BR-239) (:18-21).
      • -
      • Depends on: IUnitOfWork and the read-side IEntityQuerier<TEntity, TIdentifierType> it hands out (:40, :75, :126-127, :151), CrossSourceSpecification (:63), InlineSpecification<TEntity, TIdentifierType> (:149), PublicSessionStatusSpecification (:68, :148), and the Event, Session, and SessionSpeaker entities.
      • -
      • Concept introduced, authorization as a translatable data filter: read authorization here is not a check that runs after the query, it is the query. Every rule is resolved into a materialized list of ids and embedded in a predicate, never expressed as a navigation join. The file states the three reasons (:22-26): the criteria stay translatable on any engine (ADR-018), each aggregate keeps its by-id boundary to the others, and the results pass the specification fitness test. [Rubric §11, Security] assesses whether authorization is enforced where the data is read rather than in a view: because one helper backs the session, speaker, sponsor, room, activity, and junction filters, closing a leak in one place closes it everywhere, which is the property that motivates the whole file. [Rubric §8, Data Architecture]: a cross-aggregate rule becomes a scalar projection plus an IN, the shape a split topology can still execute.
      • -
      • Concept reinforced, read repository versus write repository: every resolver here asks the unit of work for GetReadRepository<TEntity, TIdentifierType>() (:40, :75, :126-127, :151) and types the result as IEntityQuerier<TEntity, TIdentifierType>, the query-only face of the repository. That is the correct call for a projection nothing intends to mutate, and it is a different method from the GetRepository the write handlers in this group use. [Rubric §6, CQRS and Event-Driven] assesses whether the read and write paths are actually distinct at the abstraction level, not just by convention.
      • -
      • Walkthrough: three public resolvers and one private helper.
          -
        • GetPublishedEventIdsAsync (:36-48) resolves the read repository (:40) and projects ids with a predicate in one call, GetProjectedAsync(e => e.Id, e => e.IsPublished, asTracking: false, ...) (:42-44). The result is materialized once so callers embed a stable collection EF can translate (:46-47).
        • -
        • GetVisibleSessionIdsAsync (:57-82) delegates the two-source AND to CrossSourceSpecification.BuildAsync (:63-70), passing e => e.IsPublished as the principal predicate, s => s.EventId as the dependent FK, and PublicSessionStatusSpecification.StatusCriteria as the local predicate. That helper runs the principal projection first and returns a specification whose criteria is localPredicate AND principalKeys.Contains(fk), built as an expression tree with no Expression.Invoke so it stays translatable on every provider (MMCA.Common/Source/Core/MMCA.Common.Application/Specifications/CrossSourceSpecification.cs:66-90). The specification then reaches the repository as a specification: ListAsync(specification, s => s.Id, cancellationToken) (:77-79) is the untracked, soft-delete-filtered projection that an explicit argument list used to spell out (:72-74).
        • -
        • GetVisibleSpeakerIdsAsync (:104-134) takes an optional event scope. It resolves the published set first (:109), and when a scope is supplied it narrows to that single event only if the event is published, otherwise to the empty list (:113-117); an unpublished or unknown scoped event is not an error, it simply has no public speakers (:111-112). Empty scope and empty eligible-session set both short-circuit to [] (:119-124), then the join table is projected with eligibleSessionIds.Contains(ss.SessionId) and de-duplicated (:126-133).
        • -
        • GetEligibleSessionIdsAsync (:141-158) is the private narrowing variant: new PublicSessionStatusSpecification().And(new InlineSpecification<Session, SessionIdentifierType>(s => scopedEventIds.Contains(s.EventId))) (:148-149), which keeps the criteria a translatable IN filter with no navigation join (:146-147).
        • -
        -
      • -
      • Why it's built this way: the remark at :97-103 is the most load-bearing comment in the file, and it records a real rule, not a preference. The EventSpeaker junction is deliberately not treated as a visibility grant, because the Sessionize import writes a row there for every speaker in the response, so reading it as acceptance would publish the entire imported roster and make the filter vacuous. The session link is the only acceptance signal a speaker carries, so it is the only path consulted: a speaker whose sessions are all waitlisted or declined, and one linked to nothing, both stay hidden. [Rubric §4, DDD]: the rule is stated in the vocabulary of the business (published, accepted, assigned) and lives beside the aggregates it constrains.
      • -
      • Where it's used: by the eight public-filter query handlers, one per publicly readable entity or junction: GetPublicSponsorFilterHandler (MMCA.ADC.Conference.Application/Sponsors/UseCases/GetPublicSponsorFilter/GetPublicSponsorFilterHandler.cs:25), GetPublicActivityFilterHandler (.../Activities/UseCases/GetPublicActivityFilter/GetPublicActivityFilterHandler.cs:25), GetPublicRoomFilterHandler (.../Events/UseCases/GetPublicRoomFilter/GetPublicRoomFilterHandler.cs:25), GetPublicSpeakerFilterHandler (.../Speakers/UseCases/GetPublicSpeakerFilter/GetPublicSpeakerFilterHandler.cs:26, which passes the query's optional event scope straight through), GetPublicSpeakerCategoryItemFilterHandler (.../Speakers/UseCases/GetPublicSpeakerCategoryItemFilter/GetPublicSpeakerCategoryItemFilterHandler.cs:26), GetPublicSessionSpeakerFilterHandler (.../Sessions/UseCases/GetPublicSessionSpeakerFilter/GetPublicSessionSpeakerFilterHandler.cs:24), GetPublicSessionCategoryItemFilterHandler (.../Sessions/UseCases/GetPublicSessionCategoryItemFilter/GetPublicSessionCategoryItemFilterHandler.cs:25), and GetPublicEventSpeakerFilterHandler (.../Events/UseCases/GetPublicEventSpeakerFilter/GetPublicEventSpeakerFilterHandler.cs:31, :38), which calls two resolvers so a junction row follows the visibility of both its parents.
      • -
      • Caveats / not-in-source: the id lists are materialized and embedded, and the framework says so: the helper fits principal sets that are small and bounded, the "published events" shape (MMCA.Common/Source/Core/MMCA.Common.Application/Specifications/CrossSourceSpecification.cs:17-20). A single call to GetVisibleSpeakerIdsAsync issues three sequential round trips (events, eligible sessions, join rows), and the junction handler calls two resolvers, reading the bounded Event table twice. Nothing in this file caches any of it, and there is no test class dedicated to this type: its behavior is exercised only through the eight handlers that call it.
      • +
      • What it is: the POST body for creating a session and, unusually, the command message itself. There is no separate CreateSessionCommand: CreateSessionHandler closes CreateEntityHandlerBase<TCreateRequest, TEntity, TIdentifierType, TEntityDTO> over this record (MMCA.ADC.Conference.Application/Sessions/UseCases/Create/CreateSessionHandler.cs:29-30), so the request record travels the whole decorator pipeline unchanged.
      • +
      • Depends on: ICreateRequest, ICacheInvalidating, and ISessionFieldsRequest (SessionCreateRequest.cs:11); the Session type for the prefix; the SessionIdentifierType, EventIdentifierType, and RoomIdentifierType aliases.
      • +
      • Concept introduced, the request DTO as the command: ICreateRequest is a pure marker with no members; its only job is to be a generic constraint on IEntityRequestMapper<TEntity, TCreateRequest, TIdentifierType> and on the create handler base (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:47). That constraint is what lets the generic create pipeline in AggregateRootEntityControllerBase bind a request body straight to a handler with no intermediate command type (SessionsController.cs:55-56). [Rubric §9, API and Contract Design] assesses whether the wire contract is explicit: it is, but the price is that the HTTP contract and the internal command contract are one type and cannot evolve independently. [Rubric §34, Architecture Governance & Documentation]: one type instead of two, at the cost of that coupling.
      • +
      • Concept introduced, an interface that exists to make a validator reusable: ISessionFieldsRequest declares the seven session fields the create and update requests validate identically, Title, Description, Status, LiveUrl, RecordingUrl, AccessibilityInfo, and ResourceLinks (MMCA.ADC.Conference.Application/Sessions/Validation/ISessionFieldsRequest.cs:13-35). Implementing it is what lets SessionFieldRules<T> declare the shared rule list once over a constrained T (SessionValidationRules.cs:111-124). Fields validated by only one operation stay off the interface deliberately: EventId is create-only, because BR-140 makes it immutable afterwards (ISessionFieldsRequest.cs:8-12). [Rubric §1, SOLID]: the interface is the smallest surface that makes the rules generic, not a mirror of the record.
      • +
      • Walkthrough: CachePrefix (SessionCreateRequest.cs:14) is the same session-wide prefix the child commands use, so a create evicts cached session reads. Sixteen init-only data properties follow. Only two are required: Title (:20) and EventId (:59). Id (:17) is a plain non-nullable SessionIdentifierType documented as "auto-generated if not provided", which in practice means a caller sends nothing and the property arrives as 0; CreateSessionHandler reads that 0 as its signal to allocate an id from the reserved manual range. LiveUrl, RecordingUrl, AccessibilityInfo, and ResourceLinks (:47, :50, :53, :56) are nullable strings rather than Uri, matching how Sessionize exports them; the validation rule sets say the same thing, length-only because the value is stored as an opaque string for Sessionize compatibility (SessionValidationRules.cs:56-58). Status (:32) is a nullable string, not an enum, which is what makes a session with no status at all representable (the organizer-created case PublicSessionStatusSpecification has to allow for). [Rubric §15, Best Practices and Code Quality]: init-only accessors make the request immutable once bound, and CreateSessionHandler uses with rather than mutation when it fills in the id (CreateSessionHandler.cs:94).
      • +
      • Where it's used: bound from the body of POST /Sessions on SessionsController (SessionsController.cs:271-273), an action that overrides the base create purely to add the BR-86 date-range warning header and to make the [Idempotent] contract visible at the ADC endpoint (SessionsController.cs:264-270); validated by SessionCreateRequestValidator; turned into an entity by SessionCreateRequestMapper; handled by CreateSessionHandler.
      • +
      • Caveats / not-in-source: the contract carries no duration field: a session's length is expressed only through StartsAt and EndsAt (:26, :29), so there is no client-supplied value that could disagree with the range. The one ambiguity left is the id. Because Id (:17) is non-nullable, "omitted" and "explicitly 0" arrive as the same value, and CreateSessionHandler treats both as a request for a generated id (CreateSessionHandler.cs:42, :79). A caller therefore cannot ask for id 0 itself, and nothing in the contract says so beyond the "auto-generated if not provided" summary.
      -

      SponsorCreateRequestMapper

      +

      UpdateSessionQuestionAnswerCommandValidator

      -

      MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sponsors.UseCases.Create · MMCA.ADC.Conference.Application/Sponsors/UseCases/Create/SponsorCreateRequestMapper.cs:11 · Level 10 · class (sealed)

      +

      MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.UpdateSessionQuestionAnswer · MMCA.ADC.Conference.Application/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerCommandValidator.cs:16 · Level 10 · class (sealed)

        -
      • What it is: the one place that knows how to turn a SponsorCreateRequest into a Sponsor. It does not construct the entity itself; it calls the domain factory and hands back whatever Result<Sponsor> that factory returns (MMCA.ADC.Conference.Application/Sponsors/UseCases/Create/SponsorCreateRequestMapper.cs:19-31).
      • -
      • Depends on: IEntityRequestMapper<TEntity, TCreateRequest, TIdentifierType> closed over Sponsor / SponsorCreateRequest / SponsorIdentifierType (:12), the Sponsor aggregate and its Create factory, and Result (:3).
      • -
      • Concept reinforced, request mapping is not object mapping: SponsorDTOMapper can be source-generated because its target is a settable record. This mapper cannot, because its target is a factory that returns a Result<T>: the entity's constructor is private and the only way in runs the invariants first. So the direction out of the domain is generated and the direction into it is hand-written, which is exactly the split ADR-001 describes. [Rubric §4, DDD] assesses whether invariants are unavoidable: there is no path from a request to a Sponsor that skips Sponsor.Create.
      • -
      • Walkthrough: one method.
          -
        • ArgumentNullException.ThrowIfNull(request) (:17) guards the reference the interface does not declare as nullable.
        • -
        • Task.FromResult(Sponsor.Create(...)) (:19-31) forwards twelve values in the factory's parameter order (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sponsors/Sponsor.cs:105-117). There is no await because there is no I/O: the method is Task-returning to satisfy the interface, not because anything is asynchronous, and wrapping a completed value avoids allocating a state machine.
        • -
        • The work then happens in the domain: Sponsor.Create (Sponsor.cs:105-136) combines three invariant checks for name, logo URL, and booth number (:119-122), decides whether to honor or discard the supplied id based on IsIdValueGenerated (:126-131), and raises SponsorChanged with DomainEntityState.Added before returning success (:133).
        • -
        • request.Id is a non-nullable int widened to the factory's SponsorIdentifierType? parameter (Sponsor.cs:106), which is why the request can declare a plain value type and still reach a nullable factory slot.
        • +
        • What it is: the FluentValidation validator for UpdateSessionQuestionAnswerCommand. One rule chain over one property, AnswerValue, checking that it is present and not longer than any answer type could legitimately be (MMCA.ADC.Conference.Application/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerCommandValidator.cs:18-25).
        • +
        • Depends on: AbstractValidator<T> from FluentValidation (:1, :16) and QuestionInvariants for the length constant (:2, :23-24).
        • +
        • Concept introduced, splitting a business rule by what it can see: BR-124 constrains an answer's value, but only part of it is checkable from the command alone. The class remark draws the line explicitly (:10-15): the type-independent half lives here, because "non-empty" and "under the widest ceiling" need nothing but the string; the type-specific half (a Rating between 1 and 5, an Email that parses, the Text length rule this ceiling mirrors) needs the owning question's type, which is a database read, so it stays in QuestionInvariants.EnsureAnswerValueMatchesQuestionType (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Questions/QuestionInvariants.cs:122) where the handler has the entity in hand. [Rubric §24, Forms, Validation, and UX Safety] assesses whether a caller gets the cheapest correct rejection: a blank or 5000-character answer fails in the pipeline before a database round trip, while a "4" against a 1-to-3 rating question needs the question and fails in the domain. [Rubric §3, Clean Architecture]: neither half is duplicated in the other layer.
        • +
        • Walkthrough: one RuleFor chain (:19-25).
            +
          • NotEmpty() with the message "Answer value is required." and the error code SessionQuestionAnswer.AnswerValue.Required (:20-22).
          • +
          • MaximumLength(QuestionInvariants.TextAnswerMaxLength) with the code SessionQuestionAnswer.AnswerValue.TooLong (:23-25). The constant is 2000 (QuestionInvariants.cs:28), and referencing it rather than a literal is what keeps this ceiling identical to the one the domain enforces for text answers (QuestionInvariants.cs:151-155).
          • +
          • Both failures carry an explicit WithErrorCode, so the API surfaces a stable machine-readable code rather than only English prose.
        • -
        • Why it's built this way: keeping the factory call behind an interface means the create slice can grow an asynchronous pre-check (a uniqueness lookup, for example) by changing this one class, with no edit to CreateSponsorHandler, which forwards the interface into the framework base (CreateSponsorHandler.cs:17, :21). [Rubric §1, SOLID]: dependency inversion applied at the smallest useful granularity.
        • -
        • Where it's used: registered by the IEntityRequestMapper<,,> assembly scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:221-225, invoked from MMCA.ADC.Conference.Application/DependencyInjection.cs:143), forwarded from CreateSponsorHandler into the base, and called there at MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:90.
        • -
        • Caveats / not-in-source: the async signature is currently unused, and no existence or uniqueness check happens here today; see the caveat on CreateSponsorHandler for what does and does not verify EventId. The base's PrepareAsync hook (CreateEntityHandlerBase.cs:114-118) is the other place such a check could live, and the sponsor slice overrides neither.
        • +
        • Why it's built this way: the file names its own model, AddSessionQuestionAnswerCommandValidator on the create side of the same aggregate (:7-8). Create and update of one child answer to the same rule, so the two validators are deliberately the same shape; a reader who has seen one has seen both.
        • +
        • Where it's used: nothing references it by name. It is discovered by AddValidatorsFromAssembly(moduleAssembly) in the module scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:258, invoked at MMCA.ADC.Conference.Application/DependencyInjection.cs:139) and run by ValidatingCommandDecorator<TCommand, TResult> ahead of UpdateSessionQuestionAnswerHandler.
        • +
        • Caveats / not-in-source: there is no test class for this validator in the Conference application test project, so the two error codes are pinned only by the source. Nothing here validates SessionId or SessionQuestionAnswerId; a zero or unknown id is left to the handler's load, which answers NotFound.
        -

        SponsorCreateRequestValidator

        +

        GetNowNextHandler

        -

        MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sponsors.UseCases.Create · MMCA.ADC.Conference.Application/Sponsors/UseCases/Create/SponsorCreateRequestValidator.cs:7 · Level 10 · class (sealed)

        +

        MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.NowNext · MMCA.ADC.Conference.Application/Sessions/UseCases/NowNext/GetNowNextHandler.cs:20 · Level 10 · class (sealed)

          -
        • What it is: the FluentValidation validator for SponsorCreateRequest. Its constructor is two Include calls and nothing else (MMCA.ADC.Conference.Application/Sponsors/UseCases/Create/SponsorCreateRequestValidator.cs:9-16): it owns no rule of its own, it composes the shared sponsor rule bundle and adds the one rule the create verb needs on top of it.
        • -
        • Depends on: AbstractValidator<T> from FluentValidation (:7), SponsorFieldRules<T> closed over the create request (:11), and SponsorEventIdRules<T> (:15).
        • -
        • Concept reinforced, composable rule objects, one level up: the Include technique is introduced in Group 06; what this file shows is the technique applied twice. Include merges another validator's rules into this one, and SponsorFieldRules<T> is itself nothing but eight Include calls over the ISponsorFieldsRequest members (MMCA.ADC.Conference.Application/Sponsors/Validation/SponsorValidationRules.cs:139-154). Because the bundle is constrained to the interface (SponsorValidationRules.cs:141), the create and the update validator bind the same eight rules to two different request types without restating any of them. The payoff is visible in the sibling: SponsorUpdateRequestValidator is a single Include of that same bundle (MMCA.ADC.Conference.Application/Sponsors/UseCases/Update/SponsorUpdateRequestValidator.cs:9-10), so the create and update contracts cannot drift on max lengths or error codes. [Rubric §24, Forms, Validation, and UX Safety] assesses whether validation is stated once and enforced consistently: the rule text and error codes a client sees are identical on both verbs.
        • -
        • Walkthrough: two lines, and the bundle behind the first.
            -
          • Include(new SponsorFieldRules<SponsorCreateRequest>()) (:11) pulls in the eight shared rules (SponsorValidationRules.cs:145-152): SponsorNameRules<T> over Name, a RequiredStringRules<T> with the label "Sponsor Name" and SponsorInvariants.NameMaxLength (:13-18); SponsorSortRules<T>, a NonNegativeIntRules<T> with the error code Sponsor.Sort.Negative (:126-131); three URL rules, SponsorLogoUrlRules<T> (:26-36), SponsorWebsiteUrlRules<T> (:56-66), and SponsorLinkedInUrlRules<T> (:74-84), each of which wraps AbsoluteUrlRules<T> in a When clause so an empty value passes but a supplied one must be an absolute http or https URL as well as within its max length; and three OptionalStringRules<T> derivations, SponsorDescriptionRules<T> (:43-48), SponsorTwitterHandleRules<T> (:91-96), and SponsorBoothNumberRules<T> (:103-108). Every max length comes from the domain's SponsorInvariants constants, so the request rule and the entity invariant cannot disagree.
          • -
          • Include(new SponsorEventIdRules<SponsorCreateRequest>(p => p.EventId)) (:15) is the create-only delta, and the comment above it says why (:13-14): the owning event is chosen once, at creation, and the update request does not carry it, so moving a sponsor between events is a create plus a delete. The rule derives from RequiredIdRules<T, TId> with the field phrase "an Event for the Sponsor" and the error code Sponsor.EventId.Required (SponsorValidationRules.cs:115-120).
          • +
          • What it is: the read handler behind the now-next snapshot. It picks an event, filters that event's sessions down to the publicly exportable ones, and splits them into "running at this instant" and "the next batch to start".
          • +
          • Depends on: IQueryHandler<in TQuery, TResult> closed over GetNowNextQuery and Result<NowNextDTO> (GetNowNextHandler.cs:22); IUnitOfWork (:21); TimeProvider from the BCL (:22); CalendarExportMapper for IsExportable and ToUtc (:1, :48, :81-82); CurrentEventSelector for the live window and the current-or-next rule (:68, :101); the Event and Session aggregates; NowNextSessionDTO.
          • +
          • Concept introduced, injecting the clock: the handler takes TimeProvider and reads timeProvider.GetUtcNow() once at the top (GetNowNextHandler.cs:29), then uses that single instant for every comparison in the method. Two consequences. First, the snapshot is internally consistent: a session cannot be classified as both running and upcoming because the clock moved between two comparisons. Second, the whole "is it 9:30 on conference morning" question becomes a test input, which is exactly how GetNowNextHandlerTests pins its scenarios (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/NowNext/GetNowNextHandlerTests.cs:20). [Rubric §14, Testability] assesses whether ambient state is injected rather than reached for; a DateTime.UtcNow in this method would make the behavior untestable.
          • +
          • Concept introduced, wall clock versus instant: a conference schedule is authored in local wall-clock time, but "is it running now" is a question about instants. The handler resolves the event's TimeZone string to a TimeZoneInfo (:45) and converts each session's stored wall-clock StartsAt and EndsAt to a DateTimeOffset through CalendarExportMapper.ToUtc (:81-82), which also shifts spring-forward gap times ahead one hour so an invalid local time still yields an instant (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/ExportCalendar/CalendarExportMapper.cs:48-57). NowNextSessionDTO then carries both forms, local for printing on a badge or widget and UTC for callers doing their own math (:74-82). [Rubric §27, Internationalization] in its time-zone sense: the displayed value is the event's zone, never the server's.
          • +
          • Concept introduced, refusing to swallow a data defect: the time-zone lookup has no fallback. The comment at :43-44 states the reasoning: EventInvariants.EnsureTimeZoneIsValid guards every write path (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:82), so a stored id always resolves, and an unresolvable one is a data defect that must surface rather than be papered over with UTC. [Rubric §13, Observability and Operability] assesses whether a broken invariant is visible: a TimeZoneNotFoundException here is loud, where a silent UTC fallback would render a schedule that is quietly hours wrong.
          • +
          • Walkthrough: one public method and two private helpers.
              +
            • HandleAsync (GetNowNextHandler.cs:25-72) starts with the clock (:29), then calls SelectEventAsync and refuses anything missing or unpublished with Error.NotFound targeting Event (:31-36). That guard is the access control for this endpoint: both actions are [AllowAnonymous], so "published" is the only thing standing between an anonymous caller and an unannounced event's schedule. [Rubric §11, Security].
            • +
            • It loads every session for the event through unitOfWork.GetReadRepository<Session, SessionIdentifierType>() with no includes and no visibility specification (:38-40), builds a room-id-to-name dictionary from the already-included Rooms (:41), then resolves the time zone (:45). Both repository lookups in this handler now go through GetReadRepository rather than GetRepository (:38, :89): the handler never writes, so the read-only repository is the correct one on both paths.
            • +
            • Eligibility reuses the calendar-export rule rather than restating it: rows = sessions.Where(CalendarExportMapper.IsExportable) (:47-50), which means scheduled at both ends, not a service session, and status-eligible per BR-49 through the single SessionStatuses.IsEligible allow-list (CalendarExportMapper.cs:27-29). One definition, two public surfaces.
            • +
            • now is every row whose UTC window contains the instant, ordered by start then room name case-insensitively (:52-56). next is deliberately not "the single next session": the handler takes the minimum future start (:59-60) and returns every row sharing it (:61-66), with the comment stating the intent, so parallel tracks show together (:58).
            • +
            • isLive compares the instant against the event's live window from CurrentEventSelector.GetLiveWindowUtc (:68-69, defined at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Events/CurrentEventSelector.cs:66), the same window the home surfaces use, and the payload is assembled at :71.
            • +
            • ToRow (:74-82) projects one session, resolving the room name through the dictionary and returning null when the session has no room (:78).
            • +
            • SelectEventAsync (:84-107) branches on the nullable id: an explicit id is a direct GetByIdAsync including Rooms (:92-94); otherwise it loads all published events with their rooms (:97-99) and hands them to CurrentEventSelector.SelectCurrentOrNext with accessor lambdas for start, end, and zone (:101-106, defined at CurrentEventSelector.cs:24). Passing accessors rather than an interface is what lets that selector serve both entities and DTOs across the module.
          • -
          • Why it's built this way: no handler calls this class. ValidatingCommandDecorator<TCommand, TResult> resolves it from the container and runs it before the handler, converting failures into a Result rather than an exception (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:136). [Rubric §6, CQRS & Event-Driven Design] and [Rubric §6, CQRS and Event-Driven] both point at the same design: validation is a pipeline stage, so a handler that forgets to validate cannot exist.
          • -
          • Where it's used: registered by AddValidatorsFromAssembly(moduleAssembly) inside the module scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:258, invoked at MMCA.ADC.Conference.Application/DependencyInjection.cs:143). Its tests walk the boundaries directly, including the exact-max-length pass and the null-optional-strings pass (SponsorCreateRequestValidatorTests, MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sponsors/Validation/SponsorCreateRequestValidatorTests.cs:8).
          • -
          • Caveats / not-in-source: two gaps are worth knowing. NotEmpty on an int rejects only the default value, which the framework rule documents as the deliberate "an id was never supplied" check (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:133-139), so it proves an event was chosen, not that the event exists (the database FK does that, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Sponsors/SponsorConfiguration.cs:59-65). And no rule here constrains Tier: an out-of-range enum value passes validation, and Sponsor.Create does not check it either (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sponsors/Sponsor.cs:119-122).
          • +
          • Why it's built this way: the file states the contract at :12-19, that eligibility and DST discipline are shared with the calendar export deliberately. A widget and an .ics download that disagreed about which sessions are public would be a visible defect, and the only way to guarantee they agree is to call the same predicate.
          • +
          • Where it's used: registered by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139); injected into EventsController as nowNextHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:50) and called from both now-next actions (EventsController.cs:179, :191). The payload is fetched over HTTP by NowNextService (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Services/HappeningNow/NowNextService.cs:14), rendered by HappeningNow, and read by the Android NowNextWidgetProvider. Covered by GetNowNextHandlerTests.
          • +
          • Caveats / not-in-source: the query at :38-40 loads every session row for the event, then filters, projects, sorts, and buckets in memory (:47-66). Nothing is pushed to the database beyond the EventId predicate, so the cost scales with the event's total session count rather than with the handful of rows the snapshot returns. The two cache layers on the endpoint make that acceptable in practice, not correct in principle. Separately, ToRow dereferences session.StartsAt! and session.EndsAt! (:79-82); that is safe only because IsExportable already rejected null-scheduled sessions, a coupling the compiler cannot check.
          -

          UpdateSessionQuestionAnswerCommandValidator

          +

          SessionCreateRequestMapper

          -

          MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.UpdateSessionQuestionAnswer · MMCA.ADC.Conference.Application/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerCommandValidator.cs:16 · Level 10 · class (sealed)

          +

          MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.Create · MMCA.ADC.Conference.Application/Sessions/UseCases/Create/SessionCreateRequestMapper.cs:11 · Level 10 · class (sealed)

            -
          • What it is: the FluentValidation validator for UpdateSessionQuestionAnswerCommand. One rule chain over one property, AnswerValue, checking that it is present and not longer than any answer type could legitimately be (MMCA.ADC.Conference.Application/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerCommandValidator.cs:18-25).
          • -
          • Depends on: AbstractValidator<T> from FluentValidation (:1, :16) and QuestionInvariants for the length constant (:2, :23-24).
          • -
          • Concept introduced, splitting a business rule by what it can see: BR-124 constrains an answer's value, but only part of it is checkable from the command alone. The class remark draws the line explicitly (:10-15): the type-independent half lives here, because "non-empty" and "under the widest ceiling" need nothing but the string; the type-specific half (a Rating between 1 and 5, an Email that parses, the Text length rule this ceiling mirrors) needs the owning question's type, which is a database read, so it stays in QuestionInvariants.EnsureAnswerValueMatchesQuestionType (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Questions/QuestionInvariants.cs:118) where the handler has the entity in hand. [Rubric §24, Forms, Validation, and UX Safety] assesses whether a caller gets the cheapest correct rejection: a blank or 5000-character answer fails in the pipeline before a database round trip, while a "4" against a 1-to-3 rating question needs the question and fails in the domain. [Rubric §3, Clean Architecture]: neither half is duplicated in the other layer.
          • -
          • Walkthrough: one RuleFor chain (:19-25).
              -
            • NotEmpty() with the message "Answer value is required." and the error code SessionQuestionAnswer.AnswerValue.Required (:20-22).
            • -
            • MaximumLength(QuestionInvariants.TextAnswerMaxLength) with the code SessionQuestionAnswer.AnswerValue.TooLong (:23-25). The constant is 2000 (QuestionInvariants.cs:28), and referencing it rather than a literal is what keeps this ceiling identical to the one the domain enforces for text answers (QuestionInvariants.cs:147-151).
            • -
            • Both failures carry an explicit WithErrorCode, so the API surfaces a stable machine-readable code rather than only English prose.
            • -
            -
          • -
          • Why it's built this way: the file names its own model, AddSessionQuestionAnswerCommandValidator on the create side of the same aggregate (:7-8). Create and update of one child answer to the same rule, so the two validators are deliberately the same shape; a reader who has seen one has seen both.
          • -
          • Where it's used: nothing references it by name. It is discovered by AddValidatorsFromAssembly(moduleAssembly) in the module scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:258, invoked at MMCA.ADC.Conference.Application/DependencyInjection.cs:143) and run by ValidatingCommandDecorator<TCommand, TResult> ahead of UpdateSessionQuestionAnswerHandler.
          • -
          • Caveats / not-in-source: there is no test class for this validator in the Conference application test project, so the two error codes are pinned only by the source. Nothing here validates SessionId or SessionQuestionAnswerId; a zero or unknown id is left to the handler's load, which answers NotFound.
          • +
          • What it is: the adapter that turns a validated SessionCreateRequest into a Session entity by calling the domain factory. It is one method long and contains no logic of its own.
          • +
          • Depends on: IEntityRequestMapper<TEntity, TCreateRequest, TIdentifierType> closed over Session / SessionCreateRequest / SessionIdentifierType (SessionCreateRequestMapper.cs:11-12); Result; Session.Create.
          • +
          • Concept introduced, request-to-entity mapping is not DTO mapping: entity-to-DTO mapping is source-generated by Mapperly (ADR-001), because it is a mechanical property copy with no rules. Going the other way is the opposite: constructing an entity is where invariants are enforced, so it cannot be generated. This class is therefore hand-written and does exactly one thing, forward the request's fields to the factory, so that Session.Create remains the only path into a valid Session. [Rubric §4, DDD] assesses whether entities can be constructed in an invalid state; here they cannot, because the mapper has no other constructor available to it. [Rubric §2, Design Patterns]: this is an adapter, and its value is precisely that it has no behavior of its own to disagree with the factory.
          • +
          • Walkthrough: CreateEntityAsync (SessionCreateRequestMapper.cs:15-36) guards its argument with ArgumentNullException.ThrowIfNull (:17), then returns Task.FromResult(Session.Create(...)) with fourteen positional arguments plus isInformed: and isConfirmed: passed by name (:19-35). The two named arguments matter: Session.Create declares them as trailing optional parameters after the optional roomId (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:201-203), so naming them is what lets the call skip nothing and stay readable. The method is async in signature only: it returns a completed task because nothing here awaits, which keeps the interface uniform for mappers that do need I/O without paying a state machine for the ones that do not. Validation happens inside the factory, which combines three invariant checks before allocating (Session.cs:205-208: title validity, end-after-start, optional text lengths) and returns Result.Failure<Session> with the combined errors when any fails.
          • +
          • Why it's built this way: the generic create pipeline needs a uniform way to get from some request type to some entity, and the only thing that can vary per entity is which factory to call with which fields. Isolating that in a one-method class means the pipeline never sees a domain constructor.
          • +
          • Where it's used: registered by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139); injected into CreateSessionHandler through the interface, not the concrete type (CreateSessionHandler.cs:25), and invoked by the framework's CreateCoreAsync (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:90). Covered by SessionCreateRequestMapperTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/Create/SessionCreateRequestMapperTests.cs:6).
          • +
          • Caveats / not-in-source: the mapper passes request.Id into a SessionIdentifierType? parameter (SessionCreateRequestMapper.cs:20), so a request whose Id is still 0 would reach the factory as 0 rather than as null. That does not happen on the live path, because CreateSessionHandler replaces a default id with a computed one in its PrepareAsync override before the mapper runs (CreateSessionHandler.cs:79-95), but the mapper itself does not enforce it. See also the Duration property this method drops, noted under SessionCreateRequest.
          -

          UpdateSessionQuestionAnswerHandler

          +

          SessionCreateRequestValidator

          -

          MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.UpdateSessionQuestionAnswer · MMCA.ADC.Conference.Application/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerHandler.cs:21 · Level 10 · class (sealed, partial)

          +

          MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.Create · MMCA.ADC.Conference.Application/Sessions/UseCases/Create/SessionCreateRequestValidator.cs:7 · Level 10 · class (sealed)

            -
          • What it is: the handler for UpdateSessionQuestionAnswerCommand. It supplies three overrides to the shared workflow: which navigation to load, which key to load by, and the mutation itself, which enforces the BR-52/BR-53 ownership rule before delegating to the aggregate (MMCA.ADC.Conference.Application/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerHandler.cs:21-61).
          • -
          • Depends on: MutateEntityHandlerBase<TCommand, TEntity, TIdentifierType> closed over the command, Session, and SessionIdentifierType (:24); IUnitOfWork (:21); ICurrentUserService (:22); ILogger<UpdateSessionQuestionAnswerHandler> (:23); RoleNames (:43); and Result / Error.
          • -
          • Concept reinforced, ownership enforced in the mutation step: the mechanism is taught on RemoveSessionQuestionAnswerHandler, and this class is its exact twin. The controller has already established that the caller is authenticated (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionQuestionAnswersController.cs:77), but only the handler can establish whether this particular row is theirs, because that fact lives in the loaded entity's audit column. An Organizer may edit any answer, everyone else may edit only rows whose CreatedBy matches their user id (:42-50). [Rubric §11, Security] assesses whether authorization decisions are made where the necessary facts exist: role membership comes from the token, row ownership from the aggregate, and both are compared in one expression. [Rubric §4, DDD]: the check reads the child through the root's collection, never through a separate repository.
          • -
          • Walkthrough: three overrides plus the log method.
              -
            • Includes => [nameof(Session.SessionQuestionAnswers)] (:27) and the inherited AsTracking default of true (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/MutateEntityHandlerBase.cs:76). Tracking is load-bearing: the mutation happens on this graph and SaveChangesAsync persists it only because the change tracker is watching.
            • -
            • EntityId(command) => command.SessionId (:30). Unlike its remove siblings this handler does not override LoadAsync, so it uses the base's plain by-id load (MutateEntityHandlerBase.cs:152-160). It can, because the PUT body always carries the session id (SessionQuestionAnswersController.cs:281), so there is no "id omitted" case to recover from. A missing session becomes Error.NotFound in the base, stamped with the handler name and the entity name (MutateEntityHandlerBase.cs:282-283).
            • -
            • MutateAsync (:33-53) null-guards both arguments (:38-39), finds the active answer with a.Id == command.SessionQuestionAnswerId && !a.IsDeleted (:42), and fails with Error.Forbidden(code: "SessionQuestionAnswer.NotOwner", ...) when the answer exists, the caller is not in RoleNames.Organizer, and answer.CreatedBy differs from the current user id (:43-50). The answer is not null guard is the interesting part: when the id names nothing, or names a soft-deleted row, the check is skipped and the call falls through to the domain, which resolves the child with the same active-only predicate and returns NotFound (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:531-534, into MMCA.Common/Source/Core/MMCA.Common.Domain/Entities/AuditableAggregateRootEntity.cs:103). The practical effect is that a non-owner probing for an id they cannot see receives NotFound rather than Forbidden, so the error does not confirm the row exists.
            • -
            • entity.UpdateSessionQuestionAnswer(command.SessionQuestionAnswerId, command.AnswerValue) (:52) does the real work: resolve the child, call answer.UpdateAnswer (which validates the text before assigning it, Session.cs:540-542), and raise SessionQuestionAnswerChanged with DomainEntityState.Updated (Session.cs:544). A failure returns before the save, which the base guarantees (MutateEntityHandlerBase.cs:299-300); that is safe precisely because the domain validates before it mutates, so a refused update leaves the tracked graph unchanged.
            • -
            • LogMutated (:56-57) forwards to the [LoggerMessage] partial at :59-60, which is why the class is partial.
            • +
            • What it is: the input validator for SessionCreateRequest. Its body is two Include calls and nothing else (SessionCreateRequestValidator.cs:9-17).
            • +
            • Depends on: FluentValidation's AbstractValidator<T> (:1, :7); SessionFieldRules<T> and SessionEventIdRules<T> from MMCA.ADC.Conference.Application.Sessions.Validation (:2).
            • +
            • Concept reinforced, composing validators with Include, and where the shared set stops: FluentValidation's Include folds another validator's rules into this one as though they had been written inline. The seven field rules the create and update requests share are already folded once, inside SessionFieldRules<T>, which is generic over T : ISessionFieldsRequest and reads its properties through the interface rather than through per-call selectors (MMCA.ADC.Conference.Application/Sessions/Validation/SessionValidationRules.cs:111-124). So this validator includes that one set (:11) and then adds only its own per-operation delta. [Rubric §15, Best Practices & Code Quality] assesses whether a rule has a single home: change the title constraint once, in SessionTitleRules<T> via SessionFieldRules<T>, and both request paths move together. [Rubric §1, SOLID]: each rule set is one reason to change, and the interface constraint is the smallest thing that makes the shared set generic.
            • +
            • Walkthrough: the constructor (SessionCreateRequestValidator.cs:9-17) has two statements.
                +
              • Include(new SessionFieldRules<SessionCreateRequest>()) (:11) pulls in title, description, status, live URL, recording URL, accessibility info, and resource links in that order (SessionValidationRules.cs:117-123). Title is required plus max-length (SessionValidationRules.cs:13-18); the other six are optional-string max-length rules bounded by SessionInvariants constants.
              • +
              • Include(new SessionEventIdRules<SessionCreateRequest>(p => p.EventId)) (:16) is the create-only delta, and the comment says why (:13-15): a session is scheduled inside an event, and the event it belongs to is chosen exactly once. BR-140 makes it immutable afterwards, so the update path validates it nowhere and UpdateSessionHandler rejects a change instead. The rule itself is a RequiredIdRules specialization carrying the error code Session.EventId.Required (SessionValidationRules.cs:24-29).
            • -
            • Why it's built this way: the handler mirrors its sibling UpdateEventQuestionAnswerHandler, which is deliberate: session-level and event-level questionnaires answer to the same two business rules, and reading either one teaches both. The read side enforces the complementary rule with a specification instead of a branch: the controller scopes non-Organizer list reads with OwnedByUserSpecification<TEntity, TIdentifierType> (SessionQuestionAnswersController.cs:97), so ownership shows up as a filter on reads and as a guard on writes.
            • -
            • Where it's used: registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143); injected into SessionQuestionAnswersController as updateHandler (SessionQuestionAnswersController.cs:82) and called at :218-220. Covered by UpdateSessionQuestionAnswerHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerHandlerTests.cs:14).
            • -
            • Caveats / not-in-source: the handler declares no RowVersion override, so the base's concurrency stamp is skipped (MutateEntityHandlerBase.cs:91, :290-291) and this update is unconditional: two attendees editing the same answer are last-write-wins. That is consistent with the endpoint, which states no If-Match precondition, but nothing in this file records the decision.
            • +
            • Why it's built this way: the parallel SessionUpdateRequestValidator includes the same SessionFieldRules<T> closed over its own request type and adds a different delta. Parameterizing the shared set by the request type, constrained to ISessionFieldsRequest, is what makes that reuse possible across two records that are otherwise unrelated.
            • +
            • Where it's used: discovered by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139) and run by the validation decorator ahead of CreateSessionHandler (ADR-014). Covered by SessionCreateRequestValidatorTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/Validation/SessionCreateRequestValidatorTests.cs:7).
            • +
            • Caveats / not-in-source: the validator says nothing about StartsAt versus EndsAt. Ordering is a domain invariant, checked by SessionInvariants.EnsureEndsAtIsAfterStartsAt inside the factory (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:207, defined at SessionInvariants.cs:126), so an inverted range is rejected one layer later than a too-long title is. Room assignment is likewise absent here and enforced by the handler (CreateSessionHandler.cs:100-122).

            RemoveSessionQuestionAnswerHandler

            @@ -5848,7 +5749,7 @@

            RemoveSessionQuestionAnswerHandler

          • Why it's built this way: putting the ownership rule in the aggregate would force the domain to know about the current user, which is an application-layer concern; putting it in the controller would require loading the row twice. MutateAsync is the one place that already has both the identity service and the loaded answer. [Rubric §3, Clean Architecture].
          • -
          • Where it's used: registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143); injected into SessionQuestionAnswersController as removeHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionQuestionAnswersController.cs:83) and called at SessionQuestionAnswersController.cs:296-298. Covered by RemoveSessionQuestionAnswerHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/RemoveSessionQuestionAnswer/RemoveSessionQuestionAnswerHandlerTests.cs:14).
          • +
          • Where it's used: registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); injected into SessionQuestionAnswersController as removeHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionQuestionAnswersController.cs:83) and called at SessionQuestionAnswersController.cs:300-302. Covered by RemoveSessionQuestionAnswerHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/RemoveSessionQuestionAnswer/RemoveSessionQuestionAnswerHandlerTests.cs:14).
          • Caveats / not-in-source: two things. First, the condition dereferences currentUserService.UserId!.Value (:71), so an unauthenticated caller reaching this handler would throw rather than be refused. That cannot happen through the REST surface, because the controller requires an authenticated principal for its entire surface (SessionQuestionAnswersController.cs:77), but the guarantee lives in the controller attribute, not in this file. Second, the branch is skipped entirely when answer is null (:71): a non-existent or already-deleted id falls through to the aggregate, which returns its own not-found rather than a forbidden, so the endpoint does not leak whether an answer the caller cannot see exists. Note also that this handler derives from MutateEntityHandlerBase and not from the narrower RemoveChildEntityHandlerBase its speaker sibling uses; both reach the same workflow, and nothing in source explains the difference.

          RemoveSessionSpeakerHandler

          @@ -5869,7 +5770,7 @@

          RemoveSessionSpeakerHandler

      • Why it's built this way: the fallback exists because the UI reuses one generic delete affordance across every entity, and that component knows only the row's own id. Teaching the server to resolve the parent is cheaper than special-casing the client, and it keeps the endpoint usable by callers that do have the session id. Putting the fallback in LoadAsync rather than in HandleAsync means it is the only thing that varies: the concurrency stamp, the failure short-circuit, and the post-save hooks stay the framework's.
      • -
      • Where it's used: registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:143); injected into SessionSpeakersController as removeHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionSpeakersController.cs:51) and called at SessionSpeakersController.cs:194-196. Covered by RemoveSessionSpeakerHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/RemoveSessionSpeaker/RemoveSessionSpeakerHandlerTests.cs:12).
      • +
      • Where it's used: registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); injected into SessionSpeakersController as removeHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionSpeakersController.cs:51) and called at SessionSpeakersController.cs:202-204. Covered by RemoveSessionSpeakerHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/RemoveSessionSpeaker/RemoveSessionSpeakerHandlerTests.cs:12).
      • Caveats / not-in-source: the fallback query calls FirstOrDefaultAsync with a predicate over the child collection (:37-41). In practice a join id belongs to exactly one session, but the query does not say so. The log statement also records command.SessionId (:60), which is 0 on the fallback path, so the emitted event names the join row correctly and the session as zero.

      RemoveSessionCategoryItemHandler

      @@ -5879,7 +5780,7 @@

      RemoveSessionCategoryItemHandler

      • What it is: the category-detach handler, and the member of the remove family that has to cope with a caller who does not know the parent id.
      • Depends on: RemoveChildEntityHandlerBase<TCommand, TParent, TIdentifierType> closed over RemoveSessionCategoryItemCommand, Session, and SessionIdentifierType (RemoveSessionCategoryItemHandler.cs:16); IUnitOfWork, passed to the base (:14, :16); IRepository<TEntity, TIdentifierType> as the LoadAsync parameter type (:26); Result; the SessionCategoryItem child.
      • -
      • Concept reinforced: the shared workflow taught on AddSessionSpeakerHandler, minus the DTO. Removals return a bare Result because the controller answers 204 No Content (SessionCategoryItemsController.cs:202), so there is nothing to map and no mapper to inject. The base chains through MutateEntityHandlerBase to MutateCoreAsync (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/MutateEntityHandlerBase.cs:271-321), which loads, guards NotFound, mutates, saves, and logs; RemoveChildEntityHandlerBase adds only one thing, re-declaring Includes as abstract, because a remove that cannot see the collection cannot find the child and would report a wrong NotFound (ChildEntityHandlerBase.cs:148-152).
      • +
      • Concept reinforced: the shared workflow taught on AddSessionSpeakerHandler, minus the DTO. Removals return a bare Result because the controller answers 204 No Content (SessionCategoryItemsController.cs:210), so there is nothing to map and no mapper to inject. The base chains through MutateEntityHandlerBase to MutateCoreAsync (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/MutateEntityHandlerBase.cs:271-321), which loads, guards NotFound, mutates, saves, and logs; RemoveChildEntityHandlerBase adds only one thing, re-declaring Includes as abstract, because a remove that cannot see the collection cannot find the child and would report a wrong NotFound (ChildEntityHandlerBase.cs:148-152).
      • Concept introduced, resolving an aggregate root from a child id: the DELETE endpoint takes the session id as an optional query parameter, so a caller that omits it model-binds SessionId to 0 without a 400 (RemoveSessionCategoryItemHandler.cs:32-34). This is exactly the case the base's LoadAsync hook exists for: it is virtual, defaulting to a by-id load, and is meant to be overridden only when the command can address the aggregate some other way (MutateEntityHandlerBase.cs:144-159). The override branches on the unset id (:35) and queries sessions by a predicate over the child collection, s => s.SessionCategoryItems.Any(sci => sci.Id == command.SessionCategoryItemId), including the collection and tracking it (:37-41); otherwise it loads directly by id (:44-48). Either way the rest of the workflow is identical, so the aggregate boundary is preserved: the removal is still performed by the root, never by reaching into a child repository. [Rubric §4, DDD] assesses exactly this, that children are mutated through their root. [Rubric §9, API and Contract Design]: the optional query parameter is what makes the two shapes one endpoint rather than two.
      • Walkthrough: four overrides and one log method.
        • Includes (:19) returns [nameof(Session.SessionCategoryItems)], the collection the aggregate's remove method searches.
        • @@ -5890,9 +5791,73 @@

          RemoveSessionCategoryItemHandler

      • Why it's built this way: the fallback exists because the UI reuses one generic delete affordance across every entity, and that component knows only the row's own id. Teaching the server to resolve the parent is cheaper than special-casing the client, and it keeps the endpoint usable by callers that do have the session id. Putting it in a LoadAsync override rather than in a hand-written handler is what keeps the rest of the workflow (the NotFound shape, the save ordering, the optimistic-concurrency stamp at MutateEntityHandlerBase.cs:292) shared with every other mutation.
      • -
      • Where it's used: registered by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:143); injected into SessionCategoryItemsController as removeHandler (SessionCategoryItemsController.cs:51) and called at SessionCategoryItemsController.cs:192-194, after which the controller evicts the sessions, categories, and conference output-cache tags (SessionCategoryItemsController.cs:201). Covered by RemoveSessionCategoryItemHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/RemoveSessionCategoryItem/RemoveSessionCategoryItemHandlerTests.cs:12).
      • +
      • Where it's used: registered by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139); injected into SessionCategoryItemsController as removeHandler (SessionCategoryItemsController.cs:51) and called at SessionCategoryItemsController.cs:200-202, after which the controller evicts the sessions, categories, and conference output-cache tags (SessionCategoryItemsController.cs:209). Covered by RemoveSessionCategoryItemHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/RemoveSessionCategoryItem/RemoveSessionCategoryItemHandlerTests.cs:12).
      • Caveats / not-in-source: on the direct path the handler takes SessionId on faith. Passing a valid join id together with the wrong session id yields a not-found from the aggregate rather than a cross-session removal, but nothing here verifies the pairing before the load. The fallback path uses FirstOrDefaultAsync over a collection predicate (:37-41), which is correct because a join id belongs to exactly one session, but the query does not say so. The log statement also records command.SessionId (:60), which is 0 on the fallback path, so the emitted event names the join row correctly and the session as zero.
      +

      CreateSessionHandler

      +
      +

      MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.Create · MMCA.ADC.Conference.Application/Sessions/UseCases/Create/CreateSessionHandler.cs:22 · Level 14 · class (sealed partial)

      +
      +
        +
      • What it is: the session create handler, and the most involved handler in this chapter. On top of the shared create workflow it allocates application-assigned ids out of a reserved range, requires the parent event to exist, guards room assignment, and retries a bounded number of times when a concurrent create takes the id it computed.
      • +
      • Depends on: CreateEntityHandlerBase<TCreateRequest, TEntity, TIdentifierType, TEntityDTO> closed over SessionCreateRequest, Session, SessionIdentifierType, and SessionDTO (CreateSessionHandler.cs:29-30); IUnitOfWork (:23); IServiceScopeFactory from Microsoft.Extensions.DependencyInjection (:24); IEntityRequestMapper<TEntity, TCreateRequest, TIdentifierType> (:25, implemented by SessionCreateRequestMapper); the concrete SessionDTOMapper (:26); IUniqueConstraintViolationDetector (:27); SessionInvariants for the reserved range; SessionRoomScheduling for BR-130; the Event aggregate.
      • +
      • Concept introduced, application-assigned ids in a reserved range: session primary keys are not database-generated, because the int PK is the Sessionize id (CreateSessionHandler.cs:76-78). An organizer-created session therefore needs an id that can never collide with one Sessionize will later import. The domain reserves the top of the range for that: SessionInvariants.ManualIdRangeStart is 999_999_000 and ManualIdRangeEnd is 999_999_999 (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/SessionInvariants.cs:44, :47), so a thousand manual ids sit above anything Sessionize issues. The handler queries the existing rows in that window with ignoreQueryFilters: true (:81-85), takes Max + 1 or the range start when empty (:87-89), and fails with a plain Error.Failure coded Session.ManualIdRangeExhausted when the range is exhausted (:91-92). Ignoring the query filters is load-bearing: a soft-deleted session still occupies its id, so counting only visible rows would hand out an id the database already holds. [Rubric §8, Data Architecture] assesses whether the identity strategy matches the data's provenance; here an externally-owned key space forced the choice, and the reserved range is how the two writers coexist.
      • +
      • Concept introduced, retrying a lost id race in a fresh DI scope: computing Max + 1 and inserting is a read-then-write race, so two concurrent organizer creates can compute the same id. The handler accepts that and recovers instead of locking. MaxManualIdAttempts is 3 (:33). The HandleAsync override (:36-66) loops, and the catch filter engages only when attempts remain and the detector classifies the exception as a unique-constraint violation (:60). The subtle part is the retry, which does not reuse the ambient unit of work: scopeFactory.CreateAsyncScope() produces a fresh scope and a fresh IUnitOfWork (:56-57), because the ambient DbContext still tracks the insert that just failed and would replay it (:54-55). This is precisely the extension point the base documents: a manual-id retry variant overrides HandleAsync to wrap CreateCoreAsync in its retry loop and overrides PrepareAsync to recompute the id per attempt, and CreateCoreAsync takes the unit of work as a parameter so a retry can run against a fresh scope while reusing the whole workflow (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:29-36, :76-79). [Rubric §29, Resilience and Business Continuity] assesses whether transient contention is survived rather than surfaced; a bounded, condition-filtered retry is the shape that does not turn a real error into an infinite loop. [Rubric §12, Performance and Scalability]: the design trades a rare retry for never taking a table lock.
      • +
      • Concept introduced, classifying a provider error without referencing the provider: the Application layer references the domain and no data provider at all, so it cannot see SqlException.Number 2601 or 2627, nor the EF Core DbUpdateException that wraps them. Rather than match on the exception message, which is a provider and locale detail, the question is declared as IUniqueConstraintViolationDetector and answered in Infrastructure where the provider types already live (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IUniqueConstraintViolationDetector.cs:9-29). A miss is safe rather than silent: an unclassified exception simply propagates (IUniqueConstraintViolationDetector.cs:26-29). [Rubric §3, Clean Architecture] assesses whether layer boundaries hold under pressure; they do here, and swapping engines swaps the implementation with every recovering handler unchanged.
      • +
      • Walkthrough: two overrides and two log methods; the create workflow itself is inherited.
          +
        • HandleAsync (:36-66) first short-circuits: a caller-supplied id (a Sessionize import, for example) is respected as-is and gets a single attempt with no recomputation, because a collision there is a genuine caller error (:40-43). Otherwise the retry loop runs, calling the inherited CreateCoreAsync on the ambient unit of work for attempt one (:51-52) and on a scoped one thereafter (:56-58), logging a warning on each collision (:63).
        • +
        • PrepareAsync (:69-126) is the base's optional pre-map hook (CreateEntityHandlerBase.cs:114-118), and it runs once per attempt. It resolves the repository (:74) and allocates the manual id when needed (:79-95, ending in command = command with { Id = nextId }, a copy rather than a mutation). It then loads the parent Event untracked on every create, with or without a room (:100-105), including Rooms only when a room was requested (:103), and returns Error.NotFound targeting Event when it is missing (:106-107). The comment at :97-99 gives the reason: the parent event must exist, as update already requires, and the soft-delete query filter turns a deleted event into null here, so a room-less create can no longer attach a session to a deleted event. Only then, and only when a room was requested (:110-123), does it delegate to SessionRoomScheduling.ValidateRoomAssignmentAsync for BR-130 cross-event validation plus the double-booking guard (:112-120, defined at MMCA.ADC.Conference.Application/Sessions/Validation/SessionRoomScheduling.cs:44, with excludeSessionId: null because nothing exists yet to exclude). [Rubric §4, DDD]: the parent-existence check is the same on both write paths, so the aggregate reference cannot point at a soft-deleted event through either.
        • +
        • Everything after PrepareAsync is the base's CreateCoreAsync (CreateEntityHandlerBase.cs:77-103): map through the request mapper, AddAsync then SaveChangesAsync via PersistAsync, call LogCreated, then map the SessionDTO.
        • +
        • LogCreated (:129) is the base's post-save hook, forwarding to the module's template. Two [LoggerMessage] partials close the file: LogSessionCreated at information level (:131-132) and LogManualIdCollision at warning level with the attempt counters (:134-135). The warning is the operational signal that the id race is happening more often than expected. [Rubric §13, Observability and Operability].
        • +
        +
      • +
      • Why it's built this way: every complication in this file traces to one fact, that the session key space is shared with an external system. ADR-006 gives the module its own database, but not its own id authority for sessions. Given that, the reserved range prevents collision by construction, and the retry handles the only race the range cannot prevent. Expressing both as overrides on the shared base rather than as a hand-written handler is the point of ADR-099: the unusual parts stay visible and the ordinary parts stay shared.
      • +
      • Where it's used: registered by the convention scan (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:139); injected into SessionsController as createHandler (SessionsController.cs:46), passed into AggregateRootEntityControllerBase (SessionsController.cs:55-56), and called from the overridden POST /Sessions action (SessionsController.cs:275), which is marked [Idempotent] so a retried request replays rather than creating twice (SessionsController.cs:270, ADR-017). Covered by CreateSessionHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/Create/CreateSessionHandlerTests.cs:16).
      • +
      • Caveats / not-in-source: the manual-id query loads every session row in the reserved range as entities and computes Max in memory (:81-89) rather than asking the database for the maximum. The range caps at a thousand rows, so the cost is bounded, but it is not a scalar query. The retry loop is also while (true) with its bound expressed only in the catch filter (:46, :60): correct as written, since a non-matching exception or an exhausted budget propagates, but the termination condition is not local to the loop header.
      • +
      +

      UpdateSessionQuestionAnswerHandler

      +
      +

      MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.UpdateSessionQuestionAnswer · MMCA.ADC.Conference.Application/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerHandler.cs:23 · Level 14 · class (sealed, partial)

      +
      +
        +
      • What it is: the handler for UpdateSessionQuestionAnswerCommand. It supplies three overrides to the shared workflow: which navigation to load, which key to load by, and the mutation itself, which enforces the BR-52/BR-53 ownership rule, then re-applies the feedback eligibility and answer-shape rules the create path applies, before delegating to the aggregate (MMCA.ADC.Conference.Application/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerHandler.cs:23-92).
      • +
      • Depends on: MutateEntityHandlerBase<TCommand, TEntity, TIdentifierType> closed over the command, Session, and SessionIdentifierType (:27); IUnitOfWork (:24); ICurrentUserService (:25); ILogger<UpdateSessionQuestionAnswerHandler> (:26); RoleNames (:46); SessionQuestionAnswerRules (:75, :82); the Event and Question aggregates, read by id (:73-74, :80-81); and Result / Error.
      • +
      • Concept reinforced, ownership enforced in the mutation step: the mechanism is taught on RemoveSessionQuestionAnswerHandler, and this class follows the same shape. The controller has already established that the caller is authenticated (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionQuestionAnswersController.cs:77), but only the handler can establish whether this particular row is theirs, because that fact lives in the loaded entity's audit column. An Organizer may edit any answer, everyone else may edit only rows whose CreatedBy matches their user id (:45-53). [Rubric §11, Security] assesses whether authorization decisions are made where the necessary facts exist: role membership comes from the token, row ownership from the aggregate, and both are compared in one expression. [Rubric §4, DDD]: the check reads the child through the root's collection, never through a separate repository.
      • +
      • Concept introduced, one rule set for submitting and editing an answer: the comment at :59-60 states the contract: the update path applies the same rules the create path does, BR-91, BR-49, and BR-108 on the session and its event, BR-128 and BR-124 on the question and the value. Those rules live once in the internal static SessionQuestionAnswerRules (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/SessionQuestionAnswerRules.cs:14), whose two members take the event and the question as parameters rather than loading them, so a batch caller can read each once (SessionQuestionAnswerRules.cs:17-19, :50-51). EnsureSessionAcceptsFeedback (SessionQuestionAnswerRules.cs:24) refuses a service session, an ineligible status, and a missing or unpublished event, a null event becoming Error.NotFound targeting Event (SessionQuestionAnswerRules.cs:41-46). EnsureAnswerIsValid (SessionQuestionAnswerRules.cs:56) fails BR-128 when the question is missing or does not target sessions (SessionQuestionAnswerRules.cs:58-59), then checks the value against the question type. [Rubric §15, Best Practices and Code Quality]: one rule set called from both paths means submitting and editing cannot enforce different contracts.
      • +
      • Walkthrough: three overrides, one private helper, and the log method.
          +
        • Includes => [nameof(Session.SessionQuestionAnswers)] (:30) and the inherited AsTracking default of true (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/MutateEntityHandlerBase.cs:76). Tracking is load-bearing: the mutation happens on this graph and SaveChangesAsync persists it only because the change tracker is watching.
        • +
        • EntityId(command) => command.SessionId (:33). Unlike its remove siblings this handler does not override LoadAsync, so it uses the base's plain by-id load (MutateEntityHandlerBase.cs:152-160). It can, because the PUT body always carries the session id (SessionQuestionAnswersController.cs:285), so there is no "id omitted" case to recover from. A missing session becomes Error.NotFound in the base, stamped with the handler name and the entity name (MutateEntityHandlerBase.cs:282-283).
        • +
        • MutateAsync (:36-70) is async because the rule check reads two aggregates. It null-guards both arguments (:41-42), finds the active answer with a.Id == command.SessionQuestionAnswerId && !a.IsDeleted (:45), and fails with Error.Forbidden(code: "SessionQuestionAnswer.NotOwner", ...) when the answer exists, the caller is not in RoleNames.Organizer, and answer.CreatedBy differs from the current user id (:46-53). Ownership is checked first, so a non-owner is refused before any rule read runs.
        • +
        • The rule gate (:55-62) runs only when the answer exists, and calls EnsureUpdateIsAcceptedAsync (:67-84). That helper reads the parent event by entity.EventId through UnitOfWork.GetReadRepository<Event, EventIdentifierType>() (:73-74), runs the eligibility check (:75-78), then reads the question by the stored answer.QuestionId, not by anything in the command (:80-81), and validates the new value against it (:82-83).
        • +
        • The answer is not null guard on both checks is the interesting part: when the id names nothing, or names a soft-deleted row, both are skipped and the call falls through to the domain, which resolves the child with the same active-only predicate and returns NotFound (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:531-534, into MMCA.Common/Source/Core/MMCA.Common.Domain/Entities/AuditableAggregateRootEntity.cs:105). The practical effect is that a non-owner probing for an id they cannot see receives NotFound rather than Forbidden, so the error does not confirm the row exists.
        • +
        • entity.UpdateSessionQuestionAnswer(command.SessionQuestionAnswerId, command.AnswerValue) (:64) does the real work: resolve the child, call answer.UpdateAnswer (which validates the text before assigning it, Session.cs:540-542), and raise SessionQuestionAnswerChanged with DomainEntityState.Updated (Session.cs:544). A failure returns before the save, which the base guarantees (MutateEntityHandlerBase.cs:299-300); that is safe precisely because the rules and the domain both validate before anything mutates, so a refused update leaves the tracked graph unchanged.
        • +
        • LogMutated (:87-88) forwards to the [LoggerMessage] partial at :90-91, which is why the class is partial.
        • +
        +
      • +
      • Why it's built this way: the handler mirrors its sibling UpdateEventQuestionAnswerHandler, which is deliberate: session-level and event-level questionnaires answer to the same two ownership rules, and reading either one teaches both. The read side enforces the complementary rule with a specification instead of a branch: the controller scopes non-Organizer list reads with OwnedByUserSpecification<TEntity, TIdentifierType> (SessionQuestionAnswersController.cs:97), so ownership shows up as a filter on reads and as a guard on writes. The feedback rules are shared with the submit use cases through SessionQuestionAnswerRules (SessionQuestionAnswerRules.cs:10-12) rather than restated here.
      • +
      • Where it's used: registered by the convention scan (MMCA.ADC.Conference.Application/DependencyInjection.cs:139); injected into SessionQuestionAnswersController as updateHandler (SessionQuestionAnswersController.cs:82) and called at :218-220. Covered by UpdateSessionQuestionAnswerHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sessions/UseCases/UpdateSessionQuestionAnswer/UpdateSessionQuestionAnswerHandlerTests.cs:15).
      • +
      • Caveats / not-in-source: the handler declares no RowVersion override, so the base's concurrency stamp is skipped (MutateEntityHandlerBase.cs:91, :290-291) and this update is unconditional: two attendees editing the same answer are last-write-wins. That is consistent with the endpoint, which states no If-Match precondition, but nothing in this file records the decision. The rule gate also costs two extra by-id reads per update (:73-74, :80-81), made through read repositories outside the tracked Session graph.
      • +
      +

      SponsorCreateRequest

      +
      +

      MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sponsors.UseCases.Create · MMCA.ADC.Conference.Application/Sponsors/UseCases/Create/SponsorCreateRequest.cs:12 · Level 9 · record class

      +
      +
        +
      • What it is: the body a POST /Sponsors binds to, and, without any translation step, the command the CQRS pipeline dispatches. Twelve init-only members describe a sponsor or exhibitor; one extra member, CachePrefix, tells the pipeline what to evict when the write succeeds (MMCA.ADC.Conference.Application/Sponsors/UseCases/Create/SponsorCreateRequest.cs:15).
      • +
      • Depends on: three interfaces, all declared on one line (SponsorCreateRequest.cs:12). Two are framework markers: ICreateRequest and ICacheInvalidating. The third, ISponsorFieldsRequest, is the module's own (MMCA.ADC.Conference.Application/Sponsors/Validation/ISponsorFieldsRequest.cs:12). It also depends on the Sponsor entity type (referenced only through typeof for the cache prefix) and the SponsorTier enum. SponsorIdentifierType and EventIdentifierType are both module aliases for int.
      • +
      • Concept introduced, the request that is also the command: most codebases carry a request DTO at the edge and translate it into an internal command. Here the two collapse. The controller declares ICommandHandler<SponsorCreateRequest, Result<SponsorDTO>> directly (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sponsors/SponsorsController.cs:42) and passes the same type as the TCreateRequest argument of its generic base (SponsorsController.cs:49), so a single declaration is the OpenAPI schema, the validation target, the mapper input, and the cache-invalidation carrier. The cost is that a wire concern and a use-case concern share one type; the benefit is that there is exactly one place to add a field. [Rubric §5, Vertical Slice] assesses whether a feature is expressible as one thin, self-contained slice: the sponsor create slice is this file plus a validator, a mapper, and a handler, all in the same folder. [Rubric §9, API and Contract Design] assesses whether the published contract is explicit: required string Name (:21) is the only member the binder will not default, and every other member is optional by construction.
      • +
      • Concept introduced, the shared-fields interface: ISponsorFieldsRequest declares the eight members the create and the update request validate identically (ISponsorFieldsRequest.cs:15-36). It exists purely so SponsorFieldRules<T> can be written once against a constraint rather than once per request type (MMCA.ADC.Conference.Application/Sponsors/Validation/SponsorValidationRules.cs:139-141). What is off the interface matters as much as what is on it: EventId, Id, Tier, and IsExhibitor are absent. The file states the reason for EventId (ISponsorFieldsRequest.cs:9-10): only the create request carries it, because moving a sponsor between events is a create plus a delete. [Rubric §15, Best Practices & Code Quality] assesses whether shared shape is expressed structurally rather than duplicated: two request types, one rule list, and the compiler enforces the overlap.
      • +
      • Walkthrough: the members in the order they matter.
          +
        • CachePrefix => $"{typeof(Sponsor).FullName}:" (:15) is the entity's fully-qualified name plus a colon. All three sponsor mutations declare that same prefix, so they evict one shared namespace of keys. The eviction is performed by CachingCommandDecorator<TCommand, TResult> on success only.
        • +
        • Id (:18) is documented as database-generated with caller-supplied values ignored, and the factory is what makes that true: it consults typeof(Sponsor).IsIdValueGenerated and substitutes default whenever the store owns the key (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sponsors/Sponsor.cs:126-131). Nothing rejects a supplied id; it is simply discarded.
        • +
        • Name (:21) is required, so an omitted name fails model binding before any validator runs. Tier (:24) is the enum, Sort (:42) the within-tier display order, EventId (:45) the owning event, IsExhibitor (:48) and BoothNumber (:51) the expo-floor pair, and LogoUrl, Description, WebsiteUrl, LinkedInUrl, TwitterHandle (:27-39) the optional branding strings.
        • +
        • Every member is init, so once the binder has filled the instance the validator, the mapper, and the handler all see the same frozen values.
        • +
        +
      • +
      • Why it's built this way: the shape mirrors SponsorDTO member for member minus the concurrency token, which keeps the round trip (POST a request, receive a DTO) readable without a mapping table (ADR-001). Declaring cache invalidation as a property rather than calling a cache API keeps the Application layer free of cache infrastructure, which is what [Rubric §12, Performance & Scalability] looks for: the concern is declared once, and one decorator implements it for every command that declares it.
      • +
      • Where it's used: SponsorsController takes the handler for it in its constructor (SponsorsController.cs:42) and overrides CreateAsync to add the SponsorsManage permission attribute and the output-cache eviction (SponsorsController.cs:170-179); SponsorCreateRequestValidator validates it, SponsorCreateRequestMapper turns it into an entity, and CreateSponsorHandler persists it. The framework's generic CRUD registration also closes over it (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:151).
      • +
      • Caveats / not-in-source: sponsors sit behind two independent caches, and this property addresses only one. CachePrefix drives the framework's prefix eviction; the ASP.NET output cache in front of the public sponsor reads is a separate store the controller evicts by tag in the same action (SponsorsController.cs:177). Removing either half leaves stale sponsor data visible somewhere.
      • +

      SpeakerCategoryItemNavigationPopulator

      MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Speakers · MMCA.ADC.Conference.Application/Speakers/SpeakerCategoryItemNavigationPopulator.cs:11 · Level 10 · class (sealed)

      @@ -5919,20 +5884,20 @@

      SpeakerEntityQueryService

      • What it is: the only subclass of the framework's generic query service in the whole Conference module. It adds exactly one thing to EntityQueryService<TEntity, TEntityDTO, TIdentifierType>: a one-entry map that teaches the read pipeline how to filter and sort by FullName, a name that exists on the DTO and on the entity but in no database column (MMCA.ADC.Conference.Application/Speakers/SpeakerEntityQueryService.cs:11-14).
      • Depends on: EntityQueryService<TEntity, TEntityDTO, TIdentifierType> closed over Speaker / SpeakerDTO / SpeakerIdentifierType (:21-22), and the five collaborators it forwards to the base unchanged (:15-20): IUnitOfWork, INavigationMetadataProvider, IEntityQueryPipeline, SpeakerDTOMapper, and INavigationPopulator<in TEntity> closed over Speaker (satisfied at runtime by SpeakerNavigationPopulator).
      • -
      • Concept introduced, the DTO-to-entity property map: a REST client filters and sorts using the vocabulary of the DTO it received, but the query runs against the entity. Most names line up; FullName does not. On the entity it is a computed, get-only property, public string FullName => $"{FirstName} {LastName}" (MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:61), and the EF configuration explicitly removes it from the model with builder.Ignore(p => p.FullName) (MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Speakers/SpeakerConfiguration.cs:68), so there is nothing named FullName for SQL to order by or match against. The map closes that gap by pairing the DTO name with a Dynamic LINQ expression over the two real columns rather than with a property path. [Rubric §12, Performance and Scalability] assesses whether reads stay translatable and server-side: the alternative to this one dictionary entry is fetching every speaker and matching the search box in memory, which a paged endpoint cannot do correctly. [Rubric §9, API and Contract Design] assesses whether the contract a caller sees is coherent: callers filter by the field they were served, and the translation stays a server concern. [Rubric §11, Security] is relevant too, and the framework is explicit about why: map entries are accepted unconditionally during field validation precisely because they are server-authored, never client-supplied (MMCA.Common/Source/Core/MMCA.Common.Application/Services/QueryFieldService.cs:377-379), while any name the server never mapped still has to survive reflection against the entity (:381-393), so a client cannot inject an expression of its own.
      • +
      • Concept introduced, the DTO-to-entity property map: a REST client filters and sorts using the vocabulary of the DTO it received, but the query runs against the entity. Most names line up; FullName does not. On the entity it is a computed, get-only property, public string FullName => $"{FirstName} {LastName}" (MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:61), and the EF configuration explicitly removes it from the model with builder.Ignore(p => p.FullName) (MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Speakers/SpeakerConfiguration.cs:68), so there is nothing named FullName for SQL to order by or match against. The map closes that gap by pairing the DTO name with a Dynamic LINQ expression over the two real columns rather than with a property path. [Rubric §12, Performance and Scalability] assesses whether reads stay translatable and server-side: the alternative to this one dictionary entry is fetching every speaker and matching the search box in memory, which a paged endpoint cannot do correctly. [Rubric §9, API and Contract Design] assesses whether the contract a caller sees is coherent: callers filter by the field they were served, and the translation stays a server concern. [Rubric §11, Security] is relevant too, and the framework is explicit about why: map entries are accepted unconditionally during field validation precisely because they are server-authored, never client-supplied (MMCA.Common/Source/Core/MMCA.Common.Application/Services/QueryFieldService.cs:379-381), while any name the server never mapped still has to survive reflection against the entity (:381-393), so a client cannot inject an expression of its own.
      • Walkthrough: the whole class is thirty-five lines, and all of it is configuration.
        • The primary constructor takes the five services and forwards them positionally to the base (:15-22). Note that the mapper parameter is the concrete SpeakerDTOMapper, not the IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType> the base declares (MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:32-37): the subclass names the implementation and lets the compiler widen it, which is how DI resolves the Mapperly-generated mapper by its own type.
        • PropertyMap is a private static readonly IReadOnlyDictionary<string, string> with one entry: [nameof(SpeakerDTO.FullName)] = "(FirstName + \" \" + LastName)" (:28-31). Using nameof keys the map to the DTO property (MMCA.ADC.Conference.Shared/Speakers/SpeakerDTO.cs:67), so renaming it breaks the build instead of silently breaking a sort. The outer parentheses in the value are load-bearing, and the next bullet shows why.
        • DTOToEntityPropertyMap overrides the base's virtual, empty default and returns that static instance (:34). One allocation for the process, not one per request.
        • -
        • What the base does with it is the interesting half. The value flows into three places: validation of the sort column and of every filter (MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:265, :266), and the EntityQueryParameters<TEntity> handed to the pipeline (:295, and :527 on the by-id path). On the filter path, QueryFilterService resolves the incoming key FullName through the map to the expression, resolves a PropertyInfo for the DTO-facing name so type resolution still works, and hands the expression to the string strategy (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Filtering/QueryFilterService.cs:85-97, with the two-name lookup at :245-252). A CONTAINS there becomes query.Where("(FirstName + \" \" + LastName).Contains(@0)", value) (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Filtering/StringFilterStrategy.cs:23), a plain concatenation predicate EF Core translates to SQL over the two real columns. Drop the parentheses from the map value and the same template would read FirstName + " " + LastName.Contains(@0), a different expression entirely. On the sort path, QueryFieldService.ApplySorting resolves the column through the map before appending the direction and the server-supplied tie-break key, then calls Dynamic LINQ OrderBy (MMCA.Common/Source/Core/MMCA.Common.Application/Services/QueryFieldService.cs:155-198, :190-202).
        • +
        • What the base does with it is the interesting half. The value flows into three places: validation of the sort column and of every filter (MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:265, :266), and the EntityQueryParameters<TEntity> handed to the pipeline (:295, and :527 on the by-id path). On the filter path, QueryFilterService resolves the incoming key FullName through the map to the expression, resolves a PropertyInfo for the DTO-facing name so type resolution still works, and hands the expression to the string strategy (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Filtering/QueryFilterService.cs:85-97, with the two-name lookup at :245-252). A CONTAINS there becomes query.Where("(FirstName + \" \" + LastName).Contains(@0)", value) (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Filtering/StringFilterStrategy.cs:32), a plain concatenation predicate EF Core translates to SQL over the two real columns. Drop the parentheses from the map value and the same template would read FirstName + " " + LastName.Contains(@0), a different expression entirely. On the sort path, QueryFieldService.ApplySorting resolves the column through the map before appending the direction and the server-supplied tie-break key, then calls Dynamic LINQ OrderBy (MMCA.Common/Source/Core/MMCA.Common.Application/Services/QueryFieldService.cs:155-199, :190-202).
        • Everything else this service can do is inherited untouched: parameter validation, the by-id fast path, pagination metadata, field shaping, and the navigation-population step that invokes NavigationPopulator.PopulateAsync as a delegate handed down to the pipeline (EntityQueryService.cs:356, :534).
      • Why it's built this way: the framework offers an override hook rather than a configuration file or an attribute, so the mapping lives in the module that owns the vocabulary and costs nothing for entities that need none. That is visible in the registration block: Speaker gets this subclass (MMCA.ADC.Conference.Application/DependencyInjection.cs:84) while Sponsor and SpeakerCategoryItem register the closed generic base directly (:91, :116). [Rubric §15, Best Practices & Code Quality]: the delta between "standard entity" and "entity with a computed sort field" is one dictionary entry.
      • -
      • Where it's used: registered as services.TryAddScoped<IEntityQueryService<Speaker, SpeakerDTO, SpeakerIdentifierType>, SpeakerEntityQueryService>() (MMCA.ADC.Conference.Application/DependencyInjection.cs:84) and injected into SpeakersController as the interface (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:43). Both speaker grids drive it with exactly this vocabulary: the organizer list sends filters["FullName"] = ("contains", _searchString) and sorts by "FullName" (MMCA.ADC.Conference.UI/Pages/Speakers/SpeakerList.razor.cs:69, :79), and the public list does the same (MMCA.ADC.Conference.UI/Pages/Public/Speakers/PublicSpeakerList.razor.cs:227, :130). SpeakerEntityQueryServiceTests pins the contract, asserting that the captured pipeline parameters carry the FullName key mapped to the exact entity expression (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/SpeakerEntityQueryServiceTests.cs:19, :89-104) and that an unmapped, unknown filter property fails validation before the pipeline is touched (:109).
      • +
      • Where it's used: registered as services.TryAddScoped<IEntityQueryService<Speaker, SpeakerDTO, SpeakerIdentifierType>, SpeakerEntityQueryService>() (MMCA.ADC.Conference.Application/DependencyInjection.cs:84) and injected into SpeakersController as the interface (MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:43). Both speaker grids drive it with exactly this vocabulary: the organizer list sends filters["FullName"] = ("contains", _searchString) and sorts by "FullName" (MMCA.ADC.Conference.UI/Pages/Speakers/SpeakerList.razor.cs:69, :79), and the public list does the same (MMCA.ADC.Conference.UI/Pages/Public/Speakers/PublicSpeakerList.razor.cs:218, :130). SpeakerEntityQueryServiceTests pins the contract, asserting that the captured pipeline parameters carry the FullName key mapped to the exact entity expression (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/SpeakerEntityQueryServiceTests.cs:19, :89-104) and that an unmapped, unknown filter property fails validation before the pipeline is touched (:109).
      • Caveats / not-in-source: two edges are worth knowing.
          -
        • FullName is filterable and sortable but not requestable as a shaped field. The fields parameter is validated through the overload that takes no map (MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:298, resolving to QueryFieldService.cs:366-367), so ?fields=FullName never reaches the map and is rejected as a read-only property, which is consistent with server-side projection being restricted to writable properties (QueryFieldService.cs:395-400).
        • -
        • PropertyMap is built with the default (ordinal, case-sensitive) comparer (:28), while the base's empty default uses StringComparer.OrdinalIgnoreCase (MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:101). Only the exact key FullName hits the map. A lowercase filter key misses it and is then rejected cleanly, because filter property lookup reflects case-sensitively (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Filtering/QueryFilterService.cs:349). A lowercase sort column behaves differently: sort validation matches property names case-insensitively (QueryFieldService.cs:500-501) and the unmapped fallback in ResolveSortExpression resolves with BindingFlags.IgnoreCase (:197-201), so the request passes validation and Dynamic LINQ receives the bare, EF-ignored FullName instead of the mapped expression. What the database layer does with that is not exercised anywhere in this repository: not determinable from source. Every caller in the codebase sends the exact-case key.
        • +
        • FullName is filterable and sortable but not requestable as a shaped field. The fields parameter is validated through the overload that takes no map (MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:298, resolving to QueryFieldService.cs:368-369), so ?fields=FullName never reaches the map and is rejected as a read-only property, which is consistent with server-side projection being restricted to writable properties (QueryFieldService.cs:397-402).
        • +
        • PropertyMap is built with the default (ordinal, case-sensitive) comparer (:28), while the base's empty default uses StringComparer.OrdinalIgnoreCase (MMCA.Common/Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:101). Only the exact key FullName hits the map. A lowercase filter key misses it and is then rejected cleanly, because filter property lookup reflects case-sensitively (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Filtering/QueryFilterService.cs:349). A lowercase sort column behaves differently: sort validation matches property names case-insensitively (QueryFieldService.cs:503-504) and the unmapped fallback in ResolveSortExpression resolves with BindingFlags.IgnoreCase (:197-201), so the request passes validation and Dynamic LINQ receives the bare, EF-ignored FullName instead of the mapped expression. What the database layer does with that is not exercised anywhere in this repository: not determinable from source. Every caller in the codebase sends the exact-case key.
      @@ -5969,7 +5934,7 @@

      SpeakerNavigationPopulator

      • The generic quartet is worth reading closely, because the parent key and the child keys are different types here: TParentId is SpeakerIdentifierType (System.Guid) while both TChildId values are int (MMCA.ADC.Conference.Shared/MMCA.ADC.Conference.GlobalUsings.IdentifierType.cs:22-24). The descriptor keeps the two apart as separate type parameters (ChildNavigationDescriptor.cs:15-19), so the batch load compares GUID to GUID (child => child.SpeakerId is typed SpeakerIdentifierType on both children: MMCA.ADC.Conference.Domain/Speakers/SpeakerCategoryItem.cs:24, MMCA.ADC.Conference.Domain/Speakers/SpeakerQuestionAnswer.cs:26) while each child's own read repository is still resolved by its own int key (ChildNavigationDescriptor.cs:45).
      • -
      • Both AssignAction targets go through the aggregate's own mutators, SetSpeakerCategoryItems (MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:389-390) and SetSpeakerQuestionAnswers (:463-464), each a thin delegation to the framework's SetItems over the private backing list. Both are internal, reachable from here only because the Domain project grants <InternalsVisibleTo Include="MMCA.ADC.Conference.Application" /> (MMCA.ADC.Conference.Domain/MMCA.ADC.Conference.Domain.csproj:3). The collections themselves are exposed as IReadOnlyCollection<> over private lists (Speaker.cs:66-73), so no other assembly can replace them. [Rubric §4, Domain-Driven Design]: hydration passes through the same door a business operation would, not a back-door property write.
      • +
      • Both AssignAction targets go through the aggregate's own mutators, SetSpeakerCategoryItems (MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:391-392) and SetSpeakerQuestionAnswers (:463-464), each a thin delegation to the framework's SetItems over the private backing list. Both are internal, reachable from here only because the Domain project grants <InternalsVisibleTo Include="MMCA.ADC.Conference.Application" /> (MMCA.ADC.Conference.Domain/MMCA.ADC.Conference.Domain.csproj:3). The collections themselves are exposed as IReadOnlyCollection<> over private lists (Speaker.cs:66-73), so no other assembly can replace them. [Rubric §4, Domain-Driven Design]: hydration passes through the same door a business operation would, not a back-door property write.
      • The base owns the algorithm and its guards decide when any of this runs. PopulateAsync returns immediately when there are no entities or no unsupported includes, then loads only descriptors whose PropertyName appears in UnsupportedIncludes (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Navigation/DeclarativeNavigationPopulator.cs:27-41). Both names match [Navigation(IsCollection = true)] attributes on the aggregate (Speaker.cs:66, :72), which is what puts them in the child-collection bucket during metadata discovery (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/NavigationMetadataProvider.cs:78-80). The load itself is one batched WHERE childFK IN (...parentIds) query per descriptor via NavigationLoader (MMCA.Common/Source/Core/MMCA.Common.Application/Services/NavigationLoader.cs:118), not one query per speaker.
    • @@ -5980,16 +5945,101 @@

      SpeakerNavigationPopulator

    • Caveats / not-in-source: the descriptors cover child collections only. Speaker.LinkedUserId is a scalar cross-module reference into the Identity module (MMCA.ADC.Conference.Domain/Speakers/Speaker.cs:58) and is deliberately not a navigation here, so nothing in this file hydrates a linked user. Nothing in the descriptors filters soft-deleted children either: that exclusion comes from the EF global query filter applied by the read repository the loader resolves (ADR-005). The loader's queries are untracked (MMCA.Common/Source/Core/MMCA.Common.Application/Services/NavigationLoader.cs:80-84), which is why write handlers that need a tracked graph pass an explicit includes array to the repository instead of relying on this populator.

    -

    SpeakerQuestionAnswerNavigationPopulator

    +

    ActivityNavigationPopulator

    +
    +

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Activities · MMCA.ADC.Conference.Application/Activities/ActivityNavigationPopulator.cs:12 · Level 10 · class (sealed)

    +
    +
      +
    • What it is: the navigation populator for Activity. It declares one thing: how to hydrate an activity's parent Event reference when EF Core cannot reach it with .Include(). The class body is empty (MMCA.ADC.Conference.Application/Activities/ActivityNavigationPopulator.cs:24-25); the entire implementation is the descriptor list handed to the base constructor (:14-23).
    • +
    • Depends on: DeclarativeNavigationPopulator<TEntity> closed over Activity (:14), FKNavigationDescriptor<TEntity, TChild, TChildId> closed over Activity / Event / EventIdentifierType (:16), IUnitOfWork forwarded untouched to the base (:12-14), and the Activity and Event entities.
    • +
    • Concept introduced in this group, FK back-reference hydration: Group 11 teaches the populator machinery; what this class introduces here is the other direction of it. A child-collection descriptor answers "give me the rows that point at me"; an FK descriptor answers "give me the one row I point at". The framework separates the two with a single boolean: FKNavigationDescriptor.RequiresChildren is hard-coded false (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Navigation/FKNavigationDescriptor.cs:23), and the base uses it to pick which caller flag gates the load, includeFKs rather than includeChildren (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Navigation/DeclarativeNavigationPopulator.cs:36). [Rubric §2, Design Patterns] assesses whether behavior is factored into reusable shapes: this is Template Method configured by data, so a new navigation is a descriptor rather than a new query method. [Rubric §7, Microservices Readiness] assesses whether code survives a physical split: the whole reason this file exists is that a join is unavailable when parent and child live in different data sources (ADR-006, ADR-018).
    • +
    • Walkthrough: one descriptor, four settings, and a base algorithm worth following once.
        +
      • PropertyName = nameof(Activity.Event) (:18) is the match key. The base builds an ordinal HashSet of the property names the metadata provider flagged as unsupported and loads only descriptors whose name is in it (DeclarativeNavigationPopulator.cs:30-37). The name comes from the navigation property itself, public Event? Event { get; private set; } (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Activities/Activity.cs:58), which carries a bare [Navigation] attribute; with IsCollection left at its default, metadata discovery files it in the foreign-key bucket (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/NavigationMetadataProvider.cs:78), which is exactly what makes RequiresChildren => false the right gate.
      • +
      • ParentKeySelector = e => e.EventId (:19). The descriptor types this as Func<TEntity, TChildId?> (FKNavigationDescriptor.cs:26), and Activity.EventId is a non-nullable int (Activity.cs:54), so the compiler widens it to int?. The nullable signature exists for entities whose FK is genuinely optional; here it can never be null.
      • +
      • ChildForeignKeySelector = child => child.Id (:20). Read that carefully: for an FK reference the "child foreign key" is the target's own primary key, because the predicate being built matches Event.Id against the set of Activity.EventId values.
      • +
      • AssignAction = (e, events) => e.SetEvent(events.FirstOrDefault()) (:21) calls a public aggregate mutator (Activity.cs:192) rather than assigning the property directly. The navigation itself has a private set (Activity.cs:58), so hydration goes through a named method the entity owns, which is the same discipline the child-collection populators follow. [Rubric §4, DDD]: even an infrastructure-driven write enters the entity through its own API.
      • +
      • The load is NavigationLoader.LoadFKPropertyAsync (FKNavigationDescriptor.cs:39-45), and it is deliberately batched: collect the distinct non-null keys (MMCA.Common/Source/Core/MMCA.Common.Application/Services/NavigationLoader.cs:58), return early assigning empty lists when there are none (:63), build child => parentIds.Contains(child.Id) as an expression tree (:71), run one GetAllAsync with that predicate against the read repository (:80), group the results into a dictionary (:89), and assign per parent (:92). One query for a page of activities, not one per activity.
      • +
      • Two guards mean this usually costs nothing: PopulateAsync returns immediately when the entity list is empty or when the metadata reported no unsupported includes at all (DeclarativeNavigationPopulator.cs:27-28). On a topology where activities and events share a source, that second guard is always true and the populator never touches the database.
      • +
      +
    • +
    • Why it's built this way: ADR-002 makes hydration a declaration in the Application layer rather than an EF concern, and NavigationMetadataProvider decides per navigation whether .Include() is available. Because that decision is configuration, this file is inert in the monolith and becomes the hydration path after a split, with no change to the controller, the query service, or the DTO. [Rubric §3, Clean Architecture]: there is no EF Core namespace anywhere in the file. [Rubric §12, Performance and Scalability]: the batched IN shape is what keeps a paged list from degrading into N+1.
    • +
    • Where it's used: registered as services.TryAddScoped<INavigationPopulator<Activity>, ActivityNavigationPopulator>() (MMCA.ADC.Conference.Application/DependencyInjection.cs:97) and consumed by the closed-generic EntityQueryService<TEntity, TEntityDTO, TIdentifierType> registered on the next line (:88), which invokes it as part of the read pipeline. Its tests assert the DI shape and both empty-input guards without touching the unit of work (ActivityNavigationPopulatorTests, MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Activities/ActivityNavigationPopulatorTests.cs:15-43).
    • +
    • Caveats / not-in-source: AssignAction runs for every parent, including those whose lookup found nothing, in which case FirstOrDefault() assigns null (NavigationLoader.cs:92-99). Combined with the soft-delete query filter the read repository applies (ADR-005), an activity whose owning event has been soft-deleted comes back with Event == null rather than as an error, and a caller that renders the event name has to handle that null.
    • +
    +

    CreateSponsorHandler

    +
    +

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sponsors.UseCases.Create · MMCA.ADC.Conference.Application/Sponsors/UseCases/Create/CreateSponsorHandler.cs:15 · Level 10 · class (sealed, partial)

    +
    +
      +
    • What it is: the command handler for SponsorCreateRequest, and a good demonstration of how little a create handler has to contain. The whole class is a constructor, one two-line override, and a source-generated log method (MMCA.ADC.Conference.Application/Sponsors/UseCases/Create/CreateSponsorHandler.cs:15-28). Every step of the create itself lives in the framework base it derives from.
    • +
    • Depends on: CreateEntityHandlerBase<TCreateRequest, TEntity, TIdentifierType, TEntityDTO> closed over SponsorCreateRequest / Sponsor / SponsorIdentifierType / SponsorDTO (:20-21); IUnitOfWork (:16), IEntityRequestMapper<TEntity, TCreateRequest, TIdentifierType> (:17, satisfied at runtime by SponsorCreateRequestMapper), and the concrete SponsorDTOMapper (:18), all three forwarded straight into the base constructor; plus ILogger<CreateSponsorHandler> (:19), which is the one dependency the base does not take.
    • +
    • Concept introduced, the workflow base class: the base is an abstract class that itself implements ICommandHandler<in TCommand, TResult> (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:46). That detail is load-bearing and the file says why (CreateEntityHandlerBase.cs:17-23): because the concrete subclass is what carries the closed interface, the module's Scrutor scan keeps discovering it, the decorator pipeline keeps wrapping it, and Scrutor never registers the abstract base. Inheritance here buys code reuse without changing the registration story. [Rubric §1, SOLID] assesses whether shared behavior is factored out without leaking: the subclass overrides one hook and inherits eight steps. [Rubric §2, Design Patterns]: Template Method again, this time over a write workflow instead of over navigation loading.
    • +
    • Walkthrough: two members here, and the inherited pipeline behind them.
        +
      • LogCreated(Sponsor entity) (:24) is the single override, forwarding to the module's own [LoggerMessage] partial. The base declares LogCreated as a no-op virtual precisely so logging stays per-module vocabulary (CreateEntityHandlerBase.cs:148-151).
      • +
      • LogSponsorCreated (:26-27) is the source-generated partial with [LoggerMessage(Level = LogLevel.Information, Message = "Sponsor {SponsorId} created with name '{Name}'")]. That is why the class is partial: the generator supplies the body, and the template's {SponsorId} and {Name} become structured fields rather than a formatted string. [Rubric §13, Observability and Operability] assesses whether logs are queryable: the created id is a field, not text inside a message (ADR-041).
      • +
      • The inherited HandleAsync (CreateEntityHandlerBase.cs:56-63) null-guards the command and calls CreateCoreAsync (:76-102), which runs: PrepareAsync (:83, a pass-through by default, :113-117), requestMapper.CreateEntityAsync with an early exit on a domain failure (:89-91), attemptUnitOfWork.GetRepository<Sponsor, SponsorIdentifierType>() (:94), PersistAsync (:96, which is AddAsync then SaveChangesAsync, :137-138), then LogCreated (:98), OnCreatedAsync (:99, a no-op here), and finally Result.Success(dtoMapper.MapToDTO(entity)) (:101) so the caller receives the store-assigned identity in the response body.
      • +
      • Notice what is still absent from the whole path: no validation call, no transaction scope, no cache eviction, and no try/catch. Validation is applied by ValidatingCommandDecorator<TCommand, TResult> resolving SponsorCreateRequestValidator; invalidation is applied by CachingCommandDecorator<TCommand, TResult> reading CachePrefix off the request; the decorator ordering is registered at MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:134-140. [Rubric §12, Performance & Scalability]: every concern that would otherwise be copy-pasted into thirty handlers lives in a decorator.
      • +
      +
    • +
    • Why it's built this way: the repository is resolved from the unit of work inside the workflow rather than constructor-injected, and the base states the rule outright (CreateEntityHandlerBase.cs:25-28): only the unit of work knows which physical data source the entity resolves to. Note also the injection asymmetry, the request mapper as an interface and the DTO mapper as a concrete class (CreateSponsorHandler.cs:17-18). That is a DI fact rather than a style choice: the framework scan registers both mapper families AsSelfWithInterfaces (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:206-210, :213-217), so either shape resolves.
    • +
    • Where it's used: registered by the ICommandHandler<,> assembly scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:246-250, invoked from MMCA.ADC.Conference.Application/DependencyInjection.cs:139) and injected into SponsorsController (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sponsors/SponsorsController.cs:42), reached through the base CreateAsync that the controller's permission-gated override wraps (SponsorsController.cs:170-179). The generic CRUD registration for sponsors runs after the scan and uses TryAdd, so this class keeps the create verb while the framework supplies update and delete (MMCA.ADC.Conference.Application/DependencyInjection.cs:141-151). Tests: CreateSponsorHandlerTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sponsors/UseCases/CreateSponsorHandlerTests.cs:14).
    • +
    • Caveats / not-in-source: nothing in the Application layer proves that EventId names a real event. SponsorCreateRequestValidator only requires it to be non-default through SponsorEventIdRules<T> (MMCA.ADC.Conference.Application/Sponsors/Validation/SponsorValidationRules.cs:115-120), the mapper performs no lookup, and Sponsor.Create validates only name, logo URL, and booth number (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sponsors/Sponsor.cs:119-122). The guarantee comes one layer down: the EF configuration declares a required FK to Event (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Sponsors/SponsorConfiguration.cs:59-65), so a bogus id fails at SaveChangesAsync as a database error rather than as a validation Result.
    • +
    +

    PublicConferenceVisibility

    -

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Speakers · MMCA.ADC.Conference.Application/Speakers/SpeakerQuestionAnswerNavigationPopulator.cs:11 · Level 10 · class (sealed)

    +

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Common · MMCA.ADC.Conference.Application/Common/PublicConferenceVisibility.cs:28 · Level 10 · class (static)

      -
    • What it is: the navigation populator for SpeakerQuestionAnswer read as its own entity. One navigation: the parent Speaker back-reference (MMCA.ADC.Conference.Application/Speakers/SpeakerQuestionAnswerNavigationPopulator.cs:7-10).
    • -
    • Depends on: DeclarativeNavigationPopulator<TEntity> closed over SpeakerQuestionAnswer (:13); FKNavigationDescriptor<TEntity, TChild, TChildId> (:15); IUnitOfWork (:12); the Speaker aggregate as the reference target.
    • -
    • Concept reinforced: none new. Structurally identical to SpeakerCategoryItemNavigationPopulator, which teaches the FK direction: the same closed generic over Speaker and SpeakerIdentifierType (:15), PropertyName = nameof(SpeakerQuestionAnswer.Speaker) (:17), ParentKeySelector = e => e.SpeakerId (:18, the get-only FK at MMCA.ADC.Conference.Domain/Speakers/SpeakerQuestionAnswer.cs:26), ChildForeignKeySelector = child => child.Id (:19), AssignAction calling the public mutator with FirstOrDefault() (:20, SpeakerQuestionAnswer.cs:84), and an empty class body (:23-24). The target property is the same private-set, attributed navigation shape (SpeakerQuestionAnswer.cs:22-23).
    • -
    • Where it's used: registered as services.TryAddScoped<INavigationPopulator<SpeakerQuestionAnswer>, SpeakerQuestionAnswerNavigationPopulator>() (MMCA.ADC.Conference.Application/DependencyInjection.cs:133). This is the one populator in the module with no paired query service, and the registration says so in a comment: the entity has no query service today, and registering the populator future-proofs the one that would be added alongside it (:118-119). Covered by SpeakerQuestionAnswerNavigationPopulatorTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Speakers/SpeakerQuestionAnswerNavigationPopulatorTests.cs:16, :20, :24, :35).
    • -
    • Caveats / not-in-source: because nothing resolves INavigationPopulator<SpeakerQuestionAnswer> on a read path today, this class is registered but not exercised outside its unit tests. Answers still reach clients as part of a speaker read, through the child-collection descriptor on SpeakerNavigationPopulator, which is a different code path entirely.
    • +
    • What it is: the single definition of what an anonymous or non-privileged caller may see in the conference catalog, expressed as three id-list resolvers that the public read filters turn into IN (...) specifications (MMCA.ADC.Conference.Application/Common/PublicConferenceVisibility.cs:10-15). Three rules: an event is visible when published (BR-108), a session is visible when its event is visible and its status is on the BR-49 allow-list, and a speaker is visible when they have at least one eligible session inside the scoped published-event set (BR-239) (:18-21).
    • +
    • Depends on: IUnitOfWork and the read-side IEntityQuerier<TEntity, TIdentifierType> it hands out (:40, :75, :126-127, :151), CrossSourceSpecification (:63), InlineSpecification<TEntity, TIdentifierType> (:149), PublicSessionStatusSpecification (:68, :148), and the Event, Session, and SessionSpeaker entities.
    • +
    • Concept introduced, authorization as a translatable data filter: read authorization here is not a check that runs after the query, it is the query. Every rule is resolved into a materialized list of ids and embedded in a predicate, never expressed as a navigation join. The file states the three reasons (:22-26): the criteria stay translatable on any engine (ADR-018), each aggregate keeps its by-id boundary to the others, and the results pass the specification fitness test. [Rubric §11, Security] assesses whether authorization is enforced where the data is read rather than in a view: because one helper backs the session, speaker, sponsor, room, activity, and junction filters, closing a leak in one place closes it everywhere, which is the property that motivates the whole file. [Rubric §8, Data Architecture]: a cross-aggregate rule becomes a scalar projection plus an IN, the shape a split topology can still execute.
    • +
    • Concept reinforced, read repository versus write repository: every resolver here asks the unit of work for GetReadRepository<TEntity, TIdentifierType>() (:40, :75, :126-127, :151) and types the result as IEntityQuerier<TEntity, TIdentifierType>, the query-only face of the repository. That is the correct call for a projection nothing intends to mutate, and it is a different method from the GetRepository the write handlers in this group use. [Rubric §6, CQRS and Event-Driven] assesses whether the read and write paths are actually distinct at the abstraction level, not just by convention.
    • +
    • Walkthrough: three public resolvers and one private helper.
        +
      • GetPublishedEventIdsAsync (:36-48) resolves the read repository (:40) and projects ids with a predicate in one call, GetProjectedAsync(e => e.Id, e => e.IsPublished, asTracking: false, ...) (:42-44). The result is materialized once so callers embed a stable collection EF can translate (:46-47).
      • +
      • GetVisibleSessionIdsAsync (:57-82) delegates the two-source AND to CrossSourceSpecification.BuildAsync (:63-70), passing e => e.IsPublished as the principal predicate, s => s.EventId as the dependent FK, and PublicSessionStatusSpecification.StatusCriteria as the local predicate. That helper runs the principal projection first and returns a specification whose criteria is localPredicate AND principalKeys.Contains(fk), built as an expression tree with no Expression.Invoke so it stays translatable on every provider (MMCA.Common/Source/Core/MMCA.Common.Application/Specifications/CrossSourceSpecification.cs:66-90). The specification then reaches the repository as a specification: ListAsync(specification, s => s.Id, cancellationToken) (:77-79) is the untracked, soft-delete-filtered projection that an explicit argument list used to spell out (:72-74).
      • +
      • GetVisibleSpeakerIdsAsync (:104-134) takes an optional event scope. It resolves the published set first (:109), and when a scope is supplied it narrows to that single event only if the event is published, otherwise to the empty list (:113-117); an unpublished or unknown scoped event is not an error, it simply has no public speakers (:111-112). Empty scope and empty eligible-session set both short-circuit to [] (:119-124), then the join table is projected with eligibleSessionIds.Contains(ss.SessionId) and de-duplicated (:126-133).
      • +
      • GetEligibleSessionIdsAsync (:141-158) is the private narrowing variant: new PublicSessionStatusSpecification().And(new InlineSpecification<Session, SessionIdentifierType>(s => scopedEventIds.Contains(s.EventId))) (:148-149), which keeps the criteria a translatable IN filter with no navigation join (:146-147).
      • +
      +
    • +
    • Why it's built this way: the remark at :97-103 is the most load-bearing comment in the file, and it records a real rule, not a preference. The EventSpeaker junction is deliberately not treated as a visibility grant, because the Sessionize import writes a row there for every speaker in the response, so reading it as acceptance would publish the entire imported roster and make the filter vacuous. The session link is the only acceptance signal a speaker carries, so it is the only path consulted: a speaker whose sessions are all waitlisted or declined, and one linked to nothing, both stay hidden. [Rubric §4, DDD]: the rule is stated in the vocabulary of the business (published, accepted, assigned) and lives beside the aggregates it constrains.
    • +
    • Where it's used: by the eight public-filter query handlers, one per publicly readable entity or junction: GetPublicSponsorFilterHandler (MMCA.ADC.Conference.Application/Sponsors/UseCases/GetPublicSponsorFilter/GetPublicSponsorFilterHandler.cs:25), GetPublicActivityFilterHandler (.../Activities/UseCases/GetPublicActivityFilter/GetPublicActivityFilterHandler.cs:25), GetPublicRoomFilterHandler (.../Events/UseCases/GetPublicRoomFilter/GetPublicRoomFilterHandler.cs:25), GetPublicSpeakerFilterHandler (.../Speakers/UseCases/GetPublicSpeakerFilter/GetPublicSpeakerFilterHandler.cs:26, which passes the query's optional event scope straight through), GetPublicSpeakerCategoryItemFilterHandler (.../Speakers/UseCases/GetPublicSpeakerCategoryItemFilter/GetPublicSpeakerCategoryItemFilterHandler.cs:26), GetPublicSessionSpeakerFilterHandler (.../Sessions/UseCases/GetPublicSessionSpeakerFilter/GetPublicSessionSpeakerFilterHandler.cs:24), GetPublicSessionCategoryItemFilterHandler (.../Sessions/UseCases/GetPublicSessionCategoryItemFilter/GetPublicSessionCategoryItemFilterHandler.cs:25), and GetPublicEventSpeakerFilterHandler (.../Events/UseCases/GetPublicEventSpeakerFilter/GetPublicEventSpeakerFilterHandler.cs:31, :38), which calls two resolvers so a junction row follows the visibility of both its parents.
    • +
    • Caveats / not-in-source: the id lists are materialized and embedded, and the framework says so: the helper fits principal sets that are small and bounded, the "published events" shape (MMCA.Common/Source/Core/MMCA.Common.Application/Specifications/CrossSourceSpecification.cs:17-20). A single call to GetVisibleSpeakerIdsAsync issues three sequential round trips (events, eligible sessions, join rows), and the junction handler calls two resolvers, reading the bounded Event table twice. Nothing in this file caches any of it, and there is no test class dedicated to this type: its behavior is exercised only through the eight handlers that call it.
    • +
    +

    SponsorCreateRequestMapper

    +
    +

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sponsors.UseCases.Create · MMCA.ADC.Conference.Application/Sponsors/UseCases/Create/SponsorCreateRequestMapper.cs:11 · Level 10 · class (sealed)

    +
    +
      +
    • What it is: the one place that knows how to turn a SponsorCreateRequest into a Sponsor. It does not construct the entity itself; it calls the domain factory and hands back whatever Result<Sponsor> that factory returns (MMCA.ADC.Conference.Application/Sponsors/UseCases/Create/SponsorCreateRequestMapper.cs:19-31).
    • +
    • Depends on: IEntityRequestMapper<TEntity, TCreateRequest, TIdentifierType> closed over Sponsor / SponsorCreateRequest / SponsorIdentifierType (:12), the Sponsor aggregate and its Create factory, and Result (:3).
    • +
    • Concept reinforced, request mapping is not object mapping: SponsorDTOMapper can be source-generated because its target is a settable record. This mapper cannot, because its target is a factory that returns a Result<T>: the entity's constructor is private and the only way in runs the invariants first. So the direction out of the domain is generated and the direction into it is hand-written, which is exactly the split ADR-001 describes. [Rubric §4, DDD] assesses whether invariants are unavoidable: there is no path from a request to a Sponsor that skips Sponsor.Create.
    • +
    • Walkthrough: one method.
        +
      • ArgumentNullException.ThrowIfNull(request) (:17) guards the reference the interface does not declare as nullable.
      • +
      • Task.FromResult(Sponsor.Create(...)) (:19-31) forwards twelve values in the factory's parameter order (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sponsors/Sponsor.cs:105-117). There is no await because there is no I/O: the method is Task-returning to satisfy the interface, not because anything is asynchronous, and wrapping a completed value avoids allocating a state machine.
      • +
      • The work then happens in the domain: Sponsor.Create (Sponsor.cs:105-136) combines three invariant checks for name, logo URL, and booth number (:119-122), decides whether to honor or discard the supplied id based on IsIdValueGenerated (:126-131), and raises SponsorChanged with DomainEntityState.Added before returning success (:133).
      • +
      • request.Id is a non-nullable int widened to the factory's SponsorIdentifierType? parameter (Sponsor.cs:106), which is why the request can declare a plain value type and still reach a nullable factory slot.
      • +
      +
    • +
    • Why it's built this way: keeping the factory call behind an interface means the create slice can grow an asynchronous pre-check (a uniqueness lookup, for example) by changing this one class, with no edit to CreateSponsorHandler, which forwards the interface into the framework base (CreateSponsorHandler.cs:17, :21). [Rubric §1, SOLID]: dependency inversion applied at the smallest useful granularity.
    • +
    • Where it's used: registered by the IEntityRequestMapper<,,> assembly scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:221-225, invoked from MMCA.ADC.Conference.Application/DependencyInjection.cs:139), forwarded from CreateSponsorHandler into the base, and called there at MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/CreateEntityHandlerBase.cs:90.
    • +
    • Caveats / not-in-source: the async signature is currently unused, and no existence or uniqueness check happens here today; see the caveat on CreateSponsorHandler for what does and does not verify EventId. The base's PrepareAsync hook (CreateEntityHandlerBase.cs:114-118) is the other place such a check could live, and the sponsor slice overrides neither.
    • +
    +

    SponsorCreateRequestValidator

    +
    +

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sponsors.UseCases.Create · MMCA.ADC.Conference.Application/Sponsors/UseCases/Create/SponsorCreateRequestValidator.cs:7 · Level 10 · class (sealed)

    +
    +
      +
    • What it is: the FluentValidation validator for SponsorCreateRequest. Its constructor is two Include calls and nothing else (MMCA.ADC.Conference.Application/Sponsors/UseCases/Create/SponsorCreateRequestValidator.cs:9-16): it owns no rule of its own, it composes the shared sponsor rule bundle and adds the one rule the create verb needs on top of it.
    • +
    • Depends on: AbstractValidator<T> from FluentValidation (:7), SponsorFieldRules<T> closed over the create request (:11), and SponsorEventIdRules<T> (:15).
    • +
    • Concept reinforced, composable rule objects, one level up: the Include technique is introduced in Group 06; what this file shows is the technique applied twice. Include merges another validator's rules into this one, and SponsorFieldRules<T> is itself nothing but eight Include calls over the ISponsorFieldsRequest members (MMCA.ADC.Conference.Application/Sponsors/Validation/SponsorValidationRules.cs:139-154). Because the bundle is constrained to the interface (SponsorValidationRules.cs:141), the create and the update validator bind the same eight rules to two different request types without restating any of them. The payoff is visible in the sibling: SponsorUpdateRequestValidator is a single Include of that same bundle (MMCA.ADC.Conference.Application/Sponsors/UseCases/Update/SponsorUpdateRequestValidator.cs:9-10), so the create and update contracts cannot drift on max lengths or error codes. [Rubric §24, Forms, Validation, and UX Safety] assesses whether validation is stated once and enforced consistently: the rule text and error codes a client sees are identical on both verbs.
    • +
    • Walkthrough: two lines, and the bundle behind the first. +
    • +
    • Why it's built this way: no handler calls this class. ValidatingCommandDecorator<TCommand, TResult> resolves it from the container and runs it before the handler, converting failures into a Result rather than an exception (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:136). [Rubric §6, CQRS & Event-Driven Design] and [Rubric §6, CQRS and Event-Driven] both point at the same design: validation is a pipeline stage, so a handler that forgets to validate cannot exist.
    • +
    • Where it's used: registered by AddValidatorsFromAssembly(moduleAssembly) inside the module scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:258, invoked at MMCA.ADC.Conference.Application/DependencyInjection.cs:139). Its tests walk the boundaries directly, including the exact-max-length pass and the null-optional-strings pass (SponsorCreateRequestValidatorTests, MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sponsors/Validation/SponsorCreateRequestValidatorTests.cs:8).
    • +
    • Caveats / not-in-source: two gaps are worth knowing. NotEmpty on an int rejects only the default value, which the framework rule documents as the deliberate "an id was never supplied" check (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:134-140), so it proves an event was chosen, not that the event exists (the database FK does that, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Sponsors/SponsorConfiguration.cs:59-65). And no rule here constrains Tier: an out-of-range enum value passes validation, and Sponsor.Create does not check it either (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sponsors/Sponsor.cs:119-122).

    SponsorNavigationPopulator

    @@ -5998,7 +6048,7 @@

    SponsorNavigationPopulator

    • What it is: the navigation populator for the Sponsor aggregate. One navigation, and it points up: the Event a sponsor belongs to (MMCA.ADC.Conference.Application/Sponsors/SponsorNavigationPopulator.cs:8-11).
    • Depends on: DeclarativeNavigationPopulator<TEntity> closed over Sponsor (:14); FKNavigationDescriptor<TEntity, TChild, TChildId> (:16); IUnitOfWork (:13); the Event aggregate as the reference target.
    • -
    • Concept reinforced: none new; see SpeakerCategoryItemNavigationPopulator for the FK direction. What differs is only which key is read and which aggregate is fetched: PropertyName = nameof(Sponsor.Event) (:18), ParentKeySelector = e => e.EventId (:19, over the private-set FK at MMCA.ADC.Conference.Domain/Sponsors/Sponsor.cs:45), ChildForeignKeySelector = child => child.Id (:20), AssignAction = (e, events) => e.SetEvent(events.FirstOrDefault()) (:21) calling the public mutator (Sponsor.cs:202) that writes the attributed, private-set navigation (Sponsor.cs:48-49), and an empty class body (:24-25). Worth noting the shape this reveals: Sponsor is an aggregate root that owns no children of its own, which is why it needs exactly one descriptor and why its delete is the generic DeleteEntityHandler<TEntity, TIdentifierType> that services.AddEntityCrud<Sponsor, SponsorDTO, SponsorIdentifierType, SponsorCreateRequest, SponsorUpdateRequest>() registers wholesale (MMCA.ADC.Conference.Application/DependencyInjection.cs:155, resolving to MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Crud.cs:90-92) rather than a cascade handler of its own, even though it is itself swept up by DeleteEventHandler when its event goes away.
    • +
    • Concept reinforced: none new; see SpeakerCategoryItemNavigationPopulator for the FK direction. What differs is only which key is read and which aggregate is fetched: PropertyName = nameof(Sponsor.Event) (:18), ParentKeySelector = e => e.EventId (:19, over the private-set FK at MMCA.ADC.Conference.Domain/Sponsors/Sponsor.cs:45), ChildForeignKeySelector = child => child.Id (:20), AssignAction = (e, events) => e.SetEvent(events.FirstOrDefault()) (:21) calling the public mutator (Sponsor.cs:202) that writes the attributed, private-set navigation (Sponsor.cs:48-49), and an empty class body (:24-25). Worth noting the shape this reveals: Sponsor is an aggregate root that owns no children of its own, which is why it needs exactly one descriptor and why its delete is the generic DeleteEntityHandler<TEntity, TIdentifierType> that services.AddEntityCrud<Sponsor, SponsorDTO, SponsorIdentifierType, SponsorCreateRequest, SponsorUpdateRequest>() registers wholesale (MMCA.ADC.Conference.Application/DependencyInjection.cs:151, resolving to MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Crud.cs:90-92) rather than a cascade handler of its own, even though it is itself swept up by DeleteEventHandler when its event goes away.
    • Where it's used: registered as services.TryAddScoped<INavigationPopulator<Sponsor>, SponsorNavigationPopulator>() (MMCA.ADC.Conference.Application/DependencyInjection.cs:100), immediately above the closed generic EntityQueryService<TEntity, TEntityDTO, TIdentifierType> registration for sponsors (:91), which is injected into SponsorsController (MMCA.ADC.Conference.API/Controllers/Sponsors/SponsorsController.cs:41). Covered by SponsorNavigationPopulatorTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Sponsors/SponsorNavigationPopulatorTests.cs:16, :20, :24, :35).
    • Caveats / not-in-source: SponsorDTO carries EventId but no Event (MMCA.ADC.Conference.Shared/Sponsors/SponsorDTO.cs:69), and the sponsor mapper projects no event data, so what this populator hydrates does not reach an API response on the read path today. The descriptor keeps the entity self-consistent when a sponsor is materialized with FK includes; it is not currently what any client sees.
    @@ -6031,7 +6081,7 @@

    PartnerNavigationPopulator

    • What it is: the navigation populator for the Partner aggregate. One navigation, and it points up: the Event a partner belongs to (MMCA.ADC.Conference.Application/Partners/PartnerNavigationPopulator.cs:16-22).
    • Depends on: DeclarativeNavigationPopulator<TEntity> closed over Partner (:15); FKNavigationDescriptor<TEntity, TChild, TChildId> (:17); IUnitOfWork (:14); the Event aggregate as the reference target.
    • -
    • Concept reinforced: none new; see SpeakerCategoryItemNavigationPopulator for the FK direction. What differs is only which key is read and which aggregate is fetched: PropertyName = nameof(Partner.Event) (:19), ParentKeySelector = e => e.EventId (:20, over the private-set FK at MMCA.ADC.Conference.Domain/Partners/Partner.cs:40), ChildForeignKeySelector = child => child.Id (:21), AssignAction = (e, events) => e.SetEvent(events.FirstOrDefault()) (:22) calling the public mutator (Partner.cs:154) that writes the private-set navigation (Partner.cs:44), and an empty class body (:24-25). This is the same one-descriptor shape as SponsorNavigationPopulator: Partner is another aggregate root that owns no children of its own, so its delete is the generic DeleteEntityHandler<TEntity, TIdentifierType> that services.AddEntityCrud<Partner, PartnerDTO, PartnerIdentifierType, PartnerCreateRequest, PartnerUpdateRequest>() registers wholesale (MMCA.ADC.Conference.Application/DependencyInjection.cs:156), even though it is itself swept up by DeleteEventHandler when its event goes away.
    • +
    • Concept reinforced: none new; see SpeakerCategoryItemNavigationPopulator for the FK direction. What differs is only which key is read and which aggregate is fetched: PropertyName = nameof(Partner.Event) (:19), ParentKeySelector = e => e.EventId (:20, over the private-set FK at MMCA.ADC.Conference.Domain/Partners/Partner.cs:40), ChildForeignKeySelector = child => child.Id (:21), AssignAction = (e, events) => e.SetEvent(events.FirstOrDefault()) (:22) calling the public mutator (Partner.cs:154) that writes the private-set navigation (Partner.cs:44), and an empty class body (:24-25). This is the same one-descriptor shape as SponsorNavigationPopulator: Partner is another aggregate root that owns no children of its own, so its delete is the generic DeleteEntityHandler<TEntity, TIdentifierType> that services.AddEntityCrud<Partner, PartnerDTO, PartnerIdentifierType, PartnerCreateRequest, PartnerUpdateRequest>() registers wholesale (MMCA.ADC.Conference.Application/DependencyInjection.cs:152), even though it is itself swept up by DeleteEventHandler when its event goes away.
    • Where it's used: registered as services.TryAddScoped<INavigationPopulator<Partner>, PartnerNavigationPopulator>() (MMCA.ADC.Conference.Application/DependencyInjection.cs:103), immediately above the closed generic EntityQueryService<TEntity, TEntityDTO, TIdentifierType> registration for partners (:107), which is injected into PartnersController (MMCA.ADC.Conference.API/Controllers/Partners/PartnersController.cs:42). Covered by PartnerNavigationPopulatorTests (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Partners/PartnerNavigationPopulatorTests.cs:16, :20, :31).
    • Caveats / not-in-source: PartnerDTO carries EventId but no Event (MMCA.ADC.Conference.Shared/Partners/PartnerDTO.cs:54), so what this populator hydrates does not reach an API response on the read path today, the same gap SponsorNavigationPopulator has. The descriptor keeps the entity self-consistent when a partner is materialized with FK includes; it is not currently what any client sees.
    @@ -6040,7 +6090,7 @@

    DeleteSessionHandler

    MMCA.ADC.Conference.Application · MMCA.ADC.Conference.Application.Sessions.UseCases.Delete · MMCA.ADC.Conference.Application/Sessions/UseCases/Delete/DeleteSessionHandler.cs:25 · Level 14 · class (sealed partial)

      -
    • What it is: the module's Session-specific subclass of the framework's generic delete handler. Beyond the child collections the aggregate's own cascade has to see and a log line, it now also soft-deletes the session's SessionAsset rows before the session and schedules the orphaned blobs for removal after the delete commits (DeleteSessionHandler.cs:25-129).
    • +
    • What it is: the module's Session-specific subclass of the framework's generic delete handler. Beyond the child collections the aggregate's own cascade has to see and a log line, it now also soft-deletes the session's SessionAsset rows before the session and schedules the orphaned blobs for removal after the delete commits (DeleteSessionHandler.cs:25-107).
    • Depends on: DeleteEntityHandler<TEntity, TIdentifierType> closed over Session and SessionIdentifierType (DeleteSessionHandler.cs:38); IUnitOfWork, passed to the base and used directly for the asset repository lookup (:35, :37, :100); IInternalCommandScheduler to schedule blob deletion (:36); DeleteSessionAssetBlobInternalCommand as the scheduled message (:77); DeleteEntityCommand<TEntity, TIdentifierType> as the message shape (:38); Microsoft.Extensions.Logging for the two source-generated log methods.
    • Concept introduced, specializing a generic handler by overriding hooks rather than rewriting it: DeleteEntityHandler<TEntity, TIdentifierType> is deliberately left unsealed, and its workflow is split into overridable steps precisely so a module can adjust the things a real delete outgrows: which navigations to load, a pre-delete cross-aggregate hook, and post-save logging (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/DeleteEntityHandler.cs:13-22). This class now overrides Includes, LogDeleted, OnDeletingAsync, and additionally overrides HandleAsync itself to run work after the base's save commits. [Rubric §1, SOLID] assesses substitutability at the abstraction: because the DI registration is keyed by the closed ICommandHandler<DeleteEntityCommand<Session, SessionIdentifierType>, Result> interface (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/DependencyInjection.cs:81), nothing upstream knows a subclass was substituted, or that this subclass now does more than its siblings. See ADR-099, which records the extensible DeleteEntityHandler as part of the generic write-side surface, and ADR-123, which covers the session-asset feature this delete path now has to account for.
    • Concept introduced, why a soft-delete cascade is load-order sensitive: Session.Delete() (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/Session.cs:306-316) combines three DeleteChildren<...> calls over _sessionSpeakers, _sessionQuestionAnswers, and _sessionCategoryItems with the base delete, children first and root last per BR-55 (Session.cs:308-312). Those are in-memory backing lists, so they contain only what EF materialized; declaring them in Includes is what makes them visible to the cascade at all. SessionAsset is a separate aggregate rather than an owned collection, so Session.Delete() cannot reach it: the handler soft-deletes each asset itself in OnDeletingAsync, in the same save the base workflow issues, rather than relying on the entity's own cascade. [Rubric §4, DDD] assesses whether the aggregate boundary is enforced at write time: an owned collection is cascaded by the aggregate root itself, while a separate aggregate that merely references the one being deleted has to be cascaded by the use case, exactly the split this class now draws between its Includes list and its OnDeletingAsync override. [Rubric §8, Data Architecture]: soft-delete is the workspace default (ADR-005), so an orphaned active child, or an orphaned blob, is a data-correctness bug, not a cosmetic one.
    • diff --git a/docs/onboarding/group-19-conference-infrastructure.html b/docs/onboarding/group-19-conference-infrastructure.html index 0517de11..497af5fa 100644 --- a/docs/onboarding/group-19-conference-infrastructure.html +++ b/docs/onboarding/group-19-conference-infrastructure.html @@ -231,7 +231,7 @@

      SoftDeleteUniqueIndexConvention - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:43-54), + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:44-55), so a soft-deleted link never blocks a re-insert; CategoryItemConfiguration relies on exactly that and declares its unique (CategoryId, Name) index with no filter call at all (CategoryItemConfiguration.cs:30-31). A @@ -314,7 +314,7 @@

      context walks the registered configuration assemblies and applies every IEntityTypeConfigurationSQLServer<,> implementation whose entity resolves to this context's data source key - (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:823-853, + (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:826-856, with the engine-to-interface switch at :612-618 and the registry filter at :625-636). That is why two entities with a configuration here, SessionAiScore and SpeakerQuestionAnswer, are mapped and queryable @@ -554,7 +554,7 @@

      DI wiring, and w AddMmcaChatClient, which reads the IChatGuardrail descriptors to decide whether to compose the guardrail layer at all, so a later registration would be silently absent (:78-82), and the Conference service host calls the two in exactly that order - (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:150,152). + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:152,154). AddSessionScoreResponseGuardrail() (DependencyInjection.cs:124-132) registers the one policy with TryAddEnumerable as a singleton IChatGuardrail (:128-129), so it stacks with the framework's guardrails instead of replacing them and a second call composes nothing twice (:111-115); it stays public @@ -654,22 +654,22 @@

      SessionScoringService

      • What it is: the replacement for the removed AnthropicScoringService. It implements IAiScoringService by scoring one session proposal against a Program Committee rubric through the framework's governed IChatClient (MMCA.Common.AI), rather than by calling a named vendor's HTTP API directly. The governance around it (prompt versioning, the golden evaluation suite, the injection posture) is recorded in ADR-111; the boundary that put the chat client itself behind a framework port is ADR-120.
      • Depends on: first-party: IAiScoringService (implements), SessionScoringInput, SessionScoringResult, SpeakerInfo, PromptContract (constructor parameter and the source of PromptVersion), and AiScoreResponse (own file now, not a nested record). External: Microsoft.Extensions.AI (IChatClient, ChatMessage, ChatResponse, ChatOptions, ChatGuardrailException), ILogger<T>, System.Text.Json, System.Globalization.
      • -
      • Concept introduced, the module supplies only the prompt, the framework supplies the vendor. [Rubric §3, Clean Architecture] and [Rubric §1, SOLID] (dependency inversion) assess whether inner layers depend on abstractions rather than vendors: this class constructs no HttpClient and names no vendor anywhere in its own code, it depends on IChatClient, the framework's own port. [Rubric §16, AI-Native Application Architecture] assesses whether model interaction is versioned, constrained and evaluable: PromptVersion (:224), the SessionScoringContract static (:232-233) and PromptVersionValue (:359, format yyyy-MM-dd.N) are the versioning story; the golden evaluation suite pins the rendered prompt by hash per version. [Rubric §29, Resilience & Business Continuity] assesses graceful degradation: an unconfigured client (:242-246), a guardrail block (:265-273), and any other non-cancellation exception (:274-278) all converge on FailedResult, so one bad proposal cannot abort a batch.
      • +
      • Concept introduced, the module supplies only the prompt, the framework supplies the vendor. [Rubric §3, Clean Architecture] and [Rubric §1, SOLID] (dependency inversion) assess whether inner layers depend on abstractions rather than vendors: this class constructs no HttpClient and names no vendor anywhere in its own code, it depends on IChatClient, the framework's own port. [Rubric §16, AI-Native Application Architecture] assesses whether model interaction is versioned, constrained and evaluable: PromptVersion (:84), the SessionScoringContract static (:92-93) and PromptVersionValue (:219, "2026-09-04.1", format yyyy-MM-dd.N) are the versioning story; the golden evaluation suite pins the rendered prompt by hash per version. [Rubric §29, Resilience & Business Continuity] assesses graceful degradation: an unconfigured client (:102-106), a guardrail block (:125-133), and any other non-cancellation exception (:134-138) all converge on FailedResult, so one bad proposal cannot abort a batch.
      • Walkthrough
          -
        • A primary constructor injects IChatClient? chatClient, PromptContract promptContract and ILogger<SessionScoringService> (:186-189). IChatClient is resolved with GetService, not GetRequiredService, by DependencyInjection, so a host with AI disabled or no key still starts.
        • -
        • PromptName (:196, "session-scoring") tags every usage measurement on the MMCA.Common.AI meter. ModelIdValue (:203, "claude-haiku-4-5") is pinned into the prompt contract's hash. MaxOutputTokens (:210, 256) is the class's own default that Ai:MaxOutputTokens clamps again at the framework boundary. SessionScoringContract (:232-233) is the one static PromptContract the module scores with, built from those constants plus SystemPrompt; composition passes it to the constructor so a test can score against a different contract without touching the registration.
        • -
        • ScoreSessionAsync (:236) null-checks session (:240), then guards on a disabled client (:242-246, log-and-FailedResult), builds the user message with BuildUserPrompt (:250), then asks the contract for its ChatOptions (:254) and layers on MaxOutputTokens (:255) and a JSON-schema ResponseFormat built from ScoreSchema (:256). chatClient.GetResponseAsync sends one user ChatMessage (:258-261). A caught ChatGuardrailException (:265-273) logs the guardrail's own reported reason and returns FailedResult; any other non-cancellation exception does the same (:274-278).
        • -
        • InterpretResponse (:281) logs usage from response.Usage when present (:286-289), then hands response.Text straight to ParseSingleScore (:294): refusals, empty answers and malformed text are already refused upstream by SessionScoreResponseGuardrail, so this method's own job is parse and map.
        • -
        • ParseSingleScore (:297-311) deserializes responseText into AiScoreResponse with AiScoreResponse.JsonOptions (:304) and passes it to BuildResult; a JsonException is the fail-closed path for a caller that composed the scorer without the governed pipeline, such as the golden replay suite (:307-310).
        • -
        • BuildResult (:313) requires the penalty and all six sub-scores present via a single is not { ... } pattern (:317-329); the overall score is weighted 30/10/20/20/10/10 (:333-339) and the penalty subtracted (:344), with every value passed through Clamp (:482), Math.Clamp(value, 1.0m, 10.0m) rounded to one decimal with MidpointRounding.ToEven.
        • -
        • SystemPrompt (:363) carries the ADC track list, the six weighted criteria, calibration rules ("most talks should fall between 5.5 and 7.5"), and the 0.0/0.5/1.0 penalty ladder, ending with UntrustedInputBrief (:389, :404-411), an internal const kept separate only so the evaluation suite can assert on it by name.
        • -
        • BuildUserPrompt (:418) is the containment half: a delimited, escaped envelope (<session_proposal>, <session_title>, <session_description>) rather than labelled Title:/Description: lines, with the reasoning recorded at :413-417. FormatSpeakers (:429) renders each speaker's name, tagline and bio inside <speaker> tags, or the literal <speakers>(no speaker information available)</speakers> when there are none (:431-432). Escape (:477-480) replaces < and > with entities.
        • -
        • RenderPrompt (:465) is the one public affordance for testing: the exact system-plus-user pair this service would send without calling the model, so the evaluation suite can hash it per PromptVersion.
        • -
        • FailedResult (:484, tail not shown) is the single shape of failure, matching the prior adapter's contract.
        • +
        • A primary constructor injects IChatClient? chatClient, PromptContract promptContract and ILogger<SessionScoringService> (:46-49). IChatClient is resolved with GetService, not GetRequiredService, by DependencyInjection (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/DependencyInjection.cs:47-50), so a host with AI disabled or no key still starts.
        • +
        • PromptName (:56, "session-scoring") tags every usage measurement on the MMCA.Common.AI meter. ModelIdValue (:63, "claude-haiku-4-5") is pinned into the prompt contract's hash. MaxOutputTokens (:70, 256) is the class's own default that Ai:MaxOutputTokens clamps again at the framework boundary. SessionScoringContract (:92-93) is the one static PromptContract the module scores with, built from those constants plus SystemPrompt; composition passes it to the constructor so a test can score against a different contract without touching the registration.
        • +
        • ScoreSessionAsync (:96) null-checks session (:100), then guards on a disabled client (:102-106, log-and-FailedResult), builds the user message with BuildUserPrompt (:110), then asks the contract for its ChatOptions (:114) and layers on MaxOutputTokens (:115) and a JSON-schema ResponseFormat built from ScoreSchema (:116). chatClient.GetResponseAsync sends one user ChatMessage (:118-121). A caught ChatGuardrailException (:125-133) logs the guardrail's own reported reason and returns FailedResult; any other non-cancellation exception does the same (:134-138).
        • +
        • InterpretResponse (:141) logs usage from response.Usage when present (:146-149), then hands response.Text straight to ParseSingleScore (:154): refusals, empty answers and malformed text are already refused upstream by SessionScoreResponseGuardrail, so this method's own job is parse and map.
        • +
        • ParseSingleScore (:157-171) deserializes responseText into AiScoreResponse with AiScoreResponse.JsonOptions (:164) and passes it to BuildResult; a JsonException is the fail-closed path for a caller that composed the scorer without the governed pipeline, that is, a direct construction over a bare chat client (:159-161, :167-170).
        • +
        • BuildResult (:173) requires the penalty and all six sub-scores present via a single is not { ... } pattern (:177-189); the overall score is weighted 30/10/20/20/10/10 (:193-199) and the penalty subtracted (:204), with every value passed through Clamp (:342), Math.Clamp(value, 1.0m, 10.0m) rounded to one decimal with MidpointRounding.ToEven.
        • +
        • SystemPrompt (:223) carries the ADC track list, the six weighted criteria, calibration rules ("most talks should fall between 5.5 and 7.5"), and the 0.0/0.5/1.0 penalty ladder, ending with UntrustedInputBrief (:249, :264-271), an internal const kept separate only so the evaluation suite can assert on it by name.
        • +
        • BuildUserPrompt (:278) is the containment half: a delimited, escaped envelope (<session_proposal>, <session_title>, <session_description>) rather than labelled Title:/Description: lines, with the reasoning recorded at :273-277. FormatSpeakers (:289) renders each speaker's name, tagline and bio inside <speaker> tags, or the literal <speakers>(no speaker information available)</speakers> when there are none (:291-292). Escape (:337-340) replaces < and > with entities.
        • +
        • RenderPrompt (:325) is the one public affordance for testing: the exact system-plus-user pair this service would send without calling the model, so the evaluation suite can hash it per PromptVersion.
        • +
        • FailedResult (:344-357) is the single shape of failure: every score 0m, Reasoning = "Scoring failed", Success = false. ScoreSchema (:360, built by BuildScoreSchema at :362-393) is the structured-output schema: the six criteria as numbers, penalty restricted to 0, 0.5 or 1, a string reasoning, all required and additionalProperties = false.
      • Why it's built this way: concentrating vendor specifics behind IChatClient makes swapping providers or faking the service in tests a one-class change; the never-throw-for-scoring, clamp, and all-or-nothing-parse discipline makes raw model output safe to persist into SessionAiScore. Moving AiScoreResponse to its own file, rather than nesting it as before, is what lets SessionScoreResponseGuardrail share the exact same shape and completeness check.
      • -
      • Where it's used: registered as the IAiScoringService implementation by DependencyInjection; driven per session by the durable internal-command handler ScoreEventSessionsInternalCommandHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/ScoreEventSessionsInternalCommandHandler.cs:42), which runs the scoring pass under a per-event distributed lock (ADR-114). Exercised directly by SessionScoringServiceTests, SessionScoreResponseGuardrailTests, and through RenderPrompt by the MMCA.ADC.Conference.Scoring.Evaluation.Tests project (PromptContractTests, GoldenReplayTests, LiveJudgeTests).
      • +
      • Where it's used: registered as the IAiScoringService implementation by DependencyInjection; injected into SessionScoringRunner (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/SessionScoringRunner.cs:26), which calls it per session and is in turn driven by the durable internal-command handler ScoreEventSessionsInternalCommandHandler (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/ScoreEventSessionsInternalCommandHandler.cs:42-43), which runs the scoring pass under a per-event distributed lock (ADR-114). Exercised directly by SessionScoringServiceTests, SessionScoreResponseGuardrailTests, and through RenderPrompt by the MMCA.ADC.Conference.Scoring.Evaluation.Tests project (PromptContractTests, GoldenReplayTests, LiveJudgeTests).
      • Caveats / not-in-source: there is no retry inside this class. Whatever resilience the chat call carries is the framework's IChatClient pipeline's concern, configured where AddMmcaChatClient is called; the in-class contract stays "never throw for a scoring failure, let the batch continue".

      @@ -700,6 +700,39 @@

      SessionizeService

    • Where it's used: the Sessionize import handlers in Conference Application, triggered when an organizer refreshes an event's data.

    +

    ConferenceModuleDbSeeder

    +
    +

    MMCA.ADC.Conference.Infrastructure · MMCA.ADC.Conference.Infrastructure.Persistence.DbContexts.Seeding · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/DbContexts/Seeding/ConferenceModuleDbSeeder.cs:27 · Level 9 · class

    +
    +
      +
    • What it is: the Conference module's startup data seeder. It puts the two real conference editions and the ten feedback questions into a fresh database on every boot, and, only when its includeSampleData flag is set, a small deterministic browse fixture on top (two speakers, two sessions, the speaker links, four sponsors, four partners, three social activities). Every write goes through a domain factory and the unit of work, and every insert is guarded by an existence check, so running it against an already-seeded database is a no-op.
    • +
    • Depends on: first-party: DbSeeder (base, :27), IUnitOfWork (:27, :49) and the IRepository<TEntity, TIdentifierType> handles it hands out (:72, :147, :195, :249, :308, :362, :414, :422, :470), the domain factories Event (:87, :122), Question (:175), Speaker (:215), Session (:279), Sponsor (:387), Partner (:447) and Activity (:516), the join entities EventSpeaker and SessionSpeaker (created indirectly at :344, :347, :573), the id-range constants on QuestionInvariants (:171) and SessionInvariants (:257-258), and SponsorTier (:366). External: BCL only (DateOnly, TimeOnly, TimeSpan, DateTimeKind).
    • +
    • Concept introduced, seeding through the domain rather than through SQL. A seeder in this codebase never writes rows. It calls the same static factory an HTTP command handler would call, checks the returned Result wrapper, hands the entity to a repository, and lets IUnitOfWork commit. Event.Create(...) at :87-99 is the identical entry point the organizer's create-event use case takes, so seeded data satisfies exactly the invariants that user-created data satisfies: there is no second, looser definition of a valid event hiding in the seeder. The consequence worth internalizing is that a seed insert is a full domain write with all its side effects: eventResult.Value!.Publish() at :104 flips IsPublished and raises an EventChanged domain event (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/Event.cs:310-312), so the SaveChangesAsync at :107 writes an outbox row alongside the entity row. Startup seeding therefore feeds the same dual-dispatch pipeline as runtime traffic (ADR-003), it does not bypass it. This is what Rubric section 4 (DDD) assesses whether the domain model is the single place invariants live; routing seed data through the factories is what keeps that true at the one moment it is most tempting to cheat.
    • +
    • Concept introduced, idempotency that respects soft delete. Every existence probe in this file passes ignoreQueryFilters: true (:81, :116, :151, :209, :270, :381, :441, :508), which turns off the global soft-delete filter for that one query. The comment at :74-78 gives both halves of the reason. First, a deleted seed row is a decision someone made, not a gap to refill, so the seeder must see it and stand down. Second, the fixed-id rows (questions at :175, sample sessions at :257-258) keep their primary keys when soft-deleted, so a filtered check would report "missing", re-insert the same id, and take startup down on a primary-key violation. The ExistsAsync(Expression<Func<TEntity, bool>>, bool ignoreQueryFilters, CancellationToken) overload that makes this possible is on the shared repository contract (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:62-65). ConferenceModuleDbSeederTests pins the behavior mechanically: its Moq setups match true for that parameter only (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Infrastructure.Tests/Seeding/ConferenceModuleDbSeederTests.cs:103-113), so a future edit that drops the flag falls through to Moq's default false and the "skips when exists" tests go red (the reasoning is spelled out in the comment at :99-102 of that test file). This is what Rubric section 17 (DevOps) assesses whether startup is repeatable and safe to re-run; this is the pattern that makes "boot the app twice" a non-event.
    • +
    • Concept introduced, environment-gated fixture data. The class takes bool includeSampleData = false (:27), stores it (:50), and branches on it in SeedAsync (:59-67). The default is off, and the only thing that turns it on is ConferenceModuleSeeder reading configuration.GetValue<bool>("Seeding:IncludeSampleConferenceData") (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/ConferenceModuleSeeder.cs:26). GetValue<bool> on an absent key yields false, so a production host that never sets the key gets the real events and questions and nothing else. The one place the key is set is the local Aspire AppHost, .WithEnvironment("Seeding__IncludeSampleConferenceData", "true") on the Conference service (MMCA.ADC/Source/Hosting/MMCA.ADC.AppHost/Program.cs:211, rationale at :207-209). E2E CI inherits it by launching that same AppHost (MMCA.ADC/.github/workflows/e2e.yml:219), which is why the two public-browse tests can assume rows exist (MMCA.ADC/Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/Conference/Public/PublicBrowseTests.cs:93, :102). This is what Rubric section 11 (Security) assesses whether non-production affordances are structurally unable to reach production: here the gate is a default-false configuration read in the composition root, not a runtime environment sniff inside the seeder.
    • +
    • Concept introduced, reserved manual id ranges. Conference session ids are app-assigned rather than database-generated because the integer primary key is the Sessionize id when a session arrives through the import (comment at :251-254). Sample sessions have no Sessionize id, so they take explicit ids from the top of the integer space: SessionInvariants.ManualIdRangeStart is 999_999_000 (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/SessionInvariants.cs:44) and the two fixtures take that value and that value plus one (:257-258). Questions do the same from QuestionInvariants.ManualIdRangeStart, also 999_999_000 (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Questions/QuestionInvariants.cs:40), incremented per question by id: nextId++ (:171, :175). The range sits above any id an upstream system will mint, so seed rows and imported rows can never collide, and the same constants are what the organizer-facing create handlers continue from. This is what Rubric section 8 (Data Architecture) assesses whether key strategy is deliberate; reserving a high range is the cheap alternative to a separate identity column or a synthetic-vs-natural key split.
    • +
    • Walkthrough
        +
      • Primary constructor and fields (:27, :49-50): ConferenceModuleDbSeeder(IUnitOfWork unitOfWork, bool includeSampleData = false) : DbSeeder(). The unit of work is null-guarded into _unitOfWork at :49 (the one behavior a unit test asserts directly, ConferenceModuleDbSeederTests.cs:73-75); the flag is copied to _includeSampleData at :50. The base DbSeeder contributes the SeedAsync abstract member and a GetId<TIdentifier>(int) helper for int-or-Guid key strategies (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Seeding/DbSeeder.cs:20-39), which this seeder does not use: the Conference identifier aliases are int, so literal ids pass straight through.
      • +
      • Literal constants (:29-47): the shared venue address and embedded map URL, three placeholder URLs for sample sponsors, partners and activities, and the two published sponsorship-packet URLs. The six URL constants each carry a SuppressMessage for Sonar S1075 (URIs should not be hardcoded) with a per-constant justification, which is how this repo records "the literal is the data" rather than switching the analyzer off.
      • +
      • SeedAsync (:53-67): the whole contract in fifteen lines. Three unconditional steps (:55-57) then a single if (_includeSampleData) block of six (:61-66). Ordering is a real dependency chain: sessions need the events, the speaker links need both the speakers and the sessions, sponsors and partners need the events, and partners are seeded right after sponsors and before activities.
      • +
      • SeedCloudAiConferenceEventAsync (:70-108): probes for the event by name, tolerating the pre-rename spelling so a database seeded before "2026" was prefixed stays idempotent (:79-82). On a miss it builds the 2026 Atlanta Cloud + AI Conference for 2026-05-30 in America/New_York with Sessionize code z1ecmzux (:87-99), returns quietly if the factory fails (:101-102), publishes (:104), adds and saves (:106-107).
      • +
      • SeedDevelopersConferenceEventAsync (:110-143): the same shape for the 2026 Atlanta Developers Conference on 2026-10-17, Sessionize code sf1nopko (:122-134). It needs no legacy-name alternative because that event was never renamed.
      • +
      • SeedQuestionsAsync (:145-191): one probe on the sentinel question "Rate the Session" with QuestionSource == "User" (:149-152) decides the whole set, then ten tuples (:157-169) become ten Question.Create calls with sequential reserved ids (:173-188) and a single save (:190). Six are session-scoped (five ratings plus a free-text comment), four are event-scoped. The Times.Exactly(10) assertion in the unit tests (ConferenceModuleDbSeederTests.cs:28-30) is what keeps that count honest.
      • +
      • SeedSpeakersAsync (:193-234): two sample speakers, Ada Lovelace and Alan Turing on example.com addresses (:197-201), each probed by first and last name (:207-210) and skipped individually with continue rather than aborting the batch (:212-213). An added flag means the save (:233) only runs when something actually changed. That flag pattern repeats in every sample-data method.
      • +
      • SeedSessionsAsync (:236-304): resolves both events through the shared helper (:246-247), then seeds one session per event (Ada's keynote on the Cloud + AI day, Alan's Azure talk on the Developers day, :255-259) so both the auto-filtered public pages and the organizer list's event filter have data for either selection. Start time is computed from the owning event's own date, sessionEvent.StartDate.ToDateTime(new TimeOnly(13, 0), DateTimeKind.Utc), with a comment recording that 13:00 UTC is 09:00 Eastern for both dates; the session runs one hour. Idempotency here is by title, which is also why a comment notes that a database seeded before the one-session-per-event split keeps its old shape: the title check never moves an existing row.
      • +
      • SeedSampleEventLinksAsync (:306-330): loads the two sample speakers untracked-free (asTracking: false, :310-315), bails if either is missing (:317-320), then calls the two link helpers and ORs their results (:325-326) before one save (:328-329). The comment above :325 explains why both link paths are populated: the speakers-by-event filter has a direct branch (through EventSpeaker) and a transitive branch (through SessionSpeaker), and seeding both exercises both in dev and CI.
      • +
      • LinkSampleEventSpeakersAsync (:332-351) and LinkSampleSessionSpeakersAsync (:558-577): both re-fetch with asTracking: true and an includes list so the aggregate's child collection is loaded before mutation (:337 includes nameof(Event.EventSpeakers); :563 includes nameof(Session.SessionSpeakers)). Idempotency is delegated to the domain: AddEventSpeaker and AddSessionSpeaker return a failed Result on an existing non-deleted link, so .IsSuccess doubles as the "did anything change" signal (:344, :347, :573).
      • +
      • SeedSponsorsAsync (:353-410): four sample sponsors spanning both events and all four SponsorTier values, two of them exhibitors with booth numbers (:366-369), each probed by name (:379-382) and built through Sponsor.Create (:387-399). The mapping these rows land in is SponsorConfiguration.
      • +
      • SeedPartnersAsync (:412-466): four sample partners spanning both events, three attached to the Developers edition, Community and Community and Special, and one attached to the Cloud + AI edition, Community, so the comment (:417-419) explains the landing-page band renders fully in dev and CI on the Developers side while the organizer list still has a row whichever event its filter defaults to. Each is probed by name (:437-440) and built through Partner.Create (:447-455). The mapping these rows land in is PartnerConfiguration.
      • +
      • SeedActivitiesAsync (:468-536): three social-programme entries expressed as event-local wall-clock offsets rather than absolute timestamps, a pre-conference party the evening before (DayOffset of -1), a morning coffee, and an after-party. Each is anchored on its own event's start date, activityEvent.StartDate.AddDays(dayOffset).ToDateTime(TimeOnly.MinValue), and the venue URL is only supplied when a venue name exists.
      • +
      • GetSampleEventsAsync (:539-556): the one static helper. A single GetAllAsync fetches both events (again tolerating the pre-rename Cloud + AI name, :544) with asTracking: true so callers can mutate them, then splits the result by name. Callers pass the includes they need, which is why the same helper serves both the no-include sponsor and partner paths and the EventSpeakers-include link path.
      • +
      +
    • +
    • Why it's built this way: this is the file that decides what a freshly provisioned Conference database contains, so it is written to be safe under three conditions that are easy to get wrong. Re-running (guarded by existence checks that see through soft delete), running against production (fixture data behind a default-false configuration key), and running against a database whose real content arrives from elsewhere (reserved id ranges that cannot collide with Sessionize ids). Doing the writes through domain factories and IUnitOfWork rather than raw SQL or EF HasData also means seeding participates in the audit stamping, soft-delete defaults, and outbox dispatch that ApplicationDbContext applies to every save, which matters because per-service databases (ADR-006) each get their own seeding pass at their own host's startup.
    • +
    • Where it's used: instantiated by ConferenceModuleSeeder, the module's IModuleSeeder adapter, which resolves IUnitOfWork and IConfiguration from the scope and constructs it with the gate flag (ConferenceModuleSeeder.cs:21-29). That adapter is discovered by reflection in ModuleLoader (MMCA.Common/Source/Core/MMCA.Common.Application/Modules/ModuleLoader.cs:103-104) and invoked in registration order by SeedAllAsync (ModuleLoader.cs:266-272), which the host calls from DatabaseInitializationExtensions after schema initialization (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:124). Its output is what the public browse pages in Group 21 render in dev and CI, and what the E2E feedback and share workflows target by the reserved session id.
    • +
    • Caveats / not-in-source: (1) Failures are swallowed. A failed factory Result produces a bare return or continue (:101-102, :136-137, :184-185, :225-226, :295-296, :401-402, :457-458, :528-529) with no logging: the class takes no logger, so a seed that silently does nothing leaves no trace beyond the missing rows. The Publish() calls at :104 and :139 likewise discard their Result. (2) The class is public and not sealed, unlike most types in this Infrastructure assembly. (3) Idempotency keys are names and titles, not database constraints: nothing stops a second row with the same event name, sponsor name, partner name, or session title from being created through the normal command paths, after which the seeder's probe would simply keep finding a match. (4) A comment in SeedSessionsAsync documents a real migration gap: databases seeded before the sessions were split across the two events keep the old shape and there is no fix-up path in this file, only the advice to reset the local container volume. (5) The sample-data insert order is not transactional across methods. Each method saves independently (:233, :303, :329, :409, :465, :536), so a crash partway through leaves a partially seeded fixture, which the existence checks will then complete on the next boot. (6) ConferenceModuleDbSeederTests covers only the always-on path with includeSampleData left at its default (ConferenceModuleDbSeederTests.cs:115); the six sample-data methods, now including SeedPartnersAsync, have no unit-test coverage in that file and are exercised indirectly through E2E.
    • +
    +

    DependencyInjection

    MMCA.ADC.Conference.Infrastructure · MMCA.ADC.Conference.Infrastructure · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/DependencyInjection.cs:18 · Level 5 · class (static)

    @@ -718,6 +751,25 @@

    DependencyInjection

  • Where it's used: AddModuleConferenceInfrastructure is called from the Conference module's registration chain (see ConferenceModule), which the module loader invokes in topological order; AddConferenceAiGuardrails is called by the service host and by the evaluation tiers ahead of AddMmcaChatClient.

  • +

    ModuleApplicationDbContext

    +
    +

    MMCA.ADC.Conference.Infrastructure · MMCA.ADC.Conference.Infrastructure.Persistence.DbContexts · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/DbContexts/ModuleApplicationDbContext.cs:22 · Level 12 · abstract class

    +
    +
      +
    • What it is: an abstract DbContext that names the Conference module's entity sets. It declares seventeen internal DbSet<T> properties and forwards its four constructor arguments unchanged to the framework base; it adds no OnModelCreating, no OnConfiguring, and no behavior of any kind.
    • +
    • Depends on: first-party: ApplicationDbContext (base, :25), IEntityConfigurationAssemblyProvider (:23), PhysicalDataSource (:24), and the seventeen Conference entity types it exposes, all from Group 17 except SessionAsset (mapped by SessionAssetConfiguration in this chapter) and Partner (mapped by PartnerConfiguration in this chapter): Event, Room, EventSpeaker, EventQuestionAnswer, Session, SessionSpeaker, SessionQuestionAnswer, SessionCategoryItem, SessionAsset, Speaker, SpeakerCategoryItem, Category, CategoryItem, Question, Sponsor, Partner, Activity. External: Microsoft.EntityFrameworkCore.DbContextOptions and DbSet<T> (:1).
    • +
    • Concept introduced, a DbSet list is an index, not the model. The instinct carried over from a typical EF application is that DbSet<T> properties define what the context maps. In this codebase they do not. The model is built by ApplicationDbContext, which walks the assemblies handed to it by IEntityConfigurationAssemblyProvider and applies every configuration implementing the engine's interface, filtered to the entities routed to this physical data source (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:953-986). The proof is in the arithmetic: the Conference EntityConfiguration folder holds nineteen *Configuration.cs files (seventeen plus SessionAssetConfiguration and PartnerConfiguration), two more than the seventeen DbSets here. SessionAiScore and SpeakerQuestionAnswer are mapped, migrated, and queried without ever appearing on this class. Reading the DbSet list as a coverage manifest would therefore mislead you; read the configuration folder instead. This is what Rubric section 8 (Data Architecture) assesses whether the mapping strategy is explicit and centrally governed; convention-by-assembly-scan is what lets one context class serve every module without any module editing it.
    • +
    • Concept introduced, internal sets as a layering boundary. All seventeen properties are internal (:28-78), not public. Nothing outside MMCA.ADC.Conference.Infrastructure can reach context.Sessions even with a context instance in hand. Application-layer handlers get their data through IUnitOfWork and IRepository<TEntity, TIdentifierType> instead, which is where soft-delete filtering, tracking choices, and data-source routing are decided. This is what Rubric section 3 (Clean Architecture) assesses whether the dependency rule is enforced by the compiler rather than by convention; an access modifier is the cheapest available enforcement, and it is why a handler cannot accidentally grow a raw LINQ query against a DbSet.
    • +
    • Walkthrough
        +
      • Class declaration and primary constructor (:20-25): public abstract class ModuleApplicationDbContext(DbContextOptions options, IServiceProvider serviceProvider, IEntityConfigurationAssemblyProvider assemblyProvider, PhysicalDataSource physicalDataSource) : ApplicationDbContext(options, serviceProvider, assemblyProvider, physicalDataSource). Every parameter is passed straight through; the class captures none of them and overrides nothing. Note the untyped DbContextOptions rather than DbContextOptions<TContext>, which is what allows an arbitrary concrete subclass to supply its own typed options.
      • +
      • The seventeen entity sets (:28-78): Events (:28), Rooms (:31), EventSpeakers (:34), EventQuestionAnswers (:37), Sessions (:40), SessionSpeakers (:43), SessionQuestionAnswers (:46), SessionCategoryItems (:49), Speakers (:52), SpeakerCategoryItems (:55), Categories (:58), CategoryItems (:61), Questions (:64), Sponsors (:67), Partners (:70), Activities (:73), SessionAssets (:77). Read top to bottom they trace the module's aggregate map: the two roots that own schedules (Event, Session), their join tables to speakers and categories, the taxonomy pair (Category and CategoryItem), the feedback pair (Question plus the two answer tables), the three per-event additions (Sponsor, Partner, Activity), and the newest set, SessionAssets, appended at the end rather than grouped with the other session-scoped sets. Partners was inserted between Sponsors and Activities, next to the entity it mirrors, unlike SessionAssets.
      • +
      • What is deliberately absent: no OnModelCreating override, so nothing here competes with the base's assembly scan; no OnConfiguring, so provider selection stays with the concrete engine context; no SaveChanges override, so audit stamping, soft delete, and outbox dispatch remain the base's job.
      • +
      +
    • +
    • Why it's built this way: ADR-006 fixes one sealed context class per engine, shared across modules, rather than one context class per module, and the runtime honors that literally. The concrete context is Common's sealed class SQLServerDbContext, which derives from ApplicationDbContext directly (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/SQLServerDbContext.cs:15-20), and the Conference migrations project targets that same type through IDesignTimeDbContextFactory<SQLServerDbContext> (MMCA.ADC/Source/Hosting/MMCA.ADC.Migrations.SqlServer.Conference/DesignTimeSQLServerDbContextFactory.cs:12-15). Keeping the module-level class abstract, behavior-free, and additive means the per-engine story stated in ADR-018 (SQL Server today, Cosmos and SQLite as further engines) needs no per-module change: a new engine adds one sealed class in MMCA.Common, not seventeen DbSet declarations per module. This is what Rubric section 7 (Microservices Readiness) assesses whether a module could be lifted into its own host without a rewrite; the entity-set surface being module-scoped and internal is part of what makes that lift mechanical.
    • +
    • Where it's used: the seventeen sets correspond one to one with seventeen of the nineteen configurations in this chapter, including EventConfiguration, SessionConfiguration, SponsorConfiguration, PartnerConfiguration, ActivityConfiguration and SessionAssetConfiguration, and with the tables the Conference migrations project maintains for the ADC_Conference database. Application-layer access to those tables runs through IUnitOfWork, and the rows themselves are first written by ConferenceModuleDbSeeder.
    • +
    • Caveats / not-in-source: (1) Nothing derives from this class. A repository-wide search of MMCA.ADC for the identifier ModuleApplicationDbContext returns exactly three hits, the three sibling declarations in the Conference, Engagement, and Identity Infrastructure projects, and no subclass, no DI registration, and no consumer. The class compiles and is packaged, but it is not on the runtime path today: SQLServerDbContext bypasses it. Treat this section as documentation of the module's entity surface and of an extension point that is currently unexercised, not of a type in the request path. (2) Consequently the internal visibility of the sets protects a surface nothing currently reaches; the layering point it makes is real but presently theoretical for this class. (3) SessionAiScore and SpeakerQuestionAnswer have configurations in MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/ but no DbSet here; whether that is deliberate or an oversight is not determinable from source, since no code reads the DbSet list. (4) The three sibling ModuleApplicationDbContext classes share a name across three namespaces (Conference at :20, Engagement at MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/DbContexts/ModuleApplicationDbContext.cs:19, Identity at MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Infrastructure/Persistence/DbContexts/ModuleApplicationDbContext.cs:15); each has its own section in its own chapter, so check the namespace before assuming which one a search result refers to.
    • +

    CategoryItemConfiguration

    MMCA.ADC.Conference.Infrastructure · MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Categories · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Categories/CategoryItemConfiguration.cs:10 · Level 8 · class

    @@ -730,9 +782,9 @@

    CategoryItemConfiguration

  • Concept introduced, the per-entity configuration class and what the base already did. Every configuration in this folder is an internal sealed class deriving from EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> and overriding one method, Configure(EntityTypeBuilder<TEntity> builder), whose first statement is always base.Configure(builder) (:16). Knowing exactly what that base call does is what stops you re-declaring things by hand:

    • EntityTypeConfigurationSQLServer is a shim with no body (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfigurationSQLServer.cs:17-20). Its whole contribution is the [UseDataSource(DataSource.SQLServer)] attribute it carries (:16), an instance of UseDataSourceAttribute.
    • -
    • The real work is in EntityTypeConfiguration<TEntity, TIdentifierType>. Its Configure (EntityTypeConfiguration.cs:39) reads the attribute off GetType() and throws if it is missing (:43-46), then calls ApplyEngineConventions (:48). For DataSource.SQLServer that means ToTable(typeof(TEntity).Name, NamespaceConventions.GetModuleName(typeof(TEntity)) ?? "dbo"), so the table name comes from the CLR type and the schema comes from the module segment of the entity's namespace (:66), then HasKey(p => p.Id) (:67) and either ValueGeneratedOnAdd() or ValueGeneratedNever() depending on IsIdValueGenerated (:68-71).
    • +
    • The real work is in EntityTypeConfiguration<TEntity, TIdentifierType>. Its Configure (EntityTypeConfiguration.cs:37) reads the attribute off GetType() and throws if it is missing (:43-46), then calls ApplyEngineConventions (:48). For DataSource.SQLServer that means ToTable(typeof(TEntity).Name, NamespaceConventions.GetModuleName(typeof(TEntity)) ?? "dbo"), so the table name comes from the CLR type and the schema comes from the module segment of the entity's namespace (:66), then HasKey(p => p.Id) (:67) and either ValueGeneratedOnAdd() or ValueGeneratedNever() depending on IsIdValueGenerated (:68-71).
    • Below that, EntityTypeConfigurationBase<TEntity, TIdentifierType> does exactly one thing: builder.Ignore(nameof(AuditableAggregateRootEntity<>.DomainEvents)) for aggregate roots (EntityTypeConfigurationBase.cs:29-32), keeping the in-memory event list out of the schema.
    • -
    • What the base chain does not do is equally important. The soft-delete global query filter, the rowversion concurrency token and the soft-delete index convention are installed by the context, not by these classes: ApplicationDbContext adds the query filter at ApplicationDbContext.cs:422, marks the concurrency property at :469 and :473, and registers SoftDeleteUniqueIndexConvention at :296. So a configuration class in this folder is only ever about this entity's columns, relationships and indexes.
    • +
    • What the base chain does not do is equally important. The soft-delete global query filter, the rowversion concurrency token and the soft-delete index convention are installed by the context, not by these classes: ApplicationDbContext adds the query filter at ApplicationDbContext.cs:427, marks the concurrency property at :469 and :473, and registers SoftDeleteUniqueIndexConvention at :296. So a configuration class in this folder is only ever about this entity's columns, relationships and indexes.

    Because the engine is pinned entirely by the base type, re-pointing a Conference entity at SQLite or Cosmos is a base-class swap with no edit to the body of Configure: the domain entity, the handlers and everything above stay untouched. All seventeen Conference configurations use the SQL Server base, since ADC runs SQL Server only.

    [Rubric §8, Data Architecture] assesses whether persistence is designed deliberately (typed lengths, correct nullability, FK relationships, purposeful indexes) rather than left to convention defaults: this family is where all of that lives for the Conference database. [Rubric §3, Clean Architecture] assesses dependency direction: EF mapping is confined to Infrastructure, and the domain entities carry zero EF attributes, so the domain layer stays framework-free.

    @@ -745,7 +797,7 @@

    CategoryItemConfiguration

  • base.Configure(builder) (:16): table CategoryItem, schema Conference, key on Id, value generation per the entity.
  • Column facets (:18-23): Name is HasMaxLength(CategoryInvariants.CategoryItemNameMaxLength).IsRequired(); Sort is IsRequired().
  • Parent relationship (:25-28): HasOne(p => p.Category).WithMany(p => p.CategoryItems).HasForeignKey(p => p.CategoryId).IsRequired(). Both ends of the navigation are named, so EF maps the aggregate's real collection property rather than inventing a shadow one.
  • -
  • Composite unique index (:30-31): HasIndex(p => new { p.CategoryId, p.Name }).IsUnique(). Note there is no explicit filter call here, and none is needed: SoftDeleteUniqueIndexConvention runs at model finalizing and adds the IsDeleted = 0 predicate to every unique index on a soft-deletable entity that does not already declare a filter (SoftDeleteUniqueIndexConvention.cs:51-55). The shipped index is therefore filtered, so soft-deleting a category item frees its (CategoryId, Name) slot for re-use.
  • +
  • Composite unique index (:30-31): HasIndex(p => new { p.CategoryId, p.Name }).IsUnique(). Note there is no explicit filter call here, and none is needed: SoftDeleteUniqueIndexConvention runs at model finalizing and adds the IsDeleted = 0 predicate to every unique index on a soft-deletable entity that does not already declare a filter (SoftDeleteUniqueIndexConvention.cs:52-56). The shipped index is therefore filtered, so soft-deleting a category item frees its (CategoryId, Name) slot for re-use.
  • Why it's built this way: one small single-responsibility class per entity keeps the domain a pure POCO set (the Clean Architecture dependency rule) and lets EF discover configurations by assembly scan, so adding an entity is "add a config class" with no central registration edit.

    @@ -773,121 +825,24 @@

    ConferenceCategoryConfiguration

  • Caveats / not-in-source: the doc comment (:10-11) cites a Catalog-module Category as the collision being avoided. Catalog is a MMCA.Store module, not an ADC one, so within this repo nothing would actually collide; treat the comment as rationale carried over from the shared framework vocabulary.

  • -

    EventConfiguration

    -
    -

    MMCA.ADC.Conference.Infrastructure · MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Events/EventConfiguration.cs:12 · Level 8 · class

    -
    -
      -
    • What it is: the persistence map for Event, the top aggregate of the Conference module (the conference itself: dates, venue, publication state, Sessionize linkage).
    • -
    • Depends on: first-party: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, Event, EventInvariants, QuestionModerationDefault, NullableEmailValueConverter. External: Microsoft.EntityFrameworkCore.
    • -
    • Concept reinforced, the filtered non-unique index. HasIndex(p => p.SessionizeCode).HasFilter("[SessionizeCode] IS NOT NULL") (:42-43) is filtered but not unique. A filtered index only covers the rows matching its predicate, so this one indexes just the events that carry a Sessionize code, which is the population the import path looks up by. It deliberately does not forbid two events sharing a code, and it costs nothing for the events with a null code. [Rubric §12, Performance and Scalability] assesses whether indexes are chosen for the actual query shape rather than sprayed across columns: this is a narrow index sized to one lookup.
    • -
    • Concept reinforced, a value object mapped by a converter rather than an owned type. OrganizerContactEmail is an Email value object on the entity, but the column is still the same nullable nvarchar it was when the property was a string: HasConversion(new NullableEmailValueConverter()) (:61) turns the object into its text on the way down and back on the way up. The comment above it (:57-59) states the consequence directly: converter, not OwnsOne, so the store type and length are unchanged and no migration is needed. That is the cheap half of ADR-068, a type-safe domain property bought without touching the schema. [Rubric §8, Data Architecture] assesses whether the storage shape is chosen independently of the domain shape.
    • -
    • Walkthrough
        -
      • Required core (:20-22, :28-36): Name (EventInvariants.NameMaxLength), StartDate, EndDate, and TimeZone (EventInvariants.TimeZoneMaxLength). Storing the IANA time-zone id as a column rather than baking a UTC offset into the dates is what lets the schedule render correctly across DST.
      • -
      • Optional descriptive, venue and link columns (:24-26, :45-71): Description, VenueAddress, VenueMapUrl, WiFiInfo, OrganizerContactEmail, SponsorshipPacketUrl and TicketingUrl, each IsRequired(false) with its own invariant-sourced max length, and OrganizerContactEmail additionally carrying the converter above (:60-63).
      • -
      • Sessionize linkage (:38-43, :80-84): SessionizeCode optional plus the filtered index above; LastSessionizeRefreshOn / LastSessionizeRefreshBy are optional audit-style columns recording the last import run. [Rubric §13, Observability and Operability] assesses whether the system records the provenance of imported data: these two columns answer "when was this event last synced, and by whom" from the row itself.
      • -
      • State flags (:73-78): IsPublished required; QuestionModerationDefault required, with the comment (:76) noting it is stored as an int through EF's default enum conversion and that Pending (0) is the safe default per BR-233. There is no HasConversion call, EF's default enum-to-int mapping is used as-is, and the enum really does declare Pending = 0 (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Events/Live/QuestionModerationDefault.cs:10), so the safe default is also the zero value in the database.
      • -
      • Navigation access mode (:86-97): Rooms, EventSpeakers and EventQuestionAnswers are each set to UsePropertyAccessMode(PropertyAccessMode.Field) because every one of them is a getter over a private list returned as AsReadOnly(). The comment (:86-91) is explicit that convention already infers field access here and that stating it is insurance: a later change to the property cannot silently turn materialization into a no-op. It is an access-mode declaration only, the relationships themselves stay configured from the child side.
      • -
      -
    • -
    • Why it's built this way: everything the organizer may not know at creation time is nullable, so an event can be created early and enriched later without a two-phase workflow; only the four facts that make an event an event are required.
    • -
    • Where it's used: Event is the FK target of RoomConfiguration, SessionConfiguration, EventSpeakerConfiguration, EventQuestionAnswerConfiguration, ActivityConfiguration and SponsorConfiguration.
    • -
    -
    -

    EventQuestionAnswerConfiguration

    -
    -

    MMCA.ADC.Conference.Infrastructure · MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Events · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Events/EventQuestionAnswerConfiguration.cs:11 · Level 8 · class

    -
    -
      -
    • What it is: the persistence map for EventQuestionAnswer, one attendee's answer to one event-scoped Question.
    • -
    • Depends on: first-party: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, EventQuestionAnswer, Event, EventInvariants, IndexBuilderExtensions (HasSoftDeleteFilter). External: Microsoft.EntityFrameworkCore.Metadata.Builders.
    • -
    • Concept introduced, HasSoftDeleteFilter() and the database as the concurrency backstop.
        -
      • HasSoftDeleteFilter() (IndexBuilderExtensions.cs:52-66) replaces a hand-typed HasFilter("[IsDeleted] = 0"). It builds the predicate through SoftDeleteFilterSql from the live model (:56), so a renamed soft-delete column follows automatically and the identifier quoting comes from the engine instead of a SQL-Server-shaped literal. Its engine parameter defaults to DataSource.SQLServer (:51), which is exactly what the …SQLServer base already implies. On a unique index the call is technically redundant with SoftDeleteUniqueIndexConvention, which would apply the same predicate at model finalizing; writing it explicitly keeps the intent readable at the call site, and because the convention skips any index that already declares a filter (SoftDeleteUniqueIndexConvention.cs:53) the two can never disagree. On a non-unique index like the EventId lookup here, the convention deliberately does nothing, so the explicit call is the only way to get the filter.
      • -
      • The (EventId, QuestionId, CreatedBy) unique index (:42-44) is a race backstop, and the comment (:38-41) is unusually candid about why: the application-level upsert only inspects the in-memory collection, so two concurrent submits can both take the create branch. The database refuses the second one, and the shared DbUpdateException handler turns the violation into a 409 for the client. [Rubric §8, Data Architecture] assesses whether invariants that matter are enforced where they cannot be raced, and [Rubric §15, Best Practices and Code Quality] assesses whether known limitations are documented at the point of the compensating control rather than left for the next reader to discover.
      • -
      -
    • -
    • Walkthrough: required EventId and QuestionId scalars (:19-23); required AnswerValue at EventInvariants.AnswerValueMaxLength (:25-27); required parent relationship HasOne(p => p.Event).WithMany(p => p.EventQuestionAnswers).HasForeignKey(p => p.EventId) (:29-32); soft-delete-filtered lookup index on EventId (:35-36); the BR-123 filtered unique index (:42-44).
    • -
    • Why it's built this way: CreatedBy is part of the uniqueness tuple, so "one live answer per question" is scoped per author, not globally, which is what a per-attendee feedback form needs.
    • -
    • Where it's used: written by the Conference event-feedback command handlers; read by the feedback queries. Compare its sibling SessionQuestionAnswerConfiguration, which carries the same BR-123 index but treats its parent lookup index differently for a specific reason.
    • -
    -
    -

    EventSpeakerConfiguration

    -
    -

    MMCA.ADC.Conference.Infrastructure · MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Events · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Events/EventSpeakerConfiguration.cs:11 · Level 8 · class

    -
    -
      -
    • What it is: the persistence map for the EventSpeaker join entity, which records that a speaker is part of an event's line-up.
    • -
    • Depends on: first-party: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, EventSpeaker, Event, IndexBuilderExtensions. External: Microsoft.EntityFrameworkCore.Metadata.Builders.
    • -
    • Concept introduced, the join-entity template. Four configurations in this folder are structurally identical, and this is the first: both FK scalars required, one HasOne(...).WithMany(...) relationship to the owning aggregate only (the side whose collection navigation the join belongs to), and a soft-delete-filtered composite unique index on the two FKs. The other side of the pair is deliberately not configured as a relationship, which keeps the entity a one-way child of a single aggregate and matches the DDD rule that an aggregate owns its children. [Rubric §4, DDD] assesses aggregate boundary discipline; [Rubric §8, Data Architecture] assesses the uniqueness guarantee. - The soft-delete filter on the unique index is what makes a delete-then-re-add cycle legal: a plain unique index would let a soft-deleted association keep occupying its slot forever, so an organizer could never re-add a speaker they had removed.
    • -
    • Walkthrough: required EventId (:19-20) and SpeakerId (:22-23); HasOne(p => p.Event).WithMany(p => p.EventSpeakers).HasForeignKey(p => p.EventId).IsRequired() (:25-28); HasIndex(p => new { p.EventId, p.SpeakerId }).IsUnique().HasSoftDeleteFilter() (:30-32).
    • -
    • Where it's used: the same template appears in SessionSpeakerConfiguration, SessionCategoryItemConfiguration and SpeakerCategoryItemConfiguration.
    • -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    TypeFile:LineOwning aggregateUnique index
    EventSpeakerConfigurationMMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Events/EventSpeakerConfiguration.cs:11Event(EventId, SpeakerId)
    SessionSpeakerConfigurationMMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Sessions/SessionSpeakerConfiguration.cs:11Session(SessionId, SpeakerId)
    SessionCategoryItemConfigurationMMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Sessions/SessionCategoryItemConfiguration.cs:11Session(SessionId, CategoryItemId)
    SpeakerCategoryItemConfigurationMMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Speakers/SpeakerCategoryItemConfiguration.cs:11Speaker(SpeakerId, CategoryItemId)
    -
    -

    QuestionConfiguration

    -
    -

    MMCA.ADC.Conference.Infrastructure · MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Questions · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Questions/QuestionConfiguration.cs:10 · Level 8 · class

    -
    -
      -
    • What it is: the persistence map for Question, the definition of a feedback question (its text, what it attaches to, how it renders, and where it came from).
    • -
    • Depends on: first-party: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, Question, QuestionInvariants. External: Microsoft.EntityFrameworkCore.Metadata.Builders.
    • -
    • Concept: the shared shape is taught under CategoryItemConfiguration. What is worth noticing here is that this is the flattest configuration in the folder: six required properties, no relationships and no indexes at all.
    • -
    • Walkthrough (:18-38): all six columns are IsRequired(). QuestionText, QuestionEntity, QuestionType and QuestionSource each take their length from QuestionInvariants; Sort and IsRequired (the boolean, not the fluent call) are plain required scalars. QuestionEntity and QuestionType are declared as string on the entity (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Questions/Question.cs:20, :23), not enums, so adding a question type or a new attachable entity needs no migration and no enum-to-string conversion.
    • -
    • Why it's built this way: questions are attached to events, sessions and speakers by the three …QuestionAnswer entities, and those answers carry a plain QuestionId scalar rather than a navigation, so Question itself needs no relationship configuration. Modelling the discriminators as strings keeps the question catalogue extensible from data rather than from code.
    • -
    • Where it's used: referenced by QuestionId from EventQuestionAnswerConfiguration, SessionQuestionAnswerConfiguration and SpeakerQuestionAnswerConfiguration.
    • -
    -
    -

    RoomConfiguration

    +

    ActivityConfiguration

    -

    MMCA.ADC.Conference.Infrastructure · MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Events · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Events/RoomConfiguration.cs:11 · Level 8 · class

    +

    MMCA.ADC.Conference.Infrastructure · MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Activities · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Activities/ActivityConfiguration.cs:11 · Level 9 · class

      -
    • What it is: the persistence map for Room, a physical room belonging to an Event.
    • -
    • Depends on: first-party: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, Room, Event, EventInvariants, IndexBuilderExtensions. External: Microsoft.EntityFrameworkCore.Metadata.Builders.
    • -
    • Concept introduced, re-declaring an index EF would otherwise drop. The explicit builder.HasIndex(p => p.EventId) (:48) looks redundant next to the (EventId, Name) composite below it, and the comment (:46-47) says exactly why it is not: EF removes the conventional foreign-key index as redundant once a composite index leads with the same column, but the composite is filtered, and the plain FK lookups still want an unfiltered index. This is a good example of a mapping decision that only makes sense once you know EF's own de-duplication rule; without the comment the line reads as a mistake. [Rubric §12, Performance and Scalability] assesses whether index choices survive framework conventions rather than being silently optimized away.
    • +
    • What it is: the persistence map for Activity, a social or networking item attached to an event (a pre-conference party, a coffee connect, an after-party) that is deliberately not a session: no room, no speakers, and often an external venue carried on the row itself.
    • +
    • Depends on: first-party: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, Activity, ActivityInvariants, Event, IndexBuilderExtensions. External: Microsoft.EntityFrameworkCore.Metadata.Builders.
    • +
    • Concept introduced, indexing for the sort, not just for the filter. The second index (:63-64) is HasIndex(p => new { p.EventId, p.StartTime, p.SortOrder }).HasSoftDeleteFilter(): non-unique, filtered, and composed in exactly the order the public agenda page consumes. The comment (:61-62) states the intent: the page filters by one event and orders by start time then sort order, so the composite serves the browse query directly instead of the database pulling the event slice and sorting it afterwards. This is the one place in the folder where an index's column order is chosen for an ORDER BY rather than for a lookup predicate, and it is worth reading alongside the narrower lookup index above it (:58-59, plain EventId with the same soft-delete filter). Contrast RoomConfiguration, whose paired indexes exist because the composite is filtered and the FK lookup wanted an unfiltered one; here both carry the filter, because every read of this table goes through the global query filter. [Rubric §12, Performance and Scalability] assesses whether index shape follows the queries that actually run.
    • +
    • Concept reinforced, event-local wall-clock time. StartTime and EndTime are required plain date-times with no offset column (:29-33), and the comment (:27-28) is explicit that this mirrors Session.StartsAt/EndsAt: the IANA zone lives once on the owning Event (see EventConfiguration) and is never repeated per row. The domain entity says the same thing at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Activities/Activity.cs:28-33. Storing one zone for the whole programme is what keeps a schedule internally consistent when an activity is moved. [Rubric §15, Best Practices & Code Quality] assesses single-definition-point discipline, and this is the time-zone instance of it.
    • Walkthrough
        -
      • Required (:19-24): Name at EventInvariants.RoomNameMaxLength, and Sort.
      • -
      • Optional (:26-39): Capacity (a nullable scalar with no length), plus Floor, Location and AccessibilityInfo, each with an invariant-sourced max length. AccessibilityInfo being a first-class room column, not a note bolted onto the description, is the schema-level half of ADC's accessibility commitment. [Rubric §21, Accessibility] assesses whether accessibility is designed into the data rather than added at the view.
      • -
      • Parent relationship (:41-44): required HasOne(p => p.Event).WithMany(p => p.Rooms).HasForeignKey(p => p.EventId).
      • -
      • Indexes (:48, :52-54): the re-declared plain EventId index, then HasIndex(p => new { p.EventId, p.Name }).IsUnique().HasSoftDeleteFilter(). The comment (:50-51) states its purpose plainly: it backstops the aggregate's duplicate-room-name invariant, and the soft-delete filter means a deleted room never blocks reusing its name.
      • +
      • Required (:19-21, :29-33, :47-51): Name at ActivityInvariants.NameMaxLength (200, ActivityInvariants.cs:13), StartTime, EndTime, SortOrder, and the EventId scalar.
      • +
      • Optional (:23-25, :35-45): Description (ActivityInvariants.DescriptionMaxLength), VenueName, VenueAddress and VenueUrl, each IsRequired(false) with its own invariant-sourced max length. An absent VenueName is a meaningful value rather than missing data: the domain invariant says so (ActivityInvariants.cs:38-40), and the public page falls back to the event venue.
      • +
      • Event relationship (:53-56): required HasOne(p => p.Event).WithMany().HasForeignKey(p => p.EventId), with the parameterless WithMany(), so Event exposes no activities collection. The same one-sided-navigation choice is made in SessionConfiguration: activities are read by explicit event-scoped queries, not by walking the event aggregate.
      • +
      • Indexes (:58-59, :63-64): the filtered EventId lookup, then the filtered (EventId, StartTime, SortOrder) browse index described above. Neither is unique, so SoftDeleteUniqueIndexConvention would not have touched either one, which is why both spell out HasSoftDeleteFilter().
    • -
    • Why it's built this way: the domain already refuses a duplicate room name inside the Event aggregate; the filtered unique index is the database-side guarantee for the concurrent case the in-memory check cannot see, the same defence-in-depth reasoning as BR-123 in EventQuestionAnswerConfiguration.
    • -
    • Where it's used: Room is the optional FK target of SessionConfiguration, which restricts deletes against it.
    • +
    • Why it's built this way: an activity is a first-class row rather than a flavour of session because it has a different shape (its own venue, no room, no speakers), and separating it keeps the session table free of columns that only apply to parties. [Rubric §4, DDD] assesses whether the model names distinct concepts distinctly instead of overloading one entity with a type discriminator.
    • +
    • Where it's used: exposed as DbSet<Activity> Activities on ModuleApplicationDbContext (ModuleApplicationDbContext.cs:75); read by the public agenda queries and written by the organizer-facing activity commands.

    SessionAiScoreConfiguration

    @@ -934,7 +889,7 @@

    SpeakerConfiguration

    [Rubric §4, DDD] assesses whether value objects survive the trip to storage instead of being flattened into primitives at the boundary. [Rubric §15, Best Practices & Code Quality] applies too: the conversion logic lives once in MMCA.Common, so every entity with an email gets identical semantics.

    -
  • Concept reinforced, the partially filtered unique index. HasIndex(p => p.LinkedUserId).IsUnique().HasFilter("[LinkedUserId] IS NOT NULL") (:63-65) enforces the one-to-one User to Speaker link only among speakers that have one. Without the predicate, SQL Server would treat multiple NULLs as duplicates and allow at most one unlinked speaker, which would be nonsense. Note this one is a hand-written literal rather than HasSoftDeleteFilter(), because the predicate is about LinkedUserId, not about soft delete; the soft-delete clause is not added on top, because SoftDeleteUniqueIndexConvention skips any index that already declares a filter (SoftDeleteUniqueIndexConvention.cs:53). A soft-deleted linked speaker therefore keeps holding its LinkedUserId slot.

    +
  • Concept reinforced, the partially filtered unique index. HasIndex(p => p.LinkedUserId).IsUnique().HasFilter("[LinkedUserId] IS NOT NULL") (:63-65) enforces the one-to-one User to Speaker link only among speakers that have one. Without the predicate, SQL Server would treat multiple NULLs as duplicates and allow at most one unlinked speaker, which would be nonsense. Note this one is a hand-written literal rather than HasSoftDeleteFilter(), because the predicate is about LinkedUserId, not about soft delete; the soft-delete clause is not added on top, because SoftDeleteUniqueIndexConvention skips any index that already declares a filter (SoftDeleteUniqueIndexConvention.cs:54). A soft-deleted linked speaker therefore keeps holding its LinkedUserId slot.

  • Walkthrough

      @@ -1042,59 +997,6 @@

      SessionSpeakerConfiguration

    • Where it's used: joined by the session detail and speaker detail read paths to resolve a session's presenters.

    -

    ConferenceModuleDbSeeder

    -
    -

    MMCA.ADC.Conference.Infrastructure · MMCA.ADC.Conference.Infrastructure.Persistence.DbContexts.Seeding · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/DbContexts/Seeding/ConferenceModuleDbSeeder.cs:27 · Level 9 · class

    -
    -
      -
    • What it is: the Conference module's startup data seeder. It puts the two real conference editions and the ten feedback questions into a fresh database on every boot, and, only when its includeSampleData flag is set, a small deterministic browse fixture on top (two speakers, two sessions, the speaker links, four sponsors, four partners, three social activities). Every write goes through a domain factory and the unit of work, and every insert is guarded by an existence check, so running it against an already-seeded database is a no-op.
    • -
    • Depends on: first-party: DbSeeder (base, :27), IUnitOfWork (:27, :49) and the IRepository<TEntity, TIdentifierType> handles it hands out (:72, :147, :195, :249, :308, :362, :414, :422, :470), the domain factories Event (:87, :122), Question (:175), Speaker (:215), Session (:279), Sponsor (:387), Partner (:447) and Activity (:516), the join entities EventSpeaker and SessionSpeaker (created indirectly at :344, :347, :573), the id-range constants on QuestionInvariants (:171) and SessionInvariants (:257-258), and SponsorTier (:366). External: BCL only (DateOnly, TimeOnly, TimeSpan, DateTimeKind).
    • -
    • Concept introduced, seeding through the domain rather than through SQL. A seeder in this codebase never writes rows. It calls the same static factory an HTTP command handler would call, checks the returned Result wrapper, hands the entity to a repository, and lets IUnitOfWork commit. Event.Create(...) at :87-99 is the identical entry point the organizer's create-event use case takes, so seeded data satisfies exactly the invariants that user-created data satisfies: there is no second, looser definition of a valid event hiding in the seeder. The consequence worth internalizing is that a seed insert is a full domain write with all its side effects: eventResult.Value!.Publish() at :104 flips IsPublished and raises an EventChanged domain event (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/Event.cs:310-312), so the SaveChangesAsync at :107 writes an outbox row alongside the entity row. Startup seeding therefore feeds the same dual-dispatch pipeline as runtime traffic (ADR-003), it does not bypass it. This is what Rubric section 4 (DDD) assesses whether the domain model is the single place invariants live; routing seed data through the factories is what keeps that true at the one moment it is most tempting to cheat.
    • -
    • Concept introduced, idempotency that respects soft delete. Every existence probe in this file passes ignoreQueryFilters: true (:81, :116, :151, :209, :270, :381, :441, :508), which turns off the global soft-delete filter for that one query. The comment at :74-78 gives both halves of the reason. First, a deleted seed row is a decision someone made, not a gap to refill, so the seeder must see it and stand down. Second, the fixed-id rows (questions at :175, sample sessions at :257-258) keep their primary keys when soft-deleted, so a filtered check would report "missing", re-insert the same id, and take startup down on a primary-key violation. The ExistsAsync(Expression<Func<TEntity, bool>>, bool ignoreQueryFilters, CancellationToken) overload that makes this possible is on the shared repository contract (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:62-65). ConferenceModuleDbSeederTests pins the behavior mechanically: its Moq setups match true for that parameter only (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Infrastructure.Tests/Seeding/ConferenceModuleDbSeederTests.cs:103-113), so a future edit that drops the flag falls through to Moq's default false and the "skips when exists" tests go red (the reasoning is spelled out in the comment at :99-102 of that test file). This is what Rubric section 17 (DevOps) assesses whether startup is repeatable and safe to re-run; this is the pattern that makes "boot the app twice" a non-event.
    • -
    • Concept introduced, environment-gated fixture data. The class takes bool includeSampleData = false (:27), stores it (:50), and branches on it in SeedAsync (:59-67). The default is off, and the only thing that turns it on is ConferenceModuleSeeder reading configuration.GetValue<bool>("Seeding:IncludeSampleConferenceData") (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/ConferenceModuleSeeder.cs:26). GetValue<bool> on an absent key yields false, so a production host that never sets the key gets the real events and questions and nothing else. The one place the key is set is the local Aspire AppHost, .WithEnvironment("Seeding__IncludeSampleConferenceData", "true") on the Conference service (MMCA.ADC/Source/Hosting/MMCA.ADC.AppHost/Program.cs:211, rationale at :207-209). E2E CI inherits it by launching that same AppHost (MMCA.ADC/.github/workflows/e2e.yml:219), which is why the two public-browse tests can assume rows exist (MMCA.ADC/Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/Conference/Public/PublicBrowseTests.cs:93, :102). This is what Rubric section 11 (Security) assesses whether non-production affordances are structurally unable to reach production: here the gate is a default-false configuration read in the composition root, not a runtime environment sniff inside the seeder.
    • -
    • Concept introduced, reserved manual id ranges. Conference session ids are app-assigned rather than database-generated because the integer primary key is the Sessionize id when a session arrives through the import (comment at :251-254). Sample sessions have no Sessionize id, so they take explicit ids from the top of the integer space: SessionInvariants.ManualIdRangeStart is 999_999_000 (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/SessionInvariants.cs:44) and the two fixtures take that value and that value plus one (:257-258). Questions do the same from QuestionInvariants.ManualIdRangeStart, also 999_999_000 (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Questions/QuestionInvariants.cs:40), incremented per question by id: nextId++ (:171, :175). The range sits above any id an upstream system will mint, so seed rows and imported rows can never collide, and the same constants are what the organizer-facing create handlers continue from. This is what Rubric section 8 (Data Architecture) assesses whether key strategy is deliberate; reserving a high range is the cheap alternative to a separate identity column or a synthetic-vs-natural key split.
    • -
    • Walkthrough
        -
      • Primary constructor and fields (:27, :49-50): ConferenceModuleDbSeeder(IUnitOfWork unitOfWork, bool includeSampleData = false) : DbSeeder(). The unit of work is null-guarded into _unitOfWork at :49 (the one behavior a unit test asserts directly, ConferenceModuleDbSeederTests.cs:73-75); the flag is copied to _includeSampleData at :50. The base DbSeeder contributes the SeedAsync abstract member and a GetId<TIdentifier>(int) helper for int-or-Guid key strategies (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/Seeding/DbSeeder.cs:20-39), which this seeder does not use: the Conference identifier aliases are int, so literal ids pass straight through.
      • -
      • Literal constants (:29-47): the shared venue address and embedded map URL, three placeholder URLs for sample sponsors, partners and activities, and the two published sponsorship-packet URLs. The six URL constants each carry a SuppressMessage for Sonar S1075 (URIs should not be hardcoded) with a per-constant justification, which is how this repo records "the literal is the data" rather than switching the analyzer off.
      • -
      • SeedAsync (:53-67): the whole contract in fifteen lines. Three unconditional steps (:55-57) then a single if (_includeSampleData) block of six (:61-66). Ordering is a real dependency chain: sessions need the events, the speaker links need both the speakers and the sessions, sponsors and partners need the events, and partners are seeded right after sponsors and before activities.
      • -
      • SeedCloudAiConferenceEventAsync (:70-108): probes for the event by name, tolerating the pre-rename spelling so a database seeded before "2026" was prefixed stays idempotent (:79-82). On a miss it builds the 2026 Atlanta Cloud + AI Conference for 2026-05-30 in America/New_York with Sessionize code z1ecmzux (:87-99), returns quietly if the factory fails (:101-102), publishes (:104), adds and saves (:106-107).
      • -
      • SeedDevelopersConferenceEventAsync (:110-143): the same shape for the 2026 Atlanta Developers Conference on 2026-10-17, Sessionize code sf1nopko (:122-134). It needs no legacy-name alternative because that event was never renamed.
      • -
      • SeedQuestionsAsync (:145-191): one probe on the sentinel question "Rate the Session" with QuestionSource == "User" (:149-152) decides the whole set, then ten tuples (:157-169) become ten Question.Create calls with sequential reserved ids (:173-188) and a single save (:190). Six are session-scoped (five ratings plus a free-text comment), four are event-scoped. The Times.Exactly(10) assertion in the unit tests (ConferenceModuleDbSeederTests.cs:28-30) is what keeps that count honest.
      • -
      • SeedSpeakersAsync (:193-234): two sample speakers, Ada Lovelace and Alan Turing on example.com addresses (:197-201), each probed by first and last name (:207-210) and skipped individually with continue rather than aborting the batch (:212-213). An added flag means the save (:233) only runs when something actually changed. That flag pattern repeats in every sample-data method.
      • -
      • SeedSessionsAsync (:236-304): resolves both events through the shared helper (:246-247), then seeds one session per event (Ada's keynote on the Cloud + AI day, Alan's Azure talk on the Developers day, :255-259) so both the auto-filtered public pages and the organizer list's event filter have data for either selection. Start time is computed from the owning event's own date, sessionEvent.StartDate.ToDateTime(new TimeOnly(13, 0), DateTimeKind.Utc), with a comment recording that 13:00 UTC is 09:00 Eastern for both dates; the session runs one hour. Idempotency here is by title, which is also why a comment notes that a database seeded before the one-session-per-event split keeps its old shape: the title check never moves an existing row.
      • -
      • SeedSampleEventLinksAsync (:306-330): loads the two sample speakers untracked-free (asTracking: false, :310-315), bails if either is missing (:317-320), then calls the two link helpers and ORs their results (:325-326) before one save (:328-329). The comment above :325 explains why both link paths are populated: the speakers-by-event filter has a direct branch (through EventSpeaker) and a transitive branch (through SessionSpeaker), and seeding both exercises both in dev and CI.
      • -
      • LinkSampleEventSpeakersAsync (:332-351) and LinkSampleSessionSpeakersAsync (:558-577): both re-fetch with asTracking: true and an includes list so the aggregate's child collection is loaded before mutation (:337 includes nameof(Event.EventSpeakers); :563 includes nameof(Session.SessionSpeakers)). Idempotency is delegated to the domain: AddEventSpeaker and AddSessionSpeaker return a failed Result on an existing non-deleted link, so .IsSuccess doubles as the "did anything change" signal (:344, :347, :573).
      • -
      • SeedSponsorsAsync (:353-410): four sample sponsors spanning both events and all four SponsorTier values, two of them exhibitors with booth numbers (:366-369), each probed by name (:379-382) and built through Sponsor.Create (:387-399). The mapping these rows land in is SponsorConfiguration.
      • -
      • SeedPartnersAsync (:412-466): four sample partners spanning both events, three attached to the Developers edition, Community and Community and Special, and one attached to the Cloud + AI edition, Community, so the comment (:417-419) explains the landing-page band renders fully in dev and CI on the Developers side while the organizer list still has a row whichever event its filter defaults to. Each is probed by name (:437-440) and built through Partner.Create (:447-455). The mapping these rows land in is PartnerConfiguration.
      • -
      • SeedActivitiesAsync (:468-536): three social-programme entries expressed as event-local wall-clock offsets rather than absolute timestamps, a pre-conference party the evening before (DayOffset of -1), a morning coffee, and an after-party. Each is anchored on its own event's start date, activityEvent.StartDate.AddDays(dayOffset).ToDateTime(TimeOnly.MinValue), and the venue URL is only supplied when a venue name exists.
      • -
      • GetSampleEventsAsync (:539-556): the one static helper. A single GetAllAsync fetches both events (again tolerating the pre-rename Cloud + AI name, :544) with asTracking: true so callers can mutate them, then splits the result by name. Callers pass the includes they need, which is why the same helper serves both the no-include sponsor and partner paths and the EventSpeakers-include link path.
      • -
      -
    • -
    • Why it's built this way: this is the file that decides what a freshly provisioned Conference database contains, so it is written to be safe under three conditions that are easy to get wrong. Re-running (guarded by existence checks that see through soft delete), running against production (fixture data behind a default-false configuration key), and running against a database whose real content arrives from elsewhere (reserved id ranges that cannot collide with Sessionize ids). Doing the writes through domain factories and IUnitOfWork rather than raw SQL or EF HasData also means seeding participates in the audit stamping, soft-delete defaults, and outbox dispatch that ApplicationDbContext applies to every save, which matters because per-service databases (ADR-006) each get their own seeding pass at their own host's startup.
    • -
    • Where it's used: instantiated by ConferenceModuleSeeder, the module's IModuleSeeder adapter, which resolves IUnitOfWork and IConfiguration from the scope and constructs it with the gate flag (ConferenceModuleSeeder.cs:21-29). That adapter is discovered by reflection in ModuleLoader (MMCA.Common/Source/Core/MMCA.Common.Application/Modules/ModuleLoader.cs:92-93) and invoked in registration order by SeedAllAsync (ModuleLoader.cs:255-261), which the host calls from DatabaseInitializationExtensions after schema initialization (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/DatabaseInitializationExtensions.cs:110). Its output is what the public browse pages in Group 21 render in dev and CI, and what the E2E feedback and share workflows target by the reserved session id.
    • -
    • Caveats / not-in-source: (1) Failures are swallowed. A failed factory Result produces a bare return or continue (:101-102, :136-137, :184-185, :225-226, :295-296, :401-402, :457-458, :528-529) with no logging: the class takes no logger, so a seed that silently does nothing leaves no trace beyond the missing rows. The Publish() calls at :104 and :139 likewise discard their Result. (2) The class is public and not sealed, unlike most types in this Infrastructure assembly. (3) Idempotency keys are names and titles, not database constraints: nothing stops a second row with the same event name, sponsor name, partner name, or session title from being created through the normal command paths, after which the seeder's probe would simply keep finding a match. (4) A comment in SeedSessionsAsync documents a real migration gap: databases seeded before the sessions were split across the two events keep the old shape and there is no fix-up path in this file, only the advice to reset the local container volume. (5) The sample-data insert order is not transactional across methods. Each method saves independently (:233, :303, :329, :409, :465, :536), so a crash partway through leaves a partially seeded fixture, which the existence checks will then complete on the next boot. (6) ConferenceModuleDbSeederTests covers only the always-on path with includeSampleData left at its default (ConferenceModuleDbSeederTests.cs:115); the six sample-data methods, now including SeedPartnersAsync, have no unit-test coverage in that file and are exercised indirectly through E2E.
    • -
    -
    -

    ActivityConfiguration

    -
    -

    MMCA.ADC.Conference.Infrastructure · MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Activities · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Activities/ActivityConfiguration.cs:11 · Level 9 · class

    -
    -
      -
    • What it is: the persistence map for Activity, a social or networking item attached to an event (a pre-conference party, a coffee connect, an after-party) that is deliberately not a session: no room, no speakers, and often an external venue carried on the row itself.
    • -
    • Depends on: first-party: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, Activity, ActivityInvariants, Event, IndexBuilderExtensions. External: Microsoft.EntityFrameworkCore.Metadata.Builders.
    • -
    • Concept introduced, indexing for the sort, not just for the filter. The second index (:63-64) is HasIndex(p => new { p.EventId, p.StartTime, p.SortOrder }).HasSoftDeleteFilter(): non-unique, filtered, and composed in exactly the order the public agenda page consumes. The comment (:61-62) states the intent: the page filters by one event and orders by start time then sort order, so the composite serves the browse query directly instead of the database pulling the event slice and sorting it afterwards. This is the one place in the folder where an index's column order is chosen for an ORDER BY rather than for a lookup predicate, and it is worth reading alongside the narrower lookup index above it (:58-59, plain EventId with the same soft-delete filter). Contrast RoomConfiguration, whose paired indexes exist because the composite is filtered and the FK lookup wanted an unfiltered one; here both carry the filter, because every read of this table goes through the global query filter. [Rubric §12, Performance and Scalability] assesses whether index shape follows the queries that actually run.
    • -
    • Concept reinforced, event-local wall-clock time. StartTime and EndTime are required plain date-times with no offset column (:29-33), and the comment (:27-28) is explicit that this mirrors Session.StartsAt/EndsAt: the IANA zone lives once on the owning Event (see EventConfiguration) and is never repeated per row. The domain entity says the same thing at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Activities/Activity.cs:28-33. Storing one zone for the whole programme is what keeps a schedule internally consistent when an activity is moved. [Rubric §15, Best Practices & Code Quality] assesses single-definition-point discipline, and this is the time-zone instance of it.
    • -
    • Walkthrough
        -
      • Required (:19-21, :29-33, :47-51): Name at ActivityInvariants.NameMaxLength (200, ActivityInvariants.cs:13), StartTime, EndTime, SortOrder, and the EventId scalar.
      • -
      • Optional (:23-25, :35-45): Description (ActivityInvariants.DescriptionMaxLength), VenueName, VenueAddress and VenueUrl, each IsRequired(false) with its own invariant-sourced max length. An absent VenueName is a meaningful value rather than missing data: the domain invariant says so (ActivityInvariants.cs:38-40), and the public page falls back to the event venue.
      • -
      • Event relationship (:53-56): required HasOne(p => p.Event).WithMany().HasForeignKey(p => p.EventId), with the parameterless WithMany(), so Event exposes no activities collection. The same one-sided-navigation choice is made in SessionConfiguration: activities are read by explicit event-scoped queries, not by walking the event aggregate.
      • -
      • Indexes (:58-59, :63-64): the filtered EventId lookup, then the filtered (EventId, StartTime, SortOrder) browse index described above. Neither is unique, so SoftDeleteUniqueIndexConvention would not have touched either one, which is why both spell out HasSoftDeleteFilter().
      • -
      -
    • -
    • Why it's built this way: an activity is a first-class row rather than a flavour of session because it has a different shape (its own venue, no room, no speakers), and separating it keeps the session table free of columns that only apply to parties. [Rubric §4, DDD] assesses whether the model names distinct concepts distinctly instead of overloading one entity with a type discriminator.
    • -
    • Where it's used: exposed as DbSet<Activity> Activities on ModuleApplicationDbContext (ModuleApplicationDbContext.cs:75); read by the public agenda queries and written by the organizer-facing activity commands.
    • -
    -

    SponsorConfiguration

    MMCA.ADC.Conference.Infrastructure · MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Sponsors · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Sponsors/SponsorConfiguration.cs:11 · Level 9 · class

    @@ -1112,13 +1014,13 @@

    SponsorConfiguration

  • The tier conversion (:25-27): described above.
  • Optional presentation columns (:29-47, :55-57): LogoUrl, Description, WebsiteUrl and LinkedInUrl each IsRequired(false) at 2000 characters (SponsorInvariants.cs:16, :19, :22, :25); TwitterHandle at 100 (:28); BoothNumber at 50 (:31). All seven widths read a constant, so this configuration contains no literal lengths.
  • Event relationship (:62-65): the one-way required HasOne/WithMany() pair described above. The shipped foreign key is FK_Sponsor_Event_EventId with ReferentialAction.Cascade (20260812202047_AddSponsors.cs:42-48).
  • -
  • Lookup index (:67-68): builder.HasIndex(p => p.EventId).HasSoftDeleteFilter(). It is not unique, so SoftDeleteUniqueIndexConvention would never have touched it and the explicit call is the only way the predicate gets applied; the migration confirms the shipped shape, IX_Sponsor_EventId with filter: "[IsDeleted] = 0" (20260812202047_AddSponsors.cs:51-56). It is aimed at exactly one query: the public sponsor strip fetches a page with filters["EventId"] = ("equals", ...) and sortColumn: "Sort" (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Sponsors/PublicSponsorList.razor.cs:65-75), an equality predicate on EventId intersected with the global soft-delete filter, and the filtered index covers both halves. [Rubric §12, Performance and Scalability] assesses whether index shape follows the queries that actually run.
  • +
  • Lookup index (:67-68): builder.HasIndex(p => p.EventId).HasSoftDeleteFilter(). It is not unique, so SoftDeleteUniqueIndexConvention would never have touched it and the explicit call is the only way the predicate gets applied; the migration confirms the shipped shape, IX_Sponsor_EventId with filter: "[IsDeleted] = 0" (20260812202047_AddSponsors.cs:51-56). It is aimed at exactly one query: the public sponsor strip fetches a page with filters["EventId"] = ("equals", ...) and sortColumn: "Sort" (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Sponsors/PublicSponsorList.razor.cs:69-90), an equality predicate on EventId intersected with the global soft-delete filter, and the filtered index covers both halves. [Rubric §12, Performance and Scalability] assesses whether index shape follows the queries that actually run.
  • What is absent from this file and still ends up in the table: IsDeleted, CreatedOn/CreatedBy, LastModifiedOn/LastModifiedBy and the rowversion concurrency token are all in the shipped table (20260812202047_AddSponsors.cs:32-37) without appearing anywhere in Configure. They come from ApplicationDbContext and the entity base, which makes this the cleanest single illustration in the chapter of the division of labour taught under CategoryItemConfiguration: a configuration class owns only this entity's columns, relationships and indexes.
  • Why it's built this way: sponsors are per-event data with a public, ordered presentation, so the mapping optimizes for the two things the public page does, filter by event and sort within a tier, and for schema stability as sponsorship packages change. Keeping every width on SponsorInvariants means the column, the domain guard (EnsureNameIsValid at SponsorInvariants.cs:39, EnsureLogoUrlIsValid at :51, EnsureBoothNumberIsValid at :63) and the Application-layer request validators cannot drift apart, which is what [Rubric §15, Best Practices & Code Quality] looks for. Confining all of it to one Infrastructure class keeps the Sponsor entity free of EF attributes, the Clean Architecture dependency rule this whole folder exists to serve.
  • Where it's used: applied when the concrete SQLServerDbContext for the ADC_Conference database builds its model by scanning the module assembly, exactly like its sixteen siblings; snapshotted by the Conference migrations project (MMCA.ADC/Source/Hosting/MMCA.ADC.Migrations.SqlServer.Conference, table created by 20260812202047_AddSponsors.cs). Rows are written by ConferenceModuleDbSeeder's sample-data path and by the sponsor command handlers, and read by SponsorService for PublicSponsorList. ModuleApplicationDbContext does declare a Sponsors DbSet, but as that section explains, the DbSet list is an index, not the source of the model.
  • -
  • Caveats / not-in-source: (1) There is no unique index on (EventId, Name), or on Name at all. The seeder's idempotency check probes s => s.Name == name (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/DbContexts/Seeding/ConferenceModuleDbSeeder.cs:370-372), so uniqueness of sponsor names is an application-level convention with no database backstop, unlike the room-name and session-speaker cases elsewhere in this folder. (2) The Tier column carries no check constraint, so a value outside 0..3 would be storable by anything that bypasses the domain factory; the enum is enforced in the CLR type, not in SQL. (3) BoothNumber is nullable and independent of IsExhibitor: the domain deliberately accepts a booth number on a non-exhibitor (SponsorInvariants.cs:57-58, "the flag drives display, it does not reject stored data"), and the mapping adds no constraint tying the two together. (4) The Cascade delete on the event foreign key is not overridden here; because the codebase soft-deletes rather than hard-deletes, whether that cascade ever executes in a deployed database is not determinable from source. (5) The grouping by tier that the public page renders happens in memory after the fetch (PublicSponsorList.razor.cs:82-86), not as a SQL ORDER BY Tier, so the int conversion enables a cheap column sort that today's read path does not yet ask the database to perform.
  • +
  • Caveats / not-in-source: (1) There is no unique index on (EventId, Name), or on Name at all. The seeder's idempotency check probes s => s.Name == name (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/DbContexts/Seeding/ConferenceModuleDbSeeder.cs:370-372), so uniqueness of sponsor names is an application-level convention with no database backstop, unlike the room-name and session-speaker cases elsewhere in this folder. (2) The Tier column carries no check constraint, so a value outside 0..3 would be storable by anything that bypasses the domain factory; the enum is enforced in the CLR type, not in SQL. (3) BoothNumber is nullable and independent of IsExhibitor: the domain deliberately accepts a booth number on a non-exhibitor (SponsorInvariants.cs:57-58, "the flag drives display, it does not reject stored data"), and the mapping adds no constraint tying the two together. (4) The Cascade delete on the event foreign key is not overridden here; because the codebase soft-deletes rather than hard-deletes, whether that cascade ever executes in a deployed database is not determinable from source. (5) The grouping by tier that the public page renders happens in memory after the fetch (PublicSponsorList.razor.cs:97-101), not as a SQL ORDER BY Tier, so the int conversion enables a cheap column sort that today's read path does not yet ask the database to perform.

  • PartnerConfiguration

    @@ -1145,25 +1047,123 @@

    PartnerConfiguration

  • Caveats / not-in-source: (1) There is no unique index on (EventId, Name) or on Name alone; the seeder's idempotency check probes p => p.Name == name (ConferenceModuleDbSeeder.cs:428-431), so name uniqueness is an application-level convention with no database backstop, the same gap SponsorConfiguration carries. (2) PartnerType carries no check constraint, so a value outside 0..1 would be storable by anything that bypasses the domain factory.

  • -

    ModuleApplicationDbContext

    +

    EventConfiguration

    -

    MMCA.ADC.Conference.Infrastructure · MMCA.ADC.Conference.Infrastructure.Persistence.DbContexts · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/DbContexts/ModuleApplicationDbContext.cs:22 · Level 12 · abstract class

    +

    MMCA.ADC.Conference.Infrastructure · MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Events/EventConfiguration.cs:12 · Level 8 · class

      -
    • What it is: an abstract DbContext that names the Conference module's entity sets. It declares seventeen internal DbSet<T> properties and forwards its four constructor arguments unchanged to the framework base; it adds no OnModelCreating, no OnConfiguring, and no behavior of any kind.
    • -
    • Depends on: first-party: ApplicationDbContext (base, :25), IEntityConfigurationAssemblyProvider (:23), PhysicalDataSource (:24), and the seventeen Conference entity types it exposes, all from Group 17 except SessionAsset (mapped by SessionAssetConfiguration in this chapter) and Partner (mapped by PartnerConfiguration in this chapter): Event, Room, EventSpeaker, EventQuestionAnswer, Session, SessionSpeaker, SessionQuestionAnswer, SessionCategoryItem, SessionAsset, Speaker, SpeakerCategoryItem, Category, CategoryItem, Question, Sponsor, Partner, Activity. External: Microsoft.EntityFrameworkCore.DbContextOptions and DbSet<T> (:1).
    • -
    • Concept introduced, a DbSet list is an index, not the model. The instinct carried over from a typical EF application is that DbSet<T> properties define what the context maps. In this codebase they do not. The model is built by ApplicationDbContext, which walks the assemblies handed to it by IEntityConfigurationAssemblyProvider and applies every configuration implementing the engine's interface, filtered to the entities routed to this physical data source (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:950-977). The proof is in the arithmetic: the Conference EntityConfiguration folder holds nineteen *Configuration.cs files (seventeen plus SessionAssetConfiguration and PartnerConfiguration), two more than the seventeen DbSets here. SessionAiScore and SpeakerQuestionAnswer are mapped, migrated, and queried without ever appearing on this class. Reading the DbSet list as a coverage manifest would therefore mislead you; read the configuration folder instead. This is what Rubric section 8 (Data Architecture) assesses whether the mapping strategy is explicit and centrally governed; convention-by-assembly-scan is what lets one context class serve every module without any module editing it.
    • -
    • Concept introduced, internal sets as a layering boundary. All seventeen properties are internal (:28-78), not public. Nothing outside MMCA.ADC.Conference.Infrastructure can reach context.Sessions even with a context instance in hand. Application-layer handlers get their data through IUnitOfWork and IRepository<TEntity, TIdentifierType> instead, which is where soft-delete filtering, tracking choices, and data-source routing are decided. This is what Rubric section 3 (Clean Architecture) assesses whether the dependency rule is enforced by the compiler rather than by convention; an access modifier is the cheapest available enforcement, and it is why a handler cannot accidentally grow a raw LINQ query against a DbSet.
    • +
    • What it is: the persistence map for Event, the top aggregate of the Conference module (the conference itself: dates, venue, publication state, Sessionize linkage).
    • +
    • Depends on: first-party: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, Event, EventInvariants, QuestionModerationDefault, NullableEmailValueConverter. External: Microsoft.EntityFrameworkCore.
    • +
    • Concept reinforced, the filtered non-unique index. HasIndex(p => p.SessionizeCode).HasFilter("[SessionizeCode] IS NOT NULL") (:42-43) is filtered but not unique. A filtered index only covers the rows matching its predicate, so this one indexes just the events that carry a Sessionize code, which is the population the import path looks up by. It deliberately does not forbid two events sharing a code, and it costs nothing for the events with a null code. [Rubric §12, Performance and Scalability] assesses whether indexes are chosen for the actual query shape rather than sprayed across columns: this is a narrow index sized to one lookup.
    • +
    • Concept reinforced, a value object mapped by a converter rather than an owned type. OrganizerContactEmail is an Email value object on the entity, but the column is still the same nullable nvarchar it was when the property was a string: HasConversion(new NullableEmailValueConverter()) (:61) turns the object into its text on the way down and back on the way up. The comment above it (:57-59) states the consequence directly: converter, not OwnsOne, so the store type and length are unchanged and no migration is needed. That is the cheap half of ADR-068, a type-safe domain property bought without touching the schema. [Rubric §8, Data Architecture] assesses whether the storage shape is chosen independently of the domain shape.
    • Walkthrough
        -
      • Class declaration and primary constructor (:20-25): public abstract class ModuleApplicationDbContext(DbContextOptions options, IServiceProvider serviceProvider, IEntityConfigurationAssemblyProvider assemblyProvider, PhysicalDataSource physicalDataSource) : ApplicationDbContext(options, serviceProvider, assemblyProvider, physicalDataSource). Every parameter is passed straight through; the class captures none of them and overrides nothing. Note the untyped DbContextOptions rather than DbContextOptions<TContext>, which is what allows an arbitrary concrete subclass to supply its own typed options.
      • -
      • The seventeen entity sets (:28-78): Events (:28), Rooms (:31), EventSpeakers (:34), EventQuestionAnswers (:37), Sessions (:40), SessionSpeakers (:43), SessionQuestionAnswers (:46), SessionCategoryItems (:49), Speakers (:52), SpeakerCategoryItems (:55), Categories (:58), CategoryItems (:61), Questions (:64), Sponsors (:67), Partners (:70), Activities (:73), SessionAssets (:77). Read top to bottom they trace the module's aggregate map: the two roots that own schedules (Event, Session), their join tables to speakers and categories, the taxonomy pair (Category and CategoryItem), the feedback pair (Question plus the two answer tables), the three per-event additions (Sponsor, Partner, Activity), and the newest set, SessionAssets, appended at the end rather than grouped with the other session-scoped sets. Partners was inserted between Sponsors and Activities, next to the entity it mirrors, unlike SessionAssets.
      • -
      • What is deliberately absent: no OnModelCreating override, so nothing here competes with the base's assembly scan; no OnConfiguring, so provider selection stays with the concrete engine context; no SaveChanges override, so audit stamping, soft delete, and outbox dispatch remain the base's job.
      • +
      • Required core (:20-22, :28-36): Name (EventInvariants.NameMaxLength), StartDate, EndDate, and TimeZone (EventInvariants.TimeZoneMaxLength). Storing the IANA time-zone id as a column rather than baking a UTC offset into the dates is what lets the schedule render correctly across DST.
      • +
      • Optional descriptive, venue and link columns (:24-26, :45-71): Description, VenueAddress, VenueMapUrl, WiFiInfo, OrganizerContactEmail, SponsorshipPacketUrl and TicketingUrl, each IsRequired(false) with its own invariant-sourced max length, and OrganizerContactEmail additionally carrying the converter above (:60-63).
      • +
      • Sessionize linkage (:38-43, :80-84): SessionizeCode optional plus the filtered index above; LastSessionizeRefreshOn / LastSessionizeRefreshBy are optional audit-style columns recording the last import run. [Rubric §13, Observability and Operability] assesses whether the system records the provenance of imported data: these two columns answer "when was this event last synced, and by whom" from the row itself.
      • +
      • State flags (:73-78): IsPublished required; QuestionModerationDefault required, with the comment (:76) noting it is stored as an int through EF's default enum conversion and that Pending (0) is the safe default per BR-233. There is no HasConversion call, EF's default enum-to-int mapping is used as-is, and the enum really does declare Pending = 0 (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Events/Live/QuestionModerationDefault.cs:10), so the safe default is also the zero value in the database.
      • +
      • Navigation access mode (:86-97): Rooms, EventSpeakers and EventQuestionAnswers are each set to UsePropertyAccessMode(PropertyAccessMode.Field) because every one of them is a getter over a private list returned as AsReadOnly(). The comment (:86-91) is explicit that convention already infers field access here and that stating it is insurance: a later change to the property cannot silently turn materialization into a no-op. It is an access-mode declaration only, the relationships themselves stay configured from the child side.
    • -
    • Why it's built this way: ADR-006 fixes one sealed context class per engine, shared across modules, rather than one context class per module, and the runtime honors that literally. The concrete context is Common's sealed class SQLServerDbContext, which derives from ApplicationDbContext directly (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/SQLServerDbContext.cs:15-20), and the Conference migrations project targets that same type through IDesignTimeDbContextFactory<SQLServerDbContext> (MMCA.ADC/Source/Hosting/MMCA.ADC.Migrations.SqlServer.Conference/DesignTimeSQLServerDbContextFactory.cs:12-15). Keeping the module-level class abstract, behavior-free, and additive means the per-engine story stated in ADR-018 (SQL Server today, Cosmos and SQLite as further engines) needs no per-module change: a new engine adds one sealed class in MMCA.Common, not seventeen DbSet declarations per module. This is what Rubric section 7 (Microservices Readiness) assesses whether a module could be lifted into its own host without a rewrite; the entity-set surface being module-scoped and internal is part of what makes that lift mechanical.
    • -
    • Where it's used: the seventeen sets correspond one to one with seventeen of the nineteen configurations in this chapter, including EventConfiguration, SessionConfiguration, SponsorConfiguration, PartnerConfiguration, ActivityConfiguration and SessionAssetConfiguration, and with the tables the Conference migrations project maintains for the ADC_Conference database. Application-layer access to those tables runs through IUnitOfWork, and the rows themselves are first written by ConferenceModuleDbSeeder.
    • -
    • Caveats / not-in-source: (1) Nothing derives from this class. A repository-wide search of MMCA.ADC for the identifier ModuleApplicationDbContext returns exactly three hits, the three sibling declarations in the Conference, Engagement, and Identity Infrastructure projects, and no subclass, no DI registration, and no consumer. The class compiles and is packaged, but it is not on the runtime path today: SQLServerDbContext bypasses it. Treat this section as documentation of the module's entity surface and of an extension point that is currently unexercised, not of a type in the request path. (2) Consequently the internal visibility of the sets protects a surface nothing currently reaches; the layering point it makes is real but presently theoretical for this class. (3) SessionAiScore and SpeakerQuestionAnswer have configurations in MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/ but no DbSet here; whether that is deliberate or an oversight is not determinable from source, since no code reads the DbSet list. (4) The three sibling ModuleApplicationDbContext classes share a name across three namespaces (Conference at :20, Engagement at MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/DbContexts/ModuleApplicationDbContext.cs:19, Identity at MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Infrastructure/Persistence/DbContexts/ModuleApplicationDbContext.cs:15); each has its own section in its own chapter, so check the namespace before assuming which one a search result refers to.
    • +
    • Why it's built this way: everything the organizer may not know at creation time is nullable, so an event can be created early and enriched later without a two-phase workflow; only the four facts that make an event an event are required.
    • +
    • Where it's used: Event is the FK target of RoomConfiguration, SessionConfiguration, EventSpeakerConfiguration, EventQuestionAnswerConfiguration, ActivityConfiguration and SponsorConfiguration.
    • +
    +
    +

    EventQuestionAnswerConfiguration

    +
    +

    MMCA.ADC.Conference.Infrastructure · MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Events · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Events/EventQuestionAnswerConfiguration.cs:11 · Level 8 · class

    +
    +
      +
    • What it is: the persistence map for EventQuestionAnswer, one attendee's answer to one event-scoped Question.
    • +
    • Depends on: first-party: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, EventQuestionAnswer, Event, EventInvariants, IndexBuilderExtensions (HasSoftDeleteFilter). External: Microsoft.EntityFrameworkCore.Metadata.Builders.
    • +
    • Concept introduced, HasSoftDeleteFilter() and the database as the concurrency backstop.
        +
      • HasSoftDeleteFilter() (IndexBuilderExtensions.cs:52-66) replaces a hand-typed HasFilter("[IsDeleted] = 0"). It builds the predicate through SoftDeleteFilterSql from the live model (:56), so a renamed soft-delete column follows automatically and the identifier quoting comes from the engine instead of a SQL-Server-shaped literal. Its engine parameter defaults to DataSource.SQLServer (:51), which is exactly what the …SQLServer base already implies. On a unique index the call is technically redundant with SoftDeleteUniqueIndexConvention, which would apply the same predicate at model finalizing; writing it explicitly keeps the intent readable at the call site, and because the convention skips any index that already declares a filter (SoftDeleteUniqueIndexConvention.cs:54) the two can never disagree. On a non-unique index like the EventId lookup here, the convention deliberately does nothing, so the explicit call is the only way to get the filter.
      • +
      • The (EventId, QuestionId, CreatedBy) unique index (:42-44) is a race backstop, and the comment (:38-41) is unusually candid about why: the application-level upsert only inspects the in-memory collection, so two concurrent submits can both take the create branch. The database refuses the second one, and the shared DbUpdateException handler turns the violation into a 409 for the client. [Rubric §8, Data Architecture] assesses whether invariants that matter are enforced where they cannot be raced, and [Rubric §15, Best Practices and Code Quality] assesses whether known limitations are documented at the point of the compensating control rather than left for the next reader to discover.
      +
    • +
    • Walkthrough: required EventId and QuestionId scalars (:19-23); required AnswerValue at EventInvariants.AnswerValueMaxLength (:25-27); required parent relationship HasOne(p => p.Event).WithMany(p => p.EventQuestionAnswers).HasForeignKey(p => p.EventId) (:29-32); soft-delete-filtered lookup index on EventId (:35-36); the BR-123 filtered unique index (:42-44).
    • +
    • Why it's built this way: CreatedBy is part of the uniqueness tuple, so "one live answer per question" is scoped per author, not globally, which is what a per-attendee feedback form needs.
    • +
    • Where it's used: written by the Conference event-feedback command handlers; read by the feedback queries. Compare its sibling SessionQuestionAnswerConfiguration, which carries the same BR-123 index but treats its parent lookup index differently for a specific reason.
    • +
    +
    +

    EventSpeakerConfiguration

    +
    +

    MMCA.ADC.Conference.Infrastructure · MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Events · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Events/EventSpeakerConfiguration.cs:11 · Level 8 · class

    +
    +
      +
    • What it is: the persistence map for the EventSpeaker join entity, which records that a speaker is part of an event's line-up.
    • +
    • Depends on: first-party: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, EventSpeaker, Event, IndexBuilderExtensions. External: Microsoft.EntityFrameworkCore.Metadata.Builders.
    • +
    • Concept introduced, the join-entity template. Four configurations in this folder are structurally identical, and this is the first: both FK scalars required, one HasOne(...).WithMany(...) relationship to the owning aggregate only (the side whose collection navigation the join belongs to), and a soft-delete-filtered composite unique index on the two FKs. The other side of the pair is deliberately not configured as a relationship, which keeps the entity a one-way child of a single aggregate and matches the DDD rule that an aggregate owns its children. [Rubric §4, DDD] assesses aggregate boundary discipline; [Rubric §8, Data Architecture] assesses the uniqueness guarantee. + The soft-delete filter on the unique index is what makes a delete-then-re-add cycle legal: a plain unique index would let a soft-deleted association keep occupying its slot forever, so an organizer could never re-add a speaker they had removed.
    • +
    • Walkthrough: required EventId (:19-20) and SpeakerId (:22-23); HasOne(p => p.Event).WithMany(p => p.EventSpeakers).HasForeignKey(p => p.EventId).IsRequired() (:25-28); HasIndex(p => new { p.EventId, p.SpeakerId }).IsUnique().HasSoftDeleteFilter() (:30-32).
    • +
    • Where it's used: the same template appears in SessionSpeakerConfiguration, SessionCategoryItemConfiguration and SpeakerCategoryItemConfiguration.
    • +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeFile:LineOwning aggregateUnique index
    EventSpeakerConfigurationMMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Events/EventSpeakerConfiguration.cs:11Event(EventId, SpeakerId)
    SessionSpeakerConfigurationMMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Sessions/SessionSpeakerConfiguration.cs:11Session(SessionId, SpeakerId)
    SessionCategoryItemConfigurationMMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Sessions/SessionCategoryItemConfiguration.cs:11Session(SessionId, CategoryItemId)
    SpeakerCategoryItemConfigurationMMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Speakers/SpeakerCategoryItemConfiguration.cs:11Speaker(SpeakerId, CategoryItemId)
    +
    +

    QuestionConfiguration

    +
    +

    MMCA.ADC.Conference.Infrastructure · MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Questions · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Questions/QuestionConfiguration.cs:10 · Level 8 · class

    +
    +
      +
    • What it is: the persistence map for Question, the definition of a feedback question (its text, what it attaches to, how it renders, and where it came from).
    • +
    • Depends on: first-party: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, Question, QuestionInvariants. External: Microsoft.EntityFrameworkCore.Metadata.Builders.
    • +
    • Concept: the shared shape is taught under CategoryItemConfiguration. What is worth noticing here is that this is the flattest configuration in the folder: six required properties, no relationships and no indexes at all.
    • +
    • Walkthrough (:18-38): all six columns are IsRequired(). QuestionText, QuestionEntity, QuestionType and QuestionSource each take their length from QuestionInvariants; Sort and IsRequired (the boolean, not the fluent call) are plain required scalars. QuestionEntity and QuestionType are declared as string on the entity (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Questions/Question.cs:20, :23), not enums, so adding a question type or a new attachable entity needs no migration and no enum-to-string conversion.
    • +
    • Why it's built this way: questions are attached to events, sessions and speakers by the three …QuestionAnswer entities, and those answers carry a plain QuestionId scalar rather than a navigation, so Question itself needs no relationship configuration. Modelling the discriminators as strings keeps the question catalogue extensible from data rather than from code.
    • +
    • Where it's used: referenced by QuestionId from EventQuestionAnswerConfiguration, SessionQuestionAnswerConfiguration and SpeakerQuestionAnswerConfiguration.
    • +
    +
    +

    RoomConfiguration

    +
    +

    MMCA.ADC.Conference.Infrastructure · MMCA.ADC.Conference.Infrastructure.Persistence.EntityConfiguration.Events · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/EntityConfiguration/Events/RoomConfiguration.cs:11 · Level 8 · class

    +
    +
      +
    • What it is: the persistence map for Room, a physical room belonging to an Event.
    • +
    • Depends on: first-party: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, Room, Event, EventInvariants, IndexBuilderExtensions. External: Microsoft.EntityFrameworkCore.Metadata.Builders.
    • +
    • Concept introduced, re-declaring an index EF would otherwise drop. The explicit builder.HasIndex(p => p.EventId) (:48) looks redundant next to the (EventId, Name) composite below it, and the comment (:46-47) says exactly why it is not: EF removes the conventional foreign-key index as redundant once a composite index leads with the same column, but the composite is filtered, and the plain FK lookups still want an unfiltered index. This is a good example of a mapping decision that only makes sense once you know EF's own de-duplication rule; without the comment the line reads as a mistake. [Rubric §12, Performance and Scalability] assesses whether index choices survive framework conventions rather than being silently optimized away.
    • +
    • Walkthrough
        +
      • Required (:19-24): Name at EventInvariants.RoomNameMaxLength, and Sort.
      • +
      • Optional (:26-39): Capacity (a nullable scalar with no length), plus Floor, Location and AccessibilityInfo, each with an invariant-sourced max length. AccessibilityInfo being a first-class room column, not a note bolted onto the description, is the schema-level half of ADC's accessibility commitment. [Rubric §21, Accessibility] assesses whether accessibility is designed into the data rather than added at the view.
      • +
      • Parent relationship (:41-44): required HasOne(p => p.Event).WithMany(p => p.Rooms).HasForeignKey(p => p.EventId).
      • +
      • Indexes (:48, :52-54): the re-declared plain EventId index, then HasIndex(p => new { p.EventId, p.Name }).IsUnique().HasSoftDeleteFilter(). The comment (:50-51) states its purpose plainly: it backstops the aggregate's duplicate-room-name invariant, and the soft-delete filter means a deleted room never blocks reusing its name.
      • +
      +
    • +
    • Why it's built this way: the domain already refuses a duplicate room name inside the Event aggregate; the filtered unique index is the database-side guarantee for the concurrent case the in-memory check cannot see, the same defence-in-depth reasoning as BR-123 in EventQuestionAnswerConfiguration.
    • +
    • Where it's used: Room is the optional FK target of SessionConfiguration, which restricts deletes against it.
    • +
    +

    ⬅ ADC Conference - Application & Use Cases • Index • ADC Conference - API, gRPC Contracts & Service Host ➡

    diff --git a/docs/onboarding/group-20-conference-api-grpc.html b/docs/onboarding/group-20-conference-api-grpc.html index 22275442..4d22a7dd 100644 --- a/docs/onboarding/group-20-conference-api-grpc.html +++ b/docs/onboarding/group-20-conference-api-grpc.html @@ -180,7 +180,7 @@

    The controller and inherit the full read + create + delete surface, overriding actions only to add [AllowAnonymous], an [OutputCache] policy, or a business rule (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:55, - SpeakersController.cs:52, EventsController.cs:54, QuestionsController.cs:42, + SpeakersController.cs:53, EventsController.cs:54, QuestionsController.cs:42, ConferenceCategoriesController.cs:43, SponsorsController.cs:49, PartnersController.cs:50, ActivitiesController.cs:49). Child-and-join controllers (eight: RoomsController, @@ -250,14 +250,14 @@

    One read hook pe

    The single most important thing to learn before editing any file here is where row scoping lives. It is not threaded through each action: the framework base declares one hook, GetReadSpecificationAsync - (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:531), and + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:571), and every read action (both list overloads, the lookup, the by-id read and the CSV export) calls it - (EntityControllerBase.cs:113,168,262,312,356). Its default answer is the synchronous half, - GetExportSpecification (EntityControllerBase.cs:560), itself null, so a controller that + (EntityControllerBase.cs:116,171,270,324,368). Its default answer is the synchronous half, + GetExportSpecification (EntityControllerBase.cs:601), itself null, so a controller that overrides neither reads unscoped. Thirteen of the twenty-three Conference controllers override one of the two. Ten override the asynchronous hook because their rule is resolved through a query handler: SessionsController (SessionsController.cs:75), - SpeakersController (SpeakersController.cs:95), + SpeakersController (SpeakersController.cs:96), RoomsController (RoomsController.cs:120), SponsorsController (SponsorsController.cs:69), PartnersController (PartnersController.cs:66), @@ -278,29 +278,50 @@

    One read hook pe excludes is a 404, not a 403 (a "forbidden" answer would confirm the id exists), the lookup endpoint receives the same scope as the specification's Criteria predicate, and the read actions in these files are attribute-plus-guard passthroughs whose bodies call base behind one fail-closed - check (for example EventQuestionAnswersController.cs:141-151, SessionsController.cs:210-237), + check (for example EventQuestionAnswersController.cs:145-155, SessionsController.cs:218-245), kept only because the route attributes must sit on the derived action. That guard is worth reading once: because the hook answers null for two different reasons (an Organizer whose scoping is deliberately skipped, and a non-Organizer whose owner claim cannot be resolved), each answer-controller read first calls RequireResolvableOwner(), which admits the first case and returns a 403 for the - second (EventQuestionAnswersController.cs:126-139, SessionQuestionAnswersController.cs:126-139), - so a missing claim is an authorization answer instead of a dereference. [Rubric §11, Security] is the lens, and + second (EventQuestionAnswersController.cs:135-143, SessionQuestionAnswersController.cs:135-143), + so a missing claim is an authorization answer instead of a dereference. The private method is now a + one-expression delegation to Common's OwnershipHelper.RequireResolvableOwner<TId>, which reads the + bypass role the same way GetOwnershipSpecification does, so the gate and the scope cannot disagree + about who is privileged (EventQuestionAnswersController.cs:129-131). [Rubric §11, Security] is the lens, and ADR-078 is the record: the hook exists so an export can no longer drift wider than the list it mirrors.

    +

    The export path differs from the reads in one way that matters. A null from the hook means + "unscoped" to a list action, but to ExportAsync it means refuse: the base answers a Forbidden + error rather than streaming the whole table unless the controller opts in through the virtual + AllowUnscopedExport, which defaults to false + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:272-274,508,608-613). + Sixteen Conference controllers opt in, each naming exactly the audience whose null is deliberate. + The ten async-hook controllers answer with their own IsPrivileged read-audience check (for example + SessionsController.cs:93, RoomsController.cs:138, SpeakerCategoryItemsController.cs:91), and + EventsController with the same check inline (EventsController.cs:77), because + the hook returns null precisely for a privileged reader who already lists every row. The two answer + controllers opt in through OwnershipHelper.IsAdmin(currentUserService, RoleNames.Organizer), the + check the ownership hook itself uses, so only the Organizer bypass exports the whole table and a + non-Organizer with an unresolvable owner claim keeps the framework's 403 + (EventQuestionAnswersController.cs:114-119, SessionQuestionAnswersController.cs:119). And the three + reference-data controllers that every reader lists in full return a flat true + (CategoryItemsController.cs:73, ConferenceCategoriesController.cs:47, QuestionsController.cs:46).

    The CSV export keeps a second, blunter guard on top of the hook. Twelve controllers override ExportAsync and return Forbid() outright for a caller who is not a privileged reader, rather than - serving a scoped file: SessionsController.cs:240-250, SpeakersController.cs:281-291, - EventsController.cs:128-138, SponsorsController.cs:145-155, ActivitiesController.cs:145-155, - PartnersController.cs:136-150 (which belts the guard with a capability attribute on the export - action itself, [HasPermission(ConferencePermissions.PartnersManage)] at PartnersController.cs:135), - the four join controllers (EventSpeakersController.cs:139-149, - SessionSpeakersController.cs:140-150, SessionCategoryItemsController.cs:140-150, - SpeakerCategoryItemsController.cs:140-150) and the two answer controllers against the Organizer role - (EventQuestionAnswersController.cs:204-214, SessionQuestionAnswersController.cs:204-214). + serving a scoped file: SessionsController.cs:248-258, SpeakersController.cs:290-300, + EventsController.cs:135-145, SponsorsController.cs:153-163, ActivitiesController.cs:153-163, + PartnersController.cs:144-158 (which belts the guard with a capability attribute on the export + action itself, [HasPermission(ConferencePermissions.PartnersManage)] at PartnersController.cs:143), + the four join controllers (EventSpeakersController.cs:147-157, + SessionSpeakersController.cs:148-158, SessionCategoryItemsController.cs:148-158, + SpeakerCategoryItemsController.cs:148-158) and the two answer controllers against the Organizer role + (EventQuestionAnswersController.cs:208-218, SessionQuestionAnswersController.cs:208-218). Controllers whose whole class sits behind a capability gate and expose no anonymous export (RoomsController, CategoryItemsController, QuestionsController, - ConferenceCategoriesController) need no such override.

    + ConferenceCategoriesController) need no such override: the class + gate already restricts the export to a capability holder, and the opt-in above only decides whether + that holder's null scope is honored.

    Authorization at the edge, three shapes not one

    Authorization is capability-based by default but not uniform, and the differences are the interesting part. Most write-bearing controllers carry a class-level @@ -317,27 +338,27 @@

    Authorization at the edg QuestionsManage (QuestionsController.cs:34), SpeakersManage (SpeakerCategoryItemsController.cs:47) and SessionSelectionManage (SessionSelectionController.cs:32). Reads are then re-opened action by action with - [AllowAnonymous] (BR-43 public browse, for example SessionsController.cs:136, - RoomsController.cs:137). Eleven capability constants exist in total, declared once in + [AllowAnonymous] (BR-43 public browse, for example SessionsController.cs:144, + RoomsController.cs:145). Eleven capability constants exist in total, declared once in ConferencePermissions.All (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Authorization/ConferencePermissions.cs:51-61).

    Four shapes break that pattern, and knowing why saves you from "fixing" them. SpeakersController carries only a plain [Authorize] at class level (SpeakersController.cs:41) and pushes [HasPermission(ConferencePermissions.SpeakersManage)] down - onto the individual organizer actions (export, create and delete at SpeakersController.cs:280,299,354; + onto the individual organizer actions (export, create and delete at SpeakersController.cs:289,308,365; SpeakerLinksController repeats the shape for the BR-209 link and unlink, class-level [Authorize] at SpeakerLinksController.cs:32 with SpeakersManage at :41,60), because one of its writes is an authenticated self-service surface: the BR-214 profile update re-declares plain - [Authorize] (SpeakersController.cs:323) and decides inside the action whether the caller is an + [Authorize] (SpeakersController.cs:333) and decides inside the action whether the caller is an organizer or the speaker themselves, comparing the speaker_id JWT claim to the route id - (SpeakersController.cs:334-337) and passing the answer down as CallerIsOrganizer so the handler - can refuse a self-edit of the organizer-only IsTopSpeaker field (SpeakersController.cs:342). + (SpeakersController.cs:346) and passing the answer down as CallerIsOrganizer so the handler + can refuse a self-edit of the organizer-only IsTopSpeaker field (SpeakersController.cs:353). SponsorsController, PartnersController and ActivitiesController copy the class-level-[Authorize]-plus-per-action-capability shape (SponsorsController.cs:39 with - SponsorsManage at :143,162,181,206; PartnersController.cs:40 with PartnersManage at - :135,154,172,197; ActivitiesController.cs:39 with ActivitiesManage at - :143,162,181,206). And EventQuestionAnswersController and + SponsorsManage at :152,171,190,215; PartnersController.cs:40 with PartnersManage at + :143,162,180,205; ActivitiesController.cs:39 with ActivitiesManage at + :152,171,190,215). And EventQuestionAnswersController and SessionQuestionAnswersController carry a bare [Authorize] (EventQuestionAnswersController.cs:77, SessionQuestionAnswersController.cs:77), because any signed-in attendee may submit feedback answers, so no organizer capability applies. The fourth shape @@ -349,7 +370,7 @@

    Authorization at the edg which is data no role-to-permission table can express. The controller computes two values instead: ActingSpeakerId, the caller's speaker_id claim (SessionAssetsController.cs:62-63), and IsPrivileged, an IPermissionRegistry.HasPermission(..., ConferencePermissions.SessionAssetsManage) - answer (:66-67); it stamps both onto every command (:123,231,255) and the handlers in + answer (:66-67); it stamps them onto every command (:123,187-193,232,256) and the handlers in G18 make the decision against the session's speakers. The SessionAssetsManage capability (ConferencePermissions.cs:47) therefore means "may manage the materials of a session you do not present", which is why it sits inside the ContentEditor subset. @@ -371,16 +392,16 @@

    Authorization at the edg (:153,167-173), a 50 MB cap chosen for conference-venue wifi and justified in source against the analyzer rule it trips (:154-157). The update is conditional, declaring SupportsIfMatch so a caller without an - If-Match header gets 428 and a stale token 412 (:216-217,228; + If-Match header gets 428 and a stale token 412 (:217-218,229; ADR-035). And every write - ends by evicting the conference:sessions and conference cache tags (:279-280), because the + ends by evicting the conference:sessions and conference cache tags (:280-281), because the public list it just invalidated is cached under the sessions policy that a session edit also clears.

    Orthogonal to all three shapes is the read audience, which no attribute can express because it changes the rows rather than the verdict. Eleven controllers ask CurrentUserServiceExtensions.IsPrivilegedConferenceReader() (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Authorization/CurrentUserServiceExtensions.cs:24-25) and turn the answer into a specification or null: SessionsController.cs:59, - SpeakersController.cs:56, EventsController.cs:70, SponsorsController.cs:53, + SpeakersController.cs:57, EventsController.cs:70, SponsorsController.cs:53, PartnersController.cs:57, ActivitiesController.cs:53, RoomsController.cs:104, EventSpeakersController.cs:58, SessionSpeakersController.cs:59, SessionCategoryItemsController.cs:59 and @@ -408,9 +429,9 @@

    The request records, the i RoomsController.cs:33) plus the child's own fields, all required/init for immutability (RoomsController.cs:30-55), and the action unpacks the record into the matching Add*Command or Update*Command from G18 - (AddRoomCommand at RoomsController.cs:211, - UpdateRoomCommand at :240, - RemoveRoomCommand at :266). They are + (AddRoomCommand at RoomsController.cs:219, + UpdateRoomCommand at :248, + RemoveRoomCommand at :274). They are deliberately separate from the application-layer command types and from the outbound DTOs (the §9 "DTOs decoupled from entities" discipline), so the HTTP contract can evolve independently of the command's parameter list. The aggregate-root controllers, by contrast, bind the application layer's @@ -425,8 +446,8 @@

    The request records, the i them onto BatchAddEventQuestionAnswersCommand so the whole event feedback form is upserted under one transaction and a refusal leaves nothing - written (EventQuestionAnswersController.cs:245-271), and both creates declare - Idempotent (:228,255). The session side + written (EventQuestionAnswersController.cs:258,266), and both creates declare + Idempotent (:232,259). The session side mirrors it exactly. Alongside AddSessionQuestionAnswerRequest and UpdateSessionQuestionAnswerRequest (SessionQuestionAnswersController.cs:28,61), the controller declares @@ -435,68 +456,68 @@

    The request records, the i question-and-answer pairs (SessionQuestionAnswersController.cs:41,51). Its POST /batch action maps them onto BatchAddSessionQuestionAnswersCommand - so a whole feedback form is applied atomically in one transaction (:192-211), and both creates + so a whole feedback form is applied atomically in one transaction (:258,266), and both creates declare Idempotent - (SessionQuestionAnswersController.cs:228,255) so a retried Idempotency-Key replays the first + (SessionQuestionAnswersController.cs:232,259) so a retried Idempotency-Key replays the first response rather than re-applying the form (ADR-017).

    Where the generic shape gives way: filters, warnings, calendars and conditional writes

    SessionsController shows best how a controller earns its overrides. Every read action is [AllowAnonymous] and [OutputCache(PolicyName = "SessionsCache")] - (SessionsController.cs:136-137,162-163,211-212,223-224, plus the split-out calendar read at + (SessionsController.cs:144-145,170-171,219-220,231-232, plus the split-out calendar read at SessionCalendarController.cs:35-36). Its read hook dispatches GetPublicSessionFilterQuery so a non-organizer never sees declined sessions (BR-132/BR-49), and because Session and Event can live in different data sources the published-event check is resolved by that handler through the framework's cross-source specification helper rather than by a join (SessionsController.cs:61-85; ADR-018). The paged read adds a - second layer in BuildPagedSessionSpecificationAsync (SessionsController.cs:105): Session has no + second layer in BuildPagedSessionSpecificationAsync (SessionsController.cs:113): Session has no SpeakerId column, so that filter key is intercepted and Removed before the generic filter pipeline can reject it, resolved to an id list through GetSessionsBySpeakerFilterQuery, - and ANDed with the public filter rather than substituted for it (SessionsController.cs:105-128), + and ANDed with the public filter rather than substituted for it (SessionsController.cs:113-136), because substituting would leak non-accepted sessions to anonymous callers; an unparseable value - simply ignores the key (:111-116). The Sessions surface adds three things the base has no notion of: + simply ignores the key (:119-124). The Sessions surface adds three things the base has no notion of: a GET /{id}/ics action that streams one public session as text/calendar for the add-to-calendar affordance via ExportSessionCalendarQuery, which lives on the split-out SessionCalendarController (SessionCalendarController.cs:34-45), a BR-86 X-Warning header raised on create by comparing the request times against the event's StartDate/EndDate and on update from the handler's - HasDateRangeWarning flag (SessionsController.cs:273-287, :319-323), and an explicit + HasDateRangeWarning flag (SessionsController.cs:281-295, :328-332), and an explicit Idempotent declaration on the create override - so the contract is visible at the ADC endpoint rather than only inherited (:261). Every mutating + so the contract is visible at the ADC endpoint rather than only inherited (:270). Every mutating action ends by evicting the conference:sessions and conference output-cache tags - (SessionsController.cs:289,326,337), the write-side half of the caching contract.

    + (SessionsController.cs:297,334,345), the write-side half of the caching contract.

    Conditional writes are now uniform: an update states its precondition in the HTTP If-Match header and nowhere else. SupportsIfMatch sits - on the session update (SessionsController.cs:302), the event update (EventsController.cs:215), - the event publish and unpublish (EventLifecycleController.cs:53,86), the speaker update (SpeakersController.cs:324), the category - update (ConferenceCategoriesController.cs:115), the question update (QuestionsController.cs:114) - and the sponsor, partner and activity updates (SponsorsController.cs:183, - PartnersController.cs:173, ActivitiesController.cs:183), and + on the session update (SessionsController.cs:310), the event update (EventsController.cs:222), + the event publish and unpublish (EventLifecycleController.cs:53,86), the speaker update (SpeakersController.cs:334), the category + update (ConferenceCategoriesController.cs:118), the question update (QuestionsController.cs:117) + and the sponsor, partner and activity updates (SponsorsController.cs:191, + PartnersController.cs:181, ActivitiesController.cs:191), and each action pulls the token with SupportsIfMatchAttribute.RequiredToken(HttpContext) - (SessionsController.cs:311, EventsController.cs:224, EventLifecycleController.cs:61,94, - SpeakersController.cs:339, - SponsorsController.cs:192, PartnersController.cs:182): a request with no header answers + (SessionsController.cs:319, EventsController.cs:231, EventLifecycleController.cs:61,94, + SpeakersController.cs:350, + SponsorsController.cs:200, PartnersController.cs:190): a request with no header answers 428 Precondition Required and a stale token answers 412 Precondition Failed (ADR-035). Sponsors, partners and activities take that one step further and dispatch the framework's generic UpdateEntityCommand<TEntity, TUpdateRequest, TIdentifierType> - rather than a bespoke command (SponsorsController.cs:195, PartnersController.cs:185, - ActivitiesController.cs:195), so their + rather than a bespoke command (SponsorsController.cs:203, PartnersController.cs:193, + ActivitiesController.cs:203), so their update path is generic end to end.

    Three more read-path carve-outs are worth internalizing before you touch these files. First, SpeakersController.GetAllForLookupAsync constrains the label as well as the rows: only FirstName and LastName may be requested by a non-privileged caller - (SpeakersController.cs:59,209-219), because nameProperty=Email would project the speaker email + (SpeakersController.cs:60,218-228), because nameProperty=Email would project the speaker email straight into the lookup label and go around the DTO mapper that redacts it (BR-66). Second, that controller's GetByIdAsync drops the public specification when the caller's speaker_id claim equals the route id (the self-edit form cannot load without reading the profile it edits), and because the output-cache key does not vary by caller it turns storage off for that response through IOutputCacheFeature so a private profile can never land in the shared entry - (SpeakersController.cs:247-257). The per-session reads live on + (SpeakersController.cs:256-266). The per-session reads live on SpeakerSessionsController, which pairs a class-level [Authorize] (SpeakerSessionsController.cs:28) with per-action choices: the feedback read is gated self-or-organizer in code and deliberately left uncached, since every response is @@ -507,14 +528,15 @@

    Idempotent (:52,85,116), the last mapping two domain error codes onto HTTP 429 with a Retry-After: 300 and onto 502 (EventLifecycleController.cs:128-136). EventsController itself adds - its own GET /{id}/ics (EventsController.cs:148-159) and per-event and global now-next snapshot - actions under the short-lived NowNextCache policy (:165-188), both dispatching + its own GET /{id}/ics (EventsController.cs:155-156) and per-event and global now-next snapshot + actions under the short-lived NowNextCache policy (:172-173,187-188), both dispatching GetNowNextQuery and returning a NowNextDTO; the id-less form exists because the home-screen widget has no event id to pass. Cache eviction across the two event controllers is - proportional to blast radius: an ordinary event write evicts only conference:events - (EventsController.cs:202,241, EventLifecycleController.cs:70,103), a delete also evicts sessions - and rooms (EventsController.cs:254-255), and a Sessionize refresh evicts all six tags it can touch + proportional to blast radius: a create evicts only conference:events (EventsController.cs:209), + an update, publish or unpublish adds the conference umbrella tag (EventsController.cs:248, + EventLifecycleController.cs:70,103), a delete also evicts sessions and rooms + (EventsController.cs:261-262), and a Sessionize refresh evicts all six tags it can touch (EventLifecycleController.cs:142-149). [Rubric §12, Performance & Scalability] is the lens for the whole caching story.

    Two more deviations, versioning and decision support

    ServiceInfoController exists to prove the API-versioning machinery works @@ -639,7 +661,7 @@

    The gRPC edge, Confe (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Contracts/SessionBookmarkValidationServiceGrpcAdapter.cs:27-29), and EventLiveValidationServiceGrpcAdapter does the same for all four IEventLiveValidationService methods - (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Contracts/EventLiveValidationServiceGrpcAdapter.cs:27-29,37,66,99,128), + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Contracts/EventLiveValidationServiceGrpcAdapter.cs:27-29,37,66,122,151), converting the Unix-second live-window fields back into UTC DateTimes. Both pin a 5-second per-call deadline on every RPC (SessionBookmarkValidationServiceGrpcAdapter.cs:35, EventLiveValidationServiceGrpcAdapter.cs:34), much tighter than the shared resilience pipeline's 30s @@ -648,11 +670,15 @@

    The gRPC edge, Confe Conference peer must fail fast rather than hold the caller hostage (EventLiveValidationServiceGrpcAdapter.cs:31-33). Both catch RpcException and reverse the mapping with the framework's own RpcException.ToResult decoder - (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:210,234), which reads the + (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:216,240), which reads the trailers back into Error instances and degrades a pure transport fault (connection reset, deadline exceeded) to a single Grpc.{StatusCode} failure sourced with the calling method's name (SessionBookmarkValidationServiceGrpcAdapter.cs:58,85, - EventLiveValidationServiceGrpcAdapter.cs:60). Because both the in-process implementation and each + EventLiveValidationServiceGrpcAdapter.cs:61). The live-validation adapter applies the same rule to + wire data it cannot map: a speaker id that is not a GUID or an undefined moderation-default value + returns a Grpc.MalformedResponse failure built the way the decoder builds a transport fault, never an + exception that would escape the RpcException catch + (EventLiveValidationServiceGrpcAdapter.cs:80-101,187-188). Because both the in-process implementation and each adapter satisfy the same interface, swapping a co-located module for a remote service is a registration change, not a rewrite (ADR-007; [Rubric §7, Microservices Readiness]).

    @@ -672,7 +698,7 @@

    The gRPC edge, Confe also consumes Engagement's IBookmarkCountService, so the Conference host registers AddEngagementBookmarkCountClient() - (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:409) and the AppHost deliberately + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:411) and the AppHost deliberately gives only the Engagement-to-Conference edge a startup WaitFor, leaving the reverse edge a plain WithReference so the pair cannot deadlock; transient "peer not ready" errors self-heal through the resilience pipeline (MMCA.ADC/Source/Hosting/MMCA.ADC.AppHost/Program.cs:271,274; @@ -681,17 +707,17 @@

    The service hos

    The MMCA.ADC.Conference.Service Program.cs boots only the Conference module. Kestrel is configured before anything else, and the whole of it is one line: builder.ConfigureEndpointsWithHealthProbe(HttpProtocols.Http2) - (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:89), the shared extension from + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:91), the shared extension from Common's KestrelEndpointExtensions (G16). Passing HttpProtocols.Http2 sets every endpoint default to HTTP/2-only on cleartext (h2c prior knowledge), so cross-service gRPC clients negotiate HTTP/2 without TLS or ALPN; on a cleartext endpoint Http1AndHttp2 would effectively disable HTTP/2 and Kestrel would - reject gRPC frames with GOAWAY HTTP_1_1_REQUIRED (Program.cs:79-87). That transport choice is + reject gRPC frames with GOAWAY HTTP_1_1_REQUIRED (Program.cs:81-89). That transport choice is ADR-012. The same helper adds a dedicated HTTP/1.1-only listener for the ACA httpGet probes when HealthProbe:Port is configured, because the h2c-only endpoint rejects the platform's HTTP/1.1 probe requests. The rest of the host is the standard ADC REST composition: Serilog registered as one provider rather than through - UseSerilog() so the OpenTelemetry-to-Azure-Monitor provider survives (Program.cs:104-105), an + UseSerilog() so the OpenTelemetry-to-Azure-Monitor provider survives (Program.cs:106-107), an optional Key Vault configuration source layered in before anything binds settings (:109), the Conference-owned MMCA.ADC.Conference.Scoring meter (:119), health checks with a relational database required (:163), CORS, API versioning and rate limiting (:166-168), response compression (:259), @@ -701,7 +727,7 @@

    The service hos ADR-004, ADR-019, ADR-079).

    -

    Output caching is where this host carries the most bespoke configuration (Program.cs:228-297). The +

    Output caching is where this host carries the most bespoke configuration (Program.cs:227-296). The base policy is deny-by-default NoCache (:213), so only explicitly decorated endpoints cache at all. ConferenceCache stays on the built-in default semantics because the permission-gated SessionSelectionController references it, and @@ -727,7 +753,7 @@

    The service hos real traffic.

    Two mechanisms close the distance that TTLs alone cannot. First, at two replicas the store itself must be shared: when a Redis connection string is present the host backs the output cache with Redis as - well as the distributed cache (Program.cs:183,193), because the default per-replica memory store + well as the distributed cache (Program.cs:185,195), because the default per-replica memory store meant an eviction reached only the replica that served the mutation while the other kept serving the pre-edit payload for the full TTL; the same branch adds a two-level cache, an in-process L1 over the Redis L2 under a disjoint keyspace, so a repeat read inside one replica never leaves the process while @@ -744,19 +770,22 @@

    The service hos ErrorLocalizer (ADR-027).

    One more startup extension point matters: SelfHttpOutputCacheWarmupTask, - registered via AddWarmupTask<T>() (Program.cs:310) as an + registered via AddWarmupTask<T>() (Program.cs:309) as an ADR-025 IWarmupTask. The task itself is almost empty: it derives from SelfHttpWarmupTaskBase (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/SelfHttpOutputCacheWarmupTask.cs:22-28) and - contributes only a name (:59) and a list of paths (:62), while the base owns the request machinery + contributes only a name (:65) and a list of paths (:68), while the base owns the request machinery (waiting for the server to start, resolving the actually-bound cleartext port, pinning HTTP/2 prior knowledge, and treating a failure as non-fatal). The paths are the interesting part, and there are - eight of them in two families (SelfHttpOutputCacheWarmupTask.cs:42-56), because OutputCache keys + eight of them in two families (SelfHttpOutputCacheWarmupTask.cs:45-59), because OutputCache keys on the full URL and a warmed entry is only ever hit by a byte-identical query string: family one mirrors the Blazor list pages, whose service base interpolates C# bools and so writes capital - False/True; family two mirrors the hand-written lookup services, which write lowercase literals and - pageSize=10000 (:30-41). Warming one family left the other paying a cold read on its first real + False/True; family two mirrors the hand-written lookup services, which page through /paged in id + order with lowercase literals and pageSize=500 (:30-44,55-58). Those lookup URLs are built by + Common's PagedReadAll.LookupPageUrl, and the task exposes its list as RequestedPaths (:62) so + SelfHttpOutputCacheWarmupTaskTests can pin every lookup path to that builder's output + (:38-39). Warming one family left the other paying a cold read on its first real caller. Every path is [AllowAnonymous], so the base's require-success loop sees 200 and skips nothing.

    The runtime picture, one host, two transports

    @@ -851,7 +880,7 @@

    AddCategoryItemRequest

    The codebase keeps three distinct shapes in every write path: the request record (what the HTTP client sends), the command (the application-layer message), and the entity (the domain object). The controller's CreateAsync does the manual hop - (CategoryItemsController.cs:134-139): it reads request fields and constructs the command positionally. + (CategoryItemsController.cs:137-142): it reads request fields and constructs the command positionally. That is the manual-mapping policy of ADR-001 applied at the inbound edge, no reflective mapper between the wire and the application. [Rubric §1, SOLID] (interface segregation): @@ -867,12 +896,12 @@

    AddCategoryItemRequest

    errors named after real domain terms; separating add from update (rather than one nullable-everything record) keeps each contract honest about what is mutable.
  • Where it's used: bound by CategoryItemsController's [FromBody] - create parameter only (CategoryItemsController.cs:130-131). That action is marked + create parameter only (CategoryItemsController.cs:133-134). That action is marked [Idempotent] - (CategoryItemsController.cs:129), so a retried POST carrying the same Idempotency-Key replays the + (CategoryItemsController.cs:132), so a retried POST carrying the same Idempotency-Key replays the first response instead of adding a second row: [Rubric §9, API & Contract Design], the replay contract is declared on the action because this create is hand-written rather than inherited from the CRUD base - (CategoryItemsController.cs:121-127).
  • + (CategoryItemsController.cs:124-130).

    UpdateCategoryItemRequest

    @@ -891,15 +920,15 @@

    UpdateCategoryItemRequest

    AddCategoryItemRequest). The item id to update is not in the body, it is the route's {id}; UpdateAsync threads the route id into the command (new UpdateCategoryItemCommand(request.CategoryId, id, request.Name, request.Sort), - CategoryItemsController.cs:161-166). Unlike the create, this action carries no - [Idempotent] attribute (CategoryItemsController.cs:154-156): a PUT that sets an item to a stated + CategoryItemsController.cs:164-169). Unlike the create, this action carries no + [Idempotent] attribute (CategoryItemsController.cs:157-159): a PUT that sets an item to a stated name and sort is naturally repeatable, so there is nothing to replay-protect.
  • Walkthrough: three required { get; init; } properties. CategoryId is carried on update so the handler can re-check ownership of the parent before mutating.
  • Where it's used: [FromBody] on CategoryItemsController's - UpdateAsync (CategoryItemsController.cs:156-158); on success the action evicts + UpdateAsync (CategoryItemsController.cs:159-161); on success the action evicts conference:categories and conference and returns NoContent() - (CategoryItemsController.cs:174-175).
  • + (CategoryItemsController.cs:177-178).

    CategoryItemsController

    @@ -942,7 +971,7 @@

    CategoryItemsController

    which already supplies GET, GET /paged, GET /lookup, GET /{id} and GET /export, then hand-writes its own POST, PUT and DELETE whose commands carry the owning CategoryId. Each read is overriden only to re-decorate the action with [AllowAnonymous] and a cache policy before delegating - straight back (=> base.GetAllAsync(...), :80): the attributes have to sit on the derived method for + straight back (=> base.GetAllAsync(...), :83): the attributes have to sit on the derived method for MVC to see them, which is the whole reason these bodies exist. Each write maps its request record onto exactly one command and folds any failure through the inherited HandleFailure, the one-handler-per-action shape of CQRS at the edge. [Rubric §1, SOLID] and [Rubric §15, Best Practices & Code Quality]: because the read @@ -951,30 +980,34 @@

    CategoryItemsController

  • Primary-constructor injection (CategoryItemsController.cs:63-69): the query service, the three command handlers (AddCategoryItemCommand -> Result<CategoryItemDTO>, UpdateCategoryItemCommand -> Result, RemoveCategoryItemCommand -> Result), the output-cache store and the logger; the base call passes the query service and logger to EntityControllerBase (:70).
  • -
  • The four read overrides (:72-119) each add [AllowAnonymous] (re-opening the class-level capability +
  • protected override bool AllowUnscopedExport => true (:73): the deliberate opt-in to a whole-table + export, documented as reference data every reader lists in full, with the export gated by + CategoriesManage (:72). The base default is false + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:508).
  • +
  • The four read overrides (:75-122) each add [AllowAnonymous] (re-opening the class-level capability gate for reads) and [OutputCache(PolicyName = "CategoriesCache")], then forward to the base: - GetAllAsync (:75-80), the paged GetAllAsync with the - [ModelBinder(typeof(QueryFilterModelBinder))] filter dictionary (:85-95), GetAllForLookupAsync - (:100-103) and GetByIdAsync under the named route "GetCategoryItemById" (:105-119).
  • -
  • CreateAsync (:130): [HttpPost] (:128) and [Idempotent] (:129), binds + GetAllAsync (:78-83), the paged GetAllAsync with the + [ModelBinder(typeof(QueryFilterModelBinder))] filter dictionary (:88-98), GetAllForLookupAsync + (:103-106) and GetByIdAsync under the named route "GetCategoryItemById" (:108-122).
  • +
  • CreateAsync (:133): [HttpPost] (:131) and [Idempotent] (:132), binds [FromBody] AddCategoryItemRequest, dispatches new AddCategoryItemCommand(request.CategoryId, request.CategoryItemId, request.Name, request.Sort) - (:135-139); on failure HandleFailure (:142-145), otherwise it evicts and returns - CreatedAtRoute("GetCategoryItemById", new { id = result.Value!.Id }, result.Value) (:148-151). The + (:138-142); on failure HandleFailure (:145-148), otherwise it evicts and returns + CreatedAtRoute("GetCategoryItemById", new { id = result.Value!.Id }, result.Value) (:151-154). The doc comment records why the replay contract is declared here rather than inherited: this create is - hand-written, not the base action (:121-127).
  • -
  • UpdateAsync (:156): [HttpPut("{id}")] (:155), binds the route id and + hand-written, not the base action (:124-130).
  • +
  • UpdateAsync (:159): [HttpPut("{id}")] (:158), binds the route id and [FromBody] UpdateCategoryItemRequest, dispatches - new UpdateCategoryItemCommand(request.CategoryId, id, request.Name, request.Sort) (:162-166), - evicts (:174), then NoContent() (:175).
  • -
  • DeleteAsync (:180): [HttpDelete("{id}")] (:179), binds the route id and - [FromQuery] ConferenceCategoryIdentifierType categoryId (:182), dispatches - new RemoveCategoryItemCommand(categoryId, id) (:186), evicts (:194), then NoContent(). The + new UpdateCategoryItemCommand(request.CategoryId, id, request.Name, request.Sort) (:165-169), + evicts (:177), then NoContent() (:178).
  • +
  • DeleteAsync (:183): [HttpDelete("{id}")] (:182), binds the route id and + [FromQuery] ConferenceCategoryIdentifierType categoryId (:185), dispatches + new RemoveCategoryItemCommand(categoryId, id) (:189), evicts (:197), then NoContent(). The parent id travels on the query string because a child delete needs its parent for ownership re-validation in the handler.
  • -
  • All three mutations end with outputCacheStore.EvictTagsAsync(cancellationToken, "conference:categories", "conference") (:147, 174, 194), and all three return HandleFailure +
  • All three mutations end with outputCacheStore.EvictTagsAsync(cancellationToken, "conference:categories", "conference") (:150, 177, 197), and all three return HandleFailure before the eviction, so a rejected command never disturbs the cache. [Rubric §12, Performance & Scalability]: without that call the cached reads would serve the pre-edit item list for the full - 5-minute policy TTL (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:278), so an + 5-minute policy TTL (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:277), so an organizer renaming a track would not see it on the public session pages until the entry expired.
  • @@ -996,10 +1029,12 @@

    CategoryItemsController

    SpeakerCategoryItemsController), which repeat this shape with different entities, cache tags and permissions.
  • Caveats / not-in-source: this controller applies no row scoping, so it overrides neither of the - framework read hooks and does not override the inherited /export. That export therefore streams the - whole item table, protected only by the class-level CategoriesManage capability, since the inherited - action carries no [AllowAnonymous] of its own - (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:204-209).
  • + framework read hooks and does not override the inherited /export. The base export is fail-closed: with + no read specification and no opt-in it answers a 403 and queries nothing + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:273-274). The + AllowUnscopedExport override (CategoryItemsController.cs:73) is what lets the export stream the whole + item table, protected only by the class-level CategoriesManage capability, since the inherited action + carries no [AllowAnonymous] of its own (EntityControllerBase.cs:252-257).

    ConferenceCategoriesController

    @@ -1011,60 +1046,66 @@

    ConferenceCategoriesController

    aggregate root, served at the custom route conferencecategories (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Categories/ConferenceCategoriesController.cs:33) so it cannot collide with another module's categories route. Anonymous reads, capability-gated create, - update and delete ([HasPermission(ConferencePermissions.CategoriesManage)], :34). This is the first + update and delete ([HasPermission(ConferencePermissions.CategoriesManage)], :35). This is the first aggregate-root controller in the group, so it establishes the shape the other full-CRUD controllers reuse.
  • Depends on: AggregateRootEntityControllerBase<TEntity, TEntityDTO, TIdentifierType, TCreateRequest> (the CRUD base, ConferenceCategoriesController.cs:43-44); the IEntityQueryService - (:36); a create handler keyed on + (:37); a create handler keyed on ConferenceCategoryCreateRequest - (:37); an update handler keyed on the framework's + (:38); an update handler keyed on the framework's UpdateEntityCommand<TEntity, TUpdateRequest, TIdentifierType> - (:38); a delete handler keyed on + (:39); a delete handler keyed on DeleteEntityCommand<TEntity, TIdentifierType> - (:39); IOutputCacheStore (:40); the + (:40); IOutputCacheStore (:41); the ConferenceCategoryDTO; the PUT body ConferenceCategoryUpdateRequest - (:120); and the SupportsIfMatchAttribute.
  • + (:124); and the SupportsIfMatchAttribute.
  • Concept introduced, the aggregate-root controller. [Rubric §9, API & Contract Design] assesses consistent resource CRUD: an aggregate root gets a full, uniform REST surface, and AggregateRootEntityControllerBase supplies GetAll, GetById, GetAllForLookup, Export, Create and Delete from its constructor - slots (query service, create handler, delete handler, logger, :42-43). The subclass then writes only + slots (query service, create handler, delete handler, logger, :43-44). The subclass then writes only policy, cache eviction, and the one action the base does not supply. Note that the create request type is the handler's command: ConferenceCategoryCreateRequest is passed straight into the create - handler's ICommandHandler slot (:37), + handler's ICommandHandler slot (:38), the create-from-request shape used across every Conference aggregate root.
  • Concept introduced, the required conditional write. [Rubric §9, API & Contract Design] also assesses whether a contract expresses concurrency honestly. The base has no update action, so UpdateAsync is hand-rolled, and it carries SupportsIfMatch - (:114) with [ProducesResponseType] for 409, 412 and 428 (:115-117). The token is read with - SupportsIfMatchAttribute.RequiredToken(HttpContext) (:123, declared at + (:118) with [ProducesResponseType] for 409, 412 and 428 (:119-121). The token is read with + SupportsIfMatchAttribute.RequiredToken(HttpContext) (:127, declared at MMCA.Common/Source/Presentation/MMCA.Common.API/Concurrency/SupportsIfMatchAttribute.cs:68), and the attribute's own doc comment states the contract: a request with no If-Match header answers 428 Precondition Required and a stale token answers 412 Precondition Failed - (ADR-035, doc at :105-112). The + (ADR-035, doc at :109-116). The update itself is the framework's generic UpdateEntityCommand - closed over Category, the update request and the identifier type (:126), so a category edit needs no + closed over Category, the update request and the identifier type (:130), so a category edit needs no bespoke command type at all.
  • Walkthrough
      -
    • The four reads (:45-92) are overrides that attach [AllowAnonymous] plus +
    • protected override bool AllowUnscopedExport => true (:47) opts the inherited /export in to a + whole-table export: categories are reference data every reader lists in full, and the export stays + gated by the class-level CategoriesManage capability (doc comment :46). Without it the base export + refuses with a 403 when no row scope resolves + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:273-274; default + false at :508).
    • +
    • The four reads (:49-96) are overrides that attach [AllowAnonymous] plus [OutputCache(PolicyName = "CategoriesCache")] and forward to the base, including the paged overload with the QueryFilterModelBinder filter - dictionary (:66) and GetByIdAsync under the named route "GetCategoryById" (:78).
    • -
    • CreateAsync (:96-103) and DeleteAsync (:138-145) are thin overrides that call + dictionary (:70) and GetByIdAsync under the named route "GetCategoryById" (:82).
    • +
    • CreateAsync (:99-107) and DeleteAsync (:141-149) are thin overrides that call base.CreateAsync / base.DeleteAsync and then evict, so the base does the CQRS dispatch and the override adds only the cache concern. Because the base returns an ActionResult rather than a Result, these two evict unconditionally, including after a rejected command: a cheap over-eviction, and the one place the failure-before-evict ordering used elsewhere in this unit cannot be applied.
    • -
    • UpdateAsync (:118-134) reads the required row version (:123), dispatches the generic update - command (:125-127), folds a failure through HandleFailure (:129-130), evicts (:132) and returns - Ok(result.Value) (:133).
    • -
    • All three mutations evict only the conference:categories tag (:101, 132, 143), unlike the child +
    • UpdateAsync (:122-138) reads the required row version (:127), dispatches the generic update + command (:129-131), folds a failure through HandleFailure (:133-134), evicts (:136) and returns + Ok(result.Value) (:137).
    • +
    • All three mutations evict only the conference:categories tag (:105, 136, 147), unlike the child CategoryItemsController, which also drops the broad conference tag.
  • @@ -1261,14 +1302,14 @@

    AddEventQuestionAnswerRequest

    OwnedByUserSpecification<TEntity, TIdentifierType> for anyone who is not an Organizer (BR-8, EventQuestionAnswersController.cs:96-97).
  • Walkthrough: three required { get; init; } properties, no methods. On add the controller passes - null for the answer's own id: new AddEventQuestionAnswerCommand(request.EventId, null, request.QuestionId, request.AnswerValue) (EventQuestionAnswersController.cs:234), so the domain mints + null for the answer's own id: new AddEventQuestionAnswerCommand(request.EventId, null, request.QuestionId, request.AnswerValue) (EventQuestionAnswersController.cs:238), so the domain mints the answer id.
  • Why it's built this way: naming the QuestionId on add (but not on update) encodes that you pick which question an answer belongs to once, at creation.
  • Where it's used: [FromBody] on EventQuestionAnswersController's - CreateAsync (EventQuestionAnswersController.cs:229-230), which is + CreateAsync (EventQuestionAnswersController.cs:233-234), which is [Idempotent] - (EventQuestionAnswersController.cs:228) so a retried submit cannot double-post an answer.
  • + (EventQuestionAnswersController.cs:232) so a retried submit cannot double-post an answer.

    AddEventSpeakerRequest

    @@ -1289,7 +1330,7 @@

    AddEventSpeakerRequest

    Speaker. There is no Update* sibling: a link either exists or does not, so the resource surface is add plus delete only. The controller passes null for the join entity's own id (new AddEventSpeakerCommand(request.EventId, null, request.SpeakerId), - EventSpeakersController.cs:169) so the domain mints the link id. [Rubric §9, API & Contract Design]: + EventSpeakersController.cs:177) so the domain mints the link id. [Rubric §9, API & Contract Design]: every property is required, so an incomplete association is rejected at binding.
  • Walkthrough: two required {Alias} { get; init; } members and nothing else; the doc comments name each role ("the event to add the speaker to", EventSpeakersController.cs:31).
  • @@ -1297,7 +1338,7 @@

    AddEventSpeakerRequest

    AddAssociationRequest<TParent, TChild>) keeps the schema and binding errors named after the real domain terms, the §9 readability win the codebase prefers over deduplication.
  • Where it's used: [FromBody] on EventSpeakersController's - CreateAsync (EventSpeakersController.cs:164-165), gated class-level by + CreateAsync (EventSpeakersController.cs:172-173), gated class-level by [HasPermission(ConferencePermissions.EventsManage)] (EventSpeakersController.cs:46) and marked [Idempotent] (:163). A successful add evicts three output-cache tags, conference:events, conference:speakers, and conference (:177). It is @@ -1326,14 +1367,14 @@

    AddRoomRequest

  • Walkthrough: three required members (EventId, Name, Sort) plus the optional explicit RoomId (RoomsController.cs:36) and four nullable physical fields (RoomsController.cs:44-54). CreateAsync spreads all eight fields positionally into the command - (RoomsController.cs:211-218) and, on success, evicts the conference:rooms output-cache tag before - returning CreatedAtRoute (RoomsController.cs:225-229).
  • + (RoomsController.cs:219-226) and, on success, evicts the conference:rooms output-cache tag before + returning CreatedAtRoute (RoomsController.cs:233-237).
  • Why it's built this way: modeling capacity, floor, location, and accessibility as discrete optional columns (rather than a free-text blob) keeps room metadata queryable and the contract self-documenting.
  • Where it's used: [FromBody] on RoomsController's CreateAsync - (RoomsController.cs:206-207), behind + (RoomsController.cs:214-215), behind [HasPermission(ConferencePermissions.RoomsManage)] (RoomsController.cs:91) and marked - [Idempotent] (RoomsController.cs:205).
  • + [Idempotent] (RoomsController.cs:213).

    BatchEventQuestionAnswerItemRequest

    @@ -1356,7 +1397,7 @@

    BatchEventQuestionAnswerItemRequest EventQuestionAnswersController.CreateBatchAsync projects each item into a BatchEventQuestionAnswerItem domain record (request.Answers.Select(a => new BatchEventQuestionAnswerItem(a.QuestionId, a.AnswerValue)), - EventQuestionAnswersController.cs:264). + EventQuestionAnswersController.cs:268).
  • Why it's built this way: keeping the item shape minimal (question plus answer, no event, no per-item id) mirrors that a batch submission is one form for one event, so the event id belongs on the envelope and the answer's own id is always minted by the domain, never supplied by the client.
  • @@ -1383,17 +1424,17 @@

    BatchAddEventQuestionAnswersRequest a feedback form has several questions, and the naive shape is one POST per answer, which leaves a half-submitted form on any single failure. BR-107 states the batch is applied atomically, every answer upserted under one transaction, so a refusal leaves nothing written and the client has no partial state - to reconcile (doc comment at EventQuestionAnswersController.cs:245-253). [Rubric §15, Best Practices & Code Quality]: this is the same [Idempotent] replay contract as the single-answer - create (EventQuestionAnswersController.cs:255), so a retried request with the same Idempotency-Key + to reconcile (doc comment at EventQuestionAnswersController.cs:249-257). [Rubric §15, Best Practices & Code Quality]: this is the same [Idempotent] replay contract as the single-answer + create (EventQuestionAnswersController.cs:259), so a retried request with the same Idempotency-Key replays the stored response rather than re-applying the form.
  • Walkthrough: two required { get; init; } properties, no methods. The controller maps Answers into - domain items before dispatch (EventQuestionAnswersController.cs:262-264); see + domain items before dispatch (EventQuestionAnswersController.cs:266-268); see EventQuestionAnswersController's walkthrough for the full call.
  • Why it's built this way: naming the event once on the envelope, rather than on every line, keeps a multi-question form's request body from repeating the same id per line; the transactional-upsert semantics make retry-after-partial-failure a non-issue for the caller.
  • Where it's used: [FromBody] on EventQuestionAnswersController's - CreateBatchAsync (EventQuestionAnswersController.cs:256-257), which is + CreateBatchAsync (EventQuestionAnswersController.cs:260-261), which is [Idempotent] (:255). BatchAddSessionQuestionAnswersRequest is its session-scoped sibling.
  • @@ -1415,11 +1456,11 @@

    UpdateEventQuestionAnswerRequest

    invariant: you can re-word an answer but not re-point it at a different question (that would be a delete and re-add). UpdateAsync uses the route {id} as the answer id (new UpdateEventQuestionAnswerCommand(request.EventId, id, request.AnswerValue), - EventQuestionAnswersController.cs:281) and returns NoContent() on success - (EventQuestionAnswersController.cs:284-286). + EventQuestionAnswersController.cs:285) and returns NoContent() on success + (EventQuestionAnswersController.cs:288-290).
  • Walkthrough: two required { get; init; } properties, no methods.
  • Where it's used: [FromBody] on EventQuestionAnswersController's - UpdateAsync (EventQuestionAnswersController.cs:275-277).
  • + UpdateAsync (EventQuestionAnswersController.cs:279-281).

    UpdateRoomRequest

    @@ -1436,13 +1477,13 @@

    UpdateRoomRequest

    UpdateRoomCommand.
  • Concept: the update half of the room contract (see AddRoomRequest); the room id comes from the route {id}. UpdateAsync spreads the seven body fields plus the route id into the - command (RoomsController.cs:239-248), then evicts the conference:rooms tag before returning - NoContent() (RoomsController.cs:254-255).
  • + command (RoomsController.cs:247-256), then evicts the conference:rooms tag before returning + NoContent() (RoomsController.cs:262-263).
  • Walkthrough: three required members plus four nullable optionals, no methods. Because every optional field is sent on every PUT, an omitted Capacity or Floor clears the stored value rather than leaving it untouched: this is a full replacement contract, not a patch.
  • Where it's used: [FromBody] on RoomsController's UpdateAsync - (RoomsController.cs:234-236).
  • + (RoomsController.cs:242-244).

    EventQuestionAnswersController

    @@ -1454,8 +1495,8 @@

    EventQuestionAnswersController

    public-catalog controllers in this group, every endpoint requires authentication ([Authorize], EventQuestionAnswersController.cs:77), and the reads are owner-scoped by BR-8: organizers see every answer, everyone else sees only their own. The four read actions additionally fail closed - (ADR-033) when a non-organizer caller carries no resolvable owner claim, answering 403 instead of the - crash a missing claim used to produce. + (ADR-033) when a non-organizer caller carries no resolvable owner claim, answering 403 rather than an + unscoped read or a 500.
  • Depends on: EntityControllerBase<TEntity, TEntityDTO, TIdentifierType> (the read-only base, EventQuestionAnswersController.cs:86); the @@ -1468,8 +1509,9 @@

    EventQuestionAnswersController

    and RemoveEventQuestionAnswerCommand (:80-83); ICurrentUserService and RoleNames for the scoping decision (:84); - OwnershipHelper, whose - GetOwnershipSpecification<TSpec, TId> resolves the export scope (:107-112); + OwnershipHelper, which supplies all three ownership decisions: + GetOwnershipSpecification<TSpec, TId> for the read scope (:107-112), IsAdmin for the export + opt-in (:119) and RequireResolvableOwner<TId> for the fail-closed gate (:135-143); OwnedByUserSpecification<TEntity, TIdentifierType> as the filter it builds; the IdempotentAttribute on its create and its batch create; the @@ -1483,55 +1525,63 @@

    EventQuestionAnswersController

    authorization is enforced server-side and whether results are scoped per caller rather than merely hidden in the UI; [Rubric §1, SOLID] and [Rubric §15, Best Practices & Code Quality] assess whether a rule has one home. The Common base exposes two hooks: - GetReadSpecificationAsync (asynchronous, MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:531-533) + GetReadSpecificationAsync (asynchronous, MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:571-573) and its synchronous half GetExportSpecification - (EntityControllerBase.cs:560), which the asynchronous one returns by default. Every read action + (EntityControllerBase.cs:601), which the asynchronous one returns by default. Every read action calls the asynchronous hook once per request, before the first query: GetAllAsync - (EntityControllerBase.cs:113), the paged overload (:170), GetAllForLookupAsync (:378), - GetByIdAsync (:422) and ExportAsync (:264). This controller overrides the synchronous half, + (EntityControllerBase.cs:116), the paged overload (:171), GetAllForLookupAsync (:324), + GetByIdAsync (:368) and ExportAsync (:270). This controller overrides the synchronous half, because the rule needs nothing awaited: GetExportSpecification() delegates to OwnershipHelper.GetOwnershipSpecification<OwnedByUserSpecification<EventQuestionAnswer, EventQuestionAnswerIdentifierType>, UserIdentifierType>, passing ClaimTypes.NameIdentifier as the owner claim, a factory that builds new OwnedByUserSpecification<EventQuestionAnswer, EventQuestionAnswerIdentifierType>(userId), and RoleNames.Organizer as the bypass role (EventQuestionAnswersController.cs:107-112), which is the same helper shape Store's OrdersController and ReviewsController use and ADC's Engagement AddModuleEngagementAPI passes to - OwnerOrAdminFilter (:69-73). The specification is composed into the database query, so + OwnerOrAdminFilter (doc :97-105). The specification is composed into the database query, so the scoping happens in SQL, paging counts stay honest, and nothing is filtered out of an already-fetched page. Two consequences the base states explicitly: the specification never replaces the caller's own filters (the query service ANDs the two, so a caller can only narrow what the rule already allows, - EntityControllerBase.cs:514-520), and a GetById the specification rejects answers 404, not 403, - because a "forbidden" would confirm the id exists (EntityControllerBase.cs:521-525). Because one + EntityControllerBase.cs:554-560), and a GetById the specification rejects answers 404, not 403, + because a "forbidden" would confirm the id exists (EntityControllerBase.cs:561-565). Because one hook override cannot itself prevent a missing owner claim from reaching the query as null, the four read actions carry a guard-plus-passthrough body rather than the pure passthrough an unguarded override - would allow (EventQuestionAnswersController.cs:107-112, action bodies at :141-195).
  • + would allow (EventQuestionAnswersController.cs:107-112, action bodies at :145-199).
  • Concept introduced, the fail-closed resolvable-owner gate. [Rubric §11, Security] and [Rubric §15, Best Practices & Code Quality]: GetExportSpecification() returns null for two different reasons, an Organizer caller (scoping deliberately skipped) or a non-Organizer caller whose - owner claim cannot be resolved, and only the first may read unscoped. The previous shape dereferenced - UserId!.Value for the second case and answered an unhandled exception (500); a missing claim is an - authorization answer, so RequireResolvableOwner() (:125-138) checks - currentUserService.IsInRole(RoleNames.Organizer) || currentUserService.GetClaimValue<UserIdentifierType>(ClaimTypes.NameIdentifier) is not null and - returns null to proceed or HandleFailure([Error.Forbidden(...)]) (:133-137), a 403, otherwise. This - is the same 2026-08-31 resolvable-owner rule ADR-033 states as Store's - OrdersController.RequireResolvableOwner.
  • + owner claim cannot be resolved, and only the first may read unscoped. A missing claim is an + authorization answer, so RequireResolvableOwner() (:135-143) delegates to the framework's + OwnershipHelper.RequireResolvableOwner<UserIdentifierType>, passing ClaimTypes.NameIdentifier, + RoleNames.Organizer and the controller and entity names as the error source and target + (:136-141). The helper succeeds when IsAdmin(currentUserService, bypassRole) holds or the claim + parses, and otherwise fails with Error.Forbidden ("Access denied.") + (MMCA.Common/Source/Presentation/MMCA.Common.API/Authorization/OwnershipHelper.cs:91-104); the + controller turns a failed result into HandleFailure(gate.Errors), a 403, and a success into null + so the read proceeds (EventQuestionAnswersController.cs:141-143). Because the helper checks the + bypass role with the same IsAdmin that GetOwnershipSpecification uses (OwnershipHelper.cs:46, 101), the gate and the scope agree on who is privileged (doc :129-131). This is the same 2026-08-31 + resolvable-owner rule ADR-033 states as Store's OrdersController.RequireResolvableOwner.
  • Concept introduced, closing the CSV export as a row-scoping bypass. The base ships a streaming CSV - endpoint, ExportAsync (EntityControllerBase.cs:249), whose remarks state the hazard plainly: it - carries no authorization attributes of its own and inherits whatever the concrete controller declares - (:205-211), and its rows are whatever the read hook allows, which is null by default (:229-235). - A controller that row-scopes its lists but leaves both hooks at the default therefore hands every caller - the whole table in one request (:614-619). This controller closes that twice over: the hook override - above now scopes the export exactly like the list, and ExportAsync is additionally overridden to - Forbid() unless the caller is an organizer, then delegate to the base - (EventQuestionAnswersController.cs:202-218, gate at :212-215). [Rubric §30, Compliance/Privacy/Data Governance] assesses whether personal data has a single governed exit path: - feedback answers are attributable personal content, so bulk download stays with the role that already - reads every row.
  • + endpoint, ExportAsync (EntityControllerBase.cs:257), whose remarks state that it carries no + authorization attributes of its own and inherits whatever the concrete controller declares + (:207-211). Its row scoping is fail-closed (:231-238): the rows are whatever the read hook + allows, and when that hook resolves to null the export is refused with a 403 carrying + Export.RowScopeRequired (:487, :273-274, :608-612) unless the controller opts in to whole-table + exports through AllowUnscopedExport, which is false by default (:508). That null is ambiguous + here for the same two reasons as the read gate, so this controller opts in only for the Organizer + bypass: AllowUnscopedExport => OwnershipHelper.IsAdmin(currentUserService, RoleNames.Organizer) + (EventQuestionAnswersController.cs:119, doc :114-118), and a non-Organizer with an unresolvable + owner claim keeps the framework's 403. On top of that, ExportAsync is overridden to Forbid() unless + currentUserService.IsInRole(RoleNames.Organizer), then delegate to the base (:207-222, gate at + :216-219). [Rubric §30, Compliance/Privacy/Data Governance] assesses whether personal data has a + single governed exit path: feedback answers are attributable personal content, so bulk download stays + with the role that already reads every row.
  • Concept introduced, atomic multi-row upsert as a batch endpoint. [Rubric §9, API & Contract Design] and [Rubric §29, Resilience & Business Continuity]: a feedback form has several questions; - CreateBatchAsync (:256) lets the client submit the whole form as one [HttpPost("batch")] call - (:254) instead of one request per answer, so a mid-form failure never leaves a half-saved response for - the client to reconcile (BR-107, doc :245-253). It is [Idempotent] (:255) with the same replay + CreateBatchAsync (:260) lets the client submit the whole form as one [HttpPost("batch")] call + (:258) instead of one request per answer, so a mid-form failure never leaves a half-saved response for + the client to reconcile (BR-107, doc :249-257). It is [Idempotent] (:259) with the same replay contract as the single-answer create: a retry carrying the same Idempotency-Key replays the stored response rather than re-applying the form. The action maps each BatchEventQuestionAnswerItemRequest into a domain - BatchEventQuestionAnswerItem (:262-264) and dispatches one BatchAddEventQuestionAnswersCommand, + BatchEventQuestionAnswerItem (:266-268) and dispatches one BatchAddEventQuestionAnswersCommand, so the atomicity guarantee lives in the handler's single transaction, not in the controller.
  • Walkthrough
    • Primary-constructor injection (EventQuestionAnswersController.cs:78-85): query service, four @@ -1539,30 +1589,31 @@

      EventQuestionAnswersController

      constructed with (queryService, logger) (:86).
    • GetExportSpecification() (:107-112): the OwnershipHelper delegation described above, with the doc comment at :88-106.
    • -
    • RequireResolvableOwner() (:125-138): the fail-closed gate, with the doc comment at :114-124.
    • -
    • The four reads (:141-195) are guard-plus-passthrough: each calls RequireResolvableOwner() first and, +
    • AllowUnscopedExport (:119): the Organizer-only export opt-in, with the doc comment at :114-118.
    • +
    • RequireResolvableOwner() (:135-143): the fail-closed gate, with the doc comment at :121-134.
    • +
    • The four reads (:145-199) are guard-plus-passthrough: each calls RequireResolvableOwner() first and, if it returns non-null, returns that result instead of calling the base action. GetAllAsync - (:141-152), the paged overload with the + (:145-156), the paged overload with the QueryFilterModelBinder filter dictionary - (:154-170), GetAllForLookupAsync (:172-181) and GetByIdAsync under the named route - "GetEventQuestionAnswerById" (:183-195). Note the absence of any [OutputCache] attribute anywhere + (:158-174), GetAllForLookupAsync (:176-185) and GetByIdAsync under the named route + "GetEventQuestionAnswerById" (:187-199). Note the absence of any [OutputCache] attribute anywhere in the file: a per-caller response must never land in a shared cache entry, and the controller simply never opts in.
    • -
    • ExportAsync (:202-218): the organizer gate, Forbid() at :214, otherwise base.ExportAsync(...) - at :217. Its doc comment carries the reasoning (:197-201).
    • -
    • CreateAsync (:229) carries [HttpPost] (:227) and [Idempotent] (:228), so a retried POST with +
    • ExportAsync (:207-222): the organizer gate, Forbid() at :218, otherwise base.ExportAsync(...) + at :221. Its doc comment carries the reasoning (:201-206).
    • +
    • CreateAsync (:233) carries [HttpPost] (:231) and [Idempotent] (:232), so a retried POST with the same Idempotency-Key replays the stored response instead of writing a second answer row; the doc comment records that the attribute is declared here because this create is hand-written rather than the - base action (:220-226). It dispatches + base action (:224-230). It dispatches new AddEventQuestionAnswerCommand(request.EventId, null, request.QuestionId, request.AnswerValue) - (:234), the null being the child id the domain mints, then CreatedAtRoute (:239-242).
    • -
    • CreateBatchAsync (:256) is the batch sibling of CreateAsync, described above; see + (:238), the null being the child id the domain mints, then CreatedAtRoute (:243-246).
    • +
    • CreateBatchAsync (:260) is the batch sibling of CreateAsync, described above; see BatchAddEventQuestionAnswersRequest.
    • -
    • UpdateAsync (:275) dispatches - new UpdateEventQuestionAnswerCommand(request.EventId, id, request.AnswerValue) (:281) and returns - NoContent() (:286).
    • -
    • DeleteAsync (:291) takes the parent eventId [FromQuery] (:293) because the route only carries - the child id, dispatches RemoveEventQuestionAnswerCommand(eventId, id) (:297) and returns +
    • UpdateAsync (:279) dispatches + new UpdateEventQuestionAnswerCommand(request.EventId, id, request.AnswerValue) (:285) and returns + NoContent() (:290).
    • +
    • DeleteAsync (:295) takes the parent eventId [FromQuery] (:297) because the route only carries + the child id, dispatches RemoveEventQuestionAnswerCommand(eventId, id) (:301) and returns NoContent(). [Rubric §9, API & Contract Design]: none of the write records carries a UserId; identity comes from the authenticated principal and CreatedBy is stamped by the audit pipeline, never trusted from the client.
    • @@ -1571,11 +1622,12 @@

      EventQuestionAnswersController

    • Why it's built this way: BR-8 mandates that non-organizers see only their own answers, so the rule is expressed once as a specification injected into the query pipeline rather than as a filter applied after the fact, and ADR-033 mandates that the resolution of who "own" means fails closed (403) rather - than crashing when the caller has no owner claim to resolve. Organizers bypass both the scoping - specification and the gate through the same role check, which is the shape every visibility rule in - this group uses. The parent EventId travels on every write so the handler can load the - Event aggregate and mutate the child through it; the batch - endpoint exists because a feedback form is naturally one submission, not one request per question.
    • + than crashing when the caller has no owner claim to resolve. Organizers bypass the scoping + specification, the gate and the export refusal through the same OwnershipHelper.IsAdmin check, which + is the shape every visibility rule in this group uses. The parent EventId travels on every write so + the handler can load the Event aggregate and mutate the child + through it; the batch endpoint exists because a feedback form is naturally one submission, not one + request per question.
    • Where it's used: hosted by the Conference service and reached through the YARP Gateway route /EventQuestionAnswers/{**catch-all} (MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/appsettings.json:118, @@ -1585,11 +1637,14 @@

      EventQuestionAnswersController

      session-scoped sibling (BR-9), built the same way. See ADR-033 for the ownership-authorization rule and the resolvable-owner fail-closed addition.
    • -
    • Caveats / not-in-source: the role gate on the export is now belt-and-braces rather than the only - scoping, since the hook filters the export too. The base's remarks state that a controller which - overrides the hook may relax such an interim role gate so an owner can export their own rows again - (EntityControllerBase.cs:554-558); this controller keeps the gate, so an attendee cannot export their - own answers at all. Whether that is deliberate is not determinable from source.
    • +
    • Caveats / not-in-source: the role gate on the export is belt-and-braces rather than the only + scoping, since the hook filters the export too and the base refuses an unscoped export the controller + has not opted in to. The base's remarks state that a controller which overrides the hook may relax + such an interim role gate so an owner can export their own rows again + (EntityControllerBase.cs:595-599); this controller keeps the gate, so an attendee cannot export their + own answers at all. The override's gate reads currentUserService.IsInRole(RoleNames.Organizer) + (:216) while the opt-in reads OwnershipHelper.IsAdmin, which compares currentUserService.Role + (OwnershipHelper.cs:21). Whether keeping the gate is deliberate is not determinable from source.

    EventLifecycleController

    @@ -1598,12 +1653,12 @@

    EventLifecycleController

    • What it is: the REST controller for an event's publication lifecycle: publish, unpublish, and the - Sessionize refresh import. These three actions used to live on - EventsController and now have their own controller, so the aggregate-root CRUD - controller no longer also owns state-transition and external-import concerns.
    • + Sessionize refresh import. These three actions live here rather than on + EventsController, so the aggregate-root CRUD controller does not also own + state-transition and external-import concerns.
    • Depends on: ApiControllerBase (the bare API base, not the entity-aware EntityControllerBase the CRUD controllers derive from, because this controller has no query service and no DTO list to serve, - EventLifecycleController.cs:32, 37); three + EventLifecycleController.cs:32,37); three ICommandHandlers for PublishEventCommand, UnpublishEventCommand and @@ -1620,24 +1675,29 @@

      EventLifecycleController

      (ADR-035; both carry [SupportsIfMatch] at :53, 86 and declare 409/412/428 as [ProducesResponseType] at :54-56, 87-89). Both are also [Idempotent] (:52, 85), because publishing is a state assertion and replaying the stored response for a retried key is exactly what the caller meant (doc :39-50, 74-83). - [Rubric §29, Resilience & Business Continuity]: RefreshAsync (:107-151) maps upstream trouble to + [Rubric §29, Resilience & Business Continuity]: RefreshAsync (:107-152) maps upstream trouble to retryable HTTP, an Event.Sessionize.Throttled error becoming 429 with a Retry-After: 300 header (:128-132, BR-63) and Event.Sessionize.Unavailable becoming 502 (:135-136), so an upstream throttle reaches the client as a signal rather than a 500.
    • Walkthrough
      • PublishAsync (:51-72) reads the required token (:61), dispatches - new PublishEventCommand(id, rowVersion) (:63-65), evicts conference:events (:70) and returns - NoContent().
      • -
      • UnpublishAsync (:84-105) is the same shape: token (:94), new UnpublishEventCommand(id, rowVersion) (:96-98), evict (:103), NoContent().
      • -
      • RefreshAsync (:107-151) dispatches new RefreshFromSessionizeCommand(id) (:121-123); on failure + new PublishEventCommand(id, rowVersion) (:63-65), evicts conference:events and the broad + conference tag (:70) and returns NoContent().
      • +
      • UnpublishAsync (:84-105) is the same shape: token (:94), new UnpublishEventCommand(id, rowVersion) (:96-98), evict the same two tags (:103), NoContent().
      • +
      • RefreshAsync (:107-152) dispatches new RefreshFromSessionizeCommand(id) (:121-123); on failure it maps the two named Sessionize error codes to 429 (with Retry-After: 300, :128-132) and 502 - (:135-136) before falling back to HandleFailure (:125-139); on success it evicts six output-cache - tags because the import touches six entity types (events, sessions, speakers, categories, rooms, - questions, :141-149) and returns Ok(result.Value).
      • + (:135-136) before falling back to HandleFailure (:125-139); on success it evicts the six + entity tags the import touches (events, sessions, speakers, categories, rooms, questions) plus the + broad conference tag (:141-150) and returns Ok(result.Value) (:151). +
      • The broad conference tag is the one every Conference output-cache policy carries + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:236-237, 267-284), so evicting it + also drops the entries of ConferenceCache and ConferencePublicCache, which carry no + entity-specific tag (Program.cs:237, 267) and would otherwise serve the pre-transition event + until their 5-minute TTL ran out.
    • Why it's built this way: publish, unpublish and the Sessionize refresh are lifecycle operations on - an already-created event, not CRUD, and none of them return an EventDTO; separating them from + an already-created event, not CRUD, and none of them return an EventDTO; keeping them off EventsController keeps that controller to create/read/update/delete/export and keeps this one focused on state transitions and external synchronization, each with its own concurrency and error-mapping story.
    • @@ -1690,39 +1750,44 @@

      EventSpeakersController

      GetPublicEventSpeakerFilterQuery handler, which resolves the published-event id list in the Application layer (:78-81); a failed handler result degrades to null rather than failing the read (:81). The hook has to be asynchronous here precisely because the rule is resolved through a query handler, which is - the case the base's remarks describe (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:505-513). + the case the base's remarks describe (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:545-553). + Because that null is the base's signal for "no row scope", the controller also declares + AllowUnscopedExport => IsPrivileged (:90, doc :84-89): the base's fail-closed CSV export refuses + a null scope with 403 unless this opt-in holds + (EntityControllerBase.cs:273-274, 508), so only the privileged audience may export the whole table. [Rubric §12, Performance & Scalability]: the reads are cached under the EventsCache policy (5-minute TTL, tags conference and conference:events, - MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:269), which is exactly why the writes + MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:268), which is exactly why the writes must evict. That policy is a PublicEndpointOutputCachePolicy registration and it bypasses the cache entirely for the privileged read audience (ADR-040, - Program.cs:240-267), which is what keeps an organizer's everything-inclusive payload out of the shared + Program.cs:239-266), which is what keeps an organizer's everything-inclusive payload out of the shared public entry.
    • Walkthrough
      • GetReadSpecificationAsync (EventSpeakersController.cs:72-82) is the one place the rule lives; the doc comment above it says so, and says the read actions below are attribute-only passthroughs (:60-71).
      • -
      • GetAllAsync (:87-92), the paged overload (:97-107), GetAllForLookupAsync (:116-119) and - GetByIdAsync under the named route "GetEventSpeakerById" (:121-130) each carry +
      • AllowUnscopedExport (:90) is the export opt-in for the same privileged audience the hook exempts.
      • +
      • GetAllAsync (:92-100), the paged overload (:102-115), GetAllForLookupAsync (:121-127) and + GetByIdAsync under the named route "GetEventSpeakerById" (:129-138) each carry [AllowAnonymous] + [OutputCache(PolicyName = "EventsCache")] and delegate to the base. The lookup's - doc comment (:109-112) names the side channel the hook closes: without the shared scope, a dropdown + doc comment (:117-120) names the side channel the hook closes: without the shared scope, a dropdown would enumerate the names the list endpoint hides. The base forwards the specification's Criteria predicate as the lookup filter, because the lookup query has no specification parameter - (EntityControllerBase.cs:514-520).
      • -
      • ExportAsync (:139-153) repeats the privileged-reader gate: if (!IsPrivileged) return Forbid(); - (:147-150), then base.ExportAsync(...) (:152). The doc comment states the leak an unscoped CSV + (EntityControllerBase.cs:554-560).
      • +
      • ExportAsync (:146-161) repeats the privileged-reader gate: if (!IsPrivileged) return Forbid(); + (:155-158), then base.ExportAsync(...) (:160). The doc comment states the leak an unscoped CSV would be: the junction rows of unpublished events, leaking exactly the existence the reads hide - (:132-137).
      • -
      • CreateAsync (:164) is [HttpPost] (:162) and [Idempotent] (:163), dispatches - AddEventSpeakerCommand(request.EventId, null, request.SpeakerId) (:169), returns HandleFailure - on failure (:172-175), then evicts and returns CreatedAtRoute("GetEventSpeakerById", ...) - (:177-181).
      • -
      • DeleteAsync (:186) reads the parent eventId [FromQuery] (:188), dispatches - RemoveEventSpeakerCommand(eventId, id) (:192), evicts (:200) and returns NoContent().
      • + (:140-145). +
      • CreateAsync (:172) is [HttpPost] (:170) and [Idempotent] (:171), dispatches + AddEventSpeakerCommand(request.EventId, null, request.SpeakerId) (:177), returns HandleFailure + on failure (:180-183), then evicts and returns CreatedAtRoute("GetEventSpeakerById", ...) + (:185-189).
      • +
      • DeleteAsync (:194) reads the parent eventId [FromQuery] (:196), dispatches + RemoveEventSpeakerCommand(eventId, id) (:200), evicts (:208) and returns NoContent().
      • Both mutations evict both parents' tags plus the broad one: EvictTagsAsync(cancellationToken, "conference:events", "conference:speakers", "conference") - (:177, 200). Note the ordering guard: the failure return happens before the eviction, so a rejected + (:185, 208). Note the ordering guard: the failure return happens before the eviction, so a rejected command never disturbs the cache.
      • Error-to-HTTP translation is inherited from ApiControllerBase.HandleFailure.
      • @@ -1774,43 +1839,48 @@

        RoomsController

        handler; a failed handler result degrades to null rather than failing the read (:129). Because the caller's own EventId filter goes through the generic filter pipeline unchanged, the two predicates are composed by the query service rather than substituted, so scoping to an unpublished event returns an - empty page instead of that event's rooms. The doc comments at :106-118 and :146-151 state exactly + empty page instead of that event's rooms. The doc comments at :106-118 and :154-159 state exactly that contract. Compare SpeakersController, where EventId is not a column and the paged action must intercept the key by hand.
      • Concept introduced, output-cache eviction on mutation. [Rubric §12, Performance & Scalability] assesses caching strategy: every read here is decorated [OutputCache(PolicyName = "RoomsCache")] - (RoomsController.cs:138,154,175,188), so anonymous room reads are served from a 5-minute entry + (RoomsController.cs:146,162,183,196), so anonymous room reads are served from a 5-minute entry tagged conference and conference:rooms - (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:277). The correctness half is + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:276). The correctness half is eviction: each mutation ends by calling - outputCacheStore.EvictTagsAsync(cancellationToken, "conference:rooms") (:225, 254, 272), - invalidating exactly the room reads and nothing else. [Rubric §3, Clean Architecture]: the eviction + outputCacheStore.EvictTagsAsync(cancellationToken, "conference:rooms") (:233, 262, 280), + invalidating exactly the room reads and nothing else; unlike the event controllers in this unit, the + room writes do not evict the broad conference tag. [Rubric §3, Clean Architecture]: the eviction lives in the controller, not the command handler, because IOutputCacheStore is an ASP.NET concern the Application layer must not reference.
      • Walkthrough
        • The class gate is [HasPermission(ConferencePermissions.RoomsManage)] (RoomsController.cs:91), a room-specific capability rather than the event one even though rooms hang off the event aggregate; each read re-opens with [AllowAnonymous].
        • -
        • GetAllAsync (:139-144), the paged overload (:155-165), GetAllForLookupAsync (:176-179) and - GetByIdAsync under the named route "GetRoomById" (:189-195) are attribute-only passthroughs; the +
        • AllowUnscopedExport => IsPrivileged (:138, doc :132-137) opts the privileged audience in to a + whole-table export, mirroring the hook's null for that audience.
        • +
        • GetAllAsync (:144-152), the paged overload (:160-173), GetAllForLookupAsync (:181-187) and + GetByIdAsync under the named route "GetRoomById" (:194-203) are attribute-only passthroughs; the hook scopes all four. The doc comments carry the rules the code no longer restates per action: the - paged one on AND composition (:146-151), the lookup on the closed side channel (:167-172), and + paged one on AND composition (:154-159), the lookup on the closed side channel (:175-180), and GetById on the 404 answer, "not a redacted record, so a guessed id cannot confirm that an unannounced - event exists or that a venue has been booked for it" (:181-185).
        • -
        • CreateAsync (:206) is [HttpPost] (:204) and [Idempotent] (:205), maps AddRoomRequest onto - AddRoomCommand positionally (:211-219, note the optional client-supplied RoomId in slot two), - returns HandleFailure on failure (:222-223), evicts (:225) and returns - CreatedAtRoute("GetRoomById", ...) (:226-229).
        • -
        • UpdateAsync (:234) dispatches UpdateRoomCommand with the parent EventId from the body and the - child id from the route (:240-248), evicts (:254) and returns NoContent() (:255).
        • -
        • DeleteAsync (:260) reads the parent eventId [FromQuery] (:262), dispatches - RemoveRoomCommand(eventId, id) (:266), evicts (:272) and returns NoContent().
        • -
        • All three mutations return HandleFailure before they evict (:222, 251, 269), so a failed command + event exists or that a venue has been booked for it" (:189-193).
        • +
        • CreateAsync (:214) is [HttpPost] (:212) and [Idempotent] (:213), maps AddRoomRequest onto + AddRoomCommand positionally (:218-228, note the optional client-supplied RoomId in slot two), + returns HandleFailure on failure (:230-231), evicts (:233) and returns + CreatedAtRoute("GetRoomById", ...) (:234-237).
        • +
        • UpdateAsync (:242) dispatches UpdateRoomCommand with the parent EventId from the body and the + child id from the route (:247-257), evicts (:262) and returns NoContent() (:263).
        • +
        • DeleteAsync (:268) reads the parent eventId [FromQuery] (:270), dispatches + RemoveRoomCommand(eventId, id) (:274), evicts (:280) and returns NoContent().
        • +
        • All three mutations return HandleFailure before they evict (:230, 259, 277), so a failed command never disturbs the cache.
        • There is no ExportAsync override, and none is needed: the inherited action reads the same hook as the - lists (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:262), so - the CSV shows exactly what the list shows, and it stays behind the class-level RoomsManage - capability because it carries no [AllowAnonymous] of its own.
        • + lists (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:270), so + the CSV shows exactly what the list shows. A privileged caller's null scope passes because of the + opt-in above, while a non-privileged caller whose filter handler failed (also null) is refused with + 403 rather than served every room (EntityControllerBase.cs:273-274). The export stays behind the + class-level RoomsManage capability because it carries no [AllowAnonymous] of its own.
      • Why it's built this way: rooms are read far more than they are edited (venue maps, schedule grids), @@ -1861,44 +1931,48 @@

        EventsController

        read the events those sessions belong to) and a new PublishedEventSpecification() for everyone else (EventsController.cs:69-70, doc :57-68). The base then applies it to all five read actions, so the authorization predicate is a data specification the query service composes into SQL, not imperative - post-filtering. The lookup doc comment (:97-101) names the side channel that closes: a draft event - listed by name. ExportAsync (:127-142) adds the imperative privileged-reader Forbid() on top - (:136-139).
      • + post-filtering. The lookup doc comment (:104-108) names the side channel that closes: a draft event + listed by name. Because the base's CSV export refuses a null scope unless the controller opts in + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:273-274, 508), + the controller declares AllowUnscopedExport => currentUserService.IsPrivilegedConferenceReader() + (EventsController.cs:77, doc :72-76), the same audience the hook exempts. ExportAsync + (:134-149) adds the imperative privileged-reader Forbid() on top (:143-146).
      • Concept introduced, lifecycle transitions live on a sibling controller. [Rubric §1, SOLID] and - [Rubric §15, Best Practices & Code Quality]: publish, unpublish and the Sessionize refresh used to be - actions on this controller. They are now - EventLifecycleController's, so this controller's surface is exactly + [Rubric §15, Best Practices & Code Quality]: publish, unpublish and the Sessionize refresh are + EventLifecycleController's actions, so this controller's surface is exactly create/read/update/delete/export plus the two read-only projections (.ics export, now-next), and the conditional-write (ADR-035) and Sessionize error-mapping (BR-63) concerns live with the actions that actually need them.
      • Walkthrough
          -
        • The reads (:72-119) attach [AllowAnonymous] + [OutputCache(PolicyName = "EventsCache")] and +
        • The reads (:79-126) attach [AllowAnonymous] + [OutputCache(PolicyName = "EventsCache")] and delegate to the base, which threads the published-event specification; GetByIdAsync carries the named - route "GetEventById" (:110).
        • -
        • ExportCalendarAsync (:144-159) streams an .ics document via File(...) with the text/calendar - content type and an event-{id}.ics file name (:159).
        • -
        • GetNowNextAsync (:161-174) and GetCurrentNowNextAsync (:176-188) serve the now/next snapshot for - a given event or, with new GetNowNextQuery(EventId: null) (:187), for the current one; both use the + route "GetEventById" (:117).
        • +
        • ExportCalendarAsync (:151-166) streams an .ics document via File(...) with the text/calendar + content type and an event-{id}.ics file name (:165).
        • +
        • GetNowNextAsync (:168-181) and GetCurrentNowNextAsync (:183-195) serve the now/next snapshot for + a given event or, with new GetNowNextQuery(EventId: null) (:193), for the current one; both use the short-TTL NowNextCache policy (60 seconds, - MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:285) because the payload changes with + MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:284) because the payload changes with the clock. That policy is registered without the privileged-reader bypass, because the snapshot is - identical for every role (Program.cs:283-285).
        • -
        • CreateAsync (:195-204) is an override marked [Idempotent] (:196), so a retried POST carrying - the same Idempotency-Key is deduplicated; it calls base.CreateAsync then evicts. The attribute is - single-use, so declaring it here coincides with the inherited one instead of duplicating it (doc - :190-194).
        • -
        • UpdateAsync (:219-243) reads the required token (:224), dispatches - new UpdateEventCommand(id, request, rowVersion) (:226-228), appends a non-fatal X-Warning header - when a timezone change leaves existing sessions semantically stale (BR-131, :233-239), evicts - (:241) and returns Ok(result.Value.Event) (:242).
        • -
        • DeleteAsync (:248-257) additionally evicts conference:sessions and conference:rooms because - soft-deleting an event cascades to its children (:254-255). Every other mutation evicts the single - conference:events tag (:202, 241).
        • + identical for every role (Program.cs:282-284). +
        • CreateAsync (:197-211) is an override marked [Idempotent] (:203), so a retried POST carrying + the same Idempotency-Key is deduplicated; it calls base.CreateAsync then evicts + conference:events alone (:209). The attribute is single-use, so declaring it here coincides with + the inherited one instead of duplicating it (doc :197-201).
        • +
        • UpdateAsync (:213-250) reads the required token (:231), dispatches + new UpdateEventCommand(id, request, rowVersion) (:233-235), appends a non-fatal X-Warning header + when a timezone change leaves existing sessions semantically stale (BR-131, :241-246), evicts + conference:events and the broad conference tag (:248) and returns Ok(result.Value.Event) + (:249).
        • +
        • DeleteAsync (:252-264) additionally evicts conference:sessions and conference:rooms, plus the + broad conference tag, because soft-deleting an event cascades to its children (:261-262). The + broad tag is carried by every Conference cache policy (Program.cs:236-237, 267-284), so update and + delete also drop the entries of the policies that carry no entity-specific tag; create does not.
      • Why it's built this way: the base still owns the plain CRUD, so the event-specific behavior (scoping, export gating, calendar and now-next projections) reads as a flat list of extra actions. - Moving publish, unpublish and the Sessionize refresh to + Keeping publish, unpublish and the Sessionize refresh on EventLifecycleController keeps that operational nuance (conditional writes, upstream error mapping) at a boundary dedicated to it, while this controller's actions all return an EventDTO or a projection of one.
      • @@ -1927,13 +2001,13 @@

        AddSessionCategoryItemRequest

        id); add plus delete only, no update. The controller passes null for the SessionCategoryItem id (new AddSessionCategoryItemCommand(request.SessionId, null, request.CategoryItemId), - SessionCategoryItemsController.cs:170). [Rubric §12, Performance & Scalability]: because a tag change + SessionCategoryItemsController.cs:178). [Rubric §12, Performance & Scalability]: because a tag change moves both the session and the category read models, a successful write evicts three output-cache tags, conference:sessions, conference:categories, and conference - (SessionCategoryItemsController.cs:178). + (SessionCategoryItemsController.cs:186).
      • Walkthrough: two required { get; init; } id properties, no methods.
      • Where it's used: [FromBody] on SessionCategoryItemsController's - CreateAsync (SessionCategoryItemsController.cs:165-166), behind + CreateAsync (SessionCategoryItemsController.cs:173-174), behind [HasPermission(ConferencePermissions.SessionsManage)] (SessionCategoryItemsController.cs:47) and marked [Idempotent] (:164).
      @@ -1957,12 +2031,12 @@

      AddSessionQuestionAnswerRequest

      GetExportSpecification override (SessionQuestionAnswersController.cs:96-97). On add the controller passes null for the SessionQuestionAnswer id - (SessionQuestionAnswersController.cs:234).
    • + (SessionQuestionAnswersController.cs:238).
    • Walkthrough: three required { get; init; } properties, no methods. This is the single-answer path; the whole-form path uses BatchAddSessionQuestionAnswersRequest instead.
    • Where it's used: [FromBody] on SessionQuestionAnswersController's - CreateAsync (SessionQuestionAnswersController.cs:229-230), marked + CreateAsync (SessionQuestionAnswersController.cs:233-234), marked [Idempotent] (:166).

    @@ -1980,14 +2054,14 @@

    AddSessionSpeakerRequest

    ([Rubric §4, DDD]); add plus delete only. The controller passes null for the SessionSpeaker id (new AddSessionSpeakerCommand(request.SessionId, null, request.SpeakerId), - SessionSpeakersController.cs:170). [Rubric §12, Performance & Scalability]: a successful add evicts + SessionSpeakersController.cs:178). [Rubric §12, Performance & Scalability]: a successful add evicts the conference:sessions and conference output-cache tags - (SessionSpeakersController.cs:180), and the code comment above that call states why: speaker + (SessionSpeakersController.cs:188), and the code comment above that call states why: speaker assignment changes the cached session detail and list reads the speaker dashboard relies on - (SessionSpeakersController.cs:178-179). + (SessionSpeakersController.cs:186-187).
  • Walkthrough: two required { get; init; } id properties, no methods.
  • Where it's used: [FromBody] on SessionSpeakersController's - CreateAsync (SessionSpeakersController.cs:165-166), behind + CreateAsync (SessionSpeakersController.cs:173-174), behind [HasPermission(ConferencePermissions.SessionsManage)] (SessionSpeakersController.cs:47) and marked [Idempotent] (:164).
  • @@ -2013,7 +2087,7 @@

    BatchSessionQuestionAnswerItemReq
  • Walkthrough: two required { get; init; } properties, no methods. The controller flattens the list with a collection expression and a Select, [.. request.Answers.Select(a => new BatchSessionQuestionAnswerItem(a.QuestionId, a.AnswerValue))] - (SessionQuestionAnswersController.cs:264), the same one-line manual hop from wire type to application + (SessionQuestionAnswersController.cs:268), the same one-line manual hop from wire type to application type that the single-answer path performs field by field.
  • Why it's built this way: the API keeps its own element record rather than binding directly to the application-layer BatchSessionQuestionAnswerItem, so the HTTP contract and the command stay @@ -2038,10 +2112,10 @@

    UpdateSessionQuestionAnswerRequestConcept: identical to UpdateEventQuestionAnswerRequest; the answer id is the route {id} (new UpdateSessionQuestionAnswerCommand(request.SessionId, id, request.AnswerValue), - SessionQuestionAnswersController.cs:281).

  • + SessionQuestionAnswersController.cs:285).
  • Walkthrough: two required { get; init; } properties, no methods.
  • Where it's used: [FromBody] on SessionQuestionAnswersController's - UpdateAsync (SessionQuestionAnswersController.cs:275-277).
  • + UpdateAsync (SessionQuestionAnswersController.cs:279-281).

    BatchAddSessionQuestionAnswersRequest

    @@ -2070,11 +2144,11 @@

    BatchAddSessionQuestionAnswersReq invalidation is declarative here rather than a hand-written EvictTagsAsync call as in the room and join controllers. [Rubric §6, CQRS & Event-Driven]: the controller does no looping and no orchestration, it constructs one command and dispatches it once - (SessionQuestionAnswersController.cs:262-266). + (SessionQuestionAnswersController.cs:266-270).
  • Walkthrough
    • Two required { get; init; } members: SessionId (:52) and Answers (:55), whose doc comment states the shape rule directly, "at most one per question".
    • -
    • CreateBatchAsync (SessionQuestionAnswersController.cs:256) is [HttpPost("batch")] (:192) and +
    • CreateBatchAsync (SessionQuestionAnswersController.cs:260) is [HttpPost("batch")] (:192) and [Idempotent] (:193), so a retried form submit replays the first response rather than re-applying the form. It guards with ArgumentNullException.ThrowIfNull(request) (:198), flattens the items (:200-202), dispatches @@ -2091,11 +2165,11 @@

      BatchAddSessionQuestionAnswersReq

    • Why it's built this way: the controller's own doc comment is the design record: the form is applied atomically under one transaction "so a refusal leaves nothing written and the client has no partially - saved state to reconcile" (SessionQuestionAnswersController.cs:245-248). The alternative, letting the + saved state to reconcile" (SessionQuestionAnswersController.cs:249-252). The alternative, letting the UI POST each answer separately, would leave a half-saved form behind on any mid-flight failure and would cost one round trip per question.
    • Where it's used: [FromBody] on CreateBatchAsync only - (SessionQuestionAnswersController.cs:257); consumed by the attendee session-feedback form.
    • + (SessionQuestionAnswersController.cs:261); consumed by the attendee session-feedback form.
    • Caveats / not-in-source: this controller has no IOutputCacheStore injection and no EvictTagsAsync call anywhere, so whether the batch invalidates the ASP.NET Core output cache (as opposed to the application cache reached through ICacheInvalidating) is not determinable from this @@ -2272,18 +2346,23 @@

      SessionCategoryItemsController

      with IsPrivileged (:59) short-circuiting for Organizer and ContentEditor. [Rubric §11, Security]: as with every junction controller here, the write permission follows the owning aggregate while the read filter follows the parent's visibility, and the CSV export repeats the privileged-reader gate so the - scoping cannot be bypassed by asking for the file instead of the page.
    • + scoping cannot be bypassed by asking for the file instead of the page. Because the hook returns null + for exactly the privileged audience, the controller overrides AllowUnscopedExport => IsPrivileged + (:91): the framework's export is fail-closed (the base property defaults to false at + MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:508, and a null + scope without that opt-in is refused with a 403 at :273-274), so only a privileged reader may take the + unscoped table.
    • Walkthrough: four [AllowAnonymous] + [OutputCache(PolicyName = "SessionsCache")] read - passthroughs (SessionCategoryItemsController.cs:85,95,114,122, the last carrying the named route - "GetSessionCategoryItemById"), all scoped by the hook. ExportAsync ([HttpGet("export")] at :139) - returns Forbid() for a non-privileged caller (:150) and otherwise delegates to the base. - CreateAsync ([HttpPost] at :163, [Idempotent] at :164) dispatches - AddSessionCategoryItemCommand(request.SessionId, null, request.CategoryItemId) (:170), evicts - (:178) and returns CreatedAtRoute("GetSessionCategoryItemById", ...) (:179-180); DeleteAsync - ([HttpDelete("{id}")] at :186) reads the parent sessionId [FromQuery] (:189), dispatches - RemoveSessionCategoryItemCommand(sessionId, id) (:193), evicts (:201) and returns NoContent() - (:202). Both evictions clear conference:sessions, conference:categories and conference - (:178, 201).
    • + passthroughs (SessionCategoryItemsController.cs:93,103,122,130, the last carrying the named route + "GetSessionCategoryItemById"), all scoped by the hook. ExportAsync ([HttpGet("export")] at :147) + returns Forbid() for a non-privileged caller (:158) and otherwise delegates to the base. + CreateAsync ([HttpPost] at :171, [Idempotent] at :172) dispatches + AddSessionCategoryItemCommand(request.SessionId, null, request.CategoryItemId) (:178), evicts + (:186) and returns CreatedAtRoute("GetSessionCategoryItemById", ...) (:187-190); DeleteAsync + ([HttpDelete("{id}")] at :194) reads the parent sessionId [FromQuery] (:197), dispatches + RemoveSessionCategoryItemCommand(sessionId, id) (:201), evicts (:209) and returns NoContent() + (:210). Both evictions clear conference:sessions, conference:categories and conference + (:186, 209).
    • Why it's built this way: same rationale as the other junction controllers: the child mutates only through its parent aggregate, so it gets free reads and explicit, command-routed writes; and because a tag on a session is visible from both the session page and the category page, both parents' cache tags @@ -2309,15 +2388,15 @@

      SessionQuestionAnswersController

      EntityControllerBase (SessionQuestionAnswersController.cs:86); the IEntityQueryService - (:77); four ICommandHandlers for + (:79); four ICommandHandlers for AddSessionQuestionAnswerCommand, BatchAddSessionQuestionAnswersCommand, UpdateSessionQuestionAnswerCommand and RemoveSessionQuestionAnswerCommand - (:78-81); ICurrentUserService with - RoleNames for the scoping decision (:82); - OwnershipHelper (:526) and + (:80-83); ICurrentUserService with + RoleNames for the scoping decision (:84); + OwnershipHelper (:108, 119, 136) and OwnedByUserSpecification<TEntity, TIdentifierType>; the IdempotentAttribute; the SessionQuestionAnswerDTO; and its four @@ -2327,48 +2406,55 @@

      SessionQuestionAnswersController

      UpdateSessionQuestionAnswerRequest (:26-66).
    • Concept introduced, the atomic batch write. Owner-scoped reads and the organizer-only export gate are taught at EventQuestionAnswersController; what is new here is - CreateBatchAsync (:194-209), a POST /batch that applies a whole feedback form in one call. + CreateBatchAsync (:258-275), a POST /batch that applies a whole feedback form in one call. [Rubric §6, CQRS & Event-Driven] and [Rubric §9, API & Contract Design]: the action maps the request items onto BatchSessionQuestionAnswerItem - values with a collection expression (:200-202) and dispatches a single command, so the transaction + values with a collection expression (:266-268) and dispatches a single command, so the transaction boundary is the whole form. Its doc comment states the contract the client can rely on: every answer is upserted (BR-107) under one transaction, so a refusal leaves nothing written and the client has no - partially saved state to reconcile (:183-191). It carries [Idempotent] (:193) for the same reason + partially saved state to reconcile (:249-257). It carries [Idempotent] (:259) for the same reason the single-answer create does. [Rubric §12, Performance & Scalability]: one round trip and one transaction replace one per question.
    • Concept introduced, the fail-closed owner gate (ADR-033, the 2026-08-31 resolvable-owner rule). - [Rubric §11, Security]. GetExportSpecification() (:525-530) is resolved through + [Rubric §11, Security]. GetExportSpecification() (:107-112) is resolved through OwnershipHelper.GetOwnershipSpecification<TSpec, TId> rather than a hand-rolled bypass ternary, the same shape ADR-033 prescribes and the one Store's OrdersController and ReviewsController use; ADC's vocabulary (the owner claim and the Organizer bypass role) is passed in. The owner claim is ClaimTypes.NameIdentifier, where the JWT bearer handler maps the token's sub. GetExportSpecification() returns null for two different reasons: the caller is an Organizer (scoping deliberately skipped) or the caller is a non-Organizer whose owner claim cannot be resolved, and only the - first may read unscoped. The earlier shape dereferenced UserId!.Value for the second case and answered - 500; a missing claim is now an authorization answer instead, so RequireResolvableOwner() (:544-557) - returns a 403 Error.Forbidden when the caller is neither an Organizer nor holds a resolvable - ClaimTypes.NameIdentifier claim (:546-550, 552-556), and null otherwise, letting the read proceed.
    • + first may read unscoped. A missing claim is an authorization answer, so the private + RequireResolvableOwner() (:135-143) delegates to the framework's + OwnershipHelper.RequireResolvableOwner<UserIdentifierType>, passing the same + ClaimTypes.NameIdentifier claim and Organizer bypass role (:136-141), and turns a failed gate into + a 403 through HandleFailure(gate.Errors) (:142); it returns null otherwise, letting the read + proceed. Because the gate and the scope read the bypass role the same way, they agree on who is + privileged. The export side follows the same split: AllowUnscopedExport (:119) is + OwnershipHelper.IsAdmin(currentUserService, RoleNames.Organizer), the check the hook itself uses, so + only the Organizer bypass may take the unscoped table while a non-Organizer with an unresolvable claim + keeps the framework's fail-closed 403 + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:273-274).
    • Walkthrough
      • The class is gated with a bare [Authorize] (:75), so no endpoint here is anonymous. - GetExportSpecification() (:525-530) is the framework's synchronous read hook (the rule needs + GetExportSpecification() (:107-112) is the framework's synchronous read hook (the rule needs nothing awaited), so every read action (list, paged, lookup, by-id) is scoped from this one place and another attendee's answer is a 404 rather than a redacted record. This is a distinct posture from the other session-scoped child controllers, whose reads are anonymous and filtered by the parent's visibility rather than by ownership.
      • -
      • The four reads (:559-613) are guard-plus-passthrough: each first calls RequireResolvableOwner() - (for example :566), returns that 403 ObjectResult if it is non-null, and otherwise falls through to +
      • The four reads (:145-199) are guard-plus-passthrough: each first calls RequireResolvableOwner() + (for example :152), returns that 403 ObjectResult if it is non-null, and otherwise falls through to the inherited base action. GetByIdAsync runs under the named route "GetSessionQuestionAnswerById" - (:601). As with its event-side twin, no read carries an [OutputCache] attribute, because a + (:187). As with its event-side twin, no read carries an [OutputCache] attribute, because a per-caller payload must never enter a shared cache entry.
      • -
      • ExportAsync (:621-636) returns Forbid() unless the caller is an organizer (:630-633), the BR-9 - form of the row-scoping bypass gate, with the reasoning in its doc comment (:615-620).
      • -
      • CreateAsync (:645-661) is [Idempotent] (:646) and dispatches +
      • ExportAsync (:207-222) returns Forbid() unless the caller is an organizer (:216-219), the BR-9 + form of the row-scoping bypass gate, with the reasoning in its doc comment (:201-206).
      • +
      • CreateAsync (:231-247) is [Idempotent] (:232) and dispatches AddSessionQuestionAnswerCommand(request.SessionId, null, request.QuestionId, request.AnswerValue) - (:652), then CreatedAtRoute (:657-660).
      • -
      • UpdateAsync (:692-705) dispatches - UpdateSessionQuestionAnswerCommand(request.SessionId, id, request.AnswerValue) (:699) and returns - NoContent(). DeleteAsync (:708-721) reads the parent sessionId [FromQuery] (:711) and - dispatches RemoveSessionQuestionAnswerCommand(sessionId, id) (:715).
      • + (:238), then CreatedAtRoute (:243-246). +
      • UpdateAsync (:278-291) dispatches + UpdateSessionQuestionAnswerCommand(request.SessionId, id, request.AnswerValue) (:285) and returns + NoContent(). DeleteAsync (:294-307) reads the parent sessionId [FromQuery] (:297) and + dispatches RemoveSessionQuestionAnswerCommand(sessionId, id) (:301).
    • Why it's built this way: answers are personal feedback, so the read surface cannot be public; @@ -2394,8 +2480,8 @@

      SessionsController

      • What it is: the REST controller for the Session aggregate root (/Sessions). Aggregate-root CRUD plus a cross-source visibility filter (BR-132 / BR-49), a virtual - SpeakerId filter, a gated CSV export, an out-of-range warning header (BR-86), idempotent create, a - conditional update and an iCalendar export.
      • + SpeakerId filter, a gated CSV export, an out-of-range warning header (BR-86), idempotent create and a + conditional update.
      • Depends on: AggregateRootEntityControllerBase (SessionsController.cs:55-56); two @@ -2404,19 +2490,18 @@

        SessionsController

        SessionCreateRequest create handler, the UpdateSessionCommand update handler and a DeleteEntityCommand delete - handler (:46-48); three + handler (:46-48); two IQueryHandlers for - GetPublicSessionFilterQuery, - GetSessionsBySpeakerFilterQuery - and ExportSessionCalendarQuery - (:50-52); ICurrentUserService with the - CurrentUserServiceExtensions helper (:53, 60); IOutputCacheStore - (:54); the SessionDTO, + GetPublicSessionFilterQuery and + GetSessionsBySpeakerFilterQuery + (:50-51); ICurrentUserService with the + CurrentUserServiceExtensions helper (:52, 59); IOutputCacheStore + (:53); the SessionDTO, UpdateSessionResult, SessionUpdateRequest and - EventDTO (the iCalendar export handler moved to - SessionCalendarController, so this controller no longer takes an - IQueryHandler<ExportSessionCalendarQuery, Result<string>>); the + EventDTO (the + ExportSessionCalendarQuery handler + lives on SessionCalendarController, not here); the SpecificationExtensions And composer yielding an AndSpecification; the @@ -2426,55 +2511,59 @@

        SessionsController

        [Rubric §7, Microservices Readiness]: BR-132 / BR-49 hides non-accepted sessions and the sessions of unpublished events from non-privileged readers, but a Session can live in one data source while its parent Event's published flag lives in another (the doc comment at SessionsController.cs:61-66 names - Session in Cosmos and Event in SQL Server, the polyglot option of + Session in Cosmos and Event in SQL Server at :64, the polyglot option of ADR-018). Rather than a - cross-database join, GetReadSpecificationAsync (:76-86) delegates to the GetPublicSessionFilterQuery + cross-database join, GetReadSpecificationAsync (:75-85) delegates to the GetPublicSessionFilterQuery handler, which uses the framework's cross-source specification helper to produce a Specification<TEntity, TIdentifierType> - the query service can apply; privileged readers get null. + the query service can apply; privileged readers get null. Because that null means "privileged", the + controller overrides AllowUnscopedExport => IsPrivileged (:93), opting exactly that audience in to + the unscoped table under the framework's fail-closed export (default false at + MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:508, refusal at + :273-274). [Rubric §12, Performance & Scalability]: reads are [OutputCache(PolicyName = "SessionsCache")] - (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:275) and the default sort is the - single-column nameof(SessionDTO.StartsAt) constant (:133), sorting the schedule chronologically. The - comment above it (:130-132) records why it is one column rather than the earlier comma-joined string: + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:274) and the default sort is the + single-column nameof(SessionDTO.StartsAt) constant (:141), sorting the schedule chronologically. The + comment above it (:138-140) records why it is one column rather than the earlier comma-joined string: QueryFieldService resolves a sort column against the entity's own properties, so a comma-joined "StartsAt,RoomId" value matches none of them and silently falls back to the framework's Id tie-break instead of sorting by room; sessions that share a start time now fall through to that same Id tie-break rather than to a secondary room sort.
      • Walkthrough
          -
        • BuildPagedSessionSpecificationAsync (SessionsController.cs:105-128, doc and remarks :88-105) is - the paged read's specification builder: it starts from the hook (:110), then intercepts and removes - the virtual SpeakerId filter key (Session has no such column, :112-117), resolves it through - GetSessionsBySpeakerFilterQuery (:119-121) and ANDs the two with - publicSpecification.And(...) (:126-128). As in SpeakersController, +
        • BuildPagedSessionSpecificationAsync (SessionsController.cs:113-136, doc and remarks :95-112) is + the paged read's specification builder: it starts from the hook (:117), then intercepts and removes + the virtual SpeakerId filter key (Session has no such column, :119-124), resolves it through + GetSessionsBySpeakerFilterQuery (:126-128) and ANDs the two with + publicSpecification.And(...) (:133-135). As in SpeakersController, substitution would leak non-accepted sessions, and an unparseable value or a failed handler result - simply drops the scope (:112-117, 123-124).
        • -
        • GetAllAsync (:138-159) keeps a body only to apply the default sort: it queries with the hook's - specification (:148), DefaultSortColumn (:150) and pageSize: MaxPageSize (:154). The paged - overload (:164-202) clamps the page size (:175), defaults the sort when none was supplied - (:177-181), calls the builder above (:187) and writes the X-Pagination header (:200).
        • -
        • GetAllForLookupAsync (:213-216) and GetByIdAsync (:225-231) are attribute-only passthroughs; - the base applies the same hook, so a hidden session is a 404 there (doc :204-209 and :218-221).
        • -
        • ExportAsync (:239-253) is the same bypass gate the other row-scoped controllers use: Forbid() for - a non-privileged caller (:247-250), otherwise the base (:252). Its doc comment spells out what an + simply drops the scope (:119-124, 130-131).
        • +
        • GetAllAsync (:146-167) keeps a body only to apply the default sort: it queries with the hook's + specification (:156), DefaultSortColumn (:158) and pageSize: MaxPageSize (:162). The paged + overload (:172-210) clamps the page size (:183), defaults the sort when none was supplied + (:185-189), calls the builder above (:195) and writes the X-Pagination header (:208).
        • +
        • GetAllForLookupAsync (:221-224) and GetByIdAsync (:233-239) are attribute-only passthroughs; + the base applies the same hook, so a hidden session is a 404 there (doc :212-217 and :226-229).
        • +
        • ExportAsync (:248-262) is the same bypass gate the other row-scoped controllers use: Forbid() for + a non-privileged caller (:256-259), otherwise the base (:261). Its doc comment spells out what an unscoped CSV would hand over: the whole catalog, "declined and draft-event sessions included" - (:232-237). The single-session iCalendar export that used to live here moved to its own controller, + (:241-246). The single-session iCalendar export lives on its own controller, SessionCalendarController.
        • -
        • CreateAsync (:262-290) is an override marked [Idempotent] (:261) that calls - CreateHandler.HandleAsync directly (:266) rather than base.CreateAsync, because it needs the +
        • CreateAsync (:271-299) is an override marked [Idempotent] (:270) that calls + CreateHandler.HandleAsync directly (:275) rather than base.CreateAsync, because it needs the typed Result in order to run the BR-86 check: when the request set start or end times, it re-reads the parent event and appends a non-fatal X-Warning header - if the session falls outside the event's date range (:273-286). Note that the parent re-read - pattern-matches the widened query result with eventResult.Value is EventDTO evt (:280) rather than + if the session falls outside the event's date range (:282-295). Note that the parent re-read + pattern-matches the widened query result with eventResult.Value is EventDTO evt (:289) rather than a dynamic member access, because IEntityQueryService widens its return to object for field projection, so the controller narrows it back with a type pattern (the reason is written into the - comment at :275-276).
        • -
        • UpdateAsync (:305-327) carries [SupportsIfMatch] (:301) with the 409/412/428 - [ProducesResponseType] triple (:302-304), reads the required token (:310), dispatches - new UpdateSessionCommand(id, request, rowVersion) (:313), surfaces the same BR-86 warning from - result.Value!.HasDateRangeWarning (:320) and returns Ok(result.Value.Session) (:326). - DeleteAsync (:331-337) calls the base and evicts.
        • + comment at :284-285). +
        • UpdateAsync (:314-336) carries [SupportsIfMatch] (:310) with the 409/412/428 + [ProducesResponseType] triple (:311-313), reads the required token (:319), dispatches + new UpdateSessionCommand(id, request, rowVersion) (:322), surfaces the same BR-86 warning from + result.Value!.HasDateRangeWarning (:329) and returns Ok(result.Value.Session) (:335). + DeleteAsync (:340-347) calls the base and evicts.
        • Every mutation ends at - EvictTagsAsync(cancellationToken, "conference:sessions", "conference") (:288, 325, 336), the broad + EvictTagsAsync(cancellationToken, "conference:sessions", "conference") (:297, 334, 345), the broad tag included because cross-entity projections (the speaker bookmark-count endpoints) are cached under conference:sessions and conference rather than under a speakers tag.
        @@ -2488,8 +2577,8 @@

        SessionsController

        (MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/appsettings.json:82); the public schedule UI (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/PublicSessionList.razor), the speaker dashboard's SpeakerId-filtered list, and the k6 load test's read endpoints (/Sessions/paged) - all hit it. The add-to-calendar affordance now hits - SessionCalendarController instead, which mounts under this same + all hit it. The add-to-calendar affordance hits + SessionCalendarController, which mounts under this same route prefix.

      @@ -2521,21 +2610,23 @@

      SessionSpeakersController

      EventSpeakersController, and the BR-49 parent-visibility filter (GetReadSpecificationAsync, SessionSpeakersController.cs:73-83) is the same one SessionCategoryItemsController uses, export gate included - ([HttpGet("export")] at :139, Forbid() at :150). The difference is the eviction set: + ([HttpGet("export")] at :147, Forbid() at :158) and the AllowUnscopedExport => IsPrivileged + opt-in (:91) that lets only a privileged reader take the unscoped table under the framework's + fail-closed export. The difference is the eviction set: [Rubric §12, Performance & Scalability], both mutations clear conference:sessions and the broad - conference tag (:180, 203) and deliberately do not clear conference:speakers the way the other - two-parent junction controllers do. The comment at :178-179 gives the reason: what a speaker + conference tag (:188, 211) and deliberately do not clear conference:speakers the way the other + two-parent junction controllers do. The comment at :186-187 gives the reason: what a speaker assignment changes is the cached session reads (detail and list, which the speaker dashboard relies on), so the sessions tag is the one that must go.
    • Walkthrough: four [AllowAnonymous] + [OutputCache(PolicyName = "SessionsCache")] read - passthroughs (SessionSpeakersController.cs:85,95,114,122, the last carrying the named route - "GetSessionSpeakerById"), all scoped by the hook. CreateAsync ([HttpPost] at :163, - [Idempotent] at :164) dispatches - AddSessionSpeakerCommand(request.SessionId, null, request.SpeakerId) (:170), returns HandleFailure - first on failure (:173-176), evicts (:180) and returns CreatedAtRoute("GetSessionSpeakerById", ...) - (:181-184); DeleteAsync (:189) reads the parent sessionId [FromQuery] (:191), dispatches - RemoveSessionSpeakerCommand(sessionId, id) (:195), evicts (:203) and returns NoContent() - (:204). The class gate is [HasPermission(ConferencePermissions.SessionsManage)] (:47).
    • + passthroughs (SessionSpeakersController.cs:93,103,122,130, the last carrying the named route + "GetSessionSpeakerById"), all scoped by the hook. CreateAsync ([HttpPost] at :171, + [Idempotent] at :172) dispatches + AddSessionSpeakerCommand(request.SessionId, null, request.SpeakerId) (:178), returns HandleFailure + first on failure (:181-184), evicts (:188) and returns CreatedAtRoute("GetSessionSpeakerById", ...) + (:189-192); DeleteAsync (:197) reads the parent sessionId [FromQuery] (:199), dispatches + RemoveSessionSpeakerCommand(sessionId, id) (:203), evicts (:211) and returns NoContent() + (:212). The class gate is [HasPermission(ConferencePermissions.SessionsManage)] (:47).
    • Why it's built this way: the eviction crosses aggregates deliberately, because the session's cached representation includes its speakers, so mutating the link must invalidate the session cache to keep reads correct. Everything else is the shared junction shape, which is the point: an engineer who has read @@ -2563,11 +2654,11 @@

      AddSpeakerCategoryItemRequest

      than as a field on Speaker, so a request like this is how that attribute is attached. The controller passes null for the join id (new AddSpeakerCategoryItemCommand(request.SpeakerId, null, request.CategoryItemId), - SpeakerCategoryItemsController.cs:170) and then evicts conference:speakers, - conference:categories, and conference (SpeakerCategoryItemsController.cs:178).
    • + SpeakerCategoryItemsController.cs:178) and then evicts conference:speakers, + conference:categories, and conference (SpeakerCategoryItemsController.cs:186).
    • Walkthrough: two required { get; init; } id properties, no methods.
    • Where it's used: [FromBody] on SpeakerCategoryItemsController's - CreateAsync (SpeakerCategoryItemsController.cs:165-166), behind + CreateAsync (SpeakerCategoryItemsController.cs:173-174), behind [HasPermission(ConferencePermissions.SpeakersManage)] (SpeakerCategoryItemsController.cs:47) and marked [Idempotent] (:164).
    @@ -2649,7 +2740,7 @@

    SelfHttpOutputCacheWarmupTask

  • Concept introduced, warming a cache that keys on the exact URL string. OutputCache entries are keyed by the full request URL, so a warmed entry is only ever hit if a real caller issues the byte-identical query string. That turns warm-up into a surprisingly exacting exercise, and the class comment spells out - why the list has the shape it does (SelfHttpOutputCacheWarmupTask.cs:30-41): two families of caller + why the list has the shape it does (SelfHttpOutputCacheWarmupTask.cs:30-44): two families of caller build their URLs differently.

    • Family 1 is @@ -2657,16 +2748,24 @@

      SelfHttpOutputCacheWarmupTask

      which interpolates C# bool values and therefore emits capitalized False/True. The public list pages go through it, so the /paged entries mirror exactly what PublicEventList sends - (SelfHttpOutputCacheWarmupTask.cs:44-48).
    • + (SelfHttpOutputCacheWarmupTask.cs:47-51).
    • Family 2 is the hand-written lookup services, EventLookupService, SpeakerLookupService and - CategoryItemLookupService, which write lowercase - literals and pass pageSize=10000 (SelfHttpOutputCacheWarmupTask.cs:50-55).
    • + CategoryItemLookupService, which page through + the /paged endpoint in id order with lowercase literals. Their URLs are built by + PagedReadAll.LookupPageUrl (the call site in + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Events/EventLookupService.cs:85), + so the warmed entries are page 1 at pageSize=500, sortColumn=Id, sortDirection=asc + (SelfHttpOutputCacheWarmupTask.cs:53-58). Page 1 is the one every caller issues; later pages of a + large lookup still start cold.

    Those URLs do not collide, so both families have to be warmed independently. The lesson generalizes: a warm-up list is a copy of a caller's serialization behavior, and it silently stops working when a caller - changes how it renders a query string. + changes how it renders a query string. For the lookup family that drift is now caught by a test rather + than by reading: RequestedPaths (:62) exposes the list so + MMCA.ADC/Tests/Services/MMCA.ADC.Services.Tests/Warmup/SelfHttpOutputCacheWarmupTaskTests.cs can pin + every lookup path to the output of PagedReadAll.LookupPageUrl (the class comment says so at :37-39). [Rubric §12, Performance & Scalability] assesses whether hot paths avoid cold-start cost: warming the full path (envoy, Kestrel, OutputCache, controller, EF Core, SQL) means the first conference-day request does not pay for JIT, EF model warm-up and a cold SQL plan all at once. @@ -2680,13 +2779,16 @@

    SelfHttpOutputCacheWarmupTask

  • Primary constructor (SelfHttpOutputCacheWarmupTask.cs:22-28): takes IServer, IConfiguration, IHostEnvironment, IHostApplicationLifetime and ILogger<SelfHttpOutputCacheWarmupTask>, and forwards all five straight to SelfHttpWarmupTaskBase (:28). It adds no state of its own.
  • -
  • Paths (:42-56): a private static readonly string[] of eight relative paths, the two families - described above. Four /paged and collection reads for the public list pages, four lookup reads - (speakers, events, categoryitems, conferencecategories).
  • -
  • Name => "SelfHttpOutputCache" (:59): the identifier that appears in the warm-up completion and +
  • Paths (:45-59): a private static readonly string[] of eight relative paths, the two families + described above. Four /paged and collection reads for the public list pages (:48-51), then three + /paged lookup reads (speakers, events, categoryitems, :55-57) and the unpaged + conferencecategories lookup (:58).
  • +
  • RequestedPaths (:62): an internal static IReadOnlyList<string> over the same array, present only + so the test above can read the list; the runtime path never uses it.
  • +
  • Name => "SelfHttpOutputCache" (:65): the identifier that appears in the warm-up completion and failure log lines.
  • -
  • WarmupPaths => Paths (:62): the single abstract member the base needs. That is the entire - contribution of this class.
  • +
  • WarmupPaths => Paths (:68): the single abstract member the base needs. That is the entire + runtime contribution of this class.
  • What the base then does with it (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Warmup/SelfHttpWarmupTaskBase.cs:93): return immediately under the Testing environment (:95-98), await server start, resolve the bound cleartext @@ -2709,14 +2811,15 @@

    SelfHttpOutputCacheWarmupTask

    fail.

  • Where it's used: registered as services.AddWarmupTask<SelfHttpOutputCacheWarmupTask>() in the - Conference host (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:310) and executed by - the warm-up runner that AddServiceDefaults() installs. Three sibling copies of the same pattern live in - the Store services; each differs only in its Paths list.

    + Conference host (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:309) and executed by + the warm-up runner that AddServiceDefaults() installs. SelfHttpOutputCacheWarmupTaskTests reads + RequestedPaths to pin the lookup family. Three sibling copies of the same pattern live in the Store + services; each differs only in its Paths list.

  • -
  • Caveats / not-in-source: whether a given warmed URL still matches its caller byte for byte is not - checkable from this file alone; it is an invariant maintained by reading the caller. The comment at - :50-51 notes that three of the four lookup paths were previously uncovered, so the list has drifted - before.

    +
  • Caveats / not-in-source: the family-1 entries are still an invariant maintained by reading the + caller (PublicEventList and its siblings); only the lookup + family is pinned by a test. The comment at :53-54 notes that three of the four lookup paths were + previously uncovered, so the list has drifted before.

  • ServiceInfoController

    @@ -2803,8 +2906,8 @@

    CurrentUserServiceExtensions

  • Concept introduced, the shared-list invariant between visibility and cache keys. The same ConferenceReadAudience.PrivilegedRoles list is spread into the output-cache bypass roles in the host (string[] adminBypassRoles = [.. ConferenceReadAudience.PrivilegedRoles];, - MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:267), and the comment right above it - names this helper as the other half of the pair (Program.cs:265-267). The reason is a real correctness + MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:266), and the comment right above it + names this helper as the other half of the pair (Program.cs:264-266). The reason is a real correctness hazard, not tidiness: if the visibility check and the cache-bypass list ever named different roles, a privileged reader's unfiltered payload would be written into a shared cache entry and then served to the public. Declaring the roles once makes that class of bug impossible rather than merely unlikely @@ -2835,13 +2938,13 @@

    CurrentUserServiceExtensions

    SessionCategoryItemsController (SessionCategoryItemsController.cs:59), SessionSpeakersController (SessionSpeakersController.cs:59), SpeakerCategoryItemsController (SpeakerCategoryItemsController.cs:59), - SpeakersController (SpeakersController.cs:56), + SpeakersController (SpeakersController.cs:57), SponsorsController (SponsorsController.cs:53) and EventSpeakersController (EventSpeakersController.cs:58), all under MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/. EventsController calls it inline in two places: to choose whether to apply PublishedEventSpecification - (EventsController.cs:70) and to gate a second code path (EventsController.cs:136). The host reads the + (EventsController.cs:70)
    and to gate a second code path (EventsController.cs:143). The host reads the underlying list for its cache policies (Conference.Service/Program.cs:214).
  • QuestionsController

    @@ -2851,30 +2954,34 @@

    QuestionsController

    • What it is: the REST controller for the Question aggregate root (/Questions), the feedback-question definitions attendees answer. It is the plainest - aggregate-root controller in the group: inherited CRUD, one hand-rolled conditional update, and cache - eviction, with no visibility scoping at all.
    • + aggregate-root controller in the group: inherited CRUD, one hand-rolled conditional update, an explicit + opt-in to whole-table export, and cache eviction, with no visibility scoping at all.
    • Depends on: AggregateRootEntityControllerBase (QuestionsController.cs:42-43); the IEntityQueryService - (:35); a create handler keyed on - QuestionCreateRequest (:36); an update - handler for UpdateQuestionCommand (:37); + (:36); a create handler keyed on + QuestionCreateRequest (:37); an update + handler for UpdateQuestionCommand (:38); a delete handler keyed on DeleteEntityCommand<TEntity, TIdentifierType> - (:38); IOutputCacheStore (:39); the QuestionDTO; + (:39); IOutputCacheStore (:40); the QuestionDTO; QuestionUpdateRequest as the PUT body - (:119); and the SupportsIfMatchAttribute.
    • + (:123); and the SupportsIfMatchAttribute.
    • Concept introduced: none new. This is the aggregate-root shape taught at ConferenceCategoriesController, minus even the route override. [Rubric §9, API & Contract Design]: every read is a pure override that re-decorates the base action - and delegates (QuestionsController.cs:45-92), which is what a controller looks like when it has no + and delegates (QuestionsController.cs:48-95), which is what a controller looks like when it has no per-caller rule to apply; contrast EventSpeakersController, which overrides - the read hook so the base can scope those same actions. It is also why this class does not override - ExportAsync: with no row scoping on the reads there is no scoping for an export to bypass, and because - the inherited /export action carries no [AllowAnonymous] of its own it stays behind the class-level - capability gate (the symmetry the base documents at - MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:203-209). + the read hook so the base can scope those same actions. The one consequence of having no read hook is + the export. The inherited /export action in + EntityControllerBase + is fail-closed: when GetReadSpecificationAsync resolves to null it answers 403 unless the controller + opts in through AllowUnscopedExport + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:272-274, default + false at :508). Questions are reference data every reader lists in full, so this class sets + AllowUnscopedExport => true unconditionally (QuestionsController.cs:45-46); the action carries no + [AllowAnonymous] of its own, so it stays behind the class-level capability gate. The one bespoke command here, UpdateQuestionCommand, exists because a question update is not a plain property patch; the conditional-write contract is the same one taught at ConferenceCategoriesController.
    • @@ -2885,17 +2992,18 @@

      QuestionsController

      absent from the ContentEditor subset (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Authorization/ConferencePermissions.cs:70-79), so a content editor curates sessions and speakers but cannot rewrite the feedback form. -
    • All four reads (:44-91) re-open with [AllowAnonymous] and attach +
    • AllowUnscopedExport => true (:46) is the export opt-in described above.
    • +
    • All four reads (:48-95) re-open with [AllowAnonymous] and attach [OutputCache(PolicyName = "QuestionsCache")] (5-minute TTL, tags conference and - conference:questions, MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:279), then - delegate; GetByIdAsync carries the named route "GetQuestionById" (:77).
    • -
    • CreateAsync (:95-102) and DeleteAsync (:137-144) are thin overrides: call the base, then - EvictTagsAsync(cancellationToken, "conference:questions") (:100, 142), then return the base's + conference:questions, MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:278), then + delegate; GetByIdAsync carries the named route "GetQuestionById" (:81).
    • +
    • CreateAsync (:98-106) and DeleteAsync (:140-148) are thin overrides: call the base, then + EvictTagsAsync(cancellationToken, "conference:questions") (:104, 146), then return the base's result.
    • -
    • UpdateAsync (:117-133) is the hand-rolled action: [SupportsIfMatch] (:113) with the 409/412/428 - [ProducesResponseType] triple (:114-116), SupportsIfMatchAttribute.RequiredToken(HttpContext) - (:122), new UpdateQuestionCommand(id, request, rowVersion) (:125), HandleFailure on failure - (:128-129), evict (:131), Ok(result.Value) (:132).
    • +
    • UpdateAsync (:121-137) is the hand-rolled action: [SupportsIfMatch] (:117) with the 409/412/428 + [ProducesResponseType] triple (:118-120), SupportsIfMatchAttribute.RequiredToken(HttpContext) + (:126), new UpdateQuestionCommand(id, request, rowVersion) (:129), HandleFailure on failure + (:132-133), evict (:135), Ok(result.Value) (:136).
    • Every eviction here clears the single conference:questions tag; unlike the sessions and speakers controllers it does not also clear the broad conference tag, because no cross-entity read projects a question.
    • @@ -2903,7 +3011,7 @@

      QuestionsController

    • Why it's built this way: questions carry no per-role visibility rule, so the controller carries none. It is the reference case for how little an aggregate-root controller must write when the base does - the work: policy, one update action, and eviction.
    • + the work: policy, one update action, the export opt-in, and eviction.
    • Where it's used: the Conference service host behind the Gateway route /Questions/{**catch-all} (MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/appsettings.json:130); the feedback-form builder UI under MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Question/ is the main client, and the @@ -2921,19 +3029,19 @@

      SessionAssetsController

      action is hand-written because the write side branches on caller identity (a session's own speaker versus an organizer) rather than following the generic CRUD shape.
    • Depends on: an IQueryHandler for - GetSessionAssetsQuery (:26); four + GetSessionAssetsQuery (:52); four ICommandHandlers for - AddSessionAssetLinkCommand (:27), - UploadSessionAssetCommand (:28), - UpdateSessionAssetCommand (:29) and - DeleteSessionAssetCommand (:30); - ICurrentUserService (:31), read through its speaker_id claim + AddSessionAssetLinkCommand (:53), + UploadSessionAssetCommand (:54), + UpdateSessionAssetCommand (:55) and + DeleteSessionAssetCommand (:56); + ICurrentUserService (:57), read through its speaker_id claim rather than through CurrentUserServiceExtensions, because visibility here turns on "is this caller the session's speaker", not on the shared privileged-role audience; - IPermissionRegistry (:32), checked directly against + IPermissionRegistry (:58), checked directly against ConferencePermissions.SessionAssetsManage rather than through a [HasPermission] attribute, since the privileged check has to be available as a plain bool inside every action body, not just as a class or - method gate; ASP.NET Core's IOutputCacheStore (:33); the + method gate; ASP.NET Core's IOutputCacheStore (:59); the SessionAssetDTO and SessionAssetLimits constants; the IdempotentAttribute and @@ -2941,11 +3049,11 @@

      SessionAssetsController

      SessionAssetLinkRequest / SessionAssetUpdateRequest as the POST and PUT bodies. It derives from - ApiControllerBase, not the aggregate-root base, - since there is no by-id read action to inherit.
    • + ApiControllerBase (:59), not the aggregate-root + base, since there is no by-id read action to inherit.
    • Concept introduced, two idempotency keys covering two different duplicate-write hazards. Both mutating POST actions carry [Idempotent], and the remarks on each explain a different reason - (SessionAssetsController.cs:83-86, :120-124). AddLinkAsync appends a row, so a retry would publish + (SessionAssetsController.cs:108-112, :145-150). AddLinkAsync appends a row, so a retry would publish the same link twice on the public page; a retried UploadFileAsync would additionally leave a duplicate blob, and on conference-venue wifi replaying the stored response also spares a second 50 MB transfer, turning a timeout-prone retry into a cheap one. Both stand on the same @@ -2955,14 +3063,14 @@

      SessionAssetsController

      [Rubric §29, Resilience & Business Continuity]: a mobile submit that times out and retries is expected conference-day behavior, not an edge case, so both writes are safe to replay.
    • Concept introduced, an anonymous read that must never cache a privileged answer. GetBySessionAsync is - [AllowAnonymous] and carries [OutputCache(PolicyName = "SessionsCache")] (:50-52) because it backs the + [AllowAnonymous] and carries [OutputCache(PolicyName = "SessionsCache")] (:76-78) because it backs the public session page, but the same action also serves a session's own speaker or an organizer, whose answer can include materials the public cannot see. The output-cache key does not vary by caller, so the action - checks ActingSpeakerId is not null || isPrivileged and, when true, sets - HttpContext.Features.Get<IOutputCacheFeature>()?.Context.AllowCacheStorage = false (:64-67) before + checks actingSpeakerId is not null || isPrivileged and, when true, sets + HttpContext.Features.Get<IOutputCacheFeature>()?.Context.AllowCacheStorage = false (:90-93) before calling the handler. The comment is explicit that the policy only ever turns storage off, never back on - (:63): there is no path where a privileged response is accidentally cached, only a path where it opts out - of a cache write that would otherwise happen by default. + (:87-89): there is no path where a privileged response is accidentally cached, only a path where it opts + out of a cache write that would otherwise happen by default. [Rubric §11, Security]: a privileged reader's fuller answer never lands in the entry the public shares, the same class of hazard CurrentUserServiceExtensions names for the audience/cache-bypass pair, solved here per-request instead of per-role list because the audience is @@ -2970,31 +3078,34 @@

      SessionAssetsController

      [Rubric §12, Performance & Scalability]: the public path, the overwhelming majority of calls, still gets the shared cache entry; only the caller-specific minority pays for a live read.
    • Walkthrough
        -
      • ActingSpeakerId (:36-37) reads the caller's speaker_id claim as a nullable - SpeakerIdentifierType; IsPrivileged (:40-41) checks +
      • ActingSpeakerId (:62-63) reads the caller's speaker_id claim as a nullable + SpeakerIdentifierType; IsPrivileged (:66-67) checks permissionRegistry.HasPermission(currentUserService.Roles, ConferencePermissions.SessionAssetsManage). Every action re-reads both rather than caching them, since each is a cheap claim/permission lookup.
      • -
      • GetBySessionAsync (:54-74): the anonymous, output-cached list read described above; delegates to - getBySessionHandler with (sessionId, actingSpeakerId, isPrivileged) so the query itself decides which - rows a non-owning, non-privileged caller sees.
      • -
      • AddLinkAsync (:92-106) and UploadFileAsync (:135-176) both dispatch with (request, ActingSpeakerId, IsPrivileged) and, on success, call EvictSessionCachesAsync then return - Created(SessionListUri(result.Value!.SessionId), result.Value) (:103-105, :173-175). Because there +
      • GetBySessionAsync (:80-100): the anonymous, output-cached list read described above; delegates to + getBySessionHandler with (sessionId, actingSpeakerId, isPrivileged) (:95-97) so the query itself + decides which rows a non-owning, non-privileged caller sees.
      • +
      • AddLinkAsync (:118-132) and UploadFileAsync (:161-203) both dispatch with (request, ActingSpeakerId, IsPrivileged) and, on success, call EvictSessionCachesAsync then return + Created(SessionListUri(result.Value!.SessionId), result.Value) (:129-131, :200-202). Because there is no by-id GET, the Created location points at the session's asset list rather than at a single - resource, and the doc comment on SessionListUri (:240-244) states that choice explicitly: the + resource, and the doc comment on SessionListUri (:267-273) states that choice explicitly: the filtered list IS the canonical location of the new resource. UploadFileAsync additionally validates the file up front (file is null || file.Length == 0 || file.Length > SessionAssetLimits.MaxFileBytes, - :142-148) before buffering it into a MemoryStream and forwarding the bytes to the handler (:150-157); - the [RequestSizeLimit(SessionAssetLimits.MaxRequestBytes)] attribute (:127) bounds the request itself, - with an inline SuppressMessage justifying the 50 MB cap against Sonar's S5693 default (:128-131), + :168-174), then allocates one byte[] of exactly the declared length and fills it in place with + ReadExactlyAsync (:176-184). The comment at :176-178 names why: that array is the only full copy + of the upload the request holds, shared by the validator, the format sniffer and the storage upload, + with no intermediate MemoryStream copy. The bytes then go to the handler (:186-195). The + [RequestSizeLimit(SessionAssetLimits.MaxRequestBytes)] attribute (:153) bounds the request itself, + with an inline SuppressMessage justifying the 50 MB cap against Sonar's S5693 default (:154-157), reviewed and reinforced again by the command validator and by SessionAssetLimits.MaxAssetsPerSession.
      • -
      • UpdateAsync (:190-214) is [SupportsIfMatch], reading the required token via - SupportsIfMatchAttribute.RequiredToken(HttpContext) (:202) before dispatching - UpdateSessionAssetCommand(id, request, ActingSpeakerId, IsPrivileged, rowVersion) (:205), the same +
      • UpdateAsync (:224-241) is [SupportsIfMatch] (:218), reading the required token via + SupportsIfMatchAttribute.RequiredToken(HttpContext) (:229) before dispatching + UpdateSessionAssetCommand(id, request, ActingSpeakerId, IsPrivileged, rowVersion) (:232), the same conditional-write contract taught at - ConferenceCategoriesController. DeleteAsync (:220-238) + ConferenceCategoriesController. DeleteAsync (:251-265) soft-deletes and, for a file asset, schedules its blob for removal, then returns NoContent().
      • -
      • EvictSessionCachesAsync (:253-254) is called after every mutation and evicts the conference:sessions +
      • EvictSessionCachesAsync (:280-281) is called after every mutation and evicts the conference:sessions and conference tags, the same tags a session edit evicts, so a new or removed asset never outlives the sessions cache policy's TTL on the public page.
      @@ -3035,7 +3146,7 @@

      SpeakerCategoryItemsController

      ICommandHandlers (:50-51); an IQueryHandler for GetPublicSpeakerCategoryItemFilterQuery - (:52); ICurrentUserService with the + (:52); ICurrentUserService (:53) with the CurrentUserServiceExtensions helper (:59); IOutputCacheStore (:54); the SpeakerCategoryItemDTO; the IdempotentAttribute; and the @@ -3051,18 +3162,24 @@

      SpeakerCategoryItemsController

      IsPrivileged (:59) short-circuiting for Organizer and ContentEditor.
    • Walkthrough: shape-for-shape the same as EventSpeakersController, with the SpeakersCache policy instead of EventsCache - (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:276). The four reads are + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:275). The four reads are [AllowAnonymous] + [OutputCache(PolicyName = "SpeakersCache")] passthroughs - (SpeakerCategoryItemsController.cs:85,95,114,122, the last carrying the named route - "GetSpeakerCategoryItemById"), all scoped by the hook. ExportAsync ([HttpGet("export")] at :139) - repeats the privileged-reader gate with Forbid() at :150. CreateAsync ([HttpPost] at :163, - [Idempotent] at :164) dispatches - AddSpeakerCategoryItemCommand(request.SpeakerId, null, request.CategoryItemId) (:170), evicts - (:178) and returns CreatedAtRoute("GetSpeakerCategoryItemById", ...) (:179-180); DeleteAsync - ([HttpDelete("{id}")] at :186) reads the parent speakerId [FromQuery] (:189), dispatches - RemoveSpeakerCategoryItemCommand(speakerId, id) (:193), evicts (:201) and returns NoContent() - (:202). Both evictions clear conference:speakers, conference:categories and conference - (:178, 201).
    • + (SpeakerCategoryItemsController.cs:93,103,122,130, the last carrying the named route + "GetSpeakerCategoryItemById"), all scoped by the hook. The export is guarded twice. The inherited + /export in + EntityControllerBase + is fail-closed when the read hook returns null + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:272-274), and + AllowUnscopedExport => IsPrivileged (SpeakerCategoryItemsController.cs:91, doc :85-90) opts in to + the unscoped table only for the privileged readers whose hook result is null by design. The + ExportAsync override ([HttpGet("export")] at :147) still repeats the privileged-reader gate with + Forbid() at :158 before delegating (:161). CreateAsync ([HttpPost] at :171, [Idempotent] + at :172) dispatches AddSpeakerCategoryItemCommand(request.SpeakerId, null, request.CategoryItemId) + (:178), evicts (:186) and returns CreatedAtRoute("GetSpeakerCategoryItemById", ...) (:187-190); + DeleteAsync ([HttpDelete("{id}")] at :194) reads the parent speakerId [FromQuery] (:197), + dispatches RemoveSpeakerCategoryItemCommand(speakerId, id) (:201), evicts (:209) and returns + NoContent() (:210). Both evictions clear conference:speakers, conference:categories and + conference (:186, 209).
    • Why it's built this way: it shares the exact shape of the other junction controllers because the underlying rules (mutate the child only through its parent aggregate; never let a junction row outlive its parent's visibility) are identical. Only the aggregate, the DTO, the permission and the pair of cache @@ -3071,7 +3188,9 @@

      SpeakerCategoryItemsController

    • Where it's used: the Conference service host behind the Gateway route /SpeakerCategoryItems/{**catch-all} (MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/appsettings.json:122); consumed by the speaker-profile editing UI under - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Speaker/.
    • + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Speaker/. The export authorization is + covered by + MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.API.Tests/Conventions/EntityExportAuthorizationTests.cs.

    SpeakerLinksController

    @@ -3132,7 +3251,7 @@

    SpeakersController

    dependency ceiling.
  • Depends on: AggregateRootEntityControllerBase - (SpeakersController.cs:52-53); the + (SpeakersController.cs:53-54); the IEntityQueryService (:43); three ICommandHandlers for SpeakerCreateRequest, @@ -3141,10 +3260,11 @@

    SpeakersController

    (:44-46); two IQueryHandlers for GetSpeakersByEventFilterQuery and GetPublicSpeakerFilterQuery - (:47-48); ICurrentUserService with - RoleNames and the - CurrentUserServiceExtensions helper (:49, 56); IOutputCacheStore - (:50); the SpeakerDTO and + (:47-48); ICurrentUserService with the + CurrentUserServiceExtensions helper (:49, 57); + IPermissionRegistry (:50), checked against + ConferencePermissions.SpeakersManage on the + update path; IOutputCacheStore (:51); the SpeakerDTO and SpeakerUpdateRequest; the HasPermissionAttribute and SupportsIfMatchAttribute; the @@ -3156,77 +3276,87 @@

    SpeakersController

    [HasPermission(...)]), SpeakersController carries a bare class-level [Authorize] (SpeakersController.cs:41) and then varies authorization per action. The catalog reads are [AllowAnonymous]; export, create and delete each re-assert - [HasPermission(ConferencePermissions.SpeakersManage)] (:280, 299, 354); and UpdateAsync - performs a resource-ownership check in code, comparing the caller's speaker_id JWT claim with the - route id and returning Forbid() when the caller is neither the speaker nor an organizer (:334-337, - BR-214). That is authorization a policy attribute cannot express, because it depends on the specific row - being edited; the organizer flag is then passed on to the handler as - new UpdateSpeakerCommand(id, request, CallerIsOrganizer: isOrganizer, RowVersion: rowVersion) (:342) + [HasPermission(ConferencePermissions.SpeakersManage)] (:289, 308, 365); and UpdateAsync + performs a resource-ownership check in code (:343-348, BR-214). It computes canManage as + permissionRegistry.HasPermission(currentUserService.Roles, ConferencePermissions.SpeakersManage) + (:345), the same capability the sibling create and delete actions require, so ContentEditor qualifies + alongside Organizer (comment :343-344); it compares the caller's speaker_id JWT claim with the route + id (:346); and it returns Forbid() when the caller is neither (:347-348). That is authorization a + policy attribute cannot express, because it depends on the specific row being edited; the management + flag is then passed on to the handler as + new UpdateSpeakerCommand(id, request, CallerIsOrganizer: canManage, RowVersion: rowVersion) (:353) so the handler keeps organizer-only fields unchanged on a self-edit (the rule is written out at - :309-315).
  • + :318-325).
  • Concept introduced, the virtual filter key. [Rubric §9, API & Contract Design]: EventId is not a Speaker column, so the paged action removes it from the generic filter dictionary before the pipeline - can reject it (:140-150), translates it into a specification via GetSpeakersByEventFilterQuery - (:157-158), and ANDs it with the public-speaker specification rather than substituting - (:162-166, publicSpecification.And(...) at :164, the extension member declared at + can reject it (:149-159), translates it into a specification via GetSpeakersByEventFilterQuery + (:166-167), and ANDs it with the public-speaker specification rather than substituting + (:171-173, publicSpecification.And(...) at :173, the extension member declared at MMCA.Common/Source/Core/MMCA.Common.Domain/Specifications/SpecificationExtensions.cs:48). Substituting would leak hidden speakers to a non-privileged caller; an unparseable value simply drops the scope - instead of failing the request. The remarks at :117-123 add the reason the two are not redundant: the + instead of failing the request. The remarks at :126-132 add the reason the two are not redundant: the event filter answers "linked to this event", the public filter answers "accepted for this event".
  • Walkthrough
      -
    • IsPrivileged (SpeakersController.cs:56) is the shared read-audience check; - BuildPublicSpeakerSpecificationAsync (:75-86, doc :61-74) is the BR-239 projection, parameterized +
    • IsPrivileged (SpeakersController.cs:57) is the shared read-audience check; + BuildPublicSpeakerSpecificationAsync (:76-87, doc :62-75) is the BR-239 projection, parameterized by an optional eventId because a speaker accepted for one event is not thereby public on another. Privileged readers get null.
    • -
    • GetReadSpecificationAsync (:95-97) is the framework hook, and it delegates to that builder with +
    • GetReadSpecificationAsync (:96-98) is the framework hook, and it delegates to that builder with no event context: the list, lookup and by-id actions read their scope from here, while the paged action - resolves its own because it carries an event id (doc :88-94).
    • -
    • GetAllAsync (:99-107) is a plain passthrough. The paged overload (:127-186) clamps pageSize to - MaxPageSize (:138), performs the EventId interception described above, then queries with the - composed specification (:168-179) and appends the X-Pagination header carrying the result's - PaginationMetadata (:184).
    • -
    • GetAllForLookupAsync (:201-231) has two guards. Privileged readers (a null specification) fall - through to the base action (:205-207); everyone else must name the label column from the allow-list - PublicLookupNameProperties (:59, first or last name only) or receive an - Error.InvalidEntityField failure (:209-220). This + resolves its own because it carries an event id (doc :89-95).
    • +
    • AllowUnscopedExport => IsPrivileged (:106, doc :100-105) is the opt-in the fail-closed inherited + export needs + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:272-274): the hook + returns null for a privileged reader, so without it even an organizer's export would be refused, and + for everyone else the opt-in is false.
    • +
    • GetAllAsync (:108-116) is a plain passthrough. The paged overload (:136-195) clamps pageSize to + MaxPageSize (:147), performs the EventId interception described above, then queries with the + composed specification (:177-188) and appends the X-Pagination header carrying the result's + PaginationMetadata (:193).
    • +
    • GetAllForLookupAsync (:210-240) has two guards. Privileged readers (a null specification) fall + through to the base action (:214-216); everyone else must name the label column from the allow-list + PublicLookupNameProperties (:60, first or last name only) or receive an + Error.InvalidEntityField failure (:218-229). This closes a BR-66 side channel: nameProperty=Email would otherwise project the speaker email into the lookup label, bypassing the DTO mapper that redacts it. The check runs before the query service, so a rejected label is never queried; the scoped path then forwards specification.Criteria as the lookup - where (:222-226) and rewraps the rows into a + where (:231-235) and rewraps the rows into a CollectionResult<T> of - BaseLookup<TIdentifierType> (:230).
    • -
    • GetByIdAsync (:233-269) carries the self-read carve-out: when the caller's speaker_id claim + BaseLookup<TIdentifierType> (:239).
    • +
    • GetByIdAsync (:245-278) carries the self-read carve-out: when the caller's speaker_id claim matches the route id the specification is dropped so a speaker can always load their own profile - (:247-257), and because that response can contain data the public cannot see while the output-cache + (:256-266), and because that response can contain data the public cannot see while the output-cache key does not vary by caller, the action turns storage off for this response via - HttpContext.Features.Get<IOutputCacheFeature>()?.Context.AllowCacheStorage = false (:256). The - policy only ever turns storage off, never back on, so the opt-out sticks (comment :253-255).
    • -
    • ExportAsync (:271-295) is the strongest form of the export gate taught at + HttpContext.Features.Get<IOutputCacheFeature>()?.Context.AllowCacheStorage = false (:265). The + policy only ever turns storage off, never back on, so the opt-out sticks (comment :262-264).
    • +
    • ExportAsync (:288-304) is the strongest form of the export gate taught at EventQuestionAnswersController: a declarative - [HasPermission(ConferencePermissions.SpeakersManage)] (:280) plus the imperative - if (!IsPrivileged) return Forbid(); (:289-292). The doc comment names the double bypass an unscoped - CSV would be here, going around both the public projection and the redacting DTO mapper, emails - included, and records that the attribute is stated explicitly because the class carries only a bare - [Authorize] for the inherited action to pick up (:271-278). [Rubric §30, Compliance/Privacy/Data Governance]: a speaker roster is personal data, so bulk egress is a named capability rather than a - side effect of reading the list.
    • -
    • CreateAsync (:297-307) and DeleteAsync (:352-362) call the base and evict. UpdateAsync - (:328-350) runs the BR-214 check (:333-337), reads the required If-Match token (:339), - dispatches (:341-343) and evicts (:348). Every mutation ends at - EvictTagsAsync(cancellationToken, "conference:speakers", "conference") (:305, 348, 360).
    • + [HasPermission(ConferencePermissions.SpeakersManage)] (:289) plus the imperative + if (!IsPrivileged) return Forbid(); (:298-301), on top of the base's fail-closed refusal. The doc + comment names the double bypass an unscoped CSV would be here, going around both the public projection + and the redacting DTO mapper, emails included, and records that the attribute is stated explicitly + because the class carries only a bare [Authorize] for the inherited action to pick up (:280-287). + [Rubric §30, Compliance/Privacy/Data Governance]: a speaker roster is personal data, so bulk egress + is a named capability rather than a side effect of reading the list. +
    • CreateAsync (:307-316) and DeleteAsync (:364-373) call the base and evict. UpdateAsync + (:332-361) runs the BR-214 check (:343-348), reads the required If-Match token (:350), + dispatches (:352-354) and evicts (:359). Every mutation ends at + EvictTagsAsync(cancellationToken, "conference:speakers", "conference") (:314, 359, 371).
    • The link/unlink actions (BR-209) and the three BR-210 session read projections (aggregated feedback, - single and batched bookmark counts) are no longer on this class: they moved to + single and batched bookmark counts) are not on this class: they live on SpeakerLinksController and SpeakerSessionsController respectively, both sharing this controller's - /Speakers route prefix, so the URLs and authorization did not change, only the constructor that - wires them.
    • + /Speakers route prefix.
  • -
  • Why it's built this way: speaker profiles are edited both by organizers and by the speakers +
  • Why it's built this way: speaker profiles are edited both by speaker managers and by the speakers themselves, so the controller needs row-aware authorization that a static policy cannot provide; keeping - that check inline mirrors the per-mutation ownership pattern used across the codebase. The virtual - EventId filter gives clients an event-scoped speaker list without adding a denormalized column to the - aggregate. The link/session split keeps the constructor's dependency count under - ConstructorDependencyCountTests' ceiling without shrinking the resource's surface.
  • + that check inline mirrors the per-mutation ownership pattern used across the codebase. Deriving the + update's management flag from the same SpeakersManage capability the create and delete attributes use + keeps the three mutations agreeing on who counts as a manager. The virtual EventId filter gives clients + an event-scoped speaker list without adding a denormalized column to the aggregate. The link/session + split keeps the constructor's dependency count under ConstructorDependencyCountTests' ceiling without + shrinking the resource's surface.
  • Where it's used: the Conference service host behind the Gateway route /Speakers/{**catch-all} (MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/appsettings.json:86); consumed by the public speaker directory (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Speakers/PublicSpeakerList.razor), the @@ -3264,10 +3394,10 @@

    SpeakerSessionsController

    BookmarkCountsCache: GetSessionBookmarkCountAsync (:66-81, doc :64-65) and the batched GetSessionBookmarkCountsAsync (:86-101, doc :83-85), both a 60-second policy tagged conference and conference:sessions - (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:297). [Rubric §7, Microservices Readiness]: bookmark counts are owned by the Engagement service, in another process, whose writes have + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:296). [Rubric §7, Microservices Readiness]: bookmark counts are owned by the Engagement service, in another process, whose writes have no handle on this host's cache store, so Engagement's bookmark handler publishes an eviction request over the broker that this host turns into a tag drop, and the short TTL stays as the backstop for a - message that never lands (Program.cs:287-297). The batched form (:94-96) replaces the speaker + message that never lands (Program.cs:286-296). The batched form (:94-96) replaces the speaker dashboard's per-session fan-out and only ever counts sessions actually assigned to the speaker.
  • Why it's built this way: same rationale as SpeakerLinksController: the split is a constructor-dependency fix, not a design change. Feedback stays uncached because every @@ -3291,60 +3421,66 @@

    ActivitiesController

    AggregateRootEntityControllerBase (ActivitiesController.cs:49-50); the IEntityQueryService - (:40); three ICommandHandlers for + (:41); three ICommandHandlers for ActivityCreateRequest, the framework's UpdateEntityCommand<TEntity, TUpdateRequest, TIdentifierType> and DeleteEntityCommand<TEntity, TIdentifierType> - (:41-43); an IQueryHandler for + (:42-44); an IQueryHandler for GetPublicActivityFilterQuery - (:44); ICurrentUserService plus the - CurrentUserServiceExtensions read-audience helper (:45, 52); - IOutputCacheStore (:46); the ActivityDTO; - ActivityUpdateRequest as the PUT body - (:188); the HasPermissionAttribute with the + (:45); ICurrentUserService plus the + CurrentUserServiceExtensions read-audience helper (:46, 53); + IOutputCacheStore (:47); the ActivityDTO; + ActivityUpdateRequest as the PUT body; the + HasPermissionAttribute with the ConferencePermissions catalog; the SupportsIfMatchAttribute; and the QueryFilterModelBinder.
  • Concept introduced: none new. This controller is the exact structural twin of SponsorsController: the same bare [Authorize] class gate with a per-mutation capability, the same real-EventId-column scoping (no filter interception), the same - attribute-plus-imperative export gate, at the same line numbers in both files. What differs is the - vocabulary. [Rubric §11, Security]: the class carries [Authorize] (ActivitiesController.cs:39) and - each mutation re-asserts [HasPermission(ConferencePermissions.ActivitiesManage)] (:143, 162, 181, 206), the capability declared at + AllowUnscopedExport opt-in plus attribute-plus-imperative export gate, at the same line numbers in both + files. What differs is the vocabulary. [Rubric §11, Security]: the class carries [Authorize] + (ActivitiesController.cs:39) and each mutation re-asserts + [HasPermission(ConferencePermissions.ActivitiesManage)] (:152, 171, 190, 215), the capability + declared at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Authorization/ConferencePermissions.cs:39 and included in the ContentManagement curation subset (ConferencePermissions.cs:70-79), so a content editor can run the social programme without holding event, room or question rights. [Rubric §12, Performance & Scalability]: reads run under the ActivitiesCache policy (5-minute TTL, tags conference and conference:activities, - MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:282) and every mutation evicts both + MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:281) and every mutation evicts both tags.
  • Walkthrough
    • IsPrivileged (ActivitiesController.cs:53) is the shared currentUserService.IsPrivilegedConferenceReader() read-audience check; - GetReadSpecificationAsync (:68-78) returns null for a privileged reader and otherwise the + GetReadSpecificationAsync (:69-79) returns null for a privileged reader and otherwise the Specification<TEntity, TIdentifierType> - the GetPublicActivityFilterQuery handler resolves (:74-77); a failed handler result degrades to - null rather than failing the read.
    • -
    • GetAllAsync (:83-88), the paged overload (:99-109), GetAllForLookupAsync (:114-117) and - GetByIdAsync under the named route "GetActivityById" (:123-132) are [AllowAnonymous] + + the GetPublicActivityFilterQuery handler resolves; a failed handler result degrades to null rather + than failing the read.
    • +
    • AllowUnscopedExport => IsPrivileged (:87, doc :81-86) opts the fail-closed inherited export + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:272-274) in to + the unscoped table for exactly the audience whose hook result is null; everyone else gets the + published-scope specification or, if it cannot be built, the framework's 403.
    • +
    • GetAllAsync (:89), the paged overload (:105), GetAllForLookupAsync (:120) and + GetByIdAsync under the named route "GetActivityById" (:132) are [AllowAnonymous] + [OutputCache(PolicyName = "ActivitiesCache")] passthroughs, all scoped from the one hook. The paged action's doc comment states the composition contract explicitly: EventId is a real column, so the caller's event filter travels through the generic pipeline and the published-event rule is ANDed on top - of it rather than substituted (:90-95). The by-id doc comment states that an activity of an + of it rather than substituted (:99-104). The by-id doc comment states that an activity of an unpublished event is a 404, "not a redacted record, so a guessed id cannot confirm that a party has - been scheduled" (:119-122).
    • -
    • ExportAsync (:144-158) pairs the declarative - [HasPermission(ConferencePermissions.ActivitiesManage)] (:143) with the imperative - if (!IsPrivileged) return Forbid(); (:152-155), then delegates to the base (:157). The doc + been scheduled" (:128-131).
    • +
    • ExportAsync ([HttpGet("export")] at :151) pairs the declarative + [HasPermission(ConferencePermissions.ActivitiesManage)] (:152) with the imperative + if (!IsPrivileged) return Forbid(); (:163), then delegates to the base (:166). The doc comment names the leak an unscoped CSV would be, a social programme that has not been announced, and - notes that the attribute is needed because the class carries only a bare [Authorize] (:134-141).
    • -
    • CreateAsync (:163-170) and DeleteAsync (:207-214) are thin overrides that call the base and - then evict; UpdateAsync (:186-202) is the hand-rolled action the base does not supply, reading the - required If-Match token (:191), dispatching - new UpdateEntityCommand<Activity, ActivityUpdateRequest, ActivityIdentifierType>(id, request, rowVersion) (:194), folding a failure through HandleFailure (:197-198), evicting (:200) and - returning Ok(result.Value) (:201).
    • -
    • Every eviction clears conference:activities and the broad conference tag (:168, 200, 212), the + notes that the attribute is needed because the class carries only a bare [Authorize] (:143-150).
    • +
    • CreateAsync ([HttpPost] at :170) and DeleteAsync ([HttpDelete("{id}")] at :214) are thin + overrides that call the base and then evict; UpdateAsync ([HttpPut("{id}")] at :189) is the + hand-rolled action the base does not supply, reading the required If-Match token (:200), + dispatching new UpdateEntityCommand<Activity, ActivityUpdateRequest, ActivityIdentifierType>(id, request, rowVersion) (:203), folding a failure through HandleFailure (:207), evicting (:209) + and returning Ok(result.Value) (:210).
    • +
    • Every eviction clears conference:activities and the broad conference tag (:177, 209, 221), the latter because the activity strip renders alongside other conference reads.
  • @@ -3379,43 +3515,51 @@

    PartnersController

    (:43-45); an IQueryHandler for GetPublicPartnerFilterQuery (:46); ICurrentUserService plus the CurrentUserServiceExtensions read-audience helper (:47, 57); - IOutputCacheStore (:48); the PartnerDTO; PartnerUpdateRequest as the PUT body (:163); the + IOutputCacheStore (:48); the PartnerDTO; PartnerUpdateRequest as the PUT body; the HasPermissionAttribute with the ConferencePermissions catalog; the SupportsIfMatchAttribute; and the QueryFilterModelBinder.
  • Concept introduced: none new. This is the third structural twin in the group, alongside ActivitiesController and SponsorsController: the same - bare [Authorize] class gate with a per-mutation capability, the same real-EventId-column scoping (the - paged action's doc comment states it directly, PartnersController.cs:73-74), and the same - attribute-plus-imperative export gate. [Rubric #11, Security]: the class carries [Authorize] - (PartnersController.cs:36) and each mutation re-asserts [HasPermission(ConferencePermissions.PartnersManage)] - (:119, 138, 156, 181), the capability declared at + bare [Authorize] class gate with a per-mutation capability, the same real-EventId-column scoping, the + same AllowUnscopedExport opt-in, and the same attribute-plus-imperative export gate. + [Rubric #11, Security]: the class carries [Authorize] (PartnersController.cs:40) and each mutation + re-asserts [HasPermission(ConferencePermissions.PartnersManage)] (:143, 162, 180, 205), the + capability declared at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Authorization/ConferencePermissions.cs:36 and included in the ContentManagement curation subset (ConferencePermissions.cs:76), so a content editor can manage the partner roster without holding event, room or question rights. [Rubric #12, Performance & Scalability]: reads run under the PartnersCache policy (5-minute TTL, tags conference and conference:partners, - MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:281) and every mutation evicts both tags.
  • + MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:280) and every mutation evicts both tags.
  • Walkthrough
    • IsPrivileged (PartnersController.cs:57) is the shared currentUserService.IsPrivilegedConferenceReader() read-audience check; GetReadSpecificationAsync (:66-76) returns null for a privileged reader and - otherwise the specification the GetPublicPartnerFilterQuery handler resolves (:72-75), degrading to - null on a failed result.
    • -
    • The four reads (:78, 95, 110, 121, the last under the named route "GetPartnerById" at :118) are - [AllowAnonymous] + [OutputCache(PolicyName = "PartnersCache")] passthroughs; the by-id doc comment - states that a partner of an unpublished event is a 404 for a non-privileged caller (:115-116).
    • -
    • ExportAsync (:134-150) pairs the declarative [HasPermission(ConferencePermissions.PartnersManage)] - (:135) with the imperative if (!IsPrivileged) return Forbid(); (:144-147), then delegates to the - base (:149). The doc comment names the leak an unscoped CSV would be, a non-privileged caller receiving - partners of unpublished events (:129-132).
    • -
    • CreateAsync (:152-162) and DeleteAsync (:195-205) are thin overrides that call the base and then - evict; UpdateAsync (:164-193) is the hand-rolled action the base does not supply, reading the required - If-Match token via SupportsIfMatchAttribute.RequiredToken(HttpContext) (:182), dispatching + otherwise the specification the GetPublicPartnerFilterQuery handler resolves, degrading to null on + a failed result.
    • +
    • AllowUnscopedExport => IsPrivileged (:84, doc :78-83) opts the fail-closed inherited export + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:272-274) in to + the unscoped table only for the privileged readers whose hook result is null.
    • +
    • The four reads ([HttpGet] at :86, "paged" at :100, "lookup" at :115, and by-id under the + named route "GetPartnerById" at :126) are [AllowAnonymous] + + [OutputCache(PolicyName = "PartnersCache")] passthroughs; the paged doc comment (:96-99) and the + by-id doc comment (:123-125) state the published-event scoping and that a partner of an unpublished + event is a 404 for a non-privileged caller.
    • +
    • ExportAsync ([HttpGet("export")] at :142) pairs the declarative + [HasPermission(ConferencePermissions.PartnersManage)] (:143) with the imperative + if (!IsPrivileged) return Forbid(); (:154), then delegates to the base (:157). The doc comment + names the leak an unscoped CSV would be, a non-privileged caller receiving partners of unpublished + events (:137-141).
    • +
    • CreateAsync ([HttpPost] at :161) and DeleteAsync ([HttpDelete("{id}")] at :204) are thin + overrides that call the base and then evict; UpdateAsync ([HttpPut("{id}")] at :179) is the + hand-rolled action the base does not supply, reading the required If-Match token via + SupportsIfMatchAttribute.RequiredToken(HttpContext) (:190), dispatching new UpdateEntityCommand<Partner, PartnerUpdateRequest, PartnerIdentifierType>(id, request, rowVersion) - (:184-186), folding a failure through HandleFailure (:188-189), evicting (:191) and returning - Ok(result.Value) (:192).
    • -
    • Every eviction clears conference:partners and the broad conference tag (:160, 191, 203).
    • + (:193), folding a failure through HandleFailure (:197), evicting (:199) and returning + Ok(result.Value) (:200). +
    • Every eviction clears conference:partners and the broad conference tag through the + PartnersCacheTag / ConferenceCacheTag constants (:53-54; evictions at :168, 199, 211).
  • Why it's built this way: partners share the same publish-gated visibility as the rest of the catalog, @@ -3449,18 +3593,18 @@

    SponsorsController

    AggregateRootEntityControllerBase (SponsorsController.cs:49-50); the IEntityQueryService - (:40); three ICommandHandlers for + (:41); three ICommandHandlers for SponsorCreateRequest, the framework's UpdateEntityCommand<TEntity, TUpdateRequest, TIdentifierType> and DeleteEntityCommand<TEntity, TIdentifierType> - (:41-43); an IQueryHandler for - GetPublicSponsorFilterQuery (:44); + (:42-44); an IQueryHandler for + GetPublicSponsorFilterQuery (:45); ICurrentUserService plus the - CurrentUserServiceExtensions read-audience helper (:45, 52); - IOutputCacheStore (:46); the SponsorDTO; - SponsorUpdateRequest as the PUT body - (:188); the HasPermissionAttribute with the + CurrentUserServiceExtensions read-audience helper (:46, 53); + IOutputCacheStore (:47); the SponsorDTO; + SponsorUpdateRequest as the PUT body; the + HasPermissionAttribute with the ConferencePermissions catalog; the SupportsIfMatchAttribute; and the QueryFilterModelBinder.
  • @@ -3469,36 +3613,40 @@

    SponsorsController

    not a column on the aggregate, so the paged action must intercept the key, remove it from the filter dictionary and translate it into a specification. Here the doc comments record the opposite situation, that Sponsor carries a real EventId column, so an event-scoped request travels through the generic - filter pipeline unchanged and the hook only adds the published-event rule on top of it (:54-67, 90-95). + filter pipeline unchanged and the hook only adds the published-event rule on top of it (:55-68, 99-104). The published rule and the caller's filter are composed by the query service rather than substituted, so scoping to an unpublished event returns an empty page to a non-privileged caller instead of leaking the roster. That is why this controller, like its ActivitiesController twin and unlike the speaker and session roots, has no filter-interception block at all. - [Rubric §11, Security]: the class carries a bare [Authorize] (:38) and each mutation re-asserts - [HasPermission(ConferencePermissions.SponsorsManage)] (:143, 162, 181, 206), the capability declared + [Rubric §11, Security]: the class carries a bare [Authorize] (:39) and each mutation re-asserts + [HasPermission(ConferencePermissions.SponsorsManage)] (:152, 171, 190, 215), the capability declared at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Authorization/ConferencePermissions.cs:33 and included in the ContentManagement curation subset (ConferencePermissions.cs:70-79), so a content editor can manage the sponsor roster without holding event, room or question rights. [Rubric §12, Performance & Scalability]: reads run under the SponsorsCache policy (5-minute TTL, tags conference - and conference:sponsors, MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:280) and - every mutation evicts both tags (:168, 200, 212). + and conference:sponsors, MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:279) and + every mutation evicts both tags (:177, 209, 221).
  • Walkthrough: the file is line-for-line the structural twin of ActivitiesController, so every member sits at the same line number in both. IsPrivileged (SponsorsController.cs:53) is the shared read-audience check; GetReadSpecificationAsync - (:68-78) returns null for a privileged reader and otherwise the specification the - GetPublicSponsorFilterQuery handler resolves (:74-77), degrading to null on a failed result. The - four reads (:83, 99, 114, 126, the last under the named route "GetSponsorById" at :123) are - [AllowAnonymous] + [OutputCache(PolicyName = "SponsorsCache")] passthroughs; the by-id doc comment - states that a sponsor of an unpublished event is a 404, "not a redacted record, so a guessed id cannot - confirm that a sponsorship was sold" (:119-122). ExportAsync (:144-158) is the strongest export - gate in this unit alongside SpeakersController's: the declarative - [HasPermission(ConferencePermissions.SponsorsManage)] (:143) plus the imperative - if (!IsPrivileged) return Forbid(); (:152-155), with the doc comment naming the commercial hazard an - unscoped CSV would create, confirming sponsorships that have not been announced (:134-141). - CreateAsync (:163-170) and DeleteAsync (:207-214) call the base and evict; UpdateAsync - (:186-202) reads the required If-Match token (:191), dispatches the generic - UpdateEntityCommand<Sponsor, SponsorUpdateRequest, SponsorIdentifierType> (:194), folds a failure - through HandleFailure (:197-198), evicts (:200) and returns Ok(result.Value) (:201).
  • + (:69-79) returns null for a privileged reader and otherwise the specification the + GetPublicSponsorFilterQuery handler resolves, degrading to null on a failed result. + AllowUnscopedExport => IsPrivileged (:87, doc :81-86) opts the fail-closed inherited export + (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:272-274) in to the + unscoped table only for that privileged audience. The four reads (:89, 105, 120, 132, the last under + the named route "GetSponsorById") are [AllowAnonymous] + + [OutputCache(PolicyName = "SponsorsCache")] passthroughs; the by-id doc comment states that a sponsor + of an unpublished event is a 404, "not a redacted record, so a guessed id cannot confirm that a + sponsorship was sold" (:128-131). ExportAsync ([HttpGet("export")] at :151) is the strongest + export gate in this unit alongside SpeakersController's: the declarative + [HasPermission(ConferencePermissions.SponsorsManage)] (:152) plus the imperative + if (!IsPrivileged) return Forbid(); (:163), with the doc comment naming the commercial hazard an + unscoped CSV would create, confirming sponsorships that have not been announced (:143-150). + CreateAsync ([HttpPost] at :170) and DeleteAsync ([HttpDelete("{id}")] at :214) call the + base and evict; UpdateAsync ([HttpPut("{id}")] at :189) reads the required If-Match token + (:200), dispatches the generic + UpdateEntityCommand<Sponsor, SponsorUpdateRequest, SponsorIdentifierType> (:203), folds a failure + through HandleFailure (:207), evicts (:209) and returns Ok(result.Value) (:210).
  • Why it's built this way: sponsors are commercially sensitive before an event is announced but fully public afterwards, which is the same published-event rule the rest of the catalog follows, so the controller reuses the specification hook rather than inventing a sponsor-specific visibility flag. @@ -3509,7 +3657,11 @@

    SponsorsController

    (MMCA.ADC/Source/Hosts/MMCA.ADC.Gateway/appsettings.json:134). Clients are the public sponsor page (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Sponsors/PublicSponsorList.razor) and the organizer sponsor list, create and detail pages under - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Sponsor/.
  • + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Sponsor/. Export authorization is + covered by + MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.API.Tests/Conventions/EntityExportAuthorizationTests.cs + and UpdateAsync's conditional-write contract by + MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.API.Tests/Conventions/ConditionalWriteConventionTests.cs.

    SessionBookmarksGrpcService

    @@ -3583,8 +3735,8 @@

    SessionBookmarksGrpcService

  • Where it's used: mapped in the Conference host as app.MapGrpcService<SessionBookmarksGrpcService>().RequireAuthorization() - (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:468). The RequireAuthorization() is - deliberate and explained inline (Program.cs:461-467): these RPCs answer conference-state questions + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:461). The RequireAuthorization() is + deliberate and explained inline (Program.cs:454-460): these RPCs answer conference-state questions raised on behalf of a specific end user, so internal-only ingress is not considered sufficient. Every caller is an Engagement handler sitting behind an authenticated controller, so an inbound bearer token is always present for @@ -3626,11 +3778,11 @@

    SessionBookmarkValidationSe try / catch (RpcException ex) and returns ex.ToResult() or ex.ToResult<T>() (:53-59, :79-86). That decoder walks the error-{i}-* trailers starting at index zero and stopping at the first missing error-{i}-code, mirroring the writer's loop - (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:149-157, :177-186), and a + (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/ResultGrpcExtensions.cs:154-163, :177-186), and a missing error-{i}-type falls back to ErrorType.Failure rather than throwing. Structured trailers win when present; a transport-level fault carrying none (connection reset, deadline exceeded) degrades to a single Grpc.{StatusCode} failure sourced with the calling method's name, which the decoder captures via - [CallerMemberName] (ResultGrpcExtensions.cs:210, :234). The practical consequence is stated in the + [CallerMemberName] (ResultGrpcExtensions.cs:216, :234). The practical consequence is stated in the second catch's comment (SessionBookmarkValidationServiceGrpcAdapter.cs:81-84): a Conference outage turns a bookmarks-by-event read into a Result failure the caller can handle, not a raw 500.

  • Walkthrough
      @@ -3658,7 +3810,7 @@

      SessionBookmarkValidationSe Conference.Shared's interface and not the other way round.
    • Where it's used: registered by AddConferenceSessionValidationClient() (see DependencyInjection), which the Engagement host calls - (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:281). Its eventual consumers are + (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:283). Its eventual consumers are CreateBookmarkHandler and GetUserBookmarksHandler behind BookmarksController @@ -3733,10 +3885,10 @@

      EventLiveValidationGrpcService

      the transport class keeps the domain read models free of any serialization concern.

    • Where it's used: mapped as app.MapGrpcService<EventLiveValidationGrpcService>().RequireAuthorization() - (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:469). The named callers are + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:462). The named callers are Engagement's LivePollsController and SessionQuestionsController handlers - (Program.cs:464-466).

      + (Program.cs:457-459).

    EventLiveValidationServiceGrpcAdapter

    @@ -3755,28 +3907,39 @@

    EventLiveValidationServiceGrpcAda SponsorLiveInfo, RoomSessionInfo and the QuestionModerationDefault enum; - Result in its generic form; and the same - RpcException.ToResult<T> decoder from + Result in its generic form, with + Error and + ErrorType for the malformed-response failure; and the + same RpcException.ToResult<T> decoder from ResultGrpcExtensions. Externals: Grpc.Core, - System.Guid.

    + System.Guid, System.Enum.

  • Concept introduced: none new. This is the mirror image of SessionBookmarkValidationServiceGrpcAdapter: the same - 5 second CallDeadline (EventLiveValidationServiceGrpcAdapter.cs:34, justified at :30-32 because + 5 second CallDeadline (EventLiveValidationServiceGrpcAdapter.cs:34, justified at :31-33 because these lookups gate live-layer commands such as opening a poll or submitting a question), the same try / catch (RpcException ex) around every call, and the same ex.ToResult<T>() decode of the error-{i}-* trailers. What repays close reading here is the decoding side of the wire - representations the server chose.

    + representations the server chose, and the rule that wire data the adapter cannot map is a failure + Result, the same shape a transport fault takes, never an exception that escapes the RpcException + catch (:80-81).

      -
    • DateTimeOffset.FromUnixTimeSeconds(response.LiveWindowStartUnixSeconds).UtcDateTime (:52-53, - :82-83) is the exact inverse of the server's encoding, and .UtcDateTime is what restores a UTC +
    • DateTimeOffset.FromUnixTimeSeconds(response.LiveWindowStartUnixSeconds).UtcDateTime (:53-54, + :106-107) is the exact inverse of the server's encoding, and .UtcDateTime is what restores a UTC DateTime rather than a local one. Getting that property wrong is the classic way a live window silently shifts by the host's offset.
    • -
    • [.. response.SpeakerIds.Select(Guid.Parse)] (:84) parses the string identifiers back. Guid.Parse - (not TryParse) is a deliberate assertion that the peer is a matching server version: a malformed id is - a contract violation, not a user error.
    • -
    • (QuestionModerationDefault)response.QuestionModerationDefault (:86) casts the int back to the - enum, the half of the enum contract that depends on the two assemblies agreeing on ordering.
    • +
    • The string speaker identifiers are parsed back one at a time with Guid.TryParse into a pre-sized + List<SpeakerIdentifierType> (:82-93). A value that is not a GUID short-circuits the call with + MalformedResponse<SessionLiveInfo>("speaker_ids contains a value that is not a GUID.", ...) + (:85-90) instead of throwing a FormatException past the catch.
    • +
    • (QuestionModerationDefault)response.QuestionModerationDefault (:95) casts the int back to the + enum, the half of the enum contract that depends on the two assemblies agreeing on ordering, and + Enum.IsDefined (:96-101) turns an out-of-range value into the same malformed-response failure + rather than letting an undefined enum value reach Engagement.
    • +
    • MalformedResponse<T>(message, source) (:182-188) builds that failure the way the framework's + RpcException.ToResult builds a transport fault: one ErrorType.Failure error with code + Grpc.MalformedResponse, sourced with the calling method's name (:188). A caller cannot tell a + garbled reply from a refused one by shape, only by code.

    [Rubric §9, API & Contract Design] and [Rubric §29, Resilience & Business Continuity] apply as in the sibling adapter.

    @@ -3784,19 +3947,21 @@

    EventLiveValidationServiceGrpcAda
  • Walkthrough: four methods, one per RPC, each identical in shape (build request, call with deadline and token, project the response into the read model, catch RpcException and decode).

      -
    • GetEventLiveInfoAsync(eventId, ct) (:36-62): returns - Result.Success(new EventLiveInfo(response.IsPublished, start, end)) (:50-53).
    • -
    • GetSessionLiveInfoAsync(sessionId, ct) (:65-95): the seven-argument SessionLiveInfo - reconstruction described above (:79-86).
    • -
    • GetSponsorLiveInfoAsync(sponsorId, ct) (:98-124): - new SponsorLiveInfo(response.EventId, response.IsPublished, response.SponsorName) (:112-115).
    • -
    • GetCurrentRoomSessionInfoAsync(roomId, graceMinutes, ct) (:127-156): puts GraceMinutes on the - request (:137-138) and rebuilds +
    • GetEventLiveInfoAsync(eventId, ct) (:36-63): returns + Result.Success(new EventLiveInfo(response.IsPublished, start, end)) (:51-54).
    • +
    • GetSessionLiveInfoAsync(sessionId, ct) (:65-119): validates the speaker ids (:82-93) and the + moderation default (:95-101) as described above, then the seven-argument SessionLiveInfo + reconstruction (:103-110).
    • +
    • GetSponsorLiveInfoAsync(sponsorId, ct) (:121-148): + new SponsorLiveInfo(response.EventId, response.IsPublished, response.SponsorName) (:136-139).
    • +
    • GetCurrentRoomSessionInfoAsync(roomId, graceMinutes, ct) (:150-180): puts GraceMinutes on the + request (:159-163) and rebuilds new RoomSessionInfo(response.SessionId, response.SessionTitle, response.EventId, response.IsPublished) - (:143-147).
    • -
    • Every catch block returns ex.ToResult<T>() for the matching T (:60, :93, :122, :154), so a + (:167-171).
    • +
    • Every catch block returns ex.ToResult<T>() for the matching T (:61, :117, :146, :178), so a Conference outage is uniformly a Result failure across all four lookups rather than an exception on - some paths and a failure on others.
    • + some paths and a failure on others; with MalformedResponse the same holds for a reply that arrives + but cannot be mapped.
  • Why it's built this way: ADR-007. The class doc notes the specific consequence for this pair @@ -3808,7 +3973,7 @@

    EventLiveValidationServiceGrpcAda

  • Where it's used: registered by AddConferenceEventLiveValidationClient() (see DependencyInjection), called from the Engagement host - (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:282). Consumers are Engagement's + (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:284). Consumers are Engagement's live-poll and session-question handlers (Program.cs:237-238).

  • @@ -3892,7 +4057,7 @@

    DependencyInjection

    whole answer in one 84-line file.
  • Where it's used: the Engagement service host calls both inside its application-pipeline registration, in the documented order after moduleHost.RegisterModules - (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:280-282), with the rationale for each + (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:282-284), with the rationale for each written out immediately above (Program.cs:230-238). The AppHost declares the matching Engagement to Conference reference (MMCA.ADC/Source/Hosting/MMCA.ADC.AppHost/Program.cs:270).
  • Caveats / not-in-source: the "call this after DiscoverAndRegister" requirement is carried by diff --git a/docs/onboarding/group-21-conference-ui.html b/docs/onboarding/group-21-conference-ui.html index 18f42861..8853b0a1 100644 --- a/docs/onboarding/group-21-conference-ui.html +++ b/docs/onboarding/group-21-conference-ui.html @@ -151,13 +151,13 @@

    21. ADC Conference - UI

    Folders follow the workspace's feature-by-folder rule with a technical root: pages live under Pages/<Aggregate>/ (Pages/Events/, Pages/Sessions/, Pages/Partners/, with the anonymous family nested one level deeper as Pages/Public/<Aggregate>/) and services under Services/<Aggregate>/, so a citation's folder names the feature area it belongs to.

    The layering inside the UI: a page never touches HttpClient

    Each page is a .razor + .razor.cs code-behind pair that depends on a UI service interface, never on HttpClient and never on the API's internals. The ten CRUD-shaped entities (events, sessions, speakers, conference categories, category items, questions, rooms, sponsors, partners, activities) each get a service deriving from Common's EntityServiceBase<TEntityDTO, TIdentifierType> and exposing the IEntityService<TEntityDTO, TIdentifierType> contract: EventService, SessionService, SpeakerService, ConferenceCategoryService, CategoryItemService, QuestionService, RoomService, SponsorService, PartnerService, and ActivityService (Services/Events/EventService.cs:15, Services/Sessions/SessionService.cs:10, Services/Speakers/SpeakerService.cs:13, Services/Categories/ConferenceCategoryService.cs:10, Services/Categories/CategoryItemService.cs:10, Services/Questions/QuestionService.cs:10, Services/Rooms/RoomService.cs:14, Services/Sponsors/SponsorService.cs:10, Services/Partners/PartnerService.cs:10, Services/Activities/ActivityService.cs:10). They inherit GetAllAsync/GetPagedAsync/GetByIdAsync/AddAsync/UpdateAsync/DeleteAsync and only add the handful of bespoke verbs the conference needs. Seven add nothing at all: ActivityService, SponsorService, PartnerService, SessionService, QuestionService, CategoryItemService, and ConferenceCategoryService are fourteen-line files whose entire job is to bind an endpoint name to a DTO and an identifier alias (Services/Partners/PartnerService.cs:11 to :12 is the whole of one), and their interfaces (IActivityUIService at Services/Activities/IActivityUIService.cs:9, ISponsorUIService at Services/Sponsors/ISponsorUIService.cs:9, IPartnerUIService at Services/Partners/IPartnerUIService.cs:9, and siblings) are equally empty extensions of the generic contract.

    -

    Three services show where extension goes. EventService layers PublishAsync, UnpublishAsync, RefreshFromSessionizeAsync, and RefreshFromSessionizeWithCodeAsync onto the inherited CRUD (Services/Events/EventService.cs:19, :31, :43, :53). The first two carry the loaded row version as an If-Match header built by ConcurrencyETag (Services/Events/EventService.cs:29, :41), so a publish races against a concurrent edit at the API rather than in the browser. The fourth is a small orchestration worth reading: an edited Sessionize code has to be persisted before the import runs (the import reads the code off the stored event), and the import rewrites the event's children and refresh stamp, so the method persists, imports, reloads, and hands back both halves as one SessionizeRefreshOutcome (Services/Events/EventService.cs:61 to :82, record at Services/Events/SessionizeRefreshOutcome.cs:13). That is the recurring shape: one call, one Result, one failure branch for the page. RoomService overrides AddAsync to reshape the POST body, because the API's AddRoomRequest contract names the key RoomId while the DTO calls it Id (Services/Rooms/RoomService.cs:18 to :38), and adds a two-argument DeleteAsync that passes the owning event on the query string (Services/Rooms/RoomService.cs:40). SpeakerService adds LinkUserAsync/UnlinkUserAsync for binding a speaker record to an identity account (Services/Speakers/SpeakerService.cs:17, :28).

    -

    Nothing in this layer throws for a server answer. Every call funnels through the framework's HttpResultExecutor and ProblemDetailsResultReader, so a transport fault and a ProblemDetails body both arrive as a failed Result with a typed error the page can branch on (Services/Speakers/SpeakerLookupService.cs:17 and :25, Services/Sessions/Selection/SessionSelectionService.cs:21 and :31, Services/Feedback/OrganizerFeedbackService.cs:28 and :35). [Rubric §3, Clean Architecture] and [Rubric §9, API & Contract Design]: the page binds to a DTO contract (EventDTO, SessionDTO, SpeakerDTO, SponsorDTO, ActivityDTO) plus an interface, and the wire envelope is the uniform PagedCollectionResult<T> / CollectionResult<T> the API returns for every entity.

    +

    Three services show where extension goes. EventService layers PublishAsync, UnpublishAsync, RefreshFromSessionizeAsync, and RefreshFromSessionizeWithCodeAsync onto the inherited CRUD (Services/Events/EventService.cs:19, :31, :43, :53). The first two carry the loaded row version as an If-Match header built by ConcurrencyETag (Services/Events/EventService.cs:29, :41), so a publish races against a concurrent edit at the API rather than in the browser. The fourth is a small orchestration worth reading: an edited Sessionize code has to be persisted before the import runs (the import reads the code off the stored event), and the import rewrites the event's children and refresh stamp, so the method persists, imports, reloads, and hands back both halves as one SessionizeRefreshOutcome (Services/Events/EventService.cs:61 to :82, record at Services/Events/SessionizeRefreshOutcome.cs:13). That is the recurring shape: one call, one Result, one failure branch for the page. RoomService overrides AddAsync to reshape the POST body, because the API's AddRoomRequest contract names the key RoomId while the DTO calls it Id (Services/Rooms/RoomService.cs:18 to :40), and adds a two-argument DeleteAsync that passes the owning event on the query string (Services/Rooms/RoomService.cs:42). SpeakerService adds LinkUserAsync/UnlinkUserAsync for binding a speaker record to an identity account (Services/Speakers/SpeakerService.cs:17, :28).

    +

    Nothing in this layer throws for a server answer. Every call funnels through the framework's HttpResultExecutor and ProblemDetailsResultReader, so a transport fault and a ProblemDetails body both arrive as a failed Result with a typed error the page can branch on (Services/Speakers/SpeakerLookupService.cs:21 and :30, Services/Sessions/Selection/SessionSelectionService.cs:21 and :31, Services/Feedback/OrganizerFeedbackService.cs:28 and :38). [Rubric §3, Clean Architecture] and [Rubric §9, API & Contract Design]: the page binds to a DTO contract (EventDTO, SessionDTO, SpeakerDTO, SponsorDTO, ActivityDTO) plus an interface, and the wire envelope is the uniform PagedCollectionResult<T> / CollectionResult<T> the API returns for every entity.

    The list pages: two bases, one recipe

    -

    Twelve list screens hang off DataGridListPageBase<TDto>, which supplies server-side paging against MudDataGrid<T>, cancellation lifecycle, loading and load-failed state, filter and sort extraction from MudBlazor's GridState<T>, toast error surfacing, saved page/rows-per-page/scroll restoration, and viewport-driven mobile rendering that swaps the grid for a MobileInfiniteScrollList<TItem>. Six derive from it directly, because their rows are not scoped to a conference event: EventList, SessionList, QuestionList, ConferenceCategoryList, PublicEventList, and PublicSessionList (Pages/Events/EventList.razor.cs:17, Pages/Sessions/SessionList.razor.cs:22, Pages/Questions/QuestionList.razor.cs:12, Pages/Categories/ConferenceCategoryList.razor.cs:12, Pages/Public/Events/PublicEventList.razor.cs:31, Pages/Public/Sessions/PublicSessionList.razor.cs:30). A concrete page reduces to overriding Title, GridRef, SaveFilters/RestoreFilters, and a LoadServerData delegate that calls its service's GetPagedAsync, with delete-and-confirm delegated to the shared ListPageActions helper.

    -

    The other six list pages scope their rows to one conference event, and that repetition earned its own ADC-local base: EventFilteredListPageBase<TDto> (Pages/Common/EventFilteredListPageBase.cs:25), used by RoomList, SpeakerList, SponsorList, PartnerList, ActivityList, and PublicSpeakerList (Pages/Rooms/RoomList.razor.cs:13, Pages/Speakers/SpeakerList.razor.cs:19, Pages/Sponsors/SponsorList.razor.cs:19, Pages/Partners/PartnerList.razor.cs:19, Pages/Activities/ActivityList.razor.cs:20, Pages/Public/Speakers/PublicSpeakerList.razor.cs:33). It injects IEventLookupService (Pages/Common/EventFilteredListPageBase.cs:27), starts the event load before the first await of OnInitializedAsync and exposes it as EventsLoadTask (:50, armed at :144, awaited at :137) because the grid's first ServerData call can race ahead of initialization, and gives derived pages WaitForEventsAsync and ApplyEventFilter to close that race deterministically (:192, :195). It seals SaveFilters/RestoreFilters and hands pages SavePageFilters/RestorePageFilters instead (Pages/Common/EventFilteredListPageBase.cs:86, :89, :100, :112), so the eventId half of the saved state is written once, with the explicit "all" sentinel that distinguishes a deliberate clear from no saved state at all (:105, read back at :119). Default resolution is one method: a restored id that still exists wins, a dangling one falls back to the live-or-next event computed by CurrentEventSelector (:182), and a page whose scope is locked is always pinned to that computed default (:169 to :189, opt-out property at :57, overridden by PublicSpeakerList to "only privileged readers choose" at Pages/Public/Speakers/PublicSpeakerList.razor.cs:53). Two details are worth internalizing. The event picker renders only once the component is interactive (Pages/Common/EventFilteredListPageBase.cs:72): an SSR prerender would paint a fully formed picker into static HTML whose clicks are silently swallowed until the interactive runtime attaches, which under InteractiveAuto's WebAssembly leg is a whole runtime boot later. And a failed lookup is remembered rather than swallowed (:42, :163), so a page that must fail closed can branch instead of falling through to an unscoped query. [Rubric §19, State Management & Data Flow] and [Rubric §23, Front-End Performance & Rendering].

    -

    Whether that event filter needs any translation at all depends on the entity. PartnerList is the simplest case in the family: EventId is a real Partner column, so the filter goes straight through the generic filter pipeline with no virtual-key resolution, and the page reduces to a title, a grid reference, a search box saved through SavePageFilters/RestorePageFilters, and a localized type column (Pages/Partners/PartnerList.razor.cs:19, rationale at :14 to :17, filters at :39 to :43, type label at :37). ActivityList is the same shape (Pages/Activities/ActivityList.razor.cs:20). The speaker lists are the hard case: a speaker's event association travels through join tables, so their event filter is a virtual key resolved server-side (Pages/Public/Speakers/PublicSpeakerList.razor.cs:19 to :20).

    -

    Two public lists deliberately opt out of the grid entirely. PublicSponsorList and PublicActivityList are plain ComponentBase pages (Pages/Public/Sponsors/PublicSponsorList.razor.cs:20, Pages/Public/Activities/PublicActivityList.razor.cs:21), because a sponsor roster and a social programme are bounded (both cap the fetch at 200 rows, Pages/Public/Sponsors/PublicSponsorList.razor.cs:23, Pages/Public/Activities/PublicActivityList.razor.cs:24) and render as tier-grouped logo cards and a chronological programme rather than a sortable table, so each fetches one page and orders it in memory (Pages/Public/Sponsors/PublicSponsorList.razor.cs:88 to :92, Pages/Public/Activities/PublicActivityList.razor.cs:88). PublicSpeakerList splits the difference: it keeps the base class's page-based mobile fetch path but appends each page to an accumulating list and hangs Common's InfiniteScrollSentinel below its card grid, twelve cards per chunk so a full chunk fills whole rows (Pages/Public/Speakers/PublicSpeakerList.razor.cs:25, :36).

    +

    Twelve list screens hang off DataGridListPageBase<TDto>, which supplies server-side paging against MudDataGrid<T>, cancellation lifecycle, loading and load-failed state, filter and sort extraction from MudBlazor's GridState<T>, toast error surfacing, saved page/rows-per-page/scroll restoration, and viewport-driven mobile rendering that swaps the grid for a MobileInfiniteScrollList<TItem>. Six derive from it directly, because their rows are not scoped to a conference event: EventList, SessionList, QuestionList, ConferenceCategoryList, PublicEventList, and PublicSessionList (Pages/Events/EventList.razor.cs:17, Pages/Sessions/SessionList.razor.cs:22, Pages/Questions/QuestionList.razor.cs:12, Pages/Categories/ConferenceCategoryList.razor.cs:12, Pages/Public/Events/PublicEventList.razor.cs:30, Pages/Public/Sessions/PublicSessionList.razor.cs:29). A concrete page reduces to overriding Title, GridRef, SaveFilters/RestoreFilters, and a LoadServerData delegate that calls its service's GetPagedAsync, with delete-and-confirm delegated to the shared ListPageActions helper.

    +

    The other six list pages scope their rows to one conference event, and that repetition earned its own ADC-local base: EventFilteredListPageBase<TDto> (Pages/Common/EventFilteredListPageBase.cs:25), used by RoomList, SpeakerList, SponsorList, PartnerList, ActivityList, and PublicSpeakerList (Pages/Rooms/RoomList.razor.cs:13, Pages/Speakers/SpeakerList.razor.cs:19, Pages/Sponsors/SponsorList.razor.cs:19, Pages/Partners/PartnerList.razor.cs:19, Pages/Activities/ActivityList.razor.cs:20, Pages/Public/Speakers/PublicSpeakerList.razor.cs:32). It injects IEventLookupService (Pages/Common/EventFilteredListPageBase.cs:27), starts the event load before the first await of OnInitializedAsync and exposes it as EventsLoadTask (:51, armed at :145, awaited at :138) because the grid's first ServerData call can race ahead of initialization, and gives derived pages WaitForEventsAsync and ApplyEventFilter to close that race deterministically (:193, :196). It seals SaveFilters/RestoreFilters and hands pages SavePageFilters/RestorePageFilters instead (Pages/Common/EventFilteredListPageBase.cs:87, :90, :99, :111), so the eventId half of the saved state is written once, with the explicit "all" sentinel that distinguishes a deliberate clear from no saved state at all (:106, read back at :120). Default resolution is one method: a restored id that still exists wins, a dangling one falls back to the live-or-next event computed by CurrentEventSelector against the clock read through the injected TimeProvider (:183, clock at :188), and a page whose scope is locked is always pinned to that computed default (:170 to :190, opt-out property at :57, overridden by PublicSpeakerList to "only privileged readers choose" at Pages/Public/Speakers/PublicSpeakerList.razor.cs:52). Two details are worth internalizing. The event picker renders only once the component is interactive (Pages/Common/EventFilteredListPageBase.cs:73): an SSR prerender would paint a fully formed picker into static HTML whose clicks are silently swallowed until the interactive runtime attaches, which under InteractiveAuto's WebAssembly leg is a whole runtime boot later. And a failed lookup is remembered rather than swallowed (:43, :164), so a page that must fail closed can branch instead of falling through to an unscoped query. [Rubric §19, State Management & Data Flow] and [Rubric §23, Front-End Performance & Rendering].

    +

    Whether that event filter needs any translation at all depends on the entity. PartnerList is the simplest case in the family: EventId is a real Partner column, so the filter goes straight through the generic filter pipeline with no virtual-key resolution, and the page reduces to a title, a grid reference, a search box saved through SavePageFilters/RestorePageFilters, and a localized type column (Pages/Partners/PartnerList.razor.cs:19, rationale at :14 to :17, filters at :39 to :43, type label at :37). ActivityList is the same shape (Pages/Activities/ActivityList.razor.cs:20). The speaker lists are the hard case: a speaker's event association travels through join tables, so their event filter is a virtual key resolved server-side (Pages/Public/Speakers/PublicSpeakerList.razor.cs:18 to :19).

    +

    Two public lists deliberately opt out of the grid entirely. PublicSponsorList and PublicActivityList are plain ComponentBase pages (Pages/Public/Sponsors/PublicSponsorList.razor.cs:20, Pages/Public/Activities/PublicActivityList.razor.cs:21), because a sponsor roster and a social programme are bounded (both cap the fetch at 200 rows, Pages/Public/Sponsors/PublicSponsorList.razor.cs:23, Pages/Public/Activities/PublicActivityList.razor.cs:24) and render as tier-grouped logo cards and a chronological programme rather than a sortable table, so each fetches one page and orders it in memory (Pages/Public/Sponsors/PublicSponsorList.razor.cs:103 to :107, Pages/Public/Activities/PublicActivityList.razor.cs:103 to :105). PublicSpeakerList splits the difference: it keeps the base class's page-based mobile fetch path but appends each page to an accumulating list and hangs Common's InfiniteScrollSentinel below its card grid, twelve cards per chunk so a full chunk fills whole rows (Pages/Public/Speakers/PublicSpeakerList.razor.cs:24, :35).

    Detail pages, form models, and one declaration of the rules

    The organizer detail pages have no ADC-local base: all nine inherit Common's DetailPageBase (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Common/DetailPageBase.cs:26) from markup, and it owns what every one of them would otherwise repeat verbatim: the page-scoped CancellationTokenSource with its dispose pattern (DetailPageBase.cs:28, :52), a LatestLoadGuard for route-driven reloads (documented at :9 to :11), and the inline edit-mode lifecycle, IsEditing/IsDirty plus the BeginEdit/EndEdit transitions that keep the dirty flag from being left set behind a closed editor (:46, :49, :68, :75). The nine are EventDetail, SessionDetail, SpeakerDetail, ConferenceCategoryDetail, RoomDetail, QuestionDetail, SponsorDetail, PartnerDetail, and ActivityDetail (@inherits at Pages/Events/EventDetail.razor:9, Pages/Sessions/SessionDetail.razor:9, Pages/Speakers/SpeakerDetail.razor:9, Pages/Categories/ConferenceCategoryDetail.razor:3, Pages/Rooms/RoomDetail.razor:7, Pages/Questions/QuestionDetail.razor:7, Pages/Sponsors/SponsorDetail.razor:8, Pages/Partners/PartnerDetail.razor:7, Pages/Activities/ActivityDetail.razor:7), and the dirty flag feeds Common's UnsavedChangesGuard directly. [Rubric §24, Forms, Validation & UX Safety].

    Editable fields are not loose page state. Each entity declares an abstract form model carrying its DataAnnotations, and the create page and the detail page's inline editor each bind a sealed subclass of it: EventFormModel with EventCreateModel and EventEditModel (Pages/Events/EventFormModel.cs:27, Pages/Events/EventCreateModel.cs:11, Pages/Events/EventEditModel.cs:11), and the same triad for sessions, speakers, rooms, questions, sponsors, partners, activities, and conference categories (SessionFormModel Pages/Sessions/SessionFormModel.cs:24, SpeakerFormModel Pages/Speakers/SpeakerFormModel.cs:25, RoomFormModel Pages/Rooms/RoomFormModel.cs:24, QuestionFormModel Pages/Questions/QuestionFormModel.cs:24, SponsorFormModel Pages/Sponsors/SponsorFormModel.cs:25, PartnerFormModel Pages/Partners/PartnerFormModel.cs:18 with PartnerCreateModel Pages/Partners/PartnerCreateModel.cs:10 and PartnerEditModel Pages/Partners/PartnerEditModel.cs:10, ActivityFormModel Pages/Activities/ActivityFormModel.cs:26, ConferenceCategoryFormModel Pages/Categories/ConferenceCategoryFormModel.cs:24). The rules live once: the MudForm fields bridge to the annotations through Common's ModelValidation rather than repeating Required/RequiredError per field, the length caps come from the DTO's own constants, and every ErrorMessage is a resource key resolved by the localizing DataAnnotationsModelValidator (ADR-027). PartnerFormModel is the compact worked example: its four text rules take their caps from PartnerDTO.NameMaxLength, DescriptionMaxLength, LogoUrlMaxLength, and WebsiteUrlMaxLength (Pages/Partners/PartnerFormModel.cs:28, :32, :37, :42), the missing-value key is a single const shared by the model's [Required] and the field's RequiredError affordance so the two can never drift (:24, :27), the two URL fields carry Common's AbsoluteUrl rule (:36, :41), and the one enum field defaults to PartnerType.Community on the stated rationale that, unlike a sponsorship tier, a partner type carries no commercial weight (:45 to :49). The subclasses own only the direction of travel: EventCreateModel.ToNew() builds the posted DTO and always as an unpublished draft, because publishing is a separate gesture on the detail page (Pages/Events/EventCreateModel.cs:25 to :42), while EventEditModel.LoadFrom(source) copies the loaded event into the editor in one call and ToUpdated(source) writes the edited values over the identity, row version, and published state that were loaded (Pages/Events/EventEditModel.cs:18, :48). Because both pages bind the same EventFormFields.razor component to an instance of the same base, a value the create page accepts is a value the detail page accepts. [Rubric §1, SOLID] and [Rubric §15, Best Practices & Code Quality]. The one model with no create page to mirror follows the same discipline anyway: ConferenceCategoryItemEditModel declares the inline item form's rules against CategoryItemDTO's own length constant (Pages/Categories/ConferenceCategoryItemEditModel.cs:19 to :31).

    @@ -168,39 +168,39 @@

    Contain

    Child-and-join entities: a thin POST/DELETE base

    Sessions, speakers, and events own join relationships (a speaker added to a session, a category item to a speaker) that the generic CRUD base cannot model, because the write carries a parent id. These get four near-identical services (EventSpeakerService, SessionSpeakerService, SessionCategoryItemService, SpeakerCategoryItemService) over the shared ChildEntityServiceBase, which was hoisted out of this module into MMCA.Common.UI so every consumer module can reuse it (the note is left in place at Services/Common/ChildEntityServices.cs:89). Each Conference join service reduces to supplying its endpoint and adding typed AddAsync/DeleteAsync wrappers over the base's two verbs (Services/Common/ChildEntityServices.cs:22, :35, :48, :61), and their four interfaces live together in one file (Services/Common/IChildEntityUIService.cs:14, :26, :38, :50). Note the hard-won detail, now enforced by a helper rather than by discipline: the add payload always names the parent explicitly (new { EventId = eventId, SpeakerId = speakerId }, Services/Common/ChildEntityServices.cs:26), and every removal appends its owning aggregate's id through ChildEntityDeletePath (Services/Common/ChildEntityServices.cs:76, called at :29, :42, :55, :68). The join controllers remove a row by loading the parent aggregate and asking it to drop the child, so a DELETE without the parent binds it to default, finds nothing, and answers 404 while the UI reports a generic failure (Services/Common/ChildEntityServices.cs:14 to :21).

    Display-enrichment lookups: the GetAll-vs-GetById populator gap, worked around in the UI

    -

    Because the API's list endpoints do not always populate every cross-entity navigation, several pages need a cheap id-to-name map to render speaker names beside a session or an event name beside a room, a sponsor, a partner, or an activity. Three lookup services fill that role, SpeakerLookupService, EventLookupService, and CategoryItemLookupService (behind ISpeakerLookupService, IEventLookupService, and ICategoryItemLookupService). Each does one pageSize=10000 fetch with children and foreign keys suppressed, and folds the result into a Dictionary of lightweight projection records, SpeakerInfo, EventInfo, CategoryItemInfo (Services/Speakers/SpeakerLookupService.cs:12, fetch at :22, fold at :32; Services/Events/EventLookupService.cs:33; Services/Categories/CategoryItemLookupService.cs:12; records at Services/Speakers/ISpeakerLookupService.cs:9, Services/Events/IEventLookupService.cs:14, Services/Categories/ICategoryItemLookupService.cs:9). EventInfo is the one projection that grew a feature-specific field: SponsorshipPacketUrl is an optional trailing parameter defaulting to null precisely so the many call sites that need only identity and dates stayed unchanged, and only the public sponsor page reads it (Services/Events/IEventLookupService.cs:14 to :25, read at Pages/Public/Sponsors/PublicSponsorList.razor.cs:63). The organizer detail pages use the same map for a plain display concern: PartnerDetail shows the owning event's name and falls back to the raw id when the lookup is unavailable, rather than failing the page over a label (Pages/Partners/PartnerDetail.razor.cs:24, :37 to :40). This is a deliberate client-side join over the navigation-populator (ADR-002) gap between the API's list and by-id read shapes.

    +

    Because the API's list endpoints do not always populate every cross-entity navigation, several pages need a cheap id-to-name map to render speaker names beside a session or an event name beside a room, a sponsor, a partner, or an activity. Three lookup services fill that role, SpeakerLookupService, EventLookupService, and CategoryItemLookupService (behind ISpeakerLookupService, IEventLookupService, and ICategoryItemLookupService). Each reads its whole entity set through Common's PagedReadAll, which pages the /paged endpoint in stable id order with children and foreign keys suppressed until the server's reported total is reached, because a single read truncates silently at the API's 500-row page-size clamp (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/PagedReadAll.cs:19, page size at :22, URL at :43, rationale at :13 to :17); each page is a GET through IdempotentReadRetry, and the rows fold into a Dictionary of lightweight projection records, SpeakerInfo, EventInfo, CategoryItemInfo (Services/Speakers/SpeakerLookupService.cs:12, read at :20 to :34, fold at :39 to :49; Services/Events/EventLookupService.cs:33; Services/Categories/CategoryItemLookupService.cs:12; records at Services/Speakers/ISpeakerLookupService.cs:9, Services/Events/IEventLookupService.cs:14, Services/Categories/ICategoryItemLookupService.cs:9). EventLookupService also memoizes its read per scope for five minutes, and caches the task rather than the result, so the overlapping loads EventFilteredListPageBase starts share one in-flight fetch; no lock guards the cache because a scope's component callbacks run on one logical thread (Services/Events/EventLookupService.cs:14 to :24, TTL at :41, TimeProvider injected at :33, reuse check at :61). EventInfo is the one projection that grew a feature-specific field: SponsorshipPacketUrl is an optional trailing parameter defaulting to null precisely so the many call sites that need only identity and dates stayed unchanged, and only the public sponsor page reads it (Services/Events/IEventLookupService.cs:10 to :21, read at Pages/Public/Sponsors/PublicSponsorList.razor.cs:79). The organizer detail pages use the same map for a plain display concern: PartnerDetail shows the owning event's name and falls back to the raw id when the lookup is unavailable, rather than failing the page over a label (Pages/Partners/PartnerDetail.razor.cs:24, :37 to :40). This is a deliberate client-side join over the navigation-populator (ADR-002) gap between the API's list and by-id read shapes.

    One page needed three of these together, and got a composite rather than three nullable caches and three failure branches: SpeakerDetailLookupService gathers the category items, their owning category titles, and the question texts into a single SpeakerDetailLookups record answered as one Result, short-circuiting on the first failure (Services/Speakers/SpeakerDetailLookupService.cs:13, record at Services/Speakers/ISpeakerDetailLookupService.cs:14, interface at :25). The same "one call, one failure branch" instinct produced SessionizeRefreshOutcome above and SessionLookups beside the session detail page.

    Three feature areas that go beyond CRUD

    First, the speaker self-service dashboard. SpeakerDashboard sits behind a plain [Authorize] attribute and is gated on the speaker_id JWT claim, showing the linked speaker's sessions, per-session bookmark counts, and feedback (Pages/Speakers/SpeakerDashboard.razor.cs:22). It leans on SpeakerDashboardService (behind ISpeakerDashboardUIService at Services/Speakers/ISpeakerDashboardUIService.cs:10), whose session read pushes the speaker filter server-side onto the virtual SpeakerId key, caps the page at 100 rows, and appends a per-call cache-bust parameter so this one read is a guaranteed miss against the shared sessions output cache and a just-made assignment shows immediately, while public list reads keep their cache (Services/Speakers/SpeakerDashboardService.cs:14, :19, :35, :38). Its bookmark counts come back from one batched endpoint rather than one hop per session (Services/Speakers/SpeakerDashboardService.cs:54), and it derives from Common's AuthenticatedServiceBase so its calls carry the bearer token and run through the shared retry policy. [Rubric §12, Performance & Scalability].

    -

    Second, organizer feedback moderation (BR-53). OrganizerEventFeedback and OrganizerSessionFeedback let organizers review and delete answers through OrganizerEventFeedbackService and OrganizerSessionFeedbackService (Pages/Feedback/OrganizerEventFeedback.razor.cs:18, Pages/Feedback/OrganizerSessionFeedback.razor.cs:18, services at Services/Feedback/OrganizerFeedbackService.cs:15, :66, behind IOrganizerEventFeedbackUIService and IOrganizerSessionFeedbackUIService at Services/Feedback/IOrganizerFeedbackUIService.cs:11, :27). The read is the unscoped organizer view, capped at 500 answers (Services/Feedback/OrganizerFeedbackService.cs:26), and each delete passes the parent id explicitly on the query string to satisfy the controller's binding (Services/Feedback/OrganizerFeedbackService.cs:48). [Rubric §11, Security]: the scoping is server-side and the pages carry [Authorize(Roles = "Organizer")], not a client-side hide.

    -

    Third, QR self-service. SpeakerQr renders a full-screen code a speaker can hold up at the podium with no backend call at all: the speaker comes from the speaker_id claim and the payload is built locally, so the page renders identically on the prerender and interactive passes (Pages/Speakers/SpeakerQr.razor.cs:19, :49 to :55). The payload is always the absolute public URL from Common's IPublicLinkBuilder (Pages/Speakers/SpeakerQr.razor.cs:21, :55), never the WebView-internal origin, or a code scanned off the MAUI head would open for nobody else. The framework's QrCodeButton puts the same capability on five organizer and public print surfaces: public event detail (Pages/Public/Events/PublicEventDetail.razor:31), public session detail (Pages/Public/Sessions/PublicSessionDetail.razor:44), public speaker detail (Pages/Public/Speakers/PublicSpeakerDetail.razor.cs:22), room detail (Pages/Rooms/RoomDetail.razor.cs:16), and sponsor detail (Pages/Sponsors/SponsorDetail.razor.cs:19).

    +

    Second, organizer feedback moderation (BR-53). OrganizerEventFeedback and OrganizerSessionFeedback let organizers review and delete answers through OrganizerEventFeedbackService and OrganizerSessionFeedbackService (Pages/Feedback/OrganizerEventFeedback.razor.cs:18, Pages/Feedback/OrganizerSessionFeedback.razor.cs:18, services at Services/Feedback/OrganizerFeedbackService.cs:15, :70, behind IOrganizerEventFeedbackUIService and IOrganizerSessionFeedbackUIService at Services/Feedback/IOrganizerFeedbackUIService.cs:11, :27). The read is the unscoped organizer view, paged through PagedReadAll until the reported total, so the report never aggregates a truncated answer set (Services/Feedback/OrganizerFeedbackService.cs:25 to :27), and each delete passes the parent id explicitly on the query string to satisfy the controller's binding (Services/Feedback/OrganizerFeedbackService.cs:52, :107). [Rubric §11, Security]: the scoping is server-side and the pages carry [Authorize(Roles = "Organizer")], not a client-side hide.

    +

    Third, QR self-service. SpeakerQr renders a full-screen code a speaker can hold up at the podium with no backend call at all: the speaker comes from the speaker_id claim and the payload is built locally, so the page renders identically on the prerender and interactive passes (Pages/Speakers/SpeakerQr.razor.cs:19, :49 to :55). The payload is always the absolute public URL from Common's IPublicLinkBuilder (Pages/Speakers/SpeakerQr.razor.cs:21, :55), never the WebView-internal origin, or a code scanned off the MAUI head would open for nobody else. The framework's QrCodeButton puts the same capability on five organizer and public print surfaces: public event detail (Pages/Public/Events/PublicEventDetail.razor:31), public session detail (Pages/Public/Sessions/PublicSessionDetail.razor:44), public speaker detail (Pages/Public/Speakers/PublicSpeakerDetail.razor:47), room detail (Pages/Rooms/RoomDetail.razor:61), and sponsor detail (Pages/Sponsors/SponsorDetail.razor:74).

    Session materials, a resource deliberately not shaped as CRUD

    -

    Speakers publish the decks, handouts, and links that go with their own sessions, and anyone downloads them from the public session page (ADR-123, speaker session assets). This is the one area here that does not get an IEntityService<,>. ISessionAssetUIService declares exactly the five operations the API offers, GetBySessionAsync, AddLinkAsync, UploadFileAsync, UpdateAsync, and DeleteAsync (Services/SessionAssets/ISessionAssetUIService.cs:17, :27, :35, :49, :66), with the rationale recorded on the interface itself (:11 to :16): the endpoint has no paged list, no lookup, and no by-id read (an asset is only ever fetched as part of its session's list), and its two creates are different verbs on different routes. Declaring only what exists keeps a page from reaching for a CRUD method with no endpoint behind it. The two write bodies sit beside the service as SessionAssetLinkRequest and SessionAssetUpdateRequest (Services/SessionAssets/SessionAssetRequests.cs:14, :35) rather than being reused from the Application layer, because the UI RCL references the module's Shared project only (SessionAssetRequests.cs:7 to :13); neither carries the caller's identity, which the API stamps from the token, so a request body can never claim to be a speaker it is not. [Rubric §9, API & Contract Design].

    -

    SessionAssetService derives from Common's AuthenticatedServiceBase (Services/SessionAssets/SessionAssetService.cs:26) and is the clearest worked example in this library of per-verb transport policy. Adding a link appends a row, so that POST carries an Idempotency-Key and keeps the shared retry policy (:59, :68). The file POST carries the key but has no retry at all: a picked file's stream is single-shot (neither a browser file input nor a native picker rewinds), so a second attempt would post an exhausted body, while a repeated key replays the stored response instead of re-uploading 50 MB over venue wifi (:82 to :86, :103, :130). The upload streams straight from the browser file into the multipart body with the cap stated to OpenReadStream, because Blazor's 512 KB default would otherwise truncate every real deck (:107, note at :105 to :106), and the four part names match the controller's IFormFile parameter and its [FromForm] arguments exactly (:125 to :128). The update is conditional (the ADR-035 rule): the edited row version travels as If-Match through ConcurrencyETag, and a DTO carrying no token sends no header, so the API refuses with 428 rather than overwriting another editor's change (:151, header applied at :204 to :207). Both the key and the precondition are default headers on a client built once per logical operation, so every retry attempt states the same pair (:195, reasoning at :188 to :194). [Rubric §29, Resilience].

    -

    Three components consume that service. SessionAssetsPanel is the management half (Pages/SessionAssets/SessionAssetsPanel.razor.cs:31), a component rather than a page, so the organizer session detail page and a speaker's own dashboard host it by saying only which session and whether the viewer may manage it (parameters at :36, :42). CanManage decides only what the panel offers: the right itself is re-checked by the API on every write (a privileged role, or a speaker whose speaker_id claim is on the session), so a viewer who reaches a hidden control still gets SessionAsset.Forbidden (:20 to :22). [Rubric §11, Security]. The panel enforces the two limits client-side as courtesy rather than as the rule: the per-session cap closes the add controls and short-circuits both writes (:64, :105, :139, the constant SessionAssetLimits.MaxAssetsPerSession = 10 at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/SessionAssets/SessionAssetLimits.cs:27), and the size check runs before the post so a speaker does not wait out a whole 50 MB upload on venue wifi to be told no (:148, cap at SessionAssetLimits.cs:11). Every refusal is worded from the API's machine-readable error code through a resource-key table, so SessionAsset.UnsupportedFormat, SessionAsset.LimitReached, and SessionAsset.StorageNotConfigured each read in the selected language instead of echoing the server's English sentence (ADR-027); Http.413 is the one transport status in that table, because Kestrel refuses an over-cap body before the friendly check ever runs (:247 to :250, entry at :274). And every write funnels through one MutateAsync shape: run it, report the refusal or the success, run the caller's own reset, reload the list (:209, value-returning overload at :239).

    +

    Speakers publish the decks, handouts, and links that go with their own sessions, and anyone downloads them from the public session page (ADR-123, speaker session assets). This is the one area here that does not get an IEntityService<,>. ISessionAssetUIService declares exactly the five operations the API offers, GetBySessionAsync, AddLinkAsync, UploadFileAsync, UpdateAsync, and DeleteAsync (Services/SessionAssets/ISessionAssetUIService.cs:32, :41, :55, :72, :82), with the rationale recorded on the interface itself (:11 to :16): the endpoint has no paged list, no lookup, and no by-id read (an asset is only ever fetched as part of its session's list), and its two creates are different verbs on different routes. Declaring only what exists keeps a page from reaching for a CRUD method with no endpoint behind it. The two write bodies sit beside the service as SessionAssetLinkRequest and SessionAssetUpdateRequest (Services/SessionAssets/SessionAssetRequests.cs:14, :35) rather than being reused from the Application layer, because the UI RCL references the module's Shared project only (SessionAssetRequests.cs:7 to :13); neither carries the caller's identity, which the API stamps from the token, so a request body can never claim to be a speaker it is not. [Rubric §9, API & Contract Design].

    +

    SessionAssetService derives from Common's AuthenticatedServiceBase (Services/SessionAssets/SessionAssetService.cs:26) and is the clearest worked example in this library of per-verb transport policy. Adding a link appends a row, so that POST carries an Idempotency-Key and keeps the shared retry policy (:61, :74). The file POST carries the key but has no retry at all: a picked file's stream is single-shot (neither a browser file input nor a native picker rewinds), so a second attempt would post an exhausted body, while a repeated key replays the stored response instead of re-uploading 50 MB over venue wifi (:86 to :91, :108, :135). The upload streams straight from the browser file into the multipart body with the cap stated to OpenReadStream, because Blazor's 512 KB default would otherwise truncate every real deck (:112, note at :110 to :111), and the four part names match the controller's IFormFile parameter and its [FromForm] arguments exactly (:128 to :133). The update is conditional (the ADR-035 rule): the edited row version travels as If-Match through ConcurrencyETag, and a DTO carrying no token sends no header, so the API refuses with 428 rather than overwriting another editor's change (:154 to :156, header applied at :209 to :212). Both the key and the precondition are default headers on a client built once per logical operation, so every retry attempt states the same pair (:200, reasoning at :193 to :199). [Rubric §29, Resilience].

    +

    Three components consume that service. SessionAssetsPanel is the management half (Pages/SessionAssets/SessionAssetsPanel.razor.cs:31), a component rather than a page, so the organizer session detail page and a speaker's own dashboard host it by saying only which session and whether the viewer may manage it (parameters at :36, :42). CanManage decides only what the panel offers: the right itself is re-checked by the API on every write (a privileged role, or a speaker whose speaker_id claim is on the session), so a viewer who reaches a hidden control still gets SessionAsset.Forbidden (:20 to :22). [Rubric §11, Security]. The panel enforces the two limits client-side as courtesy rather than as the rule: the per-session cap closes the add controls and short-circuits both writes (:64, :105, :139, the constant SessionAssetLimits.MaxAssetsPerSession = 10 at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/SessionAssets/SessionAssetLimits.cs:27), and the size check runs before the post so a speaker does not wait out a whole 50 MB upload on venue wifi to be told no (:146, cap at SessionAssetLimits.cs:11). Every refusal is worded from the API's machine-readable error code through a resource-key table, so SessionAsset.UnsupportedFormat, SessionAsset.LimitReached, and SessionAsset.StorageNotConfigured each read in the selected language instead of echoing the server's English sentence (ADR-027); Http.413 is the one transport status in that table, because Kestrel refuses an over-cap body before the friendly check ever runs (:247 to :250, entry at :274). And every write funnels through one MutateAsync shape: run it, report the refusal or the success, run the caller's own reset, reload the list (:209, value-returning overload at :239).

    SessionAssetComposer is the "add a material" half, split out of that panel so the panel keeps the list, the writes, and the status region while the composer owns only the draft the speaker is typing (Pages/SessionAssets/SessionAssetComposer.razor.cs:24, rationale at :6 to :9). It performs no write of its own: a well-formed link is raised on OnAddLink with its address already normalized, a refused address is raised on OnInvalidLinkAddress so the panel words the refusal in its own single aria-live region rather than growing a second one, and a picked file is raised untouched on OnFileSelected before any size or format check (:33 to :40, remarks at :12 to :17). The draft is cleared by the panel through ClearDraft rather than optimistically here, because only the panel knows whether the write actually succeeded and an EventCallback cannot report an outcome back to its raiser (:52 to :57, remarks at :18 to :22, called at SessionAssetsPanel.razor.cs:123 with the reference held at :62). One small detail earns its comment: the file input's id is per-instance rather than fixed, because a page can render one panel per session and a duplicated id would point the label at the wrong picker (:42 to :44). [Rubric §18, UI Architecture & Component Design].

    SessionAssetsDownloadList is the public read-only half, embedded in the public session page (Pages/SessionAssets/SessionAssetsDownloadList.razor.cs:18, hosted at Pages/Public/Sessions/PublicSessionDetail.razor:114). It is anonymous because the list endpoint is, and a reader who cannot see the session gets an empty list rather than a refusal. A load failure here is deliberately silent: the materials are supplementary to the session page, so the section simply does not render rather than raising an alert for something the reader never asked for (:13 to :17, :43). Both components reload only when the session id actually changes (:31 to :38, panel at SessionAssetsPanel.razor.cs:68 to :73), and both swallow OperationCanceledException from disposal or the InteractiveAuto render-mode transition (SessionAssetsDownloadList.razor.cs:45 to :48). The presentation decisions they would otherwise duplicate live once in SessionAssetDisplay (Pages/SessionAssets/SessionAssetDisplay.cs:13): the icon that names an asset by kind and by format so a reader can tell a deck from an archive before clicking (:27), and a byte count split into an amount plus the resource key naming its unit, which the caller composes through its own localizer so a Spanish reader reads "1,4 MB" (:56). The extension is read off the stored blob name first, because a blob URL can carry a signature query string and taking the extension from that would yield the query rather than the format (SessionAssetDisplay.cs:79 to :86). [Rubric §18, UI Architecture & Component Design] and [Rubric §27, Internationalization & Localization].

    Session-selection decision support, the asynchronous edge

    The most behaviour-rich page is the organizer-only SessionSelectionDashboard (Pages/Sessions/Selection/SessionSelectionDashboard.razor.cs:16), which renders category distribution, speaker overlap, locality breakdown, and AI content-similarity scoring over an event's session pool via SessionSelectionService (Services/Sessions/Selection/SessionSelectionService.cs:14, behind ISessionSelectionUIService). Every load is stamped with a monotonic generation rather than an event id, so switching away from an event and back still discards the first response even though both carry the same id: the generation is snapshotted before the fetch, re-checked before the board is replaced or an error banner is painted, and re-checked again before the spinner is cleared. Filter options are recomputed from the returned SessionSelectionDashboardDTO itself. [Rubric §19, State Management & Data Flow].

    Scoring is the asynchronous edge. ScoreSessionsAsync POSTs to the scoring endpoint with no retry pipeline, because starting a run is not idempotent and a retried POST could queue a second run behind the first (Services/Sessions/Selection/SessionSelectionService.cs:46 to :48). Because AI scoring of every eligible session can take minutes, the API does not run the pass inline: SessionSelectionController schedules a ScoreEventSessionsInternalCommand through the durable IInternalCommandScheduler and answers 202 Accepted with no body once it is queued (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionSelectionController.cs:132 to :144, command at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/ScoreEventSessionsInternalCommand.cs:38), so the service reads the status before the body reader (which treats a body-less 2xx as a failure) and maps it to a sentinel ScoreEventSessionsResultDTO with SessionsScored = -1 (Services/Sessions/Selection/SessionSelectionService.cs:54 to :57). The page turns that sentinel, and only that sentinel, into a fire-and-forget polling session, leaving its scoring flag set for the poll loop to clear while every other branch clears it inline.

    -

    The loop itself is factored into three pieces, which is the part worth studying. ScorePollSession runs the polling (Pages/Sessions/Selection/ScorePollSession.cs:36, RunAsync at :51), re-reading the cadence through the host on every tick so a bUnit test can shrink it (:63) and abandoning the session the moment the page's generation moves (:64, :70). ScorePollHost is the record of callbacks through which that loop raises its UI side effects, so no rendering decision lives in the loop and none of the loop lives on the component (Pages/Sessions/Selection/ScorePollSession.cs:19). And ScorePollTracker is the pure state machine that turns each observation into a ScorePollSignal: keep polling, apply-and-continue, all sessions scored, counts stable long enough, or no scores at all within the zero-progress budget (Pages/Sessions/Selection/ScorePollTracker.cs:33, signal enum at :8). Its budgets are explicit constants, each with its sizing reason beside it: 225 polls for a 30-minute cap at the 8-second cadence (:36), 5 consecutive fetch failures (:43), 15 zero-progress polls, which must outlast the internal-command poll that picks the run up (60 seconds in production) plus the first scoring call (:51, reason at :45 to :50), and 6 stable polls before completion, long enough to outlast one 30-second scoring call and its save so a single slow call mid-pass does not read as the end of the run (:58, reason at :53 to :57). Progress counts only scores this run wrote: the session receives each already-scored session's ScoredOn as a baseline when the run starts and counts against it on every tick (Pages/Sessions/Selection/ScorePollSession.cs:48, :92). Because the service answers a server error with a failed Result rather than an exception, the consecutive-failure budget is fed from the result branch, or a persistently failing endpoint would poll silently to the cap without ever reporting (Pages/Sessions/Selection/ScorePollSession.cs:75 to :84). [Rubric §6, CQRS & Event-Driven] and [Rubric §29, Resilience].

    +

    The loop itself is factored into three pieces, which is the part worth studying. ScorePollSession runs the polling (Pages/Sessions/Selection/ScorePollSession.cs:36, RunAsync at :51), re-reading the cadence through the host on every tick so a bUnit test can shrink it (:63) and abandoning the session the moment the page's generation moves (:64, :70). ScorePollHost is the record of callbacks through which that loop raises its UI side effects, so no rendering decision lives in the loop and none of the loop lives on the component (Pages/Sessions/Selection/ScorePollSession.cs:19). And ScorePollTracker is the pure state machine that turns each observation into a ScorePollSignal: keep polling, apply-and-continue, all sessions scored, counts stable long enough, or no scores at all within the zero-progress budget (Pages/Sessions/Selection/ScorePollTracker.cs:33, signal enum at :8). Its budgets are explicit constants, each with its sizing reason beside it: 225 polls for a 30-minute cap at the 8-second cadence (:36), 5 consecutive fetch failures (:43), 15 zero-progress polls, which must outlast the internal-command poll that picks the run up (60 seconds in production) plus the first scoring call (:51, reason at :45 to :50), and 6 stable polls before completion, long enough to outlast one 30-second scoring call and its save so a single slow call mid-pass does not read as the end of the run (:58, reason at :53 to :57). Progress counts only scores this run wrote: the session receives each already-scored session's ScoredOn as a baseline when the run starts and counts against it on every tick (Pages/Sessions/Selection/ScorePollSession.cs:48, :92). Because the service answers a server error with a failed Result rather than an exception, the consecutive-failure budget is fed from the result branch, or a persistently failing endpoint would poll silently to the cap without ever reporting (Pages/Sessions/Selection/ScorePollSession.cs:80, success read at :86). [Rubric §6, CQRS & Event-Driven] and [Rubric §29, Resilience].

    Public versus authenticated rendering, and the offline-first path

    -

    A recurring [Rubric §11, Security] pattern: the same conference entity is exposed through two page families. No page under Pages/Public/ carries an @attribute [Authorize] at all, and those reads are output-cached at the API; the organizer family gates on the Organizer role in markup (Pages/Events/EventList.razor:2). PublicSessionList shows the nuance well. It is read-only for anonymous users (BR-43), but an authenticated user gets inline bookmark stars and a My Schedule toggle wired through the optional ISessionBookmarkUIService; because Blazor's [Inject] has no optional mode (an unregistered service throws at render), the page declares that dependency as a nullable property and resolves it via IServiceProvider.GetService, so it stays null when the Engagement module is disabled (Pages/Public/Sessions/PublicSessionList.razor.cs:40 to :41, :89). [Rubric §7, Microservices Readiness]. Non-privileged readers are always locked server-side to the computed current or next event via CurrentEventDefaults and the privileged-reader list ConferenceReadAudience, so a shared organizer URL cannot pin an attendee to a different or unpublished event (Pages/Public/Sessions/PublicSessionList.razor.cs:124, :170), and the same rule decides whether the event choice is persisted at all (Pages/Public/PublicSessionListFilterState.cs:12).

    +

    A recurring [Rubric §11, Security] pattern: the same conference entity is exposed through two page families. No page under Pages/Public/ carries an @attribute [Authorize] at all, and those reads are output-cached at the API; the organizer family gates on the Organizer role in markup (Pages/Events/EventList.razor:2). PublicSessionList shows the nuance well. It is read-only for anonymous users (BR-43), but an authenticated user gets inline bookmark stars and a My Schedule toggle wired through the optional ISessionBookmarkUIService; because Blazor's [Inject] has no optional mode (an unregistered service throws at render), the page declares that dependency as a nullable property and resolves it via IServiceProvider.GetService, so it stays null when the Engagement module is disabled (Pages/Public/Sessions/PublicSessionList.razor.cs:40 to :41, :91). [Rubric §7, Microservices Readiness]. Who counts as privileged is one shared call: PublicReadAudience awaits the cascaded authentication state and tests it against the privileged-reader list ConferenceReadAudience, degrading a faulted state to the public (least-privileged) audience while letting a cancellation propagate, so an unreadable auth state never breaks a public page (Pages/Public/PublicReadAudience.cs:10, IsPrivilegedReaderAsync at :18, role test at :28, fault filter at :31), and the public session, speaker, and event pages all ask it rather than repeating the check (Pages/Public/Sessions/PublicSessionList.razor.cs:121, Pages/Public/Speakers/PublicSpeakerList.razor.cs:97, Pages/Public/Events/PublicEventList.razor.cs:72, Pages/Public/Events/PublicEventDetail.razor.cs:62). Non-privileged readers are always locked to the computed current or next event via CurrentEventDefaults, so a shared organizer URL cannot pin an attendee to a different or unpublished event (Pages/Public/Sessions/PublicSessionList.razor.cs:171 to :184); when the event lookup failed and no scope resolved, a public reader's session query fails closed instead of running unscoped (:149, :309 to :312), and the same rule decides whether the event choice is persisted at all (Pages/Public/PublicSessionListFilterState.cs:12).

    The single write on the public session detail page is extracted into its own component rather than growing the page: SessionBookmarkButton is the add-to / remove-from My Schedule toggle for one session, given only a session id and resolving everything else itself, so the page holds no bookmark state at all (Pages/Public/Sessions/SessionBookmarkButton.razor.cs:27, parameter at :38, hosted at Pages/Public/Sessions/PublicSessionDetail.razor:150). It repeats the optional-dependency move verbatim, resolving the Engagement-owned service through IServiceProvider.GetService and no-opping on every action when it is absent, so a deployment without Engagement renders exactly the same inert button it always did (:29, :33, :51, reasoning at :18 to :25). It also skips its read during SSR prerender for the same reason the hosting pages skip theirs: under InteractiveAuto the interactive instance re-runs OnParametersSetAsync, so every visit would fetch the reader's bookmarks twice, and the prerender pass renders the unset star (:54 to :60). [Rubric §23, Front-End Performance & Rendering].

    -

    The public event page carries the one piece of third-party embedding in this library, and it is classified rather than trusted. VenueMapLinks decides whether a stored venue map URL is a Google Maps embed link, which Google serves only inside an iframe and which answers "The Google Maps Embed API must be used in an iframe" when opened as a top-level page (Pages/Public/Events/VenueMapLinks.cs:10, doc note at :3 to :9). The test is deliberately narrow: https only, one of three Google hosts, and either a /maps/embed path or a /maps path carrying output=embed (:16, :23 to :41); anything else keeps opening as an ordinary external link (Pages/Public/Events/PublicEventDetail.razor:73, :93, external link at :104). The host allow-list is not cosmetic: the page CSP's frame-src has to permit exactly those origins, a coupling recorded next to the array (VenueMapLinks.cs:14 to :15). The companion BuildSearchUrl escapes the venue street address into a regular (non-embed) Maps search link, and answers null when there is no address to search for, so the affordance is absent rather than dead (:48 to :51, used at PublicEventDetail.razor:86). PublicEventDetail is Pages/Public/Events/PublicEventDetail.razor.cs:19. [Rubric §11, Security].

    -

    The offline-first behaviour that used to sit in the page now lives behind an interface. IPublicSessionScheduleService / PublicSessionScheduleService run the live paged query and compose it with the framework's OfflineFirstPageSnapshot<TItem>, keeping the last successful first page in the device-local cache so a dead venue network still shows a programme (ADR-042 Wave 3, Services/Public/IPublicSessionScheduleService.cs:34, Services/Public/PublicSessionScheduleService.cs:19). The live path is never altered: the snapshot is consulted only when a fetch fails, a failure with nothing cached travels on to the grid's own handling unchanged, and the page owns only the "showing cached data" banner it raises through a callback (Pages/Public/Sessions/PublicSessionList.razor.cs:292, :299). One page request is one SessionSchedulePageRequest (Services/Public/IPublicSessionScheduleService.cs:20), and My Schedule rides it as a set of bookmarked ids that scope the query server-side with an Id IN (...) filter rather than over-fetching and filtering in memory; an empty schedule short-circuits before the service is reached, so it can never overwrite the cached programme with nothing (Pages/Public/Sessions/PublicSessionList.razor.cs:272).

    -

    The rest of the device-capability layer (G26) shows up as injected interfaces that no-op on the web heads: the star toggle fires IHapticFeedbackService (Pages/Public/Sessions/PublicSessionListView.razor.cs:29, and again on the extracted toggle at Pages/Public/Sessions/SessionBookmarkButton.razor.cs:31), the filter bar shares a schedule screenshot through IScreenshotService and IShareService (Pages/Public/Sessions/PublicSessionListFilterBar.razor.cs:16), the public activity page opens directions through IMapNavigationService (Pages/Public/Activities/PublicActivityList.razor.cs:30), and /conference/sessions?mine=true is a deep link the MAUI head's home-screen quick action targets, which beats the saved page state when present (Pages/Public/Sessions/PublicSessionList.razor.cs:68). [Rubric §29, Resilience] and [Rubric §22, Responsive & Cross-Browser/Device].

    +

    The public event page carries the one piece of third-party embedding in this library, and it is classified rather than trusted. VenueMapLinks decides whether a stored venue map URL is a Google Maps embed link, which Google serves only inside an iframe and which answers "The Google Maps Embed API must be used in an iframe" when opened as a top-level page (Pages/Public/Events/VenueMapLinks.cs:10, doc note at :3 to :9). The test is deliberately narrow: https only, one of three Google hosts, and either a /maps/embed path or a /maps path carrying output=embed (:16, :23 to :41); anything else keeps opening as an ordinary external link (Pages/Public/Events/PublicEventDetail.razor:73, :93, external link at :104). The host allow-list is not cosmetic: the page CSP's frame-src has to permit exactly those origins, a coupling recorded next to the array (VenueMapLinks.cs:14 to :15). The companion BuildSearchUrl escapes the venue street address into a regular (non-embed) Maps search link, and answers null when there is no address to search for, so the affordance is absent rather than dead (:48 to :51, used at PublicEventDetail.razor:86). PublicEventDetail is Pages/Public/Events/PublicEventDetail.razor.cs:18. [Rubric §11, Security].

    +

    The offline-first behaviour that used to sit in the page now lives behind an interface. IPublicSessionScheduleService / PublicSessionScheduleService run the live paged query and compose it with the framework's OfflineFirstPageSnapshot<TItem>, keeping the last successful first page in the device-local cache so a dead venue network still shows a programme (ADR-042 Wave 3, Services/Public/IPublicSessionScheduleService.cs:34, Services/Public/PublicSessionScheduleService.cs:19). The live path is never altered: the snapshot is consulted only when a fetch fails, a failure with nothing cached travels on to the grid's own handling unchanged, and the page owns only the "showing cached data" banner it raises through a callback (Pages/Public/Sessions/PublicSessionList.razor.cs:336, :343). One page request is one SessionSchedulePageRequest (Services/Public/IPublicSessionScheduleService.cs:20), and My Schedule rides it as a set of bookmarked ids that scope the query server-side with an Id IN (...) filter rather than over-fetching and filtering in memory; an empty schedule short-circuits before the service is reached, so it can never overwrite the cached programme with nothing (Pages/Public/Sessions/PublicSessionList.razor.cs:316).

    +

    The rest of the device-capability layer (G26) shows up as injected interfaces that no-op on the web heads: the star toggle fires IHapticFeedbackService (Pages/Public/Sessions/PublicSessionListView.razor.cs:29, and again on the extracted toggle at Pages/Public/Sessions/SessionBookmarkButton.razor.cs:31), the filter bar shares a schedule screenshot through IScreenshotService and IShareService (Pages/Public/Sessions/PublicSessionListFilterBar.razor.cs:16), the public activity page opens directions through IMapNavigationService (Pages/Public/Activities/PublicActivityList.razor.cs:30), and /conference/sessions?mine=true is a deep link the MAUI head's home-screen quick action targets, which beats the saved page state when present (Pages/Public/Sessions/PublicSessionList.razor.cs:70). [Rubric §29, Resilience] and [Rubric §22, Responsive & Cross-Browser/Device].

    Sponsors, partners, and activities, three feature areas in miniature

    -

    The sponsor surface is a compact tour of every pattern above. Organizers manage the roster through SponsorList / SponsorCreate / SponsorDetail: the list is an EventFilteredListPageBase<SponsorDTO> that inherits the default-event resolution and persistence wholesale (Pages/Sponsors/SponsorList.razor.cs:19), and the detail page edits every field except the owning event, on the stated rationale that moving a sponsorship between events is a create plus a delete (Pages/Sponsors/SponsorDetail.razor.cs:19). Attendees see the same data twice. PublicSponsorList resolves the featured event, filters the roster to it, and groups by SponsorTier ascending (package order) then by Sort and name, so the render order is deterministic rather than insertion-dependent (Pages/Public/Sponsors/PublicSponsorList.razor.cs:88 to :92); when the event publishes no packet URL the sponsorship call to action is hidden entirely rather than offering a dead link (Pages/Public/Sponsors/PublicSponsorList.razor.cs:63). ADCHome renders the same roster as a logo strip under the same tier-then-sort-then-name rule, reading it through the typed ISponsorUIService with a server-side filter to the featured event, so a second published edition's sponsors cannot bleed onto the landing page (Pages/Home/ADCHome.razor.cs:228, paged read at :237 to :243, filter built at :311, grouping at :251 to :256), and any failure leaves the strip empty and the call to action standing. The read passes an explicit 200-row roster cap because the paged endpoints default to 10 rows (Pages/Home/ADCHome.razor.cs:42, rationale at :38 to :41).

    -

    Partners are the sponsor shape run a second time, and they are the cheapest available proof that the composition actually is open for extension. The organizer trio is PartnerList / PartnerCreate / PartnerDetail (Pages/Partners/PartnerList.razor.cs:19, Pages/Partners/PartnerCreate.razor.cs:19, Pages/Partners/PartnerDetail.razor.cs:19) over PartnerService behind IPartnerUIService, an empty extension of the generic CRUD contract (Services/Partners/PartnerService.cs:10, Services/Partners/IPartnerUIService.cs:9). Where sponsors carry a commercial tier, partners carry a PartnerType, and the two pages differ from their sponsor counterparts only in that one field and its localized label (Pages/Partners/PartnerList.razor.cs:37, Pages/Partners/PartnerDetail.razor.cs:35). The create page makes the owning event a required field, precisely because the detail page refuses to move a partner afterwards, and pre-selects the current or next event through CurrentEventSelector so the common case is one click (Pages/Partners/PartnerCreate.razor.cs:14 to :18, default at :58 to :67, Pages/Partners/PartnerDetail.razor.cs:14 to :17). On the attendee side, ADCHome renders an announced-partners band grouped by type with Community first and each group ordered by Sort then name, read through the typed IPartnerUIService with the same server-side event filter, and any failure hides the whole band (Pages/Home/ADCHome.razor.cs:86, loaded at :143, fetched at :283 to :289, grouped at :296 to :301, remarks at :269 to :272). The whole area cost one nav entry, one route triple, and no edit at all to the module's service registration, for the reason the last section gives.

    -

    The activities area (the social programme: pre-conference party, coffee connect, after-party, closing ceremony) repeats the shape with two twists. ActivityCreate, ActivityDetail, and ActivityList are the organizer trio, with the create page defaulting the owning event to the current or next one through CurrentEventSelector (Pages/Activities/ActivityCreate.razor.cs:20, Pages/Activities/ActivityDetail.razor.cs:20, Pages/Activities/ActivityList.razor.cs:20). Because EventId is a real Activity column, the list's event filter needs no virtual-key resolution and goes straight through the base class's ApplyEventFilter, unlike the speaker list, whose event filter travels as a virtual key resolved server-side through the join tables (Pages/Public/Speakers/PublicSpeakerList.razor.cs:19 to :20). PublicActivityList renders the same programme chronologically for attendees, ordering by start time first so ties are deterministic (Pages/Public/Activities/PublicActivityList.razor.cs:88), and offers the directions affordance for an activity that carries its own off-site venue (:30).

    +

    The sponsor surface is a compact tour of every pattern above. Organizers manage the roster through SponsorList / SponsorCreate / SponsorDetail: the list is an EventFilteredListPageBase<SponsorDTO> that inherits the default-event resolution and persistence wholesale (Pages/Sponsors/SponsorList.razor.cs:19), and the detail page edits every field except the owning event, on the stated rationale that moving a sponsorship between events is a create plus a delete (Pages/Sponsors/SponsorDetail.razor.cs:19). Attendees see the same data twice. PublicSponsorList resolves the featured event, filters the roster to it, and groups by SponsorTier ascending (package order) then by Sort and name, so the render order is deterministic rather than insertion-dependent (Pages/Public/Sponsors/PublicSponsorList.razor.cs:103 to :107); when the event publishes no packet URL the sponsorship call to action is hidden entirely rather than offering a dead link (Pages/Public/Sponsors/PublicSponsorList.razor.cs:79). ADCHome renders the same roster as a logo strip under the same tier-then-sort-then-name rule, reading it through the typed ISponsorUIService with a server-side filter to the featured event, so a second published edition's sponsors cannot bleed onto the landing page (Pages/Home/ADCHome.razor.cs:231, paged read at :240 to :246, filter built at :314 to :318, grouping at :255 to :259), and any failure leaves the strip empty and the call to action standing. The read passes an explicit 200-row roster cap because the paged endpoints default to 10 rows (Pages/Home/ADCHome.razor.cs:42, rationale at :38 to :41).

    +

    Partners are the sponsor shape run a second time, and they are the cheapest available proof that the composition actually is open for extension. The organizer trio is PartnerList / PartnerCreate / PartnerDetail (Pages/Partners/PartnerList.razor.cs:19, Pages/Partners/PartnerCreate.razor.cs:19, Pages/Partners/PartnerDetail.razor.cs:19) over PartnerService behind IPartnerUIService, an empty extension of the generic CRUD contract (Services/Partners/PartnerService.cs:10, Services/Partners/IPartnerUIService.cs:9). Where sponsors carry a commercial tier, partners carry a PartnerType, and the two pages differ from their sponsor counterparts only in that one field and its localized label (Pages/Partners/PartnerList.razor.cs:37, Pages/Partners/PartnerDetail.razor.cs:35). The create page makes the owning event a required field, precisely because the detail page refuses to move a partner afterwards, and pre-selects the current or next event through CurrentEventSelector so the common case is one click (Pages/Partners/PartnerCreate.razor.cs:14 to :18, default at :63 to :68, Pages/Partners/PartnerDetail.razor.cs:14 to :17). On the attendee side, ADCHome renders an announced-partners band grouped by type with Community first and each group ordered by Sort then name, read through the typed IPartnerUIService with the same server-side event filter, and any failure hides the whole band (Pages/Home/ADCHome.razor.cs:89, loaded at :146, fetched at :286 to :292, grouped at :300 to :304, remarks at :272 to :276). The whole area cost one nav entry, one route triple, and no edit at all to the module's service registration, for the reason the last section gives.

    +

    The activities area (the social programme: pre-conference party, coffee connect, after-party, closing ceremony) repeats the shape with two twists. ActivityCreate, ActivityDetail, and ActivityList are the organizer trio, with the create page defaulting the owning event to the current or next one through CurrentEventSelector (Pages/Activities/ActivityCreate.razor.cs:20, Pages/Activities/ActivityDetail.razor.cs:20, Pages/Activities/ActivityList.razor.cs:20). Because EventId is a real Activity column, the list's event filter needs no virtual-key resolution and goes straight through the base class's ApplyEventFilter, unlike the speaker list, whose event filter travels as a virtual key resolved server-side through the join tables (Pages/Public/Speakers/PublicSpeakerList.razor.cs:18 to :19). PublicActivityList renders the same programme chronologically for attendees, ordering by start time, then sort order, then name, so ties are deterministic (Pages/Public/Activities/PublicActivityList.razor.cs:103 to :105), and offers the directions affordance for an activity that carries its own off-site venue (:30).

    The landing page

    -

    ADCHome is the conference front door, shared by the web and MAUI heads; both serve the editorial images from their own site root today, so neither overrides the ImageBasePath parameter (Pages/Home/ADCHome.razor.cs:19 to :20, :66). It reads the events list through the typed IEventUIService, the same contract the organizer pages use, and features the live-or-next published event via CurrentEventSelector, while a failed read leaves the fallback defaults in place (Pages/Home/ADCHome.razor.cs:52, read at :195, selection at :199 to :204). With the sponsor and partner reads going through their typed services as well, the page declares no wire shapes of its own: its three dependencies are the injected UI services (Pages/Home/ADCHome.razor.cs:51 to :58). Three rendering decisions are worth internalizing. First, during SSR prerender it skips the backend fetches and the timer entirely and renders the static fallback, because an untimed server-side call to a cold backend would block the prerender and therefore the post-login navigation (Pages/Home/ADCHome.razor.cs:134, loads at :141 to :143). Second, the per-second countdown ticking lives in a child component behind a render fence, so this page arms only a single one-shot timer for the Live-to-Ended flip (Pages/Home/ADCHome.razor.cs:145 to :147, :162), classifying the moment into the EventPhase enum Upcoming/Live/Ended from the event's own time zone and going through CurrentEventSelector.ToUtc so the spring-forward gap at a midnight boundary cannot throw out of the render path (Pages/Home/ADCHome.razor.cs:317 to :338, enum at :90). Third, the fallback date is a named constant with an explicit warning that it must track the published event date, since a stale value makes the hero date and the countdown visibly jump once the real event loads (Pages/Home/ADCHome.razor.cs:49, warning at :46 to :48). [Rubric §23, Front-End Performance & Rendering]. The editorial content it renders (the keynote, the eight-track catalog, and the two pre-conference workshops) is held as static records in ADCHomeContent: KeynoteSpeakerInfo, ConferenceTrackInfo, and PreConferenceWorkshopInfo (Pages/Home/ADCHomeContent.cs:86, :95, :103, data at :18, :35, and :64); the workshop record carries only proper nouns plus a resource-key stem, so its audience and description lines stay localized (Pages/Home/ADCHomeContent.cs:99 to :103). The separate ticketing page for that workshop day is a fixed product fact rather than an event field, and says so where it sits (Pages/Home/ADCHome.razor.cs:34, rationale at :24 to :30).

    +

    ADCHome is the conference front door, shared by the web and MAUI heads; both keep their own copy of the editorial assets under wwwroot/images, so the default serves both and neither overrides the ImageBasePath parameter (Pages/Home/ADCHome.razor.cs:19 to :20, :64 to :66, parameter at :69). It reads the events list through the typed IEventUIService, the same contract the organizer pages use, and features the live-or-next published event via CurrentEventSelector, while a failed read leaves the fallback defaults in place (Pages/Home/ADCHome.razor.cs:52, read at :198, selection at :202 to :207). With the sponsor and partner reads going through their typed services as well, the page declares no wire shapes of its own: its dependencies are the three injected UI services plus the TimeProvider every clock read goes through (Pages/Home/ADCHome.razor.cs:51 to :61). Three rendering decisions are worth internalizing. First, during SSR prerender it skips the backend fetches and the timer entirely and renders the static fallback, because an untimed server-side call to a cold backend would block the prerender and therefore the post-login navigation (Pages/Home/ADCHome.razor.cs:137, loads at :144 to :146). Second, the per-second countdown ticking lives in a child component behind a render fence, so this page arms only a single one-shot timer for the Live-to-Ended flip (Pages/Home/ADCHome.razor.cs:148 to :150, :165), classifying the moment into the EventPhase enum Upcoming/Live/Ended from the event's own time zone and going through CurrentEventSelector.ToUtc so the spring-forward gap at a midnight boundary cannot throw out of the render path (Pages/Home/ADCHome.razor.cs:320 to :341, enum at :93). Third, the fallback date is a named constant with an explicit warning that it must track the published event date, since a stale value makes the hero date and the countdown visibly jump once the real event loads (Pages/Home/ADCHome.razor.cs:49, warning at :46 to :48). [Rubric §23, Front-End Performance & Rendering]. The editorial content it renders (the keynote, the eight-track catalog, and the two pre-conference workshops) is held as static records in ADCHomeContent: KeynoteSpeakerInfo, ConferenceTrackInfo, and PreConferenceWorkshopInfo (Pages/Home/ADCHomeContent.cs:86, :95, :103, data at :18, :35, and :64); the workshop record carries only proper nouns plus a resource-key stem, so its audience and description lines stay localized (Pages/Home/ADCHomeContent.cs:99 to :103). The separate ticketing page for that workshop day is a fixed product fact rather than an event field, and says so where it sits (Pages/Home/ADCHome.razor.cs:34, rationale at :25 to :30).

    Routes, navigation, and localized strings

    -

    All paths are centralized in ConferenceRoutePaths, a static catalogue of literal routes and id-parameterized builder methods (EventDetails(id), PublicSessionDetails(id), SponsorDetails(id), PartnerDetails(id), ActivityDetails(id), EventFeedbackOrganizer(id), and so on) typed against the module's identifier aliases and formatted culture-invariantly, so a route change happens in one file (ConferenceRoutePaths.cs:8 to :73; the partner triple at :38 to :40 is what a new area adds here). Two entries in that file are deliberate duplicates of routes owned by Engagement.UI, SponsorVisitLink and RoomCheckInLink (ConferenceRoutePaths.cs:64, :65), because Conference.UI must not reference Engagement.UI yet the organizer print surfaces need those links to encode into a QR; the reason is recorded inline at ConferenceRoutePaths.cs:60 to :63. [Rubric §25, Navigation, Routing & Information Architecture]. User-facing strings are not inline English: every page injects an IStringLocalizer<TPage> in its markup (Pages/Events/EventList.razor:5) and resolves labels and snackbar messages through L["..."] over co-located .resx resource pairs, including enum labels composed as keys (Pages/Partners/PartnerList.razor.cs:37). Where a string is deliberately left untranslated (the conference brand name, a postal address, a ticketing URL, the English-only editorial content) the code carries an explicit // i18n: allow marker with a reason (Pages/Home/ADCHome.razor.cs:35, :97, :101, Pages/Home/ADCHomeContent.cs:10 to :13). [Rubric §27, Internationalization & Localization] assesses externalized strings and culture-aware formatting; this area embodies it under ADR-027, which superseded the single-locale ADR-011 (primer §6).

    +

    All paths are centralized in ConferenceRoutePaths, a static catalogue of literal routes and id-parameterized builder methods (EventDetails(id), PublicSessionDetails(id), SponsorDetails(id), PartnerDetails(id), ActivityDetails(id), EventFeedbackOrganizer(id), and so on) typed against the module's identifier aliases and formatted culture-invariantly, so a route change happens in one file (ConferenceRoutePaths.cs:8 to :73; the partner triple at :38 to :40 is what a new area adds here). Two entries in that file are deliberate duplicates of routes owned by Engagement.UI, SponsorVisitLink and RoomCheckInLink (ConferenceRoutePaths.cs:64, :65), because Conference.UI must not reference Engagement.UI yet the organizer print surfaces need those links to encode into a QR; the reason is recorded inline at ConferenceRoutePaths.cs:60 to :63. [Rubric §25, Navigation, Routing & Information Architecture]. User-facing strings are not inline English: every page injects an IStringLocalizer<TPage> in its markup (Pages/Events/EventList.razor:6) and resolves labels and snackbar messages through L["..."] over co-located .resx resource pairs, including enum labels composed as keys (Pages/Partners/PartnerList.razor.cs:37). Where a string is deliberately left untranslated (the conference brand name, a postal address, a ticketing URL, the English-only editorial content) the code carries an explicit // i18n: allow marker with a reason (Pages/Home/ADCHome.razor.cs:35, :100, :104, Pages/Home/ADCHomeContent.cs:10 to :13). [Rubric §27, Internationalization & Localization] assesses externalized strings and culture-aware formatting; this area embodies it under ADR-027, which superseded the single-locale ADR-011 (primer §6).

    How it all plugs into the shell

    Two registration types wire the area in. ConferenceUIModule implements Common's IUIModule (the front-end counterpart of the IModule back-end contract): it declares the module's seventeen NavItem entries, whose labels are resource keys (Nav.Events, Nav.Dashboard, and so on) resolved by the shared NavMenu at render time against the co-located ConferenceUIModule.resx pair (ConferenceUIModule.cs:14, :16 to :18). Those seventeen split three ways: five public entries for everyone, Events, Sessions, Speakers, Sponsors, and Activities (ConferenceUIModule.cs:21 to :25), two speaker_id-claim-gated entries in the user section, the dashboard and the speaker's own QR (ConferenceUIModule.cs:28, :29), and an Organizer-role-gated admin group of ten, Events, Sessions, Speakers, Categories, Questions, Rooms, Sponsors, Partners, Activities, and Session Selection (ConferenceUIModule.cs:32 to :41); it then exposes its assembly so the host can discover the Razor routes (ConferenceUIModule.cs:44).

    -

    The companion DependencyInjection extension AddConferenceUI() (a C# extension(IServiceCollection) member, primer §4) is the one call a host makes (MMCA.ADC.Conference.UI/DependencyInjection.cs:18, extension block at :20, method at :26). It delegates the prologue to Common's AddUIModule<ConferenceUIModule>(), which scans the module assembly for every IEntityService<,> implementation as scoped and registers the descriptor as a singleton IUIModule (MMCA.ADC.Conference.UI/DependencyInjection.cs:30), then explicitly registers what a scan cannot infer: the four child-entity services (:33 to :36), the speaker dashboard (:39), the two organizer feedback services (:42, :43), session selection (:46), the session-materials service, which is outside the scan for exactly the reason its interface records (:51, note at :48 to :50), the offline-first schedule service (:54), the three lookup services (:57 to :59), and the composite speaker-detail lookup (:63). Public share links are not registered here: IPublicLinkBuilder comes from the framework's own AddUIShared, and the MAUI head overrides that registration afterwards so shared links always point at the web app, a note left in place where the registration used to be (MMCA.ADC.Conference.UI/DependencyInjection.cs:65 to :68). Because the scan covers the entity services, the partner area arrived with no edit to this file at all, and because the module contributes its own nav and assembly, the shell folds it in with no edit to the shell either. [Rubric §1, SOLID] (Open/Closed) and [Rubric §18, UI Architecture]. Read the per-type sections that follow for the mechanics of each page, model, and service; the bUnit and Playwright tests that exercise this library live in the testing chapter (G28).

    +

    The companion DependencyInjection extension AddConferenceUI() (a C# extension(IServiceCollection) member, primer §4) is the one call a host makes (MMCA.ADC.Conference.UI/DependencyInjection.cs:19, extension block at :21, method at :27). It delegates the prologue to Common's AddUIModule<ConferenceUIModule>(), which scans the module assembly for every IEntityService<,> implementation as scoped and registers the descriptor as a singleton IUIModule (MMCA.ADC.Conference.UI/DependencyInjection.cs:31), then explicitly registers what a scan cannot infer: the four child-entity services (:34 to :37), the speaker dashboard (:40), the two organizer feedback services (:43, :44), session selection (:47), the session-materials service, which is outside the scan for exactly the reason its interface records (:52, note at :49 to :51), the offline-first schedule service (:55), the three lookup services (:58 to :60), and the composite speaker-detail lookup (:64). Last, it TryAdds TimeProvider.System as a singleton, because the pages' current-event selection, the home countdown, and the event lookup cache all read the clock through TimeProvider, yet no UI head registers one and the Engagement UI's own TryAdd is skipped when that module is disabled (:70, reasoning at :66 to :69). Public share links are not registered here: IPublicLinkBuilder comes from the framework's own AddUIShared, and the MAUI head overrides that registration afterwards so shared links always point at the web app, a note left in place where the registration used to be (MMCA.ADC.Conference.UI/DependencyInjection.cs:72 to :75). Because the scan covers the entity services, the partner area arrived with no edit to this file at all, and because the module contributes its own nav and assembly, the shell folds it in with no edit to the shell either. [Rubric §1, SOLID] (Open/Closed) and [Rubric §18, UI Architecture]. Read the per-type sections that follow for the mechanics of each page, model, and service; the bUnit and Playwright tests that exercise this library live in the testing chapter (G28).

    ConferenceRoutePaths

    MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI/ConferenceRoutePaths.cs:8 · Level 0 · class (static)

    @@ -259,7 +259,7 @@

    ConferenceUIModule

  • Why it's built this way: mirroring the backend IModule pattern on the UI side keeps the app extensible. A host that boots without the Conference module simply has no conference nav and no conference routes, with no conditional code anywhere in the shell. The class also leaves AppBarComponentTypes and LayoutComponentTypes at their interface defaults (MMCA.Common.UI/Common/Interfaces/IUIModule.cs:19-22): Conference contributes no app-bar badge and no root overlay.
  • -
  • Where it's used: registered as a singleton IUIModule by this module's DependencyInjection through AddUIModule<ConferenceUIModule>() (DependencyInjection.cs:23, implemented at MMCA.Common.UI/DependencyInjection.cs:273-282) and aggregated by the shared UI navigation builder in group 15.
  • +
  • Where it's used: registered as a singleton IUIModule by this module's DependencyInjection through AddUIModule<ConferenceUIModule>() (DependencyInjection.cs:23, implemented at MMCA.Common.UI/DependencyInjection.cs:289-298) and aggregated by the shared UI navigation builder in group 15.
  • EventFormModel

    @@ -584,7 +584,7 @@

    EventDetail

    ConferenceRoutePaths, ErrorMessages (line 90), ResultUiExtensions.NotifyOnFailure - (lines 99, 159, 166, 286), + (lines 99, 159, 166, 296), DomainHelper's Parse<T> extension (line 4), and the DeleteConfirmation, UnsavedChangesGuard, EventFormFields, and ErrorSummary components (.../Pages/Event/EventDetail.razor:11, 37, 51, 197). @@ -632,12 +632,19 @@

    EventDetail

    operation with the current RowVersion (lines 205-207), refetches, and reports the reload miss with the same message as the toggle itself, since the toggle is what the user asked for (doc comment, lines 189-193). -
  • RefreshFromSessionizeAsync (lines 234-267) hands the whole gesture to one service call, +
  • RefreshFromSessionizeAsync (lines 234-277) hands the whole gesture to one service call, RefreshFromSessionizeWithCodeAsync(Event, _sessionizeCode, PageToken) (line 247). The service owns persist-then-import-then-reload and returns a single outcome carrying both the summary and the - refreshed event (lines 254-256), so the page keeps one failure message for the whole gesture.
  • -
  • DeleteEventAsync (lines 269-297): confirm through _deleteConfirm.ShowAsync(Event.Name) - (line 276), delete, then navigate back to the list.
  • + refreshed event (lines 264-266), so the page keeps one failure message for the whole gesture. + The failure branch does one more thing after the toast (line 250): because the code persist and + the refresh stamp can both commit before the import fails, moving the row version, it refetches the + stored event and adopts it when the read succeeds (lines 256-259). The comment (lines 252-255) names + the payoff: the next refresh, save, or publish carries the current RowVersion instead of hitting a + 409 until a reload, while the typed _sessionizeCode is left as entered so a retry re-persists it. + This is the same client half of ADR-035 + described above, applied to a partially committed gesture. +
  • DeleteEventAsync (lines 279-307): confirm through _deleteConfirm.ShowAsync(Event.Name) + (line 286), delete, then navigate back to the list.
  • Why it's built this way: an event is the root aggregate of the whole conference, so publishing, @@ -654,31 +661,32 @@

    EventDetail

    DependencyInjection

    -

    MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI/DependencyInjection.cs:18 · Level 6 · class (static)

    +

    MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI/DependencyInjection.cs:19 · Level 6 · class (static)

      -
    • What it is: the Conference UI composition root. Its single AddConferenceUI() method is the one call a host makes to register every Conference UI service (the per-entity CRUD services by assembly scan, then the child-entity, dashboard, feedback, selection, session-asset, offline-schedule, and lookup services explicitly) plus the module descriptor.

      +
    • What it is: the Conference UI composition root. Its single AddConferenceUI() method is the one call a host makes to register every Conference UI service (the per-entity CRUD services by assembly scan, then the child-entity, dashboard, feedback, selection, session-asset, offline-schedule, and lookup services explicitly) plus the module descriptor, and a fallback TimeProvider for the clock-reading pages.

    • -
    • Depends on: ConferenceUIModule and, through AddUIModule<T> (MMCA.Common.UI/DependencyInjection.cs:273-282), Scrutor's assembly-scanning API and the open generic IEntityService<TEntityDTO, TIdentifierType>. Then this module's own service contracts: IEventSpeakerUIService, ISessionSpeakerUIService, ISessionCategoryItemUIService, ISpeakerCategoryItemUIService, ISpeakerDashboardUIService, IOrganizerEventFeedbackUIService, IOrganizerSessionFeedbackUIService, ISessionSelectionUIService, ISessionAssetUIService, IPublicSessionScheduleService, ISpeakerLookupService, IEventLookupService, ICategoryItemLookupService, and ISpeakerDetailLookupService.

      +
    • Depends on: ConferenceUIModule and, through AddUIModule<T> (MMCA.Common.UI/DependencyInjection.cs:289-298), Scrutor's assembly-scanning API and the open generic IEntityService<TEntityDTO, TIdentifierType>. Then this module's own service contracts: IEventSpeakerUIService, ISessionSpeakerUIService, ISessionCategoryItemUIService, ISpeakerCategoryItemUIService, ISpeakerDashboardUIService, IOrganizerEventFeedbackUIService, IOrganizerSessionFeedbackUIService, ISessionSelectionUIService, ISessionAssetUIService, IPublicSessionScheduleService, ISpeakerLookupService, IEventLookupService, ICategoryItemLookupService, and ISpeakerDetailLookupService.

    • -
    • Concept introduced: the extension(IServiceCollection) registration block, half convention and half explicit. [Rubric §3, Clean Architecture] and [Rubric §15, Best Practices & Code Quality] both come down to keeping wiring at the edges; this file is the module's one wiring point. It uses the C# preview extension-type syntax extension(IServiceCollection services) (:20) to hang AddConferenceUI (:26) off IServiceCollection, the same idiom every module's DependencyInjection uses. The convention half is delegated to AddUIModule<ConferenceUIModule>() (:30), which does two things in one call (MMCA.Common.UI/DependencyInjection.cs:276-282): a Scrutor scan of this assembly registering every IEntityService<,> implementation AsImplementedInterfaces().WithScopedLifetime(), and the singleton registration of the descriptor itself. Registering AsImplementedInterfaces is what makes a page able to inject the narrow per-entity interface rather than the open generic, and it means adding a new entity service needs no edit here.

      +
    • Concept introduced: the extension(IServiceCollection) registration block, half convention and half explicit. [Rubric §3, Clean Architecture] and [Rubric §15, Best Practices & Code Quality] both come down to keeping wiring at the edges; this file is the module's one wiring point. It uses the C# preview extension-type syntax extension(IServiceCollection services) (:21) to hang AddConferenceUI (:27) off IServiceCollection, the same idiom every module's DependencyInjection uses. The convention half is delegated to AddUIModule<ConferenceUIModule>() (:31), which does two things in one call (MMCA.Common.UI/DependencyInjection.cs:292-298): a Scrutor scan of this assembly registering every IEntityService<,> implementation AsImplementedInterfaces().WithScopedLifetime(), and the singleton registration of the descriptor itself. Registering AsImplementedInterfaces is what makes a page able to inject the narrow per-entity interface rather than the open generic, and it means adding a new entity service needs no edit here.

    • -
    • Walkthrough: the scan runs first (:30), then the method registers by hand exactly the services the scan cannot see, because they do not implement IEntityService<,>:

      +
    • Walkthrough: the scan runs first (:31), then the method registers by hand exactly the services the scan cannot see, because they do not implement IEntityService<,>:

        -
      • four child-entity managers for the join relationships (:33-36);
      • -
      • the speaker dashboard service (:39);
      • -
      • the two organizer feedback moderation services, tagged BR-53 in the comment (:41-43);
      • -
      • the session-selection decision-support service (:46);
      • -
      • the session-asset service, registered explicitly because the endpoint has no paged list, no lookup, and no by-id read, so the service declares only the five operations the API actually offers, per the comment (:48-51);
      • -
      • the offline-first page fetch for the public session schedule, tagged to ADR-042 Wave 3 (:53-54);
      • -
      • three cross-module lookup services (:56-59);
      • -
      • and the composite lookup for the speaker detail page (:63), whose comment states its purpose precisely (:61-62): one call and one failure branch over the three lookups that page needs together.
      • +
      • four child-entity managers for the join relationships (:34-37);
      • +
      • the speaker dashboard service (:40);
      • +
      • the two organizer feedback moderation services, tagged BR-53 in the comment (:42-44);
      • +
      • the session-selection decision-support service (:47);
      • +
      • the session-asset service, registered explicitly because the endpoint has no paged list, no lookup, and no by-id read, so the service declares only the five operations the API actually offers, per the comment (:49-52);
      • +
      • the offline-first page fetch for the public session schedule, tagged to ADR-042 Wave 3 (:54-55);
      • +
      • three cross-module lookup services (:57-60);
      • +
      • the composite lookup for the speaker detail page (:64), whose comment states its purpose precisely (:62-63): one call and one failure branch over the three lookups that page needs together;
      • +
      • and TryAddSingleton(TimeProvider.System) (:70). The comment above it (:66-69) gives the reason: the pages' current-event selection, the home countdown, and the event lookup cache read the clock through TimeProvider, no UI head registers one (the heads never call AddInfrastructure, where the framework's registration lives), and the Engagement UI's own TryAdd is skipped when that module is disabled. TryAdd means whichever module registers first wins and the second call is a no-op, so the two UI modules can each guarantee the clock without colliding.
      -

      Every explicit registration is AddScoped; only the descriptor is a singleton, which is correct because it is immutable data. The method returns services for chaining (:69).

      +

      Every per-service registration is AddScoped; the singletons are the descriptor and the TimeProvider, both stateless, so one instance per container is correct. The method returns services for chaining (:76).

    • -
    • Why it's built this way: scanning the uniform bulk and spelling out the one-off collaborators keeps registration short without hiding the non-trivial wiring. The closing comment (:65-68) documents what this file deliberately does not register: IPublicLinkBuilder comes from the framework, where AddUIShared TryAdd-registers the browser-origin builder (MMCA.Common.UI/DependencyInjection.cs:148-150), and the MAUI head overrides it afterwards with AddCommonMauiPublicLinkBuilder() (MMCA.Common.UI.Maui/DependencyInjection.cs:134, called at MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/MauiProgram.cs:143) so last-registration-wins points shared links at the configured public web URL. Recording an ordering dependency that lives in another repository, right where a reader would otherwise expect the registration, is the cheap version of [Rubric §34, Architecture Governance and Documentation].

      +
    • Why it's built this way: scanning the uniform bulk and spelling out the one-off collaborators keeps registration short without hiding the non-trivial wiring. The closing comment (:72-75) documents what this file deliberately does not register: IPublicLinkBuilder comes from the framework, where AddUIShared TryAdd-registers the browser-origin builder (MMCA.Common.UI/DependencyInjection.cs:164-166), and the MAUI head overrides it afterwards with AddCommonMauiPublicLinkBuilder() (MMCA.Common.UI.Maui/DependencyInjection.cs:134, called at MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/MauiProgram.cs:143) so last-registration-wins points shared links at the configured public web URL. Recording an ordering dependency that lives in another repository, right where a reader would otherwise expect the registration, is the cheap version of [Rubric §34, Architecture Governance and Documentation].

    • -
    • Where it's used: called once during startup by each of the three UI heads: the Blazor Server host (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:154), the WebAssembly client (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Program.cs:59), and the MAUI host (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/MauiProgram.cs:131), alongside the other modules' AddXxxUI() extensions.

      +
    • Where it's used: called once during startup by each of the three UI heads: the Blazor Server host (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:166), the WebAssembly client (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Program.cs:59), and the MAUI host (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/MauiProgram.cs:131), alongside the other modules' AddXxxUI() extensions.

    PublicSessionListFilterState

    @@ -701,7 +709,7 @@

    PublicSessionListFilterState

  • Why it's built this way: the codec is pure and total, so its rules (the sentinel, the audience gate, the keep-on-unparseable behavior) can be exercised without a renderer, and the page keeps a two-line SaveFilters / RestoreFilters pair instead of forty lines of string handling. [Rubric §14, Testability] and [Rubric §1, SOLID].
  • -
  • Where it's used: called only from PublicSessionList's sealed base overrides, SaveFilters (PublicSessionList.razor.cs:76-79, passing persistEventId: _isPrivileged && _eventFilterResolved) and RestoreFilters (PublicSessionList.razor.cs:82-85).
  • +
  • Where it's used: called only from PublicSessionList's sealed base overrides, SaveFilters (PublicSessionList.razor.cs:78-81, passing persistEventId: _isPrivileged && _eventFilterResolved) and RestoreFilters (PublicSessionList.razor.cs:84-87).
  • SessionAssetComposer

    @@ -786,6 +794,23 @@ always-available "open in Google Maps" fallback link (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventDetail.razor:73,86). +

    PublicReadAudience

    +
    +

    MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI.Pages.Public · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/PublicReadAudience.cs:10 · Level 2 · class (internal static)

    +
    +
      +
    • What it is: the one privileged-reader check the public Conference pages share. A single method, IsPrivilegedReaderAsync (PublicReadAudience.cs:18), answers whether the reader behind the cascaded authentication state holds one of ConferenceReadAudience.PrivilegedRoles (class doc, :6-9).
    • +
    • Depends on: Microsoft.AspNetCore.Components.Authorization for AuthenticationState (:1) and ConferenceReadAudience from MMCA.ADC.Conference.Shared.Authorization (:2, read at :28). No injected service and no state.
    • +
    • Concept introduced, a fail-closed audience read that still honors cancellation. Four pages branch on "is this reader an Organizer or a ContentEditor"; keeping the read in one method fixes the semantics in one place, and the method doc states them (:12-17):
        +
      1. No cascaded state means the public audience. A null task returns false before any await (:20-23).
      2. +
      3. The state is awaited, never read synchronously (:27), so a token that hydrates asynchronously from the HttpOnly cookie is settled before the role test at :28 runs. The callers' own comments explain why that ordering matters for them (see PublicEventList and PublicEventDetail).
      4. +
      5. A faulted state degrades to the narrowest audience, a cancellation does not. The catch filter is ex is not OperationCanceledException (:31), so an unreadable auth state returns false (:34) and the public page still renders, while a cancellation propagates to the caller's own teardown handling. The CA1031 suppression carries its reason inline (:30). [Rubric §26, Front-End Security] (fail closed to the narrower audience) and [Rubric §11, Security].
      6. +
      +
    • +
    • Walkthrough: IsPrivilegedReaderAsync(Task<AuthenticationState>?) (:18-36): null guard (:20-23), await the task (:27), then PrivilegedRoles.Any(authState.User.IsInRole) (:28); the filtered catch (:31-35) returns false.
    • +
    • Why it's built this way: the audience decision gates what a public page shows (a grid, a breadcrumb, a redirect), so its edge cases (no state, a faulted state, a cancelled state) must answer identically on every page. One internal static helper makes that a property of the code rather than of four copies kept in step by hand, and it is unit-testable without a renderer. [Rubric §14, Testability].
    • +
    • Where it's used: PublicEventList (PublicEventList.razor.cs:72), PublicEventDetail (PublicEventDetail.razor.cs:62), PublicSessionList (PublicSessionList.razor.cs:121) and PublicSpeakerList (PublicSpeakerList.razor.cs:97), each assigning _isPrivileged from it. PublicReadAudienceTests pins the eight behaviors, including both cancellation paths propagating (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.UI.Tests/Pages/Public/PublicReadAudienceTests.cs:79, :89) and a faulted state reading as non-privileged (:69).
    • +

    SessionAssetDisplay

    MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI.Pages.SessionAssets · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/SessionAssets/SessionAssetDisplay.cs:13 · Level 2 · internal static class

    @@ -860,7 +885,10 @@

    SessionAssetsDownloadList

    33-36), the same reload guard the mutable panel uses, so re-rendering the same session never refetches.
  • Walkthrough
    • OnParametersSetAsync (lines 31-49) sets _loadedSessionId (line 38), then awaits - SessionAssetService.GetBySessionAsync(SessionId, _cts.Token) (line 42) and unwraps the + SessionAssetService.GetBySessionAsync(SessionId, cancellationToken: _cts.Token) (line 42), leaving + bypassCache at its false default so the attendee read is served from the shared output cache + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/SessionAssets/ISessionAssetUIService.cs:25-34), + and unwraps the Result with TryGetValue, falling back to an empty list on failure (line 43) rather than surfacing an error state.
    • DescribeSize(long sizeBytes) (lines 51-55) forwards to @@ -890,32 +918,32 @@

      SessionAssetsPanel

      a file, rename or delete an asset, all behind a shared cap and a shared failure-to-message mapping. A CanManage parameter toggles it into the same read-only rendering SessionAssetsDownloadList provides. Its add-link and pick-a-file inputs - now live in a child SessionAssetComposer (line 61 field _composer); this + live in a child SessionAssetComposer (line 62 field _composer); this panel keeps the cap check, the write itself, and every status message.
    • -
    • Depends on: ISessionAssetUIService (line 32 injected field), SessionIdentifierType (line 35 - parameter), SessionAssetComposer (line 61), SessionAssetDisplay - for MaxFileMegabytes and FormatSize, SessionAssetLimits.MaxAssetsPerSession (line 63), - SessionAssetLinkRequest (lines 115-121) and SessionAssetUpdateRequest (lines 185-189), and - Result (line 208). Externals: Blazor ([Inject], - [Parameter], IBrowserFile), MudBlazor (Severity), and a DeleteConfirmation component (line 60) +
    • Depends on: ISessionAssetUIService (line 33 injected field), SessionIdentifierType (line 36 + parameter), SessionAssetComposer (line 62), SessionAssetDisplay + for MaxFileMegabytes and FormatSize, SessionAssetLimits.MaxAssetsPerSession (line 64), + SessionAssetLinkRequest (lines 110-116) and SessionAssetUpdateRequest (lines 181-185), and + Result (line 209). Externals: Blazor ([Inject], + [Parameter], IBrowserFile), MudBlazor (Severity), and a DeleteConfirmation component (line 61) from MMCA.Common.UI.Components.
    • Concept introduced, one mutation gateway for every write. Add-link, upload-file, rename and delete - are four different service calls, but they all route through MutateAsync (lines 208-232, plus the - Task<Result<T>>-adapting overload at lines 238-239): set busy, run the operation, report the failure + are four different service calls, but they all route through MutateAsync (lines 209-233, plus the + Task<Result<T>>-adapting overload at lines 239-240): set busy, run the operation, report the failure through DescribeFailure or the success through a localized toast state, reload the list, clear busy in a finally. A caller supplies only the operation delegate, the success resource key, and an optional post-success callback (AddLinkAsync clears the composer's draft, SaveTitleAsync calls CancelEditing). [Rubric #24, Forms, Validation & UX Safety] assesses whether a rejection is explained in place: - FailureResourceKeys (lines 258-274) maps every machine-readable error code the four write paths can - return to a resource key, with the class doc explaining two edge cases directly in the source (lines - 645-651, 652-657 of the field's own doc comment): Http.413 is the one transport-level status in the + FailureResourceKeys (lines 259-275) maps every machine-readable error code the four write paths can + return to a resource key, with the field's own doc comment (lines 242-258) explaining two edge cases + directly in the source (lines 247-250 and 253-256): Http.413 is the one transport-level status in the table, because Kestrel refuses an over-cap request body before the friendly SessionAsset.InvalidUpload - domain check ever runs, which is the one case the client-side size check (line 145) cannot catch (a body + domain check ever runs, which is the one case the client-side size check (line 146) cannot catch (a body inflated past the cap by multipart framing); and a title refusal is listed under two different codes because two layers can raise it, the domain invariant (SessionAsset.Title.*) and the request validator's FluentValidation defaults (NotEmptyValidator / MaximumLengthValidator), which the request - validator leaves unwrapped rather than remapping. DescribeFailure (lines 281-292) falls back to one + validator leaves unwrapped rather than remapping. DescribeFailure (lines 282-293) falls back to one generic sentence for any code not in the table (a transport fault, a concurrency refusal, an unexpected
      1. rather than surfacing the server's raw text. [Rubric #27, Internationalization] assesses whether every user-facing string is externalized: every @@ -924,30 +952,35 @@

        SessionAssetsPanel

    • Walkthrough
        -
      • IsAtCap (line 63) is the one gating boolean the panel itself computes; the markup passes it into the +
      • IsAtCap (line 64) is the one gating boolean the panel itself computes; the markup passes it into the composer as Disabled alongside the busy flag.
      • -
      • OnParametersSetAsync / LoadAsync (lines 66-99) mirror - SessionAssetsDownloadList's reload guard, but on a failed fetch this - version keeps the list empty and sets a status message (SetStatus, line 89), unlike the - read-only list's silent fallback.
      • -
      • AddLinkAsync (lines 101-123) takes the (Title, Url) tuple the composer already normalized, re-checks +
      • OnParametersSetAsync / LoadAsync (lines 66-100) mirror + SessionAssetsDownloadList's reload guard, but differ in two ways. The + fetch passes bypassCache: true (line 82): the list endpoint is served under a shared output-cache + policy, so an owner's read could otherwise hit an entry built for an anonymous caller and miss a + material they just added, and the flag makes the UI service add a unique query parameter that is a + guaranteed cache miss + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/SessionAssets/ISessionAssetUIService.cs:25-34). + And on a failed fetch this version keeps the list empty and sets a status message (SetStatus, + line 90), unlike the read-only list's silent fallback.
      • +
      • AddLinkAsync (lines 102-124) takes the (Title, Url) tuple the composer already normalized, re-checks only IsAtCap, builds a SessionAssetLinkRequest with SortOrder = _assets.Count (line 115), and clears the composer's draft (_composer?.ClearDraft()) only once MutateAsync reports the write - succeeded, so a refused write leaves the speaker with what they typed (in-code comment, lines 117-118).
      • -
      • ReportInvalidLinkAddress (lines 125-131) words the composer's own refusal of a typed address. The + succeeded, so a refused write leaves the speaker with what they typed (in-code comment, lines 118-119).
      • +
      • ReportInvalidLinkAddress (lines 126-132) words the composer's own refusal of a typed address. The doc comment explains why the wording stays here rather than moving into the composer: every outcome of every action reaches the one aria-live status region below, and the sentence stays beside the matching API refusal it mirrors in FailureResourceKeys.
      • -
      • OnFileSelectedAsync (lines 133-155) takes the composer's forwarded IBrowserFile directly (no more - InputFileChangeEventArgs unwrap), rejects an over-cap file client-side against - SessionAssetLimits.MaxFileBytes (line 144) before ever posting, with a title defaulted by - DefaultTitleFor (lines 296-308), which strips the extension and falls back to the raw file name only +
      • OnFileSelectedAsync (lines 134-157) takes the composer's forwarded IBrowserFile directly, + rejects an over-cap file client-side against + SessionAssetLimits.MaxFileBytes (line 146) before ever posting, with a title defaulted by + DefaultTitleFor (lines 307-319), which strips the extension and falls back to the raw file name only if the stripped result is blank, capped at SessionAssetDTO.TitleMaxLength.
      • -
      • StartEditing / CancelEditing (lines 157-168) toggle the inline rename state; SaveTitleAsync - (lines 170-186) echoes the asset's own SortOrder back unchanged (in-code comment: omitting it would +
      • StartEditing / CancelEditing (lines 159-170) toggle the inline rename state; SaveTitleAsync + (lines 172-191) echoes the asset's own SortOrder back unchanged (in-code comment: omitting it would silently move the material to the top) and posts through UpdateAsync with the asset's RowVersion for optimistic concurrency.
      • -
      • DeleteAsync (lines 191-201) confirms through the injected DeleteConfirmation component rather than +
      • DeleteAsync (lines 193-203) confirms through the injected DeleteConfirmation component rather than a native browser confirm() (in-code comment: unstyled, untranslated and unreachable from the MAUI head), then deletes through MutateAsync.
      • The _disposed-guarded dispose pair follows the same shape as every other component in this group.
      • @@ -968,25 +1001,25 @@

        SessionAssetsPanel

      PublicEventDetail

      -

      MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI.Pages.Public.Events · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventDetail.razor.cs:19 · Level 8 · class (Blazor code-behind)

      +

      MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI.Pages.Public.Events · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventDetail.razor.cs:18 · Level 8 · class (Blazor code-behind)

      • What it is: the read-only public view of one event: venue information, rooms, support contacts, and the conference-day conveniences (copy the Wi-Fi details, open directions, a distance-to-venue hint, a collapsible venue map, a QR code for the page itself). For a public visitor it is also the landing page of the whole conference, because PublicEventList redirects them here.
      • -
      • Depends on: IEventUIService (:20), EventDTO, IToastService (:22), ConferenceReadAudience (:64), ConferenceRoutePaths (:80, :170, :172, :174), DomainHelper's Id.Parse<T> extension for the route string (:118, defined at MMCA.Common/Source/Core/MMCA.Common.Shared/Extensions/DomainHelper.cs:30), VenueMapLinks (called from the markup, not the code-behind, at PublicEventDetail.razor:73,86) to decide the venue-map rendering, and four device-capability abstractions: IClipboardService, IMapNavigationService, IGeolocationService, IGeocodingService (:23-26), whose GeoPoint results supply the DistanceKmTo used at :231. It also reads IConfiguration for the host-wide support contacts (:27, :56-57).
      • +
      • Depends on: IEventUIService (:20), EventDTO, IToastService (:22), PublicReadAudience (:62, which reads ConferenceReadAudience on the page's behalf), ConferenceRoutePaths (:72, :163, :165, :167), DomainHelper's Id.Parse<T> extension for the route string (:110, defined at MMCA.Common/Source/Core/MMCA.Common.Shared/Extensions/DomainHelper.cs:30), VenueMapLinks (called from the markup, not the code-behind, at PublicEventDetail.razor:73,86) to decide the venue-map rendering, and four device-capability abstractions: IClipboardService, IMapNavigationService, IGeolocationService, IGeocodingService (:23-26), whose GeoPoint results supply the DistanceKmTo used at :226. It also reads IConfiguration for the host-wide support contacts (:27, :59-60).
      • Concept introduced, the load guard, the audience-shaped breadcrumb trail, and best-effort progressive enhancement. Three mechanisms are worth extracting.
          -
        1. Load once per id, and let the newest load win. The route value arrives as [Parameter] string Id (:29), and OnParametersSetAsync compares it against _loadedId (:103-112) so a re-render does not refetch. On top of that, each LoadEventAsync call opens a fresh generation and cancellation token through _load.Begin() (:117), and both the fetch (:123) and the distance hint (:151) run under that token; every await point is re-checked against _load.IsCurrent(generation) (:124, :226, :232, :166), so a superseded fetch both drops its results and is actually cancelled, not merely ignored. The field doc explains why the generation and not the route id is authoritative (:92-97): _loadedId is stamped synchronously before the await, so two rapid route changes would otherwise let the later-completing fetch paint the wrong event. The finally is guarded by the same test (:162-170), because an unconditional clear would let a superseded response switch off the spinner the newer load just turned on. [Rubric #19, State Management & Data Flow].
        2. -
        3. The breadcrumb trail depends on the audience, and the audience is awaited first. OnInitializedAsync resolves privileged status from role membership before building the trail (:59-70), then adds the "Events" crumb only for a privileged reader (:78-81). The doc comment states both halves of the reasoning (:46-53): a public visitor was redirected to this page by the event list, so an Events crumb would bounce them straight back here, and the access token hydrates asynchronously from the HttpOnly cookie, so reading roles synchronously would render the wrong trail and correct it on the next render. A failed auth read is treated as non-privileged (:66-69). [Rubric #25, Navigation & Information Architecture] (assesses that navigation affordances lead somewhere the reader can actually use) and [Rubric #26, Front-End Security] (fail closed to the narrower audience).
        4. -
        5. Every capability is optional. TryComputeDistanceAsync (:213-240) returns early when geolocation or geocoding is unsupported or the venue address is blank (:220-223), and again on any null result or superseded generation (:226, :232), so a denied permission or an offline geocoder simply leaves the hint off. The doc comment states the rule plainly: this must never block the page (:213-217). [Rubric #29, Resilience & Business Continuity] (assesses degradation when an optional dependency is absent) and [Rubric #26, Front-End Security] (a location read is soft and unblocking, never a gate on content). These come from the device-capability layer of ADR-042. - A fourth detail is a small but real configuration rule: a per-event OrganizerContactEmail wins over the host-wide Support:Email, and it is re-evaluated on every load so navigating between events never leaves the previous organizer's address on screen (:147-149). [Rubric #15, Best Practices & Code Quality]: a conference can publish its own contact without a redeploy.
        6. +
        7. Load once per id, and let the newest load win. The route value arrives as [Parameter] string Id (:29), and OnParametersSetAsync compares it against _loadedId (:91-100) so a re-render does not refetch. On top of that, each LoadEventAsync call opens a fresh generation and cancellation token through _load.Begin() (:105), and both the fetch (:111) and the distance hint (:139) run under that token; every await point is re-checked against _load.IsCurrent(generation) (:112, :214, :220, :154), so a superseded fetch both drops its results and is actually cancelled, not merely ignored. The field doc explains why the generation and not the route id is authoritative (:80-85): _loadedId is stamped synchronously before the await, so two rapid route changes would otherwise let the later-completing fetch paint the wrong event. The finally is guarded by the same test (:150-158), because an unconditional clear would let a superseded response switch off the spinner the newer load just turned on. [Rubric #19, State Management & Data Flow].
        8. +
        9. The breadcrumb trail depends on the audience, and the audience is awaited first. OnInitializedAsync resolves privileged status through PublicReadAudience.IsPrivilegedReaderAsync (:62) before building the trail (:57-76), then adds the "Events" crumb only for a privileged reader (:70-73). The doc comment states both halves of the reasoning (:49-56): a public visitor was redirected to this page by the event list, so an Events crumb would bounce them straight back here, and the access token hydrates asynchronously from the HttpOnly cookie, so reading roles synchronously would render the wrong trail and correct it on the next render. A faulted auth read is treated as non-privileged by the shared helper, which lets a cancellation propagate instead (PublicReadAudience.cs:31). [Rubric #25, Navigation & Information Architecture] (assesses that navigation affordances lead somewhere the reader can actually use) and [Rubric #26, Front-End Security] (fail closed to the narrower audience).
        10. +
        11. Every capability is optional. TryComputeDistanceAsync (:206-228) returns early when geolocation or geocoding is unsupported or the venue address is blank (:208-211), and again on any null result or superseded generation (:214, :220), so a denied permission or an offline geocoder simply leaves the hint off. The doc comment states the rule plainly: this must never block the page (:201-205). [Rubric #29, Resilience & Business Continuity] (assesses degradation when an optional dependency is absent) and [Rubric #26, Front-End Security] (a location read is soft and unblocking, never a gate on content). These come from the device-capability layer of ADR-042. + A fourth detail is a small but real configuration rule: a per-event OrganizerContactEmail wins over the host-wide Support:Email, and it is re-evaluated on every load so navigating between events never leaves the previous organizer's address on screen (:135-137). [Rubric #15, Best Practices & Code Quality]: a conference can publish its own contact without a redeploy.
      • Walkthrough
          -
        • LoadEventAsync (:114-171): begin a fresh generation and token from _load.Begin() (:117), parse the id (:122), fetch with children under that token (GetByIdAsync(eventId, true, token), :123), and on failure clear Event so a failed navigation never leaves the previous event on screen, toasting the not-found wording for a 404 and the page's fixed load-failure key otherwise (:129-137); on success resolve the support address, reset _showMap to false (:142, so navigating to a different event never leaves the previous one's map expanded), and kick off the distance hint, passing both the generation and the same token (:147-151). OperationCanceledException is swallowed as expected teardown (:154-157) and a broad catch toasts the same load-failure key (:158-161).
        • -
        • _showMap (:47-48) and ToggleMap (:181) back a collapse/expand toggle for the venue map: the markup only offers it when VenueMapLinks.IsGoogleMapsEmbed recognizes the stored VenueMapUrl (PublicEventDetail.razor:73), and only actually renders the iframe when _showMap is true (PublicEventDetail.razor:90-99), so third-party map content does not load until the reader asks for it. VenueMapLinks.BuildSearchUrl always renders the plain "open in Google Maps" link beside the toggle as a fallback (PublicEventDetail.razor:86-89).
        • -
        • CopyWifiAsync (:185-196): copies Event.WiFiInfo through the clipboard abstraction and reports success or failure with one toast whose severity flips on the result (:194-195).
        • -
        • OpenDirectionsAsync (:198-211): native heads launch the platform maps app, browsers open a maps site; a false return raises a warning.
        • -
        • TryComputeDistanceAsync (:213-240): takes the load generation and a CancellationToken from the caller rather than reading _cts itself, geocodes the venue under that token (:225), reads the current-or-last-known position the same way (:231), converts kilometres to miles with an explicit named constant (:237-238), and calls StateHasChanged() (:239) because the value arrives after the render that requested it; the markup renders it to one decimal in the viewer's culture (PublicEventDetail.razor:58-61).
        • -
        • Navigation helpers (:173-183) route back to the list (privileged readers only, per the comment at :168-169), on to the public schedule, on to the activities page, and to the event feedback form. Disposal (:244-259) is the standard cancel-on-disposal pattern over the CancellationTokenSource at :34, plus disposing the LatestLoadGuard itself (:255).
        • +
        • LoadEventAsync (:102-159): begin a fresh generation and token from _load.Begin() (:105), parse the id (:110), fetch with children under that token (GetByIdAsync(eventId, true, token), :111), and on failure clear Event so a failed navigation never leaves the previous event on screen, toasting the not-found wording for a 404 and the page's fixed load-failure key otherwise (:117-126); on success reset _showMap to false (:130, so navigating to a different event never leaves the previous one's map expanded), resolve the support address (:135-137), and run the distance hint, passing both the generation and the same token (:139). OperationCanceledException is swallowed as expected teardown (:142-145) and a broad catch toasts the same load-failure key (:146-149).
        • +
        • _showMap (:46-47) and ToggleMap (:169) back a collapse/expand toggle for the venue map: the markup only offers it when VenueMapLinks.IsGoogleMapsEmbed recognizes the stored VenueMapUrl (PublicEventDetail.razor:73), and only actually renders the iframe when _showMap is true (PublicEventDetail.razor:90-99), so third-party map content does not load until the reader asks for it. VenueMapLinks.BuildSearchUrl always renders the plain "open in Google Maps" link beside the toggle as a fallback (PublicEventDetail.razor:86-89).
        • +
        • CopyWifiAsync (:173-184): copies Event.WiFiInfo through the clipboard abstraction and reports success or failure with one toast whose severity flips on the result (:181-183).
        • +
        • OpenDirectionsAsync (:186-199): native heads launch the platform maps app, browsers open a maps site; a false return raises a warning.
        • +
        • TryComputeDistanceAsync (:206-228): takes the load generation and a CancellationToken from the caller rather than reading _cts itself, geocodes the venue under that token (:213), reads the current-or-last-known position the same way (:219), converts kilometres to miles with an explicit named constant (:225-226), and calls StateHasChanged() (:227) because the value arrives after the render that requested it; the markup renders it to one decimal in the viewer's culture (PublicEventDetail.razor:58-61).
        • +
        • Navigation helpers (:161-171) route back to the list (privileged readers only, per the comment at :161-162), on to the public schedule, on to the activities page, and to the event feedback form. Disposal (:230-253) is the standard cancel-on-disposal pattern over the CancellationTokenSource at :33, plus disposing the LatestLoadGuard itself (:243).
      • Why it's built this way: the public event page is the one an attendee opens while standing in the building, so its extras (Wi-Fi, directions, distance, the venue map) are worth having, none of them is worth failing the page over, and the map specifically stays collapsed by default so the table stays compact and no third-party (Google) content loads until the reader chooses to see it (PublicEventDetail.razor:75-79 in-code comment).
      • @@ -994,28 +1027,28 @@

        PublicEventDetail

      PublicEventList

      -

      MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI.Pages.Public.Events · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventList.razor.cs:31 · Level 9 · class (Blazor code-behind)

      +

      MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI.Pages.Public.Events · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventList.razor.cs:30 · Level 9 · class (Blazor code-behind)

        -
      • What it is: the /conference/events route, where the audience decides whether a list is shown at all. A privileged reader (Organizer or ContentEditor) gets the full grid of published and unpublished events; every other visitor is redirected to the current or next event's detail page (class doc, PublicEventList.razor.cs:14-30).
      • -
      • Depends on: extends DataGridListPageBase<TDto> closed over EventDTO (:31); IEventUIService and IEventLookupService (:35-36), ConferenceReadAudience (:77), CurrentEventSelector (:102), EventInfo (:92), ConferenceRoutePaths (:114, :158), MobileInfiniteScrollList<TItem> (:46, rendered at PublicEventList.razor:23), ListPageActions (:132), and Result in the mobile fetch signature (:148). Server-side the audience split is enforced by PublishedEventSpecification.
      • +
      • What it is: the /conference/events route, where the audience decides whether a list is shown at all. A privileged reader (Organizer or ContentEditor) gets the full grid of published and unpublished events; every other visitor is redirected to the current or next event's detail page (class doc, PublicEventList.razor.cs:13-29).
      • +
      • Depends on: extends DataGridListPageBase<TDto> closed over EventDTO (:30); IEventUIService and IEventLookupService (:34-35), an injected TimeProvider (:37), PublicReadAudience (:72), CurrentEventSelector (:91), EventInfo (:81), ConferenceRoutePaths (:103, :147), MobileInfiniteScrollList<TItem> (:46, rendered at PublicEventList.razor:23), ListPageActions (:121), and Result in the mobile fetch signature (:137). Server-side the audience split is enforced by PublishedEventSpecification.
      • Concept introduced, the audience gate as a routing decision, and the three-state render. This page is the sharpest example in the group of a UI decision that has to wait for identity.
          -
        1. Resolve the audience before deciding anything. OnInitializedAsync awaits the cascading Task<AuthenticationState> and reads role membership first (:72-83). The comment (:69-71) and the class doc (:22-26) both name the failure this prevents: on all three heads (Blazor Server, WebAssembly, MAUI) the access token hydrates asynchronously from the HttpOnly cookie, so a synchronous role read would see an anonymous principal and bounce an organizer off their own list. A failed read is treated as non-privileged (:79-82). [Rubric §11, Security] and [Rubric §26, Front-End Security] (assess that an authorization-shaped branch reads a settled principal, and fails to the narrower audience).
        2. -
        3. Three states, and one of them renders nothing. _showGrid (:53) opens the search box and the layout switch for a privileged reader (:85-89); _showEmpty (:61) is set only when nothing is published anywhere, so there is no redirect target and the page has to stay and say so (:118-120); and the redirect path deliberately leaves both false (:109-116), so the page stays blank until the navigation takes effect instead of flashing a list on the way out. The field doc spells this out (:55-60). [Rubric §18, UI Architecture & Component Design] (assesses that intermediate states are designed rather than incidental).
        4. -
        5. replace: true on the redirect. The comment (:111-113) records the exact trap it prevents: left in the history stack, Back from the event detail would land here and redirect straight forward again, trapping the visitor on the detail page. [Rubric §25, Navigation & Information Architecture] (assesses that the Back button keeps working). - The redirect target is computed with CurrentEventSelector.SelectCurrentOrNext, passing the four accessors explicitly because the lookup returns EventInfo rather than EventDTO, which the comment calls out (:98-107). It is the same live-window math every other landing surface uses, so a visitor always lands on the conference that is actually happening. A failed lookup is non-critical and leaves events null (:91-96), which falls through to the empty state rather than to a broken redirect.
        6. +
        7. Resolve the audience before deciding anything. OnInitializedAsync awaits PublicReadAudience.IsPrivilegedReaderAsync over the cascading Task<AuthenticationState> first (:72). The comment (:69-71) and the class doc (:21-25) both name the failure this prevents: on all three heads (Blazor Server, WebAssembly, MAUI) the access token hydrates asynchronously from the HttpOnly cookie, so a synchronous role read would see an anonymous principal and bounce an organizer off their own list. A faulted read is treated as non-privileged by the shared helper (PublicReadAudience.cs:31). [Rubric §11, Security] and [Rubric §26, Front-End Security] (assess that an authorization-shaped branch reads a settled principal, and fails to the narrower audience).
        8. +
        9. Three states, and one of them renders nothing. _showGrid (:53) opens the search box and the layout switch for a privileged reader (:74-78); _showEmpty (:61) is set only when nothing is published anywhere, so there is no redirect target and the page has to stay and say so (:107-109); and the redirect path deliberately leaves both false (:98-105), so the page stays blank until the navigation takes effect instead of flashing a list on the way out. The field doc spells this out (:55-60). [Rubric §18, UI Architecture & Component Design] (assesses that intermediate states are designed rather than incidental).
        10. +
        11. replace: true on the redirect. The comment (:100-102) records the exact trap it prevents: left in the history stack, Back from the event detail would land here and redirect straight forward again, trapping the visitor on the detail page (NavigateTo at :103). [Rubric §25, Navigation & Information Architecture] (assesses that the Back button keeps working). + The redirect target is computed with CurrentEventSelector.SelectCurrentOrNext, passing the four accessors explicitly because the lookup returns EventInfo rather than EventDTO, which the comment calls out (:87-96). "Now" comes from the injected TimeProvider (TimeProvider.GetUtcNow().UtcDateTime, :96) rather than the static system clock, so the live-window decision reads a replaceable clock. It is the same live-window math every other landing surface uses, so a visitor always lands on the conference that is actually happening. A failed lookup is non-critical and leaves events null (:80-85), which falls through to the empty state rather than to a broken redirect.
      • Walkthrough
        • GridRef (:42) exposes the captured grid so the base can restore rows-per-page and current page; RetryLoadAsync (:45) re-runs the fetch from the inline error state the base renders when LoadFailed is set (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Common/DataGridListPageBase.cs:42). [Rubric §29, Resilience & Business Continuity].
        • -
        • SaveFilters / RestoreFilters (:123-127) persist the one search term; OnSearchChanged (:129-133) stores it and reloads whichever layout is active through ListPageActions.ReloadActiveLayoutAsync (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Common/ListPageActions.cs:26).
        • -
        • LoadServerData (:135-145) passes showCancelSnackbar: false (:145), so a superseded fetch (the reader typed another character) is silent rather than raising a toast, and turns the search string into a Name contains server filter (:142-143).
        • -
        • FetchMobilePage (:148-155) is the parallel infinite-scroll path, hard-sorted by Name ascending; OnMobileCardClick (:157-158) routes to PublicEventDetail.
        • +
        • SaveFilters / RestoreFilters (:112-116) persist the one search term; OnSearchChanged (:118-122) stores it and reloads whichever layout is active through ListPageActions.ReloadActiveLayoutAsync (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Common/ListPageActions.cs:26).
        • +
        • LoadServerData (:124-134) passes showCancelSnackbar: false (:134), so a superseded fetch (the reader typed another character) is silent rather than raising a toast, and turns the search string into a Name contains server filter (:131-132).
        • +
        • FetchMobilePage (:137-144) is the parallel infinite-scroll path, hard-sorted by Name ascending; OnMobileCardClick (:146-147) routes to PublicEventDetail.
      • Why it's built this way: a public visitor cares about the conference that is running or coming up, not about a roster of past editions, while an organizer curating the catalog needs every row including the unpublished ones. One route serving both is cheaper than two, provided the audience is known before the branch is taken.
      • Where it's used: the /conference/events route (PublicEventList.razor:1). Every non-privileged arrival leaves immediately for PublicEventDetail; privileged rows and cards navigate to the same page.
      • -
      • Caveats / not-in-source: the published-only scoping of the underlying reads for non-privileged callers (BR-108, class doc :27-28) is enforced in the Conference API through PublishedEventSpecification, not on this page.
      • +
      • Caveats / not-in-source: the published-only scoping of the underlying reads for non-privileged callers (BR-108, class doc :26-27) is enforced in the Conference API through PublishedEventSpecification, not on this page.

      ConferenceTrackInfo

      @@ -1031,7 +1064,7 @@

      ConferenceTrackInfo

    EventPhase

    -

    MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI.Pages.Home · MMCA.ADC.Conference.UI/Pages/Home/ADCHome.razor.cs:90 · Level 0 · enum (private)

    +

    MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI.Pages.Home · MMCA.ADC.Conference.UI/Pages/Home/ADCHome.razor.cs:93 · Level 0 · enum (private)

    • What it is: the three-state classification of the featured event relative to now: Upcoming, Live, Ended (:71-76). It is the single switch the landing page's hero renders from.
    • @@ -1100,8 +1133,8 @@

      PublicScheduleRoomOptions

    • Scope (:37-58): scoping (:42-44), where a non-null eventId takes that event's Rooms (an unknown id yields an empty list through the ?? [] fallback) and a null id, which only a privileged reader viewing every event can produce, takes the union across all loaded events; shaping (:46-49), DistinctBy(r => r.Id) because the union can repeat a room, then OrderBy(Sort).ThenBy(Name, StringComparer.OrdinalIgnoreCase) so the picker order is the organizer's intended order with a deterministic case-insensitive tiebreak; reconciliation (:53-55) and the tuple return (:57), which the caller destructures straight into its two fields.
  • -
  • Why it's built this way: keeping this out of the page makes it a plain static function over data, directly unit-testable without a renderer, and it keeps the page's own code down to one line (PublicSessionList.razor.cs:154-155). [Rubric §14, Testability] and [Rubric §1, SOLID].
  • -
  • Where it's used: IndexNames is called once per events load (PublicSessionList.razor.cs:142); Scope is called by that page's RefreshRoomOptions (:151-152), from the initial load (:147) and from every event-filter change (:216). The options it returns are passed down to PublicSessionListFilterBar's Rooms parameter and the surviving id to its SelectedRoomId.
  • +
  • Why it's built this way: keeping this out of the page makes it a plain static function over data, directly unit-testable without a renderer, and it keeps the page's own code down to one line (PublicSessionList.razor.cs:168-169). [Rubric §14, Testability] and [Rubric §1, SOLID].
  • +
  • Where it's used: IndexNames is called once per events load (PublicSessionList.razor.cs:136); Scope is called by that page's RefreshRoomOptions (:151-152), from the initial load (:147) and from every event-filter change (:216). The options it returns are passed down to PublicSessionListFilterBar's Rooms parameter and the surviving id to its SelectedRoomId.
  • ADCHome

    @@ -1110,36 +1143,36 @@

    ADCHome

    • What it is: the conference landing page: hero with a live countdown and a ticketing button, pre-conference workshops, keynote, track catalogue, sponsor strip with a sponsorship call to action, a partners band, and venue block. It loads the published events through the UI service layer to find which event to feature, classifies that event as Upcoming/Live/Ended, loads that event's sponsors and partners, and renders the rest from the compiled-in editorial content in ADCHomeContent. It is shared verbatim by the Web and MAUI heads, and its class doc records that both heads serve the static images from their own site root, so neither overrides ImageBasePath today (:9-16).

    • -
    • Depends on: IEventUIService (:51-52), ISponsorUIService (:54-55), and IPartnerUIService (:57-58) for the three reads, EventDTO for the featured event (:80), SponsorDTO and PartnerDTO for the grouped strips (:82-83, :85-86), EventPhase (a private inner enum, :90-95), ADCHomeContent with its KeynoteSpeakerInfo, ConferenceTrackInfo, and PreConferenceWorkshopInfo records, CurrentEventSelector from MMCA.ADC.Conference.Shared.Events, SponsorTier and PartnerType, and ConferenceRoutePaths for the "see all sponsors" link (ADCHome.razor:308). Externals: IStringLocalizer<ADCHome> injected in the markup as L (ADCHome.razor:1), System.Threading.Timer, TimeZoneInfo, MudBlazor, and the Blazor RendererInfo API. It composes one first-party child component, HomeCountdown (ADCHome.razor:40), which lives in the same folder as a single .razor file with no code-behind.

      +
    • Depends on: IEventUIService (:51-52), ISponsorUIService (:54-55), and IPartnerUIService (:57-58) for the three reads, an injected TimeProvider (:60-61) as the page's only clock, EventDTO for the featured event (:83), SponsorDTO and PartnerDTO for the grouped strips (:85-86, :88-89), EventPhase (a private inner enum, :93-98), ADCHomeContent with its KeynoteSpeakerInfo, ConferenceTrackInfo, and PreConferenceWorkshopInfo records, CurrentEventSelector from MMCA.ADC.Conference.Shared.Events, SponsorTier and PartnerType, and ConferenceRoutePaths for the "see all sponsors" link (ADCHome.razor:308). Externals: IStringLocalizer<ADCHome> injected in the markup as L (ADCHome.razor:1), System.Threading.Timer, TimeZoneInfo, MudBlazor, and the Blazor RendererInfo API. It composes one first-party child component, HomeCountdown (ADCHome.razor:40), which lives in the same folder as a single .razor file with no code-behind.

    • Concept introduced: rendering correctly across the prerender and interactive passes. [Rubric §23, Front-End Performance and Rendering] assesses whether a page avoids wasted renders and blocking work; this component is the chapter's clearest case study, and both of its decisions are recorded in the code comments. See ADR-056 for the render-mode strategy these two decisions live inside.

        -
      • Skip the fetch during prerender. OnInitializedAsync checks RendererInfo.IsInteractive and, when false, sets _isLoading = false, computes the countdown from defaults, and returns without touching the network (:134-139). The comment (:130-133) states why: an untimed server-side call to a cold or unreachable backend would block the prerender, and therefore the page load and the post-login NavigateTo("/"), indefinitely. The static fallback renders immediately and the interactive pass loads the real event. Prerender is a one-shot static render, so the timer is moot there. [Rubric §29, Resilience and Business Continuity] is the same point from the availability angle.
      • -
      • Fence the per-second re-render. The ticking digits live in the HomeCountdown child, which owns its own timer, so this page arms only a single one-shot Timer for the Live-to-Ended flip (:161-177). The comment records the alternative: a 1-second timer would re-render the entire landing page, the largest static page in the app, for the whole event, per circuit, just to catch one transition. The child goes further still: it ticks once a minute while more than 65 minutes remain and switches to once a second only for the final hour (MMCA.ADC.Conference.UI/Pages/Home/HomeCountdown.razor:32, :52-56, :59, :70-74), and it too refuses to start a timer unless RendererInfo.IsInteractive (HomeCountdown.razor:51-52).
      • +
      • Skip the fetch during prerender. OnInitializedAsync checks RendererInfo.IsInteractive and, when false, sets _isLoading = false, computes the countdown from defaults, and returns without touching the network (:137-142). The comment (:132-136) states why: an untimed server-side call to a cold or unreachable backend would block the prerender, and therefore the page load and the post-login NavigateTo("/"), indefinitely. The static fallback renders immediately and the interactive pass loads the real event. Prerender is a one-shot static render, so the timer is moot there. [Rubric §29, Resilience and Business Continuity] is the same point from the availability angle.
      • +
      • Fence the per-second re-render. The ticking digits live in the HomeCountdown child, which owns its own timer, so this page arms only a single one-shot Timer for the Live-to-Ended flip (:161-180). The comment records the alternative: a 1-second timer would re-render the entire landing page, the largest static page in the app, for the whole event, per circuit, just to catch one transition. The child goes further still: it ticks once a minute while more than 65 minutes remain and switches to once a second only for the final hour (MMCA.ADC.Conference.UI/Pages/Home/HomeCountdown.razor:32, :52-56, :59, :70-74), and it too refuses to start a timer unless RendererInfo.IsInteractive (HomeCountdown.razor:51-52).
      -

      Three more rubric threads run through it. [Rubric §22, Responsive and Cross-Browser/Device]: one component compiles into the Blazor Server, WebAssembly, and MAUI heads, with the per-head difference reduced to the ImageBasePath parameter (:63). [Rubric §27, Internationalization]: user-facing chrome resolves through L[...], while the strings that must not be translated carry explicit // i18n: allow markers with reasons (the ticketing URL, the brand name, the postal address). [Rubric §20, Design System and Theming]: the page's scoped stylesheet is a single shared copy rendered by both heads, and an architecture fitness test embeds it and fails the build if it re-hardcodes the brand hex instead of using var(--mmca-primary) (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Ui/BrandColorTokenTests.cs:12-17, embedded via MMCA.ADC.Architecture.Tests.csproj:11-13).

      +

      Four more rubric threads run through it. [Rubric §22, Responsive and Cross-Browser/Device]: one component compiles into the Blazor Server, WebAssembly, and MAUI heads, with the per-head difference reduced to the ImageBasePath parameter (:69). [Rubric §27, Internationalization]: user-facing chrome resolves through L[...], while the strings that must not be translated carry explicit // i18n: allow markers with reasons (the ticketing URL, the brand name, the postal address). [Rubric §20, Design System and Theming]: the page's scoped stylesheet is a single shared copy rendered by both heads, and an architecture fitness test embeds it and fails the build if it re-hardcodes the brand hex instead of using var(--mmca-primary) (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Ui/BrandColorTokenTests.cs:12-17, embedded via MMCA.ADC.Architecture.Tests.csproj:11-13). [Rubric §14, Testability]: every read of "now" (featured-event selection :207, the end-of-event timer :172, the phase switch :336) goes through the injected TimeProvider rather than DateTime.UtcNow, so the event choice and the phase share one clock that a test host can replace.

    • Walkthrough, in lifecycle order:

        -
      • Constants and state (:24-88): the PreConferenceTicketingUrl constant placed first because SA1203 requires constants before fields, with its doc block and S1075 suppression (:24-36), then MaxRoster (:38-42), a fixed upper bound of 200 on one event's sponsor or partner roster passed explicitly to the paged reads because the underlying endpoints default to 10 rows per page and a conference announces dozens, not thousands. EventStartTime (:44) and FallbackStartDate (:46-49) are unchanged. The three injected services (:51-58) replace what used to be a single IHttpClientFactory. Then a CancellationTokenSource, the one-shot _phaseTimer, the computed _startUtc/_endUtc, _phase, the nullable _event (now EventDTO?, :80), the grouped _sponsorTiers over SponsorDTO (:82-83) and _partnerGroups over PartnerDTO (:85-86), _isLoading (starting true), and a _disposed guard the timer callback checks.
      • -
      • Derived display properties (:97-104): EventName, EventDescription, VenueAddress, and MapSearchUrl are each _event?.X ?? <fallback>, so the page is fully renderable before and without a successful fetch. MapSearchUrl builds a Google Maps search URL with Uri.EscapeDataString over the address.
      • -
      • HeroTitleParts() (:106-123): splits the event name so the hero can accent the keyword between "Atlanta " and " Conference" (in "2026 Atlanta Developers Conference" it accents "Developers"). It uses IndexOf/LastIndexOf with StringComparison.Ordinal and falls back to rendering the whole name plain when the name does not match the brand shape, which is why an arbitrary event name never renders broken markup; the markup branches on the accent being non-empty (ADCHome.razor:19-26).
      • -
      • OnInitializedAsync (:125-148): creates the CTS, takes the prerender short-circuit described above, otherwise awaits LoadEventAsync(), LoadSponsorsAsync(), then LoadPartnersAsync() in sequence (both the sponsor and partner calls need the featured event id) and arms the phase timer.
      • -
      • LoadEventAsync (:190-216): calls EventService.GetAllAsync(cancellationToken: _cts!.Token) (:195) and, on a successful Result, picks the event with CurrentEventSelector.SelectCurrentOrNext(...) passing three accessor lambdas plus DateTime.UtcNow (:199-204). The comment is the reason it is not a FirstOrDefault: the anonymous endpoint returns published events unordered, so a naive first-item pick would pin the oldest seeded event. A failed read (API unavailable) simply leaves the fallback defaults in place; only OperationCanceledException is caught explicitly, meaning the component was disposed mid-load (:207-210), because there is no HttpRequestException to catch any more, the failure now surfaces as an unsuccessful Result handled by the if rather than a thrown exception. The finally block always clears _isLoading and recomputes the countdown (:211-215), so no failure path leaves a spinner on screen.
      • -
      • LoadSponsorsAsync (:218-264): returns immediately when no event was featured (:230-233), then calls SponsorService.GetPagedAsync filtered to the featured event, pageNumber: 1, pageSize: MaxRoster, sorted by Sort ascending (:237-243), and on a successful page reduces sponsors.Items to the tier-grouped list described in ADCHome's own walkthrough of that shape (:245-258). The filtering by event moved server-side (the comment records that the endpoint now scopes to the featured event the way PublicSponsorList does), so the client-side code no longer does its own Where(s => s.EventId == ...). Its one catch arm is OperationCanceledException (:260-263) and, on any unsuccessful page, _sponsorTiers simply stays at its previous value, which is a supported render state rather than an error state.
      • -
      • LoadPartnersAsync (:266-309): the same shape as LoadSponsorsAsync one method up: returns immediately with no featured event (:276-279), otherwise calls PartnerService.GetPagedAsync the same way (:283-289) and reduces a successful page's partners.Items to the type-grouped list (:291-303). Its one catch arm mirrors the sponsor load (:305-308) and an unsuccessful page leaves _partnerGroups empty, which hides the whole partners band rather than rendering a partial one.
      • -
      • EventFilter (:311-315): a small static helper that builds the one-entry {"EventId": ("equals", eventId)} filter dictionary both GetPagedAsync calls pass, formatting the id with CultureInfo.InvariantCulture so the filter value is stable regardless of the host's culture.
      • -
      • UpdateCountdown (:317-...): converts the event's local start and end into UTC using TimeZoneInfo.FindSystemTimeZoneById(timeZoneId) with "America/New_York" as the default, calling CurrentEventSelector.ToUtc rather than ConvertTimeToUtc because the midnight end boundary does not exist in zones that transition at 00:00 and a raw conversion would throw out of the render path (MMCA.ADC.Conference.Shared/Events/CurrentEventSelector.cs:89-94). _phase is then assigned from the switch described under EventPhase.
      • -
      • Phase timing: OnCountdownElapsedAsync is the EventCallback the HomeCountdown child raises at zero (HomeCountdown.razor:82), which recomputes the phase, re-arms, and calls InvokeAsync(StateHasChanged). ArmPhaseTimerForEventEnd returns unless the phase is Live and the remaining time is positive, then disposes any prior timer and schedules one callback at untilEnd with Timeout.InfiniteTimeSpan as the period, meaning fire once and never repeat. OnEventEnded checks _disposed before re-rendering.
      • -
      • FormatEventDate: formats the date with a pattern read from a resource (L["Hero.DateFormat"]) against CultureInfo.CurrentCulture, so both the layout and the month names follow the selected language (ADR-027).
      • -
      • Dispose: sets _disposed, cancels and disposes the CTS, and both stops (Change(-1, -1)) and disposes the phase timer. Stopping before disposing is what prevents a callback already in flight from touching a torn-down component.
      • +
      • Constants and state (:24-91): the PreConferenceTicketingUrl constant placed first because SA1203 requires constants before fields, with its doc block and S1075 suppression (:24-36), then MaxRoster (:38-42), a fixed upper bound of 200 on one event's sponsor or partner roster passed explicitly to the paged reads because the underlying endpoints default to 10 rows per page and a conference announces dozens, not thousands. EventStartTime (:44) and FallbackStartDate (:46-49) follow. Then the three injected UI services (:51-58), the injected TimeProvider (:60-61), the ImageBasePath parameter (:63-69) and the derived KeynoteImageSrc (:75-76), a CancellationTokenSource, the one-shot _phaseTimer, the computed _startUtc/_endUtc, _phase, the nullable _event (EventDTO?, :83), the grouped _sponsorTiers over SponsorDTO (:85-86) and _partnerGroups over PartnerDTO (:88-89), _isLoading (starting true), and a _disposed guard the timer callback checks (:91).
      • +
      • Derived display properties (:100-107): EventName, EventDescription, VenueAddress, and MapSearchUrl are each _event?.X ?? <fallback>, so the page is fully renderable before and without a successful fetch. MapSearchUrl builds a Google Maps search URL with Uri.EscapeDataString over the address.
      • +
      • HeroTitleParts() (:109-126): splits the event name so the hero can accent the keyword between "Atlanta " and " Conference" (in "2026 Atlanta Developers Conference" it accents "Developers"). It uses IndexOf/LastIndexOf with StringComparison.Ordinal and falls back to rendering the whole name plain when the name does not match the brand shape, which is why an arbitrary event name never renders broken markup; the markup branches on the accent being non-empty (ADCHome.razor:19-26).
      • +
      • OnInitializedAsync (:128-151): creates the CTS, takes the prerender short-circuit described above, otherwise awaits LoadEventAsync(), LoadSponsorsAsync(), then LoadPartnersAsync() in sequence (both the sponsor and partner calls need the featured event id) and arms the phase timer.
      • +
      • LoadEventAsync (:193-219): calls EventService.GetAllAsync(cancellationToken: _cts!.Token) (:198) and, on a successful Result, picks the event with CurrentEventSelector.SelectCurrentOrNext(...) passing three accessor lambdas plus TimeProvider.GetUtcNow().UtcDateTime (:202-207). The comment is the reason it is not a FirstOrDefault: the anonymous endpoint returns published events unordered, so a naive first-item pick would pin the oldest seeded event. A failed read (API unavailable) surfaces as an unsuccessful Result handled by the if, which simply leaves the fallback defaults in place; the only exception caught is OperationCanceledException, meaning the component was disposed mid-load (:210-213). The finally block always clears _isLoading and recomputes the countdown (:214-218), so no failure path leaves a spinner on screen.
      • +
      • LoadSponsorsAsync (:221-267): returns immediately when no event was featured (:233-236), then calls SponsorService.GetPagedAsync filtered to the featured event, pageNumber: 1, pageSize: MaxRoster, sorted by Sort ascending (:240-246), and on a successful page reduces sponsors.Items to a tier-grouped list: tiers ascending in package order (Platinum first), each tier ordered by Sort then Name (:248-261, comment at :250-251). The event scoping happens server-side; the method's remarks record that the endpoint filters to the featured event the way PublicSponsorList does, so a second published edition's sponsors never bleed onto this page (:224-230). Its one catch arm is OperationCanceledException (:263-266) and, on any unsuccessful page, _sponsorTiers simply stays at its previous value, which is a supported render state rather than an error state.
      • +
      • LoadPartnersAsync (:269-312): the same shape as LoadSponsorsAsync one method up: returns immediately with no featured event (:279-282), otherwise calls PartnerService.GetPagedAsync the same way (:286-292) and reduces a successful page's partners.Items to the type-grouped list, Community first (:294-306). Its one catch arm mirrors the sponsor load (:308-311) and an unsuccessful page leaves _partnerGroups empty, which hides the whole partners band rather than rendering a partial one.
      • +
      • EventFilter (:314-318): a small static helper that builds the one-entry {"EventId": ("equals", eventId)} filter dictionary both GetPagedAsync calls pass, formatting the id with CultureInfo.InvariantCulture so the filter value is stable regardless of the host's culture.
      • +
      • UpdateCountdown (:320-343): converts the event's local start and end into UTC using TimeZoneInfo.FindSystemTimeZoneById(timeZoneId) with "America/New_York" as the default (:326, :332), calling CurrentEventSelector.ToUtc rather than ConvertTimeToUtc because the midnight end boundary does not exist in zones that transition at 00:00 and a raw conversion would throw out of the render path (comment :328-331; MMCA.ADC.Conference.Shared/Events/CurrentEventSelector.cs:89-94). now is read from TimeProvider (:336) and _phase is then assigned from the switch described under EventPhase (:337-342).
      • +
      • Phase timing: OnCountdownElapsedAsync (:153-159) is the EventCallback the HomeCountdown child raises at zero (HomeCountdown.razor:82), which recomputes the phase, re-arms, and calls InvokeAsync(StateHasChanged). ArmPhaseTimerForEventEnd (:165-180) returns unless the phase is Live and the remaining time, measured against TimeProvider.GetUtcNow() (:172), is positive, then disposes any prior timer and schedules one callback at untilEnd with Timeout.InfiniteTimeSpan as the period, meaning fire once and never repeat (:179). OnEventEnded (:182-191) checks _disposed before re-rendering.
      • +
      • FormatEventDate (:345-352): formats the date with a pattern read from a resource (L["Hero.DateFormat"]) against CultureInfo.CurrentCulture, so both the layout and the month names follow the selected language (ADR-027).
      • +
      • Dispose (:354-361): sets _disposed, cancels and disposes the CTS, and both stops (Change(-1, -1)) and disposes the phase timer. Stopping before disposing is what prevents a callback already in flight from touching a torn-down component.
    • -
    • Why it's built this way: the landing page is the app's most-hit surface and the post-login destination, so its correctness budget is dominated by two failure modes that have nothing to do with its content: a slow backend blocking the prerender, and a per-second render loop multiplied by every connected circuit. Both are solved structurally (skip the fetch, fence the tick) rather than by tuning, and every dynamic block has a defined empty state, so the page is never blank. Routing the three reads through IEventUIService, ISponsorUIService, and IPartnerUIService instead of a raw IHttpClientFactory call moves the event-scoping filter server-side and lets the page consume the same Result-based service layer every other page in the group uses, rather than a bespoke GetFromJsonAsync plus private wire-model records. The two conditional calls to action follow the same discipline: the hero ticketing button and the sponsorship packet block each render only when the featured event publishes the corresponding URL, and hide entirely otherwise rather than offering a dead link, which the markup comments state at both sites.

      +
    • Why it's built this way: the landing page is the app's most-hit surface and the post-login destination, so its correctness budget is dominated by two failure modes that have nothing to do with its content: a slow backend blocking the prerender, and a per-second render loop multiplied by every connected circuit. Both are solved structurally (skip the fetch, fence the tick) rather than by tuning, and every dynamic block has a defined empty state, so the page is never blank. Routing the three reads through IEventUIService, ISponsorUIService, and IPartnerUIService keeps the event-scoping filter server-side and lets the page consume the same Result-based service layer every other page in the group uses. The two conditional calls to action follow the same discipline: the hero ticketing button and the sponsorship packet block each render only when the featured event publishes the corresponding URL, and hide entirely otherwise rather than offering a dead link, which the markup comments state at both sites.

    • -
    • Where it's used: resolved as the home component by each head's ADCHomePageContent. The Web client points ComponentType straight at this shared component (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Pages/ADCHomePageContent.cs:13); the MAUI head points at a thin local wrapper page that renders <ADCHome /> with no parameters (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/Pages/ADCHomePageContent.cs:10, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/Pages/ADCHome.razor:6). [Rubric §28, Front-End Testing]: the page has no bUnit test, but two suites hold it to account from outside, the brand-token fitness test above and the E2E pseudo-localization sentinel, which probes this page's Location.OpenInMaps resource and settles on the always-rendered .location-section, precisely because that button is static markup rather than event-load-gated (MMCA.ADC/Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/PseudoLocalizationTests.cs:31-44).

      +
    • Where it's used: resolved as the home component by each head's ADCHomePageContent. The Web client points ComponentType straight at this shared component (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Pages/ADCHomePageContent.cs:13); the MAUI head points at a thin local wrapper page that renders <ADCHome /> with no parameters (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/Pages/ADCHomePageContent.cs:10, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/Pages/ADCHome.razor:6). [Rubric §28, Front-End Testing]: a bUnit suite renders the page directly (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.UI.Tests/Pages/Home/ADCHomeTests.cs), covering the hero eyebrow and title class, the workshop cards and their links, the ticketing call to action with and without a URL (:163, :220), and the partners band ordering and empty state (:256, :306). Two suites hold it to account from outside as well: the brand-token fitness test above and the E2E pseudo-localization sentinel, which probes this page's Location.OpenInMaps resource and settles on the always-rendered .location-section, precisely because that button is static markup rather than event-load-gated (MMCA.ADC/Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/PseudoLocalizationTests.cs:31-44).

    • -
    • Caveats / not-in-source: the page's own countdown window is not identical to the selector's. UpdateCountdown starts the event at EventStartTime = 08:00 local (:44), while CurrentEventSelector starts its live window at midnight local (MMCA.ADC.Conference.Shared/Events/CurrentEventSelector.cs:71). Both end at midnight after the last day (CurrentEventSelector.cs:70). So between midnight and 08:00 on day one, the selector already treats the event as live while the hero still shows a countdown. Whether that is intended is not determinable from source. Also note the two hard-coded fallbacks used when no event loads: the date 2026-10-17 (:46-49), whose comment warns it must track the published event date or the hero date and countdown visibly jump once the real event arrives, and the venue address.

      +
    • Caveats / not-in-source: the page's own countdown window is not identical to the selector's. UpdateCountdown starts the event at EventStartTime = 08:00 local (:44, applied at :324), while CurrentEventSelector starts its live window at midnight local (MMCA.ADC.Conference.Shared/Events/CurrentEventSelector.cs:71). Both end at midnight after the last day (CurrentEventSelector.cs:70; :325 here). So between midnight and 08:00 on day one, the selector already treats the event as live while the hero still shows a countdown. Whether that is intended is not determinable from source. Also note the two hard-coded fallbacks used when no event loads: the date 2026-10-17 (:46-49), whose comment warns it must track the published event date or the hero date and countdown visibly jump once the real event arrives, and the venue address (:104).

    ScorePollSignal

    @@ -1187,9 +1220,9 @@

    ScorePollHost

  • What it is: the five-callback contract a ScorePollSession uses to reach back into the page that started it. The polling session owns no rendering: everything it needs from the component (the current load generation, the poll cadence, applying a board, re-rendering, finishing with a message) arrives as a delegate in this record.
  • Depends on: SessionSelectionDashboardDTO (the payload of ApplyFreshDashboard) and ToastSeverity (the severity of the finish message), plus BCL Func<>, Action<>, Task, and TimeSpan. It is a positional record with no body.
  • Concept introduced: the callback record as an inverted UI port. [Rubric §1, SOLID] assesses dependency inversion and interface segregation: rather than handing the loop a reference to the whole component (which would let it touch any field and would make it untestable outside a renderer), the component passes exactly the five capabilities the loop needs, each as its own delegate. [Rubric §19, State Management] is the reason this shape exists at all: two of the five members are readers, not values. CurrentGeneration and PollInterval are Func<> rather than captured snapshots so the loop re-reads the page's live state on every tick, which is what lets an event switch supersede a running session mid-flight and lets a bUnit test shrink the cadence after the loop has already started.
  • -
  • Walkthrough: CurrentGeneration (ScorePollSession.cs:20) reads the page's _loadGeneration counter, the value the loop compares its own generation against to decide it has been superseded. PollInterval (:21) reads the page's cadence, re-read every tick (:15). ApplyFreshDashboard (:22) hands a freshly polled SessionSelectionDashboardDTO to the page, which swaps it in and recomputes the derived state. RenderAsync (:23) is the component's InvokeAsync(StateHasChanged), so the re-render is marshalled onto the renderer's synchronization context from whatever thread the loop resumed on. FinishScoring (:24) ends the scoring session and surfaces the outcome as a toast, taking the message and a ToastSeverity.
  • +
  • Walkthrough: CurrentGeneration (ScorePollSession.cs:20) reads the page's _loadGeneration counter, the value the loop compares its own generation against to decide it has been superseded. PollInterval (:21) reads the page's cadence, re-read every tick (:15). ApplyFreshDashboard (:22) is an Action<SessionSelectionDashboardDTO, int>: it hands a freshly polled SessionSelectionDashboardDTO to the page together with the number of scores the current run has written so far (:16), and the page swaps the board in, stores that count for the progress display, and recomputes the derived state. The count travels separately because a rescore rewrites each session's score in place, so the board's own score list cannot measure the run's progress. RenderAsync (:23) is the component's InvokeAsync(StateHasChanged), so the re-render is marshalled onto the renderer's synchronization context from whatever thread the loop resumed on. FinishScoring (:24) ends the scoring session and surfaces the outcome as a toast, taking the message and a ToastSeverity.
  • Why it's built this way: the doc comment states the rule (:9-13): the loop raises side effects, the page performs them, so StateHasChanged, the toasts, and the generation counter that supersedes a stale session all stay on the component. Making the port a record of delegates instead of an interface keeps the wiring to one expression at the call site and avoids a second implementation existing only for tests.
  • -
  • Where it's used: constructed inline by SessionSelectionDashboard.PollForScoresAsync (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Sessions/Selection/SessionSelectionDashboard.razor.cs:226-231) and consumed throughout ScorePollSession.
  • +
  • Where it's used: constructed inline by SessionSelectionDashboard.PollForScoresAsync (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Sessions/Selection/SessionSelectionDashboard.razor.cs:233-238) and consumed throughout ScorePollSession.
  • SessionSelectionAiScores

    @@ -1198,10 +1231,10 @@

    SessionSelectionAiScores

    • What it is: the presentational AI-scores section of the selection dashboard. It renders the "Score Sessions with AI" action with its in-progress state and the per-session score table, narrowed by the parent's active filters. The scoring flow itself stays on the containing page and is triggered upward through an EventCallback.
    • Depends on: SessionSelectionDashboardDTO and its SessionAiScoreDTO rows (imported at SessionSelectionAiScores.razor.cs:2), SessionSelectionDisplay for the shared predicates, and the Blazor [Parameter] and EventCallback infrastructure from Microsoft.AspNetCore.Components (external, :1).
    • -
    • Concept introduced: lifting the action up via EventCallback. [Rubric §19, State Management] favors child components that raise intent rather than own the operation; here the child never calls a service. The scoring trigger is exposed as [Parameter] public EventCallback ScoreRequested (:16) alongside an IsScoring flag the parent flips (:15), so the long-running scoring loop, its cancellation, and its toasts all live in SessionSelectionDashboard while this section only shows the button and the progress state. Like its sibling it is otherwise a pure function of its parameters (:14-21): the whole Dashboard DTO plus the same five filter strings, each defaulting to empty.
    • -
    • Walkthrough: HasActiveFilters (:23-26) is a cheap short-circuit over the five filter strings. FilteredAiScores (:28-40) returns an empty list when the board has no scores yet (:32-33), returns Dashboard.AiScores untouched when no filter is active (:35-36), and otherwise materializes ApplyAiScoreFilters into an array with a collection expression (:38). ApplyAiScoreFilters (:42-64) is a straight pipeline of Where clauses over the flat score rows: status, now a single call to SessionSelectionDisplay.SessionMatchesStatus (:48-49) that folds what used to be an inline null-equals-Accepted ternary; locality against the row's SpeakerLocalities collection (:51-52); category against SessionCategories (:54-55); level as a case-insensitive equality on SessionLevel (:57-58); and score tier via ScoreMatchesFilter on OverallScore (:60-61). The markup colors each row's status and score chip through SessionSelectionDisplay and SessionStatusDisplay (.../SessionSelectionAiScores.razor:72 and :76).
    • +
    • Concept introduced: lifting the action up via EventCallback. [Rubric §19, State Management] favors child components that raise intent rather than own the operation; here the child never calls a service. The scoring trigger is exposed as [Parameter] public EventCallback ScoreRequested (:24) alongside an IsScoring flag the parent flips (:15) and a ScoredThisRun count the parent feeds from the polling run (:22), so the long-running scoring loop, its cancellation, and its toasts all live in SessionSelectionDashboard while this section only shows the button and the progress state. Like its sibling it is otherwise a pure function of its parameters (:14-29): the whole Dashboard DTO, the scoring flag and count, plus the same five filter strings, each defaulting to empty.
    • +
    • Walkthrough: ScoredThisRun (:22) carries its reason in its doc comment (:17-21): a rescore rewrites each session's score in place, so Dashboard.AiScores still holds every previous score while the run is in flight, and only this count measures the run's progress. ScoredShown (:31) clamps the count to Dashboard.TotalSessions, and ScoringPercent (:33-34) turns it into a percentage, guarding a zero total. The markup's in-progress alert renders both: the "N / total sessions scored" line and a named progress bar whose share also reads as text (.../SessionSelectionAiScores.razor:20-39). HasActiveFilters (:36-39) is a cheap short-circuit over the five filter strings. FilteredAiScores (:41-53) returns an empty list when the board has no scores yet (:45-46), returns Dashboard.AiScores untouched when no filter is active (:48-49), and otherwise materializes ApplyAiScoreFilters into an array with a collection expression (:51). ApplyAiScoreFilters (:55-77) is a straight pipeline of Where clauses over the flat score rows: status through a single call to SessionSelectionDisplay.SessionMatchesStatus (:59-62); locality against the row's SpeakerLocalities collection (:64-65); category against SessionCategories (:67-68); level as a case-insensitive equality on SessionLevel (:70-71); and score tier via ScoreMatchesFilter on OverallScore (:73-74). The markup colors each row's status and score chip through SessionStatusDisplay and SessionSelectionDisplay (.../SessionSelectionAiScores.razor:78 and :82).
    • Why it's built this way: the score table is a flat DTO list, so its filter pipeline is simpler than the speaker section's nested rebuild; sharing SessionSelectionDisplay keeps the two sections' notion of "matches this filter" identical even though their data shapes differ. Keeping the pipeline lazy until one final materialization avoids an intermediate array per filter stage ([Rubric §23, Front-End Performance]).
    • -
    • Where it's used: rendered inside SessionSelectionDashboard's markup (.../SessionSelectionDashboard.razor:228-235), where its ScoreRequested callback invokes the page's ScoreSessionsAsync (.../SessionSelectionDashboard.razor.cs:161); covered by SessionSelectionAiScoresTests in the Conference UI bUnit tier (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.UI.Tests/Pages/SessionSelection/SessionSelectionAiScoresTests.cs).
    • +
    • Where it's used: rendered inside SessionSelectionDashboard's markup (.../SessionSelectionDashboard.razor:241-249), which binds ScoredThisRun to the page's _scoredThisRun (:243) and its ScoreRequested callback to the page's ScoreSessionsAsync (.../SessionSelectionDashboard.razor.cs:164); covered by SessionSelectionAiScoresTests in the Conference UI bUnit tier (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.UI.Tests/Pages/Sessions/Selection/SessionSelectionAiScoresTests.cs, which sets ScoredThisRun at :129).

    [Rubric §16, AI-Native Application Architecture] applies: this type is part of the AI session-scoring feature (a model call behind a port, versioned prompt and model, an evaluation gate, metered spend; ADR-111).

    SessionSelectionFilterOptions

    @@ -1224,10 +1257,10 @@

    ScorePollSession

    • What it is: one fire-and-forget AI-score polling run for the selection dashboard. It drives ScorePollTracker (the pure counters) over repeated dashboard fetches and turns each resulting ScorePollSignal into the page's UI side effects through a ScorePollHost.
    • Depends on: ISessionSelectionUIService for the fetch, IStringLocalizer for the outcome messages, and a ScorePollHost for the callbacks, all primary-constructor parameters (ScorePollSession.cs:36-39). Internally it uses ScorePollTracker, ScorePollSignal, SessionSelectionDashboardDTO's SessionAiScoreDTO rows, and ToastSeverity; externally Task.Delay, CancellationToken, and the Conference EventIdentifierType/SessionIdentifierType aliases.
    • -
    • Concept introduced: the generation guard against a superseded async loop. [Rubric §19, State Management] asks whether asynchronous work can corrupt state it no longer owns. A polling run belongs to the event that was selected when scoring started; if the organizer switches events, the page bumps a generation counter and this loop, which re-reads that counter through host.CurrentGeneration(), exits instead of painting the previous event's board (and its toasts) over the new selection. Note the counter is checked twice per tick, once after the delay and once after the fetch (:69 and :75), because both are await points where the selection can move. [Rubric §29, Resilience and Business Continuity] covers the second concept here: because ISessionSelectionUIService answers every non-2xx response and transport fault with a failed Result rather than an exception (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Sessions/Selection/ISessionSelectionUIService.cs:11-23), the consecutive-failure budget has to be fed from the result path, and the code comment says exactly why (:80-84): left on the exception path it would be dead code and a persistently failing endpoint would poll silently to MaxPolls without ever reporting.
    • -
    • Walkthrough: RunAsync(eventId, baseline, generation, cancellationToken) (:53-115) takes a new baseline parameter (each already-scored session's ScoredOn before the run started) so progress is measured as scores this run wrote, not the raw dashboard count. It creates a tracker (:59) and loops up to ScorePollTracker.MaxPolls times (:61). Each iteration awaits host.PollInterval() under the page's disposal token (:65), checks the generation (:66-69), fetches the board through selectionService.GetDashboardAsync (:71), and checks the generation again (:72-75). A failed Result feeds tracker.RegisterFailure(), and when the budget is exhausted the run reports Snackbar.ScoringPollFailed as an error and returns (:85-89); a failure inside budget falls through to continue because TryGetValue yields nothing (:91-94). A successful fetch resets the failure counter (:96), computes scoredThisRun via ScorePollTracker.CountScoredSince(fresh.AiScores, baseline) (:97), and passes it (not the raw count) to both tracker.RegisterFetch and HandleSignalAsync; a true return means the run is over and the finish callback has already fired (:98-101). OperationCanceledException exits quietly, which is component disposal (:103-107). Falling out of the loop means the cap was reached, so, unless the run has meanwhile been superseded (:110-113), it reports Snackbar.ScoringTimedOut as a warning (:114). HandleSignalAsync(fresh, scoredThisRun, signal) (:125-153) is the effects half of the split introduced under ScorePollSignal: GaveUpNoScores finishes with an error (:129-131), Progressed applies the board and re-renders but keeps going (:133-136), CompletedAll applies, re-renders, and finishes with a success message now carrying scoredThisRun rather than the raw score count (:138-142), CompletedStable applies and defers to FinishStable(fresh, scoredThisRun) (:144-147), and Continue does nothing (:149-151). FinishStable(fresh, currentCount) (:156-166, taking the caller-computed count instead of re-reading fresh.AiScores.Count) compares it against TotalSessions and reports either Snackbar.ScoringPartial with the missed count as a warning or Snackbar.ScoringComplete as a success.
    • +
    • Concept introduced: the generation guard against a superseded async loop. [Rubric §19, State Management] asks whether asynchronous work can corrupt state it no longer owns. A polling run belongs to the event that was selected when scoring started; if the organizer switches events, the page bumps a generation counter and this loop, which re-reads that counter through host.CurrentGeneration(), exits instead of painting the previous event's board (and its toasts) over the new selection. Note the counter is checked twice per tick, once after the delay and once after the fetch (:64-67 and :70-73), because both are await points where the selection can move. [Rubric §29, Resilience and Business Continuity] covers the second concept here: because ISessionSelectionUIService answers every non-2xx response and transport fault with a failed Result rather than an exception (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Sessions/Selection/ISessionSelectionUIService.cs:11-23), the consecutive-failure budget has to be fed from the result path, and the code comment says exactly why (:75-79): left on the exception path it would be dead code and a persistently failing endpoint would poll silently to MaxPolls without ever reporting.
    • +
    • Walkthrough: RunAsync(eventId, baseline, generation, cancellationToken) (:51-111) takes a baseline (each already-scored session's ScoredOn before the run started, documented at :48) so progress is measured as scores this run wrote, not the raw dashboard count. It creates a tracker (:57) and loops up to ScorePollTracker.MaxPolls times (:59). Each iteration awaits host.PollInterval() under the page's disposal token (:63), checks the generation (:64-67), fetches the board through selectionService.GetDashboardAsync (:69), and checks the generation again (:70-73). A failed Result feeds tracker.RegisterFailure(), and when the budget is exhausted the run reports Snackbar.ScoringPollFailed as an error and returns (:80-84); a failure inside budget falls through to continue because TryGetValue yields nothing (:86-89). A successful fetch resets the failure counter (:91), computes scoredThisRun via ScorePollTracker.CountScoredSince(fresh.AiScores, baseline) (:92), and passes it (not the raw count) to both tracker.RegisterFetch and HandleSignalAsync; a true return means the run is over and the finish callback has already fired (:93-96). OperationCanceledException exits quietly, which is component disposal (:98-102). Falling out of the loop means the cap was reached, so, unless the run has meanwhile been superseded (:105-108), it reports Snackbar.ScoringTimedOut as a warning (:110). HandleSignalAsync(fresh, scoredThisRun, signal) (:121-149) is the effects half of the split introduced under ScorePollSignal: GaveUpNoScores finishes with an error (:125-127); Progressed calls host.ApplyFreshDashboard(fresh, scoredThisRun) and re-renders but keeps going (:129-132); CompletedAll applies the board with the same count, re-renders, and finishes with a success message carrying scoredThisRun (:134-138); CompletedStable applies the board with the count and defers to FinishStable(fresh, scoredThisRun) (:140-143); and Continue does nothing (:145-147). Every apply passes scoredThisRun through the ScorePollHost callback, which is how the page's progress display learns the run's own count rather than inferring it from a score list that still holds the previous run's rows. FinishStable(fresh, currentCount) (:152-167) compares the caller-computed count against TotalSessions and reports either Snackbar.ScoringPartial with the missed count as a warning or Snackbar.ScoringComplete as a success.
    • Why it's built this way: the class doc states the placement rule (:26-32): the session was extracted from the page code-behind under the rubric §18 line cap, and it stays beside the page rather than moving into ISessionSelectionUIService because every step it takes is a rendering decision, not a data-access one. Every user-visible string is a resource key resolved through the injected localizer, so the outcome messages follow a language switch like the rest of the page (ADR-027, Website/docs-src/adr/027-multi-locale-i18n.md).
    • -
    • Where it's used: constructed and run by SessionSelectionDashboard.PollForScoresAsync, now called with the caller's baseline snapshot (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Sessions/Selection/SessionSelectionDashboard.razor.cs:220). Its supersede behavior is asserted end to end by WhenEventChangesMidScoring_ThePollingSessionStopsWithoutTouchingTheNewBoard (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.UI.Tests/Pages/Sessions/Selection/SessionSelectionStaleResponseTests.cs:217).
    • +
    • Where it's used: constructed and run by SessionSelectionDashboard.PollForScoresAsync, which passes the caller's baseline snapshot to RunAsync (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Sessions/Selection/SessionSelectionDashboard.razor.cs:242). Its supersede behavior is asserted end to end by WhenEventChangesMidScoring_ThePollingSessionStopsWithoutTouchingTheNewBoard (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.UI.Tests/Pages/Sessions/Selection/SessionSelectionStaleResponseTests.cs:217).
    • Caveats / not-in-source: the resource keys used here (Snackbar.ScoringPollFailed, Snackbar.ScoringNoScores, Snackbar.ScoringComplete, Snackbar.ScoringPartial, Snackbar.ScoringTimedOut) resolve against the page's localizer, which the caller supplies; their text lives in SessionSelectionDashboard.resx and SessionSelectionDashboard.es.resx, not in this file.

    SessionSelectionFilters

    @@ -1260,12 +1293,12 @@

    SessionSelectionDashboard

    • What it is: the organizer decision-support page for choosing a conference program. It picks an event (defaulting to the current or next one), loads its decision-support DTO (category distribution, speaker overlap, content similarity, locality breakdown, AI scores), owns the filter state the two child sections read, and starts the asynchronous "score all sessions with AI" flow.
    • -
    • Depends on: ISessionSelectionUIService (loads the board and starts scoring), IEventLookupService (the event picker source), and IToastService, all injected as properties (SessionSelectionDashboard.razor.cs:18-20); SessionSelectionDashboardDTO, EventInfo, CurrentEventSelector, ConferenceRoutePaths, ToastSeverity, and the co-located SessionSelectionFilters, ScorePollSession, and ScorePollHost. It composes SessionSelectionSpeakerOverlap and SessionSelectionAiScores in its markup, and the page is routed and role-gated in the .razor half (@page "/sessions/selection-dashboard" with [Authorize(Roles = "Organizer")], .../SessionSelectionDashboard.razor:1-2).
    • -
    • Concept introduced: the smart (container) component that owns state and lifecycle, and the load-generation counter. This is the counterpart to the two presentational sections above. [Rubric §19, State Management] is fully exercised: the component holds the loaded DTO, the selected event id, the filter object, the _aiScoreLookup, and the loading and error flags (:21-45), and passes what the children need down as parameters. The generation counter _loadGeneration (:38) is the concept worth learning here: any await in a Blazor page is a window in which the user can change the selection, so every asynchronous result must prove it is still wanted before it writes anything. The field's doc comment (:32-37) explains why the generation and not the event id is authoritative: switching away from an event and back must still discard the first response even though both carry the same id. [Rubric §18, UI Architecture] is served by splitting a large page into a container, two presentational children, an extracted filter object, and an extracted polling session. [Rubric §14, Testability] shows in internal TimeSpan PollInterval (:203), documented as internal precisely so bUnit tests can shrink the cadence and exercise the loop quickly (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.UI.Tests/Pages/Sessions/Selection/SessionSelectionDashboardTests.cs:306). [Rubric §27, i18n] applies throughout: every user-visible string resolves through the injected IStringLocalizer<SessionSelectionDashboard> (.../SessionSelectionDashboard.razor:5) against the co-located SessionSelectionDashboard.resx and its .es.resx translation (ADR-027). [Rubric §11, Security] applies with the usual caveat that the Organizer role attribute is a UX gate; the services behind it enforce authorization independently.
    • -
    • Walkthrough: the component implements IDisposable and owns a CancellationTokenSource (:21) that every service call threads through; Dispose cancels and disposes it exactly once via the guarded _disposed pattern (:251-273). OnInitializedAsync (:47-87) builds the three breadcrumbs (:49-54), loads the events, and defaults the picker through CurrentEventSelector.SelectCurrentOrNext (:67-72: live now, else next upcoming, else most recently ended), then loads the board; OperationCanceledException is swallowed as disposal and IsLoading is cleared in the finally (:79-86). OnEventSelectedAsync (:89-105) bumps _loadGeneration before the selection moves (:94), so both the load path and the clear path supersede anything in flight. LoadDashboardAsync (:107-154) is the generation pattern in full: it snapshots the generation and the requested id (:114-115), turns the spinner on, fetches, and then returns early if superseded (:127-128, with the comment noting that a superseded failure must not paint an error banner over a board that loaded fine); on success it stores the board, calls _filters.Reset() and _filters.ComputeOptions(...), and rebuilds the score lookup (:130-136); and even the finally is generation-guarded so a stale response cannot switch off the spinner a newer load just turned on (:149-152). RebuildAiScoreLookup (:156-159) projects the score rows into a SessionId to OverallScore dictionary for the speaker section. ScoreSessionsAsync (:161-205) sets _isScoring, now snapshots a baseline dictionary of each visible score's SessionId to its ScoredOn before clearing the visible scores with a with expression (:165-172, added because the scoring runner rewrites each session's score in place, so progress is only measurable as scores whose timestamp moved, never as the raw count), and calls the service; a failed Result (including the 409 already-running or queue-full conflict) toasts an error and clears the flag, a SessionsScored == -1 result means the server accepted the work asynchronously so it toasts "started" and launches the fire-and-forget PollForScoresAsync(baseline) (:186-189, now carrying the snapshot), and a synchronous result toasts the scored and failed counts and reloads. The closing comment documents why there is no finally here: only the accepted asynchronous path leaves _isScoring set, handing ownership of the flag to the polling task. PollForScoresAsync(baseline) (:213) now takes the caller's baseline snapshot as a parameter and threads it through to session.RunAsync(_selectedEventId!.Value, baseline, _loadGeneration, _cts.Token) (:230); it still builds a ScorePollSession with the service, the localizer, and a ScorePollHost wired to _loadGeneration, PollInterval, ApplyFreshDashboard, InvokeAsync(StateHasChanged), and FinishScoring, runs it, and clears _isScoring in a finally so the Score button always comes back whatever path exits the loop. ApplyFreshDashboard swaps the board in and recomputes the options and the lookup, deliberately without resetting the filters. FinishScoring clears the flag, shows the toast, and requests a re-render.
    • +
    • Depends on: ISessionSelectionUIService (loads the board and starts scoring), IEventLookupService (the event picker source), IToastService, and the BCL TimeProvider (the clock behind the default-event pick), all injected as properties (SessionSelectionDashboard.razor.cs:18-21); SessionSelectionDashboardDTO, EventInfo, CurrentEventSelector, ConferenceRoutePaths, ToastSeverity, and the co-located SessionSelectionFilters, ScorePollSession, and ScorePollHost. It composes SessionSelectionSpeakerOverlap and SessionSelectionAiScores in its markup, and the page is routed and role-gated in the .razor half (@page "/sessions/selection-dashboard" with [Authorize(Roles = "Organizer")], .../SessionSelectionDashboard.razor:1-2).
    • +
    • Concept introduced: the smart (container) component that owns state and lifecycle, and the load-generation counter. This is the counterpart to the two presentational sections above. [Rubric §19, State Management] is fully exercised: the component holds the loaded DTO, the selected event id, the filter object, the _aiScoreLookup, the scoring flag and the current run's score count, and the loading and error flags (:23-48), and passes what the children need down as parameters. The generation counter _loadGeneration (:40) is the concept worth learning here: any await in a Blazor page is a window in which the user can change the selection, so every asynchronous result must prove it is still wanted before it writes anything. The field's doc comment (:34-39) explains why the generation and not the event id is authoritative: switching away from an event and back must still discard the first response even though both carry the same id. [Rubric §18, UI Architecture] is served by splitting a large page into a container, two presentational children, an extracted filter object, and an extracted polling session. [Rubric §14, Testability] shows in internal TimeSpan PollInterval (:218, eight seconds by default), documented as internal precisely so bUnit tests can shrink the cadence and exercise the loop quickly (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.UI.Tests/Pages/Sessions/Selection/SessionSelectionDashboardTests.cs:306). [Rubric §27, i18n] applies throughout: every user-visible string resolves through the injected IStringLocalizer<SessionSelectionDashboard> (.../SessionSelectionDashboard.razor:5) against the co-located SessionSelectionDashboard.resx and its .es.resx translation (ADR-027). [Rubric §11, Security] applies with the usual caveat that the Organizer role attribute is a UX gate; the services behind it enforce authorization independently.
    • +
    • Walkthrough: the component implements IDisposable and owns a CancellationTokenSource (:23) that every service call threads through; Dispose cancels and disposes it exactly once via the guarded _disposed pattern (:267-289). OnInitializedAsync (:50-90) builds the three breadcrumbs (:52-57), loads the events, and defaults the picker through CurrentEventSelector.SelectCurrentOrNext (:70-75: live now, else next upcoming, else most recently ended), passing TimeProvider.GetUtcNow().UtcDateTime as "now" (:75) from the injected clock rather than reading DateTime.UtcNow directly; then it loads the board. OperationCanceledException is swallowed as disposal and IsLoading is cleared in the finally (:82-89). OnEventSelectedAsync (:92-108) bumps _loadGeneration before the selection moves (:97), so both the load path and the clear path supersede anything in flight. LoadDashboardAsync (:110-157) is the generation pattern in full: it snapshots the generation and the requested id (:117-118), turns the spinner on, fetches, and then returns early if superseded (:130-131, with the comment noting that a superseded failure must not paint an error banner over a board that loaded fine); on success it stores the board, calls _filters.Reset() and _filters.ComputeOptions(...), and rebuilds the score lookup (:133-139); and even the finally is generation-guarded so a stale response cannot switch off the spinner a newer load just turned on (:150-156). RebuildAiScoreLookup (:159-162) projects the score rows into a SessionId to OverallScore dictionary for the speaker section. ScoreSessionsAsync (:164-215) sets _isScoring, snapshots a baseline dictionary of each visible score's SessionId to its ScoredOn, and resets _scoredThisRun to zero (:171-177); the comment there gives both reasons: the scoring runner rewrites each session's score in place, so progress is only measurable as scores whose timestamp moved, and the scores stay on screen until the server accepts the run, so a failed start leaves the board exactly as it was. It then calls the service. A failed Result (including the 409 already-running or queue-full conflict) toasts an error and clears the flag; a SessionsScored == -1 result means the server accepted the work asynchronously, so it toasts "started", only now clears the replaced scores from view with a with expression and rebuilds the lookup so the table and the overlap chips empty together, and launches the fire-and-forget PollForScoresAsync(baseline) (:189-199); and a synchronous result toasts the scored and failed counts and reloads. The closing comment documents why there is no finally here: only the accepted asynchronous path leaves _isScoring set, handing ownership of the flag to the polling task. PollForScoresAsync(baseline) (:225-250) builds a ScorePollSession with the service, the localizer, and a ScorePollHost wired to _loadGeneration, PollInterval, ApplyFreshDashboard, InvokeAsync(StateHasChanged), and FinishScoring (:230-238), runs session.RunAsync(_selectedEventId!.Value, baseline, _loadGeneration, _cts.Token) (:242), and clears _isScoring in a finally so the Score button always comes back whatever path exits the loop. ApplyFreshDashboard(fresh, scoredThisRun) (:252-258) swaps the board in, stores the run's count in _scoredThisRun for the SessionSelectionAiScores progress display (bound at .../SessionSelectionDashboard.razor:243), and recomputes the options and the lookup, deliberately without resetting the filters. FinishScoring (:260-265) clears the flag, shows the toast, and requests a re-render.
    • Why it's built this way: AI scoring is a long, failure-prone batch that depends on an external model with variable latency, so the page cannot block on it and there is no server push channel for this surface. Delegating the loop to ScorePollSession and the counting policy to ScorePollTracker leaves this class holding only lifecycle, state, and UI effects, which is what keeps a page with this much behavior inside the rubric §18 size expectation. Cancelling the token on disposal stops the loop from outliving the page, and the generation counter stops it from outliving its event.
    • -
    • Where it's used: the organizer route /sessions/selection-dashboard, reachable from the admin navigation registered by ConferenceUIModule; covered by SessionSelectionDashboardTests and SessionSelectionStaleResponseTests in the Conference UI bUnit tier (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.UI.Tests/Pages/SessionSelection/).
    • -
    • Caveats / not-in-source: the -1 sentinel on SessionsScored is what distinguishes a deferred scoring start from a synchronous one; its meaning is documented on ISessionSelectionUIService (.../Services/ISessionSelectionUIService.cs:15-20) and produced by the server-side handler, not by this component. The model used for scoring, its per-session timeout, and its failure modes are not determinable from this file.
    • +
    • Where it's used: the organizer route /sessions/selection-dashboard, reachable from the admin navigation registered by ConferenceUIModule; covered by SessionSelectionDashboardTests and SessionSelectionStaleResponseTests in the Conference UI bUnit tier (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.UI.Tests/Pages/Sessions/Selection/).
    • +
    • Caveats / not-in-source: the -1 sentinel on SessionsScored is what distinguishes a deferred scoring start from a synchronous one; its meaning is documented on ISessionSelectionUIService (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Sessions/Selection/ISessionSelectionUIService.cs:15-20) and produced by the server-side handler, not by this component. The model used for scoring, its per-session timeout, and its failure modes are not determinable from this file.

    CategoryItemInfo

    @@ -1297,7 +1330,7 @@

    CategoryItemInfo

  • CategoryTitle (line 13): the parent category's title, denormalized onto the item. It is filled in by CategoryItemLookupService from a separate categories request and falls back to string.Empty when the parent title is not found - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Categories/CategoryItemLookupService.cs:78-79).
  • + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Categories/CategoryItemLookupService.cs:80-81).
  • Why it's built this way: the denormalized CategoryTitle exists so a page can render the @@ -1381,10 +1414,10 @@

    ICategoryItemLookupService

    code can pass its own component-scoped token.
  • Why it's built this way: pages depend on this interface, never on the HTTP class, so the two requests the implementation actually makes (categories, then a 10000-page-size items read) stay an - implementation detail (CategoryItemLookupService.cs:22-38).
  • + implementation detail (CategoryItemLookupService.cs:22-42).
  • Where it's used: implemented by CategoryItemLookupService, registered scoped at - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:59 under the + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:60 under the "cross-module lookup services" comment, and injected into SessionLookups (SessionLookups.cs:35), PublicSessionDetail (PublicSessionDetail.razor.cs:27), and @@ -1465,12 +1498,12 @@

    IEventSpeakerUIService

  • Why it's built this way: keeping the four as separate interfaces in one file makes the family obvious to a reader while still giving DI four distinct binding targets - (DependencyInjection.cs:33-36). The implementations are equally uniform: each derives from the + (DependencyInjection.cs:34-37). The implementations are equally uniform: each derives from the shared ChildEntityServiceBase and supplies only its resource root (ChildEntityServices.cs:23,36,49,62).

  • Where it's used: implemented by EventSpeakerService - (ChildEntityServices.cs:22-23) and registered scoped at DependencyInjection.cs:33.

    + (ChildEntityServices.cs:22-23) and registered scoped at DependencyInjection.cs:34.

  • Caveats / not-in-source: unlike its three siblings, this contract has no page consumer in the current source. It is registered and implemented, but no Razor component injects @@ -1495,7 +1528,7 @@

    ISessionCategoryItemUIService

    Task<Result<SessionCategoryItemDTO>> (line 40) and DeleteAsync(sessionId, id, ct) returning Task<Result> (line 41).
  • Where it's used: implemented by SessionCategoryItemService - (ChildEntityServices.cs:48-49), registered at DependencyInjection.cs:35, injected into + (ChildEntityServices.cs:48-49), registered at DependencyInjection.cs:36, injected into SessionDetail (SessionDetail.razor.cs:33), which pairs it with ICategoryItemLookupService, reached through SessionLookups, to offer the untagged items as choices.
  • @@ -1517,7 +1550,7 @@

    ISessionSpeakerUIService

  • Walkthrough: AddAsync(sessionId, speakerId, ct) returning Task<Result<SessionSpeakerDTO>> (line 28) and DeleteAsync(sessionId, id, ct) returning Task<Result> (line 29).
  • Where it's used: implemented by SessionSpeakerService - (ChildEntityServices.cs:35-36), registered at DependencyInjection.cs:34, injected into + (ChildEntityServices.cs:35-36), registered at DependencyInjection.cs:35, injected into SessionDetail (SessionDetail.razor.cs:32).
  • ISpeakerCategoryItemUIService

    @@ -1537,7 +1570,7 @@

    ISpeakerCategoryItemUIService

    Task<Result<SpeakerCategoryItemDTO>> (line 52) and DeleteAsync(speakerId, id, ct) returning Task<Result> (line 53).
  • Where it's used: implemented by SpeakerCategoryItemService - (ChildEntityServices.cs:61-62), registered at DependencyInjection.cs:36, injected into + (ChildEntityServices.cs:61-62), registered at DependencyInjection.cs:37, injected into SpeakerCategoryItemsPanel (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Speakers/SpeakerCategoryItemsPanel.razor.cs:19), the component SpeakerDetail hosts.
  • @@ -1548,8 +1581,9 @@

    CategoryItemLookupService

    • What it is: the implementation behind ICategoryItemLookupService. - It makes two API calls, one for the categories and one for the items, then joins them in memory - into a dictionary of CategoryItemInfo (CategoryItemLookupService.cs:12-83).
    • + It reads the categories in one API call and the items in as many paged calls as the item total + needs, then joins them in memory into a dictionary of CategoryItemInfo + (CategoryItemLookupService.cs:12-86).
    • Depends on: CategoryItemInfo (what it emits), CategoryItemDTO and ConferenceCategoryDTO (the wire shapes it @@ -1558,7 +1592,10 @@

      CategoryItemLookupService

      envelopes those endpoints return), HttpResultExecutor and ProblemDetailsResultReader (the two halves of the - railway conversion), and BCL IHttpClientFactory. Note what it does not take: no token storage + railway conversion), PagedReadAll (the page-until-total + loop and the lookup URL format) and + IdempotentReadRetry (the transient-failure + retry on each GET), and BCL IHttpClientFactory. Note what it does not take: no token storage (CategoryItemLookupService.cs:12), so this is an unauthenticated read, unlike the entity services that derive from AuthenticatedServiceBase.
    • @@ -1566,9 +1603,11 @@

      CategoryItemLookupService

      the front end minimizes round trips for a render, and [Rubric §8, Data Architecture] assesses where denormalization happens. The category-item endpoint returns a CategoryId but not the category's title, and the UI wants the pair. Rather than asking the server for a nested projection or fetching - the parent per item, the client pulls both small collections once and does the join itself, producing - the denormalized CategoryItemInfo. Two round trips total, regardless of how many - items or how many rows the page renders. The second concept here is the two-part railway + the parent per item, the client pulls both collections in full and does the join itself, producing + the denormalized CategoryItemInfo. The cost is one round trip for the categories + plus one per 500 items (PagedReadAll.PageSize, + MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/PagedReadAll.cs:22), independent of + how many rows the page renders. The second concept here is the two-part railway conversion that every lookup in this family uses: ProblemDetailsResultReader converts a response into a Result with the server's original @@ -1579,7 +1618,7 @@

      CategoryItemLookupService

      as an error (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/HttpResultExecutor.cs:11-23). Both halves are what let this method be honestly typed as returning a Result. -
    • Walkthrough: one public method, GetAllAsync(ct) (CategoryItemLookupService.cs:15-43), with the +
    • Walkthrough: one public method, GetAllAsync(ct) (CategoryItemLookupService.cs:15-47), with the whole body wrapped in HttpResultExecutor.ExecuteAsync (:17).
      • Resolves the named "APIClient" HttpClient from the factory in a using (:20), so the per-call handler lifetime is the factory's concern.
      • @@ -1589,34 +1628,51 @@

        CategoryItemLookupService

        (:22-25). This one is the non-paged envelope: the category list is small and unpaged.
      • Short-circuits on failure, re-wrapping the errors under the dictionary's type parameter (:27-31), so a failed first call is never silently treated as "no categories".
      • -
      • BuildCategoryTitles folds the categories into a Dictionary<ConferenceCategoryIdentifierType, string>, null-guarding the collection (:54-67).
      • -
      • GETs categoryitems?includeFKs=false&includeChildren=false&pageSize=10000 into a +
      • BuildCategoryTitles folds the categories into a Dictionary<ConferenceCategoryIdentifierType, string>, null-guarding the collection (:33, :58-71).
      • +
      • Reads every item through PagedReadAll.ReadAllAsync (:37-42), handing it a per-page fetch that + GETs PagedReadAll.LookupPageUrl("categoryitems", pageNumber) into a PagedCollectionResult<CategoryItemDTO> - (:35-38).
      • -
      • Map projects the successful page through BuildLookup (:40-41), which loops the items, - resolves each parent title with TryGetValue falling back to string.Empty, and stores one - CategoryItemInfo per id (:69-83).
      • -
      • The private GetAsync<T> (:45-52) is the shared read step: one GetAsync, one - ProblemDetailsResultReader.ReadAsync<T> (:51).
      • + (:38-41). That URL is the /paged endpoint in Id ascending order, 500 rows a page, with + foreign keys and children excluded (PagedReadAll.cs:43-46). The source comment states why + (:35-36): the GET-all endpoint ignores pageSize and stops silently at the API's maximum page + size, so only paging to the reported total is unbounded. ReadAllAsync stops on an empty page or + once the accumulated count reaches TotalItemCount, and returns the first failing page's errors + unchanged (PagedReadAll.cs:57-90). +
      • Map projects the accumulated List<CategoryItemDTO> through BuildLookup (:44-45), which + loops the items, resolves each parent title with TryGetValue falling back to string.Empty, and + stores one CategoryItemInfo per id (:73-85).
      • +
      • The private GetAsync<T> (:49-56) is the shared read step for both calls: one + IdempotentReadRetry.GetAsync (:54), which re-sends the GET on a transient failure through the + same retry policy AuthenticatedServiceBase + applies (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/IdempotentReadRetry.cs:18-44), + then one ProblemDetailsResultReader.ReadAsync<T> (:55).
    • Why it's built this way: includeFKs=false&includeChildren=false keeps both payloads flat and small, which is the whole point of a lookup fetch, no navigation graphs, only the display fields. The - TryGetValue fallback (:78) means a missing or newly added category degrades to an unprefixed label - instead of throwing mid-render.
    • + stable Id sort means consecutive pages neither skip nor repeat a row (PagedReadAll.cs:30-34). The + retry exists because this service has no base class: without IdempotentReadRetry an anonymous + lookup would get a single attempt, and a GET is safe to re-send. The TryGetValue fallback (:80) + means a missing or newly added category degrades to an unprefixed label instead of throwing + mid-render.
    • Where it's used: registered scoped in AddConferenceUI against - ICategoryItemLookupService (DependencyInjection.cs:59); injected into - SessionDetail (Pages/Session/SessionDetail.razor.cs:26) and - PublicSessionDetail (Pages/Public/PublicSessionDetail.razor.cs:27), taken as - a constructor argument by SessionLookups (Pages/Session/SessionLookups.cs:32), - and composed by SpeakerDetailLookupService as the first of its three - loads (Services/SpeakerDetailLookupService.cs:12,20).
    • -
    • Caveats / not-in-source: the pageSize=10000 request (CategoryItemLookupService.cs:37) is a - hard ceiling; beyond 10,000 category items the lookup would silently drop the remainder, and there is - no follow-on page request. The categories call sends no page size at all (:24), so it depends on - that endpoint returning the full set. Nothing memoizes the result inside this class: each - GetAllAsync call re-issues both requests, and the scoped registration only means one instance per - request or circuit scope, not one fetch. Its sibling + ICategoryItemLookupService (DependencyInjection.cs:60); injected into + SessionDetail (Pages/Sessions/SessionDetail.razor.cs:31, passed on at :58) and + PublicSessionDetail (Pages/Public/Sessions/PublicSessionDetail.razor.cs:27), + taken as a constructor argument by SessionLookups + (Pages/Sessions/SessionLookups.cs:35), and composed by + SpeakerDetailLookupService as the first of its loads + (Services/Speakers/SpeakerDetailLookupService.cs:14,22). Its page-1 URL is also one of the lookup + paths SelfHttpOutputCacheWarmupTask + replays to warm the server's output cache, which is why the URL format is treated as a byte-for-byte + contract (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/SelfHttpOutputCacheWarmupTask.cs:36-41, + PagedReadAll.cs:33-34).
    • +
    • Caveats / not-in-source: the item read is bounded by PagedReadAll's runaway guard of 40 pages + (PagedReadAll.cs:28), so beyond 20,000 category items the lookup would stop without an error. The + categories call sends no page size at all (:24) and is not paged, so it depends on that endpoint + returning the full set. Nothing memoizes the result inside this class: each GetAllAsync call + re-issues every request, and the scoped registration only means one instance per request or circuit + scope, not one fetch. Its sibling EventLookupService does memoize, so the two lookups are not interchangeable on that point.
    @@ -1688,7 +1744,7 @@

    CategoryItemService

    storage (CategoryItemService.cs:10-12), and the same line declares ICategoryItemUIService.
  • Where it's used: picked up by the same assembly scan as its siblings - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:30) and resolved + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:31) and resolved through ICategoryItemUIService in ConferenceCategoryItemsPanel, the category detail page's items editor (Pages/ConferenceCategory/ConferenceCategoryItemsPanel.razor.cs:24).
  • @@ -1715,7 +1771,7 @@

    ConferenceCategoryService

  • Walkthrough: no members; the base call passes "conferencecategories" alongside the factory and token storage (ConferenceCategoryService.cs:10-12).
  • Where it's used: picked up by the same assembly scan as its siblings - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:30) and resolved + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:31) and resolved through IConferenceCategoryUIService in ConferenceCategoryList (Pages/ConferenceCategory/ConferenceCategoryList.razor.cs:16), @@ -1784,7 +1840,7 @@

    EventSpeakerService

    child base keeps the add/remove surface uniform and each payload strongly typed, without the CRUD surface a join row does not need.
  • Where it's used: registered explicitly at - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:33 (join services + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:34 (join services are not IEntityService<,> implementations, so the assembly scan does not see them).
  • Caveats / not-in-source: unlike the CRUD services, none of the ChildEntityServiceBase helpers runs inside @@ -1825,7 +1881,7 @@

    SessionCategoryItemService

    child base keeps the add/remove surface uniform and each payload strongly typed, without the CRUD surface a join row does not need.
  • Where it's used: registered explicitly at - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:35, and injected + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:36, and injected into SessionDetail's "categories on this session" editor (Pages/Session/SessionDetail.razor.cs:28).
  • @@ -1859,7 +1915,7 @@

    SessionSpeakerService

  • Why it's built this way: same rationale as the other join services, a tiny per-link service over the shared child base keeps the add/remove surface uniform and strongly typed.
  • Where it's used: registered explicitly at - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:34, and injected + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:35, and injected into SessionDetail's "speakers on this session" editor (Pages/Session/SessionDetail.razor.cs:27). The rows it creates are also what the server joins on when SpeakerDashboardService filters sessions by the virtual SpeakerId key.
  • @@ -1893,7 +1949,7 @@

    SpeakerCategoryItemService

    four differ only in the resource name, the two id fields in the payload, and the parent parameter name in the delete path, which is exactly the amount of code the base cannot supply.
  • Where it's used: registered explicitly at - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:36, and injected + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:37, and injected into SpeakerCategoryItemsPanel, the speaker detail page's category editor (Pages/Speaker/SpeakerCategoryItemsPanel.razor.cs:18).
  • @@ -1931,9 +1987,9 @@

    EventInfo

    construction sites source-compatible while the sponsor feature landed. [Rubric §15, Best Practices & Code Quality] (assesses whether a shape can grow without a ripple edit). EventLookupService is the single place that fills every member - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Events/EventLookupService.cs:109). + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Events/EventLookupService.cs:111).
  • Where it's used: PublicSponsorList resolves the current or next event and - then reads SponsorshipPacketUrl off it (PublicSponsorList.razor.cs:63); every page that injects + then reads SponsorshipPacketUrl off it (PublicSponsorList.razor.cs:79); every page that injects IEventLookupService indexes the dictionary of these records to render event names, including EventFilteredListPageBase<TDto> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Common/EventFilteredListPageBase.cs:27), @@ -2031,13 +2087,13 @@

    SessionSchedulePageRequest

  • Why it's built this way: PublicSessionScheduleService turns MyScheduleSessionIds into an extra "Id" filter with the IN operator before delegating to the - paged session query (PublicSessionScheduleService.cs:78-83), which is why the "never empty" + paged session query (PublicSessionScheduleService.cs:92-97), which is why the "never empty" invariant is documented on the record rather than defended in the service: an empty IN list would be a query that cannot match. [Rubric §12, Performance & Scalability]: the alternative the doc comment rules out, pulling a 500-row page and filtering in memory, also reported a wrong total past - 500 rows (PublicSessionScheduleService.cs:67-70).
  • + 500 rows (PublicSessionScheduleService.cs:81-84).
  • Where it's used: constructed by PublicSessionList inside its unified fetch - delegate (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Sessions/PublicSessionList.razor.cs:277-284) + delegate (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Sessions/PublicSessionList.razor.cs:321-328) and consumed by IPublicSessionScheduleService.FetchPageAsync.
  • IEventLookupService

    @@ -2060,7 +2116,7 @@

    IEventLookupService

  • Walkthrough: one member, GetAllAsync(CancellationToken cancellationToken = default) returning Task<Result<IReadOnlyDictionary<EventIdentifierType, EventInfo>>> (IEventLookupService.cs:28-29).
  • Where it's used: implemented by EventLookupService, registered scoped at - DependencyInjection.cs:58, and injected into a wide set of Conference pages, including + DependencyInjection.cs:59, and injected into a wide set of Conference pages, including EventFilteredListPageBase<TDto> (EventFilteredListPageBase.cs:27), RoomDetail (RoomDetail.razor.cs:21), RoomCreate (RoomCreate.razor.cs:16), SessionCreate @@ -2076,7 +2132,7 @@

    IEventLookupService

    pages (SponsorDetail.razor.cs:24, SponsorCreate.razor.cs:22). It is also consumed from the Engagement module's event feedback page, EventFeedback (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Pages/Feedback/EventFeedback.razor.cs:23), - which is why DependencyInjection.cs:56 labels these "cross-module lookup services".
  • + which is why DependencyInjection.cs:57 labels these "cross-module lookup services".

    IPublicSessionScheduleService

    @@ -2084,7 +2140,7 @@

    IPublicSessionScheduleService

    • What it is: the offline-first page fetch for the public session schedule - (IPublicSessionScheduleService.cs:34-53). One method runs the live paged query and keeps the last + (IPublicSessionScheduleService.cs:34-55). One method runs the live paged query and keeps the last successful FIRST page in the device-local cache, so a dead venue network still shows a programme (doc comment, lines 28-33).
    • Depends on: SessionSchedulePageRequest, @@ -2109,27 +2165,30 @@

      IPublicSessionScheduleService

    • Walkthrough: one member, FetchPageAsync(SessionSchedulePageRequest request, Action<bool> onCacheStateChanged, CancellationToken cancellationToken = default) returning Task<Result<(IReadOnlyList<SessionDTO> Items, int TotalItems)>> - (IPublicSessionScheduleService.cs:49-52).
        -
      • request (line 50): the page to fetch, see SessionSchedulePageRequest.
      • -
      • onCacheStateChanged (line 51): a callback, raised with true when the snapshot answered, so the + (IPublicSessionScheduleService.cs:51-54).
          +
        • request (line 52): the page to fetch, see SessionSchedulePageRequest.
        • +
        • onCacheStateChanged (line 53): a callback, raised with true when the snapshot answered, so the page shows its "cached schedule" banner and re-renders, and with false when a live fetch - succeeded (documented at lines 42-46). Passing a callback rather than exposing a property keeps the + succeeded (documented at lines 45-49). Passing a callback rather than exposing a property keeps the service free of any rendering knowledge.
        • -
        • The failure contract is spelled out in the method doc (lines 36-41): a successful first page is +
        • The failure contract is spelled out in the method doc (lines 36-43): a successful first page is written back to the snapshot, a failed fetch falls back to it, and a failure with nothing cached travels on to the caller unchanged, a failed Result as a failed result and a throwing cache store - as a rethrow, so the grid's own handling still applies. The implementation matches that on both - branches (PublicSessionScheduleService.cs:39-64).
        • + as a rethrow, so the grid's own handling still applies. The write-back is narrower than "any first + page": only the unfiltered programme is written, so a "My Schedule" page or a page narrowed by any + filter other than the event scope never overwrites the snapshot (lines 38-40). The implementation + matches that on both branches (PublicSessionScheduleService.cs:39-68) and enforces the + write-back rule through its private IsProgrammePage check (PublicSessionScheduleService.cs:78-79).
      • Why it's built this way: extracting the offline behavior behind an interface is what let the page keep a single fetch delegate for both the browse and "My Schedule" views. The page comment says it directly: only the banner stays in the page, the service decides when it is warranted - (PublicSessionList.razor.cs:289-291).
      • + (PublicSessionList.razor.cs:333-335).
      • Where it's used: implemented by PublicSessionScheduleService, - registered scoped at DependencyInjection.cs:54 under an explicit ADR-042 Wave 3 comment (line 41), - and injected into PublicSessionList (PublicSessionList.razor.cs:34), which - calls it from its unified fetch delegate (PublicSessionList.razor.cs:277-285).
      • + registered scoped at DependencyInjection.cs:55 under an explicit ADR-042 Wave 3 comment (line 54), + and injected into PublicSessionList (PublicSessionList.razor.cs:33), which + calls it from its unified fetch delegate (PublicSessionList.razor.cs:321-329).

      ISessionAssetUIService

      @@ -2137,7 +2196,7 @@

      ISessionAssetUIService

      • What it is: the UI-service contract for a session's materials: list, add a link, upload a file, - update, and delete (ISessionAssetUIService.cs:17-79). Unlike the generic entity-service pattern + update, and delete (ISessionAssetUIService.cs:17-85). Unlike the generic entity-service pattern elsewhere in this chapter, this contract is hand-written rather than derived from IEntityService<,>, because a session asset is not a plain CRUD resource: it has two creation paths (link vs. uploaded file) and a conditional update.
      • @@ -2148,21 +2207,28 @@

        ISessionAssetUIService

      • Concept: the conditional update carries the same If-Match shape as IEventUIService's transitions: UpdateAsync takes a required byte[] rowVersion and refuses the write with 428 Precondition Required when it is empty - (ISessionAssetUIService.cs:56-59), the same ADR-035 optimistic-concurrency contract. The list + (ISessionAssetUIService.cs:63-65), the same ADR-035 optimistic-concurrency contract. The list call is deliberately forgiving instead: GetBySessionAsync is anonymous, and a session the caller cannot see answers with an empty list rather than a failure, because it backs the public session - page as well as the organizer panel (ISessionAssetUIService.cs:19-26).
      • + page as well as the organizer panel (ISessionAssetUIService.cs:19-23). Because that list is served + under a shared output-cache policy, the owner's management view needs a way past it: the + bypassCache flag (:25-29) exists because a lookup can hit an entry built for an anonymous + caller, so the speaker's or organizer's own read adds a unique query parameter that is a guaranteed + cache miss. [Rubric §19, State Management] (assesses whether cached state can go stale in front of + the user who just changed it).
      • Walkthrough: five members.
          -
        • GetBySessionAsync(sessionId, ct) (:27-29): Task<Result<IReadOnlyList<SessionAssetDTO>>>.
        • -
        • AddLinkAsync(request, ct) (:35-37): Task<Result<SessionAssetDTO>>, takes a +
        • GetBySessionAsync(sessionId, bypassCache = false, ct) (:32-35): + Task<Result<IReadOnlyList<SessionAssetDTO>>>. The default keeps the cached public read; only the + owner's management view passes true.
        • +
        • AddLinkAsync(request, ct) (:41-43): Task<Result<SessionAssetDTO>>, takes a SessionAssetLinkRequest.
        • -
        • UploadFileAsync(sessionId, title, file, sortOrder, ct) (:49-54): - Task<Result<SessionAssetDTO>>. The doc comment (:39-48) frames it as streaming the picked +
        • UploadFileAsync(sessionId, title, file, sortOrder, ct) (:55-60): + Task<Result<SessionAssetDTO>>. The doc comment (:45-54) frames it as streaming the picked file straight into the multipart body rather than buffering the whole deck in the circuit.
        • -
        • UpdateAsync(id, rowVersion, request, ct) (:66-70): Task<Result<SessionAssetDTO>>, conditional +
        • UpdateAsync(id, rowVersion, request, ct) (:72-76): Task<Result<SessionAssetDTO>>, conditional on rowVersion as described above.
        • -
        • DeleteAsync(id, ct) (:76-78): Task<Result>. The doc comment notes a file asset also - schedules its blob for removal (:72).
        • +
        • DeleteAsync(id, ct) (:82-84): Task<Result>. The doc comment notes a file asset also + schedules its blob for removal (:78).
      • Why it's built this way: separating AddLinkAsync from UploadFileAsync instead of one @@ -2170,8 +2236,10 @@

        ISessionAssetUIService

        which matters because the upload path deliberately skips the shared retry policy (see SessionAssetService).
      • Where it's used: implemented by SessionAssetService, registered in - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs, and injected - into SessionAssetsPanel.razor.cs and SessionAssetsDownloadList.razor.cs + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:52, and injected + into SessionAssetsPanel (SessionAssetsPanel.razor.cs:33, which lists with + bypassCache: true at line 82) and SessionAssetsDownloadList + (SessionAssetsDownloadList.razor.cs:20, which keeps the cached read) (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/SessionAssets/).

      SessionizeRefreshOutcome

      @@ -2209,39 +2277,42 @@

      SessionizeRefreshOutcome

      IEventUIService.RefreshFromSessionizeWithCodeAsync (IEventUIService.cs:29), built by EventService (EventService.cs:81), and consumed by EventDetail, which unpacks both members in one branch: _refreshResult = refreshed.Summary, Event = refreshed.Event - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Events/EventDetail.razor.cs:247-256).
    • + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Events/EventDetail.razor.cs:247-266).

    EventLookupService

    MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI.Services.Events · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Events/EventLookupService.cs:33 · Level 4 · class (sealed)

      -
    • What it is: the implementation behind IEventLookupService: one bulk GET of - the events collection projected into a dictionary of EventInfo, memoized per scope - behind a single-flight cache (EventLookupService.cs:33-113). It is the most-injected service in this - chapter, so the caching is not an optimization detail, it is the reason the pattern scales.
    • +
    • What it is: the implementation behind IEventLookupService: a paged read of + the whole events collection projected into a dictionary of EventInfo, memoized per + scope behind a single-flight cache (EventLookupService.cs:33-116). It is the most-injected service + in this chapter, so the caching is not an optimization detail, it is the reason the pattern scales.
    • Depends on: EventInfo, EventDTO, PagedCollectionResult<T>, + PagedReadAll, + IdempotentReadRetry, HttpResultExecutor, ProblemDetailsResultReader; BCL IHttpClientFactory, - TimeSpan, DateTimeOffset, Task.WaitAsync. Like its category-item sibling it takes no token - storage (EventLookupService.cs:33), so this is an unauthenticated read.
    • + TimeProvider, TimeSpan, DateTimeOffset, Task.WaitAsync. The primary constructor takes the + client factory and a TimeProvider and, like its category-item sibling, no token storage + (EventLookupService.cs:33), so this is an unauthenticated read whose clock is injectable.
    • Concept introduced, the scope-lifetime single-flight cache. Three mechanisms interlock, and each one is documented in the class remarks (EventLookupService.cs:12-32).
      • The cached unit is the Task, not the result (:44). Two callers in the same scope that ask while a fetch is in flight share that one fetch instead of starting one each. The remarks explain why overlap is the normal case rather than the edge case: EventFilteredListPageBase<TDto> starts the load before its first - await via BeginEventsLoad (Pages/Common/EventFilteredListPageBase.cs:144) and a derived page - awaits it later.
      • -
      • A TTL stamped only on success (:91-96). _cachedUntil starts at DateTimeOffset.MinValue + await via BeginEventsLoad (Pages/Common/EventFilteredListPageBase.cs:137, defined at :145) + and a derived page awaits it later.
      • +
      • A TTL stamped only on success (:95-100). _cachedUntil starts at DateTimeOffset.MinValue (:51), which is what makes a faulted task, a cancelled one and a failed Result all expire immediately rather than caching a non-answer; a transient outage heals on the next page instead of being remembered for five minutes. CacheTtl is five minutes (:41), deliberately matched to the server-side "EventsCache" output-cache policy so the client entry can never be staler than the response the origin would have served anyway (:36-40).
      • No lock (:22-25). The service is registered scoped in both heads - (DependencyInjection.cs:58), and Blazor's renderer dispatches every component callback for one + (DependencyInjection.cs:59), and Blazor's renderer dispatches every component callback for one scope on a single logical thread, so the read-modify-write at :56-66 never interleaves with itself. This is a case where the hosting model, not a synchronization primitive, is the safety argument, and the code says so rather than leaving the reader to infer it. @@ -2257,20 +2328,26 @@

        EventLookupService

      • _cached (:44): the shared fetch, in flight or completed and still fresh.
      • _cachedUntil (:51): when _cached stops being reusable.
      • GetAllAsync(ct) (:53-72): snapshots _cached into a local (:56); starts a new FetchAsync() - when the entry is null, or completed and past its TTL (:61-66), resetting _cachedUntil first; - then returns the shared task guarded by the caller's token. The final line is the subtle one: the - cached task is created without a caller token and awaited under one via - entry.WaitAsync(cancellationToken) (:71), so a page disposing mid-load abandons only its own - await and never cancels the fetch the other pages in the scope are sharing (:68-70).
      • -
      • FetchAsync() (:74-99): runs the GET - events?includeFKs=false&includeChildren=false&pageSize=10000 under - HttpResultExecutor.ExecuteAsync with CancellationToken.None throughout (:76-89), reads the - response through ProblemDetailsResultReader (:84), - maps the page through BuildLookup (:87), and stamps the TTL only when the result is a success - and the payload is in hand, so the TTL measures the age of the data rather than the age of the - attempt (:91-96).
      • -
      • BuildLookup(page) (:101-113): projects each DTO into an EventInfo carrying all - seven members including SponsorshipPacketUrl (:109), keyed by evt.Id.
      • + when the entry is null, or completed and past its TTL as read from the injected + timeProvider.GetUtcNow() (:61-66), resetting _cachedUntil first; then returns the shared task + guarded by the caller's token. The final line is the subtle one: the cached task is created + without a caller token and awaited under one via entry.WaitAsync(cancellationToken) (:71), + so a page disposing mid-load abandons only its own await and never cancels the fetch the other + pages in the scope are sharing (:68-70). +
      • FetchAsync() (:74-103): reads every page through + PagedReadAll.ReadAllAsync until the reported + total (:78-92), because the GET-all endpoint ignores pageSize and stops silently at the API's + maximum page size (:76-77). Each page is one HttpResultExecutor.ExecuteAsync call that GETs + PagedReadAll.LookupPageUrl("events", pageNumber) through + IdempotentReadRetry.GetAsync (:83-86) + and reads it through ProblemDetailsResultReader + (:88-89), with CancellationToken.None throughout. The combined list is mapped through + BuildLookup (:93), and the TTL is stamped from timeProvider.GetUtcNow() only when the result + is a success and the payload is in hand, so the TTL measures the age of the data rather than the + age of the attempt (:95-100).
      • +
      • BuildLookup(events) (:105-115): takes the accumulated List<EventDTO> and projects each DTO + into an EventInfo carrying all seven members including SponsorshipPacketUrl + (:111), keyed by evt.Id.
    • Why it's built this way: the projection is the boundary between the transport contract and the @@ -2283,25 +2360,27 @@

      EventLookupService

      and collapsing them onto this URL family would trade one saved round trip for a wider payload on every one of them (EventLookupService.cs:26-31).
    • Where it's used: registered scoped against IEventLookupService - (DependencyInjection.cs:58). It is injected across the Conference UI, including + (DependencyInjection.cs:59). It is injected across the Conference UI, including EventFilteredListPageBase<TDto> (Pages/Common/EventFilteredListPageBase.cs:27, which is what puts it behind every event-filtered - grid), SessionDetail (:24), SessionCreate (:20), - RoomDetail (:20), RoomCreate (:14), - ActivityDetail (:24), ActivityCreate (:21), - SponsorDetail (:23), SponsorCreate (:20), - SpeakerDashboard (:25), SessionSelectionDashboard - (:18), OrganizerEventFeedback (:20), - PublicEventList (:36), PublicActivityList (:28), - PublicSponsorList (:27), and SessionLookups - (Pages/Session/SessionLookups.cs:30). It also crosses a module boundary into Engagement's + grid), SessionDetail (:29), SessionCreate (:23), + RoomDetail (:21), RoomCreate (:16), + ActivityDetail (:25), ActivityCreate (:23), + SponsorDetail (:24), SponsorCreate (:22), + PartnerDetail (:24), PartnerCreate (:22), + SpeakerDashboard (:26), SessionSelectionDashboard + (:19), OrganizerEventFeedback (:22), + PublicEventList (:35), PublicActivityList (:29), + PublicSponsorList (:28), and SessionLookups + (Pages/Sessions/SessionLookups.cs:33). It also crosses a module boundary into Engagement's EventFeedback page (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Pages/Feedback/EventFeedback.razor.cs:23). Pages that need a default event filter feed its Values to CurrentEventSelector along with the StartDate / EndDate / TimeZone accessors.
    • -
    • Caveats / not-in-source: the pageSize=10000 ceiling (EventLookupService.cs:81) is the same - hard cap as the other lookups, with no follow-on page request. The cache is per scope, so a Blazor +
    • Caveats / not-in-source: the read has no fixed row ceiling, since PagedReadAll keeps requesting + pages until the reported total (EventLookupService.cs:76-92); the page size it uses is + PagedReadAll's concern, not visible in this class. The cache is per scope, so a Blazor Server circuit and a WebAssembly app instance each hold their own copy, and there is no invalidation hook: an event edited in this scope is not reflected in the lookup until the five minutes elapse.
    @@ -2376,7 +2455,7 @@

    SessionAssetService

    • What it is: the implementation behind ISessionAssetUIService: list, add a link, upload a file, conditionally update, and delete, all against the SessionAssets - endpoint (SessionAssetService.cs:26-211).
    • + endpoint (SessionAssetService.cs:26-216).
    • Depends on: ISessionAssetUIService (implemented interface); AuthenticatedServiceBase for the authenticated client; HttpResultExecutor and @@ -2387,39 +2466,42 @@

      SessionAssetService

      SessionAssetDTO; BCL IHttpClientFactory, IBrowserFile, MultipartFormDataContent, CultureInfo.InvariantCulture.
    • Concept: the upload path is the teaching point. UploadFileAsync carries no retry policy at all - (SessionAssetService.cs:87-90), unlike every other method here, which runs its request through the + (SessionAssetService.cs:96-143), unlike every other method here, which runs its request through the shared RetryPolicy. The doc comment explains why: a picked file's stream is single-shot, neither a browser file input nor a native picker rewinds it, so a retried attempt would post an exhausted body. The Idempotency-Key header covers the case the retry existed for instead, because a repeated key replays the stored server response rather than re-uploading tens of megabytes over venue wifi - (:87-90). [Rubric §29, Resilience & Business Continuity] assesses whether a retry strategy + (:86-91). [Rubric §29, Resilience & Business Continuity] assesses whether a retry strategy actually fits the operation it wraps; here the right answer is to not retry the transport call and to rely on idempotency at the server instead. [Rubric §24, Forms/Validation/UX Safety]: the 50 MB cap is declared once, in SessionAssetLimits.MaxFileBytes, and stated to OpenReadStream so Blazor's own - 512 KB default cannot silently truncate a real slide deck (:107,127-128); the suppressed - Sonar rule S5693 on UploadFileAsync (:91-96) documents that the length IS limited, just not by + 512 KB default cannot silently truncate a real slide deck (:110-112); the suppressed + Sonar rule S5693 on UploadFileAsync (:92-95) documents that the length IS limited, just not by the pattern the rule looks for.
    • Walkthrough
      • Endpoint = "SessionAssets" (:29).
      • -
      • GetBySessionAsync(sessionId, ct) (:32-51): GETs SessionAssets?sessionId={id} through - HttpResultExecutor.ExecuteAsync, using the plain authenticated client (no idempotency key, no - If-Match, since it is a read), and reads the array through - ProblemDetailsResultReader (:36-49).
      • -
      • AddLinkAsync(request, ct) (:59-77): POSTs the SessionAssetLinkRequest +
      • GetBySessionAsync(sessionId, bypassCache, ct) (:32-57): GETs SessionAssets?sessionId={id} + through HttpResultExecutor.ExecuteAsync, using the plain authenticated client (no idempotency + key, no If-Match, since it is a read), and reads the array through + ProblemDetailsResultReader (:43-54). When + bypassCache is true the URL gains a &_={Guid.NewGuid():N} parameter (:39-41), so the + owner's read is a guaranteed miss against the shared output cache, the same pattern the speaker + dashboard uses (:37-38); the public download list keeps the cached read.
      • +
      • AddLinkAsync(request, ct) (:64-83): POSTs the SessionAssetLinkRequest to SessionAssets/link through a client built with a fresh Idempotency-Key and no If-Match - (:65), so a retried add cannot publish the same link twice.
      • -
      • UploadFileAsync(sessionId, title, file, sortOrder, ct) (:91-141): opens the file's stream capped - at SessionAssetLimits.MaxFileBytes (:107); builds a MultipartFormDataContent with parts named - file, sessionId, title, sortOrder (:125-128), matching the controller's IFormFile + (:73), so a retried add cannot publish the same link twice.
      • +
      • UploadFileAsync(sessionId, title, file, sortOrder, ct) (:96-143): opens the file's stream capped + at SessionAssetLimits.MaxFileBytes (:112); builds a MultipartFormDataContent with parts named + file, sessionId, title, sortOrder (:130-133), matching the controller's IFormFile parameter and its three [FromForm] arguments exactly, since a renamed part fails binding; posts - directly with httpClient.PostAsync rather than through RetryPolicy (:130-131), for the + directly with httpClient.PostAsync rather than through RetryPolicy (:135-136), for the single-shot-stream reason above.
      • -
      • UpdateAsync(id, rowVersion, request, ct) (:141-166): builds the If-Match header only when - rowVersion is non-empty (:151), so an empty token sends no header and the server answers 428 +
      • UpdateAsync(id, rowVersion, request, ct) (:146-171): builds the If-Match header only when + rowVersion is non-empty (:156), so an empty token sends no header and the server answers 428 rather than the write overwriting another editor's change; PUTs to SessionAssets/{id}.
      • -
      • DeleteAsync(id, ct) (:169-186): DELETEs SessionAssets/{id} through the plain authenticated +
      • DeleteAsync(id, ct) (:174-191): DELETEs SessionAssets/{id} through the plain authenticated client.
      • -
      • CreateRequestClientAsync(idempotencyKey, ifMatch) (:195-210): the shared client builder for a +
      • CreateRequestClientAsync(idempotencyKey, ifMatch) (:200-215): the shared client builder for a write. Both headers are set once, as default request headers on a client built once per logical operation, so every RetryPolicy attempt for that call states the same idempotency key and the same precondition: a duplicate arrival dedups at the server, and a write that lost the concurrency @@ -2432,8 +2514,10 @@

        SessionAssetService

        update, and each earns a method shaped for what it actually sends over the wire rather than a single generic Create/Update pair.
      • Where it's used: registered against ISessionAssetUIService in - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs, and injected into - SessionAssetsPanel.razor.cs and SessionAssetsDownloadList.razor.cs + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:52, and injected + into SessionAssetsPanel (SessionAssetsPanel.razor.cs:33, the one caller + passing bypassCache: true, at line 82) and SessionAssetsDownloadList + (SessionAssetsDownloadList.razor.cs:20) (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/SessionAssets/).

      EventService

      @@ -2451,7 +2535,7 @@

      EventService

    • Depends on: EntityServiceBase<TEntityDTO, TIdentifierType> for the CRUD surface, the Endpoint property (EntityServiceBase.cs:51) and the protected - SendRequestAsync dispatch (EntityServiceBase.cs:324,354); + SendRequestAsync dispatch (EntityServiceBase.cs:328,358); ConcurrencyETag to render the row version as a weak entity tag (EventService.cs:29,41); EventDTO, RefreshFromSessionizeResultDTO and @@ -2515,7 +2599,7 @@

      EventService

      optional: returning a bare summary would leave the page holding an event whose row version no longer matches, and the next conditional write would fail.
    • Where it's used: registered by the AddUIModule<ConferenceUIModule>() entity-service scan - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:30), since + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:31), since IEventUIService derives from IEntityService<,>. It is injected as IEventUIService into EventList (Pages/Event/EventList.razor.cs:21), EventDetail (Pages/Event/EventDetail.razor.cs:23), EventCreate @@ -2535,15 +2619,15 @@

      PublicSessionScheduleService

      • What it is: the offline-first page fetch behind the public session schedule. It runs the live paged session query and keeps the last successful first page in the device-local cache, so a dead venue - network still shows a programme (PublicSessionScheduleService.cs:19-107). It implements + network still shows a programme (PublicSessionScheduleService.cs:19-122). It implements IPublicSessionScheduleService.
      • Depends on: ISessionUIService as the live query (constructor, PublicSessionScheduleService.cs:20), so it composes over SessionService rather than issuing HTTP itself; ILocalCacheStore and IConnectivityStatusService - (:19-20); OfflineFirstPageSnapshot<TItem> - as the snapshot mechanism (:28-29); SessionSchedulePageRequest as + (:21-22); OfflineFirstPageSnapshot<TItem> + as the snapshot mechanism (:30-31); SessionSchedulePageRequest as its request record; SessionDTO; Result.
      • Concept introduced, offline-first as a composition, not a mode. The service is a decorator over @@ -2564,44 +2648,56 @@

        PublicSessionScheduleService

        when a dependency is gone; a venue with no signal still renders yesterday's first page) and [Rubric §19, State Management] (the banner callback keeps the user aware of which data they are looking at).
      • -
      • Concept introduced, scoping "My Schedule" server-side. The private QueryAsync shows the second +
      • Concept introduced, only the programme is remembered. Page 1 alone is not a sufficient condition + for the write-back. The private IsProgrammePage check (PublicSessionScheduleService.cs:78-79) + admits a page only when it is not "My Schedule" and carries no filter key other than the event scope + (EventId, the key PublicSessionList writes). Any other key, such as a title + search, a room or a grid column filter, marks a narrowed view that must never overwrite the cached + programme, while a different sort on page 1 still counts as the programme (doc comment, :71-76). + Without it, an attendee who last searched for one talk before losing signal would be shown that + single row as "the schedule". [Rubric §19, State Management].
      • +
      • Concept introduced, scoping "My Schedule" server-side. The private QueryAsync shows the third idea. When the "My Schedule" view is active, the request carries the attendee's bookmarked session ids, and the service copies the grid's filter dictionary and adds an Id IN (...) filter built from those - ids (PublicSessionScheduleService.cs:78-83), then lets the server page the result - (:83-86). The doc comment records what this replaced and why it matters: + ids (PublicSessionScheduleService.cs:92-97), then lets the server page the result + (:99-102). The doc comment records what this replaced and why it matters: pulling a 500-row page and filtering in memory also reported a wrong total past 500 rows - (:65-69). [Rubric §12, Performance & Scalability].
      • + (:81-84). [Rubric §12, Performance & Scalability].
      • Walkthrough
        • ScheduleCacheKey = "conference.publicSessions.page1" (PublicSessionScheduleService.cs:28): the device-local cache key, unique to this surface. The comment states the consequence of changing it - (:22-25): every already-cached schedule is orphaned. The uniqueness requirement is the framework's, + (:24-27): every already-cached schedule is orphaned. The uniqueness requirement is the framework's, since a shared key would let one list serve another list's rows (OfflineFirstPageSnapshot.cs:17-21).
        • _snapshot (PublicSessionScheduleService.cs:30-31): one OfflineFirstPageSnapshot<SessionDTO> built from the store, the connectivity probe and that key, held for the service's scoped lifetime.
        • -
        • FetchPageAsync(request, onCacheStateChanged, ct) (PublicSessionScheduleService.cs:34-65), the +
        • FetchPageAsync(request, onCacheStateChanged, ct) (PublicSessionScheduleService.cs:34-69), the only public method, and it has three exits.
            -
          1. Live success: QueryAsync returns a page (:39-40), the snapshot records it (:42), the - page's cached-data banner is switched off through the onCacheStateChanged(false) callback - (:43), and the live result is returned (:44).
          2. +
          3. Live success: QueryAsync returns a page (:41-42), the snapshot records it only when + IsProgrammePage(request) holds (:44-47), the page's cached-data banner is switched off + through the onCacheStateChanged(false) callback (:49), and the live result is returned + (:50).
          4. Live failure with a snapshot: a transport fault arrives as a failed Result, not an exception, so the fallback is tried - on the same terms (:47-49); when the snapshot answers, the banner is raised and the cached page - is returned as a success (:50); with nothing cached the original failure travels on to the + on the same terms (:53-55); when the snapshot answers, the banner is raised and the cached page + is returned as a success (:56); with nothing cached the original failure travels on to the grid's own handling.
          5. Thrown fault with a snapshot: the catch (Exception) when (_snapshot.CanServe(request.Page)) - filter (:52) still guards the paths that can throw rather than fail, the local cache store - itself and a cancelled fetch (:54). If the snapshot has nothing, the exception is rethrown - (:57-59) so cancellation and genuine faults are not swallowed.
          6. + filter (:58) still guards the paths that can throw rather than fail, the local cache store + itself and a cancelled fetch (:60). If the snapshot has nothing, the exception is rethrown + (:62-65) so cancellation and genuine faults are not swallowed.
        • -
        • QueryAsync(request, ct) (PublicSessionScheduleService.cs:74-89): builds the effective filters +
        • IsProgrammePage(request) (PublicSessionScheduleService.cs:78-79): a private static predicate, + request.MyScheduleSessionIds is null && request.Filters.Keys.All(key => key == "EventId"), the + write-back gate described above.
        • +
        • QueryAsync(request, ct) (PublicSessionScheduleService.cs:88-103): builds the effective filters described above and calls ISessionUIService.GetPagedAsync with includeChildren: true, since the - public schedule renders each session's child data (:83-86).
        • -
        • ReadSnapshotAsync(page, onCacheStateChanged, ct) (PublicSessionScheduleService.cs:95-107): reads - the snapshot and raises the "showing cached data" banner only when one actually answered (:98-102).
        • + public schedule renders each session's child data (:99-102). +
        • ReadSnapshotAsync(page, onCacheStateChanged, ct) (PublicSessionScheduleService.cs:109-121): reads + the snapshot and raises the "showing cached data" banner only when one actually answered (:114-118).
      • Why it's built this way: the offline behavior is a property of this list surface, so it lives in @@ -2610,10 +2706,10 @@

        PublicSessionScheduleService

        ISessionUIService inherits the caching.
      • Where it's used: registered explicitly as IPublicSessionScheduleService at - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:54, under the - comment naming ADR-042 Wave 3 (:41), and injected into - PublicSessionList (Pages/Public/PublicSessionList.razor.cs:31), whose grid - data delegate calls FetchPageAsync at :274.
      • + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:55, under the + comment naming ADR-042 Wave 3 (:54), and injected into + PublicSessionList (Pages/Public/Sessions/PublicSessionList.razor.cs:33), + whose grid data delegate calls FetchPageAsync at :321.
      • Caveats / not-in-source: the snapshot is best-effort. Nothing here expires it, so on a device that has been offline for a long time the cached first page can be arbitrarily old; the only signal to the user is the banner the onCacheStateChanged callback raises. What the store does on a write failure @@ -2638,7 +2734,7 @@

        SpeakerInfo

      • Walkthrough: a positional record with three members and no body (ISpeakerLookupService.cs:9-12); it is filled member for member from the speaker DTO in SpeakerLookupService - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Speakers/SpeakerLookupService.cs:40-41).
      • + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Speakers/SpeakerLookupService.cs:45-46).
      • Where it's used: returned as the dictionary value of ISpeakerLookupService.GetAllAsync (ISpeakerLookupService.cs:19-20), which the session and speaker surfaces inject to enrich speaker @@ -2732,7 +2828,7 @@

        ISpeakerDetailLookupService

        (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Speakers/SpeakerDetailLookupService.cs:22-35).
      • Where it's used: implemented by SpeakerDetailLookupService, registered scoped in the Conference UI composition root under a comment that repeats the rationale - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:61-63), and + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:62-64), and injected into exactly one page, SpeakerDetail (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Speakers/SpeakerDetail.razor.cs:30).
      @@ -2766,7 +2862,7 @@

      ISpeakerLookupService

      fields never reaches the consuming pages.
    • Where it's used: implemented by SpeakerLookupService and registered scoped - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:57). Injected into SessionDetail + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:58). Injected into SessionDetail (Pages/Session/SessionDetail.razor.cs:25), SessionList (Pages/Session/SessionList.razor.cs:26), PublicSessionList (Pages/Public/PublicSessionList.razor.cs:33), PublicSessionDetail @@ -2827,11 +2923,11 @@

      ISpeakerDashboardUIService

      subject it needs to check ownership on every call [Rubric §11, Security].
    • Where it's used: implemented by SpeakerDashboardService (SpeakerDashboardService.cs:14-16) and registered explicitly at - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:39 (an + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:40 (an explicit AddScoped because it is not an IEntityService<,> and the assembly scan would not find it); injected into SpeakerDashboard (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Speakers/SpeakerDashboard.razor.cs:25), - which calls three of the four methods (SpeakerDashboard.razor.cs:108, :136, :269).
    • + which calls three of the four methods (SpeakerDashboard.razor.cs:109, :136, :269).
    • Caveats / not-in-source: GetSessionBookmarkCountAsync, the single-session count, has no page consumer today; the dashboard uses the batched form. It remains on the contract, is implemented (SpeakerDashboardService.cs:45), and is covered by unit tests @@ -2889,7 +2985,7 @@

      SpeakerDashboardService

      (SpeakerDashboardService.cs:14-16)
      .
    • Depends on: AuthenticatedServiceBase for CreateAuthenticatedClientAsync() and the shared static Polly RetryPolicy - (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/AuthenticatedServiceBase.cs:25,51); + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/AuthenticatedServiceBase.cs:25,57); HttpResultExecutor and ProblemDetailsResultReader; PagedCollectionResult<T>; @@ -2908,7 +3004,7 @@

      SpeakerDashboardService

      SessionsController removes the key from the filter dictionary, parses it with TryParse under the invariant culture, and resolves it through the SessionSpeaker join before ANDing the result with the public-session specification - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:111-120, + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionsController.cs:119-128, documented at :90-94). The request also asks for includeFKs=false&includeChildren=false and caps the page at MaxSpeakerSessions = 100 (SpeakerDashboardService.cs:19,38). The comment records what this replaced: fetching the whole catalog with every child collection and filtering client-side. @@ -2968,7 +3064,7 @@

      SpeakerDashboardService

      correctness on the one surface where staleness is unacceptable, while the anonymous public list keeps its output cache.
    • Where it's used: registered explicitly as ISpeakerDashboardUIService - at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:39, and injected + at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:40, and injected into SpeakerDashboard, the speaker's "My Sessions" page (Pages/Speaker/SpeakerDashboard.razor.cs:24).
    • Caveats / not-in-source: the retry-plus-reader dispatch in SendGetRequestAsync is a deliberate @@ -2982,67 +3078,92 @@

      SpeakerLookupService

      MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI.Services.Speakers · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Speakers/SpeakerLookupService.cs:12 · Level 4 · class (sealed)

      -
    • What it is: a small read service that fetches every speaker once and builds a speaker-keyed lookup +
    • What it is: a small read service that fetches every speaker and builds a speaker-keyed lookup dictionary (SpeakerIdentifierType to SpeakerInfo), so pages holding raw speaker ids - can render display names and profile pictures (SpeakerLookupService.cs:12-45). It implements + can render display names and profile pictures (SpeakerLookupService.cs:12-51). It implements ISpeakerLookupService.
    • Depends on: SpeakerInfo, the three-field projection it emits (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Speakers/ISpeakerLookupService.cs:9-12); SpeakerDTO as the wire shape; PagedCollectionResult<T>; + PagedReadAll for the page loop and the page URL; + IdempotentReadRetry for the GET; HttpResultExecutor and ProblemDetailsResultReader; BCL IHttpClientFactory. Note what is absent: the primary constructor takes only IHttpClientFactory (SpeakerLookupService.cs:12), with no ITokenStorageService and no AuthenticatedServiceBase base. This is an - unauthenticated public read, so it also gets none of that base's Polly retry.
    • + unauthenticated public read. It still gets that base's Polly retry, but by composition: + IdempotentReadRetry.Policy is the same AuthenticatedServiceBase.SharedRetryPolicy object + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/IdempotentReadRetry.cs:24).
    • Concept introduced, the client-side denormalizing lookup. Session and event pages hold speaker ids but must show speaker names. Rather than issue one fetch per referenced speaker, this service - pulls the whole speaker set in one call and hands back an in-memory dictionary the page indexes - locally; the doc comment states exactly that use (SpeakerLookupService.cs:8-11). - [Rubric §23, Front-End Performance] (assesses whether a view avoids N per-item round trips; one call - serves a whole grid). [Rubric §9, API & Contract Design] shows up in the query string: - includeFKs=false&includeChildren=false asks the server for the flat rows only, so the bulk read stays - cheap on both ends. The dictionary is keyed by the identifier alias rather than a bare primitive, which - is what makes a wrong-id lookup a compile error + pulls the whole speaker set in a short run of 500-row pages and hands back an in-memory dictionary the + page indexes locally; the doc comment states exactly that use (SpeakerLookupService.cs:8-11). + [Rubric §23, Front-End Performance] (assesses whether a view avoids N per-item round trips; one paged + sweep serves a whole grid). [Rubric §9, API & Contract Design] shows up in the URL + PagedReadAll.LookupPageUrl builds: speakers/paged?...&pageSize=500&sortColumn=Id&sortDirection=asc&includeFKs=false&includeChildren=false + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/PagedReadAll.cs:43-46). The stable id + order keeps consecutive pages from skipping or repeating a row, and the two include flags ask for the + flat rows only, so the bulk read stays cheap on both ends. The dictionary is keyed by the identifier + alias rather than a bare primitive, which is what makes a wrong-id lookup a compile error (ADR-048).
    • Walkthrough
        -
      • GetAllAsync(ct) (SpeakerLookupService.cs:15-30) is an expression-bodied method returning - Result<IReadOnlyDictionary<SpeakerIdentifierType, SpeakerInfo>>. The body runs inside - HttpResultExecutor.ExecuteAsync (:17), so - a dead network becomes a transport failure rather than a thrown exception. It resolves the named - "APIClient" client from the factory (:20), GETs - speakers?includeFKs=false&includeChildren=false&pageSize=10000, a deliberately large page meant to - pull every speaker in one request (:21-23), reads the response into a +
      • GetAllAsync(ct) (SpeakerLookupService.cs:15-37) returns + Result<IReadOnlyDictionary<SpeakerIdentifierType, SpeakerInfo>>. The inline comment (:18-19) + names why it pages: the GET-all endpoint ignores pageSize and stops silently at the API's maximum + page size. It hands PagedReadAll.ReadAllAsync a + per-page delegate (:20-34). Each page runs inside + HttpResultExecutor.ExecuteAsync (:21), so + a dead network becomes a transport failure rather than a thrown exception. The delegate resolves the + named "APIClient" client from the factory (:24), GETs LookupPageUrl("speakers", pageNumber) + through IdempotentReadRetry.GetAsync + (:25-28), which re-sends on a transient failure up to three times with exponential backoff + (IdempotentReadRetry.cs:10-12), and reads the response into a Result<PagedCollectionResult<SpeakerDTO>> through - ProblemDetailsResultReader (:25-26), and then - Maps that page through BuildLookup (:28). Mapping rather than unwrapping is the point: the + ProblemDetailsResultReader (:30-31). + ReadAllAsync loops pages from 1, stopping on an empty page or once the accumulated count reaches + the reported TotalItemCount, and returns the first failing page's errors unchanged + (PagedReadAll.cs:65-89). The method then Maps the accumulated List<SpeakerDTO> through + BuildLookup (SpeakerLookupService.cs:36). Mapping rather than unwrapping is the point: the projection runs only on success, and an API-described failure travels out with its ErrorType intact.
      • -
      • BuildLookup(page) (SpeakerLookupService.cs:32-45) is a private static. It substitutes an empty - list for a null Items (:35), fills a Dictionary<SpeakerIdentifierType, SpeakerInfo> with Id, - FullName and ProfilePicture per speaker (:37-42), and returns it as an IReadOnlyDictionary - (:44). Being static is not decoration: it proves the projection touches no instance state, so it - reads in isolation.
      • +
      • BuildLookup(speakers) (SpeakerLookupService.cs:39-50) is a private static taking the flattened + List<SpeakerDTO>; the null-Items guard now lives in ReadAllAsync (PagedReadAll.cs:75). It + fills a Dictionary<SpeakerIdentifierType, SpeakerInfo> with Id, FullName and ProfilePicture + per speaker (SpeakerLookupService.cs:42-47) and returns it as an IReadOnlyDictionary (:49). + Being static is not decoration: it proves the projection touches no instance state, so it reads in + isolation.
    • -
    • Why it's built this way: one bulk fetch plus a local index is far cheaper than per-id lookups when +
    • Why it's built this way: one paged sweep plus a local index is far cheaper than per-id lookups when a page renders many speaker references, and the projection to SpeakerInfo keeps only the three display fields the UI needs rather than the full - SpeakerDTO, so what a page holds in memory stays small.
    • + SpeakerDTO, so what a page holds in memory stays small. + Paging to the reported total, instead of asking for one oversized page, is the only route that does not + truncate silently at the API's page-size clamp (PagedReadAll.cs:12-17).
    • Where it's used: registered as ISpeakerLookupService among the "cross-module lookup services" - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:57), and injected - into SessionList (Pages/Session/SessionList.razor.cs:26), - SessionDetail (Pages/Session/SessionDetail.razor.cs:25), - PublicSessionList (Pages/Public/PublicSessionList.razor.cs:33) and - PublicSessionDetail (Pages/Public/PublicSessionDetail.razor.cs:25).
    • -
    • Caveats / not-in-source: the pageSize=10000 ceiling (SpeakerLookupService.cs:22) assumes the - conference never exceeds 10,000 speakers; past that the lookup would silently miss speakers, and - nothing here detects the truncation. The dictionary is rebuilt on every call (there is no memoization - in this class), so a page that needs it twice pays for it twice. Because the read is unauthenticated, - it sees only what the public speakers endpoint exposes.
    • + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:58), and injected + into SessionList (Pages/Sessions/SessionList.razor.cs:29), + SessionDetail (Pages/Sessions/SessionDetail.razor.cs:30, passed on to + SessionLookups at :58, whose constructor takes it at + Pages/Sessions/SessionLookups.cs:34), + PublicSessionList (Pages/Public/Sessions/PublicSessionList.razor.cs:35) and + PublicSessionDetail (Pages/Public/Sessions/PublicSessionDetail.razor.cs:25). + The Conference service's + SelfHttpOutputCacheWarmupTask replays + this service's first page URL as a literal + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/SelfHttpOutputCacheWarmupTask.cs:55), so the + output cache is warm for it. +
    • Caveats / not-in-source: ReadAllAsync stops after 40 pages of 500 (20,000 rows) whatever the + server reports (PagedReadAll.cs:22-28), and returns success with what it has, so a speaker set past + that would still be cut short without a signal. The warm-up literal must match LookupPageUrl byte for + byte (PagedReadAll.cs:33-34); a change to the URL format silently turns the warm-up into a miss. + The dictionary is rebuilt on every call (there is no memoization in this class), so a page that needs + it twice pays for it twice. Because the read is unauthenticated, it sees only what the public speakers + endpoint exposes.

    SpeakerDetailLookupService

    @@ -3114,7 +3235,7 @@

    SpeakerDetailLookupService

  • Where it's used: registered explicitly rather than by the entity-service scan, because it implements no IEntityService<,>: services.AddScoped<ISpeakerDetailLookupService, SpeakerDetailLookupService>() - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:63). Its one + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:64). Its one consumer is SpeakerDetail, which injects it (Pages/Speaker/SpeakerDetail.razor.cs:29), holds the value in a single nullable field for the life of the page (SpeakerDetail.razor.cs:81), calls it once inside the guarded load @@ -3153,7 +3274,7 @@

    SpeakerService

    and, through it, AuthenticatedServiceBase; the base's Endpoint property (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:51) and its valueless - SendRequestAsync overload (EntityServiceBase.cs:354-370); + SendRequestAsync overload (EntityServiceBase.cs:358-374); ITokenStorageService; SpeakerDTO; LinkUserRequest; @@ -3168,7 +3289,7 @@

    SpeakerService

    authenticated client, the Polly retry pipeline and the Result conversion through ProblemDetailsResultReader - (EntityServiceBase.cs:354-370). [Rubric §9, API & Contract Design] (assesses whether the client + (EntityServiceBase.cs:358-374)
    . [Rubric §9, API & Contract Design] (assesses whether the client speaks the server's contract exactly rather than an assumed one): link is a PUT to {id}/link carrying a LinkUserRequest body and unlink is a DELETE to the same path, which is precisely what the controller declares @@ -3189,20 +3310,20 @@

    SpeakerService

    new Uri($"{Endpoint}/{speakerId}/link", UriKind.Relative) (:21-25). The URI is relative because the base resolves the named API client, and its configured base address, inside CreateAuthenticatedClientAsync - (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/AuthenticatedServiceBase.cs:51-53), which - is also where the bearer token is attached (AuthenticatedServiceBase.cs:57-62). The interpolated + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/AuthenticatedServiceBase.cs:57-59)
    , which + is also where the bearer token is attached (AuthenticatedServiceBase.cs:63-68). The interpolated speakerId is culture-safe because SpeakerIdentifierType aliases System.Guid (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/MMCA.ADC.Conference.GlobalUsings.IdentifierType.cs:23, ADR-048).
  • UnlinkUserAsync(speakerId, cancellationToken) (SpeakerService.cs:28-35): the same dispatch with DeleteAsync against the identical path and no body (:32-34). Both methods return the Result the base produced, success for any 2xx and - otherwise the errors the ProblemDetails response described (EntityServiceBase.cs:354-370); the + otherwise the errors the ProblemDetails response described (EntityServiceBase.cs:358-374); the server answers NoContent on both success paths (SpeakerLinksController.cs:55, :73).
  • Neither call passes an idempotencyKey or an ifMatch, both optional parameters of that overload - (EntityServiceBase.cs:354-358). That matches the contract the base documents: a key is for + (EntityServiceBase.cs:358-362). That matches the contract the base documents: a key is for non-idempotent writes (creates), and link and unlink are a PUT and a DELETE whose repetition - lands on the same state (EntityServiceBase.cs:310-312, + lands on the same state (EntityServiceBase.cs:314-316, ADR-017).
  • @@ -3213,9 +3334,9 @@

    SpeakerService

    generic contract (ISpeakerUIService.cs:10-15), means pages doing plain CRUD never see them while the organizer page that needs them gets them without a cast.
  • Where it's used: registered by the Scrutor scan inside AddUIModule<ConferenceUIModule>() - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:30, which scans + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:31, which scans the module assembly for IEntityService<,> implementations at - MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:276-280), so no explicit + MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:292-296), so no explicit AddScoped line exists for it. Injected as ISpeakerUIService into SpeakerList (Pages/Speaker/SpeakerList.razor.cs:23), SpeakerDetail (Pages/Speaker/SpeakerDetail.razor.cs:27), @@ -3261,26 +3382,34 @@

    SessionFormModel

    • What it is: the session twin of EventFormModel. It declares the editable - session fields once for both the create page and the detail page's inline editor, and it owns the one - piece of logic a session form needs that an event form does not: rejoining a date picker and a time - picker into a single timestamp.
    • + session fields once for both the create page and the detail page's inline editor, and it owns the two + pieces of logic a session form needs that an event form does not: rejoining a date picker and a time + picker into a single timestamp, and checking that the rejoined end falls after the rejoined start.
    • Depends on: SessionDTO for every length cap (MMCA.ADC.Conference.Shared.Sessions, line 2) and the Room identifier alias. Externals: System.ComponentModel.DataAnnotations.
    • -
    • Concept introduced: the shared-form-model idea is EventFormModel's. Two +
    • Concept introduced: the shared-form-model idea is EventFormModel's. Three details are specific to sessions:
      1. The split date/time pair. MudBlazor has no single date-and-time control, so a session's StartsAt and EndsAt are each bound as two properties: StartsAtDate / StartsAtTime and - EndsAtDate / EndsAtTime (lines 58-67). Combine (line 79) is the protected static rejoin, + EndsAtDate / EndsAtTime (lines 64-73). Combine (line 98) is the protected static rejoin, and it returns null unless both halves are set, so a half-entered time leaves the schedule unset rather than inventing a midnight. Both derived models call it, so the create page and the editor split and rejoin a timestamp identically.
      2. -
      3. SessionTitle rather than Title. The doc comment (lines 33-35) records the reason: every page +
      4. A client mirror of a server invariant. HasEndAfterStart (lines 87-92) rejoins both + timestamps through Combine and returns false only when both are present and the end is not + strictly after the start, so a zero-duration session is refused too. The doc comment (lines 81-86) + names the server rule it mirrors, BR-122 (SessionInvariants.EnsureEndsAtIsAfterStartsAt), and + states the same scope: a missing half is not this rule's concern. EndsAtAfterStartsAtKey + (line 37) is the resource key the hosting pages resolve for the refusal message, carried with the + server's own wording (Session.Duration.Invalid, per the doc comment at lines 33-36).
      5. +
      6. SessionTitle rather than Title. The doc comment (lines 39-42) records the reason: every page in this group already has its own localized Title property, and SonarAnalyzer S4275 fires on the collision. The rename is an analyzer-driven naming choice, not a domain one. [Rubric §24, Forms, Validation & UX Safety]: the caps are SessionDTO.TitleMaxLength and its - siblings (lines 38, 42, 46, 50, 54), so the browser rejects what the column would reject, and the - Combine rule makes an incomplete timestamp impossible to post by accident. + siblings (lines 44, 48, 52, 56, 60), so the browser rejects what the column would reject; the + Combine rule makes an incomplete timestamp impossible to post by accident; and HasEndAfterStart + lets both pages refuse an inverted schedule before the round trip the server would refuse anyway. [Rubric §15, Best Practices & Code Quality] assesses whether the analyzers-as-errors baseline is honored rather than suppressed: this is a case where the code changed a name instead of adding a #pragma.
      7. @@ -3288,22 +3417,26 @@

        SessionFormModel

      8. Walkthrough
        • SessionTitleRequiredKey (line 31) is the hoisted resource key, the same technique - EventFormModel uses.
        • -
        • Five annotated text properties (lines 39-55): SessionTitle is required, Description, Status, + EventFormModel uses; EndsAtAfterStartsAtKey (line 37) is its BR-122 sibling.
        • +
        • Five annotated text properties (lines 43-61): SessionTitle is required, Description, Status, AccessibilityInfo, and ResourceLinks are optional.
        • -
        • Six unannotated properties (lines 58-73): the four date/time halves, IsServiceSession (the - lunch-or-break flag), and the nullable RoomId.
        • -
        • Combine(DateTime? date, TimeSpan? time) (line 79) returns date.Value.Date + time.Value when both - are present, otherwise null.
        • +
        • Six unannotated properties (lines 64-79): the four date/time halves, IsServiceSession (the + lunch-or-break flag, line 76), and the nullable RoomId (line 79).
        • +
        • HasEndAfterStart() (lines 87-92) returns startsAt is null || endsAt is null || endsAt.Value > startsAt.Value over the two rejoined timestamps (line 91).
        • +
        • Combine(DateTime? date, TimeSpan? time) (lines 98-99) returns date.Value.Date + time.Value when + both are present, otherwise null.
      9. Why it's built this way: sessions carry a schedule that no single control captures, and both edit - surfaces must split and rejoin it the same way or a session saved from one screen and re-opened on the - other would move. Putting Combine on the base makes that impossible to get wrong per page.
      10. + surfaces must split, rejoin, and check it the same way or a session saved from one screen and re-opened + on the other would move (or be accepted on one screen and refused on the other). Putting Combine and + HasEndAfterStart on the base makes that impossible to get wrong per page.
      11. Where it's used: SessionCreateModel and SessionEditModel derive from it; SessionCreate and SessionDetail bind an instance to the shared SessionFormFields component - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Session/SessionFormFields.razor).
      12. + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Sessions/SessionFormFields.razor), + call HasEndAfterStart before posting, and resolve EndsAtAfterStartsAtKey for the inline refusal + (.../Pages/Sessions/SessionCreate.razor.cs:162-167, .../Pages/Sessions/SessionDetail.razor.cs:186-191).

    SessionCreateModel

    @@ -3315,36 +3448,38 @@

    SessionCreateModel

    the create posts.
  • Depends on: SessionFormModel and SessionDTO (line 1), plus the Event identifier alias. - Externals: System.Security.Cryptography.RandomNumberGenerator.
  • -
  • Concept introduced, the client-minted identifier. Unlike - EventCreateModel, which posts Id = default, this model fabricates one: - RandomNumberGenerator.GetInt32(100_000, int.MaxValue) (line 23). The reason is that a session's int - primary key is its Sessionize id, so the column is app-assigned rather than database-generated. - CreateSessionHandler only auto-allocates - from the reserved manual range when command.Id == default and otherwise respects an explicit id - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/Create/CreateSessionHandler.cs:79-95), - so the value this model generates is what gets persisted. The page still reads created.Id back off - the response before navigating (.../Pages/Session/SessionCreate.razor.cs:158), so it behaves - correctly either way. + No externals.
  • +
  • Concept introduced, server-allocated identifiers in an app-assigned key space. Like + EventCreateModel, this model posts Id = default (line 25). The reason it is + worth naming is that a session's int primary key is its Sessionize id, so the column is + app-assigned rather than database-generated, and something has to pick the number. The in-code comment + (lines 23-24, tagged M162) records that the server does: when command.Id == default, + CreateSessionHandler allocates the next id + inside the reserved manual range (SessionInvariants.ManualIdRangeStart to ManualIdRangeEnd) and + fails with Session.ManualIdRangeExhausted past its end + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/Create/CreateSessionHandler.cs:79-92), + retrying on a unique-constraint collision up to MaxManualIdAttempts (3) times + (.../CreateSessionHandler.cs:33, :60). The page reads createdSession.Id back off the response + before navigating (.../Pages/Sessions/SessionCreate.razor.cs:184). [Rubric §8, Data Architecture]: identifier assignment is an explicit, documented policy per aggregate - rather than an implicit database default, which is what lets Sessionize-imported and organizer-created - sessions share one key space.
  • + rather than an implicit database default or a client guess, which is what lets Sessionize-imported and + organizer-created sessions share one key space without colliding.
  • Walkthrough
      -
    • ToNew(EventIdentifierType eventId) (lines 20-31): the generated Id (line 23), Title from the - base's SessionTitle (line 24), the owning EventId from the parameter (line 26), the optional - RoomId (line 27), StartsAt and EndsAt each through the base's Combine (lines 28-29), and - IsServiceSession (line 30).
    • +
    • ToNew(EventIdentifierType eventId) (lines 20-33): Id = default (line 25), Title from the base's + SessionTitle (line 26), Description (line 27), the owning EventId from the parameter (line 28), + the optional RoomId (line 29), StartsAt and EndsAt each through the base's Combine + (lines 30-31), and IsServiceSession (line 32).
    • The Status, AccessibilityInfo, and ResourceLinks properties the base declares are not written here: they are edit-time fields, so a newly created session carries none of them.
  • -
  • Why it's built this way: sessions live in a key space shared with Sessionize, so the client cannot - simply post a placeholder and let the database pick; and a schedule half is only sent when it is - complete, so an organizer who picks a date but no time gets an unscheduled session rather than one - silently pinned to midnight.
  • +
  • Why it's built this way: sessions live in a key space shared with Sessionize, so the id is chosen + where the existing ids are visible (the handler's range query) rather than guessed by the client; and a + schedule half is only sent when it is complete, so an organizer who picks a date but no time gets an + unscheduled session rather than one silently pinned to midnight.
  • Where it's used: the _model field of SessionCreate - (.../Pages/Session/SessionCreate.razor.cs:33), consumed by its CreateSessionAsync - (.../Pages/Session/SessionCreate.razor.cs:149).
  • + (.../Pages/Sessions/SessionCreate.razor.cs:36), consumed by its CreateSessionAsync + (.../Pages/Sessions/SessionCreate.razor.cs:172).

    SessionEditModel

    @@ -3360,27 +3495,33 @@

    SessionEditModel

    EventEditModel's, and the split-timestamp handling is SessionFormModel's. What is worth naming is the symmetry: LoadFrom splits each timestamp with ?.Date and ?.TimeOfDay (lines 29-32) and ToUpdated rejoins it with - Combine (lines 59-60), so an untouched session round-trips to the same value. - [Rubric §8, Data Architecture]: ToUpdated re-sends the loaded RowVersion (line 54), the - optimistic-concurrency token of ADR-035. + Combine (lines 58-59), so an untouched session round-trips to the same value. ToUpdated builds its + result as session with { ... } (line 53), so every field the form does not edit is carried over from + the loaded record rather than re-listed. + [Rubric §8, Data Architecture]: because of that with expression the loaded RowVersion, the + optimistic-concurrency token of ADR-035, is re-sent unchanged, along with the identity and owning event.
  • Walkthrough
    • LoadFrom(SessionDTO session) (lines 23-38): null guard, then eleven copies, including the four schedule halves (lines 29-32) and the RoomId (line 37).
    • -
    • ToUpdated(SessionDTO session) (lines 47-65): identity and concurrency off the source (Id line 53, - RowVersion line 54, EventId line 57), the edited values off the model, and the two rejoined - timestamps (lines 59-60). A schedule half left blank sends null, which clears the stored - timestamp rather than preserving it, and the doc comment says so (lines 42-43).
    • +
    • ToUpdated(SessionDTO session) (lines 49-65): null guard, then session with overriding nine + members off the model (lines 55-63): Title, Description, RoomId, the two rejoined timestamps, + Status, IsServiceSession, AccessibilityInfo, and ResourceLinks. The doc comment (lines 40-45) + names what round-trips instead of resetting: the identity, concurrency token, owning event, and the + fields this form does not show (informed, confirmed, plenum, live and recording links). A schedule + half left blank sends null, which clears the stored timestamp rather than preserving it, and the + doc comment says so (lines 44-45).
    • The class doc (lines 8-10) records the deliberate omission: the owning event is displayed but never edited, because moving a session between events is a create plus a delete.
  • Why it's built this way: the session form has thirteen editable values and two of them are composites, so a hand-written transcription in the page is the most likely place for a dropped field. - One method each way makes the round trip verifiable by reading two adjacent blocks.
  • + One method each way makes the round trip verifiable by reading two adjacent blocks, and copying from + the loaded record with with means a DTO field the form never shows cannot be reset by omission.
  • Where it's used: the _model field of SessionDetail - (.../Pages/Session/SessionDetail.razor.cs:74), driven by its StartEditing - (.../Pages/Session/SessionDetail.razor.cs:166) and SaveChangesAsync - (.../Pages/Session/SessionDetail.razor.cs:187).
  • + (.../Pages/Sessions/SessionDetail.razor.cs:71), driven by its StartEditing + (.../Pages/Sessions/SessionDetail.razor.cs:166) and SaveChangesAsync + (.../Pages/Sessions/SessionDetail.razor.cs:196).

    SessionLookups

    @@ -3458,66 +3599,73 @@

    SessionCreate

    SessionCreateModel fields plus the owning event, posts the new record, and redirects to that session's detail page. It is the clearest place to see the one thing that makes session editing awkward: a dependent lookup, because rooms belong to the chosen event. -
  • Depends on: ISessionUIService (line 19), - IEventLookupService returning EventInfo (line 20), - IRoomUIService for the room dropdown (line 21), - IToastService (line 23), - SessionCreateModel (line 33), +
  • Depends on: ISessionUIService (line 22), + IEventLookupService returning EventInfo (line 23), + IRoomUIService for the room dropdown (line 24), + IToastService (line 26), + SessionCreateModel (line 36) and its base + SessionFormModel for the BR-122 key (line 164), RoomDTO, ModelValidation plus DataAnnotationsModelValidator - (line 57), ConferenceRoutePaths, - ErrorMessages (line 142), and + (line 70), ConferenceRoutePaths, + ErrorMessages (line 158), and ResultUiExtensions.NotifyOnFailure, which posts - the last save's Result (_saveResult) for the page's - inline ErrorSummary. It uses the Event, - Room, and Session identifier aliases. Externals: Blazor ([Inject], NavigationManager), - MudBlazor (MudForm, BreadcrumbItem), System.Globalization.CultureInfo, and the - IStringLocalizer<SessionCreate> injected by the template - (.../Pages/Session/SessionCreate.razor:7).
  • + the last save's Result (_saveResult, line 45) for the + page's inline ErrorSummary. It uses the Event, Room, and Session identifier aliases. Externals: + Blazor ([Inject], NavigationManager), MudBlazor (MudForm, BreadcrumbItem, MudAlert), + System.Globalization.CultureInfo, and the IStringLocalizer<SessionCreate> injected by the template + (.../Pages/Sessions/SessionCreate.razor:7).
  • Concept introduced, the dependent lookup. The create-form shape itself is EventCreate's; what is new here is that one field's options depend on another field's - value. LoadRoomsAsync (lines 87-110) fetches rooms filtered by the selected event, and - OnEventChangedAsync (lines 114-130) reloads them and clears the previous choice whenever the event - changes. The doc comment (lines 82-86) records why this is not cosmetic: BR-130 rejects a room from + value. LoadRoomsAsync (lines 100-123) fetches rooms filtered by the selected event, and + OnEventChangedAsync (lines 127-143) reloads them and clears the previous choice whenever the event + changes. The doc comment (lines 95-99) records why this is not cosmetic: BR-130 rejects a room from another event server-side, so the dropdown must only ever offer rooms of the chosen event. [Rubric §24, Forms, Validation & UX Safety]: the client is shaped so it cannot compose a request the - server will refuse. + server will refuse, and the same applies to the schedule: CreateSessionAsync checks + SessionFormModel.HasEndAfterStart (BR-122) before posting (lines 162-167). [Rubric §19, State Management & Data Flow]: _rooms is derived state, explicitly invalidated when - its input changes rather than left to go stale, and _model.RoomId is nulled alongside it (line 120) + its input changes rather than left to go stale, and _model.RoomId is nulled alongside it (line 133) with an in-code note that keeping it would have the server reject the save. A second idea belongs to the model rather than the page: SessionCreateModel - mints the identifier client-side, which is the opposite choice from - EventCreate's Id = default.
  • + posts Id = default and leaves allocation in the reserved manual-id range to the server, the same + choice as EventCreate's.
  • Walkthrough
      -
    • OnInitializedAsync (lines 47-80) builds the breadcrumb trail (lines 49-54), wires the validation - delegate (line 57), loads the event lookup and toasts Snackbar.LoadLookupsFailed on failure - (lines 61-67), auto-selects the only event when the lookup has exactly one entry (lines 69-72, - the same single-conference convenience as RoomCreate), then calls LoadRoomsAsync.
    • -
    • LoadRoomsAsync (lines 87-110): with no event chosen it clears _rooms and returns (lines 89-93); - otherwise it fetches up to 500 rooms filtered by EventId equals <selected> (lines 95-100). On - failure it toasts and leaves the previously offered rooms in place (lines 101-107).
    • -
    • CreateSessionAsync (lines 278-319) clears _saveResult before validating (line 285), validates the - form, sets IsSaving, posts _model.ToNew(_eventId) and stores the outcome in _saveResult (lines - 297-298), and on failure calls created.NotifyOnFailure(Toast, L) (line 303) rather than a fixed - Toast.Error, so the same wording that used to only flash in the snackbar now also renders inline - through the page's ErrorSummary, which survives the snackbar timing out while the organizer is still - reading the form (in-code comment, lines 301-302). On success it clears _isDirty, toasts success, and - navigates to ConferenceRoutePaths.SessionDetails(createdSession.Id) (line 309). The finally always - clears IsSaving.
    • +
    • OnInitializedAsync (lines 60-93) builds the breadcrumb trail (lines 62-67), wires the validation + delegate (line 70), loads the event lookup and toasts Snackbar.LoadLookupsFailed on failure + (lines 74-80), auto-selects the only event when the lookup has exactly one entry (lines 82-85, + the same single-conference convenience as RoomCreate), then calls LoadRoomsAsync + (line 87).
    • +
    • LoadRoomsAsync (lines 100-123): with no event chosen it clears _rooms and returns (lines 102-106); + otherwise it fetches up to 500 rooms filtered by EventId equals <selected> (lines 108-113). On + failure it toasts and leaves the previously offered rooms in place (lines 114-120).
    • +
    • CreateSessionAsync (lines 145-194) clears both _saveResult and _scheduleError before validating + (lines 152-153) and validates the form (lines 155-160). It then applies the client-side BR-122 check: + when _model.HasEndAfterStart() is false it stores the localized + SessionFormModel.EndsAtAfterStartsAtKey text in _scheduleError (declared at line 48), toasts it as + a warning, and returns without posting (lines 162-167); the template renders _scheduleError as an + inline MudAlert until the next attempt (.../Pages/Sessions/SessionCreate.razor:43-45). Otherwise + it sets IsSaving, posts _model.ToNew(_eventId) and stores the outcome in _saveResult + (lines 172-173), and on failure calls created.NotifyOnFailure(Toast, L) (line 178) rather than a + fixed Toast.Error, so the wording also renders inline through the page's ErrorSummary, which + survives the snackbar timing out while the organizer is still reading the form (in-code comment, + lines 176-177). On success it clears _isDirty, toasts success, and navigates to + ConferenceRoutePaths.SessionDetails(createdSession.Id) (line 184). The finally always clears + IsSaving.
    • The template shows the event picker only when it is worth showing, ShowEventPicker="@(_eventLookup is not null && _eventLookup.Count > 1)" - (.../Pages/Session/SessionCreate.razor:29), and passes _rooms plus + (.../Pages/Sessions/SessionCreate.razor:29), and passes _rooms plus RoomPickerBeforeSchedule="true" so the dependent field is chosen before the schedule - (.../Pages/Session/SessionCreate.razor:31).
    • + (.../Pages/Sessions/SessionCreate.razor:31).
  • Why it's built this way: one create-form shape (validate, post, redirect to detail) is reused across the Conference entities so behavior stays uniform; the split date/time editing exists because MudBlazor - has no single date-time picker, so SessionFormModel composes two controls and - recombines them defensively; and the event-scoped room reload keeps the client from ever offering a - value the server will reject.
  • -
  • Where it's used: the /sessions/create route (.../Pages/Session/SessionCreate.razor:1), reached + has no single date-time picker, so SessionFormModel composes two controls, + recombines them defensively, and checks their order; and the event-scoped room reload keeps the client + from ever offering a value the server will reject.
  • +
  • Where it's used: the /sessions/create route (.../Pages/Sessions/SessionCreate.razor:1), reached from SessionList's create button; on success it hands off to SessionDetail.
  • @@ -3531,16 +3679,17 @@

    SessionDetail

    speaker, and category names resolved through SessionLookups. It is the join-heaviest detail page in the Conference UI.
  • Depends on: DetailPageBase (@inherits, - .../Pages/Session/SessionDetail.razor:6); seven service clients, + .../Pages/Sessions/SessionDetail.razor:9); seven service clients, ISessionUIService, IEventLookupService, ISpeakerLookupService, ICategoryItemLookupService, ISessionSpeakerUIService, ISessionCategoryItemUIService, and - IRoomUIService (lines 23-29), plus - IToastService (line 31); - SessionLookups (line 40) and SessionEditModel (line 74); - a shared LoadGuard helper for the load-generation protocol (line 96); the + IRoomUIService (lines 28-34), plus + IToastService (line 36); + SessionLookups (line 45) and SessionEditModel (line 71), + with SessionFormModel for the BR-122 key (line 188); + a shared LoadGuard helper for the load-generation protocol (line 99); the SessionDTO shape and the SpeakerInfo / CategoryItemInfo lookup records; Result, @@ -3548,58 +3697,67 @@

    SessionDetail

    ConferenceRoutePaths, ErrorMessages (line 113), the DeleteConfirmation component, and DomainHelper's Parse<T> extension - (line 5). Uses the + (MMCA.Common.Shared.Extensions, line 10; called at line 104). Uses the Event/Room/Speaker/Session/SessionSpeaker/SessionCategoryItem/CategoryItem aliases.
  • Concept introduced, the load generation and the join-collection editor. The route-id parsing, load-once-on-parameters, and model-based shadow editing are EventDetail's. Two things are new:
    1. A monotonic load generation, factored into a shared LoadGuard helper. LoadAsync calls - LoadGuard.Begin() (line 96) for a cancellation token and a generation number, then re-checks - LoadGuard.IsCurrent(generation) after each of its three awaits (lines 102, 123, 134) and drops its + LoadGuard.Begin() (line 99) for a cancellation token and a generation number, then re-checks + LoadGuard.IsCurrent(generation) after each of its three awaits (lines 105, 126, 137) and drops its results if a newer load has started; the token from Begin(), not PageToken, is what each of those - three calls now passes down. _loadedId is stamped synchronously before the await, so two rapid - route changes would otherwise let the later-completing fetch paint the wrong session; the generation, - not _loadedId, is what is authoritative here. The finally is guarded the same way - (LoadGuard.IsCurrent(generation), line 149), because an unconditional clear would let a superseded - response switch off the spinner the newer load just turned on. Moving the counter off a page-local - _loadGeneration field and into LoadGuard makes the same protocol available to any page that needs - it, not just this one. + three calls passes down. _loadedId is stamped synchronously before the await (line 92), so two + rapid route changes would otherwise let the later-completing fetch paint the wrong session; the + generation, not _loadedId, is what is authoritative here. The finally is guarded the same way + (LoadGuard.IsCurrent(generation), line 152), because an unconditional clear would let a superseded + response switch off the spinner the newer load just turned on. Keeping the counter in LoadGuard + rather than a page-local field makes the same protocol available to any page that needs it. [Rubric §19, State Management & Data Flow] assesses whether concurrent updates to view state are ordered: this is an explicit last-writer-wins protocol rather than an implicit one. - [Rubric §12, Performance & Scalability]: nothing is cancelled, but nothing stale is rendered - either.
    2. + [Rubric §12, Performance & Scalability]: a superseded load's results are discarded rather than painted, + so nothing stale is rendered.
    3. Join management as an add/remove/available triple, factored once. The same three-method pattern applies twice, to speakers and to category items, and both funnel through MutateChildAsync - (lines 286-306), which takes the operation, its own failure and success resource keys, and an - optional picker-clearing callback. Each mutation ends with a full LoadAsync (line 300) rather than + (lines 295-315), which takes the operation, its own failure and success resource keys, and an + optional picker-clearing callback. Each mutation ends with a full LoadAsync (line 309) rather than a local patch. [Rubric §8, Data Architecture]: the update DTO re-sends the loaded RowVersion through SessionEditModel.ToUpdated, the client half of the optimistic-concurrency token (ADR-035). + [Rubric §24, Forms, Validation & UX Safety]: the save refuses an end that is not after the start + (BR-122) on the client, through the shared SessionFormModel.HasEndAfterStart, + before it posts. [Rubric §18, UI Architecture & Component Design]: the page's remaining size comes from breadth (two join collections, four lookups, seven clients), not from bespoke mechanics; enrichment lives in SessionLookups and form shape in SessionEditModel.
  • Walkthrough
      -
    • OnInitialized (lines 42-57) builds breadcrumbs, constructs SessionLookups from - the four injected lookup clients (lines 52-53), and wires the validation delegate (line 56).
    • -
    • LoadAsync (lines 96-157): LoadGuard.Begin() for the token and generation, GetByIdAsync(id, true, token) so the join collections arrive with the record (line 101), toast ErrorMessages.NotFound and - bail when the session is missing (lines 107-112), report any other failure through NotifyOnFailure - (line 116), then hydrate the global lookups (line 122) and the event's rooms (line 133), each followed - by a LoadGuard.IsCurrent re-check.
    • -
    • Edit and save (lines 159-211): StartEditing calls _model.LoadFrom(Session) then BeginEdit; - SaveChangesAsync validates the form, posts _model.ToUpdated(Session) and refetches, both written - as a single expression chaining NotifyOnFailure into IsFailure / TryGetValue (lines 187-197), - then toasts and EndEdit.
    • -
    • Delete (lines 213-239): confirm through _deleteConfirm.ShowAsync(Session.Title) (line 220), delete, +
    • OnInitialized (lines 47-62) builds breadcrumbs, constructs SessionLookups from + the four injected lookup clients (lines 57-58), and wires the validation delegate (line 61).
    • +
    • OnParametersSetAsync (lines 85-94) loads only when the route Id differs from _loadedId.
    • +
    • LoadAsync (lines 96-157): LoadGuard.Begin() for the token and generation (line 99), + GetByIdAsync(id, true, token) so the join collections arrive with the record (line 104), toast + ErrorMessages.NotFound and bail when the session is missing (lines 110-115), report any other failure + through NotifyOnFailure (line 119), then hydrate the global lookups (line 125) and the event's rooms + (line 136), each followed by a LoadGuard.IsCurrent re-check.
    • +
    • Edit and save (lines 159-220): StartEditing calls _model.LoadFrom(Session) then BeginEdit + (lines 166-167). SaveChangesAsync clears _scheduleError (line 177, the field is declared at + line 74), validates the form (lines 179-184), then refuses a schedule whose end is not after its start: + when _model.HasEndAfterStart() is false it stores the localized + SessionFormModel.EndsAtAfterStartsAtKey text in _scheduleError, toasts it as a warning, and + returns without posting (lines 186-191); the template renders it as an inline MudAlert + (.../Pages/Sessions/SessionDetail.razor:52-54). Otherwise it posts _model.ToUpdated(Session) and + refetches, both written as a single expression chaining NotifyOnFailure into IsFailure / + TryGetValue (lines 196-206), then toasts and EndEdit (lines 208-210).
    • +
    • Delete (lines 222-248): confirm through _deleteConfirm.ShowAsync(Session.Title) (line 229), delete, navigate back to the list.
    • -
    • Joins: GetAvailableSpeakers / GetAvailableCategoryItems (lines 241-245) delegate the set +
    • Joins: GetAvailableSpeakers / GetAvailableCategoryItems (lines 250-254) delegate the set difference to SessionLookups so a picker never offers an already-assigned entry; - AddSessionSpeakerAsync (lines 247-259) and AddSessionCategoryItemAsync (lines 265-276) each + AddSessionSpeakerAsync (lines 256-268) and AddSessionCategoryItemAsync (lines 274-285) each capture their ids into locals before building the delegate, with an in-code note that nullable - flow analysis does not reach inside a lambda (line 254); the two remove methods (lines 261-263, - 278-280) are one-liners over the same MutateChildAsync.
    • + flow analysis does not reach inside a lambda (line 263); the two remove methods (lines 270-272, + 287-289) are one-liners over the same MutateChildAsync.
  • Why it's built this way: a session is the join-heavy center of the program (speakers, category @@ -3608,10 +3766,10 @@

    SessionDetail

    the load generation is what makes that reload safe when the reader is navigating quickly between sessions.
  • Where it's used: the /sessions/{Id} organizer route - (.../Pages/Session/SessionDetail.razor:1-2), reached from SessionList rows and from + (.../Pages/Sessions/SessionDetail.razor:1), reached from SessionList rows and from SessionCreate's success redirect; its "view feedback" button opens - OrganizerSessionFeedback - (.../Pages/Session/SessionDetail.razor:161-162).
  • + OrganizerSessionFeedback through + ConferenceRoutePaths.SessionFeedbackOrganizer (.../Pages/Sessions/SessionDetail.razor:182-186).
  • Caveats / not-in-source: reads pass includeChildren: true so the join collections populate; how the server populates children is outside this page.
  • @@ -3626,16 +3784,17 @@

    SessionList

    order because it transitively pulls in the most lookups and defaults.
  • Depends on: extends DataGridListPageBase<TDto> over - SessionDTO (line 19) and injects + SessionDTO (line 22) and injects ISessionUIService, IEventUIService, and - ISpeakerLookupService (lines 24-26). It uses + ISpeakerLookupService (lines 27-29), plus TimeProvider (line 31) as the + clock for the default-event computation. It uses EventDTO, SpeakerInfo, SessionStatusDisplay for the status-to-color mapping, CurrentEventDefaults (the EventDTO-typed wrapper over CurrentEventSelector), ConferenceRoutePaths, - ErrorMessages (line 224), - ListPageActions (lines 158, 218), and the + ErrorMessages (line 217), + ListPageActions (lines 151, 211), and the MobileInfiniteScrollList<TItem>, DeleteConfirmation, and ListNoRecordsContent components. Uses the Event/Room/Session/Speaker aliases.
  • @@ -3643,30 +3802,29 @@

    SessionList

    event-filtered shape RoomList, SponsorList, and SpeakerList share:
    1. A third filter with a sentinel. _searchString, _selectedStatus, and _selectedEventId - persist together (SaveFilters lines 45-54, RestoreFilters lines 56-74) and are emitted as + persist together (SaveFilters lines 49-58, RestoreFilters lines 60-78) and are emitted as Title contains, Status equals, and EventId equals server filters (ApplyFilters, - lines 204-212). The event filter needs three states, not two, so a saved "all" string + lines 197-205). The event filter needs three states, not two, so a saved "all" string distinguishes an explicit clear from no saved state at all, which is what lets the computed default - apply only on a first visit (in-code comment, line 51).
    2. + apply only on a first visit (in-code comment, line 55).
    3. Enrichment from two bulk loads instead of per-row fetches. - LoadEventsAndResolveDefaultAsync fetches events with includeChildren: true (line 96) and folds - every event's rooms into one _roomNames dictionary (PopulateRoomNames, lines 120-134), while the - speaker lookup loaded in OnInitializedAsync (line 84) backs GetSpeakerList (lines 136-144), + LoadEventsAndResolveDefaultAsync fetches events with includeChildren: true (line 100) and folds + every event's rooms into one _roomNames dictionary (PopulateRoomNames, lines 124-138), while the + speaker lookup loaded in OnInitializedAsync (line 88) backs GetSpeakerList (lines 140-148), which maps a row's SessionSpeakers to display names and skips ids the lookup does not know. Both loads are explicitly non-critical: a failed Result is simply not unwrapped, and the comments - (lines 83, 94-95) say the fallback is dash display and an unset default filter, not a broken page. - The paged fetch itself also passes includeChildren: true (lines 189, 201) so each row arrives with + (lines 87, 98-99) say the fallback is dash display and an unset default filter, not a broken page. + The paged fetch itself also passes includeChildren: true (lines 182, 194) so each row arrives with its speaker joins.
    4. -
    5. Status color coding, delegated rather than local. The page used to carry its own - GetStatusColor switch; that mapping is now SessionStatusDisplay.GetStatusColor, - so the grid's status chip (.../Pages/Session/SessionList.razor:180) and - SessionSelectionDisplay share one copy of the six-case switch instead of - each page keeping its own. +
    6. Status color coding, delegated rather than local. The mapping is + SessionStatusDisplay.GetStatusColor, called from both the mobile card + and the grid's status chip (.../Pages/Sessions/SessionList.razor:87, 190), so this page and + SessionSelectionDisplay share one copy of the switch. The startup race guard is the same one RoomList uses, with the clearest explanation in - this file: _eventsLoadTask is started before the first await (lines 78-81) and awaited inside - both LoadServerData (lines 183-184) and FetchMobilePage (lines 196-197), because ApplyFilters + this file: _eventsLoadTask is started before the first await (lines 82-85) and awaited inside + both LoadServerData (lines 176-177) and FetchMobilePage (lines 189-190), because ApplyFilters runs inside LoadServerDataAsync, so the default event must be resolved before entering it, "not - merely before the fetch delegate runs" (in-code comment, lines 181-182). + merely before the fetch delegate runs" (in-code comment, lines 174-175). [Rubric §18, UI Architecture & Component Design]: the status filter surfaces the program-committee workflow inline instead of hiding it behind a separate screen. [Rubric §23, Front-End Performance & Rendering]: one children-loaded events fetch plus one speaker @@ -3677,33 +3835,36 @@

      SessionList

  • Walkthrough
      -
    • OnInitializedAsync (lines 76-90): start the events task, load the speaker lookup (tolerating +
    • OnInitializedAsync (lines 80-94): start the events task, load the speaker lookup (tolerating failure), then await the events task.
    • -
    • ResolveDefaultEventFilter (lines 106-118): keep a restored id that still exists in _events, - otherwise fall back to CurrentEventDefaults.SelectCurrentOrNext(_events, DateTime.UtcNow)?.Id - (line 116). The comment (lines 108-109) records the case this covers: a dangling saved id would - otherwise silently show an empty grid.
    • -
    • OnSearchChanged, OnStatusChanged, and OnEventFilterChanged (lines 160-177) each update one +
    • ResolveDefaultEventFilter (lines 110-122): keep a restored id that still exists in _events, + otherwise fall back to + CurrentEventDefaults.SelectCurrentOrNext(_events, TimeProvider.GetUtcNow().UtcDateTime)?.Id + (line 120). Reading "now" from the injected TimeProvider rather than DateTime.UtcNow is what lets + a test pin the clock and assert which event becomes the default. The comment (lines 112-113) records + the case this covers: a dangling saved id would otherwise silently show an empty grid.
    • +
    • OnSearchChanged, OnStatusChanged, and OnEventFilterChanged (lines 153-170) each update one filter and reload whichever layout is active via ListPageActions.ReloadActiveLayoutAsync - (lines 157-158).
    • -
    • LoadServerData (lines 179-191) and FetchMobilePage (lines 194-202) are the desktop and mobile + (lines 150-151).
    • +
    • LoadServerData (lines 172-184) and FetchMobilePage (lines 187-195) are the desktop and mobile fetch paths over the same ApplyFilters.
    • -
    • DeleteSessionAsync (lines 217-225) delegates the confirm, delete, toast, and reload sequence to +
    • DeleteSessionAsync (lines 210-218) delegates the confirm, delete, toast, and reload sequence to ListPageActions.DeleteWithConfirmationAsync; NavigateToCreate and NavigateToDetails - (lines 227-228) route to SessionCreate and SessionDetail.
    • -
    • The grid mixes PropertyColumns for the sortable fields (Title, StartsAt, EndsAt, RoomId, - .../Pages/Session/SessionList.razor:113, 147, 156, 165) with TemplateColumns for the derived ones - (speakers, status, row actions, lines 120, 174, 189), which is what keeps server-side sorting working - on the columns that have a real backing property.
    • + (lines 220-221) route to SessionCreate and SessionDetail. +
    • The grid uses PropertyColumns for the sortable fields (Title, StartsAt, EndsAt, RoomName, + Status, .../Pages/Sessions/SessionList.razor:114, 148, 158, 171, 183) and TemplateColumns only + for the speakers and row actions (lines 121, 199). The status column keeps its chip inside a + CellTemplate of a PropertyColumn because server-side sorting keys off the property expression + (in-template comment, .../Pages/Sessions/SessionList.razor:181-182).
  • Why it's built this way: sessions are the central editable entity of the program, so the list has to answer "what is in this conference, in what state, presented by whom" at a glance; defaulting to the active event and enriching from two bulk loads keeps that view both relevant and cheap.
  • -
  • Where it's used: the /sessions organizer route (.../Pages/Session/SessionList.razor:1-2, - Authorize(Roles = "Organizer")); rows open SessionDetail and the create button - opens SessionCreate.
  • +
  • Where it's used: the /sessions organizer route (.../Pages/Sessions/SessionList.razor:1-2, + Authorize(Roles = RoleNames.Organizer)); rows open SessionDetail and the create + button opens SessionCreate.
  • Caveats / not-in-source: the page builds speaker names from its own lookup rather than trusting the paged payload alone, so it degrades to a dash rather than a wrong name when a speaker id is unknown; how the paged endpoint populates SessionSpeakers is a server-side concern outside this file.
  • @@ -3753,7 +3914,7 @@

    ActivityFormModel

  • Why it's built this way: the create form and the edit form of the same aggregate diverging is a classic and invisible defect (a field that is required on one and optional on the other). Making the shared base the only declaration site removes the possibility rather than testing for it.
  • -
  • Where it's used: subclassed by ActivityCreateModel and ActivityEditModel; bound by ActivityCreate (ActivityCreate.razor.cs:83) and ActivityDetail (ActivityDetail.razor.cs:71) through the shared ActivityFormFields component.
  • +
  • Where it's used: subclassed by ActivityCreateModel and ActivityEditModel; bound by ActivityCreate (ActivityCreate.razor.cs:84) and ActivityDetail (ActivityDetail.razor.cs:71) through the shared ActivityFormFields component.

  • ActivityCreateModel

    @@ -3766,7 +3927,7 @@

    ActivityCreateModel

  • Concept introduced: none new. It is the create half of the shared-form-model pattern ActivityFormModel teaches.
  • Walkthrough: ToNew(EventIdentifierType eventId) (:19-32) is the only member. It sends Id = default (:22) and lets the server mint the key, sets StartTime / EndTime from the base's combined StartsAt / EndsAt (:25-26), and stamps the owning event from the argument (:31). [Rubric §8, Data Architecture] (assesses a deliberate identity strategy): compare SpeakerCreateModel, which mints its own Guid client-side because a speaker's key is a GUID rather than a server-assigned int.
  • Why it's built this way: the owning event is a create-time-only decision (the class doc says so at :5-9), so it is a parameter of the mapping rather than a property on the shared base that the edit form would also have to bind.
  • -
  • Where it's used: ActivityCreate holds one instance (ActivityCreate.razor.cs:83) and calls ToNew on the save path (ActivityCreate.razor.cs:184).
  • +
  • Where it's used: ActivityCreate holds one instance (ActivityCreate.razor.cs:84) and calls ToNew on the save path (ActivityCreate.razor.cs:200).

  • ActivityEditModel

    @@ -3847,13 +4008,13 @@

    SpeakerEditModel

    • What it is: the edit buffer for the inline speaker editor on the organizer detail page: the shared fields plus load-from-record and build-the-update.
    • Depends on: SpeakerFormModel (:11) and SpeakerDTO (:1).
    • -
    • Concept introduced: the typed edit buffer, as ActivityEditModel describes it. The speaker variant preserves one more field on the way out.
    • +
    • Concept introduced: the typed edit buffer, as ActivityEditModel describes it. The speaker variant builds its update as a with expression over the loaded record, so it preserves every field it does not edit rather than a hand-picked list.
    • Walkthrough
        -
      • LoadFrom(SpeakerDTO) (:18-33): null-guards, then copies the ten editable values off the displayed speaker. The doc frames the value plainly (:15-17): opening the editor becomes one call rather than ten assignments on the page.
      • -
      • ToUpdated(SpeakerDTO) (:40-61): edited values over four preserved ones, Id (:46), RowVersion (:47, ADR-035), the recomposed FullName (:50), and LinkedUserId (:59). Preserving the linked user is the same trick ActivityEditModel uses on the owning event: linking and unlinking is its own action on the detail page (see SpeakerDetail), so the field cannot be bound and cannot be lost. [Rubric §24, Forms, Validation & UX Safety].
      • +
      • LoadFrom(SpeakerDTO) (:18-32): null-guards, then copies the ten editable values off the displayed speaker. The doc frames the value plainly (:13-16): opening the editor becomes one call rather than ten assignments on the page.
      • +
      • ToUpdated(SpeakerDTO) (:41-58): returns speaker with { ... } (:45), overriding only the ten edited values plus the recomposed FullName (:49). Everything else on the loaded record round-trips untouched, and the doc names what that covers (:34-38): the identity, the RowVersion concurrency token (ADR-035), the linked user, and fields this form does not show, such as the Top Speaker flag. Linking and unlinking is its own action on the detail page (see SpeakerDetail), so LinkedUserId cannot be bound here and cannot be lost. [Rubric §24, Forms, Validation & UX Safety].
    • -
    • Why it's built this way: an edit form that silently drops a field it does not display is the failure this shape removes; every field the editor does not own is re-sent from the loaded record, in one visible place.
    • +
    • Why it's built this way: an edit form that silently drops a field it does not display is the failure this shape removes. A fresh DTO that copies named fields resets any field added later and not listed; the with expression inverts the default, so a new field on SpeakerDTO is preserved unless this model chooses to edit it.
    • Where it's used: SpeakerDetail holds one instance (SpeakerDetail.razor.cs:65), seeds it in StartEditing (SpeakerDetail.razor.cs:201) and posts ToUpdated from SaveChangesAsync (SpeakerDetail.razor.cs:224).

    @@ -3942,20 +4103,20 @@

    ActivityCreate

    • What it is: the organizer form that schedules an activity, meaning the non-session items on a programme (registration, breaks, receptions, after-parties). It collects the name, the owning event, the start and end of the window as separate date and time pickers, the display order, and an optional off-site venue (class doc, :12-17).
    • -
    • Depends on: IActivityUIService (:20), IEventLookupService returning EventInfo (:21,78), IToastService (:23), ActivityCreateModel (:81), CurrentEventSelector (:61), ModelValidation with DataAnnotationsModelValidator (:41), ConferenceRoutePaths (:36,182,194), and ErrorMessages (:160,166). Externals: NavigationManager, MudBlazor's MudForm and BreadcrumbItem, and the IStringLocalizer<ActivityCreate> from the template (ActivityCreate.razor:6).
    • -
    • Concept introduced, the cross-field rule that lives outside the form. Every other rule on this page is declared on ActivityFormModel and executed by MudBlazor through the shared _validate delegate. The schedule window cannot be: no DataAnnotation states "end after start" across four controls. ValidateSchedule (:126-148) therefore checks the model's own HasStart / HasEnd / StartsAt / EndsAt and puts its message in _scheduleError (:130,136,142) instead of the form's error list, and the markup renders that string in its own MudAlert above the shared error summary (ActivityCreate.razor:30-33, summary at :41). A cross-field failure therefore reads like every other validation error to the organizer even though MudForm knows nothing about it. [Rubric §24, Forms, Validation & UX Safety] (assesses whether a form can express only legal input and explains a rejection in place): the check runs before anything is posted (:164) and the snackbar quotes the specific message rather than a generic one (:166). +
    • Depends on: IActivityUIService (:22), IEventLookupService returning EventInfo (:23,81), IToastService (:25), ActivityCreateModel (:84), CurrentEventSelector (:64), ModelValidation with DataAnnotationsModelValidator (:44), ConferenceRoutePaths (:39,212,224), and ErrorMessages (:187,193). Externals: NavigationManager (:24), TimeProvider (:26), MudBlazor's MudForm and BreadcrumbItem, and the IStringLocalizer<ActivityCreate> from the template (ActivityCreate.razor:6).
    • +
    • Concept introduced, the cross-field rule that lives outside the form. Every other rule on this page is declared on ActivityFormModel and executed by MudBlazor through the shared _validate delegate. The schedule window cannot be: no DataAnnotation states "end after start" across four controls. ValidateSchedule (:151-173) therefore checks the model's own HasStart / HasEnd / StartsAt / EndsAt and puts its message in _scheduleError (:155,161,167) instead of the form's error list, and the markup renders that string in its own MudAlert above the shared error summary (ActivityCreate.razor:30-33, summary at :41). A cross-field failure therefore reads like every other validation error to the organizer even though MudForm knows nothing about it. [Rubric §24, Forms, Validation & UX Safety] (assesses whether a form can express only legal input and explains a rejection in place): the check runs before anything is posted (:191) and the snackbar quotes the specific message rather than a generic one (:193). The second idea is the two-stage smart default, which removes the two most common clicks without hiding either field.
        -
      1. The event picker is seeded with CurrentEventSelector.SelectCurrentOrNext evaluated over each event's start date, end date and IANA time zone against DateTime.UtcNow (:59-66), rather than the weaker "auto-select when exactly one exists" rule.
      2. -
      3. ApplyEventDateDefaults (:109-119) then seeds both date pickers with the selected event's first day (:116-118), and re-runs on every event change through OnEventSelected (:99-103). Both stages use ??= (:59, :117-118), so a value the organizer already chose is never overwritten. [Rubric §24, Forms, Validation & UX Safety]. - The event lookup is explicitly non-critical (comment, :52-53): a failed load leaves the picker empty and the required-event check at :171 guides the user, rather than blocking the page. [Rubric §29, Resilience & Business Continuity]. - The third idea is the same inline error result alongside the toast SpeakerCreate now carries. _saveResult (:92-96) records the last create attempt's Result, reset to null before each attempt (:166) and set from the service call (:185). On failure CreateActivityAsync calls created.NotifyOnFailure(Toast, L) (:190) instead of a bare Toast.Error, so the message reaches the page's ErrorSummary component as well as the snackbar.
      4. +
      5. The event picker is seeded with CurrentEventSelector.SelectCurrentOrNext evaluated over each event's start date, end date and IANA time zone against TimeProvider.GetUtcNow().UtcDateTime (:62-69), rather than the weaker "auto-select when exactly one exists" rule. The clock is the injected TimeProvider (:26), not the static DateTime.UtcNow, so "current or next" is a dependency a test can control. The event stage uses ??= (:62), so an event already set is kept.
      6. +
      7. ApplyEventDateDefaults (:125-144) then seeds both date pickers with the selected event's first day (:132-141), and re-runs on every event change through OnEventSelected (:109-113). It remembers the day it last seeded in _seededDay (:119, set at :143), and overwrites a picker only when it is empty or still holds that seeded day (:133, :138). So an untouched date follows the next event the organizer picks, while a date the organizer chose is kept (doc, :121-124). [Rubric §24, Forms, Validation & UX Safety]. + The event lookup is explicitly non-critical (comment, :53-54): a failed load leaves the picker empty and the required-event check at :185 guides the user, rather than blocking the page. [Rubric §29, Resilience & Business Continuity]. + The third idea is the same inline error result alongside the toast SpeakerCreate now carries. _saveResult (:93-97) records the last create attempt's Result, reset to null before each attempt (:182) and set from the service call (:201). On failure CreateActivityAsync calls created.NotifyOnFailure(Toast, L) (:206) instead of a bare Toast.Error, so the message reaches the page's ErrorSummary component as well as the snackbar.
    • Walkthrough
        -
      • OnInitialized (:32-44): three breadcrumbs, then the validation delegate over _model.
      • -
      • OnInitializedAsync (:46-76): loads the events through the page _cts.Token (:54), resolves the default event, applies the date defaults (:70), and swallows OperationCanceledException as the expected disposal or InteractiveAuto transition outcome (:72-75, ADR-056).
      • -
      • CreateActivityAsync (:159-201): null-guard the form (:161-164), clear _saveResult (:166), validate and re-check _eventId is null (:168-173), run ValidateSchedule (:175-179), post _model.ToNew(_eventId.Value) (:184) and record the result to _saveResult (:185), report a failed create through NotifyOnFailure (:190), clear _isDirty before navigating (:194), and route to ConferenceRoutePaths.ActivityDetails(createdActivity.Id) (:196) using the key the server assigned. The finally always clears IsSaving (:202-205).
      • -
      • NavigateToList (:208) and the standard Dispose(bool) / Dispose pair (:212-232), guarded by _disposed so the _cts is cancelled and disposed exactly once.
      • +
      • OnInitialized (:33-45): three breadcrumbs, then the validation delegate over _model.
      • +
      • OnInitializedAsync (:47-77): loads the events through the page _cts.Token (:55), resolves the default event, applies the date defaults (:71), and swallows OperationCanceledException as the expected disposal or InteractiveAuto transition outcome (:73-76, ADR-056).
      • +
      • CreateActivityAsync (:175-222): null-guard the form (:177-180), clear _saveResult (:182), validate and re-check _eventId is null (:184-189), run ValidateSchedule (:191-195), post _model.ToNew(_eventId.Value) (:200) and record the result to _saveResult (:201), report a failed create through NotifyOnFailure (:206), clear _isDirty before navigating (:210), and route to ConferenceRoutePaths.ActivityDetails(createdActivity.Id) (:212) using the key the server assigned. The finally always clears IsSaving (:218-221).
      • +
      • NavigateToList (:224) and the standard Dispose(bool) / Dispose pair (:228-248), guarded by _disposed so the _cts is cancelled and disposed exactly once.
    • Why it's built this way: an activity belongs to exactly one event and its schedule is a window rather than a point, so the form's job is to make the window easy to enter and impossible to invert. Defaulting the event and both dates from the selected conference removes the routine clicks, and keeping the window check in the code-behind lets one message name the actual problem (a missing part versus an inverted window) instead of a generic "invalid form".
    • @@ -3995,26 +4156,26 @@

      SpeakerDashboard

    • What it is: the signed-in speaker's own console. It shows their profile with an inline editor, the sessions they present at the current or next event, the bookmark count per session, and their per-session feedback on demand (class doc, :13-19).
    • -
    • Depends on: ISpeakerUIService (:23), ISpeakerDashboardUIService (:24), IEventLookupService (:25), AuthenticationStateProvider (:26), IToastService (:27), SpeakerDTO, SessionDTO, SessionFeedbackDTO (:38-42), CurrentEventSelector with EventInfo (:169), and the IStringLocalizer<SpeakerDashboard> from the template.
    • +
    • Depends on: ISpeakerUIService (:24), ISpeakerDashboardUIService (:25), IEventLookupService (:26), AuthenticationStateProvider (:27), IToastService (:28), TimeProvider (:29), SpeakerDTO (:36), SessionDTO, SessionFeedbackDTO (:40-44), CurrentEventSelector with EventInfo (:163,171-176), and the IStringLocalizer<SpeakerDashboard> from the template.
    • Concept introduced, the claim-scoped self-service page. Three mechanisms distinguish this from the organizer pages in this unit.
        -
      1. Identity comes from the token, not the route. There is no [Parameter] id: the speaker is read from the speaker_id claim (:76-86), and a missing or unparseable claim sets _hasSpeakerId = false (:81) and renders one informational alert (SpeakerDashboard.razor:19-21). The server enforces the same scope; the claim just decides what this page asks for. [Rubric §11, Security] (assesses that a self-service view derives its subject from the authenticated principal, ADR-004) and [Rubric §26, Front-End Security].
      2. -
      3. Skip the loads on the prerender pass. OnInitializedAsync returns immediately when !RendererInfo.IsInteractive (:68-71), with the comment recording exactly what that saves: the profile, the sessions and the per-session bookmark counts used to run twice per visit, once for SSR prerender and once for the interactive instance (:65-67). The prerender pass shows the loading skeleton instead. [Rubric §23, Front-End Performance & Rendering] and ADR-056.
      4. -
      5. One batched count call. GetSessionBookmarkCountsAsync(_speakerId, sessionIds, ...) (:136) returns every count from one grouped query; the comment records that each count was previously its own cross-service hop, HTTP to Conference to gRPC to Engagement (:129-131). The result is best effort: a failure is ignored with no snackbar so the dashboard still renders (:134-135). [Rubric §12, Performance & Scalability] and [Rubric §29, Resilience & Business Continuity]. +
      6. Identity comes from the token, not the route. There is no [Parameter] id: the speaker is read from the speaker_id claim (:78-88), and a missing or unparseable claim sets _hasSpeakerId = false (:83) and renders one informational alert (SpeakerDashboard.razor:19-21). The server enforces the same scope; the claim just decides what this page asks for. [Rubric §11, Security] (assesses that a self-service view derives its subject from the authenticated principal, ADR-004) and [Rubric §26, Front-End Security].
      7. +
      8. Skip the loads on the prerender pass. OnInitializedAsync returns immediately when !RendererInfo.IsInteractive (:70-73), with the comment recording exactly what that saves: the profile, the sessions and the per-session bookmark counts used to run twice per visit, once for SSR prerender and once for the interactive instance (:67-69). The prerender pass shows the loading skeleton instead. [Rubric §23, Front-End Performance & Rendering] and ADR-056.
      9. +
      10. One batched count call. GetSessionBookmarkCountsAsync(_speakerId, sessionIds, ...) (:138) returns every count from one grouped query; the comment records that each count was previously its own cross-service hop, HTTP to Conference to gRPC to Engagement (:131-133). The result is best effort: a failure is ignored with no snackbar so the dashboard still renders (:136-137). [Rubric §12, Performance & Scalability] and [Rubric §29, Resilience & Business Continuity]. The read scope is worth stating explicitly, because the class doc does (:16-19): a speaker is not a privileged reader, so the server returns only publicly visible sessions (accepted-or-unset), which means a submission still under review does not appear here. The empty-state copy names that. [Rubric §11, Security]. - Reads go through ISpeakerDashboardUIService rather than the shared sessions service on purpose: the comment at :104-106 records that the dashboard path bypasses the shared sessions output cache, so a just-made speaker assignment shows immediately instead of lagging behind a cached public list.
      11. + Reads go through ISpeakerDashboardUIService rather than the shared sessions service on purpose: the comment at :106-108 records that the dashboard path bypasses the shared sessions output cache, so a just-made speaker assignment shows immediately instead of lagging behind a cached public list.
    • Walkthrough
        -
      • State (:36-55): the claim flag and id, the full session list and the event-narrowed one, the current event name, and four collections keyed by session id, _bookmarkCounts, _sessionFeedback, _expandedSessions and _feedbackLoading, plus the profile edit fields.
      • -
      • OnInitializedAsync (:57-159): breadcrumbs, the interactivity gate, the claim read, the profile load (a not-found renders the empty dashboard without a toast, :92-99), the session load ordered by StartsAt (:114), the current-event narrowing (:116-127), and the batched counts. The outer catch (Exception) (:149-154) is explained in place: the authentication state provider is the one collaborator that still reports failure by throwing, while everything else returns a Result.
      • -
      • ResolveCurrentEventAsync (:161-175): non-critical by design (comment, :163), it returns null on a failed lookup and the page then shows all of the speaker's sessions rather than none.
      • -
      • Profile edit (:177-249): StartEditingProfile copies six fields into _edit* shadow fields (:184-189); SaveProfileAsync rebuilds a full SpeakerDTO carrying RowVersion (:208) and every field the speaker may not edit re-sent unchanged (:209-211,214-215,220), posts, re-fetches (:230) and leaves edit mode.
      • -
      • ToggleFeedbackAsync (:251-289): the HashSet.Add return value doubles as the toggle (:253-257), an already-cached session returns immediately (:259-262), and the fetch sets a per-session loading flag with an explicit StateHasChanged() (:264-265) because the spinner has to appear before the await. A not-found is treated as "no feedback captured yet" and left to the panel's empty state rather than reported (:274-279). [Rubric §23, Front-End Performance & Rendering]: feedback is fetched per session on first expand, never for the whole list.
      • +
      • State (:38-57): the claim flag and id, the full session list and the event-narrowed one, the current event name, and four collections keyed by session id, _bookmarkCounts, _sessionFeedback, _expandedSessions and _feedbackLoading, plus the profile edit fields.
      • +
      • OnInitializedAsync (:59-161): breadcrumbs, the interactivity gate, the claim read, the profile load (a not-found renders the empty dashboard without a toast, :92-102), the session load ordered by StartsAt (:116), the current-event narrowing (:118-129), and the batched counts. The outer catch (Exception) (:151-156) is explained in place: the authentication state provider is the one collaborator that still reports failure by throwing, while everything else returns a Result.
      • +
      • ResolveCurrentEventAsync (:163-177): non-critical by design (comment, :165), it returns null on a failed lookup and the page then shows all of the speaker's sessions rather than none. "Now" is the injected TimeProvider.GetUtcNow().UtcDateTime (:176), not the static clock.
      • +
      • Profile edit (:179-246): StartEditingProfile copies six fields into _edit* shadow fields (:186-191); SaveProfileAsync builds the update as Speaker with { ... } over the loaded record (:210-218), overriding only the six edited fields, so the identity, RowVersion and every field the speaker may not edit round-trip unchanged. The comment (:207-209) names the case this protects: the Top Speaker flag is preserved, so an organizer who is also the linked speaker does not reset it by saving a bio. It then posts (:220), re-fetches (:227) and leaves edit mode (:236). This is the same with shape SpeakerEditModel uses for the organizer editor.
      • +
      • ToggleFeedbackAsync (:248-286): the HashSet.Add return value doubles as the toggle (:250-254), an already-cached session returns immediately (:256-259), and the fetch sets a per-session loading flag with an explicit StateHasChanged() (:261-262) because the spinner has to appear before the await. A not-found is treated as "no feedback captured yet" and left to the panel's empty state rather than reported (:271-276). [Rubric §23, Front-End Performance & Rendering]: feedback is fetched per session on first expand, never for the whole list.
    • Why it's built this way: the dashboard is the one page a speaker sees on conference day, so it favors rendering something useful over rendering everything: a failed count load, a failed event lookup, or missing feedback each degrade to a smaller view rather than an error page.
    • Where it's used: the /speaker/dashboard route with a bare [Authorize] (SpeakerDashboard.razor:1-2); the claim, not the role, decides what it can show. It reads the same aggregate SpeakerDetail edits.
    • -
    • Caveats / not-in-source: the profile editor here does not use SpeakerFormModel. It binds six loose _edit* fields (:49-54) and its caps come from SpeakerDTO constants applied as MudBlazor MaxLength / Counter affordances in markup (SpeakerDashboard.razor:42-55), so it has no DataAnnotations pass and its MudForm (:55) is never validated in the code-behind. The organizer create and edit forms share their rules; this third editing surface does not.
    • +
    • Caveats / not-in-source: the profile editor here does not use SpeakerFormModel. It binds six loose _edit* fields (:51-56) and its caps come from SpeakerDTO constants applied as MudBlazor MaxLength / Counter affordances in markup (SpeakerDashboard.razor:42-55), so it has no DataAnnotations pass and its MudForm (:57) is never validated in the code-behind. The organizer create and edit forms share their rules; this third editing surface does not.

    ActivityList

    @@ -4027,7 +4188,7 @@

    ActivityList

  • Concept introduced, the event-filtered list page as a thin override set. Once the base class owns the event lookup, the persisted eventId filter and the current-or-next default, a list page is reduced to five small overrides, and this page is the clearest place to read them.
    • SavePageFilters / RestorePageFilters (:39-43) persist only the page's own filter, the search string; the event choice is the base's business.
    • ReloadForEventFilterAsync (:48) tells the base how to refresh this page, which here means "reload whichever layout is on screen".
    • -
    • ApplyFilters (:69-74) adds the search term as a contains filter on Name and then calls the base's ApplyEventFilter (:73), which adds EventId equals when a scope is selected (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Common/EventFilteredListPageBase.cs:193-198). For activities that key needs no special handling: EventId is a real Activity column, so it travels straight through the generic filter pipeline (class doc, :14-16). Contrast SpeakerList.
    • +
    • ApplyFilters (:69-74) adds the search term as a contains filter on Name and then calls the base's ApplyEventFilter (:73), which adds EventId equals when a scope is selected (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Common/EventFilteredListPageBase.cs:194-199). For activities that key needs no special handling: EventId is a real Activity column, so it travels straight through the generic filter pipeline (class doc, :14-16). Contrast SpeakerList.
    • GridRef (:28) hands the base the grid instance it needs to reload. [Rubric §15, Best Practices & Code Quality] (assesses whether shared behavior is factored rather than repeated) and [Rubric §1, SOLID]: the base defines the algorithm, the page supplies the varying steps. The startup race the base solves is still visible in the page: both fetch paths await WaitForEventsAsync() first (:60, :79), because the grid's first ServerData call can run ahead of initialization and ApplyFilters executes inside the base's LoadServerDataAsync (in-code comment, :58-59). Miss that await and the first page loads unscoped. @@ -4056,7 +4217,7 @@

      SpeakerList

    • What it is: the organizer browse page for speakers: server-side paging with a full-name search and avatars, an event filter, a mobile card layout, and delete-with-confirmation.
    • Depends on: extends EventFilteredListPageBase<TDto> closed over SpeakerDTO (:18), and injects ISpeakerUIService (:23). It uses ListPageActions (:42,86), ErrorMessages (:92), ConferenceRoutePaths (:95-96), the MobileInfiniteScrollList<TItem> and DeleteConfirmation components (:31-32), and Result<T> (:73).
    • Concept introduced, the virtual filter key. Structurally this is ActivityList: the same five overrides over the same base, the same WaitForEventsAsync guard before both fetches (:56, :75), the same delegation of delete to ListPageActions. One thing genuinely differs, and it is the interesting part. - ApplyEventFilter (:69) adds filters["EventId"] exactly as it does for activities, but a Speaker has no EventId column: a speaker relates to an event through the EventSpeaker and SessionSpeaker joins. So EventId here is a virtual filter key. The paged speakers endpoint intercepts it, removes it from the filter dictionary before the generic filter pipeline ever sees it, and resolves the scope through those joins instead (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:110-112,142-149). The client-side contract is therefore identical for both entities while the server-side resolution is not. [Rubric §9, API & Contract Design] (assesses whether a query contract can express a client's intent without leaking the storage shape) and [Rubric §8, Data Architecture]: the join stays server-side, where the indexes are, instead of becoming a two-step client fetch. + ApplyEventFilter (:69) adds filters["EventId"] exactly as it does for activities, but a Speaker has no EventId column: a speaker relates to an event through the EventSpeaker and SessionSpeaker joins. So EventId here is a virtual filter key. The paged speakers endpoint intercepts it, removes it from the filter dictionary before the generic filter pipeline ever sees it, and resolves the scope through those joins instead (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakersController.cs:119-121,151-158). The client-side contract is therefore identical for both entities while the server-side resolution is not. [Rubric §9, API & Contract Design] (assesses whether a query contract can express a client's intent without leaking the storage shape) and [Rubric §8, Data Architecture]: the join stays server-side, where the indexes are, instead of becoming a two-step client fetch. The class doc records this in one sentence at the top of the page (:13-16), which matters: a reader who assumes EventId is a column would look for it on the DTO and find nothing.
    • Walkthrough
      • SavePageFilters / RestorePageFilters (:35-39): persist and restore the search term only.
      • @@ -4625,32 +4786,34 @@

        PartnerCreate

        detail page for the record it just made.
      • Depends on: IPartnerUIService (line 21) for the post, IEventLookupService (line 22) for the event picker, - IToastService (line 24), + IToastService (line 24), the injected + TimeProvider (line 25) as the clock for the event default, CurrentEventSelector for the default event - (lines 60-67), ConferenceRoutePaths (lines 37,127,139), + (lines 61-68), ConferenceRoutePaths (lines 38,128,140), ResultUiExtensions for NotifyOnFailure - (line 121), + (line 122), ModelValidation and DataAnnotationsModelValidator - (line 42). Externals: Blazor ([Inject], NavigationManager, OnInitialized/OnInitializedAsync), - MudBlazor (MudForm, BreadcrumbItem, Icons.Material.Filled.Home).
      • + (line 43). Externals: Blazor ([Inject], NavigationManager, OnInitialized/OnInitializedAsync), + MudBlazor (MudForm, BreadcrumbItem, Icons.Material.Filled.Home), .NET TimeProvider.
      • Concept introduced: none new. This is the same create-page shape taught on - ConferenceCategoryCreate: validate before mutate (lines 106-110, warns - and returns before the service call), a failed save recorded on _saveResult (line 86, cleared at - line 103, set again at line 116) and reported through createResult.NotifyOnFailure(Toast, L) - (line 121) instead of a bare Toast.Error, dirty tracking cleared before the post-save navigation - (_isDirty = false at line 125, with the reason on the line), and cancel-on-disposal through a private - CancellationTokenSource and the standard Dispose(bool) pair (lines 26,143-157, OperationCanceledException + ConferenceCategoryCreate: validate before mutate (lines 107-111, warns + and returns before the service call), a failed save recorded on _saveResult (line 87, cleared at + line 104, set again at line 117) and reported through createResult.NotifyOnFailure(Toast, L) + (line 122) instead of a bare Toast.Error, dirty tracking cleared before the post-save navigation + (_isDirty = false at line 126, with the reason on the line), and cancel-on-disposal through a private + CancellationTokenSource and the standard Dispose(bool) pair (lines 27,144-158, OperationCanceledException caught as the expected teardown or InteractiveAuto transition, ADR-056). One addition over that shape: an event picker.
        • The event defaults, it does not require a click. OnInitializedAsync loads the event lookup and then, if no event id is already chosen, calls - CurrentEventSelector.SelectCurrentOrNext(_events.Values, e => e.StartDate, e => e.EndDate, e => e.TimeZone, DateTime.UtcNow)?.Id (lines 60-67). A failed lookup is treated as non-critical: it - leaves the picker empty and the required-field error on _eventId guides the user (comment, - lines 51-52).
        • + CurrentEventSelector.SelectCurrentOrNext(_events.Values, e => e.StartDate, e => e.EndDate, e => e.TimeZone, TimeProvider.GetUtcNow().UtcDateTime)?.Id (lines 61-68). "Now" comes from the injected + TimeProvider (line 25) rather than the static clock, so a test can pin which event counts as current + or next. A failed lookup is treated as non-critical: it leaves the picker empty and the required-field + error on _eventId guides the user (comment, lines 52-53).
        • The save guards on the event, not only the form. CreatePartnerAsync returns with a warning - toast when !_form.IsValid || _eventId is null (lines 106-110), so a partner cannot be created + toast when !_form.IsValid || _eventId is null (lines 107-111), so a partner cannot be created without an owning event even though the event is not one of PartnerFormModel's own fields. [Rubric §24, Forms, Validation & UX Safety] and [Rubric §29, Resilience & Business Continuity]: an event-lookup failure degrades to a guided empty picker rather than an unhandled error. @@ -4658,21 +4821,22 @@

          PartnerCreate

      • Walkthrough
          -
        • OnInitialized (lines 31-43) builds the Home / Partners / Create breadcrumb trail (lines 34-39) - and builds the validation delegate (line 42).
        • -
        • OnInitializedAsync (lines 45-73): loads _events from - IEventLookupService (line 53), then defaults _eventId through - CurrentEventSelector (lines 60-67), with OperationCanceledException swallowed (lines 69-72).
        • -
        • Form state: _events (line 77), the _model (line 78), _eventId (line 79), the captured - MudForm (line 80), _saveResult (line 86), and _isDirty (line 88).
        • -
        • CreatePartnerAsync (lines 96-137): null-guard the form, clear _saveResult (line 103), validate - and check _eventId, set IsSaving (line 112), post _model.ToNew(_eventId.Value) (line 115), - record the result (line 116), and notify on failure through createResult.NotifyOnFailure(Toast, L) - (lines 119-122), clear the dirty flag, toast success, and redirect to - ConferenceRoutePaths.PartnerDetails(created.Id) (line 127). The finally always clears IsSaving - (lines 133-136).
        • -
        • NavigateToList (line 139) routes through ConferenceRoutePaths.
        • -
        • Disposal (lines 143-163): standard Dispose(bool) pattern over the private CancellationTokenSource.
        • +
        • OnInitialized (lines 32-44) builds the Home / Partners / Create breadcrumb trail (lines 35-40) + and builds the validation delegate (line 43).
        • +
        • OnInitializedAsync (lines 46-74): loads _events from + IEventLookupService (line 54), then defaults _eventId through + CurrentEventSelector against TimeProvider.GetUtcNow().UtcDateTime (lines 61-68), with + OperationCanceledException swallowed (lines 70-73).
        • +
        • Form state: _events (line 78), the _model (line 79), _eventId (line 80), the captured + MudForm (line 81), _saveResult (line 87), and _isDirty (line 89).
        • +
        • CreatePartnerAsync (lines 97-138): null-guard the form, clear _saveResult (line 104), validate + and check _eventId, set IsSaving (line 113), post _model.ToNew(_eventId.Value) (line 116), + record the result (line 117), and notify on failure through createResult.NotifyOnFailure(Toast, L) + (lines 120-123), clear the dirty flag, toast success, and redirect to + ConferenceRoutePaths.PartnerDetails(created.Id) (line 128). The finally always clears IsSaving + (lines 134-137).
        • +
        • NavigateToList (line 140) routes through ConferenceRoutePaths.
        • +
        • Disposal (lines 142-164): standard Dispose(bool) pattern over the private CancellationTokenSource.
      • Why it's built this way: one create shape repeated per entity, with the event picker as the one @@ -4987,61 +5151,61 @@

        QuestionEditModel

      RoomCreateModel

      -

      MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI.Pages.Rooms · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Rooms/RoomCreateModel.cs:12 · Level 3 · sealed class

      +

      MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI.Pages.Rooms · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Rooms/RoomCreateModel.cs:11 · Level 3 · sealed class

      • What it is: the create-page binding target. It adds no fields to RoomFormModel (line 12), only the one method that turns the entered values into the RoomDTO the page posts.
      • Depends on: RoomFormModel (base), - RoomDTO, and the EventIdentifierType alias from - MMCA.ADC.Conference.Shared.Events (line 2). Externals: - System.Security.Cryptography.RandomNumberGenerator (line 1).
      • -
      • Concept introduced, the client-minted primary key. Almost every create page in this codebase - posts Id = default and lets the server mint the key. - SponsorCreateModel does exactly that - (.../Pages/Sponsor/SponsorCreateModel.cs:22). Rooms are the exception: ToNew fills - Id = RandomNumberGenerator.GetInt32(100_000, int.MaxValue) (line 23). The reason is upstream of - the UI. Room identity is app-assigned, not database-generated, because the int primary key - is the Sessionize room id - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/Room.cs:11), which is how an - imported room keeps a stable identity across refreshes. That id therefore has to come from - somewhere, and this page supplies it. The value travels intact: - RoomService.AddAsync maps dto.Id onto the request's RoomId field - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Rooms/RoomService.cs:27), the - controller passes request.RoomId into + RoomDTO from MMCA.ADC.Conference.Shared.Rooms + (line 1), and the solution-wide EventIdentifierType alias. No externals.
      • +
      • Concept introduced, the server-allocated key for an app-assigned identity. Room identity is + app-assigned, not database-generated, because the int primary key is the Sessionize room id + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/Room.cs:11, class at :13), + which is how an imported room keeps a stable identity across refreshes. An organizer-created room has + no Sessionize id, so someone has to pick one, and that someone is the server, not this page. ToNew + posts Id = default (line 22, with the in-code note at line 21), exactly like + SponsorCreateModel (.../Pages/Sponsors/SponsorCreateModel.cs:22). + RoomService.AddAsync then sends a default id as null in the request's RoomId + field, because an explicit 0 would otherwise read as a client-chosen id + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Rooms/RoomService.cs:27-29). + The controller passes request.RoomId into AddRoomCommand - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:211-213), - and AddRoomHandler respects an explicit id, - auto-allocating one only when command.RoomId is null - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/AddRoom/AddRoomHandler.cs:122-145). + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:221), + and AddRoomHandler, on command.RoomId is null, + takes the highest id already in the reserved range + EventInvariants.RoomManualIdRangeStart to RoomManualIdRangeEnd (999_999_000 to 999_999_999, + EventInvariants at + .../MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:66,69) plus one, or the range start when it + is empty, failing with Room.ManualIdRangeExhausted past the end + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Events/UseCases/AddRoom/AddRoomHandler.cs:125-144). + RoomSyncStrategy skips any Sessionize room + whose id falls in that range (.../Events/UseCases/RefreshFromSessionize/RoomSyncStrategy.cs:95-97), + so organizer rooms and imported rooms cannot collide. [Rubric §8, Data Architecture] assesses how identity is allocated and whether it stays stable: - this is a deliberate trade of database-generated keys for import-stable ones, and the UI pays for it - by having to mint a key itself.
      • + this is a deliberate trade of database-generated keys for import-stable ones, with allocation of the + non-imported keys kept in one place on the server.
      • Walkthrough
          -
        • ToNew(EventIdentifierType eventId) (lines 20-31) is an expression-bodied factory returning a - fresh RoomDTO. Every editable property is copied - straight off the base (lines 24, 26-30).
        • -
        • EventId = eventId (line 25) comes in as a parameter rather than living on the model: the owning +
        • ToNew(EventIdentifierType eventId) (lines 18-30) is an expression-bodied factory returning a + fresh RoomDTO. Id is left at default (line 22) and + every editable property is copied straight off the base (lines 23, 25-29).
        • +
        • EventId = eventId (line 24) comes in as a parameter rather than living on the model: the owning event is picked on the page and never changed afterwards, so it is not a form field (class doc, - lines 8-10).
        • + lines 5-10).
      • Why it's built this way: keeping the create-only concern (build the posted DTO, stamp the owning event) on the create model and every shared field on the base is what lets - RoomEditModel reuse the rules without inheriting a ToNew it must not call.
      • + RoomEditModel reuse the rules without inheriting a ToNew it must not call. + Leaving the key to the handler means the reserved-range rule lives only in the application layer.
      • Where it's used: instantiated once as _model on RoomCreate - (.../Pages/Room/RoomCreate.razor.cs:24) and consumed in CreateRoomAsync - (.../Pages/Room/RoomCreate.razor.cs:84).
      • -
      • Caveats / not-in-source: RandomNumberGenerator.GetInt32(100_000, int.MaxValue) draws from a - range that does not overlap the reserved organizer-created range - EventInvariants.RoomManualIdRangeStart to RoomManualIdRangeEnd (999_999_000 to 999_999_999, - EventInvariants at - .../MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:65,68), which is the range - RoomSyncStrategy skips when a Sessionize - feed reuses an id (.../Events/UseCases/RefreshFromSessionize/RoomSyncStrategy.cs:95-97). A room - created through this page therefore sits outside that protection. Whether that has ever produced a - collision is not determinable from source.
      • + (.../Pages/Rooms/RoomCreate.razor.cs:26) and consumed in CreateRoomAsync + (.../Pages/Rooms/RoomCreate.razor.cs:95). Covered by + MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.UI.Tests/Pages/Rooms/RoomCreateModelTests.cs. +
      • Caveats / not-in-source: the next id is computed as max-plus-one from a read of the reserved range + (AddRoomHandler.cs:128-138), so two organizer creates racing each other could compute the same id; + how that surfaces (a key violation on save, a retry) is not determinable from this section's source.

      RoomEditModel

      @@ -5409,8 +5573,8 @@

      RoomDetail

      (lines 152-154). IsSaving is cleared in the finally (lines 160-163).
    • DeleteRoomAsync (lines 166-195) awaits _deleteConfirm.ShowAsync(Room.Name) and treats anything other than true as a cancel (lines 174-177), then calls - IRoomUIService.DeleteAsync(Room.Id, PageToken) (line 181), toasts and - navigates back to the list.
    • + IRoomUIService.DeleteAsync(Room.Id, Room.EventId, PageToken) (line 181), + toasts and navigates back to the list.
  • Why it's built this way: an inline editor on the same page as the read-only record keeps the @@ -5418,24 +5582,24 @@

    RoomDetail

    RoomFormFields, serve both create and edit. Re-fetching after a successful update is the honest choice: the server may have normalized values, and the page then shows what was actually stored.
  • Where it's used: it is the navigation target of RoomCreate on success - (.../Pages/Rooms/RoomCreate.razor.cs:93) and of every row link and mobile card on - RoomList (.../Pages/Rooms/RoomList.razor.cs:77, - .../Pages/Rooms/RoomList.razor:85). Its QrCodeButton points at + (.../Pages/Rooms/RoomCreate.razor.cs:107) and of every row link and mobile card on + RoomList (.../Pages/Rooms/RoomList.razor.cs:78, + .../Pages/Rooms/RoomList.razor:51,89,102). Its QrCodeButton points at ConferenceRoutePaths.RoomCheckInLink(Room.Id) (.../Pages/Rooms/RoomDetail.razor:61), which resolves to the Engagement-owned /engage/rooms/{id} landing page - (.../MMCA.ADC.Conference.UI/ConferenceRoutePaths.cs:61), so the printed code takes an attendee to + (.../MMCA.ADC.Conference.UI/ConferenceRoutePaths.cs:65), so the printed code takes an attendee to self check-in rather than to this management screen.
  • -
  • Caveats / not-in-source: the delete call binds the base - IEntityService.DeleteAsync(id, cancellationToken) - (MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/Interfaces/IEntityService.cs:66-68), whose - implementation issues DELETE rooms/{id} with no query string - (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/EntityServiceBase.cs:203-215). It does not - bind the ADC-specific overload that appends ?eventId= (.../Services/IRoomUIService.cs:13, - .../Services/RoomService.cs:40-44), which is what RoomList calls - (.../Pages/Rooms/RoomList.razor.cs:84). The API's delete route declares - [FromQuery] EventIdentifierType eventId - (.../MMCA.ADC.Conference.API/Controllers/RoomsController.cs:259-262). What the server returns for a - delete that omits it is not determinable from this source alone.
  • +
  • Caveats / not-in-source: the delete call binds the ADC-specific overload + DeleteAsync(RoomIdentifierType roomId, EventIdentifierType eventId, CancellationToken) + (.../Services/Rooms/IRoomUIService.cs:13), not the base IEntityService.DeleteAsync(id, ...). That + overload issues DELETE rooms/{roomId}?eventId={eventId} (.../Services/Rooms/RoomService.cs:42-46), + which the API's delete route needs: it declares [FromQuery] EventIdentifierType eventId and builds + new RemoveRoomCommand(eventId, id) from it + (.../MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:267-274). RoomList + calls the same overload (.../Pages/Rooms/RoomList.razor.cs:85), so both delete paths carry the + owning event. The base DeleteAsync(id, cancellationToken) is still reachable on the same service, and + a call shaped DeleteAsync(Room.Id, PageToken) would bind it and send no eventId; nothing but review + and the page tests keeps the two-argument form out.
  • RoomList

    @@ -5679,7 +5843,7 @@

    PublicSessionListFilterBar

  • Why it's built this way: pushing all filter state to the page means the same chrome can sit above both the desktop grid and the mobile card list without either layout owning a second copy of the filters.
  • -
  • Where it's used: rendered once by PublicSessionList (PublicSessionList.razor:11-21); its callbacks land on that page's OnEventFilterChanged, OnSearchChanged, OnRoomFilterChanged and OnMyScheduleToggled handlers (PublicSessionList.razor.cs:210-233).
  • +
  • Where it's used: rendered once by PublicSessionList (PublicSessionList.razor:11-21); its callbacks land on that page's OnEventFilterChanged, OnSearchChanged, OnRoomFilterChanged and OnMyScheduleToggled handlers (PublicSessionList.razor.cs:227-250).
  • IOrganizerEventFeedbackUIService

    @@ -5711,11 +5875,11 @@

    IOrganizerEventFeedbackUIService

  • Why it's built this way: exposing IReadOnlyList<T> keeps the Blazor grid simple, and the implementation absorbs the transport shape; see OrganizerEventFeedbackService - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Feedback/OrganizerFeedbackService.cs:15-64) + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Feedback/OrganizerFeedbackService.cs:15-68) for the paged request it actually issues and the ceiling that comes with it.
  • Where it's used: implemented by OrganizerEventFeedbackService, registered scoped at - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:41-42 (whose comment + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:42-43 (whose comment names BR-53 moderation), and injected into OrganizerEventFeedback (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Feedback/OrganizerEventFeedback.razor.cs:20).
  • @@ -5757,8 +5921,8 @@

    IOrganizerSessionFeedbackUIService
  • Where it's used: implemented by OrganizerSessionFeedbackService - (OrganizerFeedbackService.cs:66-110), registered scoped at - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:43, and + (OrganizerFeedbackService.cs:70-118), registered scoped at + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:44, and injected into OrganizerSessionFeedback (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Feedback/OrganizerSessionFeedback.razor.cs:20).

  • @@ -5769,14 +5933,16 @@

    OrganizerEventFeedbackService

    • What it is: an authenticated HTTP service that reads and deletes event feedback answers on - behalf of an organizer, who sees all answers (OrganizerFeedbackService.cs:15-61). It implements + behalf of an organizer, who sees all answers (OrganizerFeedbackService.cs:15-65). It implements IOrganizerEventFeedbackUIService.
    • Depends on: AuthenticatedServiceBase (its base, supplying CreateAuthenticatedClientAsync() at - MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/AuthenticatedServiceBase.cs:51 and the + MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/AuthenticatedServiceBase.cs:57 and the shared static Polly RetryPolicy at :25), ITokenStorageService (the bearer-token - source), HttpResultExecutor and + source), PagedReadAll (the read-every-page loop, + MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/PagedReadAll.cs:19), + HttpResultExecutor and ProblemDetailsResultReader (the railway conversion), PagedCollectionResult<T> (the paged envelope) and EventQuestionAnswerDTO. BCL @@ -5788,68 +5954,81 @@

      OrganizerEventFeedbackService

      constructor that forwards IHttpClientFactory and ITokenStorageService to the base (OrganizerFeedbackService.cs:15-17); every request goes through CreateAuthenticatedClientAsync() - (:31,53), so the JWT is attached centrally rather than per call. The doc comment + (:34,57), so the JWT is attached centrally rather than per call. The doc comment (OrganizerFeedbackService.cs:11-14) records the authorization intent: organizers see all answers because the server-side specification is null for organizer users, so this client simply requests the - full paged set and does no filtering of its own. Note the direction of trust: the client is not the - thing granting the wide view, the server is. The filters[...] query grammar it uses (:26) is the + full answer set and does no filtering of its own. Note the direction of trust: the client is not the + thing granting the wide view, the server is. The filters[...] query grammar it uses (:32) is the same dynamic-filter contract the Conference REST controllers expose (ADR-034), so the client needs no bespoke endpoint for a one-off filter.
    • Walkthrough
      • Endpoint (OrganizerFeedbackService.cs:19): the private const string "eventquestionanswers" resource root.
      • -
      • GetAllAnswersAsync(eventId, ct) (:21-41): builds - {Endpoint}/paged?filters[EventId].operator=equals&filters[EventId].value={eventId}&pageSize=500&includeChildren=false - with string.Create(CultureInfo.InvariantCulture, ...) (:25-26, culture-invariant so the id - renders stably); runs the GET inside HttpResultExecutor.ExecuteAsync (:28) with the request - itself wrapped in RetryPolicy.ExecuteAsync (:32-34); reads the response through - ProblemDetailsResultReader.ReadAsync<PagedCollectionResult<EventQuestionAnswerDTO>> (:35-36); - then Maps the successful envelope down to its Items, or an empty list when the body carried none - (:40). The nesting order is the point: the reader turns any response into a +
      • GetAllAnswersAsync(eventId, ct) (:21-45): hands a per-page delegate to + PagedReadAll.ReadAllAsync (:27), which calls it + for pages 1, 2, ... until a page comes back empty or the accumulated count reaches the server's + reported total, stopping after 40 pages regardless (PagedReadAll.cs:28,48-79). The in-code comment + (:25-26) names the reason: a single request would stop silently at the API's page-size clamp and the + organizer report would aggregate a truncated answer set. Each page builds + {Endpoint}/paged?filters[EventId].operator=equals&filters[EventId].value={eventId}&pageNumber={pageNumber}&pageSize={PagedReadAll.PageSize}&sortColumn=Id&sortDirection=asc&includeChildren=false + with string.Create(CultureInfo.InvariantCulture, ...) (:31-32, culture-invariant so the id + renders stably; PageSize is 500 at PagedReadAll.cs:22, and the stable Id ascending sort keeps + consecutive pages from skipping or repeating a row); runs the GET inside + HttpResultExecutor.ExecuteAsync (:28) with the request itself wrapped in + RetryPolicy.ExecuteAsync (:35-37); and reads the response through + ProblemDetailsResultReader.ReadAsync<PagedCollectionResult<EventQuestionAnswerDTO>> (:38-39). + The accumulated list is then Mapped to IReadOnlyList<EventQuestionAnswerDTO> (:44); the first + failed page short-circuits the loop and its failure comes back unchanged (PagedReadAll.cs:69-73). + The nesting order is the point: the reader turns any response into a Result, the executor turns the absence of a response - into one, and the retry policy sits inside both.
      • -
      • DeleteAnswerAsync(eventId, answerId, ct) (:43-60): builds {Endpoint}/{answerId}?eventId={eventId} - (:48, the event id is a required query argument, mirroring the parent-scoped delete shape of + into one, the retry policy sits inside both, and the paging loop sits outside all three.
      • +
      • DeleteAnswerAsync(eventId, answerId, ct) (:47-64): builds {Endpoint}/{answerId}?eventId={eventId} + (:52, the event id is a required query argument, mirroring the parent-scoped delete shape of IRoomUIService), issues the DELETE through the same executor plus retry pair - (:50-56), and reads the valueless response through ProblemDetailsResultReader.ReadAsync - (:57). It returns a bare Result: success is "the server accepted the delete", and a domain + (:54-60), and reads the valueless response through ProblemDetailsResultReader.ReadAsync + (:61). It returns a bare Result: success is "the server accepted the delete", and a domain refusal comes back as a typed failure, not an exception.
    • Why it's built this way: inheriting the authenticated base means token attachment and the Polly retry live in one shared place, and the service owns only the URL shapes and the organizer-sees-all - read. Asking for pageSize=500 in a single call keeps the organizer feedback grid simple (no - client-side paging) at the cost of a hard ceiling, see the caveat.
    • + read. Delegating the page loop to the shared + PagedReadAll keeps the organizer feedback grid + simple (it still receives one complete list) without inheriting the silent truncation a single + oversized request would suffer: per its remarks, the API clamps any requested page size to its own + maximum (PagedReadAll.cs:12-18).
    • Where it's used: registered explicitly as IOrganizerEventFeedbackUIService under a comment naming BR-53 moderation - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:41-42) and injected + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:42-43) and injected into OrganizerEventFeedback - (Pages/Feedback/OrganizerEventFeedback.razor.cs:18). Its structural twin + (Pages/Feedback/OrganizerEventFeedback.razor.cs:20). Its structural twin OrganizerSessionFeedbackService shares the same file - (OrganizerFeedbackService.cs:66) and differs only in resource root, filter key and delete scope. + (OrganizerFeedbackService.cs:70) and differs only in resource root, filter key and delete scope. It is covered by OrganizerEventFeedbackServiceTests in the Conference UI test project.
    • -
    • Caveats / not-in-source: the read is capped at pageSize=500 (OrganizerFeedbackService.cs:26); - an event with more than 500 answers would be truncated, and there is no follow-on paging in this - method. Whether the server would actually return that many is not determinable from this file: the - page-size ceiling on the endpoint side is enforced elsewhere.
    • +
    • Caveats / not-in-source: the read is bounded by PagedReadAll's runaway guard of 40 pages of + 500 rows, 20,000 answers (PagedReadAll.cs:22-28); past that the loop stops. An event with more + answers than that would still be truncated, and nothing in this class reports it. The server-side + maximum page size is configured elsewhere (ApplicationSettings.MaxPageSize, per the remarks at + PagedReadAll.cs:14), so whether a deployment lowers it below 500 is not determinable from this file.

    OrganizerSessionFeedbackService

    -

    MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI.Services.Feedback · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Feedback/OrganizerFeedbackService.cs:66 · Level 4 · class (sealed)

    +

    MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI.Services.Feedback · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Feedback/OrganizerFeedbackService.cs:70 · Level 4 · class (sealed)

    • What it is: the organizer-side read-and-moderate service for session feedback. It fetches every answer captured against one session and deletes an individual answer. It is the structural twin of OrganizerEventFeedbackService in the same file, keyed on SessionId - and the sessionquestionanswers resource (OrganizerFeedbackService.cs:66-112), and it implements + and the sessionquestionanswers resource (OrganizerFeedbackService.cs:70-120), and it implements IOrganizerSessionFeedbackUIService.

    • Depends on: AuthenticatedServiceBase - as its base (OrganizerFeedbackService.cs:68), for CreateAuthenticatedClientAsync() and the shared - static Polly RetryPolicy; HttpResultExecutor + as its base (OrganizerFeedbackService.cs:72), for CreateAuthenticatedClientAsync() and the shared + static Polly RetryPolicy; PagedReadAll for the + read-every-page loop; HttpResultExecutor and ProblemDetailsResultReader for the two halves of the Result conversion; PagedCollectionResult<T> as the paged @@ -5893,34 +6072,37 @@

      OrganizerSessionFeedbackService

      OrganizerEventFeedbackService MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Feedback/OrganizerFeedbackService.cs:15 - eventquestionanswers root (:19); filters on EventId (:26); delete scoped ?eventId= (:48) + eventquestionanswers root (:19); filters on EventId (:32); delete scoped ?eventId= (:52) OrganizerSessionFeedbackService - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Feedback/OrganizerFeedbackService.cs:66 - sessionquestionanswers root (:70); filters on SessionId (:77); delete scoped ?sessionId= (:99) + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Services/Feedback/OrganizerFeedbackService.cs:70 + sessionquestionanswers root (:74); filters on SessionId (:87); delete scoped ?sessionId= (:107)
    • Walkthrough

        -
      • private const string Endpoint = "sessionquestionanswers" (OrganizerFeedbackService.cs:70): the +
      • private const string Endpoint = "sessionquestionanswers" (OrganizerFeedbackService.cs:74): the resource root, held as a constant rather than passed to a base constructor, because there is no CRUD base to pass it to.
      • -
      • GetAllAnswersAsync(sessionId, ct) (OrganizerFeedbackService.cs:72-92) builds - {Endpoint}/paged?filters[SessionId].operator=equals&filters[SessionId].value={sessionId}&pageSize=500&includeChildren=false +
      • GetAllAnswersAsync(sessionId, ct) (OrganizerFeedbackService.cs:76-100) hands a per-page delegate + to PagedReadAll.ReadAllAsync (:82), which + keeps requesting pages until one is empty or the server's reported total is reached (the comment at + :80-81 names the truncation it prevents). Each page builds + {Endpoint}/paged?filters[SessionId].operator=equals&filters[SessionId].value={sessionId}&pageNumber={pageNumber}&pageSize={PagedReadAll.PageSize}&sortColumn=Id&sortDirection=asc&includeChildren=false with string.Create(CultureInfo.InvariantCulture, ...), so the id never formats under the user's - locale (:76-77). It runs the call inside - HttpResultExecutor.ExecuteAsync (:79), - creates the bearer-carrying client (:82), issues the GET through the inherited Polly RetryPolicy - (:83-85), reads the response into a Result<PagedCollectionResult<SessionQuestionAnswerDTO>> - (:86-87), and maps the envelope down to the item list the page wants, substituting an empty list - for a null Items (:91). The filter goes to the server, not to memory: the organizer's grid never - pulls the whole answer table.
      • -
      • DeleteAnswerAsync(sessionId, answerId, ct) (OrganizerFeedbackService.cs:94-111) builds - {Endpoint}/{answerId}?sessionId={sessionId} (:99) and DELETEs it through the same executor, - client and retry policy (:101-107), returning the valueless - Result the reader produces (:108). The parent + locale (:86-87). It runs the call inside + HttpResultExecutor.ExecuteAsync (:83), + creates the bearer-carrying client (:89), issues the GET through the inherited Polly RetryPolicy + (:90-92), and reads the response into a Result<PagedCollectionResult<SessionQuestionAnswerDTO>> + (:93-94). The loop accumulates each page's items, and the method maps the accumulated list to the + IReadOnlyList<SessionQuestionAnswerDTO> the page wants (:99). The filter goes to the server, not + to memory: the organizer's grid never pulls the whole answer table.
      • +
      • DeleteAnswerAsync(sessionId, answerId, ct) (OrganizerFeedbackService.cs:102-119) builds + {Endpoint}/{answerId}?sessionId={sessionId} (:107) and DELETEs it through the same executor, + client and retry policy (:109-115), returning the valueless + Result the reader produces (:116). The parent sessionId is not decoration: the API removes a child row by loading the owning aggregate, so a delete sent without it addresses nothing.
      @@ -5933,16 +6115,18 @@

      OrganizerSessionFeedbackService

    • Where it's used: registered explicitly (it is not an IEntityService<,> implementation, so the assembly scan does not see it) as IOrganizerSessionFeedbackUIService at - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:43, under the + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:44, under the comment naming BR-53 moderation - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:41), and injected + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:42), and injected into OrganizerSessionFeedback - (Pages/Feedback/OrganizerSessionFeedback.razor.cs:18).

      + (Pages/Feedback/OrganizerSessionFeedback.razor.cs:20).

    • -
    • Caveats / not-in-source: the read is capped at pageSize=500 in a single call - (OrganizerFeedbackService.cs:77) with no follow-on paging, so a session with more than 500 captured - answers would be truncated in the organizer grid, and nothing in this class detects the truncation.

      +
    • Caveats / not-in-source: the read is no longer a single capped call, but it is still bounded: + PagedReadAll stops after 40 pages of 500 rows + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/PagedReadAll.cs:22-28), so a session + with more than 20,000 captured answers would be truncated in the organizer grid, and nothing in this + class detects that.

    PublicSessionListView

    @@ -5958,7 +6142,7 @@

    PublicSessionListView

  • IsBookmarked (:96-97) is a dictionary lookup, so star state costs nothing per row.
  • CanBookmark (:139-143): a session is bookmarkable only when the user is authenticated, the Engagement-owned service resolved, the session is not a service session, and SessionStatuses.IsEligible accepts its status. The comment (:136-138) records this as the BR-49 allow-list (unset or "Accepted"), the same rule the server-side bookmark validation applies, precisely so the UI never shows a star the server would then reject. [Rubric §11, Security] and [Rubric §24, Forms, Validation & UX Safety].
  • ToggleBookmarkAsync (:110-137): guards re-entry with a per-session HashSet whose Add doubles as the guard test (:112, field at :83), fires Haptics.Click() (:116, a no-op off native heads), then removes or adds, catching OperationCanceledException as expected teardown or an InteractiveAuto transition (:129-132) and clearing the guard entry in the finally (:135). The per-session guard is a fixed defect worth reading: the comment at :81-82 records that a single global in-flight flag made one slow toggle swallow every other star's click, so the list stopped responding until that request came back.
  • -
  • RemoveBookmarkAsync (:139-158): a delete that comes back not-found is treated as success, because a bookmark that is already gone still leaves the user where they asked to be (:143-148, the tolerance expressed as removed.IsFailure && !removed.IsNotFound() over ResultUiExtensions.IsNotFound, MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/ResultUiExtensions.cs:315); it then clears the entry, toasts, and reloads when the My Schedule view is active so the removed row disappears (:150-157).
  • +
  • RemoveBookmarkAsync (:139-158): a delete that comes back not-found is treated as success, because a bookmark that is already gone still leaves the user where they asked to be (:143-148, the tolerance expressed as removed.IsFailure && !removed.IsNotFound() over ResultUiExtensions.IsNotFound, MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/ResultUiExtensions.cs:319); it then clears the entry, toasts, and reloads when the My Schedule view is active so the removed row disappears (:150-157).
  • AddBookmarkAsync (:160-172): a create that did not come back with a bookmark leaves the star unset, so the page reports a warning rather than a success toast that would contradict its own UI (:162-168).
  • GetSpeakerList (:174-182) maps a session's SessionSpeakers to display names through the passed-in lookup, skipping ids the lookup does not know; OnMobileCardClick (:184-185) routes to PublicSessionDetail through ConferenceRoutePaths.
  • @@ -6024,66 +6208,68 @@

    SponsorCreate

    • What it is: the organizer form that records a sold sponsorship at /sponsors/create - (.../Pages/Sponsor/SponsorCreate.razor:1-2, Authorize(Roles = "Organizer")). It collects the + (.../Pages/Sponsors/SponsorCreate.razor:1-2, Authorize(Roles = RoleNames.Organizer)). It collects the sponsor name, tier, owning event, branding links and the optional expo booth details, and the class doc is explicit that the event picker is required because sponsorships are sold per event and the owning event cannot be changed afterwards (lines 12-16).
    • -
    • Depends on: ISponsorUIService (line 19), - IEventLookupService with EventInfo (lines 20, 75), - IToastService (line 22), - SponsorCreateModel (line 76), - CurrentEventSelector (line 60), - ConferenceRoutePaths (lines 35, 113, 125), - ErrorMessages (line 97), +
    • Depends on: ISponsorUIService (line 21), + IEventLookupService with EventInfo (lines 22, 78), + IToastService (line 24), an injected BCL + TimeProvider (line 25), + SponsorCreateModel (line 79), + CurrentEventSelector (line 63), + ConferenceRoutePaths (lines 38, 128, 140), + ErrorMessages (line 109), ModelValidation with DataAnnotationsModelValidator - (line 40), and ResultUiExtensions.NotifyOnFailure - over the create result (line 121). Externals: Blazor, MudBlazor (MudForm, BreadcrumbItem), and the - IStringLocalizer<SponsorCreate> from the template (.../Pages/Sponsor/SponsorCreate.razor:6).
    • + (line 43), and ResultUiExtensions.NotifyOnFailure + over the create result (line 122). Externals: Blazor, MudBlazor (MudForm, BreadcrumbItem), and the + IStringLocalizer<SponsorCreate> from the template (.../Pages/Sponsors/SponsorCreate.razor:7).
    • Concept introduced, the split initialization and the smart event default. This page uses both - lifecycle hooks deliberately. OnInitialized (lines 29-41) does the synchronous work (breadcrumbs, - validation delegate) so the first render already has them, and OnInitializedAsync (lines 43-71) + lifecycle hooks deliberately. OnInitialized (lines 32-44) does the synchronous work (breadcrumbs, + validation delegate) so the first render already has them, and OnInitializedAsync (lines 46-74) does the network work. The pattern matters because the async hook runs after the first render and the page must not be blank or unvalidatable in between. The default-event rule is the richer of the two idioms in this group. Rather than auto-selecting only when exactly one event exists (which is what RoomCreate does at - .../Pages/Room/RoomCreate.razor.cs:56-59), it calls + .../Pages/Rooms/RoomCreate.razor.cs:65-67), it calls CurrentEventSelector.SelectCurrentOrNext over - each event's start date, end date and IANA time zone against DateTime.UtcNow (lines 58-65), so the - picker opens on the conference the organizer is most likely selling against. The ??= (line 58) - means a value the organizer already picked is never overwritten, and the picker stays open for the - rest (ShowEventPicker="true", .../Pages/Sponsor/SponsorCreate.razor:28). + each event's start date, end date and IANA time zone against the injected + TimeProvider.GetUtcNow().UtcDateTime (lines 61-68), so the picker opens on the conference the + organizer is most likely selling against, and a test can pin "now" instead of depending on the wall + clock. The ??= (line 61) means a value the organizer already picked is never overwritten, and the + picker stays open for the rest (ShowEventPicker="true", .../Pages/Sponsors/SponsorCreate.razor:29). The failure policy also differs from RoomCreate: a failed lookup here is non-critical and silently leaves the picker empty, because the required-field error then guides - the user (in-code comment, lines 49-50). Only a genuinely unusable form is escalated to a toast. + the user (in-code comment, lines 52-53). Only a genuinely unusable form is escalated to a toast. [Rubric §24, Forms, Validation & UX Safety] assesses whether a form makes the common case cheap without hiding a field: the default is a pre-selection, not a lock, and the submit still re-checks - _eventId is null alongside _form.IsValid (line 95) so a cleared picker cannot post. - [Rubric §19, State Management] assesses ownership of transient state: _isDirty (line 88) is the - page's only cross-cutting flag, set by MarkDirty (line 94) which SponsorFormFields invokes - through its OnFieldChanged callback (.../Pages/Sponsor/SponsorCreate.razor:25). A second field, - _saveResult (line 86), now holds the last create attempt's failed Result + _eventId is null alongside _form.IsValid (line 107) so a cleared picker cannot post. + [Rubric §19, State Management] assesses ownership of transient state: _isDirty (line 89) is the + page's only cross-cutting flag, set by MarkDirty (line 95) which SponsorFormFields invokes + through its OnFieldChanged callback (.../Pages/Sponsors/SponsorCreate.razor:26). A second field, + _saveResult (line 87), holds the last create attempt's failed Result so the page's ErrorSummary can render it inline alongside the form's own validation messages, because a snackbar can time out while the organizer is still reading the form.
    • Walkthrough
        -
      • OnInitialized (lines 31-43): breadcrumbs Home, Sponsors, Create with the last disabled: true - (lines 34-39), then the validation delegate (line 42, ADR-027 comment at line 41).
      • -
      • OnInitializedAsync (lines 45-73): awaits base.OnInitializedAsync() (line 47), loads the event - lookup through _cts.Token (line 53), then resolves the default (lines 60-67). - OperationCanceledException is swallowed (lines 69-72).
      • -
      • CreateSponsorAsync (lines 96-137): null-guards _form (lines 98-101), clears _saveResult (line - 103), validates and re-checks the event (lines 105-110), sets IsSaving (line 112), posts +
      • OnInitialized (lines 32-44): breadcrumbs Home, Sponsors, Create with the last disabled: true + (lines 35-40), then the validation delegate (line 43, ADR-027 comment at line 42).
      • +
      • OnInitializedAsync (lines 46-74): awaits base.OnInitializedAsync() (line 48), loads the event + lookup through _cts.Token (line 54), then resolves the default (lines 61-68). + OperationCanceledException is swallowed (lines 70-73).
      • +
      • CreateSponsorAsync (lines 97-138): null-guards _form (lines 99-102), clears _saveResult (line + 104), validates and re-checks the event (lines 106-111), sets IsSaving (line 113), posts _model.ToNew(_eventId.Value) through AddAsync and stores the result in _saveResult (lines - 115-116), and on a failed Result calls - createResult.NotifyOnFailure(Toast, L) (line 121) rather than a fixed toast, so the same wording - also surfaces inline. On success it clears _isDirty before navigating (line 125, with the - comment), toasts, and routes to ConferenceRoutePaths.SponsorDetails(created.Id) (line 127). - IsSaving is cleared in the finally (lines 133-136).
      • -
      • NavigateToList (line 139) and the _disposed-guarded dispose pair (lines 141-157) close the page + 116-117), and on a failed Result calls + createResult.NotifyOnFailure(Toast, L) (line 122) rather than a fixed toast, so the same wording + also surfaces inline. On success it clears _isDirty before navigating (line 126, with the + comment), toasts, and routes to ConferenceRoutePaths.SponsorDetails(created.Id) (line 128). + IsSaving is cleared in the finally (lines 134-137).
      • +
      • NavigateToList (line 140) and the _disposed-guarded dispose pair (lines 142-165) close the page out.
      • The template passes two extra localized strings into the shared field block, LinksHeading and - TwitterPlaceholder (.../Pages/Sponsor/SponsorCreate.razor:26-27), which is how one - SponsorFormFields component (.../Pages/Sponsor/SponsorFormFields.razor:120,123) serves both + TwitterPlaceholder (.../Pages/Sponsors/SponsorCreate.razor:27-28), which is how one + SponsorFormFields component (.../Pages/Sponsors/SponsorFormFields.razor:120,123) serves both this page and the detail editor without either owning the copy.
    • @@ -6092,7 +6278,7 @@

      SponsorCreate

      event removes the most common click; leaving the tier unset (see SponsorFormModel) forces the one choice that must not be inherited.
    • Where it's used: reached from SponsorList's create button - (.../Pages/Sponsor/SponsorList.razor.cs:98); on success it hands off to + (.../Pages/Sponsors/SponsorList.razor.cs:99); on success it hands off to SponsorDetail.

    SponsorDetail

    @@ -6293,31 +6479,33 @@

    SponsorList

    PublicSessionList

    -

    MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI.Pages.Public.Sessions · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Sessions/PublicSessionList.razor.cs:30 · Level 10 · class (Blazor code-behind)

    +

    MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI.Pages.Public.Sessions · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Sessions/PublicSessionList.razor.cs:29 · Level 10 · class (Blazor code-behind)

      -
    • What it is: the public conference schedule and the most heavily wired page in this unit. It is the container half of a three-part page (this class, PublicSessionListFilterBar, PublicSessionListView): it owns the events, room and speaker lookups, the event/room/search/My-Schedule filter state, the bookmark dictionary, and the server-paged fetch (class doc, PublicSessionList.razor.cs:21-29).
    • -
    • Depends on: extends DataGridListPageBase<TDto> closed over SessionDTO (:27); IPublicSessionScheduleService (:31) with its SessionSchedulePageRequest (:275), IEventUIService and ISpeakerLookupService (:32-33), the optional ISessionBookmarkUIService (:38); EventDTO, RoomDTO, SpeakerInfo (:52), ConferenceReadAudience (:121), CurrentEventDefaults (:167), PublicScheduleRoomOptions (:139, :152), PublicSessionListFilterState (:74, :81), ClaimsPrincipalExtensions's GetUserId (:188), and Result (:271).
    • -
    • Concept introduced, the container page with two racing loads and a dual-branch fetch. Everything the sibling list pages do once, this page does twice and then adds a mode switch.
        -
      1. Two startup tasks, both awaited by the fetch path. OnInitializedAsync (:84-112) starts _bookmarkLoadTask (:100) and _eventsLoadTask (:104) before its first await (:108). The comments (:94-103) name the exact failure each guards: the MudDataGrid's first ServerData call can run ahead of initialization, notably on in-app back-navigation where there is no SSR prerender to supply grid data, and a half-initialized _isAuthenticated == false would make the My Schedule branch silently fall through to fetching all sessions. LoadServerData (:234-246) awaits the events task before entering the base's LoadServerDataAsync, because ApplyAdditionalFilters runs inside it (:236-239), and FetchSessionsAsync (:252-284) awaits the bookmark task (:262-265). [Rubric §19, State Management & Data Flow].
      2. -
      3. Two fetch branches, both truly server-paged. In My Schedule mode with bookmarks present, the page passes the bookmarked ids as MyScheduleSessionIds on the SessionSchedulePageRequest (:281) and the service turns them into a server-side Id IN (...) filter so the server still pages (MMCA.ADC.Conference.UI/Services/Public/IPublicSessionScheduleService.cs:14-19). An empty bookmark set short-circuits to ([], 0) (:269-272) rather than issuing a query that would return the whole catalog, and the comment records the second reason for that ordering (:267-268): it returns before the offline snapshot is written, so an empty schedule never overwrites the cached programme. [Rubric §12, Performance & Scalability].
      4. -
      5. Audience-scoped filter persistence. Only privileged readers persist an event choice, expressed as the persistEventId: _isPrivileged && _eventFilterResolved argument to PublicSessionListFilterState.Save (:76), and ResolveDefaultEventFilter (:154-169) locks everyone else to the computed current or next event via CurrentEventDefaults.SelectCurrentOrNext (:167). The comment (:156-159) states the security consequence: a shared privileged URL can never pin an attendee to a different or unpublished event, because the /events fetch is published-only for them server-side. A privileged reader's restored id survives only if it still exists in the loaded set; a dangling one falls back to the computed default (:160-165). [Rubric §11, Security] and [Rubric §26, Front-End Security].
      6. -
      7. A deep link that beats saved state. [SupplyParameterFromQuery(Name = "mine")] (:65-66) carries the MAUI head's home-screen quick action into the My Schedule view, and OnInitializedAsync applies it after the base has restored saved page state so intent wins (:88-92). [Rubric §25, Navigation & Information Architecture] and ADR-042 Wave 2.
      8. -
      9. The offline snapshot lives in the service, not the page. The section comment (:286-288) states the division: IPublicSessionScheduleService keeps the last successful first page of the programme so a dead venue network still shows a schedule, and only the banner stays here. The page passes OnCacheStateChanged into the fetch (:282) and that handler flips _showingCachedData (:289), calling StateHasChanged() only on the raise, because the clear rides the render the grid does for the fresh rows anyway (:296-303). The chip itself is markup (PublicSessionList.razor:23-29). [Rubric §29, Resilience & Business Continuity] and ADR-042 Wave 3.
      10. +
      11. What it is: the public conference schedule and the most heavily wired page in this unit. It is the container half of a three-part page (this class, PublicSessionListFilterBar, PublicSessionListView): it owns the events, room and speaker lookups, the event/room/search/My-Schedule filter state, the bookmark dictionary, and the server-paged fetch (class doc, PublicSessionList.razor.cs:20-28).
      12. +
      13. Depends on: extends DataGridListPageBase<TDto> closed over SessionDTO (:29); IPublicSessionScheduleService (:33) with its SessionSchedulePageRequest (:322), IEventUIService and ISpeakerLookupService (:34-35), an injected BCL TimeProvider (:37), the optional ISessionBookmarkUIService (:41); EventDTO, RoomDTO, SpeakerInfo (:52-56), PublicReadAudience (:121), CurrentEventDefaults (:184), PublicScheduleRoomOptions (:136, :169), PublicSessionListFilterState (:79, :86), ClaimsPrincipalExtensions's GetUserId (:205), and Result (:62, :304).
      14. +
      15. Concept introduced, the container page with two racing loads, a dual-branch fetch, and a fail-closed scope. Everything the sibling list pages do once, this page does twice and then adds a mode switch.
          +
        1. Two startup tasks, both awaited by the fetch path. OnInitializedAsync (:89-117) starts _bookmarkLoadTask (:105) and _eventsLoadTask (:109) before its first await (:113). The comments (:99-104, :107-108) name the exact failure each guards: the MudDataGrid's first ServerData call can run ahead of initialization, notably on in-app back-navigation where there is no SSR prerender to supply grid data, and a half-initialized _isAuthenticated == false would make the My Schedule branch silently fall through to fetching all sessions. LoadServerData (:254-265) awaits EnsureEventScopeAsync (:258) before entering the base's LoadServerDataAsync, because ApplyAdditionalFilters runs inside it (:256-257), and FetchSessionsAsync (:294-331) awaits the bookmark load through EnsureBookmarkStateAsync (:302, method :273-288). [Rubric §19, State Management & Data Flow].
        2. +
        3. Two fetch branches, both truly server-paged. In My Schedule mode with bookmarks present, the page passes the bookmarked ids as MyScheduleSessionIds on the SessionSchedulePageRequest (:328) and the service turns them into a server-side Id IN (...) filter so the server still pages (MMCA.ADC.Conference.UI/Services/Public/IPublicSessionScheduleService.cs:14-19). An empty bookmark set short-circuits to ([], 0) (:316-319) rather than issuing a query that would return the whole catalog, and the comment records the second reason for that ordering (:314-315): it returns before the offline snapshot is written, so an empty schedule never overwrites the cached programme. [Rubric §12, Performance & Scalability].
        4. +
        5. Audience-scoped filter persistence. Only privileged readers persist an event choice, expressed as the persistEventId: _isPrivileged && _eventFilterResolved argument to PublicSessionListFilterState.Save (:81), and ResolveDefaultEventFilter (:171-186) locks everyone else to the computed current or next event via CurrentEventDefaults.SelectCurrentOrNext evaluated against the injected TimeProvider.GetUtcNow().UtcDateTime (:184). The comment (:173-176) states the security consequence: a shared privileged URL can never pin an attendee to a different or unpublished event, because the /events fetch is published-only for them server-side. A privileged reader's restored id survives only if it still exists in the loaded set; a dangling one falls back to the computed default (:177-182). Privilege itself comes from PublicReadAudience.IsPrivilegedReaderAsync (:121), which treats a missing or faulted authentication state as the public audience and lets a cancellation propagate (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/PublicReadAudience.cs:18-35). [Rubric §11, Security] and [Rubric §26, Front-End Security].
        6. +
        7. Fail closed, with a retry that heals. A failed events fetch is remembered in _eventsLoadFailed (:129, cleared again on success at :134), and ScopeUnresolvedForPublicReader (:149) is true only for a non-privileged reader with no selected event because that fetch failed; a successful fetch that simply finds no current or next event stays unscoped (doc, :147-148). In that state FetchSessionsAsync throws an InvalidOperationException rather than issue an unscoped session query (:309-312); the comment (:307-308) records that the base turns the exception into the failed grid state with a retry, and the retry funnels back through EnsureEventScopeAsync (:153-165), which re-issues the events load while the scope is still unresolved. The bookmark read follows the same rule for My Schedule: its failure is kept in _bookmarkLoadFailure (:62, set at :213), EnsureBookmarkStateAsync re-issues the read once when My Schedule is active for a signed-in user, and a still-failing read is returned (:273-288) so FetchSessionsAsync surfaces it as a failed Result carrying the same errors (:302-305). The doc comment (:267-272) names the outcome: the page shows an error, not an empty schedule. Plain browsing is unaffected, since the stars simply stay unset (:210-211). [Rubric §29, Resilience & Business Continuity] and [Rubric §11, Security].
        8. +
        9. A deep link that beats saved state. [SupplyParameterFromQuery(Name = "mine")] (:70-71) carries the MAUI head's home-screen quick action into the My Schedule view, and OnInitializedAsync applies it after the base has restored saved page state so intent wins (:93-97). [Rubric §25, Navigation & Information Architecture] and ADR-042 Wave 2.
        10. +
        11. The offline snapshot lives in the service, not the page. The section comment (:333-335) states the division: IPublicSessionScheduleService keeps the last successful first page of the programme so a dead venue network still shows a schedule, and only the banner stays here. The page passes OnCacheStateChanged into the fetch (:329) and that handler flips _showingCachedData (:345), calling StateHasChanged() only on the raise, because the clear rides the render the grid does for the fresh rows anyway (:338-350). The chip itself is markup (PublicSessionList.razor:23-29). [Rubric §29, Resilience & Business Continuity] and ADR-042 Wave 3.
      16. Walkthrough
          -
        • SaveFilters / RestoreFilters (:73-82) are two-line delegations to PublicSessionListFilterState, the restore destructuring five values straight back into the page's fields (:80-82).
        • -
        • LoadEventsAndResolveDefaultAsync (:114-148): resolve privileged status from role membership with a failed read treated as non-privileged (:116-127), fetch events with children (:129), index every event's rooms into _roomNames through PublicScheduleRoomOptions.IndexNames (:139), load the speaker lookup (:140-143), then resolve the default event and scope the room options (:146-147). A failed events fetch toasts once and deliberately skips the speaker lookup, since it only labels sessions of the events that failed to load (:132-135). One children-loaded events fetch plus one speaker lookup replace per-row enrichment calls. [Rubric §23, Front-End Performance & Rendering].
        • -
        • RefreshRoomOptions (:151-152) destructures PublicScheduleRoomOptions.Scope straight into _rooms and _selectedRoomId; it runs after the initial load (:147) and again on every event-filter change (:216).
        • -
        • LoadBookmarkStateAsync (:176-204): reads the identifier through GetUserId (:188) and loads the bookmarked session ids into the dictionary the view patches in place; a failure is non-critical, so the stars do not appear but the sessions still load (:193-197).
        • -
        • Filter handlers (:207-230) each update one field and call ReloadViewAsync (:232), which forwards to the view child's ReloadAsync() and no-ops when the child is not yet rendered.
        • -
        • ApplyAdditionalFilters (:305-323): Title contains, EventId equals, and RoomId equals. The comment on the room branch (:317-318) is worth reading against PublicSpeakerList: Session.RoomId is a real nullable column, so it rides the generic filter pipeline with no virtual-key interception in the controller, unlike the speaker page's EventId.
        • -
        • FetchMobilePage (:329-339) awaits the same events task, builds the same filters, and reuses FetchSessionsAsync, now passing an explicit title-ascending sort (:338) so the infinite-scroll list matches the desktop grid's initial sort; the API's own default is start time. Both layouts still share one fetch implementation including its offline path.
        • -
        • The optional Engagement service is resolved with GetService (:86) for the same reason as on PublicSessionDetail: [Inject] has no optional mode (:36-37).
        • -
        -
      17. -
      18. Why it's built this way: this is the highest-traffic page of the conference, viewed on bad networks by both anonymous browsers and signed-in attendees managing a personal schedule. That drives every design decision visible here: server-side everything, one enrichment fetch, ordering guarantees around the grid's eager first call, an audience-locked event filter, a room filter derived from data already in hand, and a cached last-known-good first page owned by the service rather than the page.
      19. +
      20. SaveFilters / RestoreFilters (:78-87) are two-line delegations to PublicSessionListFilterState, the restore destructuring five values straight back into the page's fields (:85-87).
      21. +
      22. LoadEventsAndResolveDefaultAsync (:119-145): resolve privileged status through PublicReadAudience (:121), fetch events with children (:123), index every event's rooms into _roomNames through PublicScheduleRoomOptions.IndexNames (:136), load the speaker lookup (:137-140), then resolve the default event and scope the room options (:143-144). A failed events fetch sets _eventsLoadFailed, toasts once, and deliberately skips the speaker lookup, since it only labels sessions of the events that failed to load (:124-131). One children-loaded events fetch plus one speaker lookup replace per-row enrichment calls. [Rubric §23, Front-End Performance & Rendering].
      23. +
      24. EnsureEventScopeAsync (:153-165): awaits the in-flight events task, then re-issues the load only while ScopeUnresolvedForPublicReader holds; both the grid path (:258) and the mobile path (:375) call it.
      25. +
      26. RefreshRoomOptions (:168-169) destructures PublicScheduleRoomOptions.Scope straight into _rooms and _selectedRoomId; it runs after the initial load (:144) and again on every event-filter change (:236).
      27. +
      28. LoadBookmarkStateAsync (:193-224): reads the identifier through GetUserId (:205) and loads the bookmarked session ids into the dictionary the view patches in place; a failed read is non-critical for browsing (the stars do not appear but the sessions still load) and is kept in _bookmarkLoadFailure for the My Schedule path (:210-213).
      29. +
      30. Filter handlers (:227-250) each update one field and call ReloadViewAsync (:252), which forwards to the view child's ReloadAsync() and no-ops when the child is not yet rendered.
      31. +
      32. ApplyAdditionalFilters (:352-370): Title contains, EventId equals, and RoomId equals. The comment on the room branch (:364-365) is worth reading against PublicSpeakerList: Session.RoomId is a real nullable column, so it rides the generic filter pipeline with no virtual-key interception in the controller, unlike the speaker page's EventId.
      33. +
      34. FetchMobilePage (:373-382) awaits the same EnsureEventScopeAsync (:375), builds the same filters, and reuses FetchSessionsAsync, passing an explicit title-ascending sort (:381) so the infinite-scroll list matches the desktop grid's initial sort; the API's own default is start time. Both layouts share one fetch implementation including its offline path and its fail-closed guard.
      35. +
      36. The optional Engagement service is resolved with GetService (:91) for the same reason as on PublicSessionDetail: [Inject] has no optional mode (:39-40).
      37. +
    + +
  • Why it's built this way: this is the highest-traffic page of the conference, viewed on bad networks by both anonymous browsers and signed-in attendees managing a personal schedule. That drives every design decision visible here: server-side everything, one enrichment fetch, ordering guarantees around the grid's eager first call, an audience-locked event filter that refuses to run unscoped when its scope could not be loaded, a room filter derived from data already in hand, and a cached last-known-good first page owned by the service rather than the page.
  • Where it's used: the /conference/sessions route (PublicSessionList.razor:1, matching ConferenceRoutePaths.PublicSessions at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/ConferenceRoutePaths.cs:47), including the ?mine=true deep link; it renders PublicSessionListFilterBar (PublicSessionList.razor:11-21) and PublicSessionListView (:31) and routes onward to PublicSessionDetail.
  • IActivityUIService

    @@ -6350,7 +6538,7 @@

    IActivityUIService

    There is a second, load-bearing reason for the body-less specialization: registration is done by a Scrutor assembly scan, not by hand. AddUIModule<ConferenceUIModule>() scans the Conference UI assembly for every IEntityService<,> implementation and registers it with a scoped lifetime - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:28-30), so the + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:29-31), so the named marker is exactly what a page gets to inject. Every plain-CRUD sibling in this group repeats this shape.
  • Walkthrough: no members. The whole contract is "be an IEntityService bound to ActivityDTO plus @@ -6500,7 +6688,7 @@

    ISessionSelectionUIService

    second endpoint and no polling contract on the service: polling is a rendering decision, and it lives beside the page.
  • Where it's used: implemented by SessionSelectionService, registered - explicitly (DependencyInjection.cs:46, explicit because it is not an IEntityService<,> and the + explicitly (DependencyInjection.cs:47, explicit because it is not an IEntityService<,> and the assembly scan would not find it), injected into SessionSelectionDashboard (Pages/SessionSelection/SessionSelectionDashboard.razor.cs:17) and taken as a constructor argument @@ -6555,7 +6743,7 @@

    ISponsorUIService

  • Why it's built this way: sponsor management is plain CRUD from the client's point of view, so the contract adds nothing; the named marker exists so the assembly scan inside AddUIModule<ConferenceUIModule>() can bind a concrete implementation to a name the pages inject - (DependencyInjection.cs:30).
  • + (DependencyInjection.cs:31).
  • Where it's used: implemented by SponsorService; injected into SponsorList (Pages/Sponsor/SponsorList.razor.cs:23), SponsorDetail (Pages/Sponsor/SponsorDetail.razor.cs:22), @@ -6604,8 +6792,8 @@

    ActivityService

  • Where it's used: never named in DI by hand. Because it is an IEntityService<,> implementation in the Conference UI assembly, the Scrutor scan inside AddUIModule<ConferenceUIModule>() registers it AsImplementedInterfaces() with a scoped lifetime - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:30 calling - MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:273-279), which is what makes + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:31 calling + MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:289-295), which is what makes IActivityUIService resolvable in ActivityList (Pages/Activity/ActivityList.razor.cs:24), ActivityDetail (Pages/Activity/ActivityDetail.razor.cs:23), ActivityCreate @@ -6641,7 +6829,7 @@

    PartnerService

    closes the leaf.
  • Where it's used: never named in DI by hand. Because it is an IEntityService<,> implementation in the Conference UI assembly, the same Scrutor scan inside AddUIModule<ConferenceUIModule>() - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:28-30) registers it + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:29-31) registers it AsImplementedInterfaces() with a scoped lifetime, which is what makes IPartnerUIService resolvable in PartnerCreate (Pages/Partners/PartnerCreate.razor.cs:20), PartnerDetail @@ -6673,7 +6861,7 @@

    QuestionService

    IQuestionUIService and keeps the resource name in exactly one place.
  • Where it's used: picked up automatically by the Scrutor scan inside AddUIModule<ConferenceUIModule>() - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:30), so no explicit + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:31), so no explicit AddScoped line exists for it. Injected as IQuestionUIService into QuestionList (Pages/Question/QuestionList.razor.cs:16), QuestionDetail (Pages/Question/QuestionDetail.razor.cs:18), @@ -6693,11 +6881,11 @@

    RoomService

    EntityServiceBase<TEntityDTO, TIdentifierType> over "rooms" but overrides AddAsync to reshape the create payload, and adds the parent-scoped DeleteAsync(roomId, eventId) declared by IRoomUIService - (RoomService.cs:14-45).
  • + (RoomService.cs:14-47).
  • Depends on: EntityServiceBase<TEntityDTO, TIdentifierType>, its Endpoint property (EntityServiceBase.cs:51) and both SendRequestAsync overloads - (EntityServiceBase.cs:324,354); + (EntityServiceBase.cs:328,358); ITokenStorageService; RoomDTO; IRoomUIService; Result; the RoomIdentifierType / @@ -6706,45 +6894,49 @@

    RoomService

  • Concept: cross-reference the thin-leaf CRUD pattern at ActivityService for the inherited half. Two things make RoomService more than a four-liner.
    1. It overrides the base's virtual AddAsync (EntityServiceBase.cs:150) because the create - endpoint's contract record AddRoomRequest binds a RoomId property, not the DTO's Id + endpoint's contract record AddRoomRequest binds a nullable RoomIdentifierType? RoomId property, + not the DTO's Id (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/RoomsController.cs:36, - consumed at :213). The override therefore posts an anonymous body that remaps RoomId = dto.Id - alongside the remaining room fields (RoomService.cs:25-35), while still routing through the base - SendRequestAsync so it keeps the Polly retry and the Result conversion. + bound at :215 and read at :221). The override therefore posts an anonymous body that remaps the + DTO id into RoomId alongside the remaining room fields (RoomService.cs:25-37), sending a default + id as null so the handler allocates one instead of treating an explicit 0 as a client-chosen id + (RoomService.cs:27-29), while still routing through the base SendRequestAsync so it keeps the + Polly retry and the Result conversion. [Rubric §9, API & Contract Design] (assesses whether the client honors the server's request contract rather than assuming DTO/request symmetry).
    2. It adds a parent-scoped delete, because a room is addressed under its event.
  • Walkthrough
      -
    • AddAsync(dto, ct) override (RoomService.cs:18-38): guards a null DTO (:20), then +
    • AddAsync(dto, ct) override (RoomService.cs:18-40): guards a null DTO (:20), then SendRequestAsync<RoomDTO> posting the anonymous object - { RoomId = dto.Id, dto.EventId, dto.Name, dto.Sort, dto.Capacity, dto.Floor, dto.Location, dto.AccessibilityInfo } + { RoomId = dto.Id == default ? (RoomIdentifierType?)null : dto.Id, dto.EventId, dto.Name, dto.Sort, dto.Capacity, dto.Floor, dto.Location, dto.AccessibilityInfo } to Endpoint (:22-37).
    • -
    • DeleteAsync(roomId, eventId, ct) (RoomService.cs:40-44): builds +
    • DeleteAsync(roomId, eventId, ct) (RoomService.cs:42-46): builds {Endpoint}/{roomId}?eventId={eventId} with string.Create(CultureInfo.InvariantCulture, ...) so - the ids format culture-stably (:43), and dispatches it through the valueless SendRequestAsync - (:41-44), returning the Result the base produces.
    • + the ids format culture-stably (:45), and dispatches it through the valueless SendRequestAsync + (:43-46), returning the Result the base produces.
  • Why it's built this way: the create-payload remap keeps the UI honest about the server's AddRoomRequest shape, and the parent-scoped delete exists because rooms belong to an event and the endpoint binds the parent id as a query argument.
  • Where it's used: registered by the AddUIModule<ConferenceUIModule>() entity-service scan - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:30) and injected as - IRoomUIService into RoomList (Pages/Room/RoomList.razor.cs:17), - RoomDetail (Pages/Room/RoomDetail.razor.cs:19), RoomCreate - (Pages/Room/RoomCreate.razor.cs:13), SessionCreate - (Pages/Session/SessionCreate.razor.cs:21), SessionDetail - (Pages/Session/SessionDetail.razor.cs:29) and PublicSessionDetail - (Pages/Public/PublicSessionDetail.razor.cs:26) for room wayfinding.
  • + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:31) and injected as + IRoomUIService into RoomList (Pages/Rooms/RoomList.razor.cs:18), + RoomDetail (Pages/Rooms/RoomDetail.razor.cs:20), RoomCreate + (Pages/Rooms/RoomCreate.razor.cs:15), SessionCreate + (Pages/Sessions/SessionCreate.razor.cs:24), SessionDetail + (Pages/Sessions/SessionDetail.razor.cs:34) and PublicSessionDetail + (Pages/Public/Sessions/PublicSessionDetail.razor.cs:26) for room wayfinding, and taken as a + parameter by SessionLookups (Pages/Sessions/SessionLookups.cs:36).
  • Caveats / not-in-source: two consequences of the override are worth knowing, and both are visible by comparing it to the base. The base AddAsync attaches a fresh Idempotency-Key held constant across retries (EntityServiceBase.cs:159-164), and this override calls SendRequestAsync without - one (RoomService.cs:22-37), so a retried room create is not deduplicated by the server-side + one (RoomService.cs:22-39), so a retried room create is not deduplicated by the server-side idempotency filter the way other creates are. The base AddAsync and DeleteAsync also call the base's private InvalidateOnSuccess to drop stale read-cache entries - (EntityServiceBase.cs:165,213,281), which neither method here does, and being private it is not + (EntityServiceBase.cs:165,213,285), which neither method here does, and being private it is not callable from this subclass.
  • SessionSelectionService

    @@ -6804,7 +6996,7 @@

    SessionSelectionService

    branch on.
  • Where it's used: registered explicitly as ISessionSelectionUIService at - MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:46, and injected + MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:47, and injected into SessionSelectionDashboard (Pages/SessionSelection/SessionSelectionDashboard.razor.cs:17), which loads the projection at :123, starts a run at :172 and branches on the SessionsScored == -1 sentinel at :180. @@ -6841,7 +7033,7 @@

    SessionService

    services (SpeakerDashboardService and PublicSessionScheduleService) rather than as overrides here.
  • Where it's used: registered by the AddUIModule<ConferenceUIModule>() scan - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:30), and injected + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:31), and injected as ISessionUIService into SessionList (Pages/Session/SessionList.razor.cs:24), SessionDetail (Pages/Session/SessionDetail.razor.cs:23), SessionCreate @@ -6888,7 +7080,7 @@

    SponsorService

    ISponsorUIService and keeps the resource name in exactly one place.
  • Where it's used: picked up automatically by the Scrutor scan inside AddUIModule<ConferenceUIModule>() - (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:30), so no + (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/DependencyInjection.cs:31), so no explicit AddScoped line exists for it. Injected as ISponsorUIService into SponsorList (Pages/Sponsor/SponsorList.razor.cs:23), SponsorDetail (Pages/Sponsor/SponsorDetail.razor.cs:22), @@ -7069,23 +7261,23 @@

    PublicActivityList

    • What it is: the public social and networking programme. It lists the current (or next) event's activities (pre-conference party, coffee connect, after-party, closing ceremony) ordered by start time then display order, read-only and anonymous, BR-43 (class doc, PublicActivityList.razor.cs:13-20).
    • -
    • Depends on: IActivityUIService and IEventLookupService (:27-28), ActivityDTO, EventInfo (through the lookup), CurrentEventSelector (:54-59), IMapNavigationService (:29), and IToastService (:30). It derives from ComponentBase directly (:20), not from the list-page base.
    • +
    • Depends on: IActivityUIService and IEventLookupService (:28-29), ActivityDTO, EventInfo (through the lookup), CurrentEventSelector (:71-76), IMapNavigationService (:30), IToastService (:31), and the injected TimeProvider (:32) that supplies "now" to the selector (:76). It derives from ComponentBase directly (:21), not from the list-page base.
    • Concept introduced, the bounded, deterministically ordered, single-shot read. This page is not a data grid, and reading it next to PublicEventList is the clearest way to see when the base class is the wrong tool. An activity programme is a handful of items with a fixed narrative order (chronological), so there is nothing to page, sort or search.
        -
      • Bounded read: MaxActivities = 200 with the reasoning written on the constant, a conference schedules a handful, not thousands (:22-24). [Rubric §12, Performance & Scalability] (assesses that unbounded reads are avoided by design, not by luck).
      • -
      • Deterministic order: the server is asked for StartTime ascending (:72-73), and the result is re-ordered in memory by StartTime, then SortOrder, then Name (:84-90). The comment (:81-83) explains the layering: start time is the programme order, sort order breaks ties between activities that start together, and name is the final tiebreak so the list is deterministic rather than dependent on insertion order.
      • -
      • Two independent non-critical reads. Both the event lookup (:52) and the activity page (:79) are consumed through TryGetValue, and each failure has a stated fallback written next to it: without the lookup the list is simply not scoped to an event (:51), and a failed fetch leaves the page on its empty state (:77-78). Neither raises an error toast, and only OperationCanceledException is caught (:93-96) as expected teardown or an InteractiveAuto transition; the finally always clears _isLoading (:97-100). [Rubric §29, Resilience & Business Continuity].
      • -
      • Culture-aware time rendering: FormatTimeRange (:40-44) formats start and end with CultureInfo.CurrentCulture and composes them through a localized Text.TimeRange resource, so both the times and the separator follow the viewer's culture. [Rubric §27, Internationalization] (assesses that formatting and phrasing are both localized, not just the strings). +
      • Bounded read: MaxActivities = 200 with the reasoning written on the constant, a conference schedules a handful, not thousands (:23-24). [Rubric §12, Performance & Scalability] (assesses that unbounded reads are avoided by design, not by luck).
      • +
      • Deterministic order: the server is asked for StartTime ascending (:89-90), and the result is re-ordered in memory by StartTime, then SortOrder, then Name (:100-106). The comment (:97-99) explains the layering: start time is the programme order, sort order breaks ties between activities that start together, and name is the final tiebreak so the list is deterministic rather than dependent on insertion order.
      • +
      • The event lookup fails closed; the activity fetch fails soft. A failed event lookup sets _loadFailed and returns before any fetch is issued (:65-69), and the markup hands that flag to ListNoRecordsContent with OnRetry="LoadAsync" (PublicActivityList.razor:26-27), so the reader gets an error with a retry. The doc on LoadAsync states the reason (:53-58): falling through to a fetch with no event filter would list every edition's activities; a successful lookup that genuinely finds no current or next event still keeps the unscoped fetch, matching PublicSpeakerList. The activity page itself (:95) stays non-critical: a failed fetch leaves the page on its empty state (:93-94). Only OperationCanceledException is caught (:109-112) as expected teardown or an InteractiveAuto transition, and the finally always clears _isLoading (:113-116). [Rubric §29, Resilience & Business Continuity] and [Rubric §11, Security] (an audience-scoped read never silently widens).
      • +
      • Culture-aware time rendering: FormatTimeRange (:44-49) formats start and end with CultureInfo.CurrentCulture and composes them through a localized Text.TimeRange resource, so both the times and the separator follow the viewer's culture. [Rubric §27, Internationalization] (assesses that formatting and phrasing are both localized, not just the strings). The class doc also draws the domain line that shapes the whole page (:16-18): activities are not sessions. They carry no room and no speakers, and an activity with its own venue offers the same directions affordance the public event page uses for the conference venue.
    • Walkthrough
        -
      • OnInitializedAsync (:46-101): load the event lookup (:52), resolve the current or next event through CurrentEventSelector.SelectCurrentOrNext with the four accessors passed explicitly because the lookup returns EventInfo rather than EventDTO (:54-59), remember its id and name (:61-62), build an EventId equals filter when one resolved (:65-67), fetch one bounded page (:69-75), and materialize the ordered list (:84-90).
      • -
      • OpenDirectionsAsync (:103-120): does nothing for an activity with no venue address (:105-108), otherwise launches the platform maps app on native heads or a maps site in a browser (:111-114), labelling the pin with the venue name and falling back to the activity name when the venue is unnamed (:113); a false return raises one warning toast (:116-119).
      • -
      • Disposal (:124-144) is the standard cancel-on-disposal pattern over the CancellationTokenSource at :32.
      • +
      • OnInitializedAsync (:51) delegates to LoadAsync (:59-117), which is also the retry target: reset _isLoading and _loadFailed (:61-62), load the event lookup and stop on the error state when it fails (:65-69), resolve the current or next event through CurrentEventSelector.SelectCurrentOrNext with the four accessors passed explicitly because the lookup returns EventInfo rather than EventDTO, and with TimeProvider.GetUtcNow().UtcDateTime as the clock (:71-76), remember its id and name (:78-79), build an EventId equals filter when one resolved (:81-83), fetch one bounded page (:85-91), and materialize the ordered list (:100-106).
      • +
      • OpenDirectionsAsync (:119-136): does nothing for an activity with no venue address (:121-124), otherwise launches the platform maps app on native heads or a maps site in a browser (:126-130), labelling the pin with the venue name and falling back to the activity name when the venue is unnamed (:129); a false return raises one warning toast (:132-135).
      • +
      • Disposal (:138-160) is the standard cancel-on-disposal pattern over the CancellationTokenSource at :34.
    • Why it's built this way: a fixed-order programme wants a readable timeline, not sortable columns, and the read is small enough that one bounded call beats the machinery of server paging.
    • -
    • Where it's used: the /conference/activities route (PublicActivityList.razor:1, the same string as ConferenceRoutePaths.PublicActivities at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/ConferenceRoutePaths.cs:54), reached from PublicEventDetail's ViewActivities action (PublicEventDetail.razor.cs:179).
    • +
    • Where it's used: the /conference/activities route (PublicActivityList.razor:1, the same string as ConferenceRoutePaths.PublicActivities at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/ConferenceRoutePaths.cs:54), reached from PublicEventDetail's ViewActivities action (PublicEventDetail.razor.cs:167).
    • Caveats / not-in-source: the page relies on the server scoping non-privileged callers to published events (class doc, :14-16); that scoping is enforced in the Conference API, not here.

    PublicSponsorList

    @@ -7094,15 +7286,15 @@

    PublicSponsorList

    • What it is: the public sponsor and exhibitor page. It groups the current (or next) event's sponsors by tier, orders them within each tier, and renders them as logo cards. Read-only and anonymous, BR-43 (class doc, PublicSponsorList.razor.cs:12-19).
    • -
    • Depends on: ISponsorUIService and IEventLookupService (:26-27), SponsorDTO and SponsorTier (:40), EventInfo (through the lookup), and CurrentEventSelector (:53-58); MudBlazor and the page's IStringLocalizer (PublicSponsorList.razor:3). Like PublicActivityList it derives from ComponentBase directly (:19).
    • +
    • Depends on: ISponsorUIService and IEventLookupService (:27-28), SponsorDTO and SponsorTier (:45), EventInfo (through the lookup), CurrentEventSelector (:70-75), and the injected TimeProvider (:29) that supplies "now" to the selector (:75); MudBlazor and the page's IStringLocalizer (PublicSponsorList.razor:3). Like PublicActivityList it derives from ComponentBase directly (:20).
    • Concept introduced, the deterministic grouped read and the graceful empty state. Like PublicActivityList, this page is not a data grid: the roster is small and needs a fixed visual hierarchy, so the page fetches one bounded page and shapes it in memory.
        -
      • Bounded read: MaxSponsors = 200 with the reasoning stated on the constant, a conference sells dozens, not thousands (:21-22). [Rubric §12, Performance & Scalability].
      • -
      • Deterministic order: sponsors are grouped by tier, tiers ordered ascending because that is package order (Platinum first), and each group ordered by Sort then Name (:84-92), so the strip does not depend on insertion order. The comment states the rule (:81-83).
      • -
      • Empty state with no dead link: when the event has no sponsors the page falls back to the sponsorship-packet call to action, and when the event publishes no packet URL that call to action is hidden entirely rather than offering a dead link (class doc :15-17, field doc :33-36, assigned at :62). [Rubric §24, Forms, Validation & UX Safety] and [Rubric §25, Navigation & Information Architecture]: a missing value removes an affordance instead of producing a broken one.
      • -
      • Failure is non-fatal, and stated as such: both reads are consumed through TryGetValue with the fallback written beside them (:50, :77-78), only OperationCanceledException is caught (:95-98) as expected teardown or an InteractiveAuto transition, and the finally always clears _isLoading (:99-102). [Rubric §29, Resilience & Business Continuity].
      • +
      • Bounded read: MaxSponsors = 200 with the reasoning stated on the constant, a conference sells dozens, not thousands (:22-23). [Rubric §12, Performance & Scalability].
      • +
      • Deterministic order: sponsors are grouped by tier, tiers ordered ascending because that is package order (Platinum first), and each group ordered by Sort then Name (:100-108), so the strip does not depend on insertion order. The comment states the rule (:97-99).
      • +
      • Empty state with no dead link: when the event has no sponsors the page falls back to the sponsorship-packet call to action, and when the event publishes no packet URL that call to action is hidden entirely rather than offering a dead link (class doc :15-17, field doc :38-42, assigned at :79). [Rubric §24, Forms, Validation & UX Safety] and [Rubric §25, Navigation & Information Architecture]: a missing value removes an affordance instead of producing a broken one.
      • +
      • The event lookup fails closed; the sponsor fetch fails soft. A failed event lookup sets _loadFailed and returns before any fetch is issued (:64-68), and the markup hands that flag to ListNoRecordsContent with OnRetry="LoadAsync" (PublicSponsorList.razor:26-27), so the reader gets an error with a retry. The doc on LoadAsync states the reason (:52-57): falling through to a fetch with no event filter would show every edition's sponsors; a successful lookup that genuinely finds no current or next event still keeps the unscoped fetch, matching PublicSpeakerList. A failed sponsor fetch (:95) stays non-fatal and falls back to the sponsorship call to action (:93-94). Only OperationCanceledException is caught (:111-114) as expected teardown or an InteractiveAuto transition, and the finally always clears _isLoading (:115-118). [Rubric §29, Resilience & Business Continuity] and [Rubric §11, Security] (an audience-scoped read never silently widens).
    • -
    • Walkthrough: OnInitializedAsync (:45-103) loads the event lookup (:51), resolves the current or next event with CurrentEventSelector passing the four accessors explicitly (:53-58), remembers its name and sponsorship packet URL (:61-62), builds an EventId equals filter when an event resolved (:65-67), fetches one page sorted by Sort ascending (:69-75), and materializes _tiers as an ordered list of tier-to-sponsors pairs (:84-92). TierLabel (:43) localizes each tier name through the page's IStringLocalizer with a Tier.{tier} key, so the tier enum never reaches the screen untranslated ([Rubric §27, Internationalization]). Disposal (:107-127) is the standard cancel-on-disposal pattern over the CancellationTokenSource at :29.
    • +
    • Walkthrough: OnInitializedAsync (:50) delegates to LoadAsync (:58-119), which is also the retry target. It resets _isLoading and _loadFailed (:60-61), loads the event lookup and stops on the error state when it fails (:64-68), resolves the current or next event with CurrentEventSelector passing the four accessors explicitly and TimeProvider.GetUtcNow().UtcDateTime as the clock (:70-75), remembers its name and sponsorship packet URL (:78-79), builds an EventId equals filter when an event resolved (:81-83), fetches one page sorted by Sort ascending (:85-91), and materializes _tiers as an ordered list of tier-to-sponsors pairs (:100-108). TierLabel (:48) localizes each tier name through the page's IStringLocalizer with a Tier.{tier} key, so the tier enum never reaches the screen untranslated ([Rubric §27, Internationalization]). Disposal (:121-143) is the standard cancel-on-disposal pattern over the CancellationTokenSource at :31.
    • Why it's built this way: the sponsor page is a marketing surface with a fixed hierarchy, so it wants deterministic grouping rather than sortable columns, and it must look intentional on an event that has not sold a sponsorship yet.
    • Where it's used: the /conference/sponsors route (PublicSponsorList.razor:1, matching ConferenceRoutePaths.PublicSponsors at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/ConferenceRoutePaths.cs:53). The roster it renders is authored by the organizer through the sponsor admin pages in this module.
    • Caveats / not-in-source: the page relies on the server scoping non-privileged callers to published events (class doc, :13-15); that scoping is enforced in the Conference API, not here.
    • @@ -7113,26 +7305,26 @@

      EventFilteredListPageBase<TDto>

      • What it is: the base class for every Conference list page whose rows are scoped to a single conference event. It adds three things on top of the framework's grid base: the event lookup, a persisted eventId filter, and the default (current or next) event resolution. A derived page supplies only what differs: how it saves and restores its own filters, and what it reloads when the scope changes.
      • -
      • Depends on: DataGridListPageBase<TDto> (its base, :25), IEventLookupService and its EventInfo record (injected at :27, :30), CurrentEventSelector (:3, :182), and the EventIdentifierType alias. Externals: Microsoft.AspNetCore.Components for [Inject] and RendererInfo, and System.Globalization.CultureInfo (:1).
      • +
      • Depends on: DataGridListPageBase<TDto> (its base, :25), IEventLookupService and its EventInfo record (injected at :27, :31), CurrentEventSelector (:3, :183), and the EventIdentifierType alias. Externals: Microsoft.AspNetCore.Components for [Inject] and RendererInfo, System.Globalization.CultureInfo (:1), and an injected TimeProvider (:28) that is the clock for the default-event computation (:188).
      • Concept introduced: layering an application-vocabulary base on a framework base. [Rubric §3, Clean Architecture] assesses whether abstractions sit at the layer that owns their vocabulary. The class remark makes the call in one sentence (:16-18): "conference event" is domain vocabulary, not framework vocabulary, so this does not belong in MMCA.Common alongside the grid plumbing it builds on. The result is a two-layer inheritance chain where each layer owns exactly the concepts of its own repository, which is the same reasoning that keeps DetailPageBase ADC-local for a different reason (nothing to share) and this one ADC-local for this reason (nothing shareable should be shared). - The second idea is prerender-truthful interactivity, and it is worth reading in full because it is a non-obvious correctness rule rather than a preference. CanRenderEventPicker gates the picker on RendererInfo.IsInteractive && Events is { Count: > 1 } (:72), and the remark above it explains (:63-71): the SSR prerender pass resolves the events server-side and would happily paint a fully formed picker, dropdown and clear button included, into static HTML. Nothing is wired to it until the interactive runtime attaches, which under InteractiveAuto's WebAssembly leg is a whole runtime boot later, and every click landing in that window is swallowed with no feedback, leaving the list silently on the default event the prerender chose. Rendering the picker only once the component is interactive turns "the picker is on screen" into a truthful signal that a choice will be honored, for a reader and for an E2E wait alike. [Rubric §22, Responsive and Cross-Browser/Device] and [Rubric §24, Forms, Validation and UX Safety] both land on that one line, and ADR-056 is the render-mode policy it implements.
      • + The second idea is prerender-truthful interactivity, and it is worth reading in full because it is a non-obvious correctness rule rather than a preference. CanRenderEventPicker gates the picker on RendererInfo.IsInteractive && Events is { Count: > 1 } (:73), and the remark above it explains (:64-72): the SSR prerender pass resolves the events server-side and would happily paint a fully formed picker, dropdown and clear button included, into static HTML. Nothing is wired to it until the interactive runtime attaches, which under InteractiveAuto's WebAssembly leg is a whole runtime boot later, and every click landing in that window is swallowed with no feedback, leaving the list silently on the default event the prerender chose. Rendering the picker only once the component is interactive turns "the picker is on screen" into a truthful signal that a choice will be honored, for a reader and for an E2E wait alike. [Rubric §22, Responsive and Cross-Browser/Device] and [Rubric §24, Forms, Validation and UX Safety] both land on that one line, and ADR-056 is the render-mode policy it implements.
      • Walkthrough:
          -
        • Injected state (:27-50): the IEventLookupService, then Events (the id-to-EventInfo dictionary, null when the lookup failed), SelectedEventId (null meaning "all events"), EventFilterResolved (true once the scope is decided, restored or computed), EventsLoadFailed (so a page that must fail closed can branch rather than silently querying unscoped), and EventsLoadTask, the in-flight load. Every setter is private, so the base owns all transitions.
        • -
        • EventFilterIsUserControlled (:57): virtual, default true. It answers "does the reader pick the event themselves", which is what makes the choice worth persisting and worth restoring. PublicSpeakerList overrides it with a role check rather than a constant (MMCA.ADC.Conference.UI/Pages/Public/Speakers/PublicSpeakerList.razor.cs:53), so the same page is user-controlled for a privileged reader and locked to the computed event for everyone else.
        • -
        • CanRenderEventPicker and EventPickerOptions (:72, :79-80): the gate described above, and the ordered option list derived from it. Iterating EventPickerOptions instead of Events keeps every page's picker on one ordering (OrderBy(e => e.StartDate)) and keeps the markup free of the null handling the gate already did.
        • -
        • The three extension points (:83-95): ReloadForEventFilterAsync() is abstract, so a page cannot forget to reload; SavePageFilters / RestorePageFilters are virtual no-ops for page-specific values (search text, status dropdowns); OnEventsLoadingAsync() is a virtual hook for work that must run before the events are fetched, such as resolving whether the reader is privileged.
        • -
        • SaveFilters / RestoreFilters (:98-129): both are sealed override, which is the load-bearing detail. The base takes over the framework's two filter hooks, calls the page's hook first, then owns the eventId entry itself, so no derived page can accidentally drop the event scope from persisted state. The saved value is either the invariant-formatted id or the literal "all", and the comment states why the literal exists (:104): it distinguishes an explicit clear from no saved state, which would apply the default instead. Restore handles the three cases (absent, "all", parseable id) and sets EventFilterResolved only in the latter two.
        • -
        • OnInitializedAsync and BeginEventsLoad (:132-144): initialization starts the load before any await and then awaits it. BeginEventsLoad() is exposed separately so a derived page can start the event load, run its own non-event work concurrently, and only then await EventsLoadTask.
        • -
        • LoadEventsAndResolveDefaultAsync (:150-167): the hook, then the lookup with Result unwrapped by TryGetValue (see the primer on the Result pattern), then ResolveDefaultEventFilter(). A failure is non-fatal by design (:154-155): the picker stays hidden and the default filter stays unset, and the failure is remembered in EventsLoadFailed. The method is documented as callable again (:146-149), so a retry button heals a transient lookup failure.
        • -
        • ResolveDefaultEventFilter (:169-189): the precedence rule, stated in its comment (:171-172) and encoded in one condition. A restored id that still exists wins; a dangling one falls back to the computed default; and a page with a locked scope is always pinned to the computed current or next event, which is what stops a stale saved id from surviving an override of EventFilterIsUserControlled. The computation delegates to CurrentEventSelector.SelectCurrentOrNext over Events.Values (:182-187), the same selector the landing page uses, so "which event is current" has one implementation across the module.
        • -
        • WaitForEventsAsync (:192): returns the load task or Task.CompletedTask. The doc on EventsLoadTask (:44-49) says exactly when to await it: before any fetch that applies the event filter, because the grid's first ServerData call can race ahead of initialization completing. That race is why the task is a field rather than a local.
        • -
        • ApplyEventFilter (:195-199): adds EventId as an ("equals", value) pair to the outgoing filter dictionary, and only when an event is selected, so "all events" is the absence of a filter rather than a sentinel.
        • -
        • OnEventFilterChanged (:202-207): bound to the picker's ValueChanged. It sets the id, marks the scope resolved, and awaits the page's ReloadForEventFilterAsync(). A null value clears the scope.
        • -
        • GetEventName / SelectedEventName (:210-219): display helpers that degrade rather than throw, falling back to the raw id and to an empty string respectively when the lookup has nothing.
        • -
        -
      • -
      • Why it's built this way: [Rubric §19, State Management and Data Flow] is the through-line. There is one scope variable, one place that decides its default, one place that persists it, and one place that reloads on change, and the sealed override on the two persistence hooks is what keeps it that way as pages are added. [Rubric §11, Security] shows up in the EventsLoadFailed flag: a public reader whose lookup failed must not fall through to an unscoped query, so the flag exists specifically to let such a page fail closed, which PublicSpeakerList does through its ScopeUnresolvedForPublicReader() check (PublicSpeakerList.razor.cs:135). [Rubric §15, Best Practices & Code Quality]: six list pages share this, and each one's event handling is now three overrides.
      • -
      • Where it's used: six Conference list pages derive from it, each pairing an @inherits directive with a partial class: SponsorList (MMCA.ADC.Conference.UI/Pages/Sponsors/SponsorList.razor.cs:19), PartnerList (Pages/Partners/PartnerList.razor.cs:19), SpeakerList (Pages/Speaker/SpeakerList.razor.cs:18), RoomList (Pages/Room/RoomList.razor.cs:12), ActivityList (Pages/Activity/ActivityList.razor.cs:19), and PublicSpeakerList (Pages/Public/PublicSpeakerList.razor.cs:33). The five organizer pages, PartnerList included, follow one shape verbatim (SavePageFilters/RestorePageFilters, ReloadForEventFilterAsync => ReloadActiveLayoutAsync(), await WaitForEventsAsync() before each fetch, ApplyEventFilter(filters) inside it); the public page is the variant that exercises every extension point, overriding EventFilterIsUserControlled (:53), OnEventsLoadingAsync (:91), and re-calling LoadEventsAndResolveDefaultAsync() on retry (:123).
      • +
      • Injected state (:27-51): the IEventLookupService and the TimeProvider, then Events (the id-to-EventInfo dictionary, null when the lookup failed), SelectedEventId (null meaning "all events"), EventFilterResolved (true once the scope is decided, restored or computed), EventsLoadFailed (so a page that must fail closed can branch rather than silently querying unscoped), and EventsLoadTask, the in-flight load. Every setter is private, so the base owns all transitions.
      • +
      • EventFilterIsUserControlled (:58): virtual, default true. It answers "does the reader pick the event themselves", which is what makes the choice worth persisting and worth restoring. PublicSpeakerList overrides it with a role check rather than a constant (MMCA.ADC.Conference.UI/Pages/Public/Speakers/PublicSpeakerList.razor.cs:52), so the same page is user-controlled for a privileged reader and locked to the computed event for everyone else.
      • +
      • CanRenderEventPicker and EventPickerOptions (:73, :80-81): the gate described above, and the ordered option list derived from it. Iterating EventPickerOptions instead of Events keeps every page's picker on one ordering (OrderBy(e => e.StartDate)) and keeps the markup free of the null handling the gate already did.
      • +
      • The three extension points (:84-96): ReloadForEventFilterAsync() is abstract, so a page cannot forget to reload; SavePageFilters / RestorePageFilters are virtual no-ops for page-specific values (search text, status dropdowns); OnEventsLoadingAsync() is a virtual hook for work that must run before the events are fetched, such as resolving whether the reader is privileged.
      • +
      • SaveFilters / RestoreFilters (:99-130): both are sealed override, which is the load-bearing detail. The base takes over the framework's two filter hooks, calls the page's hook first, then owns the eventId entry itself, so no derived page can accidentally drop the event scope from persisted state. The saved value is either the invariant-formatted id or the literal "all", and the comment states why the literal exists (:105): it distinguishes an explicit clear from no saved state, which would apply the default instead. Restore handles the three cases (absent, "all", parseable id) and sets EventFilterResolved only in the latter two.
      • +
      • OnInitializedAsync and BeginEventsLoad (:133-145): initialization starts the load before any await and then awaits it. BeginEventsLoad() is exposed separately so a derived page can start the event load, run its own non-event work concurrently, and only then await EventsLoadTask.
      • +
      • LoadEventsAndResolveDefaultAsync (:151-168): the hook, then the lookup with Result unwrapped by TryGetValue (see the primer on the Result pattern), then ResolveDefaultEventFilter(). A failure is non-fatal by design (:155-156): the picker stays hidden and the default filter stays unset, and the failure is remembered in EventsLoadFailed. The method is documented as callable again (:147-150), so a retry button heals a transient lookup failure.
      • +
      • ResolveDefaultEventFilter (:170-190): the precedence rule, stated in its comment (:172-173) and encoded in one condition. A restored id that still exists wins; a dangling one falls back to the computed default; and a page with a locked scope is always pinned to the computed current or next event, which is what stops a stale saved id from surviving an override of EventFilterIsUserControlled. The computation delegates to CurrentEventSelector.SelectCurrentOrNext over Events.Values with TimeProvider.GetUtcNow().UtcDateTime as "now" (:183-188), the same selector the landing page uses, so "which event is current" has one implementation across the module and a test can pin the clock through the injected TimeProvider.
      • +
      • WaitForEventsAsync (:193): returns the load task or Task.CompletedTask. The doc on EventsLoadTask (:45-50) says exactly when to await it: before any fetch that applies the event filter, because the grid's first ServerData call can race ahead of initialization completing. That race is why the task is a field rather than a local.
      • +
      • ApplyEventFilter (:196-200): adds EventId as an ("equals", value) pair to the outgoing filter dictionary, and only when an event is selected, so "all events" is the absence of a filter rather than a sentinel.
      • +
      • OnEventFilterChanged (:203-208): bound to the picker's ValueChanged. It sets the id, marks the scope resolved, and awaits the page's ReloadForEventFilterAsync(). A null value clears the scope.
      • +
      • GetEventName / SelectedEventName (:211-220): display helpers that degrade rather than throw, falling back to the raw id and to an empty string respectively when the lookup has nothing.
      • +
      + +
    • Why it's built this way: [Rubric §19, State Management and Data Flow] is the through-line. There is one scope variable, one place that decides its default, one place that persists it, and one place that reloads on change, and the sealed override on the two persistence hooks is what keeps it that way as pages are added. [Rubric §11, Security] shows up in the EventsLoadFailed flag: a public reader whose lookup failed must not fall through to an unscoped query, so the flag exists specifically to let such a page fail closed, which PublicSpeakerList does through its ScopeUnresolvedForPublicReader() check (PublicSpeakerList.razor.cs:126). [Rubric §15, Best Practices & Code Quality]: six list pages share this, and each one's event handling is now three overrides.
    • +
    • Where it's used: six Conference list pages derive from it, each pairing an @inherits directive with a partial class: SponsorList (MMCA.ADC.Conference.UI/Pages/Sponsors/SponsorList.razor.cs:19), PartnerList (Pages/Partners/PartnerList.razor.cs:19), SpeakerList (Pages/Speakers/SpeakerList.razor.cs:19), RoomList (Pages/Rooms/RoomList.razor.cs:13), ActivityList (Pages/Activities/ActivityList.razor.cs:20), and PublicSpeakerList (Pages/Public/Speakers/PublicSpeakerList.razor.cs:32). The five organizer pages, PartnerList included, follow one shape verbatim (SavePageFilters/RestorePageFilters, ReloadForEventFilterAsync => ReloadActiveLayoutAsync(), await WaitForEventsAsync() before each fetch, ApplyEventFilter(filters) inside it); the public page is the variant that exercises every extension point, overriding EventFilterIsUserControlled (:52), OnEventsLoadingAsync (:90), and re-calling LoadEventsAndResolveDefaultAsync() on retry (:114).

    PublicSpeakerDetail

    @@ -7140,19 +7332,19 @@

    PublicSpeakerDetail

    • What it is: the public speaker profile: photo, bio, social links, and the sessions that speaker presents at the conference the reader is allowed to see. Email is deliberately not rendered, BR-66 (class doc, PublicSpeakerDetail.razor.cs:18-21).
    • -
    • Depends on: ISpeakerUIService, ISessionUIService and IEventUIService (:24-26), AuthenticationStateProvider read directly rather than as a cascading task (:27), IToastService (:29), LatestLoadGuard (:59), SpeakerDTO and SessionDTO, ConferenceReadAudience (:206), CurrentEventDefaults (:213), ConferenceRoutePaths (:46, :217), and DomainHelper's Id.Parse<T> (:98).
    • +
    • Depends on: ISpeakerUIService, ISessionUIService and IEventUIService (:24-26), AuthenticationStateProvider read directly rather than as a cascading task (:27), IToastService (:29), the injected TimeProvider (:30), LatestLoadGuard (:60), SpeakerDTO and SessionDTO, ConferenceReadAudience (:207), CurrentEventDefaults (:214), ConferenceRoutePaths (:47, :218), and DomainHelper's Id.Parse<T> (:99).
    • Concept introduced, the prerender skip, the server-side filter that replaced an in-memory one, and fail-closed audience scoping.
        -
      1. Prerender skip. OnParametersSetAsync returns immediately when !RendererInfo.IsInteractive (:76-79): under InteractiveAuto the interactive instance re-runs the method, so without this guard every visit fetched the speaker and the session catalog twice. The prerender pass renders the loading skeleton, and the comment names the sibling pages that use the same guard (:73-75). [Rubric §23, Front-End Performance & Rendering] (assesses avoidable duplicate work per view).
      2. -
      3. Push the filter to the server. LoadSpeakerSessionsAsync (:157-194) sends a SpeakerId equals filter with includeChildren: false, sorted by StartsAt ascending, capped at MaxSpeakerSessions = 100 (:33-34, request at :178-185). The remarks block (:147-152) records what this replaced: the page used to pull the entire session catalog with all child collections and filter it in memory on SessionSpeakers, so viewing one speaker cost a full-catalog read. Since the page never renders those children, they are gone from the request too. [Rubric §12, Performance & Scalability] and [Rubric §8, Data Architecture] (a bounded page size instead of an unbounded read).
      4. -
      5. Fail closed on the audience scope. ResolveSessionEventScopeAsync (:202-215) returns an unscoped result for a privileged reader (:205-209) and otherwise resolves the current or next event through CurrentEventDefaults (:213); a failed events lookup returns (false, null), an unresolved scope rather than a wide one, and the caller turns that into a load failure (:167-171). The comment states the leak it prevents (:164-166): without this, a speaker who also presented at past conferences leaked those sessions to attendees here. [Rubric §11, Security] and [Rubric §26, Front-End Security]. +
      6. Prerender skip. OnParametersSetAsync returns immediately when !RendererInfo.IsInteractive (:77-80): under InteractiveAuto the interactive instance re-runs the method, so without this guard every visit fetched the speaker and the session catalog twice. The prerender pass renders the loading skeleton, and the comment names the sibling pages that use the same guard (:74-76). [Rubric §23, Front-End Performance & Rendering] (assesses avoidable duplicate work per view).
      7. +
      8. Push the filter to the server. LoadSpeakerSessionsAsync (:158-195) sends a SpeakerId equals filter with includeChildren: false, sorted by StartsAt ascending, capped at MaxSpeakerSessions = 100 (:34-35, request at :179-186). The remarks block (:148-153) records what this replaced: the page used to pull the entire session catalog with all child collections and filter it in memory on SessionSpeakers, so viewing one speaker cost a full-catalog read. Since the page never renders those children, they are gone from the request too. [Rubric §12, Performance & Scalability] and [Rubric §8, Data Architecture] (a bounded page size instead of an unbounded read).
      9. +
      10. Fail closed on the audience scope. ResolveSessionEventScopeAsync (:203-216) returns an unscoped result for a privileged reader (:206-210) and otherwise resolves the current or next event through CurrentEventDefaults, with TimeProvider.GetUtcNow().UtcDateTime as "now" (:214); a failed events lookup returns (false, null), an unresolved scope rather than a wide one, and the caller turns that into a load failure (:168-172). The comment states the leak it prevents (:165-167): without this, a speaker who also presented at past conferences leaked those sessions to attendees here. [Rubric §11, Security] and [Rubric §26, Front-End Security]. [Rubric §30, Compliance, Privacy & Data Governance] (assesses deliberate handling of personal data): the speaker email exists on the DTO but is never rendered on the public page, and the class doc names the rule.
    • Walkthrough
        -
      • OnInitialized (:40-49) builds the Home / Speakers / Profile breadcrumbs; unlike PublicEventDetail it is synchronous, because the speaker list is reachable by every audience and the trail does not vary.
      • -
      • HasSocialLinks (:65-69) collapses the four optional link fields into a single render guard, so the social row is absent rather than empty when a speaker supplied none.
      • -
      • LoadSpeakerAsync (:90-141): begin a new load through LatestLoadGuard.Begin(), which returns the cancellation token and the generation together (:93, field doc at :53-58), parse the id (:98), GetByIdAsync(speakerId, true, token) (:99), clear Speaker and toast not-found-versus-load-failed on failure (:105-115), then load the sessions and toast one load failure if that resolver returned false (:119-122); OperationCanceledException is swallowed (:124-127) and the _load.IsCurrent(generation)-guarded finally clears IsLoading (:132-140).
      • -
      • Session links are plain MudLink markup to ConferenceRoutePaths.PublicSessionDetails(session.Id) (PublicSpeakerDetail.razor:117), not a code-behind navigation method; the page kept only NavigateToList (:217) for the back action. Disposal (:221-240) now delegates to _load.Dispose() (:230) instead of cancelling and disposing a raw CancellationTokenSource directly, the same LatestLoadGuard that replaced the standalone _loadGeneration counter.
      • +
      • OnInitialized (:41-50) builds the Home / Speakers / Profile breadcrumbs; unlike PublicEventDetail it is synchronous, because the speaker list is reachable by every audience and the trail does not vary.
      • +
      • HasSocialLinks (:66-70) collapses the four optional link fields into a single render guard, so the social row is absent rather than empty when a speaker supplied none.
      • +
      • LoadSpeakerAsync (:91-142): begin a new load through LatestLoadGuard.Begin(), which returns the cancellation token and the generation together (:94, field doc at :54-59), parse the id (:99), GetByIdAsync(speakerId, true, token) (:100), clear Speaker and toast not-found-versus-load-failed on failure (:106-116), then load the sessions and toast one load failure if that resolver returned false (:120-123); OperationCanceledException is swallowed (:125-128), any other exception raises the load-failure toast (:129-132), and the _load.IsCurrent(generation)-guarded finally clears IsLoading (:133-141).
      • +
      • Session links are plain MudLink markup to ConferenceRoutePaths.PublicSessionDetails(session.Id) (PublicSpeakerDetail.razor:117), not a code-behind navigation method; the page keeps only NavigateToList (:218) for the back action. Disposal (:220-241) delegates to _load.Dispose() (:231), so the same LatestLoadGuard that owns the load generation also owns cancellation of the in-flight fetch.
    • Why it's built this way: a public profile is a read-only, cache-friendly page; keeping its fetches narrow (one speaker, that speaker's sessions for one event, no children) is what makes it cheap enough to serve to an anonymous crowd, and scoping those sessions to the reader's event keeps the profile consistent with the schedule they can actually browse.
    • @@ -7160,33 +7352,33 @@

      PublicSpeakerDetail

    PublicSpeakerList

    -

    MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI.Pages.Public.Speakers · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Speakers/PublicSpeakerList.razor.cs:33 · Level 10 · class (Blazor code-behind)

    +

    MMCA.ADC.Conference.UI · MMCA.ADC.Conference.UI.Pages.Public.Speakers · MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Speakers/PublicSpeakerList.razor.cs:32 · Level 10 · class (Blazor code-behind)

      -
    • What it is: the public speaker directory, rendered as a photo-forward responsive card grid with infinite scroll, the same layout on desktop and mobile. Read-only for everyone (BR-43), no emails (BR-66), and the server returns only speakers with a visible session in the listed event, or in any published event when no event filter is applied, BR-239 (class doc, PublicSpeakerList.razor.cs:10-32).
    • -
    • Depends on: extends EventFilteredListPageBase<TDto> closed over SpeakerDTO (:33), which itself extends DataGridListPageBase<TDto> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Common/EventFilteredListPageBase.cs:25); ISpeakerUIService (:42), ConferenceReadAudience (:101), and InfiniteScrollSentinel (PublicSpeakerList.razor:144). Note what is absent: no MudDataGrid, no GridRef override, no MobileInfiniteScrollList<TItem>.
    • -
    • Concept introduced, borrowing a base class's plumbing for a layout it was not written for. A speaker is a face and a tagline, not a row of columns, so this page throws away the grid and keeps everything else. The class doc explains the trade (:21-28): paging keeps the page-based model of the base's mobile path (MobileItems, MobileCurrentPage, MobileTotalItems, MobilePageSize, declared at MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Common/DataGridListPageBase.cs:49-52), which already owns the cancellation token, the loading and failure flags, and the saved-state plumbing, and layers infinite scroll on top by appending each fetched page to _loadedSpeakers (:55, appended at :161 and :197) rather than replacing, which is what the base's mobile path does on its own. - The event-filter half is not hand-rolled either: EventFilteredListPageBase<TDto> owns the events load, the "all" sentinel, the picker, and the EventId filter, and this page supplies four overrides. EventFilterIsUserControlled => _isPrivileged (:53) is the audience gate: only privileged readers pick their own event, so only their choice is persisted and restored, and everyone else is recomputed to the current or next event (doc at :49-52, base contract at EventFilteredListPageBase.cs:57). OnEventsLoadingAsync (:91-107) resolves privileged status before the events are fetched, because that decides whether a restored choice survives, with a failed read treated as non-privileged (:104-106). ReloadForEventFilterAsync (:222) and SavePageFilters / RestorePageFilters (:73-77) fill in the rest. [Rubric §15, Best Practices & Code Quality] and [Rubric §1, SOLID] (assess reuse of one tested mechanism rather than a parallel implementation); [Rubric §11, Security] and [Rubric §26, Front-End Security] for the persistence gate. +
    • What it is: the public speaker directory, rendered as a photo-forward responsive card grid with infinite scroll, the same layout on desktop and mobile. Read-only for everyone (BR-43), no emails (BR-66), and the server returns only speakers with a visible session in the listed event, or in any published event when no event filter is applied, BR-239 (class doc, PublicSpeakerList.razor.cs:9-31).
    • +
    • Depends on: extends EventFilteredListPageBase<TDto> closed over SpeakerDTO (:32), which itself extends DataGridListPageBase<TDto> (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Common/EventFilteredListPageBase.cs:25); ISpeakerUIService (:41), PublicReadAudience (:97), which applies ConferenceReadAudience's privileged roles (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/PublicReadAudience.cs:28), and InfiniteScrollSentinel (PublicSpeakerList.razor:144). Note what is absent: no MudDataGrid, no GridRef override, no MobileInfiniteScrollList<TItem>.
    • +
    • Concept introduced, borrowing a base class's plumbing for a layout it was not written for. A speaker is a face and a tagline, not a row of columns, so this page throws away the grid and keeps everything else. The class doc explains the trade (:20-27): paging keeps the page-based model of the base's mobile path (MobileItems, MobileCurrentPage, MobileTotalItems, MobilePageSize, declared at MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Common/DataGridListPageBase.cs:49-52), which already owns the cancellation token, the loading and failure flags, and the saved-state plumbing, and layers infinite scroll on top by appending each fetched page to _loadedSpeakers (:54, appended at :152 and :188) rather than replacing, which is what the base's mobile path does on its own. + The event-filter half is not hand-rolled either: EventFilteredListPageBase<TDto> owns the events load, the "all" sentinel, the picker, and the EventId filter, and this page supplies four overrides. EventFilterIsUserControlled => _isPrivileged (:52) is the audience gate: only privileged readers pick their own event, so only their choice is persisted and restored, and everyone else is recomputed to the current or next event (doc at :48-51, base contract at EventFilteredListPageBase.cs:58). OnEventsLoadingAsync (:90-98) resolves privileged status before the events are fetched, because that decides whether a restored choice survives. It returns early, leaving the reader non-privileged, when no authentication state cascades (:92-95), and otherwise delegates to PublicReadAudience.IsPrivilegedReaderAsync (:97), whose catch degrades any non-cancellation failure to the least-privileged audience (PublicReadAudience.cs:30-34). ReloadForEventFilterAsync (:213) and SavePageFilters / RestorePageFilters (:72-76) fill in the rest. [Rubric §15, Best Practices & Code Quality] and [Rubric §1, SOLID] (assess reuse of one tested mechanism rather than a parallel implementation); [Rubric §11, Security] and [Rubric §26, Front-End Security] for the persistence gate. Four correctness details make that borrowing safe, and they are the real lesson of this page.
        -
      1. A generation counter supersedes in-flight fetches. _generation (:65) is bumped by every reset (search, event filter, breakpoint change, retry) inside LoadSpeakersAsync (:143), and both LoadSpeakersAsync (:154-157) and LoadMoreSpeakersAsync (:186-188, :202-206) discard their rows when a newer generation has taken over. Without it a slow page-1 fetch could append to the list a later query had already cleared. [Rubric §19, State Management & Data Flow].
      2. -
      3. The list is cleared before the await, not after. The comment (:147-148) states why: the grid, and with it the sentinel, is gone while page 1 is in flight, so a stray intersection-observer callback cannot ask for page 2 of the query being replaced. [Rubric §18, UI Architecture & Component Design].
      4. -
      5. The page number is committed only on success. LoadMoreSpeakersAsync saves previousPage, optimistically advances, and rolls back when LoadFailed is set (:178-198), so the retry button re-requests the same page instead of silently skipping it. [Rubric §29, Resilience & Business Continuity].
      6. -
      7. An unscoped query is refused rather than issued. ScopeUnresolvedForPublicReader() (:135) is true exactly when a non-privileged reader has no event scope because the events fetch failed. FetchCurrentPageAsync (:113-132) re-resolves the events first in that state (:121-124), which heals a transient failure on the retry path, and the fetch delegate then throws rather than falling through to an unscoped query that would show other conferences' speakers (:127-130). The comment records the invariant and its one deliberate exception (:115-120): a successful fetch that genuinely found no published events keeps the unscoped default, matching PublicSessionList. [Rubric §11, Security] and [Rubric §26, Front-End Security].
      8. +
      9. A generation counter supersedes in-flight fetches. _generation (:64) is bumped by every reset (search, event filter, breakpoint change, retry) inside LoadSpeakersAsync (:134), and both LoadSpeakersAsync (:145-148) and LoadMoreSpeakersAsync (:177-180, :193-197) discard their rows when a newer generation has taken over. Without it a slow page-1 fetch could append to the list a later query had already cleared. [Rubric §19, State Management & Data Flow].
      10. +
      11. The list is cleared before the await, not after. The comment (:138-139) states why: the grid, and with it the sentinel, is gone while page 1 is in flight, so a stray intersection-observer callback cannot ask for page 2 of the query being replaced. [Rubric §18, UI Architecture & Component Design].
      12. +
      13. The page number is committed only on success. LoadMoreSpeakersAsync saves previousPage, optimistically advances, and rolls back when LoadFailed is set (:169-189), so the retry button re-requests the same page instead of silently skipping it. [Rubric §29, Resilience & Business Continuity].
      14. +
      15. An unscoped query is refused rather than issued. ScopeUnresolvedForPublicReader() (:126) is true exactly when a non-privileged reader has no event scope because the events fetch failed. FetchCurrentPageAsync (:104-123) re-resolves the events first in that state (:112-115), which heals a transient failure on the retry path, and the fetch delegate then throws rather than falling through to an unscoped query that would show other conferences' speakers (:117-122). The comment records the invariant and its one deliberate exception (:106-111): a successful fetch that genuinely found no published events keeps the unscoped default, matching PublicSessionList. [Rubric §11, Security] and [Rubric §26, Front-End Security].
    • Walkthrough
        -
      • CardsPerPage = 12 (:35-36) is assigned to the base's MobilePageSize in the constructor (:38), with the reasoning on the constant: a multiple of 2, 3 and 4 so a full chunk fills whole rows at every breakpoint. [Rubric §22, Responsive & Cross-Browser].
      • -
      • HasMoreSpeakers (:71) is true exactly while pages remain unfetched, and that is exactly when the sentinel renders (PublicSpeakerList.razor:133-147, comment at :131-132), so the trigger and the condition cannot disagree; the markup comment notes that the sentinel's absence is the "everything is loaded" signal, for the reader and for the tests.
      • -
      • OnInitializedAsync (:79-85) awaits the base first, because the base starts the events load before its own first await so the default event filter is resolved before the first speaker fetch, then runs LoadSpeakersAsync.
      • -
      • FetchCurrentPageAsync (:113-132) delegates to the base's LoadMobileDataAsync (DataGridListPageBase.cs:747) with a fixed FullName ascending sort; ApplyFilters (:224-229) emits FullName contains plus the base's ApplyEventFilter (:228, EventFilteredListPageBase.cs:195), which travels as the virtual EventId filter key that the speakers endpoint resolves through the EventSpeaker and SessionSpeaker joins, since a Speaker row has no EventId column (class doc, :19-20).
      • -
      • OnMobileDataRequestedAsync (:214) is the base's breakpoint-change hook (DataGridListPageBase.cs:960, invoked at :313), overridden here to restart the accumulation rather than fetch one replacement page.
      • -
      • RetryLoadAsync (:211), OnSearchChanged (:216-220) and ReloadForEventFilterAsync (:222) all funnel through LoadSpeakersAsync, which is the single reset entry point.
      • -
      • Initials (:232-239) builds the no-photo avatar text with spans, tolerating a blank first or last name; HasSocialLinks (:241-245) hides the social row when a speaker supplied none.
      • +
      • CardsPerPage = 12 (:34-35) is assigned to the base's MobilePageSize in the constructor (:37), with the reasoning on the constant: a multiple of 2, 3 and 4 so a full chunk fills whole rows at every breakpoint. [Rubric §22, Responsive & Cross-Browser].
      • +
      • HasMoreSpeakers (:70) is true exactly while pages remain unfetched, and that is exactly when the sentinel renders (PublicSpeakerList.razor:133-147, comment at :131-132), so the trigger and the condition cannot disagree; the markup comment notes that the sentinel's absence is the "everything is loaded" signal, for the reader and for the tests.
      • +
      • OnInitializedAsync (:78-84) awaits the base first, because the base starts the events load before its own first await so the default event filter is resolved before the first speaker fetch, then runs LoadSpeakersAsync.
      • +
      • FetchCurrentPageAsync (:104-123) delegates to the base's LoadMobileDataAsync (DataGridListPageBase.cs:747) with a fixed FullName ascending sort; ApplyFilters (:215-220) emits FullName contains plus the base's ApplyEventFilter (:219, EventFilteredListPageBase.cs:196), which travels as the virtual EventId filter key that the speakers endpoint resolves through the EventSpeaker and SessionSpeaker joins, since a Speaker row has no EventId column (class doc, :18-19).
      • +
      • OnMobileDataRequestedAsync (:205) is the base's breakpoint-change hook (DataGridListPageBase.cs:960, invoked at :313), overridden here to restart the accumulation rather than fetch one replacement page.
      • +
      • RetryLoadAsync (:202), OnSearchChanged (:207-211) and ReloadForEventFilterAsync (:213) all funnel through LoadSpeakersAsync, which is the single reset entry point.
      • +
      • Initials (:222-230) builds the no-photo avatar text with spans, tolerating a blank first or last name; HasSocialLinks (:232-236) hides the social row when a speaker supplied none.
    • -
    • Why it's built this way: attendees browse "the speakers at this conference", not a lifetime roster, so the default filter is the primary behavior and the picker is the privileged exception; and a directory of faces reads better as an endless wall of cards than as a pager. The class doc adds one more deliberate omission (:29-31): category chips are absent because the paged endpoint is called with includeChildren=false, so asking for them would both enlarge the payload and change the URL the output-cache warmup pins (ADR-040).
    • +
    • Why it's built this way: attendees browse "the speakers at this conference", not a lifetime roster, so the default filter is the primary behavior and the picker is the privileged exception; and a directory of faces reads better as an endless wall of cards than as a pager. The class doc adds one more deliberate omission (:28-30): category chips are absent because the paged endpoint is called with includeChildren=false, so asking for them would both enlarge the payload and change the URL the output-cache warmup pins (ADR-040).
    • Where it's used: the /conference/speakers route (PublicSpeakerList.razor:1, matching ConferenceRoutePaths.PublicSpeakers at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/ConferenceRoutePaths.cs:51); cards navigate to PublicSpeakerDetail.
    • -
    • Caveats / not-in-source: the join-based resolution of the virtual EventId filter is asserted by the class doc comment; the resolution itself lives in the Conference API. A restored mobile page number is deliberately ignored (class doc, :26-28), so a reader returning to this page starts at page 1 rather than at the scroll depth they left.
    • +
    • Caveats / not-in-source: the join-based resolution of the virtual EventId filter is asserted by the class doc comment; the resolution itself lives in the Conference API. A restored mobile page number is deliberately ignored (class doc, :26-27), so a reader returning to this page starts at page 1 rather than at the scroll depth they left.

    ⬅ ADC Conference - API, gRPC Contracts & Service Host • Index • ADC Engagement Module (Session Bookmarks) ➡

    diff --git a/docs/onboarding/group-22-engagement-module.html b/docs/onboarding/group-22-engagement-module.html index aabf42aa..8343a145 100644 --- a/docs/onboarding/group-22-engagement-module.html +++ b/docs/onboarding/group-22-engagement-module.html @@ -150,13 +150,13 @@

    22. ADC Engagement Module (S

    Five small aggregates, one shape. Every write in this module lands on an AuditableAggregateRootEntity<TIdentifierType> with private setters, private constructors, and a static Create factory returning Result<T> after a Result.Combine over a matching invariants helper. UserSessionBookmark (MMCA.ADC.Engagement.Domain/UserSessionBookmarks/UserSessionBookmark.cs:16) carries two scalar foreign keys, UserId and SessionId (:19, :22), and raises one UserSessionBookmarkChanged event with a DomainEntityState discriminator instead of separate Created and Deleted events (:57, :73, :88, documented in-code as BR-60); its Reactivate() (:68-76) undeletes a soft-deleted row and re-raises the Added variant, which is the domain half of the re-bookmarking rule BR-135. The comment at :55-56 is worth reading before you copy the event: the bookmark's identity is generated by the INSERT, so the event captures a zero id by value and consumers correlate on the user and the session instead. CheckIn (MMCA.ADC.Engagement.Domain/CheckIns/CheckIn.cs:28) is one aggregate for all three scopes of the CheckInScope enum, because the row, the idempotency rule, and the attendance query have the same shape for each and only the required target differs (:13-15); its CheckedInByUserId (:49) is what keeps a self-recorded scan distinguishable from an organizer's, and it is the one aggregate here that raises an integration event straight from its factory (:112). AttendeeBadge (MMCA.ADC.Engagement.Domain/Badges/AttendeeBadge.cs:18) holds one opaque Guid credential per user (:24) and raises no domain event at all, since a badge existing is not a fact any other module reacts to (:12-15); Regenerate() (:59) revokes every printed or screenshotted copy, and the credential is minted with Guid.NewGuid() rather than Guid.CreateVersion7() precisely because a v7 value embeds a timestamp and orders monotonically, which is the one property a bearer credential must not have (:65-68). PointsEntry (MMCA.ADC.Engagement.Domain/Points/PointsEntry.cs:31) is an append-only ledger row with no mutators, so a total is always the sum of its entries (:12-13), and it snapshots the configured award at earn time (:39-40) so retuning the economy never rewrites history. LeaderboardOptIn (MMCA.ADC.Engagement.Domain/Points/LeaderboardOptIn.cs:19) stores the display name the attendee explicitly published (:35), which is why serving the board needs no call into Identity, and it keeps EraseDisplayName() (:130) deliberately separate from Delete() (:109): leaving the board and erasing the name you published there are two different promises, and only the second is irreversible. CheckIn and PointsEntry additionally declare IAuditedEntity (CheckIn.cs:28, PointsEntry.cs:31), so their field-level change history is written by the audit-trail interceptor (ADR-075): an attendance assertion about a named person and a prize-bearing ledger are both things you want to be able to prove rather than merely assert.

    The bookmark slice, end to end. BookmarksController (MMCA.ADC.Engagement.API/Controllers/BookmarksController.cs:34) is the thin REST edge: each action injects a handler and delegates, mapping Result failures to RFC 9457 Problem Details through ApiControllerBase. The POST does its own ownership bind first, because the owner arrives in the request body rather than in a query argument: a caller without the Organizer role may only create a bookmark whose UserId equals their user-identifier claim, otherwise the action returns Error.Forbidden (:63-70, ADR-033). It also carries IdempotentAttribute (:50), which is safe here for the reason stated in the doc comment: the request names the (user, session) pair explicitly and a duplicate already answers 409, so replaying a retried star is what the attendee meant (ADR-017). It then routes into CreateBookmarkHandler (MMCA.ADC.Engagement.Application/UserSessionBookmarks/UseCases/Create/CreateBookmarkHandler.cs:16), still the most instructive slice in the module: it asks Conference's ISessionBookmarkValidationService whether the session may be bookmarked at all (:29, BR-49 and BR-91 across the service boundary), returns Error.Conflict on an existing active bookmark (:36-46, BR-21), loads any soft-deleted row through the repository's FindIncludingDeletedAsync (:49-53) and hands the create-or-reactivate decision to the pure, static BookmarkManagementDomainService (:56), so BR-135 lives in the domain instead of being smeared across the handler. The domain service is a static class with one CreateOrReactivate method over the pre-fetched row (MMCA.ADC.Engagement.Domain/UserSessionBookmarks/BookmarkManagementDomainService.cs:11, :20-35): it has no dependencies and no state, so there is no interface and no DI registration, and the handler calls it directly. Only a genuinely new entity is added to the repository (CreateBookmarkHandler.cs:61-64), and the save is wrapped in a catch filtered by the framework's injected IUniqueConstraintViolationDetector (:70), so a request that loses the insert race against the filtered unique index sees the same 409 the pre-check would have produced rather than a raw 500 (:70-82). That detector is the module's shared answer to "the database is the rule, the pre-check is only a courtesy": PointsAwarder, GetOrCreateMyBadgeHandler and SetLeaderboardParticipationHandler inject the identical abstraction. On the read side, GetUserBookmarksHandler (.../GetUserBookmarks/GetUserBookmarksHandler.cs:17) returns a paged, CreatedOn-descending PagedCollectionResult<T> of DTOs, with the page size clamped by PagingMath before anything touches the database (:31) and ordering plus OFFSET/FETCH pushed into SQL through IQueryableExecutor, the Id tie-break after CreatedOn making the ORDER BY total so bookmarks created in the same instant neither repeat nor vanish across pages (:60-70); GetBookmarkedSessionIdsHandler (.../GetBookmarkedSessionIds/GetBookmarkedSessionIdsHandler.cs:12) is the lightweight sibling the unified Sessions page turns into per-row filled or empty bookmark icons. Both list endpoints are scoped by the shared OwnerOrAdminFilter applied as a [ServiceFilter] (BookmarksController.cs:84, :105), configured for ADC through OwnerOrAdminFilterOptions in AddModuleEngagementAPI (MMCA.ADC.Engagement.API/DependencyInjection.cs:45-56): ClaimTypes.NameIdentifier as the owner claim, the userId query argument, and the Organizer bypass role. The DELETE keeps a separate inline, database-backed ownership check that returns 404 rather than 403 so it never leaks whether a bookmark exists (BookmarksController.cs:131-148), then runs the generic DeleteEntityCommand<TEntity, TIdentifierType> (:151), which soft-deletes.

    Check-in: four ways in, one row out. CheckInsController (MMCA.ADC.Engagement.API/Controllers/CheckInsController.cs:39) exposes the whole surface behind [FeatureGate(EngagementFeatures.CheckIn)] and a plain [Authorize] (:33-34). An attendee fetches their own badge at GET my-badge with no permission at all (:46), because GetOrCreateMyBadgeHandler (.../GetOrCreateMyBadge/GetOrCreateMyBadgeHandler.cs:17) reads the identity from the token and mints on first use (:38-70), returning a MyBadgeDTO carrying nothing but the credential (:72-74). The QR itself is formatted by BadgePayload (MMCA.ADC.Engagement.Shared/CheckIns/Badges/BadgePayload.cs:12), one place that owns both the mmca-adc:badge: prefix (:15) and the parser (:30), so the display side and the check-in handler cannot drift. The organizer paths are gated by [HasPermission(EngagementPermissions.CheckInManage)] (CheckInsController.cs:75, :99, :179): CheckInAttendeeHandler (.../CheckInAttendee/CheckInAttendeeHandler.cs:19) resolves the scanned credential to a badge row and answers a malformed payload and an unknown-but-well-formed credential with the same error, since a scanner that could tell them apart would be an oracle for guessing credentials (:41-56), and ManualCheckInHandler covers the dead-phone fallback. Both then run the shared CheckInProcessor (MMCA.ADC.Engagement.Application/CheckIns/Services/CheckInProcessor.cs:23), a static helper rather than an injected service so neither handler gains a dependency it does not own (:11-14). The processor is split in two halves on purpose: ExecuteAsync (:109) is the organizer path, which validates the target through Conference and lets the server's answer for the owning event win over the value the scanning client sent (:124-128), while RecordAsync (:58) is the write half every entry point shares, short-circuiting a repeat scan into a success carrying the original timestamp rather than a conflict (:71-75) and otherwise recording exactly one row via the RecordedCheckIn outcome struct (:38). The two self-service flows are separate handlers that reuse only that write half. RecordSponsorVisitHandler (.../RecordSponsorVisit/RecordSponsorVisitHandler.cs:34) and RecordRoomCheckInHandler (.../RecordRoomCheckIn/RecordRoomCheckInHandler.cs:26) take the attendee from the token, never the request, and each carries its own feature flag (CheckInsController.cs:130, :161) so a printed sponsor code and a printed room code can be retired independently. The room flow is the sharpest piece of design here: only the room travels in the request, and the server resolves which session that room is hosting at the request instant plus a configured grace window (RecordRoomCheckInHandler.cs:50-52), so a shared link records nothing outside a session's window and nobody can name a session they are not standing in; an unknown room and an idle room answer identically so the response never confirms which room ids exist (:55-63). GetAttendanceStatsHandler (.../GetAttendanceStats/GetAttendanceStatsHandler.cs:15) closes the loop for organizers, and both of its figures are computed in SQL: a CountAsync for the event scope and a CountByAsync grouped per session, so what crosses the wire is one row per session rather than one per check-in (:27-34). All of this is ADR-072; the camera half of the scanner is ADR-071.

    -

    Points: one write path, several adapters. Everything that earns goes through IPointsAwarder / PointsAwarder (MMCA.ADC.Engagement.Application/Points/Services/PointsAwarder.cs:29), which takes an (activity, subjectKey) pair and knows nothing about events, sessions or sponsors. It reads the configured value from PointsSettings (MMCA.ADC.Engagement.Shared/Points/PointsSettings.cs:12) and treats a configured or missing 0 as a per-rule kill switch, writing nothing rather than a zero-value ledger row (PointsAwarder.cs:43-51); it pre-checks for an existing award purely to make the common duplicate path a clean no-op (:55-64); and it reads the unique-index violation as already awarded, not as a failure to retry (:75-82). The rule that matters is the index, not the code: the unique (UserId, ActivityType, SubjectKey) index in PointsEntryConfiguration (MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/Points/PointsEntryConfiguration.cs:45-48) is simultaneously the idempotency guarantee and the anti-farming rule, because PointsSubjectKeys collapses N questions or N answers in one session onto one subject key. Four adapters feed it. Three are integration-event handlers built on ScopedIntegrationEventHandlerBase<TIntegrationEvent>: AttendeeCheckedInPointsHandler (.../IntegrationEventHandlers/AttendeeCheckedInPointsHandler.cs:31) maps the wire scope string onto an earn rule and logs-and-skips a scope this build has never heard of rather than dead-lettering a message no retry could fix (:42-46, :65-99), while SessionFeedbackSubmittedPointsHandler and EventFeedbackSubmittedPointsHandler consume events Conference publishes. The fourth, SessionQuestionSubmittedPointsHandler (.../DomainEventHandlers/SessionQuestionSubmittedPointsHandler.cs:51), rides an in-process domain event instead, an explicit at-most-once trade-off documented in the type: a crash between the commit and the dispatch loses one small award and nothing else, and promoting it to an integration event later is a one-file change on each side (:30-39). Its doc comment is also the best worked example in the module of the id-by-value trap described above, and of why every field it needs now comes off the event rather than from a read-back (:21-29). A fifth handler earns nothing at all: UserDeletedPointsHandler (.../IntegrationEventHandlers/UserDeletedPointsHandler.cs:37) consumes Identity's UserDeleted and erases the published leaderboard name, reading with ignoreQueryFilters: true precisely because an attendee who already left the board still has a soft-deleted row carrying the name that must be erased (:53-61).

    -

    Reading the points back. PointsController (MMCA.ADC.Engagement.API/Controllers/PointsController.cs:36) is a good study in designing an ownership problem out of existence: nothing on the surface takes a user id, so the three attendee endpoints resolve the caller from the token inside their handlers and there is no ownership check to get wrong (:25-29). GetMyPointsHandler serves the caller's own total and a page of the entries behind it; SetLeaderboardParticipationHandler (.../SetLeaderboardParticipation/SetLeaderboardParticipationHandler.cs:30) takes the published display name from the caller's token claims server-side, which is why SetLeaderboardParticipationRequest carries only a boolean (:14-19), and rejoining reactivates the soft-deleted opt-in rather than accumulating dead rows (:20-24). GetLeaderboardHandler (.../GetLeaderboard/GetLeaderboardHandler.cs:28) builds the board from the opt-in list and the ledger with no call into Identity (:12-17), projecting the opt-ins to the two columns it needs through the private OptInRow record (:40-43, :85) and summing the ledger with one grouped SumByAsync in the database rather than pulling every row back to fold in memory (:50-59). Ranks are distinct and sequential with an ordinal name tie-break, so repeated reads return the same order (:18-24, :65-77). Only the organizer rollup carries a permission, [HasPermission(EngagementPermissions.PointsViewOverview)] (PointsController.cs:121), and even that view returns activity, points and timestamps without attendee identity (:114-119).

    -

    Persistence. Engagement's ModuleApplicationDbContext (MMCA.ADC.Engagement.Infrastructure/Persistence/DbContexts/ModuleApplicationDbContext.cs:19) is an abstract context declaring the module's ten internal DbSets (bookmarks, badges, check-ins, the three live-poll sets, the two question sets, the points ledger and the opt-ins, :27-54) and inheriting auditing, soft-delete query filters and domain-event dispatch from the common ApplicationDbContext (:24); the concrete per-engine class (SQLServerDbContext today) sits under it, one instance per physical database (ADR-006). The table shapes are where the module's business rules become enforceable, and every configuration derives from EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>. UserSessionBookmarkConfiguration (.../UserSessionBookmarkConfiguration.cs:17) makes BR-21 a filtered unique index over (UserId, SessionId) restricted to non-deleted rows (:31-34) and adds a SessionId-leading index the composite cannot serve (:36-39). CheckInConfiguration (.../CheckInConfiguration.cs:19) carries three filtered unique indexes, one per scope, with the scope literals kept side by side as named constants because an index filter is SQL and cannot read the enum (:22-26, :48-62); the sponsor one is the once-per-sponsor cap that makes a shared booth link worth nothing past the first scan (:58-62), and two more indexes serve the attendance rollup (:64-69). PointsEntryConfiguration adds the UserId-leading index that makes "my points" a seek (:50-52). The live-poll tables follow the same discipline even though their aggregates are taught in Group 23: LivePollVoteConfiguration (.../LivePolls/LivePollVoteConfiguration.cs:17) makes BR-225, one active vote per (LivePollId, UserId), a filtered unique index through the shared HasSoftDeleteFilter() helper (:34-37), which is the database-level guarantee behind the vote handler's create-or-reactivate dance (:12-15), and adds the (LivePollId, OptionId) index the tallies group by (:39-40). LivePollConfiguration (.../LivePolls/LivePollConfiguration.cs:16) indexes EventId for the Happening Now and organizer manage views (:34-35) and (SessionId, Status) for the session Live page and presenter view, the conference-day hot path that previously scanned (:37-41), and pins the Options navigation to PropertyAccessMode.Field so the encapsulated collection cannot silently stop materializing if the property changes shape (:43-50). LivePollOptionConfiguration (.../LivePolls/LivePollOptionConfiguration.cs:10) is the plain child-entity case: a required text column bounded by the domain's own LivePollInvariants.OptionTextMaxLength and the required FK back to its poll (:18-25). Cross-module identifiers (UserId, EventId, SessionId, SponsorId) are scalar columns throughout, never cross-database foreign keys (CheckInConfiguration.cs:16-17). The module owns ADC_Engagement and is its sole migrator, applying EF migrations idempotently at startup (MMCA.ADC.Engagement.Service/Program.cs:305-318, ADR-030; the one run that skips the schema step is design-time OpenAPI generation, and only in Development, which a production host can never be talked into, ADR-122), so events serialized into its own outbox during SaveChangesAsync never race another service's rows (ADR-003).

    -

    How Engagement talks to its neighbors. Engagement is both a client and a server on the gRPC mesh (ADR-007, ADR-008). As a server, its in-process BookmarkCountService (MMCA.ADC.Engagement.Application/UserSessionBookmarks/Services/BookmarkCountService.cs:11) answers "how many people bookmarked this session" for Conference's speaker pages, including a batched variant that replaces the caller's per-session fan-out with one grouped COUNT and back-fills a zero for every requested session that has none (:37-46). It goes on the wire through BookmarkCountsGrpcService (MMCA.ADC.Engagement.Service/Grpc/BookmarkCountsGrpcService.cs:24) and is consumed through the client-side BookmarkCountServiceGrpcAdapter (MMCA.ADC.Engagement.Contracts/BookmarkCountServiceGrpcAdapter.cs:14), which re-implements the same C# interface so calling code never knows it crossed a process. Read the trust-boundary note before copying the pattern: that endpoint is mapped deliberately without RequireAuthorization(), because the Conference callers serve [AllowAnonymous] output-cached endpoints and a cache-miss request from an anonymous visitor carries no bearer to forward; the exposure is bounded by the payload (aggregate counts, no PII, no user ids) and by an internal-only container ingress (MMCA.ADC.Engagement.Service/Program.cs:333-342). The identical bridge carries the privacy export, and there the choice flips. UserEngagementExportService (MMCA.ADC.Engagement.Application/Exports/UserEngagementExportService.cs:17) projects a user's bookmarks, submitted questions, points ledger, check-in history and leaderboard participation server-side into a UserEngagementExportDTO of ids and dates (:24-81), with soft-deleted rows excluded by the global query filter; check-ins are exported independently of the ledger, because a switched-off rule or a deduped repeat scan means the ledger is no proxy for the attendance record a data subject is entitled to (:48-50). It is served by UserEngagementExportGrpcService and consumed by Identity through UserEngagementExportServiceGrpcAdapter, and that endpoint does carry RequireAuthorization() because it returns personal data keyed by a raw user id (Program.cs:335-341, PRIVACY.md §7, ADR-076, ADR-005). The Contracts-layer DependencyInjection (MMCA.ADC.Engagement.Contracts/DependencyInjection.cs:16) is what swaps in-process for remote: each helper registers the typed client and then Replaces (never TryAdds) the existing registration, so it overwrites both the real service and the disabled stub (:45-51), and it must run after ModuleLoader.DiscoverAndRegister (:36-39).

    +

    Points: one write path, several adapters. Everything that earns goes through IPointsAwarder / PointsAwarder (MMCA.ADC.Engagement.Application/Points/Services/PointsAwarder.cs:29), which takes an (activity, subjectKey) pair and knows nothing about events, sessions or sponsors. It reads the configured value from PointsSettings (MMCA.ADC.Engagement.Shared/Points/PointsSettings.cs:12) and treats a configured or missing 0 as a per-rule kill switch, writing nothing rather than a zero-value ledger row (PointsAwarder.cs:43-51); it pre-checks for an existing award purely to make the common duplicate path a clean no-op (:55-64); and it reads the unique-index violation as already awarded, not as a failure to retry (:75-82). The rule that matters is the index, not the code: the unique (UserId, ActivityType, SubjectKey) index in PointsEntryConfiguration (MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/Points/PointsEntryConfiguration.cs:45-48) is simultaneously the idempotency guarantee and the anti-farming rule, because PointsSubjectKeys collapses N questions or N answers in one session onto one subject key. Four adapters feed it. Three are integration-event handlers built on ScopedIntegrationEventHandlerBase<TIntegrationEvent>: AttendeeCheckedInPointsHandler (.../IntegrationEventHandlers/AttendeeCheckedInPointsHandler.cs:31) maps the wire scope string onto an earn rule and logs-and-skips a scope this build has never heard of rather than dead-lettering a message no retry could fix (:42-46, :65-99), while SessionFeedbackSubmittedPointsHandler and EventFeedbackSubmittedPointsHandler consume events Conference publishes. The fourth, SessionQuestionSubmittedPointsHandler (.../DomainEventHandlers/SessionQuestionSubmittedPointsHandler.cs:51), rides an in-process domain event instead, an explicit at-most-once trade-off documented in the type: a crash between the commit and the dispatch loses one small award and nothing else, and promoting it to an integration event later is a one-file change on each side (:30-39). Its doc comment is also the best worked example in the module of the id-by-value trap described above, and of why every field it needs now comes off the event rather than from a read-back (:21-29). A fifth handler earns nothing at all: UserDeletedPointsHandler (.../IntegrationEventHandlers/UserDeletedPointsHandler.cs:37) consumes Identity's UserDeleted and erases the published leaderboard name, reading with ignoreQueryFilters: true precisely because an attendee who already left the board still has a soft-deleted row carrying the name that must be erased (:53-61). It is not the only consumer of that event in this module, because the Identity-side erasure cannot reach across the database boundary: UserDeletedBookmarksHandler (MMCA.ADC.Engagement.Application/UserSessionBookmarks/IntegrationEventHandlers/UserDeletedBookmarksHandler.cs:22) deletes the account's session bookmarks, the promise PRIVACY.md section 5 makes (:10-13), and UserDeletedBadgeHandler (MMCA.ADC.Engagement.Application/CheckIns/IntegrationEventHandlers/UserDeletedBadgeHandler.cs:22) deletes the attendee badge that carries the per-user credential while deliberately keeping the check-in records themselves (:10-13); the live layer's UserDeletedSessionQuestionsHandler and UserDeletedVotesHandler do the same for questions and votes. The two new handlers are the same shape line for line: each extends ScopedIntegrationEventHandlerBase<UserDeleted> (:25 in each), reads only active rows with tracking on, calls Delete() on each, skips the save entirely when nothing was deleted, and lets exceptions propagate (:33-58), so a redelivery under at-least-once delivery finds nothing and writes nothing, and a failed erasure is retried rather than acked away (:15-17).

    +

    Reading the points back. PointsController (MMCA.ADC.Engagement.API/Controllers/PointsController.cs:36) is a good study in designing an ownership problem out of existence: nothing on the surface takes a user id, so the three attendee endpoints resolve the caller from the token inside their handlers and there is no ownership check to get wrong (:25-29). GetMyPointsHandler serves the caller's own total and a page of the entries behind it; SetLeaderboardParticipationHandler (.../SetLeaderboardParticipation/SetLeaderboardParticipationHandler.cs:31) takes the published display name from the caller's token claims server-side, which is why SetLeaderboardParticipationRequest carries only a boolean (:16-19), and rejoining reactivates the soft-deleted opt-in rather than accumulating dead rows (:22-24). GetLeaderboardHandler (.../GetLeaderboard/GetLeaderboardHandler.cs:28) builds the board from the opt-in list and the ledger with no call into Identity (:12-17), projecting the opt-ins to the two columns it needs through the private OptInRow record (:40-43, :85) and summing the ledger with one grouped SumByAsync in the database rather than pulling every row back to fold in memory (:50-59). Ranks are distinct and sequential with an ordinal name tie-break, so repeated reads return the same order (:18-24, :65-77). Only the organizer rollup carries a permission, [HasPermission(EngagementPermissions.PointsViewOverview)] (PointsController.cs:121), and even that view returns activity, points and timestamps without attendee identity (:114-119).

    +

    Persistence. Engagement's ModuleApplicationDbContext (MMCA.ADC.Engagement.Infrastructure/Persistence/DbContexts/ModuleApplicationDbContext.cs:19) is an abstract context declaring the module's ten internal DbSets (bookmarks, badges, check-ins, the three live-poll sets, the two question sets, the points ledger and the opt-ins, :27-54) and inheriting auditing, soft-delete query filters and domain-event dispatch from the common ApplicationDbContext (:24); the concrete per-engine class (SQLServerDbContext today) sits under it, one instance per physical database (ADR-006). The table shapes are where the module's business rules become enforceable, and every configuration derives from EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>. UserSessionBookmarkConfiguration (.../UserSessionBookmarkConfiguration.cs:17) makes BR-21 a filtered unique index over (UserId, SessionId) restricted to non-deleted rows (:31-34) and adds a SessionId-leading index the composite cannot serve (:36-39). CheckInConfiguration (.../CheckInConfiguration.cs:19) carries three filtered unique indexes, one per scope, with the scope literals kept side by side as named constants because an index filter is SQL and cannot read the enum (:22-26, :48-62); the sponsor one is the once-per-sponsor cap that makes a shared booth link worth nothing past the first scan (:58-62), and two more indexes serve the attendance rollup (:64-69). PointsEntryConfiguration adds the UserId-leading index that makes "my points" a seek (:50-52). The live-poll tables follow the same discipline even though their aggregates are taught in Group 23: LivePollVoteConfiguration (.../LivePolls/LivePollVoteConfiguration.cs:17) makes BR-225, one active vote per (LivePollId, UserId), a filtered unique index through the shared HasSoftDeleteFilter() helper (:34-37), which is the database-level guarantee behind the vote handler's create-or-reactivate dance (:12-15), and adds the (LivePollId, OptionId) index the tallies group by (:39-40). LivePollConfiguration (.../LivePolls/LivePollConfiguration.cs:16) indexes EventId for the Happening Now and organizer manage views (:34-35) and (SessionId, Status) for the session Live page and presenter view, the conference-day hot path that previously scanned (:37-41), and pins the Options navigation to PropertyAccessMode.Field so the encapsulated collection cannot silently stop materializing if the property changes shape (:43-50). LivePollOptionConfiguration (.../LivePolls/LivePollOptionConfiguration.cs:10) is the plain child-entity case: a required text column bounded by the domain's own LivePollInvariants.OptionTextMaxLength and the required FK back to its poll (:18-25). Cross-module identifiers (UserId, EventId, SessionId, SponsorId) are scalar columns throughout, never cross-database foreign keys (CheckInConfiguration.cs:16-17). The module owns ADC_Engagement and is its sole migrator, applying EF migrations idempotently at startup (MMCA.ADC.Engagement.Service/Program.cs:307-317, ADR-030; the one run that skips the schema step is design-time OpenAPI generation, and only in Development, which a production host can never be talked into, ADR-122), so events serialized into its own outbox during SaveChangesAsync never race another service's rows (ADR-003).

    +

    How Engagement talks to its neighbors. Engagement is both a client and a server on the gRPC mesh (ADR-007, ADR-008). As a server, its in-process BookmarkCountService (MMCA.ADC.Engagement.Application/UserSessionBookmarks/Services/BookmarkCountService.cs:11) answers "how many people bookmarked this session" for Conference's speaker pages, including a batched variant that replaces the caller's per-session fan-out with one grouped COUNT and back-fills a zero for every requested session that has none (:37-46). It goes on the wire through BookmarkCountsGrpcService (MMCA.ADC.Engagement.Service/Grpc/BookmarkCountsGrpcService.cs:24) and is consumed through the client-side BookmarkCountServiceGrpcAdapter (MMCA.ADC.Engagement.Contracts/BookmarkCountServiceGrpcAdapter.cs:14), which re-implements the same C# interface so calling code never knows it crossed a process. Read the trust-boundary note before copying the pattern: that endpoint is mapped deliberately without RequireAuthorization(), because the Conference callers serve [AllowAnonymous] output-cached endpoints and a cache-miss request from an anonymous visitor carries no bearer to forward; the exposure is bounded by the payload (aggregate counts, no PII, no user ids) and by an internal-only container ingress. The omission is now stated rather than implied with .AllowAnonymous(), because the framework's fallback authorization policy gates every endpoint that declares nothing and would otherwise turn the deliberate omission into a 401 on the public speaker pages (MMCA.ADC.Engagement.Service/Program.cs:329-341). The identical bridge carries the privacy export, and there the choice flips. UserEngagementExportService (MMCA.ADC.Engagement.Application/Exports/UserEngagementExportService.cs:17) projects a user's bookmarks, submitted questions, points ledger, check-in history and leaderboard participation server-side into a UserEngagementExportDTO of ids and dates (:24-81), with soft-deleted rows excluded by the global query filter; check-ins are exported independently of the ledger, because a switched-off rule or a deduped repeat scan means the ledger is no proxy for the attendance record a data subject is entitled to (:48-50). It is served by UserEngagementExportGrpcService and consumed by Identity through UserEngagementExportServiceGrpcAdapter, and that endpoint does carry RequireAuthorization() because it returns personal data keyed by a raw user id (Program.cs:343-349, PRIVACY.md §7, ADR-076, ADR-005). The Contracts-layer DependencyInjection (MMCA.ADC.Engagement.Contracts/DependencyInjection.cs:16) is what swaps in-process for remote: each helper registers the typed client and then Replaces (never TryAdds) the existing registration, so it overwrites both the real service and the disabled stub (:45-51), and it must run after ModuleLoader.DiscoverAndRegister (:36-39).

    The outbound live-channel path. Engagement never blocks a command on Notification. Handlers enqueue a post-commit broadcast as a LiveChannelPublishWorkItem (MMCA.ADC.Engagement.Application/Live/ILiveChannelPublishQueue.cs:10) through ILiveChannelPublishQueue (:21), a three-string record (channel key, event name, pre-serialized payload) carrying no entity and no per-user data. LiveChannelPublishQueue (MMCA.ADC.Engagement.Application/Live/LiveChannelPublishQueue.cs:14) is the single concrete channel, registered once as a singleton and exposed to handlers via the interface (MMCA.ADC.Engagement.Application/DependencyInjection.cs:53-54). Read its backpressure contract carefully: the channel is bounded at 1024 items (:18) with BoundedChannelFullMode.DropOldest and SingleReader (:33-40), so Enqueue returns void and never rejects an item (it evicts the oldest rather than refusing the write, :50-58), and a discarded broadcast is observable only through DroppedCount (:47), the itemDropped callback, and a warning log. LiveChannelPublishProcessor (MMCA.ADC.Engagement.Infrastructure/Live/LiveChannelPublishProcessor.cs:30) is the single-reader BackgroundService drain, registered as the module's only hosted service (MMCA.ADC.Engagement.Infrastructure/DependencyInjection.cs:21), forwarding items to ILiveChannelPublisher in FIFO order: one reader preserves per-session ordering, and the swallow is BestEffort's rather than a hand-rolled catch, so a peer that has quietly stopped accepting broadcasts is countable on the besteffort.dispatch.failed meter instead of being a warning nobody alerts on (:21-28, :45). What actually gets published is taught in Group 23.

    -

    Module wiring, feature gating, and the extracted host. EngagementModule (MMCA.ADC.Engagement.API/EngagementModule.cs:14) is the IModule entry point discovered by ModuleLoader and registered in topological order; it declares a dependency on Conference with RequiresDependencies => true (:20, :23), and Register is a one-liner into AddEngagementModule (:26-27), which chains the Application, Infrastructure and API registrations in dependency order (MMCA.ADC.Engagement.API/DependencyInjection.cs:26-28). The Application registration is where the module's own services land: the bookmark aggregate's navigation populator, entity query service and generic delete handler (MMCA.ADC.Engagement.Application/DependencyInjection.cs:38-40; the bookmark domain service is static and needs no registration), the two cross-module services scoped (:43, :47), the awarder scoped and registered by hand because it is a plain service the convention scan does not see (:73-81), and everything conventional (handlers, mappers, validators, event handlers) discovered by ScanModuleApplicationServices<ClassReference>() (:85), which is where the per-layer ClassReference marker earns its keep. Each of the four layers declares its own AssemblyReference / ClassReference pair, and the design-time migrations factory reaches for the Infrastructure one, because the IEntityTypeConfiguration classes live in Infrastructure rather than Application. When the module is disabled in a host, RegisterDisabledStubs swaps in DisabledBookmarkCountService and DisabledUserEngagementExportService (EngagementModule.cs:30-34) so a Conference-only or Identity-only host still resolves those interfaces. Every capability has its own kill switch in EngagementFeatures (MMCA.ADC.Engagement.Shared/EngagementFeatures.cs:10): SessionBookmarks, LivePolls, CheckIn, SponsorVisits, RoomCheckIn, Points and SessionQA (:22, :34, :46, :59, :71, :83, :95), each turning its controller or route into a 404 without a deploy. All seven are declared [FeatureFlag(FeatureFlagLifetime.Permanent, Owner = "ADC Engagement")] (:21 and the same attribute above each of the other six), which is how the flag inventory is told these are shipped capability switches a conference chooses to run with or without, not rollout toggles waiting on a removal date (ADR-031); the two printed-QR switches say so explicitly, SponsorVisits naming the softer alternative of setting Points:SponsorVisit to 0 to keep the visit but stop the award (:48-57) and RoomCheckIn recording why it is separate at all, since the two printed surfaces are retired on their own schedules (:61-70). Authorization above authentication is capability-based, not role-based: EngagementPermissions (MMCA.ADC.Engagement.Shared/Authorization/EngagementPermissions.cs:9) declares three capabilities, engagement:live:manage, engagement:checkin:manage and engagement:points:view-overview (:16, :23, :30), plus an All list so the whole set can be granted in one call (:33-38). The grant map itself is a separate named type, EngagementPermissionGrants (MMCA.ADC.Engagement.Shared/Authorization/EngagementPermissionGrants.cs:24), whose static Apply grants every Engagement capability to the Organizer role and to no other (:33-38); attendee-facing endpoints need no grant because they carry a plain [Authorize] with no capability, and a speaker's session-scoped rights come from the speaker-assignment check inside the handlers (BR-236) rather than from any permission declared here (:26-31). AddModuleEngagementAPI applies that map through services.AddPermissions(EngagementPermissionGrants.Apply) (MMCA.ADC.Engagement.API/DependencyInjection.cs:61, ADR-020). The reason it is a named map in the Shared contracts project rather than a lambda inside the registration is that two hosts apply the same binding: this module's own host, to gate its [HasPermission(...)] endpoints, and the token-minting Identity host, so an issued access token carries a permission claim for every Engagement capability the caller's role holds and not only for the modules the minting host happens to boot (EngagementPermissionGrants.cs:6-22). MMCA.ADC.Engagement.Service boots exactly this one module and is worth reading top to bottom as the reference shape for an extracted host. Kestrel is configured in one line, builder.ConfigureEndpointsWithHealthProbe(HttpProtocols.Http2) (Program.cs:67), the shared helper from KestrelEndpointExtensions: Http2-only on cleartext so cross-service gRPC clients negotiate h2c by prior knowledge, plus the optional HTTP/1.1-only health-probe listener (ADR-012). PointsSettings and CheckInSettings are bound here rather than in the module, because the module registration takes no IConfiguration, and both are deliberately not ValidateOnStart: a missing section binds working defaults and an explicit 0 is the documented kill switch, so no value an organizer sets should stop this host from booting mid-conference (:122-133, a considered divergence from ADR-070). The scheduler and audit trail are wired next (:193, :197, ADR-074, ADR-075), and EngagementErrorResources contributes the module's error-code translations (:216, ADR-027). Composition itself runs through AddMmcaApplicationPipeline, a single ordered chain that registers the modules first and then, because Conference is disabled in this process, re-points Engagement's two Conference dependencies at gRPC clients, swaps the framework's no-op live-channel publisher for the Notification adapter, and finally wires broker messaging (:278-288, ADR-014 closes and seals the decorator pipeline at the end of it). This host is not publisher-only: the points game consumes four integration events, AttendeeCheckedIn, SessionFeedbackSubmitted, EventFeedbackSubmitted and UserDeleted (:285-288). AttendeeCheckedIn is the novel one: it is published by this same service (the CheckIn factory raises it and the outbox captures it in the check-in transaction), so this is ADC's first broker self-consumption, which MassTransit's type topology supports with no special casing (:268-271). SelfHttpWarmupTask (MMCA.ADC.Engagement.Service/SelfHttpWarmupTask.cs:23) extends SelfHttpWarmupTaskBase and holds /health/ready not-ready until the routing, auth and middleware pipeline has been JITted (Program.cs:153-157, ADR-025); the two gRPC endpoints are mapped last (:333, :341).

    -

    The UI surfaces. EngagementUIModule (MMCA.ADC.Engagement.UI/EngagementUIModule.cs:17) implements IUIModule to contribute the module's Blazor assembly (:33), the invisible LiveEventListener layout component (:31), and six nav items declared as resource keys with the organizer ones carrying a RequiredRole so the shared NavMenu hides them from attendees (:21-29, ADR-027); their targets live in EngagementRoutePaths (MMCA.ADC.Engagement.UI/EngagementRoutePaths.cs:8), which also holds the two deep-link-only self-service routes that deliberately contribute no nav item at all (:29-32). The pages divide by actor. Attendees get MyBadge (Pages/CheckIn/MyBadge.razor.cs:15), which renders the opaque payload beside the account name as plain text so a human can confirm the badge belongs to its holder (:10-13), MyPoints, and the two QR landing pages SponsorVisit (Pages/Sponsors/SponsorVisit.razor.cs:21) and RoomCheckIn, which post the write themselves so the whole interaction is one scan with nothing to press; both keep the write off the prerender pass and behind a once-per-instance guard (SponsorVisit.razor.cs:48-55) and branch their on-screen state off the server's stable error code carried back by SelfCheckInOutcome<TResult> (Services/SelfCheckInOutcome.cs:20) and named in CheckInErrorCodes (Services/CheckInErrorCodes.cs:8), because two of the three refusals share the same 404 status. Organizers get CheckInScan (Pages/CheckIn/CheckInScan.razor.cs:18), which loops the shared scanner where a camera exists and falls back to the AttendeeSearchPanel name and email search everywhere else (:12-16, :38), OrganizerAttendance and OrganizerPointsOverview. The two feedback pages, EventFeedback (Pages/Feedback/EventFeedback.razor.cs:19) and SessionFeedback (Pages/Feedback/SessionFeedback.razor.cs:18), render a dynamic question form whose definitions and answers belong to Conference, with per-question values held in a FeedbackAnswerModel (Pages/Feedback/FeedbackAnswerModel.cs:23) whose DataAnnotations are the single declaration of the 4000-character free-text rule (:30-34). Both submit the same way: the page collects every answered question and posts the whole form as one POST-as-upsert the server applies atomically, so a refusal wrote nothing, there is no partially saved state to report, and the form stays dirty for the same Submit button to retry; an all-blank form stays a local no-op rather than a request the batch endpoint would refuse (EventFeedback.razor.cs:183-202, SessionFeedback.razor.cs:212-231, BR-107). The contracts say so on the batch member of IEventFeedbackUIService and its session sibling (Services/Feedback/IFeedbackUIService.cs:32-36, :66-70), and EventFeedbackService mints the idempotency key once, outside its retry pipeline, so a timed-out submit that retries is recognized by the server rather than re-applying the form (Services/Feedback/EventFeedbackService.cs:53-58). Behind the pages sit ordinary authenticated HTTP clients built on AuthenticatedServiceBase: BookmarkService and the cross-module SessionBookmarkUIService (which also feeds SessionReminderCoordinator and the pure SessionReminderPlanner, whose SessionReminder records carry a stable notification id so rescheduling replaces rather than duplicates, Services/SessionReminderPlanner.cs:13, :29-38, ADR-042), CheckInService, PointsService, AttendeeLookupService (whose by-identifier lookup is answered from a roster snapshot because the Identity users endpoint filters by name and email but cannot be queried by identifier, Services/AttendeeLookupService.cs:10-15), and NowNextService behind INowNextService, which mirrors the Conference API's now-next wire shape locally rather than referencing Conference.Shared for one payload (Services/INowNextService.cs:6-9). The conference-day pages taught in Group 23 get their client layer from here too, and it is the same shape. LivePollUIService (MMCA.ADC.Engagement.UI/Services/SessionLive/LivePollUIService.cs:15) and SessionQuestionUIService (Services/SessionLive/SessionQuestionUIService.cs:15) are AuthenticatedServiceBase clients over the Gateway's livepolls and sessionquestions routes (:19 in each), and their contracts ILivePollUIService (Services/SessionLive/ILivePollUIService.cs:15) and ISessionQuestionUIService (Services/SessionLive/ISessionQuestionUIService.cs:15) both open with the rule that keeps a live page calm: every member answers with a Result, so a server refusal is data the page renders rather than an exception, and only the caller's own cancellation still propagates (ILivePollUIService.cs:10-13, ISessionQuestionUIService.cs:10-13). Both also say in the contract that the API enforces the manage and moderation rights regardless of what the UI chooses to render (ILivePollUIService.cs:7-9, ISessionQuestionUIService.cs:7-9), which is the only safe way to write a doc comment about a permission a browser can be told to ignore. LiveEventService behind ILiveEventUIService (Services/SessionLive/LiveEventService.cs:14, Services/SessionLive/ILiveEventUIService.cs:7) resolves the currently-live-or-next published event from the Conference API and computes its window with the same math the backend enforces, then degrades to null on any API failure so the live layer simply stays dormant instead of erroring a page (LiveEventService.cs:7-12, :33-36); the LiveEventContext record it returns (Services/SessionLive/LiveEventContext.cs:13) answers IsLiveAt and ToEventLocal for the pages, and its zone id always resolves because the Conference write path guards it (:20-31). SessionLiveUIService (Services/SessionLive/SessionLiveUIService.cs:10) is the smallest type in the chapter and the clearest one about module boundaries: it implements the ISessionLiveUIService extension point with a single line returning Engagement's own route (:13-14), so a Conference session page lights up its Live button only when this module is present. SessionLookupService behind ISessionLookupService (Services/Lookups/SessionLookupService.cs:13, Services/Lookups/ISessionLookupService.cs:19) is the enrichment client that turns session ids into the titles and times the bookmark surfaces display, projecting the Conference page into a session-keyed dictionary of the small SessionInfo record (ISessionLookupService.cs:9, SessionLookupService.cs:33-43); its contract carries the usage rule as well as the signature, because the catalog call is cheap to misuse: GetAllAsync is for a page that genuinely needs every session (the reminder planner, which schedules against every bookmarked one), and a single-session page must call GetByIdAsync rather than transfer the catalog to label one row (ISessionLookupService.cs:21-28). The comment above the fetch is worth keeping when you copy it: the base sessions endpoint takes no page size and always serves one page capped at the framework's 500-row maximum, which covers a conference catalog (SessionLookupService.cs:24-25). One more UI type is easy to miss and easy to get wrong: CurrentEventNotificationScopeProvider (Services/CurrentEventNotificationScopeProvider.cs:29) scopes ADC's notifications to event:{EventId}, caching a resolved key for five minutes because the bell polls every 30 seconds (:40), and it never answers null: on INotificationScopeProvider a null key means unscoped, which would widen an attendee's inbox to every event, so an instance that has never resolved an event answers with a well-formed key no row carries (:18-24, :34-38), failing to an empty inbox rather than to everyone's.

    +

    Module wiring, feature gating, and the extracted host. EngagementModule (MMCA.ADC.Engagement.API/EngagementModule.cs:14) is the IModule entry point discovered by ModuleLoader and registered in topological order; it declares a dependency on Conference with RequiresDependencies => true (:20, :23), and Register is a one-liner into AddEngagementModule (:26-27), which chains the Application, Infrastructure and API registrations in dependency order (MMCA.ADC.Engagement.API/DependencyInjection.cs:26-28). The Application registration is where the module's own services land: the bookmark aggregate's navigation populator, entity query service and generic delete handler (MMCA.ADC.Engagement.Application/DependencyInjection.cs:38-40; the bookmark domain service is static and needs no registration), the two cross-module services scoped (:43, :47), the awarder scoped and registered by hand because it is a plain service the convention scan does not see (:73-81), and everything conventional (handlers, mappers, validators, event handlers) discovered by ScanModuleApplicationServices<ClassReference>() (:85), which is where the per-layer ClassReference marker earns its keep. Each of the four layers declares its own AssemblyReference / ClassReference pair, and the design-time migrations factory reaches for the Infrastructure one, because the IEntityTypeConfiguration classes live in Infrastructure rather than Application. When the module is disabled in a host, RegisterDisabledStubs swaps in DisabledBookmarkCountService and DisabledUserEngagementExportService (EngagementModule.cs:30-34) so a Conference-only or Identity-only host still resolves those interfaces. Every capability has its own kill switch in EngagementFeatures (MMCA.ADC.Engagement.Shared/EngagementFeatures.cs:10): SessionBookmarks, LivePolls, CheckIn, SponsorVisits, RoomCheckIn, Points and SessionQA (:22, :34, :46, :59, :71, :83, :95), each turning its controller or route into a 404 without a deploy. All seven are declared [FeatureFlag(FeatureFlagLifetime.Permanent, Owner = "ADC Engagement")] (:21 and the same attribute above each of the other six), which is how the flag inventory is told these are shipped capability switches a conference chooses to run with or without, not rollout toggles waiting on a removal date (ADR-031); the two printed-QR switches say so explicitly, SponsorVisits naming the softer alternative of setting Points:SponsorVisit to 0 to keep the visit but stop the award (:48-57) and RoomCheckIn recording why it is separate at all, since the two printed surfaces are retired on their own schedules (:61-70). Authorization above authentication is capability-based, not role-based: EngagementPermissions (MMCA.ADC.Engagement.Shared/Authorization/EngagementPermissions.cs:9) declares three capabilities, engagement:live:manage, engagement:checkin:manage and engagement:points:view-overview (:16, :23, :30), plus an All list so the whole set can be granted in one call (:33-38). The grant map itself is a separate named type, EngagementPermissionGrants (MMCA.ADC.Engagement.Shared/Authorization/EngagementPermissionGrants.cs:24), whose static Apply grants every Engagement capability to the Organizer role and to no other (:33-38); attendee-facing endpoints need no grant because they carry a plain [Authorize] with no capability, and a speaker's session-scoped rights come from the speaker-assignment check inside the handlers (BR-236) rather than from any permission declared here (:26-31). AddModuleEngagementAPI applies that map through services.AddPermissions(EngagementPermissionGrants.Apply) (MMCA.ADC.Engagement.API/DependencyInjection.cs:61, ADR-020). The reason it is a named map in the Shared contracts project rather than a lambda inside the registration is that two hosts apply the same binding: this module's own host, to gate its [HasPermission(...)] endpoints, and the token-minting Identity host, so an issued access token carries a permission claim for every Engagement capability the caller's role holds and not only for the modules the minting host happens to boot (EngagementPermissionGrants.cs:6-22). MMCA.ADC.Engagement.Service boots exactly this one module and is worth reading top to bottom as the reference shape for an extracted host. Kestrel is configured in one line, builder.ConfigureEndpointsWithHealthProbe(HttpProtocols.Http2) (Program.cs:70), the shared helper from KestrelEndpointExtensions: Http2-only on cleartext so cross-service gRPC clients negotiate h2c by prior knowledge, plus the optional HTTP/1.1-only health-probe listener (ADR-012). PointsSettings and CheckInSettings are bound here rather than in the module, because the module registration takes no IConfiguration, and both are deliberately not ValidateOnStart: a missing section binds working defaults and an explicit 0 is the documented kill switch, so no value an organizer sets should stop this host from booting mid-conference (:122-133, a considered divergence from ADR-070). The scheduler and audit trail are wired next (:194, :198, ADR-074, ADR-075), and EngagementErrorResources contributes the module's error-code translations (:217, ADR-027). Composition itself runs through AddMmcaApplicationPipeline, a single ordered chain that registers the modules first and then, because Conference is disabled in this process, re-points Engagement's two Conference dependencies at gRPC clients, swaps the framework's no-op live-channel publisher for the Notification adapter, and finally wires broker messaging (:281-292, ADR-014 closes and seals the decorator pipeline at the end of it). This host is not publisher-only: the points game consumes four integration events, AttendeeCheckedIn, SessionFeedbackSubmitted, EventFeedbackSubmitted and UserDeleted (:288-291), and the one UserDeleted registration runs every handler for that type, so it reaches the points, bookmark and badge erasure handlers here and the session-question and vote erasure handlers of the live layer alike (:263-265). AttendeeCheckedIn is the novel one: it is published by this same service (the CheckIn factory raises it and the outbox captures it in the check-in transaction), so this is ADC's first broker self-consumption, which MassTransit's type topology supports with no special casing (:271-277). SelfHttpWarmupTask (MMCA.ADC.Engagement.Service/SelfHttpWarmupTask.cs:23) extends SelfHttpWarmupTaskBase and holds /health/ready not-ready until the routing, auth and middleware pipeline has been JITted (Program.cs:153-157, ADR-025); the two gRPC endpoints are mapped last (:341, :349).

    +

    The UI surfaces. EngagementUIModule (MMCA.ADC.Engagement.UI/EngagementUIModule.cs:17) implements IUIModule to contribute the module's Blazor assembly (:33), the invisible LiveEventListener layout component (:31), and six nav items declared as resource keys with the organizer ones carrying a RequiredRole so the shared NavMenu hides them from attendees (:21-29, ADR-027); their targets live in EngagementRoutePaths (MMCA.ADC.Engagement.UI/EngagementRoutePaths.cs:8), which also holds the two deep-link-only self-service routes that deliberately contribute no nav item at all (:29-32). The pages divide by actor. Attendees get MyBadge (Pages/CheckIn/MyBadge.razor.cs:15), which renders the opaque payload beside the account name as plain text so a human can confirm the badge belongs to its holder (:10-13), MyPoints, and the two QR landing pages SponsorVisit (Pages/Sponsors/SponsorVisit.razor.cs:21) and RoomCheckIn, which post the write themselves so the whole interaction is one scan with nothing to press; both keep the write off the prerender pass and behind a once-per-instance guard (SponsorVisit.razor.cs:48-55) and branch their on-screen state off the server's stable error code carried back by SelfCheckInOutcome<TResult> (Services/SelfCheckInOutcome.cs:20) and named in CheckInErrorCodes (Services/CheckInErrorCodes.cs:8), because two of the three refusals share the same 404 status. Organizers get CheckInScan (Pages/CheckIn/CheckInScan.razor.cs:18), which loops the shared scanner where a camera exists and falls back to the AttendeeSearchPanel name and email search everywhere else (:12-16, :38), OrganizerAttendance and OrganizerPointsOverview. The two feedback pages, EventFeedback (Pages/Feedback/EventFeedback.razor.cs:19) and SessionFeedback (Pages/Feedback/SessionFeedback.razor.cs:18), render a dynamic question form whose definitions and answers belong to Conference, with per-question values held in a FeedbackAnswerModel (Pages/Feedback/FeedbackAnswerModel.cs:23) whose DataAnnotations are the single declaration of the 4000-character free-text rule (:30-34). Both submit the same way. Before anything is sent, the page flags every required question left unanswered, for every question type, because MudForm already blocks an empty required text field but not an unset rating (EventFeedback.razor.cs:173-188, :202-206, SessionFeedback.razor.cs:202-217, :231-235). It then collects every answered question and posts the whole form as one POST-as-upsert the server applies atomically, so a refusal wrote nothing, there is no partially saved state to report, and the form stays dirty for the same Submit button to retry; a form with nothing answered that passes the required check stays a local no-op rather than a request the batch endpoint would refuse as empty (EventFeedback.razor.cs:213-232, SessionFeedback.razor.cs:242-261, BR-107). The contracts say so on the batch member of IEventFeedbackUIService and its session sibling (Services/Feedback/IFeedbackUIService.cs:32-36, :66-70), and EventFeedbackService mints the idempotency key once, outside its retry pipeline, so a timed-out submit that retries is recognized by the server rather than re-applying the form (Services/Feedback/EventFeedbackService.cs:53-58). Behind the pages sit ordinary authenticated HTTP clients built on AuthenticatedServiceBase: BookmarkService and the cross-module SessionBookmarkUIService (which also feeds SessionReminderCoordinator and the pure SessionReminderPlanner, whose SessionReminder records carry a stable notification id so rescheduling replaces rather than duplicates, Services/HappeningNow/SessionReminderPlanner.cs:15, :39, :41-48, ADR-042), CheckInService, PointsService, AttendeeLookupService (whose by-identifier lookup is answered from a roster snapshot because the Identity users endpoint filters by name and email but cannot be queried by identifier, Services/AttendeeLookupService.cs:10-15), and NowNextService behind INowNextService, which mirrors the Conference API's now-next wire shape locally rather than referencing Conference.Shared for one payload (Services/HappeningNow/INowNextService.cs:6-9). The conference-day pages taught in Group 23 get their client layer from here too, and it is the same shape. LivePollUIService (MMCA.ADC.Engagement.UI/Services/SessionLive/LivePollUIService.cs:15) and SessionQuestionUIService (Services/SessionLive/SessionQuestionUIService.cs:15) are AuthenticatedServiceBase clients over the Gateway's livepolls and sessionquestions routes (:19 in each), and their contracts ILivePollUIService (Services/SessionLive/ILivePollUIService.cs:15) and ISessionQuestionUIService (Services/SessionLive/ISessionQuestionUIService.cs:15) both open with the rule that keeps a live page calm: every member answers with a Result, so a server refusal is data the page renders rather than an exception, and only the caller's own cancellation still propagates (ILivePollUIService.cs:10-13, ISessionQuestionUIService.cs:10-13). Both also say in the contract that the API enforces the manage and moderation rights regardless of what the UI chooses to render (ILivePollUIService.cs:7-9, ISessionQuestionUIService.cs:7-9), which is the only safe way to write a doc comment about a permission a browser can be told to ignore. LiveEventService behind ILiveEventUIService (Services/SessionLive/LiveEventService.cs:14, Services/SessionLive/ILiveEventUIService.cs:7) resolves the currently-live-or-next published event from the Conference API and computes its window with the same math the backend enforces, then degrades to null on any API failure so the live layer simply stays dormant instead of erroring a page (LiveEventService.cs:7-12, :33-36); the LiveEventContext record it returns (Services/SessionLive/LiveEventContext.cs:13) answers IsLiveAt and ToEventLocal for the pages, and its zone id always resolves because the Conference write path guards it (:20-31). SessionLiveUIService (Services/SessionLive/SessionLiveUIService.cs:10) is the smallest type in the chapter and the clearest one about module boundaries: it implements the ISessionLiveUIService extension point with a single line returning Engagement's own route (:13-14), so a Conference session page lights up its Live button only when this module is present. SessionLookupService behind ISessionLookupService (Services/Lookups/SessionLookupService.cs:13, Services/Lookups/ISessionLookupService.cs:19) is the enrichment client that turns session ids into the titles and times the bookmark surfaces display, projecting the Conference page into a session-keyed dictionary of the small SessionInfo record (ISessionLookupService.cs:9, SessionLookupService.cs:33-43); its contract carries the usage rule as well as the signature, because the catalog call is cheap to misuse: GetAllAsync is for a page that genuinely needs every session (the reminder planner, which schedules against every bookmarked one), and a single-session page must call GetByIdAsync rather than transfer the catalog to label one row (ISessionLookupService.cs:21-28). The comment above the fetch is worth keeping when you copy it: the base sessions endpoint takes no page size and always serves one page capped at the framework's 500-row maximum, which covers a conference catalog (SessionLookupService.cs:24-25). One more UI type is easy to miss and easy to get wrong: CurrentEventNotificationScopeProvider (Services/CurrentEventNotificationScopeProvider.cs:29) scopes ADC's notifications to event:{EventId}, caching a resolved key for five minutes because the bell polls every 30 seconds (:40), and it never answers null: on INotificationScopeProvider a null key means unscoped, which would widen an attendee's inbox to every event, so an instance that has never resolved an event answers with a well-formed key no row carries (:18-24, :34-38), failing to an empty inbox rather than to everyone's.

    Rubric lens. This module is a compact end-to-end illustration of most of Part A. Five small aggregates whose rules live in factories and invariants are the [Rubric §4, Domain-Driven Design] story; the controller-handler-domain-service split and the one-slice-per-folder layout are [Rubric §5, Vertical Slice] and [Rubric §6, CQRS and Event-Driven], with the points adapters showing both dispatch styles (broker integration events and an in-process domain event) side by side and stating the trade-off in code. The gRPC adapters, disabled stubs and Replace-based composition are [Rubric §7, Microservices Readiness] and [Rubric §9, API and Contract Design]; the per-service database, the filtered unique indexes carrying idempotency, anti-farming and one-vote-per-poll, and the pushed-down grouped COUNT and SUM in the attendance and leaderboard reads are [Rubric §8, Data Architecture] and [Rubric §12, Performance and Scalability]. Security ([Rubric §11]) is the recurring theme rather than a bolt-on: identity taken from the token instead of the body on every self-service path, the 404-not-403 delete, indistinguishable answers for a bad payload and an unknown credential, server-resolved room sessions, a bearer credential minted from a non-monotonic Guid, and one documented unauthenticated-by-necessity gRPC endpoint. The drop-oldest publish queue with its dropped-count meter is [Rubric §29, Resilience] and [Rubric §13, Observability and Operability], as is the live client layer's Result-only surface and its dormant-rather-than-broken answer when the event lookup fails; per-capability feature flags plus a retunable points economy with per-rule kill switches are [Rubric §12, Performance & Scalability] and [Rubric §17, DevOps]; the export projection and the leaderboard name erasure are [Rubric §30, Compliance and Privacy]. The pages extend the story into Part B ([Rubric §18, UI Architecture], [Rubric §24, Forms/Validation/UX Safety] for the single-declaration answer model, and [Rubric §25, Navigation and IA] for the deep-link-only QR routes). Relevant ADRs, roughly in the order they surface above: ADR-075 (audit trail), ADR-033 (owner-or-admin filter), ADR-017 (request idempotency), ADR-001 (Mapperly mapping), ADR-072 and ADR-071 (badge check-in, points, and the scanner capability), ADR-006 and ADR-030 (database-per-service and sole migrator), ADR-003 (outbox), ADR-007 and ADR-008 (gRPC extraction and topology), ADR-076 and ADR-005 (data-subject export, soft-delete versus erasure), ADR-039 (best-effort live publish), ADR-020 (permission registry), ADR-014 (the sealed decorator pipeline the host closes), ADR-012 (Kestrel endpoint profile), ADR-070 (fail-fast configuration, and this module's documented exception to it), ADR-074 (scheduler), ADR-025 (warm-up and readiness), ADR-027 (resource-key navigation and error translations), and ADR-042 (device capabilities and reminder dispatch).

    AssemblyReference

    @@ -398,7 +398,7 @@

    GetAttendanceStatsHandler

    • What it is: the query handler for GetAttendanceStatsQuery. It answers the organizer dashboard with one event-scoped arrival count plus one row per session that has check-ins, as an AttendanceStatsDTO.
    • Depends on: IUnitOfWork through its primary constructor (GetAttendanceStatsHandler.cs:16). It reads the CheckIn aggregate through an IReadRepository<TEntity, TIdentifierType>, filters on CheckInScope, and builds SessionAttendanceDTO rows inside an AttendanceStatsDTO. It implements IQueryHandler<in TQuery, TResult> (GetAttendanceStatsHandler.cs:17).
    • -
    • Concept introduced, the persistence-neutral grouped count. [Rubric §12, Performance & Scalability] assesses whether a read pulls only what it needs. The class doc (GetAttendanceStatsHandler.cs:10-15) is explicit: both figures are read through the focused repository surface and both are computed in SQL, a COUNT for the event scope and a grouped COUNT per session, so what crosses the wire is one row per session rather than one per check-in. The member that makes this possible is CountByAsync, declared on the shared querier contract (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:167-172); its remarks (IRepository.cs:156-161) name the problem it removes, that the Application layer references no EF Core and therefore has no IQueryable to group, which would otherwise force a handler to project every matching row out of the database and fold it in memory. [Rubric §3, Clean Architecture] is the reason the fix took that shape: the aggregate is expressed as a persistence-neutral member rather than by letting the handler see the provider. [Rubric §8, Data Architecture] shows in how the two scopes are distinguished: a single CheckIn table holds both event arrivals and session attendance, separated by the Scope discriminator rather than by two tables, and CheckInConfiguration indexes EventId and SessionId for exactly this read (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/CheckIns/CheckInConfiguration.cs:64-69). [Rubric §6, CQRS & Event-Driven]: this is a pure read, resolved through GetReadRepository (line 25) rather than the writable repository, so nothing enters the change tracker.
    • +
    • Concept introduced, the persistence-neutral grouped count. [Rubric §12, Performance & Scalability] assesses whether a read pulls only what it needs. The class doc (GetAttendanceStatsHandler.cs:10-15) is explicit: both figures are read through the focused repository surface and both are computed in SQL, a COUNT for the event scope and a grouped COUNT per session, so what crosses the wire is one row per session rather than one per check-in. The member that makes this possible is CountByAsync, declared on the shared querier contract (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:169-174); its remarks (IRepository.cs:158-163) name the problem it removes, that the Application layer references no EF Core and therefore has no IQueryable to group, which would otherwise force a handler to project every matching row out of the database and fold it in memory. [Rubric §3, Clean Architecture] is the reason the fix took that shape: the aggregate is expressed as a persistence-neutral member rather than by letting the handler see the provider. [Rubric §8, Data Architecture] shows in how the two scopes are distinguished: a single CheckIn table holds both event arrivals and session attendance, separated by the Scope discriminator rather than by two tables, and CheckInConfiguration indexes EventId and SessionId for exactly this read (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/CheckIns/CheckInConfiguration.cs:64-69). [Rubric §6, CQRS & Event-Driven]: this is a pure read, resolved through GetReadRepository (line 25) rather than the writable repository, so nothing enters the change tracker.
    • Walkthrough
      • HandleAsync (GetAttendanceStatsHandler.cs:20-22) null-guards the query with ArgumentNullException.ThrowIfNull(query) (line 23).
      • It resolves the read repository for CheckIn (line 25).
      • @@ -651,7 +651,7 @@

        GetLeaderboardHandler

      • What it is: the query handler that builds the public leaderboard: the opted-in attendees with the highest points totals, ranked, truncated to the configured board length.
      • Depends on: IUnitOfWork and IOptions<PointsSettings> (primary constructor, GetLeaderboardHandler.cs:28-30), the LeaderboardOptIn and PointsEntry aggregates, its private projection OptInRow, and the published LeaderboardEntryDTO. Implements IQueryHandler<in TQuery, TResult>, returns Result. Externals: Microsoft.Extensions.Options (:1), StringComparer.Ordinal (BCL).
      • Concept, publishing a name only if its owner published it. The class doc (GetLeaderboardHandler.cs:13-16) is the design statement worth reading twice: only attendees who opted in appear, the name shown is "the snapshot they published at opt-in", and the handler "makes no call into Identity and reads no user record", so an attendee who never opted in is absent rather than present under a name they did not choose. [Rubric §30, Compliance/Privacy/Data Governance] assesses consent as a structural property rather than a checkbox: the board is built from the opt-in table outward, so non-participation is the default and cannot be undone by a bug in a filter. [Rubric §11, Security] assesses minimizing what a public surface can reveal: no Identity call means no user record is even in reach of this code path. A second design note (:19-23) covers ranking: ties keep distinct sequential ranks (1, 2, 3) rather than competition ranking (1, 1, 3), because the board is a fixed-length list of positions, and the tie-break is the published name compared ordinally so the order is "deterministic and repeatable across reads rather than dependent on whatever order the database returned". [Rubric §15, Best Practices & Code Quality] shows in that ordinal comparison: a culture-sensitive comparison would make the rendered order depend on the server's locale.
      • -
      • Concept, pushing the aggregate into the database with SumByAsync. The points side of this read is not folded in memory. SumByAsync (declared on the querier contract at MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:184, which IReadRepository<TEntity, TIdentifierType> composes at IRepository.cs:330-331) is the persistence-neutral way to ask for a GROUP BY with SUM, so only one aggregate row per attendee crosses the wire instead of every ledger row. Its EF implementation groups with an element selector (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:170-177) so the caller's expression tree reaches the provider intact. [Rubric §3, Clean Architecture] is what makes this member necessary at all: the Application layer references no EF Core, so a handler that wants a grouped aggregate has no IQueryable of its own to group (IRepository.cs:156-161). [Rubric §12, Performance & Scalability]: the contract returns decimal, so an int points column widens on the way in and narrows on the way out (GetLeaderboardHandler.cs:52-53, :61), which the code comment notes no ledger total can overflow.
      • +
      • Concept, pushing the aggregate into the database with SumByAsync. The points side of this read is not folded in memory. SumByAsync (declared on the querier contract at MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:186, which IReadRepository<TEntity, TIdentifierType> composes at IRepository.cs:332-333) is the persistence-neutral way to ask for a GROUP BY with SUM, so only one aggregate row per attendee crosses the wire instead of every ledger row. Its EF implementation groups with an element selector (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFReadRepository.cs:171-178) so the caller's expression tree reaches the provider intact. [Rubric §3, Clean Architecture] is what makes this member necessary at all: the Application layer references no EF Core, so a handler that wants a grouped aggregate has no IQueryable of its own to group (IRepository.cs:158-163). [Rubric §12, Performance & Scalability]: the contract returns decimal, so an int points column widens on the way in and narrows on the way out (GetLeaderboardHandler.cs:52-53, :61), which the code comment notes no ledger total can overflow.
      • Walkthrough
        • HandleAsync (GetLeaderboardHandler.cs:33-35) reads the opt-ins first: a read repository for LeaderboardOptIn (line 39), then GetProjectedAsync into OptInRow with asTracking: false (:40-43). The comment above it (:37-38) records why there is no "is active" predicate: the default query filter excludes soft-deleted rows, so leaving the board removes the attendee from this read for free.
        • An empty opt-in list short-circuits to an empty successful board (GetLeaderboardHandler.cs:45-46), so a conference where nobody opted in costs one query rather than two.
        • @@ -806,8 +806,8 @@

          CheckInSettings

        • Depends on: no first-party types. Externals: the options pattern (Microsoft.Extensions.Options, bound at the host).
        • Concept, the options class as a policy boundary. [Rubric §17, DevOps & Deployment] assesses whether configuration is typed and injected rather than read ad hoc, and [Rubric §7, Microservices Readiness] whether a module owns its own policy. Both are visible in one decision here: the grace window lives in Engagement, not Conference. Conference answers "which session is this room hosting"; this module decides "how early does that count" and passes its own number into the cross-module call (CheckInSettings.cs:6-9, and RecordRoomCheckInHandler.cs:48-52). Had the number lived in Conference, retuning check-in behavior would mean redeploying a service that has no stake in it.
        • Walkthrough (CheckInSettings.cs:11-22): SectionName (line 14) is the const "CheckIns", kept next to the type so the binding call cannot mistype it. RoomCheckInGraceMinutes (line 21) is an init-only int defaulting to 15, with the rationale in its own doc comment: attendees scan while the previous talk is clearing out, so a zero-grace window would reject the whole pre-session queue.
        • -
        • Why it's built this way: the binding is deliberately not ValidateOnStart. The Engagement service host states the reason inline: a missing "CheckIns" section binds the grace window to a working default, which is not a misconfiguration worth refusing to boot over mid-conference (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:150-155). The default living on the property, rather than in an appsettings file, is what makes that stance safe. [Rubric §29, Resilience & Business Continuity]: the failure mode of bad configuration here is a slightly wrong window, not a dead host.
        • -
        • Where it's used: registered with services.AddOptions<CheckInSettings>().Bind(...) (Program.cs:154-155) and injected as IOptions<CheckInSettings> into RecordRoomCheckInHandler (.cs:28), which passes settings.Value.RoomCheckInGraceMinutes to IEventLiveValidationService.GetCurrentRoomSessionInfoAsync (.cs:51-53). No other handler reads it.
        • +
        • Why it's built this way: the binding is deliberately not ValidateOnStart. The Engagement service host states the reason inline: a missing "CheckIns" section binds the grace window to a working default, which is not a misconfiguration worth refusing to boot over mid-conference (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:149-154). The default living on the property, rather than in an appsettings file, is what makes that stance safe. [Rubric §29, Resilience & Business Continuity]: the failure mode of bad configuration here is a slightly wrong window, not a dead host.
        • +
        • Where it's used: registered with services.AddOptions<CheckInSettings>().Bind(...) (Program.cs:153-154) and injected as IOptions<CheckInSettings> into RecordRoomCheckInHandler (.cs:28), which passes settings.Value.RoomCheckInGraceMinutes to IEventLiveValidationService.GetCurrentRoomSessionInfoAsync (.cs:51-53). No other handler reads it.
        • Caveats / not-in-source: covered by MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.Shared.Tests/CheckIns/CheckInSettingsTests.cs. Whether a deployed environment overrides the 15-minute default is a configuration fact, not a source fact: not determinable from this file.

        @@ -885,8 +885,8 @@

        EngagementPermissionGrants

        • What it is: the module's role-to-permission grant map: the one place that says which role holds EngagementPermissions.All.
        • Depends on: EngagementPermissions (the capability list it grants), and PermissionRegistryBuilder / RoleNames (group-08-auth.md#permissionregistrybuilder, group-24-identity-module.md#rolenames), the framework registry-builder and the Identity module's role vocabulary.
        • -
        • Concept, one grant map shared by both the owning service and the token minter. [Rubric §11, Security] assesses whether a role-to-capability binding is stated once and consumed everywhere it must hold, rather than restated and risking drift. Apply is a plain static void(PermissionRegistryBuilder) (:34) rather than an extension method or an instance method, which is what lets it be passed as a delegate: DependencyInjection's Engagement API layer registers it with services.AddPermissions(EngagementPermissionGrants.Apply) (MMCA.ADC.Engagement.API/DependencyInjection.cs:61), and the Identity service's TokenPermissionGrants.ApplyAll calls it directly alongside the other three modules' maps (MMCA.ADC.Identity.Service/Authorization/TokenPermissionGrants.cs:57). Both call sites run the identical method, so the capability set a request is authorized against at the owning service and the capability set a token is minted with cannot diverge.
        • -
        • Walkthrough (EngagementPermissionGrants.cs:24-40): one public method, Apply(PermissionRegistryBuilder permissions) (:34), which null-checks its argument (:36) and makes one call: permissions.Grant(RoleNames.Organizer, [.. EngagementPermissions.All]) (:38), spreading the three-item capability list onto the Organizer role. The doc comment (:27-31) records the two paths this class deliberately leaves alone: attendee-facing endpoints are authenticated-only [Authorize] with no capability, and a speaker's session-scoped rights flow through the speaker-assignment check in the handlers (BR-236), not through a grant here.
        • +
        • Concept, one grant map shared by both the owning service and the token minter. [Rubric §11, Security] assesses whether a role-to-capability binding is stated once and consumed everywhere it must hold, rather than restated and risking drift. Apply is a plain static void(PermissionRegistryBuilder) (:33) rather than an extension method or an instance method, which is what lets it be passed as a delegate: DependencyInjection's Engagement API layer registers it with services.AddPermissions(EngagementPermissionGrants.Apply) (MMCA.ADC.Engagement.API/DependencyInjection.cs:61), and the Identity service's TokenPermissionGrants.ApplyAll calls it directly alongside the other three modules' maps (MMCA.ADC.Identity.Service/Authorization/TokenPermissionGrants.cs:57). Both call sites run the identical method, so the capability set a request is authorized against at the owning service and the capability set a token is minted with cannot diverge.
        • +
        • Walkthrough (EngagementPermissionGrants.cs:24-39): one public method, Apply(PermissionRegistryBuilder permissions) (:33), which null-checks its argument (:35) and makes one call: permissions.Grant(RoleNames.Organizer, [.. EngagementPermissions.All]) (:37), spreading the three-item capability list onto the Organizer role. The doc comment (:26-31) records the two paths this class deliberately leaves alone: attendee-facing endpoints are authenticated-only [Authorize] with no capability, and a speaker's session-scoped rights flow through the speaker-assignment check in the handlers (BR-236), not through a grant here.
        • Why it's built this way: a token is minted once, at login, by the Identity host, so the Identity host's registry has to hold the union of every module's grants even though it boots only the Identity module; see TokenPermissionGrants's remarks (TokenPermissionGrants.cs:14-25) for why. Splitting the map out of AddModuleEngagementAPI and into its own class (rather than inlining the Grant call there, as the still-unmoved EngagementPermissions section above shows was the earlier shape) is what makes that reuse possible: a method the Engagement API layer calls for itself is also a method the Identity service can call for the same binding, with no second Grant line anywhere to fall out of sync.
        • Where it's used: called from AddModuleEngagementAPI (MMCA.ADC.Engagement.API/DependencyInjection.cs:58-61, the module's own composition) and from TokenPermissionGrants.ApplyAll (MMCA.ADC.Identity.Service/Authorization/TokenPermissionGrants.cs:53-59, the token-minting host's composition). EngagementPermissionGrantsTests builds the real registry through AddModuleEngagementAPI() (:24-28) and asserts Organizer holds every capability (:31-41), that "Admin" holds none because it is not an ADC role at all (:44-58, ADC's RoleNames declares only Organizer, ContentEditor and Attendee), and that Attendee and ContentEditor each hold none (:68-89).
        • Caveats / not-in-source: Apply grants exactly one role, RoleNames.Organizer; ADC has no Admin role to grant a second time, which is why this class's single Grant call differs from the two-role shape the EngagementPermissions section above describes, that description predates this class and now reads as stale.
        • @@ -1019,7 +1019,7 @@

          UserEngagementBookmarkExportDTO

        • Depends on: the SessionIdentifierType alias (solution-wide global using, see primer §2). It is a member of UserEngagementExportDTO.
        • Concept, ids and dates only. [Rubric §30, Compliance, Privacy & Data Governance] assesses whether a data-subject access path returns the subject's own data and nothing else. This record is the smallest expression of that rule: a bookmark is a personal-schedule entry, so the export carries the session id and the creation date, never the session's title, speaker, or any content owned by somebody else (UserEngagementBookmarkExportDTO.cs:4-5). The concept is taught once on UserEngagementExportDTO; the other export rows follow the same rule.
        • Walkthrough (UserEngagementBookmarkExportDTO.cs:7-14): required SessionIdentifierType SessionId (line 10) and required DateTime CreatedOn (line 13). Both are required, so a projection that forgets a column is a compile error rather than a silently empty export field.
        • -
        • Where it's used: projected server-side from UserSessionBookmark by UserEngagementExportService (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/Exports/UserEngagementExportService.cs:25-28); serialized by UserEngagementExportGrpcService (UserEngagementExportGrpcService.cs:46-50) and rehydrated from the wire by UserEngagementExportServiceGrpcAdapter (UserEngagementExportServiceGrpcAdapter.cs:41-45).
        • +
        • Where it's used: projected server-side from UserSessionBookmark by UserEngagementExportService (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/Exports/UserEngagementExportService.cs:25-28); serialized by UserEngagementExportGrpcService (UserEngagementExportGrpcService.cs:47-51) and rehydrated from the wire by UserEngagementExportServiceGrpcAdapter (UserEngagementExportServiceGrpcAdapter.cs:42-46).

        UserEngagementSubmittedQuestionExportDTO

        @@ -1044,7 +1044,7 @@

        UserEngagementCheckInExportDTO

      • Concept, attendance is not a projection of the ledger. [Rubric §30, Compliance, Privacy & Data Governance] assesses completeness of a data-subject access response. It would be tempting to derive attendance from the points entries, and it would be wrong: PointsAwarder writes nothing when a rule is configured to 0 (PointsAwarder.cs:43-51) and treats a repeat scan as a clean no-op (PointsAwarder.cs:55-64), so a check-in that earned no points still leaves a distinct attendance record the subject is entitled to see. The export therefore reads CheckIn directly, and the reason is written both on this record (UserEngagementCheckInExportDTO.cs:6-9) and at the call site (UserEngagementExportService.cs:48-50).
      • Walkthrough (UserEngagementCheckInExportDTO.cs:11-27): required CheckInScope Scope (line 14), the discriminator; required EventIdentifierType EventId (line 17), always set for every scope; SessionIdentifierType? SessionId (line 20) and SponsorIdentifierType? SponsorId (line 23), each nullable and set only for the matching scope; required DateTimeOffset CheckedInOn (line 26). Note this is the one export row that uses DateTimeOffset rather than DateTime, matching the aggregate's own timestamp (UserEngagementExportService.cs:59).
      • Why it's built this way: one flattened record with nullable scope-specific ids beats three parallel collections, because the export document then reads as a single chronological attendance history regardless of scope, and the reader disambiguates on Scope.
      • -
      • Where it's used: projected from CheckIn by UserEngagementExportService (UserEngagementExportService.cs:51-62); carried on UserEngagementExportDTO.CheckIns; mapped across the wire by UserEngagementExportGrpcService (UserEngagementExportGrpcService.cs:66-81) and back by UserEngagementExportServiceGrpcAdapter (UserEngagementExportServiceGrpcAdapter.cs:61-72). The nullable ids are what the two proto3 mapping comments are about: an absent target travels as 0 and is mapped back to null on arrival (UserEngagementExportGrpcService.cs:73-76, UserEngagementExportServiceGrpcAdapter.cs:68-70).
      • +
      • Where it's used: projected from CheckIn by UserEngagementExportService (UserEngagementExportService.cs:51-62); carried on UserEngagementExportDTO.CheckIns; mapped across the wire by UserEngagementExportGrpcService (UserEngagementExportGrpcService.cs:67-82) and back by UserEngagementExportServiceGrpcAdapter (UserEngagementExportServiceGrpcAdapter.cs:62-73). The nullable ids are what the two proto3 mapping comments are about: an absent target travels as 0 and is mapped back to null on arrival (UserEngagementExportGrpcService.cs:74-77, UserEngagementExportServiceGrpcAdapter.cs:69-71).

      UserEngagementPointsEntryExportDTO

      @@ -1056,7 +1056,7 @@

      UserEngagementPointsEntryExportDTODepends on: PointsActivityType (imported from MMCA.ADC.Engagement.Shared.Points, UserEngagementPointsEntryExportDTO.cs:1) and PointsSubjectKeys for the meaning of SubjectKey.

    • Concept, cross-reference: the export-row rule is the one taught on UserEngagementBookmarkExportDTO. Worth contrasting with its API twin: this record deliberately drops the entry Id that PointsEntryDTO carries (an export is portable data, not a handle the subject can call back with) and reports the audit CreatedOn rather than OccurredOnUtc. [Rubric §30, Compliance, Privacy & Data Governance].
    • Walkthrough (UserEngagementPointsEntryExportDTO.cs:9-22): required PointsActivityType ActivityType (line 12), required int Points (line 15), SubjectKey defaulted to string.Empty (line 18), required DateTime CreatedOn (line 21). SubjectKey is the one non-required member, so a row projected without it still reads as an empty string rather than a null.
    • -
    • Where it's used: projected from PointsEntry by UserEngagementExportService (UserEngagementExportService.cs:36-46); carried on UserEngagementExportDTO.PointsEntries; mapped over the wire by UserEngagementExportGrpcService (UserEngagementExportGrpcService.cs:57-65) and back by UserEngagementExportServiceGrpcAdapter (UserEngagementExportServiceGrpcAdapter.cs:52-60).
    • +
    • Where it's used: projected from PointsEntry by UserEngagementExportService (UserEngagementExportService.cs:36-46); carried on UserEngagementExportDTO.PointsEntries; mapped over the wire by UserEngagementExportGrpcService (UserEngagementExportGrpcService.cs:58-66) and back by UserEngagementExportServiceGrpcAdapter (UserEngagementExportServiceGrpcAdapter.cs:53-61).

    UserEngagementExportDTO

    @@ -1069,8 +1069,8 @@

    UserEngagementExportDTO

  • Concept introduced, the per-module export document. [Rubric §30, Compliance, Privacy & Data Governance] assesses whether a real data-subject access and portability path exists rather than a policy promise. Identity owns the aggregated user export document but owns none of Engagement's data, so each module publishes its own export shape and its own read contract (IUserEngagementExportService); Identity's EngagementUserDataExportSection stitches this document into the whole (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ExportUserData/EngagementUserDataExportSection.cs:30-45). [Rubric §7, Microservices Readiness]: because the document lives in *.Shared and the contract is an interface, the aggregation works identically whether Engagement is in-process or a separate service.
  • Concept, "active footprint" is the export's definition of current. Every collection is read through the EF global soft-delete filter, so soft-deleted rows are excluded (UserEngagementExportService.cs:14-15). The visible consequence is stated on the service: an opt-in the user has since left reports as not on the leaderboard rather than as a historical membership. That is the erasure model of ADR-005 showing through on the access path.
  • Walkthrough (UserEngagementExportDTO.cs:9-31): Bookmarks (line 12), SubmittedQuestions (line 15), PointsEntries (line 18), all IReadOnlyList<...> defaulted to []; CheckIns (line 24), carried separately from the ledger for the reason given on UserEngagementCheckInExportDTO (UserEngagementExportDTO.cs:20-23); IsOnLeaderboard (line 27) and LeaderboardDisplayName (line 30), the latter null when the user never opted in. Every collection defaulting to [] is what makes an empty document (the disabled-module stub, or a user with no Engagement footprint) safe to enumerate.
  • -
  • Why it's built this way: ids plus dates keep the document cheap to build and cheap to ship across a service boundary, and the "no content authored by other users" rule (UserEngagementExportDTO.cs:4-8) means the export can be handed to the subject without a redaction pass. The nullable LeaderboardDisplayName is a documented contract the gRPC adapter honors explicitly: proto3 scalars carry no null, so the server emits the empty string and the adapter maps it back to null (UserEngagementExportServiceGrpcAdapter.cs:75-80).
  • -
  • Where it's used: built by UserEngagementExportService (UserEngagementExportService.cs:73-81); returned by IUserEngagementExportService; serialized by UserEngagementExportGrpcService and rebuilt by UserEngagementExportServiceGrpcAdapter (UserEngagementExportServiceGrpcAdapter.cs:39-81); consumed by Identity's ExportUserDataHandler through its Engagement section.
  • +
  • Why it's built this way: ids plus dates keep the document cheap to build and cheap to ship across a service boundary, and the "no content authored by other users" rule (UserEngagementExportDTO.cs:4-8) means the export can be handed to the subject without a redaction pass. The nullable LeaderboardDisplayName is a documented contract the gRPC adapter honors explicitly: proto3 scalars carry no null, so the server emits the empty string and the adapter maps it back to null (UserEngagementExportServiceGrpcAdapter.cs:76-81).
  • +
  • Where it's used: built by UserEngagementExportService (UserEngagementExportService.cs:73-81); returned by IUserEngagementExportService; serialized by UserEngagementExportGrpcService and rebuilt by UserEngagementExportServiceGrpcAdapter (UserEngagementExportServiceGrpcAdapter.cs:40-82); consumed by Identity's ExportUserDataHandler through its Engagement section.

  • IUserEngagementExportService

    @@ -1125,7 +1125,7 @@

    PointsActivityType

  • Concept, reserving 0 instead of defining it. Numbering starts at 1 on purpose so that a defaulted column or a payload that never set the field cannot read as a real earn rule (PointsActivityType.cs:11-15). That is a deliberate violation of the analyzer rule "enums should have zero value", so the file carries a targeted SuppressMessage for CA1008 whose Justification states the reason inline (PointsActivityType.cs:17). [Rubric §15, Best Practices & Code Quality] assesses whether analyzer deviations are justified in place rather than blanket-disabled; this is the pattern to copy. The invariant that rejects the unset value is PointsEntryInvariants.EnsureActivityTypeIsDefined, which calls Enum.IsDefined (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Domain/Points/PointsEntryInvariants.cs:52-55).
  • Walkthrough (PointsActivityType.cs:20-36): EventCheckIn = 1 (line 21), the optional info-desk activation scan; SessionCheckIn = 2 (line 24), the primary conference-day earn mechanic; SessionFeedback = 3 (line 27); EventFeedback = 4 (line 30); QuestionAsked = 5 (line 33), awarded once per session rather than once per question; SponsorVisit = 6 (line 36), awarded once per sponsor from the sponsor's printed QR.
  • Why it's built this way: the doc remarks record a domain fact the numbering alone would not convey (PointsActivityType.cs:6-10): the conference runs door and arrival check-in through TicketLeap, so EventCheckIn is an optional activation scan and not a door process, which is why SessionCheckIn is the mechanic the economy is tuned around (see the values in PointsSettings).
  • -
  • Where it's used: stored on PointsEntry; switched over by PointsSettings.GetPointsFor; passed to IPointsAwarder.AwardAsync by every points handler (AttendeeCheckedInPointsHandler, SessionQuestionSubmittedPointsHandler, SessionFeedbackSubmittedPointsHandler, EventFeedbackSubmittedPointsHandler); carried on PointsEntryDTO, PointsActivityTotalDTO, and UserEngagementPointsEntryExportDTO. It also crosses the gRPC boundary as a bare int in both directions, and both sides carry a comment saying the numeric stability is what makes the cast lossless (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Grpc/UserEngagementExportGrpcService.cs:59-61, MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Contracts/UserEngagementExportServiceGrpcAdapter.cs:54-56).
  • +
  • Where it's used: stored on PointsEntry; switched over by PointsSettings.GetPointsFor; passed to IPointsAwarder.AwardAsync by every points handler (AttendeeCheckedInPointsHandler, SessionQuestionSubmittedPointsHandler, SessionFeedbackSubmittedPointsHandler, EventFeedbackSubmittedPointsHandler); carried on PointsEntryDTO, PointsActivityTotalDTO, and UserEngagementPointsEntryExportDTO. It also crosses the gRPC boundary as a bare int in both directions, and both sides carry a comment saying the numeric stability is what makes the cast lossless (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Grpc/UserEngagementExportGrpcService.cs:60-62, MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Contracts/UserEngagementExportServiceGrpcAdapter.cs:55-57).

  • SetLeaderboardParticipationRequest

    @@ -1161,7 +1161,7 @@

    IBookmarkCountService

    • What it is: the cross-module read contract that lets Conference ask "how many people bookmarked this session?" without referencing an Engagement domain entity, an Engagement DbContext, or an Engagement package beyond .Shared.
    • Depends on: nothing first-party beyond the SessionIdentifierType alias and the ServiceContractAttribute marker it carries (IBookmarkCountService.cs:1, :10); BCL Task, IReadOnlyDictionary, IReadOnlyCollection, CancellationToken.
    • -
    • Concept introduced, the extraction-ready cross-module interface. [Rubric §7, Microservices Readiness] assesses whether modules talk through contracts that survive a process split; [Rubric §1, SOLID] (Dependency Inversion) assesses whether the consumer depends on an abstraction rather than a concrete collaborator. This interface is the canonical example in ADC: it has three different implementations that a host picks between with no consumer change at all. In-process it is BookmarkCountService (registered with TryAddScoped at MMCA.ADC.Engagement.Application/DependencyInjection.cs:43); when Engagement is switched off in a host it is DisabledBookmarkCountService (MMCA.ADC.Engagement.API/EngagementModule.cs:32); and when Engagement runs as its own process it is BookmarkCountServiceGrpcAdapter, swapped in with Replace by AddEngagementBookmarkCountClient() (MMCA.ADC.Engagement.Contracts/DependencyInjection.cs:49, called from MMCA.ADC.Conference.Service/Program.cs:409). That is ADR-007 and ADR-008 made concrete: the C# call site never learns which one it got. The [ServiceContract] marker is what declares this intent in code rather than in a comment.
    • +
    • Concept introduced, the extraction-ready cross-module interface. [Rubric §7, Microservices Readiness] assesses whether modules talk through contracts that survive a process split; [Rubric §1, SOLID] (Dependency Inversion) assesses whether the consumer depends on an abstraction rather than a concrete collaborator. This interface is the canonical example in ADC: it has three different implementations that a host picks between with no consumer change at all. In-process it is BookmarkCountService (registered with TryAddScoped at MMCA.ADC.Engagement.Application/DependencyInjection.cs:43); when Engagement is switched off in a host it is DisabledBookmarkCountService (MMCA.ADC.Engagement.API/EngagementModule.cs:32); and when Engagement runs as its own process it is BookmarkCountServiceGrpcAdapter, swapped in with Replace by AddEngagementBookmarkCountClient() (MMCA.ADC.Engagement.Contracts/DependencyInjection.cs:49, called from MMCA.ADC.Conference.Service/Program.cs:411). That is ADR-007 and ADR-008 made concrete: the C# call site never learns which one it got. The [ServiceContract] marker is what declares this intent in code rather than in a comment.
    • Walkthrough: two methods, both taking an explicit (non-defaulted) CancellationToken so every implementation, including the gRPC one, threads cancellation. GetBookmarkCountForSessionAsync(sessionId, cancellationToken) (IBookmarkCountService.cs:19) returns a bare int for one session. GetBookmarkCountsForSessionsAsync(sessionIds, cancellationToken) (IBookmarkCountService.cs:28-30) is the batched form, and its contract is spelled out in the doc comment (IBookmarkCountService.cs:22-23): every requested session id is present in the result, and a session with no bookmarks maps to 0. That sentence is a real contract, not decoration: it is what forces the disabled stub to project zeros rather than return an empty map. [Rubric §12, Performance & Scalability]: the batch method exists so the speaker dashboard resolves N sessions in one round trip instead of N, which matters most when the call is a network hop rather than a local query.
    • Why it's built this way: returning plain int / IReadOnlyDictionary rather than Result<T> keeps the contract trivially projectable onto a proto message; a fault surfaces as an exception (over gRPC, a status the caller's resilience pipeline can react to) instead of a failure payload each caller must unwrap. Living in .Shared is what keeps the reference direction legal: Conference references Engagement's contract assembly only, which the Conference service project spells out in a comment on its own ProjectReference (MMCA.ADC.Conference.Service/MMCA.ADC.Conference.Service.csproj:26).
    • Where it's used: Conference's GetSessionBookmarkCountHandler and the batched GetSessionBookmarkCountsHandler; on the producing side it is what BookmarkCountsGrpcService publishes over the wire.
    • @@ -1272,7 +1272,7 @@

      ISessionBookmarkUIService

      • What it is: the narrow UI-side contract that lets the Conference session pages render a bookmark star and toggle it inline, without navigating to an Engagement page and without referencing the Engagement UI assembly.
      • Depends on: UserSessionBookmarkDTO, Result and ErrorType from MMCA.Common.Shared.Abstractions (ISessionBookmarkUIService.cs:1), and the identifier aliases.
      • -
      • Concept introduced, the cross-module UI service contract. [Rubric §18, UI Architecture] assesses how one module's page composes another module's capability. The pattern here is optional resolution: Conference's pages hold the dependency as a nullable property and ask the container for it rather than injecting it, BookmarkService = ServiceProvider.GetService<ISessionBookmarkUIService>() (MMCA.ADC.Conference.UI/Pages/Public/Sessions/PublicSessionList.razor.cs:89, and, for the detail page, the same line inside the extracted SessionBookmarkButton toggle it renders, SessionBookmarkButton.razor.cs:51), so a host that never called AddEngagementUI() simply renders no stars instead of failing to construct the page. PublicSessionListView then takes it as a nullable [Parameter] (PublicSessionListView.razor.cs:60). [Rubric §7, Microservices Readiness]: this is the presentation-layer twin of what IBookmarkCountService does for the application layer.
      • +
      • Concept introduced, the cross-module UI service contract. [Rubric §18, UI Architecture] assesses how one module's page composes another module's capability. The pattern here is optional resolution: Conference's pages hold the dependency as a nullable property and ask the container for it rather than injecting it, BookmarkService = ServiceProvider.GetService<ISessionBookmarkUIService>() (MMCA.ADC.Conference.UI/Pages/Public/Sessions/PublicSessionList.razor.cs:91, and, for the detail page, the same line inside the extracted SessionBookmarkButton toggle it renders, SessionBookmarkButton.razor.cs:51), so a host that never called AddEngagementUI() simply renders no stars instead of failing to construct the page. PublicSessionListView then takes it as a nullable [Parameter] (PublicSessionListView.razor.cs:60). [Rubric §7, Microservices Readiness]: this is the presentation-layer twin of what IBookmarkCountService does for the application layer.
      • Concept, Result all the way to the page. Every member reports its outcome as a Result carrying the API's own errors with their ErrorType intact, and the interface doc comment states the rule (ISessionBookmarkUIService.cs:9-13): the calling page branches instead of catching, and only the caller's own OperationCanceledException still propagates. [Rubric §9, API & Contract Design] and [Rubric §24, Forms, Validation & UX Safety]: a "not there" answer and a "the remove failed" answer are different facts, and a boolean cannot tell them apart. That is spelled out for the delete member (:34-38): a missing bookmark answers 404, which arrives as an ErrorType.NotFound failure rather than a bare false.
      • Walkthrough: three members, each shaped by a real UI need.
        • GetBookmarkedSessionIdsAsync(userId, cancellationToken) (ISessionBookmarkUIService.cs:20-21) returns Result<IReadOnlyDictionary<SessionIdentifierType, UserSessionBookmarkIdentifierType>>, a map rather than a set. A list page needs both facts at once: which sessions are starred, and the bookmark id it must send to un-star each one, so the dictionary saves a second round trip per row.
        • @@ -1292,9 +1292,9 @@

          PointsSettings

        • What it is: the configuration object for the points economy: what each earn rule pays and how long the public leaderboard is. Bound from the Points configuration section.
        • Depends on: PointsActivityType. Externals: the options binder (Microsoft.Extensions.Options), through the host's AddOptions<PointsSettings>().Bind(...).
        • Concept introduced, configuration as an operational control, not just a default. [Rubric §29, Resilience, Reliability & Business Continuity] assesses how configuration is bound and consumed, and [Rubric §29, Resilience, Reliability & Business Continuity] assesses whether an operator can degrade a feature without a deploy. A value of 0, or a missing configuration entry (which binds to 0), is a deliberate per-rule kill switch: PointsAwarder reads GetPointsFor(activity) and returns success without writing anything when the value is not positive (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/Points/Services/PointsAwarder.cs:43-51), so one earn rule can be turned off mid-conference while the rest keep earning, and the ledger is not polluted with zero-value entries (PointsSettings.cs:7-10).
        • -
        • Concept, deliberately not ValidateOnStart. The Engagement host binds these options without eager validation, and the inline comment states why (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:123-125): a missing Points section binds every award to the defaults below, and an explicit 0 is the documented kill switch, so there is no value an organizer could set that should stop this host from booting mid-conference. The same comment also explains why the binding lives in the host rather than in AddEngagementModule: the module registration takes no IConfiguration (Program.cs:119-122). Contrast the pattern with CheckInSettings, bound the same way immediately after for the same reason (Program.cs:129-134). [Rubric §13, Observability & Operability]: the failure mode chosen here is "boot and keep serving", not "refuse to start".
        • +
        • Concept, deliberately not ValidateOnStart. The Engagement host binds these options without eager validation, and the inline comment states why (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:122-124): a missing Points section binds every award to the defaults below, and an explicit 0 is the documented kill switch, so there is no value an organizer could set that should stop this host from booting mid-conference. The same comment also explains why the binding lives in the host rather than in AddEngagementModule: the module registration takes no IConfiguration (Program.cs:118-121). Contrast the pattern with CheckInSettings, bound the same way immediately after for the same reason (Program.cs:128-133). [Rubric §13, Observability & Operability]: the failure mode chosen here is "boot and keep serving", not "refuse to start".
        • Walkthrough
            -
          • SectionName = "Points" (PointsSettings.cs:15): the constant the host binds against, so the section name is not duplicated as a literal at the call site (Program.cs:126-127).
          • +
          • SectionName = "Points" (PointsSettings.cs:15): the constant the host binds against, so the section name is not duplicated as a literal at the call site (Program.cs:125-126).
          • Six int award properties with defaults: EventCheckIn = 25 (line 18), SessionCheckIn = 10 (line 21), SessionFeedback = 15 (line 24), EventFeedback = 15 (line 27), QuestionAsked = 5 (line 30), SponsorVisit = 20 (line 37). The deployed appsettings.json sets exactly these same values (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/appsettings.json:26-34), so the checked-in defaults are the live economy.
          • SponsorVisit carries the longest doc comment of the six (PointsSettings.cs:32-36) and it is the interesting one: the deep link an attendee scans is shareable, so the once-per-sponsor cap enforced by the subject key plus a bounded value is what makes passing the URL around worthless.
          • LeaderboardSize = 10 (line 40): how many rows the public board shows. GetLeaderboardHandler reads it through Math.Max(settings.Value.LeaderboardSize, 0) before Take(...) (GetLeaderboardHandler.cs:63, :70), so a negative configured value yields an empty board rather than an exception.
          • @@ -1302,7 +1302,7 @@

            PointsSettings

        • Why it's built this way: init-only properties keep the bound options immutable after startup, and putting GetPointsFor on the settings object rather than in the awarder means the "what is this worth" lookup has one home; the awarder is left holding only the idempotency and persistence concerns. Keeping the type in *.Shared (not Application) is what lets the host bind it without referencing the Application layer.
        • -
        • Where it's used: bound by the Engagement service host (Program.cs:126-127); injected as IOptions<PointsSettings> into PointsAwarder (PointsAwarder.cs:31, :46) and GetLeaderboardHandler (GetLeaderboardHandler.cs:30, :63).
        • +
        • Where it's used: bound by the Engagement service host (Program.cs:125-126); injected as IOptions<PointsSettings> into PointsAwarder (PointsAwarder.cs:31, :46) and GetLeaderboardHandler (GetLeaderboardHandler.cs:30, :63).
        • Caveats / not-in-source: whether a live production deployment overrides any of these values through environment configuration is not determinable from source; the repository shows only the checked-in appsettings.json values, which match the class defaults.

        EngagementRoutePaths

        @@ -1576,12 +1576,12 @@

        FeedbackAnswerModel

      • Walkthrough
        • public const int AnswerValueMaxLength = 4000 (FeedbackAnswerModel.cs:30) is the client-side mirror of the server invariant. The XML comment names its source, and it checks out: EventInvariants.AnswerValueMaxLength is 4000 (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:59) and SessionInvariants.AnswerValueMaxLength is the same (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Sessions/SessionInvariants.cs:37). The same number also sizes the column (EventQuestionAnswerConfiguration.cs:26, SessionQuestionAnswerConfiguration.cs:26), so a value the form accepts is a value the table can hold.
        • TextValue (:34) is the string? bound by the text and email question types, and carries the [MaxLength(AnswerValueMaxLength, ErrorMessage = "Field.MaxLengthError")] annotation (:33).
        • -
        • RatingValue (:37) is a plain int with no annotation, because a rating is picked from a five-star control rather than typed. Zero is the unanswered sentinel: GetAnswerValue treats RatingValue > 0 as the only answered state (EventFeedback.razor.cs:290-292, SessionFeedback.razor.cs:320-322), so an untouched rating serializes to null and is skipped on submit.
        • +
        • RatingValue (:37) is a plain int with no annotation, because a rating is picked from a five-star control rather than typed. Zero is the unanswered sentinel: GetAnswerValue treats RatingValue > 0 as the only answered state (EventFeedback.razor.cs:320-322, SessionFeedback.razor.cs:350-352), so an untouched rating serializes to null and is skipped on submit.
        • Both members are settable auto-properties, deliberately. MudBlazor binds with @bind-SelectedValue and @bind-Value (EventFeedback.razor:50, :55, :65), which needs a writable property, so a record with init-only members could not serve as the target. The mutability is a consequence of the binding contract, not a lapse from the workspace's required/init default.
      • Why it's built this way: the type doc (FeedbackAnswerModel.cs:5-22) records the one design tension worth understanding. Both pages render a dynamic question list, so whether a given answer is required is per-question data (QuestionDTO.IsRequired, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Questions/QuestionDTO.cs:47), not a property of the model. A [Required] attribute here would apply to every question at once and would be wrong for most of them, so required stays on the field's own Required="@question.IsRequired" parameter (EventFeedback.razor:61, :73) and only the length rule, which genuinely is per-value, is declared on the model. Lifting the model to a public file-level type shared by both pages, rather than a private nested holder per page, is what makes one declaration serve both forms.
      • -
      • Where it's used: seeded one instance per question in each page's OnInitializedAsync (EventFeedback.razor.cs:114, SessionFeedback.razor.cs:119), populated by PreFillExistingAnswers, read by GetAnswerValue on submit, and reset in place by DeleteAnswerAsync. A separate prototype instance is constructed in each page's OnInitialized purely to build the validation delegate (EventFeedback.razor.cs:69-72, SessionFeedback.razor.cs:69-72): ModelValidation.ForProperty validates the value handed to it rather than the instance, so one prototype serves the whole dynamic list.
      • +
      • Where it's used: seeded one instance per question in each page's OnInitializedAsync (EventFeedback.razor.cs:121, SessionFeedback.razor.cs:126), populated by PreFillExistingAnswers, read by GetAnswerValue on submit, and reset in place by DeleteAnswerAsync. A separate prototype instance is constructed in each page's OnInitialized purely to build the validation delegate (EventFeedback.razor.cs:76-79, SessionFeedback.razor.cs:76-79): ModelValidation.ForProperty validates the value handed to it rather than the instance, so one prototype serves the whole dynamic list.
      • Caveats / not-in-source: nothing here enforces the length. The attribute is inert until a validator runs it; the enforcement path is the pages' _validateAnswerText delegate on the client and the Conference invariants on the server.

      @@ -1609,7 +1609,7 @@

      NowNextSessionInfo

    • Concept, the locally mirrored wire shape. [Rubric §7, Microservices Readiness] assesses whether a module can be lifted out without dragging a neighbor's assembly along, and [Rubric §9, API & Contract Design] how a payload contract is expressed to its consumers. The bytes on the wire are produced by Conference's NowNextSessionDTO; Engagement redeclares only the fields it renders instead of referencing MMCA.ADC.Conference.Shared for one payload, and JSON binds them by property name. The type doc on the enclosing snapshot records that this is a deliberate mirror and that the Android home-screen widget makes the same call with a mirror of its own (INowNextService.cs:6-9; the widget's private copy is at MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/Platforms/Android/NowNextWidgetProvider.cs:130-134). The same tolerant-reader trade-off taught on AttendeeRow applies: nothing at compile time keeps the mirror aligned with the DTO, only the round-trip tests do.
    • Walkthrough (INowNextService.cs:27-32): five positional members, SessionId (line 28, the deep-link target), Title (29), RoomName (30, nullable), StartsAtLocal (31), and EndsAtLocal (32). Two details are load-bearing. RoomName is null whenever the session carries no room id, or carries one the event's room map does not contain, a decision the server makes in a single expression (GetNowNextHandler.cs:78). And the two times are plain DateTime wall clock in the event's zone, not UTC and not DateTimeOffset: the server passes the stored wall-clock values straight through (GetNowNextHandler.cs:79-80) while keeping the UTC instants it computed for its own filtering on its side of the row (:81-82), so no client ever converts a zone.
    • Why it's built this way: glanceable surfaces (a page card, a home-screen widget) should print exactly what the printed schedule says, so the wall-clock value travels as data. The UTC instants stay on the server's side of the contract for callers that do their own math; this mirror deliberately drops them, along with EventId. The slice arrives with ADR-042 Wave 8 (GetNowNextHandler.cs:13).
    • -
    • Where it's used: only inside NowNextSnapshot's Now and Next lists; the HappeningNow page holds them in _sessionsNow / _sessionsNext (HappeningNow.razor.cs:49-50) and renders one card per row.
    • +
    • Where it's used: only inside NowNextSnapshot's Now and Next lists; the HappeningNow page holds them in _sessionsNow / _sessionsNext (HappeningNow.razor.cs:50-51) and renders one card per row.
    • Caveats / not-in-source: the mirror is checked by NowNextServiceTests round-tripping a JSON body, not by the compiler, so a renamed server property would deserialize to a default value rather than break the build.

    @@ -1634,9 +1634,9 @@

    SessionReminder

  • What it is: an immutable value describing one planned on-device reminder for a bookmarked session, a stable notification id, when it should fire, and where a tap takes the user.
  • Depends on: the SessionIdentifierType alias (solution-wide global using, see primer §2). Externals: BCL DateTimeOffset. No first-party types beyond the alias.
  • Concept, the immutable planning DTO. [Rubric §19, State Management] assesses whether client-side state is modeled as explicit, predictable values rather than mutable ad-hoc fields. A sealed record gives value equality and init-only positional members for free (see primer §4), so a planned reminder is a snapshot that cannot drift after SessionReminderPlanner computes it. The type carries no behavior: it is pure data handed from the pure planner to the stateful coordinator.
  • -
  • Walkthrough (SessionReminderPlanner.cs:15-21): six positional members, NotificationId (int, line 14), SessionId (15), SessionTitle (16), StartsAtUtc (17), DeliverAt (18), and DeepLinkRoute (19). The doc comment (:3-12) is load-bearing intent: NotificationId is derived deterministically from the session id so rescheduling replaces an existing OS notification instead of stacking a duplicate, DeliverAt is the fire instant (start minus lead, clamped to now), and DeepLinkRoute is the app-relative route published on tap.
  • -
  • Why it's built this way: keeping the planned reminder as a value lets the planner stay a pure function and the coordinator diff plans by id (ADR-042 Wave 2, the local-notification dispatcher).
  • -
  • Where it's used: produced by SessionReminderPlanner.Plan (SessionReminderPlanner.cs:81-87); consumed by SessionReminderCoordinator, which calls Plan at SessionReminderCoordinator.cs:159 and turns each returned value into a LocalNotificationRequest (:180).
  • +
  • Walkthrough (SessionReminderPlanner.cs:15-29): six positional members, NotificationId (int, line 16), SessionId (17), SessionTitle (18), StartsAtUtc (19), DeliverAt (20), and DeepLinkRoute (21), plus one init-only body property, IsImmediate (:28). The doc comment (:5-14) is load-bearing intent: NotificationId is derived deterministically from the session id so rescheduling replaces an existing OS notification instead of stacking a duplicate, DeliverAt is the fire instant (start minus lead, clamped to now), and DeepLinkRoute is the app-relative route published on tap. IsImmediate (doc at :23-27) marks a reminder whose session was already inside the lead window at planning time, so its DeliverAt was clamped to fire almost at once; the doc states the contract the coordinator enforces, such a reminder fires once, when the session becomes tracked, not again on every replan. It is a body property rather than a seventh positional member, so existing positional construction stays valid and the planner sets it with an object initializer.
  • +
  • Why it's built this way: keeping the planned reminder as a value lets the planner stay a pure function and the coordinator diff plans by id (ADR-042 Wave 2, the local-notification dispatcher). Carrying IsImmediate on the value, rather than having the coordinator recompute "was this clamped", keeps the window decision in the one place that did the date math.
  • +
  • Where it's used: produced by SessionReminderPlanner.Plan (SessionReminderPlanner.cs:90-99); consumed by SessionReminderCoordinator, which calls Plan at SessionReminderCoordinator.cs:176, reads IsImmediate to skip an already-issued immediate reminder (:215), and turns each remaining value into a LocalNotificationRequest (:223).

  • NowNextSnapshot

    @@ -1649,27 +1649,27 @@

    NowNextSnapshot

  • Concept, the two-bucket time projection, computed once on the server. [Rubric §12, Performance & Scalability] assesses whether work sits where it is cheapest, and [Rubric §18, UI Architecture] whether a page renders a prepared view model rather than deriving one. Both buckets are decided by GetNowNextHandler against a single instant taken from an injected TimeProvider (GetNowNextHandler.cs:29), never from the browser clock. Now is every eligible session whose window is half-open around that instant, StartsAtUtc <= utcNow && utcNow < EndsAtUtc (GetNowNextHandler.cs:53), so a session that ends exactly now is already out and the one starting exactly now is already in. Next is not "the next session": it is the batch that shares the earliest future start (:58-66), which is what makes parallel tracks appear together instead of one arbitrary room winning. The empty cases are ordinary, not error states: between sessions Now is empty, after the last session Next is empty (nextStart is null and the list is [], :60-62), and before the first session Now is empty while Next carries the opening batch.
  • Walkthrough (INowNextService.cs:15-19): four positional members, EventName (line 16), IsLive (17), Now (18), and Next (19). IsLive is independent of the two lists: the server compares the same instant against the event's live window from CurrentEventSelector.GetLiveWindowUtc(StartDate, EndDate, TimeZone) (GetNowNextHandler.cs:68-69), so a live event during a coffee break reports IsLive: true with an empty Now. Ordering is fixed server-side too: Now sorts by start then by room name ordinal-ignore-case (:54-55), Next by room name alone (:65) since every row in it shares one start.
  • Why it's built this way: no client can compute either bucket correctly on its own, because eligibility depends on data a public catalog read does not expose (see INowNextService), so the snapshot is shipped precomputed and every surface (page and widget) shows the same answer. The one snapshot type also keeps IsLive next to the lists, which is what lets a caller distinguish "the conference is not running" from "nothing is on right now".
  • -
  • Where it's used: carried by the Result that INowNextService.GetAsync returns; the HappeningNow page projects it with result.Value?.Now ?? [] and result.Value?.Next ?? [] (HappeningNow.razor.cs:157-159), so a failed read renders the same empty state as an empty list. Conference's own wire type is NowNextDTO (GetNowNextHandler.cs:71), and the Android widget deserializes a third copy of the shape (NowNextWidgetProvider.cs:127, :132).
  • -
  • Caveats / not-in-source: nothing on the record marks how fresh it is. Staleness is bounded by two caches only, the query result cache of 30 seconds (GetNowNextQuery implements IQueryCacheable, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/NowNext/GetNowNextQuery.cs:38) and the endpoint's 60-second NowNextCache output-cache policy (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:285, applied at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:167 and :187). The page fetches the snapshot from its own load path (HappeningNow.razor.cs:157) and has no timer of its own for it, so a long-open tab keeps its first now-and-next lists until something refreshes it.
  • +
  • Where it's used: carried by the Result that INowNextService.GetAsync returns; the HappeningNow page projects it with result.Value?.Now ?? [] and result.Value?.Next ?? [] (HappeningNow.razor.cs:158-160), so a failed read renders the same empty state as an empty list. Conference's own wire type is NowNextDTO (GetNowNextHandler.cs:71), and the Android widget deserializes a third copy of the shape (NowNextWidgetProvider.cs:127, :132).
  • +
  • Caveats / not-in-source: nothing on the record marks how fresh it is. Staleness is bounded by two caches only, the query result cache of 30 seconds (GetNowNextQuery implements IQueryCacheable, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/NowNext/GetNowNextQuery.cs:38) and the endpoint's 60-second NowNextCache output-cache policy (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:284, applied at MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:174 and :187). The page fetches the snapshot from its own load path (HappeningNow.razor.cs:158) and has no timer of its own for it, so a long-open tab keeps its first now-and-next lists until something refreshes it.

  • SessionReminderPlanner

    -

    MMCA.ADC.Engagement.UI · MMCA.ADC.Engagement.UI.Services.HappeningNow · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Services/HappeningNow/SessionReminderPlanner.cs:31 · Level 1 · static class

    +

    MMCA.ADC.Engagement.UI · MMCA.ADC.Engagement.UI.Services.HappeningNow · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Services/HappeningNow/SessionReminderPlanner.cs:39 · Level 1 · static class

    • What it is: a pure, side-effect-free planner that turns a set of bookmarked sessions plus an event time zone and a lead time into a list of SessionReminder values. All the date math (wall-clock to UTC, DST edge cases, lead-window clamping) lives here.
    • Depends on: SessionReminder, SessionInfo (the input session shape), EngagementRoutePaths (the tap route), and the SessionIdentifierType alias. Externals: BCL TimeZoneInfo, DateTimeOffset.
    • -
    • Concept, the deterministic pure function. [Rubric §14, Testability] assesses whether logic can be exercised without infrastructure. Every input the planner needs is a parameter, including now (SessionReminderPlanner.cs:53), the current instant, which the caller supplies rather than the planner reading DateTimeOffset.UtcNow itself. That single choice makes DST behavior, lead-window clamping and skip rules unit-testable with fixed clocks and no OS notification stack. [Rubric §27, Internationalization] also applies: session times are wall-clock local to the event's IANA time zone, never UTC in the DTOs (SessionInfo.StartsAt is a bare DateTime?, ISessionLookupService.cs:12), so correct conversion is a first-class concern here rather than an afterthought.
    • +
    • Concept, the deterministic pure function. [Rubric §14, Testability] assesses whether logic can be exercised without infrastructure. Every input the planner needs is a parameter, including now (SessionReminderPlanner.cs:61), the current instant, which the caller supplies rather than the planner reading DateTimeOffset.UtcNow itself. That single choice makes DST behavior, lead-window clamping and skip rules unit-testable with fixed clocks and no OS notification stack. [Rubric §27, Internationalization] also applies: session times are wall-clock local to the event's IANA time zone, never UTC in the DTOs (SessionInfo.StartsAt is a bare DateTime?, ISessionLookupService.cs:12), so correct conversion is a first-class concern here rather than an afterthought.
    • Walkthrough:
        -
      • NotificationIdSeed (:34) is a private constant offset that keeps reminder ids clear of any other local-notification ids the app might use. The comment above it (:31-33) states the assumption the whole replace-by-id contract rests on: SessionIdentifierType hash codes are stable for value types.
      • -
      • GetNotificationId(sessionId) (:37-38) shifts the seed 16 bits and XORs the session id's hash unchecked, producing that stable id.
      • -
      • Plan(bookmarkedSessions, timeZoneId, leadTime, now) (:47-89) guards its arguments (:53-54), resolves the TimeZoneInfo (:56), then per session skips those with no start time (:61-64) or already started (:67-70), computes deliverAt = startsAtUtc - leadTime and, when that instant has already passed, fires an immediate reminder 30 seconds out rather than dropping it (:72-77), and finally emits a SessionReminder whose tap route comes from EngagementRoutePaths.SessionDetails (:79-85, MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/EngagementRoutePaths.cs:12).
      • -
      • ToUtc(localWallClock, timeZone) (:91-104) is the DST-correct converter: it marks the wall time Unspecified (:93), shifts a spring-forward invalid time forward one hour (:96-99), and lets GetUtcOffset resolve ambiguous fall-back times to the standard offset (:101-103).
      • +
      • NotificationIdSeed (:44) is a private constant offset that keeps reminder ids clear of any other local-notification ids the app might use. The comment above it (:41-43) states the assumption the whole replace-by-id contract rests on: SessionIdentifierType hash codes are stable for value types.
      • +
      • GetNotificationId(sessionId) (:47-48) shifts the seed 16 bits and XORs the session id's hash unchecked, producing that stable id.
      • +
      • Plan(bookmarkedSessions, timeZoneId, leadTime, now) (:57-103) guards its arguments (:63-64), resolves the TimeZoneInfo (:66), then per session skips those with no start time (:71-74) or already started (:77-80), computes deliverAt = startsAtUtc - leadTime (:82) and captures isImmediate = deliverAt <= now (:83); when that holds it fires the reminder 30 seconds out rather than dropping it (:84-88). Finally it emits a SessionReminder whose tap route comes from EngagementRoutePaths.SessionDetails (:90-96, MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/EngagementRoutePaths.cs:12), with IsImmediate = isImmediate set by object initializer (:97-99). The planner only records the clamp; deciding whether a clamped reminder should actually be scheduled again is the coordinator's job.
      • +
      • ToUtc(localWallClock, timeZone) (:105-118) is the DST-correct converter: it marks the wall time Unspecified (:107), shifts a spring-forward invalid time forward one hour (:110-113), and lets GetUtcOffset resolve ambiguous fall-back times to the standard offset (:115-117).
    • -
    • Why it's built this way: separating planning (pure, here) from scheduling (I/O, in the coordinator) means the tricky calendar logic is provable in isolation and the coordinator stays a thin best-effort orchestrator (ADR-042 Wave 2, named in the type doc at :22).
    • -
    • Where it's used: called by SessionReminderCoordinator.ReplanAsync (SessionReminderCoordinator.cs:159); GetNotificationId is also called there to cancel reminders for sessions that left the tracked set (:99), to cancel everything when reminders are switched off (:118), and to sweep the ids that dropped out of the plan (:170). Covered by SessionReminderPlannerTests.
    • +
    • Why it's built this way: separating planning (pure, here) from scheduling (I/O, in the coordinator) means the tricky calendar logic is provable in isolation and the coordinator stays a thin best-effort orchestrator (ADR-042 Wave 2, named in the type doc at :32).
    • +
    • Where it's used: called by SessionReminderCoordinator.ReplanAsync (SessionReminderCoordinator.cs:176); GetNotificationId is also called there to cancel reminders for sessions that left the tracked set (:109), to cancel everything when reminders are switched off (:138), and to sweep the ids that dropped out of the plan (:190). Covered by SessionReminderPlannerTests.

    ICheckInUIService

    @@ -1701,7 +1701,7 @@

    INowNextService

  • What it is: the UI-side contract for reading one event's now-and-next snapshot, a single method returning a Result over a NowNextSnapshot.
  • Depends on: NowNextSnapshot, Result, and the EventIdentifierType alias. Externals: BCL Task, CancellationToken.
  • Concept, a port over a server-owned computation. [Rubric §18, UI Architecture] assesses whether components depend on typed contracts rather than transport, and [Rubric §7, Microservices Readiness] whether cross-module reads travel through an explicit boundary. The interface doc (INowNextService.cs:34-39) is the contract worth memorizing, because it enumerates what the server owns and the client therefore must not re-derive: session eligibility (scheduled, non-service, not declined or cancelled), the event-local wall-clock conversion, and the "next = the batch sharing the earliest future start" rule. Concretely, eligibility is CalendarExportMapper.IsExportable (GetNowNextHandler.cs:48). None of that is visible in a public catalog listing, which is precisely why the page cannot compute now-and-next itself; the implementation's doc records that this endpoint replaces the old whole-catalog fetch on Happening Now (NowNextService.cs:8-12).
  • -
  • Walkthrough (INowNextService.cs:49-51): one method, GetAsync(eventId, cancellationToken = default), returning Task<Result<NowNextSnapshot>>. The failure contract is the doc at :42-46: an event that is not found or not published answers an ErrorType.NotFound failure, which the page renders as "nothing scheduled". The server produces it as Result.Failure carrying Error.NotFound when the event is missing or IsPublished is false (GetNowNextHandler.cs:32-36), the API surfaces it as a 404, and ProblemDetailsResultReader turns it back into a typed failure in the client (NowNextService.cs:29-31). Answering with a failure rather than throwing is what lets a caller degrade to an empty schedule with no try/catch of its own.
  • +
  • Walkthrough (INowNextService.cs:49-51): one method, GetAsync(eventId, cancellationToken = default), returning Task<Result<NowNextSnapshot>>. The failure contract is the doc at :42-46: an event that is not found or not published answers an ErrorType.NotFound failure, which the page renders as "nothing scheduled". The server produces it as Result.Failure carrying Error.NotFound when the event is missing or IsPublished is false (GetNowNextHandler.cs:32-36), the API surfaces it as a 404, and ProblemDetailsResultReader turns it back into a typed failure in the client (NowNextService.cs:31-33). Answering with a failure rather than throwing is what lets a caller degrade to an empty schedule with no try/catch of its own.
  • Why it's built this way: the one-method port is the DI swap point (services.AddScoped<INowNextService, NowNextService>(), MMCA.ADC.Engagement.UI/DependencyInjection.cs:60) and the test extension point, so the page can be exercised against a fake snapshot with no HTTP stack; it also keeps Engagement free of any compile-time reference to Conference for this read (ADR-007 and ADR-008 put the module in its own process, so this crosses a real service boundary over public HTTP; the implementation uses the plain "APIClient" factory client with no token, NowNextService.cs:23, because the endpoint is anonymous).
  • Where it's used: injected into the HappeningNow page (HappeningNow.razor.cs:28) and called from its load path (:156). Implemented by NowNextService (NowNextService.cs:14); covered by NowNextServiceTests.
  • Caveats / not-in-source: the contract exposes only the per-event form. The endpoint also serves an id-less form where the server auto-selects the current-or-next published event, but no first-party C# client for it exists in this UI; the Android widget calls Events/now-next with a bare HttpClient instead (NowNextWidgetProvider.cs:119-120). Note also a wording gap: the doc comment says "not declined or cancelled" while the code's actual gate is the calendar-export allow-list above.
  • @@ -1736,45 +1736,45 @@

    NowNextService

    • What it is: the HTTP implementation of INowNextService, one GET against the Conference API's anonymous Events/{id}/now-next endpoint, read into a NowNextSnapshot and handed back untouched.
    • -
    • Depends on: INowNextService, NowNextSnapshot, HttpResultExecutor, ProblemDetailsResultReader. Externals: IHttpClientFactory, HttpClient, CultureInfo.
    • -
    • Concept introduced, the thin lookup service (the contrast with the authenticated services above). [Rubric §18, UI Architecture] and [Rubric §12, Performance & Scalability]. Unlike BookmarkService this class does not derive from AuthenticatedServiceBase: it takes the named "APIClient" from the factory directly (NowNextService.cs:23), the same shape as the module's other read-only lookups (SessionLookupService). That named client is registered once in the framework (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:96) with the gateway base address plus the auth and culture delegating handlers, so a bearer token still rides along when the user has one, but the endpoint is [AllowAnonymous] (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:166) and an anonymous visitor gets the same payload. The consequence to notice: no token fetch and no RetryPolicy wrap this call (:25-27 calls GetAsync directly), because there is nothing user-specific to authorize and the answer is cheap to re-request.
    • -
    • Walkthrough (NowNextService.cs:17-33), which is mostly guards
        +
      • Depends on: INowNextService, NowNextSnapshot, HttpResultExecutor, IdempotentReadRetry, ProblemDetailsResultReader. Externals: IHttpClientFactory, HttpClient, CultureInfo.
      • +
      • Concept introduced, the thin lookup service (the contrast with the authenticated services above). [Rubric §18, UI Architecture] and [Rubric §12, Performance & Scalability]. Unlike BookmarkService this class does not derive from AuthenticatedServiceBase: it takes the named "APIClient" from the factory directly (NowNextService.cs:23), the same shape as the module's other read-only lookups (SessionLookupService). That named client is registered once in the framework (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:96) with the gateway base address plus the auth and culture delegating handlers, so a bearer token still rides along when the user has one, but the endpoint is [AllowAnonymous] (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:173) and an anonymous visitor gets the same payload. The consequence to notice: no token fetch wraps this call, because there is nothing user-specific to authorize. Retry still applies, without the base class: the GET goes through IdempotentReadRetry.GetAsync (:26-29), whose policy is the same object AuthenticatedServiceBase uses (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/IdempotentReadRetry.cs:24), so a transient failure (an HttpRequestException, a 5xx other than 501/505, 408 or 429) is re-sent up to three times with exponential backoff plus jitter (IdempotentReadRetry.cs:10-12). The inline comment names why that is safe here (NowNextService.cs:25): the read is idempotent and anonymous, like the other lookups.
      • +
      • Walkthrough (NowNextService.cs:17-35), which is mostly guards
        • The whole body runs inside HttpResultExecutor.ExecuteAsync (:20), so a refused connection or a broken socket becomes a Http.TransportFailure failure rather than an exception the page has to catch.
        • -
        • Both disposables are scoped with using, the client (:23) and the response (:25), so a per-call fetch leaks neither.
        • -
        • The relative URI is built with string.Create(CultureInfo.InvariantCulture, $"Events/{eventId}/now-next") (:26). Invariant formatting is not decoration: the id is interpolated into a path, and a culture-sensitive numeric format would produce a path the API cannot route.
        • -
        • The response goes through ProblemDetailsResultReader.ReadAsync<NowNextSnapshot> (:31), so 404 becomes a NotFound failure, with the comment naming the case it covers (an unpublished or deleted event) and the consequence: the page shows nothing scheduled, exactly as it did for the old null (:29-30). A 5xx becomes a failure carrying the server's status code instead, so the page can tell "no such event" from "the service is broken" by reading the error rather than by catching an exception.
        • -
        • The CancellationToken is threaded through the GET (:27), the read (:31) and the executor (:32), so page disposal cancels an in-flight fetch and the cancellation propagates rather than being reported as an error.
        • +
        • Both disposables are scoped with using, the client (:23) and the final response (:26), so a per-call fetch leaks neither; any response a retry discards is disposed by the policy itself (IdempotentReadRetry.cs:27-28).
        • +
        • The relative URI is built with string.Create(CultureInfo.InvariantCulture, $"Events/{eventId}/now-next") (:28). Invariant formatting is not decoration: the id is interpolated into a path, and a culture-sensitive numeric format would produce a path the API cannot route.
        • +
        • The response goes through ProblemDetailsResultReader.ReadAsync<NowNextSnapshot> (:33), so 404 becomes a NotFound failure, with the comment naming the case it covers (an unpublished or deleted event) and the consequence: the page shows nothing scheduled, exactly as it did for the old null (:31-32). A 5xx becomes a failure carrying the server's status code instead, so the page can tell "no such event" from "the service is broken" by reading the error rather than by catching an exception.
        • +
        • The CancellationToken is threaded through the retried GET (:29, honoured between attempts as well as by each send), the read (:33) and the executor (:35), so page disposal cancels an in-flight fetch or a pending backoff and the cancellation propagates rather than being reported as an error.
        • There is no client-side filtering, sorting or time math anywhere in the method: the lists arrive ordered and filtered and are passed straight to the page.
      • -
      • Why it's built this way: the class doc (:8-13) states the rationale directly, the endpoint already excludes service, declined and cancelled sessions and does the event-local time math, so nothing is recomputed here. A whole-catalog fetch plus client-side time comparison could not tell an ineligible session apart, since eligibility is not part of a public catalog listing. The call is also cheap to repeat: the server output-caches it under the NowNextCache policy (EventsController.cs:167) and the payload is identical for every role, so no per-user cache key is needed.
      • +
      • Why it's built this way: the class doc (:8-13) states the rationale directly, the endpoint already excludes service, declined and cancelled sessions and does the event-local time math, so nothing is recomputed here. A whole-catalog fetch plus client-side time comparison could not tell an ineligible session apart, since eligibility is not part of a public catalog listing. The call is also cheap to repeat: the server output-caches it under the NowNextCache policy (EventsController.cs:174) and the payload is identical for every role, so no per-user cache key is needed.
      • Where it's used: registered scoped as INowNextService in the module's UI wiring (MMCA.ADC.Engagement.UI/DependencyInjection.cs:60); consumed only by the HappeningNow page today (HappeningNow.razor.cs:28, called at :156). [Rubric §14, Testability]: covered by three facts in NowNextServiceTests, the exact request path (:20), the unchanged projection of the server snapshot (:33), and the 404-to-NotFound-failure degradation (:50).
      • -
      • Caveats / not-in-source: no timeout, retry or circuit breaker is configured on this path in the framework registration of the named client. A transient failure is therefore one transport failure and one localized error message, with a page reload as the only retry. Note also that the Conference API exposes a second, id-less form of the same snapshot for the home-screen widget (EventsController.cs:180); this service does not call it.
      • +
      • Caveats / not-in-source: the retry lives in the call, not on the named client: no timeout or circuit breaker is configured on this path in the framework registration of "APIClient". A failure that survives the three retries is one transport failure and one localized error message, with a page reload as the next attempt. Note also that the Conference API exposes a second, id-less form of the same snapshot for the home-screen widget (EventsController.cs:187); this service does not call it.

      SessionReminderCoordinator

      -

      MMCA.ADC.Engagement.UI · MMCA.ADC.Engagement.UI.Services.HappeningNow · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Services/HappeningNow/SessionReminderCoordinator.cs:20 · Level 4 · sealed partial class

      +

      MMCA.ADC.Engagement.UI · MMCA.ADC.Engagement.UI.Services.HappeningNow · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Services/HappeningNow/SessionReminderCoordinator.cs:25 · Level 4 · sealed partial class

        -
      • What it is: the piece that keeps on-device session reminders in step with the user's bookmarks (ADR-042 Wave 2). It owns a tracked session set and two settings in device preferences, and on every change it replans the whole schedule and cancel-then-reschedules the difference (class doc, :8-15).
      • -
      • Depends on: ILocalNotificationService, IDevicePreferences, ISessionLookupService, ILiveEventUIService, SessionReminderPlanner, LocalNotificationRequest, LiveEventContext, SessionInfo, SessionReminder. Externals: IStringLocalizer<T>, ILogger<T> with a source-generated [LoggerMessage].
      • -
      • Concept introduced, best-effort derived state behind a capability gate. Two ideas meet here. First, [Rubric §19, State Management]: a scheduled local notification is derived state that only stays correct if it tracks the bookmark set, and the bookmark set changes from any page and any device. This class is the one place that derivation lives. Second, [Rubric §29, Resilience & Business Continuity]: reminders are a convenience and must never fail a bookmark operation, so every entry point is best-effort. MutateTrackedAsync (:107-110) and ApplySettingsAsync (:69-72) both wrap their work in catch (Exception ex) when (ex is not OperationCanceledException) and log a warning through LogReminderSyncFailed (:190-191), rather than propagating. Note the exception filter: a genuine cancellation is not swallowed. Third, and this is what makes the coupling cheap, every entry point begins with if (!notifications.IsSupported) return; (:57-60, :88-91), so on a web head the whole class is a no-op that touches no storage and issues no calls. The [LoggerMessage] source generator is the framework's structured-logging default, [Rubric §13, Observability & Operability]: a failure here is invisible to the user by design, so a log entry is the only signal that reminders may be stale.
      • -
      • Walkthrough (constants, then the four entry points, then the two private engines)
          -
        • The preference keys and defaults are public consts: EnabledKey (:26), LeadMinutesKey (:29) and DefaultLeadMinutes = 15 (:34), with the tracked-set key kept private (:31). IsSupported (:37) forwards the capability flag so a settings page can hide the section entirely.
        • -
        • NotifyBookmarkAddedAsync (:40-41) and NotifyBookmarkRemovedAsync (:44-45) are one-liners over MutateTrackedAsync, appending with Distinct() and filtering with the default equality comparer for the identifier alias respectively.
        • -
        • ResyncAsync (:51-52) replaces the tracked set outright with an authoritative bookmark map. Its doc (:47-50) names the reason it exists: a page that loads the user's bookmarks has the truth in hand, so this is the moment to heal drift caused by changes made on another device.
        • -
        • ApplySettingsAsync (:55-73) persists both settings then replans, which is also how disabling works: ReplanAsync cancels everything when enabled is false.
        • -
        • GetSettingsAsync (:76-81) reads the pair back for the device-settings UI, defaulting to enabled and 15 minutes.
        • -
        • MutateTrackedAsync (:83-111) is the shared write path: read the tracked array, apply the caller's mutation, persist it (:94-96), cancel reminders for sessions that left the set (:99-103), then replan the rest. Cancelling before replanning is deliberate and the comment says so (:98).
        • -
        • ReplanAsync (:113-188) is the heart of the class and reads as a sequence of early returns, each protecting the existing schedule: reminders switched off cancels everything tracked and returns (:115-125); a denied OS permission returns without scheduling, and the comment records that the prompt appears at most once and a denial leaves reminders silently off until the user grants them in system settings (:127-132); no live event returns (:134-138); a catalog read the API refuses returns, with the comment giving the reason, replanning against an empty catalog would cancel reminders that are still valid (:142-148). Note the shape of that last guard: sessionLookup.GetAllAsync returns a Result and the code branches on TryGetValue (:145), so "the API said no" and "there are no sessions" are different outcomes.
        • -
        • Past the guards it narrows the tracked ids to sessions in the current event (:150-154), calls SessionReminderPlanner.Plan with the event's IANA zone, the lead time and DateTimeOffset.UtcNow (:156-160), then does the stale sweep (:169-176): the tracked ids minus the planned ids, cancelled. The comment there is the best single paragraph in the file (:161-168), it names the four ways a session can stay tracked yet drop out of the plan (deleted, moved to another event, times cleared per BR-16, start moved into the past), notes that nothing upstream cancels those, and explains why the sweep sits after a plan was computed rather than beside the early returns: cancelling on a transient null live event would wipe reminders that are still valid.
        • -
        • Finally it schedules each planned reminder as a LocalNotificationRequest with a localized title and body (:178-187). The body takes the session title and the lead minutes as format arguments (:183), so the wording is resource-driven rather than concatenated. [Rubric §27, i18n].
        • +
        • What it is: the piece that keeps on-device session reminders in step with the user's bookmarks (ADR-042 Wave 2). It owns a tracked session set and two settings in device preferences, and on every change it replans the whole schedule and cancel-then-reschedules the difference (class doc, :11-24). The class doc's second paragraph (:19-23) adds the one exception to "replan everything": a session already inside the lead window gets its immediate reminder once, when it becomes tracked on this device, and never again on a later replan.
        • +
        • Depends on: ILocalNotificationService, IDevicePreferences, ISessionLookupService, ILiveEventUIService, SessionReminderPlanner, LocalNotificationRequest, LiveEventContext, SessionInfo, SessionReminder. Externals: BCL TimeProvider (the clock, :30), IStringLocalizer<T>, ILogger<T> with a source-generated [LoggerMessage].
        • +
        • Concept introduced, best-effort derived state behind a capability gate. Two ideas meet here. First, [Rubric §19, State Management]: a scheduled local notification is derived state that only stays correct if it tracks the bookmark set, and the bookmark set changes from any page and any device. This class is the one place that derivation lives. Second, [Rubric §29, Resilience & Business Continuity]: reminders are a convenience and must never fail a bookmark operation, so every entry point is best-effort. MutateTrackedAsync (:117-120) and ApplySettingsAsync (:78-81) both wrap their work in catch (Exception ex) when (ex is not OperationCanceledException) and log a warning through LogReminderSyncFailed (:233-234), rather than propagating. Note the exception filter: a genuine cancellation is not swallowed. Third, and this is what makes the coupling cheap, every entry point begins with if (!notifications.IsSupported) return; (:66-69, :96-99), so on a web head the whole class is a no-op that touches no storage and issues no calls. The [LoggerMessage] source generator is the framework's structured-logging default, [Rubric §13, Observability & Operability]: a failure here is invisible to the user by design, so a log entry is the only signal that reminders may be stale.
        • +
        • Walkthrough (constants, then the four entry points, then the three private engines)
            +
          • The preference keys and defaults are public consts: EnabledKey (:35), LeadMinutesKey (:38) and DefaultLeadMinutes = 15 (:43), with the tracked-set key kept private (:40). IsSupported (:46) forwards the capability flag so a settings page can hide the section entirely.
          • +
          • NotifyBookmarkAddedAsync (:49-50) and NotifyBookmarkRemovedAsync (:53-54) are one-liners over MutateTrackedAsync, appending with Distinct() and filtering with the default equality comparer for the identifier alias respectively.
          • +
          • ResyncAsync (:60-61) replaces the tracked set outright with an authoritative bookmark map. Its doc (:56-59) names the reason it exists: a page that loads the user's bookmarks has the truth in hand, so this is the moment to heal drift caused by changes made on another device.
          • +
          • ApplySettingsAsync (:64-82) persists both settings then replans with an empty newly-tracked set (:76), which is also how disabling works: ReplanAsync cancels everything when enabled is false. The empty set means a settings change never re-issues an immediate reminder.
          • +
          • GetSettingsAsync (:85-90) reads the pair back for the device-settings UI, defaulting to enabled and 15 minutes.
          • +
          • MutateTrackedAsync (:92-121) is the shared write path: read the tracked array, apply the caller's mutation, compute newlyTracked = updated.Except(tracked) (:105), persist the new array (:106), cancel reminders for sessions that left the set (:109-113), then replan the rest passing newlyTracked along (:115). Cancelling before replanning is deliberate and the comment says so (:108). Because ResyncAsync runs through here too, a resync on every schedule load only counts the ids that were not already tracked as new.
          • +
          • ReplanAsync(tracked, newlyTracked, ...) (:130-199, parameter doc :123-129) is the heart of the class and reads as a sequence of early returns, each protecting the existing schedule: reminders switched off cancels everything tracked and returns (:135-145); a denied OS permission returns without scheduling, and the comment records that the prompt appears at most once and a denial leaves reminders silently off until the user grants them in system settings (:147-152); no live event returns (:154-158); a catalog read the API refuses returns, with the comment giving the reason, replanning against an empty catalog would cancel reminders that are still valid (:162-168). Note the shape of that last guard: sessionLookup.GetAllAsync returns a Result and the code branches on TryGetValue (:165), so "the API said no" and "there are no sessions" are different outcomes.
          • +
          • Past the guards it narrows the tracked ids to sessions in the current event (:170-174), calls SessionReminderPlanner.Plan with the event's IANA zone, the lead time and timeProvider.GetUtcNow() (:176-180), then does the stale sweep (:189-196): the tracked ids minus the planned ids, cancelled. The comment there is the best single paragraph in the file (:182-188), it names the four ways a session can stay tracked yet drop out of the plan (deleted, moved to another event, times cleared per BR-16, start moved into the past), notes that nothing upstream cancels those, and explains why the sweep sits after a plan was computed rather than beside the early returns: cancelling on a transient null live event would wipe reminders that are still valid. It then hands off to ScheduleAsync (:198).
          • +
          • ScheduleAsync(plan, newlyTracked, ...) (:205-231) schedules each planned reminder as a LocalNotificationRequest, with one skip: a reminder whose IsImmediate is set and whose session is not in newlyTracked is passed over (:215-218). The comment (:212-214) gives the reasoning: re-issuing it would fire an already-delivered reminder again, and because its id is still in the plan the stale sweep above leaves any still-pending copy alone. The body text no longer quotes the configured lead: minutesUntilStart is the real gap between StartsAtUtc and DeliverAt, rounded up with a floor of 1 (:222), so a clamped reminder does not claim the full lead (:220-221). Title and body are localized, the body taking the session title and minutesUntilStart as format arguments (:224-226), so the wording is resource-driven rather than concatenated. [Rubric §27, i18n].
        • -
        • Why it's built this way: ADR-042 puts device capabilities behind interfaces the web head can answer "unsupported" to, which is what lets this class be registered unconditionally (MMCA.ADC.Engagement.UI/DependencyInjection.cs:38, whose comment repeats the no-op guarantee) and injected into a service every head uses. Notification ids are derived deterministically from the session id (SessionReminderPlanner.GetNotificationId, SessionReminderPlanner.cs:39-40), which is what makes rescheduling replace instead of duplicate, and is why a replan-everything strategy is affordable.
        • -
        • Where it's used: injected as a concrete class by SessionBookmarkUIService (SessionBookmarkUIService.cs:29), which drives all three notify or resync members, and by the host's device-settings page (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/Pages/DeviceSettings.razor:4), which reads the settings (:67), writes them on change (:89, :95) and seeds its lead-time control from DefaultLeadMinutes (:61). [Rubric §14, Testability]: SessionReminderCoordinatorTests covers twelve facts, one per branch above, including the two stale-sweep cases (:129, :155), that no live event cancels nothing (:176), that an unsupported host touches nothing (:223), that lookup failures never throw out of the coordinator (:237), and that a failed catalog read leaves the existing schedule alone (:249).
        • -
        • Caveats / not-in-source: IsSupported (:37) has no consumer in the repo today, including the device-settings page its doc points at; the page relies on the internal no-ops instead. The class is also registered scoped while the state it manages is device-global: correctness rests on preferences being the single source of truth and on every mutation replanning from scratch, not on any one instance living long. Whether a scheduled notification actually fires is the platform's business, outside this file.
        • +
        • Why it's built this way: ADR-042 puts device capabilities behind interfaces the web head can answer "unsupported" to, which is what lets this class be registered unconditionally (MMCA.ADC.Engagement.UI/DependencyInjection.cs:38, whose comment repeats the no-op guarantee) and injected into a service every head uses. Notification ids are derived deterministically from the session id (SessionReminderPlanner.GetNotificationId, SessionReminderPlanner.cs:47-48), which is what makes rescheduling replace instead of duplicate, and is why a replan-everything strategy is affordable. The one thing a blind replan would get wrong, re-firing a clamped immediate reminder on every resync, is exactly what the newlyTracked thread through ReplanAsync and ScheduleAsync prevents. Reading the clock through an injected TimeProvider (:30, used at :180) instead of DateTimeOffset.UtcNow puts the coordinator under the same fixed-clock testability as the planner; the Engagement UI wiring registers TimeProvider.System with TryAddSingleton because no UI head otherwise does (MMCA.ADC.Engagement.UI/DependencyInjection.cs:70-72).
        • +
        • Where it's used: injected as a concrete class by SessionBookmarkUIService (SessionBookmarkUIService.cs:29), which drives all three notify or resync members, and by the host's device-settings page (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/Pages/DeviceSettings.razor:7), which reads the settings (:70), writes them on change (:92, :98) and seeds its lead-time control from DefaultLeadMinutes (:64). [Rubric §14, Testability]: SessionReminderCoordinatorTests covers fourteen facts, one per branch above, including the two stale-sweep cases (:132, :158), that no live event cancels nothing (:179), that an unsupported host touches nothing (:226), that lookup failures never throw out of the coordinator (:240), that a failed catalog read leaves the existing schedule alone (:252), that a resync does not re-issue an immediate reminder already scheduled (:289), and that an immediate reminder names the real remaining time (:307).
        • +
        • Caveats / not-in-source: IsSupported (:46) has no consumer in the repo today, including the device-settings page its doc points at; the page relies on the internal no-ops instead. The class is also registered scoped while the state it manages is device-global: correctness rests on preferences being the single source of truth and on every mutation replanning from scratch, not on any one instance living long. Whether a scheduled notification actually fires is the platform's business, outside this file.

        SessionFeedback

        @@ -1783,23 +1783,24 @@

        SessionFeedback

    • What it is: the code-behind for the attendee session-feedback page (@page "/feedback/session/{SessionId}", SessionFeedback.razor:1). It checks three business preconditions before it will render the question form at all, then loads the dynamic question set, pre-fills any answers the attendee already submitted, and posts the whole form back in one atomic call.
    • -
    • Depends on: IQuestionLookupService, ISessionFeedbackUIService, IEntityService<TEntityDTO, TIdentifierType> (bound as IEntityService<SessionDTO, SessionIdentifierType>, SessionFeedback.razor.cs:22), IToastService, SessionDTO, QuestionDTO, SessionQuestionAnswerDTO, FeedbackAnswerModel, ModelValidation and DataAnnotationsModelValidator, SessionStatuses.IsEligible for the BR-49 check, plus the Parse<T> extension on DomainHelper and the IsNotFound() result extension from MMCA.Common.UI.Common. Externals: Blazor NavigationManager, MudBlazor MudForm / BreadcrumbItem, System.Globalization, and from the markup an injected IStringLocalizer<SessionFeedback> L (SessionFeedback.razor:6), the shared UnsavedChangesGuard component (SessionFeedback.razor:10-13) and ErrorSummary (SessionFeedback.razor:106-107).
    • -
    • Concept introduced, the precondition-gated dynamic form. [Rubric §18, UI Architecture] assesses component/logic separation: the markup lives in SessionFeedback.razor and the behavior in this partial class, so the page's flow is reviewable apart from its layout. [Rubric §24, Forms, Validation & UX Safety] is the load-bearing category here and it shows up three times. First, CheckPreconditions (SessionFeedback.razor.cs:141) refuses before rendering inputs, so when feedback is not permitted the attendee gets one sentence explaining why rather than a form that fails on submit. Second, validation is declarative and single-sourced: _validateAnswerText (:63) is one delegate built from FeedbackAnswerModel's annotations and handed to every free-text field, so the 4000-character rule is never written twice. Third, UnsavedChangesGuard IsDirty="_isDirty" wraps the page, so navigating away with unsaved edits prompts first. The dynamic part is the interesting one: the questions are data, not markup, so the render is a switch over question.QuestionType (SessionFeedback.razor:58-85) that picks a MudRating, an email MudTextField, or a three-line multiline MudTextField, and each field's Required/RequiredError comes from the DTO rather than from the page. [Rubric §19, State Management] covers the _answers working map; [Rubric §27, i18n] covers the fact that every visible string, error messages included, is an L["..."] lookup.
    • +
    • Depends on: IQuestionLookupService, ISessionFeedbackUIService, IEntityService<TEntityDTO, TIdentifierType> (bound as IEntityService<SessionDTO, SessionIdentifierType>, SessionFeedback.razor.cs:22), IToastService, SessionDTO, QuestionDTO, SessionQuestionAnswerDTO, FeedbackAnswerModel, ModelValidation and DataAnnotationsModelValidator, SessionStatuses.IsEligible for the BR-49 check, plus the Parse<T> extension on DomainHelper and the IsNotFound() result extension from MMCA.Common.UI.Common. Externals: Blazor NavigationManager, MudBlazor MudForm / BreadcrumbItem, System.Globalization, and from the markup an injected IStringLocalizer<SessionFeedback> L (SessionFeedback.razor:7), the shared UnsavedChangesGuard component (SessionFeedback.razor:11) and ErrorSummary (SessionFeedback.razor:109).
    • +
    • Concept introduced, the precondition-gated dynamic form. [Rubric §18, UI Architecture] assesses component/logic separation: the markup lives in SessionFeedback.razor and the behavior in this partial class, so the page's flow is reviewable apart from its layout. [Rubric §24, Forms, Validation & UX Safety] is the load-bearing category here and it shows up four times. First, CheckPreconditions (SessionFeedback.razor.cs:148) refuses before rendering inputs, so when feedback is not permitted the attendee gets one sentence explaining why rather than a form that fails on submit. Second, validation is declarative and single-sourced: _validateAnswerText (:70) is one delegate built from FeedbackAnswerModel's annotations and handed to every free-text field, so the 4000-character rule is never written twice. Third, a page-owned required check covers what MudForm cannot: MudRating is not a form input, so form validation never blocks an unanswered required rating, and the _missingRequired set (:61, doc :56-60) filled by FlagMissingRequiredAnswers (:207-217) does, for every question type, and drives the inline error on each question's field (SessionFeedback.razor:59-60). Fourth, UnsavedChangesGuard IsDirty="_isDirty" wraps the page, so navigating away with unsaved edits prompts first. The dynamic part is the interesting one: the questions are data, not markup, so the render is a switch over question.QuestionType?.ToUpperInvariant() (SessionFeedback.razor:61) that picks a MudRating (case "RATING", :63), an email MudTextField (case "EMAIL", :68), or a three-line multiline MudTextField, and each field's required flag and RequiredError come from the DTO rather than from the page. [Rubric §19, State Management] covers the _answers working map; [Rubric §27, i18n] covers the fact that every visible string, error messages included, is an L["..."] lookup.
    • Walkthrough (teaching order)
      • Injected services (SessionFeedback.razor.cs:20-24) and the [Parameter] string SessionId (:26) arrive from the route. Note the parameter is a string, not an id alias: the route template carries no :int constraint, so parsing is the page's job.
      • -
      • IsLoading / IsSubmitting (:32-33), HasExistingFeedback (:35) and SubmitButtonText (:37-48) drive the chrome: the button reads "Submitting" while a post is in flight, then "Update" or "Submit" depending on whether prior answers exist. The private state fields (:50-57) hold the load error, the precondition message, the session title, the loaded questions, the _answers map, the _existingAnswerIds map, the MudForm reference and the _isDirty flag. MarkDirty (:65) is the one-line handler every input's @bind-...:after calls.
      • -
      • OnInitialized (:68-72) builds _validateAnswerText once, by handing a throwaway FeedbackAnswerModel, the model => model.TextValue selector and a DataAnnotationsModelValidator(L) to ModelValidation.ForProperty. The strongly-typed ForProperty overload (MMCA.Common/Source/Presentation/MMCA.Common.UI/Validation/ModelValidation.cs:69) is the right one here because these fields have no For expression and the value being validated has not been written to a model yet; the localizer resolves the ErrorMessage resource key (ADR-027).
      • -
      • OnInitializedAsync (:74) builds the three-item breadcrumb trail, then loads the session through SessionService.GetByIdAsync(sessionId, false, _cts.Token) (:88). The failure branch (:89-97) splits the two reasons apart with sessionResult.IsNotFound(): a genuine 404 keeps the page's own "session not found" wording, anything else falls to the generic load failure.
      • -
      • CheckPreconditions(session) (:101) runs next, and a returned message stops the load before any questions are fetched (:102-106). Only then does it load the "Session" questions ordered by Sort (:109-116), seed one FeedbackAnswerModel per question (:119), and pre-fill from the server (:122-129). Every await passes _cts.Token; OperationCanceledException is swallowed as expected on disposal (:131-134), and IsLoading is cleared in finally (:135-138).
      • -
      • CheckPreconditions (:141) encodes three rules, each returning a localized message. BR-91 rejects service sessions (session.IsServiceSession, :144-147). BR-49 rejects any session SessionStatuses.IsEligible does not allow, an unset Status or "Accepted" (case-insensitive) are the only eligible values (:150-154), the same shared allow-list Conference applies elsewhere. BR-16 rejects unscheduled sessions, where StartsAt or EndsAt is null (:158-161), because session feedback is a time-gated action. Returning null means feedback is allowed.
      • -
      • PreFillExistingAnswers (:166) is defense in depth. Because feedback questions are shared across sessions, an answer belonging to a different session would still match by QuestionId, so the loop skips any answer whose SessionId does not equal the current one (:172-175) before mapping a rating (culture-invariant int.TryParse, :184-187) or a text value onto the model. It also records the server-assigned answer id in _existingAnswerIds (:182), which is what enables the per-question clear button.
      • -
      • SubmitFeedbackAsync (:196) validates the form and toasts a warning if it is invalid (:198-206), then projects the answered questions into a pending list of (QuestionId, AnswerValue) tuples, dropping blanks (:216-220). An empty list short-circuits: with nothing answered there is no form to send, so the submit stays a local no-op rather than a request the batch endpoint would refuse (:222-232). Otherwise it makes one call, FeedbackService.SubmitAnswersAsync(sessionId, pending, _cts.Token) (:226), which the interface documents as applied atomically server-side (IFeedbackUIService.cs:66-73). A failure therefore wrote nothing: the page toasts an error and returns with _isDirty still set, so the same Submit button retries. On success it clears _isDirty, toasts the count, then calls StateHasChanged() before NavigateBack() (:237-238); the inline comment (:236) explains why: the flush lets UnsavedChangesGuard's IsDirty parameter update before NavigateTo fires the LocationChanging handler, otherwise the guard would prompt over changes that were just saved.
      • -
      • DeleteAnswerAsync (:250) clears one previously-saved answer: it looks the answer id up in _existingAnswerIds, calls FeedbackService.DeleteAnswerAsync, removes the entry, resets the model's two members (:266-271), and clears _isDirty because the delete already committed server-side (:273-275).
      • -
      • ParseSessionId (:284-285) converts the route string with SessionId.Parse<SessionIdentifierType>(), the DomainHelper extension, rather than a hand-rolled parse. NavigateBack (:287-288) returns to the Conference session detail route. GetAnswerValue (:314) is the static bridge from a FeedbackAnswerModel back to a wire string, treating RatingValue > 0 as the only answered rating (:321-323).
      • -
      • Dispose(bool) / Dispose() (:292-312) implement the standard disposable pattern, cancelling and disposing _cts so in-flight loads stop when the attendee leaves.
      • -
      -
    • -
    • Why it's built this way: pushing the three business rules into one CheckPreconditions method keeps the render path binary (either a precondition message or the form) and makes each BR individually reviewable and individually testable. The single batch submit is the more consequential choice, and it is the one place this page diverges from EventFeedback: because the server applies a whole session form atomically, there is no partially-saved state to report to the attendee, so the failure path is a plain retry rather than a "3 of 5 saved" message. The cancellation-token discipline on every await and the StateHasChanged-before-navigate ordering are correctness, not style.
    • +
    • IsLoading / IsSubmitting (:32-33), HasExistingFeedback (:35) and SubmitButtonText (:37-48) drive the chrome: the button reads "Submitting" while a post is in flight, then "Update" or "Submit" depending on whether prior answers exist. The private state fields (:50-64) hold the load error, the precondition message, the session title, the loaded questions, the _answers map, the _missingRequired set, the _existingAnswerIds map, the MudForm reference and the _isDirty flag. MarkDirty (:72) is the one-line handler every input's @bind-...:after calls.
    • +
    • OnInitialized (:75-79) builds _validateAnswerText once, by handing a throwaway FeedbackAnswerModel, the model => model.TextValue selector and a DataAnnotationsModelValidator(L) to ModelValidation.ForProperty. The strongly-typed ForProperty overload (MMCA.Common/Source/Presentation/MMCA.Common.UI/Validation/ModelValidation.cs:69) is the right one here because these fields have no For expression and the value being validated has not been written to a model yet; the localizer resolves the ErrorMessage resource key (ADR-027).
    • +
    • OnInitializedAsync (:81) builds the three-item breadcrumb trail, then loads the session through SessionService.GetByIdAsync(sessionId, false, _cts.Token) (:95). The failure branch (:96-104) splits the two reasons apart with sessionResult.IsNotFound(): a genuine 404 keeps the page's own "session not found" wording, anything else falls to the generic load failure.
    • +
    • CheckPreconditions(session) (:108) runs next, and a returned message stops the load before any questions are fetched (:109-113). Only then does it load the "Session" questions ordered by Sort (:116-123), seed one FeedbackAnswerModel per question (:126), and pre-fill from the server (:129-136). Every await passes _cts.Token; OperationCanceledException is swallowed as expected on disposal (:138-141), and IsLoading is cleared in finally (:142-145).
    • +
    • CheckPreconditions (:148) encodes three rules, each returning a localized message. BR-91 rejects service sessions (session.IsServiceSession, :151-154). BR-49 rejects any session SessionStatuses.IsEligible does not allow, an unset Status or "Accepted" (case-insensitive) are the only eligible values (:157-160), the same shared allow-list Conference applies elsewhere. BR-16 rejects unscheduled sessions, where StartsAt or EndsAt is null (:162-167), because session feedback is a time-gated action. Returning null means feedback is allowed.
    • +
    • PreFillExistingAnswers (:172) is defense in depth. Because feedback questions are shared across sessions, an answer belonging to a different session would still match by QuestionId, so the loop skips any answer whose SessionId does not equal the current one (:178-181) before mapping a rating (culture-invariant int.TryParse, :190-194) or a text value onto the model. It also records the server-assigned answer id in _existingAnswerIds (:188), which is what enables the per-question clear button.
    • +
    • FlagMissingRequiredAnswers (:207-217) clears _missingRequired, adds the id of every question that is IsRequired and whose GetAnswerValue is blank, and returns whether any were found. Going through GetAnswerValue is what makes it type-agnostic: an unrated MudRating (rating 0) reads as blank exactly like an empty text field.
    • +
    • SubmitFeedbackAsync (:219) validates the form and toasts a warning if it is invalid (:221-229), then runs FlagMissingRequiredAnswers and toasts the same Snackbar.RequiredFields warning and returns when it reports a gap (:231-235), so an unanswered required rating, or an entirely empty submit on a form with a required question, never reaches the server. It then projects the answered questions into a pending list of (QuestionId, AnswerValue) tuples, dropping blanks (:245-249). An empty list (possible only when every question is optional) short-circuits: with nothing answered there is no form to send, so the submit stays a local no-op rather than a request the batch endpoint would refuse (:251-261). Otherwise it makes one call, FeedbackService.SubmitAnswersAsync(sessionId, pending, _cts.Token) (:255), which the interface documents as applied atomically server-side (IFeedbackUIService.cs:66-73). A failure therefore wrote nothing: the page toasts an error and returns with _isDirty still set, so the same Submit button retries. On success it clears _isDirty, toasts the count, then calls StateHasChanged() before NavigateBack() (:266-267); the inline comment (:265) explains why: the flush lets UnsavedChangesGuard's IsDirty parameter update before NavigateTo fires the LocationChanging handler, otherwise the guard would prompt over changes that were just saved.
    • +
    • DeleteAnswerAsync (:279) clears one previously-saved answer: it looks the answer id up in _existingAnswerIds, calls FeedbackService.DeleteAnswerAsync, removes the entry, resets the model's two members (:296-300), and clears _isDirty because the delete already committed server-side (:302-304).
    • +
    • ParseSessionId (:313-314) converts the route string with SessionId.Parse<SessionIdentifierType>(), the DomainHelper extension, rather than a hand-rolled parse. NavigateBack (:316-317) returns to the Conference session detail route. GetAnswerValue (:343) is the static bridge from a FeedbackAnswerModel back to a wire string, treating RatingValue > 0 as the only answered rating (:350-352).
    • +
    • Dispose(bool) / Dispose() (:321-341) implement the standard disposable pattern, cancelling and disposing _cts so in-flight loads stop when the attendee leaves.
    • +
    + +
  • Why it's built this way: pushing the three business rules into one CheckPreconditions method keeps the render path binary (either a precondition message or the form) and makes each BR individually reviewable and individually testable. The single batch submit is the more consequential choice, and EventFeedback makes the same one: because the server applies a whole session form atomically, there is no partially-saved state to report to the attendee, so the failure path is a plain retry rather than a "3 of 5 saved" message. The cancellation-token discipline on every await and the StateHasChanged-before-navigate ordering are correctness, not style.
  • Where it's used: rendered at /feedback/session/{SessionId}, reached from the public session detail page's SubmitFeedback (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Sessions/PublicSessionDetail.razor.cs:233). The same route is also expressed as EngagementRoutePaths.SessionFeedback(sessionId) (EngagementRoutePaths.cs:18). Its dependencies are registered by the Engagement UI DependencyInjection. Component-tested by SessionFeedbackTests.
  • Caveats / not-in-source: the markup, the resource strings and UnsavedChangesGuard's own behavior live outside this file. The BR numbers in the comments are the page's own claim about which business rule it implements; the authoritative statements live in the ADC specifications guide, not in this file.
  • @@ -1810,18 +1811,19 @@

    EventFeedback

    • What it is: the code-behind for the attendee event-feedback page (@page "/feedback/event/{EventId}", EventFeedback.razor:1). It loads the event name and its dynamic question set, pre-fills any answers the attendee already submitted, and posts the answered questions back as a single atomic upsert (BR-107, per the class doc comment at EventFeedback.razor.cs:15-18).
    • -
    • Depends on: IQuestionLookupService, IEventFeedbackUIService, IEventLookupService, IToastService, QuestionDTO, EventQuestionAnswerDTO, FeedbackAnswerModel, ModelValidation, DataAnnotationsModelValidator and DomainHelper (the Parse<T> extension). Externals: Blazor NavigationManager, MudBlazor MudForm / BreadcrumbItem, System.Globalization, and from the markup an injected IStringLocalizer<EventFeedback> L (EventFeedback.razor:6) plus the shared UnsavedChangesGuard (EventFeedback.razor:10-13) and ErrorSummary (EventFeedback.razor:95-96) components.
    • -
    • Concept: the dynamic feedback form (the _answers map, the single validation delegate, the UnsavedChangesGuard pairing and the StateHasChanged-before-navigate ordering) is taught on SessionFeedback and is identical here. The submit strategy now matches the session page too: SubmitFeedbackAsync calls FeedbackService.SubmitAnswersAsync once with the whole answered set (EventFeedback.razor.cs:196) instead of looping a per-question call, so the server applies the batch atomically and there is nothing partial left to report. [Rubric §24, Forms, Validation & UX Safety] still applies: an empty submit is a local no-op rather than a request the batch endpoint would refuse as empty (:190-202), and a failed submit leaves _isDirty set so the same Submit button retries.
    • +
    • Depends on: IQuestionLookupService, IEventFeedbackUIService, IEventLookupService, IToastService, QuestionDTO, EventQuestionAnswerDTO, FeedbackAnswerModel, ModelValidation, DataAnnotationsModelValidator and DomainHelper (the Parse<T> extension). Externals: Blazor NavigationManager, MudBlazor MudForm / BreadcrumbItem, System.Globalization, and from the markup an injected IStringLocalizer<EventFeedback> L (EventFeedback.razor:7) plus the shared UnsavedChangesGuard (EventFeedback.razor:11) and ErrorSummary (EventFeedback.razor:98) components.
    • +
    • Concept: the dynamic feedback form (the _answers map, the single validation delegate, the _missingRequired required-answer gate, the UnsavedChangesGuard pairing and the StateHasChanged-before-navigate ordering) is taught on SessionFeedback and is identical here. The submit strategy matches the session page too: SubmitFeedbackAsync calls FeedbackService.SubmitAnswersAsync once with the whole answered set (EventFeedback.razor.cs:226), so the server applies the batch atomically and there is nothing partial left to report. [Rubric §24, Forms, Validation & UX Safety] still applies: an unanswered required question, rating included, is caught locally before any request (:202-206), an empty submit on an all-optional form is a local no-op rather than a request the batch endpoint would refuse as empty (:222-232), and a failed submit leaves _isDirty set so the same Submit button retries.
    • Walkthrough of what differs from SessionFeedback
        -
      • There is no precondition gate and no _preconditionMessage: an event either loads or it does not. OnInitializedAsync (EventFeedback.razor.cs:74) resolves the event name from EventLookup.GetAllAsync (:86), setting a localized _loadError when the lookup itself fails (:88) or when the id is absent from it (:99). Everything after that (questions ordered by Sort at :111, one FeedbackAnswerModel per question at :114, the pre-fill at :117-124) matches the session page step for step.
      • -
      • PreFillExistingAnswers (:136) applies the same cross-entity guard, skipping answers whose EventId does not match the current event (:142-145).
      • -
      • SubmitFeedbackAsync (:166) validates the form (:168-176), then projects the answered questions into pending, dropping blanks (:186-190). If pending is non-empty it posts the whole set in one call to SubmitAnswersAsync (:194-202); on failure it toasts Snackbar.SubmitFailed and returns with _isDirty still set (:196-199), otherwise it clears _isDirty and toasts Snackbar.FeedbackSubmitted with pending.Count (:204-205). This is the same atomic-batch shape as the session page; the only remaining difference is that the event page has no batch endpoint to skip when nothing is answered other than checking pending.Count > 0 itself, since there is no precondition gate to short-circuit first.
      • -
      • ParseEventId (:253-254) uses EventId.Parse<EventIdentifierType>(); NavigateBack (:256-257) returns to the Conference event detail route.
      • -
      • DeleteAnswerAsync (:220), GetAnswerValue (:283) and the disposable pattern (:261-281) are the session page's, unchanged in mechanism.
      • +
      • There is no precondition gate and no _preconditionMessage: an event either loads or it does not. OnInitializedAsync (EventFeedback.razor.cs:81) resolves the event name from EventLookup.GetAllAsync (:93), setting a localized _loadError when the lookup itself fails (:95) or when the id is absent from it (:106). Everything after that (questions ordered by Sort at :118, one FeedbackAnswerModel per question at :121, the pre-fill at :124-131) matches the session page step for step.
      • +
      • PreFillExistingAnswers (:143) applies the same cross-entity guard, skipping answers whose EventId does not match the current event (:149-152).
      • +
      • FlagMissingRequiredAnswers (:178-188) and its _missingRequired set (:61, bound to each question's field at EventFeedback.razor:48-49) are the session page's, line for line.
      • +
      • SubmitFeedbackAsync (:190) validates the form (:192-200), then flags unanswered required questions and returns with a Snackbar.RequiredFields warning if any (:202-206), then projects the answered questions into pending, dropping blanks (:216-220). If pending is non-empty it posts the whole set in one call to SubmitAnswersAsync (:224-232); on failure it toasts Snackbar.SubmitFailed and returns with _isDirty still set (:227-231), otherwise it clears _isDirty and toasts Snackbar.FeedbackSubmitted with pending.Count (:234-235). This is the same atomic-batch shape as the session page.
      • +
      • ParseEventId (:283-284) uses EventId.Parse<EventIdentifierType>(); NavigateBack (:286-287) returns to the Conference event detail route.
      • +
      • DeleteAnswerAsync (:250), GetAnswerValue (:313) and the disposable pattern (:289-311) are the session page's, unchanged in mechanism.
    • Why it's built this way: the page owns orchestration only, all I/O goes through injected UI service interfaces, which keeps it testable under bUnit and swappable per host. Moving to a single batch SubmitAnswersAsync call removes the partial-save case entirely: a refusal now means nothing was written, so the retry story is a plain resend rather than a per-question accounting. Sharing the rest of the form shape with SessionFeedback by convention rather than through a base class keeps each page self-contained and independently editable.
    • -
    • Where it's used: rendered at /feedback/event/{EventId}, reached from the public event detail page's SubmitFeedback (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventDetail.razor.cs:183). The same route is also expressed as EngagementRoutePaths.EventFeedback(eventId) (EngagementRoutePaths.cs:17). Its dependencies are registered by the Engagement UI DependencyInjection. Component-tested by EventFeedbackTests.
    • +
    • Where it's used: rendered at /feedback/event/{EventId}, reached from the public event detail page's SubmitFeedback (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventDetail.razor.cs:171). The same route is also expressed as EngagementRoutePaths.EventFeedback(eventId) (EngagementRoutePaths.cs:17). Its dependencies are registered by the Engagement UI DependencyInjection. Component-tested by EventFeedbackTests.
    • Caveats / not-in-source: the markup, the resource strings and UnsavedChangesGuard's own behavior live outside this file; this section covers the code-behind plus the bindings its members exist to serve.

    AttendeeRow

    @@ -1861,7 +1863,7 @@

    SessionInfo

  • Concept introduced, the cross-module display projection. Engagement owns no session table (database-per-service, ADR-006), so it cannot join to a session title: it fetches the fields it needs over HTTP and holds them in this small record, a deliberately thinner shape than Conference's own SessionDTO. [Rubric §9, API & Contract Design] assesses how a consumer models another service's data at its boundary; declaring five fields here means a change to unrelated SessionDTO members never ripples into Engagement. [Rubric §7, Microservices Readiness] is the same decision seen from the extraction side, and it is the identical posture the neighbouring AttendeeSummary takes against Identity: the consumer redeclares the view it renders rather than depending on the producer's assembly.
  • Walkthrough (ISessionLookupService.cs:9-14): five positional members, (SessionIdentifierType Id, string Title, DateTime? StartsAt, DateTime? EndsAt, EventIdentifierType EventId). StartsAt and EndsAt are nullable because a session imported from the schedule source may not yet have times assigned, so every consumer has to tolerate an unscheduled session. It is a plain public record, not sealed.
  • Why it's built this way: a compact record lets SessionLookupService build a whole-catalog dictionary cheaply, and value equality plus init-only members mean a page can hold, compare and sort the rows client-side without a second fetch per row.
  • -
  • Where it's used: returned by ISessionLookupService (a catalog keyed by id, or one row), constructed by SessionLookupService from SessionDTO payloads (SessionLookupService.cs:38-39, :64-65); consumed by CheckInScan as its session list (CheckInScan.razor.cs:49), by SessionReminderPlanner as the bookmarked-session input it schedules against (SessionReminderPlanner.cs:50), and by the live-session pages (Pages/SessionLive/SessionLive.razor.cs, Pages/SessionLive/PresenterView.razor.cs).
  • +
  • Where it's used: returned by ISessionLookupService (a catalog keyed by id, or one row), constructed by SessionLookupService from SessionDTO payloads (SessionLookupService.cs:39-40, :64-65); consumed by CheckInScan as its session list (CheckInScan.razor.cs:49), by SessionReminderPlanner as the bookmarked-session input it schedules against (SessionReminderPlanner.cs:58), and by the live-session pages (Pages/SessionLive/SessionLive.razor.cs, Pages/SessionLive/PresenterView.razor.cs).
  • Caveats / not-in-source: nothing here constrains Title to non-empty or the times to a sane order; both come straight off the Conference payload, and the nullable times are the only shape signal that an unscheduled session exists.

  • @@ -1903,15 +1905,15 @@

    SessionLookupService

    • What it is: the HTTP implementation of ISessionLookupService. It fetches sessions from the Conference-owned sessions resource and builds either a session-keyed lookup of SessionInfo projections or one row.
    • -
    • Depends on: ISessionLookupService, SessionInfo, SessionDTO (Conference shared), PagedCollectionResult<T>, HttpResultExecutor, ProblemDetailsResultReader. Externals: IHttpClientFactory (SessionLookupService.cs:13), System.Globalization. Note what is absent: unlike its two neighbours in this folder, it does not derive from AuthenticatedServiceBase and takes no token storage, because it reads the plain "APIClient" (:22, :54) against a public, output-cached listing.
    • -
    • Concept introduced, the single-page catalog transfer with a documented cap. GetAllAsync fetches sessions?includeFKs=false&includeChildren=false (:27) with no pagination loop at all, and the inline comment (:24-25) is what makes that safe to read: the base /sessions endpoint has no pageSize parameter, it always serves a single page capped at MaxPageSize (500), which comfortably covers a conference's session catalog. [Rubric §12, Performance & Scalability] assesses transfer sizing; here the includeFKs=false&includeChildren=false query trims the payload on the wire and the five-field SessionInfo projection trims it on the heap, so the whole-catalog read the interface sanctions stays cheap. [Rubric §9, API & Contract Design]: the class consumes Conference through the wire contract only, referencing SessionDTO and nothing else from the other module.
    • +
    • Depends on: ISessionLookupService, SessionInfo, SessionDTO (Conference shared), PagedCollectionResult<T>, HttpResultExecutor, ProblemDetailsResultReader, IdempotentReadRetry. Externals: IHttpClientFactory (SessionLookupService.cs:13), System.Globalization. Note what is absent: unlike its two neighbours in this folder, it does not derive from AuthenticatedServiceBase and takes no token storage, because it reads the plain "APIClient" (:22, :55) against a public, output-cached listing. It still gets the base class's retry, through the static IdempotentReadRetry rather than through inheritance.
    • +
    • Concept introduced, the single-page catalog transfer with a documented cap. GetAllAsync fetches sessions?includeFKs=false&includeChildren=false (:28) with no pagination loop at all, and the inline comment (:24-25) is what makes that safe to read: the base /sessions endpoint has no pageSize parameter, it always serves a single page capped at MaxPageSize (500), which comfortably covers a conference's session catalog. [Rubric §12, Performance & Scalability] assesses transfer sizing; here the includeFKs=false&includeChildren=false query trims the payload on the wire and the five-field SessionInfo projection trims it on the heap, so the whole-catalog read the interface sanctions stays cheap. [Rubric §9, API & Contract Design]: the class consumes Conference through the wire contract only, referencing SessionDTO and nothing else from the other module.
    • Walkthrough
        -
      • GetAllAsync (:17-45) runs inside HttpResultExecutor.ExecuteAsync (:19), reads the response as a PagedCollectionResult<SessionDTO> through ProblemDetailsResultReader.ReadAsync (:30-31), then read.Map(...) (:33) fills a Dictionary<SessionIdentifierType, SessionInfo> by looping page.Items (:36-40) and widens it to IReadOnlyDictionary (:42). Because Map runs on success only, no null guard on the page is needed: a failed read short-circuits carrying the API's own error.
      • -
      • GetByIdAsync (:48-67) GETs sessions/{sessionId} with an invariant-culture interpolated URI (:57), reads one SessionDTO (:62) and maps it into a SessionInfo (:64-65). The comment there (:60-61) records the contract from the page's side: a missing session answers 404, which arrives as a NotFound failure rather than the old null, and the pages render exactly the same "session unavailable" state for it.
      • +
      • GetAllAsync (:17-46) runs inside HttpResultExecutor.ExecuteAsync (:19), sends the GET through IdempotentReadRetry.GetAsync(httpClient, ...) (:26-29), reads the response as a PagedCollectionResult<SessionDTO> through ProblemDetailsResultReader.ReadAsync (:31-32), then read.Map(...) (:34) fills a Dictionary<SessionIdentifierType, SessionInfo> by looping page.Items (:37-41) and widens it to IReadOnlyDictionary (:43). Because Map runs on success only, no null guard on the page is needed: a failed read short-circuits carrying the API's own error.
      • +
      • GetByIdAsync (:49-69) GETs sessions/{sessionId} through the same IdempotentReadRetry.GetAsync (:57-60) with an invariant-culture interpolated URI (:59), reads one SessionDTO (:64) and maps it into a SessionInfo (:66-67). The comment there (:62-63) records the contract from the page's side: a missing session answers 404, which arrives as a NotFound failure rather than the old null, and the pages render exactly the same "session unavailable" state for it.
    • -
    • Why it's built this way: building the dictionary once lets a page label many bookmark or attendance rows with O(1) lookups, while the per-id method serves single-session pages without paying the catalog transfer: exactly the fast and slow split ISessionLookupService prescribes. Neither read retries, unlike the token-bearing services in this folder, because both are cheap idempotent GETs against a cached public endpoint.
    • -
    • Where it's used: registered scoped as ISessionLookupService (MMCA.ADC.Engagement.UI/DependencyInjection.cs:54). It is also the model the sibling AttendeeLookupService names in its class doc (AttendeeLookupService.cs:12-15) for the search-through, lookup-from-snapshot split. Covered by SessionLookupServiceTests (MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.UI.Tests/Services/Lookups/SessionLookupServiceTests.cs).
    • +
    • Why it's built this way: building the dictionary once lets a page label many bookmark or attendance rows with O(1) lookups, while the per-id method serves single-session pages without paying the catalog transfer: exactly the fast and slow split ISessionLookupService prescribes. Both reads retry exactly as the token-bearing services in this folder do: IdempotentReadRetry hands out the same policy object AuthenticatedServiceBase uses (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/IdempotentReadRetry.cs:24), re-sending on an HttpRequestException, a 5xx other than 501/505, 408 or 429, up to three times with 2s, 4s, 8s backoff plus jitter (IdempotentReadRetry.cs:10-12). Re-sending is safe here only because both calls are GETs, which is why a GET is the helper's sole entry point (IdempotentReadRetry.cs:35-44).
    • +
    • Where it's used: registered scoped as ISessionLookupService (MMCA.ADC.Engagement.UI/DependencyInjection.cs:54). It is also the model the sibling AttendeeLookupService names in its class doc (AttendeeLookupService.cs:12-15) for the search-through, lookup-from-snapshot split. Covered by SessionLookupServiceTests (MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.UI.Tests/Services/Lookups/SessionLookupServiceTests.cs), and named as one of the anonymous lookup callers in the class doc of IdempotentReadRetryTests (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Services/Api/IdempotentReadRetryTests.cs:12).
    • Caveats / not-in-source: the 500-row cap is a server-side fact recorded in a comment here, not a value this file reads or asserts. If a conference ever exceeded it, GetAllAsync would silently return a partial catalog, and nothing in this class detects or reports the truncation.

    @@ -2074,7 +2076,7 @@

    LiveBroadcastPatch

  • Why it's built this way: internal static with no injected state is the honest shape. There is nothing to configure and nothing to mock: the input is a list and a string, the output is a bool, and the three pages that use it call it identically. Using with expressions on the record DTOs rather than mutating them keeps the list elements immutable values, which is what lets a page compare or re-render without worrying that a background patch aliased an object it already handed to a component.
  • -
  • Where it's used: by the three conference-day pages, each guarding the call with the channel's event name so an unrelated broadcast never reaches it. SessionLive routes both events through one switch and reloads on null (SessionLive.razor.cs:187-194, patching at :190 and :192 with preserveMyVote: true); PresenterView handles them as two early-return branches with preserveMyVote: false (PresenterView.razor.cs:121, :127); HappeningNow patches only poll results and falls back to ReloadPollsAsync (HappeningNow.razor.cs:140, :145).
  • +
  • Where it's used: by the three conference-day pages, each guarding the call with the channel's event name so an unrelated broadcast never reaches it. SessionLive routes both events through one switch and reloads on null (SessionLive.razor.cs:187-194, patching at :190 and :192 with preserveMyVote: true); PresenterView handles them as two early-return branches with preserveMyVote: false (PresenterView.razor.cs:121, :127); HappeningNow patches only poll results and falls back to ReloadPollsAsync (HappeningNow.razor.cs:141, :145).
  • Caveats / not-in-source: the class is internal, so nothing outside MMCA.ADC.Engagement.UI can call it, and no dedicated test class for it exists in the repository. Its behavior is exercised only through the pages that call it.

  • @@ -2112,13 +2114,13 @@

    LiveChannelSubscription

  • Concept introduced, the component-owned resource handle. [Rubric §19, State Management] assesses where transient client state lives and who is responsible for tearing it down. The remarks block (:11-14) is the whole design in three sentences: this is not a DI service. A page news one up as a field, so the handle's lifetime is the component's, nothing has to be registered or scoped, and disposal is a no-op for a handle that never joined. Compare that with the alternative of a scoped service tracking channel membership: it would outlive the component and would have to be told when the component went away. Here the component's own DisposeAsync is the only signal needed. [Rubric §15, Best Practices & Code Quality]: two pages held the identical field pair and repeated the same three-step join and two-step leave; folding that into one type means a change to the join protocol happens once.
  • Walkthrough
    • The three fields (:17-19) are the hub service, the handler subscription token, and the channel key. All three are nullable and all three are set together.
    • -
    • IsJoined (:25) reports simply whether _channelKey is non-null. Its doc (:21-24) names the exact job it does: the pages call it as the already-joined guard inside OnAfterRenderAsync, which is deliberately not firstRender-gated (BR-229 / BR-238). See HappeningNow at HappeningNow.razor.cs:114 and SessionLive at SessionLive.razor.cs:137 for the guard in place.
    • +
    • IsJoined (:25) reports simply whether _channelKey is non-null. Its doc (:21-24) names the exact job it does: the pages call it as the already-joined guard inside OnAfterRenderAsync, which is deliberately not firstRender-gated (BR-229 / BR-238). See HappeningNow at HappeningNow.razor.cs:115 and SessionLive at SessionLive.razor.cs:137 for the guard in place.
    • JoinAsync (:35-51) null-checks the hub service (:40), returns early when a key is already recorded so a second call is a no-op (:42-45), then assigns all three fields and subscribes the handler through OnChannelEvent (:47-49) before awaiting JoinChannelAsync (:50). The ordering is the load-bearing part and the doc says why (:28-30): the key is recorded before the join is awaited, so IsJoined already answers true for any render that interleaves with the in-flight join. Without that, a render landing mid-join would see false and start a second join.
    • DisposeAsync (:54-63) disposes the handler subscription with the null-conditional (:56), leaves the channel only when both the service and the key are present (:57-60), and calls GC.SuppressFinalize (:62). A handle that never joined therefore disposes cleanly with no hub traffic.
  • Why it's built this way: real-time listening on a Blazor page is a resource with a strict pairing (subscribe and join on the way in, unsubscribe and leave on the way out) that the render loop can interleave with. Making the handle a plain field with an idempotent join and an idempotent dispose puts both halves in one place a reader can check at a glance.
  • -
  • Where it's used: HappeningNow holds one for the event channel (HappeningNow.razor.cs:52, joined at :124-127 via LivePollChannel.ForEvent, disposed at :276) and SessionLive holds one for the session channel (SessionLive.razor.cs:57, joined at :143 via LivePollChannel.ForSession, disposed at :329). Both guard the join with RendererInfo.IsInteractive as well, which keeps the prerender pass and the bUnit suite off the hub (HappeningNow.razor.cs:110-114).
  • +
  • Where it's used: HappeningNow holds one for the event channel (HappeningNow.razor.cs:53, joined at :124-127 via LivePollChannel.ForEvent, disposed at :276) and SessionLive holds one for the session channel (SessionLive.razor.cs:57, joined at :143 via LivePollChannel.ForSession, disposed at :329). Both guard the join with RendererInfo.IsInteractive as well, which keeps the prerender pass and the bUnit suite off the hub (HappeningNow.razor.cs:111-115).
  • Caveats / not-in-source: the class is not thread-safe. JoinAsync reads and writes _channelKey around an await with no lock, which holds on the single-threaded Blazor circuit context the pages run on and is not written to be shared beyond it. There is also no LeaveAsync: leaving is disposal, so a page that wanted to switch channels would need a new handle.

  • @@ -2157,7 +2159,7 @@

    CreateBookmarkHandler

  • Walkthrough: five constructor dependencies (CreateBookmarkHandler.cs:16-21), then one HandleAsync (:26-89).
    • Cross-module eligibility first (:30-33): ValidateSessionForBookmarkAsync is a Conference contract, satisfied in-process in the monolith and by a gRPC client when the modules run as separate services (ADR-007). This is the reciprocal half of the bidirectional pair: Conference calls Engagement's BookmarkCountService in the other direction, which is exactly why the AppHost deliberately omits a reciprocal WaitFor.
    • BR-21 duplicate check (:37-48) via ExistsAsync, returning Error.Conflict with code "UserSessionBookmark.Duplicate".
    • -
    • BR-135 revival lookup (:50-55): one call to FindIncludingDeletedAsync with asTracking: true, which returns the matching rows already partitioned into (Active, SoftDeleted) and lets the handler discard the active half with _. The framework calls this "the resurrection read" and documents both halves of the contract (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:196-215): dropping the query filter means "include soft-deleted rows" and nothing more (the tenant filter stays in force), and asTracking: true is required because the row is about to be mutated rather than inserted, since an untracked source would leave the reactivation silently unsaved.
    • +
    • BR-135 revival lookup (:50-55): one call to FindIncludingDeletedAsync with asTracking: true, which returns the matching rows already partitioned into (Active, SoftDeleted) and lets the handler discard the active half with _. The framework calls this "the resurrection read" and documents both halves of the contract (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:198-217): dropping the query filter means "include soft-deleted rows" and nothing more (the tenant filter stays in force), and asTracking: true is required because the row is about to be mutated rather than inserted, since an untracked source would leave the reactivation silently unsaved.
    • The decision itself is delegated to BookmarkManagementDomainService (:57-60): BookmarkManagementDomainService.CreateOrReactivate(deletedBookmark, ...) is called as a static method, not through an injected instance, and returns either a fresh aggregate or the revived one; the handler only decides persistence: AddAsync runs solely when there was no deleted row to revive (:62-66).
    • The save is wrapped in a filtered catch (:68-84). uniqueConstraintViolationDetector.IsUniqueConstraintViolation(exception) recognizes the unique index on (UserId, SessionId) firing, logs at Debug, and returns the SAME conflict error the pre-check produces (:79-83), so a lost insert race reads to the caller as an ordinary 409 instead of a 500. Note the detector is INJECTED rather than a static helper, which is what keeps the provider-specific error classification swappable per database engine.
    • Success logs at Information and returns the mapped DTO (:86-88). Both log lines are [LoggerMessage] partials (:91-95), so the message templates are compiled rather than formatted per call.
    • @@ -2172,9 +2174,9 @@

      LiveEventService

    • What it is: the implementation of ILiveEventUIService: it fetches the currently-live-or-next published event from the Conference API and computes its live window into a LiveEventContext, degrading to null when nothing is live or the API is down.
    • -
    • Depends on: ILiveEventUIService, CurrentEventSelector (the shared selection plus window math), EventDTO and PagedCollectionResult<T>, LiveEventContext, and IHttpClientFactory (LiveEventService.cs:14). It uses the plain "APIClient" (:21), not the authenticated base: the published-events read is public.
    • +
    • Depends on: ILiveEventUIService, CurrentEventSelector (the shared selection plus window math), EventDTO and PagedCollectionResult<T>, LiveEventContext, and the two primary-constructor dependencies IHttpClientFactory and TimeProvider (LiveEventService.cs:14). It uses the plain "APIClient" (:21), not the authenticated base: the published-events read is public.
    • Concept introduced, fail-soft resolution: absence and failure both become null. This is the one live-layer service that has not moved to the Result shape, and the choice is deliberate rather than an omission: the type doc states that "API failures degrade to null so the live layer simply stays dormant" (LiveEventService.cs:11-12), and the only consumer behavior for both cases is "render nothing". [Rubric §29, Resilience & Business Continuity] assesses graceful degradation under a dependency outage; here an unavailable Conference API turns the live layer off instead of erroring a page. [Rubric §12, Performance & Scalability] applies because the same public, output-cached events read backs this call. The trade-off worth naming: a page cannot tell an outage from "the conference has not started", so no diagnostic message is possible at this boundary.
    • -
    • Walkthrough: GetCurrentEventAsync (:17) opens a try (:19), creates the "APIClient" (:21), and fetches events?includeFKs=false&includeChildren=false as PagedCollectionResult<EventDTO> via GetFromJsonAsync (:23-25). It hands CurrentEventSelector.SelectCurrentOrNext the published subset with a null-safe wrapper?.Items?.Where(e => e.IsPublished) ?? [] (:28), accessor lambdas for start date, end date, and time zone (:29-31), and DateTime.UtcNow (:32); a null selection returns null (:33-36). On a hit it computes (startUtc, endUtc) from CurrentEventSelector.GetLiveWindowUtc(StartDate, EndDate, TimeZone) (:38-39) and constructs the LiveEventContext from the event id, name, time zone, and window (:41-46). A thrown HttpRequestException is caught (:48) and also returns null, with a comment recording that the live layer stays dormant while the API is unavailable (:50-51).
    • +
    • Walkthrough: GetCurrentEventAsync (:17) opens a try (:19), creates the "APIClient" (:21), and fetches events?includeFKs=false&includeChildren=false as PagedCollectionResult<EventDTO> via GetFromJsonAsync (:23-25). It hands CurrentEventSelector.SelectCurrentOrNext the published subset with a null-safe wrapper?.Items?.Where(e => e.IsPublished) ?? [] (:28), accessor lambdas for start date, end date, and time zone (:29-31), and the injected clock's timeProvider.GetUtcNow().UtcDateTime (:32), so a test can pin "now" with a fake TimeProvider instead of depending on the wall clock; a null selection returns null (:33-36). On a hit it computes (startUtc, endUtc) from CurrentEventSelector.GetLiveWindowUtc(StartDate, EndDate, TimeZone) (:38-39) and constructs the LiveEventContext from the event id, name, time zone, and window (:41-46). A thrown HttpRequestException is caught (:48) and also returns null, with a comment recording that the live layer stays dormant while the API is unavailable (:50-51).
    • Why it's built this way: delegating both "which event" and "what window" to CurrentEventSelector means the UI and the API share one implementation of the conference-day math, which is what lets LiveEventContext claim to mirror the backend window exactly. The two null paths keep the ambient listener silent whenever there is nothing to show.
    • Caveats / not-in-source: the catch covers HttpRequestException only (:48). A malformed JSON body would surface a JsonException instead, and how that propagates is not handled here; nothing in this file states the intended behavior for it.
    • Where it's used: registered as ILiveEventUIService; consumed by HappeningNow, the ambient LiveEventListener, CheckInScan, and OrganizerAttendance.
    • @@ -2311,8 +2313,8 @@

      SelfHttpWarmupTask

    • RequireSuccessStatusCode => false (:49), the interesting override. The controller requires an authenticated caller, so the unauthenticated self-request is refused by design. The XML comment (:44-48) makes the argument: the refusal still traverses Kestrel, routing, authentication, and the whole middleware pipeline, which is exactly where the cold-start JIT cost lives, so treating a 401 as a failure would log a spurious warning on every startup.
    -
  • Why it's built this way: the class doc (SelfHttpWarmupTask.cs:9) draws the contrast with Conference's warm-up, which also populates an output cache. This service caches nothing (its base output-cache policy is NoCache, MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:152), so the task buys pipeline JIT only, and it says so rather than pretending to prime a cache it cannot reach. The Identity service ships a same-named task with the same posture over its own hot read (SelfHttpWarmupTask).
  • -
  • Where it's used: registered with services.AddWarmupTask<SelfHttpWarmupTask>() in the Engagement service host (Program.cs:158), where the surrounding comment (Program.cs:154-157) repeats the rationale for an operator reading the host file.
  • +
  • Why it's built this way: the class doc (SelfHttpWarmupTask.cs:9) draws the contrast with Conference's warm-up, which also populates an output cache. This service caches nothing (its base output-cache policy is NoCache, MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:151), so the task buys pipeline JIT only, and it says so rather than pretending to prime a cache it cannot reach. The Identity service ships a same-named task with the same posture over its own hot read (SelfHttpWarmupTask).
  • +
  • Where it's used: registered with services.AddWarmupTask<SelfHttpWarmupTask>() in the Engagement service host (Program.cs:157), where the surrounding comment (Program.cs:153-156) repeats the rationale for an operator reading the host file.
  • Caveats / not-in-source: the base type decides how the bound address is discovered and whether the run is skipped under the Testing environment; this file supplies only the paths and the status-code policy.
  • UserSessionBookmarkChanged

    @@ -2347,7 +2349,7 @@

    CurrentEventNotificationScopeProv
  • GetCurrentScopeDisplayNameAsync(ct) (:104-116) is the optional half of the contract (a default interface method, INotificationScopeProvider.cs:39): it returns the current event's name for a send surface to caption its target (:108-109), and fails closed the way that member defines it, by returning null so a missing caption hides information rather than stating the wrong audience (:113-114, logged at Debug via :137-138).
  • -
  • Why it's built this way: the caching is not premature. Both notification HTTP services ask for the scope on every call (NotificationInboxService.cs:180, PushNotificationService.cs:34) and NotificationBell polls the unread count on a PeriodicTimer whose default interval is 30 seconds (NotificationBell.razor.cs:92, MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/Settings/NotificationBellOptions.cs:22), so an uncached provider would put an events fetch behind every poll. [Rubric §12, Performance & Scalability] and [Rubric §31, Cost/FinOps] both land on that arithmetic. [Rubric §13, Observability & Operability]: the three [LoggerMessage] methods are source-generated and each message states what the operator loses, not just that something failed. The registration is the other deliberate detail: services.AddScoped<INotificationScopeProvider, CurrentEventNotificationScopeProvider>() (MMCA.ADC.Engagement.UI/DependencyInjection.cs:73) uses plain AddScoped so it overrides the NullNotificationScopeProvider that AddNotificationUI registers with TryAddScoped (MMCA.Common/Source/Presentation/MMCA.Common.UI/Notifications/DependencyInjection.cs:24), which keeps the two calls order-independent (DependencyInjection.cs:61-62); the line above it (:65) has to TryAddSingleton(TimeProvider.System) because no UI head registers a TimeProvider (the heads never call AddInfrastructure, :63-64).
  • +
  • Why it's built this way: the caching is not premature. Both notification HTTP services ask for the scope on every call (NotificationInboxService.cs:180, PushNotificationService.cs:34) and NotificationBell polls the unread count on a PeriodicTimer whose default interval is 30 seconds (NotificationBell.razor.cs:103, MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/Settings/NotificationBellOptions.cs:23), so an uncached provider would put an events fetch behind every poll. [Rubric §12, Performance & Scalability] and [Rubric §31, Cost/FinOps] both land on that arithmetic. [Rubric §13, Observability & Operability]: the three [LoggerMessage] methods are source-generated and each message states what the operator loses, not just that something failed. The registration is the other deliberate detail: services.AddScoped<INotificationScopeProvider, CurrentEventNotificationScopeProvider>() (MMCA.ADC.Engagement.UI/DependencyInjection.cs:73) uses plain AddScoped so it overrides the NullNotificationScopeProvider that AddNotificationUI registers with TryAddScoped (MMCA.Common/Source/Presentation/MMCA.Common.UI/Notifications/DependencyInjection.cs:24), which keeps the two calls order-independent (DependencyInjection.cs:61-62); the line above it (:65) has to TryAddSingleton(TimeProvider.System) because no UI head registers a TimeProvider (the heads never call AddInfrastructure, :63-64).
  • Where it's used: resolved by the framework's notification services listed above, and by the send page for its targeting caption (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Notifications/NotificationSend.razor.cs:78); never called directly by an ADC page. Covered by CurrentEventNotificationScopeProviderTests.
  • Caveats / not-in-source: the cache is per instance and the registration is scoped, so on Blazor Server the window is per circuit, not per user or per process. How long a stale key can survive an event rollover is therefore bounded by the five-minute window, not by any invalidation signal.
  • @@ -2374,7 +2376,7 @@

    AttendeeCheckedIn

  • Concept introduced, the wire contract written for consumers you cannot redeploy. [Rubric §9, API & Contract Design] assesses whether contracts are versionable rather than merely correct today, and this record makes three deliberate choices for that. First, Scope is a string (:26), not the CheckInScope enum: the doc comment (:11-14) states the reason, which is that adding a scope later stays an additive change for a consumer that has not been rebuilt, where an unknown enum member would deserialize into a value the consumer's own enum cannot name. Second, SponsorId is optional and last (:31), with the doc comment (:22) recording that placement so a consumer keeps deserializing payloads written before sponsor visits existed. Third, the v1 suffix in the event name (:23) makes the version part of the routed contract rather than an implicit property of the assembly. [Rubric §6, CQRS & Event-Driven] covers the delivery half: this is an integration event, not a domain event, so it does not merely dispatch in process. It is captured with the row that produced it and published by the outbox processor (ADR-003). [Rubric §7, Microservices Readiness] applies because the payload is all scalars: nothing on it can only be resolved inside the Engagement process.
  • Walkthrough: seven positional members. UserId (:25) is the attendee. Scope (:26) is one of the CheckInScopeNames string constants. EventId (:27) is set for every scope, which is what lets a consumer bucket any check-in by conference without a lookup. SessionId (:28) is nullable and set only for a Session scope. CheckedInByUserId (:29) records who performed the check-in, an organizer for a scan and the attendee themselves for a self-recorded visit (:20), which is what keeps self-recorded rows distinguishable downstream. CheckedInOn (:30) is the recorded instant. SponsorId (:31) is the trailing optional member, defaulted to null.
  • Why it's built this way: the doc comment (:6-10) ties the event to the aggregate factory: it is added inside CheckIn.Create (CheckIn.cs:112-119) rather than in a handler, so the outbox captures it in the same transaction as the row. That gives the property the points economy depends on: a persisted check-in has published exactly one event, and a duplicate scan, which short-circuits before the factory, publishes none. Note the raise call is AddDomainEvent (CheckIn.cs:112): the framework's dual-dispatch treats an IIntegrationEvent added to an aggregate as outbox-bound, so an aggregate raises both kinds through one API. See ADR-072 for the surrounding badge-and-points decision.
  • -
  • Where it's used: raised by CheckIn (CheckIn.cs:112), consumed by AttendeeCheckedInPointsHandler. The Engagement service subscribes its own receive endpoint to this type (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:286), which the surrounding comment (:268-277) calls out as ADC's first broker self-consumption: the message leaves this process through the broker and comes back to it, except under the InProcess test transport, where it is delivered in process so the integration tier exercises the same handler without a broker.
  • +
  • Where it's used: raised by CheckIn (CheckIn.cs:112), consumed by AttendeeCheckedInPointsHandler. The Engagement service subscribes its own receive endpoint to this type (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:288), which the surrounding comment (:268-277) calls out as ADC's first broker self-consumption: the message leaves this process through the broker and comes back to it, except under the InProcess test transport, where it is delivered in process so the integration tier exercises the same handler without a broker.

  • LiveChannelPublishProcessor

    @@ -2538,7 +2540,7 @@

    EventFeedbackSubmittedPointsHandler
  • Concept introduced, the thin award adapter. [Rubric §3, Clean Architecture] assesses where knowledge sits. The rule the module protects is stated on IPointsAwarder (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/Points/Services/IPointsAwarder.cs:10-17): neither the awarder nor the ledger entity names an event, a session or a check-in, so an award is a user, an activity, an opaque subject key and a timestamp. All the conference vocabulary lives in adapters like this one, which is what would make lifting the ledger into MMCA.Common a move rather than a rewrite. [Rubric §1, SOLID] shows up twice: mapping lives here while idempotency and the per-rule kill switch live once in the awarder, and the two blocks every integration handler would otherwise repeat (the DI scope preamble and the log-and-rethrow envelope) live once in the base (MMCA.Common/Source/Core/MMCA.Common.Application/DomainEvents/ScopedIntegrationEventHandlerBase.cs:45-63). [Rubric §6, CQRS & Event-Driven] covers the delivery posture: the base propagates exceptions rather than swallowing them, so delivery stays at-least-once and redelivery is normal (ScopedIntegrationEventHandlerBase.cs:26-33), which is why this handler is written to be safely repeatable rather than to guard against a second call.
  • Walkthrough: the class overrides HandleScopedAsync (:33-36) instead of HandleAsync. The base has already null-guarded the event and opened the per-delivery async scope, and hands in that scope's IServiceProvider (ScopedIntegrationEventHandlerBase.cs:47-55), so the body is only its own resolutions plus its own logic: resolve the scoped IPointsAwarder (:38), build the subject key with PointsSubjectKeys.ForEvent(integrationEvent.EventId) (:40), which delegates to NotificationScopeKey.ForEvent and therefore produces the invariant-culture event:{id} string (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Shared/Points/PointsSubjectKeys.cs:19-20, MMCA.Common/Source/Core/MMCA.Common.Shared/Notifications/NotificationScopeKey.cs:37-38), then award PointsActivityType.EventFeedback at the event's own SubmittedOnUtc (:42-47). A rejected award (the awarder returning a failure rather than throwing) is logged at Warning and not rethrown (:49-50), because the awarder only fails when the entry itself is invalid, which is a caller bug rather than a retryable condition (IPointsAwarder.cs:30-34). An exception takes the other path: the base logs it once and lets it propagate so the delivery is retried.
  • Why it's built this way: the class comment (:13-18) states the multiplicity fact that makes this handler safe to keep this simple. Event feedback writes one answer row per question (BR-107), so one submitted form arrives here as several events. Nothing counts them: they all resolve to the same event subject key, and the awarder's uniqueness rule collapses them into a single award. That same property is what makes a broker redelivery a no-op, so no dedupe logic is written twice.
  • -
  • Where it's used: registered by the convention scan (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/DependencyInjection.cs:85), which reaches MMCA.Common's singleton registration for every IIntegrationEventHandler<> implementation (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:58-63); the singleton lifetime is exactly why the base opens a scope per delivery (:19-23). The Engagement service subscribes the matching consumer (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:288). Covered by EventFeedbackSubmittedPointsHandlerTests.
  • +
  • Where it's used: registered by the convention scan (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/DependencyInjection.cs:85), which reaches MMCA.Common's singleton registration for every IIntegrationEventHandler<> implementation (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:58-63); the singleton lifetime is exactly why the base opens a scope per delivery (:19-23). The Engagement service subscribes the matching consumer (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:290). Covered by EventFeedbackSubmittedPointsHandlerTests.

  • SessionFeedbackSubmittedPointsHandler

    @@ -2551,7 +2553,7 @@

    SessionFeedbackSubmittedPointsHan
  • Concept: the thin award adapter is taught on EventFeedbackSubmittedPointsHandler; the same reasoning applies unchanged. [Rubric §15, Best Practices & Code Quality] is worth naming here: the two files are near-identical and are deliberately kept separate rather than folded into one generic handler, because each is bound to a different event type at the DI boundary and, once ScopedIntegrationEventHandlerBase<TIntegrationEvent> absorbed the scope and error plumbing, what is left to share is five lines of mapping.
  • Walkthrough of what differs: only two lines. The subject key is built with PointsSubjectKeys.ForSession(integrationEvent.SessionId) (:42), producing session:{id} (PointsSubjectKeys.cs:25-26, NotificationScopeKey.cs:43-44), and the activity is PointsActivityType.SessionFeedback (:46). Everything else, the HandleScopedAsync override (:35-38), the awarder resolution (:40), the SubmittedOnUtc timestamp (:48) and the log-and-continue path for a rejected award (:51-52), matches the event handler line for line.
  • Why it's built this way: the class comment (:14-20) is explicit that Conference raises this event once per newly created answer, so one submission normally arrives here as several events, and nothing here counts them: they all resolve to the same session subject key and the awarder's uniqueness rule collapses them into a single award.
  • -
  • Where it's used: registered by the convention scan as a singleton (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/DependencyInjection.cs:85, MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:58-63); the consumer is wired at MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:287. Covered by SessionFeedbackSubmittedPointsHandlerTests.
  • +
  • Where it's used: registered by the convention scan as a singleton (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/DependencyInjection.cs:85, MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:58-63); the consumer is wired at MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:289. Covered by SessionFeedbackSubmittedPointsHandlerTests.

  • SessionQuestionSubmittedPointsHandler

    @@ -2585,11 +2587,11 @@

    UserSessionBookmarkCacheEvictio
    • What it is: the handler that broadcasts an output-cache eviction to the Conference service every time a bookmark is created, reactivated or removed, so Conference's cached session reads stop serving a stale bookmark count.
    • Depends on: IDomainEventHandler<in TDomainEvent> implemented over UserSessionBookmarkChanged (:46), IEventBus resolved per event (:74), OutputCacheEvictionRequested (:77) and BestEffort (:68). Externals: IServiceScopeFactory, ILogger.
    • -
    • Concept introduced, evicting a cache you do not own. [Rubric §12, Performance & Scalability] assesses whether a cross-cutting concern is solved once at the right layer. ASP.NET Core's output cache is per host: IOutputCacheStore is a local store, so a write in the owning service leaves a stale cached response sitting in front of every other process until its TTL expires (MMCA.Common/Source/Core/MMCA.Common.Domain/IntegrationEvents/OutputCacheEvictionRequested.cs:10-13). Bookmark counts are the sharp case: they are owned by Engagement but served by Conference. The class comment (:13-21) names the symptom that motivated the class, a speaker watching their dashboard seeing a star land up to a minute later, and the previous answer, a short TTL, which is a floor rather than a fix. The fix is to make eviction an event like any other: this handler publishes OutputCacheEvictionRequested carrying one tag, and the Conference host's own eviction handler drops that tag on arrival (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:303, and the reasoning at :233-242). [Rubric §7, Microservices Readiness] is why it has to be an event at all: under database-per-service (ADR-006) and process-per-service, Engagement has no handle on the other host's cache store, so the broker is the only reachable path. [Rubric §29, Resilience & Business Continuity] covers the failure posture: the publish runs through BestEffort (:68), so a failure becomes one Warning plus one metric and the stale entry expires on Conference's own TTL exactly as it did before this existed (:31-36). The TTL stays deliberately, as the backstop for a dropped message (Program.cs:295-297).
    • +
    • Concept introduced, evicting a cache you do not own. [Rubric §12, Performance & Scalability] assesses whether a cross-cutting concern is solved once at the right layer. ASP.NET Core's output cache is per host: IOutputCacheStore is a local store, so a write in the owning service leaves a stale cached response sitting in front of every other process until its TTL expires (MMCA.Common/Source/Core/MMCA.Common.Domain/IntegrationEvents/OutputCacheEvictionRequested.cs:10-13). Bookmark counts are the sharp case: they are owned by Engagement but served by Conference. The class comment (:13-21) names the symptom that motivated the class, a speaker watching their dashboard seeing a star land up to a minute later, and the previous answer, a short TTL, which is a floor rather than a fix. The fix is to make eviction an event like any other: this handler publishes OutputCacheEvictionRequested carrying one tag, and the Conference host's own eviction handler drops that tag on arrival (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:302, and the reasoning at :233-242). [Rubric §7, Microservices Readiness] is why it has to be an event at all: under database-per-service (ADR-006) and process-per-service, Engagement has no handle on the other host's cache store, so the broker is the only reachable path. [Rubric §29, Resilience & Business Continuity] covers the failure posture: the publish runs through BestEffort (:68), so a failure becomes one Warning plus one metric and the stale entry expires on Conference's own TTL exactly as it did before this existed (:31-36). The TTL stays deliberately, as the backstop for a dropped message (Program.cs:294-296).
    • Concept introduced, subscribing to the aggregate rather than to the use case. [Rubric §6, CQRS & Event-Driven] assesses where a reaction is hooked. The obvious hook is the create command handler, but the delete path runs on the framework's generic DeleteEntityCommand<TEntity, TIdentifierType> and has no ADC handler at all to add a line to. UserSessionBookmarkChanged is raised by the aggregate itself on every path that moves a count (UserSessionBookmark.cs:57 on create, :73 on reactivate, :88 on delete), so subscribing to the domain event covers all three with one class and leaves the delete flow untouched (:22-30). It also inherits the dispatch guarantee: domain-event dispatch is deferred until after the transaction commits and dropped on rollback, so no eviction is ever broadcast for a bookmark that did not persist.
    • Walkthrough
      • The primary constructor (:43-46) takes an IServiceScopeFactory and a logger; the class is a singleton by the framework convention for domain event handlers (:37-39), which is why it opens its own scope rather than injecting the scoped bus.
      • -
      • SessionsCacheTag (:53) is the literal "conference:sessions". Its doc comment (:48-52) is the load-bearing part: the tag string IS the contract between the two hosts, and it is spelled exactly as Conference registers it on its cache policies (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:275, :231, :243). Nothing in the type system checks that agreement.
      • +
      • SessionsCacheTag (:53) is the literal "conference:sessions". Its doc comment (:48-52) is the load-bearing part: the tag string IS the contract between the two hosts, and it is spelled exactly as Conference registers it on its cache policies (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:274, :231, :243). Nothing in the type system checks that agreement.
      • OperationName (:56) is the low-cardinality "bookmark-cache-evict-broadcast" that becomes the operation tag on the best-effort metric.
      • HandleAsync (:59) null-guards (:63) and then hands everything to BestEffort.ExecuteAsync (:68-80). Inside the lambda it opens one async scope (:73), resolves IEventBus (:74) and publishes new OutputCacheEvictionRequested { Tags = [SessionsCacheTag] } (:76-78). Publishing through the event bus means the message is persisted to the outbox with the same machinery as any other integration event (ADR-003).
      • The handler deliberately does not filter on domainEvent.State, and the comment says why (:65-67): every state the aggregate raises (Added on create and reactivate, Deleted on removal) moves the count Conference has cached, and evicting once more than strictly needed only costs one un-cached read.
      • @@ -2597,7 +2599,7 @@

        UserSessionBookmarkCacheEvictio
      • Why it's built this way: the alternative shapes each fail on something concrete. Hooking the command handlers misses the generic delete. Calling EvictByTagAsync locally does nothing, because the cache entries are in another process. Making the publish mandatory would let a broker hiccup fail a bookmark the attendee already saved. Subscribing to the aggregate's own event and wrapping the publish in BestEffort is the combination that covers every write path, crosses the process boundary, and cannot hurt the write it reacts to. The caching strategy this fits into is ADR-026, and the authenticated-read caching it evicts is ADR-040.
      • Where it's used: registered as a singleton by the convention scan (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/DependencyInjection.cs:85, reaching MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:51-56). Its counterpart on the receiving side is OutputCacheEvictionHandler, registered on the Conference host. Covered by UserSessionBookmarkCacheEvictionHandlerTests.
      • -
      • Caveats / not-in-source: the Conference host comment records that both halves are needed, the handler and the broker consumer, and that registering only one is a silent no-op (Program.cs:300-302); nothing in this file can detect that the other half is missing. Whether the broadcast actually reaches Conference is broker configuration and runtime, not source.
      • +
      • Caveats / not-in-source: the Conference host comment records that both halves are needed, the handler and the broker consumer, and that registering only one is a silent no-op (Program.cs:299-301); nothing in this file can detect that the other half is missing. Whether the broadcast actually reaches Conference is broker configuration and runtime, not source.

      EventFeedbackService

      @@ -2617,7 +2619,7 @@

      EventFeedbackService

    • Why it's built this way: all four calls use the retry overload that takes the CancellationToken (:33-36, :66-69, :89-91, :111-113), so Polly's backoff waits are cancellable and a page that navigates away does not keep a doomed retry alive. The batch submit now matches its session-scoped twin's shape: same idempotency-key-before-retry pattern, same /batch route convention.
    • Where it's used: registered scoped as IEventFeedbackUIService (MMCA.ADC.Engagement.UI/DependencyInjection.cs:42); injected by EventFeedback (EventFeedback.razor.cs:22), which is the only consumer. Covered by EventFeedbackServiceTests.
    • -
    • Caveats / not-in-source: the pageSize=100 on the read is a hard ceiling in this file, so an event with more than 100 answers by one attendee would be truncated silently. No page currently approaches that, and nothing here guards it. The server's EventQuestionAnswers controller now exposes a batch route (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventQuestionAnswersController.cs:254), so this service is no longer missing the batch shape its session-scoped twin has.
    • +
    • Caveats / not-in-source: the pageSize=100 on the read is a hard ceiling in this file, so an event with more than 100 answers by one attendee would be truncated silently. No page currently approaches that, and nothing here guards it. The server's EventQuestionAnswers controller now exposes a batch route (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventQuestionAnswersController.cs:258), so this service is no longer missing the batch shape its session-scoped twin has.

    PointsService

    @@ -2648,7 +2650,7 @@

    SessionFeedbackService

    • What it is: the HTTP implementation of ISessionFeedbackUIService, backed by the sessionquestionanswers resource (:18). The session-scoped counterpart of EventFeedbackService, same POST-as-upsert contract (BR-107, class doc :11-12), plus one call its event twin does not have.
    • Depends on: ISessionFeedbackUIService, AuthenticatedServiceBase, ITokenStorageService, HttpResultExecutor, ProblemDetailsResultReader, IdempotencyHeaders, SessionQuestionAnswerDTO, PagedCollectionResult<T>. Externals: IHttpClientFactory, System.Net.Http.Json, System.Globalization.
    • -
    • Concept introduced, the client half of request idempotency. The auth, retry and Result-reading story is taught at BookmarkService and the upsert contract at EventFeedbackService; neither is re-taught. What is new is SubmitAnswersAsync, and specifically where its idempotency key is minted. [Rubric §29, Resilience & Business Continuity] assesses whether a retry can be taken safely, and the inline comment at :54-57 is the reason it can be: the key is generated once, outside the retried delegate. A key generated inside would differ per attempt, the server could not recognize the retry, and a timed-out submit would re-apply the whole form. Because the HttpClient is created per logical operation and serves every attempt, setting the key as a default request header (:58) makes it ride along unchanged through all three retries. The generator itself is inherited: NewIdempotencyKey() is a compact GUID (AuthenticatedServiceBase.cs:43), and its remarks (:36-41) say the same thing from the framework side. The server end is ADR-017, applied to this route by the [Idempotent] attribute (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionQuestionAnswersController.cs:254-255).
    • +
    • Concept introduced, the client half of request idempotency. The auth, retry and Result-reading story is taught at BookmarkService and the upsert contract at EventFeedbackService; neither is re-taught. What is new is SubmitAnswersAsync, and specifically where its idempotency key is minted. [Rubric §29, Resilience & Business Continuity] assesses whether a retry can be taken safely, and the inline comment at :54-57 is the reason it can be: the key is generated once, outside the retried delegate. A key generated inside would differ per attempt, the server could not recognize the retry, and a timed-out submit would re-apply the whole form. Because the HttpClient is created per logical operation and serves every attempt, setting the key as a default request header (:58) makes it ride along unchanged through all three retries. The generator itself is inherited: NewIdempotencyKey() is a compact GUID (AuthenticatedServiceBase.cs:49), and its remarks (:36-41) say the same thing from the framework side. The server end is ADR-017, applied to this route by the [Idempotent] attribute (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Sessions/SessionQuestionAnswersController.cs:258-259).
    • Walkthrough
      • SubmitAnswerAsync (:21-40) POSTs the anonymous { SessionId, QuestionId, AnswerValue } object (:31) to the root resource and reads back the persisted SessionQuestionAnswerDTO (:38). Same shape as its event twin.
      • SubmitAnswersAsync (:43-76) is the batch form. It null-checks the answers list (:50), mints and attaches the idempotency key (:58), projects the tuples into an anonymous { SessionId, Answers = [{ QuestionId, AnswerValue }] } body (:60-64), POSTs to sessionquestionanswers/batch (:68), and Maps the returned List<T> to the interface's IReadOnlyList (:74). The server applies the form atomically, so one Result covers the whole submit.
      • @@ -2659,7 +2661,7 @@

        SessionFeedbackService

      • Why it's built this way: a session feedback form is several answers a person filled in at once, and half-applying it on a flaky conference wifi connection would be worse than failing outright. The batch endpoint plus the replayed idempotency key is what turns "submit the form" into a single atomic, safely retryable operation.
      • Where it's used: registered scoped as ISessionFeedbackUIService (MMCA.ADC.Engagement.UI/DependencyInjection.cs:43); injected by SessionFeedback (SessionFeedback.razor.cs:21), which reads existing answers (:122), submits the whole pending form through the batch call (:226) and deletes single answers (:259). [Rubric §14, Testability]: SessionFeedbackServiceTests covers eleven facts, including that the batch call sends the whole form in one request (:61) and that it carries an Idempotency-Key header (:91), that a domain-exception response becomes a failure carrying the server's own message (:43, :109, :191), that an empty envelope reads as an empty list (:162), and that an already-cancelled token aborts before a request is sent (:209).
      • -
      • Caveats / not-in-source: SubmitAnswerAsync (the single-answer form) sends no idempotency key, even though the server route carries [Idempotent] (SessionQuestionAnswersController.cs:227-228). A retried single-answer submit is therefore deduped by the upsert semantics rather than by replay. The pageSize=100 on the read is the same hard ceiling as on the event side.
      • +
      • Caveats / not-in-source: SubmitAnswerAsync (the single-answer form) sends no idempotency key, even though the server route carries [Idempotent] (SessionQuestionAnswersController.cs:231-232). A retried single-answer submit is therefore deduped by the upsert semantics rather than by replay. The pageSize=100 on the read is the same hard ceiling as on the event side.

      BookmarkService

      @@ -2670,21 +2672,21 @@

      BookmarkService

    • What it is: the HTTP implementation of IBookmarkUIService, talking to the bookmarks resource through the Gateway with authenticated, retried requests. The endpoint prefix is the const Endpoint = "bookmarks" (BookmarkService.cs:19). The class doc (:11-13) records why it does not derive from the generic CRUD base: the bookmark endpoints do not fit that shape.
    • Depends on: IBookmarkUIService (the contract it satisfies), AuthenticatedServiceBase (base class), ITokenStorageService, HttpResultExecutor, ProblemDetailsResultReader, CreateBookmarkRequest, UserSessionBookmarkDTO, PagedCollectionResult<T> (BookmarkService.cs:1-7). Externals: IHttpClientFactory, System.Net.Http.Json, System.Globalization.
    • Concept introduced, the authenticated HTTP UI service. This is the shape every write-capable service in this module follows, so it is worth reading once in full. Four pieces do the work, three inherited and one static:
        -
      • CreateAuthenticatedClientAsync() (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/AuthenticatedServiceBase.cs:51) takes the named "APIClient" from the factory (:53), reads the access token from the circuit-scoped ITokenStorageService and sets the Bearer header (:57-62). An InvalidOperationException is swallowed (:64-67), which is the SSR prerender pass where JS interop is not available yet: the call proceeds unauthenticated rather than throwing. [Rubric §26, Front-End Security] assesses how a client handles credentials; the token is never persisted by this class, it is fetched per call from the storage abstraction (ADR-051).
      • +
      • CreateAuthenticatedClientAsync() (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/AuthenticatedServiceBase.cs:57) takes the named "APIClient" from the factory (:53), reads the access token from the circuit-scoped ITokenStorageService and sets the Bearer header (:57-62). An InvalidOperationException is swallowed (:64-67), which is the SSR prerender pass where JS interop is not available yet: the call proceeds unauthenticated rather than throwing. [Rubric §26, Front-End Security] assesses how a client handles credentials; the token is never persisted by this class, it is fetched per call from the storage abstraction (ADR-051).
      • RetryPolicy (AuthenticatedServiceBase.cs:25, built at :131-134) is a static Polly policy: 3 retries with exponential backoff (2s, 4s, 8s per DefaultBackoff at :114-116) plus up to one second of jitter so a room full of clients does not re-converge on the same instant. Retryable means 5xx except 501 and 505, plus 408 and 429 (:100-109). Its onRetry disposes each retried response (:134), because Polly hands back only the final outcome and every intermediate 5xx would otherwise leak its content buffer. [Rubric §29, Resilience & Business Continuity] assesses whether transient failures are absorbed close to where they happen; this is the client half of that.
      • ProblemDetailsResultReader (MMCA.Common/Source/Core/MMCA.Common.Shared/Http/ProblemDetailsResultReader.cs:58) converts the response into a Result. It understands four payload shapes (:22-49): the MMCA error array (lossless, ErrorType preserved), the ASP.NET validation dictionary, plain RFC 9457 problem details, and a non-JSON or empty body, which it synthesizes into one error coded Http.{status} (:65). [Rubric §9, API & Contract Design] assesses whether the error contract survives the transport, and this is where it is decoded.
      • HttpResultExecutor.ExecuteAsync (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Api/HttpResultExecutor.cs:52, :87) wraps the whole delegate and converts the absence of a response into a failure: a refused connection, a DNS failure, a dropped socket, a JsonException (:121-122), each becoming Http.TransportFailure (:34). A client timeout becomes Http.Timeout (:37), while the caller's own cancellation is deliberately rethrown (:65-68) so a disposed component's abort is not reported as an error to render. The two halves together are what make a service method honestly typed as returning a Result (:11-16).
    • Walkthrough (members in call order)
        -
      • CreateAsync (:22-36) POSTs the CreateBookmarkRequest as JSON to bookmarks and reads the created UserSessionBookmarkDTO back through the reader (:34). There is no try/catch and no EnsureSuccessStatusCode: every server answer is a Result, every transport fault is caught by the executor.
      • -
      • GetUserBookmarksAsync (:39-74) builds its query string by hand into a List<string> with string.Create(CultureInfo.InvariantCulture, ...) per parameter (:48-53) and appends eventId only when the optional filter is supplied (:55-56). Invariant formatting is not decoration here: these identifiers are interpolated into a URL, and a culture-sensitive numeric format would produce a query the API cannot bind. The response is read as a PagedCollectionResult<T> and then Mapped, on the success branch only, into a (Items, TotalItems) tuple using PaginationMetadata.TotalItemCount (:68-72). Map is the point worth noticing: the projection runs only when the read succeeded, so no null check is needed and a failure passes through carrying its original errors.
      • -
      • DeleteAsync (:77-94) DELETEs by bookmark id and returns the valueless Result (:92). The comment above it (:89-91) records the behavior change deliberately: a bookmark that is not there answers 404, which now arrives as a NotFound failure rather than the old false, so "nothing to remove" stays distinguishable from "the remove failed".
      • +
      • CreateAsync (:22-37) POSTs the CreateBookmarkRequest as JSON to bookmarks and reads the created UserSessionBookmarkDTO back through the reader (:35). There is no try/catch and no EnsureSuccessStatusCode: every server answer is a Result, every transport fault is caught by the executor.
      • +
      • GetUserBookmarksAsync (:40-76) builds its query string by hand into a List<string> with string.Create(CultureInfo.InvariantCulture, ...) per parameter (:49-54) and appends eventId only when the optional filter is supplied (:56-57). Invariant formatting is not decoration here: these identifiers are interpolated into a URL, and a culture-sensitive numeric format would produce a query the API cannot bind. The response is read as a PagedCollectionResult<T> and then Mapped, on the success branch only, into a (Items, TotalItems) tuple using PaginationMetadata.TotalItemCount (:70-74). Map is the point worth noticing: the projection runs only when the read succeeded, so no null check is needed and a failure passes through carrying its original errors.
      • +
      • DeleteAsync (:79-97) DELETEs by bookmark id and returns the valueless Result (:95). The comment above it (:92-94) records the behavior change deliberately: a bookmark that is not there answers 404, which now arrives as a NotFound failure rather than the old false, so "nothing to remove" stays distinguishable from "the remove failed".
    • Why it's built this way: the class doc (:11-13) states it directly, the bookmark endpoints (create with an owner in the body, list by user, delete by bookmark id) do not fit the generic EntityServiceBase CRUD shape, so this service spells out the three calls the BookmarksController contract actually offers (BookmarksController.cs:50, :83, :122). The whole controller sits behind the EngagementFeatures.SessionBookmarks gate (BookmarksController.cs:32, see EngagementFeatures and ADR-031).
    • Where it's used: registered scoped as IBookmarkUIService in the module's UI wiring (MMCA.ADC.Engagement.UI/DependencyInjection.cs:33). [Rubric §14, Testability]: covered directly by BookmarkServiceTests.
    • -
    • Caveats / not-in-source: no page injects IBookmarkUIService today. The only references in the repo are the interface, this implementation and the DI registration; the inline bookmark toggles the UI actually renders go through SessionBookmarkUIService instead. Note also that CreateAsync sends no Idempotency-Key header, so a retried create relies on the server's duplicate guard rather than on idempotency replay (contrast SessionFeedbackService.SubmitAnswersAsync). Finally, all three calls use the retry overload without a CancellationToken (:30, :62, :86): the token still reaches the HTTP call itself, but Polly's own backoff waits are not cancellable on this path, unlike every other service in this unit.
    • +
    • Caveats / not-in-source: no page injects IBookmarkUIService today. The only references in the repo are the interface, this implementation and the DI registration; the inline bookmark toggles the UI actually renders go through SessionBookmarkUIService instead. Note also that CreateAsync sends no Idempotency-Key header, so a retried create relies on the server's duplicate guard rather than on idempotency replay (contrast SessionFeedbackService.SubmitAnswersAsync). Finally, all three calls use the cancellable retry overload, passing a _ => ... delegate plus the caller's CancellationToken to RetryPolicy.ExecuteAsync (:30-33, :63-65, :88-90): the token reaches both the HTTP call and Polly's own backoff waits, so a disposed component aborts mid-backoff instead of sleeping out the 2s, 4s, 8s schedule.

    SessionBookmarkUIService

    @@ -2699,17 +2701,17 @@

    SessionBookmarkUIService

  • GetBookmarkedSessionIdsAsync (:33-61) GETs bookmarks/session-ids?userId= and reads a Dictionary<SessionIdentifierType, UserSessionBookmarkIdentifierType> (:46-48), which is why a page can render a bookmark toggle and know the id needed to delete it from one call. It then branches on TryGetValue (:52-55): a failure is re-wrapped with its original errors and returns early, so the reminder call is unreachable on that path. On success it calls reminderCoordinator.ResyncAsync([.. bookmarks.Keys], cancellationToken) (:58) with the comment naming the intent: this response is the authoritative set, so it is the right moment to heal drift caused by another device. The dictionary is finally re-wrapped as the interface's read-only type (:60).
  • CreateBookmarkAsync (:64-90) builds a CreateBookmarkRequest from the two identifiers (:68), POSTs it, and calls NotifyBookmarkAddedAsync(sessionId, ...) only when result.IsSuccess (:84-87).
  • DeleteBookmarkAsync (:93-118) DELETEs by bookmark id and calls NotifyBookmarkRemovedAsync(sessionId, ...) only on a confirmed delete (:112-115). A 404 is a NotFound failure and therefore does not notify: nothing was removed.
  • -
  • The coordinator members these three calls use are ResyncAsync (SessionReminderCoordinator.cs:54), NotifyBookmarkAddedAsync (:40) and NotifyBookmarkRemovedAsync (:44).
  • +
  • The coordinator members these three calls use are ResyncAsync (SessionReminderCoordinator.cs:60), NotifyBookmarkAddedAsync (:40) and NotifyBookmarkRemovedAsync (:44).
  • Why it's built this way: the class doc (:12-17) records both halves, the reminder wiring lives here so reminders track bookmarks no matter which page toggled them (ADR-042 Wave 2), and reminder failures never reach the bookmark caller. The coordinator is registered scoped alongside this service (MMCA.ADC.Engagement.UI/DependencyInjection.cs:38) and its comment there states it is a no-op on hosts whose local-notification capability is unsupported, which is why a web-only host pays nothing for this coupling.
  • Where it's used: registered scoped as ISessionBookmarkUIService (MMCA.ADC.Engagement.UI/DependencyInjection.cs:34); resolved optionally by the Conference public pages, which call ServiceProvider.GetService<ISessionBookmarkUIService>() and null-check the result (PublicSessionList at PublicSessionList.razor.cs:41, :86; SessionBookmarkButton, the toggle the detail page renders, at SessionBookmarkButton.razor.cs:33, :51, :70), and passed down as a nullable parameter to PublicSessionListView (:60). That optional resolution is the extraction story in practice: Conference renders correctly in a host where the Engagement UI module is not registered at all. Covered by SessionBookmarkUIServiceTests.
  • -
  • Caveats / not-in-source: the dependency is on the concrete SessionReminderCoordinator, not an interface, so a test of this service has to construct a real coordinator; the test suite does exactly that, assembling an inert one from mocks (SessionBookmarkUIServiceTests.cs:129-136). Whether a reminder is actually scheduled is decided inside the coordinator and SessionReminderPlanner, not here.
  • +
  • Caveats / not-in-source: the dependency is on the concrete SessionReminderCoordinator, not an interface, so a test of this service has to construct a real coordinator; the test suite does exactly that, assembling an inert one from mocks (SessionBookmarkUIServiceTests.cs:129-137). Whether a reminder is actually scheduled is decided inside the coordinator and SessionReminderPlanner, not here.

  • UserDeletedPointsHandler

    -

    MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.Points.IntegrationEventHandlers · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/Points/IntegrationEventHandlers/UserDeletedPointsHandler.cs:37 · Level 8 · class (sealed partial)

    +

    MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.Points.IntegrationEventHandlers · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/Points/IntegrationEventHandlers/UserDeletedPointsHandler.cs:38 · Level 8 · class (sealed partial)

    • What it is: the erasure handler for the leaderboard. When Identity deletes an account, this takes the entry off the public board and overwrites the display name it published there. It is the one consumer in this folder that awards nothing.
    • @@ -2723,7 +2725,7 @@

      UserDeletedPointsHandler

  • Why it's built this way: the two-flag loop is what makes the handler idempotent in both directions (:22-25): an account that never joined the board has no row and writes nothing, and an account already off the board with an erased name reaches the same end state and writes nothing again. At-least-once delivery makes redelivery normal, so that property is a requirement rather than a nicety.
  • -
  • Where it's used: registered as a singleton by the convention scan (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/DependencyInjection.cs:85, MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:58-63), with the consumer wired at MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:289; the surrounding comment (:264-266) records that this is the one consumer here that earns nothing. Covered by UserDeletedPointsHandlerTests.
  • +
  • Where it's used: registered as a singleton by the convention scan (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/DependencyInjection.cs:85, MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:58-63), with the consumer wired at MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:291; the surrounding comment (:264-266) records that this is the one consumer here that earns nothing. Covered by UserDeletedPointsHandlerTests.
  • Caveats / not-in-source: the privacy commitment the comment cites (PRIVACY.md section 5) lives in the ADC repo's private docs, not in this file. What EraseDisplayName() writes in place of the name is defined on LeaderboardOptIn.

  • @@ -2742,8 +2744,8 @@

    AttendeeCheckedInPointsHandler

  • The fall-through sets activity = default, an empty subject key, and returns false (:96-98), which is the single exit the caller's guard reads.
  • -
  • Why it's built this way: this handler is where the module's most interesting delivery property lives. The event is published by the same service that consumes it, so the check-in write and the award are two separate transactions joined by the broker (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:269-275). That keeps the scan endpoint's latency independent of the points write and lets the award retry on its own. The same comment records the fallback if a deployment ever has trouble with the round trip (:275-277): the award could move to an in-module domain event handler on the same CheckIn creation, which is a one-file change because the module already awards session-question points that way, through SessionQuestionSubmittedPointsHandler.
  • -
  • Where it's used: registered as a singleton by the convention scan (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/DependencyInjection.cs:85, MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:58-63), which is why the base opens a scope per delivery (:23-27); the consumer is wired at MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:286. Covered by AttendeeCheckedInPointsHandlerTests.
  • +
  • Why it's built this way: this handler is where the module's most interesting delivery property lives. The event is published by the same service that consumes it, so the check-in write and the award are two separate transactions joined by the broker (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:271-277). That keeps the scan endpoint's latency independent of the points write and lets the award retry on its own. The same comment records the fallback if a deployment ever has trouble with the round trip (:275-277): the award could move to an in-module domain event handler on the same CheckIn creation, which is a one-file change because the module already awards session-question points that way, through SessionQuestionSubmittedPointsHandler.
  • +
  • Where it's used: registered as a singleton by the convention scan (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/DependencyInjection.cs:85, MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:58-63), which is why the base opens a scope per delivery (:23-27); the consumer is wired at MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:288. Covered by AttendeeCheckedInPointsHandlerTests.
  • Caveats / not-in-source: how many points each activity is worth, and whether a rule is switched off, is decided inside PointsAwarder from configuration, not here.

  • @@ -2873,7 +2875,7 @@

    LeaderboardOptIn

  • Why it's built this way: the unique index on UserId is filtered on the soft-delete flag (LeaderboardOptInConfiguration, LeaderboardOptInConfiguration.cs:33-35), so an attendee can hold at most one active opt-in while their history survives (ADR-095). That index is precisely what makes Reactivate necessary rather than optional: without it a rejoin would insert a second row and collide, which is the reasoning recorded at the call site (SetLeaderboardParticipationHandler.cs:67-69).
  • -
  • Where it's used: reactivated and created by SetLeaderboardParticipationHandler (SetLeaderboardParticipationHandler.cs:87 and :92 respectively); read by GetLeaderboardHandler (GetLeaderboardHandler.cs:39-43, ordering tie-break on DisplayName at :71); soft-deleted and erased by UserDeletedPointsHandler (UserDeletedPointsHandler.cs:63-82); persisted per LeaderboardOptInConfiguration. Unit-tested by LeaderboardOptInTests.
  • +
  • Where it's used: reactivated and created by SetLeaderboardParticipationHandler (SetLeaderboardParticipationHandler.cs:98 and :92 respectively); read by GetLeaderboardHandler (GetLeaderboardHandler.cs:39-43, ordering tie-break on DisplayName at :71); soft-deleted and erased by UserDeletedPointsHandler (UserDeletedPointsHandler.cs:64-83); persisted per LeaderboardOptInConfiguration. Unit-tested by LeaderboardOptInTests.
  • Caveats / not-in-source: the erasure comment cites PRIVACY.md section 5 (LeaderboardOptIn.cs:121); that document lives in the ADC repo's own docs, not in this file, so the citation cannot be verified from the source under this type.

  • @@ -3081,96 +3083,6 @@

    UserSessionBookmark

  • Where it's used: created and reactivated by BookmarkManagementDomainService; counted by BookmarkCountService; mapped by UserSessionBookmarkDTOMapper; persisted per UserSessionBookmarkConfiguration; the aggregate type parameter for the CRUD surface on BookmarksController. Unit-tested by UserSessionBookmarkTests.

  • -

    AttendeeBadgeConfiguration

    -
    -

    MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.CheckIns · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/CheckIns/AttendeeBadgeConfiguration.cs:15 · Level 8 · class (internal sealed)

    -
    -
      -
    • What it is: the EF Core mapping for the AttendeeBadge aggregate: two required columns and two unique indexes, one per identity the badge is looked up by (the attendee, and the scannable credential).
    • -
    • Depends on: the engine shim base EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> and AttendeeBadge. Externals: EF Core's EntityTypeBuilder<T>.
    • -
    • Concept introduced, the module entity configuration over the engine shim. [Rubric §8, Data Architecture] assesses whether the schema is designed for the queries and constraints the application actually needs rather than inherited from conventions; [Rubric §3, Clean Architecture] assesses whether persistence concerns stay out of the domain. Every Engagement configuration in this file group follows one shape: extend EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, call base.Configure(builder) first (AttendeeBadgeConfiguration.cs:21), then declare only what is specific to this entity. The base applies the framework conventions once (table and schema resolution, the soft-delete global query filter, audit columns, the concurrency token); the full hierarchy is taught in Group 07. The engine is the choice of base class, so re-pointing an entity at the Cosmos or SQLite shim is a base-class swap with no body edits (ADR-018); every Engagement entity uses the SQL Server shim today. The domain entity itself carries no EF attributes, which is what keeps MMCA.ADC.Engagement.Domain free of a persistence dependency.
    • -
    • Concept introduced, unique index as a race backstop. [Rubric §12, Performance & Scalability] and [Rubric §11, Security]. Both indexes here are unique and unfiltered in the source: HasIndex(b => b.UserId).IsUnique() (:31-32) and HasIndex(b => b.Credential).IsUnique() (:35-36). The first is the concurrency guarantee the get-or-create path leans on (the inline comment at :29-30 says so), the second makes a scan a single seek on a Guid column (AttendeeBadge.Credential, MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Domain/Badges/AttendeeBadge.cs:24) and forbids two badges sharing a credential. Note what is NOT written here: neither index declares HasSoftDeleteFilter(), because SoftDeleteUniqueIndexConvention already stamps IsDeleted = 0 onto every unique index of a soft-deletable entity at model finalizing (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:60-70) and leaves an index that already carries the predicate untouched (:73-76).
    • -
    • Walkthrough (AttendeeBadgeConfiguration.cs:19-37): base.Configure(builder) (:21); UserId required (:23-24); Credential required (:26-27); the unique UserId index (:31-32); the unique Credential index (:35-36). That is the whole file: five statements, each with a comment naming the behavior it protects.
    • -
    • Why it's built this way: the remarks (:11-14) state the cross-module rule the whole module obeys, UserId is a scalar reference into the Identity database and never a cross-database FK, because Engagement cannot see the Identity model (database-per-service, ADR-006). [Rubric §7, Microservices Readiness].
    • -
    • Where it's used: discovered by assembly scanning and applied when the Engagement SQLServerDbContext builds its model; the unique UserId index is what GetOrCreateMyBadgeHandler classifies through IUniqueConstraintViolationDetector when two requests race (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/CheckIns/UseCases/GetOrCreateMyBadge/GetOrCreateMyBadgeHandler.cs:54).
    • -
    -
    -

    LeaderboardOptInConfiguration

    -
    -

    MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.Points · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/Points/LeaderboardOptInConfiguration.cs:17 · Level 8 · class (internal sealed)

    -
    -
      -
    • What it is: the EF Core mapping for LeaderboardOptIn, the row that says an attendee agreed to appear on the public board and under which name. Same base shape as AttendeeBadgeConfiguration; its distinctive job is "one active opt-in per attendee".
    • -
    • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, LeaderboardOptIn, and IndexBuilderExtensions for HasSoftDeleteFilter(). Externals: EF Core's EntityTypeBuilder<T>.
    • -
    • Concept introduced, HasSoftDeleteFilter() instead of a SQL literal. [Rubric §8, Data Architecture] and [Rubric §15, Best Practices & Code Quality]. The unique index is declared .IsUnique().HasSoftDeleteFilter() (LeaderboardOptInConfiguration.cs:33-35). That extension member (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/IndexBuilderExtensions.cs:52-66) builds the predicate from the model rather than from a hand-typed "[IsDeleted] = 0", so the column name follows a rename and the quoting comes from the engine; it takes an engine parameter defaulting to DataSource.SQLServer and an optional additionalFilter that is ANDed in front of the soft-delete predicate (IndexBuilderExtensions.cs:63-65). On a unique index the call is belt-and-braces (the convention would have added the same predicate), and it is idempotent in effect because the convention skips any index that already declares the predicate; on a non-unique index it is the only way to get the filter at all.
    • -
    • Walkthrough (LeaderboardOptInConfiguration.cs:21-36): base.Configure(builder) (:23); UserId required (:25-26); DisplayName required with HasMaxLength(LeaderboardOptIn.DisplayNameMaxLength) (:28-30), the cap being the domain constant 100 (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Domain/Points/LeaderboardOptIn.cs:22), so the column width and the invariant in LeaderboardOptInInvariants can never disagree; then the filtered unique index on UserId (:33-35).
    • -
    • Why it's built this way: the remarks (:11-16) give both halves of the design. Filtering the unique index on the soft-delete flag is what makes rejoining reactivate the existing row instead of accumulating one dead row per toggle (the path SetLeaderboardParticipationHandler implements). Storing DisplayName here at all is deliberate denormalization: the name is the snapshot the attendee published, so the leaderboard query never has to reach into Identity, which is a cross-database read this topology does not allow. [Rubric §7, Microservices Readiness].
    • -
    • Where it's used: applied to the Engagement model by assembly scanning; the snapshot column is read by GetLeaderboardHandler (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/Points/UseCases/GetLeaderboard/GetLeaderboardHandler.cs:41), and the unique index is the constraint SetLeaderboardParticipationHandler both navigates and catches (SetLeaderboardParticipationHandler.cs:104-110).
    • -
    -
    -

    LivePollVoteConfiguration

    -
    -

    MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.LivePolls · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/LivePolls/LivePollVoteConfiguration.cs:17 · Level 8 · class (internal sealed)

    -
    -
      -
    • What it is: the EF Core mapping for the LivePollVote aggregate root, whose centerpiece is the filtered unique index guaranteeing one active vote per user per poll (BR-225).
    • -
    • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> (the shared base that maps audit fields, soft-delete and RowVersion), LivePollVote, and IndexBuilderExtensions for HasSoftDeleteFilter(). Externals: EF Core's EntityTypeBuilder<T>.
    • -
    • Concept introduced, the filtered unique index as the backstop for a soft-delete aggregate. [Rubric §8, Data Architecture] assesses whether uniqueness and concurrency are enforced at the storage layer rather than hoped for in application code. Because a vote toggles via soft-delete rather than hard-delete, a naive unique index would permanently block a user from re-voting: the deleted row keeps occupying its unique slot. Scoping the index to active rows is what makes the handler's create-or-reactivate dance race-safe (the remark at LivePollVoteConfiguration.cs:12-16 says exactly that). Note how the scoping is expressed: .HasSoftDeleteFilter() (:37), the framework extension member at MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/IndexBuilderExtensions.cs:52-66, rather than a hand-typed HasFilter("[IsDeleted] = 0"). The extension builds the predicate from the model, so a renamed soft-delete column or a different engine follows automatically. On a unique index the call is belt and braces: SoftDeleteUniqueIndexConvention already stamps the same predicate onto every unfiltered unique index of a soft-deletable entity at model finalizing (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:56, :60-70) and recognizes the predicate it finds rather than doubling it (:74-75). Writing it explicitly keeps BR-225 legible in the file that owns it. [Rubric §2, Design Patterns] applies in that the database constraint and the domain Reactivate method are two halves of one idempotent-write pattern; the identical shape guards SessionQuestionUpvote through SessionQuestionUpvoteConfiguration.
    • -
    • Walkthrough (LivePollVoteConfiguration.cs:21-41): Configure (:21) calls base.Configure (:23), then requires LivePollId (:25-26), OptionId (:28-29) and UserId (:31-32); note there are no navigations, because a vote is a separate aggregate by design and so carries scalar FKs only. The filtered unique index on { LivePollId, UserId } (:35-37) is BR-225, one active vote per poll and user, with the soft-delete predicate applied through HasSoftDeleteFilter() and the rule named in the comment above it (:34). The second, non-unique index on { LivePollId, OptionId } (:40) supports the grouped COUNT per option that the tally issues (:39).
    • -
    • Why it's built this way: the filter is what lets soft-delete and uniqueness coexist, routing it through the shared extension keeps every filtered index in the codebase producing byte-identical SQL, and the second index matches the tally query shape exactly, so results are computed from an index rather than by scanning the vote table.
    • -
    • Where it's used: applied by the Engagement SQLServerDbContext at model build; CastVoteHandler relies on the unique index as the final arbiter when two concurrent votes race, and LivePollResultsBuilder is the reader the second index serves. Covered by EngagementEntityConfigurationTests (MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure.Tests/Persistence/EngagementEntityConfigurationTests.cs).
    • -
    -
    -

    PointsEntryConfiguration

    -
    -

    MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.Points · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/Points/PointsEntryConfiguration.cs:21 · Level 8 · class (internal sealed)

    -
    -
      -
    • What it is: the EF Core mapping for PointsEntry, one row per awarded activity. Its composite unique index is not an optimization: it is the enforcement point for two separate rules at once.
    • -
    • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, PointsEntry, PointsSubjectKeys (for the key length), and IndexBuilderExtensions. Externals: EF Core's EntityTypeBuilder<T>.
    • -
    • Concept introduced, one index carrying two business rules. [Rubric §8, Data Architecture] and [Rubric §6, CQRS & Event-Driven]. The unique index on (UserId, ActivityType, SubjectKey) (PointsEntryConfiguration.cs:46-48) is simultaneously the idempotency key for an at-least-once award (a replayed integration event collides with the row it already wrote) and the anti-farming rule (N questions or N answers for one session collapse onto one subject key, so they award once). The remarks (:12-20) spell out the two-layer interplay: PointsAwarder pre-checks with an ExistsAsync on exactly those three columns so the ordinary duplicate is a clean no-op (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/Points/Services/PointsAwarder.cs:55-64), and a concurrent race that gets past the pre-check surfaces here as a provider exception the awarder classifies as already-awarded rather than as a failure (PointsAwarder.cs:75). The database is the arbiter of last resort, not the first line.
    • -
    • Walkthrough (PointsEntryConfiguration.cs:25-53): base.Configure(builder) (:27); UserId (:29-30), ActivityType (:32-33) and Points (:35-36) required; SubjectKey required with HasMaxLength(PointsSubjectKeys.MaxLength) (:38-40), that is 64 characters (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Shared/Points/PointsSubjectKeys.cs:14), a bounded key being what makes it indexable at all; OccurredOnUtc required (:42-43); then the filtered unique triple (:46-48) and a second, non-unique UserId index also carrying HasSoftDeleteFilter() (:51-52) so the "my points" ledger read is a seek. That second index is the case the convention does not cover: it is not unique, so without the explicit call it would carry no filter.
    • -
    • Why it's built this way: pushing idempotency into a unique constraint means correctness does not depend on the broker delivering exactly once, which it does not. [Rubric §29, Resilience & Business Continuity]. As everywhere in this module, UserId stays a scalar column, never a cross-database FK (:18-19).
    • -
    • Where it's used: applied by assembly scanning; the index is the contract PointsAwarder is written against, and the ledger it protects is read by GetLeaderboardHandler and the my-points queries, and exported by UserEngagementExportService (UserEngagementExportService.cs:36-46).
    • -
    -
    -

    SessionQuestionConfiguration

    -
    -

    MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.SessionQuestions · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/SessionQuestions/SessionQuestionConfiguration.cs:16 · Level 8 · class (internal sealed)

    -
    -
      -
    • What it is: the EF Core mapping for the SessionQuestion aggregate root (the audience-questions half of the live layer): scalar FK columns, a length constraint, and one query-shaped composite index.
    • -
    • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SessionQuestion, and SessionQuestionInvariants for the shared length constant. Externals: EF Core's EntityTypeBuilder<T>.
    • -
    • Walkthrough (SessionQuestionConfiguration.cs:20-39): base.Configure(builder) (:22); SessionId required (:24-25); UserId required (:27-28); Text required with HasMaxLength(SessionQuestionInvariants.TextMaxLength) (:30-32), a constant that itself forwards to the DTO's TextMaxLength (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Domain/SessionQuestions/SessionQuestionInvariants.cs:13), that is 500 characters (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Shared/SessionQuestions/SessionQuestionDTO.cs:18), so the column width, the domain validation and the client-side hint share one source of truth; Status required (:34-35); then HasIndex(p => new { p.SessionId, p.Status }) (:38) because both the attendee list and the moderation queue filter by session and status. This index is deliberately neither unique nor soft-delete-filtered: the question feed has no uniqueness rule to enforce, and the read that uses it is already narrowed by session.
    • -
    • Why it's built this way, cross-module FKs stay scalar: the remarks (:10-15) state it plainly. SessionId/EventId point at Conference-owned rows in another database and UserId at an Identity-owned row, so they stay indexed scalar columns; consistency flows through the Conference gRPC validation boundary, never a cross-database constraint. [Rubric §7, Microservices Readiness], [Rubric §8, Data Architecture]. See AttendeeBadgeConfiguration for the shared base-class story.
    • -
    • Where it's used: applied to the Engagement model by assembly scanning; the questions it maps are the second section of the data-subject export assembled by UserEngagementExportService (UserEngagementExportService.cs:30-34).
    • -
    -
    -

    SessionQuestionUpvoteConfiguration

    -
    -

    MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.SessionQuestions · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/SessionQuestions/SessionQuestionUpvoteConfiguration.cs:17 · Level 8 · class (internal sealed)

    -
    -
      -
    • What it is: the EF Core mapping for SessionQuestionUpvote. Same shape as SessionQuestionConfiguration, specialized to enforce one active upvote per question per user.
    • -
    • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SessionQuestionUpvote, IndexBuilderExtensions. Externals: EF Core's EntityTypeBuilder<T>.
    • -
    • Walkthrough (SessionQuestionUpvoteConfiguration.cs:21-38): base.Configure(builder) (:23); SessionQuestionId required (:25-26); UserId required (:28-29); the filtered unique index on (SessionQuestionId, UserId) (:32-34), which is BR-235 as a database guarantee (named in the summary at :10 and again at the index, :31), at most one active upvote per (question, user), while soft-deleted history may hold prior toggles; and a second, non-unique SessionQuestionId index (:37) for counting upvotes grouped by question. Note the asymmetry with PointsEntryConfiguration: that second index carries no HasSoftDeleteFilter(), so it indexes deleted rows too.
    • -
    • Why it's built this way: the filtered unique index is the database-level backstop behind the handler's create-or-reactivate logic (remarks, :12-16), the same soft-delete-plus-unique-index interplay UserSessionBookmarkConfiguration sets up for bookmarks. [Rubric §8, Data Architecture].
    • -
    • Caveats / not-in-source: the remarks (:13-14) note upvotes reference their question by a scalar FK column with no navigations, they are a separate aggregate by design, so the index is the only structural link.
    • -
    -
    -

    UserSessionBookmarkConfiguration

    -
    -

    MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.UserSessionBookmarks · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/UserSessionBookmarks/UserSessionBookmarkConfiguration.cs:17 · Level 8 · class (internal sealed)

    -
    -
      -
    • What it is: the EF Core mapping for UserSessionBookmark, the module's founding aggregate. Same base shape as the siblings above; it enforces BR-21 (one active bookmark per user per session) and adds the index the conference-day count path depends on.
    • -
    • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, UserSessionBookmark, IndexBuilderExtensions. Externals: EF Core's EntityTypeBuilder<T>.
    • -
    • Concept, indexing for the hot read, not just for the constraint. [Rubric §12, Performance & Scalability]. The second index (UserSessionBookmarkConfiguration.cs:38-39) exists because BookmarkCountService counts by SessionId alone, and a leading-UserId composite cannot seek on that predicate; the comment (:36-37) says exactly this. It is non-unique, so HasSoftDeleteFilter() is required rather than optional: the convention only stamps unique indexes (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:62-63), and the count query carries the soft-delete predicate, so an unfiltered index would not match it.
    • -
    • Walkthrough (UserSessionBookmarkConfiguration.cs:21-40): base.Configure(builder) (:23); UserId required (:25-26); SessionId required (:28-29); the filtered unique index on (UserId, SessionId) (:32-34); the filtered SessionId index (:38-39).
    • -
    • Why it's built this way: filtering the unique index on IsDeleted = 0 lets a user remove and re-add the same bookmark any number of times while guaranteeing only one is active, which is precisely why the bookmark write path reactivates rather than inserts. [Rubric §8, Data Architecture].
    • -
    • Caveats / not-in-source: the remarks (:12-16) note the FK relationship to Conference.Session is configured at the shared DbContext level (where both entity types are visible) rather than here, to avoid a cross-module Infrastructure-to-Domain dependency that would break module isolation. In the extracted topology those two entities live in different databases anyway, where CrossDataSourceDegradeConvention drops the constraint.
    • -
    -

    BookmarkCountService

    MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.UserSessionBookmarks.Services · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/UserSessionBookmarks/Services/BookmarkCountService.cs:11 · Level 8 · class (sealed)

    @@ -3191,22 +3103,61 @@

    BookmarkCountService

  • Where it's used: registered TryAddScoped by the Engagement Application DI (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/DependencyInjection.cs:43); wrapped for the wire by BookmarkCountsGrpcService; consumed by Conference's GetSessionBookmarkCountHandler and GetSessionBookmarkCountsHandler. When Engagement is disabled in a host, DisabledBookmarkCountService stands in until the gRPC adapter replaces the registration.

  • +

    UserDeletedBadgeHandler

    +
    +

    MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.CheckIns.IntegrationEventHandlers · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/CheckIns/IntegrationEventHandlers/UserDeletedBadgeHandler.cs:22 · Level 8 · class (public sealed partial)

    +
    +
      +
    • What it is: the erasure handler for attendee badges. When Identity deletes an account, this soft-deletes that user's AttendeeBadge rows in the Engagement database. The check-in records themselves are kept; only the badge, which carries a per-user credential, goes (class doc, :10-13).
    • +
    • Depends on: ScopedIntegrationEventHandlerBase<TIntegrationEvent> closed over Identity's UserDeleted integration event (:25, imported from MMCA.ADC.Identity.Shared.Users.IntegrationEvents at :4), IUnitOfWork (:34), the IRepository<TEntity, TIdentifierType> it obtains for AttendeeBadge (:35) and that aggregate's Delete() (:46). Externals: IServiceScopeFactory, ILogger<T> and the source-generated [LoggerMessage] (:61-62, which is why the class is partial).
    • +
    • Concept, cross-database erasure by event. [Rubric §30, Compliance, Privacy & Data Governance]. Under database-per-service (ADR-006) the Identity-side erasure cannot reach a row in the Engagement database, so the deletion travels as an event and the owning service carries it out (:11-13). It is one of several UserDeleted handlers in this module; the Engagement host registers a single consumer for the type, and that consumer runs every handler for it (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:263-265, :291). The retained check-in history follows the commitment the class doc cites (PRIVACY.md section 1.2), which lives in ADC's private docs rather than this repo.
    • +
    • Walkthrough
        +
      • HandleScopedAsync(integrationEvent, services, cancellationToken) (:28-59) is the override; the base has opened the per-delivery scope and passes its provider. The body takes the user id (:33), resolves the scoped unit of work (:34) and the badge repository (:35).
      • +
      • The read (:37-41) filters badge.UserId == userId && !badge.IsDeleted and asks for tracked entities (asTracking: true, :40) because mutations follow. It does not bypass the soft-delete query filter: only active badges are candidates.
      • +
      • The loop (:43-50) calls badge.Delete() per row and counts only the successes (:46-49); a failed Delete() result is skipped without a log.
      • +
      • When nothing was deleted the method returns without saving (:52-55); otherwise it saves once for the batch (:57) and logs the count at Information through LogErased (:58, :61-62).
      • +
      +
    • +
    • Why it's built this way: idempotence is a requirement, because at-least-once delivery makes redelivery normal (:15-16). Reading active rows only means a second delivery finds nothing and writes nothing. Exceptions are deliberately not caught (:16-17), so a failed erasure propagates to the base and the broker retries it rather than acking it away. [Rubric §29, Resilience & Business Continuity].
    • +
    • Where it's used: registered by the Application convention scan (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/DependencyInjection.cs:85) and driven by the UserDeleted consumer wired at MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:291. Sibling erasure handlers: UserDeletedBookmarksHandler and UserDeletedPointsHandler. Covered by UserDeletedBadgeHandlerTests (MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.Application.Tests/CheckIns/IntegrationEventHandlers/UserDeletedBadgeHandlerTests.cs).
    • +
    • Caveats / not-in-source: the deletion is a soft delete through the aggregate's Delete(), so the row stays in the table flagged deleted; whether anything later purges it is not determinable from this file.
    • +
    +
    +

    UserDeletedBookmarksHandler

    +
    +

    MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.UserSessionBookmarks.IntegrationEventHandlers · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/UserSessionBookmarks/IntegrationEventHandlers/UserDeletedBookmarksHandler.cs:22 · Level 8 · class (public sealed partial)

    +
    +
      +
    • What it is: the erasure handler for session bookmarks. When Identity deletes an account, this soft-deletes that user's active UserSessionBookmark rows, the promise the class doc quotes from PRIVACY.md section 5 ("Session bookmarks: deleted along with your account", :10-12).
    • +
    • Depends on: ScopedIntegrationEventHandlerBase<TIntegrationEvent> closed over Identity's UserDeleted integration event (:25), IUnitOfWork (:34), the IRepository<TEntity, TIdentifierType> it obtains for UserSessionBookmark (:35) and that aggregate's Delete() (:46). Externals: IServiceScopeFactory, ILogger<T> and the source-generated [LoggerMessage] (:61-62).
    • +
    • Concept, cross-database erasure by event. [Rubric §30, Compliance, Privacy & Data Governance]. The same shape as UserDeletedBadgeHandler, line for line: the bookmarks live in the Engagement database, which the Identity-side erasure cannot reach (:12-13), so the owning service performs the delete on receipt of UserDeleted. It runs under the module's single UserDeleted consumer alongside the other erasure handlers (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:263-265, :291).
    • +
    • Walkthrough
        +
      • HandleScopedAsync(integrationEvent, services, cancellationToken) (:28-59): takes the user id (:33), resolves the unit of work and the bookmark repository (:34-35), then reads the user's active bookmarks tracked (:37-41, filter bookmark.UserId == userId && !bookmark.IsDeleted at :39).
      • +
      • Each bookmark is passed to Delete() and only successes are counted (:43-50). Zero deletions returns without a save (:52-55); otherwise one SaveChangesAsync for the batch (:57) and an Information log of the count (:58, :61-62).
      • +
      +
    • +
    • Why it's built this way: active-rows-only reads make a redelivered event a no-op, and uncaught exceptions let the broker retry a failed erasure (:15-17). [Rubric §29, Resilience & Business Continuity]. Soft delete is also how an ordinary un-bookmark ends, so an erased bookmark is indistinguishable in shape from one the attendee removed.
    • +
    • Where it's used: registered by the Application convention scan (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/DependencyInjection.cs:85) and driven by the consumer at MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:291. Covered by UserDeletedBookmarksHandlerTests (MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.Application.Tests/UserSessionBookmarks/IntegrationEventHandlers/UserDeletedBookmarksHandlerTests.cs).
    • +
    • Caveats / not-in-source: the PRIVACY.md wording the class doc quotes lives in ADC's private docs; this file is the only in-repo evidence of it.
    • +
    +

    SetLeaderboardParticipationHandler

    MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.Points.UseCases.SetLeaderboardParticipation · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/Points/UseCases/SetLeaderboardParticipation/SetLeaderboardParticipationHandler.cs:31 · Level 9 · class (public sealed partial)

    • What it is: the command handler behind "put me on the leaderboard" and "take me off". One boolean in, a Result out, with all the interesting work in how it resolves the published name and how it reconciles with a soft-deleted prior opt-in.
    • -
    • Depends on: IUnitOfWork, ICurrentUserService and its CurrentUserServiceExtensions.RequireUserId member (MMCA.Common/Source/Core/MMCA.Common.Application/Extensions/CurrentUserServiceExtensions.cs:35), IUniqueConstraintViolationDetector, LeaderboardOptIn, SetLeaderboardParticipationRequest, and the framework contracts ICommandHandler<in TCommand, TResult>, Result and Error. Externals: System.Security.Claims, Microsoft.Extensions.Logging and its [LoggerMessage] source generator (which is why the class is partial, :196-197).
    • -
    • Concept, server-side identity for a public surface. [Rubric §11, Security] assesses whether trust decisions read from the token rather than the request body. SetLeaderboardParticipationRequest carries a single bool Participate and nothing else; the display name comes from the caller's claims (:64, :154-185). The class doc (:14-19) states the threat directly: a name in the body would let any caller publish a name they do not own, on a surface whose whole purpose is showing names to other people.
    • -
    • Concept, create-or-reactivate against a filtered unique index. [Rubric §8, Data Architecture], [Rubric §4, DDD]. Because the unique index declared in LeaderboardOptInConfiguration is filtered on the soft-delete flag, a soft-deleted opt-in still occupies the UserId slot for a filtered read but not for the constraint. JoinAsync therefore reads through FindIncludingDeletedAsync, which returns the active and the deleted matches as a tuple (:71-74), and the comment (:68-70) states the consequence of not doing so: every rejoin would insert a fresh row and collide with the index. State transitions stay in the aggregate (deleted.Reactivate(displayName) at :86, LeaderboardOptIn.Create(...) at :92), each returning a Result the handler propagates.
    • -
    • Concept, classifying the lost insert race through an injected detector. [Rubric §1, SOLID], [Rubric §29, Resilience & Business Continuity]. The save is wrapped in a try whose exception filter is when (uniqueConstraintViolationDetector.IsUniqueConstraintViolation(exception)) (:103). The detector is an injected abstraction (IUniqueConstraintViolationDetector) rather than an inline message match, so the provider-specific knowledge of what a duplicate-key failure looks like lives once in the infrastructure layer (SqlServerUniqueConstraintViolationDetector) and this handler stays provider-agnostic. PointsAwarder leans on the same collaborator (PointsAwarder.cs:75).
    • +
    • Depends on: IUnitOfWork, ICurrentUserService and its CurrentUserServiceExtensions.RequireUserId member (MMCA.Common/Source/Core/MMCA.Common.Application/Extensions/CurrentUserServiceExtensions.cs:35), IUniqueConstraintViolationDetector, LeaderboardOptIn, SetLeaderboardParticipationRequest, and the framework contracts ICommandHandler<in TCommand, TResult>, Result and Error. Externals: System.Security.Claims, Microsoft.Extensions.Logging and its [LoggerMessage] source generator (which is why the class is partial, :208-209).
    • +
    • Concept, server-side identity for a public surface. [Rubric §11, Security] assesses whether trust decisions read from the token rather than the request body. SetLeaderboardParticipationRequest carries a single bool Participate and nothing else; the display name comes from the caller's claims (:65, :166-197). The class doc (:15-19) states the threat directly: a name in the body would let any caller publish a name they do not own, on a surface whose whole purpose is showing names to other people.
    • +
    • Concept, create-or-reactivate against a filtered unique index. [Rubric §8, Data Architecture], [Rubric §4, DDD]. Because the unique index declared in LeaderboardOptInConfiguration is filtered on the soft-delete flag, a soft-deleted opt-in still occupies the UserId slot for a filtered read but not for the constraint. JoinAsync therefore reads through FindIncludingDeletedAsync, which returns the active and the deleted matches as a tuple (:72-75), and the comment (:69-71) states the consequence of not doing so: every rejoin would insert a fresh row and collide with the index. State transitions stay in the aggregate (deleted.Reactivate(displayName) at :98, LeaderboardOptIn.Create(...) at :104), each returning a Result the handler propagates.
    • +
    • Concept, an erased opt-in is terminal. [Rubric §30, Compliance, Privacy & Data Governance]. The reactivate path has one exception. Account erasure (UserDeletedPointsHandler) leaves the row soft-deleted with an erasure marker in place of the name, and a deleted account's token can still be valid for a while. So before reactivating, JoinAsync checks deleted.IsDisplayNameErased and returns Error.Forbidden with code Points.AccountErased instead of bringing the row back under a fresh public name (:90-96); the comment (:87-89) draws the line against an ordinary opt-out, which keeps its real name and still rejoins.
    • +
    • Concept, classifying the lost insert race through an injected detector. [Rubric §1, SOLID], [Rubric §29, Resilience & Business Continuity]. The save is wrapped in a try whose exception filter is when (uniqueConstraintViolationDetector.IsUniqueConstraintViolation(exception)) (:115). The detector is an injected abstraction (IUniqueConstraintViolationDetector) rather than an inline message match, so the provider-specific knowledge of what a duplicate-key failure looks like lives once in the infrastructure layer (SqlServerUniqueConstraintViolationDetector) and this handler stays provider-agnostic. PointsAwarder leans on the same collaborator (PointsAwarder.cs:75).
    • Walkthrough
        -
      • HandleAsync(command, cancellationToken) (:37-53): null-guards the command (:41), calls currentUserService.RequireUserId("Points.Forbidden") and returns its failure when there is no caller (:43-45), resolves the typed repository (:48), then dispatches on command.Participate to JoinAsync or LeaveAsync (:50-52).
      • -
      • JoinAsync (:59-112): resolve the name first and bail on failure (:64-66); read both the active and the soft-deleted opt-ins for the user, tracked (:71-74); if any is active, return success without writing (:78-79), because re-publishing the name on every call would let a name change ride in on a no-op request (:76-77); otherwise reactivate the soft-deleted row (:84-89) or create the first one (:90-97); then save inside the detector-filtered try (:99-109), which converts a lost insert race into the same success the already-on-board path returns, logging at Debug (:108). The comment (:105-107) frames it as intent satisfaction: being on the board is what was asked for.
      • -
      • LeaveAsync (:118-136): note the parameter type, IEntityQuerier<TEntity, TIdentifierType> rather than the full IRepository<TEntity, TIdentifierType> (:119), because this branch only reads and then mutates the aggregate it already holds. A filtered, tracked read (:123-126), success without writing when there is nothing active (:127-128), otherwise active.Delete() (:130) and save (:134). Soft-delete, not erasure, which is what leaves the row available for the next reactivate.
      • -
      • ResolveDisplayName(user) (:154-185): tries ClaimTypes.Name, then the raw name, then unique_name (:156-158), because inbound claim mapping is not uniform across the hosts that serve this module (:142-145). An empty result is an Error.Validation("Points.DisplayNameUnavailable", ...) (:163-168) rather than a placeholder, since "Attendee" or an email fragment would be meaningless or would leak something the attendee never chose to publish (:146-150). Over-long names are cut at LeaderboardOptIn.DisplayNameMaxLength (:170-184), backing off one index when the cut would split a surrogate pair (:179-182); the comment (:175-177) names the visible symptom it prevents, a U+FFFD in the public leaderboard broadcast.
      • -
      • FindClaimValue(user, claimType) (:193-194): a null-tolerant one-liner over ClaimsPrincipal.
      • +
      • HandleAsync(command, cancellationToken) (:38-54): null-guards the command (:42), calls currentUserService.RequireUserId("Points.Forbidden") and returns its failure when there is no caller (:44-46), resolves the typed repository (:49), then dispatches on command.Participate to JoinAsync or LeaveAsync (:51-53).
      • +
      • JoinAsync (:60-124): resolve the name first and bail on failure (:65-67); read both the active and the soft-deleted opt-ins for the user, tracked (:72-75); if any is active, return success without writing (:79-80), because re-publishing the name on every call would let a name change ride in on a no-op request (:77-78); otherwise, for a soft-deleted row, refuse an erased one with Points.AccountErased (:90-96) and reactivate any other (:98-100), or create the first one (:102-109); then save inside the detector-filtered try (:111-121), which converts a lost insert race into the same success the already-on-board path returns, logging at Debug (:120). The comment (:117-119) frames it as intent satisfaction: being on the board is what was asked for.
      • +
      • LeaveAsync (:130-148): note the parameter type, IEntityQuerier<TEntity, TIdentifierType> rather than the full IRepository<TEntity, TIdentifierType> (:131), because this branch only reads and then mutates the aggregate it already holds. A filtered, tracked read (:135-138), success without writing when there is nothing active (:139-140), otherwise active.Delete() (:142) and save (:146). Soft-delete, not erasure, which is what leaves the row available for the next reactivate.
      • +
      • ResolveDisplayName(user) (:166-197): tries ClaimTypes.Name, then the raw name, then unique_name (:168-170), because inbound claim mapping is not uniform across the hosts that serve this module (:154-157). An empty result is an Error.Validation("Points.DisplayNameUnavailable", ...) (:173-180) rather than a placeholder, since "Attendee" or an email fragment would be meaningless or would leak something the attendee never chose to publish (:159-161). Over-long names are cut at LeaderboardOptIn.DisplayNameMaxLength (:182-196), backing off one index when the cut would split a surrogate pair (:190-194); the comment (:187-189) names the visible symptom it prevents, a U+FFFD in the public leaderboard broadcast.
      • +
      • FindClaimValue(user, claimType) (:205-206): a null-tolerant one-liner over ClaimsPrincipal.
    • Why it's built this way: every branch that finds the world already in the requested state returns Result.Success() without writing, so the command is idempotent from the client's point of view and safe to retry. [Rubric §6, CQRS & Event-Driven]. Note what the handler does not do: it carries no transactional marker, so the CQRS decorator pipeline opens no transaction around it, which is consistent with each branch performing at most one save.
    • @@ -3227,33 +3178,6 @@

      UserSessionBookmarkDTOMapper

    • Where it's used: auto-registered by the module's convention scan and injected directly by CreateBookmarkHandler (CreateBookmarkHandler.cs:20, used at :90) and GetUserBookmarksHandler (GetUserBookmarksHandler.cs:21, used at :72); also resolved by the generic EntityQueryService<TEntity, TEntityDTO, TIdentifierType> registered for bookmarks (DependencyInjection.cs:39) behind BookmarksController. Unit-tested by UserSessionBookmarkDTOMapperTests.

    -

    LivePollConfiguration

    -
    -

    MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.LivePolls · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/LivePolls/LivePollConfiguration.cs:16 · Level 9 · class (internal sealed)

    -
    -
      -
    • What it is: the EF Core mapping for the LivePoll aggregate root: column requirements, the question length limit, the two query indexes, and the access mode EF uses to materialize the encapsulated options collection.
    • -
    • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> (the shared base that maps audit fields, soft-delete and RowVersion), LivePoll, and LivePollInvariants for QuestionMaxLength. Externals: EF Core's EntityTypeBuilder<T> and PropertyAccessMode.
    • -
    • Concept introduced, the no-cross-database foreign key rule. The base class itself is taught in Group 07; what this file makes concrete is the remark at LivePollConfiguration.cs:11-15: EventId and SessionId point at Conference-owned rows in a different database, so they stay plain indexed scalar columns and consistency flows through the Conference gRPC validation boundary, never through an FK constraint. [Rubric §7, Microservices Readiness] assesses schema independence between services, which is exactly what the scalar-reference choice buys. [Rubric §8, Data Architecture] assesses the persistence contract (nullability, lengths, indexes) and the database-per-service discipline (ADR-006); all of it is visible in a dozen lines here, and it is the same rule AttendeeBadgeConfiguration states for UserId.
    • -
    • Concept introduced, pinning EF's access mode for an encapsulated collection. [Rubric §4, DDD] assesses whether the aggregate keeps its collections closed to outside mutation. LivePoll exposes Options as a getter over a private List returned through AsReadOnly(), which means EF must read and write the backing field, never the property, or materialization silently produces nothing. EF's convention already infers field access for this shape; :49-50 states it anyway with builder.Navigation(p => p.Options).UsePropertyAccessMode(PropertyAccessMode.Field). The comment at :43-48 gives the reason: stating it makes the mapping independent of that inference, so a later change to the property (a different projection, a computed wrapper) cannot silently turn materialization into a no-op. [Rubric §15, Best Practices & Code Quality] assesses exactly this kind of fail-loudly-later choice. It is an access mode only, no schema change: the relationship itself stays configured from the child side in LivePollOptionConfiguration.
    • -
    • Walkthrough (LivePollConfiguration.cs:20-51): Configure (:20) calls base.Configure(builder) first (:22) so the common conventions land before any override; EventId required (:24-25); Question required with HasMaxLength(LivePollInvariants.QuestionMaxLength) (:27-29), sourcing the length from the domain constant so schema and invariant can never disagree; Status required (:31-32). Then HasIndex(p => p.EventId) (:35), non-unique, because the Happening Now page and the organizer manage view both query polls by event (:34); and HasIndex(p => new { p.SessionId, p.Status }) (:41), the composite added for GetOpenPollsHandler. Its comment (:37-40) is the performance record: that query filters on (SessionId, Status), runs once per attendee per session and again on every structural poll event, and only EventId was indexed, so it scanned. It deliberately mirrors SessionQuestionConfiguration, which already indexes the same pair. [Rubric §12, Performance & Scalability] assesses whether indexes match the real query shapes of the hot path. The file closes with the Navigation(...).UsePropertyAccessMode(PropertyAccessMode.Field) call described above (:49-50).
    • -
    • Why it's built this way: one length constant sourced from the domain, scalar cross-service references per ADR-006, indexes derived from the two live-layer read shapes rather than guessed, and an explicitly stated access mode so the aggregate's encapsulation and EF's materialization cannot drift apart.
    • -
    • Where it's used: discovered and applied at model-build time by the Engagement SQLServerDbContext through EF Core's configuration scanning; it is internal, so nothing else can reach it. Covered by EngagementEntityConfigurationTests (MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure.Tests/Persistence/EngagementEntityConfigurationTests.cs).
    • -
    -
    -

    LivePollOptionConfiguration

    -
    -

    MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.LivePolls · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/LivePolls/LivePollOptionConfiguration.cs:10 · Level 9 · class (internal sealed)

    -
    -
      -
    • What it is: the EF Core mapping for the LivePollOption child entity: its text limit and its real relationship back to LivePoll.
    • -
    • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, LivePollOption, and LivePollInvariants for OptionTextMaxLength. Externals: EF Core's EntityTypeBuilder<T>.
    • -
    • Concept reinforced, the in-aggregate child. Read this directly against LivePollConfiguration: there, a cross-service reference stays a bare scalar; here, both ends are Engagement-owned and in the same database, so the option gets a genuine navigation and a genuine foreign key (LivePollOptionConfiguration.cs:22-25). [Rubric §8, Data Architecture]: the contrast is the lesson, an FK is correct precisely when the constraint can be enforced by one database.
    • -
    • Walkthrough (LivePollOptionConfiguration.cs:14-26): Configure (:14) calls base.Configure (:16), then makes Text required with HasMaxLength(LivePollInvariants.OptionTextMaxLength) (:18-20); HasOne(o => o.LivePoll).WithMany(p => p.Options).HasForeignKey(o => o.LivePollId).IsRequired() (:22-25) is the required one-poll-to-many-options relationship inside the aggregate boundary, configured from the child side, which is why LivePollConfiguration only has to state the collection's access mode.
    • -
    • Why it's built this way: an option has no meaning without its poll, so the database is allowed to say so; the length again comes from the domain constant rather than a repeated literal.
    • -
    • Where it's used: applied by the Engagement SQLServerDbContext at model build; the collection it maps is rehydrated on query paths by LivePollNavigationPopulator and eager-loaded on the delete path by DeleteLivePollHandler. Covered by EngagementEntityConfigurationTests (MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure.Tests/Persistence/EngagementEntityConfigurationTests.cs).
    • -
    -

    BookmarkManagementDomainService

    MMCA.ADC.Engagement.Domain · MMCA.ADC.Engagement.Domain.UserSessionBookmarks · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Domain/UserSessionBookmarks/BookmarkManagementDomainService.cs:11 · Level 8 · class (static)

    @@ -3267,20 +3191,6 @@

    BookmarkManagementDomainService

  • Where it's used: CreateBookmarkHandler calls the static method directly after querying for a soft-deleted match, BookmarkManagementDomainService.CreateOrReactivate(deletedBookmark, command.UserId, command.SessionId) (CreateBookmarkHandler.cs:56), and only adds the returned entity to the repository when there was no prior row to revive (CreateBookmarkHandler.cs:61-64). A concurrent insert that gets past the pre-check surfaces as a unique-index violation the handler catches via uniqueConstraintViolationDetector.IsUniqueConstraintViolation(exception) and translates back into the same UserSessionBookmark.Duplicate conflict error (CreateBookmarkHandler.cs:70-82).

  • -

    CheckInConfiguration

    -
    -

    MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.CheckIns · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/CheckIns/CheckInConfiguration.cs:19 · Level 11 · class (internal sealed)

    -
    -
      -
    • What it is: the EF Core mapping for CheckIn, the one aggregate that carries all three check-in scopes (event arrival, session attendance, sponsor booth visit). It is the most constraint-dense configuration in the module: three scope-partitioned unique indexes plus two read indexes.
    • -
    • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, CheckIn, CheckInScope (by value, not by reference, see below), and IndexBuilderExtensions. Externals: EF Core's EntityTypeBuilder<T>.
    • -
    • Concept introduced, partitioning one table by a discriminator through filtered indexes. [Rubric §8, Data Architecture], [Rubric §4, DDD]. One aggregate covers every scope because the row shape, the idempotency rule and the attendance query are identical and only the required target differs (CheckInScope documents this, and CheckInInvariants enforces it). The cost is that a single unique index cannot express "one per attendee per event, and separately one per attendee per session": the three uniqueness rules must not collide with each other. Each index therefore carries its own scope predicate through HasSoftDeleteFilter(additionalFilter: ...) (CheckInConfiguration.cs:51, :56, :62), and the extension ANDs that predicate in front of the soft-delete one (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/IndexBuilderExtensions.cs:63-65). The three scope literals are hoisted into private const string fields (:24-26) with a comment explaining why they are literals at all (:22-23): an index filter is SQL, so it cannot read the C# enum. That is also why the enum's numeric values are documented as load-bearing and never renumbered.
    • -
    • Concept, the database as the concurrency backstop behind an application-level check. [Rubric §29, Resilience & Business Continuity], [Rubric §6, CQRS & Event-Driven]. The remarks (:11-18) describe the two layers together: CheckInProcessor makes a repeat scan a no-op by looking for the existing row first (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/CheckIns/Services/CheckInProcessor.cs:71-75), and these indexes make a concurrent double scan a DbUpdateException instead of two rows, so the broker retry that follows lands on the no-op path.
    • -
    • Walkthrough (CheckInConfiguration.cs:29-70): base.Configure(builder) (:31); five required properties, UserId (:33-34), Scope (:36-37), EventId (:39-40), CheckedInByUserId (:42-43), CheckedInOn (:45-46), note that SessionId and SponsorId are deliberately not required, since each is meaningful only in its own scope; then the three filtered unique indexes, (UserId, EventId) under the event scope (:49-51), (UserId, SessionId) under the session scope (:54-56), and (UserId, SponsorId) under the sponsor scope (:60-62), the last being the once-per-sponsor cap that makes a shared deep link worth nothing beyond the first scan (:58-59); finally two non-unique, soft-delete-filtered read indexes on EventId (:65-66) and SessionId (:68-69), because attendance stats read by event and group by session (:64).
    • -
    • Why it's built this way: sponsor visits are self-recorded by the attendee from a printed QR, so the cap has to be structural rather than procedural. Keeping the identifiers scalar (:16-17) is the same database-per-service rule the rest of the module follows (ADR-006).
    • -
    • Where it's used: applied to the Engagement model by assembly scanning. Its indexes are the contract every check-in writer is written against: all four handlers route through CheckInProcessor, the organizer paths CheckInAttendeeHandler (CheckInAttendeeHandler.cs:60) and ManualCheckInHandler (ManualCheckInHandler.cs:40) through ExecuteAsync (CheckInProcessor.cs:109), and the self-service paths RecordRoomCheckInHandler (RecordRoomCheckInHandler.cs:74) and RecordSponsorVisitHandler (RecordSponsorVisitHandler.cs:72) through RecordAsync (CheckInProcessor.cs:58). The rows it maps are exported by UserEngagementExportService and travel to Identity through UserEngagementExportGrpcService.
    • -
    -

    BookmarkCountServiceGrpcAdapter

    MMCA.ADC.Engagement.Contracts · MMCA.ADC.Engagement.Contracts · MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Contracts/BookmarkCountServiceGrpcAdapter.cs:14 · Level 9 · class (internal sealed)

    @@ -3295,7 +3205,7 @@

    BookmarkCountServiceGrpcAdapter

  • GetBookmarkCountsForSessionsAsync(sessionIds, cancellationToken) (:39-52): the batch form. It fills the repeated SessionIds field with AddRange (:43-44), sends the same deadline (:48), and rebuilds a dictionary from the repeated response pairs with response.Counts.ToDictionary(c => c.SessionId, c => c.Count) (:51). This is the N+1 avoidance for a speaker dashboard listing many sessions: one round trip instead of one per session.
  • -
  • Where it's used: registered by DependencyInjection.AddEngagementBookmarkCountClient(), which the Conference service calls at MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:409. Its server-side twin is BookmarkCountsGrpcService.
  • +
  • Where it's used: registered by DependencyInjection.AddEngagementBookmarkCountClient(), which the Conference service calls at MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:411. Its server-side twin is BookmarkCountsGrpcService.
  • Caveats / not-in-source: the dictionary rebuild assumes the server never returns duplicate SessionId pairs; ToDictionary would throw on a duplicate key. Nothing in this file enforces that, it rests on the server projecting from a dictionary in the first place (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Grpc/BookmarkCountsGrpcService.cs:55-59).

  • @@ -3307,15 +3217,15 @@

    BookmarkCountsGrpcService

  • What it is: the server-side gRPC endpoint that publishes Engagement's in-process IBookmarkCountService over the wire. It is the counterpart of BookmarkCountServiceGrpcAdapter: the adapter runs inside Conference and speaks gRPC out, this service runs inside Engagement and answers.
  • Depends on: IBookmarkCountService (injected via primary constructor, :24), and the generated proto base BookmarkCountService.BookmarkCountServiceBase plus the four messages from bookmark_count.proto (namespace MMCA.ADC.Engagement.Contracts.V1, :2). Externals: Grpc.Core (ServerCallContext).
  • Concept, the server-side bridge. [Rubric §7, Microservices Readiness], [Rubric §9, API & Contract Design]. This is the producer half of the pattern whose consumer half is taught in Group 13: the module keeps one C# interface, the proto generates an abstract server base, and this thin subclass forwards one to the other. Because the interface did not change when Engagement moved out of the monolith, neither Conference's call sites nor Engagement's BookmarkCountService had to change; only this bridge and its client twin were added. It mirrors, on the Engagement side, Conference's SessionBookmarksGrpcService, completing the bidirectional Conference-Engagement gRPC pair.
  • -
  • Concept, a documented unauthenticated trust boundary. [Rubric §11, Security] assesses whether every exposed surface has a stated authorization posture. This endpoint is mapped without .RequireAuthorization() (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:342, with the reasoning restated at the call site, :326-332), and the class doc (:11-22) argues why the requirement is not addable rather than merely absent: the Conference callers serve [AllowAnonymous] output-cached REST endpoints (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerSessionsController.cs:67 and :87), so a cache-miss request from an anonymous visitor carries no bearer for JwtForwardingClientInterceptor to forward, and the public speaker pages would break. The exposure is bounded by the payload (aggregate counts per session id, no PII and no user ids on the wire) and by the network boundary: the Engagement container app's ingress is external: false (MMCA.ADC/infra/main.bicep:1921), so the endpoint stays on the internal service network and the Gateway never routes it. Contrast UserEngagementExportGrpcService, which is mapped .RequireAuthorization() (Program.cs:357) because it carries personal data.
  • +
  • Concept, a documented unauthenticated trust boundary. [Rubric §11, Security] assesses whether every exposed surface has a stated authorization posture. This endpoint is mapped without .RequireAuthorization() (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:334, with the reasoning restated at the call site, :326-332), and the class doc (:11-22) argues why the requirement is not addable rather than merely absent: the Conference callers serve [AllowAnonymous] output-cached REST endpoints (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Speakers/SpeakerSessionsController.cs:67 and :87), so a cache-miss request from an anonymous visitor carries no bearer for JwtForwardingClientInterceptor to forward, and the public speaker pages would break. The exposure is bounded by the payload (aggregate counts per session id, no PII and no user ids on the wire) and by the network boundary: the Engagement container app's ingress is external: false (MMCA.ADC/infra/main.bicep:1921), so the endpoint stays on the internal service network and the Gateway never routes it. Contrast UserEngagementExportGrpcService, which is mapped .RequireAuthorization() (Program.cs:349) because it carries personal data.
  • Walkthrough
    • GetBookmarkCountForSession(request, context) (:28-40): null-guards both arguments with ArgumentNullException.ThrowIfNull (:32-33), awaits inner.GetBookmarkCountForSessionAsync(request.SessionId, context.CancellationToken) (:35-37), threading the gRPC call's own cancellation token (and therefore the client's deadline) through to the database, then wraps the int in a response message (:39).
    • GetBookmarkCountsForSessions(request, context) (:43-61): the batch rpc. It materializes the repeated field with a collection expression, [.. request.SessionIds] (:51), calls the interface's batch method, then projects the returned dictionary into repeated SessionBookmarkCount messages via AddRange(... Select(...)) (:55-59).
  • -
  • Why it's built this way: keeping the wire endpoint a paper-thin forwarder means the real read logic (a COUNT under the soft-delete filter, over the SessionId index UserSessionBookmarkConfiguration declares) lives once and is reused identically in-process and over gRPC. The endpoint is published through AddGrpcServiceDefaults() (Program.cs:298), which also installs the GrpcResultExceptionInterceptor and gRPC reflection; for these methods the interceptor has nothing to translate, since the interface returns bare values rather than a Result.
  • -
  • Where it's used: mapped at MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:342, served over the service's Http2-only cleartext (h2c) endpoint (ADR-012). Its remote caller is Conference.
  • -
  • Caveats / not-in-source: the class doc's own citations have drifted further. It names SpeakersController.cs:385 and :405 (:15-16) and infra/main.bicep:1289-1295 (:20), but the anonymous bookmark-count actions it describes have since moved out of SpeakersController.cs entirely, to SpeakerSessionsController.cs:67 and :87, and the Engagement internal ingress is now at infra/main.bicep:1921. The argument still holds; only the citations are stale.
  • +
  • Why it's built this way: keeping the wire endpoint a paper-thin forwarder means the real read logic (a COUNT under the soft-delete filter, over the SessionId index UserSessionBookmarkConfiguration declares) lives once and is reused identically in-process and over gRPC. The endpoint is published through AddGrpcServiceDefaults() (Program.cs:300), which also installs the GrpcResultExceptionInterceptor and gRPC reflection; for these methods the interceptor has nothing to translate, since the interface returns bare values rather than a Result.
  • +
  • Where it's used: mapped at MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:334, served over the service's Http2-only cleartext (h2c) endpoint (ADR-012). Its remote caller is Conference.
  • +
  • Caveats / not-in-source: the class doc's own citations have drifted further. It names lines 385 and 405 of SpeakersController.cs (:15-16), a file that now has only 374 lines, and infra/main.bicep:1289-1295 (:20), but the anonymous bookmark-count actions it describes have since moved out of SpeakersController.cs entirely, to SpeakerSessionsController.cs:67 and :87, and the Engagement internal ingress is now at infra/main.bicep:1921. The argument still holds; only the citations are stale.

  • LiveEventListener

    @@ -3324,26 +3234,26 @@

    LiveEventListener

    • What it is: the module's invisible layout component. It renders no markup; on the first render of an authenticated circuit it resolves the current event and, while that event is inside its live window, joins the event-wide live channel so a poll opening reaches the attendee on whatever page they happen to be on. The .razor half is only a comment wrapped in CascadingAuthenticationState / AuthorizeView / Authorized (LiveEventListener.razor:4-10); all the behavior lives in the code-behind, which does its own authentication check rather than relying on that wrapper.
    • -
    • Depends on: ILiveEventUIService and the LiveEventContext it returns, NotificationHubService (injected as the concrete sealed class, there is no interface in front of it), IToastService and ToastSeverity, LivePollChannel and LivePollOpenedPayload from MMCA.ADC.Engagement.Shared, IBatteryStatusService and IAccessibilityAnnouncer from MMCA.Common.UI.Services.Capabilities, and EngagementRoutePaths. Externals: Blazor's NavigationManager and the cascading Task<AuthenticationState>, IStringLocalizer<LiveEventListener> (injected in the markup, LiveEventListener.razor:2), System.Text.Json, and the [LoggerMessage] source generator (:188-189).
    • -
    • Concept, the ambient listener that must never throw. [Rubric §29, Resilience & Business Continuity] assesses whether a non-essential capability degrades instead of taking the user's session down with it. This component is contributed as a layout component and so is live on every page, and no ErrorBoundary sits above it, which the class remarks state directly (:18-25): an exception escaping its render lifecycle tears down the Blazor circuit and forces a full page reload wherever the user happens to be. The first-render pass is consequently guarded end to end: OperationCanceledException is swallowed as the expected disposal signal (:89-92), and everything else is logged and absorbed behind an inline CA1031 suppression whose justification is written into the pragma itself (:93). The remarks also name the motivating failure mode: the API client's shared resilience pipeline surfaces Polly's TimeoutRejectedException / BrokenCircuitException during a backend brownout, and the service layer's deliberately narrow HttpRequestException catch does not cover those.
    • -
    • Concept, two concurrent holders of one channel. [Rubric §19, State Management] assesses how a shared per-circuit resource is owned when more than one component holds it at once. This listener joins LivePollChannel.ForEvent(liveEvent.EventId) (:70, MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Shared/LivePolls/LivePollChannel.cs:24-25), and the Happening Now page joins the identical key for the same event. Membership therefore cannot be a set: it is reference-counted per key inside NotificationHubService (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Notifications/NotificationHubService.cs:18-21, :186-189, :217-219), so navigating away from Happening Now releases one join and leaves this listener still receiving events on event:{id}.
    • -
    • Concept, capability-aware live UX. [Rubric §21, Accessibility] and [Rubric §27, Internationalization]. Every user-visible string is a resource key resolved through the injected localizer (LiveEventListener.razor:2, ADR-027), and each poll-opened toast is mirrored to the platform's assistive channel through IAccessibilityAnnouncer (:160) so a screen-reader user gets the same event a sighted user sees.
    • +
    • Depends on: ILiveEventUIService and the LiveEventContext it returns, NotificationHubService (injected as the concrete sealed class, there is no interface in front of it), IToastService and ToastSeverity, LivePollChannel and LivePollOpenedPayload from MMCA.ADC.Engagement.Shared, IBatteryStatusService and IAccessibilityAnnouncer from MMCA.Common.UI.Services.Capabilities, and EngagementRoutePaths. Externals: Blazor's NavigationManager and the cascading Task<AuthenticationState>, IStringLocalizer<LiveEventListener> (injected in the markup, LiveEventListener.razor:2), System.Text.Json, TimeProvider (injected at :38 for the live-window clock), and the [LoggerMessage] source generator (:190-191).
    • +
    • Concept, the ambient listener that must never throw. [Rubric §29, Resilience & Business Continuity] assesses whether a non-essential capability degrades instead of taking the user's session down with it. This component is contributed as a layout component and so is live on every page, and no ErrorBoundary sits above it, which the class remarks state directly (:19-26): an exception escaping its render lifecycle tears down the Blazor circuit and forces a full page reload wherever the user happens to be. The first-render pass is consequently guarded end to end: OperationCanceledException is swallowed as the expected disposal signal (:91-94), and everything else is logged and absorbed behind an inline CA1031 suppression whose justification is written into the pragma itself (:95). The remarks also name the motivating failure mode: the API client's shared resilience pipeline surfaces Polly's TimeoutRejectedException / BrokenCircuitException during a backend brownout, and the service layer's deliberately narrow HttpRequestException catch does not cover those.
    • +
    • Concept, two concurrent holders of one channel. [Rubric §19, State Management] assesses how a shared per-circuit resource is owned when more than one component holds it at once. This listener joins LivePollChannel.ForEvent(liveEvent.EventId) (:72, MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Shared/LivePolls/LivePollChannel.cs:24-25), and the Happening Now page joins the identical key for the same event. Membership therefore cannot be a set: it is reference-counted per key inside NotificationHubService (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Notifications/NotificationHubService.cs:20-23, :186-189, :217-219), so navigating away from Happening Now releases one join and leaves this listener still receiving events on event:{id}.
    • +
    • Concept, capability-aware live UX. [Rubric §21, Accessibility] and [Rubric §27, Internationalization]. Every user-visible string is a resource key resolved through the injected localizer (LiveEventListener.razor:2, ADR-027), and each poll-opened toast is mirrored to the platform's assistive channel through IAccessibilityAnnouncer (:162) so a screen-reader user gets the same event a sighted user sees.
    • Walkthrough
        -
      • PayloadJsonOptions (:28): a static JsonSerializerOptions.Web, so payloads are read with the same web (camelCase) conventions the publisher serialized them with.
      • -
      • Seven [Inject] properties (:30-36): the live-event service, the hub service, the toast service, NavigationManager, the battery-status and accessibility-announcer capabilities, and the typed logger. The [CascadingParameter] Task<AuthenticationState>? AuthState (:38-39) is nullable because the component can be rendered outside a CascadingAuthenticationState.
      • -
      • _subscription and _channelKey (:41-42): the only mutable state, and exactly what disposal needs.
      • -
      • OnAfterRenderAsync(firstRender) (:44-101) is the whole setup path, written as a sequence of early exits: not the first render (:46-49), no cascading auth state (:51-54), an unauthenticated principal (:59-62), and no current event or an event outside its live window per liveEvent.IsLiveAt(DateTime.UtcNow) (:64-68, and MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Services/SessionLive/LiveEventContext.cs:22-23 for the half-open window it tests). Only past those does it compute _channelKey (:70).
      • -
      • The energy-saver branch (:75-85): when Battery.IsEnergySaverOn, the auto-join is skipped and an Info toast offers a "join anyway" action whose callback is JoinChannelGuardedAsync (:79-83), then the method returns without joining. The inline comment (:72-74) is explicit that only the lossy live channel is deferred and the core notification connection is untouched (ADR-042 Wave 3). Otherwise it simply awaits JoinChannelAsync() (:87).
      • -
      • JoinChannelGuardedAsync() (:107-123): the same two catches again, because the toast action fires outside the render callback that registered it and inherits none of that callback's guarding (:103-106, :77-78). IToastService.ShowAction documents the same rule from the other side: exceptions are the caller's to handle (MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/Interfaces/IToastService.cs:96-101).
      • -
      • JoinChannelAsync() (:125-134): an idempotence guard first (_channelKey is null || _subscription is not null, :127-130) so a toast-triggered join after an auto-join cannot double-subscribe, then HubService.OnChannelEvent(_channelKey, HandleChannelEventAsync) (:132) followed by HubService.JoinChannelAsync(_channelKey) (:133). Both calls are required and in that order: subscribing does not join the server-side group, and joining does not register a handler.
      • -
      • HandleChannelEventAsync(eventName, payloadJson) (:136-174): filters to LivePollChannel.PollOpened (the literal poll.opened, LivePollChannel.cs:14) with an ordinal comparison (:138-141), deserializes a LivePollOpenedPayload and returns silently on a JsonException or a null result (:143-156), announces the poll with the question text (:160), then marshals back to the renderer with InvokeAsync to raise the toast (:164-173). That call passes ToastSeverity.Info and requireInteraction: true (:172-173), so the toast is pinned open rather than expiring on the host timer, and it carries an action that navigates to EngagementRoutePaths.HappeningNow, that is /happening-now (:169, MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/EngagementRoutePaths.cs:11).
      • -
      • DisposeAsync() (:177-186): disposes the subscription handle, then leaves the channel when _channelKey was set (:179-183), and calls GC.SuppressFinalize(this) (:185). The two steps are separate because the hub service tracks handlers and reference-counted membership independently.
      • -
      • LogLiveChannelSetupFailed (:188-189): a source-generated Error-level log whose message states the degraded outcome ("the live channel stays dormant for this session"). The catch-all is deliberately silent in the UI, since this component renders nothing and has no place to show a message (:97-98). [Rubric §13, Observability].
      • +
      • PayloadJsonOptions (:29): a static JsonSerializerOptions.Web, so payloads are read with the same web (camelCase) conventions the publisher serialized them with.
      • +
      • Eight [Inject] properties (:31-38): the live-event service, the hub service, the toast service, NavigationManager, the battery-status and accessibility-announcer capabilities, the typed logger, and TimeProvider. The [CascadingParameter] Task<AuthenticationState>? AuthState (:40-41) is nullable because the component can be rendered outside a CascadingAuthenticationState.
      • +
      • _subscription and _channelKey (:43-44): the only mutable state, and exactly what disposal needs.
      • +
      • OnAfterRenderAsync(firstRender) (:46-103) is the whole setup path, written as a sequence of early exits: not the first render (:48-51), no cascading auth state (:53-56), an unauthenticated principal (:61-64), and no current event or an event outside its live window per liveEvent.IsLiveAt(TimeProvider.GetUtcNow().UtcDateTime) (:66-70, and MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Services/SessionLive/LiveEventContext.cs:22-23 for the half-open window it tests). Reading the clock through the injected TimeProvider rather than DateTime.UtcNow lets a test pin "now" (the resilience tests register TimeProvider.System, LiveEventListenerResilienceTests.cs:49). Only past those exits does it compute _channelKey (:72).
      • +
      • The energy-saver branch (:77-87): when Battery.IsEnergySaverOn, the auto-join is skipped and an Info toast offers a "join anyway" action whose callback is JoinChannelGuardedAsync (:81-85), then the method returns without joining. The inline comment (:74-76) is explicit that only the lossy live channel is deferred and the core notification connection is untouched (ADR-042 Wave 3). Otherwise it simply awaits JoinChannelAsync() (:89).
      • +
      • JoinChannelGuardedAsync() (:109-125): the same two catches again, because the toast action fires outside the render callback that registered it and inherits none of that callback's guarding (:105-108, :79-80). IToastService.ShowAction documents the same rule from the other side: exceptions are the caller's to handle (MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/Interfaces/IToastService.cs:96-101).
      • +
      • JoinChannelAsync() (:127-136): an idempotence guard first (_channelKey is null || _subscription is not null, :129-132) so a toast-triggered join after an auto-join cannot double-subscribe, then HubService.OnChannelEvent(_channelKey, HandleChannelEventAsync) (:134) followed by HubService.JoinChannelAsync(_channelKey) (:135). Both calls are required and in that order: subscribing does not join the server-side group, and joining does not register a handler.
      • +
      • HandleChannelEventAsync(eventName, payloadJson) (:138-176): filters to LivePollChannel.PollOpened (the literal poll.opened, LivePollChannel.cs:14) with an ordinal comparison (:140-143), deserializes a LivePollOpenedPayload and returns silently on a JsonException or a null result (:145-158), announces the poll with the question text (:162), then marshals back to the renderer with InvokeAsync to raise the toast (:166-175). That call passes ToastSeverity.Info and requireInteraction: true (:174-175), so the toast is pinned open rather than expiring on the host timer, and it carries an action that navigates to EngagementRoutePaths.HappeningNow, that is /happening-now (:171, MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/EngagementRoutePaths.cs:11).
      • +
      • DisposeAsync() (:179-188): disposes the subscription handle, then leaves the channel when _channelKey was set (:182-185), and calls GC.SuppressFinalize(this) (:187). The two steps are separate because the hub service tracks handlers and reference-counted membership independently.
      • +
      • LogLiveChannelSetupFailed (:190-191): a source-generated Error-level log whose message states the degraded outcome ("the live channel stays dormant for this session"). The catch-all is deliberately silent in the UI, since this component renders nothing and has no place to show a message (:99-100). [Rubric §13, Observability].
    • Why it's built this way: the live layer is best-effort by design, so every failure on this path degrades to "no live channel" rather than surfacing to the user, and the authoritative data is always one page load away. The energy-saver deferral and the screen-reader announcement are the two device-capability touch points, both reached through interfaces whose web fallbacks are no-ops, so the same component runs unchanged on Blazor Server, WebAssembly, and the MAUI hybrid head.
    • Where it's used: contributed by EngagementUIModule as its single LayoutComponentTypes entry (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/EngagementUIModule.cs:31) and rendered by the shared MainLayout on every page; regression-tested by LiveEventListenerResilienceTests (MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.UI.Tests/Components/LiveEventListenerResilienceTests.cs).
    • -
    • Caveats / not-in-source: the battery state is read once, on first render (:75), and the component never subscribes to an energy-saver-changed notification, so turning energy saver off later does not auto-join; the toast action is the only way in for that session. The leave call in DisposeAsync carries no try/catch of its own, it relies on the hub service handling its own failures. The localizer L is declared only in the markup (LiveEventListener.razor:2), so it is invisible in the code-behind despite being used throughout it.
    • +
    • Caveats / not-in-source: the battery state is read once, on first render (:77), and the component never subscribes to an energy-saver-changed notification, so turning energy saver off later does not auto-join; the toast action is the only way in for that session. The leave call in DisposeAsync carries no try/catch of its own, it relies on the hub service handling its own failures. The localizer L is declared only in the markup (LiveEventListener.razor:2), so it is invisible in the code-behind despite being used throughout it.

    CheckInDTOMapper

    @@ -3381,7 +3291,7 @@

    UserEngagementExportService

  • Why it's built this way: projecting to ids, enums, and dates server-side keeps the export cheap to build and cheap to ship across a service boundary, and it structurally prevents content authored by other users from leaking into one subject's document (UserEngagementExportDTO states that rule explicitly, MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Shared/Exports/UserEngagementExportDTO.cs:3-8). Returning a plain Task<T> rather than a Result<T> is what lets the aggregating side degrade: there is no business failure to model here, so an unreachable peer surfaces as an exception the caller turns into a missing section (ADR-007).
  • -
  • Where it's used: registered by the Engagement Application DI (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/DependencyInjection.cs:47, TryAddScoped); exposed over the wire by UserEngagementExportGrpcService, which wraps this in-process instance (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Grpc/UserEngagementExportGrpcService.cs:23). The consumer is Identity's EngagementUserDataExportSection (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ExportUserData/EngagementUserDataExportSection.cs:19), which reaches it through UserEngagementExportServiceGrpcAdapter and re-projects the DTO into the Identity export document, rendering ActivityType and Scope as their readable names (EngagementUserDataExportSection.cs:51, :60), behind ExportUserDataHandler. When the module is switched off in a host, DisabledUserEngagementExportService stands in (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.API/EngagementModule.cs:33).
  • +
  • Where it's used: registered by the Engagement Application DI (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/DependencyInjection.cs:47, TryAddScoped); exposed over the wire by UserEngagementExportGrpcService, which wraps this in-process instance (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Grpc/UserEngagementExportGrpcService.cs:24). The consumer is Identity's EngagementUserDataExportSection (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ExportUserData/EngagementUserDataExportSection.cs:19), which reaches it through UserEngagementExportServiceGrpcAdapter and re-projects the DTO into the Identity export document, rendering ActivityType and Scope as their readable names (EngagementUserDataExportSection.cs:51, :60), behind ExportUserDataHandler. When the module is switched off in a host, DisabledUserEngagementExportService stands in (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.API/EngagementModule.cs:33).
  • Caveats / not-in-source: erasure is not this class's job and is not visible here, it only reads. Whether the export is additionally rate-limited or audited when it runs in production is not determinable from this file.

  • @@ -3422,11 +3332,48 @@

    DependencyInjection

  • Why it's built this way: the default serviceName = "engagement" matches the AppHost resource name, so the common call site is argument-free. [Rubric §33, Developer Experience]. The XML docs on both methods (:35-39, :67-72) spell out the one ordering requirement: call them after ModuleLoader.DiscoverAndRegister(...), so the in-process or stub registration is already in the container for Replace to find. That is an ordering constraint the compiler cannot enforce, which is why it is written twice.

  • -
  • Where it's used: MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:409 calls AddEngagementBookmarkCountClient(); MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:316 calls AddEngagementUserExportClient(). The AppHost documents both edges of the resulting topology, including why the Conference-to-Engagement reverse edge gets no reciprocal WaitFor (MMCA.ADC/Source/Hosting/MMCA.ADC.AppHost/Program.cs:272-274, against the Engagement-to-Conference edge at :270), and the Identity-to-Engagement edge at :291.

    +
  • Where it's used: MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:411 calls AddEngagementBookmarkCountClient(); MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:315 calls AddEngagementUserExportClient(). The AppHost documents both edges of the resulting topology, including why the Conference-to-Engagement reverse edge gets no reciprocal WaitFor (MMCA.ADC/Source/Hosting/MMCA.ADC.AppHost/Program.cs:272-274, against the Engagement-to-Conference edge at :270), and the Identity-to-Engagement edge at :291.

    +
  • +
  • Caveats / not-in-source: neither method sets a per-call deadline; the 5-second budgets live in the two adapters themselves (BookmarkCountServiceGrpcAdapter.cs:20, UserEngagementExportServiceGrpcAdapter.cs:25), so a host that registered a different implementation of the same interfaces would not inherit them.

  • -
  • Caveats / not-in-source: neither method sets a per-call deadline; the 5-second budgets live in the two adapters themselves (BookmarkCountServiceGrpcAdapter.cs:20, UserEngagementExportServiceGrpcAdapter.cs:24), so a host that registered a different implementation of the same interfaces would not inherit them.

    + +

    UserEngagementExportGrpcService

    +
    +

    MMCA.ADC.Engagement.Service · MMCA.ADC.Engagement.Service.Grpc · MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Grpc/UserEngagementExportGrpcService.cs:24 · Level 12 · class (public sealed)

    +
    +
      +
    • What it is: the server-side gRPC endpoint that publishes Engagement's in-process IUserEngagementExportService to the Identity service, so Identity can fold a user's Engagement-owned personal data into the aggregated data-subject export document. Same server-bridge shape as BookmarkCountsGrpcService, with the extra concern that timestamps and enums have to survive a proto3 wire crossing.
    • +
    • Depends on: IUserEngagementExportService (primary constructor, :24) and its DTOs from MMCA.ADC.Engagement.Shared.Exports (:4); the generated base UserEngagementExportService.UserEngagementExportServiceBase and the messages from user_engagement_export.proto (namespace MMCA.ADC.Engagement.Contracts.V1, :3); the framework's GrpcWireFormat from MMCA.Common.Grpc (:5). Externals: Grpc.Core (ServerCallContext), System.Globalization (:1).
    • +
    • Concept, cross-service data-subject export as a purpose-built read contract. [Rubric §30, Compliance, Privacy & Data Governance] assesses whether a real data-subject-access and portability path exists; [Rubric §7, Microservices Readiness] assesses talking through explicit contracts. Identity owns the export document but not Engagement's data, so it depends only on the narrow interface; this class is the producer half over the wire, twin to the client-side UserEngagementExportServiceGrpcAdapter. The class doc (:9-16) enumerates what Engagement contributes: session bookmarks, submitted session questions, the points ledger, leaderboard participation, and check-in history. Unlike the bookmark-count endpoint, this one is mapped .RequireAuthorization() (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:349, with the reasoning at :346-348), which is the posture difference personal data justifies. [Rubric §11, Security].
    • +
    • Concept, proto3 has no nulls and no DateTime. [Rubric §9, API & Contract Design]. Three encoding decisions are made here and mirrored exactly on the client:
        +
      • Timestamps: SQL Server hands back DateTimeKind.Unspecified values, and the round-trip "O" format omits the Z marker for that kind, so each value goes through GrpcWireFormat.FormatUtc (:50, :56, :65), which stamps DateTimeKind.Utc before formatting (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/GrpcWireFormat.cs:42-43). The format is written once in the framework and called by both ends of the contract. The doc comment (:17-22) is careful about what this is: the stored values are already UTC, so this restores a marker rather than converting an instant. The one DateTimeOffset on the contract, CheckedInOn, already carries its offset and is formatted as-is with "O" (:79-81).
      • +
      • Absent values: an absent leaderboard name travels as the empty string (:43-45) and an absent SessionId/SponsorId as 0 (:74-77), each with the comment naming the client-side inverse.
      • +
      • Enums: ActivityType (:60-62) and Scope (:69-71) cross as their numeric values, justified in place, the points activity numbers are part of the idempotency key and the scope numbers are named literally in the SQL index filters (CheckInConfiguration, CheckInConfiguration.cs:24-26), so neither is ever renumbered.
      • +
      +
    • +
    • Walkthrough: one overridden method, GetUserEngagementExport(request, context) (:28-85). It null-guards both arguments (:32-33), awaits inner.GetUserEngagementExportAsync(request.UserId, context.CancellationToken) (:35-37), then builds the response: the two scalar leaderboard fields in the initializer (:39-46), then four AddRange(... Select(...)) projections filling the repeated fields, bookmarks (:47-51), submitted questions (:52-57), points entries (:58-66) and check-ins (:67-82).
    • +
    • Why it's built this way: keeping the endpoint a thin forwarder leaves the real work (server-side projections that the soft-delete query filter already narrows to active rows) in UserEngagementExportService, reused identically in-process and over gRPC. Published via AddGrpcServiceDefaults() (Program.cs:300); because the read returns a DTO rather than a Result, the GrpcResultExceptionInterceptor has no failure trailer to translate and a transport fault becomes a gRPC status the caller's resilience pipeline handles.
    • +
    • Where it's used: mapped at MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:349. Its remote caller is Identity's ExportUserDataHandler via UserEngagementExportServiceGrpcAdapter. Covered by UserEngagementExportGrpcServiceTests.
    • +
    +
    +

    UserEngagementExportServiceGrpcAdapter

    +
    +

    MMCA.ADC.Engagement.Contracts · MMCA.ADC.Engagement.Contracts · MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Contracts/UserEngagementExportServiceGrpcAdapter.cs:19 · Level 12 · class (internal sealed)

    +
    +
      +
    • What it is: the client-side gRPC adapter for the data-subject export, the remote twin of the in-process UserEngagementExportService. It implements IUserEngagementExportService on top of the generated export client and is what Identity resolves.
    • +
    • Depends on: IUserEngagementExportService and its DTOs from MMCA.ADC.Engagement.Shared.Exports, plus CheckInScope and PointsActivityType from the module's Shared project (:3-5); the generated UserEngagementExportService.UserEngagementExportServiceClient and its messages (:2); the framework's GrpcWireFormat (:6). Externals: System.Globalization (:1), the Grpc.Net client factory.
    • +
    • Concept, the same-interface transport swap plus wire rehydration. [Rubric §7, Microservices Readiness], [Rubric §9, API & Contract Design]. This is the export sibling of BookmarkCountServiceGrpcAdapter: the same interface as the in-process implementation, so consumers swap one DI registration (ADR-007). It also owns the inverse of every encoding decision the server made, and each inverse is written next to the decision it undoes.
    • +
    • Concept, parsing defensively across a rolling deploy. [Rubric §29, Resilience & Business Continuity]. The three DateTime fields are read with GrpcWireFormat.ParseUtc (:45, :51, :60), which parses with DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal (MMCA.Common/Source/Presentation/MMCA.Common.Grpc/GrpcWireFormat.cs:37, :61-62) so that a peer replica still emitting the marker-less form (new Identity code running against an old Engagement replica during a rolling deploy) does not yield Kind=Unspecified, and AssumeUniversal alone would yield Kind=Local. The comment there (GrpcWireFormat.cs:32-36) also records why RoundtripKind is deliberately absent: DateTime.Parse rejects it alongside either Assume* or AdjustToUniversal with an ArgumentException, and its purpose (preserving a non-UTC kind) is the opposite of what this contract wants. The one parser left in this file, ParseRoundtripOffset (:88-92), keeps the offset the server emitted rather than folding it into UTC, with AssumeUniversal covering an offset-less value from an old peer (:85-87).
    • +
    • Walkthrough
        +
      • CallDeadline (:25), a 5-second static, applied at :37. The comment (:22-24) gives the same reasoning as the bookmark adapter with an export-specific twist: the aggregation is best-effort per section, so a hung peer should degrade its section quickly rather than stall the export request.
      • +
      • GetUserEngagementExportAsync(userId, cancellationToken) (:28-83): sends a GetUserEngagementExportRequest { UserId = userId } with the deadline and the caller's token (:32-38), then rebuilds UserEngagementExportDTO from the four repeated fields with collection expressions: bookmarks (:42-46), submitted questions (:47-52), points entries (:53-61, casting the numeric ActivityType back to its enum at :57), and check-ins (:62-73, casting Scope back at :66, mapping a 0 SessionId/SponsorId back to null at :70-71, and parsing CheckedInOn as a DateTimeOffset at :72). Finally IsOnLeaderboard (:74) and the empty-string-to-null mapping for LeaderboardDisplayName (:79-81), whose comment (:76-78) notes the same mapping also covers an old peer that never sets the field at all.
      • +
    • +
    • Why it's built this way: the class doc (:10-18) states the resilience posture, transport failures propagate to the caller (Identity's ExportUserDataHandler), which degrades the Engagement section to Available = false rather than failing the whole export. Best-effort cross-service aggregation: a peer outage never sinks an entire data-subject export. [Rubric §30, Compliance, Privacy & Data Governance].
    • +
    • Where it's used: registered by DependencyInjection.AddEngagementUserExportClient(), called from MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:315.
    +

    ModuleApplicationDbContext

    MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.DbContexts · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/DbContexts/ModuleApplicationDbContext.cs:19 · Level 12 · class (abstract)

    @@ -3434,53 +3381,147 @@

    ModuleApplicationDbContext

    • What it is: the Engagement module's EF Core context declaration. It is an abstract class over the framework's ApplicationDbContext that names, as typed DbSet<T> properties, every entity this bounded context owns: the bookmark aggregate, the badge, the check-in, the three live-poll types, the two question types, and the two points types.
    • Depends on: ApplicationDbContext, IEntityConfigurationAssemblyProvider and PhysicalDataSource (all forwarded to the base constructor), plus the ten Engagement entities: UserSessionBookmark, AttendeeBadge, CheckIn, LivePoll, LivePollOption, LivePollVote, SessionQuestion, SessionQuestionUpvote, PointsEntry, LeaderboardOptIn. Externals: EF Core's DbContext, DbSet<T> and DbContextOptions.
    • -
    • Concept introduced, the module's entity inventory is a declaration, not the runtime context. [Rubric §8, Data Architecture] assesses whether persistence structure is deliberate rather than accidental, and [Rubric §3, Clean Architecture] assesses where infrastructure concerns are allowed to live (here: only in *.Infrastructure, never in the domain entities). The instructive detail is what this class does not do. Nothing in the repository derives from it: a repo-wide search for the name finds exactly three declarations, this one plus the sibling per-module classes in Conference (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/DbContexts/ModuleApplicationDbContext.cs:22) and Identity (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Infrastructure/Persistence/DbContexts/ModuleApplicationDbContext.cs:15), with no subclass and no consumer anywhere in MMCA.ADC/Source or MMCA.ADC/Tests. The context that actually runs is Common's sealed SQLServerDbContext, which extends ApplicationDbContext directly (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/SQLServerDbContext.cs:15-20) and builds its model by scanning assemblies for entity configurations, not by reading DbSet<T> properties: its OnModelCreating calls ApplyConfigurationsForEntitiesInContext(DataSource.SQLServer, modelBuilder) (SQLServerDbContext.cs:85-89), which picks the engine's configuration interface IEntityTypeConfigurationSQLServer<,> and walks assemblyProvider.GetConfigurationAssemblies(), applying every matching configuration whose entity routes to this context's data source (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:950-978). That is why a module ships UserSessionBookmarkConfiguration and its siblings rather than a context of its own, and it is exactly the ADR-006 rule the workspace states as "one sealed context class per engine, never per module".
    • +
    • Concept introduced, the module's entity inventory is a declaration, not the runtime context. [Rubric §8, Data Architecture] assesses whether persistence structure is deliberate rather than accidental, and [Rubric §3, Clean Architecture] assesses where infrastructure concerns are allowed to live (here: only in *.Infrastructure, never in the domain entities). The instructive detail is what this class does not do. Nothing in the repository derives from it: a repo-wide search for the name finds exactly three declarations, this one plus the sibling per-module classes in Conference (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/Persistence/DbContexts/ModuleApplicationDbContext.cs:22) and Identity (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Infrastructure/Persistence/DbContexts/ModuleApplicationDbContext.cs:15), with no subclass and no consumer anywhere in MMCA.ADC/Source or MMCA.ADC/Tests. The context that actually runs is Common's sealed SQLServerDbContext, which extends ApplicationDbContext directly (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/SQLServerDbContext.cs:15-20) and builds its model by scanning assemblies for entity configurations, not by reading DbSet<T> properties: its OnModelCreating calls ApplyConfigurationsForEntitiesInContext(DataSource.SQLServer, modelBuilder) (SQLServerDbContext.cs:85-89), which picks the engine's configuration interface IEntityTypeConfigurationSQLServer<,> and walks assemblyProvider.GetConfigurationAssemblies(), applying every matching configuration whose entity routes to this context's data source (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:953-987). That is why a module ships UserSessionBookmarkConfiguration and its siblings rather than a context of its own, and it is exactly the ADR-006 rule the workspace states as "one sealed context class per engine, never per module".
    • Walkthrough
        -
      • The primary constructor (ModuleApplicationDbContext.cs:19-24) takes DbContextOptions, IServiceProvider, IEntityConfigurationAssemblyProvider and PhysicalDataSource and forwards all four to ApplicationDbContext unchanged (:24). It adds no state and overrides nothing, so audit stamping and domain event capture stay on the framework's SaveChanges interceptors, which the base resolves and registers in OnConfiguring (ApplicationDbContext.cs:292-306), while the soft-delete global filter, the tenant filter, the concurrency tokens, and the outbox and inbox tables stay in the base's OnModelCreating (ApplicationDbContext.cs:411-439).
      • +
      • The primary constructor (ModuleApplicationDbContext.cs:19-24) takes DbContextOptions, IServiceProvider, IEntityConfigurationAssemblyProvider and PhysicalDataSource and forwards all four to ApplicationDbContext unchanged (:24). It adds no state and overrides nothing, so audit stamping and domain event capture stay on the framework's SaveChanges interceptors, which the base resolves and registers in OnConfiguring (ApplicationDbContext.cs:297-311), while the soft-delete global filter, the tenant filter, the concurrency tokens, and the outbox and inbox tables stay in the base's OnModelCreating (ApplicationDbContext.cs:416-444).
      • Ten internal DbSet<T> properties, one per owned entity: UserSessionBookmarks (:27), AttendeeBadges (:30), CheckIns (:33), LivePolls (:36), LivePollOptions (:39), LivePollVotes (:42), SessionQuestions (:45), SessionQuestionUpvotes (:48), PointsEntries (:51, annotated in its own doc comment as the append-only points ledger), and LeaderboardOptIns (:54).
      • internal, not public, is the visibility choice: even if a host did derive from this class, the sets would stay invisible outside the Engagement Infrastructure assembly, so no application-layer code could reach past IUnitOfWork and its repositories into raw EF.
      • Read as documentation, the list is the module's data footprint in one screen: one personal-schedule aggregate, one badge, one attendance record, the conference-day live layer, and the points game. Everything UserEngagementExportService has to gather for a data-subject export is on this list.
    • -
    • Why it's built this way: database-per-service (ADR-006) gives Engagement its own ADC_Engagement database with its own outbox, so it never races another service for outbox rows, and cross-module references (UserId, SessionId, EventId, SponsorId) stay scalar columns rather than cross-database foreign keys. The base class is written to be instantiated once per physical data source, keyed by DataSourceModelCacheKeyFactory so each database builds a model containing only its own entities (ApplicationDbContext.cs:342-345). Keeping the module rung abstract and empty of behavior means the engine choice lives entirely in the framework: swapping SQL Server for the Cosmos or SQLite context (ADR-018) is a configuration-plus-configuration-base-class change, not a per-module context rewrite.
    • +
    • Why it's built this way: database-per-service (ADR-006) gives Engagement its own ADC_Engagement database with its own outbox, so it never races another service for outbox rows, and cross-module references (UserId, SessionId, EventId, SponsorId) stay scalar columns rather than cross-database foreign keys. The base class is written to be instantiated once per physical data source, keyed by DataSourceModelCacheKeyFactory so each database builds a model containing only its own entities (ApplicationDbContext.cs:347-350). Keeping the module rung abstract and empty of behavior means the engine choice lives entirely in the framework: swapping SQL Server for the Cosmos or SQLite context (ADR-018) is a configuration-plus-configuration-base-class change, not a per-module context rewrite.
    • Where it's used: as a type, nowhere. The Engagement database is scaffolded by MMCA.ADC.Migrations.SqlServer.Engagement, whose design-time factory builds a Common SQLServerDbContext and registers the Engagement Infrastructure assembly as a configuration source (MMCA.ADC/Source/Hosting/MMCA.ADC.Migrations.SqlServer.Engagement/DesignTimeSQLServerDbContextFactory.cs:14-17, :45); the project reference to MMCA.ADC.Engagement.Infrastructure in that csproj (MMCA.ADC/Source/Hosting/MMCA.ADC.Migrations.SqlServer.Engagement/MMCA.ADC.Migrations.SqlServer.Engagement.csproj:15) is what puts this file's assembly (and, with it, the entity configurations) in scope.
    • Caveats / not-in-source: the class exists as a declaration only. It compiles, documents the module's entity set, and mirrors the shape of the Conference and Identity module contexts, but no code path instantiates or inherits it today. Whether it is retained deliberately (as the module-owned inventory, and as the rung a future per-engine subclass would use) or is simply vestigial is not determinable from source: neither the file's doc comment nor an ADR says.
    -

    UserEngagementExportGrpcService

    +

    AttendeeBadgeConfiguration

    -

    MMCA.ADC.Engagement.Service · MMCA.ADC.Engagement.Service.Grpc · MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Grpc/UserEngagementExportGrpcService.cs:23 · Level 12 · class (public sealed)

    +

    MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.CheckIns · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/CheckIns/AttendeeBadgeConfiguration.cs:15 · Level 8 · class (internal sealed)

      -
    • What it is: the server-side gRPC endpoint that publishes Engagement's in-process IUserEngagementExportService to the Identity service, so Identity can fold a user's Engagement-owned personal data into the aggregated data-subject export document. Same server-bridge shape as BookmarkCountsGrpcService, with the extra concern that timestamps and enums have to survive a proto3 wire crossing.
    • -
    • Depends on: IUserEngagementExportService (primary constructor, :23) and its DTOs from MMCA.ADC.Engagement.Shared.Exports (:4); the generated base UserEngagementExportService.UserEngagementExportServiceBase and the messages from user_engagement_export.proto (namespace MMCA.ADC.Engagement.Contracts.V1, :3). Externals: Grpc.Core (ServerCallContext), System.Globalization (:1).
    • -
    • Concept, cross-service data-subject export as a purpose-built read contract. [Rubric §30, Compliance, Privacy & Data Governance] assesses whether a real data-subject-access and portability path exists; [Rubric §7, Microservices Readiness] assesses talking through explicit contracts. Identity owns the export document but not Engagement's data, so it depends only on the narrow interface; this class is the producer half over the wire, twin to the client-side UserEngagementExportServiceGrpcAdapter. The class doc (:9-15) enumerates what Engagement contributes: session bookmarks, submitted session questions, the points ledger, leaderboard participation, and check-in history. Unlike the bookmark-count endpoint, this one is mapped .RequireAuthorization() (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:357, with the reasoning at :338-340), which is the posture difference personal data justifies. [Rubric §11, Security].
    • -
    • Concept, proto3 has no nulls and no DateTime. [Rubric §9, API & Contract Design]. Three encoding decisions are made here and mirrored exactly on the client:
        -
      • Timestamps: SQL Server hands back DateTimeKind.Unspecified values, and the round-trip "O" format omits the Z marker for that kind, so each value is stamped DateTime.SpecifyKind(..., DateTimeKind.Utc) before formatting (:49, :55, :64). The doc comment (:16-21) is careful about what this is: the stored values are already UTC, so this restores a marker rather than converting an instant. The one DateTimeOffset on the contract, CheckedInOn, already carries its offset and is formatted as-is (:78-80).
      • -
      • Absent values: an absent leaderboard name travels as the empty string (:42-44) and an absent SessionId/SponsorId as 0 (:73-76), each with the comment naming the client-side inverse.
      • -
      • Enums: ActivityType (:59-61) and Scope (:68-70) cross as their numeric values, justified in place, the points activity numbers are part of the idempotency key and the scope numbers are named literally in the SQL index filters (CheckInConfiguration, CheckInConfiguration.cs:24-26), so neither is ever renumbered.
      • +
      • What it is: the EF Core mapping for the AttendeeBadge aggregate: two required columns and two unique indexes, one per identity the badge is looked up by (the attendee, and the scannable credential).
      • +
      • Depends on: the engine shim base EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> and AttendeeBadge. Externals: EF Core's EntityTypeBuilder<T>.
      • +
      • Concept introduced, the module entity configuration over the engine shim. [Rubric §8, Data Architecture] assesses whether the schema is designed for the queries and constraints the application actually needs rather than inherited from conventions; [Rubric §3, Clean Architecture] assesses whether persistence concerns stay out of the domain. Every Engagement configuration in this file group follows one shape: extend EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, call base.Configure(builder) first (AttendeeBadgeConfiguration.cs:21), then declare only what is specific to this entity. The base applies the framework conventions once (table and schema resolution, the soft-delete global query filter, audit columns, the concurrency token); the full hierarchy is taught in Group 07. The engine is the choice of base class, so re-pointing an entity at the Cosmos or SQLite shim is a base-class swap with no body edits (ADR-018); every Engagement entity uses the SQL Server shim today. The domain entity itself carries no EF attributes, which is what keeps MMCA.ADC.Engagement.Domain free of a persistence dependency.
      • +
      • Concept introduced, unique index as a race backstop. [Rubric §12, Performance & Scalability] and [Rubric §11, Security]. Both indexes here are unique and unfiltered in the source: HasIndex(b => b.UserId).IsUnique() (:31-32) and HasIndex(b => b.Credential).IsUnique() (:35-36). The first is the concurrency guarantee the get-or-create path leans on (the inline comment at :29-30 says so), the second makes a scan a single seek on a Guid column (AttendeeBadge.Credential, MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Domain/Badges/AttendeeBadge.cs:24) and forbids two badges sharing a credential. Note what is NOT written here: neither index declares HasSoftDeleteFilter(), because SoftDeleteUniqueIndexConvention already stamps IsDeleted = 0 onto every unique index of a soft-deletable entity at model finalizing (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:61-71) and leaves an index that already carries the predicate untouched (:73-76).
      • +
      • Walkthrough (AttendeeBadgeConfiguration.cs:19-37): base.Configure(builder) (:21); UserId required (:23-24); Credential required (:26-27); the unique UserId index (:31-32); the unique Credential index (:35-36). That is the whole file: five statements, each with a comment naming the behavior it protects.
      • +
      • Why it's built this way: the remarks (:11-14) state the cross-module rule the whole module obeys, UserId is a scalar reference into the Identity database and never a cross-database FK, because Engagement cannot see the Identity model (database-per-service, ADR-006). [Rubric §7, Microservices Readiness].
      • +
      • Where it's used: discovered by assembly scanning and applied when the Engagement SQLServerDbContext builds its model; the unique UserId index is what GetOrCreateMyBadgeHandler classifies through IUniqueConstraintViolationDetector when two requests race (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/CheckIns/UseCases/GetOrCreateMyBadge/GetOrCreateMyBadgeHandler.cs:54).
      -
    • -
    • Walkthrough: one overridden method, GetUserEngagementExport(request, context) (:27-84). It null-guards both arguments (:31-32), awaits inner.GetUserEngagementExportAsync(request.UserId, context.CancellationToken) (:34-36), then builds the response: the two scalar leaderboard fields in the initializer (:38-45), then four AddRange(... Select(...)) projections filling the repeated fields, bookmarks (:46-50), submitted questions (:51-56), points entries (:57-65) and check-ins (:66-81).
    • -
    • Why it's built this way: keeping the endpoint a thin forwarder leaves the real work (server-side projections that the soft-delete query filter already narrows to active rows) in UserEngagementExportService, reused identically in-process and over gRPC. Published via AddGrpcServiceDefaults() (Program.cs:298); because the read returns a DTO rather than a Result, the GrpcResultExceptionInterceptor has no failure trailer to translate and a transport fault becomes a gRPC status the caller's resilience pipeline handles.
    • -
    • Where it's used: mapped at MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:357. Its remote caller is Identity's ExportUserDataHandler via UserEngagementExportServiceGrpcAdapter. Covered by UserEngagementExportGrpcServiceTests.
    • +
      +

      LeaderboardOptInConfiguration

      +
      +

      MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.Points · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/Points/LeaderboardOptInConfiguration.cs:17 · Level 8 · class (internal sealed)

      +
      +
        +
      • What it is: the EF Core mapping for LeaderboardOptIn, the row that says an attendee agreed to appear on the public board and under which name. Same base shape as AttendeeBadgeConfiguration; its distinctive job is "one active opt-in per attendee".
      • +
      • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, LeaderboardOptIn, and IndexBuilderExtensions for HasSoftDeleteFilter(). Externals: EF Core's EntityTypeBuilder<T>.
      • +
      • Concept introduced, HasSoftDeleteFilter() instead of a SQL literal. [Rubric §8, Data Architecture] and [Rubric §15, Best Practices & Code Quality]. The unique index is declared .IsUnique().HasSoftDeleteFilter() (LeaderboardOptInConfiguration.cs:33-35). That extension member (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/IndexBuilderExtensions.cs:52-66) builds the predicate from the model rather than from a hand-typed "[IsDeleted] = 0", so the column name follows a rename and the quoting comes from the engine; it takes an engine parameter defaulting to DataSource.SQLServer and an optional additionalFilter that is ANDed in front of the soft-delete predicate (IndexBuilderExtensions.cs:63-65). On a unique index the call is belt-and-braces (the convention would have added the same predicate), and it is idempotent in effect because the convention skips any index that already declares the predicate; on a non-unique index it is the only way to get the filter at all.
      • +
      • Walkthrough (LeaderboardOptInConfiguration.cs:21-36): base.Configure(builder) (:23); UserId required (:25-26); DisplayName required with HasMaxLength(LeaderboardOptIn.DisplayNameMaxLength) (:28-30), the cap being the domain constant 100 (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Domain/Points/LeaderboardOptIn.cs:22), so the column width and the invariant in LeaderboardOptInInvariants can never disagree; then the filtered unique index on UserId (:33-35).
      • +
      • Why it's built this way: the remarks (:11-16) give both halves of the design. Filtering the unique index on the soft-delete flag is what makes rejoining reactivate the existing row instead of accumulating one dead row per toggle (the path SetLeaderboardParticipationHandler implements). Storing DisplayName here at all is deliberate denormalization: the name is the snapshot the attendee published, so the leaderboard query never has to reach into Identity, which is a cross-database read this topology does not allow. [Rubric §7, Microservices Readiness].
      • +
      • Where it's used: applied to the Engagement model by assembly scanning; the snapshot column is read by GetLeaderboardHandler (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/Points/UseCases/GetLeaderboard/GetLeaderboardHandler.cs:41), and the unique index is the constraint SetLeaderboardParticipationHandler both navigates and catches (SetLeaderboardParticipationHandler.cs:115-121).

      -

      UserEngagementExportServiceGrpcAdapter

      +

      LivePollVoteConfiguration

      -

      MMCA.ADC.Engagement.Contracts · MMCA.ADC.Engagement.Contracts · MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Contracts/UserEngagementExportServiceGrpcAdapter.cs:18 · Level 12 · class (internal sealed)

      +

      MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.LivePolls · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/LivePolls/LivePollVoteConfiguration.cs:17 · Level 8 · class (internal sealed)

        -
      • What it is: the client-side gRPC adapter for the data-subject export, the remote twin of the in-process UserEngagementExportService. It implements IUserEngagementExportService on top of the generated export client and is what Identity resolves.
      • -
      • Depends on: IUserEngagementExportService and its DTOs from MMCA.ADC.Engagement.Shared.Exports, plus CheckInScope and PointsActivityType from the module's Shared project (:3-5); the generated UserEngagementExportService.UserEngagementExportServiceClient and its messages (:2). Externals: System.Globalization (:1), the Grpc.Net client factory.
      • -
      • Concept, the same-interface transport swap plus wire rehydration. [Rubric §7, Microservices Readiness], [Rubric §9, API & Contract Design]. This is the export sibling of BookmarkCountServiceGrpcAdapter: the same interface as the in-process implementation, so consumers swap one DI registration (ADR-007). It also owns the inverse of every encoding decision the server made, and each inverse is written next to the decision it undoes.
      • -
      • Concept, parsing defensively across a rolling deploy. [Rubric §29, Resilience & Business Continuity]. The comment on ParseRoundtripUtc (:84-90) is the most instructive block in the file. It parses with DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal (:91-95) so that a peer replica still emitting the marker-less form (new Identity code running against an old Engagement replica during a rolling deploy) does not yield Kind=Unspecified, and AssumeUniversal alone would yield Kind=Local. It also records why RoundtripKind is deliberately absent: DateTime.Parse rejects it alongside either Assume* or AdjustToUniversal with an ArgumentException, and its purpose (preserving a non-UTC kind) is the opposite of what this contract wants. ParseRoundtripOffset (:100-104) keeps the offset the server emitted rather than folding it into UTC, with AssumeUniversal covering an offset-less value from an old peer (:97-99).
      • -
      • Walkthrough
          -
        • CallDeadline (:24), a 5-second static, applied at :36. The comment (:21-23) gives the same reasoning as the bookmark adapter with an export-specific twist: the aggregation is best-effort per section, so a hung peer should degrade its section quickly rather than stall the export request.
        • -
        • GetUserEngagementExportAsync(userId, cancellationToken) (:27-82): sends a GetUserEngagementExportRequest { UserId = userId } with the deadline and the caller's token (:31-37), then rebuilds UserEngagementExportDTO from the four repeated fields with collection expressions: bookmarks (:41-45), submitted questions (:46-51), points entries (:52-60, casting the numeric ActivityType back to its enum at :56), and check-ins (:61-72, casting Scope back at :65, mapping a 0 SessionId/SponsorId back to null at :69-70, and parsing CheckedInOn as a DateTimeOffset at :71). Finally IsOnLeaderboard (:73) and the empty-string-to-null mapping for LeaderboardDisplayName (:78-80), whose comment (:75-77) notes the same mapping also covers an old peer that never sets the field at all.
        • +
        • What it is: the EF Core mapping for the LivePollVote aggregate root, whose centerpiece is the filtered unique index guaranteeing one active vote per user per poll (BR-225).
        • +
        • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> (the shared base that maps audit fields, soft-delete and RowVersion), LivePollVote, and IndexBuilderExtensions for HasSoftDeleteFilter(). Externals: EF Core's EntityTypeBuilder<T>.
        • +
        • Concept introduced, the filtered unique index as the backstop for a soft-delete aggregate. [Rubric §8, Data Architecture] assesses whether uniqueness and concurrency are enforced at the storage layer rather than hoped for in application code. Because a vote toggles via soft-delete rather than hard-delete, a naive unique index would permanently block a user from re-voting: the deleted row keeps occupying its unique slot. Scoping the index to active rows is what makes the handler's create-or-reactivate dance race-safe (the remark at LivePollVoteConfiguration.cs:12-16 says exactly that). Note how the scoping is expressed: .HasSoftDeleteFilter() (:37), the framework extension member at MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/IndexBuilderExtensions.cs:52-66, rather than a hand-typed HasFilter("[IsDeleted] = 0"). The extension builds the predicate from the model, so a renamed soft-delete column or a different engine follows automatically. On a unique index the call is belt and braces: SoftDeleteUniqueIndexConvention already stamps the same predicate onto every unfiltered unique index of a soft-deletable entity at model finalizing (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:57, :60-70) and recognizes the predicate it finds rather than doubling it (:74-75). Writing it explicitly keeps BR-225 legible in the file that owns it. [Rubric §2, Design Patterns] applies in that the database constraint and the domain Reactivate method are two halves of one idempotent-write pattern; the identical shape guards SessionQuestionUpvote through SessionQuestionUpvoteConfiguration.
        • +
        • Walkthrough (LivePollVoteConfiguration.cs:21-41): Configure (:21) calls base.Configure (:23), then requires LivePollId (:25-26), OptionId (:28-29) and UserId (:31-32); note there are no navigations, because a vote is a separate aggregate by design and so carries scalar FKs only. The filtered unique index on { LivePollId, UserId } (:35-37) is BR-225, one active vote per poll and user, with the soft-delete predicate applied through HasSoftDeleteFilter() and the rule named in the comment above it (:34). The second, non-unique index on { LivePollId, OptionId } (:40) supports the grouped COUNT per option that the tally issues (:39).
        • +
        • Why it's built this way: the filter is what lets soft-delete and uniqueness coexist, routing it through the shared extension keeps every filtered index in the codebase producing byte-identical SQL, and the second index matches the tally query shape exactly, so results are computed from an index rather than by scanning the vote table.
        • +
        • Where it's used: applied by the Engagement SQLServerDbContext at model build; CastVoteHandler relies on the unique index as the final arbiter when two concurrent votes race, and LivePollResultsBuilder is the reader the second index serves. Covered by EngagementEntityConfigurationTests (MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure.Tests/Persistence/EngagementEntityConfigurationTests.cs).
        -
      • -
      • Why it's built this way: the class doc (:9-17) states the resilience posture, transport failures propagate to the caller (Identity's ExportUserDataHandler), which degrades the Engagement section to Available = false rather than failing the whole export. Best-effort cross-service aggregation: a peer outage never sinks an entire data-subject export. [Rubric §30, Compliance, Privacy & Data Governance].
      • -
      • Where it's used: registered by DependencyInjection.AddEngagementUserExportClient(), called from MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:316.
      • +
        +

        PointsEntryConfiguration

        +
        +

        MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.Points · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/Points/PointsEntryConfiguration.cs:21 · Level 8 · class (internal sealed)

        +
        +
          +
        • What it is: the EF Core mapping for PointsEntry, one row per awarded activity. Its composite unique index is not an optimization: it is the enforcement point for two separate rules at once.
        • +
        • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, PointsEntry, PointsSubjectKeys (for the key length), and IndexBuilderExtensions. Externals: EF Core's EntityTypeBuilder<T>.
        • +
        • Concept introduced, one index carrying two business rules. [Rubric §8, Data Architecture] and [Rubric §6, CQRS & Event-Driven]. The unique index on (UserId, ActivityType, SubjectKey) (PointsEntryConfiguration.cs:46-48) is simultaneously the idempotency key for an at-least-once award (a replayed integration event collides with the row it already wrote) and the anti-farming rule (N questions or N answers for one session collapse onto one subject key, so they award once). The remarks (:12-20) spell out the two-layer interplay: PointsAwarder pre-checks with an ExistsAsync on exactly those three columns so the ordinary duplicate is a clean no-op (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/Points/Services/PointsAwarder.cs:55-64), and a concurrent race that gets past the pre-check surfaces here as a provider exception the awarder classifies as already-awarded rather than as a failure (PointsAwarder.cs:75). The database is the arbiter of last resort, not the first line.
        • +
        • Walkthrough (PointsEntryConfiguration.cs:25-53): base.Configure(builder) (:27); UserId (:29-30), ActivityType (:32-33) and Points (:35-36) required; SubjectKey required with HasMaxLength(PointsSubjectKeys.MaxLength) (:38-40), that is 64 characters (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Shared/Points/PointsSubjectKeys.cs:14), a bounded key being what makes it indexable at all; OccurredOnUtc required (:42-43); then the filtered unique triple (:46-48) and a second, non-unique UserId index also carrying HasSoftDeleteFilter() (:51-52) so the "my points" ledger read is a seek. That second index is the case the convention does not cover: it is not unique, so without the explicit call it would carry no filter.
        • +
        • Why it's built this way: pushing idempotency into a unique constraint means correctness does not depend on the broker delivering exactly once, which it does not. [Rubric §29, Resilience & Business Continuity]. As everywhere in this module, UserId stays a scalar column, never a cross-database FK (:18-19).
        • +
        • Where it's used: applied by assembly scanning; the index is the contract PointsAwarder is written against, and the ledger it protects is read by GetLeaderboardHandler and the my-points queries, and exported by UserEngagementExportService (UserEngagementExportService.cs:36-46).
        • +
        +
        +

        SessionQuestionConfiguration

        +
        +

        MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.SessionQuestions · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/SessionQuestions/SessionQuestionConfiguration.cs:16 · Level 8 · class (internal sealed)

        +
        +
          +
        • What it is: the EF Core mapping for the SessionQuestion aggregate root (the audience-questions half of the live layer): scalar FK columns, a length constraint, and one query-shaped composite index.
        • +
        • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SessionQuestion, and SessionQuestionInvariants for the shared length constant. Externals: EF Core's EntityTypeBuilder<T>.
        • +
        • Walkthrough (SessionQuestionConfiguration.cs:20-39): base.Configure(builder) (:22); SessionId required (:24-25); UserId required (:27-28); Text required with HasMaxLength(SessionQuestionInvariants.TextMaxLength) (:30-32), a constant that itself forwards to the DTO's TextMaxLength (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Domain/SessionQuestions/SessionQuestionInvariants.cs:13), that is 500 characters (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Shared/SessionQuestions/SessionQuestionDTO.cs:18), so the column width, the domain validation and the client-side hint share one source of truth; Status required (:34-35); then HasIndex(p => new { p.SessionId, p.Status }) (:38) because both the attendee list and the moderation queue filter by session and status. This index is deliberately neither unique nor soft-delete-filtered: the question feed has no uniqueness rule to enforce, and the read that uses it is already narrowed by session.
        • +
        • Why it's built this way, cross-module FKs stay scalar: the remarks (:10-15) state it plainly. SessionId/EventId point at Conference-owned rows in another database and UserId at an Identity-owned row, so they stay indexed scalar columns; consistency flows through the Conference gRPC validation boundary, never a cross-database constraint. [Rubric §7, Microservices Readiness], [Rubric §8, Data Architecture]. See AttendeeBadgeConfiguration for the shared base-class story.
        • +
        • Where it's used: applied to the Engagement model by assembly scanning; the questions it maps are the second section of the data-subject export assembled by UserEngagementExportService (UserEngagementExportService.cs:30-34).
        • +
        +
        +

        SessionQuestionUpvoteConfiguration

        +
        +

        MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.SessionQuestions · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/SessionQuestions/SessionQuestionUpvoteConfiguration.cs:17 · Level 8 · class (internal sealed)

        +
        +
          +
        • What it is: the EF Core mapping for SessionQuestionUpvote. Same shape as SessionQuestionConfiguration, specialized to enforce one active upvote per question per user.
        • +
        • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, SessionQuestionUpvote, IndexBuilderExtensions. Externals: EF Core's EntityTypeBuilder<T>.
        • +
        • Walkthrough (SessionQuestionUpvoteConfiguration.cs:21-38): base.Configure(builder) (:23); SessionQuestionId required (:25-26); UserId required (:28-29); the filtered unique index on (SessionQuestionId, UserId) (:32-34), which is BR-235 as a database guarantee (named in the summary at :10 and again at the index, :31), at most one active upvote per (question, user), while soft-deleted history may hold prior toggles; and a second, non-unique SessionQuestionId index (:37) for counting upvotes grouped by question. Note the asymmetry with PointsEntryConfiguration: that second index carries no HasSoftDeleteFilter(), so it indexes deleted rows too.
        • +
        • Why it's built this way: the filtered unique index is the database-level backstop behind the handler's create-or-reactivate logic (remarks, :12-16), the same soft-delete-plus-unique-index interplay UserSessionBookmarkConfiguration sets up for bookmarks. [Rubric §8, Data Architecture].
        • +
        • Caveats / not-in-source: the remarks (:13-14) note upvotes reference their question by a scalar FK column with no navigations, they are a separate aggregate by design, so the index is the only structural link.
        • +
        +
        +

        UserSessionBookmarkConfiguration

        +
        +

        MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.UserSessionBookmarks · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/UserSessionBookmarks/UserSessionBookmarkConfiguration.cs:17 · Level 8 · class (internal sealed)

        +
        +
          +
        • What it is: the EF Core mapping for UserSessionBookmark, the module's founding aggregate. Same base shape as the siblings above; it enforces BR-21 (one active bookmark per user per session) and adds the index the conference-day count path depends on.
        • +
        • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, UserSessionBookmark, IndexBuilderExtensions. Externals: EF Core's EntityTypeBuilder<T>.
        • +
        • Concept, indexing for the hot read, not just for the constraint. [Rubric §12, Performance & Scalability]. The second index (UserSessionBookmarkConfiguration.cs:38-39) exists because BookmarkCountService counts by SessionId alone, and a leading-UserId composite cannot seek on that predicate; the comment (:36-37) says exactly this. It is non-unique, so HasSoftDeleteFilter() is required rather than optional: the convention only stamps unique indexes (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:63-64), and the count query carries the soft-delete predicate, so an unfiltered index would not match it.
        • +
        • Walkthrough (UserSessionBookmarkConfiguration.cs:21-40): base.Configure(builder) (:23); UserId required (:25-26); SessionId required (:28-29); the filtered unique index on (UserId, SessionId) (:32-34); the filtered SessionId index (:38-39).
        • +
        • Why it's built this way: filtering the unique index on IsDeleted = 0 lets a user remove and re-add the same bookmark any number of times while guaranteeing only one is active, which is precisely why the bookmark write path reactivates rather than inserts. [Rubric §8, Data Architecture].
        • +
        • Caveats / not-in-source: the remarks (:12-16) note the FK relationship to Conference.Session is configured at the shared DbContext level (where both entity types are visible) rather than here, to avoid a cross-module Infrastructure-to-Domain dependency that would break module isolation. In the extracted topology those two entities live in different databases anyway, where CrossDataSourceDegradeConvention drops the constraint.
        • +
        +
        +

        LivePollConfiguration

        +
        +

        MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.LivePolls · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/LivePolls/LivePollConfiguration.cs:16 · Level 9 · class (internal sealed)

        +
        +
          +
        • What it is: the EF Core mapping for the LivePoll aggregate root: column requirements, the question length limit, the two query indexes, and the access mode EF uses to materialize the encapsulated options collection.
        • +
        • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> (the shared base that maps audit fields, soft-delete and RowVersion), LivePoll, and LivePollInvariants for QuestionMaxLength. Externals: EF Core's EntityTypeBuilder<T> and PropertyAccessMode.
        • +
        • Concept introduced, the no-cross-database foreign key rule. The base class itself is taught in Group 07; what this file makes concrete is the remark at LivePollConfiguration.cs:11-15: EventId and SessionId point at Conference-owned rows in a different database, so they stay plain indexed scalar columns and consistency flows through the Conference gRPC validation boundary, never through an FK constraint. [Rubric §7, Microservices Readiness] assesses schema independence between services, which is exactly what the scalar-reference choice buys. [Rubric §8, Data Architecture] assesses the persistence contract (nullability, lengths, indexes) and the database-per-service discipline (ADR-006); all of it is visible in a dozen lines here, and it is the same rule AttendeeBadgeConfiguration states for UserId.
        • +
        • Concept introduced, pinning EF's access mode for an encapsulated collection. [Rubric §4, DDD] assesses whether the aggregate keeps its collections closed to outside mutation. LivePoll exposes Options as a getter over a private List returned through AsReadOnly(), which means EF must read and write the backing field, never the property, or materialization silently produces nothing. EF's convention already infers field access for this shape; :49-50 states it anyway with builder.Navigation(p => p.Options).UsePropertyAccessMode(PropertyAccessMode.Field). The comment at :43-48 gives the reason: stating it makes the mapping independent of that inference, so a later change to the property (a different projection, a computed wrapper) cannot silently turn materialization into a no-op. [Rubric §15, Best Practices & Code Quality] assesses exactly this kind of fail-loudly-later choice. It is an access mode only, no schema change: the relationship itself stays configured from the child side in LivePollOptionConfiguration.
        • +
        • Walkthrough (LivePollConfiguration.cs:20-51): Configure (:20) calls base.Configure(builder) first (:22) so the common conventions land before any override; EventId required (:24-25); Question required with HasMaxLength(LivePollInvariants.QuestionMaxLength) (:27-29), sourcing the length from the domain constant so schema and invariant can never disagree; Status required (:31-32). Then HasIndex(p => p.EventId) (:35), non-unique, because the Happening Now page and the organizer manage view both query polls by event (:34); and HasIndex(p => new { p.SessionId, p.Status }) (:41), the composite added for GetOpenPollsHandler. Its comment (:37-40) is the performance record: that query filters on (SessionId, Status), runs once per attendee per session and again on every structural poll event, and only EventId was indexed, so it scanned. It deliberately mirrors SessionQuestionConfiguration, which already indexes the same pair. [Rubric §12, Performance & Scalability] assesses whether indexes match the real query shapes of the hot path. The file closes with the Navigation(...).UsePropertyAccessMode(PropertyAccessMode.Field) call described above (:49-50).
        • +
        • Why it's built this way: one length constant sourced from the domain, scalar cross-service references per ADR-006, indexes derived from the two live-layer read shapes rather than guessed, and an explicitly stated access mode so the aggregate's encapsulation and EF's materialization cannot drift apart.
        • +
        • Where it's used: discovered and applied at model-build time by the Engagement SQLServerDbContext through EF Core's configuration scanning; it is internal, so nothing else can reach it. Covered by EngagementEntityConfigurationTests (MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure.Tests/Persistence/EngagementEntityConfigurationTests.cs).
        • +
        +
        +

        LivePollOptionConfiguration

        +
        +

        MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.LivePolls · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/LivePolls/LivePollOptionConfiguration.cs:10 · Level 9 · class (internal sealed)

        +
        +
          +
        • What it is: the EF Core mapping for the LivePollOption child entity: its text limit and its real relationship back to LivePoll.
        • +
        • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, LivePollOption, and LivePollInvariants for OptionTextMaxLength. Externals: EF Core's EntityTypeBuilder<T>.
        • +
        • Concept reinforced, the in-aggregate child. Read this directly against LivePollConfiguration: there, a cross-service reference stays a bare scalar; here, both ends are Engagement-owned and in the same database, so the option gets a genuine navigation and a genuine foreign key (LivePollOptionConfiguration.cs:22-25). [Rubric §8, Data Architecture]: the contrast is the lesson, an FK is correct precisely when the constraint can be enforced by one database.
        • +
        • Walkthrough (LivePollOptionConfiguration.cs:14-26): Configure (:14) calls base.Configure (:16), then makes Text required with HasMaxLength(LivePollInvariants.OptionTextMaxLength) (:18-20); HasOne(o => o.LivePoll).WithMany(p => p.Options).HasForeignKey(o => o.LivePollId).IsRequired() (:22-25) is the required one-poll-to-many-options relationship inside the aggregate boundary, configured from the child side, which is why LivePollConfiguration only has to state the collection's access mode.
        • +
        • Why it's built this way: an option has no meaning without its poll, so the database is allowed to say so; the length again comes from the domain constant rather than a repeated literal.
        • +
        • Where it's used: applied by the Engagement SQLServerDbContext at model build; the collection it maps is rehydrated on query paths by LivePollNavigationPopulator and eager-loaded on the delete path by DeleteLivePollHandler. Covered by EngagementEntityConfigurationTests (MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure.Tests/Persistence/EngagementEntityConfigurationTests.cs).
        • +
        +
        +

        CheckInConfiguration

        +
        +

        MMCA.ADC.Engagement.Infrastructure · MMCA.ADC.Engagement.Infrastructure.Persistence.EntityConfiguration.CheckIns · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure/Persistence/EntityConfiguration/CheckIns/CheckInConfiguration.cs:19 · Level 11 · class (internal sealed)

        +
        +
          +
        • What it is: the EF Core mapping for CheckIn, the one aggregate that carries all three check-in scopes (event arrival, session attendance, sponsor booth visit). It is the most constraint-dense configuration in the module: three scope-partitioned unique indexes plus two read indexes.
        • +
        • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, CheckIn, CheckInScope (by value, not by reference, see below), and IndexBuilderExtensions. Externals: EF Core's EntityTypeBuilder<T>.
        • +
        • Concept introduced, partitioning one table by a discriminator through filtered indexes. [Rubric §8, Data Architecture], [Rubric §4, DDD]. One aggregate covers every scope because the row shape, the idempotency rule and the attendance query are identical and only the required target differs (CheckInScope documents this, and CheckInInvariants enforces it). The cost is that a single unique index cannot express "one per attendee per event, and separately one per attendee per session": the three uniqueness rules must not collide with each other. Each index therefore carries its own scope predicate through HasSoftDeleteFilter(additionalFilter: ...) (CheckInConfiguration.cs:51, :56, :62), and the extension ANDs that predicate in front of the soft-delete one (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/IndexBuilderExtensions.cs:63-65). The three scope literals are hoisted into private const string fields (:24-26) with a comment explaining why they are literals at all (:22-23): an index filter is SQL, so it cannot read the C# enum. That is also why the enum's numeric values are documented as load-bearing and never renumbered.
        • +
        • Concept, the database as the concurrency backstop behind an application-level check. [Rubric §29, Resilience & Business Continuity], [Rubric §6, CQRS & Event-Driven]. The remarks (:11-18) describe the two layers together: CheckInProcessor makes a repeat scan a no-op by looking for the existing row first (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/CheckIns/Services/CheckInProcessor.cs:71-75), and these indexes make a concurrent double scan a DbUpdateException instead of two rows, so the broker retry that follows lands on the no-op path.
        • +
        • Walkthrough (CheckInConfiguration.cs:29-70): base.Configure(builder) (:31); five required properties, UserId (:33-34), Scope (:36-37), EventId (:39-40), CheckedInByUserId (:42-43), CheckedInOn (:45-46), note that SessionId and SponsorId are deliberately not required, since each is meaningful only in its own scope; then the three filtered unique indexes, (UserId, EventId) under the event scope (:49-51), (UserId, SessionId) under the session scope (:54-56), and (UserId, SponsorId) under the sponsor scope (:60-62), the last being the once-per-sponsor cap that makes a shared deep link worth nothing beyond the first scan (:58-59); finally two non-unique, soft-delete-filtered read indexes on EventId (:65-66) and SessionId (:68-69), because attendance stats read by event and group by session (:64).
        • +
        • Why it's built this way: sponsor visits are self-recorded by the attendee from a printed QR, so the cap has to be structural rather than procedural. Keeping the identifiers scalar (:16-17) is the same database-per-service rule the rest of the module follows (ADR-006).
        • +
        • Where it's used: applied to the Engagement model by assembly scanning. Its indexes are the contract every check-in writer is written against: all four handlers route through CheckInProcessor, the organizer paths CheckInAttendeeHandler (CheckInAttendeeHandler.cs:60) and ManualCheckInHandler (ManualCheckInHandler.cs:40) through ExecuteAsync (CheckInProcessor.cs:109), and the self-service paths RecordRoomCheckInHandler (RecordRoomCheckInHandler.cs:74) and RecordSponsorVisitHandler (RecordSponsorVisitHandler.cs:72) through RecordAsync (CheckInProcessor.cs:58). The rows it maps are exported by UserEngagementExportService and travel to Identity through UserEngagementExportGrpcService.


        diff --git a/docs/onboarding/group-23-engagement-live-layer.html b/docs/onboarding/group-23-engagement-live-layer.html index 57db6add..509a473c 100644 --- a/docs/onboarding/group-23-engagement-live-layer.html +++ b/docs/onboarding/group-23-engagement-live-layer.html @@ -230,12 +230,18 @@

        The two aggregates and their in reactivate-instead-of-reinsert dance (BR-225/BR-135) the bookmark module uses: CastVoteHandler reads active and soft-deleted rows in one call, then updates a live vote, revives a deleted one, or inserts a new one - (MMCA.ADC.Engagement.Application/LivePolls/UseCases/CastVote/CastVoteHandler.cs:52-78), so a user + (MMCA.ADC.Engagement.Application/LivePolls/UseCases/CastVote/CastVoteHandler.cs:82-110), so a user who changes their mind never piles up tombstones. ToggleUpvoteHandler does the mirror image and additionally refuses to let an author upvote their own question (BR-235, - .../SessionQuestions/UseCases/ToggleUpvote/ToggleUpvoteHandler.cs:40-48). Both tables are indexed - for the way they are actually read: the vote table carries a second (LivePollId, OptionId) index - for the grouped tally (LivePollVoteConfiguration.cs:40).

        + .../SessionQuestions/UseCases/ToggleUpvote/ToggleUpvoteHandler.cs:43-51). The unique index is + also what settles a race between two first votes (or upvotes) from the same user: the loser's + insert fails on save, and both handlers recognize that through + IUniqueConstraintViolationDetector + and treat it as success rather than a 409, because the caller has voted either way and the fresh + read that follows shows the state the winning request produced (M146, CastVoteHandler.cs:54-64, + ToggleUpvoteHandler.cs:99-119). Both tables are indexed for the way they are actually read: the + vote table carries a second (LivePollId, OptionId) index for the grouped tally + (LivePollVoteConfiguration.cs:40).

        The write path, and where the realtime broadcast actually happens

        Each operation is a vertical slice under Application/{LivePolls|SessionQuestions}/UseCases/{Op}/. The three lifecycle commands do not hand-roll their load-mutate-save sequence at all: they derive @@ -253,7 +259,7 @@

        The wr differs between slices, and what is worth studying, is how the ephemeral broadcast leaves the process. There are three shapes in the code today, and the differences are deliberate.

        The hot paths raise a domain event and broadcast from the handler for it. Casting a vote - (CastVoteHandler.cs:19) and toggling an upvote (ToggleUpvoteHandler.cs:17) publish nothing + (CastVoteHandler.cs:20) and toggling an upvote (ToggleUpvoteHandler.cs:18) publish nothing themselves. The vote and upvote aggregates raise LivePollVoteChanged and SessionQuestionUpvoteChanged, and the matching domain-event handlers, LivePollVoteChangedHandler @@ -319,10 +325,10 @@

        The wr

        Server-side guards round out the write path. The two hot paths cannot rely on a rowversion conflict, because a vote only touches the LivePollVote row and never the poll row, so they add an explicit TOCTOU re-check instead: the handler re-reads the aggregate immediately before saving and documents - the accepted millisecond residue (CastVoteHandler.cs:80-82, :96-120; - ToggleUpvoteHandler.cs:75-77, :96-120). Only the upvote-on path re-checks; clearing an upvote is + the accepted millisecond residue (CastVoteHandler.cs:50-52, :112-136; + ToggleUpvoteHandler.cs:76-81, :121-145). Only the upvote-on path re-checks; clearing an upvote is deliberately still allowed after a dismissal or after the window closes - (ToggleUpvoteHandler.cs:71-78). And question submission carries a spam cap: a user may hold at most + (ToggleUpvoteHandler.cs:74-81). And question submission carries a spam cap: a user may hold at most ten open (non-dismissed) questions per session (SessionQuestionInvariants.cs:15-22, enforced at SubmitQuestionHandler.cs:72-83), so an auto-approving event default cannot be used to flood the channel. Both the constant and its enforcement document themselves as a soft cap: the count and @@ -345,7 +351,7 @@

        One WebSoc forwards the pre-serialized JSON payload to the Notification service's LiveChannelGrpcService ingress, which then does the real group send. The host states that as one .Register(...) step in its application-pipeline - builder (MMCA.ADC.Engagement.Service/Program.cs:283, rationale at :238-245), and the extension + builder (MMCA.ADC.Engagement.Service/Program.cs:285, rationale at :238-245), and the extension behind that line does a Replace, not a TryAdd, so the adapter beats the framework's Null default (MMCA.ADC.Notification.Contracts/DependencyInjection.cs:42-51, the Replace itself at :48). This is exactly the "a host that does not map the hub can replace the registration with its own @@ -375,18 +381,18 @@

        One WebSoc reconnect). SessionLive and HappeningNow no longer talk to it directly: they hold a LiveChannelSubscription helper that owns the join, the multicast handle, the already-joined flag, and the teardown as one disposable unit - (SessionLive.razor.cs:56, :143, :329; HappeningNow.razor.cs:51, :124-127, :276). + (SessionLive.razor.cs:56, :143, :329; HappeningNow.razor.cs:53, :126-129, :278). PresenterView still wires the three calls by hand, which is the shape the other two grew out of (PresenterView.razor.cs:100-110, teardown at :181-185). The join is deliberately not firstRender-gated: the first render fires at the first await in OnInitializedAsync while the session is still null, so a firstRender-only join never attached; the subscription's IsJoined doubles as the already-joined guard, and the RendererInfo.IsInteractive check keeps the prerender pass and the bUnit suite from dialing the hub (SessionLive.razor.cs:132-137, and the same reasoning - at HappeningNow.razor.cs:109-113 and PresenterView.razor.cs:100-102). SessionLive and + at HappeningNow.razor.cs:111-115 and PresenterView.razor.cs:100-102). SessionLive and PresenterView also skip their data loads entirely on the prerender pass, since the interactive instance re-runs OnInitializedAsync and nothing here is cache-served for a logged-in user (SessionLive.razor.cs:67-73, PresenterView.razor.cs:55-57); HappeningNow knowingly does not, - and says why in a NOTE at HappeningNow.razor.cs:65-66.

        + and says why in a NOTE at HappeningNow.razor.cs:67-68.

        The read path and how the UI reacts

        Reads do not go through the generic entity-query machinery; the live views need shaped projections. LivePollResultsBuilder @@ -403,17 +409,22 @@

        The read path and how the UI reacts (:36-44), adding per-caller MyUpvote/IsMine flags (:48-58). Those two feed the query handlers behind GET /livepolls/open, /livepolls/{id}/results, /sessionquestions, and /sessionquestions/moderation. GetOpenPollsHandler requires an explicit - event or session scope (.../GetOpenPolls/GetOpenPollsHandler.cs:24-30), excludes session-scoped - polls from the event-wide list (BR-230, :41), and then makes one batched build call for the whole - listing (:45-50). Both question reads are bounded server-side so a flooded session cannot produce - an unbounded payload, and the attendee read is the more interesting of the two: - GetSessionQuestionsHandler spends two separate budgets, 200 - approved questions ranked by upvote count in the database before the cap applies (a correlated - COUNT subquery over the upvote table, since question and upvote are separate aggregates with no - navigation between them, .../GetSessionQuestions/GetSessionQuestionsHandler.cs:32, :45-58) plus - 25 of the caller's own non-approved questions taken newest first (:35, :60-69), because one - shared budget filled by oldest id let a flood of low-value questions push both the most upvoted - question and the caller's own newest submission out of the payload (:17-24). The moderation read + event or session scope (.../GetOpenPolls/GetOpenPollsHandler.cs:31-37), excludes session-scoped + polls from the event-wide list (BR-230, :53), and then makes one batched build call for the whole + listing (:57-62). Before any of that it applies the published-scope gate (L105): unless the + scope belongs to a published event, and also when the Conference lookup fails, it answers an empty + list, so the read never confirms that an unpublished scope exists (:16-18, :39-42, :67-81). + The attendee question read applies the same gate (GetSessionQuestionsHandler.cs:44-52), and + casting a vote loads the poll through it too (CastVoteHandler.cs:138-140). Both question reads are + bounded server-side so a flooded session cannot produce an unbounded payload, and the attendee read + is the more interesting of the two: GetSessionQuestionsHandler + spends two separate budgets, 200 approved questions ranked by upvote count in the database + before the cap applies (a correlated COUNT subquery over the upvote table, since question and + upvote are separate aggregates with no navigation between them, + .../GetSessionQuestions/GetSessionQuestionsHandler.cs:34, :57-70) plus 25 of the caller's own + non-approved questions taken newest first (:37, :72-81), because one shared budget filled by + oldest id let a flood of low-value questions push both the most upvoted question and the caller's + own newest submission out of the payload (:19-24). The moderation read caps at 200 and orders Pending first (.../GetModerationQueue/GetModerationQueueHandler.cs:26, :46-47, :53-54). LivePollNavigationPopulator declares the poll's Options child @@ -459,7 +470,7 @@

        The read path and how the UI reacts computes its live window with the same math the backend enforces (:27-46), degrading to null on an API failure (:48-52) so the live surfaces simply stay dormant rather than error; HappeningNow joins the event channel only while IsLiveAt is true - (MMCA.ADC.Engagement.UI/Services/SessionLive/LiveEventContext.cs:22-23, HappeningNow.razor.cs:118-121). The + (MMCA.ADC.Engagement.UI/Services/SessionLive/LiveEventContext.cs:22-23, HappeningNow.razor.cs:120-129). The cross-module ISessionLiveUIService / SessionLiveUIService contract is what lets a Conference session page light up its "Live" button when Engagement is deployed @@ -487,7 +498,7 @@

        LivePollsController.cs:149, :168). Crucially, the caller's identity (user id, speaker_id claim, roles) is always bound from the token via ICurrentUserService, never from the request body - (LivePollsController.cs:284-293). Two Common API behaviors show up on these routes as well: every + (LivePollsController.cs:199-207). Two Common API behaviors show up on these routes as well: every mutating endpoint is marked [Idempotent] (ADR-017) so a conference-day retry over flaky wifi replays the first response instead of creating a second poll, question, or vote @@ -508,7 +519,7 @@

        QuestionModerationDefault, consumed at SubmitQuestionHandler.cs:41-64, :86-88); it resolves in-process when co-hosted and - over gRPC when extracted (MMCA.ADC.Engagement.Service/Program.cs:282, rationale at :236-237). On + over gRPC when extracted (MMCA.ADC.Engagement.Service/Program.cs:284, rationale at :236-237). On the client side the Conference session page reaches back through the Engagement UI's ISessionLiveUIService implementation for the Live route. The EngagementModule declares the dependency, and the @@ -519,11 +530,31 @@

        AuthenticatedServiceBase and go back through the Gateway's public REST routes, not a back channel (MMCA.ADC.Engagement.UI/Services/SessionLive/LivePollUIService.cs:15-19).

        +

        The layer also listens to Identity, in one direction only: account erasure. Votes, questions, and + upvotes are keyed by the user id and live in the Engagement database, which the Identity-side + deletion cannot reach, so two consumers of Identity's + UserDeleted integration event finish the job. + UserDeletedVotesHandler + (MMCA.ADC.Engagement.Application/LivePolls/IntegrationEventHandlers/UserDeletedVotesHandler.cs:21) + soft-deletes the user's active live-poll votes (:36-49), and + UserDeletedSessionQuestionsHandler + (.../SessionQuestions/IntegrationEventHandlers/UserDeletedSessionQuestionsHandler.cs:22) does the + same for the user's session questions (the user-authored text) and question upvotes (:36-64). Both + derive from Common's + ScopedIntegrationEventHandlerBase<TIntegrationEvent>, + which opens a DI scope per event (UserDeletedVotesHandler.cs:24, + UserDeletedSessionQuestionsHandler.cs:25). They are idempotent because broker delivery is + at-least-once: each read sees active rows only, so a redelivery finds nothing and returns without a + save (UserDeletedVotesHandler.cs:51-54, UserDeletedSessionQuestionsHandler.cs:66-69), and + neither catches an exception, so a failed erasure is retried rather than acknowledged away (the + rationale is in each type's summary, UserDeletedVotesHandler.cs:13-17, + UserDeletedSessionQuestionsHandler.cs:14-18). A successful pass logs one Information line with the + counts (UserDeletedVotesHandler.cs:60-61, UserDeletedSessionQuestionsHandler.cs:75-76).

        Rubric lenses this chapter exercises. [Rubric §4, DDD] (two aggregates with lifecycle state machines, invariant guards, the live-window snapshot, and the single-event-with-state design); [Rubric §6, CQRS & Event-Driven] (command/query slices over a shared mutate-entity base, durable - domain events through the outbox, and the separate ephemeral channel broadcast that two of those - domain events trigger); [Rubric §7, Microservices Readiness] (the ILiveChannelPublisher port with + domain events through the outbox, the separate ephemeral channel broadcast that two of those + domain events trigger, and the idempotent UserDeleted integration-event consumers); [Rubric §7, Microservices Readiness] (the ILiveChannelPublisher port with a SignalR implementation, a Null default, and a gRPC forwarding adapter swapped in by Replace, plus the Conference validation boundary); [Rubric §8, Data Architecture] (filtered unique indexes behind the create-or-reactivate rule, the (SessionId, Status) conference-day index, and cross-context @@ -533,7 +564,8 @@

        CastVoteCommandValidator

      CastVoteHandler

      -

      MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.LivePolls.UseCases.CastVote · MMCA.ADC.Engagement.Application/LivePolls/UseCases/CastVote/CastVoteHandler.cs:19 · Level 10 · class

      +

      MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.LivePolls.UseCases.CastVote · MMCA.ADC.Engagement.Application/LivePolls/UseCases/CastVote/CastVoteHandler.cs:20 · Level 10 · class

      • What it is: the command handler that records (or changes) a vote on an open poll and answers with the fresh tallies. It broadcasts nothing itself: the poll.results-changed push is raised as a domain event and enqueued post-commit by LivePollVoteChangedHandler (stated in the doc comment, - MMCA.ADC.Engagement.Application/LivePolls/UseCases/CastVote/CastVoteHandler.cs:11-18).

        + MMCA.ADC.Engagement.Application/LivePolls/UseCases/CastVote/CastVoteHandler.cs:12-19).

      • Depends on: IUnitOfWork, - LivePollResultsBuilder, the BCL TimeProvider, and ILogger<T> (primary - constructor, :20-23); it implements + IEventLiveValidationService (the + cross-module read of the scope's published flag), LivePollResultsBuilder, + the BCL TimeProvider, + IUniqueConstraintViolationDetector, + and ILogger<T> (primary constructor, :20-26); it implements ICommandHandler<in TCommand, TResult> - as ICommandHandler<CastVoteCommand, Result<LivePollResultsDTO>> (:23), and it works over + as ICommandHandler<CastVoteCommand, Result<LivePollResultsDTO>> (:26), and it works over LivePoll, LivePollVote, and LivePollResultsDTO. Note two things that are absent: there is no live-channel dependency at all (neither publisher port nor @@ -814,55 +849,78 @@

        CastVoteHandler

      • Concept introduced, the one-active-vote soft-delete dance (BR-225/BR-135). [Rubric §8, Data Architecture] (assesses how soft-delete coexists with uniqueness without duplicate rows). A user may vote, change their vote, retract it, then vote again; the invariant is exactly one active vote per - (poll, user), backed by a filtered unique index. The handler realizes that with a three-way branch - (:52-78):

        + (poll, user), backed by a filtered unique index. The handler realizes that with a three-way branch + in the private ApplyVoteAsync (:76-110):

        • Load all rows for (poll, user) through the repository's - FindIncludingDeletedAsync (:52-55), whose contract returns a named tuple of (Active, SoftDeleted) collections - (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:215), so + FindIncludingDeletedAsync (:85-88), whose contract returns a named tuple of (Active, SoftDeleted) collections + (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:217), so the soft-deleted row is visible without a caller having to remember an ignoreQueryFilters flag. - The two heads are taken at :56-57, and the load is tracked (asTracking: true, :54) because + The two heads are taken at :89-90, and the load is tracked (asTracking: true, :87) because the branch below mutates them.
        • -
        • If an active vote exists, activeVote.ChangeOption(command.OptionId) (:61) updates the row in +
        • If an active vote exists, activeVote.ChangeOption(command.OptionId) (:94) updates the row in place.
        • -
        • Else if a soft-deleted vote exists, deletedVote.Reactivate(command.OptionId) (:67) un-deletes +
        • Else if a soft-deleted vote exists, deletedVote.Reactivate(command.OptionId) (:100) un-deletes and re-points it, rather than inserting a duplicate that would collide with the index.
        • -
        • Else LivePollVote.Create(...) builds a fresh vote and AddAsync stages it (:73-77).
        • +
        • Else LivePollVote.Create(...) builds a fresh vote and AddAsync stages it (:104-108).

        Every branch propagates its Result failure rather than - throwing (:62-63, :68-69, :74-75). This is the exact pattern the bookmark feature (BR-135) + throwing (:95, :101, :105-106), and a success carries a bool that is true only when a + brand-new row was inserted (:109), the one case a concurrent request can collide with on the + unique index (doc comment, :76-81). This is the exact pattern the bookmark feature (BR-135) established, reused so a hot, re-votable poll never accumulates dead rows.

      • +
      • Concept introduced, the concurrent first vote. [Rubric §29, Resilience & Business Continuity]. + Two requests from the same user can both see "no row" and both insert. The loser's + SaveChangesAsync (:56) then fails on the filtered unique index, and the handler catches exactly + that case with an exception filter: applied.Value (a fresh insert) and + uniqueConstraintViolationDetector.IsUniqueConstraintViolation(exception) (:58). It does not turn + that into a 409: the caller has voted either way, so it logs at Debug level through + LogConcurrentFirstVote (:63, declared :179-180) and falls through to read fresh tallies that + show the winning request's state (comment, :60-62). Any other exception still propagates.

        +
      • +
      • Concept introduced, the published-scope gate (L105). [Rubric §11, Security]. The poll is loaded + through LoadPublishedPollAsync (:138-157), which returns null both when the poll does not exist + and when its scope is unpublished, so a poll on an unpublished event or session answers the same + NotFound as a poll that never existed (doc comment, :138-142). IsScopePublishedAsync + (:159-177) reads the published flag across the Conference boundary through + IEventLiveValidationService: the + session's via GetSessionLiveInfoAsync for a session poll (:165-171), otherwise the event's via + GetEventLiveInfoAsync (:173-176). A failed lookup counts as unpublished. This is the same rule + GetPollResultsHandler applies, and the sibling listing + GetOpenPollsHandler applies it too.

        +
      • Concept introduced, the TOCTOU re-check. [Rubric §29, Resilience & Business Continuity]. Between the eligibility check and the save, a concurrent close can commit. A rowversion conflict cannot catch - that race, and the handler's own doc comment explains exactly why (:96-105): casting a vote only + that race, and the handler's own doc comment explains exactly why (:112-121): casting a vote only inserts or updates a LivePollVote row and never touches the poll row, so the poll's concurrency - token is never part of this unit of work. Instead RecheckPollAcceptsVoteAsync (:106-120) re-reads - the poll fresh immediately before saving (:111-115) and re-runs CanAcceptVote (:119). The + token is never part of this unit of work. Instead RecheckPollAcceptsVoteAsync (:122-136) re-reads + the poll fresh immediately before saving (:127-131) and re-runs CanAcceptVote (:135). The comment is honest that a millisecond window remains and is accepted, since such a vote is indistinguishable from one cast just before the close.

      • Concept reinforced, broadcast privacy (BR-229), enforced one layer out. [Rubric §11, Security] and [Rubric §12, Performance]. The command returns the caller's full tally including their own - vote (:91), while the fan-out payload must not leak one user's choice to every subscriber. Neither + vote (:71), while the fan-out payload must not leak one user's choice to every subscriber. Neither concern is settled here: the vote aggregate raises LivePollVoteChanged, and LivePollVoteChangedHandler rebuilds the tally with userId: null so MyVoteOptionId stays null before enqueueing (MMCA.ADC.Engagement.Application/LivePolls/DomainEventHandlers/LivePollVoteChangedHandler.cs:71-82). - The comment left behind here (CastVoteHandler.cs:88-90) names the reason for the move: enqueuing + The comment left behind here (CastVoteHandler.cs:68-70) names the reason for the move: enqueuing inside the command would publish tallies for a vote that a later rollback discards. Each client then refreshes its own card via GetPollResultsQuery, which re-reads its vote.

      • -
      • Walkthrough: resolve the poll repository and load the poll with its Options, no-tracking - (:30-35); NotFound guard (:37-41); the domain gate - poll.CanAcceptVote(timeProvider.GetUtcNow().UtcDateTime, command.OptionId) (:43), which enforces +

      • Walkthrough: resolve the poll repository and load the published poll with its Options, + no-tracking, through LoadPublishedPollAsync (:33-34); NotFound guard (:36-40); the domain gate + poll.CanAcceptVote(timeProvider.GetUtcNow().UtcDateTime, command.OptionId) (:42), which enforces open plus inside the snapshotted window plus option-belongs-to-poll - (MMCA.ADC.Engagement.Domain/LivePolls/LivePoll.cs:169-194), with a short-circuit on failure - (:44-45); resolve the vote repository (:47); the three-way vote branch (:52-78); the TOCTOU - re-check (:80-82); SaveChangesAsync (:84); a source-generated LoggerMessage at Information - level (:86, declared :122-123), [Rubric §13, Observability]; then rebuild the caller's tallies - through LivePollResultsBuilder.BuildAsync (:91) and return them (:93).

        + (MMCA.ADC.Engagement.Domain/LivePolls/LivePoll.cs:167-197), with a short-circuit on failure + (:43-44); the three-way vote branch via ApplyVoteAsync (:46-48); the TOCTOU re-check + (:50-52); SaveChangesAsync inside the concurrent-first-vote try/catch (:54-64); a + source-generated LoggerMessage at Information level (:66, declared :182-183), + [Rubric §13, Observability]; then rebuild the caller's tallies through + LivePollResultsBuilder.BuildAsync (:71) and return them (:73).

      • Why it's built this way: including soft-deleted rows in the lookup is load-bearing, because without it a re-vote would try to insert a second row and hit the unique index. Moving the broadcast @@ -1004,42 +1062,55 @@

        GetEventPollsHandler

      GetOpenPollsHandler

      -

      MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.LivePolls.UseCases.GetOpenPolls · MMCA.ADC.Engagement.Application/LivePolls/UseCases/GetOpenPolls/GetOpenPollsHandler.cs:15 · Level 10 · class

      +

      MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.LivePolls.UseCases.GetOpenPolls · MMCA.ADC.Engagement.Application/LivePolls/UseCases/GetOpenPolls/GetOpenPollsHandler.cs:21 · Level 10 · class

      • What it is: the read handler returning the open polls for a scope, with live tallies and the caller's own vote - (MMCA.ADC.Engagement.Application/LivePolls/UseCases/GetOpenPolls/GetOpenPollsHandler.cs:15).
      • -
      • Depends on: IUnitOfWork and - LivePollResultsBuilder (:15-17); it implements + (MMCA.ADC.Engagement.Application/LivePolls/UseCases/GetOpenPolls/GetOpenPollsHandler.cs:21).
      • +
      • Depends on: IUnitOfWork, + IEventLiveValidationService, and + LivePollResultsBuilder (:21-24); it implements IQueryHandler<in TQuery, TResult> - as IQueryHandler<GetOpenPollsQuery, Result<IReadOnlyList<LivePollResultsDTO>>> (:17) over + as IQueryHandler<GetOpenPollsQuery, Result<IReadOnlyList<LivePollResultsDTO>>> (:24) over LivePoll, LivePollStatus, and LivePollResultsDTO.
      • Concept, in-handler scope validation. [Rubric §24, Forms, Validation & UX Safety]. Because a query has no validating decorator, this handler opens by rejecting the "neither scope" case itself: if both SessionId and EventId are null it returns - Error.Validation(code: "LivePoll.Scope.Required", ...) (:24-30), a coded failure the API edge can + Error.Validation(code: "LivePoll.Scope.Required", ...) (:31-37), a coded failure the API edge can map and localize like any other. It then picks the query shape by scope: session scope filters - p.SessionId == sessionId && p.Status == LivePollStatus.Open (:34-38), event scope filters - p.EventId == query.EventId && p.SessionId == null && p.Status == LivePollStatus.Open (:39-43). + p.SessionId == sessionId && p.Status == LivePollStatus.Open (:46-50), event scope filters + p.EventId == query.EventId && p.SessionId == null && p.Status == LivePollStatus.Open (:51-55). That p.SessionId == null clause is what BR-230's "session-scoped polls are excluded from the - event-wide feed" means in code (doc comment, :10-14).
      • + event-wide feed" means in code (doc comment, :11-14). +
      • Concept, the published-scope gate (L105). [Rubric §11, Security]. Before any poll is read, the + handler asks IsScopePublishedAsync (:39-42, body :67-81) whether the scope belongs to a + published event, reading across the Conference boundary through + IEventLiveValidationService: the + session's flag via GetSessionLiveInfoAsync when SessionId is set (:69-75), otherwise the + event's via GetEventLiveInfoAsync (:77-80). An unpublished scope, and a failed lookup, both + answer an empty success list rather than an error, so the listing never confirms that an + unpublished scope exists (doc comment, :15-19). It is the same rule + GetPollResultsHandler and + SubmitQuestionHandler enforce, and the one + CastVoteHandler applies before a vote.
      • Concept, the batched tally. [Rubric §12, Performance & Scalability]. The whole listing's tallies are computed in a single call to - LivePollResultsBuilder.BuildManyAsync (:47-50), not in a per-poll loop. - The comment states the budget (:45-46): three queries total for the listing, the poll read plus the + LivePollResultsBuilder.BuildManyAsync (:59-62), not in a per-poll loop. + The comment states the budget (:57-58): three queries total for the listing, the poll read plus the builder's two set-wide reads, never two per poll. Inside the builder those two reads are one grouped COUNT over every poll in the set (MMCA.ADC.Engagement.Application/LivePolls/Services/LivePollResultsBuilder.cs:61-66) and, only when a caller is present, one read of that caller's votes across the same set (:75-79). This is the handler where an N+1 would hurt most (it backs the page the whole room refreshes), and it is the one place the code spends effort to avoid it.
      • -
      • Walkthrough: scope guard (:24-30); resolve the repository through +
      • Walkthrough: scope guard (:31-37); published-scope gate returning an empty list (:39-42); + resolve the repository through unitOfWork.GetReadRepository<LivePoll, LivePollIdentifierType>(), typed as - IEntityQuerier<LivePoll, LivePollIdentifierType> (:32); scope-selected GetAllAsync - eager-loading Options no-tracking (:33-43); order by id and hand the whole set to - BuildManyAsync with the caller's UserId (:47-50); return the results (:52).
      • + IEntityQuerier<LivePoll, LivePollIdentifierType> (:44); scope-selected GetAllAsync + eager-loading Options no-tracking (:45-55); order by id and hand the whole set to + BuildManyAsync with the caller's UserId (:59-62); return the results (:64).
      • Why it's built this way: reusing LivePollResultsBuilder means the event-wide board, the session live page, and the post-vote response all compute tallies identically, so a pushed change and a pulled refresh can never disagree ([Rubric §1, SOLID]).
      • @@ -1273,14 +1344,14 @@

        SubmitQuestionCommandValidator

      ToggleUpvoteHandler

      -

      MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.ToggleUpvote · MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/ToggleUpvote/ToggleUpvoteHandler.cs:17 · Level 8 · class

      +

      MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.ToggleUpvote · MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/ToggleUpvote/ToggleUpvoteHandler.cs:18 · Level 8 · class

        -
      • What it is: the command handler that applies an upvote toggle, enforces the Q&A upvote rules, and returns the fresh active-upvote count. It broadcasts nothing itself: the question.upvote-changed push is raised as a domain event by the aggregate and enqueued post-commit by SessionQuestionUpvoteChangedHandler (ToggleUpvoteHandler.cs:13-15). A sealed partial class implementing ICommandHandler<in TCommand, TResult> as ICommandHandler<ToggleUpvoteCommand, Result<int>> (ToggleUpvoteHandler.cs:17-20).
      • -
      • Depends on: three primary-constructor parameters (ToggleUpvoteHandler.cs:17-20): IUnitOfWork for repositories, TimeProvider (BCL) for a testable clock, and ILogger<ToggleUpvoteHandler>. Note what is absent: no live-channel dependency at all, neither ILiveChannelPublisher nor the ILiveChannelPublishQueue that its sibling SubmitQuestionHandler takes. It works with the SessionQuestion and SessionQuestionUpvote aggregates and returns Error failures through Result.
      • -
      • Concept introduced: the soft-delete and reactivate toggle behind a filtered unique index, called the BR-135 dance in the summary (ToggleUpvoteHandler.cs:10-12). A user may hold at most one active upvote per question. Un-upvoting soft-deletes the row instead of hard-deleting it, and a later re-upvote reactivates that same row rather than inserting a duplicate the unique index would reject. That is why the load uses the repository's resurrection read, FindIncludingDeletedAsync, which returns the matching rows already partitioned into active and soft-deleted in one round trip (:55-58, contract at MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:215-219). [Rubric §8, Data Architecture] assesses soft-delete discipline and uniqueness; reactivation plus the filtered index keep at most one live vote without churning keys (see ADR-005). [Rubric §12, Performance and Scalability] assesses hot-path write cost; upvotes never touch the question row, so a popular question does not serialize its voters behind one rowversion.
      • -
      • Walkthrough: load the question untracked by id (ToggleUpvoteHandler.cs:27-32) and fail with Error.NotFound when missing (:34-38). Enforce BR-235's self-upvote ban by comparing question.UserId with the caller, returning Error.Invariant code SessionQuestionUpvote.OwnQuestion (:40-48). Take the upvote repository (:50) and run the resurrection read for (question, user) with tracking so a reactivation actually saves (:55-58), then pick the first of each partition (:59-60). Branch on intent (:62-64): ApplyUpvoteAsync for Upvote == true, RemoveUpvote otherwise, propagating any failure (:65-66). ApplyUpvoteAsync (:127-159) returns a no-op success when an active row already exists (:135-138), otherwise checks BR-237 through the aggregate's question.CanAcceptUpvote(nowUtc) (:140-142, which rejects a non-Approved question or one past its snapshotted LiveWindowEndUtc, MMCA.ADC.Engagement.Domain/SessionQuestions/SessionQuestion.cs:201-222), then either Reactivate()s the soft-deleted row (:144-150) or creates and adds a new one (:152-156). RemoveUpvote (:165-176) soft-deletes the active row via Delete() or no-ops (:167-170). Only when something actually changed (:68-69) does the upvote-on path re-check eligibility (:73-78) and then SaveChangesAsync and log (:80-82); removing an upvote deliberately skips the re-check so a voter can still withdraw after a dismissal or after the window closes (:71-72). Finally the handler recomputes the count with CountAsync filtered on SessionQuestionId alone (:85-87), relying on the global soft-delete query filter to exclude withdrawn votes, and returns Result.Success(upvoteCount) (:93).
      • -
      • Why it's built this way: RecheckQuestionAcceptsUpvoteAsync (:106-120) exists because of a race a rowversion cannot catch, and the doc comment says so precisely (:96-105): the upvote only inserts or updates a SessionQuestionUpvote row and never touches the question row, so the question's concurrency token is never part of this unit of work. A moderator's dismissal committing between the first eligibility check and the save would otherwise slip through, so the handler re-reads the question fresh immediately before saving. A millisecond residue remains and is explicitly accepted (:101-104): such an upvote is indistinguishable from one cast just before the dismissal. Publishing was moved out of this handler for two reasons recorded at :89-92: it awaited a gRPC call on the request path, and it could announce an upvote that a later rollback discards. TimeProvider injection makes the live-window check deterministic under test. See ADR-039 for the live-channel transport the downstream event uses.
      • +
      • What it is: the command handler that applies an upvote toggle, enforces the Q&A upvote rules, and returns the fresh active-upvote count. It broadcasts nothing itself: the question.upvote-changed push is raised as a domain event by the aggregate and enqueued post-commit by SessionQuestionUpvoteChangedHandler (ToggleUpvoteHandler.cs:15-16). A sealed partial class implementing ICommandHandler<in TCommand, TResult> as ICommandHandler<ToggleUpvoteCommand, Result<int>> (ToggleUpvoteHandler.cs:18-23).
      • +
      • Depends on: five primary-constructor parameters (ToggleUpvoteHandler.cs:18-23): IUnitOfWork for repositories, IEventLiveValidationService (the Conference cross-module contract) for the published-event gate, TimeProvider (BCL) for a testable clock, IUniqueConstraintViolationDetector to recognize a lost unique-index race, and ILogger<ToggleUpvoteHandler>. Note what is absent: no live-channel dependency at all, neither ILiveChannelPublisher nor the ILiveChannelPublishQueue that its sibling SubmitQuestionHandler takes. It works with the SessionQuestion and SessionQuestionUpvote aggregates, reads SessionLiveInfo, and returns Error failures through Result.
      • +
      • Concept introduced: the soft-delete and reactivate toggle behind a filtered unique index, called the BR-135 dance in the summary (ToggleUpvoteHandler.cs:11-14). A user may hold at most one active upvote per question. Un-upvoting soft-deletes the row instead of hard-deleting it, and a later re-upvote reactivates that same row rather than inserting a duplicate the unique index would reject. That is why the load uses the repository's resurrection read, FindIncludingDeletedAsync, which returns the matching rows already partitioned into active and soft-deleted in one round trip (:58-61, contract at MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Persistence/IRepository.cs:217-221). [Rubric §8, Data Architecture] assesses soft-delete discipline and uniqueness; reactivation plus the filtered index keep at most one live vote without churning keys (see ADR-005). [Rubric §12, Performance and Scalability] assesses hot-path write cost; upvotes never touch the question row, so a popular question does not serialize its voters behind one rowversion.
      • +
      • Walkthrough: load the question untracked by id (ToggleUpvoteHandler.cs:30-35) and fail with Error.NotFound when missing (:37-41). Enforce BR-235's self-upvote ban by comparing question.UserId with the caller, returning Error.Invariant code SessionQuestionUpvote.OwnQuestion (:43-51). Take the upvote repository (:53) and run the resurrection read for (question, user) with tracking so a reactivation actually saves (:58-61), then pick the first of each partition (:62-63). Branch on intent (:65-67): ApplyUpvoteAsync for Upvote == true, RemoveUpvote otherwise, propagating any failure (:68-69). ApplyUpvoteAsync (:152-201) returns a no-op success when an active row already exists (:160-163), otherwise checks BR-237 through the aggregate's question.CanAcceptUpvote(nowUtc) (:165-167, which rejects a non-Approved question or one past its snapshotted LiveWindowEndUtc, MMCA.ADC.Engagement.Domain/SessionQuestions/SessionQuestion.cs:201-222). It then applies the published-scope gate (L105) that mirrors SubmitQuestionHandler: it fetches the session's live facts through GetSessionLiveInfoAsync, propagates a lookup failure, and rejects an unpublished event with Error.Invariant code SessionQuestionUpvote.EventNotPublished (:169-184). Finally it either Reactivate()s the soft-deleted row (:186-192) or creates and adds a new one (:194-199). RemoveUpvote (:207-218) soft-deletes the active row via Delete() or no-ops (:209-217); like the eligibility re-check, the published gate sits only on the upvote-on path, so withdrawing stays allowed (:169-170). Only when something actually changed (:71-72) does the upvote-on path re-check eligibility (:76-81) and then save through SaveUpvoteAsync and log (:83-85); removing an upvote deliberately skips the re-check so a voter can still withdraw after a dismissal or after the window closes (:74-75). SaveUpvoteAsync (:104-119) wraps SaveChangesAsync and, only for a brand-new row (upvote on, no active and no soft-deleted row, :110), swallows a unique-constraint violation recognized by the detector, logging it at Debug (:115-118, message at :223-224). Finally the handler recomputes the count with CountAsync filtered on SessionQuestionId alone (:88-90), relying on the global soft-delete query filter to exclude withdrawn votes, and returns Result.Success(upvoteCount) (:96).
      • +
      • Why it's built this way: two concurrency cases are handled differently. A concurrent first upvote by the same user (two taps, two requests) can lose the filtered unique index to the other request; the doc comment at :99-103 explains why that is not a 409: the caller has upvoted either way, and the count read that follows reports the state the winning request produced. Only the first-insert shape is absorbed; any other save failure still throws. The second case is the moderator race: RecheckQuestionAcceptsUpvoteAsync (:131-145) exists because of a race a rowversion cannot catch, and the doc comment says so precisely (:121-130): the upvote only inserts or updates a SessionQuestionUpvote row and never touches the question row, so the question's concurrency token is never part of this unit of work. A moderator's dismissal committing between the first eligibility check and the save would otherwise slip through, so the handler re-reads the question fresh immediately before saving. A millisecond residue remains and is explicitly accepted (:126-129): such an upvote is indistinguishable from one cast just before the dismissal. Publishing was moved out of this handler for two reasons recorded at :92-95: it awaited a gRPC call on the request path, and it could announce an upvote that a later rollback discards. TimeProvider injection makes the live-window check deterministic under test. See ADR-039 for the live-channel transport the downstream event uses.
      • Where it's used: dispatched by SessionQuestionsController when an attendee taps upvote; the returned int updates the caller's own UI immediately, while the post-commit SessionQuestionUpvoteChanged domain event carries the count-only broadcast to everyone else.
      • Caveats / not-in-source: the filtered unique index that makes the reactivate path necessary is declared in the EF configuration, not in this handler.
      @@ -1299,14 +1370,14 @@

      GetModerationQueueHandler

    GetSessionQuestionsHandler

    -

    MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.GetSessionQuestions · MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/GetSessionQuestions/GetSessionQuestionsHandler.cs:26 · Level 9 · class

    +

    MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.SessionQuestions.UseCases.GetSessionQuestions · MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/GetSessionQuestions/GetSessionQuestionsHandler.cs:27 · Level 9 · class

      -
    • What it is: the query handler for the attendee view of a session's questions: every Approved question, most upvoted first, followed by the caller's own non-approved ones. A sealed class implementing IQueryHandler<in TQuery, TResult> as IQueryHandler<GetSessionQuestionsQuery, Result<IReadOnlyList<SessionQuestionDTO>>> (GetSessionQuestionsHandler.cs:26-29).
    • -
    • Depends on: IUnitOfWork, IQueryableExecutor, and SessionQuestionViewBuilder (GetSessionQuestionsHandler.cs:26-29). It reads the SessionQuestion and SessionQuestionUpvote tables and returns SessionQuestionDTO rows, filtering on QuestionStatus.
    • -
    • Concept introduced: two separate server-side budgets instead of one shared cap, and ranking in the database before the cap applies. Both are defended in the class summary (GetSessionQuestionsHandler.cs:17-24). The read returns at most MaxReturnedQuestions Approved questions (a const int of 200, :32) plus at most MaxReturnedOwnQuestions of the caller's own non-approved ones (a const int of 25, :35). A single shared budget filled by oldest id would let a flood of low-value questions push both the most upvoted question and the caller's own newest submission out of the payload. The ranking is a correlated COUNT subquery over the upvote table (:55) rather than a navigation, because SessionQuestion and SessionQuestionUpvote are deliberately separate aggregates with no navigation between them; the comment notes the subquery seeks the SessionQuestionId index and that the global soft-delete filter keeps withdrawn upvotes out of the count without an explicit predicate (:45-50). [Rubric §12, Performance and Scalability] assesses whether a hot read stays bounded and pushes work to the database; ordering and capping both happen in SQL, so a plenum session cannot materialize an unbounded set. [Rubric §11, Security] assesses data scoping; other users' Pending and Dismissed rows are excluded by the predicate itself.
    • -
    • Walkthrough: take read repositories for questions and for upvotes (GetSessionQuestionsHandler.cs:42-43). Capture upvotes = upvoteRepo.TableNoTracking (:51) and compose the Approved read: filter by session and QuestionStatus.Approved, order by the correlated upvote count descending with Id as the tiebreak, take 200 (:52-58). Compose the caller's own read separately: same session, UserId == query.UserId, Status != QuestionStatus.Approved, newest first by descending Id, take 25 (:64-69). Excluding Approved there is load-bearing and the comment says why (:60-63): the first read already returned the caller's approved questions, so without the filter they would come back twice. Flip the caller's slice back to ascending id (:73) because the panel renders a user's own submissions oldest first. Build the DTOs for both slices in one call, passing the caller id so MyUpvote and IsMine resolve (:75). Compose the final list with a collection expression (:80-84): the leading approved.Count DTOs re-sorted by UpvoteCount then Id, then the caller's own slice unchanged. Return Result.Success(ordered) (:86).
    • -
    • Why it's built this way: the re-sort at :82 is not redundant with the database order. The view builder computes the counts it returns, and re-sorting on those computed values keeps the presented order tie-stable against the numbers the user actually sees (:77-79). Splitting the two reads is also what lets each carry its own ORDER BY: one by popularity, one by recency, which no single query could do.
    • +
    • What it is: the query handler for the attendee view of a session's questions: every Approved question, most upvoted first, followed by the caller's own non-approved ones. A sealed class implementing IQueryHandler<in TQuery, TResult> as IQueryHandler<GetSessionQuestionsQuery, Result<IReadOnlyList<SessionQuestionDTO>>> (GetSessionQuestionsHandler.cs:27-31).
    • +
    • Depends on: IUnitOfWork, IEventLiveValidationService (the Conference cross-module contract, used for the published-event gate), IQueryableExecutor, and SessionQuestionViewBuilder (GetSessionQuestionsHandler.cs:27-31). It reads SessionLiveInfo and the SessionQuestion and SessionQuestionUpvote tables, and returns SessionQuestionDTO rows, filtering on QuestionStatus.
    • +
    • Concept introduced: two separate server-side budgets instead of one shared cap, and ranking in the database before the cap applies. Both are defended in the class summary (GetSessionQuestionsHandler.cs:18-25). The read returns at most MaxReturnedQuestions Approved questions (a const int of 200, :34) plus at most MaxReturnedOwnQuestions of the caller's own non-approved ones (a const int of 25, :37). A single shared budget filled by oldest id would let a flood of low-value questions push both the most upvoted question and the caller's own newest submission out of the payload. The ranking is a correlated COUNT subquery over the upvote table (:67) rather than a navigation, because SessionQuestion and SessionQuestionUpvote are deliberately separate aggregates with no navigation between them; the comment notes the subquery seeks the SessionQuestionId index and that the global soft-delete filter keeps withdrawn upvotes out of the count without an explicit predicate (:57-62). [Rubric §12, Performance and Scalability] assesses whether a hot read stays bounded and pushes work to the database; ordering and capping both happen in SQL, so a plenum session cannot materialize an unbounded set. [Rubric §11, Security] assesses data scoping; other users' Pending and Dismissed rows are excluded by the predicate itself, and a session whose event is not published lists nothing at all.
    • +
    • Walkthrough: first apply the published-scope gate (L105), the rule SubmitQuestionHandler enforces: fetch the session's live facts through GetSessionLiveInfoAsync, and when the lookup fails or the event is not published, return an empty success list (GetSessionQuestionsHandler.cs:44-52). Then take read repositories for questions and for upvotes (:54-55). Capture upvotes = upvoteRepo.TableNoTracking (:63) and compose the Approved read: filter by session and QuestionStatus.Approved, order by the correlated upvote count descending with Id as the tiebreak, take 200 (:64-70). Compose the caller's own read separately: same session, UserId == query.UserId, Status != QuestionStatus.Approved, newest first by descending Id, take 25 (:76-81). Excluding Approved there is load-bearing and the comment says why (:72-75): the first read already returned the caller's approved questions, so without the filter they would come back twice. Flip the caller's slice back to ascending id (:85) because the panel renders a user's own submissions oldest first. Build the DTOs for both slices in one call, passing the caller id so MyUpvote and IsMine resolve (:87). Compose the final list with a collection expression (:92-96): the leading approved.Count DTOs re-sorted by UpvoteCount then Id, then the caller's own slice unchanged. Return Result.Success(ordered) (:98).
    • +
    • Why it's built this way: the gate answers an unpublished or unresolvable session with an empty list rather than an error, so the read never confirms the session exists (:44-45). The re-sort at :94 is not redundant with the database order. The view builder computes the counts it returns, and re-sorting on those computed values keeps the presented order tie-stable against the numbers the user actually sees (:89-91). Splitting the two reads is also what lets each carry its own ORDER BY: one by popularity, one by recency, which no single query could do.
    • Where it's used: dispatched from GET /SessionQuestions in SessionQuestionsController for GetSessionQuestionsQuery, behind the session live Q&A panel; the same DTO shape is refreshed live by the SessionQuestionChannel events.

    SubmitQuestionHandler

    @@ -1640,18 +1711,18 @@

    HappeningNow

    • What it is: the conference-day home page at /happening-now. It shows now-and-next sessions, the event's open live polls with live tallies, and (for organizers only) a poll-manage tab, and it joins the event's live channel while the event is live so poll events refresh the tallies without polling.
    • -
    • Depends on: injected ILiveEventUIService, ILivePollUIService, INowNextService, NotificationState, NotificationHubService, IToastService, and IHapticFeedbackService (HappeningNow.razor.cs:26-32); the DTOs LiveEventContext, LivePollResultsDTO, LivePollDTO, and NowNextSessionInfo; the LivePollChannel key and event vocabulary; the UI helpers LiveChannelSubscription and LiveBroadcastPatch; RoleNames from the Common auth contracts; and the child component PollManagementPanel. It implements IAsyncDisposable.
    • -
    • Concept introduced, the live Blazor surface: prerender-safe load, then interactive channel join. [Rubric §18, UI Architecture] (assesses component lifecycle and separation of load from live wiring), [Rubric §19, State Management], and [Rubric §23, Front-End Performance]. The page splits its lifecycle in two. OnInitializedAsync (HappeningNow.razor.cs:54) does the data load: it reads the organizer flag from the cascading AuthenticationState via IsInRole(RoleNames.Organizer) (:72), fetches the current LiveEventContext and returns early if there is none, then loads sessions, open polls, and (only for organizers) the manage list. The live wiring waits for OnAfterRenderAsync (:107-128). Note what that method is not: it is not firstRender-gated, and the comment at :109-112 explains why. First render fires at the first await inside OnInitializedAsync, while _liveEvent is still null, so a firstRender-only join would never attach (BR-229). Instead the join happens on the first render after the load, using the subscription's own IsJoined as the already-joined guard and RendererInfo.IsInteractive to keep the prerender pass and the bUnit suite from dialling the hub. It also refuses to join unless the event is live right now (_liveEvent.IsLiveAt(DateTime.UtcNow), :118). [Rubric §28, Front-End Testing]: the shape of this method is driven by what the component test can exercise, and the comment at :65-66 records the related trade-off, unlike the sibling Live and Presenter pages this page keeps its loads on the prerender pass and accepts the double fetch, because adding the guard needs a hub-service test extension point (deferred).
    • +
    • Depends on: injected ILiveEventUIService, ILivePollUIService, INowNextService, NotificationState, NotificationHubService, IToastService, IHapticFeedbackService, and the BCL TimeProvider (HappeningNow.razor.cs:26-33); the DTOs LiveEventContext, LivePollResultsDTO, LivePollDTO, and NowNextSessionInfo; the LivePollChannel key and event vocabulary; the UI helpers LiveChannelSubscription and LiveBroadcastPatch; RoleNames from the Common auth contracts; and the child component PollManagementPanel. It implements IAsyncDisposable.
    • +
    • Concept introduced, the live Blazor surface: prerender-safe load, then interactive channel join. [Rubric §18, UI Architecture] (assesses component lifecycle and separation of load from live wiring), [Rubric §19, State Management], and [Rubric §23, Front-End Performance]. The page splits its lifecycle in two. OnInitializedAsync (HappeningNow.razor.cs:55) does the data load: it reads the organizer flag from the cascading AuthenticationState via IsInRole(RoleNames.Organizer) (:74), fetches the current LiveEventContext and returns early if there is none, then loads sessions, open polls, and (only for organizers) the manage list. The live wiring waits for OnAfterRenderAsync (:109-130). Note what that method is not: it is not firstRender-gated, and the comment at :111-114 explains why. First render fires at the first await inside OnInitializedAsync, while _liveEvent is still null, so a firstRender-only join would never attach (BR-229). Instead the join happens on the first render after the load, using the subscription's own IsJoined as the already-joined guard and RendererInfo.IsInteractive to keep the prerender pass and the bUnit suite from dialling the hub. It also refuses to join unless the event is live right now, reading the clock through the injected TimeProvider rather than the static system clock (_liveEvent.IsLiveAt(TimeProvider.GetUtcNow().UtcDateTime), :120), so a test can pin "now" inside or outside the live window. [Rubric §28, Front-End Testing]: the shape of this method is driven by what the component test can exercise, and the comment at :67-68 records the related trade-off, unlike the sibling Live and Presenter pages this page keeps its loads on the prerender pass and accepts the double fetch, because adding the guard needs a hub-service test extension point (deferred).
    • Walkthrough, in teaching order:
        -
      • Injected state and fields (HappeningNow.razor.cs:26-52): the seven injected services, the cascading AuthState, a CancellationTokenSource _cts for disposal-safe async, the breadcrumb list, the IsLoading/IsSaving flags, _loadError, _isOrganizer, the loaded _liveEvent, the two poll lists (_polls for open polls with tallies, _managePolls for every status), the now and next session lists, and a LiveChannelSubscription _channel (:51) that encapsulates join and leave.
      • -
      • Load (HappeningNow.razor.cs:54-106): sets breadcrumbs, subscribes to NotificationState.OnChange (:63) so the header announcements badge stays in sync with the shared unread count, reads the organizer role, fetches the current event, then chains the loads so a failure short-circuits the rest. Every failure funnels into a single localized _loadError (:94), OperationCanceledException is swallowed as expected-during-disposal, and IsLoading is always cleared in finally.
      • -
      • Channel join (HappeningNow.razor.cs:108-129): joins LivePollChannel.ForEvent for the loaded event id and registers HandleChannelEventAsync as the handler.
      • -
      • Channel handling (HappeningNow.razor.cs:134-147): this is the performance heart. For a LivePollChannel.PollResultsChanged event it calls LiveBroadcastPatch.TryApplyPollResults(_polls, payloadJson, preserveMyVote: true) (:139) to patch the matching poll's tallies in place from the broadcast payload, keeping this circuit's own vote marker; only then does it re-render. Everything else (structural events such as opened and closed) falls through to ReloadPollsAsync. The comment at :135-137 records why reload-on-broadcast was abandoned: one hot poll turned V votes times C viewers into V*C authenticated refetches under burst voting. [Rubric §12, Performance & Scalability].
      • -
      • Loads and refresh (HappeningNow.razor.cs:149-195): LoadSessionsAsync calls the public now-next endpoint through INowNextService, and the doc comment (:148-153) pins the division of labour, the server owns the eligibility filter, the event-local wall clock, and the "next = the batch sharing the earliest future start" rule, so the page only renders what comes back; a not-found answer (an unpublished or deleted event) is normalized to success so the page shows an empty state rather than a load failure (:162). LoadPollsAsync and LoadManagePollsAsync (:165-169) each Tap their result into a list. ReloadPollsAsync (:171-194) is the background-refresh path: a failure toasts and returns rather than crashing the page, because the manual refresh button and the next channel event are the retry paths (:182-183). [Rubric §29, Resilience].
      • -
      • Voting (HappeningNow.razor.cs:197-226): VoteAsync fires a haptic click, a no-op off native heads (ADR-042, :198-199), casts the vote through ILivePollUIService, and on success replaces the matching entry in _polls with the returned LivePollResultsDTO (:211-215), so the voter sees their own result immediately without waiting for the broadcast.
      • -
      • Post-action reload callbacks (HappeningNow.razor.cs:228-253): ReloadManagePollsAsync and ReloadPollListsAsync are the two handlers bound to PollManagementPanel's change callbacks. The comment at :227-230 states the contract: the panel performs its own poll call, then the page (which owns the lists) reloads what the action affected, and reports a failed reload here while still inside the panel's try block, so a cancellation during disposal stays expected.
      • -
      • Error surfacing and formatting (HappeningNow.razor.cs:255-267): the same ShowActionError split as the panel (a stated refusal shows the server's localized Problem Details, an unexpected fault shows the generic fallback, ADR-027 Decision 9), plus FormatSessionTime, which formats the event-local start and end with CultureInfo.CurrentCulture (:265-266). [Rubric §27, i18n]: a displayed time formats with the current culture, unlike a channel key, which formats invariant.
      • -
      • Disposal (HappeningNow.razor.cs:269-280): unsubscribes from NotificationState.OnChange, cancels and disposes the _cts, and disposes the channel subscription (which leaves the SignalR group).
      • +
      • Injected state and fields (HappeningNow.razor.cs:26-53): the eight injected services (the last is TimeProvider, :33), the cascading AuthState, a CancellationTokenSource _cts for disposal-safe async, the breadcrumb list, the IsLoading/IsSaving flags, _loadError, _isOrganizer, the loaded _liveEvent, the two poll lists (_polls for open polls with tallies, _managePolls for every status), the now and next session lists, and a LiveChannelSubscription _channel (:53) that encapsulates join and leave.
      • +
      • Load (HappeningNow.razor.cs:55-107): sets breadcrumbs, subscribes to NotificationState.OnChange (:65) so the header announcements badge stays in sync with the shared unread count, reads the organizer role, fetches the current event, then chains the loads so a failure short-circuits the rest. Every failure funnels into a single localized _loadError (:96), OperationCanceledException is swallowed as expected-during-disposal, and IsLoading is always cleared in finally.
      • +
      • Channel join (HappeningNow.razor.cs:109-130): joins LivePollChannel.ForEvent for the loaded event id and registers HandleChannelEventAsync as the handler.
      • +
      • Channel handling (HappeningNow.razor.cs:135-148): this is the performance heart. For a LivePollChannel.PollResultsChanged event it calls LiveBroadcastPatch.TryApplyPollResults(_polls, payloadJson, preserveMyVote: true) (:141) to patch the matching poll's tallies in place from the broadcast payload, keeping this circuit's own vote marker; only then does it re-render. Everything else (structural events such as opened and closed) falls through to ReloadPollsAsync. The comment at :137-139 records why reload-on-broadcast was abandoned: one hot poll turned V votes times C viewers into V*C authenticated refetches under burst voting. [Rubric §12, Performance & Scalability].
      • +
      • Loads and refresh (HappeningNow.razor.cs:150-196): LoadSessionsAsync calls the public now-next endpoint through INowNextService, and the doc comment (:150-155) pins the division of labour, the server owns the eligibility filter, the event-local wall clock, and the "next = the batch sharing the earliest future start" rule, so the page only renders what comes back; a not-found answer (an unpublished or deleted event) is normalized to success so the page shows an empty state rather than a load failure (:164). LoadPollsAsync and LoadManagePollsAsync (:167-171) each Tap their result into a list. ReloadPollsAsync (:173-196) is the background-refresh path: a failure toasts and returns rather than crashing the page, because the manual refresh button and the next channel event are the retry paths (:184-185). [Rubric §29, Resilience].
      • +
      • Voting (HappeningNow.razor.cs:198-227): VoteAsync fires a haptic click, a no-op off native heads (ADR-042, :200-201), casts the vote through ILivePollUIService, and on success replaces the matching entry in _polls with the returned LivePollResultsDTO (:213-217), so the voter sees their own result immediately without waiting for the broadcast.
      • +
      • Post-action reload callbacks (HappeningNow.razor.cs:229-254): ReloadManagePollsAsync and ReloadPollListsAsync are the two handlers bound to PollManagementPanel's change callbacks. The comment at :229-232 states the contract: the panel performs its own poll call, then the page (which owns the lists) reloads what the action affected, and reports a failed reload here while still inside the panel's try block, so a cancellation during disposal stays expected.
      • +
      • Error surfacing and formatting (HappeningNow.razor.cs:256-268): the same ShowActionError split as the panel (a stated refusal shows the server's localized Problem Details, an unexpected fault shows the generic fallback, ADR-027 Decision 9), plus FormatSessionTime, which formats the event-local start and end with CultureInfo.CurrentCulture (:267-268). [Rubric §27, i18n]: a displayed time formats with the current culture, unlike a channel key, which formats invariant.
      • +
      • Disposal (HappeningNow.razor.cs:270-281): unsubscribes from NotificationState.OnChange, cancels and disposes the _cts, and disposes the channel subscription (which leaves the SignalR group).
    • Why it's built this way: patch-in-place from the self-contained LivePollResultsDTO broadcast (rather than a refetch on every event) is what keeps a hot poll from stampeding the API under burst voting, and gating the channel join on RendererInfo.IsInteractive plus a live-window check means a prerender pass or an already-ended event never opens a SignalR connection (ADR-039). Announcements are deliberately not duplicated on this page: they live in the shared notification inbox and are reached from the header link with a live unread badge (HappeningNow.razor.cs:17-22), so there is one inbox, not two.
    • @@ -1878,7 +1949,7 @@

      LivePollAuthorization

    • What it is: the one shared rights check for the whole live layer. It decides whether a caller may manage (author, open, close, moderate) content in a given scope.
    • Depends on: SessionLiveInfo (the Conference-owned session snapshot it inspects), Result and Error.
    • Concept introduced, the BR-236 rights shape as one authorization gate. [Rubric §11, Security] assesses whether authorization is centralized and consistent rather than re-implemented per endpoint. Every live-layer mutation and every moderator-only read routes its rights decision through this single method, so the rule "organizers manage everything; a speaker manages only content scoped to a session they are assigned to" lives in exactly one place (doc comment, LivePollAuthorization.cs:7-10). [Rubric §1, SOLID]: authorization is one responsibility, not smeared across six handlers. [Rubric §7, Microservices Readiness]: the speaker-assignment fact arrives as SessionLiveInfo.SpeakerIds from the Conference service, so this check consumes a cross-service snapshot rather than reaching into another module's tables.
    • -
    • Walkthrough: one static method, EnsureCanManage(bool callerIsOrganizer, SpeakerIdentifierType? callerSpeakerId, SessionLiveInfo? sessionInfo, string source) (LivePollAuthorization.cs:57-79). Order matters. An organizer short-circuits to Result.Success() (:63-66). Otherwise, if a session scope is supplied and the caller has a speaker id and that id is in sessionInfo.SpeakerIds (:68-70), success. Anything else returns Error.Forbidden("LivePoll.NotAuthorized", ...) carrying the caller-supplied source (:75-78). Passing sessionInfo as null (event-wide scope) means only organizers pass, which is exactly the intent for event-wide polls (:15-16).
    • +
    • Walkthrough: one static method, EnsureCanManage(bool callerIsOrganizer, SpeakerIdentifierType? callerSpeakerId, SessionLiveInfo? sessionInfo, string source) (LivePollAuthorization.cs:22-44). Order matters. An organizer short-circuits to Result.Success() (:28-31). Otherwise, if a session scope is supplied and the caller has a speaker id and that id is in sessionInfo.SpeakerIds (:33-35), success. Anything else returns Error.Forbidden("LivePoll.NotAuthorized", ...) carrying the caller-supplied source (:40-43). Passing sessionInfo as null (event-wide scope) means only organizers pass, which is exactly the intent for event-wide polls (:15-16).
    • Why it's built this way: a pure static helper keeps the rule dependency-free and trivially unit-testable, and the explicit source parameter threads the calling handler name into the error, which is this codebase's convention for stack-free tracing.
    • Where it's used: eight call sites across six handlers in both live-layer verticals: CreateLivePollHandler (CreateLivePollHandler.cs:54,64), OpenLivePollHandler (OpenLivePollHandler.cs:52,62), CloseLivePollHandler (CloseLivePollHandler.cs:47,54), GetSessionManagePollsHandler (GetSessionManagePollsHandler.cs:42), GetModerationQueueHandler (GetModerationQueueHandler.cs:37), and ModerateQuestionHandler (ModerateQuestionHandler.cs:60). Two of those, the moderation queue and the organizer poll list, are reads that still run the check, which is the point of centralizing it: moderator-only reads and writes cannot drift apart.
    @@ -2027,7 +2098,7 @@

    LivePollVote

  • Why it's built this way: separating the write-hot vote from the read-hot poll is the central scalability decision of the poll subsystem. Combined with the filtered unique index and reactivation, a poll can absorb a burst of conference-day votes without serializing them on one row.
  • -
  • Where it's used: created, re-pointed, and reactivated by CastVoteHandler (CastVoteHandler.cs:61,67,73); tallied on the read side by LivePollResultsBuilder through a grouped COUNT.
  • +
  • Where it's used: created, re-pointed, and reactivated by CastVoteHandler (CastVoteHandler.cs:94,100,104); tallied on the read side by LivePollResultsBuilder through a grouped COUNT.
  • SessionQuestion

    @@ -2174,7 +2245,7 @@

    LivePoll

  • Close() (:141): Open only, and no reopen path exists (:143-150); flips to Closed (:152) and raises Updated (:154).
  • CanAcceptVote(nowUtc, optionId) (:167): the guard the vote handler calls. It requires Open status (:169-176), requires nowUtc to be strictly before a snapshotted window end that is actually set (:178-185), and requires the option to exist, be non-deleted, and belong to this poll (_options.Exists(...), :187-194). Each failure returns its own Error code. This runs entirely against in-memory state, with no cross-service call.
  • SetOptions(options) (:201-202): an internal hook that routes through the base SetItems, used only by LivePollNavigationPopulator to rehydrate the collection.
  • -
  • Delete() (:210): refuses to delete an Open poll (BR-228, "LivePoll.DeleteWhileOpen", :212-219), then cascade soft-deletes through the base helper, Result.Combine(DeleteChildren<LivePollOption, LivePollOptionIdentifierType>(_options), base.Delete()) (:223-225), and raises LivePollChanged Deleted only when the combined result succeeded (:227-228). The comment above it explains the ordering: children first, root last, so Result.Combine aggregates every child failure with the root's own and a failing option cannot leave a half-applied delete behind (:221-222). DeleteChildren is the shared base method at MMCA.Common/Source/Core/MMCA.Common.Domain/Entities/AuditableAggregateRootEntity.cs:273.
  • +
  • Delete() (:210): refuses to delete an Open poll (BR-228, "LivePoll.DeleteWhileOpen", :212-219), then cascade soft-deletes through the base helper, Result.Combine(DeleteChildren<LivePollOption, LivePollOptionIdentifierType>(_options), base.Delete()) (:223-225), and raises LivePollChanged Deleted only when the combined result succeeded (:227-228). The comment above it explains the ordering: children first, root last, so Result.Combine aggregates every child failure with the root's own and a failing option cannot leave a half-applied delete behind (:221-222). DeleteChildren is the shared base method at MMCA.Common/Source/Core/MMCA.Common.Domain/Entities/AuditableAggregateRootEntity.cs:275.
  • Why it's built this way: snapshotting the live-window end at Open trades a small amount of staleness for removing a synchronous Conference call from every single vote (ADR-007 describes the gRPC boundary this sidesteps), and the explicit transition guards make an invalid lifecycle move impossible regardless of which handler calls in. Refusing to delete an open poll rather than silently closing it means a delete can never end a running vote behind the audience's back.
  • @@ -2271,11 +2342,92 @@

    SessionQuestionUpvoteChangedHandler MMCA.ADC.Engagement.Application/DependencyInjection.cs:85) and invoked by the framework's domain-event dispatcher whenever ToggleUpvoteHandler commits a SessionQuestionUpvote change, which that handler's comment points at - (MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/ToggleUpvote/ToggleUpvoteHandler.cs:89-90). + (MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/ToggleUpvote/ToggleUpvoteHandler.cs:92-93). The work item it enqueues is drained by the hosted processor behind ILiveChannelPublishQueue (MMCA.ADC.Engagement.Application/DependencyInjection.cs:53-54). +

    UserDeletedSessionQuestionsHandler

    +
    +

    MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.SessionQuestions.IntegrationEventHandlers · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/SessionQuestions/IntegrationEventHandlers/UserDeletedSessionQuestionsHandler.cs:22 · Level 8 · class (public sealed partial)

    +
    +
      +
    • What it is: the erasure handler for the Q&A half of the live layer. When Identity deletes an account, this soft-deletes that user's SessionQuestion rows (user-authored text) and SessionQuestionUpvote rows in the Engagement database (class doc, UserDeletedSessionQuestionsHandler.cs:10-13).
    • +
    • Depends on: ScopedIntegrationEventHandlerBase<TIntegrationEvent> closed over Identity's UserDeleted integration event (:25, imported from MMCA.ADC.Identity.Shared.Users.IntegrationEvents at :4), IUnitOfWork (:34), and the two aggregates' Delete() overrides (SessionQuestion at SessionQuestion.cs:229, SessionQuestionUpvote at SessionQuestionUpvote.cs:86). Externals: IServiceScopeFactory, ILogger<T> and the source-generated [LoggerMessage] (:75-76, which is why the class is partial).
    • +
    • Concept, cross-database erasure by event. [Rubric §30, Compliance, Privacy & Data Governance]. Under database-per-service (ADR-006) the Identity-side erasure cannot reach rows keyed by the erased user id in the Engagement database, so the deletion arrives as an event and the owning service carries it out (:11-13). The base class opens one DI scope per delivery and lets any exception propagate after one error log line, which is what makes delivery at-least-once and retryable (MMCA.Common/Source/Core/MMCA.Common.Application/DomainEvents/ScopedIntegrationEventHandlerBase.cs:27-33, :45-63). The Engagement host registers a single UserDeleted consumer that runs every erasure handler for the type, this one included (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:263-265, :291).
    • +
    • Walkthrough
        +
      • HandleScopedAsync(integrationEvent, services, cancellationToken) (:28-73) is the override; the base has opened the scope and passes its provider. The body takes the user id (:33) and resolves the scoped unit of work (:34).
      • +
      • Two tracked reads (asTracking: true, :39, :45), one per table, each filtered to UserId == userId && !IsDeleted (:38, :44): only active rows are candidates.
      • +
      • Two loops call Delete() per row and count only the successes, questions (:48-55) then upvotes (:57-64); a failed Delete() result is skipped without a log.
      • +
      • When both counts are zero the method returns without saving (:66-69); otherwise one SaveChangesAsync covers both tables (:71) and LogErased records both counts at Information (:72, :75-76).
      • +
      +
    • +
    • Why it's built this way: idempotence is a requirement, because at-least-once delivery makes redelivery normal (:15-16). Reading active rows only means a second delivery finds nothing and writes nothing. Exceptions are deliberately not caught (:16-17), so a failed erasure propagates and is retried rather than acked away. [Rubric §29, Resilience & Business Continuity]. Going through each aggregate's own Delete() rather than a bulk update keeps the soft-delete and its domain event (SessionQuestionChanged / SessionQuestionUpvoteChanged with Deleted) on the same path as a user-initiated delete.
    • +
    • Where it's used: registered by the Application convention scan (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/DependencyInjection.cs:85) and driven by the UserDeleted consumer at MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:291. Sibling erasure handler in this group: UserDeletedVotesHandler; others in the module: UserDeletedBadgeHandler, UserDeletedBookmarksHandler, UserDeletedPointsHandler. Covered by UserDeletedSessionQuestionsHandlerTests (group 28, MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.Application.Tests/SessionQuestions/IntegrationEventHandlers/UserDeletedSessionQuestionsHandlerTests.cs).
    • +
    • Caveats / not-in-source: the deletion is a soft delete, so the rows stay flagged deleted. Upvotes other users cast on the erased user's questions are not touched by this handler; they remain attached to a now-deleted question.
    • +
    +

    UserDeletedVotesHandler

    +
    +

    MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.LivePolls.IntegrationEventHandlers · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/LivePolls/IntegrationEventHandlers/UserDeletedVotesHandler.cs:21 · Level 8 · class (public sealed partial)

    +
    +
      +
    • What it is: the erasure handler for the poll half of the live layer. When Identity deletes an account, this soft-deletes that user's LivePollVote rows in the Engagement database (class doc, UserDeletedVotesHandler.cs:10-12).
    • +
    • Depends on: ScopedIntegrationEventHandlerBase<TIntegrationEvent> closed over UserDeleted (:24, imported at :4), IUnitOfWork (:33) and the vote repository it obtains (:34), and LivePollVote's Delete() override (LivePollVote.cs:120). Externals: IServiceScopeFactory, ILogger<T> and the source-generated [LoggerMessage] (:60-61).
    • +
    • Concept reinforced, cross-database erasure by event (see UserDeletedSessionQuestionsHandler). [Rubric §30, Compliance, Privacy & Data Governance]. This is the single-table twin: a vote carries no free text, but it is still keyed by the erased user id in a database the Identity-side erasure cannot reach (:11-12).
    • +
    • Walkthrough
        +
      • HandleScopedAsync (:27-58): takes the user id (:32), resolves the unit of work and the LivePollVote repository (:33-34).
      • +
      • One tracked read (:36-40) filtered to vote.UserId == userId && !vote.IsDeleted (:38).
      • +
      • The loop (:43-49) calls vote.Delete() per row and counts only the successes.
      • +
      • Zero deletions return without saving (:51-54); otherwise one save (:56) and an Information log of the count (:57, :60-61).
      • +
      +
    • +
    • Why it's built this way: same contract as its sibling: active-rows-only makes redelivery a no-op (:14-15), and exceptions propagate so a failed erasure is retried (:15-16). [Rubric §29, Resilience & Business Continuity]. Soft-deleting through LivePollVote.Delete() raises LivePollVoteChanged with Deleted, the same event an un-vote raises, and leaves the row in place under the filtered unique index described at LivePollVote.
    • +
    • Where it's used: registered by the Application convention scan (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/DependencyInjection.cs:85) and driven by the UserDeleted consumer at MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:291 (the "poll-vote" erasure handler named at :264). Covered by UserDeletedVotesHandlerTests (group 28, MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.Application.Tests/LivePolls/IntegrationEventHandlers/UserDeletedVotesHandlerTests.cs).
    • +
    +

    LivePollResultsBuilder

    +
    +

    MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.LivePolls.Services · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/LivePolls/Services/LivePollResultsBuilder.cs:12 · Level 9 · class (sealed)

    +
    +
      +
    • What it is: the shared read-side service that computes poll result tallies: per-option active-vote counts, the total, the caller's own vote when there is a caller, and the poll's concurrency token. It computes one poll or a whole set with the same code path.
    • +
    • Depends on: IUnitOfWork (for the read repository), IQueryableExecutor (async materialization without an EF dependency in the Application layer), LivePoll / LivePollVote, and the result DTOs LivePollResultsDTO / LivePollOptionResultDTO.
    • +
    • Concept introduced, computing tallies with a grouped SQL COUNT instead of materializing votes. [Rubric §12, Performance & Scalability] assesses whether hot read paths avoid loading whole tables. The comment at LivePollResultsBuilder.cs:59-60 states the intent: tallies come from a GroupBy(new { LivePollId, OptionId }).Select(Count()) that returns one row per (poll, option) rather than one row per vote, on a path that runs on every vote, every results read, and every open-polls listing. Centralizing this in one builder means all three surfaces compute results identically (:8-10).
    • +
    • Concept introduced, batching a per-item query into a set-wide one. [Rubric §12, Performance & Scalability] again, on round trips rather than row counts. BuildManyAsync takes the whole poll set and issues a fixed number of queries: one grouped COUNT over every poll (:61-66) and, only when a caller is present, one read of that caller's votes across the same set (:75-79). The doc comment names the cost this replaced: a per-poll loop issued two queries per poll, so a session with a dozen open polls cost two dozen round trips (:33-38). Single-poll callers are not a separate code path; BuildAsync simply wraps its argument in a one-element list and takes results[0] (:29-30), so there is only one implementation to keep correct.
    • +
    • Concept introduced, making the parts add up to the whole. [Rubric §9, API & Contract Design] covers whether a payload is internally consistent. The projection keeps only the options a poll still presents (.Where(o => !o.IsDeleted), :99), so votes cast on an option that was later removed are excluded from the breakdown, and TotalVotes is summed from that same projected list rather than counted independently (:116). A client computing percentages from the parts therefore always reconciles with the total. Both comments say so in place (:95-97, :114-115).
    • +
    • Walkthrough: a primary constructor takes IUnitOfWork and IQueryableExecutor (:12); the class has three members.
        +
      • BuildAsync(poll, userId?, cancellationToken) (:22-31): null-checks the poll (:27), delegates to BuildManyAsync([poll], ...) (:29), and returns the single element (:30).
      • +
      • BuildManyAsync(polls, userId?, cancellationToken) (:44-88): null-checks (:49), returns an empty list for an empty input before touching the database (:51-54), takes a no-tracking read repository for LivePollVote (:56) and the distinct poll ids (:57). It runs the grouped count over voteRepo.TableNoTracking filtered by pollIds.Contains(v.LivePollId) (:61-66) and folds the rows into a dictionary keyed by the (LivePollId, OptionId) tuple (:68). The caller's own votes are a separate set-wide read issued only when userId is non-null: broadcast payloads pass null and skip it entirely (BR-229, :70-85). Finally it maps every poll through Assemble in the order supplied (:87).
      • +
      • Assemble(poll, countsByPollOption, myVoteByPoll) (:90-123), a private static: filters to non-deleted options, orders by Sort, and projects each into a LivePollOptionResultDTO whose VoteCount comes from the dictionary via GetValueOrDefault, so an option with zero votes still appears (:98-107). It then assembles the LivePollResultsDTO (:109-122) with poll id, question, status, the summed TotalVotes (:116), the options, MyVoteOptionId (:118, null when no caller or no vote), and RowVersion (:121). That last line is deliberate: the concurrency token travels with the results so a surface fed only by results holds the token it puts in the If-Match header of an open or close (:119-120).
      • +
      +
    • +
    • Why it's built this way: the grouped count keeps the tally cost proportional to option count rather than vote count, and the set-wide shape keeps the round-trip count constant rather than proportional to the number of polls on screen. Skipping the "my vote" read for broadcast payloads (which have no single caller) avoids a pointless query on the fan-out path.
    • +
    • Where it's used: registered as scoped in the module's DI (DependencyInjection.cs:68) and injected into CastVoteHandler (CastVoteHandler.cs:23, called at :91), GetPollResultsHandler (GetPollResultsHandler.cs:26), and GetOpenPollsHandler (GetOpenPollsHandler.cs:24, the one caller of BuildManyAsync at :47), and resolved out of a fresh scope by LivePollVoteChangedHandler for the results broadcast (LivePollVoteChangedHandler.cs:55,73).
    • +
    • Caveats / not-in-source: Options must already be loaded on every passed LivePoll (via LivePollNavigationPopulator or an explicit include, as LivePollVoteChangedHandler does at LivePollVoteChangedHandler.cs:60). Assemble reads poll.Options directly and does not load it; the XML docs say so at :15-16 and :38.
    • +
    +

    LivePollNavigationPopulator

    +
    +

    MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.LivePolls.Services · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/LivePolls/Services/LivePollNavigationPopulator.cs:11 · Level 10 · class (sealed)

    +
    +
      +
    • What it is: the declarative navigation populator that loads a LivePoll's Options collection on query-service paths where EF Core .Include() is not applied.
    • +
    • Depends on: DeclarativeNavigationPopulator<TEntity> (base), ChildNavigationDescriptor<TEntity, TParentId, TChild, TChildId> (the descriptor), IUnitOfWork, LivePoll / LivePollOption.
    • +
    • Concept reinforced, declarative navigation population (ADR-002). [Rubric §2, Design Patterns]. The framework's entity-query path returns entities without EF includes; a populator declares, in data, which child collections to rehydrate and how. That is the whole class: it subclasses DeclarativeNavigationPopulator<TEntity> closed over LivePoll and passes exactly one ChildNavigationDescriptor<TEntity, TParentId, TChild, TChildId> (LivePollNavigationPopulator.cs:11-22).
    • +
    • Walkthrough: a primary constructor takes IUnitOfWork and forwards a single-element descriptor array to the base (:11-22). The descriptor (:15) wires PropertyName = nameof(LivePoll.Options) (:17), ParentKeySelector = p => p.Id (:18), ChildForeignKeySelector = child => child.LivePollId (:19), and AssignAction = (p, options) => p.SetOptions(options) (:20). That last line calls the aggregate's internal SetOptions, so the collection is rehydrated through the root's own SetItems path rather than by writing the backing field directly. The class body is empty (:23-24); all behavior lives in the base.
    • +
    • Why it's built this way: expressing the load as a descriptor rather than hand-written query code keeps every populator uniform and lets the base own batching and assignment. Routing the assignment through SetOptions preserves the aggregate boundary even during rehydration.
    • +
    • Where it's used: registered as INavigationPopulator<LivePoll> in the module's DI (DependencyInjection.cs:59), so the query pipeline runs it before LivePollResultsBuilder reads poll.Options. Note the sibling registration one line on: LivePollVote gets a NullNavigationPopulator<TEntity> (DependencyInjection.cs:60), because a vote has nothing to rehydrate.
    • +
    +

    LivePollOptionNavigationPopulator

    +
    +

    MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.LivePolls.Services · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/LivePolls/Services/LivePollOptionNavigationPopulator.cs:11 · Level 10 · class (sealed)

    +
    +
      +
    • What it is: the mirror-image populator for LivePollOption: it fills the option's back-reference to its parent LivePoll when an option is queried on its own.
    • +
    • Depends on: DeclarativeNavigationPopulator<TEntity> (base), FKNavigationDescriptor<TEntity, TChild, TChildId> (the descriptor), IUnitOfWork, LivePoll / LivePollOption.
    • +
    • Concept reinforced, the two descriptor flavors (see LivePollNavigationPopulator and ADR-002). [Rubric §2, Design Patterns]. This pair is the clearest illustration of the difference anywhere in the module. A ChildNavigationDescriptor<TEntity, TParentId, TChild, TChildId> walks down from a parent key to many children, while an FKNavigationDescriptor<TEntity, TChild, TChildId> walks up an FK to a single parent, which is why its AssignAction ends in FirstOrDefault().
    • +
    • Walkthrough: a primary constructor takes IUnitOfWork and forwards one FKNavigationDescriptor<LivePollOption, LivePoll, LivePollIdentifierType> to the base (LivePollOptionNavigationPopulator.cs:11-22). The descriptor (:15) sets PropertyName = nameof(LivePollOption.LivePoll) (:17), ParentKeySelector = e => e.LivePollId (:18, the FK on the option, which is the inversion relative to the child descriptor), ChildForeignKeySelector = child => child.Id (:19, the poll's own primary key), and AssignAction = (e, livePolls) => e.SetLivePoll(livePolls.FirstOrDefault()) (:20), calling the option's explicit setter method rather than assigning a public property. The class body is empty (:23-24).
    • +
    • Why it's built this way: the base loads parents in one batched query for a whole page of options rather than one query per option, so declaring the relationship in data is what removes the N+1 a naive lazy-loaded back-reference would create. Going through SetLivePoll is why LivePollOption.LivePoll can keep a private set and still be assignable here: the writer is named, not open to anyone.
    • +
    • Where it's used: registered as INavigationPopulator<LivePollOption> in the module's DI (DependencyInjection.cs:64).
    • +

    SessionQuestionViewBuilder

    MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.SessionQuestions.Services · MMCA.ADC.Engagement.Application/SessionQuestions/Services/SessionQuestionViewBuilder.cs:12 · Level 8 · class (sealed)

    @@ -2331,55 +2483,11 @@

    SessionQuestionViewBuilder

    SubmitQuestionHandler (MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/Submit/SubmitQuestionHandler.cs:35), GetSessionQuestionsHandler - (MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/GetSessionQuestions/GetSessionQuestionsHandler.cs:29), + (MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/GetSessionQuestions/GetSessionQuestionsHandler.cs:31), and GetModerationQueueHandler (MMCA.ADC.Engagement.Application/SessionQuestions/UseCases/GetModerationQueue/GetModerationQueueHandler.cs:23), the last being the callerUserId is null path. -

    LivePollResultsBuilder

    -
    -

    MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.LivePolls.Services · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/LivePolls/Services/LivePollResultsBuilder.cs:12 · Level 9 · class (sealed)

    -
    -
      -
    • What it is: the shared read-side service that computes poll result tallies: per-option active-vote counts, the total, the caller's own vote when there is a caller, and the poll's concurrency token. It computes one poll or a whole set with the same code path.
    • -
    • Depends on: IUnitOfWork (for the read repository), IQueryableExecutor (async materialization without an EF dependency in the Application layer), LivePoll / LivePollVote, and the result DTOs LivePollResultsDTO / LivePollOptionResultDTO.
    • -
    • Concept introduced, computing tallies with a grouped SQL COUNT instead of materializing votes. [Rubric §12, Performance & Scalability] assesses whether hot read paths avoid loading whole tables. The comment at LivePollResultsBuilder.cs:59-60 states the intent: tallies come from a GroupBy(new { LivePollId, OptionId }).Select(Count()) that returns one row per (poll, option) rather than one row per vote, on a path that runs on every vote, every results read, and every open-polls listing. Centralizing this in one builder means all three surfaces compute results identically (:8-10).
    • -
    • Concept introduced, batching a per-item query into a set-wide one. [Rubric §12, Performance & Scalability] again, on round trips rather than row counts. BuildManyAsync takes the whole poll set and issues a fixed number of queries: one grouped COUNT over every poll (:61-66) and, only when a caller is present, one read of that caller's votes across the same set (:75-79). The doc comment names the cost this replaced: a per-poll loop issued two queries per poll, so a session with a dozen open polls cost two dozen round trips (:33-38). Single-poll callers are not a separate code path; BuildAsync simply wraps its argument in a one-element list and takes results[0] (:29-30), so there is only one implementation to keep correct.
    • -
    • Concept introduced, making the parts add up to the whole. [Rubric §9, API & Contract Design] covers whether a payload is internally consistent. The projection keeps only the options a poll still presents (.Where(o => !o.IsDeleted), :99), so votes cast on an option that was later removed are excluded from the breakdown, and TotalVotes is summed from that same projected list rather than counted independently (:116). A client computing percentages from the parts therefore always reconciles with the total. Both comments say so in place (:95-97, :114-115).
    • -
    • Walkthrough: a primary constructor takes IUnitOfWork and IQueryableExecutor (:12); the class has three members.
        -
      • BuildAsync(poll, userId?, cancellationToken) (:22-31): null-checks the poll (:27), delegates to BuildManyAsync([poll], ...) (:29), and returns the single element (:30).
      • -
      • BuildManyAsync(polls, userId?, cancellationToken) (:44-88): null-checks (:49), returns an empty list for an empty input before touching the database (:51-54), takes a no-tracking read repository for LivePollVote (:56) and the distinct poll ids (:57). It runs the grouped count over voteRepo.TableNoTracking filtered by pollIds.Contains(v.LivePollId) (:61-66) and folds the rows into a dictionary keyed by the (LivePollId, OptionId) tuple (:68). The caller's own votes are a separate set-wide read issued only when userId is non-null: broadcast payloads pass null and skip it entirely (BR-229, :70-85). Finally it maps every poll through Assemble in the order supplied (:87).
      • -
      • Assemble(poll, countsByPollOption, myVoteByPoll) (:90-123), a private static: filters to non-deleted options, orders by Sort, and projects each into a LivePollOptionResultDTO whose VoteCount comes from the dictionary via GetValueOrDefault, so an option with zero votes still appears (:98-107). It then assembles the LivePollResultsDTO (:109-122) with poll id, question, status, the summed TotalVotes (:116), the options, MyVoteOptionId (:118, null when no caller or no vote), and RowVersion (:121). That last line is deliberate: the concurrency token travels with the results so a surface fed only by results holds the token it puts in the If-Match header of an open or close (:119-120).
      • -
      -
    • -
    • Why it's built this way: the grouped count keeps the tally cost proportional to option count rather than vote count, and the set-wide shape keeps the round-trip count constant rather than proportional to the number of polls on screen. Skipping the "my vote" read for broadcast payloads (which have no single caller) avoids a pointless query on the fan-out path.
    • -
    • Where it's used: registered as scoped in the module's DI (DependencyInjection.cs:68) and injected into CastVoteHandler (CastVoteHandler.cs:21, called at :91), GetPollResultsHandler (GetPollResultsHandler.cs:26), and GetOpenPollsHandler (GetOpenPollsHandler.cs:17, the one caller of BuildManyAsync at :47), and resolved out of a fresh scope by LivePollVoteChangedHandler for the results broadcast (LivePollVoteChangedHandler.cs:55,73).
    • -
    • Caveats / not-in-source: Options must already be loaded on every passed LivePoll (via LivePollNavigationPopulator or an explicit include, as LivePollVoteChangedHandler does at LivePollVoteChangedHandler.cs:60). Assemble reads poll.Options directly and does not load it; the XML docs say so at :15-16 and :38.
    • -
    -

    LivePollNavigationPopulator

    -
    -

    MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.LivePolls.Services · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/LivePolls/Services/LivePollNavigationPopulator.cs:11 · Level 10 · class (sealed)

    -
    -
      -
    • What it is: the declarative navigation populator that loads a LivePoll's Options collection on query-service paths where EF Core .Include() is not applied.
    • -
    • Depends on: DeclarativeNavigationPopulator<TEntity> (base), ChildNavigationDescriptor<TEntity, TParentId, TChild, TChildId> (the descriptor), IUnitOfWork, LivePoll / LivePollOption.
    • -
    • Concept reinforced, declarative navigation population (ADR-002). [Rubric §2, Design Patterns]. The framework's entity-query path returns entities without EF includes; a populator declares, in data, which child collections to rehydrate and how. That is the whole class: it subclasses DeclarativeNavigationPopulator<TEntity> closed over LivePoll and passes exactly one ChildNavigationDescriptor<TEntity, TParentId, TChild, TChildId> (LivePollNavigationPopulator.cs:11-22).
    • -
    • Walkthrough: a primary constructor takes IUnitOfWork and forwards a single-element descriptor array to the base (:11-22). The descriptor (:15) wires PropertyName = nameof(LivePoll.Options) (:17), ParentKeySelector = p => p.Id (:18), ChildForeignKeySelector = child => child.LivePollId (:19), and AssignAction = (p, options) => p.SetOptions(options) (:20). That last line calls the aggregate's internal SetOptions, so the collection is rehydrated through the root's own SetItems path rather than by writing the backing field directly. The class body is empty (:23-24); all behavior lives in the base.
    • -
    • Why it's built this way: expressing the load as a descriptor rather than hand-written query code keeps every populator uniform and lets the base own batching and assignment. Routing the assignment through SetOptions preserves the aggregate boundary even during rehydration.
    • -
    • Where it's used: registered as INavigationPopulator<LivePoll> in the module's DI (DependencyInjection.cs:59), so the query pipeline runs it before LivePollResultsBuilder reads poll.Options. Note the sibling registration one line on: LivePollVote gets a NullNavigationPopulator<TEntity> (DependencyInjection.cs:60), because a vote has nothing to rehydrate.
    • -
    -

    LivePollOptionNavigationPopulator

    -
    -

    MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.LivePolls.Services · MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/LivePolls/Services/LivePollOptionNavigationPopulator.cs:11 · Level 10 · class (sealed)

    -
    -
      -
    • What it is: the mirror-image populator for LivePollOption: it fills the option's back-reference to its parent LivePoll when an option is queried on its own.
    • -
    • Depends on: DeclarativeNavigationPopulator<TEntity> (base), FKNavigationDescriptor<TEntity, TChild, TChildId> (the descriptor), IUnitOfWork, LivePoll / LivePollOption.
    • -
    • Concept reinforced, the two descriptor flavors (see LivePollNavigationPopulator and ADR-002). [Rubric §2, Design Patterns]. This pair is the clearest illustration of the difference anywhere in the module. A ChildNavigationDescriptor<TEntity, TParentId, TChild, TChildId> walks down from a parent key to many children, while an FKNavigationDescriptor<TEntity, TChild, TChildId> walks up an FK to a single parent, which is why its AssignAction ends in FirstOrDefault().
    • -
    • Walkthrough: a primary constructor takes IUnitOfWork and forwards one FKNavigationDescriptor<LivePollOption, LivePoll, LivePollIdentifierType> to the base (LivePollOptionNavigationPopulator.cs:11-22). The descriptor (:15) sets PropertyName = nameof(LivePollOption.LivePoll) (:17), ParentKeySelector = e => e.LivePollId (:18, the FK on the option, which is the inversion relative to the child descriptor), ChildForeignKeySelector = child => child.Id (:19, the poll's own primary key), and AssignAction = (e, livePolls) => e.SetLivePoll(livePolls.FirstOrDefault()) (:20), calling the option's explicit setter method rather than assigning a public property. The class body is empty (:23-24).
    • -
    • Why it's built this way: the base loads parents in one batched query for a whole page of options rather than one query per option, so declaring the relationship in data is what removes the N+1 a naive lazy-loaded back-reference would create. Going through SetLivePoll is why LivePollOption.LivePoll can keep a private set and still be assignable here: the writer is named, not open to anyone.
    • -
    • Where it's used: registered as INavigationPopulator<LivePollOption> in the module's DI (DependencyInjection.cs:64).
    • -

    DeleteLivePollHandler

    MMCA.ADC.Engagement.Application · MMCA.ADC.Engagement.Application.LivePolls.UseCases.Delete · MMCA.ADC.Engagement.Application/LivePolls/UseCases/Delete/DeleteLivePollHandler.cs:14 · Level 9 · class (sealed)

    @@ -2700,7 +2808,7 @@

    LivePollVoteChangedHandler

  • Where it's used: discovered by convention-based scanning (MMCA.ADC.Engagement.Application/DependencyInjection.cs:85) and invoked by the domain-event dispatcher after CastVoteHandler commits; its comment at - MMCA.ADC.Engagement.Application/LivePolls/UseCases/CastVote/CastVoteHandler.cs:88-89 points back + MMCA.ADC.Engagement.Application/LivePolls/UseCases/CastVote/CastVoteHandler.cs:68-69 points back here to explain why the handler itself no longer publishes.

  • diff --git a/docs/onboarding/group-24-identity-module.html b/docs/onboarding/group-24-identity-module.html index 4a9360f8..5f70df85 100644 --- a/docs/onboarding/group-24-identity-module.html +++ b/docs/onboarding/group-24-identity-module.html @@ -238,7 +238,9 @@

    Projects, one bounded context

    IdentityPermissions / IdentityPermissionGrants capability constants and grant map (MMCA.ADC.Identity.Shared/Authorization/IdentityPermissions.cs:10, - IdentityPermissionGrants.cs:17). MMCA.ADC.Identity.UI sits in the same module folder and holds the + IdentityPermissionGrants.cs:17), and the JwtAudience configuration contract that + every host validating an Identity-minted token reads + (MMCA.ADC.Identity.Shared/Authorization/JwtAudience.cs:14). MMCA.ADC.Identity.UI sits in the same module folder and holds the Blazor pages; two further projects live under MMCA.ADC/Source/Services/ instead, because they exist only for the extracted topology: MMCA.ADC.Identity.Contracts (the gRPC adapter) and MMCA.ADC.Identity.Service (the extracted process host). The identifier alias for this context is @@ -286,7 +288,7 @@

    Authentication: a the BR-205 / BR-206 refresh-session rotation with reuse detection (ADR-050); the ADC subclass documents that division of labor at - MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:16-47. - AuthenticationService (AuthenticationService.cs:48) fills in only the - context-specific pieces: CreateUser supplies the Attendee default role (BR-45, :143-150), + MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:17-49. + AuthenticationService (AuthenticationService.cs:50) fills in only the + context-specific pieces: CreateUser supplies the Attendee default role (BR-45, :139-146), CreateAccessToken attaches the speaker_id claim when the user is linked to a speaker (BR-209, - :153-154 via the SpeakerClaims helper at :374), OnUserRegisteredAsync raises the - UserRegistered integration event (:170), ValidateLoginCandidateAsync and + :149-150 via the SpeakerClaims helper at :383), OnUserRegisteredAsync raises the + UserRegistered integration event (:166-169), ValidateLoginCandidateAsync and ValidateRefreshCandidateAsync refuse an account an administrator has locked, after the password - check so the distinct message cannot be read by anyone sweeping addresses (:100-119), and - ExternalLoginAsync drives the OAuth find-by-provider, else link-by-email, else create flow (:196, - body at :208, - ADR-036). Two of its constructor - dependencies name the session model directly: IRefreshSessionStore - and IOptions<RefreshSessionSettings> are required, so - the per-user session cap and the enabled flag are read from bound configuration on every login and - refresh rather than from an unstated default (:56-57, rationale at :42-46); a third, - IOptions<EmailConfirmationSettings>, is handed to - the base as well (:58, :69).

    + check so the distinct message cannot be read by anyone sweeping addresses (:96-120), and + ExternalLoginAsync drives the OAuth find-by-provider, else link-by-email, else create flow (:192, + body at :204, + ADR-036); that flow applies the + same administrator lock to both existing-account branches before anything is saved, so the lock binds + every sign-in path and not only the password and refresh hooks (:284-295). Session issuance is + deliberately not among its constructor dependencies: access-token minting, refresh-session rotation + and the per-user session cap live in the framework + IAuthSessionIssuer that AddInfrastructure registers, so the + token lifetimes and the cap are configuration (the Jwt and RefreshSessions sections) rather than + arguments of this service (:56, :63, rationale at :43-48). The shared + AuthenticationValidators bundle (:55) and + IOptions<EmailConfirmationSettings> (:57) are + handed straight to the base as well (:62, :65).

    Three details in that class are worth reading closely. First, atomicity: RegisterAsync wraps the - whole base workflow in UnitOfWork.ExecuteInTransactionAsync (AuthenticationService.cs:89), so + whole base workflow in UnitOfWork.ExecuteInTransactionAsync (AuthenticationService.cs:85), so the user insert (first save) and the outbox row for UserRegistered (raised in - OnUserRegisteredAsync once the id exists, :170-174) commit together. + OnUserRegisteredAsync once the id exists, :166-172) commit together. The event is not fire-and-forget after the fact, it is captured by the outbox inside the same transaction (ADR-003); the class remarks spell out the failure the design removes, namely a serialized UserId = 0 payload that the - cross-service Conference consumer cannot resolve (:29-41). The external-login path does the same - through ExternalLoginAsync (:196, transaction at :203, with the event raised only for a - brand-new account, :295-300). Second, the link-by-email gate: linking an external identity to an + cross-service Conference consumer cannot resolve (:31-41). The external-login path does the same + through ExternalLoginAsync (:192, transaction at :199, with the event raised only for a + brand-new account, :299-309). The two paths differ in one payload field: a local registration + raises the event with EmailVerified: false, because an address typed into a form proves nothing, so + Conference logs any speaker whose email matches for an organizer to link by hand instead of + auto-linking (:160-163, :169), while the OAuth path carries the provider's own verified-email + assertion, which is what decides whether that auto-link may run at all (:181-183, :260-265, + :307). Second, the link-by-email gate: linking an external identity to an existing local account on nothing but an email match would be an account-takeover path through any provider that hands out unverified emails, so TryLinkProviderToExistingAccountAsync - (AuthenticationService.cs:319) consults + (AuthenticationService.cs:328) consults IExternalLoginEmailVerifier (MMCA.ADC.Identity.Application/Users/IExternalLoginEmailVerifier.cs:11) and returns - Auth.ExternalEmailNotVerified when the answer is no (AuthenticationService.cs:341-350); the check + Auth.ExternalEmailNotVerified when the answer is no (AuthenticationService.cs:350-359); the check before it refuses outright when the address already resolves to an account linked to a different provider, because the aggregate stores one provider pair and re-linking would strand the original - login (:324-334), and the check after it refuses an account that already signs in with a password - (Auth.ExternalLinkRequiresLocalSignIn, :352-365), because the verifier proves only the provider + login (:333-343), and the check after it refuses an account that already signs in with a password + (Auth.ExternalLinkRequiresLocalSignIn, :361-374), because the verifier proves only the provider side of the address and nothing proves the local row's address was ever confirmed, so only an account with no local credentials is claimable by an email match. Its implementation HttpContextExternalLoginEmailVerifier @@ -365,18 +376,26 @@

    Authentication: a absent principal, or non-request context all report unverified. It fails closed, which means GitHub logins (whose OAuth payload carries no such assertion) never auto-link by design; the host maps Google's email_verified and legacy verified_email payload keys onto that one claim - (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:207-208) and does the same for Apple's + (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:206-207) and does the same for Apple's id_token assertion (:213). Identity signs its tokens with RS256 and publishes the public key at /.well-known/jwks.json; peer services validate tokens by fetching that document rather than sharing a secret (Program.cs:33-37, ADR-004, [Rubric §11, - Security]). Third, one EmailExistsAsync override deserves its own note, because the reason is the - opposite of the obvious one: it passes ignoreQueryFilters: true (AuthenticationService.cs:139-140) + Security]). The audience those peers expect is configuration with one named contract: + JwtAudience owns the Jwt:Audience key (JwtAudience.cs:17) and a + RequireConfigured guard that throws when the value is absent or blank (:25-29), and the + Conference, Engagement and Notification hosts each pass their bearer setup through it + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:329, + MMCA.ADC.Engagement.Service/Program.cs:177, MMCA.ADC.Notification.Service/Program.cs:163). The + value deliberately has no hardcoded fallback: a host that silently used one would boot with an + audience nobody configured and answer every request with a 401 that reads like a token problem + instead of a wiring problem, so a missing key fails at startup instead (JwtAudience.cs:8-13). Third, one EmailExistsAsync override deserves its own note, because the reason is the + opposite of the obvious one: it passes ignoreQueryFilters: true (AuthenticationService.cs:135-136) not to keep an erased address reserved. Erasure always pairs Delete with Anonymize, which rewrites the address to a placeholder, so a real erased email is re-registrable by design (GDPR), and the unique Email index is filtered on [IsDeleted] = 0 anyway, so a soft-deleted row no longer blocks the insert. The filter bypass stays so the pre-insert check reports a live conflict instead of letting - a legacy or placeholder row surface as a save-time surprise (:130-138).

    + a legacy or placeholder row surface as a save-time surprise (:126-136).

    The HTTP surface is equally thin. AuthController (MMCA.ADC.Identity.API/Controllers/AuthController.cs:30) extends UserAccountAuthControllerBase<TChangePasswordCommand, TChangePreferencesCommand> @@ -401,6 +420,12 @@

    Authentication: a ADR-032), ChangePreferencesHandler (ChangePreferencesHandler.cs:17), and GetUserPreferencesHandler (GetUserPreferencesHandler.cs:8-13). + The password one is body-less but not dependency-less: it hands the base the + IRefreshSessionStore, the + ILoginProtectionService and a TimeProvider + (ChangePasswordHandler.cs:28-30) so a successful change revokes every live session on the account, + because otherwise a stolen refresh chain outlives the credential the user just replaced, which is the + one remediation a compromised account has (:19-22). OAuthController (OAuthController.cs:20) is a body-less subclass of OAuthControllerBase (G12) that drives the Google/GitHub/Apple challenge, callback, complete, single-use-code-exchange flow with tokens never @@ -477,7 +502,7 @@

    Email confirmation: the idempotent ConfirmEmail() (:283), set directly when an OAuth provider already asserts a verified address (:271, :304), and reset during erasure (:500). EF maps it as a required column with a false default, so a row written by anything that does not set it stays valid and the - migration can backfill the accounts that existed before the flag did (UserConfiguration.cs:109-113).

    + migration can backfill the accounts that existed before the flag did (UserConfiguration.cs:107-112).

    Both halves are ADC specializations over framework bases. SendEmailConfirmationCommand (.../UseCases/SendEmailConfirmation/SendEmailConfirmationCommand.cs:20) carries one address and @@ -673,25 +698,30 @@

    Avatars: the third mutating slice

    replaces the caller's avatar rather than appending one, so replaying the stored URL for a repeated Idempotency-Key saves a re-encode and keeps a flaky mobile upload from looking like a failure (:72-77). SetUserAvatarHandler - (MMCA.ADC.Identity.Application/Users/UseCases/SetUserAvatar/SetUserAvatarHandler.cs:26) handles a + (MMCA.ADC.Identity.Application/Users/UseCases/SetUserAvatar/SetUserAvatarHandler.cs:27) handles a SetUserAvatarCommand that carries the bytes as a ReadOnlyMemory<byte> (SetUserAvatarCommand.cs:10) and is built on MutateEntityPayloadHandlerBase<TCommand, TEntity, TIdentifierType, TResultPayload> - (:26). It never trusts the client-declared content type: it sniffs magic bytes through the shared + (:34). It never trusts the client-declared content type: it sniffs magic bytes through the shared ImageContentSniffer before the aggregate is even loaded, so an unsupported upload gets the format error whether or not the account exists - (:43-58), re-encodes to a canonical 256x256 JPEG via - IImageProcessor (:28), uploads under a + (:52-69), re-encodes to a canonical 256x256 JPEG via + IImageProcessor (:29, :103), uploads under a randomized blob name through - IFileStorageService (:29), and parks the - replaced blob's name in the MutationContext (:98) - so OnMutatedAsync (:113) can schedule its deletion once the new URL is persisted. That deletion is - not best effort: the handler enqueues a + IFileStorageService (:30, :111-118), and + parks the replaced blob's name in the MutationContext + (:126) so OnMutatedAsync (:141) can schedule its deletion once the new URL is persisted. That + deletion is not best effort: the handler enqueues a DeleteAvatarBlobInternalCommand through the framework - internal-command scheduler (:30, :127-133), a durable row the processor retries until storage - answers (ADR-114). The random suffix - means a replacement never reuses the old URL, so stale caches self-resolve (:12-14). The result - travels back as a UserAvatarDTO built in BuildResult (:138). + IInternalCommandScheduler + (:31, :156-160), a durable row the processor retries until storage answers (ADR-114). The upload + runs inside the mutation, so it also covers the opposite orphan: when the save that should point the + row at the new blob fails or throws, HandleAsync schedules that blob's deletion before passing the + original outcome on (:71-89), from a fresh DI scope with no cancellation, because the request scope + is the one whose save just failed and a cancelled request must not cancel the cleanup (:175-198). + The random suffix means a replacement never reuses the old URL, so stale caches self-resolve + (:17-18). The result travels back as a UserAvatarDTO built in BuildResult + (:169). RemoveUserAvatarHandler (over RemoveUserAvatarCommand) and GetUserAvatarHandler (over GetUserAvatarQuery) are @@ -725,14 +755,16 @@

    Persistence, seeding, and the EntityTypeConfigurationSQLServer<TEntity, TIdentifierType>, maps the Email value object through the shared EmailValueConverter (:20-24), mirrors the - invariant length constants onto the columns, ignores the computed FullName and IsExternalLogin - members (:116-118), and pins three indexes that encode business rules as schema ([Rubric §8, Data - Architecture]): unique Email (:123), a filtered unique index on LinkedSpeakerId that enforces - the 1:1 User-to-Speaker relationship of BR-208 (:125-127), and a filtered unique composite on - (LoginProvider, ProviderKey) for external accounts (:129-131). None of them spells the soft-delete + invariant length constants onto the columns, stores the names and the free-text device fields as + nvarchar so a non-Latin name is kept as entered rather than corrupted to ? by a varchar code + page (:26-27, :65-66), ignores the computed FullName, IsExternalLogin and IsLocked members + (:114-117), and pins three indexes that encode business rules as schema ([Rubric §8, Data + Architecture]): unique Email (:122), a filtered unique index on LinkedSpeakerId that enforces + the 1:1 User-to-Speaker relationship of BR-208 (:124-126), and a filtered unique composite on + (LoginProvider, ProviderKey) for external accounts (:128-130). None of them spells the soft-delete clause by hand: the framework's SoftDeleteUniqueIndexConvention - appends AND [IsDeleted] = 0 at model finalization, and writing it here would double it (:120-122, + appends AND [IsDeleted] = 0 at model finalization, and writing it here would double it (:119-121, ADR-095).

    Seeding is gated, not ambient. IdentityModuleSeeder (MMCA.ADC.Identity.API/IdentityModuleSeeder.cs:15) returns immediately unless @@ -792,11 +824,13 @@

    Crossing the BaseIntegrationEvent named Identity.UserRegistered.v1 on the wire, :35) on the aggregate, and the outbox carries it to Conference, whose UserRegisteredHandler - runs the speaker email-match auto-link (BR-207). Conference then publishes + runs the speaker email-match auto-link (BR-207) only when the event says the provider verified the + address, and otherwise leaves the match for an organizer to link by hand + (AuthenticationService.cs:160-163, :301-303). Conference then publishes SpeakerLinkedToUser / SpeakerUnlinkedFromUser back, which SpeakerLinkedToUserHandler - (MMCA.ADC.Identity.Application/Speakers/IntegrationEventHandlers/SpeakerLinkedToUserHandler.cs:27) + (MMCA.ADC.Identity.Application/Speakers/IntegrationEventHandlers/SpeakerLinkedToUserHandler.cs:32) and SpeakerUnlinkedFromUserHandler consume to set or clear User.LinkedSpeakerId (:54), so the speaker_id claim appears on the next token issued (eventual consistency, BR-209). Both extend @@ -837,7 +871,7 @@

    The UI edge

    ConfirmEmail page and IEmailConfirmationUIService, the latter registered by the Common UI layer - (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:128). + (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:144). The Profile page (MMCA.ADC.Identity.UI/Pages/Users/Profile/Profile.razor.cs:18) lets an authenticated user change their password, manage their avatar, and delete their account. It mirrors the server's 2 MB cap client-side before any upload starts (:28, :135-139), validates the new password @@ -891,15 +925,15 @@

    End-to-end: one registration

    receives the register POST, captures the client IP for BR-213 rate limiting and the user-agent for the session about to be opened (AuthController.cs:56-59), and calls RegisterAsync on AuthenticationService, which opens one transaction - (AuthenticationService.cs:89-91) and hands off to the shared G08 engine. The request shape was + (AuthenticationService.cs:85-87) and hands off to the shared G08 engine. The request shape was already checked by RegisterRequestValidator (RegisterRequestValidator.cs:12) in the pipeline, so the engine only has to confirm the email is not - taken (AuthenticationService.cs:139-140), call User.Create(...) with the Attendee role - (:143-150), hash the password through the shared + taken (AuthenticationService.cs:135-136), call User.Create(...) with the Attendee role + (:139-146), hash the password through the shared IPasswordHasher (ADR-032), add the aggregate, and save. Only after that first save, when the EF identity id exists, does OnUserRegisteredAsync raise - UserRegistered and save again (:170-176); both saves sit inside the one + UserRegistered and save again (:166-172); both saves sit inside the one transaction, so the user row and its outbox row commit atomically (ADR-003). One step follows the commit: AuthController schedules a @@ -908,11 +942,13 @@

    End-to-end: one registration

    the 201, because the account exists and the user can ask for a link from the confirmation page (AuthController.cs:73-93). The refresh half of the token pair is a row in the shared RefreshSessions store, not a column on the aggregate - (:263-268). The first access token returned does not yet carry speaker_id. Asynchronously, - Conference matches the email to a speaker and publishes SpeakerLinkedToUser; - SpeakerLinkedToUserHandler sets User.LinkedSpeakerId + (:263-268). The first access token returned does not yet carry speaker_id. Because the address + was typed into a form, the event says EmailVerified: false, so Conference does not auto-link on the + email match; it logs the matching speaker for an organizer to link by hand + (AuthenticationService.cs:160-163). Once that link is made, Conference publishes + SpeakerLinkedToUser; SpeakerLinkedToUserHandler sets User.LinkedSpeakerId (SpeakerLinkedToUserHandler.cs:54), and the attendee's next token carries the claim - (AuthenticationService.cs:153-154). No password left the domain in plaintext, no cross-database + (AuthenticationService.cs:149-150). No password left the domain in plaintext, no cross-database foreign key was written, no event was hand-dispatched, and the same code path behaves identically whether Identity runs inside the monolith or as its own service, which is exactly the property the framework groups (G01 through G15) exist to provide. For the why behind each choice, @@ -1016,7 +1052,7 @@

    IdentityErrorResources

  • Concept introduced, edge error-message localization keyed by error Code. [Rubric §27, Internationalization] (assesses whether user-facing strings, error text included, are translated rather than English-only). ADR-027 localizes failures at the API edge: a domain Error's Code (for example "User.Email.Empty") is the resource key, and the shared IErrorLocalizer looks that key up across every registered resource source before the failure is written into the ProblemDetails response. Each module contributes translations additively by registering its own anchor type, so Identity's strings live in IdentityErrorResources.resx and IdentityErrorResources.es.resx rather than in one central framework file that every module would have to edit.
  • Walkthrough: the class body is empty (IdentityErrorResources.cs:11-13); everything worth knowing is in the doc comment (:3-10), which records two design points. Keys are the domain error Code. And runtime-variable messages (those that interpolate a user-supplied value) are deliberately omitted from the .resx so they degrade to their English message with the value intact instead of showing a broken or value-less translation.
  • Why it's built this way: AddErrorResources<TResource>() (MMCA.Common/Source/Presentation/MMCA.Common.API/DependencyInjection.cs:136-141) resolves IStringLocalizerFactory, calls Create(typeof(TResource)), and registers the result as one more ErrorResourceSource singleton (:124-125); the convention "a .resx named after the type, sitting beside it" is what binds the strings, so an empty marker class is exactly enough. The framework registers its own ErrorResources through that very same helper inside AddErrorLocalization() (:107-113, the call at :111), which is what makes the mechanism additive rather than replace-only: each source is a plain AddSingleton, so registrations accumulate into the enumerable the localizer reads instead of overwriting one another.
  • -
  • Where it's used: registered at startup by the extracted Identity host, services.AddErrorResources<IdentityErrorResources>() (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:280).
  • +
  • Where it's used: registered at startup by the extracted Identity host, services.AddErrorResources<IdentityErrorResources>() (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:279).

  • IExternalLoginEmailVerifier

    @@ -1029,7 +1065,7 @@

    IExternalLoginEmailVerifier

  • Concept introduced, the account-takeover guard as an explicit port. [Rubric §11, Security] (assesses whether authentication trust decisions are explicit and fail closed) and [Rubric §3, Clean Architecture] (assesses dependencies pointing inward). Linking an external identity to a local account on nothing but an email match is a takeover primitive: any provider that hands out unverified email addresses would let an attacker register the victim's address and inherit the victim's account. The verified-email assertion lives in the short-lived ExternalLogin cookie principal, which is an HTTP concern, so the decision input is declared here as an interface in the Application layer and implemented at the API edge. Application code stays free of HttpContext, and the security rule stays testable with a two-line fake. [Rubric §1, SOLID]: an interface with exactly one method and exactly one reason to change.
  • Walkthrough: Task<bool> IsCurrentExternalLoginEmailVerifiedAsync() (IExternalLoginEmailVerifier.cs:19). The XML comment (:13-18) fixes the semantics precisely: true only when the provider explicitly asserts verification (Google's email_verified claim); providers that assert nothing (GitHub's OAuth flow) yield false. There is no third "unknown" state, unknown is treated as unverified. The type-level comment (:3-10) names the consumer and the threat it is defending against.
  • Why it's built this way: fail-closed by construction. Because the contract collapses "not verified" and "no assertion" into false, adding a new provider cannot silently open the auto-link path; it stays closed until someone deliberately maps a verification claim for it (ADR-036).
  • -
  • Where it's used: consumed by AuthenticationService inside the external-login workflow (MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:227-228); implemented by HttpContextExternalLoginEmailVerifier, which re-authenticates the ExternalLogin scheme and parses the email_verified claim (MMCA.ADC.Identity.API/Authentication/HttpContextExternalLoginEmailVerifier.cs:32-35), returning false when there is no HttpContext (:26-30), no principal, or no parseable claim value (:33-35).
  • +
  • Where it's used: consumed by AuthenticationService inside the external-login workflow (MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:223-224); implemented by HttpContextExternalLoginEmailVerifier, which re-authenticates the ExternalLogin scheme and parses the email_verified claim (MMCA.ADC.Identity.API/Authentication/HttpContextExternalLoginEmailVerifier.cs:32-35), returning false when there is no HttpContext (:26-30), no principal, or no parseable claim value (:33-35).

  • GetUsersQuery

    @@ -1080,7 +1116,7 @@

    IdentityModule

  • Concept introduced, the disabled-module stub. The module contract itself is taught in G14; the Identity-specific lesson is RegisterDisabledStubs. [Rubric §7, Microservices Readiness] (assesses whether modules compose and deploy independently): every ADC host boots the same module assemblies but enables only some of them. A host with Identity disabled still contains consumers that depend on IAttendeeQueryService (Notification needs the attendee id list for a broadcast), so this method registers DisabledAttendeeQueryService as a singleton (IdentityModule.cs:19-20). DI validation succeeds, the consumer degrades gracefully, and in the extracted topology the composition root later replaces that stub with a gRPC-backed adapter (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Contracts/DependencyInjection.cs:47, a services.Replace(...) of the scoped AttendeeQueryServiceGrpcAdapter; the comment at :45-46 records why Replace and not TryAdd: the in-process implementation or the stub may already be in the container).
  • Walkthrough: three members, all one-liners. Name => "Identity" (:16) is the topological-sort key and the value the loader logs. RegisterDisabledStubs(IServiceCollection) (:19-20) registers the stub singleton. Register(IServiceCollection, IConfigurationBuilder, ApplicationSettings) (:23-24) delegates straight to services.AddIdentityModule(applicationSettings); note that the IConfigurationBuilder parameter is accepted and unused here, because Identity contributes no configuration sources of its own. No dependency-declaration members are overridden, so the interface defaults apply (a leaf). There is deliberately no seeding here: that is a separate IModuleSeeder, IdentityModuleSeeder.
  • Why it's built this way: the module boundary is what makes each module extractable into its own service host without a rewrite (ADR-007, ADR-008). In the extracted MMCA.ADC.Identity.Service only this module is enabled; other services register the disabled stub and then overwrite it with a gRPC client. Application code never learns which transport it got.
  • -
  • Where it's used: discovered and registered in Kahn-topological order by the ModuleLoader at host startup; RegisterDisabledStubs runs in hosts where the Identity module is not enabled, for example MMCA.ADC.Notification.Service, whose composition root then calls AddIdentityAttendeeClient() over the top (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:225, with the rationale at :204).
  • +
  • Where it's used: discovered and registered in Kahn-topological order by the ModuleLoader at host startup; RegisterDisabledStubs runs in hosts where the Identity module is not enabled, for example MMCA.ADC.Notification.Service, whose composition root then calls AddIdentityAttendeeClient() over the top (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:222, with the rationale at :204).

  • DependencyInjection

    @@ -1134,40 +1170,40 @@

    GetUserAvatarHandler


    AuthenticationService

    -

    MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users · MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:48 · Level 14 · class (sealed)

    +

    MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users · MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:50 · Level 14 · class (sealed)

    • What it is: ADC's authentication service. The generic login, registration, refresh, and revocation workflow is inherited from AuthenticationServiceBase<TUser>; this subclass supplies the ADC-specific pieces: the Attendee default role, the speaker_id claim, the outbox-atomic UserRegistered integration event, and the entire external OAuth login flow.

    • -
    • Depends on: AuthenticationServiceBase<TUser> and IAuthenticationService; IUnitOfWork, ITokenService, IPasswordHasher, ILoginProtectionService, AuthenticationValidators, IRefreshSessionStore and TimeProvider (BCL); IOptions<RefreshSessionSettings> and IOptions<EmailConfirmationSettings> (:57-58), passed straight through to the base constructor; IExternalLoginEmailVerifier; User, UserRole, UserRegistered, Email, Result and Error; AuthenticationResponse and RegisterRequest; System.Security.Claims.

      +
    • Depends on: AuthenticationServiceBase<TUser> and IAuthenticationService; IUnitOfWork, IPasswordHasher, ILoginProtectionService, AuthenticationValidators and IAuthSessionIssuer (:56), plus IOptions<EmailConfirmationSettings> (:57), all passed straight through to the base constructor (:58-65, with twoFactor: null); IExternalLoginEmailVerifier; User, UserRole, UserRegistered, Email, Result and Error; AuthenticationResponse and RegisterRequest; System.Security.Claims.

    • Concept introduced (1), the template-method base with app-specific hooks. [Rubric §2, Design Patterns] (assesses whether recurring shapes use a named, understood pattern) and [Rubric §15, Best Practices & Code Quality]. The base owns the security-critical sequence: validate first, ADR-029 lockout and registration rate limits, the ADR-004 dual-fetch pattern, and BR-205/206 refresh-session rotation with reuse detection (ADR-050). What varies per application is expressed as hooks, and this class overrides exactly those:

        -
      • FindUntrackedByEmailAsync (:94-98), an untracked read (asTracking: false) used by the login path.
      • -
      • ValidateLoginCandidateAsync (:108-109) and ValidateRefreshCandidateAsync (:118-119), added for ADR-116: both delegate to the private CheckNotLocked (:121-127), which refuses Error.Unauthorized("Auth.AccountLocked", ...) when user.IsLocked. The base runs the login check after the password check, so reaching the distinct "locked" message already proves the caller owns the account, which is what makes the message safe to show (it is not an oracle for sweeping addresses). The refresh check is the backstop for a session opened the same instant an admin locks the account, since locking revokes the live sessions.
      • -
      • EmailExistsAsync (:139-140), the registration uniqueness probe, run with ignoreQueryFilters: true.
      • -
      • CreateUser (:143-150), which fixes ADC's default role as UserRole.Attendee.Value (BR-45).
      • -
      • CreateAccessToken (:153-154), which appends the speaker_id claim when the account is linked to a speaker; the claim is built by the private SpeakerClaims helper, returning null when LinkedSpeakerId has no value so the claim is simply absent rather than empty (BR-209).
      • -
      • OnUserRegisteredAsync (:170-176), the post-save hook, below.
      • +
      • FindUntrackedByEmailAsync (:90-94), an untracked read (asTracking: false) used by the login path.
      • +
      • ValidateLoginCandidateAsync (:104-105) and ValidateRefreshCandidateAsync (:114-115), added for ADR-116: both delegate to the private CheckNotLocked (:117-123), which refuses Error.Unauthorized("Auth.AccountLocked", ...) when user.IsLocked. The base runs the login check after the password check, so reaching the distinct "locked" message already proves the caller owns the account, which is what makes the message safe to show (it is not an oracle for sweeping addresses). The refresh check is the backstop for a session opened the same instant an admin locks the account, since locking revokes the live sessions.
      • +
      • EmailExistsAsync (:135-136), the registration uniqueness probe, run with ignoreQueryFilters: true.
      • +
      • CreateUser (:139-146), which fixes ADC's default role as UserRole.Attendee.Value (BR-45).
      • +
      • CreateAccessToken (:149-150), which appends the speaker_id claim when the account is linked to a speaker; the claim is built by the private SpeakerClaims helper (:383-384), returning null when LinkedSpeakerId has no value so the claim is simply absent rather than empty (BR-209).
      • +
      • OnUserRegisteredAsync (:166-172), the post-save hook, below.
    • Concept introduced (2), bypassing the query filter for a uniqueness probe, and what that actually protects. [Rubric §8, Data Architecture] and [Rubric §30, Compliance / Privacy / Data Governance]. Erasure in ADC always pairs Delete with Anonymize, which rewrites the stored address to a placeholder, so an erased account's real email is re-registrable by design, which is exactly what a right-to-erasure promise requires (ADR-005). And the unique Email index is filtered on [IsDeleted] = 0, because the framework's SoftDeleteUniqueIndexConvention appends that clause, so a soft-deleted row does not block the insert at the database level either. What the ignoreQueryFilters: true bypass buys is diagnosis rather than enforcement: the pre-insert check keeps seeing past the filter so it can report a live conflict as a clean domain error, instead of letting a legacy or placeholder row surface later as a save-time surprise.

    • Concept introduced (3), the outbox-atomic registration event, and what its EmailVerified flag settles. [Rubric §6, CQRS & Event-Driven], [Rubric §8, Data Architecture], [Rubric §29, Resilience & Business Continuity]. User.Id is a database-generated identity column, so at the moment the aggregate is created the id is still 0. The outbox serializes an event's payload at capture time, so raising UserRegistered before the insert would persist UserId = 0, and the cross-service Conference consumer, which has no access to the Identity database to re-match by email, could never resolve it. The fix is visible in two places:

        -
      • RegisterAsync (:84-91) re-implements the interface member with new and wraps the base implementation in UnitOfWork.ExecuteInTransactionAsync(token => base.RegisterAsync(request, ipAddress, userAgent, token), cancellationToken). The interface is re-listed on the class declaration specifically so this override wins for callers holding an IAuthenticationService, since the base method is not virtual.
      • -
      • OnUserRegisteredAsync (:170-176) runs after the base's first save, when the identity value exists: it calls user.AddDomainEvent(new UserRegistered(user.Id, user.Email.Value, user.FirstName, user.LastName, user.Role, EmailVerified: false)), saves a second time so the outbox row is captured, and returns the user. Local registration always passes EmailVerified: false, because the address was typed into the form and nothing proves the caller owns it: Conference logs any speaker whose email matches for an organizer to link by hand (BR-209) rather than auto-linking. Both saves sit inside the one transaction opened by RegisterAsync, so a crash before commit rolls back user and event together, and after commit the OutboxProcessor guarantees delivery.
      • +
      • RegisterAsync (:80-87) re-implements the interface member with new and wraps the base implementation in UnitOfWork.ExecuteInTransactionAsync(token => base.RegisterAsync(request, ipAddress, userAgent, token), cancellationToken). The interface is re-listed on the class declaration specifically so this override wins for callers holding an IAuthenticationService, since the base method is not virtual.
      • +
      • OnUserRegisteredAsync (:166-172) runs after the base's first save, when the identity value exists: it calls user.AddDomainEvent(new UserRegistered(user.Id, user.Email.Value, user.FirstName, user.LastName, user.Role, EmailVerified: false)), saves a second time so the outbox row is captured, and returns the user. Local registration always passes EmailVerified: false, because the address was typed into the form and nothing proves the caller owns it: Conference logs any speaker whose email matches for an organizer to link by hand (BR-209) rather than auto-linking. Both saves sit inside the one transaction opened by RegisterAsync, so a crash before commit rolls back user and event together, and after commit the OutboxProcessor guarantees delivery.
    • -
    • Concept introduced (4), the three-way external-login resolution, now split into its own link helper. [Rubric §11, Security]. ExternalLoginAsync (:196-205) is a transaction wrapper around ExternalLoginCoreAsync (:208-308), which resolves the caller into exactly one of three paths:

      +
    • Concept introduced (4), the three-way external-login resolution, now split into its own link helper. [Rubric §11, Security]. ExternalLoginAsync (:192-201) is a transaction wrapper around ExternalLoginCoreAsync (:204-317), which resolves the caller into exactly one of three paths:

        -
      1. Known external identity (:217-220), a tracked lookup by LoginProvider and ProviderKey. Found means log in, nothing else runs.
      2. -
      3. Email matches an existing local account (:246-261). The raw claim is parsed through the value object first, Email.Create(email) (:236), failing closed with Error.Validation("Auth.ExternalEmailInvalid", ...) (:239-243) rather than taking .Value directly: Result<T>.Value is null on failure and does not throw, so an unparseable provider claim would otherwise turn the lookup into "find the users whose email IS NULL". When a match is found, the linking decision itself is now delegated to TryLinkProviderToExistingAccountAsync(existingUser, loginProvider, providerKey) (:253-258), a private helper (:319-376) that holds both refusals: a one-link-per-account check (the aggregate stores a single (LoginProvider, ProviderKey) pair, so linking a second provider would strand the original login, IsExternalLogin is LoginProvider is not null), and the takeover guard, externalLoginEmailVerifier.IsCurrentExternalLoginEmailVerifiedAsync(), refusing Error.Unauthorized("Auth.ExternalEmailNotVerified", ...) when the provider did not assert a verified email. Both refusal messages point at the forgot-password recovery path, so neither is a dead end.
      4. -
      5. Brand new user (:263-285). Unlike the link path, account creation is not gated on the verified-email assertion: emailVerified is captured from the same IsCurrentExternalLoginEmailVerifiedAsync() call (:268-269) and passed straight into User.CreateExternal(email, firstName, lastName, loginProvider, providerKey, emailVerified) (:275-276) rather than used to refuse. Refusing creation would close sign-up entirely for a provider that asserts nothing (GitHub carries no email_verified claim); instead the flag rides along on two downstream decisions: ADR-116 uses it to decide whether the new account starts already confirmed (a verified assertion) or unconfirmed and sent a confirmation link like a local registration, and it is carried on the UserRegistered event (:295-300) so Conference only auto-links a speaker profile when the address was actually proven.
      6. +
      7. Known external identity (:213-216), a tracked lookup by LoginProvider and ProviderKey. Found means log in, subject only to the lock check below.
      8. +
      9. Email matches an existing local account (:247-257). The raw claim is parsed through the value object first, Email.Create(email) (:232), failing closed with Error.Validation("Auth.ExternalEmailInvalid", ...) (:233-239) rather than taking .Value directly: Result<T>.Value is null on failure and does not throw, so an unparseable provider claim would otherwise turn the lookup into "find the users whose email IS NULL". When a match is found, the linking decision itself is now delegated to TryLinkProviderToExistingAccountAsync(existingUser, loginProvider, providerKey) (:249-250), a private helper (:328-378) that holds three refusals, checked in order: a one-link-per-account check, Error.Conflict("Auth.ExternalProviderAlreadyLinked", ...) (:337-343), run before any verifier round trip because the aggregate stores a single (LoginProvider, ProviderKey) pair and linking a second provider would strand the original login (IsExternalLogin is LoginProvider is not null); the provider-side takeover guard, externalLoginEmailVerifier.IsCurrentExternalLoginEmailVerifiedAsync() (:350-351), refusing Error.Unauthorized("Auth.ExternalEmailNotVerified", ...) (:353-358) when the provider did not assert a verified email; and the pre-registration takeover guard (SEC-ADC-01), Error.Conflict("Auth.ExternalLinkRequiresLocalSignIn", ...) (:368-374) whenever existingUser.HasLocalPassword. The verifier proves only the provider side of the address, and nothing proves the local row's address was ever confirmed, so linking an account that already signs in with a password could hand whoever typed that address into the registration form the provider owner's identity, with their own password still in place. Only an account with no local credentials is claimable by an email match, and only then does existingUser.LinkExternalProvider(loginProvider, providerKey) (:376) run. Every refusal message names a recovery path (sign in with the original provider, sign in with the password and link from the profile, or Forgot password), so none is a dead end.
      10. +
      11. Brand new user (:258-281). Unlike the link path, account creation is not gated on the verified-email assertion: emailVerified is captured from the same IsCurrentExternalLoginEmailVerifiedAsync() call (:264-265) and passed straight into User.CreateExternal(email, firstName, lastName, loginProvider, providerKey, emailVerified) (:271-272) rather than used to refuse. Refusing creation would close sign-up entirely for a provider that asserts nothing (GitHub carries no email_verified claim); instead the flag rides along on two downstream decisions: ADR-116 uses it to decide whether the new account starts already confirmed (a verified assertion) or unconfirmed and sent a confirmation link like a local registration, and it is carried on the UserRegistered event (:304-309) so Conference only auto-links a speaker profile when the address was actually proven.
      -

      All surviving paths then converge (:288): save; for a new user only, raise UserRegistered with the captured emailVerified flag and save again (:295-300), the same post-identity pattern as local registration; and finally hand off to the base's IssueTokensAsync(user, cancellationToken: cancellationToken) (:307), which opens a refresh session through the shared workflow (hash at rest, per-user cap, rotation chain) rather than stamping a plaintext token on the aggregate. The OAuth callback arrives through the framework's OAuthControllerBase, which does not surface the caller's IP or user-agent, so the session records neither; both fields are informational and nothing validates against them.

      +

      Before anything is saved, both existing-account paths (matched by provider key, or linked by email) pass the same ADR-116 lock check as the password and refresh hooks: CheckNotLocked(user, nameof(ExternalLoginAsync)) (:288-295) refuses a locked account before a session is opened, so an administrator lock binds every sign-in path; a brand-new account cannot be locked and skips it. All surviving paths then converge (:297): save; for a new user only, raise UserRegistered with the captured emailVerified flag and save again (:304-309), the same post-identity pattern as local registration; and finally hand off to the base's IssueTokensAsync(user, cancellationToken: cancellationToken) (:316), which opens a refresh session through the shared workflow (hash at rest, per-user cap, rotation chain) rather than stamping a plaintext token on the aggregate. The OAuth callback arrives through the framework's OAuthControllerBase, which does not surface the caller's IP or user-agent, so the session records neither; both fields are informational and nothing validates against them.

    • -
    • Why it's built this way: the base class keeps every application on one audited auth workflow, so a fix to lockout or refresh-session reuse detection lands once in MMCA.Common rather than per app; the hooks keep ADC's divergences (role, claim, event, lock check) small and named. The two IOptions<> bundles used to travel through a single AuthenticationServiceSettings facade; that type was deleted on 2026-09-23 and both bundles now reach the base constructor directly, which raised the architecture suite's constructor-dependency ceiling from 9 to 10 for this class rather than keep an artificial bundling slot (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Cqrs/ConstructorDependencyCountTests.cs:24-28). The UserRegistered integration event, rather than an in-process domain event, is a deliberate divergence from MMCA.Store, because Conference runs in a separate process with its own database and can only learn about a registration asynchronously (ADR-003 outbox, ADR-006 database-per-service). The EmailVerified flag is what lets Conference tell a typed-in address from a provider-proven one without re-implementing the OAuth trust decision on its own side of the boundary. The eventual-consistency cost stays the same either way: the first token issued does not yet carry speaker_id, and the claim appears on the next refresh once Conference has published SpeakerLinkedToUser back.

      +
    • Why it's built this way: the base class keeps every application on one audited auth workflow, so a fix to lockout or refresh-session reuse detection lands once in MMCA.Common rather than per app; the hooks keep ADC's divergences (role, claim, event, lock check) small and named. Session issuance (access-token minting, refresh-session rotation and the per-user session cap) sits behind the framework's IAuthSessionIssuer, which AddInfrastructure registers, so the token lifetimes and the cap are configuration (the Jwt and RefreshSessions sections) rather than constructor arguments of this service (class doc comment, :44-47); one collaborator stands where the token service, clock, session store and session options used to, leaving the constructor at 7 dependencies (:51-57). The architecture suite's constructor-dependency ceiling still reads 10 (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Cqrs/ConstructorDependencyCountTests.cs:28), and its comments still name this class as the high-water mark at 10 (:6, :24-27), so the gate carries headroom this class no longer uses. The UserRegistered integration event, rather than an in-process domain event, is a deliberate divergence from MMCA.Store, because Conference runs in a separate process with its own database and can only learn about a registration asynchronously (ADR-003 outbox, ADR-006 database-per-service). The EmailVerified flag is what lets Conference tell a typed-in address from a provider-proven one without re-implementing the OAuth trust decision on its own side of the boundary. The eventual-consistency cost stays the same either way: the first token issued does not yet carry speaker_id, and the claim appears on the next refresh once Conference has published SpeakerLinkedToUser back.

    • Where it's used: registered as the IAuthenticationService implementation by the Application-layer DependencyInjection (MMCA.ADC.Identity.Application/DependencyInjection.cs:35); driven by AuthController for local credentials and OAuthController for the social paths.

    • @@ -1399,79 +1435,6 @@

      ClassReference

    • Caveats / not-in-source: whether the API-layer copy has an active consumer is Not determinable from source within this unit; the API registration (MMCA.ADC.Identity.API/DependencyInjection.cs:47-59) does not reference it.

    -

    IAttendeeQueryService

    -
    -

    MMCA.ADC.Identity.Shared · MMCA.ADC.Identity.Shared.Users · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/IAttendeeQueryService.cs:11 · Level 0 · interface

    -
    -
      -
    • What it is: the one cross-module read contract Identity publishes. It answers a single question, "which user ids are active attendees?", so the Notification module can fan a broadcast out to every attendee without knowing anything about the User aggregate.
    • -
    • Depends on: MMCA.Common.Shared.Abstractions for the ServiceContractAttribute marker (IAttendeeQueryService.cs:1), the UserIdentifierType alias, and BCL (Task, IReadOnlyList<T>, CancellationToken). Note what is not imported: no Identity Domain, Application, or Infrastructure namespace appears anywhere in the file.
    • -
    • Concept introduced, the cross-module contract in the Shared assembly. [Rubric §7, Microservices Readiness] (assesses whether modules talk through narrow, transport-agnostic interfaces that survive extraction into separate processes) and [Rubric §3, Clean Architecture] (assesses dependency direction: the consumer depends on an abstraction, not on the producer's internals). The doc comment (IAttendeeQueryService.cs:5-9) states the rule outright: the contract lives in Shared so the Notification module can call it without depending on the Identity implementation, preserving module boundary isolation. That one placement decision is what makes three different wirings interchangeable behind the same interface: the in-process AttendeeQueryService when Identity runs in the same host, the DisabledAttendeeQueryService stub when the module is switched off, and the AttendeeQueryServiceGrpcAdapter when Identity runs as its own service. No consumer code changes between those three.
    • -
    • Concept reinforced, the [ServiceContract] marker as a machine-checked boundary. The attribute at IAttendeeQueryService.cs:10 is not decoration and it is not System.ServiceModel: it is the framework's own marker introduced in G13, whose doc records that a dedicated ServiceContractPurityTestsBase fitness rule scans every mapped assembly for types carrying it and fails the build if such a type depends on the producing service's Domain, Application, or Infrastructure layers (MMCA.Common/Source/Core/MMCA.Common.Shared/Abstractions/ServiceContractAttribute.cs:3-19). Marking this interface therefore converts "we intend to keep this extractable" from a comment into a test ([Rubric §14, Testability], [Rubric §34, Architecture Governance and Documentation]). The attribute also carries a Version that defaults to "v1" (ServiceContractAttribute.cs:37); this interface uses the parameterless constructor, so it is implicitly v1.
    • -
    • Walkthrough: one member. GetAttendeeUserIdsAsync(CancellationToken cancellationToken = default) (IAttendeeQueryService.cs:18) returns Task<IReadOnlyList<UserIdentifierType>>, documented as the identifiers of all active (non-deleted) users with the Attendee role (IAttendeeQueryService.cs:13-17). The return type is deliberately just ids, not user records: the caller needs recipients, not personal data, so the contract carries the minimum ([Rubric §30, Compliance, Privacy and Data Governance], data minimization across a module boundary).
    • -
    • Why it's built this way: a coarse, id-only, async, cancellable method maps cleanly onto a gRPC unary call, which is exactly the extraction path ADR-007 describes, and the [ServiceContract] marker names it as such. Anything richer (a filtered query object, an IQueryable) would leak Identity's persistence model across the boundary and would not survive the process split.
    • -
    • Where it's used: consumed by Notification's AttendeeNotificationRecipientProvider (which bridges it to INotificationRecipientProvider); implemented in-process by AttendeeQueryService, stubbed by DisabledAttendeeQueryService, served over the wire by AttendeesGrpcService, and satisfied remotely by AttendeeQueryServiceGrpcAdapter.
    • -
    -

    UserAdminDTO

    -
    -

    MMCA.ADC.Identity.Shared · MMCA.ADC.Identity.Shared.Users · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/UserAdminDTO.cs:23 · Level 0 · record (sealed)

    -
    -
      -
    • What it is: the administration-facing view of a user account, served behind the users:manage capability: id, email, name, role, creation date, lock state, and the two account-nature flags an operator needs to act on the account: whether it signs in externally and whether the email is confirmed.
    • -
    • Depends on: the UserIdentifierType alias; BCL (DateTime).
    • -
    • Concept introduced, the permission-scoped sibling DTO. [Rubric §8, Data Architecture] (assesses whether reads project only the columns a screen needs) and [Rubric §11, Security] (assesses that secrets never reach a serialization boundary). The type's remarks (UserAdminDTO.cs:8-13) states directly why this is not more fields bolted onto UserListDTO: the two DTOs are reached through different permissions, identity:users:read serves the organizer's roster, users:manage serves the administration screens. Device metadata stays out of both (attendee device privacy), and no credential material is ever projected here either, the same restraint UserDataExportSubjectDTO documents for the export path.
    • -
    • Walkthrough: nine positional parameters (UserAdminDTO.cs:23-32), each documented at :14-22: UserId, Email, FirstName, LastName, Role, CreatedOn, a nullable LockedOn, IsExternalLogin, and IsEmailConfirmed (ADR-116). Two computed members read off those without adding wire fields: IsLocked => LockedOn is not null (:35) and FullName => $"{FirstName} {LastName}" (:38).
    • -
    • Why it's built this way: this is the app-owned TUserDto closing the framework's generic administration slice. UserAdministrationService implements IUserAdministrationService<UserAdminDTO> (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/Administration/UserAdministrationService.cs:43) and projects it directly inside the paged EF query, .Select(u => new UserAdminDTO(...)) after OrderBy/Skip/Take (UserAdministrationService.cs:72-86), so the excluded columns never leave the database: the same projection-as-privacy-boundary pattern UserListDTO uses for its own row. UsersAdminController closes the framework's generic controller over it, UsersAdminControllerBase<UserAdminDTO> (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersAdminController.cs:25-26), and both DI registrations name the closed type explicitly: services.TryAddScoped<IUserAdministrationService<UserAdminDTO>, UserAdministrationService>() on the Application side (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:42) and services.AddUserAdministrationUI<UserAdminDTO>() on the UI side (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/DependencyInjection.cs:31).
    • -
    • Where it's used: produced by UserAdministrationService.ListAsync/GetAsync (UserAdministrationService.cs:53-108) and served through UsersAdminController's Admin/Users endpoints; consumed client-side as IUserAdminUIService<UserAdminDTO> on the UserDetail page (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Pages/Users/UserDetail/UserDetail.razor.cs:32, :42), and covered by unit and controller tests (MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.UI.Tests/Pages/Users/UserDetail/UserDetailTests.cs, MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/Administration/UserAdministrationServiceTests.cs, MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.API.Tests/Controllers/UsersAdminControllerTests.cs).
    • -
    -

    UserAvatarDTO

    -
    -

    MMCA.ADC.Identity.Shared · MMCA.ADC.Identity.Shared.Users · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/UserAvatarDTO.cs:6 · Level 0 · record (sealed)

    -
    -
      -
    • What it is: the one-field response body every avatar endpoint returns: the current public avatar URL, or null when the user has none (BR-116a).
    • -
    • Depends on: nothing first-party; one BCL attribute (System.Diagnostics.CodeAnalysis.SuppressMessage).
    • -
    • Concept reinforced, the response DTO as a stable wire shape. [Rubric §9, API and Contract Design] (assesses whether endpoints return a named, versionable shape rather than a bare primitive). Returning { "avatarUrl": ... } instead of a raw string means a later addition (a thumbnail URL, an upload timestamp) is an additive change, not a breaking one. The CA1054 suppression (UserAvatarDTO.cs:5) carries its own justification in source: this is a serialized DTO field, so the URL stays a string on the wire rather than becoming a Uri.
    • -
    • Walkthrough: a single positional record, public sealed record UserAvatarDTO(string? AvatarUrl) (UserAvatarDTO.cs:6), with the parameter documented at UserAvatarDTO.cs:4. The nullable parameter is the whole contract: "no avatar" is a first-class, non-exceptional state.
    • -
    • Why it's built this way: avatars are managed file storage (ADR-045), so the only thing a client ever needs back from an upload is the new public URL. Handing it back in a named record keeps the read endpoint, the write endpoint, and the client projection on one type.
    • -
    • Where it's used: produced by the avatar read and write use cases (GetUserAvatarHandler, SetUserAvatarHandler) and returned by UsersController's me/avatar endpoints (GET at MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersController.cs:51 returning Ok(result.Value) at :65, POST at :79 returning at :115). The delete endpoint (:119) returns a bare ActionResult with a 204 (:133), because there is no avatar state left to report. Client-side it is deserialized by UserService in GetMyAvatarUrlAsync (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Services/UserService.cs:112-115) and UploadMyAvatarAsync (:139-142), both of which immediately flatten it with dto.Map(value => value.AvatarUrl ?? string.Empty) so the page never handles the envelope, only a Result<string>. The Profile page stores that string in its _avatarUrl field (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Pages/Users/Profile/Profile.razor.cs:74, :173).
    • -
    -

    DisabledAttendeeQueryService

    -
    -

    MMCA.ADC.Identity.Shared · MMCA.ADC.Identity.Shared.Users · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/DisabledAttendeeQueryService.cs:7 · Level 1 · class (internal sealed)

    -
    -
      -
    • What it is: the no-op stand-in for IAttendeeQueryService that gets registered when the Identity module is switched off in a host. It returns an empty attendee list instead of failing DI.
    • -
    • Depends on: IAttendeeQueryService, the UserIdentifierType alias; BCL (Task.FromResult).
    • -
    • Concept introduced, the disabled-module stub (null object). [Rubric §2, Design Patterns] (assesses recognized patterns applied deliberately: this is the Null Object pattern) and [Rubric §7, Microservices Readiness]. The module system lets a host run any subset of modules; a host that disables Identity would otherwise fail to resolve every cross-module Identity interface at startup. IModule.RegisterDisabledStubs closes that hole, and IdentityModule registers exactly this type there (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/IdentityModule.cs:19-20). Crucially the stub lives in Shared, the same assembly as the contract, so a host can reference the stub without pulling in Identity's Application or Domain assemblies: the Notification service's csproj carries a comment saying precisely that (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/MMCA.ADC.Notification.Service.csproj:26-29). The class is internal sealed (DisabledAttendeeQueryService.cs:7), which is why the registration has to happen from inside this assembly's own module descriptor rather than from a host's Program.cs.
    • -
    • Walkthrough: the whole class is one expression-bodied method. GetAttendeeUserIdsAsync returns Task.FromResult<IReadOnlyList<UserIdentifierType>>([]) (DisabledAttendeeQueryService.cs:10-11): a completed task over an empty collection expression, so there is no allocation-heavy work and no async state machine.
    • -
    • Why it's built this way: returning empty rather than throwing keeps "Identity is not in this host" a configuration fact instead of a runtime error. In the extracted topology the stub is also the safety net: AddIdentityAttendeeClient() calls services.Replace(ServiceDescriptor.Scoped<IAttendeeQueryService, AttendeeQueryServiceGrpcAdapter>()) (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Contracts/DependencyInjection.cs:47), and its doc comment (:25-36) records why Replace rather than TryAdd: either the real in-process AttendeeQueryService or this stub may already be in the container, and after the call the resolved interface is the gRPC adapter in both cases (:45-46). If that replacement were ever skipped, a broadcast would reach nobody rather than crash the Notification host. The same pattern is the module convention across the repo (DisabledBookmarkCountService, DisabledSessionBookmarkValidationService, DisabledUserNotificationExportService).
    • -
    • Where it's used: registered by IdentityModule.RegisterDisabledStubs as a singleton (IdentityModule.cs:20); overwritten at startup in the Notification service, whose application-pipeline registration runs s => s.AddIdentityAttendeeClient() as its second step (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:225) with the ordering and Replace rationale spelled out in the block comment above it (:204-208).
    • -
    -

    UserDTO

    -
    -

    MMCA.ADC.Identity.Shared · MMCA.ADC.Identity.Shared.Users · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/UserDTO.cs:8 · Level 1 · record

    -
    -
      -
    • What it is: the general-purpose account DTO: id, email, first/last name, and role. It is the credential-free projection of the User aggregate.
    • -
    • Depends on: IBaseDTO<TIdentifierType> (the framework DTO contract from MMCA.Common.Shared.DTOs, imported at UserDTO.cs:1), the UserIdentifierType alias.
    • -
    • Concept reinforced, the identified DTO (IBaseDTO<TIdentifierType>). [Rubric §9, API and Contract Design] (assesses a consistent, machine-checkable response shape) and [Rubric §11, Security] (assesses that secrets never reach a serialization boundary). Implementing IBaseDTO<TIdentifierType> (UserDTO.cs:8) is what lets this DTO plug into the framework's generic mapper and service abstractions: Id (:11) satisfies the interface. Just as important is the omission: the UserDTOMapper doc comment (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/DTOs/UserDTOMapper.cs:11) records that PasswordHash, PasswordSalt, and RefreshToken are excluded from the projection, so the DTO is the enforced boundary between an aggregate that holds credentials and anything that can be serialized.
    • -
    • Walkthrough: five required init members (UserDTO.cs:11-23), Id (:11), Email (:14, documented as the login credential, BR-200), FirstName (:17), LastName (:20), and Role as a string used for authorization decisions (:23). Because the mapper is a Mapperly source generator, adding a member here changes generated code at build time rather than at runtime (ADR-001, manual and Mapperly mapping).
    • -
    • Why it's built this way: required on every member means the compiler, not a reviewer, catches a mapper that forgets a field, and keeping the type in Shared lets any layer (API, UI, tests) name it without referencing the Domain assembly.
    • -
    • Where it's used: produced by UserDTOMapper, a [Mapper]-attributed sealed partial class (UserDTOMapper.cs:13-15) implementing IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType> over (User, UserDTO, UserIdentifierType). The generator fills in MapToDTO (UserDTOMapper.cs:18); the collection overload is hand-written over it (:21-25), and a private EmailToString helper converts the Email value object to its string form (:28).
    • -
    -

    UserListDTO

    -
    -

    MMCA.ADC.Identity.Shared · MMCA.ADC.Identity.Shared.Users · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/UserListDTO.cs:14 · Level 1 · record (sealed)

    -
    -
      -
    • What it is: the row shape for the organizer user list (BR-51): id, email, first/last name, role, creation date, and lock state, and nothing else.
    • -
    • Depends on: the UserIdentifierType alias; IUserAdminDTO (MMCA.Common.Shared.Auth.Administration, imported at UserListDTO.cs:1); BCL (DateTime).
    • -
    • Concept introduced, the list projection DTO as a privacy boundary, and now also the framework's shared roster row. [Rubric §8, Data Architecture] and [Rubric §30, Compliance, Privacy and Data Governance] for the projection; [Rubric §7, Microservices Readiness] and [Rubric §2, Design Patterns] for the interface. The type's doc comment is explicit that device-specific fields are excluded to protect attendee device privacy: the User aggregate carries DeviceId, DeviceModel, DeviceManufacturer and friends, but an organizer browsing the user grid has no business seeing them. Its remarks (ADR-116) add a second fact on top: this is also the row the framework's shared user-administration list renders, so the type answers IUserAdminDTO by reusing UserId, Email, and Role as-is, without renaming the wire contract, and maps the lock state explicitly off LockedOn (UserListDTO.cs:14). Because the projection is still built inside the query, a .Select(u => new UserListDTO { ... }) translated to SQL after Skip/Take (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/GetUsers/GetUsersHandler.cs:45-57), the excluded columns are never even read from the database, so this is a privacy boundary and a performance win at once ([Rubric §12, Performance and Scalability]).
    • -
    • Walkthrough: seven members (UserListDTO.cs:17-42). UserId (:17), Email (:20), FirstName (:23), LastName (:26), and Role (:29) are required init; CreatedOn is a plain init DateTime (:32). Role is a string, not the UserRole value object: the wire format stays primitive, and the closed-set type is a domain concern. The nullable LockedOn (:39, documented :34-38) is new: when an administrator locked the account, or null while it is usable, carried on the roster so an operator can see which accounts are locked without opening each one; the lock itself is changed through the Admin/Users endpoints. bool IUserAdminDTO.IsLocked => LockedOn is not null; (:42) is an explicit interface implementation, so IsLocked is reachable only through an IUserAdminDTO-typed reference, leaving the record's own public surface unchanged for every existing consumer that names UserListDTO directly.
    • -
    • Why it's built this way: keeping the list DTO separate from UserDTO lets the grid evolve (sortable columns, CreatedOn, now LockedOn) without touching the general-purpose account DTO, and it keeps the list endpoint's payload small enough to page cheaply. Counting, sorting, paging and projecting all happen at the database level in that order (GetUsersHandler.cs:39, :42, :45-56), so the DTO is the only shape that ever leaves SQL Server. Implementing IUserAdminDTO explicitly, rather than adding a second public lock-state property, lets UserListDTO also satisfy the framework's shared roster contract (ADR-116) without growing the type's own public surface: the interface exists so a framework-owned consumer can render either this DTO or UserAdminDTO (which the app-side Admin/Users screens use instead) through the same shape.
    • -
    • Where it's used: produced by GetUsersHandler inside a PagedCollectionResult<T> (GetUsersHandler.cs:62), returned by UsersController's list endpoint (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersController.cs:138-141, :158), and consumed client-side through IUserUIService / UserService (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Services/UserService.cs:27, :64, :71-72) as the MudDataGrid row type on the UserList page, which inherits DataGridListPageBase<TDto> closed over it (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Pages/Users/UserList.razor.cs:17). The grid's filter keys are read back off the DTO with nameof(UserListDTO.Email) and friends (UserList.razor.cs:53-56, :63), so a renamed property breaks at compile time rather than at runtime. Also referenced from UserAdminDTO's own doc comment (UserAdminDTO.cs:5) and consumed from SpeakerDetail/SpeakerUserSearch in the Conference module (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Speakers/SpeakerDetail.razor.cs, SpeakerUserSearch.cs).
    • -

    DependencyInjection

    MMCA.ADC.Identity.Infrastructure · MMCA.ADC.Identity.Infrastructure · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Infrastructure/DependencyInjection.cs:11 · Level 0 · class (static)

    @@ -1516,6 +1479,51 @@

    DependencyInjection

    of this chapter). They all slug to the bare dependencyinjection anchor, which resolves to the first occurrence in the assembled chapter, so cross-references disambiguate by layer in prose. +

    JwtAudience

    +
    +

    MMCA.ADC.Identity.Shared · MMCA.ADC.Identity.Shared.Authorization · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Authorization/JwtAudience.cs:14 · Level 0 · class (static)

    +
    +
      +
    • What it is: the configuration contract for the audience of the access tokens the Identity + service mints (JwtAudience.cs:3-7). It names one configuration key and offers one guard that + turns a missing value into a startup failure, so every service host that validates Identity's + tokens reads the audience the same way.
    • +
    • Depends on: nothing first-party; BCL InvalidOperationException and string only. The file + has no using directives.
    • +
    • Concept, fail fast on missing configuration. The remarks (JwtAudience.cs:8-13) state the + failure this prevents: a host that silently fell back to a hardcoded audience would boot with a + value nobody configured and answer every request with a 401 "that reads like a token problem + instead of a wiring problem." [Rubric §11, Security] assesses token validation correctness: the + audience check is only meaningful if the value is deliberately configured, never defaulted. + [Rubric §13, Observability and Operability] assesses how quickly a misconfiguration surfaces: + throwing at startup with a message that names the key turns a confusing runtime 401 into a + boot-time error that says what to set. [Rubric §7, Microservices Readiness]: the type lives in + the module's Shared project, so each extracted service host consumes one key and one guard + rather than re-declaring them.
    • +
    • Walkthrough: public const string ConfigKey = "Jwt:Audience" (JwtAudience.cs:28), documented + (:27) as Jwt__Audience when supplied as an environment variable. RequireConfigured(string? configuredValue) (:36-40) is an expression-bodied guard: when string.IsNullOrWhiteSpace is + true it throws InvalidOperationException with the message "Jwt:Audience is not configured. Set + it in the service's appsettings.json or as the Jwt__Audience environment variable." (:38-39, + the key interpolated from ConfigKey); otherwise it returns the value unchanged (:40). Blank and + whitespace-only values are rejected exactly like null.
    • +
    • Why it's built this way: taking the already-read value (rather than an IConfiguration) keeps + the guard a pure function with no framework dependency, so the Shared project stays free of + configuration packages and the guard is trivially unit-testable. The value itself lives only in + configuration (each service's appsettings.json and, in Azure, the deployment template, per + :9-10).
    • +
    • Where it's used: the three non-Identity service hosts pass + JwtAudience.RequireConfigured(builder.Configuration[JwtAudience.ConfigKey]) as the audience: + argument of AddForwardedJwtBearer(...): + MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:329 (call at :327-331), + MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:177 and + MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:163. Unit-tested by + MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Shared.Tests/Authorization/JwtAudienceTests.cs: + a configured value is returned as-is (:13-15), and null, "" and " " each throw a + message containing Jwt:Audience (:17-26).
    • +
    • Caveats / not-in-source: the Identity service host (the token issuer) does not reference this + type; how it sources the audience it stamps into minted tokens is Not determinable from source + within this unit.
    • +

    RoleNames

    MMCA.ADC.Identity.Shared · MMCA.ADC.Identity.Shared.Authorization · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Authorization/RoleNames.cs:20 · Level 0 · class (static)

    @@ -1835,7 +1843,7 @@

    UserDataExportSubjectDTO

  • Why it's built this way: ADR-076 split the export handler in two, an orchestration that is identical in ADC and Store (authorize, load, fan out, degrade, envelope) and a projection that names app types and therefore cannot be shared. This record is that projection. Keeping it flat and required-heavy means the compiler catches a snapshot that forgets a field, and keeping it in Shared lets the API, the UI, and the tests name it without referencing the Domain assembly. Together with the erasure path on the User aggregate (ADR-005), it is the read half of the privacy pair: export what we hold, then anonymize it on request.
  • Where it's used: assembled by ExportUserDataHandler in one object initializer (ExportUserDataHandler.cs:48-74) and returned as the object? subject (:76); the shared base then stamps it into the envelope's Subject alongside a FormatVersion of "1.0" and the section list (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ExportUserData/ExportUserDataHandlerBase.cs:61, :110-117). The whole envelope is served by UsersDataExportController, a subclass of the framework's DataExportControllerBase<TQuery> that supplies only the Users route and the ADC query factory (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersDataExportController.cs:26-35).
  • -
  • Caveats / not-in-source: the endpoint that serves this record is feature-gated, not always on. The base carries [Authorize] and [FeatureGate(PrivacyFeatures.DataExport)] at the class level (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:57-58), so GET /Users/{userId}/export (:77) returns 404 in a host that has not enabled PrivacyFeatures.DataExport. Whether any given deployed environment has that flag on is configuration, not code: Not determinable from source which environments currently serve it.
  • +
  • Caveats / not-in-source: the endpoint that serves this record is feature-gated, not always on. The base carries [Authorize] and [FeatureGate(PrivacyFeatures.DataExport)] at the class level (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:59-60), so GET /Users/{userId}/export (:77) returns 404 in a host that has not enabled PrivacyFeatures.DataExport. Whether any given deployed environment has that flag on is configuration, not code: Not determinable from source which environments currently serve it.
  • UserDataExportSubmittedQuestionDTO

    @@ -1848,40 +1856,42 @@

    UserDataExportSubmittedQuestionDTOWalkthrough: three required init members, QuestionId (UserDataExportSubmittedQuestionDTO.cs:10), SessionId (:13), and CreatedOn in UTC (:16).
  • Where it's used: carried in UserDataExportEngagementSectionDTO's SubmittedQuestions (UserDataExportEngagementSectionDTO.cs:17), populated by EngagementUserDataExportSection (EngagementUserDataExportSection.cs:41-46); the source aggregate is SessionQuestion in the Engagement live layer.
  • -

    IdentityRoutePaths

    +

    IAttendeeQueryService

    -

    MMCA.ADC.Identity.UI · MMCA.ADC.Identity.UI · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/IdentityRoutePaths.cs:6 · Level 0 · class (static)

    +

    MMCA.ADC.Identity.Shared · MMCA.ADC.Identity.Shared.Users · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/IAttendeeQueryService.cs:11 · Level 0 · interface

      -
    • What it is: the route strings and route-builder helpers the Identity UI module owns: /users, /roles, /profile, /confirm-email, plus two parameterized builders for a specific user's detail page and a specific role's edit page (ADR-116).
    • -
    • Depends on: UserIdentifierType (the Identity identifier alias, UserDetail's parameter); externals: BCL System.Globalization.CultureInfo (UserDetail's culture-invariant interpolation) and System.Uri (RoleEdit's Uri.EscapeDataString).
    • -
    • Concept reinforced, route constants as the module's public navigation surface. [Rubric §25, Navigation and Information Architecture] (assesses whether routes are declared in one place so menu entries, redirects, and tests cannot drift from the pages themselves) and [Rubric §15, Best Practices & Code Quality]. The nav items in IdentityUIModule reference IdentityRoutePaths.Profile, .Users and .Roles (IdentityUIModule.cs:17-27) rather than repeating the strings, so renaming a route is a one-line change here.
    • -
    • Walkthrough: four public static readonly string fields on a public static class (:6): Users = "/users" (:8), Profile = "/profile" (:9), Roles = "/roles" (:13, ADR-116 role-administration roster) and ConfirmEmail = "/confirm-email" (:16, the anonymous landing page for the emailed confirmation link). Two parameterized builders follow: UserDetail(UserIdentifierType userId) (:20-21), which culture-invariantly interpolates {Users}/{userId}, and RoleEdit(string role) (:29-30), which interpolates {Roles}/{Uri.EscapeDataString(role ?? string.Empty)}. The doc comment on RoleEdit (:23-27) explains why the role name IS the key: a role has no surrogate id anywhere in the system, so the registry, the grant table and the token all name it by its string, and the value is escaped because a role name can contain characters a raw URL segment cannot.
    • -
    • Caveats / not-in-source: the @page directives on UserList, Profile, RoleList and RoleEdit still spell their route literally, because a Razor @page directive requires a compile-time const, and these are static readonly fields, not const. So this type is the single source of truth for navigation and for building a link to a specific detail, not for the page routing attribute itself.
    • -
    • Where it's used: IdentityUIModule's NavItems, the Roles tests (MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.UI.Tests/Pages/Roles/RoleListTests.cs, RoleEditTests.cs), and UserDetail's own page (UserDetail.razor.cs).
    • +
    • What it is: the one cross-module read contract Identity publishes. It answers a single question, "which user ids are active attendees?", so the Notification module can fan a broadcast out to every attendee without knowing anything about the User aggregate.
    • +
    • Depends on: MMCA.Common.Shared.Abstractions for the ServiceContractAttribute marker (IAttendeeQueryService.cs:1), the UserIdentifierType alias, and BCL (Task, IReadOnlyList<T>, CancellationToken). Note what is not imported: no Identity Domain, Application, or Infrastructure namespace appears anywhere in the file.
    • +
    • Concept introduced, the cross-module contract in the Shared assembly. [Rubric §7, Microservices Readiness] (assesses whether modules talk through narrow, transport-agnostic interfaces that survive extraction into separate processes) and [Rubric §3, Clean Architecture] (assesses dependency direction: the consumer depends on an abstraction, not on the producer's internals). The doc comment (IAttendeeQueryService.cs:5-9) states the rule outright: the contract lives in Shared so the Notification module can call it without depending on the Identity implementation, preserving module boundary isolation. That one placement decision is what makes three different wirings interchangeable behind the same interface: the in-process AttendeeQueryService when Identity runs in the same host, the DisabledAttendeeQueryService stub when the module is switched off, and the AttendeeQueryServiceGrpcAdapter when Identity runs as its own service. No consumer code changes between those three.
    • +
    • Concept reinforced, the [ServiceContract] marker as a machine-checked boundary. The attribute at IAttendeeQueryService.cs:10 is not decoration and it is not System.ServiceModel: it is the framework's own marker introduced in G13, whose doc records that a dedicated ServiceContractPurityTestsBase fitness rule scans every mapped assembly for types carrying it and fails the build if such a type depends on the producing service's Domain, Application, or Infrastructure layers (MMCA.Common/Source/Core/MMCA.Common.Shared/Abstractions/ServiceContractAttribute.cs:3-19). Marking this interface therefore converts "we intend to keep this extractable" from a comment into a test ([Rubric §14, Testability], [Rubric §34, Architecture Governance and Documentation]). The attribute also carries a Version that defaults to "v1" (ServiceContractAttribute.cs:37); this interface uses the parameterless constructor, so it is implicitly v1.
    • +
    • Walkthrough: one member. GetAttendeeUserIdsAsync(CancellationToken cancellationToken = default) (IAttendeeQueryService.cs:18) returns Task<IReadOnlyList<UserIdentifierType>>, documented as the identifiers of all active (non-deleted) users with the Attendee role (IAttendeeQueryService.cs:13-17). The return type is deliberately just ids, not user records: the caller needs recipients, not personal data, so the contract carries the minimum ([Rubric §30, Compliance, Privacy and Data Governance], data minimization across a module boundary).
    • +
    • Why it's built this way: a coarse, id-only, async, cancellable method maps cleanly onto a gRPC unary call, which is exactly the extraction path ADR-007 describes, and the [ServiceContract] marker names it as such. Anything richer (a filtered query object, an IQueryable) would leak Identity's persistence model across the boundary and would not survive the process split.
    • +
    • Where it's used: consumed by Notification's AttendeeNotificationRecipientProvider (which bridges it to INotificationRecipientProvider); implemented in-process by AttendeeQueryService, stubbed by DisabledAttendeeQueryService, served over the wire by AttendeesGrpcService, and satisfied remotely by AttendeeQueryServiceGrpcAdapter.
    -

    RoleEdit

    +

    UserAdminDTO

    -

    MMCA.ADC.Identity.UI · MMCA.ADC.Identity.UI.Pages.Roles · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Pages/Roles/RoleEdit.razor.cs:16 · Level 0 · class (partial)

    +

    MMCA.ADC.Identity.Shared · MMCA.ADC.Identity.Shared.Users · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/UserAdminDTO.cs:23 · Level 0 · record (sealed)

      -
    • What it is: the code-behind for the role permission-editor page: one [Parameter], the role name bound from the route.
    • -
    • Depends on: nothing beyond ComponentBase and the [Parameter] attribute (ASP.NET Core Components).
    • -
    • Concept: a minimal route-bound component parameter, the same shape used across the UI layer wherever a page's identity comes entirely from its URL segment; see IdentityRoutePaths.RoleEdit(role), the builder that constructs the link this page receives Role from.
    • -
    • Walkthrough: public string Role { get; set; } = string.Empty (RoleEdit.razor.cs:19-20), the role being edited, defaulted to empty and overwritten by route binding before first render.
    • -
    • Why it's built this way: a role has no surrogate id, so the route parameter is the role's own name; the page's Razor markup (not shown here) renders the permission editor against that name.
    • -
    • Where it's used: reached from IdentityRoutePaths.RoleEdit(role), which RoleList links to per row. Exercised by MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.UI.Tests/Pages/Roles/RoleEditTests.cs and cross-checked for authorization by MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.UI.Tests/IdentityRouteAuthorizationTests.cs.
    • +
    • What it is: the administration-facing view of a user account, served behind the users:manage capability: id, email, name, role, creation date, lock state, and the two account-nature flags an operator needs to act on the account: whether it signs in externally and whether the email is confirmed.
    • +
    • Depends on: the UserIdentifierType alias; BCL (DateTime).
    • +
    • Concept introduced, the permission-scoped sibling DTO. [Rubric §8, Data Architecture] (assesses whether reads project only the columns a screen needs) and [Rubric §11, Security] (assesses that secrets never reach a serialization boundary). The type's remarks (UserAdminDTO.cs:8-13) states directly why this is not more fields bolted onto UserListDTO: the two DTOs are reached through different permissions, identity:users:read serves the organizer's roster, users:manage serves the administration screens. Device metadata stays out of both (attendee device privacy), and no credential material is ever projected here either, the same restraint UserDataExportSubjectDTO documents for the export path.
    • +
    • Walkthrough: nine positional parameters (UserAdminDTO.cs:23-32), each documented at :14-22: UserId, Email, FirstName, LastName, Role, CreatedOn, a nullable LockedOn, IsExternalLogin, and IsEmailConfirmed (ADR-116). Two computed members read off those without adding wire fields: IsLocked => LockedOn is not null (:35) and FullName => $"{FirstName} {LastName}" (:38).
    • +
    • Why it's built this way: this is the app-owned TUserDto closing the framework's generic administration slice. UserAdministrationService implements IUserAdministrationService<UserAdminDTO> (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/Administration/UserAdministrationService.cs:43) and projects it directly inside the paged EF query, .Select(u => new UserAdminDTO(...)) after OrderBy/Skip/Take (UserAdministrationService.cs:72-86), so the excluded columns never leave the database: the same projection-as-privacy-boundary pattern UserListDTO uses for its own row. UsersAdminController closes the framework's generic controller over it, UsersAdminControllerBase<UserAdminDTO> (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersAdminController.cs:25-26), and both DI registrations name the closed type explicitly: services.TryAddScoped<IUserAdministrationService<UserAdminDTO>, UserAdministrationService>() on the Application side (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:42) and services.AddUserAdministrationUI<UserAdminDTO>() on the UI side (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/DependencyInjection.cs:31).
    • +
    • Where it's used: produced by UserAdministrationService.ListAsync/GetAsync (UserAdministrationService.cs:53-108) and served through UsersAdminController's Admin/Users endpoints; consumed client-side as IUserAdminUIService<UserAdminDTO> on the UserDetail page (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Pages/Users/UserDetail/UserDetail.razor.cs:32, :42), and covered by unit and controller tests (MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.UI.Tests/Pages/Users/UserDetail/UserDetailTests.cs, MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/Administration/UserAdministrationServiceTests.cs, MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.API.Tests/Controllers/UsersAdminControllerTests.cs).
    -

    RoleList

    +

    UserAvatarDTO

    -

    MMCA.ADC.Identity.UI · MMCA.ADC.Identity.UI.Pages.Roles · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Pages/Roles/RoleList.razor.cs:27 · Level 0 · class (partial)

    +

    MMCA.ADC.Identity.Shared · MMCA.ADC.Identity.Shared.Users · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/UserAvatarDTO.cs:6 · Level 0 · record (sealed)

      -
    • What it is: the code-behind for the role-administration roster page (ADR-116); the class declaration carries no members of its own (public partial class RoleList : ComponentBase;, :27), so all behavior lives in the paired Razor markup.
    • -
    • Depends on: ComponentBase (ASP.NET Core Components) only, at this span.
    • -
    • Concept: a bare partial declaration is the minimum a routable Razor component needs when the markup file owns all state and logic; contrast with RoleEdit, whose code-behind at least carries a route parameter.
    • -
    • Why it's built this way: the roster page has no route parameters of its own (unlike the per-role editor), so there is nothing for the code-behind to declare; it exists only to anchor the @page "/roles" directive and the partial split from the .razor markup.
    • -
    • Where it's used: reached via IdentityRoutePaths.Roles, linked from IdentityUIModule's "Roles" nav item. Exercised by MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.UI.Tests/Pages/Roles/RoleListTests.cs and IdentityRouteAuthorizationTests.cs.
    • +
    • What it is: the one-field response body every avatar endpoint returns: the current public avatar URL, or null when the user has none (BR-116a).
    • +
    • Depends on: nothing first-party; one BCL attribute (System.Diagnostics.CodeAnalysis.SuppressMessage).
    • +
    • Concept reinforced, the response DTO as a stable wire shape. [Rubric §9, API and Contract Design] (assesses whether endpoints return a named, versionable shape rather than a bare primitive). Returning { "avatarUrl": ... } instead of a raw string means a later addition (a thumbnail URL, an upload timestamp) is an additive change, not a breaking one. The CA1054 suppression (UserAvatarDTO.cs:5) carries its own justification in source: this is a serialized DTO field, so the URL stays a string on the wire rather than becoming a Uri.
    • +
    • Walkthrough: a single positional record, public sealed record UserAvatarDTO(string? AvatarUrl) (UserAvatarDTO.cs:6), with the parameter documented at UserAvatarDTO.cs:4. The nullable parameter is the whole contract: "no avatar" is a first-class, non-exceptional state.
    • +
    • Why it's built this way: avatars are managed file storage (ADR-045), so the only thing a client ever needs back from an upload is the new public URL. Handing it back in a named record keeps the read endpoint, the write endpoint, and the client projection on one type.
    • +
    • Where it's used: produced by the avatar read and write use cases (GetUserAvatarHandler, SetUserAvatarHandler) and returned by UsersController's me/avatar endpoints (GET at MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersController.cs:51 returning Ok(result.Value) at :65, POST at :79 returning at :115). The delete endpoint (:119) returns a bare ActionResult with a 204 (:133), because there is no avatar state left to report. Client-side it is deserialized by UserService in GetMyAvatarUrlAsync (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Services/UserService.cs:112-115) and UploadMyAvatarAsync (:139-142), both of which immediately flatten it with dto.Map(value => value.AvatarUrl ?? string.Empty) so the page never handles the envelope, only a Result<string>. The Profile page stores that string in its _avatarUrl field (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Pages/Users/Profile/Profile.razor.cs:74, :173).

    UserDataExportEngagementSectionDTO

    @@ -1906,86 +1916,76 @@

    UserDataExportNotificationSectionD
  • Walkthrough: one member. IReadOnlyList<UserDataExportNotificationDTO> Notifications { get; init; } = [] (UserDataExportNotificationSectionDTO.cs:13), documented as newest first. The ordering is part of the contract but is produced by the peer, not enforced here.
  • Where it's used: constructed by NotificationUserDataExportSection (NotificationUserDataExportSection.cs:33-44) and returned via UserDataExportSectionResult.Complete(SectionName, data) (:46). It is the second of the two sections ADC registers, and the registration order is the document order: AddUserDataExportSection<EngagementUserDataExportSection>() then AddUserDataExportSection<NotificationUserDataExportSection>() (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:48-49, with the ordering rationale in the comment at :38-41).
  • -

    ConfirmEmailCommand

    +

    DisabledAttendeeQueryService

    -

    MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.UseCases.ConfirmEmail · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ConfirmEmail/ConfirmEmailCommand.cs:11 · Level 1 · record (sealed)

    +

    MMCA.ADC.Identity.Shared · MMCA.ADC.Identity.Shared.Users · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/DisabledAttendeeQueryService.cs:7 · Level 1 · class (internal sealed)

      -
    • What it is: ADC's command wrapper for redeeming an email-confirmation link (ADR-116). A single-member positional record that carries the shared ConfirmEmailRequest wire payload.
    • -
    • Depends on: ConfirmEmailRequest (the shared request DTO), ICommandWithRequest<ConfirmEmailRequest> (the marker that routes it through the shared request-validator convention, the same mechanism ForgotPasswordCommand uses).
    • -
    • Concept: the same anonymous-command shape ForgotPasswordCommand teaches: the caller has no session yet (the link is followed from an email client), so the command carries only the request payload and no authenticated user id.
    • -
    • Walkthrough: public sealed record ConfirmEmailCommand(ConfirmEmailRequest Request) : ICommandWithRequest<ConfirmEmailRequest> (ConfirmEmailCommand.cs:11-12). No body; Request is the whole payload.
    • -
    • Why it's built this way: routing through ICommandWithRequest<TRequest> gets the command an auto-registered IValidator<ConfirmEmailCommand> for free via CommandRequestValidator<TCommand, TRequest>, the same convention ForgotPasswordCommand relies on, so ADC supplies no bespoke validator for this command.
    • -
    • Where it's used: constructed by EmailConfirmationController and handled by ConfirmEmailHandler. Covered by MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.API.Tests/Controllers/EmailConfirmationControllerTests.cs and MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/UseCases/ConfirmEmail/ConfirmEmailHandlerTests.cs, and pinned by CommandValidatorCoverageTests.
    • +
    • What it is: the no-op stand-in for IAttendeeQueryService that gets registered when the Identity module is switched off in a host. It returns an empty attendee list instead of failing DI.
    • +
    • Depends on: IAttendeeQueryService, the UserIdentifierType alias; BCL (Task.FromResult).
    • +
    • Concept introduced, the disabled-module stub (null object). [Rubric §2, Design Patterns] (assesses recognized patterns applied deliberately: this is the Null Object pattern) and [Rubric §7, Microservices Readiness]. The module system lets a host run any subset of modules; a host that disables Identity would otherwise fail to resolve every cross-module Identity interface at startup. IModule.RegisterDisabledStubs closes that hole, and IdentityModule registers exactly this type there (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/IdentityModule.cs:19-20). Crucially the stub lives in Shared, the same assembly as the contract, so a host can reference the stub without pulling in Identity's Application or Domain assemblies: the Notification service's csproj carries a comment saying precisely that (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/MMCA.ADC.Notification.Service.csproj:26-29). The class is internal sealed (DisabledAttendeeQueryService.cs:7), which is why the registration has to happen from inside this assembly's own module descriptor rather than from a host's Program.cs.
    • +
    • Walkthrough: the whole class is one expression-bodied method. GetAttendeeUserIdsAsync returns Task.FromResult<IReadOnlyList<UserIdentifierType>>([]) (DisabledAttendeeQueryService.cs:10-11): a completed task over an empty collection expression, so there is no allocation-heavy work and no async state machine.
    • +
    • Why it's built this way: returning empty rather than throwing keeps "Identity is not in this host" a configuration fact instead of a runtime error. In the extracted topology the stub is also the safety net: AddIdentityAttendeeClient() calls services.Replace(ServiceDescriptor.Scoped<IAttendeeQueryService, AttendeeQueryServiceGrpcAdapter>()) (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Contracts/DependencyInjection.cs:47), and its doc comment (:25-36) records why Replace rather than TryAdd: either the real in-process AttendeeQueryService or this stub may already be in the container, and after the call the resolved interface is the gRPC adapter in both cases (:45-46). If that replacement were ever skipped, a broadcast would reach nobody rather than crash the Notification host. The same pattern is the module convention across the repo (DisabledBookmarkCountService, DisabledSessionBookmarkValidationService, DisabledUserNotificationExportService).
    • +
    • Where it's used: registered by IdentityModule.RegisterDisabledStubs as a singleton (IdentityModule.cs:20); overwritten at startup in the Notification service, whose application-pipeline registration runs s => s.AddIdentityAttendeeClient() as its second step (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:222) with the ordering and Replace rationale spelled out in the block comment above it (:204-208).
    -

    HttpContextExternalLoginEmailVerifier

    +

    UserDTO

    -

    MMCA.ADC.Identity.API · MMCA.ADC.Identity.API.Authentication · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Authentication/HttpContextExternalLoginEmailVerifier.cs:17 · Level 1 · class (sealed)

    +

    MMCA.ADC.Identity.Shared · MMCA.ADC.Identity.Shared.Users · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/UserDTO.cs:8 · Level 1 · record

      -
    • What it is: the API-edge implementation of IExternalLoginEmailVerifier. It answers one question during an in-flight OAuth callback: did the external provider assert that this login's email address is verified?
    • -
    • Depends on: IExternalLoginEmailVerifier (the Application-layer contract it satisfies), ExternalAuthExtensions for the ExternalLogin scheme name; externals: ASP.NET Core (IHttpContextAccessor, HttpContext.AuthenticateAsync, ClaimsPrincipal.FindFirst), BCL bool.TryParse.
    • -
    • Concept introduced, the fail-closed security probe implemented where the evidence lives. [Rubric §11, Security] (assesses whether an authorization-relevant decision is made on evidence the caller does not control, and what happens when the evidence is missing) and [Rubric §3, Clean Architecture] (assesses whether the direction of dependency runs inward: the interface is declared in the Application layer at MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/IExternalLoginEmailVerifier.cs:11, and only this implementation knows about HTTP). The attack it exists to stop is spelled out both on the interface (IExternalLoginEmailVerifier.cs:6-9) and at the call site (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:221-226): auto-linking an external identity to an existing local account on nothing but an email match hands an attacker that account if the provider will issue tokens for an address it never confirmed. The verifier is therefore consulted before the link, and every uncertain answer is false.
    • -
    • Walkthrough: a primary-constructor class taking one dependency (HttpContextExternalLoginEmailVerifier.cs:17-18) and exposing one method.
        -
      • EmailVerifiedClaimType (HttpContextExternalLoginEmailVerifier.cs:21), the internal const string "email_verified". internal rather than private so the claim name is nameable from the test assembly instead of being re-typed as a literal.
      • -
      • IsCurrentExternalLoginEmailVerifiedAsync() (HttpContextExternalLoginEmailVerifier.cs:24-36). It reads httpContextAccessor.HttpContext and returns false when there is none (:26-30), so a call outside a request reports unverified instead of throwing.
      • -
      • It then re-authenticates the short-lived ExternalLogin cookie (:32, ExternalAuthExtensions.ExternalLoginScheme, the constant at MMCA.Common/Source/Presentation/MMCA.Common.API/Authentication/ExternalAuthExtensions.cs:28). That is the same principal OAuthControllerBase.CompleteAsync just authenticated (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/OAuthControllerBase.cs:103), so the claim is read from the provider's own freshly minted principal, never from anything a client supplied.
      • -
      • The tail collapses three failure modes into one expression (:33-35): a null principal, a missing claim, or a value that is not a parseable true all yield false, via authenticateResult.Principal?.FindFirst(...)?.Value plus bool.TryParse(claimValue, out var verified) && verified.
      • -
      -
    • -
    • Why it's built this way: the claim only exists because the Identity service host maps it. services.PostConfigure<GoogleOptions>(...) maps both the v3 email_verified key and the legacy v2 verified_email key onto the single email_verified claim (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:205-209), and a second PostConfigure does the same for Apple, whose id_token asserts email_verified for relays and verified addresses alike (Program.cs:214-215). The comment above the Google block records the consequence (Program.cs:199-204): GitHub's OAuth user payload asserts nothing, so a GitHub login reads as unverified by design and the link-by-email path is refused for it. Registration is services.TryAddScoped<IExternalLoginEmailVerifier, HttpContextExternalLoginEmailVerifier>() immediately after AddHttpContextAccessor() in AddModuleIdentityAPI (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/DependencyInjection.cs:55-56), with the rationale for placing it in the API layer in the comment above (DependencyInjection.cs:51-54).
    • -
    • Where it's used: injected into AuthenticationService (AuthenticationService.cs:53) and called from the external-login path when an account with that email already exists (AuthenticationService.cs:227-228). An unverified answer returns Error.Unauthorized with the code Auth.ExternalEmailNotVerified and a message steering the user back to the local credential flow (AuthenticationService.cs:230-236); only a verified answer reaches existingUser.LinkExternalProvider(...) (AuthenticationService.cs:367). The OAuthController endpoints are the surface this runs behind.
    • -
    • Caveats / not-in-source: which providers are registered, their scopes, and the callback URLs live in the Identity service host, not here. This type only reads a claim someone else mapped.
    • +
    • What it is: the general-purpose account DTO: id, email, first/last name, and role. It is the credential-free projection of the User aggregate.
    • +
    • Depends on: IBaseDTO<TIdentifierType> (the framework DTO contract from MMCA.Common.Shared.DTOs, imported at UserDTO.cs:1), the UserIdentifierType alias.
    • +
    • Concept reinforced, the identified DTO (IBaseDTO<TIdentifierType>). [Rubric §9, API and Contract Design] (assesses a consistent, machine-checkable response shape) and [Rubric §11, Security] (assesses that secrets never reach a serialization boundary). Implementing IBaseDTO<TIdentifierType> (UserDTO.cs:8) is what lets this DTO plug into the framework's generic mapper and service abstractions: Id (:11) satisfies the interface. Just as important is the omission: the UserDTOMapper doc comment (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/DTOs/UserDTOMapper.cs:11) records that PasswordHash, PasswordSalt, and RefreshToken are excluded from the projection, so the DTO is the enforced boundary between an aggregate that holds credentials and anything that can be serialized.
    • +
    • Walkthrough: five required init members (UserDTO.cs:11-23), Id (:11), Email (:14, documented as the login credential, BR-200), FirstName (:17), LastName (:20), and Role as a string used for authorization decisions (:23). Because the mapper is a Mapperly source generator, adding a member here changes generated code at build time rather than at runtime (ADR-001, manual and Mapperly mapping).
    • +
    • Why it's built this way: required on every member means the compiler, not a reviewer, catches a mapper that forgets a field, and keeping the type in Shared lets any layer (API, UI, tests) name it without referencing the Domain assembly.
    • +
    • Where it's used: produced by UserDTOMapper, a [Mapper]-attributed sealed partial class (UserDTOMapper.cs:13-15) implementing IEntityDTOMapper<TEntity, TEntityDTO, TIdentifierType> over (User, UserDTO, UserIdentifierType). The generator fills in MapToDTO (UserDTOMapper.cs:18); the collection overload is hand-written over it (:21-25), and a private EmailToString helper converts the Email value object to its string form (:28).
    -

    IdentityUIModule

    +

    UserListDTO

    -

    MMCA.ADC.Identity.UI · MMCA.ADC.Identity.UI · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/IdentityUIModule.cs:15 · Level 3 · class (sealed)

    +

    MMCA.ADC.Identity.Shared · MMCA.ADC.Identity.Shared.Users · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/UserListDTO.cs:14 · Level 1 · record (sealed)

      -
    • What it is: the Identity module's UI descriptor. It tells the shared Blazor shell two things: which navigation entries this module contributes, and which assembly to scan for routable components.
    • -
    • Depends on: IUIModule (the contract, IdentityUIModule.cs:5, :13), NavItem and NavSection from MMCA.Common.UI.Common (:3), IdentityRoutePaths, RoleNames and AdministrationPermissions from MMCA.Common.Shared.Auth (:2); externals: MudBlazor Icons.Material.Filled (:5), BCL System.Reflection.Assembly (:1).
    • -
    • Concept reinforced, the pluggable UI module descriptor. [Rubric §18, UI Architecture] (assesses whether the shell discovers features rather than hard-coding them) and [Rubric §25, Navigation and Information Architecture] (assesses that menu structure, role gating, and routes are declared next to the feature that owns them). The IUIModule contract is introduced in G15: a host collects every registered implementation, merges their NavItems into the sidebar, and passes their Assembly to the router's additional assemblies so pages in a Razor Class Library are found (ADR-067). The effect is that adding the Identity module to a host adds its pages and its menu entries in one registration, with no edit to the shell. [Rubric §11, Security] and [Rubric §27, Internationalization] both show up in the three declarations below.
    • -
    • Walkthrough: two members.
        -
      • NavItems (IdentityUIModule.cs:17-33), a collection-expression-initialized IReadOnlyList<NavItem> with three entries. "My Profile" points at IdentityRoutePaths.Profile with a Person icon in NavSection.User and no required role, so every signed-in user sees it (:19). "Users" points at IdentityRoutePaths.Users with a SupervisedUserCircle icon, passes RoleNames.Organizer as the RequiredRole positional argument, and sits in NavSection.Admin (:20), so the link is only rendered for organizers (MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/NavItem.cs:5, :16). Note that this hides the entry: the actual enforcement is server-side on UsersController, whose list endpoint demands IdentityPermissions.UsersRead (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersController.cs:139). Menu gating is UX, not authorization.
      • -
      • The third entry, "Roles" (:22-31), points at IdentityRoutePaths.Roles with an AdminPanelSettings icon, and is gated on RequiredPermission: AdministrationPermissions.ManageRoles instead of RequiredRole. The comment above it (:22-26) states the reason: role administration is the one screen whose whole point is that the role-to-capability map is editable, so an entry gated on a role name would state the thing the screen exists to let an operator change; the sidebar instead reads the permission claim the Identity host mints from its registry.
      • -
      • Every entry passes typeof(IdentityUIModule) as the TitleResource argument, which per the NavItem contract turns "Nav.MyProfile", "Nav.Users" and "Nav.Roles" into resource keys resolved against this type's resources at render time, per circuit, so the menu follows the active culture; a key the resource type does not declare renders as the raw string, which keeps a not-yet-translated entry legible instead of blank (NavItem.cs:13-18, ADR-027).
      • -
      • Assembly => typeof(IdentityUIModule).Assembly (:35): the self-referencing assembly handle used for Blazor route discovery.
      • -
      -
    • -
    • Why it's built this way: a descriptor class is the smallest thing that can carry declarative metadata into DI. Because it is a plain sealed class with no dependencies, it registers as a singleton and costs nothing at runtime, while keeping the sidebar's Identity section owned by the Identity module rather than by the host. Gating the Roles entry on a capability rather than a role name means the entry survives whatever role an operator later reassigns ManageRoles to.
    • -
    • Where it's used: passed as the type argument to the framework's AddUIModule<TModule>() inside DependencyInjection.AddIdentityUI (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/DependencyInjection.cs:24). That one call does both jobs: it is the Scrutor scan root (FromAssemblyOf<TModule>) and the descriptor registration (AddSingleton<IUIModule, TModule>), at MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:276-282.
    • +
    • What it is: the row shape for the organizer user list (BR-51): id, email, first/last name, role, creation date, and lock state, and nothing else.
    • +
    • Depends on: the UserIdentifierType alias; IUserAdminDTO (MMCA.Common.Shared.Auth.Administration, imported at UserListDTO.cs:1); BCL (DateTime).
    • +
    • Concept introduced, the list projection DTO as a privacy boundary, and now also the framework's shared roster row. [Rubric §8, Data Architecture] and [Rubric §30, Compliance, Privacy and Data Governance] for the projection; [Rubric §7, Microservices Readiness] and [Rubric §2, Design Patterns] for the interface. The type's doc comment is explicit that device-specific fields are excluded to protect attendee device privacy: the User aggregate carries DeviceId, DeviceModel, DeviceManufacturer and friends, but an organizer browsing the user grid has no business seeing them. Its remarks (ADR-116) add a second fact on top: this is also the row the framework's shared user-administration list renders, so the type answers IUserAdminDTO by reusing UserId, Email, and Role as-is, without renaming the wire contract, and maps the lock state explicitly off LockedOn (UserListDTO.cs:14). Because the projection is still built inside the query, a .Select(u => new UserListDTO { ... }) translated to SQL after Skip/Take (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/GetUsers/GetUsersHandler.cs:45-57), the excluded columns are never even read from the database, so this is a privacy boundary and a performance win at once ([Rubric §12, Performance and Scalability]).
    • +
    • Walkthrough: seven members (UserListDTO.cs:17-42). UserId (:17), Email (:20), FirstName (:23), LastName (:26), and Role (:29) are required init; CreatedOn is a plain init DateTime (:32). Role is a string, not the UserRole value object: the wire format stays primitive, and the closed-set type is a domain concern. The nullable LockedOn (:39, documented :34-38) is new: when an administrator locked the account, or null while it is usable, carried on the roster so an operator can see which accounts are locked without opening each one; the lock itself is changed through the Admin/Users endpoints. bool IUserAdminDTO.IsLocked => LockedOn is not null; (:42) is an explicit interface implementation, so IsLocked is reachable only through an IUserAdminDTO-typed reference, leaving the record's own public surface unchanged for every existing consumer that names UserListDTO directly.
    • +
    • Why it's built this way: keeping the list DTO separate from UserDTO lets the grid evolve (sortable columns, CreatedOn, now LockedOn) without touching the general-purpose account DTO, and it keeps the list endpoint's payload small enough to page cheaply. Counting, sorting, paging and projecting all happen at the database level in that order (GetUsersHandler.cs:39, :42, :45-56), so the DTO is the only shape that ever leaves SQL Server. Implementing IUserAdminDTO explicitly, rather than adding a second public lock-state property, lets UserListDTO also satisfy the framework's shared roster contract (ADR-116) without growing the type's own public surface: the interface exists so a framework-owned consumer can render either this DTO or UserAdminDTO (which the app-side Admin/Users screens use instead) through the same shape.
    • +
    • Where it's used: produced by GetUsersHandler inside a PagedCollectionResult<T> (GetUsersHandler.cs:62), returned by UsersController's list endpoint (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersController.cs:138-141, :158), and consumed client-side through IUserUIService / UserService (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Services/UserService.cs:27, :64, :71-72) as the MudDataGrid row type on the UserList page, which inherits DataGridListPageBase<TDto> closed over it (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Pages/Users/UserList.razor.cs:17). The grid's filter keys are read back off the DTO with nameof(UserListDTO.Email) and friends (UserList.razor.cs:53-56, :63), so a renamed property breaks at compile time rather than at runtime. Also referenced from UserAdminDTO's own doc comment (UserAdminDTO.cs:5) and consumed from SpeakerDetail/SpeakerUserSearch in the Conference module (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Speakers/SpeakerDetail.razor.cs, SpeakerUserSearch.cs).
    -

    ConfirmEmailHandler

    +

    IdentityRoutePaths

    -

    MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.UseCases.ConfirmEmail · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ConfirmEmail/ConfirmEmailHandler.cs:21 · Level 9 · class (sealed)

    +

    MMCA.ADC.Identity.UI · MMCA.ADC.Identity.UI · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/IdentityRoutePaths.cs:6 · Level 0 · class (static)

      -
    • What it is: ADC's handler for ConfirmEmailCommand (ADR-116). Like ForgotPasswordHandler, it is a thin primary-constructor subclass with no body of its own: every step of the redeem workflow lives in the shared base.
    • -
    • Depends on: ConfirmEmailHandlerBase<TUser, TCommand> (the framework base class, MMCA.Common.Application), User, ConfirmEmailCommand, IUnitOfWork, IEmailConfirmationTokenService; externals: Microsoft.Extensions.Logging.ILogger<T>.
    • -
    • Concept: the same template-method split ForgotPasswordHandler teaches: the framework owns the entire redeem sequence (token lookup, expiry check, marking the user confirmed) and ADC supplies nothing beyond the type arguments and constructor forwarding, because confirming an email needs no app-specific lookup step the way starting a reset does.
    • -
    • Walkthrough: public sealed class ConfirmEmailHandler(IUnitOfWork unitOfWork, IEmailConfirmationTokenService tokenService, ILogger<ConfirmEmailHandler> logger) : ConfirmEmailHandlerBase<User, ConfirmEmailCommand>(unitOfWork, tokenService, logger); (ConfirmEmailHandler.cs:21-25). Three constructor parameters, all forwarded to the base; no members, no overrides visible at this span.
    • -
    • Why it's built this way: the redeem workflow is identical across every app that uses email confirmation, so the base class carries it once and each app only fixes the TUser/TCommand type arguments. This mirrors ForgotPasswordHandler's split, except that here even the one hook ForgotPasswordHandlerBase requires (finding the account by email) is not needed, because the token itself already identifies the user.
    • -
    • Where it's used: registered as ICommandHandler<ConfirmEmailCommand, Result> by the module scan (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:53), and injected into EmailConfirmationController. Covered by MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/UseCases/ConfirmEmail/ConfirmEmailHandlerTests.cs and the framework's own MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Users/EmailConfirmationHandlerBaseTests.cs.
    • -
    • Caveats / not-in-source: the redeem logic itself (token validation, expiry, marking the account confirmed) lives entirely in ConfirmEmailHandlerBase in MMCA.Common and is not visible in this file.
    • +
    • What it is: the route strings and route-builder helpers the Identity UI module owns: /users, /roles, /profile, /confirm-email, plus two parameterized builders for a specific user's detail page and a specific role's edit page (ADR-116).
    • +
    • Depends on: UserIdentifierType (the Identity identifier alias, UserDetail's parameter); externals: BCL System.Globalization.CultureInfo (UserDetail's culture-invariant interpolation) and System.Uri (RoleEdit's Uri.EscapeDataString).
    • +
    • Concept reinforced, route constants as the module's public navigation surface. [Rubric §25, Navigation and Information Architecture] (assesses whether routes are declared in one place so menu entries, redirects, and tests cannot drift from the pages themselves) and [Rubric §15, Best Practices & Code Quality]. The nav items in IdentityUIModule reference IdentityRoutePaths.Profile, .Users and .Roles (IdentityUIModule.cs:17-27) rather than repeating the strings, so renaming a route is a one-line change here.
    • +
    • Walkthrough: four public static readonly string fields on a public static class (:6): Users = "/users" (:8), Profile = "/profile" (:9), Roles = "/roles" (:13, ADR-116 role-administration roster) and ConfirmEmail = "/confirm-email" (:16, the anonymous landing page for the emailed confirmation link). Two parameterized builders follow: UserDetail(UserIdentifierType userId) (:20-21), which culture-invariantly interpolates {Users}/{userId}, and RoleEdit(string role) (:29-30), which interpolates {Roles}/{Uri.EscapeDataString(role ?? string.Empty)}. The doc comment on RoleEdit (:23-27) explains why the role name IS the key: a role has no surrogate id anywhere in the system, so the registry, the grant table and the token all name it by its string, and the value is escaped because a role name can contain characters a raw URL segment cannot.
    • +
    • Caveats / not-in-source: the @page directives on UserList, Profile, RoleList and RoleEdit still spell their route literally, because a Razor @page directive requires a compile-time const, and these are static readonly fields, not const. So this type is the single source of truth for navigation and for building a link to a specific detail, not for the page routing attribute itself.
    • +
    • Where it's used: IdentityUIModule's NavItems, the Roles tests (MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.UI.Tests/Pages/Roles/RoleListTests.cs, RoleEditTests.cs), and UserDetail's own page (UserDetail.razor.cs).
    -

    DependencyInjection

    +

    RoleEdit

    -

    MMCA.ADC.Identity.UI · MMCA.ADC.Identity.UI · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/DependencyInjection.cs:12 · Level 5 · class (static)

    +

    MMCA.ADC.Identity.UI · MMCA.ADC.Identity.UI.Pages.Roles · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Pages/Roles/RoleEdit.razor.cs:16 · Level 0 · class (partial)

      -
    • What it is: the one-call registration entry point for the Identity UI layer. AddIdentityUI() wires the module's entity services, its user-administration and role-administration clients (ADR-116), its bespoke user service, and its IdentityUIModule descriptor into any Blazor host.
    • -
    • Depends on: IdentityUIModule, IUserUIService / UserService from MMCA.ADC.Identity.UI.Services, UserAdminDTO (the DTO the user-administration client closes over), and the framework's AddUIModule<TModule>(), AddUserAdministrationUI<TUserAdminDTO>() and AddRoleAdministrationUI() extensions from MMCA.Common.UI; externals: Microsoft.Extensions.DependencyInjection.IServiceCollection (:1).
    • -
    • Concept reinforced, the extension(IServiceCollection) registration block. [Rubric §15, Best Practices and Code Quality] (assesses idiomatic, current-language composition) and [Rubric §3, Clean Architecture] (assesses that each layer owns its own wiring instead of the host reaching into it). This file is the UI-layer instance of the convention used across all four repos: instead of a classic public static IServiceCollection AddX(this IServiceCollection services), the method lives inside a C# preview extension(IServiceCollection services) block (DependencyInjection.cs:14) and the receiver is named once for the whole block, which the class doc calls out explicitly (:7-10). Callers see an ordinary services.AddIdentityUI().
    • -
    • Walkthrough: AddIdentityUI() (DependencyInjection.cs:20-42) does three things and returns services for chaining (:41).
        -
      • services.AddUIModule<IdentityUIModule>() (:24), the shared two-step prologue every module's Add{Module}UI() opens with, with the intent restated in the comment above it (:22-23). Inside MMCA.Common.UI that single call runs the Scrutor scan over the descriptor's assembly, registering every IEntityService<TEntityDTO, TIdentifierType> implementation AsImplementedInterfaces with a scoped lifetime, then registers the descriptor with AddSingleton<IUIModule, TModule> (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:276-282). Convention over configuration: a new standard CRUD-shaped UI service needs no registration edit here.
      • -
      • services.AddUserAdministrationUI<UserAdminDTO>() (:31), the framework's client for the shared Admin/Users endpoints, gated server-side on users:manage. The comment above it (:26-30) is explicit that this is closed over ADC's own UserAdminDTO, which the user detail page reads and the shared user-administration list acts through, and that it is a different resource under a different permission from the "Users" roster nav entry, not more methods on that roster's client.
      • -
      • services.AddRoleAdministrationUI() (:36), the framework's client for the Admin/Roles endpoints, gated server-side on roles:manage. The comment above it (:33-35) explains it is non-generic, unlike the user-administration client, because a role is a string and a permission is a string, so there is no ADC DTO to close over.
      • -
      • An explicit services.AddScoped<IUserUIService, UserService>() (:39). The comment above it (:38) explains why this one is hand-written: users are a custom contract, not an IEntityService, so the scan cannot pick it up (the same asymmetry IUserUIService documents, because the Users API returns UserListDTO rather than an IBaseDTO-shaped entity DTO and exposes only list plus delete).
      • +
      • What it is: the code-behind for the role permission-editor page: one [Parameter], the role name bound from the route.
      • +
      • Depends on: nothing beyond ComponentBase and the [Parameter] attribute (ASP.NET Core Components).
      • +
      • Concept: a minimal route-bound component parameter, the same shape used across the UI layer wherever a page's identity comes entirely from its URL segment; see IdentityRoutePaths.RoleEdit(role), the builder that constructs the link this page receives Role from.
      • +
      • Walkthrough: public string Role { get; set; } = string.Empty (RoleEdit.razor.cs:19-20), the role being edited, defaulted to empty and overwritten by route binding before first render.
      • +
      • Why it's built this way: a role has no surrogate id, so the route parameter is the role's own name; the page's Razor markup (not shown here) renders the permission editor against that name.
      • +
      • Where it's used: reached from IdentityRoutePaths.RoleEdit(role), which RoleList links to per row. Exercised by MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.UI.Tests/Pages/Roles/RoleEditTests.cs and cross-checked for authorization by MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.UI.Tests/IdentityRouteAuthorizationTests.cs.
      -
    • -
    • Why it's built this way: one host-facing method per module keeps the three hosts symmetric: they each call AddIdentityUI() and nothing else. The mix of shared scan helpers and generic and non-generic administration clients is deliberate, and the framework doc for AddUIModule<TModule>() (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:261-267) records the ordering rationale: module-specific services are registered by the caller after the prologue, so a module whose service must win over a shared default still controls its own registration order. The email-confirmation client (IEmailConfirmationUIService / EmailConfirmationService, ADR-116) is no longer registered here: DependencyInjection.cs:38-42 (prior revision) dropped it, leaving AddIdentityUI() with three registrations instead of four.
    • -
    • Where it's used: called by all three UI hosts, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:151 (Blazor Server), MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Program.cs:56 (the WebAssembly client), and MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/MauiProgram.cs:128 (MAUI), which is what lets the same Razor Class Library render on web and mobile.
    • -
    • Caveats / not-in-source: the Identity module ships one DependencyInjection class per layer (this UI one plus the API, Application, and Infrastructure copies covered in sibling parts of this chapter). They all slug to the bare dependencyinjection anchor, which resolves to the first occurrence in the assembled chapter, so cross-references disambiguate by layer in prose.
    • +

      RoleList

      +
      +

      MMCA.ADC.Identity.UI · MMCA.ADC.Identity.UI.Pages.Roles · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Pages/Roles/RoleList.razor.cs:27 · Level 0 · class (partial)

      +
      +
        +
      • What it is: the code-behind for the role-administration roster page (ADR-116); the class declaration carries no members of its own (public partial class RoleList : ComponentBase;, :27), so all behavior lives in the paired Razor markup.
      • +
      • Depends on: ComponentBase (ASP.NET Core Components) only, at this span.
      • +
      • Concept: a bare partial declaration is the minimum a routable Razor component needs when the markup file owns all state and logic; contrast with RoleEdit, whose code-behind at least carries a route parameter.
      • +
      • Why it's built this way: the roster page has no route parameters of its own (unlike the per-role editor), so there is nothing for the code-behind to declare; it exists only to anchor the @page "/roles" directive and the partial split from the .razor markup.
      • +
      • Where it's used: reached via IdentityRoutePaths.Roles, linked from IdentityUIModule's "Roles" nav item. Exercised by MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.UI.Tests/Pages/Roles/RoleListTests.cs and IdentityRouteAuthorizationTests.cs.

      ForgotPasswordCommand

      @@ -2053,21 +2053,36 @@

      SetUserAvatarCommand

      UserAvatarDTO carrying the new URL, and pre-screened by SetUserAvatarCommandValidator.
    -

    ChangePasswordRequestValidator

    +

    ConfirmEmailCommand

    -

    MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.Validation · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/Validation/ChangePasswordRequestValidator.cs:11 · Level 1 · class (sealed)

    +

    MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.UseCases.ConfirmEmail · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ConfirmEmail/ConfirmEmailCommand.cs:11 · Level 1 · record (sealed)

      -
    • What it is: the FluentValidation rule set for a password change: the current password must be present, and the new password must clear the shared strength rules.
    • -
    • Depends on: ChangePasswordRequest (the shared request DTO it validates), StrongPasswordRules<T>; externals: FluentValidation (AbstractValidator<T>, RuleFor, Include).
    • -
    • Concept introduced, the request validator that reaches the pipeline indirectly. [Rubric §6, CQRS & Event-Driven] (assesses whether cross-cutting concerns sit in the pipeline rather than inside handlers) and [Rubric §11, Security] (assesses that password strength is enforced at a boundary the caller cannot bypass). Nothing in ADC constructs this class: it is picked up by the convention scan in the Identity Application layer's AddModuleIdentityApplication (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:53, ScanModuleApplicationServices<ClassReference>()) and registered as IValidator<ChangePasswordRequest> by the AddValidatorsFromAssembly call inside that scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:117). The Validating decorator, though, validates the command, not the request. The bridge is the framework's CommandRequestValidator<TCommand, TRequest> (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommandRequestValidator.cs:30), which does RuleFor(c => c.Request).SetValidator(validator) (CommandRequestValidator.cs:39) and is closed over any command implementing ICommandWithRequest<TRequest> by a reflection loop in the same scan (DependencyInjection.ModuleScanning.cs:121-135). ChangePasswordCommand declares that marker (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ChangePassword/ChangePasswordCommand.cs:16), so writing this one small validator is enough to put password rules in front of the handler with no wiring at all.
    • -
    • Walkthrough: one constructor (ChangePasswordRequestValidator.cs:13-19).
        -
      • RuleFor(x => x.CurrentPassword).NotEmpty() with the message "Current password is required." and the error code User.CurrentPassword.Required (ChangePasswordRequestValidator.cs:15-16). The explicit WithErrorCode matters: the API surfaces codes, not English text, so a client can branch on the code and a translator can own the message.
      • -
      • Include(new StrongPasswordRules<ChangePasswordRequest>(x => x.NewPassword)) (ChangePasswordRequestValidator.cs:18). Include folds another rule set into this one against a selected property, so the framework owns "what counts as a strong password" in one place and every app inherits changes to it.
      • +
      • What it is: ADC's command wrapper for redeeming an email-confirmation link (ADR-116). A single-member positional record that carries the shared ConfirmEmailRequest wire payload.
      • +
      • Depends on: ConfirmEmailRequest (the shared request DTO), ICommandWithRequest<ConfirmEmailRequest> (the marker that routes it through the shared request-validator convention, the same mechanism ForgotPasswordCommand uses).
      • +
      • Concept: the same anonymous-command shape ForgotPasswordCommand teaches: the caller has no session yet (the link is followed from an email client), so the command carries only the request payload and no authenticated user id.
      • +
      • Walkthrough: public sealed record ConfirmEmailCommand(ConfirmEmailRequest Request) : ICommandWithRequest<ConfirmEmailRequest> (ConfirmEmailCommand.cs:11-12). No body; Request is the whole payload.
      • +
      • Why it's built this way: routing through ICommandWithRequest<TRequest> gets the command an auto-registered IValidator<ConfirmEmailCommand> for free via CommandRequestValidator<TCommand, TRequest>, the same convention ForgotPasswordCommand relies on, so ADC supplies no bespoke validator for this command.
      • +
      • Where it's used: constructed by EmailConfirmationController and handled by ConfirmEmailHandler. Covered by MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.API.Tests/Controllers/EmailConfirmationControllerTests.cs and MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/UseCases/ConfirmEmail/ConfirmEmailHandlerTests.cs, and pinned by CommandValidatorCoverageTests.
      • +
      +

      HttpContextExternalLoginEmailVerifier

      +
      +

      MMCA.ADC.Identity.API · MMCA.ADC.Identity.API.Authentication · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Authentication/HttpContextExternalLoginEmailVerifier.cs:17 · Level 1 · class (sealed)

      +
      +
        +
      • What it is: the API-edge implementation of IExternalLoginEmailVerifier. It answers one question during an in-flight OAuth callback: did the external provider assert that this login's email address is verified?
      • +
      • Depends on: IExternalLoginEmailVerifier (the Application-layer contract it satisfies), ExternalAuthExtensions for the ExternalLogin scheme name; externals: ASP.NET Core (IHttpContextAccessor, HttpContext.AuthenticateAsync, ClaimsPrincipal.FindFirst), BCL bool.TryParse.
      • +
      • Concept introduced, the fail-closed security probe implemented where the evidence lives. [Rubric §11, Security] (assesses whether an authorization-relevant decision is made on evidence the caller does not control, and what happens when the evidence is missing) and [Rubric §3, Clean Architecture] (assesses whether the direction of dependency runs inward: the interface is declared in the Application layer at MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/IExternalLoginEmailVerifier.cs:11, and only this implementation knows about HTTP). The attack it exists to stop is spelled out both on the interface (IExternalLoginEmailVerifier.cs:6-9) and at the call site (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:217-222): auto-linking an external identity to an existing local account on nothing but an email match hands an attacker that account if the provider will issue tokens for an address it never confirmed. The verifier is therefore consulted before the link, and every uncertain answer is false.
      • +
      • Walkthrough: a primary-constructor class taking one dependency (HttpContextExternalLoginEmailVerifier.cs:17-18) and exposing one method.
          +
        • EmailVerifiedClaimType (HttpContextExternalLoginEmailVerifier.cs:21), the internal const string "email_verified". internal rather than private so the claim name is nameable from the test assembly instead of being re-typed as a literal.
        • +
        • IsCurrentExternalLoginEmailVerifiedAsync() (HttpContextExternalLoginEmailVerifier.cs:24-36). It reads httpContextAccessor.HttpContext and returns false when there is none (:26-30), so a call outside a request reports unverified instead of throwing.
        • +
        • It then re-authenticates the short-lived ExternalLogin cookie (:32, ExternalAuthExtensions.ExternalLoginScheme, the constant at MMCA.Common/Source/Presentation/MMCA.Common.API/Authentication/ExternalAuthExtensions.cs:28). That is the same principal OAuthControllerBase.CompleteAsync just authenticated (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/OAuthControllerBase.cs:103), so the claim is read from the provider's own freshly minted principal, never from anything a client supplied.
        • +
        • The tail collapses three failure modes into one expression (:33-35): a null principal, a missing claim, or a value that is not a parseable true all yield false, via authenticateResult.Principal?.FindFirst(...)?.Value plus bool.TryParse(claimValue, out var verified) && verified.
      • -
      • Why it's built this way: the current password is deliberately only checked for presence here, never for strength. Its correctness is a credential comparison against the stored hash, which lives in ChangePasswordHandler via the framework's ChangePasswordHandlerBase, and an old account may legitimately hold a password that no longer meets today's rules. Applying strength rules to it would lock those users out of the very screen that would fix the problem.
      • -
      • Where it's used: resolved as IValidator<ChangePasswordRequest> by CommandRequestValidator<ChangePasswordCommand, ChangePasswordRequest>, which the Validating decorator runs before ChangePasswordHandler for the password-change endpoint on AuthController.
      • +
      • Why it's built this way: the claim only exists because the Identity service host maps it. services.PostConfigure<GoogleOptions>(...) maps both the v3 email_verified key and the legacy v2 verified_email key onto the single email_verified claim (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:204-208), and a second PostConfigure does the same for Apple, whose id_token asserts email_verified for relays and verified addresses alike (Program.cs:213-214). The comment above the Google block records the consequence (Program.cs:198-203): GitHub's OAuth user payload asserts nothing, so a GitHub login reads as unverified by design and the link-by-email path is refused for it. Registration is services.TryAddScoped<IExternalLoginEmailVerifier, HttpContextExternalLoginEmailVerifier>() immediately after AddHttpContextAccessor() in AddModuleIdentityAPI (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/DependencyInjection.cs:55-56), with the rationale for placing it in the API layer in the comment above (DependencyInjection.cs:51-54).
      • +
      • Where it's used: injected into AuthenticationService (AuthenticationService.cs:54) and called from the external-login path when an account with that email already exists (AuthenticationService.cs:223-224). An unverified answer returns Error.Unauthorized with the code Auth.ExternalEmailNotVerified and a message steering the user back to the local credential flow (AuthenticationService.cs:226-232); only a verified answer reaches existingUser.LinkExternalProvider(...) (AuthenticationService.cs:376). The OAuthController endpoints are the surface this runs behind.
      • +
      • Caveats / not-in-source: which providers are registered, their scopes, and the callback URLs live in the Identity service host, not here. This type only reads a claim someone else mapped.

      SetUserAvatarCommandValidator

      @@ -2139,160 +2154,55 @@

      SetUserAvatarCommandValidator

      .../UsersController.cs:95), so an oversized upload arriving over HTTP is reported with the controller's code, and these codes surface only for a caller that bypasses the endpoint.
    -

    ExportUserDataQuery

    -
    -

    MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.UseCases.ExportUserData · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ExportUserData/ExportUserDataQuery.cs:12 · Level 2 · record (sealed)

    -
    -
      -
    • What it is: the request for a data-subject export (PRIVACY.md §7): which account to export, plus who is asking and with what role.
    • -
    • Depends on: IUserOwnedRequest, the UserIdentifierType alias.
    • -
    • Concept introduced, carrying the caller inside the query. [Rubric §11, Security] (assesses whether authorization decisions are made on trusted, explicit inputs) and [Rubric §5, Vertical Slice] (assesses whether a use case owns its own request shape). Three positional members (ExportUserDataQuery.cs:12-15): UserId, CurrentUserId, and the nullable CurrentUserRole. The handler never reaches for HttpContext; the controller reads the claims once through ICurrentUserService and hands them to the query factory (UsersDataExportController, MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersDataExportController.cs:32-35). That is what makes the whole use case testable without a web host, and it is why the ownership rule can live in the shared base rather than in a controller filter. Implementing IUserOwnedRequest is the load-bearing part: the framework's generic constraint is where TQuery : IUserOwnedRequest (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ExportUserData/ExportUserDataHandlerBase.cs:55), so the shared UserOwnershipRule can read UserId, CurrentUserId, and CurrentUserRole off any app's query record (ExportUserDataHandlerBase.cs:81-86).
    • -
    • Walkthrough: a positional record with an XML doc per parameter and nothing else (ExportUserDataQuery.cs:5-15). What it deliberately does not implement is as informative as what it does: it carries no IQueryCacheable, so the Caching decorator has nothing to act on. The base handler's remarks say why (ExportUserDataHandlerBase.cs:42-45): the document it produces is PII by design and must never be logged or cached.
    • -
    • Why it's built this way: the nullable CurrentUserRole mirrors reality at the edge, where a role claim may simply be absent. The privilege check is therefore written to accept null and answer false rather than to assume a role exists (ExportUserDataHandler.HasExportPrivilege, ExportUserDataHandler.cs:38).
    • -
    • Where it's used: constructed by UsersDataExportController for GET Users/{userId}/export (UsersDataExportController.cs:32-35) and handled by ExportUserDataHandler, which the controller receives as IQueryHandler<ExportUserDataQuery, Result<UserDataExportDTO>> (UsersDataExportController.cs:27).
    • -
    -

    SelfHttpWarmupTask

    -
    -

    MMCA.ADC.Identity.Service · MMCA.ADC.Identity.Service · MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/SelfHttpWarmupTask.cs:23 · Level 2 · class (sealed, internal)

    -
    -
      -
    • What it is: the Identity service's startup warm-up. Once Kestrel is listening, it replays the hot users read against the host's own cleartext endpoint so routing, authentication, and the middleware pipeline are JIT-compiled before the first real request arrives.
    • -
    • Depends on: SelfHttpWarmupTaskBase from MMCA.Common.Aspire.Warmup; externals: ASP.NET Core (IServer, IHostApplicationLifetime, IHostEnvironment), IConfiguration, ILogger<T>.
    • -
    • Concept reinforced, the cold-start warm-up task (ADR-025). [Rubric §12, Performance & Scalability] (assesses whether first-request latency after a scale-out or restart is managed rather than paid by a user) and [Rubric §13, Observability & Operability] (assesses that readiness reflects the instance's real ability to serve). The base class, taught in group-16, discovers the bound address, waits for the host lifetime, and issues the configured GETs; the runner registered by AddWarmupReadiness() keeps /health/ready not-ready until the task has had its chance, so the orchestrator does not route traffic into a cold instance. Failures are logged and fall back to lazy warm-up, so a warm-up problem degrades startup latency rather than blocking the service (SelfHttpWarmupTask.cs:13-16).
    • -
    • Walkthrough: a primary-constructor class that forwards all five dependencies to the base (SelfHttpWarmupTask.cs:23-29) and overrides three members.
        -
      • Paths (SelfHttpWarmupTask.cs:33-36), one entry: users?pageNumber=1&pageSize=10. The comment above it ties the string to reality (SelfHttpWarmupTask.cs:31-32): it is the exact shape UserService.GetPagedAsync builds for the organizer list once the empty filter and sort values are dropped, leaving only the paging pair.
      • -
      • Name => "SelfHttpWarmup" (SelfHttpWarmupTask.cs:39) and WarmupPaths => Paths (SelfHttpWarmupTask.cs:42), the base's two abstractions.
      • -
      • RequireSuccessStatusCode => false (SelfHttpWarmupTask.cs:49), the one genuinely interesting override. UsersController is [Authorize] plus [HasPermission], so an unauthenticated self-request is refused by design and returns 401. The comment (SelfHttpWarmupTask.cs:44-48) makes the argument: the refusal still traverses Kestrel, routing, authentication, and the whole middleware pipeline, which is exactly where the cold-start JIT cost lives, so a 401 is the correct outcome and treating it as a failure would log a spurious warning on every single startup.
      • -
      -
    • -
    • Why it's built this way: the class doc (SelfHttpWarmupTask.cs:6-17) draws the contrast with Conference's task, which warms public read endpoints and therefore also populates the output cache. Identity has no anonymous read to warm, and its host sets a NoCache base output-cache policy anyway (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:165), so this task buys pipeline JIT only and says so rather than pretending to prime a cache it cannot reach.
    • -
    • Where it's used: registered with services.AddWarmupTask<SelfHttpWarmupTask>() in the Identity service host (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:171), where the surrounding comment block (Program.cs:167-170) repeats the rationale for an operator reading the host file.
    • -
    • Caveats / not-in-source: the base type decides how the bound address is discovered and whether the run is skipped under the Testing environment; this file only supplies the paths and the status-code policy.
    • -
    -

    UserDeleted

    -
    -

    MMCA.ADC.Identity.Domain · MMCA.ADC.Identity.Domain.Users.DomainEvents · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/DomainEvents/UserDeleted.cs:10 · Level 2 · record (sealed)

    -
    -
      -
    • What it is: the in-process domain event the User aggregate raises when an account is soft-deleted (BR-56). It carries the user id and nothing else.
    • -
    • Depends on: BaseDomainEvent, the UserIdentifierType alias.
    • -
    • Concept reinforced, the domain event as an internal fact. [Rubric §4, DDD] (assesses whether state changes that other code cares about are expressed as named domain facts instead of inferred from a database write) and [Rubric §6, CQRS & Event-Driven]. The distinction from an integration event is the one to keep straight, and this module makes it unusually concrete: there is a second UserDeleted record, in MMCA.ADC.Identity.Shared.Users.IntegrationEvents, carrying the same fact to other processes. A BaseDomainEvent such as this one is dispatched in-process by the framework's domain-event dispatcher after SaveChangesAsync (deferred until after commit when a transaction is open), while a BaseIntegrationEvent leaves its outbox row unprocessed for the OutboxProcessor to publish to the broker (ADR-003). Same AddDomainEvent call at the aggregate, two very different delivery paths, chosen purely by base type.
    • -
    • Walkthrough: a positional record with one member, public sealed record class UserDeleted(UserIdentifierType UserId) : BaseDomainEvent (UserDeleted.cs:10-12). The id-only payload is deliberate: an in-process subscriber can load whatever else it needs from the same unit of work, and a fat payload would go stale between raise and dispatch. Note the absence of an [EventName] attribute, which the two Shared integration events both carry: a domain event never crosses a wire, so it needs no stable published name.
    • -
    • Why it's built this way: raising the event inside User.Delete (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:448) and only when the base soft-delete actually succeeded (User.cs:445-449) means a second delete on an already-deleted account raises nothing, so subscribers cannot see a duplicate fact. The override does nothing else, and its doc comment explains the omission (User.cs:437-441): outstanding refresh sessions live in the RefreshSessions table and the refresh flow re-fetches the account through the soft-delete query filter, so every session stops working the moment this commits, with no explicit revocation call needed here.
    • -
    • Where it's used: raised by User.Delete (User.cs:443-451) and asserted by the domain tests (MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Domain.Tests/Users/UserInvariantsAndRoleTests.cs:271-281).
    • -
    • Caveats / not-in-source: no IDomainEventHandler<UserDeleted> exists anywhere in ADC source today. The doc comment names cascade cleanup and audit logging as the intended consumers (UserDeleted.cs:6-7), but that is an available extension point, not shipped behavior. The cross-process cleanup that is shipped travels on the Shared integration event of the same name, raised separately by DeleteUserHandler; the two records are not connected in code.
    • -
    -

    UserPasswordChanged

    -
    -

    MMCA.ADC.Identity.Domain · MMCA.ADC.Identity.Domain.Users.DomainEvents · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/DomainEvents/UserPasswordChanged.cs:9 · Level 2 · record (sealed)

    -
    -
      -
    • What it is: the in-process domain event raised when a user's credentials are replaced (BR-204).
    • -
    • Depends on: BaseDomainEvent, the UserIdentifierType alias.
    • -
    • Concept: structurally identical to the domain UserDeleted, and the same domain-event-versus-integration-event distinction applies. What is worth noticing is the omission: the payload is the id only (UserPasswordChanged.cs:9-11), never the new hash or salt. A security-relevant event that carried credential material would turn every future subscriber, and every log line that serialized it, into a leak ([Rubric §11, Security]).
    • -
    • Walkthrough: public sealed record class UserPasswordChanged(UserIdentifierType UserId) : BaseDomainEvent (UserPasswordChanged.cs:9-11).
    • -
    • Where it's used: raised by User.ChangePassword after the two credential invariants pass and the new hash and salt are assigned (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:360-376, with the raise at :307), and asserted by the domain tests (MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Domain.Tests/Users/UserTests.cs:173).
    • -
    • Caveats / not-in-source: like the domain UserDeleted, no handler subscribes to it in ADC source today. Session revocation on a password change is not driven from this event, and the aggregate itself no longer carries a refresh token to revoke: sessions live in their own table (User.cs:438-440).
    • -
    -

    UserDeleted

    -
    -

    MMCA.ADC.Identity.Shared · MMCA.ADC.Identity.Shared.Users.IntegrationEvents · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/IntegrationEvents/UserDeleted.cs:26 · Level 3 · record (sealed)

    -
    -
      -
    • What it is: the cross-service announcement that an account has been erased (UC-21, BR-56). Same name as the Identity domain event above, different assembly, different delivery path, different job.
    • -
    • Depends on: BaseIntegrationEvent, EventNameAttribute, the UserIdentifierType alias.
    • -
    • Concept introduced, the erasure announcement, and why its payload is nearly empty. [Rubric §30, Compliance, Privacy & Data Governance] (assesses whether an erasure reaches every store that holds the subject's personal data) and [Rubric §11, Security]. Two positional members only (UserDeleted.cs:26-29): UserId and DeletedOn. The doc comment gives the reason in one sentence (UserDeleted.cs:17-21): a downstream module reacting to an erasure already stores the scalar user id, and carrying a name or an email here would publish the very personal data the erasure exists to remove, onto a broker that persists messages. An erasure event that leaked PII would be self-defeating.
    • -
    • Concept introduced, the wire name is not the CLR name. [EventName("Identity.UserDeleted.v1")] (UserDeleted.cs:25) pins the string the broker and the outbox use, so the two same-named records can never collide on the wire and a future namespace move or rename cannot break a running consumer. The version suffix is what makes an incompatible payload change expressible as a new name rather than as a silent break (ADR-010).
    • -
    • Walkthrough: the interesting mechanics are not in the record, they are at the raise site. DeleteUserHandler calls user.AddDomainEvent(new UserDeleted(command.UserId, timeProvider.GetUtcNow())) inside OnAfterSoftDeleteAsync, before the erasure is saved (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/DeleteUser/DeleteUserHandler.cs:43-59), and the record's own doc spells out what that buys (UserDeleted.cs:7-10): the outbox row is written by the very SaveChangesAsync that commits the soft-delete and the anonymization, so an account that is erased has always produced exactly one event, and an erasure that rolls back produces none. Publishing after the commit instead would leave a crash window in which the account is gone and the published name is not. The timestamp comes from the injected TimeProvider (DeleteUserHandler.cs:33), not DateTimeOffset.UtcNow, so the handler stays testable.
    • -
    • Why it's built this way: the doc comment (UserDeleted.cs:11-16) is explicit that this record does not replace the in-process domain UserDeleted: Engagement is a different process with its own database and never sees an Identity in-process dispatch. Two records carrying one fact is the honest modelling of a two-process reality, and the base type is the only thing that decides which path a raise takes (ADR-003).
    • -
    • Where it's used: consumed by Engagement's UserDeletedPointsHandler (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/Points/IntegrationEventHandlers/UserDeletedPointsHandler.cs:37-44), which takes the account off the public leaderboard and overwrites the LeaderboardOptIn display name the attendee had published there, the one place Engagement holds a name rather than a scalar id (UserDeletedPointsHandler.cs:13-20). The subscription is wired in the Engagement service host with x.RegisterIntegrationEventConsumer<UserDeleted>() (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:289). That handler is idempotent in both directions and deliberately does not swallow exceptions, so a failed erasure is retried by the outbox and the broker instead of being acked away with a log line (UserDeletedPointsHandler.cs:22-27).
    • -
    -

    NotificationUserDataExportSection

    -
    -

    MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.UseCases.ExportUserData · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ExportUserData/NotificationUserDataExportSection.cs:18 · Level 3 · class (sealed)

    -
    -
      -
    • What it is: the Notifications contribution to a data-subject export: the user's notification inbox rows, fetched from the Notification service and projected into the export's own DTO shape.
    • -
    • Depends on: IUserDataExportSection (the contract), UserDataExportSectionResult, IUserNotificationExportService (the cross-service peer), UserDataExportNotificationSectionDTO, UserDataExportNotificationDTO.
    • -
    • Concept introduced, the export section as a pluggable contributor. [Rubric §30, Compliance, Privacy & Data Governance] (assesses whether a data-subject access request can actually be satisfied across every store that holds the subject's data) and [Rubric §7, Microservices Readiness] (assesses that a module reaches a peer through an interface it could satisfy in-process or over the wire). An access request is only as complete as the list of places that answer it, and in ADC those places are separate processes with separate databases. The framework's answer is a small interface, IUserDataExportSection (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ExportUserData/IUserDataExportSection.cs:20), with a stable SectionName that appears verbatim in the document a subject reads (IUserDataExportSection.cs:27) and one ExportAsync per user (:38). Sections accumulate through AddUserDataExportSection<TSection>(), which registers them scoped and via TryAddEnumerable so a double registration adds one entry (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Extensibility.cs:41-43), and they are exported in registration order (IUserDataExportSection.cs:16-17).
    • -
    • Walkthrough: a primary-constructor class over one dependency (NotificationUserDataExportSection.cs:18-19).
        -
      • SectionName => "Notifications" (NotificationUserDataExportSection.cs:22). Treat it as contract text, not a label: it is the key a subject (or a regulator) reads in the JSON.
      • -
      • ExportAsync (NotificationUserDataExportSection.cs:25-47) awaits GetUserNotificationExportAsync(userId, ...) on the peer (:29-31), then projects each row into a UserDataExportNotificationDTO with NotificationId, Title, SentOn, IsRead, ReadOn, and ScopeKey (:35-43), wrapped in a UserDataExportNotificationSectionDTO (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/DataExport/UserDataExportNotificationSectionDTO.cs:10). ScopeKey is the event-scoping key each notification carries (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/DataExport/UserDataExportNotificationDTO.cs:30), and exporting it is what lets a subject see which event a notification belonged to rather than an undifferentiated list. The re-projection is what keeps the exported wire shape owned by Identity.Shared rather than by whatever the peer happens to return today.
      • -
      • UserDataExportSectionResult.Complete(SectionName, data) (:46) closes it out. A user with an empty inbox produces an empty list and a Complete result, which is a truthful "nothing here" rather than the ambiguous "could not tell" an Unavailable would report (IUserDataExportSection.cs:9-13).
      • -
      -
    • -
    • Why it's built this way: the class doc (NotificationUserDataExportSection.cs:11-15) makes the omission explicit: transport failures are not caught here. Catching them locally would mean every section reinventing the degrade policy; letting them propagate one frame lets the base of ExportUserDataHandler apply one policy to all sections ([Rubric §29, Resilience & Business Continuity]).
    • -
    • Where it's used: registered second, after Engagement, in AddModuleIdentityApplication (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:49), which fixes its position in the document; the peer client is wired in the Identity service host with services.AddNotificationUserExportClient() inside the application-pipeline chain (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:317), resolving to a gRPC adapter outside the Notification process. Covered by MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/UseCases/NotificationUserDataExportSectionTests.cs.
    • -
    -

    UserRegistered

    -
    -

    MMCA.ADC.Identity.Shared · MMCA.ADC.Identity.Shared.Users.IntegrationEvents · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/IntegrationEvents/UserRegistered.cs:36 · Level 3 · record (sealed)

    -
    -
      -
    • What it is: the cross-service announcement that a new account exists. It carries the database-generated user id plus the identity fields another context needs to match on, and it is the event that drives the BR-207 speaker auto-link.
    • -
    • Depends on: BaseIntegrationEvent, EventNameAttribute, the UserIdentifierType alias.
    • -
    • Concept introduced, the integration event as a published contract. [Rubric §6, CQRS & Event-Driven] (assesses whether contexts collaborate through facts rather than commands), [Rubric §7, Microservices Readiness] (assesses that the producer does not know its consumers), and [Rubric §9, API & Contract Design]. Four properties of this record make it a contract rather than an internal message. It lives in Shared, the assembly a consumer may reference without touching Identity's Domain or Application. It carries denormalized primitives (string Email, string FirstName, string LastName, string Role) rather than the Email value object or UserRole, so a subscriber needs no Identity types to deserialize it. It is a record with positional members, so adding an optional member later is an additive change consumers can ignore, which is exactly how EmailVerified arrived. And it declares its wire name explicitly, [EventName("Identity.UserRegistered.v1")] (UserRegistered.cs:35), so the published contract survives any CLR-side rename (ADR-010).
    • -
    • Concept introduced, an additive member that gates a downstream side effect. [Rubric §9, API & Contract Design], [Rubric §11, Security]. The sixth positional member, bool EmailVerified = false (UserRegistered.cs:36-43), is what makes the BR-207 speaker auto-link safe rather than merely convenient: the type's own doc comment says nothing proves ownership of an address typed into the registration form, so a speaker profile may only be handed out when an external provider asserted the address as verified. Because the member is additive with a false default, an outbox row written before it existed, or one still in flight across a deploy, deserializes to the value that links nothing, so the rollout cannot silently grant auto-links it should not.
    • -
    • Walkthrough: six positional members (UserRegistered.cs:36-43): UserId, Email, FirstName, LastName, Role, EmailVerified. Email is the field the auto-link actually matches on; the two name fields exist so a subscriber can report candidates without a call back into Identity; EmailVerified is always false for a local password registration, whose address is unproven, and reflects the external provider's own assertion for an OAuth registration.
    • -
    • Why it's built this way: the ordering problem is the whole story. The id is a database-generated identity column, so the event cannot be raised inside User.Create (the factory remarks say so outright, MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:185-191): the id is still default at that point, and the outbox serializes the payload at capture time, so it would persist UserId = 0, which the cross-service Conference consumer cannot resolve (AuthenticationService.cs:30-37). Instead AuthenticationService wraps the base registration workflow in a single UnitOfWork.ExecuteInTransactionAsync (AuthenticationService.cs:84-91), then in OnUserRegisteredAsync raises the event on the already-persisted aggregate and saves a second time (AuthenticationService.cs:170-175). The first save populates the real id; the second save writes the outbox row; both are inside one transaction, so the user and the event commit atomically. The remarks on that override (AuthenticationService.cs:122-132) name the consequence honestly: this is eventual consistency, and the token handed back to the just-registered user does not yet carry the speaker_id claim. The same raise happens for brand-new external OAuth users, guarded by an isNewUser flag (AuthenticationService.cs:290-300), and it is that path that can populate EmailVerified = true.
    • -
    • Where it's used: consumed by Conference's UserRegisteredHandler, which runs the email-match speaker auto-link and publishes SpeakerLinkedToUser back so Identity can set User.LinkedSpeakerId; Identity registers the consumers for that return event in its own host (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:320-321).
    • -
    • Caveats / not-in-source: the type's own doc comment says the event is "Published by AuthenticationService AFTER the unit-of-work commit" (UserRegistered.cs:7-9), and the User.Create remarks repeat the phrasing (User.cs:186-190); neither still describes the mechanism. Current source raises it via AddDomainEvent on the aggregate after the first save, inside the transaction, so the outbox row commits with the insert (AuthenticationService.cs:137-138). The code is ground truth here; both doc comments predate the atomicity fix described at AuthenticationService.cs:30-40. Whether UserRegisteredHandler currently reads EmailVerified to gate the auto-link, or the flag is carried on the wire ahead of that consumer-side change, is not determinable from this unit's source.
    • -
    -

    EngagementUserDataExportSection

    +

    IdentityUIModule

    -

    MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.UseCases.ExportUserData · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ExportUserData/EngagementUserDataExportSection.cs:19 · Level 4 · class (sealed)

    +

    MMCA.ADC.Identity.UI · MMCA.ADC.Identity.UI · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/IdentityUIModule.cs:15 · Level 3 · class (sealed)

      -
    • What it is: the Engagement contribution to a data-subject export: session bookmarks, submitted session questions, the points ledger, check-in history, and leaderboard participation, read from the Engagement service and projected into the export's DTOs.
    • -
    • Depends on: IUserDataExportSection, UserDataExportSectionResult, IUserEngagementExportService, UserDataExportEngagementSectionDTO and its four row types (UserDataExportBookmarkDTO, UserDataExportSubmittedQuestionDTO, UserDataExportPointsEntryDTO, UserDataExportCheckInDTO).
    • -
    • Concept reinforced, the export section (introduced at NotificationUserDataExportSection), here at its widest. [Rubric §30, Compliance, Privacy & Data Governance] and [Rubric §9, API & Contract Design]. Everything structural is identical to its sibling: a primary constructor over one peer interface, a constant SectionName (EngagementUserDataExportSection.cs:23), one ExportAsync, and no local try/catch (EngagementUserDataExportSection.cs:12-16). What differs is breadth, five collections instead of one, which is what makes its two projection comments worth reading.
    • -
    • Walkthrough: ExportAsync (EngagementUserDataExportSection.cs:26-71) makes a single peer call (:30-32) and then assembles one section DTO (:34-68).
        -
      • Bookmarks (:36-40) and SubmittedQuestions (:41-46) project scalar ids plus CreatedOn. No session or question titles are pulled across: Engagement stores ids, and the export is truthful about what Engagement actually holds.
      • -
      • PointsEntries (:47-55) converts ActivityType with .ToString(), and the comment says why (:49-50): the export document is read by the data subject, so an activity travels as its readable name rather than an enum number. That is a real API-design decision, because it means renaming an enum member changes an externally visible document. The row also carries Points and SubjectKey (:52-53), the scalar ledger facts.
      • -
      • CheckIns (:56-65) applies the same rule to Scope (:58-60) and carries the nullable EventId, SessionId, and SponsorId alongside CheckedInOn, which is what makes the three check-in scopes distinguishable in the output.
      • -
      • IsOnLeaderboard and LeaderboardDisplayName (:66-67) close the section. Those are the same two facts Engagement's UserDeletedPointsHandler erases on account deletion, which is the neat symmetry of this module: access and erasure operate on the identical set of data.
      • -
      • UserDataExportSectionResult.Complete(SectionName, data) (:70).
      • +
      • What it is: the Identity module's UI descriptor. It tells the shared Blazor shell two things: which navigation entries this module contributes, and which assembly to scan for routable components.
      • +
      • Depends on: IUIModule (the contract, IdentityUIModule.cs:5, :13), NavItem and NavSection from MMCA.Common.UI.Common (:3), IdentityRoutePaths, RoleNames and AdministrationPermissions from MMCA.Common.Shared.Auth (:2); externals: MudBlazor Icons.Material.Filled (:5), BCL System.Reflection.Assembly (:1).
      • +
      • Concept reinforced, the pluggable UI module descriptor. [Rubric §18, UI Architecture] (assesses whether the shell discovers features rather than hard-coding them) and [Rubric §25, Navigation and Information Architecture] (assesses that menu structure, role gating, and routes are declared next to the feature that owns them). The IUIModule contract is introduced in G15: a host collects every registered implementation, merges their NavItems into the sidebar, and passes their Assembly to the router's additional assemblies so pages in a Razor Class Library are found (ADR-067). The effect is that adding the Identity module to a host adds its pages and its menu entries in one registration, with no edit to the shell. [Rubric §11, Security] and [Rubric §27, Internationalization] both show up in the three declarations below.
      • +
      • Walkthrough: two members.
          +
        • NavItems (IdentityUIModule.cs:17-33), a collection-expression-initialized IReadOnlyList<NavItem> with three entries. "My Profile" points at IdentityRoutePaths.Profile with a Person icon in NavSection.User and no required role, so every signed-in user sees it (:19). "Users" points at IdentityRoutePaths.Users with a SupervisedUserCircle icon, passes RoleNames.Organizer as the RequiredRole positional argument, and sits in NavSection.Admin (:20), so the link is only rendered for organizers (MMCA.Common/Source/Presentation/MMCA.Common.UI/Common/NavItem.cs:5, :16). Note that this hides the entry: the actual enforcement is server-side on UsersController, whose list endpoint demands IdentityPermissions.UsersRead (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersController.cs:139). Menu gating is UX, not authorization.
        • +
        • The third entry, "Roles" (:22-31), points at IdentityRoutePaths.Roles with an AdminPanelSettings icon, and is gated on RequiredPermission: AdministrationPermissions.ManageRoles instead of RequiredRole. The comment above it (:22-26) states the reason: role administration is the one screen whose whole point is that the role-to-capability map is editable, so an entry gated on a role name would state the thing the screen exists to let an operator change; the sidebar instead reads the permission claim the Identity host mints from its registry.
        • +
        • Every entry passes typeof(IdentityUIModule) as the TitleResource argument, which per the NavItem contract turns "Nav.MyProfile", "Nav.Users" and "Nav.Roles" into resource keys resolved against this type's resources at render time, per circuit, so the menu follows the active culture; a key the resource type does not declare renders as the raw string, which keeps a not-yet-translated entry legible instead of blank (NavItem.cs:13-18, ADR-027).
        • +
        • Assembly => typeof(IdentityUIModule).Assembly (:35): the self-referencing assembly handle used for Blazor route discovery.
      • -
      • Why it's built this way: registration order is document order, and Engagement is registered first (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:44-49), with the comment above the two calls recording that this is deliberate rather than incidental. Stable ordering means an exported document can be diffed across runs.
      • -
      • Where it's used: the peer client is wired in the Identity service host with services.AddEngagementUserExportClient() as one step of the AddMmcaApplicationPipeline chain (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:314-317), which resolves to a gRPC adapter outside the Engagement process. The host comment states the coupling explicitly (Program.cs:293-300): both export clients are best-effort consumers, so a peer that stays unreachable degrades one section instead of failing the export, and Identity never hard-depends on either peer. Covered by MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/UseCases/EngagementUserDataExportSectionTests.cs.
      • +
      • Why it's built this way: a descriptor class is the smallest thing that can carry declarative metadata into DI. Because it is a plain sealed class with no dependencies, it registers as a singleton and costs nothing at runtime, while keeping the sidebar's Identity section owned by the Identity module rather than by the host. Gating the Roles entry on a capability rather than a role name means the entry survives whatever role an operator later reassigns ManageRoles to.
      • +
      • Where it's used: passed as the type argument to the framework's AddUIModule<TModule>() inside DependencyInjection.AddIdentityUI (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/DependencyInjection.cs:24). That one call does both jobs: it is the Scrutor scan root (FromAssemblyOf<TModule>) and the descriptor registration (AddSingleton<IUIModule, TModule>), at MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:292-298.
      -

      RegisterRequestValidator

      +

      ConfirmEmailHandler

      -

      MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.Validation · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/Validation/RegisterRequestValidator.cs:12 · Level 7 · class (sealed)

      +

      MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.UseCases.ConfirmEmail · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ConfirmEmail/ConfirmEmailHandler.cs:21 · Level 9 · class (sealed)

        -
      • What it is: the rule set for a registration request: a well-formed email within the domain's length limit, a strong password, required first and last names, and an optional address that is validated only when present.
      • -
      • Depends on: RegisterRequest, EmailRules<T>, StrongPasswordRules<T>, RequiredStringRules<T>, AddressValidator, UserInvariants; externals: FluentValidation.
      • -
      • Concept reinforced, composing shared rule sets, and tying them to the domain's constants. [Rubric §6, CQRS & Event-Driven], [Rubric §15, Best Practices & Code Quality]. This validator is almost entirely Include calls: the four reusable rule sets from MMCA.Common.Application.Validation carry the actual logic, and this class only chooses which property each applies to. The detail worth copying is that the length arguments are not literals: UserInvariants.EmailMaxLength, UserInvariants.FirstNameMaxLength, and UserInvariants.LastNameMaxLength (RegisterRequestValidator.cs:16,18,19) come straight from the aggregate's own rules. A request can therefore never be accepted at the edge and then rejected by the domain for a length the API thought was fine.
      • -
      • Walkthrough: one constructor (RegisterRequestValidator.cs:14-24).
          -
        • EmailRules<RegisterRequest>(x => x.Email, "Email", UserInvariants.EmailMaxLength) (RegisterRequestValidator.cs:16): presence, format, and length, with "Email" as the display name in messages.
        • -
        • StrongPasswordRules<RegisterRequest>(x => x.Password) (RegisterRequestValidator.cs:17), the same complexity policy ChangePasswordRequestValidator applies to a new password.
        • -
        • Two RequiredStringRules<RegisterRequest> for the names (RegisterRequestValidator.cs:18-19).
        • -
        • The conditional address rule (RegisterRequestValidator.cs:21-23): RuleFor(x => x.Address).SetValidator(new AddressValidator()!).When(x => x.Address is not null). When is what makes the address genuinely optional: omitting it is valid, supplying a malformed one is not.
        • -
        -
      • -
      • Why it's built this way: registration is anonymous and internet-facing, so it is the most hostile input surface in the module. Composing framework-owned rule sets means a tightening of the shared password policy applies here without a code change ([Rubric §11, Security]).
      • -
      • Where it's used: auto-registered as IValidator<RegisterRequest> by ScanModuleApplicationServices<ClassReference>() (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:53) and injected into AuthenticationValidators, which AuthenticationService receives through its base and runs at the top of the registration workflow. The endpoint is POST auth/register on AuthController.
      • +
      • What it is: ADC's handler for ConfirmEmailCommand (ADR-116). Like ForgotPasswordHandler, it is a thin primary-constructor subclass with no body of its own: every step of the redeem workflow lives in the shared base.
      • +
      • Depends on: ConfirmEmailHandlerBase<TUser, TCommand> (the framework base class, MMCA.Common.Application), User, ConfirmEmailCommand, IUnitOfWork, IEmailConfirmationTokenService; externals: Microsoft.Extensions.Logging.ILogger<T>.
      • +
      • Concept: the same template-method split ForgotPasswordHandler teaches: the framework owns the entire redeem sequence (token lookup, expiry check, marking the user confirmed) and ADC supplies nothing beyond the type arguments and constructor forwarding, because confirming an email needs no app-specific lookup step the way starting a reset does.
      • +
      • Walkthrough: public sealed class ConfirmEmailHandler(IUnitOfWork unitOfWork, IEmailConfirmationTokenService tokenService, ILogger<ConfirmEmailHandler> logger) : ConfirmEmailHandlerBase<User, ConfirmEmailCommand>(unitOfWork, tokenService, logger); (ConfirmEmailHandler.cs:21-25). Three constructor parameters, all forwarded to the base; no members, no overrides visible at this span.
      • +
      • Why it's built this way: the redeem workflow is identical across every app that uses email confirmation, so the base class carries it once and each app only fixes the TUser/TCommand type arguments. This mirrors ForgotPasswordHandler's split, except that here even the one hook ForgotPasswordHandlerBase requires (finding the account by email) is not needed, because the token itself already identifies the user.
      • +
      • Where it's used: registered as ICommandHandler<ConfirmEmailCommand, Result> by the module scan (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:53), and injected into EmailConfirmationController. Covered by MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/UseCases/ConfirmEmail/ConfirmEmailHandlerTests.cs and the framework's own MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Users/EmailConfirmationHandlerBaseTests.cs.
      • +
      • Caveats / not-in-source: the redeem logic itself (token validation, expiry, marking the account confirmed) lives entirely in ConfirmEmailHandlerBase in MMCA.Common and is not visible in this file.
      -

      ExportUserDataHandler

      +

      DependencyInjection

      -

      MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.UseCases.ExportUserData · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ExportUserData/ExportUserDataHandler.cs:30 · Level 9 · class (sealed)

      +

      MMCA.ADC.Identity.UI · MMCA.ADC.Identity.UI · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/DependencyInjection.cs:12 · Level 5 · class (static)

        -
      • What it is: ADC's data-subject export handler. It is a thin subclass of the framework's export workflow that answers exactly two app-specific questions: which role may export somebody else's account, and which of the account's own fields count as portable personal data.
      • -
      • Depends on: ExportUserDataHandlerBase<TUser, TQuery>, User, ExportUserDataQuery, UserRole, UserDataExportSubjectDTO, IUnitOfWork, IUserDataExportSection; externals: TimeProvider, ILogger<T>.
      • -
      • Concept introduced, the template-method handler where the app supplies only its vocabulary. [Rubric §30, Compliance, Privacy & Data Governance] (assesses whether access and portability are implemented once and identically everywhere), [Rubric §2, Design Patterns] (template method), and [Rubric §1, SOLID] (the base is closed for modification and open through three protected hooks). The base (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ExportUserData/ExportUserDataHandlerBase.cs:49) owns the whole workflow: the owner-or-privileged-role check via UserOwnershipRule.CheckOwnership, returning a User.ExportForbidden error (ExportUserDataHandlerBase.cs:80-90), a no-tracking load through GetReadRepository with Error.NotFound when the account is gone (:92-98), the subject snapshot, the section fan-out (:104-108), and the envelope stamped with CurrentFormatVersion "1.0" and a GeneratedOn from TimeProvider (:61, :110-117). Two of those choices carry real weight. The fan-out is sequential on purpose, because sections share the scoped unit of work and its DbContext, which is not thread-safe, and because registration order is the published order of the document (:102-103). And each section runs inside its own try/catch (:173-198) that degrades a failing section to Available = false with a deliberately generic reason, sending the exception detail to the log and never to the subject (:185-197); OperationCanceledException is explicitly excluded, because a cancelled request is not a degraded one.
      • -
      • Walkthrough: a primary constructor forwarding four dependencies to the base (ExportUserDataHandler.cs:30-35) and two overrides.
          -
        • HasExportPrivilege(string? currentUserRole) => UserRole.IsOrganizer(currentUserRole) (ExportUserDataHandler.cs:38). One line, and the case-insensitive comparison lives inside UserRole.IsOrganizer rather than here, so a claim with unexpected casing cannot silently deny an organizer (a test pins the lowercase "organizer" case, MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/UseCases/ExportUserData/ExportUserDataHandlerTests.cs:110).
        • -
        • BuildSubjectSnapshotAsync (ExportUserDataHandler.cs:41-77) guards its argument (:46) and projects the aggregate into a UserDataExportSubjectDTO: identity and profile fields, IsExternalLogin and LoginProvider, LinkedSpeakerId, AvatarUrl, the seven MAUI device fields, and the two audit timestamps (:48-73). What is absent is the point: no PasswordHash, no PasswordSalt, no refresh token, no external ProviderKey. The class doc states the principle (ExportUserDataHandler.cs:16-19): credentials are secrets, not portable personal data, and a portability right is not a right to a copy of your own password hash.
        • -
        • The timestamp handling is the subtle bit (ExportUserDataHandler.cs:67-73). SQL Server hands audit timestamps back as Kind=Unspecified, and the DTO documents them as UTC but serializes them without a Z marker in that state, so DateTime.SpecifyKind(..., DateTimeKind.Utc) only restores the marker on a value that was already UTC. LastModifiedOn is null-checked first, since a never-updated account has none.
        • -
        • The method returns Task.FromResult<object?>(subject) (:76): the hook is asynchronous because some apps read a second aggregate for the snapshot, and ADC does not need to.
        • +
        • What it is: the one-call registration entry point for the Identity UI layer. AddIdentityUI() wires the module's entity services, its user-administration and role-administration clients (ADR-116), its bespoke user service, and its IdentityUIModule descriptor into any Blazor host.
        • +
        • Depends on: IdentityUIModule, IUserUIService / UserService from MMCA.ADC.Identity.UI.Services, UserAdminDTO (the DTO the user-administration client closes over), and the framework's AddUIModule<TModule>(), AddUserAdministrationUI<TUserAdminDTO>() and AddRoleAdministrationUI() extensions from MMCA.Common.UI; externals: Microsoft.Extensions.DependencyInjection.IServiceCollection (:1).
        • +
        • Concept reinforced, the extension(IServiceCollection) registration block. [Rubric §15, Best Practices and Code Quality] (assesses idiomatic, current-language composition) and [Rubric §3, Clean Architecture] (assesses that each layer owns its own wiring instead of the host reaching into it). This file is the UI-layer instance of the convention used across all four repos: instead of a classic public static IServiceCollection AddX(this IServiceCollection services), the method lives inside a C# preview extension(IServiceCollection services) block (DependencyInjection.cs:14) and the receiver is named once for the whole block, which the class doc calls out explicitly (:7-10). Callers see an ordinary services.AddIdentityUI().
        • +
        • Walkthrough: AddIdentityUI() (DependencyInjection.cs:20-42) does three things and returns services for chaining (:41).
            +
          • services.AddUIModule<IdentityUIModule>() (:24), the shared two-step prologue every module's Add{Module}UI() opens with, with the intent restated in the comment above it (:22-23). Inside MMCA.Common.UI that single call runs the Scrutor scan over the descriptor's assembly, registering every IEntityService<TEntityDTO, TIdentifierType> implementation AsImplementedInterfaces with a scoped lifetime, then registers the descriptor with AddSingleton<IUIModule, TModule> (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:292-298). Convention over configuration: a new standard CRUD-shaped UI service needs no registration edit here.
          • +
          • services.AddUserAdministrationUI<UserAdminDTO>() (:31), the framework's client for the shared Admin/Users endpoints, gated server-side on users:manage. The comment above it (:26-30) is explicit that this is closed over ADC's own UserAdminDTO, which the user detail page reads and the shared user-administration list acts through, and that it is a different resource under a different permission from the "Users" roster nav entry, not more methods on that roster's client.
          • +
          • services.AddRoleAdministrationUI() (:36), the framework's client for the Admin/Roles endpoints, gated server-side on roles:manage. The comment above it (:33-35) explains it is non-generic, unlike the user-administration client, because a role is a string and a permission is a string, so there is no ADC DTO to close over.
          • +
          • An explicit services.AddScoped<IUserUIService, UserService>() (:39). The comment above it (:38) explains why this one is hand-written: users are a custom contract, not an IEntityService, so the scan cannot pick it up (the same asymmetry IUserUIService documents, because the Users API returns UserListDTO rather than an IBaseDTO-shaped entity DTO and exposes only list plus delete).
        • -
        • Why it's built this way: the handler needs no DI registration of its own. ScanModuleApplicationServices<ClassReference>() finds it through the base class's IQueryHandler<TQuery, Result<UserDataExportDTO>> implementation, which both the Identity DI comment (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:46-47) and the framework's own registration remarks record (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Extensibility.cs:32-35), and a dedicated test pins it (MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/UseCases/ExportUserData/ExportUserDataRegistrationTests.cs:29). Best-effort section aggregation is the same posture the module takes for the best-effort live-channel publish (ExportUserDataHandler.cs:23-28): one peer outage costs the subject one section, never the whole document ([Rubric §29, Resilience & Business Continuity]).
        • -
        • Where it's used: injected into UsersDataExportController as IQueryHandler<ExportUserDataQuery, Result<UserDataExportDTO>> (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersDataExportController.cs:27) and invoked from the inherited GET Users/{userId}/export action, which the framework's DataExportControllerBase<TQuery> declares [Authorize] and feature-gates on PrivacyFeatures.DataExport (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:57-58, action at :77-82), returning the document as a file download (:109). Covered by MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/UseCases/ExportUserDataHandlerTests.cs and, at the controller edge, MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.API.Tests/Controllers/UsersDataExportControllerTests.cs.
        • -
        • Caveats / not-in-source: ADC does not override OnExportCompletedAsync, the base's post-assembly hook for an access-log row or a metric (ExportUserDataHandlerBase.cs:159), so an export leaves no application-level audit record beyond ordinary request logging.
        • +
        • Why it's built this way: one host-facing method per module keeps the three hosts symmetric: they each call AddIdentityUI() and nothing else. The mix of shared scan helpers and generic and non-generic administration clients is deliberate, and the framework doc for AddUIModule<TModule>() (MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:277-283) records the ordering rationale: module-specific services are registered by the caller after the prologue, so a module whose service must win over a shared default still controls its own registration order. The email-confirmation client (IEmailConfirmationUIService / EmailConfirmationService, ADR-116) is no longer registered here: DependencyInjection.cs:38-42 (prior revision) dropped it, leaving AddIdentityUI() with three registrations instead of four.
        • +
        • Where it's used: called by all three UI hosts, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:163 (Blazor Server), MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Program.cs:56 (the WebAssembly client), and MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/MauiProgram.cs:128 (MAUI), which is what lets the same Razor Class Library render on web and mobile.
        • +
        • Caveats / not-in-source: the Identity module ships one DependencyInjection class per layer (this UI one plus the API, Application, and Infrastructure copies covered in sibling parts of this chapter). They all slug to the bare dependencyinjection anchor, which resolves to the first occurrence in the assembled chapter, so cross-references disambiguate by layer in prose.

        ForgotPasswordHandler

        @@ -2302,23 +2212,23 @@

        ForgotPasswordHandler

      • What it is: ADC's start-a-password-reset handler. Like the export handler it is a thin subclass of a shared workflow, and it supplies exactly one app-specific step: how to find an ADC account from an email address without tracking it.
      • Depends on: ForgotPasswordHandlerBase<TUser, TCommand>, User, ForgotPasswordCommand, IUnitOfWork, IPasswordResetTokenService, IEmailSender, PasswordResetSettings, Email; externals: IOptions<T>, ILogger<T>.
      • Concept introduced, the success-always workflow. [Rubric §11, Security] (assesses whether an anonymous endpoint's responses and logs leak which accounts exist) and [Rubric §2, Design Patterns] (template method again, with a single abstract member). The base is worth reading end to end (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ForgotPassword/ForgotPasswordHandlerBase.cs:36-101), because every exit from it is Result.Success(): a malformed address (:60-61), an address with no account (:68-69), a request the token service throttled (:75-76), and an email send that threw (:90-95) all log a reason and report success, exactly like the happy path (:98-99). The remarks state the rule outright (ForgotPasswordHandlerBase.cs:22-25): only the request validator can produce a 400, and it only inspects the shape of the address, so nothing about the response distinguishes a registered address from an unregistered one.
          -
        • The logging is part of that contract rather than an afterthought. UserUseCaseLog.PasswordResetRejected takes a plain reason string and neither an address nor an account id (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UserUseCaseLog.cs:39-40), and the comment above it says why (UserUseCaseLog.cs:37-38): the log must not become the enumeration oracle the responses are not. The two log lines that do carry a user id (:25-26, :28-29) are only reachable once an account has already been resolved.
        • +
        • The logging is part of that contract rather than an afterthought. The base declares its own [LoggerMessage] methods, and PasswordResetRejected takes a plain reason string and neither an address nor an account id (ForgotPasswordHandlerBase.cs:165-166), and the comment above it says why (ForgotPasswordHandlerBase.cs:163-164): the log must not become the enumeration oracle the responses are not. The two log lines that do carry a user id, PasswordResetRequested and PasswordResetEmailFailed (:157-158, :160-161), are only reachable once an account has already been resolved.
        • The send-failure branch is a small resilience decision worth copying (ForgotPasswordHandlerBase.cs:91-96): the token has already been issued and is still valid, so the user can simply retry, and reporting the SMTP failure to the caller would be an oracle of a different kind ([Rubric §29, Resilience & Business Continuity]).
      • Walkthrough: a primary-constructor class over five dependencies, every one of them forwarded straight to the base (ForgotPasswordHandler.cs:21-27), plus a single override.
        • FindUntrackedByEmailAsync(Email email, CancellationToken) (ForgotPasswordHandler.cs:30-34) is the whole ADC contribution. It takes a read repository off the unit of work, UnitOfWork.GetReadRepository<User, UserIdentifierType>(), and calls FirstOrDefaultAsync with u => u.Email == email and asTracking: false (:30-33).
        • Three details in those four lines. The predicate compares the Email value object rather than a raw string, so normalization is the value object's job and not a lowercase call here. asTracking: false is correct because this handler never mutates the account: it only needs the id to mint a token against, and the redeem side is a separate use case (ResetPasswordHandler). And the unit of work is reached through the base's protected UnitOfWork property, which exists precisely so the lookup override has a repository to reach (ForgotPasswordHandlerBase.cs:46).
        • -
        • The base declares this one member abstract for a stated reason (ForgotPasswordHandlerBase.cs:28-31, abstract member at :109): each app's User stores the address differently, so resolving an account by email is the only step the framework cannot write. Everything else, including the email body, is shared. The handler's own remarks note that this mirrors the untracked lookup AuthenticationService already uses for login (ForgotPasswordHandler.cs:16-20, and see AuthenticationService.cs:94-98).
        • +
        • The base declares this one member abstract for a stated reason (ForgotPasswordHandlerBase.cs:28-31, abstract member at :109): each app's User stores the address differently, so resolving an account by email is the only step the framework cannot write. Everything else, including the email body, is shared. The handler's own remarks note that this mirrors the untracked lookup AuthenticationService already uses for login (ForgotPasswordHandler.cs:16-20, and see AuthenticationService.cs:90-94).
      • -
      • Why it's built this way: the reset credential is a cache record rather than three new columns on the hottest table in the system, so this handler needs no migration and no sweeper: IPasswordResetTokenService owns the token, its lifetime, and the per-email throttle. The knobs live in PasswordResetSettings, bound from the PasswordReset section with ValidateDataAnnotations().ValidateOnStart() (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:154-157), which is what turns a bad TokenLifetimeMinutes (default 30, MMCA.Common/Source/Core/MMCA.Common.Application/Auth/PasswordResetSettings.cs:29) into a startup failure rather than a runtime surprise. One default is deliberately permissive: ResetUrl defaults to the empty string (PasswordResetSettings.cs:25), and an unconfigured host degrades to a token-only email the user pastes into the reset page by hand rather than shipping a broken link (ForgotPasswordHandlerBase.cs:139-148).
      • +
      • Why it's built this way: the reset credential is a cache record rather than three new columns on the hottest table in the system, so this handler needs no migration and no sweeper: IPasswordResetTokenService owns the token, its lifetime, and the per-email throttle. The knobs live in PasswordResetSettings, bound from the PasswordReset section with ValidateDataAnnotations().ValidateOnStart() (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:160-163), which is what turns a bad TokenLifetimeMinutes (default 30, MMCA.Common/Source/Core/MMCA.Common.Application/Auth/PasswordResetSettings.cs:29) into a startup failure rather than a runtime surprise. One default is deliberately permissive: ResetUrl defaults to the empty string (PasswordResetSettings.cs:25), and an unconfigured host degrades to a token-only email the user pastes into the reset page by hand rather than shipping a broken link (ForgotPasswordHandlerBase.cs:139-148).
      • Where it's used: registered as ICommandHandler<ForgotPasswordCommand, Result> by the module scan, which finds it through the base class's interface exactly as it finds the export handler (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:53). It is injected into PasswordResetController (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/PasswordResetController.cs:29), whose inherited POST Auth/forgot-password action is [AllowAnonymous], [Idempotent], rate-limited by the auth-ip policy, and answers 202 Accepted on every success (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/PasswordResetAuthControllerBase.cs:75-92). Covered by MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/UseCases/ForgotPasswordHandlerTests.cs, whose three cases pin the shipped behavior: a registered address issues a token and sends (:67), an unknown address still succeeds without issuing one (:86), and the lookup runs untracked (:100).
      • Caveats / not-in-source: what actually delivers the mail is not visible here. IEmailSender is resolved from the host, so whether a reset email leaves the process depends on the Identity service's SMTP configuration, which this type neither reads nor validates.

      SetUserAvatarHandler

      -

      MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.UseCases.SetUserAvatar · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/SetUserAvatar/SetUserAvatarHandler.cs:26 · Level 15 · class (sealed, partial)

      +

      MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.UseCases.SetUserAvatar · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/SetUserAvatar/SetUserAvatarHandler.cs:27 · Level 15 · class (sealed, partial)

      • What it is: the command handler that uploads or replaces the signed-in user's avatar (BR-116a, @@ -2341,19 +2251,20 @@

        SetUserAvatarHandler

        ImageContentSniffer; User; UserAvatarDTO; Result; Error; externals Microsoft.Extensions.Logging - (ILogger plus [LoggerMessage] source generation), System.Guid, System.IO.MemoryStream, - System.Uri, System.Globalization.CultureInfo.
      • + (ILogger plus [LoggerMessage] source generation), Microsoft.Extensions.DependencyInjection + (IServiceScopeFactory, for the failure-path cleanup scope), System.Guid, + System.IO.MemoryStream, System.Uri, System.Globalization.CultureInfo.
      • Concept introduced, safe handling of a user-supplied binary upload (ADR-045). This is the one place in the Identity module that accepts arbitrary bytes from an end user, and it treats them as hostile. [Rubric §11, Security] assesses input trust boundaries and defense against malicious uploads: the accepted format is decided by magic-byte sniffing (ImageContentSniffer.IsAllowedImage, - called at SetUserAvatarHandler.cs:54, implemented at + called at SetUserAvatarHandler.cs:63, implemented at MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/Storage/ImageContentSniffer.cs:15-16 as JPEG or PNG or WebP, each with its own byte-signature test at :21-36), never the client-declared - content type, and the image is re-encoded rather than stored as received, which the class doc - (:10-15) notes strips EXIF and kills polyglots (a payload that is a valid image and a valid + content type, and the image is re-encoded rather than stored as received, which the class doc + (:14-19, specifically :16) notes strips EXIF and kills polyglots (a payload that is a valid image and a valid script). [Rubric §30, Compliance, Privacy and Data Governance]: the same re-encode drops geolocation and camera EXIF a user did not intend to publish, and the stored URL lands on a [Pii]-tagged property @@ -2363,100 +2274,218 @@

        SetUserAvatarHandler

      • Concept introduced (second), the templated mutation workflow with a side-data context. This handler writes almost no plumbing: the base MutateEntityHandlerCore<TCommand, TEntity, TIdentifierType> - owns the load-mutate-save sequence + owns the load-mutate-save sequence in MutateCoreAsync (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/MutateEntityHandlerBase.cs:271-322): - resolve the repository (:279), load the aggregate (:280), answer Error.NotFound stamped with - the handler name when it is missing (:281-282), stamp the caller's rowversion when the endpoint is - conditional (:290-291, ADR-035), run the subclass mutation (:293), save (:302), then call - LogMutated (:304) and the post-commit OnMutatedAsync (:305). Subclasses fill in hooks. The + resolve the repository (:280), load the aggregate (:281), answer Error.NotFound stamped with + the handler name when it is missing (:282-283), stamp the caller's rowversion when the endpoint is + conditional (:291-296, ADR-035), run the subclass mutation (:298), skip the save on an + idempotent no-op (:304-305), save (:316), then call LogMutated (:318) and the post-commit + OnMutatedAsync (:319). Subclasses fill in hooks. The MutationContext is the mechanism that lets one hook hand a value to a later one: a Dictionary<string, object?> bag (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/Crud/MutationContext.cs:33) with Set<TValue> (:56), type-checked TryGet<TValue> (:69) and GetOrDefault<TValue> (:93). [Rubric §2, Design Patterns] assesses whether recurring shapes are factored into a named - abstraction: this is Template Method with an explicit, typed side channel instead of mutable handler - fields. [Rubric §15, Best Practices & Code Quality]: the interesting five lines of this handler are the image - work, not the repository and save ceremony. [Rubric §13, Observability and Operability]: the - success path logs through a compile-time [LoggerMessage] source-generated method (:153-154), - which is why the class is partial.
      • + abstraction: this is Template Method with an explicit, typed side channel for the value that crosses + the commit (the replaced blob's name). The one value the overridden HandleAsync must read after a + failed run, the blob this request just uploaded, lives instead in a single private field, + _uploadedBlobName (:42-47): set by the mutation, cleared by the post-commit hook, and (per its + doc) still set when the save fails, which is how the handler knows the new blob has been orphaned. [Rubric §15, Best Practices & Code Quality]: the interesting lines of this + handler are the image work and the orphan bookkeeping, not the repository and save ceremony. + [Rubric §13, Observability and Operability]: the success path logs through a compile-time + [LoggerMessage] source-generated method (:206-207), as do both scheduling-failure warnings + (:209-210, :212-213), which is why the class is partial.
      • Walkthrough
        • Primary-constructor injection of IUnitOfWork, IImageProcessor, IFileStorageService, - IInternalCommandScheduler and - an ILogger<SetUserAvatarHandler> (SetUserAvatarHandler.cs:26-31), with the unit of work - forwarded to the base MutateEntityPayloadHandlerBase<SetUserAvatarCommand, User, UserIdentifierType, UserAvatarDTO> (:32). Two constants: internal const int AvatarSize = 256, - the canonical edge length (:35), and private const string PreviousBlobNameKey = "Avatar.PreviousBlobName", the context key the replaced blob's name travels under (:38).
        • -
        • EntityId (:41) tells the base which aggregate to load: command.UserId.
        • -
        • Format gate, before the aggregate is loaded (:48-63): HandleAsync is overridden purely to - run ImageContentSniffer.IsAllowedImage(command.Content.Span) (:54) and return + IInternalCommandScheduler, + an IServiceScopeFactory and an ILogger<SetUserAvatarHandler> (SetUserAvatarHandler.cs:27-33), + with the unit of work forwarded to the base MutateEntityPayloadHandlerBase<SetUserAvatarCommand, User, UserIdentifierType, UserAvatarDTO> (:34). Two constants: internal const int AvatarSize = 256, the canonical edge length (:37), and private const string PreviousBlobNameKey = "Avatar.PreviousBlobName", the context key the replaced blob's name travels under (:40). One + field: private string? _uploadedBlobName (:47), the blob this request uploaded until the row + pointing at it is committed.
        • +
        • EntityId (:50) tells the base which aggregate to load: command.UserId.
        • +
        • Format gate, before the aggregate is loaded (:57-69): the async HandleAsync override + first runs ImageContentSniffer.IsAllowedImage(command.Content.Span) (:63) and returns Error.Validation with code "Avatar.UnsupportedFormat" and the source stamped as the handler - name (:56-59) before delegating to base.HandleAsync (:62). The doc (:43-46) gives the - reason: an unsupported upload is answered with the format error whether or not the account exists, - so the response cannot be used to probe for account existence.
        • -
        • The mutation (:66-102), which runs with the aggregate already loaded and tracked. The bytes - are wrapped in a non-writable MemoryStream (:72) and handed to + name (:65-68) without touching the base. The doc (:52-55) gives the reason: an unsupported + upload is answered with the format error whether or not the account exists, so the response + cannot be used to probe for account existence.
        • +
        • Orphan guard around the base workflow (:71-89): the comment (:71-73) states the problem. + The new blob is uploaded before the row is saved, so a save that throws, or a failure returned + after the upload, leaves a blob no row points at. The override resets _uploadedBlobName (:74), + awaits base.HandleAsync (:77), and when the result is a failure with the field still set it + schedules that blob's cleanup before returning the original result unchanged (:78-83). A thrown + exception is caught only when the field is set (exception filter at :85), the cleanup is + scheduled, and the exception is rethrown (:87-88), so the caller sees exactly the outcome it + would have seen without the guard.
        • +
        • The mutation (:93-130), which runs with the aggregate already loaded and tracked. The bytes + are wrapped in a non-writable MemoryStream (:99) and handed to imageProcessor.NormalizeToSquareJpegAsync(content, AvatarSize, ...) inside an await using - block (:74-77); a failed Result<byte[]> re-returns its own errors (:79-82), so the failure - detail from the image layer reaches the client unchanged and, critically, the save never - happens (the base short-circuits at MutateEntityHandlerBase.cs:299-300).
        • -
        • Upload (:84-96): a fresh eight-hex-character suffix comes from - Guid.NewGuid().ToString("N")[..8] (:84), and the blob name is built culture-invariantly with - string.Create(CultureInfo.InvariantCulture, $"{command.UserId}-{suffix}.jpg") (:85). The JPEG - is streamed to fileStorage.UploadAsync(blobName, jpeg, "image/jpeg", ...) (:90); a failed - upload short-circuits the same way (:93-96).
        • -
        • Hand the orphan forward, then mutate (:98-99): context.Set(PreviousBlobNameKey, TryGetBlobName(entity.AvatarUrl)) captures the blob this write is about to orphan before the - property is overwritten (:98), then entity.SetAvatarUrl(uploaded.Value!.AbsoluteUri) - (:99, domain setter at + block (:101-104); a failed Result<byte[]> re-returns its own errors (:106-109), so the + failure detail from the image layer reaches the client unchanged and the save never happens + (the base short-circuits at MutateEntityHandlerBase.cs:299-300). Nothing has been uploaded yet + on this path, so the field stays null and no cleanup is scheduled.
        • +
        • Upload (:111-123): a fresh eight-hex-character suffix comes from + Guid.NewGuid().ToString("N")[..8] (:111), and the blob name is built culture-invariantly with + string.Create(CultureInfo.InvariantCulture, $"{command.UserId}-{suffix}.jpg") (:112). The JPEG + is streamed to fileStorage.UploadAsync(blobName, jpeg, "image/jpeg", ...) (:117); a failed + upload short-circuits the same way (:120-123).
        • +
        • Record the new blob, hand the old one forward, then mutate (:125-127): _uploadedBlobName = blobName (:125) marks the fresh upload as an orphan candidate until commit; + context.Set(PreviousBlobNameKey, TryGetBlobName(entity.AvatarUrl)) captures the blob this write + is about to orphan before the property is overwritten (:126); then + entity.SetAvatarUrl(uploaded.Value!.AbsoluteUri) (:127, domain setter at MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:352). The base commits.
        • -
        • LogMutated (:105-106) fires the source-generated LogAvatarSet at Information level with the - message "User {UserId} avatar updated" (:153-154).
        • -
        • OnMutatedAsync (:113-135), the post-commit hook: it reads - context.GetOrDefault<string>(PreviousBlobNameKey) (:119) and returns immediately when it is - null (:120-123). Otherwise it schedules a +
        • LogMutated (:133-134) fires the source-generated LogAvatarSet at Information level with the + message "User {UserId} avatar updated" (:206-207).
        • +
        • OnMutatedAsync (:141-166), the post-commit hook: it first clears _uploadedBlobName + (:147-148), because the row now points at the new blob and it is no longer an orphan candidate. + It then reads context.GetOrDefault<string>(PreviousBlobNameKey) (:150) and returns immediately + when it is null (:151-154). Otherwise it schedules a DeleteAvatarBlobInternalCommand through - internalCommandScheduler.ScheduleAsync(...) (:127-129) instead of deleting the blob inline; a - scheduling failure is logged via LogAvatarBlobScheduleFailed at Warning (:131-134) rather than + internalCommandScheduler.ScheduleAsync(...) (:158-160) instead of deleting the blob inline; a + scheduling failure is logged via LogAvatarBlobScheduleFailed at Warning (:162-165) rather than surfaced to the caller, since the mutation itself already committed. The comment above the call - (:125-126) explains why: no transaction is active this late, so the row is already written and + (:156-157) explains why: no transaction is active this late, so the row is already written and signalled immediately, and the internal-command processor picks the delete up on its next tick rather than waiting for the poll interval.
        • -
        • BuildResult (:122-126) shapes the handler's answer from the mutated aggregate: +
        • BuildResult (:169-173) shapes the handler's answer from the mutated aggregate: Result.Success(new UserAvatarDTO(entity.AvatarUrl)).
        • -
        • TryGetBlobName (:129-132): an internal static helper that parses the stored URL with +
        • ScheduleUploadedBlobCleanupAsync (:180-198), the failure-path cleanup: it opens a fresh async + DI scope (:184), resolves its own IInternalCommandScheduler from it (:185), and schedules a + DeleteAvatarBlobInternalCommand for the orphaned new blob with CancellationToken.None + (:186-188). The doc (:175-179) gives both reasons: the request scope's context is the one + whose save just failed, and a cancelled request token must not cancel the cleanup. A failed + Result logs LogAvatarBlobScheduleFailed (:189-192) and a thrown exception logs + LogAvatarBlobScheduleThrew with the exception (:194-197); neither replaces the original + outcome.
        • +
        • TryGetBlobName (:200-204): an internal static helper that parses the stored URL with Uri.TryCreate, requires more than one segment, and returns the unescaped final segment as the blob name (otherwise null). It is deliberately internal static because RemoveUserAvatarHandler reuses it.
      • Why it's built this way: the random blob-name suffix means a replacement never reuses the old - URL, so browser and CDN caches self-resolve without an explicit purge (class doc, :1-6). Doing + URL, so browser and CDN caches self-resolve without an explicit purge (class doc, :17-18). Doing the blob work inside the mutation and deferring the cleanup to a durable queue is the ADR-096 shape combined with the ADR-114 - durable-job pattern, and the class remarks name the change outright: an upload that fails still stops - the write before the row is touched, but the blob the write orphans is now handed to the - internal-command queue rather than deleted inline, so a storage outage or a crash in the post-commit - tail defers the delete instead of leaking an image with nothing left that knows about it. Sniffing + durable-job pattern, as the class remarks (:20-26) state: an upload that fails stops the write + before the row is touched, and the blob the write orphans is handed to the internal-command queue + rather than deleted inline, so a storage outage or a crash in the post-commit tail defers the delete + instead of leaking an image with nothing left that knows about it. The orphan guard in HandleAsync + covers the other direction: a fresh upload whose save then fails or throws is queued for deletion + through the same command, from a scope and a token that the failed request cannot affect. Sniffing plus mandatory re-encoding is the ADR-045 rule that an accepted upload is only ever stored in a shape the server itself produced.
      • Where it's used: dispatched by UsersController.SetAvatarAsync (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersController.cs:109-111, injected as ICommandHandler<SetUserAvatarCommand, Result<UserAvatarDTO>> at :33); the returned - UserAvatarDTO carries the new URL back for immediate display. Unit-tested by - MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/UseCases/SetUserAvatarHandlerTests.cs.
      • + UserAvatarDTO carries the new URL back for immediate display. The type is also + referenced from RemoveUserAvatarHandler, + DeleteUserHandler and + SetUserAvatarCommandValidator. Unit-tested by + MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/UseCases/SetUserAvatar/SetUserAvatarHandlerTests.cs.
      • Caveats / not-in-source: the concrete resize and crop strategy behind NormalizeToSquareJpegAsync, and the storage provider behind IFileStorageService, live in MMCA.Common and are not visible here: this handler only orchestrates them. The 2 MB per-upload size limit is enforced outside this file, at the API boundary (.../UsersController.cs:81, :92-98) and in - SetUserAvatarCommandValidator (:29-32); the comment at :47-48 - says the size limit stays app-side. The actual blob deletion no longer runs inside this handler at - all: OnMutatedAsync only schedules + SetUserAvatarCommandValidator (:29-32); the comment at :61-62 + says the size limit stays app-side. The actual blob deletion never runs inside this handler: both + the post-commit hook and the failure-path cleanup only schedule DeleteAvatarBlobInternalCommand; its processor and retry - behavior live in the internal-command infrastructure, not here. A failed ScheduleAsync call is - logged but its Result is otherwise not further inspected.
      • + behavior live in the internal-command infrastructure, not here. A failed or thrown scheduling call + is logged and otherwise swallowed, so a blob can still be left behind when the queue itself is + unavailable (both warning messages say it "must be removed by hand", :209, :212). The + _uploadedBlobName field makes the handler instance stateful for the duration of one call; that + is safe only while each call gets its own handler instance, a lifetime set by DI registration + outside this file. +
      +

      ChangePasswordRequestValidator

      +
      +

      MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.Validation · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/Validation/ChangePasswordRequestValidator.cs:11 · Level 1 · class (sealed)

      +
      +
        +
      • What it is: the FluentValidation rule set for a password change: the current password must be present, and the new password must clear the shared strength rules.
      • +
      • Depends on: ChangePasswordRequest (the shared request DTO it validates), StrongPasswordRules<T>; externals: FluentValidation (AbstractValidator<T>, RuleFor, Include).
      • +
      • Concept introduced, the request validator that reaches the pipeline indirectly. [Rubric §6, CQRS & Event-Driven] (assesses whether cross-cutting concerns sit in the pipeline rather than inside handlers) and [Rubric §11, Security] (assesses that password strength is enforced at a boundary the caller cannot bypass). Nothing in ADC constructs this class: it is picked up by the convention scan in the Identity Application layer's AddModuleIdentityApplication (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:53, ScanModuleApplicationServices<ClassReference>()) and registered as IValidator<ChangePasswordRequest> by the AddValidatorsFromAssembly call inside that scan (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.ModuleScanning.cs:117). The Validating decorator, though, validates the command, not the request. The bridge is the framework's CommandRequestValidator<TCommand, TRequest> (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommandRequestValidator.cs:30), which does RuleFor(c => c.Request).SetValidator(validator) (CommandRequestValidator.cs:39) and is closed over any command implementing ICommandWithRequest<TRequest> by a reflection loop in the same scan (DependencyInjection.ModuleScanning.cs:121-135). ChangePasswordCommand declares that marker (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ChangePassword/ChangePasswordCommand.cs:16), so writing this one small validator is enough to put password rules in front of the handler with no wiring at all.
      • +
      • Walkthrough: one constructor (ChangePasswordRequestValidator.cs:13-19).
          +
        • RuleFor(x => x.CurrentPassword).NotEmpty() with the message "Current password is required." and the error code User.CurrentPassword.Required (ChangePasswordRequestValidator.cs:15-16). The explicit WithErrorCode matters: the API surfaces codes, not English text, so a client can branch on the code and a translator can own the message.
        • +
        • Include(new StrongPasswordRules<ChangePasswordRequest>(x => x.NewPassword)) (ChangePasswordRequestValidator.cs:18). Include folds another rule set into this one against a selected property, so the framework owns "what counts as a strong password" in one place and every app inherits changes to it.
        • +
        +
      • +
      • Why it's built this way: the current password is deliberately only checked for presence here, never for strength. Its correctness is a credential comparison against the stored hash, which lives in ChangePasswordHandler via the framework's ChangePasswordHandlerBase, and an old account may legitimately hold a password that no longer meets today's rules. Applying strength rules to it would lock those users out of the very screen that would fix the problem.
      • +
      • Where it's used: resolved as IValidator<ChangePasswordRequest> by CommandRequestValidator<ChangePasswordCommand, ChangePasswordRequest>, which the Validating decorator runs before ChangePasswordHandler for the password-change endpoint on AuthController.
      • +
      +

      ExportUserDataQuery

      +
      +

      MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.UseCases.ExportUserData · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ExportUserData/ExportUserDataQuery.cs:12 · Level 2 · record (sealed)

      +
      +
        +
      • What it is: the request for a data-subject export (PRIVACY.md §7): which account to export, plus who is asking and with what role.
      • +
      • Depends on: IUserOwnedRequest, the UserIdentifierType alias.
      • +
      • Concept introduced, carrying the caller inside the query. [Rubric §11, Security] (assesses whether authorization decisions are made on trusted, explicit inputs) and [Rubric §5, Vertical Slice] (assesses whether a use case owns its own request shape). Three positional members (ExportUserDataQuery.cs:12-15): UserId, CurrentUserId, and the nullable CurrentUserRole. The handler never reaches for HttpContext; the controller reads the claims once through ICurrentUserService and hands them to the query factory (UsersDataExportController, MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersDataExportController.cs:32-35). That is what makes the whole use case testable without a web host, and it is why the ownership rule can live in the shared base rather than in a controller filter. Implementing IUserOwnedRequest is the load-bearing part: the framework's generic constraint is where TQuery : IUserOwnedRequest (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ExportUserData/ExportUserDataHandlerBase.cs:55), so the shared UserOwnershipRule can read UserId, CurrentUserId, and CurrentUserRole off any app's query record (ExportUserDataHandlerBase.cs:81-86).
      • +
      • Walkthrough: a positional record with an XML doc per parameter and nothing else (ExportUserDataQuery.cs:5-15). What it deliberately does not implement is as informative as what it does: it carries no IQueryCacheable, so the Caching decorator has nothing to act on. The base handler's remarks say why (ExportUserDataHandlerBase.cs:42-45): the document it produces is PII by design and must never be logged or cached.
      • +
      • Why it's built this way: the nullable CurrentUserRole mirrors reality at the edge, where a role claim may simply be absent. The privilege check is therefore written to accept null and answer false rather than to assume a role exists (ExportUserDataHandler.HasExportPrivilege, ExportUserDataHandler.cs:38).
      • +
      • Where it's used: constructed by UsersDataExportController for GET Users/{userId}/export (UsersDataExportController.cs:32-35) and handled by ExportUserDataHandler, which the controller receives as IQueryHandler<ExportUserDataQuery, Result<UserDataExportDTO>> (UsersDataExportController.cs:27).
      • +
      +

      SelfHttpWarmupTask

      +
      +

      MMCA.ADC.Identity.Service · MMCA.ADC.Identity.Service · MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/SelfHttpWarmupTask.cs:23 · Level 2 · class (sealed, internal)

      +
      +
        +
      • What it is: the Identity service's startup warm-up. Once Kestrel is listening, it replays the hot users read against the host's own cleartext endpoint so routing, authentication, and the middleware pipeline are JIT-compiled before the first real request arrives.
      • +
      • Depends on: SelfHttpWarmupTaskBase from MMCA.Common.Aspire.Warmup; externals: ASP.NET Core (IServer, IHostApplicationLifetime, IHostEnvironment), IConfiguration, ILogger<T>.
      • +
      • Concept reinforced, the cold-start warm-up task (ADR-025). [Rubric §12, Performance & Scalability] (assesses whether first-request latency after a scale-out or restart is managed rather than paid by a user) and [Rubric §13, Observability & Operability] (assesses that readiness reflects the instance's real ability to serve). The base class, taught in group-16, discovers the bound address, waits for the host lifetime, and issues the configured GETs; the runner registered by AddWarmupReadiness() keeps /health/ready not-ready until the task has had its chance, so the orchestrator does not route traffic into a cold instance. Failures are logged and fall back to lazy warm-up, so a warm-up problem degrades startup latency rather than blocking the service (SelfHttpWarmupTask.cs:13-16).
      • +
      • Walkthrough: a primary-constructor class that forwards all five dependencies to the base (SelfHttpWarmupTask.cs:23-29) and overrides three members.
          +
        • Paths (SelfHttpWarmupTask.cs:33-36), one entry: users?pageNumber=1&pageSize=10. The comment above it ties the string to reality (SelfHttpWarmupTask.cs:31-32): it is the exact shape UserService.GetPagedAsync builds for the organizer list once the empty filter and sort values are dropped, leaving only the paging pair.
        • +
        • Name => "SelfHttpWarmup" (SelfHttpWarmupTask.cs:39) and WarmupPaths => Paths (SelfHttpWarmupTask.cs:42), the base's two abstractions.
        • +
        • RequireSuccessStatusCode => false (SelfHttpWarmupTask.cs:49), the one genuinely interesting override. UsersController is [Authorize] plus [HasPermission], so an unauthenticated self-request is refused by design and returns 401. The comment (SelfHttpWarmupTask.cs:44-48) makes the argument: the refusal still traverses Kestrel, routing, authentication, and the whole middleware pipeline, which is exactly where the cold-start JIT cost lives, so a 401 is the correct outcome and treating it as a failure would log a spurious warning on every single startup.
        • +
        +
      • +
      • Why it's built this way: the class doc (SelfHttpWarmupTask.cs:6-17) draws the contrast with Conference's task, which warms public read endpoints and therefore also populates the output cache. Identity has no anonymous read to warm, and its host sets a NoCache base output-cache policy anyway (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:163), so this task buys pipeline JIT only and says so rather than pretending to prime a cache it cannot reach.
      • +
      • Where it's used: registered with services.AddWarmupTask<SelfHttpWarmupTask>() in the Identity service host (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:169), where the surrounding comment block (Program.cs:165-168) repeats the rationale for an operator reading the host file.
      • +
      • Caveats / not-in-source: the base type decides how the bound address is discovered and whether the run is skipped under the Testing environment; this file only supplies the paths and the status-code policy.
      • +
      +

      UserDeleted

      +
      +

      MMCA.ADC.Identity.Domain · MMCA.ADC.Identity.Domain.Users.DomainEvents · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/DomainEvents/UserDeleted.cs:10 · Level 2 · record (sealed)

      +
      +
        +
      • What it is: the in-process domain event the User aggregate raises when an account is soft-deleted (BR-56). It carries the user id and nothing else.
      • +
      • Depends on: BaseDomainEvent, the UserIdentifierType alias.
      • +
      • Concept reinforced, the domain event as an internal fact. [Rubric §4, DDD] (assesses whether state changes that other code cares about are expressed as named domain facts instead of inferred from a database write) and [Rubric §6, CQRS & Event-Driven]. The distinction from an integration event is the one to keep straight, and this module makes it unusually concrete: there is a second UserDeleted record, in MMCA.ADC.Identity.Shared.Users.IntegrationEvents, carrying the same fact to other processes. A BaseDomainEvent such as this one is dispatched in-process by the framework's domain-event dispatcher after SaveChangesAsync (deferred until after commit when a transaction is open), while a BaseIntegrationEvent leaves its outbox row unprocessed for the OutboxProcessor to publish to the broker (ADR-003). Same AddDomainEvent call at the aggregate, two very different delivery paths, chosen purely by base type.
      • +
      • Walkthrough: a positional record with one member, public sealed record class UserDeleted(UserIdentifierType UserId) : BaseDomainEvent (UserDeleted.cs:10-12). The id-only payload is deliberate: an in-process subscriber can load whatever else it needs from the same unit of work, and a fat payload would go stale between raise and dispatch. Note the absence of an [EventName] attribute, which the two Shared integration events both carry: a domain event never crosses a wire, so it needs no stable published name.
      • +
      • Why it's built this way: raising the event inside User.Delete (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:448) and only when the base soft-delete actually succeeded (User.cs:445-449) means a second delete on an already-deleted account raises nothing, so subscribers cannot see a duplicate fact. The override does nothing else, and its doc comment explains the omission (User.cs:437-441): outstanding refresh sessions live in the RefreshSessions table and the refresh flow re-fetches the account through the soft-delete query filter, so every session stops working the moment this commits, with no explicit revocation call needed here.
      • +
      • Where it's used: raised by User.Delete (User.cs:443-451) and asserted by the domain tests (MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Domain.Tests/Users/UserInvariantsAndRoleTests.cs:271-281).
      • +
      • Caveats / not-in-source: no IDomainEventHandler<UserDeleted> exists anywhere in ADC source today. The doc comment names cascade cleanup and audit logging as the intended consumers (UserDeleted.cs:6-7), but that is an available extension point, not shipped behavior. The cross-process cleanup that is shipped travels on the Shared integration event of the same name, raised separately by DeleteUserHandler; the two records are not connected in code.
      • +
      +

      UserPasswordChanged

      +
      +

      MMCA.ADC.Identity.Domain · MMCA.ADC.Identity.Domain.Users.DomainEvents · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/DomainEvents/UserPasswordChanged.cs:9 · Level 2 · record (sealed)

      +
      +
        +
      • What it is: the in-process domain event raised when a user's credentials are replaced (BR-204).
      • +
      • Depends on: BaseDomainEvent, the UserIdentifierType alias.
      • +
      • Concept: structurally identical to the domain UserDeleted, and the same domain-event-versus-integration-event distinction applies. What is worth noticing is the omission: the payload is the id only (UserPasswordChanged.cs:9-11), never the new hash or salt. A security-relevant event that carried credential material would turn every future subscriber, and every log line that serialized it, into a leak ([Rubric §11, Security]).
      • +
      • Walkthrough: public sealed record class UserPasswordChanged(UserIdentifierType UserId) : BaseDomainEvent (UserPasswordChanged.cs:9-11).
      • +
      • Where it's used: raised by User.ChangePassword after the two credential invariants pass and the new hash and salt are assigned (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:360-376, with the raise at :307), and asserted by the domain tests (MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Domain.Tests/Users/UserTests.cs:173).
      • +
      • Caveats / not-in-source: like the domain UserDeleted, no handler subscribes to it in ADC source today. Session revocation on a password change is not driven from this event, and the aggregate itself no longer carries a refresh token to revoke: sessions live in their own table (User.cs:438-440).
      • +
      +

      UserDeleted

      +
      +

      MMCA.ADC.Identity.Shared · MMCA.ADC.Identity.Shared.Users.IntegrationEvents · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/IntegrationEvents/UserDeleted.cs:26 · Level 3 · record (sealed)

      +
      +
        +
      • What it is: the cross-service announcement that an account has been erased (UC-21, BR-56). Same name as the Identity domain event above, different assembly, different delivery path, different job.
      • +
      • Depends on: BaseIntegrationEvent, EventNameAttribute, the UserIdentifierType alias.
      • +
      • Concept introduced, the erasure announcement, and why its payload is nearly empty. [Rubric §30, Compliance, Privacy & Data Governance] (assesses whether an erasure reaches every store that holds the subject's personal data) and [Rubric §11, Security]. Two positional members only (UserDeleted.cs:26-29): UserId and DeletedOn. The doc comment gives the reason in one sentence (UserDeleted.cs:17-21): a downstream module reacting to an erasure already stores the scalar user id, and carrying a name or an email here would publish the very personal data the erasure exists to remove, onto a broker that persists messages. An erasure event that leaked PII would be self-defeating.
      • +
      • Concept introduced, the wire name is not the CLR name. [EventName("Identity.UserDeleted.v1")] (UserDeleted.cs:25) pins the string the broker and the outbox use, so the two same-named records can never collide on the wire and a future namespace move or rename cannot break a running consumer. The version suffix is what makes an incompatible payload change expressible as a new name rather than as a silent break (ADR-010).
      • +
      • Walkthrough: the interesting mechanics are not in the record, they are at the raise site. DeleteUserHandler calls user.AddDomainEvent(new UserDeleted(command.UserId, timeProvider.GetUtcNow())) inside OnAfterSoftDeleteAsync, before the erasure is saved (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/DeleteUser/DeleteUserHandler.cs:43-59), and the record's own doc spells out what that buys (UserDeleted.cs:7-10): the outbox row is written by the very SaveChangesAsync that commits the soft-delete and the anonymization, so an account that is erased has always produced exactly one event, and an erasure that rolls back produces none. Publishing after the commit instead would leave a crash window in which the account is gone and the published name is not. The timestamp comes from the injected TimeProvider (DeleteUserHandler.cs:33), not DateTimeOffset.UtcNow, so the handler stays testable.
      • +
      • Why it's built this way: the doc comment (UserDeleted.cs:11-16) is explicit that this record does not replace the in-process domain UserDeleted: Engagement is a different process with its own database and never sees an Identity in-process dispatch. Two records carrying one fact is the honest modelling of a two-process reality, and the base type is the only thing that decides which path a raise takes (ADR-003).
      • +
      • Where it's used: consumed by Engagement's UserDeletedPointsHandler (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Application/Points/IntegrationEventHandlers/UserDeletedPointsHandler.cs:38-45), which takes the account off the public leaderboard and overwrites the LeaderboardOptIn display name the attendee had published there, the one place Engagement holds a name rather than a scalar id (UserDeletedPointsHandler.cs:13-20). The subscription is wired in the Engagement service host with x.RegisterIntegrationEventConsumer<UserDeleted>() (MMCA.ADC/Source/Services/MMCA.ADC.Engagement.Service/Program.cs:291). That handler is idempotent in both directions and deliberately does not swallow exceptions, so a failed erasure is retried by the outbox and the broker instead of being acked away with a log line (UserDeletedPointsHandler.cs:23-28).

      IUserUIService

      @@ -2476,6 +2505,37 @@

      IUserUIService

    • Why it's built this way: an interface here is what lets the Profile and UserList pages be tested with a fake instead of a live HTTP stack ([Rubric §14, Testability]), and it is the boundary that keeps HttpClient, retries, and JSON shapes out of the components entirely.
    • Where it's used: implemented by UserService; injected into UserList (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Pages/Users/UserList.razor.cs:33) and Profile (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Pages/Users/Profile/Profile.razor.cs:20); registered scoped in AddIdentityUI (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/DependencyInjection.cs:39).
    +

    NotificationUserDataExportSection

    +
    +

    MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.UseCases.ExportUserData · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ExportUserData/NotificationUserDataExportSection.cs:18 · Level 3 · class (sealed)

    +
    +
      +
    • What it is: the Notifications contribution to a data-subject export: the user's notification inbox rows, fetched from the Notification service and projected into the export's own DTO shape.
    • +
    • Depends on: IUserDataExportSection (the contract), UserDataExportSectionResult, IUserNotificationExportService (the cross-service peer), UserDataExportNotificationSectionDTO, UserDataExportNotificationDTO.
    • +
    • Concept introduced, the export section as a pluggable contributor. [Rubric §30, Compliance, Privacy & Data Governance] (assesses whether a data-subject access request can actually be satisfied across every store that holds the subject's data) and [Rubric §7, Microservices Readiness] (assesses that a module reaches a peer through an interface it could satisfy in-process or over the wire). An access request is only as complete as the list of places that answer it, and in ADC those places are separate processes with separate databases. The framework's answer is a small interface, IUserDataExportSection (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ExportUserData/IUserDataExportSection.cs:20), with a stable SectionName that appears verbatim in the document a subject reads (IUserDataExportSection.cs:27) and one ExportAsync per user (:38). Sections accumulate through AddUserDataExportSection<TSection>(), which registers them scoped and via TryAddEnumerable so a double registration adds one entry (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Extensibility.cs:41-43), and they are exported in registration order (IUserDataExportSection.cs:16-17).
    • +
    • Walkthrough: a primary-constructor class over one dependency (NotificationUserDataExportSection.cs:18-19).
        +
      • SectionName => "Notifications" (NotificationUserDataExportSection.cs:22). Treat it as contract text, not a label: it is the key a subject (or a regulator) reads in the JSON.
      • +
      • ExportAsync (NotificationUserDataExportSection.cs:25-47) awaits GetUserNotificationExportAsync(userId, ...) on the peer (:29-31), then projects each row into a UserDataExportNotificationDTO with NotificationId, Title, SentOn, IsRead, ReadOn, and ScopeKey (:35-43), wrapped in a UserDataExportNotificationSectionDTO (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/DataExport/UserDataExportNotificationSectionDTO.cs:10). ScopeKey is the event-scoping key each notification carries (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/DataExport/UserDataExportNotificationDTO.cs:30), and exporting it is what lets a subject see which event a notification belonged to rather than an undifferentiated list. The re-projection is what keeps the exported wire shape owned by Identity.Shared rather than by whatever the peer happens to return today.
      • +
      • UserDataExportSectionResult.Complete(SectionName, data) (:46) closes it out. A user with an empty inbox produces an empty list and a Complete result, which is a truthful "nothing here" rather than the ambiguous "could not tell" an Unavailable would report (IUserDataExportSection.cs:9-13).
      • +
      +
    • +
    • Why it's built this way: the class doc (NotificationUserDataExportSection.cs:11-15) makes the omission explicit: transport failures are not caught here. Catching them locally would mean every section reinventing the degrade policy; letting them propagate one frame lets the base of ExportUserDataHandler apply one policy to all sections ([Rubric §29, Resilience & Business Continuity]).
    • +
    • Where it's used: registered second, after Engagement, in AddModuleIdentityApplication (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:49), which fixes its position in the document; the peer client is wired in the Identity service host with services.AddNotificationUserExportClient() inside the application-pipeline chain (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:316), resolving to a gRPC adapter outside the Notification process. Covered by MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/UseCases/NotificationUserDataExportSectionTests.cs.
    • +
    +

    UserRegistered

    +
    +

    MMCA.ADC.Identity.Shared · MMCA.ADC.Identity.Shared.Users.IntegrationEvents · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/IntegrationEvents/UserRegistered.cs:36 · Level 3 · record (sealed)

    +
    +
      +
    • What it is: the cross-service announcement that a new account exists. It carries the database-generated user id plus the identity fields another context needs to match on, and it is the event that drives the BR-207 speaker auto-link.
    • +
    • Depends on: BaseIntegrationEvent, EventNameAttribute, the UserIdentifierType alias.
    • +
    • Concept introduced, the integration event as a published contract. [Rubric §6, CQRS & Event-Driven] (assesses whether contexts collaborate through facts rather than commands), [Rubric §7, Microservices Readiness] (assesses that the producer does not know its consumers), and [Rubric §9, API & Contract Design]. Four properties of this record make it a contract rather than an internal message. It lives in Shared, the assembly a consumer may reference without touching Identity's Domain or Application. It carries denormalized primitives (string Email, string FirstName, string LastName, string Role) rather than the Email value object or UserRole, so a subscriber needs no Identity types to deserialize it. It is a record with positional members, so adding an optional member later is an additive change consumers can ignore, which is exactly how EmailVerified arrived. And it declares its wire name explicitly, [EventName("Identity.UserRegistered.v1")] (UserRegistered.cs:35), so the published contract survives any CLR-side rename (ADR-010).
    • +
    • Concept introduced, an additive member that gates a downstream side effect. [Rubric §9, API & Contract Design], [Rubric §11, Security]. The sixth positional member, bool EmailVerified = false (UserRegistered.cs:36-43), is what makes the BR-207 speaker auto-link safe rather than merely convenient: the type's own doc comment says nothing proves ownership of an address typed into the registration form, so a speaker profile may only be handed out when an external provider asserted the address as verified. Because the member is additive with a false default, an outbox row written before it existed, or one still in flight across a deploy, deserializes to the value that links nothing, so the rollout cannot silently grant auto-links it should not.
    • +
    • Walkthrough: six positional members (UserRegistered.cs:36-43): UserId, Email, FirstName, LastName, Role, EmailVerified. Email is the field the auto-link actually matches on; the two name fields exist so a subscriber can report candidates without a call back into Identity; EmailVerified is always false for a local password registration, whose address is unproven, and reflects the external provider's own assertion for an OAuth registration.
    • +
    • Why it's built this way: the ordering problem is the whole story. The id is a database-generated identity column, so the event cannot be raised inside User.Create (the factory remarks say so outright, MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:185-191): the id is still default at that point, and the outbox serializes the payload at capture time, so it would persist UserId = 0, which the cross-service Conference consumer cannot resolve (AuthenticationService.cs:31-38). Instead AuthenticationService wraps the base registration workflow in a single UnitOfWork.ExecuteInTransactionAsync (AuthenticationService.cs:80-87), then in OnUserRegisteredAsync raises the event on the already-persisted aggregate and saves a second time (AuthenticationService.cs:166-171). The first save populates the real id; the second save writes the outbox row; both are inside one transaction, so the user and the event commit atomically. The remarks on that override (AuthenticationService.cs:118-128) name the consequence honestly: this is eventual consistency, and the token handed back to the just-registered user does not yet carry the speaker_id claim. The same raise happens for brand-new external OAuth users, guarded by an isNewUser flag (AuthenticationService.cs:299-309), and it is that path that can populate EmailVerified = true.
    • +
    • Where it's used: consumed by Conference's UserRegisteredHandler, which runs the email-match speaker auto-link and publishes SpeakerLinkedToUser back so Identity can set User.LinkedSpeakerId; Identity registers the consumers for that return event in its own host (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:319-320).
    • +
    • Caveats / not-in-source: the type's own doc comment says the event is "Published by AuthenticationService AFTER the unit-of-work commit" (UserRegistered.cs:7-9), and the User.Create remarks repeat the phrasing (User.cs:186-190); neither still describes the mechanism. Current source raises it via AddDomainEvent on the aggregate after the first save, inside the transaction, so the outbox row commits with the insert (AuthenticationService.cs:133-134). The code is ground truth here; both doc comments predate the atomicity fix described at AuthenticationService.cs:31-41. Whether UserRegisteredHandler currently reads EmailVerified to gate the auto-link, or the flag is carried on the wire ahead of that consumer-side change, is not determinable from this unit's source.
    • +

    SendEmailConfirmationCommand

    MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.UseCases.SendEmailConfirmation · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/SendEmailConfirmation/SendEmailConfirmationCommand.cs:20 · Level 4 · record (sealed)

    @@ -2487,93 +2547,61 @@

    SendEmailConfirmationCommand

  • Walkthrough: a sealed record wrapping one SendEmailConfirmationRequest Request property.
  • Where it's used: raised by EmailConfirmationController and AuthController; handled by SendEmailConfirmationHandler.
  • -

    TokenPermissionGrants

    -
    -

    MMCA.ADC.Identity.Service · MMCA.ADC.Identity.Service.Authorization · MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Authorization/TokenPermissionGrants.cs:34 · Level 4 · class (static)

    -
    -
      -
    • What it is: the composition point that applies every module's permission-grant map to one host's shared PermissionRegistryBuilder, used by the Identity Service host that issues tokens for all four modules.
    • -
    • Depends on: PermissionRegistryBuilder, IdentityPermissionGrants, ConferencePermissionGrants, EngagementPermissionGrants, NotificationPermissionGrants; externals: Microsoft.Extensions.DependencyInjection.IServiceCollection.
    • -
    • Concept reinforced, an extension block as a DI registration surface. The workspace-wide convention of C# extension types for DI (CLAUDE.md) shows up here as extension(IServiceCollection services) (TokenPermissionGrants.cs:69) exposing AddTokenPermissionGrants().
    • -
    • Walkthrough: AddTokenPermissionGrants() (TokenPermissionGrants.cs:77-78) calls services.AddPermissions(ApplyAll). ApplyAll(PermissionRegistryBuilder permissions) (TokenPermissionGrants.cs:86-92) calls each module's own Apply(permissions) in turn: Identity, Conference, Engagement, Notification. Its doc (TokenPermissionGrants.cs:81-84) notes it is exposed separately so a test can build the same registry without booting a web host.
    • -
    • Why it's built this way: a token-issuing service needs to know every module's permission set, not just its own, so the token it mints carries claims another module's API can authorize against; the grant maps accumulate and union, so calling this alongside the Identity module's own registration is additive and order-independent (TokenPermissionGrants.cs:72-74).
    • -
    • Where it's used: registered by the Identity Service host's composition root.
    • -
    -

    UserRole

    +

    EngagementUserDataExportSection

    -

    MMCA.ADC.Identity.Domain · MMCA.ADC.Identity.Domain.Users · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/UserRole.cs:18 · Level 4 · class (sealed)

    +

    MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.UseCases.ExportUserData · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ExportUserData/EngagementUserDataExportSection.cs:19 · Level 4 · class (sealed)

      -
    • What it is: the closed set of ADC roles as a value object: Organizer, Attendee, and ContentEditor, with case-insensitive parsing, validation, and equality.
    • -
    • Depends on: RoleValue (the shared base), RoleNames (the string constants), Result and Error; externals: System.Collections.Frozen.FrozenDictionary.
    • -
    • Concept introduced, the closed-set value object over a persisted string. [Rubric §4, DDD] assesses whether a concept with rules gets a type instead of a bare primitive, and [Rubric §2, Design Patterns] covers the shared-base factoring. The base RoleValue supplies case-insensitive value equality, hashing, and validation once for both apps; this type only fixes the ADC role set, and the class remarks say so (UserRole.cs:12-17). Note what it is not: an enum. The User aggregate stores Role as a string (User.cs:71) so EF maps a plain column and a JWT claim round-trips without conversion, while every place that reasons about roles goes through this type. That combination, primitive at rest and typed in the domain, is the pattern ADR-068 describes.
    • -
    • Walkthrough
        -
      • The three static instances (UserRole.cs:21, :23, :30), each built from a RoleNames constant so the domain and the auth layer cannot drift apart. The ContentEditor doc (UserRole.cs:26-30) is worth reading: it defines the role as a strict capability subset of Organizer, able to curate sessions, speakers, and categories but not to change event structure, rooms, feedback questions, run session selection, or read the user list.
      • -
      • AllByValue (UserRole.cs:33-34), a FrozenDictionary<string, UserRole> built once by the base's BuildLookup (MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/RoleValue.cs:76). Frozen dictionaries trade a slower build for faster reads, which is exactly right for a table populated at type-init and then read on every authorization check ([Rubric §12, Performance & Scalability]).
      • -
      • All (UserRole.cs:45) exposes the values; the private constructor (UserRole.cs:36-39) makes the three statics the only instances that can exist.
      • -
      • FromString(string role) (UserRole.cs:52-59) is the Result-returning parse: a hit returns Result.Success, a miss returns Error.Invariant with the code User.Role.Invalid. role ?? string.Empty (UserRole.cs:53) means a null argument is a clean validation failure, not a NullReferenceException.
      • -
      • IsValid(string role) (UserRole.cs:66) is the boolean form used by UserInvariants.EnsureRoleIsValid, with the same null-to-empty coalesce.
      • -
      • IsOrganizer(string? role) (UserRole.cs:76) compares with StringComparison.OrdinalIgnoreCase, and its doc explains the trap it exists to avoid (UserRole.cs:68-75): a raw claim string may carry any casing, and comparing against Organizer.Value directly would compare ordinally, quietly failing on "organizer". This is an authorization-relevant helper, so the subtlety is load-bearing ([Rubric §11, Security]).
      • -
      • The equality surface: the two operators (UserRole.cs:78-81) and the three overrides that forward to the base (UserRole.cs:84, :86, :89), plus ToString() returning the canonical string form (UserRole.cs:94).
      • +
      • What it is: the Engagement contribution to a data-subject export: session bookmarks, submitted session questions, the points ledger, check-in history, and leaderboard participation, read from the Engagement service and projected into the export's DTOs.
      • +
      • Depends on: IUserDataExportSection, UserDataExportSectionResult, IUserEngagementExportService, UserDataExportEngagementSectionDTO and its four row types (UserDataExportBookmarkDTO, UserDataExportSubmittedQuestionDTO, UserDataExportPointsEntryDTO, UserDataExportCheckInDTO).
      • +
      • Concept reinforced, the export section (introduced at NotificationUserDataExportSection), here at its widest. [Rubric §30, Compliance, Privacy & Data Governance] and [Rubric §9, API & Contract Design]. Everything structural is identical to its sibling: a primary constructor over one peer interface, a constant SectionName (EngagementUserDataExportSection.cs:23), one ExportAsync, and no local try/catch (EngagementUserDataExportSection.cs:12-16). What differs is breadth, five collections instead of one, which is what makes its two projection comments worth reading.
      • +
      • Walkthrough: ExportAsync (EngagementUserDataExportSection.cs:26-71) makes a single peer call (:30-32) and then assembles one section DTO (:34-68).
          +
        • Bookmarks (:36-40) and SubmittedQuestions (:41-46) project scalar ids plus CreatedOn. No session or question titles are pulled across: Engagement stores ids, and the export is truthful about what Engagement actually holds.
        • +
        • PointsEntries (:47-55) converts ActivityType with .ToString(), and the comment says why (:49-50): the export document is read by the data subject, so an activity travels as its readable name rather than an enum number. That is a real API-design decision, because it means renaming an enum member changes an externally visible document. The row also carries Points and SubjectKey (:52-53), the scalar ledger facts.
        • +
        • CheckIns (:56-65) applies the same rule to Scope (:58-60) and carries the nullable EventId, SessionId, and SponsorId alongside CheckedInOn, which is what makes the three check-in scopes distinguishable in the output.
        • +
        • IsOnLeaderboard and LeaderboardDisplayName (:66-67) close the section. Those are the same two facts Engagement's UserDeletedPointsHandler erases on account deletion, which is the neat symmetry of this module: access and erasure operate on the identical set of data.
        • +
        • UserDataExportSectionResult.Complete(SectionName, data) (:70).
      • -
      • Why it's built this way: a FromString returning Result<UserRole> instead of throwing keeps role parsing on the same error channel as every other validation in the codebase (ADR-013), and keeping the role set in the Domain assembly means the invariant "a user's role is one of these three" is enforced where the aggregate lives, not at the API edge.
      • -
      • Where it's used: by UserInvariants.EnsureRoleIsValid (UserInvariants.cs:65); by User's EF constructor default and by CreateExternal, both writing UserRole.Attendee.Value (User.cs:163, :218); by AuthenticationService for the BR-45 attendee default on registration (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:150); by the seeder (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Infrastructure/Persistence/DbContexts/Seeding/IdentityModuleDbSeeder.cs:36-38); and by the two ownership-checked handlers whose privilege override is a one-liner, DeleteUserHandler (.../UseCases/DeleteUser/DeleteUserHandler.cs:42-43) and ExportUserDataHandler (.../UseCases/ExportUserData/ExportUserDataHandler.cs:38).
      • -
      • Caveats / not-in-source: there is no implicit string conversion on this type, so callers that need the persisted form write .Value explicitly (User.cs:163, AttendeeQueryService.cs:19). The role set here is three values, while the class remarks still describe the spec as defining two (UserRole.cs:12-14); the code is ground truth.
      • +
      • Why it's built this way: registration order is document order, and Engagement is registered first (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:44-49), with the comment above the two calls recording that this is deliberate rather than incidental. Stable ordering means an exported document can be diffed across runs.
      • +
      • Where it's used: the peer client is wired in the Identity service host with services.AddEngagementUserExportClient() as one step of the AddMmcaApplicationPipeline chain (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:313-316), which resolves to a gRPC adapter outside the Engagement process. The host comment states the coupling explicitly (Program.cs:293-300): both export clients are best-effort consumers, so a peer that stays unreachable degrades one section instead of failing the export, and Identity never hard-depends on either peer. Covered by MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/UseCases/EngagementUserDataExportSectionTests.cs.
      -

      UserInvariants

      +

      RegisterRequestValidator

      -

      MMCA.ADC.Identity.Domain · MMCA.ADC.Identity.Domain.Users · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/UserInvariants.cs:9 · Level 6 · class (static)

      +

      MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.Validation · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/Validation/RegisterRequestValidator.cs:12 · Level 7 · class (sealed)

        -
      • What it is: the rule book for the User aggregate: the field length constants plus one Ensure... method per invariant, each returning a Result.
      • -
      • Depends on: CommonInvariants (the shared primitive checks), Result / Error, UserRole; externals: System.Net.Mail.MailAddress.
      • -
      • Concept reinforced, the static invariants class. [Rubric §4, DDD] assesses whether invariants live with the aggregate that owns them, and [Rubric §1, SOLID] covers the single-responsibility split. The framework convention, introduced in group-02, is that an aggregate's factory does not contain its rules: the rules live in a static sibling class as independent, individually testable functions, and the factory composes them with Result.Combine so the caller gets every violation at once instead of the first one. User.Create is the worked example, combining six checks in a single call (User.cs:208-215).
      • -
      • Walkthrough
          -
        • Four public constants (UserInvariants.cs:12-21): FirstNameMaxLength and LastNameMaxLength at 100, EmailMaxLength at 100, DeviceFieldMaxLength at 256. They are public const for a reason: the Application-layer RegisterRequestValidator reuses them (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/Validation/RegisterRequestValidator.cs:16, :18-19) and the EF configuration sizes its columns from them (UserConfiguration at MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Infrastructure/Persistence/EntityConfiguration/UserConfiguration.cs:22, :27, :32, and on all seven device columns at :54, :58, :62, :66, :70, :74, :78), so the domain rule, the wire validation, and the schema cannot disagree.
        • -
        • EnsureEmailIsValid (UserInvariants.cs:23-43) is the only sequential (short-circuiting) check: not empty (:25-27), then within length (:29-31), then parseable by MailAddress.TryCreate, returning User.Email.InvalidFormat on the last (:33-40). The order matters because running a format parse on an empty string would report a confusing second error.
        • -
        • EnsureFirstNameIsValid / EnsureLastNameIsValid (UserInvariants.cs:45-53) each Result.Combine a non-empty check with a max-length check, so both problems surface together.
        • -
        • EnsurePasswordHashIsValid / EnsurePasswordSaltIsValid (UserInvariants.cs:55-59) delegate to CommonInvariants.EnsureBytesAreNotEmpty. They assert presence only: strength is a request-level concern (ChangePasswordRequestValidator) and the hash itself is opaque to the domain.
        • -
        • EnsureRoleIsValid (UserInvariants.cs:64-71) defers to UserRole.IsValid and returns User.Role.Invalid on a miss.
        • -
        • EnsurePreferredCultureIsValid and EnsurePreferredThemeIsValid (UserInvariants.cs:76-93) forward to the shared CommonInvariants equivalents, passing ADC's own error codes and messages. The allowlist of SupportedCultures (ADR-027) and the light/dark theme set (ADR-028) live in the framework; only the wording is app-specific.
        • +
        • What it is: the rule set for a registration request: a well-formed email within the domain's length limit, a strong password, required first and last names, and an optional address that is validated only when present.
        • +
        • Depends on: RegisterRequest, EmailRules<T>, StrongPasswordRules<T>, RequiredStringRules<T>, AddressValidator, UserInvariants; externals: FluentValidation.
        • +
        • Concept reinforced, composing shared rule sets, and tying them to the domain's constants. [Rubric §6, CQRS & Event-Driven], [Rubric §15, Best Practices & Code Quality]. This validator is almost entirely Include calls: the four reusable rule sets from MMCA.Common.Application.Validation carry the actual logic, and this class only chooses which property each applies to. The detail worth copying is that the length arguments are not literals: UserInvariants.EmailMaxLength, UserInvariants.FirstNameMaxLength, and UserInvariants.LastNameMaxLength (RegisterRequestValidator.cs:16,18,19) come straight from the aggregate's own rules. A request can therefore never be accepted at the edge and then rejected by the domain for a length the API thought was fine.
        • +
        • Walkthrough: one constructor (RegisterRequestValidator.cs:14-24).
            +
          • EmailRules<RegisterRequest>(x => x.Email, "Email", UserInvariants.EmailMaxLength) (RegisterRequestValidator.cs:16): presence, format, and length, with "Email" as the display name in messages.
          • +
          • StrongPasswordRules<RegisterRequest>(x => x.Password) (RegisterRequestValidator.cs:17), the same complexity policy ChangePasswordRequestValidator applies to a new password.
          • +
          • Two RequiredStringRules<RegisterRequest> for the names (RegisterRequestValidator.cs:18-19).
          • +
          • The conditional address rule (RegisterRequestValidator.cs:21-23): RuleFor(x => x.Address).SetValidator(new AddressValidator()!).When(x => x.Address is not null). When is what makes the address genuinely optional: omitting it is valid, supplying a malformed one is not.
        • -
        • Why it's built this way: every method takes a source string that becomes the error's source field, so a failure reports which operation raised it (nameof(Create), nameof(ChangePassword), nameof(UpdatePreferences)). That is why the same invariant can be shared by the create and change paths without losing diagnostic context ([Rubric §13, Observability & Operability]).
        • -
        • Where it's used: by User.Create (User.cs:210-214), User.CreateExternal (User.cs:258-259), User.UpdatePreferences (User.cs:333-334), and User.ChangePassword (User.cs:363-364); the length constants are reused by RegisterRequestValidator and UserConfiguration.
        • -
        • Caveats / not-in-source: EnsureEmailIsValid has no callers in current ADC source. The aggregate validates its address through the Email value object's own Create instead (User.cs:207, :207, :368), so the email invariant here is a second, currently unused expression of the same rule.
        • +
        • Why it's built this way: registration is anonymous and internet-facing, so it is the most hostile input surface in the module. Composing framework-owned rule sets means a tightening of the shared password policy applies here without a code change ([Rubric §11, Security]).
        • +
        • Where it's used: auto-registered as IValidator<RegisterRequest> by ScanModuleApplicationServices<ClassReference>() (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:53) and injected into AuthenticationValidators, which AuthenticationService receives through its base and runs at the top of the registration workflow. The endpoint is POST auth/register on AuthController.
        -

        User

        +

        ExportUserDataHandler

        -

        MMCA.ADC.Identity.Domain · MMCA.ADC.Identity.Domain.Users · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:34 · Level 7 · class (sealed)

        +

        MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.UseCases.ExportUserData · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ExportUserData/ExportUserDataHandler.cs:30 · Level 9 · class (sealed)

          -
        • What it is: the Identity aggregate root. One row per account, holding credentials, role, profile fields, optional MAUI device metadata, external-login identifiers, UI preferences, the avatar URL, email-confirmation and account-lock state, and the scalar link to a Conference Speaker.
        • -
        • Depends on: AuditableAggregateRootEntity<TIdentifierType>, IPasswordChangeableUser, IUserPreferences, IErasableUser (which extends IAnonymizable), IEmailConfirmableUser, IAuditedEntity, Email, UserRole, UserInvariants, UserPasswordChanged, UserDeleted, PiiAttribute, IdValueGeneratedAttribute, Result; externals: BCL only.
        • -
        • Concept introduced, the interface list as a workflow contract. [Rubric §4, DDD] assesses whether the aggregate is the consistency boundary and enforces its own invariants, and [Rubric §1, SOLID] assesses interface segregation: five narrow capability interfaces instead of one fat base (User.cs:34-35). The class remarks (User.cs:18-32) explain something genuinely easy to get wrong. The shared framework workflows are generic over capability interfaces: ChangePasswordHandlerBase<TUser, TCommand> constrains on IPasswordChangeableUser (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:40), the erasure workflow constrains on IErasableUser, and SendEmailConfirmationHandlerBase<TUser, TCommand> constrains on IEmailConfirmableUser (added for ADR-116). Listing IErasableUser on this type directly is load-bearing because Delete here hides the base soft-delete with new (User.cs:443); only re-declaring the interface on User re-maps the interface slot onto this type's own member, which is what keeps the UserDeleted domain event inside the shared erasure path. Remove the interface from the declaration list and the code still compiles while quietly calling the base method instead.
            -
          • IAuditedEntity (User.cs:35) is the one non-behavioural marker in the list, and the remarks justify it (User.cs:26-31): it opts the aggregate into a change history rather than just the last-writer audit fields. [Rubric §30, Compliance, Privacy & Data Governance]: account records are where a support or compliance question actually gets asked (who changed this attendee's email, when was this account raised to Organizer, who anonymized it), and LastModifiedOn/By alone answers only "who touched it last".
          • -
          • [IdValueGenerated] (User.cs:33) tells the persistence layer the id is database-generated. That single attribute is the root cause of the UserRegistered two-save dance: the id does not exist until after the insert, which the Create doc states outright (User.cs:185-191).
          • -
          -
        • -
        • Walkthrough
            -
          • State (User.cs:50-133). IsEmailConfirmed (User.cs:47) records whether the address has been proven reachable, either by redeeming a confirmation link or by an external provider asserting it (ADR-116). Email is an Email value object, not a string, and is marked [Pii] along with FirstName, LastName, and AvatarUrl; the [Pii] marks are what let the privacy tooling find personal columns (ADR-005). PasswordHash and PasswordSalt are byte[] mapped to varbinary(max), inside a scoped CA1819 suppression that names EF mapping as the reason. Role is a string for EF and JWT round-tripping while UserRole owns the rules. LinkedSpeakerId is a nullable SpeakerIdentifierType scalar, the 1:1 bidirectional counterpart of Speaker.LinkedUserId (BR-208). LockedOn (User.cs:491) records when an administrator locked the account, or null while usable; a locked account keeps every row it owns (bookmarks, feedback, points) and can be unlocked, which separates it from the irreversible Anonymize. Every setter is private: state changes only through the methods below.
          • -
          • What is deliberately absent: refresh tokens. The class summary states it: sessions are per-device rows in the framework's RefreshSessions table (BR-205/206), hashed at rest, not columns on this aggregate. [Rubric §8, Data Architecture]: a per-device session list cannot be modelled as one nullable token column on the account row, and moving it out is what lets a user revoke one device without touching the others.
          • -
          • Computed members: IsExternalLogin => LoginProvider is not null (User.cs:494); IsLocked => LockedOn is not null (User.cs:505), computed from LockedOn rather than stored so the state and the instant it began cannot disagree (the login and refresh paths refuse a locked account, and locking also revokes the account's live refresh sessions, so the lock takes effect within one access-token lifetime rather than at the next sign-in); HasLocalPassword => PasswordHash.Length > 0 (User.cs:515), which the external-login flow reads to decide whether an OAuth identity may be attached by email match alone (an account whose password someone already holds must be claimed by signing in with that password first); and FullName (User.cs:518). All are derived rather than stored so they cannot go stale.
          • -
          • Constructors (User.cs:520-544): a private parameterless one for EF that initializes the non-nullable members to safe defaults, including Role = UserRole.Attendee.Value (:527), and a private full one used by the factories.
          • -
          • Create (User.cs:563-591): parses the email, then Result.Combines the email result with five UserInvariants checks, returns Result.Failure<User>(result.Errors) on any failure, and otherwise constructs with Id = default.
          • -
          • CreateExternal (User.cs:611-639): the OAuth path. It validates email and names only, sets PasswordHash/PasswordSalt to empty arrays and forces UserRole.Attendee.Value, and records LoginProvider/ProviderKey. A new emailVerified parameter (default false, :617) sets IsEmailConfirmed from it (:635): a provider assertion is exactly the proof the confirmation email exists to collect, so an account created from one starts confirmed and is never asked to prove it again; GitHub's OAuth flow asserts nothing, so those accounts start unconfirmed like a local registration. The password invariants are deliberately skipped because an external account has no local password.
          • -
          • ConfirmEmail (User.cs:647-651): sets IsEmailConfirmed = true and returns success. Idempotent: confirming an already-confirmed account is a no-op success, which at-least-once delivery of the confirmation link requires.
          • -
          • Behavior: LinkExternalProvider (User.cs:664-669) attaches a provider to an existing local account and also sets IsEmailConfirmed = true (:668), since the link is only ever made after the provider asserted the address as verified (the caller enforces that; it is the account-takeover guard), so the assertion also settles the confirmation question for an account that had never answered it; LinkSpeaker / UnlinkSpeaker (User.cs:676-684) maintain the BR-207/BR-209 scalar cross-context link; UpdateDeviceMetadata sets the seven MAUI fields in one call; SetAvatarUrl records or clears the avatar URL and nothing else, with the doc drawing the boundary explicitly: size, format sniffing, and re-encoding belong to the upload use case, and the domain only stores the resulting public URL.
          • -
          • Lock(DateTime utcNow) / Unlock(): Lock is idempotent via LockedOn ??= utcNow, so locking an already-locked account keeps the original instant rather than resetting it, preserving the audit trail's "when the lock began". Unlock sets LockedOn = null and is a no-op on an account that is not locked.
          • -
          • ChangeRole(string role) (BR-41, BR-45): validates through UserInvariants.EnsureRoleIsValid, then assigns the role's canonical spelling via UserRole.FromString(role).Value!.Value rather than the caller's casing, since the role column is compared and filtered ordinally elsewhere (the user list's role filter, the JWT role claim). A no-op when the account already holds the role.
          • -
          • UpdatePreferences: combines the culture and theme invariants and assigns only after both pass, so a rejected theme cannot leave a half-applied culture.
          • -
          • ChangePassword: validates the new hash and salt, assigns, and raises UserPasswordChanged via AddDomainEvent.
          • -
          • Delete: the new hiding method. It calls base.Delete() and raises UserDeleted only when that succeeded. Its doc answers the obvious question about outstanding sessions: they are not touched here and do not need to be, because the refresh flow re-fetches the account through the soft-delete query filter, so every session stops working the moment this commits.
          • -
          • Anonymize (span ends User.cs:504): the erasure half. It builds the placeholder address deleted-{Id}@anonymized.invalid with CultureInfo.InvariantCulture, and the id embedded in the address is what keeps the unique-email invariant (BR-200) satisfiable across many erased accounts. It is idempotent by construction: if the current email already equals the placeholder there is nothing left to erase and it returns success. Otherwise it overwrites the email, names, credentials, all seven device fields, both external-login fields, and the avatar URL, and now also resets IsEmailConfirmed = false (User.cs:387-389): the placeholder address is not reachable and was never proven, so the flag cannot keep asserting that it was.
          • +
          • What it is: ADC's data-subject export handler. It is a thin subclass of the framework's export workflow that answers exactly two app-specific questions: which role may export somebody else's account, and which of the account's own fields count as portable personal data.
          • +
          • Depends on: ExportUserDataHandlerBase<TUser, TQuery>, User, ExportUserDataQuery, UserRole, UserDataExportSubjectDTO, IUnitOfWork, IUserDataExportSection; externals: TimeProvider, ILogger<T>.
          • +
          • Concept introduced, the template-method handler where the app supplies only its vocabulary. [Rubric §30, Compliance, Privacy & Data Governance] (assesses whether access and portability are implemented once and identically everywhere), [Rubric §2, Design Patterns] (template method), and [Rubric §1, SOLID] (the base is closed for modification and open through three protected hooks). The base (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ExportUserData/ExportUserDataHandlerBase.cs:49) owns the whole workflow: the owner-or-privileged-role check via UserOwnershipRule.CheckOwnership, returning a User.ExportForbidden error (ExportUserDataHandlerBase.cs:80-90), a no-tracking load through GetReadRepository with Error.NotFound when the account is gone (:92-98), the subject snapshot, the section fan-out (:104-108), and the envelope stamped with CurrentFormatVersion "1.0" and a GeneratedOn from TimeProvider (:61, :110-117). Two of those choices carry real weight. The fan-out is sequential on purpose, because sections share the scoped unit of work and its DbContext, which is not thread-safe, and because registration order is the published order of the document (:102-103). And each section runs inside its own try/catch (:173-198) that degrades a failing section to Available = false with a deliberately generic reason, sending the exception detail to the log and never to the subject (:185-197); OperationCanceledException is explicitly excluded, because a cancelled request is not a degraded one.
          • +
          • Walkthrough: a primary constructor forwarding four dependencies to the base (ExportUserDataHandler.cs:30-35) and two overrides.
              +
            • HasExportPrivilege(string? currentUserRole) => UserRole.IsOrganizer(currentUserRole) (ExportUserDataHandler.cs:38). One line, and the case-insensitive comparison lives inside UserRole.IsOrganizer rather than here, so a claim with unexpected casing cannot silently deny an organizer (a test pins the lowercase "organizer" case, MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/UseCases/ExportUserData/ExportUserDataHandlerTests.cs:110).
            • +
            • BuildSubjectSnapshotAsync (ExportUserDataHandler.cs:41-77) guards its argument (:46) and projects the aggregate into a UserDataExportSubjectDTO: identity and profile fields, IsExternalLogin and LoginProvider, LinkedSpeakerId, AvatarUrl, the seven MAUI device fields, and the two audit timestamps (:48-73). What is absent is the point: no PasswordHash, no PasswordSalt, no refresh token, no external ProviderKey. The class doc states the principle (ExportUserDataHandler.cs:16-19): credentials are secrets, not portable personal data, and a portability right is not a right to a copy of your own password hash.
            • +
            • The timestamp handling is the subtle bit (ExportUserDataHandler.cs:67-73). SQL Server hands audit timestamps back as Kind=Unspecified, and the DTO documents them as UTC but serializes them without a Z marker in that state, so DateTime.SpecifyKind(..., DateTimeKind.Utc) only restores the marker on a value that was already UTC. LastModifiedOn is null-checked first, since a never-updated account has none.
            • +
            • The method returns Task.FromResult<object?>(subject) (:76): the hook is asynchronous because some apps read a second aggregate for the snapshot, and ADC does not need to.
          • -
          • Why it's built this way: Delete and Anonymize are separate operations, and the doc on Anonymize says exactly why: the row survives so cross-context scalar references (bookmarks, notifications) and the audit trail do not break, which is the anonymize-in-place policy of ADR-005. It is also why a re-registration with an erased account's original email succeeds by design. The avatar comment draws the matching line for storage: the domain nulls the URL, and the use case, which knows the blob boundary, deletes the file (ADR-045). IsEmailConfirmed and the lock/role additions exist because ADC opted into the framework's optional Identity completions (ADR-116): ADC sends the confirmation but does not gate sign-in on it (Authentication:EmailConfirmation:RequireConfirmedEmail is false), because an attendee who registers on the conference floor must be able to use the app immediately; the flag is what an organizer reads before trusting an address, and what a later decision to gate would key off without a second migration.
          • -
          • Where it's used: persisted by UserConfiguration, projected by UserDTOMapper, driven by AuthenticationService and the Users use cases (ChangePasswordHandler, ChangePreferencesHandler, DeleteUserHandler, ExportUserDataHandler, SetUserAvatarHandler, RemoveUserAvatarHandler, SendEmailConfirmationHandler, and the reset-password path behind PasswordResetController), mutated on the cross-context link by SpeakerLinkedToUserHandler and SpeakerUnlinkedFromUserHandler, and read by GetUsersHandler.
          • -
          • Caveats / not-in-source: Anonymize leaves Role, PreferredCulture, PreferredTheme, and LinkedSpeakerId untouched, so an erased account keeps its role, its UI preferences, and any speaker link. Those are treated as non-identifying here; nothing in this file states that judgement, so it is a behavior to notice rather than a documented decision.
          • +
          • Why it's built this way: the handler needs no DI registration of its own. ScanModuleApplicationServices<ClassReference>() finds it through the base class's IQueryHandler<TQuery, Result<UserDataExportDTO>> implementation, which both the Identity DI comment (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/DependencyInjection.cs:46-47) and the framework's own registration remarks record (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.Extensibility.cs:32-35), and a dedicated test pins it (MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/UseCases/ExportUserData/ExportUserDataRegistrationTests.cs:29). Best-effort section aggregation is the same posture the module takes for the best-effort live-channel publish (ExportUserDataHandler.cs:23-28): one peer outage costs the subject one section, never the whole document ([Rubric §29, Resilience & Business Continuity]).
          • +
          • Where it's used: injected into UsersDataExportController as IQueryHandler<ExportUserDataQuery, Result<UserDataExportDTO>> (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersDataExportController.cs:27) and invoked from the inherited GET Users/{userId}/export action, which the framework's DataExportControllerBase<TQuery> declares [Authorize] and feature-gates on PrivacyFeatures.DataExport (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:59-60, action at :77-82), returning the document as a file download (:109). Covered by MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Application.Tests/Users/UseCases/ExportUserDataHandlerTests.cs and, at the controller edge, MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.API.Tests/Controllers/UsersDataExportControllerTests.cs.
          • +
          • Caveats / not-in-source: ADC does not override OnExportCompletedAsync, the base's post-assembly hook for an access-log row or a metric (ExportUserDataHandlerBase.cs:159), so an export leaves no application-level audit record beyond ordinary request logging.

          SendEmailConfirmationHandler

          @@ -2606,6 +2634,40 @@

          UserService

        • Why it's built this way: manual query-string assembly, rather than a typed query object, keeps the client honest about what the API actually accepts. Returning Result rather than throwing is what lets the pages branch: Profile uses TryGetValue on the avatar read (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Pages/Users/Profile/Profile.razor.cs:72), IsSuccess on the avatar removal (Profile.razor.cs:182), and IsFailure on the account delete (Profile.razor.cs:260), with no try/catch in any of the three.
        • Where it's used: registered as IUserUIService in AddIdentityUI (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/DependencyInjection.cs:39) and consumed by UserList (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Pages/Users/UserList.razor.cs:58,72,81) and Profile (Profile.razor.cs:166) through the interface, so the same code runs on Blazor Server, WebAssembly, and MAUI.
        +

        TokenPermissionGrants

        +
        +

        MMCA.ADC.Identity.Service · MMCA.ADC.Identity.Service.Authorization · MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Authorization/TokenPermissionGrants.cs:34 · Level 4 · class (static)

        +
        +
          +
        • What it is: the composition point that applies every module's permission-grant map to one host's shared PermissionRegistryBuilder, used by the Identity Service host that issues tokens for all four modules.
        • +
        • Depends on: PermissionRegistryBuilder, IdentityPermissionGrants, ConferencePermissionGrants, EngagementPermissionGrants, NotificationPermissionGrants; externals: Microsoft.Extensions.DependencyInjection.IServiceCollection.
        • +
        • Concept reinforced, an extension block as a DI registration surface. The workspace-wide convention of C# extension types for DI (CLAUDE.md) shows up here as extension(IServiceCollection services) (TokenPermissionGrants.cs:36) exposing AddTokenPermissionGrants().
        • +
        • Walkthrough: AddTokenPermissionGrants() (TokenPermissionGrants.cs:44-45) calls services.AddPermissions(ApplyAll). ApplyAll(PermissionRegistryBuilder permissions) (TokenPermissionGrants.cs:53-59) calls each module's own Apply(permissions) in turn: Identity, Conference, Engagement, Notification. Its doc (TokenPermissionGrants.cs:48-51) notes it is exposed separately so a test can build the same registry without booting a web host.
        • +
        • Why it's built this way: a token-issuing service needs to know every module's permission set, not just its own, so the token it mints carries claims another module's API can authorize against; the grant maps accumulate and union, so calling this alongside the Identity module's own registration is additive and order-independent (TokenPermissionGrants.cs:38-42).
        • +
        • Where it's used: registered by the Identity Service host's composition root.
        • +
        +

        UserRole

        +
        +

        MMCA.ADC.Identity.Domain · MMCA.ADC.Identity.Domain.Users · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/UserRole.cs:18 · Level 4 · class (sealed)

        +
        +
          +
        • What it is: the closed set of ADC roles as a value object: Organizer, Attendee, and ContentEditor, with case-insensitive parsing, validation, and equality.
        • +
        • Depends on: RoleValue (the shared base), RoleNames (the string constants), Result and Error; externals: System.Collections.Frozen.FrozenDictionary.
        • +
        • Concept introduced, the closed-set value object over a persisted string. [Rubric §4, DDD] assesses whether a concept with rules gets a type instead of a bare primitive, and [Rubric §2, Design Patterns] covers the shared-base factoring. The base RoleValue supplies case-insensitive value equality, hashing, and validation once for both apps; this type only fixes the ADC role set, and the class remarks say so (UserRole.cs:12-17). Note what it is not: an enum. The User aggregate stores Role as a string (User.cs:71) so EF maps a plain column and a JWT claim round-trips without conversion, while every place that reasons about roles goes through this type. That combination, primitive at rest and typed in the domain, is the pattern ADR-068 describes.
        • +
        • Walkthrough
            +
          • The three static instances (UserRole.cs:21, :23, :30), each built from a RoleNames constant so the domain and the auth layer cannot drift apart. The ContentEditor doc (UserRole.cs:26-30) is worth reading: it defines the role as a strict capability subset of Organizer, able to curate sessions, speakers, and categories but not to change event structure, rooms, feedback questions, run session selection, or read the user list.
          • +
          • AllByValue (UserRole.cs:33-34), a FrozenDictionary<string, UserRole> built once by the base's BuildLookup (MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/RoleValue.cs:76). Frozen dictionaries trade a slower build for faster reads, which is exactly right for a table populated at type-init and then read on every authorization check ([Rubric §12, Performance & Scalability]).
          • +
          • All (UserRole.cs:45) exposes the values; the private constructor (UserRole.cs:36-39) makes the three statics the only instances that can exist.
          • +
          • FromString(string role) (UserRole.cs:52-59) is the Result-returning parse: a hit returns Result.Success, a miss returns Error.Invariant with the code User.Role.Invalid. role ?? string.Empty (UserRole.cs:53) means a null argument is a clean validation failure, not a NullReferenceException.
          • +
          • IsValid(string role) (UserRole.cs:66) is the boolean form used by UserInvariants.EnsureRoleIsValid, with the same null-to-empty coalesce.
          • +
          • IsOrganizer(string? role) (UserRole.cs:76) compares with StringComparison.OrdinalIgnoreCase, and its doc explains the trap it exists to avoid (UserRole.cs:68-75): a raw claim string may carry any casing, and comparing against Organizer.Value directly would compare ordinally, quietly failing on "organizer". This is an authorization-relevant helper, so the subtlety is load-bearing ([Rubric §11, Security]).
          • +
          • The equality surface: the two operators (UserRole.cs:78-81) and the three overrides that forward to the base (UserRole.cs:84, :86, :89), plus ToString() returning the canonical string form (UserRole.cs:94).
          • +
          +
        • +
        • Why it's built this way: a FromString returning Result<UserRole> instead of throwing keeps role parsing on the same error channel as every other validation in the codebase (ADR-013), and keeping the role set in the Domain assembly means the invariant "a user's role is one of these three" is enforced where the aggregate lives, not at the API edge.
        • +
        • Where it's used: by UserInvariants.EnsureRoleIsValid (UserInvariants.cs:65); by User's EF constructor default and by CreateExternal, both writing UserRole.Attendee.Value (User.cs:163, :218); by AuthenticationService for the BR-45 attendee default on registration (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/AuthenticationService.cs:146); by the seeder (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Infrastructure/Persistence/DbContexts/Seeding/IdentityModuleDbSeeder.cs:36-38); and by the two ownership-checked handlers whose privilege override is a one-liner, DeleteUserHandler (.../UseCases/DeleteUser/DeleteUserHandler.cs:42-43) and ExportUserDataHandler (.../UseCases/ExportUserData/ExportUserDataHandler.cs:38).
        • +
        • Caveats / not-in-source: there is no implicit string conversion on this type, so callers that need the persisted form write .Value explicitly (User.cs:163, AttendeeQueryService.cs:19). The role set here is three values, while the class remarks still describe the spec as defining two (UserRole.cs:12-14); the code is ground truth.
        • +

        DeleteAvatarBlobInternalCommand

        MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.UseCases.DeleteAvatarBlob · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/DeleteAvatarBlob/DeleteAvatarBlobInternalCommand.cs:17 · Level 4 · record (sealed)

        @@ -2668,67 +2730,6 @@

        EmailConfirmationController

      • Why it's built this way: both actions end in the same result.IsFailure ? HandleFailure(result.Errors) : ... tail every controller in the module uses, so a validation or not-found failure becomes the shared ProblemDetails shape without a bespoke branch here.
      • Where it's used: registration schedules a SendEmailConfirmationCommand from AuthController after a successful POST register (ADR-116); the confirmation page then calls confirm-email with the token from the emailed link. The architecture suite's anonymous-endpoint test enumerates both actions explicitly.
      -

      Profile

      -
      -

      MMCA.ADC.Identity.UI · MMCA.ADC.Identity.UI.Pages.Users.Profile · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Pages/Users/Profile/Profile.razor.cs:18 · Level 6 · class (partial)

      -
      -
        -
      • What it is: the code-behind for the "My Profile" page at /profile: the signed-in user's self-service surface, namely set or remove the avatar, change the password, reach the signed-in-devices page, and delete the account.
      • -
      • Depends on: IUserUIService, IAuthUIService, IToastService, IMediaPickerService and PickedMedia, plus Blazor's AuthenticationStateProvider, NavigationManager, InputFileChangeEventArgs, and MudBlazor's MudForm / MudMessageBox (Profile.razor.cs:1-25). The localizer L is injected in the markup half (Profile.razor:4), which is also where the route and the [Authorize] attribute live (Profile.razor:1-2).
      • -
      • Concept introduced (1), the cross-head capability branch. [Rubric §22, Responsive & Cross-Browser] assesses whether one component genuinely serves different hosts. The same page runs in the Blazor web head and inside the MAUI hybrid, and the avatar affordance differs: the markup branches on MediaPicker.IsSupported (Profile.razor:47) to offer "choose photo" plus "take photo" buttons on a native head, and falls back to a hidden <InputFile accept="image/jpeg,image/png,image/webp"> behind a label-styled button on the web (Profile.razor:60-66). Both branches converge on one private method, UploadAvatarStreamAsync (Profile.razor.cs:164-175), so the upload contract is written once. That convergence is also why IUserUIService's upload method is typed on a raw Stream rather than on a framework-specific file type (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Services/IUserUIService.cs:47).
      • -
      • Concept introduced (2), the component-scoped CancellationTokenSource. [Rubric §18, UI Architecture]. The page holds private readonly CancellationTokenSource _cts = new() (Profile.razor.cs:30), passes _cts.Token into every service call, and cancels plus disposes it through the standard disposable pattern (Profile.razor.cs:280-298). Every async entry point then catches OperationCanceledException separately from Exception and deliberately does nothing (Profile.razor.cs:78-81, :113-116, :150-153, :192-195, :235-238, :270-273), with the comment naming the two causes: component disposal and an InteractiveAuto render-mode transition. A user who navigated away must not be shown a red error toast for a request that was cancelled on their behalf; a genuine failure still toasts.
      • -
      • Concept reinforced, the Result-returning UI service. [Rubric §24, Forms, Validation & UX Safety]. Every call into IUserUIService returns a Result, and the page reads it with TryGetValue or IsSuccess rather than with a null check (Profile.razor.cs:72, :167, :182, :260). A failed read and a successful read of nothing are therefore different states, which is what lets the avatar load stay silent on failure while an avatar upload failure toasts.
      • -
      • Walkthrough
          -
        • OnInitializedAsync (Profile.razor.cs:58-90): awaits AuthStateProvider.GetAuthenticationStateAsync() and reads the subject with the shared GetUserId() claims helper (:66), whose comment records that it accepts both the sub and the nameidentifier form and parses invariantly. The page identifies its subject from the token, never from a route parameter, which is what makes it structurally incapable of acting on another account. Only when the claim parses does it fetch the avatar URL (:72-75), and a failed avatar read is deliberately silent (:70-71): the card falls back to its person icon rather than greeting the user with an error. _isLoading is cleared in finally (:86-89).
        • -
        • Client-side avatar guard (:27-28, :135-139): MaxAvatarBytes = 2 * 1024 * 1024, and the browser path rejects an oversized file with a warning toast before any bytes leave the machine. The doc comment says exactly what this is: a mirror of the server's BR-116a limit, not the enforcement point. The server re-checks in UsersController (UsersController.cs:92-98) and again in SetUserAvatarHandler.
        • -
        • Avatar paths: PickAvatarAsync and CaptureAvatarAsync (:96, :98) both funnel through UploadPickedMediaAsync (:100-125), which disposes the PickedMedia with using (:105) and returns silently when the user cancelled the picker (media is null, :106-109). The browser path OnBrowserInputChangedAsync (:127-162) opens the stream with file.OpenReadStream(MaxAvatarBytes, _cts.Token) (:144) and wraps it in await using so the read stream is released even on failure. UploadAvatarStreamAsync (:164-175) toasts on a failed result and only assigns _avatarUrl on success (:173-174).
        • -
        • RemoveAvatarAsync (:177-200): the failure branch of the result toasts the same message the page would show for a thrown exception (:189), so the user sees one consistent outcome for "did not work".
        • -
        • Password form (:37-52, :202-243): two local validators, ValidateNewPassword (minimum length 8, :46-47) and ValidateConfirmPassword (ordinal equality against _newPassword, :49-52), both returning null for an empty value so the field's own Required rule owns the "missing" message instead of stacking two errors, exactly as the comment above them states (:43-45). SavePasswordAsync runs form.ValidateAsync() and returns early when invalid (:211-215), so a shape error costs no round trip; the errors also render through the shared ErrorSummary component, which deduplicates and localizes them (Profile.razor:100-102). On success it resets the form and clears all three fields (:224-228). [Rubric §24, Forms, Validation & UX Safety]: this is client-side validation as an ergonomics layer over, never instead of, the server rules that live in ChangePasswordRequestValidator and ChangePasswordHandler.
        • -
        • Signed-in devices (Profile.razor:112-128): a card whose only content is a link to RoutePaths.Sessions (:124). The comment above it records why there is no logic here (:112-113): the sessions page itself ships from MMCA.Common, so this card is only the way in from the profile.
        • -
        • DeleteAccountAsync (:245-278): guards on a known _userId (:247-251), shows the MudMessageBox (Profile.razor:148-158) and aborts unless the answer is exactly true (confirmed is not true, :253-255, so cancel and dismiss both abort), then deletes, calls AuthService.LogoutAsync(), toasts, and navigates to / with forceLoad: true (:266-268). The forced load matters: the account is gone, so the circuit and every piece of cached client state must be rebuilt from scratch rather than kept alive against a dead identity.
        • -
        -
      • -
      • Why it's built this way: keeping avatar, password, sessions, and deletion on one page mirrors the account-level scope of all four (each acts on the caller and only the caller), and every action ends in a toast with a localized message, so the page needs no bespoke error surface. [Rubric §21, Accessibility]: the markup carries an explicit aria-label on the MudAvatar (Profile.razor:33) because MudBlazor renders role="img", which the axe role-img-alt rule requires to carry an accessible name even in the fallback-icon state, and the comment above it says so (Profile.razor:30-32).
      • -
      • Where it's used: routed at /profile for any authenticated user in both the web and MAUI heads. Its server round trips land on the Identity avatar endpoints on UsersController, AuthController's PUT password, and UsersController's DELETE {userId}.
      • -
      • Caveats / not-in-source: the 8-character minimum in ValidateNewPassword is a local literal (Profile.razor.cs:47); it does not reference the shared strong-password rule set the server applies, so the two can drift and only a server rejection would surface the difference.
      • -
      -

      UserDetail

      -
      -

      MMCA.ADC.Identity.UI · MMCA.ADC.Identity.UI.Pages.Users.UserDetail · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Pages/Users/UserDetail/UserDetail.razor.cs:22 · Level 6 · class (sealed)

      -
      -
        -
      • What it is: the code-behind for the organizer account-detail page: lock or unlock an account and change its role, one user at a time.
      • -
      • Depends on: IUserAdminUIService<TUserDto> (closed over UserAdminDTO), IAppDialogService, IToastService, RoleNames; externals: Blazor's AuthenticationStateProvider, BreadcrumbItem.
      • -
      • Concept introduced, the self-action guard on an administration page. [Rubric §11, Security], [Rubric §24, Forms, Validation & UX Safety]. CanAdminister (UserDetail.razor.cs:131-134) compares the account on screen against the caller's own id, read from the authentication state during OnInitializedAsync (:145-147), and the two admin actions are enabled only when it is true. An organizer locking or role-changing their own account mid-session is the trap this closes: the page never lets the operator submit that request in the first place.
      • -
      • Walkthrough
          -
        • OnInitializedAsync (:137-147): builds the breadcrumb trail and reads _currentUserId from AuthStateProvider.GetAuthenticationStateAsync().
        • -
        • OnParametersSetAsync (:154): calls LoadAsync on every parameter set rather than only on first render, so navigating from one account to another inside the same page instance reloads the account named in the URL, the same pattern UserList's sibling pages rely on.
        • -
        • LoadAsync (:156-165): fetches the account through UserAdminService.GetAsync(Id, ...) and seeds _selectedRole from the loaded user, falling back to RoleNames.Attendee when the fetch failed.
        • -
        • ToggleLockAsync (:167-207): confirms through Dialogs.ConfirmAsync with a lock- or unlock-worded message built from the account's own email, then calls UserAdminService.LockAsync or UnlockAsync, toasts the outcome, and reloads on success. _isBusy is set before the call and cleared in finally (:187, :203-206).
        • -
        • ChangeRoleAsync (:209-244): a no-op when the selected role already matches the account's role (:211-214, ordinal comparison), otherwise the same confirm-then-call-then-toast-then-reload shape as ToggleLockAsync, through UserAdminService.SetRoleAsync.
        • -
        • Dispose (:247-252): cancels and disposes the page's CancellationTokenSource and suppresses finalization.
        • -
        -
      • -
      • Why it's built this way: AssignableRoles (:106-107) is [RoleNames.Attendee, RoleNames.ContentEditor, RoleNames.Organizer], least to most privileged, taken from the domain's own role vocabulary so the menu never drifts from the roles the server actually accepts. Both mutating actions confirm before acting, the same destructive-action discipline UserList's delete uses.
      • -
      • Where it's used: routed from UserList as the per-account detail page in the ADC web and MAUI UI; the account it edits is read and written through IUserAdminUIService<UserAdminDTO>, the framework's shared administration client (ADR-116), which calls the API surface UsersAdminController exposes.
      • -
      -

      UserList

      -
      -

      MMCA.ADC.Identity.UI · MMCA.ADC.Identity.UI.Pages.Users · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Pages/Users/UserList.razor.cs:24 · Level 6 · class (partial)

      -
      -
        -
      • What it is: the code-behind for the organizer user list page at /users, a thin shell over the framework's shared UserAdminList<TUser> (ADR-116), which renders the roster, the mobile cards, and the administration actions (lock, unlock, change role) against the framework's Admin/Users endpoints. ADC supplies what stays its own: the route, the Organizer authorization, the BR-51 roster endpoint, the extra name and creation columns, the role vocabulary, and delete (UC-21).
      • -
      • Depends on: UserAdminList<TUser>, IUserUIService, UserListDTO, RoleNames, Result.
      • -
      • Concept introduced, splitting the roster read from the administration write. [Rubric §18, UI Architecture], [Rubric §7, Microservices Readiness]. The class doc's <remarks> (UserList.razor.cs:14-19) states the reasoning directly: the roster keeps reading through IUserUIService rather than the framework's administration read service, because ADC's /Users endpoint is the one that has always offered per-column filters and server-side sorting; the three account actions (lock, unlock, change role) go through the framework's client either way, since those are identical in every app. The class was previously its own DataGridListPageBase<UserListDTO> subclass with its own grid, mobile list, search, and delete wiring; that machinery moved into UserAdminList<TUser> and this class shrank to ComponentBase plus one data-fetch method.
      • -
      • Walkthrough
          -
        • AssignableRoles (UserList.razor.cs:29-30): the same least-to-most-privileged role array UserDetail carries, kept here because the base's inline role editor needs it for this list.
        • -
        • FetchPageAsync (:45-69): the one method ADC still owns. It maps the base's generic filter bag onto the four filters IUserUIService.GetPagedAsync takes (BR-51). The free-text search box arrives under UserAdminList<UserListDTO>.SearchFilterKey and wins over an explicit Email column filter (:56-57), because both affordances resolve to the one email parameter.
        • -
        • FilterValue (:72-76): reads one filter's value out of the bag by key; the operator half of the tuple is unused, because every filter here is treated as a value match or a search term, not a MudBlazor comparison operator.
        • -
        -
      • -
      • Why it's built this way: nameof(UserListDTO.Email) and its three siblings, rather than string literals, keep the filter keys tied to the DTO's own property names, so a rename is a compile error rather than a silently dead filter.
      • -
      • Where it's used: routed as the organizer user-management page in the ADC web and MAUI UI; the server side it calls for the roster is UsersController, whose list endpoint is gated on the UsersRead capability in IdentityPermissions (UsersController.cs:139); the lock/unlock/role actions it delegates to the base call UsersAdminController. RoleList reuses UserList.razor.cs as a reference for the same list-shell pattern applied to roles.
      • -

      UsersAdminController

      MMCA.ADC.Identity.API · MMCA.ADC.Identity.API.Controllers · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersAdminController.cs:25 · Level 6 · class (sealed)

      @@ -2741,6 +2742,27 @@

      UsersAdminController

    • Why it's built this way: the override bodies add nothing; the class is a place to attach an idempotency declaration the base's generic action cannot carry per-app, since the reasoning text is specific to what "already idempotent in the domain" means for this aggregate.
    • Where it's used: mounted by the Identity service host, routed through the Gateway under Admin/Users; driven by UserDetail and UserList through the framework's IUserAdminUIService<TUserDto> client. AdminRolesController is the equivalent surface for roles.
    +

    UserInvariants

    +
    +

    MMCA.ADC.Identity.Domain · MMCA.ADC.Identity.Domain.Users · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/UserInvariants.cs:9 · Level 6 · class (static)

    +
    +
      +
    • What it is: the rule book for the User aggregate: the field length constants plus one Ensure... method per invariant, each returning a Result.
    • +
    • Depends on: CommonInvariants (the shared primitive checks), Result / Error, UserRole; externals: System.Net.Mail.MailAddress.
    • +
    • Concept reinforced, the static invariants class. [Rubric §4, DDD] assesses whether invariants live with the aggregate that owns them, and [Rubric §1, SOLID] covers the single-responsibility split. The framework convention, introduced in group-02, is that an aggregate's factory does not contain its rules: the rules live in a static sibling class as independent, individually testable functions, and the factory composes them with Result.Combine so the caller gets every violation at once instead of the first one. User.Create is the worked example, combining six checks in a single call (User.cs:208-215).
    • +
    • Walkthrough
        +
      • Four public constants (UserInvariants.cs:12-21): FirstNameMaxLength and LastNameMaxLength at 100, EmailMaxLength at 100, DeviceFieldMaxLength at 256. They are public const for a reason: the Application-layer RegisterRequestValidator reuses them (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/Validation/RegisterRequestValidator.cs:16, :18-19) and the EF configuration sizes its columns from them (UserConfiguration at MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Infrastructure/Persistence/EntityConfiguration/UserConfiguration.cs:22, :27, :32, and on all seven device columns at :54, :58, :62, :66, :70, :74, :78), so the domain rule, the wire validation, and the schema cannot disagree.
      • +
      • EnsureEmailIsValid (UserInvariants.cs:23-43) is the only sequential (short-circuiting) check: not empty (:25-27), then within length (:29-31), then parseable by MailAddress.TryCreate, returning User.Email.InvalidFormat on the last (:33-40). The order matters because running a format parse on an empty string would report a confusing second error.
      • +
      • EnsureFirstNameIsValid / EnsureLastNameIsValid (UserInvariants.cs:45-53) each Result.Combine a non-empty check with a max-length check, so both problems surface together.
      • +
      • EnsurePasswordHashIsValid / EnsurePasswordSaltIsValid (UserInvariants.cs:55-59) delegate to CommonInvariants.EnsureBytesAreNotEmpty. They assert presence only: strength is a request-level concern (ChangePasswordRequestValidator) and the hash itself is opaque to the domain.
      • +
      • EnsureRoleIsValid (UserInvariants.cs:64-71) defers to UserRole.IsValid and returns User.Role.Invalid on a miss.
      • +
      • EnsurePreferredCultureIsValid and EnsurePreferredThemeIsValid (UserInvariants.cs:76-93) forward to the shared CommonInvariants equivalents, passing ADC's own error codes and messages. The allowlist of SupportedCultures (ADR-027) and the light/dark theme set (ADR-028) live in the framework; only the wording is app-specific.
      • +
      +
    • +
    • Why it's built this way: every method takes a source string that becomes the error's source field, so a failure reports which operation raised it (nameof(Create), nameof(ChangePassword), nameof(UpdatePreferences)). That is why the same invariant can be shared by the create and change paths without losing diagnostic context ([Rubric §13, Observability & Operability]).
    • +
    • Where it's used: by User.Create (User.cs:210-214), User.CreateExternal (User.cs:258-259), User.UpdatePreferences (User.cs:333-334), and User.ChangePassword (User.cs:363-364); the length constants are reused by RegisterRequestValidator and UserConfiguration.
    • +
    • Caveats / not-in-source: EnsureEmailIsValid has no callers in current ADC source. The aggregate validates its address through the Email value object's own Create instead (User.cs:207, :207, :368), so the email invariant here is a second, currently unused expression of the same rule.
    • +

    OAuthController

    MMCA.ADC.Identity.API · MMCA.ADC.Identity.API.Controllers · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/OAuthController.cs:20 · Level 7 · class (sealed)

    @@ -2758,6 +2780,39 @@

    OAuthController

  • Where it's used: mounted by the Identity service host and fronted by the Gateway; the browser and MAUI clients drive it, and the email-verified guard on the linking step is answered by HttpContextExternalLoginEmailVerifier.
  • Caveats / not-in-source: the provider registrations, the scopes, and the Google email_verified claim mapping live in the Identity service host's Program.cs, not in this file, so which of the three named providers a given environment actually offers is not determinable from here.
  • +

    User

    +
    +

    MMCA.ADC.Identity.Domain · MMCA.ADC.Identity.Domain.Users · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:34 · Level 7 · class (sealed)

    +
    +
      +
    • What it is: the Identity aggregate root. One row per account, holding credentials, role, profile fields, optional MAUI device metadata, external-login identifiers, UI preferences, the avatar URL, email-confirmation and account-lock state, and the scalar link to a Conference Speaker.
    • +
    • Depends on: AuditableAggregateRootEntity<TIdentifierType>, IPasswordChangeableUser, IUserPreferences, IErasableUser (which extends IAnonymizable), IEmailConfirmableUser, IAuditedEntity, Email, UserRole, UserInvariants, UserPasswordChanged, UserDeleted, PiiAttribute, IdValueGeneratedAttribute, Result; externals: BCL only.
    • +
    • Concept introduced, the interface list as a workflow contract. [Rubric §4, DDD] assesses whether the aggregate is the consistency boundary and enforces its own invariants, and [Rubric §1, SOLID] assesses interface segregation: five narrow capability interfaces instead of one fat base (User.cs:34-35). The class remarks (User.cs:18-32) explain something genuinely easy to get wrong. The shared framework workflows are generic over capability interfaces: ChangePasswordHandlerBase<TUser, TCommand> constrains on IPasswordChangeableUser (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:49), the erasure workflow constrains on IErasableUser, and SendEmailConfirmationHandlerBase<TUser, TCommand> constrains on IEmailConfirmableUser (added for ADR-116). Listing IErasableUser on this type directly is load-bearing because Delete here hides the base soft-delete with new (User.cs:443); only re-declaring the interface on User re-maps the interface slot onto this type's own member, which is what keeps the UserDeleted domain event inside the shared erasure path. Remove the interface from the declaration list and the code still compiles while quietly calling the base method instead.
        +
      • IAuditedEntity (User.cs:35) is the one non-behavioural marker in the list, and the remarks justify it (User.cs:26-31): it opts the aggregate into a change history rather than just the last-writer audit fields. [Rubric §30, Compliance, Privacy & Data Governance]: account records are where a support or compliance question actually gets asked (who changed this attendee's email, when was this account raised to Organizer, who anonymized it), and LastModifiedOn/By alone answers only "who touched it last".
      • +
      • [IdValueGenerated] (User.cs:33) tells the persistence layer the id is database-generated. That single attribute is the root cause of the UserRegistered two-save dance: the id does not exist until after the insert, which the Create doc states outright (User.cs:185-191).
      • +
      +
    • +
    • Walkthrough
        +
      • State (User.cs:50-133). IsEmailConfirmed (User.cs:47) records whether the address has been proven reachable, either by redeeming a confirmation link or by an external provider asserting it (ADR-116). Email is an Email value object, not a string, and is marked [Pii] along with FirstName, LastName, and AvatarUrl; the [Pii] marks are what let the privacy tooling find personal columns (ADR-005). PasswordHash and PasswordSalt are byte[] mapped to varbinary(max), inside a scoped CA1819 suppression that names EF mapping as the reason. Role is a string for EF and JWT round-tripping while UserRole owns the rules. LinkedSpeakerId is a nullable SpeakerIdentifierType scalar, the 1:1 bidirectional counterpart of Speaker.LinkedUserId (BR-208). LockedOn (User.cs:491) records when an administrator locked the account, or null while usable; a locked account keeps every row it owns (bookmarks, feedback, points) and can be unlocked, which separates it from the irreversible Anonymize. Every setter is private: state changes only through the methods below.
      • +
      • What is deliberately absent: refresh tokens. The class summary states it: sessions are per-device rows in the framework's RefreshSessions table (BR-205/206), hashed at rest, not columns on this aggregate. [Rubric §8, Data Architecture]: a per-device session list cannot be modelled as one nullable token column on the account row, and moving it out is what lets a user revoke one device without touching the others.
      • +
      • Computed members: IsExternalLogin => LoginProvider is not null (User.cs:130); IsLocked => LockedOn is not null (User.cs:141), computed from LockedOn rather than stored so the state and the instant it began cannot disagree (the login and refresh paths refuse a locked account, and locking also revokes the account's live refresh sessions, so the lock takes effect within one access-token lifetime rather than at the next sign-in); HasLocalPassword => PasswordHash.Length > 0 (User.cs:151), which the external-login flow reads to decide whether an OAuth identity may be attached by email match alone (an account whose password someone already holds must be claimed by signing in with that password first); and FullName (User.cs:154). All are derived rather than stored so they cannot go stale.
      • +
      • Constructors (User.cs:156-180): a private parameterless one for EF that initializes the non-nullable members to safe defaults, including Role = UserRole.Attendee.Value (:163), and a private full one used by the factories.
      • +
      • Create (User.cs:199-227): parses the email, then Result.Combines the email result with five UserInvariants checks, returns Result.Failure<User>(result.Errors) on any failure, and otherwise constructs with Id = default.
      • +
      • CreateExternal (User.cs:247-275): the OAuth path. It validates email and names only, sets PasswordHash/PasswordSalt to empty arrays and forces UserRole.Attendee.Value, and records LoginProvider/ProviderKey. A new emailVerified parameter (default false, :253) sets IsEmailConfirmed from it (:271): a provider assertion is exactly the proof the confirmation email exists to collect, so an account created from one starts confirmed and is never asked to prove it again; GitHub's OAuth flow asserts nothing, so those accounts start unconfirmed like a local registration. The password invariants are deliberately skipped because an external account has no local password.
      • +
      • ConfirmEmail (User.cs:283-287): sets IsEmailConfirmed = true and returns success. Idempotent: confirming an already-confirmed account is a no-op success, which at-least-once delivery of the confirmation link requires.
      • +
      • Behavior: LinkExternalProvider (User.cs:300-305) attaches a provider to an existing local account and also sets IsEmailConfirmed = true (:304), since the link is only ever made after the provider asserted the address as verified (the caller enforces that; it is the account-takeover guard), so the assertion also settles the confirmation question for an account that had never answered it; LinkSpeaker / UnlinkSpeaker (User.cs:312-320) maintain the BR-207/BR-209 scalar cross-context link; UpdateDeviceMetadata sets the seven MAUI fields in one call; SetAvatarUrl records or clears the avatar URL and nothing else, with the doc drawing the boundary explicitly: size, format sniffing, and re-encoding belong to the upload use case, and the domain only stores the resulting public URL.
      • +
      • Lock(DateTime utcNow) / Unlock(): Lock is idempotent via LockedOn ??= utcNow, so locking an already-locked account keeps the original instant rather than resetting it, preserving the audit trail's "when the lock began". Unlock sets LockedOn = null and is a no-op on an account that is not locked.
      • +
      • ChangeRole(string role) (BR-41, BR-45): validates through UserInvariants.EnsureRoleIsValid, then assigns the role's canonical spelling via UserRole.FromString(role).Value!.Value rather than the caller's casing, since the role column is compared and filtered ordinally elsewhere (the user list's role filter, the JWT role claim). A no-op when the account already holds the role.
      • +
      • UpdatePreferences: combines the culture and theme invariants and assigns only after both pass, so a rejected theme cannot leave a half-applied culture.
      • +
      • ChangePassword: validates the new hash and salt, assigns, and raises UserPasswordChanged via AddDomainEvent.
      • +
      • Delete: the new hiding method. It calls base.Delete() and raises UserDeleted only when that succeeded. Its doc answers the obvious question about outstanding sessions: they are not touched here and do not need to be, because the refresh flow re-fetches the account through the soft-delete query filter, so every session stops working the moment this commits.
      • +
      • Anonymize (span ends User.cs:504): the erasure half. It builds the placeholder address deleted-{Id}@anonymized.invalid with CultureInfo.InvariantCulture, and the id embedded in the address is what keeps the unique-email invariant (BR-200) satisfiable across many erased accounts. It is idempotent by construction: if the current email already equals the placeholder there is nothing left to erase and it returns success. Otherwise it overwrites the email, names, credentials, all seven device fields, both external-login fields, and the avatar URL, and now also resets IsEmailConfirmed = false (User.cs:387-389): the placeholder address is not reachable and was never proven, so the flag cannot keep asserting that it was.
      • +
      +
    • +
    • Why it's built this way: Delete and Anonymize are separate operations, and the doc on Anonymize says exactly why: the row survives so cross-context scalar references (bookmarks, notifications) and the audit trail do not break, which is the anonymize-in-place policy of ADR-005. It is also why a re-registration with an erased account's original email succeeds by design. The avatar comment draws the matching line for storage: the domain nulls the URL, and the use case, which knows the blob boundary, deletes the file (ADR-045). IsEmailConfirmed and the lock/role additions exist because ADC opted into the framework's optional Identity completions (ADR-116): ADC sends the confirmation but does not gate sign-in on it (Authentication:EmailConfirmation:RequireConfirmedEmail is false), because an attendee who registers on the conference floor must be able to use the app immediately; the flag is what an organizer reads before trusting an address, and what a later decision to gate would key off without a second migration.
    • +
    • Where it's used: persisted by UserConfiguration, projected by UserDTOMapper, driven by AuthenticationService and the Users use cases (ChangePasswordHandler, ChangePreferencesHandler, DeleteUserHandler, ExportUserDataHandler, SetUserAvatarHandler, RemoveUserAvatarHandler, SendEmailConfirmationHandler, and the reset-password path behind PasswordResetController), mutated on the cross-context link by SpeakerLinkedToUserHandler and SpeakerUnlinkedFromUserHandler, and read by GetUsersHandler.
    • +
    • Caveats / not-in-source: Anonymize leaves Role, PreferredCulture, PreferredTheme, and LinkedSpeakerId untouched, so an erased account keeps its role, its UI preferences, and any speaker link. Those are treated as non-identifying here; nothing in this file states that judgement, so it is a behavior to notice rather than a documented decision.
    • +

    UsersController

    MMCA.ADC.Identity.API · MMCA.ADC.Identity.API.Controllers · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersController.cs:30 · Level 9 · class (sealed)

    @@ -2801,16 +2856,16 @@

    UsersDataExportController

    • What it is: the data-subject access and portability endpoint, GET /Users/{userId}/export. Like OAuthController it declares no action of its own: the action, its authorization, its feature gate, and the file-download response all come from DataExportControllerBase<TQuery>, and this class supplies the route and one query factory.
    • Depends on: DataExportControllerBase<TQuery> (base), IQueryHandler<in TQuery, TResult> closed over ExportUserDataQuery, UserDataExportDTO, ICurrentUserService, Result, and PrivacyFeatures.
    • -
    • Concept introduced, the abstract base with an app-owned query factory. [Rubric §30, Compliance, Privacy & Data Governance] assesses whether the subject-rights obligations are implemented rather than described, and [Rubric §15, Best Practices & Code Quality] covers the factoring. The export workflow is identical in every app, but the query record is not: each app's ExportUserDataQuery lives in its own Application assembly, so a concrete shared controller could not construct a type it cannot see. The base's remarks state that reasoning explicitly (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:43-48), and the resolution is an abstract CreateQuery (:119-122) implemented here as a one-liner (UsersDataExportController.cs:32-35).
    • -
    • Concept introduced (2), defence in depth on a privacy endpoint. [Rubric §11, Security]. Three independent gates sit in front of one document. The base carries [Authorize] (DataExportControllerBase.cs:57), so an anonymous caller never arrives; it carries [FeatureGate(PrivacyFeatures.DataExport)] (:58), so the whole surface stays absent until a host turns the flag on (ADR-031); and the handler independently enforces owner-or-Organizer, which is why the class doc here can say "the handler enforces it" (UsersDataExportController.cs:16-17) and the base can say the endpoint cannot leak another subject's data even if it were mounted without an [Authorize] of its own (DataExportControllerBase.cs:49-54). The ADC privilege predicate is one line in ExportUserDataHandler (.../UseCases/ExportUserData/ExportUserDataHandler.cs:38).
    • +
    • Concept introduced, the abstract base with an app-owned query factory. [Rubric §30, Compliance, Privacy & Data Governance] assesses whether the subject-rights obligations are implemented rather than described, and [Rubric §15, Best Practices & Code Quality] covers the factoring. The export workflow is identical in every app, but the query record is not: each app's ExportUserDataQuery lives in its own Application assembly, so a concrete shared controller could not construct a type it cannot see. The base's remarks state that reasoning explicitly (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/Privacy/DataExportControllerBase.cs:45-50), and the resolution is an abstract CreateQuery (:119-122) implemented here as a one-liner (UsersDataExportController.cs:32-35).
    • +
    • Concept introduced (2), defence in depth on a privacy endpoint. [Rubric §11, Security]. Three independent gates sit in front of one document. The base carries [Authorize] (DataExportControllerBase.cs:59), so an anonymous caller never arrives; it carries [FeatureGate(PrivacyFeatures.DataExport)] (:58), so the whole surface stays absent until a host turns the flag on (ADR-031); and the handler independently enforces owner-or-Organizer, which is why the class doc here can say "the handler enforces it" (UsersDataExportController.cs:16-17) and the base can say the endpoint cannot leak another subject's data even if it were mounted without an [Authorize] of its own (DataExportControllerBase.cs:51-56). The ADC privilege predicate is one line in ExportUserDataHandler (.../UseCases/ExportUserData/ExportUserDataHandler.cs:38).
    • Walkthrough: three attributes and two members.
        -
      • [ApiController], [Route("Users")], [ApiVersion("1.0")] (UsersDataExportController.cs:23-25). The route is the literal Users, not [controller], and the <remarks> say why (:19-22): the base fixes the action template to {userId}/export (DataExportControllerBase.cs:77), so a subclass routed at Users keeps serving the published /Users/{userId}/export path even though the class is named UsersDataExportController. This is the same trick PasswordResetController uses on /Auth.
      • +
      • [ApiController], [Route("Users")], [ApiVersion("1.0")] (UsersDataExportController.cs:23-25). The route is the literal Users, not [controller], and the <remarks> say why (:19-22): the base fixes the action template to {userId}/export (DataExportControllerBase.cs:79), so a subclass routed at Users keeps serving the published /Users/{userId}/export path even though the class is named UsersDataExportController. This is the same trick PasswordResetController uses on /Auth.
      • The primary constructor (:26-29) takes the closed export handler and ICurrentUserService and forwards both to the base.
      • -
      • CreateQuery(userId, currentUserId, currentUserRole) (:32-35): builds ADC's ExportUserDataQuery from the route id and the authenticated caller. The base's constraint where TQuery : IUserOwnedRequest (DataExportControllerBase.cs:62) is what lets the shared pipeline reason about ownership on an app-owned record.
      • -
      • The inherited action, ExportAsync (DataExportControllerBase.cs:82-110): reject an unusable caller id with Privacy.Unauthorized (:86-90), build the query and dispatch (:92-94), then serialize the package with JsonSerializer.SerializeToUtf8Bytes(export, JsonSerializerOptions.Web) and return File(...) (:107-109). The comment explains the choice (:103-106): Ok(export) would content-negotiate and render inline, and the point of this endpoint is a document the subject saves and keeps. The download name is built from the package's own GeneratedOn under InvariantCulture (:133-134), so the file name and the document always agree and a saved file sorts the same in every locale.
      • +
      • CreateQuery(userId, currentUserId, currentUserRole) (:32-35): builds ADC's ExportUserDataQuery from the route id and the authenticated caller. The base's constraint where TQuery : IUserOwnedRequest (DataExportControllerBase.cs:64) is what lets the shared pipeline reason about ownership on an app-owned record.
      • +
      • The inherited action, ExportAsync (DataExportControllerBase.cs:84-114): reject an unusable caller id with Privacy.Unauthorized (:86-90), build the query and dispatch (:92-94), then serialize the package with JsonSerializer.SerializeToUtf8Bytes(export, JsonSerializerOptions.Web) and return File(...) (:107-109). The comment explains the choice (:103-106): Ok(export) would content-negotiate and render inline, and the point of this endpoint is a document the subject saves and keeps. The download name is built from the package's own GeneratedOn under InvariantCulture (:133-134), so the file name and the document always agree and a saved file sorts the same in every locale.
    • -
    • Why it's built this way: ADR-076 is the decision this implements, and the split (route and query here, everything else in the base) follows the AuthControllerBase precedent the base cites (DataExportControllerBase.cs:38-42): the app owns its URL space, the framework owns the workflow.
    • +
    • Why it's built this way: ADR-076 is the decision this implements, and the split (route and query here, everything else in the base) follows the AuthControllerBase precedent the base cites (DataExportControllerBase.cs:40-44): the app owns its URL space, the framework owns the workflow.
    • Where it's used: handled by ExportUserDataHandler; called by an authenticated subject exporting their own data, or by an Organizer acting on a request, through the Gateway's existing /Users route.
    • Caveats / not-in-source: whether the endpoint is reachable in a given environment depends on the PrivacyFeatures.DataExport flag value, which lives in host configuration and not in this file.
    @@ -2841,26 +2896,87 @@

    AuthController

    • What it is: the /auth endpoint surface: login, register, refresh, revoke, change password, and get/set the stored culture and theme preferences. Most of it is inherited; this class overrides two actions and supplies the two command factories.
    • Depends on: UserAccountAuthControllerBase<TChangePasswordCommand, TChangePreferencesCommand> (which itself extends AuthControllerBase), IAuthenticationService, ICurrentUserService, ChangePasswordCommand, ChangePreferencesCommand, GetUserPreferencesQuery, AuthenticationResponse, RegisterRequest, LoginRequest, UserPreferencesResponse, IInternalCommandScheduler, SendEmailConfirmationCommand; externals: ASP.NET Core rate limiting ([EnableRateLimiting]).
    • -
    • Concept introduced, the generic controller base parameterized by the app's command types. [Rubric §15, Best Practices & Code Quality], [Rubric §1, SOLID], [Rubric §11, Security]. The base owns the four token actions plus PUT password (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/UserAccountAuthControllerBase.cs:87), PUT preferences (:112), and GET preferences (:138). It cannot own the command records, because ADC marks its change-password command ICacheInvalidating with a cache prefix built from ADC's own User type while Store does not; the shared handler base's remarks record that reason (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:18-23). The resolution is two abstract factory methods (UserAccountAuthControllerBase.cs:67, :77), which this class implements as one-liners (AuthController.cs:985-992): the shared workflow stays shared, and each app keeps its own command semantics. PasswordResetController is the same pattern applied to the recovery pair.
    • -
    • Concept introduced (2), scheduling a side effect after commit instead of sending it inline (ADR-116). [Rubric §6, CQRS & Event-Driven], [Rubric §29, Resilience & Business Continuity]. RegisterAsync no longer stops at 201 Created: once the registration itself succeeds, it schedules a SendEmailConfirmationCommand through IInternalCommandScheduler (AuthController.cs:951-955). The comment above the call (:940-950) states the reasoning in full: the registration transaction has already committed by that point, so the row is written and signalled immediately and the internal-command processor picks it up on its next tick; a mail outage or a crash in between defers the send rather than losing it. It also names why the trigger lives in the controller rather than in a domain event: ADC raises no UserRegistered domain event (only the cross-service integration event UserRegistered), and AuthenticationService already sits at the dependency ceiling, so the controller is the one place that both knows a registration just succeeded and is free to take a new dependency. A scheduling failure never fails the 201: the account exists and the user is signed in either way, and they can request a fresh link from the confirmation page; the failure is logged at Error through LogConfirmationScheduleFailed (:958-960, :965-966) because a recurring one means nobody is being asked to confirm anything.
    • +
    • Concept introduced, the generic controller base parameterized by the app's command types. [Rubric §15, Best Practices & Code Quality], [Rubric §1, SOLID], [Rubric §11, Security]. The base owns the four token actions plus PUT password (MMCA.Common/Source/Presentation/MMCA.Common.API/Controllers/UserAccountAuthControllerBase.cs:87), PUT preferences (:112), and GET preferences (:138). It cannot own the command records, because ADC marks its change-password command ICacheInvalidating with a cache prefix built from ADC's own User type while Store does not; the shared handler base's remarks record that reason (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:19-24). The resolution is two abstract factory methods (UserAccountAuthControllerBase.cs:67, :77), which this class implements as one-liners (AuthController.cs:117-125): the shared workflow stays shared, and each app keeps its own command semantics. PasswordResetController is the same pattern applied to the recovery pair.
    • +
    • Concept introduced (2), scheduling a side effect after commit instead of sending it inline (ADR-116). [Rubric §6, CQRS & Event-Driven], [Rubric §29, Resilience & Business Continuity]. RegisterAsync no longer stops at 201 Created: once the registration itself succeeds, it schedules a SendEmailConfirmationCommand through IInternalCommandScheduler (AuthController.cs:84-88). The comment above the call (:73-83) states the reasoning in full: the registration transaction has already committed by that point, so the row is written and signalled immediately and the internal-command processor picks it up on its next tick; a mail outage or a crash in between defers the send rather than losing it. It also names why the trigger lives in the controller rather than in a domain event: ADC raises no UserRegistered domain event (only the cross-service integration event UserRegistered), and AuthenticationService already sits at the dependency ceiling, so the controller is the one place that both knows a registration just succeeded and is free to take a new dependency. A scheduling failure never fails the 201: the account exists and the user is signed in either way, and they can request a fresh link from the confirmation page; the failure is logged at Error through LogConfirmationScheduleFailed (:90-93, :98-99) because a recurring one means nobody is being asked to confirm anything.
    • Walkthrough: primary constructor forwarding seven dependencies to the base (AuthController.cs:33-47, two more than before: IInternalCommandScheduler and ILogger<AuthController>), then four members.
        -
      • RegisterAsync (AuthController.cs:919-963) is a genuine override rather than a pass-through. It passes the base's ClientIpAddress and ClientUserAgent into AuthenticationService.RegisterAsync (:931-933; the two properties are computed from HttpContext on the base). Success returns 201 Created explicitly (:962), after the confirmation-scheduling step above.
      • -
      • LoginAsync (AuthController.cs:979-982) overrides only to re-declare attributes, then calls base.LoginAsync. The reason is the attribute set: [EnableRateLimiting(WebApplicationBuilderExtensions.RateLimitPolicyAuthIp)] (:975) and the documented 429. The doc comment (:968-971) states the threat model precisely: the per-email lockout of BR-212 cannot stop one source spraying a single common password across many different emails, so a per-IP fixed window sits on top of it (ADR-019, ADR-029). Both anonymous endpoints, register and login, carry the same policy.
      • -
      • CreateChangePasswordCommand and CreateChangePreferencesCommand (AuthController.cs:985-992), the two factory implementations that bind ADC's command records to the base's workflow.
      • +
      • RegisterAsync (AuthController.cs:52-96) is a genuine override rather than a pass-through. It passes the base's ClientIpAddress and ClientUserAgent into AuthenticationService.RegisterAsync (:64-66; the two properties are computed from HttpContext on the base). Success returns 201 Created explicitly (:95), after the confirmation-scheduling step above.
      • +
      • LoginAsync (AuthController.cs:106-115) overrides only to re-declare attributes, then calls base.LoginAsync. The reason is the attribute set: [EnableRateLimiting(WebApplicationBuilderExtensions.RateLimitPolicyAuthIp)] (:108) and the documented 429. The doc comment (:101-105) states the threat model precisely: the per-email lockout of BR-212 cannot stop one source spraying a single common password across many different emails, so a per-IP fixed window sits on top of it (ADR-019, ADR-029). Both anonymous endpoints, register and login, carry the same policy.
      • +
      • CreateChangePasswordCommand and CreateChangePreferencesCommand (AuthController.cs:117-125), the two factory implementations that bind ADC's command records to the base's workflow.
    • -
    • Why it's built this way: [Route("[controller]")] makes the prefix /auth, which is what the Gateway's route map fronts, and [ApiVersion("1.0")] keeps it on the header-based versioning scheme (ADR-046). The class is now partial (:33) because the [LoggerMessage] source generator needs a partial method declaration for LogConfirmationScheduleFailed (:965-966). Both anonymous actions are marked [AllowAnonymous] with the fully qualified attribute name because the file's using set does not import the authorization namespace.
    • +
    • Why it's built this way: [Route("[controller]")] makes the prefix /auth, which is what the Gateway's route map fronts, and [ApiVersion("1.0")] keeps it on the header-based versioning scheme (ADR-046). The class is now partial (:33) because the [LoggerMessage] source generator needs a partial method declaration for LogConfirmationScheduleFailed (:98-99). Both anonymous actions are marked [AllowAnonymous] with the fully qualified attribute name because the file's using set does not import the authorization namespace.
    • Where it's used: the entry point for every authenticated ADC client. The Blazor and MAUI clients call login/register/refresh through the Gateway; the Profile page uses the inherited PUT auth/password and the preferences pair; ChangePasswordRequestValidator guards the password action through the Validating decorator; a successful register schedules the confirmation link handled by EmailConfirmationController. Its anonymous sibling on the same /Auth prefix is PasswordResetController.
    +

    Profile

    +
    +

    MMCA.ADC.Identity.UI · MMCA.ADC.Identity.UI.Pages.Users.Profile · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Pages/Users/Profile/Profile.razor.cs:18 · Level 6 · class (partial)

    +
    +
      +
    • What it is: the code-behind for the "My Profile" page at /profile: the signed-in user's self-service surface, namely set or remove the avatar, change the password, reach the signed-in-devices page, and delete the account.
    • +
    • Depends on: IUserUIService, IAuthUIService, IToastService, IMediaPickerService and PickedMedia, plus Blazor's AuthenticationStateProvider, NavigationManager, InputFileChangeEventArgs, and MudBlazor's MudForm / MudMessageBox (Profile.razor.cs:1-25). The localizer L is injected in the markup half (Profile.razor:4), which is also where the route and the [Authorize] attribute live (Profile.razor:1-2).
    • +
    • Concept introduced (1), the cross-head capability branch. [Rubric §22, Responsive & Cross-Browser] assesses whether one component genuinely serves different hosts. The same page runs in the Blazor web head and inside the MAUI hybrid, and the avatar affordance differs: the markup branches on MediaPicker.IsSupported (Profile.razor:47) to offer "choose photo" plus "take photo" buttons on a native head, and falls back to a hidden <InputFile accept="image/jpeg,image/png,image/webp"> behind a label-styled button on the web (Profile.razor:60-66). Both branches converge on one private method, UploadAvatarStreamAsync (Profile.razor.cs:164-175), so the upload contract is written once. That convergence is also why IUserUIService's upload method is typed on a raw Stream rather than on a framework-specific file type (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Services/IUserUIService.cs:47).
    • +
    • Concept introduced (2), the component-scoped CancellationTokenSource. [Rubric §18, UI Architecture]. The page holds private readonly CancellationTokenSource _cts = new() (Profile.razor.cs:30), passes _cts.Token into every service call, and cancels plus disposes it through the standard disposable pattern (Profile.razor.cs:280-298). Every async entry point then catches OperationCanceledException separately from Exception and deliberately does nothing (Profile.razor.cs:78-81, :113-116, :150-153, :192-195, :235-238, :270-273), with the comment naming the two causes: component disposal and an InteractiveAuto render-mode transition. A user who navigated away must not be shown a red error toast for a request that was cancelled on their behalf; a genuine failure still toasts.
    • +
    • Concept reinforced, the Result-returning UI service. [Rubric §24, Forms, Validation & UX Safety]. Every call into IUserUIService returns a Result, and the page reads it with TryGetValue or IsSuccess rather than with a null check (Profile.razor.cs:72, :167, :182, :260). A failed read and a successful read of nothing are therefore different states, which is what lets the avatar load stay silent on failure while an avatar upload failure toasts.
    • +
    • Walkthrough
        +
      • OnInitializedAsync (Profile.razor.cs:58-90): awaits AuthStateProvider.GetAuthenticationStateAsync() and reads the subject with the shared GetUserId() claims helper (:66), whose comment records that it accepts both the sub and the nameidentifier form and parses invariantly. The page identifies its subject from the token, never from a route parameter, which is what makes it structurally incapable of acting on another account. Only when the claim parses does it fetch the avatar URL (:72-75), and a failed avatar read is deliberately silent (:70-71): the card falls back to its person icon rather than greeting the user with an error. _isLoading is cleared in finally (:86-89).
      • +
      • Client-side avatar guard (:27-28, :135-139): MaxAvatarBytes = 2 * 1024 * 1024, and the browser path rejects an oversized file with a warning toast before any bytes leave the machine. The doc comment says exactly what this is: a mirror of the server's BR-116a limit, not the enforcement point. The server re-checks in UsersController (UsersController.cs:92-98) and again in SetUserAvatarHandler.
      • +
      • Avatar paths: PickAvatarAsync and CaptureAvatarAsync (:96, :98) both funnel through UploadPickedMediaAsync (:100-125), which disposes the PickedMedia with using (:105) and returns silently when the user cancelled the picker (media is null, :106-109). The browser path OnBrowserInputChangedAsync (:127-162) opens the stream with file.OpenReadStream(MaxAvatarBytes, _cts.Token) (:144) and wraps it in await using so the read stream is released even on failure. UploadAvatarStreamAsync (:164-175) toasts on a failed result and only assigns _avatarUrl on success (:173-174).
      • +
      • RemoveAvatarAsync (:177-200): the failure branch of the result toasts the same message the page would show for a thrown exception (:189), so the user sees one consistent outcome for "did not work".
      • +
      • Password form (:37-52, :202-243): two local validators, ValidateNewPassword (minimum length 8, :46-47) and ValidateConfirmPassword (ordinal equality against _newPassword, :49-52), both returning null for an empty value so the field's own Required rule owns the "missing" message instead of stacking two errors, exactly as the comment above them states (:43-45). SavePasswordAsync runs form.ValidateAsync() and returns early when invalid (:211-215), so a shape error costs no round trip; the errors also render through the shared ErrorSummary component, which deduplicates and localizes them (Profile.razor:100-102). On success it resets the form and clears all three fields (:224-228). [Rubric §24, Forms, Validation & UX Safety]: this is client-side validation as an ergonomics layer over, never instead of, the server rules that live in ChangePasswordRequestValidator and ChangePasswordHandler.
      • +
      • Signed-in devices (Profile.razor:112-128): a card whose only content is a link to RoutePaths.Sessions (:124). The comment above it records why there is no logic here (:112-113): the sessions page itself ships from MMCA.Common, so this card is only the way in from the profile.
      • +
      • DeleteAccountAsync (:245-278): guards on a known _userId (:247-251), shows the MudMessageBox (Profile.razor:148-158) and aborts unless the answer is exactly true (confirmed is not true, :253-255, so cancel and dismiss both abort), then deletes, calls AuthService.LogoutAsync(), toasts, and navigates to / with forceLoad: true (:266-268). The forced load matters: the account is gone, so the circuit and every piece of cached client state must be rebuilt from scratch rather than kept alive against a dead identity.
      • +
      +
    • +
    • Why it's built this way: keeping avatar, password, sessions, and deletion on one page mirrors the account-level scope of all four (each acts on the caller and only the caller), and every action ends in a toast with a localized message, so the page needs no bespoke error surface. [Rubric §21, Accessibility]: the markup carries an explicit aria-label on the MudAvatar (Profile.razor:33) because MudBlazor renders role="img", which the axe role-img-alt rule requires to carry an accessible name even in the fallback-icon state, and the comment above it says so (Profile.razor:30-32).
    • +
    • Where it's used: routed at /profile for any authenticated user in both the web and MAUI heads. Its server round trips land on the Identity avatar endpoints on UsersController, AuthController's PUT password, and UsersController's DELETE {userId}.
    • +
    • Caveats / not-in-source: the 8-character minimum in ValidateNewPassword is a local literal (Profile.razor.cs:47); it does not reference the shared strong-password rule set the server applies, so the two can drift and only a server rejection would surface the difference.
    • +
    +

    UserDetail

    +
    +

    MMCA.ADC.Identity.UI · MMCA.ADC.Identity.UI.Pages.Users.UserDetail · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Pages/Users/UserDetail/UserDetail.razor.cs:22 · Level 6 · class (sealed)

    +
    +
      +
    • What it is: the code-behind for the organizer account-detail page: lock or unlock an account and change its role, one user at a time.
    • +
    • Depends on: IUserAdminUIService<TUserDto> (closed over UserAdminDTO), IAppDialogService, IToastService, RoleNames; externals: Blazor's AuthenticationStateProvider, BreadcrumbItem.
    • +
    • Concept introduced, the self-action guard on an administration page. [Rubric §11, Security], [Rubric §24, Forms, Validation & UX Safety]. CanAdminister (UserDetail.razor.cs:131-134) compares the account on screen against the caller's own id, read from the authentication state during OnInitializedAsync (:145-147), and the two admin actions are enabled only when it is true. An organizer locking or role-changing their own account mid-session is the trap this closes: the page never lets the operator submit that request in the first place.
    • +
    • Walkthrough
        +
      • OnInitializedAsync (:137-147): builds the breadcrumb trail and reads _currentUserId from AuthStateProvider.GetAuthenticationStateAsync().
      • +
      • OnParametersSetAsync (:154): calls LoadAsync on every parameter set rather than only on first render, so navigating from one account to another inside the same page instance reloads the account named in the URL, the same pattern UserList's sibling pages rely on.
      • +
      • LoadAsync (:156-165): fetches the account through UserAdminService.GetAsync(Id, ...) and seeds _selectedRole from the loaded user, falling back to RoleNames.Attendee when the fetch failed.
      • +
      • ToggleLockAsync (:167-207): confirms through Dialogs.ConfirmAsync with a lock- or unlock-worded message built from the account's own email, then calls UserAdminService.LockAsync or UnlockAsync, toasts the outcome, and reloads on success. _isBusy is set before the call and cleared in finally (:187, :203-206).
      • +
      • ChangeRoleAsync (:209-244): a no-op when the selected role already matches the account's role (:211-214, ordinal comparison), otherwise the same confirm-then-call-then-toast-then-reload shape as ToggleLockAsync, through UserAdminService.SetRoleAsync.
      • +
      • Dispose (:247-252): cancels and disposes the page's CancellationTokenSource and suppresses finalization.
      • +
      +
    • +
    • Why it's built this way: AssignableRoles (:106-107) is [RoleNames.Attendee, RoleNames.ContentEditor, RoleNames.Organizer], least to most privileged, taken from the domain's own role vocabulary so the menu never drifts from the roles the server actually accepts. Both mutating actions confirm before acting, the same destructive-action discipline UserList's delete uses.
    • +
    • Where it's used: routed from UserList as the per-account detail page in the ADC web and MAUI UI; the account it edits is read and written through IUserAdminUIService<UserAdminDTO>, the framework's shared administration client (ADR-116), which calls the API surface UsersAdminController exposes.
    • +
    +

    UserList

    +
    +

    MMCA.ADC.Identity.UI · MMCA.ADC.Identity.UI.Pages.Users · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.UI/Pages/Users/UserList.razor.cs:24 · Level 6 · class (partial)

    +
    +
      +
    • What it is: the code-behind for the organizer user list page at /users, a thin shell over the framework's shared UserAdminList<TUser> (ADR-116), which renders the roster, the mobile cards, and the administration actions (lock, unlock, change role) against the framework's Admin/Users endpoints. ADC supplies what stays its own: the route, the Organizer authorization, the BR-51 roster endpoint, the extra name and creation columns, the role vocabulary, and delete (UC-21).
    • +
    • Depends on: UserAdminList<TUser>, IUserUIService, UserListDTO, RoleNames, Result.
    • +
    • Concept introduced, splitting the roster read from the administration write. [Rubric §18, UI Architecture], [Rubric §7, Microservices Readiness]. The class doc's <remarks> (UserList.razor.cs:14-19) states the reasoning directly: the roster keeps reading through IUserUIService rather than the framework's administration read service, because ADC's /Users endpoint is the one that has always offered per-column filters and server-side sorting; the three account actions (lock, unlock, change role) go through the framework's client either way, since those are identical in every app. The class was previously its own DataGridListPageBase<UserListDTO> subclass with its own grid, mobile list, search, and delete wiring; that machinery moved into UserAdminList<TUser> and this class shrank to ComponentBase plus one data-fetch method.
    • +
    • Walkthrough
        +
      • AssignableRoles (UserList.razor.cs:29-30): the same least-to-most-privileged role array UserDetail carries, kept here because the base's inline role editor needs it for this list.
      • +
      • FetchPageAsync (:45-69): the one method ADC still owns. It maps the base's generic filter bag onto the four filters IUserUIService.GetPagedAsync takes (BR-51). The free-text search box arrives under UserAdminList<UserListDTO>.SearchFilterKey and wins over an explicit Email column filter (:56-57), because both affordances resolve to the one email parameter.
      • +
      • FilterValue (:72-76): reads one filter's value out of the bag by key; the operator half of the tuple is unused, because every filter here is treated as a value match or a search term, not a MudBlazor comparison operator.
      • +
      +
    • +
    • Why it's built this way: nameof(UserListDTO.Email) and its three siblings, rather than string literals, keep the filter keys tied to the DTO's own property names, so a rename is a compile error rather than a silently dead filter.
    • +
    • Where it's used: routed as the organizer user-management page in the ADC web and MAUI UI; the server side it calls for the roster is UsersController, whose list endpoint is gated on the UsersRead capability in IdentityPermissions (UsersController.cs:139); the lock/unlock/role actions it delegates to the base call UsersAdminController. RoleList reuses UserList.razor.cs as a reference for the same list-shell pattern applied to roles.
    • +

    SpeakerLinkedToUserHandler

    -

    MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Speakers.IntegrationEventHandlers · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Speakers/IntegrationEventHandlers/SpeakerLinkedToUserHandler.cs:27 · Level 8 · class (sealed, partial)

    +

    MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Speakers.IntegrationEventHandlers · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Speakers/IntegrationEventHandlers/SpeakerLinkedToUserHandler.cs:32 · Level 8 · class (sealed, partial)

    • What it is: the Identity-side subscriber for Conference's SpeakerLinkedToUser integration event. It sets User.LinkedSpeakerId, the Identity half of the bidirectional User-to-Speaker link (BR-209).
    • Depends on: ScopedIntegrationEventHandlerBase<TIntegrationEvent> (base, which implements IIntegrationEventHandler<in TIntegrationEvent>); SpeakerLinkedToUser from MMCA.ADC.Conference.Shared; IUnitOfWork and IRepository<TEntity, TIdentifierType>; User; IServiceScopeFactory and ILogger with the [LoggerMessage] source generator.
    • Concept introduced, the integration-event handler as a base-class subclass. [Rubric §6, CQRS & Event-Driven] and [Rubric §7, Microservices Readiness]. Two structural facts separate this from a domain-event handler. - First, integration-event handlers are registered as singletons, so they cannot constructor-inject a scoped service such as IUnitOfWork. Every handler therefore needs a DI scope per delivery. Rather than repeat that preamble, this class extends ScopedIntegrationEventHandlerBase<SpeakerLinkedToUser> (SpeakerLinkedToUserHandler.cs:27-30), which opens the async scope, hands the subclass that scope's IServiceProvider, and disposes it (MMCA.Common/Source/Core/MMCA.Common.Application/DomainEvents/ScopedIntegrationEventHandlerBase.cs:45-63). The subclass body is only its own resolutions plus its own logic, and the class doc says exactly that (SpeakerLinkedToUserHandler.cs:19-23). + First, integration-event handlers are registered as singletons, so they cannot constructor-inject a scoped service such as IUnitOfWork. Every handler therefore needs a DI scope per delivery. Rather than repeat that preamble, this class extends ScopedIntegrationEventHandlerBase<SpeakerLinkedToUser> (SpeakerLinkedToUserHandler.cs:32-35), which opens the async scope, hands the subclass that scope's IServiceProvider, and disposes it (MMCA.Common/Source/Core/MMCA.Common.Application/DomainEvents/ScopedIntegrationEventHandlerBase.cs:45-63). The subclass body is only its own resolutions plus its own logic, and the class doc says exactly that (SpeakerLinkedToUserHandler.cs:19-23). Second, this is a cross-service subscription: the event type comes from Conference's Shared assembly, travels through Conference's outbox and the MassTransit broker, and lands here (ADR-003). The class doc records what it replaced, a direct call from Conference into an Identity service interface (:13-18). Inverting that into an event is what allowed the two modules to become separate processes with separate databases, and it is why the doc can state that this handler is the only place User.LinkedSpeakerId changes in response to a Conference-side change.
    • Walkthrough (primary constructor :27-30, HandleScopedAsync :33-58)
      1. Scope-resolved services (:38-39), IUnitOfWork from the provided IServiceProvider, then GetRepository<User, UserIdentifierType>() (the mutating repository, because this path writes). The null guard on the event itself lives in the base (ScopedIntegrationEventHandlerBase.cs:47).
      2. @@ -2961,7 +3077,7 @@

        DeleteUserCommand

      3. What it is: the command that soft-deletes and erases a user account (UC-21). It carries the account being deleted and the authenticated caller, because deletion is one of the few operations where the caller may legitimately not be the subject.
      4. Depends on: the UserIdentifierType alias; User (for typeof(User).FullName); ICacheInvalidating; ITransactional; IUserOwnedRequest.
      5. Concept introduced, the owner-or-privileged-role request shape. [Rubric §11, Security] assesses whether authorization decisions are made from data the caller cannot forge, and [Rubric §1, SOLID] covers why this is an interface rather than a naming convention. IUserOwnedRequest extends IUserScopedRequest with CurrentUserId and a nullable CurrentUserRole (MMCA.Common/Source/Core/MMCA.Common.Application/Users/IUserOwnedRequest.cs:8-15), which is exactly the triple the shared UserOwnershipRule needs to answer "may this caller act on that account". Both extra values are filled from the token by the controller, never from the body, so a client cannot claim a role it was not issued. Only two ADC use cases wear this shape, deletion and data export, and they are precisely the two that must let an Organizer act on someone else's row.
      6. -
      7. Concept introduced, marking erasure as one atomic write. [Rubric §29, Resilience & Business Continuity] and [Rubric §30, Compliance, Privacy & Data Governance]. The record now also implements ITransactional (DeleteUserCommand.cs:46-51), and its <remarks> explains why: the soft-delete flag, the anonymized personal data, the UserDeleted outbox row, and the internal-command row that schedules the avatar blob delete (ADR-114) all need to commit together or not at all. DeleteUserHandler's tail enrolls that internal-command row before the save, and an enrolled row is exactly what a transaction makes atomic with the change it belongs to; without ITransactional the schedule could flush ahead of a half-finished erasure.
      8. +
      9. Concept introduced, marking erasure as one atomic write. [Rubric §29, Resilience & Business Continuity] and [Rubric §30, Compliance, Privacy & Data Governance]. The record now also implements ITransactional (DeleteUserCommand.cs:22), and its <remarks> (:8-15) explains why: the soft-delete flag, the anonymized personal data, the UserDeleted outbox row, and the internal-command row that schedules the avatar blob delete (ADR-114) all need to commit together or not at all. DeleteUserHandler's tail enrolls that internal-command row before the save, and an enrolled row is exactly what a transaction makes atomic with the change it belongs to; without ITransactional the schedule could flush ahead of a half-finished erasure.
      10. Walkthrough: a three-parameter positional record, UserId, CurrentUserId, CurrentUserRole (DeleteUserCommand.cs:11-14), plus the computed CachePrefix => $"{typeof(User).FullName}:" (:17). CurrentUserRole is string? because a token may carry no role claim at all, and the null case must resolve to "no privilege" rather than to an exception.
      11. Why it's built this way: modelling the caller as part of the command, rather than reaching for an ambient HttpContext inside the handler, is what keeps the handler testable without a web host and keeps the Application layer free of ASP.NET types ([Rubric §3, Clean Architecture], [Rubric §14, Testability]).
      12. Where it's used: constructed by UsersController's DeleteAsync from currentUserService.UserId and currentUserService.Role (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/UsersController.cs:166-181); handled by DeleteUserHandler.
      13. @@ -2978,44 +3094,18 @@

        ResetPasswordCommand

      14. Why it's built this way: the record stays application-side while its workflow lives in the framework, for the reason the base states in its own <remarks>: the shared handler reads the command only through ICommandWithRequest<ResetPasswordRequest>, so each application keeps its own record and its own cache-invalidation decision (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:24-27). That is the same hoist boundary the change-password vertical uses, and the summary on this record says so explicitly (ResetPasswordCommand.cs:10-12).
      15. Where it's used: built by PasswordResetController's CreateResetPasswordCommand override, a single expression-bodied new(request) (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.API/Controllers/PasswordResetController.cs:39), behind POST /Auth/reset-password; handled by ResetPasswordHandler.
    -

    UserConfiguration

    -
    -

    MMCA.ADC.Identity.Infrastructure · MMCA.ADC.Identity.Infrastructure.Persistence.EntityConfiguration · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Infrastructure/Persistence/EntityConfiguration/UserConfiguration.cs:12 · Level 8 · class (internal, sealed)

    -
    -
      -
    • What it is: the EF Core mapping for the User aggregate: column types and widths, the value-object conversion for Email, the ignored computed properties, and three indexes.
    • -
    • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> (base), EmailValueConverter, User and UserInvariants, and EF Core's EntityTypeBuilder<T>.
    • -
    • Concept introduced, the engine-typed configuration base and the shared length constants. [Rubric §8, Data Architecture] assesses whether the schema is declared explicitly rather than inferred, and [Rubric §15, Best Practices & Code Quality] covers why the numbers are not literals. Two things are worth internalizing. - First, the base class is what routes this entity to a physical data source: deriving from EntityTypeConfigurationSQLServer<User, UserIdentifierType> (UserConfiguration.cs:13) declares the engine, and the database name resolves from the module, which is how User lands in ADC_Identity rather than in a shared database. base.Configure(builder) (:18) must run first, because it applies the framework conventions (key, audit columns, the soft-delete global query filter) that this method then refines. - Second, every length that also matters to the domain is read from UserInvariants rather than typed twice: EmailMaxLength (:22), FirstNameMaxLength (:27), LastNameMaxLength (:32), and DeviceFieldMaxLength on all seven device columns (:54, :58, :62, :66, :70, :74, :78). The constants themselves are 100, 100, 100 and 256 (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/UserInvariants.cs:12-21). That is the mechanism that stops an over-long name from passing the domain check and then being truncated by the database.
    • -
    • Walkthrough
        -
      • Email (:20-24): HasConversion(new EmailValueConverter()) maps the Email value object onto a plain column, then HasMaxLength, IsUnicode(false) and IsRequired(). IsUnicode(false) recurs on nearly every string here: these become varchar, not nvarchar, halving the storage for values that are ASCII by definition (emails, role names, culture codes, URLs).
      • -
      • Names (:26-34): FirstName and LastName, both required, both bounded by the domain constants above.
      • -
      • Credentials (:36-41): PasswordHash and PasswordSalt are configured with no options at all, deliberately. The comment states that EF maps byte[] to varbinary(max) by default so no explicit length is needed (BR-204), and that external-login accounts hold empty arrays.
      • -
      • Fixed-width strings: Role at 50 (:43-46), LoginProvider at 50 and ProviderKey at 256 (:82-88), PreferredCulture and PreferredTheme at 10 each (:91-97), AvatarUrl at 512 (:100-102).
      • -
      • LinkedSpeakerId (:50): configured as a plain scalar property with no relationship at all. That is the visible consequence of ADR-006: Speaker lives in another database, so there is no foreign key to declare. The comment above it (:48-49) records the intent, a nullable link that is unique when non-null (BR-208, BR-209).
      • -
      • LockedOn (:104-106): the administrative lock instant (users:manage), nullable and configured with no explicit options; the comment states that null means the account is usable and the instant is what an operator needs to answer "since when" it was locked.
      • -
      • IsEmailConfirmed (:108-113): required, with HasDefaultValue(false) (ADR-116). The comment explains the default: it keeps the column non-nullable for rows written by anything that does not set the flag, and the migration backfills the accounts that existed before it did.
      • -
      • Ignored members (:115-118): FullName, IsExternalLogin and now IsLocked are computed on the aggregate, so builder.Ignore keeps EF from expecting columns for them.
      • -
      • Indexes (:120-131): a unique index on Email with no hand-written filter (:123), which is the BR-200 "email is the identity" rule enforced at the storage layer; a unique filtered index on LinkedSpeakerId with HasFilter("[LinkedSpeakerId] IS NOT NULL") (:125-127), which makes the User-to-Speaker link 1:1 while still allowing the many users who have no linked speaker; and a unique filtered composite on (LoginProvider, ProviderKey) (:129-131), so one external identity cannot be attached to two accounts.
      • -
      -
    • -
    • Concept introduced, what soft delete does to a unique index. [Rubric §8, Data Architecture]. Read the Email index again: it declares IsUnique() and no filter (:123), yet the row it guards is never physically deleted. A soft-deleted row still occupies its unique slot, so without help the address of a deleted account could never be reused. The help is a model-finalizing convention rather than a hand-written filter, and the comment above the index block says so (:120-122): SoftDeleteUniqueIndexConvention walks every soft-deletable entity type at model finalization (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:45-49), building the predicate through the same SoftDeleteFilterSql.Build a hand-authored index would reach (:56), and no-opping for Cosmos (:42-43). The behavior to internalize is that a hand-authored filter is extended, not skipped: an index with no filter gets the soft-delete clause outright (:65-70), an index that already constrains the soft-delete column is left exactly as it is (:72-75), and any other hand-written predicate is kept and AND-ed with the soft-delete clause (:77-79). So all three indexes here end up excluding deleted rows: Email gains the clause alone, and the two that spell their own HasFilter keep their own predicate plus the appended one. The convention's <remarks> records that skipping them, as an earlier version did, silently left exactly the hand-authored partial-unique indexes as the only ones a soft-deleted row could keep blocking (:17-25).
    • -
    • Why it's built this way: [Rubric §12, Performance & Scalability]: the two hand-filtered indexes are sparse-column cases where the majority of rows are null, so filtering keeps each index small and keeps writes to the common rows out of it entirely. [Rubric §11, Security]: the unique constraints on email and on the provider pair are the last line of defence behind the application-level uniqueness probes, so a race between two concurrent registrations fails at the database rather than producing two accounts.
    • -
    • Where it's used: discovered by assembly scan through IEntityConfigurationAssemblyProvider and applied when the concrete engine context builds the model declared by ModuleApplicationDbContext; the resulting schema is materialized by the per-service Identity migrations project.
    • -
    • Caveats / not-in-source: two small mismatches are worth knowing. First, the file comment warns that spelling the soft-delete clause here "would double it" (:120-122), but the convention guards that case with SoftDeleteFilterSql.ContainsPredicate and leaves such a filter untouched (SoftDeleteUniqueIndexConvention.cs:72-75), so the comment is more cautious than the code requires. Second, the erasure path is what frees the two provider slots on an already-deleted row: User.Anonymize nulls LoginProvider and ProviderKey and rewrites the address to a per-id deleted-{Id}@anonymized.invalid placeholder (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:465-503); LinkedSpeakerId is not cleared there, but because the convention appends the soft-delete clause to that index too, a soft-deleted user no longer blocks the slot.
    • -

    ChangePasswordHandler

    MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.UseCases.ChangePassword · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/ChangePassword/ChangePasswordHandler.cs:24 · Level 9 · class (sealed)

    • What it is: ADC's change-password handler. The class body is empty: the whole workflow lives in the framework base ChangePasswordHandlerBase<TUser, TCommand>, and this type exists to bind the generic parameters and to keep the name.
    • -
    • Depends on: ChangePasswordHandlerBase<TUser, TCommand> (base), IUnitOfWork, IPasswordHasher, ILogger<T>, IRefreshSessionStore, TimeProvider, User, and ChangePasswordCommand.
    • -
    • Concept introduced, the name-preserving thin subclass. [Rubric §15, Best Practices & Code Quality] assesses de-duplication across applications, and [Rubric §9, API & Contract Design] covers why the class name survives the move. ADC and Store carried line-identical copies of this handler, so the workflow was hoisted into MMCA.Common (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:13-16). What could not be hoisted is the error payload: every failure the framework returns carries a source, the base defaults it to GetType().Name through a virtual HandlerName (ChangePasswordHandlerBase.cs:48-53), and clients match on the string ChangePasswordHandler. Keeping an empty subclass under the original name makes the hoist invisible on the wire, and the <remarks> says so outright (ChangePasswordHandler.cs:14-23). It is a small pattern with a large consequence: a refactor that would otherwise be a breaking API change becomes a no-op for consumers. The same shape recurs at ChangePreferencesHandler, GetUserPreferencesHandler, and (for the controller layer) at PasswordResetController and UsersDataExportController.
    • -
    • Concept introduced, revoking live sessions on a credential change (SEC-Common-02). [Rubric §11, Security]. The constructor now also takes IRefreshSessionStore and a TimeProvider, forwarded straight to the base (ChangePasswordHandler.cs:24-35), so a successful change revokes every live refresh session on the account, not only the credential the caller just replaced. Without them a stolen refresh chain would outlive the very password change meant to remediate it, which is the one recovery step a compromised account has. ResetPasswordHandler carries the identical addition for the same reason.
    • -
    • Walkthrough: a primary constructor taking IUnitOfWork, IPasswordHasher, ILogger<ChangePasswordHandler>, IRefreshSessionStore and TimeProvider, forwarding all five to the base (ChangePasswordHandler.cs:24-35), with an empty body (:36-37). The inherited workflow (ChangePasswordHandlerBase.cs:56-101) is: null-guard the command (:46); load the user through the mutating repository and return Error.NotFound tagged with HandlerName when absent (:48-53); verify the supplied current password with passwordHasher.VerifyPassword(command.Request.CurrentPassword, user.PasswordHash, user.PasswordSalt) and return Error.Unauthorized("Auth.InvalidCurrentPassword", ...) on a mismatch (:55-59); hash the new password into a fresh hash and salt pair (:61); call user.ChangePassword(newHash, newSalt) and, only when that succeeds, save and log (:62-67); return the aggregate's Result unchanged (:69).
    • -
    • Why it's built this way: [Rubric §11, Security]. Note the division of responsibility the base encodes. Proving knowledge of the current password is a cryptographic operation, so it happens where the stored hash and salt are in hand, not in a request validator (a validator can only check that a value is present). The domain then applies its own invariants on the new credential material, and nothing is persisted unless both gates pass. The generic constraint where TUser : AuditableAggregateRootEntity<UserIdentifierType>, IPasswordChangeableUser (ChangePasswordHandlerBase.cs:40) is what lets the base call ChangePassword on an application's aggregate without knowing the concrete type, and the hashing scheme itself is ADR-032.
    • +
    • Depends on: ChangePasswordHandlerBase<TUser, TCommand> (base), IUnitOfWork, IPasswordHasher, ILogger<T>, IRefreshSessionStore, ILoginProtectionService, TimeProvider, User, and ChangePasswordCommand.
    • +
    • Concept introduced, the name-preserving thin subclass. [Rubric §15, Best Practices & Code Quality] assesses de-duplication across applications, and [Rubric §9, API & Contract Design] covers why the class name survives the move. ADC and Store carried line-identical copies of this handler, so the workflow was hoisted into MMCA.Common (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePassword/ChangePasswordHandlerBase.cs:14-17). What could not be hoisted is the error payload: every failure the framework returns carries a source, the base defaults it to GetType().Name through a virtual HandlerName (ChangePasswordHandlerBase.cs:57-62), and clients match on the string ChangePasswordHandler. Keeping an empty subclass under the original name makes the hoist invisible on the wire, and the <remarks> says so outright (ChangePasswordHandler.cs:14-23). It is a small pattern with a large consequence: a refactor that would otherwise be a breaking API change becomes a no-op for consumers. The same shape recurs at ChangePreferencesHandler, GetUserPreferencesHandler, and (for the controller layer) at PasswordResetController and UsersDataExportController.
    • +
    • Concept introduced, revoking live sessions on a credential change (SEC-Common-02). [Rubric §11, Security]. The constructor takes IRefreshSessionStore and a TimeProvider, forwarded straight to the base (ChangePasswordHandler.cs:24-39), so a successful change revokes every live refresh session on the account (ChangePasswordHandlerBase.cs:110-114), not only the credential the caller just replaced. Without them a stolen refresh chain would outlive the very password change meant to remediate it, which is the one recovery step a compromised account has. ResetPasswordHandler carries the identical addition for the same reason.
    • +
    • Concept introduced, throttling the current-password check (ADR-029). [Rubric §11, Security]. The constructor also takes ILoginProtectionService, forwarded to the base (ChangePasswordHandler.cs:29, :36). A wrong current password counts against the account through the same failed-attempt counter and exponential lockout that guard sign-in, so a caller holding a session cannot use the endpoint as an unthrottled password oracle (ChangePasswordHandlerBase.cs:34-40). The counter is keyed password-change:{userId} rather than by email, because the authenticated user exposes no address and a separate key keeps a change-password lockout from locking the owner out of sign-in (ChangePasswordHandlerBase.cs:37-39).
    • +
    • Walkthrough: a primary constructor taking IUnitOfWork, IPasswordHasher, ILogger<ChangePasswordHandler>, IRefreshSessionStore, ILoginProtectionService and TimeProvider, forwarding all six to the base (ChangePasswordHandler.cs:24-37), with an empty body (:38-39). The inherited workflow (ChangePasswordHandlerBase.cs:65-120) is: null-guard the command (:69); load the user through the mutating repository and return Error.NotFound tagged with HandlerName when absent (:71-76); reject an account with no stored hash or salt (an external-OAuth account) with the same Error.Unauthorized("Auth.InvalidCurrentPassword", ...) (:82-86); return the lockout failure if the password-change key is locked (:88-93); verify the supplied current password with passwordHasher.VerifyPassword(command.Request.CurrentPassword, user.PasswordHash, user.PasswordSalt) and, on a mismatch, increment the failed-attempt counter and return Error.Unauthorized("Auth.InvalidCurrentPassword", ...) (:95-100); reset the counter on a match (:102); hash the new password into a fresh hash and salt pair (:104); call user.ChangePassword(newHash, newSalt) and, only when that succeeds, save, revoke every refresh session and log (:105-117); return the aggregate's Result unchanged (:119).
    • +
    • Why it's built this way: [Rubric §11, Security]. Note the division of responsibility the base encodes. Proving knowledge of the current password is a cryptographic operation, so it happens where the stored hash and salt are in hand, not in a request validator (a validator can only check that a value is present). The domain then applies its own invariants on the new credential material, and nothing is persisted unless both gates pass. The generic constraint where TUser : AuditableAggregateRootEntity<UserIdentifierType>, IPasswordChangeableUser (ChangePasswordHandlerBase.cs:49) is what lets the base call ChangePassword on an application's aggregate without knowing the concrete type, and the hashing scheme itself is ADR-032.
    • Where it's used: resolved as ICommandHandler<ChangePasswordCommand, Result> and injected into AuthController (AuthController.cs:36), which exposes it through the base's PUT /auth/password action; the Profile page is the client.

    ChangePreferencesCommandValidator

    @@ -3045,7 +3135,7 @@

    ChangePreferencesHandler

  • What it is: ADC's change-preferences handler, an empty subclass. The whole workflow lives in ChangePreferencesHandlerBase<TUser, TCommand>; this type exists to bind the generic parameters and to keep the name.
  • Depends on: ChangePreferencesHandlerBase<TUser, TCommand> (base), IUnitOfWork, ILogger<T>, User, and ChangePreferencesCommand.
  • Concept reinforced, the name-preserving thin subclass (introduced at ChangePasswordHandler); the <remarks> here carries the same rationale, that the class name is kept so the source reported on every error stays ChangePreferencesHandler, which clients match on (ChangePreferencesHandler.cs:12-16). The behavior worth learning is the null-coalescing merge inside the base. [Rubric §27, Internationalization] assesses whether a locale choice survives a session, and [Rubric §24, Forms, Validation & UX Safety] covers partial updates. The merge is one expression (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ChangePreferences/ChangePreferencesHandlerBase.cs:53-55): user.UpdatePreferences(command.Request.Culture ?? user.PreferredCulture, command.Request.Theme ?? user.PreferredTheme). A request carrying only a culture re-supplies the stored theme, and the reverse, which is precisely why the app-bar language switcher cannot wipe the user's dark-mode choice. It is also the same "null means unchanged" contract ChangePreferencesCommandValidator encodes with its When clauses one stage earlier.
  • -
  • Walkthrough: a primary constructor taking IUnitOfWork and ILogger<ChangePreferencesHandler> and forwarding both to the base (ChangePreferencesHandler.cs:17-20), with an empty body (:21-22). The inherited workflow (ChangePreferencesHandlerBase.cs:40-63): a null guard on the command (:44); load through the mutating repository, unitOfWork.GetRepository<TUser, UserIdentifierType>(), returning Error.NotFound.WithSource(HandlerName).WithTarget(typeof(TUser).Name) when absent (:46-51); the merge above (:53-55); then, only when the returned Result is a success, SaveChangesAsync plus the shared UserUseCaseLog.PreferencesChanged log (:56-60); return the result unchanged (:62). Value checking is not the handler's job: User.UpdatePreferences combines the culture allowlist and the light/dark rule and hands back a failure the handler simply propagates.
  • +
  • Walkthrough: a primary constructor taking IUnitOfWork and ILogger<ChangePreferencesHandler> and forwarding both to the base (ChangePreferencesHandler.cs:17-20), with an empty body (:21-22). The inherited workflow (ChangePreferencesHandlerBase.cs:40-63): a null guard on the command (:44); load through the mutating repository, unitOfWork.GetRepository<TUser, UserIdentifierType>(), returning Error.NotFound.WithSource(HandlerName).WithTarget(typeof(TUser).Name) when absent (:46-51); the merge above (:53-55); then, only when the returned Result is a success, SaveChangesAsync plus the handler's own source-generated PreferencesChanged log (:56-60, declared at :65-66); return the result unchanged (:62). Value checking is not the handler's job: User.UpdatePreferences combines the culture allowlist and the light/dark rule and hands back a failure the handler simply propagates.
  • Why it's built this way: guarding both the save and the log behind IsSuccess means a rejected culture or theme produces a clean failure (a 400 at the edge) with no write and, just as importantly, no misleading "preferences changed" log line for an operator to chase later. The HandlerName indirection (ChangePreferencesHandlerBase.cs:37, GetType().Name) is what makes the empty subclass sufficient: the base reports the runtime type name, so the hoist is invisible on the wire.
  • Where it's used: resolved as ICommandHandler<ChangePreferencesCommand, Result> and injected into AuthController (AuthController.cs:37) as PUT /Auth/preferences; wrapped by the decorator pipeline, whose caching decorator performs the eviction declared on ChangePreferencesCommand.
  • Caveats / not-in-source: UpdatePreferences raises no domain event (User.cs:330-343), so a preference change writes the row and publishes nothing to the outbox.
  • @@ -3057,32 +3147,20 @@

    DeleteUserHandler

    • What it is: ADC's account-deletion handler (UC-21). Unlike the two thin siblings it is not empty: it inherits the shared erasure workflow from DeleteUserHandlerBase<TUser, TCommand> and supplies the two genuinely ADC-specific pieces, the privileged role and an erasure tail that announces the deletion cross-service and cleans up the avatar blob.
    • Depends on: DeleteUserHandlerBase<TUser, TCommand> (base), IUnitOfWork, IInternalCommandScheduler, ICacheService (taken only to forward to the base, :32, :34), TimeProvider, UserRole, UserDeleted (the Identity.Shared integration event), SetUserAvatarHandler (for its TryGetBlobName helper), DeleteAvatarBlobInternalCommand, and Result.
    • -
    • Concept introduced (1), erasure as a fixed workflow with application-specific hooks. [Rubric §30, Compliance, Privacy & Data Governance] assesses whether a deletion request actually destroys personal data. The base (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:76-159) runs a fixed order: check ownership through UserOwnershipRule.CheckOwnership using the application's HasDeletePrivilege answer (:82-92); load the user, Error.NotFound when absent (:94-99); soft-delete (:114-119); run the application's tail (:121-126); anonymize (:128-133); save (:135); write the shared soft-deleted marker (:137-149); then run whatever post-commit actions the tail enqueued (:151-154) and log the erasure (:156). The two-step delete-then-anonymize is ADR-005's resolution of a real tension: the row must survive because other bounded contexts hold scalar UserId references and the audit trail depends on it, but the personal data must not survive, because the privacy promise is erasure. One detail in the base rewards a second read (:109-115): it dispatches through IErasableUser erasable = user; rather than calling user.Delete() directly, because member lookup on a type parameter prefers its class constraint, and ADC's User hides the base Delete() with public new Result Delete() (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:443). Interface dispatch is what guarantees the aggregate's own version, the one that raises the in-process UserDeleted domain event (User.cs:445-451), actually runs.
    • +
    • Concept introduced (1), erasure as a fixed workflow with application-specific hooks. [Rubric §30, Compliance, Privacy & Data Governance] assesses whether a deletion request actually destroys personal data. The base (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/DeleteUser/DeleteUserHandlerBase.cs:80-163) runs a fixed order: check ownership through UserOwnershipRule.CheckOwnership using the application's HasDeletePrivilege answer (:82-92); load the user, Error.NotFound when absent (:94-99); soft-delete (:114-119); run the application's tail (:121-126); anonymize (:128-133); save (:135); write the shared soft-deleted marker (:137-149); then run whatever post-commit actions the tail enqueued (:151-154) and log the erasure (:156). The two-step delete-then-anonymize is ADR-005's resolution of a real tension: the row must survive because other bounded contexts hold scalar UserId references and the audit trail depends on it, but the personal data must not survive, because the privacy promise is erasure. One detail in the base rewards a second read (:109-115): it dispatches through IErasableUser erasable = user; rather than calling user.Delete() directly, because member lookup on a type parameter prefers its class constraint, and ADC's User hides the base Delete() with public new Result Delete() (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:443). Interface dispatch is what guarantees the aggregate's own version, the one that raises the in-process UserDeleted domain event (User.cs:445-451), actually runs.
    • Concept introduced (2), raising a cross-service integration event from inside the erasure. [Rubric §6, CQRS & Event-Driven] and [Rubric §30]. Personal data this account published into another service has to travel: Engagement holds a DisplayName snapshot on the leaderboard opt-in, Engagement is its own process with its own database, and the in-process domain event User.Delete() raises never reaches it. So the override calls user.AddDomainEvent(new UserDeleted(command.UserId, timeProvider.GetUtcNow())) (DeleteUserHandler.cs:59) on the aggregate, before the save. The comment (:52-57) states the invariant this buys: the outbox row is written by the very SaveChangesAsync that commits the erasure, so the fact and its announcement cannot come apart, whereas publishing after the commit would leave a crash window in which the account is gone and the published name is not. The event payload is deliberately just the id and a timestamp, because carrying a name or email would publish the very data the erasure exists to remove onto a broker that persists messages (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/IntegrationEvents/UserDeleted.cs:17-21); its own doc also spells out that it stays separate from the same-named Identity domain event, which Engagement never sees (:11-16).
    • -
    • Concept introduced (3), scheduling the blob delete as a durable internal command instead of a post-commit action. [Rubric §29, Resilience & Business Continuity] and [Rubric §30, Compliance, Privacy & Data Governance]. The hook signature still takes the base's ICollection<Func<CancellationToken, Task>> afterCommit parameter (DeleteUserHandler.cs:43-47), but no longer uses it: earlier the avatar delete was queued into that best-effort post-commit list, which only ran once the process survived past the save. Now, when there is a blob, the hook calls ScheduleAvatarBlobDeletionAsync (:68-70), which enrolls a DeleteAvatarBlobInternalCommand row through internalCommandScheduler.ScheduleAsync (:78-80). Because this hook runs inside the erasure transaction, before SaveChangesAsync (DeleteUserHandlerBase.cs:114-135), the scheduled row commits with the anonymized aggregate or rolls back with it (ADR-114), which is exactly why DeleteUserCommand is now marked ITransactional (DeleteUserCommand.cs:46-51). An erasure that persists can therefore never lose the instruction to delete the photo, unlike the best-effort post-commit call it replaces, which silently dropped the deletion whenever storage was unreachable or the host died mid-tail; the framework's internal-command processor now picks the row up on its next poll and retries it until storage answers. If the scheduling call itself fails, ScheduleAvatarBlobDeletionAsync returns Result.Failure(scheduled.Errors) (:85) rather than success, which fails the whole erasure: a partial erasure the caller was told succeeded is worse than a retryable failure.
    • +
    • Concept introduced (3), scheduling the blob delete as a durable internal command instead of a post-commit action. [Rubric §29, Resilience & Business Continuity] and [Rubric §30, Compliance, Privacy & Data Governance]. The hook signature still takes the base's ICollection<Func<CancellationToken, Task>> afterCommit parameter (DeleteUserHandler.cs:43-47), but no longer uses it: earlier the avatar delete was queued into that best-effort post-commit list, which only ran once the process survived past the save. Now, when there is a blob, the hook calls ScheduleAvatarBlobDeletionAsync (:68-70), which enrolls a DeleteAvatarBlobInternalCommand row through internalCommandScheduler.ScheduleAsync (:78-80). Because this hook runs inside the erasure transaction, before SaveChangesAsync (DeleteUserHandlerBase.cs:118-139), the scheduled row commits with the anonymized aggregate or rolls back with it (ADR-114), which is exactly why DeleteUserCommand is now marked ITransactional (DeleteUserCommand.cs:22). An erasure that persists can therefore never lose the instruction to delete the photo, unlike the best-effort post-commit call it replaces, which silently dropped the deletion whenever storage was unreachable or the host died mid-tail; the framework's internal-command processor now picks the row up on its next poll and retries it until storage answers. If the scheduling call itself fails, ScheduleAvatarBlobDeletionAsync returns Result.Failure(scheduled.Errors) (:85) rather than success, which fails the whole erasure: a partial erasure the caller was told succeeded is worse than a retryable failure.
    • Walkthrough
      • Constructor (:29-35): five dependencies, of which unitOfWork, cacheService and logger are forwarded straight to DeleteUserHandlerBase<User, DeleteUserCommand> (:35); only internalCommandScheduler and timeProvider are used by this type's own code. There is no logger field and no [LoggerMessage] partial here: the one warning this path can emit belongs to the base.
      • -
      • HasDeletePrivilege (:38-40): UserRole.IsOrganizer(currentUserRole), whose implementation is an OrdinalIgnoreCase comparison (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/UserRole.cs:76), with a <remarks> noting it is case-insensitive because a role claim may carry any casing. Store's equivalent answers with its Admin role, which the base's own hook documentation names alongside ADC's (DeleteUserHandlerBase.cs:161-164); that one line is the entire difference in the authorization model between the two applications.
      • -
      • OnAfterSoftDeleteAsync (:43-74): it first captures the avatar blob name before anonymization clears the URL (:49-51), reusing SetUserAvatarHandler.TryGetBlobName (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/SetUserAvatar/SetUserAvatarHandler.cs:145). That ordering is the whole reason the hook runs where it does. It then raises the integration event (:59) and, only when there was a blob, schedules its durable deletion via ScheduleAvatarBlobDeletionAsync instead of enqueuing a post-commit action (:68-70); with no blob it returns Result.Success() directly (:73).
      • -
      • ScheduleAvatarBlobDeletionAsync (:76-86): awaits internalCommandScheduler.ScheduleAsync(new DeleteAvatarBlobInternalCommand(avatarBlobName), ...) (:78-80) and translates a failed schedule into Result.Failure(scheduled.Errors) (:85), which propagates up through OnAfterSoftDeleteAsync and aborts the erasure before anything is persisted (DeleteUserHandlerBase.cs:121-126).
      • -
      • Marker failure policy (DeleteUserHandlerBase.cs:140-149): the shared cache write is wrapped in a try/catch that swallows everything except OperationCanceledException and logs through UserUseCaseLog.SoftDeletedMarkerFailed. The reasoning is stated on the base (:21-29): the deletion is already committed by the time this runs, the marker only shortens the window in which an already-issued token keeps working, and a cache fault must not turn a successful, irreversible erasure into a failure the caller would retry. That is the shape ADR-096 generalized into a policy.
      • +
      • HasDeletePrivilege (:38-40): UserRole.IsOrganizer(currentUserRole), whose implementation is an OrdinalIgnoreCase comparison (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/UserRole.cs:76), with a <remarks> noting it is case-insensitive because a role claim may carry any casing. Store's equivalent answers with its Admin role, which the base's own hook documentation names alongside ADC's (DeleteUserHandlerBase.cs:165-168); that one line is the entire difference in the authorization model between the two applications.
      • +
      • OnAfterSoftDeleteAsync (:43-74): it first captures the avatar blob name before anonymization clears the URL (:49-51), reusing SetUserAvatarHandler.TryGetBlobName (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/SetUserAvatar/SetUserAvatarHandler.cs:201). That ordering is the whole reason the hook runs where it does. It then raises the integration event (:59) and, only when there was a blob, schedules its durable deletion via ScheduleAvatarBlobDeletionAsync instead of enqueuing a post-commit action (:68-70); with no blob it returns Result.Success() directly (:73).
      • +
      • ScheduleAvatarBlobDeletionAsync (:76-86): awaits internalCommandScheduler.ScheduleAsync(new DeleteAvatarBlobInternalCommand(avatarBlobName), ...) (:78-80) and translates a failed schedule into Result.Failure(scheduled.Errors) (:85), which propagates up through OnAfterSoftDeleteAsync and aborts the erasure before anything is persisted (DeleteUserHandlerBase.cs:125-130).
      • +
      • Marker failure policy (DeleteUserHandlerBase.cs:144-153): the shared cache write is wrapped in a try/catch that swallows everything except OperationCanceledException and logs through the handler's own source-generated SoftDeletedMarkerFailed (:152, declared at :200). The reasoning is stated on the base (:21-29): the deletion is already committed by the time this runs, the marker only shortens the window in which an already-issued token keeps working, and a cache fault must not turn a successful, irreversible erasure into a failure the caller would retry. That is the shape ADR-096 generalized into a policy.
    • -
    • Why it's built this way: [Rubric §11, Security]. Access tokens are self-contained and valid until they expire, so deleting an account does not by itself stop a token already in the wild; the marker is what lets the shared SoftDeletedUserMiddleware reject those requests, and it lives in the base so every application gets the identical revocation window rather than each one re-implementing it in its own tail (DeleteUserHandlerBase.cs:21-25). Refresh sessions need no equivalent treatment, and the base says why: they live in their own table and the refresh flow re-fetches the account through the soft-delete query filter, so every outstanding session stops working the moment this commits (DeleteUserHandlerBase.cs:103-107, echoed on the aggregate at User.cs:436-441). Treating the access-token marker as best-effort is the correct trade: erasure is the promise that must hold, and the token window is bounded anyway. [Rubric §30]: the avatar photo is personal data too (BR-116a), so its deletion is scheduled durably rather than merely being a best-effort call.
    • +
    • Why it's built this way: [Rubric §11, Security]. Access tokens are self-contained and valid until they expire, so deleting an account does not by itself stop a token already in the wild; the marker is what lets the shared SoftDeletedUserMiddleware reject those requests, and it lives in the base so every application gets the identical revocation window rather than each one re-implementing it in its own tail (DeleteUserHandlerBase.cs:21-25). Refresh sessions need no equivalent treatment, and the base says why: they live in their own table and the refresh flow re-fetches the account through the soft-delete query filter, so every outstanding session stops working the moment this commits (DeleteUserHandlerBase.cs:107-111, echoed on the aggregate at User.cs:436-441). Treating the access-token marker as best-effort is the correct trade: erasure is the promise that must hold, and the token window is bounded anyway. [Rubric §30]: the avatar photo is personal data too (BR-116a), so its deletion is scheduled durably rather than merely being a best-effort call.
    • Where it's used: resolved as ICommandHandler<DeleteUserCommand, Result> and invoked by UsersController's DeleteAsync (UsersController.cs:166-181), which returns 204 on success; the callers are the Profile page's self-service deletion and the organizer UserList. The UserDeleted integration event it raises is consumed downstream by Engagement's UserDeletedPointsHandler. The scheduled DeleteAvatarBlobInternalCommand is drained later by DeleteAvatarBlobInternalCommandHandler.
    • -
    • Caveats / not-in-source: two things to watch. First, the avatar delete no longer runs inline during the request: ScheduleAvatarBlobDeletionAsync only enrolls the internal-command row (DeleteUserHandler.cs:76-86), and the blob is actually removed later, off the request, whenever the internal-command processor drains it; a caller who deletes the account cannot assume the photo is already gone at the moment the 204 comes back. Second, this class's own summary still says the deletion "revokes the refresh token (BR-56)" (DeleteUserHandler.cs:15), which the code no longer does anywhere on this path: sessions are invalidated implicitly by the soft-delete filter, as both the base (DeleteUserHandlerBase.cs:103-107) and User.Delete (User.cs:436-441) now spell out. Trust the code; the summary line is stale.
    • -
    -

    GetUserPreferencesHandler

    -
    -

    MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.UseCases.GetPreferences · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/GetPreferences/GetUserPreferencesHandler.cs:13 · Level 9 · class (sealed)

    -
    -
      -
    • What it is: the read side of user preferences: given a user id, return the stored culture and theme. Another empty subclass, this one over GetUserPreferencesHandlerBase<TUser>.
    • -
    • Depends on: GetUserPreferencesHandlerBase<TUser> (base), IUnitOfWork, and User. Note what is absent: no ILogger. A query that only reads has nothing to announce.
    • -
    • Concept reinforced, the thin CQRS read handler (the subclass shape is taught at ChangePasswordHandler; the query pipeline at IQueryHandler<in TQuery, TResult>). [Rubric §6, CQRS & Event-Driven]: the base implements IQueryHandler<GetUserPreferencesQuery, Result<UserPreferencesResponse>> (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/GetPreferences/GetUserPreferencesHandlerBase.cs:21-22), so it runs through the query decorator chain with no Validating and no Transactional decorator, because a read opens no transaction. Both GetUserPreferencesQuery and UserPreferencesResponse were byte-identical in the two applications and were hoisted whole (:10-13), which is why this base is generic in the aggregate only, and why its constraint is the lighter AuditableBaseEntity<UserIdentifierType>, IUserPreferences rather than the aggregate-root constraint the write bases need (:23). [Rubric §27, Internationalization]: this is the server end of "remember my language", reading User.PreferredCulture straight off the aggregate.
    • -
    • Walkthrough: a primary constructor taking IUnitOfWork and forwarding it (GetUserPreferencesHandler.cs:13-14), empty body. The inherited HandleAsync (GetUserPreferencesHandlerBase.cs:33-45) guards the query for null (:37), resolves unitOfWork.GetReadRepository<TUser, UserIdentifierType>() (:39), calls GetByIdAsync(query.UserId, ...) (:40), and returns either Error.NotFound tagged with HandlerName and the aggregate's type name or Result.Success(new UserPreferencesResponse(user.PreferredCulture, user.PreferredTheme)) (:41-44).
    • -
    • Why it's built this way: the base's <remarks> (GetUserPreferencesHandlerBase.cs:15-19) records that the two application copies disagreed on the repository (ADC read, Store write) and that the read repository is the correct choice for a handler which never calls SaveChangesAsync, so Store gained a no-tracking read on adoption. That is the ordinary payoff of consolidating duplicated code: the merge forces a decision, and the better of the two behaviors wins for everyone.
    • -
    • Where it's used: resolved as IQueryHandler<GetUserPreferencesQuery, Result<UserPreferencesResponse>> and injected into AuthController (AuthController.cs:35) as GET /Auth/preferences; the response seeds the client's culture and theme at startup.
    • +
    • Caveats / not-in-source: two things to watch. First, the avatar delete no longer runs inline during the request: ScheduleAvatarBlobDeletionAsync only enrolls the internal-command row (DeleteUserHandler.cs:76-86), and the blob is actually removed later, off the request, whenever the internal-command processor drains it; a caller who deletes the account cannot assume the photo is already gone at the moment the 204 comes back. Second, this class's own summary still says the deletion "revokes the refresh token (BR-56)" (DeleteUserHandler.cs:15), which the code no longer does anywhere on this path: sessions are invalidated implicitly by the soft-delete filter, as both the base (DeleteUserHandlerBase.cs:107-111) and User.Delete (User.cs:436-441) now spell out. Trust the code; the summary line is stale.

    ResetPasswordHandler

    @@ -3092,7 +3170,7 @@

    ResetPasswordHandler

  • What it is: ADC's completion step for a forgotten password: redeem the single-use token, set the new credential, then clear the account's lockout. Like its change-preferences and get-preferences siblings it is an empty subclass; the workflow lives in ResetPasswordHandlerBase<TUser, TCommand>.
  • Depends on: ResetPasswordHandlerBase<TUser, TCommand> (base), IUnitOfWork, IPasswordHasher, IPasswordResetTokenService, ILoginProtectionService, ILogger<T>, IRefreshSessionStore, TimeProvider, User, and ResetPasswordCommand.
  • Concept introduced, revoking live sessions on a completed reset (SEC-Common-02). [Rubric §11, Security]. The constructor now also takes IRefreshSessionStore and a TimeProvider, forwarded straight to the base alongside the other five dependencies (ResetPasswordHandler.cs:24-39), so a completed reset revokes every live session on the account. A reset is the remediation for an account someone else is signed into, and without this addition it evicted nobody. ChangePasswordHandler carries the identical addition for the same reason.
  • -
  • Concept introduced, uniform failure as an anti-enumeration device. [Rubric §11, Security] assesses whether an anonymous endpoint leaks facts about accounts it will not authenticate. The base collapses every rejection to one error: Error.Unauthorized("Auth.InvalidResetToken", "The reset link is invalid or has expired. Please request a new one.", HandlerName) (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:116-120). An unknown token, an expired token, a token issued for a different address, a token past its validation-attempt cap and an account that has since vanished are all indistinguishable to the caller, which the <remarks> states as the point: the endpoint reveals nothing about which addresses hold accounts or which tokens exist (:18-22). The two failure branches differ only in the reason string handed to the log, "token rejected" (:66) and "account no longer resolvable" (:75), so an operator can still tell them apart while the client cannot. [Rubric §13, Observability & Operability]: both go through the shared UserUseCaseLog helpers (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UserUseCaseLog.cs:35, :37), and the success line records only the user id, never the address or the token.
  • +
  • Concept introduced, uniform failure as an anti-enumeration device. [Rubric §11, Security] assesses whether an anonymous endpoint leaks facts about accounts it will not authenticate. The base collapses every rejection to one error: Error.Unauthorized("Auth.InvalidResetToken", "The reset link is invalid or has expired. Please request a new one.", HandlerName) (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/ResetPassword/ResetPasswordHandlerBase.cs:116-120). An unknown token, an expired token, a token issued for a different address, a token past its validation-attempt cap and an account that has since vanished are all indistinguishable to the caller, which the <remarks> states as the point: the endpoint reveals nothing about which addresses hold accounts or which tokens exist (:18-22). The two failure branches differ only in the reason string handed to the log, "token rejected" (:81) and "account no longer resolvable" (:90), so an operator can still tell them apart while the client cannot. [Rubric §13, Observability & Operability]: both go through the handler's own source-generated [LoggerMessage] methods, PasswordResetRejected and PasswordResetCompleted (ResetPasswordHandlerBase.cs:122-128), and the success line records only the user id, never the address or the token.
  • Concept introduced, consume the token before the write. [Rubric §11, Security] again, and the ordering is the interesting part. The base redeems the token first (ResetPasswordHandlerBase.cs:76-78) and only then loads the user, hashes and saves. The comment states the trade explicitly (:58-60): leaving the token live until the write succeeds would open a replay window in which the same token redeems twice, so the token is burned up front, and the cost of a later invariant failure is that the user requests one more reset. Note also that the account is never named by the request: userId comes out of the redeemed token (:70), so the endpoint cannot be pointed at somebody else's account.
  • Walkthrough: a primary constructor taking the seven dependencies and forwarding all of them to the base (ResetPasswordHandler.cs:24-39), with an empty body (:40-41). The inherited HandleAsync (ResetPasswordHandlerBase.cs:65-114): null guard (:54); read the payload through the ICommandWithRequest<ResetPasswordRequest> constraint (:56), which is the whole reason the base never mentions ADC's command type; tokenService.ValidateAndConsumeAsync(request.Email, request.Token, ...) and the uniform failure on rejection (:61-68); load through the mutating repository by the id the token yielded, same uniform failure when the row is gone (:71-77); passwordHasher.HashPassword(request.NewPassword) and user.ChangePassword(newHash, newSalt) (:79-84), which returns the aggregate's own Result and is propagated untouched on failure; SaveChangesAsync (:86); then loginProtection.ResetFailedAttemptsAsync(request.Email, ...) (:89) with the one-line reason above it, that a user who reset the password because of a lockout must not stay locked out (:88); finally the success log and the result (:91-92).
  • Why it's built this way: the token material never touches the database. ADR-091 puts it in the cache, hashed at rest, with a per-email request throttle and a per-token validation-attempt cap owned by IPasswordResetTokenService (MMCA.Common/Source/Core/MMCA.Common.Application/Auth/IPasswordResetTokenService.cs:5-9), so the reset vertical adds no schema and no migration. The clean split between "who owns the token" (the service) and "who owns the credential" (the aggregate, through User.ChangePassword at MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:360) is what lets this handler be twelve lines of forwarding.
  • @@ -3114,7 +3192,7 @@

    AttendeeQueryServiceGrpcAdapter

  • Why it's built this way: [Rubric §29, Resilience & Business Continuity]. Choosing a deadline shorter than the retry budget is the difference between "this dependency is slow" and "this request never returns". Letting transport failures surface rather than swallowing them means a broadcast that could not determine its audience fails visibly instead of quietly notifying nobody.
  • -
  • Where it's used: registered by DependencyInjection's AddIdentityAttendeeClient in this same project, which the Notification service host calls inside its application-pipeline registration (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:225). Its server-side counterpart is AttendeesGrpcService.
  • +
  • Where it's used: registered by DependencyInjection's AddIdentityAttendeeClient in this same project, which the Notification service host calls inside its application-pipeline registration (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:222). Its server-side counterpart is AttendeesGrpcService.
  • Caveats / not-in-source: the interface returns a plain list rather than a Result, so a transport fault reaches the caller as a raw RpcException after the Polly pipeline gives up; there is no Result.Failure translation on this path.
  • AttendeesGrpcService

    @@ -3131,8 +3209,20 @@

    AttendeesGrpcService

  • Response construction (:32-34): a fresh GetAttendeeUserIdsResponse, then response.UserIds.AddRange(userIds). Protobuf repeated fields are read-only collections that are populated, never assigned.
  • -
  • Why it's built this way: [Rubric §11, Security]. The endpoint is mapped with app.MapGrpcService<AttendeesGrpcService>().RequireAuthorization() (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:373), and the comment above the mapping (:327-334) is worth reading in full: the response enumerates attendee user ids, so internal-only ingress is not considered sufficient, and the authorization holds today only because the single caller chain is HTTP-triggered (Notification's AttendeeNotificationRecipientProvider under an organizer-authorized controller), which means an inbound bearer always exists for JwtForwardingClientInterceptor to forward. The comment states the standing condition explicitly: a scheduler or broker-consumer path would run with no HttpContext, forward no bearer, and would need its own credential before it ships.
  • -
  • Where it's used: registered through AddGrpcServiceDefaults() (Program.cs:330, whose comment at :298-303 names this class as the endpoint it publishes) and mapped at Program.cs:373; the only client is AttendeeQueryServiceGrpcAdapter. The service serves it over Kestrel's HTTP/2 cleartext channel, the transport profile of ADR-012. Reflection is mapped only in Development (:337-340).
  • +
  • Why it's built this way: [Rubric §11, Security]. The endpoint is mapped with app.MapGrpcService<AttendeesGrpcService>().RequireAuthorization() (MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/Program.cs:362), and the comment above the mapping (:327-334) is worth reading in full: the response enumerates attendee user ids, so internal-only ingress is not considered sufficient, and the authorization holds today only because the single caller chain is HTTP-triggered (Notification's AttendeeNotificationRecipientProvider under an organizer-authorized controller), which means an inbound bearer always exists for JwtForwardingClientInterceptor to forward. The comment states the standing condition explicitly: a scheduler or broker-consumer path would run with no HttpContext, forward no bearer, and would need its own credential before it ships.
  • +
  • Where it's used: registered through AddGrpcServiceDefaults() (Program.cs:329, whose comment at :298-303 names this class as the endpoint it publishes) and mapped at Program.cs:362; the only client is AttendeeQueryServiceGrpcAdapter. The service serves it over Kestrel's HTTP/2 cleartext channel, the transport profile of ADR-012. Reflection is mapped only in Development (:337-340).
  • + +

    GetUserPreferencesHandler

    +
    +

    MMCA.ADC.Identity.Application · MMCA.ADC.Identity.Application.Users.UseCases.GetPreferences · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/UseCases/GetPreferences/GetUserPreferencesHandler.cs:13 · Level 9 · class (sealed)

    +
    +
      +
    • What it is: the read side of user preferences: given a user id, return the stored culture and theme. Another empty subclass, this one over GetUserPreferencesHandlerBase<TUser>.
    • +
    • Depends on: GetUserPreferencesHandlerBase<TUser> (base), IUnitOfWork, and User. Note what is absent: no ILogger. A query that only reads has nothing to announce.
    • +
    • Concept reinforced, the thin CQRS read handler (the subclass shape is taught at ChangePasswordHandler; the query pipeline at IQueryHandler<in TQuery, TResult>). [Rubric §6, CQRS & Event-Driven]: the base implements IQueryHandler<GetUserPreferencesQuery, Result<UserPreferencesResponse>> (MMCA.Common/Source/Core/MMCA.Common.Application/Users/UseCases/GetPreferences/GetUserPreferencesHandlerBase.cs:21-22), so it runs through the query decorator chain with no Validating and no Transactional decorator, because a read opens no transaction. Both GetUserPreferencesQuery and UserPreferencesResponse were byte-identical in the two applications and were hoisted whole (:10-13), which is why this base is generic in the aggregate only, and why its constraint is the lighter AuditableBaseEntity<UserIdentifierType>, IUserPreferences rather than the aggregate-root constraint the write bases need (:23). [Rubric §27, Internationalization]: this is the server end of "remember my language", reading User.PreferredCulture straight off the aggregate.
    • +
    • Walkthrough: a primary constructor taking IUnitOfWork and forwarding it (GetUserPreferencesHandler.cs:13-14), empty body. The inherited HandleAsync (GetUserPreferencesHandlerBase.cs:33-45) guards the query for null (:37), resolves unitOfWork.GetReadRepository<TUser, UserIdentifierType>() (:39), calls GetByIdAsync(query.UserId, ...) (:40), and returns either Error.NotFound tagged with HandlerName and the aggregate's type name or Result.Success(new UserPreferencesResponse(user.PreferredCulture, user.PreferredTheme)) (:41-44).
    • +
    • Why it's built this way: the base's <remarks> (GetUserPreferencesHandlerBase.cs:15-19) records that the two application copies disagreed on the repository (ADC read, Store write) and that the read repository is the correct choice for a handler which never calls SaveChangesAsync, so Store gained a no-tracking read on adoption. That is the ordinary payoff of consolidating duplicated code: the merge forces a decision, and the better of the two behaviors wins for everyone.
    • +
    • Where it's used: resolved as IQueryHandler<GetUserPreferencesQuery, Result<UserPreferencesResponse>> and injected into AuthController (AuthController.cs:35) as GET /Auth/preferences; the response seeds the client's culture and theme at startup.

    DependencyInjection

    @@ -3145,7 +3235,7 @@

    DependencyInjection

    The consequence is an ordering rule, stated in the same comment (:33-37): call this after ModuleLoader.DiscoverAndRegister(...), so the in-process or stub registration is in the container by the time Replace looks for it. Calling it earlier is not an error the compiler or the container reports; it simply leaves the wrong implementation in place.
  • Walkthrough: the method lives inside an extension(IServiceCollection services) block (:16), the workspace idiom for DI registration (see primer §4). AddIdentityAttendeeClient(string serviceName = "identity") (:41) does two things: AddTypedGrpcClient<AttendeeQueryService.AttendeeQueryServiceClient>(serviceName) (:43), which the framework wires to Aspire service discovery at http://{serviceName} over HTTP/2 cleartext with the standard JwtForwardingClientInterceptor and Polly resilience handler; and services.Replace(ServiceDescriptor.Scoped<IAttendeeQueryService, AttendeeQueryServiceGrpcAdapter>()) (:47), with the inline comment restating the Replace-not-TryAdd rule at the point of use (:45-46). It then returns services for chaining (:49). The serviceName default of "identity" matches the AppHost resource name, so the common case passes no argument.
  • Why it's built this way: keeping this helper in the .Contracts project rather than in the consuming service means the knowledge of "how you talk to Identity remotely" lives once, next to the .proto that defines the call, and every future consumer gets it with a project reference plus one line (ADR-007). The Scoped lifetime matches the in-process implementation it replaces, so swapping transports changes no lifetime assumption anywhere in the graph.
  • -
  • Where it's used: called by the Notification service host as the second of three ordered steps inside AddMmcaApplicationPipeline, between module registration and broker messaging (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:223-227). That enclosing call is what makes the ordering rule structural rather than a convention: the pipeline helper runs AddApplication(), then every registration callback, then AddApplicationDecorators(), and seals, so Replace is guaranteed to see the module's registration and the decorators are guaranteed to wrap what results (:194-214, which repeats the Replace rationale at the call site). The matching AppHost reference is noted at MMCA.ADC/Source/Hosting/MMCA.ADC.AppHost/Program.cs:268.
  • +
  • Where it's used: called by the Notification service host as the second of three ordered steps inside AddMmcaApplicationPipeline, between module registration and broker messaging (MMCA.ADC/Source/Services/MMCA.ADC.Notification.Service/Program.cs:220-224). That enclosing call is what makes the ordering rule structural rather than a convention: the pipeline helper runs AddApplication(), then every registration callback, then AddApplicationDecorators(), and seals, so Replace is guaranteed to see the module's registration and the decorators are guaranteed to wrap what results (:194-214, which repeats the Replace rationale at the call site). The matching AppHost reference is noted at MMCA.ADC/Source/Hosting/MMCA.ADC.AppHost/Program.cs:268.
  • Caveats / not-in-source: this is the only public member of the class today, so the .Contracts project's DI surface is exactly this one call.
  • ModuleApplicationDbContext

    @@ -3160,6 +3250,34 @@

    ModuleApplicationDbContext

  • Why it's built this way: keeping the module's contribution abstract is what lets the same entity declarations be hosted by a SQL Server context in production and, with no code change, by a different engine's context. It is also what makes the "never split the context per module" rule enforceable: modules add abstract declarations, they never introduce a second concrete context class (ADR-006).
  • Where it's used: inherited by the concrete engine context the framework's physical context factory builds for the ADC_Identity database (the connection string the extracted service resolves, MMCA.ADC/Source/Services/MMCA.ADC.Identity.Service/appsettings.json:52); that database also carries its own dbo.OutboxMessages table, so Identity's outbox never contends with another service's.
  • +

    UserConfiguration

    +
    +

    MMCA.ADC.Identity.Infrastructure · MMCA.ADC.Identity.Infrastructure.Persistence.EntityConfiguration · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Infrastructure/Persistence/EntityConfiguration/UserConfiguration.cs:12 · Level 14 · class (internal, sealed)

    +
    +
      +
    • What it is: the EF Core mapping for the User aggregate: column types and widths, the value-object conversion for Email, the ignored computed properties, and three indexes.
    • +
    • Depends on: EntityTypeConfigurationSQLServer<TEntity, TIdentifierType> (base), EmailValueConverter, User and UserInvariants, and EF Core's EntityTypeBuilder<T>.
    • +
    • Concept introduced, the engine-typed configuration base and the shared length constants. [Rubric §8, Data Architecture] assesses whether the schema is declared explicitly rather than inferred, and [Rubric §15, Best Practices & Code Quality] covers why the numbers are not literals. Two things are worth internalizing. + First, the base class is what routes this entity to a physical data source: deriving from EntityTypeConfigurationSQLServer<User, UserIdentifierType> (UserConfiguration.cs:13) declares the engine, and the database name resolves from the module, which is how User lands in ADC_Identity rather than in a shared database. base.Configure(builder) (:18) must run first, because it applies the framework conventions (key, audit columns, the soft-delete global query filter) that this method then refines. + Second, every length that also matters to the domain is read from UserInvariants rather than typed twice: EmailMaxLength (:22), FirstNameMaxLength (:29), LastNameMaxLength (:33), and DeviceFieldMaxLength on all seven device columns (:54, :58, :62, :68, :71, :74, :77). The constants themselves are 100, 100, 100 and 256 (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/UserInvariants.cs:12-21). That is the mechanism that stops an over-long name from passing the domain check and then being truncated by the database.
    • +
    • Walkthrough
        +
      • Email (:20-24): HasConversion(new EmailValueConverter()) maps the Email value object onto a plain column, then HasMaxLength, IsUnicode(false) and IsRequired(). IsUnicode(false) recurs on every string whose values are ASCII by definition (emails, role names, device identifiers, provider keys, culture codes, URLs): these become varchar, not nvarchar, halving the storage. The rule is the split, not a blanket default: human-entered text omits IsUnicode(false) and so stays nvarchar (M148).
      • +
      • Names (:26-34): FirstName and LastName, both required, both bounded by the domain constants above, and both deliberately left Unicode. The comment (:26-27) states why: names are human text in any script, so a non-Latin name is stored as entered instead of being corrupted to ? by a varchar code page.
      • +
      • Device metadata (:52-78): seven optional, MAUI-only columns (BR-201, BR-202), all bounded by DeviceFieldMaxLength. The machine identifiers DeviceId, DeviceFormFactor, DevicePlatform and DeviceType stay varchar, while DeviceModel, DeviceManufacturer and the user-chosen DeviceName are free text and stay nvarchar (:65-74).
      • +
      • Credentials (:36-41): PasswordHash and PasswordSalt are configured with no options at all, deliberately. The comment states that EF maps byte[] to varbinary(max) by default so no explicit length is needed (BR-204), and that external-login accounts hold empty arrays.
      • +
      • Fixed-width strings: Role at 50 (:43-46), LoginProvider at 50 and ProviderKey at 256 (:80-87), PreferredCulture and PreferredTheme at 10 each (:89-96), AvatarUrl at 512 (:98-101).
      • +
      • LinkedSpeakerId (:50): configured as a plain scalar property with no relationship at all. That is the visible consequence of ADR-006: Speaker lives in another database, so there is no foreign key to declare. The comment above it (:48-49) records the intent, a nullable link that is unique when non-null (BR-208, BR-209).
      • +
      • LockedOn (:103-105): the administrative lock instant (users:manage), nullable and configured with no explicit options; the comment states that null means the account is usable and the instant is what an operator needs to answer "since when" it was locked.
      • +
      • IsEmailConfirmed (:107-112): required, with HasDefaultValue(false) (ADR-116). The comment explains the default: it keeps the column non-nullable for rows written by anything that does not set the flag, and the migration backfills the accounts that existed before it did.
      • +
      • Ignored members (:114-117): FullName, IsExternalLogin and IsLocked are computed on the aggregate, so builder.Ignore keeps EF from expecting columns for them.
      • +
      • Indexes (:119-130): a unique index on Email with no hand-written filter (:122), which is the BR-200 "email is the identity" rule enforced at the storage layer; a unique filtered index on LinkedSpeakerId with HasFilter("[LinkedSpeakerId] IS NOT NULL") (:124-126), which makes the User-to-Speaker link 1:1 while still allowing the many users who have no linked speaker; and a unique filtered composite on (LoginProvider, ProviderKey) (:128-130), so one external identity cannot be attached to two accounts.
      • +
      +
    • +
    • Concept introduced, what soft delete does to a unique index. [Rubric §8, Data Architecture]. Read the Email index again: it declares IsUnique() and no filter (:122), yet the row it guards is never physically deleted. A soft-deleted row still occupies its unique slot, so without help the address of a deleted account could never be reused. The help is a model-finalizing convention rather than a hand-written filter, and the comment above the index block says so (:119-121): SoftDeleteUniqueIndexConvention walks every soft-deletable entity type at model finalization (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/SoftDeleteUniqueIndexConvention.cs:46-50), building the predicate through the same SoftDeleteFilterSql.Build a hand-authored index would reach (:57), and no-opping for Cosmos (:43-44). The behavior to internalize is that a hand-authored filter is extended, not skipped: an index with no filter gets the soft-delete clause outright (:66-71), an index that already constrains the soft-delete column is left exactly as it is (:73-76), and any other hand-written predicate is kept and AND-ed with the soft-delete clause (:78-80). So all three indexes here end up excluding deleted rows: Email gains the clause alone, and the two that spell their own HasFilter keep their own predicate plus the appended one. The convention's doc comment records that skipping them, as an earlier version did, silently left exactly the hand-authored partial-unique indexes as the only ones a soft-deleted row could keep blocking (:19-26).
    • +
    • Why it's built this way: [Rubric §12, Performance & Scalability]: the two hand-filtered indexes are sparse-column cases where the majority of rows are null, so filtering keeps each index small and keeps writes to the common rows out of it entirely. [Rubric §11, Security]: the unique constraints on email and on the provider pair are the last line of defence behind the application-level uniqueness probes, so a race between two concurrent registrations fails at the database rather than producing two accounts.
    • +
    • Where it's used: discovered by assembly scan through IEntityConfigurationAssemblyProvider and applied when the concrete engine context builds the model declared by ModuleApplicationDbContext; the resulting schema is materialized by the per-service Identity migrations project.
    • +
    • Caveats / not-in-source: two small mismatches are worth knowing. First, the file comment warns that spelling the soft-delete clause here "would double it" (:119-121), but the convention guards that case with SoftDeleteFilterSql.ContainsPredicate and leaves such a filter untouched (SoftDeleteUniqueIndexConvention.cs:73-76), so the comment is more cautious than the code requires. Second, the erasure path is what frees the two provider slots on an already-deleted row: User.Anonymize nulls LoginProvider and ProviderKey and rewrites the address to a per-id deleted-{Id}@anonymized.invalid placeholder (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:465-503); LinkedSpeakerId is not cleared there, but because the convention appends the soft-delete clause to that index too, a soft-deleted user no longer blocks the slot.
    • +

    IdentityModuleDbSeeder

    MMCA.ADC.Identity.Infrastructure · MMCA.ADC.Identity.Infrastructure.Persistence.DbContexts.Seeding · MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Infrastructure/Persistence/DbContexts/Seeding/IdentityModuleDbSeeder.cs:28 · Level 15 · class

    diff --git a/docs/onboarding/group-25-adc-host-composition.html b/docs/onboarding/group-25-adc-host-composition.html index 53ded6bf..351c9616 100644 --- a/docs/onboarding/group-25-adc-host-composition.html +++ b/docs/onboarding/group-25-adc-host-composition.html @@ -176,7 +176,7 @@

    25. ADC Appl interactive Server circuit), MMCA.ADC.UI.Web.Client (the Blazor WebAssembly client, compiled to run in the browser), and MMCA.ADC.UI (the .NET MAUI host, which packages the same components into a native app and renders them in a BlazorWebView). Read the three composition - roots side by side (MMCA.ADC.UI.Web/Program.cs:34-178, MMCA.ADC.UI.Web.Client/Program.cs:23-76, + roots side by side (MMCA.ADC.UI.Web/Program.cs:35-190, MMCA.ADC.UI.Web.Client/Program.cs:23-76, MMCA.ADC.UI/MauiProgram.cs:53-198) and the family resemblance is obvious: the same MudBlazor registration, the same AddUIShared(builder.Configuration), the same four conditional module registrations, then a short tail of host-specific adapters. [Rubric §18, UI Architecture] assesses @@ -194,25 +194,25 @@

    25. ADC Appl host-specific. Home-page content: IHomePageContent lets the shared / route render an app-specific landing page, and each head registers its own - ADCHomePageContent (web MMCA.ADC.UI.Web/Program.cs:92 and + ADCHomePageContent (web MMCA.ADC.UI.Web/Program.cs:93 and MMCA.ADC.UI.Web.Client/Program.cs:45, MAUI MMCA.ADC.UI/MauiProgram.cs:124). Token storage: ITokenStorageService abstracts where JWTs live, and each head picks its implementation in one line: AddCommonMauiTokenStorage() on MAUI (MMCA.ADC.UI/MauiProgram.cs:163, backed by the framework's MauiTokenStorageService), - AddCommonServerTokenStorage() on the Server head (MMCA.ADC.UI.Web/Program.cs:144, backed by + AddCommonServerTokenStorage() on the Server head (MMCA.ADC.UI.Web/Program.cs:145, backed by ServerTokenStorageService), and an explicit WasmTokenStorageService registration in the browser client (MMCA.ADC.UI.Web.Client/Program.cs:48). The refresher behind ITokenRefresher splits the same way: the two browser heads use SameOriginProxyTokenRefresher - (MMCA.ADC.UI.Web/Program.cs:145, MMCA.ADC.UI.Web.Client/Program.cs:49) while MAUI, which has no + (MMCA.ADC.UI.Web/Program.cs:146, MMCA.ADC.UI.Web.Client/Program.cs:49) while MAUI, which has no same-origin proxy to lean on, uses DirectApiTokenRefresher (MMCA.ADC.UI/MauiProgram.cs:164). Form factor is the same story in three registration lines: AddCommonWebFormFactor(), AddWasmFormFactor(), and AddMauiFormFactor() - (MMCA.ADC.UI.Web/Program.cs:164, MMCA.ADC.UI.Web.Client/Program.cs:76, + (MMCA.ADC.UI.Web/Program.cs:176, MMCA.ADC.UI.Web.Client/Program.cs:76, MMCA.ADC.UI/MauiProgram.cs:169), all satisfying the same IFormFactor contract. OAuth button availability (IOAuthUISettings, satisfied by @@ -231,14 +231,14 @@

    25. ADC Appl IOAuthUISettings is registered before AddUIShared on every head, because a TryAdd already satisfied by an earlier plain Add is a no-op, and that is what makes the head's implementation win and the social-login buttons appear - (MMCA.ADC.UI/MauiProgram.cs:99,101, MMCA.ADC.UI.Web/Program.cs:84-85, + (MMCA.ADC.UI/MauiProgram.cs:99,101, MMCA.ADC.UI.Web/Program.cs:85-86, MMCA.ADC.UI.Web.Client/Program.cs:38-39). Direction two: everything that overrides a shared null/neutral default goes after it, which covers UseMauiDeviceCapabilities() (MauiProgram.cs:104), the push token providers AddMauiPushDeviceTokenProvider() (ADR-044, MauiProgram.cs:120), UseCommonBarcodeScanner(...) for badge-check-in QR scanning (MauiProgram.cs:151-153), and AddBrowserDeviceCapabilities() on the web heads - (MMCA.ADC.UI.Web/Program.cs:90, MMCA.ADC.UI.Web.Client/Program.cs:43). Direction three: a module + (MMCA.ADC.UI.Web/Program.cs:91, MMCA.ADC.UI.Web.Client/Program.cs:43). Direction three: a module that registers its own default with a plain Add must be beaten the same way, which is why AddCommonMauiPublicLinkBuilder() (the framework's MAUI IPublicLinkBuilder, so a copied link points @@ -247,7 +247,7 @@

    25. ADC Appl provider: AddCommonBlazorCsp(), backed by BlazorCspPolicyProvider, is registered before AddCommonSecurityHeaders(...) so it wins over the static default - (MMCA.ADC.UI.Web/Program.cs:177-178), feeding the framework's + (MMCA.ADC.UI.Web/Program.cs:189-190), feeding the framework's SecurityHeadersMiddleware over the ICspPolicyProvider boundary. The MAUI comment block also records the one ordering-insensitive call in the sequence, @@ -258,7 +258,7 @@

    25. ADC Appl

    Which modules are in the build is configuration, not code. All three heads gate every module UI behind UIModuleConfiguration.IsModuleEnabled - (MMCA.ADC.UI/MauiProgram.cs:127-137, MMCA.ADC.UI.Web/Program.cs:150-160, + (MMCA.ADC.UI/MauiProgram.cs:127-137, MMCA.ADC.UI.Web/Program.cs:162-172, MMCA.ADC.UI.Web.Client/Program.cs:55-65), reading the Modules section (all four enabled in the MAUI head's embedded settings, MMCA.ADC.UI/appsettings.json:8-13), so a deployment can ship Conference-only, or Conference plus Engagement, without touching source. That is the client-side @@ -268,7 +268,7 @@

    25. ADC Appl registered. On the web host the composition is explicit at the end of Program.cs: every registered IUIModule's Assembly is concatenated with the three shared UI assemblies, deduplicated, and handed to MapRazorComponents<App>().AddAdditionalAssemblies(...) - (MMCA.ADC.UI.Web/Program.cs:287-301). This is the group's cleanest + (MMCA.ADC.UI.Web/Program.cs:300-314). This is the group's cleanest [Rubric §15, Best Practices & Code Quality] and [Rubric §25, Navigation, Routing & IA] moment: routes and navigation are discovered from the enabled module set rather than maintained in a central list.

    The landing page and the two content adapters. The conference landing page itself is not @@ -357,7 +357,7 @@

    25. ADC Appl and OnNewIntent overrides also forward to EssentialsPlatform so a cold-start shortcut tap actually raises OnAppAction (MainActivity.cs:49-55,61). AppDelegate does the equivalent for iOS Universal Links in ContinueUserActivity - (MMCA.ADC.UI/Platforms/iOS/AppDelegate.cs:26-44), forwards shortcut taps to Essentials in + (MMCA.ADC.UI/Platforms/iOS/AppDelegate.cs:26-49), forwards shortcut taps to Essentials in PerformActionForShortcutItem (AppDelegate.cs:63-67), and carries the two fixed UIKit selectors that publish the APNs device token (or a null on failure) into the framework's ApnsTokenBridge (AppDelegate.cs:52-59, @@ -385,7 +385,7 @@

    25. ADC Appl (NowNextWidgetProvider.cs:119) is a compact [Rubric §29, Resilience] statement about an optional surface. The web side of the link association is served by the Blazor host, which maps the App Links and Universal Links association documents from configuration - (MMCA.ADC.UI.Web/Program.cs:266-277), and the applinks components mirror the same Blazor routes + (MMCA.ADC.UI.Web/Program.cs:279-290), and the applinks components mirror the same Blazor routes the app uses: identical URLs on web and device, no route translation table.

    Testability of a head that has no test project. No MAUI target framework in this workspace has a test project, so the ordering contract inside MauiProgram is verified by review rather than by a @@ -406,12 +406,12 @@

    25. ADC Appl

    Host security: platform-appropriate token handling. The token-storage choices are a compact study in secret handling matched to the threat model. On the browser heads the high-value refresh token is never exposed to JavaScript: it stays in an HttpOnly cookie and is exchanged through a - same-origin proxy refresher (MMCA.ADC.UI.Web/Program.cs:145, + same-origin proxy refresher (MMCA.ADC.UI.Web/Program.cs:146, MMCA.ADC.UI.Web.Client/Program.cs:49), and the Server head additionally runs a cookie-backed SSR authentication scheme, SessionCookieAuthenticationHandler, plus an SSR validate-or-refresh step ahead of authentication, so [Authorize] component routes survive F5 - and open-in-new-tab (MMCA.ADC.UI.Web/Program.cs:98-103,234-237). On MAUI, which has no DOM and + and open-in-new-tab (MMCA.ADC.UI.Web/Program.cs:99-104,246-249). On MAUI, which has no DOM and therefore no XSS surface, the framework's MauiTokenStorageService stores both tokens in OS SecureStorage, the platform secure enclave (Android Keystore, iOS Keychain, Windows @@ -440,11 +440,11 @@

    25. ADC Appl UiRateLimitingExtensions chains a per-client-IP fixed window with a replica-wide concurrency ceiling so a request must satisfy both, and rejects with 429 without queuing - (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Hardening/UiRateLimitingExtensions.cs:105,131,163,172). + (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Hardening/UiRateLimitingExtensions.cs:118,147,179,188). The framework defaults in UiRateLimitingSettings are 300 requests per IP per 60-second window and 200 in flight per replica, and the type's own remarks tell a host - whose audience sits behind one address to raise the window (UiRateLimitingSettings.cs:45-74). ADC + whose audience sits behind one address to raise the window (UiRateLimitingSettings.cs:45-76). ADC does exactly that: 1200 requests per IP per 60-second window, four times the storefront's figure, because on conference day the whole venue sits behind one NAT and presents to the limiter as a single client IP, while the in-flight ceiling stays at 200 @@ -452,16 +452,16 @@

    25. ADC Appl is multiplied by the replica count, the same deliberate trade the Gateway kit documents: an edge limiter answers on every request and a shared counter would put a network round trip in front of the whole site (UiRateLimitingSettings.cs:26-31). The section is bound, data-annotation validated, and - validated on start (UiRateLimitingExtensions.cs:151-153). The exemptions are the interesting part: + validated on start (UiRateLimitingExtensions.cs:167-169). The exemptions are the interesting part: the liveness and readiness probes, /_framework, /_content, and /hubs take the no-limiter partition, so the static assets a single page load pulls never throttle the first attendee, while /_blazor is deliberately left unexempt because the negotiate endpoint is exactly what opens a circuit (UiRateLimitingExtensions.cs:42,55); an unresolvable client IP fails open rather than - collapsing every unattributable request into one shared bucket (UiRateLimitingExtensions.cs:27,94). - The host registers it in one call (MMCA.ADC.UI.Web/Program.cs:142), and in the pipeline the + collapsing every unattributable request into one shared bucket (UiRateLimitingExtensions.cs:27,107). + The host registers it in one call (MMCA.ADC.UI.Web/Program.cs:143), and in the pipeline the middleware sits after UseForwardedHeaders, so the partition key is the caller's address and not the ingress's, and before anything that renders a page or opens a circuit - (MMCA.ADC.UI.Web/Program.cs:192,203).

    + (MMCA.ADC.UI.Web/Program.cs:204,215).

    Bounding circuits, not just arrival rate. The second ceiling bounds resident state. Because the app renders Interactive Auto, every first page load opens a Blazor Server circuit, and a circuit stays resident for as long as the connection lives, so a caller who opens circuits slowly enough to @@ -481,10 +481,10 @@

    25. ADC Appl because OnCircuitOpenedAsync has no "refuse" return value: the one place in this codebase where the Result pattern gives way to an exception, because the contract being implemented belongs to ASP.NET Core - (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Hardening/BoundedCircuitHandler.cs:58-75, - rationale at BoundedCircuitHandler.cs:23). Closes floor at zero, so a teardown that was never - counted cannot hand out permits forever (BoundedCircuitHandler.cs:79-90), and the refusal is logged - through a source-generated LoggerMessage (BoundedCircuitHandler.cs:93). + (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Hardening/BoundedCircuitHandler.cs:64-84, + rationale at BoundedCircuitHandler.cs:24). Closes floor at zero, so a teardown that was never + counted cannot hand out permits forever (BoundedCircuitHandler.cs:88-108), and the refusal is logged + through a source-generated LoggerMessage (BoundedCircuitHandler.cs:111). BlazorCircuitLimitExtensions wires both halves from that one configuration section, deliberately as two calls because they attach to different builders: RetentionFrom(...) supplies the CircuitOptions callback for @@ -492,7 +492,7 @@

    25. ADC Appl singleton, since circuit handlers resolve from each circuit's own scope and a scoped registration would count to one and cap nothing (MMCA.Common/Source/Presentation/MMCA.Common.UI.Web/Hardening/BlazorCircuitLimitExtensions.cs:28-40,46-60, - host wiring at MMCA.ADC.UI.Web/Program.cs:67-77). The retention numbers are where this host departs + host wiring at MMCA.ADC.UI.Web/Program.cs:68-78). The retention numbers are where this host departs from the framework defaults: 25 retained disconnected circuits (the framework kit's own default, against ASP.NET Core's 100), but a retention period of 180 seconds instead of the kit's 60, because the kit's remarks name a conference venue's flaky shared network as exactly the case that raises it @@ -512,7 +512,7 @@

    25. ADC Appl ADR-028). All three heads share one localization stance and each implements its own half of it. The Blazor Server host sets CurrentUICulture from the culture cookie before SSR prerender and exposes a culture-switch - endpoint (MMCA.ADC.UI.Web/Program.cs:220,258); the WASM client mirrors the same cookie into the + endpoint (MMCA.ADC.UI.Web/Program.cs:232,271); the WASM client mirrors the same cookie into the browser thread culture through MmcaCultureBootstrap before the app runs, so there is no locale flash or prerender/hydration mismatch @@ -535,7 +535,7 @@

    25. ADC Appl nothing flashes white before the WebView renders (MMCA.ADC.UI/MainPage.xaml:8,10-15).

    How it all fits at runtime. A request to the Blazor Web host passes forwarded headers, the shared security-header middleware and this origin's own edge limiter - (MMCA.ADC.UI.Web/Program.cs:192,198,203), then renders the shared layout from + (MMCA.ADC.UI.Web/Program.cs:204,210,215), then renders the shared layout from MMCA.Common.UI; the navbar is composed from each enabled module's IUIModule descriptor, and / renders the Conference landing page through ADCHomePageContent. After prerender, the interactive Server circuit or the @@ -544,7 +544,7 @@

    25. ADC Appl reading whichever ITokenStorageService the host registered, and the WASM client discovers its API endpoint at startup from the Server host's /client-config endpoint instead of having it baked into the static bundle - (MMCA.ADC.UI.Web/Program.cs:247-253, MMCA.ADC.UI.Web.Client/Program.cs:30-31), with exactly one + (MMCA.ADC.UI.Web/Program.cs:259-265, MMCA.ADC.UI.Web.Client/Program.cs:30-31), with exactly one retry on a cold start and a loud failure after that through the framework's MmcaClientConfigBootstrap (MMCA.ADC.UI.Web.Client/Program.cs:25-31), and a discarded @@ -688,7 +688,7 @@

    NowNextWidgetProvider

    MMCA.ADC.UI · MMCA.ADC.UI · MMCA.ADC.UI/Platforms/Android/NowNextWidgetProvider.cs:22 · Level 10 · class (sealed)

      -
    • What it is: the Android home-screen AppWidgetProvider (ADR-042 Wave 8) that renders a "Now / Next" card. On each update it fetches the anonymous, cached GET Events/now-next snapshot (the id-less form, where the server picks the live-or-next published event) and renders one "Now" and one "Next" line. It never throws: a failed fetch leaves the previous RemoteViews in place (class summary, MMCA.ADC.UI/Platforms/Android/NowNextWidgetProvider.cs:11-18). The endpoint it calls is [AllowAnonymous] and output-cached under the NowNextCache policy (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:180-182).
    • +
    • What it is: the Android home-screen AppWidgetProvider (ADR-042 Wave 8) that renders a "Now / Next" card. On each update it fetches the anonymous, cached GET Events/now-next snapshot (the id-less form, where the server picks the live-or-next published event) and renders one "Now" and one "Next" line. It never throws: a failed fetch leaves the previous RemoteViews in place (class summary, MMCA.ADC.UI/Platforms/Android/NowNextWidgetProvider.cs:11-18). The endpoint it calls is [AllowAnonymous] and output-cached under the NowNextCache policy (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.API/Controllers/Events/EventsController.cs:187-189).
    • Depends on: NowNextSnapshot and NowNextSession (its private records), MainActivity (the tap target, :88), IConfiguration resolved from IPlatformApplication.Current.Services (:112), System.Net.Http.HttpClient, System.Text.Json, and the Android widget SDK.
    • Concept introduced, best-effort background rendering under a platform time budget. OnUpdate (:24-35) must return fast, so after null-guarding its three arguments (:26-29) it calls GoAsync() (:33) to keep the broadcast alive while the snapshot downloads, then starts UpdateWidgetsAsync without awaiting it (:34); the comment at :31-32 records that the platform budget is roughly 10s, far above one cached GET. UpdateWidgetsAsync (:37-66) wraps the whole flow in a try/catch that swallows every exception, with the CA1031 suppression justified inline (:56-58: a widget update is best-effort and the last rendering stays), and always calls pendingResult?.Finish() in finally (:62-65). [Rubric §29, Resilience & Business Continuity] assesses graceful degradation: a network or parse failure degrades to the stale card rather than to a visible error. [Rubric §23, Front-End Performance] is engaged by leaning on the server's output cache and a short client timeout instead of any local polling loop.
    • Walkthrough: BuildViews (:67-98) inflates the nownext_widget layout (:69), sets the event-name text (:70), reads three localized strings from Android resources (:72-74), and fills the Now/Next lines through FormatRow, showing the "nothing scheduled" string when Now is empty and an empty string when Next is empty (:76-81). It then builds an explicit tap intent targeting MainActivity with ActionView and the app-internal https://app.internal/happening-now URI (:86-91); the S1075 hardcoded-URI suppression is justified because this is an app-internal route rather than an external address, and only the URI path is consumed by the deep-link publisher, which makes the host part a placeholder (:83-85). The PendingIntent is created UpdateCurrent | Immutable (:92-93) and attached to the widget root (:94). FormatRow (:100-107) does the invariant HH:mm formatting described under NowNextSession. FetchSnapshotAsync (:109-127) reads Api:ApiEndpoint from configuration (:112, whose value is the gateway base URL at MMCA.ADC.UI/appsettings.json:19), returns null when it is missing (:113-116), builds a short-lived HttpClient with an 8s timeout (:118), GETs the relative Events/now-next (:119), returns null on a non-success status (:120-123), and otherwise deserializes with JsonSerializerOptions.Web (:126).
    • @@ -727,7 +727,7 @@

      ADCHomePageContent

    • Concept introduced, app-supplied content for a shared shell. The framework ships one generic home shell; each host app registers a single IHomePageContent that hands the shell a ComponentType and a PageTitle. The dependency is inverted: the shared shell never references an ADC page. [Rubric §18, UI Architecture] assesses how a reusable shell is specialized per app, and here the entire specialization is two properties. [Rubric §2, Design Patterns] applies as well, since this is a minimal strategy/adapter sitting at a UI boundary.
    • Walkthrough: both classes are two expression-bodied properties and no state. ComponentType selects the landing component (MMCA.ADC.UI.Web.Client/Pages/ADCHomePageContent.cs:13, MMCA.ADC.UI/Pages/ADCHomePageContent.cs:10); PageTitle => "Atlanta Developers Conference" is identical on both (:15 and :12 respectively) and carries an explicit i18n: allow marker because the conference brand name is deliberately not localized. The web class summary notes that the shared component's default image base path already matches the web head's site-root assets (MMCA.ADC.UI.Web.Client/Pages/ADCHomePageContent.cs:6-10), so no parameters are passed. The MAUI wrapper exists for the mirror-image reason: its comment records that both heads serve speaker images from their own site root, the MAUI head carrying its copy under wwwroot/images/speakers, and that the shared component carries the Web head's countdown fence (the self-ticking HomeCountdown child) for both heads, so no base-path override is needed there either (MMCA.ADC.UI/Pages/ADCHome.razor:1-5).
    • Why it's built this way: pointing at the Conference module's component instead of duplicating a landing page means the web and MAUI heads render the same marketing surface, and a change to the conference home lands everywhere at once. The MAUI head keeps its one-line wrapper so head-specific editorial assets stay in ADC rather than migrating into the shared MMCA.Common.UI RCL.
    • -
    • Where it's used: registered as a singleton IHomePageContent by all three heads: the WebAssembly client (MMCA.ADC.UI.Web.Client/Program.cs:45), the Blazor Server host (MMCA.ADC.UI.Web/Program.cs:92), and MauiProgram (MMCA.ADC.UI/MauiProgram.cs:124, resolving the MMCA.ADC.UI.Pages class imported at :20).
    • +
    • Where it's used: registered as a singleton IHomePageContent by all three heads: the WebAssembly client (MMCA.ADC.UI.Web.Client/Program.cs:45), the Blazor Server host (MMCA.ADC.UI.Web/Program.cs:93), and MauiProgram (MMCA.ADC.UI/MauiProgram.cs:124, resolving the MMCA.ADC.UI.Pages class imported at :20).

    MauiProgram

    @@ -764,10 +764,10 @@

    AppDelegate

    • What it is: the iOS application delegate. It boots MAUI by returning MauiProgram's app, receives Universal Links (ADR-043), carries the two APNs registration callbacks (ADR-044), and forwards home-screen quick-action taps to Essentials.
    • -
    • Depends on: MauiProgram, IDeepLinkDispatcher, the framework's ApnsTokenBridge from MMCA.Common.UI.Maui.Capabilities.Notifications (MMCA.ADC.UI/Platforms/iOS/AppDelegate.cs:2), MAUI's MauiUIApplicationDelegate and the Microsoft.Maui.ApplicationModel.Platform Essentials helper aliased as EssentialsPlatform (:5), and Foundation/UIKit.
    • -
    • Concept introduced, iOS Universal Links next to Android App Links. The product concept matches MainActivity's App Links but the plumbing differs: iOS delivers the tapped web URL as an NSUserActivity of type BrowsingWeb, and the app must carry the associated-domains entitlement plus a live apple-app-site-association file on that host (class summary, :11-15). A second concept lands here too, the fixed-selector native callback: RegisteredForRemoteNotifications and FailedToRegisterForRemoteNotifications (:52-59) are bound by the Objective-C registrar to this instance through their [Export] selectors, so they cannot be static and their signatures are dictated by UIKit, which is why the CA1822 "make static" analyzer is suppressed around exactly those two members (:50, :60). [Rubric §25, Navigation & IA] applies because deep links resolve to in-app routes on iOS exactly as on Android, through the same dispatcher. [Rubric §3, Clean Architecture] applies to the push split: only these two platform hooks stay app-side, and everything downstream of them lives in the framework (:46-49).
    • -
    • Walkthrough: CreateMauiApp (:23) delegates to MauiProgram.CreateMauiApp(). ContinueUserActivity (:26-44) checks for a BrowsingWeb activity with a non-null WebPageUrl (:31-32), reassembles path plus optional ?query (:35), and when the result is non-blank publishes it through IDeepLinkDispatcher and returns true (:36-40); every other case defers to the base implementation (:43). The two APNs hooks publish into the framework's ApnsTokenBridge: success hands over the device token as a lowercase hex string (:53-54), failure publishes null (:57-59) so the framework's ApnsPushDeviceTokenProvider, which awaits that rendezvous, is unblocked rather than left hanging when there is no entitlement or no network. PerformActionForShortcutItem (:63-67) forwards to EssentialsPlatform.PerformActionForShortcutItem; without that override the app opens on a shortcut tap but OnAppAction never fires and no navigation happens (:15-17). The [Register("AppDelegate")] attribute (:19) is what makes the type visible to the Objective-C runtime.
    • -
    • Why it's built this way: mirroring the Android deep-link path through one shared dispatcher means the in-app navigation logic is written once in the shared DeepLinkListener, and each platform delegate only translates its native event into a route string. The same principle governs push: the delegate publishes a token into a bridge and the framework owns everything after that, so the app-side surface is two one-line methods.
    • +
    • Depends on: MauiProgram, IDeepLinkDispatcher and the static DeepLinkDispatcher.IsAppRelativeRoute shape check (MMCA.Common.UI.Services.Capabilities.Navigation, MMCA.ADC.UI/Platforms/iOS/AppDelegate.cs:3), the framework's ApnsTokenBridge from MMCA.Common.UI.Maui.Capabilities.Notifications (:2), MAUI's MauiUIApplicationDelegate and the Microsoft.Maui.ApplicationModel.Platform Essentials helper aliased as EssentialsPlatform (:5), and Foundation/UIKit.
    • +
    • Concept introduced, iOS Universal Links next to Android App Links. The product concept matches MainActivity's App Links but the plumbing differs: iOS delivers the tapped web URL as an NSUserActivity of type BrowsingWeb, and the app must carry the associated-domains entitlement plus a live apple-app-site-association file on that host (class summary, :11-15). A second concept lands here too, the fixed-selector native callback: RegisteredForRemoteNotifications and FailedToRegisterForRemoteNotifications (:57-64) are bound by the Objective-C registrar to this instance through their [Export] selectors, so they cannot be static and their signatures are dictated by UIKit, which is why the CA1822 "make static" analyzer is suppressed around exactly those two members (:55, :65). A third concept is ask before you publish: IDeepLinkDispatcher.Publish throws ArgumentException on a route that is not app-relative (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Capabilities/Navigation/DeepLinkDispatcher.cs:104-113), and an exception escaping a UIKit delegate callback terminates the app, so the delegate runs the same static shape check first, as MainActivity does, and a crafted universal link is dropped instead of crashing the app on tap (:36-41). [Rubric §25, Navigation & IA] applies because deep links resolve to in-app routes on iOS exactly as on Android, through the same dispatcher. [Rubric §3, Clean Architecture] applies to the push split: only these two platform hooks stay app-side, and everything downstream of them lives in the framework (:51-54).
    • +
    • Walkthrough: CreateMauiApp (:23) delegates to MauiProgram.CreateMauiApp(). ContinueUserActivity (:26-49) checks for a BrowsingWeb activity with a non-null WebPageUrl (:31-32) and reassembles path plus optional ?query (:35). It then asks DeepLinkDispatcher.IsAppRelativeRoute (:41), which accepts only a non-blank value with exactly one leading slash and no backslash, control character or scheme (DeepLinkDispatcher.cs:49-72); when the route passes, the delegate publishes it through IDeepLinkDispatcher and returns true (:42-45). Every other case, including a rejected route, defers to the base implementation (:48). The two APNs hooks publish into the framework's ApnsTokenBridge: success hands over the device token as a lowercase hex string (:58-59), failure publishes null (:62-64) so the framework's ApnsPushDeviceTokenProvider, which awaits that rendezvous, is unblocked rather than left hanging when there is no entitlement or no network. PerformActionForShortcutItem (:68-72) forwards to EssentialsPlatform.PerformActionForShortcutItem; without that override the app opens on a shortcut tap but OnAppAction never fires and no navigation happens (:15-17). The [Register("AppDelegate")] attribute (:19) is what makes the type visible to the Objective-C runtime.
    • +
    • Why it's built this way: mirroring the Android deep-link path through one shared dispatcher means the in-app navigation logic is written once in the shared DeepLinkListener, and each platform delegate only translates its native event into a route string. The dispatcher enforces the route shape at Publish so no head can forget it, while each head asks the same predicate first so that enforcement never surfaces as a crash. The same principle governs push: the delegate publishes a token into a bridge and the framework owns everything after that, so the app-side surface is two one-line methods.
    • Where it's used: Program passes this type to UIApplication.Main (MMCA.ADC.UI/Platforms/iOS/Program.cs:11). MacCatalyst compiles a separate same-named delegate under MMCA.ADC.UI/Platforms/MacCatalyst/AppDelegate.cs:12 that forwards CreateMauiApp (:14) and PerformActionForShortcutItem (:17-21) but does not handle Universal Links or APNs, so those two paths taught here are iOS-only.

    MainApplication

    diff --git a/docs/onboarding/group-26-device-capability-layer.html b/docs/onboarding/group-26-device-capability-layer.html index 1adcc589..aeadfe68 100644 --- a/docs/onboarding/group-26-device-capability-layer.html +++ b/docs/onboarding/group-26-device-capability-layer.html @@ -329,7 +329,7 @@

    (IDeepLinkDispatcher.cs:13) or drains it from a buffer after first render (IDeepLinkDispatcher.cs:22). The default DeepLinkDispatcher - (MMCA.Common.UI/Services/Capabilities/Navigation/DeepLinkDispatcher.cs:9) is registered as a singleton + (MMCA.Common.UI/Services/Capabilities/Navigation/DeepLinkDispatcher.cs:11) is registered as a singleton (MMCA.Common.UI/Services/Capabilities/DependencyInjection.cs:78) because native callers publish into it from outside any scope, and it does two jobs. First it validates the shape of the route: Publish throws unless IsAppRelativeRoute passes, meaning exactly one leading slash followed by a @@ -457,7 +457,7 @@

    BiometricGate component, which subscribes on initialize and unsubscribes on dispose (MMCA.Common.UI/Components/Capabilities/BiometricGate.razor:10, - MMCA.Common.UI/Components/Capabilities/BiometricGate.razor.cs:54,76,93) and re-locks only when the + MMCA.Common.UI/Components/Capabilities/BiometricGate.razor.cs:55,77,94) and re-locks only when the app was away longer than its ReLockAfter threshold, 30 seconds by default (BiometricGate.razor.cs:25,95-99). That threshold is why the event carries a duration at all: a lock that fired on every task-switch flicker would train users to defeat it. [Rubric §26, Front-End Security] and [Rubric §11, Security].

    @@ -516,7 +516,7 @@

    (MauiSecureTokenStore.cs:5-21); it writes the refresh token first and drops both entries on a partial failure so storage is never a mismatched pair (MauiSecureTokenStore.cs:34-53). MauiTokenStorageService - (MMCA.Common.UI.Maui/Services/MauiTokenStorageService.cs:19) sits above it as the + (MMCA.Common.UI.Maui/Services/MauiTokenStorageService.cs:25) sits above it as the ITokenStorageService and adds what a long-lived mobile session needs: a freshness check with a 30-second skew (MauiTokenStorageService.cs:23,33) and a single-flight refresh under a Lock, because an unguarded @@ -662,10 +662,10 @@

    IFormFactor

    WebFormFactor (Blazor Server), and MauiFormFactor (native). Each head registers exactly one of them as a singleton in its composition root, and each registration helper's doc comment points at the other two so a host author cannot pick the wrong one - (MMCA.Common.UI/DependencyInjection.cs:199-204, MMCA.Common.UI.Web/DependencyInjection.cs:65-69, + (MMCA.Common.UI/DependencyInjection.cs:215-220, MMCA.Common.UI.Web/DependencyInjection.cs:65-69, MMCA.Common.UI.Maui/DependencyInjection.cs:137-142). The ADC WASM client calls AddWasmFormFactor() (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Program.cs:76), the - Blazor Server head AddCommonWebFormFactor() (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:164), + Blazor Server head AddCommonWebFormFactor() (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:176), and the MAUI head AddMauiFormFactor() (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/MauiProgram.cs:169). Store wires the same three (MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web.Client/Program.cs:63, MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:132, @@ -758,19 +758,19 @@

    MauiSpeechToTextService

    MauiTextToSpeechService

    -

    MMCA.Common.UI.Maui · MMCA.Common.UI.Maui.Capabilities.Media · MMCA.Common.UI.Maui/Capabilities/Media/MauiTextToSpeechService.cs:12 · Level 1 · class (sealed partial, IDisposable)

    +

    MMCA.Common.UI.Maui · MMCA.Common.UI.Maui.Capabilities.Media · MMCA.Common.UI.Maui/Capabilities/Media/MauiTextToSpeechService.cs:14 · Level 1 · class (sealed partial, IDisposable)

    • What it is: the MAUI adapter for ITextToSpeechService, speaking text over TextToSpeech.Default with locale matching and a cancellable in-flight utterance.
    • Depends on: ITextToSpeechService; MAUI Essentials TextToSpeech, SpeechOptions, Locale; BCL Lock, CancellationTokenSource, CultureInfo.
    • Concept: single-utterance serialization plus best-effort locale selection. [Rubric §21, Accessibility] assesses assistive affordances; read-aloud is one, and it is offered as a first-class capability rather than a platform afterthought. [Rubric §27, i18n] assesses culture-awareness; the adapter picks a voice for the current UI culture and falls back to the platform default, so a device without an es voice still speaks rather than throwing (MMCA.Common.UI.Maui/Capabilities/Media/MauiTextToSpeechService.cs:7-9).
    • Walkthrough
        -
      • _gate (MauiTextToSpeechService.cs:14, a Lock) and _activeUtterance (MauiTextToSpeechService.cs:15, a nullable CancellationTokenSource) track the one in-flight utterance.
      • -
      • IsSupported (MauiTextToSpeechService.cs:18): a constant true.
      • -
      • SpeakAsync(string text, CancellationToken = default) (MauiTextToSpeechService.cs:21): guards text with ArgumentException.ThrowIfNullOrWhiteSpace (:23), calls StopAsync first so a new utterance preempts the previous one (:25), links a fresh CTS to the caller's token and stores it under the lock (:27-31), then speaks with SpeechOptions whose Locale comes from MatchLocaleAsync(CultureInfo.CurrentUICulture) (:35-39). OperationCanceledException is expected and swallowed (:41-44); the finally clears _activeUtterance only if it is still this utterance, then disposes the CTS (:45-56).
      • -
      • StopAsync() (MauiTextToSpeechService.cs:60): reads the active CTS under the lock, returns if there is none (:68-71), else CancelAsync, swallowing ObjectDisposedException for the case where the utterance completed concurrently (:77-80).
      • -
      • Dispose() (MauiTextToSpeechService.cs:84): disposes and clears any active CTS under the lock.
      • -
      • MatchLocaleAsync(CultureInfo culture) (MauiTextToSpeechService.cs:93): fetches the installed locales and returns the first whose Language matches the culture's two-letter ISO code (:97-99), or null (meaning "platform default voice") on no match or on FeatureNotSupportedException (:101-104).
      • +
      • _gate (MauiTextToSpeechService.cs:16, a Lock) and _activeUtterance (MauiTextToSpeechService.cs:17, a nullable CancellationTokenSource) track the one in-flight utterance.
      • +
      • IsSupported (MauiTextToSpeechService.cs:20): a constant true.
      • +
      • SpeakAsync(string text, CancellationToken = default) (MauiTextToSpeechService.cs:23): guards text with ArgumentException.ThrowIfNullOrWhiteSpace (:23), calls StopAsync first so a new utterance preempts the previous one (:25), links a fresh CTS to the caller's token and stores it under the lock (:27-31), then speaks with SpeechOptions whose Locale comes from MatchLocaleAsync(CultureInfo.CurrentUICulture) (:35-39). OperationCanceledException is expected and swallowed (:41-44); the finally clears _activeUtterance only if it is still this utterance, then disposes the CTS (:45-56).
      • +
      • StopAsync() (MauiTextToSpeechService.cs:62): reads the active CTS under the lock, returns if there is none (:68-71), else CancelAsync, swallowing ObjectDisposedException for the case where the utterance completed concurrently (:77-80).
      • +
      • Dispose() (MauiTextToSpeechService.cs:86): disposes and clears any active CTS under the lock.
      • +
      • MatchLocaleAsync(CultureInfo culture) (MauiTextToSpeechService.cs:95): fetches the installed locales and returns the first whose Language matches the culture's two-letter ISO code (:97-99), or null (meaning "platform default voice") on no match or on FeatureNotSupportedException (:101-104).
    • Why it's built this way: MAUI exposes no stop API, so StopAsync cancels the in-flight utterance's token instead (MauiTextToSpeechService.cs:10). The Lock-guarded single-utterance state keeps overlapping SpeakAsync calls from talking over each other, and returning null from locale matching lets the platform choose a voice rather than failing the whole call. The per-head selection itself is ADR-042.
    • @@ -811,10 +811,10 @@

      WasmFormFactor

      It lives in MMCA.Common.UI rather than in a WASM-specific package because it needs no WASM-specific reference: BCL only. That is what makes the registration helper AddWasmFormFactor() a plain singleton registration in the shared package - (MMCA.Common.UI/DependencyInjection.cs:205-206), and the surrounding doc comment is where the - three-way choice is documented for host authors (MMCA.Common.UI/DependencyInjection.cs:199-204). + (MMCA.Common.UI/DependencyInjection.cs:221-222), and the surrounding doc comment is where the + three-way choice is documented for host authors (MMCA.Common.UI/DependencyInjection.cs:215-220).
    • Where it's used: registered by AddWasmFormFactor() - (MMCA.Common.UI/DependencyInjection.cs:205) from the .Client WASM host only + (MMCA.Common.UI/DependencyInjection.cs:221) from the .Client WASM host only (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Program.cs:76, MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web.Client/Program.cs:63). Covered by WasmFormFactorTests, which asserts the singleton lifetime, that exactly one IFormFactor @@ -895,7 +895,7 @@

      WindowLifecycleExtensions

      MMCA.Common.UI.Maui · MMCA.Common.UI.Maui · MMCA.Common.UI.Maui/WindowLifecycleExtensions.cs:22 · Level 2 · class (static)

      -
    • What it is: a one-method extension over MAUI's Window that wires the window's Stopped and Resumed events to the app's IAppLifecycleNotifier, so a head opts into background/foreground notification with a single call instead of subscribing both events itself (WindowLifecycleExtensions.cs:29-51).
    • +
    • What it is: a one-method extension over MAUI's Window that wires the window's Stopped and Resumed events to the app's IAppLifecycleNotifier, so a head opts into background/foreground notification with a single call instead of subscribing both events itself (WindowLifecycleExtensions.cs:24-49).
    • Depends on: IAppLifecycleNotifier (resolved from the supplied service provider, WindowLifecycleExtensions.cs:41). Externals: MAUI Window as the extended type, and IServiceProvider/IServiceCollection-style GetService<T>().
    • Concept introduced: an extension member over a MAUI platform type rather than over IServiceCollection. Everywhere else in this group extension(...) blocks target IServiceCollection or MauiAppBuilder (see DependencyInjection, HostingDependencyInjection); here the same C# preview syntax targets Window itself, because window lifecycle events are only reachable once a Window instance exists, which is later than either service registration point. The method is also a deliberate no-op when unwired: a host that registered no IAppLifecycleNotifier still gets a valid window back untouched (WindowLifecycleExtensions.cs:41-45).
      • [Rubric §1, SOLID] assesses dependency inversion. The window depends on the resolved IAppLifecycleNotifier abstraction, never on a concrete notifier, so a head can supply any implementation or none.
      • @@ -989,7 +989,7 @@

        AppResumedEventArgs

        (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Capabilities/DeviceStatus/AppLifecycleNotifier.cs:41); read by BiometricGate's OnResumed handler, which re-locks the app when the background gap meets or exceeds its re-lock threshold - (MMCA.Common/Source/Presentation/MMCA.Common.UI/Components/Capabilities/BiometricGate.razor.cs:93-99). + (MMCA.Common/Source/Presentation/MMCA.Common.UI/Components/Capabilities/BiometricGate.razor.cs:94-100).

      IBatteryStatusService

      @@ -1267,7 +1267,7 @@

      NullHapticFeedbackService

  • Why it's built this way: ADR-042. Haptics are pure enhancement, never the carrier of information, so silently doing nothing is a complete implementation of the contract on a host without a vibrator.
  • -
  • Where it's used: TryAddSingleton in AddDeviceCapabilityDefaults() (DependencyInjection.cs:52); MauiHapticFeedbackService is the native override (MMCA.Common/Source/Presentation/MMCA.Common.UI.Maui/DependencyInjection.cs:50) and AddBrowserDeviceCapabilities() registers no haptics implementation (DependencyInjection.cs:100-115), so web heads keep this. ADC registers it explicitly in a bUnit test so the live-channel page renders without hardware (MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.UI.Tests/Pages/LiveChannelJoinTests.cs:59-60), and CapabilityFallbackTests asserts all three calls are silent and non-throwing (CapabilityFallbackTests.cs:36-49).
  • +
  • Where it's used: TryAddSingleton in AddDeviceCapabilityDefaults() (DependencyInjection.cs:52); MauiHapticFeedbackService is the native override (MMCA.Common/Source/Presentation/MMCA.Common.UI.Maui/DependencyInjection.cs:50) and AddBrowserDeviceCapabilities() registers no haptics implementation (DependencyInjection.cs:100-115), so web heads keep this. ADC registers it explicitly in a bUnit test so the live-channel page renders without hardware (MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.UI.Tests/Pages/LiveChannelJoinTests.cs:60-61), and CapabilityFallbackTests asserts all three calls are silent and non-throwing (CapabilityFallbackTests.cs:36-49).
  • UnavailableExternalAuthBroker

    @@ -1294,7 +1294,7 @@

    AppLifecycleNotifier

    • What it is: the sole implementation of IAppLifecycleNotifier. It tracks the timestamp an app entered the background and computes the elapsed away-time when it resumes, - raising Resumed with that duration (AppLifecycleNotifier.cs:9-116).
    • + raising Resumed with that duration (AppLifecycleNotifier.cs:9-43).
    • Depends on: IAppLifecycleNotifier, AppResumedEventArgs; System.Threading.TimeProvider (optional constructor parameter, defaults to TimeProvider.System) and System.Threading.Lock.
    • @@ -1305,12 +1305,12 @@

      AppLifecycleNotifier

      host can call NotifyEnteredBackground()/NotifyResumed() from platform lifecycle callbacks that are not guaranteed to run on one thread.
    • Walkthrough
        -
      • NotifyEnteredBackground() (AppLifecycleNotifier.cs:93-99): stamps _backgroundedAt with +
      • NotifyEnteredBackground() (AppLifecycleNotifier.cs:20-26): stamps _backgroundedAt with _timeProvider.GetUtcNow() under the lock.
      • -
      • NotifyResumed() (AppLifecycleNotifier.cs:102-115): computes away as now minus _backgroundedAt - if a value was recorded, else TimeSpan.Zero (:108-110); clears _backgroundedAt back to null - (:111); then raises Resumed outside the lock with a duration clamped to never go negative - (:114).
      • +
      • NotifyResumed() (AppLifecycleNotifier.cs:29-42): computes away as now minus _backgroundedAt + if a value was recorded, else TimeSpan.Zero (:35-37); clears _backgroundedAt back to null + (:38); then raises Resumed outside the lock with a duration clamped to never go negative + (:41).
    • Why it's built this way: computing the elapsed time inside NotifyResumed() (rather than making @@ -1558,17 +1558,17 @@

      BrowserMapNavigationService

    • What it is: the web adapter for IMapNavigationService. There is no maps app to launch in a browser, so it builds a Google Maps search URL for the address and hands it to IExternalLinkService to open in a new tab.
    • -
    • Depends on: IMapNavigationService (the contract it implements, MMCA.Common.UI/Services/Capabilities/Geo/BrowserMapNavigationService.cs:9) and IExternalLinkService (constructor-injected, :15, :18-19); BCL Uri and Uri.EscapeDataString. Unlike every other browser adapter in this group it takes no CapabilitiesJsModule: it composes over a sibling capability instead of calling JS itself.
    • +
    • Depends on: IMapNavigationService (the contract it implements, MMCA.Common.UI/Services/Capabilities/Geo/BrowserMapNavigationService.cs:9) and IExternalLinkService (constructor-injected, :17, :20-21); BCL Uri and Uri.EscapeDataString. Unlike every other browser adapter in this group it takes no CapabilitiesJsModule: it composes over a sibling capability instead of calling JS itself.
    • Concept introduced, capability composition. Most adapters in this group sit directly on one platform API; this one is built entirely out of another capability, which is why it lands at Level 1 while its JS-backed siblings sit at Level 2. The payoff is that the "open something outside the app" policy (a new tab on the web, the system browser on native) is decided once in IExternalLinkService and every consumer inherits it. [Rubric §2, Design Patterns] assesses whether patterns are applied with intent rather than by habit; adapter-over-adapter here avoids a second copy of the window-open rules. [Rubric §22, Responsive/Cross-Browser] assesses whether the app spans device classes gracefully; the same OpenAddressAsync call site produces a native maps intent on a phone and a maps tab in a desktop browser.
    • Walkthrough
        -
      • MapsSearchUrl (MMCA.Common.UI/Services/Capabilities/Geo/BrowserMapNavigationService.cs:14): the constant https://www.google.com/maps/search/?api=1&query= prefix, wrapped in a scoped #pragma warning disable S1075 (:11, :13) with a comment stating that the public Maps search endpoint IS the integration point on the web, so there is nothing environment-dependent to configure (:9-10).
      • -
      • The constructor (:18-19) is an expression body capturing the injected IExternalLinkService into _externalLinkService (:15).
      • -
      • OpenAddressAsync(string address, string? label, CancellationToken = default) (:22): guards with ArgumentException.ThrowIfNullOrWhiteSpace(address) (:24), appends Uri.EscapeDataString(address) to the constant to build the Uri (:26), awaits _externalLinkService.OpenAsync(uri, cancellationToken) (:27), and returns true (:28). The label parameter is accepted for contract parity and is unused here.
      • +
      • MapsSearchUrl (MMCA.Common.UI/Services/Capabilities/Geo/BrowserMapNavigationService.cs:14): the constant https://www.google.com/maps/search/?api=1&query= prefix, wrapped in a scoped #pragma warning disable S1075 (:13, :15) with a comment stating that the public Maps search endpoint IS the integration point on the web, so there is nothing environment-dependent to configure (:11-12).
      • +
      • The constructor (:20-21) is an expression body capturing the injected IExternalLinkService into _externalLinkService (:17).
      • +
      • OpenAddressAsync(string address, string? label, CancellationToken = default) (:24): guards with ArgumentException.ThrowIfNullOrWhiteSpace(address) (:26), appends Uri.EscapeDataString(address) to the constant to build the Uri (:28), and returns the awaited result of _externalLinkService.OpenAsync(uri, cancellationToken) (:29), so the caller sees whatever the link opener reported. The label parameter is accepted for contract parity and is unused here.
    • Why it's built this way: the web has no address-to-map handoff of its own, so a search URL is the honest equivalent; escaping the address is what keeps a street name containing & or # from truncating the query. Delegating the open (rather than calling window.open directly) keeps the noopener,noreferrer hardening in one place (ADR-042).
    • Where it's used: registered scoped as IMapNavigationService by AddBrowserDeviceCapabilities() (MMCA.Common.UI/Services/Capabilities/DependencyInjection.cs:107); its siblings are MauiMapNavigationService and NullMapNavigationService. Consumed by venue and location components.
    • -
    • Caveats / not-in-source: the true return is unconditional. Because OpenAsync returns Task (not a success flag) and the underlying window.open result is discarded by CapabilitiesJsModule, a popup blocked by the browser still reports success to the caller. Not determinable from source: whether any consumer branches on that bool today.
    • +
    • Caveats / not-in-source: the returned bool is only as precise as the opener beneath it. IExternalLinkService.OpenAsync returns Task<bool> (MMCA.Common.UI/Services/Capabilities/Interop/IExternalLinkService.cs:23), and BrowserExternalLinkService yields false when JS interop is unavailable (prerender, a torn-down circuit) (MMCA.Common.UI/Services/Capabilities/Interop/BrowserExternalLinkService.cs:23-27). But the JS openExternal returns true unless window.open threw (MMCA.Common.UI/wwwroot/capabilities-interop.js:37-46): with noopener the spec makes window.open return null even on success, so a popup blocked by the browser still reports true. Not determinable from source: whether any consumer branches on that bool today.

    InMemoryDevicePreferences

    @@ -1685,7 +1685,7 @@

    NullGeocodingService

  • Walkthrough - sealed class implementing the interface (NullGeocodingService.cs:4). IsSupported => false (NullGeocodingService.cs:7) tells callers to omit the proximity hint entirely. GeocodeAsync (NullGeocodingService.cs:10-11) ignores its address and cancellationToken arguments and returns Task.FromResult<GeoPoint?>(null), an already-completed task, so there is no async state machine and no thread hop.
  • Why it's built this way - ADR-042 (device capability abstraction). The domain model deliberately carries addresses and no coordinates, so geocoding is a pure presentation-time convenience and this contract is "the only place they ever exist" (MMCA.Common.UI/Services/Capabilities/Geo/IGeocodingService.cs:3-8). Making the unsupported case a first-class null (rather than an exception or a feature flag the caller must invent) keeps proximity hints optional everywhere.
  • -
  • Where it's used - registered by AddDeviceCapabilityDefaults (MMCA.Common.UI/Services/Capabilities/DependencyInjection.cs:46) and resolved by any component that shows a distance-from-venue hint, for example ADC's public event detail page, which injects both location contracts and bails out of the hint when either reports unsupported (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventDetail.razor.cs:26-27,220). The MAUI head replaces it with MauiGeocodingService (MMCA.Common.UI.Maui/DependencyInjection.cs:53); there is no browser override, so web and Server heads keep this null default.
  • +
  • Where it's used - registered by AddDeviceCapabilityDefaults (MMCA.Common.UI/Services/Capabilities/DependencyInjection.cs:46) and resolved by any component that shows a distance-from-venue hint, for example ADC's public event detail page, which injects both location contracts and bails out of the hint when either reports unsupported (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventDetail.razor.cs:25-26,208). The MAUI head replaces it with MauiGeocodingService (MMCA.Common.UI.Maui/DependencyInjection.cs:53); there is no browser override, so web and Server heads keep this null default.
  • NullGeolocationService

    @@ -1697,7 +1697,7 @@

    NullGeolocationService

  • Concept introduced - none new. This is the sibling of NullGeocodingService for the "where is this device" half of the location story (geocoding turns an address into a point, geolocation reads the device's own point). The same [Rubric §1 - SOLID], [Rubric §2 - Design Patterns], and [Rubric §22 - Responsive / Cross-Browser] notes apply.
  • Walkthrough - sealed class (NullGeolocationService.cs:4). IsSupported => false (NullGeolocationService.cs:7). GetCurrentOrLastKnownAsync returns Task.FromResult<GeoPoint?>(null) (NullGeolocationService.cs:10-11); because it never touches the platform it also never fires the OS permission prompt the real contract warns about (MMCA.Common.UI/Services/Capabilities/Geo/IGeolocationService.cs:13-18), which is the desired behavior on a head that could not honor a grant anyway.
  • Why it's built this way - ADR-042. Location is soft and best-effort by contract: permission denial, timeout, or any platform failure already yields null (MMCA.Common.UI/Services/Capabilities/Geo/IGeolocationService.cs:3-6,14-16), so a head with no location provider is just the permanent version of that same "no fix" outcome, and no caller needs a second code path for it.
  • -
  • Where it's used - registered by AddDeviceCapabilityDefaults (MMCA.Common.UI/Services/Capabilities/DependencyInjection.cs:45); consumed alongside the geocoder by ADC's public event detail page (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventDetail.razor.cs:26,220). The MAUI head overrides it with MauiGeolocationService (MMCA.Common.UI.Maui/DependencyInjection.cs:52); web and Server heads keep this default, since AddBrowserDeviceCapabilities registers no geolocation implementation (MMCA.Common.UI/Services/Capabilities/DependencyInjection.cs:95-110).
  • +
  • Where it's used - registered by AddDeviceCapabilityDefaults (MMCA.Common.UI/Services/Capabilities/DependencyInjection.cs:45); consumed alongside the geocoder by ADC's public event detail page (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/Public/Events/PublicEventDetail.razor.cs:25,208). The MAUI head overrides it with MauiGeolocationService (MMCA.Common.UI.Maui/DependencyInjection.cs:52); web and Server heads keep this default, since AddBrowserDeviceCapabilities registers no geolocation implementation (MMCA.Common.UI/Services/Capabilities/DependencyInjection.cs:95-110).
  • IClipboardService

    @@ -1739,8 +1739,15 @@

    IExternalLinkService

  • InterceptsLinks (IExternalLinkService.cs:16): whether links must be intercepted and opened via OpenAsync (true in native WebView hosts); when false, components may render a plain anchor with target="_blank" (IExternalLinkService.cs:12-14).
  • -
  • OpenAsync(Uri uri, CancellationToken = default) (IExternalLinkService.cs:19): opens the URI in - the system browser or a new tab, best-effort (IExternalLinkService.cs:18).
  • +
  • OpenAsync(Uri uri, CancellationToken = default) (IExternalLinkService.cs:23): opens the URI in + the system browser or a new tab, best-effort (IExternalLinkService.cs:18), and returns + Task<bool>: true when the platform reported the URL was opened, false when nothing was + opened (no handler, JS interop unavailable, or a host that does not open links) + (IExternalLinkService.cs:21-22). BrowserMapNavigationService + passes that result straight through as its own outcome + (MMCA.Common.UI/Services/Capabilities/Geo/BrowserMapNavigationService.cs:29), while the + ExternalLink component awaits it and ignores the value + (MMCA.Common.UI/Components/Capabilities/ExternalLink.razor:41).
  • Why it's built this way: exposing InterceptsLinks means the browser head keeps native anchor @@ -1823,18 +1830,18 @@

    NullExternalLinkService

    • What it is: the default IExternalLinkService: no link interception, so components render plain anchors with target="_blank", which the class summary notes is the correct behavior on web heads even without JavaScript (NullExternalLinkService.cs:3-6).
    • Depends on: IExternalLinkService; BCL Uri and Task.
    • -
    • Concept introduced, the flag that is a routing switch rather than a capability probe. InterceptsLinks does not mean "I can open links"; it means "route clicks through me instead of letting the browser handle them" (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Capabilities/Interop/IExternalLinkService.cs:11-16). The shared ExternalLink component reads it and builds an EventCallback only when it is true, otherwise leaving the click handler as default so the plain MudLink with target="_blank" and rel="noopener noreferrer" handles the navigation natively (MMCA.Common/Source/Presentation/MMCA.Common.UI/Components/Capabilities/ExternalLink.razor:10-15, :31-34). With this default in place, OpenAsync is therefore never called, which is why its inert body is harmless.
        +
      • Concept introduced, the flag that is a routing switch rather than a capability probe. InterceptsLinks does not mean "I can open links"; it means "route clicks through me instead of letting the browser handle them" (MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Capabilities/Interop/IExternalLinkService.cs:11-16). The shared ExternalLink component reads it and builds an EventCallback only when it is true, otherwise leaving the click handler as default so the plain MudLink with target="_blank" and rel="noopener noreferrer" handles the navigation natively (MMCA.Common/Source/Presentation/MMCA.Common.UI/Components/Capabilities/ExternalLink.razor:11-16, :32-35). With this default in place, OpenAsync is therefore never called from the component, which is why its inert body is harmless.
        • [Rubric §25, Navigation & IA] assesses whether navigation stays coherent across surfaces. This flag exists because target="_blank" silently dead-ends inside a WebView, so the native head must intercept while the web head must not (IExternalLinkService.cs:3-8).
        • -
        • [Rubric §26, Front-End Security] assesses outbound-link hygiene. The rel="noopener noreferrer" that protects the opener lives in the component, not here, and it is exactly the path this default leaves in charge (ExternalLink.razor:14).
        • +
        • [Rubric §26, Front-End Security] assesses outbound-link hygiene. The rel="noopener noreferrer" that protects the opener lives in the component, not here, and it is exactly the path this default leaves in charge (ExternalLink.razor:15).
      • Walkthrough
        • InterceptsLinks (NullExternalLinkService.cs:10): constant false, the "let the anchor do its job" answer.
        • -
        • OpenAsync(Uri uri, CancellationToken) (NullExternalLinkService.cs:13): returns Task.CompletedTask, dropping the URI. Reached only if a caller ignores InterceptsLinks and calls it anyway.
        • +
        • OpenAsync(Uri uri, CancellationToken) (NullExternalLinkService.cs:13): returns Task.FromResult(false), dropping the URI and reporting truthfully that nothing was opened. Reached only if a caller ignores InterceptsLinks and calls it anyway, and a caller that forwards the result (such as BrowserMapNavigationService, MMCA.Common/Source/Presentation/MMCA.Common.UI/Services/Capabilities/Geo/BrowserMapNavigationService.cs:29) then sees a failure rather than a false success.
      • Why it's built this way: ADR-042. The framework routes every external link through one component so the WebView problem is solved once; on hosts without that problem the cheapest correct behavior is the browser's own.
      • -
      • Where it's used: TryAddSingleton in AddDeviceCapabilityDefaults() (DependencyInjection.cs:56); overridden by BrowserExternalLinkService (DependencyInjection.cs:107) and by MauiExternalLinkService (MMCA.Common/Source/Presentation/MMCA.Common.UI.Maui/DependencyInjection.cs:54), which is the implementation that actually sets InterceptsLinks to true. CapabilityFallbackTests asserts the flag is false and OpenAsync does not throw (CapabilityFallbackTests.cs:156, :156).
      • +
      • Where it's used: TryAddSingleton in AddDeviceCapabilityDefaults() (DependencyInjection.cs:47); overridden by BrowserExternalLinkService (DependencyInjection.cs:102) and by MauiExternalLinkService (MMCA.Common/Source/Presentation/MMCA.Common.UI.Maui/DependencyInjection.cs:54), which is the implementation that actually sets InterceptsLinks to true. CapabilityFallbackTests asserts the flag is false and OpenAsync does not throw (CapabilityFallbackTests.cs:156, :162).

      NullScreenshotService

      @@ -1889,12 +1896,12 @@

      BrowserExternalLinkService

      • What it is: the web adapter for IExternalLinkService. On the web an anchor already works, so it reports InterceptsLinks => false and only handles programmatic opens (the maps fallback) through window.open.
      • -
      • Depends on: IExternalLinkService and CapabilitiesJsModule (MMCA.Common.UI/Services/Capabilities/Interop/BrowserExternalLinkService.cs:10, :13); the openExternal export (MMCA.Common.UI/wwwroot/capabilities-interop.js:37); BCL Uri.
      • -
      • Concept introduced: the InterceptsLinks capability flag, the group's answer to "should the component render a plain anchor or route through the service". A native head inside a BlazorWebView must intercept, because target="_blank" dead-ends there; a browser head must not, because intercepting would replace working native anchor behavior (middle-click, open-in-new-tab, the browser's own popup policy) with a worse imitation (:3-6). [Rubric §25, Navigation & IA] assesses whether navigation is coherent and predictable across the app; one flag lets a single shared link component be correct on both heads. [Rubric §26, Front-End Security] applies to the JS side: window.open(url, '_blank', 'noopener,noreferrer') (MMCA.Common.UI/wwwroot/capabilities-interop.js:39) prevents the opened page from reaching back through window.opener and strips the referrer.
      • -
      • Walkthrough: the constructor captures the module (MMCA.Common.UI/Services/Capabilities/Interop/BrowserExternalLinkService.cs:13). InterceptsLinks => false (:16). OpenAsync(Uri uri, CancellationToken = default) (:19-26) null-guards uri (:21) and invokes openExternal with uri.ToString() (:23-25), discarding the result because the contract returns Task.
      • +
      • Depends on: IExternalLinkService and CapabilitiesJsModule (MMCA.Common.UI/Services/Capabilities/Interop/BrowserExternalLinkService.cs:10, :13); the openExternal export (MMCA.Common.UI/wwwroot/capabilities-interop.js:40); BCL Uri.
      • +
      • Concept introduced: the InterceptsLinks capability flag, the group's answer to "should the component render a plain anchor or route through the service". A native head inside a BlazorWebView must intercept, because target="_blank" dead-ends there; a browser head must not, because intercepting would replace working native anchor behavior (middle-click, open-in-new-tab, the browser's own popup policy) with a worse imitation (:3-6). [Rubric §25, Navigation & IA] assesses whether navigation is coherent and predictable across the app; one flag lets a single shared link component be correct on both heads. [Rubric §26, Front-End Security] applies to the JS side: window.open(url, '_blank', 'noopener,noreferrer') (MMCA.Common.UI/wwwroot/capabilities-interop.js:42) prevents the opened page from reaching back through window.opener and strips the referrer.
      • +
      • Walkthrough: the constructor captures the module (MMCA.Common.UI/Services/Capabilities/Interop/BrowserExternalLinkService.cs:13). InterceptsLinks => false (:16). OpenAsync(Uri uri, CancellationToken = default) (:19-28) null-guards uri (:21), invokes openExternal with uri.ToString() through InvokeOrDefaultAsync<bool?> (:24-26), and returns opened ?? false (:27): the JS boolean when the call ran, false when interop was unavailable (prerender, a torn-down circuit) and nothing was opened (:23).
      • Why it's built this way: leaving anchors alone on the web is the cheaper and more correct default; the programmatic path exists only for callers like BrowserMapNavigationService that have no anchor to click.
      • Where it's used: registered scoped as IExternalLinkService by AddBrowserDeviceCapabilities() (MMCA.Common.UI/Services/Capabilities/DependencyInjection.cs:102); siblings are MauiExternalLinkService and NullExternalLinkService. Consumed by the shared external-link component and by BrowserMapNavigationService.
      • -
      • Caveats / not-in-source: a popup blocker can make window.open return null while the JS still reports true (MMCA.Common.UI/wwwroot/capabilities-interop.js:37-44 only catches a throw), so an open silently blocked by the browser is not detected.
      • +
      • Caveats / not-in-source: true means only that window.open did not throw. The JS reports true unless it catches a throw (MMCA.Common.UI/wwwroot/capabilities-interop.js:41-46), and its comment states why it cannot do better: with the noopener feature the HTML spec makes window.open return null even when the tab opened, so a blocked popup cannot be told apart from an opened one (capabilities-interop.js:37-39). A popup silently blocked by the browser is therefore still reported as opened.

      BrowserShareService

      @@ -2094,7 +2101,7 @@

      NullSpeechToTextService

  • Why it's built this way: ADR-042. Speech recognition is a platform service with a microphone permission attached, so an unavailable recognizer must be an ordinary, prompt-free null rather than an exception a form has to handle.
  • -
  • Where it's used: TryAddSingleton in AddDeviceCapabilityDefaults() (DependencyInjection.cs:63); MauiSpeechToTextService is the native override (MMCA.Common/Source/Presentation/MMCA.Common.UI.Maui/DependencyInjection.cs:62). ADC registers it explicitly in a bUnit test so the live-channel page renders without a recognizer (MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.UI.Tests/Pages/LiveChannelJoinTests.cs:61-62), and CapabilityFallbackTests pins the null result (CapabilityFallbackTests.cs:152-153).
  • +
  • Where it's used: TryAddSingleton in AddDeviceCapabilityDefaults() (DependencyInjection.cs:63); MauiSpeechToTextService is the native override (MMCA.Common/Source/Presentation/MMCA.Common.UI.Maui/DependencyInjection.cs:62). ADC registers it explicitly in a bUnit test so the live-channel page renders without a recognizer (MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.UI.Tests/Pages/LiveChannelJoinTests.cs:62-63), and CapabilityFallbackTests pins the null result (CapabilityFallbackTests.cs:152-153).
  • NullTextToSpeechService

    @@ -2422,7 +2429,7 @@

    NullLocalNotificationService

  • Concept introduced - none new, but note the two-signal contract this default has to satisfy cleanly. Scheduling notifications is native-only (there is no browser equivalent the framework wires), so the default has to make both the capability probe and the actions safe: IsSupported == false steers the UI, and the action methods are no-ops so a caller that skips the probe still cannot crash. Same [Rubric §2 - Design Patterns] and [Rubric §22 - Responsive / Cross-Browser] framing as NullGeocodingService.
  • Walkthrough - sealed class (NullLocalNotificationService.cs:4). IsSupported => false (NullLocalNotificationService.cs:7). RequestPermissionAsync returns Task.FromResult(false) (NullLocalNotificationService.cs:10-11), reporting permission as not granted so a caller never proceeds to schedule. ScheduleAsync (NullLocalNotificationService.cs:14-15), CancelAsync (NullLocalNotificationService.cs:18-19), and CancelAllAsync (NullLocalNotificationService.cs:22) each return Task.CompletedTask, doing nothing with their arguments; CancelAsync in particular is already contractually allowed to ignore unknown ids (MMCA.Common.UI/Services/Capabilities/Notifications/ILocalNotificationService.cs:24-25), so ignoring all of them is a consistent extreme of the same rule.
  • Why it's built this way - ADR-042. The real contract already specifies that scheduling without permission is a no-op and that implementations never throw on denial (MMCA.Common.UI/Services/Capabilities/Notifications/ILocalNotificationService.cs:6-8,21), so the null default is that rule taken to its limit: permission is never granted, therefore nothing is ever scheduled. Reminder features degrade to nothing on web without a single conditional in the feature code.
  • -
  • Where it's used - registered by AddDeviceCapabilityDefaults (MMCA.Common.UI/Services/Capabilities/DependencyInjection.cs:50); overridden by MauiLocalNotificationService on native heads (MMCA.Common.UI.Maui/DependencyInjection.cs:57). ADC's SessionReminderCoordinator is the shaped consumer: it takes the contract by constructor injection, re-exposes IsSupported for the UI to bind against, and short-circuits its schedule and cancel entry points when the capability reports unsupported (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Services/HappeningNow/SessionReminderCoordinator.cs:21,40,60,90). Web and Server heads keep this default.
  • +
  • Where it's used - registered by AddDeviceCapabilityDefaults (MMCA.Common.UI/Services/Capabilities/DependencyInjection.cs:50); overridden by MauiLocalNotificationService on native heads (MMCA.Common.UI.Maui/DependencyInjection.cs:57). ADC's SessionReminderCoordinator is the shaped consumer: it takes the contract by constructor injection, re-exposes IsSupported for the UI to bind against, and short-circuits its schedule and cancel entry points when the capability reports unsupported (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Services/HappeningNow/SessionReminderCoordinator.cs:26,46,66,96). Web and Server heads keep this default.
  • NullPushDeviceTokenProvider

    @@ -2474,7 +2481,7 @@

    MauiPublicLinkBuilder

  • Why it's built this way: UriKind.Absolute at construction plus the blank check means a bad PublicSite:BaseUrl fails once, at container build, rather than producing a plausible but wrong link on every share. The class doc notes the value is pinned by the same mechanism as the head's gateway endpoint (:8-10), so one embedded configuration file defines both where the app talks and what it links to.
  • -
  • Where it's used: registered scoped as IPublicLinkBuilder by AddCommonMauiPublicLinkBuilder() (MMCA.Common.UI.Maui/DependencyInjection.cs:134-135), which replaces the TryAddScoped default NavigationPublicLinkBuilder installed by AddUIShared (MMCA.Common.UI/DependencyInjection.cs:150). Consumed by the share, copy-link and QR affordances that pair with IShareService and IClipboardService.
  • +
  • Where it's used: registered scoped as IPublicLinkBuilder by AddCommonMauiPublicLinkBuilder() (MMCA.Common.UI.Maui/DependencyInjection.cs:134-135), which replaces the TryAddScoped default NavigationPublicLinkBuilder installed by AddUIShared (MMCA.Common.UI/DependencyInjection.cs:166). Consumed by the share, copy-link and QR affordances that pair with IShareService and IClipboardService.
  • MauiSecureTokenStore

    @@ -2499,14 +2506,14 @@

    MauiSecureTokenStore

    MauiTokenStorageService

    -

    MMCA.Common.UI.Maui · MMCA.Common.UI.Maui.Services · MMCA.Common.UI.Maui/Services/MauiTokenStorageService.cs:19 · Level 1 · class (sealed)

    +

    MMCA.Common.UI.Maui · MMCA.Common.UI.Maui.Services · MMCA.Common.UI.Maui/Services/MauiTokenStorageService.cs:25 · Level 1 · class (sealed)

    • What it is: the freshness-checking ITokenStorageService that sits on top of MauiSecureTokenStore. Raw persistence is delegated; this type adds what a long-lived mobile session needs, namely a proactive refresh when the stored access token is at or near expiry, and a single-flight guarantee so concurrent callers share one refresh.
    • -
    • Depends on: ITokenStorageService (the contract, MMCA.Common.UI.Maui/Services/MauiTokenStorageService.cs:21), ISecureTokenStore and ITokenRefresher (primary-constructor parameters, :19-21), and JwtTokenInfo (:33); BCL System.Threading.Lock (:25).
    • +
    • Depends on: ITokenStorageService (the contract, MMCA.Common.UI.Maui/Services/MauiTokenStorageService.cs:27), ISecureTokenStore and ITokenRefresher (primary-constructor parameters, :19-21), and JwtTokenInfo (:33); BCL System.Threading.Lock (:25).
    • Concept introduced, single-flight acquisition. A mobile head has several independent consumers of the access token: the delegating handler on every API call, the auth-state provider, and the SignalR connection (:9-12). If two of them find the token stale at the same moment and each starts its own refresh, the second refresh rotates the refresh token again and invalidates the pair the first caller is still holding, so a purely additive fix (_hydrateInFlight ??= HydrateAsync() with no lock) is not enough. The pattern here is: take a short lock only long enough to publish or read the shared task, await it outside the lock, then clear the slot only if it is still yours. [Rubric §12, Performance & Scalability] assesses whether the system avoids redundant work under concurrency; collapsing N refreshes into one is that. [Rubric §11, Security] applies because token rotation makes the redundant refresh actively harmful, not merely wasteful. [Rubric §1, SOLID] applies to the split itself: freshness policy and raw persistence are two responsibilities and now live in two types, which is also what keeps the DI graph acyclic.
    • Walkthrough
        -
      • ExpirySkew = TimeSpan.FromSeconds(30) (MMCA.Common.UI.Maui/Services/MauiTokenStorageService.cs:23): the head start. A token that expires inside the next 30 seconds is treated as already stale, so a refresh happens before a request can fail.
      • +
      • ExpirySkew = TimeSpan.FromSeconds(30) (MMCA.Common.UI.Maui/Services/MauiTokenStorageService.cs:29): the head start. A token that expires inside the next 30 seconds is treated as already stale, so a refresh happens before a request can fail.
      • _hydrateSync (:25), a System.Threading.Lock, and _hydrateInFlight (:27), the nullable shared Task<string?>.
      • GetAccessTokenAsync() (:30-66): reads the stored token from the raw store (:32) and returns it verbatim when JwtTokenInfo.IsFresh(stored, ExpirySkew) (:33-36), the fast path that touches no lock. Otherwise it enters the lock, assigns _hydrateInFlight ??= HydrateAsync(), and copies the task to a local (:43-48); the comment at :38-42 records both why the lock is needed and why holding it is cheap (HydrateAsync reaches its first await immediately, so nothing slow runs under it). It awaits the shared task (:52), and in a finally re-takes the lock and clears the field only when it still references the same task (:58-64), the guard the comment at :56-57 explains: an unguarded clear can drop a newer hydrate started after this one completed, splitting the next set of callers again.
      • GetRefreshTokenAsync() (:69), SetTokensAsync(...) (:72-73), ClearTokensAsync() (:76): straight pass-throughs to the raw store. Only the access-token read has freshness semantics.
      • @@ -2569,7 +2576,7 @@

        MauiExternalLinkService

      • Walkthrough
        • InterceptsLinks (MauiExternalLinkService.cs:13): true, telling shared components to route through OpenAsync rather than render a raw anchor.
        • -
        • OpenAsync(Uri uri, CancellationToken = default) (MauiExternalLinkService.cs:16): null-guards uri (:18); for http and https (compared with Uri.UriSchemeHttp/Https under OrdinalIgnoreCase) it uses Browser.Default.OpenAsync(uri, BrowserLaunchMode.SystemPreferred) and returns (:22-27); everything else (mailto:, tel:, sms:) goes to Launcher.Default.TryOpenAsync (:31), because Browser.Default only accepts http(s) (:29-30). FeatureNotSupportedException is swallowed: the link is a convenience, not a workflow (:33-36).
        • +
        • Task<bool> OpenAsync(Uri uri, CancellationToken = default) (MauiExternalLinkService.cs:16): null-guards uri (:18); for http and https (compared with Uri.UriSchemeHttp/Https under OrdinalIgnoreCase) it uses Browser.Default.OpenAsync(uri, BrowserLaunchMode.SystemPreferred) and returns true (:22-27); everything else (mailto:, tel:, sms:) goes to Launcher.Default.TryOpenAsync, whose own bool is returned as the result (:31), because Browser.Default only accepts http(s) (:29-30). FeatureNotSupportedException is swallowed and reported as false: the link is a convenience, not a workflow (:33-37). The bool honours the contract's "the platform reported the URL was opened" meaning (MMCA.Common.UI/Services/Capabilities/Interop/IExternalLinkService.cs:21-23), so a caller can tell an unhandled scheme from a successful launch.
      • Why it's built this way: splitting web schemes (system browser) from contact schemes (OS launcher) is what makes mailto: and tel: links work from inside the WebView, where a plain anchor would silently do nothing and the browser API would reject the scheme outright.
      • @@ -2591,8 +2598,8 @@

        MauiLocalCacheStore

      • SetAsync<T>(string key, T value, CancellationToken = default) (MauiLocalCacheStore.cs:17): guards the key (:19), resolves the path creating the directory (:23), serializes (:24), and File.WriteAllTextAsyncs (:25), catching IOException and UnauthorizedAccessException because a failed write only means a colder next launch (:27-34).
      • GetAsync<T>(string key, CancellationToken = default) (MauiLocalCacheStore.cs:38): guards the key (:40), returns default when the file does not exist (:45-48), otherwise reads and deserializes (:50-51), collapsing IOException, UnauthorizedAccessException and JsonException to default (:53-64).
      • RemoveAsync(string key, CancellationToken = default) (MauiLocalCacheStore.cs:68): deletes the file (:74), swallowing the same IO failures (:76-83), and returns a completed task (:85).
      • -
      • ClearAsync(CancellationToken = default) (MauiLocalCacheStore.cs:151): deletes the whole mmca-cache directory recursively when it exists (:155-159), swallowing the same IOException/UnauthorizedAccessException pair as the other members (:161-168), and returns a completed task (:170).
      • -
      • GetPath(string key, bool ensureDirectory) (MauiLocalCacheStore.cs:173): builds mmca-cache under FileSystem.AppDataDirectory (:175), optionally creates it (:176-179), and maps the key to a file name through a conservative character filter that keeps ASCII letters, digits, - and . and replaces everything else with _, then appends .json (:181-182). The class doc notes keys are code-controlled, not user input (:8-9).
      • +
      • ClearAsync(CancellationToken = default) (MauiLocalCacheStore.cs:89): deletes the whole mmca-cache directory recursively when it exists (:93-97), swallowing the same IOException/UnauthorizedAccessException pair as the other members (:99-106), and returns a completed task (:108).
      • +
      • GetPath(string key, bool ensureDirectory) (MauiLocalCacheStore.cs:111): builds mmca-cache under FileSystem.AppDataDirectory (:113), optionally creates it (:114-117), and maps the key to a file name through a conservative character filter that keeps ASCII letters, digits, - and . and replaces everything else with _, then appends .json (:119-120). The class doc notes keys are code-controlled, not user input (:8-9).
    • Why it's built this way: file-per-key JSON keeps the store dependency-free (no embedded database to ship or migrate), and best-effort IO with default returns means a cache miss or a corrupt file degrades to a live fetch rather than surfacing an error to the user; ClearAsync reuses the same best-effort disposition so a full-cache wipe cannot itself throw.
    • @@ -2750,7 +2757,7 @@

      DeepLinkRouteEventArgs

  • Why it's built this way: a small dedicated EventArgs type keeps the dispatcher's event strongly typed and lets the listener component read the route without casting, part of the single-funnel deep-link design (ADR-042).
  • -
  • Where it's used: declared as the payload of IDeepLinkDispatcher.RouteRequested (MMCA.Common.UI/Services/Capabilities/Navigation/IDeepLinkDispatcher.cs:13), constructed inside DeepLinkDispatcher.Publish (MMCA.Common.UI/Services/Capabilities/Navigation/DeepLinkDispatcher.cs:133), and read by the DeepLinkListener component's handler (MMCA.Common.UI/Components/Capabilities/DeepLinkListener.razor:32-36).
  • +
  • Where it's used: declared as the payload of IDeepLinkDispatcher.RouteRequested (MMCA.Common.UI/Services/Capabilities/Navigation/IDeepLinkDispatcher.cs:13), constructed inside DeepLinkDispatcher.Publish (MMCA.Common.UI/Services/Capabilities/Navigation/DeepLinkDispatcher.cs:137), and read by the DeepLinkListener component's handler (MMCA.Common.UI/Components/Capabilities/DeepLinkListener.razor:32-36).
  • CapabilitiesJsModule

    @@ -2797,7 +2804,7 @@

    WebFormFactor

  • Walkthrough: the class is sealed and stateless, with no fields and no constructor (WebFormFactor.cs:12-19). GetFormFactor() is an expression-bodied member returning the constant "Web" (WebFormFactor.cs:15); it is a constant rather than a probe because Blazor Server always executes this code server-side, so there is nothing to detect. GetPlatform() returns Environment.OSVersion.ToString() (WebFormFactor.cs:18), the server OS description. Both members carry <inheritdoc/> (WebFormFactor.cs:14,17), so the documented vocabulary for the return values lives once on the interface (MMCA.Common.UI/Services/IFormFactor.cs:9,12).
  • Why it's built this way: prerender and interactive Server render both run on the server, so no reliable client-device signal exists at this layer; answering "Web" plus the server OS is the honest answer for this host rather than a guess about the browser. Keeping the type stateless and app-neutral is what allowed it to move up into MMCA.Common.UI.Web and be shared by every Blazor Web host (ADR-042).
  • -
  • Where it's used: registered by the Blazor Server host through AddCommonWebFormFactor(), which binds IFormFactor to this class as a singleton (MMCA.Common.UI.Web/DependencyInjection.cs:70-71); the same XML doc points the WASM client at AddWasmFormFactor() from MMCA.Common.UI instead (MMCA.Common.UI.Web/DependencyInjection.cs:66-68). Both server heads call it once at startup: ADC at MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:164 and Store at MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:132. Resolved by any shared component that injects IFormFactor to branch on the current host.
  • +
  • Where it's used: registered by the Blazor Server host through AddCommonWebFormFactor(), which binds IFormFactor to this class as a singleton (MMCA.Common.UI.Web/DependencyInjection.cs:70-71); the same XML doc points the WASM client at AddWasmFormFactor() from MMCA.Common.UI instead (MMCA.Common.UI.Web/DependencyInjection.cs:66-68). Both server heads call it once at startup: ADC at MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:176 and Store at MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:132. Resolved by any shared component that injects IFormFactor to branch on the current host.
  • Caveats / not-in-source: GetPlatform() reports the server OS, not the browser or the client device, so it must not be read as a client fingerprint. Because the registration is a plain AddSingleton, a head that also called AddWasmFormFactor() in the same container would end up with two IFormFactor descriptors and last-registration-wins resolution; nothing in this type guards against that.
  • MauiLocalNotificationService

    @@ -2828,25 +2835,28 @@

    MauiLocalNotificationService

    MauiPushRegistrationService

    -

    MMCA.Common.UI.Maui · MMCA.Common.UI.Maui.Capabilities.Notifications · MMCA.Common.UI.Maui/Capabilities/Notifications/MauiPushRegistrationService.cs:16 · Level 2 · class (sealed partial, primary constructor)

    +

    MMCA.Common.UI.Maui · MMCA.Common.UI.Maui.Capabilities.Notifications · MMCA.Common.UI.Maui/Capabilities/Notifications/MauiPushRegistrationService.cs:16 · Level 2 · class (sealed partial, primary constructor, IDisposable)

      -
    • What it is: the MAUI implementation of IPushRegistrationService (ADR-044): it orchestrates registering this device with the server by asking the app for a platform push token, minting and remembering a stable installation id, and syncing that pair to the API's Notifications/Devices endpoints.
    • -
    • Depends on: IPushDeviceTokenProvider (the app-supplied token source), IHttpClientFactory, IDevicePreferences (durable storage for the installation id), and ILogger<MauiPushRegistrationService>, all four taken by the primary constructor (MauiPushRegistrationService.cs:16-20); plus PushDeviceToken, System.Net.Http.Json, and source-generated [LoggerMessage] partials.
    • -
    • Concept introduced, the orchestrator adapter. Every other native adapter in this unit wraps one platform static. This one wraps nothing: it is a composition of three injected services plus an HTTP call, and it is the type that makes the two-part push design from IPushDeviceTokenProvider concrete. The framework ships the whole registration pipeline (this class), the app ships the credentialed token source, and until it does, GetTokenAsync returns null and RegisterAsync exits at its first check (MauiPushRegistrationService.cs:32-36). "Wired but inert" is not a special mode, it is just this early return.
        -
      • The second idea here is the client-generated installation id. The device, not the server, mints a Guid (:93) and persists it in device preferences under a fixed key (:21), so one physical install keeps one server-side device row across token rotations and re-registrations. Using PUT rather than POST against Notifications/Devices (:39-42) is what makes re-registration idempotent.
      • -
      • [Rubric §29, Resilience & Business Continuity] assesses degradation: registration is explicitly a best-effort side channel that never throws (:11-13). Both public methods funnel every exception into a warning log and a false or void return (:52-58, :77-82), so a failed registration can never break a login or a page render.
      • -
      • [Rubric §13, Observability & Operability] assesses diagnostics quality. All three failure paths log through source-generated [LoggerMessage] methods at Warning (:98-105), and a rejected registration logs the actual HTTP status code (:46), which is the difference between a debuggable failure and a silent one.
      • -
      • [Rubric §11, Security] assesses credential handling. The class holds no push credentials at all: it receives an already-minted platform token and posts it over the authenticated "APIClient" HTTP client (:38).
      • +
      • What it is: the MAUI implementation of IPushRegistrationService (ADR-044): it orchestrates registering this device with the server by asking the app for a platform push token, minting and remembering a stable installation id, and syncing that pair to the API's Notifications/Devices endpoints. Registration is serialized behind a one-slot SemaphoreSlim (MauiPushRegistrationService.cs:24), which is why the class also implements IDisposable (:20, :61).
      • +
      • Depends on: IPushDeviceTokenProvider (the app-supplied token source), IHttpClientFactory, IDevicePreferences (durable storage for the installation id), and ILogger<MauiPushRegistrationService>, all four taken by the primary constructor (MauiPushRegistrationService.cs:16-20); plus PushDeviceToken, System.Net.Http.Json, System.Threading.SemaphoreSlim, and source-generated [LoggerMessage] partials.
      • +
      • Concept introduced, the orchestrator adapter. Every other native adapter in this unit wraps one platform static. This one wraps nothing: it is a composition of three injected services plus an HTTP call, and it is the type that makes the two-part push design from IPushDeviceTokenProvider concrete. The framework ships the whole registration pipeline (this class), the app ships the credentialed token source, and until it does, GetTokenAsync returns null and RegisterCoreAsync exits at its first check (MauiPushRegistrationService.cs:65-69). "Wired but inert" is not a special mode, it is just this early return.
          +
        • The second idea here is the client-generated installation id. The device, not the server, mints a Guid (:119) and persists it in device preferences under a fixed key (:22), so one physical install keeps one server-side device row across token rotations and re-registrations. Using PUT rather than POST against Notifications/Devices (:73-76) is what makes re-registration idempotent.
        • +
        • The third idea is serializing the whole registration, not just the id creation. Two registration passes can genuinely overlap: OnNewToken fires one on a Firebase thread while the login pass runs one from the renderer, and with an empty preference store (an iOS reinstall keeps the Keychain session but not the preferences) both would mint an installation id and PUT it, leaving two server rows for one device (:30-36). The remark also records why a lock around id creation alone is not enough: both passes would still PUT, one with a stale id. So RegisterAsync holds _registration across the entire RegisterCoreAsync call and releases it in a finally (:41-49).
        • +
        • [Rubric §29, Resilience & Business Continuity] assesses degradation: registration is explicitly a best-effort side channel that never throws (:14-15). Both public methods funnel every exception into a warning log and a false or void return (:52-57, :104-108), so a failed registration can never break a login or a page render. Because the semaphore wait sits inside the try (:41), a cancelled wait is caught the same way.
        • +
        • [Rubric §13, Observability & Operability] assesses diagnostics quality. All three failure paths log through source-generated [LoggerMessage] methods at Warning (:124-131), and a rejected registration logs the actual HTTP status code (:80), which is the difference between a debuggable failure and a silent one.
        • +
        • [Rubric §11, Security] assesses credential handling. The class holds no push credentials at all: it receives an already-minted platform token and posts it over the authenticated "APIClient" HTTP client (:72).
      • Walkthrough
          -
        • The primary constructor (MauiPushRegistrationService.cs:16-20) takes the four dependencies; InstallationIdKey is the fixed preference key mmca.push.installationId (:21).
        • -
        • IsSupported (MauiPushRegistrationService.cs:25): a constant true.
        • -
        • RegisterAsync(CancellationToken = default) (MauiPushRegistrationService.cs:28): gets the token and returns false if there is none (:31-35); resolves or creates the installation id (:37); creates the named "APIClient" in a using (:38); PUTs an anonymous body of installation id, Platform, and PushChannel to the relative Notifications/Devices URI (:39-42); logs and returns false on a non-success status (:44-48), otherwise true (:50).
        • -
        • UnregisterAsync(CancellationToken = default) (MauiPushRegistrationService.cs:63): reads the stored installation id and returns early when there is none (:66-70); otherwise DELETEs Notifications/Devices/{id} with the id URL-escaped (:72-75). Note that it deliberately does not clear the stored id, so a later re-register reuses the same installation.
        • -
        • GetOrCreateInstallationIdAsync(CancellationToken) (MauiPushRegistrationService.cs:86): returns the stored id when non-blank (:87-91), else generates Guid.NewGuid().ToString("N"), persists it through IDevicePreferences, and returns it (:93-95).
        • -
        • Three [LoggerMessage] partial declarations at Warning level (MauiPushRegistrationService.cs:99-106): registration rejected with a status code, registration failed, unregistration failed.
        • +
        • The primary constructor (MauiPushRegistrationService.cs:16-20) takes the four dependencies; InstallationIdKey is the fixed preference key mmca.push.installationId (:22); _registration is a SemaphoreSlim(1, 1) (:24).
        • +
        • IsSupported (MauiPushRegistrationService.cs:27): a constant true.
        • +
        • RegisterAsync(CancellationToken = default) (MauiPushRegistrationService.cs:37): awaits _registration.WaitAsync (:41), runs RegisterCoreAsync and releases the semaphore in a finally (:42-49); any exception is logged and turned into false (:52-57).
        • +
        • Dispose() (MauiPushRegistrationService.cs:61): disposes the semaphore.
        • +
        • RegisterCoreAsync(CancellationToken) (MauiPushRegistrationService.cs:63, private): gets the token and returns false if there is none (:65-69); resolves or creates the installation id (:71); creates the named "APIClient" in a using (:72); PUTs an anonymous body of installation id, Platform, and PushChannel to the relative Notifications/Devices URI (:73-76); logs and returns false on a non-success status (:78-82), otherwise true (:84).
        • +
        • UnregisterAsync(CancellationToken = default) (MauiPushRegistrationService.cs:88): reads the stored installation id and returns early when there is none (:92-96); otherwise DELETEs Notifications/Devices/{id} with the id URL-escaped (:98-101). It does not take the registration semaphore, and it deliberately does not clear the stored id, so a later re-register reuses the same installation.
        • +
        • GetOrCreateInstallationIdAsync(CancellationToken) (MauiPushRegistrationService.cs:111): returns the stored id when non-blank (:113-117), else generates Guid.NewGuid().ToString("N"), persists it through IDevicePreferences, and returns it (:119-121). It is reached only from RegisterCoreAsync, so it always runs under the semaphore.
        • +
        • Three [LoggerMessage] partial declarations at Warning level (MauiPushRegistrationService.cs:124-131): registration rejected with a status code, registration failed, unregistration failed.
      • Why it's built this way: ADR-044. Splitting "how do I get a token" (app-owned, credentialed) from "how do I tell the server about it" (framework-owned, this class) is what lets the framework ship a complete, tested push path that carries no vendor keys, and the registration comment in the composition root says exactly that (MMCA.Common.UI.Maui/DependencyInjection.cs:64-66).
      • @@ -2876,29 +2886,29 @@

        IDeepLinkDispatcher

      DeepLinkDispatcher

      -

      MMCA.Common.UI · MMCA.Common.UI.Services.Capabilities.Navigation · MMCA.Common.UI/Services/Capabilities/Navigation/DeepLinkDispatcher.cs:9 · Level 3 · class

      +

      MMCA.Common.UI · MMCA.Common.UI.Services.Capabilities.Navigation · MMCA.Common.UI/Services/Capabilities/Navigation/DeepLinkDispatcher.cs:11 · Level 3 · class

        -
      • What it is: the default IDeepLinkDispatcher. It raises RouteRequested when a listener is attached, otherwise buffers the most recent route (capacity one) so a cold-start tap survives until the Blazor router renders (DeepLinkDispatcher.cs:111-120). Publish now also rejects any route that is not app-relative before it does either (:100-109). Registered as a singleton so native callers can resolve it from the MAUI root provider.
      • +
      • What it is: the default IDeepLinkDispatcher. It raises RouteRequested when a listener is attached, otherwise buffers the most recent route (capacity one) so a cold-start tap survives until the Blazor router renders (DeepLinkDispatcher.cs:126-134). Publish also rejects any route that is not app-relative before it does either (:108-113). Registered as a singleton so native callers can resolve it from the MAUI root provider.
      • Depends on: IDeepLinkDispatcher, the contract it implements, and DeepLinkRouteEventArgs, what it raises; the BCL System.Threading.Lock type for its gate.
      • -
      • Concept introduced: reading the event handler inside the lock, and with it the modern System.Threading.Lock. The obvious implementation snapshots RouteRequested into a local, then takes a lock only to write the buffer. The source rejects that explicitly, and the comment records the interleaving it loses (DeepLinkDispatcher.cs:111-120): Publish sees no handler, the listener then subscribes, the listener drains an empty buffer, and only afterwards does Publish write into a buffer nobody will read again, so the route is dropped. That race is real on a native head, where the callback thread and the first render are genuinely concurrent (the warm-boot deep link). Reading the handler and writing the buffer as one step under the same gate leaves only two orders: either the subscription was visible and the event fires, or it was not and the buffer write completes before the lock releases, so the listener's TryConsumePending, which must take the same lock, finds the route. The invoke itself still happens outside the lock (:133) because a listener that navigates on that callback must not run under it.
          -
        • [Rubric §19, State Management] §19 assesses safe transient state. The single-slot _pendingRoute (:12) is read and cleared atomically in TryConsumePending (:139-143), so a buffered route is delivered exactly once.
        • +
        • Concept introduced: reading the event handler inside the lock, and with it the modern System.Threading.Lock. The obvious implementation snapshots RouteRequested into a local, then takes a lock only to write the buffer. The source rejects that explicitly, and the comment records the interleaving it loses (DeepLinkDispatcher.cs:115-124): Publish sees no handler, the listener then subscribes, the listener drains an empty buffer, and only afterwards does Publish write into a buffer nobody will read again, so the route is dropped. That race is real on a native head, where the callback thread and the first render are genuinely concurrent (the warm-boot deep link). Reading the handler and writing the buffer as one step under the same gate leaves only two orders: either the subscription was visible and the event fires, or it was not and the buffer write completes before the lock releases, so the listener's TryConsumePending, which must take the same lock, finds the route. The invoke itself still happens outside the lock (:137) because a listener that navigates on that callback must not run under it.
            +
          • [Rubric §19, State Management] §19 assesses safe transient state. The single-slot _pendingRoute (:14) is read and cleared atomically in TryConsumePending (:143-147), so a buffered route is delivered exactly once.
          • [Rubric §12, Performance & Scalability] §12 assesses lock discipline. The critical section is a field read and a field assignment; the handler invocation, which can trigger navigation and a render, is deliberately outside it.
          • [Rubric §15, Best Practices & Code Quality] §15 assesses whether non-obvious code explains itself. The nine-line comment above the lock states the exact dropped-route interleaving, which is the kind of reasoning that is otherwise lost the first time someone "simplifies" the method.
          • -
          • [Rubric §11, Security] §11 assesses input validation at a trust boundary. IsAppRelativeRoute (:45-68) is the SEC-Common-88 / SEC-ADC-65 fix: an exported Android activity is reachable by an explicit intent from any app on the device, which bypasses the manifest filter's scheme and host constraints, so a hostile caller can hand Publish a route like //attacker.example/p that resolves protocol-relative against the WebView base and leaves the app origin. The check is on the route's shape only (a single leading slash, no backslash, no control character, no scheme), never on the originating host, because a legitimate deep link (a home-screen widget) may have a placeholder host. A shape violation now makes Publish throw ArgumentException (:100-109) instead of forwarding the route to navigation.
          • +
          • [Rubric §11, Security] §11 assesses input validation at a trust boundary. IsAppRelativeRoute (:49-72) is the SEC-Common-88 / SEC-ADC-65 fix: an exported Android activity is reachable by an explicit intent from any app on the device, which bypasses the manifest filter's scheme and host constraints, so a hostile caller can hand Publish a route like //attacker.example/p that resolves protocol-relative against the WebView base and leaves the app origin. The check is on the route's shape only (a single leading slash, no backslash, no control character, no scheme), never on the originating host, because a legitimate deep link (a home-screen widget) may have a placeholder host. A shape violation makes Publish throw ArgumentException (:108-113) instead of forwarding the route to navigation.
        • Walkthrough: static validation helpers, then fields, the event, and two instance methods.
            -
          • IsAppRelativeRoute(string?) (DeepLinkDispatcher.cs:45-68): false for null or whitespace (:47-50); false for any backslash or control character (:52-59); false when StartsWithScheme matches (:61-64); otherwise true only when the route starts with exactly one / followed by a non-slash character (:67).
          • -
          • StartsWithScheme(string) (:74-92): a private RFC 3986 scheme check, ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) ":", used only by IsAppRelativeRoute.
          • -
          • _gate (:11): a Lock instance, the typed C# 13 lock rather than locking on a plain object. _pendingRoute (:12): the single-slot buffer.
          • -
          • RouteRequested event (:15): the implemented event.
          • -
          • Publish(string) (:100-133): validates non-null/whitespace with ArgumentException.ThrowIfNullOrWhiteSpace (:102), then throws ArgumentException when IsAppRelativeRoute rejects the route (:104-109); inside lock (_gate) it reads the handler (:124) and, when it is null, stores the route and returns while still holding the gate (:126-129). With a handler present it falls through and invokes it outside the lock with a new DeepLinkRouteEventArgs (:133).
          • -
          • TryConsumePending(out string?) (:136-146): takes and clears the pending route under the lock (:139-143) and returns whether one was present (:145).
          • +
          • IsAppRelativeRoute([NotNullWhen(true)] string?) (DeepLinkDispatcher.cs:49-72): false for null or whitespace (:51-54); false for any backslash or control character (:59-62); false when StartsWithScheme matches (:64-67); otherwise true only when the route starts with exactly one / followed by a non-slash character (:71). The [NotNullWhen(true)] annotation (:49) tells the compiler's flow analysis that a true result proves the route non-null, so a platform head can guard with this check and pass the value straight to Publish with no separate null test (:30-31); the ADC heads do exactly that (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/Platforms/Android/MainActivity.cs:92-97, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/Platforms/iOS/AppDelegate.cs:41-43).
          • +
          • StartsWithScheme(string) (:78-96): a private RFC 3986 scheme check, ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) ":", used only by IsAppRelativeRoute.
          • +
          • _gate (:13): a Lock instance, the typed C# 13 lock rather than locking on a plain object. _pendingRoute (:14): the single-slot buffer.
          • +
          • RouteRequested event (:17): the implemented event.
          • +
          • Publish(string) (:104-138): validates non-null/whitespace with ArgumentException.ThrowIfNullOrWhiteSpace (:106), then throws ArgumentException when IsAppRelativeRoute rejects the route (:108-113); inside lock (_gate) it reads the handler (:128) and, when it is null, stores the route and returns while still holding the gate (:129-133). With a handler present it falls through and invokes it outside the lock with a new DeepLinkRouteEventArgs (:137).
          • +
          • TryConsumePending(out string?) (:141-150): takes and clears the pending route under the lock (:143-147) and returns whether one was present (:149).
        • Why it's built this way: native taps can arrive on any thread and either before or after the listener attaches, so the dispatcher must be both thread-safe and cold-start-safe. A singleton with a locked single-slot buffer is the minimal design that satisfies both (ADR-042). The route-shape check sits in Publish rather than at each platform head because that is the one boundary every untrusted platform callback crosses (SEC-Common-88 / SEC-ADC-65).
        • -
        • Where it's used: registered as the singleton IDeepLinkDispatcher in AddDeviceCapabilityDefaults (MMCA.Common.UI/Services/Capabilities/DependencyInjection.cs:78); published into by the MAUI notification-tap bridge (MMCA.Common.UI.Maui/DeviceCapabilitiesInitializer.cs:30-40); consumed by the DeepLinkListener component; exercised by DeepLinkDispatcherTests, DeepLinkRouteShapeTests, and DeepLinkListenerTests (see Group 27).
        • +
        • Where it's used: registered as the singleton IDeepLinkDispatcher in AddDeviceCapabilityDefaults (MMCA.Common.UI/Services/Capabilities/DependencyInjection.cs:78); published into by the MAUI notification-tap bridge (MMCA.Common.UI.Maui/DeviceCapabilitiesInitializer.cs:30-40) and by the ADC Android and iOS heads after an IsAppRelativeRoute guard (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/Platforms/Android/MainActivity.cs:92, MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/Platforms/iOS/AppDelegate.cs:41); consumed by the DeepLinkListener component; exercised by DeepLinkDispatcherTests, DeepLinkRouteShapeTests, and DeepLinkListenerTests (see Group 27).

        DependencyInjection

        @@ -2907,7 +2917,7 @@

        DependencyInjection

        • What it is: the composition root for this entire group. One static class holding two registration helpers: AddDeviceCapabilityDefaults() fills the container with a safe default for every capability contract, and AddBrowserDeviceCapabilities() overrides the subset a browser can really do (DependencyInjection.cs:15-22).
        • Depends on: every contract in this chapter, plus the Fallbacks namespace for the null defaults (DependencyInjection.cs:4) and the Browser namespace for the JS-interop implementations (:3); Microsoft.Extensions.DependencyInjection and its Extensions namespace for the TryAdd* helpers (:1-2).
        • -
        • Concept introduced: two-phase, last-registration-wins capability selection. This is the mechanism every other section in this chapter refers back to, so read the two phases as one story. Phase one: AddUIShared calls AddDeviceCapabilityDefaults() (MMCA.Common.UI/DependencyInjection.cs:162, under a comment stating the rule at :137-138), which TryAdd-registers a null or neutral implementation for every contract (DependencyInjection.cs:40-82). TryAdd is what makes this idempotent: a host that calls AddUIShared twice does not double-register, and an app that pre-registered its own implementation before AddUIShared keeps it (DependencyInjection.cs:16-20). Phase two: the head calls its own override helper after AddUIShared, using a plain Add rather than TryAdd, and because .NET DI resolves the last registration for a single-service request, the real implementation wins (DependencyInjection.cs:18-20). Browser overrides live in this same file; native overrides ship separately in the MMCA.Common.UI.Maui package as AddMauiDeviceCapabilities (DependencyInjection.cs:20-21), which is what keeps MMCA.Common.UI free of any MAUI reference.
            +
          • Concept introduced: two-phase, last-registration-wins capability selection. This is the mechanism every other section in this chapter refers back to, so read the two phases as one story. Phase one: AddUIShared calls AddDeviceCapabilityDefaults() (MMCA.Common.UI/DependencyInjection.cs:178, under a comment stating the rule at :137-138), which TryAdd-registers a null or neutral implementation for every contract (DependencyInjection.cs:40-82). TryAdd is what makes this idempotent: a host that calls AddUIShared twice does not double-register, and an app that pre-registered its own implementation before AddUIShared keeps it (DependencyInjection.cs:16-20). Phase two: the head calls its own override helper after AddUIShared, using a plain Add rather than TryAdd, and because .NET DI resolves the last registration for a single-service request, the real implementation wins (DependencyInjection.cs:18-20). Browser overrides live in this same file; native overrides ship separately in the MMCA.Common.UI.Maui package as AddMauiDeviceCapabilities (DependencyInjection.cs:20-21), which is what keeps MMCA.Common.UI free of any MAUI reference.
            • [Rubric §1, SOLID] §1 assesses SOLID adherence. Open/Closed shows up concretely: adding a fourth head means adding a new AddXDeviceCapabilities() helper, not editing shared components or this defaults table.
            • [Rubric §2, Design Patterns] §2 assesses whether classic patterns earn their keep. This is Strategy selected by the container, with Null Object as the default strategy (see NullGeocodingService for the null-object shape taught in full).
            • [Rubric §22, Responsive / Cross-Browser] §22 assesses graceful behavior across heads. The point of the defaults table is that no shared component can ever fail to resolve a capability, whichever head it renders in, including during prerender before JS exists.
            • @@ -2931,7 +2941,7 @@

              DependencyInjection

          • Why it's built this way: ADR-042 is cited on the class itself (DependencyInjection.cs:16), with ADR-044 and ADR-045 cited inline for the push and media groups. The TryAdd-then-Add ordering is what lets one shared component tree run on three heads with zero host-detection code, and the scoped-versus-singleton split is driven by one question asked per contract: does this hold per-user state on a Blazor Server circuit?
          • -
          • Where it's used: called by AddUIShared (MMCA.Common.UI/DependencyInjection.cs:162). The public browser helper is called by both web heads in each app: ADC at MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:90 and MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Program.cs:43, Store at MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:99 and MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web.Client/Program.cs:39.
          • +
          • Where it's used: called by AddUIShared (MMCA.Common.UI/DependencyInjection.cs:178). The public browser helper is called by both web heads in each app: ADC at MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:91 and MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web.Client/Program.cs:43, Store at MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:99 and MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web.Client/Program.cs:39.
          • Caveats / not-in-source: the MAUI-side helper (AddMauiDeviceCapabilities) is not in this file; it ships in the MMCA.Common.UI.Maui package, which is deliberately outside MMCA.Common.slnx and built by dedicated windows jobs (ADR-042).

          diff --git a/docs/onboarding/group-27-common-ai-integration.html b/docs/onboarding/group-27-common-ai-integration.html index 75753664..81a3d221 100644 --- a/docs/onboarding/group-27-common-ai-integration.html +++ b/docs/onboarding/group-27-common-ai-integration.html @@ -209,7 +209,7 @@

          The configuration surface is rest of the framework follows (ADR-070). [Rubric section 11, Security] is in play at ApiKey: the property is documented as binding from Key - Vault in production and from user secrets locally (AiSettings.cs:62-67), so the package itself never + Vault in production and from user secrets locally (AiSettings.cs:69-74), so the package itself never decides where the secret comes from.

          The provider is a registered factory, not a type the package names

          IAiProviderFactory @@ -326,7 +326,7 @@

          The outer layer: what a ca tool marked consequential must also be confirmed by the caller for this request (:259-262), and the confirmation never overrides a policy that denied it (:256-258). Both markers are plain string keys on the property bags Microsoft.Extensions.AI already carries, published by the static - ChatToolPolicy (ChatToolPolicy.cs:23): mmca.tool.consequential on the tool + ChatToolPolicy (ChatToolPolicy.cs:24): mmca.tool.consequential on the tool (:29) and mmca.tool.confirmed on the options (:40), so any tool factory and any caller can participate without referencing this package (:10-14). The distinction they encode is reading versus writing: a lookup can be offered on a policy decided once, while a tool that sends, moves money @@ -353,7 +353,7 @@

          The outer layer: what a ca

          The middle layer: what the call may say, and what it may answer

          Bounds are configuration, so the framework can decide them. Content is not, so it does not. IChatGuardrail - (MMCA.Common/Source/Core/MMCA.Common.AI/Chat/IChatGuardrail.cs:20) is the extension point an + (MMCA.Common/Source/Core/MMCA.Common.AI/Chat/IChatGuardrail.cs:21) is the extension point an application implements to inspect an outgoing request (InspectRequestAsync, :27-30), a completed response (InspectResponseAsync, :37-40) and, through a default-interface member that allows unless overridden, each streamed update (InspectStreamedUpdateAsync, :56-59), and the @@ -533,14 +533,14 @@

          The prompt is a versioned artifactWhere it runs today, and how it is tested

          Exactly one feature in the workspace calls a model: ADC's organizer-facing session scoring. The Conference service host reads one secret, Ai:ApiKey, and derives Ai:Enabled from its presence - (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:129-134); the infrastructure + (MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs:131-136); the infrastructure template writes the Key Vault secret into the container app under that same key and local - development keeps it in user secrets (Program.cs:123-125), so a host with no key starts with + development keeps it in user secrets (Program.cs:125-127), so a host with no key starts with scoring unavailable rather than failing validation. It then registers the Anthropic adapter with - AddAnthropicAiProvider() (Program.cs:139), its guardrails with AddConferenceAiGuardrails - (Program.cs:150) and the governed client with AddMmcaChatClient(builder.Configuration) - (Program.cs:152), in that order because the last call reads the guardrail descriptors, and - subscribes the framework meter and activity source by literal name (Program.cs:171-172). + AddAnthropicAiProvider() (Program.cs:141), its guardrails with AddConferenceAiGuardrails + (Program.cs:152) and the governed client with AddMmcaChatClient(builder.Configuration) + (Program.cs:154), in that order because the last call reads the guardrail descriptors, and + subscribes the framework meter and activity source by literal name (Program.cs:173-174). AddConferenceAiGuardrails (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Infrastructure/DependencyInjection.cs:85) composes both framework policies, AddPiiRedactionGuardrail() and AddContentPolicyGuardrail @@ -599,55 +599,66 @@

          AiSettings

        • What it is: the bound, validated Ai configuration section (SectionName = "Ai", - AiSettings.cs:217) that gates and configures the entire AI integration: whether it is on at all - (Enabled, AiSettings.cs:231), which provider and model to call - (Provider/Model, AiSettings.cs:234,242), the API key, an optional endpoint override, the - output-token ceiling, the per-call timeout, and opt-ins (AllowTools, RequireGuardrail, - EnableCache) plus an optional input-token pre-flight budget.
        • + AiSettings.cs:25) that gates and configures the entire AI integration: whether it is on at all + (Enabled, AiSettings.cs:46), which provider and model to call + (Provider/Model, AiSettings.cs:55,65), the API key, an optional endpoint override, the + output-token ceiling, the per-call timeout (capped by MaxTimeout), and opt-ins (AllowTools, + RequireGuardrail, EnableCache) plus an optional input-token pre-flight budget.
        • Depends on: System.ComponentModel.DataAnnotations (IValidatableObject, [Range]); no - first-party dependency of its own, though its doc comments name AddCommonKeyVaultConfiguration and - Persistence:EnableSensitiveDataLogging as sibling conventions elsewhere in Common.
        • + first-party dependency of its own, though its doc comments name AddCommonKeyVaultConfiguration as + the sibling convention elsewhere in Common that supplies ApiKey in production (AiSettings.cs:67-76).
        • Concept introduced, an off-by-default dependency with conditional validation. [Rubric §16, AI-Native Application Architecture] (assesses whether an LLM dependency is safe to ship in every environment): Enabled defaults to false, and when it is false AddMmcaChatClient registers no IChatClient at all, so resolving one yields null and a consumer gates on the service - being present rather than reading a flag itself (AiSettings.cs:225-231). Provider is no longer a + being present rather than reading a flag itself (AiSettings.cs:40-46). Provider is a string, not a closed enum: it is matched case-insensitively against the registered IAiProviderFactory.Name of whatever provider packages the host referenced (MMCA.Common.AI.Anthropic and MMCA.Common.AI.OpenAI each ship one), so the provider switch lives in the host's package references and its configuration value, not in this framework's - source (AiSettings.cs:194-201). [Rubric §11, Security] (assesses secret handling): ApiKey is - required only when Enabled, and its doc comment records that production binds it from Key Vault via - AddCommonKeyVaultConfiguration, never from a checked-in settings file (AiSettings.cs:214-223).
        • -
        • Walkthrough: two constants, DefaultMaxOutputTokens = 1024 (AiSettings.cs:220) and - DefaultTimeout = TimeSpan.FromSeconds(30) (AiSettings.cs:223), back the two settings that ship with - a value even when the section is silent. Provider (AiSettings.cs:202) is required when Enabled - and names a registered provider by string, e.g. Anthropic or OpenAI; an unknown name fails at - startup naming the registered ones. Model is required when Enabled because it is part of the - prompt contract, hashed into PromptContract.Hash, so an implicit provider default would silently - change evaluated behavior on the provider's own schedule rather than on a reviewed version bump, and - it is also a bound: BoundedChatClient refuses a request that names a different - model (AiSettings.cs:204-211). Endpoint (AiSettings.cs:225-230) is an optional absolute URI to - route through an AI gateway, a regional endpoint or an OpenAI-compatible server; each adapter passes it - to its SDK's base-address option. MaxOutputTokens ([Range(1, 1_000_000)], AiSettings.cs:236) is - the hard ceiling BoundedChatClient.Bound clamps every call down to. Timeout (AiSettings.cs:243) - is enforced with a token linked to the caller's own, so a caller cancelling early still wins. - RequireGuardrail (AiSettings.cs:252-264) defaults to true: an enabled host that registers no - guardrail and no redactor fails at startup rather than shipping an uninspected model call; a host that - deliberately wants none sets it false, which is a reviewable line rather than an absence nobody can - see. PerCallInputTokenBudget ([Range(1, int.MaxValue)], AiSettings.cs:277-278) is null by - default, leaving input unbounded, and is an ESTIMATE never a billing figure. Validate - (AiSettings.cs:287-330) is the conditional half: it yield breaks immediately when !Enabled - (AiSettings.cs:289-292), so a host that leaves AI off is valid with nothing else set, which is what + source (AiSettings.cs:15-19, AiSettings.cs:48-55). [Rubric §11, Security] (assesses secret + handling): ApiKey is required only when Enabled, and its doc comment records that production binds + it from Key Vault via AddCommonKeyVaultConfiguration, never from a checked-in settings file + (AiSettings.cs:67-76).
        • +
        • Walkthrough: two defaults, DefaultMaxOutputTokens = 1024 (AiSettings.cs:28) and + DefaultTimeout = TimeSpan.FromSeconds(30) (AiSettings.cs:31), back the two settings that ship with + a value even when the section is silent; a third static, MaxTimeout = TimeSpan.FromHours(1) + (AiSettings.cs:33-38), is the largest per-call timeout validation accepts. Provider + (AiSettings.cs:55) is required when Enabled and names a registered provider by string, e.g. + Anthropic or OpenAI; an unknown name fails at startup naming the registered ones. Model + (AiSettings.cs:57-65) is required when Enabled because it is part of the prompt contract, hashed + into PromptContract.Hash, so an implicit provider default would silently change evaluated behavior on + the provider's own schedule rather than on a reviewed version bump, and it is also a bound: + BoundedChatClient refuses a request that names a different model. + Endpoint (AiSettings.cs:78-83) is an optional absolute URI to route through an AI gateway, a + regional endpoint or an OpenAI-compatible server; each adapter passes it to its SDK's base-address + option. MaxOutputTokens ([Range(1, 1_000_000)], AiSettings.cs:89-90) is the hard ceiling + BoundedChatClient.Bound clamps every call down to. Timeout (AiSettings.cs:92-96) is enforced with + a token linked to the caller's own, so a caller cancelling early still wins. AllowTools + (AiSettings.cs:98-103) is false by default, and while it is false any tools on a request are + stripped before the call. RequireGuardrail (AiSettings.cs:105-117) defaults to true: an enabled + host that registers no guardrail fails at startup rather than shipping an uninspected model call, and + the message names the one-line fix (AddPiiRedactionGuardrail()); a host that deliberately wants none + sets it false, which is a reviewable line rather than an absence nobody can see. EnableCache + (AiSettings.cs:119-123) is off by default and stays off when no IDistributedCache is registered. + PerCallInputTokenBudget ([Range(1, int.MaxValue)], AiSettings.cs:130-131) is null by default, + leaving input unbounded, and is an ESTIMATE never a billing figure. Validate + (AiSettings.cs:140-190) is the conditional half: it yield breaks immediately when !Enabled + (AiSettings.cs:142-145), so a host that leaves AI off is valid with nothing else set, which is what lets the section ship in every appsettings file; when enabled it requires Provider, Model, - ApiKey, an absolute Endpoint when one is set, and a positive Timeout - (AiSettings.cs:294-329).
        • + ApiKey, an absolute Endpoint when one is set, a positive Timeout, and a Timeout no greater than + MaxTimeout (AiSettings.cs:147-189). That last check exists because a bare number in configuration + binds as DAYS, and a value above roughly 49.7 days makes every call throw where the timeout is armed; + the one-hour cap rejects every bare-number value while leaving any plausible per-call budget alone, + and its message points at the 00:00:30 form (AiSettings.cs:184-189).
        • Why it's built this way: IValidatableObject.Validate runs through .ValidateDataAnnotations().ValidateOnStart() in AiServiceCollectionExtensions.AddMmcaChatClient (MMCA.Common/Source/Core/MMCA.Common.AI/DependencyInjection.cs:123-126), so a misconfigured Ai - section fails at host startup, not on the first call. Provider became a string, not an enum, so a new - vendor is a new adapter package registering a factory, never a change to this framework's source. See - ADR-120.
        • + section, including a Timeout mistyped as a bare number, fails at host startup, not on the first call. + Provider is a string, not an enum, so a new vendor is a new adapter package registering a factory, + never a change to this framework's source. See + ADR-120 and + ADR-070.
        • Where it's used: bound and consumed by AiServiceCollectionExtensions, enforced by BoundedChatClient, and its Provider value is threaded into UsageRecordingChatClient as the fallback tag when the resolved client @@ -666,10 +677,10 @@

          GuardrailVerdict

        • Concept introduced, a closed allow/block gate. [Rubric §11, Security] (assesses whether content passing through an AI dependency is inspected and the inspection outcome cannot be misconstructed): the constructor is private, so the only way to produce a blocking verdict is Block(string reason), which - throws on a null or whitespace reason (GuardrailVerdict.cs:51-56); a caller cannot construct a blocked + throws on a null or whitespace reason (GuardrailVerdict.cs:39-44); a caller cannot construct a blocked verdict that silently carries no explanation.
        • Walkthrough: Allow (GuardrailVerdict.cs:37) is a static property returning - new(isAllowed: true, reason: null). Block(reason) (GuardrailVerdict.cs:51-56) validates the reason + new(isAllowed: true, reason: null). Block(reason) (GuardrailVerdict.cs:39-44) validates the reason with ArgumentException.ThrowIfNullOrWhiteSpace before constructing isAllowed: false. The constant UnspecifiedReason (GuardrailVerdict.cs:28) exists for the struct's own default value, whose Reason is null because it went through neither factory.
        • @@ -689,7 +700,7 @@

          IAiTokenEstimator

    • What it is: a one-method extension point for estimating how many input tokens a piece of prompt - text will cost: int EstimateTokenCount(string text) (IAiTokenEstimator.cs:57).
    • + text will cost: int EstimateTokenCount(string text) (IAiTokenEstimator.cs:19).
    • Depends on: nothing first-party beyond the caller that resolves it.
    • Concept introduced, a pluggable estimator behind a cheap built-in default. [Rubric §1, SOLID] (assesses whether a dependency is inverted behind an abstraction rather than @@ -699,7 +710,7 @@

      IAiTokenEstimator

      (BoundedChatClient.EstimateInputTokens, BoundedChatClient.cs:179), so a host that wants provider-accurate tokenization can register a real tokenizer without changing the bounding logic.
    • Walkthrough: the interface has no other members; the contract is the single method plus its two - doc-comment lines describing the parameter and return value (IAiTokenEstimator.cs:54-56).
    • + doc-comment lines describing the parameter and return value (IAiTokenEstimator.cs:17-18).
    • Why it's built this way: BoundedChatClient.EstimateInputTokens is public static precisely so a caller (or the estimator implementation itself) can reproduce the same estimate the budget check uses (BoundedChatClient.cs:155-179), and it deliberately rounds up with a cheap four-characters-per-token @@ -720,14 +731,14 @@

      PromptContract

      • What it is: an immutable, hashable identity for one prompt: Name, Version, Model, SystemPrompt (PromptContract.cs:78). It both builds the ChatOptions a call needs - (ToChatOptions, PromptContract.cs:108-109) and stamps its own identity onto telemetry so every + (ToChatOptions, PromptContract.cs:53-54) and stamps its own identity onto telemetry so every call can be traced back to the exact prompt that produced it.
      • Depends on: ChatOptions (Microsoft.Extensions.AI), System.Security.Cryptography.SHA256, and System.Text.Encoding.
      • Concept introduced, prompt-as-versioned-contract. [Rubric §16, AI-Native Application Architecture] (assesses whether prompts are governed, versioned artifacts rather than inline strings): the record's Hash property (PromptContract.cs:101) is a lowercase hex SHA-256 of Name|Version|Model|SystemPrompt, with every component's line endings normalized to LF first - (NormalizeLineEndings, PromptContract.cs:157-158) so the same prompt checked out on Windows and on + (NormalizeLineEndings, PromptContract.cs:102-103) so the same prompt checked out on Windows and on Linux hashes identically and a CI gate cannot be tripped by core.autocrlf (PromptContract.cs:90-93). The three identity values are stamped onto ChatOptions.AdditionalProperties under fixed keys (NamePropertyKey/VersionPropertyKey/HashPropertyKey, PromptContract.cs:81-87), so a request @@ -735,12 +746,12 @@

        PromptContract

      • Walkthrough: Hash (PromptContract.cs:101) is computed on every read, deliberately not cached in a field, because a record's generated copy constructor copies fields verbatim and a cached hash would survive a with expression describing the prompt the copy was made FROM (PromptContract.cs:96-100, - exactly the drift the type exists to prevent). ToChatOptions (PromptContract.cs:108-109) builds a + exactly the drift the type exists to prevent). ToChatOptions (PromptContract.cs:53-54) builds a fresh ChatOptions with ModelId and Instructions set, then delegates to Apply. Apply - (PromptContract.cs:117-127) stamps Name, Version, and Hash onto an existing ChatOptions in + (PromptContract.cs:62-72) stamps Name, Version, and Hash onto an existing ChatOptions in place and returns it for chaining, for a caller that already built options and needs its own temperature or response format alongside the prompt identity. ReadName/ReadVersion - (PromptContract.cs:132,137) and the private ReadProperty (PromptContract.cs:139-143) reverse the + (PromptContract.cs:77,82) and the private ReadProperty (PromptContract.cs:84-88) reverse the stamp, reading back whatever Apply wrote (or null when nothing was stamped).
      • Why it's built this way: hashing four short strings on every read is cheap enough that correctness wins outright over caching (PromptContract.cs:96-100). See @@ -780,13 +791,13 @@

        ChatGuardrailException


        IChatGuardrail

        -

        MMCA.Common.AI.Chat · MMCA.Common.AI.Chat · MMCA.Common/Source/Core/MMCA.Common.AI/Chat/IChatGuardrail.cs:20 · Level 1 · interface

        +

        MMCA.Common.AI.Chat · MMCA.Common.AI.Chat · MMCA.Common/Source/Core/MMCA.Common.AI/Chat/IChatGuardrail.cs:21 · Level 1 · interface

        • What it is: the extension point a host implements to inspect chat traffic: InspectRequestAsync before the model is called, InspectResponseAsync after a completed response, and InspectStreamedUpdateAsync for one streamed update, each returning a - GuardrailVerdict (IChatGuardrail.cs:27-30,37-40,56-59). The streamed member + GuardrailVerdict (IChatGuardrail.cs:28-31,38-41,57-60). The streamed member is a default interface member that allows by default, so a guardrail written before it existed keeps compiling and keeps its streaming behavior.
        • Depends on: GuardrailVerdict, @@ -798,12 +809,12 @@

          IChatGuardrail

          registers neither a guardrail nor a redactor pays no cost, see AiServiceCollectionExtensions's descriptor check, and an enabled host with RequireGuardrail true (the default) fails at startup instead.
        • -
        • Walkthrough: InspectRequestAsync (IChatGuardrail.cs:27-30) takes the materialized (and, if any +
        • Walkthrough: InspectRequestAsync (IChatGuardrail.cs:28-31) takes the materialized (and, if any redactor ran, already-redacted) message list and the call's options, returning a verdict; a block here - stops the call before the provider is reached. InspectResponseAsync (IChatGuardrail.cs:37-40) takes + stops the call before the provider is reached. InspectResponseAsync (IChatGuardrail.cs:38-41) takes the completed ChatResponse and the same options; a block here stops the response from reaching the caller after the provider call already ran. InspectStreamedUpdateAsync - (IChatGuardrail.cs:56-59) takes one ChatResponseUpdate fragment; a rule that needs the whole answer + (IChatGuardrail.cs:57-60) takes one ChatResponseUpdate fragment; a rule that needs the whole answer has to accumulate it itself, and a block ends the stream from that update on, though the caller has already seen every update yielded before it.
        • Why it's built this way: separate inspection points let an implementation block on @@ -1153,26 +1164,33 @@

          AiUsageMeter


          ChatToolPolicy

          -

          MMCA.Common.AI.Guardrails · MMCA.Common.AI.Guardrails · MMCA.Common/Source/Core/MMCA.Common.AI/Guardrails/ChatToolPolicy.cs:23 · Level 0 · class

          +

          MMCA.Common.AI.Guardrails · MMCA.Common.AI.Guardrails · MMCA.Common/Source/Core/MMCA.Common.AI/Guardrails/ChatToolPolicy.cs:24 · Level 0 · class

          • What it is: a static helper reading two AdditionalProperties conventions the tool-calling boundary shares: whether a tool declares itself consequential, and which tool names the caller confirmed for the current request.
          • -
          • Depends on: Microsoft.Extensions.AI (AITool, ChatOptions).
          • +
          • Depends on: Microsoft.Extensions.AI (AITool, ChatOptions), System.Text.Json + (JsonElement).
          • Concept introduced, per-request confirmation via AdditionalProperties. [Rubric §16, AI-Native Application Architecture] (assesses tool-calling safety patterns): a confirmation is carried as a key in ChatOptions.AdditionalProperties, never as a typed property, so it composes with any ChatOptions without a wrapper type. [Rubric §11, Security]: the confirmation is scoped to ONE - request by design (ChatToolPolicy.cs:33 remark), because a confirmation that outlived the request - it was given for would be a standing grant, exactly what a confirmation step exists to avoid.
          • -
          • Walkthrough: ConsequentialPropertyKey = "mmca.tool.consequential" (ChatToolPolicy.cs:26) + request by design (ChatToolPolicy.cs:37-40 remark), because a confirmation that outlived the + request it was given for would be a standing grant, exactly what a confirmation step exists to avoid. + The consequential marker, by contrast, fails closed: it is the whole write-safety gate, so only a + definite false reads as harmless (ChatToolPolicy.cs:56-59).
          • +
          • Walkthrough: ConsequentialPropertyKey = "mmca.tool.consequential" (ChatToolPolicy.cs:30) marks an AITool as doing something that cannot be undone by not reading the answer. - ConfirmedToolsPropertyKey = "mmca.tool.confirmed" (ChatToolPolicy.cs:31) holds the confirmed - names for the current request. IsConsequential (ChatToolPolicy.cs:40-55) reads the tool's - AdditionalProperties: a missing key is false; a bool value is used as-is; a string value is - parsed with bool.TryParse (configuration and JSON both hand a boolean over as text, and a tool - declared consequential in a settings file must not read as harmless because of that); anything else - is false. ReadConfirmedTools (ChatToolPolicy.cs:58-78) reads the confirmed names off + ConfirmedToolsPropertyKey = "mmca.tool.confirmed" (ChatToolPolicy.cs:41) holds the confirmed + names for the current request. IsConsequential (ChatToolPolicy.cs:49-68) reads the tool's + AdditionalProperties: a missing key is false; a bool value is used as-is; a string value + goes through the private ParsedOrConsequential (ChatToolPolicy.cs:70-71), which returns false + only for text that parses as false and true for anything else, unparseable text included + (configuration hands a boolean over as text, and a tool declared consequential in a settings file + must not read as harmless because of that); a JsonElement of kind False is false, a + JsonElement of kind String goes through the same ParsedOrConsequential (a JSON-bound bag + hands values over as JsonElement); and a marker present in any other shape reads as true + (ChatToolPolicy.cs:60-67). ReadConfirmedTools (ChatToolPolicy.cs:76-97) reads the confirmed names off ChatOptions.AdditionalProperties, accepting a single string, an IEnumerable<string>, or a non-generic IEnumerable (a JSON array bound as object[]), converting each element with Convert.ToString and dropping empties; anything else returns none.
          • @@ -1279,17 +1297,21 @@

            ReplayChatClient

            (assesses whether behavior that depends on an external, non-deterministic dependency can be exercised deterministically): rather than calling a live model, a test replays a recorded transcript, so the assertion exercises the consumer's parsing and handling code without network variance. -
          • Walkthrough: two constructors (ReplayChatClient.cs:22-27,29-38), one for a single response, one +
          • Walkthrough: two constructors (ReplayChatClient.cs:26-29,37-47), one for a single response, one for an ordered list (rejecting an empty list, since a replay client needs at least one recorded - answer). LastOptions, LastMessages, CallCount (ReplayChatClient.cs:41-49) record what the + answer). LastOptions, LastMessages, CallCount (ReplayChatClient.cs:50-56) record what the client was last called with, for assertions. GetResponseAsync and GetStreamingResponseAsync - (ReplayChatClient.cs:57-86) both funnel through the private Record, with the streaming path + (ReplayChatClient.cs:65-94) both funnel through the private Record, with the streaming path projecting the SAME recorded response onto the streaming shape via the abstraction's own ToChatResponseUpdates() conversion, so a test asserting the streamed answer exercises the same corpus as the buffered path rather than a second, hand-built approximation of it. Record - (ReplayChatClient.cs:98-109) advances through the recorded list and repeats the LAST response once + (ReplayChatClient.cs:104-122) advances through the recorded list and repeats the LAST response once exhausted, so a test calling once more than it recorded gets a stable answer rather than an exception - from the harness pretending to be a failure in the code under test. Dispose is a no-op: the replay + from the harness pretending to be a failure in the code under test. It snapshots the messages + outside the lock, then takes one Lock (_sync, ReplayChatClient.cs:20) over the index read, the + CallCount increment and the LastMessages/LastOptions writes (ReplayChatClient.cs:112-121), + so concurrent calls neither lose a count nor pair one call's messages with another call's options. + Dispose is a no-op: the replay client holds no transport. GetService returns itself only when asked for its own type.
          • Where it's used: GoldenReplayTestsBase.RunCaseAsync constructs one per case; ReplayChatClientTests, ADC's GoldenReplayTests, and ReferenceGoldenReplayTests.
          • @@ -1413,28 +1435,28 @@

            ContentPolicyGuardrail

            (InspectStreamedUpdateAsync) checks each fragment rather than the whole answer, so a pattern that spans two streamed updates is missed by design; a host that needs whole-answer certainty uses the buffered path. [Rubric §16, AI-Native Application Architecture]. -
          • Walkthrough: ConfiguredPatternOptions (ContentPolicyGuardrail.cs:919, +
          • Walkthrough: ConfiguredPatternOptions (ContentPolicyGuardrail.cs:67, IgnoreCase | CultureInvariant) is what every CONFIGURED pattern compiles with; MatchTimeout - (ContentPolicyGuardrail.cs:922, 1 second) bounds a single match so a pathological pattern cannot + (ContentPolicyGuardrail.cs:70, 1 second) bounds a single match so a pathological pattern cannot hang a request; BuiltInPatternOptions adds ExplicitCapture on the BUILT-INS ONLY - (ContentPolicyGuardrail.cs:924-927), because turning that off under a host would silently change + (ContentPolicyGuardrail.cs:72-75), because turning that off under a host would silently change what a configured pattern's own backreferences mean. BuiltInMarkers - (ContentPolicyGuardrail.cs:929-939) pairs eight labeled [GeneratedRegex] patterns + (ContentPolicyGuardrail.cs:77-87) pairs eight labeled [GeneratedRegex] patterns (ignore-previous-instructions, disregard-system-prompt, role-reassignment, new-instructions, reveal-system-prompt, act-as-unrestricted, developer-mode, do-anything-now), each with a 1-second - generated-regex timeout. The constructor (ContentPolicyGuardrail.cs:952-959) compiles every + generated-regex timeout. The constructor (ContentPolicyGuardrail.cs:100-107) compiles every configured pattern ONCE, at resolve time; a pattern that does not compile has already failed ContentPolicySettings.Validate at startup, so this constructor is not where a typo is discovered. - Redact (ContentPolicyGuardrail.cs:967-987) rewrites only when InjectionMode is Redact: Block + Redact (ContentPolicyGuardrail.cs:115-135) rewrites only when InjectionMode is Redact: Block mode must inspect what the caller supplied, so rewriting there would hide the marker from the check that is supposed to refuse it; Off mode does neither. InspectRequestAsync - (ContentPolicyGuardrail.cs:995-1013) allows in every mode except Block, because in Redact mode + (ContentPolicyGuardrail.cs:143-161) allows in every mode except Block, because in Redact mode the redactor has already run and there is nothing left to refuse. InspectResponseAsync/ - InspectStreamedUpdateAsync (ContentPolicyGuardrail.cs:1016-1041) both funnel through + InspectStreamedUpdateAsync (ContentPolicyGuardrail.cs:164-189) both funnel through InspectAnswerText, which matches against BlockedResponsePatterns and returns a block reason naming the pattern's INDEX, never the matched text: the reason reaches the caller, and the point of the rule was that the text should not. RedactMessage/RedactText - (ContentPolicyGuardrail.cs:1180-1215) rewrite only TextContent, passing images, function calls, + (ContentPolicyGuardrail.cs:328-363) rewrite only TextContent, passing images, function calls, and provider-specific content through untouched: a guardrail that silently dropped content it does not understand would be a far worse failure than leaving it alone.
          • Why it's built this way: matches the ADR-120 governed pipeline's split between redaction (silent, @@ -1463,15 +1485,15 @@

            ContentPolicySettings

            pattern, not the first user who trips it, is the one who sees it.
          • Walkthrough: SectionName = "Ai:ContentPolicy" (ContentPolicySettings.cs:26); DefaultRedactionPlaceholder = "[redacted-instruction]" (ContentPolicySettings.cs:29), a VISIBLE - placeholder on purpose (ContentPolicySettings.cs:1276-1280 remark) so the model can tell something + placeholder on purpose (ContentPolicySettings.cs:63-67 remark) so the model can tell something was removed and a trace reviewer can tell a redaction from a sentence the user never wrote. - InjectionMode defaults Redact (ContentPolicySettings.cs:1248). - AdditionalRequestPatterns/BlockedResponsePatterns (ContentPolicySettings.cs:1258,1270) default + InjectionMode defaults Redact (ContentPolicySettings.cs:35). + AdditionalRequestPatterns/BlockedResponsePatterns (ContentPolicySettings.cs:45,57) default to empty; an empty BlockedResponsePatterns switches the response half of the policy off entirely, because a model that has already said the thing cannot unsay it, so the only available response-side - answer is a refusal. RedactionPlaceholder is [Required] (ContentPolicySettings.cs:1281). - Validate (ContentPolicySettings.cs:1295-1306) runs ValidatePatterns over both lists, which calls - DescribeFailure (ContentPolicySettings.cs:1329-1345) per pattern: an empty-or-whitespace pattern + answer is a refusal. RedactionPlaceholder is [Required] (ContentPolicySettings.cs:68). + Validate (ContentPolicySettings.cs:82-93) runs ValidatePatterns over both lists, which calls + DescribeFailure (ContentPolicySettings.cs:116-132) per pattern: an empty-or-whitespace pattern is rejected outright (it would match everything, "never what a policy meant"), otherwise the pattern is compiled with ContentPolicyGuardrail.ConfiguredPatternOptions/MatchTimeout and any ArgumentException message is surfaced against SectionName:memberName[index].
          • @@ -1484,29 +1506,36 @@

            ContentPolicySettings


            PiiRedactionGuardrail

            -

            MMCA.Common.AI.Guardrails · MMCA.Common.AI.Guardrails · MMCA.Common/Source/Core/MMCA.Common.AI/Guardrails/PiiRedactionGuardrail.cs:39 · Level 2 · class

            +

            MMCA.Common.AI.Guardrails · MMCA.Common.AI.Guardrails · MMCA.Common/Source/Core/MMCA.Common.AI/Guardrails/PiiRedactionGuardrail.cs:39 · Level 3 · class

            • What it is: the sealed partial class implementing IChatRequestRedactor and IChatGuardrail that strips emails and phone - numbers from outgoing message text. The guardrail half is a pass-through (always Allow): this type + numbers from the text carried by outgoing messages, tool calls and tool results included. The guardrail half is a pass-through (always Allow): this type only redacts, it never blocks.
            • Depends on: IChatRequestRedactor/IChatGuardrail/GuardrailVerdict, System.Text.RegularExpressions.
            • Concept introduced, configuration-free, unconditional redaction. Contrasts with ContentPolicyGuardrail: PiiRedactionGuardrail has no configurable options and applies to every outgoing message unconditionally.
            • -
            • Walkthrough: EmailPattern/PhonePattern (PiiRedactionGuardrail.cs:1397-1407) are +
            • Walkthrough: EmailPattern/PhonePattern (PiiRedactionGuardrail.cs:70-80) are [GeneratedRegex] source-generated matchers with a 1-second timeout (email: a standard local-part@domain shape; phone: an optional leading country code plus a 10-digit US-shaped number, guarded by negative lookaround so it does not clip a longer digit run). Redact - (PiiRedactionGuardrail.cs:1372-1383) rewrites EVERY message, not only ChatRole.User (unlike + (PiiRedactionGuardrail.cs:45-56) rewrites EVERY message, not only ChatRole.User (unlike ContentPolicyGuardrail), via the private RedactMessage - (PiiRedactionGuardrail.cs:1409-1435), which replaces email matches then phone matches in - TextContent only, passing other content types through untouched for the same reason - ContentPolicyGuardrail does (a guardrail that silently dropped content it does not understand would - be worse than leaving it alone). InspectRequestAsync/InspectResponseAsync - (PiiRedactionGuardrail.cs:1386-1395) both unconditionally return GuardrailVerdict.Allow.
            • + (PiiRedactionGuardrail.cs:82-98), which rebuilds each message (keeping role, AuthorName, + MessageId and AdditionalProperties) and hands every content item to RedactContent + (PiiRedactionGuardrail.cs:105-123). That switch rewrites non-empty TextContent and + TextReasoningContent, a FunctionResultContent whose result is a string, and the string + argument values of a FunctionCallContent (other argument values kept as-is), because a tool's + output is exactly where contact details turn up (a customer lookup, a directory search). Each + rewrite goes through RedactText (PiiRedactionGuardrail.cs:125-126), which replaces email + matches then phone matches. Binary and other non-text content (images, non-string tool results, + provider-specific items) passes through untouched for the same reason ContentPolicyGuardrail gives + (a guardrail that silently dropped content it does not understand would be worse than leaving it + alone). InspectRequestAsync/InspectResponseAsync + (PiiRedactionGuardrail.cs:59-68) both unconditionally return GuardrailVerdict.Allow.
            • Why it's built this way: unconditional redaction with zero configuration lets the guardrail satisfy AiSettings.RequireGuardrail with no setup. See ADR-120 and @@ -1535,10 +1564,10 @@

              GuardrailServiceCollectionExtensio a host that later gives the type state would otherwise get two copies of it. Registration uses C# extension members as the idiom, not static extension methods.

            • Walkthrough: AddPiiRedactionGuardrail - (GuardrailServiceCollectionExtensions.cs:1475-1486) registers PiiRedactionGuardrail once via + (GuardrailServiceCollectionExtensions.cs:33-44) registers PiiRedactionGuardrail once via TryAddSingleton, then TryAddEnumerable-registers it under both IChatGuardrail and IChatRequestRedactor. AddContentPolicyGuardrail - (GuardrailServiceCollectionExtensions.cs:1504-1521) additionally binds ContentPolicySettings from + (GuardrailServiceCollectionExtensions.cs:62-79) additionally binds ContentPolicySettings from Ai:ContentPolicy with .ValidateDataAnnotations().ValidateOnStart() (so a bad pattern fails the deployment, not a user's request) before registering ContentPolicyGuardrail the same two-contract way. Both methods' doc remarks state they must be called BEFORE AddMmcaChatClient, which reads the @@ -1556,10 +1585,10 @@

              IAiProviderFactory

              • What it is: the boundary a vendor SDK adapter implements to plug into AddMmcaChatClient's provider selection. Name (IAiProviderFactory.cs:31) is the case-insensitive string a config file's - Ai:Provider selects it by; Create (IAiProviderFactory.cs:39) builds the ungoverned + Ai:Provider selects it by; Create (IAiProviderFactory.cs:34) builds the ungoverned IChatClient for already-validated AiSettings.
              • Depends on: AiSettings (the bound Ai section it receives), Microsoft.Extensions.AI.IChatClient - and IServiceProvider (both external), both parameters of Create (IAiProviderFactory.cs:39).
              • + and IServiceProvider (both external), both parameters of Create (IAiProviderFactory.cs:34).
              • Concept introduced, the provider-factory boundary. [Rubric §1, SOLID] (assesses dependency inversion and substitutability): AddMmcaChatClient and AiProviderValidator depend only on this interface, never on a concrete vendor SDK type, so a new provider (a third LLM vendor) is one new @@ -1568,13 +1597,13 @@

                IAiProviderFactory

                first-class, swappable concern rather than hard-wired code): this interface is the entire swap point, recorded in ADR-120.
              • Walkthrough: two members only, Name (IAiProviderFactory.cs:31, a string getter) and - Create(AiSettings settings, IServiceProvider serviceProvider) (IAiProviderFactory.cs:39, returns - IChatClient). The doc comment on Create (IAiProviderFactory.cs:38) records that ownership of the + Create(AiSettings settings, IServiceProvider serviceProvider) (IAiProviderFactory.cs:34, returns + IChatClient). The doc comment on Create (IAiProviderFactory.cs:33) records that ownership of the returned client passes to the governed pipeline that wraps it, i.e. the factory itself never disposes what it builds.
              • Why it's built this way: serviceProvider is passed alongside settings so an adapter that needs a logger or a named HttpClient factory can resolve one without the interface growing a parameter per - future need (IAiProviderFactory.cs:37). The instances that implement it, AnthropicAiProviderFactory + future need (IAiProviderFactory.cs:32). The instances that implement it, AnthropicAiProviderFactory and OpenAiProviderFactory, are registered as IEnumerable<IAiProviderFactory> so AddMmcaChatClient and AiProviderValidator can enumerate every adapter package the host actually referenced, never a fixed list.
              • Where it's used: DependencyInjection.cs in MMCA.Common.AI resolves the matching factory to @@ -1603,17 +1632,17 @@

                AiProviderValidator

                the chat client.
              • Walkthrough: the constructor captures the injected factories into a fixed array (AiProviderValidator.cs:18, _factories = [.. factories]). Validate (AiProviderValidator.cs:65) - first short-circuits when Enabled is false or Provider is blank (AiProviderValidator.cs:71, + first short-circuits when Enabled is false or Provider is blank (AiProviderValidator.cs:27, deferring to the data-annotation validator so the blank case is reported exactly once, not twice). - Otherwise it calls the static helper Match (AiProviderValidator.cs:85), which does a case-insensitive + Otherwise it calls the static helper Match (AiProviderValidator.cs:41), which does a case-insensitive FirstOrDefault over the factories' Name; a hit returns Success, a miss calls DescribeMismatch - (AiProviderValidator.cs:92) to build the failure text. DescribeMismatch branches on whether any - factory is registered at all (AiProviderValidator.cs:96): zero factories means no adapter package is + (AiProviderValidator.cs:48) to build the failure text. DescribeMismatch branches on whether any + factory is registered at all (AiProviderValidator.cs:52): zero factories means no adapter package is referenced, so the message names both adapter packages and their registration calls (AddAnthropicAiProvider(), AddOpenAiProvider()) plus the manual AddMmcaChatClient overload - (AiProviderValidator.cs:98-101); one or more factories means the name is simply wrong, so the message - lists the registered provider names, sorted case-insensitively (AiProviderValidator.cs:104,106).
              • -
              • Why it's built this way: Match and DescribeMismatch are internal static (AiProviderValidator.cs:85,92) + (AiProviderValidator.cs:54-57); one or more factories means the name is simply wrong, so the message + lists the registered provider names, sorted case-insensitively (AiProviderValidator.cs:60,62).
              • +
              • Why it's built this way: Match and DescribeMismatch are internal static (AiProviderValidator.cs:41,48) so the unit tests can exercise the matching and message-building logic directly without standing up the full options pipeline. The class itself is internal sealed (AiProviderValidator.cs:16): it is wiring, registered by MMCA.Common.AI's own DependencyInjection.cs, not a public extension point.
              • @@ -1630,23 +1659,23 @@

                AnthropicAiProviderFactory

                equals Anthropic (ProviderName, AnthropicAiProviderFactory.cs:16).
              • Depends on: IAiProviderFactory, AiSettings; externally, the Anthropic SDK's AnthropicClient and ClientOptions, and Microsoft.Extensions.AI's AsIChatClient adapter extension - (AnthropicAiProviderFactory.cs:164).
              • + (AnthropicAiProviderFactory.cs:48).
              • Concept: first concrete example of the factory boundary IAiProviderFactory introduces; see that section for the swap-point rationale. Introduces one new idea of its own: AsIChatClient is the SDK's own Microsoft.Extensions.AI adapter, so this factory never hand-rolls the Messages API over - HttpClient (AnthropicAiProviderFactory.cs:140-144).
              • -
              • Walkthrough: Name returns the const string ProviderName = "Anthropic" (AnthropicAiProviderFactory.cs:133,136). - Create (AnthropicAiProviderFactory.cs:149) null-checks settings, builds an SDK ClientOptions - from ApiKey and Timeout (AnthropicAiProviderFactory.cs:153-157), and, when settings.Endpoint - is set, overrides BaseUrl (AnthropicAiProviderFactory.cs:159-162). It then constructs + HttpClient (AnthropicAiProviderFactory.cs:24-25).
              • +
              • Walkthrough: Name returns the const string ProviderName = "Anthropic" (AnthropicAiProviderFactory.cs:17,20). + Create (AnthropicAiProviderFactory.cs:33) null-checks settings, builds an SDK ClientOptions + from ApiKey and Timeout (AnthropicAiProviderFactory.cs:37-41), and, when settings.Endpoint + is set, overrides BaseUrl (AnthropicAiProviderFactory.cs:43-46). It then constructs new AnthropicClient(options).AsIChatClient(settings.Model, settings.MaxOutputTokens) - (AnthropicAiProviderFactory.cs:164), passing the model and output ceiling as the client's defaults.
              • + (AnthropicAiProviderFactory.cs:48), passing the model and output ceiling as the client's defaults.
              • Why it's built this way: a [SuppressMessage("Reliability", "CA2000", ...)] on Create - (AnthropicAiProviderFactory.cs:145-148) documents why the built client is not disposed here: ownership + (AnthropicAiProviderFactory.cs:29-32) documents why the built client is not disposed here: ownership passes to the IChatClient the pipeline wraps (see BoundedChatClient), and the DI container disposes it with the singleton; disposing in the factory would close the transport before the first call. The model and output ceiling passed as SDK defaults are not the last word: BoundedChatClient - still pins and clamps per call (AnthropicAiProviderFactory.cs:141-143), because a default is a + still pins and clamps per call (AnthropicAiProviderFactory.cs:26-27), because a default is a suggestion and a bound is not. Recorded in ADR-120.
              • Where it's used: registered by AnthropicAiServiceCollectionExtensions.AddAnthropicAiProvider(); exercised by MMCA.Common/Tests/Core/MMCA.Common.AI.Tests/Providers/AdapterFactoryTests.cs.
              • @@ -1660,21 +1689,21 @@

                OpenAiProviderFactory

                equals OpenAI (ProviderName, OpenAiProviderFactory.cs:20).
              • Depends on: IAiProviderFactory, AiSettings; externally, OpenAI's OpenAIClient, OpenAIClientOptions, System.ClientModel.ApiKeyCredential, and Microsoft.Extensions.AI's - AsIChatClient (OpenAiProviderFactory.cs:217-219).
              • + AsIChatClient (OpenAiProviderFactory.cs:49-51).
              • Concept: same factory-boundary role as AnthropicAiProviderFactory (see IAiProviderFactory for the shared rationale). Differs in one behavior worth flagging on its own: the OpenAI client binds its - model at construction time, not per call (OpenAiProviderFactory.cs:196-199).
              • -
              • Walkthrough: Name returns the const string ProviderName = "OpenAI" (OpenAiProviderFactory.cs:189,192). - Create (OpenAiProviderFactory.cs:201) null-checks settings, then requires both Model - (OpenAiProviderFactory.cs:205-206) and ApiKey (OpenAiProviderFactory.cs:207-208) with an + model at construction time, not per call (OpenAiProviderFactory.cs:28-31).
              • +
              • Walkthrough: Name returns the const string ProviderName = "OpenAI" (OpenAiProviderFactory.cs:21,24). + Create (OpenAiProviderFactory.cs:33) null-checks settings, then requires both Model + (OpenAiProviderFactory.cs:37-38) and ApiKey (OpenAiProviderFactory.cs:39-40) with an InvalidOperationException naming the missing Ai: setting when either is absent, unlike Anthropic's factory, which tolerates a null model. It builds OpenAIClientOptions from Timeout - (OpenAiProviderFactory.cs:210), applies Endpoint when set (OpenAiProviderFactory.cs:212-215), and + (OpenAiProviderFactory.cs:42), applies Endpoint when set (OpenAiProviderFactory.cs:44-47), and returns new OpenAIClient(new ApiKeyCredential(apiKey), options).GetChatClient(model).AsIChatClient() - (OpenAiProviderFactory.cs:217-219).
              • + (OpenAiProviderFactory.cs:49-51).
              • Why it's built this way: because the SDK binds the model at construction, the pinned model is the only one every request can go to; BoundedChatClient refuses a request naming any other model - (OpenAiProviderFactory.cs:196-199), which keeps a PromptContract's model assertion meaningful here the + (OpenAiProviderFactory.cs:28-31), which keeps a PromptContract's model assertion meaningful here the same way it is on adapters (Anthropic's) that honor a per-request override. The eager InvalidOperationException on a missing model or key fails at first Create call rather than deeper inside the SDK. Recorded in ADR-120.
              • diff --git a/docs/onboarding/group-28-testing-infrastructure.html b/docs/onboarding/group-28-testing-infrastructure.html index 7a5f5d67..e9bb4380 100644 --- a/docs/onboarding/group-28-testing-infrastructure.html +++ b/docs/onboarding/group-28-testing-infrastructure.html @@ -154,7 +154,8 @@

                28. Testing & Quality Infrastruc MMCA.Common/FACTS.md:19-43; the sixth test-support package, MMCA.Common.AI.Testing, belongs to group-27), the architecture-fitness rule library that gates the build, the runtime-conformance bases that gate a - booted host, the backend-less component Gallery harness, the BenchmarkDotNet performance suite, and + booted host, the backend-less component Gallery harness, the BenchmarkDotNet performance suite, the + weekly load tier, and the many per-repo test projects that consume all of it. The distinction to hold onto while reading: most of the types in this group are reusable bases, fixtures, harnesses, and helpers compiled into and shipped by MMCA.Common, while the concrete [Fact]-bearing test classes that subclass them @@ -351,7 +352,7 @@

                Orchestration smoke te ADR-098 deliberately left out.

                AppHostFixtureBase - (MMCA.Common.Testing.Aspire/Fixtures/AppHostFixtureBase.cs:38) is the xUnit collection fixture: it + (MMCA.Common.Testing.Aspire/Fixtures/AppHostFixtureBase.cs:41) is the xUnit collection fixture: it boots a real AppHost once and hands the running DistributedApplication to every test in the collection. Its central distinction is readiness rather than liveness, per resource. "The application started" only means the orchestrator launched the processes, so resources carrying a health check are @@ -359,11 +360,15 @@

                Orchestration smoke te says which of the two happened instead of pretending they are the same (AppHostFixtureBase.cs:22-28). Like the SQL fixtures it records only the first original value of every environment variable it pushes, so a re-pushed key cannot clobber its own restore point and a - collection cannot leak key material into the ones that run after it (AppHostFixtureBase.cs:40-45). + collection cannot leak key material into the ones that run after it (AppHostFixtureBase.cs:43-48). An unmet precondition is a skip, not a failure: the gate sets SkipReason and nothing is started - (AppHostFixtureBase.cs:50-53, IsAvailable at :56), and the consuming project owns the - Assert.SkipWhen(!Fixture.IsAvailable, Fixture.SkipReason!) call, because this package deliberately - takes no dependency on the xUnit assertion library (AppHostFixtureBase.cs:29-36).

                + (AppHostFixtureBase.cs:56, IsAvailable at :59), and the consuming project owns the skip call, + because this package deliberately takes no dependency on the xUnit assertion library + (AppHostFixtureBase.cs:29-34). The call is written as a branch, + if (!Fixture.IsAvailable) { Assert.Skip(Fixture.SkipReason!); }, never as + Assert.SkipWhen(!Fixture.IsAvailable, Fixture.SkipReason!): SkipWhen validates its reason before + the condition, and the reason is null exactly when the stack did start, so that form throws on every + runner able to boot the AppHost (AppHostFixtureBase.cs:34-38).

                What counts as a precondition is declared, not guessed. AppHostEnvironmentRequirement (MMCA.Common.Testing.Aspire/Preconditions/AppHostEnvironmentRequirement.cs:10) is a [Flags] enum @@ -683,7 +688,7 @@

                Architecture f SliceCohesionFitnessTests (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/SliceCohesionFitnessTests.cs:17) points a fixture map at the test assembly itself (FixtureApplicationMap, - SliceCohesionFitnessTests.cs:58-68) and pins the three shapes that matter: a stranded abstract base + SliceCohesionFitnessTests.cs:58-66) and pins the three shapes that matter: a stranded abstract base is flagged (StrandedFixtureHandlerBase, MMCA.Common.Architecture.Tests/SliceFixtures/Stranded/StrandedFixtureHandler.cs:11), a base declared beside its contract is not (GetFixturePreferencesHandlerBase, @@ -700,22 +705,26 @@

                Component tests: real MudBla and the vendor-neutral toast/confirm facades every migrated page injects (BunitComponentTestBase.cs:46,53), puts JSInterop in loose mode so MudBlazor components that probe JS during render do not throw (BunitComponentTestBase.cs:55), then wires a mutable - AuthenticationStateProvider (BunitComponentTestBase.cs:58, the provider class at :162) plus an - IsAuthenticatedAuthorizationService (BunitComponentTestBase.cs:57, class at :176) so both - <AuthorizeView> cascades and pages that inject the provider directly behave. Tests render - anonymously by default via RenderUnderTest<TComponent> (BunitComponentTestBase.cs:124) or as a - supplied ClaimsPrincipal via RenderAs<TComponent> (BunitComponentTestBase.cs:130, which sets - the provider and the cascading AuthenticationState together at :135-140), with a SetUser hook - (:121) for mid-test auth changes and TestPrincipal + AuthenticationStateProvider (BunitComponentTestBase.cs:63, the provider class at :167) plus the + real DefaultAuthorizationService (BunitComponentTestBase.cs:62) so both <AuthorizeView> + cascades and pages that inject the provider directly behave. The real service is registered + explicitly because bUnit pre-registers a placeholder that throws and AddAuthorizationCore only + TryAdds, and because it evaluates the requirements a view actually builds (roles, the default + deny-anonymous policy) where a permissive double would authorize every authenticated principal + whatever the view asks for (BunitComponentTestBase.cs:58-61). Tests render + anonymously by default via RenderUnderTest<TComponent> (BunitComponentTestBase.cs:129) or as a + supplied ClaimsPrincipal via RenderAs<TComponent> (BunitComponentTestBase.cs:135, which sets + the provider and the cascading AuthenticationState together at :140-143), with a SetUser hook + (:126) for mid-test auth changes and TestPrincipal (MMCA.Common.Testing.UI/Infrastructure/TestPrincipal.cs:7) minting the authenticated principal: a name claim, the user identifier written twice (under sub and under NameIdentifier, because a real principal reaches a page under either name), the requested roles, and an authentication type so IsAuthenticated is true (TestPrincipal.cs:22-32, rationale at :14-21). RenderMudProviders - (BunitComponentTestBase.cs:148) mounts the popover, dialog, and snackbar providers and returns them - as a MudProviderHandles record (BunitComponentTestBase.cs:157) so + (BunitComponentTestBase.cs:153) mounts the popover, dialog, and snackbar providers and returns them + as a MudProviderHandles record (BunitComponentTestBase.cs:162) so components that open a dialog or raise a toast have somewhere to render. One helper is worth knowing before you write your first list-page test: ConfigureDataGridListPageHost - (BunitComponentTestBase.cs:100) wires the list-page state services, an inert viewport double so + (BunitComponentTestBase.cs:105) wires the list-page state services, an inert viewport double so IsMobile is deterministic (:108-111), persistent component state (:114), and SetRendererInfo last (:116-117), because SetRendererInfo freezes the bUnit service provider and every registration made after it is silently ignored (:77-83). The class is pinned to bUnit v2 (the line @@ -732,7 +741,7 @@

                Component tests: real MudBla and HasText (:32), all keyed on accessible text rather than brittle CSS paths.

                HTTP-backed UI services are exercised without a server through CapturingHttpMessageHandler - (MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:19), a canned-response, + (MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:20), a canned-response, request-recording HttpMessageHandler supporting both a responder delegate (CapturingHttpMessageHandler.cs:39) and route registration (SetResponse, CapturingHttpMessageHandler.cs:49), with registered routes consulted first and unmatched requests @@ -1157,11 +1166,32 @@

                Contract, pipeline, and benchmark MMCA.Common.Benchmarks/SpecificationBenchmarks.cs:8-14), both with [MemoryDiagnoser] allocation tracking. Its results are compared in CI by build/perfgate against the committed MMCA.Common/Tests/Performance/perf-baseline.json - (MMCA.Common/.github/workflows/ci.yml:371), so moving a number has to be a deliberate, reviewed + (MMCA.Common/.github/workflows/ci.yml:397), so moving a number has to be a deliberate, reviewed change, [Rubric §12, Performance & Scalability]. The same job family carries one more quiet gate worth knowing: the unit run is invoked with --minimum-expected-tests 2000 - (MMCA.Common/.github/workflows/ci.yml:158), so a discovery regression that silently drops thousands + (MMCA.Common/.github/workflows/ci.yml:161), so a discovery regression that silently drops thousands of tests fails the build instead of reporting a green, empty run.

                +

                Benchmarks time one operation in isolation; MMCA.Common.LoadTests measures the framework at volume. + LoadStack (MMCA.Common/Tests/Performance/MMCA.Common.LoadTests/Support/LoadStack.cs:25) + wires the real persistence stack exactly as a host does, AddApplication() plus + AddInfrastructure(configuration), over one temp-file SQLite database, substituting nothing below the + service registrations (LoadStack.cs:13-19); it is a file rather than :memory: because each + scenario opens one connection per scope, as a deployed host does (LoadStack.cs:20-24). Two suites + run on it. OutboxThroughputLoadTests + (MMCA.Common.LoadTests/OutboxThroughputLoadTests.cs:20) enqueues 10,000 integration events as the + framework writes them and lets the real OutboxProcessor, resolved as a hosted service with its + default settings, drain them to a CountingMessageBus: every message must + be delivered exactly once and stamped processed, above a floor of 500 messages per second + (OutboxThroughputLoadTests.cs:12-19,22,30). PagedQueryLoadTests + (MMCA.Common.LoadTests/PagedQueryLoadTests.cs:13) drives EntityQueryService.GetAllAsync over + 100,000 rows seeded by PagedQueryFixture, asserting exact rows rather + than a count (so a fast wrong answer still fails) under per-shape median latency ceilings and a p95 + ceiling across 50 concurrent paged reads (PagedQueryLoadTests.cs:7-12,23-31). Each constant carries + the local measurement it was derived from, with about 3x headroom over the worst of 25 runs. The tier + is deliberately outside ci.yml and not a required check: the project sits outside + MMCA.Common.slnx, so the solution-wide test run never discovers it, and a timing tier on shared + runners is a trend signal rather than a merge gate, so load-tests.yml runs it weekly on a schedule + plus manual dispatch (MMCA.Common/.github/workflows/load-tests.yml:3-17).

                The takeaway for a new engineer: pick the tier that matches what you are proving (a fast unit test for domain logic, bUnit for a component, an integration fixture for a full request path, a cross-service fixture for a real broker round-trip, an AppHost collection for the orchestration @@ -1318,7 +1348,7 @@

                :3-:8).

                • Depends on - the BCL only: ArgumentException, ArgumentNullException, ArgumentOutOfRangeException, InvalidOperationException, Exception, Action, ICollection<string>.
                • -
                • Concept introduced - the Result pattern's build-time boundary, and the three exceptions it still allows. ADR-013 says the domain reports failure by returning Result, never by throwing: a thrown business failure skips Result.Combine invariant composition, turns a 4xx outcome into a 500, and pays an exception unwind on a path that is not exceptional (rule doc, MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.DomainThrows.cs:23-:29). The rule is not "never throw", though. Argument guards report a caller bug rather than a business outcome, and a caller cannot recover from passing null, so there is nothing for a Result to carry; exactly three exception types are permitted (:16-:21). Everything else in a Domain assembly is a violation. The mechanism is deliberately narrow: for each throw opcode the rule looks at the immediately preceding instruction, skipping the nops a Debug build interleaves, and reads the exception type off a newobj (:143-:154). If the preceding instruction is anything else, the thrown value came from somewhere the instruction stream cannot name, and the site is reported as UNVERIFIABLE rather than guessed at (:127-:131). [Rubric §4 - DDD] assesses whether the domain expresses outcomes in its own vocabulary; an exception is control flow borrowed from the host. [Rubric §15 - Best Practices & Code Quality] and [Rubric §14 - Testability] cover the rest.
                • +
                • Concept introduced - the Result pattern's build-time boundary, and the three exceptions it still allows. ADR-013 says the domain reports failure by returning Result, never by throwing: a thrown business failure skips Result.Combine invariant composition, turns a 4xx outcome into a 500, and pays an exception unwind on a path that is not exceptional (rule doc, MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.DomainThrows.cs:26-:29). The rule is not "never throw", though. Argument guards report a caller bug rather than a business outcome, and a caller cannot recover from passing null, so there is nothing for a Result to carry; exactly three exception types are permitted (:16-:21). Everything else in a Domain assembly is a violation. The mechanism is deliberately narrow: for each throw opcode the rule looks at the immediately preceding instruction, skipping the nops a Debug build interleaves, and reads the exception type off a newobj (:143-:154). If the preceding instruction is anything else, the thrown value came from somewhere the instruction stream cannot name, and the site is reported as UNVERIFIABLE rather than guessed at (:127-:131). [Rubric §4 - DDD] assesses whether the domain expresses outcomes in its own vocabulary; an exception is control flow borrowed from the host. [Rubric §15 - Best Practices & Code Quality] and [Rubric §14 - Testability] cover the rest.
                • Walkthrough - a bare throw; inside a catch compiles to the distinct rethrow opcode, not throw, so it never even enters the scan (fixture doc, :72-:75). The rule also skips compiler-synthesized bodies: the skeleton members of a C# extension(T) block, and the explicit interface implementations on compiler-generated types such as the read-only list wrapper emitted for a collection expression (:156-:177).
                @@ -1485,7 +1515,7 @@

                ObservabilityConventionTests

              • What it is - the SLO alert-to-runbook pairing gate for ADC. It derives from ObservabilityConventionTestsBase and overrides one member, raising the non-vacuity floor from the base's default of 3 to 5 (MMCA.ADC.Architecture.Tests/Governance/ObservabilityConventionTests.cs:7-:15).
              • Depends on - ObservabilityConventionTestsBase, plus two EmbeddedResource entries in the csproj that supply the files the base reads: infra/main.bicep under the logical name infra.main.bicep and infra/OPERATIONS.md under infra.OPERATIONS.md (MMCA.ADC.Architecture.Tests/MMCA.ADC.Architecture.Tests.csproj:17-:22).
              • Concept introduced, identity by inheritance plus a floor override. This is the thin-subclass pattern from BrandColorTokenTests, extended one step. The base defaults ResourceAssembly to GetType().Assembly (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/ObservabilityConventionTestsBase.cs:51), so the derived type is the configuration: deriving in this assembly is what points the rule at ADC's embedded bicep and runbook, the same identity-only wiring the file comment describes ("this repo supplies only its identity", ObservabilityConventionTests.cs:3-:6). On top of that wiring, this subclass raises MinimumAlertSpecs from the base's default of 3 to 5 (:14), because ADC declares five SLO alert specs, the four request and resilience SLOs plus the AI scoring token ceiling (moved into sloAlertSpecs so the pairing gate covers it), and leaving the base floor in place would let the gate pass vacuously if the parse anchors drifted and discovered only three. [Rubric section 13 - Observability & Operability] assesses whether the system can be operated under failure, which means alerts that lead somewhere; this pairs each provisioned alert with a runbook section at build time instead of at 3am.
              • -
              • Walkthrough - one member. MinimumAlertSpecs => 5 (:14) raises the base's default of 3 (ObservabilityConventionTestsBase.cs:39) to ADC's actual alert-spec count, now that the AI scoring token ceiling moved into sloAlertSpecs alongside the four request and resilience SLOs. Everything else runs from the base's three inherited facts: SloAlertSpecs_AreDiscovered_GateIsNotVacuous (ObservabilityConventionTestsBase.cs:54) enforces that floor; EveryProvisionedSloAlert_HasASeverityCorrectRunbookSection (:64) walks the alerts declared in the embedded bicep and requires a matching, severity-correct section in the embedded runbook; and EveryRunbookAlertSection_MapsToAProvisionedAlert (:92) closes the other direction, failing on an orphan runbook section for an alert that no longer exists. The resource names the base reads default to infra.main.bicep and infra.OPERATIONS.md (:42, :45), which is why the csproj logical names must match exactly.
              • +
              • Walkthrough - one member. MinimumAlertSpecs => 5 (:14) raises the base's default of 3 (ObservabilityConventionTestsBase.cs:39) to ADC's actual alert-spec count, now that the AI scoring token ceiling moved into sloAlertSpecs alongside the four request and resilience SLOs. Everything else runs from the base's three inherited facts: SloAlertSpecs_AreDiscovered_GateIsNotVacuous (ObservabilityConventionTestsBase.cs:80) enforces that floor; EveryProvisionedSloAlert_HasASeverityCorrectRunbookSection (:64) walks the alerts declared in the embedded bicep and requires a matching, severity-correct section in the embedded runbook; and EveryRunbookAlertSection_MapsToAProvisionedAlert (:92) closes the other direction, failing on an orphan runbook section for an alert that no longer exists. The resource names the base reads default to infra.main.bicep and infra.OPERATIONS.md (:42, :45), which is why the csproj logical names must match exactly.
              • Why it's built this way - alert definitions live in infrastructure-as-code and the response procedure lives in a Markdown runbook; nothing in either file references the other, so the pairing is exactly the kind of invariant that decays silently. Embedding both into the test assembly turns the pairing into a compile-and-run artifact.
              • Where it's used - runs with the rest of the suite in the build-and-test job (MMCA.ADC/.github/workflows/deploy.yml:189, :284).
              • @@ -1568,6 +1598,25 @@

                InlineStyleTests

              • Where it's used - runs with the rest of the suite in the build-and-test job (MMCA.ADC/.github/workflows/deploy.yml:189, :284).
              • Caveats / not-in-source - the lookbehind is a heuristic against CSS text appearing inline in markup; it narrows false positives but the rule doc makes no claim the exclusion is exhaustive.
              • +

                MobileHostParityTests

                +
                +

                MMCA.ADC.Architecture.Tests · MMCA.ADC.Architecture.Tests.Ui · MMCA.ADC.Architecture.Tests/Ui/MobileHostParityTests.cs:16 · Level 4 · class (public, sealed, partial)

                +
                +
                  +
                • What it is - the drift guard over the production hostnames the MAUI head hardcodes in four places no compiler connects: Api:ApiEndpoint and PublicSite:BaseUrl in the embedded appsettings.json, the Android App Links host MainActivity.PublicWebHost, and the iOS applinks: associated-domains entitlement (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Ui/MobileHostParityTests.cs:6-:15). Unlike the other UI rules in this unit it derives from no shared base: it is an ADC-only test with one fact.
                • +
                • Depends on - ArchitectureMapBase.FindRepoRoot("MMCA.ADC.slnx") to locate Source/Hosts/UI/MMCA.ADC.UI from the compiled test assembly (MobileHostParityTests.cs:36-:37), and the BCL System.Text.Json and System.Text.RegularExpressions (:1-:2). Externals: xUnit v3 and AwesomeAssertions, global-used across the assembly (MMCA.ADC.Architecture.Tests/GlobalUsings.cs:1-:5).
                • +
                • Concept introduced, a cross-file configuration parity check. The four values live in a JSON file, a C# constant and a plist, so a type system cannot tie them together; only a test that reads all three files can. The rule states two different relationships. The web UI serves the app-link association, so PublicSite:BaseUrl, the Android host and the iOS applinks: host must be the identical host. The API endpoint is the gateway, a different app, so it is only required to share the Container Apps environment domain with the web host (:10-:13). [Rubric §34 - Architecture Governance & Documentation] covers the enforced-not-trusted half: an environment move or a custom-domain cutover that misses one spot fails the build instead of shipping a mobile build whose deep links or API calls point at the old environment (:13-:14).
                • +
                • Walkthrough - one fact and four helpers.
                    +
                  • PublicSiteAndAppLinkHosts_AreIdentical_AndTheApiSharesTheirEnvironment (:33-:59) parses appsettings.json with JsonOptions (comments skipped, trailing commas allowed, :27-:31) and takes the Host of Api:ApiEndpoint and PublicSite:BaseUrl (:39-:43); today those are the adc-prod-gateway and adc-prod-ui hosts (MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/appsettings.json:20, :23). It reads Platforms/Android/MainActivity.cs and Platforms/iOS/Entitlements.plist as text (:45-:52), whose declarations sit at MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/Platforms/Android/MainActivity.cs:39 and MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI/Platforms/iOS/Entitlements.plist:11. It then asserts Android equals the public-site host, iOS equals the public-site host, and the environment domains of the API and public-site hosts are equal, each with a because: that names the platform requirement (:54-:58).
                  • +
                  • SingleCapture (:61-:66) requires exactly one match before returning the host group, so a duplicated or missing declaration fails with "exactly one declaration of {what} is expected" rather than silently comparing the first hit.
                  • +
                  • EnvironmentDomain (:68-:69) strips everything up to the first dot, turning adc-prod-ui.<env>.eastus2.azurecontainerapps.io into <env>.eastus2.azurecontainerapps.io.
                  • +
                  • AndroidPublicWebHost and IosAppLinksHost (:71-:75) are GeneratedRegex properties with ExplicitCapture and a 2000 ms match timeout, matching PublicWebHost = "..." and <string>applinks:...</string> respectively.
                  • +
                  +
                • +
                • Why it's built this way - reading the platform files as text rather than compiling them is what lets an architecture test assembly check a MAUI head it does not reference (the MAUI head is not in ADC's CI solution filter, which lists this test project at MMCA.ADC/MMCA.ADC.CI.slnf:59). The MainActivity constant carries its own comment pointing back at PublicSite:BaseUrl (MainActivity.cs:37), and this test is what makes that comment binding.
                • +
                • Where it's used - no usage outside the defining file; it runs with the rest of the architecture suite in the build-and-test job (MMCA.ADC/.github/workflows/deploy.yml:220), whose test step runs dotnet test --solution MMCA.ADC.CI.slnf (:346).
                • +
                • Caveats / not-in-source - the environment check compares everything after the first label, so it assumes both hosts are single-label app names under the same environment suffix; a custom domain that is not shaped <app>.<environment domain> would need the rule revisited. Not determinable from source: whether an Android AndroidManifest intent filter or other platform files carry the host as well (the test reads only the three files named above).
                • +

                ChildlessFixture, ExemptedOffenderFixture, HelperCascadingFixture, LoopCascadingFixture, MissingOverrideFixture, SelfOnlyDeleteFixture

                MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.CascadeFixtures · (see per-type table) · Level 5 · class

                @@ -1575,7 +1624,7 @@

                AuditableAggregateRootEntity<TIdentifierType> over int, differing only in what (if anything) their Delete() override does with their child collection. They are the six-way truth table for the cascade-soft-delete rule.

                • Depends on - AuditableAggregateRootEntity<TIdentifierType>, CascadeChildFixture, and Result (CascadeFixtures.cs:1-:2).
                • -
                • Concept - cross-references the cascade concept introduced by CascadeChildFixture. What this set adds is the body half of the rule, and it is the sharper half. The rule reads the IL of the Delete() override and accepts exactly two shapes: a direct call to DeleteChildren (the helper on the aggregate base, MMCA.Common/Source/Core/MMCA.Common.Domain/Entities/AuditableAggregateRootEntity.cs:273), or a zero-argument Delete reached through the callvirt opcode, which is what a hand-rolled foreach (var line in _lines) line.Delete(); compiles to (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.CascadeSoftDelete.cs:172-:186). What it deliberately refuses to accept is base.Delete(), and the reason is a small piece of C# trivia doing real work: base.X() on a virtual member is the one thing C# emits as a non-virtual call, and nothing else in the language produces that pairing, so excluding it separates "delete myself" from "delete my children" without guessing (:178-:182). That single discrimination is what makes SelfOnlyDeleteFixture catchable at all.
                • +
                • Concept - cross-references the cascade concept introduced by CascadeChildFixture. What this set adds is the body half of the rule, and it is the sharper half. The rule reads the IL of the Delete() override and accepts exactly two shapes: a direct call to DeleteChildren (the helper on the aggregate base, MMCA.Common/Source/Core/MMCA.Common.Domain/Entities/AuditableAggregateRootEntity.cs:275), or a zero-argument Delete reached through the callvirt opcode, which is what a hand-rolled foreach (var line in _lines) line.Delete(); compiles to (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.CascadeSoftDelete.cs:172-:186). What it deliberately refuses to accept is base.Delete(), and the reason is a small piece of C# trivia doing real work: base.X() on a virtual member is the one thing C# emits as a non-virtual call, and nothing else in the language produces that pairing, so excluding it separates "delete myself" from "delete my children" without guessing (:178-:182). That single discrimination is what makes SelfOnlyDeleteFixture catchable at all.
                • Walkthrough - the rule reports two distinct reasons and names the offending collection in both, so the failure text tells a developer which member to fix: "no Delete() override, so its children ({names}) stay active" (:160) and "Delete() never deletes a child, so its children ({names}) stay active" (:165). Allowlist entries are type full names or namespace prefixes, matched ordinally exactly as the hard-delete rule matches (:79-:83).

                @@ -1660,9 +1709,9 @@

                ProtoContractTests

                • What it is - the frozen wire contract for ADC's synchronous cross-service API. It names the seven .proto files the four *.Contracts projects compile and commits a 76-line snapshot of everything they declare, so a renumbered field or a renamed rpc fails the build (MMCA.ADC.Architecture.Tests/Contracts/ProtoContractTests.cs:9-:18, :20-:98).
                • Depends on - ProtoContractTestsBase. Nothing else: the rule reads .proto files off disk from the repo root, so this class takes no map and the csproj needs no reference to the *.Contracts projects.
                • -
                • Concept introduced, pinning a contract that no compiler checks. A .proto file is a published contract between processes that are built separately, so nothing in a single repo's build notices when it changes incompatibly. The rule library rebuilds the live contract by parsing the files and diffs it against the committed list, reporting each side separately as "present in the .proto files but NOT frozen" and "frozen but NOT present in the .proto files" (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:67-:77). What gets pinned is exactly the wire surface: the package, every rpc with its request/response types and streaming flags, every message field with its declared type, label, and field number, and every enum value with its number (ArchitectureRules.Protos.cs:21-:24). What is deliberately not pinned is syntax, import, and option lines including csharp_namespace, because none of them changes a byte on the wire and failing on them is the fastest way to teach a team to update a snapshot without reading it (:27-:31). [Rubric §9 - API & Contract Design] assesses contract governance across the whole surface, not just REST. [Rubric §7 - Microservices Readiness]: this list is the synchronous coupling between ADC's four services, in the same way IntegrationEventContractTests is the asynchronous one (ADR-007).
                • +
                • Concept introduced, pinning a contract that no compiler checks. A .proto file is a published contract between processes that are built separately, so nothing in a single repo's build notices when it changes incompatibly. The rule library rebuilds the live contract by parsing the files and diffs it against the committed list, reporting each side separately as "present in the .proto files but NOT frozen" and "frozen but NOT present in the .proto files" (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:68-:77). What gets pinned is exactly the wire surface: the package, every rpc with its request/response types and streaming flags, every message field with its declared type, label, and field number, and every enum value with its number (ArchitectureRules.Protos.cs:22-:24). What is deliberately not pinned is syntax, import, and option lines including csharp_namespace, because none of them changes a byte on the wire and failing on them is the fastest way to teach a team to update a snapshot without reading it (:27-:31). [Rubric §9 - API & Contract Design] assesses contract governance across the whole surface, not just REST. [Rubric §7 - Microservices Readiness]: this list is the synchronous coupling between ADC's four services, in the same way IntegrationEventContractTests is the asynchronous one (ADR-007).
                • Walkthrough - three members, all implementing abstract hooks.
                    -
                  • SolutionFileName => "MMCA.ADC.slnx" (:5) implements ProtoContractTestsBase.SolutionFileName (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Contracts/ProtoContractTestsBase.cs:22). The rule resolves the repo root from it via ArchitectureMapBase.FindRepoRoot (ArchitectureRules.Protos.cs:45), so the files are read from the working tree regardless of the runner's working directory.
                  • +
                  • SolutionFileName => "MMCA.ADC.slnx" (:5) implements ProtoContractTestsBase.SolutionFileName (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Contracts/ProtoContractTestsBase.cs:22). The rule resolves the repo root from it via ArchitectureMapBase.FindRepoRoot (ArchitectureRules.Protos.cs:46), so the files are read from the working tree regardless of the runner's working directory.
                  • ProtoFiles (:9-:18) lists seven repo-root-relative paths, and the comment states the scope rule: every .proto compiled by the four *.Contracts projects (:7-:8). Two from Conference (event_live_validation, session_bookmark_validation), two from Engagement (bookmark_count, user_engagement_export), one from Identity (attendee_query), and two from Notification (live_channel, user_notification_export).
                  • FrozenProtoContracts (:20-:98) is the snapshot: 64 message ... lines (:22-:85), one per field, each ending in = <number> : <type>, and 12 service ... lines (:86-:97), one per rpc. The entries are sorted, which is what makes a regenerated snapshot diff line by line, and the base's <remarks> explains how to regenerate it (print ArchitectureRules.BuildProtoContract(...) and paste, ProtoContractTestsBase.cs:12-:17). Reading the list is the fastest way to see what ADC's services actually say to each other: live-window validation, current-room lookup and sponsor live info from Conference, bookmark counts and the engagement export from Engagement, the attendee user-id list from Identity, and channel push plus the notification export from Notification.
                  • The single inherited fact is ProtoContracts_ShouldMatch_TheFrozenSnapshot (ProtoContractTestsBase.cs:33).
                  • @@ -1827,7 +1876,7 @@

                    PiiConventionTestsBase

                @@ -1883,18 +1932,6 @@

                AiDependencyIsolationTests

              • Walkthrough - one member, Map (:16), implementing the base's abstract property (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Layering/AiDependencyIsolationTestsBase.cs:21). Two inherited facts: LanguageModelSdks_ShouldStayAt_TheModelBoundary (:24) fails on a module naming the vendor SDK directly (Anthropic, OpenAI, Azure.AI, or Microsoft.Extensions.AI itself) outside Infrastructure, and GovernedAiPackage_ShouldStayBehind_Infrastructure (:28) fails on a module taking the framework's governed MMCA.Common.AI package as a convenience and passing a PromptContract around as a domain type.
              • Why it's built this way - the base's summary states the reason both directions are checked: either leak turns "the app calls a model at one place" into "the model is spread through the app", and a dependency that is everywhere cannot be versioned, evaluated, capped or swapped (AiDependencyIsolationTestsBase.cs:7-:12). [Rubric §16 - AI-Native Development] is the direct target.
              • -

                ClockReadTests

                -
                -

                MMCA.ADC.Architecture.Tests · MMCA.ADC.Architecture.Tests.Domain · MMCA.ADC.Architecture.Tests/Domain/ClockReadTests.cs:8 · Level 13 · class (public, sealed)

                -
                -
                  -
                • What it is - the map-only ADC subclass of the framework's clock-read fitness function: the whole body is one line supplying AdcArchitectureMap (MMCA.ADC.Architecture.Tests/Domain/ClockReadTests.cs:8-:11).
                • -
                • Depends on - ClockReadTestsBase from MMCA.Common.Testing.Architecture and AdcArchitectureMap.
                • -
                • Concept introduced - the same thin-subclass shape as BrandColorTokenTests and AiDependencyIsolationTests: the rule body lives once in the shared base, and deriving a sealed subclass over ADC's map is what turns it on for this repo (ADR-015). The base's own concern (proved once in MMCA.Common and exercised here by name only) is that direct reads of DateTime.Now/DateTime.UtcNow/DateTimeOffset.Now inside domain and application code are untestable and non-deterministic; production code is expected to take an injected clock abstraction instead. [Rubric §14 - Testability] is the direct target.
                • -
                • Walkthrough - one member, Map (:10), implementing the base's abstract property over ADC's assemblies.
                • -
                • Why it's built this way - identical rationale to every other map-only subclass in this unit: the fixture proof lives once in MMCA.Common (see MMCA.Common.Architecture.Tests/Domain/ClockReadTests.cs and MMCA.Common.Architecture.Tests/ClockReadFixtures/ClockReadFixtures.cs), so each consumer repo need only wire its own map to gain the coverage.
                • -
                • Where it's used - runs with the rest of the suite in the build-and-test job; not consumed by any other type in this unit.
                • -

                CommandValidatorCoverageTests

                MMCA.ADC.Architecture.Tests · MMCA.ADC.Architecture.Tests.Cqrs · MMCA.ADC.Architecture.Tests/Cqrs/CommandValidatorCoverageTests.cs:18 · Level 13 · class (public, sealed)

                @@ -1932,8 +1969,8 @@

                ConstructorDependencyCountTests

              • What it is - a single-responsibility ceiling applied to three separate populations: no *Service in a mapped module Application assembly may exceed ten constructor dependencies, no API controller may exceed ten, and no CQRS handler may exceed seven (MMCA.ADC.Architecture.Tests/Cqrs/ConstructorDependencyCountTests.cs:22-:28, :56, :65).
              • Depends on - ConstructorDependencyCountTestsBase and AdcArchitectureMap.
              • Concept introduced, the ratchet set to the real high-water mark, and its history kept in the file. A ceiling is only a ceiling if it sits at the current maximum, so the class comment keeps a ledger of every move, on the stated principle that "a ceiling only means something if every move of it was deliberate". It was 8 while the comment claimed AuthenticationService had 8 dependencies; it had 7, so the gate carried a phantom slot of headroom and was tightened to the real mark on 2026-07-28. It moved to 9 when refresh tokens became multi-device sessions: the framework's AuthenticationServiceBase constructor took on the session store and its options, and ADC's subclass passed both through on top of the IExternalLoginEmailVerifier its OAuth auto-link gate needs. It moved again, 9 to 10, on 2026-09-23: the AuthenticationServiceSettings holder that had bundled IOptions<RefreshSessionSettings> and IOptions<EmailConfirmationSettings> into one slot was deleted, and both options now reach the framework base directly (ConstructorDependencyCountTests.cs:22-:26). The comment then makes the judgement explicit each time: the additions are framework-imposed collaborators of the same facade rather than a second responsibility, so the honest response is a raised ceiling and not an artificial bundle. [Rubric section 1 - SOLID] assesses single responsibility among other things; constructor arity is the cheapest mechanical proxy for a class that has accumulated too many jobs, and the comment is careful to distinguish a cohesive facade from a bundle.
              • -
              • Walkthrough - four members. Map (:22), and MaxConstructorDependencies => 10 (:28), which implements the base's abstract service ceiling (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Cqrs/ConstructorDependencyCountTestsBase.cs:34) and backs ApplicationServices_DoNotExceedConstructorDependencyCeiling (:52), which scans every non-abstract class whose name ends in "Service" in the map's module Application assemblies (:54-:58), asserts the set is non-empty so the guard cannot pass vacuously (:60-:61), and reports each offender with its own arity (:63-:80). The subclass comment names AuthenticationService, now at the raised mark of 10, and CreateSessionHandler next at 7 (ConstructorDependencyCountTests.cs:6-:8). - The other two members arrived with the framework's 2026-09 wave, which added a controller ceiling and a handler ceiling to the base alongside the existing service one. MaxControllerConstructorDependencies => 10 (:56) implements the base's Controllers_DoNotExceedConstructorDependencyCeiling (ConstructorDependencyCountTestsBase.cs:90), which scans every concrete ControllerBase-derived class across the map's API assemblies. Its own doc comment keeps the same kind of ledger: the fact first reported 14 (SpeakersController), 12 (EventsController), 11 (SessionsController) and 10 (LivePollsController), each serving several sub-resources from one class; each was split by sub-resource into sibling controllers on the same route prefix, action templates and authorization attributes, so no URL, verb or authorization posture moved (SpeakerLinksController/SpeakerSessionsController 14 to 9, EventLifecycleController 12 to 9, SessionCalendarController 11 to 10, LivePollVotingController 10 to 7). SessionsController stops at 10 rather than 9 deliberately: its remaining dependency is IEntityQueryService<Event, ...>, held only for the BR-86 advisory X-Warning header on create, which the update path gets from UpdateSessionResult.HasDateRangeWarning but the create path cannot, because the framework base fixes its create handler at ICommandHandler<SessionCreateRequest, Result<SessionDTO>> and the Application layer has no way to set a response header; the honest mark is therefore 10, to be lowered the moment a framework change makes 9 true (ConstructorDependencyCountTests.cs:30-:55). MaxHandlerConstructorDependencies => 7 (:65) implements Handlers_DoNotExceedConstructorDependencyCeiling (ConstructorDependencyCountTestsBase.cs:114), scanning every concrete ICommandHandler<,> / IQueryHandler<,> implementation in the map's module Application assemblies, set at the mark the fact first reported: ResetPasswordHandler and SubmitQuestionHandler jointly at 7, with CreateSessionHandler, RefreshFromSessionizeHandler, UploadSessionAssetHandler, CreateBookmarkHandler and RecordRoomCheckInHandler next at 6 (:62-:63).
              • +
              • Walkthrough - four members. Map (:22), and MaxConstructorDependencies => 10 (:28), which implements the base's abstract service ceiling (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Cqrs/ConstructorDependencyCountTestsBase.cs:40) and backs ApplicationServices_DoNotExceedConstructorDependencyCeiling (:52), which scans every non-abstract class whose name ends in "Service" in the map's module Application assemblies (:54-:58), asserts the set is non-empty so the guard cannot pass vacuously (:60-:61), and reports each offender with its own arity (:63-:80). The subclass comment names AuthenticationService, now at the raised mark of 10, and CreateSessionHandler next at 7 (ConstructorDependencyCountTests.cs:6-:8). + The other two members arrived with the framework's 2026-09 wave, which added a controller ceiling and a handler ceiling to the base alongside the existing service one. MaxControllerConstructorDependencies => 10 (:56) implements the base's Controllers_DoNotExceedConstructorDependencyCeiling (ConstructorDependencyCountTestsBase.cs:116), which scans every concrete ControllerBase-derived class across the map's API assemblies. Its own doc comment keeps the same kind of ledger: the fact first reported 14 (SpeakersController), 12 (EventsController), 11 (SessionsController) and 10 (LivePollsController), each serving several sub-resources from one class; each was split by sub-resource into sibling controllers on the same route prefix, action templates and authorization attributes, so no URL, verb or authorization posture moved (SpeakerLinksController/SpeakerSessionsController 14 to 9, EventLifecycleController 12 to 9, SessionCalendarController 11 to 10, LivePollVotingController 10 to 7). SessionsController stops at 10 rather than 9 deliberately: its remaining dependency is IEntityQueryService<Event, ...>, held only for the BR-86 advisory X-Warning header on create, which the update path gets from UpdateSessionResult.HasDateRangeWarning but the create path cannot, because the framework base fixes its create handler at ICommandHandler<SessionCreateRequest, Result<SessionDTO>> and the Application layer has no way to set a response header; the honest mark is therefore 10, to be lowered the moment a framework change makes 9 true (ConstructorDependencyCountTests.cs:30-:55). MaxHandlerConstructorDependencies => 7 (:65) implements Handlers_DoNotExceedConstructorDependencyCeiling (ConstructorDependencyCountTestsBase.cs:140), scanning every concrete ICommandHandler<,> / IQueryHandler<,> implementation in the map's module Application assemblies, set at the mark the fact first reported: ResetPasswordHandler and SubmitQuestionHandler jointly at 7, with CreateSessionHandler, RefreshFromSessionizeHandler, UploadSessionAssetHandler, CreateBookmarkHandler and RecordRoomCheckInHandler next at 6 (:62-:63).
              • Why it's built this way - each ceiling is meant to be raised consciously rather than drifted past, which is what the comments ask for and what their own history demonstrates. The controller split is the sharpest example: four controllers that had accreted several sub-resources were split along existing route and authorization boundaries rather than pinned at their old, inflated marks.
              • Caveats / not-in-source - the sibling rule HandlerConventionTests raises the base's separate arity check to 10 for a different population (HandlerConventionTests.cs:18), and its <remarks> states the coupling directly: the two guards must move together, or the stricter one turns the other into a comment (:11-:12). Note the service scan is name-based (*Service), so a handler or a builder with the same arity is judged only by its own ceiling, not this one.
              • @@ -1969,9 +2006,9 @@

                DataResidencyTests

              • Concept introduced, a test as the join between a document and an infrastructure fact. Most of the rules in this unit compare code to code. This one compares prose to infrastructure: it reads the deployed region out of the source of truth (SQL_LOCATION="${SQL_LOCATION_OVERRIDE:-westus2}", MMCA.ADC/.github/workflows/deploy.yml:1158) and then requires PRIVACY.md to say the same thing, comparing whitespace-insensitively and case-insensitively so "West US 2" matches the westus2 region token. [Rubric §30 - Compliance/Privacy/Data Governance] assesses whether privacy claims are true and stay true; a policy that names a region the data never lived in is a compliance defect that no code review would catch, and this is the mechanism that closes it.
              • Walkthrough - three members.
                • Map (:14) exists only so the base can resolve the repo root through ArchitectureMapBase.FindRepoRoot($"{Map.RepoToken}.slnx"); no assembly scanning happens in this rule.
                • -
                • ForbiddenResidencyClaims => ["central United States"] (:16) overrides the base's empty default (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/DataResidencyTestsBase.cs:23) and blocks a specific stale statement from returning, one that once contradicted the deployed region (DataResidencyTests.cs:9-:10).
                • -
                • ExtractDeployedRegion(string repoRoot) (:20-:31) implements the base's abstract hook (DataResidencyTestsBase.cs:53). It reads .github/workflows/deploy.yml (:22), locates the literal marker SQL_LOCATION_OVERRIDE:- with an ordinal IndexOf (:24-:25), asserts the marker exists with a because explaining what the workflow must declare (:26-:27), then takes the alphanumeric run that follows as the region (:29-:30). Assert-then-parse rather than return-empty is what the base asks implementations to do.
                • -
                • The inherited fact PrivacyPolicy_DataStorageRegion_MatchesDeployedRegion (DataResidencyTestsBase.cs:26) then asserts the normalized policy contains the normalized region and contains none of the forbidden claims.
                • +
                • ForbiddenResidencyClaims => ["central United States"] (:16) overrides the base's empty default (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/DataResidencyTestsBase.cs:24) and blocks a specific stale statement from returning, one that once contradicted the deployed region (DataResidencyTests.cs:9-:10).
                • +
                • ExtractDeployedRegion(string repoRoot) (:20-:31) implements the base's abstract hook (DataResidencyTestsBase.cs:87). It reads .github/workflows/deploy.yml (:22), locates the literal marker SQL_LOCATION_OVERRIDE:- with an ordinal IndexOf (:24-:25), asserts the marker exists with a because explaining what the workflow must declare (:26-:27), then takes the alphanumeric run that follows as the region (:29-:30). Assert-then-parse rather than return-empty is what the base asks implementations to do.
                • +
                • The inherited fact PrivacyPolicy_DataStorageRegion_MatchesDeployedRegion (DataResidencyTestsBase.cs:27) then asserts the normalized policy contains the normalized region and contains none of the forbidden claims.
              • Why it's built this way - the account data and session bookmarks live in the Azure SQL database, and the QiMata Sponsorship subscription forces that SQL server into a different region from the Container Apps (DataResidencyTests.cs:5-:9), so "where the app runs" is genuinely not "where the personal data sits". Parsing the SQL region default rather than the app region encodes that distinction.
              • @@ -2088,54 +2125,17 @@

                FrameworkVersionConsistencyTests

              • Concept introduced - a policy made executable. ADR-016 says consumers bump every MMCA.Common.* entry together, with no phased rollout. A policy that lives only in a document is enforced by memory; this rule enforces it by build. [Rubric §32 - Dependency & Supply-Chain] assesses how dependency versions are governed; [Rubric §15 - Best Practices & Code Quality]: a half-swept pin set is the kind of defect that surfaces as an unrelated runtime error weeks later.
              • Walkthrough - one member, Map (:11). The inherited fact is AllMmcaCommonPackages_ArePinnedToOneVersion (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/FrameworkVersionConsistencyTestsBase.cs:25), and the base's non-vacuity floor MinimumCommonPackageCount stays at its default of 13 (:22). ADC currently pins 16 MMCA.Common.* packages in MMCA.ADC/Directory.Packages.props, so the floor is met with room to spare.
              • -

                HandlerConventionTests

                -
                -

                MMCA.ADC.Architecture.Tests · MMCA.ADC.Architecture.Tests.Cqrs · MMCA.ADC.Architecture.Tests/Cqrs/HandlerConventionTests.cs:16 · Level 13 · class (public, sealed)

                -
                -
                  -
                • What it is - the CQRS handler placement and composition guard: handlers and validators live in the Application layer, handlers do not inject other handlers, application services do not inject handlers, domain event handlers are sealed and live in Application, and application services respect a constructor-arity limit that ADC raises to ten (MMCA.ADC.Architecture.Tests/Cqrs/HandlerConventionTests.cs:18).
                • -
                • Depends on - HandlerConventionTestsBase and AdcArchitectureMap.
                • -
                • Walkthrough - two members. Map (:16) and MaxServiceConstructorParameters => 10 (:18), raising the base default of 8 (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Cqrs/HandlerConventionTestsBase.cs:12). Six facts are inherited (:15, :18, :21, :24, :27, :30). [Rubric §6 - CQRS & Event-Driven] assesses whether the command/query split is structural rather than nominal; a handler that injects another handler is the classic way that split quietly becomes a call graph. [Rubric §1 - SOLID] covers the arity half.
                • -
                • Why it's built this way - the <remarks> (:6-:13) records both the reason for the raise and the coupling it creates. The shared AuthenticationServiceBase constructor took on the refresh-session store and its options, and ADC's subclass passed both through alongside the IExternalLoginEmailVerifier its OAuth auto-link gate needs; the rationale lives in ConstructorDependencyCountTests, which sets the same ceiling. Both moved 9 to 10 on 2026-09-23 for AuthenticationService, when the options holder that bundled its two settings objects into one slot was deleted (:11-:13). The last sentence of the <remarks> is the operative one: the two guards must move together, or the stricter one turns the other into a comment.
                • -
                • Caveats / not-in-source - ADC therefore has two overlapping arity limits with the same value (10) but different scans (this one over application services generally, the sibling over *Service types in module Application assemblies). Whether they cover an identical type set is decided inside ArchitectureRules and is not determinable from these subclasses.
                • -
                -

                FixtureBadFeatures, FixtureExpiredFeatures, FixtureGoodFeatures

                +

                ClockReadTests

                -

                MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.FeatureFlagFixtures · (see per-type table) · Level 1 · class

                +

                MMCA.ADC.Architecture.Tests · MMCA.ADC.Architecture.Tests.Domain · MMCA.ADC.Architecture.Tests/Domain/ClockReadTests.cs:8 · Level 13 · class (public, sealed)

                -

                Three static fixture classes carrying [FeatureFlag]-annotated constants for FeatureFlagLifecycleTests: one class of constants that must pass the lifecycle rule, one of constants that must each fail it for a distinct reason, and one that is expired.

                -
                  -
                • Depends on - FeatureFlagAttribute and FeatureFlagLifetime (MMCA.Common.Shared.FeatureFlags, FeatureFlagFixtures.cs:11-:41).
                • -
                • Concept - cross-references the flag-lifecycle concept introduced by FeatureFlagLifecycleTestsBase (Bases/Governance/FeatureFlagLifecycleTestsBase.cs:10): every flag constant must declare [FeatureFlag(Permanent|Temporary, RemoveBy, Owner)], and a temporary flag past its RemoveBy date fails the build (ADR-031). These three classes are the fixture matrix FeatureFlagLifecycleTests runs the rule against: one constant per violation the rule is meant to catch, plus the shapes that must stay silent.
                • -
                • Walkthrough - FixtureGoodFeatures (:11-:20) has two constants that must pass: PermanentFlag, declared Permanent with no RemoveBy (:13-:14), and LiveTemporaryFlag, Temporary with a RemoveBy far enough out to stay green, 2099-12-31 (:16-:18). FixtureBadFeatures (:23-:41) has four constants that must each fail for a different reason: UnannotatedFlag carries no [FeatureFlag] at all (:28-:29); PermanentWithRemoveByFlag is Permanent yet still names a RemoveBy date, a contradiction (:31-:33); TemporaryWithoutRemoveByFlag is Temporary with no RemoveBy, which the rule treats as permanent in everything but name (:35-:37); TemporaryWithUnparseableRemoveByFlag is Temporary with a RemoveBy in an order the rule cannot parse, "31/12/2030" (:39-:41). FixtureExpiredFeatures (:42-:47) has one constant, ExpiredFlag, Temporary with RemoveBy = "2020-01-01", which is what the expiry check exists to catch (:44-:46).
                • -
                • Why it's built this way - splitting the matrix into good/bad/expired classes rather than one flat list lets each FeatureFlagLifecycleTests fact read only the constants relevant to it, so adding a new failure mode to FixtureBadFeatures cannot accidentally break a fact that only reads FixtureGoodFeatures.
                • -
                -
                Every domain entity declaring a [Pii] property implements IAnonymizable (ADR-005). Supplies only Map (:15). Structurally vacuous in the framework, and its own doc says so (:7-:12); the machinery is proven non-vacuously by PiiErasureContractFitnessTests. [Rubric §30 - Compliance/Privacy.]
                - - - - - - - - - - - - - - - - - - - - - - -
                TypeFile:LineRole
                FixtureGoodFeaturesMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/FeatureFlagFixtures/FeatureFlagFixtures.cs:11Two constants that must pass the lifecycle rule.
                FixtureBadFeaturesMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/FeatureFlagFixtures/FeatureFlagFixtures.cs:23Four constants, each failing for a distinct reason (unannotated, contradictory, dateless-temporary, unparseable date).
                FixtureExpiredFeaturesMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/FeatureFlagFixtures/FeatureFlagFixtures.cs:42One constant past its RemoveBy date.
                  -
                • Where it's used - all three are read across multiple facts of FeatureFlagLifecycleTests (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/FeatureFlagLifecycleTests.cs), which asserts each fixture's constants land in the rule's expected pass/fail buckets.
                • +
                • What it is - the map-only ADC subclass of the framework's clock-read fitness function: the whole body is one line supplying AdcArchitectureMap (MMCA.ADC.Architecture.Tests/Domain/ClockReadTests.cs:8-:11).
                • +
                • Depends on - ClockReadTestsBase from MMCA.Common.Testing.Architecture and AdcArchitectureMap.
                • +
                • Concept introduced - the same thin-subclass shape as BrandColorTokenTests and AiDependencyIsolationTests: the rule body lives once in the shared base, and deriving a sealed subclass over ADC's map is what turns it on for this repo (ADR-015). The base's own concern (proved once in MMCA.Common and exercised here by name only) is that direct reads of DateTime.Now/DateTime.UtcNow/DateTimeOffset.Now inside domain and application code are untestable and non-deterministic; production code is expected to take an injected clock abstraction instead. [Rubric §14 - Testability] is the direct target.
                • +
                • Walkthrough - one member, Map (:10), implementing the base's abstract property over ADC's assemblies.
                • +
                • Why it's built this way - identical rationale to every other map-only subclass in this unit: the fixture proof lives once in MMCA.Common (see MMCA.Common.Architecture.Tests/Domain/ClockReadTests.cs and MMCA.Common.Architecture.Tests/ClockReadFixtures/ClockReadFixtures.cs), so each consumer repo need only wire its own map to gain the coverage.
                • +
                • Where it's used - runs with the rest of the suite in the build-and-test job; not consumed by any other type in this unit.

                HandlerResultConventionTests

                @@ -2173,11 +2173,11 @@

                IntegrationEventContractTests

                MMCA.ADC.Architecture.Tests · MMCA.ADC.Architecture.Tests.Contracts · MMCA.ADC.Architecture.Tests/Contracts/IntegrationEventContractTests.cs:3 · Level 13 · class (public, sealed)

                  -
                • What it is - the frozen wire contract for ADC's cross-service asynchronous API. It commits a seven-line snapshot of every integration event's full name and property shape, and the build fails if the live contract differs (MMCA.ADC.Architecture.Tests/Contracts/IntegrationEventContractTests.cs:9-:20).
                • +
                • What it is - the frozen wire contract for ADC's cross-service asynchronous API. It commits a seven-line snapshot of every integration event's wire name and property shape, and the build fails if the live contract differs (MMCA.ADC.Architecture.Tests/Contracts/IntegrationEventContractTests.cs:9-:20).
                • Depends on - IntegrationEventContractTestsBase and AdcArchitectureMap.
                • -
                • Concept introduced, the approval snapshot. The rules above check shape rules; this one checks identity. A consumer in another service deserializes by shape, so a renamed, removed, or retyped property (or a brand-new event shipped without a consumer) breaks the contract at runtime with no compile error anywhere. The base rebuilds the live contract from the map and asserts it against the committed list (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Contracts/IntegrationEventContractTestsBase.cs:20-:26), so any change surfaces as a diff in this file that a reviewer must consciously accept. [Rubric §9 - API & Contract Design] assesses contract governance, and the asynchronous contract is as much an API as the REST surface; [Rubric §7 - Microservices Readiness]: with all four ADC modules already running as separate services, this list is the actual coupling between them. Its synchronous twin is ProtoContractTests.
                • -
                • Walkthrough - two members. Map (:5), and ExpectedContract (:9-:23), a collection expression of seven strings in FullName { Prop:Type, ... } form: EventFeedbackSubmitted and SessionFeedbackSubmitted from Conference, SpeakerLinkedToUser and SpeakerUnlinkedFromUser from Conference (the pair Identity consumes to set and clear the linked-speaker reference on the user), AttendeeCheckedIn from Engagement, and UserDeleted plus UserRegistered from Identity. The properties are listed in sorted order, which is how a rebuilt contract stays comparable line by line. The inherited fact is IntegrationEventContracts_ShouldMatch_TheFrozenSnapshot (IntegrationEventContractTestsBase.cs:26).
                • -
                • Why it's built this way - the comment states the rule of engagement (:7-:8): update the snapshot deliberately, and version the event or coordinate the consumer rollout in the same commit. The AttendeeCheckedIn entry carries its own inline justification (:15-:16): SponsorId is additive, optional, defaults to null, and is declared last precisely so a payload written before the sponsor scope existed still deserializes (confirmed in the event itself, MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Shared/CheckIns/IntegrationEvents/AttendeeCheckedIn.cs:22, :31). UserRegistered now carries the same pattern (:19-:21): EmailVerified is additive and optional, defaulting to false, so a payload written before the flag existed still deserializes, and it deserializes to the value that links nothing, since the Conference consumer auto-links a speaker only on a verified provider email.
                • +
                • Concept introduced, the approval snapshot. The rules above check shape rules; this one checks identity. A consumer in another service deserializes by shape, so a renamed, removed, or retyped property (or a brand-new event shipped without a consumer) breaks the contract at runtime with no compile error anywhere. The base rebuilds the live contract from the map (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Contracts/IntegrationEventContractTestsBase.cs:38) and reports every observable difference against the committed list (:63-:78): a missing event, a new event, and per event a missing, extra, or retyped member. So any change surfaces as a diff in this file that a reviewer must consciously accept. [Rubric §9 - API & Contract Design] assesses contract governance, and the asynchronous contract is as much an API as the REST surface; [Rubric §7 - Microservices Readiness]: with all four ADC modules already running as separate services, this list is the actual coupling between them. Its synchronous twin is ProtoContractTests.
                • +
                • Walkthrough - two members. Map (:5), and ExpectedContract (:9-:23), a collection expression of seven strings in Key { Prop:Type, ... } form, where the key is the event's [EventName] wire identity (Conference.EventFeedbackSubmitted.v1, Identity.UserRegistered.v1) and falls back to the full type name only for an event that declares none (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Events.cs:82-:89): EventFeedbackSubmitted and SessionFeedbackSubmitted from Conference, SpeakerLinkedToUser and SpeakerUnlinkedFromUser from Conference (the pair Identity consumes to set and clear the linked-speaker reference on the user), AttendeeCheckedIn from Engagement, and UserDeleted plus UserRegistered from Identity. The builder lists properties sorted by name with generic arguments spelled out (SessionId:Nullable<Int32>, ArchitectureRules.Events.cs:64-:75, :98), but the base compares each event's members as a set, not a sequence, because JSON carries no member order and a reordered declaration changes nothing a consumer can observe (IntegrationEventContractTestsBase.cs:11-:15). The inherited fact is IntegrationEventContracts_ShouldMatch_TheFrozenSnapshot (IntegrationEventContractTestsBase.cs:36).
                • +
                • Why it's built this way - the comment states the rule of engagement (:7-:8): update the snapshot deliberately, and version the event or coordinate the consumer rollout in the same commit. Keying each line on the [EventName] value (for example [EventName("Engagement.AttendeeCheckedIn.v1")], MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Shared/CheckIns/IntegrationEvents/AttendeeCheckedIn.cs:23) means a CLR rename or namespace move does not churn the snapshot, exactly as it does not churn the wire (ArchitectureRules.Events.cs:77-:81); the .v1 suffix is where a breaking change gets versioned. The AttendeeCheckedIn entry carries its own inline justification (:15-:16): SponsorId is additive, optional, defaults to null, and is declared last precisely so a payload written before the sponsor scope existed still deserializes (confirmed in the event itself, MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Shared/CheckIns/IntegrationEvents/AttendeeCheckedIn.cs:22, :31). UserRegistered now carries the same pattern (:19-:21): EmailVerified is additive and optional, defaulting to false, so a payload written before the flag existed still deserializes, and it deserializes to the value that links nothing, since the Conference consumer auto-links a speaker only on a verified provider email.
                • Caveats / not-in-source - the snapshot proves that the shape has not changed, not that any consumer actually handles it. Consumer-side behavior is exercised by the cross-service Testcontainers tier, not here.

                IntegrationEventPayloadPurityTests

                @@ -2295,67 +2295,16 @@

                ServiceContractPurityTests

              • Where it's used - the rule is live in ADC, not latent. Six [ServiceContract] interfaces are declared in mapped module Shared assemblies today: IAttendeeQueryService (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Shared/Users/IAttendeeQueryService.cs:10), IEventLiveValidationService (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Events/Live/IEventLiveValidationService.cs:12), ISessionBookmarkValidationService (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Shared/Sessions/ISessionBookmarkValidationService.cs:10), IBookmarkCountService (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Shared/UserSessionBookmarks/IBookmarkCountService.cs:10), IUserEngagementExportService (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Shared/Exports/IUserEngagementExportService.cs:13), and IUserNotificationExportService (MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.Shared/UserNotifications/IUserNotificationExportService.cs:13). All six sit in Shared assemblies the map registers, so the rule inspects them on every run.
              • Caveats / not-in-source - the *.Contracts projects that hold the generated gRPC clients and adapters are not in AdcArchitectureMap, so any [ServiceContract] type declared there rather than in Shared would be outside this scan.
              -

              SharedLayerTests

              -
              -

              MMCA.ADC.Architecture.Tests · MMCA.ADC.Architecture.Tests.Layering · MMCA.ADC.Architecture.Tests/Layering/SharedLayerTests.cs:3 · Level 13 · class (public, sealed)

              -
              -
                -
              • What it is - the guard on the Shared layer, the one layer other modules are allowed to reference: a module's Shared project must not depend on that module's own internal layers, must not reach sibling modules, and must stay free of EF Core.
              • -
              • Depends on - SharedLayerTestsBase and AdcArchitectureMap. Map-only shape (ConcurrencyConventionTests).
              • -
              • Walkthrough - one member, Map (:5). Three inherited facts (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Layering/SharedLayerTestsBase.cs:12, :15, :18). [Rubric §7 - Microservices Readiness] and [Rubric §9 - API & Contract Design]: Shared holds the DTOs, requests, integration events, and (in ADC) the six [ServiceContract] interfaces that cross a module boundary, so a dependency from Shared into Domain or Infrastructure would drag the module's internals along with its contract and make extraction impossible.
              • -
              -

              DuplicateTicketErrors, DynamicErrors, SharedCodeErrors, TicketErrors, TwoBranchTicketErrors, UnprefixedErrors

              +

              HandlerConventionTests

              -

              MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.ErrorCodeFixtures · (see per-type table) · Level 2 · class

              +

              MMCA.ADC.Architecture.Tests · MMCA.ADC.Architecture.Tests.Cqrs · MMCA.ADC.Architecture.Tests/Cqrs/HandlerConventionTests.cs:16 · Level 13 · class (public, sealed)

              -

              Six internal static classes in one file (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs), each holding one or two expression-bodied factory methods that construct an Error. Together they compile the error catalog the two catalog rules have to judge: well-formed codes, a cross-type collision, an unprefixed code, one code reused across two branches of a single type, a shared framework static, and a code built at run time.

              -
                -
              • Depends on - Error and its static factories NotFoundError, Conflict and Validation (ErrorCodeFixtures.cs:1). Nothing else.
              • -
              • Concept introduced - the error code as the module's public vocabulary, read out of IL. A client switches on Order.NotFound and a support ticket quotes it, so two modules both shipping Item.Invalid make that vocabulary ambiguous in a way that only surfaces in production (rule doc, MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.ErrorCatalog.cs:31-:37). Two rules police it. ErrorCodesAreUnique (:62) fails when the same literal code is constructed by more than one type, and the choice of "type" as the unit is deliberate: one error with two exits in the same class is not a collision, which keeps a normal two-branch handler out of the report (:45-:50). ErrorCodesUseAnAllowedPrefix (:102) fails on any literal code whose prefix a caller-supplied delegate rejects, so the convention stays the consumer's rather than the framework's (:92-:98). Both read the code out of the compiled IL: the scan looks for calls to any of nine Error factory names plus .ctor (:17-:29) and reads the first argument, and both deliberately skip the record copy constructor through a first-parameter-is-string check so error with { Source = ... } is not mistaken for a new code (:11-:15). Codes that are not literals cannot be judged statically, and the rules refuse to guess: they are listed as UNVERIFIABLE in the failure message so the reader knows the catalog has a blind spot (:51-:55). [Rubric §9 - API & Contract Design] assesses whether the published failure vocabulary is governed; [Rubric §15 - Best Practices & Code Quality] covers traceability, since a prefixed code alone says where it came from; [Rubric §14 - Testability] covers the fixtures.
              • -
              • Walkthrough - scope is the per-module Domain and Application assemblies only (:38-:44), which is why the self-test map registers this assembly as a module Application layer rather than a framework one.
              • -
              -
              - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              TypeFile:LineWhat it models
              TicketErrorsMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs:11The clean catalog: Tickets.NotFound via Error.NotFoundError (:13) and Tickets.AlreadyClosed via Error.Conflict (:15). Correctly prefixed, so it must pass the prefix rule, while being one half of the collision below.
              DuplicateTicketErrorsMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs:19Ships Tickets.NotFound a second time from a different type, with a different message (:21). The collision the uniqueness rule exists to catch, and the failure message must name both owning types.
              TwoBranchTicketErrorsMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs:28The near-miss: one method with a ternary that constructs Tickets.Invalid on both branches with different reasons (:30-:33). Two construction sites, one owning type, so it is not a collision and the rule must stay silent.
              UnprefixedErrorsMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs:37Error.Validation("SomethingBroke", ...) (:39), a code with no module prefix at all. The prefix rule must report it and name the owning type.
              DynamicErrorsMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs:46The blind spot: Error.Validation("Tickets." + entity, ...) (:48). The code argument is a concatenation rather than a literal, so the scan reports the site as UNVERIFIABLE rather than passing or failing it.
              SharedCodeErrorsMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs:52Error.NotFoundError("Error.NotFound", ...) (:54), reusing one of the generic statics on Error itself. The allowed-shared-codes list exempts it from both rules, which is what those generics exist for.
                -
              • Why they're built this way - the pairs are what make each rule discriminating rather than merely loud. TicketErrors and DuplicateTicketErrors are the collision; TwoBranchTicketErrors is the shape a naive "count the construction sites" implementation would wrongly flag; SharedCodeErrors proves the exemption is honored on the prefix rule as well as the uniqueness rule. Keeping every fixture internal static with expression-bodied members means the IL is minimal and the reported owner name is unambiguous. See ADR-015.
              • -
              • Where they're used - compiled into this assembly and scanned by ErrorCatalogFitnessTests through its FixtureModuleMap, which anchors the module Application layer on typeof(TicketErrors).Assembly (ErrorCatalogFitnessTests.cs:112).
              • +
              • What it is - the CQRS handler placement and composition guard: handlers and validators live in the Application layer, handlers do not inject other handlers, application services do not inject handlers, domain event handlers are sealed and live in Application, and application services respect a constructor-arity limit that ADC raises to ten (MMCA.ADC.Architecture.Tests/Cqrs/HandlerConventionTests.cs:18).
              • +
              • Depends on - HandlerConventionTestsBase and AdcArchitectureMap.
              • +
              • Walkthrough - two members. Map (:16) and MaxServiceConstructorParameters => 10 (:18), raising the base default of 8 (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Cqrs/HandlerConventionTestsBase.cs:12). Six facts are inherited (:15, :18, :21, :24, :27, :30). [Rubric §6 - CQRS & Event-Driven] assesses whether the command/query split is structural rather than nominal; a handler that injects another handler is the classic way that split quietly becomes a call graph. [Rubric §1 - SOLID] covers the arity half.
              • +
              • Why it's built this way - the <remarks> (:6-:13) records both the reason for the raise and the coupling it creates. The shared AuthenticationServiceBase constructor took on the refresh-session store and its options, and ADC's subclass passed both through alongside the IExternalLoginEmailVerifier its OAuth auto-link gate needs; the rationale lives in ConstructorDependencyCountTests, which sets the same ceiling. Both moved 9 to 10 on 2026-09-23 for AuthenticationService, when the options holder that bundled its two settings objects into one slot was deleted (:11-:13). The last sentence of the <remarks> is the operative one: the two guards must move together, or the stricter one turns the other into a comment.
              • +
              • Caveats / not-in-source - ADC therefore has two overlapping arity limits with the same value (10) but different scans (this one over application services generally, the sibling over *Service types in module Application assemblies). Whether they cover an identical type set is decided inside ArchitectureRules and is not determinable from these subclasses.

              StronglyTypedIdTests

              @@ -2376,6 +2325,17 @@

              SliceCohesionTests

            • Depends on - SliceCohesionTestsBase and AdcArchitectureMap. Map-only shape (ConcurrencyConventionTests).
            • Walkthrough - one member, Map (:10). Two inherited facts, Handlers_ShouldBeCoLocatedWith_TheirContracts and Validators_ShouldBeCoLocatedWith_TheirContracts (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Cqrs/SliceCohesionTestsBase.cs:15, :19). [Rubric §5 - Vertical Slice] assesses whether a feature is one navigable unit; the rule turns the folder convention into something the build can check, so the slice does not erode into a layer-per-type layout one refactoring at a time.
            +

            SoftDeleteEnforcementTests

            +
            +

            MMCA.ADC.Architecture.Tests · MMCA.ADC.Architecture.Tests.Domain · MMCA.ADC.Architecture.Tests/Domain/SoftDeleteEnforcementTests.cs:15 · Level 13 · class (public, sealed)

            +
            +
              +
            • What it is - the ADC soft-delete enforcement guard (ADR-005): ADC deletes by setting IsDeleted = true, so EF Core's row-erasing members are banned everywhere except a reviewed allowlist of purge types (MMCA.ADC.Architecture.Tests/Domain/SoftDeleteEnforcementTests.cs:3-:7).
            • +
            • Depends on - SoftDeleteEnforcementTestsBase and AdcArchitectureMap. Unlike the map-only instances it also overrides the base's allowlist hook.
            • +
            • Concept introduced - an allowlist that names types, not a namespace. Every allowlisted type lives in MMCA.Common, and no ADC module, service or host erases a row of its own; the list still names each framework type individually rather than exempting MMCA.Common wholesale, so a framework type that starts erasing rows in a later release fails this gate and gets reviewed (:9-:12). [Rubric §8 - Data Architecture] applies to the delete policy itself, and [Rubric §11 - Security] to the retention entries, where erasing is the privacy requirement rather than the defect.
            • +
            • Walkthrough - two members. Map (:17) supplies AdcArchitectureMap. AllowedHardDeleteTypes (:20-:56) overrides the base's empty default (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Domain/SoftDeleteEnforcementTestsBase.cs:27) with seven fully qualified type names, each with its reason in a comment (the base accepts full type names or namespace prefixes, SoftDeleteEnforcementTestsBase.cs:21-:26, and ADC uses full names only): EFRepository`2 as the framework's set-based delete escape hatch, whose one ADC caller (SessionScoringRunner) replaces derived AI scores before rescoring (:22-:26); OutboxCleanupService for outbox and inbox retention (:28-:31); AuditTrailCleanupJob for audit-trail retention (:33-:35); RefreshSessionCleanupService for refresh-session rows, which carry no IsDeleted flag and hold device data (:37-:41); InternalCommandCleanupService and InternalCommandAdministration for scheduled-command retention, on a timer and on demand (:43-:50); and EFPermissionGrantStore, because a revoked grant must erase its row (:52-:55). One inherited fact, HardDeletes_ShouldOnlyOccurIn_AllowedPurgeTypes, which hands the map and the list to ArchitectureRules .HardDeletesOnlyInAllowedTypes (SoftDeleteEnforcementTestsBase.cs:29-:31).
            • +
            • Caveats / not-in-source - the allowlist is matched by type name string, so a rename of any listed framework type turns this gate red until the entry is updated; that coupling is the intended review trigger, not a defect.
            • +

            SpecificationConventionTests

            MMCA.ADC.Architecture.Tests · MMCA.ADC.Architecture.Tests.Domain · MMCA.ADC.Architecture.Tests/Domain/SpecificationConventionTests.cs:8 · Level 13 · class (public, sealed)

            @@ -2394,7 +2354,7 @@

            StateManagementConventionTests

          • What it is - the Blazor state-ownership guard: the Identity, Conference, and Engagement UI assemblies carry no mutable static state, and stateful UI services stay scoped (MMCA.ADC.Architecture.Tests/Ui/StateManagementConventionTests.cs:3-:8).
          • Depends on - StateManagementConventionTestsBase and AdcArchitectureMap. Map-only shape (ConcurrencyConventionTests).
          • Concept introduced - why this is a correctness rule and not a style rule. Under Blazor Server every user gets a circuit inside one process, so a mutable static member is shared across every connected attendee, and a singleton-registered stateful service is the same defect wearing a DI hat: one user's selection becomes everyone's. The comment says exactly this (:6-:7). [Rubric §19 - State Management] assesses how client state is scoped and owned; [Rubric §11 - Security] is the sharper edge, since cross-circuit leakage of user state is a data exposure, not just a bug.
          • -
          • Walkthrough - one member, Map (:11). Two inherited facts, UiAssemblies_CarryNoMutableStaticState and UiProjects_RegisterStatefulServicesScoped (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Ui/StateManagementConventionTestsBase.cs:28, :66). The base's AllowedStaticMembers hook (:25) is left at its empty default, so ADC's module UIs have zero static-state allowances. Notification declares no Layer.Ui assembly in the map, so the scan covers the three module UIs only.
          • +
          • Walkthrough - one member, Map (:11). Two inherited facts, UiAssemblies_CarryNoMutableStaticState and UiProjects_RegisterStatefulServicesScoped (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Ui/StateManagementConventionTestsBase.cs:29, :66). The base's AllowedStaticMembers hook (:25) is left at its empty default, so ADC's module UIs have zero static-state allowances. Notification declares no Layer.Ui assembly in the map, so the scan covers the three module UIs only.

          UIArchitectureConventionTests

          @@ -2406,6 +2366,15 @@

          UIArchitectureConventionTests

        • Concept introduced - a size cap as a proxy for a structural property. Nothing can test "this component separates orchestration from presentation", but a code-behind that has grown past 400 lines has almost certainly stopped doing so. The comment records the payoff: the gate subsumes tech-debt item TD-13, because the oversized Conference dashboards were split to conform when it landed (:7-:8). That is the ratchet working in the other direction from RawQueryableConventionTests: instead of pinning the violations, the violations were fixed. [Rubric §18 - UI Architecture] assesses front-end composition and is the rule's own stated target.
        • Walkthrough - one member, Map (:12). Two inherited facts, CodeBehinds_StayWithinTheLineCap and RazorFiles_KeepInlineCodeBlocksSmall (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Ui/UIArchitectureConventionTestsBase.cs:44, :63). All four tuning hooks stay at their base defaults: MaxCodeBehindLines 400 (:22), MaxInlineCodeLines 120 (:29), MinimumCodeBehindFiles 1 (:35), and an empty ExcludedPathFragments (:41), so ADC excludes nothing from the scan.
        +

        SharedLayerTests

        +
        +

        MMCA.ADC.Architecture.Tests · MMCA.ADC.Architecture.Tests.Layering · MMCA.ADC.Architecture.Tests/Layering/SharedLayerTests.cs:3 · Level 13 · class (public, sealed)

        +
        +
          +
        • What it is - the guard on the Shared layer, the one layer other modules are allowed to reference: a module's Shared project must not depend on that module's own internal layers, must not reach sibling modules, and must stay free of EF Core.
        • +
        • Depends on - SharedLayerTestsBase and AdcArchitectureMap. Map-only shape (ConcurrencyConventionTests).
        • +
        • Walkthrough - one member, Map (:5). Three inherited facts (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Layering/SharedLayerTestsBase.cs:12, :15, :18). [Rubric §7 - Microservices Readiness] and [Rubric §9 - API & Contract Design]: Shared holds the DTOs, requests, integration events, and (in ADC) the six [ServiceContract] interfaces that cross a module boundary, so a dependency from Shared into Domain or Infrastructure would drag the module's internals along with its contract and make extraction impossible.
        • +

        ServiceModels

        MMCA.ADC.Architecture.Tests · MMCA.ADC.Architecture.Tests.Domain · MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Domain/DeleteBehaviorConventionTests.cs:70 · Level 14 · class (private, static, nested in DeleteBehaviorConventionTests)

        @@ -2441,7 +2410,18 @@

        AsyncClockReadingFixture

      • Depends on - nothing beyond the BCL: Task, Task.Yield(), and DateTimeOffset.UtcNow (:29-:30).
      • Concept introduced - why the clock-read rule has to follow a compiler-generated state machine, not just the method body as written. An async method with an await is rewritten by the compiler into a state machine type, so the actual DateTimeOffset.UtcNow read lives on a nested MoveNext rather than on StampAsync itself; a rule that only enumerates a type's own IL would miss it. ClockReadTestsBase must resolve that nested read back to the member that wrote the source, the same requirement a lambda body creates for LambdaClockReadingFixture, so ClockReadTests proves both cases side by side.
      • Walkthrough - one method, StampAsync() (:27-:31): await Task.Yield() (:29) forces the compiler to build the state machine, then return DateTimeOffset.UtcNow (:30) is the flagged read.
      • -
      • Where it's used - asserted by ClockReadTests.ReadsInsideLambdaAndAsyncBodies_AreFlaggedUnderTheirSourceMember, which requires the report contain "AsyncClockReadingFixture.StampAsync reads DateTimeOffset.UtcNow" (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/ClockReadTests.cs:40).
      • +
      • Where it's used - asserted by ClockReadTests.ReadsInsideLambdaAndAsyncBodies_AreFlaggedUnderTheirSourceMember, which requires the report contain "AsyncClockReadingFixture.StampAsync reads DateTimeOffset.UtcNow" (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/ClockReadTests.cs:46).
      • +
      +

      EngineHit

      +
      +

      MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Governance · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/DataSourceBranchingFitnessTests.cs:149 · Level 0 · record

      +
      +
        +
      • What it is - the one-line scan result of the data-source branching gate, private sealed record EngineHit(string File, int Line, string Text) nested inside DataSourceBranchingFitnessTests (DataSourceBranchingFitnessTests.cs:149). Its doc names the three fields: the file path relative to Source/, the 1-based line number, and the line text (:148).
      • +
      • Depends on - nothing beyond the BCL; three positional properties, no members of its own.
      • +
      • Concept introduced - why a text-scan fitness gate keeps each hit as a value, not a formatted string. Engine-specific behavior is meant to live behind IDataSourceEngine (ADR-130), and nothing in the compiler stops a new DataSource.Sqlite comparison or is SqliteDbContext check from growing back outside the strategies, so the gate scans Source/**/*.cs as text against a file-granular allow-list (class doc, :7-:16). Keeping the hit structured lets two opposite assertions share one scan: ConcreteEngineBranching_StaysInsideTheEngineStrategies filters hits whose File is not allow-listed (:73-:77), and AllowList_HasNoStaleEntries projects the same hits to a set of File values and fails on any allow-listed file that no longer produces one (:85-:89), so the allow-list shrinks with the code. [Rubric §14 - Testability] covers the gate.
      • +
      • Walkthrough - built only in ScanSource() (:112-:140): for every .cs file under the repo's Source root, excluding bin and obj (:114-:120), each line matching the EngineReference regex (:142-:146) becomes new EngineHit(relative, i + 1, lines[i].Trim()) with the path normalized to forward slashes (:128, :134). Before scanning, the method asserts more than 500 files were found so a wrong scan root cannot pass vacuously (:122-:123). The violating hits are rendered as Source/{File}:{Line}: {Text} and handed to ArchitectureAssert (:77-:79).
      • +
      • Where it's used - private to DataSourceBranchingFitnessTests (rolled up under per-project test rollup); not referenced outside the defining file.

      FatFixtureController

      @@ -2487,18 +2467,6 @@

      IBadgeGranter

    • Walkthrough - one member, GrantAsync(CancellationToken) (:66). It takes a token but returns nothing meaningful; the signature exists only to be callable.
    • Where it's used - implemented by BadgeGranter (:70) and injected into InterfaceDispatchSavingHandler (:83).
    -

    IFakeExportService

    -
    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Layering · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Layering/ModuleConformanceTestsBaseTests.cs:25 · Level 0 · interface

    -
    -
      -
    • What it is - a marker-only cross-module contract that exists purely as test data: it stands in for the kind of service one module exports and another module resolves, so the module-conformance base can be proven to reach a real DI container.
    • -
    • Depends on - nothing. The whole declaration is public interface IFakeExportService; (ModuleConformanceTestsBaseTests.cs:25), a body-less interface using the semicolon form, with a one-line doc above it (:24).
    • -
    • Concept introduced - the cross-module export contract, in miniature. Under the module system (IModule, ModuleLoader), a module that is switched off in ModulesSettings still has to leave behind stub registrations, otherwise every other module that resolves its exported interface fails to construct. IFakeExportService is the smallest possible stand-in for such an exported interface. [Rubric §14 - Testability] assesses whether a rule can be proven with focused inputs; a marker interface is the least amount of surface that still lets the fitness base observe a real ServiceDescriptor.
    • -
    • Walkthrough - no members. Its only role is to be the ServiceType that FakeDependentModule registers a stub against (:44) and that FakeDependentModuleConformanceTests looks up in the built collection (:74).
    • -
    • Why it's built this way - a real cross-module contract would drag a module's whole application surface into the framework's architecture-test assembly. A local marker keeps the fixture self-contained, which is the same discipline the rule library itself follows (it matches framework types by full name rather than referencing them).
    • -
    • Where it's used - implemented by DisabledFakeExportService and registered by FakeDependentModule.
    • -

    InjectedClockFixture

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.ClockReadFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ClockReadFixtures/ClockReadFixtures.cs:34 · Level 0 · class

    @@ -2506,7 +2474,7 @@

    InjectedClockFixture

    • What it is - a compiled-in fixture behind ClockReadTestsBase (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Domain/ClockReadTestsBase.cs:15) that takes a constructor-injected TimeProvider timeProvider and calls timeProvider.GetUtcNow() (ClockReadFixtures.cs:34-:37): the sanctioned shape the clock-read rule asks for.
    • Depends on - System.TimeProvider (BCL) only.
    • -
    • Concept introduced - fitness rules that read IL instead of source. ArchitectureRules.DomainAndApplicationDoNotReadTheClock (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.ClockReads.cs:55) loads each Domain/Application assembly with Mono.Cecil and scans every method body, lambdas and async state machines included since the compiler turns both into nested types, for a call to one of four ambient-clock getters: DateTime.UtcNow, DateTime.Now, DateTimeOffset.UtcNow, DateTimeOffset.Now (:20-:26, :38-:41). A clock read buried in a business rule cannot be driven by a test, so every expiry or "is it overdue" branch it guards is either untested or tested by sleeping (:32-:35). This fixture proves the rule stays silent when time is taken as an input rather than read from the ambient clock, the complement to LambdaClockReadingFixture and OffsetNowReadingFixture, which prove the rule fires. [Rubric §34 - Architecture Governance & Documentation] covers exactly this kind of self-verifying fitness function: the rule's own test fixtures are compiled, not asserted against a description of the rule.
    • +
    • Concept introduced - fitness rules that read IL instead of source. ArchitectureRules.DomainAndApplicationDoNotReadTheClock (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.ClockReads.cs:56) loads each Domain/Application assembly with Mono.Cecil and scans every method body, lambdas and async state machines included since the compiler turns both into nested types, for a call to one of four ambient-clock getters: DateTime.UtcNow, DateTime.Now, DateTimeOffset.UtcNow, DateTimeOffset.Now (:20-:26, :38-:41). A clock read buried in a business rule cannot be driven by a test, so every expiry or "is it overdue" branch it guards is either untested or tested by sleeping (:32-:35). This fixture proves the rule stays silent when time is taken as an input rather than read from the ambient clock, the complement to LambdaClockReadingFixture and OffsetNowReadingFixture, which prove the rule fires. [Rubric §34 - Architecture Governance & Documentation] covers exactly this kind of self-verifying fitness function: the rule's own test fixtures are compiled, not asserted against a description of the rule.
    • Walkthrough - one method, Stamp(), returning timeProvider.GetUtcNow() (:36). Never executed; only its IL is read.
    • Why it's built this way - the file's own class-level comment states the rule reads IL, so its behavior must be pinned against real method bodies of each shape rather than against a reading of the rule (ClockReadFixtures.cs:3-:4).
    • Where it's used - exercised by ClockReadTests (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/ClockReadTests.cs), which drives ClockReadTestsBase.DomainAndApplicationDoNotReadTheClock.
    • @@ -2518,7 +2486,7 @@

      LambdaClockReadingFixture

      • What it is - a compiled-in offender fixture whose Clock() method returns a Func<DateTime> that reads DateTime.UtcNow from inside a lambda body (ClockReadFixtures.cs:18-:21); the compiler moves that lambda into a nested type, so the fixture proves the clock-read rule (see InjectedClockFixture) still attributes the violation back to the declaring member.
      • Depends on - nothing beyond System.DateTime (BCL).
      • -
      • Concept introduced - cross-reference: the IL-scanning approach is introduced under InjectedClockFixture. This fixture is the "lambda closure" edge case: ClockReadsIn (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.ClockReads.cs:95) resolves a compiler-generated nested type back to the source member that declared it, so a violation inside Clock()'s lambda is still reported and allowlisted against Clock, not against an anonymous internal type name.
      • +
      • Concept introduced - cross-reference: the IL-scanning approach is introduced under InjectedClockFixture. This fixture is the "lambda closure" edge case: ClockReadsIn (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.ClockReads.cs:96) resolves a compiler-generated nested type back to the source member that declared it, so a violation inside Clock()'s lambda is still reported and allowlisted against Clock, not against an anonymous internal type name.
      • Walkthrough - one method, Clock(), returning () => DateTime.UtcNow (:20).
      • Why it's built this way - proves the rule's owner-attribution works for the lambda case, not just a plain method body.
      • Where it's used - exercised by ClockReadTests (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/ClockReadTests.cs).
      • @@ -2547,6 +2515,18 @@

        OffsetNowReadingFixture

      • Why it's built this way - the baseline offender the rule must catch before the harder lambda and async-state-machine variants are proven.
      • Where it's used - exercised by ClockReadTests (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/ClockReadTests.cs).
      +

      PasswordProbe

      +
      +

      MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Ui · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Ui/PasswordRuleParityTests.cs:54 · Level 0 · record

      +
      +
        +
      • What it is - private sealed record PasswordProbe(string Password); nested inside PasswordRuleParityTests (PasswordRuleParityTests.cs:54), documented as "the smallest model the server rule can be bound to" (:53). It gives both the client attribute and the server validator a real object to evaluate one password against.
      • +
      • Depends on - nothing; a positional record with one string property.
      • +
      • Concept introduced - client/server validation parity. The Register form validates a password with PasswordComplexityAttribute while the API validates it with StrongPasswordRules<T>; if the two disagree, the form either rejects a password the API would accept or lets one through that the API refuses after a round trip (PasswordRuleParityTests.cs:7-:12). The test lives in the architecture test project because it is the one project referencing both sides: the UI test project references MMCA.Common.UI only, and UI may depend on Shared alone (:14-:15). [Rubric §24 - Forms, Validation & UX Safety] assesses whether client-side validation matches the server's verdict; this probe is what lets a single theory compare the two verdicts directly rather than trusting they were written alike.
      • +
      • Walkthrough - the theory ClientPasswordComplexity_MatchesServerStrongPasswordRules builds one probe per row (:38), then computes the client verdict the way an EditForm does, calling GetValidationResult with a ValidationContext(probe) whose MemberName is nameof(PasswordProbe.Password) (:41-:43), and the server verdict by binding new StrongPasswordRules<PasswordProbe>(static p => p.Password) and calling Validate(probe).IsValid (:44-:46). The two must be equal (:48-:50). The twelve InlineData rows cover ASCII valid and invalid cases plus accented, umlaut, CJK and full-width-digit inputs (:24-:35), written as escapes so the source stays ASCII (:16-:18).
      • +
      • Why it's built this way - the server rule is a FluentValidation validator over a model, so it needs a type with a property selector to bind to; a private nested record is the least surface that satisfies that without borrowing a real request DTO.
      • +
      • Where it's used - only inside PasswordRuleParityTests (no usages outside the defining file).
      • +

      ReadRepositoryFixtureCommand

      MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.ReadRepositoryFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ReadRepositoryFixtures/ReadRepositoryFixtures.cs:14 · Level 0 · record

      @@ -2571,17 +2551,17 @@

      ReadRepositoryFixtureQuery

    • Why it's built this way - one query shared by both handlers keeps the fixture pair a controlled minimal-difference comparison: the only variable between them is which repository accessor is called.
    • Where it's used - the query type of IQueryHandler<ReadRepositoryFixtureQuery, Result<int>> implemented by both WriteRepositoryQueryHandlerFixture and ReadRepositoryQueryHandlerFixture (ReadRepositoryFixtures.cs:16-:37), which close over ReadRepositoryFixtureAggregate (:10) as the repository's entity type argument.
    -

    RebuildFixtureProjectionCommand

    +

    IFakeExportService

    -

    MMCA.Common.Architecture.Tests - MMCA.Common.Architecture.Tests.ConstructorDependencyFixtures - MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ConstructorDependencyFixtures/ConstructorDependencyFixtures.cs:30 - Level 0 - record

    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Layering · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Layering/ModuleConformanceTestsBaseTests.cs:25 · Level 0 · interface

      -
    • What it is - internal sealed record RebuildFixtureProjectionCommand; (ConstructorDependencyFixtures.cs:30), a payload-free marker command in the compiled injection-shape fixtures for ConstructorDependencyCountTestsBaseTests (:763 in the group's type map).
    • -
    • Depends on - nothing; the record declares no members.
    • -
    • Concept introduced - why a fitness fixture command can be deliberately empty. The class-level doc comment on the file explains the fixtures exist to give ConstructorDependencyCountTestsBaseTests one fat and one thin top-level member of each population the ceiling covers, API controllers, command handlers and query handlers (:8-:9). Types are TOP-LEVEL on purpose because the gate skips nested types, and every injected collaborator is read back so none is an unread primary-constructor parameter (:10-:12). This command is marked payload-free specifically so the command-validator coverage rule skips it: pointing a coverage map at this assembly still counts the same five data-carrying commands it counted before the fixture file existed (:12-:14, :29).
    • -
    • Walkthrough - no fields, no properties, nothing to read back; the type exists purely to give RebuildFixtureProjectionHandler a command generic argument to close over.
    • -
    • Why it's built this way - a command with a payload would add itself to the validator-coverage count and skew an unrelated fitness assertion; a marker record sidesteps that without weakening the constructor-dependency fixture it exists to support.
    • -
    • Where it's used - the command type of ICommandHandler<RebuildFixtureProjectionCommand, Result> implemented by RebuildFixtureProjectionHandler (:33-:42), the "three collaborators" (fat) member of the command-handler population.
    • +
    • What it is - a marker-only cross-module contract that exists purely as test data: it stands in for the kind of service one module exports and another module resolves, so the module-conformance base can be proven to reach a real DI container.
    • +
    • Depends on - nothing. The whole declaration is public interface IFakeExportService; (ModuleConformanceTestsBaseTests.cs:25), a body-less interface using the semicolon form, with a one-line doc above it (:24).
    • +
    • Concept introduced - the cross-module export contract, in miniature. Under the module system (IModule, ModuleLoader), a module that is switched off in ModulesSettings still has to leave behind stub registrations, otherwise every other module that resolves its exported interface fails to construct. IFakeExportService is the smallest possible stand-in for such an exported interface. [Rubric §14 - Testability] assesses whether a rule can be proven with focused inputs; a marker interface is the least amount of surface that still lets the fitness base observe a real ServiceDescriptor.
    • +
    • Walkthrough - no members. Its only role is to be the ServiceType that FakeDependentModule registers a stub against (:44) and that FakeDependentModuleConformanceTests looks up in the built collection (:74).
    • +
    • Why it's built this way - a real cross-module contract would drag a module's whole application surface into the framework's architecture-test assembly. A local marker keeps the fixture self-contained, which is the same discipline the rule library itself follows (it matches framework types by full name rather than referencing them).
    • +
    • Where it's used - implemented by DisabledFakeExportService and registered by FakeDependentModule.

    ThinFixtureController

    @@ -2615,7 +2595,7 @@

    TwoMemberClockFixture

  • Depends on - nothing beyond System.DateTime; a leaf fixture compiled purely so the clock-read rule has real IL to scan.
  • Concept - the member-level allowlist case for the clock-read rule: with two otherwise-identical members reading the same forbidden API, an allowlist entry naming only TwoMemberClockFixture.Exempted must silence that one member and leave StillReported reported, proving the allowlist is checked per member rather than per type.
  • Walkthrough - Exempted() and StillReported() are single-expression methods, => DateTime.UtcNow;, differing only in name.
  • -
  • Where it's used - MemberAllowlistEntry_ExemptsOnlyThatMember (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/ClockReadTests.cs:51-:57) allowlists {FixtureNamespace}.{nameof(TwoMemberClockFixture)}.Exempted and asserts the report excludes Exempted (:55) while still containing StillReported (:56).
  • +
  • Where it's used - MemberAllowlistEntry_ExemptsOnlyThatMember (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/ClockReadTests.cs:57-:57) allowlists {FixtureNamespace}.{nameof(TwoMemberClockFixture)}.Exempted and asserts the report excludes Exempted (:55) while still containing StillReported (:56).
  • UtcNowReadingFixture

    @@ -2628,38 +2608,50 @@

    UtcNowReadingFixture

  • Walkthrough - public DateTime Stamp() => DateTime.UtcNow; (:8), the whole type.
  • Where it's used - DirectReadsOfEitherClockType_AreFlagged (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/ClockReadTests.cs:22-:29) asserts the report contains UtcNowReadingFixture.Stamp reads DateTime.UtcNow (:27).
  • -

    FixtureDomainEvent

    +

    TodayReadingFixture

    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.DomainEventSaveFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/DomainEventSaveFixtures/DomainEventSaveFixtures.cs:13 · Level 1 · record

    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.ClockReadFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ClockReadFixtures/ClockReadFixtures.cs:48 · Level 0 · class

      -
    • What it is - the single throwaway domain event every handler fixture in DomainEventSaveFixtures is generic over. It carries no meaning; it exists so the four fixture handlers can implement a real, closed IDomainEventHandler<T>.
    • -
    • Depends on - IDomainEvent (public sealed record FixtureDomainEvent(DateTime DateOccurred, Guid MessageId) : IDomainEvent;, DomainEventSaveFixtures.cs:13, with the interface import at :3).
    • -
    • Concept - cross-references the handler-save concept introduced by IBadgeGranter. Its own contribution is the closure: the rule finds handlers by asking CallGraphIndex whether a type implements the open interface MMCA.Common.Application.Interfaces.IDomainEventHandler1 matched by full name with Cecil's arity marker (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Cqrs/ArchitectureRules.DomainEventHandlerSaves.cs:8-:9,:96`), so any closed instantiation works and one event type is enough for the whole fixture set.
    • -
    • Walkthrough - two positional members, DateTime DateOccurred and Guid MessageId, which are the two members IDomainEvent requires. It is the only public type in the file; the handlers around it are all internal.
    • -
    • Where it's used - the type argument of DirectSavingHandler, TransitiveSavingHandler, InterfaceDispatchSavingHandler and InnocentHandler, and the assembly anchor of DomainEventHandlerSaveFitnessTests's FixtureAssemblyMap (DomainEventHandlerSaveFitnessTests.cs:116).
    • +
    • What it is - internal sealed class TodayReadingFixture (:48-:51) with one member, Stamp(), that returns DateOnly.FromDateTime(DateTime.Today) (:50). Its doc comment calls DateTime.Today "the fifth ambient-clock getter" (:47).
    • +
    • Depends on - nothing beyond System.DateTime and System.DateOnly.
    • +
    • Concept - widens the clock-read rule's coverage past UtcNow/Now: DateTime.Today reads the ambient local clock just as DateTime.Now does, so a member that only wants a date still has to be reported. Wrapping the read in DateOnly.FromDateTime also shows the rule keys on the getter call itself, not on the member's return type.
    • +
    • Walkthrough - public DateOnly Stamp() => DateOnly.FromDateTime(DateTime.Today); (:50), the whole type.
    • +
    • Where it's used - DateTimeToday_IsFlagged (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/ClockReadTests.cs:32-:35) asserts the report contains {nameof(TodayReadingFixture)}.Stamp reads DateTime.Today (:34), in the ClockReadTests suite.
    -

    DisabledFakeExportService

    +

    RebuildFixtureProjectionCommand

    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Layering · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Layering/ModuleConformanceTestsBaseTests.cs:28 · Level 1 · class

    +

    MMCA.Common.Architecture.Tests - MMCA.Common.Architecture.Tests.ConstructorDependencyFixtures - MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ConstructorDependencyFixtures/ConstructorDependencyFixtures.cs:30 - Level 0 - record

      -
    • What it is - the stub implementation a disabled FakeDependentModule leaves behind, so the fixture models the real "module off, contract still resolvable" behavior rather than describing it.
    • -
    • Depends on - IFakeExportService (public sealed class DisabledFakeExportService : IFakeExportService;, ModuleConformanceTestsBaseTests.cs:28).
    • -
    • Concept - cross-references the disabled-stub concept introduced by IFakeExportService. The pairing matters: a stub type distinct from the real implementation is what lets an assertion prove which implementation the container holds, not merely that the service type is registered.
    • -
    • Walkthrough - no members; a body-less sealed class with its doc comment at :27. It is the exact type FakeDependentModuleConformanceTests asserts on via descriptor.ImplementationType.Should().Be<DisabledFakeExportService>() (:76).
    • -
    • Where it's used - registered as a singleton by FakeDependentModule.RegisterDisabledStubs (:43-:44).
    • +
    • What it is - internal sealed record RebuildFixtureProjectionCommand; (ConstructorDependencyFixtures.cs:30), a payload-free marker command in the compiled injection-shape fixtures for ConstructorDependencyCountTestsBaseTests (:763 in the group's type map).
    • +
    • Depends on - nothing; the record declares no members.
    • +
    • Concept introduced - why a fitness fixture command can be deliberately empty. The class-level doc comment on the file explains the fixtures exist to give ConstructorDependencyCountTestsBaseTests one fat and one thin top-level member of each population the ceiling covers, API controllers, command handlers and query handlers (:8-:9). Types are TOP-LEVEL on purpose because the gate skips nested types, and every injected collaborator is read back so none is an unread primary-constructor parameter (:10-:12). This command is marked payload-free specifically so the command-validator coverage rule skips it: pointing a coverage map at this assembly still counts the same five data-carrying commands it counted before the fixture file existed (:12-:14, :29).
    • +
    • Walkthrough - no fields, no properties, nothing to read back; the type exists purely to give RebuildFixtureProjectionHandler a command generic argument to close over.
    • +
    • Why it's built this way - a command with a payload would add itself to the validator-coverage count and skew an unrelated fitness assertion; a marker record sidesteps that without weakening the constructor-dependency fixture it exists to support.
    • +
    • Where it's used - the command type of ICommandHandler<RebuildFixtureProjectionCommand, Result> implemented by RebuildFixtureProjectionHandler (:33-:42), the "three collaborators" (fat) member of the command-handler population.
    -

    CustomExceptionThrowingFixture

    +

    ResourceEntry

    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.DomainThrowFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/DomainThrowFixtures/DomainThrowFixtures.cs:67 · Level 1 · class

    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Ui · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Ui/PluralSentenceResourceTests.cs:70 · Level 0 · record

      -
    • What it is - a one-method static fixture that throws the file's custom domain exception, so the domain-throw rule can be shown to judge by constructed type rather than by a list of framework exception names.
    • -
    • Depends on - TicketDomainException (DomainThrowFixtures.cs:69).
    • -
    • Concept - cross-references the domain-throw concept introduced by its sibling fixtures. What this one adds is the discrimination that matters most in practice: teams that adopt the Result pattern often keep a DomainException hierarchy as a compromise, and a rule built around a deny-list of BCL exception names would wave it through. The rule instead allows exactly three types and reports everything else (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.DomainThrows.cs:16-:21,:132-:135), so a custom exception is caught for what it is. The fixture doc puts it in one sentence: the same defect wearing a domain name (:48).
    • -
    • Walkthrough - the whole type is internal static class CustomExceptionThrowingFixture (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/DomainThrowFixtures/DomainThrowFixtures.cs:67) with one member, internal static void Reject() => throw new TicketDomainException("The ticket was rejected"); (:69). The newobj immediately precedes the throw, so ConstructedExceptionType names it and the site is a hard violation rather than an UNVERIFIABLE.
    • -
    • Where it's used - the subject of CustomDomainException_IsAlsoFlagged (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/DomainThrowFitnessTests.cs:48-:57), and the residual offender the allowlist facts assert is still reported when only its sibling is exempted (:114-:126).
    • +
    • What it is - private sealed record ResourceEntry(string Key, string Value); (:70), nested inside PluralSentenceResourceTests and documented as "one resource entry: its key and its value" (:69).
    • +
    • Depends on - nothing; two positional string members.
    • +
    • Concept - the unit of input for the fixed-plural-sentence detector. PluralSentenceResourceTests fails on a .resx value that renders a count inside a fixed plural sentence ("{0} recipients", "{0} item(s)"), which reads wrongly for a count of one; count-sensitive messages are expected to declare .One / .Other siblings resolved through the plural localizer extension (ADR-027) (:6-:14). Flattening every resource to a (Key, Value) pair lets the same detector run over real resource files and over hand-written inline cases.
    • +
    • Walkthrough - ReadEntries projects each <data> element of a .resx file into new ResourceEntry(name, value), defaulting a missing attribute or element to string.Empty (:72-:77). FixedPluralSentences(IReadOnlyList<ResourceEntry>) collects every key, skips a .Other key and the base key of a pair that declares a .Other sibling (:87-:90), then reports each value where a format placeholder is followed by a plural-marked word (:91-:94).
    • +
    • Where it's used - only inside the defining file: built by ReadEntries for the repository scan in FrameworkResources_HaveNoCountInAFixedPluralSentence (:18-:39), and constructed inline (new(key, value)) by the detector theories Detector_Flags_ACountInAFixedPluralSentence (:46-:48), Detector_Accepts_AValueThatIsNotAFixedPluralSentence (:56-:58) and Detector_Accepts_TheBaseKeyOfAPluralPair_AsItsFallback (:61-:67). PluralSentenceResourceTests itself is rolled up per project.
    • +
    +

    SwitchExpressionFixture

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.DomainThrowFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/DomainThrowFixtures/DomainThrowFixtures.cs:115 · Level 0 · class

    +
    +
      +
    • What it is - internal static class SwitchExpressionFixture (:115-:120) with one method, Label(int n), whose body is a switch expression with arms for 1 and 2 and no discard arm (:118).
    • +
    • Depends on - nothing beyond the compiler: the #pragma warning disable CS8509, IDE0072 around the method (:117, :119) is what lets an incomplete switch expression compile at all.
    • +
    • Concept - a false-positive guard for the domain-throw rule. For a switch expression with no discard arm the compiler writes its own default-arm throw of SwitchExpressionException into the method; that throw is not the developer's, so the rule must stay silent about it (doc comment, :110-:114). Without this fixture an IL-level throw scan could pass every other case and still flag every non-exhaustive switch in a domain assembly.
    • +
    • Walkthrough - internal static string Label(int n) => n switch { 1 => "one", 2 => "two" }; (:118), the whole type.
    • +
    • Where it's used - CompilerSwitchExpressionThrow_IsNotFlagged (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/DomainThrowFitnessTests.cs:71-:79) runs ArchitectureRules.DomainThrowsOnlyArgumentGuards over the fixture assembly (:73) and asserts the failure message does not contain SwitchExpressionException (:76-:78), in the DomainThrowFitnessTests suite; the rule body lives in ArchitectureRules.

    DbSetRemovingFixture, ExecuteDeletingFixture, SoftDeletingFixture

    @@ -2732,6 +2724,25 @@

    InheritingFixtureController

  • Walkthrough - no members; the declaration ends at its semicolon (:94). Being concrete puts it in the scan (IsController walks the base chain, AnonymousEndpointTestsBase.cs:163-:112), and the DeclaredOnly filter is what keeps it silent: it declares no methods of its own, so AnonymousEndpointsOf yields nothing for it.
  • Where it's used - the subject of Base_DoesNotReport_AnInheritedAttributeOnTheDerivedController (AnonymousEndpointTestsBaseTests.cs:61-:71), which reads ConformantTests's scan output and asserts it does not contain {InheritingFixtureController.FullName}.InheritedAnonymousAsync (:69-:70). The inline comment there names the consequence being prevented (:64-:66).
  • +

    EditorRequiredParameterConventionTests

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Ui · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Ui/EditorRequiredParameterConventionTests.cs:26 · Level 1 · class

    +
    +
      +
    • What it is - a framework-only component-contract gate (rubric section 18, per the class doc at :5-:9): every public [Parameter] on a public MMCA.Common.UI component whose type is a non-nullable reference type must carry [EditorRequired], so the Razor compiler flags a call site that omits it (RZ2012) instead of the component meeting null at render time.
    • +
    • Depends on - UISharedAssemblyReference (the reflection anchor for the shared UI assembly, EditorRequiredParameterConventionTests.cs:104), plus externals: ASP.NET Core IComponent, ParameterAttribute, EditorRequiredAttribute, CascadingParameterAttribute, SupplyParameterFromQueryAttribute, RenderFragment and RenderFragment<T> (:1, :105-:109, :119-:120), System.Reflection.NullabilityInfoContext (:72, :125), xUnit [Fact] and AwesomeAssertions.
    • +
    • Concept introduced - nullability annotations as a component contract. A non-nullable reference parameter is a promise that the caller supplies a value; [EditorRequired] is what turns that promise into a compile-time diagnostic at the call site. Reflection reads the nullability the compiler emitted, so the gate can find every parameter whose type says "never null" but whose attributes do not ask the caller for it. What reflection cannot see is a property initializer, so a parameter that is genuinely optional because its initializer supplies a real default is listed by name with the default it relies on; an initializer that only guards against null (string.Empty, []) is not a default and is not listed (class doc, :17-:23). [Rubric §18 - UI Architecture] is the category the class doc names; [Rubric §14 - Testability] applies because the gate also proves its own scan and allow-list stay live.
    • +
    • Walkthrough - one private set and three [Fact]s.
        +
      • ParametersWithInitializerDefaults (:31-:62) is an ordinal HashSet<string> of ten Component.Property keys, each preceded by a comment naming its default: ApiFileDownloadButton.ContentType ("application/octet-stream"), ApiFileDownloadButton.HttpClientName ("APIClient"), ApiFileDownloadButton.Icon, ClickableCard.Class, EmptyState.Icon, ErrorSummary.Class, MmcaThemeProviders.Theme (MMCATheme.Instance), the EmptyIcon of MobileCardList1andMobileInfiniteScrollList1, and UserAdminList1.AssignableRoles` (an empty list). Generic components appear under their reflection names with the arity marker.
      • +
      • Components_AreDiscovered_GateIsNotVacuous (:64-:67) asserts the scan finds at least one parameter, so a drifted anchor cannot let the gate pass having scanned nothing.
      • +
      • NonNullableReferenceParameters_ShouldBe_EditorRequired (:69-:86) keeps every discovered parameter that is a non-nullable reference type and lacks [EditorRequired] (read with inherit: false), keys it as DeclaringType.Name.PropertyName, drops the allow-listed keys, orders the rest ordinally and asserts the list is empty; the failure message names the three remedies (add [EditorRequired], make it nullable, or list it with its initializer default) and every offender.
      • +
      • InitializerDefaultAllowList_HasNoStaleEntries (:88-:97) asserts every allow-list key still names a live parameter, since an entry for a parameter that no longer exists hides nothing.
      • +
      • Two private helpers. ComponentParameters (:103-:109) takes the public classes in the shared UI assembly that implement IComponent, their public instance properties declared on the type itself (BindingFlags.DeclaredOnly), and keeps those marked [Parameter] but not [CascadingParameter] or [SupplyParameterFromQuery]. IsNonNullableReference (:111-:126) excludes value types, generic parameters and RenderFragment / RenderFragment<T>, then returns whether NullabilityInfoContext.Create(property).WriteState is NotNull.
      • +
      +
    • +
    • Why it's built this way - the scope exclusions are each a ruling in the class doc (:10-:15): value types cannot be null; an absent RenderFragment or EventCallback legitimately means "nothing to render / nobody listening" (EventCallback is a struct, so the value-type test already drops it); an unconstrained generic parameter's nullability belongs to the caller's type argument; cascading and query-supplied members are supplied by the framework, not the caller. The doc also names two initializer guards it refuses to exempt, PageHeader.Title (an empty level-one heading) and MobileCardList.Items (the list's data), as gaps rather than exemptions (:22-:23). The stale-entry fact is what keeps the allow-list from outliving the components it excuses.
    • +
    • Where it's used - an independent class in the MMCA.Common.Architecture.Tests suite with no usages outside its defining file. It guards only the framework's own shared UI assembly, so it has no Store or ADC counterpart.
    • +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Ui · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Ui/NavigationContractTests.cs:19 · Level 1 · class

    @@ -2789,7 +2800,7 @@

    ObservabilityConventionTestsBaseT
  • What it is - a cross-assembly regression guard for the shared SLO alert-to-runbook pairing rule. It subclasses ObservabilityConventionTestsBase from an assembly other than the one the base ships in, re-points it at a fixture IaC pair embedded in this test project, and asserts the base resolves its manifest resources from the derived type's assembly.
  • Depends on - ObservabilityConventionTestsBase (public sealed class ObservabilityConventionTestsBaseTests : ObservabilityConventionTestsBase, ObservabilityConventionTestsBaseTests.cs:14) and the two fixture resources embedded by the csproj under the logical names fixtures.observability-main.bicep and fixtures.observability-OPERATIONS.md (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/MMCA.Common.Architecture.Tests.csproj:17-:22). Externals: System.Reflection.Assembly, xUnit [Fact], AwesomeAssertions.
  • Concept introduced - proving a shipped base class's defaults from outside its own assembly. The rule library ships as the MMCA.Common.Testing.Architecture package (ArchitectureRules plus its *TestsBase family), so a base can carry a default that is only ever wrong for a consumer: ResourceAssembly => GetType().Assembly (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/ObservabilityConventionTestsBase.cs:51) must bind to the subclass's assembly, because the base's own assembly never contains a consumer's bicep. Testing that default inside the base's own assembly would prove nothing (both assemblies would be the same one), so the guard is deliberately located here, one assembly away. [Rubric §13 - Observability & Operability] assesses whether alerts stay operable, and the rule this class exercises is the one that keeps every provisioned SLO alert paired with a severity-correct runbook section. [Rubric §14 - Testability] applies because this is a meta-test that keeps a shared gate honest, and [Rubric §33 - Developer Experience] because the alternative failure mode is a break that surfaces only in the first downstream repo to adopt the base.
  • -
  • Walkthrough - two property overrides re-point the base's resource names at the fixtures: BicepResource => "fixtures.observability-main.bicep" (:16) and RunbookResource => "fixtures.observability-OPERATIONS.md" (:18), replacing the base defaults infra.main.bicep and infra.OPERATIONS.md (ObservabilityConventionTestsBase.cs:42,:45). One own [Fact], ResourceAssembly_DefaultsToTheDerivedTypesAssembly (:24-:29), asserts the resolved ResourceAssembly is the same object as this class's assembly and is not the base's assembly. Inheritance supplies the rest: the base's three [Fact]s (ObservabilityConventionTestsBase.cs:53,:63,:91) run against the fixture pair, so the whole discovery-and-pairing path is exercised across the assembly boundary. The fixture bicep declares exactly three well-formed specs plus the two parse anchors the base looks for, var sloAlertSpecs and resource sloAlerts (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Fixtures/observability-main.bicep:6-:25), which meets the base's default MinimumAlertSpecs => 3 (ObservabilityConventionTestsBase.cs:39); the fixture runbook carries a matching ### fixture-alert-<key> (sev N) heading per spec (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Fixtures/observability-OPERATIONS.md:9-:19), so both pairing directions and the severity check pass.
  • +
  • Walkthrough - two property overrides re-point the base's resource names at the fixtures: BicepResource => "fixtures.observability-main.bicep" (:16) and RunbookResource => "fixtures.observability-OPERATIONS.md" (:18), replacing the base defaults infra.main.bicep and infra.OPERATIONS.md (ObservabilityConventionTestsBase.cs:42,:45). One own [Fact], ResourceAssembly_DefaultsToTheDerivedTypesAssembly (:24-:29), asserts the resolved ResourceAssembly is the same object as this class's assembly and is not the base's assembly. Inheritance supplies the rest: the base's three [Fact]s (ObservabilityConventionTestsBase.cs:79,:63,:91) run against the fixture pair, so the whole discovery-and-pairing path is exercised across the assembly boundary. The fixture bicep declares exactly three well-formed specs plus the two parse anchors the base looks for, var sloAlertSpecs and resource sloAlerts (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Fixtures/observability-main.bicep:6-:25), which meets the base's default MinimumAlertSpecs => 3 (ObservabilityConventionTestsBase.cs:39); the fixture runbook carries a matching ### fixture-alert-<key> (sev N) heading per spec (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Fixtures/observability-OPERATIONS.md:9-:19), so both pairing directions and the severity check pass.
  • Why it's built this way - the class doc records the exact regression it exists for (:5-:13): resolving against the base's own assembly instead of the subclass's is a silent break, since the framework's CI would stay green and only the first adopting consumer would fail. Keeping the guard in a different assembly is the only way to make that difference observable. It is the packaging discipline ADR-058 describes for shipped conformance suites, applied to a fitness base; the rule it guards implements the alerting side of ADR-041.
  • Where it's used - an independent class in the Common architecture suite. The real adopters are ObservabilityConventionTests in MMCA.ADC and MMCA.Store (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Governance/ObservabilityConventionTests.cs:7, MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/ObservabilityConventionTests.cs:7), which subclass the same base with no overrides at all and rely entirely on the default this class pins.
  • Caveats / not-in-source - the fixture bicep is deliberately minimal (its own header comment says so, observability-main.bicep:1-:4): it proves the base's parse-and-pair logic, not that any real consumer template is well formed. The consumers' own subclasses do that against their real infra/ files.
  • @@ -2827,17 +2838,6 @@

    ReopenTicketCommand, UpdateTick
  • Why they're built this way - the half-bridge is the failure mode a validation-coverage rule is most likely to miss, because the registration exists and only the resolution is empty. Compiling both halves side by side is what turns that from a code-review concern into an assertion.
  • Where they're used - named by nameof in CommandCoveredThroughTheRequestBridge_IsCovered and BridgeWithNoRequestValidator_IsNotCoverage (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/CommandValidatorCoverageFitnessTests.cs:44-:64), and handled by ReopenTicketHandler and UpdateTicketHandler.
  • -

    UpdateTicketRequestValidator

    -
    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.CommandValidatorFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/CommandValidatorFixtures/CommandValidatorFixtures.cs:33 · Level 1 · class

    -
    -
      -
    • What it is - the validator that makes ReopenTicketCommand, UpdateTicketCommand's bridge half a real hit: an AbstractValidator<UpdateTicketRequest> for the request UpdateTicketCommand wraps, not for the command itself.
    • -
    • Depends on - FluentValidation's AbstractValidator<T> (:1,:33) and UpdateTicketRequest (:28), the record it validates.
    • -
    • Concept introduced - the request bridge only counts if a validator actually resolves for the wrapped request type. CommandRequestValidator reaches the command's request through ICommandWithRequest<out TRequest> and asks whether ValidatedTypes contains it; this class is what makes that lookup non-empty for UpdateTicketRequest, so UpdateTicketCommand reads as covered even though it declares no validator of its own. ReopenTicketCommand is the same shape with no validator at all, and the pair only proves the rule because they differ in exactly this one respect.
    • -
    • Walkthrough - one rule, RuleFor(r => r.Title).NotEmpty() (:35), applied through the constructor.
    • -
    • Where it's used - resolved by name in CommandCoveredThroughTheRequestBridge_IsCovered (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/CommandValidatorCoverageFitnessTests.cs:44-:53), which asserts ValidatedTypes contains UpdateTicketRequest and that UpdateTicketCommand is reported covered.
    • -

    SampleDeploymentObservabilityTests

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Governance · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/SampleDeploymentObservabilityTests.cs:12 · Level 1 · class

    @@ -2846,8 +2846,8 @@

    SampleDeploymentObservabilityTestsWhat it is - a subclass of ObservabilityConventionTestsBase that runs the shipped alert-to-runbook pairing gate over the framework's own deployment sample, samples/deployment, instead of a fixture. It is the one place in this repository the base runs against real infrastructure.
  • Depends on - ObservabilityConventionTestsBase (public sealed class SampleDeploymentObservabilityTests : ObservabilityConventionTestsBase, SampleDeploymentObservabilityTests.cs:12) and the sample's own IaC pair, embedded under the logical names samples.deployment.main.bicep and samples.deployment.OPERATIONS.md (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/MMCA.Common.Architecture.Tests.csproj:26-:30, sourced from MMCA.Common/samples/deployment/main.bicep and .../OPERATIONS.md).
  • Concept - cross-references the assembly-boundary concept introduced by ObservabilityConventionTestsBaseTests: both are subclasses of the same base that re-point its resource names, but where that class proves the resolution mechanism against a minimal fixture, this class proves the mechanism's real target. The sample is the artifact consumers copy (samples/deployment/DEPLOYMENT.md, per MMCA.Common/CLAUDE.md), so a runbook section that went missing there would propagate into every repository that lifted it; this is the gate proving its own worked example (SampleDeploymentObservabilityTests.cs:5-:10). [Rubric §13 - Observability & Operability] is the category directly exercised, the same alert-to-runbook pairing rule as its sibling; [Rubric §33 - Developer Experience] applies because the sample is a teaching artifact, and a silently stale one would mislead every adopter rather than just this repository.
  • -
  • Walkthrough - three property overrides re-point the base at the sample's real files: BicepResource => "samples.deployment.main.bicep" (:14) and RunbookResource => "samples.deployment.OPERATIONS.md" (:16), replacing the base defaults infra.main.bicep and infra.OPERATIONS.md. MinimumAlertSpecs => 4 (:20) raises the base's default of 3, because the sample provisions four alerts: failed-requests, server-response-time, availability and ai-token-spend (:18-:19). No [Fact]s of its own; inheritance supplies the base's three pairing/severity facts, run against the real sample bicep and runbook instead of a fixture.
  • -
  • Why it's built this way - the class doc states the comment inline (:18-:19): raising MinimumAlertSpecs is meant to track the sample provisioning a new alert, never to paper over one going missing, since lowering the count to make a red run go green would defeat the gate's purpose. sample-deployment-validate in ci.yml (MMCA.Common/CLAUDE.md) is the surrounding CI job that keeps the sample itself deployable; this class is the one that keeps its alerts and runbook in lockstep.
  • +
  • Walkthrough - four property overrides. Two re-point the base at the sample's real files: BicepResource => "samples.deployment.main.bicep" (:14) and RunbookResource => "samples.deployment.OPERATIONS.md" (:16), replacing the base defaults infra.main.bicep and infra.OPERATIONS.md. MinimumAlertSpecs => 4 (:20) raises the base's default of 3, because the sample provisions four alerts: failed-requests, server-response-time, availability and ai-token-spend (:18-:19). RequireWorkbook => true (:24) opts into the base's monitoring-view check, which is off by default (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/ObservabilityConventionTestsBase.cs:58). No [Fact]s of its own; inheritance supplies the base's four facts, run against the real sample bicep and runbook instead of a fixture: the alert-to-runbook pairing and severity facts, plus MonitoringWorkbookOrDashboard_IsProvisioned_WhenRequired (ObservabilityConventionTestsBase.cs:65-:77), which returns early unless RequireWorkbook is on and here asserts the sample bicep declares a Microsoft.Insights/workbooks or Microsoft.Portal/dashboards resource.
  • +
  • Why it's built this way - the class doc states the comment inline (:18-:19): raising MinimumAlertSpecs is meant to track the sample provisioning a new alert, never to paper over one going missing, since lowering the count to make a red run go green would defeat the gate's purpose. The RequireWorkbook opt-in carries its own reason (:22-:23): because consumers copy the sample, it must also ship the operator-facing view the alerts point at (rubric section 13). The base keeps the check off by default so a consumer that does not opt in keeps exactly the alert-to-runbook checks it had (ObservabilityConventionTestsBase.cs:53-:57). sample-deployment-validate in ci.yml (MMCA.Common/CLAUDE.md) is the surrounding CI job that keeps the sample itself deployable; this class is the one that keeps its alerts and runbook in lockstep.
  • Where it's used - an independent class in the Common architecture suite; it has no consumers outside CI, since it is the terminal check on the framework's own sample rather than a fixture other tests build on.
  • RightModel

    @@ -2861,66 +2861,87 @@

    RightModel

  • Walkthrough - one auto-property, string Name initialized to string.Empty (:9), present only so the type is not empty.
  • Where it's used - the property type of LeftService.Model, which is what closes the loop; both are scanned by NamespaceCycleFitnessTests.
  • -

    DuplicateTicketErrors, DynamicErrors

    +

    FixtureBadFeatures, FixtureExpiredFeatures, FixtureGoodFeatures

    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.ErrorCodeFixtures · (see per-type table) · Level 2 · class

    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.FeatureFlagFixtures · (see per-type table) · Level 1 · class

    -

    Two of the compiled Error-construction sites ErrorCatalogFitnessTests judges: one ships a code that collides with another type, the other builds its code at run time so no static scan can read it.

    +

    Three static fixture classes carrying [FeatureFlag]-annotated constants for FeatureFlagLifecycleTests: one class of constants that must pass the lifecycle rule, one of constants that must each fail it for a distinct reason, and one that is expired.

      -
    • Depends on - Error (MMCA.Common.Shared.Abstractions, :1) and its static factories, Error.NotFoundError and Error.Validation.
    • -
    • Concept - cross-references the IL-scanned catalog concept the fixture-set class doc introduces (ErrorCodeFixtures.cs:5-:10): the rule reads error codes out of compiled IL rather than source, so every behavior it must judge (a well-formed code, a cross-type collision, same-type branch reuse, a missing prefix, an allowlisted shared code, a runtime-built code) has to exist as a real compiled call site. DuplicateTicketErrors is the collision case; DynamicErrors is the blind spot the rule cannot resolve by static analysis and must instead report as UNVERIFIABLE rather than silently passing or failing it.
    • -
    • Walkthrough - DuplicateTicketErrors.NotFound() calls Error.NotFoundError("Tickets.NotFound", "The ticket is missing") (:21), the same code string TicketErrors.NotFound() constructs (:13), so the two types collide on Tickets.NotFound. DynamicErrors.For(string entity) builds its code by string concatenation, "Tickets." + entity (:48), so the code argument is not a literal the scan can read.
    • -
    • Where they're used - DuplicateCode_AcrossTwoTypes_IsFlagged asserts the uniqueness rule's failure message names both TicketErrors and DuplicateTicketErrors (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/ErrorCatalogFitnessTests.cs:21-:31); DynamicCode_IsReportedAsUnverifiable asserts the prefix rule's failure message contains UNVERIFIABLE and names DynamicErrors (:78-:87).
    • +
    • Depends on - FeatureFlagAttribute and FeatureFlagLifetime (MMCA.Common.Shared.FeatureFlags, FeatureFlagFixtures.cs:11-:41).
    • +
    • Concept - cross-references the flag-lifecycle concept introduced by FeatureFlagLifecycleTestsBase (Bases/Governance/FeatureFlagLifecycleTestsBase.cs:10): every flag constant must declare [FeatureFlag(Permanent|Temporary, RemoveBy, Owner)], and a temporary flag past its RemoveBy date fails the build (ADR-031). These three classes are the fixture matrix FeatureFlagLifecycleTests runs the rule against: one constant per violation the rule is meant to catch, plus the shapes that must stay silent.
    • +
    • Walkthrough - FixtureGoodFeatures (:11-:20) has two constants that must pass: PermanentFlag, declared Permanent with no RemoveBy (:13-:14), and LiveTemporaryFlag, Temporary with a RemoveBy far enough out to stay green, 2099-12-31 (:16-:18). FixtureBadFeatures (:23-:41) has four constants that must each fail for a different reason: UnannotatedFlag carries no [FeatureFlag] at all (:28-:29); PermanentWithRemoveByFlag is Permanent yet still names a RemoveBy date, a contradiction (:31-:33); TemporaryWithoutRemoveByFlag is Temporary with no RemoveBy, which the rule treats as permanent in everything but name (:35-:37); TemporaryWithUnparseableRemoveByFlag is Temporary with a RemoveBy in an order the rule cannot parse, "31/12/2030" (:39-:41). FixtureExpiredFeatures (:42-:47) has one constant, ExpiredFlag, Temporary with RemoveBy = "2020-01-01", which is what the expiry check exists to catch (:44-:46).
    • +
    • Why it's built this way - splitting the matrix into good/bad/expired classes rather than one flat list lets each FeatureFlagLifecycleTests fact read only the constants relevant to it, so adding a new failure mode to FixtureBadFeatures cannot accidentally break a fact that only reads FixtureGoodFeatures.
    - + - - - - - - - - + + + + + + + + + + + + +
    Type File:LineThe case it modelsRole
    DuplicateTicketErrorsMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs:19Ships "Tickets.NotFound", the same code TicketErrors constructs, from a second type: the collision the uniqueness rule catches.
    DynamicErrorsMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs:46Builds its code by concatenation at run time, so the argument is not a literal and the rule reports the site as UNVERIFIABLE instead of guessing.FixtureGoodFeaturesMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/FeatureFlagFixtures/FeatureFlagFixtures.cs:11Two constants that must pass the lifecycle rule.
    FixtureBadFeaturesMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/FeatureFlagFixtures/FeatureFlagFixtures.cs:23Four constants, each failing for a distinct reason (unannotated, contradictory, dateless-temporary, unparseable date).
    FixtureExpiredFeaturesMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/FeatureFlagFixtures/FeatureFlagFixtures.cs:42One constant past its RemoveBy date.
    -

    FakeDependentModule

    +
      +
    • Where it's used - all three are read across multiple facts of FeatureFlagLifecycleTests (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/FeatureFlagLifecycleTests.cs), which asserts each fixture's constants land in the rule's expected pass/fail buckets.
    • +
    +

    FixtureDomainEvent

    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Layering · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Layering/ModuleConformanceTestsBaseTests.cs:31 · Level 2 · class

    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.DomainEventSaveFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/DomainEventSaveFixtures/DomainEventSaveFixtures.cs:13 · Level 1 · record

      -
    • What it is - the "hard" module fixture: a module that declares dependencies, refuses to start without them, and exports a disabled stub. It exercises every member of the module contract that a leaf module leaves at its default.
    • -
    • Depends on - IModule (public sealed class FakeDependentModule : IModule, ModuleConformanceTestsBaseTests.cs:31), ApplicationSettings (the Register parameter, :39), IFakeExportService and DisabledFakeExportService (the stub pair, :44), plus IServiceCollection and IConfigurationBuilder from Microsoft.Extensions.* (:1-:2).
    • -
    • Concept introduced - the full module contract as a testable surface. IModule has five members, three of which are defaulted (Dependencies => [], RequiresDependencies => false, RegisterDisabledStubs as an empty body, MMCA.Common/Source/Core/MMCA.Common.Application/Modules/IModule.cs:17,:23,:34), so the entire contract ModuleLoader registers on can be silently wrong without a compile error. This fixture is the one that overrides all three, so the conformance base is exercised against real (not defaulted) values. [Rubric §14 - Testability] covers the fixture role; [Rubric §7 - Microservices Readiness] is what the stub half protects, since a module that can be switched off without breaking the modules that import its contract is a module that can also be extracted (ADR-059).
    • -
    • Walkthrough
        -
      • Name => "FakeDependent" (:33): the key ModulesSettings entries and other modules' dependency lists match on.
      • -
      • Dependencies => ["FakeLeaf", "FakeOther"] (:35): two entries, one of which (FakeLeafModule) exists in the fixture set and one of which deliberately does not, so the list is a pure data declaration rather than a resolvable graph.
      • -
      • RequiresDependencies => true (:37): the flag that turns a disabled dependency into a startup failure instead of a substituted stub.
      • -
      • Register(...) (:39-:41): an empty body. Registration behavior is not what the conformance base asserts, so the fixture spends nothing on it.
      • -
      • RegisterDisabledStubs(IServiceCollection services) (:43-:44): a single expression-bodied services.AddSingleton<IFakeExportService, DisabledFakeExportService>(), which is the only member with observable behavior and the one FakeDependentModuleConformanceTests inspects.
      • +
      • What it is - the single throwaway domain event every handler fixture in DomainEventSaveFixtures is generic over. It carries no meaning; it exists so the four fixture handlers can implement a real, closed IDomainEventHandler<T>.
      • +
      • Depends on - IDomainEvent (public sealed record FixtureDomainEvent(DateTime DateOccurred, Guid MessageId) : IDomainEvent;, DomainEventSaveFixtures.cs:13, with the interface import at :3).
      • +
      • Concept - cross-references the handler-save concept introduced by IBadgeGranter. Its own contribution is the closure: the rule finds handlers by asking CallGraphIndex whether a type implements the open interface MMCA.Common.Application.Interfaces.IDomainEventHandler1 matched by full name with Cecil's arity marker (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Cqrs/ArchitectureRules.DomainEventHandlerSaves.cs:8-:9,:96`), so any closed instantiation works and one event type is enough for the whole fixture set.
      • +
      • Walkthrough - two positional members, DateTime DateOccurred and Guid MessageId, which are the two members IDomainEvent requires. It is the only public type in the file; the handlers around it are all internal.
      • +
      • Where it's used - the type argument of DirectSavingHandler, TransitiveSavingHandler, InterfaceDispatchSavingHandler and InnocentHandler, and the assembly anchor of DomainEventHandlerSaveFitnessTests's FixtureAssemblyMap (DomainEventHandlerSaveFitnessTests.cs:116).
      -
    • -
    • Why it's built this way - the doc comment (:30) names the intent: this is the shape of the two real consumer modules that are not leaves (Store Sales, ADC Notification). Modeling them locally means the shared base is proven against both module shapes inside MMCA.Common's own CI, before any consumer sees it (ADR-015).
    • -
    • Where it's used - the TModule of FakeDependentModuleConformanceTests (:60) and of the deliberately-drifted DriftedTests (:131).
    • +

      DisabledFakeExportService

      +
      +

      MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Layering · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Layering/ModuleConformanceTestsBaseTests.cs:28 · Level 1 · class

      +
      +
        +
      • What it is - the stub implementation a disabled FakeDependentModule leaves behind, so the fixture models the real "module off, contract still resolvable" behavior rather than describing it.
      • +
      • Depends on - IFakeExportService (public sealed class DisabledFakeExportService : IFakeExportService;, ModuleConformanceTestsBaseTests.cs:28).
      • +
      • Concept - cross-references the disabled-stub concept introduced by IFakeExportService. The pairing matters: a stub type distinct from the real implementation is what lets an assertion prove which implementation the container holds, not merely that the service type is registered.
      • +
      • Walkthrough - no members; a body-less sealed class with its doc comment at :27. It is the exact type FakeDependentModuleConformanceTests asserts on via descriptor.ImplementationType.Should().Be<DisabledFakeExportService>() (:76).
      • +
      • Where it's used - registered as a singleton by FakeDependentModule.RegisterDisabledStubs (:43-:44).
      -

      FakeLeafModule

      +

      CustomExceptionThrowingFixture

      -

      MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Layering · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Layering/ModuleConformanceTestsBaseTests.cs:15 · Level 2 · class

      +

      MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.DomainThrowFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/DomainThrowFixtures/DomainThrowFixtures.cs:67 · Level 1 · class

        -
      • What it is - the minimal module fixture: Name plus an empty Register, and nothing else. Its whole purpose is to not override Dependencies or RequiresDependencies, so the conformance base has to reach the interface's default implementations to read them.
      • -
      • Depends on - IModule (public sealed class FakeLeafModule : IModule, ModuleConformanceTestsBaseTests.cs:15) and ApplicationSettings (the Register parameter, :19).
      • -
      • Concept introduced - default interface implementations are only reachable through the interface. A default member declared on IModule (IModule.cs:17,:23) does not exist on the concrete class, so module.Dependencies will not compile against FakeLeafModule and a reflection lookup on the concrete type finds nothing. The shared base solves this by locating the IModule interface by full name and reading the property off the interface (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Layering/ModuleConformanceTestsBase.cs:80-:99), which dispatches to the override when there is one and to the framework default when there is not. Before the base existed, the hand-written consumer tests needed an explicit (IModule) cast to get the same reach (class doc, :9-:14). [Rubric §14 - Testability] and [Rubric §1 - SOLID] both apply: the fixture exists so the base's interface-dispatch contract is provable rather than assumed.
      • -
      • Walkthrough - Name => "FakeLeaf" (:17) and an empty Register(...) (:19-:21). That is the entire type; the absence of members is the fixture.
      • -
      • Why it's built this way - this is the exact shape the three byte-identical consumer {X}ModuleTests files collapse into (class doc, :12-:13), so the leaf path is the most-travelled one and the one whose silent breakage would be widest.
      • -
      • Where it's used - the TModule of FakeLeafModuleConformanceTests (:51), which in turn is driven directly by two of ModuleConformanceTestsBaseTests's facts (:112-:129).
      • +
      • What it is - a one-method static fixture that throws the file's custom domain exception, so the domain-throw rule can be shown to judge by constructed type rather than by a list of framework exception names.
      • +
      • Depends on - TicketDomainException (DomainThrowFixtures.cs:69).
      • +
      • Concept - cross-references the domain-throw concept introduced by its sibling fixtures. What this one adds is the discrimination that matters most in practice: teams that adopt the Result pattern often keep a DomainException hierarchy as a compromise, and a rule built around a deny-list of BCL exception names would wave it through. The rule instead allows exactly three types and reports everything else (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.DomainThrows.cs:19-:21,:132-:135), so a custom exception is caught for what it is. The fixture doc puts it in one sentence: the same defect wearing a domain name (:48).
      • +
      • Walkthrough - the whole type is internal static class CustomExceptionThrowingFixture (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/DomainThrowFixtures/DomainThrowFixtures.cs:67) with one member, internal static void Reject() => throw new TicketDomainException("The ticket was rejected"); (:69). The newobj immediately precedes the throw, so ConstructedExceptionType names it and the site is a hard violation rather than an UNVERIFIABLE.
      • +
      • Where it's used - the subject of CustomDomainException_IsAlsoFlagged (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/DomainThrowFitnessTests.cs:48-:57), and the residual offender the allowlist facts assert is still reported when only its sibling is exempted (:114-:126).
      • +
      +

      UpdateTicketRequestValidator

      +
      +

      MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.CommandValidatorFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/CommandValidatorFixtures/CommandValidatorFixtures.cs:33 · Level 1 · class

      +
      +
        +
      • What it is - the validator that makes ReopenTicketCommand, UpdateTicketCommand's bridge half a real hit: an AbstractValidator<UpdateTicketRequest> for the request UpdateTicketCommand wraps, not for the command itself.
      • +
      • Depends on - FluentValidation's AbstractValidator<T> (:1,:33) and UpdateTicketRequest (:28), the record it validates.
      • +
      • Concept introduced - the request bridge only counts if a validator actually resolves for the wrapped request type. CommandRequestValidator reaches the command's request through ICommandWithRequest<out TRequest> and asks whether ValidatedTypes contains it; this class is what makes that lookup non-empty for UpdateTicketRequest, so UpdateTicketCommand reads as covered even though it declares no validator of its own. ReopenTicketCommand is the same shape with no validator at all, and the pair only proves the rule because they differ in exactly this one respect.
      • +
      • Walkthrough - one rule, RuleFor(r => r.Title).NotEmpty() (:35), applied through the constructor.
      • +
      • Where it's used - resolved by name in CommandCoveredThroughTheRequestBridge_IsCovered (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/CommandValidatorCoverageFitnessTests.cs:44-:53), which asserts ValidatedTypes contains UpdateTicketRequest and that UpdateTicketCommand is reported covered.

      InnocentHandler

      @@ -2985,6 +3006,26 @@

      PasswordHashingFitnessTests

    • Where it's used - an independent class in the Common architecture suite. It is the shape half of a pair; the parameter values are pinned by PasswordHasherSecurityTests in the Infrastructure unit-test project (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Auth/PasswordHasherSecurityTests.cs:18), which holds PBKDF2-HMAC-SHA512 at 600,000 iterations with a 32-byte salt and a 64-byte output as reflected private constants (:20-:22). The class doc here records the division of labour (PasswordHashingFitnessTests.cs:10-:13).
    • Caveats / not-in-source - HaveDependencyOnAll reports a reference in the compiled IL, not that the reference is on the hashing path. It cannot tell an actually-used PBKDF2 call from a dead one, which is why the value pins in the companion test remain load-bearing.
    +

    PasswordRuleParityTests

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Ui · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Ui/PasswordRuleParityTests.cs:21 · Level 2 · class

    +
    +
      +
    • What it is - a client/server validation parity test for the password rule: for every row it asks the Register form's PasswordComplexityAttribute and the server's StrongPasswordRules<T> for a verdict and fails when the two disagree (PasswordRuleParityTests.cs:40-:55).
    • +
    • Depends on - PasswordComplexityAttribute (MMCA.Common.UI.Pages.Auth, :3,:45), StrongPasswordRules<T> (MMCA.Common.Application.Validation, :2,:48), System.ComponentModel.DataAnnotations for ValidationContext and ValidationResult (:1), and externals xUnit plus AwesomeAssertions. Both sides evaluate the shared PasswordComplexity definition (MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/PasswordComplexity.cs:18), which this test never names directly: it asserts the agreement, not the mechanism.
    • +
    • Concept introduced - a rule implemented twice is tested as a pair, not twice. The form and the API each need the password rule, and the layering forbids them from sharing a validator (UI may depend on Shared only). The fix in source is one definition in Shared that both sides call: the attribute calls PasswordComplexity.Evaluate (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Auth/PasswordComplexityAttribute.cs:24) and the server rule chains PasswordComplexity.MinimumLength, MaximumLength and the four regexes (MMCA.Common/Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:194-:201). This test is what keeps that true: it does not assert either verdict, only that they are equal, so a change that moves one side (a new character class, an ASCII-only regex) goes red even when each side's own unit tests stay green. A disagreement means the form either rejects a password the API would accept, or lets one through that the API then refuses after a round trip (class doc, :8-:12). The rows lean on the Unicode cases because that is where a naive [A-Z] / [^a-zA-Z0-9] implementation diverges: PasswordComplexity treats any \p{L} (ideographs included) as a letter and any \p{Nd} as a digit (PasswordComplexity.cs:12-:15,:27-:43), so an accented or CJK letter never counts as the special character. [Rubric §24 - Forms, Validation & UX Safety] assesses exactly this client/server parity; [Rubric §14 - Testability] covers the technique.
    • +
    • Walkthrough
        +
      • ClientPasswordComplexity_MatchesServerStrongPasswordRules(string password, string scenario) (:27-:55): a [Theory] over twelve [InlineData] rows (:28-:39). Seven are ASCII: a valid password, a valid one at exactly the eight-character minimum, then one row each for no uppercase, no lowercase, no digit, no special character, and seven characters. Five are non-ASCII: an accented lowercase letter as the only candidate special character (:35), an uppercase umlaut as the only uppercase (:36), CJK ideographs as the only non-ASCII-alphanumeric characters (:37), CJK beside a full ASCII complement (:38), and a full-width digit as the only digit (:39).
      • +
      • The client verdict is computed the way the EditForm computes it: GetValidationResult with a ValidationContext whose MemberName is nameof(PasswordProbe.Password), compared against ValidationResult.Success (:44-:47).
      • +
      • The server verdict binds a StrongPasswordRules<PasswordProbe> to p => p.Password and reads Validate(probe).IsValid (:48-:50), the same way the API's FluentValidation validators run it over a model.
      • +
      • clientVerdict.Should().Be(serverVerdict, ...) (:52-:54) puts the scenario label and the server's verdict in the because, so a red row reads as "the Register form and the API must agree on 'full-width digit as the only digit' (server says valid)" rather than a bare boolean mismatch.
      • +
      • PasswordProbe (:58) is a private sealed record with one Password property: the smallest model the generic server rule can be bound to.
      • +
      +
    • +
    • Why it's built this way - the class doc records the placement (:14-:15): the architecture test project is the one project that references both sides, since the UI test project references MMCA.Common.UI only. The non-ASCII rows are written as \u escapes so the test source stays ASCII (:16-:18).
    • +
    • Where it's used - an independent class in the Common architecture suite; no usage sites outside the defining file. Its subjects are the PasswordComplexityAttribute on the Register form and the StrongPasswordRules<T> the API's validators compose.
    • +
    • Caveats / not-in-source - parity is asserted only over the twelve rows, and none of them is empty or longer than 128 characters. Those are the two inputs where the sides are not the same rule by design: the attribute returns success on null or empty input and leaves that to RequiredAttribute (PasswordComplexityAttribute.cs:24), and PasswordComplexity.Evaluate does not check the upper bound (PasswordComplexity.cs:46-:57), while the server rule applies NotEmpty and MaximumLength(PasswordComplexity.MaximumLength) (CommonValidationRules.cs:195,:197).
    • +

    SharedCodeErrors

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.ErrorCodeFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs:52 · Level 2 · class

    @@ -3029,104 +3070,119 @@

    UnprefixedErrors

  • Walkthrough - one member, Broken() => Error.Validation("SomethingBroke", "No module prefix at all") (:39).
  • Where it's used - UnprefixedCode_IsFlagged asserts both "SomethingBroke" and nameof(UnprefixedErrors) appear in the prefix-rule failure message, so the report names both the code and its owning type (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/ErrorCatalogFitnessTests.cs:56-:64).
  • -

    EmptyScanTests

    -
    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Api · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Api/AnonymousEndpointTestsBaseTests.cs:178 · Level 3 · class

    -
    -
      -
    • What it is - the adversarial fixture for the anonymous-endpoint gate's non-vacuity guard: a subclass pointed at an assembly that contains no controllers and no routable components at all, so the scan finds nothing and the guard must fail.
    • -
    • Depends on - AnonymousEndpointTestsBase (private sealed class EmptyScanTests : AnonymousEndpointTestsBase, AnonymousEndpointTestsBaseTests.cs:178) and the MMCA.Common.Shared assembly reached through typeof(Shared.Abstractions.Result).Assembly (:121).
    • -
    • Concept introduced - guarding the guard: why a fitness function needs a floor. The allow-list assertion is offenders.Should().BeEmpty() (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Api/AnonymousEndpointTestsBase.cs:85-:62), and an empty scan produces zero offenders, so the gate passes loudest exactly when it has stopped looking. A renamed assembly, a moved anchor type, or a repo re-layout all produce that failure mode, and none of them is visible in a green run. The base therefore ships a third fact, ScannedEndpointSet_IsNotEmpty (:66-:76), that counts the discovered controller and routable-component types and requires at least MinimumScannedTypes, whose default is 1 (:51). This fixture is what proves the counter is real: point it at an assembly that genuinely has neither shape and the fact must throw. Compare the equivalent floors elsewhere in the suite: MinimumScannedTypes => 21 on AnonymousEndpointTests, MinimumBaseResources => 3 on LocalizationResourceTests, MinimumRoutes = 8 in NavigationContractTests, and the DistinctErrorCodeCount and HandledCommandCount inventories the catalog and validator rules expose for the same purpose. [Rubric §14 - Testability] assesses whether the guardrails are themselves trustworthy, and a vacuity floor is the cheapest thing that keeps one honest over a decade of refactors.
    • -
    • Walkthrough - two overrides and an inline comment that states why the chosen assembly works: the Shared package has neither controllers nor routable components (:119). TargetAssemblies returns that one assembly (:120-:121), and AllowedAnonymousEndpoints is empty (:123), which is irrelevant here because nothing is discovered to compare against. Being private and nested is what keeps xUnit from collecting its three inherited facts as deliberately-red tests of their own (class doc, :10-:11).
    • -
    • Where it's used - the subject of Base_Fails_WhenNothingWasScanned (AnonymousEndpointTestsBaseTests.cs:37-:43), which converts the inherited ScannedEndpointSet_IsNotEmpty into a delegate and asserts it throws (:40-:42).
    • -
    • Caveats / not-in-source - that fact asserts only that an exception is thrown, not what its message says. It proves the floor bites; it does not pin the wording.
    • -
    -

    CompliantStringFixtureId

    -
    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.StronglyTypedIdFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/StronglyTypedIdFixtures/StronglyTypedIdFixtures.cs:24 · Level 3 · record struct

    -
    -
      -
    • What it is - the same positive control as CompliantFixtureId, over a string primitive instead of int.
    • -
    • Depends on - IStronglyTypedId<TSelf, TValue> closed over itself and string (public readonly record struct CompliantStringFixtureId(string Value) : IStronglyTypedId<CompliantStringFixtureId, string>, StronglyTypedIdFixtures.cs:24-:25).
    • -
    • Concept introduced - cross-references CompliantFixtureId. Its contribution is discrimination across the primitive type: the fitness rule inspects the shape of the wrapper, not its backing type, and a rule that only ever saw an int-backed compliant declaration could not prove it treats a string-backed one the same way.
    • -
    • Walkthrough - the primary constructor parameter Value (:24) and one static factory, From(string value) => new(value) (:27).
    • -
    • Where it's used - the same IdentifierFixtureMap scan and the same Rule_SaysNothingAboutTheCanonicalDeclaration assertion as CompliantFixtureId (StronglyTypedIdFitnessTests.cs:50).
    • -
    -

    AcyclicConsumer

    -
    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.CycleFixtures.Acyclic · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/CycleFixtures/Acyclic/AcyclicFixtures.cs:6 · Level 3 · class

    -
    -
      -
    • What it is - the negative control for the namespace-cycle rule: a third fixture namespace that points into Left and that nothing points back at, so it must never appear in a cycle report.
    • -
    • Depends on - LeftService (the one-way Service property, AcyclicFixtures.cs:9, using at :1).
    • -
    • Concept - cross-references LeftModelBase. Its role is the discrimination half of the proof: a rule that reported every namespace with any edge would also "pass" the cycle test, so the fixture set needs a namespace that is genuinely coupled yet genuinely acyclic. Depending on Left (a namespace that is in a cycle) is deliberate: it proves the rule reports strongly connected components, not merely everything reachable from a cycle.
    • -
    • Walkthrough - one member, public LeftService? Service { get; set; } (:9).
    • -
    • Where it's used - asserted absent from the failure message by Rule_FlagsTwoNamespaceCycle_ButNotAcyclicNamespaces (NamespaceCycleFitnessTests.cs:23-:25).
    • -
    -

    ArchiveTicketHandler

    -
    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.CommandValidatorFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/CommandValidatorFixtures/CommandValidatorFixtures.cs:62 · Level 3 · class

    -
    -
      -
    • What it is - the handler for ArchiveTicketCommand, a command that carries data (TicketId) and has a handler but no validation of any kind: the shape the command-validator coverage rule must flag.
    • -
    • Depends on - ICommandHandler<ArchiveTicketCommand, Result> (MMCA.Common.Application.UseCases.Contracts) and ArchiveTicketCommand (the record it handles, :60,:62).
    • -
    • Concept introduced - cross-references CreateTicketHandler. Its contribution is discrimination at the far end of the coverage spectrum: CreateTicketHandler has direct coverage and UpdateTicketHandler has bridge coverage, while this one has neither, so it is what proves the rule actually detects an uncovered command rather than passing everything with a handler.
    • -
    • Walkthrough - one member, HandleAsync(ArchiveTicketCommand command, CancellationToken cancellationToken = default) => Task.FromResult(Result.Success()) (:64-:65). No constructor, no validator anywhere in the file for ArchiveTicketCommand.
    • -
    • Where it's used - CommandWithNoValidation_IsFlagged asserts nameof(ArchiveTicketCommand) present in the coverage failure message (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/CommandValidatorCoverageFitnessTests.cs:23-:31); AllowlistedCommand_IsExempt reuses it as the fixture that must stay flagged once a different command is allowlisted, proving the allowlist narrows to exactly the name it lists (:78-:89).
    • -
    -

    CompliantFixtureId

    +

    DuplicateTicketErrors, DynamicErrors, SharedCodeErrors, TicketErrors, TwoBranchTicketErrors, UnprefixedErrors

    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.StronglyTypedIdFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/StronglyTypedIdFixtures/StronglyTypedIdFixtures.cs:17 · Level 3 · record struct

    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.ErrorCodeFixtures · (see per-type table) · Level 2 · class

    +

    Six internal static classes in one file (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs), each holding one or two expression-bodied factory methods that construct an Error. Together they compile the error catalog the two catalog rules have to judge: well-formed codes, a cross-type collision, an unprefixed code, one code reused across two branches of a single type, a shared framework static, and a code built at run time.

      -
    • What it is - the positive control for the strongly typed ID fitness function (ADR-115): the canonical declaration shape, an int-backed wrapper, that the rule must say nothing about.
    • -
    • Depends on - IStronglyTypedId<TSelf, TValue> closed over itself and int (public readonly record struct CompliantFixtureId(int Value) : IStronglyTypedId<CompliantFixtureId, int>, StronglyTypedIdFixtures.cs:17).
    • -
    • Concept introduced - the two-line strongly typed ID is exactly two lines, no more. A readonly record struct with a positional Value and a static From factory is the whole contract; nothing else is required and nothing else is permitted before the rule starts flagging (see NotARecordFixtureId, MutableFixtureId, ExtraStateFixtureId for the three ways to fail it). This fixture is the shape those three are measured against: a rule that flagged it too would be unusable, since it is the identical shape every real identifier in Source/ would take if one adopted the opt-in.
    • -
    • Walkthrough - the primary constructor parameter Value (:17) and one static factory, From(int value) => new(value) (:18), documented as "the one member the canonical declaration writes" (:18).
    • -
    • Where it's used - the IdentifierFixtureMap assembly scan in StronglyTypedIdFitnessTests (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/StronglyTypedIdFitnessTests.cs:53-:58) includes it by assembly membership, and Rule_SaysNothingAboutTheCanonicalDeclaration asserts its name is absent from the failure message (:44-:50).
    • +
    • Depends on - Error and its static factories NotFoundError, Conflict and Validation (ErrorCodeFixtures.cs:1). Nothing else.
    • +
    • Concept introduced - the error code as the module's public vocabulary, read out of IL. A client switches on Order.NotFound and a support ticket quotes it, so two modules both shipping Item.Invalid make that vocabulary ambiguous in a way that only surfaces in production (rule doc, MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.ErrorCatalog.cs:31-:37). Two rules police it. ErrorCodesAreUnique (:62) fails when the same literal code is constructed by more than one type, and the choice of "type" as the unit is deliberate: one error with two exits in the same class is not a collision, which keeps a normal two-branch handler out of the report (:45-:50). ErrorCodesUseAnAllowedPrefix (:102) fails on any literal code whose prefix a caller-supplied delegate rejects, so the convention stays the consumer's rather than the framework's (:92-:98). Both read the code out of the compiled IL: the scan looks for calls to any of nine Error factory names plus .ctor (:17-:29) and reads the first argument, and both deliberately skip the record copy constructor through a first-parameter-is-string check so error with { Source = ... } is not mistaken for a new code (:11-:15). Codes that are not literals cannot be judged statically, and the rules refuse to guess: they are listed as UNVERIFIABLE in the failure message so the reader knows the catalog has a blind spot (:51-:55). [Rubric §9 - API & Contract Design] assesses whether the published failure vocabulary is governed; [Rubric §15 - Best Practices & Code Quality] covers traceability, since a prefixed code alone says where it came from; [Rubric §14 - Testability] covers the fixtures.
    • +
    • Walkthrough - scope is the per-module Domain and Application assemblies only (:38-:44), which is why the self-test map registers this assembly as a module Application layer rather than a framework one.
    -

    CreateTicketHandler

    -
    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.CommandValidatorFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/CommandValidatorFixtures/CommandValidatorFixtures.cs:21 · Level 3 · class

    -
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeFile:LineWhat it models
    TicketErrorsMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs:11The clean catalog: Tickets.NotFound via Error.NotFoundError (:13) and Tickets.AlreadyClosed via Error.Conflict (:15). Correctly prefixed, so it must pass the prefix rule, while being one half of the collision below.
    DuplicateTicketErrorsMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs:19Ships Tickets.NotFound a second time from a different type, with a different message (:21). The collision the uniqueness rule exists to catch, and the failure message must name both owning types.
    TwoBranchTicketErrorsMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs:28The near-miss: one method with a ternary that constructs Tickets.Invalid on both branches with different reasons (:30-:33). Two construction sites, one owning type, so it is not a collision and the rule must stay silent.
    UnprefixedErrorsMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs:37Error.Validation("SomethingBroke", ...) (:39), a code with no module prefix at all. The prefix rule must report it and name the owning type.
    DynamicErrorsMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs:46The blind spot: Error.Validation("Tickets." + entity, ...) (:48). The code argument is a concatenation rather than a literal, so the scan reports the site as UNVERIFIABLE rather than passing or failing it.
    SharedCodeErrorsMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs:52Error.NotFoundError("Error.NotFound", ...) (:54), reusing one of the generic statics on Error itself. The allowed-shared-codes list exempts it from both rules, which is what those generics exist for.
      -
    • What it is - the handler for CreateTicketCommand, the direct-coverage positive control: an explicit CreateTicketCommandValidator (AbstractValidator<CreateTicketCommand>, :16-:19) validates the command itself.
    • -
    • Depends on - ICommandHandler<CreateTicketCommand, Result> and CreateTicketCommand (:13,:21).
    • -
    • Concept introduced - cross-references ArchiveTicketHandler. Its contribution is the coverage baseline: AddValidatorsFromAssembly picks up CreateTicketCommandValidator by its IValidator<CreateTicketCommand> closure with no bridge involved, which is the simplest of the three coverage shapes the rule must recognize (direct, bridge, and none).
    • -
    • Walkthrough - one member, HandleAsync(CreateTicketCommand command, CancellationToken cancellationToken = default) => Task.FromResult(Result.Success()) (:23-:24).
    • -
    • Where it's used - CommandWithItsOwnValidator_IsCovered asserts nameof(CreateTicketCommand) absent from the coverage failure message (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/CommandValidatorCoverageFitnessTests.cs:34-:42); its assembly anchors FixtureModuleMap's single module (typeof(CreateTicketCommand).Assembly, :121).
    • +
    • Why they're built this way - the pairs are what make each rule discriminating rather than merely loud. TicketErrors and DuplicateTicketErrors are the collision; TwoBranchTicketErrors is the shape a naive "count the construction sites" implementation would wrongly flag; SharedCodeErrors proves the exemption is honored on the prefix rule as well as the uniqueness rule. Keeping every fixture internal static with expression-bodied members means the IL is minimal and the reported owner name is unambiguous. See ADR-015.
    • +
    • Where they're used - compiled into this assembly and scanned by ErrorCatalogFitnessTests through its FixtureModuleMap, which anchors the module Application layer on typeof(TicketErrors).Assembly (ErrorCatalogFitnessTests.cs:112).
    -

    DriftedTests

    +

    DuplicateTicketErrors, DynamicErrors

    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Layering · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Layering/ModuleConformanceTestsBaseTests.cs:131 (and a same-named sibling in Api/AnonymousEndpointTestsBaseTests.cs:100) · Level 3 · class

    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.ErrorCodeFixtures · (see per-type table) · Level 2 · class

    +

    Two of the compiled Error-construction sites ErrorCatalogFitnessTests judges: one ships a code that collides with another type, the other builds its code at run time so no static scan can read it.

      -
    • What it is - the name is used twice in this assembly, once per fitness base under proof, and both are the same idea: a private sealed subclass of a shipped *TestsBase whose configuration is deliberately wrong, so the base's assertions can be shown to actually fail on the drift they claim to catch. They are nested in different outer classes, so their full names differ and neither is visible outside its own file.
    • -
    • Depends on - the base each one drifts from, plus the fixture it points at: ModuleConformanceTestsBase<TModule> with FakeDependentModule for one, AnonymousEndpointTestsBase with the fixture controllers for the other.
    • -
    • Concept introduced - negative fixtures, and hiding them from test discovery. A fitness base that asserts nothing passes everywhere; the only way to know an assertion bites is to feed it a case that must fail. But a public subclass of an xUnit base is itself collected, so its inherited [Fact]s would run and report as red tests of their own. Declaring the drifted subclass private and nested keeps xUnit from collecting it, while the enclosing class can still instantiate it and invoke the inherited methods directly as delegates (var assert = new DriftedTests().Module_ShouldDeclare_ExpectedName;, ModuleConformanceTestsBaseTests.cs:91). Both class docs record that reasoning in the same words (ModuleConformanceTestsBaseTests.cs:81-:85, AnonymousEndpointTestsBaseTests.cs:10-:11). [Rubric §14 - Testability] assesses whether the guardrails themselves are trustworthy; this is the fixture shape that earns that trust. Compare NavigatingSpec, the same negative-fixture technique applied to a specification rule, and ProbeTests, which parameterizes the drift instead of hard-coding it.
    • +
    • Depends on - Error (MMCA.Common.Shared.Abstractions, :1) and its static factories, Error.NotFoundError and Error.Validation.
    • +
    • Concept - cross-references the IL-scanned catalog concept the fixture-set class doc introduces (ErrorCodeFixtures.cs:5-:10): the rule reads error codes out of compiled IL rather than source, so every behavior it must judge (a well-formed code, a cross-type collision, same-type branch reuse, a missing prefix, an allowlisted shared code, a runtime-built code) has to exist as a real compiled call site. DuplicateTicketErrors is the collision case; DynamicErrors is the blind spot the rule cannot resolve by static analysis and must instead report as UNVERIFIABLE rather than silently passing or failing it.
    • +
    • Walkthrough - DuplicateTicketErrors.NotFound() calls Error.NotFoundError("Tickets.NotFound", "The ticket is missing") (:21), the same code string TicketErrors.NotFound() constructs (:13), so the two types collide on Tickets.NotFound. DynamicErrors.For(string entity) builds its code by string concatenation, "Tickets." + entity (:48), so the code argument is not a literal the scan can read.
    • +
    • Where they're used - DuplicateCode_AcrossTwoTypes_IsFlagged asserts the uniqueness rule's failure message names both TicketErrors and DuplicateTicketErrors (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/ErrorCatalogFitnessTests.cs:21-:31); DynamicCode_IsReportedAsUnverifiable asserts the prefix rule's failure message contains UNVERIFIABLE and names DynamicErrors (:78-:87).
    - + - - - + + + - - - + + +
    Type File:LineWhat is deliberately wrongThe case it models
    DriftedTests (in AnonymousEndpointTestsBaseTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Api/AnonymousEndpointTestsBaseTests.cs:161Points TargetAssemblies at this test assembly (:102-:103), so the scan finds the fixture controllers' [AllowAnonymous] attributes, and then supplies an empty AllowedAnonymousEndpoints (:105). Every discovered endpoint is therefore an offender, and the failure message must name AnonymousFixtureController.DuplicateTicketErrorsMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs:19Ships "Tickets.NotFound", the same code TicketErrors constructs, from a second type: the collision the uniqueness rule catches.
    DriftedTests (in ModuleConformanceTestsBaseTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Layering/ModuleConformanceTestsBaseTests.cs:131Three overrides, each wrong in a different way against FakeDependentModule's real declarations: ExpectedName => "NotTheDeclaredName" (:133) against the module's "FakeDependent" (:33); ExpectedDependencies => ["FakeLeaf"] (:135) against the module's two-entry ["FakeLeaf", "FakeOther"] (:35), so one dependency is missing; and ExpectedRequiresDependencies => false (:137) against the module's true (:37). AssertDisabledStubs is deliberately not overridden, so the fourth inherited fact stays vacuous here.DynamicErrorsMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ErrorCodeFixtures/ErrorCodeFixtures.cs:46Builds its code by concatenation at run time, so the argument is not a literal and the rule reports the site as UNVERIFIABLE instead of guessing.
    +

    FakeDependentModule

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Layering · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Layering/ModuleConformanceTestsBaseTests.cs:31 · Level 2 · class

    +
      -
    • Walkthrough - one wrong value per assertion, and no more. If a single drifted subclass were wrong in three ways at once and the base only ever threw on the first, the other two assertions could rot undetected; the module-side fixture avoids that by being driven three separate times, once per fact.
    • -
    • Where it's used - the anonymous-endpoint one drives Base_Fails_WhenAnAnonymousEndpointIsNotAllowListed (AnonymousEndpointTestsBaseTests.cs:15-:24); the module one is instantiated three times by ModuleConformanceTestsBaseTests (:91,:99,:107), once per assertion under proof.
    • +
    • What it is - the "hard" module fixture: a module that declares dependencies, refuses to start without them, and exports a disabled stub. It exercises every member of the module contract that a leaf module leaves at its default.
    • +
    • Depends on - IModule (public sealed class FakeDependentModule : IModule, ModuleConformanceTestsBaseTests.cs:31), ApplicationSettings (the Register parameter, :39), IFakeExportService and DisabledFakeExportService (the stub pair, :44), plus IServiceCollection and IConfigurationBuilder from Microsoft.Extensions.* (:1-:2).
    • +
    • Concept introduced - the full module contract as a testable surface. IModule has five members, three of which are defaulted (Dependencies => [], RequiresDependencies => false, RegisterDisabledStubs as an empty body, MMCA.Common/Source/Core/MMCA.Common.Application/Modules/IModule.cs:17,:23,:34), so the entire contract ModuleLoader registers on can be silently wrong without a compile error. This fixture is the one that overrides all three, so the conformance base is exercised against real (not defaulted) values. [Rubric §14 - Testability] covers the fixture role; [Rubric §7 - Microservices Readiness] is what the stub half protects, since a module that can be switched off without breaking the modules that import its contract is a module that can also be extracted (ADR-059).
    • +
    • Walkthrough
        +
      • Name => "FakeDependent" (:33): the key ModulesSettings entries and other modules' dependency lists match on.
      • +
      • Dependencies => ["FakeLeaf", "FakeOther"] (:35): two entries, one of which (FakeLeafModule) exists in the fixture set and one of which deliberately does not, so the list is a pure data declaration rather than a resolvable graph.
      • +
      • RequiresDependencies => true (:37): the flag that turns a disabled dependency into a startup failure instead of a substituted stub.
      • +
      • Register(...) (:39-:41): an empty body. Registration behavior is not what the conformance base asserts, so the fixture spends nothing on it.
      • +
      • RegisterDisabledStubs(IServiceCollection services) (:43-:44): a single expression-bodied services.AddSingleton<IFakeExportService, DisabledFakeExportService>(), which is the only member with observable behavior and the one FakeDependentModuleConformanceTests inspects.
      • +
      +
    • +
    • Why it's built this way - the doc comment (:30) names the intent: this is the shape of the two real consumer modules that are not leaves (Store Sales, ADC Notification). Modeling them locally means the shared base is proven against both module shapes inside MMCA.Common's own CI, before any consumer sees it (ADR-015).
    • +
    • Where it's used - the TModule of FakeDependentModuleConformanceTests (:60) and of the deliberately-drifted DriftedTests (:131).
    • +
    +

    FakeLeafModule

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Layering · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Layering/ModuleConformanceTestsBaseTests.cs:15 · Level 2 · class

    +
    +
      +
    • What it is - the minimal module fixture: Name plus an empty Register, and nothing else. Its whole purpose is to not override Dependencies or RequiresDependencies, so the conformance base has to reach the interface's default implementations to read them.
    • +
    • Depends on - IModule (public sealed class FakeLeafModule : IModule, ModuleConformanceTestsBaseTests.cs:15) and ApplicationSettings (the Register parameter, :19).
    • +
    • Concept introduced - default interface implementations are only reachable through the interface. A default member declared on IModule (IModule.cs:17,:23) does not exist on the concrete class, so module.Dependencies will not compile against FakeLeafModule and a reflection lookup on the concrete type finds nothing. The shared base solves this by locating the IModule interface by full name and reading the property off the interface (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Layering/ModuleConformanceTestsBase.cs:80-:99), which dispatches to the override when there is one and to the framework default when there is not. Before the base existed, the hand-written consumer tests needed an explicit (IModule) cast to get the same reach (class doc, :9-:14). [Rubric §14 - Testability] and [Rubric §1 - SOLID] both apply: the fixture exists so the base's interface-dispatch contract is provable rather than assumed.
    • +
    • Walkthrough - Name => "FakeLeaf" (:17) and an empty Register(...) (:19-:21). That is the entire type; the absence of members is the fixture.
    • +
    • Why it's built this way - this is the exact shape the three byte-identical consumer {X}ModuleTests files collapse into (class doc, :12-:13), so the leaf path is the most-travelled one and the one whose silent breakage would be widest.
    • +
    • Where it's used - the TModule of FakeLeafModuleConformanceTests (:51), which in turn is driven directly by two of ModuleConformanceTestsBaseTests's facts (:112-:129).

    FixtureCompliantV1, FixtureCompliantV2, FixtureCompliantV3, FixtureContestedV1, FixtureContestedV2, FixtureContestedV3, FixtureBackwardsV1, FixtureBackwardsV2

    @@ -3190,79 +3246,301 @@

    ADR-090. +
  • Where they're used - the type arguments of the five fixture upcasters (FixtureCompliantV1ToV2Upcaster and family), named by nameof in the assertions of EventUpcasterFitnessTests, and, because they are this assembly's only integration events, the live contract IntegrationEventContractTestsBaseTests mutates.
  • + +

    FixtureCleanEvent

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.PayloadPurityFixtures.IntegrationEvents · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/PayloadPurityFixtures/IntegrationEvents/PayloadPurityFixtures.cs:27 · Level 3 · record

    +
    +
      +
    • What it is - the control fixture for the integration-event payload-purity rule: a one-parameter positional record whose only member is a primitive string Sku, proving the rule stays quiet on a contract that is actually clean.
    • +
    • Depends on - BaseIntegrationEvent (internal sealed record FixtureCleanEvent(string Sku) : BaseIntegrationEvent;, PayloadPurityFixtures.cs:27). Sits beside its offending sibling, FixtureLeakingEvent, which nests a payload typed on AuditableBaseEntity<int> (:12-:15, :20) purely to give the rule something to flag.
    • +
    • Concept introduced - cross-references SchemaVersion as the ordering a fixture set has to make visible from the FixtureCompliantV1 and family section: both fixture sets sit in the same PayloadPurityFixtures/UpcasterFixtures *.IntegrationEvents namespace convention so the residency rule stays satisfied while they prove an unrelated rule. [Rubric §9 - API & Contract Design] applies because a published event is a wire contract, and payload purity (no domain entities crossing the wire) is part of that contract; [Rubric §14 - Testability] covers the positive/negative fixture pair.
    • +
    • Where it's used - the clean half of IntegrationEventPayloadPurityTests (2 references) and one more site, proving the rule does not flag a contract carrying only primitives.
    • +
    +

    FakeDependentModuleConformanceTests

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Layering · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Layering/ModuleConformanceTestsBaseTests.cs:60 · Level 3 · class

    +
    +
      +
    • What it is - the shared module-conformance base wired up against the "hard" fixture module: the subclass shape a real non-leaf module (Store Sales, ADC Notification) uses, with all three expectations declared and the disabled-stub hook implemented.
    • +
    • Depends on - ModuleConformanceTestsBase<TModule> (public sealed class FakeDependentModuleConformanceTests : ModuleConformanceTestsBase<FakeDependentModule>, ModuleConformanceTestsBaseTests.cs:60), FakeDependentModule, IFakeExportService, DisabledFakeExportService, and Microsoft.Extensions.DependencyInjection's ServiceCollection / ServiceLifetime (:70,:77).
    • +
    • Concept introduced - asserting on a ServiceCollection instead of a built container. The disabled-stub hook is a registration contract, not a resolution one, so the fixture never builds a provider: it constructs a bare ServiceCollection (:70), calls RegisterDisabledStubs on it (:72), and then inspects the resulting ServiceDescriptor directly (:74). That is what lets one assertion cover all three things that can go wrong (wrong service type, wrong implementation type, wrong lifetime) without any of the module's real dependencies having to exist. [Rubric §7 - Microservices Readiness] is the property under guard: a module that can be switched off while its exported contract stays resolvable is a module that can be extracted. [Rubric §14 - Testability] covers the technique.
    • +
    • Walkthrough - three property overrides declare the expectations: ExpectedName => "FakeDependent" (:62), ExpectedDependencies => ["FakeOther", "FakeLeaf"] (:64), and ExpectedRequiresDependencies => true (:66). The dependency list is written in the opposite order to the module's own declaration (:35), which is deliberate: the base compares with BeEquivalentTo (ModuleConformanceTestsBase.cs:51), so order must not matter, and writing it reversed proves it does not. AssertDisabledStubs(FakeDependentModule module) (:68-:78) is the one real override: single descriptor for IFakeExportService (:74-:75), implementation type DisabledFakeExportService (:76), lifetime Singleton (:77).
    • +
    • Where it's used - collected and run directly by xUnit (it is public and sealed, so its four inherited [Fact]s are real tests of the fixture module), and referenced by ModuleConformanceTestsBaseTests as the non-leaf half of the base's coverage.
    • +
    +

    FakeLeafModuleConformanceTests

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Layering · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Layering/ModuleConformanceTestsBaseTests.cs:51 · Level 3 · class

    +
    +
      +
    • What it is - the same shared base wired up against the leaf fixture, declaring only ExpectedName and leaving every other expectation at the base's default. It is the exact subclass shape the three byte-identical consumer {X}ModuleTests files collapse into.
    • +
    • Depends on - ModuleConformanceTestsBase<TModule> (public sealed class FakeLeafModuleConformanceTests : ModuleConformanceTestsBase<FakeLeafModule>, ModuleConformanceTestsBaseTests.cs:51) and FakeLeafModule.
    • +
    • Concept - cross-references the default-interface-dispatch concept from FakeLeafModule. The pairing is what makes the proof work: the fixture module declares neither Dependencies nor RequiresDependencies, and this subclass declares neither expectation, so the base's defaults ([] and false, ModuleConformanceTestsBase.cs:29,:35) must line up with IModule's defaults read through the interface. If either side stopped reaching the interface, this pair would go red.
    • +
    • Walkthrough - one member: ExpectedName => "FakeLeaf" (:53). Everything else is inherited, including the vacuous AssertDisabledStubs default, which is exactly what makes the fourth fact a no-op here.
    • +
    • Where it's used - collected by xUnit in its own right, and driven directly (as an object, not as a test class) by two of ModuleConformanceTestsBaseTests's facts (:112-:129).
    • +
    +

    MutableFixtureId, ExtraStateFixtureId

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.StronglyTypedIdFixtures · (see per-type table) · Level 3 · record struct

    +
    +
      +
    • What it is - two of the three offending shapes the StronglyTypedIdsAreReadonlyRecordStructs fitness rule (ADR-115) is proven against: a record struct missing readonly, and a readonly record struct that wraps a second value alongside the primitive.
    • +
    • Depends on - IStronglyTypedId<TSelf, TValue>, the interface every wrapper in this file (StronglyTypedIdFixtures.cs) implements. Both sit alongside two compliant siblings, CompliantFixtureId and CompliantStringFixtureId, and one non-record sibling, NotARecordFixtureId, all in the same file, none of which the rule flags for the two shapes below.
    • +
    • Concept introduced - why the wrapper shape itself is the contract, not just its interface. The strongly-typed-identifier opt-in (MMCA.Common/CLAUDE.md, "Strongly typed identifiers") is two lines: a readonly record struct plus a From factory. MutableFixtureId drops readonly (record struct MutableFixtureId(int Value) : IStronglyTypedId<MutableFixtureId, int>, StronglyTypedIdFixtures.cs:53), so its wrapped value could be reassigned after an entity has been keyed on it. ExtraStateFixtureId carries a second field, Label (readonly record struct ExtraStateFixtureId(int Value, string Label) : IStronglyTypedId<ExtraStateFixtureId, int>, :63-:64), which makes the EF column mapping ambiguous and the converter's round trip lossy; its From factory only ever fills the primitive and leaves Label at string.Empty (:67), so the extra state is dead weight by construction. Neither shape fails to compile: only reflection over the compiled type (record-ness, readonly-ness, declared instance field count) can catch them, which is why they are compiled fixtures rather than inline test doubles. [Rubric §14 - Testability] covers the fixture technique; [Rubric §5 - Domain Model] is what the rule protects, a value object that cannot silently mutate or smuggle untyped state past its wrapper.
    • +
    • Walkthrough - MutableFixtureId.From(int value) => new(value) (:56) is otherwise identical to the compliant declaration; the only diff is the missing readonly modifier on the struct itself. ExtraStateFixtureId.From(int value) => new(value, string.Empty) (:67) is the whole type: a two-line factory that proves the second constructor parameter is reachable but never legitimately populated by the framework's own convention.
    • +
    +
    + + + + + + + + + + + + + + + + + +
    TypeFile:LineNotes (what differs)
    MutableFixtureIdMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/StronglyTypedIdFixtures/StronglyTypedIdFixtures.cs:53record struct, not readonly record struct: the missing modifier is the offense.
    ExtraStateFixtureIdMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/StronglyTypedIdFixtures/StronglyTypedIdFixtures.cs:63readonly record struct but wraps a second field, Label, beyond the primitive Value.
    +
      +
    • Where it's used - both are the subject of one [Fact] each on StronglyTypedIdFitnessTests: Rule_FlagsAWrapperThatIsNotReadonly asserts the thrown message contains nameof(MutableFixtureId) and the word "readonly" (StronglyTypedIdFitnessTests.cs:23-:31), and Rule_FlagsAWrapperCarryingExtraState asserts it contains nameof(ExtraStateFixtureId) and "Label" (:33-:41).
    • +
    +

    EmptyScanTests

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Api · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Api/AnonymousEndpointTestsBaseTests.cs:178 · Level 3 · class

    +
    +
      +
    • What it is - the adversarial fixture for the anonymous-endpoint gate's non-vacuity guard: a subclass pointed at an assembly that contains no controllers and no routable components at all, so the scan finds nothing and the guard must fail.
    • +
    • Depends on - AnonymousEndpointTestsBase (private sealed class EmptyScanTests : AnonymousEndpointTestsBase, AnonymousEndpointTestsBaseTests.cs:178) and the MMCA.Common.Shared assembly reached through typeof(Shared.Abstractions.Result).Assembly (:121).
    • +
    • Concept introduced - guarding the guard: why a fitness function needs a floor. The allow-list assertion is offenders.Should().BeEmpty() (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Api/AnonymousEndpointTestsBase.cs:85-:62), and an empty scan produces zero offenders, so the gate passes loudest exactly when it has stopped looking. A renamed assembly, a moved anchor type, or a repo re-layout all produce that failure mode, and none of them is visible in a green run. The base therefore ships a third fact, ScannedEndpointSet_IsNotEmpty (:66-:76), that counts the discovered controller and routable-component types and requires at least MinimumScannedTypes, whose default is 1 (:51). This fixture is what proves the counter is real: point it at an assembly that genuinely has neither shape and the fact must throw. Compare the equivalent floors elsewhere in the suite: MinimumScannedTypes => 21 on AnonymousEndpointTests, MinimumBaseResources => 3 on LocalizationResourceTests, MinimumRoutes = 8 in NavigationContractTests, and the DistinctErrorCodeCount and HandledCommandCount inventories the catalog and validator rules expose for the same purpose. [Rubric §14 - Testability] assesses whether the guardrails are themselves trustworthy, and a vacuity floor is the cheapest thing that keeps one honest over a decade of refactors.
    • +
    • Walkthrough - two overrides and an inline comment that states why the chosen assembly works: the Shared package has neither controllers nor routable components (:119). TargetAssemblies returns that one assembly (:120-:121), and AllowedAnonymousEndpoints is empty (:123), which is irrelevant here because nothing is discovered to compare against. Being private and nested is what keeps xUnit from collecting its three inherited facts as deliberately-red tests of their own (class doc, :10-:11).
    • +
    • Where it's used - the subject of Base_Fails_WhenNothingWasScanned (AnonymousEndpointTestsBaseTests.cs:37-:43), which converts the inherited ScannedEndpointSet_IsNotEmpty into a delegate and asserts it throws (:40-:42).
    • +
    • Caveats / not-in-source - that fact asserts only that an exception is thrown, not what its message says. It proves the floor bites; it does not pin the wording.
    • +
    +

    CompliantStringFixtureId

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.StronglyTypedIdFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/StronglyTypedIdFixtures/StronglyTypedIdFixtures.cs:24 · Level 3 · record struct

    +
    +
      +
    • What it is - the same positive control as CompliantFixtureId, over a string primitive instead of int.
    • +
    • Depends on - IStronglyTypedId<TSelf, TValue> closed over itself and string (public readonly record struct CompliantStringFixtureId(string Value) : IStronglyTypedId<CompliantStringFixtureId, string>, StronglyTypedIdFixtures.cs:24-:25).
    • +
    • Concept introduced - cross-references CompliantFixtureId. Its contribution is discrimination across the primitive type: the fitness rule inspects the shape of the wrapper, not its backing type, and a rule that only ever saw an int-backed compliant declaration could not prove it treats a string-backed one the same way.
    • +
    • Walkthrough - the primary constructor parameter Value (:24) and one static factory, From(string value) => new(value) (:27).
    • +
    • Where it's used - the same IdentifierFixtureMap scan and the same Rule_SaysNothingAboutTheCanonicalDeclaration assertion as CompliantFixtureId (StronglyTypedIdFitnessTests.cs:50).
    • +
    +

    AcyclicConsumer

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.CycleFixtures.Acyclic · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/CycleFixtures/Acyclic/AcyclicFixtures.cs:6 · Level 3 · class

    +
    +
      +
    • What it is - the negative control for the namespace-cycle rule: a third fixture namespace that points into Left and that nothing points back at, so it must never appear in a cycle report.
    • +
    • Depends on - LeftService (the one-way Service property, AcyclicFixtures.cs:9, using at :1).
    • +
    • Concept - cross-references LeftModelBase. Its role is the discrimination half of the proof: a rule that reported every namespace with any edge would also "pass" the cycle test, so the fixture set needs a namespace that is genuinely coupled yet genuinely acyclic. Depending on Left (a namespace that is in a cycle) is deliberate: it proves the rule reports strongly connected components, not merely everything reachable from a cycle.
    • +
    • Walkthrough - one member, public LeftService? Service { get; set; } (:9).
    • +
    • Where it's used - asserted absent from the failure message by Rule_FlagsTwoNamespaceCycle_ButNotAcyclicNamespaces (NamespaceCycleFitnessTests.cs:23-:25).
    • +
    +

    ArchiveTicketHandler

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.CommandValidatorFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/CommandValidatorFixtures/CommandValidatorFixtures.cs:62 · Level 3 · class

    +
    +
      +
    • What it is - the handler for ArchiveTicketCommand, a command that carries data (TicketId) and has a handler but no validation of any kind: the shape the command-validator coverage rule must flag.
    • +
    • Depends on - ICommandHandler<ArchiveTicketCommand, Result> (MMCA.Common.Application.UseCases.Contracts) and ArchiveTicketCommand (the record it handles, :60,:62).
    • +
    • Concept introduced - cross-references CreateTicketHandler. Its contribution is discrimination at the far end of the coverage spectrum: CreateTicketHandler has direct coverage and UpdateTicketHandler has bridge coverage, while this one has neither, so it is what proves the rule actually detects an uncovered command rather than passing everything with a handler.
    • +
    • Walkthrough - one member, HandleAsync(ArchiveTicketCommand command, CancellationToken cancellationToken = default) => Task.FromResult(Result.Success()) (:64-:65). No constructor, no validator anywhere in the file for ArchiveTicketCommand.
    • +
    • Where it's used - CommandWithNoValidation_IsFlagged asserts nameof(ArchiveTicketCommand) present in the coverage failure message (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/CommandValidatorCoverageFitnessTests.cs:23-:31); AllowlistedCommand_IsExempt reuses it as the fixture that must stay flagged once a different command is allowlisted, proving the allowlist narrows to exactly the name it lists (:78-:89).
    • +
    +

    CompliantFixtureId

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.StronglyTypedIdFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/StronglyTypedIdFixtures/StronglyTypedIdFixtures.cs:17 · Level 3 · record struct

    +
    +
      +
    • What it is - the positive control for the strongly typed ID fitness function (ADR-115): the canonical declaration shape, an int-backed wrapper, that the rule must say nothing about.
    • +
    • Depends on - IStronglyTypedId<TSelf, TValue> closed over itself and int (public readonly record struct CompliantFixtureId(int Value) : IStronglyTypedId<CompliantFixtureId, int>, StronglyTypedIdFixtures.cs:17).
    • +
    • Concept introduced - the two-line strongly typed ID is exactly two lines, no more. A readonly record struct with a positional Value and a static From factory is the whole contract; nothing else is required and nothing else is permitted before the rule starts flagging (see NotARecordFixtureId, MutableFixtureId, ExtraStateFixtureId for the three ways to fail it). This fixture is the shape those three are measured against: a rule that flagged it too would be unusable, since it is the identical shape every real identifier in Source/ would take if one adopted the opt-in.
    • +
    • Walkthrough - the primary constructor parameter Value (:17) and one static factory, From(int value) => new(value) (:18), documented as "the one member the canonical declaration writes" (:18).
    • +
    • Where it's used - the IdentifierFixtureMap assembly scan in StronglyTypedIdFitnessTests (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/StronglyTypedIdFitnessTests.cs:53-:58) includes it by assembly membership, and Rule_SaysNothingAboutTheCanonicalDeclaration asserts its name is absent from the failure message (:44-:50).
    • +
    +

    CreateTicketHandler

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.CommandValidatorFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/CommandValidatorFixtures/CommandValidatorFixtures.cs:21 · Level 3 · class

    +
    +
      +
    • What it is - the handler for CreateTicketCommand, the direct-coverage positive control: an explicit CreateTicketCommandValidator (AbstractValidator<CreateTicketCommand>, :16-:19) validates the command itself.
    • +
    • Depends on - ICommandHandler<CreateTicketCommand, Result> and CreateTicketCommand (:13,:21).
    • +
    • Concept introduced - cross-references ArchiveTicketHandler. Its contribution is the coverage baseline: AddValidatorsFromAssembly picks up CreateTicketCommandValidator by its IValidator<CreateTicketCommand> closure with no bridge involved, which is the simplest of the three coverage shapes the rule must recognize (direct, bridge, and none).
    • +
    • Walkthrough - one member, HandleAsync(CreateTicketCommand command, CancellationToken cancellationToken = default) => Task.FromResult(Result.Success()) (:23-:24).
    • +
    • Where it's used - CommandWithItsOwnValidator_IsCovered asserts nameof(CreateTicketCommand) absent from the coverage failure message (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/CommandValidatorCoverageFitnessTests.cs:34-:42); its assembly anchors FixtureModuleMap's single module (typeof(CreateTicketCommand).Assembly, :121).
    • +
    +

    DriftedTests

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Layering · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Layering/ModuleConformanceTestsBaseTests.cs:131 (and a same-named sibling in Api/AnonymousEndpointTestsBaseTests.cs:100) · Level 3 · class

    +
    +
      +
    • What it is - the name is used twice in this assembly, once per fitness base under proof, and both are the same idea: a private sealed subclass of a shipped *TestsBase whose configuration is deliberately wrong, so the base's assertions can be shown to actually fail on the drift they claim to catch. They are nested in different outer classes, so their full names differ and neither is visible outside its own file.
    • +
    • Depends on - the base each one drifts from, plus the fixture it points at: ModuleConformanceTestsBase<TModule> with FakeDependentModule for one, AnonymousEndpointTestsBase with the fixture controllers for the other.
    • +
    • Concept introduced - negative fixtures, and hiding them from test discovery. A fitness base that asserts nothing passes everywhere; the only way to know an assertion bites is to feed it a case that must fail. But a public subclass of an xUnit base is itself collected, so its inherited [Fact]s would run and report as red tests of their own. Declaring the drifted subclass private and nested keeps xUnit from collecting it, while the enclosing class can still instantiate it and invoke the inherited methods directly as delegates (var assert = new DriftedTests().Module_ShouldDeclare_ExpectedName;, ModuleConformanceTestsBaseTests.cs:91). Both class docs record that reasoning in the same words (ModuleConformanceTestsBaseTests.cs:81-:85, AnonymousEndpointTestsBaseTests.cs:10-:11). [Rubric §14 - Testability] assesses whether the guardrails themselves are trustworthy; this is the fixture shape that earns that trust. Compare NavigatingSpec, the same negative-fixture technique applied to a specification rule, and ProbeTests, which parameterizes the drift instead of hard-coding it.
    • +
    +
    + + + + + + + + + + + + + + + +
    TypeFile:LineWhat is deliberately wrong
    DriftedTests (in AnonymousEndpointTestsBaseTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Api/AnonymousEndpointTestsBaseTests.cs:161Points TargetAssemblies at this test assembly (:102-:103), so the scan finds the fixture controllers' [AllowAnonymous] attributes, and then supplies an empty AllowedAnonymousEndpoints (:105). Every discovered endpoint is therefore an offender, and the failure message must name AnonymousFixtureController.
    DriftedTests (in ModuleConformanceTestsBaseTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Layering/ModuleConformanceTestsBaseTests.cs:131Three overrides, each wrong in a different way against FakeDependentModule's real declarations: ExpectedName => "NotTheDeclaredName" (:133) against the module's "FakeDependent" (:33); ExpectedDependencies => ["FakeLeaf"] (:135) against the module's two-entry ["FakeLeaf", "FakeOther"] (:35), so one dependency is missing; and ExpectedRequiresDependencies => false (:137) against the module's true (:37). AssertDisabledStubs is deliberately not overridden, so the fourth inherited fact stays vacuous here.
      -
    • Why they're built this way - three groups because the two rules have three distinct outcomes to prove between them (clean, duplicate claim, wrong direction), and because a chain has to be distinguishable from a duplicate claim. Nothing but Sku on any of them: the payload is irrelevant to both rules, and every byte of fixture that is not load-bearing is a byte that can mislead a future reader about what is being tested. See ADR-090.
    • -
    • Where they're used - the type arguments of the five fixture upcasters (FixtureCompliantV1ToV2Upcaster and family), named by nameof in the assertions of EventUpcasterFitnessTests, and, because they are this assembly's only integration events, the live contract IntegrationEventContractTestsBaseTests mutates.
    • +
    • Walkthrough - one wrong value per assertion, and no more. If a single drifted subclass were wrong in three ways at once and the base only ever threw on the first, the other two assertions could rot undetected; the module-side fixture avoids that by being driven three separate times, once per fact.
    • +
    • Where it's used - the anonymous-endpoint one drives Base_Fails_WhenAnAnonymousEndpointIsNotAllowListed (AnonymousEndpointTestsBaseTests.cs:15-:24); the module one is instantiated three times by ModuleConformanceTestsBaseTests (:91,:99,:107), once per assertion under proof.
    -

    FixtureCleanEvent

    +

    NotARecordFixtureId

    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.PayloadPurityFixtures.IntegrationEvents · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/PayloadPurityFixtures/IntegrationEvents/PayloadPurityFixtures.cs:27 · Level 3 · record

    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.StronglyTypedIdFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/StronglyTypedIdFixtures/StronglyTypedIdFixtures.cs:40 · Level 3 · struct

      -
    • What it is - the control fixture for the integration-event payload-purity rule: a one-parameter positional record whose only member is a primitive string Sku, proving the rule stays quiet on a contract that is actually clean.
    • -
    • Depends on - BaseIntegrationEvent (internal sealed record FixtureCleanEvent(string Sku) : BaseIntegrationEvent;, PayloadPurityFixtures.cs:27). Sits beside its offending sibling, FixtureLeakingEvent, which nests a payload typed on AuditableBaseEntity<int> (:12-:15, :20) purely to give the rule something to flag.
    • -
    • Concept introduced - cross-references SchemaVersion as the ordering a fixture set has to make visible from the FixtureCompliantV1 and family section: both fixture sets sit in the same PayloadPurityFixtures/UpcasterFixtures *.IntegrationEvents namespace convention so the residency rule stays satisfied while they prove an unrelated rule. [Rubric §9 - API & Contract Design] applies because a published event is a wire contract, and payload purity (no domain entities crossing the wire) is part of that contract; [Rubric §14 - Testability] covers the positive/negative fixture pair.
    • -
    • Where it's used - the clean half of IntegrationEventPayloadPurityTests (2 references) and one more site, proving the rule does not flag a contract carrying only primitives.
    • +
    • What it is - a negative fixture: a strongly-typed identifier wrapper implemented as a plain readonly struct rather than a readonly record struct, so the strongly-typed-id fitness rule can be proven to reject that shape.
    • +
    • Depends on - IStronglyTypedId<TSelf, TValue>, which it implements over int (public readonly struct NotARecordFixtureId(int value) : IStronglyTypedId<NotARecordFixtureId, int>, StronglyTypedIdFixtures.cs:40).
    • +
    • Concept introduced - cross-references the negative-fixture technique explained at DriftedTests (a deliberately-wrong subclass or shape used to prove an assertion actually bites). Here the "wrong" axis is the CLR shape itself, not a configuration override: the rule requires record struct so it gets value equality and an immutable, printable wrapper for free, and this fixture is the one type in the suite that withholds exactly that. [Rubric §14 - Testability] applies.
    • +
    • Walkthrough - a primary-constructor readonly struct with one member, public int Value { get; } = value; (:29), and the required factory public static NotARecordFixtureId From(int value) => new(value); (:32). Both satisfy IStronglyTypedId<TSelf, TValue>'s contract; only the record keyword is missing.
    • +
    • Why it's built this way - a rule that only checked the interface would pass this fixture, which is precisely the gap StronglyTypedIdsAreReadonlyRecordStructs closes by additionally reflecting on the CLR type shape.
    • +
    • Where it's used - Rule_FlagsAWrapperThatIsNotARecord (StronglyTypedIdFitnessTests.cs:13-:21), which asserts the thrown message names NotARecordFixtureId and mentions "record".
    -

    FakeDependentModuleConformanceTests

    +

    NoEntityDataSources

    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Layering · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Layering/ModuleConformanceTestsBaseTests.cs:60 · Level 3 · class

    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Domain · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/DeleteBehaviorConventionTests.cs:112 · Level 3 · class

      -
    • What it is - the shared module-conformance base wired up against the "hard" fixture module: the subclass shape a real non-leaf module (Store Sales, ADC Notification) uses, with all three expectations declared and the disabled-stub hook implemented.
    • -
    • Depends on - ModuleConformanceTestsBase<TModule> (public sealed class FakeDependentModuleConformanceTests : ModuleConformanceTestsBase<FakeDependentModule>, ModuleConformanceTestsBaseTests.cs:60), FakeDependentModule, IFakeExportService, DisabledFakeExportService, and Microsoft.Extensions.DependencyInjection's ServiceCollection / ServiceLifetime (:70,:77).
    • -
    • Concept introduced - asserting on a ServiceCollection instead of a built container. The disabled-stub hook is a registration contract, not a resolution one, so the fixture never builds a provider: it constructs a bare ServiceCollection (:70), calls RegisterDisabledStubs on it (:72), and then inspects the resulting ServiceDescriptor directly (:74). That is what lets one assertion cover all three things that can go wrong (wrong service type, wrong implementation type, wrong lifetime) without any of the module's real dependencies having to exist. [Rubric §7 - Microservices Readiness] is the property under guard: a module that can be switched off while its exported contract stays resolvable is a module that can be extracted. [Rubric §14 - Testability] covers the technique.
    • -
    • Walkthrough - three property overrides declare the expectations: ExpectedName => "FakeDependent" (:62), ExpectedDependencies => ["FakeOther", "FakeLeaf"] (:64), and ExpectedRequiresDependencies => true (:66). The dependency list is written in the opposite order to the module's own declaration (:35), which is deliberate: the base compares with BeEquivalentTo (ModuleConformanceTestsBase.cs:51), so order must not matter, and writing it reversed proves it does not. AssertDisabledStubs(FakeDependentModule module) (:68-:78) is the one real override: single descriptor for IFakeExportService (:74-:75), implementation type DisabledFakeExportService (:76), lifetime Singleton (:77).
    • -
    • Where it's used - collected and run directly by xUnit (it is public and sealed, so its four inherited [Fact]s are real tests of the fixture module), and referenced by ModuleConformanceTestsBaseTests as the non-leaf half of the base's coverage.
    • +
    • What it is - a private nested stub IEntityDataSourceRegistry that answers every lookup with the default SQL Server key and every TryGet with false, used to build the minimal, real ApplicationDbContext model the delete-behavior convention test needs without wiring the framework's actual multi-source registry.
    • +
    • Depends on - IEntityDataSourceRegistry (the interface it implements, DeleteBehaviorConventionTests.cs:112) and DataSourceKey / DataSource (DataSourceKey.Default(DataSource.SQLServer), :114,:116). Registered as the singleton behind a bare ServiceCollection built by DeleteBehaviorConventionTests's own model-building helper (services.AddSingleton<IEntityDataSourceRegistry>(new NoEntityDataSources());, :96).
    • +
    • Concept introduced - a no-op collaborator has to be honest about doing nothing. The class doc (:107-:111) explains the shape: the two GetDataSourceKey overloads answer the default key rather than throw, and TryGetDataSourceKey always answers false, because nothing in this test's model-building path calls the "get" overloads and a throwing fixture would trip this repo's own domain-throw architecture rule. With every entity answering TryGet = false, CrossDataSourceDegradeConvention (MMCA.Common/CLAUDE.md, "Multi-Database Strategy") treats every relationship as single-source, which is exactly the FK-constraints-intact case the delete-behavior rule is checking. [Rubric §14 - Testability] is the concern: a stub collaborator that only ever answers the one shape the test under proof needs, and nothing more.
    • +
    • Walkthrough - GetDataSourceKey(Type) and GetDataSourceKey(string) both return DataSourceKey.Default(DataSource.SQLServer) (:114,:116); TryGetDataSourceKey(string, out DataSourceKey) sets key = default and returns false (:118-:122); GetPhysicalSourcesInUse() returns an empty array (:124).
    • +
    • Where it's used - registered by the private Services() helper (:88-:98) that builds the ServiceProvider behind the model-building test context in DeleteBehaviorConventionTests; no site outside the defining file references it.
    -

    FakeLeafModuleConformanceTests

    +

    FixtureCountEvent, FixtureNullableCountEvent

    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Layering · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Layering/ModuleConformanceTestsBaseTests.cs:51 · Level 3 · class

    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents · (see per-type table) · Level 3 · record

    +

    A twin pair of compiled integration-event fixtures that differ in exactly one thing: whether their single Count member is int or int?. Each one's summary names the other as its twin "differing only in the member's nullability" (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:31,:35).

      -
    • What it is - the same shared base wired up against the leaf fixture, declaring only ExpectedName and leaving every other expectation at the base's default. It is the exact subclass shape the three byte-identical consumer {X}ModuleTests files collapse into.
    • -
    • Depends on - ModuleConformanceTestsBase<TModule> (public sealed class FakeLeafModuleConformanceTests : ModuleConformanceTestsBase<FakeLeafModule>, ModuleConformanceTestsBaseTests.cs:51) and FakeLeafModule.
    • -
    • Concept - cross-references the default-interface-dispatch concept from FakeLeafModule. The pairing is what makes the proof work: the fixture module declares neither Dependencies nor RequiresDependencies, and this subclass declares neither expectation, so the base's defaults ([] and false, ModuleConformanceTestsBase.cs:29,:35) must line up with IModule's defaults read through the interface. If either side stopped reaching the interface, this pair would go red.
    • -
    • Walkthrough - one member: ExpectedName => "FakeLeaf" (:53). Everything else is inherited, including the vacuous AssertDisabledStubs default, which is exactly what makes the fourth fact a no-op here.
    • -
    • Where it's used - collected by xUnit in its own right, and driven directly (as an object, not as a test class) by two of ModuleConformanceTestsBaseTests's facts (:112-:129).
    • +
    • Depends on - BaseIntegrationEvent (both derive from it directly, ContractShapeFixtures.cs:33,:37). They are scanned by the frozen-contract gate IntegrationEventContractTestsBase through the self-test's private FixtureMap (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/IntegrationEventContractTestsBaseTests.cs:254).
    • +
    • Concept introduced - a twin fixture isolates one variable. Two types identical in every other respect mean any difference in their rendered contract line can only come from the one axis that differs, here value-type nullability. An int retyped to int? changes what a consumer must accept, so a snapshot that printed both as Int32 would let a breaking wire change through silently. This is the same isolate-one-variable technique the negative fixtures in this chapter use (see DriftedTests). [Rubric §14 - Testability] assesses whether the code's guarantees are provable by tests; this pair proves the contract renderer does not erase Nullable<T>.
    • +
    • Walkthrough - each is a one-line positional internal sealed record with one member; there is no body.
    -

    MutableFixtureId, ExtraStateFixtureId

    +
    + + + + + + + + + + + + + + + + + +
    TypeFile:LineWhat differs
    FixtureCountEventMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:33(int Count): the non-nullable member.
    FixtureNullableCountEventMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:37(int? Count): the nullable member.
    +
      +
    • Why they're built this way - the assertion compares the two rendered member lists against each other rather than against hard-coded strings, so it stays true whatever spelling the renderer chooses for a nullable value type, as long as the two differ.
    • +
    • Where they're used - Contract_Differs_WhenTheOnlyChangeIsIntToNullableInt in IntegrationEventContractTestsBaseTests (IntegrationEventContractTestsBaseTests.cs:117-:124), which asserts MembersOf(contract, ".FixtureNullableCountEvent ") is not equal to MembersOf(contract, ".FixtureCountEvent ").
    • +
    +

    FixtureLabelEvent, FixtureNullableLabelEvent

    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.StronglyTypedIdFixtures · (see per-type table) · Level 3 · record struct

    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents · (see per-type table) · Level 3 · record

    +

    The reference-type counterpart of FixtureCountEvent, FixtureNullableCountEvent: a twin pair whose only difference is nullable reference annotations, both on a plain member (Label) and on a generic argument (Notes). Each summary names the other as its twin "differing only in nullable annotations" (ContractShapeFixtures.cs:39,:44).

      -
    • What it is - two of the three offending shapes the StronglyTypedIdsAreReadonlyRecordStructs fitness rule (ADR-115) is proven against: a record struct missing readonly, and a readonly record struct that wraps a second value alongside the primitive.
    • -
    • Depends on - IStronglyTypedId<TSelf, TValue>, the interface every wrapper in this file (StronglyTypedIdFixtures.cs) implements. Both sit alongside two compliant siblings, CompliantFixtureId and CompliantStringFixtureId, and one non-record sibling, NotARecordFixtureId, all in the same file, none of which the rule flags for the two shapes below.
    • -
    • Concept introduced - why the wrapper shape itself is the contract, not just its interface. The strongly-typed-identifier opt-in (MMCA.Common/CLAUDE.md, "Strongly typed identifiers") is two lines: a readonly record struct plus a From factory. MutableFixtureId drops readonly (record struct MutableFixtureId(int Value) : IStronglyTypedId<MutableFixtureId, int>, StronglyTypedIdFixtures.cs:53), so its wrapped value could be reassigned after an entity has been keyed on it. ExtraStateFixtureId carries a second field, Label (readonly record struct ExtraStateFixtureId(int Value, string Label) : IStronglyTypedId<ExtraStateFixtureId, int>, :63-:64), which makes the EF column mapping ambiguous and the converter's round trip lossy; its From factory only ever fills the primitive and leaves Label at string.Empty (:67), so the extra state is dead weight by construction. Neither shape fails to compile: only reflection over the compiled type (record-ness, readonly-ness, declared instance field count) can catch them, which is why they are compiled fixtures rather than inline test doubles. [Rubric §14 - Testability] covers the fixture technique; [Rubric §5 - Domain Model] is what the rule protects, a value object that cannot silently mutate or smuggle untyped state past its wrapper.
    • -
    • Walkthrough - MutableFixtureId.From(int value) => new(value) (:56) is otherwise identical to the compliant declaration; the only diff is the missing readonly modifier on the struct itself. ExtraStateFixtureId.From(int value) => new(value, string.Empty) (:67) is the whole type: a two-line factory that proves the second constructor parameter is reachable but never legitimately populated by the framework's own convention.
    • +
    • Depends on - BaseIntegrationEvent (ContractShapeFixtures.cs:42,:47) and IReadOnlyList<T> (BCL). Scanned by IntegrationEventContractTestsBase.
    • +
    • Concept introduced - nullable reference annotations are not in the CLR type. Unlike int?, a string? is the same runtime type as string; the annotation lives in compiler-emitted nullability metadata. A contract renderer that read only PropertyType would print both twins identically, so this pair proves the renderer reads the annotation too, including on a generic argument (IReadOnlyList<string?>). A member that may now be null breaks a consumer that relied on it never being null, which is why the snapshot must see it. Cross-references the isolate-one-variable technique at FixtureCountEvent, FixtureNullableCountEvent; [Rubric §14 - Testability] applies.
    • +
    • Walkthrough - one-line positional internal sealed records with two members each and no body.
    - + - - - + + + - - - + + +
    Type File:LineNotes (what differs)What differs
    MutableFixtureIdMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/StronglyTypedIdFixtures/StronglyTypedIdFixtures.cs:53record struct, not readonly record struct: the missing modifier is the offense.FixtureLabelEventMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:42(string Label, IReadOnlyList<string> Notes); renders as { Label:String, Notes:IReadOnlyList<String> } (IntegrationEventContractTestsBaseTests.cs:134-:135).
    ExtraStateFixtureIdMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/StronglyTypedIdFixtures/StronglyTypedIdFixtures.cs:63readonly record struct but wraps a second field, Label, beyond the primitive Value.FixtureNullableLabelEventMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:47(string? Label, IReadOnlyList<string?> Notes); renders as { Label:String?, Notes:IReadOnlyList<String?> } (:131-:132).
      -
    • Where it's used - both are the subject of one [Fact] each on StronglyTypedIdFitnessTests: Rule_FlagsAWrapperThatIsNotReadonly asserts the thrown message contains nameof(MutableFixtureId) and the word "readonly" (StronglyTypedIdFitnessTests.cs:23-:31), and Rule_FlagsAWrapperCarryingExtraState asserts it contains nameof(ExtraStateFixtureId) and "Label" (:33-:41).
    • +
    • Why they're built this way - unlike the value-type pair, these are pinned to exact strings, because the ? suffix is the very spelling under test: it is what makes the nullable twin render differently from the non-nullable one.
    • +
    • Where they're used - in IntegrationEventContractTestsBaseTests: Contract_Differs_WhenTheOnlyChangeIsANullableReferenceAnnotation (IntegrationEventContractTestsBaseTests.cs:127-:137) asserts both rendered member lists; Base_Fails_WhenOnlyAMembersNullabilityChanges (:140-:153) rewrites the FixtureNullableLabelEvent line from Label:String? to Label:String in a copy of the live contract and asserts the gate throws naming "member Label changed type".
    • +
    +

    FixtureNamedEvent

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:55 · Level 3 · record

    +
    +
      +
    • What it is - an integration-event fixture that declares a stable wire identity with [EventName("Fixture.Named.v1")] (ContractShapeFixtures.cs:54), so the frozen contract must key it on that name rather than on its CLR type name (summary, :49-:52).
    • +
    • Depends on - BaseIntegrationEvent and EventNameAttribute (MMCA.Common.Domain.Attributes, :1). Scanned by IntegrationEventContractTestsBase.
    • +
    • Concept introduced - the wire identity, not the CLR name, is the contract key. The [EventName] value is what the outbox stores, so a CLR rename or namespace move of an event that declares one must not churn the snapshot. [Rubric §7 - Microservices Readiness] assesses whether services can evolve independently over stable contracts; this fixture proves the contract gate keys on the identity a consumer actually sees. [Rubric §14 - Testability] applies too.
    • +
    • Walkthrough - internal sealed record FixtureNamedEvent(int Value) : BaseIntegrationEvent; (:55): one plain member, no body.
    • +
    • Where it's used - Contract_KeysAnEventOnItsEventName_WhenItDeclaresOne in IntegrationEventContractTestsBaseTests (IntegrationEventContractTestsBaseTests.cs:156-:166), which asserts the live contract contains Fixture.Named.v1 { Value:Int32 } and that no line contains FixtureNamedEvent.
    • +
    +

    FixtureShapedBase

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:10 · Level 3 · record

    +
    +
      +
    • What it is - an abstract intermediate integration-event base sitting between the framework envelope and a concrete fixture event. Its member is part of every derived event's wire shape, so the frozen contract must pin it (summary, ContractShapeFixtures.cs:6-:9).
    • +
    • Depends on - BaseIntegrationEvent (:10). Its one subclass is FixtureShapedEvent (:22), which adds int? Count and IReadOnlyList<string> Tags.
    • +
    • Concept introduced - two inheritance layers, two different owners. A consumer may put shared members on its own base record below BaseIntegrationEvent; those members travel on the wire like any other and belong in the event's contract. The framework envelope's own members, by contrast, are the framework's contract, not the event's. This fixture is what lets the self-test assert both halves of that line at once. [Rubric §14 - Testability] applies.
    • +
    • Walkthrough - public string Origin { get; init; } = string.Empty; (:13) is the single inherited member; being abstract, the record is never itself an event in the contract.
    • +
    • Why it's built this way - a renderer that read only DeclaredOnly properties of the concrete event would drop Origin; one that walked the whole chain would leak the envelope. The expected line, FixtureShapedEvent { Count:Nullable<Int32>, Origin:String, Tags:IReadOnlyList<String> }, rules out both.
    • +
    • Where it's used - only through FixtureShapedEvent: Contract_SpellsOutGenericArguments_AndPinsAnIntermediateBase in IntegrationEventContractTestsBaseTests (IntegrationEventContractTestsBaseTests.cs:86-:98) asserts that line, and that no ContractShapeFixtures line contains SchemaVersion: or DateOccurred: (:94-:97). No site outside the defining file names FixtureShapedBase itself.
    • +
    +

    FixtureTallyEvent

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:29 · Level 3 · record

    +
    +
      +
    • What it is - an integration-event fixture whose one member is a two-argument generic, IReadOnlyDictionary<string, int> Counts (ContractShapeFixtures.cs:29), so its contract line carries a comma inside angle brackets that the snapshot parser must not split on (summary, :24-:27).
    • +
    • Depends on - BaseIntegrationEvent and IReadOnlyDictionary<TKey, TValue> (BCL). Scanned by IntegrationEventContractTestsBase.
    • +
    • Concept introduced - a member-list parser must respect generic nesting. The snapshot line separates members with commas, and a naive split would cut IReadOnlyDictionary<String, Int32> into two bogus members, so a retyped argument would surface as a confusing add-and-remove rather than one changed member. Cross-references the twin-fixture technique at FixtureCountEvent, FixtureNullableCountEvent; [Rubric §14 - Testability] applies.
    • +
    • Walkthrough - a one-line positional internal sealed record with no body.
    • +
    • Where it's used - Base_Fails_WhenAGenericArgumentOfAMultiArgumentMemberChanges in IntegrationEventContractTestsBaseTests (IntegrationEventContractTestsBaseTests.cs:101-:114): it replaces <String, Int32> with <String, Int64> on the FixtureTallyEvent line of a copy of the live contract and asserts the gate throws naming "member Counts changed type".

    GetFixtureEntityHandlerBase<TQuery>

    @@ -3300,17 +3578,38 @@

    GetFixtureProjectionHandler

  • Why it's built this way - three constructor parameters put it one dependency over a tight ceiling (MaxHandlerConstructorDependencies => 2, ConstructorDependencyCountTestsBaseTests.cs:107) and exactly on a looser one (=> 3, :116), the two-sided proof a single fixture with the wrong count could not give on its own.
  • Where it's used - Handlers_AreFlagged_WhenEitherHandlerContractExceedsTheCeiling (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/ConstructorDependencyCountTestsBaseTests.cs:36-:49), which asserts the failure message names GetFixtureProjectionHandler (3 ctor dependencies); Handlers_AtTheCeiling_AreNotFlagged (:51-:58) reuses the same fixture, over the same FixtureMap (:74-:84), against the looser ceiling and asserts no throw.
  • -

    NotARecordFixtureId

    +
    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.StronglyTypedIdFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/StronglyTypedIdFixtures/StronglyTypedIdFixtures.cs:40 · Level 3 · struct

    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Domain · (see per-type table) · Level 6 · class

    +

    The same unsafe and safe pair as NavigatingSpec and ScalarOnlySpec, rebuilt one level further down the specification hierarchy as QuerySpecification<TEntity, TIdentifierType> subclasses. They exist to pin a single ruling: adding includes, ordering, and paging to a specification must not take it out of the navigation rule's reach.

      -
    • What it is - a negative fixture: a strongly-typed identifier wrapper implemented as a plain readonly struct rather than a readonly record struct, so the strongly-typed-id fitness rule can be proven to reject that shape.
    • -
    • Depends on - IStronglyTypedId<TSelf, TValue>, which it implements over int (public readonly struct NotARecordFixtureId(int value) : IStronglyTypedId<NotARecordFixtureId, int>, StronglyTypedIdFixtures.cs:40).
    • -
    • Concept introduced - cross-references the negative-fixture technique explained at DriftedTests (a deliberately-wrong subclass or shape used to prove an assertion actually bites). Here the "wrong" axis is the CLR shape itself, not a configuration override: the rule requires record struct so it gets value equality and an immutable, printable wrapper for free, and this fixture is the one type in the suite that withholds exactly that. [Rubric §14 - Testability] applies.
    • -
    • Walkthrough - a primary-constructor readonly struct with one member, public int Value { get; } = value; (:29), and the required factory public static NotARecordFixtureId From(int value) => new(value); (:32). Both satisfy IStronglyTypedId<TSelf, TValue>'s contract; only the record keyword is missing.
    • -
    • Why it's built this way - a rule that only checked the interface would pass this fixture, which is precisely the gap StronglyTypedIdsAreReadonlyRecordStructs closes by additionally reflecting on the CLR type shape.
    • -
    • Where it's used - Rule_FlagsAWrapperThatIsNotARecord (StronglyTypedIdFitnessTests.cs:13-:21), which asserts the thrown message names NotARecordFixtureId and mentions "record".
    • +
    • Depends on - QuerySpecification<TEntity, TIdentifierType> (both, SpecificationFitnessTests.cs:76,:89), FitnessDependent and FitnessPrincipal (the entities), and System.Linq.Expressions (:1).
    • +
    • Concept introduced - walking the whole base chain instead of matching one type. The rule selects candidates with HasBaseTypeStartingWith("MMCA.Common.Domain.Specifications.Specification") (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.Specifications.cs:7,:33), and that helper climbs BaseType all the way up (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/RuleHelpers.cs:88-:91). Because QuerySpecification itself derives from Specification, the prefix still matches two levels down, and the string prefix match means both Specification and QuerySpecification are caught by the one condition. That is the kind of thing that is true today and could quietly stop being true after a refactor of the specification hierarchy, which is exactly why it gets its own fact rather than being left as an assumption. [Rubric §7 - Microservices Readiness] and [Rubric §8 - Data Architecture] are the underlying properties (a navigating predicate cannot cross a data-source boundary, ADR-006); [Rubric §14 - Testability] is why the pair exists.
    • +
    • Walkthrough - both declare an explicit parameterless constructor, which is load-bearing: the rule instantiates candidates to read Criteria and skips any type without one (ArchitectureRules.Specifications.cs:37-:41). Each constructor also exercises a different part of the query surface, so the fixtures prove that the extra machinery does not interfere with the analysis.
    • +
    +
    + + + + + + + + + + + + + + + + + +
    TypeFile:LineWhat differs
    NavigatingQuerySpecMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/SpecificationFitnessTests.cs:76The offender. Its constructor adds ordering and paging, AddOrderBy(d => d.PrincipalId) and ApplyPaging(skip: 0, take: 10) (:79-:83), and its Criteria dereferences the navigation, d => d.Principal!.IsActive (:86). Must be reported.
    ScalarOnlyQuerySpecMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/SpecificationFitnessTests.cs:89The safe counterpart, and a deliberately awkward one: its constructor calls AddInclude(nameof(FitnessDependent.Principal)) (:92), so it does pull in the related entity, while its Criteria filters only on own columns (:95). Must not be reported. Includes are an eager-loading instruction, not a filter predicate, and the rule analyzes the predicate only.
    +
      +
    • Why they're built this way - ScalarOnlyQuerySpec is the sharper of the two. Without it, a future rule implementation that (reasonably but wrongly) also inspected Includes would still pass every other fixture in the file. Both are declared public while their plain-Specification siblings are private, which makes no difference to the rule (it scans ConcreteClasses across the assembly, not by visibility).
    • +
    • Where they're used - the two inputs to Rule_AlsoAnalyzesQuerySpecifications in SpecificationFitnessTests (:26-:38).

    RebuildFixtureProjectionHandler

    @@ -3354,17 +3653,6 @@

    StubMap

  • Why it's built this way - the test it serves needs a violation that is real: the registered assembly genuinely does depend on MMCA.Common.Application (it is the Infrastructure assembly, :25), and the map declares that namespace as another module's Application layer. A hand-written map is the only way to arrange a true dependency edge into a fabricated module boundary without inventing assemblies. See ADR-059 for the module boundary the rules enforce.
  • Where it's used - constructed twice in ModuleIsolationTestsBaseTests: once as the static CrossLayerViolation shared by two facts (:21-:25) and once as a clean map pointed at a namespace nothing references (:59-:63).
  • -

    NoEntityDataSources

    -
    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Domain · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/DeleteBehaviorConventionTests.cs:112 · Level 3 · class

    -
    -
      -
    • What it is - a private nested stub IEntityDataSourceRegistry that answers every lookup with the default SQL Server key and every TryGet with false, used to build the minimal, real ApplicationDbContext model the delete-behavior convention test needs without wiring the framework's actual multi-source registry.
    • -
    • Depends on - IEntityDataSourceRegistry (the interface it implements, DeleteBehaviorConventionTests.cs:112) and DataSourceKey / DataSource (DataSourceKey.Default(DataSource.SQLServer), :114,:116). Registered as the singleton behind a bare ServiceCollection built by DeleteBehaviorConventionTests's own model-building helper (services.AddSingleton<IEntityDataSourceRegistry>(new NoEntityDataSources());, :96).
    • -
    • Concept introduced - a no-op collaborator has to be honest about doing nothing. The class doc (:107-:111) explains the shape: the two GetDataSourceKey overloads answer the default key rather than throw, and TryGetDataSourceKey always answers false, because nothing in this test's model-building path calls the "get" overloads and a throwing fixture would trip this repo's own domain-throw architecture rule. With every entity answering TryGet = false, CrossDataSourceDegradeConvention (MMCA.Common/CLAUDE.md, "Multi-Database Strategy") treats every relationship as single-source, which is exactly the FK-constraints-intact case the delete-behavior rule is checking. [Rubric §14 - Testability] is the concern: a stub collaborator that only ever answers the one shape the test under proof needs, and nothing more.
    • -
    • Walkthrough - GetDataSourceKey(Type) and GetDataSourceKey(string) both return DataSourceKey.Default(DataSource.SQLServer) (:114,:116); TryGetDataSourceKey(string, out DataSourceKey) sets key = default and returns false (:118-:122); GetPhysicalSourcesInUse() returns an empty array (:124).
    • -
    • Where it's used - registered by the private Services() helper (:88-:98) that builds the ServiceProvider behind the model-building test context in DeleteBehaviorConventionTests; no site outside the defining file references it.
    • -

    TestingAspireBoundaryTests

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Layering · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Layering/TestingAspireBoundaryTests.cs:20 · Level 3 · class

    @@ -3383,6 +3671,18 @@

    TestingAspireBoundaryTests

  • Why it's built this way - ADR-117 introduces the AppHost integration-test base this suite protects; keeping the test package's own dependency graph thin is what lets it be referenced from an AppHost project without pulling Domain or Infrastructure along for the ride. See ADR-117.
  • Where it's used - an independent fitness test in the Common architecture suite; it has no consumers of its own.
  • +

    FitnessPrincipal

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Domain · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/SpecificationFitnessTests.cs:57 · Level 4 · class

    +
    +
      +
    • What it is - a throwaway "principal" entity used only as test data for the specification-navigation fitness function. It is the entity that a dependent record points at, so a specification can be written that tries to reach across the navigation into it.
    • +
    • Depends on - AuditableBaseEntity<TIdentifierType> (it is a public sealed class FitnessPrincipal : AuditableBaseEntity<int>, SpecificationFitnessTests.cs:57).
    • +
    • Concept introduced - test fixture entities for a fitness function. A fitness function is an executable architecture rule (see ArchitectureRules); to prove such a rule actually fires you feed it a deliberately-crafted model rather than the real domain. FitnessPrincipal is one half of that crafted model: a bare entity carrying a single scalar (IsActive, SpecificationFitnessTests.cs:59) so a specification can navigate to it. [Rubric §14 - Testability] assesses whether rules are provable with focused inputs; this fixture exists precisely so the guard is tested against a known-unsafe shape instead of hoping a real specification trips it.
    • +
    • Walkthrough - one auto-property, bool IsActive (:59). That is the only member; identity and audit fields come from the base. It is the navigation target referenced by FitnessDependent.Principal. Deriving from AuditableBaseEntity<int> is load-bearing rather than cosmetic: the rule's navigation walker only counts a property as an entity navigation when its type (or its collection element type) inherits the auditable entity base (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.Specifications.cs:121-:138).
    • +
    • Why it's built this way - the fitness test must be non-vacuous: it needs a real cross-entity navigation to flag. A minimal principal with a single scalar is the smallest thing a specification can legally navigate into.
    • +
    • Where it's used - referenced by FitnessDependent and, through it, by NavigatingSpec and NavigatingQuerySpec; the whole fixture set drives SpecificationFitnessTests.
    • +

    FakeArchitectureMap

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Layering · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Layering/LayerDependencyOverrideTests.cs:127 · Level 4 · class

    @@ -3454,6 +3754,29 @@

    ConformantTests

  • Walkthrough - TargetAssemblies is this test assembly (:128-:129), so the scan sees the nested fixture controllers. AllowedAnonymousEndpoints (:131-:136) holds the three entries described above; note the abstract base is listed at its own name rather than the deriving controller's, which is the ruling InheritingFixtureController pins. MinimumScannedTypes is left at the base's default of 1, which the fixture set clears comfortably. One member is added beyond the base's surface: internal IReadOnlyCollection<string> AnonymousEndpointsForTest() => [.. AnonymousEndpoints()]; (:138), which materializes the base's protected enumeration so the enclosing test class can assert on the emitted set directly. The base exposes AnonymousEndpoints() as protected for exactly this kind of extension (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Api/AnonymousEndpointTestsBase.cs:182-:125).
  • Where it's used - Base_Accepts_TypeLevelAndMethodLevelEntries (AnonymousEndpointTestsBaseTests.cs:45-:59) runs its three inherited facts, and Base_DoesNotReport_AnInheritedAttributeOnTheDerivedController (:61-:71) reads its AnonymousEndpointsForTest() output.
  • +

    DataSourceBranchingFitnessTests

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Governance · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/DataSourceBranchingFitnessTests.cs:17 · Level 4 · class

    +
    +
      +
    • What it is - a text-scanning fitness test over the framework's own Source/**/*.cs that fails whenever a line names a concrete database engine (a DataSource.X value or an engine-specific context type) outside a fixed per-file allow-list, plus two facts that keep that allow-list exact and four detector facts that pin the regex itself.
    • +
    • Depends on - ArchitectureAssert for the verdict (:87, :99), ArchitectureMapBase only for its static FindRepoRoot("MMCA.Common.slnx") (:122); the rule it protects is the IDataSourceEngine strategy split keyed by DataSource. Externals: a source-generated Regex ([GeneratedRegex], :150-:154), System.IO.Directory/File, xUnit [Fact]/[Theory], and AwesomeAssertions.
    • +
    • Concept introduced - a text-level fitness test for a rule the type system cannot see. The class doc states the gap (:7-:16): engine-specific behavior is meant to live in one IDataSourceEngine strategy per engine (ADR-130), but nothing in the compiler stops a new if (engine == DataSource.Sqlite) or context is SqliteDbContext branch from growing back anywhere else. A NetArchTest dependency rule cannot help either, because the offending code references types the file is allowed to reference; what is wrong is the branch, not the dependency. So the gate reads every source file as text and matches one regex (:151): DataSource\.(CosmosDB|Sqlite|SQLServer|PostgreSQL) for an enum value, or is (not )?\w*(SQLServer|Cosmos|Sqlite|PostgreSQL|Npgsql)\w*DbContext for a type test against an engine context. The allow-list is keyed at file granularity and is two-sided: a hit outside it fails, and a listed file that no longer produces a hit also fails, so the list shrinks with the code instead of keeping a pass for a file that could regress later (:13-:15). [Rubric §34 - Architecture Governance & Documentation] assesses whether architectural decisions are enforced rather than only written down; this test is ADR-130's executable form. [Rubric §8 - Data Architecture] is the property at stake, since a scattered engine branch is what makes a fifth engine or a changed engine rule a hunt rather than one class. [Rubric §14 - Testability] covers the detector facts that prove the regex itself.
    • +
    • Walkthrough
        +
      • Remedy (:27): the one-line fix text appended to every failure, "route engine-specific behavior through IDataSourceEngine (ADR-130)".
      • +
      • AllowedFiles (:32-:76): an ordinal Dictionary<string, string> of Source/-relative paths (forward slashes) to the reason each may name an engine. Twenty-four entries in eight commented groups: the four engine strategies (:35-:38); the four sealed per-engine contexts, one class per engine under ADR-006 (:42-:45); the four EntityTypeConfiguration* shim bases, since [UseDataSource(DataSource.X)] is how an entity picks its engine (:48-:51); four settings classes whose default engine is a value rather than a branch (:54-:57); DesignTimeDbContextHelper with its per-engine design-time entry points (:60); IndexBuilderExtensions, whose engine parameter defaults to SQL Server (:64); DataSourceResolver, which owns the framework-default engine constant (:67); and five files that ask for the framework tables' source via ResolveLogical(DataSource.SQLServer, Default), which the resolver maps onto whatever the host configured (:71-:75).
      • +
      • ConcreteEngineBranching_StaysInsideTheEngineStrategies (:78-:88): scans, drops every hit whose file is allow-listed, and reports each remaining one as Source/{file}:{line}: {text}.
      • +
      • AllowList_HasNoStaleEntries (:90-:102): collects the set of files that produced any hit and fails on every allow-list key absent from it.
      • +
      • Detector_Flags_ConcreteEngineBranching (:104-:111): five [InlineData] lines the regex must match, covering an equality test, a case label, is, is not with a pattern variable, and the Npgsql spelling of a context name.
      • +
      • Detector_Accepts_EngineNeutralCode (:113-:118): three lines it must not match, including dataSourceEngines.Resolve(key.Engine) (the sanctioned route), a DataSource property declaration, and a type test against the engine-neutral ApplicationDbContext.
      • +
      • ScanSource() (:120-:148): enumerates *.cs under {repo root}/Source excluding bin and obj, in ordinal order, asserts more than 500 files were found (:131, the non-vacuous floor so a wrong scan root cannot pass having read nothing), then matches line by line and records an EngineHit with a 1-based line number and the trimmed text.
      • +
      • EngineReference (:150-:154): the generated regex, CultureInvariant | ExplicitCapture, with a 1000 ms match timeout. EngineHit (:157) is a private record of file, line and text.
      • +
      +
    • +
    • Why it's built this way - ADR-130 moves engine-specific behavior behind one strategy per engine; this gate is what stops the decision decaying one convenient branch at a time. A file-level allow-list with a reason per entry keeps every exception reviewable in one place, and the stale-entry fact means that removing an engine reference from a listed file forces the entry out in the same change.
    • +
    • Where it's used - an independent fitness test in the Common architecture suite; it has no consumers and scans only Common's own Source/ tree (no *TestsBase is shipped for Store or ADC to subclass).
    • +
    • Caveats / not-in-source - the scan is textual, so it does not distinguish code from comments or string literals: a doc comment naming DataSource.SQLServer counts as a hit, which is why several entries (for example IndexBuilderExtensions and DataSourceResolver) cite "and its doc" or "and doc" as part of the reason.
    • +

    DriftedTests

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests · (see per-type table) · Level 4 · class

    @@ -3486,38 +3809,41 @@

    DriftedTests

  • Why they're built this way - one wrong value per assertion, and no more. If a single drifted subclass were wrong in three ways at once and the base only ever threw on the first, the other two assertions could rot undetected; the module-side fixture avoids that by being driven three separate times, once per fact.
  • Where they're used - the anonymous-endpoint one drives Base_Fails_WhenAnAnonymousEndpointIsNotAllowListed (AnonymousEndpointTestsBaseTests.cs:15-:24); the module one is instantiated three times by ModuleConformanceTestsBaseTests (:91,:99,:107), once per assertion under proof.
  • - +

    FixtureMap (Cqrs), FixtureMap (Governance)

    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Domain · (see per-type table) · Level 6 · class

    +

    MMCA.Common.Architecture.Tests.Cqrs and MMCA.Common.Architecture.Tests.Governance · (see per-type table) · Level 4 · class

    -

    The same unsafe and safe pair as NavigatingSpec and ScalarOnlySpec, rebuilt one level further down the specification hierarchy as QuerySpecification<TEntity, TIdentifierType> subclasses. They exist to pin a single ruling: adding includes, ordering, and paging to a specification must not take it out of the navigation rule's reach.

    +

    Two unrelated one-entry maps that happen to share the plain name FixtureMap, both private sealed and nested in a different outer test class, so neither is visible outside its own file.

      -
    • Depends on - QuerySpecification<TEntity, TIdentifierType> (both, SpecificationFitnessTests.cs:76,:89), FitnessDependent and FitnessPrincipal (the entities), and System.Linq.Expressions (:1).
    • -
    • Concept introduced - walking the whole base chain instead of matching one type. The rule selects candidates with HasBaseTypeStartingWith("MMCA.Common.Domain.Specifications.Specification") (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.Specifications.cs:7,:33), and that helper climbs BaseType all the way up (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/RuleHelpers.cs:88-:91). Because QuerySpecification itself derives from Specification, the prefix still matches two levels down, and the string prefix match means both Specification and QuerySpecification are caught by the one condition. That is the kind of thing that is true today and could quietly stop being true after a refactor of the specification hierarchy, which is exactly why it gets its own fact rather than being left as an assumption. [Rubric §7 - Microservices Readiness] and [Rubric §8 - Data Architecture] are the underlying properties (a navigating predicate cannot cross a data-source boundary, ADR-006); [Rubric §14 - Testability] is why the pair exists.
    • -
    • Walkthrough - both declare an explicit parameterless constructor, which is load-bearing: the rule instantiates candidates to read Criteria and skips any type without one (ArchitectureRules.Specifications.cs:37-:41). Each constructor also exercises a different part of the query surface, so the fixtures prove that the extra machinery does not interfere with the analysis.
    • +
    • Depends on - ArchitectureMapBase, LayerRef and the Layer enum.
    • +
    • Concept introduced - each proves a different rule against a deliberately scoped fixture set, the same pattern taught at FixtureAssemblyMap: register only the layer(s) the rule under test reads, so the assertion is provable against a known shape rather than the real codebase. The Cqrs instance is a dual-layer map, Module("Fixtures", Layer.Api, ...) plus Module("Fixtures", Layer.Application, ...) over the same assembly (ConstructorDependencyCountTestsBaseTests.cs:81-:82), because ConstructorDependencyCountTestsBase reads both Map.Api() and Map.ModuleApplication() to bound controller and handler constructors independently. The Governance instance is a single Shared-layer Framework(Layer.Shared, ...) map (FeatureFlagLifecycleTests.cs:108-:109) that stands in for the deliberately bad fixtures FeatureFlagsDeclareLifetime must flag: an undeclared flag, a Permanent flag that also sets RemoveBy, a Temporary flag with no RemoveBy, and one whose RemoveBy does not parse as an ISO yyyy-MM-dd date. [Rubric §14 - Testability] assesses whether both rules are provable with focused inputs; [Rubric §34 - Architecture Governance and Documentation] is why FeatureFlagsDeclareLifetime exists (ADR-031's dead-toggle detector).
    • +
    • Walkthrough - both declare RepoToken => "MMCA.Common" and a single-entry DefineLayers(); see the per-type table for the exact layer call each makes.
    - + + - - - + + + + - - - + + + +
    Type File:LineWhat differsLayer(s)The rule it scopes
    NavigatingQuerySpecMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/SpecificationFitnessTests.cs:76The offender. Its constructor adds ordering and paging, AddOrderBy(d => d.PrincipalId) and ApplyPaging(skip: 0, take: 10) (:79-:83), and its Criteria dereferences the navigation, d => d.Principal!.IsActive (:86). Must be reported.FixtureMap (in ConstructorDependencyCountTestsBaseTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/ConstructorDependencyCountTestsBaseTests.cs:75Module("Fixtures", Layer.Api, ...) and Module("Fixtures", Layer.Application, ...), both anchored on typeof(FatFixtureController).Assembly (:81-:82)ConstructorDependencyCountTestsBase's controller and handler ceiling checks, via the TightCeilingTests/ConformantTests subclasses (:98,:110).
    ScalarOnlyQuerySpecMMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/SpecificationFitnessTests.cs:89The safe counterpart, and a deliberately awkward one: its constructor calls AddInclude(nameof(FitnessDependent.Principal)) (:92), so it does pull in the related entity, while its Criteria filters only on own columns (:95). Must not be reported. Includes are an eager-loading instruction, not a filter predicate, and the rule analyzes the predicate only.FixtureMap (in FeatureFlagLifecycleRuleTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/FeatureFlagLifecycleTests.cs:104Framework(Layer.Shared, typeof(FixtureBadFeatures).Assembly) (:109)ArchitectureRules.FeatureFlagsDeclareLifetime, via the private DeclarationFailure() helper (:96-:101), and TemporaryFeatureFlagsAreNotPastRemoveBy for the expiry facts (:72,:86-:87).
      -
    • Why they're built this way - ScalarOnlyQuerySpec is the sharper of the two. Without it, a future rule implementation that (reasonably but wrongly) also inspected Includes would still pass every other fixture in the file. Both are declared public while their plain-Specification siblings are private, which makes no difference to the rule (it scans ConcreteClasses across the assembly, not by visibility).
    • -
    • Where they're used - the two inputs to Rule_AlsoAnalyzesQuerySpecifications in SpecificationFitnessTests (:26-:38).
    • +
    • Why they're built this way - each is nested private inside the single test class that consumes it, keeping an eight-to-ten-line fixture map beside the assertions it serves rather than in a shared file whose scope a reader would have to look up. It is the same trade-off FixtureModuleMap and FixtureAssemblyMap make.
    • +
    • Where they're used - constructed inline where each fact needs it: new FixtureMap() inside TightCeilingTests/ConformantTests (ConstructorDependencyCountTestsBaseTests.cs:100,:112), and new FixtureMap() inline at each ArchitectureRules call in FeatureFlagLifecycleRuleTests (FeatureFlagLifecycleTests.cs:72,:86,:98).

    FixtureEntity

    @@ -3578,18 +3904,6 @@

    (ArchitectureRules.Upcasters.cs:67).
  • Where they're used - reflected over by EventUpcasterFitnessTests through UpcasterTestMap, and named by nameof in its assertions.
  • -

    FitnessPrincipal

    -
    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Domain · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/SpecificationFitnessTests.cs:57 · Level 4 · class

    -
    -
      -
    • What it is - a throwaway "principal" entity used only as test data for the specification-navigation fitness function. It is the entity that a dependent record points at, so a specification can be written that tries to reach across the navigation into it.
    • -
    • Depends on - AuditableBaseEntity<TIdentifierType> (it is a public sealed class FitnessPrincipal : AuditableBaseEntity<int>, SpecificationFitnessTests.cs:57).
    • -
    • Concept introduced - test fixture entities for a fitness function. A fitness function is an executable architecture rule (see ArchitectureRules); to prove such a rule actually fires you feed it a deliberately-crafted model rather than the real domain. FitnessPrincipal is one half of that crafted model: a bare entity carrying a single scalar (IsActive, SpecificationFitnessTests.cs:59) so a specification can navigate to it. [Rubric §14 - Testability] assesses whether rules are provable with focused inputs; this fixture exists precisely so the guard is tested against a known-unsafe shape instead of hoping a real specification trips it.
    • -
    • Walkthrough - one auto-property, bool IsActive (:59). That is the only member; identity and audit fields come from the base. It is the navigation target referenced by FitnessDependent.Principal. Deriving from AuditableBaseEntity<int> is load-bearing rather than cosmetic: the rule's navigation walker only counts a property as an entity navigation when its type (or its collection element type) inherits the auditable entity base (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.Specifications.cs:121-:138).
    • -
    • Why it's built this way - the fitness test must be non-vacuous: it needs a real cross-entity navigation to flag. A minimal principal with a single scalar is the smallest thing a specification can legally navigate into.
    • -
    • Where it's used - referenced by FitnessDependent and, through it, by NavigatingSpec and NavigatingQuerySpec; the whole fixture set drives SpecificationFitnessTests.
    • -

    FixtureApplicationMap

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Cqrs · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/SliceCohesionFitnessTests.cs:58 · Level 4 · class

    @@ -3608,7 +3922,7 @@

    FixtureAssemblyMap

    The name is used four times in this assembly, once per IL-reading rule under proof, and all four are the same one-entry map: this test assembly registered as a single framework layer, so the rule's whole-layer scan lands on the fixtures. They are nested in different outer classes, so their full names differ and none is visible outside its own file.

    • Depends on - ArchitectureMapBase, LayerRef and the Layer enum.
    • -
    • Concept introduced - the layer a fixture map declares is not decoration, it is the scope selector. Each of these four rules starts from a different part of the map, so each map has to name the matching layer or the rule reads nothing and passes vacuously. The cascade and hard-delete rules walk every assembly the map registers, through ScannableAssemblyLocations (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.SoftDelete.cs:88-:92), so their layer choice is nominal; the domain-throw rule reads map.OfLayer(Layer.Domain) and nothing else (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.DomainThrows.cs:107-:111), so registering Domain is mandatory there. All four use Framework(...) rather than Module(...), which is the opposite choice from FixtureModuleMap and correct for the same reason: these rules do not filter on module ownership, so the simplest entry is the honest one. Reading assemblies off disk by Assembly.Location also explains a shared limitation: a rule in this family cannot inspect a dynamic or single-file assembly, and the helper silently skips any location that does not exist on disk. [Rubric §14 - Testability] assesses whether a guardrail is provable with focused inputs, and a map that scopes the scan to fixtures is what makes each of these four rules provable at all.
    • +
    • Concept introduced - the layer a fixture map declares is not decoration, it is the scope selector. Each of these four rules starts from a different part of the map, so each map has to name the matching layer or the rule reads nothing and passes vacuously. The cascade and hard-delete rules walk every assembly the map registers, through ScannableAssemblyLocations (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.SoftDelete.cs:88-:92), so their layer choice is nominal; the domain-throw rule reads map.OfLayer(Layer.Domain) and nothing else (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.DomainThrows.cs:111-:111), so registering Domain is mandatory there. All four use Framework(...) rather than Module(...), which is the opposite choice from FixtureModuleMap and correct for the same reason: these rules do not filter on module ownership, so the simplest entry is the honest one. Reading assemblies off disk by Assembly.Location also explains a shared limitation: a rule in this family cannot inspect a dynamic or single-file assembly, and the helper silently skips any location that does not exist on disk. [Rubric §14 - Testability] assesses whether a guardrail is provable with focused inputs, and a map that scopes the scan to fixtures is what makes each of these four rules provable at all.
    • Walkthrough - each declares RepoToken => "MMCA.Common" and a single-entry DefineLayers() naming one layer and one anchor type from the fixture namespace it serves. Anchoring by typeof(SomeFixture).Assembly rather than by Assembly.GetExecutingAssembly() is deliberate: it ties the map to a type a compiler would fail on if the fixtures moved.
    @@ -3638,63 +3952,27 @@

    FixtureAssemblyMap

    - - - - - - -
    Framework(Layer.Application, typeof(FixtureDomainEvent).Assembly) (:116) DomainEventHandlersDoNotSave.
    FixtureAssemblyMap (in DomainThrowFitnessTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/DomainThrowFitnessTests.cs:138Framework(Layer.Domain, typeof(NonThrowingFixture).Assembly) (:144)DomainThrowsOnlyArgumentGuards, which reads the Domain layer only, so this entry is load-bearing.
    -
      -
    • Why they're built this way - four nested six-line classes rather than one shared fixture map, so each test file is readable end to end and no test can change another's scope by editing a shared file. It is the same trade-off FixtureModuleMap makes.
    • -
    • Caveats / not-in-source - because each map registers the whole test assembly, a rule in this family sees every type in it, not just the fixtures in its own namespace. Three of the four are unaffected because their fixtures are the only matching shapes present; the domain-throw rule is not, which is why it carries an explicit allowlist of the assembly's non-fixture throws (DomainThrowFitnessTests.cs:29-:33).
    • -
    • Where they're used - constructed once as a readonly field per test class (CascadeSoftDeleteFitnessTests.cs:21, SoftDeleteEnforcementFitnessTests.cs:19, DomainEventHandlerSaveFitnessTests.cs:21, DomainThrowFitnessTests.cs:35) and passed to every rule call in each.
    • -
    -

    FixtureLeakedPayload

    -
    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.PayloadPurityFixtures.IntegrationEvents · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/PayloadPurityFixtures/IntegrationEvents/PayloadPurityFixtures.cs:20 · Level 4 · record

    -
    -
      -
    • What it is - the domain-typed payload the payload-purity rule is built to catch: a nested record carrying an AuditableBaseEntity<int>? reference, one level down from the event that holds it.
    • -
    • Depends on - AuditableBaseEntity<TIdentifierType> closed over int (internal sealed record FixtureLeakedPayload(AuditableBaseEntity<int>? Entity);, PayloadPurityFixtures.cs:20).
    • -
    • Concept - the framework's payload-purity rule (ADR-010) requires an integration event crossing a transport boundary to carry primitives and DTOs only, because a subscriber on the far side has no shared DbContext to resolve a leaked entity reference against. This fixture is nested one level inside its event on purpose: PayloadRule_FlagsADomainTypeReachedThroughANestedPayload asserts the failure message names the full path FixtureLeakingEvent.Payload.Entity, not just Payload, proving the rule walks nested payload shapes rather than stopping at the event's own properties (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/IntegrationEventPayloadPurityTests.cs:55-:57).
    • -
    • Where it's used - the sole property of the fixture event's Payload, read by IntegrationEventPayloadPurityRuleTests.PayloadRule_FlagsADomainTypeReachedThroughANestedPayload (IntegrationEventPayloadPurityTests.cs:48-:59).
    • -
    -

    FixtureMap (Cqrs), FixtureMap (Governance)

    -
    -

    MMCA.Common.Architecture.Tests.Cqrs and MMCA.Common.Architecture.Tests.Governance · (see per-type table) · Level 4 · class

    -
    -

    Two unrelated one-entry maps that happen to share the plain name FixtureMap, both private sealed and nested in a different outer test class, so neither is visible outside its own file.

    -
      -
    • Depends on - ArchitectureMapBase, LayerRef and the Layer enum.
    • -
    • Concept introduced - each proves a different rule against a deliberately scoped fixture set, the same pattern taught at FixtureAssemblyMap: register only the layer(s) the rule under test reads, so the assertion is provable against a known shape rather than the real codebase. The Cqrs instance is a dual-layer map, Module("Fixtures", Layer.Api, ...) plus Module("Fixtures", Layer.Application, ...) over the same assembly (ConstructorDependencyCountTestsBaseTests.cs:81-:82), because ConstructorDependencyCountTestsBase reads both Map.Api() and Map.ModuleApplication() to bound controller and handler constructors independently. The Governance instance is a single Shared-layer Framework(Layer.Shared, ...) map (FeatureFlagLifecycleTests.cs:108-:109) that stands in for the deliberately bad fixtures FeatureFlagsDeclareLifetime must flag: an undeclared flag, a Permanent flag that also sets RemoveBy, a Temporary flag with no RemoveBy, and one whose RemoveBy does not parse as an ISO yyyy-MM-dd date. [Rubric §14 - Testability] assesses whether both rules are provable with focused inputs; [Rubric §34 - Architecture Governance and Documentation] is why FeatureFlagsDeclareLifetime exists (ADR-031's dead-toggle detector).
    • -
    • Walkthrough - both declare RepoToken => "MMCA.Common" and a single-entry DefineLayers(); see the per-type table for the exact layer call each makes.
    • -
    -
    - - - - - - - - - - - - - - - - - - - + + + + +
    TypeFile:LineLayer(s)The rule it scopes
    FixtureMap (in ConstructorDependencyCountTestsBaseTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/ConstructorDependencyCountTestsBaseTests.cs:75Module("Fixtures", Layer.Api, ...) and Module("Fixtures", Layer.Application, ...), both anchored on typeof(FatFixtureController).Assembly (:81-:82)ConstructorDependencyCountTestsBase's controller and handler ceiling checks, via the TightCeilingTests/ConformantTests subclasses (:98,:110).
    FixtureMap (in FeatureFlagLifecycleRuleTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/FeatureFlagLifecycleTests.cs:104Framework(Layer.Shared, typeof(FixtureBadFeatures).Assembly) (:109)ArchitectureRules.FeatureFlagsDeclareLifetime, via the private DeclarationFailure() helper (:96-:101), and TemporaryFeatureFlagsAreNotPastRemoveBy for the expiry facts (:72,:86-:87).
    FixtureAssemblyMap (in DomainThrowFitnessTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/DomainThrowFitnessTests.cs:149Framework(Layer.Domain, typeof(NonThrowingFixture).Assembly) (:144)DomainThrowsOnlyArgumentGuards, which reads the Domain layer only, so this entry is load-bearing.
      -
    • Why they're built this way - each is nested private inside the single test class that consumes it, keeping an eight-to-ten-line fixture map beside the assertions it serves rather than in a shared file whose scope a reader would have to look up. It is the same trade-off FixtureModuleMap and FixtureAssemblyMap make.
    • -
    • Where they're used - constructed inline where each fact needs it: new FixtureMap() inside TightCeilingTests/ConformantTests (ConstructorDependencyCountTestsBaseTests.cs:100,:112), and new FixtureMap() inline at each ArchitectureRules call in FeatureFlagLifecycleRuleTests (FeatureFlagLifecycleTests.cs:72,:86,:98).
    • +
    • Why they're built this way - four nested six-line classes rather than one shared fixture map, so each test file is readable end to end and no test can change another's scope by editing a shared file. It is the same trade-off FixtureModuleMap makes.
    • +
    • Caveats / not-in-source - because each map registers the whole test assembly, a rule in this family sees every type in it, not just the fixtures in its own namespace. Three of the four are unaffected because their fixtures are the only matching shapes present; the domain-throw rule is not, which is why it carries an explicit allowlist of the assembly's non-fixture throws (DomainThrowFitnessTests.cs:29-:33).
    • +
    • Where they're used - constructed once as a readonly field per test class (CascadeSoftDeleteFitnessTests.cs:21, SoftDeleteEnforcementFitnessTests.cs:19, DomainEventHandlerSaveFitnessTests.cs:21, DomainThrowFitnessTests.cs:35) and passed to every rule call in each.
    • +
    +

    FixtureLeakedPayload

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.PayloadPurityFixtures.IntegrationEvents · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/PayloadPurityFixtures/IntegrationEvents/PayloadPurityFixtures.cs:20 · Level 4 · record

    +
    +
      +
    • What it is - the domain-typed payload the payload-purity rule is built to catch: a nested record carrying an AuditableBaseEntity<int>? reference, one level down from the event that holds it.
    • +
    • Depends on - AuditableBaseEntity<TIdentifierType> closed over int (internal sealed record FixtureLeakedPayload(AuditableBaseEntity<int>? Entity);, PayloadPurityFixtures.cs:20).
    • +
    • Concept - the framework's payload-purity rule (ADR-010) requires an integration event crossing a transport boundary to carry primitives and DTOs only, because a subscriber on the far side has no shared DbContext to resolve a leaked entity reference against. This fixture is nested one level inside its event on purpose: PayloadRule_FlagsADomainTypeReachedThroughANestedPayload asserts the failure message names the full path FixtureLeakingEvent.Payload.Entity, not just Payload, proving the rule walks nested payload shapes rather than stopping at the event's own properties (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/IntegrationEventPayloadPurityTests.cs:55-:57).
    • +
    • Where it's used - the sole property of the fixture event's Payload, read by IntegrationEventPayloadPurityRuleTests.PayloadRule_FlagsADomainTypeReachedThroughANestedPayload (IntegrationEventPayloadPurityTests.cs:48-:59).

    FixtureModuleMap

    @@ -3729,16 +4007,17 @@

    FixtureModuleMap

  • Why they're built this way - each is nested private sealed inside the test class that uses it, so neither is visible to the other and neither can be mistaken for a repo map. Duplicating eight lines is cheaper than a shared fixture map whose meaning would have to be read from another file.
  • Where they're used - constructed once as a readonly field per test class (CommandValidatorCoverageFitnessTests.cs:20, ErrorCatalogFitnessTests.cs:19) and passed to every rule call in each.
  • -

    StaleAllowListTests

    +

    FixtureShapedEvent

    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Api · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Api/AnonymousEndpointTestsBaseTests.cs:169 · Level 4 · class

    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.ContractShapeFixtures.IntegrationEvents · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/ContractShapeFixtures/IntegrationEvents/ContractShapeFixtures.cs:22 · Level 4 · record

      -
    • What it is - the adversarial fixture for the stale-entry half of the anonymous-endpoint gate: a subclass whose allow-list names an endpoint that does not exist, so the base's AllowList_HasNoStaleEntries fact must report it rather than shrug.
    • -
    • Depends on - AnonymousEndpointTestsBase (private sealed class StaleAllowListTests : AnonymousEndpointTestsBase, AnonymousEndpointTestsBaseTests.cs:169).
    • -
    • Concept introduced - an allow-list rots in two directions, and only one of them is obvious. A missing entry fails loudly the moment a new [AllowAnonymous] lands. A stale entry fails silently and permanently: the endpoint gets renamed, re-gated with [Authorize], or deleted, and the list keeps granting a permission that is no longer being requested. Nothing breaks, so nobody looks, and the next endpoint that happens to match that identifier inherits an approval nobody granted it. The base therefore runs the comparison both ways, computing stale as the allow-list entries with no match in the scanned set (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Api/AnonymousEndpointTestsBase.cs:108-:89), with a because that spells out the consequence: an entry that no longer matches hides a renamed or re-gated endpoint behind a permission that is no longer being granted (:88). [Rubric §11 - Security] is the property; [Rubric §15 - Best Practices & Code Quality] is the mechanism, since a self-pruning list is one that stays readable.
    • -
    • Walkthrough - TargetAssemblies is this test assembly (:110-:111), so the fixture controllers are discovered normally. AllowedAnonymousEndpoints holds exactly one entry, "MMCA.Common.Architecture.Tests.NoLongerAnonymousController.ReadAsync" (:113-:114), naming a controller that has never existed. That makes the fixture fail both facts at once (every real fixture endpoint is now an unlisted offender as well), which is harmless because the fact under proof invokes only AllowList_HasNoStaleEntries as a delegate.
    • -
    • Where it's used - the subject of Base_Fails_WhenTheAllowListHasAStaleEntry (AnonymousEndpointTestsBaseTests.cs:26-:35), which asserts the message contains "NoLongerAnonymous" (:33), the distinctive fragment of the phantom identifier.
    • +
    • What it is - an internal sealed integration-event fixture with two constructed-generic members, int? Count and IReadOnlyList<string> Tags, that derives from an intermediate consumer base instead of directly from the framework envelope (ContractShapeFixtures.cs:22). It exists so the frozen wire-contract snapshot can be proven to spell generic arguments out and to include a member inherited from a consumer base.
    • +
    • Depends on - the private fixture base FixtureShapedBase (ContractShapeFixtures.cs:10), which adds one member, string Origin (:13), and itself derives from BaseIntegrationEvent (:10).
    • +
    • Concept - cross-references the frozen-contract concept taught at IntegrationEventContractTestsBase. The snapshot line for an event lists its public instance properties sorted by name, drops anything declared in the framework envelope namespace MMCA.Common.Domain.DomainEvents (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Events.cs:59,:68), and renders each type by its simple name with generic arguments spelled out (:91-:96). This fixture carries the two shapes a naive renderer gets wrong. A Nullable<Int32> or IReadOnlyList<String> printed as the open-generic name would hide a retyped argument, and an intermediate base sitting between the event and the envelope is still part of the wire shape, so its Origin member must appear while the envelope's SchemaVersion and DateOccurred must not. [Rubric §9, API & Contract Design] assesses whether published contracts are versioned and guarded against silent breaks; this fixture is what makes the snapshot's type rendering checkable rather than assumed. [Rubric §14, Testability] covers the self-test it feeds.
    • +
    • Walkthrough - a positional record with no body. Its siblings in the same file each isolate one other rendering rule: FixtureTallyEvent a comma inside angle brackets (ContractShapeFixtures.cs:29), FixtureCountEvent / FixtureNullableCountEvent value nullability (:33,:37), FixtureLabelEvent / FixtureNullableLabelEvent reference nullability (:42,:47), and FixtureNamedEvent an [EventName] wire key (:54-:55).
    • +
    • Where it's used - read through the fixture map by IntegrationEventContractTestsBaseTests, whose Contract_SpellsOutGenericArguments_AndPinsAnIntermediateBase asserts the live contract contains the exact line ...FixtureShapedEvent { Count:Nullable<Int32>, Origin:String, Tags:IReadOnlyList<String> } and that no ContractShapeFixtures line carries SchemaVersion: or DateOccurred: (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/IntegrationEventContractTestsBaseTests.cs:85-:98). The live contract comes from ArchitectureRules.BuildIntegrationEventContract(new FixtureMap()) (:198-:199).
    • +
    • Caveats / not-in-source - being internal keeps it off the test assembly's public surface; the contract builder evidently enumerates non-public types, since the assertion at :90-:93 passes against it, but the enumeration helper itself was not read for this section.

    ModuleConformanceTestsBaseTests

    @@ -3759,6 +4038,26 @@

    ModuleConformanceTestsBaseTests

  • Where it's used - an independent class in the Common architecture suite. The base it protects is subclassed by five real module tests: MMCA.ADC/Tests/Modules/Notification/MMCA.ADC.Notification.API.Tests/NotificationModuleTests.cs:8, MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.API.Tests/IdentityModuleTests.cs:5, MMCA.Store/Tests/Modules/Sales/MMCA.Store.Sales.API.Tests/SalesModuleTests.cs:5, MMCA.Store/Tests/Modules/Identity/MMCA.Store.Identity.API.Tests/IdentityModuleTests.cs:5, and MMCA.Store/Tests/Modules/Catalog/MMCA.Store.Catalog.API.Tests/CatalogModuleTests.cs:5.
  • Caveats / not-in-source - the three adversarial facts assert only that an exception is thrown (Should().Throw<Exception>(), :93,:101,:109), not which assertion produced it or what its message says. They prove the base is not vacuous; they do not pin its failure text.
  • +

    PluralSentenceResourceTests

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Ui · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Ui/PluralSentenceResourceTests.cs:15 · Level 4 · class

    +
    +
      +
    • What it is - a resource-file fitness test: one fact that scans every framework .resx for a count rendered inside a fixed plural sentence ("{0} items in your cart"), plus three detector self-tests that pin what the scanner flags and what it accepts.
    • +
    • Depends on - ArchitectureMapBase for FindRepoRoot("MMCA.Common.slnx") (PluralSentenceResourceTests.cs:20), ArchitectureAssert for NoViolations (:35-:38), and the plural API it steers toward, StringLocalizerPluralExtensions. Externals: System.Xml.Linq.XDocument for reading .resx entries (:72-:77), a source-generated Regex (:111-:112), xUnit and AwesomeAssertions.
    • +
    • Concept introduced - a count inside a single-form sentence is a localization defect, and it is detectable mechanically. "{0} items" reads wrongly for a count of one, and translators cannot fix it because the key has only one form. The framework's answer is a pair of sibling keys, KEY.One and KEY.Other, resolved by L.Plural(KEY, count, count), which picks .One for exactly one and .Other otherwise and falls back to the base key when the plural key is missing (MMCA.Common/Source/Presentation/MMCA.Common.UI/Globalization/StringLocalizerPluralExtensions.cs:23,:26,:40-:51; ADR-027). This test turns that convention into a build failure whose message is the instruction: split the key and use L.Plural (PluralSentenceResourceTests.cs:37-:38). [Rubric §27, i18n] assesses whether text adapts correctly to language and quantity; this fixes pluralization at the resource layer, for the English and Spanish sets the framework ships. [Rubric §14, Testability] covers the detector self-tests, and [Rubric §34, Architecture Governance & Documentation] the choice of an executable convention over a written one.
    • +
    • Walkthrough
        +
      • FrameworkResources_HaveNoCountInAFixedPluralSentence (:17-:39): enumerates *.resx under the repo's Source folder, skipping MudTranslations* files and anything under bin or obj (:20-:27), then asserts a non-vacuous floor of at least four files (SharedResource and ErrorResources, base plus es) so a wrong scan root cannot pass having read nothing (:29-:30). Every violation is reported as file: key = "value" (count followed by 'word') (:32-:33,:94).
      • +
      • Detector_Flags_ACountInAFixedPluralSentence (:41-:48): four inline cases, including the (s) hedge ("{0} item(s) in your cart"), a Spanish sentence, and a count in parentheses, each expected to produce exactly one violation.
      • +
      • Detector_Accepts_AValueThatIsNotAFixedPluralSentence (:50-:58): five cases that must produce none: a .Other key, a singular noun after the placeholder, a verb ("{0} with Id {1} was not found."), a preposition, and a .One key.
      • +
      • Detector_Accepts_TheBaseKeyOfAPluralPair_AsItsFallback (:60-:67): a base key whose .Other sibling exists is skipped, because it is only the fallback for a resource set not yet split.
      • +
      • FixedPluralSentences (:85-:95) is the detector: it drops .Other keys and any key with a .Other sibling (:89-:90), then matches each value against CountFollowedByPlural, a placeholder ({0}, {1:N0}) followed by whitespace and a word with an optional (s) / (es) suffix (:110-:112, 1000 ms match timeout). IsPluralMarked (:97-:102) counts a word as plural when it ends in (s) or (es), or is four or more letters ending in s and not in NonNounsEndingInS, a fixed English and Spanish stop list (does, this, antes, entonces and so on, :105-:108).
      • +
      +
    • +
    • Why it's built this way - the detector is a heuristic, so it is proven from both sides with inline data rather than trusted: the flag cases pin its recall and the accept cases pin the false positives it must not raise. The resource scan is a plain XML read of data elements (:72-:77) rather than a ResourceManager load, so it needs no culture setup and sees every key in every satellite file.
    • +
    • Where it's used - an independent class in the Common architecture suite; nothing references it outside its own file. The framework call site it protects is NotificationSend, which resolves L.Plural("Notif.Send.SentTo", ...) (MMCA.Common/Source/Presentation/MMCA.Common.UI/Pages/Notifications/NotificationSend.razor.cs:117).
    • +
    • Caveats / not-in-source - the four-letter, ends-in-s rule is English-shaped. A plural of three letters or fewer ("{0} ids") or an irregular plural not ending in s ("{0} children") passes unflagged, and a non-noun ending in s that is missing from the stop list would be a false positive. The scan covers MMCA.Common's own Source tree only; consumer repos are not scanned by this class.
    • +

    EmptyScanTests

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Cqrs · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/ConstructorDependencyCountTestsBaseTests.cs:121 · Level 5 · class

    @@ -3766,10 +4065,21 @@

    EmptyScanTests

    • What it is - the adversarial fixture for the constructor-dependency gate's non-vacuity guards: a subclass whose map's Api and Application layers hold neither a controller nor a handler, so both NotBeEmpty guards inside the base's facts must fire.
    • Depends on - ConstructorDependencyCountTestsBase (private sealed class EmptyScanTests : ConstructorDependencyCountTestsBase, ConstructorDependencyCountTestsBaseTests.cs:121) and its sibling BareMap (:87-:96), whose two layers both point at typeof(Common.Shared.Abstractions.Result).Assembly (:93-:94), a package with no controllers and no CQRS handlers.
    • -
    • Concept - cross-references the vacuity-guard concept the base class states directly in each fact: Controllers_DoNotExceedConstructorDependencyCeiling asserts controllers.Should().NotBeEmpty("the guard must scan at least one API controller (otherwise it passes vacuously)") (ConstructorDependencyCountTestsBase.cs:94-:95), and Handlers_DoNotExceedConstructorDependencyCeiling asserts the equivalent for handlers (:118-:119). A scan of nothing would otherwise report zero offenders and pass, which is indistinguishable from a healthy repo. [Rubric §14 - Testability] is the theme: the guard that proves the guard is not vacuous.
    • +
    • Concept - cross-references the vacuity-guard concept the base class states directly in each fact: Controllers_DoNotExceedConstructorDependencyCeiling asserts controllers.Should().NotBeEmpty("the guard must scan at least one API controller (otherwise it passes vacuously)") (ConstructorDependencyCountTestsBase.cs:120-:95), and Handlers_DoNotExceedConstructorDependencyCeiling asserts the equivalent for handlers (:118-:119). A scan of nothing would otherwise report zero offenders and pass, which is indistinguishable from a healthy repo. [Rubric §14 - Testability] is the theme: the guard that proves the guard is not vacuous.
    • Walkthrough - Map is BareMap (:123), and all three ceilings are left at their ConformantTests-matching values (int.MaxValue, 3, 3, :125,:127,:129) because the ceilings are irrelevant here: the fact must throw before ever comparing a count against them, on the NotBeEmpty guard alone.
    • Where it's used - BothPopulations_FailVacuousScans (ConstructorDependencyCountTestsBaseTests.cs:60-:72) constructs one EmptyScanTests instance and asserts both Controllers_DoNotExceedConstructorDependencyCeiling and Handlers_DoNotExceedConstructorDependencyCeiling throw, each with a because clause stating what the empty scan would otherwise hide: "a map whose API assemblies hold no controller would pass while checking nothing" (:68-:69) and the handler equivalent (:70-:71).
    +

    StaleAllowListTests

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Api · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Api/AnonymousEndpointTestsBaseTests.cs:169 · Level 4 · class

    +
    +
      +
    • What it is - the adversarial fixture for the stale-entry half of the anonymous-endpoint gate: a subclass whose allow-list names an endpoint that does not exist, so the base's AllowList_HasNoStaleEntries fact must report it rather than shrug.
    • +
    • Depends on - AnonymousEndpointTestsBase (private sealed class StaleAllowListTests : AnonymousEndpointTestsBase, AnonymousEndpointTestsBaseTests.cs:169).
    • +
    • Concept introduced - an allow-list rots in two directions, and only one of them is obvious. A missing entry fails loudly the moment a new [AllowAnonymous] lands. A stale entry fails silently and permanently: the endpoint gets renamed, re-gated with [Authorize], or deleted, and the list keeps granting a permission that is no longer being requested. Nothing breaks, so nobody looks, and the next endpoint that happens to match that identifier inherits an approval nobody granted it. The base therefore runs the comparison both ways, computing stale as the allow-list entries with no match in the scanned set (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Api/AnonymousEndpointTestsBase.cs:108-:89), with a because that spells out the consequence: an entry that no longer matches hides a renamed or re-gated endpoint behind a permission that is no longer being granted (:88). [Rubric §11 - Security] is the property; [Rubric §15 - Best Practices & Code Quality] is the mechanism, since a self-pruning list is one that stays readable.
    • +
    • Walkthrough - TargetAssemblies is this test assembly (:110-:111), so the fixture controllers are discovered normally. AllowedAnonymousEndpoints holds exactly one entry, "MMCA.Common.Architecture.Tests.NoLongerAnonymousController.ReadAsync" (:113-:114), naming a controller that has never existed. That makes the fixture fail both facts at once (every real fixture endpoint is now an unlisted offender as well), which is harmless because the fact under proof invokes only AllowList_HasNoStaleEntries as a delegate.
    • +
    • Where it's used - the subject of Base_Fails_WhenTheAllowListHasAStaleEntry (AnonymousEndpointTestsBaseTests.cs:26-:35), which asserts the message contains "NoLongerAnonymous" (:33), the distinctive fragment of the phantom identifier.
    • +

    StaleUndecoratedAllowListTests

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Api · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Api/AnonymousEndpointTestsBaseTests.cs:197 · Level 4 · class

    @@ -3808,7 +4118,7 @@

    ConformantTests

    • What it is - the positive fixture for the constructor-dependency ceiling gate over controllers and handlers: a subclass whose ceilings sit exactly at the fixture population's widest constructor, so both facts must pass.
    • -
    • Depends on - ConstructorDependencyCountTestsBase (private sealed class ConformantTests : ConstructorDependencyCountTestsBase, ConstructorDependencyCountTestsBaseTests.cs:110, base at MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Cqrs/ConstructorDependencyCountTestsBase.cs:20) and its sibling FixtureMap (:75-:84), which points both the Api and Application layers of the map at this test assembly (Module("Fixtures", Layer.Api, ...) and Module("Fixtures", Layer.Application, ...), :81-:82).
    • +
    • Depends on - ConstructorDependencyCountTestsBase (private sealed class ConformantTests : ConstructorDependencyCountTestsBase, ConstructorDependencyCountTestsBaseTests.cs:110, base at MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Cqrs/ConstructorDependencyCountTestsBase.cs:26) and its sibling FixtureMap (:75-:84), which points both the Api and Application layers of the map at this test assembly (Module("Fixtures", Layer.Api, ...) and Module("Fixtures", Layer.Application, ...), :81-:82).
    • Concept introduced - a ceiling is a ratchet, not a budget. The base class doc states the rule directly: a consumer sets its high-water mark at the current widest constructor so the next class cannot silently grow past it (ConstructorDependencyCountTestsBase.cs:13-:17). ConformantTests is the fixture that proves a ceiling set at that mark does not itself trip the gate, which is what distinguishes a ratchet from an arbitrary cap: MaxControllerConstructorDependencies => 3 and MaxHandlerConstructorDependencies => 3 (:116,:118) equal the exact dependency counts the sibling TightCeilingTests fixture proves are offenders one below (TightCeilingTests ceilings of 2, :105,:107, flag FatFixtureController (3 ctor dependencies) and the two handler fixtures at the same count, ConstructorDependencyCountTestsBaseTests.cs:23,:44,:47). [Rubric §1 - Single Responsibility] is the property under test; [Rubric §14 - Testability] is why the fixture proves the boundary case rather than only the offending one.
    • Walkthrough - Map is the shared FixtureMap (:112), MaxConstructorDependencies is left at int.MaxValue (:114) because neither test that constructs this fixture exercises the Application-*Service fact, only the controller and handler delegates read directly off the instance. MaxControllerConstructorDependencies => 3 (:116) and MaxHandlerConstructorDependencies => 3 (:118) are the two ceilings under proof.
    • Where it's used - Controllers_AtTheCeiling_AreNotFlagged (ConstructorDependencyCountTestsBaseTests.cs:28-:34) reads new ConformantTests().Controllers_DoNotExceedConstructorDependencyCeiling and asserts NotThrow; Handlers_AtTheCeiling_AreNotFlagged (:52-:58) does the same against Handlers_DoNotExceedConstructorDependencyCeiling. Both assertions state the same ruling in their because clause: "the ceiling is a high-water mark: the widest controller/handler sits exactly on it" (:33,:57).
    • @@ -3956,25 +4266,14 @@

      ExecuteDeletingFixture

    • Walkthrough - internal static class ExecuteDeletingFixture with one static method, PurgeAsync(IQueryable<FixtureEntity>, CancellationToken) (:28-:29), a one-line pass-through into ExecuteDeleteAsync.
    • Where it's used - named in HardDelete_ReportsEveryErasingMember's "PurgeAsync" assertion (SoftDeleteEnforcementFitnessTests.cs:39) and in AllowlistedType_SilencesOnlyThatType, where it is the offender still reported after DbSetRemovingFixture alone is allowlisted (:72-:73).
    -

    ExemptedOffenderFixture

    -
    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.CascadeFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/CascadeFixtures/CascadeFixtures.cs:88 · Level 5 · class

    -
    -
      -
    • What it is - the same shape as MissingOverrideFixture (children present, Delete() never overridden), kept as a separate type so one self-test can allowlist it by name and prove the exemption silences exactly that type and no other offender.
    • -
    • Depends on - AuditableAggregateRootEntity<TIdentifierType> (internal sealed class ExemptedOffenderFixture : AuditableAggregateRootEntity<int>, CascadeFixtures.cs:88) and CascadeChildFixture (the child collection, :90).
    • -
    • Concept - cross-references the allowlist-precision concept introduced at ChildlessFixture's sibling assertions. Being a genuine offender with the identical shape as MissingOverrideFixture is the point: if the rule's allowlist matched by shape rather than by fully-qualified type name, allowlisting this type would silence both, and AllowlistedType_SilencesOnlyThatType would have nothing to distinguish.
    • -
    • Walkthrough - private readonly List<CascadeChildFixture> _exempt = [] (:90), exposed as IReadOnlyCollection<CascadeChildFixture> Exempt (:92). No Delete() override.
    • -
    • Where it's used - the subject of AllowlistedType_SilencesOnlyThatType in CascadeSoftDeleteFitnessTests (CascadeSoftDeleteFitnessTests.cs:73-:85): passing {FixtureNamespace}.{nameof(ExemptedOffenderFixture)} to the rule removes it from the failure message (:76,:83) while MissingOverrideFixture is still reported (:84).
    • -

    FixtureAssemblyMap

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Domain · (see per-type table) · Level 5 · class

    -

    The name is used four times in this assembly, once per IL-reading rule under proof, and all four are the same one-entry map: this test assembly registered as a single framework layer, so the rule's whole-layer scan lands on the fixtures. They are nested in different outer classes, so their full names differ and none is visible outside its own file.

    +

    The name is used six times in this assembly, once per IL-reading rule under proof, and all six are the same one-entry map: this test assembly registered as a single framework layer, so the rule's whole-layer scan lands on the fixtures. They are nested in different outer classes, so their full names differ and none is visible outside its own file.

    • Depends on - ArchitectureMapBase, LayerRef and the Layer enum.
    • -
    • Concept introduced - the layer a fixture map declares is not decoration, it is the scope selector. Each of these four rules starts from a different part of the map, so each map has to name the matching layer or the rule reads nothing and passes vacuously. The cascade and hard-delete rules walk every assembly the map registers, through ScannableAssemblyLocations (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.SoftDelete.cs:87-:91), so their layer choice is nominal; the domain-throw rule reads map.OfLayer(Layer.Domain) and nothing else (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.DomainThrows.cs:106-:110), so registering Domain is mandatory there. All four use Framework(...) rather than Module(...), which is the opposite choice from FixtureModuleMap and correct for the same reason: these rules do not filter on module ownership, so the simplest entry is the honest one. Reading assemblies off disk by Assembly.Location also explains a shared limitation: a rule in this family cannot inspect a dynamic or single-file assembly, and the helper silently skips any location that does not exist on disk.
    • +
    • Concept introduced - the layer a fixture map declares is not decoration, it is the scope selector. Each of these six rules starts from a different part of the map, so each map has to name a layer the rule reads or the rule reads nothing and passes vacuously. The cascade and hard-delete rules walk every assembly the map registers, through ScannableAssemblyLocations (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.SoftDelete.cs:88-:92), so their layer choice is nominal; the domain-throw rule reads map.OfLayer(Layer.Domain) and nothing else (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.DomainThrows.cs:112), so registering Domain is mandatory there. The clock-read rule reads the Domain and Application layers together (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.ClockReads.cs:65-:66), so either would do and its map picks Domain; the read-repository rule reads Application only (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Cqrs/ArchitectureRules.QueryHandlerRepositories.cs:87), so that entry is mandatory too. All six use Framework(...) rather than Module(...), which is the opposite choice from FixtureModuleMap and correct for the same reason: these rules do not filter on module ownership, so the simplest entry is the honest one. Reading assemblies off disk by Assembly.Location also explains a shared limitation: a rule in this family cannot inspect a dynamic or single-file assembly, and the helper silently skips any location that does not exist on disk.
    • Walkthrough - each declares RepoToken => "MMCA.Common" and a single-entry DefineLayers() naming one layer and one anchor type from the fixture namespace it serves. Anchoring by typeof(SomeFixture).Assembly rather than by Assembly.GetExecutingAssembly() is deliberate: it ties the map to a type a compiler would fail on if the fixtures moved.
    @@ -4006,15 +4305,38 @@

    FixtureAssemblyMap

    - - + + + + + + + + + + + + + +
    FixtureAssemblyMap (in DomainThrowFitnessTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/DomainThrowFitnessTests.cs:138Framework(Layer.Domain, typeof(NonThrowingFixture).Assembly) (:144)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/DomainThrowFitnessTests.cs:149Framework(Layer.Domain, typeof(NonThrowingFixture).Assembly) (:155) DomainThrowsOnlyArgumentGuards, which reads the Domain layer only, so this entry is load-bearing.
    FixtureAssemblyMap (in ClockReadTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/ClockReadTests.cs:84Framework(Layer.Domain, typeof(InjectedClockFixture).Assembly) (:90)DomainAndApplicationDoNotReadTheClock.
    FixtureAssemblyMap (in QueryHandlerReadRepositoryTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/Repositories/QueryHandlerReadRepositoryTests.cs:58Framework(Layer.Application, typeof(ReadRepositoryQueryHandlerFixture).Assembly) (:64)QueryHandlersUseReadRepositories, which reads the Application layer only, so this entry is load-bearing.
      -
    • Why they're built this way - four nested six-line classes rather than one shared fixture map, so each test file is readable end to end and no test can change another's scope by editing a shared file. It is the same trade-off FixtureModuleMap makes.
    • -
    • Caveats / not-in-source - because each map registers the whole test assembly, a rule in this family sees every type in it, not just the fixtures in its own namespace. Three of the four are unaffected because their fixtures are the only matching shapes present; the domain-throw rule is not, which is why it carries an explicit allowlist of the assembly's non-fixture throws (DomainThrowFitnessTests.cs:29-:33).
    • -
    • Where they're used - constructed once as a readonly field per test class (CascadeSoftDeleteFitnessTests.cs:21, SoftDeleteEnforcementFitnessTests.cs:19, DomainEventHandlerSaveFitnessTests.cs:21, DomainThrowFitnessTests.cs:35) and passed to every rule call in each.
    • +
    • Why they're built this way - six nested six-line classes rather than one shared fixture map, so each test file is readable end to end and no test can change another's scope by editing a shared file. It is the same trade-off FixtureModuleMap makes.
    • +
    • Caveats / not-in-source - because each map registers the whole test assembly, a rule in this family sees every type in it, not just the fixtures in its own namespace. Three of the six are unaffected because their fixtures are the only matching shapes present. The domain-throw rule is not, which is why it carries an explicit allowlist of the assembly's non-fixture throws (DomainThrowFitnessTests.cs:29-:33). The clock-read and read-repository tests take the other way out: they assert on what the failure report names rather than on the report being otherwise empty, and say so in their class docs (ClockReadTests.cs:10-:12, QueryHandlerReadRepositoryTests.cs:10-:12).
    • +
    • Where they're used - constructed once as a readonly field per test class (CascadeSoftDeleteFitnessTests.cs:21, SoftDeleteEnforcementFitnessTests.cs:19, DomainEventHandlerSaveFitnessTests.cs:21, DomainThrowFitnessTests.cs:35, ClockReadTests.cs:18, QueryHandlerReadRepositoryTests.cs:18). The first four pass it to every rule call; the last two pass it through a single Report helper that turns the rule's failure into a message string (ClockReadTests.cs:74, QueryHandlerReadRepositoryTests.cs:48), and keep the real CommonArchitectureMap as their base class's Map.
    • +
    +

    ExemptedOffenderFixture

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.CascadeFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/CascadeFixtures/CascadeFixtures.cs:88 · Level 5 · class

    +
    +
      +
    • What it is - the same shape as MissingOverrideFixture (children present, Delete() never overridden), kept as a separate type so one self-test can allowlist it by name and prove the exemption silences exactly that type and no other offender.
    • +
    • Depends on - AuditableAggregateRootEntity<TIdentifierType> (internal sealed class ExemptedOffenderFixture : AuditableAggregateRootEntity<int>, CascadeFixtures.cs:88) and CascadeChildFixture (the child collection, :90).
    • +
    • Concept - cross-references the allowlist-precision concept introduced at ChildlessFixture's sibling assertions. Being a genuine offender with the identical shape as MissingOverrideFixture is the point: if the rule's allowlist matched by shape rather than by fully-qualified type name, allowlisting this type would silence both, and AllowlistedType_SilencesOnlyThatType would have nothing to distinguish.
    • +
    • Walkthrough - private readonly List<CascadeChildFixture> _exempt = [] (:90), exposed as IReadOnlyCollection<CascadeChildFixture> Exempt (:92). No Delete() override.
    • +
    • Where it's used - the subject of AllowlistedType_SilencesOnlyThatType in CascadeSoftDeleteFitnessTests (CascadeSoftDeleteFitnessTests.cs:73-:85): passing {FixtureNamespace}.{nameof(ExemptedOffenderFixture)} to the rule removes it from the failure message (:76,:83) while MissingOverrideFixture is still reported (:84).

    FixtureLeakingEvent

    @@ -4151,8 +4473,8 @@

    ProtoContractFitnessTests

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Contracts · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/ProtoContractFitnessTests.cs:14 · Level 5 · class

      -
    • What it is - the meta-test for the frozen-.proto contract gate: five facts that prove a pinned proto matches its committed snapshot, that a renumbered field and an added rpc are both reported, that a missing file fails loudly, and that the parser ignores the lines that are not part of the wire contract.
    • -
    • Depends on - ArchitectureRules (ProtoContractsMatchFrozenList, ProtoContractFitnessTests.cs:47, and BuildProtoContract, :93), ArchitectureMapBase (FindRepoRoot, :92), two fixture .proto files under TestData/, and externals xUnit plus AwesomeAssertions.
    • +
    • What it is - the meta-test for the frozen-.proto contract gate: six facts that prove a pinned proto matches its committed snapshot, that a renumbered field and an added rpc are both reported, that a missing file fails loudly, that the parser ignores the lines that are not part of the wire contract, and that block comments in any position do not hide declarations.
    • +
    • Depends on - ArchitectureRules (ProtoContractsMatchFrozenList, ProtoContractFitnessTests.cs:47, and BuildProtoContract, :93), ArchitectureMapBase (FindRepoRoot, :92), three fixture .proto files under TestData/, and externals xUnit plus AwesomeAssertions.
    • Concept introduced - a wire contract as a committed, sorted string snapshot. A .proto file is a published contract: a renumbered field silently breaks every deployed peer, because protobuf keys on the number and not the name. The rule renders each file into a canonical, ordered list of lines (one per rpc with its streaming flag, one per message field with its label, type and NUMBER, one per enum value) and compares that list to a frozen array checked into the test (:28-:42). Regenerating the snapshot is a deliberate act: you print BuildProtoContract and paste the result, which is what makes a contract change a reviewable diff rather than a silent one. [Rubric §9 - API & Contract Design] assesses whether published contracts are versioned and change-controlled; this is the gRPC half of that discipline, and it is the same technique the integration-event contract snapshot uses (IntegrationEventContractTestsBaseTests proves that side). [Rubric §7 - Microservices Readiness] applies because these are exactly the contracts that survive extraction (ADR-007).
    • Walkthrough - three constants locate the fixtures relative to the repo root: SolutionFileName = "MMCA.Common.slnx" (:16), the TestData directory (:18), and the pinned and drifted file arrays (:20,:22). The 12-line FrozenContract (:28-:42) is the snapshot, sorted so the comparison is order-independent for the author and deterministic for the diff.
      • Rule_PassesWhenTheProtoMatchesItsFrozenSnapshot (:44-:50): the positive case.
      • @@ -4160,10 +4482,11 @@

        ProtoContractFitnessTests

      • Rule_FlagsAnAddedRpc (:66-:74): asserts the added DeleteProduct rpc is named, because an addition peers have not been told about is still a contract change.
      • Rule_ReportsAMissingProtoFileExplicitly (:76-:87): a path typo must produce <missing proto file> does-not-exist.proto rather than silently pinning an empty contract, the classic way a snapshot gate rots into a no-op.
      • BuildProtoContract_IgnoresSyntaxImportAndOptionLines (:89-:98): resolves the repo root, builds the contract from the pinned file, and asserts it equals FrozenContract exactly and contains neither the csharp_namespace option nor the timestamp.proto import (:95-:97). Those lines are code-generation details, not wire contract, so churning them must not fail anyone's build.
      • +
      • BuildProtoContract_SurvivesBlockCommentsInEveryPosition (:100-:110): builds the contract from fitness-catalog-commented.proto (:104-:105) and asserts it equals the same FrozenContract (:107-:109). That fixture carries a block comment closed on its own line, a trailing one after a field, a /* inside a line comment, and a block spanning two lines (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/TestData/fitness-catalog-commented.proto:17, :19, :20, :24-:25). A comment stripper that miscounts any of them swallows the declarations after it, which would shrink the contract and surface as a spurious "missing" diff, so comments are proven inert the same way the option and import lines are.
    • -
    • Why it's built this way - MMCA.Common ships no protos of its own, so this is the framework's only exercise of a consumer-facing rule (class doc, :9-:12); consumers subclass ProtoContractTestsBase. The two fixture files differ by exactly the two breaking changes under test (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/TestData/fitness-catalog.proto versus fitness-catalog-drifted.proto), and the pinned one deliberately includes a nested message, a nested enum, a repeated field, an optional field, and a streaming rpc so the parser is exercised on every shape it claims to handle.
    • -
    • Where it's used - an independent class in the Common architecture suite. Neither fixture proto is compiled by any project.
    • +
    • Why it's built this way - MMCA.Common ships no protos of its own, so this is the framework's only exercise of a consumer-facing rule (class doc, :9-:12); consumers subclass ProtoContractTestsBase. The pinned and drifted fixture files differ by exactly the two breaking changes under test (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/TestData/fitness-catalog.proto versus fitness-catalog-drifted.proto), and the pinned one deliberately includes a nested message, a nested enum, a repeated field, an optional field, and a streaming rpc so the parser is exercised on every shape it claims to handle. The commented file renders to the identical snapshot, so the one frozen array serves all three.
    • +
    • Where it's used - an independent class in the Common architecture suite. None of the three fixture protos is compiled by any project.

    ReadRepositoryFixtureAggregate

    @@ -4260,7 +4583,7 @@

    TightCeilingTests

    • What it is - a private nested fixture inside ConstructorDependencyCountTestsBaseTests: a ConstructorDependencyCountTestsBase subclass whose controller and handler ceilings are set tight (2 each) against a nested FixtureMap that scans a three-dependency controller and two three-dependency handlers, so both populations are guaranteed to be flagged.
    • -
    • Depends on - ConstructorDependencyCountTestsBase (the abstract base it extends, ConstructorDependencyCountTestsBaseTests.cs:139), the nested FixtureMap (:116-:125) pointing both the Api and Application layers at this test assembly, and int.MaxValue for MaxConstructorDependencies, since the Application *Service ceiling is not exercised by this fixture (:101-:104).
    • +
    • Depends on - ConstructorDependencyCountTestsBase (the abstract base it extends, ConstructorDependencyCountTestsBaseTests.cs:98), the nested FixtureMap (:116-:125) pointing both the Api and Application layers at this test assembly, and int.MaxValue for MaxConstructorDependencies, since the Application *Service ceiling is not exercised by this fixture (:101-:104).
    • Concept introduced - a ceiling paired with fixtures that deliberately exceed it, the standard shape for proving a counting rule fires. The two ceilings, MaxControllerConstructorDependencies => 2 (:106) and MaxHandlerConstructorDependencies => 2 (:108), sit one below the three dependencies the fixture controller and fixture handlers actually declare, so Controllers_DoNotExceedConstructorDependencyCeiling and Handlers_DoNotExceedConstructorDependencyCeiling both throw when run against this map. [Rubric section 14 - Testability] is the technique.
    • Walkthrough - three overrides only: Map returns the nested FixtureMap (:100), MaxConstructorDependencies is left at int.MaxValue with a comment stating the Application *Service fact is not exercised here (:102-:104), and the two ceilings (:106,:108) are both set to 2.
    • Why it's built this way - one map with deliberately-noncompliant fixtures lets ConstructorDependencyCountTestsBaseTests prove the offender-naming behavior (type name plus dependency count) for both the controller and handler populations from a single nested type, rather than duplicating the fixture assembly per population.
    • @@ -4334,12 +4657,12 @@

      DependencyVersionTests

    FixtureMap

    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Contracts · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/IntegrationEventContractTestsBaseTests.cs:124 · Level 6 · class

    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Contracts · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/IntegrationEventContractTestsBaseTests.cs:254 · Level 6 · class

    • What it is - a consumer-shaped map over this test assembly, declaring one module Shared layer, so the integration-event contract scan covers the fixture events here and none of the framework's own.
    • -
    • Depends on - ArchitectureMapBase (private sealed class FixtureMap : ArchitectureMapBase, IntegrationEventContractTestsBaseTests.cs:124), LayerRef and the Layer enum.
    • -
    • Concept - cross-references the module-versus-framework distinction taught at FixtureModuleMap and FakeConsumerMap. The single Module("Fixture", Layer.Shared, ...) entry (:130) does two things at once. It makes the map module-bearing, so IntegrationEvents excludes framework layers (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Events.cs:70-:73) and no framework-shipped event can leak into the contract the probes compare; and it names Shared, which is where a consumer's integration events are required to live, so the fixture events do not simultaneously trip the residency rule. RepoToken => "MMCA.FixtureRepo" (:126) is deliberately unlike MMCA.Common, so nothing derived from the token can collide with the real framework namespaces.
    • +
    • Depends on - ArchitectureMapBase (private sealed class FixtureMap : ArchitectureMapBase, IntegrationEventContractTestsBaseTests.cs:254), LayerRef and the Layer enum.
    • +
    • Concept - cross-references the module-versus-framework distinction taught at FixtureModuleMap and FakeConsumerMap. The single Module("Fixture", Layer.Shared, ...) entry (:130) does two things at once. It makes the map module-bearing, so IntegrationEvents excludes framework layers (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Events.cs:141-:73) and no framework-shipped event can leak into the contract the probes compare; and it names Shared, which is where a consumer's integration events are required to live, so the fixture events do not simultaneously trip the residency rule. RepoToken => "MMCA.FixtureRepo" (:126) is deliberately unlike MMCA.Common, so nothing derived from the token can collide with the real framework namespaces.
    • Walkthrough - DefineLayers() is a yield-based iterator with one entry (:128-:131). The events it brings into scope are the eight upcaster fixture contracts, which are the only IIntegrationEvent implementations this assembly declares. That reuse is what makes the class doc's promise hold, that the mutation cases stay honest as the fixture types change (:9-:10): the baseline is rebuilt from live types on every call rather than hard-coded.
    • Where it's used - built fresh by LiveContract() (:86-:87) on every mutation, and supplied as the Map of every ProbeTests instance (:136).
    @@ -4414,37 +4737,33 @@

    PiiErasureContractFitnessTests

  • Why it's built this way - the plain [Pii] => IAnonymizable scan is vacuous in the framework (no PII entity in Common's Domain); this test closes that gap by forcing the machinery through a stand-in subject, so the §30 guarantee is proven, not merely assumed. Consumers (MMCA.ADC's User) run the same contract against their real aggregates.
  • Where it's used - an independent test class in the Common architecture suite; its structural counterpart is PiiConventionTests, whose own doc names this class as the non-vacuous half of the pair (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/PiiConventionTests.cs:9-:11).
  • -

    ProbeTests

    -
    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Contracts · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/IntegrationEventContractTestsBaseTests.cs:134 · Level 6 · class

    -
    -
      -
    • What it is - a parameterized negative fixture: a private sealed subclass of the frozen-contract base whose ExpectedContract is whatever the enclosing test hands its constructor.
    • -
    • Depends on - IntegrationEventContractTestsBase (private sealed class ProbeTests(IReadOnlyList<string> expected) : IntegrationEventContractTestsBase, IntegrationEventContractTestsBaseTests.cs:134) and FixtureMap (:136).
    • -
    • Concept introduced - parameterizing the drift instead of hard-coding it. DriftedTests is the same technique with fixed wrong values, which works when a base has three assertions and each needs one wrong constant. Here the base has one assertion with six interesting failure modes, so a fixed drifted subclass would mean six near-identical nested classes. A primary-constructor parameter collapses them into one type instantiated six times with different data (:20,:30,:41,:54,:68,:79). The trade-off is worth naming: the parameterized form is only safe because the expected contract is derived from the live one and then mutated, so a probe can never assert against a stale hand-written literal.
    • -
    • Walkthrough - two overrides, both initialized rather than computed: Map { get; } = new FixtureMap() (:136) and ExpectedContract { get; } = expected (:138). Being private keeps xUnit from collecting the inherited IntegrationEventContracts_ShouldMatch_TheFrozenSnapshot as a test of its own, and the enclosing facts invoke it as a delegate instead.
    • -
    • Where it's used - constructed six times across the facts of IntegrationEventContractTestsBaseTests, once per contract mutation.
    • -

    IntegrationEventContractTestsBaseTests

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Contracts · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/IntegrationEventContractTestsBaseTests.cs:13 · Level 6 · class

      -
    • What it is - the meta-test for the frozen integration-event contract base: six facts proving that a reordered member list is not a break, and that everything a consumer can actually observe (a missing member, an extra member, a retyped member, a missing event) still is.
    • -
    • Depends on - IntegrationEventContractTestsBase (the type under test, through the nested ProbeTests), ArchitectureRules (BuildIntegrationEventContract, :87), its nested FixtureMap, and externals xUnit plus AwesomeAssertions.
    • -
    • Concept introduced - a snapshot gate has to be insensitive to everything the wire is insensitive to, or it trains people to update it by rote. The base compares each event's member list as a SET rather than a sequence, and the reason is stated plainly in its own doc (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Contracts/IntegrationEventContractTestsBase.cs:10-:16): JSON carries no member order, so reordering two properties in a record declaration changes nothing a consumer can observe, and failing the build for it would teach a team to regenerate the one gate that guards real breakage without reading it. The comparison therefore reports four kinds of difference and no others: MISSING EVENT, NEW EVENT (:52-:56), and per event MISSING member, changed type and EXTRA member (:78-:96). The retype is reported as a retype rather than as an unrelated add and remove, because that is the failure a consumer actually sees: the property still deserializes, into the wrong shape (:64-:68). [Rubric §6 - CQRS & Event-Driven] and [Rubric §9 - API & Contract Design] are the properties; [Rubric §33 - Developer Experience] is the reason for the set comparison, since a gate that cries wolf stops being read.
    • -
    • Walkthrough - the class never hard-codes an expected contract. It builds the live one from FixtureMap through LiveContract() (:86-:87) and then mutates it, so every case stays honest as the fixture types change (class doc, :8-:10).
        +
      • What it is - the meta-test for the frozen integration-event contract base: thirteen facts proving that a reordered member list is not a break, that everything a consumer can actually observe (a missing member, an extra member, a retyped member, a retyped generic argument, a nullability change, a missing event) still is, that the rendered contract is precise enough to see those changes at all, and that the snapshot-regeneration switch writes paste-ready literals.
      • +
      • Depends on - IntegrationEventContractTestsBase (the type under test, through the nested ProbeTests), ArchitectureRules (BuildIntegrationEventContract, :199), its nested FixtureMap, the fixture events in the same test assembly (keyed by .FixtureTallyEvent, .FixtureCountEvent, .FixtureNullableLabelEvent and the like), and externals xUnit plus AwesomeAssertions.
      • +
      • Concept introduced - a snapshot gate has to be insensitive to everything the wire is insensitive to, and sensitive to everything it is not, or it trains people to update it by rote. The base compares each event's member list as a SET rather than a sequence, and the reason is stated plainly in its own doc (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Contracts/IntegrationEventContractTestsBase.cs:10-:16): JSON carries no member order, so reordering two properties in a record declaration changes nothing a consumer can observe, and failing the build for it would teach a team to regenerate the one gate that guards real breakage without reading it. The comparison therefore reports five kinds of difference and no others: MISSING EVENT and NEW EVENT (:69-:73), and per event MISSING member, changed type and EXTRA member (:90-:114). The retype is reported as a retype rather than as an unrelated add and remove, because that is the failure a consumer actually sees: the property still deserializes, into the wrong shape (:82-:84). The member list is split on commas outside angle brackets (SplitTopLevel, :163-:180), so a multi-argument generic type stays one member, and each line is keyed on the event's [EventName] value when it declares one, its full type name otherwise (:29-:31). [Rubric §6 - CQRS & Event-Driven] and [Rubric §9 - API & Contract Design] are the properties; [Rubric §33 - Developer Experience] is the reason for the set comparison, since a gate that cries wolf stops being read.
      • +
      • Walkthrough - the class never hard-codes a whole expected contract. It builds the live one from FixtureMap through LiveContract() (:198-:199) and then either mutates it or asserts on specific lines of it, so every case stays honest as the fixture types change (class doc, :8-:10).
        • Base_Passes_WhenTheCommittedLiteralsAreAlphabetical (:15-:23): the unmutated baseline, which is the shape every consumer's committed literal is written in today.
        • -
        • Base_Passes_WhenMembersAreListedInADifferentOrder (:25-:34): reverses each line's member list through ReverseMembers (:90-:94) and asserts NotThrow, with the because stating why order is not observable.
        • +
        • Base_Passes_WhenMembersAreListedInADifferentOrder (:25-:34): reverses each line's member list through ReverseMembers (:202-:206) and asserts NotThrow, with the because stating why order is not observable.
        • Base_Fails_WhenTheCodeDeclaresAMemberTheContractDoesNot (:36-:47) and Base_Fails_WhenTheContractDeclaresAMemberTheCodeDropped (:49-:60): drop the first member and append a GhostProperty:String, asserting "EXTRA member" and "MISSING member GhostProperty:String" respectively.
        • Base_Fails_WhenAMembersTypeChanges (:62-:74): rewrites the first member's type to Guid and asserts "changed type".
        • Base_Fails_WhenAnEventIsMissingFromTheSnapshot (:76-:83): drops the first line entirely and asserts "NEW EVENT", which is the direction that matters most, an event shipped without a consumer rollout.
        • -
        • MutateFirstMultiMemberEvent (:100-:109) is the helper that keeps all four failure cases robust: it finds the first event declaring more than one member and asserts that such an event exists (:104), so a fixture reshaped down to single-member events fails loudly instead of making the cases vacuous. Split (:111-:118) parses one line back into a name and a member array.
        • +
        • Contract_SpellsOutGenericArguments_AndPinsAnIntermediateBase (:85-:98): asserts the exact FixtureShapedEvent line, { Count:Nullable<Int32>, Origin:String, Tags:IReadOnlyList<String> }, so generic arguments and a member inherited from an intermediate consumer base are both rendered, and asserts no fixture line carries SchemaVersion: or DateOccurred:, because the framework envelope is the framework's contract, not the event's.
        • +
        • Base_Fails_WhenAGenericArgumentOfAMultiArgumentMemberChanges (:100-:114): rewrites <String, Int32> to <String, Int64> on the FixtureTallyEvent line and asserts "member Counts changed type", proving the comma inside the angle brackets belongs to the member type and not to the member list.
        • +
        • Contract_Differs_WhenTheOnlyChangeIsIntToNullableInt (:116-:124): the member blocks of FixtureNullableCountEvent and FixtureCountEvent must differ, since int to int? changes what a consumer must accept.
        • +
        • Contract_Differs_WhenTheOnlyChangeIsANullableReferenceAnnotation (:126-:137): pins { Label:String?, Notes:IReadOnlyList<String?> } against its non-nullable twin { Label:String, Notes:IReadOnlyList<String> }, so both a nullable member and a nullable generic argument are part of the rendered promise.
        • +
        • Base_Fails_WhenOnlyAMembersNullabilityChanges (:139-:153): strips the ? from Label:String? and asserts "member Label changed type".
        • +
        • Contract_KeysAnEventOnItsEventName_WhenItDeclaresOne (:155-:166): the contract contains Fixture.Named.v1 { Value:Int32 } and no line mentions FixtureNamedEvent, so a CLR rename does not churn the snapshot of an event whose [EventName] is the wire identity the outbox stores.
        • +
        • Base_WritesTheLiveContractAsLiterals_WhenTheSnapshotOutputVariableIsSet (:168-:188): sets IntegrationEventContractTestsBase.SnapshotOutputVariable (MMCA_CONTRACT_SNAPSHOT_OUT, base :27) to a temp path, runs the probe fact, and asserts the file holds each live line as a quoted, comma-terminated C# literal (the base writes it at :40-:44); a finally clears the variable and deletes the file.
        • +
        • MutateFirstMultiMemberEvent (:212-:221) is the helper that keeps the four generic failure cases robust: it finds the first event declaring more than one member and asserts that such an event exists (:216), so a fixture reshaped down to single-member events fails loudly instead of making the cases vacuous. Split (:223-:228) parses one line back into a name and a member array through SplitTopLevel (:231-:248), which mirrors the base's angle-bracket-aware splitter; MembersOf (:191-:195) returns the member block of the single line containing a marker.
      • -
      • Why it's built this way - deriving the expectations from the live contract and mutating them is what stops this meta-test from becoming a second snapshot that also needs maintaining. The probe subclasses are private so xUnit does not collect their inherited facts as tests of their own (class doc, :10-:11), the same discipline DriftedTests follows.
      • +
      • Why it's built this way - deriving the expectations from the live contract and mutating them is what stops this meta-test from becoming a second snapshot that also needs maintaining. The few literal lines it does pin are the rendering rules themselves (generic arguments, nullability, the [EventName] key), where the exact text is the point. The probe subclasses are private so xUnit does not collect their inherited facts as tests of their own (class doc, :10-:11), the same discipline DriftedTests follows. The snapshot-output fact mutates a process-wide environment variable; its own comment (:171-:172) relies on it being the only fact that invokes the probe this way and on xUnit running one class's tests sequentially.
      • Where it's used - an independent class in the Common architecture suite. The base it protects is subclassed per repo by the consumers, whose committed ExpectedContract literals are the real subject of the gate.
      • -
      • Caveats / not-in-source - the base's Parse keeps a malformed committed line whole as an event with no members (IntegrationEventContractTestsBase.cs:99-:122) so it surfaces as a mismatch rather than being silently dropped. No fact here exercises that path.
      • +
      • Caveats / not-in-source - the base's Parse keeps a malformed committed line, braces stripped, as an event with no members (IntegrationEventContractTestsBase.cs:131-:138) so it surfaces as a mismatch rather than being silently dropped. No fact here exercises that path.

      LocalizationResourceTests

      @@ -4470,6 +4789,72 @@

      ModuleProbeMap

    • Why it's built this way - the class doc above IntegrationEventPayloadPurityRuleTests states the intent directly (:23-:26): "a rule that only ever passes proves nothing", both rules are pointed at this assembly whose fixtures include a deliberately bad event, and both must name it. FrameworkProbeMap and ModuleProbeMap are the two probes that make that adversarial coverage possible without touching the rule under test.
    • Where it's used - ResidencyRule_FlagsAnEventOutsideASharedAssembly (:30-:39), asserting the message contains FixtureCleanEvent and "is not a *.Shared assembly". Private to IntegrationEventPayloadPurityRuleTests; not used outside this file.
    +

    FixtureAssemblyMap

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Cqrs.Repositories · (see per-type table) · Level 9 · class

    +
    +

    The name is used five times in this assembly, once per IL-reading rule under proof, and all five are the same one-entry map: this test assembly registered as a single framework layer, so the rule's whole-layer scan lands on the fixtures. They are nested in different outer classes, so their full names differ and none is visible outside its own file.

    +
      +
    • Depends on - ArchitectureMapBase, LayerRef and the Layer enum.
    • +
    • Concept introduced - the layer a fixture map declares is not decoration, it is the scope selector. Each of these five rules starts from a different part of the map, so each map has to name the matching layer or the rule reads nothing and passes vacuously. The cascade and hard-delete rules walk every assembly the map registers, through ScannableAssemblyLocations (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.SoftDelete.cs:88-:92), so their layer choice is nominal; the domain-throw rule reads map.OfLayer(Layer.Domain) and nothing else (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.DomainThrows.cs:111-:111), so registering Domain is mandatory there, and the read-repository rule reads map.OfLayer(Layer.Application) only (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Cqrs/ArchitectureRules.QueryHandlerRepositories.cs:86-:89), so Application is mandatory there. All five use Framework(...) rather than Module(...), which is the opposite choice from FixtureModuleMap and correct for the same reason: these rules do not filter on module ownership, so the simplest entry is the honest one. Reading assemblies off disk by Assembly.Location also explains a shared limitation: a rule in this family cannot inspect a dynamic or single-file assembly, and the helper silently skips any location that does not exist on disk. [Rubric §14 - Testability] assesses whether a guardrail is provable with focused inputs, and a map that scopes the scan to fixtures is what makes each of these five rules provable at all.
    • +
    • Walkthrough - each declares RepoToken => "MMCA.Common" and a single-entry DefineLayers() naming one layer and one anchor type from the fixture namespace it serves. Anchoring by typeof(SomeFixture).Assembly rather than by Assembly.GetExecutingAssembly() is deliberate: it ties the map to a type a compiler would fail on if the fixtures moved.
    • +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeFile:LineLayer and anchorThe rule it scopes
    FixtureAssemblyMap (in CascadeSoftDeleteFitnessTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/CascadeSoftDeleteFitnessTests.cs:97Framework(Layer.Domain, typeof(CascadeChildFixture).Assembly) (:103)AggregatesCascadeSoftDeleteToChildren.
    FixtureAssemblyMap (in SoftDeleteEnforcementFitnessTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/SoftDeleteEnforcementFitnessTests.cs:77Framework(Layer.Infrastructure, typeof(FixtureEntity).Assembly) (:83)HardDeletesOnlyInAllowedTypes.
    FixtureAssemblyMap (in DomainEventHandlerSaveFitnessTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/DomainEventHandlerSaveFitnessTests.cs:110Framework(Layer.Application, typeof(FixtureDomainEvent).Assembly) (:116)DomainEventHandlersDoNotSave.
    FixtureAssemblyMap (in DomainThrowFitnessTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/DomainThrowFitnessTests.cs:149Framework(Layer.Domain, typeof(NonThrowingFixture).Assembly) (:144)DomainThrowsOnlyArgumentGuards, which reads the Domain layer only, so this entry is load-bearing.
    FixtureAssemblyMap (in QueryHandlerReadRepositoryTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/Repositories/QueryHandlerReadRepositoryTests.cs:58Framework(Layer.Application, typeof(ReadRepositoryQueryHandlerFixture).Assembly) (:64)QueryHandlersUseReadRepositories, which reads the Application layer only.
    +
      +
    • Why they're built this way - five nested six-line classes rather than one shared fixture map, so each test file is readable end to end and no test can change another's scope by editing a shared file. It is the same trade-off FixtureModuleMap makes.
    • +
    • Caveats / not-in-source - because each map registers the whole test assembly, a rule in this family sees every type in it, not just the fixtures in its own namespace. Four of the five are unaffected because their fixtures are the only matching shapes present; the domain-throw rule is not, which is why it carries an explicit allowlist of the assembly's non-fixture throws (DomainThrowFitnessTests.cs:29-:33).
    • +
    • Where they're used - constructed once as a readonly field per test class (CascadeSoftDeleteFitnessTests.cs:21, SoftDeleteEnforcementFitnessTests.cs:19, DomainEventHandlerSaveFitnessTests.cs:21, DomainThrowFitnessTests.cs:35, QueryHandlerReadRepositoryTests.cs:18) and passed to every rule call in each.
    • +
    +

    ProbeTests

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Contracts · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/IntegrationEventContractTestsBaseTests.cs:264 · Level 6 · class

    +
    +
      +
    • What it is - a parameterized negative fixture: a private sealed subclass of the frozen-contract base whose ExpectedContract is whatever the enclosing test hands its constructor.
    • +
    • Depends on - IntegrationEventContractTestsBase (private sealed class ProbeTests(IReadOnlyList<string> expected) : IntegrationEventContractTestsBase, IntegrationEventContractTestsBaseTests.cs:264) and FixtureMap (:136).
    • +
    • Concept introduced - parameterizing the drift instead of hard-coding it. DriftedTests is the same technique with fixed wrong values, which works when a base has three assertions and each needs one wrong constant. Here the base has one assertion with six interesting failure modes, so a fixed drifted subclass would mean six near-identical nested classes. A primary-constructor parameter collapses them into one type instantiated six times with different data (:20,:30,:41,:54,:68,:79). The trade-off is worth naming: the parameterized form is only safe because the expected contract is derived from the live one and then mutated, so a probe can never assert against a stale hand-written literal.
    • +
    • Walkthrough - two overrides, both initialized rather than computed: Map { get; } = new FixtureMap() (:136) and ExpectedContract { get; } = expected (:138). Being private keeps xUnit from collecting the inherited IntegrationEventContracts_ShouldMatch_TheFrozenSnapshot as a test of its own, and the enclosing facts invoke it as a delegate instead.
    • +
    • Where it's used - constructed six times across the facts of IntegrationEventContractTestsBaseTests, once per contract mutation.
    • +

    ScalarOnlySpec

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Domain · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/SpecificationFitnessTests.cs:69 · Level 6 · class

    @@ -4497,7 +4882,7 @@

    SoftDeleteEnforcementFitnessTests

  • Why it's built this way - the class doc names the principle again (:7-:14): a rule that reads IL cannot be verified by reading it, so the offending and the innocent call sites are compiled side by side and a map is pointed at them. See ADR-005 and ADR-015.
  • -
  • Where it's used - an independent class in the Common architecture suite; the shipped binding of the same rule over real code is SoftDeleteEnforcementTestsBase, activated here by SoftDeleteEnforcementTests with the framework's four reviewed eraser types.
  • +
  • Where it's used - an independent class in the Common architecture suite; the shipped binding of the same rule over real code is SoftDeleteEnforcementTestsBase, activated here by SoftDeleteEnforcementTests with the framework's four reviewed eraser types.
  • SpecificationFitnessTests

    @@ -4577,61 +4962,6 @@

    QueryHandlerReadRepositoryTests: QueryHandlerAskingForTheWriteRepository_IsFlagged_EvenInsideAnAsyncBody and AllowlistedHandler_IsSilenced assert on WriteRepositoryQueryHandlerFixture (QueryHandlerReadRepositoryTests.cs:23-:26,:38-:41), QueryHandlerAskingForTheReadRepository_IsNotFlagged asserts ReadRepositoryQueryHandlerFixture is absent (:28-:30), and CommandHandlerAskingForTheWriteRepository_IsOutOfScope asserts WriteRepositoryCommandHandlerFixture is absent (:32-:36). -

    FixtureAssemblyMap

    -
    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Cqrs.Repositories · (see per-type table) · Level 9 · class

    -
    -

    The name is used five times in this assembly, once per IL-reading rule under proof, and all five are the same one-entry map: this test assembly registered as a single framework layer, so the rule's whole-layer scan lands on the fixtures. They are nested in different outer classes, so their full names differ and none is visible outside its own file.

    -
      -
    • Depends on - ArchitectureMapBase, LayerRef and the Layer enum.
    • -
    • Concept introduced - the layer a fixture map declares is not decoration, it is the scope selector. Each of these five rules starts from a different part of the map, so each map has to name the matching layer or the rule reads nothing and passes vacuously. The cascade and hard-delete rules walk every assembly the map registers, through ScannableAssemblyLocations (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.SoftDelete.cs:88-:92), so their layer choice is nominal; the domain-throw rule reads map.OfLayer(Layer.Domain) and nothing else (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.DomainThrows.cs:107-:111), so registering Domain is mandatory there, and the read-repository rule reads map.OfLayer(Layer.Application) only (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Cqrs/ArchitectureRules.QueryHandlerRepositories.cs:86-:89), so Application is mandatory there. All five use Framework(...) rather than Module(...), which is the opposite choice from FixtureModuleMap and correct for the same reason: these rules do not filter on module ownership, so the simplest entry is the honest one. Reading assemblies off disk by Assembly.Location also explains a shared limitation: a rule in this family cannot inspect a dynamic or single-file assembly, and the helper silently skips any location that does not exist on disk. [Rubric §14 - Testability] assesses whether a guardrail is provable with focused inputs, and a map that scopes the scan to fixtures is what makes each of these five rules provable at all.
    • -
    • Walkthrough - each declares RepoToken => "MMCA.Common" and a single-entry DefineLayers() naming one layer and one anchor type from the fixture namespace it serves. Anchoring by typeof(SomeFixture).Assembly rather than by Assembly.GetExecutingAssembly() is deliberate: it ties the map to a type a compiler would fail on if the fixtures moved.
    • -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    TypeFile:LineLayer and anchorThe rule it scopes
    FixtureAssemblyMap (in CascadeSoftDeleteFitnessTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/CascadeSoftDeleteFitnessTests.cs:97Framework(Layer.Domain, typeof(CascadeChildFixture).Assembly) (:103)AggregatesCascadeSoftDeleteToChildren.
    FixtureAssemblyMap (in SoftDeleteEnforcementFitnessTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/SoftDeleteEnforcementFitnessTests.cs:77Framework(Layer.Infrastructure, typeof(FixtureEntity).Assembly) (:83)HardDeletesOnlyInAllowedTypes.
    FixtureAssemblyMap (in DomainEventHandlerSaveFitnessTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/DomainEventHandlerSaveFitnessTests.cs:110Framework(Layer.Application, typeof(FixtureDomainEvent).Assembly) (:116)DomainEventHandlersDoNotSave.
    FixtureAssemblyMap (in DomainThrowFitnessTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/DomainThrowFitnessTests.cs:138Framework(Layer.Domain, typeof(NonThrowingFixture).Assembly) (:144)DomainThrowsOnlyArgumentGuards, which reads the Domain layer only, so this entry is load-bearing.
    FixtureAssemblyMap (in QueryHandlerReadRepositoryTests)MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/Repositories/QueryHandlerReadRepositoryTests.cs:58Framework(Layer.Application, typeof(ReadRepositoryQueryHandlerFixture).Assembly) (:64)QueryHandlersUseReadRepositories, which reads the Application layer only.
    -
      -
    • Why they're built this way - five nested six-line classes rather than one shared fixture map, so each test file is readable end to end and no test can change another's scope by editing a shared file. It is the same trade-off FixtureModuleMap makes.
    • -
    • Caveats / not-in-source - because each map registers the whole test assembly, a rule in this family sees every type in it, not just the fixtures in its own namespace. Four of the five are unaffected because their fixtures are the only matching shapes present; the domain-throw rule is not, which is why it carries an explicit allowlist of the assembly's non-fixture throws (DomainThrowFitnessTests.cs:29-:33).
    • -
    • Where they're used - constructed once as a readonly field per test class (CascadeSoftDeleteFitnessTests.cs:21, SoftDeleteEnforcementFitnessTests.cs:19, DomainEventHandlerSaveFitnessTests.cs:21, DomainThrowFitnessTests.cs:35, QueryHandlerReadRepositoryTests.cs:18) and passed to every rule call in each.
    • -

    PointsAwarder

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.DomainEventSaveFixtures · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/DomainEventSaveFixtures/DomainEventSaveFixtures.cs:42 · Level 9 · class

    @@ -4684,7 +5014,19 @@

    CommonArchitectureMap

  • Concept introduced - the map as the single point of repo-specific truth for architecture rules. The rule bodies live once in MMCA.Common.Testing.Architecture and are parameterized by an IArchitectureMap; each repo supplies exactly one map so the same rules run identically across Common, Store, and ADC. Because Common is a module-less framework, every layer is registered as a framework layer via the Framework(...) helper rather than a module layer, and that distinction is load-bearing well beyond bookkeeping: several rules branch on map.ModuleNames.Count to decide whether they are judging a framework or a consumer (see EventScopeFitnessTests), and several others read only the per-module accessors, which is why the self-tests for those use FixtureModuleMap instead of this one. [Rubric §3 - Clean Architecture] assesses whether layer boundaries are explicit and enforced; this map is the machine-readable statement of those boundaries.
  • Walkthrough - RepoToken => "MMCA.Common" (:17) identifies the repo and is what the source-scanning rules use to locate the repo root (they look for {RepoToken}.slnx). DefineLayers() (:19-:28) returns one Framework(Layer.X, anchorType.Assembly) entry per package, using a single anchor type to resolve each assembly (mirrors the old PackageAssemblies helper): Shared, Domain, Application, Infrastructure, Api, Grpc, and Ui (:21-:27). The doc comment (:8-:13) records a deliberate omission: MMCA.Common.UI.Maui (ADR-042) is absent because its four MAUI TFM assemblies cannot load in the ubuntu net10.0 test process, so its UI-plus-Shared boundary is enforced at compile time by EnforceUIMauiLayerBoundary in Source/Build/MMCA.Common.LayerEnforcement.targets and the windows build-maui CI job instead.
  • Why it's built this way - one map per repo keeps the rule bodies DRY and identical everywhere (see the "Architecture Enforcement" section in MMCA.Common/CLAUDE.md); anchoring by type keeps the assembly reference refactor-safe.
  • -
  • Where it's used - supplied as Map by every thin *ConventionTests subclass in this unit, used directly by EventScopeFitnessTests as the module-less contrast case (EventScopeFitnessTests.cs:39), by EventUpcasterFitnessTests as the map that owns no upcasters (EventUpcasterFitnessTests.cs:51) and by DomainThrowFitnessTests as the framework's own ADR-013 reference run (DomainThrowFitnessTests.cs:131). It is also the pattern the single-layer fitness maps (SpecTestMap, IdempotencyTestMap, CancellationTestMap, CycleTestMap, FixtureAssemblyMap) collapse.
  • +
  • Where it's used - supplied as Map by every thin *ConventionTests subclass in this unit, used directly by EventScopeFitnessTests as the module-less contrast case (EventScopeFitnessTests.cs:39), by EventUpcasterFitnessTests as the map that owns no upcasters (EventUpcasterFitnessTests.cs:51) and by DomainThrowFitnessTests as the framework's own ADR-013 reference run (DomainThrowFitnessTests.cs:142). It is also the pattern the single-layer fitness maps (SpecTestMap, IdempotencyTestMap, CancellationTestMap, CycleTestMap, FixtureAssemblyMap) collapse.
  • + +

    FrameworkModuleArchitectureMap

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Domain.EntityModel · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/EntityModel/FrameworkModuleArchitectureMap.cs:16 · Level 12 · class

    +
    +
      +
    • What it is - a second architecture map for the framework repo that registers MMCA.Common's own Shared, Domain and Application assemblies as one business module named "Framework", so the module-scoped DDD rules have real subjects in a repo that otherwise declares no modules.
    • +
    • Depends on - ArchitectureMapBase (internal sealed class FrameworkModuleArchitectureMap : ArchitectureMapBase, FrameworkModuleArchitectureMap.cs:16), the Layer enum, LayerRef, and four anchor types: Result (:25), BaseEntity<> (:26), DomainEventDispatcher (:27) and ApplicationDbContext (:28).
    • +
    • Concept introduced - the same assemblies, declared twice, because the declaration decides which rules can see them. CommonArchitectureMap registers every package as a framework layer, which is the correct shape for layering and purity rules but leaves any rule that reads only IArchitectureMap's ModuleDomain, ModuleShared and ModuleApplication accessors with nothing to judge. The doc comment names those rules (sealed entities, no public setters, no public aggregate constructors, immutable DTOs, commands, queries and domain events) and the aggregates they then run over: the Notifications family, PushNotification and UserNotification (:5-:15). Without this map those facts would pass vacuously in the framework repo, and the framework would ship DDD rules it does not itself obey. [Rubric §4 - DDD] is the property; [Rubric §34 - Architecture Governance & Documentation] covers the framework holding itself to the bar it exports.
    • +
    • Walkthrough - public const string ModuleName = "Framework" (:19) is the module label. RepoToken => "MMCA.Common" (:21) matches the primary map. DefineLayers() (:23-:29) returns three Module(ModuleName, Layer.X, ...) entries for Shared, Domain and Application (:25-:27) and one Framework(Layer.Infrastructure, ...) entry (:28). Infrastructure stays a framework layer on purpose: the rules only read it to prove entities and DTOs do not live there (:13-:14). Api, Grpc and Ui are not registered at all, unlike CommonArchitectureMap.
    • +
    • Why it's built this way - adding module entries to CommonArchitectureMap would flip every rule that branches on map.ModuleNames.Count into judging the framework as a consumer; a separate map scoped to the two DDD bindings that need it keeps that switch untouched.
    • +
    • Where it's used - supplied as Map by exactly two classes in the same folder: EntityConventionTests (EntityConventionTests.cs:14) and ImmutabilityTests (ImmutabilityTests.cs:13).

    FrameworkSanityTests

    @@ -4715,6 +5057,18 @@

    ModuleIsolationTestsBaseTests

  • Why it's built this way - proving a gap requires asserting six negatives, which is only legible because they are listed as an array of Action and looped (:40-:53). Writing them as six separate facts would have buried the point. See ADR-015.
  • Where it's used - an independent class in the Common architecture suite. The rules it covers are bound for real by ModuleIsolationTestsBase in the two module-bearing consumer repos; MMCA.Common has no modules, so it runs none of them outside this file.
  • +

    FrameworkModels

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Domain · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/DeleteBehaviorConventionTests.cs:55 · Level 13 · class

    +
    +
      +
    • What it is - a private static helper nested inside DeleteBehaviorConventionTests that builds a real, compiled EF Core model for each of the framework's two production engines, so the delete-behavior convention rule can inspect the SQLite model and the SQL Server model rather than reason about attributes in the abstract.
    • +
    • Depends on - SqliteDbContext and SQLServerDbContext (:63,:78), PhysicalDataSource and DataSourceKey.Default (:67,:82), AuditSaveChangesInterceptor and DomainEventSaveChangesInterceptor (:91-:95), IEntityDataSourceRegistry (:96), and Microsoft.EntityFrameworkCore.DbContextOptionsBuilder from the BCL.
    • +
    • Concept introduced - standing up the minimum viable ApplicationDbContext for a model-only assertion. Reading OnModelCreating output requires a constructed context, but constructing one drags in every service OnConfiguring demands. Services() (:88-:98) registers only what that base method actually resolves: an AuditSaveChangesInterceptor and a DomainEventSaveChangesInterceptor, both handed an explicit TimeProvider.System, the latter wired to a NoDomainEventDispatcher and a NoOutboxSignal, plus a NoEntityDataSources registry, each a do-nothing fake bound through the real interfaces. Nothing here touches a database; Sqlite() opens DataSource=:memory: and SqlServer() points at an unreachable connection string, because building the model does not execute a connection. [Rubric §34 - Architecture Governance & Documentation] is the property: a convention test that asserts against context.Model is asserting against the same object EF Core migrations generate from, which is a stronger guarantee than pattern-matching source text.
    • +
    • Walkthrough - two public factory methods and one private helper. Sqlite() (:57-:70) and SqlServer() (:72-:85) each build a DbContextOptionsBuilder<T>, construct the matching sealed context (SqliteDbContext or SQLServerDbContext) with a PhysicalDataSource keyed by DataSourceKey.Default(DataSource.Sqlite|SQLServer), and return context.Model after disposing the context. Services() (:88-:98) builds the shared ServiceProvider both factories pass in, passing timeProvider: TimeProvider.System explicitly to DomainEventSaveChangesInterceptor (:91-:95) rather than relying on its default.
    • +
    • Why it's built this way - engine parity is the point: the same convention rule runs against both models from the same fixture setup, so a delete-behavior default that only SQLite or only SQL Server gets right would show up as a two-model mismatch rather than pass silently on whichever engine the CI runner happened to exercise.
    • +
    • Where it's used - called from DeleteBehaviorConventionTests's [Theory] cases to source the model each case asserts against; not used outside that file.
    • +

    CascadeSoftDeleteConventionTests, ConcurrencyConventionTests, ContractImplementationTests

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Domain · (see per-type table) · Level 13 · class

    @@ -4745,86 +5099,40 @@

    ConcurrencyConventionTestsBase No *UpdateRequest carries a concurrency token in its body: the token belongs in the If-Match header, not in the payload, so a client cannot forge or omit it silently. Supplies only Map (:14). MMCA.Common is module-less, so there is no update request to judge; the rule fires if the framework itself grows one that reintroduces a body token (:5-:10). [Rubric §9 - API & Contract Design.] - - ContractImplementationTests - MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/ContractImplementationTests.cs:11 - ContractImplementationTestsBase - The class serving a published [ServiceContract] interface must not itself be public, so consumers bind to the contract rather than to the implementation. Supplies only Map (:13). MMCA.Common marks no interface with the attribute today, so the rule is attribute-driven and currently selects nothing (:5-:9). Its purity sibling ServiceContractPurityTests is in the same position for the same reason. [Rubric §7 - Microservices Readiness, Rubric §1 - SOLID.] - - -
      -
    • Why they're built this way - each is a two-line subclass with a doc comment that is longer than its body, which is the correct ratio for a ratchet: the code says nothing surprising and the comment carries the whole decision. Keeping them in this repo also means the framework is held to the same bar it exports, so a new framework type that would break a consumer's gate fails here first rather than in the consumers after a release.
    • -
    • Where they're used - all three run in the MMCA.Common.Architecture.Tests project during CI's build-and-test job. The same three bases are subclassed non-vacuously by MMCA.Store and MMCA.ADC over their own modules.
    • -
    • Caveats / not-in-source - a green run of any of these three says nothing about the framework's code today. Only CascadeSoftDeleteConventionTests has a companion that proves its rule fires at all (CascadeSoftDeleteFitnessTests); the other two rules have no fixture-backed proof in this repo.
    • -
    -

    DomainThrowFitnessTests

    -
    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/DomainThrowFitnessTests.cs:17 · Level 13 · class

    -
    -
      -
    • What it is - the meta-test for the domain-throw rule: nine facts pinning both offender kinds, the three permitted argument guards, the bare rethrow, the non-throwing control, the UNVERIFIABLE verdict, both allowlist granularities, and a final run of the rule against MMCA.Common's own Domain.
    • -
    • Depends on - ArchitectureRules (DomainThrowsOnlyArgumentGuards, DomainThrowFitnessTests.cs:40), its nested FixtureAssemblyMap, CommonArchitectureMap (:131), the six types in MMCA.Common.Architecture.Tests.DomainThrowFixtures (:1), and Xunit.Sdk.XunitException (:3).
    • -
    • Concept introduced - the allowlist doing its day job, in the test's own setup. Every other self-test in this assembly points its map at a namespace whose only inhabitants are its fixtures. This one cannot: the rule reads the whole Domain-layer assembly, and this assembly contains two throws that are not fixtures. NavigationContractTests guards its embedded resource with an InvalidOperationException, and the OpenAPI XML-comment source generator emits a transformer into every assembly that references the API layer. Both are allowlisted in a named static array with a doc comment explaining each (:21-:33), and the comment makes the wider point: generated plumbing is exactly what an allowlist is for. The first entry is a fully qualified type name, MMCA.Common.Architecture.Tests.Ui.NavigationContractTests (:31), so the allowlist tracks the test's folder: moving that class under Ui/ moved the string with it. Reading that array teaches more about how the escape hatch is meant to be used than any of the escape-hatch facts do. The final fact is a different kind of assertion again: MMCACommonDomain_HoldsOnlyArgumentGuards (:128-:135) runs the rule against the real CommonArchitectureMap with an empty allowlist and asserts NotThrow, with the because recording the fact that makes it interesting, that MMCA.Common's own Domain is the reference implementation of ADR-013 and its single throw is the ArgumentNullException guard in Specification.cs. That is the framework holding itself to the rule it exports, with no exemptions at all. [Rubric §4 - DDD] and [Rubric §15 - Best Practices & Code Quality] are the properties; [Rubric §34 - Architecture Governance & Documentation] covers the allowlist-with-reasons discipline; [Rubric §14 - Testability] is the technique.
    • -
    • Walkthrough - a private const string FixtureNamespace (:19), the NonFixtureThrows array (:29-:33) and a readonly FixtureAssemblyMap (:35) are shared; every fixture fact passes NonFixtureThrows so the fixtures are the only subject.
        -
      • BusinessException_IsFlagged (:37-:46): asserts both the owning type and the exception's full name System.InvalidOperationException appear (:44-:45). Naming the exception is what tells a developer which throw to convert.
      • -
      • CustomDomainException_IsAlsoFlagged (:48-:57): the custom-exception case.
      • -
      • ArgumentGuards_AreNotFlagged (:59-:68): the permitted-guard case, with a because listing all three exception types and stating why they are exempt, a caller bug rather than a business outcome.
      • -
      • BareRethrow_IsNotFlagged (:70-:79) and NonThrowingCode_IsNotFlagged (:81-:90): the two silent cases.
      • -
      • ThrowOfAValueBuiltElsewhere_IsReportedAsUnverifiable (:92-:101): asserts the literal "UNVERIFIABLE" and the owning method's type name, the same third-verdict discipline ErrorCatalogFitnessTests pins for the catalog rules.
      • -
      • AllowlistedNamespace_SilencesTheRule (:103-:112) and AllowlistedType_SilencesOnlyThatType (:114-:126): the two granularities, each built by spreading NonFixtureThrows and appending one entry, with the type-level fact carrying the control assertion that the other offender is still reported (:124-:125).
      • -
      • MMCACommonDomain_HoldsOnlyArgumentGuards (:128-:135): the self-application described above.
      • -
      -
    • -
    • Why it's built this way - nine facts for one rule is the highest count in the suite, and the reason is that this rule has the widest blast radius: it judges every throw in every Domain assembly of every repo that adopts it, so both its false positives and its blind spots are expensive. Asserting the exception's full name rather than just the type's is what makes the report usable. See ADR-013 and ADR-015.
    • -
    • Where it's used - an independent class in the Common architecture suite; the shipped consumer-facing binding of the rule is DomainThrowTestsBase.
    • -
    • Caveats / not-in-source - the Microsoft.AspNetCore.OpenApi.Generated allowlist entry (:32) silences a whole generated namespace. If the generator ever emitted a type a developer also wrote into that namespace, its throws would be silenced too; nothing in the class detects that. The sibling entry is a string, not a typeof, so renaming or relocating NavigationContractTests without editing :31 would quietly re-arm the rule against it.
    • -
    -

    EventScopeFitnessTests

    -
    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Contracts · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/EventScopeFitnessTests.cs:13 · Level 13 · class

    -
    -
      -
    • What it is - the ownership-scoping guard for the integration-event rules: three facts pinning that a consumer-shaped map neither snapshots nor polices the framework's own events, while the framework's module-less map still covers them at the source.
    • -
    • Depends on - ArchitectureRules (BuildIntegrationEventContract and IntegrationEventsResideInSharedIntegrationEventsNamespace, EventScopeFitnessTests.cs:18,:30), CommonArchitectureMap (:39), its own FakeConsumerMap, and BaseIntegrationEvent as the Domain-assembly anchor (:1,:56).
    • -
    • Concept introduced - a rule's scope is part of its contract, and ownership decides it. Both event rules ask the same question of a map, and both answer differently depending on whether the map declares modules. IntegrationEvents includes framework layers only when map.ModuleNames.Count == 0 (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Events.cs:68-:73), and the residency rule enforces the Shared-layer requirement only when the map is module-bearing (:31,:33). The reasoning is ownership: a framework-shipped event is the framework's contract, gated by the framework's own conventions and public-API baseline, so a consumer's frozen snapshot must neither churn on it nor claim jurisdiction over where it lives. This class is a regression guard, and the class doc names the incident that motivated it (:6-:12): when the framework shipped its first concrete integration event, OutputCacheEvictionRequested, the Helpdesk canary broke. [Rubric §6 - CQRS & Event-Driven] assesses whether event contracts are governed; [Rubric §9 - API & Contract Design] covers the frozen-snapshot mechanism; [Rubric §33 - Developer Experience] is the failure mode being prevented, a framework release that reds every consumer's architecture suite for a change they did not make.
    • -
    • Walkthrough
        -
      • ModuleBearingMap_ExcludesFrameworkEvents_FromContractSnapshot (:15-:23): builds the contract from FakeConsumerMap and asserts no line mentions OutputCacheEvictionRequested.
      • -
      • ModuleBearingMap_PassesResidencyRule_DespiteFrameworkEvents (:26-:34): asserts the residency rule does not throw for the consumer map, even though the framework's Domain assembly hosts that event outside any Shared assembly (inline comment, :28-:29).
      • -
      • ModuleLessMap_StillCoversFrameworkEvents (:36-:44): the other half of the proof, using the real CommonArchitectureMap and asserting the same event is in the contract. Without this third fact the scoping change would be indistinguishable from simply having disabled the rule.
      • -
      -
    • -
    • Why it's built this way - a scoping fix is exactly the kind of change that silently over-corrects. Pinning both the exclusion and the retention, against a real framework event rather than a fixture one, is what keeps the fix from becoming a hole.
    • -
    • Where it's used - an independent class in the Common architecture suite; the rules it scopes are bound for real by EventVersioningConventionTests here and by the per-repo IntegrationEventContractTestsBase subclasses in the consumers.
    • + + ContractImplementationTests + MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/ContractImplementationTests.cs:11 + ContractImplementationTestsBase + The class serving a published [ServiceContract] interface must not itself be public, so consumers bind to the contract rather than to the implementation. Supplies only Map (:13). MMCA.Common marks no interface with the attribute today, so the rule is attribute-driven and currently selects nothing (:5-:9). Its purity sibling ServiceContractPurityTests is in the same position for the same reason. [Rubric §7 - Microservices Readiness, Rubric §1 - SOLID.] + + +
        +
      • Why they're built this way - each is a two-line subclass with a doc comment that is longer than its body, which is the correct ratio for a ratchet: the code says nothing surprising and the comment carries the whole decision. Keeping them in this repo also means the framework is held to the same bar it exports, so a new framework type that would break a consumer's gate fails here first rather than in the consumers after a release.
      • +
      • Where they're used - all three run in the MMCA.Common.Architecture.Tests project during CI's build-and-test job. The same three bases are subclassed non-vacuously by MMCA.Store and MMCA.ADC over their own modules.
      • +
      • Caveats / not-in-source - a green run of any of these three says nothing about the framework's code today. Only CascadeSoftDeleteConventionTests has a companion that proves its rule fires at all (CascadeSoftDeleteFitnessTests); the other two rules have no fixture-backed proof in this repo.
      -

      EventUpcasterFitnessTests

      +

      DomainThrowFitnessTests

      -

      MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Contracts · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/EventUpcasterFitnessTests.cs:12 · Level 13 · class

      +

      MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Domain · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/DomainThrowFitnessTests.cs:17 · Level 13 · class

        -
      • What it is - the meta-test for the two event-upcaster fitness rules: four facts proving the unique-source rule reports a contract claimed twice, that it leaves a legitimate chain alone, that the version rule reports an upcaster pointing at a lower SchemaVersion, and that both rules pass on a map owning no upcasters at all.
      • -
      • Depends on - ArchitectureRules (EventUpcastersHaveUniqueSourceTypes and EventUpcastersIncreaseSchemaVersion, EventUpcasterFitnessTests.cs:53,:55,:61,:68), the fixture contracts and fixture upcasters (:1), its nested UpcasterTestMap, CommonArchitectureMap (:51), plus xUnit and AwesomeAssertions.
      • -
      • Concept introduced - proving a rule is not vacuous when the framework itself has nothing to judge. MMCA.Common ships no upcaster of its own, so running either rule over the real CommonArchitectureMap proves only that it does not crash. That is a real property worth pinning (a rule that threw on an empty set would red every consumer that has not adopted upcasting yet), and the fourth fact pins exactly it. But the other three facts have to manufacture their subjects, which is what the UpcasterFixtures namespace and the private map are for. The pattern to take away: a fitness function for a consumer-facing convention is proven in the framework repo against fixtures, and only smoke-tested against the framework's own code. ProtoContractFitnessTests, CommandValidatorCoverageFitnessTests and ServiceContractPurityTests sit in the same position for their rules. [Rubric §6 - CQRS & Event-Driven] and [Rubric §9 - API & Contract Design] are the properties defended; [Rubric §14 - Testability] is the technique.
      • -
      • Walkthrough - two private helpers each run one rule against a fresh UpcasterTestMap and return the thrown message: RunUniqueSourceRule (:59-:64) and RunSchemaVersionRule (:66-:71). Because the fixtures always contain an offender for each rule, both helpers can assert Should().Throw<Exception>().Which.Message unconditionally and let the facts make positive and negative Contain assertions against the one string.
          -
        • UniqueSourceRule_FlagsTheContestedContract_ButNotTheCompliantLadder (:14-:25): asserts the message names the contested contract and both rival upcasters (:19-:21), which is what makes the failure actionable, and that the compliant first rung is absent (:22-:24).
        • -
        • UniqueSourceRule_DoesNotFlag_AnUpcasterWhoseSourceIsAnotherUpcastersTarget (:31-:33): the sharp one, given its own fact and its own doc comment (:27-:30). The compliant ladder's middle contract is both a target and a source; that is a chain, not a duplicate claim, and the rule must leave it alone.
        • -
        • SchemaVersionRule_FlagsTheBackwardsUpcaster_ButNotTheCompliantLadder (:35-:46): names the offender (:40) and, notably, pins the message text "must declare a HIGHER SchemaVersion" (:41-:43), because the wording is what tells a developer which direction is allowed. Both clean rungs are asserted absent (:44-:45).
        • -
        • BothRules_Pass_OnAMapThatOwnsNoUpcasters (:48-:57): runs both rules against the real framework map and asserts NotThrow, with the because stating that the framework ships no upcaster so the rule passes vacuously (:54).
        • +
        • What it is - the meta-test for the domain-throw rule: ten facts pinning both offender kinds, the three permitted argument guards, the compiler-written switch-expression throw, the bare rethrow, the non-throwing control, the UNVERIFIABLE verdict, both allowlist granularities, and a final run of the rule against MMCA.Common's own Domain.
        • +
        • Depends on - ArchitectureRules (DomainThrowsOnlyArgumentGuards, DomainThrowFitnessTests.cs:40), its nested FixtureAssemblyMap (:149-:157), CommonArchitectureMap (:142), the fixture types in MMCA.Common.Architecture.Tests.DomainThrowFixtures (:1), including SwitchExpressionFixture (DomainThrowFixtures.cs:115), and Xunit.Sdk.XunitException (:3).
        • +
        • Concept introduced - the allowlist doing its day job, in the test's own setup. Every other self-test in this assembly points its map at a namespace whose only inhabitants are its fixtures. This one cannot: the rule reads the whole Domain-layer assembly, and this assembly contains two throws that are not fixtures. NavigationContractTests guards its embedded resource with an InvalidOperationException, and the OpenAPI XML-comment source generator emits a transformer into every assembly that references the API layer. Both are allowlisted in a named static array with a doc comment explaining each (:21-:33), and the comment makes the wider point: generated plumbing is exactly what an allowlist is for. The first entry is a fully qualified type name, MMCA.Common.Architecture.Tests.Ui.NavigationContractTests (:31), so the allowlist tracks the test's folder: moving that class under Ui/ moved the string with it. Reading that array teaches more about how the escape hatch is meant to be used than any of the escape-hatch facts do. The final fact is a different kind of assertion again: MMCACommonDomain_HoldsOnlyArgumentGuards (:139-:146) runs the rule against the real CommonArchitectureMap with an empty allowlist and asserts NotThrow, with the because recording the fact that makes it interesting, that MMCA.Common's own Domain is the reference implementation of ADR-013 and its single throw is the ArgumentNullException guard in Specification.cs. That is the framework holding itself to the rule it exports, with no exemptions at all. [Rubric §4 - DDD] and [Rubric §15 - Best Practices & Code Quality] are the properties; [Rubric §34 - Architecture Governance & Documentation] covers the allowlist-with-reasons discipline; [Rubric §14 - Testability] is the technique.
        • +
        • Walkthrough - a private const string FixtureNamespace (:19), the NonFixtureThrows array (:29-:33) and a readonly FixtureAssemblyMap (:35) are shared; every fixture fact passes NonFixtureThrows so the fixtures are the only subject.
            +
          • BusinessException_IsFlagged (:37-:46): asserts both the owning type and the exception's full name System.InvalidOperationException appear (:44-:45). Naming the exception is what tells a developer which throw to convert.
          • +
          • CustomDomainException_IsAlsoFlagged (:48-:57): the custom-exception case.
          • +
          • ArgumentGuards_AreNotFlagged (:59-:68): the permitted-guard case, with a because listing all three exception types and stating why they are exempt, a caller bug rather than a business outcome.
          • +
          • CompilerSwitchExpressionThrow_IsNotFlagged (:70-:79): asserts the report never names SwitchExpressionException. Its subject is SwitchExpressionFixture, a switch expression with no discard arm (DomainThrowFixtures.cs:111-:118), whose default-arm throw is written by the compiler rather than the developer, so the rule must not charge it to the domain method.
          • +
          • BareRethrow_IsNotFlagged (:81-:90) and NonThrowingCode_IsNotFlagged (:92-:101): the two silent cases.
          • +
          • ThrowOfAValueBuiltElsewhere_IsReportedAsUnverifiable (:103-:112): asserts the literal "UNVERIFIABLE" and the owning method's type name, the same third-verdict discipline ErrorCatalogFitnessTests pins for the catalog rules.
          • +
          • AllowlistedNamespace_SilencesTheRule (:114-:123) and AllowlistedType_SilencesOnlyThatType (:125-:137): the two granularities, each built by spreading NonFixtureThrows and appending one entry, with the type-level fact carrying the control assertion that the other offender is still reported (:135-:136).
          • +
          • MMCACommonDomain_HoldsOnlyArgumentGuards (:139-:146): the self-application described above.
        • -
        • Why it's built this way - the two rules exist because an upcast chain that is not a function, or that runs backwards, produces a bug that only appears when an old outbox row is replayed, potentially long after the change that caused it (ADR-090, building on ADR-010). Catching it at build time is worth a fixture namespace.
        • -
        • Where it's used - an independent class in the Common architecture suite. The shipped binding of both rules is EventConventionTestsBase, whose two facts call them for every repo (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Contracts/EventConventionTestsBase.cs:23,:26), and which MMCA.Common activates through EventVersioningConventionTests.
        • -
        -

        EventVersioningConventionTests

        -
        -

        MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Contracts · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/EventVersioningConventionTests.cs:12 · Level 13 · class

        -
        -
          -
        • What it is - the two-line binding of the integration-event convention rules (SchemaVersion, BaseIntegrationEvent inheritance, namespace residency; ADR-010) to MMCA.Common's own map.
        • -
        • Depends on - EventConventionTestsBase (public sealed class EventVersioningConventionTests : EventConventionTestsBase, EventVersioningConventionTests.cs:12) and CommonArchitectureMap (:14).
        • -
        • Concept introduced - none new; the same shipped-binding shape FeatureFlagLifecycleTests below and every other *TestsBase subclass in this suite follow. The class doc names the one concrete event this binding proves against today: OutputCacheEvictionRequested in MMCA.Common.Domain.IntegrationEvents (:8-:10); EventUpcasterFitnessTests above proves the upcaster half of the same rule family against fixtures because the framework ships no upcaster of its own. [Rubric §6 - CQRS & Event-Driven] and [Rubric §9 - API & Contract Design] are the properties; [Rubric §34 - Architecture Governance & Documentation] is the shipped-binding discipline (ADR-010).
        • -
        • Walkthrough - one member, Map (:14), overridden to new CommonArchitectureMap(); the base class's facts run against MMCA.Common.Domain.IntegrationEvents.
        • -
        • Why it's built this way - the class doc states its own failure mode directly, that the rules fail the build the moment a new integration event ships that breaks the convention, rather than only surfacing when an old outbox row is replayed (:9-:10).
        • -
        • Where it's used - an independent class in the Common architecture suite; the same base is subclassed by MMCA.ADC and MMCA.Store with their own maps.
        • +
        • Why it's built this way - ten facts for one rule, and the reason is that this rule has the widest blast radius: it judges every throw in every Domain assembly of every repo that adopts it, so both its false positives and its blind spots are expensive. Asserting the exception's full name rather than just the type's is what makes the report usable. See ADR-013 and ADR-015.
        • +
        • Where it's used - an independent class in the Common architecture suite; the shipped consumer-facing binding of the rule is DomainThrowTestsBase.
        • +
        • Caveats / not-in-source - the Microsoft.AspNetCore.OpenApi.Generated allowlist entry (:32) silences a whole generated namespace. If the generator ever emitted a type a developer also wrote into that namespace, its throws would be silenced too; nothing in the class detects that. The sibling entry is a string, not a typeof, so renaming or relocating NavigationContractTests without editing :31 would quietly re-arm the rule against it.

        AggregateConventionTests

        @@ -4879,19 +5187,20 @@

        ClockReadTests

        MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Domain · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/ClockReadTests.cs:14 · Level 13 · class

          -
        • What it is - the meta-test for the ambient-clock rule: five facts pinning that a direct read of either clock type is flagged, that a read inside a lambda or an async state machine is still attributed to the member that wrote it, that an injected TimeProvider is left alone, and both allowlist granularities.
        • -
        • Depends on - ArchitectureRules (DomainAndApplicationDoNotReadTheClock, ClockReadTests.cs:79), its nested FixtureAssemblyMap, and the six fixtures in the sibling MMCA.Common.Architecture.Tests.ClockReadFixtures namespace: UtcNowReadingFixture, OffsetNowReadingFixture, LambdaClockReadingFixture, AsyncClockReadingFixture, InjectedClockFixture and TwoMemberClockFixture (:27).
        • -
        • Concept introduced - a compiler-generated body still belongs to the member that wrote it. A lambda closure and an async state machine each compile into their own nested type, so a naive IL scan would attribute a clock read inside either to that generated type rather than to the source. ReadsInsideLambdaAndAsyncBodies_AreFlaggedUnderTheirSourceMember (:42-:53) is the fact that pins the rule does not make that mistake: LambdaClockReadingFixture.Clock and AsyncClockReadingFixture.StampAsync both appear in the report under their declaring member's name, not a compiler-generated one. [Rubric §14 - Testability] is the technique, a fitness function proven against fixtures before it is trusted against real code; [Rubric §4 - DDD] is the property, since the rule is what lets a domain method take time as an input instead of reading it.
        • -
        • Walkthrough - a private const string FixtureNamespace (:27) and a nested FixtureAssemblyMap (:89-:97) point the rule at this test assembly's own Domain layer, anchored on InjectedClockFixture (:95); a private Report helper (:75-:86) runs the rule and returns the caught XunitException.Message, or an empty string when the rule passes.
            -
          • DirectReadsOfEitherClockType_AreFlagged (:34-:40): asserts the report names both UtcNowReadingFixture.Stamp (DateTime.UtcNow) and OffsetNowReadingFixture.Stamp (DateTimeOffset.Now).
          • -
          • ReadsInsideLambdaAndAsyncBodies_AreFlaggedUnderTheirSourceMember (:42-:53): the attribution case above.
          • -
          • InjectedTimeProvider_IsNotFlagged (:55-:59): the control, asserting InjectedClockFixture never appears.
          • -
          • MemberAllowlistEntry_ExemptsOnlyThatMember (:61-:68): allowlists TwoMemberClockFixture.Exempted by its fully qualified member name and asserts the sibling StillReported member on the same type is still flagged, proving the allowlist grain works at member level, not type level.
          • -
          • NamespaceAllowlistEntry_ExemptsEveryTypeUnderIt (:70-:72): allowlists the whole fixture namespace and asserts nothing from it appears.
          • +
          • What it is - both the framework's own binding of the ambient-clock rule and its meta-test: it subclasses ClockReadTestsBase over CommonArchitectureMap, so the inherited fact runs the rule against MMCA.Common's own Domain and Application, and it adds six fixture facts pinning that a direct read of either clock type is flagged, that DateTime.Today is flagged, that a read inside a lambda or an async state machine is still attributed to the member that wrote it, that an injected TimeProvider is left alone, and both allowlist granularities (class doc, ClockReadTests.cs:7-:13).
          • +
          • Depends on - ClockReadTestsBase (public sealed class ClockReadTests : ClockReadTestsBase, :14), CommonArchitectureMap (:20), ArchitectureRules (DomainAndApplicationDoNotReadTheClock, :74), its nested FixtureAssemblyMap (:84-:92), and the seven fixtures in the sibling MMCA.Common.Architecture.Tests.ClockReadFixtures namespace (:16): UtcNowReadingFixture, OffsetNowReadingFixture, LambdaClockReadingFixture, AsyncClockReadingFixture, InjectedClockFixture, TwoMemberClockFixture and TodayReadingFixture (ClockReadFixtures.cs:48).
          • +
          • Concept introduced - a compiler-generated body still belongs to the member that wrote it. A lambda closure and an async state machine each compile into their own nested type, so a naive IL scan would attribute a clock read inside either to that generated type rather than to the source. ReadsInsideLambdaAndAsyncBodies_AreFlaggedUnderTheirSourceMember (:37-:48) is the fact that pins the rule does not make that mistake: LambdaClockReadingFixture.Clock and AsyncClockReadingFixture.StampAsync both appear in the report under their declaring member's name, not a compiler-generated one. [Rubric §14 - Testability] is the technique, a fitness function proven against fixtures before it is trusted against real code; [Rubric §4 - DDD] is the property, since the rule is what lets a domain method take time as an input instead of reading it.
          • +
          • Walkthrough - Map is overridden with new CommonArchitectureMap() (:20), which is all the inherited DomainAndApplication_ShouldNotReadTheAmbientClock fact needs (ClockReadTestsBase.cs:26-:28). For the self-tests, a private const string FixtureNamespace (:16) and a readonly nested FixtureAssemblyMap instance (:18) point the rule at this test assembly's own Domain layer, anchored on InjectedClockFixture (:90); a private Report helper (:70-:81) runs the rule against that map and returns the caught XunitException.Message, or an empty string when the rule passes. Because the fixture map registers the whole test assembly, every self-test asserts on what the report names rather than on the report being empty (:10-:12).
              +
            • DirectReadsOfEitherClockType_AreFlagged (:22-:29): asserts the report names both UtcNowReadingFixture.Stamp (DateTime.UtcNow) and OffsetNowReadingFixture.Stamp (DateTimeOffset.Now).
            • +
            • DateTimeToday_IsFlagged (:31-:35): asserts TodayReadingFixture.Stamp reads DateTime.Today, with the because that DateTime.Today reads the ambient clock exactly as DateTime.Now does.
            • +
            • ReadsInsideLambdaAndAsyncBodies_AreFlaggedUnderTheirSourceMember (:37-:48): the attribution case above.
            • +
            • InjectedTimeProvider_IsNotFlagged (:50-:54): the control, asserting InjectedClockFixture never appears.
            • +
            • MemberAllowlistEntry_ExemptsOnlyThatMember (:56-:63): allowlists TwoMemberClockFixture.Exempted by its fully qualified member name and asserts the sibling StillReported member on the same type is still flagged, proving the allowlist grain works at member level, not type level.
            • +
            • NamespaceAllowlistEntry_ExemptsEveryTypeUnderIt (:65-:67): allowlists the whole fixture namespace and asserts nothing from it appears.
          • -
          • Why it's built this way - the rule inspects IL rather than source, so its two riskiest cases, a closure and a state machine, need their own fixtures and their own fact; without ReadsInsideLambdaAndAsyncBodies_AreFlaggedUnderTheirSourceMember a regression that misattributed either would pass every other fact silently. The two allowlist facts follow the same member-vs-namespace split DomainThrowFitnessTests uses for its own allowlist.
          • -
          • Where it's used - an independent class in the Common architecture suite; the shipped binding of the rule for a real repo is ClockReadTestsBase.
          • +
          • Why it's built this way - the rule inspects IL rather than source, so its riskiest cases, a closure and a state machine, need their own fixtures and their own fact; without ReadsInsideLambdaAndAsyncBodies_AreFlaggedUnderTheirSourceMember a regression that misattributed either would pass every other fact silently. DateTime.Today gets its own fixture for the same reason: it is a third property name on the clock type, and a rule that matched only Now and UtcNow would miss it. The two allowlist facts follow the same member-vs-namespace split DomainThrowFitnessTests uses for its own allowlist. Folding the binding and the meta-test into one class means the framework runs the exact rule it ships against its own code in the same file that proves the rule works.
          • +
          • Where it's used - discovered and run by xUnit in the Common architecture suite. ADC's sibling binding of ClockReadTestsBase is MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Domain/ClockReadTests.cs. See ADR-128.

          ConcurrencyConventionTests

          @@ -4929,6 +5238,77 @@

          DomainPurityTests

        • Why it's built this way - the same one-class-per-repo pattern every other *TestsBase subclass in this suite uses (ADR-015).
        • Where it's used - an independent class in the Common architecture suite; Store and ADC subclass the same base with their own maps.
        +

        EntityConventionTests

        +
        +

        MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Domain.EntityModel · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/EntityModel/EntityConventionTests.cs:12 · Level 13 · class

        +
        +
          +
        • What it is - the framework repo's binding of the DDD entity-shape rules: MMCA.Common's own Notifications aggregates are sealed, built through a Result-returning Create factory with no public constructor, and expose no public setter (class doc, EntityConventionTests.cs:5-:11).
        • +
        • Depends on - EntityConventionTestsBase (:12) and FrameworkModuleArchitectureMap (:14), not CommonArchitectureMap.
        • +
        • Concept - the map choice is the whole point. The base's facts read the module-scoped accessors, so over CommonArchitectureMap they would have nothing to judge and pass vacuously; FrameworkModuleArchitectureMap registers the framework assemblies as a module so the rules land on PushNotification and UserNotification (:9-:10). [Rubric §4 - DDD] assesses whether the tactical patterns are actually applied; this is what makes the framework obey the factory-plus-Result construction contract it asks consumers to follow (ADR-129).
        • +
        • Walkthrough - one member, Map (:14). Eight inherited facts (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Domain/EntityConventionTestsBase.cs:15, :18, :21, :24, :27, :30, :33, :36) cover aggregate-root exposure, the Result-returning Create factory, no public aggregate constructors, factory return types, sealing, Domain-layer placement, non-public property setters, and the DTO/request exclusion from Domain and Infrastructure.
        • +
        • Why it's built this way - the same one-class-per-repo pattern every other *TestsBase subclass in this suite uses (ADR-015), with the map swapped so the rules are live rather than ratchets.
        • +
        • Where it's used - discovered and run by xUnit in the Common architecture suite; its sibling ImmutabilityTests uses the same map. ADC subclasses the same base with its own map (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Domain/EntityConventionTests.cs).
        • +
        +

        DeleteBehaviorConventionTests

        +
        +

        MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Domain · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/DeleteBehaviorConventionTests.cs:28 · Level 14 · class

        +
        +
          +
        • What it is - the framework repo's binding of the delete-behavior fitness functions (ADR-119): a sealed subclass that builds the framework's own two finalized EF Core models (SQLite and SQL Server) with no database connection opened, and asserts each against the RestrictDeleteByDefaultConvention stamp the model already carries.
        • +
        • Depends on - DeleteBehaviorConventionTestsBase (public sealed class DeleteBehaviorConventionTests : DeleteBehaviorConventionTestsBase, DeleteBehaviorConventionTests.cs:28), ArchitectureRules (CascadingForeignKeysAreExplicitlyOptedIn, ConventionForeignKeysRestrictDeletes, DeleteBehaviorConventionTests.cs:57,:66), SqliteDbContext and SQLServerDbContext (:79,:94-:98), and a set of private no-op fakes (NoModuleAssemblies, NoEntityDataSources, NoDomainEventDispatcher, NoOutboxSignal, :122-:163) that satisfy the DI graph ApplicationDbContext.OnConfiguring demands without any real registrations.
        • +
        • Concept - the base class's own Model property only exercises one engine, so this subclass adds a second [Fact] (SqlServerModel_CascadingDeletes_AreExplicitlyOptedIn / SqlServerModel_ConventionDeletes_AreRestricted, :53-:69) that builds a second model against SQLServerDbContext and asserts both rules against it too, covering both relational engines the framework maps entities onto today. The nested FrameworkModels helper documents the trick that makes this possible without infrastructure: only the model is asserted, so neither context ever opens its connection, and EF builds the model from the configurations alone (:71-:74). NoEntityDataSources answers TryGetDataSourceKey with false for everything so the cross-source degrade convention becomes a no-op, and its two other lookups return a default key rather than throwing so a throwing fixture does not trip the repo's own domain-throw rule (:127-:131).
        • +
        • Walkthrough - Model (inherited) builds the SQLite model via DbContextOptionsBuilder<SqliteDbContext>().UseSqlite("DataSource=:memory:") and FrameworkModels.Sqlite() (:77-:90); the two added facts build the SQL Server model the same way via FrameworkModels.SqlServer() (:92-:105). Both paths construct their context with the same four dependencies: Services() (an AuditSaveChangesInterceptor, a DomainEventSaveChangesInterceptor, and a no-op IEntityDataSourceRegistry, :108-:118), a NoModuleAssemblies provider that returns an empty assembly list so the model holds only the framework's own tables (:121-:125), and a PhysicalDataSource describing the (unused) connection. Both inherited facts then read context.Model and assert the convention's stamps against it.
        • +
        • Why it's built this way - PostgreSQL takes the SQL Server mapping unchanged for delete-behavior purposes, so asserting SQLite plus SQL Server covers the framework's relational engines without a third context class; Cosmos is a deliberate no-op for this convention since it has no foreign key constraints to restrict (DeleteBehaviorConventionTestsBase.cs:16-:18). Building the model with in-memory/unopened connections keeps the fitness test fast and database-free, consistent with the rest of MMCA.Common.Architecture.Tests.
        • +
        • Where it's used - discovered and run by xUnit as part of MMCA.Common.Architecture.Tests; nothing constructs it. MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Domain/DeleteBehaviorConventionTests.cs is the sibling binding in the ADC repo, built the same way against ADC's own module assemblies.
        • +
        +

        EventScopeFitnessTests

        +
        +

        MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Contracts · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/EventScopeFitnessTests.cs:13 · Level 13 · class

        +
        +
          +
        • What it is - the ownership-scoping guard for the integration-event rules: three facts pinning that a consumer-shaped map neither snapshots nor polices the framework's own events, while the framework's module-less map still covers them at the source.
        • +
        • Depends on - ArchitectureRules (BuildIntegrationEventContract and IntegrationEventsResideInSharedIntegrationEventsNamespace, EventScopeFitnessTests.cs:18,:30), CommonArchitectureMap (:39), its own FakeConsumerMap, and BaseIntegrationEvent as the Domain-assembly anchor (:1,:56).
        • +
        • Concept introduced - a rule's scope is part of its contract, and ownership decides it. Both event rules ask the same question of a map, and both answer differently depending on whether the map declares modules. IntegrationEvents includes framework layers only when map.ModuleNames.Count == 0 (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Events.cs:139-:73), and the residency rule enforces the Shared-layer requirement only when the map is module-bearing (:31,:33). The reasoning is ownership: a framework-shipped event is the framework's contract, gated by the framework's own conventions and public-API baseline, so a consumer's frozen snapshot must neither churn on it nor claim jurisdiction over where it lives. This class is a regression guard, and the class doc names the incident that motivated it (:6-:12): when the framework shipped its first concrete integration event, OutputCacheEvictionRequested, the Helpdesk canary broke. [Rubric §6 - CQRS & Event-Driven] assesses whether event contracts are governed; [Rubric §9 - API & Contract Design] covers the frozen-snapshot mechanism; [Rubric §33 - Developer Experience] is the failure mode being prevented, a framework release that reds every consumer's architecture suite for a change they did not make.
        • +
        • Walkthrough
            +
          • ModuleBearingMap_ExcludesFrameworkEvents_FromContractSnapshot (:15-:23): builds the contract from FakeConsumerMap and asserts no line mentions OutputCacheEvictionRequested.
          • +
          • ModuleBearingMap_PassesResidencyRule_DespiteFrameworkEvents (:26-:34): asserts the residency rule does not throw for the consumer map, even though the framework's Domain assembly hosts that event outside any Shared assembly (inline comment, :28-:29).
          • +
          • ModuleLessMap_StillCoversFrameworkEvents (:36-:44): the other half of the proof, using the real CommonArchitectureMap and asserting the same event is in the contract. Without this third fact the scoping change would be indistinguishable from simply having disabled the rule.
          • +
          +
        • +
        • Why it's built this way - a scoping fix is exactly the kind of change that silently over-corrects. Pinning both the exclusion and the retention, against a real framework event rather than a fixture one, is what keeps the fix from becoming a hole.
        • +
        • Where it's used - an independent class in the Common architecture suite; the rules it scopes are bound for real by EventVersioningConventionTests here and by the per-repo IntegrationEventContractTestsBase subclasses in the consumers.
        • +
        +

        EventUpcasterFitnessTests

        +
        +

        MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Contracts · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/EventUpcasterFitnessTests.cs:12 · Level 13 · class

        +
        +
          +
        • What it is - the meta-test for the two event-upcaster fitness rules: four facts proving the unique-source rule reports a contract claimed twice, that it leaves a legitimate chain alone, that the version rule reports an upcaster pointing at a lower SchemaVersion, and that both rules pass on a map owning no upcasters at all.
        • +
        • Depends on - ArchitectureRules (EventUpcastersHaveUniqueSourceTypes and EventUpcastersIncreaseSchemaVersion, EventUpcasterFitnessTests.cs:53,:55,:61,:68), the fixture contracts and fixture upcasters (:1), its nested UpcasterTestMap, CommonArchitectureMap (:51), plus xUnit and AwesomeAssertions.
        • +
        • Concept introduced - proving a rule is not vacuous when the framework itself has nothing to judge. MMCA.Common ships no upcaster of its own, so running either rule over the real CommonArchitectureMap proves only that it does not crash. That is a real property worth pinning (a rule that threw on an empty set would red every consumer that has not adopted upcasting yet), and the fourth fact pins exactly it. But the other three facts have to manufacture their subjects, which is what the UpcasterFixtures namespace and the private map are for. The pattern to take away: a fitness function for a consumer-facing convention is proven in the framework repo against fixtures, and only smoke-tested against the framework's own code. ProtoContractFitnessTests, CommandValidatorCoverageFitnessTests and ServiceContractPurityTests sit in the same position for their rules. [Rubric §6 - CQRS & Event-Driven] and [Rubric §9 - API & Contract Design] are the properties defended; [Rubric §14 - Testability] is the technique.
        • +
        • Walkthrough - two private helpers each run one rule against a fresh UpcasterTestMap and return the thrown message: RunUniqueSourceRule (:59-:64) and RunSchemaVersionRule (:66-:71). Because the fixtures always contain an offender for each rule, both helpers can assert Should().Throw<Exception>().Which.Message unconditionally and let the facts make positive and negative Contain assertions against the one string.
            +
          • UniqueSourceRule_FlagsTheContestedContract_ButNotTheCompliantLadder (:14-:25): asserts the message names the contested contract and both rival upcasters (:19-:21), which is what makes the failure actionable, and that the compliant first rung is absent (:22-:24).
          • +
          • UniqueSourceRule_DoesNotFlag_AnUpcasterWhoseSourceIsAnotherUpcastersTarget (:31-:33): the sharp one, given its own fact and its own doc comment (:27-:30). The compliant ladder's middle contract is both a target and a source; that is a chain, not a duplicate claim, and the rule must leave it alone.
          • +
          • SchemaVersionRule_FlagsTheBackwardsUpcaster_ButNotTheCompliantLadder (:35-:46): names the offender (:40) and, notably, pins the message text "must declare a HIGHER SchemaVersion" (:41-:43), because the wording is what tells a developer which direction is allowed. Both clean rungs are asserted absent (:44-:45).
          • +
          • BothRules_Pass_OnAMapThatOwnsNoUpcasters (:48-:57): runs both rules against the real framework map and asserts NotThrow, with the because stating that the framework ships no upcaster so the rule passes vacuously (:54).
          • +
          +
        • +
        • Why it's built this way - the two rules exist because an upcast chain that is not a function, or that runs backwards, produces a bug that only appears when an old outbox row is replayed, potentially long after the change that caused it (ADR-090, building on ADR-010). Catching it at build time is worth a fixture namespace.
        • +
        • Where it's used - an independent class in the Common architecture suite. The shipped binding of both rules is EventConventionTestsBase, whose two facts call them for every repo (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Contracts/EventConventionTestsBase.cs:23,:26), and which MMCA.Common activates through EventVersioningConventionTests.
        • +
        +

        EventVersioningConventionTests

        +
        +

        MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Contracts · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/EventVersioningConventionTests.cs:12 · Level 13 · class

        +
        +
          +
        • What it is - the two-line binding of the integration-event convention rules (SchemaVersion, BaseIntegrationEvent inheritance, namespace residency; ADR-010) to MMCA.Common's own map.
        • +
        • Depends on - EventConventionTestsBase (public sealed class EventVersioningConventionTests : EventConventionTestsBase, EventVersioningConventionTests.cs:12) and CommonArchitectureMap (:14).
        • +
        • Concept introduced - none new; the same shipped-binding shape FeatureFlagLifecycleTests below and every other *TestsBase subclass in this suite follow. The class doc names the one concrete event this binding proves against today: OutputCacheEvictionRequested in MMCA.Common.Domain.IntegrationEvents (:8-:10); EventUpcasterFitnessTests above proves the upcaster half of the same rule family against fixtures because the framework ships no upcaster of its own. [Rubric §6 - CQRS & Event-Driven] and [Rubric §9 - API & Contract Design] are the properties; [Rubric §34 - Architecture Governance & Documentation] is the shipped-binding discipline (ADR-010).
        • +
        • Walkthrough - one member, Map (:14), overridden to new CommonArchitectureMap(); the base class's facts run against MMCA.Common.Domain.IntegrationEvents.
        • +
        • Why it's built this way - the class doc states its own failure mode directly, that the rules fail the build the moment a new integration event ships that breaks the convention, rather than only surfacing when an old outbox row is replayed (:9-:10).
        • +
        • Where it's used - an independent class in the Common architecture suite; the same base is subclassed by MMCA.ADC and MMCA.Store with their own maps.
        • +

        FakeConsumerMap

        MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Contracts · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/EventScopeFitnessTests.cs:50 · Level 13 · class

        @@ -4962,54 +5342,25 @@

        FeatureFlagLifecycleRuleTests

      • Why it's built this way - the declaration rule alone would still let a temporary flag rot forever once its rollout finishes; pairing it with an expiry rule that runs against a caller-supplied Today is what turns "flag documented" into "flag that fails the build when it overstays". Freezing Today as a class constant, rather than each fact computing its own offset from DateTime.UtcNow, keeps every fact's expected message exact instead of relative. See ADR-031.
      • Where it's used - an independent class in the Common architecture suite; the shipped binding of both rules for a real repo is FeatureFlagLifecycleTests.
      -

      QueryHandlerReadRepositoryTests

      -
      -

      MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Cqrs.Repositories · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/Repositories/QueryHandlerReadRepositoryTests.cs:14 · Level 13 · class

      -
      -
        -
      • What it is - the fixture-level proof of the query-handler-uses-read-repository convention rule: four [Fact]s that run ArchitectureRules.QueryHandlersUseReadRepositories against a small fixture assembly and assert on which handler names its failure message names.
      • -
      • Depends on - ArchitectureRules (:141, called as ArchitectureRules.QueryHandlersUseReadRepositories(_fixtureMap, allowed)), QueryHandlerReadRepositoryTestsBase (public sealed class QueryHandlerReadRepositoryTests : QueryHandlerReadRepositoryTestsBase, :14), ArchitectureMapBase (the nested FixtureAssemblyMap, :151-:159), and three fixtures from MMCA.Common.Architecture.Tests.ReadRepositoryFixtures: WriteRepositoryQueryHandlerFixture, ReadRepositoryQueryHandlerFixture, WriteRepositoryCommandHandlerFixture (:118,:123,:128).
      • -
      • Concept introduced - finding a repository call after an await, inside the compiler's generated state machine. The first [Fact]'s own assertion message states the point directly: "the GetRepository call sits after an await, inside the compiler's state machine, and must still be found" (:116-:119). A query handler that calls GetRepository for the write side only after awaiting some other call compiles to a state machine type, not the method body as written, so a naive IL scan over the declared method would miss it; this fixture exists specifically to prove the rule still catches that shape. [Rubric §6 - CQRS & Event-Driven] is the property: a query handler that reaches for the write repository is either a misclassified command or a read that will bypass whatever read-side optimization (a cached, projected, or replica-backed repository) the write repository does not offer.
      • -
      • Walkthrough - four [Fact]s plus a private Report helper and a nested FixtureAssemblyMap. QueryHandlerAskingForTheWriteRepository_IsFlagged_EvenInsideAnAsyncBody (:116-:119) asserts the report contains WriteRepositoryQueryHandlerFixture. QueryHandlerAskingForTheReadRepository_IsNotFlagged (:122-:123) asserts the opposite for the clean fixture. CommandHandlerAskingForTheWriteRepository_IsOutOfScope (:126-:129) asserts a command handler naming the write repository is never flagged, "a command handler writes, so the write repository is the right one there" (:129). AllowlistedHandler_IsSilenced (:132-:134) passes the write-repository query handler's full name into the rule's allowed collection and asserts it drops out of the report. Report (:137-:148) calls the rule inside a try/catch (XunitException) and returns either the empty string or the caught message, so each fact can assert on message content rather than only on throw/no-throw. FixtureAssemblyMap (:151-:159) is an ArchitectureMapBase whose only registered layer is Framework(Layer.Application, typeof(ReadRepositoryQueryHandlerFixture).Assembly), so the rule scans only this test assembly's fixtures rather than the framework's real Application layer.
      • -
      • Why it's built this way - proving the rule by message content, the same idiom IntegrationEventPayloadPurityRuleTests uses, catches a rule that throws for the wrong reason; and routing the scan through a fixture-only map keeps the real CommonArchitectureMap free of deliberately-wrong handlers that would otherwise have to be allowlisted permanently just to keep this test passing. See ADR-015.
      • -
      • Where it's used - run by the MMCA.Common.Architecture.Tests suite in CI's build-and-test job; its fixtures live in ReadRepositoryFixtures.cs in the same assembly, and MMCA.ADC subclasses the same base against its own map (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Cqrs/QueryHandlerReadRepositoryTests.cs).
      • -
      -

      RawSqlConventionTests

      -
      -

      MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Cqrs · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/RawSqlConventionTests.cs:12 · Level 13 · class

      -
      -
        -
      • What it is - the MMCA.Common binding of the raw-SQL convention rule: a subclass that scans the framework's own Application and Infrastructure source for interpolated or concatenated SQL text and allowlists the one file that legitimately needs it.
      • -
      • Depends on - RawSqlConventionTestsBase (public sealed class RawSqlConventionTests : RawSqlConventionTestsBase, :12) and CommonArchitectureMap (:14); ArchitectureMapBase.FindRepoRoot (:19) locates the solution root the scan walks from.
      • -
      • Concept introduced - scanning source text, not the compiled model, because the risk (SQL injection) lives in the string literal, not in a type reference a Mono.Cecil rule can see. ScannedSourceDirectories (:17-:22) points the base's text scan at Source/Core/MMCA.Common.Application and Source/Core/MMCA.Common.Infrastructure, the two layers where a hand-built query could appear. AllowedFiles (:25-:32) allowlists exactly one file, DbContextFactory.cs, for a SET IDENTITY_INSERT [schema].[table] ON/OFF statement whose table and schema names come off EF model metadata (entityType.GetSchema()/GetTableName()) rather than caller input, because T-SQL has no parameterized form for an identifier in that position. [Rubric §29 - Security Practices] is the property: the rule fails CI on any new interpolated SQL string outside this one reviewed exception, catching the injection surface before a query with caller-controlled text merges. [Rubric §34 - Architecture Governance & Documentation] is the allowlist-with-reasons discipline; the comment names the matching S2077 Sonar suppression at the call site itself, so the same exception is argued in two places that have to agree.
      • -
      • Walkthrough - two members. ScannedSourceDirectories (:17-:22) yields the two directory paths under the discovered repo root. AllowedFiles (:25-:32) is a one-entry IReadOnlyList<string> with the justification comment (:27-:30) directly above the filename (:31).
      • -
      • Why it's built this way - text-scanning rather than model-scanning means the rule also catches raw SQL that never reaches EF Core at all (a SqlCommand built by hand, for instance), which a Cecil-based rule over compiled IL would only see after the fact if it used a parameter, not a literal. See ADR-015.
      • -
      • Where it's used - run by the MMCA.Common.Architecture.Tests suite in CI's build-and-test job. Subclassed the same way in MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Cqrs/RawSqlConventionTests.cs.
      • -
      -

      ServiceContractPurityTests

      +

      FrameworkConstructorDependencyTests

      -

      MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Layering · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Layering/ServiceContractPurityTests.cs:11 · Level 13 · class

      +

      MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Cqrs.ConstructorDependencies · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/ConstructorDependencies/FrameworkConstructorDependencyTests.cs:25 · Level 13 · class (public, sealed)

        -
      • What it is - the MMCA.Common binding of the [ServiceContract] purity rule: a two-line subclass that supplies the repo's map so any type marked as part of a published wire surface is checked for dependencies on the producing service's Domain, Application or Infrastructure.
      • -
      • Depends on - ServiceContractPurityTestsBase (public sealed class ServiceContractPurityTests : ServiceContractPurityTestsBase, ServiceContractPurityTests.cs:11), IArchitectureMap and CommonArchitectureMap (the single override, :13), and indirectly ServiceContractAttribute, which the rule matches by full name rather than by reference.
      • -
      • Concept introduced - a rule that is deliberately vacuous today, kept as a ratchet. Most gates in this chapter earn their place by failing on real code. This one asserts nothing in MMCA.Common, because the framework marks no type with [ServiceContract], and both the subclass doc (:9-:10) and the base's remarks (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Layering/ServiceContractPurityTestsBase.cs:12-:17) say so plainly. The value is in the timing, the same argument the three ratchet subclasses rest on: the invariant is enforced from the first marked type onward, with no test for anyone to remember to write, at the moment when a contract package's shape is still cheap to change. Two design choices follow from that. It is attribute-driven rather than Layer.Contracts-driven, because no repo registers that layer today and a layer-iterating rule would pass vacuously forever (base remarks, :9-:11); and it scans every assembly the map registers, so a marked type is judged wherever it lives. A marked type sitting inside a Domain, Application or Infrastructure assembly then fails by construction, which the rule's own remarks call the intent: a published contract belongs in a *.Contracts or Shared assembly, not inside the service it describes (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Contracts.cs:26-:29). [Rubric §7 - Microservices Readiness] is the property: a contract that leaks a domain entity, a handler abstraction or a persistence type forces every consumer to take the producer's internals as a package dependency, which is what makes an extraction irreversible (:16-:18). [Rubric §9 - API & Contract Design] covers the wire surface itself, and [Rubric §34 - Architecture Governance & Documentation] the ratchet.
      • -
      • Walkthrough - one member, protected override IArchitectureMap Map { get; } = new CommonArchitectureMap(); (:13). Everything else is inherited: the base contributes a single [Fact], ServiceContracts_ShouldNotDependOn_ServiceInternals (ServiceContractPurityTestsBase.cs:24-:26), which calls ArchitectureRules.ServiceContractsDoNotDependOnServiceInternals(Map). The rule first derives the forbidden namespace set from the map's Domain, Application and Infrastructure layers (ArchitectureRules.Contracts.cs:57-:66) and returns immediately when that set is empty (:35-:38), then walks every layer, selects marked types through the Mono.Cecil custom rule CarriesServiceContractAttribute (:44,:69-:74), and asserts ShouldNot().HaveDependencyOnAny(forbidden) per layer with the layer's root namespace in the message (:42-:52).
      • -
      • Why it's built this way - matching the marker by its full-name string, "MMCA.Common.Shared.Abstractions.ServiceContractAttribute" (ArchitectureRules.Contracts.cs:10-:11), is the same zero-reference idiom the rest of the rule library uses: the testing package deliberately takes no compile dependency on the framework assemblies it inspects. The rule complements, and does not replace, the transport- and layer-purity rules that guard the same boundary from the layer side (ADR-007, ADR-015).
      • -
      • Where it's used - run by the MMCA.Common.Architecture.Tests suite in CI's build-and-test job. Its siblings in this chapter are ProtoContractFitnessTests and ContractImplementationTests, the other consumer-facing contract gates the framework exercises without owning any subject of its own.
      • -
      • Caveats / not-in-source - because the framework marks no type, this class asserts nothing today; there is no fixture proving the rule fires. That proof exists only in whichever repo first marks a type.
      • +
      • What it is - the single-responsibility ceiling applied to the framework itself: the three constructor-dependency facts of ConstructorDependencyCountTestsBase, re-pointed from the (empty) module populations at the framework's own Application, Infrastructure and API assemblies, with a ceiling of 7 for services, 5 for controllers and 7 for handlers (FrameworkConstructorDependencyTests.cs:33, :37, :40).
      • +
      • Depends on - ConstructorDependencyCountTestsBase (:25), CommonArchitectureMap (:31), the Layer enum and IArchitectureMap's OfLayer (:43, :46, :49), plus reflection over ControllerBase and the ICommandHandler/IQueryHandler contracts, matched by full name rather than by type reference (:27-:29).
      • +
      • Concept introduced - reusing a consumer-shaped rule on a repo with no modules by overriding its populations, not its rule. The base's defaults scan module assemblies (ScannedHandlers => Scan(Map.ModuleApplication(), IsCommandOrQueryHandler), MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Cqrs/ConstructorDependencyCountTestsBase.cs:78), which are empty in MMCA.Common, so a plain two-line binding would pass vacuously. The class doc names the fix (FrameworkConstructorDependencyTests.cs:6-:12): override the three virtual populations (ConstructorDependencyCountTestsBase.cs:61, :71, :78) and the measured-constructor selector (:86) so the same facts judge the framework's own classes. Two choices make that honest. Abstract classes are included and protected constructors are measured (FrameworkConstructorDependencyTests.cs:51-:53), because the framework's widest classes are abstract bases such as AuthenticationServiceBase and the *HandlerBase/*ControllerBase families whose only constructors are protected (:10-:12). Optional parameters count, since an optional collaborator is still a collaborator (:22). [Rubric §1 - SOLID] is the property; [Rubric §34 - Architecture Governance & Documentation] is the ratchet discipline the doc states outright: lower a ceiling as remediation lands, never raise one to turn a run green (:23).
      • +
      • Walkthrough
          +
        • Ceilings: MaxConstructorDependencies => 7 (:33); MaxControllerConstructorDependencies => 5 (:37), commented as the high-water mark of CrudEntityControllerBase, InboxController and UserAccountAuthControllerBase when the gate landed (:35-:36); MaxHandlerConstructorDependencies => 7 (:40), the mark of ResetPasswordHandlerBase (:39). These override the abstract members at ConstructorDependencyCountTestsBase.cs:40, :47, :55.
        • +
        • Populations: ScannedServices is every framework class in the Application and Infrastructure layers (FrameworkConstructorDependencyTests.cs:42-:43); ScannedControllers is the API layer filtered by IsController (:45-:46); ScannedHandlers is the Application layer filtered by IsCommandOrQueryHandler (:48-:49), which per the class doc includes the decorators (:18-:20).
        • +
        • MeasuredConstructors (:51-:53): public, protected, and protected-internal instance constructors.
        • +
        • FrameworkClasses (:61-:68): distinct assemblies, top-level non-static classes, not compiler-generated, and not records (detected by the compiler-emitted <Clone>$ method), because a positional record's parameters are data fields rather than injected collaborators (:55-:60).
        • +
        • IsController (:70-:81) walks the base-type chain for Microsoft.AspNetCore.Mvc.ControllerBase; IsCommandOrQueryHandler (:83-:87) matches any generic interface whose definition's full name is the open generic ICommandHandler<,> or IQueryHandler<,> (arity 2).
        • +
        • The three facts themselves live in the base: ApplicationServices_DoNotExceedConstructorDependencyCeiling, Controllers_DoNotExceedConstructorDependencyCeiling and Handlers_DoNotExceedConstructorDependencyCeiling (ConstructorDependencyCountTestsBase.cs:93, :116, :140).
        -

        FrameworkModels

        -
        -

        MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Domain · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/DeleteBehaviorConventionTests.cs:55 · Level 13 · class

        -
        -
          -
        • What it is - a private static helper nested inside DeleteBehaviorConventionTests that builds a real, compiled EF Core model for each of the framework's two production engines, so the delete-behavior convention rule can inspect the SQLite model and the SQL Server model rather than reason about attributes in the abstract.
        • -
        • Depends on - SqliteDbContext and SQLServerDbContext (:63,:78), PhysicalDataSource and DataSourceKey.Default (:67,:82), AuditSaveChangesInterceptor and DomainEventSaveChangesInterceptor (:91-:95), IEntityDataSourceRegistry (:96), and Microsoft.EntityFrameworkCore.DbContextOptionsBuilder from the BCL.
        • -
        • Concept introduced - standing up the minimum viable ApplicationDbContext for a model-only assertion. Reading OnModelCreating output requires a constructed context, but constructing one drags in every service OnConfiguring demands. Services() (:88-:98) registers only what that base method actually resolves: an AuditSaveChangesInterceptor and a DomainEventSaveChangesInterceptor, both handed an explicit TimeProvider.System, the latter wired to a NoDomainEventDispatcher and a NoOutboxSignal, plus a NoEntityDataSources registry, each a do-nothing fake bound through the real interfaces. Nothing here touches a database; Sqlite() opens DataSource=:memory: and SqlServer() points at an unreachable connection string, because building the model does not execute a connection. [Rubric §34 - Architecture Governance & Documentation] is the property: a convention test that asserts against context.Model is asserting against the same object EF Core migrations generate from, which is a stronger guarantee than pattern-matching source text.
        • -
        • Walkthrough - two public factory methods and one private helper. Sqlite() (:57-:70) and SqlServer() (:72-:85) each build a DbContextOptionsBuilder<T>, construct the matching sealed context (SqliteDbContext or SQLServerDbContext) with a PhysicalDataSource keyed by DataSourceKey.Default(DataSource.Sqlite|SQLServer), and return context.Model after disposing the context. Services() (:88-:98) builds the shared ServiceProvider both factories pass in, passing timeProvider: TimeProvider.System explicitly to DomainEventSaveChangesInterceptor (:91-:95) rather than relying on its default.
        • -
        • Why it's built this way - engine parity is the point: the same convention rule runs against both models from the same fixture setup, so a delete-behavior default that only SQLite or only SQL Server gets right would show up as a two-model mismatch rather than pass silently on whichever engine the CI runner happened to exercise.
        • -
        • Where it's used - called from DeleteBehaviorConventionTests's [Theory] cases to source the model each case asserts against; not used outside that file.
        • + +
        • Why it's built this way - matching the handler and controller contracts by full-name string keeps the test assembly free of a compile-time reference it would only need for a type check, and keeping the rule body in the base means the framework and every consumer are judged by the same arithmetic; only the populations and the ceilings differ.
        • +
        • Where it's used - an independent class in the Common architecture suite (no usage sites outside the defining file); MMCA.ADC binds the same base with its module populations through ConstructorDependencyCountTests.

        HandlerResultConventionTests

        @@ -5035,6 +5386,18 @@

        IdempotencyConventionTests

      • Why it's built this way - the same shipped-binding shape as every other *TestsBase subclass in this suite (ADR-015); the idempotency mechanics themselves are ADR-017.
      • Where it's used - an independent class in the Common architecture suite; MMCA.ADC and MMCA.Store subclass the same base with their own maps.
      +

      ImmutabilityTests

      +
      +

      MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Domain.EntityModel · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/EntityModel/ImmutabilityTests.cs:11 · Level 13 · class (public, sealed)

      +
      +
        +
      • What it is - the MMCA.Common binding of the immutability rules: DTOs, command/query messages, domain events, integration events and value objects expose no public mutable setter, run over the framework's own assemblies (class doc, ImmutabilityTests.cs:5-:10).
      • +
      • Depends on - ImmutabilityTestsBase (:11) and FrameworkModuleArchitectureMap (:13), deliberately not CommonArchitectureMap.
      • +
      • Concept introduced - choosing the map that makes a module-scoped rule bite. The DTO, command/query and domain-event immutability rules scope to the map's module layers (FrameworkModuleArchitectureMap.cs:6-:10), and CommonArchitectureMap registers the framework assemblies as framework layers, where those rules are vacuous. FrameworkModuleArchitectureMap registers the same Shared, Domain and Application assemblies as one module named Framework, leaving Infrastructure a framework layer (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/EntityModel/FrameworkModuleArchitectureMap.cs:19, :25-:28), so the rules actually run over the aggregates MMCA.Common ships, the Notifications family (:6-:11). It is the opposite move to FakeConsumerMap: there a module entry is added to prove a rule stays out of framework code; here it is added to pull framework code in. [Rubric §4 - DDD] is the property (the class doc tags rubric section 4, ImmutabilityTests.cs:6); [Rubric §34 - Architecture Governance & Documentation] is applying a consumer convention to the framework itself.
      • +
      • Walkthrough - one member, Map (:13), overridden to new FrameworkModuleArchitectureMap(). The five facts are inherited: Dtos_ShouldBe_Immutable, CommandsAndQueries_ShouldBe_Immutable, DomainEvents_ShouldBe_Immutable, IntegrationEvents_ShouldBe_Immutable and ValueObjects_ShouldBe_ImmutableSealedAndInShared (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Domain/ImmutabilityTestsBase.cs:13, :16, :19, :22, :25).
      • +
      • Why it's built this way - a two-line binding over the wrong map would be green forever and prove nothing; a dedicated map keeps the shared rule untouched while giving it real subjects in the repo that defines it.
      • +
      • Where it's used - an independent class in the Common architecture suite. MMCA.ADC subclasses the same base under the same class name with its own map (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Domain/ImmutabilityTests.cs).
      • +

      IntegrationEventPayloadPurityRuleTests

      MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Contracts · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/IntegrationEventPayloadPurityTests.cs:28 · Level 13 · class

      @@ -5124,6 +5487,65 @@

      FeatureFlagLifecycleTests

    • Why it's built this way - the same one-class-per-repo pattern every other *TestsBase subclass follows (ADR-015): the rule body and every edge case live once in FeatureFlagLifecycleTestsBase / FeatureFlagLifecycleRuleTests, and each repo supplies only its own IArchitectureMap. Because this fact runs against the live clock rather than a frozen Today, a framework flag that MMCA.Common itself lets overstay its RemoveBy fails this CI job on the day it expires, not only when someone thinks to check.
    • Where it's used - an independent class in the Common architecture suite; MMCA.ADC subclasses the same base with its own map (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Governance/FeatureFlagLifecycleTests.cs), and the FeatureFlagFixtures.cs file in this assembly feeds FeatureFlagLifecycleRuleTests its deliberately bad and good fixtures.
    +

    PiiConventionTests

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Governance · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/PiiConventionTests.cs:20 · Level 13 · class

    +
    +
      +
    • What it is - the framework repo's binding of the PII rules (ADR-005, rubric section 30): a sealed subclass that inherits the [Pii]-implies-IAnonymizable rule and adds a second, framework-only rule of its own, that every string property whose name says it holds a person's email, name, phone number or address carries [Pii] (class doc, PiiConventionTests.cs:5-:19).
    • +
    • Depends on - PiiConventionTestsBase (public sealed class PiiConventionTests : PiiConventionTestsBase, :20), CommonArchitectureMap and the IArchitectureMap abstraction it satisfies (:43), the Layer enum it queries the map with (:73-:74), and, by name only, PiiAttribute, IAnonymizable and PiiRedactor.
    • +
    • Concept introduced - a marking rule that gives a vacuous erasure rule something to check. The inherited fact, EntitiesWithPiiProperties_ShouldImplement_IAnonymizable (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/PiiConventionTestsBase.cs:12), only fires on a property that already carries [Pii]; a property nobody marked is invisible to it, and PiiRedactor logs that value in clear text (:9-:10). This subclass closes that gap by deciding from the property name which members hold personal data and requiring the marker on each. The name rule is deliberately narrow: a contact detail is any name containing Email, Phone or Address (ContactFragments, :33), and a person's name is a property ending in one of nine names such as FirstName, DisplayName or UserName (PersonNames, :37-:41). A bare Name is excluded on purpose, because in the framework it names a role, permission, event or file, never a person (:35-:36). [Rubric §30 - Compliance/Privacy/Data Governance] is the property (ADR-005).
    • +
    • Walkthrough - Map (:43) is the usual override. PersonalDataMembers (:71-:84) builds the scanned population: every type of the map's Domain layer (:73), plus the Application types whose namespace is, or sits under, one of MMCA.Common.Application.Auth, .Users or .Notifications (PersonalDataApplicationNamespaces, :25-:30; filter :74-:78); from those it keeps non-compiler-generated classes (:81), takes their public instance properties declared on the type itself (:82), and keeps the string-typed ones whose name passes IsPersonalDataName (:83, :86-:88). Two facts then run over it. PersonalDataMembers_AreDiscovered_GateIsNotVacuous (:45-:49) asserts the population is non-empty, so a drifted namespace list or name rule fails rather than leaving a gate that checks nothing. PersonalDataMembers_ShouldCarry_PiiAttribute (:51-:65) collects every member with no attribute named PiiAttribute (:22, matched by type name with inherit: true, :55-:56) and fails with the sorted DeclaringType.Property list in the message (:57-:64).
    • +
    • Why it's built this way - the Shared assembly is out of scope by construction, not by omission: it does not reference Domain, where [Pii] lives, so a Shared DTO cannot carry the marker (:16-:17). Matching the attribute by type name rather than by typeof keeps the check independent of which assembly declared the attribute. The redaction and erasure machinery the two rules feed is proven end to end separately by PiiErasureContractFitnessTests (:17-:18).
    • +
    • Where it's used - discovered and run by xUnit as part of MMCA.Common.Architecture.Tests in CI's build-and-test job; nothing constructs it. MMCA.ADC subclasses the same base against its own map (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Governance/PiiConventionTests.cs), without the framework-only marking rule.
    • +
    +

    Probe

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Governance · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/DataResidencyTestsBaseTests.cs:24 · Level 13 · class

    +
    +
      +
    • What it is - a private nested subclass of DataResidencyTestsBase inside DataResidencyTestsBaseTests, whose only job is to expose the base's protected ContainsRegionClaim helper to the test class (DataResidencyTestsBaseTests.cs:24-:31).
    • +
    • Depends on - DataResidencyTestsBase (private sealed class Probe : DataResidencyTestsBase, :24), CommonArchitectureMap and the IArchitectureMap abstraction (:26).
    • +
    • Concept - a test-only subclass used as a key to a protected member. ContainsRegionClaim is protected static on the base (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/DataResidencyTestsBase.cs:57), so only a derived type can call it; Probe derives, forwards it as public static bool Contains(string policy, string claim) (:28), and satisfies the two abstract members with the minimum: Map (:26) and an ExtractDeployedRegion that returns string.Empty (:30). It is private deliberately: the base carries a [Fact] of its own, and a public subclass would be collected by xUnit and run that fact against the real repo root, which the framework repo has no deployed region for (class doc, DataResidencyTestsBaseTests.cs:6-:8).
    • +
    • Where it's used - only by ContainsRegionClaim_MatchesWholeRegionTokensOnly in the enclosing class (:20).
    • +
    +

    QueryHandlerReadRepositoryTests

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Cqrs.Repositories · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/Repositories/QueryHandlerReadRepositoryTests.cs:14 · Level 13 · class

    +
    +
      +
    • What it is - the fixture-level proof of the query-handler-uses-read-repository convention rule: four [Fact]s that run ArchitectureRules.QueryHandlersUseReadRepositories against a small fixture assembly and assert on which handler names its failure message names.
    • +
    • Depends on - ArchitectureRules (:141, called as ArchitectureRules.QueryHandlersUseReadRepositories(_fixtureMap, allowed)), QueryHandlerReadRepositoryTestsBase (public sealed class QueryHandlerReadRepositoryTests : QueryHandlerReadRepositoryTestsBase, :14), ArchitectureMapBase (the nested FixtureAssemblyMap, :151-:159), and three fixtures from MMCA.Common.Architecture.Tests.ReadRepositoryFixtures: WriteRepositoryQueryHandlerFixture, ReadRepositoryQueryHandlerFixture, WriteRepositoryCommandHandlerFixture (:118,:123,:128).
    • +
    • Concept introduced - finding a repository call after an await, inside the compiler's generated state machine. The first [Fact]'s own assertion message states the point directly: "the GetRepository call sits after an await, inside the compiler's state machine, and must still be found" (:116-:119). A query handler that calls GetRepository for the write side only after awaiting some other call compiles to a state machine type, not the method body as written, so a naive IL scan over the declared method would miss it; this fixture exists specifically to prove the rule still catches that shape. [Rubric §6 - CQRS & Event-Driven] is the property: a query handler that reaches for the write repository is either a misclassified command or a read that will bypass whatever read-side optimization (a cached, projected, or replica-backed repository) the write repository does not offer.
    • +
    • Walkthrough - four [Fact]s plus a private Report helper and a nested FixtureAssemblyMap. QueryHandlerAskingForTheWriteRepository_IsFlagged_EvenInsideAnAsyncBody (:116-:119) asserts the report contains WriteRepositoryQueryHandlerFixture. QueryHandlerAskingForTheReadRepository_IsNotFlagged (:122-:123) asserts the opposite for the clean fixture. CommandHandlerAskingForTheWriteRepository_IsOutOfScope (:126-:129) asserts a command handler naming the write repository is never flagged, "a command handler writes, so the write repository is the right one there" (:129). AllowlistedHandler_IsSilenced (:132-:134) passes the write-repository query handler's full name into the rule's allowed collection and asserts it drops out of the report. Report (:137-:148) calls the rule inside a try/catch (XunitException) and returns either the empty string or the caught message, so each fact can assert on message content rather than only on throw/no-throw. FixtureAssemblyMap (:151-:159) is an ArchitectureMapBase whose only registered layer is Framework(Layer.Application, typeof(ReadRepositoryQueryHandlerFixture).Assembly), so the rule scans only this test assembly's fixtures rather than the framework's real Application layer.
    • +
    • Why it's built this way - proving the rule by message content, the same idiom IntegrationEventPayloadPurityRuleTests uses, catches a rule that throws for the wrong reason; and routing the scan through a fixture-only map keeps the real CommonArchitectureMap free of deliberately-wrong handlers that would otherwise have to be allowlisted permanently just to keep this test passing. See ADR-015.
    • +
    • Where it's used - run by the MMCA.Common.Architecture.Tests suite in CI's build-and-test job; its fixtures live in ReadRepositoryFixtures.cs in the same assembly, and MMCA.ADC subclasses the same base against its own map (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Cqrs/QueryHandlerReadRepositoryTests.cs).
    • +
    +

    RawSqlConventionTests

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Cqrs · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/RawSqlConventionTests.cs:12 · Level 13 · class

    +
    +
      +
    • What it is - the MMCA.Common binding of the raw-SQL convention rule: a subclass that scans the framework's own Application and Infrastructure source for interpolated or concatenated SQL text and allowlists the one file that legitimately needs it.
    • +
    • Depends on - RawSqlConventionTestsBase (public sealed class RawSqlConventionTests : RawSqlConventionTestsBase, :12) and CommonArchitectureMap (:14); ArchitectureMapBase.FindRepoRoot (:19) locates the solution root the scan walks from.
    • +
    • Concept introduced - scanning source text, not the compiled model, because the risk (SQL injection) lives in the string literal, not in a type reference a Mono.Cecil rule can see. ScannedSourceDirectories (:17-:22) points the base's text scan at Source/Core/MMCA.Common.Application and Source/Core/MMCA.Common.Infrastructure, the two layers where a hand-built query could appear. AllowedFiles (:25-:32) allowlists exactly one file, DbContextFactory.cs, for a SET IDENTITY_INSERT [schema].[table] ON/OFF statement whose table and schema names come off EF model metadata (entityType.GetSchema()/GetTableName()) rather than caller input, because T-SQL has no parameterized form for an identifier in that position. [Rubric §29 - Security Practices] is the property: the rule fails CI on any new interpolated SQL string outside this one reviewed exception, catching the injection surface before a query with caller-controlled text merges. [Rubric §34 - Architecture Governance & Documentation] is the allowlist-with-reasons discipline; the comment names the matching S2077 Sonar suppression at the call site itself, so the same exception is argued in two places that have to agree.
    • +
    • Walkthrough - two members. ScannedSourceDirectories (:17-:22) yields the two directory paths under the discovered repo root. AllowedFiles (:25-:32) is a one-entry IReadOnlyList<string> with the justification comment (:27-:30) directly above the filename (:31).
    • +
    • Why it's built this way - text-scanning rather than model-scanning means the rule also catches raw SQL that never reaches EF Core at all (a SqlCommand built by hand, for instance), which a Cecil-based rule over compiled IL would only see after the fact if it used a parameter, not a literal. See ADR-015.
    • +
    • Where it's used - run by the MMCA.Common.Architecture.Tests suite in CI's build-and-test job. Subclassed the same way in MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Cqrs/RawSqlConventionTests.cs.
    • +
    +

    ServiceContractPurityTests

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Layering · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Layering/ServiceContractPurityTests.cs:11 · Level 13 · class

    +
    +
      +
    • What it is - the MMCA.Common binding of the [ServiceContract] purity rule: a two-line subclass that supplies the repo's map so any type marked as part of a published wire surface is checked for dependencies on the producing service's Domain, Application or Infrastructure.
    • +
    • Depends on - ServiceContractPurityTestsBase (public sealed class ServiceContractPurityTests : ServiceContractPurityTestsBase, ServiceContractPurityTests.cs:11), IArchitectureMap and CommonArchitectureMap (the single override, :13), and indirectly ServiceContractAttribute, which the rule matches by full name rather than by reference.
    • +
    • Concept introduced - a rule that is deliberately vacuous today, kept as a ratchet. Most gates in this chapter earn their place by failing on real code. This one asserts nothing in MMCA.Common, because the framework marks no type with [ServiceContract], and both the subclass doc (:9-:10) and the base's remarks (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Layering/ServiceContractPurityTestsBase.cs:12-:17) say so plainly. The value is in the timing, the same argument the three ratchet subclasses rest on: the invariant is enforced from the first marked type onward, with no test for anyone to remember to write, at the moment when a contract package's shape is still cheap to change. Two design choices follow from that. It is attribute-driven rather than Layer.Contracts-driven, because no repo registers that layer today and a layer-iterating rule would pass vacuously forever (base remarks, :9-:11); and it scans every assembly the map registers, so a marked type is judged wherever it lives. A marked type sitting inside a Domain, Application or Infrastructure assembly then fails by construction, which the rule's own remarks call the intent: a published contract belongs in a *.Contracts or Shared assembly, not inside the service it describes (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Contracts.cs:26-:29). [Rubric §7 - Microservices Readiness] is the property: a contract that leaks a domain entity, a handler abstraction or a persistence type forces every consumer to take the producer's internals as a package dependency, which is what makes an extraction irreversible (:16-:18). [Rubric §9 - API & Contract Design] covers the wire surface itself, and [Rubric §34 - Architecture Governance & Documentation] the ratchet.
    • +
    • Walkthrough - one member, protected override IArchitectureMap Map { get; } = new CommonArchitectureMap(); (:13). Everything else is inherited: the base contributes a single [Fact], ServiceContracts_ShouldNotDependOn_ServiceInternals (ServiceContractPurityTestsBase.cs:24-:26), which calls ArchitectureRules.ServiceContractsDoNotDependOnServiceInternals(Map). The rule first derives the forbidden namespace set from the map's Domain, Application and Infrastructure layers (ArchitectureRules.Contracts.cs:57-:66) and returns immediately when that set is empty (:35-:38), then walks every layer, selects marked types through the Mono.Cecil custom rule CarriesServiceContractAttribute (:44,:69-:74), and asserts ShouldNot().HaveDependencyOnAny(forbidden) per layer with the layer's root namespace in the message (:42-:52).
    • +
    • Why it's built this way - matching the marker by its full-name string, "MMCA.Common.Shared.Abstractions.ServiceContractAttribute" (ArchitectureRules.Contracts.cs:10-:11), is the same zero-reference idiom the rest of the rule library uses: the testing package deliberately takes no compile dependency on the framework assemblies it inspects. The rule complements, and does not replace, the transport- and layer-purity rules that guard the same boundary from the layer side (ADR-007, ADR-015).
    • +
    • Where it's used - run by the MMCA.Common.Architecture.Tests suite in CI's build-and-test job. Its siblings in this chapter are ProtoContractFitnessTests and ContractImplementationTests, the other consumer-facing contract gates the framework exercises without owning any subject of its own.
    • +
    • Caveats / not-in-source - because the framework marks no type, this class asserts nothing today; there is no fixture proving the rule fires. That proof exists only in whichever repo first marks a type.
    • +

    SliceCohesionTests

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Cqrs · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/SliceCohesionTests.cs:10 · Level 13 · class

    @@ -5145,9 +5567,21 @@

    StronglyTypedIdConventionTests

  • Depends on - StronglyTypedIdTestsBase (public sealed class StronglyTypedIdConventionTests : StronglyTypedIdTestsBase, StronglyTypedIdConventionTests.cs:12), CommonArchitectureMap and the IArchitectureMap abstraction it satisfies (StronglyTypedIdConventionTests.cs:14).
  • Concept - the same base-plus-thin-subclass shape as UIArchitectureConventionTests and CancellationTokenConventionTestsBase: the rule lives once in MMCA.Common.Testing.Architecture and is re-run per repo by a subclass whose only job is to name the repo. The rule is deliberately vacuous today: every repo still defaults to the primitive identifier aliases (ADR-048/ADR-085), so MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.StronglyTypedIds.cs:38-:41 finds zero matching types across the map's layers and the rule passes with nothing asserted. Its purpose is to hold the FIRST wrapper a repo declares to the shape the framework's EF converter, JSON converter, comparer and route binding are written against (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Domain/StronglyTypedIdTestsBase.cs:4-:8).
  • Walkthrough - one member: protected override IArchitectureMap Map { get; } = new CommonArchitectureMap(); (StronglyTypedIdConventionTests.cs:14). The inherited fact, StronglyTypedIds_ShouldBe_ReadonlyRecordStructs, calls ArchitectureRules.StronglyTypedIdsAreReadonlyRecordStructs(Map) (StronglyTypedIdTestsBase.cs:14-:15), which loads every type across the map's distinct layer assemblies, filters to types implementing IStronglyTypedId by full-name prefix match, and asserts three properties on each match: it is a value type (ArchitectureRules.StronglyTypedIds.cs:43-:45), declared readonly (:47-:49), and a record (:51-:53), plus that it declares no state beyond the single Value property.
  • -
  • Why it's built this way - the three checked properties are load-bearing rather than stylistic: struct keeps an identifier allocation-free on the hot path a primitive alias occupies today, readonly prevents a tracked entity's key from changing under it, and record supplies the structural equality the wrapper exists for (StronglyTypedIds.cs:15-:22). The rule is kept separate from the value-object immutability rule because an identifier has no validation to fail and no Result-returning factory (:27-:30).
  • +
  • Why it's built this way - the three checked properties are load-bearing rather than stylistic: struct keeps an identifier allocation-free on the hot path a primitive alias occupies today, readonly prevents a tracked entity's key from changing under it, and record supplies the structural equality the wrapper exists for (ArchitectureRules.StronglyTypedIds.cs:14-:22). The rule is kept separate from the value-object immutability rule because an identifier has no validation to fail and no Result-returning factory (:26-:29).
  • Where it's used - discovered and run by xUnit as part of MMCA.Common.Architecture.Tests; nothing constructs it. No other repo subclasses StronglyTypedIdTestsBase today: the aliases remain the default everywhere, so this is currently the only binding.
  • +

    TenantEntityConventionTests

    +
    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Domain.EntityModel · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/EntityModel/TenantEntityConventionTests.cs:20 · Level 13 · class

    +
    +
      +
    • What it is - a framework-only tenancy marker rule (rubric section 4): two [Fact]s asserting that every concrete MMCA.Common entity that declares or inherits a TenantId property implements ITenantEntity (class doc, TenantEntityConventionTests.cs:6-:11). Unlike its neighbours in this part it is not a subclass of a shared *TestsBase; the rule exists only in this repo.
    • +
    • Depends on - CommonArchitectureMap (constructed directly, :47), the Layer enum (:49), ITenantEntity (:35), and, by full-name prefix only, BaseEntity<TIdentifierType> (BaseEntityFullNamePrefix, :22).
    • +
    • Concept introduced - a column that looks scoped but is not. ITenantEntity is the marker that applies the named Tenant query filter and the TenantSaveChangesInterceptor stamp, so an entity carrying a tenant column without it is readable and writable across tenants while appearing tenant-scoped (:9-:11). The rule catches that by structure (a property named TenantId, public or not, :23, :34) rather than by trusting the author to remember the marker. Equally deliberate is what it leaves out: the population is the BaseEntity<TId> hierarchy only, so the outbox, internal-command and audit-trail rows fall outside it. Those rows RECORD the tenant of the scope that wrote them (a nullable ambient-context column restored on delivery) rather than being owned by a tenant, and a read filter on them would hide work from the background drainers (:12-:18). [Rubric §4 - DDD] is the category the doc cites; multi-tenant isolation is the property it protects.
    • +
    • Walkthrough - FrameworkEntities (:45-:54) unions the map's Domain layer with its Infrastructure assemblies (:49-:51), de-duplicates, and keeps concrete classes for which DerivesFromBaseEntity is true (:53). DerivesFromBaseEntity (:56-:68) walks the base-type chain, reducing each generic base to its definition, and matches the full name against MMCA.Common.Domain.Entities.BaseEntity` by ordinal prefix (:58-:64), so every arity of BaseEntity and every subclass of it (auditable, aggregate root) qualifies. FrameworkEntities_AreDiscovered_GateIsNotVacuous (:25-:28) asserts the population is non-empty; EntitiesWithTenantId_ShouldImplement_ITenantEntity (:30-:42) selects entities that expose a TenantId instance property but are not assignable to ITenantEntity and fails with their full names (:33-:41).
    • +
    • Why it's built this way - the non-vacuous guard sits beside the real assertion because both halves of the population query are name- and layer-driven: a renamed BaseEntity or a map that stopped registering Domain would otherwise leave a rule that passes over an empty set.
    • +
    • Where it's used - discovered and run by xUnit as part of MMCA.Common.Architecture.Tests in CI's build-and-test job; nothing constructs it and no consumer repo carries a counterpart.
    • +

    UIArchitectureConventionTests

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Ui · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Ui/UIArchitectureConventionTests.cs:11 · Level 13 · class

    @@ -5171,7 +5605,7 @@

    UpcasterTestMap

  • Walkthrough - RepoToken => "MMCA.Common" (:76) and a one-entry DefineLayers() returning Framework(Layer.Application, typeof(EventUpcasterFitnessTests).Assembly) (:78-:79). The doc comment states the intent in one line (:73). The layer's derived root namespace is unused by these rules, which enumerate ConcreteClasses across whole assemblies (ArchitectureRules.Upcasters.cs:67) rather than namespace-scoped subsets.
  • Where it's used - constructed on every call of the test class's two private rule helpers (EventUpcasterFitnessTests.cs:61,:68).
  • -

    SoftDeleteEnforcementTests

    +

    SoftDeleteEnforcementTests

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Domain · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/SoftDeleteEnforcementTests.cs:19 · Level 13 · class

    @@ -5198,12 +5632,12 @@

    StateManagementConventionTests

    • What it is - the framework repo's binding of the state-management convention fitness functions (rubric section 19): a sealed subclass that supplies the map and one recorded exemption, so MMCA.Common.UI itself is held to the per-circuit state model Blazor Server requires.
    • Depends on - StateManagementConventionTestsBase (public sealed class StateManagementConventionTests : StateManagementConventionTestsBase, StateManagementConventionTests.cs:11), CommonArchitectureMap and the IArchitectureMap abstraction it satisfies (StateManagementConventionTests.cs:13).
    • -
    • Concept - the same base-plus-thin-subclass shape as SliceCohesionTests, StronglyTypedIdConventionTests and UIArchitectureConventionTests: the rule is authored once in MMCA.Common.Testing.Architecture and re-run per repo by a subclass whose only job is to name the repo (and, here, to record one exemption). Blazor Server shares one process across every circuit, so a mutable static member silently leaks one user's state into another's; the base holds two rules against that (StateManagementConventionTestsBase.cs:6-:15): a reflection scan of the repo's Layer.Ui assemblies fails on any mutable static field or settable static property, and a source scan fails the build if a production UI project registers a stateful *StateService/*StateContainer as a singleton rather than scoped.
    • -
    • Walkthrough - two members. Map (:13) is the usual override. AllowedStaticMembers (:21-:22) overrides the base's empty default with one entry, MMCA.Common.UI.Pages.Common.ErrorMessages._localizer, with the reason recorded next to it in the doc comment (:16-:19): the root layout configures the shared IStringLocalizer exactly once, idempotently, and the localizer itself resolves against the ambient UI culture per call, so no user's state leaks to another; the static API is kept because every consumer call site depends on it (ADR-027). The two inherited facts, UiAssemblies_CarryNoMutableStaticState and UiProjects_RegisterStatefulServicesScoped, run against MMCA.Common.UI unchanged (StateManagementConventionTestsBase.cs:28,:66).
    • -
    • Why it's built this way - exempting the member individually rather than excluding the whole ErrorMessages type keeps the rule honest about exactly what is write-once versus per-request: a future mutable field added to that same class would still fail. The base's first fact asserts its UI-assembly set is non-empty before scanning (StateManagementConventionTestsBase.cs:32-:33), so a map that forgot to register Layer.Ui fails loudly instead of passing vacuously.
    • -
    • Where it's used - discovered and run by xUnit as part of MMCA.Common.Architecture.Tests; nothing constructs it.
    • +
    • Concept - the same base-plus-thin-subclass shape as SliceCohesionTests, StronglyTypedIdConventionTests and UIArchitectureConventionTests: the rule is authored once in MMCA.Common.Testing.Architecture and re-run per repo by a subclass whose only job is to name the repo (and, here, to record one exemption). Blazor Server shares one process across every circuit, so a mutable static member silently leaks one user's state into another's; the base holds two rules against that (StateManagementConventionTestsBase.cs:7-:15): a reflection scan of the repo's Layer.Ui assemblies fails on any mutable static field or settable static property, and a source scan fails the build if a production UI project registers a stateful *StateService/*StateContainer as a singleton rather than scoped.
    • +
    • Walkthrough - three members. Map (:13) is the usual override. AllowedStaticMembers (:21-:22) overrides the base's empty default with one entry, MMCA.Common.UI.Pages.Common.ErrorMessages._localizer, with the reason recorded next to it in the doc comment (:16-:19): the root layout configures the shared IStringLocalizer exactly once, idempotently, and the localizer itself resolves against the ambient UI culture per call, so no user's state leaks to another; the static API is kept because every consumer call site depends on it (ADR-027). The third member is a fact of this subclass's own, SingletonScan_JudgesPathsBelowTheSourceRoot_AndCatchesAWrappedRegistration (:24-:47), which proves the base's source scanner rather than the repo: it builds a throwaway tree under the temp directory whose path deliberately contains a Testing segment above the Source root (:29), writes the same line-wrapped services.AddSingleton<\n ProbeStateService>(); registration into an MMCA.Probe.UI project and an MMCA.Probe.Domain project (:30-:36), calls the base's protected static FindSingletonStateRegistrations (:38; MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Ui/StateManagementConventionTestsBase.cs:92), and asserts exactly one offender (the UI project's Registrations.cs, line 1) from exactly one scanned file (:40-:41), deleting the tree in a finally (:43-:46). The two inherited facts, UiAssemblies_CarryNoMutableStaticState and UiProjects_RegisterStatefulServicesScoped, run against MMCA.Common.UI unchanged (StateManagementConventionTestsBase.cs:29,:67).
    • +
    • Why it's built this way - exempting the member individually rather than excluding the whole ErrorMessages type keeps the rule honest about exactly what is write-once versus per-request: a future mutable field added to that same class would still fail. The base's first fact asserts its UI-assembly set is non-empty before scanning (StateManagementConventionTestsBase.cs:33-:34), and its second asserts at least one UI source file was scanned (:74-:75), so a map that forgot to register Layer.Ui or a scan root that matched nothing fails loudly instead of passing vacuously. The extra fact pins two scanner properties a real-repo run cannot: paths are judged relative to the source root, segment by segment, so a checkout cloned under a directory named Testing is still scanned (StateManagementConventionTestsBase.cs:83-:86, :100-:103, and the comment at :27-:28 here); and the match runs up to the end of the statement, so a registration wrapped across lines is still one hit (StateManagementConventionTestsBase.cs:123-:125).
    • +
    • Where it's used - discovered and run by xUnit as part of MMCA.Common.Architecture.Tests; nothing constructs it. MMCA.ADC subclasses the same base against its own map (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Ui/StateManagementConventionTests.cs).
    -

    SoftDeleteEnforcementTests

    +

    SoftDeleteEnforcementTests

    MMCA.ADC.Architecture.Tests · MMCA.ADC.Architecture.Tests · MMCA.ADC.Architecture.Tests/Domain/SoftDeleteEnforcementTests.cs:15 · Level 13 · class (public, sealed)

    @@ -5225,17 +5659,16 @@

    SoftDeleteEnforcementTests

  • Why it's built this way - the entries are a good worked example of how MMCA reasons about erasure: most of them erase because privacy requires it, not despite it. The base frames the list the same way as the other ratchets, as a reviewed inventory of every place that does not soft-delete rather than a blanket exemption (SoftDeleteEnforcementTestsBase.cs:11-:15).
  • Caveats / not-in-source - entries are full names including the generic-arity suffix (EFRepository`2), so an arity change would drop the exemption and turn the rule red, which is the safe direction.
  • -

    DeleteBehaviorConventionTests

    +

    DataResidencyTestsBaseTests

    -

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Domain · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/DeleteBehaviorConventionTests.cs:28 · Level 14 · class

    +

    MMCA.Common.Architecture.Tests · MMCA.Common.Architecture.Tests.Governance · MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/DataResidencyTestsBaseTests.cs:10 · Level 14 · class

      -
    • What it is - the framework repo's binding of the delete-behavior fitness functions (ADR-119): a sealed subclass that builds the framework's own two finalized EF Core models (SQLite and SQL Server) with no database connection opened, and asserts each against the RestrictDeleteByDefaultConvention stamp the model already carries.
    • -
    • Depends on - DeleteBehaviorConventionTestsBase (public sealed class DeleteBehaviorConventionTests : DeleteBehaviorConventionTestsBase, DeleteBehaviorConventionTests.cs:28), ArchitectureRules (CascadingForeignKeysAreExplicitlyOptedIn, ConventionForeignKeysRestrictDeletes, DeleteBehaviorConventionTests.cs:57,:66), SqliteDbContext and SQLServerDbContext (:79,:94-:98), and a set of private no-op fakes (NoModuleAssemblies, NoEntityDataSources, NoDomainEventDispatcher, NoOutboxSignal, :122-:163) that satisfy the DI graph ApplicationDbContext.OnConfiguring demands without any real registrations.
    • -
    • Concept - the base class's own Model property only exercises one engine, so this subclass adds a second [Fact] (SqlServerModel_CascadingDeletes_AreExplicitlyOptedIn / SqlServerModel_ConventionDeletes_AreRestricted, :53-:69) that builds a second model against SQLServerDbContext and asserts both rules against it too, covering both relational engines the framework maps entities onto today. The nested FrameworkModels helper documents the trick that makes this possible without infrastructure: only the model is asserted, so neither context ever opens its connection, and EF builds the model from the configurations alone (:71-:74). NoEntityDataSources answers TryGetDataSourceKey with false for everything so the cross-source degrade convention becomes a no-op, and its two other lookups return a default key rather than throwing so a throwing fixture does not trip the repo's own domain-throw rule (:127-:131).
    • -
    • Walkthrough - Model (inherited) builds the SQLite model via DbContextOptionsBuilder<SqliteDbContext>().UseSqlite("DataSource=:memory:") and FrameworkModels.Sqlite() (:77-:90); the two added facts build the SQL Server model the same way via FrameworkModels.SqlServer() (:92-:105). Both paths construct their context with the same four dependencies: Services() (an AuditSaveChangesInterceptor, a DomainEventSaveChangesInterceptor, and a no-op IEntityDataSourceRegistry, :108-:118), a NoModuleAssemblies provider that returns an empty assembly list so the model holds only the framework's own tables (:121-:125), and a PhysicalDataSource describing the (unused) connection. Both inherited facts then read context.Model and assert the convention's stamps against it.
    • -
    • Why it's built this way - PostgreSQL takes the SQL Server mapping unchanged for delete-behavior purposes, so asserting SQLite plus SQL Server covers the framework's relational engines without a third context class; Cosmos is a deliberate no-op for this convention since it has no foreign key constraints to restrict (DeleteBehaviorConventionTestsBase.cs:16-:18). Building the model with in-memory/unopened connections keeps the fitness test fast and database-free, consistent with the rest of MMCA.Common.Architecture.Tests.
    • -
    • Where it's used - discovered and run by xUnit as part of MMCA.Common.Architecture.Tests; nothing constructs it. MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Domain/DeleteBehaviorConventionTests.cs is the sibling binding in the ADC repo, built the same way against ADC's own module assemblies.
    • +
    • What it is - the unit test for the region comparison inside the data-residency gate: one six-case [Theory] that pins DataResidencyTestsBase.ContainsRegionClaim to whole-token matching (DataResidencyTestsBaseTests.cs:12-:21).
    • +
    • Depends on - DataResidencyTestsBase, reached through its nested Probe subclass (:24-:31).
    • +
    • Concept introduced - testing the helper because the gate itself reads the real repo. The base's one fact, PrivacyPolicy_DataStorageRegion_MatchesDeployedRegion, locates the repo root, parses the deployed region from a repo-specific source and reads PRIVACY.md (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/DataResidencyTestsBase.cs:26-:45), so it cannot be fed fixtures; the comparison helper is the testable unit (class doc, DataResidencyTestsBaseTests.cs:6-:8). The failure it guards is a false pass: Azure region names nest (westus is a prefix of westus2, centralus is a suffix of southcentralus), so a naive substring check would let a policy claiming "West US 2" satisfy a deployment in westus. The helper normalizes both sides (whitespace stripped, upper-cased) and accepts an occurrence only when no digit follows it and the text before it does not end in a directional prefix (DataResidencyTestsBase.cs:47-:79). [Rubric §30 - Compliance/Privacy/Data Governance] is the property (ADR-105).
    • +
    • Walkthrough - ContainsRegionClaim_MatchesWholeRegionTokensOnly(policy, claim, expected) (:19-:22) calls Probe.Contains and asserts the expected boolean, with the because-text "a region that is a prefix of another region must not satisfy the residency gate" (:22). The six [InlineData] rows (:13-:18) pair the negatives with their positives: westus against "West US 2" is false while westus2 is true; centralus against "South Central US" is false while against "Central US" it is true; and two rows prove markdown emphasis (**West US 2**) and a spelled-out claim ("Central US") still match.
    • +
    • Where it's used - discovered and run by xUnit as part of MMCA.Common.Architecture.Tests; the gate it backs runs in consumer repos as thin subclasses such as DataResidencyTests in MMCA.ADC.

    CrossServiceDataSource

    @@ -5545,13 +5978,14 @@

    MmcaGatewayHardeningTestsBase& per-client-IP rate limiter and its bypass list, an optional tighter named policy on the credential route, the correlation ID stamped and echoed on every response, the per-downstream readiness checks, and the active destination health probe on every cluster - (MMCA.Common/Source/Hosting/MMCA.Common.Testing/Conformance/MmcaGatewayHardeningTestsBase.cs:15-22). The subclass - supplies only its own route-table facts. + (MMCA.Common/Source/Hosting/MMCA.Common.Testing/Conformance/MmcaGatewayHardeningTestsBase.cs:15-22), which + a host may declare off, in which case the same gate asserts it stays off. The subclass supplies only + its own route-table facts.
  • Depends on: RecordingHttpForwarder (not referenced in code, but the - registration the base's doc expects a subclass to make, :30-34), plus + registration the base's doc expects a subclass to make, :32-35), plus Microsoft.AspNetCore.TestHost's TestServer, WebApplicationFactory, Microsoft.Extensions.Diagnostics.HealthChecks' HealthCheckServiceOptions, YARP's - IProxyConfigProvider / IProxyConfigFilter, AwesomeAssertions and Xunit (:1-10). On the host + IProxyConfigProvider / IProxyConfigFilter, AwesomeAssertions and Xunit (:1-11). On the host side the behaviors it asserts come from the gateway kit: GatewayRateLimitingExtensions, GatewayCorrelationMiddleware, @@ -5559,10 +5993,10 @@

    MmcaGatewayHardeningTestsBase& GatewayHealthCheckDefaultsConfigFilter.

  • Concept introduced, driving the test server directly instead of through an HttpClient. The rate limit assertions go through TestServer.SendAsync(Action<HttpContext>, CancellationToken) rather than - an HttpClient (:22-28), and the reason is a chain of two facts: the kit partitions on + an HttpClient (:24-29), and the reason is a chain of two facts: the kit partitions on Connection.RemoteIpAddress, which a TestServer request leaves null, and the kit deliberately fails open on an unresolvable IP rather than collapsing every unattributable request into one shared - bucket (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Gateway/GatewayRateLimitingExtensions.cs:197-203). + bucket (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Gateway/GatewayRateLimitingExtensions.cs:215-221). A client-driven test could therefore never observe a 429. Setting the connection IP per request also gives each gate its own partition, which is what lets eight gates share one class fixture without disturbing each other. [Rubric §11, Security] and [Rubric §12, Performance & Scalability] are the @@ -5577,50 +6011,57 @@

    MmcaGatewayHardeningTestsBase&
  • Walkthrough
    • Client addresses (MMCA.Common/Source/Hosting/MMCA.Common.Testing/Conformance/MmcaGatewayHardeningTestsBase.cs:42-58): six constants, all inside the RFC 5737 TEST-NET-3 documentation range (203.0.113.0/24). Each gate - burns its own address, so the fixture's shared host gives every one a fresh window (:27-28); - ForwardedProxyIp (:57) stands in for the ingress proxy in the forwarded-header gate.
    • -
    • Abstract knobs, the only four a subclass must supply: Factory (:60), PermitLimit (:67, stated + burns its own address, so the fixture's shared host gives every one a fresh window (:28-29); + ForwardedProxyIp (:58) stands in for the ingress proxy in the forwarded-header gate.
    • +
    • Abstract knobs, the only four a subclass must supply: Factory (:61), PermitLimit (:68, stated by the subclass rather than read from the kit so an operator can see the number), LimitedPath - (:73) and DownstreamServices (:76).
    • -
    • Virtual knobs with kit defaults: BypassedPaths (:84-88, /.well-known/jwks.json and /health, - exempt because probes and JWKS discovery run at high frequency by design), NamedPolicyPath (:94, - null skips that gate) and NamedPolicyPermitLimit (:100), ActiveProbeInterval (:103, 30 - seconds) and ActiveProbeTimeout (:106, 5 seconds), ActiveProbePath (:113, /alive and not + (:71) and DownstreamServices (:77).
    • +
    • Virtual knobs with kit defaults: BypassedPaths (:85-89, /.well-known/jwks.json and /health, + exempt because probes and JWKS discovery run at high frequency by design), NamedPolicyPath (:95, + null skips that gate) and NamedPolicyPermitLimit (:101), ActiveHealthChecksExpected (:111, + default true; false fits a host whose clusters each front one platform-balanced address such as + a Container Apps internal FQDN, where ejecting the only destination can only turn a slow request + into a 503, the platform already routes only to ready replicas, and every probe bills an otherwise + idle downstream replica at the active rate, :103-110), ActiveProbeInterval (:114, 30 seconds) + and ActiveProbeTimeout (:117, 5 seconds), ActiveProbePath (:124, /alive and not /health, because readiness on a downstream flips during its own rolling deployment and ejecting a - destination for that is the gateway treating a healthy deploy as an outage, :108-112), - CorrelationHeader (:116), DownstreamCheckPrefix (:119) and ForwardedForHeader (:122).
    • -
    • Route_IsThrottledOnceTheClientExhaustsItsWindow (:124-144): sends exactly PermitLimit requests, - then one more, asserting none of the first batch was a 429 and the overflow was (:139-143).
    • -
    • NamedPolicyRoute_IsThrottledAtItsOwnTighterAllowance (:146-180): returns early when the host - declares no named policy (:151-154), a plain return rather than a declared dynamic skip because + destination for that is the gateway treating a healthy deploy as an outage, :119-123), + CorrelationHeader (:127), DownstreamCheckPrefix (:130) and ForwardedForHeader (:133).
    • +
    • Route_IsThrottledOnceTheClientExhaustsItsWindow (:135-155): sends exactly PermitLimit requests, + then one more, asserting none of the first batch was a 429 and the overflow was (:150-154).
    • +
    • NamedPolicyRoute_IsThrottledAtItsOwnTighterAllowance (:157-191): returns early when the host + declares no named policy (:162-165), a plain return rather than a declared dynamic skip because that would need xunit.v3.assert, which this shipped fixture library deliberately does not - reference (:149-150). Otherwise it exhausts the tighter allowance, asserts the overflow is shed, - and then asserts the same client on a route without the policy is still admitted (:174-179), + reference (:160-161). Otherwise it exhausts the tighter allowance, asserts the overflow is shed, + and then asserts the same client on a route without the policy is still admitted (:185-190), which is what makes the rejection attributable to the route policy rather than the global limiter.
    • -
    • BypassedPaths_AreNotThrottledEvenAfterTheWindowIsExhausted (:182-207): guards against an empty - bypass list (:185-186), burns the window and proves it is exhausted (:194-195), then asserts +
    • BypassedPaths_AreNotThrottledEvenAfterTheWindowIsExhausted (:193-218): guards against an empty + bypass list (:196-197), burns the window and proves it is exhausted (:205-206), then asserts every exempt path still answers with something other than 429.
    • -
    • The two correlation gates (:209-225 and :227-242): a request with no header must come back with a +
    • The two correlation gates (:220-236 and :238-253): a request with no header must come back with a generated non-blank correlation ID, and a caller-supplied ID must be echoed unchanged, so a trace that started upstream survives the hop through the edge.
    • -
    • Readiness_IncludesADownstreamCheckPerService (:244-270): reads the registered - HealthCheckServiceOptions (:251-252) and, per downstream, asserts a check named +
    • Readiness_IncludesADownstreamCheckPerService (:255-281): reads the registered + HealthCheckServiceOptions (:262-263) and, per downstream, asserts a check named {DownstreamCheckPrefix}{service} exists, is tagged ready, is not tagged live, and has - FailureStatus = Unhealthy (:263-268). The comment is the reasoning: a gateway that cannot reach + FailureStatus = Unhealthy (:274-279). The comment is the reasoning: a gateway that cannot reach its services must leave the load balancer, but restarting the gateway process fixes nothing about a downstream outage, so the check must never fail liveness.
    • -
    • EveryCluster_CarriesAnActiveHealthCheckProbingAlive (:272-306): resolves the proxy config and the - registered IProxyConfigFilter chain (:276-277), then runs each raw cluster through every filter - (:283-291) because the provider hands out the pre-filter config and the kit's defaults only appear - after the filters run, exactly as YARP's config manager does at load time. It then asserts each - cluster carries an enabled active health check probing ActiveProbePath at the declared interval and - timeout (:296-304).
    • -
    • RateLimiter_PartitionsByForwardedClientIp_NotByProxyIp (:308-331): every request arrives from the +
    • EveryCluster_CarriesAnActiveHealthCheckProbingAlive (:283-324): resolves the proxy config and the + registered IProxyConfigFilter chain (:287-288), then runs each raw cluster through every filter + (:296-302) because the provider hands out the pre-filter config and the kit's defaults only appear + after the filters run, exactly as YARP's config manager does at load time. When + ActiveHealthChecksExpected is false it asserts the opposite, that no cluster carries an enabled + active check, and moves on (:308-313), so an active block left behind in configuration still fails + the build. Otherwise it asserts each cluster carries an enabled active health check probing + ActiveProbePath at the declared interval and timeout (:315-322). The gate keeps its name in both + modes.
    • +
    • RateLimiter_PartitionsByForwardedClientIp_NotByProxyIp (:326-349): every request arrives from the same connection IP while X-Forwarded-For names a different caller. It exhausts caller A's window, - confirms the exhaustion, then asserts caller B behind the same proxy IP is still admitted, which only - holds if the forwarded-headers middleware runs before the limiter.
    • -
    • Helpers: SendAsync(path, clientIp) (:340-353) and SendForwardedAsync(path, proxyIp, forwardedFor) - (:363-378), each shaping an HttpContext on the test server (method, HTTPS scheme, path, connection + confirms the exhaustion (:339-340), then asserts caller B behind the same proxy IP is still + admitted (:346-348), which only holds if the forwarded-headers middleware runs before the limiter.
    • +
    • Helpers: SendAsync(path, clientIp) (:351-371) and SendForwardedAsync(path, proxyIp, forwardedFor) + (:373-396), each shaping an HttpContext on the test server (method, HTTPS scheme, path, connection IP, and for the second one the forwarded header) and returning the response status code.
  • @@ -5628,24 +6069,34 @@

    MmcaGatewayHardeningTestsBase& genuinely per-host facts are the route table and the configured numbers, so those are the four abstract members and everything else is a virtual with a kit default. Asserting the effective YARP config (post-filter) rather than the raw file is what keeps the health-probe gate honest about what the host - will actually do (ADR-089).

  • + will actually do (ADR-089). + Turning the active probe off is a declared host fact rather than an absent assertion: the + ActiveHealthChecksExpected switch flips the gate to assert the probe stays off, so the choice cannot + drift silently in either direction (:103-110, :308-313).
  • Where it's used: both gateways, each a sealed subclass over its own Program. ADC - (MMCA.ADC/Tests/Hosts/MMCA.ADC.Gateway.Tests/GatewayHardeningTests.cs:29-30) states a 120-request - allowance (:38), /Events/1 as the limited path (:41), four downstreams (:47-53), /hubs added - to the bypass list because a SignalR connection is long-lived (:62-67), and /Auth/login at 30 - requests as its named policy (:70, :80). Store - (MMCA.Store/Tests/Hosts/MMCA.Store.Gateway.Tests/GatewayHardeningTests.cs:32-33) reads its + (MMCA.ADC/Tests/Hosts/MMCA.ADC.Gateway.Tests/GatewayHardeningTests.cs:29) states a 120-request + allowance (:40), /Events/1 as the limited path (:43), four downstreams (:49-55), /hubs added + to the bypass list because a SignalR connection is long-lived (:57-69), /Auth/login at 30 + requests as its named policy (:72, :82), and declares active health checks off + (MmcaGateway:HealthCheckDefaults:Active:Enabled=false) because each cluster fronts one Container + Apps address and every probe bills an idle downstream replica (:84-89). Store + (MMCA.Store/Tests/Hosts/MMCA.Store.Gateway.Tests/GatewayHardeningTests.cs:34) reads its PermitLimit straight out of the bound GatewayRateLimitingSettings options so - the suite cannot drift from the section it exercises (:43-44), uses /Products/1 (:47), three - downstreams (:53), and adds /Payments/webhook to the bypass list because a throttled Stripe webhook - becomes a retry storm and an unreconciled payment (:63-68).
  • + the suite cannot drift from the section it exercises (:40-46), uses /Products/1 (:49), three + downstreams (:55), adds /Payments/webhook to the bypass list because a throttled Stripe webhook + becomes a retry storm and an unreconciled payment (:57-70), declares /Auth/login at 30 requests as + its named policy (:73, :86; only the login and register submission routes carry it, because the + UI replica refreshes sessions server-side with no X-Forwarded-For and a client-IP partition there + would key the whole storefront on one container address, :75-85), and declares active health checks + off for the same one-address reason as ADC (:88-93).
  • Caveats / not-in-source: the gates need a booted gateway host, which MMCA.Common's own test project does not have, so the framework can only guard the base's shape headlessly (MMCA.Common/Tests/Hosting/MMCA.Common.Testing.Tests/Conformance/MmcaGatewayHardeningTestsBaseTests.cs:21): that - all eight gate names are still declared as [Fact] (:22-32, :42-53) and that exactly the four - route-table members are still abstract (:34-40, :55-66), with an abstract - SampleGatewayHardeningTests (:74) as compile coverage for the subclass surface.
  • + all eight gate names are still declared as [Fact] (:23-33, :43-54) and that exactly the four + route-table members are still abstract (:35-41, :56-67), which keeps ActiveHealthChecksExpected + a virtual, with an abstract SampleGatewayHardeningTests (:75, overriding + ActiveHealthChecksExpected at :93) as compile coverage for the subclass surface.

    ProductionHostApplicationFactory<TEntryPoint>

    @@ -6039,7 +6490,7 @@

    CrossServiceFixtureBase

    MMCA.Store/Tests/Integration/MMCA.Store.CrossService.IntegrationTests/Infrastructure/CrossServiceFixture.cs:29 (two databases at :56-60, prefix MMCA.Store.Migrations.SqlServer at :63). MMCA.Common covers the container-free half of the base through its own private FakeCrossServiceFixture - (MMCA.Common/Tests/Hosting/MMCA.Common.Testing.Tests/Fixtures/CrossServiceFixtureBaseTests.cs:107). + (MMCA.Common/Tests/Hosting/MMCA.Common.Testing.Tests/Fixtures/CrossServiceFixtureBaseTests.cs:119).
  • Caveats / not-in-source: this tier needs a Docker daemon. Where each repo schedules it is a CI decision recorded outside this class; the base itself says nothing about scheduling. It also proves the round-trip over RabbitMQ, not the production transport, which is what @@ -6361,7 +6812,7 @@

    RecordingHttpForwarder

    SqlServerIntegrationTestFixtureBase<TEntryPoint>

    -

    MMCA.Common.Testing · MMCA.Common.Testing.Fixtures · MMCA.Common/Source/Hosting/MMCA.Common.Testing/Fixtures/SqlServerIntegrationTestFixtureBase.cs:27 · Level 1 · class (abstract)

    +

    MMCA.Common.Testing · MMCA.Common.Testing.Fixtures · MMCA.Common/Source/Hosting/MMCA.Common.Testing/Fixtures/SqlServerIntegrationTestFixtureBase.cs:27 · Level 2 · class (abstract)

    • What it is: the reusable fixture that boots a real service host in-process against a throwaway @@ -6369,7 +6820,9 @@

      SqlServerIntegrationTest Respawn, and drops the database on disposal. It is the concrete engine behind IIntegrationTestFixture for SQL Server hosts.

    • Depends on: IIntegrationTestFixture (implemented, - MMCA.Common/Source/Hosting/MMCA.Common.Testing/Fixtures/SqlServerIntegrationTestFixtureBase.cs:27), plus + MMCA.Common/Source/Hosting/MMCA.Common.Testing/Fixtures/SqlServerIntegrationTestFixtureBase.cs:27), + CrossServiceFixtureBase (its static ComposeConnectionString helper, + MMCA.Common/Source/Hosting/MMCA.Common.Testing/Fixtures/CrossServiceFixtureBase.cs:82), plus Microsoft.AspNetCore.Mvc.Testing (WebApplicationFactory), Microsoft.Data.SqlClient, Respawn, and Xunit's IAsyncLifetime (:1-4).
    • Concept introduced, the disposable-database integration fixture and environment-variable overrides. @@ -6392,7 +6845,10 @@

      SqlServerIntegrationTest connection string), DatabaseNamePrefix (:61), and CreateFactory() (:134, where the subclass builds the host). CreateClient() (:64) satisfies the interface by delegating to the factory.

    • InitializeAsync (:67-96): resolves the server base from SqlBaseEnvironmentVariable or falls - back to LocalDB (:69-70), composes a GUID-suffixed database name and connection string (:71-72), + back to LocalDB (:69-70), composes a GUID-suffixed database name (:71) and builds the connection + string through CrossServiceFixtureBase.ComposeConnectionString (:72), which overlays the catalog + on the base string with a SqlConnectionStringBuilder and forces TrustServerCertificate + (MMCA.Common/Source/Hosting/MMCA.Common.Testing/Fixtures/CrossServiceFixtureBase.cs:82-88), forces ASPNETCORE_ENVIRONMENT=Testing and pushes the top-level SQL connection string as environment variables (:75-76), lets the subclass push its own via ConfigureTestEnvironment (:77), and builds the factory (:79); creating the client is what triggers the host's Migrate @@ -6407,7 +6863,7 @@

      SqlServerIntegrationTest a key cannot clobber the restore point; RestoreEnvironment (:157-165) puts them all back and clears the map.

    • DropDatabaseAsync (:167-188): clears pooled connections so the database is free to drop (:170), - connects to master, and runs a guarded SET SINGLE_USER WITH ROLLBACK IMMEDIATE plus + connects to master through the same ComposeConnectionString helper (:172), and runs a guarded SET SINGLE_USER WITH ROLLBACK IMMEDIATE plus DROP DATABASE (:180-183), with a scoped CA2100 suppression justified because the database name is a server-generated GUID, never user input (:179).
    @@ -6418,15 +6874,17 @@

    SqlServerIntegrationTest collapses onto the single overridden top-level connection string, making the fixture behave like a clean single-database monolith. LocalDB-by-default keeps local runs zero-config while CI can point at a SQL service container. Note the contrast with CrossServiceFixtureBase, - which needs the opposite (named sources per host so several EF models can coexist in one process).

  • + which needs the opposite (named sources per host so several EF models can coexist in one process); + the two fixtures nevertheless share one pure, static connection-string composer, so the catalog + overlay is identical in both and unit-testable without a container (CrossServiceFixtureBase.cs:74-82).
  • Where it's used: the base of every per-service integration fixture in both apps, four in ADC (MMCA.ADC/Tests/Integration/MMCA.ADC.Conference.IntegrationTests/Infrastructure/ConferenceIntegrationTestFixture.cs:18, - .../MMCA.ADC.Engagement.IntegrationTests/Infrastructure/EngagementIntegrationTestFixture.cs:17, - .../MMCA.ADC.Identity.IntegrationTests/Infrastructure/IdentityIntegrationTestFixture.cs:22, - .../MMCA.ADC.Notification.IntegrationTests/Infrastructure/NotificationIntegrationTestFixture.cs:17) + .../MMCA.ADC.Engagement.IntegrationTests/Infrastructure/EngagementIntegrationTestFixture.cs:18, + .../MMCA.ADC.Identity.IntegrationTests/Infrastructure/IdentityIntegrationTestFixture.cs:23, + .../MMCA.ADC.Notification.IntegrationTests/Infrastructure/NotificationIntegrationTestFixture.cs:18) and three in Store (MMCA.Store/Tests/Integration/MMCA.Store.Catalog.IntegrationTests/Infrastructure/CatalogIntegrationTestFixture.cs:16, - .../MMCA.Store.Identity.IntegrationTests/Infrastructure/IdentityIntegrationTestFixture.cs:15, + .../MMCA.Store.Identity.IntegrationTests/Infrastructure/IdentityIntegrationTestFixture.cs:16, .../MMCA.Store.Sales.IntegrationTests/Infrastructure/SalesIntegrationTestFixture.cs:17). Each is then the TFixture for that service's integration and contract tests.
  • Caveats / not-in-source: the fixture needs a reachable SQL Server, so these suites build but do not @@ -6922,29 +7380,30 @@

    ObservabilityConventionTestsBase

    MMCA.Common.Testing.Architecture · MMCA.Common.Testing.Architecture · MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/ObservabilityConventionTestsBase.cs:30 · Level 0 · abstract partial class

    • -
    • What it is: an SLO alert-to-runbook pairing gate. It parses the SLO metric alerts a consumer's infra/main.bicep provisions and asserts each one keeps a matching, severity-correct triage section in that repo's infra/OPERATIONS.md, in both directions: a missing runbook section fails, and so does an orphan runbook section whose alert no longer exists.
    • +
    • What it is: an SLO alert-to-runbook pairing gate. It parses the SLO metric alerts a consumer's infra/main.bicep provisions and asserts each one keeps a matching, severity-correct triage section in that repo's infra/OPERATIONS.md, in both directions: a missing runbook section fails, and so does an orphan runbook section whose alert no longer exists. An opt-in fourth check additionally requires the bicep to provision an operator-facing monitoring view (an Azure Monitor workbook or a portal dashboard) next to those alerts.
    • Depends on: System.Globalization, source-generated System.Text.RegularExpressions regexes, Assembly.GetManifestResourceStream, AwesomeAssertions, and [Fact] (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/ObservabilityConventionTestsBase.cs:1-2). No first-party dependency and, notably, no IArchitectureMap: it reads embedded text, not assemblies.
    • Concept introduced, the documentation-pairing gate. [Rubric §13, Observability & Operability] assesses whether an operator paged at 3am has usable guidance; the failure mode this base closes is the silent one, an alert added, renamed, or re-tiered while its runbook section stays behind. [Rubric §17, DevOps] applies because the source of truth is the IaC template itself, and [Rubric §34, Architecture Governance] because it makes the "every alert has a runbook" convention executable rather than aspirational.
    • Walkthrough
        -
      • Overridable knobs: MinimumAlertSpecs (line 39, default 3), the two manifest-resource logical names BicepResource (line 42, infra.main.bicep) and RunbookResource (line 45, infra.OPERATIONS.md), and ResourceAssembly (line 51), which defaults to GetType().Assembly.
      • -
      • SloAlertSpecs_AreDiscovered_GateIsNotVacuous (lines 53-61) is the honesty guard: discovering fewer specs than the floor means the parse anchors drifted, not that alerts disappeared.
      • -
      • EveryProvisionedSloAlert_HasASeverityCorrectRunbookSection (lines 63-89) matches each alert key against the runbook headings by the -alert- infix (line 73, the constant at line 32) and then asserts the heading also carries the (sev N) tag matching the bicep severity (lines 80-84).
      • -
      • EveryRunbookAlertSection_MapsToAProvisionedAlert (lines 91-103) is the reverse direction, flagging stale guidance.
      • -
      • DiscoverAlertSpecs (lines 105-126) slices the bicep between var sloAlertSpecs and resource sloAlerts (lines 109-112, each index assertion carrying its own because), runs AlertKeyRegex and AlertSeverityRegex over that block, and asserts the two match counts agree (line 117) so a changed spec shape fails loudly instead of silently mis-pairing. DiscoverRunbookAlertHeadings (line 128) keeps only the ### headings carrying the infix. The three [GeneratedRegex] partial properties sit at lines 139-146, each with a 2000 ms match timeout.
      • -
      • ReadEmbedded (lines 131-137) throws a message naming the assembly when a resource is missing.
      • +
      • Overridable knobs: MinimumAlertSpecs (line 39, default 3), the two manifest-resource logical names BicepResource (line 42, infra.main.bicep) and RunbookResource (line 45, infra.OPERATIONS.md), and ResourceAssembly (line 51), which defaults to GetType().Assembly. A fifth knob, RequireWorkbook (line 58), defaults to false, so a subclass that does not opt in keeps exactly the alert-to-runbook checks.
      • +
      • MonitoringWorkbookOrDashboard_IsProvisioned_WhenRequired (lines 65-77) returns immediately unless RequireWorkbook is on; when it is, it asserts MonitoringViewResourceRegex finds at least one resource declaration of type Microsoft.Insights/workbooks or Microsoft.Portal/dashboards in the bicep (line 75), so the signals the alerts fire on also have a place an operator can look at them.
      • +
      • SloAlertSpecs_AreDiscovered_GateIsNotVacuous (lines 79-87) is the honesty guard: discovering fewer specs than the floor means the parse anchors drifted, not that alerts disappeared.
      • +
      • EveryProvisionedSloAlert_HasASeverityCorrectRunbookSection (lines 89-115) matches each alert key against the runbook headings by the -alert- infix (line 99, the constant at line 32) and then asserts the heading also carries the (sev N) tag matching the bicep severity (lines 106-110).
      • +
      • EveryRunbookAlertSection_MapsToAProvisionedAlert (lines 117-129) is the reverse direction, flagging stale guidance.
      • +
      • DiscoverAlertSpecs (lines 131-152) slices the bicep between var sloAlertSpecs and resource sloAlerts (lines 135-138, each index assertion carrying its own because), runs AlertKeyRegex and AlertSeverityRegex over that block, and asserts the two match counts agree (line 143) so a changed spec shape fails loudly instead of silently mis-pairing. DiscoverRunbookAlertHeadings (line 154) keeps only the ### headings carrying the infix. The four [GeneratedRegex] partial properties sit at lines 165-175, each with a 2000 ms match timeout; the last, MonitoringViewResourceRegex (lines 174-175), is multiline and case-insensitive.
      • +
      • ReadEmbedded (lines 157-163) throws a message naming the assembly when a resource is missing.
    • Why it's built this way: the class doc (lines 23-28) records why the ResourceAssembly default is load-bearing. The base ships inside the framework package, so resolving resources against its own assembly would look for the consumer's bicep inside MMCA.Common.Testing.Architecture.dll and always throw. Defaulting to the derived type's assembly means a subclass needs no wiring beyond two EmbeddedResource entries in its csproj (the snippet is in the doc at lines 18-22).
    • -
    • Where it's used: subclassed in ADC (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Governance/ObservabilityConventionTests.cs:7), where the single override raises MinimumAlertSpecs from the base default of three to the four specs ADC declares (:13) so the gate cannot pass vacuously on a drifted parse, and as a one-line body-less class in Store (MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/ObservabilityConventionTests.cs:7); see ObservabilityConventionTests. MMCA.Common carries ObservabilityConventionTestsBaseTests instead, a deliberate cross-assembly guard that repoints both resource names at local fixtures (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/ObservabilityConventionTestsBaseTests.cs:14, resources at :16-18) and adds one extra [Fact] pinning the ResourceAssembly default to the derived type's assembly rather than the base's (:24-29).
    • +
    • Where it's used: subclassed in ADC (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Governance/ObservabilityConventionTests.cs:7), where the single override raises MinimumAlertSpecs from the base default of three to five (:14) so the gate cannot pass vacuously on a drifted parse, and in Store (MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/Governance/ObservabilityConventionTests.cs:7), whose one override raises the floor to four (:13); see ObservabilityConventionTests. Neither consumer opts into RequireWorkbook. MMCA.Common subclasses it twice. ObservabilityConventionTestsBaseTests is a deliberate cross-assembly guard that repoints both resource names at local fixtures (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/ObservabilityConventionTestsBaseTests.cs:14, resources at :16-18) and adds one extra [Fact] pinning the ResourceAssembly default to the derived type's assembly rather than the base's (:24-29). SampleDeploymentObservabilityTests (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/SampleDeploymentObservabilityTests.cs:12) runs the gate over the framework's own samples/deployment bicep and runbook (:14-16) with a floor of four (:20), and is the one subclass that turns RequireWorkbook on (:24), since the sample is the deployment consumers copy.

    ProtoScopeKind

    -

    MMCA.Common.Testing.Architecture · MMCA.Common.Testing.Architecture · MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:286 · Level 0 · private enum

    +

    MMCA.Common.Testing.Architecture · MMCA.Common.Testing.Architecture · MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:299 · Level 0 · private enum

      -
    • What it is: the four block kinds the .proto parser tracks while walking a file: Service, Message, Enum, Oneof (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:286-292).
    • +
    • What it is: the four block kinds the .proto parser tracks while walking a file: Service, Message, Enum, Oneof (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:299-305).
    • Depends on: nothing; a private enum nested in ArchitectureRules.
    • -
    • Concept: the proto contract gate parses .proto files with a hand-rolled line scanner, not a protobuf compiler, so it needs an explicit notion of "which block am I inside" to decide how to render the current line. The kind is what DescribeMember branches on (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:194): a Service scope renders rpcs (lines 198-209), an Enum scope renders values (lines 211-217), and anything else renders message fields (lines 219-227). [Rubric §9, API & Contract Design] assesses whether a published contract surface is pinned deliberately; this enum is the vocabulary that pinning is expressed in.
    • +
    • Concept: the proto contract gate parses .proto files with a hand-rolled line scanner, not a protobuf compiler, so it needs an explicit notion of "which block am I inside" to decide how to render the current line. The kind is what DescribeMember branches on (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:195): a Service scope renders rpcs (lines 198-209), an Enum scope renders values (lines 211-217), and anything else renders message fields (lines 219-227). [Rubric §9, API & Contract Design] assesses whether a published contract surface is pinned deliberately; this enum is the vocabulary that pinning is expressed in.
    • Walkthrough: TryPushScope (line 166) maps the regex-captured keyword to a member (lines 174-180), with Oneof as the discard fallback arm. Oneof exists specifically to be treated as transparent: a oneof block contributes no name segment because on the wire its members belong to the enclosing message (comment plus push, lines 182-184).
    • Where it's used: carried by ProtoScope and consumed by DescribeMember (line 194); both are private to the ArchitectureRules.Protos.cs partial.
    @@ -6982,7 +7441,7 @@

    RuleHelpers

  • Why it's built this way: every helper avoids a compile-time reference to the type it detects (base types matched by string prefix), which is what lets one rule body run identically across four repos that do not reference each other. The class carries a file-level [SuppressMessage] for CA1708 (lines 10-13): with multiple extension(T) blocks in one static class the analyzer flags the compiler-generated grouping members as case-colliding, a documented false positive.
  • Where it's used: throughout the ArchitectureRules partials, inside CrossEntityNavigationFinder, and directly by RouteAuthorizationTestsBase and AnonymousEndpointTestsBase.
  • -
  • Caveats / not-in-source: the type is internal, so consumer repos cannot call these helpers directly; they reach the same behavior only through the public rules and bases. StateManagementConventionTestsBase is the one base that re-implements the tolerant type load privately rather than using this class (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Ui/StateManagementConventionTestsBase.cs:99).
  • +
  • Caveats / not-in-source: the type is internal, so consumer repos cannot call these helpers directly; they reach the same behavior only through the public rules and bases. StateManagementConventionTestsBase is the one base that re-implements the tolerant type load privately rather than using this class (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Ui/StateManagementConventionTestsBase.cs:128).
  • LayerRef

    @@ -6997,10 +7456,10 @@

    LayerRef

    ProtoScope

    -

    MMCA.Common.Testing.Architecture · MMCA.Common.Testing.Architecture · MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:297 · Level 1 · private sealed record

    +

    MMCA.Common.Testing.Architecture · MMCA.Common.Testing.Architecture · MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:310 · Level 1 · private sealed record

      -
    • What it is: one open block in the .proto parser's scope stack: a ProtoScopeKind plus the block's name, empty for a transparent oneof (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:294-297).
    • +
    • What it is: one open block in the .proto parser's scope stack: a ProtoScopeKind plus the block's name, empty for a transparent oneof (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:307-310).
    • Depends on: ProtoScopeKind. Private to the ArchitectureRules.Protos.cs partial.
    • Concept: the two-field record is what makes nested messages and enums render under their qualified name without a real grammar. DescribeProtoFile keeps a Stack<ProtoScope> (line 112), pushes on a scope header and pops on a line starting with a closing brace (lines 124-132), and QualifiedName (line 234) reverses the stack and joins the non-empty names under the file's package.
    • Walkthrough: a two-parameter positional sealed record (line 297), constructed only in TryPushScope (line 184). Because the Oneof arm passes string.Empty as the name, the length filter in QualifiedName (line 238) drops it, which is exactly the wire semantics: a oneof member is a field of the enclosing message.
    • @@ -7037,45 +7496,47 @@

      ArchitectureMapBase

  • Why it's built this way: a per-repo map stays a flat declaration of assemblies (the two abstract members), and everything derivable is derived, so the maps cannot drift in how they compute namespaces.
  • -
  • Where it's used: each repo's concrete map subclasses this: CommonArchitectureMap (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/CommonArchitectureMap.cs:15), AdcArchitectureMap (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/AdcArchitectureMap.cs:14), StoreArchitectureMap (MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/StoreArchitectureMap.cs:8), and HelpdeskArchitectureMap (MMCA.Helpdesk/Tests/Architecture/MMCA.Helpdesk.Architecture.Tests/HelpdeskArchitectureMap.cs:8), plus the private SpecTestMap fixture inside MMCA.Common's SpecificationFitnessTests (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/SpecificationFitnessTests.cs:40). FindRepoRoot is called directly by every file-reading base and rule: DataResidencyTestsBase, FormsConventionTestsBase, FrameworkVersionConsistencyTestsBase, LocalizedTextConventionTestsBase, RawQueryableConventionTestsBase, StateManagementConventionTestsBase, UIArchitectureConventionTestsBase, the SortableColumnConventionTestsBase subclasses that build their own markup roots, and the proto rule ProtoContractsMatchFrozenList (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:45).
  • +
  • Where it's used: each repo's concrete map subclasses this: CommonArchitectureMap (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/CommonArchitectureMap.cs:15), AdcArchitectureMap (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/AdcArchitectureMap.cs:14), StoreArchitectureMap (MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/StoreArchitectureMap.cs:8), and HelpdeskArchitectureMap (MMCA.Helpdesk/Tests/Architecture/MMCA.Helpdesk.Architecture.Tests/HelpdeskArchitectureMap.cs:8), plus the private SpecTestMap fixture inside MMCA.Common's SpecificationFitnessTests (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/SpecificationFitnessTests.cs:40). FindRepoRoot is called directly by every file-reading base and rule: DataResidencyTestsBase, FormsConventionTestsBase, FrameworkVersionConsistencyTestsBase, LocalizedTextConventionTestsBase, RawQueryableConventionTestsBase, StateManagementConventionTestsBase, UIArchitectureConventionTestsBase, the SortableColumnConventionTestsBase subclasses that build their own markup roots, and the proto rule ProtoContractsMatchFrozenList (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:46).
  • ConstructorDependencyCountTestsBase

    -

    MMCA.Common.Testing.Architecture · MMCA.Common.Testing.Architecture · MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Cqrs/ConstructorDependencyCountTestsBase.cs:20 · Level 3 · abstract class

    +

    MMCA.Common.Testing.Architecture · MMCA.Common.Testing.Architecture · MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Cqrs/ConstructorDependencyCountTestsBase.cs:26 · Level 3 · abstract class

    • What it is: a single-responsibility-ceiling fitness function applied to three injection points a module composes: Application-layer *Service facades, API controllers, and CQRS command/query handlers. It fails the build if any concrete class in a population has a constructor with more than that population's accepted dependency count.
    • -
    • Depends on: IArchitectureMap (via Map.ModuleApplication() and Map.Api()), [Fact], AwesomeAssertions, and reflection over constructors and interfaces.
    • -
    • Concept introduced, quantifying the SRP smell across three populations. [Rubric §1, SOLID] assesses single-responsibility discipline; a ballooning constructor-dependency list is the canonical smell wherever it appears, and this base turns a previously implicit judgement call into an enforced, per-population ceiling so the next class cannot silently grow past it (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Cqrs/ConstructorDependencyCountTestsBase.cs:5-19). Each ceiling is a ratchet, not a budget: a consumer sets it at its current high-water mark, raises it only as a recorded deliberate decision, and lowers it the moment remediation makes a lower number true (lines 10-16).
    • -
    • Walkthrough: the subclass supplies Map (line 30), plus one abstract ceiling per population: MaxConstructorDependencies for Application services (line 37), MaxControllerConstructorDependencies for API controllers (line 44), and MaxHandlerConstructorDependencies for CQRS handlers (line 52). Three facts each follow the same shape: scan a population, assert non-vacuity, then assert no offender.
        -
      • ApplicationServices_DoNotExceedConstructorDependencyCeiling (line 55) filters Map.ModuleApplication() for concrete *Service classes inline (lines 58-62).
      • -
      • Controllers_DoNotExceedConstructorDependencyCeiling (line 105) and Handlers_DoNotExceedConstructorDependencyCeiling (line 129) both go through the shared private Scan (line 151, flat-maps ConcreteClasses, excludes nested and compiler-generated types) filtered by IsController (line 171, matches the full name Microsoft.AspNetCore.Mvc.ControllerBase via HasBaseTypeStartingWith) or IsCommandOrQueryHandler (line 174, matches a generic interface whose definition's full name starts with the ICommandHandler/IQueryHandler prefix constants, lines 188-190). All three facts assert non-vacuity before the ceiling check (lines 133-134, 109-110, 60-61) and report offenders by name and count via the shared Offenders helper (line 158) or its inline equivalent for the service check (lines 227-238).
      • -
      • Controller and handler matching is by full type-name string, not a compiled reference to Microsoft.AspNetCore.Mvc or MMCA.Common.Application, so the rule library keeps zero compile dependency on either (lines 186-187).
      • +
      • Depends on: IArchitectureMap (via Map.ModuleApplication() and Map.Api() in the default populations), [Fact], AwesomeAssertions, and reflection over constructors (System.Reflection.ConstructorInfo) and interfaces.
      • +
      • Concept introduced, quantifying the SRP smell across three populations. [Rubric §1, SOLID] assesses single-responsibility discipline; a ballooning constructor-dependency list is the canonical smell wherever it appears, and this base turns a previously implicit judgement call into an enforced, per-population ceiling so the next class cannot silently grow past it (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Cqrs/ConstructorDependencyCountTestsBase.cs:5-25). Each ceiling is a ratchet, not a budget: a consumer sets it at its current high-water mark, raises it only as a recorded deliberate decision, and lowers it the moment remediation makes a lower number true (lines 13-17). What gets measured is itself an extension point: the three populations and the constructors measured on each type are virtual, with defaults that reproduce the module scan, so a consumer that overrides nothing scans exactly the module populations (lines 18-24).
      • +
      • Walkthrough: the subclass supplies Map (line 34), plus one abstract ceiling per population: MaxConstructorDependencies for Application services (line 40), MaxControllerConstructorDependencies for API controllers (line 47), and MaxHandlerConstructorDependencies for CQRS handlers (line 55). Four virtual members say what is measured:
          +
        • ScannedServices (line 61) defaults to every concrete class whose name ends in Service in Map.ModuleApplication() (lines 62-65).
        • +
        • ScannedControllers (line 71) and ScannedHandlers (line 78) default to the shared private Scan (line 162, flat-maps ConcreteClasses, excludes nested and compiler-generated types) over Map.Api() and Map.ModuleApplication(), filtered by IsController (line 182, matches the full name Microsoft.AspNetCore.Mvc.ControllerBase via HasBaseTypeStartingWith) or IsCommandOrQueryHandler (line 185, matches a generic interface whose definition's full name starts with the ICommandHandler/IQueryHandler prefix constants, lines 186-189).
        • +
        • MeasuredConstructors(Type) (line 86) defaults to type.GetConstructors(), the public instance constructors (line 89); the widest measured constructor is the type's dependency count.
        • +
        • Three facts then share one shape: materialize the population, assert non-vacuity, assert no offender. ApplicationServices_DoNotExceedConstructorDependencyCeiling (line 93), Controllers_DoNotExceedConstructorDependencyCeiling (line 116) and Handlers_DoNotExceedConstructorDependencyCeiling (line 140) each assert non-vacuity first (lines 97-98, 120-121, 144-145) and report offenders by name and count through the shared instance helper Offenders (line 169), which calls the overridable MeasuredConstructors for each type (line 174).
        • +
        • Controller and handler matching is by full type-name string, not a compiled reference to Microsoft.AspNetCore.Mvc or MMCA.Common.Application, so the rule library keeps zero compile dependency on either (lines 28-32).
      • -
      • Why it's built this way: splitting the ceiling by population lets each stay honest: a controller is a thin translation layer over handlers, so a long injection list there means it serves more than one resource, while a handler serves exactly one use case and is the population where a long list is least defensible (lines 98-103, 122-127). It overlaps the arity check in HandlerConventionTestsBase but scopes specifically to these three facades and lets a repo pin an exact number per population rather than take a shared default. Repos without business modules (MMCA.Common itself) have nothing to scan and do not subclass this (line 58 of the summary doc).
      • -
      • Where it's used: subclassed in ADC (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Cqrs/ConstructorDependencyCountTests.cs:20) with MaxConstructorDependencies at 9 (:24, held by AuthenticationService, history at :9-18), MaxControllerConstructorDependencies at 10 (:52, held by SessionsController, history at :26-51), and MaxHandlerConstructorDependencies at 7 (:61, held jointly by ResetPasswordHandler and SubmitQuestionHandler, rationale at :54-60). Store subclasses it with a service ceiling of 8, one below ADC because Store is local-credential only and has no external-login email verifier (MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/ConstructorDependencyCountTests.cs:20, ceiling at :24, rationale at :8-18). MMCA.Helpdesk deliberately does not adopt it and records why in a comment: its Application layer is handlers-only, and the base's anti-vacuity guard fails when it finds no *Service at all (MMCA.Helpdesk/Tests/Architecture/MMCA.Helpdesk.Architecture.Tests/ArchitectureTests.cs:177-179).
      • +
      • Why it's built this way: splitting the ceiling by population lets each stay honest: a controller is a thin translation layer over handlers, so a long injection list there means it serves more than one resource, while a handler serves exactly one use case and is the population where a long list is least defensible (lines 109-114, 133-138). It overlaps the arity check in HandlerConventionTestsBase but scopes specifically to these three facades and lets a repo pin an exact number per population rather than take a shared default. Making the populations and the measured constructors virtual is what lets a repo without business modules adopt the same gate: per the summary doc, MMCA.Common overrides them to scan its framework assemblies (lines 22-23).
      • +
      • Where it's used: MMCA.Common subclasses it over its own framework in FrameworkConstructorDependencyTests (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/ConstructorDependencies/FrameworkConstructorDependencyTests.cs:25): the populations are every top-level class in the Application and Infrastructure assemblies, ControllerBase-derived classes in the API assembly, and handler implementations in the Application assembly, abstract classes included (:42-49), and MeasuredConstructors widens to public and protected constructors because the widest framework classes are abstract bases whose only constructors are protected (:51-53, rationale :8-12). Its ceilings are 7 for services (:33), 5 for controllers (:37, held by CrudEntityControllerBase, InboxController and UserAccountAuthControllerBase) and 7 for handlers (:40, held by ResetPasswordHandlerBase). The base's own behavior is pinned by ConstructorDependencyCountTestsBaseTests. It is also subclassed in ADC (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Cqrs/ConstructorDependencyCountTests.cs:20) with MaxConstructorDependencies at 9 (:24, held by AuthenticationService, history at :9-18), MaxControllerConstructorDependencies at 10 (:52, held by SessionsController, history at :26-51), and MaxHandlerConstructorDependencies at 7 (:61, held jointly by ResetPasswordHandler and SubmitQuestionHandler, rationale at :54-60). Store subclasses it with a service ceiling of 8, one below ADC because Store is local-credential only and has no external-login email verifier (MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/ConstructorDependencyCountTests.cs:20, ceiling at :24, rationale at :8-18). MMCA.Helpdesk deliberately does not adopt it and records why in a comment: its Application layer is handlers-only, and the base's anti-vacuity guard fails when it finds no *Service at all (MMCA.Helpdesk/Tests/Architecture/MMCA.Helpdesk.Architecture.Tests/ArchitectureTests.cs:177-179).

      ArchitectureRules

      MMCA.Common.Testing.Architecture · MMCA.Common.Testing.Architecture · MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Api/ArchitectureRules.Controllers.cs:3 · Level 4 · static partial class

        -
      • What it is: the reusable rule library: one large static partial class split across thirty ArchitectureRules.*.cs files, whose methods each assert one architectural invariant across every applicable assembly a map declares. A repo's test classes reduce to a sealed subclass of the matching *TestsBase supplying its own map.
      • +
      • What it is: the reusable rule library: one large static partial class split across thirty-seven ArchitectureRules.*.cs files, whose methods each assert one architectural invariant across every applicable assembly a map declares. A repo's test classes reduce to a sealed subclass of the matching *TestsBase supplying its own map.
      • Depends on: IArchitectureMap, Layer, ArchitectureAssert, RuleHelpers, CallGraphIndex, NetArchTest (Types.InAssembly(...)), Mono.Cecil plus Mono.Cecil.Cil for the IL-reading rules, System.Xml.Linq for the props-file and .resx rules, source-generated System.Text.RegularExpressions for the proto and localized-text parsers, and, for the specification rule, System.Linq.Expressions plus CrossEntityNavigationFinder.
      • -
      • Concept introduced, the rule as a parameterized function. Each method takes an IArchitectureMap (or, for the file-reading rules, a path and an allowlist) and does its own loop, so the *TestsBase classes are thin [Fact] shells that delegate. The partial is organized by concern across the files ArchitectureRules.{CancellationTokens, CascadeSoftDelete, CommandValidators, Contracts, Controllers, Cycles, DomainEventHandlerSaves, DomainThrows, Entities, ErrorCatalog, Events, FolderWidth, Governance, HandlerResults, Handlers, Idempotency, Immutability, Layers, Localization, LocalizedText, Markup, Modules, Naming, Protos, Purity, Slices, SoftDelete, Specifications, Transport, Upcasters}.cs, filed under seven concern folders (Rules/Api, Rules/Contracts, Rules/Cqrs, Rules/Domain, Rules/Governance, Rules/Layering, Rules/Ui), so the rule library follows the same feature-by-folder convention it enforces. [Rubric §3, Clean Architecture], [Rubric §4, DDD], [Rubric §7, Microservices Readiness], and [Rubric §34, Architecture Governance] all apply: this is where the codebase's structural decisions become executable assertions.
      • +
      • Concept introduced, the rule as a parameterized function. Each method takes an IArchitectureMap (or, for the file-reading rules, a path and an allowlist) and does its own loop, so the *TestsBase classes are thin [Fact] shells that delegate. The partial is organized by concern across the files ArchitectureRules.{Ai, CancellationTokens, CascadeSoftDelete, ClockReads, CommandValidators, Contracts, Controllers, Cycles, DeleteBehavior, DomainEventHandlerSaves, DomainThrows, Entities, ErrorCatalog, Events, FeatureFlags, FolderWidth, Governance, HandlerResults, Handlers, Idempotency, Immutability, IntegrationEventPurity, Layers, Localization, LocalizedText, Markup, Modules, Naming, Protos, Purity, QueryHandlerRepositories, Slices, SoftDelete, Specifications, StronglyTypedIds, Transport, Upcasters}.cs, filed under seven concern folders (Rules/Api, Rules/Contracts, Rules/Cqrs, Rules/Domain, Rules/Governance, Rules/Layering, Rules/Ui), so the rule library follows the same feature-by-folder convention it enforces. [Rubric §3, Clean Architecture], [Rubric §4, DDD], [Rubric §7, Microservices Readiness], and [Rubric §34, Architecture Governance] all apply: this is where the codebase's structural decisions become executable assertions.
      • Walkthrough: six representative shapes, each the template for a family of rules.
        • NetArchTest shape, ControllersDoNotDependOnInfrastructure (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Api/ArchitectureRules.Controllers.cs:6): loops the map's per-module API layer refs, computes the forbidden Infrastructure namespace via map.RootNamespace(...), runs a Types.InAssembly(...) chain filtered to the Controller suffix, and reports through ArchitectureAssert.NoViolations(result, ...). ControllersDoNotDependOnEntityFrameworkCore (line 22) is the same loop against the literal Microsoft.EntityFrameworkCore namespace, keeping data access out of the API layer.
        • Layer-flow shape, ArchitectureRules.Layers.cs: one public method per forbidden edge, DomainDoesNotDependOnApplication (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Layering/ArchitectureRules.Layers.cs:12) through UiDoesNotDependOnInfrastructure (line 60), all delegating to the private LayerNotDependOnLayer (line 139), which loops every assembly of the from layer and asserts no dependency on the to layer's namespace. Two non-vacuity rules sit alongside them, LayerMapDeclaresLayers (line 72) and the two ModulesDeclareLayers overloads (lines 89 and 113).
        • Reflection shape, ControllersAreSealed (ArchitectureRules.Controllers.cs:37): flat-maps map.Api() to each assembly's ConcreteClasses (the RuleHelpers extension property), filters non-sealed controllers via the private IsController (line 70, which matches on the Controller suffix or an MVC base type), and asserts the string offender list is empty. ControllersInheritApiControllerBase (line 54) is the same shape with a caller-supplied exempt set, accepting either ApiControllerBase or EntityControllerBase<TEntity, TEntityDTO, TIdentifierType> as the base.
        • Custom-rule shape, ServiceContractsDoNotDependOnServiceInternals (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Contracts.cs:32): the one rule that hands NetArchTest a MeetCustomRule predicate (line 44) reading Cecil metadata directly, because the selector is an attribute, not a name or a namespace.
        • -
        • IL-reading shape, the six rules that must see inside a method body, because neither NetArchTest nor reflection can: HardDeletesOnlyInAllowedTypes (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.SoftDelete.cs:62), DomainThrowsOnlyArgumentGuards (ArchitectureRules.DomainThrows.cs:67), the two error-catalog rules (ArchitectureRules.ErrorCatalog.cs:62, :102), AggregatesCascadeSoftDeleteToChildren (ArchitectureRules.CascadeSoftDelete.cs:100), and the transitive DomainEventHandlersDoNotSave (ArchitectureRules.DomainEventHandlerSaves.cs:76). All six open each mapped assembly with Mono.Cecil.ModuleDefinition.ReadModule over ScannableAssemblyLocations (ArchitectureRules.SoftDelete.cs:88) and match callees, base types and thrown exception types by full NAME, which is how the package keeps its zero-reference stance one level deeper than the reflection rules do.
        • +
        • IL-reading shape, the rules that must see inside a method body, because neither NetArchTest nor reflection can: HardDeletesOnlyInAllowedTypes (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.SoftDelete.cs:62), DomainThrowsOnlyArgumentGuards (ArchitectureRules.DomainThrows.cs:71), the two error-catalog rules ErrorCodesAreUnique and ErrorCodesUseAnAllowedPrefix (ArchitectureRules.ErrorCatalog.cs:62, :102), AggregatesCascadeSoftDeleteToChildren (ArchitectureRules.CascadeSoftDelete.cs:100), the transitive DomainEventHandlersDoNotSave (ArchitectureRules.DomainEventHandlerSaves.cs:76), DomainAndApplicationDoNotReadTheClock (ArchitectureRules.ClockReads.cs:56), and QueryHandlersUseReadRepositories (ArchitectureRules.QueryHandlerRepositories.cs:37). They open each mapped assembly with Mono.Cecil.ModuleDefinition.ReadModule (for example ArchitectureRules.SoftDelete.cs:73 over ScannableAssemblyLocations, line 88; ArchitectureRules.ErrorCatalog.cs:169 over its own ErrorCatalogAssemblyLocations, line 184) and match callees, base types and thrown exception types by full NAME, which is how the package keeps its zero-reference stance one level deeper than the reflection rules do.
        • Graph shape, NamespacesHaveNoDependencyCycles (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Layering/ArchitectureRules.Cycles.cs:45): builds a namespace-to-namespace graph per layer assembly from signature-level references (BuildNamespaceGraph, line 84), finds every strongly connected component (FindNamespaceCycles, line 253), and reports the shortest cycle path plus any extra members of the component. This is the largest single rule file in the library.
      • Why it's built this way: ADR-015 records the intent: the rule bodies live once here, and each repo's architecture test project is a set of sealed subclasses supplying its map, so all four repos enforce identical rules. The compile-time MMCA.Common/Source/Build/MMCA.Common.LayerEnforcement.targets guards the same layer flow at build time as a second, faster gate.
      • Where it's used: every *TestsBase in this group calls into it; those [Fact] methods are its public surface. A handful of rules are also called directly from MMCA.Common's own fitness self-tests, for example BuildProtoContract/AssertProtoContract from ProtoContractFitnessTests (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Contracts/ProtoContractFitnessTests.cs:14) and the cascade rule from CascadeSoftDeleteFitnessTests (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/CascadeSoftDeleteFitnessTests.cs:17).
      • -
      • Caveats / not-in-source: the full method roster spans thirty partials; only the entry file and representative methods are cited here. The authoritative fitness-method and base-class counts are generated into MMCA.Common/FACTS.md:45-50 and CI-gated, so read them there rather than counting by hand.
      • +
      • Caveats / not-in-source: the full method roster spans thirty-seven partials; only the entry file and representative methods are cited here. The authoritative fitness-method and base-class counts are generated into MMCA.Common/FACTS.md:45-50 and CI-gated, so read them there rather than counting by hand.

      DataResidencyTestsBase

      @@ -7085,8 +7546,11 @@

      DataResidencyTestsBase

    • What it is: a compliance-drift fitness function: the data-residency statement in a repo's PRIVACY.md must match the region where personal data is actually provisioned, and known-stale region claims must not reappear.
    • Depends on: IArchitectureMap, ArchitectureMapBase.FindRepoRoot, System.IO (File.ReadAllText), AwesomeAssertions.
    • Concept introduced, a document-versus-infrastructure consistency gate. [Rubric §30, Compliance / Privacy / Data Governance] assesses whether privacy claims track reality; this base fails the build if either the deployed region or the privacy policy changes without the other, closing the gap where a policy once claimed a region the data never lived in (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/DataResidencyTestsBase.cs:3-13).
    • -
    • Walkthrough: the subclass supplies Map (line 16), the optional ForbiddenResidencyClaims list (line 23), and implements ExtractDeployedRegion(repoRoot) (line 53) against its own source of truth (the doc cites ADC parsing the SQL region default out of deploy.yml and Store parsing infra/DISASTER-RECOVERY.md). The single [Fact] PrivacyPolicy_DataStorageRegion_MatchesDeployedRegion (line 26) locates the repo root via FindRepoRoot on the map's repo token (line 28), asserts the extracted region is non-blank (lines 31-32), reads PRIVACY.md, then asserts the normalized policy contains the region (line 37) and none of the forbidden claims (lines 40-44). Normalize (line 57) strips whitespace and upper-cases (ToUpperInvariant, per CA1308), so "West US 2" matches the "westus2" token.
    • -
    • Where it's used: subclassed in Store (MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/DataResidencyTests.cs:12) and ADC (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Governance/DataResidencyTests.cs:12). Module-less MMCA.Common has no deployed region, and Helpdesk records it as N/A for reduced-scope reasons (MMCA.Helpdesk/Tests/Architecture/MMCA.Helpdesk.Architecture.Tests/ArchitectureTests.cs:180).
    • +
    • Walkthrough: the subclass supplies Map (line 16), the optional ForbiddenResidencyClaims list (line 24), and implements ExtractDeployedRegion(repoRoot) (line 87) against its own source of truth (the doc cites ADC parsing the SQL region default out of deploy.yml and Store parsing infra/DISASTER-RECOVERY.md). The single [Fact] PrivacyPolicy_DataStorageRegion_MatchesDeployedRegion (line 27) locates the repo root via FindRepoRoot on the map's repo token (line 29), asserts the extracted region is non-blank (lines 32-33), reads PRIVACY.md (line 35), then asserts the policy states the region (line 37) and none of the forbidden claims (lines 40-44), both through ContainsRegionClaim.
        +
      • ContainsRegionClaim(policyText, regionClaim) (line 57) is a protected static whole-token matcher. It normalizes both sides with Normalize (line 93: strips whitespace and upper-cases with ToUpperInvariant, per CA1308, so "West US 2" matches the "westus2" token), then walks every occurrence and accepts one only when the next character is not a digit and the text before it does not end with one of the DirectionalPrefixes NORTH, SOUTH, EAST, WEST, CENTRAL (lines 64-79, prefixes at line 89). So westus does not match "West US 2" and centralus does not match "South Central US" (lines 47-53), which keeps a forbidden region that is a prefix of the real one from failing the gate, and a stated region that is only a prefix of another from passing it.
      • +
      +
    • +
    • Where it's used: subclassed in Store (MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/DataResidencyTests.cs:12) and ADC (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Governance/DataResidencyTests.cs:12); the matcher itself is exercised by DataResidencyTestsBaseTests in MMCA.Common (see per-project test rollup). Module-less MMCA.Common has no deployed region, and Helpdesk records it as N/A for reduced-scope reasons (MMCA.Helpdesk/Tests/Architecture/MMCA.Helpdesk.Architecture.Tests/ArchitectureTests.cs:180).

    FormsConventionTestsBase

    @@ -7150,16 +7614,17 @@

    RawSqlConventionTestsBase

    StateManagementConventionTestsBase

    -

    MMCA.Common.Testing.Architecture · MMCA.Common.Testing.Architecture · MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Ui/StateManagementConventionTestsBase.cs:17 · Level 4 · abstract class

    +

    MMCA.Common.Testing.Architecture · MMCA.Common.Testing.Architecture · MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Ui/StateManagementConventionTestsBase.cs:18 · Level 4 · abstract partial class

    • What it is: a Blazor Server state-management gate: user and session state must live in per-circuit scoped services, never in mutable static members (which leak one user's state to another) or in singleton-registered stateful services.
    • -
    • Depends on: IArchitectureMap, ArchitectureMapBase.FindRepoRoot, reflection over the UI assemblies, a Source/ file scan, and System.Runtime.CompilerServices.CompilerGeneratedAttribute.
    • -
    • Concept introduced, a reflection plus source-scan combined gate. [Rubric §19, State Management] assesses per-circuit state safety; Blazor Server shares one process across every circuit, so a static member is shared across every user (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Ui/StateManagementConventionTestsBase.cs:5-16).
    • -
    • Walkthrough: the subclass supplies Map (line 19, whose UI assemblies must be registered under Layer.Ui) and optionally AllowedStaticMembers (line 25).
        -
      • UiAssemblies_CarryNoMutableStaticState (line 28) reflects over Map.OfLayer(Layer.Ui), first asserting the set is non-empty (lines 32-33), then skipping enums, interfaces, and compiler-generated types (line 40) before flagging any declared static field that is not readonly, not const, and not compiler-generated, plus any settable static property, minus the exempted members (lines 45-56).
      • -
      • UiProjects_RegisterStatefulServicesScoped (line 66) scans Source/ .cs files (skipping obj, bin, non-UI paths, and Testing paths, lines 74-80) for a line containing both AddSingleton and a StateService or StateContainer name, recording a file name and line number as an offender (lines 85-90).
      • -
      • The private GetLoadableTypes (line 99) repeats the tolerant load locally rather than using the internal RuleHelpers, and IsCompilerGenerated (line 111) treats any member name containing an angle bracket as generated, or one carrying CompilerGeneratedAttribute.
      • +
      • Depends on: IArchitectureMap, ArchitectureMapBase.FindRepoRoot, reflection over the UI assemblies, a Source/ file scan, source-generated System.Text.RegularExpressions ([GeneratedRegex], hence partial), and System.Runtime.CompilerServices.CompilerGeneratedAttribute.
      • +
      • Concept introduced, a reflection plus source-scan combined gate. [Rubric §19, State Management] assesses per-circuit state safety; Blazor Server shares one process across every circuit, so a static member is shared across every user (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Ui/StateManagementConventionTestsBase.cs:6-17).
      • +
      • Walkthrough: the subclass supplies Map (line 20, whose UI assemblies must be registered under Layer.Ui) and optionally AllowedStaticMembers (line 26).
          +
        • UiAssemblies_CarryNoMutableStaticState (line 29) reflects over Map.OfLayer(Layer.Ui), first asserting the set is non-empty (lines 33-34), then skipping enums, interfaces, and compiler-generated types (line 41) before flagging any declared static field that is not readonly, not const, and not compiler-generated, plus any settable static property, minus the exempted members (lines 46-57).
        • +
        • UiProjects_RegisterStatefulServicesScoped (line 67) resolves the repo's Source/ directory, delegates to FindSingletonStateRegistrations (line 72), and asserts two things: that at least one UI file was scanned, so the gate cannot pass by seeing nothing (lines 74-75), and that no offender was found (lines 76-78).
        • +
        • FindSingletonStateRegistrations(sourceDir, out scannedUiFiles) (line 92) is protected static, so the scan is testable on its own. It judges each .cs path RELATIVE to sourceDir, directory segment by segment: a file counts only when one segment matches UiProjectSegment (a *.UI or *.UI.* project directory, line 121) and no segment contains Testing or is bin/obj (lines 100-106), so where the checkout lives cannot skip or include it. It then runs SingletonStateRegistration (line 126, AddSingleton\b[^;]*?State(Service|Container)\b) over the whole file text, up to the end of each statement, so the generic, factory and typeof forms and a registration wrapped across lines all match; each match becomes a File.cs:line offender computed from the match index (lines 108-114). Both [GeneratedRegex] properties carry a 1000 ms match timeout (lines 120, 125).
        • +
        • The private GetLoadableTypes (line 128) repeats the tolerant load locally rather than using the internal RuleHelpers, and IsCompilerGenerated (line 140) treats any member name containing an angle bracket as generated, or one carrying CompilerGeneratedAttribute.
      • Where it's used: subclassed in the repos with Blazor UI assemblies: MMCA.Common (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Ui/StateManagementConventionTests.cs:11), Store (MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/StateManagementConventionTests.cs:10), and ADC (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Ui/StateManagementConventionTests.cs:9).
      • @@ -7310,7 +7775,7 @@

        DeleteBehaviorConventionTestsBase

      • What it is: a cascading-delete convention base (ADR-119): a foreign key that cascades must say so explicitly in code, and every foreign key left to convention must resolve to a restrictive delete.
      • Depends on: ArchitectureRules.CascadingForeignKeysAreExplicitlyOptedIn and ArchitectureRules.ConventionForeignKeysRestrictDeletes. Unlike most bases in this family it takes no IArchitectureMap; the subclass instead supplies the finalized EF Core model directly.
      • -
      • Concept introduced, judging a live model instead of IL. The base's Model is typed object on purpose: the testing package deliberately carries no EF Core reference (doc, lines 28-29), so the subclass passes dbContext.Model and the rule inspects it through reflection rather than a compile-time EF Core dependency. This pairs with RestrictDeleteByDefaultConvention, the model-finalizing convention that makes DeleteBehavior.Restrict the default for every relationship unless a mapping calls .OnDelete(DeleteBehavior.Cascade) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/RestrictDeleteByDefaultConvention.cs:10-19,100): a convention-created cascade is EF Core's silent default, and an accidental one deletes a subtree nobody meant to remove. [Rubric §8, Data Architecture] assesses deliberate, reviewable delete semantics; [Rubric §4, DDD] assesses whether aggregate boundaries are respected by cascading relationships.
      • +
      • Concept introduced, judging a live model instead of IL. The base's Model is typed object on purpose: the testing package deliberately carries no EF Core reference (doc, lines 28-29), so the subclass passes dbContext.Model and the rule inspects it through reflection rather than a compile-time EF Core dependency. This pairs with RestrictDeleteByDefaultConvention, the model-finalizing convention that makes DeleteBehavior.Restrict the default for every relationship unless a mapping calls .OnDelete(DeleteBehavior.Cascade) (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Conventions/RestrictDeleteByDefaultConvention.cs:11-20,101): a convention-created cascade is EF Core's silent default, and an accidental one deletes a subtree nobody meant to remove. [Rubric §8, Data Architecture] assesses deliberate, reviewable delete semantics; [Rubric §4, DDD] assesses whether aggregate boundaries are respected by cascading relationships.
      • Walkthrough: two [Fact]s, both driven by the one abstract Model property (line 31): CascadingDeletes_AreExplicitlyOptedIn (line 33) fails on a foreign key that cascades but carries no marker recording the opt-in; ConventionDeletes_AreRestricted (line 36) fails on a foreign key left at EF Core's own convention default that resolves to anything other than Restrict.
      • Where it's used: subclassed in ADC (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Domain/DeleteBehaviorConventionTests.cs) and in MMCA.Common itself (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/DeleteBehaviorConventionTests.cs), each passing its own finalized model.
      @@ -7362,12 +7827,12 @@

      DomainThrowTestsBase

    • What it is: a Result-pattern purity gate (ADR-013): the domain returns Result, it does not throw. Any throw in the map's Domain assemblies whose exception is not an argument guard fails the build.
    • -
    • Depends on: IArchitectureMap and ArchitectureRules.DomainThrowsOnlyArgumentGuards (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.DomainThrows.cs:67), which reads IL with Mono.Cecil.
    • -
    • Concept introduced, why a thrown business failure is a defect and not a style choice. It skips Result.Combine invariant composition, arrives at the API as a 500 instead of the outcome the caller can act on, and pays an exception unwind on a path that is not exceptional (doc, lines 4-9; rule doc at ArchitectureRules.DomainThrows.cs:23-29). The three argument guards stay allowed because they report a caller BUG rather than a business outcome, and a caller cannot recover from passing null, so there is nothing for a Result to carry (:11-21). [Rubric §4, DDD] and [Rubric §15, Best Practices & Code Quality] assess the error-handling model; [Rubric §9, API & Contract Design] assesses the status code a caller actually sees.
    • +
    • Depends on: IArchitectureMap and ArchitectureRules.DomainThrowsOnlyArgumentGuards (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Domain/ArchitectureRules.DomainThrows.cs:71), which reads IL with Mono.Cecil.
    • +
    • Concept introduced, why a thrown business failure is a defect and not a style choice. It skips Result.Combine invariant composition, arrives at the API as a 500 instead of the outcome the caller can act on, and pays an exception unwind on a path that is not exceptional (doc, lines 4-9; rule doc at ArchitectureRules.DomainThrows.cs:26-32). The three argument guards stay allowed because they report a caller BUG rather than a business outcome, and a caller cannot recover from passing null, so there is nothing for a Result to carry (:11-21). [Rubric §4, DDD] and [Rubric §15, Best Practices & Code Quality] assess the error-handling model; [Rubric §9, API & Contract Design] assesses the status code a caller actually sees.
    • Walkthrough
      • The subclass supplies Map (line 23) and optionally AllowedThrowingTypes (line 31, empty by default, which allows nothing beyond the argument guards).
      • The single [Fact] Domain_ShouldNotThrow_ExceptArgumentGuards (line 34) forwards both to the rule.
      • -
      • The rule opens each Domain assembly with ModuleDefinition.ReadModule over DomainAssemblyLocations (ArchitectureRules.DomainThrows.cs:107) and, for every throw instruction, reads the type of the exception constructed immediately before it (ConstructedExceptionType, line 143).
      • +
      • The rule opens each Domain assembly with ModuleDefinition.ReadModule over DomainAssemblyLocations (ArchitectureRules.DomainThrows.cs:111) and, for every throw instruction, reads the type of the exception constructed immediately before it (ConstructedExceptionType, line 143).
      • Three things it deliberately does not touch (doc, lines 39-48): a bare rethrow compiles to a distinct opcode and is ignored, so preserving a caught exception stays free; the ArgumentNullException.ThrowIfNull family emits a plain call with no throw in the caller, so the modern guard style always passes; and compiler-written bodies are skipped (the skeleton members of a C# extension block, and the explicit interface implementations on a compiler-generated type), because their exceptions are not anyone's code.
      • When the thrown value was not constructed in place (a prepared local, a field, a factory call), the type is not knowable from the instruction stream. Those sites are reported as UNVERIFIABLE in the failure message rather than passed or failed (doc, lines 49-54; WithUnverifiable composes the message at ArchitectureRules.ErrorCatalog.cs:151).
      @@ -7495,18 +7960,20 @@

      IntegrationEventContractTestsBase

      MMCA.Common.Testing.Architecture · MMCA.Common.Testing.Architecture · MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Contracts/IntegrationEventContractTestsBase.cs:18 · Level 5 · abstract class

      -
    • What it is: a frozen wire-contract guard: it rebuilds the live integration-event contract (one line per event, the full name followed by its members in braces) and compares it to a committed snapshot the subclass supplies, so a renamed, removed, or retyped property, or a new event shipped without its consumer, fails the build.
    • -
    • Depends on: IArchitectureMap, ArchitectureRules.BuildIntegrationEventContract (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Events.cs:45), and ArchitectureAssert.
    • +
    • What it is: a frozen wire-contract guard: it rebuilds the live integration-event contract (one line per event, a key followed by its members in braces, where the key is the event's [EventName] value when it declares one and its full type name otherwise) and compares it to a committed snapshot the subclass supplies, so a renamed, removed, or retyped property, or a new event shipped without its consumer, fails the build.
    • +
    • Depends on: IArchitectureMap, ArchitectureRules.BuildIntegrationEventContract (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Events.cs:50), and ArchitectureAssert.
    • Concept introduced, the snapshot fitness function, and why it compares as a SET. [Rubric §9, API & Contract Design] and [Rubric §7, Microservices Readiness] assess whether cross-service contracts stay stable; because a consumer in another service deserializes by shape, this gate makes any contract change a deliberate, coordinated commit. The member list inside each event's braces is compared as a set rather than a sequence (doc, lines 10-16), and the reasoning is worth reading: JSON carries no member order, so reordering two properties in a record's declaration changes nothing a consumer can observe, and failing the build for it would train the one gate that guards real breakage to be updated by rote. Everything observable stays a failure: a missing member, an extra member, a changed type, and any change to the set of events itself.
    • Walkthrough
        -
      • The subclass supplies Map (line 20) and the committed ExpectedContract snapshot (line 23).
      • -
      • IntegrationEventContracts_ShouldMatch_TheFrozenSnapshot (line 26) builds the actual contract (line 28) and funnels the differences through ArchitectureAssert with a message instructing the author to version the event, coordinate the consumer rollout, and update ExpectedContract in the same commit (lines 30-35).
      • -
      • Compare (line 46) parses both sides and reports three classes of difference: an event the committed contract declares and the code no longer does (line 53), a NEW event shipped without a consumer rollout or a snapshot update (line 56), and per-event member differences (line 59).
      • -
      • CompareMembers (line 73) reports a same-named member with a different type as a retype rather than as an unrelated add and remove, because that is the failure a consumer actually sees: the property still deserializes, into the wrong shape (doc, lines 64-68). Missing and extra members are reported separately (lines 82, 94).
      • -
      • Parse (line 106) turns contract lines into an event-to-members map, and keeps a line that does not carry the braced shape whole as an event with no members, so a malformed committed literal surfaces as a mismatch rather than being silently dropped (doc, lines 99-103). Braces are stripped from that key deliberately, because the reported difference is formatted through the shared assertion helper and a stray brace in a reason string is a format hazard (comment, lines 116-117).
      • +
      • The subclass supplies Map (line 20) and the committed ExpectedContract snapshot (line 33), whose lines take the shape Key { Prop:Type, ... } (doc, lines 30-31).
      • +
      • SnapshotOutputVariable (line 27) names the environment variable MMCA_CONTRACT_SNAPSHOT_OUT. When it is set to a file path, the fact writes the live contract there as ready-to-paste C# string literals, one per line, before comparing (lines 40-44). It only regenerates the snapshot text: the comparison still runs and still fails on drift (doc, lines 23-25).
      • +
      • IntegrationEventContracts_ShouldMatch_TheFrozenSnapshot (line 36) builds the actual contract (line 38) and funnels the differences through ArchitectureAssert with a message instructing the author to version the event, coordinate the consumer rollout, and update ExpectedContract in the same commit, and pointing at the environment variable as the way to get the live contract as literals (lines 46-52).
      • +
      • Compare (line 63) parses both sides and reports three classes of difference: an event the committed contract declares and the code no longer does (line 70), a NEW event shipped without a consumer rollout or a snapshot update (line 73), and per-event member differences (line 76).
      • +
      • CompareMembers (line 90) reports a same-named member with a different type as a retype rather than as an unrelated add and remove, because that is the failure a consumer actually sees: the property still deserializes, into the wrong shape (doc, lines 82-84). Missing and extra members are reported separately (lines 99, 111).
      • +
      • Parse (line 123) turns contract lines into an event-to-members map, and keeps a line that does not carry the braced shape whole as an event with no members, so a malformed committed literal surfaces as a mismatch rather than being silently dropped (doc, lines 117-119). Braces are stripped from that key deliberately, because the reported difference is formatted through the shared assertion helper and a stray brace in a reason string is a format hazard (comment, lines 133-134).
      • +
      • SplitTopLevel (line 163) splits a member list only on commas outside angle brackets, tracking the bracket depth (line 170), so a multi-argument generic member type such as IReadOnlyDictionary<String, Int32> stays one member rather than being cut in two (doc, lines 158-159); Parse calls it for every braced line (line 142).
    • -
    • Where it's used: subclassed in the repos publishing integration events: Store (MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/IntegrationEventContractTests.cs:3), ADC (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Contracts/IntegrationEventContractTests.cs:3), and Helpdesk, whose one-line snapshot is a compact worked example (MMCA.Helpdesk/Tests/Architecture/MMCA.Helpdesk.Architecture.Tests/ArchitectureTests.cs:99, snapshot at :105-108). It complements EventConventionTestsBase and has a synchronous counterpart in ProtoContractTestsBase.
    • +
    • Where it's used: subclassed in the repos publishing integration events: Store (MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/Contracts/IntegrationEventContractTests.cs:3), ADC (MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Contracts/IntegrationEventContractTests.cs:3), and Helpdesk, whose one-line snapshot is a compact worked example (MMCA.Helpdesk/Tests/Architecture/MMCA.Helpdesk.Architecture.Tests/ArchitectureTests.cs:99, snapshot at :105-108). It complements EventConventionTestsBase and has a synchronous counterpart in ProtoContractTestsBase.

    IntegrationEventPayloadPurityTestsBase

    @@ -7628,7 +8095,7 @@

    PiiConventionTestsBase

  • Depends on: IArchitectureMap, ArchitectureRules.EntitiesWithPiiImplementAnonymizable (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Governance/ArchitectureRules.Governance.cs:11).
  • Concept: cross-references the delegating-base shape (AggregateConventionTestsBase); the [Pii] plus IAnonymizable soft-delete-versus-erasure model is taught in Group 02 (ADR-005). [Rubric §30, Compliance / Privacy / Data Governance] and [Rubric §11, Security] assess erasure discipline. The IAnonymizable contract is matched by its full name (ArchitectureRules.Governance.cs:7) so a same-named local interface cannot satisfy the rule (comment, lines 5-6), while the [Pii] marker is matched by simple name (:50).
  • Walkthrough: one [Fact] EntitiesWithPiiProperties_ShouldImplement_IAnonymizable (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/PiiConventionTestsBase.cs:12) delegating to the rule, which scans every Domain-layer type for a [Pii]-marked public instance property (HasPiiProperty, ArchitectureRules.Governance.cs:48) and reports the ones that do not implement the contract.
  • -
  • Where it's used: subclassed in all four repos (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/PiiConventionTests.cs:13, MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/PiiConventionTests.cs:3, MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Governance/PiiConventionTests.cs:3, and Helpdesk at MMCA.Helpdesk/Tests/Architecture/MMCA.Helpdesk.Architecture.Tests/ArchitectureTests.cs:116).
  • +
  • Where it's used: subclassed in all four repos (MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/PiiConventionTests.cs:20, MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/PiiConventionTests.cs:3, MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Governance/PiiConventionTests.cs:3, and Helpdesk at MMCA.Helpdesk/Tests/Architecture/MMCA.Helpdesk.Architecture.Tests/ArchitectureTests.cs:116).
  • ProtoContractTestsBase

    @@ -7636,12 +8103,12 @@

    ProtoContractTestsBase

    • What it is: the frozen wire-contract guard for a repo's gRPC .proto files, the synchronous counterpart to IntegrationEventContractTestsBase. The subclass names its solution file, its proto files, and the committed snapshot; the base rebuilds the live contract and reports the diff.
    • -
    • Depends on: ArchitectureRules.ProtoContractsMatchFrozenList (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:37), which resolves paths through ArchitectureMapBase.FindRepoRoot (line 45) and parses through ProtoScope / ProtoScopeKind. There is no Map on this base: it reads committed .proto text, not assemblies.
    • -
    • Concept introduced, pinning a contract by what actually crosses the wire. The rule's remarks are explicit about the two halves of the decision (ArchitectureRules.Protos.cs:19-35). Pinned: the file's package, every service with each rpc (name, request type, response type, and both streaming flags), every message field (name, declared type, label, and field NUMBER), and every enum value and number, with nested messages and enums under their qualified name. Deliberately not pinned: syntax, import lines, and option declarations including csharp_namespace, because none of them changes a byte on the wire; including them would fail the gate on edits that break nobody, which is the fastest way to teach a team to update the snapshot without reading it. [Rubric §9, API & Contract Design] and [Rubric §7, Microservices Readiness] assess contract stability across a service boundary.
    • +
    • Depends on: ArchitectureRules.ProtoContractsMatchFrozenList (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Contracts/ArchitectureRules.Protos.cs:38), which resolves paths through ArchitectureMapBase.FindRepoRoot (line 45) and parses through ProtoScope / ProtoScopeKind. There is no Map on this base: it reads committed .proto text, not assemblies.
    • +
    • Concept introduced, pinning a contract by what actually crosses the wire. The rule's remarks are explicit about the two halves of the decision (ArchitectureRules.Protos.cs:20-36). Pinned: the file's package, every service with each rpc (name, request type, response type, and both streaming flags), every message field (name, declared type, label, and field NUMBER), and every enum value and number, with nested messages and enums under their qualified name. Deliberately not pinned: syntax, import lines, and option declarations including csharp_namespace, because none of them changes a byte on the wire; including them would fail the gate on edits that break nobody, which is the fastest way to teach a team to update the snapshot without reading it. [Rubric §9, API & Contract Design] and [Rubric §7, Microservices Readiness] assess contract stability across a service boundary.
    • Walkthrough
      • Three abstract members: SolutionFileName (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Contracts/ProtoContractTestsBase.cs:22, marking the repo root), ProtoFiles (line 25, repo-root-relative paths), and FrozenProtoContracts (line 30, the committed snapshot).
      • The single [Fact] ProtoContracts_ShouldMatch_TheFrozenSnapshot (line 33) forwards all three to the rule.
      • -
      • The comparison is two-directional: AssertProtoContract (ArchitectureRules.Protos.cs:58) reports lines present in the protos but not frozen with a plus marker and frozen lines missing from the protos with a minus marker (lines 67-70), then funnels both through ArchitectureAssert. A .proto path that does not exist yields one explicit missing-file line rather than silently contributing nothing (line 96), and the whole snapshot is distinct-and-ordered so the comparison is stable (line 103).
      • +
      • The comparison is two-directional: AssertProtoContract (ArchitectureRules.Protos.cs:59) reports lines present in the protos but not frozen with a plus marker and frozen lines missing from the protos with a minus marker (lines 67-70), then funnels both through ArchitectureAssert. A .proto path that does not exist yields one explicit missing-file line rather than silently contributing nothing (line 96), and the whole snapshot is distinct-and-ordered so the comparison is stable (line 103).
    • Why it's built this way: the remarks say the snapshot is meant to be regenerated deliberately by printing ArchitectureRules.BuildProtoContract(...) for the same files, as part of the commit that changes the contract, never edited to make a red test go green (Bases/ProtoContractTestsBase.cs:12-17). MMCA.Common ships no .proto of its own (it supplies the gRPC plumbing, not the contracts), so the framework does NOT subclass this (lines 9-11).
    • @@ -7792,7 +8259,7 @@

      AuthOutcome

    • Depends on: nothing; a plain internal enum, reachable from the framework's own gallery suite through the InternalsVisibleTo in the package's csproj (MMCA.Common.Testing.E2E/MMCA.Common.Testing.E2E.csproj:12).
    • Concept introduced, naming the silent failure. Two of the three members are the obvious outcomes. The third, Silent, is the one worth teaching: a submit that produced neither a navigation nor a rendered error alert (a 500 that renders nothing, a dropped request, a JS exception mid-submit) is not a success and not a reported failure, and it needs its own name precisely so the wait can refuse to treat it as either (doc, :12-13). [Rubric §14, Testability] assesses whether a harness can tell "passed" from "never actually ran"; making the third state a first-class enum member is how this package does that.
    • Walkthrough: each member carries a doc comment stating the signal combination behind it: the page navigated away or a signed-in state is showing (:6-7), the auth page is still showing with an error alert on it (:9-10), or none of the three signals fired (:12-13).
    • -
    • Where it's used: returned by AuthOutcomeRules.Classify and branched on inside E2ETestBase.WaitForAuthResultAsync (MMCA.Common.Testing.E2E/Infrastructure/E2ETestBase.cs:272, :253), and asserted directly by AuthOutcomeRulesTests.
    • +
    • Where it's used: returned by AuthOutcomeRules.Classify and branched on inside E2ETestBase.WaitForAuthResultAsync (MMCA.Common.Testing.E2E/Infrastructure/E2ETestBase.cs:279, :253), and asserted directly by AuthOutcomeRulesTests.

    AxeOptions

    @@ -7808,18 +8275,18 @@

    AxeOptions

  • Why it's built this way: shipping the options in the package rather than re-declaring them per test guarantees every consumer scans the identical rule set; the narrowly scoped pager exception keeps one known third-party gap from forcing a blanket rule-disable across all scans.
  • -
  • Where it's used: passed to PageExtensions.AssertNoAccessibilityViolationsAsync through E2ETestBase.ScanAsync (strict Wcag21Aa, MMCA.Common.Testing.E2E/Infrastructure/E2ETestBase.cs:368) and .ScanGridAsync (the pager exception, :329), and directly by the *_ShouldHaveNoAccessibilityViolations facts on UserLoginTestsBase (MMCA.Common.Testing.E2E/Workflows/Identity/UserLoginTestsBase.cs:82), UserRegistrationTestsBase (MMCA.Common.Testing.E2E/Workflows/Identity/UserRegistrationTestsBase.cs:95), ProfileManagementTestsBase (MMCA.Common.Testing.E2E/Workflows/Identity/ProfileManagementTestsBase.cs:178), and PasswordResetTestsBase (MMCA.Common.Testing.E2E/Workflows/Identity/PasswordResetTestsBase.cs:88, :99).
  • +
  • Where it's used: passed to PageExtensions.AssertNoAccessibilityViolationsAsync through E2ETestBase.ScanAsync (strict Wcag21Aa, MMCA.Common.Testing.E2E/Infrastructure/E2ETestBase.cs:375) and .ScanGridAsync (the pager exception, :329), and directly by the *_ShouldHaveNoAccessibilityViolations facts on UserLoginTestsBase (MMCA.Common.Testing.E2E/Workflows/Identity/UserLoginTestsBase.cs:82), UserRegistrationTestsBase (MMCA.Common.Testing.E2E/Workflows/Identity/UserRegistrationTestsBase.cs:95), ProfileManagementTestsBase (MMCA.Common.Testing.E2E/Workflows/Identity/ProfileManagementTestsBase.cs:191), and PasswordResetTestsBase (MMCA.Common.Testing.E2E/Workflows/Identity/PasswordResetTestsBase.cs:88, :99).
  • E2EPolling

    MMCA.Common.Testing.E2E · MMCA.Common.Testing.E2E.Infrastructure · MMCA.Common.Testing.E2E/Infrastructure/E2EPolling.cs:18 · Level 0 · static class

      -
    • What it is: a generic poll-until helper with two overloads, one that returns the last probed value of type T and one that returns a plain bool, both looping a probe until a condition holds or a deadline passes (MMCA.Common.Testing.E2E/Infrastructure/E2EPolling.cs:25, :77).
    • +
    • What it is: a generic poll-until helper with two overloads, one that returns the last probed value of type T and one that returns a plain bool, both looping a probe until a condition holds or a deadline passes (MMCA.Common.Testing.E2E/Infrastructure/E2EPolling.cs:39, :72).
    • Depends on: System.Diagnostics.Stopwatch (BCL, Stopwatch.GetTimestamp/GetElapsedTime) for the deadline arithmetic; no Playwright type appears in the signature, so it is reusable against any async probe, a page reload, a locator read, an HTTP call.
    • -
    • Concept introduced, asserting on the real failure, not on a bare timeout. The doc on PollUntilAsync<T> states the design choice directly: the method always returns the last probed value, timeout or not, so a caller that times out still gets a real value to assert on and fails with that assertion's message rather than with an opaque "poll timed out" (E2EPolling.cs:34-36). [Rubric §14, Testability] assesses whether a failure explains itself; returning the last value instead of throwing on timeout is what makes that possible here.
    • -
    • Walkthrough: DefaultTimeout is 30 seconds and DefaultInterval is 500 ms (:28, :31). PollUntilAsync<T>(probe, isSatisfied, timeout, interval, cancellationToken) (:45-51) null-checks probe and isSatisfied (:52-53), computes the effective budget and delay from the optional parameters or the two defaults (:55-56), probes once up front, then loops Task.Delay plus a re-probe while the condition is unmet and there is still budget left for one more full interval (:59-64), and returns the last probed value either way (:66). The bare-bool overload (:77-82) is a one-line forward to the generic overload with isSatisfied fixed to identity (static present => present).
    • -
    • Why it's built this way: checking Stopwatch.GetElapsedTime(started) + delay < budget rather than just < budget (:60) avoids starting one more delay-and-probe cycle that cannot possibly land before the deadline, so the method does not overrun its stated timeout by a near-full interval.
    • +
    • Concept introduced, asserting on the real failure, not on a bare timeout. The doc on PollUntilAsync<T> states the design choice directly: the method always returns the last probed value, timeout or not, so a caller that times out still gets a real value to assert on and fails with that assertion's message rather than with an opaque "poll timed out" (E2EPolling.cs:26-30). [Rubric §14, Testability] assesses whether a failure explains itself; returning the last value instead of throwing on timeout is what makes that possible here.
    • +
    • Walkthrough: DefaultTimeout is 30 seconds and DefaultInterval is 500 ms (:21, :24). PollUntilAsync<T>(probe, isSatisfied, timeout, interval, cancellationToken) (:39-44) null-checks probe and isSatisfied (:46-47), computes the effective budget and delay from the optional parameters or the two defaults (:49-50), probes once up front, then loops Task.Delay plus a re-probe while the condition is unmet and there is still budget left for one more full interval (:53-58), and returns the last probed value either way (:60). The bare-bool overload (:72-77) is a one-line forward to the generic overload with isSatisfied fixed to identity (static present => present). Both overloads carry a SuppressMessage for RS0026 (multiple public overloads with optional parameters), justified as grandfathered public API released after the v1.152 baseline while RS0026/RS0027 were off, so changing either signature would be a breaking change (:38, :71).
    • +
    • Why it's built this way: checking Stopwatch.GetElapsedTime(started) + delay < budget rather than just < budget (:54) avoids starting one more delay-and-probe cycle that cannot possibly land before the deadline, so the method does not overrun its stated timeout by a near-full interval.
    • Where it's used: by ADC's SpeakerSelfServiceTests (MMCA.ADC/Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/Conference/Speaker/SpeakerSelfServiceTests.cs, 3 call sites) and 2 more sites in the ADC E2E suite.

    E2ETestConfiguration

    @@ -7869,8 +8336,8 @@

    ProfilePage

    • What it is: the Page Object for the authenticated /profile screen, exposing the name, address, and password sections' fields and buttons as named locators.
    • -
    • Depends on: Microsoft.Playwright and PageExtensions.BlazorNavigateAsync (MMCA.Common.Testing.E2E/PageObjects/ProfilePage.cs:1-2).
    • -
    • Concept: the Page Object Model taught in LoginPage. One difference is load-bearing: GotoAsync uses BlazorNavigateAsync("/profile"), client-side routing (ProfilePage.cs:34-35), not a full page load, because /profile is [Authorize] and server-side rendering cannot read the JWT from browser storage, so a full load would bounce to /login. [Rubric §28, Front-End Testing] and [Rubric §11, Security] both apply: exercising the authenticated page correctly requires respecting the client-token boundary.
    • +
    • Depends on: Microsoft.Playwright and PageExtensions.GotoProtectedAsync (MMCA.Common.Testing.E2E/PageObjects/ProfilePage.cs:1-2, :35).
    • +
    • Concept: the Page Object Model taught in LoginPage. One difference is load-bearing: GotoAsync uses GotoProtectedAsync("/profile") (ProfilePage.cs:34-35), client-side routing rather than a full page load, because /profile is [Authorize] and server-side rendering cannot read the JWT from browser storage, so a full load would bounce to /login. GotoProtectedAsync first makes sure the Blazor runtime is loaded (loading / if it is not), re-routes through / when already on another route so the target always gets a fresh component lifecycle, then calls BlazorNavigateAsync (MMCA.Common.Testing.E2E/Infrastructure/PageExtensions.cs:160-191). [Rubric §28, Front-End Testing] and [Rubric §11, Security] both apply: exercising the authenticated page correctly requires respecting the client-token boundary.
    • Walkthrough: three commented locator groups. Name (FirstNameField, LastNameField, SaveNameButton, :12-15); address (six fields plus SaveAddressButton, :17-24); password (CurrentPasswordField, NewPasswordField located with Exact = true so it does not also match "Confirm New Password", ConfirmNewPasswordField, ChangePasswordButton, :26-30). ErrorAlert is located by ARIA alert role rather than a MudBlazor class (:32). GotoAsync is the client-side navigation described above (:34-35).
    • Where it's used: by ProfileManagementTestsBase for all six of its facts. ADC's own ProfileManagementTests covers the same screen without deriving from the shared base or using this Page Object (MMCA.ADC/Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/Identity/ProfileManagementTests.cs:8).
    @@ -7915,7 +8382,7 @@

    RoleAdminPage

    • What it is: the Page Object for the role-administration screens: the /roles roster list and the per-role /roles/{role} permission editor, exposing the roster table, the per-role edit link, the editor's permission checkboxes and save button, and a one-call SetPermissionAsync action.
    • Depends on: Microsoft.Playwright (IPage, ILocator, Assertions) and PageExtensions.GotoProtectedAsync (MMCA.Common.Testing.E2E/PageObjects/RoleAdminPage.cs:106, :108, :120, :123).
    • -
    • Concept: the Page Object Model taught in LoginPage. Two comments carry the load-bearing detail. PermissionCheckbox targets input[data-permission='{permission}'] because MudCheckBox splats unmatched attributes straight onto its own input, so the permission name lands on the checkbox element itself rather than a wrapper (RoleAdminPage.cs:130-136). SetPermissionAsync sets the checkbox with Force = true because MudBlazor renders the real input at zero opacity underneath its icon button, which would otherwise fail Playwright's actionability check on a control a real user can click fine (:145-148). [Rubric §28, Front-End Testing] applies as with every Page Object here; [Rubric §12, Roles/Permissions] applies because the screen under test is the role-permission editor itself.
    • +
    • Concept: the Page Object Model taught in LoginPage. Two comments carry the load-bearing detail. PermissionCheckbox targets input[data-permission='{permission}'] because MudCheckBox splats unmatched attributes straight onto its own input, so the permission name lands on the checkbox element itself rather than a wrapper (RoleAdminPage.cs:44-50). SetPermissionAsync sets the checkbox with Force = true because MudBlazor renders the real input at zero opacity underneath its icon button, which would otherwise fail Playwright's actionability check on a control a real user can click fine (:59-62). [Rubric §28, Front-End Testing] applies as with every Page Object here; [Rubric §12, Roles/Permissions] applies because the screen under test is the role-permission editor itself.
    • Walkthrough: constructor takes the IPage (:108). RoleTable is .mud-table, the signal that the roster loaded rather than failed (:111); EditLinks is every edit-role test-id (:114); SaveButton is the save-permissions test-id (:117). GotoListAsync/GotoEditorAsync navigate to /roles and /roles/{role} through GotoProtectedAsync (:119-123). RowByRole(role) filters the roster rows by text (:127-128). PermissionCheckbox(permission) locates the data-attributed input (:135-136). SetPermissionAsync(permission, granted) force-checks the box, clicks save, then waits (15 second timeout) for the "Stored permissions saved." snackbar, because the save runs on the server side of the Blazor circuit and the click alone proves nothing (:143-156).
    • Why it's built this way: the doc on the save wait records a concrete failure mode it closes: navigating away before the PUT lands tears the circuit down, cancels the request, and the Identity service logs the write as "Operation cancelled by user"; a consumer deploy run once lost the write on all three tries before this wait was added (:151-154).
    • Where it's used: by ADC's RoleAdministrationTests (MMCA.ADC/Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/Identity/RoleAdministrationTests.cs, 4 call sites) and AccessibilityTests (MMCA.ADC/Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/AccessibilityTests.cs, 2 call sites).
    • @@ -7952,7 +8419,7 @@

      AuthOutcomeRules

    • Concept introduced, extracting the decision out of the wait. The surrounding wait is inherently browser-bound (three racing Playwright waits), which makes its verdict hard to test. Pulling the classification into a parameterless-of-browser function makes the interesting case, the silent failure, assertable in a unit test with no server that has to be staged into failing without rendering anything. The type doc spells out the bug this closes: a submit that failed with neither a navigation nor a rendered error alert used to leave the wait returning normally, the caller's follow-up interactivity wait was already satisfied by the still-rendered auth page, and login and registration then reported success on a sign-in that never happened (MMCA.Common.Testing.E2E/Infrastructure/AuthOutcomeRules.cs:20-26). [Rubric §14, Testability] assesses whether logic is separable from the infrastructure it runs on; [Rubric §15, Best Practices & Code Quality] applies because the precedence rule now lives in one readable place instead of inside a Task.WhenAny continuation.
    • Walkthrough: one method. Classify(bool navigatedAway, bool errorAlertVisible, bool logoutVisible) (:38) returns Succeeded when the page navigated away from the auth page OR the signed-in state's logout control is showing (:40-43), and otherwise ErrorShown if an error alert is visible, Silent if not (:45). The precedence is deliberate and documented (:30-34): a completed forceLoad is unambiguous, so an error alert flashed on the way out is not a failure, and a visible logout button is the same verdict reached through interactivity instead of through navigation.
    • Why it's built this way: the E2E package deliberately keeps its surface small, so this stays internal and is opened to exactly one assembly through InternalsVisibleTo on MMCA.Common.UI.E2E.Tests (MMCA.Common.Testing.E2E/MMCA.Common.Testing.E2E.csproj:12), which is the framework's own gallery test project. That is the narrow version of "make it testable" rather than widening a shipped public API for a test.
    • -
    • Where it's used: called once, by E2ETestBase.WaitForAuthResultAsync (MMCA.Common.Testing.E2E/Infrastructure/E2ETestBase.cs:267-270), and covered directly by six browser-free facts in AuthOutcomeRulesTests (MMCA.Common/Tests/Presentation/MMCA.Common.UI.E2E.Tests/Auth/AuthOutcomeRulesTests.cs:15, :22, :29, :34, :39, :44).
    • +
    • Where it's used: called once, by E2ETestBase.WaitForAuthResultAsync (MMCA.Common.Testing.E2E/Infrastructure/E2ETestBase.cs:274-277), and covered directly by six browser-free facts in AuthOutcomeRulesTests (MMCA.Common/Tests/Presentation/MMCA.Common.UI.E2E.Tests/Auth/AuthOutcomeRulesTests.cs:15, :22, :29, :34, :39, :44).
    • Caveats / not-in-source: both the class doc and the test class describe the rule as a "four-way classification" (AuthOutcomeRules.cs:18, AuthOutcomeRulesTests.cs:8), while AuthOutcome declares three members; the code is the authority here.

    PlaywrightFixture

    @@ -7995,7 +8462,7 @@

    WebVitalsBudget

    PageExtensions

    -

    MMCA.Common.Testing.E2E · MMCA.Common.Testing.E2E.Infrastructure · MMCA.Common.Testing.E2E/Infrastructure/PageExtensions.cs:23 · Level 1 · static class

    +

    MMCA.Common.Testing.E2E · MMCA.Common.Testing.E2E.Infrastructure · MMCA.Common.Testing.E2E/Infrastructure/PageExtensions.cs:23 · Level 2 · static class

    • What it is: the interactivity toolbox of the E2E package: C# extension(T) members over Playwright's IPage and ILocator that wait for Blazor to become interactive, navigate its InteractiveAuto pages correctly, fill and click through the re-hydration race, drive a MudDataGrid list page (search, delete-confirm, settle), and run an axe-core accessibility scan.
    • @@ -8005,11 +8472,11 @@

      PageExtensions

    • The constants. MudDataGridRowSelector is the public default data-row selector for a MudDataGrid in table layout (:29). BlazorRuntimeLoadedPredicate probes for any truthy window.Blazor._internal and is shared by the wait and by GotoProtectedAsync's readiness probe so the two cannot drift (:31-39). InteractiveMarkerPredicate probes for the data-mmca-interactive attribute (:47-48). TimedSettleScript is the marker-free settle, two animation frames plus a 500 ms delay, for a click that may land on a page with no marker at all (:50-57); SingleFrameSettleScript is the one-frame flush used once the marker has already confirmed interactivity (:60).
    • extension(IPage page) (:62). WaitForBlazorAsync runs the two-phase wait then flushes one frame (:85-97). GotoAndWaitForBlazorAsync navigates, waits for LoadState.Load rather than NetworkIdle (which never settles under a persistent SignalR socket), then waits for interactivity (:103-110). BlazorNavigateAsync drives Blazor's client-side router through Blazor.navigateTo, tolerating the context-destroyed race a forceLoad can cause, then polls window.location.pathname instead of WaitForURLAsync (whose default Load wait hangs on a same-document navigation) and re-asserts interactivity, retrying once if that itself races a reload (:118-151). GotoProtectedAsync reaches an [Authorize] page by first ensuring Blazor is up (loading a public page when it is not) and re-routing through / so the target always gets a fresh component lifecycle, then client-navigating (:160-191). WaitForPageAndBlazorAsync covers a full-page navigation's load-plus-timed-settle (:197-206). AssertNoAccessibilityViolationsAsync runs RunAxe (with optional AxeOptions), returns early on zero violations, and otherwise builds a per-node summary and throws AccessibilityViolationException (:307-332).
    • The list-page trio, also on IPage. SearchAndWaitForRowAsync settles the grid, fills the search field through FillAndVerifyAsync, then waits for a row containing the term (:233-249); its remarks record why each of the three steps replaced a hand-rolled per-page copy, one of which slept a fixed 1.5 seconds (:208-228). ConfirmDeleteAsync clicks a delete affordance, confirms the dialog by data-testid rather than by a MudBlazor filled-button class (which silently changed meaning when a dialog gained a second filled action), and settles the resulting grid reload (:266-285, rationale at :251-262). WaitForGridToSettleAsync waits for zero [role='progressbar'] elements and deliberately does NOT wait for a row, because an empty result set is a legitimate state (:287-299).
    • -
    • extension(ILocator locator) (:335). FillAndVerifyAsync fills, then auto-waits ToHaveValueAsync, and if the value was wiped by re-hydration it clears the field, re-types character by character with a 20 ms delay, and re-asserts (:347-366). This is the single shared fill helper the base and the Page Objects all call. ClickAndVerifyAsync waits for interactivity, then clicks and waits a third of the timeout for the expected effect, up to three clicks in total, so a genuinely applied click is never re-issued and only a no-op click is retried (:380-411). ClickAndWaitForUrlAsync clicks a navigating link and re-clicks until the URL matches the supplied regular expression, for grid rows whose cells wrap content in MudLink so a row-center click lands on padding (:423-446).
    • -
    • The private CompactHtml collapses a violating node's markup to one trimmed line, truncated at 220 characters, so the failure message points at the exact offending element (:455-464).
    • +
    • extension(ILocator locator) (:335). FillAndVerifyAsync fills, then auto-waits ToHaveValueAsync, and if the value was wiped by re-hydration it clears the field, re-types character by character with a 20 ms delay, and re-asserts (:347-366). This is the single shared fill helper the base and the Page Objects all call. ClickAndVerifyAsync waits for interactivity, then clicks and waits a third of the timeout for the expected effect, up to three clicks in total (maxAttempts = 3, :390), the last of which lets the assertion throw with full diagnostics (:381-412). An effect slower than timeout / 3 is treated as a swallowed click and re-issued, so a non-idempotent submit that can take longer than that must be called with a larger timeout (remarks at :373-379). ClickAndWaitForUrlAsync clicks a navigating link and re-clicks until the URL matches the supplied regular expression, for grid rows whose cells wrap content in MudLink so a row-center click lands on padding (:424-447).
    • +
    • The private CompactHtml collapses a violating node's markup to one trimmed line, truncated at 220 characters, so the failure message points at the exact offending element (:456-465).
    -
  • Why it's built this way: the fill and click helpers exist because InteractiveAuto's prerender-then-hydrate model makes a bare fill or click a race on a fast host; auto-waiting assertions with a bounded re-type or re-click are strictly safer than fixed delays, since they succeed as soon as the value or effect appears. The interactivity marker is the honest gate, so a page that never stamps it fails the wait rather than proceeding to click dead controls (:82-83). Two [SuppressMessage] attributes document analyzer false positives across the extension(T) boundary: CA1708 on the class, where the compiler-generated grouping members read as case-colliding (:15-18), and IDE0051 for the private constants and for CompactHtml, which the SDK 10.0.201+ analyzer cannot see being referenced from inside an extension block (:19-22, :451-454).
  • +
  • Why it's built this way: the fill and click helpers exist because InteractiveAuto's prerender-then-hydrate model makes a bare fill or click a race on a fast host; auto-waiting assertions with a bounded re-type or re-click are strictly safer than fixed delays, since they succeed as soon as the value or effect appears. The interactivity marker is the honest gate, so a page that never stamps it fails the wait rather than proceeding to click dead controls (:82-83). Two [SuppressMessage] attributes document analyzer false positives across the extension(T) boundary: CA1708 on the class, where the compiler-generated grouping members read as case-colliding (:15-18), and IDE0051 for the private constants and for CompactHtml, which the SDK 10.0.201+ analyzer cannot see being referenced from inside an extension block (:19-22, :452-455).
  • Where it's used: throughout the Page Objects (ForgotPasswordPage, LoginPage, ProfilePage, RegisterPage, ResetPasswordPage), inside E2ETestBase (FillFieldAsync, ScanAsync, ScanGridAsync, the navigation helpers), by WebVitalsPageExtensions, and directly by every workflow base in this group. The list-page trio is what the consumer Page Objects are built on: Store's ProductListPage and CategoryListPage and ADC's session, speaker, room, event, question, category and user list pages all delegate their search and delete to it (for example MMCA.Store/Tests/E2E/MMCA.Store.E2E.Tests/PageObjects/ProductListPage.cs:21, :42, and MMCA.ADC/Tests/E2E/MMCA.ADC.E2E.Tests/PageObjects/Conference/Sessions/SessionListPage.cs:39, :51).
  • E2ETestCollection

    @@ -8046,13 +8513,13 @@

    E2ETestBase

  • Concept introduced, the per-test browser context. The fixture launches one browser; this base opens a new IBrowserContext (an isolated cookie and storage jar) per test in InitializeAsync and disposes it in DisposeAsync, so tests cannot leak session state into each other. It is the E2E analogue of the integration-test base's per-test database reset. [Rubric §28, Front-End Testing] assesses realistic, isolated UI tests; [Rubric §21, Accessibility] applies through the scan helpers; [Rubric §14, Testability] through the shared, correctly sequenced auth helpers every workflow reuses. The auth-result handling also touches [Rubric §22, Responsive/Cross-Browser], since the same waits must survive Server-mode and WASM render timing on any engine.
  • Walkthrough: teaching order.
    • Lifecycle. The class carries [Collection(E2ETestCollection.Name)] (:8), the fixture arrives through the constructor, and the current Page is a protected property (:11-18). InitializeAsync creates a context with IgnoreHTTPSErrors and the base URL, sets the default timeout from configuration, optionally starts trace capture with screenshots, snapshots, and sources when TracePath is set, and opens the Page (:20-38). DisposeAsync stops tracing, closes the page, and disposes the context, each step guarded by a null check because a failed InitializeAsync leaves the fields null and the resulting NullReferenceException would otherwise mask the real setup error (:40-62). The private StopTracingAsync (:68-90) carries the per-test trace policy: a plain file path keeps the single-file behavior, while a directory path writes a trace named after the current test only when that test failed (TestContext.Current.TestState?.Result == TestResult.Failed, :79-85), so a full-suite run yields just the failing traces with no overwriting.
    • -
    • Auth entry points. LoginAsAdminAsync and LoginAsUserAsync (:92-96) delegate to LoginAsync with the AdminCredentials and UserCredentials pair. LoginAsync (:98-145) first clears any existing session when a sign-out button is visible, removing the auth_access_token and auth_refresh_token localStorage entries and issuing a DELETE /auth/session-cookie fetch, guarded against the context-destroyed race from an in-flight logout (:106-122); it then navigates to /login, fills through FillFieldAsync, clicks, and awaits WaitForAuthResultAsync followed by WaitForInteractiveOrReloadAsync. SignOutAsync (:162-173) clicks the sign-out button and then waits for the browser to actually land on /login, matching against the shared cached LoginUrlPattern regex on a 15 second budget (:175-176); a PlaywrightException whose message carries NS_BINDING_ABORTED repeats the same wait once. RegisterNewUserAsync (:178-211) generates a unique e2e-{id}@test.com email with the fixed password TestPass123!, fills the register form, submits, runs the same two post-auth waits, and returns the created credentials.
    • -
    • Post-auth robustness. WaitForInteractiveOrReloadAsync (:224-235) waits for interactivity and, on either a PlaywrightException or a TimeoutException, reloads once and re-waits rather than watching the same stuck boot; the comment records why both exception types are caught (Playwright's TimeoutException derives from System.TimeoutException, not PlaywrightException, so an earlier single catch skipped the retry entirely) and why a reload beats a re-wait (the framework assets are now HTTP-cached, :213-223). WaitForAuthResultAsync (:245-299) starts three signals through Task.WhenAny, leaving the auth page, the logout button appearing, or an error alert appearing (:250-257), observes the two losers so their timeout faults never resurface as unobserved-task exceptions in an unrelated test (:263-265, helper at :303-308), and then classifies the settled state through AuthOutcomeRules.Classify (:267-270). Succeeded returns; the other two outcomes get one grace window (:279), after which ErrorShown throws an InvalidOperationException carrying the alert text (:284-288) and Silent throws one naming the auth path, the total budget and the current URL (:294-298). AuthSucceededWithinGraceAsync (:313-331) implements that window, falling back to the logout-button signal when no navigation occurs.
    • -
    • Helpers. NavigateAndWaitAsync (:333-334), the shared static FillFieldAsync delegating to PageExtensions.FillAndVerifyAsync (:341-342), UniqueId (:344), and the two scan helpers. ScanGridAsync (:355-361) waits for a visible data row and for zero [role='progressbar'] elements, then scans with AxeOptions.Wcag21AaExceptMudPagerCombobox; ScanAsync (:365-369) applies the progressbar guard only and scans strictly with Wcag21Aa.
    • +
    • Auth entry points. LoginAsAdminAsync and LoginAsUserAsync (:92-96) delegate to LoginAsync with the AdminCredentials and UserCredentials pair. LoginAsync (:98-145) first clears any existing session when a sign-out button is visible, removing the auth_access_token and auth_refresh_token localStorage entries and issuing a DELETE /auth/session-cookie fetch, guarded against the context-destroyed race from an in-flight logout (:106-122); it then navigates to /login, fills through FillFieldAsync, clicks, and awaits WaitForAuthResultAsync followed by WaitForInteractiveOrReloadAsync. SignOutAsync (:165-176) clicks the sign-out button and then waits for the browser to actually land on /login, matching against the shared cached LoginUrlPattern regex on a 15 second budget (:182-183); a PlaywrightException that the private IsSupersededNavigation recognizes, by a message carrying either NS_BINDING_ABORTED or Navigation canceled by policy check (:178-180), repeats the same wait once. RegisterNewUserAsync (:185-218) generates a unique e2e-{id}@test.com email with the fixed password TestPass123!, fills the register form, submits, runs the same two post-auth waits, and returns the created credentials.
    • +
    • Post-auth robustness. WaitForInteractiveOrReloadAsync (:231-242) waits for interactivity and, on either a PlaywrightException or a TimeoutException, reloads once and re-waits rather than watching the same stuck boot; the comment records why both exception types are caught (Playwright's TimeoutException derives from System.TimeoutException, not PlaywrightException, so an earlier single catch skipped the retry entirely) and why a reload beats a re-wait (the framework assets are now HTTP-cached, :220-230). WaitForAuthResultAsync (:252-306) starts three signals through Task.WhenAny, leaving the auth page, the logout button appearing, or an error alert appearing (:257-264), observes the two losers so their timeout faults never resurface as unobserved-task exceptions in an unrelated test (:270-272, helper at :310-315), and then classifies the settled state through AuthOutcomeRules.Classify (:274-277). Succeeded returns; the other two outcomes get one grace window (:286), after which ErrorShown throws an InvalidOperationException carrying the alert text (:291-295) and Silent throws one naming the auth path, the total budget and the current URL (:301-305). AuthSucceededWithinGraceAsync (:320-338) implements that window, falling back to the logout-button signal when no navigation occurs.
    • +
    • Helpers. NavigateAndWaitAsync (:340-341), the shared static FillFieldAsync delegating to PageExtensions.FillAndVerifyAsync (:348-349), UniqueId (:351), and the two scan helpers. ScanGridAsync (:362-368) waits for a visible data row and for zero [role='progressbar'] elements, then scans with AxeOptions.Wcag21AaExceptMudPagerCombobox; ScanAsync (:372-376) applies the progressbar guard only and scans strictly with Wcag21Aa.
    • Dark-theme helpers. SeedDarkThemeAsync adds an mmca_theme=dark cookie to the browser context, read by the Common UI's theme.js on the next page load. WaitForDarkModeAsync then waits for the theme toggle's accessible name to flip to "Switch to light mode", the signal that ThemeService has initialized from the cookie and MudThemeProvider has re-rendered with the dark palette; because the layout renders the toggle twice (the app bar and the mobile nav top row), the wait targets the desktop cluster's copy (.appbar-icon-actions .First), and the expected name is the English resource, matching the default E2E culture.
  • -
  • Why it's built this way: the auth helpers encode hard-won timing knowledge once (the forceLoad reload, the Server-versus-WASM hydration lag, the cookie-and-localStorage dual session store), so every consumer workflow inherits a deterministic sign-in instead of re-deriving the races. Clearing both token stores is essential: the Blazor Server host is cookie-only, so a localStorage clear alone would leave the next login authenticated as the wrong user (:100-105). Sign-out is a base helper for the same reason: a workflow that clicked the button and then waited for LoadState.Load was waiting on the CURRENT document, whose load event had already fired, so the next goto died with "execution context was destroyed" or "interrupted by another navigation"; the URL wait fixes that once for every caller, and the NS_BINDING_ABORTED retry covers Firefox, which surfaces the abandoned first request when the logout forceLoad is superseded mid-flight by the app's own redirect onto /login (chromium and webkit never raise it, :147-161). The scan split lets grid pages accept the documented pager-combobox exception while every other page stays strict, and the grid wait keys off a data row rather than the loading bar hiding, which would resolve instantly before the transient unnamed progressbar even appears (:346-354). SeedDarkThemeAsync sets the cookie directly instead of driving the UI toggle so a workflow test can start already in dark mode without spending a page interaction on it; WaitForDarkModeAsync is a separate call, not folded into the seed helper, because the caller must navigate between seeding the cookie and checking the applied theme.
  • +
  • Why it's built this way: the auth helpers encode hard-won timing knowledge once (the forceLoad reload, the Server-versus-WASM hydration lag, the cookie-and-localStorage dual session store), so every consumer workflow inherits a deterministic sign-in instead of re-deriving the races. Clearing both token stores is essential: the Blazor Server host is cookie-only, so a localStorage clear alone would leave the next login authenticated as the wrong user (:100-105). Sign-out is a base helper for the same reason: a workflow that clicked the button and then waited for LoadState.Load was waiting on the CURRENT document, whose load event had already fired, so the next goto died with "execution context was destroyed" or "interrupted by another navigation"; the URL wait fixes that once for every caller, and the one-shot retry covers the engines that report the superseded navigation as an error: Firefox surfaces the abandoned first request as NS_BINDING_ABORTED when the logout forceLoad is superseded mid-flight by the app's own redirect onto /login, and WebKit raises the same supersession as "Navigation canceled by policy check" now that sign-out runs through the same-origin API proxy, while chromium raises neither (remarks at :152-164). The scan split lets grid pages accept the documented pager-combobox exception while every other page stays strict, and the grid wait keys off a data row rather than the loading bar hiding, which would resolve instantly before the transient unnamed progressbar even appears (:353-361). SeedDarkThemeAsync sets the cookie directly instead of driving the UI toggle so a workflow test can start already in dark mode without spending a page interaction on it; WaitForDarkModeAsync is a separate call, not folded into the seed helper, because the caller must navigate between seeding the cookie and checking the applied theme.
  • Where it's used: the base class of all eight workflow bases in this unit (AuthorizationTestsBase, LogoutTestsBase, PasswordResetTestsBase, ProfileManagementTestsBase, PseudoLocalizationTestsBase, UserLoginTestsBase, UserPreferencesTestsBase, UserRegistrationTestsBase) and, through them and directly, every E2E test class in the ADC and Store suites (for example ADC's own ProfileManagementTests, which derives from this base rather than the shared profile workflow, MMCA.ADC/Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/Identity/ProfileManagementTests.cs:8).
  • WebVitalsPageExtensions

    @@ -8118,8 +8585,8 @@

    ProfileManagementTestsBase

  • What it is: the reusable profile workflow base. It verifies that name, address, and password changes persist, that the profile page loads pre-filled with the registered data, an opt-in email-change journey, and that the profile page is accessibility-clean.
  • Depends on: E2ETestBase, ProfilePage, PageExtensions, AxeOptions, AwesomeAssertions, and Microsoft.Playwright (MMCA.Common.Testing.E2E/Workflows/Identity/ProfileManagementTestsBase.cs:1-6).
  • Concept: the authored-once workflow base taught in AuthorizationTestsBase, here driving a ProfilePage. [Rubric §24, Forms/Validation/UX Safety] assesses whether edit-and-persist journeys work end to end; [Rubric §21, Accessibility] applies through the a11y fact.
  • -
  • Walkthrough: one virtual switch, ProfileSupportsEmailChange, off by default (:23). Six facts follow. ChangeName_ShouldUpdateProfileName clears and fills both name fields, saves, re-navigates, and asserts the values persisted (:26-53); ChangeAddress_ShouldUpdateProfileAddress does the same for the five address fields and asserts on line 1 (:55-78). Both use Playwright's plain FillAsync rather than the re-hydration-safe helper, since the profile page is reached by client-side navigation on an already interactive runtime. ChangePassword_WithValidCurrentPassword_ShouldSucceed fills the three password fields through the shared FillFieldAsync, waits for the "Password changed successfully." snackbar, then signs out through the base's SignOutAsync and logs back in with the new password (:80-109, sign-out at :105). ChangeEmail_ShouldUpdateEmail is opt-in and returns immediately unless ProfileSupportsEmailChange is overridden true (:111-145). ProfilePage_ShouldLoadWithUserData asserts the form is pre-filled from registration (:147-165). ProfilePage_ShouldHaveNoAccessibilityViolations scans with AxeOptions.Wcag21Aa (:167-180).
  • -
  • Why it's built this way: the email-change fact is a declared opt-in rather than a DOM probe because the previous probing version passed vacuously when the field was absent, reporting coverage for a journey the app does not offer; overriding the flag makes a missing field fail loud (:17-22, :127-129). The sign-out-then-login step delegates to E2ETestBase.SignOutAsync instead of clicking and waiting inline, so this workflow inherits both halves of the fix (the /login URL wait rather than LoadState.Load, and the Firefox NS_BINDING_ABORTED retry) from one place; the retained comment records why the load-state wait raced the in-flight logout navigation (:99-104).
  • +
  • Walkthrough: one virtual switch, ProfileSupportsEmailChange, off by default (:23), and three virtual snackbar texts, NameSavedMessage ("Name updated successfully."), AddressSavedMessage and EmailSavedMessage (:30, :33, :36), which a consumer whose profile page words its confirmations differently overrides. Six facts follow. ChangeName_ShouldUpdateProfileName clears and fills both name fields, saves, waits up to 30 seconds for the NameSavedMessage snackbar (:56), re-navigates, and asserts the values persisted (:39-66); ChangeAddress_ShouldUpdateProfileAddress does the same for the five address fields, waiting on AddressSavedMessage (:83), and asserts on line 1 (:68-91). Both use Playwright's plain FillAsync rather than the re-hydration-safe helper, since the profile page is reached by client-side navigation on an already interactive runtime. ChangePassword_WithValidCurrentPassword_ShouldSucceed fills the three password fields through the shared FillFieldAsync, waits for the "Password changed successfully." snackbar, then signs out through the base's SignOutAsync and logs back in with the new password (:93-122, sign-out at :118). ChangeEmail_ShouldUpdateEmail is opt-in and returns immediately unless ProfileSupportsEmailChange is overridden true; when it runs, it waits on EmailSavedMessage before reloading (:124-158, wait at :150). ProfilePage_ShouldLoadWithUserData asserts the form is pre-filled from registration (:160-178). ProfilePage_ShouldHaveNoAccessibilityViolations scans with AxeOptions.Wcag21Aa (:180-193).
  • +
  • Why it's built this way: each save waits for its confirmation snackbar rather than LoadState.Load because navigating away while the save is still in flight cancels the write, so without the wait the read-back could run before the save persisted (:25-29). The email-change fact is a declared opt-in rather than a DOM probe because the previous probing version passed vacuously when the field was absent, reporting coverage for a journey the app does not offer; overriding the flag makes a missing field fail loud (:17-22, :126-129). The sign-out-then-login step delegates to E2ETestBase.SignOutAsync instead of clicking and waiting inline, so this workflow inherits both halves of the fix (the /login URL wait rather than LoadState.Load, and the one-shot retry on a superseded navigation that Firefox and WebKit report as an error) from one place; the retained comment records why the load-state wait raced the in-flight logout navigation (:112-117).
  • Where it's used: subclassed only by Store, with no additions and no override of ProfileSupportsEmailChange (MMCA.Store/Tests/E2E/MMCA.Store.E2E.Tests/Workflows/Identity/ProfileManagementTests.cs:5), so the email-change fact passes without exercising a journey Store offers. ADC does not subclass this base: its profile page supports the avatar photo, password change, and account deletion but no name or address editing, so MMCA.ADC.E2E.Tests writes its own ProfileManagementTests directly on E2ETestBase with a password-change fact mirroring this one, a claims-page fact, and an avatar upload-replace-remove round trip that builds its two PNGs from base64 constants so the test needs no fixture file on disk (MMCA.ADC/Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/Identity/ProfileManagementTests.cs:8, :26, :56, :70, constants at :13-16).
  • PseudoLocalizationTestsBase

    @@ -8176,6 +8643,18 @@

    UserRegistrationTestsBase

  • Why it's built this way: the mismatched-passwords fact submits once and asserts the field-level validation text rather than re-clicking, because the [Compare] validation fires OnInvalidSubmit and a re-clicking helper would make the message flicker out from under the wait; the text is asserted (not the alert element) because it renders in both the Server-mode and WebAssembly paths, while the page-level alert appears only on the Server-mode prerender path (:43-47, :56-60).
  • Where it's used: subclassed in both consumer E2E suites (MMCA.Store/Tests/E2E/MMCA.Store.E2E.Tests/Workflows/Identity/UserRegistrationTests.cs:5, MMCA.ADC/Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/Identity/UserRegistrationTests.cs:5).
  • +

    IsAuthenticatedAuthorizationService

    +
    +

    MMCA.Common.Testing.UI · MMCA.Common.Testing.UI · MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:176 · Level 0 · private sealed nested class

    +
    +
      +
    • What it is: the permissive-but-real IAuthorizationService that BunitComponentTestBase registers, so any policy succeeds for an authenticated principal and fails for an anonymous one.
    • +
    • Depends on: Microsoft.AspNetCore.Authorization (IAuthorizationService, AuthorizationResult, IAuthorizationRequirement, MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:3) and BCL ClaimsPrincipal. Nested privately inside its only consumer.
    • +
    • Concept introduced, collapsing policy evaluation to the authentication bit. A component test cares whether the authorized branch of <AuthorizeView> renders, not whether a named policy's requirement handlers are wired; re-hosting the real policy provider in every test project would couple UI tests to each app's policy catalogue. This double answers by identity alone, which keeps the render assertion honest about the branch while staying agnostic about policy names. [Rubric §11, Security] assesses how authorization is expressed and enforced; note the deliberate inversion here, this double grants every policy, so it proves a page renders when signed in and never proves a policy actually denies. [Rubric §14, Testability] assesses how cheaply a component renders in isolation; one class replaces a whole policy graph.
    • +
    • Walkthrough: two members, both IAuthorizationService overloads. The requirements overload (:178-182) returns AuthorizationResult.Success() when user.Identity?.IsAuthenticated == true and AuthorizationResult.Failed() otherwise, wrapped in Task.FromResult so no async machinery is allocated. The policy-name overload (:184-185) delegates to the first with an empty requirement collection, so a component gated on a named policy takes the same identity-only decision.
    • +
    • Where it's used: registered as a singleton IAuthorizationService in the BunitComponentTestBase constructor (:57), immediately after AddAuthorizationCore() (:56), so it wins over the core default for every derived component test.
    • +
    • Caveats / not-in-source: role-based <AuthorizeView Roles="..."> is evaluated by the Blazor component itself against the principal's role claims (which TestPrincipal supplies), not by this service, so role gating still discriminates in a component test while policy gating does not.
    • +

    BunitInteractionExtensions

    MMCA.Common.Testing.UI · MMCA.Common.Testing.UI · MMCA.Common.Testing.UI/Infrastructure/BunitInteractionExtensions.cs:12 · Level 0 · static class

    @@ -8196,10 +8675,10 @@

    BunitInteractionExtensions

    CapturedRequest

    -

    MMCA.Common.Testing.UI · MMCA.Common.Testing.UI · MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:158 · Level 0 · sealed record

    +

    MMCA.Common.Testing.UI · MMCA.Common.Testing.UI · MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:189 · Level 0 · sealed record

      -
    • What it is: the immutable snapshot of one HTTP request a UI service sent, recorded by CapturingHttpMessageHandler so a test can assert on what went out, not only on what came back (MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:154-157).
    • +
    • What it is: the immutable snapshot of one HTTP request a UI service sent, recorded by CapturingHttpMessageHandler so a test can assert on what went out, not only on what came back (MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:185-188).
    • Depends on: BCL only, System.Net.Http.HttpMethod, System.Uri, and IReadOnlyDictionary<string, string>. No first-party types.
    • Concept introduced, capture-then-assert on the outbound side. A canned-response fake proves the service handles a given payload; it says nothing about whether the service built the right URL, attached the bearer token, sent the right If-Match, or serialized the right body. Recording every request as a value object makes those assertions possible without a server. [Rubric §14, Testability] assesses how much of a component's contract can be verified in isolation; capturing the request turns the client's outbound contract (route, query string, headers, body) into something a unit test can pin. [Rubric §9, API and Contract Design] applies indirectly: these captures are where a UI service's assumed route and header shape is asserted against the API's actual one.
    • Walkthrough: six positional members plus one init member.
        @@ -8241,18 +8720,6 @@

        FreshApiClientFactory

      • Why it's built this way: ignoring the client name keeps the double usable from any repo without knowing the named-client key the service under test resolves, while the shared handler keeps one capture log per test.
      • Where it's used: exposed as UiHttpServiceHarness.ClientFactory (:47,58) and returned from HttpTestDoubles.ClientFactory (HttpTestDoubles.cs:23-24); the fresh-instance contract and the shared base address are asserted directly in MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Infrastructure/SharedHttpTestDoublesTests.cs:22,27-28,38.
      -

      IsAuthenticatedAuthorizationService

      -
      -

      MMCA.Common.Testing.UI · MMCA.Common.Testing.UI · MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:176 · Level 0 · private sealed nested class

      -
      -
        -
      • What it is: the permissive-but-real IAuthorizationService that BunitComponentTestBase registers, so any policy succeeds for an authenticated principal and fails for an anonymous one.
      • -
      • Depends on: Microsoft.AspNetCore.Authorization (IAuthorizationService, AuthorizationResult, IAuthorizationRequirement, MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:3) and BCL ClaimsPrincipal. Nested privately inside its only consumer.
      • -
      • Concept introduced, collapsing policy evaluation to the authentication bit. A component test cares whether the authorized branch of <AuthorizeView> renders, not whether a named policy's requirement handlers are wired; re-hosting the real policy provider in every test project would couple UI tests to each app's policy catalogue. This double answers by identity alone, which keeps the render assertion honest about the branch while staying agnostic about policy names. [Rubric §11, Security] assesses how authorization is expressed and enforced; note the deliberate inversion here, this double grants every policy, so it proves a page renders when signed in and never proves a policy actually denies. [Rubric §14, Testability] assesses how cheaply a component renders in isolation; one class replaces a whole policy graph.
      • -
      • Walkthrough: two members, both IAuthorizationService overloads. The requirements overload (:178-182) returns AuthorizationResult.Success() when user.Identity?.IsAuthenticated == true and AuthorizationResult.Failed() otherwise, wrapped in Task.FromResult so no async machinery is allocated. The policy-name overload (:184-185) delegates to the first with an empty requirement collection, so a component gated on a named policy takes the same identity-only decision.
      • -
      • Where it's used: registered as a singleton IAuthorizationService in the BunitComponentTestBase constructor (:57), immediately after AddAuthorizationCore() (:56), so it wins over the core default for every derived component test.
      • -
      • Caveats / not-in-source: role-based <AuthorizeView Roles="..."> is evaluated by the Blazor component itself against the principal's role claims (which TestPrincipal supplies), not by this service, so role gating still discriminates in a component test while policy gating does not.
      • -

      MarkupSnapshotResult

      MMCA.Common.Testing.UI · MMCA.Common.Testing.UI · MMCA.Common.Testing.UI/Infrastructure/MarkupSnapshot.cs:104 · Level 0 · readonly record struct

      @@ -8266,10 +8733,10 @@

      MarkupSnapshotResult

    MudProviderHandles

    -

    MMCA.Common.Testing.UI · MMCA.Common.Testing.UI · MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:157 · Level 0 · protected sealed nested record

    +

    MMCA.Common.Testing.UI · MMCA.Common.Testing.UI · MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:162 · Level 0 · protected sealed nested record

      -
    • What it is: the three handles returned by BunitComponentTestBase.RenderMudProviders(), one per rendered MudBlazor provider, so a test can query the popover, dialog, or snackbar markup after triggering it (MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:156).
    • +
    • What it is: the three handles returned by BunitComponentTestBase.RenderMudProviders(), one per rendered MudBlazor provider, so a test can query the popover, dialog, or snackbar markup after triggering it (MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:161).
    • Depends on: bUnit's IRenderedComponent<T> and MudBlazor's MudPopoverProvider, MudDialogProvider, and MudSnackbarProvider (:157-160, imports at :2,11-12).
    • Concept: the provider-root problem, taught with BunitComponentTestBase. MudBlazor renders overlays into provider components that normally live in the app layout; in a component test there is no layout, so a dialog or snackbar has nowhere to go. Rendering the providers as separate roots gives them somewhere, and this record is how the test keeps a reference to each root to query it afterwards. [Rubric §20, Design System and Theming] assesses how the shared component library is adopted and exercised; overlay behavior is only testable once the design system's provider contract is honored in the test host.
    • Walkthrough: a positional record with Popover, Dialog, and Snackbar (:157-160), constructed once in RenderMudProviders() after the three Render<T>() calls (:150-153). Dialog is the handle a test queries for MudMessageBox confirm buttons; Snackbar is the one it queries for toast text.
    • @@ -8277,18 +8744,18 @@

      MudProviderHandles

    MutableAuthenticationStateProvider

    -

    MMCA.Common.Testing.UI · MMCA.Common.Testing.UI · MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:162 · Level 0 · private sealed nested class

    +

    MMCA.Common.Testing.UI · MMCA.Common.Testing.UI · MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:167 · Level 0 · private sealed nested class

    • What it is: the AuthenticationStateProvider BunitComponentTestBase registers, holding one principal that the test can swap at any point, with change notification to listeners.
    • Depends on: Microsoft.AspNetCore.Components.Authorization (AuthenticationStateProvider, AuthenticationState, :5) and BCL ClaimsPrincipal.
    • -
    • Concept introduced, the two ways a Blazor page learns who the user is. Some components read the cascading AuthenticationState value; others inject AuthenticationStateProvider and call GetAuthenticationStateAsync() themselves. A hardcoded-anonymous provider serves neither well once a test needs the signed-in branch, so the base supplies a mutable one and drives both routes from the same principal. The class remark says exactly this: the provider is mutable because it is "a superset of a hardcoded-anonymous one" (:24-27). [Rubric §19, State Management] assesses how shared client state is owned and propagated; auth state is the canonical cascading state, and this is the test-side owner of it.
    • -
    • Walkthrough: a primary-constructor class taking the initial principal (:162), storing it in a mutable _principal field (:164). SetPrincipal(ClaimsPrincipal) (:166-170) assigns the field and then calls the base NotifyAuthenticationStateChanged with a fresh AuthenticationState, so an already-rendered <AuthorizeView> re-evaluates rather than keeping its first answer. GetAuthenticationStateAsync() (:172-173) returns Task.FromResult(new AuthenticationState(_principal)), so no async state machine is allocated per call.
    • -
    • Where it's used: instantiated as the base's _authProvider field seeded with Anonymous (:42), registered as a singleton AuthenticationStateProvider (:58), and mutated by SetUser (:121) and by every RenderAs call before rendering (:135).
    • +
    • Concept introduced, the two ways a Blazor page learns who the user is. Some components read the cascading AuthenticationState value; others inject AuthenticationStateProvider and call GetAuthenticationStateAsync() themselves. A hardcoded-anonymous provider serves neither well once a test needs the signed-in branch, so the base supplies a mutable one and drives both routes from the same principal. The class remark says exactly this: the provider is mutable because it is "a superset of a hardcoded-anonymous one" (:24-27). This provider is the only auth double left in the harness: the authorization decision itself is made by the framework's real DefaultAuthorizationService (:62), which evaluates whatever principal this provider holds, so the principal (and its role claims) is the whole of what a test controls. [Rubric §19, State Management] assesses how shared client state is owned and propagated; auth state is the canonical cascading state, and this is the test-side owner of it.
    • +
    • Walkthrough: a primary-constructor class taking the initial principal (:167), storing it in a mutable _principal field (:169). SetPrincipal(ClaimsPrincipal) (:171-175) assigns the field and then calls the base NotifyAuthenticationStateChanged with a fresh AuthenticationState, so an already-rendered <AuthorizeView> re-evaluates rather than keeping its first answer. GetAuthenticationStateAsync() (:177-178) returns Task.FromResult(new AuthenticationState(_principal)), so no async state machine is allocated per call.
    • +
    • Where it's used: instantiated as the base's _authProvider field seeded with Anonymous (:42), registered as a singleton AuthenticationStateProvider (:63), and mutated by SetUser (:126) and by every RenderAs call before rendering (:140). It has no usage outside the defining file, being a private nested type.

    Route

    -

    MMCA.Common.Testing.UI · MMCA.Common.Testing.UI · MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:139 · Level 0 · private sealed nested record

    +

    MMCA.Common.Testing.UI · MMCA.Common.Testing.UI · MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:170 · Level 0 · private sealed nested record

    • What it is: one registered canned response inside CapturingHttpMessageHandler: an HTTP method, an absolute path, a status code, an optional JSON body, and the method that turns all four into a fresh HttpResponseMessage.
    • @@ -8300,27 +8767,27 @@

      Route

    CapturingHttpMessageHandler

    -

    MMCA.Common.Testing.UI · MMCA.Common.Testing.UI · MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:19 · Level 1 · sealed class

    +

    MMCA.Common.Testing.UI · MMCA.Common.Testing.UI · MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:20 · Level 1 · sealed class

      -
    • What it is: the canned-response, request-recording HttpMessageHandler that lets an HTTP-backed UI service be unit tested with no server: it answers every request from registered routes or a responder delegate, and records what was sent (MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:7-18).
    • +
    • What it is: the canned-response, request-recording HttpMessageHandler that lets an HTTP-backed UI service be unit tested with no server: it answers every request from registered routes or a responder delegate, and records what was sent (MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:7-19).
    • Depends on: its two companions Route (private nested) and CapturedRequest. Externals: System.Net.Http.HttpMessageHandler (the BCL extension point), System.Text.Json for body serialization, and System.Net/System.Text (:1-3).
    • Concept introduced, faking at the transport boundary instead of the service boundary. The alternative would be mocking the UI service's own interface, which proves nothing about the service. Subclassing HttpMessageHandler puts the fake one layer lower, so the service under test runs its real URL construction, its real serialization, its real header setting, its real status-code handling, and its real error mapping; only the wire is simulated. [Rubric §14, Testability] assesses how much real behavior a unit test covers; this is the difference between testing the service and testing a mock of it. [Rubric §9, API and Contract Design] applies because the registered routes are a written-down expectation of the API's shape, and [Rubric §12, Performance and Scalability] indirectly: the whole tier runs in-process with no sockets, which is what keeps the UI test tier in the fast unit run.
    • Walkthrough: state, then the two configuration modes, then the send path.
        -
      • Static WebJson (:21) is a single JsonSerializerOptions(JsonSerializerDefaults.Web) instance, matching what the WebAPI actually emits (camelCase, case-insensitive reads), so a body serialized here deserializes in the service exactly as a real response would.
      • -
      • _respond (:23) is the optional responder delegate; _routes (:24) and _requests (:25) are the registration and capture lists.
      • -
      • The parameterless constructor (:31-33) selects route-registration mode; the delegate constructor (:39) selects responder mode. The two are not exclusive: routes always win, and the delegate is the fallback (:119-137).
      • -
      • Requests (:42) exposes the captures in order as an IReadOnlyList<CapturedRequest>.
      • -
      • SetResponse(method, absolutePath, statusCode, body = null) (:49-58) normalizes the body through a switch: null stays null (empty body), a string is treated as raw JSON and passed through untouched, and anything else is serialized with WebJson (:51-56). It then appends a Route (:57).
      • -
      • RequestsFor(method, absolutePath) (:61-65) filters the captures by verb and case-insensitive path via a collection expression, the assertion helper most tests use.
      • -
      • SendAsync (:67-71) is the override: capture first, then respond. CaptureAsync (:73-92) reads the request content to a string when present (:75-79) and builds the CapturedRequest from the URI, absolute path, path-and-query, and the Authorization header rendered to a string (:81-88), then sets its Headers from CaptureHeaders (:89-91). Reading the body here, before responding, is what makes the body assertable at all, since the content stream is consumed by the read.
      • -
      • CaptureHeaders (:99-117) builds one OrdinalIgnoreCase dictionary, copying every request header (:103-106) and then, when a body is present, every content header (:108-114), comma-joining multi-valued entries. Its doc gives the reason both halves are needed: If-Match lives on the request while Content-Type lives on the content, and a caller asserting either should not have to know which (:94-98).
      • -
      • Respond (:119-137) resolves the response in strict precedence: the last matching registered route (:121-129), else the responder delegate (:131-134), else 404 Not Found with an empty body (:136). The doc explains the 404 default as deliberate, mirroring the WebAPI's not-found behavior so an incidental call (a token refresh, say) does not have to be set up in every test (:12-14).
      • +
      • Static WebJson (:22) is a single JsonSerializerOptions(JsonSerializerDefaults.Web) instance, matching what the WebAPI actually emits (camelCase, case-insensitive reads), so a body serialized here deserializes in the service exactly as a real response would.
      • +
      • _respond (:24) is the optional responder delegate; _routes (:25) and _requests (:26) are the registration and capture lists, and _sync (:27) is the one System.Threading.Lock that guards both.
      • +
      • The parameterless constructor (:33-35) selects route-registration mode; the delegate constructor (:41) selects responder mode. The two are not exclusive: routes always win, and the delegate is the fallback (:144-168).
      • +
      • Requests (:44-53) returns a snapshot of the captures, in order, as an IReadOnlyList<CapturedRequest>: it copies _requests into a new collection under the lock (:48-51), so a test that enumerates it while requests are still arriving cannot hit a "collection was modified" failure.
      • +
      • SetResponse(method, absolutePath, statusCode, body = null) (:60-72) normalizes the body through a switch: null stays null (empty body), a string is treated as raw JSON and passed through untouched, and anything else is serialized with WebJson (:62-67). It then appends a Route under the lock (:68-71).
      • +
      • RequestsFor(method, absolutePath) (:75-85) filters the captures by verb and case-insensitive path via a collection expression built under the lock, the assertion helper most tests use.
      • +
      • SendAsync (:87-96) is the override: capture first, then respond. The capture is awaited outside the lock and only the _requests.Add is taken under it (:89-93), so no await ever runs while the lock is held. CaptureAsync (:98-117) reads the request content to a string when present (:100-104) and builds the CapturedRequest from the URI, absolute path, path-and-query, and the Authorization header rendered to a string (:106-113), then sets its Headers from CaptureHeaders (:114-116). Reading the body here, before responding, is what makes the body assertable at all, since the content stream is consumed by the read.
      • +
      • CaptureHeaders (:124-142) builds one OrdinalIgnoreCase dictionary, copying every request header (:128-131) and then, when a body is present, every content header (:133-139), comma-joining multi-valued entries. Its doc gives the reason both halves are needed: If-Match lives on the request while Content-Type lives on the content, and a caller asserting either should not have to know which (:119-123).
      • +
      • Respond (:144-168) resolves the response in strict precedence: the last matching registered route, looked up under the lock (:146-160), else the responder delegate (:162-165), else 404 Not Found with an empty body (:167). The doc explains the 404 default as deliberate, mirroring the WebAPI's not-found behavior so an incidental call (a token refresh, say) does not have to be set up in every test (:12-14).
    • -
    • Why it's built this way: the two configuration modes serve two different test shapes. A service test that exercises one endpoint many ways wants the delegate ("answer everything this way"); a test that walks a multi-call flow wants named routes with last-registration-wins overrides. Supporting both in one handler avoids two parallel fakes drifting apart.
    • -
    • Where it's used: owned by UiHttpServiceHarness (MMCA.Common.Testing.UI/Infrastructure/UiHttpServiceHarness.cs:24,39,43-45), constructed directly by tests that wire the pieces themselves (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Infrastructure/SharedHttpTestDoublesTests.cs:21,35), and covered in its own right by MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Infrastructure/CapturingHttpMessageHandlerTests.cs:25,37,53,65.
    • -
    • Caveats / not-in-source: _requests and _routes are plain List<T> with no synchronization (:24-25), so a test issuing genuinely concurrent requests through one handler is outside what this type guarantees.
    • +
    • Why it's built this way: the two configuration modes serve two different test shapes. A service test that exercises one endpoint many ways wants the delegate ("answer everything this way"); a test that walks a multi-call flow wants named routes with last-registration-wins overrides. Supporting both in one handler avoids two parallel fakes drifting apart. The lock exists because UI services fan out: the class doc states that recording and route registration are thread-safe "so a service that fans requests out concurrently loses none", and that Requests returns a snapshot (:17-18). A plain List<T> appended from parallel SendAsync calls can drop entries or throw, which would turn a correct Task.WhenAll in a service into a flaky test.
    • +
    • Where it's used: owned by UiHttpServiceHarness (MMCA.Common.Testing.UI/Infrastructure/UiHttpServiceHarness.cs:24,39,43-45), constructed directly by tests that wire the pieces themselves (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Infrastructure/SharedHttpTestDoublesTests.cs:21,35), and used across the ADC UI service suites (for example MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.UI.Tests/Services/SessionLive/LivePollUIServiceTests.cs and MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.UI.Tests/Services/SpeakerDashboardServiceTests.cs). It is covered in its own right by MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Infrastructure/CapturingHttpMessageHandlerTests.cs:25,37,53,65, including Capture_RecordsEveryRequest_WhenRequestsArriveConcurrently (CapturingHttpMessageHandlerTests.cs:252), which drives parallel workers through one handler with Task.WhenAll (:267) to pin the concurrency guarantee.
    • +
    • Caveats / not-in-source: the lock covers the two lists only. The responder delegate (:164) is invoked outside it, so a delegate that keeps its own state across calls must be thread-safe itself if the service under test issues requests concurrently.

    MarkupSnapshot

    @@ -8375,7 +8842,7 @@

    TestPrincipal

  • Why it's built this way: role gating in Blazor is claim-matching, not policy evaluation (the test IsAuthenticatedAuthorizationService grants every policy), so getting the role claims right on the principal is what makes an authorized-branch assertion meaningful.
  • -
  • Where it's used: passed to BunitComponentTestBase.RenderAs across all three repos, for example MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.UI.Tests/Pages/HappeningNow/HappeningNowTests.cs, MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.UI.Tests/Pages/Common/EventFilteredListPageBaseTests.cs, MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Layout/NavMenuTests.cs:54 (a named user), and MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Pages/Auth/SessionsTests.cs:80.
  • +
  • Where it's used: passed to BunitComponentTestBase.RenderAs across all three repos, for example MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.UI.Tests/Pages/HappeningNow/HappeningNowTests.cs, MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.UI.Tests/Pages/Common/EventFilteredListPageBaseTests.cs, MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Layout/NavMenuTests.cs:54 (a named user), and MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Pages/Auth/SessionsTests.cs:88.
  • Caveats / not-in-source: the framework ships no built-in role names (the earlier Organizer convenience was removed in favor of InRole); every caller, in Common, ADC, or Store, states its own app's role string through InRole or through AuthenticatedUser(roles: ...) directly.
  • BunitComponentTestBase

    @@ -8383,17 +8850,17 @@

    BunitComponentTestBase

    MMCA.Common.Testing.UI · MMCA.Common.Testing.UI · MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:37 · Level 2 · public abstract class

      -
    • What it is: the one shared base class for bUnit component tests across all three repos. It stands up MudBlazor services and the vendor-neutral UI facades over them, puts JS interop in loose mode, wires real-but-permissive auth doubles, registers localization and a clock, and adds the helpers derived tests actually call (RenderUnderTest, RenderAs, RenderMudProviders, SetUser, ConfigureDataGridListPageHost) (MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:16-36).

      +
    • What it is: the one shared base class for bUnit component tests across all three repos. It stands up MudBlazor services and the vendor-neutral UI facades over them, puts JS interop in loose mode, wires the real authorization service over a mutable authentication-state provider, registers localization and a clock, and adds the helpers derived tests actually call (RenderUnderTest, RenderAs, RenderMudProviders, SetUser, ConfigureDataGridListPageHost) (MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:16-36).

    • -
    • Depends on: its three nested types MutableAuthenticationStateProvider, IsAuthenticatedAuthorizationService, and MudProviderHandles, plus TestPrincipal as the intended source of authenticated principals. It also registers first-party UI services: the AddCommonUiFacades() pair from MMCA.Common.UI (:53, defined at MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:181) and the two list-page state services ListPageStateService and ListPageQueryStateService (:105-106). Externals: bUnit's BunitContext base, JSRuntimeMode, AddBunitPersistentComponentState, and SetRendererInfo (:2), MudBlazor's AddMudServices, the three providers, and IBrowserViewportService (:11-12), Moq for the inert viewport double (:10), and Microsoft.Extensions.DependencyInjection plus its TryAdd extensions (:6-7).

      +
    • Depends on: its two nested types MutableAuthenticationStateProvider and MudProviderHandles, plus TestPrincipal as the intended source of authenticated principals. It also registers first-party UI services: the AddCommonUiFacades() pair from MMCA.Common.UI (:53, defined at MMCA.Common/Source/Presentation/MMCA.Common.UI/DependencyInjection.cs:197) and the two list-page state services ListPageStateService and ListPageQueryStateService (:110-111). Externals: ASP.NET Core's DefaultAuthorizationService and IAuthorizationService from Microsoft.AspNetCore.Authorization (:3), bUnit's BunitContext base, JSRuntimeMode, AddBunitPersistentComponentState, and SetRendererInfo (:2), MudBlazor's AddMudServices, the three providers, and IBrowserViewportService (:11-12), Moq for the inert viewport double (:10), and Microsoft.Extensions.DependencyInjection plus its TryAdd extensions (:6-7).

    • Concept introduced, the component test harness. A Blazor component under test needs a renderer, a service provider, and whatever ambient services its markup injects; assembling that per test project is how three repos end up with three subtly different harnesses. This base assembles it once, in a shipped package, so a component that renders in Common's tests renders identically in ADC's and Store's. Several decisions carry the weight:

      • The vendor-neutral facades ship with the harness (:53): every migrated page and the shared Result helpers inject IToastService and the confirm-dialog facade. AddCommonUiFacades() wraps the MudBlazor services registered on the line above, so it belongs immediately after AddMudServices(); without it a consumer's component test fails to resolve IToastService and each repo re-registers the same pair in its own base. The registration uses TryAdd internally, so a test wanting a recording double registers one afterwards and last registration wins (:48-52).
      • Loose JS interop (:55): MudBlazor components probe JS during render (measurements, popover positioning). In bUnit's default strict mode every unplanned call throws, so a test would have to pre-plan interop it does not care about; loose mode returns defaults instead.
      • -
      • Real authorization, fake decision (:56-58): AddAuthorizationCore() registers the genuine Blazor authorization plumbing, then the two nested doubles replace only the decision inputs. <AuthorizeView> therefore runs its real code path.
      • -
      • Localization by default (:60-64): the comment ties this to ADR-027; components inject IStringLocalizer<T>, so registering the open generic once lets every component test render localized markup against the neutral resources in the component's own assembly with no per-test setup. [Rubric §27, i18n] assesses whether localization is systematic rather than incidental; making it the harness default is what keeps localized pages testable everywhere.
      • -
      • A clock, added with TryAdd (:66-70): TimeProvider.System backs the staleness measurements the notification bell's poll interval and the navigation-refresh window depend on. TryAddSingleton means a test that drives time deterministically registers a fake provider of its own and wins. The comment also notes that AddLogging above already brought in the options infrastructure, so IOptions<T> of an unconfigured settings class resolves to its defaults.
      • +
      • Real authorization, test-controlled principal (:56-63): AddAuthorizationCore() registers the genuine Blazor authorization plumbing, and the base then registers the framework's own DefaultAuthorizationService as the singleton IAuthorizationService (:62) next to the mutable provider instance (:63). The comment gives two reasons for the explicit registration (:58-61): bUnit pre-registers a placeholder IAuthorizationService that throws, and AddAuthorizationCore only TryAdds, so without it the placeholder would win; and the real service evaluates the requirements a view actually builds (Roles, the default deny-anonymous policy), whereas a permissive double would authorize every authenticated principal whatever the view asks for. <AuthorizeView> therefore runs its real code path, role checks included, and the only substituted input is the principal. [Rubric §11, Security] is the reason this matters: a role-gated branch that renders for any signed-in user in tests would hide exactly the authorization bug the test exists to catch.
      • +
      • Localization by default (:65-69): the comment ties this to ADR-027; components inject IStringLocalizer<T>, so registering the open generic once lets every component test render localized markup against the neutral resources in the component's own assembly with no per-test setup. [Rubric §27, i18n] assesses whether localization is systematic rather than incidental; making it the harness default is what keeps localized pages testable everywhere.
      • +
      • A clock, added with TryAdd (:71-75): TimeProvider.System backs the staleness measurements the notification bell's poll interval and the navigation-refresh window depend on. TryAddSingleton means a test that drives time deterministically registers a fake provider of its own and wins. The comment also notes that AddLogging above already brought in the options infrastructure, so IOptions<T> of an unconfigured settings class resolves to its defaults.
      • Anonymous by default (:40,42): the seed principal has no authentication type, so tests opt in to the authorized branch explicitly rather than inheriting it.

      [Rubric §28, Front-End Testing] assesses whether UI behavior is verified automatically; this base is the entry point for the entire component tier. [Rubric §14, Testability] assesses how cheaply a unit renders in isolation. [Rubric §15, Best Practices & Code Quality]: the class remark documents that the bUnit v2 symbols (BunitContext, Render<T>) are confined to this file, so a hypothetical downgrade to bUnit v1 changes this one class and no derived test, because derived tests only ever call RenderUnderTest/RenderAs (:28-35).

      @@ -8401,15 +8868,15 @@

      BunitComponentTestBase

    • Walkthrough: fields first, then the constructor, then the helpers.

      • Anonymous (:40) is a static readonly ClaimsPrincipal over a bare ClaimsIdentity, unauthenticated precisely because no authentication type is supplied. _authProvider (:42) is the MutableAuthenticationStateProvider seeded with it.
      • -
      • The constructor (:44-71) runs the registrations described above, in order: AddMudServices() (:46), AddCommonUiFacades() (:53), loose JSInterop.Mode (:55), AddAuthorizationCore() (:56), the singleton IsAuthenticatedAuthorizationService (:57), the singleton provider instance (:58), AddLogging() and AddLocalization() (:63-64), and TryAddSingleton(TimeProvider.System) (:70).
      • -
      • ConfigureDataGridListPageHost(isInteractive = true, rendererName = "Server", stubViewport = true) (:100-118) is the one helper whose call ordering is load-bearing, and the reason it exists as a single method. It registers the two list-page state services with TryAddScoped (:105-106), substitutes an inert Mock.Of<IBrowserViewportService>() when stubViewport is true (:108-111), calls AddBunitPersistentComponentState() because the list-page base persists grid state across the prerender/interactive boundary (:113-114), and finally calls SetRendererInfo(new RendererInfo(rendererName, isInteractive)) (:116-117). The doc spells out the trap: SetRendererInfo builds and freezes the bUnit service provider, so any registration made after it is silently ignored and the page resolves the framework default instead of the test's double; fifteen call sites across the repos hand-rolled this block and each had to remember that rule (:77-83). Two parameters encode real branches: isInteractive because list pages bound their prerender fetches on RendererInfo.IsInteractive, so a non-interactive renderer means no data ever loads (:93-97), and stubViewport because in bUnit no browser answers MudBlazor's breakpoint subscription, so the real service would leave the card-versus-grid choice up to timing (:84-91). The viewport double is registered with a plain Add rather than TryAdd on purpose: AddMudServices already registered the real implementation and last registration wins. [Rubric §22, Responsive/Cross-Browser] assesses whether responsive behavior is deterministic and verified; pinning the viewport is what makes the mobile-versus-desktop branch of a list page assertable at all.
      • -
      • SetUser(ClaimsPrincipal) (:121) forwards to _authProvider.SetPrincipal, changing the injected provider's answer and notifying listeners without rendering a new root, which is how a mid-test sign-in or sign-out is simulated.
      • -
      • RenderUnderTest<TComponent>(parameters) (:124-127) is the anonymous-render entry point and simply calls RenderAs(Anonymous, parameters).
      • -
      • RenderAs<TComponent>(principal, parameters) (:130-141) is the one that matters: it sets the provider's principal (:135) and then renders with a cascading Task<AuthenticationState> value added ahead of the caller's parameters (:138-139). Driving both routes from one principal is what makes the cascading consumers and the injecting consumers agree.
      • -
      • RenderMudProviders() (:148-154) renders MudPopoverProvider, MudDialogProvider, and MudSnackbarProvider as separate roots and returns the MudProviderHandles triple. The doc is explicit that it must be called before the component under test (:143-147).
      • +
      • The constructor (:44-76) runs the registrations described above, in order: AddMudServices() (:46), AddCommonUiFacades() (:53), loose JSInterop.Mode (:55), AddAuthorizationCore() (:56), the singleton DefaultAuthorizationService (:62), the singleton provider instance (:63), AddLogging() and AddLocalization() (:68-69), and TryAddSingleton(TimeProvider.System) (:75).
      • +
      • ConfigureDataGridListPageHost(isInteractive = true, rendererName = "Server", stubViewport = true) (:105-123) is the one helper whose call ordering is load-bearing, and the reason it exists as a single method. It registers the two list-page state services with TryAddScoped (:110-111), substitutes an inert Mock.Of<IBrowserViewportService>() when stubViewport is true (:113-116), calls AddBunitPersistentComponentState() because the list-page base persists grid state across the prerender/interactive boundary (:118-119), and finally calls SetRendererInfo(new RendererInfo(rendererName, isInteractive)) (:121-122). The doc spells out the trap: SetRendererInfo builds and freezes the bUnit service provider, so any registration made after it is silently ignored and the page resolves the framework default instead of the test's double; fifteen call sites across the repos hand-rolled this block and each had to remember that rule (:82-88). Two parameters encode real branches: isInteractive because list pages bound their prerender fetches on RendererInfo.IsInteractive, so a non-interactive renderer means no data ever loads (:98-102), and stubViewport because in bUnit no browser answers MudBlazor's breakpoint subscription, so the real service would leave the card-versus-grid choice up to timing (:89-96). The viewport double is registered with a plain Add rather than TryAdd on purpose: AddMudServices already registered the real implementation and last registration wins. [Rubric §22, Responsive/Cross-Browser] assesses whether responsive behavior is deterministic and verified; pinning the viewport is what makes the mobile-versus-desktop branch of a list page assertable at all.
      • +
      • SetUser(ClaimsPrincipal) (:126) forwards to _authProvider.SetPrincipal, changing the injected provider's answer and notifying listeners without rendering a new root, which is how a mid-test sign-in or sign-out is simulated.
      • +
      • RenderUnderTest<TComponent>(parameters) (:129-132) is the anonymous-render entry point and simply calls RenderAs(Anonymous, parameters).
      • +
      • RenderAs<TComponent>(principal, parameters) (:135-146) is the one that matters: it sets the provider's principal (:140) and then renders with a cascading Task<AuthenticationState> value added ahead of the caller's parameters (:143-144). Driving both routes from one principal is what makes the cascading consumers and the injecting consumers agree.
      • +
      • RenderMudProviders() (:153-159) renders MudPopoverProvider, MudDialogProvider, and MudSnackbarProvider as separate roots and returns the MudProviderHandles triple. The doc is explicit that it must be called before the component under test (:148-152).
    • -
    • Why it's built this way: registering the auth doubles as concrete instances rather than mocks means the authorization pipeline under test is the framework's own; only the two decision inputs are substituted. Moq is a deliberate dependency for exactly one thing, the viewport double, because a hand-written stub would have to track every member MudBlazor adds to IBrowserViewportService (MMCA.Common.Testing.UI/MMCA.Common.Testing.UI.csproj:17-21). And keeping the version-specific bUnit symbols in one file is a blast-radius decision recorded in the class remark rather than left to be rediscovered.

      +
    • Why it's built this way: registering the framework's own DefaultAuthorizationService plus a concrete provider instance, rather than mocks or a permissive decision double, means the authorization pipeline under test is the production one; only the principal is substituted, so a test that renders a role-gated view as a principal lacking the role sees the denied branch, as production would (:58-62). Moq is a deliberate dependency for exactly one thing, the viewport double, because a hand-written stub would have to track every member MudBlazor adds to IBrowserViewportService (MMCA.Common.Testing.UI/MMCA.Common.Testing.UI.csproj:17-21). And keeping the version-specific bUnit symbols in one file is a blast-radius decision recorded in the class remark rather than left to be rediscovered.

    • Where it's used: subclassed by a thin repo-local BunitTestBase in each consumer, which adds only that repo's extra registrations: MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/BunitTestBase.cs:17 (theme service, culture applier, capability fallbacks, public-link builder) and MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.UI.Tests/BunitTestBase.cs:23 (the ADR-042 device-capability defaults, the public-link builder, the public session schedule service, and an empty IConfiguration), plus the equivalent bases in ADC's Identity and Engagement suites and in Store's Sales, Catalog, and Identity UI test projects. ConfigureDataGridListPageHost is called from every list-page suite, for example MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Pages/Common/DataGridListPageBaseTests.cs:35, MMCA.Store/Tests/Modules/Sales/MMCA.Store.Sales.UI.Tests/Pages/ShoppingCart/ShoppingCartListTests.cs:37, MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.UI.Tests/Pages/Sessions/SessionListEventFilterTests.cs:41 (stubViewport: false), and MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.UI.Tests/Pages/Common/EventFilteredListPagePrerenderTests.cs:42 (isInteractive: false, the prerender branch). The base's own registration contract is guarded by MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Infrastructure/BunitComponentTestBaseFacadeTests.cs:18, which derives from the shipped base rather than the repo-local one precisely so the facade registrations cannot be papered over by a consumer.

    • @@ -8533,10 +9000,10 @@

      NullTokenStorageService

      • What it is: an in-memory-empty ITokenStorageService: there is no stored session in the gallery. It exists so the AuthDelegatingHandler that AddUIShared registers resolves cleanly, and it is never actually invoked because the gallery makes no API calls (MMCA.Common.UI.Gallery/Stubs/NullTokenStorageService.cs:6-10).
      • Depends on: ITokenStorageService from MMCA.Common.UI.Services.Auth.
      • -
      • Concept introduced: the same stub pattern as NullTokenRefresher, covering storage rather than refresh, and the first place where a stub exists purely to satisfy a transitive constructor. AddUIShared registers AuthDelegatingHandler as transient (MMCA.Common.UI/DependencyInjection.cs:92) and attaches it to the named "APIClient" pipeline (MMCA.Common.UI/DependencyInjection.cs:96, :101), and that handler's primary constructor takes an ITokenStorageService (MMCA.Common.UI/Services/Auth/AuthDelegatingHandler.cs:10-11). Nothing in AddUIShared supplies one, so the host must. [Rubric §26, Front-End Security] assesses how auth tokens are stored and handled; this stub deliberately holds nothing, so the test host persists no credential material at all, not even in memory.
      • +
      • Concept introduced: the same stub pattern as NullTokenRefresher, covering storage rather than refresh, and the first place where a stub exists purely to satisfy a transitive constructor. AddUIShared registers AuthDelegatingHandler as transient (MMCA.Common.UI/DependencyInjection.cs:92) and attaches it to the named "APIClient" pipeline (MMCA.Common.UI/DependencyInjection.cs:96, :101), and that handler's primary constructor takes an ITokenStorageService (MMCA.Common.UI/Services/Auth/AuthDelegatingHandler.cs:12-13). Nothing in AddUIShared supplies one, so the host must. [Rubric §26, Front-End Security] assesses how auth tokens are stored and handled; this stub deliberately holds nothing, so the test host persists no credential material at all, not even in memory.
      • Walkthrough: four members, each inert but shape-complete so DI binds. GetAccessTokenAsync() (:12) and GetRefreshTokenAsync() (:14) return Task.FromResult<string?>(null); SetTokensAsync(accessToken, refreshToken) (:16) and ClearTokensAsync() (:18) discard their inputs and return Task.CompletedTask.
      • Where it's used: registered scoped by GalleryHost (MMCA.Common.UI.Gallery/GalleryHost.cs:61), inside the block placed ahead of AddUIShared (MMCA.Common.UI.Gallery/GalleryHost.cs:56-64).
      • -
      • Caveats / not-in-source: unlike NoOpAuthUIService, this registration is not a TryAdd override. AddUIShared makes no ITokenStorageService registration anywhere in its body (MMCA.Common.UI/DependencyInjection.cs:36-159); the concrete storage services are supplied per host head instead, so in the gallery this stub is the only registration rather than a winning one.
      • +
      • Caveats / not-in-source: unlike NoOpAuthUIService, this registration is not a TryAdd override. AddUIShared makes no ITokenStorageService registration anywhere in its body (MMCA.Common.UI/DependencyInjection.cs:36-175); the concrete storage services are supplied per host head instead, so in the gallery this stub is the only registration rather than a winning one.

      SampleGridData

      @@ -8597,19 +9064,19 @@

      NoOpAuthUIService

      • What it is: a no-op IAuthUIService for the backend-less gallery. The gallery renders the real Login, Register, and Sessions pages for accessibility and render-smoke scanning only, so every operation returns a benign default (MMCA.Common.UI.Gallery/Stubs/NoOpAuthUIService.cs:8-13).
      • Depends on: IAuthUIService from MMCA.Common.UI.Services.Auth, the Result and Error types from MMCA.Common.Shared.Abstractions, and the LoginRequest, RegisterRequest, AuthenticationResponse, and RefreshSessionSummaryResponse contracts from MMCA.Common.Shared.Auth.
      • -
      • Concept introduced, registration order as the override mechanism. This stub is registered before AddUIShared, whose TryAddScoped<IAuthUIService, AuthUIService>() (MMCA.Common.UI/DependencyInjection.cs:124) then defers to it, exactly as the class doc comment states (MMCA.Common.UI.Gallery/Stubs/NoOpAuthUIService.cs:11-12). TryAdd* is first-registration-wins, so a test host overrides only the extension points it names and inherits every other registration the shared UI makes, with no fork of the composition root. [Rubric §11, Security] assesses how authentication is handled; here the client-side auth boundary is neutralized entirely so the scan touches the real login and register markup without any credential flow. [Rubric §14, Testability]: substituting the top-level UI auth service for constants is what makes those pages renderable in isolation. [Rubric §1, SOLID]: the stub is only possible because the shared pages depend on the IAuthUIService abstraction rather than on AuthUIService, so swapping the implementation needs no change to any page.
      • +
      • Concept introduced, registration order as the override mechanism. This stub is registered before AddUIShared, whose TryAddScoped<IAuthUIService, AuthUIService>() (MMCA.Common.UI/DependencyInjection.cs:140) then defers to it, exactly as the class doc comment states (MMCA.Common.UI.Gallery/Stubs/NoOpAuthUIService.cs:11-12). TryAdd* is first-registration-wins, so a test host overrides only the extension points it names and inherits every other registration the shared UI makes, with no fork of the composition root. [Rubric §11, Security] assesses how authentication is handled; here the client-side auth boundary is neutralized entirely so the scan touches the real login and register markup without any credential flow. [Rubric §14, Testability]: substituting the top-level UI auth service for constants is what makes those pages renderable in isolation. [Rubric §1, SOLID]: the stub is only possible because the shared pages depend on the IAuthUIService abstraction rather than on AuthUIService, so swapping the implementation needs no change to any page.
      • Walkthrough:
          -
        • Shared constants (:16-23): two static readonly session GUIDs, CurrentDeviceSessionId (all-ones) and OtherDeviceSessionId (all-twos), built from their component parts rather than parsed, with a comment recording why, the MA0176 analyzer rejects parsing a constant at runtime (:15). Unavailable (:22-23) is a single shared Error.Failure("Gallery.AuthUnavailable", "The gallery has no backend.") reused by every failing member.
        • -
        • Credential operations (:25-32): LoginAsync, RegisterAsync, and ExchangeOAuthCodeAsync each return Result.Failure<AuthenticationResponse>(Unavailable), so the pages render and submit but never establish a session.
        • -
        • Session lifecycle (:34-40): LogoutAsync() returns Task.CompletedTask, TryRefreshTokenAsync(ct) returns false, and ChangePasswordAsync(currentPassword, newPassword, ct) returns Result.Failure(Unavailable).
        • -
        • Password reset (:44-48): RequestPasswordResetAsync(email, ct) and ResetPasswordAsync(email, token, newPassword, ct) also fail with Unavailable. The comment at :42-43 records why that still produces useful scan coverage, the Forgot Password page shows its confirmation regardless of the result (anti-enumeration), so the post-submit state renders anyway. ForgotPasswordPageE2ETests asserts exactly that (MMCA.Common.UI.E2E.Tests/Auth/ForgotPasswordPageE2ETests.cs:27-38).
        • -
        • Device sessions (:53-74): GetSessionsAsync(ct) is the one member that returns real data. It builds two RefreshSessionSummaryResponse rows, a current device (all-ones id, a Chrome-on-Windows user agent, IpAddress: "203.0.113.7", IsCurrent: true) and another device (all-twos id, a Safari-on-iOS user agent, a null IpAddress, IsCurrent: false). The comment at :50-52 gives the reason, the axe scan has to reach the populated table, the current-device chip, and a live revoke button, which an empty state would hide, and one row is flagged current so both branches of the actions cell render.
        • -
        • Revoke (:76-77): RevokeSessionAsync(sessionId, ct) returns Result.Failure(Unavailable), so the button is present and wired without changing anything.
        • +
        • Shared constants (:17-24): two static readonly session GUIDs, CurrentDeviceSessionId (all-ones) and OtherDeviceSessionId (all-twos), built from their component parts rather than parsed, with a comment recording why, the MA0176 analyzer rejects parsing a constant at runtime (:16). Unavailable (:23-24) is a single shared Error.Failure("Gallery.AuthUnavailable", "The gallery has no backend.") reused by every failing member.
        • +
        • Credential operations (:26-33): LoginAsync, RegisterAsync, and ExchangeOAuthCodeAsync each return Result.Failure<AuthenticationResponse>(Unavailable), so the pages render and submit but never establish a session.
        • +
        • Session lifecycle (:35-41): LogoutAsync() returns Task.CompletedTask, TryRefreshTokenAsync(ct) returns false, and ChangePasswordAsync(currentPassword, newPassword, ct) returns Result.Failure(Unavailable).
        • +
        • Password reset (:45-49): RequestPasswordResetAsync(email, ct) and ResetPasswordAsync(email, token, newPassword, ct) also fail with Unavailable. The comment at :43-44 records why that still produces useful scan coverage, the Forgot Password page shows its confirmation regardless of the result (anti-enumeration), so the post-submit state renders anyway. ForgotPasswordPageE2ETests asserts exactly that (MMCA.Common.UI.E2E.Tests/Auth/ForgotPasswordPageE2ETests.cs:27-38).
        • +
        • Device sessions (:54-75): GetSessionsAsync(ct) is the one member that returns real data. It builds two RefreshSessionSummaryResponse rows, a current device (all-ones id, a Chrome-on-Windows user agent, IpAddress: "203.0.113.7", IsCurrent: true) and another device (all-twos id, a Safari-on-iOS user agent, a null IpAddress, IsCurrent: false). The comment at :51-53 gives the reason, the axe scan has to reach the populated table, the current-device chip, and a live revoke button, which an empty state would hide, and one row is flagged current so both branches of the actions cell render.
        • +
        • Revoke (:77-81): RevokeSessionAsync(sessionId, ct) (:77-78) and RevokeAllSessionsAsync(ct) (:80-81) both return Result.Failure(Unavailable), so the per-row revoke buttons and the account-wide sign-out are present and wired without changing anything. The shared Sessions page shows a failed account-wide revoke and stays put rather than signing out locally (MMCA.Common.UI/Pages/Auth/Sessions.razor.cs:163-167, :191-194), so in the gallery that button keeps the page on its error path.
      • Why it's built this way: placing the stub ahead of AddUIShared exploits the shared UI's TryAdd* idempotence rather than requiring the shared registration extension to grow test hooks, which keeps the production DI code free of test-only branches. Returning a real failure Result rather than a null also keeps the pages on their genuine error-handling paths, which is what makes an error alert's markup part of the scanned surface.
      • Where it's used: registered scoped by GalleryHost (MMCA.Common.UI.Gallery/GalleryHost.cs:60), the first of the pre-AddUIShared stub registrations. It is consumed indirectly by LoginPageE2ETests, RegisterPageE2ETests, ForgotPasswordPageE2ETests, ResetPasswordPageE2ETests, and SessionsPageE2ETests, the last of which asserts on the rendered form of these very rows, the visible texts "Chrome on Windows" and "This device" and the buttons "Sign out of Safari on iOS" and "Sign out of every device, including this one" (MMCA.Common.UI.E2E.Tests/Auth/SessionsPageE2ETests.cs:28-32).
      • -
      • Caveats / not-in-source: the two user-agent strings are supplied raw (MMCA.Common.UI.Gallery/Stubs/NoOpAuthUIService.cs:63, :69) while the test asserts on friendly names. The parsing that turns one into the other lives in the shared sessions page, not in this stub or this unit.
      • +
      • Caveats / not-in-source: the two user-agent strings are supplied raw (MMCA.Common.UI.Gallery/Stubs/NoOpAuthUIService.cs:63, :70) while the test asserts on friendly names. The parsing that turns one into the other lives in the shared sessions page, not in this stub or this unit.

      GalleryAuthenticationStateProvider

      @@ -8654,7 +9121,7 @@

      GalleryHost

    • Caveats / not-in-source: the class summary (MMCA.Common.UI.Gallery/GalleryHost.cs:16-21) lags the body in two ways. It names only /login, /register, and /components while BuildApp also serves the guarded notification and sessions pages and the /grid virtualization page, and it still describes the stub set as including an "anonymous auth state", whereas GalleryAuthenticationStateProvider mirrors the request rather than forcing anonymous.

    Per-project test rollup

    -

    The remaining test types in G25 are logged here by project rather than as individual ### sections (per the TESTS note): each project below is a Microsoft Testing Platform / xUnit v3 assembly, and the table gives its purpose and testing style (unit, integration, architecture-fitness, E2E, or component). The table covers 51 assemblies and 2330 types.

    +

    The remaining test types in G25 are logged here by project rather than as individual ### sections (per the TESTS note): each project below is a Microsoft Testing Platform / xUnit v3 assembly, and the table gives its purpose and testing style (unit, integration, architecture-fitness, E2E, or component). The table covers 53 assemblies and 2458 types.

    @@ -8670,7 +9137,7 @@

    Per-project test rollup

    - + @@ -8700,7 +9167,7 @@

    Per-project test rollup

    - + @@ -8720,7 +9187,7 @@

    Per-project test rollup

    - + @@ -8780,7 +9247,7 @@

    Per-project test rollup

    - + @@ -8810,8 +9277,8 @@

    Per-project test rollup

    - - + + @@ -8825,12 +9292,12 @@

    Per-project test rollup

    - + - + @@ -8840,7 +9307,7 @@

    Per-project test rollup

    - + @@ -8850,7 +9317,7 @@

    Per-project test rollup

    - + @@ -8860,7 +9327,7 @@

    Per-project test rollup

    - + @@ -8874,13 +9341,23 @@

    Per-project test rollup

    + + + + + - + + + + + + - + @@ -8905,18 +9382,18 @@

    Per-project test rollup

    - + - + - - + +
    MMCA.ADC.Conference.Application.Tests193196 Unit tests for Conference command/query handlers, validators, and DTO mappers: the module's largest suite, covering CQRS handler behavior in isolation.
    MMCA.ADC.Conference.UI.Tests6681 bUnit component tests for Conference Blazor pages and components: rendering, parameter binding, and interaction behavior.
    MMCA.ADC.Engagement.Application.Tests6570 Unit tests for Engagement command/query handlers, validators, and DTO mappers (points, badges, check-ins).
    MMCA.ADC.Identity.Shared.Tests23 Unit tests for Identity cross-cutting Shared-project helpers.
    MMCA.ADC.Services.Tests7Unit tests for ADC-wide shared service helpers used across modules.17Unit tests for the service-host gRPC servers and their client adapters invoked directly (no booted host): wire round trips, RpcException-to-Result mapping, the live-channel ingress bounds, the export timestamp contract, and the Conference output-cache warm-up paths.
    MMCA.ADC.UI.Web.Tests
    MMCA.Common.API.Tests155170 Unit tests for the framework's API base classes and conventions: controller bases, versioning, rate limiting, and parameter descriptors.
    MMCA.Common.Application.Tests398401 Unit tests for the framework's CQRS/Application layer: command/query handler bases, the decorator pipeline (caching, authorization, multi-tenancy), and validation conventions. The framework's largest single suite.
    MMCA.Common.Aspire.Tests5052 Unit tests for the framework's Aspire integration helpers: readiness budgets, environment gates, and probe paths.
    MMCA.Common.Domain.Tests6264 Unit tests for the framework's base Domain types: entities, aggregate roots, specifications, and domain events.
    MMCA.Common.Grpc.Tests1618 Unit tests for the framework's gRPC infrastructure helpers.
    Unit tests for the framework's Redis-backed cache infrastructure.
    MMCA.Common.Infrastructure.SQLServer.Tests8Integration tests that run the shipped SQL Server context and DbContextFactory against a real SQL Server in Docker (MMCA.Common.UI.Web.Tests 12 24/Core/MMCA.Common.Infrastructure.SQLServer.Tests/SQLServerPersistenceTests.cs:44).
    MMCA.Common.Infrastructure.Tests499514 Unit tests for the framework's core Infrastructure layer: SQL Server ApplicationDbContext, interceptors, repositories, outbox processing, and EF conventions. The framework's largest suite overall.
    MMCA.Common.LoadTests14Weekly load tests of the framework's real persistence stack over a temp-file SQLite database: the dynamic paged-query path at 100,000 rows and outbox throughput at 10,000 events (MMCA.Common.UI.Web.Tests 12 24/Performance/MMCA.Common.LoadTests/PagedQueryLoadTests.cs:13).
    MMCA.Common.Shared.Tests5860 Unit tests for the framework's Shared-layer cross-cutting helpers (identifier types, common value objects).
    MMCA.Common.UI.E2E.Tests2021 Playwright end-to-end tests for the framework's shared UI Gallery/demo host.
    MMCA.Common.UI.Tests152170 bUnit component tests for the framework's reusable Blazor UI package (shared components, pages, services).
    MMCA.Common.UI.Web.Tests12Unit tests for the framework's Blazor Web host services (used by consumer Web hosts): server-side token storage, web form-factor detection, UI rate limiting and the bounded circuit handler (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Web.Tests/Hardening/BoundedCircuitHandlerTests.cs:19), the trusted-caller handler, header registration and service discovery (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Web.Tests/Security/TrustedCallerHandlerTests.cs:13), and the Blazor CSP policy provider (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Web.Tests/Security/BlazorCspPolicyProviderTests.cs:26).24Unit tests for the framework's Blazor Web host services (used by consumer Web hosts): server-side token storage, web form-factor detection, UI rate limiting and the bounded circuit handler (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Web.Tests/Hardening/BoundedCircuitHandlerTests.cs:20), the trusted-caller handler, header registration and service discovery (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Web.Tests/Security/TrustedCallerHandlerTests.cs:13), the Blazor CSP policy provider (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Web.Tests/Security/BlazorCspPolicyProviderTests.cs:26), and the same-origin API proxy end to end (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Web.Tests/SameOriginProxy/SameOriginApiProxyTokenTests.cs:15).

    Test suites by module

    @@ -8931,23 +9408,23 @@

    MMCA.Common, the fr MMCA.Common.Shared.Tests - 58 - The innermost layer: the Result/Error/ErrorType pattern and its extensions, value objects (Money, Email, Address, DateRange, ...) and their factory-method invariants, DTO/paging contracts, and the striped keyed lock behind KeyedSemaphoreStripe (mutual exclusion per key, independent progress across stripes, release on the exception path, and a bounded table size no matter how many caller-supplied keys arrive, MMCA.Common/Tests/Core/MMCA.Common.Shared.Tests/Concurrency/KeyedSemaphoreStripeTests.cs:12). Two files defend contracts that only bite in a head with no ASP.NET pipeline: MoneySerializationTests pins the round trip of Money's private [JsonConstructor], which is also the constructor EF Core uses to materialize the owned type, so a materializer yielding a null currency must fail fast rather than surface a half-built value object (.../ValueObjects/MoneySerializationTests.cs:11); and SupportedCulturesTests pins SupportedCultures.ResolveClosest, the fallback chain a head applies when it resolves its own culture instead of getting request localization's Accept-Language matching for free, since an Android device reports a specific culture (es-MX) while the allowlist holds a neutral one and without the language-level match a Spanish phone would silently start in English (.../Globalization/SupportedCulturesTests.cs:11, ADR-027 / §27). The smart enumeration Enumeration<TEnumeration> has reflection-based member discovery, Result-returning FromValue/FromName lookups (case-insensitive by name, an UnknownValue/UnknownName error rather than an exception) and type-guarded equality pinned, so two enumerations that happen to share an integer value are never equal and never collide in a hash set (.../ValueObjects/EnumerationTests.cs:10). KeysetPaginationTests covers the keyset ("seek") paging value types, the request's clamp semantics, the page result, and the cursor codec's round-trip, version gate and rejection of anything malformed, which matters because a cursor is a client-held token and a corrupted one must be refused rather than quietly paged from the top (.../Abstractions/KeysetPaginationTests.cs:10, ADR-055); RoleValueTests pins that RoleValue.Validate compares roles case-insensitively no matter which comparer the caller's set was built with, after a set built with the default ordinal comparer rejected a correctly spelled role that differed only in casing (.../Auth/RoleValueTests.cs:13). Its newest four all exist because a contract is shared by callers that must never disagree: ErrorTypeSeverityTests pins the severity ranking every transport edge uses to pick the status for a combined result, including that the 400-family ranks equal and that ties keep the earliest error (.../Abstractions/ErrorTypeSeverityTests.cs:10); ProblemDetailsResultReader parses an RFC 9457 payload back into Error instances and lives here rather than in the API package precisely so the UI can reach it (.../Http/ProblemDetailsResultReaderTests.cs:15); NotificationScopeKey ships its formatter and the regex that guards it together, and every key the formatter produces is asserted against that pattern, which is the default the notification hub enforces (.../Notifications/NotificationScopeKeyTests.cs:14); and ModuleNameConventionsTests pins the MMCA.{App}.{Module}.{Layer} parse shared by persistence (SQL schema and data-source names) and the logging decorators' scope enrichment, including the namespaces that carry no module at all (.../Conventions/ModuleNameConventionsTests.cs:15). ConcurrencyETag, the weak entity tag that carries the concurrency token, is pinned here too, one layer below the filter that consumes it (.../Http/ConcurrencyETagTests.cs:10, ADR-035). Pure unit tests, no DI or DB. + 60 + The innermost layer: the Result/Error/ErrorType pattern and its extensions, value objects (Money, Email, Address, DateRange, ...) and their factory-method invariants, DTO/paging contracts, and the striped keyed lock behind KeyedSemaphoreStripe (mutual exclusion per key, independent progress across stripes, release on the exception path, and a bounded table size no matter how many caller-supplied keys arrive, MMCA.Common/Tests/Core/MMCA.Common.Shared.Tests/Concurrency/KeyedSemaphoreStripeTests.cs:12). Two files defend contracts that only bite in a head with no ASP.NET pipeline: MoneySerializationTests pins the round trip of Money's private [JsonConstructor], which is also the constructor EF Core uses to materialize the owned type, so a materializer yielding a null currency must fail fast rather than surface a half-built value object (.../ValueObjects/MoneySerializationTests.cs:11); and SupportedCulturesTests pins SupportedCultures.ResolveClosest, the fallback chain a head applies when it resolves its own culture instead of getting request localization's Accept-Language matching for free, since an Android device reports a specific culture (es-MX) while the allowlist holds a neutral one and without the language-level match a Spanish phone would silently start in English (.../Globalization/SupportedCulturesTests.cs:11, ADR-027 / §27). The smart enumeration Enumeration<TEnumeration> has reflection-based member discovery, Result-returning FromValue/FromName lookups (case-insensitive by name, an UnknownValue/UnknownName error rather than an exception) and type-guarded equality pinned, so two enumerations that happen to share an integer value are never equal and never collide in a hash set (.../ValueObjects/EnumerationTests.cs:10). KeysetPaginationTests covers the keyset ("seek") paging value types, the request's clamp semantics, the page result, and the cursor codec's round-trip, version gate and rejection of anything malformed, which matters because a cursor is a client-held token and a corrupted one must be refused rather than quietly paged from the top (.../Abstractions/KeysetPaginationTests.cs:10, ADR-055); RoleValueTests pins that RoleValue.Validate compares roles case-insensitively no matter which comparer the caller's set was built with, after a set built with the default ordinal comparer rejected a correctly spelled role that differed only in casing (.../Auth/RoleValueTests.cs:13). Its newest four all exist because a contract is shared by callers that must never disagree: ErrorTypeSeverityTests pins the severity ranking every transport edge uses to pick the status for a combined result, including that the 400-family ranks equal and that ties keep the earliest error (.../Abstractions/ErrorTypeSeverityTests.cs:10); ProblemDetailsResultReader parses an RFC 9457 payload back into Error instances and lives here rather than in the API package precisely so the UI can reach it (.../Http/ProblemDetailsResultReaderTests.cs:15); NotificationScopeKey ships its formatter and the regex that guards it together, and every key the formatter produces is asserted against that pattern, which is the default the notification hub enforces (.../Notifications/NotificationScopeKeyTests.cs:14); and ModuleNameConventionsTests pins the MMCA.{App}.{Module}.{Layer} parse shared by persistence (SQL schema and data-source names) and the logging decorators' scope enrichment, including the namespaces that carry no module at all (.../Conventions/ModuleNameConventionsTests.cs:15). ConcurrencyETag, the weak entity tag that carries the concurrency token, is pinned here too, one layer below the filter that consumes it (.../Http/ConcurrencyETagTests.cs:10, ADR-035). PasswordComplexityTests pins the shared strong-password rule that both the server validator and the client form attribute evaluate, with Unicode-aware character classes: an accented or CJK letter is a letter (never the special character), a non-ASCII uppercase letter is uppercase, and a non-ASCII decimal digit is a digit (MMCA.Common.UI.Web.Tests 12 24/Core/MMCA.Common.Shared.Tests/Auth/PasswordComplexityTests.cs:12). Pure unit tests, no DI or DB. MMCA.Common.Domain.Tests - 62 - The entity hierarchy (BaseEntity→AuditableBaseEntity→AuditableAggregateRootEntity), domain-event collection, SetItems<T>/GetChildOrNotFound<T>, specifications, and the PiiAttribute/anonymization boundary plus the logging/telemetry redaction half of the [Pii] contract (masks marked members so a data subject's values never reach logs, MMCA.Common/Tests/Core/MMCA.Common.Domain.Tests/Privacy/PiiRedactorTests.cs:12, ADR-005 / §30, PiiRedactor). OwnedByUserSpecification<TEntity, TIdentifierType>, the reusable "rows this caller created" criteria, is covered with a fake that overrides the virtual CreatedBy getter, because that audit field is stamped by the infrastructure layer through EF's change tracker and is otherwise unsettable from a test (.../Specifications/OwnedByUserSpecificationTests.cs:8). SpecificationCompositionTests pins HOW the boolean composers build their criteria rather than only what the composed predicate answers, and neither property is visible from IsSatisfiedBy: the composed tree must contain no InvocationExpression, because a provider that cannot unwrap one (Cosmos) throws at translation time on an ANDed specification, and it must be built once per instance, because the query pipeline reads Criteria on every request while the old implementation rebuilt the tree every time (.../Specifications/SpecificationCompositionTests.cs:18, ADR-018). QuerySpecificationTests covers the builder state a QuerySpecification carries beyond its predicate (includes, ordering, paging, tracking, soft-delete scope) and pins the base chain the SpecificationsDoNotNavigateToOtherEntities fitness rule keys on (.../Specifications/QuerySpecificationTests.cs:14). OutputCacheEvictionRequested is asserted as a shape rather than a behavior (members, schema version, empty-tags default) because it is the framework's own published integration event and every host that consumes it must be able to deserialize it forever (.../IntegrationEvents/OutputCacheEvictionRequestedTests.cs:12, ADR-010). Its newest file is the RefreshSession record (BR-205/206): the token is stored only as an upper-case hex SHA-256 digest and never in the clear, captured client metadata is truncated to its column width rather than overflowing at insert, revocation records when, why and the successor, and re-revoking keeps the first reason (.../Auth/RefreshSessionTests.cs:13, ADR-097). Pure unit tests over the framework domain primitives. + 64 + The entity hierarchy (BaseEntity→AuditableBaseEntity→AuditableAggregateRootEntity), domain-event collection, SetItems<T>/GetChildOrNotFound<T>, specifications, and the PiiAttribute/anonymization boundary plus the logging/telemetry redaction half of the [Pii] contract (masks marked members so a data subject's values never reach logs, MMCA.Common/Tests/Core/MMCA.Common.Domain.Tests/Privacy/PiiRedactorTests.cs:12, ADR-005 / §30, PiiRedactor). OwnedByUserSpecification<TEntity, TIdentifierType>, the reusable "rows this caller created" criteria, is covered with a fake that overrides the virtual CreatedBy getter, because that audit field is stamped by the infrastructure layer through EF's change tracker and is otherwise unsettable from a test (.../Specifications/OwnedByUserSpecificationTests.cs:8). SpecificationCompositionTests pins HOW the boolean composers build their criteria rather than only what the composed predicate answers, and neither property is visible from IsSatisfiedBy: the composed tree must contain no InvocationExpression, because a provider that cannot unwrap one (Cosmos) throws at translation time on an ANDed specification, and it must be built once per instance, because the query pipeline reads Criteria on every request while the old implementation rebuilt the tree every time (.../Specifications/SpecificationCompositionTests.cs:18, ADR-018). QuerySpecificationTests covers the builder state a QuerySpecification carries beyond its predicate (includes, ordering, paging, tracking, soft-delete scope) and pins the base chain the SpecificationsDoNotNavigateToOtherEntities fitness rule keys on (.../Specifications/QuerySpecificationTests.cs:14). OutputCacheEvictionRequested is asserted as a shape rather than a behavior (members, schema version, empty-tags default) because it is the framework's own published integration event and every host that consumes it must be able to deserialize it forever (.../IntegrationEvents/OutputCacheEvictionRequestedTests.cs:12, ADR-010). Its newest file is the RefreshSession record (BR-205/206): the token is stored only as an upper-case hex SHA-256 digest and never in the clear, captured client metadata is truncated to its column width rather than overflowing at insert, revocation records when, why and the successor, and re-revoking keeps the first reason (.../Auth/RefreshSessionTests.cs:13, ADR-097). Its two newest types are fixtures inside PiiRedactorTests, PiiBase and PiiOverride (MMCA.Common.UI.Web.Tests 12 24/Core/MMCA.Common.Domain.Tests/Privacy/PiiRedactorTests.cs:32, :38). Pure unit tests over the framework domain primitives. MMCA.Common.Application.Tests - 398 - The CQRS engine: the decorator pipeline in its registered nesting order, which now carries two validating rings (commands: FeatureGate→Authorization→Logging→Caching→Validating→Timeout→Transactional→handler, MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:134-140; queries: FeatureGate→Authorization→Logging→Caching→Validating→Timeout→handler, .../DependencyInjection.cs:138-143, both registered innermost-first because Scrutor's TryDecorate applies in reverse, .../DependencyInjection.cs:115), the opt-in MiniProfiler decorators added by AddApplicationProfiling (.../DependencyInjection.cs:565) and the CqrsMetrics counters/histograms (MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Decorators/CqrsMetricsTests.cs:20), ModuleLoader topological ordering, DomainEventDispatcher plus the swallow-and-log SafeDomainEventHandler base (.../DomainEvents/SafeDomainEventHandlerTests.cs:14), validation, the IMessageBus abstraction, entity-query projection/paging and the per-type filter strategies, the cross-source CrossSourceSpecification helper, the magic-byte upload sniffer behind ImageContentSniffer (.../ImageContentSnifferTests.cs:12, ADR-045), and the notification read/send handlers driven by an injected TimeProvider test clock. Two older files defend non-obvious properties rather than behavior: PagingMathTests pins the page arithmetic (.../Services/Query/PagingMathTests.cs:12), and QueryFilterServicePropertyCacheTests asserts on the real static property cache inside QueryFilterService to prove that caching a miss never happens, since filter names arrive in the query string and a negatively-cached miss would let any caller grow a process-lifetime dictionary one bogus name at a time while the request still returned a tidy 400 (.../Services/Filtering/QueryFilterServicePropertyCacheTests.cs:14, §11/§12). The hoisted user use cases hold a large block: ChangePasswordHandlerBaseTests pins verify-before-write ordering and the no-save-on-invariant-failure rule (.../Users/ChangePasswordHandlerBaseTests.cs:15, ADR-032); DeleteUserHandlerBaseTests pins the owner-or-privileged-role gate, the delete-then-anonymize-then-save ordering and the post-commit queue (.../Users/DeleteUserHandlerBaseTests.cs:14); UserOwnershipRule gets its own tests for the self-service authorization check that was written out four times across the two apps before the hoist (.../Users/UserOwnershipRuleTests.cs:11); SoftDeletedUserValidatorTests proves the BR-133 check is one query with the soft-delete global filter deliberately bypassed (.../Users/SoftDeletedUserValidatorTests.cs:13); and ExportUserDataHandlerBaseTests drives ExportUserDataHandlerBase<TUser, TQuery> through the ownership gate, the read-only account load and the best-effort section fan-out, where a section that throws or reports itself unavailable must degrade and let the rest of the package travel (a data-subject request is a legal deadline, so a partial export beats a failed one) while a cancelled section propagates instead (.../Users/ExportUserDataHandlerBaseTests.cs:17, ADR-076 / §30). The password-recovery pair is the newest addition to that family and both files are written around anti-enumeration: a malformed address, an unknown address, a throttled request and a failed send are all indistinguishable successes, and the mail that IS sent carries both the prefilled link and the raw token (.../Users/ForgotPasswordHandlerBaseTests.cs:20), while every rejection on the reset leg collapses to one generic Auth.InvalidResetToken, the write happens only when the aggregate accepts it, the token is consumed before the save, and a successful reset clears the lockout so the new credential is immediately usable (.../Users/ResetPasswordHandlerBaseTests.cs:18, ADR-091). RefreshSessionManagementTests covers the per-device surface layered on that workflow: the sid claim stamped on every issued access token, a refresh stamping the successor session's id rather than the predecessor's, an older access token with no sid still accepted, and a device list that carries the client fields and never the token material (.../Auth/RefreshSessionManagementTests.cs:26, ADR-097). Alongside them, SoftDeletedUserCache has its key shape and culture invariance pinned because the API middleware reads that exact key (.../Auth/SoftDeletedUserCacheTests.cs:13); CachingDecoratorConstructorSelectionTests pins that the container picks the logger-bearing constructor on both caching decorators, since if selection ever flipped the decorators would keep working and every test would stay green while production silently stopped reporting cache failures (.../Decorators/CachingDecoratorConstructorSelectionTests.cs:21); and CachingDecoratorTenantScopingTests covers the tenancy half of the same pair, where ICacheService is a singleton that cannot see the scoped tenant, so the key transformation is the isolation (.../Decorators/CachingDecoratorTenantScopingTests.cs:16, ADR-073 / §11). The two newer decorator rings have their own files: AuthorizationCommandDecoratorTests (.../Decorators/AuthorizationCommandDecoratorTests.cs:11) and its query twin cover the permission check that runs inside the pipeline rather than only at the controller, and TimeoutCommandDecoratorTests (.../Decorators/TimeoutCommandDecoratorTests.cs:9) covers the per-handler execution budget. Read-pipeline determinism and projection pushdown form their own block (ADR-055, §12): EntityQueryPipelineOrderingTests pins that a paginated read always ends up with a total order, because Skip/Take over a partial order is undefined and lets one row appear on two consecutive pages while another appears on none, while an unpaginated read is deliberately left alone (.../Services/EntityQueryPipelineOrderingTests.cs:16), with the tie-break parameter covered directly and its omission pinned as behavior-preserving (.../Services/QueryFieldServiceTieBreakTests.cs:11); EntityQueryService gains a projection path that selects DTO columns and never calls the mapper, with a fallback to materialize-then-map when the read cannot be projected (.../Services/EntityQueryServiceProjectionTests.cs:19), and its two-constructor arrangement is proved to actually resolve under Microsoft.Extensions.DependencyInjection, which has no notion of an optional dependency (.../Services/EntityQueryServiceResolutionTests.cs:19); PushNotificationDTOProjectorTests is the safety property underneath all of that, since the two paths are chosen by whether a projector is registered and a divergence would make the response depend on a DI detail (.../Notifications/PushNotificationDTOProjectorTests.cs:16). BestEffortTests covers the helper the fire-and-forget call sites share: the success path is transparent, a failure is swallowed with exactly one Warning and one metric increment, and cancellation is deliberately NOT part of the swallow (.../Services/BestEffortTests.cs:13). The newest block is composition and contract guards. EventUpcasterRegistry covers the schema-evolution ladder: identity for an unregistered contract, a V1→V2→V3 chain applied in order even when registered out of order, envelope (MessageId/DateOccurred) preservation across every hop whether or not the author copies it, and the three constructor-time misconfigurations (duplicate source, cycle, self-map) that must throw naming the offender (.../Services/EventUpcasterRegistryTests.cs:19, ADR-090); its sample contracts live in the test assembly only so the framework's frozen contract snapshot never churns on them. ApplicationPipelineCompositionTests covers AddMmcaApplicationPipeline, the one call that replaces the manual AddApplication → scan → AddApplicationDecorators sequence, and the registration-time seal that turns a handler registered after the decorators (which Scrutor would silently leave undecorated) into a startup failure (.../ApplicationPipelineCompositionTests.cs:20, ADR-014). PackageGraphPurityTests reads the csproj itself, because the IL-based fitness rules can only see what the code uses and are blind to what a PackageReference drags in: one web or persistence package would enter the graph of every consumer of this host-agnostic package and nothing else in the build would notice (.../PackageGraphPurityTests.cs:15, §32). CqrsContractInspectorTests covers the inspector behind the fitness rule that a command marker is never handled as a query and that a request's declared result type matches its handler's (.../UseCases/CqrsContractInspectorTests.cs:7), and ScopedIntegrationEventHandlerBaseTests pins the base that opens one DI scope per delivery (integration-event handlers are singletons and cannot hold a scoped service), disposes it on every path, logs a failure exactly once before letting it propagate so the delivery mechanism can redeliver, and passes cancellation through unlogged because host shutdown is not a delivery failure (.../DomainEvents/ScopedIntegrationEventHandlerBaseTests.cs:15). The framework's largest suite by breadth; fast unit tests with mocked infrastructure. + 401 + The CQRS engine: the decorator pipeline in its registered nesting order, which now carries two validating rings (commands: FeatureGate→Authorization→Logging→Caching→Validating→Timeout→Transactional→handler, MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:134-140; queries: FeatureGate→Authorization→Logging→Caching→Validating→Timeout→handler, .../DependencyInjection.cs:138-143, both registered innermost-first because Scrutor's TryDecorate applies in reverse, .../DependencyInjection.cs:115), the opt-in MiniProfiler decorators added by AddApplicationProfiling (.../DependencyInjection.cs:565) and the CqrsMetrics counters/histograms (MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Decorators/CqrsMetricsTests.cs:20), ModuleLoader topological ordering, DomainEventDispatcher plus the swallow-and-log SafeDomainEventHandler base (.../DomainEvents/SafeDomainEventHandlerTests.cs:14), validation, the IMessageBus abstraction, entity-query projection/paging and the per-type filter strategies, the cross-source CrossSourceSpecification helper, the magic-byte upload sniffer behind ImageContentSniffer (.../ImageContentSnifferTests.cs:12, ADR-045), and the notification read/send handlers driven by an injected TimeProvider test clock. Two older files defend non-obvious properties rather than behavior: PagingMathTests pins the page arithmetic (.../Services/Query/PagingMathTests.cs:12), and QueryFilterServicePropertyCacheTests asserts on the real static property cache inside QueryFilterService to prove that caching a miss never happens, since filter names arrive in the query string and a negatively-cached miss would let any caller grow a process-lifetime dictionary one bogus name at a time while the request still returned a tidy 400 (.../Services/Filtering/QueryFilterServicePropertyCacheTests.cs:14, §11/§12). The hoisted user use cases hold a large block: ChangePasswordHandlerBaseTests pins verify-before-write ordering and the no-save-on-invariant-failure rule (.../Users/ChangePasswordHandlerBaseTests.cs:15, ADR-032); DeleteUserHandlerBaseTests pins the owner-or-privileged-role gate, the delete-then-anonymize-then-save ordering and the post-commit queue (.../Users/DeleteUserHandlerBaseTests.cs:14); UserOwnershipRule gets its own tests for the self-service authorization check that was written out four times across the two apps before the hoist (.../Users/UserOwnershipRuleTests.cs:11); SoftDeletedUserValidatorTests proves the BR-133 check is one query with the soft-delete global filter deliberately bypassed (.../Users/SoftDeletedUserValidatorTests.cs:13); and ExportUserDataHandlerBaseTests drives ExportUserDataHandlerBase<TUser, TQuery> through the ownership gate, the read-only account load and the best-effort section fan-out, where a section that throws or reports itself unavailable must degrade and let the rest of the package travel (a data-subject request is a legal deadline, so a partial export beats a failed one) while a cancelled section propagates instead (.../Users/ExportUserDataHandlerBaseTests.cs:17, ADR-076 / §30). The password-recovery pair is the newest addition to that family and both files are written around anti-enumeration: a malformed address, an unknown address, a throttled request and a failed send are all indistinguishable successes, and the mail that IS sent carries both the prefilled link and the raw token (.../Users/ForgotPasswordHandlerBaseTests.cs:20), while every rejection on the reset leg collapses to one generic Auth.InvalidResetToken, the write happens only when the aggregate accepts it, the token is consumed before the save, and a successful reset clears the lockout so the new credential is immediately usable (.../Users/ResetPasswordHandlerBaseTests.cs:18, ADR-091). RefreshSessionManagementTests covers the per-device surface layered on that workflow: the sid claim stamped on every issued access token, a refresh stamping the successor session's id rather than the predecessor's, an older access token with no sid still accepted, and a device list that carries the client fields and never the token material (.../Auth/RefreshSessionManagementTests.cs:26, ADR-097). Alongside them, SoftDeletedUserCache has its key shape and culture invariance pinned because the API middleware reads that exact key (.../Auth/SoftDeletedUserCacheTests.cs:13); CachingDecoratorConstructorSelectionTests pins that the container picks the logger-bearing constructor on both caching decorators, since if selection ever flipped the decorators would keep working and every test would stay green while production silently stopped reporting cache failures (.../Decorators/CachingDecoratorConstructorSelectionTests.cs:21); and CachingDecoratorTenantScopingTests covers the tenancy half of the same pair, where ICacheService is a singleton that cannot see the scoped tenant, so the key transformation is the isolation (.../Decorators/CachingDecoratorTenantScopingTests.cs:16, ADR-073 / §11). The two newer decorator rings have their own files: AuthorizationCommandDecoratorTests (.../Decorators/AuthorizationCommandDecoratorTests.cs:11) and its query twin cover the permission check that runs inside the pipeline rather than only at the controller, and TimeoutCommandDecoratorTests (.../Decorators/TimeoutCommandDecoratorTests.cs:9) covers the per-handler execution budget. Read-pipeline determinism and projection pushdown form their own block (ADR-055, §12): EntityQueryPipelineOrderingTests pins that a paginated read always ends up with a total order, because Skip/Take over a partial order is undefined and lets one row appear on two consecutive pages while another appears on none, while an unpaginated read is deliberately left alone (.../Services/EntityQueryPipelineOrderingTests.cs:16), with the tie-break parameter covered directly and its omission pinned as behavior-preserving (.../Services/QueryFieldServiceTieBreakTests.cs:11); EntityQueryService gains a projection path that selects DTO columns and never calls the mapper, with a fallback to materialize-then-map when the read cannot be projected (.../Services/EntityQueryServiceProjectionTests.cs:19), and its two-constructor arrangement is proved to actually resolve under Microsoft.Extensions.DependencyInjection, which has no notion of an optional dependency (.../Services/EntityQueryServiceResolutionTests.cs:19); PushNotificationDTOProjectorTests is the safety property underneath all of that, since the two paths are chosen by whether a projector is registered and a divergence would make the response depend on a DI detail (.../Notifications/PushNotificationDTOProjectorTests.cs:16). BestEffortTests covers the helper the fire-and-forget call sites share: the success path is transparent, a failure is swallowed with exactly one Warning and one metric increment, and cancellation is deliberately NOT part of the swallow (.../Services/BestEffortTests.cs:13). The newest block is composition and contract guards. EventUpcasterRegistry covers the schema-evolution ladder: identity for an unregistered contract, a V1→V2→V3 chain applied in order even when registered out of order, envelope (MessageId/DateOccurred) preservation across every hop whether or not the author copies it, and the three constructor-time misconfigurations (duplicate source, cycle, self-map) that must throw naming the offender (.../Services/EventUpcasterRegistryTests.cs:19, ADR-090); its sample contracts live in the test assembly only so the framework's frozen contract snapshot never churns on them. ApplicationPipelineCompositionTests covers AddMmcaApplicationPipeline, the one call that replaces the manual AddApplication → scan → AddApplicationDecorators sequence, and the registration-time seal that turns a handler registered after the decorators (which Scrutor would silently leave undecorated) into a startup failure (.../ApplicationPipelineCompositionTests.cs:20, ADR-014). PackageGraphPurityTests reads the csproj itself, because the IL-based fitness rules can only see what the code uses and are blind to what a PackageReference drags in: one web or persistence package would enter the graph of every consumer of this host-agnostic package and nothing else in the build would notice (.../PackageGraphPurityTests.cs:15, §32). CqrsContractInspectorTests covers the inspector behind the fitness rule that a command marker is never handled as a query and that a request's declared result type matches its handler's (.../UseCases/CqrsContractInspectorTests.cs:7), and ScopedIntegrationEventHandlerBaseTests pins the base that opens one DI scope per delivery (integration-event handlers are singletons and cannot hold a scoped service), disposes it on every path, logs a failure exactly once before letting it propagate so the delivery mechanism can redeliver, and passes cancellation through unlogged because host shutdown is not a delivery failure (.../DomainEvents/ScopedIntegrationEventHandlerBaseTests.cs:15). Its newest types are fixtures: Harness and RecordingSetter back MarkAllNotificationsReadHandlerTests (MMCA.Common.UI.Web.Tests 12 24/Core/MMCA.Common.Application.Tests/Notifications/MarkAllNotificationsReadHandlerTests.cs:141, :207), and ScanFailingAssembly backs ModuleLoaderTests (.../Modules/ModuleLoaderTests.cs:325). The framework's largest suite by breadth; fast unit tests with mocked infrastructure. MMCA.Common.Infrastructure.Tests - 499 - The widest layer: EF repositories + Unit of Work, the multi-database resolver/registry (DataSourceResolver, EntityDataSourceRegistry, DbContextFactory) with its transaction coverage (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:33) and the cross-data-source degrade convention (.../Persistence/DataSources/CrossDataSourceDegradeConventionTests.cs:24), the outbox processor plus its wake signal (.../Persistence/Outbox/Processing/OutboxSignalTests.cs:13) and the consumer-side EfInboxStore idempotency ledger (.../Persistence/Inbox/EfInboxStoreTests.cs:27, ADR-021), caching, JWT issuance + JWKS + the login-attempt lockout service (.../Auth/LoginProtectionServiceTests.cs:14), column-level encryption (.../Persistence/Encryption/EncryptedStringConverterTests.cs:6), the filtered-unique-index soft-delete convention (.../Persistence/Conventions/SoftDeleteUniqueIndexConventionTests.cs:24), image processing (.../Storage/ImageSharpImageProcessorTests.cs:15), the SignalR push + live-channel plumbing, the message-bus implementations, the polyglot Cosmos-config portability suite (ADR-018), and the in-repo disaster-recovery database-restore drill (.../Resilience/DatabaseRestoreDrillTests.cs:18, a CI-gated RTO baseline, ADR-009 / §29). Three files are pure §12 performance guards and are the only place the emitted SQL or the tracker's work is inspected at all: QueryParameterizationTests asserts that the dynamic-LINQ filter and sort strategies send their values as SQL parameters rather than inlined literals, which is what decides whether SQL Server reuses a plan and whether EF's compiled-query cache hits (.../Persistence/Specifications/QueryParameterizationTests.cs:26); SaveChangeDetectionTests pins that a save runs change detection exactly once and that suppressing the extra passes lost the tracker no actual changes (.../Persistence/Interceptors/SaveChangeDetectionTests.cs:24); and PeriodicBackgroundServiceTests drives PeriodicBackgroundService deterministically through a FakeTimeProvider clock to cover the enablement gate, the startup delay, interval-driven cycles, and the failing-cycle-never-kills-the-loop contract (.../Scheduling/PeriodicBackgroundServiceTests.cs:15). A second cluster hardens the save path: DbContextFactorySaveIntegrityTests pins the post-loop assertion that turns silent data loss into a failure, since the save loop is bounded and in-process domain-event dispatch runs inside it, so a handler can leave tracked changes behind that the loop will never reach (.../Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:29); DbContextFactoryCommitAmbiguityTests covers the case where a commit-phase failure has an unknowable outcome, because the database may have applied the transaction and lost only the acknowledgement (.../Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:34); DomainEventCaptureExclusionTests proves event capture is scoped-exclusion aware (.../Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:26); and EFRepositoryAuditStampTests pins that the repository's own save entry points stamp the acting user like the unit of work does, after they were found calling the plain EF overloads and attributing everything written through them to the system sentinel (.../Persistence/Repositories/EFRepositoryAuditStampTests.cs:32). Two sit outside persistence: the IDistributedLock pair, where RedisDistributedLock must acquire through a single atomic SET NX PX carrying a TTL and release through a compare-and-delete on the owner token (.../Concurrency/RedisDistributedLockTests.cs:15), with InProcessDistributedLock held to the same contract as the no-Redis fallback (.../Concurrency/InProcessDistributedLockTests.cs:12); and AzureNotificationHubDeviceRegistrar, whose tests focus on the ownership check, since installation ids are client-supplied and the user-scoped delete must verify the user:{id} tag the upsert stamps before it removes anything (.../Notifications/Push/AzureNotificationHubDeviceRegistrarTests.cs:16, ADR-044). The enterprise wave contributes four clusters here. Tenancy: ApplicationDbContextTenantFilterTests proves over real SQLite that the named Tenant global filter composes with SoftDelete rather than replacing it, that a null-tenant system context still sees every row, and that one cached model serves two tenants at once with disjoint results (.../Persistence/Tenancy/ApplicationDbContextTenantFilterTests.cs:15); TenantSaveChangesInterceptorTests covers the write side, where an insert is stamped from the scope and any modify/delete that would cross the boundary throws (.../Persistence/Tenancy/TenantSaveChangesInterceptorTests.cs:12); TenantDataSourceTargetTests covers the (source, tenant) expansion the background sweeps run on, because a tenant with its own database has its own outbox, inbox and trail tables and nothing else opens that database (.../Persistence/Tenancy/TenantDataSourceTargetTests.cs:18); AddMultiTenancyTests pins the DI surface, the fail-closed defaults and the startup validation (.../Persistence/Tenancy/AddMultiTenancyTests.cs:19); and TenantContextTests pins the one-write scope (.../Context/TenantContextTests.cs:10). Audit trail: AuditTrailSaveChangesInterceptorTests asserts every case against a real SQLite round-trip, so what is asserted is what commits, one summary row per insert/delete, one row per changed property on a modify, nothing for an entity marked modified but unchanged, and a [Pii] property recording the redaction token and never the clear value (.../Persistence/AuditTrail/AuditTrailSaveChangesInterceptorTests.cs:18, ADR-075 / §30); AuditTrailReader covers newest-first paging with clamped arguments and an empty answer when the source has no trail table (.../Persistence/AuditTrail/AuditTrailReaderTests.cs:20); AuditTrailCleanupJobTests covers the retention purge and its two do-nothing paths (.../Persistence/AuditTrail/AuditTrailCleanupJobTests.cs:22). Scheduler: ScheduledJobRunner is driven over a FakeTimeProvider and an in-memory ScheduledJobs table so the schedule arithmetic is exact rather than timing-dependent, covering registration sync, the configuration override, the claim lease, outcome recording, and the missed-run policy where a clock that jumped past many occurrences runs once and advances rather than storming (.../Scheduling/ScheduledJobRunnerTests.cs:22, ADR-074); CronosNextOccurrenceTests pins the cron grammar, the strictly-after semantics and the UTC-only clock that keeps a schedule stable across both daylight-saving transitions (.../Scheduling/CronosNextOccurrenceTests.cs:11); AddScheduledJobsTests pins the opt-in DI shape (.../Scheduling/AddScheduledJobsTests.cs:16); SchedulerMetrics pins the meter name a host registers for export and the instrument names, units and tags an operator builds dashboards on (.../Scheduling/SchedulerMetricsTests.cs:13); and SchedulerModelGateTests builds the real model under each combination of Scheduler:Enabled and data source to prove a host that never opted in gets exactly the model it had before the scheduler shipped (.../Scheduling/SchedulerModelGateTests.cs:26). HybridCache: HybridCacheService runs against a real in-process AddHybridCache with a recording L2, and the key-shape assertions carry the whole design, since an entry written by the old cache at the same logical key must be a clean miss, never the WRONGTYPE fault that motivated the split (.../Caching/HybridCacheServiceTests.cs:24, ADR-077). The read contract (ADR-055) is covered against a real provider rather than a mock, because all of it is translation: EFReadRepositoryKeysetPagingTests covers keyset paging end to end, cursor round-trips, next-page detection, the non-unique sort key that makes the identifier tie-break load-bearing, and the two rejection paths of an unknown sort column and a malformed cursor (.../Persistence/Repositories/Read/EFReadRepositoryKeysetPagingTests.cs:16); EFReadRepositorySpecificationTests covers the specification-driven members plus tracking and soft-delete scope (.../Persistence/Repositories/Read/EFReadRepositorySpecificationTests.cs:16); EFReadRepositoryProjectedFilterTests pins the ignoreQueryFilters flag on GetProjectedAsync against the production named soft-delete filter, because without it any caller needing deleted rows (an admin restore screen, a GDPR export) had to abandon the projection (.../Persistence/Repositories/Read/EFReadRepositoryProjectedFilterTests.cs:16); SpecificationEvaluator is exercised over SQLite for the ordering chain bound back to its concrete key type by reflection, includes with the collection split-query switch, and Skip/Take (.../Persistence/Specifications/SpecificationEvaluatorTests.cs:15); and PushNotificationProjectionTranslationTests proves a real provider translates the projection into SQL, enum-to-string conversion included, a failure that would otherwise surface only at runtime (.../Persistence/PushNotificationProjectionTranslationTests.cs:15). Its newest work is the auth, outbox and messaging edges. Auth: PasswordResetTokenServiceTests verifies the token lifecycle single use, the wrong-token attempt cap, the per-email request throttle, the address normalization that keeps casing variants on one record, and the hash-at-rest guarantee (.../Auth/PasswordResetTokenServiceTests.cs:18, ADR-091); EFRefreshSessionStoreRotationTests asserts the rotation claim against a real SQLite database because the guarantee is the database's, not the change tracker's, since two requests presenting the same still-live token each load their own tracked copy with RevokedAt null and an in-memory check-then-act would let both mint a successor (.../Persistence/Auth/EFRefreshSessionStoreRotationTests.cs:27, ADR-050); RefreshSessionCleanupServiceTests covers the retention sweep, where a row is a candidate once it stopped being usable rather than once it was created (.../Persistence/Auth/RefreshSessionCleanupServiceTests.cs:33); and PasswordHasherSecurityTests pins the cryptographic parameters rather than the round trip, since a hasher that hashes and verifies with the same weak settings still round-trips: two known-answer tests recompute the digest independently, a negative one proves the iteration count participates in verification, and reflection pins the private constants so lowering one fails the build (.../Auth/PasswordHasherSecurityTests.cs:18, ADR-102). Outbox and migrations: OutboxAdministration is the operator path that gives a dead-lettered event a way back into delivery instead of leaving "wait for the retention sweep" as the only ending, exercised over a real SQLite outbox table so the set-based replay update is the one that would run in production (.../Persistence/Outbox/Administration/OutboxAdministrationTests.cs:27); OutboxProcessorOrderingTests covers ordered delivery, where rows sharing an OrderingKey must reach the bus one at a time in OccurredOn order and that guarantee must survive the two things a batch-local sort cannot cover, a successor arriving in a later batch and a second replica polling the same table (.../Persistence/Outbox/Processing/OutboxProcessorOrderingTests.cs:35); DependencyInjectionOutboxGateTests pins the registration gate, where an in-process host pays for no outbox table or poll loop, a broker host always gets one, and asking for a broker without the outbox fails at registration rather than silently dropping every cross-service event (.../DependencyInjectionOutboxGateTests.cs:15, ADR-003); MigrationApplyProofTests runs a committed migration from a separate fixture assembly against a real SQLite file through DbContextFactory, the same call the "Migrate" strategy makes, so "the framework applies migrations" is proved rather than assumed (.../Persistence/MigrationApplyProofTests.cs:35), with DbContextFactoryMigrationTargetTests proving which sources the factory acts on (.../Persistence/DbContexts/DbContextFactoryMigrationTargetTests.cs:25). Messaging: UpcastingIntegrationEventConsumer is the draining consumer bound to a retired contract, deduping on the original message id, upcasting to the terminal contract, dispatching to that contract's handlers, and recording the inbox row only after every handler succeeded (.../Messaging/Consumers/UpcastingIntegrationEventConsumerTests.cs:26, ADR-090); EventUpcasterStartupValidatorTests covers the startup check over the registered ladders (.../Messaging/Consumers/EventUpcasterStartupValidatorTests.cs:20); IntegrationEventConsumerHarnessTests drives the consumers through a real MassTransit bus on the in-memory transport, because the registration binding, the MessageId surviving serialization, and a handler exception genuinely becoming the Fault<TEvent> that FaultIntegrationEventConsumer subscribes to are broker behaviors no direct Consume call can show (.../Messaging/Consumers/IntegrationEventConsumerHarnessTests.cs:28, and the fault consumer's own file at .../Messaging/Consumers/FaultIntegrationEventConsumerTests.cs:15, ADR-087); InboxDisabledWarningService covers the one startup line that keeps a disabled dedup store from looking exactly like an enabled one (.../Persistence/Inbox/InboxDisabledWarningServiceTests.cs:11); and ServiceBusEmulatorSupportTests pins the emulator branch's detection and derived management connection string, so production cannot be diverted into it by accident (.../Messaging/ServiceBusEmulatorSupportTests.cs:15). Two more round it out: SqlServerUniqueConstraintViolationDetectorTests covers both halves of the detector, the authoritative provider error numbers and the message fallback, building a real SqlException through the provider's own non-public factory and reporting itself skipped rather than failing the build if a provider upgrade moves that factory (.../Persistence/SqlServerUniqueConstraintViolationDetectorTests.cs:17); and ConnectionStringSettingsValidatorTests covers the one startup rule that spans two configuration sections, a host must be able to reach some database declared either top level or as a named DataSources entry, which is why it is a validator rather than a [Required] annotation (.../Settings/ConnectionStringSettingsValidatorTests.cs:15, ADR-070). Mostly unit with EF-InMemory/SQLite boundaries (no real SQL Server here). + 514 + The widest layer: EF repositories + Unit of Work, the multi-database resolver/registry (DataSourceResolver, EntityDataSourceRegistry, DbContextFactory) with its transaction coverage (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/DbContexts/DbContextFactoryTransactionTests.cs:37) and the cross-data-source degrade convention (.../Persistence/DataSources/CrossDataSourceDegradeConventionTests.cs:24), the outbox processor plus its wake signal (.../Persistence/Outbox/Processing/OutboxSignalTests.cs:13) and the consumer-side EfInboxStore idempotency ledger (.../Persistence/Inbox/EfInboxStoreTests.cs:27, ADR-021), caching, JWT issuance + JWKS + the login-attempt lockout service (.../Auth/LoginProtectionServiceTests.cs:14), column-level encryption (.../Persistence/Encryption/EncryptedStringConverterTests.cs:6), the filtered-unique-index soft-delete convention (.../Persistence/Conventions/SoftDeleteUniqueIndexConventionTests.cs:24), image processing (.../Storage/ImageSharpImageProcessorTests.cs:15), the SignalR push + live-channel plumbing, the message-bus implementations, the polyglot Cosmos-config portability suite (ADR-018), and the in-repo disaster-recovery database-restore drill (.../Resilience/DatabaseRestoreDrillTests.cs:18, a CI-gated RTO baseline, ADR-009 / §29). Three files are pure §12 performance guards and are the only place the emitted SQL or the tracker's work is inspected at all: QueryParameterizationTests asserts that the dynamic-LINQ filter and sort strategies send their values as SQL parameters rather than inlined literals, which is what decides whether SQL Server reuses a plan and whether EF's compiled-query cache hits (.../Persistence/Specifications/QueryParameterizationTests.cs:26); SaveChangeDetectionTests pins that a save runs change detection exactly once and that suppressing the extra passes lost the tracker no actual changes (.../Persistence/Interceptors/SaveChangeDetectionTests.cs:24); and PeriodicBackgroundServiceTests drives PeriodicBackgroundService deterministically through a FakeTimeProvider clock to cover the enablement gate, the startup delay, interval-driven cycles, and the failing-cycle-never-kills-the-loop contract (.../Scheduling/PeriodicBackgroundServiceTests.cs:15). A second cluster hardens the save path: DbContextFactorySaveIntegrityTests pins the post-loop assertion that turns silent data loss into a failure, since the save loop is bounded and in-process domain-event dispatch runs inside it, so a handler can leave tracked changes behind that the loop will never reach (.../Persistence/DbContexts/DbContextFactorySaveIntegrityTests.cs:29); DbContextFactoryCommitAmbiguityTests covers the case where a commit-phase failure has an unknowable outcome, because the database may have applied the transaction and lost only the acknowledgement (.../Persistence/DbContexts/DbContextFactoryCommitAmbiguityTests.cs:34); DomainEventCaptureExclusionTests proves event capture is scoped-exclusion aware (.../Persistence/Interceptors/DomainEventCaptureExclusionTests.cs:26); and EFRepositoryAuditStampTests pins that the repository's own save entry points stamp the acting user like the unit of work does, after they were found calling the plain EF overloads and attributing everything written through them to the system sentinel (.../Persistence/Repositories/EFRepositoryAuditStampTests.cs:32). Two sit outside persistence: the IDistributedLock pair, where RedisDistributedLock must acquire through a single atomic SET NX PX carrying a TTL and release through a compare-and-delete on the owner token (.../Concurrency/RedisDistributedLockTests.cs:15), with InProcessDistributedLock held to the same contract as the no-Redis fallback (.../Concurrency/InProcessDistributedLockTests.cs:12); and AzureNotificationHubDeviceRegistrar, whose tests focus on the ownership check, since installation ids are client-supplied and the user-scoped delete must verify the user:{id} tag the upsert stamps before it removes anything (.../Notifications/Push/AzureNotificationHubDeviceRegistrarTests.cs:16, ADR-044). The enterprise wave contributes four clusters here. Tenancy: ApplicationDbContextTenantFilterTests proves over real SQLite that the named Tenant global filter composes with SoftDelete rather than replacing it, that a null-tenant system context still sees every row, and that one cached model serves two tenants at once with disjoint results (.../Persistence/Tenancy/ApplicationDbContextTenantFilterTests.cs:15); TenantSaveChangesInterceptorTests covers the write side, where an insert is stamped from the scope and any modify/delete that would cross the boundary throws (.../Persistence/Tenancy/TenantSaveChangesInterceptorTests.cs:12); TenantDataSourceTargetTests covers the (source, tenant) expansion the background sweeps run on, because a tenant with its own database has its own outbox, inbox and trail tables and nothing else opens that database (.../Persistence/Tenancy/TenantDataSourceTargetTests.cs:18); AddMultiTenancyTests pins the DI surface, the fail-closed defaults and the startup validation (.../Persistence/Tenancy/AddMultiTenancyTests.cs:19); and TenantContextTests pins the one-write scope (.../Context/TenantContextTests.cs:10). Audit trail: AuditTrailSaveChangesInterceptorTests asserts every case against a real SQLite round-trip, so what is asserted is what commits, one summary row per insert/delete, one row per changed property on a modify, nothing for an entity marked modified but unchanged, and a [Pii] property recording the redaction token and never the clear value (.../Persistence/AuditTrail/AuditTrailSaveChangesInterceptorTests.cs:18, ADR-075 / §30); AuditTrailReader covers newest-first paging with clamped arguments and an empty answer when the source has no trail table (.../Persistence/AuditTrail/AuditTrailReaderTests.cs:20); AuditTrailCleanupJobTests covers the retention purge and its two do-nothing paths (.../Persistence/AuditTrail/AuditTrailCleanupJobTests.cs:22). Scheduler: ScheduledJobRunner is driven over a FakeTimeProvider and an in-memory ScheduledJobs table so the schedule arithmetic is exact rather than timing-dependent, covering registration sync, the configuration override, the claim lease, outcome recording, and the missed-run policy where a clock that jumped past many occurrences runs once and advances rather than storming (.../Scheduling/ScheduledJobRunnerTests.cs:22, ADR-074); CronosNextOccurrenceTests pins the cron grammar, the strictly-after semantics and the UTC-only clock that keeps a schedule stable across both daylight-saving transitions (.../Scheduling/CronosNextOccurrenceTests.cs:11); AddScheduledJobsTests pins the opt-in DI shape (.../Scheduling/AddScheduledJobsTests.cs:16); SchedulerMetrics pins the meter name a host registers for export and the instrument names, units and tags an operator builds dashboards on (.../Scheduling/SchedulerMetricsTests.cs:13); and SchedulerModelGateTests builds the real model under each combination of Scheduler:Enabled and data source to prove a host that never opted in gets exactly the model it had before the scheduler shipped (.../Scheduling/SchedulerModelGateTests.cs:26). HybridCache: HybridCacheService runs against a real in-process AddHybridCache with a recording L2, and the key-shape assertions carry the whole design, since an entry written by the old cache at the same logical key must be a clean miss, never the WRONGTYPE fault that motivated the split (.../Caching/HybridCacheServiceTests.cs:24, ADR-077). The read contract (ADR-055) is covered against a real provider rather than a mock, because all of it is translation: EFReadRepositoryKeysetPagingTests covers keyset paging end to end, cursor round-trips, next-page detection, the non-unique sort key that makes the identifier tie-break load-bearing, and the two rejection paths of an unknown sort column and a malformed cursor (.../Persistence/Repositories/Read/EFReadRepositoryKeysetPagingTests.cs:16); EFReadRepositorySpecificationTests covers the specification-driven members plus tracking and soft-delete scope (.../Persistence/Repositories/Read/EFReadRepositorySpecificationTests.cs:16); EFReadRepositoryProjectedFilterTests pins the ignoreQueryFilters flag on GetProjectedAsync against the production named soft-delete filter, because without it any caller needing deleted rows (an admin restore screen, a GDPR export) had to abandon the projection (.../Persistence/Repositories/Read/EFReadRepositoryProjectedFilterTests.cs:16); SpecificationEvaluator is exercised over SQLite for the ordering chain bound back to its concrete key type by reflection, includes with the collection split-query switch, and Skip/Take (.../Persistence/Specifications/SpecificationEvaluatorTests.cs:15); and PushNotificationProjectionTranslationTests proves a real provider translates the projection into SQL, enum-to-string conversion included, a failure that would otherwise surface only at runtime (.../Persistence/PushNotificationProjectionTranslationTests.cs:15). Its newest work is the auth, outbox and messaging edges. Auth: PasswordResetTokenServiceTests verifies the token lifecycle single use, the wrong-token attempt cap, the per-email request throttle, the address normalization that keeps casing variants on one record, and the hash-at-rest guarantee (.../Auth/PasswordResetTokenServiceTests.cs:18, ADR-091); EFRefreshSessionStoreRotationTests asserts the rotation claim against a real SQLite database because the guarantee is the database's, not the change tracker's, since two requests presenting the same still-live token each load their own tracked copy with RevokedAt null and an in-memory check-then-act would let both mint a successor (.../Persistence/Auth/EFRefreshSessionStoreRotationTests.cs:27, ADR-050); RefreshSessionCleanupServiceTests covers the retention sweep, where a row is a candidate once it stopped being usable rather than once it was created (.../Persistence/Auth/RefreshSessionCleanupServiceTests.cs:33); and PasswordHasherSecurityTests pins the cryptographic parameters rather than the round trip, since a hasher that hashes and verifies with the same weak settings still round-trips: two known-answer tests recompute the digest independently, a negative one proves the iteration count participates in verification, and reflection pins the private constants so lowering one fails the build (.../Auth/PasswordHasherSecurityTests.cs:18, ADR-102). Outbox and migrations: OutboxAdministration is the operator path that gives a dead-lettered event a way back into delivery instead of leaving "wait for the retention sweep" as the only ending, exercised over a real SQLite outbox table so the set-based replay update is the one that would run in production (.../Persistence/Outbox/Administration/OutboxAdministrationTests.cs:27); OutboxProcessorOrderingTests covers ordered delivery, where rows sharing an OrderingKey must reach the bus one at a time in OccurredOn order and that guarantee must survive the two things a batch-local sort cannot cover, a successor arriving in a later batch and a second replica polling the same table (.../Persistence/Outbox/Processing/OutboxProcessorOrderingTests.cs:35); DependencyInjectionOutboxGateTests pins the registration gate, where an in-process host pays for no outbox table or poll loop, a broker host always gets one, and asking for a broker without the outbox fails at registration rather than silently dropping every cross-service event (.../DependencyInjectionOutboxGateTests.cs:15, ADR-003); MigrationApplyProofTests runs a committed migration from a separate fixture assembly against a real SQLite file through DbContextFactory, the same call the "Migrate" strategy makes, so "the framework applies migrations" is proved rather than assumed (.../Persistence/MigrationApplyProofTests.cs:35), with DbContextFactoryMigrationTargetTests proving which sources the factory acts on (.../Persistence/DbContexts/DbContextFactoryMigrationTargetTests.cs:25). Messaging: UpcastingIntegrationEventConsumer is the draining consumer bound to a retired contract, deduping on the original message id, upcasting to the terminal contract, dispatching to that contract's handlers, and recording the inbox row only after every handler succeeded (.../Messaging/Consumers/UpcastingIntegrationEventConsumerTests.cs:26, ADR-090); EventUpcasterStartupValidatorTests covers the startup check over the registered ladders (.../Messaging/Consumers/EventUpcasterStartupValidatorTests.cs:20); IntegrationEventConsumerHarnessTests drives the consumers through a real MassTransit bus on the in-memory transport, because the registration binding, the MessageId surviving serialization, and a handler exception genuinely becoming the Fault<TEvent> that FaultIntegrationEventConsumer subscribes to are broker behaviors no direct Consume call can show (.../Messaging/Consumers/IntegrationEventConsumerHarnessTests.cs:28, and the fault consumer's own file at .../Messaging/Consumers/FaultIntegrationEventConsumerTests.cs:15, ADR-087); InboxDisabledWarningService covers the one startup line that keeps a disabled dedup store from looking exactly like an enabled one (.../Persistence/Inbox/InboxDisabledWarningServiceTests.cs:11); and ServiceBusEmulatorSupportTests pins the emulator branch's detection and derived management connection string, so production cannot be diverted into it by accident (.../Messaging/ServiceBusEmulatorSupportTests.cs:15). Two more round it out: SqlServerUniqueConstraintViolationDetectorTests covers both halves of the detector, the authoritative provider error numbers and the message fallback, building a real SqlException through the provider's own non-public factory and reporting itself skipped rather than failing the build if a provider upgrade moves that factory (.../Persistence/SqlServerUniqueConstraintViolationDetectorTests.cs:17); and ConnectionStringSettingsValidatorTests covers the one startup rule that spans two configuration sections, a host must be able to reach some database declared either top level or as a named DataSources entry, which is why it is a validator rather than a [Required] annotation (.../Settings/ConnectionStringSettingsValidatorTests.cs:15, ADR-070). The permission-grant snapshot matches role names case-insensitively on read and keeps an entry across the gap before the next rebuild while still expiring it when refreshes keep failing (MMCA.Common.UI.Web.Tests 12 24/Core/MMCA.Common.Infrastructure.Tests/Persistence/Auth/PermissionGrantCacheTests.cs:18), beside its EF store (.../Persistence/Auth/EFPermissionGrantStoreTests.cs:29). Three files pin engine differences: the keyset seek predicate has to follow each engine's null placement (SQL Server and SQLite sort nulls first ascending, PostgreSQL the reverse), evaluated by SQLite because the defect is about three-valued SQL logic (.../Persistence/Repositories/KeysetQueryBuilderNullOrderingTests.cs:22); the notification configurations must emit PostgreSQL index filters when a PostgreSQL-only host substitutes its engine (.../Persistence/Configuration/NotificationConfigurationEngineTests.cs:23); and CosmosDbContextTests covers the Cosmos context (.../Persistence/DbContexts/CosmosDbContextTests.cs:12). The image normalizer must decode only the first frame of an animated upload, measured through a counting ImageSharp MemoryAllocator in a non-parallel collection because the allocator is process-wide (.../Storage/ImageSharpImageProcessorFrameBoundTests.cs:25), and AddTypedServiceClient must configure its standard resilience handler from the shared HttpResilienceDefaults rather than the library defaults (.../Messaging/TypedServiceClientRegistrationTests.cs:12). Mostly unit with EF-InMemory/SQLite boundaries (no real SQL Server here: that tier is MMCA.Common.Infrastructure.SQLServer.Tests). MMCA.Common.Infrastructure.Redis.Tests @@ -8955,19 +9432,24 @@

    MMCA.Common, the fr The one tier in the framework that runs the shipped caches against a real Redis. The unit tier mocks IDistributedCache, which means it asserts the calls the cache makes and never the storage format Redis ends up holding, and that is a blind spot with teeth: Redis keys are typed, INCR creates a string, and the IDistributedCache Redis provider stores every entry as a hash of absexp/sldexp/data, so mixing the two at one key round-trips flawlessly against a mock and answers WRONGTYPE in production, on the ADR-029 rate-limit and lockout counters. DistributedCacheServiceRedisTests starts a redis:7-alpine Testcontainer, builds DistributedCacheService exactly as AddCaching does when both a distributed cache and a multiplexer are registered, and proves the increment-then-read round-trip, that increments carry a TTL so a counter can never lock a subject out forever, that concurrent writers may undercount but must never leave the key unreadable (the honest statement of the current read-modify-write contract), prefix invalidation over a real SCAN, and a plain set/get/remove smoke (MMCA.Common/Tests/Core/MMCA.Common.Infrastructure.Redis.Tests/DistributedCacheServiceRedisTests.cs:27). HybridCacheServiceRedisTests is its ADR-077 sibling and exists for the same reason at a higher stake: only a real server can distinguish "the two substrates share a keyspace" from "they do not", so it proves an entry written by either cache is a soft miss (never a fault) for the other, that prefix eviction runs both patterns and also drops the evicting process's own local copy, and that increments stay monotonic across two instances sharing one Redis (.../HybridCacheServiceRedisTests.cs:35). Needs Docker, so the project is outside the slnx and runs in the redis-integration CI job (MMCA.Common/.github/workflows/ci.yml:741). Integration style. + MMCA.Common.Infrastructure.SQLServer.Tests + 8 + The real-server tier for the shipped SQLServerDbContext and DbContextFactory. The unit tier mocks or substitutes the provider, so it cannot see three behaviours only a server has: SET IDENTITY_INSERT is session state that holds only on the connection that ran it, a rowversion is generated by the server on every write, and the outbox row has to reach the database in the same SaveChanges as the aggregate that raised the event (MMCA.Common.UI.Web.Tests 12 24/Core/MMCA.Common.Infrastructure.SQLServer.Tests/SQLServerPersistenceTests.cs:44). The other seven types are in-file fixtures (SqlThing and its configuration, RecordingDomainEventDispatcher, FixedCurrentUserService, FixedAssemblyProvider, NoTenantContext, .../SQLServerPersistenceTests.cs:223). It needs a Docker daemon, so it sits outside MMCA.Common.slnx and runs in its own sqlserver-integration CI job (MMCA.Common/.github/workflows/ci.yml:918). Integration style. + + MMCA.Common.API.Tests - 155 - The presentation pipeline: ApiControllerBase.HandleFailure ErrorType-to-HTTP mapping, the exception-handler chain, the [Idempotent] filter + Idempotency-Key replay, permission policies/ownership filters, correlation, the JWKS and OIDC-discovery endpoints, the session-cookie auth handler/refresher/jar, the shared notification + device controllers, the public-endpoint output-cache policy, the database-initialization startup (the SQLite-EnsureCreated-under-Migrate path), and the error-message localization edge (localizes the human-readable message while leaving the machine Code/ProblemDetails title untouched, ADR-027 / §27). UserAccountAuthControllerBaseTests covers the shared account-management controller base that landed with the hoisted user use cases, driving change-password, preferences and delete through mocked handlers plus ICurrentUserService (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Controllers/Auth/UserAccountAuthControllerBaseTests.cs:17), and PasswordResetAuthControllerBaseTests is its recovery sibling, pinning the two endpoints and their attributes, because both actions are anonymous by necessity so losing the per-IP policy or the idempotency marker would break nothing visible and simply leave an unauthenticated endpoint unthrottled (.../Controllers/PasswordResetAuthControllerBaseTests.cs:23, ADR-091). The rate-limiting files are worth reading together (ADR-019, §11): RateLimitPartitionTests drives the global limiter's exemption and partition-key logic directly (infrastructure paths and gRPC content types bypass, anonymous traffic gets the no-limiter partition, an authenticated caller partitions by name then user_id then remote IP) and then the per-IP anti-spray policy, which deliberately fails open, since an unattributable request gets no limiter rather than sharing one bucket with every other unattributable caller (.../Startup/RateLimitPartitionTests.cs:17); AuthControllerBaseRateLimitTests asserts the attachment by reflection, because the policy is applied with an attribute and a dropped attribute breaks nothing loudly (the endpoint simply stops being throttled), pinning LoginAsync/RegisterAsync carrying the auth-IP policy and RefreshAsync deliberately not carrying it, since refresh is automatic and every Blazor Server circuit shares the UI host's IP (.../Controllers/AuthControllerBaseRateLimitTests.cs:21); RateLimitingSettingsTests guards the defaults and their binding, load-bearing because the permit-count overload of AddCommonRateLimiting delegates to them (.../RateLimiting/RateLimitingSettingsTests.cs:13); RedisFixedWindowRateLimiter covers the permit comparison, the one-shot TTL on the key that opens a window, and the fail-open posture on a Redis fault, since a limiter that failed closed would turn a cache outage into a site-wide 429 storm (.../RateLimiting/RedisFixedWindowRateLimiterTests.cs:16); and RateLimitAlgorithmSelectionTests resolves each partition's factory and inspects the limiter it actually builds, because the partition key alone never says whether a request is counted in memory or against the shared Redis counter (.../Startup/RateLimitAlgorithmSelectionTests.cs:21). Enterprise-wave files: TenantResolutionMiddlewareTests covers TenantResolutionMiddleware at the edge, the configured claim-then-header strategy order, the trimmed value, the fail-closed RequireTenant rejection as ProblemDetails, the excluded paths, and the two shapes that must pass every request straight through (.../Middleware/TenantResolutionMiddlewareTests.cs:17); the CSV export pair holds CsvWriter to RFC 4180 field by field (quote only what needs it, double an embedded quote, CRLF line endings, exactly one BOM, .../Export/CsvWriterTests.cs:8) while EntityControllerBaseExportTests drives the page-loop that exists because the query pipeline has no IAsyncEnumerable path, asserting the fan-in across pages, the short-first-page early stop, and the truncation marker appended exactly at the row cap including both page-boundary edges (.../Controllers/EntityControllerBaseExportTests.cs:25, ADR-078); DataExportControllerBaseTests pins the shipped DSAR endpoint, the dated download a subject receives, the ProblemDetails failure path, and the two attributes that are its whole security posture, asserted directly because nothing else fails when one is dropped (.../Controllers/Privacy/DataExportControllerBaseTests.cs:29, ADR-076 / §30). Conditional writes are covered in layers: SupportsIfMatchAttributeTests pins where the filter takes the token from and which status a conflict gets (.../Concurrency/SupportsIfMatchAttributeTests.cs:17) and EntityControllerBaseETagTests pins that a single read emits the token as a weak ETag so a client can turn straight around and send it as If-Match, while a DTO with no token gets no header (.../Controllers/EntityControllerBaseETagTests.cs:22, ADR-035). Next to them IdempotencyFilterPassthroughTests guards a premise rather than a behavior: a request without an Idempotency-Key header passes through untouched, which is what makes attaching [Idempotent] to an existing POST a non-breaking change for every client already calling it (.../Idempotency/IdempotencyFilterPassthroughTests.cs:22). OutputCacheEvictionHandler closes the cross-service cache loop: every tag on the message is evicted, a single tag's failure is swallowed and logged rather than rethrown (rethrowing would redeliver the message, re-evict the tags that already succeeded, and eventually dead-letter a message whose only consequence is a cache entry that expires on its own), and cancellation still propagates (.../Caching/OutputCacheEvictionHandlerTests.cs:20, ADR-026). Its newest block is contract and startup guards. OpenApiBaselineTests fetches the framework-owned OpenAPI document from a real in-memory host, normalizes it and diffs it against a committed openapi-baseline.v1.json, so an SDK or Asp.Versioning.OpenApi bump, a change to the framework's OpenAPI registration, or a change to the generated ProblemDetails schema fails here instead of reaching consumers unnoticed (.../OpenApi/OpenApiBaselineTests.cs:35, §9); ProblemDetailsRoundTripTests closes the loop between the two halves of the error contract by emitting through the real controller path with the serializer options MVC uses on the wire and reading the payload back with ProblemDetailsResultReader, which is what keeps the reader honest (.../Controllers/ProblemDetailsRoundTripTests.cs:22); MiddlewarePipelineBuilder covers the named-step edge pipeline, its default order, the insert/replace/remove operations a host customizes it with, and the invariants Build re-checks so a customized pipeline fails at startup rather than misrouting at runtime (.../Startup/MiddlewarePipelineBuilderTests.cs:12); ForwardedJwtBearerSecurityTests pins two executable security invariants, that the signature algorithm stays RS256 and that RequireHttpsMetadata resolves secure-by-default everywhere except Development, by executing the real registration and reading the resulting options back with nothing fetched from the authority (.../Startup/ForwardedJwtBearerSecurityTests.cs:22, §11), with the authority-resolution half in .../Startup/JwtAuthorityExtensionsTests.cs:13; EntityControllerBaseReadSpecificationTests pins the read-scoping hook, that every read action (both list overloads, lookup, by-id and export) asks GetReadSpecificationAsync for the rows this caller may see while a controller that overrides nothing queries exactly as unscoped as it always did (.../Controllers/EntityControllerBaseReadSpecificationTests.cs:29); and ModuleHostExtensionsTests covers the host-side module wiring (.../Startup/ModuleHostExtensionsTests.cs:22). CurrentUserTargetingContextAccessorTests rounds the row out on the feature-flag side, pinning which claim carries the user id (the Targeting filter hashes it, so a rollout is only sticky per user if that id is stable) and that an anonymous request produces an empty context rather than an error, because a feature filter must never be able to fail a request (.../FeatureManagement/CurrentUserTargetingContextAccessorTests.cs:15). Unit tests of middleware/filters/controllers in isolation. + 170 + The presentation pipeline: ApiControllerBase.HandleFailure ErrorType-to-HTTP mapping, the exception-handler chain, the [Idempotent] filter + Idempotency-Key replay, permission policies/ownership filters, correlation, the JWKS and OIDC-discovery endpoints, the session-cookie auth handler/refresher/jar, the shared notification + device controllers, the public-endpoint output-cache policy, the database-initialization startup (the SQLite-EnsureCreated-under-Migrate path), and the error-message localization edge (localizes the human-readable message while leaving the machine Code/ProblemDetails title untouched, ADR-027 / §27). UserAccountAuthControllerBaseTests covers the shared account-management controller base that landed with the hoisted user use cases, driving change-password, preferences and delete through mocked handlers plus ICurrentUserService (MMCA.Common/Tests/Presentation/MMCA.Common.API.Tests/Controllers/Auth/UserAccountAuthControllerBaseTests.cs:17), and PasswordResetAuthControllerBaseTests is its recovery sibling, pinning the two endpoints and their attributes, because both actions are anonymous by necessity so losing the per-IP policy or the idempotency marker would break nothing visible and simply leave an unauthenticated endpoint unthrottled (.../Controllers/PasswordResetAuthControllerBaseTests.cs:23, ADR-091). The rate-limiting files are worth reading together (ADR-019, §11): RateLimitPartitionTests drives the global limiter's exemption and partition-key logic directly (infrastructure paths and gRPC content types bypass, anonymous traffic gets the no-limiter partition, an authenticated caller partitions by name then user_id then remote IP) and then the per-IP anti-spray policy, which deliberately fails open, since an unattributable request gets no limiter rather than sharing one bucket with every other unattributable caller (.../Startup/RateLimitPartitionTests.cs:17); AuthControllerBaseRateLimitTests asserts the attachment by reflection, because the policy is applied with an attribute and a dropped attribute breaks nothing loudly (the endpoint simply stops being throttled), pinning LoginAsync/RegisterAsync carrying the auth-IP policy and RefreshAsync deliberately not carrying it, since refresh is automatic and every Blazor Server circuit shares the UI host's IP (.../Controllers/AuthControllerBaseRateLimitTests.cs:21); RateLimitingSettingsTests guards the defaults and their binding, load-bearing because the permit-count overload of AddCommonRateLimiting delegates to them (.../RateLimiting/RateLimitingSettingsTests.cs:13); RedisFixedWindowRateLimiter covers the permit comparison, the one-shot TTL on the key that opens a window, and the fail-open posture on a Redis fault, since a limiter that failed closed would turn a cache outage into a site-wide 429 storm (.../RateLimiting/RedisFixedWindowRateLimiterTests.cs:16); and RateLimitAlgorithmSelectionTests resolves each partition's factory and inspects the limiter it actually builds, because the partition key alone never says whether a request is counted in memory or against the shared Redis counter (.../Startup/RateLimitAlgorithmSelectionTests.cs:21). Enterprise-wave files: TenantResolutionMiddlewareTests covers TenantResolutionMiddleware at the edge, the configured claim-then-header strategy order, the trimmed value, the fail-closed RequireTenant rejection as ProblemDetails, the excluded paths, and the two shapes that must pass every request straight through (.../Middleware/TenantResolutionMiddlewareTests.cs:17); the CSV export pair holds CsvWriter to RFC 4180 field by field (quote only what needs it, double an embedded quote, CRLF line endings, exactly one BOM, .../Export/CsvWriterTests.cs:8) while EntityControllerBaseExportTests drives the page-loop that exists because the query pipeline has no IAsyncEnumerable path, asserting the fan-in across pages, the short-first-page early stop, and the truncation marker appended exactly at the row cap including both page-boundary edges (.../Controllers/EntityControllerBaseExportTests.cs:25, ADR-078); DataExportControllerBaseTests pins the shipped DSAR endpoint, the dated download a subject receives, the ProblemDetails failure path, and the two attributes that are its whole security posture, asserted directly because nothing else fails when one is dropped (.../Controllers/Privacy/DataExportControllerBaseTests.cs:29, ADR-076 / §30). Conditional writes are covered in layers: SupportsIfMatchAttributeTests pins where the filter takes the token from and which status a conflict gets (.../Concurrency/SupportsIfMatchAttributeTests.cs:17) and EntityControllerBaseETagTests pins that a single read emits the token as a weak ETag so a client can turn straight around and send it as If-Match, while a DTO with no token gets no header (.../Controllers/EntityControllerBaseETagTests.cs:22, ADR-035). Next to them IdempotencyFilterPassthroughTests guards a premise rather than a behavior: a request without an Idempotency-Key header passes through untouched, which is what makes attaching [Idempotent] to an existing POST a non-breaking change for every client already calling it (.../Idempotency/IdempotencyFilterPassthroughTests.cs:22). OutputCacheEvictionHandler closes the cross-service cache loop: every tag on the message is evicted, a single tag's failure is swallowed and logged rather than rethrown (rethrowing would redeliver the message, re-evict the tags that already succeeded, and eventually dead-letter a message whose only consequence is a cache entry that expires on its own), and cancellation still propagates (.../Caching/OutputCacheEvictionHandlerTests.cs:20, ADR-026). Its newest block is contract and startup guards. OpenApiBaselineTests fetches the framework-owned OpenAPI document from a real in-memory host, normalizes it and diffs it against a committed openapi-baseline.v1.json, so an SDK or Asp.Versioning.OpenApi bump, a change to the framework's OpenAPI registration, or a change to the generated ProblemDetails schema fails here instead of reaching consumers unnoticed (.../OpenApi/OpenApiBaselineTests.cs:35, §9); ProblemDetailsRoundTripTests closes the loop between the two halves of the error contract by emitting through the real controller path with the serializer options MVC uses on the wire and reading the payload back with ProblemDetailsResultReader, which is what keeps the reader honest (.../Controllers/ProblemDetailsRoundTripTests.cs:22); MiddlewarePipelineBuilder covers the named-step edge pipeline, its default order, the insert/replace/remove operations a host customizes it with, and the invariants Build re-checks so a customized pipeline fails at startup rather than misrouting at runtime (.../Startup/MiddlewarePipelineBuilderTests.cs:12); ForwardedJwtBearerSecurityTests pins two executable security invariants, that the signature algorithm stays RS256 and that RequireHttpsMetadata resolves secure-by-default everywhere except Development, by executing the real registration and reading the resulting options back with nothing fetched from the authority (.../Startup/ForwardedJwtBearerSecurityTests.cs:22, §11), with the authority-resolution half in .../Startup/JwtAuthorityExtensionsTests.cs:13; EntityControllerBaseReadSpecificationTests pins the read-scoping hook, that every read action (both list overloads, lookup, by-id and export) asks GetReadSpecificationAsync for the rows this caller may see while a controller that overrides nothing queries exactly as unscoped as it always did (.../Controllers/EntityControllerBaseReadSpecificationTests.cs:29); and ModuleHostExtensionsTests covers the host-side module wiring (.../Startup/ModuleHostExtensionsTests.cs:22). CurrentUserTargetingContextAccessorTests rounds the row out on the feature-flag side, pinning which claim carries the user id (the Targeting filter hashes it, so a rollout is only sticky per user if that id is stable) and that an anonymous request produces an empty context rather than an error, because a feature filter must never be able to fail a request (.../FeatureManagement/CurrentUserTargetingContextAccessorTests.cs:15). The OpenAPI pair is pinned from the host side: AddCommonOpenApiHostRegistrationTests has this project play the host so the XML-comment source generator intercepts its own AddOpenApi() calls, and a host swapping its hand-written registration for the framework pair must get exactly the document plain AddOpenApi() produces (MMCA.Common.UI.Web.Tests 12 24/Presentation/MMCA.Common.API.Tests/OpenApi/AddCommonOpenApiHostRegistrationTests.cs:36), while MapCommonOpenApi() declares its document anonymous under the fallback authorization policy and still maps nothing in Production (.../OpenApi/MapCommonOpenApiAuthorizationTests.cs:23). DesignTimeDatabaseInitializationTests pins the guard that skips schema initialization during build-time OpenAPI generation, honoured only in Development (.../Startup/DesignTimeDatabaseInitializationTests.cs:23). The claims-only session-cookie mode set by the same-origin proxy hands script a claims-only token instead of the credential (.../SessionCookies/ClaimsOnlySessionCookieEndpointsTests.cs:24), and that token keeps every claim and the expiry but carries no signature (.../SessionCookies/SessionClaimsTokenTests.cs:15). ErrorHttpMappingTests is an exhaustiveness pin so a new ErrorType cannot silently fall back to 400 (.../Middleware/ErrorHttpMappingTests.cs:12); a cancellation the client did not cause falls through to the generic handler instead of being reported as a 499 disconnect (.../Middleware/OperationCanceledExceptionHandlerTests.cs:14); the CSV export decides end-of-data from the data, because a full clamped page can be shorter than the requested page size (.../Export/EntityCsvExporterTests.cs:13); and the two fail-closed ownership gates answer 403 for an unresolvable owner claim and 404 for a non-owner (.../Authorization/OwnershipHelperGateTests.cs:18). Unit tests of middleware/filters/controllers in isolation. MMCA.Common.Grpc.Tests - 16 - The gRPC transport boundary: Result to RpcException round-tripping, the JWT-forwarding client interceptor, and the Polly resilience pipeline on typed clients (retry, circuit-breaker, and fault-injection). GrpcResultExceptionInterceptor is a good example of a test written around a fixed defect: the error-carrying case keeps the shared ToRpcException mapping, while the empty-errors case (what the message-only constructors produce) used to discard the exception message entirely and answer a placeholder "Unspecified failure", leaving the caller with a failure and no cause; it now keeps StatusCode.Internal and carries the real message, because synthesizing an Error.Failure instead would have downgraded a server-side fault to InvalidArgument and blamed the caller (MMCA.Common/Tests/Presentation/MMCA.Common.Grpc.Tests/GrpcResultExceptionInterceptorTests.cs:24). Its newest file covers the decode leg, the client-side turn of an RpcException back into a Result with its errors intact (.../ResultGrpcExtensionsDecoderTests.cs:14). Unit tests asserting ADR-007 / ADR-009 behavior. + 18 + The gRPC transport boundary: Result to RpcException round-tripping, the JWT-forwarding client interceptor, and the Polly resilience pipeline on typed clients (retry, circuit-breaker, and fault-injection). GrpcResultExceptionInterceptor is a good example of a test written around a fixed defect: the error-carrying case keeps the shared ToRpcException mapping, while the empty-errors case (what the message-only constructors produce) used to discard the exception message entirely and answer a placeholder "Unspecified failure", leaving the caller with a failure and no cause; it now keeps StatusCode.Internal and carries the real message, because synthesizing an Error.Failure instead would have downgraded a server-side fault to InvalidArgument and blamed the caller (MMCA.Common/Tests/Presentation/MMCA.Common.Grpc.Tests/GrpcResultExceptionInterceptorTests.cs:24). Its newest file covers the decode leg, the client-side turn of an RpcException back into a Result with its errors intact (.../ResultGrpcExtensionsDecoderTests.cs:14). GrpcWireFormatTests pins the wire conventions the hand-written adapters shared by copy: both the Z-marked and the marker-less timestamp forms must land as DateTimeKind.Utc on the same instant (MMCA.Common.UI.Web.Tests 12 24/Presentation/MMCA.Common.Grpc.Tests/GrpcWireFormatTests.cs:18). Unit tests asserting ADR-007 / ADR-009 behavior. MMCA.Common.Aspire.Tests - 50 - The service-defaults package: OutboxPollFilterProcessor (drops recurring outbox-poll spans from telemetry export), the SecurityHeadersMiddleware, the head-based trace-sampling cost knob (a ratio in (0,1) opts in, anything else samples everything so a typo cannot silently drop all telemetry, MMCA.Common/Tests/Hosting/MMCA.Common.Aspire.Tests/Telemetry/TracesSampleRatioTests.cs:11, §31), the metrics-instrumentation toggle (.../Telemetry/MetricsInstrumentationToggleTests.cs:16) and the Serilog host wiring (.../Logging/SerilogHostExtensionsTests.cs:19). It guards the one deliberate asymmetry in AddInfrastructureHealthChecks: a missing SQL connection string throws at startup when the host requires it, while absent Redis/RabbitMQ skip silently, and the optional checks carry HealthCheckTags.Optional so they never gate /health/ready (.../Health/InfrastructureHealthChecksTests.cs:16, §29). That optionality now has two files of its own after a production incident where readiness went hard-dependent on Redis: RedisPingHealthCheckTests covers the ping probe itself (.../Health/RedisPingHealthCheckTests.cs:16) and RedisReadinessSafetyTests pins that no Redis check can ever appear in the readiness set, because DistributedCacheService already degrades around a cache blip and an untagged check would instead take every replica out of rotation at once (.../Health/RedisReadinessSafetyTests.cs:24). A large half of the suite is §29 warm-up material: WarmupReadinessGate must stay closed until warm-up finishes, then latch open idempotently and safely under concurrency (.../Warmup/WarmupReadinessGateTests.cs:10), and the health-check face of that gate reports Unhealthy while the replica is still warming and Healthy the moment the gate opens, which is what actually keeps a cold replica out of the /health/ready rotation (.../Warmup/WarmupReadinessHealthCheckTests.cs:11); the warm-up hosted service must run every IWarmupTask once and open the gate even when a task fails or hangs (bounded by a per-task timeout), so a transient dependency outage cannot wedge a replica permanently out of rotation (.../Warmup/WarmupHostedServiceTests.cs:13, ADR-025); and SelfHttpWarmupTaskBase covers what the per-service copies each had to get right alone: port resolution under dynamic ports, the Testing short-circuit, the h2c version pin, and the non-fatal wrapper (.../Warmup/SelfHttpWarmupTaskBaseTests.cs:28). Three files pin deployment-shaped configuration and share a technique worth borrowing, assert on the plan a builder produced, never on a running dependency: KestrelEndpointExtensionsTests asserts the listener plan directly rather than through a bound server, because that plan decides whether a deployed revision answers its platform health probes at all (.../Kestrel/KestrelEndpointExtensionsTests.cs:14); DataProtectionExtensionsTests covers the two-stage gate where no DataProtection:BlobStorageUri leaves the in-memory default and takes no Azure dependency at startup while a configured URI swaps in the blob key-ring repository (.../DataProtection/DataProtectionExtensionsTests.cs:19); and KeyVaultConfigurationExtensionsTests runs its gate and malformed-configuration cases against a real HostApplicationBuilder while the cases that do add a source run against a builder double whose configuration merely collects them, because a real ConfigurationManager builds and loads every source the instant it is added and loading a Key Vault source means a live call to the vault (.../Configuration/KeyVaultConfigurationExtensionsTests.cs:27). Its gateway block covers the edge kit this package ships, the extension points ADC's Gateway host consumes rather than hand-writing (ADR-088): GatewayCorrelationMiddleware must leave a correlation ID on the request and the response, preserve a caller-supplied one end to end, and need nothing from DI, which is the whole reason it exists separately from the context-bound CorrelationIdMiddleware in MMCA.Common.API (.../Gateway/GatewayCorrelationMiddlewareTests.cs:15); the downstream health checks are pinned by name, tag and failure status, including that a repeated registration does not produce the duplicate check name the health-check service rejects at startup (.../Gateway/GatewayDownstreamHealthChecksTests.cs:17); the edge limiter covers its settings defaults, the bypass matcher, the fail-open posture on an unattributable client, and that registering twice does not throw (.../Gateway/GatewayRateLimitingTests.cs:21); and the CORS extension covers the allowed-origin policy a browser-facing edge needs (.../Gateway/GatewayCorsExtensionsTests.cs:19). Unit suite over the Aspire service-defaults package. + 52 + The service-defaults package: OutboxPollFilterProcessor (drops recurring outbox-poll spans from telemetry export), the SecurityHeadersMiddleware, the head-based trace-sampling cost knob (a ratio in (0,1) opts in, anything else samples everything so a typo cannot silently drop all telemetry, MMCA.Common/Tests/Hosting/MMCA.Common.Aspire.Tests/Telemetry/TracesSampleRatioTests.cs:11, §31), the metrics-instrumentation toggle (.../Telemetry/MetricsInstrumentationToggleTests.cs:16) and the Serilog host wiring (.../Logging/SerilogHostExtensionsTests.cs:19). It guards the one deliberate asymmetry in AddInfrastructureHealthChecks: a missing SQL connection string throws at startup when the host requires it, while absent Redis/RabbitMQ skip silently, and the optional checks carry HealthCheckTags.Optional so they never gate /health/ready (.../Health/InfrastructureHealthChecksTests.cs:16, §29). That optionality now has two files of its own after a production incident where readiness went hard-dependent on Redis: RedisPingHealthCheckTests covers the ping probe itself (.../Health/RedisPingHealthCheckTests.cs:16) and RedisReadinessSafetyTests pins that no Redis check can ever appear in the readiness set, because DistributedCacheService already degrades around a cache blip and an untagged check would instead take every replica out of rotation at once (.../Health/RedisReadinessSafetyTests.cs:24). A large half of the suite is §29 warm-up material: WarmupReadinessGate must stay closed until warm-up finishes, then latch open idempotently and safely under concurrency (.../Warmup/WarmupReadinessGateTests.cs:10), and the health-check face of that gate reports Unhealthy while the replica is still warming and Healthy the moment the gate opens, which is what actually keeps a cold replica out of the /health/ready rotation (.../Warmup/WarmupReadinessHealthCheckTests.cs:11); the warm-up hosted service must run every IWarmupTask once and open the gate even when a task fails or hangs (bounded by a per-task timeout), so a transient dependency outage cannot wedge a replica permanently out of rotation (.../Warmup/WarmupHostedServiceTests.cs:13, ADR-025); and SelfHttpWarmupTaskBase covers what the per-service copies each had to get right alone: port resolution under dynamic ports, the Testing short-circuit, the h2c version pin, and the non-fatal wrapper (.../Warmup/SelfHttpWarmupTaskBaseTests.cs:28). Three files pin deployment-shaped configuration and share a technique worth borrowing, assert on the plan a builder produced, never on a running dependency: KestrelEndpointExtensionsTests asserts the listener plan directly rather than through a bound server, because that plan decides whether a deployed revision answers its platform health probes at all (.../Kestrel/KestrelEndpointExtensionsTests.cs:14); DataProtectionExtensionsTests covers the two-stage gate where no DataProtection:BlobStorageUri leaves the in-memory default and takes no Azure dependency at startup while a configured URI swaps in the blob key-ring repository (.../DataProtection/DataProtectionExtensionsTests.cs:19); and KeyVaultConfigurationExtensionsTests runs its gate and malformed-configuration cases against a real HostApplicationBuilder while the cases that do add a source run against a builder double whose configuration merely collects them, because a real ConfigurationManager builds and loads every source the instant it is added and loading a Key Vault source means a live call to the vault (.../Configuration/KeyVaultConfigurationExtensionsTests.cs:27). Its gateway block covers the edge kit this package ships, the extension points ADC's Gateway host consumes rather than hand-writing (ADR-088): GatewayCorrelationMiddleware must leave a correlation ID on the request and the response, preserve a caller-supplied one end to end, and need nothing from DI, which is the whole reason it exists separately from the context-bound CorrelationIdMiddleware in MMCA.Common.API (.../Gateway/GatewayCorrelationMiddlewareTests.cs:15); the downstream health checks are pinned by name, tag and failure status, including that a repeated registration does not produce the duplicate check name the health-check service rejects at startup (.../Gateway/GatewayDownstreamHealthChecksTests.cs:17); the edge limiter covers its settings defaults, the bypass matcher, the fail-open posture on an unattributable client, and that registering twice does not throw (.../Gateway/GatewayRateLimitingTests.cs:21); and the CORS extension covers the allowed-origin policy a browser-facing edge needs (.../Gateway/GatewayCorsExtensionsTests.cs:19). ServiceDefaultsRetryTests pins the retry predicate every factory HttpClient gets, one retry for the idempotent verbs and none for POST and PATCH (MMCA.Common.UI.Web.Tests 12 24/Hosting/MMCA.Common.Aspire.Tests/Resilience/ServiceDefaultsRetryTests.cs:18), and LiveMetricsConfigurationTests pins the Azure Monitor distro's own Live Metrics off switch (AzureMonitor__EnableLiveMetrics=false) through ConfigureOpenTelemetry(), because Live Metrics alone can hold an idle Container Apps replica above the idle billing line (.../Telemetry/LiveMetricsConfigurationTests.cs:20). Unit suite over the Aspire service-defaults package. MMCA.Common.Aspire.Hosting.Tests @@ -8986,17 +9468,17 @@

    MMCA.Common, the fr MMCA.Common.UI.Tests - 152 - Shared Blazor components (delete-confirmation, empty-state, the mobile card/infinite-scroll lists, notification bell/inbox/list/send pages, primitives), the MudBlazor theme/provider harness, HTTP-resilience/service-exception helpers, list-page state/query-state services, the primitive markup snapshots, the auth-form view-model validation (§24), and the i18n globalization pair (the [!!...!!] bracket-sentinel pseudo-localizer and the ResxMudLocalizer MudBlazor-chrome boundary) plus the auth-aware nav menu and its mobile top-row. On i18n (ADR-027 / §27): CultureSwitcherTests guards that the switcher delegates to ICultureApplier instead of navigating to /culture/set itself, since that URL is a server endpoint and a hard-coded navigation left MAUI Blazor Hybrid heads (which host no ASP.NET pipeline) routing it through the Blazor router onto the not-found page (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Globalization/CultureSwitcherTests.cs:17); EndpointCultureApplier covers the web default, which must route through /culture/set with a force load because only that round trip writes the cookie SSR prerender and the WASM runtime both read (.../Globalization/EndpointCultureApplierTests.cs:15); and DocumentLanguageTests covers the component that makes a hybrid head's lang attribute follow a culture switch, asserted here precisely because no automated accessibility gate can catch a wrong lang (axe checks presence and syntax only, .../Components/DocumentLanguageTests.cs:13). On theming, ThemeToggleTests covers the app-bar Day/Dark switch including that disposal unsubscribes so the long-lived ThemeService never calls back into a dead component (.../Components/ThemeToggleTests.cs:16, ADR-028), and ApiUserPreferenceWriterTests pins the two guards that keep a signed-out or stale session from spending one 401 per theme/culture toggle, a write that is best effort and whose caller never learns it failed, making a doomed request pure cost (.../Services/ApiUserPreferenceWriterTests.cs:16). On write safety, EntityServiceBaseIdempotencyRetryTests pins the Idempotency-Key EntityServiceBase<TEntityDTO, TIdentifierType> emits on creates and only on creates, the key staying identical across every retry of one logical operation, and the retry predicate's shape (5xx yes, 501 no, 429 yes) plus cancellation aborting the pipeline; the retried cases pay the real Polly backoff, so each is driven through the smallest attempt count that proves the behavior and asserts on captured attempts, never on wall-clock timing (.../Services/EntityServiceBaseIdempotencyRetryTests.cs:21). Its capability and JS boundary: LazyJsModule pins the single-flight import() the UI services delegate to, because an unguarded _module ??= await import(...) lets two concurrent callers each start an import so the browser holds two module instances and the later assignment leaks the earlier reference, while a failed import must not be cached and disposal must tolerate a disconnected circuit (.../Services/LazyJsModuleTests.cs:14); CapabilityFallbackTests sweeps every null/neutral capability default, each of which must degrade (report unsupported, no-op, or return empty) and never throw, because these are what shared components resolve on a head with no native or browser override (.../Services/Capabilities/CapabilityFallbackTests.cs:12, ADR-042); and QrCodeImageTests covers the managed QR render, a PNG data URI with required alt text, nothing at all for a blank payload, and a re-encode when the payload or error-correction level changes (.../Components/QrCodeImageTests.cs:11, ADR-071). Its newest work is the shared read/refresh plumbing and the account surface. LatestLoadGuard is the generation counter behind every "supersede an in-flight load" fix in both apps, pinned once here (.../Common/LatestLoadGuardTests.cs:11), and UiReadCache covers the opt-in client read cache pages share (.../Services/Caching/UiReadCacheTests.cs:15). ErrorSummaryTests covers the form-level error block that turns a failed Result into an accessible summary (.../Components/ErrorSummaryTests.cs:18, §24), ApiFileDownloadButtonTests the authenticated download path (.../Components/ApiFileDownloadButtonTests.cs:24), InfiniteScrollSentinelTests the intersection-observer sentinel behind the mobile lists (.../Components/InfiniteScrollSentinelTests.cs:14), HttpResultExecutorTests the single place a UI service turns an HTTP response into a Result (.../Services/HttpResultExecutorTests.cs:15), and AbsoluteUrlAttributeTests the validation attribute the share/QR surfaces depend on (.../Validation/AbsoluteUrlAttributeTests.cs:14). The multi-device session surface lands here as a page plus a parser: SessionsTests renders the sign-out-other-devices page in its loading, data, empty and error states (.../Pages/Auth/SessionsTests.cs:27) and UserAgentSummaryTests pins the user-agent-to-readable-device summary shown on each row, which is presentation over an attacker-controlled string and therefore must never throw on garbage (.../Services/Auth/UserAgentSummaryTests.cs:13, ADR-097). OfflineFirstPageSnapshotTests adds golden-markup coverage for the offline-first page shell (.../Pages/Common/OfflineFirstPageSnapshotTests.cs:14). Rendered with bUnit (component-render unit tests via BunitComponentTestBase). + 170 + Shared Blazor components (delete-confirmation, empty-state, the mobile card/infinite-scroll lists, notification bell/inbox/list/send pages, primitives), the MudBlazor theme/provider harness, HTTP-resilience/service-exception helpers, list-page state/query-state services, the primitive markup snapshots, the auth-form view-model validation (§24), and the i18n globalization pair (the [!!...!!] bracket-sentinel pseudo-localizer and the ResxMudLocalizer MudBlazor-chrome boundary) plus the auth-aware nav menu and its mobile top-row. On i18n (ADR-027 / §27): CultureSwitcherTests guards that the switcher delegates to ICultureApplier instead of navigating to /culture/set itself, since that URL is a server endpoint and a hard-coded navigation left MAUI Blazor Hybrid heads (which host no ASP.NET pipeline) routing it through the Blazor router onto the not-found page (MMCA.Common/Tests/Presentation/MMCA.Common.UI.Tests/Globalization/CultureSwitcherTests.cs:17); EndpointCultureApplier covers the web default, which must route through /culture/set with a force load because only that round trip writes the cookie SSR prerender and the WASM runtime both read (.../Globalization/EndpointCultureApplierTests.cs:15); and DocumentLanguageTests covers the component that makes a hybrid head's lang attribute follow a culture switch, asserted here precisely because no automated accessibility gate can catch a wrong lang (axe checks presence and syntax only, .../Components/DocumentLanguageTests.cs:13). On theming, ThemeToggleTests covers the app-bar Day/Dark switch including that disposal unsubscribes so the long-lived ThemeService never calls back into a dead component (.../Components/ThemeToggleTests.cs:16, ADR-028), and ApiUserPreferenceWriterTests pins the two guards that keep a signed-out or stale session from spending one 401 per theme/culture toggle, a write that is best effort and whose caller never learns it failed, making a doomed request pure cost (.../Services/ApiUserPreferenceWriterTests.cs:16). On write safety, EntityServiceBaseIdempotencyRetryTests pins the Idempotency-Key EntityServiceBase<TEntityDTO, TIdentifierType> emits on creates and only on creates, the key staying identical across every retry of one logical operation, and the retry predicate's shape (5xx yes, 501 no, 429 yes) plus cancellation aborting the pipeline; the retried cases pay the real Polly backoff, so each is driven through the smallest attempt count that proves the behavior and asserts on captured attempts, never on wall-clock timing (.../Services/EntityServiceBaseIdempotencyRetryTests.cs:21). Its capability and JS boundary: LazyJsModule pins the single-flight import() the UI services delegate to, because an unguarded _module ??= await import(...) lets two concurrent callers each start an import so the browser holds two module instances and the later assignment leaks the earlier reference, while a failed import must not be cached and disposal must tolerate a disconnected circuit (.../Services/LazyJsModuleTests.cs:14); CapabilityFallbackTests sweeps every null/neutral capability default, each of which must degrade (report unsupported, no-op, or return empty) and never throw, because these are what shared components resolve on a head with no native or browser override (.../Services/Capabilities/CapabilityFallbackTests.cs:12, ADR-042); and QrCodeImageTests covers the managed QR render, a PNG data URI with required alt text, nothing at all for a blank payload, and a re-encode when the payload or error-correction level changes (.../Components/QrCodeImageTests.cs:11, ADR-071). Its newest work is the shared read/refresh plumbing and the account surface. LatestLoadGuard is the generation counter behind every "supersede an in-flight load" fix in both apps, pinned once here (.../Common/LatestLoadGuardTests.cs:11), and UiReadCache covers the opt-in client read cache pages share (.../Services/Caching/UiReadCacheTests.cs:15). ErrorSummaryTests covers the form-level error block that turns a failed Result into an accessible summary (.../Components/ErrorSummaryTests.cs:18, §24), ApiFileDownloadButtonTests the authenticated download path (.../Components/ApiFileDownloadButtonTests.cs:24), InfiniteScrollSentinelTests the intersection-observer sentinel behind the mobile lists (.../Components/InfiniteScrollSentinelTests.cs:14), HttpResultExecutorTests the single place a UI service turns an HTTP response into a Result (.../Services/HttpResultExecutorTests.cs:15), and AbsoluteUrlAttributeTests the validation attribute the share/QR surfaces depend on (.../Validation/AbsoluteUrlAttributeTests.cs:14). The multi-device session surface lands here as a page plus a parser: SessionsTests renders the sign-out-other-devices page in its loading, data, empty and error states (.../Pages/Auth/SessionsTests.cs:27) and UserAgentSummaryTests pins the user-agent-to-readable-device summary shown on each row, which is presentation over an attacker-controlled string and therefore must never throw on garbage (.../Services/Auth/UserAgentSummaryTests.cs:13, ADR-097). OfflineFirstPageSnapshotTests adds golden-markup coverage for the offline-first page shell (.../Pages/Common/OfflineFirstPageSnapshotTests.cs:14). The WebAssembly half of the same-origin API proxy sends no Authorization header and always X-CSRF: 1 once Api:SameOriginApiEndpoint is set (MMCA.Common.UI.Web.Tests 12 24/Presentation/MMCA.Common.UI.Tests/Services/SameOriginProxyClientTests.cs:26); TokenRefreshPipelineTests drives the real APIClient pipeline so the refresh POST cannot re-enter its own in-flight hydrate (.../Services/Auth/TokenRefreshPipelineTests.cs:20); IdempotentReadRetryTests gives the anonymous lookup services the same transient-failure retry policy instance, GET only (.../Services/Api/IdempotentReadRetryTests.cs:16); PagedReadAllTests pins paging until the reported total (.../Services/Api/PagedReadAllTests.cs:14); NotificationPageGateTests hides the notification pages for an opted-in host that never registered the notification UI (.../Notifications/NotificationPageGateTests.cs:16); and two smaller pins cover the MAUI back-navigation module release (.../Services/Navigation/MauiBackNavigationBridgeTests.cs:12) and the cookie-sync write outcome (.../Services/Auth/JsFetchSessionCookieSyncTests.cs:13), with InMemoryHubServer as an in-process SignalR stand-in (.../Services/Notifications/InMemoryHubServer.cs:17). Rendered with bUnit (component-render unit tests via BunitComponentTestBase). MMCA.Common.UI.Web.Tests - 12 - The Blazor Server web-host pieces: ServerTokenStorageService (during SSR prerender tokens come from the HttpOnly session cookies; on the interactive circuit the access token is held in memory, hydrated single-flight, and refreshed proactively near expiry, while the refresh token is never readable, MMCA.Common/Tests/Presentation/MMCA.Common.UI.Web.Tests/Services/ServerTokenStorageServiceTests.cs), the server form-factor probe (.../Services/WebFormFactorTests.cs), and BlazorCspPolicyProvider, which pins the enforced production Content-Security-Policy verbatim (connect-src locked to the configured API/Gateway origin, no unsafe-eval, permissive Report-Only degradation on an unparseable endpoint, .../Security/BlazorCspPolicyProviderTests.cs, §26). Unit tests. + 24 + The Blazor Server web-host pieces: ServerTokenStorageService (during SSR prerender tokens come from the HttpOnly session cookies; on the interactive circuit the access token is held in memory, hydrated single-flight, and refreshed proactively near expiry, while the refresh token is never readable, MMCA.Common/Tests/Presentation/MMCA.Common.UI.Web.Tests/Services/ServerTokenStorageServiceTests.cs), the server form-factor probe (.../Services/WebFormFactorTests.cs), and BlazorCspPolicyProvider, which pins the enforced production Content-Security-Policy verbatim (connect-src locked to the configured API/Gateway origin, no unsafe-eval, permissive Report-Only degradation on an unparseable endpoint, .../Security/BlazorCspPolicyProviderTests.cs, §26). Twelve of its types cover the same-origin API proxy, booted in-process against a fake gateway (MMCA.Common.UI.Web.Tests 12 24/Presentation/MMCA.Common.UI.Web.Tests/SameOriginProxy/ProxyTestHarness.cs:148): the bearer is attached server-side from the HttpOnly cookie and a racing refresh runs once per session (.../SameOriginProxy/SameOriginApiProxyTokenTests.cs:15), every unsafe method needs X-CSRF: 1 (.../SameOriginApiProxyCsrfTests.cs:12), a foreign Origin or non-same-origin Sec-Fetch-Site is refused 403 (.../SameOriginApiProxyOriginTests.cs:14), an undecided refresh (5xx, 429) keeps the cookies and answers 503 with Retry-After (.../SameOriginApiProxyRefreshOutcomeTests.cs:14), sign-in, refresh and sign-out keep the token pair in the cookies (.../SameOriginApiProxyAuthFlowTests.cs:20), SignalR negotiate and WebSocket upgrades (HTTP/1.1 and RFC 8441 extended CONNECT) carry the cookie bearer (.../SameOriginApiProxyHubTests.cs:29), a host that does not opt in is unchanged (.../SameOriginApiProxyOptInTests.cs:21), and the Server circuit trades only protected handoffs (.../SessionHandoffServicesTests.cs:14). Unit and in-process integration tests. MMCA.Common.UI.E2E.Tests - 20 + 21 Playwright axe-core (WCAG 2.1 AA) + render-smoke over the backend-less Gallery host, driven from GalleryAxeTestBase: real Login/Register pages, the primitives/components showcase, and the shared Notification pages against stubbed collaborators, plus the dark-mode toggle, a Web-Vitals budget probe, and two i18n/mobile-parity gates, a qps-Ploc pseudo-locale round-trip asserting the [!! sentinel and no horizontal overflow under roughly 40% text expansion (ADR-027 / §27) and the culture+theme controls pinned into the mobile top-row below 1024px (§22). StickySidebarE2ETests illustrates why a rendered-behavior assertion beats a CSS assertion: it keeps the desktop sidebar pinned while the page scrolls, a behavior that broke because position: sticky resolves against the nearest ancestor scroll container and two innocuous rules (html, body { overflow-y: auto } and .page { overflow-x: hidden }, where a non-visible overflow-x forces the computed overflow-y from visible to auto) turned content-sized ancestors that never scroll into dead scrollports; the test asserts the pinning, so it catches any future ancestor that reintroduces one (MMCA.Common/Tests/Presentation/MMCA.Common.UI.E2E.Tests/Layout/StickySidebarE2ETests.cs:23). Its newest pages are the password-recovery pair (.../ForgotPasswordPageE2ETests.cs, .../ResetPasswordPageE2ETests.cs:9, ADR-091), the sessions page (.../SessionsPageE2ETests.cs:13), a grid page added to the Gallery so the shared data grid is scanned like every other primitive (.../GridPageE2ETests.cs:16), and AuthOutcomeRulesTests, which pins the outcome vocabulary the auth flows assert on so the browser tests and the components cannot drift apart (.../AuthOutcomeRulesTests.cs:12). Deliberately outside MMCA.Common.slnx; runs in CI's ui-e2e job across chromium, firefox and webkit. E2E/accessibility style. [Rubric §21, Accessibility] (assesses automated a11y gating): this is where the framework proves zero axe violations before downstream apps consume the pages. @@ -9004,6 +9486,11 @@

    MMCA.Common, the fr 6 A BenchmarkDotNet performance-smoke executable covering the two DB-free hot paths. SpecificationBenchmarks measures the per-instance compiled-expression cache behind Specification<TEntity, TIdentifierType>.IsSatisfiedBy (a cached-compile baseline versus the recompile-each-call anti-pattern) and the And/Or composition cost (MMCA.Common/Tests/Performance/MMCA.Common.Benchmarks/SpecificationBenchmarks.cs:14); QueryPipelineBenchmarks adds the read side, which runs on every list request in every consumer and regresses silently because the dynamic-LINQ predicate is re-parsed per call and the shaper reflects over the DTO: a single CONTAINS filter, a three-strategy mixed filter, dynamic sorting, and full-field versus sparse-fields= shaping of a 100-row page (.../QueryPipelineBenchmarks.cs:17). Deliberately outside MMCA.Common.slnx (like the Gallery), but not on-demand-only: CI's performance-smoke job (MMCA.Common/.github/workflows/ci.yml:332) runs the suite and then build/perfgate compares the results against the committed Tests/Performance/perf-baseline.json, failing on any violation of its allocation ceilings or machine-independent ratio floors, and on a rule naming a benchmark that produced no measurement, so the gate cannot pass vacuously (.../ci.yml:371). Moving a number deliberately means updating the baseline in the same PR. [Rubric §12, Performance & Scalability] (assesses measured, not assumed, hot-path cost): this is the evidence harness, and the baseline file turns it from a runs-clean smoke into a regression gate. Performance-smoke style. + + MMCA.Common.LoadTests + 14 + Load tests over the framework's real persistence stack on one temp-file SQLite database, wired the way a host wires it (AddApplication() plus AddInfrastructure, a file database because each scope opens its own connection, MMCA.Common.UI.Web.Tests 12 24/Performance/MMCA.Common.LoadTests/Support/LoadStack.cs:25). PagedQueryLoadTests drives EntityQueryService.GetAllAsync against 100,000 seeded rows and asserts exact rows rather than a count (.../PagedQueryLoadTests.cs:13, seeded by .../Support/PagedQueryFixture.cs:16); OutboxThroughputLoadTests drains 10,000 events through the real OutboxProcessor to a counting transport, each delivered exactly once, above a rate floor (.../OutboxThroughputLoadTests.cs:20). LoadResults writes one JSON summary per scenario (.../Support/LoadResults.cs:11). Runs weekly from its own workflow (MMCA.Common/.github/workflows/load-tests.yml:16). +

    MMCA.ADC, Conference module (the largest application module)

    @@ -9026,8 +9513,8 @@

    MMCA.ADC, Conf

    - - + + @@ -9041,8 +9528,8 @@

    MMCA.ADC, Conf

    - - + + @@ -9076,8 +9563,8 @@

    LiveChannelPublishQueue). The ADR-072 wave contributes three load-bearing files. PointsAwarder is where anti-farming and redelivery idempotency turn out to be the same rule: an award already present writes nothing, a rule configured to 0 (or an undefined activity) succeeds and writes nothing, an entry keeps the value it was awarded when the configuration later changes, and a save that loses the unique-index race reports success, including when the duplicate wording is buried in an inner exception, while any other save failure propagates (.../Points/Services/PointsAwarderTests.cs:13). CheckInAttendeeHandler pins the scan path: a repeat scan reports the original check-in and writes nothing, a session-scoped scan is filed under the session's own owning event (not the client's event context), an unknown and a malformed credential return the same BadgeNotFound (no probing oracle), and an unpublished event or a missing caller is refused (.../CheckIns/UseCases/CheckInAttendeeHandlerTests.cs:17). GetLeaderboardHandler pins the board as opt-in only (points without an opt-in never appear, and leaving removes you), ordering by total with an ordinal name tie-break, truncation to the configured size where 0 or a negative publishes nothing rather than throwing, and the snapshotted name being what is published (.../Points/UseCases/GetLeaderboardHandlerTests.cs:12). UserDeletedPointsHandlerTests closes the privacy loop: on UserDeleted the entry comes off the board and the published name is erased even for an already-left opt-in, the read deliberately looks past the soft-delete filter and tracks, and a redelivery reaches the same state while writing once (.../Points/IntegrationEventHandlers/UserDeletedPointsHandlerTests.cs:14, §30). Its newest file is the cross-service cache edge: bookmark counts are owned here but served by Conference, in another process whose output cache this side cannot touch directly, so UserSessionBookmarkCacheEvictionHandlerTests pins the one thing that closes that distance, that every bookmark change publishes an eviction request carrying the tag Conference actually registered, and that a broker which is down still cannot fail a bookmark the attendee already saved (.../UserSessionBookmarks/DomainEventHandlers/UserSessionBookmarkCacheEvictionHandlerTests.cs:20, ADR-026). Unit. +

    + @@ -9111,8 +9598,8 @@

    MMCA.AD

    - - + + @@ -9186,8 +9673,8 @@

    MMCA.A

    - - + + diff --git a/docs/onboarding/index.html b/docs/onboarding/index.html index 90fc4457..ea0ce610 100644 --- a/docs/onboarding/index.html +++ b/docs/onboarding/index.html @@ -209,7 +209,7 @@

    How the inventory & level are resolved by namespace-aware name matching; ~96% bind by namespace visibility, the rest by a globally-unique-name fallback (586 edges), and 38 references are dropped as ambiguous. The functional grouping is then applied mechanically (Tools/invtool/classify.ps1 → 00-group-taxonomy.md), so every one of the - 5,034 distinct type nodes maps to exactly one group with no silent drops. See the + 5,234 distinct type nodes maps to exactly one group with no silent drops. See the manifest's accuracy note for residual caveats.


    Chapters

    @@ -227,7 +227,7 @@

    Front matter

    - + @@ -287,13 +287,13 @@

    Group chapters (primary axis)

    - + - + @@ -323,19 +323,19 @@

    Group chapters (primary axis)

    - + - + - + @@ -371,25 +371,25 @@

    Group chapters (primary axis)

    - + - + - + - + @@ -413,7 +413,7 @@

    Group chapters (primary axis)

    - +
    MMCA.ADC.Conference.Application.Tests193The command/query handlers for the Conference controllers, validators, navigation populators, the Sessionize import orchestrator + sync strategies (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeHandlerTests.cs:16), and the event/session live-window validation served to the live layer over gRPC (.../Events/EventLiveValidationServiceTests.cs:17, alongside GetPublicSessionFilterHandler and its cross-source filter query, ADR-018). It carries the durable AI-scoring pass (ADR-114) in three files. ScoreEventSessionsInternalCommandHandlerTests pins the handler's contract with the framework's at-least-once processor: it takes a per-event claim and skips rather than waits when another replica holds it (each pass is one paid Anthropic call per session), completes the row on a business refusal because replaying it would only pay for the same refusal again, lets a thrown fault propagate so the framework's backoff and attempt ceiling own the retry, and releases the claim on the way out (.../Sessions/UseCases/DecisionSupport/ScoreEventSessionsInternalCommandHandlerTests.cs:17, ScoreEventSessionsInternalCommandHandler). SessionScoringRunnerTests covers the pass itself, including the resume rule that skips every session scored since the command's request instant, which is what makes a queued duplicate or a retried attempt cheap (.../Sessions/UseCases/DecisionSupport/SessionScoringRunnerTests.cs:491, SessionScoringRunner). ScoreEventSessionsInternalCommandMarkerTests pins the opt-in pipeline markers on ScoreEventSessionsInternalCommand: the scoring feature flag, the session-selection capability, and a 12-minute execution budget that must stay below the handler's 15-minute claim time-to-live so the claim cannot expire mid-pass (.../Sessions/UseCases/DecisionSupport/ScoreEventSessionsInternalCommandMarkerTests.cs:51, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/ScoreEventSessionsInternalCommandHandler.cs:56). It also carries the sessions-by-speaker filter handler, whose specification resolves the SessionSpeaker join down to an engine-portable ID-list criteria so the speaker pages and speaker dashboard filter server-side instead of pulling the whole catalog (.../Sessions/UseCases/GetSessionsBySpeakerFilter/GetSessionsBySpeakerFilterHandlerTests.cs:16, GetSessionsBySpeakerFilterHandler, §12). A family of public filter handlers (session, session-speaker, session-category-item, event-speaker, speaker, speaker-category-item, room, sponsor, and now activity) all defend the same anonymous-endpoint leak: a junction filter must select only rows whose parent is publicly visible, so a hidden session never leaks its existence through the speakers assigned to it (.../Sessions/UseCases/GetPublicSessionSpeakerFilter/GetPublicSessionSpeakerFilterHandlerTests.cs:18, and its activity twin at .../Activities/UseCases/GetPublicActivityFilter/GetPublicActivityFilterHandlerTests.cs:18, BR-49, §11). ExportEventCalendarHandler covers the whole-schedule .ics endpoint, which is anonymous, so unpublished and unknown events must collapse to the same NotFound (no existence oracle) and a legacy row carrying an unresolvable time zone must degrade rather than fault the request (.../Sessions/UseCases/ExportCalendar/ExportEventCalendarHandlerTests.cs:16). The sponsor and activity use cases are deliberately plain: create/update handlers assert the mapper-failure path, the repository add and the save, and nothing more, because the interesting behavior lives in the aggregate and in the public filter (.../Sponsors/UseCases/CreateSponsorHandlerTests.cs:14, .../Activities/UseCases/CreateActivityHandlerTests.cs:13). Its newest files are convention guards rather than behavior: SessionRoomFilterTests pins the public schedule's Room filter at the Application layer, since Session.RoomId is a real nullable column riding the generic filter pipeline with no dedicated handler, so the feature would break silently if the property were renamed or retyped (an unknown filter key is a validation failure and a mistyped one is ignored at apply time, .../Sessions/SessionRoomFilterTests.cs:15); ConferenceCrudRegistrationTests sweeps the module's CRUD registrations so a new aggregate cannot ship half-wired (.../ConferenceCrudRegistrationTests.cs:32); and BatchAddSessionQuestionAnswersHandlerTests covers the batched answer write behind the organizer feedback screens (.../Sessions/UseCases/BatchAddSessionQuestionAnswers/BatchAddSessionQuestionAnswersHandlerTests.cs:24). The biggest application suite in ADC; fast unit tests with mocked repositories/services.196The command/query handlers for the Conference controllers, validators, navigation populators, the Sessionize import orchestrator + sync strategies (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionize/RefreshFromSessionizeHandlerTests.cs:17), and the event/session live-window validation served to the live layer over gRPC (.../Events/EventLiveValidationServiceTests.cs:17, alongside GetPublicSessionFilterHandler and its cross-source filter query, ADR-018). It carries the durable AI-scoring pass (ADR-114) in three files. ScoreEventSessionsInternalCommandHandlerTests pins the handler's contract with the framework's at-least-once processor: it takes a per-event claim and skips rather than waits when another replica holds it (each pass is one paid Anthropic call per session), completes the row on a business refusal because replaying it would only pay for the same refusal again, lets a thrown fault propagate so the framework's backoff and attempt ceiling own the retry, and releases the claim on the way out (.../Sessions/UseCases/DecisionSupport/ScoreEventSessionsInternalCommandHandlerTests.cs:17, ScoreEventSessionsInternalCommandHandler). SessionScoringRunnerTests covers the pass itself, including the resume rule that skips every session scored since the command's request instant, which is what makes a queued duplicate or a retried attempt cheap (.../Sessions/UseCases/DecisionSupport/SessionScoringRunnerTests.cs:491, SessionScoringRunner). ScoreEventSessionsInternalCommandMarkerTests pins the opt-in pipeline markers on ScoreEventSessionsInternalCommand: the scoring feature flag, the session-selection capability, and a 12-minute execution budget that must stay below the handler's 15-minute claim time-to-live so the claim cannot expire mid-pass (.../Sessions/UseCases/DecisionSupport/ScoreEventSessionsInternalCommandMarkerTests.cs:51, MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/ScoreEventSessionsInternalCommandHandler.cs:56). It also carries the sessions-by-speaker filter handler, whose specification resolves the SessionSpeaker join down to an engine-portable ID-list criteria so the speaker pages and speaker dashboard filter server-side instead of pulling the whole catalog (.../Sessions/UseCases/GetSessionsBySpeakerFilter/GetSessionsBySpeakerFilterHandlerTests.cs:16, GetSessionsBySpeakerFilterHandler, §12). A family of public filter handlers (session, session-speaker, session-category-item, event-speaker, speaker, speaker-category-item, room, sponsor, and now activity) all defend the same anonymous-endpoint leak: a junction filter must select only rows whose parent is publicly visible, so a hidden session never leaks its existence through the speakers assigned to it (.../Sessions/UseCases/GetPublicSessionSpeakerFilter/GetPublicSessionSpeakerFilterHandlerTests.cs:18, and its activity twin at .../Activities/UseCases/GetPublicActivityFilter/GetPublicActivityFilterHandlerTests.cs:18, BR-49, §11). ExportEventCalendarHandler covers the whole-schedule .ics endpoint, which is anonymous, so unpublished and unknown events must collapse to the same NotFound (no existence oracle) and a legacy row carrying an unresolvable time zone must degrade rather than fault the request (.../Sessions/UseCases/ExportCalendar/ExportEventCalendarHandlerTests.cs:16). The sponsor and activity use cases are deliberately plain: create/update handlers assert the mapper-failure path, the repository add and the save, and nothing more, because the interesting behavior lives in the aggregate and in the public filter (.../Sponsors/UseCases/CreateSponsorHandlerTests.cs:14, .../Activities/UseCases/CreateActivityHandlerTests.cs:13). Its newest files are convention guards rather than behavior: SessionRoomFilterTests pins the public schedule's Room filter at the Application layer, since Session.RoomId is a real nullable column riding the generic filter pipeline with no dedicated handler, so the feature would break silently if the property were renamed or retyped (an unknown filter key is a validation failure and a mistyped one is ignored at apply time, .../Sessions/SessionRoomFilterTests.cs:15); ConferenceCrudRegistrationTests sweeps the module's CRUD registrations so a new aggregate cannot ship half-wired (.../ConferenceCrudRegistrationTests.cs:32); and BatchAddSessionQuestionAnswersHandlerTests covers the batched answer write behind the organizer feedback screens (.../Sessions/UseCases/BatchAddSessionQuestionAnswers/BatchAddSessionQuestionAnswersHandlerTests.cs:24). Its newest files pin the BR-71 category delete cascade, which only reaches loaded items, so the mock returns the category only when the handler declares the include (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Categories/UseCases/DeleteConferenceCategoryHandlerTests.cs:17); the session-asset update's ownership re-check through the real access service (.../SessionAssets/UseCases/UpdateSessionAssetHandlerTests.cs:21); the erasure of a deleted account's feedback answers (.../Users/IntegrationEventHandlers/UserDeletedFeedbackHandlerTests.cs:16); and a speaker create that mints a fresh Guid rather than persisting Guid.Empty (.../Speakers/UseCases/Create/SpeakerCreateRequestMapperTests.cs:7). The biggest application suite in ADC; fast unit tests with mocked repositories/services.
    MMCA.ADC.Conference.Infrastructure.Tests
    MMCA.ADC.Conference.UI.Tests66Conference Blazor pages and components: the public event/session/speaker/sponsor/activity detail + filtered list pages, the management CRUD forms and management-route authorization, the organizer feedback dashboards, the speaker dashboard, the session-selection dashboard with its AI-score and speaker-overlap views (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.UI.Tests/Pages/Sessions/Selection/SessionSelectionAiScoresTests.cs:15), the home countdown (.../Pages/Home/ADCHomeTests.cs:20), and the share/QR/add-to-calendar buttons. One cluster is all stale-state guards, the failure class bUnit is uniquely good at catching: SessionDetailRoomCacheTests pins that navigating to a session in a different event refetches the per-event room lookup, since otherwise the page renders the previous event's room names and offers its rooms in the edit picker, while navigating within the same event must reuse the cache (.../Pages/Session/SessionDetailRoomCacheTests.cs:20); SessionSelectionStaleResponseTests covers M48, where the organizer dashboard fires a load per event selection and keeps a fire-and-forget score-polling loop running, and both used to write whatever came back into shared state without checking whether the selection had moved on, so a slow response for the previous event could overwrite the board or paint an error banner over it (a generation counter now supersedes in-flight work on every selection, .../Pages/SessionSelection/SessionSelectionStaleResponseTests.cs:23). The sponsor, activity and speaker-QR pages are the newest: PublicSponsorListTests pins the roster scoping to the current-or-next event, grouping by tier in package order with a sort-then-name order inside a tier, the booth badge for exhibitors, and the sponsorship-packet call to action that must appear when the roster is empty and disappear entirely when no packet URL is configured (.../Pages/Public/PublicSponsorListTests.cs:18); PublicActivityListTests does the same for the activity strip on the public schedule (.../Pages/Public/PublicActivityListTests.cs:16); and SpeakerQrTests pins that with the speaker_id claim the code encodes the absolute public profile URL (an app-relative route is useless to another device's camera) and that without the claim the page says so rather than rendering a code that points nowhere (.../Pages/Speaker/SpeakerQrTests.cs:17, ADR-071). Its newest technique is golden-markup: ComponentsSnapshotTests renders each reused Conference component from parameters alone and diffs its normalized markup against a committed baseline under Snapshots/, normalizing the per-render GUIDs MudBlazor injects so the comparison stays deterministic and OS-independent, and refreshed with UPDATE_SNAPSHOTS=1 after an intentional change (.../Components/ComponentsSnapshotTests.cs:27, §28). Alongside it, EventFilteredListPagePrerenderTests pins the prerender pass of the shared filtered-list page base (.../Pages/Common/EventFilteredListPagePrerenderTests.cs:20) and ClientUrlValidationTests pins the client-side URL validation the share and external-link surfaces depend on (.../Pages/ClientUrlValidationTests.cs:23). Rendered with bUnit (BunitTestBase over the shared BunitComponentTestBase). Component tests.81Conference Blazor pages and components: the public event/session/speaker/sponsor/activity detail + filtered list pages, the management CRUD forms and management-route authorization, the organizer feedback dashboards, the speaker dashboard, the session-selection dashboard with its AI-score and speaker-overlap views (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.UI.Tests/Pages/Sessions/Selection/SessionSelectionAiScoresTests.cs:15), the home countdown (.../Pages/Home/ADCHomeTests.cs:20), and the share/QR/add-to-calendar buttons. One cluster is all stale-state guards, the failure class bUnit is uniquely good at catching: SessionDetailRoomCacheTests pins that navigating to a session in a different event refetches the per-event room lookup, since otherwise the page renders the previous event's room names and offers its rooms in the edit picker, while navigating within the same event must reuse the cache (.../Pages/Session/SessionDetailRoomCacheTests.cs:20); SessionSelectionStaleResponseTests covers M48, where the organizer dashboard fires a load per event selection and keeps a fire-and-forget score-polling loop running, and both used to write whatever came back into shared state without checking whether the selection had moved on, so a slow response for the previous event could overwrite the board or paint an error banner over it (a generation counter now supersedes in-flight work on every selection, .../Pages/SessionSelection/SessionSelectionStaleResponseTests.cs:23). The sponsor, activity and speaker-QR pages are the newest: PublicSponsorListTests pins the roster scoping to the current-or-next event, grouping by tier in package order with a sort-then-name order inside a tier, the booth badge for exhibitors, and the sponsorship-packet call to action that must appear when the roster is empty and disappear entirely when no packet URL is configured (.../Pages/Public/PublicSponsorListTests.cs:18); PublicActivityListTests does the same for the activity strip on the public schedule (.../Pages/Public/PublicActivityListTests.cs:16); and SpeakerQrTests pins that with the speaker_id claim the code encodes the absolute public profile URL (an app-relative route is useless to another device's camera) and that without the claim the page says so rather than rendering a code that points nowhere (.../Pages/Speaker/SpeakerQrTests.cs:17, ADR-071). Its newest technique is golden-markup: ComponentsSnapshotTests renders each reused Conference component from parameters alone and diffs its normalized markup against a committed baseline under Snapshots/, normalizing the per-render GUIDs MudBlazor injects so the comparison stays deterministic and OS-independent, and refreshed with UPDATE_SNAPSHOTS=1 after an intentional change (.../Components/ComponentsSnapshotTests.cs:27, §28). Alongside it, EventFilteredListPagePrerenderTests pins the prerender pass of the shared filtered-list page base (.../Pages/Common/EventFilteredListPagePrerenderTests.cs:20) and ClientUrlValidationTests pins the client-side URL validation the share and external-link surfaces depend on (.../Pages/ClientUrlValidationTests.cs:23). The newest files pin the shared privileged-reader check of the four public pages, where an auth fault degrades to the public audience but a cancellation propagates (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.UI.Tests/Pages/Public/PublicReadAudienceTests.cs:16); the My Schedule view's inline error and retry on a failed bookmark read (.../Pages/Public/Sessions/PublicSessionListMyScheduleTests.cs:21); that only the unfiltered programme overwrites the offline snapshot (.../Services/PublicSessionScheduleServiceTests.cs:15); the organizer session list's current-event default read from the injected TimeProvider at the local-midnight boundary (.../Pages/Sessions/SessionListCurrentEventClockTests.cs:23); and the client-side BR-122 schedule check (.../Pages/Sessions/SessionDetailScheduleTests.cs:25), plus create/edit model and UI-service tests for rooms, sessions, speakers and session assets. Rendered with bUnit (BunitTestBase over the shared BunitComponentTestBase). Component tests.
    MMCA.ADC.Conference.IntegrationTests70Bookmark, feedback, and live-layer (poll / session-question) add/remove/query handlers and validators, including the cross-module ISessionBookmarkValidationService / IBookmarkCountService / IEventLiveValidationService gRPC collaborators (stubbed), the poll-results builder (MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.Application.Tests/LivePolls/Services/LivePollResultsBuilderTests.cs:23), the read-side poll handlers added with the presenter surface (.../LivePolls/UseCases/GetPollResultsHandlerTests.cs:20), the question-upvote domain-event handler (.../SessionQuestions/DomainEventHandlers/SessionQuestionUpvoteChangedHandlerTests.cs:25), and the best-effort ILiveChannelPublisher ingress together with the queue that decouples it from the request path (.../Live/LiveChannelPublishQueueTests.cs:10, LiveChannelPublishQueue). The ADR-072 wave contributes three load-bearing files. PointsAwarder is where anti-farming and redelivery idempotency turn out to be the same rule: an award already present writes nothing, a rule configured to 0 (or an undefined activity) succeeds and writes nothing, an entry keeps the value it was awarded when the configuration later changes, and a save that loses the unique-index race reports success, including when the duplicate wording is buried in an inner exception, while any other save failure propagates (.../Points/Services/PointsAwarderTests.cs:13). CheckInAttendeeHandler pins the scan path: a repeat scan reports the original check-in and writes nothing, a session-scoped scan is filed under the session's own owning event (not the client's event context), an unknown and a malformed credential return the same BadgeNotFound (no probing oracle), and an unpublished event or a missing caller is refused (.../CheckIns/UseCases/CheckInAttendeeHandlerTests.cs:17). GetLeaderboardHandler pins the board as opt-in only (points without an opt-in never appear, and leaving removes you), ordering by total with an ordinal name tie-break, truncation to the configured size where 0 or a negative publishes nothing rather than throwing, and the snapshotted name being what is published (.../Points/UseCases/GetLeaderboardHandlerTests.cs:12). UserDeletedPointsHandlerTests closes the privacy loop: on UserDeleted the entry comes off the board and the published name is erased even for an already-left opt-in, the read deliberately looks past the soft-delete filter and tracks, and a redelivery reaches the same state while writing once (.../Points/IntegrationEventHandlers/UserDeletedPointsHandlerTests.cs:14, §30). Its newest file is the cross-service cache edge: bookmark counts are owned here but served by Conference, in another process whose output cache this side cannot touch directly, so UserSessionBookmarkCacheEvictionHandlerTests pins the one thing that closes that distance, that every bookmark change publishes an eviction request carrying the tag Conference actually registered, and that a broker which is down still cannot fail a bookmark the attendee already saved (.../UserSessionBookmarks/DomainEventHandlers/UserSessionBookmarkCacheEvictionHandlerTests.cs:20, ADR-026). The poll delete's soft-delete cascade only reaches loaded options, so the mock returns the poll only when the handler declares the include (MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.Application.Tests/LivePolls/UseCases/DeleteLivePollHandlerTests.cs:18), and four UserDeleted...HandlerTests pin account erasure of badges, bookmarks, session questions and votes (.../UserSessionBookmarks/IntegrationEventHandlers/UserDeletedBookmarksHandlerTests.cs:14). Unit.
    MMCA.ADC.Engagement.Infrastructure.Tests
    MMCA.ADC.Identity.Shared.Tests2Identity DTOs/requests and mappers (User, roles, LinkedSpeakerId). Unit.3Identity DTOs/requests and mappers (User, roles, LinkedSpeakerId), plus JwtAudienceTests, the fail-fast audience contract the Conference, Engagement and Notification hosts call before AddForwardedJwtBearer: a configured value passes through and a missing one stops startup naming the key (MMCA.ADC/Tests/Modules/Identity/MMCA.ADC.Identity.Shared.Tests/Authorization/JwtAudienceTests.cs:11). Unit.
    MMCA.ADC.Identity.Domain.Tests
    MMCA.ADC.Services.Tests7The only project that tests the service-host gRPC adapters directly rather than through a booted host. Its subject is a single, easily-missed wire contract in the data-subject export named by ADC's private PRIVACY.md §7 (ADR-005 / §30): SQL Server returns DateTimeKind.Unspecified values, and the "O" format omits the Z marker for them, so a timestamp that is genuinely UTC crosses the wire looking like a local time. UserEngagementExportGrpcServiceTests and UserNotificationExportGrpcServiceTests assert the emit leg (every timestamp ends with Z and parses back to the same instant, MMCA.ADC/Tests/Services/MMCA.ADC.Services.Tests/Exports/UserEngagementExportGrpcServiceTests.cs:17); the two ...GrpcAdapterTests assert the parse leg, which has to survive a rolling deploy where new Identity code talks to a Notification replica still emitting the marker-less form, so both wire forms must land as DateTimeKind.Utc on identical ticks (.../Exports/UserNotificationExportServiceGrpcAdapterTests.cs:19). FakeServerCallContext is a minimal ServerCallContext that lets a server implementation be invoked directly: the export services read only the cancellation token, so nothing else has to behave (.../Support/FakeServerCallContext.cs:10). In MMCA.ADC.CI.slnf (MMCA.ADC/MMCA.ADC.CI.slnf:60), so it runs on every PR with no DB. Unit style.17The only project that tests the service-host gRPC adapters directly rather than through a booted host. Its first subject is an easily-missed wire contract in the data-subject export named by ADC's private PRIVACY.md §7 (ADR-005 / §30): SQL Server returns DateTimeKind.Unspecified values, and the "O" format omits the Z marker for them, so a timestamp that is genuinely UTC crosses the wire looking like a local time. UserEngagementExportGrpcServiceTests and UserNotificationExportGrpcServiceTests assert the emit leg (every timestamp ends with Z and parses back to the same instant, MMCA.ADC/Tests/Services/MMCA.ADC.Services.Tests/Exports/UserEngagementExportGrpcServiceTests.cs:17); the two ...GrpcAdapterTests assert the parse leg, which has to survive a rolling deploy where new Identity code talks to a Notification replica still emitting the marker-less form, so both wire forms must land as DateTimeKind.Utc on identical ticks (.../Exports/UserNotificationExportServiceGrpcAdapterTests.cs:19). FakeServerCallContext is a minimal ServerCallContext that lets a server implementation be invoked directly: the export services read only the cancellation token, so nothing else has to behave (.../Support/FakeServerCallContext.cs:10). The rest round-trip each cross-module gRPC server through its client adapter, resolved via the public composition with the typed client mocked (GrpcCalls builds the call a mocked client returns, MMCA.ADC/Tests/Services/MMCA.ADC.Services.Tests/Helpers/GrpcCalls.cs:9): attendee ids keep their order (.../Attendees/AttendeesGrpcServiceTests.cs:23), bookmark counts and bookmark validation survive the wire (.../Bookmarks/BookmarkCountsGrpcServiceTests.cs:17, .../Bookmarks/SessionBookmarksGrpcServiceTests.cs:18), and the adapters turn an RpcException or unmappable wire data into a failure Result rather than an escaping exception (.../Bookmarks/SessionBookmarkValidationServiceGrpcAdapterTests.cs:23, .../Live/EventLiveValidationServiceGrpcAdapterTests.cs:24, .../Live/EventLiveValidationGrpcServiceTests.cs:23). The anonymous live-channel ingress refuses a malformed key or event name or an oversized payload with InvalidArgument before the SignalR publisher (.../Live/LiveChannelGrpcServiceTests.cs:27), its publisher adapter is best-effort and swallows a failed push (.../Live/LiveChannelPublisherGrpcAdapterTests.cs:16), and the Conference warm-up's literal paths are pinned to PagedReadAll.LookupPageUrl so a warmed OutputCache key is one the UI actually requests (.../Warmup/SelfHttpOutputCacheWarmupTaskTests.cs:15). In MMCA.ADC.CI.slnf (MMCA.ADC/MMCA.ADC.CI.slnf:62), so it runs on every PR with no DB. Unit style.
    MMCA.ADC.CrossService.IntegrationTests
    00-inventory.mdPhase 0, every in-scope type (5,034 distinct), mechanically extracted, with file:linePhase 0, every in-scope type (5,234 distinct), mechanically extracted, with file:line
    00-dependency-manifest.md
    7 Persistence & EF Core165177 SQLServerDbContext over abstract ApplicationDbContext, interceptors (incl. the deferred-dispatch record), repositories, engine-aware entity config, data-source routing, conventions (incl. the soft-delete unique-index convention), factories, and the persistence/audit-trail/connection-string/data-source/tenancy settings classes (regrouped here from the module-system chapter on 2026-09-05)
    8 Authentication & Authorization153162 JWT/JWKS dual-fetch, the shared AuthenticationServiceBase<TUser> login/refresh workflow (IAuthUser), current-user/claims, password hashing, cookie sessions, role policies + the permission-based authorization mechanism (registry, [HasPermission]), the RoleValue base, the external-auth-broker contract (ADR-042/043), the JWT/JWKS settings (JwtSettings, JwksSettings, JwtSigningAlgorithm)
    13 gRPC & Inter-Service Contracts67 Typed gRPC clients/servers, interceptors, Result-over-the-wire (ADR-007)
    14 Module System, Composition & Configuration8887 IModule + Kahn-ordered loader, DI composition roots, data-source attributes, options-binding plumbing (the per-subsystem settings classes live with their subsystems since 2026-09-05), plus the host-level infrastructure services: managed file storage + image processing (ADR-045), native-push sender + device registrar (ADR-044), TenantContext, the upcasting/fault integration-event consumers and PeriodicBackgroundService
    15 Common UI Framework153174 Reusable MudBlazor building blocks: data-grid list page base, theme, common pages/services, the Blazor Server UI-host hardening kit (circuit limits + UI rate limiting), i18n culture bootstrap + day/dark ThemeService, user-preference readers/writers, pseudo-localization gate, OAuth UI settings + token storage (Web/WASM), hub-channel subscriptions (ADR-039), the shared DetailPageBase, the email-confirmation page + UI service and the client-config endpoint (extracted from ADC in v1.211.0)
    21 ADC Conference, UI162163 Conference Blazor pages + UI services (incl. the Partner management pages), the single canonical ADC Home page (bound to the Conference DTOs through the UI services), the AI-scoring poll recovery tracker, calendar/QR export UI, OfflineBanner, PresenterLayout on Common theme providers
    22 ADC Engagement Module (Session Bookmarks)194196 The attendee-activity slice of the Engagement bounded context, four capability families: the session-bookmark aggregate, the QR badge check-in surface (organizer scanning, manual fallback, attendee self-service, attendance rollup), the points economy (append-only ledger + opt-in public leaderboard), and the feedback UI; plus use cases, persistence, API/contracts/service, the durable live-channel publish queue (ADR-039), the cross-service user-engagement export slice (gRPC); the live-layer UI services (SessionLiveUIService, LivePollUIService, SessionQuestionUIService, LiveEventService, SessionLookupService) and the live-poll EF configurations sit here too (regrouped from the live-layer chapter on 2026-09-05)
    23 ADC Engagement Live Layer (Real-Time Polls & Session Q&A)8587 Event-wide live polls with voting + moderated per-session Q&A with upvoting, over the ADR-039 hub-channel transport and the cross-service gRPC live-channel adapter (HappeningNow / SessionLive / PresenterView)
    24 ADC Identity Module (Users, Profiles, GDPR Export/Erasure)107108 The Identity bounded context end-to-end (incl. IdentityPermissions, user culture/theme preferences, the ADR-045 user-avatar photo slice end to end, the external-login email verifier + extractable-host Kestrel config; AuthenticationService now extends Common's shared base)
    28 Testing & Quality Infrastructure2,7712,923 All test projects + reusable Testing/Testing.E2E/Testing.UI/Testing.Architecture bases (incl. the handler + decorator-pipeline test bases, the shared production-host/graceful-shutdown bases, the observability-convention fitness base, and the Gallery auth stubs) + architecture-fitness tests + Gallery + the BenchmarkDotNet perf-smoke suite
    diff --git a/sitemap.xml b/sitemap.xml index 7c7b5846..9dee8e19 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -697,202 +697,202 @@ https://ivanball.github.io/docs/onboarding/index.html - 2026-09-26 + 2026-10-02 0.8 https://ivanball.github.io/docs/onboarding/00-dependency-manifest.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/00-group-taxonomy.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/00-inventory.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/00-primer.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-01-result-error-handling.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-02-domain-building-blocks.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-03-querying-specifications.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-04-events-outbox.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-05-cqrs-pipeline.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-06-validation.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-07-persistence-ef-core.html - 2026-09-27 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-08-auth.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-09-caching.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-10-notifications.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-11-navigation-populators.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-12-api-hosting-mapping.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-13-grpc-contracts.html - 2026-09-23 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-14-module-system-composition.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-15-common-ui-framework.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-16-aspire-orchestration.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-17-conference-domain.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-18-conference-application.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-19-conference-infrastructure.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-20-conference-api-grpc.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-21-conference-ui.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-22-engagement-module.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-23-engagement-live-layer.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-24-identity-module.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-25-adc-host-composition.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-26-device-capability-layer.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-27-common-ai-integration.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/group-28-testing-infrastructure.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/devops-aspire.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/devops-cicd.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/devops-iac.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/devops-runbooks.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/devops-testing.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/99-coverage-audit.html - 2026-09-26 + 2026-10-02 0.6 https://ivanball.github.io/docs/onboarding/CONCEPT-MAPS.html - 2026-09-26 + 2026-10-02 0.6